面试了多家互联网大厂,总结出以下高频面试题:1、redis过期key的删除策略?定时删除:在设置key过期时间的同时,创建一个timer定时器)。让计时器在密钥过期时立即删除密钥。懒删除:不管key是否过期,但是每次从key空间中获取key时,检查获取的key是否过期,如果过期则删除key;如果未过期,则返回密钥。定期删除:程序每隔一段时间检查一次数据库,并删除其中过期的key。2、redus淘汰策略当redis内存数据集的大小上升到一定规模时,就会执行数据淘汰策略。Redis提供了6种数据淘汰策略:淘汰策略也可以保证所有热点数据都缓存在Redis中。客户端运行新命令并添加新数据。redi检查内存使用情况,如果大于maxmemory的限制,就会按照设定的策略进行回收。注意这里的6个机制,volatile和allkeys指定是从设置了过期时间的数据集中剔除数据,还是从所有数据集中剔除数据。下面的lru、ttl和random是三种不同的淘汰策略,加上一个永不回收的no-envictionpolicy。使用策略规则:如果数据呈现幂律分布,即有的数据访问频率高,有的数据访问频率低,则使用allkeys-lru;如果数据呈现均等分布,即所有数据访问频率相同,则使用allkeys-random3,Redis分布式锁实现Redis的分布式缓存特性使其成为分布式锁的基本实现。一个锁是否上锁可以通过Redis中是否存在某个锁ID来判断。为了保证判断锁是否存在的原子性,保证只有一个线程获取同一个锁,Redis有SETNX(即SETifNoteExists)和GETSET(先写入新值,返回旧值,原子性操作,可以用来区分是否不是第一次操作)操作。(1)关于setnx:设置key的值为value,当且仅当key不存在时,返回值为1。如果给定的key已经存在,setnx什么也不做,返回0。(2)关于set:一般操作exseconds-seconds:设置失效时长,单位秒px-milliseconds:设置失效时长,单位毫秒nx-设置key不存在时的值,成功返回OK,返回(nil)xx-keyonfailure存在时设置值,成功则返回OK,失败则返回(nil)。为了防止主机宕机或网络断开后出现死锁,Redis并没有像ZK那样自然实现,只能靠设置超时时间来避免。所以如果使用setnx实现分布式锁,实现步骤如下:先使用setnx竞争锁,然后使用expire给锁加一个过期时间,防止锁忘记释放。如果进程在setnx之后执行expire之前意外crash或者重启维护,那么就需要将setnx和expire合并为一条指令使用。Redis的setnx命令是在key不存在的时候设置key,但是setnx不能同时完成expiration设置过期时间,无法保证setnx和expire的原子性。我们可以使用set命令来完成setnx和expire操作,这个操作是一个原子操作。示例如下:案例:setname=p7+,失败时间为100s,如果不存在则set1.1.1.1:6379>setnamep7+ex100nxOK1.1.1.1:6379>getname"p7+"1.1.1.1:6379>ttlname(integer)94从上面可以看出,多个命令放在同一个redis连接中,redis是单线程的,所以上面的操作可以看成是setnx和expire的结合,是原子的。4、Redis的Reactor模式Redis基于Reactor模式开发了网络事件处理器。该处理器称为文件事件处理器。它由4部分组成:多套接字、IO多路复用程序、文件事件派发器、事件处理器。因为文件事件调度队列的消费是单线程的,所以Redis被称为单线程模型。5、redis是否支持事务回滚?“不支持回滚操作,Redis支持简单事务模式,只能丢弃,不能回滚。”当Redis执行事务命令时,Redis会在命令进入队列时检测事务命令是否正确,如果不正确则会产生错误。不管前面或后面的命令是否会被事务回滚,什么都不会执行。当命令的格式正确,但由于数据结构的操作出现错误时,命令的执行就会出错,而其前后的命令都会正常执行。这一点和数据库有很大区别,这是需要注意的地方。对于一些重要的操作,我们必须通过程序检查数据的正确性,以保证Redis事务的正确执行,避免数据不一致。Redis之所以维护这么简单的事务,完全是为了保证移动互联网核心问题的性能。6、Redis的事务机制和CASwatch命令提供了redis事务中的CAS行为。为了检测监视的键是否被多个客户端同时更改而导致冲突,将对这些键进行监视。如果在执行exec命令之前至少修改了一个被监视的键??,则整个事情将回滚而不执行任何操作,从而确保原子操作,执行exec将得到空回复。7、Redis和Memcached的区别Redis特点:快,因为数据存在内存中,类似于HashMap,HashMap的优点是查找和运算的时间复杂度是O(1)支持丰富的数据类型,支持字符串,链表、散列、集合和有序集合支持事务,并且操作是原子的。所谓原子性,就是所有的数据变化要么被执行,要么根本不执行。丰富的功能:可用于缓存、消息、key过期时间,过期自动删除。与Memcached的区别在于:存储方式Memecache将所有数据存储在内存中,断电后挂掉,数据不能超过内存大小。Redis的一部分存储在硬盘上,保证了数据的持久化。数据支持类型Memcache支持相对简单的数据类型。Redis具有复杂的数据类型。使用不同的底层模型,与客户端通信的底层实现方式和应用协议也不同。Redis直接自己搭建了VM(VirtualMemory)机制,因为如果一般的系统调用系统函数(比如java调用自己的API),会浪费一定的时间去移动和请求。8、缓存穿透、缓存穿透、缓存雪崩(1)缓存穿透查询不存在的数据,缓存中没有数据,数据库中也没有数据。所以所有的请求都访问数据库,给数据库造成压力。解决方案如下:使用Bloomfilter将所有可能的数据hash成一个大的bitmap,一个一定不存在的数据会被bitmap拦截,从而避免对数据库的查询压力。如果查询到的数据为空,则直接缓存空数据并设置较短的过期时间。这样下次访问的时候就直接返回空值了。(2)缓存击穿缓存击穿是指缓存失效后,瞬间并发客户端查询同一条数据,导致数据库压力过大的情况。业界普遍的做法是使用互斥体。简单的说,当缓存失败时(判断取出来的值是空的),不是立即加载db,而是先使用缓存工具的一些有成功操作返回值的操作(比如Redis的SETNX或者MemcacheADD)设置一个互斥键,当操作成功返回时,然后执行加载数据库操作并重置缓存;否则,重试整个获取缓存方法。代码类似如下:publicStringget(Stringkey){Stringvalue=redis.get(key);if(value==null){//表示缓存值过期//设置超时3分钟,防止下次缓存时deloperationfailsCannotloaddbif(redis.setnx(key_mutex,1,3*60)==1){//表示设置成功value=db.get(key);redis.set(key,value,expire_secs);redis.del(key_mutex);}//此时表示其他线程同时加载了db并设置回缓存,//此时重试获取缓存值else{sleep(50);get(key);//重复Try}}else{returnvalue;}}(3)缓存雪崩雪崩是指缓存中大量热数据过期后大量查询请求涌入,因为大部分数据在Redis层已经过期,请求渗透到数据库层,大批量的请求如洪水般涌入,造成数据库压力,导致查询拥塞甚至宕机。解决方案:分配缓存过期时间。比如每个key的过期时间是随机的,防止大量数据同时过期,这样所有的请求就不会同时落到数据库层。如果缓存数据库采用分布式部署,将热点数据均匀分布在不同的Redis和数据库中,有效分担压力,不要一个人扛。让Redis数据永不过期(如果业务允许)。九、Redis数据倾斜1、bigkeys的存在:业务层避免创建bigkeys,将集合类型的bigkeys拆分成多个小集合,分散保存bigkeys。保存了大量的集合元素(集合类型),会增加本实例的数据量,内存资源消耗也相应增加。bigkey的操作一般会导致实例的IO线程阻塞。如果bigkey的访问量比较大,会影响本实例其他请求的处理速度。2.手动分配槽位不均匀:避免为一个实例分配更多的槽位,并进行槽位迁移。3、现有热点数据:采用不同key前缀的多副本方式。我们对热点数据进行多份拷贝,并为每份数据拷贝的key添加一个随机前缀,这样就不会和其他拷贝数据映射到同一个slot。这样,热点数据的多个副本可以同时服务于请求,同时这些副本的key是不同的,会映射到不同的slot。在给这些slot分配实例的时候,也要注意分配给不同的实例,这样热点数据的访问压力就会分散到不同的实例上。热点数据多副本方式只能用于只读热点数据。如果读写热点数据,不适合使用多副本的方式,因为保证多副本之间的数据一致性会带来额外的开销。10、为什么Redis的单线程模型效率这么高?纯内存操作;核心是基于非阻塞IO多路复用机制;底层用C语言实现。一般来说,用C语言实现的程序离操作系统“距离”越近,执行速度会相对快些;单线程也避免了多线程频繁的上下文切换问题,防止了多线程可能产生的竞争问题。11、Redis是否做异步和延迟队列?一般用一个链表结构作为队列,rpush生产消息,lpop消费消息。当lpop没有消息的时候,需要sleep一会再试。如果对方问,我们可以不用sleep吗?list中还有一个名为blpop的命令。当没有消息时,它会阻塞,直到消息到达。如果对方问是否可以一次生产多次消费?使用pub/sub主题订阅者模式,可以实现1:N的消息队列。使用zset(orderedset),以时间戳为score,消息内容为key调用zadd产生消息,consumer使用zrangebyscore命令获取N秒前轮询的数据进行处理。12、Redis集群策略(1)Redis主从同步Redis的主从结构是一主一从、一主多从或级联结构。复制类型根据是否全量可分为全量同步和增量同步。(2)RedisSentry实现主从复制后,如果要监控master,Redis提供了sentinel机制。sentinel的意思就是监控Redis系统的运行状态,并做出相应的响应。RedisSentinal专注于高可用性。当master宕机时,会自动将slave提升为master,继续提供服务。(3)RedisCluster注重扩展性,在单个redis内存不足时使用Cluster进行分片存储。在redis-cluster架构中,redis-master节点一般用于接收读写,而redis-slave节点一般只用于备份。它与相应的主设备具有相同的插槽集。如果某个redis-master意外失效,则将其对应的slave升级为临时的redis-master。13、Redis同步机制Redis可以使用主从同步和从从同步。第一次同步时,主节点进行bgsave,同时将后续的修改操作记录到内存缓冲区中。完成后将rdb文件完全同步到复制节点,复制节点接收到rdb镜像后加载到内存中。加载完成后,通知主节点将修改后的操作记录同步到复制节点进行重放,完成同步过程。
