redis学习-事务、同步异步连接

1、Pipeline功能

Pileline是redis提供的一种批处理通信协议的能力,目的是节约网络传输时间。通过一次发送多次请求命令,从而减少网络传输的时间,Pipeline不具备事务性。原理如图:

测试代码如下:(注意看输出结果,就明白了)

import redis

# 创建 Redis 连接
r = redis.Redis(host='localhost', port=6379, socket_timeout=5)

# 创建 Pipeline 对象
pipe = r.pipeline()

# 往 Pipeline 中加入多个命令
pipe.set("key1", "value1")
pipe.set("key2", "value2")
pipe.get("key1")

# 执行 Pipeline 中的所有命令并获取结果
results = pipe.execute()
print(results)  # 输出[True, True, b'value1']

2、事务

把用户定义的一些数据库操作,看成一个整体,要么全部执行,要么全部不执行。

2.1、方案一:MULTI、EXEC

redis提供了MULTI、EXEC、WATCH和DISCARD命令来实现事务,在一个事务中,所有命令被收集起来以原子性执行。

相关命令

MULTI:事务开始,之后的命令会被放入一个队列,不会立即执行

EXEC:执行事务

DISCARD:取消事务

WATCH:检测key的变动,若在事务执行中,key变动则取消事务;在事务开启前调用。

测试代码:multi~exec之间的代码具有事务性,保证原子性

import redis

# 创建 Redis 连接
r = redis.Redis(host='localhost', port=6379, socket_timeout=5)

# 创建 Pipeline 对象
pipe = r.pipeline()

# 往 Pipeline 中加入多个命令
pipe.watch("key1")

# multi标记事务开始
pipe.multi()
pipe.set("key1", "value1")
pipe.set("key2", 10)
pipe.incr("key2")
pipe.get("key1")

# 执行 Pipeline 中的所有命令并获取结果
results = pipe.execute()
print(results)  # 输出类似 [True, True, 11, b'value1']

2.2、方案二:lua脚本(工作中用的多)

redis中加载了一个lua虚拟机,用来执行lua脚本。redis确保lua脚本的执行是原子性的,使用lua脚本可以在一个请求中执行多个redis命令,并且这些命令以原子性执行,要么全部执行,要么全部不执行。使用EVAL和EVALSHA执行脚本

相关命令

1、EVAL script numkeys key [key ...] arg [arg ...]

        script 是你想要执行的 Lua 脚本代码。
        numkeys 是脚本将要使用的键的数量。这很重要,因为 Redis 需要知道哪些键是该脚本的一      部分,以便正确地处理锁和其他并发控制机制。
        key [key ...] 是提供给脚本的键列表。
        arg [arg ...] 是传递给脚本的额外参数,它们不是键。

2、EVALSHA sha1 numkeys key [key ...] arg [arg ...]

        sha1 是之前通过 EVAL 命令执行过的 Lua 脚本的 SHA1 校验和。如果 Redis 已经缓存了这个脚本,它可以直接执行而不需要重新解析脚本文本。
        其余参数与 EVAL 相同。

eval测试代码

import redis

# 创建 Redis 连接
r = redis.Redis(host='localhost', port=6379, socket_timeout=5)

# 定义 Lua 脚本
lua_script = """
local key = KEYS[1]
local value = tonumber(ARGV[1]) -- 将传入的参数转换为数字
redis.call('set', key, value)
local retrieved_value = redis.call('get', key)
local doubled_value = tonumber(retrieved_value) * 2 -- 将获取到的值转换为数字后乘以2
return doubled_value
"""

# 使用 EVAL 执行 Lua 脚本,传入键名 'mykey'
result = r.eval(lua_script, 1, 'mykey', 100)

print(f"The result is: {result}")

evalsha测试代码:

# 从文件中读取 lua脚本内容
cat test1.lua | redis-cli script load --pipe

# 加载 lua脚本字符串 生成 sha1
> script load 'local val = KEYS[1]; return val'
"b8059ba43af6ffe8bed3db65bac35d452f8115d8"

# 检查脚本缓存中,是否有该 sha1 散列值的lua脚本
> script exists "b8059ba43af6ffe8bed3db65bac35d452f8115d8"
1) (integer) 1

# 清除所有脚本缓存
> script flush
OK

# 如果当前脚本运行时间过长(死循环),可以通过 script kill 杀死当前运行的脚本
> script kill
(error) NOTBUSY No scripts in execution right now

3、ACID特性

redis本身不是一个完全符合ACID(原子性,一致性、隔离性、持久性)特性的数据库,而是提供了一些特性来接近这些原则。
 

A(原子性):

事务是一个不可分割的工作单位,事务中的操作要么全部成功,要么全部失败; redis
不支持回滚;即使事务队列中的某个命令在执行期间出现了错误,整个事务也会继续执行下去,直
到将事务队列中的所有命令都执行完毕为止。
C(一致性):
事务的前后,所有的数据都保持一个一致的状态,不能违反数据的一致性检测;这里
的一致性是指预期的一致性而不是异常后的一致性;所以 redis 也不满足;这个争议很大: redis
确保事务执行前后的数据的完整约束;但是并不满足业务功能上的一致性;比如转账功能,一个扣
钱一个加钱;可能出现扣钱执行错误,加钱执行正确,那么最终还是会加钱成功;系统凭空多了
钱。
I(隔离性):
各个事务之间互相影响的程度; redis 是单线程执行,天然具备隔离性;

D(持久性)

redis 只有在 aof 持久化策略的时候,并且需要在 redis.conf
appendfsync=always 才具备持久性;实际项目中几乎不会使用 aof 持久化策略;

4、同步连接

redis同步连接指客户端和redis服务端的连接方式,每个命令都是同步执行。意味着客户端发送一个命令给redis服务器时,必须要等服务器响应,才能继续下一条指令。

4.1、hiredis(C库)

redis源码中有带hiredis,hiredis是一个用于解析Redis协议的C库,该库中有同步API、异步API等。使用该库可以实现C语言操作Redis数据库,C++操作Redis可参考redis-plus-plus库。

hiredis路径:redis\deps\hiredis   # 相对redis源码

hiredis编译

cd hiredis
make
sudo make install

测试代码:

gcc redis-test-sync.c -o redis-test-sync -lhiredis

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <hiredis/hiredis.h>

int current_tick() {
    int t = 0;
    struct timespec ti;
	clock_gettime(CLOCK_MONOTONIC, &ti);
	t = (int)ti.tv_sec * 1000;
	t += ti.tv_nsec / 1000000;
    return t;
}

int main(int argc, char **argv) {
    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

    // 连接redis服务器
    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 num = (argc > 1) ? atoi(argv[1]) : 1000;

    int before = current_tick();

    for (int i=0; i<num; i++) {
        // 发送redis命令
        reply = redisCommand(c,"INCR counter");
        printf("INCR counter: %lld\n", reply->integer);
        freeReplyObject(reply);
    }

    int used = current_tick()-before;

    printf("after %d exec redis command, used %d ms\n", num, used);

    /* Disconnects and frees the context */
    redisFree(c);

    return 0;
}

5、异步连接

异步连接允许客户端在发送命令后不等待响应,可以继续执行其他操作。实现异步连接需要一个 Redis 驱动,该驱动需要将 Redis 连接融入项目中的 Reactor 模式进行管理。

这部分还未理解很深,可参考网上其他链接。

学习链接:https://github.com/0voice

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值