高并发高可用学习笔记
高并发解决解决方案
系统层:将一个系统拆分成多系统
- 将原来系统的模块,设计成新的子系统,单独部署,各个子系统之间,通过接口调用
- Dubbo(待研究)
缓存层:遇到“读”瓶颈,使用缓存
为啥单线程的redis可以支撑高并发,效率比多线程的memcached还高?redis底层原理?
基础:socket网络通信原理
:redis和memcache区别:
redis数据类型多,数据结构多
memcached 原生不支持集群,自带集群模式
redis的单线程模型
名词:file(文件) event(事件) handler(文件事件处理器)
因为文件事件处理器,是单线程的,所以redis才叫单线程模型
IO多路复用监听socket通信中产生的事件,并将事件放入到队列,
文件事件分派器,根据socket通信内容,指派处理器
redis单线程模型能支撑高并发的原因:
1:连接处理器,请求处理器,应答处理器,都是纯内存操作,快速处理
2:文件事件处理器(IO多路复用程序),非阻塞(监听并不断插入队列)
3:避免了多线程上下文切换
redis的数据类型,与对应的应用场景
- 1:string kv 最简单的一种
- 2:hash 对象,数组,改的时候,可以只改一个属性
- 3:list 链表 可以模拟队列,分页 lrange
- 4:set 程序部署在多台机器上,进行去重
- 5:zset 有序集合,排行榜应用
redis的过期策略?
定期删除(100ms随机抽取部分过期,并删除)
+惰性删除(查出过期,就删)
+ 配置一个内存淘汰机制(最近最少用)
1:缓存基于内存,不使用,会被热点数据挤掉
2:过期时间,需要加一定的随机性,防止雪崩
3:过期不代表,一定会删除,但是查是肯定查不到的
如何保证redis高并发高可用?
1高并发:集群(哨兵模式)几十万QPS
2高可用:持久化到磁盘
如何重启恢复redis缓存数据 快照备份和日志备份
- 主从复制的两种方式:
1:将RDB快照文件写到磁盘,再传输到slave
2:内存中快照文件,不存磁盘,直接传到slave - 主从加哨兵,什么时候会丢数据(master中最新数据没来得及复制到salve 就挂了)
- 脑裂问题,如何减少数据丢失:主从之间断网 从自动选主,出现两个主 ,导致新主上,没有新数据 解决原理:min-slaves-to-write min-slaves-max-lag 控制slave延迟不能比master落后太多,会让master暂停写入,让client自己降级缓存,或者灌入kafka消息队列
- 主观宕机 客观宕机 如何选举,如何自动修正其他slave配置
- slave 不会自动过期可以 master过期key 会发删除命令到slave
- backlog 是第一次做全量复制中断,后续做增量复制用的
- 恢复备份时 会导致master run id 变化,触发slave会重新做 全量复制
缓存雪崩,穿透,击穿
-
雪崩,大规模失效,如同时过期 ,某个缓存宕机
事前,缓存必须高可用,redis多个slave 且多级缓存,系统内部有ehcache最热数据的缓存,大小约1G,然后是redis缓存,然后是MYSQL
事中,本地ehcache缓存,加限流加降级 避免MYSQL被打死 限流2000QPS 其余3000QPS降级,返回友情提示,保证MYSQL不死,缓存恢复后,不再限流
事后,redis 必须做持久化,后续可以快速恢复 -
穿透,4/5请求(自己bug 或者黑客的假请求),缓存没有,库里面也没有 解决,库里面没查到,也将null写到缓存
-
击穿,抗了很大并发,突然失效 ,解决:设置永不过期
缓存和数据库双写一致性问题:
- 大并发写情况下,与一致性要求不高情况下的方案:先删缓存,然后写数据库
- 大并发读写,且严格要求一致性,串行化读写,将相同商品ID的读写,哈希到同一服务器的同一内存队列中,并合并队列中的相同读操作,根据实际情况增加机器,保证相应时间
redis并发竞争问题 redis的CAS方案
白话:多个客户端,写同一个KEY,如何解决并发? 解决:分布式锁 且数据本身从MYSQL查出来的时候,要带时间戳,把时间戳,当成版本,对比,决定是否写入缓存
线上缓存实例情况
redis cluster 10台机器,其中5台机器部署了master ,且各挂载一个slave
5个节点 对外进行读写服务 每台机器5万QPS 共25万QPS
8核CPU +32G内存 1T磁盘 分配给redis的,是10G 共计50G
任何主实例宕机 salve自动切换为master
如果每条数据10Kb 10W条数据1G 可以存500万数据 常驻200W条
总redis内存 占用20G 不到50%
队列层:遇到“写”瓶颈,使用队列
队列的主要作用:削峰,异步,解耦
- 削峰案例:将高于平时的大量数据,“储存”到队列中,慢慢处理;
- 异步案例:写入延时过高,或者写并发过高,异步处理
- 解耦案例:A子系统发布数据,各个子系统,按需订阅
如何保证消息队列高可用
rabbitMQ 高可用
- 普通镜像模式:非高可用模式,单台宕机后,丢失消息(类似一致性hash实现的memcache,宕机一台,丢失缓存)
- 镜像集群模式:普通模式的升级,每台机器的队列,都有完整备份,宕机后,备份机顶上,非分布式,但是高可用
kafka高可用
- 本身的分布式高可用架构,多个partition(leader),每个partition都有至少一个副本(fellow),leader宕机,fellow顶上
如何保证消息的幂等性
- 队列消息体中,单一业务消息,包含唯一消息ID,发送方生成,消费方判重
如何保证队列中消息不丢失
可能的丢失原因:
- 1:写到队列时候的网络原因
- 2:消息到了队列,队列内部出错,没保存下来
- 3:队列保存好后,消费者没来得及消费,队列自己挂了
- 4:消费者,拿到了消息,但是没有来得及消费,就挂了,队列以为已经处理完了
rabbitMQ解决方案
- 发送失败
1:基于rabbitMQ的发送消息事务(类似MYSQL事务提交回滚),保证发送成功(缺点,阻塞,降低队列吞吐)
2:生产者调成confirm模式(回调机制,优化了上一种方式),生产者提供回调函数
- 队列本身丢失消息:将队列的信息持久化到磁盘:
2.1(持久化Q)将队列设置为持久化,
2.2(持久化M)将消息的delivery Mode 设置为持久化,
这种情况下,还有风险,放到内存就挂掉,没来得及持久化,但是概率已经很低了。
- 消费者丢失数据:原因:消费者打开了autoACK,处理:关闭自动应答,自己写代码应答(类似kafka应答)
kafka解决方案
- kafka的leader和follower类似redis的哨兵模式的故障切换
- 若leader倒下(消息到leader就倒下,没到follower),自动选举一个follower,导致的数据丢失
1:kafka服务端min.insync.replicas参数,大于1,每个partition至少两个副本
2:min.insync.replicas参数,大于1,要求每个leader至少感知一个follower存在
3:生产端设置acks=all这个是要求每条数据,都写入了leader和follower才认为写入成功
4:生产端设置retries=MAX 设置无限次重发送
以上设置,可以实现,宕掉leader后,若leader还保有一个follower是可以正常工作的。即可保证kafka本身不会丢数据
按照以上设置kafka生产者不会丢失数据
怎么保证从消息队列中拿到的数据按顺序执行
- rabbitMQ
问题场景:需要保证顺序消费的数据,被不同消费者消费
解决:需要保证顺序性的数据,放置到同一个Queue,让同一个消费者消费 - kafka
生产者指定相同KEY,相同Key的数据,进行hash分发,会被按顺序放到同一个partition,kafka默认就是一个消费者,对应一个partition,若单线程,吞吐量不够,可以多线程消费
若消费者是多线程并发处理,就会出问题
此时,需要保证顺序的那几条消息,加入内存队列,给相同一个线程消费,类似rabbitMQ,才能进行多线程并发,加大吞吐量
几百万消息在消息队列积压了几个小时怎么办
原因:一般是消费者挂掉了
解决1:先修复消费者
2:问题转变为如何快速消费所有数据
2.1新申请一批机器,将原来消费者程序,改成新的生产者,新机器改为消费者,加速消费
2.2 坑:MQ被设置自动过期,解决:写程序,想办法重现原来生产者发送的数据
2.3 坑:MQ磁盘快爆了。类似2.1方式解决 ,或者消费者,消费完就扔掉,后续写程序结合2.2查漏补缺,实现快速消费
队列下的数据库写瓶颈:分库分表
分库分表(常用是水平拆分) 对订单ID,进行hash分发,分布式数据库,到不同库,解决库写不过来的情况
数据库分库分表中间件
sharding-jdbc (分布式事务,分库分表,读写分离,都支持,client___缺点耦合,维护成本低,升级成本高)
mycat(代理____维护成本高,升级成本低)
cobar(代理) TDDL(client) atlas(代理)
如何不停机将数据库迁移到分库分表
低级方案:公告,停机运维 ,停机,重新分发 中途没搞定,回滚到原来数据,第二天继续
高级方案:不停机双写方案
如何设计可以动态扩容的分库分表方案
低级:停机,重新迁移 上亿数据,维护时间超级长
刚开始做分库分表,规划三五年内够用
扩容原因:数据量太大的扩容,写并发太大的扩容
扩容方式:8台机器原来8个库,每个库8张表 将其中4个库4个表,迁移到新的8台机 相当于扩容两倍,但是库数量和表的总数没变,若使用 取模方法 获取库的位置,很方便迁移
分库分表以后,全局ID主键如何处理?
1:给一个表,专门插入一下,可以得到唯一自增ID,后续手动将ID设置为得到的ID
缺点:单表成为性能瓶颈 适合并发低,但是数据量大的情况
2:redis 分布式锁 自增
3:snowflake算法
读写分离的原理,主从同步的延时怎么处理?
主从同步,为了支撑更多读写
经验: 主库1000/S 写 几ms延迟 主库 2000/s 会有几十毫秒延迟 主库并发越高,延迟越久
5.6版本以后,从库relay日志 可以多线程,速度稍快
半同步复制:semi-sync复制 主库写入数据,且至少一个从库获取到binlog日志,才算写入成功 这样保证不丢失数据
- 原因:延迟问题:没读到,后续更新,代码以为读到,出现bug
-
解决方案
1:拆库,降低写入并发,降低延迟(所以这里是有效方案) 2:并行复制,有点作用,但是不大 3:写代码时,需要慎重,需要考虑,有主从延时。(这里是源头解决) 4:若后续有更新操作,不要读了再更新,直接更新 5:要求高的,直接读一下主库(不推荐)
缓存下的数据库读瓶颈:读写分离
搜索: 单机lucene,集群ES
其他问题
分布式Session
存redis
分布式事务
- 方案一:两阶段提交方案(XA方案----此方案low,交叉访问了服务)
第一阶段:先和各个子系统确认,是否正常 (团建前确认)
第二阶段:完成事务 (执行团建) - 方案二:TCC方案(各个系统有自己的数据库,A系统,联系BC银行) 实现方案较复杂,维护成本高
Try阶段:通过接口让B银行检查,前够不够,并冻住,让C银行检查,账户是否存在
confirm阶段:A系统在自己数据库插入记录,调用B接口,扣款,调用C接口,转账
cancel阶段:如果上面出问题,回滚,自己写代码,实现回滚 - 方案三:本地消息表方案
各自有自己的消息表,并且都有状态,然后自己检查状态,重发消息,
消息处理好,通知别人,让别人改状态 - 方案四:可靠消息最终一致性方案 3.2.6版本RocketMQ
上个方案中,插入表以前,发prepare消息,插入成功后,发confirm消息,表的待确定,改为rocketMQ消息的待确定,待确定太久,rocketMQ会自动扫描,调用A系统回调,重发MQ,A系统,不断重发,总可以发成功,最终B也执行成功,整个系统需要保证幂等性 - 方案五:最大努力通知方案 一定程度,允许分布式事务失败
多次通知,通知多次后,即使失败,也不管了 如日志
分布式事务,会导致代码复杂十倍,平衡方案
平衡方案:监控代码,记录完整日志,事后人工修复,只有事务要求极高的地方采用这个分布式事务
订单,资金 这种做分布式事务
积分,订单信息,这种做监控,写日志