目录
INCR key;DECR key 对存储在指定key的数值执行原子的加1操作
所谓原子操作是指不会被线程调度机制打断的操作
这种操作一旦开始,就一直运行到结束,中间不会有任何context switch(切换到另一个线程);
(1)在单线程中,能在单条指令中完成的操作都可以认为是原子操作,因为中断只能发生于指令之间;
(2)在多线程中,不能被其他线程打断的操作叫做原子操作;
Redis中命令的原子性主要得益于Redis的单线程;
Redis事务
Redis事务是一个单独的隔离操作,事务中的所有命令都会序列化、按顺序的执行。事务在执行的过程中,不会被其他客户端发送过来的命令请求所打断;
Redis事务的主要作用就是串联 多个命令防止别的命令插队;
multi开启事务:
exec:执行
事务的错误处理方式![](https://img-blog.csdnimg.cn/8b5b2d24f4414b7393dd8a946471a378.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5bCP6aaoamF2YQ==,size_20,color_FFFFFF,t_70,g_se,x_16)
如果在执行的时候出现错误,出现错误的不执行,其他的正常执行
Redis冲突问题
1、悲观锁
顾名思义就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会被阻塞,直到拿到锁,传统的关系型数据库里面就用到了很多这种锁机制,比如表锁、行锁等、读锁、写锁等都是在操作之前先上锁;
2、乐观锁(Redis使用的是乐观锁)
每次拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会去判断在此期间有没有其他操作更新这个数据(可以使用版本号机制)。乐观锁多用于读、写的应用类型,这样就可以提高吞吐量,Redis就是利用这个机制显现的;
总结:
Redis事务三特性
1、单独的隔离操作
事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断;
2、没有隔离级别的概念(和其他操作没有相互的关联,是单独的隔离操作)
队列中的命令没有提交之前都不会实际执行,因为事务提交前任何指令都不会被实际执行;
3、不保证原子性
事务中如果有一条命令执行失败,其后的命令也会执行 ,没有回滚;
Redis持久化
RDB(默认开启)
在指定的时间间隔内将内存中的数据集快照写到磁盘中去;
Redis会单独创建一个子进程来持久化,会先江数据写入到一个临时文件中,等到持久化过程结束之后,用这个临时文件替换上一次持久化好的文件;
建立一个临时文件的原因:防止直接持久化过程中出现中断,导致数据的不完整性;
RDB的缺点是最后一次持久化后的数据可能丢失(写时复制技术)
在最后一次数据更新的时候,服务器出现问题,但是现在还没有进行持久化(因为他是按照时间间隔持久化的)所以可能会导致最后一次数据的丢失;
优势:
适合大规模的数据恢复
对数据完整性和一致性要求不高更适合使用
节省磁盘空间
恢复速度快
劣势
Fork的时候,内存中的数据被克隆了一份,占用内存空间;
虽然Redis在fork时使用了写时拷贝技术,但是如果数据庞大时比较消耗性能
在备份周期一定间隔时间做一次备份,所以如果Redis意外down掉的话,就会丢失最后一次快照后的所有修改
AOF(默认关闭)
以日志的形式来记录每一个写操作(增量保存),不记录读操作,将Redis执行过的所有写指令记录下来(读操作不记录),只许追加文件但不可以改写文件,redis启动的时候会读取该文件重新构建数据,也就是说,redis重启的话就根据日志文件的内容将写指令从前到后执行一次来完成数据的恢复;
AOP和RDB同时开启之后,系统默认读取AOF的数据(数据不会存在丢失);
AOF持久化流程
1、客户端的请求写命令会被append追加到AOF缓冲区中;
2、AOF缓冲区根据AOF持久化策略【always,everysec,no】将操作sync同步到磁盘的AOF文件(appendonly.aof)中;
3、AOF文件大小超过重写策略或手动重写时,会对AOF文件rewrite重写,压缩AOF文件容量;
4、当服务器重启的时候会重新load加载AOF文件的写操作达到数据恢复的目的;
异常恢复:redis-check-aof --fix
如果appendonly.aof出现错误,可以通过redis-check-aof --fix appendonly.aof进行修复
AOF同步频率设置
appendfsync always:始终同步,每次Redis的写入都会立刻记入日志;性能较差,但是数据完整性好;
appendfsync always everysec:每秒进行同步,每秒计入一次,数据可能出现丢失;
appendfsync no:redis不主动同步,把同步时机交给操作系统
Rewrite 压缩:重写时机 64m的两倍 128的时候重写压缩(指令整合,set a a1 set b b1 重写位:set a a1 b b1)节约空间
优势:
备份机制更加稳健,丢失数据的概率降低;;
可读的日志文件,可以通过redis-check-aof恢复appendonly.aof;
劣势:
比RDB记录更多的磁盘空间(除了数据之外,还记录了指令操作)
恢复备份速度慢(记录的东西多,恢复就慢)
每次读写都同步的话,有一定的性能压力(每写一次就同步一次存在一定的压力)
存在潜在Bug,可能造成问题
总结:官方建议两个都启用
如果对数据不敏感,可以单独使用RDB
不建议单独使用AOF,因为AOF存在一些潜在的BUG
如果只是做内存的缓存,不需要持久化两个都不用
Redis主从复制
主机数据更新之后根据配置和策略,自动同步到备机的master/saver机制,Master以写为主,Slave以读为主;
好处:
1、读写分离
2、容灾快速恢复(当一台从服务器down之后,还可以从其他从服务器读取数据)
问题:从服务器down可以从其他从服务器读取数据,因为只能有一个主服务器(因为如果有多个主服务器,就不知道将哪个主服务器的数据复制到从服务器中)、
所以就引出了集群的概念
一主二仆
搭建一主两从过程
需要在从机中使用slaveof 主机ip 端口号指定是谁的从机
特点:
1、当从服务器down之后,重启之后不能主动的加入到主从里面,需要重新加入到主机的从服务器中;
2、当加入到主机服务器的时候,会将主机的数据从头复制到从机服务器中;
3、主服务器down之后,从服务器不会做任何变化,还是从服务器,主服务器重启之后,仍然是从服务器的主机;
主从复制原理 :
1、当从服务器连接上主服务器之后,从服务器向主服务器发送进行数据同步的消息;
2、主服务器接到从服务器发送过来的同步消息,把主服务器数据进行持久化,生成rdb文件,然后把rdb文件发送给服务器,从服务器拿到rdb之后进行读取;
3、每次主服务器进行写操作之后,就会和从服务器进行数据同步;
全量复制:slave服务在接收到rdb文件之后,将其存盘并加载到内存中(完全复制操作);
增量复制:主服务器做出的修改传递给从服务器,完成数据同步;
(从服务器只要重新连接主服务器,就会进行一次完全同步(就会进行全量复制))
薪火相传
上一个从服务器可以是下一个从服务器的主服务器,从服务器同样可以接收其他从服务器的连接和同步请求,那么该从服务器(图中第二个)就作为了链条中下一个从服务器的主服务器,可以有效缓解主服务器的写压力;
缺点:
一旦中间的某个从服务器down之后,后面的从服务器就没有办法备份,主机挂掉了从服务器还是从服务器,没有办法写数据;
反客为主
针对主服务器down之后,可以通过命令slaveof on one让一台从服务器变为主服务器;
缺点:需要手动进行设置
哨兵模式
反客为主的自动版,能够后台监控主机是否故障,如果故障了可以根据投票数自动将从服务器转换为主服务器;
创建过程
1、vim sentinel.conf
2、 在sentinel.conf文件中写上:sentinel monitor mymaster 192.168.200.129 6379 1
其中 1 代表,至少有多少个(1个)哨兵同意迁移的数量;
3、启动哨兵:redis-sentinel /myredis/sentinel.conf;
当主机down之后,从机选举中产生新的主机,;
根据优先级别:replica-priority;进行选举新的主机
1、replica-priority默认值100:数值越大优先级越低;
2、当优先级一样的时候,选择偏移量大的;(偏移量是指获取原主机数据最全的)
3、选择runid最小的从服务器(runid是随机的)
4、原主机重启后变成从机
复制延时
由于所有的写操作都是现在主服务器上操作,然后同步更新到从服务器中,所以从主服务器同步到从服务器有一定的延迟,当系统繁忙,或者从服务器的数量增加会使得,复制延迟更加严重;
集群(重点)
问题:
容量不够,redis如何进行扩容
并发写操作,redis如何分摊
代理主机方式:
无中心化集群:
任何一台服务器都可以作为集群的入口,他们之间可以互相连通;
Redis集群特点
1、Redis集群实现了对Redis的水平扩容,即启动N个redis节点,将整个数据库分布存储在着N个节点中,每个节点存储总数据的1/N;
2、Redis集群通过分区来提供一定程度的可用性:即使集群中有一部分节点失效或者无法进行通讯,集群也可以继续处理命令请求;
集群设置过程:
配置文件:
include /myredis/redis.conf
pidfile "/var/run/redis_6391.pid"
port 6391
dbfilename "dump6391.rdb"
cluster-enabled yes //打开集群模式
cluster-config-file nodes-6391.conf //设置节点配置文件名
cluster-node-timeout 15000 //设定节点失联时间,超过该事件,集群自动进行主从切换
将六个节点合成一个集群
启动六个节点
创建集群:
在/opt/redis/src下(redis的安装环境中进行下面操作创建集群)
redis-cli --cluster create --cluster-replicas 1 192.168.200.129:6379 192.168.200.129:6380 192.168.200.129:6381 192.168.200.129:6389 192.168.200.129:6390 192.168.200.129:6391
集群连接:
redis-cli -c -p 端口号;
分配原则:尽量保证每个主数据库运行在不同IP地址,每个从库和主库不在一个IP地址上;
保证一台机器down掉之后,还能保证其他机器的高可用;
插槽的作用:将数据平均分配到集群中的主机中,减轻压力;
故障恢复:
如果主机down之后,从机会变为主机,之前的主机就会变成从机;
如果主机和从机同时down之后:
根据配置文件中的cluster-require-full-converage 为yes,那么整个集群都挂掉;
cluster-require-full-converage 为 no,那么除了该段插槽数据全部不能使用,无法存储,其他段插槽数据正常使用;
Redis集群的好处:
实现扩容
分摊压力
无中心配置相对简单
Redis集群的不足
多键操作不被支持
多键的Redis事务也不被支持,lua脚本不被支持;
面试重点
1.1 缓存穿透![](https://img-blog.csdnimg.cn/e18e4c30852e4372ade1cd2b41b9ed29.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5bCP6aaoamF2YQ==,size_20,color_FFFFFF,t_70,g_se,x_16)
问题描述:
1、应用服务器压力变大;(访问量变大)
2、redis命中率降低(缓存中没有想要的数据,这个时候就会去数据库中查找)
3、一直查询数据库,就导致最终数据库的崩溃;
出现此现象的原因:
1、在redis中查询不到数据(穿透缓存直接查数据库);
2、出现很多非正常url访问(数据是伪造的,在数据库中找不到数据,就会一直去查询数据库,导致数据库的崩溃);
解决方案:
1、对空值进行缓存:
如果查询返回的数据为空(不管数据存不存在),都把这个空结果(null)进行缓 存,设置空结果的过期时间会很短,最长不超过五分钟;
2、设置可访问的名单(白名单)
使用bitmaps类型定义一个可以访问的名单,名单id作为bitmaps的偏移量,每次访问和bitmap里面的id进行比较,如果访问id不在bitmaps里面,进行拦截,不允许访问;
(每次访问都要去bitmaps中去查询id,效率会有所影响)
3、采用布隆过滤器
用于检索一个元素是否在一个集合中,他的优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率和删除困难;将所有可能存在的数据哈希到一个足够大的bitmaps中,一个一定不存在的数据会被这个bitmaps拦截,从而避免了对底层存储系统的查询压力;
4、进行实时监控
当发现Redis的命中率开始急速降低,需要排查访问对象和访问的数据,和运维人员配合,可以设置黑名单限制服务(一直在访问数据库中不存在的数据);
总结:
总的目的就是把访问的攻击限制在服务器外面,减少服务器压力
1.2 缓存击穿
缓存击穿现象:
1、数据库访问压力瞬间增加;
2、redis里面没有出现大量Key过期;
3、redis正常运行;
4、数据库出现崩溃
出现此现象的原因:
Redis中某个Key过期了,但是这个时候又有大量的访问使用这个key,所有就会去访问数据库,瞬间有大量的访问去访问数据库的时候,出现缓存击穿,数据库崩溃;
解决方案:
1、预先设置热门数据:在Redis高峰访问之前,把一些热门数据提前存入到redis里面,加大这些热门数据key的时长;
2、实时调整:现场监控哪些数据热门,实时调整Key的过期时间;
3、使用锁:
查询Redis中的数据如果为空,就设置排它锁(这个时候其他线程就去查询数据,然后将数据缓存到Redis中),睡眠一会之后再去查询Reids,如果成功,就去查询数据库、同步缓存,删除排它锁;效率低
1.3 缓存雪崩
雪崩问题描述:
数据库压力变大响应变慢,应用访问也会变慢,就会造成Redis中会有大量的访问等待。最终造成数据库、服务器、Redis崩溃;
造成此现象的原因:
在极短时间内,查询大量Key的集中过期情况
解决方案
1、构建多级缓存架构:nginx缓存+redis缓存+其他缓存;(结构过于复杂)
2、使用锁或队列:(有效)
用加锁或者队列的方式保证来保证不会有大量的线程对数据库一次性进行读写, 从而避免失效时大量的并发请求落到底层存储系统上,不适合高并发的情况(加锁之后效率低);
3、设置过期时间标志更新缓存
记录缓存数据是否过期(设置提前量,对于一些快要过期的缓存进行更新),如 果过期会触发通知另外的线程在后台去更新实际Key的缓存;
4、将缓存失效时间分散开
可以在原有的失效时间基础上增加一个随机值,比如:5分01秒过期,5分02秒过期,这样每一个缓存的过期时间的重复率就会降低很多(减少出现大量缓存同时过期的情况),就很难触发集体失效事件;
2 分布式锁
原来单体单机部署的系统演变为分布式集群系统,由于分布式系统多线程、多进程并且分布在不同的机器上,这就是原来的单机部署下的并发控制锁失效,因为不同的机器并不认识其他机器的锁,这个时候就需要一个能够跨系统来进行对资源的访问,这就是分布式锁需要解决的问题;(加一把锁之后对分布式集群系统里面的机器都有效)
2.1 使用redis实现分布式锁(设置锁和过期时间)
setnx key value:只有在键不存在的时候才能操作,相当于加锁,只有通过del 删除之后才能进行操作(del 相当于释放锁);
问题1:如果一直占用锁,其他线程就无法使用,可以通过expire key time,设定锁的占用时间(expire),过期之后释放锁;
问题2:setnx key val 和 expire key time不是一个原子操作,如果还没有来得及进行设置过期时间,服务器就down了,无法设置他的过期时间;
解决措施:一边设置值的时候,同时设置他的过期时间(这样就变成原子操作);
set users 10 nx ex 12;这样就即设置值又设置了过期时间;
1、使用setnx上锁,通过del释放锁;
2、锁一直没有释放,设置key过期时间,自动释放;
2.2 分布式锁(UUID方式误删)
A把B的锁释放了;可以通过给锁加上一个UUID,每一个操作对应一个UUID在释放锁的时候判断锁的UUID和当前操作的UUID是否一致,如果一致释放锁,不一致就不释放锁,可以做到防止把别人的锁释放;
2.3 保证删除的原子性
在比较之后发现UUID一样,但是还没有进行删除操作,就自动释放了,然后B拿到了锁正在进行操作,这个时候,A执行到删除操作,将B的锁释放了(因为之前已经比较过UUID了,这个时候就误删了别人的锁);
实现Redis删除操作的原子性:
使用LUA脚本:LUA脚本是类似与redis事务,具有原子性,不会被其他命令插队(我没删完锁,别人就不能插队拿到锁),可以完成redis事务的操作;
分布式锁总结:
为了确保分布式锁可用,需要满足以下条件:
1、互斥性:任何时刻只有一个客户端持有锁;
2、不会发生死锁:即使有一个客户端在持有锁期间发生崩溃,没有主动释放锁,也能保证其他客户端能拿到锁;
3、解铃还需系铃人:加锁和解锁必须是同一个客户端,客户端自己不能把别人加的锁释放了;
4、加锁和释放锁必须具有原子性;