redis 默认有 16 个数据库

image-20240105143208746

默认使用的是第 0 个

可以使用 select 进行切换数据库,dbsize 查看当前数据库大小

127.0.0.1:6379> select 3 #切换数据库
OK
127.0.0.1:6379[3]> dbsize #查看当前 DB 大小
(integer) 0
127.0.0.1:6379[3]>

不同的数据库可以存储不同的值 (不互通) 1 中 不能访问到 2 中的数据

127.0.0.1:6379> select 3
OK
127.0.0.1:6379[3]> dbsize
(integer) 0
127.0.0.1:6379[3]> set name dkx
OK
127.0.0.1:6379[3]> dbsize
(integer) 1
127.0.0.1:6379[3]> select 0
OK
127.0.0.1:6379> get name
(nil)
127.0.0.1:6379> select 3
OK
127.0.0.1:6379[3]> get name
"dkx"
127.0.0.1:6379[3]>

查看当前数据库所有的 key: keys *

127.0.0.1:6379> keys * #这个库是空的下面的 key 都是测试工具测试的时候添加的
1) "mylist"
2) "key:__rand_int__"
3) "counter:__rand_int__"
127.0.0.1:6379> select 3
OK
127.0.0.1:6379[3]> keys *
1) "user"
127.0.0.1:6379[3]>

清空当前数据库: flushdb (在数据库 3 中使用 flushdb, 数据库 3 中数据全被清除,数据库 0 中的数据不受影响)

127.0.0.1:6379[3]> flushdb
OK
127.0.0.1:6379[3]> keys *
(empty list or set)
127.0.0.1:6379[3]>

接上命令我们在 3 号数据库中有数据我们切换到 0 使用命令: flushall (清空所有数据库)

127.0.0.1:6379[3]> select 0
OK
127.0.0.1:6379> flushall
OK
127.0.0.1:6379> select 3
OK
127.0.0.1:6379[3]> keys *
(empty list or set)
127.0.0.1:6379[3]>

判断指定 key 是否存在: exists

127.0.0.1:6379> exists user
(integer) 0
127.0.0.1:6379> set user lisi
OK
127.0.0.1:6379> exists user
(integer) 1
127.0.0.1:6379>

设置 key 的过期时间: expire (翻译 expire : 到期) 过期后指定的 key 就会被删除

查看 key 的有效期剩余秒数: ttl

127.0.0.1:6379> expire name 10
(integer) 1
127.0.0.1:6379> ttl name
(integer) 6
127.0.0.1:6379> get name
"lisi"
127.0.0.1:6379> ttl name
(integer) -2
127.0.0.1:6379> get name
(nil)
127.0.0.1:6379>

移动当前的 key 到指定数据库: move

127.0.0.1:6379> set name a
OK
127.0.0.1:6379> get name
"a"
127.0.0.1:6379> move name 3
(integer) 1
127.0.0.1:6379> get name
(nil)
127.0.0.1:6379> select 3
OK
127.0.0.1:6379[3]> get name
"a"
127.0.0.1:6379[3]>

查看当前 key 的数据类型: type (查看 key 不存在返回 "none")

127.0.0.1:6379> get name
"liusang"
127.0.0.1:6379> type name
string
127.0.0.1:6379>

<font style="color:red">incr,decr,incrby,decrby 只能用于 String 类型的 key</font>

将数值加 1: incr (直接使用可以创建一个 String 类型的 key 比如执行: incr ar 这里的 ar 并不存在,创建一个 1 的 number)

将数值减 1: decr (直接使用可以创建一个 String 类型的 key 比如执行: decr ar 这里的 ar 并不存在,创建一个 - 1 的 number)

127.0.0.1:6379> set number 0
OK
127.0.0.1:6379> get number
"0"
127.0.0.1:6379> incr number
(integer) 1
127.0.0.1:6379> get number
"1"
127.0.0.1:6379> decr number
(integer) 0
127.0.0.1:6379> get number
"0"
127.0.0.1:6379>

将数值增加指定数量: incrby

将数值减少指定数量: decrby

127.0.0.1:6379> get number
"0"
127.0.0.1:6379> incrby number 10
(integer) 10
127.0.0.1:6379> get number
"10"
127.0.0.1:6379> decrby number 11
(integer) -1
127.0.0.1:6379> get number
"-1"
127.0.0.1:6379>

redis 是单线程的!

明白 Redis 是很快的,官方表示,Redis 是基于内存操作,CPU 不是 Redis 性能瓶颈,Redis 的瓶颈是根据机器的内存和网络带宽,既然可以使用单线程来实现,就使用单线程了!所以就使用了单线程了

Redis 是 C 语言写的,官方提供的数据为 100000 + 的 QPS, 说明完全不比同样是使用 key-value 的 Memecache 差!

Redis 为什么单线程还这么快?

1. 误区 1: 高性能的服务器一定是多线程的?

2. 误区 2: 多线程 (CPU 上下文切换) 一定比单线程效率高

CPU>内存>硬盘 的速度

核心:redis 是将所有的数据全部放在内存中,所以说使用单线程去操作效率就是最高的,多线程 (CPU 上下文切换: 耗时操作!!!), 对于内存系统来说,如果没有上下文切换效率就是最高的!多次读写都是在一个 CPU 上的,在内存情况下,这个就是最佳方案!

# 五大数据类型

# String (字符串)

获取字符串的字符长度 strlen

127.0.0.1:6379> set user "liusang"
OK
127.0.0.1:6379> strlen user
(integer) 7
127.0.0.1:6379>

追加字符串 (如果当前 key 不存在那么就创建): append key value (只能对 String 类型使用)

127.0.0.1:6379> get name
"liusang"
127.0.0.1:6379> append name "shiwonanshen!"
(integer) 20
127.0.0.1:6379> get name
"liusangshiwonanshen!"
127.0.0.1:6379>

截取指定区间的字符串范围 (start:0 end:-1 表示全部): getrange

127.0.0.1:6379> set name liusangshiwonanshen
OK
127.0.0.1:6379> get name
"liusangshiwonanshen"
127.0.0.1:6379> getrange name 0 4
"liusa"
127.0.0.1:6379> getrange name 0 -1
"liusangshiwonanshen"
127.0.0.1:6379>

替换指定位置开始的字符串: setrange

127.0.0.1:6379> get name
"liusangshiwonanshen"
127.0.0.1:6379> setrange name 2 ****
(integer) 19
127.0.0.1:6379> get name
"li****gshiwonanshen"
127.0.0.1:6379>

设置过期时间: setex

127.0.0.1:6379> setex name 10 "hello"#设置hello10秒后过期
OK
127.0.0.1:6379> ttl name
(integer) 3
127.0.0.1:6379> get name
"hello"
127.0.0.1:6379> ttl name
(integer) -2
127.0.0.1:6379> get name
(nil)
127.0.0.1:6379>

不存在则创建,存在则不创建 (在分布式锁中会常常使用): setnx

127.0.0.1:6379> setnx myname zhangsan #如果 myname 不存在则创建
(integer) 1
127.0.0.1:6379> setnx myname zhangsan #myname 存在则创建失败
(integer) 0
127.0.0.1:6379> get myname
"zhangsan"
127.0.0.1:6379>

同时设置多个值: mset

同时获取多个值: mget (不是只能同时获取 mset 创建的 key 其它的是 String 类型就可以)

127.0.0.1:6379> keys *
(empty list or set)
127.0.0.1:6379> mset k1 v1 k2 v2 k3 v3 #同时设置多个值
OK
127.0.0.1:6379> mget k1 k2 k3 #同时获取多个值
1) "v1"
2) "v2"
3) "v3"
127.0.0.1:6379> msetnx k1 v1 k4 v4 #配合 setnx 如果有存在的 key 则条件不成立
(integer) 0
127.0.0.1:6379> mget k4 #没有创建成功
1) (nil)
127.0.0.1:6379>

# 对象

127.0.0.1:6379> set user:1 {name:lisi,age:18} #设置一个 user:1 对象 值为 json 字符串来保存一个对象
OK
127.0.0.1:6379> get user:1
"{name:lisi,age:18}"
#这里的 key 是一个巧妙的设计: user:{id}:{filed} , 如此设计在 Redis 中是完全 OK 了
127.0.0.1:6379> mset user:1:name lisi user:1:age 1
OK
127.0.0.1:6379> mget user:1:name user:1:age
1) "lisi"
2) "1"
127.0.0.1:6379>

先获取再设置值: getset

127.0.0.1:6379> getset dkx niubi 
(nil)
127.0.0.1:6379> get dkx
"niubi"
127.0.0.1:6379> getset dkx 666
"niubi"
127.0.0.1:6379> get dkx
"666"
127.0.0.1:6379>

数据结构是相通的!

String 类似的使用场景: value 除了字符串还可以是数字!

# List

在 redis 中,我们可以把 list 玩成,栈,队列,阻塞队列

跟 String 语法类似,所有的 list 命令都是用 l , 字母 l 开头的,<font style="color:green"> 添加 :lpush 前,rpush 后 </font> | <font style="color:red"> 移除并返回 /lpop 前 /rpop 后 </font>

image-20240105143226491

127.0.0.1:6379> lpush list a
(integer) 1
127.0.0.1:6379> lpush list b
(integer) 2
127.0.0.1:6379> rpush list c
(integer) 3
127.0.0.1:6379> lpop list
"b"
127.0.0.1:6379> rpop list
"c"
127.0.0.1:6379> lpop list
"a"
127.0.0.1:6379>

通过索引获取值: lindex

127.0.0.1:6379> lpush list a
(integer) 1
127.0.0.1:6379> lpush list b
(integer) 2
127.0.0.1:6379> lpush list c
(integer) 3
127.0.0.1:6379> lindex list 2
"a"
127.0.0.1:6379>

获取长度: llen

127.0.0.1:6379> lpush name a
(integer) 1
127.0.0.1:6379> llen name
(integer) 1
127.0.0.1:6379>

移除列表中的 key: lrem key

指定范围获取列表中的值: lrange start end

127.0.0.1:6379> lpush name a
(integer) 1
127.0.0.1:6379> lpush name b
(integer) 2
127.0.0.1:6379> lpush name a
(integer) 3
127.0.0.1:6379> lrange name 0 -1
1) "a"
2) "b"
3) "a"
127.0.0.1:6379> lrem name 2 a
(integer) 2
127.0.0.1:6379> lrange name 0 -1
1) "b"

截断,保留列表中截断的值: ltrim key start end

127.0.0.1:6379> lpush name a
(integer) 1
127.0.0.1:6379> lpush name b
(integer) 2
127.0.0.1:6379> lpush name c
(integer) 3
127.0.0.1:6379> lpush name d
(integer) 4
127.0.0.1:6379> ltrim name 1 2 #通过下标截取指定的长度,这个 list 已经被改变了,截断了只剩下截取的元素!
OK
127.0.0.1:6379> lrange name 0 -1 #查看全部元素
1) "c"
2) "b"
127.0.0.1:6379>

image-20240105143241383

移除列表的最后一个元素,将它移动到新的列表中,如果移动到列表不存在则创建: rpoplpush key source distination

翻译: source (根源) , distination (目的地)

  • <font style="color:red"> 只有: rpoplpush 格式没有 lpoprpush 或者 lpoplpush 的格式 </font>
127.0.0.1:6379> lrange name 0 -1
1) "c"
2) "b"
127.0.0.1:6379> rpoplpush name nametwo # 移除并移动到指定列表中指定列表不存在则创建
"b"
127.0.0.1:6379> lrange name 0 -1 # 查看原来的列表
1) "c"
127.0.0.1:6379> lrange nametwo 0 -1 # 查看新的列表
1) "b"
127.0.0.1:6379>

将列表中指定存在的下标的值替换为另外一个值. 更新操作: lset key index value

127.0.0.1:6379> exists list # 判断列表是否存在
(integer) 0
127.0.0.1:6379> lset list 0 a # 视图向不存在的列表中添加值
(error) ERR no such key
127.0.0.1:6379> lpush name a # 向 list 中添加值
(integer) 1
127.0.0.1:6379> lrange name 0 -1 # 查看列表的值
1) "a"
127.0.0.1:6379> lset name 0 liusang # 通过 lset 指定下标替换值
OK
127.0.0.1:6379> lrange name 0 -1 # 查看列表的值
1) "liusang"
127.0.0.1:6379> lset name 1 lisi # 视图在存在的列表中设置不存在的索引值
(error) ERR index out of range
127.0.0.1:6379>

将给定 value 插入到指定 value 的 before (之前)者 after (之后): linsert key [before|after] pivot value

翻译: (pivot : 支点)

127.0.0.1:6379> lrange name 0 -1
1) "a"
127.0.0.1:6379> linsert name before a niubi #将 niubi 插入到 a 的之前
(integer) 2
127.0.0.1:6379> lrange name 0 -1
1) "niubi"
2) "a"
127.0.0.1:6379> linsert name after a 666 #将 666 插入到 a 的之后
(integer) 3
127.0.0.1:6379> lrange name 0 -1
1) "niubi"
2) "a"
3) "666"
127.0.0.1:6379>

小结

  • 它实际上是一个链表,before Node after , left, right (前后,左右) 都可以插入值
  • 如果 key 不存在,插入的话就会创建新的列表
  • 如果 key 存在,插入的话就会新增内容
  • 如果移除了所有值,空列表,也代表不存在!
  • 在 <u> 两边插入或者改动值,效率最高!中间元素,相对来说效率会低 </u> 一点

<u> 消息队列 (lpush rpop), 栈 (lpush lpop)</u>

# Set (集合):set 中的值是不能重复

单词: member (成员)

向集合中添加元素: sadd key value

查看集合中所有元素: smembers key

查看集合中是否包含该元素: sismember key value

127.0.0.1:6379> sadd myset liusang #向集合中添加元素
(integer) 1
127.0.0.1:6379> sadd myset niubi
(integer) 1
127.0.0.1:6379> smembers myset #查看集合中所有元素
1) "niubi"
2) "liusang"
127.0.0.1:6379> sismember myset niubi #查看集合中是否包含该元素
(integer) 1
127.0.0.1:6379> sismember myset 123
(integer) 0
127.0.0.1:6379>

查看集合中元素个数: scard key

127.0.0.1:6379> smembers myset
1) "niubi"
2) "liusang"
127.0.0.1:6379> scard myset
(integer) 2
127.0.0.1:6379>

移除指定元素: srem key value

127.0.0.1:6379> smembers myset
1) "bbb"
2) "aaa"
3) "liusang"
127.0.0.1:6379> srem myset aaa
(integer) 1
127.0.0.1:6379> smembers myset
1) "bbb"
2) "liusang"
127.0.0.1:6379>

随机抽取指定 count 个元素: srandmember key count (默认只抽取一个出来)

127.0.0.1:6379> smembers myset
1) "aaa"
2) "ccc"
3) "bbb"
4) "liusang"
127.0.0.1:6379> srandmember myset
"ccc"
127.0.0.1:6379> srandmember myset
"ccc"
127.0.0.1:6379> srandmember myset
"liusang"
127.0.0.1:6379> srandmember myset
"ccc"
127.0.0.1:6379> srandmember myset
"liusang"
127.0.0.1:6379> srandmember myset
"ccc"
127.0.0.1:6379> srandmember myset
"liusang"
127.0.0.1:6379> srandmember myset
"ccc"
127.0.0.1:6379> srandmember myset
"liusang"
127.0.0.1:6379> srandmember myset
"liusang"
127.0.0.1:6379> srandmember myset
"aaa"
127.0.0.1:6379> srandmember myset
"ccc"
127.0.0.1:6379> srandmember myset 2
1) "aaa"
2) "liusang"
127.0.0.1:6379> srandmember myset 2
1) "bbb"
2) "ccc"
127.0.0.1:6379>

随机弹出元素: spop key (弹出列表后相当于从列表中删除)

127.0.0.1:6379> smembers myset
1) "aaa"
2) "ccc"
3) "bbb"
4) "liusang"
127.0.0.1:6379> spop myset
"liusang"
127.0.0.1:6379> spop myset
"aaa"
127.0.0.1:6379> spop myset
"bbb"
127.0.0.1:6379> smembers myset
1) "ccc"
127.0.0.1:6379>

将指定的元素移动到另外指定的数据库集合中: smove source distination member (指定的数据库不存在则创建)

127.0.0.1:6379> smembers myset
1) "bbb"
2) "aaa"
3) "ccc"
127.0.0.1:6379> smove myset myset1 aaa
(integer) 1
127.0.0.1:6379> smembers myset
1) "bbb"
2) "ccc"
127.0.0.1:6379> smembers myset1
1) "aaa"
127.0.0.1:6379> keys * 
1) "myset"
2) "myset1"
127.0.0.1:6379>

差集: sdiff : 假设有集合 A 和 B, 所有属于 A 且不属于 B 的元素的集合被称为 A 与 B 的差集

  • <u> 示例:对于集合 A={a,<font style="color:red"> b</font>, <font style="color:red">c</font>, d} 和集合 B={<font style="color:red">b</font>, <font style="color:red">c</font>, w}, 则 A 与 B 的差集为 {a, d}</u>

交集: sinter : 两个集合之间相同的元素

  • <u> 示例:对于集合 A={a,<font style="color:red"> b</font>, <font style="color:red">c</font>, d} 和集合 B={<font style="color:red">b</font>, <font style="color:red">c</font>, w}, 则 A 与 B 的交集为 {b,c}</u>

并集: sunion : 合并两个集合中去除相同元素 (<u> 并不是说删除,而是不算重复的保留一个 </u>) 后的整合

  • <u> 示例:对于集合 A={a,<font style="color:red"> b</font>, <font style="color:red">c</font>, d} 和集合 B={<font style="color:red">b</font>, <font style="color:red">c</font>, w}, 则 A 与 B 的交集为 {a,b,c,d,w}</u>
127.0.0.1:6379> sadd k1 a
(integer) 1
127.0.0.1:6379> sadd k1 b
(integer) 1
127.0.0.1:6379> sadd k1 c
(integer) 1
127.0.0.1:6379> sadd k2 c
(integer) 1
127.0.0.1:6379> sadd k2 d
(integer) 1
127.0.0.1:6379> sadd k2 e
(integer) 1
127.0.0.1:6379> sdiff k1 k2
1) "b"
2) "a"
127.0.0.1:6379> sinter k1 k2
1) "c"
127.0.0.1:6379> sunion k1 k2
1) "b"
2) "a"
3) "c"
4) "e"
5) "d"
127.0.0.1:6379>

# Hash (哈希集合)

Map 集合,key-Map 集合,值是 map 集合!本质和 String 类型没有太大区别,还是一个简单的 key-value 而,value 是可以由两个值组成

向集合中添加一个字段值对: hset key field value field1 value1 ...

通过键字段获取值: hget key field

向集合中添加一个键多个字段值: hmset key field value field1 value1 ... (默认的 hset 也可以添加多个)

获取集合中多个值的键的值: hmget key field field1 ...

获取全部数据: hgetall key

删除指定键中指定的字段: hdel key field field1 field2 ...

127.0.0.1:6379> hset h1 k1 niubi
(integer) 1
127.0.0.1:6379> hget h1 k1
"niubi"
127.0.0.1:6379> hmset h1 k1 hello k2 666
OK
127.0.0.1:6379> hmget h1 k1 k2
1) "hello"
2) "666"
127.0.0.1:6379> hgetall h1
1) "k1"
2) "hello"
3) "k2"
4) "666"
127.0.0.1:6379> hdel h1 k1
(integer) 1
127.0.0.1:6379> hgetall h1
1) "k2"
2) "666"
127.0.0.1:6379>

获取元素个数,一对 field value 为一个个数: hlen key

127.0.0.1:6379> hgetall h1
1) "k2"
2) "666"
3) "k3"
4) "niubi666"
127.0.0.1:6379> hlen h1
(integer) 2
127.0.0.1:6379>

判断集合指定的键中是否存在该字段: hexists key field

127.0.0.1:6379> hexists h1 k1
(integer) 0
127.0.0.1:6379> hexists h1 k2
(integer) 1
127.0.0.1:6379> keys *
1) "h1"
127.0.0.1:6379> hgetall h1
1) "k2"
2) "666"
3) "k3"
4) "niubi666"
127.0.0.1:6379>

获取指定键中所有的 field: hkeys key

获取指定键中所有的 field 的 value: hvals key

127.0.0.1:6379> hgetall h1
1) "k2"
2) "666"
3) "k3"
4) "niubi666"
127.0.0.1:6379> hkeys h1
1) "k2"
2) "k3"
127.0.0.1:6379> hvals h1
1) "666"
2) "niubi666"
127.0.0.1:6379>

指定增加数量 (如果 count 给定为负数那么就是减): hincrby key field count (如果 key 不存在则创建)

判断如果不存在字段则添加,如果存在则不添加: hsetnx key field value

127.0.0.1:6379> hset h1 k1 5
(integer) 1
127.0.0.1:6379> hincrby h1 k1 1
(integer) 6
127.0.0.1:6379> hincrby h1 k1 -1
(integer) 5
127.0.0.1:6379> hsetnx h1 k1 hello
(integer) 0
127.0.0.1:6379> hget h1 k1
"5"
127.0.0.1:6379> hsetnx h1 k2 hello
(integer) 1
127.0.0.1:6379> hget h1 k2
"hello"
127.0.0.1:6379>

hash 变更的数据 user, name, age, 尤其是用户信息之类的,经常变动的信息!hash 更适合于对象的存储String 更适合字符串存储

# Zset (有序集合)

向集合中添加元素: zadd key NX|XX 前者按编号排序,后者存储 value

按照指定范围获取集合中的元素: zrange key start end

127.0.0.1:6379> zadd h1 1 a
(integer) 1
127.0.0.1:6379> zadd h1 2 b
(integer) 1
127.0.0.1:6379> zadd h1 3 e
(integer) 1
127.0.0.1:6379> zadd h1 4 c
(integer) 1
127.0.0.1:6379> zrange h1 0 -1
1) "a"
2) "b"
3) "e"
4) "c"
127.0.0.1:6379>

查询指定的键从最小值到最大值排序获取 (-inf 不能大于 + inf 否则报错 (+inf 可指定条件)): zrangebyscore

127.0.0.1:6379> zadd h1 5000 xiaogong #添加三个用户分别有工资
(integer) 1
127.0.0.1:6379> zadd h1 3500 dagong
(integer) 1
127.0.0.1:6379> zadd h1 2500 liuhong
(integer) 1
127.0.0.1:6379> zadd h1 500 dazuo
(integer) 1
127.0.0.1:6379> zrange h1 0 -1 #查询所有的用户,默认排序按降序
1) "dazuo"
2) "liuhong"
3) "dagong"
4) "xiaogong"
127.0.0.1:6379> zrangebyscore h1 -inf +inf #排序查询 从负∞ 到正∞
1) "dazuo"
2) "liuhong"
3) "dagong"
4) "xiaogong"
127.0.0.1:6379> zrangebyscore h1 -inf 2500 #查询 从负∞ 到指定的 2500 的工资为止
1) "dazuo"
2) "liuhong"
127.0.0.1:6379>

通过键以升序获取元素 (反转查询结果): zrevrange key start end

127.0.0.1:6379> zrangebyscore h1 -inf +inf withscores
1) "dazuo"
2) "500"
3) "liuhong"
4) "2500"
5) "xiaogong"
6) "5000"
127.0.0.1:6379> zrevrange h1 0 -1
1) "xiaogong"
2) "liuhong"
3) "dazuo"
127.0.0.1:6379>

通过键获取元素并附带分数 (withscores): zrangebyscore key -inf +inf withscores

翻译:带分数 withscores

127.0.0.1:6379> zrangebyscore h1 -inf +inf withscores
1) "dazuo"
2) "500"
3) "liuhong"
4) "2500"
5) "xiaogong"
6) "5000"
127.0.0.1:6379>

移除指定键中的 member: zrem key member

127.0.0.1:6379> zrange h1 0 -1
1) "dazuo"
2) "liuhong"
3) "dagong"
4) "xiaogong"
127.0.0.1:6379> zrem h1 dagong
(integer) 1
127.0.0.1:6379> zrange h1 0 -1
1) "dazuo"
2) "liuhong"
3) "xiaogong"
127.0.0.1:6379>

通过键获取集合中的元素个数: zcard key

127.0.0.1:6379> zrange h1 0 -1
1) "dazuo"
2) "liuhong"
3) "xiaogong"
127.0.0.1:6379> zcard h1
(integer) 3
127.0.0.1:6379>

获取指定区间的成员数量: zcount key start end

127.0.0.1:6379> zrange h1 0 -1 withscores
1) "dazuo"
2) "500"
3) "liuhong"
4) "2500"
5) "xiaogong"
6) "5000"
127.0.0.1:6379> zcount h1 500 5000 #从 500 到 5000 的存在的元素的个数
(integer) 3
127.0.0.1:6379>
  • 区分总结: set 与 zset 都是 value 不可重复的
    • 其中 String , Hash 添加命令都是 set 而 Set , Zset 添加命令是 add
    • 其中获取元素个数:

# 三种特殊数据类型

# geospatial 地理位置

<u> 朋友的定位,附近的人,打车距离计算 </u>

Redis 的 Geo 在 Redis3.2 版本就推出了,这个功能可以 <u> 推算地理位置的信息两地之间的距离方圆几里的人 </u>

geoadd

#geoadd 添加地理位置
#规则:两级无法直接添加,我们一般会下载城市数据,直接通过 java 程序一次性导入
#参数: key  值 (经度,纬度,名称)
#有效的经度从 - 180 度到 180 度
#有效的纬度从 - 85.05112878 度到 85.05112878 度
#当坐标位置超出上述指定范围时,该命令将会返回一个错误,添加时注意以下的格式说明
#											  经度  | 纬度
127.0.0.1:6379> geoadd china:city 116.40 39.90 beijing
(integer) 1
127.0.0.1:6379>

geopos

获得当前定位:一定是一个坐标值

#获取指定的城市的经度,纬度
127.0.0.1:6379> geopos china:city beijing
1) 1) "116.39999896287918091"
   2) "39.90000009167092543"
127.0.0.1:6379> geopos china:city beijing congqi
1) 1) "116.39999896287918091"
   2) "39.90000009167092543"
2) 1) "106.49999767541885376"
   2) "29.52999957900659211"

geodist : 两个城市的直线距离

返回两个给定位置之间的距离

单位:

  • m: 表示单位为米
  • km: 表示单位为千米
  • mi: 表示单位为英里
  • ft: 表示单位为英尺

北京与重庆默认的距离 (重庆拼音打错了)

127.0.0.1:6379> geodist china:city beijing congqi
"1464070.8051"
127.0.0.1:6379>
127.0.0.1:6379> geodist china:city beijing congqi m #北京到重庆的直线距离以米为单位
"1464070.8051"
127.0.0.1:6379> geodist china:city beijing congqi km #北京到重庆的直线距离以千米为单位
"1464.0708"
127.0.0.1:6379> geodist china:city beijing congqi mi #北京到重庆的直线距离以英里为单位
"909.7337"
127.0.0.1:6379> geodist china:city beijing congqi ft #北京到重庆的直线距离以英尺为单位
"4803381.9063"
127.0.0.1:6379>

georadius 以给定的经纬度为中心,找出某一半径内的元素

我附近的人?(获得所有附近的人的地址,定位!) 通过半径来查询

获得指定数量的人,200

127.0.0.1:6379> georadius china:city 110 30 1000 km #获得以 110 , 30 这个经纬度为中心,方圆 1000km (千米) 内的城市
1) "congqi"
2) "hangzhou"
127.0.0.1:6379> georadius china:city 110 30 1000 km withdist #显示两个城市的直线距离
1) 1) "congqi"
   2) "341.9374"
2) 1) "hangzhou"
   2) "977.5143"
127.0.0.1:6379> georadius china:city 110 30 1000 km withcoord #显示两个城市的经度纬度
1) 1) "congqi"
   2) 1) "106.49999767541885376"
      2) "29.52999957900659211"
2) 1) "hangzhou"
   2) 1) "120.1600000262260437"
      2) "30.2400003229490224"
127.0.0.1:6379> georadius china:city 110 30 1000 km withdist withcoord count 1 #限制显示数量
1) 1) "congqi"
   2) "341.9374"
   3) 1) "106.49999767541885376"
      2) "29.52999957900659211"
127.0.0.1:6379> georadius china:city 110 30 1000 km withdist withcoord count 2
1) 1) "congqi"
   2) "341.9374"
   3) 1) "106.49999767541885376"
      2) "29.52999957900659211"
2) 1) "hangzhou"
   2) "977.5143"
   3) 1) "120.1600000262260437"
      2) "30.2400003229490224"
127.0.0.1:6379>

georadiusbymember 以某一城市为中心,查找指定范围内的其它城市

翻译: radiusbymember 半径成员

#以某一城市为中心,查找指定范围内的其它城市
127.0.0.1:6379> georadiusbymember china:city beijing 1300 km
1) "hangzhou"
2) "beijing"
127.0.0.1:6379> georadiusbymember china:city beijing 1500 km
1) "congqi"
2) "hangzhou"
3) "beijing"
127.0.0.1:6379> georadiusbymember china:city beijing 1000 km
1) "beijing"
127.0.0.1:6379>

geohash 返回一个或多个位置元素的 geohash 表示

该命令将返回 11 个字符的 geohash 字符串

#将二维的经纬度转换为一维的字符串,如果两个字符串越相似就代表越近
127.0.0.1:6379> geohash china:city beijing hangzhou
1) "wx4fbxxfke0"
2) "wtmkn31bfb0"
127.0.0.1:6379> geohash china:city beijing congqi hangzhou
1) "wx4fbxxfke0"
2) "wm5xzrybty0"
3) "wtmkn31bfb0"
127.0.0.1:6379>
  • geo 底层的实现原理其实就是 Zset, 我们可以使用 Zset 命令来操作 geo
127.0.0.1:6379> zrange china:city 0 -1 #查看地图中全部元素
1) "congqi"
2) "hangzhou"
3) "beijing"
127.0.0.1:6379> zrem china:city congqi #将指定元素从地图中删除
(integer) 1
127.0.0.1:6379> zrange china:city 0 -1
1) "hangzhou"
2) "beijing"
127.0.0.1:6379>

# Hyperloglog

什么是基数?

A

B

基数 (不重复的元素):B {1, 3, 5, 7, 8} = 5 , 可以接受误差!

简介

Redis2.8.9 版本就更新了 Hyperloglog 数据结构

Redis Hyperloglog 基数统计的算法

优点:占用的内存是固定的,2^64 不同的元素的基数,只需要废 12KB 内存,如果要从内存角度来比较的话 Hyperloglog 首选!

网页的 UV (一个人访问一个网站多次,但是还是算作一个人) 视频浏览量

传统的方式,set 保存用户的 id, 然后就可以统计 set 中的元素数量作为标准判断

这个方式如果保存大量的 id, 就会比较麻烦,我们的目的是为了计数,而不是保存用户 id

127.0.0.1:6379> PFadd Hyper a b c d e #创建第一组元素
(integer) 1
127.0.0.1:6379> PFadd Hyper1 f g h i j k  #创建第二组元素
(integer) 1
127.0.0.1:6379> PFcount Hyper #查看第一组元素个数
(integer) 5
127.0.0.1:6379> PFcount Hyper1 #查看第二组元素个数
(integer) 6
127.0.0.1:6379> PFmerge Hyper2 Hyper Hyper1 #将两组元素以基数进行合并
OK
127.0.0.1:6379> PFcount Hyper2 #查看第一组与第二组合并后的数量
(integer) 11
127.0.0.1:6379> PFadd Hyper4 a b c i m k #创建第三组元素
(integer) 1
127.0.0.1:6379> PFmerge Hyper5 Hyper Hyper4 #将第一组与第三组以基数进行合并
OK
127.0.0.1:6379> PFcount Hyper5 #查看合并后的数量
(integer) 8
127.0.0.1:6379>

如果允许容错,那么一定可以使用 Hyperloglog

如果不允许容错,就使用 set 或者自己的数据类型即可

# Bitmaps

位存储

统计疫情感染人数: 0 1 0 1 0 : 1, 被感染 0, 正常

统计用户信息,活跃,不活跃,登录,未登录,打卡,365 打卡,两个状态的都可以使用 Bitmaps

Bitmaps 位图,数据结构!都是操作二进制来进行记录,就只有 0 和 1 两个状态!

365 天 = 365bit 1 字节 = 8bit 接近用户一年的打卡情况为 46 个字节左右

使用 bitmaps 来记录,周一到周日的打卡情况!

周一:1 周二:1 周三:0 周四:1 周五:0 周六:0 周日:1

127.0.0.1:6379> setbit sign 0 1
(integer) 0
127.0.0.1:6379> setbit sign 1 1
(integer) 0
127.0.0.1:6379> setbit sign 2 0
(integer) 0
127.0.0.1:6379> setbit sign 3 1
(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 1
(integer) 0
127.0.0.1:6379>

查看某一天是否有打卡!

127.0.0.1:6379> getbit sign 1
(integer) 1
127.0.0.1:6379> getbit sign 2
(integer) 0
127.0.0.1:6379> getbit sign 6
(integer) 1
127.0.0.1:6379>

统计操作,统计 打卡的天数!

127.0.0.1:6379> bitcount sign 0 -1
(integer) 4
127.0.0.1:6379> bitcount sign
(integer) 4
127.0.0.1:6379>

# 事务

Redis 事务本质:一组命令的集合!一个事务中的所有命令都会被序列化,在事务执行过程中,会按照顺序执行!

一次性,顺序型,排它性!执行一系列的命令!

----队列 set set set 执行----

原子性:要么同时成功,要么同时失败

<font color='red'>Redis 事务没有隔离级别的概念 </font>

所有的命令在事务中,并没有直接被执行,只有发起执行命令的时候才会执行!<font color='red' size=4> 执行命令:Exec</font>

<font color='red'>Redis 单条命令是保证原子性的但是 Redis 的事务是不保证原子性!</font>

Redis 的事务:每次执行完毕后事务就会消失,需要再次开启事务

  • 开启事务 (multi)
  • 命令入队 (...)
  • 执行事务 (exec)

正常执行事务!

127.0.0.1:6379> multi #开启事务
OK
127.0.0.1:6379> set k1 v1 #添加键值
QUEUED # 命令入队
127.0.0.1:6379> set k2 v2 #添加键值
QUEUED
127.0.0.1:6379> get k2 #通过键获取值
QUEUED
127.0.0.1:6379> set k3 v3 #添加键值
QUEUED
127.0.0.1:6379> exec #执行事务
1) OK
2) OK
3) "v2"
4) OK
127.0.0.1:6379>

discard 放弃事务!

翻译: discard 丢弃

127.0.0.1:6379> multi #开启事务
OK
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> set k3 v3
QUEUED
127.0.0.1:6379> discard #放弃事务
OK
127.0.0.1:6379> get k1 #事务队列中的命令都没有被执行
(nil)
127.0.0.1:6379> get k2
(nil)
127.0.0.1:6379> get k3
(nil)
127.0.0.1:6379>

编译型异常 (代码有问题!命令有错!), 事务中所有的命令都不会被执行!

127.0.0.1:6379> multi
OK
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> getset k1
(error) ERR wrong number of arguments for 'getset' command
127.0.0.1:6379> set k3 v3
QUEUED
127.0.0.1:6379> set k4 v4
QUEUED
127.0.0.1:6379> exec
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> get k1
(nil)
127.0.0.1:6379> get k4
(nil)
127.0.0.1:6379>

运行时异常 (1/0), 如果事务队列中存在语法性,那么执行命令的时候,其它命令是可以正常执行的,错误命令抛出异常!

127.0.0.1:6379> set k1 v1
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> incr k1 #给一个字符串进行 + 1 执行的时候报错
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> set k3 v3
QUEUED
127.0.0.1:6379> get k3
QUEUED
127.0.0.1:6379> exec
1) (error) ERR value is not an integer or out of range #虽然第一条命令报错了,但是依旧正常执行流程成功了
2) OK
3) OK
4) "v3"
127.0.0.1:6379> get k2
"v2"
127.0.0.1:6379> get k3
"v3"
127.0.0.1:6379>

监控!Watch (乐观锁) (面试常问)

# 悲观锁:

  • 很悲观,认为什么时候都会出现问题,无论做什么都会加锁

# 乐观锁:

  • 很乐观,认为什么时候都不会出现问题,所以一般不会加锁,更新数据的时候去判断一下,在此期间是否有人修改过这个数据

  • 获取 version

  • 更新的时候比较 version

Redis 监视测试

正常执行成功!

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> decrby money 20 #花掉 20 元
QUEUED
127.0.0.1:6379> incrby out 20 #增加花掉记录 20 元
QUEUED
127.0.0.1:6379> exec #执行事务命令
1) (integer) 80
2) (integer) 20
127.0.0.1:6379>

测试多 Terminal 修改值,使用 watch 可以当作 redis 的乐观锁操作!

#终端一号
127.0.0.1:6379> watch money #监视 money
OK
127.0.0.1:6379> multi #开启事务
OK
127.0.0.1:6379> decrby money 10 #花掉 10 元
QUEUED
127.0.0.1:6379> incrby out 10 #记录花掉 10 元
QUEUED
#终端二号
127.0.0.1:6379> get money #获取余额
"80"
127.0.0.1:6379> set money 1000 #设置为 1000 元
OK
127.0.0.1:6379> get money #查看设置的余额
"1000"
127.0.0.1:6379>
#终端一号
127.0.0.1:6379> exec #执行事务命令
(nil) #exec 执行之前,另外一个线程,修改了我们的值,这个时候,就会导致事务执行失败!
127.0.0.1:6379>
  • <u>unwatch 解开全部的乐观锁 watch, 当执行了 exec 或者 discard 后 watch 也会解锁 </u>

<font style="color:red"> 如果不进行 watch, 使用多线程进行同样操作 </font>

#Terminal 一
127.0.0.1:6379> get monery
"100"
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> decrby monery 20
QUEUED
127.0.0.1:6379(TX)> incrby out  20
QUEUED
127.0.0.1:6379(TX)> exec
#Terminal 二
127.0.0.1:6379> get monery
"100"
127.0.0.1:6379> set monery 100
OK
127.0.0.1:6379> 
#Terminal 一
127.0.0.1:6379(TX)> exec
1) (integer) 80
2) (integer) 20
127.0.0.1:6379> get  monery
"80"
127.0.0.1:6379>