redis学习总结

@TOC

redis数据结构原理

待整理~

redis持久化

RDB持久化

  1. 执行流程
  • 父进程执行fork操作创建子进程,这个过程中父进程是阻塞的,Redis不能执行来自客户端的任何命令。
  • 子进程创建RDB文件,根据父进程内存快照生成临时快照文件,完成后对原有文件进行原子替换,替换后子进程信号通知主进程
  1. rdb自动持久化配置:
    时间策略要按照实际情况配置多条,数据的存储时不均匀的,高峰期短时间间隔要存一次,低峰期长时间间隔要存一次。
    # 文件名称
    dbfilename dump.rdb
    

时间策略

save 900 1
save 300 10
save 60 10000

文件保存路径

dir /etc/redis/data/

如果持久化出错,主进程是否停止写入

stop-writes-on-bgsave-error yes

是否压缩

rdbcompression yes

导入时是否检查

rdbchecksum yes

3. rdb策略将内存中的数据生成快照保存到磁盘,是全量存储,内存大的话会比较耗时,大量磁盘io。
4. 持久化的触发方式:
- save命令:client向server发送save命令,同步阻塞。
- bgsave命令:server中执行命令,异步子进程执行。
- 自动持久化,通过save配置项完成。
5. rdb文件恢复:如果开启了aof,则不生效。如果没开启aof,则寻找dir配置下的rdb文件。
### AOF持久化
1. aof同步步骤:
- Redis收到写命令后首先会追加到AOF缓冲区aof_buf(write)
- 通过一定时机(配置决定),调用系统函数 fsync()AOF缓冲区的数据刷到磁盘
2. 落盘时机配置(fsync):
always: 命令写入aof缓冲区后立即调用系统fsync操作同步到AOF文件,数据绝对安全。
no:有操作系统决定何时调用fsync()
everysec:每秒调fsync()一次。 若损失数据只损失1秒,对于大多数系统来说足够了。
3. 文件重写rewrite
- 由父进程fork子进程进行重写
- 使用写时重写技术,在重写完成期间,Redis的写命令同时追加到aof_buf和aof_rewirte_buf两个缓冲区。
- 重写文件完成后,将aof_rewirte_buf数据输入新文件
- 用新aof文件替换老aof文件。

## redis集群三种模式
### 主从模式(实现主从分离,提高吞吐,多机备份)
从数据库一般都是只读的,并且接收主数据库同步过来的数据
要点:
1. 主从复制还是哨兵和集群能够实施的基础,主从复制是Redis高可用的基础。
2. 再看CAP,由于redis复制是异步复制,会导致短时的数据不一致,所以无法满足一致性C,但可以保证当网络分区发生时,各个节点依旧可用。redis主从模式是CP,而不是AP。redis会使用最终一致性策略,保证主从同步数据一致。
3. 主从复制实现原理:
- 6大步骤: 1. 设置主节点ip及端口、2. 建立套接字socket、 3. 发送ping命令、 4. 权限验证 、 5. 同步 6. 命令传播
- 设置主节点ip及端口: 使用slaveof命令,可配置文件使用、可命令行使用
- 复制阶段:从节点向主节点发送psync或sync命令(2.8版本之前),可以实现全量复制与增量复制
- 命令传播:当通过复制阶段后,主从节点进入命令传播阶段,主节点将自己执行的写命令发送给从节点,从节点接收命令并执行,从而保证主从节点数据的一致性。命令传播是异步的,主节点并不会等从节点同步执行完命令,这样会导致“延迟不一致”现象。
4. 延迟不一致现象:网络波动、写命令频率过高,会导致数据延迟不一致。同时,repl-disable-tcp-nodelay配置也会影响,设置为yes,会将代发数据合并发送,延迟大概40ms(取决于系统),如果设置为no,写命令实时发送同步。
5. 全量复制和部分复制
- 全量复制:用于初次复制或其他无法进行部分复制的情况,将主节点中的所有数据都发送给从节点,是一个非常重型的操作
- 部分复制:用于网络中断等情况后的复制,只同步网络断开期间的缓存写数据,如果网络中断时间过长,导致主节点没有能够完整地保存中断期间执行的写命令,则无法进行部分复制,仍使用全量复制。
6. 全量复制原理剖析:
fork子进程,开始执行bgsave,生成RDB文件,同时开辟缓冲区记录从现在开始的写命令。2. 将rdb文件传输给从服务器,从服务器执行完毕后,主节点将缓冲区写数据同步到从节点,保证最终一致性。3. 如果从节点开启了AOF,会触发bgrewriteof,从而保证从节点的aof文件最新。
7. 部分复制原理剖析:
- 复制偏移量:与mysql类似,主从节点各自维护offset字段,不一致则复制
- 复制积压缓冲区:底层结构是定长、先进先出FIFO的队列,可以repl-backlog-size参数设置大小。 当主从节点offset的差距过大超过缓冲区长度时,将无法执行部分复制,只能执行全量复制。
- 服务器运行id(runid):每个从节点都存储着主节点同步下来的runid,当网络分区发生时,重连后slave节点会判断同步的runid是否存在,如果存在优先考虑增量复制,如果不存在,则全量复制。
8. psync命令复制原理:主节点收到psync命令后,进行判断,如果命令为psync命令,则执行全量复制。如果收到的为psync {runid} {offset}命令,则执行增量复制。执行增量复制过程中如果offset差超过了buffer,则执行全量复制。
9. 心跳检测机制及其作用:1. 检测主从链接 2. 辅助实现min-slaves选项 3. 检测命令丢失,保证数据一致
- 通过判断在线的从节点数量,实现min-slaves。
min-slaves概念,保证高可用:
未达到下面两个条件时,写操作就不会被执行
min-slaves-to-write 3 #最少包含的从服务器
min-slaves-max-lag 10 #延迟值
- 心跳会返回offset信息,通过offset判断从节点的数据同步是否及时,不及时则通过offset补发数据
10. 主从复制超时原理:断开连接后会尝试重连。
- 主节点判断超时:每秒1次调用复制定时函数replicationCron(),如果时间大于到上次REPLCONF ACK的时长,则断开连接,释放资源(缓冲器、连接、带宽),超时时间由参数repl-timeout值控制。
- 从节点判断超时:主要也是由repl-timeout参数控制。1. 连接建立阶段,若大于时间则断开连接。 2. 全量复制阶段:如果收到rdb的时间超时,则断开连接。 3. 命令传播阶段:如果收到主节点ping的时间过长,超过timeout,则断开连接。
- 如果rdb文件过大,会导致一直同步失败,会无线重连,应适当调大timeout值
11. 主库挂了发生什么?
不影响slave的读,但redis不再提供写服务,master重启后redis将重新对外提供写服务。
如果slave-serve-stale-data参数设置为yes,主节点挂掉后从节点可以继续提供服务,如果设置为no,主节点挂掉后从节点不再提供读数据服务,仅提供info、slaveof等少量命令。在强一致场景需要考虑设置为no,如分布式锁,如果主节点挂掉,数据没来得及同步从节点,会导致从节点读不到,锁失效。
重启master节点需要保证rdb文件或者aof文件是最新。
12. redis如何保证主从服务器连接正常且数据最终一致?
命令传播阶段,从服务器会利用心跳检测机制定时的向主服务发送消息。
13. 如果提高数据实时一致性?
优化主从节点之间的网络环境(如在同机房部署);监控主从节点延迟(通过offset)判断,如果从节点延迟过大,通知应用不再通过该从节点读取数据;
14. java客户端连接redis如何实现读写分离,读负载均衡?
常见的客户端有jredis,lettuce,redission。以lettuce为例,可以使用

```java
StatefulRedisMasterSlaveConnection connection = MasterSlave.connect(redisClient, new Utf8StringCodec(), redisURI);
connection.setReadFrom(ReadFrom.NEAREST);
// MASTER 主读
// SLAVE 从读
// MASTER_PREFERRED 优先master -> slave
// SLAVE_PREFERRED 优先slave -> master
// NEAREST 最近节点读
// 实现很简单,重写ReadFrom的select方法,自带的方法均无法实现负载均衡
static final class ReadFromSlavePreferred extends ReadFrom {

        @Override
        public List<RedisNodeDescription> select(Nodes nodes) {

            List<RedisNodeDescription> result = new ArrayList<>(nodes.getNodes().size());
            //优先添加slave节点
            for (RedisNodeDescription node : nodes) {
                if (node.getRole() == RedisInstance.Role.SLAVE) {
                    result.add(node);
                }
            }
            //最后添加master节点
            for (RedisNodeDescription node : nodes) {
                if (node.getRole() == RedisInstance.Role.MASTER) {
                    result.add(node);
                }
            }
            return result;
        }
// 自定义负载均衡
@Bean(destroyMethod = "close")
   StatefulRedisMasterSlaveConnection<String, String> statefulRedisMasterSlaveConnection(RedisClient redisClient, RedisURI redisURI) {
       StatefulRedisMasterSlaveConnection connection = MasterSlave.connect(redisClient, new Utf8StringCodec(), redisURI);
       connection.setReadFrom(new ReadFrom() {
           @Override
           public List<RedisNodeDescription> select(Nodes nodes) {
               List<RedisNodeDescription> list = nodes.getNodes();
               Collections.shuffle(list);
               return list;
           }
       });
       return connection;
   }

哨兵模式(主从高可用,自动化修复)

  1. 高可用架构一般将sentinel与redis实例部署在不同的服务器上。
  2. 工作机制:每个sentinel以每秒钟一次的频率向它所知的master,slave以及其他sentinel实例发送一个 PING 命令 。如果响应超过 down-after-milliseconds,则标记为主观下线。其它sentinel以每秒钟一次的频率ping主观下线的实例,如果足够多的(通过配置指定)sentinel确认该实例的主观下线状态,则置为客观下线。Sentinel 和其他 Sentinel 协商 主节点 的状态,如果 主节点 处于 SDOWN 状态,则投票自动选出新的 主节点。将剩余的 从节点 指向 新的主节点 进行 数据复制
  3. 当使用sentinel模式的时候,客户端就不要直接连接Redis,而是连接sentinel的ip和port,由luttuce自动获取redis集群拓扑,从而获取master节点与slave节点的信息。并通过监听redis事件更新拓扑信息。
  4. 哨兵模式切换主节点导致数据不一致,“脑裂问题”的解决方案:
  • 当网络分区发生时,会导致master下线,实际master没有挂,选举出新的master后会出现两个master,当网络分区修好后,旧master会变为slave同步新master的数据,导致数据丢失。
    配置:
    min-slaves-to-write 1 # 必须至少要有1个slave
    min-slaves-max-lag 10 # 数据复制和同步延迟不能超过10秒
    不符合以上情况,master将拒绝接受请求。将数据丢失控制在10秒。
    从业务上看,及其重要的数据,可以进行处理:
    进redis前可以在其它地方临时存储。
    可以加网关,降低流入速度,防止瞬间进入过多数据。

    集群模式(数据分片,解决了写操作无法负载均衡,单机容量存储、并发问题)

  1. 一致性hash与hash槽
  • 简单hash算法,通过计算hash值 % 机器数量,得到数据的实际对应节点。当增加或删除节点时,会导致大量的缓存失效,甚至导致雪崩。
  • 一致性哈希算法中,整个哈希空间是一个虚拟圆环,共计2^32 - 1 个节点。将每个redis节点的ip地址进行hash计算后落位虚拟节点。每条数据通过计算hash值后顺时针落位对应节点。当某个节点增、删、不可用时,只会影响下一个节点,而不会影响所有节点。
    一致性hash算法的问题是会导致数据倾斜,如果节点数过少,在盘中的分布又不均匀,可能会导致所有数据都落在极少的节点上。
  • 哈希槽,redis包含了16384个哈希槽,每个 key 通过计算后都会落在具体一个槽位上,而这个槽位是属于哪个存储节点的,则由用户自己定义分配。
    对于槽位的转移和分派,redis 集群是不会自动进行的,而是需要人工配置的。所以 redis 集群的高可用是依赖于节点的主从复制与主从间的自动故障转移。
  1. redis-cluster是redis集群的官方实现版,具有投票、容错性等特点。
  • 集群中可以有多个master节点,需要保证所有master节点恰好覆盖所有hash槽,否则集群不能启动。添加、删除节点,只需要更改对应的哈希槽范围。
    node1 : [1,15000]
    node2 : [15001,20000]
  • 何时master节点算挂掉?
    半数以上master节点与master节点通信超时(cluster-node-timeout),认为当前master节点挂掉,如果此时该master节点由slave节点,则集群依旧正常运行,对应挂掉的节点不再提供写入服务。
  • 何时集群挂掉?

1.只要某个槽段失效,集群就会进入不可用状态。主、从都挂。
2.超过半数的master节点挂掉,不管slave是否正常,集群不可用。

  • 一般情况,各个槽段的主、从redis节点可以交叉部署,部署在不同的服务器上。

redis应用场景

分布式锁

  1. 单机版分布式锁:
  • 加锁终极版:set nx px + 事务id
  • 解锁终极版:使用lua脚本实现。
    if redis.call(“get”,KEYS[1]) == ARGV[1] then
    return redis.call(“del”,KEYS[1])
    else
    return 0
    end
  • 为何解锁需要用lua,而加锁不需要?
    加锁过程,set命令的 nx 参数 = set if not exist,get value->判断不存在->set value。set 命令的 px参数,设置过期时间。相当于三条命令保持原子性。
    解锁过程,get获取键值,判断value是否相等,如果此时JVM

redis架构思维

待整理~

本作品采用《CC 协议》,转载必须注明作者和本文链接
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!