这是我参与「第三届青训营 -后端场」笔记创作活动的第4篇笔记,回顾分享之前写的关于Redis笔记
1.基础知识
redis配置说明:https://www.runoob.com/redis/redis-conf.html
redis命令:http://www.redis.cn/commands.html#
- redis默认有16个数据库,默认使用第0个数据库
使用select index切换数据库
127.0.0.1:6379> select 1
OK
127.0.0.1:6379[1]> select 0 切换数据库
OK
127.0.0.1:6379> dbsize 查看db大小
(integer) 4
127.0.0.1:6379> keys * 查看所有的key
1) "counter:__rand_int__"
2) "key:__rand_int__"
3) "mylist"
4) "name"
127.0.0.1:6379> get name
"joker"
127.0.0.1:6379> flushdb 清空当前数据库
OK
127.0.0.1:6379> flushall 清空所有数据库
OK
Redis是单线程的 !!!! 但Redis6开始是多线程
Redis是基于内存操作,CPU不是Redis的性能瓶颈,Redis的性能瓶颈是机器内存和网络带宽
为什么Redis是单线程的,还这么快
注意:
- 高性能的服务器不一定就是多线程的
- 多线程(CPU上下文会切换)不一定比单线程效率高
速度 : CPU>内存>硬盘
核心:redis将所有数据都放在内存中,所以使用多线程操作效率最高(多线程,CPU上下文会切换,耗时操作)用空间换时间
2.数据类型
Redis支持五种数据类型:string(字符串),hash(哈希),list(列表),set(集合)及zset(sorted set:有序集合)。
2.1 redis-key
127.0.0.1:6379> set mykey myvalue
OK
127.0.0.1:6379> get mykey
"myvalue"
127.0.0.1:6379> EXISTS mykey 判断是否key存在
(integer) 1
127.0.0.1:6379> EXISTS mykey1
(integer) 0
127.0.0.1:6379> move mykey 1 移动key到1号数据库中
(integer) 1
127.0.0.1:6379>
127.0.0.1:6379> del mykey 删除对应key
(integer) 1
127.0.0.1:6379> EXPIRE mykey 10 设置key存活时间为10秒
(integer) 1
127.0.0.1:6379> ttl mykey 剩余存在时间
(integer) 5
127.0.0.1:6379> ttl mykey
(integer) 2
127.0.0.1:6379> ttl mykey
(integer) -2
127.0.0.1:6379> type mykey 判断key的类型
string
2.2 string
127.0.0.1:6379> clear
127.0.0.1:6379> set mykey myvalue
OK
127.0.0.1:6379> get mykey
"myvalue"
127.0.0.1:6379>
- 追加 append ,如果key不存在,相当于set key value
127.0.0.1:6379> get mykey
"101"
127.0.0.1:6379> APPEND mykey 10
(integer) 5
127.0.0.1:6379> get mykey
"10110"
- 获取字符串长度
127.0.0.1:6379> STRLEN mykey
(integer) 5
- 虽然是String类型,但如果是数字的字符串,可以用
incr强制转换成数字再追加
127.0.0.1:6379> set mykey 100
OK
127.0.0.1:6379> get mykey
"100"
127.0.0.1:6379> incr mykey 加1
(integer) 101
127.0.0.1:6379> incr mykey
(integer) 102
127.0.0.1:6379> incr mykey
(integer) 103
127.0.0.1:6379> DECR mykey 减1
(integer) 10111
127.0.0.1:6379> DECR mykey
(integer) 10110
127.0.0.1:6379> INCRBY mykey 10 加10
(integer) 10120
127.0.0.1:6379> DECRBY mykey 20 减20
(integer) 10100
127.0.0.1:6379>
- getrange 获得字符串区间
127.0.0.1:6379> GETRANGE mykey 0 2
"101"
127.0.0.1:6379> GETRANGE mykey 0 -1 0到-1是全取
"10100"
- 替换 set和setrange
127.0.0.1:6379> SETRANGE mykey 0 xx
(integer) 5
127.0.0.1:6379> get mykey
"xx100"
- 设置key存活时间 setex (分布式锁中会常使用)
127.0.0.1:6379> setex username 30 "admin"
OK
127.0.0.1:6379> ttl username
(integer) 22
- 使用
setnx加锁 (分布式锁中会常使用) key不存在就会成功,如果key已经存在了,则失败
127.0.0.1:6379> SETNX mykey1 "Hello"
(integer) 1
127.0.0.1:6379> SETNX mykey1 "World"
(integer) 0
127.0.0.1:6379> get mykey1
"Hello"
127.0.0.1:6379> set mykey1 "World"
OK
127.0.0.1:6379> get mykey1
"World"
MSET会用新的value替换已经存在的value,就像普通的SET命令一样。
127.0.0.1:6379> MSET key1 "Hello1" key2 "Hello2"
OK
127.0.0.1:6379> keys *
1) "key2"
2) "key1"
- 返回所有指定的key的value。对于每个不对应string或者不存在的key,都返回特殊值
nil。
127.0.0.1:6379> MGET key1 key2
1) "Hello1"
2) "Hello2"
MSETNX是原子的,对应给定的keys到他们相应的values上。只要有一个key已经存在,MSETNX一个操作都不会执行。 由于这种特性,MSETNX可以实现要么所有的操作都成功,要么一个都不执行
127.0.0.1:6379> MSETNX key1 v1 key3 v2
(integer) 0
- 存储对象
127.0.0.1:6379> MSET user:1:name "joker" user:1:age 22 user:{id}:{filed}
OK
127.0.0.1:6379> MGET user:1:name user:1:age
1) "joker"
2) "22"
127.0.0.1:6379> set user:1 {name:joker,age:22} 设置一个user:1对象 值为json字符来保存对象
OK
127.0.0.1:6379> get user:1
"{name:joker,age:22}"
getset先get,再set ,如果不存在,输出nil,再设置新值
127.0.0.1:6379> GETSET user:1 "new"
"{name:joker,age:22}"
127.0.0.1:6379> get user:1
"new"
String使用场景:
- 计数器
- 统计多单位数量
- 对象缓存
2.3 list (所有list命令都是字母L开头的)
RPUSH list valuesLPUSH list values和LRANGE list index index设置list值和取值
127.0.0.1:6379> RPUSH list A B C D E 从右边推入 , LPUSH是从左边推入
(integer) 5
127.0.0.1:6379> LRANGE list 0 2 取序号 0 到 2
1) "A"
2) "B"
3) "C"
127.0.0.1:6379> LRANGE list 0 -1 取序号 0 到 倒数第一个
1) "A"
2) "B"
3) "C"
4) "D"
5) "E"
127.0.0.1:6379> LRANGE list 0 -2 取序号 0 到 倒数第二个
1) "A"
2) "B"
3) "C"
4) "D"
- 判断是否存在对应key
127.0.0.1:6379> EXISTS list
(integer) 1
- 删除值
LPOP和RPOPpop,它从list中删除元素并同时返回删除的值。可以在左边或右边操作
127.0.0.1:6379> LRANGE list 0 -1
1) "A"
2) "B"
3) "C"
4) "D"
5) "E"
127.0.0.1:6379> LPOP list 从list的左边删除第一个值
"A"
127.0.0.1:6379> RPOP list 从list的右边删除第二个值
"E"
127.0.0.1:6379> LRANGE list 0 -1
1) "B"
2) "C"
3) "D"
- 可以使用LTRIM把 list 从左边截取指定长度
127.0.0.1:6379> LRANGE list 0 -1
1) "B"
2) "C"
3) "D"
127.0.0.1:6379> LTRIM list 0 1
OK
127.0.0.1:6379> LRANGE list 0 -1
1) "B"
2) "C"
- 从list下标取值
LINDEX
127.0.0.1:6379> LINDEX list 0
"B"
- 返回list长度
127.0.0.1:6379> LLEN list
(integer) 2
- 从存于 key 的列表里移除前 count 次出现的值为 value 的元素。 这个 count 参数通过下面几种方式影响这个操作:
count > 0: 从头往尾移除值为 value 的元素。
count < 0: 从尾往头移除值为 value 的元素。
count = 0: 移除所有值为 value 的元素。
比如, LREM list -2 “hello” 会从存于 list 的列表里移除最后两个出现的 “hello”。
127.0.0.1:6379> LRANGE list 0 -1
1) "B"
2) "C"
3) "B"
4) "C"
5) "B"
127.0.0.1:6379> LREM list -2 B
(integer) 2
127.0.0.1:6379> LRANGE list 0 -1
1) "B"
2) "C"
3) "C"
- 原子性地返回并移除存储在 原来List 列表的最后一个元素(列表尾部元素), 并把该元素放入存储在 目标列表 的第一个元素位置(列表头部)。
127.0.0.1:6379> LRANGE list 0 -1
1) "B"
2) "C"
3) "C"
127.0.0.1:6379> RPOPLPUSH list childList 移除list中最后一个值,将其移动到childList中的头位置
"C"
127.0.0.1:6379> keys *
1) "list"
2) "childList"
3) "user:1"
127.0.0.1:6379> LRANGE list 0 -1
1) "B"
2) "C"
127.0.0.1:6379> LRANGE childList 0 -1
1) "C"
- 设置 index 位置的list元素的值为 value
LSET
127.0.0.1:6379> LRANGE list 0 -1
1) "B"
2) "C"
127.0.0.1:6379> LSET list 0 "A"
OK
-
把 value 插入存于 key 的列表中在基准值 pivot 的前面或后面。
当 key 不存在时,这个list会被看作是空list,任何操作都不会发生。
当 key 存在,但保存的不是一个list的时候,会返回error。
BEFORE 或者 AFTER
127.0.0.1:6379> LRANGE list 0 -1
1) "A"
2) "C"
127.0.0.1:6379> LINSERT list before "C" "new" 在原列表中,检索“C”,存在时,在前面添加“new”
(integer) 3
127.0.0.1:6379> LRANGE list 0 -1
1) "A"
2) "new"
3) "C"
127.0.0.1:6379> LINSERT list before "M" "new" 在原列表中,检索“M”,不存在时,返回-1
(integer) -1
总结:
- list可以从两边插入数据和删除数据,高效
- 如果移除所有值时,list也会消失
- 实现消息排队,消息队列(LPUSH , RPOP),栈(LPUSH,LPOP)
2.4 set
Redis Set 是 String 的无序排列,不可重复
sadd添加 set值 ,smembers打印出全部值
127.0.0.1:6379> keys *
1) "list"
2) "user:1"
127.0.0.1:6379> sadd mySet Hello
(integer) 1
127.0.0.1:6379> sadd mySet Set
(integer) 1
127.0.0.1:6379> sadd mySet Set
(integer) 0
127.0.0.1:6379> smembers mySet
1) "Set"
2) "Hello"
sismember判断set中是否存在对应值
127.0.0.1:6379> SISMEMBER mySet "Set"
(integer) 1
127.0.0.1:6379> SISMEMBER mySet "World"
(integer) 0
scard key返回集合存储的key的基数 (集合元素的数量)
127.0.0.1:6379> scard mySet
(integer) 2
srem删除 set 集合中指定值
127.0.0.1:6379> sMembers mySet
1) "C"
2) "B"
3) "A"
4) "Hello"
127.0.0.1:6379> SREM mySet "A" "D" 如果set集合中存在对应值,删除;
(integer) 1
127.0.0.1:6379> sMembers mySet
1) "C"
2) "B"
3) "Hello"
srandmember key随机获取 set 集合中的值
127.0.0.1:6379> SRANDMEMBER mySet
"B"
127.0.0.1:6379> SRANDMEMBER mySet
"C"
127.0.0.1:6379> SRANDMEMBER mySet
"C"
SPOP key随机删除集合中的值
127.0.0.1:6379> SMEMBERS mySet
1) "C"
2) "B"
3) "Hello"
127.0.0.1:6379> SPOP mySet
"C"
127.0.0.1:6379> SMEMBERS mySet
1) "B"
2) "Hello"
SMOVE source destination member将一个集合中的值
127.0.0.1:6379> SMOVE mySet mySet1 "Hello"
(integer) 1
127.0.0.1:6379> Smembers mySet1
1) "Hello"
- 数学集合类(交集、并集、差集)
127.0.0.1:6379> SMEMBERS mySet
1) "A"
2) "B"
127.0.0.1:6379> SMEMBERS mySet1
1) "A"
2) "Hello"
127.0.0.1:6379> SDIFF mySet mySet1 mySet集合相对于mySet1不同的值 (差集)
1) "B"
127.0.0.1:6379> SINTER mySet mySet1 两个集合的相同的部分(交集) 共同好友的实现
1) "A"
127.0.0.1:6379> SUNION mySet mySet1 两个集合合起来 (并集)
1) "Hello"
2) "B"
3) "A"
2.5 hash
-
hset key field value设置 key 指定的哈希集中指定字段的值。
如果 key 指定的哈希集不存在,会创建一个新的哈希集并与 key 关联。
如果字段在哈希集中存在,它将被重写。
hget key filed返回 key 指定的哈希集中该字段所关联的值
127.0.0.1:6379> hset myHash name joker
(integer) 1
127.0.0.1:6379> hget myHash name
"joker"
-
hmset key field value [field value ...]设置
key指定的哈希集中指定字段的值。该命令将重写所有在哈希集中存在的字段。如果key指定的哈希集不存在,会创建一个新的哈希集并与key关联hmget key field [field ...]返回
key指定的哈希集中指定字段的值。对于哈希集中不存在的每个字段,返回
nil值。因为不存在的keys被认为是一个空的哈希集,对一个不存在的key执行 HMGET 将返回一个只含有nil值的列表
127.0.0.1:6379> hmset myHash age 11 sex man
OK
127.0.0.1:6379> hmget myHash name age sex
1) "joker"
2) "11"
3) "man"
hgetall key获取全部数据
127.0.0.1:6379> HGETALL myHash
1) "name"
2) "joker"
3) "age"
4) "11"
5) "sex"
6) "man"
hdel key field删除指定字段,对应的value值也消失了
127.0.0.1:6379> HGETALL myHash
1) "name"
2) "joker"
3) "age"
4) "11"
5) "sex"
6) "man"
127.0.0.1:6379> HDEL myHash sex
(integer) 1
127.0.0.1:6379> HGETALL myHash
1) "name"
2) "joker"
3) "age"
4) "11"
hlen key获取集合set长度
127.0.0.1:6379> HGETALL myHash
1) "name"
2) "joker"
3) "age"
4) "11"
127.0.0.1:6379> HLEN myHash
(integer) 2
hexists返回hash里面field是否存在
127.0.0.1:6379> HGETALL myHash
1) "name"
2) "joker"
3) "age"
4) "11"
127.0.0.1:6379> HEXISTS myHash age 存在
(integer) 1
127.0.0.1:6379> HEXISTS myHash sex 不存在
(integer) 0
- 只显示出所有的field或者value ,
hkeys或者hvals
127.0.0.1:6379> hgetall myHash
1) "name"
2) "joker"
3) "age"
4) "11"
127.0.0.1:6379> HKEYS myHash
1) "name"
2) "age"
127.0.0.1:6379> HVALS myHash
1) "joker"
2) "11"
hincrby自增和自减
127.0.0.1:6379> HINCRBY myHash age 10
(integer) 22
127.0.0.1:6379> HINCRBY myHash age -10
(integer) 12
HSETNX key field value只在key指定的哈希集中不存在指定的字段时,设置字段的值。如果key指定的哈希集不存在,会创建一个新的哈希集并与key关联。如果字段已存在,该操作无效果。
127.0.0.1:6379> HSETNX myHash age 13 field存在,所以失败
(integer) 0
127.0.0.1:6379> HSETNX myHash sex "man" field不存在,所以成功
(integer) 1
127.0.0.1:6379> hgetall myHash
1) "name"
2) "joker"
3) "age"
4) "12"
5) "sex"
6) "boy"
使用场景:
- 变更的数据 user age name ,尤其是用户信息类的,经常变动的信息。hash更适合对象的存储,string更适合字符串的存储
2.6 zset(有序集合)
zadd key score value添加值
127.0.0.1:6379> zadd myZset 1 first 添加一个值
(integer) 1
127.0.0.1:6379> zadd myZset 2 second 3 third 添加多个值
(integer) 2
127.0.0.1:6379> zrange myZset 0 -1
1) "first"
2) "second"
3) "third"
ZRANGEBYSCORE key min max升序排序(从小到大)
127.0.0.1:6379> ZRANGEBYSCORE myZset -inf +inf 根据score大小排序,范围在负无穷到正无穷
1) "first"
2) "second"
3) "third"
127.0.0.1:6379> ZRANGEBYSCORE myZset -inf +inf withscores 顺便打印出score
1) "first"
2) "1"
3) "second"
4) "2"
5) "third"
6) "3"
ZREVRANGEBYSCORE key max min降序排序(从大到小)
127.0.0.1:6379> ZREVRANGEBYSCORE myZset +inf -inf
1) "third"
2) "second"
3) "first"
127.0.0.1:6379> ZREVRANGEBYSCORE myZset +inf -inf withscores
1) "third"
2) "3"
3) "second"
4) "2"
5) "first"
6) "1"
zrem key value删除指定元素
127.0.0.1:6379> zrange myZset 0 -1
1) "first"
2) "second"
3) "third"
127.0.0.1:6379> zrem myZset first
(integer) 1
127.0.0.1:6379> zrange myZset 0 -1
1) "second"
2) "third"
zcard key获得集合的个数
127.0.0.1:6379> zcard myZset
(integer) 2
zcount key min max获取指定分数范围的元素个数
127.0.0.1:6379> zrange myZset 0 -1 withscores
1) "second"
2) "2"
3) "third"
4) "3"
127.0.0.1:6379> zcount myZset 0 2 获取score为0到1的个数
(integer) 1
127.0.0.1:6379> zcount myZset 0 3 获取score为0到2的个数
(integer) 2
使用场景:
- set 排序 存储班级成绩 工资排序
- 普通消息 重要消息 带权重进行判断
- 排行榜
2.7 geospatial (地理位置)
-
geoadd key 经度 纬度 member将指定的地理空间位置(纬度、经度、名称)添加到指定的key中。这些数据将会存储到sorted set这样的目的是为了方便使用GEORADIUS或者GEORADIUSBYMEMBER命令对数据进行半径查询等操作- 有效的经度从-180度到180度。
- 有效的纬度从-85.05112878度到85.05112878度。
127.0.0.1:6379> geoadd china:city 116.23 40.22 beijing
(integer) 1
127.0.0.1:6379> geoadd china:city 116.41 23.57 jieyang
(integer) 1
127.0.0.1:6379> geoadd china:city 114.02 30.58 wuhan
(integer) 1
geopos key从key里返回所有给定位置元素的位置(经度和纬度)。
127.0.0.1:6379> geopos china:city jieyang 只查询揭阳的经纬度
1) 1) "116.40999823808670044"
2) "23.57000072404306223"
127.0.0.1:6379> geopos china:city jieyang beijing wuhan 查询揭阳,北京,武汉的经纬度
1) 1) "116.40999823808670044"
2) "23.57000072404306223"
2) 1) "116.23000055551528931"
2) "40.2200010338739844"
3) 1) "114.01999980211257935"
2) "30.58000021509926825"
-
GEODIST key member1 member2 [unit]返回两个给定位置之间的距离。如果两个位置之间的其中一个不存在, 那么命令返回空值。
指定单位的参数 unit 必须是以下单位的其中一个:
- m 表示单位为米。
- km 表示单位为千米。
- mi 表示单位为英里。
- ft 表示单位为英尺。
127.0.0.1:6379> geodist china:city jieyang beijing km 以千米为单位,查看揭阳到北京的直线距离
"1851.9947"
127.0.0.1:6379> geodist china:city jieyang beijing m 以米为单位,查看揭阳到北京的直线距离
"1851994.7423"
-
GEORADIUS key longitude latitude radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count]给定经纬度,组成一个中心点,再给定半径,找到在这个区域中存在的值
范围可以使用以下其中一个单位:
- m 表示单位为米。
- km 表示单位为千米。
- mi 表示单位为英里。
- ft 表示单位为英尺。
在给定以下可选项时, 命令会返回额外的信息:
WITHDIST: 在返回位置元素的同时, 将位置元素与中心之间的距离也一并返回。 距离的单位和用户给定的范围单位保持一致。WITHCOORD: 将位置元素的经度和维度也一并返回。WITHHASH: 以 52 位有符号整数的形式, 返回位置元素经过原始 geohash 编码的有序集合分值。 这个选项主要用于底层应用或者调试, 实际中的作用并不大。
命令默认返回未排序的位置元素。 通过以下两个参数, 用户可以指定被返回位置元素的排序方式:
ASC: 根据中心的位置, 按照从近到远的方式返回位置元素。DESC: 根据中心的位置, 按照从远到近的方式返回位置元素。
附近的人,(获取所有人的地址),通过半径来查询
127.0.0.1:6379> GEORADIUS china:city 116 23 100 km
1) "jieyang"
-
GEORADIUSBYMEMBER key member radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count]这个命令和 GEORADIUS 命令一样, 都可以找出位于指定范围内的元素, 但是
GEORADIUSBYMEMBER的中心点是由给定的位置元素决定的, 而不是像 GEORADIUS 那样, 使用输入的经度和纬度来决定中心点
127.0.0.1:6379> GEORADIUSBYMEMBER china:city jieyang 500 km 以揭阳的经纬度为中心,半径为500km的圆
1) "jieyang"
127.0.0.1:6379> GEORADIUSBYMEMBER china:city jieyang 1000 km 以揭阳的经纬度为中心,半径为1000km的圆
1) "jieyang"
2) "wuhan"
GEOHASH key member [member ...]该命令将返回11个字符的Geohash字符串,所以没有精度Geohash,损失相比,使用内部52位表示。
# 将二维的经纬度转换成一维的字符串,如果两个字符串越接近,两个距离越近
127.0.0.1:6379> GEOHASH china:city jieyang wuhan
1) "ws4y0fz1jw0"
2) "wt3jfwfv6t0"
127.0.0.1:6379> GEOHASH china:city jieyang beijing
1) "ws4y0fz1jw0"
2) "wx4sucu47r0"
GEO底层的实现原理其实就是Zset,我们可以使用Zset命令来调用它
127.0.0.1:6379> zrange china:city 0 -1
1) "jieyang"
2) "wuhan"
3) "beijing"
应用:
- 朋友的定位,附近的人,打车距离计算
2.8 hyperloglogs(基数)
什么是基数?
比如数据集 {1, 3, 5, 7, 5, 7, 8} , 那么这个数据集的基数集为 {1, 3, 5 ,7, 8} , 基数(不重复元素)为5。 基数估计就是在误差可接受的范围内,快速计算基数
传统统计网页访问量,set保存用户的id,然后统计set元素数量作为标准判断。这种做法大量保存用户id,会比较麻烦,我们的目的只是计数,而不是保存用户id。
使用redis hyperloglogs,一个人访问一个网站多次,还是算作一个人。优点:占用的内存是固定的,2^64不同的元素技术,只需要废12KB内存,如果从内存角度来比较,Hyperloglogs比较好
PFADD key element [element ...]将除了第一个参数以外的参数存储到以第一个参数为变量名的HyperLogLog结构中
127.0.0.1:6379> pfadd key1 a b c d e
(integer) 1
pfcount key返回key中的个数
127.0.0.1:6379> pfadd key1 a b c d e
(integer) 1
127.0.0.1:6379> pfcount key1
(integer) 5
127.0.0.1:6379> pfadd key1 a b c d e
(integer) 1
127.0.0.1:6379> pfcount key1
(integer) 5
127.0.0.1:6379> pfadd key2 a b c d e f
(integer) 1
127.0.0.1:6379> PFMERGE key1 key2 将key2中的值添加到key1中,如果有重复的,则不添加
OK
127.0.0.1:6379> pfcount key1 原来长度为5,添加key2中的f,长度变成6
(integer) 6
如果允许容错,那么一定可以使用Hyperloglogs !
如果不允许容错,就使用set或者自己写的数据类型!
2.9 Bitmaps
统计用户信息,活跃,不活跃;登录,未登录;打卡,365打卡
Bitmaps位图,数据结构,都是操作二进制位来进行记录,就只有0和1两个状态
365天=365bit 1字节=8bit 46个字节左右
使用bitmap 来记录 周一到周日的打卡
周一:1 周二:0 周三:0 周四:0 周五:0 周六:0 周日:0
127.0.0.1:6379> setbit sign 0 1
(integer) 0
127.0.0.1:6379> setbit sign 1 0
(integer) 0
127.0.0.1:6379> setbit sign 2 0
(integer) 0
127.0.0.1:6379> setbit sign 3 0
(integer) 0
127.0.0.1:6379> setbit sign 4 0
(integer) 0
127.0.0.1:6379> setbit sign 5 0
(integer) 0
127.0.0.1:6379> setbit sign 6 0
(integer) 0
查看某一天是否打卡
127.0.0.1:6379> getbit sign 2
(integer) 0
127.0.0.1:6379> getbit sign 0
(integer) 1
统计打卡数 -- BITCOUNT key [start end]
127.0.0.1:6379> bitcount sign 0 6
(integer) 1
3.事务
原子性:要么都成功,要么都失败
Redis单条命令是保证原子性的,但事务不保证原子性
Redis事务本质:一组命令的集合!一个事务中的所有命令都会被序列化,事务在执行中会按顺序执行。(一次性、顺序性、排他性)
队列 : set set set get 按顺序执行
Redis事务没有隔离级别的概念
所有命令在事务中,并没有直接被执行!只有发起执行命令时才会执行 EXEC
redis的事务:
- 开启事务(MULTI)
- 命令入队(……)
- 执行事务(EXEC)
正常执行事务
127.0.0.1:6379> multi #开启事务
OK
# 命令入队
127.0.0.1:6379(TX)> set k1 v1
QUEUED
127.0.0.1:6379(TX)> set k2 v2
QUEUED
127.0.0.1:6379(TX)> get k1
QUEUED
127.0.0.1:6379(TX)> set k3 v3
QUEUED
127.0.0.1:6379(TX)> exec # 执行事务
1) OK
2) OK
3) "v1"
4) OK
放弃事务
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> set k4 v4
QUEUED
127.0.0.1:6379(TX)> set k1 v1
QUEUED
127.0.0.1:6379(TX)> discard # 取消事务,队列会消失
OK
编译型异常(代码有问题!命令有错误!),事务中所有命令都不会执行
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> sett k1 #错误的命令
(error) ERR unknown command `sett`, with args beginning with: `k1`,
127.0.0.1:6379(TX)> get k1 #执行不了
QUEUED
127.0.0.1:6379(TX)> exec #事务也是报错的
(error) EXECABORT Transaction discarded because of previous errors.
运行时异常( 1/0 ),如果队列事务中存在语法性错误,在执行命令时,其他命令可以正常执行,错误命令抛出异常
127.0.0.1:6379> set k1 "v1" #设置其值为字符串
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> incr k1 #字符串无法自增1,所以在运行时会报错
QUEUED
127.0.0.1:6379(TX)> set k2 v2
QUEUED
127.0.0.1:6379(TX)> get k1
QUEUED
127.0.0.1:6379(TX)> get k2
QUEUED
127.0.0.1:6379(TX)> exec
1) (error) ERR value is not an integer or out of range #除了这个有错误,其他还能正常执行
2) OK
3) "v1"
4) "v2"
这也验证了redis单条命令有原子性,事务无法保证原子性
4.监控(Watch)(乐观锁)
-
悲观锁:无论做什么都加锁
-
乐观锁:做什么事都不加锁
- 获取version
- 更新时比较version
正常运行成功
127.0.0.1:6379> set money 100
OK
127.0.0.1:6379> set out 0
OK
127.0.0.1:6379> watch money #监视 money 对象
OK
127.0.0.1:6379> multi #事务正常结束,数据期间没有发生变动,这个时候就正常执行
OK
127.0.0.1:6379(TX)> decrby money 20
QUEUED
127.0.0.1:6379(TX)> incrby out 20
QUEUED
127.0.0.1:6379(TX)> exec
1) (integer) 80
2) (integer) 20
测试多线程修改值,使用watch可以当做redis乐观锁操作
线程A:
127.0.0.1:6379> watch money
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> decrby money 20
QUEUED
127.0.0.1:6379(TX)> incrby out 20
QUEUED
# 执行到此处时先不运行事务,先到线程B中修改money的值,
# 在执行之前,另外一个线程修改了我们监视的 money 的值,这时候会导致我们执行事务失败
127.0.0.1:6379(TX)> exec
(nil)
线程B:
127.0.0.1:6379> get money
"180"
# 修改money的值
127.0.0.1:6379> set money 300
OK
错误后,如果想修改(如果失败,获取最新值就好)
127.0.0.1:6379> unwatch
OK
127.0.0.1:6379> watch money
OK
•~~~~
重新开启事务,添加命令,执行事务
5. 订阅和发布
- 打开一个客户端订阅channel
subscribe channel
- 打开另外一个客户端,给 channel发布消息 hello
publish channel value
\