口转发默认是10条,通过我的上一篇文章可以突破到16条,但是再多也没有效果,而除了改html文件,lua脚本是只读的,而且也没看到到底哪里限制住了,因为本身是通过iptables实现的,所以取个巧,直接用命令行脚本设置得了按照如下步骤即可:
1.ssh登陆路由器,执行如下命令:
vi /tmp/port-forward.sh
2.把如下内容复制进去然后保存
oip=`ip a show ppp0 | grep inet | awk '{print }'|tr -d "addr:"`
sip=
sport=
oport=
iptables -t nat -A port_forward -d $oip/32 -p tcp -m tcp --dport $oport -j DNAT --to-destination $sip:$sport
iptables -t nat -A port_forward_ctf -p tcp -m tcp --dport $oport -j SKIPCTF
iptables -t nat -A port_forward_post -s 192.168.1.0/24 -d $sip/32 -p tcp -m tcp --dport $sport -j SNAT --to-source $oip:$oport
3.在高级设置启动任务里按照如下增加一行
这样就可以把192.168.1.134的3603端口映射到外部3603端口,这三个参数依次:内部ip,内部端口,外部端口。
这样每次启动都会执行这个命令进行端口映射,每增加一个端口就在启动任务里增加一行就行,只是每增加一个还需要在路由器中手动先执行一下,不然只能重启路由器!
者:潘梦源
Kruise Rollout[1]是 OpenKruise 社区开源的渐进式交付框架。Kruise Rollout 支持配合流量和实例灰度的金丝雀发布、蓝绿发布、A/B Testing 发布,以及发布过程能够基于 Prometheus Metrics 指标自动化分批与暂停,并提供旁路的无感对接、兼容已有的多种工作负载(Deployment、CloneSet、DaemonSet)。
目前 Kruise Rollout 新增了流量调度支持自定义资源的能力,从而更好的支持渐进式发布中的流量调度。本文将对 Kruise Rollout 所提出的方案进行介绍。
什么是渐进式发布?
渐进式发布(Progressive Delivery)是一种软件部署和发布策略,旨在逐步将新版本或功能引入生产环境,以降低风险并确保系统的稳定性。一些常见的渐进式发布形式如下:
金丝雀发布、A/B 测试和蓝绿发布都是逐步测试和评估新功能或变更的策略,它们可以根据具体的需求和场景选择适合的部署和测试策略,并结合流量灰度等技术实现逐步发布和测试新版本或功能。
为什么需要对网关资源提供支持?
Kruise Rollout 目前已经对 Gateway API 提供了支持,那么为什么还需要对不同供应商的网关资源提供支持呢?在解释这个问题之前,我们先来简单介绍一下 Gateway API。
当前社区中不同的供应商都有自己的网关资源,并提出了自己的标准,而 Kubernetes 为了提供一个统一的网关资源标准,构建标准化的,独立于供应商的 API,提出了 Gateway API。目前,尽管 Gateway API 还处于开发阶段,但已经有很多项目表示支持或计划支持 Gateway API。包括:
然而由于目前 Gateway API 并不能覆盖供应商所提出网关资源的所有功能,并且仍然有大量用户使用供应商提供的网关资源,虽然用户可以通过开发 Gateway API 对网关资源进行适配,但这样的工作量较大,所以仅仅为 Gateway API 提供支持是远远不够的,尽管随着 Gateway API 特性的不断丰富,在未来,使用 Gateway API 将成为一种更加推荐的方式。因此,虽然 Kruise Rollout 目前已经提供了对 Gateway API 的支持,如何对现有供应商多种多样的网关资源提供支持仍然是一个重要的问题。
如何兼容社区多样的网关方案?
当前社区中已经存在许多广泛使用的供应商提供的网关资源,比如:Istio、Kong、Apisix 等,然而正如前文所述,这些资源的配置并没有形成统一的标准,因此无法设计出一套通用的代码对资源进行处理,这种情况给开发人员带来了一些不便和挑战。
argo-rollouts 与 flagger 兼容方案
为了能够兼容更多的社区网关资源,一些方案被提出,例如 flagger、argo-rollouts 为每一种网关资源都提供了代码实现。这些方案的实现相对简单,但也存在一些问题:
argo-rollouts 不同资源配置
因此,需要一种支持用户定制,可以灵活插拔的实现方案,以适配社区以及用户定制的多种多样的网关资源,来满足社区不同的用户的需求,增强 Kruise Rollout 的兼容性和扩展性。
为此,我们提出了一种基于 Lua 脚本的网关资源可扩展流量调度方案。
Kruise Rollout 使用基于 Lua 脚本的网关资源定制方案,本方案通过调用 Lua 脚本根据发布策略和网关资源原始状态来获取并更新资源的期待工作状态(状态包含 spec、labels 以及 annotations),可以使用户能够轻松地适配和集成不同类型的网关资源,而无需修改现有的代码和配置。
本方案对于网关资源的处理可以表示为上图,整个过程可以描述为:
通过使用 Kruise Rollout,用户可以:
同时,Kruise Rollout 采用的方案仅需要添加 5 个新接口即可实现对多种多样网关资源的支持。相比之下,其他方案例如 argo-rollouts 则为不同供应商的网关资源提供了不同的接口,对于 Istio 和 Apisix 来说,argo-rollouts 分别提供了 14 个和 4 个新的接口,而且,该方案随着对更多网关资源的支持,接口数量还会持续增长。相比之下,Kruise Rollout 并不需要为新的网关资源提供新的接口,这使得 Kruise Rollout 成为一种更简洁、更易于维护的选择,而不会增加过多的接口负担。同时,编写 Lua 脚本相对于开发 Gateway API 对网关资源进行适配,可以大大减小开发人员的工作量。
以下展示了一个利用 Lua 脚本对 Istio DestinationRule 进行处理的的示例。
apiVersion: rollouts.kruise.io/v1alpha1
kind: Rollout
...
spec:
...
trafficRoutings:
- service: mocka
createCanaryService: false # 使用原有service,不创建新的canary service
networkRefs: # 需要控制的网关资源
- apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
name: ds-demo
patchPodTemplateMetadata:
labels
version: canary # 为新版本pod打上label
2. 对 Istio DestinationRule 进行处理的 Lua 脚本为:
local spec = obj.data.spec -- 获取资源的spec,obj.data为资源的状态信息
local canary = {} -- 初始化一条指向新版本的canary路由规则
canary.labels = {} -- 初始化canary路由规则的labels
canary.name = "canary" -- 定义canary路由规则名称
-- 循环处理rollout配置的新版本pod label
for k, v in pairs(obj.patchPodMetadata.labels) do
canary.labels[k] = v -- 向canary规则中加入pod label
end
table.insert(spec.subsets, canary) -- 向资源的spec.subsets中插入canary规则
return obj.data -- 返回资源状态
3. 处理完的 DestinationRule 为:
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
spec:
...
subsets:
- labels: # -+
version: canary # |- Lua脚本处理后新插入的规则
name: canary # -+
- labels:
version: base
name: version-base
接下来介绍一个利用我们所提出方案对 Istio 进行支持的具体案例。
1. 首先部署如下图所示的服务。该服务由以下几部分构成:
nginx 服务的 deployment 如下所示:
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
replicas: 1
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
version: base
spec:
containers:
- name: nginx
image: nginx
ports:
- containerPort: 80
volumeMounts:
- name: html-volume
mountPath: /usr/share/nginx/html
volumes:
- name: html-volume
configMap:
name: nginx-configmap-base # 挂载ConfigMap作为index
2. 创建 rollout 资源,配置发布规则,该 rollout 分为两批发布:
apiVersion: rollouts.kruise.io/v1alpha1
kind: Rollout
metadata:
name: rollouts-demo
annotations:
rollouts.kruise.io/rolling-style: canary
spec:
disabled: false
objectRef:
workloadRef:
apiVersion: apps/v1
kind: Deployment
name: nginx-deployment
strategy:
canary:
steps:
- weight: 20 # 第一批转发20%的流量进入新版本pod
- replicas: 1 # 第二批将包含version=canary header的流量转发入新版本pod
matches:
- headers:
- type: Exact
name: version
value: canary
trafficRoutings:
- service: nginx-service # 旧版本pod使用的service
createCanaryService: false # 不创建新的canary service,新旧pod共用一个service
networkRefs: # 需要修改的网关资源
- apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
name: nginx-vs
- apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
name: nginx-dr
patchPodTemplateMetadata: # 为新版本pod打上version=canary的label
labels:
version: canary
3. 修改 nginx 服务 deployment 中挂载的 ConfigMap 开始金丝雀发布。
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
...
volumes:
- name: html-volume
configMap:
name: nginx-configmap-canary # 挂载新的ConfigMap作为index
4. 开始发布第一批,Kruise Rollout 自动调用定义的 Lua 脚本对 VirtualService 和 DestinationRule 资源进行修改,进行流量调度,将 20% 的流量转发至新版本 pod 中,此时整个服务的流量表示为下图所示:
5. 执行命令 kubectl-kruise rollout approve rollout/rollouts-demo,开始发布第二批,Kruise Rollout 自动调用定义的 Lua 脚本对 VirtualService 和 DestinationRule 资源进行修改,进行流量调度,将包含 version=canary header 的流量转发至新版本 pod 中,此时整个服务的流量表示为下图所示:
6. 执行命令 kubectl-kruise rollout approve rollout/rollouts-demo,发布结束,VirtualService 和 DestinationRule 资源恢复至发布前状态,所有流量路由至新版本 pod。
在调用 Lua 脚本获取资源状态新状态时,Kruise Rollout 支持两种 Lua 脚本调用方式,分别为:
Kruise Rollout 默认首先查找本地是否存在已发布的 Lua 脚本,这些脚本通常需要设计测试案例进行单元测试验证其可用性,具有更好的稳定性。测试案例的格式如下所示,Kruise Rollout 利用 Lua 脚本根据 rollout 中定义的发布策略对资源原始状态进行处理,得到发布过程中每一步的资源新状态,并与测试案例中 expected 中定义的期待状态进行对比,以验证 Lua 脚本是否按照预期工作。
rollout:
# rollout配置
original:
# 资源的原始状态
expected:
# 发布过程中资源的期待状态
在资源的 Lua 脚本未发布的情况下,用户还可以快速的通过在 ConfigMap 中配置 Lua 脚本的方式由 Kruise Rollout 调用从而对资源进行处理。
apiVersion: v1
kind: ConfigMap
metadata:
name: kruise-rollout-configuration
namespace: kruise-rollout
data:
# 键以lua.traffic.routing.Kind.CRDGroup的形式命名
"lua.traffic.routing.DestinationRule.networking.istio.io": |
--- 定义Lua脚本
local spec = obj.data.spec
local canary = {}
canary.labels = {}
canary.name = "canary"
for k, v in pairs(obj.patchPodMetadata.labels) do
canary.labels[k] = v
end
table.insert(spec.subsets, canary)
return obj.data
详细的 Lua 脚本配置说明参见 Kruise Rollout 官网[2]。
非常欢迎你通过 Github/Slack/钉钉/微信 等方式加入我们来参与 OpenKruise 开源社区。
你是否已经有一些希望与我们社区交流的内容呢?可以在我们的社区双周会[3]上分享你的声音,或通过以下渠道参与讨论:
相关链接:
[1] Kruise Rollout
https://github.com/openkruise/rollouts
[2] Kruise Rollout 官网
https://openkruise.io/rollouts/introduction
[3] 社区双周会
https://shimo.im/docs/gXqmeQOYBehZ4vqo
[4] Slack channel
https://kubernetes.slack.com/?redir=%2Farchives%2Fopenkruise
简单来说,事务(transaction)是指单个逻辑单元执行的一系列操作。
事务有如下四大特性:
Redis中的事务通过multi,exec,discard,watch这四个命令来完成。
Redis的单个命令都是原子性的,所以确保事务的就是多个命令集合一起执行。
Redis命令集合打包在一起,使用同一个任务确保命令被依次有序且不被打断的执行,从而保证事务性。
Redis是弱事务,不支持事务的回滚。
事务命令简介
事务操作
# 普通的执行多个命令
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set m_name zhangsan
QUEUED
127.0.0.1:6379> hmset m_set name zhangsan age 20
QUEUED
127.0.0.1:6379> exec
1) OK
2) OK
# 执行命令前清空队列 将会导致事务执行不成功
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set m_name_1 lisi
QUEUED
127.0.0.1:6379> hmset m_set_1 name lisi age 21
QUEUED
# 提交事务前执行了清空队列命令
127.0.0.1:6379> discard
OK
127.0.0.1:6379> exec
(error) ERR EXEC without MULTI
# 监听一个key,并且在事务提交之前改变在另一个客户端改变它的值,也会导致事务失败
127.0.0.1:6379> set m_name_2 wangwu01
OK
127.0.0.1:6379> watch m_name_2
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set m_name_2 wangwu02
QUEUED
# 另外一个客户端在exec之前执行之后,这里会返回nil,也就是清空了队列,而不是执行成功
127.0.0.1:6379> exec
(nil)
# 另外一个客户端在exec之前执行
127.0.0.1:6379> set m_name_2 niuqi
OK
我们前面总是在说,Redis的事务命令是打包放在一个队列里的。那么来看一下Redis客户端的数据结构吧。
client数据结构
typedef struct client {
// 客户端唯一的ID
uint64_t id;
// 客户端状态 表示是否在事务中
uint64_t flags;
// 事务状态
multiState mstate;
// ...还有其他的就不一一列举了
} client;
multiState事务状态数据结构
typedef struct multiState {
// 事务队列 是一个数组,按照先入先出顺序,先入队的命令在前 后入队的命令在后
multiCmd *commands; /* Array of MULTI commands */
// 已入队命令数
int count; /* Total number of MULTI commands */
// ...略
} multiState;
multiCmd事务命令数据结构
/* Client MULTI/EXEC state */
typedef struct multiCmd {
// 命令的参数
robj **argv;
// 参数长度
int argv_len;
// 参数个数
int argc;
// redis命令的指针
struct redisCommand *cmd;
} multiCmd;
Redis的事务执行流程图解
Redis的事务执行流程分析
我们知道,Redis有一个expires的字典用于key的过期事件,同样,监听的key也有一个类似的watched_keys字典,key是要监听的key,值是一个链表,记录了所有监听这个key的客户端。
而监听,就是监听这个key是否被改变,如果被改变了,监听这个key的客户端的flags属性就设置为REDIS_DIRTY_CAS。
Redis客户端向服务器端发送exec命令,服务器判断Redis客户端的flags,如果为REDIS_DIRTY_CAS,则清空事务队列。
redis监听机制图解
redis监听key数据结构
回过头再看一下RedisDb类的watched_keys,确实是一个字典,数据结构如下:
typedef struct redisDb {
dict *dict; /* 存储所有的key-value */
dict *expires; /* 存储key的过期时间 */
dict *blocking_keys; /* blpop存储阻塞key和客户端对象*/
dict *ready_keys; /* 阻塞后push,响应阻塞的那些客户端和key */
dict *watched_keys; /* 存储watch监控的key和客户端对象 WATCHED keys for MULTI/EXEC CAS */
int id; /* 数据库的ID为0-15,默认redis有16个数据库 */
long long avg_ttl; /* 存储对象的额平均ttl(time in live)时间用于统计 */
unsigned long expires_cursor; /* Cursor of the active expire cycle. */
list *defrag_later; /* List of key names to attempt to defrag one by one, gradually. */
clusterSlotToKeyMapping *slots_to_keys; /* Array of slots to keys. Only used in cluster mode (db 0). */
} redisDb;
为什么说Redis是弱事务性呢? 因为如果redis事务中出现语法错误,会暴力的直接清除整个队列的所有命令。
# 在事务外设置一个值为test
127.0.0.1:6379> set m_err_1 test
OK
127.0.0.1:6379> get m_err_1
"test"
# 开启事务 修改值 但是队列的其他命令出现语法错误 整个事务会被discard
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set m_err_1 test1
QUEUED
127.0.0.1:6379> sets m_err_1 test2
(error) ERR unknown command `sets`, with args beginning with: `m_err_1`, `test2`,
127.0.0.1:6379> set m_err_1 test3
QUEUED
127.0.0.1:6379> exec
(error) EXECABORT Transaction discarded because of previous errors.
# 重新获取值
127.0.0.1:6379> get m_err_1
"test"
我们发现,如果命令队列中存在语法错误,是直接的清除队列的所有命令,并不是进行事务回滚,但是语法错误是能够保证原子性的。
再来看一些,如果出现类型错误呢?比如开启事务后设置一个key,先设置为string, 然后再当成列表操作。
# 开启事务
127.0.0.1:6379> multi
OK
# 设置为字符串
127.0.0.1:6379> set m_err_1 test_type_1
QUEUED
# 当初列表插入两个值
127.0.0.1:6379> lpush m_err_1 test_type_1 test_type_2
QUEUED
# 执行
127.0.0.1:6379> exec
1) OK
2) (error) WRONGTYPE Operation against a key holding the wrong kind of valu
# 重新获取值,我们发现我们的居然被改变了,明明,事务执行失败了啊
127.0.0.1:6379> get m_err_1
"test_type_1"
直到现在,我们确定了redis确实不支持事务回滚。因为我们事务失败了,但是命令却是执行成功了。
弱事务总结
那么,redis就没有办法保证原子性了吗,当然有,Redis的lua脚本就是对弱事务的一个补充。
lua是一种轻量小巧的脚本语言,用标准C语言编写并以源代码形式开放, 其设计目的是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能。
Lua应用场景:游戏开发、独立应用脚本、Web应用脚本、扩展和数据库插件。
OpenResty:一个可伸缩的基于Nginx的Web平台,是在nginx之上集成了lua模块的第三方服务器。
OpenResty是一个通过Lua扩展Nginx实现的可伸缩的Web平台,内部集成了大量精良的Lua库、第三方模块以及大多数的依赖项。用于方便地搭建能够处理超高并发(日活千万级别)、扩展性极高的动态Web应用、Web服务和动态网 关。 功能和nginx类似,就是由于支持lua动态脚本,所以更加灵活,可以实现鉴权、限流、分流、日志记 录、灰度发布等功能。
OpenResty通过Lua脚本扩展nginx功能,可提供负载均衡、请求路由、安全认证、服务鉴权、流量控 制与日志监控等服务。
类似的还有Kong(Api Gateway)、tengine(阿里)
lua脚本下载和安装http://www.lua.org/download.html
lua脚本参考文档:http://www.lua.org/manual/5.4/
# curl直接下载
curl -R -O http://www.lua.org/ftp/lua-5.4.4.tar.gz
# 解压
tar zxf lua-5.4.4.tar.gz
# 进入,目录
cd lua-5.4.4
# 编译安装
make all test
编写lua脚本
编写一个lua脚本test.lua,就定义一个本地变量,打印出来即可。
local name = "zhangsan"
print("name:",name)
执行lua脚本
[root@VM-0-5-centos ~]# lua test.lua
name: zhangsan
Redis从2.6开始,就内置了lua编译器,可以使用EVAL命令对lua脚本进行求值。
脚本命令是原子性的,Redis服务器再执行脚本命令时,不允许新的命令执行(会阻塞,不在接受命令)。、
EVAL命令
通过执行redis的eval命令,可以运行一段lua脚本。
EVAL script numkeys key [key ...] arg [arg ...]
EVAL命令说明
简单来说,就是
eval lua脚本片段 参数个数(假设参数个数=2) 参数1 参数2 参数1值 参数2值
EVAL命令执行
# 执行一段lua脚本 就是把传入的参数和对应的值返回回去
127.0.0.1:6379> eval "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}" 2 name age zhangsan 20
1) "name"
2) "age"
3) "zhangsan"
4) "20"
lua脚本中调用redis
我们直到了如何接受和返回参数了,那么lua脚本中如何调用redis呢?
其实就是redis.call会把异常抛出来,redis.pcall则时捕获了异常,不会抛出去。
lua脚本调用redis设置值
# 使用redis.call设置值
127.0.0.1:6379> eval "return redis.call('set',KEYS[1],ARGV[1])" 1 eval_01 001
OK
127.0.0.1:6379> get eval_01
"001"
EVALSHA命令
前面的eval命令每次都要发送一次脚本本身的内容,从而每次都会编译脚本。
Redis提供了一个缓存机制,因此不会每次都重新编译脚本,可能在某些场景,脚本传输消耗的带宽可能是不必要的。
为了减少带宽的西消耗,Redis实现了evaklsha命令,它的作用和eval一样,只是它接受的第一个参数不是脚本,而是脚本的SHA1校验和(sum)。
所以如何获取这个SHA1的值,就需要提到Script命令。
执行evalsha命令
# 使用script load将脚本内容加载到缓存中,返回sha的值
127.0.0.1:6379> script load "return redis.call('set',KEYS[1],ARGV[1])"
"c686f316aaf1eb01d5a4de1b0b63cd233010e63d"
# 使用evalsha和返回的sha的值 + 参数个数 参数名称和值执行
127.0.0.1:6379> evalsha c686f316aaf1eb01d5a4de1b0b63cd233010e63d 1 eval_02 002
OK
# 获取结果
127.0.0.1:6379> get eval_02
"002"
我们上面都是将脚本写在代码行里面,可以不可以将脚本内容写在xxx.lua中,直接执行呢? 当然是可以的。
使用redis-cli运行外置lua脚本
编写外置脚本test2.lua, 设置值到redis中。
# 脚本内容 也就是设置一个值
return redis.call('set',KEYS[1],ARGV[1])
# 执行结果,可以使用./redis-cli -h 127.0.0.1 -p 6379 指定redis ip、端口等
root@62ddf68b878d:/data# redis-cli --eval /data/test2.lua eval_03 , test03
OK
利用Redis整合lua脚本,主要是为了保证性能是事务的原子性,因为redis的事务功能确实有些差劲!
Redis如果开启了主从复制,脚本是如何从主服务器复制到从服务器的呢?
首先,redis的脚本复制有两种模式,脚本传播模式和命令传播模式。
在开启了主从,并且开启了AOF持久化的情况下。
其实就是主服务器执行什么脚本,从服务器就执行什么样的脚本。但是如果有当前事件,随机函数等会导致差异。
主服务器执行命令
# 执行多个redis命令并返回
127.0.0.1:6379> eval "local result1 = redis.call('set',KEYS[1],ARGV[1]); local result2 = redis.call('set',KEYS[2],ARGV[2]); return {result1, result2}" 2 eval_test_01 eval_test_02 0001 0002
1) OK
2) OK
127.0.0.1:6379> get eval_test_01
"0001"
127.0.0.1:6379> get eval_test_02
"0002"
那么主服务器将向从服务器发送完全相同的eval命令:
eval "local result1 = redis.call('set',KEYS[1],ARGV[1]); local result2 = redis.call('set',KEYS[2],ARGV[2]); return {result1, result2}" 2 eval_test_01 eval_test_02 0001 0002
注意:在这一模式下执行的脚本不能有时间、内部状态、随机函数等。执行相同的脚本以及参数必须产生相同的效果。在Redis5,也是处于同一个事务中。
处于命令传播模式的主服务器会将执行脚本产生的所有写命令用事务包裹起来,然后将事务复制到AOF文件以及从服务器里面.
因为命令传播模式复制的是写命令而不是脚本本身,所以即使脚本本身包含时间、内部状态、随机函数等,主服务器给所有从服务器复制的写命令仍然是相同的。
为了开启命令传播模式,用户在使用脚本执行任何写操作之前,需要先在脚本里面调用以下函数:
redis.replicate_commands()
redis.replicate_commands() 只对调用该函数的脚本有效:在使用命令传播模式执行完当前脚本之后,服务器将自动切换回默认的脚本传播模式。
执行脚本
eval "redis.replicate_commands();local result1 = redis.call('set',KEYS[1],ARGV[1]); local result2 = redis.call('set',KEYS[2],ARGV[2]); return {result1, result2}" 2 eval_test_03 eval_test_04 0003 0004
appendonly.aof文件内容
*1
$5
MULTI
*3
$3
set
$12
eval_test_03
$4
0003
*3
$3
set
$12
eval_test_04
$4
0004
*1
$4
EXEC
可以看到,在一个事务里面执行了我们脚本执行的命令。
同样的道理,主服务器只需要向从服务器发送这些命令就可以实现主从脚本数据同步了。
Redis的事务是弱事务,多个命令开启事务一起执行性能比较低,且不能一定保证原子性。所以lua脚本就是对它的补充,它主要就是为了保证redis的原子性。
比如有的业务(接口Api幂等性设计,生成token,(取出toker并判断是否存在,这就不是原子操作))我们需要获取一个key, 并且判断这个key是否存在。就可以使用lua脚本来实现。
还有很多地方,我们都需要redis的多个命令操作需要保证原子性,此时lua脚本可能就是一个不二选择。
本人还写了Redis的其他相关文章,有兴趣的可以点击查看!
*请认真填写需求信息,我们会在24小时内与您取得联系。