Redis实践应用
Redis 是一款高性能的键值对数据库,它在内存中存储数据并支持持久化。
Redis 提供了丰富的数据类型和功能,使其成为许多应用场景的理想选择。
本文将以5.x版本为例深入探讨 Redis 的实践应用。
# 一、数据类型及其命令
Redis 支持多种数据类型,这里先对其基础类型进行介绍。
# 1、字符串(String)
字符串是 Redis 中最基本的数据结构,它可以存储文本或二进制数据。相关命令如下:
set
: 用来设置 string 值,第一个参数是 key,第二个参数是 value。示例:set mykey "something"
。get
: 获取 key 对应的 string 值。示例:get mykey
。strlen
: 获取 key 对应的 string 值的长度。示例:strlen mykey
。setnx
: 如果 key 不存在,则设置 string 值。示例:setnx mykey "newvalue"
。setex
: 设置 key 对应的 string 值,并设置过期时间(单位:秒)。示例:setex mykey 60 "newvalue"
。psetex
: 设置 key 对应的 string 值,并设置过期时间(单位:毫秒)。示例:psetex mykey 60000 "newvalue"
。mset
: 一次设置多个 key 的 string 值。示例:mset key1 "value1" key2 "value2" key3 "value3"
。mget
: 一次获取多个 key 的 string 值。示例:mget key1 key2 key3
。msetnx
: 如果所有给定的 key 都不存在,则一次设置多个 key 的 string 值。示例:msetnx key1 "value1" key2 "value2"
。incr
: 将 key 对应的数字值自增 1。示例:incr mykey
。decr
: 将 key 对应的数字值自减 1。示例:decr mykey
。incrby
: 将 key 对应的数字值增加指定整数。示例:incrby mykey 5
。decrby
: 将 key 对应的数字值减少指定整数。示例:decrby mykey 5
。incrbyfloat
: 将 key 对应的数字值增加指定浮点数。示例:incrbyfloat mykey 1.5
。append
: 将指定字符串追加到 key 对应的 string 值。示例:append mykey "extra"
。getrange
: 获取 key 对应的 string 值的指定范围。示例:getrange mykey 0 4
。setrange
: 修改 key 对应的 string 值的指定范围。示例:setrange mykey 4 "newpart"
。getset
: 将 key 对应的 string 值设置为新值,并返回旧值。示例:getset mykey "newvalue"
。
# 2、列表(List)
列表是一个有序的字符串集合,可以通过索引访问。相关命令如下:
lpush
: 在列表头部插入一个或多个值。示例:lpush mylist "apple" "banana" "cherry"
。rpush
: 在列表尾部插入一个或多个值。示例:rpush mylist "apple" "banana" "cherry"
。lpop
: 移除并返回列表头部的元素。示例:lpop mylist
。rpop
: 移除并返回列表尾部的元素。示例:rpop mylist
。lrange
: 返回列表中指定范围内的元素。示例:lrange mylist 0 -1
。llen
: 返回列表的长度。示例:llen mylist
。lindex
: 返回列表中指定位置的元素。示例:lindex mylist 0
。lset
: 设置列表中指定位置的元素。示例:lset mylist 0 "orange"
。linsert
: 在列表的指定元素前或后插入一个新元素。示例:linsert mylist before "banana" "orange"
。lrem
: 移除列表中指定数量的指定元素。示例:lrem mylist 1 "banana"
。rpoplpush
: 移除列表的尾部元素,并将其插入到另一个列表的头部。示例:rpoplpush sourceList destList
。
阻塞相关的命令:
blpop
: 从列表头部移除并返回第一个非空元素,如果列表为空则阻塞等待。示例:blpop mylist 0
。brpop
: 从列表尾部移除并返回第一个非空元素,如果列表为空则阻塞等待。示例:brpop mylist 0
。brpoplpush
: 从一个列表的尾部移除元素,并将其插入到另一个列表的头部,同时返回该元素。如果源列表为空则阻塞等待。示例:brpoplpush sourceList destList 0
。
请注意,blpop
和 brpop
命令的最后一个参数是超时时间(单位:秒)。设置为 0 表示无限期等待。
# 3、集合(Set)
集合是一个无序的、不包含重复元素的字符串集合。相关命令如下:
sadd
: 向集合添加一个或多个成员。示例:sadd myset "apple" "banana" "cherry"
。smembers
: 返回集合中的所有成员。示例:smembers myset
。sismember
: 判断成员是否是集合的成员。示例:sismember myset "apple"
。scard
: 获取集合的成员数。示例:scard myset
。spop
: 移除并返回集合中的一个随机元素。示例:spop myset
。srem
: 移除集合中一个或多个成员。示例:srem myset "apple" "banana"
。sunion
: 返回给定集合的并集。示例:sunion set1 set2
。sinter
: 返回给定集合的交集。示例:sinter set1 set2
。sdiff
: 返回给定集合的差集。示例:sdiff set1 set2
。smove
: 将一个成员从一个集合移动到另一个集合。示例:smove sourceSet destSet "apple"
。sdiffstore
: 返回给定集合的差集并存储在目标集合。示例:sdiffstore destSet set1 set2
。sinterstore
: 返回给定集合的交集并存储在目标集合。示例:sinterstore destSet set1 set2
。sunionstore
: 返回给定集合的并集并存储在目标集合。示例:sunionstore destSet set1 set2
。
# 4、有序集合(Sorted Set)
有序集合类似于集合,但是每个成员都关联了一个分数(score),根据分数排序。相关命令如下:
zadd
: 向有序集合添加一个或多个成员,或更新已存在成员的分数。示例:zadd myzset 1 "apple" 2 "banana" 3 "cherry"
。zrange
: 返回有序集合中指定范围内的成员,按分数值递增排序。示例:zrange myzset 0 -1
。zrevrange
: 返回有序集合中指定范围内的成员,按分数值递减排序。示例:zrevrange myzset 0 -1
。zrangebyscore
: 返回有序集合中指定分数区间内的成员,按分数值递增排序。示例:zrangebyscore myzset 1 3
。zrevrangebyscore
: 返回有序集合中指定分数区间内的成员,按分数值递减排序。示例:zrevrangebyscore myzset 3 1
。zcount
: 返回有序集合中指定分数区间内的成员数量。示例:zcount myzset 1 3
。zcard
: 获取有序集合的成员数。示例:zcard myzset
。zscore
: 返回有序集合中指定成员的分数。示例:zscore myzset "apple"
。zrank
: 返回有序集合中指定成员的排名,按分数值递增排序。示例:zrank myzset "apple"
。zrevrank
: 返回有序集合中指定成员的排名,按分数值递减排序。示例:zrevrank myzset "apple"
。zrem
: 移除有序集合中的一个或多个成员。示例:zrem myzset "apple" "banana"
。zincrby
: 为有序集合中的成员增加分数。示例:zincrby myzset 1 "apple"
。zremrangebyrank
: 移除有序集合中指定排名区间的所有成员。示例:zremrangebyrank myzset 0 1
。zremrangebyscore
: 移除有序集合中指定分数区间的所有成员。示例:zremrangebyscore myzset 1 2
。zinterstore
: 计算给定的一个或多个有序集合的交集并将结果集存储在新的有序集合中。示例:zinterstore out 2 zset1 zset2
。zunionstore
: 计算给定的一个或多个有序集合的并集并将结果集存储在新的有序集合中。示例:zunionstore out 2 zset1 zset2
。zscan
: 增量迭代有序集合中的元素。示例:zscan myzset 0
。
# 5、哈希(Hash)
哈希是一个由键值对组成的无序集合,它将键值对映射到一个 Redis 键。相关命令如下:
hset
: 为哈希表中的字段赋值。示例:hset myhash field1 "value1"
。hget
: 获取哈希表中指定字段的值。示例:hget myhash field1
。hexists
: 查看哈希表中,指定的字段是否存在。示例:hexists myhash field1
。hdel
: 删除哈希表中的一个或多个指定字段。示例:hdel myhash field1 field2
。hlen
: 获取哈希表中字段的数量。示例:hlen myhash
。hkeys
: 获取哈希表中的所有字段名。示例:hkeys myhash
。hvals
: 获取哈希表中的所有字段值。示例:hvals myhash
。hgetall
: 获取哈希表中的所有字段和值。示例:hgetall myhash
。hincrby
: 为哈希表中的指定字段的整数值加上增量。示例:hincrby myhash field1 1
。hincrbyfloat
: 为哈希表中的指定字段的浮点数值加上增量。示例:hincrbyfloat myhash field1 1.0
。hmset
: 同时将多个字段值对设置到哈希表的多个字段。示例:hmset myhash field1 "value1" field2 "value2"
。hmget
: 获取哈希表中所有给定字段的值。示例:hmget myhash field1 field2
。hscan
: 增量地迭代哈希表中的键值对。示例:hscan myhash 0
。
# 二、数据库命令
Redis 支持多个数据库,每个数据库都是一个独立的键空间。以下是与数据库相关的命令:
select
: 切换到指定的数据库。示例:select 1
。flushdb
: 清空当前数据库中的所有 key。示例:flushdb
。flushall
: 清空整个 Redis 服务器的数据。示例:flushall
。dbsize
: 返回当前数据库的 key 的数量。示例:dbsize
。info
: 获取 Redis 服务器的信息和统计数据。示例:info
。lastsave
: 返回最近一次 Redis 成功将数据存储到磁盘上的时间。示例:lastsave
。bgsave
: 在后台异步保存当前数据库的数据到磁盘。示例:bgsave
。save
: 阻塞方式将所有数据保存到磁盘。示例:save
。config get
: 获取 Redis 配置参数的值。示例:config get maxmemory
。config set
: 设置 Redis 配置参数的值。示例:config set maxmemory 100mb
。slaveof
: 改变复制策略设置。示例:slaveof 127.0.0.1 6379
。monitor
: 实时打印出 Redis 服务器接收到的命令
# 三、连接命令
Redis为了确保连接的安全性和稳定性,有如下命令:
auth
: 通过密码认证连接。ping
: 检查与服务器的连接是否仍然活着。echo
: 返回给定的字符串。select
: 选择指定的数据库。quit
: 关闭连接。client getname
: 获取与客户端相关联的名字。client setname
: 将与客户端相关联的名字设置为给定的字符串。client list
: 获取所有连接到服务器的客户端信息。client kill
: 关闭指定的客户端连接。
通过auth命令可以进行密码认证,确保连接的安全性。使用select命令可以切换到指定的数据库上下文。通过client命令可以获取或设置客户端相关的信息,以及管理连接到服务器的客户端。
# 四、命令
Redis 中的键(key)是唯一的,用于标识存储的数据。以下是与 key 相关的命令:
del
: 删除给定的一个或多个 key。示例:del key1 key2
。dump
: 序列化给定 key,并返回被序列化的值。示例:dump mykey
。exists
: 检查给定 key 是否存在。示例:exists mykey
。expire
: 为给定 key 设置过期时间(以秒为单位)。示例:expire mykey 60
。expireat
: 为给定 key 设置过期时间(以 POSIX 时间戳为单位)。示例:expireat mykey 1677598225
。keys
: 查找所有符合给定模式的 key。示例:keys *
。move
: 将当前数据库的 key 移动到给定的数据库。示例:move mykey 1
。persist
: 移除给定 key 的过期时间。示例:persist mykey
。pexpire
: 为给定 key 设置过期时间(以毫秒为单位)。示例:pexpire mykey 60000
。pexpireat
: 为给定 key 设置过期时间(以毫秒级别的 POSIX 时间戳为单位)。示例:pexpireat mykey 1677598225000
。pttl
: 以毫秒为单位返回 key 的剩余过期时间。示例:pttl mykey
。rename
: 修改 key 的名称。示例:rename oldkey newkey
。renamenx
: 仅当新 key 不存在时,将 key 改名为 newkey。示例:renamenx oldkey newkey
。ttl
: 以秒为单位,返回给定 key 的剩余过期时间。示例:ttl mykey
。type
: 返回 key 所储存的值的数据类型。示例:type mykey
。scan
: 增量地迭代一组 key 和相应的值。示例:scan 0
。
# 五、位图(Bitmaps)
位图本质上是一个字符串,它将特殊的命令用于处理位值。位图可用于大量的计数场景,如用户在线状态、网站访问统计等。相关命令如下:
setbit
: 对 key 所储存的字符串值,设置或清除指定偏移量上的位(bit)。示例:setbit mykey 7 1
。getbit
: 对 key 所储存的字符串值,获取指定偏移量上的位(bit)。示例:getbit mykey 7
。bitcount
: 计算给定字符串中被设置为 1 的位的数量。示例:bitcount mykey
。bitop
: 对一个或多个保存二进制位的字符串 key 进行位元操作,并将结果保存到 destkey 上。示例:bitop AND destkey mykey1 mykey2
。bitpos
: 返回 key 对应的字符串值中第一个值为 bit 的位的位置。示例:bitpos mykey 1
。bitfield
: 对一个或多个整数字段进行位操作。示例:bitfield mykey set i5 0 31
。
# 六、HyperLogLog(基数估算器)
HyperLogLog 是一种基数估算器,用于估算一个集合中不重复元素的数量。相关命令如下:
pfadd
: 将指定元素添加到 HyperLogLog 中。示例:pfadd myhll "apple" "banana" "cherry"
。pfcount
: 返回给定 HyperLogLog 的基数估算值。示例:pfcount myhll
。pfmerge
: 将多个 HyperLogLog 合并为一个。示例:pfmerge desthll hll1 hll2 hll3
。
# 七、GeoHash
Redis 支持地理位置数据的存储和查询,基于 GeoHash 算法。相关命令如下:
geoadd
: 添加地理位置坐标。示例:geoadd mygeo 13.361389 38.115556"Palermo" 15.087269 37.502669 "Catania"
。geopos
: 获取地理位置的坐标。示例:geopos mygeo "Palermo" "Catania"
。geodist
: 计算两个地理位置之间的距离。示例:geodist mygeo "Palermo" "Catania" km
。georadius
: 查询指定范围内的地理位置。示例:georadius mygeo 15 37 100 km
。georadiusbymember
: 查询与指定地理位置成员在给定范围内的地理位置。示例:georadiusbymember mygeo "Palermo" 100 km
。
# 八、发布与订阅
Redis提供了简单的发布订阅功能。相关命令如下:
subscribe
: 订阅一个或多个频道。示例:subscribe news
或subscribe news sports
。unsubscribe
: 取消订阅一个或多个频道。示例:unsubscribe news
或unsubscribe news sports
。psubscribe
: 订阅一个或多个匹配模式。示例:psubscribe news.*
或psubscribe *news* sports.*
。punsubscribe
: 取消订阅一个或多个匹配模式。示例:punsubscribe news.*
或punsubscribe *news* sports.*
。publish
: 向指定频道发布消息。示例:publish news "Hello, world!"
。pubsub channels [pattern]
: 列出所有被订阅的频道。如果指定了pattern,则只列出与之匹配的频道。示例:pubsub channels
或pubsub channels news.*
。pubsub numsub [channel [channel ...]]
: 获取指定频道的订阅者数量。如果未指定任何频道,则返回所有频道的订阅者数量。示例:pubsub numsub news
或pubsub numsub news sports
。pubsub numpat
: 获取匹配模式的订阅数量。示例:pubsub numpat
。
# 九、事务
Redis提供对事务的支持。相关命令如下:
multi
: 开启一个新事务。exec
: 执行所有事务内的命令。如果事务中有语法错误或运行时错误,整个事务将会回滚。discard
: 放弃当前事务。watch
: 监视一个或多个键,如果在执行exec之前键的值发生变化,整个事务将被回滚。可以监视多个键,也可以在一个事务内多次使用watch命令。unwatch
: 取消所有键的监视。
在Redis事务中,使用multi命令开始一个新事务,然后按顺序执行一系列命令。这些命令不会立即被执行,而是被存储在事务队列中。当执行exec命令时,Redis会逐个执行所有事务内的命令。如果事务中所有命令都执行成功,则事务被提交;如果有任何一个命令执行失败,则整个事务将被回滚。
watch命令用于在事务执行期间监视一个或多个键,如果在执行exec之前键的值发生变化,整个事务将被回滚。这样可以避免在事务执行期间其他客户端对被监视的键进行操作导致数据不一致的问题。
discard命令用于放弃当前事务,将事务队列清空并退出事务执行环境。
Redis事务允许在单个客户端内执行一系列操作,将这些操作作为一个原子性操作进行提交或回滚。使用事务可以保证一系列操作的原子性,避免出现数据不一致的问题。
# 十、持久化
Redis提供了两种持久化方式,分别是RDB持久化和AOF持久化。
# 1、RDB持久化
RDB持久化是Redis默认的持久化方式,它将Redis在内存中的数据定期保存到磁盘上,形成一个快照文件。RDB文件可以用于备份、恢复或者迁移Redis实例。
RDB持久化相关的命令:
save
: 在主线程中同步执行SAVE命令,将数据保存到磁盘上。在持久化操作完成之前,Redis主线程将被阻塞。bgsave
: 异步执行BGSAVE命令,将数据保存到磁盘上。在持久化操作完成之前,Redis主线程不会被阻塞。lastsave
: 返回上一次成功将数据保存到磁盘上的时间,以UNIX时间戳的形式表示。
# 2、AOF持久化
AOF持久化是一种追加式文件写入方式,它记录Redis服务器接收到的每一条写命令,并将这些命令追加到AOF文件末尾。当Redis重新启动时,它可以通过重新执行AOF文件中记录的命令来恢复数据。
AOF持久化相关的命令:
bgrewriteaof
: 异步执行BGREWRITEAOF命令,将AOF文件重写为更紧凑的格式。config set appendonly yes
: 启用AOF持久化。config set appendfsync always
: 将AOF缓冲区中的命令写入磁盘的频率设置为每次命令都写入磁盘。config set appendfsync everysec
: 将AOF缓冲区中的命令写入磁盘的频率设置为每秒钟写入一次磁盘。config set appendfsync no
: 将AOF缓冲区中的命令写入磁盘的频率设置为尽可能少地写入磁盘。
Redis提供了RDB持久化和AOF持久化两种方式,以确保数据在发生故障时不会丢失。使用RDB持久化可以定期保存Redis的快照文件,而使用AOF持久化可以记录Redis服务器接收到的每一条写命令。在生产环境中,通常会同时使用这两种方式来提高数据的安全性和可靠性。
# 十一、Lua脚本
Redis脚本是一种由Lua脚本语言编写的命令序列,可以在Redis服务器端运行。脚本可以用于执行复杂的数据处理、计算或者业务逻辑,也可以用于实现原子性的操作或者复杂的事务。
Redis提供了以下脚本相关的命令:
eval
: 执行Lua脚本,并返回执行结果。evalsha
: 执行已经存储在服务器端的Lua脚本,并返回执行结果。script exists
: 检查指定的脚本是否已经被服务器端缓存。script load
: 将指定的脚本缓存到服务器端,并返回脚本的SHA1摘要。script flush
: 清空服务器端缓存的所有Lua脚本。script kill
: 中断正在执行的Lua脚本。
使用eval命令可以执行一个由Lua脚本语言编写的命令序列,并返回执行结果。eval命令需要传入两个参数:第一个参数是Lua脚本字符串,第二个参数是一个可选的参数列表,用于传递参数给脚本。脚本可以使用Redis提供的一系列API来访问Redis的数据结构,进行数据处理和计算等操作。
使用evalsha命令可以执行已经存储在服务器端的Lua脚本,并返回执行结果。evalsha命令需要传入两个参数:第一个参数是脚本的SHA1摘要,第二个参数是一个可选的参数列表,用于传递参数给脚本。evalsha命令可以提高脚本的执行效率,因为它避免了每次执行时都需要传递脚本字符串的开销。
使用script exists命令可以检查指定的脚本是否已经被服务器端缓存。如果指定的脚本已经被缓存,则返回1,否则返回0。
使用script load命令可以将指定的脚本缓存到服务器端,并返回脚本的SHA1摘要。缓存脚本可以提高脚本的执行效率,因为它避免了每次执行时都需要传递脚本字符串的开销。
使用script flush命令可以清空服务器端缓存的所有Lua脚本。
使用script kill命令可以中断正在执行的Lua脚本。
总的来说,Redis脚本可以用于执行复杂的数据处理、计算或者业务逻辑,也可以用于实现原子性的操作或者复杂的事务。使用Lua脚本可以将多个操作打包成一个原子性操作,从而提高数据的安全性和可靠性。
# 1、分布式锁的示例
定义一段脚本:
-- 获得锁的值
local lockValue = redis.call('get', KEYS[1])
-- 如果锁没有被占用,则获取锁
if not lockValue then
redis.call('set', KEYS[1], ARGV[1])
redis.call('expire', KEYS[1], ARGV[2])
return true
end
-- 如果锁被当前线程占用,则更新锁的过期时间
if lockValue == ARGV[1] then
redis.call('expire', KEYS[1], ARGV[2])
return true
end
-- 如果锁被其他线程占用,则返回false
return false
在这个脚本中,我们使用Redis提供的get
命令获取锁的值,如果锁没有被占用,则获取锁。如果锁被当前线程占用,则更新锁的过期时间。如果锁被其他线程占用,则返回false。
在执行这个脚本时,我们需要传入三个参数:第一个参数是锁的键名,第二个参数是要获取锁的值,第三个参数是锁的过期时间。
使用Java语言来执行这个脚本:
Jedis jedis = new Jedis("localhost");
String lockKey = "mylock";
String lockValue = UUID.randomUUID().toString();
int expireTime = 300;
String script = "local lockValue = redis.call('get', KEYS[1]); " +
"if not lockValue then " +
" redis.call('set', KEYS[1], ARGV[1]); " +
" redis.call('expire', KEYS[1], ARGV[2]); " +
" return true; " +
"end; " +
"if lockValue == ARGV[1] then " +
" redis.call('expire', KEYS[1], ARGV[2]); " +
" return true; " +
"end; " +
"return false;";
List<String> keys = new ArrayList<>();
keys.add(lockKey);
List<String> args = new ArrayList<>();
args.add(lockValue);
args.add(String.valueOf(expireTime));
Object result = jedis.eval(script, keys, args);
if ((boolean) result) {
System.out.println("获取锁成功!");
} else {
System.out.println("获取锁失败!");
}
在这个Java程序中,我们使用Jedis连接Redis服务器,并执行Lua脚本。在执行脚本时,我们需要传入三个参数:锁的键名、要获取锁的值以及锁的过期时间。如果脚本执行成功,则表示获取锁成功;否则表示获取锁失败。
当然,这个分布式锁还比较简陋,自己去实现也容易出错。推荐使用Redisson客服端来实现分布式锁。
# 十二、扩展模块
Redis 扩展模块可以为 Redis 提供额外的功能和数据结构。以下是一些常用且有用的 Redis 扩展模块:
Redisearch:一个全文搜索引擎模块,允许您在 Redis 中进行复杂的全文搜索,支持模糊匹配、数值过滤和地理位置查询。它还提供了索引维护和实时查询的功能。
官方网站:https://redis.io/docs/stack/search/ (opens new window)RedisGraph:一个基于图数据库的 Redis 模块,支持基于图形的数据查询和操作。它使用图结构来存储数据,并支持 Cypher 查询语言进行高效的图查询。
官方网站:https://redis.io/docs/stack/graph/ (opens new window)RedisBloom:一个提供概率数据结构的 Redis 模块,包括布隆过滤器、计数布隆过滤器、顶点草图和空间草图。这些数据结构可以用来实现内存高效的去重、统计和近似查询等功能。
官方网站:https://redis.io/docs/stack/bloom/ (opens new window)RedisJSON:一个提供原生 JSON 数据类型支持的 Redis 模块,允许在 Redis 中存储、更新和查询 JSON 数据。它支持 JSONPath 和 JSON API,使得操作 JSON 数据变得更加简便。
官方网站:https://redis.io/docs/stack/json/ (opens new window)
这些扩展模块为 Redis 提供了更多高级功能,可以帮助您更好地满足特定需求。请注意,这些模块需要单独安装并在 Redis 配置中启用。
# 十三、客户端使用
Redisson 是一个功能丰富的 Redis 客户端,提供了多种分布式数据结构和服务,如分布式锁、限流等。
以下是抛砖引玉的介绍,更多内容请到官网查看 (opens new window):
# 1、分布式锁
Redisson提供了一种分布式锁实现,它基于Redis的原子性和过期时间等特性来实现分布式锁。
# 1.1、获取锁
在Redisson中,可以通过以下方式获取锁:
RLock lock = redissonClient.getLock("myLock");
lock.lock();
try {
// 执行业务逻辑
} finally {
lock.unlock();
}
其中,redissonClient
是Redisson的客户端实例,myLock
是要获取的锁名字,lock()
方法是获取锁操作,unlock()
方法是释放锁操作。当lock()
方法执行成功后,说明当前线程获取到了分布式锁,可以执行业务逻辑;当业务逻辑执行完成后,需要释放锁,否则其他线程无法获取该锁。
# 1.2、指定过期时间
可以通过lock()
方法的重载形式指定锁的过期时间,如下:
RLock lock = redissonClient.getLock("myLock");
lock.lock(10, TimeUnit.SECONDS);
try {
// 执行业务逻辑
} finally {
lock.unlock();
}
其中,第一个参数是过期时间,第二个参数是时间单位,表示在指定时间内没有释放锁,锁会自动失效。这种方式可以防止锁被永久占用,提高系统的可用性。
# 1.3、尝试获取锁
在某些情况下,需要在不阻塞的情况下尝试获取锁。可以使用tryLock()
方法,如下:
RLock lock = redissonClient.getLock("myLock");
if (lock.tryLock()) {
try {
// 执行业务逻辑
} finally {
lock.unlock();
}
} else {
// 获取锁失败,可以进行其他操作
}
其中,tryLock()
方法会立即返回,如果获取锁成功,则可以执行业务逻辑;如果获取锁失败,则可以进行其他操作,如等待一段时间后再尝试获取锁。
# 1.4、公平锁和非公平锁
Redisson支持公平锁和非公平锁两种锁类型,默认情况下是非公平锁。可以通过以下方式指定锁类型:
// 非公平锁
RLock lock1 = redissonClient.getLock("myLock");
// 公平锁
RLock lock2 = redissonClient.getFairLock("myLock");
其中,getFairLock()
方法返回一个公平锁实例。
# a、补充:公平锁与不公平锁
公平锁和不公平锁是相对于锁的获取机制而言的。
在一个非公平锁的实现中,线程在尝试获取锁时,会直接去争抢锁,而不管其他线程是否在等待锁。这意味着,某些线程可能会一直被饿死,无法获取到锁。这种实现方式的优点是速度快,缺点是可能会导致线程饥饿。
相对地,公平锁会保证锁的获取顺序与线程的等待顺序一致。也就是说,等待时间最久的线程会最先获取到锁。这种实现方式的优点是公平,缺点是速度较慢,因为线程需要排队等待锁的释放。
需要注意的是,公平锁只能保证锁的获取顺序是公平的,但不能保证执行顺序也是公平的。也就是说,等待时间最久的线程获取到锁后,可能会因为执行逻辑的复杂度等因素而导致执行时间较长,而其他线程则需要等待较长的时间才能获取锁。
# 1.5、多重锁
在一些情况下,需要获取多个锁。可以使用RedissonMultiLock
类来实现,如下:
RLock lock1 = redissonClient.getLock("lock1");
RLock lock2 = redissonClient.getLock("lock2");
RLock lock3 = redissonClient.getLock("lock3");
RedissonMultiLock multiLock = new RedissonMultiLock(lock1, lock2, lock3);
multiLock.lock();
try {
// 执行业务逻辑
} finally {
multiLock.unlock();
}
其中,RedissonMultiLock
类是一个用于同时获取多个锁的工具类,使用方式与单个锁类似,只需要将多个锁传入构造函数即可。在使用完毕后,需要调用unlock()
方法释放所有锁。
# 2、限流
Redisson提供了基于Redis的分布式限流功能,可以实现对请求的流量和访问次数进行限制。
# 2.1、限制流量
Redisson提供了基于漏桶算法的流量限制功能,可以限制单位时间内的请求次数和流量大小。可以通过以下方式实现:
// 每秒钟最多处理10个请求
RPermitExpirableSemaphore semaphore = redissonClient.getPermitExpirableSemaphore("mySemaphore");
semaphore.trySetPermits(10);
semaphore.acquire(1, TimeUnit.SECONDS);
try {
// 执行业务逻辑
} finally {
semaphore.release();
}
其中,mySemaphore
是信号量的名称,trySetPermits()
方法设置了最大处理请求数,acquire()
方法获取信号量,release()
方法释放信号量。如果在1秒钟内超过10个请求,则后续请求会被阻塞,直到当前请求处理完成并释放了信号量。
# 2.2、限制访问次数
Redisson提供了基于计数器的访问次数限制功能,可以限制某个URL或API的访问次数。可以通过以下方式实现:
// 每个用户最多访问10次
RMapCache<String, Integer> mapCache = redissonClient.getMapCache("myMapCache");
String userId = "123456";
int count = mapCache.getOrDefault(userId, 0);
if (count < 10) {
mapCache.put(userId, count + 1, 1, TimeUnit.MINUTES);
// 执行业务逻辑
} else {
// 返回访问次数过多的错误信息
}
其中,myMapCache
是分布式缓存的名称,getOrDefault()
方法获取指定键的值,put()
方法存储键值对,并设置过期时间。如果一个用户在1分钟内访问超过10次,则返回访问次数过多的错误信息。
需要注意的是,以上示例仅仅是演示Redisson提供的限流功能,并不能完全防止恶意攻击。如果需要更严格的限流控制,可以考虑结合其他技术手段,如CDN、WAF、防火墙等,以确保系统的安全性和可靠性。
# 十四、作为缓存的实践
在实际应用中,Redis 常被用作缓存层,用于存储热点数据以提高系统性能。以下是一些缓存实践的规范和异常处理:
# 1、缓存规范
请参考:代码质量管理之规范篇
# 2、数据预热
数据预热是指在系统启动或低峰期,提前将热点数据加载到缓存中,以提高缓存命中率。可以使用定时任务或者异步线程池等方式,定期扫描数据库中的热点数据,并将其加载到缓存中。
在实际应用中,还可以根据用户的行为和偏好,动态调整热点数据的预热策略,以更好地满足用户的需求。
# 3、缓存异常处理
# 3.1、缓存穿透
缓存穿透是指查询不存在的数据,导致请求直接到达数据库层。可以采用布隆过滤器或设置空值缓存策略来解决。
# 3.2、缓存击穿
缓存击穿是指某个热点 key 过期后,大量请求同时到达数据库层。可以采用分布式锁或者提前更新缓存策略来解决。
# 3.3、缓存雪崩
缓存雪崩是指大量的 key 同时过期,导致大量请求直接到达数据库层。可以采用设置随机过期时间或者使用分布式缓存来解决。
# 十五、总结
本文比较全面的介绍了 Redis 的各方面的使用。
希望这篇文章能帮助您深入了解 Redis 并在实际应用中更好地发挥其优势。
祝你变得更强!