目录
Redis数据类型(string,hash,list,set,zset)
Redis缓存雪崩、缓存穿透、击穿、热key、缓存一致性问题
-
Redis数据类型(string,hash,list,set,zset)
1、String(set、get)【最常用的类型,一个键最大能存储512MB】结构体保存了char数组的长度,空闲长度,char数组
127.0.0.1:6379> set key value
OK
127.0.0.1:6379> get key
"value"
2、Hash(hmset、hget、hgetall)【适合用于存储对象,因为对象包含很多属性】
127.0.0.1:6379> hmset hashmap key1 value1 key2 value2
OK
127.0.0.1:6379> hget hashmap key1
"value1"
127.0.0.1:6379> hgetall hashmap
1) "key1"
2) "value1"
3) "key2"
4) "value2"
3、List (LPUSH、RPUSH、LRANGE)【按照插入顺序排序,内部实现为LinkedList,所以用来实现排行榜、不需要分页(比如每次都只取列表的前5个元素)或者更新频率低(比如每天凌晨更新一次)的列表】
127.0.0.1:6379> LPUSH list a
(integer) 1
127.0.0.1:6379> RPUSH list b
(integer) 2
127.0.0.1:6379> LRANGE list 0 10
1) "a"
2) "b"
4、Set (sadd 、smembers )【内部实现为hashtable。在微博中,可以将一个用户所有的关注人存在一个集合中,将其所有粉丝存在一个集合。Redis还为集合提供了求交集sinter、并集sunion、差集sdiff等操作,可以非常方便的实现如共同关注、共同喜好、二度好友等功能】
127.0.0.1:6379> sadd myset a
(integer) 1
127.0.0.1:6379> sadd myset b
(integer) 1
127.0.0.1:6379> sadd myset a
(integer) 0
127.0.0.1:6379> smembers myset
1) "a"
2) "b"
5、Zset(zadd、zrange myzset 0 10 withscores)【内部实现为跳表。根据概率决定节点的等级。排行榜】
127.0.0.1:6379> zadd myzset 1 a
(integer) 1
127.0.0.1:6379> zadd myzset 2 b
(integer) 1
127.0.0.1:6379> zadd myzset 3 c
(integer) 1
127.0.0.1:6379> zadd myzset 4 c
(integer) 0
127.0.0.1:6379> zrange myzset 0 10 withscores
1) "a"
2) "1"
3) "b"
4) "2"
5) "c"
6) "4"
-
在 Java 中使用 Redis连接池
Java Redis提供了类redis.clients.jedis.JedisPool
来管理我们的Reids连接池对象,并且我们可以使用redis.clients.jedis.JedisPoolConfig
来对连接池进行配置,代码如下:
JedisPoolConfig poolConfig = new JedisPoolConfig();
// 最大空闲数
poolConfig.setMaxIdle(50);
// 最大连接数
poolConfig.setMaxTotal(100);
// 最大等待毫秒数
poolConfig.setMaxWaitMillis(20000);
// 使用配置创建连接池
JedisPool pool = new JedisPool(poolConfig, "localhost");
// 从连接池中获取单个连接
Jedis jedis = pool.getResource();
// 如果需要密码
//jedis.auth("password");
Redis只支持6种数据类型(string/hash/list/set/zset/hyperloglog),但在Java中以类对象为主,所以通常是将对象进行序列化和反序列化存取为String类型。Spring为我们提供了RedisTemplate进行自动正反序列化
-
Redis集群
Redis TCP端口:每个节点都需要打开两个TCP连接,一个用于正常的客户端请求,一个用于集群内部通信,偏移量总是10000。比如客户端通信端口是6379,那集群总线端口则是16379。Redis集群采用P2P的Gossip(流言)协议,Gossip协议工作原理就是节点彼此不断通信交换信息,一段时间后所有的节点都会知道集群完整的信息,这种方式类似流言传播。每个节点都有定时任务,每秒会随机选取5个节点找出最久没有通信的1个节点发送ping消息,每秒会扫描10次本地节点列表,如果发现节点最近一次接受pong消息的时间大于cluster_node_timeout/2,则立刻发送ping消息,防止该节点信息太长时间未更新。每个ping消息的数据量体现在消息头和消息体中,其中消息头主要占用空间的字段是myslots[CLUSTER_SLOTS/8],占用2KB,这块空间占用相对固定。消息体会携带一定数量的其他节点信息用于信息交换,消息体携带数据量跟集群的节点数息息相关,更大的集群每次消息通信的成本也就更高,因此对于Redis集群来说并不是大而全的集群更好。
Redis插槽:Redis的服务器节点相互连通,客户端可以通过任何一个节点连接到集群。Redis集群内置了自动分片机制,集群内部会将所有的key映射到16384个插槽中(使用CRC16算法计算出散列值然后对16384取余),每个节点负责其中部分的插槽的读写(为什么Redis集群有16384个槽)。
集群可用性:
- 每个节点都保存着这个集群所有主节点及从节点的信息,他们之间通过相互的ping-pong判断节点是否可以连接,内部使用二进制协议优化传输速度和带宽
- 投票过程是集群中所有主节点参与,如果有一半的节点ping一个节点时没有回应,集群就认为这个节点宕机了,然后去连接他的备用节点
- 如果该节点和备用节点全部挂了,集群不可用(cluster_state:fail);集群中超过半数的节点检测失效,无论是否有从节点也进入fail
-
Redis 主从架构搭建及同步方法
1、主从同步的方法
增量同步:redis同步的是指令流,主节点会将修改指令记录在本地内存buffer中,然后将指令同步到从节点,从节点执行这些指令。由于内存buffer有限,主节点的buffer是一个定长的环形数组,所以主节点内存buffer满了后,在内存中存储的修改指令可能会覆盖。
全量同步:将主节点的所有数据全部快照到RDB文件中,传输给从节点进行全量加载,之后再进行增量同步。由于快照同步时间可能过长或者主节点内存buffer太小,很有可能在快照同步期间主节点buffer又满了,又会发起快照同步,进入死循环,所以需要配置一个合适大小的buffer。
- 部分同步:从Redis 2.8开始,如果遭遇连接断开,重新连接之后可以从中断处继续进行复制,而不必重新同步。它的工作原理是这样:主服务器端为复制流维护一个内存缓冲区(in-memory backlog)。主从服务器都维护一个复制偏移量(replication offset)和master run id ,当连接断开时,从服务器会重新连接上主服务器,然后请求继续复制,假如主从服务器的两个master run id相同,并且指定的偏移量在内存缓冲区中还有效,复制就会从上次中断的点开始继续。如果其中一个条件不满足,就会进行完全重新同步(在2.8版本之前就是直接进行完全重新同步)。因为主运行id不保存在磁盘中,如果从服务器重启了的话就只能进行完全同步了。部分重新同步这个新特性内部使用PSYNC命令,旧的实现中使用SYNC命令。Redis2.8版本可以检测出它所连接的服务器是否支持PSYNC命令,不支持的话使用SYNC命令。
- 无盘复制:快照同步耗费系统性能,从 Redis 2.8.18 版开始支持无盘复制。主节点一边遍历内存一边将序列化的内容传输到从节点,而不是生成完整的RDB文件后才发送,从节点接收数据后存储到磁盘,接收完后一次性加载。
2、Redis主从同步策略
主从刚刚连接的时候,进行全量同步;全同步结束后,进行增量同步。当然,如果有需要,slave 在任何时候都可以发起全量同步。redis 策略是,无论如何,首先会尝试进行增量同步,如不成功,要求从机进行全量同步。如果多个Slave断线了,需要重启的时候,因为只要Slave启动,就会发送sync请求和主机全量同步,当多个同时出现的时候,可能会导致Master IO剧增宕机
3、Redis主从架构搭建
使用3个虚拟机搭建一主二从的redis主从架构集群。首先参考redis-4.0.12单节点安装在每台机器上安装redis,然后修改redis配置文件,其中一个master节点的配置如下(未列出的保持默认即可):
# basic
daemonize yes
port 6379
logfile /home/hadoop/logs/redis/6379/redis_6379.log
pidfile /home/hadoop/pid/redis/6379/redis_6379.pid
dir /home/hadoop/data/redis/6379
# aof
# 主节点打开AOF机制
appendonly yes
# master
# 绑定本台机器的IP,否则主从节点无法通信
bind 192.168.239.101
# 设置master的认证口令为redis
requirepass redis
# backlog大小
repl-backlog-size 1mb
# 快照同步的超时时间
repl-timeout 60
# 开启无盘复制
repl-diskless-sync yes
# 无盘复制的延迟默认为5s,是为了等待更多的slave连接
repl-diskless-sync-delay 5
# 是否开启主从节点复制数据的延迟机制
# 当关闭时,主节点产生的命令数据无论大小都会及时地发送给从节点,这样主从之间延迟会变小
# 但增加了网络带宽的消耗。适用于主从之间的网络环境良好的场景
# 当开启时,主节点会合并较小的TCP数据包从而节省带宽。
# 默认发送时间间隔取决于Linux的内核,一般默认为40毫秒。
# 这种配置节省了带宽但增大主从之间的延迟。适用于主从网络环境复杂或带宽紧张的场景
repl-disable-tcp-nodelay no
# 触发快照同步的条件
# 如果增量同步的缓存大于256MB,或者超过60s大于64MB,则触发快照同步
client-output-buffer-limit slave 256mb 64mb 60
# 主从节点进行心跳的时间间隔
repl-ping-slave-period 10
两个slave节点的配置如下:
# basic
daemonize yes
port 6379
logfile /home/hadoop/logs/redis/6379/redis_6379.log
pidfile /home/hadoop/pid/redis/6379/redis_6379.pid
dir /home/hadoop/data/redis/6379
# slave
# 绑定本机的IP,另一个为192.168.239.103
bind 192.168.239.102
# 绑定master的ip和port
slaveof 192.168.239.101 6379
# 从节点只读
slave-read-only yes
# 从节点在处于快照同步期间是否对外提供服务
slave-serve-stale-data yes
# 如果 master 检测到 slave 的数量小于这个配置设置的值,将拒绝对外提供服务,0 代表,无论 slave 有几个都会对外提供服务
min-slaves-to-write 0
# 如果 master 发现大于等于 ${min-slaves-to-write} 个 slave 与自己的心跳超过此处配置的时间(单位s)
# 就拒绝对外提供服务
min-slaves-max-lag 10
# master的认证口令
masterauth redis
-
Redis master宕机和脑裂
两个配置可以减少异步复制和脑裂导致的数据丢失:
- min-slaves-to-write 1
- min-slaves-max-lag 10
解释:要求至少有1个slave,数据复制和同步的延迟不能超过10秒,如果说一旦所有的slave,数据复制和同步的延迟都超过了10秒钟,那么这个时候,master就不会再接收任何请求了
(1)减少异步复制的数据丢失
有了min-slaves-max-lag这个配置,就可以确保说,一旦slave复制数据和ack延时太长,就认为可能master宕机后损失的数据太多了,那么就拒绝写请求,这样可以把master宕机时由于部分数据未同步到slave导致的数据丢失降低的可控范围内
(2)减少脑裂的数据丢失
如果一个master出现了脑裂,跟其他slave丢了连接,那么上面两个配置可以确保说,如果不能继续给指定数量的slave发送数据,而且slave超过10秒没有给自己ack消息,那么就直接拒绝客户端的写请求,这样脑裂后的旧master就不会接受client的新数据,也就避免了数据丢失
上面的配置就确保了,如果跟任何一个slave丢了连接,在10秒后发现没有slave给自己ack,那么就拒绝新的写请求。因此在脑裂场景下,最多就丢失10秒的数据
-
Redis缓存雪崩、缓存穿透、击穿、热key、缓存一致性问题
一、缓存雪崩:
1、缓存失效时间相同导致大量缓存同时失效
- 缓存时间加随机因子,不同商品设置不同失效时间
2、缓存系统故障
- 事前:增加缓存系统高可用方案设计,避免出现系统性故障(主从、集群)
- 事故中:
- 增加多级缓存,在单一缓存故障时,仍有其他缓存系统可用,如之前项目中使用的三级缓存方案:内存级缓存->Memcached->Redis这样的方案;
- 启用
熔断限流机制
,只允许可承受流量,避免全部流量压垮系统(hystrix)
- 事后:缓存数据持久化,在故障后快速恢复缓存系统
二、缓存穿透
1、访问不存在数据从而绕过缓存,直接读取数据库
- 数据不存在时,在缓存系统设置空值
- 布隆过滤器bloomfilter,他的原理也很简单就是利用高效的数据结构和算法快速判断出你这个Key是否在数据库中存在,不存在你return就好了。核心是一个超大的位数组和几个哈希函数,通过哈希函数将key映射到位数组上,查找时通过同样的方法就可以判断key是否不存在。只能确定不存在,不能确定存在,因为有hash冲突。
三、缓存击穿
缓存击穿是指当缓存中尚未加载某个热点数据或某个热点数据已过期,在该热点数据载入缓存之前,有大量的查询请求穿过缓存,直接查询数据库。这种情况会导致数据库压力瞬间骤增,造成大量请求阻塞,甚至直接挂掉。
解决缓存击穿的方法有两种,第一种是设置key永不过期(无法解决首次加载的情况);第二种是使用分布式锁,保证同一时刻只能有一个查询请求重新加载热点数据到缓存中,这样,其他的线程只需等待该线程运行完毕,即可重新从Redis中获取数据。
四、热key
缓存击穿是高并发请求在某一瞬间打到DB,热key一般是指高并发请求在某一瞬间打到redis集群的某台机器,导致机器cpu、io等飙升或宕机。
- 将热key分片存储在多台机器
- 使用本地缓存
五、缓存一致性
1、常规流程是:读操作:命中缓存则返回,无缓存则读数据库并写缓存;写操作:先删除缓存,再更新数据库。会出现的问题是当写操作先删除缓存尚未更新数据库时,读操作未命中缓存所以读数据库并写缓存,然后写操作更新数据库,导致缓存中数据和数据库不一致
- 最终一致性方案:事实上很多大公司的代码规范中都规定不要在事务中调用远程服务,因为在处理速度上来说,cpu > 操作系统缓存 > 内存 > 磁盘 > 网络,网络调用会花费大量时间,大事务会造成数据库吞吐量降低,因此摒弃事务这种方法。既然保证不了强一致性,就保证最终一致性:先更新数据库,成功后再删除缓存,若删除失败则将失败消息发送到MQ,MQ不断重试让缓存失效,使用MQ来保证缓存和数据库的最终一致性。实际上这种方法还是有问题,当删除缓存失败了要向MQ发消息时,服务器宕机或重启了导致消息没有发送到MQ,怎么办?具体更深入的研究请参考以下链接。
-
Redis过期删除策略(定期删除+惰性删除)
设置了过期时间的key是如何删除的?过期后会立即释放内存吗?
定期删除:redis对过期key的清理频率和清理时间有限制,以尽量不影响服务的性能。redis会把设置了过期时间的key放在单独的字典中,每隔一段时间执行一次删除,具体的算法为:
1、redis配置项"hz"定义了清理任务的执行周期,默认为10,即cpu空闲时每秒执行10次。每次清理时间不超过cpu时间的25%,若"hz"为10则一次清理不超过25ms。
2、清理时依次遍历所有db,从db中随机取20个key判断是否过期,过期则删除。若超过5个key过期则重复此步骤,否则遍历下一个db。
惰性删除:过期的key并不一定会马上删除,还会占用着内存,当你真正查询这个key时,redis会检查这个设置了过期时间的key是否过期,如果过期了就删除并返回空。
-
Redis缓存淘汰策略
在redis内存使用超过一定值时(redis配置maxmemory)会根据淘汰策略删除一些缓存:
- noeviction:当内存使用超过配置的时候会返回错误,不会驱逐任何键(默认策略,不建议使用)
- allkeys-lru:加入键的时候,如果过限,首先通过LRU算法驱逐最久没有使用的键
- volatile-lru:加入键的时候如果过限,首先从设置了过期时间的键集合中驱逐最久没有使用的键(不建议使用)
- allkeys-random:加入键的时候如果过限,从所有key随机删除
- volatile-random:加入键的时候如果过限,从过期键的集合中随机驱逐(不建议使用)
- volatile-ttl:从配置了过期时间的键中驱逐马上就要过期的键
- volatile-lfu:从所有配置了过期时间的键中驱逐使用频率最少的键
- allkeys-lfu:从所有键中驱逐使用频率最少的键
如果淘汰策略是allkeys-lru或者volatile-lru, 则根据配置的采样值maxmemory_samples,随机从数据库中选择maxmemory_samples个key, 淘汰其中热度最低的key对应的缓存数据。如果淘汰策略是volatile-ttl,则根据配置的采样值maxmemory_samples,随机从数据库中选择maxmemory_samples个key,淘汰其中最先要超时的key对应的缓存数据。所以采样参数maxmemory_samples配置的数值越大, 就越能精确的查找到待淘汰的缓存数据,但是也消耗更多的CPU计算,执行效率降低
-
持久化RDB和AOF
RDB把数据(键值对)以快照的形式保存在磁盘上。提供了三种机制:save(主进程操作,阻塞服务器)、bgsave(子进程操作)、自动化(配置文件中设置的保存条件数组,比如900秒1条修改操作、60秒1000条修改操作)
AOF保存所有修改命令,将每一个收到的写命令都通过write函数追加到文件中,通俗的理解就是日志记录。提供了三种机制:每次修改同步、每秒同步、不同步。由于aof文件过大,有重写机制。
优劣:
- 数据丢失:当进行快照持久化时,会开启一个子进程专门负责快照持久化,子进程会拥有父进程的内存数据,父进程修改内存子进程不会反应出来,所以在快照持久化期间修改的数据不会被保存,可能丢失数据。AOF可以更好的保护数据不丢失,一般AOF会每隔1秒,通过一个后台线程执行一次fsync操作,最多丢失1秒钟的数据。
- 性能:AOF开启后,支持的写QPS会比RDB支持的写QPS低,因为AOF一般会配置成每秒fsync一次日志文件,当然,每秒一次fsync,性能也还是很高的
- 恢复速度:RDB 在恢复大数据集时的速度比 AOF 的恢复速度要快。
深度好文:
Redis高可用集群-哨兵模式(Redis-Sentinel)搭建配置教程