10.主从复制

什么是主从复制

持久化保证了即使redis 服务重启也会丢失数据,因为 redis 服务重启后会将硬盘上持久化的数据恢复到内存中,但是当 redis服务器的硬盘损坏了可能会导致数据丢失,如果通过 redis 的主从复制机制就可以避免这种单点故障

主从复制的作用

  • 数据副本
  • 扩展读性能

主从配置注意事项

  • 一个master可以有多个slave
  • 一个slave只能有一个master
  • 数据流是单向的,master到slave

10.1 主从复制配置

主从配置有两种实现方式

  • slaveof命令
  • 文件配置

注:info replication查看主从分片信息

slaveof命令

取消复制

文件配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
################################# REPLICATION #################################

# Master-Slave replication. Use slaveof to make a Redis instance a copy of
# another Redis server. A few things to understand ASAP about Redis replication.
#
# 1) Redis replication is asynchronous, but you can configure a master to
# stop accepting writes if it appears to be not connected with at least
# a given number of slaves.
# 2) Redis slaves are able to perform a partial resynchronization with the
# master if the replication link is lost for a relatively small amount of
# time. You may want to configure the replication backlog size (see the next
# sections of this file) with a sensible value depending on your needs.
# 3) Replication is automatic and does not need user intervention. After a
# network partition slaves automatically try to reconnect to masters
# and resynchronize with them.
#
# slaveof <masterip> <masterport>
# Since Redis 2.6 by default slaves are read-only.
#
# Note: read only slaves are not designed to be exposed to untrusted clients
# on the internet. It's just a protection layer against misuse of the instance.
# Still a read only slave exports by default all the administrative commands
# such as CONFIG, DEBUG, and so forth. To a limited extent you can improve
# security of read only slaves using 'rename-command' to shadow all the
# administrative / dangerous commands.
slave-read-only yes

slaveof命令 vs 文件配置

方式 命令 配置
优点 无需重启 统一配置
缺点 不便于管理 需要重启

10.2 全量复制与增量复制

How Redis replication works

1
2
3
4
Every Redis master has a replication ID: it is a large pseudo random string that marks a given story of the dataset. Each master also takes an offset that increments for every byte of replication stream that it is produced to be sent to slaves, in order to update the state of the slaves with the new changes modifying the dataset. The replication offset is incremented even if no slave is actually connected, so basically every given pair of:
Replication ID, offset
Identifies an exact version of the dataset of a master.
When slaves connects to masters, they use the PSYNC command in order to send their old master replication ID and the offsets they processed so far. This way the master can send just the incremental part needed. However if there is not enough backlog in the master buffers, or if the slave is referring to an history (replication ID) which is no longer known, than a full resynchronization happens: in this case the slave will get a full copy of the dataset, from scratch.

翻译

1
2
3
4
每一个Redis master都有一个 replication ID :这是一个较大的伪随机字符串,标记了一个给定的数据集。每个 master也持有一个偏移量,master将自己产生的复制流发送给slave时,发送多少个字节的数据,自身的偏移量就会增加多少,目的是当有新的操作修改自己的数据集时,它可以以此更新slave的状态。复制偏移量即使在没有一个slave连接到master时,也会自增,所以基本上每一对给定的
Replication ID, offset
都会标识一个master数据集的确切版本
当slave连接到master时,它们使用PSYNC命令来发送它们记录的旧的master replication ID和它们至今为止处理的偏移量。通过这种方式, master能够仅发送slave所需的增量部分。但是如果master的缓冲区中没有足够的命令积压缓冲记录,或者如果slave引用了不再知道的历史记录(replication ID),则会转而进行一个全量重同步:在这种情况下,slave会得到一个完整的数据集副本,从头开始

全量复制

1
The master starts a background saving process in order to produce an RDB file. At the same time it starts to buffer all new write commands received from the clients. When the background saving is complete, the master transfers the database file to the slave, which saves it on disk, and then loads it into memory. The master will then send all buffered commands to the slave.

翻译

1
master开启一个后台保存进程,以便于生产一个RDB文件。同时它开始缓冲所有从客户端接收到的新的写入命令。当后台保存完成时, master将数据集文件传输给slave, slave将之保存在磁盘上,然后加载文件到内存。再然后master 会发送所有缓冲的命令发给slave

全量复制的开销

  • bgsave时间
  • RDB文件网络传输时间
  • 从节点清空数据时间
  • 从节点加载数据时间
  • 可能的AOF重写时间

如何避免全量复制

  • master节点避免关闭持久化功能
  • redis sentinel

11.redis sentinel

redis sentinel(哨兵模式),是一种特殊的模式。用于监控redis集群中Master主服务器工作的状态,在Master主服务器发生故障的时候,可以实现Master和Slave服务器的切换,保证系统的高可用

当一个实例在故障转移后被提升为 master 时,它仍然能够与旧 master 的 slaves 进行部分重同步

redis sentinel故障转移

  1. 多个sentinel发现并确认master出现故障
  2. 选举出一个sentinel执行failover操作
  3. 选出一个slave作为master
  4. 通知其余的slave成为新master的slave
  5. 通过客户端主从变化
  6. 等待老的master复活成为新的master的slave

11.1 redis sentinel安装配置

  1. 配置主从节点
  2. 配置开启sentinel监控主节点
节点 主机 端口
redis-master 192.168.0.100 7000
redis-slave1 192.168.0.101 7001
redis-slave2 192.168.0.102 7002
redis-sentinel1 192.168.0.100 26379
redis-sentinel2 192.168.0.101 26380
redis-sentinel3 192.168.0.102 26381

redis-7000.conf

1
2
3
4
5
port 7000
daemonize yes
pidfile /var/run/redis-7000.pid
logfile "redis-7000.log"
dir /opt/modules/redis-4.0.14/data

redis-sentinel.conf

1
2
3
4
5
6
7
8
port 26379
daemonize yes
bind 192.168.0.100 127.0.0.1
dir "/opt/modules/redis-4.0.14/data"
logfile "redis-sentinel.log"
sentinel monitor mymaster 192.169.0.100 6379 2
sentinel down-after-milliseconds mymaster 30000
sentinel failover-timeout mymaster 180000

访问sentinel

1
redis-cli -h 192.168.0.100 -p 26379

11.2 java客户端

添加依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.0.1</version>
<type>jar</type>
<scope>compile</scope>
</dependency>

<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.26</version>
</dependency>

<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.25</version>
</dependency>

<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>

<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>

代码演示说明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public class JedisSentinelTest {

private static Logger logger = LoggerFactory.getLogger(JedisSentinelTest.class);

public static void main(String[] args) {
String masterName = "mymaster";
Set<String> sentinels = new HashSet<String>();
sentinels.add("192.168.0.110:26379");
sentinels.add("192.168.0.105:26380");
sentinels.add("192.168.0.108:26381");

JedisSentinelPool sentinelPool = new JedisSentinelPool(masterName, sentinels);

int index = 0;
while(true) {
Jedis jedis = null;
try {
jedis = sentinelPool.getResource();
String key = "k-" + index;
String value = "v-" + index;
//set key
jedis.set(key, value);
logger.info("key: " + key + " value: " + value);
index++;
}catch (Exception e) {
logger.error(e.getMessage());
}finally {
if(jedis != null) {
jedis.close();
}

}
}
}
}

12.redis cluster

怎么才能够突破单机瓶颈,让redis支撑海量数据

支撑N个redis master node,每个master node都可以挂载多个slave node

多master + 读写分离 + 高可用

对比replication+sentinel

replication+sentinal:数据量很少,主要承载高并发场景,提高吞吐量,保证redis的高可用性

reids cluster:海量数据+高并发+高可用

13.缓存设计与优化

13.1 缓存的受益与成本

  • 受益
    1. 加速读写
    2. 减低后端负载
  • 成本
    1. 数据不一致
    2. 增加缓存逻辑维护
  • 使用场景
    • 缓存高消耗SQL:join结果集、分组统计等
    • 加速请求响应:优化I/O响应时间
    • 计数器优化:redis累加,再同步到DB

13.2 缓存更新策略

  1. LRU/LFU/FIFO算法剔除
  2. 超时剔除
  3. 主动更新
策略 一致性 维护成本
LRU/LFU/FIFO算法 最差
超时剔除 较差
主动更新

建议

  • 对于低一致性数据,使用LRU/LFU/FIFO算法和超时剔除
  • 对于高一致性数据,使用主动更新,LRU/LFU/FIFO算法和超时兜底

13.3 缓存粒度控制

  • 通用性:全量属性最好
  • 占用空间:部分属性最好
  • 维护成本:表面上全量属性更好

13.4 缓存穿透

缓存穿透,是指查询一个数据库一定不存在的数据。正常的使用缓存流程大致是,数据查询先进行缓存查询,如果key不存在或者key已经过期,再对数据库进行查询,并把查询到的对象,放进缓存。如果数据库查询对象为空,则不放进缓存

产生原因

  • 业务代码逻辑问题
  • 爬虫或恶意攻击

如何发现

  • 业务执行时间
  • 相关指标:缓存命中数、存储层命中数

解决方案

  1. 缓存空对象

13.4 缓存击穿

缓存击穿,是指一个key非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像在一个屏障上凿开了一个洞

解决方案

  1. 互斥锁

    首个发现缓存过期的线程,对当前key获取进行加锁,在同步数据到redis中后,释放锁

  2. 永不过期

    为key添加逻辑过期时间,发现超过逻辑日期时间后,使用单独的线程构建缓存

13.5 缓存雪崩

缓存雪崩,是指在某一个时间段,缓存集中过期失效

产生雪崩的原因之一,比如在写本文的时候,马上就要到双十二零点,很快就会迎来一波抢购,这波商品时间比较集中的放入了缓存,假设缓存一个小时。那么到了凌晨一点钟的时候,这批商品的缓存就都过期了。而对这批商品的访问查询,都落到了数据库上,对于数据库而言,就会产生周期性的压力波峰

其实集中过期,倒不是非常致命,比较致命的缓存雪崩,是缓存服务器某个节点宕机或断网

解决方案

根据业务的不同,为不同的key设置不同的过期时间

14.redis开发规范

14.1 key设计

  • 可读性

    以业务名为前缀(防止key冲突),用冒号分割,比如业务名:表名:id,如p2p:user:id

  • 简洁性

    在保证语义的情况下,控制key的长度,当key较多时,内存占用也不容忽视,如

    user:{uid}:friends:messages:{mid}简化为u:{uid}:f:ms:{mid}

  • 不包含特殊字符

    不包含空格、换行、单双引号及其他转义符

key的长度通常控制在39个字节以内(redis3.x)

k-v个数 39字节 40字节
10万 15.69M 18.75M
100万 146.29M 176.81M
1000万 1.47G 1.77G
1亿 14.6G 17.7G

14.2 value设计

设计建议

  • 拒绝bigKey
  • 选择合适的数据结构
  • 过期设计

拒绝bigKey

不成文的规定

  • string类型控制在10kb以内
  • hash、list、set、zset元素个数不超过5000

bigKey的影响

  • 网络阻塞
  • redis阻塞
  • 集群节点数据倾斜
  • 频繁序列化,消耗cpu资源

发现bigKey

  • 应用异常

  • redis-cli –bigkeys

  • debug object

  • 流量监控报警

删除bigKey

  • 隐性删除

    设置key过期

  • lazy delete(redis 4.x)

14.3 命名优化

  1. hgetall、lrange、smembers、zrange、sinter在有遍历需求下,可使用hscan、sscan、zscan代替
  2. 禁用keys、flushall、flushdb
  3. 不推荐使用redis事务

15.内存优化

15.1 内存管理

内存使用统计

使用info memory可以查看redis 的内存使用情况

属性名 属性说明
used_memory redis实际存储数据的内存总量
used_memory_human 以可读格式返回redis使用的内存总量
used_memory_rss 从操作系统角度,redis进程占用的总物理内存
used_memory_peak 分配的最大内存,used_memory属性的历史峰值
used_memory_peak_human 以可读格式显示内存消耗峰值
used_memory_lua Lua引擎消耗的内存
mem_fragmentation_ratio used_memory_rss/used_memory比值,表示内存碎片率
mem_allocator redis所使用的内存分配器,默认:jemalloc