1. Redis相关知识点笔记
1.1 Redis 支持的数据类型
键只能是字符串;值可存储为:String List set(集合无序) zset(有序集合) hash(散列表)
1.2 Redis 持久化了解和认识
持久化就是把内存的数据写到磁盘中去,防止服务宕机内存中数据丢失。Redis 有两种持久化机制,分别是RDB和AOF持久化。其中RDB是Redis 默认的方式,按照一定时间将内存中的数据以快照形式保存到硬盘中,对应产生的数据文件是dump.db。可以通过设置配置文件中的save参数来设置快照的周期。数据大时,启动效率要比AOF高。使用单独的子进程进行持久化,主进程不进行任何的io操作,保证了Redis的高性能。而AOF持久化是将Redis执行的每次写命令记录到单独的日志文件中,当重启Redis 会重新将持久化的日志中的文件恢复。如果两种持久化方式都开启,数据恢复时,Redis会优先选择AOF恢复。但是AOF文件相对来说较大,且恢复速度慢,如果数据大时,AOF启动效率低。
至于选择哪种持久化方式要根据业务需求和承受的数据丢失,甚至若只希望数据在服务器运行的时候存在,也可以不用任何持久化方式。
1.3 Redis过期键的删除策略
当Redis中缓存key过期时,一般有三种过期策略:
定时过期:给每个设置过期时间的key创建一个定时器,到期立即清除,这种情况对内存很友好,但是要占用大量CPU资源。
惰性过期:只有访问这个key时才判断是否过期,过期则清除。这种情况最大节省CPU,但对内存不友好。
再有就是有一个折中定时过期的方法,每隔一定时间,会扫描 一定数量的数据库的expire字典中一定数量的key,清除过期的key。(expires字典会保存所有设置了过期时间的key的过期时间数据,其中,key是指向键空间中的某个键的指针,value是该键的毫秒精度的UNIX时间戳表示的过期时间。键空间是指该Redis集群中保存的所有键。)
Redis中同时使用了惰性过期和定期过期两种过期策略。
1.4 Redis对过期数据是怎么处理的呢?(Redis是通过expire来设置key过期时间)
经常用的过期数据处理有两种:一是定时去清理过期的缓存;二是当用户请求过来时才判断请求用得到的缓存是否过期,过期的话就到底层系统得到新数据并更新缓存(这种是我经常用的)
1.5 Redis的内存淘汰策略都有哪些?
Redis的内存淘汰策略是指Redis用户缓存的内存不足时,怎么处理需要新写入且需要申请额外空间的数据。
最常用的是全局键空间移除最近最少使用的key,其他还有新写入报错机制,随机移除key。再有就是设置过期时间的键空间移除:有最近最少使用啊,随机啊,更早时间过期的key移除啊。
总结
Redis的内存淘汰策略的选取并不会影响过期的key的处理。内存淘汰策略用于处理内存不足时的需要申请额外空间的数据;过期策略用于处理过期的缓存数据
1.6 对Redis事务的理解
首先要明白事务的四个特性:ACID。即原子性,一致性,隔离性,持久性。原子性就是事物的操作要么都发生,要么都不发生。一致性,就是事务前后的数据的完整性要保持一致。隔离性就是并发事务之间的数据是独立的。一个事务的执行不影响其他事务的执行。持久性就是一个事务的提交对数据库中的数据的改变是永久的。
其中Redis事务总具有一致性和隔离性(Redis是单进程的程序)。另外Redis虽然单条命令是原子性执行的,但事务不保证原子性,且没有回滚操作的,事务中任意一个命令执行失败,其他命令会继续执行。
1.7 如何解决Redis并发竞争key的问题?
一般有分布式锁和消息队列两种解决方案。
在并发量过大的情况下,可以通过消息中间件进行处理,把并行读写进行串行化。把Redis.set操作放在队列中使其串行化,必须的一个一个执行。这种方式在一些高并发的场景中算是一种通用的解决方案。
再有就是布式锁(zookeeper 和 redis 都可以实现分布式锁)。(如果不存在 Redis 的并发竞争 Key 问题,不要使用分布式锁,这样会影响性能)
基于zookeeper临时有序节点可以实现的分布式锁。大致思想为:每个客户端对某个方法加锁时,在zookeeper上的与该方法对应的指定节点的目录下,生成一个唯一的瞬时有序节点。 判断是否获取锁的方式很简单,只需要判断有序节点中序号最小的一个。 当释放锁的时候,只需将这个瞬时节点删除即可。同时,其可以避免服务宕机导致的锁无法释放,而产生的死锁问题。完成业务流程后,删除对应的子节点释放锁。
在实践中,当然是从以可靠性为主。所以首推Zookeeper
另外Redis 官方站提出了一种权威的基于 Redis 实现分布式锁的方式名叫 Redlock,此种方式比原先的单节点的方法更安全。它可以保证以下特性:
安全特性:互斥访问,即永远只有一个 client 能拿到锁
避免死锁:最终 client 都可能拿到锁,不会出现死锁的情况,即使原本锁住某资源的 client crash 了或者出现了网络分区
容错性:只要大部分 Redis 节点存活就可以正常提供服务
1.8 对Redis缓存异常的理解
Redis常见的缓存异常有:缓存雪崩,缓存穿透,缓存击穿,缓存预热,缓存降级,缓存热点key。
其中缓存雪崩就是同一时间大面积缓存失效,导致后面请求大都直接落到数据库上,造成数据库短时间请求压力过大而崩掉。可以用设置缓存数据的时间设置成随机来解决。若果并发量不是太大,使用最多的是加锁排队。还可以给每一个缓存数据增加缓存标记用来记录缓存是否生效,如果失效就更新数据缓存。
缓存穿透就是缓存中和数据库中都没有数据,导致所有请求都落到数据库上,造成短时间内数据库压力过大而崩掉。可以在接口层增加校验(例如用户鉴权校验,id做基础校验,Id<0直接拦截)。也可以将key-value写成key-null,缓存失效时间可以设置成短一点,例如30s(如果设置时间太长,可能会导致正常情况下也无法使用),这样可以防止攻击用户反复用同一个id暴力攻击。也可以采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap(典型的就是hash表)中,这样的话,一个一定不存在的数据会被这个bigmap拦截,从而避免对底层存储系统的查询压力。
缓存击穿就是缓存中没有,数据库中有的数据(一般是缓存时间到期引起的),这个时候由于并发用户特别多,同时读缓存没读到数据,又同时去数据库读取数据,引起数据库瞬间的压力过大。这里和缓存雪崩不一样的是,缓存击穿是指并发查询同一条数据。而缓存雪崩是不同的数据大面积过期失效,很多数据缓存查不到而查数据库。缓存击穿的情况可以通过设置热点数据用不过期,也可以加互斥锁。
缓存预热问题。缓存预热就是系统上线后,将相关的缓存数据直接加载到缓存系统中。这样可以有效避免,用户请求的时候,直接查询数据库,在保存到缓存中的问题。可以直接写一个缓存刷新页面,上线时手工操作一下。如果数据量不大,可以在项目启动的时候自动进行加载。定时刷新缓存。
1.9 对Redis集群中哨兵的理解
哨兵,即sentinel,是Redis集群中非常重要的一个组件,需要至少三个实例。我认为主要有集群监控、消息通知,故障转移,配置中心这些功能。其中集群监控负责监控maste和slave 进程是否正常。消息通知我的理解是如果某个Redis实例出问题了,哨兵负责发消息作为警报通知管理员。而故障转移就是如果master节点挂掉了,会自动转移到slave节点。需要注意的是判断master节点是否挂掉,需要哨兵选举的。
1.10 Redis如何实现分布式锁
因为Redis为单进程单线程的,且是串行访问,是通过队列模式将并发访问变成串行访问的,而且多客户端对Redis连接不存在竞争关系。所以Redis中可以使用SETNX命令来实现分布式锁。使用setnx获取锁,返回0则key已经存在,即锁已经存在,获取失败。反之则成功。为了防止发生死锁,其他线程调用setnx命令总返回0,还应该给key设置一个合理的过期时间。