Transactions

Introduction

为了保证单条命令的原子性,Redis使用了单线程,命令都是顺序依次执行(unlink是个例外,是异步的)。

事务保证了将多条命令作为一个单元执行。

Using Transactions

事务所有命令:

127.0.0.1:6379> help @transactionsDISCARD -summary: Discard all commands issued after MULTIsince: 2.0.0EXEC -summary: Execute all commands issued after MULTIsince: 1.2.0MULTI -summary: Mark the start of a transaction blocksince: 1.2.0UNWATCH -summary: Forget about all watched keyssince: 2.2.0WATCH key [key ...]summary: Watch the given keys to determine execution of the MULTI/EXEC blocksince: 2.2.0

MULTI相当于begin transaction, MULTI后面的命令会存入命令队列,EXEC一次性执行队列中所有命令,DISCARD放弃队列中所有命令。

可以用2个client来演示事务的隔离性。

multi中不能再嵌套multi,也就是事务不能嵌套。

对于语法错误和数据类型操作错误,不会影响事务中其它的操作,或者说不会引发rollback。其实redis就不支持rollback:

127.0.0.1:6379> set key01 1
OK
127.0.0.1:6379> set key02 a
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> incrby key01 1
QUEUED
127.0.0.1:6379> incrby key02 1
QUEUED
127.0.0.1:6379> exec
1) (integer) 2
2) (error) ERR value is not an integer or out of range
127.0.0.1:6379> get key01
"2"

rollback的开销很大,避免rollback也保证了最小延迟和最大吞吐。

对于系统错误,如内存不够,Redis会保证不会出现部分写和不一致性。

传统数据库交易中的命令是马上执行,因此消耗了系统资源;Redis中是将所有命令放入队列,然后一次性执行。

扩展阅读

  • Redis Clients Handling
  • Transactions

Optimistic Concurrency Control

Optimistic Concurrency Control是指监控1个或多个key的变化,如果改变了就放弃事务。这通常是由于你已经读取了这个值。

Optimistic Concurrency Control是通过WATCH和UNWATCH实现的。WATCH必须在事务开始前,事务结束后,会自动UNWATCH。

WATCH命令不是全局的,只影响到调用它的客户端。

client1> set key01 200
OK
client1> watch key01
OK
client1> multi
OK
client1> incrby key01 1
QUEUED
client2> get key01
"200"
client2> decr key01
(integer) 199
client1> exec
(nil)
client1> get key01
"199"

EXEC何时会失败呢? 这里的失败可认为是DISCARD。当命令有语法错误,或WATCH的key被更改时,都会失败。但如果只是操作错误的数据类型,如对字符串加1,则只是忽略这个命令而已。

Object Storage with Hashes

Introduction

用String和Hash都可以存取对象存储,但前者不能增量存取,后者可以单独操作filed,如读写,测试存在,但TTL是针对整个key来设的。

Storing a Simple Object

Hash的多field加value的结构非常类似JSON,而且灵活的schema避免了alter table操作。

HSET可实现JSON结构。相关命令包括HGET, HMGET和HGETALL(阻塞操作,建议小于100 field时用),HSCAN(非阻塞操作)。

小数据集时用HMGET方便,大数据集时建议HSCAN。

HSET时,会自动创建不存在的field,相关包括HEXISTS, HSETNX。

HINCR,HINCRBYFLOAT可对field执行数字操作。

HDEL可删除filed。

Storing Complex Objects

复杂对象,在关系型数据库里就是主从表,在NOSQL中就是带子文档的JSON。

可以用3种方式实现:

  1. 多个Hash
  2. 多个Hash+Set
  3. 单个Hash

Redis推荐方式3,完全平的方式。好处是简单,支持原子操作,无需事务。实现非常类似于层级文件系统到对象存储文件名的映射,用不同的filed name即可实现。例如假设B为子文档:

hmset key B:C value B:D value

不好的地方是删除子文档时需要删除多个field。还有就是设计上的考虑,子文档放在那个父文档中,后续子文档移动时会涉及很多操作。还有就是对象会比拆开存要大。

第2种方式即将子文档单独存为Hash,它和父文档的关系需要通过应用体现,当然在key命名上也可以提示。这种方式的好处为,可独立存放,可扩展,可单独设置TTL。不好的地方是一个对象需要多个key表示,关系需要维护。然后对象所有key需要存在一个shard,这是后话。

Use Case: Inventory Control

Use Case Overview

售票系统,每个用户可以买多张票,有3场比赛,柔道资格赛,男子100m决赛和女子4x100接力预赛。

Inventory Control

流程参考这个图:

在此流程中,减库存和生成订单需放在一个事务中。

示例程序为uc02-inventory-control/inventory.py

示例程序演示了3个测试场景,依次由简单到复杂:

  • 票不够(仅检查票)
  • 票够钱不够,这里假设用户为joan时,钱总是不够(检查票和钱)
  • 持票超过30秒,票返回库存

7个用户, 数据结构如下:

127.0.0.1:6379> scan 0 match uc02:customer* count 30000
1) "0"
2) 1) "uc02:customer:jim"2) "uc02:customer:joan"3) "uc02:customer:jamie"4) "uc02:customer:amy"5) "uc02:customer:mary"6) "uc02:customer:bill"7) "uc02:customer:fred"127.0.0.1:6379> type uc02:customer:jim
hash127.0.0.1:6379> hgetall uc02:customer:jim
1) "id"
2) "jim"
3) "customer_name"
4) "jim somebody"

有3个比赛的票,数据结构如下, 注意其中的余票数和票价:

127.0.0.1:6379> scan 0 match uc02:event:* count 30000
1) "0"
2) 1) "uc02:event:320-GHI-921"2) "uc02:event:123-ABC-723"3) "uc02:event:737-DEF-911"
127.0.0.1:6379> type uc02:event:320-GHI-921
hash
127.0.0.1:6379> hgetall uc02:event:320-GHI-9211) "sku"2) "320-GHI-921"3) "name"4) "Womens Judo Qualifying"5) "disabled_access"6) "False"7) "medal_event"8) "False"9) "venue"
10) "Nippon Budokan"
11) "category"
12) "Martial Arts"
13) "capacity"
14) "14471"
15) "available:General"
16) "500"
17) "price:General"
18) "15.25"
19) "held:General"
20) "0"127.0.0.1:6379> hgetall uc02:event:123-ABC-7231) "sku"2) "123-ABC-723"3) "name"4) "Men's 100m Final"5) "disabled_access"6) "True"7) "medal_event"8) "True"9) "venue"
10) "Olympic Stadium"
11) "category"
12) "Track & Field"
13) "capacity"
14) "60102"
15) "available:General"
16) "10"
17) "price:General"
18) "25.0"127.0.0.1:6379> hgetall uc02:event:737-DEF-9111) "sku"2) "737-DEF-911"3) "name"4) "Women's 4x100m Heats"5) "disabled_access"6) "True"7) "medal_event"8) "False"9) "venue"
10) "Olympic Stadium"
11) "category"
12) "Track & Field"
13) "capacity"
14) "60102"
15) "available:General"
16) "10"
17) "price:General"
18) "19.5"
19) "held:General"
20) "0"

为防止在订票期间库存发生改变,程序使用WATCH监控了相关的库存(例如uc02:event:123-ABC-723)。

订单的数据结构如下,和库存之间通过sku关联:

127.0.0.1:6379> scan 0 match uc02:sales_order:* count 30000
1) "0"
2) 1) "uc02:sales_order:CUJXZN-ITBCOW"2) "uc02:sales_order:RSNKID-SOCIFH"
127.0.0.1:6379> type uc02:sales_order:CUJXZN-ITBCOW
hash
127.0.0.1:6379> hgetall uc02:sales_order:CUJXZN-ITBCOW1) "order_id"2) "CUJXZN-ITBCOW"3) "customer"4) "bill"5) "tier"6) "General"7) "qty"8) "5"9) "cost"
10) "125.0"
11) "event_sku"
12) "123-ABC-723"
13) "ts"
14) "1606361972"

Python代码建议好好看看。

Reservations

场景2中加入了信用卡验证的环节。因此售票过程分多个事务完成。

首先扣票是第一个事务,扣票成功后会生成临时的持票记录。第二个事务围为信用卡验证环节,如果信用卡验证失败,就将持有票返回票库,成功则生成订单,清空持票记录。

通过调试python(python -m pdb)程序,我们得到持票的数据结构, 包括扣票数量,票级别和扣票时间,field名字的后缀时随机字符串:

127.0.0.1:6379> scan 0 match uc02:ticket_hold* count 30000
1) "0"
2) 1) "uc02:ticket_hold:737-DEF-911"
127.0.0.1:6379> type uc02:ticket_hold:737-DEF-911
hash
127.0.0.1:6379> hgetall uc02:ticket_hold:737-DEF-911
1) "qty:KUEBNB-BKFHGK"
2) "5"
3) "tier:KUEBNB-BKFHGK"
4) "General"
5) "ts:KUEBNB-BKFHGK"
6) "1606368660"

对应的库存记录中,在held:General字段也会记录扣票数量。

Expiration of Reservations

场景3模拟的是自动将持票退回库存的场景。比如购票过程中网断了。程序自动生成了3条持票记录,然后进入循环,每个1秒检查持票记录,超过30秒后自动将票返回票库。直到没有持票就退出循环。注意,这里并没有设TTL。

持票定义如下,可以看到到期时间一次为14秒后,8秒后和立刻:

  holds = {'qty:VPIR6X': 3, 'tier:VPIR6X': tier, 'ts:VPIR6X': int(cur_t - 16),'qty:B1BFG7': 5, 'tier:B1BFG7': tier, 'ts:B1BFG7': int(cur_t - 22),'qty:UZ1EL0': 7, 'tier:UZ1EL0': tier, 'ts:UZ1EL0': int(cur_t - 30)}

因此输出如下:

uc02:event:320-GHI-921
== Create ticket holds, expire > 30 sec, return tickets to inventory
320-GHI-921, Available:485, Reservations:['3', '5', '7']
320-GHI-921, Available:492, Reservations:['3', '5', None]
320-GHI-921, Available:492, Reservations:['3', '5', None]
320-GHI-921, Available:492, Reservations:['3', '5', None]
320-GHI-921, Available:492, Reservations:['3', '5', None]
320-GHI-921, Available:492, Reservations:['3', '5', None]
320-GHI-921, Available:492, Reservations:['3', '5', None]
320-GHI-921, Available:492, Reservations:['3', '5', None]
320-GHI-921, Available:492, Reservations:['3', '5', None]
320-GHI-921, Available:497, Reservations:['3', None, None]
320-GHI-921, Available:497, Reservations:['3', None, None]
320-GHI-921, Available:497, Reservations:['3', None, None]
320-GHI-921, Available:497, Reservations:['3', None, None]
320-GHI-921, Available:497, Reservations:['3', None, None]
320-GHI-921, Available:497, Reservations:['3', None, None]
320-GHI-921, Available:500, Reservations:[None, None, None]

最后的建议是,当持票记录多时,建议从Hash改为Sorted Set,然后将时间戳作为value。这样方便提取最先过期的持票记录。
None, None]
320-GHI-921, Available:497, Reservations:[‘3’, None, None]
320-GHI-921, Available:497, Reservations:[‘3’, None, None]
320-GHI-921, Available:497, Reservations:[‘3’, None, None]
320-GHI-921, Available:500, Reservations:[None, None, None]


最后的建议是,当持票记录多时,建议从Hash改为Sorted Set,然后将时间戳作为value。这样方便提取最先过期的持票记录。

Redis RU101课程 Introduction to Redis Data Structures 第3周学习笔记相关推荐

  1. Redis RU101课程 Introduction to Redis Data Structures 第5周学习笔记

    Geospatial 在这一周,经纬度的概念非常重要,请参考 在任何一本地图上,你都可以看到经线和纬线纵横交织,它们的作用就是为了确定一个地点或地区的位置. 地球像陀螺那样斜着身子旋转着,南极和北极就 ...

  2. Redis RU101课程 Introduction to Redis Data Structures 第2周学习笔记

    Capped Collections & Set Operations Cardinality & Capped Collections 首先谈到了基数(cardinality)的概念 ...

  3. Introduction to 3D Game Programming with DirectX 12 学习笔记之 --- 第十五章:第一人称摄像机和动态索引...

    Introduction to 3D Game Programming with DirectX 12 学习笔记之 --- 第十五章:第一人称摄像机和动态索引 原文:Introduction to 3 ...

  4. Introduction to 3D Game Programming with DirectX 12 学习笔记之 --- 第十三章:计算着色器(The Compute Shader)...

    Introduction to 3D Game Programming with DirectX 12 学习笔记之 --- 第十三章:计算着色器(The Compute Shader) 原文: Int ...

  5. Introduction to 3D Game Programming with DirectX 12 学习笔记之 --- 第十九章:法线贴图

    Introduction to 3D Game Programming with DirectX 12 学习笔记之 --- 第十九章:法线贴图 原文:Introduction to 3D Game P ...

  6. 机电传动控制课程第一周学习笔记

    机电传动课程第一周学习笔记 本周的学习内容主要是第一章绪论和第二章机电传动系统的动力学基础,结合课程学习和预习复习回顾内容如下: 1.绪论:学习了机电传动控制目的与任务.发展历程和我们该如何学习这门课 ...

  7. Python_Example_ Data Structures and Algorithm Analysis 学习/示例

    Author: 楚格 2018-11-19   19:05:11 IDE: Pycharm2018.02   Python 3.7 KeyWord :  Data Structures and Alg ...

  8. ASP.NET Core分布式项目实战(课程介绍,MVP,瀑布与敏捷)--学习笔记

    任务1:课程介绍 课程目标: 1.进一步理解 ASP.NET Core 授权认证框架.MVC 管道 2.掌握 Oauth2,结合 Identity Sercer4 实现 OAuth2 和 OpenID ...

  9. 基于Problem Solving with Algorithms and Data Structures using Python的学习记录(4)——Recursion

    4.1.目标 本章的目标如下: 要理解可能难以解决的复杂问题有一个简单的递归解决方案. 学习如何递归地写出程序. 理解和应用递归的三个定律. 将递归理解为一种迭代形式. 实现问题的递归公式化. 了解计 ...

最新文章

  1. ios NSLayoutConstraint
  2. mongodb spring 超时时间_spring data mongodb 配置遇到的几个问题
  3. python小课骗局-谈谈学风变python小课感想,菜鸟表示真的挺简单
  4. 反欺诈技术揭秘-设备指纹VS关系网络模型 此博文包含图片 (2017-05-12 10:23:52)转载▼ 标签: 设备指纹 关系网络 反欺诈 神经网络模型 分类: 风控 文章来源:网络(经整合梳理
  5. 100c之31:哥德巴赫猜想
  6. linux shell for while if case break continue echo test 及算术运算符 关系运算符 布尔运算符 逻辑运算符 字符串运算符 文件测试运算符
  7. Atom与markdown
  8. C++客户端面经总结
  9. 网页设计常用字体(转)
  10. 华为交换机eth口作用_基于华为交换机的基本配置——以Eth-Trunk链路聚合技术为例.pdf...
  11. 华三模拟器启动设备失败【启动设备MSR36-20_1失败】
  12. 支付宝20积分兑换15天会员
  13. 【Selenium自动化测试】鼠标与键盘操作
  14. Qt设计一个给图像打掩膜的界面
  15. 195. 中文小说出海资料整理
  16. C语言计算圆柱的表面积 体积
  17. Angular 4入门教程系列:1:HelloWorld
  18. 射击训练模拟设备有哪些?室内靶场使用设备介绍
  19. (跟我一起来学区块链(1.7))之 区块链的价值
  20. 2-5.基金的信息披露

热门文章

  1. 进程监控命令ps和top(五)
  2. 算法:最小生成树(prime,kruskal,floyd)
  3. JetBrains 各 IDE 通用快捷键总结(包括 InteliJ idea,PyCharm,RubyMine,GoLand 等)
  4. 页面换肤(点击事件)
  5. ubuntu安装SSH的方法
  6. compareTo()方法
  7. Git开发--git merge request 步骤
  8. Ubuntu16 xfce 安装中文输入法
  9. 土圭垚㙓数学课(三)四元数
  10. EasyRecovery全新版本文件数据恢复软件功能下载介绍