redis
应用场景
- 一些限流场景
- 数据库缓存
- 消息队列
2. 基础知识
数据结构:5种基本数据结构:SDS、Hash、List、Set、Zset;
- String: redis自己构建了一种sds(简单动态字符串)
定义结构如下:
struct sdshdr {
// 记录buf数组中已使用字节的数量
// 等于sds所保存字符串的长度
int len;
// 记录buf数组中未使用字节的数量
int free;
// 字节数组,用于保存字符串
char buf[];
};
-
Hash: 可以将Redis中的Hash类型看成具有<key,<key1,value>>,其中同一个key可以有多个不同key值的<key1,value>,所以该类型非常适合于存储值对象的信息
-
List:按照插入顺序排序的字符串链表;链表节点结构如下:
typedef struct listNode {
// 前置节点
struct listNode *prev;
// 后置节点
// 节点的值
void *value;
}listNode;
-
Set:Set类型看作为没有排序的字符集合。如果多次添加相同元素,Set中将仅保留该元素的一份拷贝
-
Zset:有序集合
-
其他数据结构:HyperLogLOG、Geo、Pub/Sub
-
用于防止缓存击穿的数据结构:Redis Module、BloomFilter、RedisSearch、Redis-ML
设置key过期时间命令:
setRedis(Key, value, time + Mathrandom()*10000);expire;
3. 分布式锁
先拿setnx(set if not exsit)来争抢锁,抢到之后,在用expire给锁加一个过期时间防止锁忘记释放;如果在setnx之后执行expire之前,进程意外crush或者重启维护,把setnx和expire合成一条命令
4. keys命令
-
redis是单线程,keys指令会导致线程阻塞一段时间,线上服务停顿,直到指令执行完毕,服务才能恢复
-
scan指令可以无阻塞的提取出指定模式的key列表,但是会有一定的重复概率,在客户端做一次去重就可以了,但整体花费的时间会比直接用keys要长
-
smembers命令可以返回集合健当前包含的所有元素
-
scan这类增量式迭代命令来说,因为在对健进行增量迭代的过程中,健可能会被修改,所以增量迭代命令只能对被返回的元素提供有限的保证
5. 异步队列
- list结构作为队列,rpush生产消息,lpop消费消息,当lpop没有消息的时候,要适当sleep一会,重试
- list还有个指令blpop,在没有消息的时候,它会阻塞住直到消息过来
- pub/sub主题订阅者模式,可以实现1N的消息队列,生产一次,消费多次
- pub/sub主题订阅者模式,在消费者下线的情况下,生产的消息可能会丢失,得使用专业的消息队列
6. 延时队列
- sortedset,拿时间戳作为score,消息内容作为key,调用zadd来生产消息,消费者占用zrangebyscore指令获取N秒之前的数据轮询进行处理
7. 持久化
RDB做镜像全量持久化,AOF做增量持久化,但是RDB会耗费较长时间,无法实时,在停机的时候,会导致大量丢失数据,因此需要AOF配合使用,在redis实例重启时,会使用RDB重新构建内存,仔使用AOF重放近期的操作指令来实现完整恢复重启之前的状态
Redis自身机制是AOF持久化开启,且存在AOF文件时,优先加载AOF文件,AOF关闭或者AOF文件不存在时,加载RDB文件,加载AOF/RDB后,启动成功;AOF/RDB文件存在错误时,redis启动失败,并打印错误信息
8. 机器断电对持久化的影响
AOF日志sync属性的配置,在不要求性能的情况下,可以每条写指令都sync一下磁盘,就不会丢数据,高性能的情况下,可以使用定时sync,例如1s一次,这个时候,最多会丢1s的数据
9. RDB原理
reids有2个命令可以生成RDB文件,一个是SAVE,一个是BGSAVE,其中SAVE命令会阻塞redis服务器进程,直到RDB文件创建完毕,在服务器阻塞期间,服务器不能处理任何命令请求;BGSAVE会fork出一个子进程,然后由子进程负责创建RDB文件,父进程继续处理命令请求
对应配置:save 900 1 表示900s内如果有1条是写入命令,就触发产生一次快照,可以理解为就进行一次备份;save 300 10 表示300s内有10条写入,就产生快照
10. AOF原理
通过将发送到服务器的写操作命令记录下来,形成AOF文件,文件默认名称是appendonly.aof,可以通过appendfilename来指定文件名称
对应配置:appendfsync everysec: 每秒同步一次,最多会丢1s的数据;appendfsync always:把每个写命令都立即同步到aof,很慢,但是很安全;appendfsync no:redis不处理交给OS来处理,非常快,但是也最不安全
11. 集群同步机制
redis可以使用主从同步,从从同步
第一次同步时,主节点做一次BGSAVE,同时将后续修改操作记录到内存buffer,待完成后,将RDB文件全量同步到复制节点;复制节点接受完成后将RDB镜像加载到内存,加载完成后,通知主节点后续增量数据通过AOF日志同步即可,类似于mongodb oplog
12. pipeline的好处
可以将多次IO往返时间缩减为1次,前提是pipeline执行的指令之间没有因果关系
13. 集群HA
- 哨兵模式侧重于高可用,在master宕机时,从剩下的slave中选举出新的master,继续提供服务
- cluster模式侧重于扩展性,单个redis内存不足时,对数据进行分片存储
14. 内存淘汰机制
- 当redis在使用内存达到某个阀值的时候(maxmemory配置),就会触发内存淘汰机制,选取一些key来删除
- noeviction:当内存不足以容纳新写入数据时,新写入操作会报错。默认策略
- allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的key
- allkeys-random:当内存不足以容纳新写入数据时,在键空间中,随机移除某个key。
- volatile-lru:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最近最少使用的key
- volatile-random:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个key。
- volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的key优先移除。
- 不同策略适用场景
-
Redis只是作为缓存,不作为DB持久化,那推荐选择allkeys-lru
-
Redis同时用于缓存和数据持久化,那推荐选择volatile-lru
15. 缓存雪崩、击穿、穿透
- 缓存雪崩
原因: 大面积缓存失效,新的缓存还未到期间,原本应该访问缓存的请求都去查询数据库了,造成数据库服务器压力过大,甚至宕机
解决方法:
- 批量往redis存数据的时候,把每个key的失效时间都加个随机值,确保数据不会在同一时间大面积失效
- cluster模式部署,将热点数据均匀分布在不同的redis库中,也能避免全部失效
- 设置热点数据永不过期,有更新操作就更新缓存
- 缓存穿透
原因: 用户查询数据,在数据库没有,自然在缓存中也不会有。这样就导致用户查询的时候,在缓存中找不到,每次都要去数据库再查询一遍,然后返回空(相当于进行了两次无用的查询)。这样请求就绕过缓存直接查数据库,这也是经常提的缓存命中率问题
解决方法:
- 采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被这个bitmap拦截掉,从而避免了对底层存储系统的查询压力
- 把这些key对应的值设置为null,丢到缓存里面,并设置过期时间
- 对于恶意攻击产生的,可以考虑限流,限制IP访问次数
- 缓存击穿
原因: 一个热点的key,承载了大量的并发,在这个key失效的一瞬间,持续的高并发,会之间请求数据库,相当于在一个伞上凿了一个洞
解决方法:
-
设置热点数据永不过期
-
增加互斥锁
16. 哨兵
参见: redis
哨兵 + 主从实现redis的高可用
监控:Sentinel不断的检查master和slave是否正常的运行
自动故障转移:能够进行自动切换。当一个master节点不可用时,能够选举出master的多个slave中的一个来作为新的master,其它的slave节点会将它所追随的master的地址改为被提升为master的slave的新地址
配置提供者:哨兵作为Redis客户端发现的权威来源:客户端连接到哨兵请求当前可靠的master的地址。如果发生故障,哨兵将报告新地址
17. 和memcache的区别
集群: redis和memcache都支持集群
数据类型: Redis支持的数据类型要丰富得多,Redis不仅仅支持简单的k/v类型的数据,同时还提供String,List,Set,Hash,Sorted Set,pub/sub,Transactions数据结构的存储;memcache支持简单数据类型,需要客户端自己处理复杂对象
持久化: redis支持数据落地持久化存储,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用;memcache不支持数据持久存储
分布式:redis支持主从,cluster模式;memcache可以使用一致性hash做分布式,但是要在客户端实现
value大小:memcache是一个内存缓存,key的长度小于250字符,单个item存储要小于1M
memcache支持多核多线程,redis单线程操作