了解Redis

 ̄綄美尐妖づ 提交于 2020-03-21 03:12:56

一. 数据结构

1. value对象的通用结构

typedef struct redisObject {
    type:4; // 结构化类型
    encoding:4; // 这些结构化类型具体的实现方式,同一个类型可以有多种实现
    lru:REDIS_LRU_BITS; // 对象的空转时长,用于有限内存下长久不访问的对象的清理
    int refcount; // 应用技术,用于对象的清理
    void *ptr; // 指向实际承载地址
}

2.常用类型

String:字节串;整数;浮点数;

List:列表对象,用于存储String序列;内部以linkedlist或ziplist来承载;

Map:内部以hashtable或ziplist来承载;map内部的key和value不能再嵌套map,只能是String;

Set:内部以inset或hashtable来承载;一个无序集合,元素不重复;

Sorted-Set:内部以ziplist或skiplist+hashtable来承载;有序的key-value对,key不重复,value为浮点数,按照value排序;

二. 客户端与服务器交互

1. 交互协议分为两部分:网络模型和序列化协议;

2. 网络模型:redis协议位于TCP层之上,即客户端和redis实例保持双工的连接,服务器端为每个客户端建立对应的连接;

3. 序列化协议:

协议数据分为不同的类型,每种类型的数据均以CRLF结束,通过首字符区分,

首字符为redis命令名的字符表示redis命令,

首字符为‘+’表示simple string,

首字符为‘$’表示bulk string,

首字符为‘-’表示error,

首字符为‘:’表示integer,

首字符为‘*’表示array;

4.客户端发送请求,服务器发送响应数据;每一个请求有且仅有一个响应;响应数据的发送发生在服务器完全接收到对应的请求数据之后;

5. pipeline:不等上一次结果返回就发送下一次请求的模式;

6. pipeline模式下,如果存在多个客户端,每个客户端发送的每个命令在服务器看来是等同的,执行顺序可能存在交叉,当我们需要将批量执行的语句原子化时需要redis的事务模式;

三. 事务模式

1. 原子性:

客户端发送给服务器的请求暂存在连接对象对应的请求队列中;

发送完一个批次的所有请求后,服务器一次执行队列中的所有请求;

单个实例的redis仅单线程执行所有请求,所以不会交叉执行多个客户端的请求;

2. redis执行器单线程的一次执行粒度是“命令”,由MULTI开启事务,随后发送的请求暂存在服务器的连接上,最后通过EXEC一次性批量执行,并将所有执行结果作为一个响应,以array类型的协议数据返回给客户端;

3. 一致性:当入队阶段出现语法错误时,不执行后续EXEC,不会对数据实际产生影响,当有一条请求执行失败时,后续请求继续执行,在返回客户端的array响应中标记这条出错的结果,由客户端决定如何修复,redis自身不包括回滚机制,所以严格的讲redis的事务并不是一致的;

4. 事务的只读操作:

只读操作放在批量执行中没有任何意义,既不会改变事务的执行行为,也不会改变redis的数据状态,所以入队请求应该全是写操作;

只读操作需要放在MULTI语句之前执行,否则如果穿插了其他客户端的语句可能会将值改变,造成最终数据不一致;

5. 乐观锁的可串行化事务隔离:将本次事务涉及的所有key注册为观察模式,如果在执行事务期间被修改过,那么EXEC将直接失败;

6. 事务实现:

redisClient中通过两个属性保存事务的状态,

flags:包含多个bit,其中两个bit分别标记了当前连接处于MULTI和EXEC之间,当前连接WATCH之后到现在所观察的key是否被修改过;

mstate:中的count标记总共有多少个待执行命令,中的comands为该连接的请求队列;

7. 事务交互模式

客户端发送四类请求:监听,只读,写请求的批量执行或放弃执行请求,写请求的入队;

交互时序为:开启监听-只读操作-MULTI请求-根据前面只读操作的结果编排/参数赋值/入队写操作-一次性批量执行队列中的写请求;

四. 脚本模式

1. redis允许客户端向服务器提交一个脚本,结构化的编排业务事务中的多个redis操作,脚本还可以获取每次操作的结果作为下次操作的入参;

2. 脚本交互模式:

客户端发送eval lua_script_string 2 key 1 key2 first second给服务器;

服务器根据string本身的内容通过sha1计算sha值,存放到redisServer对象的lua_scripts成员变量中,它为map类型,sha作为key;

服务器端原子化的通过内置lua环境之下string,其可能包含对redis的方法调用;

执行完成后将lua的结果转化成redis的类型返回给客户端;

3. script特性

每一个提交给服务器的lua_script_string都会在lua_script map中常驻,除非显式通过FLUSH清理;

script在实例的主备间可通过script重放和cmd重放两种方式实现复制;

之前执行过的script后续可直接通过它的sha指定而不用重新发送一遍;

五. 发布/订阅模式

1. 一个客户端触发,通过服务器中转,多个客户端被动接受;

2. 角色关系:客户端分为发布者和订阅者两种;发布端和订阅者通过channel关联;

3. 交互方向:发布者和redis服务器的交互仍为请求/响应模式;服务器收到消息后推送数据给订阅者;

4. channel:订阅者通过SUBSCRIBE将自己绑定到某个channel上,发布者的publish命令指定某个消息发送到哪个channel,服务器将消息转发给channel上绑定的订阅者;

5. channel的订阅关系维护在redis实例级别,独立于redisDb的key-value体系;

六. 单机处理逻辑

1. 多路复用

redis服务器对于命令的处理是单线程的,但是IO层面却同时面向多个客户端并发的提供服务,并发到内部单线程的转化通过多路复用框架实现;

七. 持久化

1. 全量模式

基于全量的持久化即在持久化触发的时刻,将当时的状态(所有db的key-value值)完全保存下来,形成一个sanpshot;

当redis重启时通过加载最近一个snapshot数据,可将redis恢复至最近一次持久化的状态上;

写入流程:

SAVE:可以由客户端显式触发,也可在redis shutdown时触发,执行时其他命令不会并发执行,保证了数据的一致;

BGSAVE:可以由客户端显式触发,也可以通过配置定时任务触发,执行时fork出一个子线程,备份的数据是fork时的数据,期间不影响redis的可用性,但由于涉及内存的复制,所以需要保证空闲内存足够,否则会阻塞服务器运行;

2. 增量模式

增量持久化保存的是状态的每一次“变迁”,当初始状态给定,经过相同的变迁序列之后,最终的状态也是确定的;

写入流程:

在主循环中每次处理完写命令后执行,通过propagate函数触发;

随着redis持续的运行,会不断的产生新的数据append到AOF文件中,导致文件占用大量磁盘空间,降低redis启动时的回放加载效率;

通过rewrite机制优化性能:当多次变迁的增量数据积累到大于某个状态快照的程度,将用快照来代替;快照仍然使用cmd形式,只是转换成插入命令;

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!