文章目录
redis pipeline
是客户端提供的一种机制,而不是服务端提供的
注意:pipeline不具备事务性
目的:节约网络传输时间
通过以此发送多次请求命令,从而减少网络传输等待时间。

redis事务
事务:用户定义一系列数据库操作,这些操作视为一个完整的逻辑处理工作单元,要么全部执行,要么全部不执行,是不可分割的工作单元。
MULTI # 开启事务
EXEC # 调用exec才开始一起执行
DISCARD # 取消事务
WATCH #检测key的变动,若在事务执行中,变动则取消事务,在事务开启前
#调用,乐观锁实现。
watch的过程中假如有其他连接操作了key,那么会直接返回nil。(乐观锁)
用乐观锁实现,所以失败需要重试,增加业务逻辑的复杂度。
比如需要对数据库中值为100的key,进行加倍操作。

在多条并发连接下,需要探讨事务。
但是在实际的开发中不会使用MULTI EXEC
lua脚本

redis为了实现多语句事务,内嵌了lua虚拟机
redis的业务逻辑就是处理命令,单线程逻辑,命令是一个一个处理的。
lua虚拟机执行lua代码,可以执行多个命令,能确保把lua代码作为一个整体执行。
在lua虚拟机中执行的redis指令会被视为一个整体执行。

EVALSHA
执行一次脚本,其中有多条redis指令,那么就意味着多次数据包的往返 ,可不可以把lua脚本发送到reids-server,让其进行编译,redis返回给cli一个标识这个脚本的唯一ID,每次cli想执行这个操作,告诉server这个ID即可。
这个ID是由脚本的字符生成的SHA1 哈希值,它的特点如下 :
①固定长度: 无论输入的数据有多大,SHA1 计算出来的结果(哈希值或摘要)都是一个长度固定的 160 位(bit) 的二进制数。为了方便表示和存储,通常会将这个二进制数转换成 40 位的十六进制字符串
②唯一性(理论上): 理论上,每一个不同的输入都应当产生一个独一无二的 SHA1 值。这个特性使得 SHA1 值常被用来校验数据的完整性。
③确定性:同一个输入数据,无论何时何地用同一个 SHA1 算法去计算,结果都完全一样。
④雪崩效应: 输入数据哪怕只改变一个微小的字符(比如把 Hello 改成 Helo),其 SHA1 值也会发生巨大的、看似随机的变化。

在server中,启动后,一般先flushall清空,然后再加载脚本缓存。
redis默认是开启了持久化的
script load lua1 → hx1
unordered_map<ID,hx> #ID 用于redis标识lua脚本
evalsha hx
script flush # 清除所有脚本缓存
script kill # 当前脚本运行时间过长,可以直接kill杀死运行脚本
ACID特性分析
分析事务,就是要分析事务的ACID特性
A原子性:
事务是一个不可分割的工作单位,事务中的操作要么全部成功,要么全部失败,要能回滚事务中的操作 ;redis不支持回滚;即使事务队列中的某个命令在执行期间出现了错误,整个事务也会继续执行下去,直到将事务队列中的所有命令都执行完毕为止。
原子操作的原子性:其他核心要么看到还没开始执行,要么看到已经全部执行,不可能看到中间的结果。
C一致性:
事务的前后,所有的数据都保持一个一致的状态,不能违反数据的一致性检测;这里的一致性是指预期的一致性而不是异常后的一致性;所以redis也不满足;这个争议很大:redis能确保事务执行前后的数据的完整约束;但是并不满足业务功能上的致性;比如转账功能,一个扣钱一个加钱;可能出现扣钱执行错误,加钱执行正确,那么最终还是会加钱成功;系统凭空多了钱;
一致性有两种:逻辑一致性 (可能被破坏)和 数据库一致性(由数据库保证)
lua脚本具备原子性和隔离性,但是不具备一致性和持久性。
I隔离性:
各个事务之间互相影响的程度;redis是单线程执行,天然具备隔离性;
D持久性:
redis 只有在 aof 持久化策略的时候,并且需要在 redis.conf 中 appendfsync=always 才具备持久性;实际项目中几乎不会使用aof持久化策略;
redis驱动的实现
①同步连接 阻塞 io 默认的驱动
c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <hiredis/hiredis.h>
int main() {
unsigned int j, isunix = 0;
redisContext *c;
redisReply *reply;
const char *hostname = "127.0.0.1";
int port = 6379;
struct timeval timeout = { 1, 500000 }; // 1.5 seconds
c = redisConnectWithTimeout(hostname, port, timeout);
if (c == NULL || c->err) {
if (c) {
printf("Connection error: %s\n", c->errstr);
redisFree(c);
} else {
printf("Connection error: can't allocate redis context\n");
}
exit(1);
}
int roleid = 10001;
reply = redisCommand(c, "hgetall role:%d", roleid);
if (reply->type != REDIS_REPLY_ARRAY) {
printf("reply error: %s\n", reply->str);
} else {
printf("reply:number of elements=%lu\n", reply->elements);
for (size_t i = 0; i < reply->elements; i++) {
printf("\t %lu : %s\n", i, reply->element[i]->str);
}
}
freeReplyObject(reply);
/* Disconnects and frees the context */
redisFree(c);
return 0;
}
gcc redis-sync.c -o sync -lhiredis
②异步连接 基于当前的网络模块
hiredis提供了适配reactor 网络模型的异步驱动方式
适配事件对象
适配事件函数
过程详情见1:41:59
hiredis帮助完成了协议解析 读写事件封装 缓冲区操作 协议加密

回顾一下reactor怎么处理建立连接事件的流程

cli上:① socket设置非阻塞
② connect(非阻塞socket立即返回)
③ 给socket注册写事件(epoll)
④ 写事件触发,建立成功
⑤ 注销写事件,注册读事件(如果不注销,会一直检测发送缓冲区触发)
⑥发送命令到redis,如果发送不完全或者失败,注册读事件