redis基础知识学习
简介
- Redis 是一个 Key-Value 存储系统。支持存储的 value 类型相对更多,
包括 string(字符串)、list(链表)、set(集合)和 zset(有序集合)。这些数据类型都支持 push/pop、add/remove 及取交集并集和差集及更丰富的操作,而且这些操作都是原子性的。- Redis 是一个开源的使用 ANSI C 语言编写、支持网络、可基于内存亦可持久化的日志型、
Key-Value数据库,并提供多种语言的API。从2010年3月15日起,Redis的开发工作由VMware
主持。
数据类型:
Redis 提供了键(Key)和键值(Value)的映射关系,但值类型不仅仅支持常规的数值和string还支持
- Lists (列表)
- Sets (集合)
- Sorted sets (有序集合)
- Hashes (哈希表)
持久化方式
- 直接存储于内存中:使用截图的方式,将内存中的数据不断写入磁盘,性能较高,但是可能会引起一定程度的数据丢失。
- 使用虚拟内存:使用类似 MySQL 的日志方式,
记录每次更新的日志。
应用场景:
非常喜欢某位大佬的比喻
Redis 开创了一种新的数据存储思路,使用 Redis,我们不用在面对功能单调的数据库时,把精力放在如何把大象放进冰箱这样的问题上,而是利用 Redis 灵活多变的数据结构和数据操作,为不同的大象构建不同的冰箱。
- 排行榜
- 需要精确设置过期时间的需求
- 计算器应用
- 去重(利用集合结构)
- 实时系统,反垃圾系统
- Pub/Sub 构建实时消息系统(实时聊天系统)
- 缓存,这个在java开发时经常用作二级缓存来提高系统性能
安装
网上教程很多,而且非常简单,这里简单说一下步骤
- 下载redis:
wget http://redis.googlecode.com/files/redis-2.2.12.tar.gz
- 解压编译源程序
tar xzf redis-2.2.12.tar.gz
cd redis-2.2.12
make
- 启动
src/redis-server
- 客户端连接
src/redis-cli
- 停止
src/redis-cli shutdown
redis配置
一般,默认的配置足够用,但是可能存在某些需求来修改配置,这里可以参考redis配置文件详解
操作
string类型
string 类型是二进制安全的: redis 的 string 可以包含任何数据,比如 jpg 图片或者序列化的对象。内部使用byte数组实现(一个字节)
- set:设置 key 对应的值为 string 类型的 value
redis 127.0.0.1:6379> set name Tom
OK
- setnx:设置 key 对应的值为 string 类型的 value。如果 key 已经存在,返回 0,nx 是 not exist 的意思。
redis 127.0.0.1:6379> get name
“Tom”
redis 127.0.0.1:6379> setnx name Tom1
(integer) 0
redis 127.0.0.1:6379> get name
“Tom”
- setex:设置 key 对应的值为 string 类型的 value,并指定此键值对应的有效期。如下,设置light=white 时间5s。
redis 127.0.0.1:6379> setex light 5 white
OK
redis 127.0.0.1:6379> get light
“white”
redis 127.0.0.1:6379> get light //5秒之后调用自动清除
(nil)
- setrange:设置指定 key 的 value 值的子字符串。
redis 127.0.0.1:6379> get name
“Tom”
redis 127.0.0.1:6379> setrange name 2 im
(integer) 4
redis 127.0.0.1:6379> get name
“Toim”
- mset:一次设置多个 key 的值,成功返回 ok 表示所有的值都设置了,失败返回 0 表示没有任何值被设置。
redis 127.0.0.1:6379> mset name1 Lisa name2 Mark
OK
redis 127.0.0.1:6379> get name1
“Lisa”
redis 127.0.0.1:6379> get name2
“Mark”
- msetnx:一次设置多个 key 的值,成功返回 ok 表示所有的值都设置了,失败返回 0 表示没有任何值被设置,但是不会覆盖已经存在的 key。
- get:获取 key 对应的 string 值,如果 key 不存在返回 nil。
- getset:设置 key 的值,并返回 key 的旧值
redis 127.0.0.1:6379> get name2
“Mark”
redis 127.0.0.1:6379> getset name2 Jim
“Mark”
redis 127.0.0.1:6379> get name2
“Jim”
- getrange:获取指定 key 的 value 值的子字符串。左下标从0开始,右下标从-1开始。
redis 127.0.0.1:6379> get name1
“Lisa”
redis 127.0.0.1:6379> getrange name1 0 2
“Lis”
- mget:一次获取多个 key 的值,如果对应 key 不存在,则对应返回 nil。
- incr:对 key 的值做加加操作,并返回新的值。注意 incr 一个不是 int 的 value 会返回错误,incr 一个不存在的 key,则设置 key 为 1。
redis 127.0.0.1:6379> set age 20
OK
redis 127.0.0.1:6379> get age
“20”
redis 127.0.0.1:6379> incr age
(integer) 21
redis 127.0.0.1:6379> get age
“21”
redis 127.0.0.1:6379> get grade
(nil)
redis 127.0.0.1:6379> incr grade
(integer) 1
redis 127.0.0.1:6379> get grade
“1”
- incrby:同 incr 类似,加指定值 ,key 不存在时候会设置 key,并认为原来的 value 是 0.
redis 127.0.0.1:6379> get age
“21”
redis 127.0.0.1:6379> incrby age 5
(integer) 26
redis 127.0.0.1:6379> get age
“26”
- decr:对 key 的值做的是减减操作,decr 一个不存在 key,则设置 key 为-1
- decrby:同 decr,减指定值。
- append:给指定 key 的字符串值追加 value,返回新字符串值的长度。
redis 127.0.0.1:6379> get name2
“Jim”
redis 127.0.0.1:6379> append name2 itty
(integer) 7
redis 127.0.0.1:6379> get name2
“Jimitty”
- strlen:取指定 key 的 value 值的长度。
redis 127.0.0.1:6379> get name2
“Jimitty”
redis 127.0.0.1:6379> strlen name2
(integer) 7
hashes类型及操作
Redis hash 是一个 string 类型的 field 和 value 的映射表.它的添加、删除操作都是 O(1)(平均)。
hash 特别适合用于存储对象。相较于将对象的每个字段存成单个 string 类型。将一个对象存
储在 hash 类型中会占用更少的内存,并且可以更方便的存取整个对象。省内存的原因是新
建一个 hash 对象时开始是用 zipmap(又称为 small hash)来存储的。这个 zipmap 其实并不
是 hash table,但是 zipmap 相比正常的 hash 实现可以节省不少 hash 本身需要的一些元数据
存储开销。尽管 zipmap 的添加,删除,查找都是 O(n),但是由于一般对象的 field 数量都不
太多。所以使用 zipmap 也是很快的,也就是说添加删除平均还是 O(1)。如果 field 或者 value
的大小超出一定限制后,Redis 会在内部自动将 zipmap 替换成正常的 hash 实现. 这个限制可
以在配置文件中指定
hash-max-zipmap-entries 64 #配置字段最多 64 个
hash-max-zipmap-value 512 #配置 value 最大为 512 字节
- hset:设置 hash field 为指定值,如果 key 不存在,则先创建。
redis 127.0.0.1:6379> hset hash1 name Tom
(integer) 1
- hsetnx:设置 hash field 为指定值,如果 key 不存在,则先创建。如果 field 已经存在,返回 0,nx 是not exist 的意思。
redis 127.0.0.1:6379> hsetnx hash1 age 12
(integer) 1
redis 127.0.0.1:6379> hsetnx hash1 age 12
(integer) 0
- hmset:同时设置 hash 的多个 field;
redis 127.0.0.1:6379> hmset hash1 field1 name field2 sex
OK
- hget:获取指定的 hash field。
redis 127.0.0.1:6379> hget hash1 field1
“name”
- hmget:获取全部指定的 hash filed。
- hincrby:指定的 hash filed 加上给定值。
- hexists:测试指定 field 是否存在。
- hlen:返回指定 hash 的 field 数量。
- hdel:返回指定 hash 的 field 数量。
- hkeys:返回 hash 的所有 field。
redis 127.0.0.1:6379> hkeys hash1
- “name”
- “age”
- “field1”
- “field2”
- hvals:返回 hash 的所有 value。
redis 127.0.0.1:6379> hvals hash1
- “Tom”
- “12”
- “name”
- “sex”
- hgetall:获取某个 hash 中全部的 filed 及 value。
redis 127.0.0.1:6379> hgetall hash1
- “name”
- “Tom”
- “age”
- “12”
- “field1”
- “name”
- “field2”
- “sex”
lists 类型及操作
list 是一个链表结构,主要功能是 push、pop、获取一个范围的所有值等等,操作中 key 理解为链表的名字。Redis 的 list 类型其实就是一个每个子元素都是 string 类型的双向链表。链表的最大长度是(2的 32 次方)。我们可以通过 push,pop 操作从链表的头部或者尾部添加删除元素。这使得 list既可以用作栈,也可以用作队列。
- lpush:在 key 对应 list 的头部(从左向右)添加字符串元素
redis 127.0.0.1:6379> lpush list world
(integer) 1
redis 127.0.0.1:6379> lpush list Hello
(integer) 2
redis 127.0.0.1:6379> lrange list 0 -1
1) “Hello”
2) “world”
- rpush:在 key 对应 list 的尾部(从右向左)添加字符串元素
redis 127.0.0.1:6379> rpush list1 hello
(integer) 1
redis 127.0.0.1:6379> rpush list1 world
(integer) 2
redis 127.0.0.1:6379> lrange list1 0 -1
1) “hello”
2) “world”
- linsert:在 key 对应 list 的特定位置之前或之后添加字符串元素
redis 127.0.0.1:6379> lrange list1 0 -1
1)“hello”
2)“world”
redis 127.0.0.1:6379> linsert list1 before world my
(integer) 3
redis 127.0.0.1:6379> lrange list1 0 -1
1)“hello”
2)“my”
3)“world”
- lset:设置 list 中指定下标的元素值(下标从 0 开始)
redis 127.0.0.1:6379> rpush list2 1
(integer) 1
redis 127.0.0.1:6379> rpush list2 2
(integer) 2
redis 127.0.0.1:6379> rpush list2 3
(integer) 3
redis 127.0.0.1:6379> rpush list2 4
(integer) 4
redis 127.0.0.1:6379> lrange list2 0 -1
1)“1”
2)“2”
3)“3”
4)“4”
redis 127.0.0.1:6379> lset list2 2 5
OK
redis 127.0.0.1:6379> lrange list2 0 -1
1)“1”
2)“2”
3)“5”
4)“4”
- lrem:从 key 对应 list 中删除 count 个和 value 相同的元素。count为负数时表示从后往前删除,count==0时全部删除
redis 127.0.0.1:6379> lrange list2 0 -1
1)“1”
2)“2”
3)“5”
4)“4”
5)“4”
6)“4”
redis 127.0.0.1:6379> lrem list2 2 4 //删除两个4
(integer) 2
redis 127.0.0.1:6379> lrange list2 0 -1
1)“1”
2)“2”
3)“5”
4)“4”
- ltrim:保留指定 key 的值范围内的数据
redis 127.0.0.1:6379> rpush list3 1
(integer) 1
redis 127.0.0.1:6379> rpush list3 2
(integer) 2
redis 127.0.0.1:6379> rpush list3 3
(integer) 3
redis 127.0.0.1:6379> rpush list3 3
(integer) 4
redis 127.0.0.1:6379> rpush list3 4
(integer) 5
redis 127.0.0.1:6379> ltrim list3 0 2
OK
redis 127.0.0.1:6379> lrange list3 0 -1
1) "1"
2) "2"
3) "3"
- lpop:从 list 的头部删除元素,并返回删除元素
- rpop:从 list 的尾部删除元素,并返回删除元素
- rpoplpush:从第一个 list 的尾部移除元素并添加到第二个 list 的头部,最后返回被移除的元素值,整个操作是原子的.如果第一个 list 是空或者不存在返回 nil.
redis 127.0.0.1:6379> rpush list4 tom
(integer) 1
redis 127.0.0.1:6379> rpush list4 jim
(integer) 2
redis 127.0.0.1:6379> rpush list5 1
(integer) 1
redis 127.0.0.1:6379> rpush list5 2
(integer) 2
redis 127.0.0.1:6379> rpoplpush list4 list5
"jim"
redis 127.0.0.1:6379> lrange list4 0 -1
1) "tom"
redis 127.0.0.1:6379> lrange list5 0 -1
1) "jim"
2) "1"
3) "2"
- lindex:返回名称为 key 的 list 中 index 位置的元素
redis 127.0.0.1:6379> lrange list5 0 -1
1) "jim"
2) "1"
3) "2"
redis 127.0.0.1:6379> lindex list5 0
"jim"
- llen: 返回 key 对应 list 的长度
sets 类型及操作
1.集合,和我们数学中的集合概念相似,对集合的操作有添加删除元素,有对多个集合求交并差等操作,操作中 key 理解为集合的名字。
2.set 的是通过 hash table 实现的,所以添加、删除和查找的复杂度都是 O(1)。hash table 会随着添加或者删除自动的调整大小。
- sadd:向名称为 key 的 set 中添加元素
redis 127.0.0.1:6379> sadd set1 hello
(integer) 1
redis 127.0.0.1:6379> sadd set1 world
(integer) 1
redis 127.0.0.1:6379> sadd set1 world
(integer) 0
redis 127.0.0.1:6379> smembers set1
1) "world"
2) "hello"
- srem:删除名称为 key 的 set 中的元素 member
redis 127.0.0.1:6379> srem set1 hello
(integer) 1
redis 127.0.0.1:6379> smembers set1
1) "world"
- spop:随机返回并删除名称为 key 的 set 中一个元素
redis 127.0.0.1:6379> smembers set1
1) "hello"
2) "world"
3) "my"
redis 127.0.0.1:6379> spop set1
"my"
redis 127.0.0.1:6379> smembers set1
1) "hello"
2) "world"
- sdiff:返回所有给定 key 与第一个 key 的差集
redis 127.0.0.1:6379> smembers set1
1) "hello"
2) "world"
redis 127.0.0.1:6379> sadd set2 hello
(integer) 1
redis 127.0.0.1:6379> sdiff set1 set2
1) "world"
- sdiffstore:返回所有给定 key 与第一个 key 的差集,并将结果存为另一个 key
redis 127.0.0.1:6379> smembers set1
1) "world"
2) "hello"
3) "my"
redis 127.0.0.1:6379> smembers set2
1) "hello"
redis 127.0.0.1:6379> sdiffstore set3 set1 set2
(integer) 2
redis 127.0.0.1:6379> smembers set3
1) "world"
2) "my"
- sinter:返回所有给定 key 的交集
redis 127.0.0.1:6379> smembers set1
1) "world"
2) "hello"
3) "my"
redis 127.0.0.1:6379> smembers set3
1) "world"
2) "my"
redis 127.0.0.1:6379> sinter set1 set3
1) "world"
2) "my"
- sinterstore:返回所有给定 key 的交集,并将结果存为另一个 key
- sunion:返回所有给定 key 的并集
- sunionstore:返回所有给定 key 的并集,并将结果存为另一个 key
- smove:从第一个 key 对应的 set 中移除 member 并添加到第二个对应 set 中
- scard:返回名称为 key 的 set 的元素个数
- sismember:测试 member 是否是名称为 key 的 set 的元素
- srandmember:随机返回名称为 key 的 set 的一个元素,但是不删除元素
sorted sets 类型及操作
1.和 set 一样 sorted set 也是 string 类型元素的集合,不同的是每个元素都会关联一个 double类型的 score。sorted set 的实现是 skip list 和 hash table 的混合体。
2.当元素被添加到集合中时,一个元素到 score 的映射被添加到 hash table 中,所以给定一个元素获取 score 的开销是 O(1),另一个 score 到元素的映射被添加到 skip list,并按照 score 排序,所以就可以有序的获取集合中的元素。添加,删除操作开销都是 O(log(N))和 skip list 的开销一致,redis 的 skip list 实现用的是双向链表,这样就可以逆序从尾部取元素。sorted set 最经常的使用方式应该是作为索引来使用.我们可以把要排序的字段作为 score 存储,对象的 id当元素存储。
- zadd:向名称为 key 的 zset 中添加元素 member,score 用于排序。如果该元素已经存在,则根据score 更新该元素的顺序
redis 127.0.0.1:6379> zadd sset 1 Tom
(integer) 1
redis 127.0.0.1:6379> zadd sset 2 Jim
(integer) 1
redis 127.0.0.1:6379> zadd sset 3 Jim
(integer) 0
redis 127.0.0.1:6379> zrange sset 0 -1 withscores
1) "Tom"
2) "1"
3) "Jim"
4) "3"
- zrem:删除名称为 key 的 zset 中的元素 member
redis 127.0.0.1:6379> zrange sset 0 -1 withscores
1) "Tom"
2) "1"
3) "Jim"
4) "3"
redis 127.0.0.1:6379> zrem sset Tom
(integer) 1
redis 127.0.0.1:6379> zrange sset 0 -1 withscores
1) "Jim"
2) "3"
- zincrby:如果在名称为 key 的 zset 中已经存在元素 member,则该元素的 score 增加 increment;否则向集合中添加该元素,其 score 的值为 increment.
- zrank:返回名称为 key 的 zset 中 member 元素的排名(按 score 从小到大排序)即下标
- zrevrank:返回名称为 key 的 zset 中 member 元素的排名(按 score 从大到小排序)即下标
- zrevrange:返回名称为 key 的 zset(按 score 从大到小排序)中的 index 从 start 到 end 的所有元素
redis 127.0.0.1:6379> zrevrange sset 0 -1 withscores
1) "Jim"
2) "3"
- zrangebyscore:返回集合中 score 在给定区间的元素
redis 127.0.0.1:6379> zrangebyscore sset 2 4 withscores
1) "Jim"
2) "3"
3) "Mark"
4) "4"
- zcount: 返回集合中 score 在给定区间的数量
redis 127.0.0.1:6379> zcount sset 1 4
(integer) 2
- zcard:返回集合中元素个数
redis 127.0.0.1:6379> zcard sset
(integer) 2
- zscore:返回给定元素对应的 score
redis 127.0.0.1:6379> zscore sset Jim
"3"
- zremrangebyrank:删除集合中排名在给定区间的元素
- zremrangebyscore:删除集合中 score 在给定区间的元素
Redis常用命令
- keys:返回满足给定 pattern 的所有 key
redis 127.0.0.1:6379> keys *
1) "list"
2) "set1"
3) "list1"
4) "age"
5) "set2"
6) "list2"
7) "sset"
8) "set3"
9) "list3"
10) "user"
11) "list4"
12) "username"
13) "list5"
14) "name"
15) "mylist"
16) "name1"
17) "grade"
18) "name2"
19) "hash1"
20) "testkey"
redis 127.0.0.1:6379> keys set*
1) "set1"
2) "set2"
3) "set3"
- exists:确认一个 key 是否存在
redis 127.0.0.1:6379> exists set1
(integer) 1
- del:删除一个 key
redis 127.0.0.1:6379> del set1
(integer) 1
redis 127.0.0.1:6379> exists set1
(integer) 0
- expire:设置一个 key 的过期时间(单位:秒)
redis 127.0.0.1:6379> expire set2 5
(integer) 1
redis 127.0.0.1:6379> exists set2
- move:将当前数据库中的 key 转移到其它数据库中
redis 127.0.0.1:6379> select 0
OK
redis 127.0.0.1:6379> set age 30
OK
redis 127.0.0.1:6379> get age
"30"
redis 127.0.0.1:6379> move age 1
(integer) 1
redis 127.0.0.1:6379> select 1
OK
redis 127.0.0.1:6379[1]> get age
"30"
- persist:移除给定 key 的过期时间
redis 127.0.0.1:6379[1]> expire age 300
(integer) 1
redis 127.0.0.1:6379[1]> ttl age
(integer) 296
redis 127.0.0.1:6379[1]> persist age
(integer) 1
redis 127.0.0.1:6379[1]> ttl age
(integer) -1
redis 127.0.0.1:6379[1]> get age
"30"
- randomkey: 随机返回 key 空间的一个 key
redis 127.0.0.1:6379> randomkey
"list2"
redis 127.0.0.1:6379> randomkey
"list2"
redis 127.0.0.1:6379> randomkey
"hash1"
redis 127.0.0.1:6379> randomkey
"list2"
redis 127.0.0.1:6379> randomkey
"list5"
redis 127.0.0.1:6379> randomkey
"user"
- rename:重命名 key
redis 127.0.0.1:6379> keys set*
1) "set3"
redis 127.0.0.1:6379> rename set3 set
OK
redis 127.0.0.1:6379> keys set*
1) "set"
- type:返回值的类型
redis 127.0.0.1:6379> type set
set
redis 127.0.0.1:6379> type list3
list
- ping:测试连接是否存活
redis 127.0.0.1:6379> ping
PONG
redis 127.0.0.1:6379> ping
Could not connect to Redis at 127.0.0.1:6379: Unknown error//服务器未启动
(2.03s)
- echo:在命令行打印一些内容
- select:选择数据库。Redis 数据库编号从 0~15,我们可以选择任意一个数据库来进行数据的存取。
- quit:退出连接。
- dbsize:返回当前数据库中 key 的数目。
- info:获取服务器的信息和统计。
- monitor:实时转储收到的请求。
- config get:获取服务器配置信息。
- flushdb:删除当前选择数据库中的所有 key。
- flushall:删除所有数据库中的所有 key。
事务控制
redis 对事务的支持目前还比较简单。redis 只能保证一个 client 发起的事务中的命令可以连续的执行,而中间不会插入其他 client 的命令。 由于 redis 是单线程来处理所有 client 的请求的所以做到这点是很容易的。一般情况下 redis 在接受到一个 client 发来的命令后会立即处理并 返回处理结果,但是当一个 client 在一个连接中发出 multi 命令有,这个连接会进入一个事务上下文,该连接后续的命令并不是立即执行,而是先放到一个队列中。当从此连接受到 exec 命令后,redis 会顺序的执行队列中的所有命令。并将所有命令的运行结果打包到一起返回给 client.然后此连接就 结束事务上下文。
示例:
redis 127.0.0.1:6379> get age
"26"
redis 127.0.0.1:6379> multi
OK
redis 127.0.0.1:6379> set age 30
QUEUED
redis 127.0.0.1:6379> set age 34
QUEUED
redis 127.0.0.1:6379> exec
1) OK
2) OK
redis 127.0.0.1:6379> get age
"34"
取消事务:discard,执行的所有操作会回滚到执行multi命令之前的状态
redis 127.0.0.1:6379> get age
"30"
redis 127.0.0.1:6379> multi
OK
redis 127.0.0.1:6379> set age 40
QUEUED
redis 127.0.0.1:6379> set age 50
QUEUED
redis 127.0.0.1:6379> discard
OK
redis 127.0.0.1:6379> get age
"30"
乐观锁:
前面介绍了简单的事务管理,但是面对复杂的事务,可能需要更为高级的事务管理方法,在此之前,我们先了解一下乐观锁的概念。
乐观锁:大多数是基于数据版本(version)的记录机制实现的。何谓数据版本?即为数据增
加一个版本标识,在基于数据库表的版本解决方案中,一般是通过为数据库表添加一个
“version”字段来实现读取出数据时,将此版本号一同读出,之后更新时,对此版本号加 1。
此时,将提交数据的版本号与数据库表对应记录的当前版本号进行比对,如果提交的数据版
本号大于数据库表当前版本号,则予以更新,否则认为是过期数据。
简单演示一下:
管理员1 | 管理员2 |
---|---|
管理员1从用户账户读出数据(version=1) | 管理员2也对此数据进行了读取 |
管理员1将数据进行了修改,并提交,此时数据库中(version=2) | |
此时管理员2对数据修改之后,数据(version=2)在提交时version不大于数据库中的数据version,所以提交失败 |
这样,就避免了管理员2 用基于 version=1 的旧数据修改的结果来覆盖管理员2 的操作结果的可能。
Redis乐观锁的实现:
session1 | session2 |
---|---|
redis 127.0.0.1:6379> get age "30" redis 127.0.0.1:6379> watch age OK redis 127.0.0.1:6379> multi OK | |
redis 127.0.0.1:6379> get age "30" redis 127.0.0.1:6379> set age 40 OK redis 127.0.0.1:6379> get age "40" | |
redis 127.0.0.1:6379> set age 20 QUEUED redis 127.0.0.1:6379> exec (nil) redis 127.0.0.1:6379> get age "40" redis 127.0.0.1:6379> |
从以上实例可以看到在
第一步,Session 1 还没有来得及对 age 的值进行修改
第二步,Session 2 已经将 age 的值设为 30
第三步,Session 1 希望将 age 的值设为 20,但结果一执行返回是 nil,说明执行失败,之后我们再取一下 age 的值是 30,这是由于 Session 1 中对 age 加了乐观锁导致的。
watch 命令会监视给定的 key,当 exec 时候如果监视的 key 从调用 watch 后发生过变化,则整个事务会失败。也可以调用 watch 多次监视多个 key.这 样就可以对指定的 key 加乐观锁了。注意 watch 的 key 是对整个连接有效的,事务也一样。如果连接断开,监视和事务都会被自动清除。当然了 exec,discard,unwatch 命令都会清除连接中的所有监视。
redis存在问题
接触过关系型数据库的朋友都清楚,当一个事务执行出错,发生回滚时,和他同时执行的其他事务也会同时回滚,但是redis却不是这样
redis 只能保证事务的每个命令连续执行,但是如果事务中的一个命令失败了,并不回滚其他命令。
展示一下:
redis 127.0.0.1:6379> get age
"40"
redis 127.0.0.1:6379> get name
"Toim"
redis 127.0.0.1:6379> multi
OK
redis 127.0.0.1:6379> incr age
QUEUED
redis 127.0.0.1:6379> incr name
QUEUED
redis 127.0.0.1:6379> exec
1) (integer) 41
2) (error) ERR value is not an integer or out of range
redis 127.0.0.1:6379> get age
"41"
redis 127.0.0.1:6379> get name
"Toim"
redis 127.0.0.1:6379>
从这个例子中可以看到,age 由于是个数字,那么它可以有自增运算,但是 name 是个字符串,无法对其进行自增运算,所以会报错,如果按传统关系型数据库的思路来讲,整个事务都会回滚,但是我们看到 redis 却是将可以执行的命令提交了!!!
发布订阅消息
- 何为发布订阅:
发布订阅(pub/sub)是一种消息通信模式,主要的目的是解耦消息发布者和消息订阅者之间的耦合,这点和设计模式中的观察者模式比较相似。pub/sub 不仅仅解决发布者和订阅者直接代码级别耦合也解决两者在物理部署上的耦合。
- redis中的实现
redis 作为一个 pub/sub 的 server,在订阅者和发布者之间起到了消息路由的功能。订阅者可以通过 subscribe 和 psubscribe 命令向 redis server 订阅自己感兴趣的消息类型,redis 将消息类型称为通道(channel)。当发布者通过
publish 命令向 redis server 发送特定类型的消息时。订阅该消息类型的全部 client 都会收到此消息。这里消息的传递是多对多的。一个client可以订阅多个channel,也可以向多个channel发送消息。
-
小小演示
Client1 | Client2 | Client3 |
---|---|---|
redis 127.0.0.1:6379> subscribe chanel1 Reading messages… (press Ctrl-C to quit) 1) "subscribe" 2) "chanel1" 3) (integer) 1 | redis 127.0.0.1:6379> subscribe chanel1 chanel2 Reading messages… (press Ctrl-C to quit) 1) "subscribe" 2) "chanel1" 3) (integer) 1 1) "subscribe" 2) "chanel2" 3) (integer) 2 | |
redis 127.0.0.1:6379> publish chanel1 hahah (integer) 2 redis 127.0.0.1:6379> | ||
1) "message" 2) "chanel1" 3) “hahah” | 1) "message" 2) "chanel1" 3) “hahah” | |
redis 127.0.0.1:6379> publish chanel2 hello (integer) 1 redis 127.0.0.1:6379> | ||
1) "subscribe" 2) "chanel1" 3) (integer) 1 1) "message" 2) "chanel1" 3) “hahah” | 1) "subscribe" 2) "chanel1" 3) (integer) 1 1) "subscribe" 2)"chanel2" 3) (integer) 2 1) "message" 2) "chanel1" 3) "hahah" 1) "message" 2) "chanel2" 3) “hello” |
1、client1 订阅了 chanel1 这个 channel 这个频道的消息,client2 订阅了 chanel1 和 chanel2这 2 个频道的消息
2、client3 是用于发布 chanel1 和 chanel2这 2 个频道的消息发布者
3、接下来我们在 client3 发布了一条消息”publish chanel1 hahah”,大家可以看到这条消息是发往 chanel1 这个频道的
4、理所当然的 client1 和 client2 都接收到了这个频道的消息
5、 然后 client3 又发布了一条消息”publish chanel2 hello”,这条消息是发往 chanel2的,由于client1 并没有订阅 chanel1 ,所以 client1 的结果中并没有显示出任何结果,但 client2 订阅了这个频道,所以 client2 是会有返回结果的。