文章目录
第三章 缓存中间件
第二节 Redis分布式缓存
3.2.1 Redis常用命令和数据结构(一)
1. Redis介绍
- Redis是一个开源的使用C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。
- 本质是客户端-服务端应用软件程序。
- 特点是使用简单、性能强悍、功能应用场景丰富。
- 官网:https://redis.io/
- 中文维护网站(更新不是最快的,基本够用):http://www.redis.cn
- 常见的客户端:Jedis、lettuce等
- spring 文档:https://docs.spring.io/spring-data/redis/docs/2.1.5.RELEASE/reference/html/
2. 通用命令

- 启动服务后打开客户端
./bin/redis-cli #还有相关的参数指令
3. 数据结构
1)String
- String数据结构是简单的key-value类型,value其实不仅是String,也可以是数字。
- 使用场景:微博数,粉丝数(常规计数),速率限制器
- 常用命令
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RNlLVk0S-1570522671209)(evernotecid://C8B8AA71-A9FF-4008-AE69-9DC7DAF8E08F/appyinxiangcom/7433385/ENResource/p1013)]@w=450](https://code84.com/wp-content/uploads/2022/09/20191008162006188.png)
set hello tony ex 3 # 3秒后失效
set hello tony px 3000 # 3000毫秒后失 px 3000
set hello tony nx # 只有当没有这条数据时才能设置成功
- 速率限制器是用户请求后,记录用户请求次数,当达到一定次数后不再允许其访问。
2)List
- List就是链表,相信略有数据结构知识的人都应该能理解其结构。
- 使用场景:微博的关注列表,粉丝列表
- 常用命令

3)Set
- Set就是一个集合,集合的概念就是一堆不重复值的组合。利用Redis提供的Set数据结构,可以存储一些集合性的数据。
- 使用场景:实现如共同关注、共同喜好、二度好友
- 常用命令

4)Sorted Set
- Sorted Set的使用场景与set类似,区别是set不是自动有序的,而Sorted Set可以通过用户额外提供一个优先级(score)的参数来为成员排序,并且是插入有序的,即自动排序。
- 使用场景:排行榜、按照用户投票和时间排序
- 常用命令

5)Hash
- Hash是一个string类型的field和value的映射表
- 使用场景:存储部分变更数据,如用户信息
- 常用命令

6)GEO
- GEO 3.2 版本开始对GEO(地理位置)的支持
- 使用场景:LBS应用开发
- 常用命令

7)Stream
- Stream 5.0 版本开始的新结构“流”
- 使用场景:消费者生产者场景(类似MQ)
- 常用命令

4. 代码示例
代码参考:redisdemo
- 相关配置和API使用请查看官网介绍
- 如果代码中访问不了redis,需要调整redis的访问策略
- 在
/usr/local/redis/目录下创建conf目录 - 在
/usr/local/redis/conf/目录下导入官方的redis.conf文件
bind 127.0.0.1
# bind 0.0.0.0 #任意网络均可访问,但因为有安全漏洞,建议只给内网服务器ip进行绑定
# 绑定redis服务器网卡IP,默认为127.0.0.1,即本地回环地址。
# 这样的话,访问redis服务只能通过本机的客户端连接,而无法通过远程连接。
# 如果bind选项为空的话,那会接受所有来自于可用网络接口的连接。
# 如上配置,绑定一个127.0.0.1的本机地址和192.168.1.100的外网地址。
- 重新启动redis,加载配置文件。
./bin/redis-server conf/redis.conf
3.2.1 Redis常用命令和数据结构(二)
1. Redis介绍
- 几种基础API的使用参考官网和代码示例。(后期有空把示例写入该文档)
- key事件通知(Redis keyspace notifications):通过发布/订阅获得key事件的通知(版本2.8或更高)。
- 可能接收的事件示例如下:
- 所有影响给定键的命令。
- 所有接收LPUSH操作的键。
- 所有在数据库0中到期的键。
- 可用场景:
- 订单超时未支付场景,定时删除,删除后会通知客户端进行处理。
- 分布式锁
- 默认不开启事件监听,如果要使用,推荐使用更改配置文件的方式
- Redis Server提供类似数据库的功能,每个Server上可以设置多个database
- 使用 select 选择第几个库
3.2.2 Redis持久化
1. 持久化介绍
- Redis的数据默认都存放在内存中,如果没有配置持久化,redis重启后数据就全丢失了,于是需要开启redis的持久化功能,将数据保存到磁盘上,当redis重启后,可以从磁盘中恢复数据。

2. 持久化方式
1)RDB持久化
- RDB持久化方式能够在指定的时间间隔对你的数据进行快照存储。
i. 命令使用
- 客户端直接通过命令
BGSAVE或者SAVE来创建一个内存快照。BGSAVE调用fork来创建一个子进程,子进程负责将快照写入磁盘,而父进程仍然继续处理命令。SAVE执行SAVE命令过程中,不再响应其他命令。
- 在
redis.conf中调整save配置选项,当在规定的时间内,Redis发生了写操作的个数满足条件会触发发生BGSAVE命令
# 900秒之内至少一次写操作
save 900 1
# 300秒之内至少发生10次写操作
save 300 10
# 60秒之内至少发生10000次
save 60 10000
ii. 优点与缺点

2)AOF(append only file)持久化
- AOF持久化方式记录每次对服务器写的操作,当服务器重启的时候会重新执行这些命令来恢复原始的数据。
- AOF文件可修改,重启后会修改redis中的数据。
i. 命令使用
- 开启AOF持久化
appendonly yes
- AOF策略调整
#每次有数据修改发生时都会写入AOF文件
appendfsync always
#每秒钟同步一次,该策略为AOF的缺省策略
appendfsync everysec
#从不同步。高效但是数据不会被持久化
appendfsync no
ii. 优点与缺点

3.2.3 Redis内存管理
1. 内存分配
- 通过不数据类型的大小限制
- Strings类型:一个String类型的value最大可以存储512M。
- Lists类型:list的元素个数最多为2^32-1个,也就是4294967295个。
- Sets类型:元素个数最多为2^32-1个,也就是4294967295个。
- Hashes类型:键值对个数最多为2^32-1个,也就是4294967295个。
- 最大内存控制
maxmemory #最大内存阈值
maxmemory-policy #到达阈值的执行策略
2. 内存压缩
- 超过设置的数据就不会进行压缩
- 大小超出压缩范围,溢出后Redis将自动将其转换为正常大小
# 配置字段最多512个
hash-max-zipmap-entries 512
# 配置value最大为64字节
hash-max-zipmap-value 64
# 配置元素个数最多512个
list-max-ziplist-entries 512
# 配置value最大为64字节
list-max-ziplist-value 64
# 配置元素个数最多512个
set-max-intset-entries 512
# 配置元素个数最多128个
zset-max-ziplist-entries 128
# 配置value最大为64字节
zset-max-ziplist-value 64
3. 过期数据的处理策略
1)主动处理
- redis主动触发检测key是否过期,每秒执行10次。
- 过程如下
- 从具有相关过期的秘钥集中测试20个随机秘钥
- 删除找到的所有已过期秘钥
- 如果超过25%的秘钥已过期,请从步骤1重新开始
2)被动处理
- 每次访问key的时候,发现超时后被动过期,清理掉
4. 数据恢复阶段过期数据的处理策略
1)RDB方式
- 过期的key不会被持久化到文件中。
- 载入时过期的key,会通过redis的主动和被动方式清理掉。
2)AOF方式
- 当redis使用AOF方式持久化时,每次遇到过期的key,redis会追加一条DEL命令到AOF文件
- 也就是说,只要我们顺序载入执行AOF命令文件就会删除过期的键。
注意:过期数据的时间计算和计算机本身的时间是有直接联系的!
5. Redis内存回收策略
- 配置文件中设置:
maxmemory-policy noeviction - 动态调整:
config set maxmemory-policy noeviction
1)LRU算法
- LRU(Least recently used,最近最少使用):根据数据的历史访问记录来进行淘汰数据。
- 核心思想:如果数据最近被访问过,那么将来被访问的几率也更高。
- 注意:Redis的LRU算法并非完整的实现,完整的LRU实现是因为这需要太多的内存。
- 方法:通过少量keys进行取样(50%),然后回收其中一个最好的key。
- 配置方式:
maxmemory-samples 5
2)LFU算法
- LFU(Least Frequently Used)根据数据的历史访问频率来淘汰数据
- 核心思想:如果数据过去被访问多次,那么将来被访问的频率也更高。
- Redis实现的是近似的实现,每次对key进行访问时,用基于概率的对数计数器来记录访问次数,同时这个计数器会随着时间推移而减小。
- Morris counter算法依据:https://en.wikipedia.org/wiki/Approximate_counting_algorithm
- 启动LFU算法后,可以使用热点数据分析功能。
redis-cli --hotkeys
3.2.4 Redis主从复制
1. 主从复制介绍
代码示例:ReplicationRWTests
1)什么是主从复制
- 数据发送到redis后,复制同步到从服务器
- 可以解决单点故障问题,提高并发访问量

2)为什么要使用主从复制
- redis-server单点故障
- 单节点QPS有限
3)主从复制应用场景分析
- 读写分离场景,规避redis单机瓶颈
- 故障切换,master出问题后还有slave节点可以使用
2. 搭建主从复制集群
- 主Redis Server以普通模式启动,主要是启动从服务器的方式。
1)命令行方式
- 连接需要实现从节点的redis,执行下面的命令
slaveof <ip> <port>
- 新版本中也可以使用
replicaof <masterip> <masterport>
2)redis.conf配置文件方式
- 配置文件中增加
slaveof <ip> <port>
- 新版本中也可以使用
replicaof <masterip> <masterport>
- 从服务器是否只读(默认yes)
slave-read-only yes
3)退出主从集群的方式
slaveof no one
3. 检查主从复制集群

4. 主从复制流程

- 从服务器通过
psync命令发送服务器已有的同步进度(同步源ID、同步进度offset) - Master收到请求,同步源为当前master,则根据偏移量增量同步
- 同步源非当前master,则进入全量同步:master生成RDB,传输到slave,加载到slave内存
5. 主从复制核心知识
- Redis默认使用异步复制,slave和master之间异步地确认处理的数据量。
- 一个master可以拥有多个slave。
- slave可以接受其他slave的连接。slave可以有下级sub slave
- 主从同步过程在master侧是非阻塞的。
- slave初次同步需要删除旧数据,加载新数据,会阻塞到来的连接请求。

6. 主从复制应用场景
- 主从复制可以用来支持读写分离
- slave服务器设定为只读,可以用在数据安全的场景下。(比如,用户信息,订单系统访问从服务器,只读。而只有用户系统可以修改主redis,保证数据安全。)
- 可以使用主从复制来避免master持久化造成的开销。master关闭持久化,slave配置为不定期保存或是启用AOF。(注意:重新启动的master程序将从一个空数据集开始,如果一个slave试图与它同步,那么这个slave也会被清空。)

7. 主从复制的注意事项
1)读写分离场景
- 数据复制延时导致读到过期数据或者读不到数据(网络原因、slave阻塞)
- 从节点故障(多个client)如何迁移
2)全量复制情况下
- 第一次建立主从关系或者runid不匹配会导致全量复制
- 故障转移的时候也会出现全量复制
3)复制风暴
- master故障重启,如果slave节点较多,所有slave都要复制,对服务器的性能,网络的压力都有很大影响。
- 如果一个机器部署了多个master
4)写能力有限
- 主从复制还是只有一台master,提供的写服务能力有限
5)master故障情况下
- 如果是master无持久化,slave开启持久化来保留数据的场景,建议不要配置redis自动重启。
- 启动redis自动重启,master启动后,无备份数据,可能导致集群数据丢失的情况。
- 可以让从服务器变为主,主服务器变为从,通过这种角色转换完成数据同步
- 另一种方式,把从服务器的备份文件拷贝到主服务器下,重启就可以加载数据,不会影响主从结构
6)带有效期的key
- slave不会让key过期,而是等待master让key过期
- 在Lua脚本执行期间,不执行任何key过期操作
8. Java代码使用读写分离
- 当Java使用Redis客户端时,会判断是set请求还是get请求
- 如果是set请求则会发给master
- 如果是get请求则会发给slave
- Java的redis客户端底层通过info命令得知当前的master节点和slave节点信息
3.2.5 Redis哨兵高可用机制
1. 哨兵(Sentinel)机制核心作用
- 哨兵提供了服务发现的机制
- 客户端连接哨兵,通过哨兵来连接可用的redis服务器
- 通常哨兵也会部署集群,他们之间有通信,底层实现,用户不用管
- 哨兵投票,大部分哨兵认为该redis服务挂了,则认为这个redis实例挂了。
- 客户端通过哨兵连接到redis后,这是哨兵挂掉不会对程序有影响,只有当当前连接的redis实例挂掉了,客户端才会去询问哨兵。
参考文档:redis哨兵高可用搭建.md

2. 核心运作流程


3. 7大核心概念
- 哨兵如何知道Redis主从信息(自动发现机制)
- 什么是master主观下线
- 什么是客观下线
- 哨兵之间如何通信(哨兵之间的自动发现)
- 哪个哨兵负责故障转移?(哨兵领导选举机制)
- slave选举机制
- 最终主从切换的过程
0)哨兵启动和配置
- 启动命令:
redis-server /path/to/sentinel.conf --sentinel - 配置文件启动时指定,运行过程中会自动变更,记录哨兵的检测结果

1)哨兵如何知道Redis主从信息
- 哨兵配置文件中,保存着主从集群中master的信息,可以通过info命令,进行主从信息自动发现。

2)什么是主观下线(sdown)

主观下线:单个哨兵自身认为redis实例已经不能提供服务
检测机制:哨兵向redis发送ping请求,+PONG、-LOADING、-MASTERDOWN这三种情况视为正常,其他回复均视为无效。
对应配置文件的配置项:
setinel down-after-milliseconds mymaster 1000
3)什么是客观下线(odown)

客观下线:一定数量值的哨兵认为master已经下线。
检测机制:当哨兵主观认为master下线后,则会通过SENTINEL is-master-down-by-addr 命令 询问其他哨兵是否认为master已经下线,如果达成共识(达到quorum个数),就会认为master节点客观下线,开始故障转移流程。
对应配置文件的配置项:
setinel monitor mymaster 60.205.209.106 6380 2
4)哨兵之间如何通信
- 哨兵之间的自动发现

- 哨兵之间通过命令进行通信

- 哨兵之间通过订阅发布进行通信

5)哨兵领导选举机制
- 基于Raft算法实现的选举机制,流程简述如下:
- 拉票阶段:每个哨兵节点希望自己成为领导者;
- sentinel节点收到拉票命令后,如果没有收到或同意过其他sentinel节点的请求,就同意该sentinel节点的请求(每个sentinel只持有一个同意票数);
- 如果sentinel节点发现自己的票数已经超过一半的数值,那么它将成为领导者,去执行故障转移;
- 投票结束后,如果超过failover-timeout的时间内,没有进行实际的故障转移操作,则重新拉票选举。
- 每个节点会以随机时间之后开始拉票,因此先开始拉票的节点,优势越大
注:以了解raft协议为主。
GitHub:https://raft.github.io/
动画演示:http://thesecretlivesofdata.com/
6)slave选举方案

7)最终主从切换的过程
- 针对即将成为master的slave节点,将其撤出主从集群,自动执行:
slaveof NO ONE
- 针对其他slave节点,使它们成为新master的从属,自动执行:
slaveof new_master_host new_master_port
4. 哨兵服务部署方案
- 两个哨兵的模式,不建议

- 1主2从,三个哨兵(哨兵独立部署也可以),官方推荐的方式

- 1主2从,网络分区下,可能出现数据不一致或丢失。

注:Redis集群非强一致
3.2.6 Redis集群分片存储
1. 为什么要分片存储
- 示例:公司用户量3千万,用户基本信息缓存到redis中,需要内存10G,如何设计Redis的缓存架构?
- 3千万用户,各种业务场景对用户信息的访问量很大。(单台redis实例的读写瓶颈凸显)
- 单Redis实例管理10G内存,必然影响处理效率。
- redis的内存需求可能超过机器的最大内存。(一台机器不够用)
- 故此,引出分片存储的技术。
2. 官方集群方案
- redis cluster是Redis的分布式集群解决方案,在3.0版本推出后有效地解决了redis分布式方面的需求实现了数据在多个Redis节点之间自动分片、故障自动转移、扩容机制等功能。

- redis总共有16384个slot供存放数据(0~16383)
- 要存的数据的key,通过hash再取模,然后计算出一个slot,根据该slot所属的redis实例,将数据存放在该redis实例上
- 客户端随便连接redis集群中的某一台设备,然后发送数据
- 该实例计算后发现这条数据应该发到另一个redis实例上,这时会给客户端返回重定向的指令,告诉客户端应该发往哪个客户端
- 然后客户端会往正确的机器上发送数据
- 为避免性能下降(避免每次发往错的redis),客户端会缓存slot分配信息。
- 然后每次发送数据时,客户端自己计算slot,然后根据缓存的分配信息发往正确的redis实例
- 客户端会定时刷新slot分配信息,或者在收到重定向的响应时更新分配信息的缓存
3. 搭建集群
- 准备6个独立的redis服务
- 通过redis-cli工具创建集群
- 检验集群
- 故障转移测试
- 集群扩容
- 集群节点删除
详细操作步骤,通过附件《redis5集群搭建》实操即可
4. 集群关心的问题
1)增加了slot槽的计算,是不是比单机性能差?
- 共16384个槽,slots槽计算方式公开的,HASH_SLOT = CRC16(key) mod 16384。
- 为了避免每次都需要服务器计算重定向,优秀的java客户端都实现了本地计算,并且缓存服务器slots分配,有变动时再更新本地内容,从而避免了多次重定向带来的性能损耗。(结合画图过程理解)
2)redis集群大小,到底可以装多少数据?
- 理论是可以做到16384个槽,每个槽对应一个实例,但是redis官方建议是最大1000个实例。存储足够大了
3)集群节点间是怎么通信的?
- 每个Redis集群节点都有一个额外的TCP端口,每个节点使用TCP连接与每个其他节点连接。检测和故障转移这些步骤基本和哨兵模式类似(毕竟是同一个软件,同一个作者设计)。
4)ask和moved重定向的区别
重定向包括两种情况
- 若确定slot不属于当前节点,redis会返回moved
- 若当前redis节点正在处理slot迁移,则代表此处请求对应的key暂时不在此节点,返回ask,告诉客户端本次请求重定向。
5)数据倾斜和访问倾斜的问题
倾斜导致集群中部分节点数据多,压力大。解决方案分为前期和后期:
- 前期是业务层面提前预测,哪些key是热点,在设计的过程中规避。
- 后期是slot迁移,尽量将压力分摊(slot调整有自动rebalance、reshard和手动)。
6)slot手动迁移怎么做?
迁移过程如下,大致描述如下:
- 在迁移目的节点执行
cluster setslot <slot> IMPORTING <node ID>命令,指明需要迁移的slot和迁移源节点。 - 在迁移源节点执行
cluster setslot <slot> MIGRATING <node ID>,指明需要迁移的slot和迁移目的节点。 - 在迁移源节点执行
cluster getketsinslot获取该slot的key列表。 - 在迁移源节点执行对每个key执行
migrate命令,该命令会同步把该key迁移到目的节点。 - 在迁移源节点反复执行
cluster getkeysinslot命令,直到该slot的列表为空。 - 在迁移源节点和目的节点执行
cluster setslot <slot> NODE <node ID>,完成迁移操作。
7)节点之间会交换信息,传递的消息包括槽的信息,带来带宽消耗。
注意:避免使用大的一个集群,可以分多个集群。
8)Pub/Sub发布订阅机制
注意:对集群内任意的一个节点执行publish发布消息,这个消息会在集群中进行传播,其他节点接收到发布的消息。
9)读写分离
- redis-cluster默认所有的从节点上的读写,都会重定向到key对应槽的主节点上。
- 可以通过readonly设置当前连接可读,通过readwrite取消当前连接的可读状态。
注意:主从节点依然存在数据不一致的问题。
5. 非官方集群方案
- 豌豆荚团队开源产品

- 推特开源产品

这两个产品相当于一个nginx,是一个redis的代理软件
但是这两个产品社区不活跃,比较久远了(不推荐使用,推荐官方的集群方案)
3.2.7 Redis监控
1. Monitor命令
- monitor是一个调试命令,返回服务器处理的每个命令。对于发现程序的错误非常有用。
- 出于安全考虑,某些特殊管理命令CONFIG 不会记录到MONITOR输出

运行一个MONITOR命令能够降低50%的吞吐量,运行多个MONITOR命令降低的吞吐量更多。
2. Info命令
- INFO命令以一种易于理解和阅读的格式,返回关于Redis服务器的各种信息和统计数值。

- 可以通过section返回部分信息,如果没有使用任何参数时,默认为default。
查看官网或者查看文档:redis-info命令详解.md
3. 图形化监控工具 - RedisLive
- GitHub:https://github.com/nkrode/RedisLive
- 参考文档:redislive.md
