【基础篇】Redis深入理解与实践指南(二)之Redis基础知识扫盲

172 阅读23分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第8天,点击查看活动详情

基础知识

Redis默认有16个数据库

image-20201223175006041

默认使用的是第0个数据库,

可以使用select进行切换数据库:

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

image-20201223175624413

127.0.0.1:6379[3]> keys *  # 查看数据库所有的key
1) "name"
127.0.0.1:6379[3]> 

清除当前数据库 flushdb

清除所有数据库 flushall

127.0.0.1:6379[3]> flushdb
OK
127.0.0.1:6379[3]> keys *
(empty array)
​
127.0.0.1:6379> flushall
OK
127.0.0.1:6379> keys *
(empty array)

思考:为什么redis端口号是6379?是意大利女星MERZ在九宫格上的数字。

Redis是单线程的

Redis是很快点的,官方表示,Redis是基于内存操作,CPU不是Redis的性能瓶颈;Redis的瓶颈是根据机器的内存和网络带宽。

既然可以使用过单线程来实现,所以就使用单线程了!

Redis是用C语言写的,官方提供的数据为11万QPS,完全不比同样是使用Key-value的Memcached差!

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

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

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

速度:CPU>内存>硬盘

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

五大数据类型

官网文档

Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。 它支持多种类型的数据结构,如 字符串(strings)散列(hashes)列表(lists)集合(sets)有序集合(sorted sets) 与范围查询, bitmapshyperloglogs地理空间(geospatial) 索引半径查询。 Redis 内置了 复制(replication)LUA脚本(Lua scripting)LRU驱动事件(LRU eviction)事务(transactions) 和不同级别的 磁盘持久化(persistence), 并通过 Redis哨兵(Sentinel)和自动 分区(Cluster)提供高可用性(high availability)。

现在实践的所有命令自己一定要记住,后面我们使用SpringBoot、Jedis,所有的方法就是这些命令!

Redis-Key

127.0.0.1:6379> keys * # 查看所有的key
1) "name"
127.0.0.1:6379> set age 1  # 在当前数据库(默认是第0个)写入key
OK
127.0.0.1:6379> keys *
1) "name"
2) "age"
127.0.0.1:6379> EXISTS name # 判断当前的key是否存在
(integer) 1
127.0.0.1:6379> EXISTS name1
(integer) 0
127.0.0.1:6379> move name 1 # 移除当前的key
(integer) 1127.0.0.1:6379> keys * 
1) "age"
127.0.0.1:6379> set name peng
OK
127.0.0.1:6379> keys *
1) "name"
2) "age"
127.0.0.1:6379> get name
"peng"
127.0.0.1:6379> expire name 10 # 设置key的过期时间,单位是秒
(integer) 1
127.0.0.1:6379> ttl name # 查看当前key的剩余时间
(integer) 1
127.0.0.1:6379> ttl name
(integer) -2
127.0.0.1:6379> get name
(nil)
127.0.0.1:6379> type name # 查看当前key的类型
string
127.0.0.1:6379> type age
string
127.0.0.1:6379>

String(字符串)

90%的java程序员使用Redis只会使用一个String类型!

###################################################################
127.0.0.1:6379> set key1 v1 # 设置值
OK
127.0.0.1:6379> get key1    # 获得值
"v1"
127.0.0.1:6379> keys *      # 获得所有值
1) "key1"
127.0.0.1:6379> exists key1 # 判断某一个key是否存在
(integer) 1
127.0.0.1:6379> append key1 "hello"  # 追加字符串,如果当前key不存在,则相当于set name
(integer) 7
127.0.0.1:6379> get key1
"v1hello"
127.0.0.1:6379> strlen key1 # 获取字符串的长度
(integer) 7
127.0.0.1:6379> append key1 ",peng"
(integer) 12
127.0.0.1:6379> get key1
"v1hello,peng"###################################################################
# i++
# 步长 i+=
127.0.0.1:6379> set views 0 # 初始浏览量为0
OK
127.0.0.1:6379> get views 
"0"
127.0.0.1:6379> incr views  # 自增1,浏览量变为1    
(integer) 1
127.0.0.1:6379> incr views
(integer) 2
127.0.0.1:6379> get views
"2"
127.0.0.1:6379> type views
string
127.0.0.1:6379> decr views  # 自减1,浏览量-1
(integer) 1
127.0.0.1:6379> decr views
(integer) 0
127.0.0.1:6379> decr views
(integer) -1
127.0.0.1:6379> get views
"-1"
127.0.0.1:6379> INCRBY views 10 # 可以使用步长,指定增量
(integer) 9
127.0.0.1:6379> INCRBY views 10
(integer) 19
127.0.0.1:6379> DECRBY views 5
(integer) 14###################################################################
# 字符串范围 range
127.0.0.1:6379> set key1 "hello,peng"   # 设置key1的值
OK
127.0.0.1:6379> get key1
"hello,peng"
127.0.0.1:6379> GETRANGE key1 0 3       # 获取字符串 【0,3】
"hell"
127.0.0.1:6379> GETRANGE key1 0 -1      # 获取全部的字符串和get key是一样的
"hello,peng"# 替换
127.0.0.1:6379> set key2 abcdefg
OK
127.0.0.1:6379> get key2
"abcdefg"
127.0.0.1:6379> SETRANGE key2 1 xx  # 替换指定位置开始的字符串
(integer) 7
127.0.0.1:6379> get key2
"axxdefg"###################################################################
# setex(set with expire)    # 设置过期时间与值
# setnx(set if not exists)  # 当前key不存在再设置,一般配合setex使用,作用是当前key过期后再设置值(相当于延时设置),在分布式锁(乐观锁)中会经常使用!127.0.0.1:6379> setex key3 30 "hello"   # 设置key3的值为hello,30秒后过期
OK
127.0.0.1:6379> ttl key3
(integer) 25
127.0.0.1:6379> get key3
"hello"
127.0.0.1:6379> setnx mykey "redis"     # 如果mykey不存在,则创建mykey
(integer) 1
127.0.0.1:6379> keys *
1) "mykey"
2) "key1"
3) "key2"
127.0.0.1:6379> ttl key3
(integer) -2
127.0.0.1:6379> setnx mykey "MongoDB"   # 如果mykey存在,则创建失败
(integer) 0
127.0.0.1:6379> get mykey
"redis"###################################################################
# mset
# mget
127.0.0.1:6379> mset k1 v1 k1 v2 k3 v3  # 批量设置中有重复的key会覆盖前面key的值
OK
127.0.0.1:6379> keys *
1) "k1"
2) "k3"
127.0.0.1:6379> mset k1 v1 k2 v2 k3 v3  # 同时设置多个值
OK
127.0.0.1:6379> keys *
1) "k1"
2) "k2"
3) "k3"
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      # msetnx是一个原子性的操作,要么一起成功,要么一起失败!
(integer) 0
127.0.0.1:6379> get k4
(nil)
​
# 对象
set user:1{name:zhangsan,age:3} # 设置一个user:1对象的值为json字符来保存一个对象!# 这里的key是一个巧妙的设计: user:{id}:{filed(即,key)},如此设计在Redis中是完全OK了127.0.0.1:6379> mset user:1:name zhangsan user1:1:age 2
OK
127.0.0.1:6379> mget user:1:name user1:1:age 
1) "zhangsan"
2) "2"###################################################################
getset # 先get然后set
CAS # 原子操作,比较并交换值,类似于getset
127.0.0.1:6379> getset db redis # 如果不存在值,则返回nil
(nil)
127.0.0.1:6379> get db
"redis"
127.0.0.1:6379> getset db mongodb # 如果存在值,则返回原来的值,并设置新的值
"redis"
127.0.0.1:6379> get db
"mongodb"###################################################################

数据结构是相同的,因此在了解一个程序运行的底层原理的时候其实就是分析它的数据结构!

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

  • 计数器
  • 统计多单位的数量
  • 粉丝数
  • 对象缓存存储

List(列表)

基本数据类型,列表,默认数据结构是栈

image-20201225180240453

在Redis里面,使用有点类似于栈,查询时“先进后出”,我们可以将list变成栈、队列、阻塞队列!

所有的list命令都是l开头的,Redis不区分大小写命令!

###################################################################
127.0.0.1:6379> LPUSH list one  # 将一个值或者多个值,插入到列表的头部(左);也可以理解为从左往右推,结果就是数组最后一个元素在最左边,导致倒序。一般rpush用的比较多
(integer) 1
127.0.0.1:6379> LPUSH list two
(integer) 2
127.0.0.1:6379> LPUSH list three
(integer) 3
127.0.0.1:6379> LRANGE list 0 -1 # 获取list中的值,因为是Lpush,因此是倒序
1) "three"
2) "two"
3) "one"
127.0.0.1:6379> LRANGE list 0 1 # 通过区间获取具体的值
1) "three"
2) "two"
127.0.0.1:6379> RPUSH list right # 将一个值或者多个值,插入到列表尾部(右)
(integer) 4
127.0.0.1:6379> LRANGE list 0 1
1) "three"
2) "two"
127.0.0.1:6379> LRANGE list 0 -1
1) "three"
2) "two"
3) "one"
4) "right"###################################################################
LPOP # 默认移除一个数组左边的元素(pop,弹出去),顺序移除
EPOP # 移除一个右边的元素
127.0.0.1:6379> LRANGE list 0 -1
1) "three"
2) "two"
3) "one"
4) "right"
127.0.0.1:6379> lpop list   # 移除列表的第一个元素
"three"
127.0.0.1:6379> rpop list   # 移除list的最后一个元素
"right"
127.0.0.1:6379> LRANGE list 0 -1
1) "two"
2) "one"###################################################################
Lindex
阻塞队列:如果当前list为空,需要进行阻塞,这里Lindex可以实现生产者与消费者的查询
127.0.0.1:6379> lindex list 1 # 通过下标获取list中的某一个值
"one"
127.0.0.1:6379> lindex list 0
"two"###################################################################
Llen
127.0.0.1:6379> lpush list one 
(integer) 1
127.0.0.1:6379> lpush list two
(integer) 2
127.0.0.1:6379> lpush list three
(integer) 3
127.0.0.1:6379> Llen list   # 返回列表的长度
(integer) 3###################################################################
移除指定的值
取关 uid
Lrem
127.0.0.1:6379> Lpush list three
(integer) 4
127.0.0.1:6379> LRANGE list 0 -1
1) "three"
2) "three"
3) "two"
4) "one"
127.0.0.1:6379> Lrem list 1 one  # 移除list集合中指定个数的value,精确匹配
(integer) 1
127.0.0.1:6379> LRANGE list 0 -1
1) "three"
2) "three"
3) "two"
127.0.0.1:6379> Lrem list 2 three # 移除两个相同的值,默认是从list右边的元素往左边移除
(integer) 2
127.0.0.1:6379> LRANGE list 0 -1
1) "two"
127.0.0.1:6379> Lpush list three
(integer) 2
127.0.0.1:6379> Lrem list 1 three
(integer) 1
127.0.0.1:6379> LRANGE list 0 -1
1) "two"###################################################################
Ltrim   # 修剪,将list截断
127.0.0.1:6379> Rpush mylist "hello"
(integer) 1
127.0.0.1:6379> Rpush mylist "hello1"
(integer) 2
127.0.0.1:6379> Rpush mylist "hello2"
(integer) 3
127.0.0.1:6379> Rpush mylist "hello3"
(integer) 4
127.0.0.1:6379> ltrim  mylist 1 2  # 通过下标截取指定的长度,这个list已经被改变了,截断了只剩下截取的元素!
OK
127.0.0.1:6379> LRANGE mylist 0 -1
1) "hello1"
2) "hello2"###################################################################
rpoplpush  # 移除列表的最后一个元素,将它移动到新的列表中
127.0.0.1:6379> Rpush mylist "hello"
(integer) 1
127.0.0.1:6379> Rpush mylist "hello1"
(integer) 2
127.0.0.1:6379> Rpush mylist "hello2"
(integer) 3
127.0.0.1:6379> rpoplpush mylist myotherlist # 移除列表mylist的最后一个元素,将它移动到新的列表myotherlist中
"hello2"
127.0.0.1:6379> lrange mylist 0 -1  # 查看原来的列表
1) "hello"
2) "hello1"
127.0.0.1:6379> lrange myotherlist 0 -1 # 查看目标列表中,存在移动后的值
1) "hello2"###################################################################
lset 将列表中指定下标的值替换为另外一个值,更新操作
127.0.0.1:6379> EXIST list # 判断这个列表是否存在
(integer) 0
127.0.0.1:6379> lset list 0 item # 如果不存在列表我们去更新就会报错
(error) ERR no such key
127.0.0.1:6379> lpush list value1
(integer) 1
127.0.0.1:6379> LRANGE list 0 0
1) "value1"
127.0.0.1:6379> lset list 0 item # 如果存在,更新当前下标的值
OK
127.0.0.1:6379> LRANGE list 0 0
1) "item"
127.0.0.1:6379> lset list 1 other # 如果不存在,则会报错
(error) ERR index out of range
​
###################################################################
linsert # 将某一个具体的value插入到某一个元素的前面或者后面
127.0.0.1:6379> Rpush mylist "hello"
(integer) 1
127.0.0.1:6379> Rpush mylist "world"
(integer) 2
127.0.0.1:6379> LINSERT mylist before "world" "other"
(integer) 3
127.0.0.1:6379> LRANGE mylist 0 -1
1) "hello"
2) "other"
3) "world"
127.0.0.1:6379> LINSERT mylist before world new
(integer) 4
127.0.0.1:6379> LRANGE mylist 0 -1
1) "hello"
2) "other"
3) "new"
4) "world"

小结

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

可以用于消息队列,并且因为Redis的数据具有优先级,比起消息队列的无优先级,会更好的跟踪消息。

消息队列(Lpush Rpop,相当于左边进右边出,就是按顺序的)Lpush Rpop 栈(Lpush Lpop,相当于左边进左边出,那么就是倒序的)

Set(集合)

set中的值是无序且不能重复的!

###################################################################
127.0.0.1:6379> sadd myset hello    # set集合中添加值
(integer) 1
127.0.0.1:6379> sadd myset peng
(integer) 1
127.0.0.1:6379> sadd myset loveyou
(integer) 1
127.0.0.1:6379> SMEMBERS myset      # 查看指定set的所有值
1) "loveyou"
2) "peng"
3) "hello"
127.0.0.1:6379> SISMEMBER myset hello # 判断某一个值是不是在set集合中
(integer) 1
127.0.0.1:6379> SISMEMBER myset world
(integer) 0###################################################################
127.0.0.1:6379> scard myset # 获取set集合中的内容元素个数
(integer) 4###################################################################
srem
127.0.0.1:6379> srem myset hello    # 移除set集合中的指定元素
(integer) 1
127.0.0.1:6379> scard myset
(integer) 3
127.0.0.1:6379> SMEMBERS myset
1) "loveyou2"
2) "loveyou"
3) "peng"###################################################################
因为set是无序不重合集合,因此可以做随机抽取!
​
127.0.0.1:6379> SMEMBERS myset
1) "loveyou2"
2) "loveyou"
3) "peng"
127.0.0.1:6379> srandmember myset   # 随机抽选出一个元素
"peng"
127.0.0.1:6379> srandmember myset
"peng"
127.0.0.1:6379> srandmember myset
"loveyou"
127.0.0.1:6379> srandmember myset 2 # 随机抽选出指定个数的元素
1) "loveyou"
2) "loveyou2"
127.0.0.1:6379> srandmember myset   # 随机抽选出一个元素
"loveyou2"###################################################################
删除指定的key,随机删除指定的key!
127.0.0.1:6379> SMEMBERS myset
1) "loveyou2"
2) "loveyou"
3) "peng"
127.0.0.1:6379> Spop myset  # 随机删除一些set集合中的元素!
"peng"
127.0.0.1:6379> Spop myset
"loveyou2"
127.0.0.1:6379> SMEMBERS myset
1) "loveyou"###################################################################
将一个指定的值,移动到另外一个set集合中!
127.0.0.1:6379> clear
127.0.0.1:6379> sadd myset hello
(integer) 1
127.0.0.1:6379> sadd myset world
(integer) 1
127.0.0.1:6379> sadd myset peng
(integer) 1
127.0.0.1:6379> sadd myset2 "set2"
(integer) 1
127.0.0.1:6379> smove myset myset2 peng # 将一个指定的值,移动到另外一个set集合!
(integer) 1
127.0.0.1:6379> SMEMBERS myset
1) "world"
2) "hello"
127.0.0.1:6379> SMEMBERS myset2
1) "set2"
2) "peng"###################################################################
微博,B站,共同关注!(并集)
数字集合类:
 - 差集
 - 交集
 - 并集
127.0.0.1:6379> sadd key1 a
(integer) 1
127.0.0.1:6379> sadd key1 b
(integer) 1
127.0.0.1:6379> sadd key1 c
(integer) 1
127.0.0.1:6379> sadd key2 c
(integer) 1
127.0.0.1:6379> sadd key2 d
(integer) 1
127.0.0.1:6379> sadd key2 e
(integer) 1
127.0.0.1:6379> sdiff key1 key2     # 差集
1) "a"
2) "b"
127.0.0.1:6379> sdiff key2 key1
1) "d"
2) "e"
127.0.0.1:6379> sinter key1 key2    # 交集
1) "c"
127.0.0.1:6379> sunion key1 key2    # 并集
1) "a"
2) "b"
3) "c"
4) "d"
5) "e"

可以将微博、B站中的A用户所有关注的人、粉丝放在一个set集合中,实现共同关注功能!

六度分割理论(小世界现象,即陌生人之间所间隔的人不会超过六个,也就是说,最多通过六个人你就能够认识任何一个陌生人。)

推荐好友,也就是通过交集得出的!

Hash(哈希)

Map集合,Key—Map(即key—<key,value>),这时候这个值是一个map集合。本质和String没有区别,还是一个简单的key-value!

set myhash field peng

###################################################################
127.0.0.1:6379> hset myhash field1 peng # set一个具体的k-v
(integer) 1
127.0.0.1:6379> hset myhash field1 
(error) ERR wrong number of arguments for 'hset' command
127.0.0.1:6379> hget myhash field
(nil)
127.0.0.1:6379> hget myhash field1
"peng"
127.0.0.1:6379> hmset myhash field hello field world # 新的set会覆盖原来的值
OK
127.0.0.1:6379> hmset myhash field1 hello field2 world # set多个k-v
OK
127.0.0.1:6379> hmget myhash field1 field2      # 获取多个字段值
1) "hello"
2) "world"
127.0.0.1:6379> hgetall myhash  # 获取全部的数值
1) "field1"
2) "hello"
3) "field"
4) "world"
5) "field2"
127.0.0.1:6379> hdel myhash field1  # 删除hash指定的key字段,对应的value也就消失了!
(integer) 1
127.0.0.1:6379> hgetall myhash
1) "field"
2) "world"
3) "field2"
4) "world"
###################################################################
hlen
127.0.0.1:6379> hset myhash field1 hello field2 world
(integer) 1
127.0.0.1:6379> hgetall myhash
1) "field2"
2) "world"
3) "field1"
4) "hello"
127.0.0.1:6379> hlen myhash # 获取hash表的字段长度
(integer) 2
​
​
###################################################################
hexist
127.0.0.1:6379> HEXISTS myhash field1   # 判断hash中指定字段是否存在!
(integer) 1
127.0.0.1:6379> HEXISTS myhash field3
(integer) 0###################################################################
# 只获得所有field的key
# 只获得所有field的value
127.0.0.1:6379> hkeys myhash # 获取所有的字段
1) "field2"
2) "field1"
127.0.0.1:6379> hvals myhash # 获取所有的值
1) "world"
2) "hello"###################################################################
incr decr hsetnx
127.0.0.1:6379> hset myhash field3 5    
(integer) 1
127.0.0.1:6379> HINCRBY myhash field3 1     # 指定增量
(integer) 6
127.0.0.1:6379> HINCRBY myhash field3 -1
(integer) 5
127.0.0.1:6379> hsetnx myhash field4 hello  # 如果不存在则可以设置
(integer) 1
127.0.0.1:6379> hsetnx myhash field4 world  # 如果存在则不可以设置
(integer) 0###################################################################
hset # 设置hash字段的值到hash表中
hget # 读取hash表中字段的值
127.0.0.1:6379> hset user:1 name peng
(integer) 1
127.0.0.1:6379> hget user:1
(error) ERR wrong number of arguments for 'hget' command
127.0.0.1:6379> hget user:1 name
"peng"
###################################################################
hmset # 批量设置hash字段的值
hmget # 批量读取hash字段的值(读取时一定要有key和字段)
127.0.0.1:6379> hmset website baidu www.baidu.com google www.google.com # 批量set hash字段的值
OK
127.0.0.1:6379> hmget website
(error) ERR wrong number of arguments for 'hmget' command
127.0.0.1:6379> hmget website baidu google # 批量get hash的值
1) "www.baidu.com"
2) "www.google.com"

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

ZSet(有序集合)

在set的基础上,增加了一个值,set k1 v1 zset k1 score v1,相当于做了一个排序(通过计数位byscores实现)!

###################################################################
127.0.0.1:6379> zadd myset 1 one    # 增加一个值
(integer) 1
127.0.0.1:6379> zadd myset 2 two
(integer) 1
127.0.0.1:6379> zadd myset 2 two 3 three # 添加多个值
(integer) 1
127.0.0.1:6379> ZRANGE myset 0 -1   # 查看所有的值
1) "one"
2) "two"
3) "three"###################################################################
排序如何实现
127.0.0.1:6379> zadd salary 2500 xiaohong   # 添加三个用户
(integer) 1
127.0.0.1:6379> zadd salary 5000 zhangsan
(integer) 1
127.0.0.1:6379> zadd salary 500 peng
(integer) 1
# ZRANGEBYSCORE key min max
127.0.0.1:6379> ZRANGEBYSCORE salary -inf +inf # 显示全部的用户,按照从小到大排序(字典序)
1) "peng"
2) "xiaohong"
3) "zhangsan"
127.0.0.1:6379> ZREVRANGE salary 0 -1   # 从大到小排序
1) "zhangsan"
2) "peng"
127.0.0.1:6379> ZRANGEBYSCORE salary +inf -inf
(empty array)
127.0.0.1:6379> ZRANGEBYSCORE salary 0 -1
(empty array)
127.0.0.1:6379> ZRANGEBYSCORE salary -inf +inf withscores   # 显示全部的用户并且附带成绩
1) "peng"
2) "2500"
3) "xiaohong"
4) "2500"
5) "zhangsan"
6) "5000"
127.0.0.1:6379> ZRANGEBYSCORE salary -inf 2500 withscores   # 显示工资小于2500的员工并升序排列!
1) "peng"
2) "500"
3) "xiaohong"
4) "2500"###################################################################
zrem  移除zset中的元素
127.0.0.1:6379> zrange salary 0 -1  # 查看zset中的所有元素
1) "peng"
2) "xiaohong"
3) "zhangsan"
127.0.0.1:6379> zrem salary xiaohong # 移除zset中的指定元素
(integer) 1
127.0.0.1:6379> zrange salary 0 -1
1) "peng"
2) "zhangsan"
127.0.0.1:6379> zcard salary    # 获取有序集合中key的个数
(integer) 2###################################################################127.0.0.1:6379> zadd myset 1 hello
(integer) 1
127.0.0.1:6379> zadd myset 2 world 3 peng
(integer) 2
127.0.0.1:6379> zcount myset 1 3    # 获取指定区间的成员数量
(integer) 3
127.0.0.1:6379> zcount myset 1 2
(integer) 2

其余的一些API,通过我们的学习,剩下的如果工作中有需要,这个时候可以去查看官方文档!

案例思路:set排序 存储班级成绩表、工资表排序

带权重进行判断:普通消息,1;重要消息,2

排行榜应用实现,取Top N测试!

三大特殊数据类型

Geospatial(地理位置)

在翻阅Redis GEO API时发现其并没有删除指令,因为其底层是使用zset进行实现的。 我们可以使用zrem 进行数据的删除。

朋友的定位,附近的人,打车距离计算怎么实现?

Redis的Geo在Redis 3.2版本就推出了!这个功能可以推算地理位置的信息,两地之间的距离,方圆几里之内的人!

可以查询一些精确的数据: jingweidu.51240.com/

只有六个命令

image-20201226025202883

geoadd

# geoadd 添加地理位置
# 规则:两极无法直接添加,我们一般会下载城市数据,直接通过java程序一次性导入!
# 参数:key 值(经度、纬度、名称)
# GEOADD 命令以标准的 x,y 格式接受参数, 所以用户必须先输入经度, 然后再输入纬度。 
# GEOADD 能够记录的坐标是有限的: 非常接近两极的区域是无法被索引的。 精确的坐标限制由 EPSG:900913 / EPSG:3785 / OSGEO:41001 等坐标系统定义, 具体如下:
# 有效的经度介于 -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> geoadd china:city 121.47 31.23 shanghai
(integer) 1
127.0.0.1:6379> geoadd china:city 106.50 29.53 chongqing 114.05 22.52 shenzhen
(integer) 2
127.0.0.1:6379> geoadd china:city 120.16 30.24 hangzhou 108.96 34.26 xian
(integer) 2

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 chongqing # 获取指定城市的经度和纬度
1) 1) "116.39999896287918091"
   2) "39.90000009167092543"
2) 1) "106.49999767541885376"
   2) "29.52999957900659211"

geodist

两个人的距离

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

  • 如果两个位置之间的其中一个不存在,那么返回空值。

  • 指定单位的参数unit必须是一下单位的其中一个:

    • m 表示单位为米。
    • km 表示单位为千米。
    • mi 表示单位为英里。
    • ft 表示单位为英尺。
127.0.0.1:6379> geodist china:city beijing shanghai km
"1067.3788" # 查看北京到上海的直线距离
127.0.0.1:6379> geodist china:city beijing chongqing km
"1464.0708" # 查看北京到重庆的直线距离

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

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

选项:

  • WITHDIST:在返回位置元素的同时,将位置元素与中心之间的距离也一并返回。
  • WITHCOORD:将位置元素的经度和纬度也一并返回。
  • WITHHASH:以52位有符号整数的形式,返回位置元素经过原始geohash编码的有序集合分值。这个选项主要用于底层应用或者调试,实际中的作用并不大。
  • ASC:根据中心的位置,按照从近到远的方式返回位置元素
  • DESC:根据中心的位置,按照从远到近的方式返回位置元素

获得指定数量的人,200

127.0.0.1:6379> georadius china:city 110 30 1000 km # 以110,30这个经纬度为中心,寻找方圆1000km内的城市
1) "chongqing"
2) "xian"
3) "shenzhen"
4) "hangzhou"
127.0.0.1:6379> georadius china:city 110 30 500 km
1) "chongqing"
2) "xian"
127.0.0.1:6379> georadius china:city 110 30 500 km withdist # 显示到中心距离的位置
1) 1) "chongqing"
   2) "341.9374"
2) 1) "xian"
   2) "483.8340"
127.0.0.1:6379> georadius china:city 110 30 500 km withcoord    # 显示他人的定位信息
1) 1) "chongqing"
   2) 1) "106.49999767541885376"
      2) "29.52999957900659211"
2) 1) "xian"
   2) 1) "108.96000176668167114"
      2) "34.25999964418929977"127.0.0.1:6379> georadius china:city 110 30 500 km withdist withcoord count 1   # 筛选出指定的结果!
1) 1) "chongqing"
   2) "341.9374"
   3) 1) "106.49999767541885376"
      2) "29.52999957900659211"
127.0.0.1:6379> georadius china:city 110 30 500 km withdist withcoord count 2
1) 1) "chongqing"
   2) "341.9374"
   3) 1) "106.49999767541885376"
      2) "29.52999957900659211"
2) 1) "xian"
   2) "483.8340"
   3) 1) "108.96000176668167114"
      2) "34.25999964418929977"
127.0.0.1:6379> georadius china:city 110 30 500 km withdist withcoord count 3
1) 1) "chongqing"
   2) "341.9374"
   3) 1) "106.49999767541885376"
      2) "29.52999957900659211"
2) 1) "xian"
   2) "483.8340"
   3) 1) "108.96000176668167114"
      2) "34.25999964418929977"

georadiusbymember

# 找出位于指定元素周围的其他元素!
127.0.0.1:6379> georadiusbymember china:city beijing 1000 km
1) "beijing"
2) "xian"
127.0.0.1:6379> georadiusbymember china:city shanghai 1000 km
1) "hangzhou"
2) "shanghai"

geohash

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

为了解决编码和解码过程中geohash初始最小最大坐标不同,使用内部52位整数编码表示,将返回一个标准的Geohash,测试时使用!

它可以在 geohash.org 网站使用

  • 一个数组,数组中的每个项都是一个geohash。命令返回的geohash的位置与用户给定的位置元素的位置一一对应。

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

# 将二维的经纬度转换为一维的字符串,如果两个字符串越接近,那么则距离越近!
127.0.0.1:6379> geohash china:city beijing chongqing
1) "wx4fbxxfke0"
2) "wm5xzrybty0"

Geo底层的实现原理其实就是Zset,我们可以使用zset命令来操作geo!

127.0.0.1:6379> zrange china:city 0 -1  # 查看地图中全部的元素
1) "chongqing"
2) "xian"
3) "shenzhen"
4) "hangzhou"
5) "shanghai"
6) "beijing"
127.0.0.1:6379> zrem china:city beijing
(integer) 1
127.0.0.1:6379> zrange china:city 0 -1  # 移除指定的元素
1) "chongqing"
2) "xian"
3) "shenzhen"
4) "hangzhou"
5) "shanghai"

Hyperloglogs(超日志)

什么是基数

A{1,3,5,7,8,7}

B{1,3,5,7,8}

基数:不重复的元素 = 5,可以接受误差!

简介

Redis 2.8.9版本就更新了Hyperloglog数据结构

Redis Hyperloglog基数统计的算法!

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

网页的UV(unique visitor,互联网访问量,即网页浏览量。一个人访问一个网站多次,但还是算作一个人)

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

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

0.81%错误率!统计UV任务,可以忽略不计的!

image-20201226043808952

测试使用

127.0.0.1:6379> pfadd mykey a b c d e f g h i j # 创建第一组元素 mykey
(integer) 1
127.0.0.1:6379> PFCOUNT mykey   # 统计 mykey元素的基数数量
(integer) 10
127.0.0.1:6379> pfadd mykey2 i j z x c v b n m  # 创建第二组元素 mykey2
(integer) 1
127.0.0.1:6379> PFCOUNT mykey2  
(integer) 9
127.0.0.1:6379> pfmerge mykey3 mykey mykey2 # 合并两组 mykey mykey2 => mykey3并集
OK
127.0.0.1:6379> PFCOUNT mykey3 查看并集中的元素个数
(integer) 15

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

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

Bitmap(位图)

难道这个数据结构使用比较冷门就不去学习?这些在生活中或者开发中,都有十分多的应用场景;学习了,就是多一种思路!

技多不压身!

位存储

统计疫情感染人数:0 1 0 1 0

统计用户信息:活跃,不活跃;登录,不登录;打卡,365打卡!

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

365天 = 365 bit 1字节 = 8bit 46个字节左右

测试

image-20201226050628929

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

周一:1 周二 :0周三:1周四:0周五:1周六: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 1
(integer) 0
127.0.0.1:6379> setbit sign 3 0
(integer) 0
127.0.0.1:6379> setbit sign 4 1
(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 5
(integer) 0
127.0.0.1:6379> getbit sign 4
(integer) 1

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

使用数据结构去操作数据比创建一个java对象快速很多

127.0.0.1:6379> bitcount sign   # 统计这周的打卡记录,就可以看到是否有全勤! 
(integer) 3

Redis实现事务

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

一次性、顺序性、排他性!执行一系列的命令!

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

Redis事务没有隔离级别的概念!

所有的命令在事务中,并没有直接被执行,只有发起执行命令的时候才会执行! Exec

Redis单条命令保证原子性,但是事务并不保证原子性!因为作为内存数据库,是断电易失的,缓存只需要保证速度就行了。

官网介绍:MULTIEXECDISCARDWATCH 是 Redis 事务相关的命令。

锁:Redis可以实现乐观锁,watch

Redis的事务:

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

正常执行事务

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> 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 k4 v4
QUEUED
127.0.0.1:6379> discard  # 取消事务
OK
127.0.0.1:6379> get k4  # 取消事务后队列中的命令都不会被执行!
(nil)

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

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> getset k3   # 错误的命令
(error) ERR wrong number of arguments for 'getset' command
127.0.0.1:6379> set k4 v4
QUEUED
127.0.0.1:6379> set k5 v5
QUEUED
127.0.0.1:6379> exec    # 执行会务报错!
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> get k5  # 所有的命令都不会被执行!
(nil)

运行时异常(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
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"

监控!Watch(面试常问!

Redis实现乐观锁

悲观锁

  • 很悲观,什么时候都会出问题,无论做什么都会加锁!比如:Java中的synchronizd中所有的操作都会加锁。

乐观锁

  • 很乐观,认为什么时候都不会出问题,所以不会上锁!更新数据的时候去判断一下,在此期间是否有人修改过这个问题!比如:MySQL中的version字段实现乐观锁,乐观锁使用较多!
  • 获取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> DECRBY money 20
QUEUED
127.0.0.1:6379> INCRBY out 20
QUEUED
127.0.0.1:6379> exec
1) (integer) 80
2) (integer) 20

测试多线程修改值,使用watch可以当做Redis的乐观锁操作!

127.0.0.1:6379> watch money
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> DECRBY money 10
QUEUED
127.0.0.1:6379> INCRBY out 10
QUEUED
127.0.0.1:6379> exec    # 执行之前,另外一个线程修改了当前的值,这个时候,就会导致事务执行失败!
(nil)

如果修改失败,直接获取最新的值就好!

image-20201227032159866

Redis事务以及Redis乐观锁实现就先了解到这里,下面我们开始安装Redis服务器:

欢迎关注白羊🐏,感谢观看ヾ(◍°∇°◍)ノ゙