最近项目重启的时候,redis出现了一个错误:Failed opening the RDB file crontab (in server root dir /etc) for saving:Permission denied的问题,导致redis缓存中的数据全部丢失了,项目中查询redis缓存数据全部失效,这里先说出来解决方法是给redis设置密码即可解决。
主要是对报错信息比较感兴趣, RDB file crontab这个是什么文件呢,root dir /etc这个dir又是做什么的呢,为什么会提示没有权限呢? 经典的夺命三连,哈哈哈,现在我们就一起来解决这些疑问。
首先第一个问题主要涉及到redis日志文件
随着redis的广泛流行,基本上redis在项目中用来存储热点数据是大家都比较钟爱的选择,而redis的快是它的最大优势,而这个快就体现在redis的数据在存储在内存中的,不需要经过磁盘io,从而节省很多磁盘寻址的时间,提高数据访问的速度,但是在内存中的数据始终容易丢失,服务重启或者某些不可控因素都会使得redis数据丢失,所以redis的数据的持久化是很值得我们去了解和学习的,保证线上数据能够得到持久化的访问。
在redis数据的持久化中,用到的和mysql一样的日志逻辑,通过将每一次对redis的操作用日志记录下来,写入磁盘,这样,当服务器重启时候,会从磁盘中读取redis的日志文件,从而来恢复redis中的热点数据。
redis的备份方式主要有两种,AOF日志和RDB快照,我们来看看两者的异同
AOF日志
我们知道mysql的日志文件是写前日志,在实际写入数据之前,先把对应的操作写到日志,而redis则是写后日志,先把数据写到内存中,然后再把数据写到日志文件中,我们知道mysql的redolog日志存储的是物理修改语句,比喻什么字段修改成了xx值(这里后面会详细介绍一下mysql的日志,比如 undolog,redolog,binlog),而AOF日志存储的是每次redis接收到的名宁语句,比如 set xx xx,这里有一个非常重要的一点,就是AOF日志为了避免检查的开销,不会对写入的命令进行检查,如果是写前日志,那么在宕机恢复的时候,可能会读到错误的命令,所以AOF写后日志的好处就是可以保证写到日志的命令都是正确的,这样在数据恢复的时候不会出错。而且redis是在写操作之后才写日志,这样也不会阻塞当前的写操作。
但是AOF日志也有两个潜在风险。
第一,如果服务器在redis写入内存后就宕机了,数据此时是没有写到日志的,那么在服务重启,数据恢复的时候,可能会造成数据的丢失。
第二,之前说了,写后日志不会阻塞当前写操作,但是会阻塞后续的操作,应为写入日志的线程也是在主线程之中的,如果磁盘io的压力过大,就会阻塞后续的redis操作。
上面两个情况都是和磁盘写入的时机相关的,我们来看看redis关于这方面的设计方式
- Alway(同步写回):每个写命令执行完,就会把相应命令写到磁盘。
- Everysec(每秒写回):每个写命令执行完,会把当前的日志写到内存缓冲区,然后每隔一秒钟将数据将数据写到磁盘。
- NO(系统写回):每个写命令执行完,会把当前的日志写到内存缓冲区,由系统来决定合适将数据写到磁盘。
通过上面三种方式,我们知道都不能把保证数据不丢失,可以根据项目中的时间情况来选择使用哪一种方式。
其实不知道大家有没有想到一个问题,随着AOF日志文件越来越大,日志写入磁盘的效率也会降低,同时在恢复数据的时候,过大的AOF日志会导致数据恢复的时间线拉长,所以AOF日志有一个重写机制。
重写机制是指把多个redis命令整合成一个命令来减少AOF日志文件的大小,那么这个整合又是什么意思呢,因为AOF日志是采用追加的方式,对于一行数据,可能经历过了多次的修改,那么在日志文件中就会有多个命令,但其实只需要记住当前最后一条命令即可,所以重写机制就是读取当前redis的所有数据,然后用set命令对每条数据进行记录,用新的AOF日志来替换旧的AOF日志,这样就减少了AOF日志文件的大小。
那么AOF日志重写会阻塞主线成么?
其实是不会的,和写入AOF日志在主线程进行不同,重写机制是redis专门fork一个子线程来进行的,所以不会阻塞主线程,而且在重写的同时,新的AOF日志会由主线程卸载缓冲区,不会造成数据日志的丢失。
RDB快照
相比于AOF日志存储的是redis的操作命令,当AOF文件过大时会造成数据恢复的时间线拉长,而RDB快照则是记录redis某一时刻的数据,并将其写入磁盘,这样在宕机数据恢复时,只需要把RDB文件读入内存即可。
这里快照的生成又会产生很多问题,比什么时候去产生快照呢,产生快照的时候是否会阻塞主线程呢,同时在产生快照的时候,修改的数据又该怎么操作呢?
产生快照的频率我们很难把握,确实,快照频率越高,那么我们丢失数据的风险是最小的,但是产生快照是需要时间的。
在redis中提供两个命令来生成RDB文件
save:在主线程中执行,会造成主线程阻塞
bgsave:专门创建一个子线程来生成RDB快照,不会阻塞主线程,这也是redis默认生成快照的方式。
有人可能会说,那我用bgsave来生成快照不就好了,这样不会阻塞主线程而且数据丢失的风险也不高,这里确实bgsave子线程不会阻塞主线程,但是这个线程是主线程fork的,频繁的fork子线程也是会对主线程有影响的。
所以为了防止在生成RDB快照的时候,不能对此时修改的数据进行维护,redis有一个写时复制的操作,就是在生成快照时,如果对其中的键值修改了,会创建这个键值的副本,这个时候写操作会继续进行,同时会把这个复制的副本写到RDB快照。
在redis4.0版本中,采用的是RDB快照和AOF日志混合使用的方式,采用一定频率来生成RDB快照,同时用AOF日志来记录在生成快照期间redis修改的数据。
这样的话,既可以享受用RDB快照来快速恢复数据,同时又可以通过AOF日志方式来保存修改的数据,而且快照的方式就可以不用AOF日志的重写日志,两者互补,这种策略是非常推荐的。
2.redis的配置.
关于 dir目录是用来做什么的,这个指的是本地数据库的存放目录,就是因为线上的redis将这个目录修改的,导致数据全部都读取不到。redis其余相关数据,网上都可以查到,这里就不做相应介绍了。
3.redis安全设置
一般redis安装完成之后,会使用默认的端口号6379,并且是没有设置密码,这样redis就会接收任何的请求来源,会遭到恶意的攻击。所以我们要把redis设置密码,文章最开始提示没有权限,如果你把 /etc 的权限完全开放的话,而且redis没有设置密码,那么可能会被人在文件下加入挖矿病毒。切记,redis暴漏在外网下,一定要设置密码,防止恶意攻击。
设置密码的方式:
docke-compose:
isafety-redis:
image: redis:latest
container_name: isafety-redis
restart: always
privileged: true
command: redis-server --requirepass zxm10***
ports:
- "6379:6379"
volumes:
- /etc/localtime:/etc/localtime:ro
- "${REDIS_DIR}/data:/data" # 挂载数据目录
- "${REDIS_DIR}/config/redis.conf:/etc/redis/redis.conf" # 挂载配置文件目录
docker直接启动方式:
docker run --name redis -p 6379:6379 redis-test --requirepass [yourpassword]
挂载配置文件,直接下修改 conf文件 requirepass [yourpassword] 然后重启即可
这次的问题主要扩展了一下redis数据持久化的设计方式,redis的配置文件中也有相关的设置,比如是否开启修改数据后立刻写入日志,数据日志的save的方式等等,小伙伴们可以自行查看一下配置文件的相关注释来了解一下啦。