前言高并发往往发生在活跃用户数量多、用户集中度高的业务场景中,比如:闪杀活动、定时红包等。在为了让业务顺利运行,为用户提供良好的交互体验,我们需要根据业务场景实现的预估并发数等因素,设计适合自己业务场景的高并发处理方案。在这几年电子商务相关产品的开发中,有幸遇到过并发带来的各种坑。一路上有很多血和泪。这里总结作为自己的存档记录,分享给大家。每个人。服务器架构业务从发展初期走向逐步成熟,服务器架构也从相对单一的集群发展到分布式服务。一个能够支持高并发的服务,需要一个好的服务器架构,需要负载均衡,数据库主从集群,nosql缓存主从集群,静态文件上传CDN。大部分服务器需要运维人员配合搭建。需要用到的服务器架构大致如下:服务器负载均衡(如:nginx、阿里云SLB)资源监控分布式数据库主从分离,集群DBA表优化,索引优化,其他分布式nosql主从分离、集群主从分离、集群主从分离、集群redismongodbmemcachecdnhtmlcssjs图片并发测试高并发相关业务需要并发测试,通过大量数据评估整个架构所能支持的并发量分析。测试高并发,可以使用第三方服务器或者自己的测试服务器,使用测试工具测试并发请求,分析测试数据,得到可以支持的并发请求数的评估。这可以作为预警参考。第三方服务:阿里云性能测试并发测试工具:ApacheJMeterVisualStudio性能负载测试MicrosoftWebApplicationStressTool实用方案通用方案日常用户流量较大,但比较分散,偶尔会出现高集中用户;场景:用户签到、用户中心、用户下单等服务端架构图:说明:场景中的这些服务基本都是用户进入APP后操作的。除了活动日(618、双十一等),这些服务的用户数不会高聚合,而且这些业务相关的表都是大数据表,大部分业务都是查询操作,所以我们需要减少用户直接访问数据库的查询次数;优先查询缓存,如果缓存不存在,则执行DB查询,并将查询结果缓存起来。更新用户相关的缓存需要分布式存储,比如使用用户ID进行hash分组,将用户分布到不同的缓存中。这样的缓存集总量不会很大,不会影响查询效率。方案如下:用户签到获取积分计算用户分布的key,在redishash中找到用户今天的签到信息。如果查询到登录信息,则返回登录信息。签到信息同步redis缓存。如果在DB中没有找到今天的签到记录,执行签到逻辑,操作DB添加今天的签到记录,添加签到点(整个DB操作就是一个事务)缓存签到信息到redis,并返回签到信息注意这里会出现并发情况下的逻辑问题,比如:一天多次签到,给用户发放多个积分。用户订单这里我们只缓存用户第一页的订单信息,每页40条数据,用户一般只读取第一页的订单数据。用户访问订单列表。如果是第一页读缓存,如果不是读DB计算得到用户分布的key,在redishash中找到用户订单信息。如果查询用户订单信息,则返回订单信息。如果不存在,则对第一页的订单数据进行DB查询,然后缓存redis,将订单信息返回给用户中心,计算用户分布。key和redishash查找用户订单信息。如果查询用户信息,则返回用户信息。如果不存在,则进行用户DB查询,然后缓存redis,返回用户信息。上面的其他业务示例主要针对用户存储缓存。如果是公共缓存的数据需要注意一些问题。注意公共缓存数据需要考虑如下,可能会导致大量命中DB查询。可以使用管理后台更新缓存,或者锁定DB查询。上面的例子是一个比较简单的高并发架构,在并发量不是很高的时候可以很好的支持。但是随着业务的增长,并发用户数的增加,我们的架构也会不断的优化演进。比如业务的Servitization,每个服务都有自己的并发架构,自己的平衡服务器,分布式数据库,nosql主从集群,比如:用户服务,订单服务;消息队列秒杀、secgrab等主动服务,用户瞬间抢购收益产生的高并发请求场景:定时接收红包,等待服务器架构图:描述:场景中的定时接收是一个高并发业务,例如秒杀活动。一次暴击,如果你扛不住,它就会倒下,进而影响到整个生意;像这种既有查询操作又有高并发插入或更新数据的业务,上面提到的通用方案无法支持。都是直接打DB;在设计这个业务的时候,会用到消息队列。可以将参与用户的信息添加到消息队列中,然后写一个多线程程序消费队列,给队列中的用户分发红包;方案如:定时收红包一般都是使用redis列表。用户参与活动时,将用户参与信息推送到队列中,编写多线程程序弹出数据进行红包分发。这样可以支持高并发下的用户,可以正常使用。参与活动,避免数据库服务器宕机的危险附加:很多服务可以通过消息队列来完成。如:定时短信发送服务,使用sset(sortedset),发送时间戳作为排序依据,短信数据队列按照时间排序,然后写一个程序定时循环读取sset队列中的第一项,当前是否timeexceedsthesendingtime时间,如果超过,发送短信。一级缓存高并发请求连接缓存服务器超过服务器可接收的请求连接数,部分用户出现建立连接超时后无法读取数据的问题;因此,需要一种解决方案,可以在高并发高的时候减少对缓存服务器的命中;这时候就有了一级缓存方案。一级缓存是使用站点服务器缓存来存储数据。注意只存储部分请求量大的数据,一定要控制缓存的数据量。它不会受到站点服务器内存过度使用的影响。为了站点应用的正常运行,一级缓存需要设置一个以秒为单位的过期时间。具体时间根据业务场景设置。目的是在不连接缓存的情况下,让数据获取在有高并发请求时命中一级缓存。Nosql数据服务器,减轻nosql数据服务器的压力,比如APP首屏商品数据接口,这些数据是公开的,不会为用户定制,这些数据不会经常更新,这样的接口需要量比较大的请求加入一级缓存;服务器架构图:合理规范使用nosql缓存数据库,根据业务拆分缓存数据库集群,基本可以很好的支撑业务。毕竟一级缓存使用的是站点服务器缓存,所以要好好利用。静态数据高并发请求数据是不会变化的,如果不能请求自己的服务器获取数据,那么可以减少服务器的资源压力。如果更新频率不高,数据可以在短时间内延迟,可以将数据静态转换成JSON、XML、HTML等数据文件上传到CDN。从缓存和数据库中获取。当管理员操作后台编辑数据然后重新生成静态文件上传到CDN,这样在高并发的时候数据获取可以命中到CDN服务器上。CDN节点的同步有一定的延迟,所以找一家靠谱的CDN服务器商也很重要。对于其他方案,对于不经常更新的数据,APP和PC浏览器可以将数据缓存到本地,然后每次请求接口。上传当前缓存数据的版本号时,服务端接收版本号判断版本号是否与最新的数据版本号一致。如果没有,则查询最新的数据,返回最新的数据和最新的版本号。如果相同,则返回状态码通知数据已经是最新的。降低服务器压力:资源、带宽等。分层、分段、分布式的大型网站必须很好地支持高并发,这需要长期的规划和设计。前期需要对系统进行分层,在开发过程中进行核心业务。拆分成模块化单元,按需分布式部署,独立团队维护开发。分层将系统在水平维度上分成几个部分。每个部门负责一个比较简单、比较单一的职责,然后通过上层对下层的依赖和调度,形成一个完整的系统。例如,电子商务系统分为:应用层、服务层、数据层。(具体层数取决于自己的业务场景)应用层:网站首页、用户中心、商品中心、购物车、红包业务、活动中心等,负责具体业务和视图展示服务层:订单服务、用户管理服务、红包服务、商品服务等,为应用层提供服务,支持数据层:关系数据库、nosql数据库等,提供数据存储查询服务分层架构逻辑清晰,可部署物理部署在同一台物理机上,但是随着网站业务的发展,需要将分层的模块分离部署在不同的服务器上,这样网站才能支持更多的用户访问。划分成不同的模块单元,封装成高内聚、低耦合的模块,既有利于软件的开发和维护,又便于不同模块的分布式部署,提高网站的并发处理能力和功能扩展。例如,用户中心可以划分为:账户分布式信息模块、订单模块、充值模块、提现模块、优惠券模块等分布式应用和服务,会分层或分布式部署业务,独立应用服务器、数据库、缓存服务器当业务达到一定的用户量时,再对服务器、数据库进行负载均衡,并缓存主从集群的分布式静态资源,如:static资源上传到CDN分布式计算,例如:使用Hadoop进行大数据的分布式计算分布式数据和存储,例如:各个分布式节点按照哈希算法或其他算法分布式存储数据。网站分层-图1。集群独立部署服务器、应用服务器、数据库、nosql数据库,用于需要用户集中访问的服务。核心业务基本需要搭建集群,即多台服务器部署同一个应用组成集群,通过负载均衡设备对外提供服务。服务器集群可以为同一个服务提供更多的并发支持,所以当有更多的用户访问时,只需要在集群中添加一台新的机器即可。此外,当其中一台服务器发生故障时,可以通过负载均衡故障转移机制将请求转移到集群中的其他服务器,因此可以提高系统可用性应用服务器集群nginx反向代理slb(relational/nosql)数据库集群master-从机分离,从机集群通过反向代理实现负载均衡-图2来自网络异步如果涉及高并发业务的数据库操作,主要的压力都在数据库服务器上。虽然采用了主从分离,但是对数据库的操作都是在主库上进行的。单个数据库服务器连接池允许的最大连接数是有限制的。当连接数达到最大值时,需要进行其他连接。数据操作的请求需要等待空闲连接,这样在高并发的情况下很多请求会出现连接超时。那么对于这种高并发的业务我们应该如何设计开发方案来降低数据库服务器的压力呢?例如:自动弹窗签到、双11过0:00并发请求签到界面、双11抢红包事件、双11订单存储等设计考虑:逆向思维,压力在数据库,那么业务接口不进行数据库操作,没有压力数据持久化允许延迟吗?如何让业务接口不直接操作DB,又让数据持久化?方案设计:对于这种涉及数据库操作的高并发业务,需要考虑使用异步客户端发起接口请求。服务器响应迅速,客户端将结果显示给用户。数据库操作是如何通过异步同步实现异步同步的?使用消息队列将存储的内容入队到消息队列中,业务接口快速响应用户的结果(高峰期可以温馨提示延迟到达)然后写一个独立的程序从消息队列中存储数据出队,刷新存储成功后用户相关的缓存,存储失败则记录日志,方便反馈查询和重新持久化。这样,数据库操作很容易只需要一个程序(多线程)即可完成,不会给数据带来压力补充:除了高并发服务,消息队列也可以用于其他服务有相同需求的,比如:短信发送中间件等高并发服务异步持久化数据可能会影响用户体验,可以通过可配置模式,或自动监控资源消耗,在实时或异步之间切换,以便在正常流量情况下,可以使用数据库的实时操作来提高用户体验。异步也可以指编程异步函数,异步线程,在某些时候可以使用异步操作,把不需要等待结果的操作放到异步中,然后继续后面的操作,节省等待的时间这部分操作。高并发的业务接口大部分是查询业务数据,比如:商品列表、商品信息、用户信息、红包信息等,这些数据不会经常变化,持久化直接连接从库进行数据库高并发条件下的查询操作。多台从数据库服务器无法承受这么大的数量(前面说了单台数据库服务器允许的最大连接数是有限制的),那么我们如何设计这样一个高并发的业务接口呢?设计考虑:或者逆向思考,压力在数据库上,那么我们没有数据库查询数据不经常变化,为什么还要一直查询DB呢?为什么客户端在数据没有变化的情况下向服务器请求相同的数据?方案设计:数据不经常变化,所以我们可以缓存数据有很多种方式,一般:应用服务器直接缓存内存,主流:存储在memcache,redis内存数据库缓存直接存储在应用服务器,读取速度快,内存数据库服务器允许的连接数可以支持大量,数据存储在内存中,读取速度快,加上主从集群,可以支持大量并发查询。根据业务场景,配合客户端本地存储使用。如果我们的数据内容不经常变化,为什么还要一直请求呢?如果服务器获取到相同的数据,可以匹配到数据版本号。如果版本号不同,接口会重新查询缓存,返回数据和版本号。如果相同,则不查询数据,直接响应数据。这样不仅可以提高界面响应速度,还可以节省服务器带宽。有些服务器带宽虽然是按流量计费的,但也不是绝对无限的。高并发时,服务器带宽也可能导致请求响应慢。补充:缓存也指静态资源客户端缓存CDN缓存,CDN节点缓存我们的静态资源,减轻服务器压力,面向服务的SOA服务架构设计微服务更细粒度的服务,一系列独立的服务共同组成一个系统,采用面向服务的思想,抽象核心业务或通用业务功能Detachment服务独立部署,以对外接口的形式提供功能最理想的设计是将一个复杂的系统拆分成多个服务,这些服务共同构成了系统的业务。优点:松散耦合、高可用性、高扩展性、易维护。通过面向服务的设计,服务器独立部署,负载均衡,数据库集群,服务可以支持更高的并发业务。示例:用户行为跟踪记录统计说明:通过上报应用模块、操作事件、事件对象等数据来记录用户的操作行为如:记录用户在某个产品模块中点击某个产品,或者浏览某个产品等。业务也是核心业务的用户行为跟踪,所以请求量非常大,高峰期会产生大量的并发请求。架构:nodejsWEB应用服务器负载均衡redis主从集群mysql主节点nodejs+express+ejs+redis+mysql服务器使用nodejs,nodejs是单进程(pm2根据CPU核数启动多个工作进程),以及采用事件驱动机制。适用于I/O密集型业务,能够处理高并发业务设计:并发量大,不能直接存储。使用:异步数据同步,消息队列请求接口上报数据,该接口会将上报数据推送到redis的list队列中nodejs写库脚本,循环popredislist数据,存入库,执行相关统计更新。没有数据的时候,休眠几秒,因为数据量会比较大。上报的数据表按照日期命名。存储接口:报表数据接口统计查询接口在线跟进:服务业务基本正常。每日报表中有数千万的冗余数据。当高并发业务所在的服务器宕机时,需要有备份服务器来快速替代。在应用服务器上压力大的时候可以快速的把机器加入到集群中,所以我们需要有备用的机器可以随时待命。最理想的方式是自动监控服务器资源消耗并发出告警,自动切换降级方案,自动执行服务器更换和添加??操作。自动化可以降低人工操作的成本,可以快速操作,避免人工操作。错误。冗余数据库备份备份服务器自动化自动化监控自动化报警自动化降级通过GitLab事件,我们应该反思备份数据并不代表万无一失。我们需要确保高可用性。首先,备份是否正常,备份数据是否可用,需要我们进行定期检查,或者自动化监控,以及如何避免人为操作失误。(不过gitlab在事件中的开放态度和积极的处理方式还是值得学习的)
