一、Redis持久化方案
1、Rdb方式
save 900 1
save 300 10
save 60 10000
save <指定时间间隔> <执行指定次数更新操作>,满足条件就将内存中的数据同步到硬盘中。官方出厂配置默认是 900秒内有1个更改,300秒内有10个更改以及60秒内有10000个更改,则将内存中的数据快照写入磁盘。
在redis.conf中可以指定持久化文件存储的目录:

RDB 的优缺点
优点:
1 适合大规模的数据恢复。
2 如果业务对数据完整性和一致性要求不高,RDB是很好的选择。
缺点:
1、数据的完整性和一致性不高,因为RDB可能在最后一次备份时宕机了。
2、备份时占用内存,因为Redis 在备份时会独立创建一个子进程,将数据写入到一个临时文件(此时内存中的数据是原来的两倍哦),最后再将临时文件替换之前的备份文件。所以Redis 的持久化和数据的恢复要选择在夜深人静的时候执行是比较合理的。
2、Aof方式
Redis默认是不使用该方式持久化的。Aof方式的持久化,是操作一次redis数据库,则将操作的记录存储到aof持久化文件中。
将redis.conf中的appendonly改为yes,即开启aof方式的持久化方案。

Aof文件存储的目录和rdb方式的一样。
Aof文件存储的名称

AOF 的优缺点
优点:数据的完整性和一致性更高
缺点:因为AOF记录的内容多,文件会越来越大,数据恢复也会越来越慢。
在使用aof和rdb方式时,如果redis重启,则数据从aof文件加载。
二、Redis主从复制
1、概述
持久化保证了即使redis服务重启也不会丢失数据,因为redis服务重启后会将硬盘上持久化的数据恢复到内存中,但是当redis服务器的硬盘损坏了可能会导致数据丢失,如果通过redis的主从复制机制就可以避免这种单点故障,如下图:

说明:
- 主redis中的数据有两个副本(replication)即从redis1和从redis2,即使一台redis服务器宕机其它两台redis服务也可以继续提供服务。
- 主redis中的数据和从redis上的数据保持实时同步,当主redis写入数据时通过主从复制机制会复制到两个从redis服务上。
- 只有一个主redis,可以有多个从redis。
- 主从复制不会阻塞master,在同步数据时,master 可以继续处理client 请求
- 一个redis可以即是主又是从,如下图:

2、配置
第一步:复制出一个从机
cp bin/ bin2 –r
第二步:修改从机的redis.conf
语法:Slaveof masterip masterport
slaveof 192.168.242.137 6379
第三步:修改从机的port地址为6380
在redis.conf中修改

第四步:清除从机中的持久化文件
rm -rf appendonly.aof dump.rdb
第五步:启动从机
./redis-server redis.conf
第六步:启动6380的客户端
./redis-cli -p 6380
3、优缺点
优点:
- 对于读占比较高的场景,可以通过把一部分读流量分摊到从节点(slave)来减轻主节点(master)压力,同时需要注意永远只对主节点执行写操作;
- 作为主节点的一个备份,一旦主节点出了故障不可达的情况,从节点可以作为后备“顶”上来,并且保证数据尽量不丢失。
缺点:
- 一旦主节点故障,顶上来的从节点可读不可写,需要手动将一个从节点晋升为主节点,同时需要修改应用方的主节点地址,还需要命令其他从节点去复制新的主节点,整个过程都需要人工干预;
- 主节点的写能力受到单机的限制;
- 主节点的存储能力受到单机的限制。
三、哨兵
哨兵机制的出现是为了解决主从复制的缺点的,即当主节点出现故障时,由Redis Sentinel自动完成故障发现和转移,并通知应用方,实现高可用性。
1、哨兵运行流程
1、多个哨兵节点监视一个主redis节点

2、主节点故障,哨兵发现主节点不可达


3、哨兵节点选举出一个领导者,对故障进行转移

4、故障转移的四个步骤

5、故障转移以后的拓扑图

2、哨兵配置方式
https://www.cnblogs.com/leeSmall/p/8398401.html
四、Redis集群
1、概述

- 所有的redis节点彼此互联(PING-PONG机制),内部使用二进制协议优化传输速度和带宽.
- 节点的fail是通过集群中超过半数的节点检测失效时才生效.
- 客户端与redis节点直连,不需要中间proxy层.客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可
- redis-cluster把所有的物理节点映射到[0-16383]slot上,cluster 负责维护node<->slot<->value
Redis 集群中内置了 16384 个哈希槽,当需要在 Redis 集群中放置一个 key-value 时,redis 先对 key 使用 crc16 算法算出一个结果,然后把结果对 16384 求余数,这样每个 key 都会对应一个编号在 0-16383 之间的哈希槽,redis 会根据节点数量大致均等的将哈希槽映射到不同的节点
示例如下:

2、redis-cluster投票:容错

- 集群中所有master参与投票,如果半数以上master节点与其中一个master节点通信超时(cluster-node-timeout),认为该master节点挂掉.
- 什么时候整个集群不可用?
- 如果集群任意master挂掉,且当前master没有slave,则集群进入fail状态。也可以理解成集群的[0-16383]slot映射不完全时进入fail状态。
- 如果集群超过半数以上master挂掉,无论是否有slave,集群进入fail状态。
五、分布式锁
锁的获取:利用 SETNX key value 这一原子性操作:只在键 key 不存在的情况下, 将键 key 的值设置为 value ;若键 key 已经存在, 则 SETNX 命令不做任何动作。SETNX 是『SET if Not eXists』(如果不存在,则 SET)的简写。
/**
* 加锁
* @param lockName 锁的key
* @param acquire_timeout 锁获取超时时间
* @param timeout 锁过期时间(毫秒)
* @return 锁标识
*/
public String lockWithTimeout(String lockName, long acquire_timeout, long timeout) {
Jedis jedis = null;
String retIdentifier = null;
try {
// 获取连接
jedis=pool.getResource();
// 随机生成一个value
String identifier = UUID.randomUUID().toString();
// 锁名,即key值
String lockKey = "lock:" + lockName;
int lockExpire = (int) (timeout / 1000);
long end = System.currentTimeMillis() + acquire_timeout;
while (System.currentTimeMillis() < end) {
//setnx不存在才set。set成功时获取锁,失败时锁被占用
if (jedis.setnx(lockKey, identifier) == 1) {
//获得了锁
jedis.expire(lockKey, lockExpire);
// 返回value值,用于释放锁时间确认
retIdentifier = identifier;
return retIdentifier;
}
//如果没有设置超时时间,则设置
if (jedis.ttl(lockKey) == -1) {
jedis.expire(lockKey, lockExpire);
}
//没有获取到锁,一秒后重试
try{
Thread.sleep(100);
}catch (InterruptedException e){
e.printStackTrace();
Thread.currentThread().interrupt();
}
}
} catch (JedisException e) {
e.printStackTrace();
} finally {
if (jedis != null) {
jedis.close();
}
}
//没有获得锁
return retIdentifier;
}
锁的释放:为了防止删除锁的key时,key自动过期,且又有新锁添加,而导致误删,需要利用看门狗(watch)实现CAS锁。
/**
* 释放锁
* @param lockName 锁的key
* @param identifier 释放锁的标识
* @return
*/
public boolean releaseLock(String lockName, String identifier) {
Jedis jedis = null;
String lockKey = "lock:" + lockName;
boolean retFlag = false;
try {
jedis = pool.getResource();
while (true) {
/*
watch相当于CAS锁,若期间lockKey对应的value发生改变,会阻止下一次事务的执行,
所以会和multi配合使用。
*/
//在这之前锁过期,会因为equals失败而防止删掉别的锁
jedis.watch(lockKey);
// 通过前面返回的value值判断是不是该锁,若是该锁,则删除,释放锁
if (identifier.equals(jedis.get(lockKey))) {
Transaction transaction = jedis.multi();
transaction.del(lockKey);
//在这之前锁过期,会因为exec失败而防止删掉别的锁
List<Object> results = transaction.exec();
if (results != null) {
retFlag = true;
}
//释放失败
// continue;
}
jedis.unwatch();
break;
}
} catch (JedisException e) {
e.printStackTrace();
} finally {
if (jedis != null) {
jedis.close();
}
}
return retFlag;
}
五、Pipeline
Redis客户端执行一条命令分为如下四个过程:
- 发送命令
- 命令排队
- 命令执行
- 返回结果
其中1)+4)称为Round Trip Time(RTT,往返时间)。
Pipeline(流水线)机制能将一组Redis命令进行组装,通过一次RTT传输给Redis,再将这组Redis命令的执行结果按顺序返回给客户端,下图为没有使用Pipeline执行了n条命令,整个过程需要n次RTT:

下图为使用Pipeline执行了n次命令,整个过程需要1次RTT。
六、Bigkey
bigkey是指key对应的value所占的内存空间比较大:
- 字符串类型:体现在单个value值很大,一般认为超过10KB就是bigkey,但这个值和具体的OPS相关。
- 非字符串类型:哈希、列表、集合、有序集合,体现在元素个数过多。
bigkey无论是空间复杂度和时间复杂度都不太友好,主要有以下三个方面:
- 内存空间不均匀(平衡):例如在Redis Cluster中,bigkey会造成节点的内存空间使用不均匀。
- 超时阻塞:由于Redis单线程的特性,操作bigkey比较耗时,也就意味着阻塞Redis可能性增大。
- 网络拥塞:每次获取bigkey产生的网络流量较大,假设一个bigkey为1MB,每秒访问量为1000,那么每秒产生1000MB的流量,对于普通的千兆网卡(按照字节算是128MB/s)的服务器来说简直是灭顶之灾,而且一般服务器会采用单机多实例的方式来部署,也就是说一个bigkey可能会对其他实例造成影响,其后果不堪设想。
如何发现
redis-cli--bigkeys:可以命令统计bigkey的分布;
debug object:判断一个key是否为bigkey,只需要执行debug object key查看serializedlength属性即可,它表示key对应的value序列化之后的字节数;
scan+debug object:如果怀疑存在bigkey,可以使用scan命令渐进的扫描出所有的key,分别计算每个key的serializedlength,找到对应bigkey进行相应的处理和报警
- 如果键值个数比较多,scan+debug object会比较慢,可以利用Pipeline机制完成。
- 对于元素个数较多的数据结构,debug object执行速度比较慢,存在阻塞Redis的可能。
- 如果有从节点,可以考虑在从节点上执行。
如何删除
用del删除bigkey通常来说会阻塞Redis服务。
string:对于string类型使用del命令一般不会产生阻塞;
hash、list、set、sorted set
以hash为例子,使用hscan命令,每次获取部分(例如100个)fieldvalue,再利用hdel删除每个field(为了快速可以使用Pipeline)
本文围绕Redis展开,介绍了持久化方案,包括Rdb和Aof方式及其优缺点;阐述主从复制机制、配置与优缺点;讲解哨兵机制运行流程与配置;介绍Redis集群的原理与容错;还提及分布式锁、Pipeline机制,以及bigkey的发现与删除方法。

968

被折叠的 条评论
为什么被折叠?



