• [技术干货] Lua面向对象编程的基本原理示例【转载】
    关于Lua语言中的table以及metatable的基本知识,这里就不赘述了,官方手册中描述的很清楚。测试代码 执行结果如下:代码说明 基类(父类) A首先来分析下4-25行的代码。4-9行:定义父类A的成员变量和函数(按照C++中的习惯,可以叫做方法),可以看出Lua语言中的函数是“一等公民”,是可以赋值给一个变量的。11-16行:相当于是构造函数,用来创建一个父类A的对象。18-20行:给父类A增加一个函数,待会在分析子类B的时候再说。22行:调用A:new()函数,创建一个类A的对象,赋值给变量objA。在A:new()函数中,关键是第13行代码:此时self等于A,就相当于是A.__index = A,这是合法的。因为函数的调用方式是A:new(),Lua的语法糖会把A作为第一个参数传递给new()函数的第一个隐藏参数self。然后执行14行的setmetatable(t, self),相当于把表t的元表设置为A。以上两行搞明白之后,23-24行的打印语句就简单了:23行:因为表objA中没有成员a,但是objA被设置了元表A,而且该元表A带有__index属性,该属性的值是表A自己,于是就到A中查找是否有成员a,于是就打印出:__index 属性的值,可以是一个表,可以是一个函数;只不过这里特殊一点:__index 设置为 A 自己;24行:查找函数的过程是一样的,找到元表A的__index属性的值,也就是表A自己中的funcA函数,然后调用,打印出:派生类(子类) B28-33行:定义了子类B,其实它也是一个对象。在创建函数A:new(t)中,参数t的值是:此时,self仍然是父类A,B的创建过程与objA的创建过程是一样的,只不过给参数t设置了子类B自己的成员变量和函数。所以,B的元表被设置为A(14行代码的功劳),当然了A的__index仍然被设置为A自己。关键是35行:objB = B:new(),得仔细唠唠。子类B并没有自己的new函数,但是类B(也是一个 table) 的元表被设置为A,并且A.__index = A,所以最终就找到了A中的new函数,也就是11-16行代码。进入这个函数中时,第一个隐藏参数self被设置为 B 了,因为函数调用形式是:B:new()。所以:13 行 self.__index = self  相当于设置 B.__index = B14 行 etmetatable(t, self) 相当于把表 t 的元表设置为 Bnew()函数返回之后,就把t赋值给objB。下面再看一下36-39行的打印语句:36行:objB中并没有成员a,但是objB的元表是B,而且B.__index = B,所以就到B中去查找a。虽然B中也没有a,但是B的元表是A,而且A.__index = A,所以就在A中找到了成员a,打印出:37行:objB中并没有成员b,但是objB的元表是B,而且B.__index = B,所以在B中找到了成员b,因此打印出:37和38行的查找过程是类似的,只不过换成了函数而已。子类对象操作自己的变量41行:objB:myadd(10)。查找myadd函数的过程与查找obj.a的过程是一样的,这里再唠叨一遍:1. objB 中并没有函数 myadd,但是 objB 的元表是 B,而且 B.__index = B,所以就到 B 中去查找 myadd;2. 虽然 B 中也没有 myadd,但是 B 的元表是 A,而且 A.__index = A,所以就在 A 中找到了函数 myadd;于是就调用了函数:而且self等于objB,因此函数体中就等于是:加法表达式中的objB.a的读取过程,上面已经描述过了,最终定位到的是父类A中的a,即:1。1 + 10 = 11,然后把11赋值给objB.a。在赋值操作中,被赋值的objB.a就不再是父类A中的那个a了!因为objB本质是一个table,给objB设置键值对的时候:1. 如果键已经存在了,那么就直接设置该键的值;2. 如果键不存在,那么 lua 会看它的元表中是否有 __newindex 字段(可以是一个table,也可以是一个函数);2-1. 如果有   __newindex 字段,那么就是调用 __newindex (如果是一个函数),或者在 __newindex 中添加键值对(如果是一个table);2-2. 如果没有 __newindex 字段,那么就直接在 objB 中存储该键值对;根据上面这个规则,就会设置objB.a = 11。明白以上这些之后,42和43行的打印语句就不复杂了。42行:objA最终找到的a是父类A中的成员a,打印出:objA.a = 1。43行:objB中自己已经有了成员a,所以打印出:objB.a = 11。继续往下继承有了上面的基础,再从子类B中派生出类C,C派生出类D... 都不是什么问题了,如下所示:感兴趣的读者可以自己测试一下。转载自https://iot.ofweek.com/2022-08/ART-132200-11000-30571498.html
  • [干货汇总] 基于Redis + Lua脚本的设计红包雨
    【摘要】 2018年,王思聪的冲顶大会,西瓜视频的百万英雄,再到映客的芝士超人,直播答题火爆全网。我服务的一家电商公司也加入了这次热潮,技术团队研发了直播答题功能。答题结束之后,红包会以红包雨的形式落下,用户点击屏幕上落下的红包,若抢到红包,红包会以现金的形式进入用户账户。 红包雨是一个典型的高并发场景,短时间内有海量请求访问服务端,技术团队为了让系统运行顺畅,抢红包采用了基于 Redis + Lua...本文分享自华为云社区《红包雨中:Redis 和 Lua 的邂逅》,作者:勇哥java实战分享。2018年,王思聪的冲顶大会,西瓜视频的百万英雄,再到映客的芝士超人,直播答题火爆全网。我服务的一家电商公司也加入了这次热潮,技术团队研发了直播答题功能。答题结束之后,红包会以红包雨的形式落下,用户点击屏幕上落下的红包,若抢到红包,红包会以现金的形式进入用户账户。红包雨是一个典型的高并发场景,短时间内有海量请求访问服务端,技术团队为了让系统运行顺畅,抢红包采用了基于 Redis + Lua 脚本的设计方案。​​1 整体流程我们分析下抢红包的整体流程 :运营系统配置红包雨活动总金额以及红包个数,提前计算出各个红包的金额并存储到 Redis 中;抢红包雨界面,用户点击屏幕上落下的红包,发起抢红包请求;TCP 网关接收抢红包请求后,调用答题系统抢红包 dubbo 服务,抢红包服务本质上就是执行 Lua 脚本,将结果通过 TCP 网关返回给前端;用户若抢到红包,异步任务会从 Redis 中 获取抢得的红包信息,调用余额系统,将金额返回到用户账户。2 红包 Redis 设计抢红包有如下规则:同一活动,用户只能抢红包一次 ;红包数量有限,一个红包只能被一个用户抢到。如下图,我们设计三种数据类型:运营预分配红包列表 ;队列元素 json 数据格式 :1{2 //红包编号3 redPacketId : '365628617880842241' 4 //红包金额5 amount : '12.21' 6}用户红包领取记录列表;队列元素 json 数据格式:1{2 //红包编号3 redPacketId : '365628617880842241'4 //红包金额5 amount : '12.21',6 //用户编号7 userId : '265628617882842248'8}用户红包防重 Hash 表;抢红包 Redis 操作流程 :通过 hexist 命令判断红包领取记录防重 Hash 表中用户是否领取过红包 ,若用户未领取过红包,流程继续;从运营预分配红包列表 rpop 出一条红包数据 ;操作红包领取记录防重 Hash 表 ,调用 HSET 命令存储用户领取记录;将红包领取信息 lpush 进入用户红包领取记录列表。抢红包的过程 ,需要重点关注如下几点 :执行多个命令,是否可以保证原子性 , 若一个命令执行失败,是否可以回滚;在执行过程中,高并发场景下,是否可以保持隔离性;后面的步骤依赖前面步骤的结果。Redis 支持两种模式 : 事务模式 和 Lua 脚本,接下来,我们一一展开。3 事务原理Redis 的事务包含如下命令:序号命令及描述1MULTI 标记一个事务块的开始。2EXEC 执行所有事务块内的命令。3DISCARD 取消事务,放弃执行事务块内的所有命令。4WATCH key [key ...] 监视一个(或多个) key ,如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断。5UNWATCH 取消 WATCH 命令对所有 key 的监视。事务包含三个阶段:事务开启,使用 MULTI , 该命令标志着执行该命令的客户端从非事务状态切换至事务状态 ;命令入队,MULTI 开启事务之后,客户端的命令并不会被立即执行,而是放入一个事务队列 ;执行事务或者丢弃。如果收到 EXEC 的命令,事务队列里的命令将会被执行 ,如果是 DISCARD 则事务被丢弃。下面展示一个事务的例子。1redis> MULTI 2OK3redis> SET msg "hello world"4QUEUED5redis> GET msg6QUEUED7redis> EXEC81) OK91) hello world这里有一个疑问?在开启事务的时候,Redis key 可以被修改吗?在事务执行 EXEC 命令之前 ,Redis key 依然可以被修改。在事务开启之前,我们可以 watch 命令监听 Redis key 。在事务执行之前,我们修改 key 值 ,事务执行失败,返回 nil 。通过上面的例子,watch 命令可以实现类似乐观锁的效果 。4 事务的ACID4.1 原子性原子性是指:一个事务中的所有操作,或者全部完成,或者全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚到事务开始前的状态,就像这个事务从来没有执行过一样。第一个例子:在执行 EXEC 命令前,客户端发送的操作命令错误,比如:语法错误或者使用了不存在的命令。1redis> MULTI2OK3redis> SET msg "other msg"4QUEUED5redis> wrongcommand ### 故意写错误的命令6(error) ERR unknown command 'wrongcommand' 7redis> EXEC8(error) EXECABORT Transaction discarded because of previous errors.9redis> GET msg10"hello world"在这个例子中,我们使用了不存在的命令,导致入队失败,整个事务都将无法执行 。第二个例子:事务操作入队时,命令和操作的数据类型不匹配 ,入队列正常,但执行 EXEC 命令异常 。1redis> MULTI 2OK3redis> SET msg "other msg"4QUEUED5redis> SET mystring "I am a string"6QUEUED7redis> HMSET mystring name "test"8QUEUED9redis> SET msg "after"10QUEUED11redis> EXEC121) OK132) OK143) (error) WRONGTYPE Operation against a key holding the wrong kind of value154) OK16redis> GET msg17"after"这个例子里,Redis 在执行 EXEC 命令时,如果出现了错误,Redis 不会终止其它命令的执行,事务也不会因为某个命令执行失败而回滚 。综上,我对 Redis 事务原子性的理解如下:命令入队时报错, 会放弃事务执行,保证原子性;命令入队时正常,执行 EXEC 命令后报错,不保证原子性;也就是:Redis 事务在特定条件下,才具备一定的原子性 。4.2 隔离性数据库的隔离性是指:数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。事务隔离分为不同级别 ,分别是:未提交读(read uncommitted)提交读(read committed)可重复读(repeatable read)串行化(serializable)首先,需要明确一点:Redis 并没有事务隔离级别的概念。这里我们讨论 Redis 的隔离性是指:并发场景下,事务之间是否可以做到互不干扰。我们可以将事务执行可以分为 EXEC 命令执行前和 EXEC 命令执行后两个阶段,分开讨论。EXEC 命令执行前在事务原理这一小节,我们发现在事务执行之前 ,Redis key 依然可以被修改。此时,可以使用 WATCH 机制来实现乐观锁的效果。EXEC 命令执行后因为 Redis 是单线程执行操作命令, EXEC 命令执行后,Redis 会保证命令队列中的所有命令执行完 。 这样就可以保证事务的隔离性。4.3 持久性数据库的持久性是指 :事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。Redis 的数据是否持久化取决于 Redis 的持久化配置模式 。没有配置 RDB 或者 AOF ,事务的持久性无法保证;使用了 RDB模式,在一个事务执行后,下一次的 RDB 快照还未执行前,如果发生了实例宕机,事务的持久性同样无法保证;使用了 AOF 模式;AOF 模式的三种配置选项 no 、everysec 都会存在数据丢失的情况 。always 可以保证事务的持久性,但因为性能太差,在生产环境一般不推荐使用。综上,redis 事务的持久性是无法保证的 。4.4 一致性一致性的概念一直很让人困惑,在我搜寻的资料里,有两类不同的定义。维基百科我们先看下维基百科上一致性的定义:Consistency ensures that a transaction can only bring the database from one valid state to another, maintaining database invariants: any data written to the database must be valid according to all defined rules, including constraints, cascades, triggers, and any combination thereof. This prevents database corruption by an illegal transaction, but does not guarantee that a transaction is correct. Referential integrity guarantees the primary key – foreign key relationship.在这段文字里,一致性的核心是“约束”,“any data written to the database must be valid according to all defined rules ”。如何理解约束?这里引用知乎问题 如何理解数据库的内部一致性和外部一致性,蚂蚁金服 OceanBase 研发专家韩富晟回答的一段话:“约束”由数据库的使用者告诉数据库,使用者要求数据一定符合这样或者那样的约束。当数据发生修改时,数据库会检查数据是否还符合约束条件,如果约束条件不再被满足,那么修改操作不会发生。关系数据库最常见的两类约束是“唯一性约束”和“完整性约束”,表格中定义的主键和唯一键都保证了指定的数据项绝不会出现重复,表格之间定义的参照完整性也保证了同一个属性在不同表格中的一致性。“ Consistency in ACID ”是如此的好用,以至于已经融化在大部分使用者的血液里了,使用者会在表格设计的时候自觉的加上需要的约束条件,数据库也会严格的执行这个约束条件。所以事务的一致性和预先定义的约束有关,保证了约束即保证了一致性。我们细细品一品这句话: This prevents database corruption by an illegal transaction, but does not guarantee that a transaction is correct。写到这里可能大家还是有点模糊,我们举经典转账的案例。我们开启一个事务,张三和李四账号上的初始余额都是1000元,并且余额字段没有任何约束。张三给李四转账1200元。张三的余额更新为 -200 , 李四的余额更新为2200。从应用层面来看,这个事务明显不合法,因为现实场景中,用户余额不可能小于 0 , 但是它完全遵循数据库的约束,所以从数据库层面来看,这个事务依然保证了一致性。Redis 的事务一致性是指:Redis 事务在执行过程中符合数据库的约束,没有包含非法或者无效的错误数据。我们分三种异常场景分别讨论:执行 EXEC 命令前,客户端发送的操作命令错误,事务终止,数据保持一致性;执行 EXEC 命令后,命令和操作的数据类型不匹配,错误的命令会报错,但事务不会因为错误的命令而终止,而是会继续执行。正确的命令正常执行,错误的命令报错,从这个角度来看,数据也可以保持一致性;执行事务的过程中,Redis 服务宕机。这里需要考虑服务配置的持久化模式。无持久化的内存模式:服务重启之后,数据库没有保持数据,因此数据都是保持一致性的;RDB / AOF 模式: 服务重启后,Redis 通过 RDB / AOF 文件恢复数据,数据库会还原到一致的状态。综上所述,在一致性的核心是约束的语意下,Redis 的事务可以保证一致性。《设计数据密集型应用》这本书是分布式系统入门的神书。在事务这一章节有一段关于 ACID 的解释:Atomicity, isolation, and durability are properties of the database,whereas consistency (in the ACID sense) is a property of the application. The application may rely on the database’s atomicity and isolation properties in order to achieve consistency, but it’s not up to the database alone. Thus, the letter C doesn’t really belong in ACID.原子性,隔离性和持久性是数据库的属性,而一致性(在 ACID 意义上)是应用程序的属性。应用可能依赖数据库的原子性和隔离属性来实现一致性,但这并不仅取决于数据库。因此,字母 C 不属于 ACID 。很多时候,我们一直在纠结的一致性,其实就是指符合现实世界的一致性,现实世界的一致性才是事务追求的最终目标。为了实现现实世界的一致性,需要满足如下几点:保证原子性,持久性和隔离性,如果这些特征都无法保证,那么事务的一致性也无法保证;数据库本身的约束,比如字符串长度不能超过列的限制或者唯一性约束;业务层面同样需要进行保障 。4.5 总结我们通常称 Redis 为内存数据库 , 不同于传统的关系数据库,为了提供了更高的性能,更快的写入速度,在设计和实现层面做了一些平衡,并不能完全支持事务的 ACID。Redis 的事务具备如下特点:保证隔离性;无法保证持久性;具备了一定的原子性,但不支持回滚;一致性的概念有分歧,假设在一致性的核心是约束的语意下,Redis 的事务可以保证一致性。另外,在抢红包的场景下, 因为每个步骤需要依赖上一个步骤返回的结果,需要通过 watch 来实现乐观锁 ,从工程角度来看, Redis 事务并不适合该业务场景。5 Lua 脚本5.1 简介“ Lua ” 在葡萄牙语中是“月亮”的意思,1993年由巴西的 Pontifical Catholic University 开发。该语言的设计目的是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能。Lua 脚本可以很容易的被 C/C ++ 代码调用,也可以反过来调用 C/C++ 的函数,这使得 Lua 在应用程序中可以被广泛应用。不仅仅作为扩展脚本,也可以作为普通的配置文件,代替 XML, Ini 等文件格式,并且更容易理解和维护。Lua 由标准 C 编写而成,代码简洁优美,几乎在所有操作系统和平台上都可以编译,运行。一个完整的 Lua 解释器不过 200 k,在目前所有脚本引擎中,Lua 的速度是最快的。这一切都决定了 Lua 是作为嵌入式脚本的最佳选择。Lua 脚本在游戏领域大放异彩,大家耳熟能详的《大话西游II》,《魔兽世界》都大量使用 Lua 脚本。Java 后端工程师接触过的 api 网关,比如 Openresty ,Kong 都可以看到 Lua 脚本的身影。从 Redis 2.6.0 版本开始, Redis内置的 Lua 解释器,可以实现在 Redis 中运行 Lua 脚本。使用 Lua 脚本的好处 :减少网络开销。将多个请求通过脚本的形式一次发送,减少网络时延。原子操作。Redis会将整个脚本作为一个整体执行,中间不会被其他命令插入。复用。客户端发送的脚本会永久存在 Redis 中,其他客户端可以复用这一脚本而不需要使用代码完成相同的逻辑。Redis Lua 脚本常用命令:序号命令及描述1EVAL script numkeys key [key ...] arg [arg ...] 执行 Lua 脚本。2EVALSHA sha1 numkeys key [key ...] arg [arg ...] 执行 Lua 脚本。3SCRIPT EXISTS script [script ...] 查看指定的脚本是否已经被保存在缓存当中。4SCRIPT FLUSH 从脚本缓存中移除所有脚本。5SCRIPT KILL 杀死当前正在运行的 Lua 脚本。6SCRIPT LOAD script 将脚本 script 添加到脚本缓存中,但并不立即执行这个脚本。5.2 EVAL 命令命令格式:1EVAL script numkeys key [key ...] arg [arg ...]说明:script是第一个参数,为 Lua 5.1脚本;第二个参数numkeys指定后续参数有几个 key;key [key ...],是要操作的键,可以指定多个,在 Lua 脚本中通过KEYS[1], KEYS[2]获取;arg [arg ...],参数,在 Lua 脚本中通过ARGV[1], ARGV[2]获取。简单实例:1redis> eval "return ARGV[1]" 0 100 2"100"3redis> eval "return {ARGV[1],ARGV[2]}" 0 100 10141) "100"52) "101"6redis> eval "return {KEYS[1],KEYS[2],ARGV[1]}" 2 key1 key2 first second71) "key1"82) "key2"93) "first"104) "second"下面演示下 Lua 如何调用 Redis 命令 ,通过redis.call()来执行了 Redis 命令 。1redis> set mystring 'hello world'2OK3redis> get mystring4"hello world"5redis> EVAL "return redis.call('GET',KEYS[1])" 1 mystring6"hello world"7redis> EVAL "return redis.call('GET','mystring')" 08"hello world"5.3 EVALSHA 命令使用 EVAL 命令每次请求都需要传输 Lua 脚本 ,若 Lua 脚本过长,不仅会消耗网络带宽,而且也会对 Redis 的性能造成一定的影响。思路是先将 Lua 脚本先缓存起来 , 返回给客户端 Lua 脚本的 sha1 摘要。 客户端存储脚本的 sha1 摘要 ,每次请求执行 EVALSHA 命令即可。EVALSHA 命令基本语法如下:redis> EVALSHA sha1 numkeys key [key ...] arg [arg ...] 实例如下:redis> SCRIPT LOAD "return 'hello world'" "5332031c6b470dc5a0dd9b4bf2030dea6d65de91" redis> EVALSHA 5332031c6b470dc5a0dd9b4bf2030dea6d65de91 0 "hello world"5.4 事务 VS Lua 脚本从定义上来说, Redis 中的脚本本身就是一种事务, 所以任何在事务里可以完成的事, 在脚本里面也能完成。 并且一般来说, 使用脚本要来得更简单,并且速度更快。因为脚本功能是 Redis 2.6 才引入的, 而事务功能则更早之前就存在了, 所以 Redis 才会同时存在两种处理事务的方法。不过我们并不打算在短时间内就移除事务功能, 因为事务提供了一种即使不使用脚本, 也可以避免竞争条件的方法, 而且事务本身的实现并不复杂。-- https://redis.io/Lua 脚本是另一种形式的事务,他具备一定的原子性,但脚本报错的情况下,事务并不会回滚。Lua 脚本可以保证隔离性,而且可以完美的支持后面的步骤依赖前面步骤的结果。综上,Lua 脚本是抢红包场景最优的解决方案。但在编写 Lua 脚本时,要注意如下两点:为了避免 Redis 阻塞,Lua 脚本业务逻辑不能过于复杂和耗时;仔细检查和测试 Lua 脚本 ,因为执行 Lua 脚本具备一定的原子性,不支持回滚。6 实战准备我选择 Redisson 3.12.0 版本作为 Redis 的客户端,在 Redisson 源码基础上做一层薄薄的封装。创建一个 PlatformScriptCommand 类, 用来执行 Lua 脚本。// 加载 Lua 脚本 String scriptLoad(String luaScript); // 执行 Lua 脚本 Object eval(String shardingkey, String luaScript, ReturnType returnType, List<Object> keys, Object... values); // 通过 sha1 摘要执行Lua脚本 Object evalSha(String shardingkey, String shaDigest, List<Object> keys, Object... values);这里为什么我们需要添加一个 shardingkey 参数呢 ?因为 Redis 集群模式下,我们需要定位哪一个节点执行 Lua 脚本。public int calcSlot(String key) { if (key == null) { return 0; } int start = key.indexOf('{'); if (start != -1) { int end = key.indexOf('}'); key = key.substring(start+1, end); } int result = CRC16.crc16(key.getBytes()) % MAX_SLOT; log.debug("slot {} for {}", result, key); return result; }7 抢红包脚本客户端执行 Lua 脚本后返回 json 字符串。用户抢红包成功{ "code":"0", //红包金额 "amount":"7.1", //红包编号 "redPacketId":"162339217730846210" }用户已领取过{ "code":"1" }用户抢红包失败{ "code":"-1" }Redis Lua 中内置了 cjson 函数,用于 json 的编解码。-- KEY[1]: 用户防重领取记录 local userHashKey = KEYS[1]; -- KEY[2]: 运营预分配红包列表 local redPacketOperatingKey = KEYS[2]; -- KEY[3]: 用户红包领取记录 local userAmountKey = KEYS[3]; -- KEY[4]: 用户编号 local userId = KEYS[4]; local result = {}; -- 判断用户是否领取过 if redis.call('hexists', userHashKey, userId) == 1 then result['code'] = '1'; return cjson.encode(result); else -- 从预分配红包中获取红包数据 local redPacket = redis.call('rpop', redPacketOperatingKey); if redPacket then local data = cjson.decode(redPacket); -- 加入用户ID信息 data['userId'] = userId; -- 把用户编号放到去重的哈希,value设置为红包编号 redis.call('hset', userHashKey, userId, data['redPacketId']); -- 用户和红包放到已消费队列里 redis.call('lpush', userAmountKey, cjson.encode(data)); -- 组装成功返回值 result['redPacketId'] = data['redPacketId']; result['code'] = '0'; result['amount'] = data['amount']; return cjson.encode(result); else -- 抢红包失败 result['code'] = '-1'; return cjson.encode(result); end end脚本编写过程中,难免会有疏漏,如何进行调试?个人建议两种方式结合进行。编写 junit 测试用例 ;从 Redis 3.2 开始,内置了 Lua debugger(简称LDB), 可以使用 Lua debugger 对 Lua 脚本进行调试。8 异步任务在 Redisson 基础上封装了两个类 ,简化开发者的使用成本。RedisMessageConsumer : 消费者类,配置监听队列名,以及对应的消费监听器String groupName = "userGroup"; String queueName = "userAmountQueue"; RedisMessageQueueBuilder buidler = redisClient.getRedisMessageQueueBuilder(); RedisMessageConsumer consumer = new RedisMessageConsumer(groupName, buidler); consumer.subscribe(queueName, userAmountMessageListener); consumer.start();RedisMessageListener : 消费监听器,编写业务消费代码public class UserAmountMessageListener implements RedisMessageListener { @Override public RedisConsumeAction onMessage(RedisMessage redisMessage) { try { String message = (String) redisMessage.getData(); // TODO 调用用户余额系统 // 返回消费成功 return RedisConsumeAction.CommitMessage; }catch (Exception e) { logger.error("userAmountService invoke error:", e); // 消费失败,执行重试操作 return RedisConsumeAction.ReconsumeLater; } } }9 写到最后"纸上得来终觉浅, 绝知此事要躬行" 。学习 Redis Lua 过程中,查询了很多资料,一个例子一个例子的实践,收获良多。非常坦诚的讲 , 写这篇文章之前,我对 Redis Lua 有很多想当然的理解,比如 Redis 的事务不能回滚就让我惊讶不已。所以当面对自己不熟悉的知识点时,不要轻易下结论,以谦卑的心态去学习,才是一个工程师需要的心态。同时,没有任何一项技术是完美的,在设计和编码之间,有这样或者那样的平衡,这才是真实的世界。
  • [技术干货] 何使用Vim搭建Lua开发环境详解
    前言SpaceVim 是一个模块化的 Vim IDE,针对 lua 这一语言的支持主要依靠 lang#lua 模块以及与之相关的其它模块。 的这篇文章主要介绍如何使用 SpaceVim 搭建 lua 的开发环境,侧重介绍跟 lua 开发相关使用技巧。 在阅读这篇文章之前,可以先来看看使用 Vim 搭建基础的开发环境,对语言相关以外的功能有一个大致的了解。使用 Vim 搭建基本开发环境主要介绍如何使用 SpaceVim 搭建基础的开发环境安装在入门指南里,介绍了不同系统安装 SpaceVim 的步骤。在安装过程中还是存在一些问题,比如颜色主题看上去和官网不一致,出现各种字体乱码。 安装 SpaceVim 最理想的环境是 neovim + nerdfont + 一个支持真色的终端模拟器。neovim:建议查阅其wiki,获取安装步骤nerdfont: Linux 或 Mac 下 SpaceVim 安装脚本会自动下载字体,windows 用户需要自行下载 nerd 字体并安装一款支持真色的终端,如果不能启用真色,可以在配置文件里禁用 SpaceVim 真色:[options]  enable_guicolors = false基本配置SpaceVim 的配置文件有两种,一种是全局配置文件,另外一种是项目专属配置文件。我们可以这样理解,在全局配置文件里,主要设置一些常规的选项和模块, 比如 shell 模块、tags 模块。项目专属配置文件则通常用来配置跟当前项目相关的模块及选项,比如对于 python 项目,可以在项目专属配置文件里启用 lang#python 模块。这样操作的好处在于,当处理多个不同语言项目是,不需要频繁更新配置文件,也不用担心载入过多的冗余插件,和无关的语言模块。基本使用首先,需要了解下 SpaceVim 启动后几个界面元素:顶部标签栏、底部状态栏。可以看到,标签栏通常只有一个,主要用来列出已经打开的文件或者是标签页。文件及窗口操作SpaceVim 会在状态栏展示各个窗口的编号,可以使用快捷键 SPC + 数字 快速跳到对应的窗口,在顶部标签了,会列出当前已经打开的文件或者标签裂变, 可以使用快捷键 Leader + 数字 快速跳到对应的文件。在这里默认的 Leader 是 \ 键。安装模块SpaceVim 初次安装时默认并未启用相关语言模块。首先需要启用 lang#lua 模块, 通过快捷键 SPC f v d 打开配置文件,添加:[[layers]] name = "lang#lua"启用 lang#lua 模块后,在打开 lua 文件时,就可以使用语言专属快捷键,这些快捷键都是以 SPC l 为前缀的。代码自动补全autocomplete 模块为 SpaceVim 提供了自动补全功能, 该模块会根据当前环境自动在多种补全引擎之间选择合适的, 默认的补全引擎有:deoplete、neocomplete、ycm、asyncomplete 以及 neocomplcache。 几种自动补全引擎当中,要数 deoplete 的体验效果最好。语法检查checkers 模块为 SpaceVim 提供了语法检查的功能,该模块默认已经载入。该模块默认使用 neomake 这一异步语法检查工具。对于 luac 的支持,是通过异步调用 luac 命令来完成的。工程文件跳转SpaceVim 自带工程管理插件,可以识别项目根目录,自动跳转alternate文件。快速运行在编辑 lua 文件时,可以快速运行当前文件,这个功能有点类似于 vscode 的 code runner 插件,默认的快捷键是 SPC l r。按下后, 会在屏幕下方打开一个插件窗口,运行的结果会被展示在窗口内。于此同时,光标并不会跳到该插件窗口,避免影响编辑。在这里需要说明下, 这一功能是根据当前文件的路径调用相对应的 lua 命令。因此,在执行这个快捷键之前,应当先保存一下该文件。代码格式化lua 代码格式化,主要依赖 format 模块,同时需要安装相关的后台命令 luaformatter:[[layers]] name = "format"安装 luaformatter:luarocks install formatter交互式编程在编辑 lua 文件时,可通过快捷键 SPC l s i 启动 lua -i 交互窗口,之后使用快捷键将代码发送至解释器。默认快捷键都以 SPC l s 为前缀。
  • [技术干货] Lua基础之运算符的使用示例
    Lua分为主要三类运算符,分别是算术运算符,关系运算符,逻辑运算符,还有特殊运算符。1、算术运算符有: + (加)   -(减)   *(乘)  /(除)   %(取模)   ^(求幂)   -(符号)来看一个例子:test6.luanum1 = 4 ;num2 = 2 ;print(num1+num2);print(num1-num2);print(num1*num2);print(num1/num2);print(num1%num2);print(num1^num2);print(-num1);解释运行:lua test6.lua结果:6282.0016.0-42、关系运算符有: ==(等于)   ~=(不等于)  >(大于)  <(小于)  >=(大于等于)  <=(小于等于)当比较结果成立返回true,否则返回false来看一个例子: test6.luanum = 1 ;num1 = 1;num2 = 2;num3 = 3; num4 = 4;num5 = 5;num6 = 6;ret = num1 == num2 ;print(ret);ret = num1 ~= num2 ; print(ret);ret = num3 > num2 ;print(ret);ret = num4 < num3 ; print(ret);ret = num5 >= num6 ;print(ret);ret = num1 <= num ;print(ret);解释运行:lua test6.lua结果:falsetruetruefalsefalsetrue3、逻辑运算符有: and(逻辑与)   or(逻辑或)   not(逻辑非)假设有A和B,如果A and B, 为真返回A,为假返回B 假设有A和B ,如果A or  B , 为真返回A ,为假返回B假设有A和B ,如果 not A  , 为真返回true ,为假返回false来看一个例子:test6.luanum1 = 100 ;num2 = 200 ;num3 = 0 ;ret = num1 and num3 ; print(ret);ret = num1 or num3 ; print(ret);ret = not num1 ;print(ret);ret = not(num1 and num2);print(ret);解释运行:lua test6.lua结果:0100falsefalse4、特殊运算符 .. 表示连接两个字符串     #表示返回字符串或者表的长度(注意,这里的长度不包括'\0')案例: test6.lua解释运行: lua test6.lua--定义两个字符串str1 = "Hello";str2 = "World";--连接两个字符串ret = str1..str2;print(ret);--获取连接后的字符串的长度str_length = #ret ;print(str_length);结果:HelloWorld10当然,lua的运算符也是有优先级的,优先级从高到底的顺序为:^  not   -(负号)  *  /  +  -  ..  <  >  <=  >=   ~=  ==  and or
  • [技术行业前沿] 【数据库系列】华为云GuassDB(for Redis)发布全新版本推出:Lua脚本和SSL连接加密
    >摘要:9月8日,华为云GuassDB(for Redis)正式推出全新版本。新版本内核带来性能提升、无损升级、慢日志统计等多维度产品体验,同时推出Lua脚本和SSL连接加密两大重要功能,让业务设计更加灵活,公网访问更安全。本文分享自华为云社区《[华为云GuassDB(for Redis)发布全新版本,两大核心特性正式亮相](https://bbs.huaweicloud.com/forum/thread-153489-1-1.html?utm_source=csdn&utm_medium=bbs-ex&utm_campaign=database&utm_content=content)》,作者:GaussDB 数据库。 9月8日,华为云GuassDB(for Redis)正式推出全新版本。新版本内核带来性能提升、无损升级、慢日志统计等多维度产品体验,同时推出Lua脚本和SSL连接加密两大重要功能,让业务设计更加灵活,公网访问更安全。 ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/202201/28/150733eft0q09uz0z39cwo.png) GaussDB(for Redis)是华为云推出的企业级分布式KV数据库,它完全兼容Redis协议,提供丰富的数据类型,同时基于云原生存储计算分离架构,在成本、可靠性等方面为企业带来全新价值,此番推出的两大功能特性更是为企业业务发展带来全新体验。 # Lua脚本功能:业务设计更灵活 GaussDB(for Redis)推出的Lua脚本功能,支持用户预设逻辑,组合执行多条命令,让业务设计更加灵活。使用方法上,GaussDB(for Redis)的Lua脚本功能与开源Redis保持完全兼容。用户可以将一组命令编入Lua脚本,交给GaussDB(for Redis)执行,从而实现原子操作的效果。 相比开源Redis Cluster,GaussDB(for Redis)的Lua脚本功能更为优秀: - **脚本执行不易引发请求阻塞**:这是由于GaussDB(for Redis)实例内部有着更细粒度的数据分片,同时每个分片都有多线程执行命令的能力。 - **消除“脚本复制”的副作用**:开源Redis主从脚本复制让时间模块、随机命令等功能受限,GaussDB(for Redis)内核采用全新实现,并无此类限制,业务设计更轻松。 - **强一致保障**:在高并发场景,GaussDB(for Redis)提供数据强一致保障,业务多点访问不会发生脏读。 根据以往经验,Lua脚本在一些业务场景起着关键作用,例如:**订单系统**要求用户余额不出现负数,库存系统要避免商品超卖……它们都需要使用Lua脚本来确保“查询+扣减”的原子性语义。GaussDB(for Redis)将Lua脚本与强一致特性结合,给业务设计带来极大灵活性。 # SSL连接加密功能:公网访问更安全 GaussDB(for Redis)提供的SSL连接加密功能,支持客户端使用SSL协议连接数据库,提升公网访问安全性。用户只需从华为云控制台下载证书,并使用支持SSL协议的客户端(例如Redis-cli 6.0),即可与实例建立安全可靠连接。 通过控制台,用户还可以随时开启或禁用SSL连接模式。当连接模式发生切换,旧连接会被断开以确保实例网络安全。 ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/202201/28/150931mx4htvon8rx6y4ii.png) 相比开源Redis 6.0 SSL,GaussDB(for Redis)保持兼容并带来以下优势: - **性能更好**:开启SSL后的性能损失更小,约15%;而开源Redis损失更多。 - **多线程完美兼容**:开启SSL不影响多线程并发能力,而开源Redis的SSL与多线程存在二选一冲突。 在一些场景中,业务有从公网甚至海外访问数据库的需求。此时,对于核心数据存储,全链路的安全保障尤为重要,新版GaussDB(for Redis)能够极大提升公网访问安全性。 # GuassDB(for Redis)核心价值 作为云原生KV数据库,GaussDB(for Redis)有着全面领先于开源Redis的能力: - **成本降低75%以上**:全量数据落盘,容量利用率高 - **高稳定性**:即使N-1节点故障,全量数据依旧可用 - **高可靠性**:数据三副本冗余存储,无丢失风险 - **强一致性**:强一致性保障,多点访问无脏读问题 - **强抗写能力**:全部节点可写,多线程设计 - **强扩展能力**:节点分钟级、容量秒级扩容 目前GaussDB(for Redis)已经凭借出色的产品实力在游戏系统、电商平台、推荐系统、社交媒体、物联网等众多企业级应用场景中发挥巨大作用。新推出的Lua脚本和SSL连接加密两大功能特性,更是为企业数字化转型注入了全新动力。想体验更多产品能力,[欢迎前往华为云官网](https://www.huaweicloud.com/product/gaussdbforredis.html)。
  • [技术干货] 【云数据库知识】Nosql数据库--Cassandra lua driver
    本文的前提是已经对lua、openResty、Cassandra有了一定了解,如果需要了解相关资料可以参考如下连接lua 入门教程https://www.runoob.com/lua/lua-tutorial.htmlopenResty官网http://openresty.org/cn/由于Cassandra官方并没有提供lua的官方驱动,所以本文使用开源项目lua-cassandra 来连接cassandra,关于lua-cassandra的资料如下:git: https://github.com/thibaultcha/lua-cassandrawiki: https://thibaultcha.github.io/lua-cassandra/注意:lua连接cassandra 的程序是需要运行在openResty环境中的,所以请提前搭建好openResty环境以及安装好lua-cassandra 驱动库lua-cassandra 分为两个单机版和集群版,由于单纯使用单机版没有意义,本文以集群版驱动为例使用引入驱动库以及连接cassandra集群12345678910111201    --引入驱动库    02    local Cluster = require "resty.cassandra.cluster"                    03    --初始化集群信息并通过连接点连接集群    04    local cluster, err = Cluster.new {    05        shm = "cassandra", -- defined by the lua_shared_dict directive    06        contact_points = {"127.0.0.1"},    07        keyspace = "keyspace_name"    08    }    09    if not cluster then    10        ngx.log(ngx.ERR, "could not create cluster: ", err)    11        return ngx.exit(500)    12    end创建keyspace和table12345671    --注意keyspace和table 参数处需要使用转义符,不然会报错    2    local create_keyspace, err = cluster:execute "CREATE KEYSPACE ks_test WITH replication = {\'class\':\'SimpleStrategy\', \'replication_factor\' : 1};"    3    4    local use_keyspace, err = cluster:execute "use kb_test;"    5    6    7    local create_table, err = cluster:execute "CREATE TABLE tb_test (k int,p int,s int static,v int,PRIMARY KEY (k, p));"select 查询12345671    local rows, err = cluster:execute "SELECT * FROM tb_test"    2    if not rows then    3        ngx.log(ngx.ERR, "could not retrieve users: ", err)    4        return ngx.exit(500)    5    end    6    7    ngx.say("users: ", #rows)batch查询12345678910111201    local res, err = cluster:batch({    02        {"INSERT INTO a(id, age) VALUES(?, 7)", {123}},    03        {"UPDATE a SET age = 2 WHERE id = ?", {3}},    04        {"UPDATE a SET age = 3 WHERE id = ?", {4}}    05        }, {    06            logged = false    07        })    08    09    if not res then    10        ngx.log(ngx.ERR, "could not execute batch: ", err)    11        ngx.exit(500)    12    enddrop表操作12345671    local use_keyspace, err = cluster:execute "use ks_test;"    2    3    local del_test ,err = cluster:execute "drop table tb_test;"    4    if not del_test then    5        ngx.log(ngx.ERR, "could not execute drop: ", err)    6        ngx.exit(500)    7    endallow filter 查询1234567891001    local use_keyspace, err = cluster:execute "use ks_test;"    02    03    local allowfiltering ,err = cluster:execute "SELECT * FROM  tb_test where age=7 allow filtering;"    04    if not allowfiltering then    05        ngx.log(ngx.ERR, "could not execute drop: ", err)    06        ngx.exit(500)    07    end    08    for idx, var in pairs(allowfiltering) do    09    ngx.say(string.format("列名,数据:%s:%s", idx, var))    10    end写入数据12345671    local use_keyspace, err = cluster:execute "use ks_test;"    2    3    local usettl ,err = cluster:execute "INSERT INTO tb_test(id, age) VALUES(666, 7) using ttl 20;"    4    if not usettl then    5        ngx.log(ngx.ERR, "could not execute drop: ", err)    6        ngx.exit(500)    7    end
  • [技术干货] 用sysbench来测试MySQL的性能的教程
    鉴于最近对OpenStack的兴趣和激情,我想要确保我可以做恰当的系统性能评估。我主要开始转向sysbench,是因为它带来一系列丰富的针对不同层面的测试(通过 -test=option 来获知) ,包括有:    fileio - 文件 I/O测试    cpu - CPU系能测试    memory - 内存功能速度测试    threads - 线程子系统系能测试    mutex - 互斥性能测试正如你所看到的的,sysbench将让你的心思着重放在你的硬件和基础架构的许多基础组件上,例如你的磁盘子系统,以及你的CPUs和内存。有一个额外的选项,是用来执行对MySQL的模拟压力测试,并且当我在0.5版本中没有看到以上的所提到的可支持测试项时候,我很惊讶,尽管它可以用来显示“联机事务处理 – 联机事务处理测试”。-test=oltp到底怎么回事?? 这个列表来自于sysbench最近的发布版本0.5——如果你仅仅是在这个版本上使用它,使用 Frederic Descamps(感谢lefred!)所提供的 包。如果你用的(系统)是EPEL,Ubuntu 14.04 或者 Debian 7,就可以使用0.4.12版本(检查sysbench的版本 -version)。那么,测试类型OLTP再也不会出现。这个版本做了什么呢?我使劲挠头,直到我询问Percona IRC才知道在0.5版本里的标准OLTP测试类型被替换成了不一样的语法,它使用传递参数到sysbench,替换掉了引用脚本写入lua的方式。它的优点是,现在你有了一个接口,可以写入你特定的负载测试中(提供你了解的lua(接口),用起来也不难)。如果你还想要运行预定义负载测试,它们还是存在的,但是你必须把它们作为RPM的一部分去安装,或者直接拷贝到你的系统。幸运的是,如果你使用lefred提供的包,你会发现下面这些lua脚本(这里使用了截止至2014年8月4号的Amazon ami): [root@pxc-control ~]# ls -l /usr/share/doc/sysbench/tests/db/ total 44 -rw-r--r-- 1 root root 3585 Sep 7 2012 common.lua -rw-r--r-- 1 root root 340 Sep 7 2012 delete.lua -rw-r--r-- 1 root root 830 Sep 7 2012 insert.lua -rw-r--r-- 1 root root 2925 Sep 7 2012 oltp.lua -rw-r--r-- 1 root root 342 Sep 7 2012 oltp_simple.lua -rw-r--r-- 1 root root 425 Sep 7 2012 parallel_prepare.lua -rw-r--r-- 1 root root 343 Sep 7 2012 select.lua -rw-r--r-- 1 root root 3964 Sep 7 2012 select_random_points.lua -rw-r--r-- 1 root root 4066 Sep 7 2012 select_random_ranges.lua -rw-r--r-- 1 root root 343 Sep 7 2012 update_index.lua -rw-r--r-- 1 root root 552 Sep 7 2012 update_non_index.lua所以诀窍(如果你希望这么叫它)不是通过单一词语传递--test指令,而是通过传递一个lua脚本的完整路径。下面首先是旧的方式(sysbench 0.4.12来自EPEL库):--test=oltp --oltp-test-mode=complex下面是新的方式(sysbench 0.5): --test=/usr/share/doc/sysbench/tests/db/insert.lua下面是我在一个3个节点的PXC集群上跑haproxy,做一个仅插入的测试类型,下面是我传递给sysbench的全部命令:[root@pxc-control ~]# cat sys_haproxy.sh #!/bin/bash sysbench --test=/usr/share/doc/sysbench/tests/db/insert.lua --mysql-host=pxc-control --mysql-port=9999 --mysql-user=sysbench-haproxy --mysql-password=sysbench-haproxy --mysql-db=sbtest --mysql-table-type=innodb --oltp-test-mode=complex --oltp-read-only=off --oltp-reconnect=on --oltp-table-size=1000000 --max-requests=100000000 --num-threads=3 --report-interval=1 --report-checkpoints=10 --tx-rate=24 $1下面是insert.lua脚本的内容:[root@pxc-control ~]# cat /usr/share/doc/sysbench/tests/db/insert.lua pathtest = string.match(test, "(.*/)") or "" dofile(pathtest .. "common.lua") function thread_init(thread_id) set_vars() end function event(thread_id) local table_name local i local c_val local k_val local pad_val table_name = "sbtest".. sb_rand_uniform(1, oltp_tables_count) if (oltp_auto_inc) then i = 0 else i = sb_rand_uniq(1, oltp_table_size) end k_val = sb_rand(1, oltp_table_size) c_val = sb_rand_str([[ ###########-###########-###########-###########-###########-###########-###########-###########-###########-###########]]) pad_val = sb_rand_str([[ ###########-###########-###########-###########-###########]]) rs = db_query("INSERT INTO " .. table_name .. " (id, k, c, pad) VALUES " .. string.format("(%d, %d, '%s', '%s')",i, k_val, c_val, pad_val)) end我比较喜欢sysbench 0.5的一个地方(超过lua 接口,肯定的!)是它现在带来一个配置项 –report-interval option(我通常都是设置为 =1),以便在脚本运行时你可以定时看到输出信息。不要等到运行结束是再得到反馈!这里有个用sysbench 0.5的测试样例,通过本地hapoxy实例运行插入数据动作以及向在同一个PXC(Percona XtrDB Cluster)集群上的三个节点写数据动作,例如OpenStack Trove会做: [root@pxc-control ~]# ./sys_haproxy.sh run sysbench 0.5: multi-threaded system evaluation benchmark Running the test with following options: Number of threads: 3 Report intermediate results every 1 second(s) Random number generator seed is 0 and will be ignored Threads started! [ 1s] threads: 3, tps: 0.00, reads/s: 0.00, writes/s: 1099.28, response time: 9.86ms (95%) [ 2s] threads: 3, tps: 0.00, reads/s: 0.00, writes/s: 973.02, response time: 10.77ms (95%) [ 3s] threads: 3, tps: 0.00, reads/s: 0.00, writes/s: 1181.01, response time: 6.23ms (95%) [ 4s] threads: 3, tps: 0.00, reads/s: 0.00, writes/s: 1103.00, response time: 6.77ms (95%) 
  • [技术干货] 关于sysbench自定义lua脚本性能测试,这些你知道吗?
    一、前言        我们经常有这样的需求,表定义或者表定义数据已经有了,需要评估某些语句的的性能,比如有如下表:                DROP TABLE IF EXISTS `testiptable`;                CREATE TABLE `testiptable` (                  `id` int(11) NOT NULL AUTO_INCREMENT,                  `ipaddr` varchar(20) DEFAULT NULL,                  `rectime` varchar(30) DEFAULT NULL,                  PRIMARY KEY (`id`)                ) ENGINE = InnoDB DEFAULT CHARSET = utf8;        此时我们需要评估某个实例写入的性能,该如何进行呢?        目前业界常用的mysql性能评估工具,最流行、对mysql最亲和的就是sysbench。sysbench除了自带的lua脚本,比如oltp_read_write.lua、oltp_insert.lua等等,当然,sysbench还支持今天我们重点要介绍的自定义脚本。        insert的关键的语句如下:        insert into testiptable(ipaddr, rectime) values(‘192.168.10.121’, ‘2020-03-29 09:20:18’),(‘192.168.10.121’, ‘2020-03-29 10:20:18’);        注:测试写入性能的时候,会进行多线程和批量insert。        sysbench执行,一般函数:thread_init、event、thread_done,这三个函数分别对应线程初始化、线程执行事件、线程结束执行操作。Sysbench执行时会按照thread_init-> event-> thread_done顺序进行。二、脚本定义2.1 线程初始化和线程结束脚本        function thread_init(thread_id)                   drv = sysbench.sql.driver()                    con = drv:connect()        end       注:thread_init函数最重要的就是驱动连接。        function thread_done()                    con:disconnect()        end        注:thread_done函数主要是执行完成       函数内部没有local标识的变量,脚本内都可以使用,如con和drv后续的脚本内部都可以使用。2.2 线程执行事件        sysbench.cmdline.options = {                 bulk_inserts = {"Number of values to to insert", 1}        }        sysbench.cmdline.options,可以在执行脚本时将输入参数传入脚本。我们在写入时需要进行批量insert, 通过--bulk_inserts=xx来设置批量修改。        注意:大括号中前面是说明,后面是默认值。如果不输入参数,脚本自动按照默认值执行。除了能传递数字,还可以传递bool型的true和false,如:                    sysbench.cmdline.options = {                             skip_trx= {"skip transaction", false}                    }        我们来看一下数据insert部分函数是如何实现的:         function execute_inserts(thread_id)                local ipaddr                local rectime                local sql = "insert " ..tablename.." (ipaddr,rectime) values"                for i=1,sysbench.opt.bulk_inserts do                        ipaddr = string.format("192.168.%d.%d",sysbench.rand.pareto(10,40),sysbench.rand.pareto(1,250))                        rectime = os.date("%Y-%m-%d %H:%M:%S",os.time())                        sql = sql .. string.format("('%s','%s')",ipaddr,rectime)                        if ( i ~= sysbench.opt.bulk_inserts) then                                sql = sql .. ","                        end                end                con:query(sql)        end        注:con:query(sql),该语句作用为执行数据库语句。        function event(thread_id)                execute_inserts(thread_id)         end三、脚本执行        sysbench ./insert.lua --mysql-user=root --mysql-password='Test_123' --mysql-db=test --report-interval=1 --events=0 --time=180 --mysql-host=192.168.0.139 --bulk_inserts=100 run        注:如果不输入--bulk_inserts=100,脚本默认--bulk_inserts=1。   四、总结        Sysbench自定义脚本,主要要实现event逻辑,可以通过sysbench.cmdline.options列表进行命令行传参。
  • Cassandra lua driver
    本文的前提是已经对lua、openResty、Cassandra有了一定了解,如果需要了解相关资料可以参考如下连接lua 入门教程https://www.runoob.com/lua/lua-tutorial.htmlopenResty官网http://openresty.org/cn/由于Cassandra官方并没有提供lua的官方驱动,所以本文使用开源项目lua-cassandra 来连接cassandra,关于lua-cassandra的资料如下:git: https://github.com/thibaultcha/lua-cassandrawiki: https://thibaultcha.github.io/lua-cassandra/注意:lua连接cassandra 的程序是需要运行在openResty环境中的,所以请提前搭建好openResty环境以及安装好lua-cassandra 驱动库lua-cassandra 分为两个单机版和集群版,由于单纯使用单机版没有意义,本文以集群版驱动为例使用引入驱动库以及连接cassandra集群01    --引入驱动库     02    local Cluster = require "resty.cassandra.cluster"                     03    --初始化集群信息并通过连接点连接集群     04    local cluster, err = Cluster.new {     05        shm = "cassandra", -- defined by the lua_shared_dict directive     06        contact_points = {"127.0.0.1"},     07        keyspace = "keyspace_name"     08    }     09    if not cluster then     10        ngx.log(ngx.ERR, "could not create cluster: ", err)     11        return ngx.exit(500)     12    end创建keyspace和table1    --注意keyspace和table 参数处需要使用转义符,不然会报错     2    local create_keyspace, err = cluster:execute "CREATE KEYSPACE ks_test WITH replication = {\'class\':\'SimpleStrategy\', \'replication_factor\' : 1};"     3     4    local use_keyspace, err = cluster:execute "use kb_test;"     5     6     7    local create_table, err = cluster:execute "CREATE TABLE tb_test (k int,p int,s int static,v int,PRIMARY KEY (k, p));"select 查询1    local rows, err = cluster:execute "SELECT * FROM tb_test"     2    if not rows then     3        ngx.log(ngx.ERR, "could not retrieve users: ", err)     4        return ngx.exit(500)     5    end     6     7    ngx.say("users: ", #rows)batch查询01    local res, err = cluster:batch({     02        {"INSERT INTO a(id, age) VALUES(?, 7)", {123}},     03        {"UPDATE a SET age = 2 WHERE id = ?", {3}},     04        {"UPDATE a SET age = 3 WHERE id = ?", {4}}     05        }, {     06            logged = false     07        })     08     09    if not res then     10        ngx.log(ngx.ERR, "could not execute batch: ", err)     11        ngx.exit(500)     12    enddrop表操作1    local use_keyspace, err = cluster:execute "use ks_test;"     2     3    local del_test ,err = cluster:execute "drop table tb_test;"     4    if not del_test then     5        ngx.log(ngx.ERR, "could not execute drop: ", err)     6        ngx.exit(500)     7    endallow filter 查询01    local use_keyspace, err = cluster:execute "use ks_test;"     02     03    local allowfiltering ,err = cluster:execute "SELECT * FROM  tb_test where age=7 allow filtering;"     04    if not allowfiltering then     05        ngx.log(ngx.ERR, "could not execute drop: ", err)     06        ngx.exit(500)     07    end     08    for idx, var in pairs(allowfiltering) do     09    ngx.say(string.format("列名,数据:%s:%s", idx, var))     10    end写入数据1    local use_keyspace, err = cluster:execute "use ks_test;"     2     3    local usettl ,err = cluster:execute "INSERT INTO tb_test(id, age) VALUES(666, 7) using ttl 20;"     4    if not usettl then     5        ngx.log(ngx.ERR, "could not execute drop: ", err)     6        ngx.exit(500)     7    end