前言:
前些日子完成了整个交易服务的缓存系统的构建,颇有些心得。就在公司组织了一场关于redis的技术分享,算是个我这几年对redis的经验总结。
这次主要分享redis的数据结构原理及缓存的方案。虽然公司内同事工作中时常用到redis,但对redis的底层结构及社区里的redis方案了解有限。😅 相信这次的分享对大家都有所帮忙,对我的kpi打分也有帮忙。😁
地址:
下面是ppt中的一些内容截图,另外完整的pdf已经推到github里,有兴趣的可以看下。https://github.com/rfyiamcool/share_ppt/blob/master/redis_qa.pdf



























文本 (for seo):
// xiaorui.cc
Redis经验之谈
xiaorui.cc
github.com/rfyiamcool
menu
数据结构
底层数据结构
功能点
使用经验
高级场景
Redis6
Redis数据结构
String 字符串
key value
List 列表
timeline
未读消息
简单消息队列
Hash 字典
一对多的关系映射
Set 集合
好友的交集, 并集, 差集
去重
Sorted Set 有序集合
排行榜
排序
Stream 流
类似kafka模型的消息队列
消费组, ack, 偏移量 …
Redis数据结构
bitmap 位图
用户签到
用户在线状态
geo 地理位置
查看附近的人
状态位标记
外卖
…
…
pubsub 发布订阅
不靠谱的发布订阅
hyperloglog 基数统计
每日访问的ip数统计
…
Redis底层数据结构
SDS
Dict
LinkedList
Inset
Ziplist
SkipTable
Quicklist
RadixTree
…
Redis数据结构组成
String
sds
Hash
ziplist
dict
Set
inset
dict
List
ziplist
quicklist
Sorted Set
hash + skiptable
Stream
radix-tree
key的规范
每个业务单独的database ( cluster不⽀持 )
加⼊项⽬的前缀
⼀级key不要超过千万
尽量都加⼊TTL
使⽤ { hashtag } 来绑定亲和性
Value的规范
选择合适的数据结构
长字符压缩存取 (snappy, msgpack, more…)
避免big key ( 删除和迁移时阻塞)
避免hot key (单点性能)
ziplist更省内存
hash (order_id_1000)
hash (order_id)
oid_1000
oid_1000
oid_1002
oid_1002
oid_1333
oid_1333
hash (order_id_2000)
oid_2111
oid_2111
oid_2333
oid_2333
hash-max-ziplist-entries = 1000
hash-max-ziplist-value = 128
省内存
连续内存, 紧凑的编码, 减少了碎⽚, 减少了指针引⽤
ziplist⽀持的数据结构
hash-max-ziplist-entries && hash-max-ziplist-value
list-max-ziplist-size
zset-max-ziplist-entries && zset-max-ziplist-value
持久化
RDB
快照备份
加载顺序
先 AOF
AOF
日志追加
always
every sec
RewriteAOF
混合模式 RDB + AOF
后 RDB
加载速度
快 RDB
慢 AOF
持久化
RDB
AOF
redis-main
rdb snapshot
child-process
fork
注意事项
避免使⽤O(n)的指令
keys *, hgetall, smembers, zrange all, lrange all
直接在redis.conf⾥rename-command阻塞指令
使⽤scan, hscan, sscan, zscan
使⽤unlink异步删除key
提⾼吞吐
使⽤pipeline批量传输, 减少⽹络RTT
使⽤多值指令 (mset, hmset)
使⽤script lua
⼲掉aof ?
(big key) or (hot key)
big key
scan / small range get
unlink (redis 4.0 async del)
hash shard
hot key
hash shard
redis lua
减少RTT消耗
⾃定义函数
保证多指令原⼦性
注意阻塞问题
redis module
自定义注册新命令
自定义新数据结构
性能比redis lua更强劲
redis4.0 以上
RedisJson
RedisBloom
RedisTimeSeries
more …
排查问题
外部 (慎用)
redis-cli monitor
内部
keyspace
slow log
redis-cli —latency
内存碎片
- - bigkeys
string, bytes空间
set, list, zset, hash, 元素个数
redis-rdb-tool
分析内存分布
memory usage key_name
memory stats
memory purge
阻塞及延迟
redis-cli --intrinsic-latency 10
redis-cli --latency-history
info -> instantaneous_ops_per_sec
info -> used_memory_human
connected_clients
单线程 ?
主线程
aof⽇志
bio线程
过期key的实现
惰性删除
定时删除
默认每100ms进行一次
从过期字典中随机取出 20 个键
删除这 20 个键中过期的键
如果过期键的比例超过 25%, 重复步骤 1 和 2
直到25ms最长执行时间退出
触发MaxMemory时尝试删除
缓存淘汰
CONFIG SET maxmemory 50 GB
volatile-lru (默认)
从设置过期数据集⾥查找最近最少使⽤
volatile-ttl
从设置过期的数据集⾥清理已经过期的
key.
volatile-random
从设置过期的数据集中任意选择数据淘汰
allkeys-lru
从数据集中挑选最近最少使⽤的数据淘汰
allkeys-random
从数据集中任意选择数据淘汰
no-enviction
不清理
事务
client
command
watch 监听key的变化
is
transaction
mutli 开启事务
exec 执⾏事务
discard 放弃事务
unwatch 放弃监听
pipeline vs 事务
watch/unwatch
watch
multi
queue
pipeline会被打断
事务不会被打断
redis不⽀持标准的acid事务
exec/discard
单机进化到多实例
什么是多实例
为什么要多实例化
多实例化需要注意什么?
单机多实例
what 多实例
6379
6380
6381
++
redis1
redis2
redis3
more
why 多实例
最⼤程度的使⽤内存
避免单实例RDB Write时
被kernel oom
使⽤swap造成阻塞.
copy on write will block
绕开redis单⼯作线程的问题
阻塞指令
系统调⽤
单实例启动太慢
busy event
扩展, 迁移, 内存随便整理
hashcrc & codec
more …
How 多实例
如 128G 内存
11G 为⼀个实例, 启动个10实例
redis server关闭⾃动rdb及aof
后台脚本来控制bgsave.
启动时也是⼀个个的启动
简约集群
主从模式
vip多线程版 twemproxy
codis
redis cluster
集群
master/slave
sentinal
sentinal
sentinal
update
proxy
master
write
rdb
psync
fullsync
read
slave
slave
codis vs redis cluster
cluster
codis
hash_tag
y
y
design
中⼼化
去中⼼化
pipeline
client move order
⽀持
slot
y
y
多租户
y
y
性能
high
this < cluster
code
复杂
简单
范围
⼴
也有不少⼤⼚
codis
codis-proxy
codis-proxy
zookeeper
redis
redis-m
主机组
redis-s
dashboard
redis cluster
常⽤场景
缓存
缓存⼀致性
缓存穿透
缓存击穿
缓存雪崩
分布式锁
redlock
延迟队列
…
缓存⼀致性
write cache -> write db
write db -> write cache
evict cache -> write db
write db -> evict cache
evit cache -> write db ->
evit cache
缓存⼀致性
write db -> write cache
更新cache失败
write cache -> write db
更新db失败
并发引起脏数据
client1更新了DB
client2更新了DB
client2更新cache
但client1覆盖了client2的cache
缓存⼀致性
evict cache -> write db
延迟引起脏数据
write db -> evict cache
脏数据概率⼩于先evit cache
client1先删除缓存
client1查询数据库, 得⼀个旧值
client2查询发现缓存不存在
client2将新值写⼊数据库
client2数据库查询得到旧值
client2删除缓存
client2将旧值写⼊缓存
client1将查到的旧值写⼊缓存
client1将新值写⼊数据库
evit cache -> write db -> evit cache
脏数据概率更⼩
规避脏数据
TTL
定时更新
Binlog订阅更新
Delay Queue
sync-srv
缓存⼀致性
所以, 在更新策略上, 难以保证绝对⼀致性, 但可以最终⼀致性
拼概率, 减少产⽣脏数据的可能
write db; write cache; binlog or ttl 双写
多数公司的选择
write db; binlog更新
didi, iqiyi
write db; evct cache 也是个好选择 !
facebook, 58
穿透&雪崩
穿透 (访问⼀个不存在的key)
在缓存中加⼊该key的null值
bloomfilter
雪崩 (⼤量key的失效)
不主动配置TTL
后台同步缓存时, 加⼊jitter ttl
击穿
击穿 (⼤量请求未缓存的key)
实现redis分段锁, 同样的请求争夺⼀把锁
拿到锁的去数据库查询
未拿到锁的等待, 再尝试访问缓存
缓存中还没有数据, 尝试数据库拿取
分布式锁
安全可靠
lua make ( compare and set ) !!!
say no
可重⼊锁
set + nx + ex
client_1
Redis
true
say yes
公平调度
say hard
{ bll_lock_key: ident }
client_2
set + nx + ex
false
redlock
client2
2/n + 1
推荐
最少5个实例
Redis
Redis
Redis
Redis
Redis
3个及以上拿到锁
未拿到锁的, 释放锁
client1
redis单节点性能
⼤约
单命令并发可达 10w TPS
管道及多指令可达 50w TPS
单命令时延在 150 us 左右
单节点
经历过的性能指标
1w 的稳定长连接
10w TPS
队列千万级别
百万数量key
单节点
维护经验
300个redis实例
30台服务器 (混部)
每个实例10G
约 3T 内存
集群
redis6
新增的resp3加⼊缓存特性
返回key的属性, ⽐如频率
更新范围
acl⽤户权限控制
控制命令及key
redis cluster proxy
兼容各类sdk
io多线程
io线程负责read, decode, encode, write
操作内存还是主线程
other ppt
redis cluster那些事⼉
https://github.com/rfyiamcool/share_ppt/blob/master/redis_cluster.pdf
redis之⾼级应⽤
https://github.com/rfyiamcool/share_ppt/blob/master/redis_advance.pdf
⼤话redis设计与实现
https://github.com/rfyiamcool/share_ppt/blob/master/rediscode.pdf
“Q&A!”
–峰云就她了
