掌握聚合最新动态了解行业最新趋势
API接口,开发服务,免费咨询服务

大话程序猿眼里的高并发

高并发是指在同一个时间点,有很多用户同时的访问URL地址,比如:淘宝的双11,双12,就会产生高并发,如贴吧的爆吧,就是恶意的高并发请求,也就是DDOS攻击,再屌丝点的说法就像玩撸啊撸被ADC暴击了一样,那伤害你懂得(如果你看懂了,这个说法说明是正在奔向人生巅峰的屌丝。

高并发会来带的后果

  • 服务端:
    • 导致站点服务器/DB服务器资源被占满崩溃,数据的存储和更新结果和理想的设计是不一样的,比如:出现重复的数据记录,多次添加了用户积分等。
  • 用户角度:
    • 尼玛,这么卡,老子来参加活动的,刷新了还是这样,垃圾网站,再也不来了。
  • 我的经历:
    在做公司产品网站的过程中,经常会有这样的需求,比如什么搞个活动专题,抽奖,签到,搞个积分竞拍等等,如果没有考虑到高并发下的数据处理,那就Game Over了,很容易导致抽奖被多抽走,签到会发现一个用户有多条记录,签到一次获得了获得了多积分,等等,各种超出正常逻辑的现象,这就是做产品网站必须考虑的问题,因为这些都是面向大量用户的,而不是像做ERP管理系统,OA系统那样,只是面向员工。

下面我进行实例分析,简单粗暴,动态分析,纯属本人个人经验分享,如有说错,或者有更好的建议或者意见的请留言,大家一起成长。

并发下的数据处理:

通过表设计,如:记录表添加唯一约束,数据处理逻辑使用事物防止并发下的数据错乱问题
通过服务端锁进程防止包并发下的数据错乱问题

这里主要讲述的是在并发请求下的数据逻辑处理的接口,如何保证数据的一致性和完整性,这里的并发可能是大量用户发起的,也可能攻击者通过并发工具发起的并发请求


如例子:通过表设计防止并发导致数据错乱

  • 需求点 
    【签到功能】 一天一个用户只能签到一次,
    签到成功后用户获取到一个积分
  • 已知表 
    用户表,包含积分字段 
    高并发意淫分析(属于开发前的猜测): 
    在高并发的情况下,会导致,一个用户签到记录会有多条,或者用户签到后不止加一积分。
  • 我的设计 
    首先根据需求我会添加一张签到记录表,重点来了,这张表需要把用户唯一标识字段(ID,Token)和签到日期字段添加为唯一约束,或者唯一索引,这样就可以防止并发的时候插入重复用户的签到记录。然后再程序代码逻辑里,先执行签到数据的添加(这里可以防止并发,添加成功后再进行积分的添加,这样就可以防止重复的添加积分了。最后我还是建议所有的数据操作都写在一个sql事务里面, 这样在添加失败,或者编辑用户积分失败的时候可以回滚数据。

如例子2(事务+通过更新锁 防止并发导致数据错乱 或者事物+Update的锁表机制)

  • 需求点: 
    【抽奖功能】 抽奖一次消耗一个积分 抽奖中奖后编辑剩余奖品总数 剩余奖品总数为0,或者用户积分为0的时候无法进行抽奖
  • 已知表: 
    用户表,包含积分字段 奖品表,包含奖品剩余数量字段
  • 高并发意淫分析(属于开发前的猜测): 
    在高并发的情况下,会导致用户参与抽奖的时候积分被扣除,而奖品实际上已经被抽完了
  • 我的设计: 
    在事物里,通过WITH (UPDLOCK) 锁住商品表,或者Update 表的奖品剩余数量和最后编辑时间字段,来把数据行锁住,然后进行用户积分的消耗,都完成后提交事物,失败就回滚。 这样就可以保证,只有可能存在一个操作在操作这件商品的数量,只有等到这个操作事物提交后,其他的操作这个商品行的事物才会继续执行。

如例子3(通过程序代码防止包并发下的数据错乱问题)

  • 需求点:
    【缓存数据到cache里】, 当缓存不存在的时候,从数据库中获取并保存在cache里,如果存在从cache里获取,每天10点必须更新一次,其他时间点缓存两个小时更新一次 到10点的时候,凡是打开页面的用户会自动刷新页面
  • 问题点:
    这里有个逻辑用户触发缓存的更新,用户刷新页面,当缓存存在的时候,会取到最后一次缓存更新时间,如果当前时间大于十点,并且最后缓存时间是10点前,则会从数据库中重新获取数据保存到cache中。 还有客户端页面会在10点时候用js发起页面的刷新,就是因为有这样的逻辑,导致10点的时候有很多并发请求同时过来,然后就会导致很多的sql查询操作,理想的逻辑是,只有一个请求会去数据库获取,其他都是从缓存中获取数据。(因为这个sql查询很耗服务器性能,所以导致在10点的时候,突然间数据库服务器压力暴增)
  • 解决问题:
    C#通过 (锁)lock,在从数据读取到缓存的那段代码前面加上锁,这样在并发的情况下只会有一个请求是从数据库里获取数据,其他都是从缓存中获取。

访问量大的数据统计接口

  • 需求: 用户行为数据统计接口,用来记录商品展示次数,用户通过点击图片,或者链接,或者其他方式进入到商品详情的行为次数
  • 问题点:
    这接口是给前端ajax使用,访问量会很大,一页面展示的时候就会有几十件商品的展示,滚动条滚到到页面显示商品的时候就会请求接口进行展示数据的统计,每次翻页又会加载几十件
  • 意淫分析:
    设想如果同时有1W个用户同时在线访问页面,一个次拉动滚动条屏幕页面展示10件商品,这样就会有10W个请求过来,服务端需要把请求数据入库。在实际线上环境可能还会超过这个请求量,如果不经过进行高并发设计处理,服务器分分钟给跪了。
  • 解决问题:
    我们通过nodejs写了一个数据处理接口,把统计数据先存到redis的list里。(使用nodejs写接口的好处是,nodejs使用单线程异步事件机制,高并发处理能力强,不会因为数据逻辑处理问题导致服务器资源被占用而导致服务器宕机) 然后再使用nodejs写了一个脚本,脚本功能就是从redis里出列数据保存到mysql数据库中。这个脚本会一直运行,当redis没有数据需要同步到数据库中的时候,sleep,让在进行数据同步操作

高并发的下的服务器压力均衡,合理站点架设,DB部署

以下我所知道的:

  1. 服务器代理nginx,做服务器的均衡负载,把压力均衡到多台服务器
  2. 部署集群 mysql数据库, redis服务器,或者mongodb服务器,把一些常用的查询数据,并且不会经常的变化的数据保存到其他nosql DB服务器中,来减少数据库服务器的压力,加快数据的响应速度。
  3. 数据缓存,Cache
  4. 在高并发接口的设计中可以使用具有高并发能力的编程语言去开发,如:nodejs 做web接口
  5. 服务器部署,图片服务器分离,静态文件走CDN
  6. DBA数据库的优化查询条件,索引优化
  7. 消息存储机制,将数据添加到信息队列中(redis list),然后再写工具去入库
  8. 脚本合理控制请求,如,防止用户重复点击导致的ajax多余的请求,等等。

并发测试神器推荐

  1. Apache JMeter
  2. Microsoft Web Application Stress Tool
  3. Visual Studio 性能负载

分层,分割,分布式

大型网站要很好支撑高并发,这是需要长期的规划设计 
在初期就需要把系统进行分层,在发展过程中把核心业务进行拆分成模块单元,根据需求进行分布式部署,可以进行独立团队维护开发。

  • 分层
    • 将系统在横向维度上切分成几个部分,每个部门负责一部分相对简单并比较单一的职责,然后通过上层对下层的依赖和调度组成一个完整的系统
    • 比如把电商系统分成:应用层,服务层,数据层。(具体分多少个层次根据自己的业务场景)
    • 应用层:网站首页,用户中心,商品中心,购物车,红包业务,活动中心等,负责具体业务和视图展示
    • 服务层:订单服务,用户管理服务,红包服务,商品服务等,为应用层提供服务支持
    • 数据层:关系数据库,nosql数据库 等,提供数据存储查询服务
    • 分层架构是逻辑上的,在物理部署上可以部署在同一台物理机器上,但是随着网站业务的发展,必然需要对已经分层的模块分离部署,分别部署在不同的服务器上,使网站可以支撑更多用户访问
  • 分割
    • 在纵向方面对业务进行切分,将一块相对复杂的业务分割成不同的模块单元
    • 包装成高内聚低耦合的模块不仅有助于软件的开发维护,也便于不同模块的分布式部署,提高网站的并发处理能力和功能扩展
    • 比如用户中心可以分割成:账户信息模块,订单模块,充值模块,提现模块,优惠券模块等
  • 分布式
    • 分布式应用和服务,将分层或者分割后的业务分布式部署,独立的应用服务器,数据库,缓存服务器
    • 当业务达到一定用户量的时候,再进行服务器均衡负载,数据库,缓存主从集群
    • 分布式静态资源,比如:静态资源上传cdn
    • 分布式计算,比如:使用hadoop进行大数据的分布式计算
    • 分布式数据和存储,比如:各分布节点根据哈希算法或其他算法分散存储数据

网站分层-图1来自网络


集群

对于用户访问集中的业务独立部署服务器,应用服务器,数据库,nosql数据库。 核心业务基本上需要搭建集群,即多台服务器部署相同的应用构成一个集群,通过负载均衡设备共同对外提供服务, 服务器集群能够为相同的服务提供更多的并发支持,因此当有更多的用户访问时,只需要向集群中加入新的机器即可, 另外可以实现当其中的某台服务器发生故障时,可以通过负载均衡的失效转移机制将请求转移至集群中其他的服务器上,因此可以提高系统的可用性

  • 应用服务器集群
    • nginx 反向代理
    • slb
    • … …
  • (关系/nosql)数据库集群
    • 主从分离,从库集群

通过反向代理均衡负载-图2来自网络


异步

在高并发业务中如果涉及到数据库操作,主要压力都是在数据库服务器上面,虽然使用主从分离,但是数据库操作都是在主库上操作,单台数据库服务器连接池允许的最大连接数量是有限的 
当连接数量达到最大值的时候,其他需要连接数据操作的请求就需要等待有空闲的连接,这样高并发的时候很多请求就会出现connection time out 的情况 
那么像这种高并发业务我们要如何设计开发方案可以降低数据库服务器的压力呢?

  • 如:
    • 自动弹窗签到,双11跨0点的时候并发请求签到接口
    • 双11抢红包活动
    • 双11订单入库
  • 设计考虑:
    • 逆向思维,压力在数据库,那业务接口就不进行数据库操作不就没压力了
    • 数据持久化是否允许延迟?
    • 如何让业务接口不直接操作DB,又可以让数据持久化?
  • 方案设计:
    • 像这种涉及数据库操作的高并发的业务,就要考虑使用异步了
    • 客户端发起接口请求,服务端快速响应,客户端展示结果给用户,数据库操作通过异步同步
    • 如何实现异步同步?
    • 使用消息队列,将入库的内容enqueue到消息队列中,业务接口快速响应给用户结果(可以温馨提示高峰期延迟到账)
    • 然后再写个独立程序从消息队列dequeue数据出来进行入库操作,入库成功后刷新用户相关缓存,如果入库失败记录日志,方便反馈查询和重新持久化
    • 这样一来数据库操作就只有一个程序(多线程)来完成,不会给数据带来压力
  • 补充:
    • 消息队列除了可以用在高并发业务,其他只要有相同需求的业务也是可以使用,如:短信发送中间件等
    • 高并发下异步持久化数据可能会影响用户的体验,可以通过可配置的方式,或者自动化监控资源消耗来切换时时或者使用异步,这样在正常流量的情况下可以使用时时操作数据库来提高用户体验
    • 异步同时也可以指编程上的异步函数,异步线程,在有的时候可以使用异步操作,把不需要等待结果的操作放到异步中,然后继续后面的操作,节省了等待的这部分操作的时间

缓存

高并发业务接口多数都是进行业务数据的查询,如:商品列表,商品信息,用户信息,红包信息等,这些数据都是不会经常变化,并且持久化在数据库中
高并发的情况下直接连接从库做查询操作,多台从库服务器也抗不住这么大量的连接请求数(前面说过,单台数据库服务器允许的最大连接数量是有限的)
那么我们在这种高并发的业务接口要如何设计呢?

  • 设计考虑:
    • 还是逆向思维,压力在数据库,那么我们就不进行数据库查询
    • 数据不经常变化,我们为啥要一直查询DB?
    • 数据不变化客户端为啥要向服务器请求返回一样的数据?
  • 方案设计:
    • 数据不经常变化,我们可以把数据进行缓存,缓存的方式有很多种,一般的:应用服务器直接Cache内存,主流的:存储在memcache、redis内存数据库
    • Cache是直接存储在应用服务器中,读取速度快,内存数据库服务器允许连接数可以支撑到很大,而且数据存储在内存,读取速度快,再加上主从集群,可以支撑很大的并发查询
    • 根据业务情景,使用配合客户端本地存,如果我们数据内容不经常变化,为啥要一直请求服务器获取相同数据,可以通过匹配数据版本号,如果版本号不一样接口重新查询缓存返回数据和版本号,如果一样则不查询数据直接响应
    • 这样不仅可以提高接口响应速度,也可以节约服务器带宽,虽然有些服务器带宽是按流量计费,但是也不是绝对无限的,在高并发的时候服务器带宽也可能导致请求响应慢的问题
  • 补充:
    • 缓存同时也指静态资源客户端缓存
    • cdn缓存,静态资源通过上传cdn,cdn节点缓存我们的静态资源,减少服务器压力
    • redis的使用技巧参考我的博文[大话Redis基础],[大话Redis进阶]

面向服务

  • SOA面向服务架构设计
  • 微服务更细粒度服务化,一系列的独立的服务共同组成系统

使用服务化思维,将核心业务或者通用的业务功能抽离成服务独立部署,对外提供接口的方式提供功能。
最理想化的设计是可以把一个复杂的系统抽离成多个服务,共同组成系统的业务,优点:松耦合,高可用性,高伸缩性,易维护。
通过面向服务化设计,独立服务器部署,均衡负载,数据库集群,可以让服务支撑更高的并发


  • 服务例子:
    • 用户行为跟踪记录统计
  • 说明:
    • 通过上报应用模块,操作事件,事件对象,等数据,记录用户的操作行为
    • 比如:记录用户在某个商品模块,点击了某一件商品,或者浏览了某一件商品
  • 背景:
    • 由于服务需要记录用户的各种操作行为,并且可以重复上报,准备接入服务的业务又是核心业务的用户行为跟踪,所以请求量很大,高峰期会产生大量并发请求。
  • 架构:
    • nodejs WEB应用服务器均衡负载
    • redis主从集群
    • mysql主
    • nodejs+express+ejs+redis+mysql
    • 服务端采用nodejs,nodejs是单进程(PM2根据cpu核数开启多个工作进程),采用事件驱动机制,适合I/O密集型业务,处理高并发能力强
  • 业务设计:
    • 并发量大,所以不能直接入库,采用:异步同步数据,消息队列
    • 请求接口上报数据,接口将上报数据push到redis的list队列中
    • nodejs写入库脚本,循环pop redis list数据,将数据存储入库,并进行相关统计Update,无数据时sleep几秒
    • 因为数据量会比较大,上报的数据表按天命名存储
  • 接口:
    • 上报数据接口
    • 统计查询接口
  • 上线跟进:
    • 服务业务基本正常
    • 每天的上报表有上千万的数据

冗余,自动化

当高并发业务所在的服务器出现宕机的时候,需要有备用服务器进行快速的替代,在应用服务器压力大的时候可以快速添加机器到集群中,所以我们就需要有备用机器可以随时待命。 最理想的方式是可以通过自动化监控服务器资源消耗来进行报警,自动切换降级方案,自动的进行服务器替换和添加操作等,通过自动化可以减少人工的操作的成本,而且可以快速操作,避免人为操作上面的失误。

  • 冗余
    • 数据库备份
    • 备用服务器
  • 自动化
    • 自动化监控
    • 自动化报警
    • 自动化降级

通过GitLab事件,我们应该反思,做了备份数据并不代表就万无一失了,我们需要保证高可用性,首先备份是否正常进行,备份数据是否可用,需要我们进行定期的检查,或者自动化监控, 还有包括如何避免人为上的操作失误问题。(不过事件中gitlab的开放性姿态,积极的处理方式还是值得学习的)


总结

高并发架构是一个不断衍变的过程,冰洞三尺非一日之寒,长城筑成非一日之功 
打好基础架构方便以后的拓展,这点很重要

原文来自:SFLYQ

声明:所有来源为“聚合数据”的内容信息,未经本网许可,不得转载!如对内容有异议或投诉,请与我们联系。邮箱:marketing@think-land.com

0512-88869195
数 据 驱 动 未 来
Data Drives The Future