Redis面试题2

本文涉及的产品
云数据库 Redis 版,社区版 2GB
推荐场景:
搭建游戏排行榜
简介: Redis面试题2

这里存在两个严重的问题

  • 锁过期
  • 释放了别人的锁


第1个问题是评估操作共享资源的时间不准确导致的,如果只是一味增大过期时间,只能缓解问题降低出现问题的概率,依旧无法彻底解决问题。原因在于客户端在拿到锁之后,在操作共享资源时,遇到的场景是很复杂的,既然是预估的时间,也只能是大致的计算,不可能覆盖所有导致耗时变长的场景。


第2个问题是释放了别人的锁,原因在于释放锁的操作是无脑操作,并没有检查这把锁的归属,这样解锁不严谨。如何解决呢?


锁被别人给释放了

解决办法是,客户端在加锁时,设置一个只有自己知道的唯一标识进去,例如可以是自己的线程ID,如果是redis实现,就是SET key unique_value EX 10 NX。之后在释放锁时,要先判断这把锁是否归自己持有,只有是自己的才能释放它。


//释放锁 比较unique_value是否相等,避免误释放

if redis.get("key") == unique_value then

   return redis.del("key")


这里释放锁使用的是GET + DEL两条命令,这时又会遇到原子性问题了。

  • 客户端1执行GET,判断锁是自己的
  • 客户端2执行了SET命令,强制获取到锁(虽然发生概念很低,但要严谨考虑锁的安全性)
  • 客户端1执行DEL,却释放了客户端2的锁


由此可见,以上GET + DEL两个命令还是必须原子的执行才行。怎样原子执行两条命令呢?答案是Lua脚本,可以把以上逻辑写成Lua脚本,让Redis执行。因为Redis处理每个请lock_key,ARGV[1]是当前客户端的唯一标识,这两个值都是我们在执行 Lua脚本时作为参数传入的。


//Lua脚本语言,释放锁 比较unique_value是否相等,避免误释放
if redis.call("get",KEYS[1]) == ARGV[1] then
    return redis.call("del",KEYS[1])
else
    return 0
end

最后我们执行以下命令,即可


redis-cli  --eval  unlock.script lock_key , unique_value

这样一路优先下来,整个加锁、解锁流程就更严谨了,先小结一下,基于Redis实现的分布式锁,一个严谨的流程如下:


加锁时要设置过期时间SET lock_key unique_value EX expire_time NX,并且有唯一标识

操作共享资源

释放锁:Lua脚本,先GET判断锁是否归属自己,再DEL释放锁来保证原子性


有了这个严谨的锁模型,我们还需要重新思考之前的那个问题,锁的过期时间不好评估怎么办。

如何确定锁的过期时间

前面提到过,过期时间如果评估得不好,这个锁就会有提前过期的风险,一种妥协的解决方案是,尽量冗余过期时间,降低锁提前过期的概率,但这个方案并不能完美解决问题。是否可以设置这样的方案,加锁时,先设置一个预估的过期时间,然后开启一个守护线程,定时去检测这个锁的失效时间,如果锁快要过期了,操作共享资源还未完成,那么就自动对锁进行续期,重新设置过期时间。


这是一种比较好的方案,已经有一个库把这些工作都封装好了,它就是Redisson。Redisson是一个Java语言实现的Redis SDK客户端,在使用分布式锁时,它就采用了自动续期的方案来避免锁过期,这个守护线程我们一般叫它看门狗线程。这个SDK提供的API非常友好,它可以像操作本地锁一样操作分布式锁。客户端一旦加锁成功,就会启动一个watch dog看门狗线程,它是一个后台线程,会每隔一段时间(这段时间的长度与设置的锁的过期时间有关)检查一下,如果检查时客户端还持有锁key(也就是说还在操作共享资源),那么就会延长锁key的生存时间。


那如果客户端在加锁成功后就宕机了呢?宕机了那么看门狗任务就不存在了,也就无法为锁续期了,锁到期自动失效。

Redis的部署方式对锁的影响

上面讨论的情况,都是锁在单个Redis 实例中可能产生的问题,并没有涉及到Redis的部署架构细节。

  • Redis发展到现在,几种常见的部署架构有:
  • 单机模式;
  • 主从模式;
  • 哨兵(sentinel)模式;
  • 集群模式;


我们使用Redis时,一般会采用主从集群+哨兵的模式部署,哨兵的作用就是监测redis节点的运行状态。普通的主从模式,当master崩溃时,需要手动切换让slave成为master,使用主从+哨兵结合的好处在于,当master异常宕机时,哨兵可以实现故障自动切换,把slave提升为新的master,继续提供服务,以此保证可用性。那么当主从发生切换时,分布式锁依旧安全吗?


想像这样的场景:

  • 客户端1在master上执行SET命令,加锁成功
  • 此时,master异常宕机,SET命令还未同步到slave上(主从复制是异步的)
  • 哨兵将slave提升为新的master,但这个锁在新的master上丢失了,导致客户端2来加锁成功了,两个客户端共同操作共享资源


可见,当引入Redis副本后,分布式锁还是可能受到影响。即使Redis通过sentinel保证高可用,如果这个master节点由于某些原因发生了主从切换,那么就会出现锁丢失的情况。

集群模式+Redlock实现高可靠的分布式锁

为了避免Redis实例故障而导致的锁无法工作的问题,Redis的开发者 Antirez提出了分布式锁算法Redlock。Redlock算法的基本思路,是让客户端和多个独立的Redis实例依次请求加锁,如果客户端能够和半数以上的实例成功地完成加锁操作,那么我们就认为,客户端成功地获得分布式锁了,否则加锁失败。这样一来,即使有单个Redis实例发生故障,因为锁变量在其它实例上也有保存,所以,客户端仍然可以正常地进行锁操作,锁变量并不会丢失。


来具体看下Redlock算法的执行步骤。Redlock算法的实现要求Redis采用集群部署模式,无哨兵节点,需要有N个独立的Redis实例(官方推荐至少5个实例)。接下来,我们可以分成3步来完成加锁操作。


第一步是,客户端获取当前时间。


第二步是,客户端按顺序依次向N个Redis实例执行加锁操作。


这里的加锁操作和在单实例上执行的加锁操作一样,使用SET命令,带上NX、EX/PX选项,以及带上客户端的唯一标识。当然,如果某个Redis实例发生故障了,为了保证在这种情况下,Redlock算法能够继续运行,我们需要给加锁操作设置一个超时时间。如果客户端在和一个Redis实例请求加锁时,一直到超时都没有成功,那么此时,客户端会和下一个Redis实例继续请求加锁。加锁操作的超时时间需要远远地小于锁的有效时间,一般也就是设置为几十毫秒。


第三步是,一旦客户端完成了和所有Redis实例的加锁操作,客户端就要计算整个加锁过程的总耗时。


客户端只有在满足两个条件时,才能认为是加锁成功,条件一是客户端从超过半数(大于等于 N/2+1)的Redis实例上成功获取到了锁;条件二是客户端获取锁的总耗时没有超过锁的有效时间。


为什么大多数实例加锁成功才能算成功呢?

多个Redis实例一起来用,其实就组成了一个分布式系统。在分布式系统中总会出现异常节点,所以在谈论分布式系统时,需要考虑异常节点达到多少个,也依旧不影响整个系统的正确运行。这是一个分布式系统的容错问题,这个问题的结论是:如果只存在故障节点,只要大多数节点正常,那么整个系统依旧可以提供正确服务。


在满足了这两个条件后,我们需要重新计算这把锁的有效时间,计算的结果是锁的最初有效时间减去客户端为获取锁的总耗时。如果锁的有效时间已经来不及完成共享数据的操作了,我们可以释放锁,以免出现还没完成共享资源操作,锁就过期了的情况。


当然,如果客户端在和所有实例执行完加锁操作后,没能同时满足这两个条件,那么,客户端就要向所有Redis节点发起释放锁的操作。


为什么释放锁,要操作所有的节点呢,不能只操作那些加锁成功的节点吗?

因为在某一个Redis节点加锁时,可能因为网络原因导致加锁失败,例如一个客户端在一个Redis实例上加锁成功,但在读取响应结果时由于网络问题导致读取失败,那这把锁其实已经在Redis上加锁成功了。所以释放锁时,不管之前有没有加锁成功,需要释放所有节点上的锁以保证清理节点上的残留的锁。


在Redlock算法中,释放锁的操作和在单实例上释放锁的操作一样,只要执行释放锁的 Lua脚本就可以了。这样一来,只要N个Redis实例中的半数以上实例能正常工作,就能保证分布式锁的正常工作了。所以,在实际的业务应用中,如果你想要提升分布式锁的可靠性,就可以通过Redlock算法来实现。


什么是 RedLock

Redis 官方站提出了一种权威的基于 Redis 实现分布式锁的方式名叫Redlock,此种方式比原先的单节点的方法更安全。它可以保证以下特性:


互斥性:在任何时候,只能有一个客户端能够持有锁;

避免死锁:当客户端拿到锁后,即使发生了网络分区或者客户端宕机,也不会发生死锁;(利用key的存活时间)

容错性:只要多数节点的redis实例正常运行,就能够对外提供服务,加锁或者释放锁;


RedLock算法

假设有N个redis的master节点,这些节点是相互独立的(不需要主从或者其他协调的系统)。N推荐为奇数

客户端在获取锁时,需要做以下操作


获取当前时间戳,以微妙为单位(1秒(s)=103毫秒(ms)。1毫秒等于103微秒(μs))

使用相同的lockName和lockValue,尝试从N个节点获取锁。在获取锁时,要求等待获取锁的时间远小于锁的释放时间,如锁的lease_time为10秒,那么wait_time应该为5-50毫秒;避免因为redis实例挂掉,客户端需要等待更长的时间才能返回,即需要让客户端能够fast_fail;如果一个redis实例不可用,那么需要继续从下个redis实例获取锁

当从N个节点获取锁结束后,如果客户端能够从多数节点(N/2 + 1)中成功获取锁,且获取锁的时间小于失效时间,那么可认为,客户端成功获得了锁。(获取锁的时间=当前时间戳 - 第一步获取的时间戳,即客户端获取锁的第一步就是获取当前时间戳,以微妙为单位)

客户端成功获得锁后,那么锁的实际有效时间 = 设置锁的有效时间 - 获取锁的时间。

客户端获取锁失败后,N个节点的redis实例都会释放锁,即使未能加锁成功。


为什么N推荐为奇数呢?

原因1:本着最大容错的情况下,占用服务资源最少的原则,2N+1和2N+2的容灾能力是一样的,所以采用2N+1;比如,5台服务器允许2台宕机,容错性为2,6台服务器也只能允许2台宕机,容错性也是2,因为要求超过半数节点存活才OK。

原因2:假设有6个redis节点,client1和client2同时向redis实例获取同一个锁资源,那么可能发生的结果是——client1获得了3把锁,client2获得了3把锁,由于都没有超过半数,那么client1和client2获取锁都失败,对于奇数节点是不会存在这个问题。


使用缓存常见的问题有哪些?

缓存雪崩

缓存雪崩表示在某一时间段,缓存中的大量热点Key在一瞬间失效,导致请求全部走数据库,有可能搞垮数据库,使整个服务瘫痪。

使缓存集中失效的原因

1、redis服务器挂掉了。

2、对缓存数据设置了相同的过期时间,导致某时间段内缓存集中失效。

缓存雪崩解决方案

针对原因1,可以实现redis的高可用,Redis Cluster 或者 Redis Sentinel(哨兵) 等方案。

将缓存失效时间分散开,比如每个key的过期时间是随机,防止同一时间大量数据过期现象发生,这样不会出现同一时间全部请求都落在数据库层,如果缓存数据库是分布式部署,将热点数据均匀分布在不同Redis和数据库中,有效分担压力

简单粗暴,让Redis数据永不过期(如果业务准许,比如不用更新的名单类)。当然,如果业务数据准许的情况下可以,比如中奖名单用户,每期用户开奖后,名单不可能会变了,无需更新。

使用双缓存策略,设置两个缓存,原始缓存和备用缓存,原始缓存失效时,访问备用缓存,备用缓存失效时间设置长点。

一般并发量不是特别多的时候,使用最多的解决方案是加锁排队(加锁可以让着失效的缓存每次只有一个可以调用数据库,从而减轻了数据库的压力防止它崩溃)


缓存穿透

缓存穿透是指缓存和数据库中都没有的数据,导致所有的请求都落到数据库上, 造成数据库短时间内承受大量请求而崩掉。注意是查大量缓存和数据库中没有的数据

解决方案


接口层增加校验,如用户鉴权校验,id做基础校验,id<=0的直接拦截;

从缓存取不到的数据,在数据库中也没有取到,这时也可以将key- value对写为key-null,缓存有效时间可以设置短点,如30秒(设置太长会导致正常情况也没法使用)。这样可以防止攻击用户反复用同一个id暴力攻击

采用布隆过滤器,一个一定不存在的数据会被布隆过滤器拦截掉,从而避免了对底层存储系统的查询压力


缓存击穿

缓存击穿是指某些热点缓存刚好过期,这时大量的请求过来(请求热点数据),由于读缓存没读到数据(因为刚好过期),然后这大量的请求直接去数据库去取数据,引起数据库压力瞬间增大,造成过大压力。和缓存雪崩不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。


解决方案


加互斥锁。在并发的多个请求中,只有第一个请求线程能拿到锁并执行数据库查询操作,其他的线程拿不到锁就阻塞等着,等到第一个线程将数据写入缓存后,直接走缓存。如果是分布式应用就需要使用分布式锁

热点数据不过期。直接将缓存设置为不过期,然后由定时任务去异步更新缓存。使用时需要考虑业务能接受数据不一致的时间,因为其余线程(非构建缓存的线程)可能访问的是老数据,对于不追求强一致性的系统是可以接受的。


缓存预热

缓存预热就是系统上线后,将相关的缓存数据直接加载到缓存系统。这样就可以避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题!用户直接查询事先被预热的缓存数据!有个问题就是缓存中的数据可能不是最新的数据,缓存预热在秒杀商品中有很明显的体现,把需要秒杀的商品信息在秒杀前放到缓存中去,然后秒杀的时候大量的请求就直接从缓存中读,从而避免数据库压力暴增导致数据库崩溃


解决方案


直接写个缓存刷新页面,上线时手工操作一下;

数据量不大,可以在项目启动的时候自动进行加载;

定时刷新缓存;


缓存降级

当访问量剧增、服务出现问题(如响应时间慢或不响应)或非核心服务影响到核心流程的性能时,仍然需要保证服务还是可用的,即使是有损服务。系统可以根据一些关键数据进行自动降级,也可以配置开关实现人工降级。


服务降级的目的,是为了防止Redis服务故障,导致数据库跟着一起发生雪崩问题。因此,对于不重要的缓存数据,可以采取服务降级策略,例如一个比较常见的做法就是,Redis出现问题,不去数据库查询,而是直接返回默认值给用户。缓存降级的最终目的是保证核心服务可用,即使是有损的。而且有些服务是无法降级的(如加入购物车、结算)。


在进行降级之前要对系统进行梳理,看看系统是不是可以丢卒保帅;从而梳理出哪些必须誓死保护,哪些可降级;


比如可以参考日志级别设置预案

1.一般:比如有些服务偶尔因为网络抖动或者服务正在上线而超时,可以自动降级;

2.警告:有些服务在一段时间内成功率有波动(如在95~100%之间), 可以自动降级或人工降级,并发送告警;

3.错误:比如可用率低于90%,或者数据库连接池被打爆了,或者访问量突然猛增到系统能承受的最大阀值,此时可以根据情况自动降级或者人工降级;

4.严重错误:比如因为特殊原因数据错误了,此时需要紧急人工降级。


热点数据和冷数据

热点数据

热点数据,就是信息修改频率不高,读取频率非常高。一般数据更新前至少读取两次,缓存才有意义。这个是最基本的策略,如果缓存还没有起作用就失效了,那就没有太大价值了。比如我们的某IM产品,生日祝福模块,当天的寿星列表,缓存 以后可能读取数十万次。再举个例子,某导航产品,我们将导航信息,缓存以后可能读取数百万次。


冷数据


对于冷数据而言,大部分数据可能还没有再次访问到就已经被挤出内存,不仅占用内存,而且价值不大。


那存不存在,修改频率很高,但是又不得不考虑缓存的场景呢?


有!


比如,这个读取接口对数据库的压力很大,但是又是热点数据,这个时候就需要考虑通过缓存手段,减少数据库的压力,比如我们的某助手产品的,点赞数,收藏数,分享数等是非常典型的热点数据,但是又不断变化,此时就需要将数据同步保存到Redis缓存,减少数据库压力。


缓存热点key

缓存中的一个Key(比如一个促销商品),在某个时间点过期的时候,恰好在这个 时间点对这个Key有大量的并发请求过来,这些请求发现缓存过期一般都会从后端DB加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端DB压垮。这个我感觉跟缓存击穿一个样


解决方案


对缓存查询加锁,如果KEY不存在,就加锁,然后查DB入缓存,然后解锁;其他进程如果发现有锁就等待,然后等解锁后返回数据或者进入DB查询


Redis支持的Java客户端都有哪些?官方推荐用哪个?

Redisson、Jedis、lettuce等等,官方推荐使用Redisson。


Redis和Redisson有什么关系?

Redisson是一个在Redis的基础上实现的一个分布式协调Redis客服端,它不仅提供了一系列的分布式的Java常用对象,其中包括(Set,SortedSet, Map, List, Queue, BlockingQueue, Deque, Semaphore, Lock, AtomicLong,Publish / Subscribe, Bloom filter, Spring cache, Executor service, ), 还提供了许多分布式服务。Redisson提供了使用Redis的最简单和最便捷的方法。Redisson的宗旨是促进使用者对Redis的关注分离(Separation of Concern),从而让使用者能够将精力更集中地放在处理业务逻辑上。


Jedis与Redisson对比有什么优缺点?

Jedis是Redis的Java实现的客户端,其API提供了比较全面的Redis命令的支持;Redisson实现了分布式和可扩展的Java数据结构,和Jedis相比,功能较为简单,不支持字符串操作,不支持排序、事务、管道、分区等Redis特性。Redisson的宗旨是促进使用者对Redis的关注分离,从而让使用者能够将精力更集中地放在处理业务逻辑上。

Jedis使用阻塞的I/O,且其方法调用都是同步的,程序流程要等到sockets处理完I/O才能执行,不支持异步,Jedis客户端实例不是线程安全的,所以需要通过连接池来使用Jedis。Redisson使用非阻塞的I/O和基于Netty框架的事件驱动的通信层,其方法调用时异步的。Redisson的API是线程安全的,所以操作单个Redisson连接来完成各种操作。

Redisson在Redis的基础上实现了java缓存标准规范;Redisson还提供了第三方框架整合,例如Spring Session回话管理器的实现。


Redis与Memcached的区别

应该说Memcached和Redis都能很好的满足解决我们的问题,它们性能都很高,总的来说,可以把Redis理解为是对Memcached的拓展,是更加重量级的实现,提供了更多更强大的功能。


Redis与Memcached的区别?

Redis和Memcache都是将数据存放在内存中,都是内存数据库。不过memcache还可用于缓存其他东西,例如图片、视频等等。

存储方式: memecache 把数据全部存在内存之中,断电后会挂掉,数据不能超过内存大小。 redis有部份存在硬盘上,这样能保证数据的持久性,支持数据的持久化(注:有快照和AOF日志两种持久化方式,在实际应用的时候,要特别注意配置文件快照参数,要不就很有可能服务器频繁满载做dump)

数据支持类型: redis在数据支持上要比memecache多的多,Redis不仅仅支持简单的k/v类型的数据,同时还提供list,set,hash等数据结构的存储。

可靠性上: MemCached不支持数据持久化,断电或重启后数据消失,但其稳定性是有保证的。Redis支持数据持久化和数据恢复,允许单点故障,Redis数据丢失后可以通过aof恢复,但是同时也会付出性能的代价。

应用场景: Memcached:动态系统中减轻数据库负载,提升性能;做缓存,适合多读少写,大数据量的情况(如人人网大量查询用户信息、好友信息、文章信息等)。 Redis:适用于对读写效率要求都很高,数据处理业务复杂和对安全性要求较高的系统(如新浪微博的计数和微博发布部分系统,对数据安全性、读写要求都很高)。

内存空间和数据量大小: MemCached可以修改最大内存,采用LRU算法。Redis增加了VM的特性,突破了物理内存的限制

个人总结一下,有持久化需求或者对数据结构和处理有高级要求的应用,选择redis,其他简单的key/value存储,选择memcache。


注意事项


Memcached单个key-value大小有限,一个value最大只支持1MB,而Redis最大支持512MB。

Memcached只是个内存缓存,对可靠性无要求;而Redis更倾向于内存数据库,因此对可靠性方面要求比较高。

从本质上讲,Memcached只是一个单一key-value内存Cache;而Redis则是一个数据结构内存数据库,支持五种数据类型,因此Redis除单纯缓存作用外,还可以处理一些简单的逻辑运算,Redis不仅可以缓存,而且还可以作为数据库用。

新版本(3.0)的Redis是指集群分布式,也就是说集群本身均衡客户端请求,各个节点可以交流,可拓展行、可维护性更强大。


如何保证缓存与数据库双写时的数据一致性?

你只要用缓存,就可能会涉及到缓存与数据库双存储双写,你只要是双写,就一定会有数据一致性的问题,那么你如何解决一致性问题?


一般来说,就是如果你的系统不是严格要求缓存+数据库必须一致性的话,缓存 可以稍微的跟数据库偶尔有不一致的情况,最好不要做这个方案,读请求和写请求串行化,串到一个内存队列里去,这样就可以保证一定不会出现不一致的情况


串行化之后,就会导致系统的吞吐量会大幅度的降低,用比正常情况下多几倍的机器去支撑线上的一个请求。


还有一种方式就是可能会暂时产生不一致的情况,但是发生的几率特别小,就是先更新数据库,然后再删除缓存。

1686406671323.png


Redis常见性能问题和解决方案?

Redis常见性能问题和解决方案?


Redis官方为什么不提供Windows版本?

因为目前Linux版本已经相当稳定,而且用户量很大,无需开发windows版本, 反而会带来兼容性等问题。


因为redis 是单线程高性能的。


所以redis需要单线程轮询。


操作系统机制的轮询是不太一样的。


简而言之 linxu轮询用epoll,


window 用selector


但是性能上来说 epoll是高于selector 的。


所以redis推荐使用linux版本。


一个字符串类型的值能存储最大容量是多少?还知道其他的最大容量吗?

一个字符串类型的值能存储最大容量是多少?

512M

扩展补充

redis key 允许的最大key大小为512 MB,空字符串也是有效的键。key最好越短越好

Redis的值的存储容量


String类型:一个String类型的value最大可以存储512M

List类型:list的元素个数最多为2^32-1个,也就是4294967295个。

Set类型:元素个数最多为2^32-1个,也就是4294967295个。

Hash类型:键值对个数最多为2^32-1个,也就是4294967295个。

Sorted set类型(zset):跟Sets类型相似。


Redis如何做大量数据插入?

方式一:使用Luke协议,通过redis-cli –pipe(派 p)发送数据到服务器

使用正常模式的Redis 客户端执行大量数据插入不是一个好主意:因为一个个的插入会有大量的时间浪费在每一个命令往返时间上。使用管道(pipelining)是一种可行的办法,但是在大量插入数据的同时又需要执行其他新命令时,这时读取数据的同时需要确保请可能快的的写入数据。


只有一小部分的客户端支持非阻塞输入/输出(non-blocking I/O),并且并不是所有客户端能以最大限度的提高吞吐量的高效的方式来分析答复。


例如,如果我们需要生成一个10亿的`keyN -> ValueN’的大数据集,我们会创建一个如下的redis命令集的文件:


SET Key0 Value0
SET Key1 Value1
...
SET KeyN ValueN

从Redis 2.6开始redis-cli支持一种新的被称之为pipe mode的新模式用于执行大量数据插入工作。

cat data.txt | redis-cli --pipe
All data transferred. Waiting for the last reply...
Last reply received from server.
errors: 0, replies: 1000000


首先生成Redis协议

它会非常简单的生成和解析Redis协议,Redis协议文档请参考Redis协议说明。 但是为了生成大量数据插入的目标,你需要了解每一个细节协议,每个命令会用如下方式表示:

*<args><cr><lf>
$<len><cr><lf>
<arg0><cr><lf>
<arg1><cr><lf>
...
<argN><cr><lf>


这里的是”\r”(或者是ASCII的13)、是”\n”(或者是ASCII的10)。

例如:命令SET key value协议格式如下:

*3<cr><lf>
$3<cr><lf>
SET<cr><lf>
$3<cr><lf>
key<cr><lf>
$5<cr><lf>
value<cr><lf>
"*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$5\r\nvalue\r\n"


pipe mode的工作原理是什么?


redis-cli –pipe试着尽可能快的发送数据到服务器。

读取数据的同时,解析它。

一旦没有更多的数据输入,它就会发送一个特殊的ECHO命令,后面跟着20个随机的字符。我们相信可以通过匹配回复相同的20个字符是同一个命令的行为。

一旦这个特殊命令发出,收到的答复就开始匹配这20个字符,当匹配时,就可以成功退出了。

同时,在分析回复的时候,我们会采用计数器的方法计数,以便在最后能够告诉我们大量插入数据的数据量。


方案二:采用Jedis的父类中的pipelined()方法获取管道

我们可以采用Jedis的父类中的pipelined()方法获取管道,它可以实现一次性发送多条命令并一次性返回结果,这样就大量的减少了客户端与Redis的通信次数,可以有效的提高程序效率(但是,因为Redis要一次性返回所有结果,它会把这些结果都缓存起来,因此命令越多,缓存消耗的内存也会越大,具体还要视情况而定).此外Pipeline的原理是队列(先进先出),这样也保证了数据的顺序性。


public static void main(String[] args) throws Exception {
        Jedis jedis = new Jedis("127.0.0.1", 6474);
        Pipeline p = jedis.pipelined();
        p.setex("key_a", 120, "11111");
        p.setex("key_b", 120, "2222");
        p.sync();
        if (jedis != null && jedis.isConnected()) {
            jedis.close();
        }
    }

方案三:使用RedisTemplate批量保存数据

public void saveDataToRedis(Map<String, String> map) {
        redisTemplate.executePipelined(new RedisCallback<String>() {
            @Override
            public String doInRedis(RedisConnection connection) throws DataAccessException {
                map.forEach((key, value) -> connection.set(redisTemplate.getKeySerializer().serialize(key), redisTemplate.getValueSerializer().serialize(value)));
                return null;
            }
        });
    }


假如Redis里面有1亿个key,其中有10w个key是以某个固定的已知的前缀开头的,怎样将它们全部找出来?

使用keys指令可以扫出指定模式的key列表。

对方接着追问:如果这个redis正在给线上的业务提供服务,那使用keys指令会有什么问题?

这个时候你要回答redis关键的一个特性:redis的单线程的。keys指令会导致线程阻塞一段时间,线上服务会停顿,直到指令执行完毕,服务才能恢复。这个时候可以使用scan指令,scan指令可以无阻塞的提取出指定模式的key列表,但是会有一定的重复概率,在客户端做一次去重就可以了,但是整体所花费的时间会比直接用keys指令长。


Keys 命令

Keys 命令用于查找所有符合给定模式 pattern 的 key

redis 127.0.0.1:6379> KEYS pattern


举个例子

redis 127.0.0.1:6379> SET runoob1 redis 
OK 
redis 127.0.0.1:6379> SET runoob2 mysql 
OK 
redis 127.0.0.1:6379> SET run3 mongodb 
OK
redis 127.0.0.1:6379> KEYS runoob* 
    1) "runoob1" 
    2) "runoob2"
redis 127.0.0.1:6379> KEYS * 
    1) "runoob3" 
    2) "runoob1" 
    3) "runoob2"


使用Redis做过异步队列吗,是如何实现的

方式一:生产者消费者模式

使用list类型保存数据信息,rpush生产消息,lpop消费消息,当lpop没有消息消费的时候(LPOP命令没有任何消息返回时,说明队列中的消息已经被消费完毕,且RPUSH还没有及时生产新消息),可以sleep一段时间,然后再检查有没有信息,如果不想sleep的话,可以使用blpop, 在没有信息的时候,会一直阻塞,直到信息的到来。


RPUSH key value //在列表的尾部插入数据

LPOP key //从头部取出数据

BLPOP key timeout //阻塞直到队列有消息或者超时,但是只能提供给一个消费着消费

可不可以不用 sleep 呢?


用 blpop/brpop 替代前面的 lpop/rpop,list 还有个指令叫 blpop阻塞读在队列没有消息的时候会一直阻塞,直到信息的到来,一旦数据到来,则立刻醒过来。消息的延迟几乎为零。


能不能生产一次消费多次呢?

使用 pub/sub 主题订阅者模式,可以实现1:N 的消息队列。

方式二:发布订阅者模式

使用pub/sub主题订阅者模式,可以实现1:N的消息队列。


redis可以通过pub/sub主题订阅模式实现一个生产者,多个消费者,当然也存在一定的缺点,该模式无法保证消息一定传达,无法保证中途会不会消失,对于发布者来说事件是即发即失的,如果某个消费者在某个生产者发送消息的时候下线,重新上线是无法接收到消息的,遇到这种问题就要使用专门的消息队列进行如理,如kafka、RabbitMQ等。简单说,在消费者下线的情况下,生产的消息会丢失。


pub/sub 有什么缺点?

在消费者下线的情况下,生产的消息会丢失,得使用专业的消息队列如 RabbitMQ等。Redis5.0 新增了 Stream 数据结构,这个功能给 Redis 带来了持久化消息队列。


Redis如何实现延时队列

前言

在我们日常生活中,我们可以发现:


在淘宝、京东等购物平台上下单,超过一定时间未付款,订单会自动取消。

打车的时候,在规定时间没有车主接单,平台会取消你的单并提醒你暂时没有车主接单。

点外卖的时候,如果商家在10分钟还没接单,就会自动取消订单。

收快递的时候,如果我们没有点确认收货,在一段时间后程序会自动完成订单。

在平台完成订单后,如果我们没有在规定时间评论商品,会自动默认买家不评论。

……


当用户发送一个消息请求给服务器后台的时候,服务器会检测这条消息是否需要进行延时处理,如果需要就放入到延时队列中,由延时任务检测器进行检测和处理,对于不需要进行延时处理的任务,服务器会立马对消息进行处理,并把处理后的结果返会给用户。

对于在延时任务检测器内部的话,有查询延迟任务和执行延时任务两个职能,任务检测器会先去延时任务队列进行队列中信息读取,判断当前队列中哪些任务已经时间到期并将已经到期的任务输出执行(设置一个定时任务)。


所谓的延迟队列就是可以延迟执行队列里的任务,例如上面的京东购物下单了但是没有付款,这时这个订单就放到了延时队列里,如果时间一到还没付款那就会取消订单,在时间到之前消费了延时队列里的订单那就是直接付款了


Redis如何实现延时队列?

我们可以使用 zset(sortedset)数据类型,具有去重有序(分数排序)的功能,用设置好的时间戳作为score进行排序(时间戳就是过期的时间,多久过期),使用 zadd score1 value1 ....命令就可以一直往内存中生产消息。使用zrangebyscore来查询当前延时队列中所有任务,找出所有需要进行处理的延时任务,在依次进行操作。也可以通过 zrangebyscore key min max withscores limit 0 1 查询最早的一条任务,来进行消费。


总的来说,你可以通过以下两种方式来实现:

(1)使用zrangebyscore来查询当前延时队列中所有任务,找出所有需要进行处理的延时任务,在依次进行操作。


(2)查找当前最早的一条任务,通过score值来判断任务执行的时候是否大于了当前系统的时候,比如说:最早的任务执行时间在3点,系统时间在2点58分),表示这个应该需要立马被执行啦,时间快到了


Redis来实现延时队列有何优势呢

Redis zset支持高性能的 score 排序。

Redis是在内存上进行操作的,速度非常快。

Redis可以搭建集群,当消息很多时候,我们可以用集群来提高消息处理的速度,提高可用性。

Redis具有持久化机制,当出现故障的时候,可以通过AOF和RDB方式来对数据进行恢复,保证了数据的可靠性



相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
目录
相关文章
|
1月前
|
NoSQL Redis Sentinel
【怒怼大厂面试官】听说你精通Redis?说说Redis哨兵
面试官:Redis哨兵知道吧?知道的,Sentinel哨兵本质是一个运行在特殊模式下的Redis服务器。面试官:嗯然后呢?它的主要作用是通过检测Redis主从服务器的下线状态,选举出新Redis主服务器,也就是故障转移,来保证Redis的高可用性。
78 4
【怒怼大厂面试官】听说你精通Redis?说说Redis哨兵
|
5天前
|
NoSQL MongoDB Redis
Python与NoSQL数据库(MongoDB、Redis等)面试问答
【4月更文挑战第16天】本文探讨了Python与NoSQL数据库(如MongoDB、Redis)在面试中的常见问题,包括连接与操作数据库、错误处理、高级特性和缓存策略。重点介绍了使用`pymongo`和`redis`库进行CRUD操作、异常捕获以及数据一致性管理。通过理解这些问题、易错点及避免策略,并结合代码示例,开发者能在面试中展现其技术实力和实践经验。
99 8
Python与NoSQL数据库(MongoDB、Redis等)面试问答
|
16天前
|
缓存 NoSQL Java
面试官:Redis如何实现延迟任务?
延迟任务是计划任务,用于在未来特定时间执行。常见应用场景包括定时通知、异步处理、缓存管理、计划任务、订单处理、重试机制、提醒和数据采集。Redis虽无内置延迟任务功能,但可通过过期键通知、ZSet或Redisson实现。然而,这种方法精度有限,稳定性较差,适合轻量级需求。Redisson的RDelayedQueue提供更简单的延迟队列实现。
183 9
|
17天前
|
缓存 NoSQL 定位技术
深入探索Redis:面试中必须掌握的关键知识点
深入探索Redis:面试中必须掌握的关键知识点
|
22天前
|
NoSQL Java 测试技术
面试官:如何搭建Redis集群?
**Redis Cluster** 是从 Redis 3.0 开始引入的集群解决方案,它分散数据以减少对单个主节点的依赖,提升读写性能。16384 个槽位分配给节点,客户端通过槽位信息直接路由请求。集群是无代理、去中心化的,多数命令直接由节点处理,保持高性能。通过 `create-cluster` 工具快速搭建集群,但适用于测试环境。在生产环境,需手动配置文件,启动节点,然后使用 `redis-cli --cluster create` 分配槽位和从节点。集群动态添加删除节点、数据重新分片及故障转移涉及复杂操作,包括主从切换和槽位迁移。
31 0
面试官:如何搭建Redis集群?
|
1月前
|
运维 负载均衡 NoSQL
【大厂面试官】知道Redis集群和Redis主从有什么区别吗
集群节点之间的故障检测和Redis主从中的哨兵检测很类似,都是通过PING消息来检测的。。。面试官抓抓脑袋,继续看你的简历…得想想考点你不懂的😰。
67 1
|
1月前
|
NoSQL Redis
【怒怼大厂面试官】听说你精通Redis?Redis数据同步懂吗
面试官:不用慌尽管说,错了也没关系。。。来说说Redis数据同步。是这样的,Redis有一个叫命令传播的概念,如果像面试官说的这种场景,再使用上面我提到的AOF缓冲区就有点浪费内存空间了。所以Redis会将主服务器的这条Del删除命令
62 2
【怒怼大厂面试官】听说你精通Redis?Redis数据同步懂吗
|
1月前
|
NoSQL Redis 数据库
【怒怼大厂面试官】听说你精通Redis?说说Redis持久化
咳咳咳,看你简历写了精通Redis,那我就随便问问。主要有RDB持久化、AOF持久化。是这样,Redis服务器会维护一个AOF重写缓冲区,该缓冲区会在子进程创建新AOF文件期间,记录服务器执行的所有写命令。
55 1
【怒怼大厂面试官】听说你精通Redis?说说Redis持久化
|
2月前
|
缓存 NoSQL Linux
面试必备:一线大厂Redis设计规范与性能优化
本文梳理了在使用Redis过程需要遵循的一些最佳实践,包括针对架构维度的一些深入性能优化的知识,如果面试官问你:"说下在使用Redis的过程中,需要注意哪些规范?",如果你按照本文的思路回答,肯定能让面试官眼前一亮,offer自然就到手了。
52 0
面试必备:一线大厂Redis设计规范与性能优化
|
2月前
|
存储 NoSQL Java
面试官:Redis如何保证高可用?
面试官:Redis如何保证高可用?
76 2
面试官:Redis如何保证高可用?

热门文章

最新文章