- redis全局命令
查看所有键: keys *
查看键总数: dbsize
dbsize命令在计算键总数时不会遍历所有键,而是直接获取Redis内置的键总数变量,所以dbsize命令的时间复杂度是O(1)
而keys命令会遍历所有键,所以它的时间复杂度是O(n),所以线上禁止使用keys
检查键是否存在: exists key
删除键: del key, 删除一个不存在的key会返回0,删除成功返回1
键过期: expire key seconds
返回剩余时间: ttl key 返回-1:没设置过期时间 -2:键不存在 正数:剩余时间
键的数据结构类型:type key 键不存在返回none
object encoding key 返回内部编码
内部编码好处:
第一,可以改进内部编码,而对外的数据结构和命令没有影响
第二,多种内部编码实现可以在不同场景下发挥各自的优势
- 单线程架构: 单线程架构和I/O多路复用模型
- 每次客户端调用都经历了发送命令、执行命令、返回结果三个过程
- 因为Redis是单线程来处理命令的,所以一条命令从客户端达到服务端不会立刻被执行,所有命令都会进入一个队列中,然后逐个被执行
- 为什么单线程还能这么快?
1.纯内存访问
2.非阻塞I/O,Redis使用epoll作为I/O多路复用技术的实现,再加上Redis自身的事件处理模型将epoll中的连接、读写、关闭都转换为事件,不在网络I/O上浪费过多的时间
3.单线程避免了线程切换和竞态产生的消耗
字符串
1.值最大不能超过512m
2.setnx hello redis 当hello不存在的时候才会设置成功,返回1,存在则设置失败返回0
作用:多个客户端执行setnx,设置成功的就相当于获取到锁,实现分布式锁
setex:当值存在时,对值修改
3.批量设置值: mset key value eg: mset a 1 b 2 c 3 d 4
4.批量获取值: mget a b c d
5.批量操作可以节省请求的网络开销
6.incr key 自增操作
incr命令用于对值做自增操作,返回结果分为三种情况:
·值不是整数,返回错误。
·值是整数,返回自增后的结果。
·键不存在,按照值为0自增,返回结果为1。
例如对一个不存在的键执行incr操作后,返回结果是1:
其他命令:decr(自减)、incrby(自增指定数字)、decrby(自减指定数字)、incrbyfloat(自增浮点数)
7.字符串类型的内部编码有3种:
·int:8个字节的长整型。
·embstr:小于等于39个字节的字符串。
·raw:大于39个字节的字符串。
Redis会根据当前值的类型和长度决定使用哪种内部编码实现。
使用场景
1.缓存
2.计数
3.共享session
4.短信验证码,一个ip一定时间内限制访问次数
哈希
- value={{field1,value1},...{fieldN,valueN}}
hset key field value
设置值
hset user:1 name tom
hset user:1 sex male
获取值
hget user:1 name
删除值
hdel user:1 name
计算fiela的个数
hlen user:1
批量操作
hmset user:1 name mike age 12 city tianjin
hmget user:1 name city
判断field是否存在
hexists user:1 name
获取所有的field
hkeys user:1
获取所有的value
hvals user:1
获取所有的key value
hgetall user:1
在使用hgetall时,如果哈希元素个数比较多,会存在阻塞Redis的可能。如果开发人员只需要获取部分field,可以使用hmget,
如果一定要获取全部field-value,可以使用hscan命令,该命令会渐进式遍历哈希类型
获取指定value的长度
hstrlen user:1 name
ziplist(压缩列表):当哈希类型元素个数小于hash-max-ziplist-entries
配置(默认512个)、同时所有值都小于hash-max-ziplist-value配置(默认64
字节)时,Redis会使用ziplist作为哈希的内部实现,ziplist使用更加紧凑的
结构实现多个元素的连续存储,所以在节省内存方面比hashtable更加优秀。
·hashtable(哈希表):当哈希类型无法满足ziplist的条件时,Redis会使
用hashtable作为哈希的内部实现,因为此时ziplist的读写效率会下降,而
hashtable的读写时间复杂度为O(1)。
使用场景
1.缓存用户信息
缓存用户信息方案
1)
set user:1:name tom
set user:1:age 23
set user:1:city beijing
优点:简单直观,每个属性都支持更新操作。
缺点:占用过多的键,内存占用量较大,同时用户信息内聚性比较差,所以此种方案一般不会在生产环境使用。
set user:1 serialize(userInfo) 序列化字符串类型:将用户信息序列化后用一个键保存。
优点:简化编程,如果合理的使用序列化可以提高内存的使用效率。
缺点:序列化和反序列化有一定的开销,同时每次更新属性都需要把全部数据取出进行反序列化,更新后再序列化到Redis中。
- hmset user:1 name tomage 23 city beijing
优点:简单直观,如果使用合理可以减少内存空间的使用。
缺点:要控制哈希在ziplist和hashtable两种内部编码的转换,hashtable会消耗更多内存。
列表 list
- 在Redis中,可以对列表两端插入(push)和弹出(pop)
从右边插入元素
rpush listkey c b a
从左到右获取列表的所有元素
lrange listkey 0 -1
从左边插入元素
lpush key value
向某个元素前或者后插入元素
linsert listkey before|after b java
获取长度
llen listkey
阻塞操作
blpop和brpop是lpop和rpop的阻塞版本
brpop list:test 3
列表为空:如果timeout=3,那么客户端要等到3秒后返回,如果timeout=0,那么客户端一直阻塞等下去
在使用brpop时,有两点需要注意
1.如果是多个键,那么brpop会从左至右遍历键,一旦有一个键能弹出元素,客户端立即返回
2.如果多个客户端对同一个键执行brpop,那么最先执行brpop命令的客户端可以获取到弹出的值,后面的客户端继续阻塞,直到又有消息进来
列表类型的内部编码有两种。
1.ziplist(压缩列表):当列表的元素个数小于list-max-ziplist-entries配置(默认512个),
同时列表中每个元素的值都小于list-max-ziplist-value配置时(默认64字节),Redis会选用ziplist来作为列表的内部实现来减少内存的使用。
2.linkedlist(链表):当列表类型无法满足ziplist的条件时,Redis会使用linkedlist作为列表的内部实现。
Redis3.2版本提供了quicklist内部编码,简单地说它是以一个ziplist为节点的linkedlist,它结合了ziplist和linkedlist两者的优势,目前使用的都是quicklist
使用场景
1.消息队列
lpush+brpop组合可以实现阻塞队列
lrpush从列表左侧插入元素,多个消费者客户端使用brpop命令阻塞式的“抢”列表尾部的元素,多个客户端保证了消费的负载均衡和高可用性
2.文章列表
1.每篇文章使用哈希结构存储,例如每篇文章有3个属性title、timestamp、content
hmset acticle:1 title xx timestamp 1476536196 content xxxx
2.向用户文章列表添加文章,user:{id}:articles作为用户文章列表的键
lpush user:1:acticles article:1 article3
...
lpush user:k:acticles article:5
...
分页获取用户文章列表,例如下面伪代码获取用户id=1的前10篇文章
articles = lrange user:1:articles 0 9
for article in {articles}
hgetall {article}
·lpush+lpop=Stack(栈)
·lpush+rpop=Queue(队列)
·lpsh+ltrim=Capped Collection(有限集合)
·lpush+brpop=Message Queue(消息队列)
集合
集合内操作
添加元素
sadd myset a b c
如果重复添加,就只显示成功插入的个数
删除元素
srem myset a b
计算元素个数
scard myset 不会直接遍历,而是直接取redis内部的变量
判断元素是否在集合中
sismember myset c
随机从集合返回指定个数元素
srandmember key [count]
从集合随机弹出元素
spop key
获取所有元素
smembers key
smembers和lrange、hgetall都属于比较重的命令,如果元素过多存在阻塞Redis的可能性,这时候可以使用sscan来完成
- 集合间操作
sadd user:1:follow it music his sports
sadd user:2:follow it news ent sports
交集: sinter user:1:follow user:2:follow
1) "sports"
2) "it"
并集: sunion user:1:follow user:2:follow
差集: sdiff user:1:follow user:2:follow
将交集、并集、差集的结果保存
sinterstore destination key [key ...]
suionstore destination key [key ...]
sdiffstore destination key [key ...]
eg: sinterstore user:1_2:inter user:1:follow user:2:follow
内部编码
集合类型的内部编码有两种:
·intset(整数集合):当集合中的元素都是整数且元素个数小于set-max-intset-entries配置(默认512个)时,Redis会选用intset来作为集合的内部实现,从而减少内存的使用。
·hashtable(哈希表):当集合类型无法满足intset的条件时,Redis会使用hashtable作为集合的内部实现。
使用场景
1.标签
给用户添加标签
sadd user:1:tags tag1 tag2 tag5
sadd user:2:tags tag2 tag3 tag5
...
sadd user:k:tags tag1 tag2 tag4
...
(2)给标签添加用户
sadd tag1:users user:1 user:3
sadd tag2:users user:1 user:2 user:3
...
sadd tagk:users user:1 user:2
可以使用sinter命令,来计算用户共同感兴趣的标签,如下代码所示:
sinter user:1:tags user:2:tags
有序集合
分数可以重复,值不能重复
zadd user:ranking 251 tom
zadd user:ranking 1 kris 91 mike 200 frank 220 tim 250 martin
计算成员个数
zcard user:ranking
计算某个成员的分数
zscore user:ranking tom
删除成员
zrem user:ranking mike
增加成员的分数
zincrby user:ranking 9 tom
有序集合是按照分值排名的,zrange是从低到高返回,zrevrange反之
zrange user:ranking 0 2 withscores withscores是带分数的意思
zrevrange user:ranking 0 2 withscores
有序集合类型的内部编码有两种:
·ziplist(压缩列表):当有序集合的元素个数小于zset-max-ziplist-entries配置(默认128个),
同时每个元素的值都小于zset-max-ziplist-value配置(默认64字节)时,Redis会用ziplist来作为有序集合的内部实现,
ziplist可以有效减少内存的使用。
·skiplist(跳跃表):当ziplist条件不满足时,有序集合会使用skiplist作为内部实现,因为此时ziplist的读写效率会下降。
- 使用场景
有序集合比较典型的使用场景就是排行榜系统
键重命名
rename key newkey
如果newKey存在,则会覆盖掉
为了防止被强行rename,Redis提供了renamenx命令,确保只有newKey不存在时候才被覆盖
随机返回一个键: randomkey
键过期:
expire key seconds:键在seconds秒后过期
expireat key timestamp:键在秒级时间戳timestamp后过期
setex命令作为set+expire的组合,不但是原子执行,同时减少了一次网络通讯的时间 - 迁移键功能非常重要,因为有时候我们只想把部分数据由一个Redis迁
移到另一个Redis(例如从生产环境迁移到测试环境),Redis发展历程中提
供了move、dump+restore、migrate三组迁移键的方法,它们的实现方式以及
使用的场景不太相同 - scan采用渐进式遍历的方式来解决keys命令可能带来的阻塞问题
- 如果要使用多个数据库功能,完全可以在一台机器上部署多个
Redis实例,彼此用端口来做区分,因为现代计算机或者服务器通常是有多
个CPU的。这样既保证了业务之间不会受到影响,又合理地使用了CPU资
源
第三章
- 慢查询配置
slowlog-log-slower-than 预设阀值,微秒 和slowlog-max-len 慢查询日志最多存储多少条
redis-server --test-memory 1024
config set slowlog-log-slower-than 20000
config set slowlog-max-len 1000
config rewrite
slowlog-max-len: 线上建议调大慢查询列表,记录慢查询时Redis会对长命令做截断操作,并不会占用大量内存。增大慢查询列表可以减缓慢查询被剔除的可能,例如线上可设置为1000以上
slowlog-log-slower-than: 默认值超过10毫秒判定为慢查询,需要根据Redis并发量调整该值。由于Redis采用单线程响应命令,对于高流量的场景,如果命令执行时间在1毫秒以上,
那么Redis最多可支撑OPS不到1000。因此对于高OPS场景的Redis建议设置为1毫秒
- redis-cli
redis-cli -r 3 ping 执行三次ping命令 - redis-server
redis-server --test-memory 1024 检测当前操作系统能否提供1G的内存给Redis - Pipeline
Pipeline(流水线)它能将一组Redis命令进行组装,通过一次RTT传输给Redis,再将这组Redis命令的执行结果按顺序返回给客户端
Redis提供了简单的事务功能,将一组需要一起执行的命令放到multi和exec两个命令之间。multi命令代表事务开始,exec命令代表事务结束,它们之间的命令是原子顺序执行的
如果要停止事务的执行,可以使用discard命令代替exec命令即可
Redis提供了简单的事务,之所以说它简单,主要是因为它不支持事务中的回滚特性,同时无法实现命令之间的逻辑关系计算
Lua
- 定义一个字符串类型的数据 local strings val = "world" 没有local代表的是全局变量
- 数组 local tables myArray = {"redis", "jedis", true, 88.0}
- 循环遍历
local int sum = 0
for i = 1, 100
do
sum = sum + i
end
-- 输出结果为 5050
print(sum)
for index,value in ipairs(myArray)
do
print(index)
print(value)
end
local tables myArray = {"redis", "jedis", true, 88.0}
for i = 1, #myArray
do
if myArray[i] == "jedis"
then
print("true")
break
else
--do nothing
end
end
- 哈希 local tables user_1 = {age = 28, name = "tome"}
for key,value in pairs(user_1)
do print(key .. value)
end
加载脚本:script load命令可以将脚本内容加载到Redis内存中
redis-cli script load "$(cat lua_get.lua)"
evalsha 7413dc2440db1fea7c0a0bde841fa68eefaf149c 1 redis world
- Bitmaps
Redis提供了Bitmaps这个“数据结构”可以实现对位的操作
- 位命令
setbit unique:users:2016-04-05 0 1
getbit unique:users:2016-04-05 8
操作计算2016-04-05这天的独立访问用户数量:
bitcount unique:users:2016-04-05
持久化
-
RDB: RDB持久化是把当前进程数据生成快照保存到硬盘的过程,触发RDB持久化过程分为手动触发和自动触发
-
手动触发分别对应save和bgsave命令
-
save命令:阻塞当前Redis服务器,直到RDB过程完成为止,对于内存比较大的实例会造成长时间阻塞,线上环境不建议使用。
-
bgsave命令:Redis进程执行fork操作创建子进程,RDB持久化过程由子进程负责,完成后自动结束。阻塞只发生在fork阶段,一般时间很短。
-
Redis内部所有的涉及RDB的操作都采用bgsave的方式,而save命令已经废弃
-
执行bgsave命令,Redis父进程判断当前是否存在正在执行的子进程,如RDB/AOF子进程,如果存在bgsave命令直接返回
-
父进程执行fork操作创建子进程,fork操作过程中父进程会阻塞,通过info stats命令查看latest_fork_usec选项,可以获取最近一个fork操作的耗时,单位为微秒
-
父进程fork完成后,bgsave命令返回“Background saving started”信息并不再阻塞父进程,可以继续响应其他命令
-
子进程创建RDB文件,根据父进程内存生成临时快照文件,完成后对原有文件进行原子替换。执行lastsave命令可以获取最后一次生成RDB的时间,对应info统计的rdb_last_save_time选项
-
进程发送信号给父进程表示完成,父进程更新统计信息,具体见info Persistence下的rdb_*相关选项
-
RDB是一个紧凑压缩的二进制文件,代表Redis在某个时间点上的数据
快照。非常适用于备份,全量复制等场景。比如每6小时执行bgsave备份,
并把RDB文件拷贝到远程机器或者文件系统中(如hdfs),用于灾难恢复 -
AOF: 持久化:以独立日志的方式记录每次写命令,重启时再重新执行AOF文件中的命令达到恢复数据的目的。
-
AOF的工作流程操作:命令写入(append)、文件同步(sync)、文件重写(rewrite)、重启加载(load)
- 所有的写入命令会追加到aof_buf(缓冲区)中。
2)AOF缓冲区根据对应的策略向硬盘做同步操作。
3)随着AOF文件越来越大,需要定期对AOF文件进行重写,达到压缩的目的。
4)当Redis服务器重启时,可以加载AOF文件进行数据恢复。
- Redis提供了多种AOF缓冲区同步文件策略,由参数appendfsync控制
配置为always时,每次写入都要同步AOF文件,在一般的SATA硬盘上,Redis只能支持大约几百TPS写入,显然跟Redis高性能特性背道而驰,不建议配置
配置为no,由于操作系统每次同步AOF文件的周期不可控,而且会加大每次同步硬盘的数据量,虽然提升了性能,但数据安全性无法保证。
配置为everysec,是建议的同步策略,也是默认配置,做到兼顾性能和数据安全性。理论上只有在系统突然宕机的情况下丢失1秒的数据。
- 重写机制
重写后的AOF文件为什么可以变小:
1)进程内已经超时的数据不再写入文件。
2)旧的AOF文件含有无效命令,将该部分去掉
- 多条写命令可以合并为一个
AOF重写过程可以手动触发和自动触发:
·手动触发:直接调用bgrewriteaof命令。
·自动触发:根据auto-aof-rewrite-min-size和auto-aof-rewrite-percentage参数确定自动触发时机。
- Redis持久化文件加载流程
- 加载损坏的AOF文件时会拒绝启动
对于错误格式的AOF文件,先进行备份,然后采用redis-check-aof--fix命
令进行修复,修复后使用diff-u对比数据的差异,找出丢失的数据,有些可
以人工修改补全。
AOF文件可能存在结尾不完整的情况,比如机器突然掉电导致AOF尾部
文件命令写入不全。Redis为我们提供了aof-load-truncated配置来兼容这种情
况,默认开启。加载AOF时,当遇到此问题时会忽略并继续启动
- 主从模式下断开复制: slaveof no one
- 通过slaveof命令还可以实现切主操作
- 切主后从节点会清空之前所有的数据,线上人工操作时小心slaveof在错误的节点上执行或者指向错误的主节点
- 主从复制延迟: repl-disable-tcp-nodelay
关闭:主节点产生的命令数据无论大小都会及时地发送给从节点,这样主从之间延迟会变小,但增加了网络带宽的消耗。适用于主从之间的网络环境良好的场景,如同机架或同机房部署
开启:主节点会合并较小的TCP数据包从而节省带宽。默认发送时间间隔取决于Linux的内核,一般默认为40毫秒。
这种配置节省了带宽但增大主从之间的延迟。适用于主从网络环境复杂或带宽紧张的场景,如跨机房部署。 - 复制过程
- 全量复制
全量复制是Redis最早支持的复制方式,也是主从第一次建立复制时必须经历的阶段。触发全量复制的命令是sync和psync
- 读写分离
对于读占比较高的场景,可以通过把一部分读流量分摊到从节点(slave)来减轻主节点(master)压力,同时需要注意永远只对主节点执行写操作
当使用从节点响应读请求时,业务端可能会遇到如下问题:
·复制数据延迟
·读到过期数据。
·从节点故障。 - 规避复制风暴
复制风暴是指大量从节点对同一主节点或者对同一台机器的多个主节点短时间内发起全量复制的过程。
复制风暴对发起复制的主节点或者机器造成大量开销,导致CPU、内存、带宽消耗。因此我们应该分析出复制风暴发生的场景,提前采用合理的方式规避。
- Redis原生提供慢查询统计功能,执行slowlog get{n}命令可以获取最近的n条慢查询命令,
默认对于执行超过10毫秒的命令都会记录到一个定长队列中,线上实例建议设置为1毫秒便于及时发现毫秒级以上的命令。
内存
- info memory命令获取内存相关指标
- 限定redis内存使用:maxmemory=4GB
- 动态设置内存大小: config set maxmemory 6GB
- 内存回收策略
1.删除到达过期时间的键对象。(惰性删除和定时任务删除)
2.内存使用达到maxmemory上限时触发内存溢出控制策略。
哨兵
- Redis Sentinel与Redis主从复制模式只是多了若干Sentinel节点,所以Redis Sentinel并没有针对Redis节点做了特殊处理
- 对于节点的故障判断是由多个Sentinel节点共同完成,这样可以有效地
防止误判。 - Sentinel节点集合是由若干个Sentinel节点组成的,这样即使个别Sentinel
节点不可用,整个Sentinel节点集合依然是健壮的 - sentinal实现原理
Redis Sentinel的三个定时任务、主观下线和客观下线、Sentinel领导者选举、故障转移
1.每隔10秒,每个Sentinel节点会向主节点和从节点发送info命令获取最新的拓扑结构
通过向主节点执行info命令,获取从节点的信息,这也是为什么Sentinel节点不需要显式配置监控从节点
当有新的从节点加入时都可以立刻感知出来
节点不可达或者故障转移后,可以通过info命令实时更新节点拓扑信息
2.每隔2秒,每个Sentinel节点会向Redis数据节点的__sentinel__:hello频道上发送该Sentinel节点对于主节点的判断以及当前Sentinel节点的信息
同时每个Sentinel节点也会订阅该频道,来了解其他Sentinel节点以及它们对主节点的判断
3.每隔1秒,每个Sentinel节点会向主节点、从节点、其余Sentinel节点发送一条ping命令做一次心跳检测,来确认这些节点当前是否可达
当这些节点超过down-after-milliseconds没有进行有效回复,Sentinel节点就会对该节点做失败判定,这个行为叫做主观下线。
从字面意思也可以很容易看出主观下线是当前Sentinel节点的一家之言,存在误判的可能
当Sentinel主观下线的节点是主节点时,该Sentinel节点会通过sentinel is-master-down-by-addr命令向其他Sentinel节点询问对主节点的判断,
当超过
也就是大部分Sentinel节点都对主节点的下线做了同意的判定,那么这个判定就是客观的
- 领导者选举
假如Sentinel节点对于主节点已经做了客观下线,那么是不是就可以立即进行故障转移了?
当然不是,实际上故障转移的工作只需要一个Sentinel节点来完成即可,所以Sentinel节点之间会做一个领导者选举的工作,
选出一个Sentinel节点作为领导者进行故障转移的工作。Redis使用了Raft算法实现领导者选举 - 执行故障转移
Redis Cluster
Redis Cluser采用虚拟槽分区,所有的键根据哈希函数映射到0~16383整数槽内,计算公式:slot=CRC16(key)&16383。每一个节点负责维护一部分槽以及槽所映射的键值数据
Redis虚拟槽分区的特点:
·解耦数据和节点之间的关系,简化了节点扩容和收缩难度。
·节点自身维护槽的映射关系,不需要客户端或者代理服务维护槽分区元数据。
·支持节点、槽、键之间的映射查询,用于数据路由、在线伸缩等场景。
Redis集群相对单机在功能上存在一些限制,需要开发人员提前了解,在使用时做好规避。限制如下
1)key批量操作支持有限。如mset、mget,目前只支持具有相同slot值的key执行批量操作。对于映射为不同slot值的key由于执行mget、mget等操作可能存在于多个节点上因此不被支持。
2)key事务操作支持有限。同理只支持多key在同一节点上的事务操作,当多个key分布在不同的节点上时无法使用事务功能。
3)key作为数据分区的最小粒度,因此不能将一个大的键值对象如hash、list等映射到不同的节点。
4)不支持多数据库空间。单机下的Redis可以支持16个数据库,集群模式下只能使用一个数据库空间,即db0。
5)复制结构只支持一层,从节点只能复制主节点,不支持嵌套树状复制结构。
- 搭建集群三个步骤
1.准备节点
2.节点握手 cluster meet{ip}{port}
3.分配槽 redis-cli -h 127.0.0.1 -p 6379 cluster addslots {0...5461} - 集群启动过程
- 当集群内节点信息发生变化,如添加节点、节点下线、故障转移等。节点会自动保存集群状态到配置文件中。
需要注意的是,Redis自动维护集群配置文件,不要手动修改,防止节点重启时产生集群信息错乱 - 节点握手: 节点握手是指一批运行在集群模式下的节点通过Gossip协议彼此通信,达到感知对方的过程。节点握手是集群彼此通信的第一步,由客户端发起命令:cluster meet{ip}{port}
- 用redis-trib.rb搭建集群
- 节点通信
- 集群中每个节点通过一定规则挑选要通信的节点,每个节点可能知道全部节点,
也可能仅知道部分节点,只要这些节点彼此可以正常通信,最终它
们会达到一致的状态。当节点出故障、新节点加入、主从角色变化、槽信息
变更等事件发生时,通过不断的ping/pong消息通信,经过一段时间后所有的
节点都会知道整个集群全部节点的最新状态,从而达到集群状态同步的目的。
- 集群伸缩
- 扩容步骤
1.准备新节点,启动后的新节点作为孤儿节点运行
2.加入集群,执行cluster meet命令
3.迁移槽和数据。
- 收缩集群
- 首先需要确定下线节点是否有负责的槽,
如果是,需要把槽迁移到其他节点,保证节点下线后整个集群槽节点映射的完整性。 - 当下线节点不再负责槽或者本身是从节点时,就可以通知集群内其
他节点忘记下线节点,当所有的节点忘记该节点后可以正常关闭。
缓存设计
1.缓存穿透
1)缓存空对象
2)布隆过滤器
例如:一个推荐系统有4亿个用户id,每个小时算法工程师会根据每个用户之前历史行为计算出推荐数据放到存储层中,但是最新的用户由于没有历史行为,就会发生缓存穿透的行为,为此可以将所有推荐数据的用户做成布隆过滤器。如果布隆过滤器认为该用户id不存在,那么就不会访问存储层,在一定程度保护了存储层
2.缓存雪崩:缓存层由于宕机,key过期等原因导致大量请求到了存储层
- 保证缓存层服务高可用性
- 依赖隔离组件为后端限流并降级
在实际项目中,我们需要对重要的资源(例如Redis、MySQL、HBase、外部接口)都进行隔离,
让每种资源都单独运行在自己的线程池中,即使个别资源出现了问题,对其他服务没有影响