redis 常见问题二

2021-07-19  本文已影响0人  零一间

redis 分布式锁

在分布式系统环境下,一个方法在同一时间只能被一个机器的一个线程执行; 高可用的获取锁与释放锁; 高性能的获取锁与释放锁; 具备可重入特性; 具备锁失效机制,防止死锁; 具备非阻塞锁特性,即没有获取到锁将直接返回获取锁失败。

分布式锁的特性是排他性、避免死锁、高可用。

分布式锁的实现可以通过数据库的乐观锁(通过版本号)或者悲观锁(通过for update)、Redis的setnx()命令、Zookeeper,etcd等。

官方权威的用Redis实现分布式锁管理器的算法,我们把这个算法称为RedLock。

redis分布式锁常用一下命令:

设置锁

SET {分布式锁key} {分布式锁值} NX PX {过期时间}

注意:

解锁

匹配随机值,业务结束,删除key对应的数据。

注意:

Redis 服务器会单线程原子性执行 lua 脚本,保证 lua 脚本在处理的过程中不会被任意其它请求打断。

if redis.call("get",KEYS[1]) == ARGV[1] then
    return redis.call("del",KEYS[1])
else
    return 0
end

编程语言常用eval函数/方法执行。

可重入式锁

也叫做递归锁,指的是在同一线程内,外层函数获得锁之后,内层递归函数仍然可以获取到该锁。
例如:一个方法获取到锁之后,可能在方法内调这个方法此时就获取不到锁了。

缓存雪崩、缓存击穿、缓存穿透

缓存穿透

缓存穿透是指不断的请求缓存中不存在的数据, 则所有请求全部落在了数据库层, 高并发的情况下, 就直接影响了整个系统的业务, 甚至可能导致系统崩溃

解决

缓存雪崩

缓存雪崩是指缓存中数据大批量同一时间过期或者redis服务挂了,而查询数据量巨大,引起数据库压力过大。

解决

1.业务分级缓存,可以在做数据缓存的时候, 按分类进行缓存, 添加不同的缓存时间
2.缓存的同时, 对缓存时间加上一个随机数, 以至于不会让所有缓存同一时间大量失效
3.对于redis服务挂掉的问题,可以实现redis的高可用主从架构, 并且做redis的持久化, 在redis挂掉的同时时读取本地缓存数据, 同时恢复redis服务加载持久化的数据

缓存击穿

概念

缓存击穿和缓存雪崩有点像, 不过不是大面积的缓存失效,热点key。
缓存击穿指的是缓存中某一个key的值不断的接收着大量的请求, 而在这个key值失效的瞬间, 大量的请求落在了数据库上, 从而导致数据库可能压力过大

解决

1、加分布式锁或者分布式队列

用加分布式锁或者分布式队列的方式保证缓存的单线程写,从而避免失效时大量的并发请求落到底层存储系统上。在加锁方法内先从缓存中再获取一次(防止另外的线程优先获取锁已经写入了缓存),没有再查DB写入缓存。 (当然也可以: 在没有获取锁的线程中一直轮询缓存,至超时)

2、添加超时标记

在缓存的对象上增加一个属性来标识超时时间,当获取到数据后,校验数据内部的标记时间,判定是否快超时了,如果是,异步发起一个线程(控制好并发)去主动更新该缓存。

3、另外还有一个粗暴的方法,如果你的热点数据要求实时性比较低,那么可以设置热点数据在热点时段不过期,在访问低峰期过期,比如每天凌晨过期。

4、冷热数据分离。

Redis是单线程的,但Redis为什么这么快?

Redis内存划分

作为数据库,数据是最主要的部分;这部分占用的内存会统计在used_memory中。

Redis主进程本身运行肯定需要占用内存,如代码、常量池等等;这部分内存大约几兆,在大多数生产环境中与Redis数据占用的内存相比可以忽略。这部分内存不是由jemalloc分配,因此不会统计在used_memory中。

缓冲内存包括客户端缓冲区、复制积压缓冲区、AOF缓冲区等;其中,客户端缓冲存储客户端连接的输入输出缓冲;复制积压缓冲用于部分复制功能;AOF缓冲区用于在进行AOF重写时,保存最近的写入命令。在了解相应功能之前,不需要知道这些缓冲的细节;这部分内存由jemalloc分配,因此会统计在used_memory中。

内存碎片是Redis在分配、回收物理内存过程中产生的。例如,如果对数据的更改频繁,而且数据之间的大小相差很大,可能导致redis释放的空间在物理内存中并没有释放,但redis又无法有效利用,这就形成了内存碎片。内存碎片不会统计在used_memory中。

Redis还提供的高级工具

像慢查询分析、性能测试、Pipeline、事务、Lua自定义命令、Bitmaps、HyperLogLog、发布/订阅、Geo等个性化功能。

Redis缓存和数据库间数据一致性问题

高并发的业务场景下,经常使用redis做一个缓冲操作,先读取缓存,如果缓存不存在,则读取数据库。

获取数据不会存在太大问题,但是写入的时候,就可能出现数据不一致的情况。因为写和读是并发的,没法保证顺序,就会出现缓存和数据库的数据不一致的问题。

第一种方案:采用延时双删策略

在写库前后都进行redis.del(key)操作,并且设定合理的超时时间。

具体的步骤就是

先删除缓存,再写数据库,休眠500毫秒,再次删除缓存。

具体该休眠多久,需要评估自己的项目的读数据业务逻辑的耗时。确保读请求结束,写请求可以删除读请求造成的缓存脏数据。

当然这种策略还要考虑redis和数据库主从同步的耗时。最后的的写数据的休眠时间:则在读数据业务逻辑的耗时基础上,加几百ms即可。比如:休眠1秒。

设置缓存过期时间

从理论上来说,给缓存设置过期时间,是保证最终一致性的解决方案。所有的写操作以数据库为准,只要到达缓存过期时间,则后面的读请求自然会从数据库中读取新值然后回填缓存。

该方案的弊端

结合双删策略+缓存超时设置,这样最差的情况就是在超时时间内数据存在不一致,而且又增加了写请求的耗时。

第二种方案:异步更新缓存(基于订阅binlog的同步机制)

技术整体思路:

MySQL binlog增量订阅消费+消息队列+增量数据更新到redis

增量,指的是mysql的update、insert、delate变更数据。

Redis更新

一个是全量(将全部数据一次写入到redis)
一个是增量(实时更新)

一旦MySQL中产生了新的写入、更新、删除等操作,就可以把解析的binlog相关的消息推送至Redis,Redis再根据binlog中的记录,对Redis进行更新。类似MySQL的主从备份机制。

例如:
canal是阿里巴巴旗下的一款开源项目,MySQL binlog 增量订阅&消费组件,目前主要支持了MySQL(也支持mariaDB)。

redis通讯协议

Redis客户端和服务端之间使用一种名为RESP(REdis Serialization Protocol)的二进制安全文本协议进行通信。RESP设计的十分精巧,下面是一张完备的协议描述图:

img

Redis中海量数据操作注意事项

Redis是单线程服务,所有指令都是顺序执行,当某一指令耗时很长时,就会阻塞后续的指令执行。例如大key,大value,keys操作等。

禁用/重命名危险命令

例如:
keys会在大量key遍历时,比较耗时,会阻塞后续的指令执行,严重时导致Redis实例崩溃甚至服务器宕机。利用SCAN系列命令完成数据迭代。同时禁用keys命令。

# 禁用key
rename-command FLUSHALL ""
rename-command FLUSHDB  ""
rename-command CONFIG   ""
rename-command KEYS     "" 

大key

大key一般是指value值大别大的key。

处理方案:

暂时想这么多。

redis安全

禁止一些高危命令

rename-command FLUSHALL ""
rename-command FLUSHDB  ""
rename-command CONFIG   ""
rename-command KEYS     "" 

禁止root用户启动redis

为 Redis 服务创建单独的用户和家目录,并且配置禁止登陆。

添加密码验证,同时配置文件权限修改。

redis效率高,简单的密码容易为攻击者暴破。密码要设置复杂。

requirepass mypassword
chmod 600 /etc/redis/redis.conf  #redis配置文件

禁止外网访问 Redis

bind 127.0.0.1

修改默认6379端口

port 6666

默认端口为6379。

内网运行,尽量避免有公网访问

对于各种内网代理要进行风险评估,不要随意搭建。防止攻击。

监控redis状态

Redis 中设置过期时间主要通过以下四种方式

EXPIRE <KEY> <TTL> : 将键的生存时间设为 ttl 秒
PEXPIRE <KEY> <TTL> :将键的生存时间设为 ttl 毫秒
EXPIREAT <KEY> <timestamp> :将键的过期时间设为 timestamp 所指定的秒数时间戳
PEXPIREAT <KEY> <timestamp>: 将键的过期时间设为 timestamp 所指定的毫秒数时间戳.

Redis 过期删除策略

定期删除

redis会把设置了过期时间的key放在单独的字典中,每隔一段时间执行一次删除(在redis.conf配置文件设置hz,1s刷新的频率)过期key的操作。

由于算法采用的随机取key判断是否过期的方式,故几乎不可能清理完所有的过期Key;调高hz参数可以提升清理的频率,过期key可以更及时的被删除,但hz太高会增加CPU时间的消耗,为了保证不会循环过度,导致卡顿,扫描时间上限默认不超过25ms。

系统中应避免大量的key同时过期,给要过期的key设置一个随机范围。

惰性删除

过期的key并不一定会马上删除,还会占用着内存。 当你真正查询这个key时,redis会检查一下,这个设置了过期时间的key是否过期了? 如果过期了就会删除,返回空。

Redis采用的过期策略

惰性删除+定期删除

Redis 内存淘汰策略

noeviction:当内存使用超过配置的时候会返回错误,不会驱逐任何键(默认策略,不建议使用)
allkeys-lru:加入键的时候,如果过限,首先通过LRU算法驱逐最久没有使用的键
volatile-lru:加入键的时候如果过限,首先从设置了过期时间的键集合中驱逐最久没有使用的键(不建议使用)
allkeys-random:加入键的时候如果过限,从所有key随机删除
volatile-random:加入键的时候如果过限,从过期键的集合中随机驱逐(不建议使用)
volatile-ttl:从配置了过期时间的键中驱逐马上就要过期的键
volatile-lfu:从所有配置了过期时间的键中驱逐使用频率最少的键
allkeys-lfu:从所有键中驱逐使用频率最少的键

参考:

Redis - 缓存雪崩、缓存击穿、缓存穿透
https://segmentfault.com/a/1190000022349593

Redis分布式锁
http://www.redis.cn/topics/distlock.html

高并发架构系列:Redis缓存和MySQL数据一致性方案详解
https://www.jianshu.com/p/b28fb9d5acb7

Redis 键的过期删除策略及缓存淘汰策略
https://www.cnblogs.com/rinack/p/11549362.html

上一篇 下一篇

猜你喜欢

热点阅读