Redis协议与异步方式

文章目录

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,如果发送不完全或者失败,注册读事件

https://github.com/0voice

相关推荐
Mike117.1 小时前
GBase 8c 做性能优化时,我更先看统计信息、执行计划和资源池,而不是一上来就改 SQL
数据库·sql·性能优化
不剪发的Tony老师1 小时前
rsql:一款功能强大的SQL命令行工具
数据库·sql
2401_823943202 小时前
如何从Python初学者进阶为专家?
jvm·数据库·python
l1t2 小时前
DeepSeek辅助测试不同文件格式的读写性能和大小
数据库·人工智能·python
2301_818419012 小时前
用Python和Twilio构建短信通知系统
jvm·数据库·python
小年糕是糕手2 小时前
【35天从0开始备战蓝桥杯 -- Day6】
开发语言·前端·网络·数据库·c++·蓝桥杯
2401_873204652 小时前
使用Docker容器化你的Python应用
jvm·数据库·python
gis开发2 小时前
pg2b3dm 生成建筑物3dtiles
数据库
烟花巷子2 小时前
使用Python进行网络设备自动配置
jvm·数据库·python