高并发系统设计的总结
高并发系统设计
高并发系统设计的三个通用方法:缓存、异步和横向扩展
一、基础篇
1、高并发系统设计的三大目标:高性能、高可用、可扩展
2、性能优化原则
- 实际问题为导向
- 性能优化也遵循“八二原则”
- 性能优化需要数据支撑
- 性能优化的过程是持续的
3、性能的度量指标
- 平均值
- 最大值
- 分位值
4、高并发下的性能优化
- 提高系统的处理核心数
- 减少单次任务响应时间(CPU密集型或IO密集型)
5、高可用系统设计的思路
- 系统设计(failover(故障转移)、超时控制、降级和限流)
- 系统运维(灰度发布、故障演练)
6、高可扩展性的设计思路——拆分(将复杂的问题简单化)
- 存储层的扩展性
- 业务层的扩展性
二、数据库篇
1、池化技术(空间换时间,期望使用预先创建好的对象来减少频繁创建对象的性能开销,同时还可以对对象进行统一的管理,降低了对象的使用的成本)
- 用连接池预先建立数据库连接
2、查询请求增加可用——主从读写分离
- 主从复制(一般一个主库最多挂 3~5 个从库,主从的延迟是一个关键的监控指标)
- 如何访问数据库 (使用据库中间件,如Mycat、ShardingSphere)
3、写入数量请求增加可用——分库分表
分库分表是一种常见的将数据分片的方式,它的基本思想是依照某一种策略将数据尽量平均的分配到多个数据库节点或者多个表中。
- 数据库做垂直拆分——垂直拆分的原则一般是按照业务类型来拆分,核心思想是专库专用,将业务耦合度比较高的表拆分到单独的库中,比如:用户库、内容库
- 数据库做水平拆分——水平拆分指的是将单一数据表按照某一种规则拆分到多个数据库和多个数据表中,关注点在数据的特点。常见拆分规则:
- 按照某一个字段的哈希值做拆分,这种拆分规则比较适用于实体表,比如说用户表,内容表,我们一般按照这些实体表的 ID 字段来拆分
- 另一种比较常用的是按照某一个字段的区间来拆分,比较常用的是时间字段。
4、分布式场景下分库分表后的发号器
比如,基于 Snowflake 算法搭建发号器,当然也有其他方案,可以参考之前的博客——浅谈分布式ID生成方案。
三、缓存篇
1、缓存分类
- 缓存主要就是静态缓存、分布式缓存和热点本地缓存这三种。
2、缓存的读写策略
- Cache Aside(旁路缓存)策略,分读策略和写策略(对命中率有要求,1、分布式锁,2、缓存过期时间)
- Read/Write Through(读穿 / 写穿)策略
3、缓存的高可用
- 客户端
- 中间代理层
- 服务端(Redis高可用,哨兵模式和集群模式)
4、缓存穿透
- 回种空值和布隆过滤(布谷鸟过滤)
5、缓存击穿
5.1 什么是击穿
缓存击穿是我们可能遇到的第二个使用缓存方案可能遇到的问题。
在平常高并发的系统中,大量的请求同时查询一个 key 时,此时这个key正好失效了,就会导致大量的请求都打到数据库上面去。这种现象我们称为缓存击穿。
5.2 会带来什么问题
会造成某一时刻数据库请求量过大,压力剧增。
5.3 如何解决
上面的现象是多个线程同时去查询数据库的这条数据,那么我们可以在第一个查询数据的请求上使用一个 互斥锁来锁住它。
其他的线程走到这一步拿不到锁就等着,等第一个线程查询到了数据,然后做缓存。后面的线程进来发现已经有缓存了,就直接走缓存。
6、缓存雪崩
6.1 什么是缓存雪崩
缓存雪崩的情况是说,当某一时刻发生大规模的缓存失效的情况,比如你的缓存服务宕机了,会有大量的请求进来直接打到DB上面。结果就是DB 称不住,挂掉。
6.2 解决办法
6.2.1 事前:
-
使用集群缓存,保证缓存服务的高可用
这种方案就是在发生雪崩前对缓存集群实现高可用,如果是使用 Redis,可以使用 主从+哨兵 ,Redis Cluster 来避免 Redis 全盘崩溃的情况。
6.2.2 事中:
-
ehcache本地缓存 + Hystrix限流&降级,避免数据库被打死
使用 ehcache 本地缓存的目的也是考虑在 Redis Cluster 完全不可用的时候,ehcache 本地缓存还能够支撑一阵。
使用 Hystrix进行限流 & 降级 ,比如一秒来了5000个请求,我们可以设置假设只能有一秒 2000个请求能通过这个组件,那么其他剩余的 3000 请求就会走限流逻辑。
然后去调用我们自己开发的降级组件(降级),比如设置的一些默认值呀之类的。以此来保护最后的数据库不会被大量的请求给打死。
6.2.3 事后:
-
开启Redis持久化机制,尽快恢复缓存集群
一旦重启,就能从磁盘上自动加载数据恢复内存中的数据。
四、消息队列篇
1、MQ作用
- 异步处理
- 模块解耦
- 削峰填谷
2、MQ保证消息不丢失
- 生成端重试
- MQ服务端配置集群模式(Kafka,ISR),RabbitMQ类似做高可用,同步。(想保证高可用保证不丢失,最直观的就是制造冗余,多做备份,数据互备)
- 消费端合理处理消费进度
3、消息重复处理
- 保证消息处理的幂等性(通用层,业务层)
- 通用层:生成唯一ID
- 业务层:乐观锁,version号
4、减少消息延迟
- 消费端
- 优化消费代码提升性能(多线程,一次性获取多条消息进行处理)
- 增加消费者数量(根据MQ的不同来定)
- 消息队列(MQ中间件设计配置的优化)
五、分布式服务篇
1、微服务拆分原则
- 单一服务内部功能的高内聚和低耦合
- 关注服务拆分的粒度,先粗略拆分,再逐渐细化
- 拆分的过程,要尽量避免影响产品的日常功能迭代
- 服务接口的定义要具备可扩展性
2、服务拆分带来问题
- 引入配置中心、服务治理以及分布式追踪工具
- 分布式事务
3、注册中心(Consul、Eureka、Zookeeper、etcd等)
- 服务状态管理(心跳机制)
4、分布式Trace(traceId+spanId,如:Zipkin,Jaeger)
5、负载均衡
1、分类
- 代理类负载均衡服务(LVS、Nginx)【QPS在10万以内,可以直接使用Nginx,不用考虑引入LVS】
- 客户端负载均衡服务(配合注册中心使用)
2、常见的负载均衡策略
- 静态策略(轮询、权重),常见的Nginx 提供了 ip_hash 和 url_hash 算法;LVS 提供了按照请求的源地址,和目的地址做 hash 的策略;Dubbo 也提供了随机选取策略,以及一致性 hash 的策略。
- 动态策略,常见的Dubbo 提供的 LeastAcive 策略,就是优先选择活跃连接数最少的服务;Spring Cloud 全家桶中的 Ribbon 提供了 WeightedResponseTimeRule 是使用响应时间,给每个服务节点计算一个权重,然后依据这个权重,来给调用方分配服务节点。
6、网关
1、分类
- 入口网关
- 它提供客户端一个统一的接入地址,API 网关可以将用户的请求动态路由到不同的业务服务上,并且做一些必要的协议转换工作。
- 在 API 网关中,我们可以植入一些服务治理的策略,比如服务的熔断、降级,流量控制和分流等等。
- 客户端的认证和授权的实现,也可以放在 API 网关中。
- API 网关还可以做一些与黑白名单相关的事情,比如针对设备 ID、用户 IP、用户 ID 等维度的黑白名单。
- 当然在 API 网关中也可以做一些日志记录的事情,比如记录 HTTP 请求的访问日志。
- 出口网关,我们在系统开发中,会依赖很多外部的第三方系统,比如典型的例子:第三方账户登录、使用第三方工具支付等等。我们可以在应用服务器和第三方系统之间,部署出口网关,在出口网关中,对调用外部的 API 做统一的认证、授权,审计以及访问控制。
2、实现
zuul(采用责任链模式,zuul1和zuul2设计有不太相同,1.0采用IO阻塞模型,2.0采用IO多路复用模型,使用Netty构建),kong、Tyk等。
六、维护篇
1、服务监控(延迟,通信量、错误和饱和度)
2、如何采集数据指标(Agent、埋点、日志)
常见方案:Agent,比如使用 JMX,监控 Kafka 队列的堆积数,再比如,你也可以通过 JMX 监控 JVM 内存信息和 GC 相关的信息。埋点,这个方式与 Agent 的不同之处在于,Agent 主要收集的是组件服务端的信息,而埋点则是从客户端的角度,来描述所使用的组件,和服务的性能和可用性,主要在代码中埋点。日志,Apache Flume、Fluentd、Filebeat等
3、监控数据的处理和存储
一般会经过MQ然后回流到储存层。
一个处理程序接收到数据后,把数据写入到 Elasticsearch,然后通过 Kibana 展示数据,这份数据主要是用来做原始数据的查询;
另一个处理程序是一些流式处理的中间件,比如,Spark、Storm。它们从消息队列里,接收数据后会做一些处理。
4、应用性能管理(APM系统,应用性能管理(Application Performance Management,简称 APM))
5、配置中心
配置中心可以算是微服务架构中的一个标配组件了。业界也提供了多种开源方案供你选择,比较出名的有携程开源的 Apollo,百度开源的 Disconf,360 开源的 QConf,Spring Cloud 的组件 Spring Cloud Config 等等。
如何保证配置中心高可用——配置中心“旁路化”。
6、降级和熔断机制
-
降级(开关降级、限流降级)
限流是一种常见的服务保护策略,你可以在整体服务、单个服务、单个接口、单个 IP 或者单个用户等多个维度进行流量的控制;
令牌桶算法和漏桶算法则能够塑形流量,让流量更加平滑,但是令牌桶算法能够应对一定的突发流量,所以在实际项目中应用更多。
-
熔断(服务治理中的熔断机制指的是在发起服务调用的时候,如果返回错误或者超时的次数超过一定阈值,则后续的请求不再发向远程服务而是暂时返回错误。)
七、实战篇
1、如何设计海量数据的计数器?
- 数据库 + 缓存的方案是计数系统的初级阶段,完全可以支撑中小访问量和存储量的存储服务。如果你的项目还处在初级阶段,量级还不是很大,那么你一开始可以考虑使用这种方案。
- 通过对原生 Redis 组件的改造,我们可以极大地减小存储数据的内存开销。
- 使用 SSD+ 内存的方案可以最终解决存储计数数据的成本问题。这个方式适用于冷热数据明显的场景,在使用时需要考虑如何将内存中的数据做换入换出。
2、50万QPS下如何设计未读数系统?
- 评论未读、@未读、赞未读等一对一关系的未读数可以使用通用的计数方案来解决;
- 在系统通知未读、全量用户打点等存在有限的共享存储的场景下,可以通过记录用户上次操作的时间或者偏移量,来实现未读方案;
- 信息流未读方案最为复杂,采用的是记录用户博文数快照的方式。
3、通用信息流系统的推模式要如何做?
- 推模式就是在用户发送信息时,主动将信息写入到他的粉丝(关联用户)的收件箱中;
- 推送信息是否延迟、存储的成本、方案的可扩展性以及针对取消关注和信息删除的特殊处理是推模式的主要问题;
- 推模式比较适合粉丝(关联用户)数有限的场景。
4、通用信息流系统的拉模式要如何做?
- 在拉模式下,我们只需要保存用户的发件箱,用户的信息流是通过聚合关注者发件箱数据来实现的;
- 拉模式会有比较大的聚合成本,缓存节点也会存在带宽的瓶颈,所以我们可以通过一些权衡策略尽量减少获取数据的大小,以及部署缓存副本的方式来抗并发;
- 推拉结合的模式核心是只推送活跃的粉丝用户,需要维护用户的在线状态以及活跃粉丝的列表,所以需要增加多余的空间成本来存储,这个你需要来权衡。
参考
- 阿里一面:关于【缓存穿透、缓存击穿、缓存雪崩、热点数据失效】问题的解决方案
推荐阅读
-
专为物联网(IoT)设计的操作系统 TinyOS
-
电源系统优化设计中,低压差稳压器(LDO)的类型如何选择?
-
60岁拿2000元创业,靠 "神 "做生意,如今年赚24亿--如今的天堂伞集团,无论是口碑还是销量,都是业内首屈一指的 "大牛"。王斌章的一把伞,仅去年的销售额就实现了近6亿元的好成绩。雨伞销售更是占据了中国所有雨伞行业的最大份额。可以说,如今的天堂伞,无论是质量还是口碑,都堪称行业翘楚。 一把天堂伞,如何在王斌章手中打出新高度,玩出大生意?总结起来,两点制胜法宝:一是质量绝对保证,二是服务有保障。这看似很简单,但几十年来不折不扣地执行,特别是在保护伞这个不太重要的对象上,想要做到始终如一,难度很大。这也是为什么中国只有一个王斌章被称为 "全球伞王 "的原因。 天堂伞成立之初,销售场地选在附近的一个广场,以摆摊的形式销售王斌章亲手制作的杭州伞。凭借几十年的手艺和严谨的态度,加上上乘的伞料,即使价格比普通伞高3倍5倍,也打开了市场,积累了第一批人气和资金。
-
腾讯二次元专访:如何保证接口的idempotency?如何实现高并发下的接口idempotency?
-
高并发分布中的 MQ 消息重传惰性解决方案
-
高并发性的接口幂等性解决方案
-
如何确保高并发下的接口惰性?
-
分布式系统中的等效设计
-
基于 Matlab 卷积神经网络的深度学习验证码识别系统 - IV.总结
-
基于深度学习的 SAR 图像船舶检测方案设计-IV.总结