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

相关推荐
weixin_40871777几秒前
为什么宝塔面板定时访问URL任务总是报502_检查目标接口响应时间与延长任务执行超时设置
jvm·数据库·python
m0_746752303 分钟前
如何在导航栏中实现左右分列的菜单项布局
jvm·数据库·python
21439654 分钟前
怎么为MongoDB事务调优:将读操作尽量移到事务外面执行
jvm·数据库·python
baidu_3409988210 分钟前
JavaScript中函数式编程中不可变性与闭包的关联
jvm·数据库·python
djjdjdjdjjdj12 分钟前
c++如何利用std--tie实现多个文件属性字段的快速比较排序【详解】
jvm·数据库·python
m0_6784854518 分钟前
CSS如何使用Less的Merge功能合并多个属性值_通过逗号或空格组织css参数
jvm·数据库·python
2401_8971905520 分钟前
团队版Navicat专属功能:如何共享数据库架构ER模型_核心机制解析
jvm·数据库·python
m0_6403093021 分钟前
如何利用 Block Tree 避免不必要的子组件重渲染?Vue3 编译黑科技
jvm·数据库·python
hERS EOUS22 分钟前
Redis 下载与安装 教程 windows版
数据库·windows·redis
LiAo_1996_Y24 分钟前
layui table数据表格分页 layui表格如何开启服务端分页
jvm·数据库·python