它好快,我好爱(Redis)

94 阅读17分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 12 月更文挑战」的第1天,点击查看活动详情

⭐ SpringFramework 是一个开源的、松耦合的、分层的、可配置的一站式企业级 Java 开发框架,它的核心是 IOC 与 AOP ,它可以更容易的构建出企业级 Java 应用,并且它可以根据应用开发的组件需要,整合对应的技术。
⭐ Redis作为目前主流的分布式缓存技术,成为现在后台开发工程师必备的基础技能。
😌 博客为了阐述自己的理解与记录。欢迎大家 点赞、留言、关注。

Redis概述\color{green}Redis概述

Redis是什么

Redis 是开源,内存数据结构存储中间件,主要用于数据库,缓存,消息代理和流动引擎。Redis是互联网技术领域中应用最广泛的分布式存储中间件。它是Remote Dictionary Service 三个单词中加粗部分的缩写。以上是官网中给到的定义,然而在我所接触实际使用场景和项目中主要作为数据库和缓存来使用,对于Redis的其他应用有其他更适合的中间件。Redis客户端对于编程语言的支持还是很广泛的。在Java方面主要有Redisson、Jedis、Lettuce、JRedis等等。

redis官网

Redis的优势与版本选择

对于Redis版本是一直在更新与迭代的,到目前为止在官网上已经有 7.x releases

1.为什么选择Redis

  • 具有丰富的数据结构。
  • 支持三种集群模式,确保了服务的高可用性。
  • 数据存在内存中,读写速度非常快,可以到达10w/s的频率。
  • Redis为某些命令提供了原子性操作,并且支持事务可以使用Lua脚本为一连串命令提供原子操作。

2.如何选择Redis版

大多数兄弟一开始接触Redis的时候知道它很快😏。**Why is redis faster? ** 个人认为主要有以下几个原因:

1.基于内存存储数据

redis_faster.png

Redis速度的快的原因是一个基于内存的数据库。内存的访问比起硬盘的IO快了几个数量级,因此在内存中提高了数据读写的吞吐量和降低延迟。唯一需要权衡的地方是需要注意数据集的大小不能当做RDBMD 数据来用、并且需要设置缓存过期时间。从代码方面来讲,基础内存实现内存的数据结构比基于硬盘的数据结构实现更容易。

2.IO多路复用和单线程的实现

为什么采用单线程

Redis 高性能的另一个原因是采用了单线程,这个原因不是很直观。现在的服务器大部分是多核CPU。为啥不使用多线程来实现Redis进而提升利用率,这样不是可以让Redis在恒定的时间内处理更多的请求? 我想主要原因是在多线程应用中避免不了就是同步机制和锁的问题,这些问题在多线程在应用中是很难去推理。并且这样会增加程序的复杂性更容易出错从而失去了应用的稳定性,也很难证明性能提升的合理性。对于Redis而言,使用单线程存储数据就很容易去理解了。
Redis 采用Reactor 模式的网络模型,对于一个客户端的请求、主线程负责一个完整的请求,如下图:

image.png

单线程环境如何维持高性能

在单线程环境中Redis通过I/O多路复用操作系统允许一个线程同时监听多个套接字(socket)连接,即一个线程可以处理客户端的多个请求,减少了线程切换的开销,同时也避免了I/O的阻塞。在传统的系统中,是通过调用poll和select来完成的。但是如果有成千上万个连接时,这些系统的调用性能不是很好的。在linux 系统中epoll是I/O多路复用的高性能变体,可以在恒定时间内处理成千上万个请求。

image.png

Redis需要在不同的平台上运行时,为了最大化执行效率和性能,会根据不同的编译平台选择不同I/O复用函数。Redis会选择时间复杂度为O(1)的I/O多路复用功能作为底层实现。比如我们通常使用的生产服务器是Linux,Redis对Linux操作系统提供了EPoll(Event Poll)函数库作为支撑。

1_fEi1ZZCxQ29J7dH8dIU6wQ.webp

3.多种高效的低级数据结构

image.png

Redis速度快的第三个原因。由于Redis是一种内存数据库,它可以利用多种高效的低级数据结构,如上图中的hashTale、SkipList、ZipList等等。是Redis常用五种数据结构的底层数据结构。有兴趣的小伙伴可以去了解一下。

上面铺垫了这么多,在日常生产使用中应该如何去选择对应的Redis版本呢?要知道到目前为止Redis版本已经更新到了Redis7了。

我所知道最早的版本是3.0+ 的版本,这个版本正式支持集群Redis Cluster分布式的实现,同时也是大家都知道的单线程版本。之后来到了4.0版本支持了提供了RDB-AOF混合持久化格式,充分利用了AOF和RDB各自优势,并且提供了非阻塞操作delflushall/flushdb 功能有效的解决了bigKey可能造成的Redis阻塞问题(3.0版本最麻烦的问题)。5.0版本没有带来其他的特性和性能上的提升,重构核心代码。6.0版本真正意义上的告别了大家印象中的单线程,用一种全新的多线程I/O来解决问题。 如图: image.png

图中的 多个IO线程 只处理Redis客户端过来的多个请求,提高了Redis对于IO的读写性能,即将主线程的IO读写任务拆分给一组独立的线程取执行,可以让多个Sockt的读写并行化。然后Redis采用I/O多路复用技术可以让单线程处理多个连接请求、从而达到减少网络IO的时间消耗。将最耗时的Socket的读取、请求解析、写入单独外包出去,剩下的命令执行仍然由主线程串行执行并和内存的数据交互。

综上所述在实际开发中可以根据系统承载量、复杂程度来选择不同的版本。

Redis安装

Centos_Redis安装

Redis的数据类型

1、Redis 命令不区分大小写,但是key区分大小写
2、help @类型名字,可以查看具体的使用方式和参数
3、type key 可以查看key数据redis中的那种数据类型

String(字符型)

Redis的String数据类型在开发中使用频率最高的数据类型。

最常用

set key value
get key

同时设置或者获取多个键值

MSET key value [key value ....]
MGET key [key ....]

数值增减

递增数值:INCR key
增加指定的整数:INCRBY key increment
递减数值:DECR key
减少指定的整数:DECRBY key decrement

获取字符串长度

STRLEN key

分布式锁

当key值不存在时才创建key:

setnx key value
set key value [EX seconds] [PX milliseconds] [NX|XX]

EX:key在多少秒之后过期
PX:key在多少毫秒后过期
NX:当key不存在的时候,才创建key,效果等同于setnx
XX:当key存在的时候,覆盖key

应用场景

1、点赞文章或者视频时,点击一下累计一次
2、用于存储一些token值之类的

Hash(散列型)

结构类似于java代码中的 Map<String,Map<Object,Object>> 类型

一次性设置一个字段值

HSET key field value

一次获取一个字段值

HGET key field

一次设置多个字段值

HMSET key field value [field value ...]

一次获取多个字段值

HMGET key field [field ....]

获取所有字段值

hgetall key

获取某个key内的全部数量

hlen

删除一个key

hdel

应用场景

数据量小的时候可以用作购物车。存一些频繁访问的数据,热点数据等。

List(列表类型)

一个双端链表的结构,容量是2的32次方减1个元素,大概40多亿,主要功能有push/pop等,一般用在栈、队列、消息队列等场景

向列表左边添加元素

LPUSH key value [value ...]

向列表右边添加元素

RPUSH key value [value ....]

查看列表

LRANGE key start stop

获取所有元素 LRANGE key 0 -1

清空队列

ltrim key start end

对一个列表进行修剪(trim),就是说,让列表只保留指定区间内的元素,不在指定区间之内的元素都将被删除。举个例子,执行命令 LTRIM list 0 2 ,表示只保留列表 list 的前三个元素,其余素全部删除。
ltrim key 1 0; 0(n),慎用!是清空整个列表。

获取列表中元素的个数

LLEN key

应用场景: 链表常用来做异步队列使用

  • 将需要延后处理的任务结构体序列化(JSON)成字符串塞进 Redis 的列表
  • 另一个线程从这个列表中轮询数据进行处理。
  • lpush + lpop = stack 先进后出的栈
  • lpush + rpop = queue 先进先出的队列
  • lpush + ltrim = capped collection 有限集合
  • lpush + brpop = message queue 消息队列

Set(集合类型)

Set 是 string 类型的无序集合。集合成员是唯一的,这就意味着集合中没有重复的数据。
在 Redis 中,添加、删除和查找的时间复杂都是 O(1)(不管 Set 中包含多少元素)。
集合中最大的成员数为 232 – 1 (4294967295), 每个集合可存储 40 多亿个成员。

添加元素

SADD key member [member ...]

删除元素

SREM key member [member ...]

遍历集合中的所有元素

SMEMBERS key

判断元素是否在集合中

SISMEMBER key member

获取集合中的元素总数

SCARD key

从集合中随机弹出一个元素

  • 元素不删除:SRANDMEMBER key [数字]
  • 出一个删一个:SPOP key [数字]

集合运算

  • 集合的差集运算: SDIFF key [key ...]
  • 集合的交集运算: SINTER key [key ...]
  • 集合的并集运算: SUNION key [key ...]

应用场景

  • 微信朋友圈点赞
  • 微博好友关注社交关系
  • 统计网站的独立IP。利用set集合当中元素唯一性,可以快速实时统计访问网站的独立IP

SortedSet(有序集合型,简称zSet)

sorted set有序集合和集合一样也是 string 类型元素的集合,同时也不允许有重复的成员。 不同的是sorted set中的每个元素都会关联一个 double 类型的分数,sorted set通过这个分数给集合中的成员进行从小到大的排序。有序集合中的成员是唯一的,关联的 score 可以重复。

添加元素

ZADD key score member [score member ...]

获取元素分数从小到大的顺序并返回start到stop之间的所有元素

ZRANGE key start stop [WITHSCORES]

WITHSCORES 是否要返回分数

获取元素的分数

ZSCORE key member

删除元素

ZREM key member [member ...]

获取指定分数范围的元素

ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count]

增加某个元素的分数

ZINCRBY key increment member

获取集合中元素的数量

ZCARD key

获得指定分数范围内的元素个数

ZCOUNT key min max

按照排名范围删除元素

ZREMRANGEBYRANK key start stop

获取元素的排名

  • 从小到大: ZRANK key member
  • 从大到小: ZREVRANK key member

应用场景

  • 通过 score 的排序功能,可以实现类似排行榜,学习成绩的排序功能
  • 可以实现一个延迟队列,将 score 存储过期时间,从小到大排序,最靠前的就是最先过期的。
  • 实现带权重队列,比如普通消息的 score 为1,重要消息的 score 为2,然后工作线程可以选择按 score 的倒序来获取工作任务。让重要的任务优先执行

Bitmap(位图)

用string 类型作为底层数据结构实现的一种二值状态的数据类型

位图的本质是数组,它是基于String数据类型的按位操作,该数组由多个二进制位组成,每个二进制位都对应一个偏移量(一个索引或者一个位格)。Bitmap支持的最大位数是2^32位,它可以极大的节约存储空间,使用512M内存就可以存储42.9亿的字节信息。🧐,网上一大堆资料都是这样介绍,还是比较晦涩,于是便去官网、网上找视频消化消化。以下便是我结合bitmap命令做出的解释。

以下是bit字符串Acii码对应的二进制字节数组。 image.png 如果直接使用String类型的set命令往redis中设置一个keyname,值为bit,然后使用getbit key offset 便可以获取在任意索引位置的一个二进制值,如下:

127.0.0.1:6379> set name bit
OK
127.0.0.1:6379> getbit name 0
(integer) 0
127.0.0.1:6379> getbit name 1
(integer) 1
127.0.0.1:6379>

到这里就清楚了,实际上bitmap数据结构是redis提供一种操作位的一种方式。keyname对应的valuebit,在数据库中展示方式如图:

image.png

给位图指定索引设置值

setbit key offset value

setbit命令,offset偏移量指需要把key中的第几位设置成对应的value,并且value值只能是1或者0。

获取位图指定索引的值

getbit key offset

获取指定位图范围位值为1的个数

bitcount key [start end]

start、end 单位为字节(第几个字节)不指定就获取全部值为1的个数
127.0.0.1:6379> bitcount map
(integer) 11
127.0.0.1:6379> 127.0.0.1:6379> bitcount map 0 1
(integer) 7
127.0.0.1:6379>

集合操作

bitop destkey key [key...]

做多个bitmap的and(交集)、or(并集)、not(非)、xor(异或)操作并将结果保存在destkey中

统计字节数占用多少

strlen key

不是字符串长度而是占据几个字节,超过8位后自己按照8位一组一byte再扩容

应用场景

可以做二值、例如集合元素的取值就只有0和1两种。 在签到打卡的场景中,我们只用记录有签到(1)或没签到(0)

HyperLogLog(统计)

学习hyperloglog之前我们先看看一个场景需要计算不同用户访问某个网站的UV,或者统计网站中某个文档的UV,如果一个用户多次访问网站也需要只算一次。

Unique Visitor(UV),独立访客,一般理解为客户端IP(需要考虑去重处理)。

解决这个问题我们可以通过之前的Set、Hash、BitMap,但是如果数据量非常大例如类似于Google、Baidu这种网站每天的访问量都是上亿级别的。使用以上的三种结构会非常的占用内存,显然是不可行的。Redis便提供了HyperLogLog这种数据结构。

但是bitmap统计的值是非常准确的,hyperloglog 属于是以牺牲准确率的方式来换取空间。

HyperLogLog 的优点在于它所需的内存并不会因为集合的大小而改变,无论集合包含的元素有多少个,HyperLogLog进行计算所需的内存总是固定的,并且是非常少的
每个 HyperLogLog 最多只需要花费 12KB 内存,在标准误差 0.81%的前提下,就可以计算 2 的 64 次方个元素的基数。
HyperLogLog是一个基于基数统计(统计一个集合中不重复的元素个数)的常见方法。

将所有元素添加到key中

pfadd key element

统计key的估算值(不精确)

pfcount key

合并key到新key

pgmerge new_key key1 key2

GEO(地理空间)

Redis 地理空间索引可用于存储坐标和搜索坐标。 这种数据结构对于在给定半径或边界框内查找附近的点非常有用。

增加地理位置信息

geo add key longitude latitude member [longitude latitude member ...]

geoadd bike:rentable 114.05564000000004 22.533181999999996 共享单车01 114.05298900000003 22.533360000000012 共享单车02

127.0.0.1:6379> geoadd bike:rentable 114.05564000000004 22.533181999999996 共享单车01 114.05298900000003 22.533360000000012 共享单车02
(integer) 2

获取地理位置信息

geopos key

127.0.0.1:6379> geopos bike:rentable 共享单车01
1) 1) "114.05564099550247192"
   2) "22.53318317270009885"
127.0.0.1:6379>

获取两个地理位置的距离

geodist key member1 member2 [m|km|ft|mi]

127.0.0.1:6379> geodist bike:rentable 共享单车01  共享单车02 m
"272.9643"
127.0.0.1:6379> geodist bike:rentable 共享单车01 共享单车02 km
"0.2730"
127.0.0.1:6379>

返回数据的GEOhash表示

geohash key menber

127.0.0.1:6379> geohash bike:rentable 共享单车01
1) "ws105qhc310"
127.0.0.1:6379>

获取指定位置范围的地理信息位置集合

GEORADIUS key longitude latitude radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [ASC|DESC] [COUNT count]

127.0.0.1:6379> GEORADIUS bike:rentable 114.057346 22.534364 300 m WITHCOORD WITHDIST WITHHASH ASC COUNT 100
共享单车04
145.5563
4046432247600759
114.05811399221420288
22.53326428377720703
共享单车06
213.2788
4046432250552429
114.05700355768203735
22.53625525474522107
共享单车01
218.9338
4046432247083221
114.05564099550247192
22.53318317270009885
127.0.0.1:6379>

命令介绍
以给定的经纬度为中心, 返回键包含的位置元素当中, 与中心的距离不超过给定最大距离的所有位置元素。

范围可以使用以下其中一个单位:

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

在给定以下可选项时, 命令会返回额外的信息:

  • WITHDIST : 在返回位置元素的同时, 将位置元素与中心之间的距离也一并返回。 距离的单位和用户给定的范围单位保持一致。
  • WITHCOORD : 将位置元素的经度和维度也一并返回。
  • WITHHASH : 以 52 位有符号整数的形式, 返回位置元素经过原始 geohash 编码的有序集合分值。 这个选项主要用于底层应用或者调试, 实际中的作用并不大。 命令默认返回未排序的位置元素。 通过以下两个参数, 用户可以指定被返回位置元素的排序方式:
  • ASC : 根据中心的位置, 按照从近到远的方式返回位置元素。DESC : 根据中心的位置, 按照从远到近的方式返回位置元素。
  • 在默认情况下, GEORADIUS 命令会返回所有匹配的位置元素。 虽然用户可以使用 COUNT 选项去获取前 N 个匹配元素, 但是因为命令在内部可能会需要对所有被匹配的元素进行处理, 所以在对一个非常大的区域进行搜索时, 即使只使用 COUNT 选项去获取少量元素, 命令的执行速度也可能会非常慢。 但是从另一方面来说, 使用 COUNT 选项去减少需要返回的元素数量, 对于减少带宽来说仍然是非常有用的。

获取指定元素范围的地理信息位置集合

georadiusbymenber key menber radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [ASC|DESC] [COUNT count]

127.0.0.1:6379> georadiusbymember bike:rentable 共享单车02 800 m WITHCOORD WITHDIST WITHHASH ASC COUNT 100
共享单车02
0.0000
4046432245687193
114.05299097299575806
22.53336060318125789
共享单车01
272.9643
4046432247083221
114.05564099550247192
22.53318317270009885
共享单车06
523.0595
4046432250552429
114.05700355768203735
22.53625525474522107
共享单车04
526.4221
4046432247600759
114.05811399221420288
22.53326428377720703
127.0.0.1:6379>

以上都是添加和查询功能,删除功能呢?使用type key 发现返回了zset,说明底层是使用zset实现的,我们可以使用zrem 进行数据的删除。

127.0.0.1:6379> type bike:rentable
zset
127.0.0.1:6379>

应用场景

实现附近的xxx功能

Stream(队列)

因为消息可靠性的、和消息积压造成内存资源紧张问题,在日常的业务场景中基本不会用到,就不展开了。

注释:
poll、select、epoll解释
基数估算