Redis内存数据库学习

文章目录

Redis简介

Redis 之所以称之为字典服务,是因为 Redis 是一个 key-value 存储系统。支持存储的 value类型很多,包括 String(字符串)、List(链表)、Set(集合)、Zset(sorted set --有序集合)和 Hash(哈希类型)等。
一种基于内存的,NoSQL非关系型数据库。

NoSQL

NoSQL 数据库的产生就是为了解决大规模数据集合多重数据种类带来的挑战,特别是大数据应用难题。
(1) 键值存储数据库
就像 Map 一样的 key-value 对。典型代表就是 Redis。
(2) 列存储数据库
关系型数据库是典型的行存储数据库。其存在的问题是,按行存储的数据在物理层面占用的是连续存储空间,不适合海量数据存储。而按列存储则可实现分布式存储,适合海量存
储。典型代表是 HBase。
(3) 文档型数据库
其是 NoSQL 与关系型数据的结合,最像关系型数据库的 NoSQL。典型代表是 MongoDB。
(4) 图形(Graph)数据库
用于存放一个节点关系的数据库,例如描述不同人间的关系。典型代表是 Neo4J。

Redis的常用命令

Redis 基本命令

首先通过 redis-cli 命令进入到 Redis 命令行客户端,然后再运行下面的命令。

1.心跳命令 ping

键入 ping 命令,会看到 PONG 响应,则说明该客户端与 Redis 的连接是正常的。该命令亦称为心跳命令。

2.读写键值命令

set key value 会将指定 key-value 写入到 DB。get key 则会读取指定 key 的 value 值。

3.DB 切换 select

Redis 默认有 16 个数据库。默认使用的是 0 号 DB,可以通过 select db 索引来切换 DB。

4.查看 key 数量 dbsize

dbsize 命令可以查看当前数据库中 key 的数量。

5.删除当前库中数据 flushdb

flushdb 命令仅仅删除的是当前数据库中的数据,不影响其它库。

6.删除所有库中数据命令 flushall

flushall 命令可以删除所有库中的所有数据。所以该命令的使用一定要慎重。

7.退出客户端命令

使用 exit 或 quit 命令均可退出 Redis 命令行客户端。

Key 操作命令

Redis 中存储的数据整体是一个 Map,其 key 为 String 类型,而 value 则可以是 String、
Hash 表、List、Set 等类型。

1.keys

格式:KEYS pattern
功能:查找所有符合给定模式 pattern 的 key,pattern 为正则表达式。
说明:KEYS 的速度非常快,但在一个大的数据库中使用它可能会阻塞当前服务器的服务。所以生产环境中一般不使用该命令,而使用 scan 命令代替。

2.exists

格式:EXISTS key
功能:检查给定 key 是否存在。
说明:若 key 存在,返回 1 ,否则返回 0 。

3.del

格式:DEL key [key …]
功能:删除给定的一个或多个 key 。不存在的 key 会被忽略。
说明:返回被删除 key 的数量。

4.rename

格式:RENAME key newkey
功能:将 key 改名为 newkey。
说明:当 key 和 newkey 相同,或者 key 不存在时,返回一个错误。当 newkey 已经存在时, RENAME 命令将覆盖旧值。改名成功时提示 OK ,失败时候返回一个错误。

5.move

格式:MOVE key db
功能:将当前数据库的 key 移动到给定的数据库 db 当中。
说明:如果当前数据库(源数据库)和给定数据库(目标数据库)有相同名字的给定 key ,或者 key 不存在于当前数据库,那么 MOVE 没有任何效果。移动成功返回 1 ,失败则返回 0 。

6.type

格式:TYPE key
功能:返回 key 所储存的值的类型。
说明:返回值有以下六种
none (key 不存在)
string (字符串)
list (列表)
set (集合)
zset (有序集)
hash (哈希表)

7.expire 与 pexpire

格式:EXPIRE key seconds
功能:为给定 key 设置生存时间。当 key 过期时(生存时间为 0),它会被自动删除。
expire 的时间单位为秒,pexpire 的时间单位为毫秒。在 Redis 中,带有生存时间的 key被称为“易失的”(volatile)。
说明:生存时间设置成功返回 1。若 key 不存在时返回 0 。rename 操作不会改变 key的生存时间。

8.ttl 与 pttl

格式:TTL key
功能:TTL, time to live,返回给定 key 的剩余生存时间。
说明:其返回值存在三种可能:
当 key 不存在时,返回 -2 。
当 key 存在但没有设置剩余生存时间时,返回 -1 。
否则,返回 key 的剩余生存时间。ttl 命令返回的时间单位为秒,而 pttl 命令返回的时间单位为毫秒。

9.persist

格式:PERSIST key
功能:去除给定 key 的生存时间,将这个 key 从“易失的”转换成“持久的”。
说明:当生存时间移除成功时,返回 1;若 key 不存在或 key 没有设置生存时间,则返回 0。

10.randomkey

格式:RANDOMKEY
功能:从当前数据库中随机返回(不删除)一个 key。
说明:当数据库不为空时,返回一个 key。当数据库为空时,返回 nil。

11.scan

格式:SCAN cursor [MATCH pattern] [COUNT count] [TYPE type]
功能:用于迭代数据库中的数据库键。其各个选项的意义为:
cursor:本次迭代开始的游标。
pattern :本次迭代要匹配的 key 的模式。
count :本次迭代要从数据集里返回多少元素,默认值为 10 。
type:本次迭代要返回的 value 的类型,默认为所有类型。
SCAN 命令是一个基于游标 cursor 的迭代器:SCAN 命令每次被调用之后,都会向用户返回返回一个包含两个元素的数组, 第一个元素是用于进行下一次迭代的新游标,而第二个元素则是一个数组, 这个数组中包含了所有被迭代的元素。用户在下次迭代时需要使用这个新游标作为 SCAN 命令的游标参数,以此来延续之前的迭代过程。当SCAN 命令的游标参数被设置为 0 时,服务器将开始一次新的迭代。如果新游标返回 0 表示迭代已结束。
说明:使用间断的、负数、超出范围或者其他非正常的游标来执行增量式迭代不会造成服务器崩溃。
当数据量很大时,count 的数量的指定可能会不起作用,Redis 会自动调整每次的遍历数目。由于scan 命令每次执行都只会返回少量元素,所以该命令可以用于生产环境,而不会出现像 KEYS 命令带来的服务器阻塞问题。增量式迭代命令所使用的算法只保证在数据集的大小有界的情况下迭代才会停止,换句话说,如果被迭代数据集的大小不断地增长的话,增量式迭代命令可能永远也无法完成一次完整迭代。即当一个数据集不断地变大时,想要访问这个数据集中的所有元素就需要做越来越多的工作, 能否结束一个迭代取决于用户执行迭代的速度是否比数据集增长的速度更快。
相关命令:另外还有 3 个 scan 命令用于对三种类型的 value 进行遍历。
hscan:属于 Hash 型 Value 操作命令集合,用于遍历当前 db 中指定 Hash 表的所有 field-value 对。
sscan:属于 Set 型 Value 操作命令集合,用于遍历当前 db 中指定 set 集合的所有元素
zscan:属于 ZSet 型 Value 操作命令集合,用于遍历当前 db 中指定有序集合的所有元素(数值与元素值)

String 型 Value 操作命令

Redis 存储数据的 Value 可以是一个 String 类型数据。String 类型的 Value 是 Redis 中最基本,最常见的类型。String 类型的 Value 中可以存放任意数据,包括数值型,甚至是二进制的图片、音频、视频、序列化对象等。一个 String 类型的 Value 最大是 512M 大小。

1.set

格式:SET key value [EX seconds | PX milliseconds] [NX|XX]
功能:SET 除了可以直接将 key 的值设为 value 外,还可以指定一些参数。
EX seconds:为当前 key 设置过期时间,单位秒。等价于 SETEX 命令。
PX milliseconds:为当前 key 设置过期时间,单位毫秒。等价于 PSETEX 命令。
NX:指定的 key 不存在才会设置成功,用于添加指定的 key。等价于 SETNX 命令。
XX:指定的 key 必须存在才会设置成功,用于更新指定 key 的 value。
说明:如果 value 字符串中带有空格,则该字符串需要使用双引号或单引号引起来,否则会认为 set 命令的参数数量不正确,报错。

2.setex 与 psetex

格式:SETEX/PSETEX key seconds value
功能:set expire,其不仅为 key 指定了 value,还为其设置了生存时间。setex 的单位为秒,psetex 的单位为毫秒。
说明:如果 key 已经存在, 则覆写旧值。该命令类似于以下两个命令,不同之处是,
SETEX 是一个原子性操作,关联值和设置生存时间两个动作会在同一时间内完成,该命
令在 Redis 用作缓存时,非常实用。
SET key value
EXPIRE key seconds # 设置生存时间

3.setnx

格式:SETNX key value
功能:SET if Not eXists,将 key 的值设为 value ,当且仅当 key 不存在。若给定的 key
已经存在,则 SETNX 不做任何动作。成功,返回 1,否则,返回 0。
说明:该命令等价于 set key value nx

4.getset

格式:GETSET key value
功能:将给定 key 的值设为 value ,并返回 key 的旧值。
说明:当 key 存在但不是字符串类型时,返回一个错误;当 key 不存在时,返回 nil 。

5.mset 与 msetnx

格式:MSET/MSETNX key value [key value …]
功能:同时设置一个或多个 key-value 对。
说明:如果某个给定 key 已经存在,那么 MSET 会用新值覆盖原来的旧值,如果这不是你所希望的效果,请考虑使用 MSETNX 命令:它只会在所有给定 key 都不存在的情况下进行设置操作。MSET/MSETNX 是一个原子性(atomic)操作,所有给定 key 都会在同一时间内被设置,某些给定 key 被更新而另一些给定 key 没有改变的情况不可能发生。该命令永不失败。

6.mget

格式:MGET key [key …]
功能:返回所有(一个或多个)给定 key 的值。
说明:如果给定的 key 里面,有某个 key 不存在,那么这个 key 返回特殊值 nil 。因此,该命令永不失败。

7.append

格式:APPEND key value
功能:如果 key 已经存在并且是一个字符串, APPEND 命令将 value 追加到 key 原来的值的末尾。如果 key 不存在, APPEND 就简单地将给定 key 设为 value ,就像执行 SET key value 一样。
说明:追加 value 之后, key 中字符串的长度。

8.incr 与 decr

格式:INCR key 或 DECR key
功能:increment,自动递增。将 key 中存储的数字值增一。
decrement,自动递减。将 key 中存储的数字值减一。
说明:如果 key 不存在,那么 key 的值会先被初始化为 0,然后再执行增一/减一操作。如果值不能表示为数字,那么返回一个错误提示。如果执行正确,则返回增一/减一后的值。

9.incrby 与 decrby

格式:INCRBY key increment 或 DECRBY key decrement
功能:将 key 中存储的数字值增加/减少指定的数值,这个数值只能是整数,可以是负数,但不能是小数。
说明:如果 key 不存在,那么 key 的值会先被初始化为 0,然后再执行增/减操作。如果值不能表示为数字,那么返回一个错误提示。如果执行正确,则返回增/减后的值。

10.incrbyfloat

格式:INCRBYFLOAT key increment
功能:为 key 中所储存的值加上浮点数增量 increment 。
说明:与之前的说明相同。没有 decrbyfloat 命令,但 increment 为负数可以实现减操作效果。

11.strlen

格式:STRLEN key
功能:返回 key 所储存的字符串值的长度。
说明:当 key 储存的不是字符串值时,返回一个错误;当 key 不存在时,返回 0 。

12.getrange

格式:GETRANGE key start end
功能:返回 key 中字符串值的子字符串,字符串的截取范围由 start 和 end 两个偏移量决定,包括 start 和 end 在内。
说明:end 必须要比 start 大。支持负数偏移量,表示从字符串最后开始计数,-1 表示最后一个字符,-2 表示倒数第二个,以此类推。

13.setrange

格式:SETRANGE key offset value
功能:用 value 参数替换给定 key 所储存的字符串值 str,从偏移量 offset 开始。
说明:当 offset 值大于 str 长度时,中间使用零字节\x00 填充,即 0000 0000 字节填充;对于不存在的 key 当作空串处理。

14.位操作命令

名称中包含 BIT 的命令,都是对二进制位的操作命令,例如,setbit、getbit、bitcount、
bittop、bitfield,这些命令不常用。

15.典型应用场景

Value 为 String 类型的应用场景很多,这里仅举这种典型应用场景的例子:
(1) 数据缓存
Redis 作为数据缓存层,MySQL 作为数据存储层。应用服务器首先从 Redis 中获取数据,如果缓存层中没有,则从 MySQL 中获取后先存入缓存层再返回给应用服务器。
(2) 计数器
在 Redis 中写入一个 value 为数值型的 key 作为平台计数器、视频播放计数器等。每个有效客户端访问一次,或视频每播放一次,都是直接修改 Redis 中的计数器,然后再以异步方式持久化到其它数据源中,例如持久化到 MySQL。
(3) 共享 Session
对于一个分布式应用系统,如果将类似用户登录信息这样的 Session 数据保存在提供登录服务的服务器中,那么如果用户再次提交像收藏、支付等请求时可能会出现问题:在提供收藏、支付等服务的服务器中并没有该用户的 Session 数据,从而导致该用户需要重新登录。对于用户来说,这是不能接受的。此时,可以将系统中所有用户的 Session 数据全部保存到 Redis 中,用户在提交新的请求后,系统先从 Redis 中查找相应的 Session 数据,如果存在,则再进行相关操作,否则跳转到登录页面。这样就不会引发“重新登录”问题。
(4) 限速器
现在很多平台为了防止 DoS(Denial of Service,拒绝服务)攻击,一般都会限制一个 IP不能在一秒内访问超过 n 次。而 Redis 可以可以结合 key 的过期时间与 incr 命令来完成限速功能,充当限速器。
注意,其无法防止 DDoS(Distributed Denial of Service,分布式拒绝服务)攻击。

Hash 型 Value 操作命令

Redis 存储数据的 Value 可以是一个 Hash 类型。Hash 类型也称为 Hash 表、字典等。
Hash 表就是一个映射表 Map,也是由键-值对构成,为了与整体的 key 进行区分,这里的键称为 field,值称为 value。注意,Redis 的 Hash 表中的 field-value 对均为 String 类型。

1.hset

格式:HSET key field value
功能:将哈希表 key 中的域 field 的值设为 value 。
说明:如果 key 不存在,一个新的哈希表被创建并进行 HSET 操作。如果域 field 已经存在于哈希表中,旧值将被覆盖。如果 field 是哈希表中的一个新建域,并且值设置成功,返回 1 。如果哈希表中域 field 已经存在且旧值已被新值覆盖,返回 0 。

2.get

格式:HGET key field
功能:返回哈希表 key 中给定域 field 的值。
说明:当给定域不存在或是给定 key 不存在时,返回 nil 。

3.hmset

格式:HMSET key field value [field value …]
功能:同时将多个 field-value (域-值)对设置到哈希表 key 中。
说明:此命令会覆盖哈希表中已存在的域。如果 key 不存在,一个空哈希表被创建并执行 HMSET 操作。如果命令执行成功,返回 OK 。当 key 不是哈希表(hash)类型时,返回一个错误。

4.hmget

格式:HMGET key field [field …]
功能:按照给出顺序返回哈希表 key 中一个或多个域的值。
说明:如果给定的域不存在于哈希表,那么返回一个 nil 值。因为不存在的 key 被当作一个空哈希表来处理,所以对一个不存在的 key 进行 HMGET 操作将返回一个只带有 nil 值的表。

5.hgetall

格式:HGETALL key
功能:返回哈希表 key 中所有的域和值。
说明:在返回值里,紧跟每个域名(field name)之后是域的值(value),所以返回值的长度是哈希表大小的两倍。若 key 不存在,返回空列表。若 key 中包含大量元素,则该命令可能会阻塞 Redis 服务。所以生产环境中一般不使用该命令,而使用 hscan 命令代替。

6.hsetnx

格式:HSETNX key field value
功能:将哈希表 key 中的域 field 的值设置为 value ,当且仅当域 field 不存在。
说明:若域 field 已经存在,该操作无效。如果 key 不存在,一个新哈希表被创建并执行 HSETNX 命令。

7.hdel

格式:HDEL key field [field …]
功能:删除哈希表 key 中的一个或多个指定域,不存在的域将被忽略。
说明:返回被成功移除的域的数量,不包括被忽略的域。

8.hexits

格式:HEXISTS key field
功能:查看哈希表 key 中给定域 field 是否存在。
说明:如果哈希表含有给定域,返回 1 。如果不含有给定域,或 key 不存在,返回 0 。

9.hincrby 与 hincrbyfloat

格式:HINCRBY key field increment
功能:为哈希表 key 中的域 field 的值加上增量 increment 。hincrby 命令只能增加整数值,而 hincrbyfloat 可以增加小数值。
说明:增量也可以为负数,相当于对给定域进行减法操作。如果 key 不存在,一个新的哈希表被创建并执行 HINCRBY 命令。如果域 field 不存在,那么在执行命令前,域的值被初始化为 0。对一个储存字符串值的域 field 执行 HINCRBY 命令将造成一个错误。

10.hkeys 与 hvals

格式:HKEYS key 或 HVALS key
功能:返回哈希表 key 中的所有域/值。
说明:当 key 不存在时,返回一个空表。

11.hlen

格式:HLEN key
功能:返回哈希表 key 中域的数量。
说明:当 key 不存在时,返回 0 。

12.hstrlen

格式:HSTRLEN key field
功能:返回哈希表 key 中, 与给定域 field 相关联的值的字符串长度(string length)。
说明:如果给定的键或者域不存在, 那么命令返回 0 。

13.应用场景

Hash 型 Value 非常适合存储对象数据。key 为对象名称,value 为描述对象属性的 Map,对对象属性的修改在 Redis 中就可直接完成。其不像 String 型 Value 存储对象,那个对象是序列化过的,例如序列化为 JSON 串,对对象属性值的修改需要先反序列化为对象后再修改,修改后再序列化为 JSON 串后写入到 Redis。

List 型 Value 操作命令

Redis 存储数据的 Value 可以是一个 String 列表类型数据。即该列表中的每个元素均为String 类型数据。列表中的数据会按照插入顺序进行排序。不过,该列表的底层实际是一个无头节点的双向链表,所以对列表表头与表尾的操作性能较高,但对中间元素的插入与删除的操作的性能相对较差。

1.lpush/rpush

格式:LPUSH key value [value …] 或 RPUSH key value [value …]
功能:将一个或多个值 value 插入到列表 key 的表头/表尾(表头在左表尾在右)
说明:如果有多个 value 值,对于 lpush 来说,各个 value 会按从左到右的顺序依次插入到表头;对于 rpush 来说,各个 value 会按从左到右的顺序依次插入到表尾。如果 key不存在,一个空列表会被创建并执行操作。当 key 存在但不是列表类型时,返回一个错误。执行成功时返回列表的长度。

2.llen

格式:LLEN key
功能:返回列表 key 的长度。
说明:如果 key 不存在,则 key 被解释为一个空列表,返回 0 。如果 key 不是列表类型,返回一个错误。

3.lindex

格式:LINDEX key index
功能:返回列表 key 中,下标为 index 的元素。列表从 0 开始计数。
说明:如果 index 参数的值不在列表的区间范围内(out of range),返回 nil 。

4.lset

格式:LSET key index value
功能:将列表 key 下标为 index 的元素的值设置为 value 。
说明:当 index 参数超出范围,或对一个空列表(key 不存在)进行 LSET 时,返回一个错误。

5.lrange

格式:LRANGE key start stop
功能:返回列表 key 中指定区间[start, stop]内的元素,即包含两个端点。
说明:List 的下标从 0 开始,即以 0 表示列表的第一个元素,以 1 表示列表的第二个元素,以此类推。也可以使用负数下标,以 -1 表示列表的最后一个元素, -2 表示列表的倒数第二个元素,以此类推。超出范围的下标值不会引起错误。如果 start 下标比列表的最大下标 还要大,那么 LRANGE 返回一个空列表。如果 stop 下标比最大下标还要大,Redis 将 stop 的值设置为最大下标。

6.lpushx 与 rpushx

格式:LPUSHX key value 或 RPUSHX key value
功能:将值 value 插入到列表 key 的表头/表尾,当且仅当 key 存在并且是一个列表。
说明:当 key 不存在时,命令什么也不做。若执行成功,则输出表的长度。

7.linsert

格式:LINSERT key BEFORE|AFTER pivot value
功能:将值 value 插入到列表 key 当中,位于元素 pivot 之前或之后。
说明:当 pivot 元素不存在于列表中时,不执行任何操作,返回-1;当 key 不存在时,key 被视为空列表,不执行任何操作,返回 0;如果 key 不是列表类型,返回一个错误;如果命令执行成功,返回插入操作完成之后,列表的长度。

8.lpop / rpop

格式:LPOP key [count] 或 RPOP key [count]
功能:从列表 key 的表头/表尾移除 count 个元素,并返回移除的元素。count 默认值 1
说明:当 key 不存在时,返回 nil

9.blpop / brpop

格式:BLPOP key [key …] timeout 或 BRPOP key [key …] timeout
功能:BLPOP/BRPOP 是列表的阻塞式(blocking)弹出命令。它们是 LPOP/RPOP 命令的阻塞版本,当给定列表内没有任何元素可供弹出的时候,连接将被 BLPOP/BRPOP 命令阻塞,直到等待 timeout 超时或发现可弹出元素为止。当给定多个 key 参数时,按参数 key的先后顺序依次检查各个列表,弹出第一个非空列表的头元素。timeout 为阻塞时长,单位为秒,其值若为 0,则表示只要没有可弹出元素,则一直阻塞。
说明:假如在指定时间内没有任何元素被弹出,则返回一个 nil 和等待时长。反之,返回一个含有两个元素的列表,第一个元素是被弹出元素所属的 key ,第二个元素是被弹出元素的值。

10.rpoplpush

格式:RPOPLPUSH source destination
功能:命令 RPOPLPUSH 在一个原子时间内,执行以下两个动作:
将列表 source 中的最后一个元素(尾元素)弹出,并返回给客户端。
将 source 弹出的元素插入到列表 destination ,作为 destination 列表的的头元素。
如果 source 不存在,值 nil 被返回,并且不执行其他动作。如果 source 和 destination相同,则列表中的表尾元素被移动到表头,并返回该元素,可以把这种特殊情况视作列表的旋转(rotation)操作。

11.brpoplpush

格式:BRPOPLPUSH source destination timeout
功能:BRPOPLPUSH 是 RPOPLPUSH 的阻塞版本,当给定列表 source 不为空时,BRPOPLPUSH 的表现和 RPOPLPUSH 一样。当列表 source 为空时, BRPOPLPUSH 命令将阻塞连接,直到等待超时,或有另一个客户端对 source 执行 LPUSH 或 RPUSH 命令为止。timeout 为阻塞时长,单位为秒,其值若为 0,则表示只要没有可弹出元素,则一直阻塞。
说明:假如在指定时间内没有任何元素被弹出,则返回一个 nil 和等待时长。反之,返回一个含有两个元素的列表,第一个元素是被弹出元素的值,第二个元素是等待时长。

12.lrem

格式:LREM key count value
功能:根据参数 count 的值,移除列表中与参数 value 相等的元素。count 的值可以
是以下几种:
count > 0 : 从表头开始向表尾搜索,移除与 value 相等的元素,数量为 count 。
count < 0 : 从表尾开始向表头搜索,移除与 value 相等的元素,数量为 count 的绝对值。
count = 0 : 移除表中所有与 value 相等的值。
说明:返回被移除元素的数量。当 key 不存在时, LREM 命令返回 0 ,因为不存在的 key 被视作空表(empty list)。

13.ltrim

格式:LTRIM key start stop
功能:对一个列表进行修剪(trim),就是说,让列表只保留指定区间内的元素,不在指定区间之内的元素都将被删除。
说明:下标(index)参数 start 和 stop 都以 0 为底,也就是说,以 0 表示列表的第一个元素,以 1 表示列表的第二个元素,以此类推。也可以使用负数下标,以 -1 表示列表的最后一个元素, -2 表示列表的倒数第二个元素,以此类推。当 key 不是列表类型时,返回一个错误。如果 start 下标比列表的最大下标 end ( LLEN list 减去 1 )还要大,或者 start > stop , LTRIM 返回一个空列表,因为 LTRIM 已经将整个列表清空。如果 stop 下标比 end 下标还要大,Redis 将 stop 的值设置为 end 。

14.应用场景

Value 为 List 类型的应用场景很多,主要是通过构建不同的数据结构来实现相应的业务
功能。这里仅对这些数据结构的实现方式进行总结,不举具体的例子。
(1) 栈
通过 lpush + lpop 可以实现栈数据结构效果:先进后出。通过 lpush 从列表左侧插入数据,通过 lpop 从列表左侧取出数据。当然,通过 rpush + rpop 也可以实现相同效果,只不过操作的是列表右侧。
(2) 队列
通过 lpush + rpop 可以实现队列数据结构效果:先进先出。通过 lpush 从列表左侧插入数据,通过 rpop 从列表右侧取出数据。当然,通过 rpush + lpop 也可以实现相同效果,只不过操作的方向正好相反。
(3) 阻塞式消息队列
通过 lpush + brpop 可以实现阻塞式消息队列效果。作为消息生产者的客户端使用 lpush从列表左侧插入数据,作为消息消费者的多个客户端使用 brpop 阻塞式“抢占”列表尾部数据进行消费,保证了消费的负载均衡与高可用性。brpop 的 timeout 设置为 0,表示只要没有数据可弹出,就永久阻塞。
(4) 动态有限集合
通过 lpush + ltrim 可以实现有限集合。通过 lpush 从列表左侧向列表中添加数据,通过ltrim 保持集合的动态有限性。像企业的末位淘汰、学校的重点班等动态管理,都可通过这种动态有限集合来实现。当然,通过 rpush + ltrim 也可以实现相同效果,只不过操作的方向正好相反。

Set 型 Value 操作命令

Redis 存储数据的 Value 可以是一个 Set 集合,且集合中的每一个元素均 String 类型。Set与 List 非常相似,但不同之处是 Set 中的元素具有无序性与不可重复性,而 List 则具有有序性与可重复性。
Redis 中的 Set 集合与 Java 中的 Set 集合的实现相似,其底层都是 value 为 null 的 hash表。也正因为此,才会引发无序性与不可重复性。

1.sadd

格式:SADD key member [member …]
功能:将一个或多个 member 元素加入到集合 key 当中,已经存在于集合的 member元素将被忽略。
说明:假如 key 不存在,则创建一个只包含 member 元素作成员的集合。当 key 不是集合类型时,返回一个错误。

2.smembers

格式:SMEMBERS key
功能:返回集合 key 中的所有成员。
说明:不存在的 key 被视为空集合。若 key 中包含大量元素,则该命令可能会阻塞 Redis服务。所以生产环境中一般不使用该命令,而使用 sscan 命令代替。

3.scard

格式:SCARD key
功能:返回 Set 集合的长度
说明:当 key 不存在时,返回 0 。

4.sismember

格式:SISMEMBER key member
功能:判断 member 元素是否集合 key 的成员。
说明:如果 member 元素是集合的成员,返回 1 。如果 member 元素不是集合的成员,或 key 不存在,返回 0 。

5.smove

格式:SMOVE source destination member
功能:将 member 元素从 source 集合移动到 destination 集合。
说明:如果 source 集合不存在或不包含指定的 member 元素,则 SMOVE 命令不执行任何操作,仅返回 0 。否则, member 元素从 source 集合中被移除,并添加到destination 集合中去,返回 1。当 destination 集合已经包含 member 元素时, SMOVE命令只是简单地将 source 集合中的 member 元素删除。当 source 或 destination 不是集合类型时,返回一个错误。

6.srem

格式:SREM key member [member …]
功能:移除集合 key 中的一个或多个 member 元素,不存在的 member 元素会被忽略,且返回成功移除的元素个数。
说明:当 key 不是集合类型,返回一个错误。

7.srandmember

格式:SRANDMEMBER key [count]
功能:返回集合中的 count 个随机元素。count 默认值为 1。
说明:若 count 为正数,且小于集合长度,那么返回一个包含 count 个元素的数组,数组中的元素各不相同。如果 count 大于等于集合长度,那么返回整个集合。如果count 为负数,那么返回一个包含 count 绝对值个元素的数组,但数组中的元素可能会出现重复。

8spop

格式:SPOP key [count]
功能:移除并返回集合中的 count 个随机元素。count 必须为正数,且默认值为 1。
说明:如果 count 大于等于集合长度,那么移除并返回整个集合。

9.sdiff / sdiffstore

格式:SDIFF key [key …] 或 SDIFFSTORE destination key [key …]
功能:返回第一个集合与其它集合之间的差集。差集,difference。
说明:这两个命令的不同之处在于,sdiffstore 不仅能够显示差集,还能将差集存储到指定的集合 destination 中。如果 destination 集合已经存在,则将其覆盖。不存在的 key 被视为空集。

10.sinter / sinterstore

格式:SINTER key [key …] 或 SINTERSTORE destination key [key …]
功能:返回多个集合间的交集。交集,intersection。
说明:这两个命令的不同之处在于,sinterstore 不仅能够显示交集,还能将交集存储到指定的集合 destination 中。如果 destination 集合已经存在,则将其覆盖。不存在的 key被视为空集。

11.sunion / sunionstore

格式:SUNION key [key …] 或 SUNIONSTORE destination key [key …]
功能:返回多个集合间的并集。并集,union。
说明:这两个命令的不同之处在于,sunionstore 不仅能够显示并集,还能将并集存储到指定的集合 destination 中。如果 destination 集合已经存在,则将其覆盖。不存在的 key被视为空集。

12.应用场景

Value 为 Set 类型的应用场景很多,这里对这些场景仅进行总结。
(1) 动态黑白名单
例如某服务器中要设置用于访问控制的黑名单。如果直接将黑名单写入服务器的配置文件,那么存在的问题是,无法动态修改黑名单。此时可以将黑名单直接写入 Redis,只要有客户端来访问服务器,服务器在获取到客户端 IP后先从 Redis的黑名单中查看是否存在该 IP,如果存在,则拒绝访问,否则访问通过。
(2) 有限随机数
有限随机数是指返回的随机数是基于某一集合范围内的随机数,例如抽奖、随机选人。通过 spop 或 srandmember 可以实现从指定集合中随机选出元素。
(3) 用户画像
社交平台、电商平台等各种需要用户注册登录的平台,会根据用户提供的资料与用户使用习惯,为每个用户进行画像,即为每个用户定义很多可以反映该用户特征的标签,这些标签就可以使用 sadd 添加到该用户对应的集合中。这些标签具有无序、不重复特征。同时平台还可以使用 sinter/sinterstore 根据用户画像间的交集进行好友推荐、商品推荐、客户推荐等。

有序 Set 型 Value 操作命令

Redis 存储数据的 Value 可以是一个有序 Set,这个有序 Set 中的每个元素均 String 类型。
有序 Set 与 Set 的不同之处是,有序 Set 中的每一个元素都有一个分值 score,Redis 会根据score 的值对集合进行由小到大的排序。其与 Set 集合要求相同,元素不能重复,但元素的score 可以重复。由于该类型的所有命令均是字母 z 开头,所以该 Set 也称为 ZSet。

1.zadd

格式:ZADD key score member [[score member] [score member] …]
功能:将一个或多个 member 元素及其 score 值加入到有序集 key 中的适当位置。
说明:score 值可以是整数值或双精度浮点数。如果 key 不存在,则创建一个空的有序集并执行 ZADD 操作。当 key 存在但不是有序集类型时,返回一个错误。如果命令执行成功,则返回被成功添加的新成员的数量,不包括那些被更新的、已经存在的成员。若写入的 member 值已经存在,但 score 值不同,则新的 score 值将覆盖老 score。

2.zrange 与 zrevrange

格式:ZRANGE key start stop [WITHSCORES] 或 ZREVRANGE key start stop [WITHSCORES]
功能:返回有序集 key 中,指定区间内的成员。zrange 命令会按 score 值递增排序,zrevrange命令会按score递减排序。具有相同 score 值的成员按字典序/逆字典序排列。可以通过使用WITHSCORES 选项,来让成员和它的 score 值一并返回。
说明:下标参数从 0 开始,即 0 表示有序集第一个成员,以 1 表示有序集第二个成员,以此类推。也可以使用负数下标,-1 表示最后一个成员,-2 表示倒数第二个成员,以此类推。超出范围的下标并不会引起错误。例如,当 start 的值比有序集的最大下标还要大,或是 start > stop 时,ZRANGE 命令只是简单地返回一个空列表。再比如 stop 参数的值比有序集的最大下标还要大,那么 Redis 将 stop 当作最大下标来处理。若 key 中指定范围内包含大量元素,则该命令可能会阻塞 Redis 服务。所以生产环境中如果要查询有序集合中的所有元素,一般不使用该命令,而使用 zscan 命令代替。

3.zrangebyscore 与 zrevrangebyscore

格式:ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count]
ZREVRANGEBYSCORE key max min [WITHSCORES] [LIMIT offset count]
功能:返回有序集 key 中,所有 score 值介于 min 和 max 之间(包括等于 min 或max )的成员。有序集成员按 score 值递增/递减次序排列。具有相同 score 值的成员按字典序/逆字典序排列。可选的 LIMIT 参数指定返回结果的数量及区间(就像 SQL 中的SELECT LIMIT offset, count ),注意当 offset 很大时,定位 offset 的操作可能需要遍历整个有序集,此过程效率可能会较低。可选的 WITHSCORES 参数决定结果集是单单返回有序集的成员,还是将有序集成员及其 score 值一起返回。
说明:min 和 max 的取值是正负无穷大的。默认情况下,区间的取值使用闭区间 (小于等于或大于等于),也可以通过给参数前增加左括号“(”来使用可选的开区间 (小于或大于)。

4.zcard

格式:ZCARD key
功能:返回集合的长度
说明:当 key 不存在时,返回 0 。

5.zcount

格式:ZCOUNT key min max
功能:返回有序集 key 中,score 值在 min 和 max 之间(默认包括 score 值等于 min 或 max )的成员的数量。

6.zscore

格式:ZSCORE key member
功能:返回有序集 key 中,成员 member 的 score 值。
说明:如果 member 元素不是有序集 key 的成员,或 key 不存在,返回 nil 。

7.zincrby

格式:ZINCRBY key increment member
功能:为有序集 key 的成员 member 的 score 值加上增量 increment 。increment 值可以是整数值或双精度浮点数。
说明:可以通过传递一个负数值 increment ,让 score 减去相应的值。当 key 不存在,或 member 不是 key 的成员时, ZINCRBY key increment member 等同于 ZADD key increment member 。当 key 不是有序集类型时,返回一个错误。命令执行成功,则返回 member 成员的新 score 值。

8.zrank 与 zrevrank

格式:ZRANK key member 或 ZREVRANK key member
功能:返回有序集 key 中成员 member 的排名。zrank 命令会按 score 值递增排序,zrevrank 命令会按 score 递减排序。
说明:score 值最小的成员排名为 0 。如果 member 不是有序集 key 的成员,返回 nil 。

9.zrem

格式:ZREM key member [member …]
功能:移除有序集 key 中的一个或多个成员,不存在的成员将被忽略。
说明:当 key 存在但不是有序集类型时,返回一个错误。执行成功,则返回被成功移除的成员的数量,不包括被忽略的成员。

10.zremrangebyrank

格式:ZREMRANGEBYRANK key start stop
功能:移除有序集 key 中,指定排名(rank)区间内的所有成员。
说明:排名区间分别以下标参数 start 和 stop 指出,包含 start 和 stop 在内。排名区间参数从 0 开始,即 0 表示排名第一的成员, 1 表示排名第二的成员,以此类推。也可以使用负数表示,-1 表示最后一个成员,-2 表示倒数第二个成员,以此类推。命令执行成功,则返回被移除成员的数量。

11.zremrangebyscore

格式:ZREMRANGEBYSCORE key min max
功能:移除有序集 key 中,所有 score 值介于 min 和 max 之间(包括等于 min 或max )的成员。
说明:命令执行成功,则返回被移除成员的数量。

12.zrangebylex

格式:ZRANGEBYLEX key min max [LIMIT offset count]
功能:该命令仅适用于集合中所有成员都具有相同分值的情况。当有序集合的所有成员都具有相同的分值时,有序集合的元素会根据成员的字典序(lexicographical ordering)来进行排序。即这个命令返回给定集合中元素值介于 min 和 max 之间的成员。如果有序集合里面的成员带有不同的分值, 那么命令的执行结果与 zrange key 效果相同。
说明:合法的 min 和 max 参数必须包含左小括号“(”或左中括号“[”,其中左小括号“(”表示开区间, 而左中括号“[”则表示闭区间。min 或 max 也可使用特殊字符“+”和“-”,分别表示正无穷大与负无穷大。

13.zlexcount

格式:ZLEXCOUNT key min max
功能:该命令仅适用于集合中所有成员都具有相同分值的情况。该命令返回该集合中元素值本身(而非 score 值)介于 min 和 max 范围内的元素数量。

14.zremrangebylex

格式:ZREMRANGEBYLEX key min max
功能:该命令仅适用于集合中所有成员都具有相同分值的情况。该命令会移除该集合中元素值本身介于 min 和 max 范围内的所有元素。

15.应用场景

有序 Set 最为典型的应用场景就是排行榜,例如音乐、视频平台中根据播放量进行排序的排行榜;电商平台根据用户评价或销售量进行排序的排行榜等。将播放量作为 score,将作品 id 作为 member,将用户评价积分或销售量作为 score,将商家 id 作为 member。使用zincrby 增加排序 score,使用 zrevrange 获取 Top 前几名,使用 zrevrank 查询当前排名,使用zscore 查询当前排序 score 等。

HyperLogLog

HyperLogLog 是 Redis 2.8.9 版本中引入的一种新的数据类型,其意义是 hyperlog log,超级日志记录。该数据类型可以简单理解为一个 set 集合,集合元素为字符串。但实际上HyperLogLog 是一种基数计数概率算法,通过该算法可以利用极小的内存完成独立总数的统计。其所有相关命令都是对这个“set 集合”的操作。

1.pfadd

格式:PFADD key element *element …+
功能:将任意数量的元素添加到指定的 HyperLogLog 集合里面。如果内部存储被修改了返回 1,否则返回 0。

2.pfcount

格式:PFCOUNT key *key …+
功能:该命令作用于单个 key 时,返回给定 key 的 HyperLogLog 集合的近似基数;该命令作用于多个 key 时,返回所有给定 key 的 HyperLogLog 集合的并集的近似基数;如果key 不存在,则返回 0。

3.pfmerge

格式:PFMERGE destkey sourcekey *sourcekey …+
功能:将多个 HyperLogLog 集合合并为一个 HyperLogLog 集合,并存储到 destkey 中,合并后的 HyperLogLog 的基数接近于所有 sourcekey 的 HyperLogLog 集合的并集。

4.应用场景

HyperLogLog 可对数据量超级庞大的日志数据做不精确的去重计数统计。当然,这个不精确的度在 Redis 官方给出的误差是 0.81%。这个误差对于大多数超大数据量场景是被允许的。对于平台上每个页面每天的 UV 数据,非常适合使用 HyperLogLog 进行记录。

Geospatial

通过该类型可以设置、查询某地理位置的经纬度,查询某范围内的空间元素,计算两空间元素间的距离等。

1.geoadd

格式:GEOADD key longitude latitude member *longitude latitude member …+
功能:将一到多个空间元素添加到指定的空间集合中。
说明:当用户尝试输入一个超出范围的经度或者纬度时,该命令会返回一个错误。

2.geopos

格式:GEOPOS key member *member …+
功能:从指定的地理空间中返回指定元素的位置,即经纬度。
说明:因为 该命令接受可变数量元素作为输入,所以即使用户只给定了一个元素,命令也会返回数组。

3.geodist

格式:GEODIST key member1 member2 [unit]
功能:返回两个给定位置之间的距离。其中 unit 必须是以下单位中的一种:
m :米,默认
km :千米
mi :英里
ft:英尺
说明:如果两个位置之间的其中一个不存在, 那么命令返回空值。另外,在计算距离时会假设地球为完美的球形, 在极限情况下, 这一假设最大会造成 0.5% 的误差。

4.geohash

格式:GEOHASH key member *member …+
功能:返回一个或多个位置元素的 Geohash 值。
说明:GeoHash 是一种地址编码方法。他能够把二维的空间经纬度数据编码成一个字符串。该值主要用于底层应用或者调试, 实际中的作用并不大。

5.georadius

格式:GEORADIUS key longitude latitude radius m|km|ft|mi [WITHCOORD] [WITHDIST]
[WITHHASH] [ASC|DESC] [COUNT count]
功能:以给定的经纬度为中心,返回指定地理空间中包含的所有位置元素中,与中心距离不超过给定半径的元素。返回时还可携带额外的信息:
WITHDIST :在返回位置元素的同时,将位置元素与中心之间的距离也一并返回。距离的单位和用户给定的范围单位保持一致。
WITHCOORD :将位置元素的经维度也一并返回。
WITHHASH:将位置元素的 Geohash 也一并返回,不过这个 hash 以整数形式表示命令默认返回未排序的位置元素。 通过以下两个参数,用户可以指定被返回位置元素的排序方式:
ASC :根据中心的位置,按照从近到远的方式返回位置元素。
DESC :根据中心的位置,按照从远到近的方式返回位置元素。
说明:在默认情况下, 该命令会返回所有匹配的位置元素。虽然用户可以使用 COUNT 选项去获取前 N 个匹配元素,但因为命令在内部可能会需要对所有被匹配的元素进行处理,所以在对一个非常大的区域进行搜索时,即使使用 COUNT 选项去获取少量元素,该命令的执行速度也可能会非常慢。

6.georadiusbymember

格式:GEORADIUSBYMEMBER key member radius m|km|ft|mi [WITHCOORD] [WITHDIST]
[WITHHASH] [ASC|DESC] [COUNT count]
功能:这个命令和 GEORADIUS 命令一样,都可以找出位于指定范围内的元素,但该命令的中心点是由位置元素形式给定的,而不是像 GEORADIUS 那样,使用输入的经纬度来指定中心点。
说明:返回结果中也是包含中心点位置元素的

7.应用场景

Geospatial 的意义是地理位置,所以其主要应用地理位置相关的计算。例如,微信发现中的“附近”功能,添加朋友中“雷达加朋友”功能;QQ 动态中的“附近”功能;钉钉中的“签到”功能等。

Redis 事务

Redis 的事务的本质是一组命令的批处理。这组命令在执行过程中会被顺序地、一次性全部执行完毕,只要没有出现语法错误,这组命令在执行期间是不会被中断。

1.Redis 事务特性

Redis 的事务仅保证了数据的一致性,不具有像 DBMS 一样的 ACID 特性。
这组命令中的某些命令的执行失败不会影响其它命令的执行,不会引发回滚。即不具备原子性。
这组命令通过乐观锁机制实现了简单的隔离性。没有复杂的隔离级别。
这组命令的执行结果是被写入到内存的,是否持久取决于 Redis 的持久化策略,与事务无关。

2.Redis 事务实现

Redis 事务通过三个命令进行控制。
muti:开启事务
exec:执行事务
discard:取消事务

Redis中的list类型

在Redis中,list类型的数据是一个先进后出、后进先出的栈结构
压栈:Push
弹栈:Pop
可以选择从左侧压栈来存入数据
可以选择从右侧压栈来存入数据(一般选择)
从Redis中读取list数据时,始终是从左至右读取的,通常,为了更加符合使用列表的习惯,大多情况下采取“从XX压入数据”的做法。

Redis相关test


@SpringBootTest
public class RedisTests {

    @Autowired
    RedisTemplate<String, Serializable> redisTemplate;

    // 当读写操作与值无关时,直接调用redisTemplate的方法
    // 当需要读写Redis中string类型的数据时,需要先opsForValue
    // 当需要读写Redis中list类型的数据时,需要先opsForList
    // 当需要读写Redis中set类型的数据时,需要先opsForSet

    @Test
    void setValue() {
        ValueOperations<String, Serializable> opsForValue = redisTemplate.opsForValue();
        opsForValue.set("username1", "肖老师", 20, TimeUnit.SECONDS);
    }

    @Test
    void getValue() {
        ValueOperations<String, Serializable> opsForValue = redisTemplate.opsForValue();
        Serializable username1 = opsForValue.get("username1");
        System.out.println(username1);
    }

    @Test
    void setObjectValue() {
        Tag tag = new Tag();
        tag.setId(678L);
        tag.setName("Redis");

        ValueOperations<String, Serializable> opsForValue = redisTemplate.opsForValue();
        opsForValue.set("tag1", tag);
    }

    @Test
    void getObjectValue() {
        ValueOperations<String, Serializable> opsForValue = redisTemplate.opsForValue();
        Serializable serializable = opsForValue.get("tag1");
        System.out.println(serializable.getClass().getName());
        System.out.println(serializable);

        Tag tag = (Tag) serializable;
        System.out.println(tag);
    }

    @Test
    void delete() {
        String key = "tag1";
        Boolean deleteResult = redisTemplate.delete(key);
        System.out.println(deleteResult);
    }

    @Test
    void deleteBatch() {
        Set<String> keys = new HashSet<>();
        keys.add("username1");
        keys.add("username2");
        keys.add("username3");
        keys.add("username4");
        keys.add("username5");

        Long deleteCount = redisTemplate.delete(keys);
        System.out.println("deleteCount = " + deleteCount);
    }

    @Test
    void keys() {
        String pattern = "*";
        Set<String> keys = redisTemplate.keys(pattern);
        for (String key : keys) {
            System.out.println(key);
        }
    }

    // 向Redis中存入list数据,具体内容为8个字符串
    @Test
    void rightPush() {
        ListOperations<String, Serializable> opsForList = redisTemplate.opsForList();
        String key = "name-list";

        opsForList.rightPush(key, "name-1");
        opsForList.rightPush(key, "name-2");
        opsForList.rightPush(key, "name-3");
        opsForList.rightPush(key, "name-4");
        opsForList.rightPush(key, "name-5");
        opsForList.rightPush(key, "name-6");
        opsForList.rightPush(key, "name-7");
        opsForList.rightPush(key, "name-8");
    }

    // 从Redis中读取list数据
    @Test
    void range() {
        ListOperations<String, Serializable> opsForList = redisTemplate.opsForList();
        String key = "name-list";
        long start = 0;//redis无越界,默认为最大
        long end = -1;//全部都读取

        List<Serializable> list = opsForList.range(key, start, end);
        System.out.println("读取列表完成,列表长度:" + list.size());
        for (Serializable serializable : list) {
            System.out.println(serializable);
        }
    }
}

从左至右读取

关于Key的格式

  • Redis本身并没有要求Key应该如何定义,但是,由于Redis被设计为可以存放特别多数据,并且,各种各样的数据都可以放在Redis中,所以,在实际应用中,应该使用有规律Key,否则,后续读写数据时会很麻烦!

    • 例如:当存放“用户”相关数据时,应该使用user字样作为Key的一部分,存放“文章”相关数据时,应该使用article字样作为Key的一部分。

    • 示例:假设有20个用户数据,ID分别是从1到20,使用Redis中的string类型分别存储这20个用户数据时,各数据对应的Key可以是:user-1user-2、……、user-20,后续,只要知道被获取数据的ID,就可以知道Key,并基于这个Key来读取数据。

  • 在定义Key时,各部分之间应该使用某个符号进行分隔,例如user-1就是在user前缀和ID之间使用了减号进行分隔!

  • 在开发实践中,Key的定义应该是多层级的,并且,应该保证同类数据一定具有相同的组成部分,例如:

    • 用户详情数据:user:item:1user:item:2
    • 用户列表数据:user:list
    • 文章详情数据:article:item:1article:item:2
    • 文章列表数据:article:list

Redis编程

引入依赖

<!-- Spring Boot支持Redis编程的依赖项 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

配置

  # Redis配置
  redis:
    # 主机
    host: localhost
    # 端口
    port: 6379
    # 用户名
    username: ~
    # 密码
    password: ~

当需要读写Redis中的数据时,需要使用RedisTemplate的工具类,通常,会使用配置类中的@Bean方法来配置此类的对象,以便于后续可以随时通过自动装配来获取此类的对象!


/**
 * Redis的配置类
 */
@Slf4j
@Configuration
public class RedisConfiguration {

    public RedisConfiguration() {
        log.debug("创建配置类对象:RedisConfiguration");
    }

    @Bean
    public RedisTemplate<String, Serializable> redisTemplate(
            RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<String, Serializable> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(redisConnectionFactory);//设置连接工程
        redisTemplate.setKeySerializer(RedisSerializer.string());//返回一个字符串的序列化器
        redisTemplate.setValueSerializer(RedisSerializer.json());//值的序列化器
        return redisTemplate;
    }
}

使用Redis时的数据一致性问题

在开发实践中,数据最终肯定是保存在关系型数据库(例如MySQL等)中的,同时,为了提高查询效率、保护关系型数据库,通常会将某些数据从关系型数据库中读出来,并写入到Redis中,后续,将优先从Redis中读取数据!

由于在关系型数据库和Redis中都存储了数据,如果某个数据需要修改,最终修改的肯定是关系型数据库中的数据,但是,如果Redis中的数据没有及时更新,却仍从Redis中读取数据,则读取到的数据就是“不准确的”!

所以,当同一个数据在多个不同的位置存储了多份,就可能出现以上问题,通常称之为“数据一致性”的问题,即2个或多个不同的存储位置,本应该“相同”的数据其实“并不相同”!

关于数据一致性问题

  • 并不一定有必要及时更新数据,例如:MySQL中的数据发生了变化,但是Redis中却不更新,此时,数据并不一致,但是,对于软件的使用可能没有严重影响
    • 例如:某个热门视频的播放次数
    • 例如:购买火车票时,列表页面中显示的的各车次的余票数量
  • 某些数据的更新频率可能非常低,这类数据基本上没有数据一致性问题
    • 例如:全国省市区数据
  • 并不是所有数据都适合在Redis中也存一份,对于访问频率非常低的数据,或数据量特别大的数据,可以不使用Redis
    • 例如:用户3年前的历史订单

解决数据一致性问题的做法主要有

  • 即时更新:当关系型数据库的数据发生变化,马上更新Redis中的数据
    • 保证数据的实时一致性
  • 周期性更新:当关系型数据库的数据发生变化,不会马上更新Redis中的数据,而是每间隔一段时间,或到了某个指定的时间时,执行1次同步数据的操作
    • 保证数据的最终一致性
  • 手动更新:当关系型数据库的数据发生变化,不会马上更新Redis中的数据,而是由管理人员(或运营人员)明确执行更新操作,才会同步数据
    • 保证数据的最终一致性

在项目中使用redis

实现手动更新:
1.redis的repository层接口三个方法:向缓存中写入数据、删除缓存中的数据、从缓存中读取数据
2.impl:再重写方法
3.service层:重建缓存中的数据–先删除–再创建list集合–再写入
4.controller层

关于ApplicationRunner

在Spring Boot中,可以自定义组件类,实现ApplicationRunner接口,重写其中的run()抽象方法,后续,当启动项目后,会在启动成功后的第一时间自动调用此run()方法!

利用以上机制,可以实现“缓存预热”(项目启动时就将数据加载到缓存中)。

@Slf4j
@Component
public class LoadCacheRunner implements ApplicationRunner {

    @Autowired
    private ICategoryService categoryService;

    @Override
    public void run(ApplicationArguments args) throws Exception {
        log.debug("开始执行【加载缓存数据】");
        categoryService.rebuildListCache();
    }

}

!!!项目启动成功后立马加载缓存数据
在这里插入图片描述在这里插入图片描述

计划任务

配置类 ScheduleConfiguration

  • 在Spring Boot中,默认禁止了所有的计划任务,需要在配置类上添加@EnableScheduling注解,以启用当前项目中所有计划任务。
  • 在项目中,自定义组件类,并在组件类中自定义方法,在方法上添加@Scheduled注解,则此方法就是一个计划任务方法,可以周期性的执行。
@Slf4j
@Configuration
@EnableScheduling
public class ScheduleConfiguration {

    public ScheduleConfiguration() {
        log.debug("创建配置类对象:ScheduleConfiguration");
    }

}

处理类别缓存的计划任务

/**
 * 处理类别缓存的计划任务
 */
@Slf4j
@Component
public class CategoryCacheSchedule {

    @Autowired
    private ICategoryService categoryService;

    public CategoryCacheSchedule() {
        log.debug("创建计划任务组件对象:CategoryCacheSchedule");
    }

    // fixedRate:执行频率,根据上一次执行的开始时间来计算下一次的执行时间,取值以毫秒为单位
    // fixedDelay:执行间隔,根据上一次执行的结束时间来计算下一次的执行时间,取值以毫秒为单位
    // cron:使用cron表达式来配置执行时间
    // -- cron表达式的本质是一个字符串,此字符串中包括6~7个域,每个域的值之间使用空格分隔
    // -- cron表达式中的域从左至右依次是:秒 分 时 日 月 周 [年]
    // -- 各域的值可以使用通配符:
    // -- 使用星号(*)表示任意值
    // -- 使用问号(?)表示不关心此值,此通配符只能用于“日”和“周”所在的域
    // -- 在各个域中,可以使用减号表示连接的区间,例如在“月”所在的域使用 5-7 表示“从5至7”
    // -- 在各个域中,可以使用英文的逗号分隔多个值,表示满足列举的任意值均可,例如在“月”所在的域使用 1,3,5,7,9 表示1月或3月或5月或7月或9月均匹配
    // -- 在各个域中,数字值可以写成“x/y”的格式,表示此域的值为x时执行,且每间隔y再次执行,直至其它域的值不满足匹配
    // -- 更多内容可参考:https://zhuanlan.zhihu.com/p/573018560
    @Scheduled(fixedRate = 2 * 60 * 1000)
    public void rebuildListCache() {
        log.debug("开始执行【重建缓存中的类别列表】的计划任务");
        categoryService.rebuildListCache();
    }

}

!!!计划任务在项目启动之前
在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值