高并发40问总结
01. 高并发通用设计
- 横向扩展:分而治之是一种常见的高并发系统设计方法,采用分布式部署的方式把流量分流开,让每个服务器都承担一部分并发和流量
- 缓存:使用缓存来提高系统的性能。缓存的语义很丰富,可以将任何降低响应时间的中间存储都称为缓存。
- 异步:在某些场景下,未处理完成之前,我们可以让请求先返回,在数据准备好之后再通知请求方,这样可以在单位时间内处理更多的请求。请求丢到消息队列中,快速响应用户。
选择最小的结构,满足业务和流量的要求,避免过度设计带来的成本上升。
02 |架构分层:我们为什么一定要这么做?
- 分层的设计可以简化系统设计,让不同的人专注做某一层次的事情
- 分层之后可以做到很高的复用
- 分层架构可以让我们更容易做横向扩展(单独部署谋一层,如service,rpc调用)
参照阿里发布的《阿里巴巴 Java 开发手册 v1.4.0(详尽版)》
03|系统设计目标(一):如何提升系统性能?
三高”,也就是“高并发”“高性能”“高可用”,它们是互联网系统架构设计永恒的主题
高并发系统设计的三大目标:高性能、高可用、可扩展
高并发:运用设计手段让系统能够处理更多的用户并发请求,也就是承担更大的流量。它是一切架构设计的背景和前提。
性能和可用性,是我们实现高并发系统必须要考虑的因素。性能反应了系统的使用体验。可用性则表示系统可以正常服务用户的时间
**“可扩展性”,**它同样是高并发系统设计需要考虑的因素
优化原则:
- 首先,性能优化一定不能盲目,一定是问题导向的(我理解,目标导向)。脱离了问题,盲目地提早优化会增加系统的复杂度,浪费开发人员的时间
- 其次,性能优化也遵循“八二原则”,即你可以用 20% 的精力解决 80% 的性能问题。在优化过程中,抓主要矛盾
- 再次,性能优化也要有数据支撑。不能盲目的调优秀
- 最后,性能优化的过程是持续的
性能的度量指标
-
平均响应时间
-
最大响应时间
-
分位值(很适合作为时间段内,响应时间统计值来使用),我们把这段时间请求的响应时间从小到大排序,,排除了偶发性。
P99的计算方式,是将1000笔请求的RT从小到大进行排序,然后取排在第99%位的数值,基于以上举例数据来进行计算,P99=50ms,其他分位值的计算方式类似
响应时间 1s 时,吞吐量是每秒 1 次,响应时间缩短到 10ms,那么吞吐量就上升到每秒 100 次。所以,一般我们度量性能时都会同时兼顾吞吐量和响应时间,比如我们设立性能优化的目标时通常会这样表述**:在每秒 1 万次的请求量下,响应时间 99 分位值在 10ms 以下**。
高并发下的性能优化
主要有两种思路:
- 提高系统处理的核心数(需要压测找到平衡)
- 减少单次任务的响应时间(看你的系统是IO密集还是CPU密集型)
- CPU密集型,需要大量CPU计算,可以采用更高效的算法等。可以使用Profile工具(火焰图、阿尔萨斯等)找到消耗CPU时间最多的方法或者模块。如果是 CPU 则使用 On-CPU 火焰图。
如果是 IO 或锁则使用 Off-CPU 火焰图 - IO密集型,是指系统的大部分操作是在等待IO完成。这里IO指的是磁盘IO和网络IO。发现这类瓶颈采用的手段主要有两类
- 采用工具,Linux工具集很丰富,网络协议栈、网卡、磁盘、文件系统、内存,等等。也可以借助普罗米修斯explore-node监控
- 监控来发现性能问题,可以自己打日志,或者借助工具,如普罗米修斯等,监控MySQL、Redis等
- CPU密集型,需要大量CPU计算,可以采用更高效的算法等。可以使用Profile工具(火焰图、阿尔萨斯等)找到消耗CPU时间最多的方法或者模块。如果是 CPU 则使用 On-CPU 火焰图。
04 | 系统设计目标(二):系统怎样做到高可用?
可用性的度量:
MTBF(Mean Time Between Failure)是平均故障间隔的意思,代表两次故障的间隔时间,也就是系统正常运转的平均时间。这个时间越长,系统稳定性越高。
**MTTR(Mean Time To Repair)**表示故障的平均恢复时间,也可以理解为平均故障时间。这个值越小,故障对于用户的影响越小。
Availability = MTBF / (MTBF + MTTR)
高可用系统设计思路
1. 系统设计
Design for failure
- 部署的时候,要考虑故障转移。
- 超时控制。通过收集系统之间的调用日志,统计比如说 99% 的响应时间是怎样的,然后依据这个时间来指定超时时间
- 降级和限流。降级是为了保证核心服务的稳定而牺牲非核心服务的做法(关闭一些非核心服务)。限流完全是另外一种思路,它通过对并发的请求进行限制来保护系统。
2. 系统运维
可以从灰度发布和故障演练两个方面来考虑如何提升系统的可用性。
- 出问题,一般都是更新新功能的时候出现的,因此处理要提供回滚方案,另一个手段就是灰度发布(不是一次性全上线,是按照一定比例逐步推进)。
- 故障演练。故意破坏一些组件,看效果
05 | 系统设计目标(三):如何让系统易于扩展?
高扩展是一个设计指标,但是提高扩展性会很复杂,无状态的服务和组件更易于扩展,而像 MySQL 这种存储服务是有状态的,就比较难以扩展。因为向存储集群中增加或者减少机器时,会涉及大量数据的迁移,而一般传统的关系型数据库都不支持。
数据库、缓存、依赖的第三方、负载均衡、交换机带宽等等都是系统扩展时需要考虑的因素
高扩展的设计思路
拆分是提升系统扩展性最重要的一个思路,它会把庞杂的系统拆分成独立的,有单一职责的模块。相对于大系统来说,考虑一个一个小模块的扩展性当然会简单一些
存储和业务拆分扩展。
07 | 池化技术:如何减少频繁创建数据库连接的性能损耗?
通过观察tcp数据包的时间,可以发现tcp连接握手断开挥手的时间,远大于sql执行的时间(常规条件下),所以考虑用池化技术,来减少这种频繁的连接断开操作,说白了是一种空间换时间的方式。
如果无视磁盘和网络,那么结论非常简单。有几个核心就创建几个线程,再增加连接数就会因为上下文切换损耗而导致性能下降。
连接数 = ((核心数 * 2) + 有效磁盘数)
tips:在重复创建链接的情况下,大概4/5的时间消耗在创建和销毁链接,加入sql执行时间是1ms,链接的时间是4ms,那么1s中执行查询是200次。用上池化技术,查询就到了1000次。(数据库是单核单线程,忽略io和网络;这样估算比较好理解)
引申出:数据库优化,减少io(三层树、pool_buffer等);加快扫描速度和行数=》(索引)
08 | 数据库优化方案(一):查询增加时候,如何做主从分离?
读写请求量的差距可能达到几个数量级,将读写流量分开,做主从的读写分离。
主从分离的技术点:
- 数据的拷贝,称为主从复制
- 在主从分离的情况下,我们如何屏蔽主从分离带来的访问数据库方式的变化,让开发同学像是在使用单一数据库一样。
1. 主从复制
一主多从,写只发生在主库,读发生在从库。
**是不是我无限制地增加从库的数量就可以抵抗大量的并发呢?**实际上并不是的。因为随着从库数量增加,从库连接上来的 IO 线程比较多,主库也需要创建同样多的 log dump 线程来处理复制的请求,对于主库资源消耗比较高,同时受限于主库的网络带宽,所以在实际使用中,一般一个主库最多挂 3~5 个从库
对于主从延迟的问题:
- 数据冗余(网络传输的时候,直接给全量的数据,而不是一个id),看数据的情况,不让去查从库
- 使用缓存,还是不用去查从库
- 查主库(一般不用)
2. 如何访问数据库
分库分表+主从,导致配置非常复杂,所以业界涌现很多中间件。
- 淘宝TDDL 为代表,看成是一种数据源代理,它的配置管理多个数据源,每个数据源对应一个数据库(主|从)。当有一个数据库请求时,中间件将SQL发给指定的数据源来处理,并将结果返回。和代码一块运行,适合小团队使用。Sharding-jdbc
- 单独部署得代理层方案。如Mycat
09 | 数据库优化方案(二):写入数据量增加时,如何实现分库分表?
垂直拆分,顾名思义就是对数据库竖着拆分,也就是将数据库的表拆分到多个不同的数据库中
垂直拆分的原则一般是按照业务类型来拆分,核心思想是专库专用。
…
10 | 发号器:如何保证分库分表后ID的全局唯一性?
基于 Snowflake 算法搭建发号器
id是时间有序的,排序可以直接使用。时间有序,插入的时候性能也更高
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-957XrTAD-1690529784375)(C:\Users\Administrator\Desktop\interviews\高并发40问总结.assets\image-20230627111309281.png)]
如果你的系统部署在多个机房,那么 10 位的机器 ID 可以继续划分为 2~3 位的 IDC 标示(可以支撑 4 个或者 8 个 IDC 机房)和 7~8 位的机器 ID(支持 128-256 台机器);12 位的序列号代表着每个节点每毫秒最多可以生成 4096 的 ID
一般来说我们会有两种算法的实现方式
- 嵌入到业务代码中,也就是分布式服务器中。好处是不跨网络,性能好一点。但是需要更多的机器ID位数来支持更多的业务服务,需要一致性组件保证每次机器重启获取唯一的机器ID
- 发号器。如果qps不高的话,会导致每毫秒末尾ID永远是1。
- 时间戳记录秒而不是毫秒
- 生成序号的起始可以随机下
11 | NoSQL:在高并发场景下,数据库和NoSQL如何做到互补?
对于机械磁盘的访问方式有两种:一种是随机 IO;另一种是顺序 IO。随机 IO 就需要花费时间做昂贵的磁盘寻道,一般来说,它的读写效率要比顺序 IO 小两到三个数量级,所以我们想要提升写入的性能就要尽量减少随机 IO
12 | 缓存:数据库成为瓶颈后,动态数据的查询要如何加速?
实际上,凡是位于速度相差较大的两种硬件之间,用于协调两者数据传输速度差异的结构,均可称之为缓存。
常见的缓存主要就是静态缓存(nginx)、分布式缓存(Redis)和热点本地缓存(Guava Cache 等)这三种
13 | 缓存的使用姿势(一):如何选择缓存的读写策略?
针对不同的业务场景,缓存的读写策略是不同的。
Cache Aside(旁路缓存)策略
更新缓存再更新数据库(导致数据不一致)
产生问题的原因:变更数据库和变更缓存是两个独立的操作。
处理:
读策略的步骤是:
- 从缓存中读取数据;
- 如果缓存命中,则直接返回数据;
- 如果缓存不命中,则从数据库中查询数据;
- 查询到数据后,将数据写入到缓存中,并且返回给用户。
写策略的步骤是:
- 更新数据库中的记录;
- 删除缓存记录。
因为写的速度要小于读的速度,且删除缓存很快,所以很少出问题。
缓存旁观最大问题:写入频繁时候,缓存被频繁删除,导致缓存命中有问题。两种解决方案:
- 更新数据也更新缓存,只是更新缓存的时候加上分布式锁(和数据库原子执行,我理解)
- 同样更新数据也更新缓存,不过加上缓存过期时间,一段时间的不一致也可以容忍
Read/Write Through(读穿 / 写穿)策略
用户只与缓存打交道,由缓存和数据库通信,写入或者读取数据
Write Back(写回)策略
这个策略的核心思想是在写入数据时只写入缓存,并且把缓存块儿标记为“脏”的。而脏块儿只有被再次使用时才会将其中的数据写入到后端存储中
写比较简单,写到缓存然后直接然后;对于读,如果命中返回,不命中,找缓存块,如果这个换存块是脏的,将数据写入后面存储,并后面存储拉取新的数据。
适合写多读少情况。
14 | 缓存的使用姿势(二):缓存如何做到高可用?
数据库部署的时候常选用的机器配置最低在8核16G以上,正常在16核32G。
一般Java应用系统部署在4核8G的机器上,每秒钟抗下500左右的并发访问量,差不多是比较合适的,当然这个也不一定。 一台机器能抗下每秒多少请求,往往是跟你每个请求处理耗费多长时间是关联的。大体上来说,根据我们大量的经验观察而言,4核8G的机器部署普通的Java应用系统,每秒大致就是抗下几百的并发访问,从每秒一两百请求到每秒七八百请求,都是有可能的,关键是看你每个请求处理需要耗费多长时间。
**一般8核16G的机器部署的MySQL数据库,每秒抗个一两千并发请求是没问题的,**但是如果你的并发量再高一些,假设每秒有几千并发请求,那么可能数据库就会有点危险了,因为数据库的CPU、磁盘、IO、内存的负载都会很高,弄不数据库压力过大就会宕机。
对于16核32G的机器部署的MySQL数据库,每秒抗个两三千,甚至三四千的并发请求也都是可以的,但是如果你达到每秒上万请求,那么数据库的CPU、磁盘、IO、内存的负载瞬间都会飙升到很高,数据库也是可能会扛不住宕机的
分布式缓存的高可用方案
- 客户端方案。就是在客户端配置多个缓存的节点,通过缓存的写入和读取算法策略来实现分布式,从而提高缓存的可用性
- 中间代理层方案。在应用和缓存节点之间增加代理层,代理层的高可用策略,会帮助提升缓存系统的高可用
- 服务器方案,Redis Sentinel (哨兵)方案
1. 客户端方案
缓存数据如何分片?
- Hash分片算法(对缓存的key做hash计算,然后对总节点数取余)
- 一致性Hash分片算法。
分片之后,保证某个节点出现问题,以它节点仍然可用,保证了一定的可用性。
15 | 缓存的使用姿势(三):缓存穿透了怎么办?
解决方案:
向缓存回种空值+使用布隆过滤器
布隆过滤器:一个超大的数组,hash之后,指定位置是0或者1,1代表又数据。节省空间
主要有两个缺陷:
- 有误判的概率(hash碰撞,可以多计个hash算法,所有的hash值都为1才可以)
- 不支持删除元素(也是给hash碰撞有关,如果删了碰撞的位置,那就删多了)
对于击穿的问题:用分布式锁,只有一个线程能到达数据库,其它线程挂起(sleep),然后再调回(reture getData() 这里面获取到数据,直接return数据了)这个方法
核心思想是:减少对于数据库的并发请求
16 | CDN:静态资源如何加速?
静态资源的关键点就是就近访问。
CDN就是将静态的资源分发到,位于多个地理位置机房中的服务器上,因此它能很好的解决就近访问的问题。
搭建CDN系统:
- 如何将用户的请求映射到CDN节点上
- 如何根据用户的地理信息选择到比较近的节点
加餐 | 数据的迁移应该如何做?
一般来说,有两种方案可以做数据库的迁移。
“双写”方案
- 将新的库配置为源库的从库,用来同步数据,如果需要将数据同步到多库多表,可以使用一些三方工具获取Binlog日志,在获取增量日志后按照分库分表写入到辛苦
- 双写。改造业务代码,数据写入的时候,不仅写入旧库,还要写入新库,可以异步写入,不过写入错误的需要记录,后续补上,保证一致性。启动双写后,新库不再监听Binlog日志。
- 校验数据,因为量太大,校验部分数据就行
- 切换读数据。可以采用灰度发布的方式切换。
- 观察几天没问题,切换写数据
级联同步方案
适合自建机房向云上迁移的场景。
- 将新库作为旧库的从库,用于数据的同步
- 在将一个备库作为新库的从库,用于数据的备份
- 等三个库写入一致后,将读流量切到新库
- 暂停业务写入,将写流量切换到新库
17 | 消息队列:秒杀时如何处理每秒上万次的下单请求?
我们大部分的优化,都停留在读的环节,我们优化数据库、缓存都是提高读的能力(绝大部分场景都是高并发的读)。但是有些情况是高并发写场景,引出了消息队列。
把消息队列看做:暂时存储数据的一个容器。
将秒杀的请求暂时放到消息队列中,然后业务服务器响应用户“秒杀结果正在计算中”。在后台启动若干个队列处理程序(有限个线程)执行
18 | 消息投递:如何保证消息仅仅被消费一次?
1. 消息丢失?
- 消息从生产者写入到消息队列过程中
- 消息在队列存储过程中
- 消息在消费过程中
1.在生产的过程中丢失消息
网络可能抖动,导致丢失,超时重传,可能导致重复
2.在消息队列中丢失消息
例如Kafka异步刷盘,如果宕机之类的。但是提高刷盘频率,太影响性能,可以通过集群多几个副本备份。建议给一个Follower发送就可以返回成功,不能是所有的,太影响性能。
3.在消费过程中消息丢失
以Kafka为例,消息的过程分为3步:接收、处理、更新消费进度。一定要等到接收和处理完之后,再更新消息。但是可能会出现重复,比如,处理完,刚好宕机,这条消息没能更新。
2. 如何保证消息只能消费一次?
宕机等不可避免,所以保证消息的消费结果幂等(结果和只消费一次等同)即可。
2.1 如何在生成和消费过程中幂等
Kafka中消息存储包含生成者id和消息id,如果同一个生产者,消息id重复,回丢失掉。
2.2 在消费端幂等
通用层:
消息生成的时候,有一个唯一id ,等消费完了,插入数据库,消费下一个的时候,查查库,看看是否消费过了。有个问题是:处理完,宕机了,没写入库,如果不是要求特别严格,可以使用,而不考虑引入事务。
业务层:
生产消息的时候,给要操作的逻辑相关数据,弄个version,使用乐观锁,将版本号和和消息一块发送,更新的时候,比较version,就不会重复了。比如转账的时候,哪个账户加钱,用乐观锁。
总结:
- 消息丢失可以通过客户端重试、消息队列集群、一级消费端合理处理来解决。
- 为了解决消息丢失常常导致性能问题(存储刷盘)及消息重复问题
- 使用幂等来解决消息重复的问题
19 | 消息队列:如何降低消息队列系统中消息的延迟?
1. 如何监控消息延迟
- 通过消息队列提供的工具,监控消息的堆积情况
- 通过生成监控消息的方式来监控消息的延迟情况(循环定时插入,往里插入时间;时间差=消息消费时间-消息生成时间),这种方式监控更加直观。
2. 减少延迟的正确姿势
需要在消费端和消息队列两个层面完成。
消费端:
提高了处理能力,处理的快了,消息延迟就低了。
- 优化消费代码,提升性能
- 增加消费者数量
不过,这两种方式受限于消息队列的实现。如果使用Kafka就无法通过增加消费者数量的方式,提升消费能力。(Kafka一个分区只能有一个消费者消费),可以通过增加分区来提高消费者数量。
如果不增加消费者,可以在一个consumer中提升处理消息的并发度。把拉取到的消息丢到线程池中处理。可以提升消息处理的吞吐量。
消息队列本身:
消息队列本身读取性能优化做了哪些事情。
- 零拷贝技术
21 | 系统架构:每秒1万次请求的系统要做服务化拆分吗?
随着功能的复杂、开发团队规模越来越大,单体架构有一些缺陷。
1.数据库连接数成为瓶颈
MySQL客户端连接数据,上限16384。如果机器太多,可能不够用
2.一体架构增加研发成本,抑制研发效率提升。《人月神话》,沟通成本和人数有关,呈指数增加。代码都在一起,配合容易出问题,一个小修改,可能影响整个项目。微服务,分模块。
3.运维影响**:单体项目太大,几十万代码,编译,单元测试、打包运行,都要花很长时间,而且小修改就要构建整个项目,不灵活
如何利用微服务解决这些痛点:
功能内聚,维护开发人员责任明确,每个系统是之前的子集,构建速度较大提升,即使出现问题,也可以熔断降级处理,不至于一个小bug响应整个项目,加上云原生的技术,部署运维很方便高效。
22 | 微服务架构:微服务化后,系统架构要如何改造?
微服务拆分的原则
- 单一服务内部功能高内聚、低耦合。
- 拆分初期可以拆的粗一些
- 一边产品迭代、一边拆分
- 优先剥离较独立的业务(如推送、发短信等)
- 优先拆分被依赖的服务
- 接口参数要具有可扩展性,以为服务间调用,你修改,别人可能也不知道,导致报错。参数封装类,大家用一个
微服务带来的问题和优化思路
- 服务通过网络调用,引入注册中心
- 服务之间有复杂的依赖关系。当出现不可用的时候,可能会雪崩。要引入服务治理(熔断、降级、限流、超时控制等方法)
- 拆分服务后,一条链路涉及很多服务。定位问题,需要引入分布式追踪工具,以及更细致的服务端监控报表。
23 | RPC框架:10万QPS下如何实现毫秒级的服务调用?
如果要提升RPC框架的性能,需要从网络传输和序列化两方面来优化
1. 如何提升网络传输性能
选择一种高性能的I/O模型。
首先,IO会经历一个等待资源的阶段,比如等待网络传输数据可用,有两种处理方式:
- 阻塞。指的是在数据不可用时,IO一直阻塞,直到数据返回。
- 非阻塞。指的是数据不可用时,IO请求立即返回,直到被通知资源可用为止。
然后是使用资源的阶段,比如从网络接收数据,拷贝到应用缓冲区里面。这时候,有两种处理方式:
- 同步处理,IO请求在读取或者写入数据时会阻塞,直到读取或者写入数据完成
- 异步处理,立即返回,当OS完成IO请求,将数据拷贝到APP缓冲区,通知应用IO请求执行完成。
使用最广泛的是:多路复用(同步非阻塞的升级、同时监控好几个通道)
2. 合理的序列化方案:
默认的采用JSON就行,如果要求高性能的话,可以考虑谷歌的Protobuf序列化协议
24 | 注册中心:分布式系统如何寻址?
注册中心的基本功能有两点:
- 提供服务地址的存储
- 当存储发生变化时,可以将变更的内容推送给客户端
25 | 分布式Trace:横跨几十个分布式组件的慢请求要如何排查?
1. 单机
可以通过requestId+打印时间,查看耗时的情况。当然这其中,有用aop,有觉着数据量太大直接requestId%10==0,有发送到mq,然后抽取到es中。
2. 分布式
跨越几个服务或者分布式组件。
分布式监控的原理:
traceId+spanId(每层+1)
将traceId 和spanId 放到请求体中,发送给下面的服务器。最终算出时间,放到消息队列,最终抽到es中,或者开源组件中。
3. 总结
追踪主要有两点:
- 代码无侵入,可以采用AOP来解决
- 性能要低损耗,控制采样的频率和采用静态代理方式(不懂)
单体或者微服务架构,需要traceId(requestId),可以在客户端生成,这样服务端+客户端都能串起来。
26 | 负载均衡:怎样提升系统的横向扩展能力?
高并发三个通用的方法:缓存、异步(消息队列)、横向扩展
1. 负载均衡的种类
- 代理类负载均衡(LVS-4层网络模型,负载更高、Nginx)
- 客户端负载均衡(内嵌在服务内,一般和注册中心连用-更灵活,获取全量的后面服务节点)
2. 常用的负载均衡策略
- 静态策略(不会参考后端服务的实际运行情况)
- 动态策略(根据后端特征-响应时间、连接数等来选择哪个节点)
有些服务器还提供了节点的故障检测功能。淘宝开源的nginx_upstream_check_module 模块,定期探测后盾服务的一个指定接口,根据状态码判断,到达阀值,移除掉。
27 | API网关:系统的门面要如何做呢?
- 入口网关
- 出口网关
1. 入口网关
部署在负载均衡服务器和应用服务器之间。主要有以下作用:
- 动态路由到不同的服务
- 可以做一些服务治理,如熔断、降级、流控
- 应用认证和授权在网关中实现
- 黑白名单的事情
- 做一些日志的事情,traceId可以在网关中生成
2. 出口网关
服务依赖于第三方,我们的出口网关位于服务和第三方服务之间,在出口网关中,对调用外部的API做统一认证、授权、审计等。
3. API网关如何实现
-
首先考虑性能—》核心在IO模型上。
-
考虑扩展性:热插拔(用责任链去做)
-
提高处理能力,加入线程池技术(避免某个服务出现问题,耗尽线程池)
- 可以针对不同的服务使用不同的线程池
- 控制服务能使用线程的最大数量
28 | 多机房部署:跨地域的分布式系统如何做?
1. 多机房含义
在不同的IDC机房中,部署多套服务,这些服务共享一份业务数据,并且都可以承受来着用户的流量。
机房之间的调用,延迟是客观存在的,都在北京几毫秒,北京上海接近30ms,北京广东50ms
1.1 不考虑城市容灾,同城双活就够了:
核心思想是:尽量避免跨机房的调用。
- A 机房部署主库+从库,B机房部署从库,A机房挂了,B的从库提升为主库
- 缓存也可以部署两个机房
- 向注册中心注册不通的服务组,尽量调用本机房的
1.2 异地多活
数据写入时候,保证只写本机房的数据存储服务,采用同步方案将数据同步到异地机房。一般有两种同步方案:
- 基于存储系统的主从复制,比如MySQL 和Redis ,一个机房部署主库,另外的机房部署从库,同步主从复制
- 基于消息队列的方式。产生写请求后,写个消息到队列,另外的机房消费,再存储
29 | Service Mesh:如何屏蔽服务化系统的服务治理细节?(todo)
主要处理服务间的通信,主要实现方式是在应用程序同主机上部署一个代理程序-Sidecar(边车),服务由直连,变成通过边车。
30 | 给系统加上眼睛:服务端监控要怎么做?
1. 监控指标如何选择
四个黄金信号:延迟、通信量、错误和饱和度
- 延迟指的是请求的响应时间。接口、访问数据库等的响应时间
- 通信量可以理解为吞吐量,单位时间请求量的大小。
- 错误表示当前系统发生的错误数量。
- 饱和度指的是服务或者资源到达上限的程度(服务或者资源的利用率),比如CPU、内存、磁盘、数据库连接等
如何采集数据指标
- Agent
- 代码埋点
监控的处理和存储
一般先将消息写入到队列来承接数据,主要作用是削峰填谷
一般会部署两个队列来处理程序,来消费队列中的数据。
- 一个接收数据,写入es,通过kibana展示,做原始查询。
- 一个经过流式处理,做些聚合 ,存储在时序数据库,通过Grafana来连接时序数据库。
一般会形成以下的报表:
- 访问趋势报表
- 性能报表。响应时间
- 资源报表
31 | 应用性能管理:用户的使用体验应该如何监控?
APM:对应用各个层面做全方位的监控,期望及时发现可能存在的问题,并加以解决,从而提升系统的性能和可用性。
32 | 压力测试:怎样设计全链路压力测试平台?
1. 如何搭建全链路压测平台
由于压力测试是在正式环境运行,所以需要区分压力测试流量和正式流量,这样可以针对压力测试流量做单独的处理。
在流量高峰,拷贝一些流量,清洗后放到HBase、MongoDB 这些NoSQL存储组件,称为为流量数据工厂。
当需要压测的时候,从这些工厂获取数据,将数据切分成多分下发到测试节点。
GoReplay 劫持流量,拷贝到流量工厂。
- 要对流量进行染色,比如在请求头标记是压测流量
2. 数据如何隔离
压测流量拷贝下来的同时,我们也需要改造系统,实现将正式流量和压测流量隔离。
压测,有些行为影响到用户的统计分析,推荐等,还有一些插入的操作。这些就需要特殊的处理
MySQL放入影子库、Redis设置不同的前缀,es放到单独索引表中。
写操作是上行流量,读操作是下行流量,都要有特殊处理
3. 压测测试如何实施
不会一次加完,缓慢加,跑跑,看看监控,有瓶颈,回退,扩容,再加。
33 | 配置管理:成千上万的配置项要如何管理?
1. 如何对配置进行管理?
主要有两种配置:
- 通过配置文件来管理
- 使用配置中心来管理
- 通过轮询的方式。对配置项整体做一个MD5,这样就不用整体拉取配置,占用宽带了,因为大部分时候,配置是没有改变的,只需要对比下MD5值就行
- 通过长连接,配置中心感知到变化后,将变更推送给客户端
34 | 降级熔断:如何屏蔽非核心系统故障的影响?
1. 雪崩是如何发生的
局部故障最终导致全局故障-雪崩。
服务调用等待服务提供方的响应时间变长,它的资源被耗尽,发生级联反应,引发雪崩
- 服务提供者不可用
- 重试加大流量
- 服务调用者不可用
当服务调用者使用 同步调用 时, 会产生大量的等待线程占用系统资源. 一旦线程资源被耗尽,服务调用者提供的服务也将处于不可用状态, 于是服务雪崩效应产生了.
熔断指:返回错误或者超时的次数超过一定阀值,后续的请求不在发想远程服务而暂时返回错误。有限状态机,关键是三种状态转换。
- 关闭(调用远程服务)
- 半打开(尝试调用远程服务)
- 打开(返回错误;定期探测服务是否恢复)
打开状态到半打卡,超过一定阀值,才关闭
2. 降级机制怎么做?
相比熔断来讲,降级是一个更大的概念。站在整体系统负载的角度上,放弃部分非核心功能或者服务,保证整体的可用性,是一种有损的系统容错方式。
开关降级是在代码预设一些“开关”,用来控制服务调用的返回值。可以通过配置中心实现。降级的是非核心业务。
- 对于读取操作,可以只读取缓存,而不读取数据库,缓解数据库压力
- 非核心的写数据场景,可以转成异步写
35 | 流量控制:高并发系统中我们如何操纵流量?
1. 究竟什么是限流
限流指的是通过限制到达系统的最大并发请求数据量,保证系统能够正常响应,对于超过限制的流量,拒绝保证系统整体的可用性。一般部署在入口,如API网关。
- 对系统每分钟处理多少请求做限制(qps)
- 单个接口设置每分钟流量的限制
- 限制单个IP、用户ID或者设备ID一段时间内发送请求的数量
- 对于服务多个三方的开放平台,限制appKey的访问速率
2. 限流算法
2.1 固定窗口
简单记录1分钟内的请求,超过了就不让请求了。有个缺陷:无法限制短时间之内的集中流量。限制100,前10秒100次了,后面50秒就不用用了。
2.2 滑动时间窗口
原理:将时间的窗口划分为多个小窗口,每个小窗口中都有独立的请求计数。
假如在将1s划分成5份,那么在1s-1.2s之间来了一次请求,那么就算的区间就是0.2-1.2这个区间的总请求量。
解决了窗口边界的大流量问题,但是无法限制短时间内的集中流量,也就是说流量控制不能更平滑。
2.3 漏桶算法
漏桶算法:在流量产生端和接收端增加一个漏桶,流量会进去和暂存到漏桶里,漏桶出口会按照一个固定速率将流量漏到接收端(服务端。)
如果流入的流量某段时间馁大增,超过漏桶能承载的极限,触发 限流策略,被拒绝服务。
一般会使用消息队列作为漏桶的实现。思想和削峰填谷类似。
2.4 令牌桶算法
- 如果我们需要在1s内限制访问N次,那么每隔1/N秒的时间,往令牌桶放一个令牌。
- 请求需要先从令牌桶获取一个令牌,如果没有,就需要等待或者直接拒绝服务
- 令牌桶有一个总数,超过了就不能再放了,限制了令牌的总数。
令牌桶能面对一次突发流量,直接拿完令牌桶的令牌去请求。
漏桶算法能够强行限制数据的传输速率,令牌桶算法能够在限制数据的平均传输速率的同时还允许某种程度的突发传输
在开发高并发系统时有三把利器用来保护系统:缓存、降级和限流
37 | 计数系统设计(一):面对海量数据的计数器要如何做?
计数的话,刚开始使用MySQL,如果要求速度快,使用Redis,考虑到存储成本的问题,可以对原生Redis做一些改造
- 原生的Redis的Key是String类型,一个8字节的Long类型,需要28个字节空间,采用Long存储,只要8字节
- 去除原生Redis多余的指针,如果记录时间戳,次数等信息
一些比较老的数据,落盘
38 | 计数系统设计(二):50万QPS下如何设计未读数系统?
避免操作全量数据未读数。为没一个用户存储一个时间戳,点了共享的数据,时间戳更新为现在的,不再显示标记,如红点。以后比较时间戳和共享数据的时间戳就行了,看看有没有新数据。