全量同步:
1:从节点第一次连接主节点时
2:从节点断开时间太久,日志中的offset被覆盖时
1:从节点请求增量同步
2:主节点检查replid不一致,拒绝增量同步,开始全量同步
3:主节点将完整内存数据生成rdb文件,发送给从节点
4:从节点清空本地数据,加载rdb文件
5:主节点将rdb期间接收到的命令记录在日志中,并持续将命令发送给从节点
6:从节点接收到命令,与主节点保持同步
增量同步?
1:从节点提交自己的offset到主节点,主节点获取日志中offset之后的命令发送给从节点
1:从节点请求增量同步
2:主节点检查replid是否一致
3:一致,主节点从日志中获取offset后的数据
4:主节点发送offset后的命令
5:从节点接收后执行命令
6:完成同步
在从服务节点初始化完成,也就是全量同步完成后,每一次主节点的操作命令,都会立刻同步到从节点上
1:主从复制,一主多从
2:级联复制,一个主节点,一个从节点可以做其他从节点的主节点
哨兵模式:主从切换技术,常规的主从需要人工干预,费时费力;
哨兵是一个独立的进程,通过发送命令,等待redis响应,从而监控redis的运行状态
1:主从复制(一主一或多从)读写分离,主动同步,无容错,恢复功能
2:哨兵(基于主从,多了哨兵模式)
3:cluster(多主多从,推荐3主3从)数据分区
哨兵模式: 着眼于高可用,在 主节点 宕机时会自动将 从节点 提升为主节点,继续提供服务。
Cluster:着眼于扩展性,在单个 redis 内存不足时,使用 Cluster 进行分片存储。
1:通过发送命令,让redis返回运行状态(检查是否挂了)
2:当哨兵监测到redis主机宕机,会自动将从节点替换主节点,然后通过发布订阅模式,通知其他从节点,修改配置文件,切换主机
一个哨兵进程监控出错,可以使用别的哨兵对其进行监控,各个哨兵之间相互监控,也叫多哨兵模式
主服务器宕机,哨兵1先监测到结果,但是系统并不会马上进行故障转移,仅仅是哨兵1主观认为主服务器不可用,这种现象叫主观下线
当其他哨兵也监测到主服务不可用,并且达到一定数量时,哨兵之间会进行投票,选举出一个头领,然后由这个头领,选择由哪个从节点代替主节点,选择成功,就会发布订阅模式,通知其他哨兵,修改从属关系
1:健康性:从节点响应时间
2:完整性:数据备份的完整性
3:稳定性:心跳检测
4:以上都一样,节点启动分配的id,最小的那个
1:使用数据持久化方式保证数据不丢失
2:redis集群(哨兵或cluster),正常情况主机工作,备机备份不工作;备机每隔几秒向主机发送一个ping,主机若正常就会返回一个peng,如果不返回,备机就会连续发送三次ping,如果一直没有返回,说明主机宕机,备机就会顶上,这叫心跳监测
1:先确保消费速度没问题
2:在线扩容,建立临时队列,写一个临时分发程序,以轮询方式写入临时队列
3:等大量数据请求完成后,恢复正常队列
以laravel为例;laravel消息队列,支持执行失败的任务,进行重试,失败任务会被存储在failed_jobs数据表中,可以指定最大尝试次数,如果负载过高,可以延迟失败任务的重试
一般使用 list 结构作为队列,rpush 生产消息,lpop 消费消息。当 lpop 没有消息的时候,要适当 sleep 一会再重试。也可以使用blpop命令,他会在没有消息的时候,会阻塞住直到消息到来。
使用 pub/sub 主题订阅者模式,可以实现1:N 的消息队列。
在消费者下线的情况下,生产的消息会丢失,得使用专业的消息队列如 RabbitMQ等。
原理:
使用 zset有序集合,拿时间戳作为score权重分数,消息内容作为 key 调用 zadd 来生产消息,消费者用 zrangebyscore 指令获取 N 秒之前的数据轮询进行处理。
实现:
1:定义延迟队列类;
2:构造方法:创建redis对象
3:插入队列方法:zadd方法将任务写入队列中
4:队列处理:轮询队列,使用zrangebyscore获取满足到期时间的元素,判断是否为空,为空,终止循环;不为空,则使用zrem方法将元素从集合中删除,并进行逻辑处理
(1)事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。
(2)事务是一个原子操作:事务中的命令要么全部被执行,要么全部都不执行。
multi、exec、discard、watch
答:expire 和 persist 命令。
原理:使用redis链表,pop操作是原子的,高并发时,是按照顺序执行的
1:将商品库存数据写入到redis 队列
2:秒杀,判断用户是否存在,存在就终止,不存在继续抢购
3:出库,使用lpop移除库存,若库存为0则终止,若非0则继续
4:记录成功用户,将抢购成功用户存入新的消费队列
5:消费队列持久化抢购数据,将抢购用户记录到数据库中完成最终消费
如果消息队列失败,可能会导致少卖,这时需要配置重试策略,当失败后,会立刻尝试第二次、第三次消费,如果仍然失败,就将消息持久化到磁盘,交给补偿服务轮询处理
1:queue:table,生成队列任务表
2:配置env文件redis队列
3:jobs目录下创建任务类,构造方法中获取参数,handle方法处理业务逻辑
4:分发任务,控制器中使用任务类静态调用dispatch进行分发,当业务触发时,就会将消息分发给队列异步执行
缓存穿透:数据库和缓存都没有数据(都没数据+并发访问)
缓存击穿:数据库有,缓存没有(单个key过期+并发访问)
缓存雪崩:数据库有,缓存没有(大量key过期+并发访问)
解决:
缓存穿透:
1:缓存空对象,设置较短过期时间(浪费内存,数据不一致)
2:步隆过滤器,判断key是否存在,存在就返回,不存在返回空
缓存击穿:
1:不给key设置过期时间,或者设置定时器,定期更新缓存
2:给key加锁,当第一次请求时,去数据库获取数据并更新缓存,然后释放锁,当其他请求时,由于缓存中已经有值,就不再请求数据库
缓存雪崩:
1:如果key大量过期,将key的过期时间打散
2:如果redis故障,使用哨兵模式
内存淘汰、超时剔除、主动更新
内存淘汰:内存不足时,自动淘汰部分数据,下次查询时更新
超时剔除:设置缓存过期时间,到期自动删除,下次查询时更新
主动更新:编写业务逻辑,更新数据库时更新缓存
1:先更数据库,再更缓存(数据库脏读数据,会被跟新到缓存)
2:先更缓存,再更数据库(缓存成功,数据库失败,数据不一致)
3:先删缓存,再更数据库
4:先更数据库,再删缓存(更新成功,删除失败)推荐方案
1:使用消息队列,数据库更新成功后,向队列发送消息,缓存模块监听到新消息,会执行删除,利用消息队列手动提交机制,可保证删除顺利完成
2:canal监听;监听mysql变化,发生变化后,立即通知缓存服务,缓存收到通知,删除缓存
1:setnx命令;可能在获取锁之后,程序挂了,没来得及释放锁,导致死锁
2:setnx+expire命令;可能在这之间程序挂了,锁未释放,导致死锁
3:set命令在redis2.8中setnx和expire可以同时设置;
4:new Redlock对象,lock加锁,unlock删锁
因为redis是单进程单线程的,对redis来说,执行get、set以及eval等api,都是一个一个的任务,这些任务都会由redis的线程去负责执行,任务要么成功,要么失败,这就是redis原子性的原因,redis是io多路复用模型,即一个线程来处理多个tcp连接,好处就是客户端并发请求,也得排队处理,一定程度上解决了多线程模型带来的并发问题;
也不一定,但redis提供了原子操作命令,也即是锁命令:incr(get和set的集合体)、decr、setnx等
分布式锁:是控制分布式系统,或不同系统之间,共同资源访问时,一种锁的实现;不同系统之间共享某个资源时,往往需要互斥来防止彼此干扰,保证一致性
1:互斥性;在任意时刻,只有一个客户端持有锁
2:无死锁;即便持有锁的客户端崩溃,锁仍然可以被获取
3:容错;只要大部分redis节点都活着,客户端就可以获取和释放锁
分布式锁的实现:数据库、memcached、redis
先拿 setnx 来争抢锁,抢到之后,再用 expire 给锁加一个过期时间防止锁忘记了释放。
我记得 set 指令有非常复杂的参数,这个应该是可以同时把 setnx 和expire 合成一条指令来用的!