作者:陈hancox_894 | 来源:互联网 | 2022-03-01 07:20
本篇文章给大家分享使用Redis必须知道的21个注意要点。有一定的参考价值,有需要的朋友可以参考一下,希望对大家有所帮助。
反例:
set user:666:name jay
set user:666:age 18
正例
hmset user:666 name jay age 18
1.3. 给Key设置过期时间,同时注意不同业务的key,尽量过期时间分散一点
- 因为Redis的数据是存在内存中的,而内存资源是很宝贵的。
- 我们一般是把Redis当做缓存来用,而不是数据库,所以key的生命周期就不宜太长久啦。
- 因此,你的key,一般建议用expire设置过期时间。
如果大量的key在某个时间点集中过期,到过期的那个时间点,Redis可能会存在卡顿,甚至出现缓存雪崩现象,因此一般不同业务的key,过期时间应该分散一些。有时候,同业务的,也可以在时间上加一个随机值,让过期时间分散一些。
1.4.建议使用批量操作提高效率
我们日常写SQL的时候,都知道,批量操作效率会更高,一次更新50条,比循环50次,每次更新一条效率更高。其实Redis操作命令也是这个道理。
Redis客户端执行一次命令可分为4个过程:1.发送命令-> 2.命令排队-> 3.命令执行-> 4. 返回结果。1和4 称为RRT(命令执行往返时间)。 Redis提供了批量操作命令,如mget、mset等,可有效节约RRT。但是呢,大部分的命令,是不支持批量操作的,比如hgetall,并没有mhgetall存在。Pipeline 则可以解决这个问题。
Pipeline是什么呢?它能将一组Redis命令进行组装,通过一次RTT传输给Redis,再将这组Redis命令的执行结果按顺序返回给客户端.
我们先来看下没有使用Pipeline执行了n条命令的模型:
2.3、生产环境不能使用 keys指令
Redis Keys 命令用于查找所有符合给定模式pattern的key。如果想查看Redis 某类型的key有多少个,不少小伙伴想到用keys命令,如下:
keys key前缀*
但是,redis的keys
是遍历匹配的,复杂度是O(n)
,数据库数据越多就越慢。我们知道,redis是单线程的,如果数据比较多的话,keys指令就会导致redis线程阻塞,线上服务也会停顿了,直到指令执行完,服务才会恢复。因此,一般在生产环境,不要使用keys指令。官方文档也有声明:
Warning: consider KEYS as a command that should only be used in production environments with extreme care. It may ruin performance when it is executed against large databases. This command is intended for debugging and special operations, such as changing your keyspace layout. Don't use KEYS in your regular application code. If you're looking for a way to find keys in a subset of your keyspace, consider using sets.
其实,可以使用scan指令,它同keys命令一样提供模式匹配功能。它的复杂度也是 O(n),但是它通过游标分步进行,不会阻塞redis线程;但是会有一定的重复概率,需要在客户端做一次去重。
scan支持增量式迭代命令,增量式迭代命令也是有缺点的:举个例子, 使用 SMEMBERS 命令可以返回集合键当前包含的所有元素, 但是对于 SCAN 这类增量式迭代命令来说, 因为在对键进行增量式迭代的过程中, 键可能会被修改, 所以增量式迭代命令只能对被返回的元素提供有限的保证 。
2.4 禁止使用flushall、flushdb
- Flushall 命令用于清空整个 Redis 服务器的数据(删除所有数据库的所有 key )。
- Flushdb 命令用于清空当前数据库中的所有 key。
这两命令是原子性的,不会终止执行。一旦开始执行,不会执行失败的。
2.5 注意使用del命令
删除key你一般使用什么命令?是直接del?如果删除一个key,直接使用del命令当然没问题。但是,你想过del的时间复杂度是多少嘛?我们分情况探讨一下:
- 如果删除一个String类型的key,时间复杂度就是
O(1)
,可以直接del。 - 如果删除一个List/Hash/Set/ZSet类型时,它的复杂度是
O(n)
, n表示元素个数。
因此,如果你删除一个List/Hash/Set/ZSet类型的key时,元素越多,就越慢。当n很大时,要尤其注意,会阻塞主线程的。那么,如果不用del,我们应该怎么删除呢?
- 如果是List类型,你可以执行
lpop或者rpop
,直到所有元素删除完成。 - 如果是Hash/Set/ZSet类型,你可以先执行
hscan/sscan/scan
查询,再执行hdel/srem/zrem
依次删除每个元素。
2.6 避免使用SORT、SINTER等复杂度过高的命令。
执行复杂度较高的命令,会消耗更多的 CPU 资源,会阻塞主线程。所以你要避免执行如SORT、SINTER、SINTERSTORE、ZUNIONSTORE、ZINTERSTORE
等聚合命令,一般建议把它放到客户端来执行。
3、项目实战避坑操作
3.1 分布式锁使用的注意点
分布式锁其实就是,控制分布式系统不同进程共同访问共享资源的一种锁的实现。秒杀下单、抢红包等等业务场景,都需要用到分布式锁。我们经常使用Redis作为分布式锁,主要有这些注意点:
3.1.1 两个命令SETNX + EXPIRE分开写(典型错误实现范例)
if(jedis.setnx(key_resource_id,lock_value) == 1){ //加锁
expire(key_resource_id,100); //设置过期时间
try {
do something //业务请求
}catch(){
}
finally {
jedis.del(key_resource_id); //释放锁
}
}
如果执行完setnx
加锁,正要执行expire设置过期时间时,进程crash或者要重启维护了,那么这个锁就“长生不老”了,别的线程永远获取不到锁啦,所以一般分布式锁不能这么实现。
3.1.2 SETNX + value值是过期时间 (有些小伙伴是这么实现,有坑)
long expires = System.currentTimeMillis() + expireTime; //系统时间+设置的过期时间
String expiresStr = String.valueOf(expires);
// 如果当前锁不存在,返回加锁成功
if (jedis.setnx(key_resource_id, expiresStr) == 1) {
return true;
}
// 如果锁已经存在,获取锁的过期时间
String currentValueStr = jedis.get(key_resource_id);
// 如果获取到的过期时间,小于系统当前时间,表示已经过期
if (currentValueStr != null && Long.parseLong(currentValueStr) 这种方案的缺点:
- 过期时间是客户端自己生成的,分布式环境下,每个客户端的时间必须同步
- 没有保存持有者的唯一标识,可能被别的客户端释放/解锁。
- 锁过期的时候,并发多个客户端同时请求过来,都执行了
jedis.getSet()
,最终只能有一个客户端加锁成功,但是该客户端锁的过期时间,可能被别的客户端覆盖。
3.1.3: SET的扩展命令(SET EX PX NX)(注意可能存在的问题)
if(jedis.set(key_resource_id, lock_value, "NX", "EX", 100s) == 1){ //加锁
try {
do something //业务处理
}catch(){
}
finally {
jedis.del(key_resource_id); //释放锁
}
}
这个方案还是可能存在问题:
3.1.4 SET EX PX NX + 校验唯一随机值,再删除(解决了误删问题,还是存在锁过期,业务没执行完的问题)
if(jedis.set(key_resource_id, uni_request_id, "NX", "EX", 100s) == 1){ //加锁
try {
do something //业务处理
}catch(){
}
finally {
//判断是不是当前线程加的锁,是才释放
if (uni_request_id.equals(jedis.get(key_resource_id))) {
jedis.del(lockKey); //释放锁
}
}
}
在这里,判断是不是当前线程加的锁和释放锁不是一个原子操作。如果调用jedis.del()释放锁的时候,可能这把锁已经不属于当前客户端,会解除他人加的锁。
一般也是用lua脚本代替。lua脚本如下:
if redis.call('get',KEYS[1]) == ARGV[1] then
return redis.call('del',KEYS[1])
else
return 0
end;
3.1.5 Redisson框架 + Redlock算法 解决锁过期释放,业务没执行完问题+单机问题
Redisson 使用了一个Watch dog
解决了锁过期释放,业务没执行完问题,Redisson原理图如下:
Redis setKey
源码如下:
void setKey(redisDb *db,robj *key,robj *val) {
if(lookupKeyWrite(db,key)==NULL) {
dbAdd(db,key,val);
}else{
dbOverwrite(db,key,val);
}
incrRefCount(val);
removeExpire(db,key); //去掉过期时间
signalModifiedKey(db,key);
}
实际业务开发中,同时我们要合理评估Redis的容量,避免频繁set覆盖,导致设置了过期时间的key失效。新手小白容易犯这个错误。
3.4 缓存穿透问题
先来看一个常见的缓存使用方式:读请求来了,先查下缓存,缓存有值命中,就直接返回;缓存没命中,就去查数据库,然后把数据库的值更新到缓存,再返回。
更多编程相关知识,请访问:编程视频!!
以上就是21个使用Redis时必须注意的要点(总结)的详细内容,更多请关注其它相关文章!