Redis简介
Redis是一个开源的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。JAVA客户端是jedis。Redis6.0已经支持多线程了(面试提问单线程问题的时候要注意了)。
Redis数据结构(罗列)
- String:二进制安全的字符串
- Lists:安插入顺序排序的字符串元素集合。基本是链表。
- Sets:无序不重复集合。
- Sorted sets(zset):里面的元素总是通过score进行排序。有序集合。
- Hashes:键值都是字符串的哈希表。
- Bit arrays:位集合(可以实现类似布隆过滤器的功能结构)
- HyperLogLog:是用来做基数统计的算法。用于估计一个set中元素数量的概率性的数据结构。
- Geospatial Indexes:地理空间索引
- Streams:流信息
Redis keys
Redis key值是二进制安全的。(二进制安全 是指,在传输数据时,保证二进制数据的信息安全,也就是不被篡改、破译等,如果被攻击,能够及时检测出来。),可以是空值,也可以是任何二进制序列。
key的自动创建和删除
在实践中或例子里可以看到,我们没有在推入元素之前创建空的list,或者在list没有元素时删除它。在list为null删除key,并使用lpush时而创建空list. 这是Redis的职责。适用于list,zset,set,hash类型。
概括如下:
1当我们向一个聚合数据类型中添加元素时,如果目标键不存在,就在添加元素前创建空的聚合数据类型。
2当我们从聚合数据类型中移除元素时,如果值仍然是空的,键自动被销毁。
3对一个空的 key 3调用一个只读的命令,比如 LLEN (返回 list 的长度),或者一个删除元素的命令,将总是产生同样的结果。该结果和对一个空的聚合类型做同个操作的结果是一样的。
一、string 字符串
可以在一个键下保存一个图片,值的长度不能超过512MB.
//设置键值
> set mykey somevalue
OK
//获取键值
> get mykey
"somevalue"
//也可以通过命令进行递增操作
> set counter 100
OK
> incr counter //+1
(integer) 101
> incr counter
(integer) 102
> incrby counter 50 //+50
(integer) 152
INCR 命令将字符串值解析成整型后加一,然后在保存为字符串。类似命令有INCRBY,DECR,DECRBY。都是原子性操作,不会导致竞争的情况。
redis也可以做批量的操作,一次性设置获取多个值。
> mset a 10 b 20 c 30
OK
> mget a b c
"10"
"20"
"30"
其他命令
exists key:返回1或0标识给定key的值是否存在。
del key:返回1或0标识给定key的值是否存在
type key:返回key对应值的存储类型。
expire key time:给值设定存活时间。time可以是毫秒或者秒。(如果用rename命令修改key,相关时间也会转移到新名称上。针对已有过期时间的key进行操作,会刷新过期时间)
ttl key:用来查看key所对应值的剩余时间。
redis没有设置过期时间,ttl返回-1。
redis已经大于过期时间,ttl返回-2.
设置了剩余时间,且没过期的。ttl返回剩余时间。
没有设置过期时间的值可以通过del进行删除。
redis keys的过期有两种方式
1. 主动过期
当一些客户端尝访问它时,key会被发现并主动过期。
2. 被动过期
如果有一些keys,可能永远也不会访问。所以定时随机测试设置keys的过期时间。所有过期的keys将会从密钥空间删除。具体就是Redis每秒10次的操作。
a. 测试随机的20个keys进行相关过期检测。
b. 删除所有已经过期的keys
c. 如果有多于25%的keys过期,重复a
Redis String 使用场景: 普通key/value存储,比如统计IP访问次数(利用key名 + incr)
二、Redis Lists
Redis lists基于Linked List实现。在头部或者尾部添加一个元素的操作的时间复杂度是常数级别的。用LPUSH命令在十个元素的list头部添加元素和在千万级list头部添加元素的速度相同。
对于数据库来说,一个重要特性是能非常快的在列表上添加元素。而且redis lists能在常数时间内取得常数长度。所以用linked list实现。
rpush key value:向list的右边/尾部添加一个新元素。
lpush key value:向list的左边/头部添加一个新元素。
lrange key index index:从list中取出范围内元素,index可以为负数,表示从右边/尾部计数。
当然也可以批量处理数据
> rpush mylist 1 2 3 4 5 "foo bar"
(integer) 9
> lrange mylist 0 -1
1) "first"
2) "A"
3) "B"
4) "1"
5) "2"
6) "3"
7) "4"
8) "5"
9) "foo bar"
**rpop/lpop key **:从list中删除元素并返回删除元素的值。可以在头部/尾部操作。
**ltrim key index index **:把list从左边截取指定长度
> rpush mylist 1 2 3 4 5
(integer) 5
> ltrim mylist 0 2
OK
> lrange mylist 0 -1
1) "1"
2) "2"
3) "3"
list值类型,可以用来实现聊天系统,可以用来作为消息队列(按照顺序访问数据)。也可以使用lrange对结果进行分页。
用作为消息队列时,如果用lpush和rpop实现功能会出现一种情景(list为空,但是消费者一直轮询获取,会增加redis的访问压力、和cpu消耗),所以redis提供了阻塞式访问的brpop和blpop命令。可见详解:http://www.redis.cn/commands/blpop.html
三、Redis Hash
由键值对组成。适合存储对象型数据。Redis中的每个hash可以存储 2^32个键值对。
可以对散列存储的数字进行自增或自荐
> hmset user:1000 username antirez birthyear 1977 verified 1
OK
> hget user:1000 username
"antirez"
> hget user:1000 birthyear
"1977"
> hgetall user:1000
1) "username"
2) "antirez"
3) "birthyear"
4) "1977"
5) "verified"
6) "1"
hmset key value(k,v):用于设置hash的多个域。
hget key k:用于获取单个k对应的v.
hmget key k k k:用于获取多个k对应的v.
**hincrby key k 10 **:针对单独的值域进行操作。
更多指令请查看:http://redis.io/commands#hash
Redis hash应用场景:
存储对象型数据,如 人(属性,值,属性,值)
四、Redis Set
是对String的无序排列。
> sadd myset 1 2 3
(integer) 3
> smembers myset
1. 3
2. 1
3. 2
> sismember myset 3
(integer) 1
**sadd key v v v **:将新的元素添加到key中。
**smembers key **:返回所有元素,元素顺序不定。
smembers key v :检测某个元素是否存在
Sets适合用于标识对象间的关系,比如标识标记/打标签。
被打上相同标签的元素列表
> sadd tag:1:news 1000
(integer) 1
> sadd tag:2:news 1000
(integer) 1
> sadd tag:5:news 1000
(integer) 1
> sadd tag:77:news 1000
(integer) 1
## 获取一个对象的所有tag
> smembers news:1000:tags
1. 5
2. 1
3. 77
4. 2
set数据类型还有一个功能是sdiff,取差集(myset中排除myset2的值)。也可以用来对比两个集合的交集SINTER(属于A,也属于B)。可以利用这个功能处理对账数据。
> SADD myset "hello"
(integer) 1
> SADD myset "foo"
(integer) 1
> SADD myset "bar"
(integer) 1
> SADD myset2 "hello"
(integer) 1
> SADD myset2 "world"
(integer) 1
> SDIFF myset myset2
1) "foo"
2) "bar"
我在实际工作中,tag标记做法直接在数据库中做,方便多个维度的数据查询。
位运算的使用
关于redis set操作的更多命令查看:https://www.redis.net.cn/tutorial/3511.html
Redis set使用场景:
利用唯一性统计独立IP等。利用对交集、并集、差集的计算对数据进行过滤处理,如共同好友、推荐信息的数据过滤等。
五、Redis zset
Redis 有序集合和set集合一样也是string类型元素的集合,且不允许重复的成员。
不同的是每个元素都会关联一个double类型的分数。redis正是通过分数来为集合中的成员进行从小到大的排序。
有序集合的成员是唯一的,但分数(score)却可以重复。(如果设置所有的元素score为一致的,则默认按照字典序排序。)
> zadd hackers 1940 "Alan Kay"
(integer) 1
> zadd hackers 1957 "Sophie Wilson"
(integer 1)
> zadd hackers 1953 "Richard Stallman"
(integer) 1
> zadd hackers 1949 "Anita Borg"
(integer) 1
> zadd hackers 1965 "Yukihiro Matsumoto"
(integer) 1
> zadd hackers 1914 "Hedy Lamarr"
(integer) 1
> zadd hackers 1916 "Claude Shannon"
(integer) 1
> zadd hackers 1969 "Linus Torvalds"
(integer) 1
> zadd hackers 1912 "Alan Turing"
(integer) 1
## 正序输出
> zrange hackers 0 -1
1) "Alan Turing"
2) "Hedy Lamarr"
3) "Claude Shannon"
4) "Alan Kay"
5) "Anita Borg"
6) "Richard Stallman"
7) "Sophie Wilson"
8) "Yukihiro Matsumoto"
9) "Linus Torvalds"
## 反序输出
> zrevrange hackers 0 -1
1) "Linus Torvalds"
2) "Yukihiro Matsumoto"
3) "Sophie Wilson"
4) "Richard Stallman"
5) "Anita Borg"
6) "Alan Kay"
7) "Claude Shannon"
8) "Hedy Lamarr"
9) "Alan Turing"
## 1950以下的数据
> zrangebyscore hackers -inf 1950
1) "Alan Turing"
2) "Hedy Lamarr"
3) "Claude Shannon"
4) "Alan Kay"
5) "Anita Borg"
## 删除1940~1960区间内的元素
> zremrangebyscore hackers 1940 1960
(integer) 4
## 查询zset中的元素index
> zrank hackers "Anita Borg"
(integer) 4
Redis ZSet使用场景:
排行榜、统计类的数据需求。
六、Redis Bitmap
位图不是实际的数据类型,而是在String类型上定义的一组面向位的操作。
由于字符串是二进制安全的,最大长度是512MB,转换成位可以设置 2^32不同的位。
512MB = 2^9 * 2^3(byte) * 2^10(kb) * 2^10(mb) = 2^32(bit)
位图的最大优点之一,存储信息时可以节省大量空间。
> set key big
位操作分为两类:
1.固定时间的单个位操作(设置位为1或0获取当前位的值)
## 设置位值为1
> setbit key 10 1
(integer) 1
## 获取
> getbit key 10
(integer) 1
## 获取没有存过的值时为0
> getbit key 11
(integer) 0
2.对位组的操作(获取范围内的位的数量、效率很高)
> setbit key 0 1
(integer) 0
> setbit key 100 1
(integer) 0
> bitcount key
(integer) 2
注: Bitmap 和 HyperLogLog是基于String类型,但是拓展了自己的语义
Redis Bitmap使用情景
- 各种实时分析。
例如,假设您想知道网站用户每天访问量最长的时间。您从零开始计算天数,即从您公开网站的那一天开始,并在用户每次访问该网站时对SETBIT进行设置。作为位索引,您只需花费当前的unix时间,减去初始偏移,然后除以3600 * 24。
这样,对于每个用户,您都有一个小的字符串,其中包含每天的访问信息。
> setbit key 20200310-UID 1
> setbit key 20200311-UID 1
> setbit key 20200311-UID 0
类似bloomfilter的实现,防止缓存穿透。(将存量 + 增量的标识数据进行存储,去判断)
可以通过多个bitmap的交集、并集、not(非)、xor(疑惑)操作处理一些位运算的逻辑
bitmap 可以使用RLE编码进一步压缩空间
七、Redis HyperLogLogs(HLL)
属于一种概率算法,(LC,LLC,HLL)三种越来越节省内存,降低误差率。
HyperLogLog优点,在输入元素的数量或者体积非常大时。计算基数所需的空间总是固定很小的。每个HyperLogLog的键只需要花费12KB内存,在标准误差0.81%的前提下,就可以计算接近2^64个不同的基数。
用bitmap存储1一亿个统计数据大概需要12M内存;而在HLL中,只需要不到1K内存就能做到。
HyperLogLog只会根据输入元素来计算基数,而不会存储元素本身,所以不能返回各个元素。
## 添加指定元素到HLL中
> pfadd hll a b c d
(integer) 1
## 返回给定的HLL的基数估算值
> pfcount hll
(integer) 4
## 合并HLL
> pfmerge key sourceA [sourceB]
HLL的使用场景:常用来统计一个集合中不重复的元素个数,例如网站PV,搜索关键词数量,数据分析、网络监控及数据库优化等领域。
HLL比 bitmap更节省内存,但有一定误差( 标准误差 0.81%)
八、Redis Geospatial Indexes(地理空间索引)
将制定的地理空间位置(经度、纬度、名称)添加到指定的key中。这些数据将会存储到Sorted set中。目的是为了方便GEORADIUS或者GEORADIUSBYMEMBER命令对数据进行半径查询等操作。
sorted set使用一种称为Geohash的技术进行填充。经度和纬度的位是交错的,以形成一个独特的52位整数. 我们知道,一个sorted set 的double score可以代表一个52位的整数,而不会失去精度。
这种格式允许半径查询检查的1 + 8个领域需要覆盖整个半径,并丢弃元素以外的半径。通过计算该区域的范围,通过计算所涵盖的范围,从不太重要的部分的排序集的得分,并计算得分范围为每个区域的sorted set中的查询。
官方示例
## 增加意大利西西里岛的两个城市坐标(Palermo和 卡塔尼亚)
redis> GEOADD Sicily 13.361389 38.115556 "Palermo" 15.087269 37.502669 "Catania"
(integer) 2
## 返回两个给定位置之间的距离。(默认单位米)
redis> GEODIST Sicily Palermo Catania
"166274.1516"
## 以给定的经纬度为中心,返回键包含的位置元素当中, 与中心的距离不超过给定最大距离的所有位置元素。
## 100m
redis> GEORADIUS Sicily 15 37 100 km
1) "Catania"
## 200m
redis> GEORADIUS Sicily 15 37 200 km
1) "Palermo"
2) "Catania"
redis>
可以通过查询不同地方的坐标信息进行验证。查询点这里
Geospatial indexes使用场景:这里假设地球是一个球体,因为使用的距离公式是Haversine公式。这个公式仅适用于地球,而不是一个完美的球体。当在社交网站和其他大多数需要查询半径的应用中使用时,这些偏差都不算问题。但是,在最坏的情况下的偏差可能是0.5%,所以一些地理位置很关键的应用还是需要谨慎考虑。
九、Redis Streams
Stream是Redis 5.0引入的一种新数据类型,是一个新的强大的支持多播的可持久化的消息队列。
相比于现有的PUB/SUB、BLOCKED LIST,其虽然也可以在简单的场景下作为消息队列来使用,但是Redis Stream无疑要完善很多。Redis Stream提供了消息的持久化和主备复制功能、新的RadixTree数据结构来支持更高效的内存使用和消息读取、甚至是类似于Kafka的Consumer Group功能。
它以更抽象的方式对日志数据结构进行建模,但是日志的本质仍然完好无损:像日志文件一样,通常实现为仅在追加模式下打开的文件, Redis流主要是仅追加数据结构。至少从概念上讲,由于Redis是流式传输在内存中表示的抽象数据类型,因此它们实现了更强大的操作,以克服日志文件本身的限制。
尽管数据结构本身非常简单,但Redis流却成为最复杂的Redis类型的原因在于它实现了其他非强制性功能:一组阻止操作,使消费者可以等待生产者将新数据添加到流中,此外还有一个称为“ 消费群体”的概念。
消费者群体最初是由流行的称为Kafka(TM)的消息传递系统引入的。Redis用完全不同的术语重新实现了一个类似的想法,但是目标是相同的:允许一组客户合作使用同一消息流的不同部分。
> XADD mystream * sensor-id 1234 temperature 19.8
1518951480106-0
##上面对XADD命令的调用使用自动生成的条目ID
##将一个条目添加sensor-id: 1234, temperature: 19.8到key流中mystream,该条目ID是该命令返回的,具体来说是1518951480106-0。
##它以键名作为第一个参数mystream,第二个参数是标识流中每个条目的条目ID。
## 获取Stream中的项目数:
> XLEN mystream
(integer) 1
针对流的操作有很多,可以点击这里查看官方文档
Redis stream使用场景:消息队列,和kafka, RocketMq ,RabbitMq等各种消息中间件要按照当前环境的情况和要求合理使用。
整理自 deathearth.com