Redis
特性
1.速度快 (基于内存)
10w OPS
单线程 (非阻塞IO)
2.持久化
Redis所有数据保持在内存中,对数据的更新将异步地保存到磁盘上。
方式RDB,AOF
3.多数据结构
string list hash set zset
bitmap 位图(0,1)
HyperLogLog 超小内存唯一值计数
GEO:地理信息定位
4.支持多语言
java php ruby nodejs …
5.功能丰富
发布订阅 Lua脚本 事务 pipeline
6.主从复复制
7.高可用 分布式
Redis典型应用场景
缓存系统
排行榜
计数器
社交网络
消息队列系统
实时系统
安装
1 | linux |
连接
1 | redis-cli -h 10.10.79.150 -p 6384 |
命令
keys [pattern]
dbszie
exists key
del key
expire key second
type key
ttl key (-2 key不存,-1存在但没有设置时间)
persist key
数据结构
string
上限512M
缓存 计数 分布式锁
get/set/del/incr/decr/incrby/decrby
记录网站每个用户个人主页的访问量?
incr userid:pageview(单线程∶无竞争)
缓存视频的基本信息(数据源在MySQL中)伪代码
1 | public VideoInfo get(long id) { |
分布式id生成器
incr id(原子操作)
set
setnx 不存在,才设置
setxx 存在,才设置
mget/mset 多值设置
getset key newvaule 设置新值返回旧值
append key value 追加到旧值
strlen key
getrange from to
hash
hget key field
hset key field val
hdel key field
1 | 127.0.0.1:6379> hset user:1:info age 23 |
hexist key field
hlen key
hmget key field1,field2….
hmset key field1 value1 field2 value2…fieldN valueN
hgetall key
hvals key 返回key所有val
hkeys 返回key所有filed
记录网站每个用户个人主页的访问量?
hincrby user:1:info pageview count
list
有序 重复 左右传输
rpush key val1 val2….
lpush
linsert key before|after value newvalue 在value 前|后 插入新值newvalue
lpop /rpop
lrem key count value
#根据count值,从列表中删除所有value相等的项
(1) count>0,从左到右,删除最多count个value相等的项
(2) count<0,从右到左,删除最多Math.abs(count)个value相等的项
(3) count=O,删除所有value相等的项
ltrim key start end
lrange key start end
lset key index newValue
#设置列表指定索引值为newValue
blpop key timeout
#lpop阻塞版本, timeout是阻塞超时时间,timeout=0为永远不阻塞
brpop key timeout
set
无序 不重复
sadd key element 。。。
srem key element 。。。
scard 集合大小
sismember key element 是否存在
srandmember 随机
smembers 所有
spop
抽奖系统 /tag标签
sdiff key1 key2 差集
sinter key1 key2 交集
sunion key1 key2 并集
共同关注好友、喜欢
SADD = Tagging
SPOP/SRANDMEMBER = Random item
SADD + SINTER = Social Graph
zset
有序集合
key | score | val |
---|---|---|
user:ranking | 1 | lisi |
44 | wuli | |
50 | sss |
zadd key score element (nlogn)
zrem key element …
zscore key element
zincrby key increscore element 增分
zcard key 个数
zrange key start end [withscore]
zrangebyscore key minScore maxScore[WITHSCORES]
#返回指定分数范围内的升序元素[分值]
zcount key minScore maxScore
#返回有序集合内在指定分数范围内的个数
zremrangebyrank key start end
#删除指定排名内的升序元素
zremrangebyscore key minScore maxScore
#删除指定分数内的升序元素
zrevrank
zrevrange
zrevrangebyscore
zinterstore
zunionstore
排行榜单 score: timeStamp、 saleCount、 followCount
Jedis/Jedispool
redis连接工具
maxTotal
资源池最大连接数
maxIdle
资源池允许最大空闲连接数
minIdle
资源池确保最少空闲连接数
jmxEnabled
是否开启jmx监控,可用于监控
blockWhenEx当资源池用尽后,调用者是否要等truehausted
待。只有当为true时,下面的maxWaitMillis才会生效
maxWaitMillis当资源池连接用尽后,调用者的最大等待时间(单位为毫秒)
-1:表示永不超时
testOnBorrow
向资源池借用连接时是否做连接有 效性检测(ping),无效连接会被移除
testOnReturn
向资源池归还连接时是否做连接有效性检测(ping),无效连接会被移除
比较难确定的,举个例子∶
1.命令平均执行时间0.1ms = 0.001s。
2.业务需要50000 QPS。
3.maxTotal理论值= 0.001* 50000 = 50个。实际值要偏大一些。
建议maxIdle = maxTotal (减少创建新连接的开销。)
建议预热minIdle ( 减少第一次启动后的新连接开销。)
解决方案
1.慢查询阻塞:池子连接都被hang住。
2.资源池参数不合理∶例如QPS高、池子小。
3.连接泄露(没有close()):此类问题比较难定位,例如client list、netstat等,最重要的是代码。
慢查询
1.client发生命令
2.redis 命令排队(队列)
3.执行命令
4.返回结果给client
说明:
(1)慢查询发生在第3阶段
(2)客户端超时不一定慢查询,但慢查询是客户端超时的一个可能因素
配置:
slowlog-max-len 队列最大长度 默认128
slowlog-log-slower-than 默认10000
1.慢查询阈值(单位:微秒)
- slowlog-log-slower-than=0,记录所有命令
- slowlog-log-slower-than<0,不记录任何命令。
1 | config set slowlog-max-len 1000 |
slowlog get [n]:获取慢查询队列
slowlog len:获取慢查询队列长度
slowlog reset:清空慢查询队列
slowlog-log-slower-than不要设置过大,默认10ms,通常设置1ms
slowlog-max-len不要设置过小,通常设置1000左右。
pipeline
1次pipeline(n条命令)=1次网络时间+n次命令时间
命令 | N个命令 | 1次1次pipeline(n条命令) |
---|---|---|
时间 | n次网络时间+n次命令时间 | 1次网络时间+n次命令时间 |
数据量 | 1条命令 | n条命令 |
1.Redis的命令时间是微秒级别。
- pipeline每次条数要控制(网络)。
注意每次pipeline携带数据量
pipeline每次只能作用在一个Redis节点上
M操作与pipeline区别
发布订阅
publish channel msg
subscribe [channel] # ─个或多个
unsubscribe [channel] # ─个或多个
psubscribe [pattern…]#订阅模式。
punsubscribe [pattern…]#退订指定的模式。
pubsub channels #列出至少有一个订阅者的频道。
pubsub numsub [channel…] #列出给定频道的订阅者数量
Bitmap
setbit key offset val
getbit key offset
bitcount key [start end]
获取位图指定范围(start到end,单位为字节,如果不指定就是获取全部)位值为1的个数
bitop op destkey key [key…]
做多个Bitmap的and(交集)、or(并集)、not(非)、xor(异或)操作并将结果保存在destkey中
bitpos key targetBit [start] [end]
计算位图指定范围(start到end,单位为字节,如果不指定就是获取全部)第一个偏移量对应的值等于targetBit的位置
用户在线人数统计等。
hyperloglog
pfadd key element [element …]:向hyperloglog添加元素
pfcount key [key …]:计算hyperloglog的独立总数
pfmerge destkey sourcekey [sourcekey …]:合并多个hyperloglog
geo
redis 3.2+
geo key longitude latitude member[longitude latitude member …]
#增加地理位置信息
如:geoadd cities:locations 116.28 39.55 beijing
geopos key member [member …]#获取地理位置信息
geodist key member1 member2 [unit]
#获取两个地理位置的距离
#unit: m(米)、km(千米)、mi(英里)、ft(尺)
1 | georadius key longitude latitude radiusm|kmlft|mi [withcoord] [withdist][withhash] [COUNT count] [asc|desc][store key][storedist key] |
持久化
RDB:
1.命令方式
save (同步)(O(n)) 会阻塞客户端命令
bgsave(异步) fork函数 由子进程生成RDB文件 需要fork,消耗内存
2.配置文件redis.conf
1 | save 900 1 |
触发机制-不容忽略方式
1.全量复制
- debug reload
- shutdown
RDB是Redis内存到硬盘的快照,用于持久化。
save通常会阻塞Redis。
bgsave不会阻塞Redis,但是会fork新进程。
save自动配置满足任一就会被执行。
有些触发机制不容忽视
AOF:
always 不丢失数据,IO开销大
everysec 每秒一闪fsync ,丢1秒数据
no 不可控
AOF重写:命令优化,删除重复,过期数据优化
1.bgrewriteaof fork子进程 内存中 AOF重写
2.配置方式
1 | auto-aof-rewrite-min-size |
1 | appendonly yes |
命 | RDB | AOF |
---|---|---|
启动优先级 | 低 | 高 |
体积 | 小 | 大 |
恢复 | 快 | 慢 |
轻重 | 重 | 轻 |
fork 操作
改善fork
优先使用物理机或者高效支持fork操作的虚拟化技术
控制Redis实例最大可用内存: maxmemory
合理配置Linux内存分配策略: vm.overcommit_memory=1
降低fork频率∶例如放宽AOF重写自动触发时机,不必要的全量复制
主从复制
一主一从
一主多从
1.一个master可以有多个slave
2.一个slave只能有一个master
3.数据流向是单向的,master到slave
slaveof 主ip
slaveof no one 取消
配置方式
slaveof ip port
slave-read-only yes
全量复制开销
- bgsave时间
2.RDB文件网络传输时间3.从节点清空数据时间
4.从节点加载RDB的时间
5.可能的AOF重写时间
Master挂掉 :【手动故障转移】选slave作为master
读写分离
1.读写分离:读流量分摊到从节点。
2.可能遇到问题:
·复制数据延迟
·读到过期数据
·从节点故障
配置不一致
例如maxmemory不一致:丢失数据
规避全量复制
1.第一次全量复制
·第一次不可避免·小主节点、低峰
2.节点运行ID不匹配。
主节点重启
(运行ID变化)·故障转移,例如哨兵或集群
3.复制积压缓冲区不足。
网络中断,部分复制无法满足
。增大复制缓冲区配置rel_backlog_size,网络“增强”。
1.单主节点复制风暴︰
问题:主节点重启,多从节点复制解决︰更换复制拓扑
2.单机器复制风暴
如右图∶机器宕机后,大量全量复制主节点分散多机器
redis sentinel
默认端口 26379
故障转移:
1.多个sentinel发现并确认master有问题。
⒉选举出一个sentinel作为领导。
3.选出一个slave作为master。
4.通知其余slave成为新的master的slave。
5.通知客户端主从变化
6.等待老的master复活成为新master的slave。
1 | #sentinel配置 |
客户端接入流程
1.Sentinel地址集合
masterName
不是代理模式
1
2JedisSentinelPool sentinelPool = new JedisSentinelPool(masterName, sentinelSet, poolConfig, timeout);
三个定时任务
1.每10秒每个sentinel对master和slave执行info
发现slave节点
确认主从关系
2.每2秒每个sentinel通过master节点的channel交换信息(pub/sub)·通过_sentinel_:hello频道交互
。交互对节点的“看法”和自身信息
3.每1秒每个sentinel对其他sentinel和redis执行ping·心跳检测,失败判定依据
1 | sentinel monitor <masterName> <ip><port> <quorum> |
领导者选举
原因:只有一个sentinel节点完成故障转移
选举:通过sentinel is-master-down-by-addr命令都希望成为领导者
·
1.每个做主观下线的Sentinel节点向其他Sentinel节点发送命令,要求将它设置为领导者。
收到命令的Sentinel节点如果没有同意通过其他Sentinel节点发送的命令,那么将同意该请求,否则拒绝
如果该Sentinel节点发现自己的票数已经超过Sentinel集合半数且超过quorum,那么它将成为领导者。
4.如果此过程有多个Sentinel节点成为了领导者,那么将等待一段时间重新进行选举。
故障转移( sentinel领导者节点完成)
1.从slave节点中选出一个“合适的”节点作为新的master节点
2.对上面的slave节点执行slaveof no one命令让其成为master节点。
3.向剩余的slave节点发送命令,让它们成为新master节点的slave节点,复制规则和parallel-syncs参数有关。
4.更新对原来master节点配置为slave,并保持着对其“关注“,当其恢复后命令它去复制新的master节点。
选择“合适的”slave节点
1选择slave-priority(slave节点优先级)最高的slave节点,如果存在则返回,不存在则继续。
2选择复制偏移量最大的slave节点(复制的最完整),如果存在则返回,不存在则继续。
3选择runId最小的slave节点。
redis cluster
节点取余
客户端分片∶哈希+取余
节点伸缩︰数据节点关系变化,导致数据迁移
迁移数量和添加节点数量有关∶建议翻倍扩容
一致性哈希
客户端分片∶哈希+顺时针(优化取余)
节点伸缩∶只影响邻近节点,但是还是有数据迁移
翻倍伸缩∶保证最小迁移数据和负载均衡
虚拟槽分区
预设虚拟槽:每个槽映射一个数据子集,一般比节点数大
良好的哈希函数∶例如CRC16
服务端管理节点、槽、数据︰例如Redis Cluster
1 | #配置开启Redis |
moved和ask
两者都是客户单重定向
moved:槽已经确定迁移
ask:槽还在迁移中
jediscluster
1 | //JedisCluster基本使用 |
集群完整性:
cluster-require-full-coverage默认为yes
集群中16384个槽全部可用∶保证集群完整性
节点故障或者正在故障转移︰
(error) CLUSTERDOWN The cluster is down
大多数业务无法容忍,cluster-require-full-coverage建议设置为no
数据倾斜
节点和槽分配不均
不同槽对应键值数量差异较大
包含bigkey
内存相关配置不一致
请求倾斜
热点key:重要的key或者bigkey优化:
-避免bigkey
-热键不要用hash_tag
-当一致性不高时,可以用本地缓存+MQ
集群读写分离
只读连接︰集群模式的从节点不接受任何读写请求。
-重定向到负责槽的主节点
-readonly命令可以读:连接级别命令
离线/在线迁移
官方迁移工具:redis-trib.rb import
-只能从单机迁移到集群
-不支持在线迁移: source需要停写-不支持断点续传
-单线程迁移∶影响速度
在线迁移∶
-唯品会redis-migrate-tool
-豌豆荚:redis-port
思考-分布式Redis不一定好
.
1.Redis Cluster:满足容量和性能的扩展性,很多业务”不需要
大多数时客户端性能会”降低”。
命令无法跨节点使用: mget、keys、scan、flush、sinter等。
Lua和事务无法跨节点使用。
客户端维护更复杂:SDK和应用本身消耗(例如更多的连接池)。
2.很多场景Redis Sentinel已经足够好。
缓存使用和优化
缓存更新策略
1.LRU/LFU/FIFO算法剔除︰例如maxmemory-policy。
2.超时剔除∶例如expire。
3.主动更新:开发控制生命周期
两条建议
1.低一致性:最大内存和淘汰策略
2.高一致性:超时剔除和主动更新结合,最大内存和淘汰策略兜底。
缓存粒度控制-三个角度
1.通用性:全量属性更好。
2.占用空间︰部分属性更好。
3.代码维护:表面上全量属性更好。
缓存穿透
原因
1.业务代码自身问题
2.恶意攻击、爬虫等等
如何发现
1.业务的相应时间
2.业务本身问题
3.相关指标∶总调用数、缓存层命中数、存储层命中数
解决方案:
1.缓存空对象,并设置过期时间
两个问题
i.需要更多的键。
ii.缓存层和存储层数据“短期”不一致。
2.布隆过滤器拦截
问题
现有50亿个电话号码,现有10万个电话号码,要快速准确判断这些电话号码是否已经存在?
.通过数据库查询:实现快速有点难。
.数据预放在集合中:50亿*8字节~ 40GB(内存浪费或不够)
类似问题很多
垃圾邮件过滤
文字处理软件(例如word )错误单词检测
网络爬虫重复url检测。
布隆过滤器构建
参数:m个二进制向量,n个预备数据,k个hash函数
构建布隆过滤器:n个预备数据走一遍上面过程
判断元素存在∶走一遍上面过程∶如果都是1,则在表明存在,反之不存在。
误差率
·肯定存在误差︰恰好都命中了。
·直观因素:m/n的比率,hash函数的个数。
m/n与误差率成反比,k与误差率成反比。
本地布隆过滤器
·现有库:guava
·本地布隆过滤器的问题︰
(1)容量受限制。
(2)多个应用存在多个布隆过滤器,构建同步复杂。
基于Redis单机实现存在的问题
速度慢︰比本地慢,输在网络
-解决:单独部署,与应用同机房甚至机架部署。
容量受限:Redis最大字符串为512MB、Redis单机容量。
-解决︰基于Redis Cluster实现。
雪崩
雪崩问题︰缓存层高可用、客户端降级、提前演练是解决雪崩问题的重要方法。
无底洞
优化IO的几种方法
1.命令本身优化∶例如慢查询keys、hgetall bigkey
2.减少网络通信次数
3.降低接入成本︰例如客户端长连接/连接池、NIO等
热点key重新问题
产生原因:多个线程对key访问,前面的线程并没有完全建立缓存,后面线程再次建立缓存。
三个目标和两个解决
1.三个目标︰
减少重缓存的次数
数据尽可能一致
减少潜在危险
2.两个解决︰
·互斥锁(mutex key)
1 | //伪代码 |
·永远不过期
存在问题:不保证一致性
1.缓存层面∶没有设置过期时间(没有用expire)。
2.功能层面:为每个value添加逻辑过期时间,但发现超过逻辑过期时间后,会使用单独的线程去构建、缓存。
设计规范
Key名设计
三大建议
1 可读性和可管理性
以业务名(或数据库名)为前缀(防止key冲突),用冒号分割,比如业务名:表名:id,如: ugc:video:1
2简洁性
1 | 保证语义的前提下,控制key的长度,当key较多时,内存占用也不容忽视( redis3:39字节embstr),如:user:{uid }:friends:messages:{mid}简化为u:{uid}: fr:m:{mid} |
3不要包含特殊字符
反例:包含空格、换行、单双引号以及其他转义字符
Value设计
1拒绝bigkey
强制
string类型控制在10KB以内
hash、list、set、zset元素个数不要超过5000
反例:一个包含几百万个元素的list、hash等,一个巨大的json字符串
bigkey的危害:
网络阻塞
集群节点数据不均衡
Redis阻塞
频繁序列化∶应用服务器CPU消耗
bigkey发现:
◆应用异常
redis-cli –bigkeys
scan + debug object
主动报警∶网络流量监控、客户端监控
内核热点key问题优化
2选择合适的数据结构
例如:实体类型(数据结构内存优化:例如ziplist,注意内存和性能的平衡)
反例:
set user:1:name tom;
set user:1:age 19;
set user:1:favor football
正例:
hmset user:1 name tom age 19 favor football
3过期设计
周期数据需要设置过期时间,object idle time可以找垃圾key-value
过期时间不宜集中∶缓存穿透和雪崩等问题
1.惰性删除∶访问key -> expired dict -> del key
2.定时删除︰每秒运行10次,采样删除。
超过maxmemory后触发相应策略,由maxmemory-policy控制。
Noeviction:默认策略,不会删除任何数据,拒绝所有写入操作并返回端错误信息“ ( error ) OOM command not allowed when usedmemory”此时Redis只响应读操作由maxmemory-policy控制。
Volatile-Iru:根据LRU算法删除设置了超时属性( expire )的键,直到腾出足够空间为止。如果没有可删除的键对象,回退到noeviction策略。
Allkeys-lru:根据LRU算法删除键,不管数据有没有设置超时属性,直到腾出足够空间为止。
Allkeys-random:随机删除所有键,直到腾出足够空间为止。
volatile-random :随机删除过期键,直到腾出足够空间为止。
volatile-ttl:根据键值对象的ttl属性,删除最近将要过期数据。如果没有,回退到noeviction策略。