redis专题
2026-01-05 10:54:37 0 举报
AI智能生成
本专题深入讲解了如何在各种应用场景中部署和优化Redis,同时也强调了最佳实践,如持久化机制、事务处理以及集群配置。通过修饰语“深入”、“高效”、“灵活”,本专题旨在全方位提升学习者对Redis的认知水平,为开发高性能、可扩展的应用打下坚实的基础。
作者其他创作
大纲/内容
部署模式
集群模式
定义
Redis 集群是 Redis 的⼀种分布式运⾏模式,它通过分⽚<br>(sharding)来提供数据的⾃动分区和管理,从⽽实现数据的⾼可⽤性和可扩展性。<br>在集群模式下,数据被分割成多个部分(称为槽或slots),分布在多个 Redis 节点上。<br>集群中的节点分为主节点和从节点:主节点负责读写请求和集群信息的维护;从节点只进⾏主节点数据和状态信息的复制。
作用
数据分区
1.数据分区(或称数据分⽚)是集群最核⼼的功能。 集群将数据分散到多个节点,⼀⽅⾯突破了Redis单机内存⼤⼩的限制,存储容量⼤⼤增加;<br>2.另⼀⽅⾯每个主节点都可以对外提供读服务和写服务,⼤⼤提⾼了集群的响应能⼒。 Redis单机内存⼤⼩受限问题,在介绍持久化和主从复制时都有提及;<br>3.例如,如果单机内存太⼤,bgsave和bgrewriteaof的fork操作可能导致主进程阻塞,主从环境下主机切换时可能导致从节点⻓时间⽆法提供服务,全量复制阶段主节点的复制缓冲区可能溢出。
Redis集群引⼊了哈希槽的概念 Redis集群有16384个哈希槽(编号0-16383) 集群的每个节点负责⼀部分哈希槽 每个Key通过CRC16校验后对16384取余来决定放置哪个哈希槽,<br>通过这个值,去找到对应的插槽所对应的节点,然后直接⾃动跳转到这个对应的节点上进⾏存取操作
Slot如何分配?<br>
在建⽴集群时,Redis会根据集群节点数量,将这些槽位尽量平均的分配到各个节点上。
新增节点时
如果集群中的节点数量发⽣了变化。(增加了节点或者减少了节点)。就需触发⼀次reshard,重新分配槽位。⽽槽位中对应的key,也会随着进⾏数据迁移。
参考样例
# 增加6387,6388两个Redis服务,并启动<br># 添加到集群当中<br>redis-cli -a 123qweasd -p 6381 --cluster add-node 192.168.65.214:6387<br>192.168.65.214:6388<br># 确定集群状态 此时新节点上是没有slot分配的<br>redis-cli -a 123qweasd -p 6381 --cluster check 192.168.65.214:6381<br># ⼿动触发reshard,重新分配槽位<br>redis-cli -a 123qweasd -p 6381 <font color="#e74f4c">reshard</font> 192.168.65.214:6381<br># 再次确定集群状态 此时新节点上会有⼀部分槽位分配<br>redis-cli -a 123qweasd -p 6381 --cluster check 192.168.65.214:6381
⾼可⽤
集群⽀持主从复制和主节点的⾃动故障转移(与哨兵类<br>似);当任⼀节点发⽣故障时,集群仍然可以对外提供服务。
核心解决三个问题
客户端需要频繁切换master的问题。<br>
服务端数据量太⼤后,单个复制集难以承担的问题。<br>
master节点挂了之后,主动将slave切换成master,保证服务稳定
配置
主节点配置
# 允许所有的IP地址<br>bind * -::* <br># 后台运⾏<br>daemonize yes <br># 允许远程连接<br>protected-mode no <br># 开启集群模式<br>cluster-enabled yes<br># 集群节点超时时间<br>cluster-node-timeout 5000<br># 配置数据存储⽬录<br>dir "/opt/software/redis/cluster"<br># 开启AOF持久化<br>appendonly yes<br># 端⼝<br>port 6379 <br># log⽇志<br>logfile "/opt/software/redis/redis-stable/cluste<br>r/redis6379.log" <br># 集群配置⽂件<br>cluster-config-file nodes-6379.conf<br># AOF⽂件名<br>appendfilename "appendonly6379.aof" <br># RBD⽂件名<br>dbfilename "dump6379.rdb"
从节点配置
# 允许所有的IP地址<br>bind * -::* <br># 后台运⾏<br>daemonize yes <br># 允许远程连接<br>protected-mode no <br># 开启集群模式<br>cluster-enabled yes<br># 集群节点超时时间<br>cluster-node-timeout 5000<br># 配置数据存储⽬录<br>dir "/opt/software/redis/cluster"<br># 开启AOF持久化<br>appendonly yes<br># 端⼝<br>port 6380<br># log⽇志<br>logfile "/opt/software/redis/redis-stable/cluste<br>r/redis6380.log" <br># 集群配置⽂件<br>cluster-config-file nodes-6380.conf<br># AOF⽂件名<br>appendfilename "appendonly6380.aof" <br># RBD⽂件名<br>dbfilename "dump6380.rdb"
启动redis服务后创建集群参考命令
-- 创建三主三从集群模式,每⼀个主节点带⼀个从节点<br>redis-cli --cluster create --cluster-replicas 1 1<br>92.168.75.129:6379 192.168.75.129:6380 192.168.7<br>5.131:6379 192.168.75.131:6380 192.168.75.132:637<br>9 192.168.75.132:6380
相关命令
-- 查看集群信息<br>redis-cli cluster info
-- 查看单个节点信息<br>redis-cli info replication<br>
-- 查看集群节点身份信息<br>redis-cli cluster nodes<br>
-- 停⽌redis服务<br>redis-cli -p 6379 shutdown<br>redis-cli -p 6380 shutdown
集群选举原理
gossip协议
Redis集群之间通过gossip协议进⾏频繁的通信,⽤于传递消息和更新节点状态。
gossip协议包含多种消息,包括ping,pong,meet,fail等等。<br>
meet:某个节点发送meet给新加⼊的节点,让新节点加⼊集群中,然后新节点就会开始与其他<br>节点进⾏通信;
ping:每个节点都会频繁给其他节点发送ping,其中包含⾃⼰的状态还有⾃⼰维护的集群元数据,互相通过 ping交换元数据(类似⾃⼰感知到的集群节点增加和移除,hash slot信息等);
pong: 对ping和meet消息的返回,包含⾃⼰的状态和其他信息,也可以⽤于信息⼴播和更新;
fail: 某个节点判断另⼀个节点fail之后,就发送fail给其他节点,通知其他节点,指定的节点宕机了。<br>
作用
节点间发送⼼跳和确认其他节点的存在。<br>
通知其他节点新节点的加⼊或已经下线的节点。<br>
通过反馈机制更新节点的状态,如权重、过期时间等<br>
优点
gossip集群是去中⼼化的,各个节点彼此之间通过gossip协议互相通信,保证集群内部各个节点最终能够达成统⼀。gossip协议更新元数据并不是同时在集群内部同步,⽽是陆陆续续请求到所有节点上。因此gossip协议的数据统⼀是有⼀定的延迟的。
gossip协议最⼤的好处在于,即使集群节点的数量增加,每个节点的负载也不会增加很多,⼏乎是恒定的。因此在Redis集群中,哪怕构建⾮常多的节点,也不会对服务性能造成很⼤的影响。但是gossip协议的数据同步是有延迟的,如果集群节点太多,数据同步的延迟时间也会增加。这对于Redis是不合适的。因此,通常不建议构建太⼤的Redis集群。
注意点
Redis集群中,每个节点都有⼀个专⻔⽤于节点之间进⾏gossip通信的端⼝,就是⾃⼰提供服务的端⼝+10000.因此,在部署Redis集群时,要注意防⽕墙配置,不要把这个端⼝屏蔽了。
集群选举流程
slave发现⾃⼰的master变为FAIL
将⾃⼰记录的集群currentEpoch加1,并⼴播FAILOVER_AUTH_REQUEST信息(currentEpoch可<br>以理解为选举周期,通过cluster info指令可以看到)
其他节点收到该信息,只有master响应,判断请求者的合法性,并发送FAILOVER_AUTH_ACK,对每⼀个 epoch只发送⼀次ack<br>
尝试failover的slave收集master返回的FAILOVER_AUTH_ACK
slave收到超过半数master的ack后变成新Master(这⾥解释了集群为什么⾄少需要三个主节点,<br>如果只有两 个,当其中⼀个挂了,只剩⼀个主节点是不能选举成功的)
slave⼴播Pong消息通知其他集群节点
从节点并不是在主节点⼀进⼊ FAIL 状态就⻢上尝试发起选举,⽽是有⼀定延迟,⼀定的延迟确保我<br>们等待 FAIL状态在集群中传播,slave如果⽴即尝试选举,其它masters或许尚未意识到FAIL状态,<br>可能会拒绝投票
延迟计算公式: DELAY = 500ms + random(0 ~ 500ms) + SLAVE_RANK * 1000ms
SLAVE_RANK表示此slave已经从master复制数据的总量的rank。Rank越⼩代表已复制的数据越新。这种⽅ 式下,持有最新数据的slave将会⾸先发起选举
哨兵模式
原理
Redis哨兵模式是通过在独⽴的哨兵节点上运⾏特定的哨兵进程来实现的。
哨兵
在启动时,每个哨兵节点会执⾏选举过程,其中⼀个哨兵节点被选为领导者(leader),负责协调其他哨兵节点。
选举过程
每个在线的哨兵节点都可以成为领导者,每个哨兵节点会向其它哨兵发is-master-down-by-addr命令,征求判断并要求将⾃⼰设置为领导者;<br>当其它哨兵收到此命令时,可以同意或者拒绝它成为领导者;<br>如果哨兵发现⾃⼰在选举的票数⼤于等于num(sentinels)/2+1<br>时,将成为领导者,如果没有超过,继续选举。
客观下线O_DOWN
当主观下线的节点是主节点时,此时该哨兵3节点会通过指令<br>sentinel is-masterdown-by-addr寻求其它哨兵节点对主节点的<br>判断,当超过quorum(选举)个数,此时哨兵节点则认为该主节点确实有问题,这样就客观下线了,⼤部分哨兵节点都同意下线操作,也就说是客观下线。
主观下线S_DOWN
对于每⼀Sentinel服务,他会不断地往master发送⼼跳,监听master的状态。如果经过⼀段时间参数sentinel down-after-milliseconds <master-name> <milliseconds> 指定。默认30秒)没有收到master的响应,他就会主观的认为这个master服务下线了。也就是S_DOWN。<br>
作用
监控主从节点
哨兵节点通过发送命令周期性地检查主从节点的健康状<br>态,包括主节点是否在线、从节点是否同步等。如果哨兵节<br>点发现主节点不可⽤,它会触发⼀次故障转移
故障转移
⼀旦主节点被判定为不可⽤,哨兵节点会执⾏故障转移
操作。它会从当前的从节点中选出⼀个新的主节点,并将其他
从节点切换到新的主节点。这样,系统可以继续提供服务⽽⽆
需⼈⼯介⼊。
故障转移过程<br>
由Sentinel节点定期监控发现主节点是否出现了故障:<br>sentinel会向master发送⼼跳PING来确认master是否存活,如果master在“⼀定时间范围”内不回应PONG 或者是回复了⼀<br>个错误消息,那么这个sentinel会主观地(单⽅⾯地)认为这个<br>master已经不可⽤了。
确认主节点
1.过滤掉不健康的(下线或断线),没有回复过哨兵ping响应<br>的从节点<br>2.选择从节点优先级最⾼的<br>3.选择复制偏移量最⼤,此指复制最完整的从节点<br>4.当主节点出现故障, 由领导者负责处理主节点的故障转<br>移。<br>
客户端重定向
哨兵节点会通知客户端新的主节点的位置,使其能够与<br>新的主节点建⽴连接并发送请求。这确保了客户端可以⽆缝切<br>换到新的主节点,继续进⾏操作。
配置
修改sentinel.conf文件<br>
protected-mode no <br> #6⾏,关闭保护模式<br>daemonize yes <br> #15⾏,指定sentinel为后台启动<br>logfile /opt/software/redis/redis-stable/sentinel.log #34⾏,指定⽇志存放路径<br>dir /opt/software/redis <br> #73⾏,指定数据库存放路径<br>sentinel monitor mymaster 192.168.75.129 6379 2<br> #93⾏,修改 指定该哨兵节点监控20.0.0.20:6379这个主<br>节点,该主节点的名称是mymaster,最后的2的含义与主节点的<br>故障判定有关:⾄少需要2个哨兵节点同意,才能判定主节点故<br>障并进⾏故障转移<br>sentinel down-after-milliseconds mymaster 30000<br> #134⾏,判定服务器down掉的时间周期,默认30000毫秒<br>(30秒)<br>sentinel failover-timeout mymaster 180000 <br> #234⾏,故障节点的最⼤超时时间为180000(180秒)<br>
缺点
复制延迟<br>
在主从复制中,从节点的数据是异步复制⾃主节点的。这意<br>味着在主节点故障时,从节点可能还没有完全同步最新的数<br>据,从⽽导致数据丢失。
故障检测和转移时间
Sentinel 检测到主节点故障并执⾏故障转移需要⼀定的时<br>间。在这段时间内,主节点可能已经接收了⼀些写操作,但<br>这些操作尚未被复制到从节点。
⽹络分区<br>
在发⽣⽹络分区(⽹络分裂)的情况下,⼀部分节点可能与
主节点失去联系。如果此时主节点继续处理写操作,那么在
⽹络恢复之前,这些操作可能不会被复制到从节点。
多个从节点同时故障<br>
如果所有的从节点同时故障或在故障转移之前与主节点失
联,那么在主节点故障时,将没有可⽤的从节点来提升为主
节点。
主从模式
作用
数据冗余:主从复制实现了数据的热备份,是持久化之外的⼀<br>种数据冗余⽅式。
故障恢复:当主节点出现问题时,可以由从节点提供服务,实<br>现快速的故障恢复;实际上是⼀种服务的冗余。
负载均衡:在主从复制的基础上,配合读写分离,可以由主节<br>点提供写服务,由从节点提供读服务(即写Redis数据时应⽤连接主节点,读Redis数据时应⽤连接从节点),分担服务器负载;尤其是在写少读多的场景下,通过多个从节点分担读负载,可以⼤⼤提⾼Redis服务器的并发量。
⾼可⽤基⽯:除了上述作⽤以外,主从复制还是哨兵和集群能<br>够实施的基础,因此说主从复制是Redis⾼可⽤的基础。
配置
从节点需要添加主节点信息<br>
# 添加主节点信息
replicaof 192.168.75.129 6379
SLAVEOF host port<br>
在运⾏期间修改slave节点的信息。如果该服务已经是某个主库的从库了,那么就会停⽌和原master的同步关系。<br>
缺点
复制延时,信号衰减<br>
主节点挂了后,不能提供写能力,需要人工干预
简单总结:主从复制。当Master数据有变化时,⾃动将新的数据异步同步到其他slave中。<br>
如何确定主从状态
客户端登录进redis服务后执行:info replication查看<br>
对于slave从节点,虽然禁⽌了对数据的写操作,但是并没有禁⽌CONFIG、DEBUG等管理指令,这些指令如果和主节点不⼀致,还是容易造成数据不⼀致。如果为了安全起⻅,可以使⽤rename-command⽅法屏蔽这些危险的指令。<br>
如果从节点已经有数据了,同步会做如何处理?<br>
salve数据被master覆盖
复制流程
全量复制参考图
<br>
1.Slave启动后,向master发送⼀个sync请求。等待建⽴成功后,slave会删除掉⾃⼰的数据⽇志⽂件,等待主节点同步。
2.master接收到slave的sync请求后,会触发⼀次RDB全量备份,同时收集所有接收到的修改数据的指令。然后master将RDB和操作指令全量同步给slave。完成第⼀次全量同步。
3.主从关系建⽴后,master会定期向slave发送⼼跳包,确认slave的状态。⼼跳发送的间隔通过参数repl-ping-replica-period指定。默认10秒。<br>
4.只要slave定期向master回复⼼跳请求,master就会持续将后续收集到的修改数据的指令传递给slave。同时,master会记录offset,即已经同步给slave的消息偏移量。
5.如果slave短暂不回复master的⼼跳请求,master就会停⽌向slave同步数据。直到slave重新上线后,master从offset开始,继续向slave同步数据。
断点续传参考图
<br>
单节点部署模式
环境:需要安装gcc,yun install gcc<br>
下载:wget https://download.redis.io/redis-stable.tar.g<br>z<br>
关闭防火墙:systemctl stop firewalld.service<br>
查看防火墙状态:firewall-cmd --state<br>
修改vim redis.conf<br>
1.bind * -::* #87⾏,修改bind 项,* -::* ⽀持远程连接<br>2.daemonize yes #309⾏,开启守护进程,后台运⾏<br>3.logfile /opt/software/redis/redis-stable/redis.log #355⾏,指定⽇志⽂件⽬录<br>4.dir /opt/software/redis #510⾏,指定⼯作⽬录<br>5.requirepass 1qaz@WSX #1044⾏,给默认<br>⽤户设置密码,主要是使⽤ redis-cli 连接 redis-server<br>时,需要通过密码校验。⾃⾏学习,可以不设置。<br>6.protected-mode no #111⾏,允许远程连接 如果不设置密码必须讲此设置关闭。
启动:./src/redis-server redis.conf<br>
缺点:一旦发生单点故障 redis服务即不可用<br>
基础数据结构
string结构
常用操作
set key value
存入字符串键值对
MSET key value [key value ...]<br>
批量存储字符串键值对<br>
setnx key value<br>
存入一个不存在的字符串键值对
get key
获取key的值
MGET key [key ...]<br>
批量获取字符串键值
del key<br>
删除一个键
EXPIRE key seconds
设置一个键的过期时间(秒)
原子加减
INCR key
将key中储存的数字值加1
DECR key<br>
将key中储存的数字值减1<br>
INCRBY key increment<br>
将key所储存的值加上increment
DECRBY key decrement<br>
将key所储存的值减去decrement
应用场景
单值缓存
对象缓存:如用户token<br>
分布式锁
SET product:10001 true ex 10 nx //防止程序意外终止导致死锁
底层对应结构
embstr
sds动态字符串
存入的值如果小于44长度,redis底层重新编码后,redisobject里的指针指向sds,并且将sds压缩到redisobejct地址后面,方便读取
int
如果存入的值可以转换为long类型,底层就会用数组来存储,并且有1-1000的缓存值,如果在1000内直接取内存值。<br>
raw
如果value是⼀个字符串类型,并且⻓度⼤于44字节,就会⽤raw保存
固定链接的内存地址
sds8
sds16
sds32
sds64
hash结构
常用操作
HSET key field value<br>
存储一个哈希表key的键值
HSETNX key field value<br>
存储一个不存在的哈希表key的键值
HMSET key field value [field value ...]<br>
在一个哈希表key中存储多个键值对
HGET key field<br>
获取哈希表key对应的field键值
HMGET key field [field ...]<br>
批量获取哈希表key中多个field键值
HDEL key field [field ...]<br>
删除哈希表key中的field键值<br>
HLEN key<br>
返回哈希表key中field的数量
HGETALL key<br>
返回哈希表key中所有的键值<br>
HINCRBY key field increment<br>
为哈希表key中field键的值加上增量increment
应用场景
对象缓存
购物车
<br>
优缺点
优点
1)同类数据归类整合储存,方便数据管理<br>2)相比string操作消耗内存与cpu更小<br>3)相比string储存更节省空间
缺点
1) 过期功能不能使用在field上,只能用在key上<br>2) Redis集群架构下不适合大规模使用<br>
底层数据结构
listpack
如何判断value⾥的数据少,涉及到两个参数。hash-max-listpack-entries 限制value⾥键值对的个数(默认512),hash-max-listpack-value 限制value⾥值的数据⼤⼩(默认64字<br>节)。
从6的ziplist升级为listpack结构的原因
解决连锁更新问题
hashtable
如果hash对象保存的键值对超过512个,或者所有键值对的字符串⻓度超过64字节,底层的数据结构就会由listpack升级成为hashtable。
对于同⼀个hash数据,listpack结构可以升级为hashtable结构,但是hashtable结构不会降级成为listpack。
list结构
常用操作
LPUSH key value [value ...]
将一个或多个值value插入到key列表的表头(最左边)<br>
RPUSH key value [value ...]
将一个或多个值value插入到key列表的表尾(最右边)
LPOP key<br>
移除并返回key列表的头元素<br>
RPOP key<br>
移除并返回key列表的尾元素
LRANGE key start stop<br>
返回列表key中指定区间内的元素,区间以偏移量start和stop指定
BLPOP key [key ...] timeout<br>
从key列表表头弹出一个元素,若列表中没有元素,阻塞等待<br>timeout秒,如果timeout=0,一直阻塞等待
BRPOP key [key ...] timeout<br>
从key列表表尾弹出一个元素,若列表中没有元素,阻塞等待<br>timeout秒,如果timeout=0,一直阻塞等待
应用场景
Stack(栈) = LPUSH + LPOP<br>Queue(队列)= LPUSH + RPOP<br>Blocking MQ(阻塞队列)= LPUSH + BRPOP
视频列表、签到列表<br>排队机<br>简化版的MQ
<br>
注意点
1)一个list的容量是2的32次方减1个元素,大概40多亿。但是在应用时,要注意大key的<br>问题。<br>2)list的底层是一个双向链表,对双端的操作性能很高。但是通过索引下表直接操作某一<br>个中间节点的性能就会比较低。
底层数据结构
listpack
quicklist
<br>
set结构
常用操作
SADD key member [member ...]
往集合key中存入元素,元素存在则忽略,<br>若key不存在则新建
SREM key member [member ...]<br>
从集合key中删除元素
SMEMBERS key<br>
获取集合key中所有元素
SCARD key<br>
获取集合key的元素个数
SISMEMBER key member<br>
判断member元素是否存在于集合key中
SRANDMEMBER key [count]<br>
从集合key中选出count个元素,元素不从key中删除<br>
SPOP key [count]<br>
从集合key中选出count个元素,元素从key中删除
运算操作
SINTER key [key ...]<br>
交集运算
SINTERSTORE destination key [key ..]<br>
将交集结果存入新集合destination中<br>
SUNION key [key ..]<br>
并集运算
SUNIONSTORE destination key [key ...]<br>
将并集结果存入新集合destination中
SDIFF key [key ...]<br>
差集运算
SDIFFSTORE destination key [key ...]<br>
将差集结果存入新集合destination中
应用场景
微信抽奖小程序
1)点击参与抽奖加入集合<br>SADD key {userlD}<br>2)查看参与抽奖所有用户<br>SMEMBERS key<br>3)抽取count名中奖者<br>SRANDMEMBER key [count] / SPOP key [count]
微信微博点赞,收藏,标签<br>
1) 点赞<br>SADD like:{消息ID} {用户ID}<br>2) 取消点赞<br>SREM like:{消息ID} {用户ID}<br>3) 检查用户是否点过赞<br>SISMEMBER like:{消息ID} {用户ID}<br>4) 获取点赞的用户列表<br>SMEMBERS like:{消息ID}<br>5) 获取点赞用户数<br>SCARD like:{消息ID}
集合操作<br>
SINTER set1 set2 set3 -> { c } 共同关注的人<br>SUNION set1 set2 set3 ->{ a,b,c,d,e } 朋友圈的人<br>SDIFF set1 set2 set3 -> { a } 推荐好友<br>
<br>
底层结构
intset
listpack<br>
hashtable
zset结构<br>
常用操作
ZADD key score member [[score member]…]
往有序集合key中加入带分值元素
ZREM key member [member …]<br>
从有序集合key中删除元素
ZSCORE key member<br>
返回有序集合key中元素member的分值
ZINCRBY key increment member
为有序集合key中元素member的分值加上increment
ZCARD key<br>
返回有序集合key中元素个数
ZRANGE key start stop [WITHSCORES]<br>
正序获取有序集合key从start下标到stop下标的元素<br>
ZREVRANGE key start stop [WITHSCORES]<br>
倒序获取有序集合key从start下标到stop下标的元素
运算操作
ZUNIONSTORE destkey numkeys key [key ...] <br>
并集计算
ZINTERSTORE destkey numkeys key [key …]<br>
交集计算
应用场景
Zset集合操作实现排行榜<br>
1)点击新闻<br>ZINCRBY hotNews:20190819 1 守护香港<br>2)展示当日排行前十<br>ZREVRANGE hotNews:20190819 0 9 WITHSCORES<br>3)七日搜索榜单计算<br>ZUNIONSTORE hotNews:20190813-20190819 7<br>hotNews:20190813 hotNews:20190814... hotNews:20190819<br>4)展示七日排行前十<br>ZREVRANGE hotNews:20190813-20190819 0 9 WITHSCORES
<br>
底层数据结构
listpack<br>
skiplist
bitmap结构<br>
常用操作
SETBIT key offset value<br>
将一个二进制数组的offset位置设置成value。value只能是0或者1。
GETBIT key offset<br>
返回一个二进制数组的offset位置的值<br>
BITCOUNT key [start end [BYTE|BIT]]
返回二进制数组中1的个数
BITPOS key bit [start [end [BYTE|BIT]]]<br>
返回bitmap中第一个值为bit的offset位置。
BITOP AND|OR|XOR|NOT destkey key [key ...]<br>
对两个bitmap做二进制的与或非计算。
应用场景
每日签到
SETBIT dailycheck:1 100 1 1号用户第100天完成了签到<br>BITCOUNT dailycheck:1 统计1号用户的签到次数<br>BITPOS dailycheck:1 统计1号用户第一天签到的时间
优点
快速、高效、节省空间
<br>
Hyperloglog类型
作用
用于统计一个集合中不重复的元素个数。<br>典型应用场景例如根据用户访问记录统计网站的UV。
常用操作<br>
PFADD visitlog 192.168.65.111 192.168.65.112 192.168.65.111<br>
添加用户访问记录
PFCOUNT visitlog<br>
统计不同的独立访客
其他操作
PFMERGE destkey [sourcekey [sourcekey ...]]
将多个hyperloglong数据整合成一条记录。
Geo类型
常用操作
GEOADD key [NX|XX] [CH] longitude latitude member [longitude latitude member ...]
添加一个或多个地点
GEOPOS key [member [member ...]]
返回地址的经纬度
GEODIST key member1 member2 [M|KM|FT|MI]<br>
计算两个地点之间的距离
GEORADIUS key longitude latitude radius M|KM|FT|MI [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count<br>[ANY]] [ASC|DESC] [STORE key|STOREDIST key]
查询某个经纬度地址附近的地点<br>
GEOSEARCH key FROMMEMBER member|FROMLONLAT longitude latitude BYRADIUS radius<br>M|KM|FT|MI|BYBOX width height M|KM|FT|MI [ASC|DESC] [COUNT count [ANY]] [WITHCOORD]<br>[WITHDIST] [WITHHASH]<br>
查询某个地点附近的地点
应用场景
添加商家地址<br>GEOADD changsha 113.017489 28.200454 火车站<br>112.96903 28.201195 橘子洲 113.017031 28.199706 赛格广<br>场 113.017004 28.197677 国储<br>• 查询距离<br>GEODIST changsha 火车站 橘子洲 M<br>• 查找火车站附近的景点<br>GEORADIUSBYMEMBER changsha 火车站 2 KM withdist<br>withcoord count 4 withhash
stream类型
作用
Redis版的MQ -- 阻塞队列 + pub/sub
常用操作
XADD key [NOMKSTREAM] [MAXLEN|MINID [=|~] threshold [LIMIT count]] *|id field value [field value ...]<br>
往对列的末尾发布一条消息
XDEL key id [id ...]<br>
删除队列中的一条消息<br>
XLEN key<br>
获取队列的长度
XRANGE key start end [COUNT count<br>
查询队列中的消息
应用
• 创建队列,并添加消息 *表示让系统自动生成ID<br>XADD mystream * name loulan name roy name admin<br>• 查看对列消息 - 对列开始 + 对列结尾<br>XRANGE mystream - +<br>• 创建消费者组 0 从队列头部开始消费。 $ 从队列尾部开始消费<br>XGROUP CREATE mystream groupA 0<br>• 消费消息 > 表示从第一条未被消费过的消息消费。也可以指定ID<br>XREADGROUP GROUP groupA consumer1 count 2 STREAMS mystream ><br>• 查看消费者组的消费进度<br>XPENDING mystream groupA
redis6与redis7上层应用于底层结构区别
<br>
上层结构与底层结构沟通使用的是redis封装的
redisobject
<br>
type
上层结构使用
encoding
查看底层数据结构使用
object encoding key
线程模型
客户端多线程,服务端单线程<br>
<br>
在服务端,Redis响应⽹络IO和键值对读写的请求,则是由⼀个单独的主线程完成的。Redis基于epoll实现了IO多路复⽤,这就可以⽤⼀个主线程同时响应多个客户端Socket连接的请求。
Redis6.x和redis7.x版本中
Redis后端的很多⽐较费时的操作,⽐如持久化RDB,AOF⽂件、unlink异步删除、集群数据同步等,都是由额外的线程执⾏的。例如,对于 FLUSHALL操作,就已经提供了异步的⽅式。
如何保证原子性
复合指令
Redis内部提供了很多复合指令,他们是⼀个指令,可是明显⼲着多个指令的活。<br>⽐如 MSET(HMSET)、GETSET、SETNX、SETEX。这些复合指令都能很好的保持<br>原⼦性。
redis事务<br>
Redis的事务并不是像数据库的事务那样,保证事务中的指令⼀起成功或者⼀起失败。Redis的事务作⽤,仅仅只是保证事务中的原⼦操作是⼀起执⾏,⽽不会在执⾏过程中被其他指令加塞。
Redis事务失败如何回滚?<br>
Redis中的事务回滚,不是回滚数据,⽽是回滚操作。
如果事务是在EXEC执⾏前失败(⽐如事务中的指令敲错了,或者指令的参数不对),那么整个事务的操作都不会执⾏。
如果事务是在EXEC执⾏之后失败(⽐如指令操作的key类型不对),那么事务中的其他操作都会正常执⾏,不受影响。
事务执⾏过程中出现失败了怎么办?<br>
只要客户端执⾏了EXEC指令,那么就算之后客户端的连接断开了,事务就会⼀直进⾏下去。
事务有可能造成数据不⼀致。
使⽤redis-check-aof⼯具修复AOF⽂件,将这些不完整的事务操作记录移<br>除掉。
pipeline管道<br>
cat command.txt | redis-cli -a 123qweasd -<br>-pipe
如果你有⼤批量的数据需要快速写⼊到Redis中,这种⽅式可以⼀定程度提⾼<br>执⾏效率
核⼼作⽤
优化RTT(round-trip time)
当客户端执⾏⼀个指令,数据包需要通过⽹络从Client传到Server,然后再从Server返回到Client。这个中间的时间消耗,就称为RTT
注意点
pipeline不具备原⼦性。pipeline只是将多条命令发送到服务端,最终还是可能会被其他客户端的指令加塞的,虽然这种概率通常⽐较⼩。所以在pipeline中通常不建议进⾏复杂的数据操作。同时,这也表明,执⾏复合指令和事务,会阻塞其他命令执⾏,⽽执⾏pipeline不会。
pipeline在执⾏过程中,会阻塞当前客户端。在pipeline中不建议拼装过多的指令。
pipeline机制适合做⼀些在⾮热点时段进⾏的数据调整任务。
Lua脚本
什么是lua脚本
lua是⼀种⼩巧的脚本语⾔,他拥有很多⾼级语⾔的特性。⽐如参数类型、作⽤域、函数等。
EVAL script numkeys [key [key ...]] [arg [arg ...]]
script参数是⼀段Lua脚本程序,它会被运⾏在Redis服务器上下⽂中,这段脚<br>本不必(也不应该)定义为⼀ 个Lua函数。<br>numkeys参数⽤于指定键名参数的个数。键名参数 key [key ...] 从EVAL的第三<br>个参数开始算 起,表示在脚本中所⽤到的那些Redis键(key),这些键名参数可<br>以在 Lua中通过全局变量KEYS数组,⽤1 为基址的形式访问( KEYS[1] ,<br>KEYS[2] ,以此类推)。<br>在命令的最后,那些不是键名参数的附加参数 arg [arg ...] ,可以在Lua中通过<br>全局变量ARGV数组访问, 访问的形式和KEYS变量类似( ARGV[1] 、 ARGV[2]<br>,诸如此类)。
注意点
1.不要在Lua脚本中出现死循环和耗时的运算,否则redis会阻塞,将不接受其他的命令。相⽐之下,管道pipeline不会阻塞redis。<br>Redis中有⼀个配置参数来控制Lua脚本的最⻓控制时间。默认5秒钟。当lua脚本执⾏时间超过了这个时⻓,Redis会对其他操作返回⼀个BUSY错误,⽽不会⼀直阻塞。
2.尽量使⽤只读脚本
只读脚本是Redis7中新增的⼀种脚本执⾏⽅法,表示那些不修改Redis数据集的只读脚本。需要在脚本上加上⼀个只读的标志,并通过指令EVAL_RO触发。在只读脚本中不允许执⾏任何修改数据集的操作,并且可以随时使⽤SCRIPT_KILL指令停⽌。
3.热点脚本可以缓存到服务端<br>
Redis Function
在Redis7之后,提供了另外⼀种让程序员解脱<br>的⽅法-Redis Function。
Redis Function允许将⼀些功能声明成⼀个统⼀的函数,提前加载到Redis服务端<br>(可以由熟悉Redis的管理员加载)。客户端可以直接调⽤这些函数,⽽不需要再去开发函数的具体实现。
案例
文件名mylib.lua<br>
#!lua name=mylib<br>local function my_hset(keys, args)<br>local hash = keys[1]<br>local time = redis.call('TIME')[1]<br>return redis.call('HSET',<br>hash, '_last_modified_', time, unpack(args))<br>end<br>redis.register_function('my_hset', my_hset)
redis 加载funcation<br>
cat mylib.lua | redis-cli -a<br>123qweasd -x FUNCTION LOAD REPLACE
查看funcation以及调用
127.0.0.1:6379> FUNCTION LIST<br>1) 1) "library_name"<br>2) "mylib"<br>3) "engine"<br>4) "LUA"<br>5) "functions"<br>6) 1) 1) "name"<br>2) "my_hset"<br>3) "description"<br>4) (nil)<br>5) "flags"<br>6) (empty array)<br>127.0.0.1:6379> FCALL my_hset 1 myhash myfield "some value"<br>another_field "another value"<br>(integer) 3<br>127.0.0.1:6379> HGETALL myhash<br>1) "_last_modified_"<br>2) "1717748001"<br>3) "myfield"<br>4) "some value"<br>5) "another_field"<br>6) "another value"
注意点
Function同样也可以进⾏只读调⽤。
如果在集群中使⽤Function,⽬前版本需要在各个节点都⼿动加载⼀次。Redis不会在集群中进⾏Function同步
Function是要在服务端缓存的,所以不建议使⽤太多太⼤的Function。<br>
Function和Script⼀样,也有⼀系列的管理指令。使⽤指令 help @scripting ⾃⾏了解。
数据安全性分析<br>
存储方式原理分析
RDB
优点
RDB⽂件⾮常紧凑,⾮常适合定期备份数据。<br>
RDB快照⾮常适合灾难恢复。<br>
RDB备份时性能⾮常快,对主线程的性能⼏乎没有影响。RDB备份时,主线程只需要启动⼀个负责数据备份的⼦线程即可。所有的备份⼯作都由⼦线程完成,这对主线程的IO性能⼏乎没有影响。-->bgsave指令<br>
与AOF相⽐,RDB在进⾏⼤数据量重启时会快很多。<br>
缺点
RDB不能对数据进⾏实时备份,所以,总会有数据丢失的可能。<br>
RDB需要fork化⼦线程的数据写⼊情况,在fork的过程中,需要将内存中的数据克隆⼀份。如果数据量太⼤,或者CPU性能不是很好,RDB⽅式就容易造成Redis短暂的服务停⽤。相⽐之下,AOF也需要进⾏持久化,但频率较低。并且你可以调整⽇志重写的频率。
核心配置
save 3600 1 300 100 60 10000
每隔1小时如果达到一次修改保存一次,每隔5分钟达到100次修改,保存一次,每隔一分钟 达到1万次修改保存一次
dir ⽂件⽬录
dbfilename ⽂件名 默认dump.rdb<br>
rdbcompression 是否启⽤RDB压缩,默认yes。 如果不想消耗CPU进⾏压缩,可以设置为no<br>
top-writes-oin-bgsave-error 默认yes。如果配置成no,表示你不在乎数据不⼀致或者有其他的⼿段发现和控制这种不⼀致。在快照写⼊失败时,也能确保redis继续接受新的写⼊请求。
rdbchecksum 默认yes。在存储快照后,还可以让redis使⽤CRC64算法来进⾏数据校验,但是这样做会增加⼤约10%的性能消耗。如果希望获得最⼤的性能提升,可以关闭此功能。
何时触发RDB
到达配置⽂件中默认的快照配置时,会⾃动触发RDB快照
⼿动执⾏save或者bgsave指令时,会触发RDB快照。 其中save⽅法会在备份期间阻塞主线程。<br>bgsve则不会阻塞主线程。但是他会fork⼀个⼦线程进⾏持久化,这个过程中会要将数据复制⼀份,因此会占⽤更多内存和CPU。
主从复制时会触发RDB备份。
AOF
优点
AOF持久化更安全。例如Redis默认每秒进⾏⼀次AOF写⼊,这样,即使服务崩溃,最多损失⼀秒的操作。<br>
AOF的记录⽅式是在之前基础上每次追加新的操作。因此AOF不会出现记录不完整的情况。即使因为⼀些特殊原因,造成⼀个操作没有记录完整,也可以使⽤redis-check-aof⼯<br>具轻松恢复。
当AOF⽂件太⼤时,Redis会⾃动切换新的⽇志⽂件。这样就可以防⽌单个⽂件太⼤的问题。<br>
AOF记录操作的⽅式⾮常简单易懂,你可以很轻松的⾃⾏调整⽇志。⽐如,如果你错误的执⾏了⼀次 FLUSHALL 操作,将数据误删除了。使⽤AOF,你可以简单的将⽇志中最后<br>⼀条FLUSHALL指令删掉,然后重启数据库,就可以恢复所有数据。
缺点
针对同样的数据集,AOF⽂件通常⽐RDB⽂件更⼤。<br>
在写操作频繁的情况下,AOF备份的性能通常⽐RDB更慢。
重要配置
appendonly 是否开启aof。 默认是不开启的。<br>
appendfilename ⽂件名称。<br>
Redis7中,对⽂件名称做了调整。原本只是⼀个⽂件,现在换成了三个⽂件。base.rdb⽂件即⼆进制的数据⽂件。incr.aof是增量的操作⽇志。manifest则是记录⽂件信息的元⽂件。其实在Redis7之前的版本中,aof⽂件也会包含⼆进制的RDB部分和⽂本的AOF部分。在Redis7中,将这两部分分成了单独的⽂件,这样,即可以分别⽤来恢复⽂件,也便于控制AOF⽂件的⼤⼩。
<br>
appendfsync 同步⽅式。默认everysecond 每秒记录⼀次。no 不记录(交由操作系统进⾏内存刷盘)。 always 记录每次操作,数据更安全,但性能较低。
appenddirname AOF⽂件⽬录。新增参数,指定aof⽇志的⽂件⽬录。 实际⽬录是 {dir}+{appenddirname}
auto-aof-rewrite-percentage, auto-aof-rewrite-min-size ⽂件重写触发策略。默认每个⽂件64M, 写到100%,进⾏⼀次重写。
AOF重写也可以通过指令 BGREWRITEAOF ⼿动触发
no-appendfsync-on-rewrite: aof重写期间是否同步<br>
AOF日志恢复
aof出现日志记录不完整的情况<br>
使用操作:redis-check-aof --fix<br>
修复aof日志
一般采用RDB与AOF混合的方式<br>
配置:aof-use-rdb-preamble yes<br>
存储策略
1.⽆持久化:完全关闭数据持久化,不保证数据安全。相当于将Redis完全当做缓存来⽤<br>2.RDB(RedisDatabase):按照⼀定的时间间隔缓存Redis所有数据快照。<br>3.AOF(Append Only File):记录Redis收到的每⼀次写操作。这样可以通过操作重演的⽅式恢复Redis的数据<br>4.RDB+AOF:同时保存Redis的数据和操作。
性能压测工具
redis-benchmark
# 20个线程,100W个请求,测试redis的set指令(写数据)<br>redis-benchmark -a 123qweasd -t set -n 1000000 -c 20
高并发分布式锁实战
redisson
加锁流程
<br>
高并发缓存设计与优化
缓存穿透
查询一个根本不存在的数据, 缓存层和存储层都不会命中, 通常出于容错的考虑, 如果从存储层查不到数据则不写入缓存层。
危害
缓存穿透将导致不存在的数据每次请求都要到存储层去查询, 失去了缓存保护后端存储的意义
原因
自身业务代码或者数据出现问题<br>
一些恶意攻击、 爬虫等造成大量空命中<br>
解决方案
1.缓存空对象,并设置过期时间
2.布隆过滤器
对于不存在的数据布隆过滤器一般都能够过滤掉,不让请求再往后端发送
当布隆过滤器说某个值存在时,这个值可<br>能不存在;当它说不存在时,那就肯定不存在。
参考代码
<br>
注意
布隆过滤器不能删除数据,如果要删除得重新初始化数据。
缓存失效/击穿<br>
由于大批量缓存在同一时间失效可能导致大量请求同时穿透缓存直达数据库,可能会造成数据库瞬间压力过大甚至挂掉
解决方案
这种情况我们在批量增加缓存时最好将这一批数据的缓存过期时间设置为一个时间段内的不同时间。
缓存雪崩
缓存雪崩指的是缓存层支撑不住或宕掉后, 流量会像奔逃的野牛一样, 打向后端存储层。
解决与预防方案
保证缓存层服务高可用性,比如使用Redis Sentinel或Redis Cluster。<br>
依赖隔离组件为后端限流熔断并降级。比如使用Sentinel或Hystrix限流降级组件。
提前演练。 在项目上线前, 演练缓存层宕掉后, 应用以及后端的负载情况以及可能出现的问题, 在此基础上做一些预案设定。
冷数据突发变热点解决方案<br>
通过分布式锁,允许一个线程重建缓存,而不是所有的线程去查询然后重建缓存
缓存与数据双写不一致问题
大并发下,同时操作数据库与缓存会存在数据不一致性问题
解决方案
1.对于并发几率很小的数据(如个人维度的订单数据、用户数据等),这种几乎不用考虑这个问题,很少会发生缓存不一致,可以给缓存数据加上过期时间,每隔一段时间触发读的主动更新即可
2、 就算并发很高,如果业务上能容忍短时间的缓存数据不一致(如商品名称,商品分类菜单等),缓存加上过期<br>时间依然可以解决大部分业务对于缓存的要求。
3、如果不能容忍缓存数据不一致,可以通过加分布式读写锁保证并发读写或写写的时候按顺序排好队,读读的<br>时候相当于无锁。<br>
4、也可以用阿里开源的canal通过监听数据库的binlog日志及时的去修改缓存,但是引入了新的中间件,增加了系统的复杂度。
key-value优化
key优化<br>
可读性和可管理性
以业务名(或数据库名)为前缀(防止key冲突),用冒号分隔,比如业务名:表名:id
简洁性<br>
保证语义的前提下,控制key的长度,当key较多时,内存占用也不容忽视
不要包含特殊字符<br>
反例:包含空格、换行、单双引号以及其他转义字符
value优化
拒绝bigkey(防止网卡流量、慢查询)<br>
字符串类型:它的big体现在单个value值很大,一般认为超过10KB就是bigkey。
非字符串类型:哈希、列表、集合、有序集合,它们的big体现在元素个数太多。<br>一般来说,string类型控制在10KB以内,hash、list、set、zset元素个数不要超过5000。
控制key的生命周期,redis不是垃圾桶。
客户端优化
1.避免多个应用使用一个redis
不相干的业务拆分,公共数据做服务化。
使用redis链接池
<br>
过期策略
1.被动删除<br>
当读/写一个已经过期的key时,会触发惰性删除策略,直接删除掉这个过期key
2.主动删除<br>
由于惰性删除策略无法保证冷数据被及时删掉,所以Redis会定期主动淘汰一批已过期的key<br>
针对设置了过期时间的key做处理<br>
volatile-ttl:在筛选时,会针对设置了过期时间的键值对,根据过期时间的先后进行删<br>除,越早过期的越先被删除。
volatile-random:就像它的名称一样,在设置了过期时间的键值对中,进行随机删除。<br>
volatile-lru:会使用 LRU 算法筛选设置了过期时间的键值对删除。<br>
LRU 算法(Least Recently Used,最近最少使用)<br>淘汰很久没被访问过的数据,以最近一次访问时间作为参考。
volatile-lfu:会使用 LFU 算法筛选设置了过期时间的键值对删除。
LFU 算法(Least Frequently Used,最不经常使用)<br>淘汰最近一段时间被访问次数最少的数据,以次数作为参考。
针对所有的key做处理<br>
allkeys-random:从所有键值对中随机选择并删除数据。<br>
allkeys-lru:使用 LRU 算法在所有数据中进行筛选删除。<br>
allkeys-lfu:使用 LFU 算法在所有数据中进行筛选删除。
不处理<br>
noeviction:不会剔除任何数据,拒绝所有写入操作并返回客户端错误信息"(error)<br>OOM command not allowed when used memory",此时Redis只响应读操作。
注意/建议
根据自身业务类型,配置好maxmemory-policy(默认是noeviction),推荐使用volatile-lru。如果不设置最大内存,当 Redis 内存超出物理内存限制时,内存的数据会开始和磁盘产生频繁的交换 (swap),会让 Redis 的性能急剧下降。<br>当Redis运行在主从模式时,只有主结点才会执行过期删除策略,然后把删除操作”del key”同步到从结点删除数据。<br>
3.当前已用内存超过maxmemory限定时,触发主动清理策略
系统参数优化
vm.swapiness<br>
cat /proc/version #查看linux内核版本<br>echo 1 > /proc/sys/vm/swappiness<br>echo vm.swapiness=1 >> /etc/sysctl.conf
OOM killer 机制是指Linux操作系统发现可用内存不足时,强制杀死一些用户进程(非内核<br>进程),来保证系统有足够的可用内存进行分配
swappiness值越低,表示操作系统更加倾向于使用物理内存。swappiness的取值越大,说明操作系统可能使用<br>swap的概率越高,越低则越倾向于使用物理内存
vm.overcommit_memory(默认0)
0:表示内核将检查是否有足够的可用物理内存(实际不一定用满)供应用进程使用;如果有足够的可用物理内存,内存申请允许;否则,内存申请失败,并把错误返回给应用进程<br>
1:表示内核允许分配所有的物理内存,而不管当前的内存状态如何:如果是0的话,可能导致类似fork等操作执行失败,申请不到足够的内存空间Redis建议把这个值设置为1,就是为了让fork操作能够在低内存下也执行成功。<br>
1 cat /proc/sys/vm/overcommit_memory<br>2 echo "vm.overcommit_memory=1" >> /etc/sysctl.conf<br>3 sysctl vm.overcommit_memory=1
合理设置文件句柄
1 ulimit ‐a #查看系统文件句柄数,看open files那项<br>2 ulimit ‐n 65535 #设置系统文件句柄数
慢查询日志
1 Redis慢日志命令说明:<br>2 config get slow* #查询有关慢日志的配置信息<br>3 config set slowlog‐log‐slower‐than 20000 #设置慢日志使时间阈值,单位微秒,此处为20毫秒,即超过20<br>毫秒的操作都会记录下来,生产环境建议设置1000,也就是1ms,这样理论上redis并发至少达到1000,如果要求单<br>机并发达到1万以上,这个值可以设置为100<br>4 config set slowlog‐max‐len 1024 #设置慢日志记录保存数量,如果保存数量已满,会删除最早的记录,最新<br>的记录追加进来。记录慢查询日志时Redis会对长命令做截断操作,并不会占用大量内存,建议设置稍大些,防止丢<br>失日志<br>5 config rewrite #将服务器当前所使用的配置保存到redis.conf<br>6 slowlog len #获取慢查询日志列表的当前长度<br>7 slowlog get 5 #获取最新的5条慢查询日志。慢查询日志由四个属性组成:标识ID,发生时间戳,命令耗时,执<br>行命令和参数<br>8 slowlog reset #重置慢查询日志
收藏
收藏
0 条评论
下一页