NoSQL
概述
NoSQL(Not Only SQL),意为“不仅仅是SQL”,泛指非关系形的数据库。
NoSQL不依赖业务逻辑方式存储,而以简单的key-value模式存储,因此大大的增加了数据库的扩展能力
- 不遵循SQL标准
- 不支持ACID(原子性、一致性、隔离性、持久性)
- 远超于SQL的性能
适用场景
- 对数据高并发的读写
- 海量数据的读写
- 对数据高可扩展性的读写
不适用场景
- 需要事务支持
- 基于sql的结构化查询存储,处理复杂的关系,需要即席查询
- 用不着sql的和用了sql也不行的情况,考虑用NoSQL
Redis概述与安装
概述
- Redis是一个开源的key-value存储系统
- 和Memcached类似,它支持存储的value类型相对更多,包括string、list、set、zset和hash
- 这些数据类型都支持push/pop、add/remove及取交集并集和差集及更丰富的操作,而且这些操作都是原子性的
- 在此基础上,Redis支持各种不同方式的排序
- 与memcache一样,为了保证效率,数据都是缓存在内存中
- 区别是Redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件
- 并且在此基础上实现了**master-slave(主从)**同步
介绍
- 端口为6379
- 默认16个数据库,类似数组下标从0开始,初始默认使用0号库
- 使用命令
select <dbid>
来切换数据库。如:select 8
- 统一密码管理,所有库相同密码
- dbsize查看当前数据库的key的数量
- flushdb清空当前库
- flushall通杀所有库
Redis是单线程+多路IO复用技术
多路复用:是指使用一个线程来检查多个文件描述符(Socket)的就绪状态,比如调用select和poll函数,传入多个文件描述符,如果有一个文件描述符就绪,则返回;否则阻塞直到超时。得到就绪状态后进行真正的操作可以在同一个线程里执行,也可以启动线程执行(比如使用线程池)
串行 VS 多线程+锁(memcache) VS 单线程+多路IO复用(Redis)
与Memcache的不同:
- 支持多数据类型
- 支持持久化
- 单线程+多路IO复用
应用场景
配合关系型数据库做高速缓存
- 高频次,热门访问的数据,降低数据量IO
- 分布式架构,做session共享
多样的数据结构存储持久化数据
- 最新N个数据 通过list实现按自然时间排序的数据
- 排行榜,Top N 通过zset(有序集合)
- 时效性的数据,比如手机验证码 Expire 过期
- 计数器,秒杀 原子性,自增方法INCR、DECR
- 去除大量数据中的重复数据 利用set集合
- 构建队列 利用list集合
- 发布订阅消息系统 pub/sub 模式
安装
centos7下输入以下命令一键安装docker
官方安装指南
$ curl -fsSL get.docker.com -o get-docker.sh
$ sudo sh get-docker.sh --mirror Aliyun
使用以下命令在Docker Hub中搜索redis仓库
$ docker search redis
并拉取镜像
$ docker pull redis
完成后在根目录/
下创建配置文件
$ mkdir conf
$ cd conf
$ vim redis.conf
修改redis.conf
protected-mode no
requirepass 你的redis密码
运行redis容器
$ docker pull redis
docker run -p 6379:6379 --name test-redis -v $PWD/conf/redis.conf:/etc/redis/redis.conf -v $PWD/data:/redis/data -d redis redis-server /etc/redis/redis.conf
#参数说明
-p 7000:6379:将容器的 6379 端口映射到主机的 7000 端口(第一个物理机端口,第二个容器端口)。
-v $PWD/conf/redis.conf:/etc/redis/redis.conf:映射配置文件。
-v $PWD/data:/redis/data:映射数据文件。
--requirepass "sast_forever":redis连接密码。
-d: 后台运行容器,并返回容器ID
redis-server /etc/redis/redis.conf:使用指定的配置文件启动redis
常用五大数据类型
键(key)
常用命令
下表给出了与 Redis 键相关的基本命令:
序号 | 命令及描述 |
---|---|
1 | DEL key 该命令用于在 key 存在时删除 key。 |
2 | DUMP key 序列化给定 key ,并返回被序列化的值。 |
3 | EXISTS key 检查给定 key 是否存在。 |
4 | EXPIRE key seconds 为给定 key 设置过期时间,以秒计。 |
5 | EXPIREAT key timestamp EXPIREAT 的作用和 EXPIRE 类似,都用于为 key 设置过期时间。 不同在于 EXPIREAT 命令接受的时间参数是 UNIX 时间戳(unix timestamp)。 |
6 | PEXPIRE key milliseconds 设置 key 的过期时间以毫秒计。 |
7 | PEXPIREAT key milliseconds-timestamp 设置 key 过期时间的时间戳(unix timestamp) 以毫秒计 |
8 | KEYS pattern 查找所有符合给定模式( pattern)的 key 。 |
9 | MOVE key db 将当前数据库的 key 移动到给定的数据库 db 当中。 |
10 | PERSIST key 移除 key 的过期时间,key 将持久保持。 |
11 | PTTL key 以毫秒为单位返回 key 的剩余的过期时间。 |
12 | TTL key 以秒为单位,返回给定 key 的剩余生存时间(TTL, time to live)。 |
13 | RANDOMKEY 从当前数据库中随机返回一个 key 。 |
14 | RENAME key newkey 修改 key 的名称 |
15 | RENAMENX key newkey 仅当 newkey 不存在时,将 key 改名为 newkey 。 |
16 | [SCAN cursor MATCH pattern] [COUNT count] 迭代数据库中的数据库键。 |
17 | TYPE key 返回 key 所储存的值的类型。 |
字符串(String)
简介
String是Redis最基本的类型,一个key对应一个value。一个Redis中字符串value最多可以是512M。
String类型是二进制安全的。意味着Redis的String中可以包含任何数据,比如jpg图片或者序列化的对象。
常用命令
原子操作:不会被线程调度机制打断的操作
这种操作一旦开始。就一直运行到结束,中间不会由任何 context switch(切换到另一个线程)
- 在单线程中,能在单条指令中完成的操作都可以认为是原子操作,因为中断只能发生于指令之间
- 在多线程中,不能被其他进程(线程)打断的操作就叫原子操作
下表列出了常用的 redis 字符串命令:
序号 | 命令及描述 |
---|---|
1 | SET key value 设置指定 key 的值。 |
2 | GET key 获取指定 key 的值。 |
3 | GETRANGE key start end 返回 key 中字符串值的子字符 |
4 | GETSET key value 将给定 key 的值设为 value ,并返回 key 的旧值(old value)。 |
5 | GETBIT key offset 对 key 所储存的字符串值,获取指定偏移量上的位(bit)。 |
6 | MGET key1 key2…] 获取所有(一个或多个)给定 key 的值。 |
7 | SETBIT key offset value 对 key 所储存的字符串值,设置或清除指定偏移量上的位(bit)。 |
8 | SETEX key seconds value 将值 value 关联到 key ,并将 key 的过期时间设为 seconds (以秒为单位)。 |
9 | SETNX key value 只有在 key 不存在时设置 key 的值。 |
10 | SETRANGE key offset value 用 value 参数覆写给定 key 所储存的字符串值,从偏移量 offset 开始。 |
11 | STRLEN key 返回 key 所储存的字符串值的长度。 |
12 | MSET key value key value …] 同时设置一个或多个 key-value 对。 |
13 | MSETNX key value key value …] 同时设置一个或多个 key-value 对,当且仅当所有给定 key 都不存在。 |
14 | PSETEX key milliseconds value 这个命令和 SETEX 命令相似,但它以毫秒为单位设置 key 的生存时间,而不是像 SETEX 命令那样,以秒为单位。 |
15 | INCR key 将 key 中储存的数字值增一。 |
16 | INCRBY key increment 将 key 所储存的值加上给定的增量值(increment) 。 |
17 | INCRBYFLOAT key increment 将 key 所储存的值加上给定的浮点增量值(increment) 。 |
18 | DECR key 将 key 中储存的数字值减一。 |
19 | DECRBY key decrement key 所储存的值减去给定的减量值(decrement) 。 |
20 | APPEND key value 如果 key 已经存在并且是一个字符串, APPEND 命令将指定的 value 追加到该 key 原来值(value)的末尾。 |
数据结构
String的数据结构为简单动态字符串(Simple Dynamic String,SDS)。是可以修改的字符串,内部结构上类似于Java的ArrayList,采用预分配冗余空间的方式来减少内存的频繁分配。
当前字符串实际分配的空间capacity一般要高于实际字符串长度len。若超出则扩容
- len<1M:加倍现有空间
- len>1M:多扩1M空间
字符串最大长度为512M。
列表(List)
简介
单键多值。
Redis列表是简单的字符串列表,按照插入顺序排序。可以添加一个元素到列表的头部或尾部。
其底层是个双向列表,对两端的操作性能很高,通过索引下标的操作中间的节点性能较差。
常用命令
下表列出了列表相关的基本命令:
序号 | 命令及描述 |
---|---|
1 | BLPOP key1 key2 ] timeout 移出并获取列表的第一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。 |
2 | BRPOP key1 key2 ] timeout 移出并获取列表的最后一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。 |
3 | BRPOPLPUSH source destination timeout 从列表中弹出一个值,将弹出的元素插入到另外一个列表中并返回它; 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。 |
4 | LINDEX key index 通过索引获取列表中的元素 |
5 | LINSERT key BEFORE|AFTER pivot value 在列表的元素前或者后插入元素 |
6 | LLEN key 获取列表长度 |
7 | LPOP key 移出并获取列表的第一个元素 |
8 | LPUSH key value1 value2] 将一个或多个值插入到列表头部 |
9 | LPUSHX key value 将一个值插入到已存在的列表头部 |
10 | LRANGE key start stop 获取列表指定范围内的元素 |
11 | LREM key count value 移除列表元素 |
12 | LSET key index value 通过索引设置列表元素的值 |
13 | LTRIM key start stop 对一个列表进行修剪(trim),就是说,让列表只保留指定区间内的元素,不在指定区间之内的元素都将被删除。 |
14 | RPOP key 移除列表的最后一个元素,返回值为移除的元素。 |
15 | RPOPLPUSH source destination 移除列表的最后一个元素,并将该元素添加到另一个列表并返回 |
16 | RPUSH key value1 value2] 在列表中添加一个或多个值 |
17 | RPUSHX key value 为已存在的列表添加值 |
数据结构
List的数据结构为快速链表quickList
首先在列表元素较少的情况下会使用一块连续的内存存储,这个结构是ziplist,即压缩列表。
它将所有的元素紧挨着一起存储,分配的是一块连续的内存。
当数据量比较多时才会改成quicklist。
因为普通的链表需要的附加指针空间太大,会比较浪费空间。Redis将链表和ziplist结合起来组成了quicklist,也就是将多个ziplist使用双向指针串起来使用。这样既满足了快速的插入删除性能,又不会出现太大的空间冗余。
集合(Set)
简介
Redis set对外提供的功能是与list类似的一个列表的功能,特殊之处在于set是可以自动排重的,当你需要存储一个列表数据,又不希望出现重复数据时,set是一个很好的选择,并且set提供了判断某个成员是否在一个set集合内的重要接口,这也是list所不能提供的。
Redis的set是string类型的无序集合。它底层其实是一个value为null的hash表,所以添加,删除,查找的复杂度都是O(1),即数据增加,其查找数据的时间不变。
常用命令
下表列出了 Redis 集合基本命令:
序号 | 命令及描述 |
---|---|
1 | SADD key member1 member2] 向集合添加一个或多个成员 |
2 | SCARD key 获取集合的成员数 |
3 | SDIFF key1 key2] 返回第一个集合与其他集合之间的差异。 |
4 | SDIFFSTORE destination key1 key2] 返回给定所有集合的差集并存储在 destination 中 |
5 | SINTER key1 key2] 返回给定所有集合的交集 |
6 | SINTERSTORE destination key1 key2] 返回给定所有集合的交集并存储在 destination 中 |
7 | SISMEMBER key member 判断 member 元素是否是集合 key 的成员 |
8 | SMEMBERS key 返回集合中的所有成员 |
9 | SMOVE source destination member 将 member 元素从 source 集合移动到 destination 集合 |
10 | SPOP key 移除并返回集合中的一个随机元素 |
11 | SRANDMEMBER key count] 返回集合中一个或多个随机数 |
12 | SREM key member1 member2] 移除集合中一个或多个成员 |
13 | SUNION key1 key2] 返回所有给定集合的并集 |
14 | SUNIONSTORE destination key1 key2] 所有给定集合的并集存储在 destination 集合中 |
15 | [SSCAN key cursor MATCH pattern] [COUNT count] 迭代集合中的元素 |
数据结构
Set数据结构是dict字典,字典是使用哈希表实现的。
Java中的HashSet的内部实现使用的是HashMap,只不过所有的value都指向同一个对象。Redis的set内u也使用hash结构,所有的value也都指向同一个内部值
哈希(Hash)
简介
Redis hash是一个键值对集合
Redis hash是一个string类型的field和value的映射表,hash特别适合用于存储对象,类似Java中的Map<String,Object>
常用命令
下表列出了 redis hash 基本的相关命令:
序号 | 命令及描述 |
---|---|
1 | HDEL key field1 field2] 删除一个或多个哈希表字段 |
2 | HEXISTS key field 查看哈希表 key 中,指定的字段是否存在。 |
3 | HGET key field 获取存储在哈希表中指定字段的值。 |
4 | HGETALL key 获取在哈希表中指定 key 的所有字段和值 |
5 | HINCRBY key field increment 为哈希表 key 中的指定字段的整数值加上增量 increment 。 |
6 | HINCRBYFLOAT key field increment 为哈希表 key 中的指定字段的浮点数值加上增量 increment 。 |
7 | HKEYS key 获取所有哈希表中的字段 |
8 | HLEN key 获取哈希表中字段的数量 |
9 | HMGET key field1 field2] 获取所有给定字段的值 |
10 | HMSET key field1 value1 field2 value2 ] 同时将多个 field-value (域-值)对设置到哈希表 key 中。 |
11 | HSET key field value 将哈希表 key 中的字段 field 的值设为 value 。 |
12 | HSETNX key field value 只有在字段 field 不存在时,设置哈希表字段的值。 |
13 | HVALS key 获取哈希表中所有值。 |
14 | [HSCAN key cursor MATCH pattern] [COUNT count] 迭代哈希表中的键值对。 |
数据结构
Hash类型对应的数据结构是两种:
- ziplist(压缩列表) 当field-value长度较短且个数较少时使用
- hashtable(哈希表) 否则
有序集合(Zset)
简介
Redis有序集合zset与普通集合set非常相似,是一个没有重复元素的字符串集合。
不同之处时有序集合的每个成员都关联了一个评分(score),这个评分被用来从最低分到最高分的方式排序集合中的成员。集合的成员是唯一的,但是评分可以重复。
因为元素是有序的,所以可以很快的根据评分(score)或者次序(position)来获取一个范围的元素。
访问有序集合的中间元素也是非常快的,因此能够使用有序集合作为一个没有重复成员的智能列表。
常用命令
下表列出了 redis 有序集合的基本命令:
数据结构
**SortedSet(zset)**是Redis提供的一个非常特别的数据结构,一方面它等价于Java的数据结构Map<String,Double>,可以给每一个元素value赋予一个权重score,另一方面它又类似于TreeSet,内部的元素会按照权重score进行排序,可以得到每个元素的名次,还可以通过score的范围来获取元素的列表。
zset底层使用了两个数据结构:
- hash:关联元素value和权重score,保障元素value的唯一性,可以通过元素value找到相应的score值
- 跳跃表:给元素value排序,根据score的范围获取元素列表
新数据类型
BitMaps
简介
现代计算机用二进制(位) 作为信息的基础单位, 1个字节等于8位, 例如“abc”字符串是由3个字节组成, 但实际在计算机存储时将其用二进制表示, “abc”分别对应的ASCII码分别是97、 98、 99, 对应的二进制分别是01100001、 01100010和01100011,
合理地使用操作位能够有效地提高内存使用率和开发效率。
Redis提供了Bitmaps这个“数据类型”可以实现对位的操作:
(1) Bitmaps本身不是一种数据类型, 实际上它就是字符串(key-value) , 但是它可以对字符串的位进行操作。
(2) Bitmaps单独提供了一套命令, 所以在Redis中使用Bitmaps和使用字符串的方法不太相同。 可以把Bitmaps想象成一个以位为单位的数组, 数组的每个单元只能存储0和1, 数组的下标在Bitmaps中叫做偏移量。
命令
setbit
setbit <key> <offset> <value>
设置Bitmaps中某个偏移量的值(0或1)
offset:偏移量从0开始
实例
每个独立用户是否访问过网站存放在Bitmaps中, 将访问的用户记做1, 没有访问的用户记做0, 用偏移量作为用户的id。
getbit
getbit <key> <offset>
获取Bitmaps中某个偏移量的值
获取键的第offset位的值(从0开始算)
实例
获取id=8的用户是否在2020-11-06这天访问过, 返回0说明没有访问过
bitcount
统计字符串被设置为1的bit数。一般情况下,给定的整个字符串都会被进行计数,通过指定额外的 start 或 end 参数,可以让计数只在特定的位上进行。start 和 end 参数的设置,都可以使用负数值:比如 -1 表示最后一个位,而 -2 表示倒数第二个位,start、end 是指bit组的字节的下标数,二者皆包含。
bitcount <key> [start end]
统计字符串从start字节到end字节比特值为1的数量
Bitmaps与set对比
假设网站有1亿用户, 每天独立访问的用户有5千万, 如果每天用集合类型和Bitmaps分别存储活跃用户可以得到表
set和Bitmaps存储一天活跃用户对比
集合类型 64位 50000000 64位*50000000 = 400MB
Bitmaps 1位 100000000 1位*100000000 = 12.5MB
很明显, 这种情况下使用Bitmaps能节省很多的内存空间, 尤其是随着时间推移节省的内存还是非常可观的
set和Bitmaps存储独立用户空间对比 一天 一个月 一年
集合类型 400MB 12GB 144GB
Bitmaps 12.5MB 375MB 4.5GB
但Bitmaps并不是万金油, 假如该网站每天的独立访问用户很少, 例如只有10万(大量的僵尸用户) , 那么两者的对比如下表所示, 很显然, 这时候使用Bitmaps就不太合适了, 因为基本上大部分位都是0。
set和Bitmaps存储一天活跃用户对比(独立用户比较少)
数据类型 每个userid占用空间 需要存储的用户量 全部内存量
集合类型 64位 100000 64位100000 = 800KB
Bitmaps 1位 100000000 1位100000000 = 12.5MB
HyperLogLog
简介
在工作当中,我们经常会遇到与统计相关的功能需求,比如统计网站 PV(PageView 页面访问量),可以使用 Redis 的 incr、incrby 轻松实现。但像 UV(UniqueVisitor 独立访客)、独立 IP 数、搜索记录数等需要去重和计数的问题如何解决?这种求集合中不重复元素个数的问题称为基数问题。
解决基数问题有很多种方案:
数据存储在 MySQL 表中,使用 distinct count 计算不重复个数。
使用 Redis 提供的 hash、set、bitmaps 等数据结构来处理。
以上的方案结果精确,但随着数据不断增加,导致占用空间越来越大,对于非常大的数据集是不切实际的。能否能够降低一定的精度来平衡存储空间?Redis 推出了 HyperLogLog。
Redis HyperLogLog 是用来做基数统计的算法,HyperLogLog 的优点是:在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定的、并且是很小的。
在 Redis 里面,每个 HyperLogLog 键只需要花费 12 KB 内存,就可以计算接近 2^64 个不同元素的基数。这和计算基数时,元素越多耗费内存就越多的集合形成鲜明对比。
但是,因为 HyperLogLog 只会根据输入元素来计算基数,而不会储存输入元素本身,所以 HyperLogLog 不能像集合那样,返回输入的各个元素。
什么是基数?
比如数据集 {1, 3, 5, 7, 5, 7, 8},那么这个数据集的基数集为 {1, 3, 5 ,7, 8},基数 (不重复元素) 为 5。 基数估计就是在误差可接受的范围内,快速计算基数。
命令
pfadd <key> <element> [element...]
添加指定元素到HyperLogLog中。若执行后HLL估计的近似基数变化,返回1,否则返回0pfcount <key> [key...]
计算HLL的近似基数pfmerge <destkey> <sourcekey> [sourcekey...]
将一个或多个HLL合并后的结果存储在另一个HLL中。比如每月活跃用户可以使用每天的活跃用户来合并计算可得
Geospatial
简介
Redis 3.2 中增加了对 GEO 类型的支持。GEO,Geographic,地理信息的缩写。该类型,就是元素的 2 维坐标,在地图上就是经纬度。redis 基于该类型,提供了经纬度设置,查询,范围查询,距离查询,经纬度 Hash 等常见操作。
命令
geoadd <key> <longitude> <latitude> <member> [longitude latitude member...]
添加地理位置(经度、纬度、名称)geopos <key> <member> [member...]
获取指定地区的坐标值geodist <key> <member1> <member2> [m|km|ft|mi]
获取两个位置之间的直线距离georadius <key> <longitude> <latitude> radius m|km|ft|mi
以给定的经纬度为中心,找出某一半径
发布和订阅
介绍
Redis发布订阅(pub / sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接受消息。
Redis客户端可以订阅任意数量的频道。
发布和订阅
下图展示了频道 channel1 , 以及订阅这个频道的三个客户端 —— client2 、 client5 和 client1 之间的关系:
当有新消息通过 PUBLISH 命令发送给频道 channel1 时, 这个消息就会被发送给订阅它的三个客户端:
实例
以下实例演示了发布订阅是如何工作的,需要开启两个 redis-cli 客户端。
在我们实例中我们创建了订阅频道名为 runoobChat:
第一个 redis-cli 客户端
redis 127.0.0.1:6379**>** SUBSCRIBE runoobChat
Reading messages… (press Ctrl-C to quit)
1**)** “subscribe”
2**)** “runoobChat”
3**)** (integer) 1
现在,我们先重新开启个 redis 客户端,然后在同一个频道 runoobChat 发布两次消息,订阅者就能接收到消息。
第二个 redis-cli 客户端
redis 127.0.0.1:6379> PUBLISH runoobChat “Redis PUBLISH test”
(integer) 1
redis 127.0.0.1:6379> PUBLISH runoobChat “Learn redis by runoob.com”
(integer) 1
# 订阅者的客户端会显示如下消息
\1) “message”
\2) “runoobChat”
\3) “Redis PUBLISH test”
\1) “message”
\2) “runoobChat”
\3) “Learn redis by runoob.com”
gif 演示如下:
- 开启本地 Redis 服务,开启两个 redis-cli 客户端。
- 在第一个 redis-cli 客户端输入 SUBSCRIBE runoobChat,意思是订阅
runoobChat
频道。 - 在第二个 redis-cli 客户端输入 PUBLISH runoobChat “Redis PUBLISH test” 往 runoobChat 频道发送消息,这个时候在第一个 redis-cli 客户端就会看到由第二个 redis-cli 客户端发送的测试消息。
Redis 发布订阅命令
下表列出了 redis 发布订阅常用命令:
序号 | 命令及描述 |
---|---|
1 | PSUBSCRIBE pattern pattern …] 订阅一个或多个符合给定模式的频道。 |
2 | [PUBSUB subcommand argument [argument …]] 查看订阅与发布系统状态。 |
3 | PUBLISH channel message 将信息发送到指定的频道。 |
4 | [PUNSUBSCRIBE pattern [pattern …]] 退订所有给定模式的频道。 |
5 | SUBSCRIBE channel channel …] 订阅给定的一个或多个频道的信息。 |
6 | [UNSUBSCRIBE channel [channel …]] 指退订给定的频道。 |
事务
事务定义
Redis事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。
Redis事务的主要作用就是串联多个命令防止别的命令插队
multi、exec、discard
从输入multi命令开始,输入的命令都会依次进入命令队列中,但不会执行,直到输入exec后,Redis会将之前的命令队列中的命令依次执行。
组队的过程中可以通过discard来放弃组队。
事务中的错误处理
组队阶段中某个命令出现了报告错误,执行时整个的所有队列都会被取消。类比Java的编译时异常
如果执行阶段某个命令报出了错误,则只有报错的命令不会被执行,而其他的命令都会执行。类比Java的运行时异常
事务冲突
例子
假设银行卡内有10000元,现在三个人同时对其进行扣款,分别为8000、5000、2000元。解决方案如下
悲观锁
悲观锁(Pessimistic Lock),顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞(block),直到它拿到锁。传统的关系型数据库里就用到了很多这种锁机制,比如行锁、表锁,读锁、写锁等,都是在做操作之前先上锁。
乐观锁
乐观锁(Optimistic Lock),顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁。但是在更新的时候会判断以下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量。Redis就是利用这种check-and-set机制实现事务的。
watch key [key…]
在执行multi之前,先执行watch key1 [key2…],可以监视一个或多个key,如果在事务执行之前这个(或这些)key被其他命令改动,那么事务将被打断。
unwatch
取消watch命令对所有key的监视。
如果在执行watch命令之后,exec命令或discard命令先被执行了的话,那么就不需要再执行unwatch了。
Redis事务特性
- 单独的隔离操作
- 事务中的所有命令都会序列化、按顺序地执行、事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。
- 没有隔离级别的概念
- 队列中的命令没有提交之前都不会实际被执行,因为事务提交前任何指令都不会被实际执行。
- 不保证原子性
- 事务中如果有一条命令执行失败,其后的命令仍然被执行,没有回滚