redis是单线程的,没有锁。mysql是InnoDB存储引擎,有行锁
redis没有表的概念,以db间隔数据,可以理解为命名空间,最多16个,编号是0到15
缓存使用场景:
1、DB缓存,减轻数据库压力
2、提高系统响应时间
3、服务间session共享,例如存储登录信息
4、做分布式锁
5、做乐观锁,如高性能、秒杀
缓存的概念:
cpu上面有一块区域叫cache,叫二级缓存,缓存是借助了这个概念,以空间换时间
缓存读写模式:
1、 读的时候,先读缓存,缓存没有的话,就读数据库,然后取出数据后放入缓存,同时返回响应; 更新的时候,先更新数据库,然后再删除缓存。
为啥删除缓存不是更新缓存呢?因为加入缓存是hash或者list类型的话,需要遍历,这样会增加处理时间
Redis安装:
1、 yum install -y gcc-c++
2、 yum install -y wget
3、 wget http://download.redis.io/releases/redis-5.0.5.tar.gz
4、 tar -zxf redis-5.0.5.tar.gz
5、 cd redis-5.0.5/src
Make
6、 mkdir /opt/redis
7、 make install PREFIX=/opt/redis
Redis基本数据存储类型:
常用的:String、hash、List、Set、sortedset
不常用的:bitmap位图类型、geo地理位置类型
Redis5.0新增一种:stream类型
String类型可以表达三种类型的值:字符串、整数、浮点数,不同类型的值编码不同
List类型存储有序的可重复的元素,获取头部或者尾部元素的速度非常快,元素最多有2^32-1个元素,用于栈或者队列使用
set元素最多有2^32-1个元素
Hash类型最多有2^32-1个元素
bitmap位图类型是进行位操作的,本身会节省极大的空间,应用场景:用户每月签到,用户id为key,日期作为偏移量,1表示签到
统计活跃用户,日期为key,用户id为偏移量,1代表活跃
查询用户在线状态,日期为key,用户id为偏移量,1表示在线
Geo地理位置类型
stream数据流类型, 用于可持久化的消息队列
Redis命令:
1、ttl key 查看一个key的剩余时间
2、type key 查看一个key的类型
String常用命令:
3、set key value 赋值
4、get key 取值
5、getset key value 取值并赋值
6、setnx key value 当value不存在的时候赋值,存在就不赋值 用于分布式锁
7、append key value 向尾部追加值
8、strlen key 获取字符串长度
9、incr key 递增数字 用于乐观锁
10、incrby key num 增加指定的整数
11、decr key 递减数字
12、decrby key num 减少指定的整数
List常用命令:
13、lpush key v1 v2 v3 从左侧插入
14、lpop key 从列表左侧取出
15、rpush key v1 v2 v3 从右侧插入
16、rpop key 从列表右侧取出
17、lpushx key value 将值插到列表头部
18、rpushx key value 将值插到列表尾部
19、blpop key timeout 从列表左侧取出,当列表为空时阻塞,可以 设置最大阻塞时间,单位为秒
20、brpop key timeout 从列表右侧取出,当列表为空时阻塞,可以 设置最大阻塞时间,单位为秒
21、llen key 获取列表中元素的个数
22、lindex key index 获取列表中下标为index的元素
23、lrange key start end 获取列表中指定区间的元素
24、lrem key count value 删除列表中与value相同的元素,当count>0时从左侧开始删,当count<0时从右侧开始删,当count=0时,全删
25、lset key index value 将列表index的元素设置成value值
26、ltrim key start end 对列表进行修剪,只保留start到end区间
27、rpoplpush key1 key2 从key1右侧弹出并插入到key2左侧
28、brpoplpush key1 key2 从key1右侧弹出并插入到key2左侧,会阻塞
29、linsert key before/after pivot value 将value插入到列表,并且位于pivot之前或者之后
Set常用命令:
30、sadd key value1 value2 … 为集合添加新成员
31、srem key value1 value2 … 删除集合中指定的成员
32、smember key 获取集合中 所有的元素
33、spop key 返回集合中随机一个元素,并将该元素删除
34、srandmember key 返回集合中一个随机元素,不会删除该元素
35、scard key 获取集合中元素的数量
36、sismember key member 判断元素是否在集合内
37、sinter key1 key2 key3 求多集合的交集
38、sdiff key1 key2 key3 求多集合的差集
39、sunion key1 key2 key3 求多集合的并集
Hash常用命令:
40、hset key field value 赋值,不区分新增或者修改
41、hmset key field1 v1 field2 v2 批量赋值
42、hsetnx key field value 赋值,如果field存在则不操作
43、hexists key field 查看某个field是否存在
44、hget key field 获取一个字段
45、hmget key field1 field2 获取多个字段
46、hgetall key
47、获取所有的字段
48、hdel key field1 field 2 删除指定元素
49、hincrby key field increment 指定字段自增
50、hlen key 获得字段数量
bitmap常用命令:
51、setbit key offset value 设置key在offset处的bit值,只能是0或者1
52、getbit key offset 获得key在offset处的bit值
53、bitcount key 获得key的bit位为1的个数
54、bitpos key value 返回第一个被设置为bit值的索引值
55、bitop and/or/xor/not destkey key1 key2… 对多个key逻辑运算后存入destkey
Geo常用命令:
56、 geoadd key 经度 纬度 成员名称1 经度1 纬度1 成员名称2 经度2 纬度 2 … 添加地理坐标
57、 geohash key 成员名称1 成员名称2… 返回标准的geohash串
58、 geopos key 成员名称1 成员名称2… 返回成员经纬度
59、 geodist key 成员1 成员2 单位 计算成员间距离
60、 georadiusbymember key 成员 值 单位 count 数 asc[desc] 根据成员查找附近的成员
发布与订阅
Redis提供了发布订阅功能,可以用于消息的传输
Redis的发布订阅机制包括三个部分,publisher,subscriber和Channel, 发布者和订阅者都是Redis客户端,Channel则为Redis服务器端。
订阅 subscribe channel1 channel2 ..
发布消息 publish channel message
退订 unsubscribe channel
模式匹配 psubscribe ch* 订阅所有以ch开头的频道
模式退订 punsubscribe ch*
发布订阅使用场景: 哨兵模式,Redisson框架使用
在Redis哨兵模式中,哨兵通过发布与订阅的方式与Redis主服务器和Redis从服务器进行通信
Redisson是一个分布式锁框架,在Redisson分布式锁释放的时候,是使用发布与订阅的方式通知的,
Redis事务
Redis的事务是通过multi、exec、discard和watch这四个命令来完成的。
Redis的单个命令都是原子性的,所以这里需要确保事务性的对象是命令集合。
Redis将命令集合序列化并确保处于同一事务的命令集合连续且不被打断的执行
Redis不支持回滚操作
事务命令
multi:用于标记事务块的开始,Redis会将后续的命令逐个放入队列中,然后使用exec原子化地执行这个
命令队列
exec:执行命令队列
discard:清除命令队列
watch:监视key
unwatch:清除监视key
用multi开启事务之后,再输入的命令不会立即执行,直到使用exec执行命令队列
事务机制
1. 事务开始
在RedisClient中,有属性flags,用来表示是否在事务中,flags=REDIS_MULTI
2. 命令入队
RedisClient将命令存放在事务队列中 (EXEC,DISCARD,WATCH,MULTI除外)
3. 事务队列
multiCmd *commands 用于存放命令
4. 执行事务
RedisClient向服务器端发送exec命令,RedisServer会遍历事务队列,执行队列中的命令,最后将执 行的结果一次性返回给客户端。
慢查询设置
在redis.conf中可以配置和慢查询日志相关的选项:
#执行时间超过多少微秒的命令请求会被记录到日志上 0 :全记录 <0 不记录
slowlog-log-slower-than 10000
#slowlog-max-len 存储慢查询日志条数
slowlog-max-len 128
config set的方式可以临时设置,redis重启后就无效
config set slowlog-log-slower-than 微秒
config set slowlog-max-len 条数
查看日志:slowlog get [n]
查看日志数量的 slowlog len
清除日志 slowlog reset
使用slowlog get 可以获得执行较慢的redis命令,针对该命令可以进行优化:
1、尽量使用短的key,对于value有些也可精简,能使用int就int。
2、避免使用keys *、hgetall等全量操作。
3、减少大key的存取,打散为小key
4、将rdb改为aof模式
rdb fork 子进程 主进程阻塞 redis大幅下降
关闭持久化 , (适合于数据量较小)
改aof 命令式
5、想要一次添加多条数据的时候可以使用管道
6、尽可能地使用哈希存储
7、尽量限制下redis使用的内存大小,这样可以避免redis使用swap分区或者出现OOM错误
内存与硬盘的swap
监视器
Redis客户端通过执行MONITOR命令可以将自己变为一个监视器,实时地接受并打印出服务器当前处理
的命令请求的相关信息。
此时,当其他客户端向服务器发送一条命令请求时,服务器除了会处理这条命令请求之外,还会将这条
命令请求的信息发送给所有监视器。
客户端成为监视器命令: monitor
Redis监控平台:
Grafana 是一个开箱即用的可视化工具,具有功能齐全的度量仪表盘和图形编辑器,有灵活丰富的图形
化选项,可以混合多种风格,支持多个数据源特点。
Prometheus是一个开源的服务监控系统,它通过HTTP协议从远程的机器收集数据并存储在本地的时序
数据库上。
redis_exporter为Prometheus提供了redis指标的导出,配合Prometheus以及grafana进行可视化及监
控。
Redis持久化:
一、RDB:redis默认持久化方式,是通过快照方式持久化数据。
触发方式:
1. 符合自定义配置的快照规则( 在redis.conf中配置:save 多少秒内 数据变了多少,如 save 900 1 # 表示15分钟(900秒钟)内至少1个键被更改则进行快照。)
2. 执行save或者bgsave命令
3. 执行flushall命令
4. 执行主从复制操作 (第一次)
优点:
1、 RDB是二进制压缩文件,占用空间小,便于传输(传给slaver)
2、主进程fork子进程,由子进程生成rdb文件,可以最大化Redis性能,redis中的 数据量不能太多,复制过程中主进程阻塞
3、数据恢复时快
缺点:
不保证数据完整性,会丢失最后一次快照以后更改的所有数据
2、AOF:默认不开启 ,以记录操作日志的方式持久化
保存方式:
1、不保存,只有redis关闭、AOF功能关闭、缓存满时会执行save命令,执行save时主线程会阻塞
2、每秒钟保存一次,save命令由后台子进程执行,不会阻塞主进程,推荐使用
3、每个命令都会保存一次,save由主进程执行,会阻塞
AOF数据还原的流程:服务器端生成一个伪客户端,然后从aof文件中读取命令执行
3、混合持久化:redis4.0开始支持混合持久化,即RDB+AOF
RDB的头+AOF的身体---->appendonly.aof
开启:
RDB与AOF对比:
1、RDB存某个时刻的数据快照,采用二进制压缩存储,文件小。AOF存操作命令,采用文本存储(混合),文件大。
2、RDB性能高、AOF性能较低,但是RDB生成快照时会阻塞主进程,数据量大时也会影响性能
3、RDB在配置触发状态会丢失最后一次快照以后更改的所有数据,AOF设置为每秒保存一次,则最多丢2秒的数据
4、Redis以主服务器模式运行,RDB不会保存过期键值对数据,Redis以从服务器模式运行,RDB会保
存过期键值对,当主服务器向从服务器同步时,再清空过期键值对。AOF写入文件时,对过期的key会追加一条del命令,当执行AOF重写时,会忽略过期key和del命令
当用redis做数据库时,rdb与aof都要开启
当用redis做缓存时,只需要开启aof即可
Redis底层数据结构:
redisDb数据结构
typedef struct redisDb {
int id; //id是数据库序号,为0-15(默认Redis有16个数据库)
long avg_ttl; //存储的数据库对象的平均ttl(time to live),用于统计
dict *dict; //存储数据库所有的key-value
dict *expires; //存储key的过期时间
dict *blocking_keys;//blpop 存储阻塞key和客户端对象
dict *ready_keys;//阻塞后push 响应阻塞客户端 存储阻塞后push的key和客户端对象
dict *watched_keys;//存储watch监控的的key和客户端对象
} redisDb;
redisObject数据结构:
typedef struct redisObject {
unsigned type:4;//类型 五种对象类型
unsigned encoding:4;//编码
void *ptr;//指向底层实现数据结构的指针
//...
int refcount;//引用计数
//...
unsigned lru:LRU_BITS; //LRU_BITS为24bit 记录最后一次被命令程序访问的时间
//...
}robj;
type类型:
1、String
redis使用 SDS (简单动态字符串)存储字符串和整形
struct sdshdr{
//记录buf数组中已使用字节的数量
int len;
//记录 buf 数组中未使用字节的数量
int free;
//字节数组,用于保存字符串
char buf[];
}
SDS优势:
方便获取长度,长度=free+len+1
由于记录了长度,在可能造成缓冲区溢出时会重新分配内存,杜绝缓冲区溢出
可以存储二进制数据,
2、跳跃表:(有序列表)
类似二分查找
插入节点时,对每一层的操作进行随机(二分之一的概率)插入或者不插入
3、散列表(Hash):
数组+链表
hash表数组初始长度是4,后面每次扩容都是翻倍
4、压缩列表(ziplist):
压缩列表(ziplist)是由一系列特殊编码的连续内存块组成的顺序型数据结构,节省内存, 是一个字节数组,可以包含多个节点(entry)。每个节点可以保存一个字节数组或一个整数。
sorted-set和hash元素个数少且是小整数或短字符串(直接使用)
list用快速链表(quicklist)数据结构存储,而快速链表是双向列表与压缩列表的组合。(间接使用)
5、整数集合(intset)
整数集合(intset)是一个有序的(整数升序)、存储整数的连续存储结构。
当集合元素都是整数并且数值都小于2^64时,会使用
6、快速列表(quicklist)
快速列表是redis列表的底层实现
快速列表就是一个双向列表,里面每一个元素都是ziplist
缓存淘汰策略:
redis默认的缓存淘汰策略是禁止驱逐
设置maxmemory,当redis使用内存量接近该值时,会执行缓存淘汰
redis删除数据方式有定时删除、惰性删除、主动删除三种,redis采用惰性删除和主动删除两种
1、定时删除:在创建键的时候同时创建一个定时器,到时间了就删掉。会消耗cpu
2、惰性删除:读取数据的时候先检查一下数据是否有效,如果失效了,就删掉
3、主动删除:通过设置主动删除策略,来删除不符合条件的键,默认是no-envoction(禁止驱逐)
一、lru:最近最少使用
删除那些最近没有被使用的键。原理是记录了每个键的最后访问时间,当淘汰机制触发时,redis会随机挑选一批元素,从中取出lru最大的值进行淘汰
Lru分为两种:volatile-lru和allkeys-lru
1、volatile-lru是从设置了过期时间的key中数据淘汰(性能没有allkeys好,因为还要存过期时间)
2、allkeys-lru是从所有数据中进行数据淘汰(一般用这种即可)
二、lfu:最近最少使用
原理是记录访问次数,删除访问次数少的数据
lfu也分为两种:volatile-lfu和allkeys-lfu
三、random,随机删除
分为两种volatile-random和allkeys-random
四、ttl
从设置了过期时间的key中删除即将过期的数据。可以自己控制缓存删除策略
五、noenviction
禁止驱逐(默认的)
缓存预热:
缓存预热就是提前将缓存数据写到redis里面,避免用户使用的时候再写缓存
1、数据量不大的时候,可以在项目启动的时候写入
2、采用定时任务的方式刷新缓存
缓存常见问题
一、缓存穿透:
高并发查询的key不存在(数据库也不存在),访问会直接请求db
解决方案:
1、不存在的key也放到缓存中,并且赋值为空(过期时间不能太长)。这种方式,面对恶意攻击时(制造大量不存在的key进行请求)就不好用了
2、使用布隆过滤器。在缓存前加一层 布隆过滤器 ,先去查过滤器中是否存在key,不存在直接返回,存在的话就去访问缓存和db
布隆过滤器就是一个很长的二进制向量+多个hash函数组成。
假设我们布隆过滤器是一个64位的二进制和三个hash函数组成,当缓存中存在数据的时候,对这个key分别进行hash函数运算,得到的偏移量分别是0,16,37,那么就把这三位的数值置成1.当再进来一个请求key的时候,进行hash之后偏移量是12,45,48,这三位的二进制值分别是0,1,1,不全是1,所以这个key就不在缓存中存在。
使用很长的二进制以及多个hash函数的原因就是解决hash冲突,二进制代替字符串也节省空间
二、缓存雪崩:
短时间内,大量的key失效(或者redis重启),使得请求都打到数据库上
1、对不同的key设置不同的过期时间
2、设置二级缓存(可能数据不一致)
3、使用高可用(主从集群,可能有脏读)
三、缓存击穿:
超高并发访问的key失效了,导致请求打到服务器
1、使用分布式锁控制访问线程,使用setnx互斥锁先判断,这样其他线程就在等待,保证不会有高并发去访问数据库
2、不设置超时时间(这样会出现数据库数据更新,缓存数据不及时更新的情况,可以用延时双删策略处理)
延时双删:
数据库数据更新的时候,删除缓存数据。过两秒钟再删一次。这样避免数据库更新没有commit的时候,缓存已经删除了,有请求进来读的还是老数据,又把老数据读到缓存中了,造成脏读的问题
四、数据不一致
缓存跟数据库数据不一致
强一致性很难,可保证最终一致。更新数据库和删除缓存不是一个原子操作,时序不可控,高并发情况下会产生数据不一致。
解决方案:
1)、 延时双删解决。
2)、设置缓存过期时间
3)、记录缓存删除失败日志,然后跑脚本删除
五、数据并发竞争
多个redis客户端同时set同一个key引起的并发问题
1、用分布式锁+时间戳
用setnx设置分布式锁,再保存一个时间戳来保证set的顺序
2、使用消息队列
六、hot key
有大量的请求访问redis某个key,由于流量集中达到了网络上限,导致redis宕机,造成缓存击穿
发现热key:
1、预估hot key,比如秒杀商品,火爆新闻
2、客户端进行统计key的访问量
3、如果是proxy,如codis,可以在代理端统计
4、利用redis自带的命令,monitors、hotkeys()执行缓慢,一般不要用
5、利用大数据流式计算,如spark、flink
解决热key
1、把分布式缓存变成本地缓存。当发现热key时,把缓存数据取出,直接读本地缓存,用Ehcache、Guava Cache都可以(数据不会保证时时一致)
2、主从节点的redis可以在主节点备份一份热点数据,访问的时候随机访问到服务器上面,均摊压力
3、熔断限流
比如每个实例限制每秒最多请求缓存集群400次,一超过就熔断返回“服务器异常稍后刷新”的提示。
七、big key
影响
1、 大key会大量占用内存,在集群中无法均衡
2、 Redis的性能下降,主从复制异常
3、 在主动删除或过期删除时会操作时间过长而引起服务阻塞(删除del命令是一个阻塞命令)
如何发现大key
1、 redis-cli --bigkeys命令。可以找到某个实例5种数据类型(String、hash、list、set、zset)的最大key。但如果Redis 的key比较多,执行该命令会比较慢
2、获取redis的rdb文件, 通过rdbtools分析rdb生成csv文件,再导入MySQL或其他数据库中进行分析统计,根据size_in_bytes统计bigkey
优化
1、String类型的大key,可以存mongo啥的。如果必须要存redis,就单独存储,不要跟其他的key一起存储,采用一主一从或者多从方式
2、将单个key拆成多个,用mget批量获取
3、hash、set、zset、list类型元素过多,可以拆开(类似分页)
4、删除大key的时候,不要用del命令,会阻塞。用unlink命令,这个会先把key删除,然后再异步删除value
用redis做mybatis的二级缓存,分布式环境下可以使用
分布式锁
用watch实现乐观锁
1、利用redis的watch功能,监控这个redisKey的状态值
2、获取redisKey的值
3、创建redis事务
4、给这个key的值+1
5、然后去执行这个事务,如果key的值被修改过则回滚,key不加1
实现分布式锁
1、使用set(推荐使用)
/**
* 使用redis的set命令实现获取分布式锁
* @param lockKey 可以就是锁
* @param requestId 请求ID,保证同一性 uuid+threadID
* @param expireTime 过期时间,避免死锁
* @return
*/
public boolean getLock(String lockKey,String requestId,int expireTime) {
//NX:保证互斥性
// hset 原子性操作 只要lockKey有效 则说明有进程在使用分布式锁
String result = jedis.set(lockKey, requestId, "NX", "EX", expireTime);
if("OK".equals(result)) {
return true;
}
return false;
}
2、使用setnx
public boolean getLock(String lockKey,String requestId,int expireTime) {
Long result = jedis.setnx(lockKey, requestId);
if(result == 1) {
//成功设置 进程down 永久有效 别的进程就无法获得锁
jedis.expire(lockKey, expireTime);
return true;
}
return false;
}
删除锁
1、 (del命令实现) -- 并发有问题
/**
* 释放分布式锁
* @param lockKey
* @param requestId
*/
public static void releaseLock(String lockKey,String requestId) {
if (requestId.equals(jedis.get(lockKey))) {
jedis.del(lockKey);
}
}
问题在于如果调用jedis.del()方法的时候,这把锁已经不属于当前客户端的时候会解除他人加的锁。
那么是否真的有这种场景?答案是肯定的,比如客户端A加锁,一段时间之后客户端A解锁,在执行 jedis.del()之前,锁突然过期了,此时客户端B尝试加锁成功,然后客户端A再执行del()方法,则将客户 端B的锁给解除了。
2、 (redis+lua脚本实现)—推荐
public static boolean releaseLock(String lockKey, String requestId) {
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return
redis.call('del', KEYS[1]) else return 0 end";
Object result = jedis.eval(script, Collections.singletonList(lockKey),
Collections.singletonList(requestId));
if (result.equals(1L)) {
return true;
}
return false;
}
Redission分布式锁
分布式集群架构中的session分离
用spring-session-data-redis(SpringSession),可以实现基于redis来实现的session分离
Redis高可用方案:
一、主从复制
主redis不需要配置,从redis修改redis.conf文件
# slaveof
# 表示当前【从服务器】对应的【主服务器】的IP是192.168.10.135,端口是6379。
replicaof 192.168.10.135 6379
作用:
1、从机作为主机的数据备份,默认情况下,从机可读不可 写,主机宕机后,从机也不能写,可以用哨兵模式实现主从切换
2、读写分离。主负责写,从负责部分读,提高性能和吞吐量,但是主从数据一致性会有问题
主服务器向从服务器传输数据,第一次使用rdb文件全量传输数据,后面使用命令增量传输数据
全量同步:
1、同步快照阶段: Master 创建并发送快照RDB给Slave , Slave 载入并解析快照。Master 同时将此阶段所产生的新的写命令存储到缓冲区。
2、同步写缓冲阶段: Master 向Slave 同步存储在缓冲区的写操作命令。
3、同步增量阶段: Master 向Slave 同步写操作命令。
增量同步:
1、Redis增量同步主要指Slave完成初始化后开始正常工作时, Master 发生的写操作同步到Slave 的过程。
2、 通常情况下, Master 每执行一个写命令就会向Slave 发送相同的写命令,然后Slave 接收并执行。
二、哨兵模式
哨兵模式是 由一个或多个(只能是单数,原因是选举leader的时候不能出现相同的选票 )sentinel实例组成sentinel集群可以监视一个或多个主服务器和多个从服务器。 当主服务器进入下线状态时,sentinel可以将该主服务器下的某一从服务器升级为主服务器继续提供服务,从而保证redis的高可用性。
哨兵检测模式
1、哨兵获取主从服务器信息,有两种方式:订阅方式、命令连接
订阅方式:哨兵每2秒向主从服务器发一个消息,消息会携带哨兵自身信息以及自助服务器信息
命令连接:哨兵每10秒向主服务器发送一个命令,获取主服务器以及从服务器的信息
哨兵之间不需要相互检测是否正常,因为可以互相通过对服务器的命令获取其他哨兵的信息
2、检测主从服务器状态:
哨兵每秒会向所有建立了连接的服务器发送ping命令,实例在 down-after-milliseconds毫秒内没有返回,就认为是下线了。当哨兵检测服务器下线之后,会向其他的哨兵发送查询命令,如果其他哨兵(数量达到配置中的quorum数量,一般是哨兵总数的二分之一以上)也认为该服务器下线了,就认为这个服务器真的下线了
3、故障转移
1、选举一个leader哨兵,使用raf选举t算法(一般就是谁最先发现下线的服务器,谁就是哨兵leader)
leader哨兵会进行的操作
1. 它会将失效Master 的其中一个Slave 升级为新的Master , 并让失效Master 的其他Slave 改为复制新的Master ;
2. 当客户端试图连接失效的Master 时,集群也会向客户端返回新Master 的地址,使得集群可以使用现在的Master 替换失效Master 。
3. Master 和Slave 服务器切换后, Master 的redis.conf 、Slave 的redis.conf 和sentinel.conf 的配置文件的内容都会发生相应的改变,即, Master 主服务器的redis.conf配置文件中会多一行replicaof 的配置, sentinel.conf 的监控目标会随之调换。
哨兵选新主节点的逻辑:
1. 过滤掉主观下线的节点
2. 选择slave-priority(权限)最高的节点,如果由则返回没有就继续选择(一般都不会这只这个)
3. 选择出复制偏移量最大的系节点,因为复制偏移量越大则数据复制的越完整,如果有就返回了,没有就继续
4. 选择run_id最小的节点,因为run_id越小说明重启次数越少,越稳定
集群:
主从模式主要实现高可用、高性能(读写分离,可能会有数据一致性的问题),但是如果数据量很多,一个主服务器放不下,就需要多台主服务器解决高扩展问题,这时候就需要用到集群了
集群中,数据是分区的,是横向扩展的
数据分区方式:
1、范围分区(服务器端)
对key的不同的范围分区,比如1~10000分配到redis1,10000~20000分配到redis2
好处:简单,方便迁移和扩展
缺点:热点数据分配不均,性能损失;非数字型key无法使用
2、hash分区(服务器端)
Redis实例=hash(key)%N N是主机个数
好处:支持热点数据,热点数据分布均匀,性能好
缺点:扩展性差,需要重新计算,迁移复杂
3、一致性hash(服务器端)
普通hash是对主机数取模,一致性hash,是对2^32(42亿)取模。然后用服务的ip地址hash之后对2^32取模。来了 key之后,得到hash值,往后找,找到第一个ip的hash值,就落在这台服务器上面
扩容:如上图,redis主机ABC,假如再加一个D,位于AC之间,那么BC上面的数据都不会落在D上面,只需要把A上面的数据重新分配就好了
hash环偏移问题:
可以增加主机解决,但是成本较高。还可以使用虚拟节点,
一致性hash缺点:
1、复杂度高,客户端需要自己处理路由、高可用、故障转移等问题
4、代理端分区(逐渐被淘汰了)
常见代理端有codis,Twitter
codis将所有的keymore划分到1024个槽位,同时保存了槽位和服务分组的映射关系
proxy优点:
1、客户端不需要关心数据分区
2、支持在线数据迁移
3、高可用,
4、自动进行数据均衡
5、最大支持1024个redis实例
缺点:
1、redis是自己的一个单独分支,不能保持跟官方一致(codis已经不再更新了,慢慢被淘汰了,大趋势是使用原生rediscluster)
2、codis的proxy只有一个,整个性能会下降20%
3、某些命令不支持
5、官方cluster分区
Redis3.0之后,官方提出了完整的集群解决方案,包括 sharding(分区)、replication(复制)、failover(故障转移)。称为RedisCluster。
Redis5.0前采用redis-trib进行集群的创建和管理,需要ruby支持
Redis5.0可以直接使用Redis-cli进行集群的创建和管理
cluster是去中心化,没有中心节点,是点对点(p2p)的集群架构
cluster分片原理:
cluster使用了16384(2^14)个slot哈希槽,把所有的节点映射到solt上面,采用平均分配和连续分配。加入有5个节点:
redis先对key进行crc16算法(这个算法是16位的算法,但是slot只使用了14位,作者发博客说是因为他觉得16384足够了,一般redis集群最多不会超过1000个节点)进行计算,然后对16384取模,找到哈希槽,就找到了哈希槽映射的节点
slot一定要连续,否则cluster不能工作
优势
1、高性能
Redis Cluster 的性能与单节点部署是同级别的。
多主节点、负载均衡、读写分离
2、高可用
Redis Cluster 支持标准的 主从复制配置来保障高可用和高可靠。
Redis Cluster 也实现了一个类似 Raft 的共识方式,来保障整个集群的可用性。
3、易扩展
向 Redis Cluster 中添加新节点,或者移除节点,都是透明的,不需要停机。
水平、垂直方向都非常容易扩展。
数据分区,海量数据,数据存储
4、原生
部署 Redis Cluster 不需要其他的代理或者工具,而且 Redis Cluster 和单机 Redis 几乎完全兼容。
搭建cluster最少需要三台主服务器,三台从服务器
客户端连接集群:
./redis-cli -h 127.0.0.1 -p 7001 -c
-c代表是以redis集群方式进行连接
集群连接重定向:
一、moved重定向
1.每个节点通过通信都会共享Redis Cluster中槽和集群中对应节点的关系
2.客户端向Redis Cluster的任意节点发送命令,接收命令的节点会根据CRC16规则进行hash运算与
16384取余,计算自己的槽和对应节点
3.如果保存数据的槽被分配给当前节点,则去槽中执行命令,并把命令执行结果返回给客户端
4.如果保存数据的槽不在当前节点的管理范围内,则向客户端返回moved重定向异常
5.客户端接收到节点返回的结果,如果是moved异常,则从moved异常中获取目标节点的信息
6.客户端向目标节点发送命令,获取命令执行结果
二、ask重定向
在对集群进行扩容和缩容时,需要对槽及槽中数据进行迁移
当客户端向某个节点发送命令,节点向客户端返回moved异常,告诉客户端数据对应的槽的节点信息如果此时正在进行集群扩展或者缩空操作,当客户端向正确的节点发送命令时,槽及槽中数据已经被迁移到别的节点了,就会返回ask,这就是ask重定向机制
1.客户端向目标节点发送命令,目标节点中的槽已经迁移支别的节点上了,此时目标节点会返回ask转向给客户端
2.客户端向新的节点发送Asking命令给新的节点,然后再次向新节点发送命令
3.新节点执行命令,把命令执行结果返回给客户端
moved和ask的区别
1、moved:槽已确认转移
2、ask:槽还在转移过程中
迁移:
1、向节点B发送状态变更命令,将B的对应slot 状态置为importing。
2、向节点A发送状态变更命令,将A对应的slot 状态置为migrating。
3、向A 发送migrate 命令,告知A 将要迁移的slot对应的key 迁移到B。
4、当所有key 迁移完成后,cluster setslot 重新设置槽位。
集群容灾
故障检测
集群中的每个节点都会定期地(每秒)向集群中的其他节点发送PING消息
如果在一定时间内(cluster-node-timeout),发送ping的节点A没有收到某节点B的pong回应,则A将B标识为pfail。
A在后续发送ping时,会带上B的pfail信息, 通知给其他节点。
如果B被标记为pfail的个数大于集群主节点个数的一半(N/2 + 1)时,B会被标记为fail,A向整个集群广播,该节点已经下线。
其他节点收到广播,标记B为fail。
从节点选举
raft,每个从节点,都根据自己对master复制数据的offset,来设置一个选举时间,offset越大(复制数据越多)的从节点,选举时间越靠前,优先进行选举。
slave 通过向其他master发送FAILVOER_AUTH_REQUEST 消息发起竞选,master 收到后回复FAILOVER_AUTH_ACK 消息告知是否同意。
slave 发送FAILOVER_AUTH_REQUEST 前会将currentEpoch 自增,并将最新的Epoch 带入到FAILOVER_AUTH_REQUEST 消息中,如果自己未投过票,则回复同意,否则回复拒绝。
所有的Master开始slave选举投票,给要进行选举的slave进行投票,如果大部分master node(N/2 +1)都投票给了某个从节点,那么选举通过,那个从节点可以切换成master。
RedisCluster失效的判定:
1、集群中半数以上的主节点都宕机(无法投票)
2、宕机的主节点的从节点也宕机了(slot槽分配不连续)
变更通知
当slave 收到过半的master 同意时,会成为新的master。此时会以最新的Epoch 通过PONG 消息广播自己成为master,让Cluster 的其他节点尽快的更新拓扑结构(node.conf)。
副本漂移
我们知道在一主一从的情况下,如果主从同时挂了,那整个集群就挂了。为了避免这种情况我们可以做一主多从,但这样成本就增加了。
Redis提供了一种方法叫副本漂移,这种方法既能提高集群的可靠性又不用增加太多的从机。
Master1宕机,则Slaver11提升为新的Master1集群检测到新的Master1是单点的(无从机)
集群从拥有最多的从机的节点组(Master3)中,选择节点名称字母顺序最小的从机(Slaver31)漂移到单点的主从节点组(Master1)。
具体流程如下(以上图为例):
1、将Slaver31的从机记录从Master3中删除
2、将Slaver31的的主机改为Master1
3、在Master1中添加Slaver31为从节点
4、将Slaver31的复制源改为Master1
5、通过ping包将信息同步到集群的其他节点