当前位置: 首页 > 后端技术 > Java

阿里P8面试官:如何设计一个能处理千万级并发的架构(超详细)-续

时间:2023-04-01 19:45:15 Java

在上一篇文章中详细分析了设计一个千万级并发需要考虑的问题体系结构,以及如何解决它们的计划。在这篇文章中,我们主要分析如何在工作场所用户数量充足的情况下,同时提升架构的性能和降低平均响应时间。如何降低RT的值继续看上图,一个request只有在tomcat容器中的应用执行后才能返回,而request在执行过程中会做什么呢?查询数据库,访问磁盘数据,执行内存操作,调用远程服务。这些操作的每一步都会消耗时间。只有这些操作完成后才能返回当前客户端请求。因此,降低RT的方法就是优化业务逻辑的处理。数据库瓶颈优化当18000个请求进入服务器并接收到时,开始业务逻辑处理,必须查询数据库。每个请求至少有一次查询数据库的操作,大部分需要查询3~5次以上。如果我们假设按照3次计算,那么每秒会向数据库发出54000次请求。假设数据库服务器每秒支持10,000个查询。request(影响数据库请求次数的因素有很多,比如数据库表的数据量,数据库服务器本身的系统性能,查询语句的复杂度),那么6个数据库服务器就是需要支持每秒10,000个请求。此外,还涉及数据库层面的其他优化方案。首先是Mysql最大连接数的设置。您可能遇到过诸如MySQL:ERROR1040:Toomanyconnections之类的问题。原因是访问量太大,连接数耗尽。显示像“%max_connections%”这样的变量;如果服务器有大量的并发连接请求,建议增加这个值来增加并发连接数。当然,这是建立在机器支持的基础上的,因为如果连接数增加了,因为MySQL会为每个连接提供一个连接缓冲区,占用的内存会更多,所以这个值要适当调整,并且不应盲目增大设定值。数据表的数据量太大,比如几千万甚至上亿。在这种情况下,SQL优化就没有意义了,因为数据量这么大的查询,必然会涉及到计算。可以做缓存,解决读请求高并发的问题。一般来说,数据库的读写请求也遵循2/8规则。在每秒54,000个请求中,大约有43,200个是读取请求。这些读请求中的大部分90%都可以通过缓存来解决。分库分表,减少单表数据量。如果单表数据量小,查询性能自然会得到有效提升。读写分离,避免事务操作对查询操作的性能影响。写入操作本身会消耗资源和数据库写入。操作是IO写入。写入过程通常涉及唯一性验证、索引构建、索引排序等操作,消耗大量资源。写操作的响应时间往往是读操作的几倍甚至几十倍。锁争用写操作往往需要锁,包括表级锁、行级锁等,这些锁就是排它锁。一个session占用排他锁后,其他session无法读取数据,会很严重。对数据读取性能影响大。因此,MYSQL部署往往采用读写分离。主库用于写入数据和一些时效性要求高的读操作,而从库承担大部分的读操作,这样可以大大提高数据库的整体性能。不同类型的数据使用不同的存储库,MongoDBnosql文档化存储Redisnosqlkey-value存储HBasenosql,列式存储,其实本质上有点类似于key-value数据库。Cassandra,Cassandra是Apache的分布式数据库。它具有高度可扩展性,可用于管理大量结构化数据。TIDB是PingCAP自主设计开发的开源分布式关系型数据库。它是一个同时支持在线交易的数据库。处理和在线分析处理(HybridTransactionalandAnalyticalProcessing,HTAP)集成的分布式数据库产品。为什么将mysql数据库中的数据放在redis缓存中可以提高性能?Redis以k-v格式存储数据。时间复杂度为O(1),常量阶,而mysql引擎底层实现为B+TREE,时间复杂度为O(logn),为对数阶。Redis会比Mysql快一点点。Mysql的数据存储是存储在表中的。查找数据时,必须先全局扫描表或者根据索引查找。这就涉及到磁盘搜索。如果磁盘搜索是单点搜索,可能会更快,但顺序搜索会更慢。而redis就不用那么麻烦了。本身是存储在内存中的,会根据数据在内存中的位置直接取出来。Redis是单线程多路复用IO。单线程避免了线程切换的开销,而多路IO避免了IO等待的开销。在多核处理器下,可以提高处理器的使用效率来分区数据。然后每个处理器处理不同的数据。池化技术减少了频繁创建数据库连接带来的性能损失。在每次数据库操作之前,先建立连接,然后进行数据库操作,最后释放连接。这个过程涉及到网络通信的延迟,频繁创建连接对象和销毁对象的性能开销等,当请求量很大时,由此带来的性能影响非常大。磁盘数据访问优化对于磁盘的操作,无非就是读写。例如,对于交易系统的场景,一般会设计对账文件的解析和写入。至于磁盘的操作,优化的方法无非就是磁盘的pagecache。借助缓存I/O,可以充分利用系统缓存,减少实际I/O次数。顺序读写可以用追加写入代替随机写入,减少寻址开销,加快I/O写入。SSD取代HDD,固态硬盘的I/O效率远高于机械硬盘。当需要频繁读写同一个磁盘空间时,可以使用mmap(内存映射)代替read/write,减少内存拷贝次数。在需要同步写入的场景下,尽量合并写入请求,而不是让每个请求都同步写入磁盘,即可以使用fsync()代替O_SYNC来充分利用内存充分利用内存缓存,以及将一些经常访问的数据和对象存放在内存中,避免重复加载或访问数据库造成的性能损失。调用远程服务调用远程服务时,有几个因素会影响IO性能。阻塞等待返回结果的远程调用异步通信网络通信需要时间内网通信增加网络带宽远程服务通信的稳定性耗尽,无法创建更多的线程来处理请求。针对这种情况的优化,除了不断的程序优化(数据库调优、算法调优、缓存等)之外,可以考虑在架构上做一些调整,先把结果返回给客户端,让用户可以继续使用使用客户端在服务器端进行其他操作,然后在服务器端异步处理复杂的逻辑处理模块。这种异步处理方式适用于客户端对处理结果不敏感,不需要实时性的情况,比如群发邮件、消息等。异步设计的解决方案:多线程、MQ。除了上述拆分应用服务的手段外,还需要将业务系统拆分成微服务。原因是随着业务的发展,应用本身的复杂度会不断增加,熵也会增加。.业务系统的功能越来越多,参与开发迭代的人也越来越多。当多人维护一个非常大的项目时,很容易出现问题。单个应用系统难以实现横向扩展,而且由于服务器资源有限,所有请求都集中在某个服务器节点上,导致资源消耗过大,导致系统不稳定,测试、部署成本越来越高,higher....其实最终的还是很难突破单体应用的性能瓶颈。也就是说,如果我们要支持18000QPS,单个服务节点是肯定支持不了的。因此,服务拆分的优点是可以由多个计算机阶段组成。一个大规模的分布式计算网络通过网络通信完成一套完整的业务逻辑。服务如何拆分服务如何拆分,这个问题看似简单,很多同学都会说,直接按业务拆分。但是,在实际实现过程中,你会发现拆分存在一些边界问题。例如,某些数据模型可以有A模块或B模块。这时候怎么分呢?另外,服务拆分的粒度应该怎么划分?一般来说,服务的拆分是按照业务来实现的,然后基于DDD来指导微服务的边界划分。领域驱动是一组方法论。采用领域驱动设计方法论定义领域模型,从而确定业务边界和应用边界,保证业务模型和代码模型的一致性。无论是DDD还是微服务,都必须遵循软件设计的基本原则:高内聚、低耦合。服务之间具有高内聚性和低耦合性。事实上,一个领域服务对应了一组功能,而这些功能必然有一些共性。比如订单服务,字段边界越清晰,功能之间的内聚性就越高,创建订单、修改订单、查询订单列表等服务之间的耦合度就会越低。服务拆分也需要根据技术团队和公司目前的状态来做。如果你是初创团队,没必要太追求微服务,否则会导致业务逻辑过于分散,技术架构负载过大,团队基础设施不够完善,会拉长整个交货时间,影响公司的发展。会产生更大的影响。因此,在做服务拆分时需要考虑几个因素。公司业务领域目前的市场性质,如果是市场比较敏感的项目,前期应该先出来,然后再迭代优化。开发团队的成熟度,团队技术能否承接。基础能力是否足够,比如DevOps、运维、测试自动化等基础能力。团队是否有能力支撑大量服务实例运行带来的运维复杂度,能否很好地监控服务。测试团队的执行效率。如果测试团队不能支持自动化测试、自动回归、压力测试等手段来提高测试效率,势必导致测试工作量的大幅增加,导致项目上线周期的延迟。如果是对一个旧系统进行改造,可能会涉及到更多的风险和问题,所以在开始改造之前,需要考虑几个步骤:拆分前准备阶段,设计拆分改造方案,实施拆分方案拆分前,梳理一下目前的整个架构,以及各个模块的依赖关系,接口准备阶段主要是梳理一下依赖关系和接口,然后就可以考虑怎么拆了。第一个切入点在哪里,也就是可以快速将一个复杂的单系统变成两个更小的系统的目标,对系统现有业务的影响最小。尽量避免构建分布式单体应用程序,即所谓的分布式系统,其中包含大量必须部署在一起的紧密耦合的服务。如果在没有分析清楚的情况下强行拆除,可能会不慎切断大动脉,立即造成A级重大故障,后患无穷。不同阶段分裂的关键点不同。每个阶段的重点都要突出。第一阶段,对核心业务进行瘦身,对非核心部分进行裁减,减少需要处理的系统规模;第二阶段。根据微服务重新设计核心业务部分;第三阶段,将实施核心业务部分的重构设计。拆分的方式也有代码拆分、部署拆分、数据拆分三种。另外,每个阶段都需要专注于一两个具体的目标,否则目标太多,很难把一件事做好。例如某系统的微服务拆分制定了如下目标:性能指标(吞吐量和延迟):核心事务吞吐量提升一倍以上(TPS:1000->10000),A业务延迟降低half(Latency:250ms->125ms),B服务延迟减半(Latency:70ms->35ms)。稳定性指标(可用性、故障恢复时间):可用性>=99.99%,A类故障恢复时间<=15分钟,季度频率<=1次。质量指标:编写完善的产品需求文档、设计文档、部署维护文档,核心事务代码单测覆盖率90%以上,自动化测试用例和场景覆盖率100%,实现可持续的性能测试基准环境和长-term持续性能优化机制。可扩展性指标:完成代码、部署、运行时、数据多个维度的合理拆分。对于核心系统重构后的各个业务和交易模块,以及相应的数据存储,可以随时通过添加机器资源实现伸缩扩展。可维护性指标:建立完善的监控指标,特别是全链路实时性能指标数据,覆盖所有关键业务和状态,缩短监控告警的响应和处置时间,配合运维团队实现容量规划和管理。系统可以在一分钟内拉起或回滚到最后一个可用版本(启动时间<=1分钟)。易用性指标,通过重构,新的API接口合理简洁,极大地满足了各层次用户的使用和需求,客户满意度持续上升。业务支持指标:对于新业务需求和功能的开发,在保证质量的前提下,开发效率翻倍,开发资源和周期减半。当然,不要指望一次性完成所有目标。您可以在每个阶段选择一个或两个高优先级的目标执行。微服务架构带来的问题微服务架构首先是分布式架构,其次我们需要对外暴露和提供业务服务能力,然后我们需要围绕这些业务能力考虑各种非功能性能力。这些分散的服务本身需要被管理,并且对服务调用者是透明的,所以就有了服务注册和发现的功能需求。同样,每个服务可能会部署多台机器和多个实例,所以我们需要路由和寻址能力来做负载均衡,提高系统的可扩展性。对外提供了这么多不同的服务接口,我们也需要一个机制来对它们进行统一的访问控制,把一些非业务的策略实现到这个访问层,比如权限相关的,这就是服务网关。同时我们发现,随着业务的发展和一些具体的运营活动,比如秒杀推广,流量会增加十倍以上。这时候就需要考虑系统容量,服务之间的强弱依赖,做服务降级,Fuse,系统过载保护等措施。由于微服务带来的复杂性,应用配置和业务配置分散在各处,因此分布式配置中心的需求也应运而生。最后,系统去中心化部署后,所有的调用都是跨进程的。我们还需要一套可以在线进行链路跟踪和性能监控的技术,帮助我们及时了解系统内部的状态和指标,以便我们对系统进行分析和干预。整体架构图基于以上从微观到宏观的整体分析,我们基本可以设计出整体架构图。接入层,外部请求和内部系统之间的网关,所有的请求都要经过api网关。应用层,也叫聚合层,为相关业务提供聚合接口,调用中台服务进行组装。中台服务也是业务服务层,以业务为纬度提供与业务相关的接口。中台的本质是为整个架构提供复用能力。比如古炮云课堂和Gper社区都需要评论系统。这时候评论系统不能再和云课堂或者Gper耦合,为了设计的复用性更好。社区定制化需求,所以作为一个设计评审平台,没有必要做很深入的思考,如何提供一个可以针对不同场景复用的能力。你会发现当这个服务作为一种机制来实现的时候,它就变成了一个baaS服务。服务商为客户(开发者)提供一体化的云后端服务,如提供文件存储、数据存储、推送服务、身份验证服务等功能,帮助开发者快速开发应用。#理解什么是高并发,总结什么是高并发。高并发没有具体的定义。高并发主要描述突发流量大的场景。如果在面试的时候或者实际工作中,你的领导或者面试官问你如何设计一个系统来处理千万级别的流量,你就按照我说的方法一一分析。需要形成可量化的数据指标,比如QPS、DAU、总用户数、TPS、访问峰值等。针对这些数据情况,开始设计整个架构方案,然后在高并发下实现宏指标。一个满足高并发的系统并不是一味的追求高性能至少需要三个宏观层面的目标:高性能,性能体现的是系统的并行处理能力,在有限的硬件投入下,提高性能就意味着节约成本。同时,性能也体现了用户体验。响应时间分别为100毫秒和1秒,给用户带来完全不同的感受。高可用性是指系统可以正常服务的时间。一是全年无休,无故障;另一个时不时出现事故和停机,用户必须选择前者。另外,如果系统只能做到90%可用,也会极大地拖累业务。高扩展是指系统的扩展能力,是否能在流量高峰时短时间内完成扩展,能更顺畅地应对高峰流量,如双十一活动、明星离婚等热点事件。微观指标性能指标可以通过性能指标衡量当前存在的性能问题,同时作为性能优化的评价依据。一般来说,以一段时间内的界面响应时间作为指标。1、平均响应时间:最常用,但缺陷很明显,对慢请求不敏感。比如有10000个请求,其中1ms有9900个,100ms有100个,那么平均响应时间就是1.99ms。虽然平均耗时只增加了0.99ms,但1%的请求响应时间却增加了100倍。2、TP90、TP99等分位数值:将响应时间从小到大排序,TP90代表第90个百分位的响应时间。分位数越大,对慢速请求越敏感。可用性指标高可用性意味着系统具有很高的无故障运行能力。可用性=平均故障时间/系统总运行时间。一般用几个9来描述系统的可用性。对于高并发系统,最基本的要求是:保证3个9或者4个9。原因很简单。如果你只能做到两个9,那就意味着有1%的失败时间。像一些年GMV或者营收超过1000亿的大公司,1%就是10亿的业务影响。ScalabilityIndex面对突发流量,临时修改架构是不可能的。最快的方法是通过增加机器来线性增加系统的处理能力。对于业务集群或者基础组件,可扩展性=性能提升率/机器增加率,理想的可扩展性是:资源增加几倍,性能增加几倍。一般来说,扩容应该保持在70%以上。但是从高并发系统的整体架构来看,扩容的目标不仅仅是将服务设计成无状态的,因为当流量增加10倍时,业务服务可以快速扩容10倍,但是数据库可能成为新的瓶颈。像MySQL这样的有状态存储服务通常在扩展方面存在技术困难。如果没有提前规划好架构(纵横拆分),就会涉及到大量数据的迁移。因此,高可扩展性需要考虑:服务集群、数据库、缓存和消息队列等中间件、负载均衡、带宽、依赖的第三方等。当并发量达到一定水平时,以上每一个因素都可能变得可扩展。瓶颈点。实用方案scale-up的一般设计方法,其目标是提高单机的处理能力,方案包括:1.提高单机的硬件性能:通过增加内存,CPU核心数,存储容量,或将磁盘升级为SSD和其他堆硬件来提高。2、提高单机软件性能:使用缓存减少IO次数,使用并发或异步的方式提高吞吐量。水平扩展(scale-out)因为单机的性能永远是有限的,所以最终需要通过集群部署引入水平扩展,进一步提升并发处理能力,包括以下两个方向:1.做一个好的jobinlayeredarchitecture:这是Advance横向扩展,因为高并发系统往往业务复杂,复杂的问题可以通过分层处理来简化,更容易实现横向扩展。2、各层横向扩展:无状态横向扩展,有状态分片路由。业务集群通常可以设计成无状态的,而数据库和缓存通常是有状态的。因此,需要为存储分片设计分区键。当然也可以通过主从同步和读写分离来提高读性能。高性能实践方案1.集群部署,通过负载均衡降低单机压力。2、多级缓存,包括静态数据的CDN、本地缓存、分布式缓存等的使用,以及缓存场景下热键的处理、缓存穿透、缓存并发、数据一致性等。3、分库分??表和索引优化,借助搜索引擎解决复杂的查询问题。4、考虑使用NoSQL数据库,如HBase、TiDB等,但团队必须熟悉这些组件,并有较强的运维能力。5.异步,通过多线程、MQ,甚至是延迟任务,对二级进程进行异步处理。6、限流需要考虑业务是否允许限流(比如允许秒杀场景),包括前端限流、Nginx接入层限流、服务端限流。7、流量削峰填谷,通过MQ承接流量。8.并发处理,通过多线程将串行逻辑并行化。9、预计算,比如抢红包的场景,可以提前计算好红包的数量并缓存起来,发红包的时候直接使用。10、缓存预热,通过异步任务将数据提前预热到本地缓存或分布式缓存。11、减少IO次数,比如数据库和缓存的批量读写,RPC批量接口支持,或者通过冗余数据killRPC调用。12、减少IO时数据包的大小,包括使用轻量级的通信协议、合适的数据结构、去除接口中的冗余字段、减小缓存键的大小、压缩缓存值。13、程序逻辑优化,如前置大概率阻塞执行过程的判断逻辑,优化For循环的计算逻辑,或采用更高效的算法。14、各种池化技术的使用和池大小设置,包括HTTP请求池、线程池(设置核心参数考虑CPU密集型还是IO密集型)、数据库和Redis连接池等15、JVM优化,包括大小新生代和老年代,GC算法的选择等,尽可能降低GC频率和时间消耗。16、锁的选择,对于读多写少的场景使用乐观锁,或者考虑使用分段锁来减少锁冲突。高可用实践方案1、对端节点故障转移,Nginx和服务治理框架都支持一个节点故障后访问另一个节点。2、非对等节点的故障转移,通过心跳检测实现主从切换(如redis哨兵模式或集群模式,mysql主从切换等)。3.接口层面的超时设置、重试策略和幂等设计。4、降级处理:保证核心服务,牺牲非核心服务,必要时熔断;或者当核心链接出现问题时,还有一个替代链接。5、限流处理:对超过系统处理能力的请求直接拒绝或返回错误码。6、MQ场景下的消息可靠性保证,包括producer端的retry机制,broker端的持久化,consumer端的ack机制。7、灰度发布,可支持小流量按机器维度部署,观察系统日志和业务指标,运行稳定后全量推送。8、监控告警:完善的监控系统,包括对CPU、内存、磁盘、网络最基本的监控,以及对Web服务器、JVM、数据库、各种中间件、业务指标的监控。9、容灾演练:类似于现在的“混沌工程”,对系统进行一些破坏性的手段,观察局部故障是否会导致可用性问题。高可用方案主要从冗余、权衡、系统运维三个方向考虑。同时需要有配套的职责机制和故障处理流程。在线出现问题时,可以及时跟进。高扩展实践方案1.合理的分层架构:比如上面提到的互联网最常见的分层架构,根据数据访问层和业务逻辑层对微服务进一步细粒度分层(但性能需要评估,并且网络中会有一个额外的跃点)。2、存储层的拆分:按业务维度进行纵向拆分,再按数据特征维度(分库分表)进一步横向拆分。3、业务层的拆分:最常见的拆分是基于业务维度(如电商场景下的商品服务、订单服务),或者按照核心接口和非核心接口拆分,或者按照请求拆分(例如ToC和ToB,APP和H5)。

猜你喜欢