搬砖攻略
当前位置: 九游会俱乐部-九游会官网登录入口 > 行业资讯 > 搬砖攻略准确的讲,redis 外交事务涵盖两种方式 : 外交事务方式 和 lua javascript。
先说结论:
redis 的外交事务方式具有如下表所示特点:
确保隔绝性;难以确保无毒性;具有了一定的氢原子性,但不支持初始化;连续性的概念有分歧,假设在连续性的核心是约束的语意下,redis 的外交事务能确保连续性。但 lua javascript更具有实用情景,它是另一种形式的外交事务,他具有一定的氢原子性,但javascript收起的情况下,外交事务并不能初始化。lua javascript能确保隔绝性,而且能完美的支持后面的步骤依赖前面步骤的结果。
lua javascript方式的身影几乎无处不在,比如分布式锁、延迟数组、抢红包等情景。
一文讲透 redis 事务 (事务模式 vs lua 脚本)-九游会俱乐部
redis 的外交事务涵盖如下表所示指示:
序号
指示及描述
1
multi 标记一种外交事务块的开始。
2
exec 继续执行所有外交事务块内的指示。
3
discard 取消外交事务,放弃继续执行外交事务块内的所有指示。
4
watch key [key ...] 监视一种(或多个) key ,假如在外交事务继续执行以后那个(或这些) key 被其他指示所改动,那么外交事务将被打断。
5
unwatch 取消 watch 指示对所有 key 的监视。
外交事务涵盖三个阶段:
外交事务打开,采用 multi , 该指示标志着继续执行该指示的应用程序从非外交事务状态切换至外交事务状态 ;指示归队,multi 打开外交事务之后,应用程序的指示并不能被立即继续执行,而是放入一种外交事务数组 ;继续执行外交事务或是丢弃。假如收到 exec 的指示,外交事务数组里的指示将会被继续执行 ,假如是 discard 则外交事务被丢弃。下面展示一种外交事务的范例。
redis> multi ok redis> set msg "hello world" queued redis> get msg queued redis> exec 1) ok 1) hello world这里有一种疑问?在打开外交事务的时候,redis key 能被修正吗?
在外交事务继续执行 exec 指示以后 ,redis key 依然能被修正。
在外交事务打开以后,我能 watch 指示监听 redis key 。在外交事务继续执行以后,我修正 key 值 ,外交事务继续执行失败,返回 nil 。
通过上面的范例,watch 指示能实现类似乐观锁的效果 。
氢原子性是指:一种外交事务中的所有操作方式,或是全部完成,或是全部不完成,不能结束在中间某个环节。外交事务在继续执行过程中发生严重错误,会被初始化到外交事务开始前的状态,就像那个外交事务从来没有继续执行过一样。
第一种范例:
在继续执行 exec 指示前,应用程序发送的操作方式指示严重错误,比如:语法严重错误或是采用了不存在的指示。
redis> multi ok redis> set msg "other msg" queued redis> wrongcommand ### 故意写严重错误的指示 (error) err unknown command wrongcommand redis> exec (error) execabort transaction discarded because of previous errors. redis> get msg "hello world"在那个范例中,我采用了不存在的指示,导致归队失败,整个外交事务都将难以继续执行 。
第二个范例:
外交事务操作方式归队时,指示和操作方式的信息类型不匹配 ,归数组正常,但继续执行 exec 指示异常 。
redis> multi ok redis> set msg "other msg" queued redis> set mystring "i am a string" queued redis> hmset mystring name "test" queued redis> set msg "after" queued redis> exec 1) ok 2) ok 3) (error) wrongtype operation against a key holding the wrong kind of value 4) ok redis> get msg "after"那个范例里,redis 在继续执行 exec 指示时,假如出现了严重错误,redis 不能终止其它指示的继续执行,外交事务也不能因为某个指示继续执行失败而初始化 。
综上,我对 redis 外交事务氢原子性的理解如下表所示:
指示归队时收起, 会放弃外交事务继续执行,确保氢原子性;指示归队时正常,继续执行 exec 指示后收起,不确保氢原子性;也就是:redis 外交事务在特定条件下,才具有一定的氢原子性 。
资料库的隔绝性是指:资料库允许多个mammalian外交事务同时对其信息进行读写和修正的能力,隔绝性能防止多个外交事务mammalian继续执行时由于交叉继续执行而导致信息的不一致。
外交事务隔绝分为不同级别 ,分别是:
未提交读(read uncommitted)提交读(read committed)可重复读(repeatable read)串行化(serializable)首先,需要明确一点:redis 并没有外交事务隔绝级别的概念。这里我讨论 redis 的隔绝性是指:mammalian情景下,外交事务之间是否能做到互不干扰。
我能将外交事务继续执行能分为 exec 指示继续执行前和 exec 指示继续执行后两个阶段,分开讨论。
exec 指示继续执行前在外交事务原理这一小节,我发现在外交事务继续执行以后 ,redis key 依然能被修正。此时,能采用 watch 机制来实现乐观锁的效果。
exec 指示继续执行后因为 redis 是单线程继续执行操作方式指示, exec 指示继续执行后,redis 会确保指示数组中的所有指示继续执行完 。 这样就能确保外交事务的隔绝性。
资料库的无毒性是指 :外交事务处理结束后,对信息的修正就是永久的,即便系统故障也不能丢失。
redis 的信息是否长久化取决于 redis 的长久化布局方式 。
没有布局 rdb 或是 aof ,外交事务的无毒性难以确保;采用了 rdb方式,在一种外交事务继续执行后,下一次的 rdb 快照还未继续执行前,假如发生了实例宕机,外交事务的无毒性同样难以确保;采用了 aof 方式;aof 方式的三种布局选项 no 、everysec 都会存在信息丢失的情况 。always 能确保外交事务的无毒性,但因为性能太差,在生产环境一般不推荐采用。综上,redis 外交事务的无毒性是难以确保的 。
连续性的概念一直很让人困惑,在我搜寻的资料里,有两类不同的表述。
维基百科我先看下维基百科上连续性的表述:
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 。
很多时候,我一直在纠结的连续性,其实就是指合乎现实生活世界的连续性,现实生活世界的连续性才是外交事务追求的最终目标。
为了实现现实生活世界的连续性,需要满足如下表所示几点:
确保氢原子性,无毒性和隔绝性,假如这些特征都难以确保,那么外交事务的连续性也难以确保;资料库本身的约束,比如字符串长度不能超过列的限制或是唯一性约束;业务层面同样需要进行保障 。我通常称 redis 为内存资料库 , 不同于传统的关系资料库,为了提供了更高的性能,更快的写入速度,在设计和实现层面做了一些平衡,并不能完全支持外交事务的 acid。
redis 的外交事务具有如下表所示特点:
确保隔绝性;难以确保无毒性;具有了一定的氢原子性,但不支持初始化;连续性的概念有分歧,假设在连续性的核心是约束的语意下,redis 的外交事务能确保连续性。从工程角度来看,假设外交事务操作方式中每个步骤需要依赖上一种步骤返回的结果,则需要通过 watch 来实现乐观锁 。
lua 由标准 c 编写而成,代码简洁优美,几乎在所有操作方式系统和平台上都能编译,运行。lua javascript能很容易的被 c/c 代码调用,也能反过来调用 c/c 的函数,这使得 lua 在应用程序中能被广泛应用。
lua javascript在游戏领域大放异彩,大家耳熟能详的《大话西游ii》,《魔兽世界》都大量采用 lua javascript。java 后端工程师接触过的 api 网关,比如 openresty ,kong 都能看到 lua javascript的身影。
从 redis 2.6.0 版本开始, redis内置的 lua 解释器,能实现在 redis 中运行 lua javascript。
采用 lua javascript的好处 :
减少网络开销。将多个请求通过javascript的形式一次发送,减少网络时延。氢原子操作方式。redis会将整个javascript作为一种整体继续执行,中间不能被其他指示插入。复用。应用程序发送的javascript会永久存在 redis 中,其他应用程序能复用这一javascript而不需要采用代码完成相同的逻辑。redis lua javascript常用指示:
序号
指示及描述
1
eval script numkeys key [key ...] arg [arg ...] 继续执行 lua javascript。
2
evalsha sha1 numkeys key [key ...] arg [arg ...] 继续执行 lua javascript。
3
script exists script [script ...] 查看指定的javascript是否已经被保存在缓存当中。
4
script flush 从javascript缓存中移除所有javascript。
5
script kill 杀死当前正在运行的 lua javascript。
6
script load script 将javascript script 添加到javascript缓存中,但并不立即继续执行那个javascript。
指示格式:
eval script numkeys key [key ...] arg [arg ...]说明:
script是第一种参数,为 lua 5.1javascript;第二个参数numkeys指定后续参数有几个 key;key [key ...],是要操作方式的键,能指定多个,在 lua javascript中通过keys[1], keys[2]获取;arg [arg ...],参数,在 lua javascript中通过argv[1], argv[2]获取。简单实例:
redis> eval "return argv[1]" 0 100 "100" redis> eval "return {argv[1],argv[2]}" 0 100 101 1) "100" 2) "101" redis> eval "return {keys[1],keys[2],argv[1]}" 2 key1 key2 first second 1) "key1" 2) "key2" 3) "first" 4) "second"下面演示下 lua 如何调用 redis 指示 ,通过redis.call()来继续执行了 redis 指示 。
redis> set mystring hello world ok redis> get mystring "hello world" redis> eval "return redis.call(get,keys[1])" 1 mystring "hello world" redis> eval "return redis.call(get,mystring)" 0 "hello world"采用 eval 指示每次请求都需要传输 lua javascript ,若 lua javascript过长,不仅会消耗网络带宽,而且也会对 redis 的性能造成一定的影响。
思路是先将 lua javascript先缓存起来 , 返回给应用程序 lua javascript的 sha1 摘要。 应用程序存储javascript的 sha1 摘要 ,每次请求继续执行 evalsha 指示即可。
evalsha 指示基本语法如下表所示:
redis> evalsha sha1 numkeys key [key ...] arg [arg ...]实例如下表所示:
redis> script load "return hello world" "5332031c6b470dc5a0dd9b4bf2030dea6d65de91" redis> evalsha 5332031c6b470dc5a0dd9b4bf2030dea6d65de91 0 "hello world"从表述上来说, redis 中的javascript本身就是一种外交事务, 所以任何在外交事务里能完成的事, 在javascript里面也能完成。 并且一般来说, 采用javascript要来得更简单,并且速度更快。
因为javascript功能是 redis 2.6 才引入的, 而外交事务功能则更早以后就存在了, 所以 redis 才会同时存在两种处理外交事务的方法。
不过我并不打算在短时间内就移除外交事务功能, 因为外交事务提供了一种即使不采用javascript, 也能避免竞争条件的方法, 而且外交事务本身的实现并不复杂。
-- https://redis.io/
lua javascript是另一种形式的外交事务,他具有一定的氢原子性,但javascript收起的情况下,外交事务并不能初始化。lua javascript能确保隔绝性,而且能完美的支持后面的步骤依赖前面步骤的结果。
lua javascript方式的身影几乎无处不在,比如分布式锁、延迟数组、抢红包等情景。
不过在编写 lua javascript时,要注意如下表所示两点:
为了避免 redis 阻塞,lua javascript业务逻辑不能过于复杂和耗时;仔细检查和测试 lua javascript ,因为继续执行 lua javascript具有一定的氢原子性,不支持初始化。