一. 数据结构
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形式,只是转换成插入命令;
来源:https://www.cnblogs.com/bbbbs/p/12536235.html