本笔记来源于《Redis开发与运维》(付磊 张益军著)
初识Redis
单线程面向快速执行场景的内纯数据库
特性
- 数据结构:string字符串(位图bitmaps,hyperloglog),hash哈希表,list列表,set集合,zset有序集合
- 单线程模型
- 持久化方式:RDB,AOF
- 主从复制
数据结构 & API
object encoding testObject
:查看testObject内部编码
zipXxx
:是更加紧凑的数据结构,集合元素总数少(默认512,有序集合zset是128)且所有元素不大时(64 bytes)采用,否则用其他大容量结构
lpush + lpop = stack
lpush + rpop = queue
lpush + ltrim = 有限集合
lpush + brpop = 消息队列 # brpop阻塞方法
ttl : time to live # 过期时间s
pttl # 过期时间 ms
persist # 持久化,会消除过期时间
set # string set会移除过期时间
复制代码
不支持二级数据结构内部元素过期功能
命令 | 作用域 | 原子性 | 支持多个键 | 使用建议 |
---|---|---|---|---|
move | Redis实例内部 | 是 | 否 | 弃用 |
dump + restore | Redis实例之间 | 否 | 否 | 非原子,不用 |
migrate | Redis实例之间 | 是 | 是 | 原子,建议使用 |
单线程架构
命令到达服务端后,按队列依次单线程执行
epoll I/O多路复用提升IO性能
redis快的原因:
- 纯内存访问
- I/O多路复用:epoll
- 单线程避免线程竞争
keys全量遍历键可能导致redis阻塞,可以在从节点上执行,或者使用渐进式的scan代替(可能漏掉期间进行的更新)
附加功能
慢查询分析
慢查询只统计执行命令的时间,并不统计网络通信和命令排队的时间
slowlog-log-slower-than : 0记录所有命令,<0不记录,>0记录超过该阈值的命令(微妙μs),默认10000,并发量100
slowlog-max-len : 按列表储存,日志最大数量
复制代码
redis shell
--bigkeys : 使用scan命令对键采样,找到内存占用较大的键值
--latency/latency-history/dist : 检测网络延迟
--stat:实时获取统计信息
复制代码
pipeline
将一组redis命令组装,通过一次RTT(Round Trip Time往返时间)传输给Redis,再将执行结构一次性按序返回
pipeline非原子操作
事务与Lua
事务
watch key # 此代码后到事务执行前,key没有被其他客户端修改过才执行事务
multi
cmd...
exec
discard #停止事务
复制代码
命令写错了,不执行事务 运行时错误,已执行的不回滚,redis不支持事务回滚
Lua
Lua脚本为原子执行
eval:执行lua脚本 eval 脚本内容 key个数 key列表 参数列表
evalsha:服务器复用脚本 evalsha 脚本SHA1值 key个数 key列表 参数列表
script kill:杀掉正在执行的Lua脚本
bitmaps
bitmaps本质上是字符串,不是一种数据结构
HyperLogLog
HyperLogLog本质上是字符串,不是一种数据结构
可以利用极小的内存空间完成独立总数的统计
近似估计,有失误率,用精度换取空间
Redis阻塞
-
定位异常节点
可以修改客户端connection类,专门捕获连接,发送命令和协议读取事件的异常,打印出对应的ip port,定位阻塞节点
-
排查内在原因
API或数据结构使用不合理 CPU饱和 与持久化相关的阻塞
redis内存
哨兵 sentinel
客户端在初始化时是连接sentinel集合获取master节点信息sentinel节点之间可以共享数据
sentinel可以同时监控多个主节点
- 每个sentinel会对数据节点和其他sentinel节点进行监控,发现节点不可达时,对节点做下线标识
- 被下线标识的是master节点,则sentinel会和其他sentinel节点协商,当大部分sentinel节点认为master节点不可达,就选举出一个sentinel节点完成故障自动转移
leader sentinel选举(Raft算法):任何一个sentinel确认主节点主观下线后,立即向其他所有sentinel节点发送投票命令(设置发送方sentinel为leader),其他节点未同意的才能同意请求,否则拒绝,当至少有max(quorum,sentinels/2+1)
个sentinel节点同意成立,否则进行下一次选举
类似zookeeper leader选举,本质上都是多数投票,未选出结果就多轮投票
# sentinel.conf
# sentinel节点配置了master节点信息,会从master节点获取其他slave节点信息
# 2: 1)至少2(quorum)个sentinel认为主节点不可达,才可以判定不可达
# 2)至少有max(2,sentinels/2+1)个节点参与选举,才能选出leader sentinel
sentinel monitor master-1 10.211.55.10 6381 2
# 每个sentinel ping其余所有sentinel节点和数据节点,超时5000ms则判定节点失效
sentinel down-after-milliseconds master-1 5000
# 故障转移超时时间18s
sentinel failover-timeout master-1 18000
# 添加主节点密码,防止无法监控
sentinel auth-pass master-1 test123
# 每次故障转移,限制每次2个slave节点向新master节点发起复制,减少对master机器影响
sentinel parallel-syncs master-1 2
复制代码
tips
- sentinel节点不应该部署到同一个机器上
- 奇数个sentinel节点
集群
槽是redis集群管理数据的基本单位集群模式下只有处理槽的主节点才负责读写请求和集群槽等关键信息维护.而从节点只进行主节点数据和状态信息复制
Tips
redis实现分布式锁
前提:redis单线程执行
string的setnx和setxx命令:setnx键必须不存在才能设置成功,setxx键必须存在才能设置成功
public class RedisTool {
private static final String LOCK_SUCCESS = "OK";
//setnx如果已有key存在,则函数不会调用成功
private static final String SET_IF_NOT_EXIST = "NX";
private static final String SET_WITH_EXPIRE_TIME = "PX";
private static final Long RELEASE_SUCCESS = 1L;
/**
* 加锁
*/
public static boolean tryGetDistributedLock(Jedis jedis, String lockKey, String requestId, int expireTime) {
//requestId:让加锁和解锁是同一个客户端
//设置过期时间,即使客户端崩溃也可解锁,防止死锁
String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);
if (LOCK_SUCCESS.equals(result)) {
return true;
}
return false;
}
/**
* 释放分布式锁
*/
public static boolean releaseDistributedLock(Jedis jedis, String lockKey, String requestId) {
//Lua脚本:获取锁对应的value值,检查是否与requestId相等,如果相等则删除锁(解锁)
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
//eval()方法可以确保原子性
Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));
if (RELEASE_SUCCESS.equals(result)) {
return true;
}
return false;
}
}
复制代码
参考链接
- Redis 分布式锁的正确实现方式