redis协议与异步方式学习笔记

目录

  • 1 交互方式 pipline
  • 2 广播机制
    • 2.1 概念演示
    • 2.2 使用场景
  • 3 redis事物
    • 3.1 概念
    • 3.2 使用场景
    • 3.3 解决的问题
      • 3.3.1 背景:多线程竞争出现问题
      • 3.3.2 事务
      • 3.3.3 安全性事务
    • 3.4两种类型的“事务”
      • 3.4.1 watch ... multi exec
      • 3.4.2 lua 脚本实现“原子”执行,重点掌握
      • 3.4.3 watch multi exec 与 lua 脚本 的区别
  • 4 redis联通,通过hiredis压缩协议,解析协议,扩展事件处理
    • 4.1 hiredis 安装
    • 4.2 hiredis : 如何引入自己项目中
      • 1 局限性: 一定要使用reactor模型。因为在hiredis中,具体的IO操作是hiredis实现的,
      • 2 项目中要访问redis
        • 2.1 \redis\hiredis\examples example-libevent.c
        • 2.2 \redis\hiredis\adapters libevent.h 适配libevent网络库
        • 2.3 流程备注

1 交互方式 pipline

pipeline  一次性发送多个命令,可以节约时间 (相对于客户端一次发一个命令,然后服务端回复一条命令)

在这里插入图片描述

异步连接 不需要规划, 带一个回调函数
时间窗口限流 ------------>  测试代码 github 地址 找mark 老师要
客户端缓存,然后一起发出(这里不是redis缓存)

2 广播机制

2.1 概念演示

发布者与注册者之间是1:n的关系,类似于观察者模式。
下图中两个客户端监听 “n1”, 第三个客户端向"n1"发布消息:
在这里插入图片描述
还可以是监听 n1.*, 用命令psubscibe
在这里插入图片描述
实际使用redis的时候,会与redis server建立两条连接,一条专门订阅(一直处于等待状态,相当于阻塞了),另一条用来处理种命令。

2.2 使用场景

缺陷:不保证消息一定到达(比如节点宕机),限制了发布订阅的使用场景
哪些场景可以允许丢失?-----用发布订阅模式
比如:游戏服务端发公告

反之:用kafka: 分布式消息队列: 能确保消息一定到达 (kafka 是工业级的,后续再写学习心得)
redis: stream 也可以,但是不推荐

3 redis事物

3.1 概念

用户定义一系列数据库操作,这些操作视为一个完整的逻辑处理工作单元,要么全部执行,要么全部不执行,是不可分割的工作单元。
工作中没有使用过很复杂的数据库场景,从而就用不到事物

3.2 使用场景

在什么场景下探讨事物? ----> 并发连接的场景:多条连接处理相同的功能

单条连接,多条连接处理不同的功能: 不需要考虑事物

3.3 解决的问题

3.3.1 背景:多线程竞争出现问题

		client1:  1  get count : 1
				      2    set count  2  
		client2:  也要同时去操作count
					  3   set count 3 
		eg1 不希望 eg2影响它
		希望的结果是 3 12  或者 12 3, 而不是 1 3 2。
		1和2要作为一个整体执行,由此可以联想到 C/C++ 的原子性,但是redis中对应的是“事务”,事务可以将12绑定在一起。

3.3.2 事务

为解决并发连接互相干扰的问题,“事务”来了
事务是整体同生共死,假如有ABCD四个节点与服务器连接,每一个节点中都可能有事物,其中任意一个节点都有可能宕机,为了保证事物的安全性,怎么办?==> 安全性事务。

3.3.3 安全性事务

安全性事务具备 ACID特性:(引用3.3.1中的 命令1 2 3 )

A  原子性:要么都成功,要么都失败。
	   遇到失败了,需要回退到执行之前的状态。
C 完整检测一致:key 已经是一个string类型,不能当作另一个类型(如list, set)去操作。
	逻辑上的一致   1  3  2 就破坏了逻辑性(相对于 3 12  或者 12 3)。
I  隔离性:client1 client2应该隔离,需要加锁,串行性执行。
	锁事务(原子变量是 锁总线)。
	后续:mysql: MVCC 非一致锁定夺。
D 持久性:数据刷到磁盘。

lua脚本满足原子性和隔离性,一致性和持久性不满足

3.4两种类型的“事务”

3.4.1 watch … multi exec

语法:

watch  
语句 
multi  #开启事务
n个语句  
# 告诉redis视为一个整体(n个语句依次入队,这里是redis服务端的行为,与pipeline不一样),保证里面的key不会被修改,否则不提交事务,直接返回nil
 exec   # 提交事务  redis服务端,依次执行n个语句的队列,因为是服务端的行为,所以可以控制不会被打断

eg:

WATCH zset
element = ZRANGE zset 0 0
MULTI
ZREM zset element
EXEC

说明:

multi  与 exec之间  :  redis 将事物进行缓存 (acid)	, 然后通过缓存命令统一执行。
用的就是 redis pipeline 技术: 客户端缓存命令统一发送。
但这些可以通过lua脚本去执行,因为redis中含有lua虚拟机,lua可以实时的拿到redis内存数据,引出下一节。

3.4.2 lua 脚本实现“原子”执行,重点掌握

redis 中加载了一个 lua 虚拟机;用来执行 redis lua 脚本;redislua 脚本的执行是原子性的;当某个脚本正在执行的时候,不会有其他命令或者脚本被执行;
lua 脚本当中的命令会直接修改数据状态;
lua 脚本 mysql 存储区别:MySQL存储过程不具备事务性,所以也不具备原子性;
注意:如果项目中使用了 lua 脚本,不需要使用上面的事务命令;
eg: eval后面的就是lua脚本

27.0.0.1:6379> set score:10001 100
OK
127.0.0.1:6379> eval 'local key = KEYS[1]; local val = redis.call("get", key); redis.call("set", key, 2*val); return 2*val;' 1  score:10001
(integer) 200
127.0.0.1:6379> eval 'local key = KEYS[1]; local val = redis.call("get", key); redis.call("set", key, 2*val); return 2*val;' 1  score:10001
(integer) 400
127.0.0.1:6379> eval 'local key = KEYS[1]; local val = redis.call("get", key); redis.call("set", key, 2*val); return 2*val;' 1  score:10001
(integer) 800

语法:EVAL script numkeys key [key ...] arg [arg ...]
numkeys 对应上面例子中的1,KEYS[1]表示 score:10001, 可选参数arg没有使用到。
redis.call 相当于调用redis命令
EVAL的第二个参数是参数的个数,后面的参数(从第三个参数),表示在脚本中所用到的那些 Redis 键(key),这些键名参数可以在 Lua 中通过全局变量 KEYS 数组,用 1 为基址的形式访问( KEYS[1] , KEYS[2] ,以此类推)。

3.4.3 watch multi exec 与 lua 脚本 的区别

1  multi exec 中间的事物语句拿不到结果
语句之间不能构成依赖逻辑关系   ===> 工作中几乎不会用到

2  相对的 lua都可以 。但是redis 没有回滚机制, lua脚本中有一部分语句执行成功,已经生效,后面的语句报错,与3.3.3.A里面提到的“遇到失败了,需要回退到执行之前的状态”不符。 
要实现完整的原子性,lua需要自己加代码,在报错的地方加上类似 if err 写代码回退 ,回滚到事物之前的状态。
eg: 
	local res, err = redis.pcall(...)
	if (err) {...自己写回滚的代码}

4 redis联通,通过hiredis压缩协议,解析协议,扩展事件处理

4.1 hiredis 安装

进入到 redis/deps/hiredis

make
sudo make install

4.2 hiredis : 如何引入自己项目中

1 局限性: 一定要使用reactor模型。因为在hiredis中,具体的IO操作是hiredis实现的,

也就说读数据read, 写数据write, 建立连接connect都是在hiredis中完成.
不可以使用proactor网络模型是因为 read write connect等操作都是在再proactor中完成,不是在hiredis中完成。

2 项目中要访问redis

参考

2.1 \redis\hiredis\examples example-libevent.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>

#include <hiredis.h>
#include <async.h>
#include <adapters/libevent.h>

void getCallback(redisAsyncContext *c, void *r, void *privdata) {
    redisReply *reply = r;
    if (reply == NULL) {
        if (c->errstr) {
            printf("errstr: %s\n", c->errstr);
        }
        return;
    }
    printf("argv[%s]: %s\n", (char*)privdata, reply->str);

    /* Disconnect after receiving the reply to GET */
    redisAsyncDisconnect(c);
}

void connectCallback(const redisAsyncContext *c, int status) {
    if (status != REDIS_OK) {
        printf("Error: %s\n", c->errstr);
        return;
    }
    printf("Connected...\n");
}

void disconnectCallback(const redisAsyncContext *c, int status) {
    if (status != REDIS_OK) {
        printf("Error: %s\n", c->errstr);
        return;
    }
    printf("Disconnected...\n");
}

int main (int argc, char **argv) {
#ifndef _WIN32
    signal(SIGPIPE, SIG_IGN);
#endif

    struct event_base *base = event_base_new();
    redisOptions options = {0};
    REDIS_OPTIONS_SET_TCP(&options, "127.0.0.1", 6379);
    struct timeval tv = {0};
    tv.tv_sec = 1;
    options.connect_timeout = &tv;


    redisAsyncContext *c = redisAsyncConnectWithOptions(&options);
    if (c->err) {
        /* Let *c leak for now... */
        printf("Error: %s\n", c->errstr);
        return 1;
    }

    redisLibeventAttach(c,base);
    redisAsyncSetConnectCallback(c,connectCallback);
    redisAsyncSetDisconnectCallback(c,disconnectCallback);
    redisAsyncCommand(c, NULL, NULL, "SET key %b", argv[argc-1], strlen(argv[argc-1]));
    redisAsyncCommand(c, getCallback, (char*)"end-1", "GET key");
    event_base_dispatch(base);
    return 0;
}

2.2 \redis\hiredis\adapters libevent.h 适配libevent网络库

 typedef struct redisLibeventEvents {
    redisAsyncContext *context;
    struct event *ev;
    struct event_base *base;
    struct timeval tv;
    short flags;
    short state;
} redisLibeventEvents;

。。。

2.3 流程备注

1 客户端:与redis建立连接,把指令压缩成redis协议发给redis服务器;
2 redis服务器: 解析执行,返回结果(也要压缩成redis协议的数据)。
3 客户端:再把 redis协议数据解析出来
客户端与服务端的连接 通过 hiredis 来管理,包含:
a 压缩协议
b 解析协议
c 扩展: 事件处理,可以用来适配reactor模型
reactor 回顾:
1 事件对象
2 注册事件
3 事件循环
4 触发事件 —> 处理 IO
参考\redis\hiredis\adapters libevent.h适配libevent网络库,关键部分如下:

static void redisLibeventUpdate(void *privdata, short flag, int isRemove) {
    redisLibeventEvents *e = (redisLibeventEvents *)privdata;
    const struct timeval *tv = e->tv.tv_sec || e->tv.tv_usec ? &e->tv : NULL;

    if (isRemove) {
        if ((e->flags & flag) == 0) {
            return;
        } else {
            e->flags &= ~flag;
        }
    } else {
        if (e->flags & flag) {
            return;
        } else {
            e->flags |= flag;
        }
    }

    event_del(e->ev);
    event_assign(e->ev, e->base, e->context->c.fd, e->flags | EV_PERSIST,
                 redisLibeventHandler, privdata);
    event_add(e->ev, tv);
}

static void redisLibeventAddRead(void *privdata) {
    redisLibeventUpdate(privdata, EV_READ, 0);
}

static void redisLibeventDelRead(void *privdata) {
    redisLibeventUpdate(privdata, EV_READ, 1);
}

static void redisLibeventAddWrite(void *privdata) {
    redisLibeventUpdate(privdata, EV_WRITE, 0);
}

static void redisLibeventDelWrite(void *privdata) {
    redisLibeventUpdate(privdata, EV_WRITE, 1);
}
 /* Register functions to start/stop listening for events */
    ac->ev.addRead = redisLibeventAddRead;
    ac->ev.delRead = redisLibeventDelRead;
    ac->ev.addWrite = redisLibeventAddWrite;
    ac->ev.delWrite = redisLibeventDelWrite;
    ac->ev.cleanup = redisLibeventCleanup;
    ac->ev.scheduleTimer = redisLibeventSetTimeout;

文章参考与<零声教育>的C/C++linux服务期高级架构系统教程学习:链接

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/32830.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

再以汇编代码分析c++的右值引用

汇编分析c语言的执行结果最为准确。 可见&#xff0c;右值引用其实还是引用&#xff0c; bb 和 cc 都是对 aa 的引用&#xff0c;其内存里存储了 aa 的地址。 而且还有一个很奇特的现象&#xff0c;bb无法给cc赋值&#xff0c;右值引用无法给右值赋值。 同样是调用std:: move…

d2l_第七章学习_卷积神经网络

参考: d2l今日学习——卷积神经网络&#xff08;CNN&#xff09;https://blog.csdn.net/m0_61165991/article/details/124176077图像工程&#xff08;上册&#xff09;-图像处理傅里叶变换https://blog.csdn.net/qq_43369406/article/details/131350139CNN卷积神经网络基础知识…

STC15 Proteus仿真DHT11环境湿度采集报警系统STC15W4K32S4-0043

STC15 Proteus仿真DHT11环境湿度采集报警系统STC15W4K32S4-0043 Proteus仿真小实验&#xff1a; STM32 Proteus仿真DHT11环境湿度采集报警系统STC15W4K32S4-0043 功能&#xff1a; Protues版本&#xff1a;8.9 硬件组成&#xff1a;STC15W4K32S4单片机 LCD1602显示器DHT11…

基于深度学习的高精度推土机检测识别系统(PyTorch+Pyside6+YOLOv5模型)

摘要&#xff1a;基于深度学习的高精度推土机检测识别系统可用于日常生活中检测与定位推土机目标&#xff0c;利用深度学习算法可实现图片、视频、摄像头等方式的推土机目标检测识别&#xff0c;另外支持结果可视化与图片或视频检测结果的导出。本系统采用YOLOv5目标检测模型训…

2023 node 接入腾讯云短信服务,实现发送短信功能

1、在 腾讯云开通短信服务&#xff0c;并申请签名和正文模板 腾讯云短信 https://console.cloud.tencent.com/smsv2 a、签名即是短信的开头。例如 【腾讯云短信】xxxxxxx&#xff1b; b、正文模板即短信内容&#xff0c; 变量部分使用{1}&#xff0c; 数字从1开始累推。例如&a…

深度学习-第T10周——数据增强

深度学习-第T10周——数据增强 深度学习-第T10周——数据增强一、前言二、我的环境三、前期工作1、导入数据集2、查看图片数目 四、数据预处理1、 加载数据1.1、设置图片格式1.2、划分训练集1.3、划分验证集1.4、查看标签1.5、再次检查数据1.6、配置数据集 2、数据可视化 五、数…

软件工程实践总结

前言 这次我们学校花了很多心血在这次的课设上&#xff0c;真的是特别感动和感谢&#xff0c;当你遇到真心为你好对你好的老师的时候&#xff0c;真的是会觉得人间值得&#xff01; 之前在学软件工程的时候我就会觉得这些理论的东西有什么用啊&#xff0c;什么UML&#xff0c;…

Scrapy框架之下载中间件(详解)

目录 Scrapy中下载中间件 概念 方法 process_request(self, request, spider) 参数: process_response(self, request, response, spider) 参数 基本步骤 示例代码 注意 Scrapy 中 Downloader 设置UA 开发UserAgent下载中间件 代码 三方模块 配置模块到Settin…

【js30天挑战】第四天:数组操作

总结 filter(筛选条件为true的项) map(你想要输出的东西)&#xff0c;进来多少个 出去多少个 sort()&#xff0c;默认可排字母顺序。sort(compareFn(a, b))其中compareFn(a, b)返回的值若大于0则a在b的后面。 reduce()&#xff0c;最复杂。reduce(func(){上一轮计算出的结果…

Flink-SQL 写入PostgreSQL 问题汇总

​ 1.主键字段为空问题 错误信息 org.apache.flink.table.api.TableException: Column bus_no is NOT NULL, however, a null value is being written into it. You can set job configuration table.exec.sink.not-null-enforcerDROP to suppress this exception and drop …

罗技k380键盘教程

在智能手机和平板电脑上享受台式电脑般舒适便捷的输入体验。罗技蓝牙™ 多设备键盘 K380 是一款小巧独特的键盘&#xff0c;让您在家中任何地方都能使用个人设备进行沟通和创作。 借助便捷的易于切换™ 按钮&#xff0c;可以通过蓝牙™ 无线技术同时连接最多三台设备&#xff…

【实用技巧】使用USB数据线向亚马逊kindle导入电子书

一、内容简介 本文主要介绍如何使用USB数据线向亚马逊kindle阅读器导入电子书。 二、所需原料 笔记本电脑、Kindle阅读器、Kindle适配的USB-a数据线。 三、导入方法 1、使用USB-a数据线将Kindle阅读器与电脑连接。 2、找到Kindle文件夹-documents-Downloads-Items1目录。…

Django框架实现简单的接口开发

前提创建一个Django项目&#xff0c;目录如下&#xff1a; Django框架上进行GET请求接口开发示例: 1.在上面项目结构目录Template下&#xff0c;新建一个login.html页面&#xff0c;定义表单提交请求的方式为post&#xff0c;具体代码如下。 <!DOCTYPE HTML> <html …

freemarker 使用word模板赋值

1. 引包<dependency><groupId>org.freemarker</groupId><artifactId>freemarker</artifactId><version>2.3.28</version></dependency>word文档工具类import freemarker.template.Configuration; import freemarker.template.…

快来看看Java在编程语言中的优势与特性吧

作者主页&#xff1a;paper jie的博客_CSDN博客-C语言,算法详解领域博主 本文作者&#xff1a;大家好&#xff0c;我是paper jie&#xff0c;感谢你阅读本文&#xff0c;欢迎一建三连哦。 其他专栏&#xff1a;《系统解析C语言》《C语言》《C语言-语法篇》 内容分享&#xff1a…

CentOS 7.9 安装 Jenkins

CentOS 7.9 安装 Jenkins 文章目录 CentOS 7.9 安装 Jenkins一、概述二、安装1、安装 OpenJDK2、安装 Jenkins3、启动 Jenkins4、给 Jenkins 放行端口 三、初始化 Jenkins 配置1、访问2、解锁 Jenkins3、配置清华大学的源地址4、安装插件5、创建管理员用户6、完成安装 四、功能…

【C++】C++关于异常的学习

文章目录 C语言传统的处理错误的方式一、异常的概念及用法二、自定义异常体系总结 C语言传统的处理错误的方式 传统的错误处理机制&#xff1a; 1. 终止程序&#xff0c;如 assert &#xff0c;缺陷&#xff1a;用户难以接受。如发生内存错误&#xff0c;除 0 错误时就会终止…

三相一次重合闸程序逻辑原理(二)

在手动合闸至故障线路或手动分闸及保护或自动装置要求不允许重合闸&#xff08;如母线、变压器保护及低频减载动作&#xff09;等情况下&#xff0c;闭锁重合闸的输入开关量触点接通&#xff0c;H4输出“1”&#xff0c;非门Z4输出“0”&#xff0c;计数器清零&#xff08;CD0&…

基于Java+SpringBoot+Vue前后端分离网课在线学习观看系统

博主介绍&#xff1a;✌全网粉丝20W,csdn特邀作者、博客专家、CSDN新星计划导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专…

Spring Boot banner详解

Spring Boot 3.x系列文章 Spring Boot 2.7.8 中文参考指南(一)Spring Boot 2.7.8 中文参考指南(二)-WebSpring Boot 源码阅读初始化环境搭建Spring Boot 框架整体启动流程详解Spring Boot 系统初始化器详解Spring Boot 监听器详解Spring Boot banner详解 自定义banner Spring …