将当前数据进行保存,快照形式,存储数据结果,存储格式简单,关注点在数据
命令:
127.0.0.1:6379>save
作用:手动执行一次保存操作,默会在Redis的安装路径的data目录下生成一个dump.rdb二进制文件
在Redis服务启动的时候,就会自动加载rdb文件,将数据恢复
当有四个客户端依次执行以上命令时,由于Redis单线程的特点,四条指令会被塞进队列中依次执行,但是save指令的执行会阻塞当前Redis服务器,直到当前rdb过程完成为止,因此有可能会造成长时间阻塞,线上环境不建议使用。
命令
127.0.0.1:6379>bgsave
Background saving started
作用:手动启动后台保存操作(会调用glibc的函数fork产生一个子进程,快照持久化完全交给子进程来处理,父进程继续处理客户端请求,子进程刚刚产生时,他和父进程共享内存里面的代码段和数据段),但不是立即执行
命令:
在配置文件中添加如下配置:
save second changes
作用:满足限定时间范围内(second)key的变化数量达到指定数量(changes)即进行持久化
范例:在10秒内300个key发生变化就会进行持久化
save 10 300
将数据的操作过程进行保存,日志形式,存储的是操作过程,存储格式复杂,关注点在数据的操作过程
当Redis服务端接收到set指令时,并不会直接写.aof文件,而是先存储在缓存区,等达到一定条件之后再写.aof文件
配置:
在配置文件中添加如下配置:
appendonly yes|no
作用:
是否开启AOF持久化功能,默认为不开启状态
配置:
在配置文件中添加如下配置:
appendfsync always|everysec|no
作用:
AOF写数据策略
其他配置:
appendfilename filename
修改AOF持久化文件名,建议配置为APPendonly-端口号.aof
dir
AOF持久化文件保存路径,与RDB持久化文件保持一致即可
随着命令不断写入AOF,文件会越来越大,为了解决这个问题,Redis引入了AOF重写机制压缩文件体积。AOF文件重写是将Redis进程内的数据转化为写命令同步到新AOF文件的过程。简单说就是对同一个数据的弱干条命令执行结果转化为最终结果数据对应的指令进行记录。
如下图中左边的指令,重写后会生成左边两条指令,但最终结果都是一样的
手动重写:
127.0.0.1:6379>bgrewriteaof
Background append only file rewriting started
自动重写:
在配置文件中添加如下配置:
auto-aof-rewrite-min-size(自动重写最小尺寸) size
auto-aof-rewrite-percentage(自动重写百分比) percentage
点击查看
Redis事务就是一个命令执行的队列,将一系列预定义命令包装成一个整体(一个队列),当执行时,一次性按照添加顺序依次执行,中间不会被打断或者干扰。
开启事务:
127.0.0.1:6379>multi
作用:设定事务的开启位置,此指令执行后,后续的所有指令均加入到事务队列中
执行事务:
127.0.0.1:6379>exec
作用:设定事务的结束位置,同时执行事务。与multi成对出现,成对使用
注意:加入事务的命令暂时进入到任务队列中,并没有立即执行,只有执行exec命令才开始执行
取消事务:
127.0.0.1:6379>discard
作用:终止当前事务的定义,发生在multi之后,exec之前
略
描述:比如一个不存在的命令test key
事务处理:如果事务队列的命令中存在语法错误,那么所有的指令都不执行
描述:比如对string类型的key执行incr操作
事务处理:能够正确执行的命令会执行,运行错误的命令不会被执行
注意:已经执行完毕的命令对应的数据不会自动回滚,需要程序员自己在代码中实现回滚
天猫双11热卖过程中,对已经售罄的商品追加不过4个业务员都有权限进行补货。补货的操作可能是一些列的操作,牵扯到多个连续操作,如何保证不会重复操作?
对key添加监视锁,在执行exec前如果key发生了变化,将终止事务执行
127.0.0.1:6379>watch key1 key2
取消对所有key的监视
127.0.0.1:6379>unwatch
天猫双11热卖过程中,对已经售罄的货物追加补货,且补货完成,客户购买热情高涨,3秒内将所有商品购买完毕,本次补货已经将库存清空,那么如何避免最后一件商品不被多人购买?【超卖问题】
使用setnx设置一个公共锁
127.0.0.1:6379>setnx lock-key value
作用:利用setnx命令的返回值特性,有值则返回设置失败,无值则返回设置成功。对于返回设置成功的,拥有控制权,进行下一步的具体业务操作,对于返回设置失败的,不具有控制权,排队或等待。
注意:操作完毕通过del lock-key操作释放锁
但是有个问题:如果逻辑执行到中间出现异常了,可能会导致del指令没有被调用,这样就会陷入死锁,锁永远等不到释放。于是我们在拿到锁之后,再给锁加上一个过期时间,比如5s,这样即使中间出现异常可以保证锁会在5s之后得到释放。
> setnx lock-num true
OK
> expire lock-num 5
do something...
>del lock-num
但是这样还有一个问题,如果在setnx和expire之间服务器进程突然挂掉了,就会导致expire得不到执行,也会造成死锁。这种问题的根源就在于setnx和expire是两条指令而不是原子指令。
为了解决这个问题,在Redis2.8版本中,作者加入了set指令的扩展参数,是的setnx和expire指令可以一起执行,彻底解决了分布式锁。
> set lock-num true ex 5 nx
OK
do something...
> del lock-num
上面这个指令就是setnx和expire组合在一起的原子指令,他就是分布式锁的奥义所在
Redis的分布式锁不能解决超时问题,如果在加锁和解锁之间的逻辑执行的时间太长,以至于超出了了锁的超时限制,就会出现问题,这时因为第一个线程持有的锁过期了,临界区的逻辑还没执行完,而同时第二个线程就提前获取了这把锁,导致临界区的代码不能得到严格的串行执行。
为了避免这个问题,Redis分布式锁不要用于较长时间的任务
有一个稍微安全一点的方案就是将set指令的value参数设置为一个随机数,释放锁时先匹配随机数是否一致,然后再删除key,这是为了确保当前线程持有的锁不会被其他线程释放,除非这个锁时因为过期了而被服务器自动释放的。
但是匹配value和删除key也不是一个原子操作,这就需要Lua脚本来处理了。
过期删除策略
内存淘汰LRU算法
在我们平时的开发过程中,会有一些bool型数据需要存取,比如用户一年的签到记录,签了是1,没签是0,要记录365天。如果使用普通的key/value,每个用户要记录365个,当用户上亿的时候,需要的存储空间是惊人的。
为了解决这个问题,Redis提供了位图数据结构,这样每天的签到记录值占据一个位,365天就是365个位,46个字节就可以完全放下。这就大大节约了存储空间。位图的最小单位是比特(bit),每个bit的取值只能是0或1。
获取指定key对应偏移量上的bit值
>getbit key offset
设置指定key对应偏移量上的bit值,value只能是1或0
>setbit key offset value
位图不是特殊的数据结构,他的内容其实就是个普通的字符串,也就是byte数组。
如果要统计网站上的PV,那非常好办,给每个网页分配一个独立的Redis计数器就可以了,把这个计数器的key后缀加上当天的日期,这样来一个请求,执行incrby指令一次,最终就可以统计出所有的PV数据。
但是UV不一样,他要去重,同一个用户一天之内的多访问请求只能计数一次。这就要求每个网页的请求都需要带上用户的ID,无论是登录用户还是非登录用户都需要一个唯一ID来标识。
你也许已经想到了一个简单的方案:那就是为每一个网页设置一个独立的set集合来存储所有当天访问过此页面的用户ID,当一个请求过来时,我们是用sadd将用户ID塞进去就可以了,通过scard可以取出这个集合的大小,这个数字就是这个网页的UV。但是访问量很大时,那就需要一个很大的set集合来统计,这就非常浪费空间。
HyperLogLog就可以解决这个问题,HyperLogLog提供不精确的去重计数方案,虽然不精确,但也不是非常离谱,标准误差是0.81%。
使用方法:
HyperLogLog提供了两个指令pfadd和pfcount,一个是增加计数,一个是获取计数。
> pfadd codehole user1
(integer) 1
> pfadd codehole user2
(integer) 1
> pfcount codehole
(integer) 2
HyperLogLog出了上面两个命令之外还提供了第三个命令:pfmerge,用于将多个pf计数值加在一起形成新的值。
HyperLogLog数据结构需要占据12KB的存储空间,所以不适合统计单个用户的相关逻辑,如果用户有上亿个,可以知道,所使用的的存储空间相比于set就是九牛一毛。
用于地理位置距离排序算法,如附近的人。
添加两个坐标点a和b并计算其距离
>geoadd geos 1 1 a
(integer) 1
>geoadd geos 2 2 b
(integer) 1
>geodist geos a b
"157270.0561"
互联网“三高”架构:
单机Redis的风险与问题:
解决办法:为了避免单机Redis服务器故障,准备多台服务,互相连通,将数据复制多个副本保存在不同的服务器上,连接在一起,并保证数据是同步的。即使有一台服务器宕机,其他服务器依然可以继续提供服务,实现Redis的高可用,同时实现数据冗余备份。
主从复制的作用:
主从复制大致可以分为3个阶段(下面有详细描述):
步骤1:设置master的地址和端口,保存master信息
步骤2:建立socket连接
步骤3:发送ping命令
步骤4:身份验证
步骤5:发送slave端口信息
至此,主从连接成!
搭建步骤:
在slave的配置文件中添加如下配置
slaveof 127.0.0.1 6379
再次启动slave即可连接完成
状态:
slave:info中保存了master的ip地址与端口
master:info中保存slave的ip地址与端口
总体:两者之间建立了socket连接
步骤1:slave请求同步数据
步骤2:master创建RDB同步数据
步骤3:slave恢复RDB同步数据
步骤4:slave请求部分同步数据(全量同步产生的数据,同步的是指令而不是数据)
步骤5:slave恢复部分同步数据(slave执行同步过来的增量数据的指令,恢复数据)
至此,数据同步工作完成
状态:
slave:具有master端全部数据
master:保存slave当前数据同步的位子
总体:master和slave在当前节点数据一致
1、如果master数据量巨大,数据同步阶段应该避免流量高峰,避免造成master阻塞,影响业务运行
2、复制缓冲区大小设置不合理,会导致数据溢出。如进行全量复制周期太长,进行部分复制时发现缓冲区数据已经存在丢失情况,必须进行第二次全量复制,致使slave陷入死循环状态。
3、master单机内存占用主机内存的比例不应过大,建议使用50%-70%,留下30%-50%的内存用于执行bgsave命令和创建复制缓冲区
1、为避免slave进行全量复制、部分复制时服务器响应阻塞或数据不同步,建议关闭此期间的对外服务
2、数据同步阶段,master发送给slave信息可以理解为master是slave的一个客户端,主动向slave发送命令
3、多个slave同时对master请求数据同步,master发送的RDB文件增多,会对带宽造成巨大冲击,所以要适量错峰
4、slave过多时,建议调整拓扑结构,由一主多从变为树状结构,中间节点既是master,也是slave。
命令传播阶段出现了断网现象:
部分复制的三个核心要素:
服务器的运行id(run id)【识别身份】
主服务器的复制积压缓冲区
主从服务器的复制偏移量
心跳检测
注意:当slave多数掉线或延迟过高,master为保障数据稳定性,将拒绝所有信息同步操作
1、伴随着系统的运行,master的数据量越来越大,一旦master重启,runid将会发生变化,会导致全部slave的全量复制操作。解决办法:内部调整
2、频繁的全量复制。解决办法:修改缓冲区的大小
3、多个slave之间数据不同步。解决办法:优化主从网络环境,通常部署在同一个机房、监控主从节点延迟,如果slave延迟过大,暂时屏蔽程序对该slave的数据访问。
哨兵(sentinel)是一个分布式系统,用于对主从结构中的每台服务器进行监控,当出现故障时通过投票机制选择新的master并将所有slave连接到新的master。
哨兵的作用:
注意:哨兵也是一台Redis服务器,只是不提供数据服务,通常哨兵配置数量为单数
配置哨兵:
步骤1:配置一拖二的主从结构
步骤2:配置三个哨兵(配置相同,端口不同),参看sentinel.conf
步骤3:启动哨兵
redis-sentinel sentinel-端口号.conf
启动顺序:先启动主机,再启动从机,最后启动哨兵
哨兵在进行主从切换的过程中经历三个阶段
总结:sentinel会向master要状态,向slave要状态,向其他sentinel要状态,最后sentinel之间相互同步信息
过程描述:某个sentinel向master发送hello指令,如果master没有回应,sentinel就会频繁发送hello指令,发到一定阶段以后,就会把这个master标记为SRI_S_DOWN(主观下线:只有一个sentinel认为master挂了)状态,同时把信息在sentinel之间同步,然后其他sentinel也会频繁向master发送hello指令,结果还是没回应,其他的sentinel同样会把信息在sentinel之间同步,同步完之后就会认为master真的挂了(其实是过半sentinel认为他挂了就是真的挂了,上面配置有写),同时把master的状态标记为SRI_O_DOWN(客观下线:超过半数以上的sentinel认为master挂了)。
sentinel之间投票选出一个leader:每一个sentinel会向其他sentinel发送消息,其他sentinel会根据接收到消息的顺序决定将票投给哪个sentinel,如果一轮没有投票产生,那就再来一轮投票,每增加一轮,竞选次数就+1,最终,拥有多数投票的sentinel处理下一步工作。
sentinel处置阶段:被选举出来的sentinel会根据四大原则挑选备选的master,如下:
sentinel发送指令:
向新的master发送slaveof no one
向其他salve发送slaveof 新master的ip port,和新的master建立主从关系
向其他sentinel通过发布订阅机制同步新的master信息
缓存预热就是系统启动前,提前将相关的缓存数据加载到缓存系统(Redis),避免在用户请求的时候,先查询数据库(MySQL),然后再将数据缓存的问题!用户直接查询视线被预热的缓存数据,可以优先降低数据库(MySQL的压力)。
现象:数据库服务器崩溃
1、系统平稳运行过程中,忽然数据库连接量激增
2、应用服务器无法及时处理请求
3、数据库崩溃
4、应用服务器崩溃
5、重启应用服务器无效
6、Redis服务器崩溃
7、重启数据库后再次被瞬间流量放倒
问题排查
1、在一个较短的时间内,缓存中较多的key集中过期
2、此周期内请求访问过期的数据,Redis未命中,Redis向数据库获取数据
3、数据库同时接收到大量的请求无法及时处理
4、Redis大量请求被积压,开始出现超时现象
5、数据库流量激增,数据库崩溃
6、重启后仍然面对缓存中无可用数据的问题
7、Redis服务器被严重占用,Redis服务器崩溃
8、应用服务器无法及时响应数据请求,来自客户端的请求越来越多,应用服务器崩溃
9、应用服务器、Redis、数据库全部重启,效果还是不理想
解决方案(道)
1、更多的页面静态化处理
2、构建多级缓存架构:Nginx缓存+Redis缓存+Cache缓存
3、检测MySQL严重耗时的业务进行SQL优化
4、灾难预警机制
5、限流、降级
解决方案(术)
1、LRU( 最近最少使用)与LFU(最不经常使用)切换
2、数据有效期策略调整:过期时间使用固定时间+随机值的形式,避免大量key同时过期
3、超热数据使用永久key
4、定期维护:对即将过期数据做访问量分析,配合访问量做热点数据的延迟过期
总结
缓存雪崩就是瞬间过期数据量太大,导致对数据库服务器造成压力。如果能够有效避免过期时间集中,可以优先解决雪崩现象的出现,配合其他策略一起使用,并监控服务器的运行情况,根据运行记录做快速调整。
现象:
1、系统平稳运行过程中
2、数据库连接量瞬间激增
3、Redis服务器无大量key过期
4、Redis内存和CPU平稳,无波动
5、数据库崩溃
问题排查
1、Redis中某个可以过期,该key访问量巨大
2、多个数据请求从服务器直接压到Redis,均为命中
3、Redis在短时间内发起了大量对数据库中同一数据的访问
解决方案
缓存击穿可以看做时缓存雪崩的一种特殊形式,所以解决方案可以参考缓存雪崩
总结
缓存击穿就是单个高热数据过期的瞬间,数据量访问较大,未命中Redis后,发起了大量对同一数据的数据库访问,导致对数据库服务器造成巨大压力。
现象
1、应用系统运行平稳
2、应用服务器流量随时间增量较大
3、Redis服务器命中率随时间逐步降低
4、Redis内存平稳,CPU占用激增
5、数据库压力激增直至数据库崩溃
问题排查
1、Redis中大面积出现未命中key
2、出现非正常访问的URL访问
解决方案
1、将key与空结果缓存到Redis中,并设置一个60s有效期。那么在这一分钟里,所有同样的访问,都将直接从缓存中获得NULL
2、采用布隆过滤器,将数据库有的数据放一份副本到足够大的布隆过滤器中,只有用户访问,先把条件从布隆过滤器中判断一下,如果存储则放行,如果没有则直接拦截返回,从而避免了对底层数据库的访问
3、热点数据不过期 ,将用户频繁访问的热点数据设置为永不过期,这就可以保证访问不会落到数据库