高并发热点/单点数据_性能问题解决方案

问题的描述

前段时间接了一个双11的活动,业务逻辑:**用户购买某一类商品后,活动期间的4个整点在活动页点击button领取红包,每一个时段奖品数量有限,先到先得。**听着很简单,但是活动开始时,异常火热,流量超过了咱们的预估,原本是分时段领取奖品,结果演变为了秒杀。前端

当时每一个整点的QPS瞬间飙高,响应时间RT短期内居高不下,可是整个Check下来应用所有机器的负载都很是正常,后来全面查找缘由,才找到问题的根源, 是因为每一个时段更新数据库同一条奖品致使超时!mysql

整点开抢后瞬时巨量的请求同时涌入,即便咱们Apache端作过初步限流,应用也作了信号量的控制,并且加上分布式缓存的使用,减缓了至关大的压力,整个业务逻辑校验阶段运做良好,可是系统的瓶颈就转移到其余环节:减奖品库存!由于咱们每一个时段只有一个奖品A,每次减库存都是update奖品A中的奖品余额字段!大量符合发奖要求的用户请求瞬时涌入数据库去更新此条记录,update锁行,致使后面的请求所有排队等待,等前面一个update完成释放行锁后才能处理下一个请求,大量请求等待,占用了数据库的链接!一旦数据库同一时间片内的链接数被打满,就会致使这个时间片内其余后来的所有请求因拿不到链接而超时,致使访问此数据库的其余环节也出现问题!因此RT就会异常飙高!nginx

根据木桶理论,咱们后续确定必须得优化这个最短板,将这个瓶颈解决!针对这样的状况,咱们这边出了两套方案:一、强依赖分布式缓存达到减库存的目的;二、热点/单点数据拆分,弱依赖分布式缓存,采用分散热点的方式减库存.下面请容许我详细分解下这两套方案,也但愿你们提各类建设性意见!sql

1、强依赖分布式缓存

应用中使用分布式缓存来存储当前时间段的奖品余额,有用户中奖则将此缓存中的余额减一,不须要查询和实时更新数据库,而是每隔自定义的一段时间将缓存中的余额异步更新至数据库中。数据库

优势:这种方式彻底依赖于缓存,读写速度快,不须要实时更新数据库,下降了数据库至关大的压力;数组

缺点:缓存不是100%稳定,很容易丢,即便采用持久化的缓存,在高并发下有时也会出问题; 一旦丢失数据,这样就致使数据库记录的奖品余额比实际真实存在的奖品余额要多,这个时候读数据库,就会致使奖品多发,也就是所谓的超卖!缓存

2、热点/单点数据拆分,弱依赖分布式缓存

某时段的一个奖品拆分为多条后,如何能保证先到先得的业务需求将奖品准确发完,这里就引入分布式缓存做为辅助,缓存不彻底稳定不要紧,只是借助其在多条奖品中进行准确分发,当数据库全部奖品都有余额的状况时,能减小查询操做!只有当某一条奖品余额为0时缓存中的数据才会失效,这时才须要查询一次数据库!服务器

步骤:
数组M:奖品的总数。
数字P:没有奖品的行。
数字R:有奖品的行。
若是数字R中有,可是数据库中没有,更新缓存。

      一、 同个时段的奖品拆为多份(好比10份),加行号N区分(1~10),奖1~10的数值存入数组M中;

      二、 根据行号1~10 ,查询分布式缓存中是否存在各行奖品对应的记录(缓存中存放没余额的奖品行);

            是: 将存在的行号存入数组P;

            否: 数组P值为NULL;

      三、 数组M-数组P=数组R;

      四、 判断数组R是否为空 ;

            是: 没有奖品余额,返回未中奖!

            否: 在数组R中随机一个行号L;

      五、 更新数据库表,将L行奖品余额减一;

            更新成功: 减奖品库存成功,直接返回发奖成功!

            更新失败: 极大可能缘由是因为没有奖品致使

                         5.一、 查询数据库中这10个奖品List(全量list);

                         5.二、 将List中没有奖品余额的行同步至对应的缓存(第2 步),并判断List中是否全部奖品行余额全为0;

                                 是: 无奖品,返回未中奖;

                                 否: List中选择一个有余额的奖品,最好是余额最多的,将行号存入L,执行第5 步;

输入图片说明

优势:此方案不会发生奖品多发的状况,将单行数据分拆为多行,分散了热点,一样能够减轻数据库更新时超负荷长链等待致使的链接被等待用户占用然后续请求超时,能够经过拆分为适量的行来解决单点热点数据带来的性能问题!架构

缺点:此方案须要作业务拆解,增长了业务的复杂性!奖品拆分为多条,数据量太大时,不是很便捷,可能会带来数据库性能问题,但这个能够经过分库分表,旧数据迁移备份的方式解决!在奖品快被抽完的那么几微秒的用户可能存在误杀!并发

这就是目前针对数据库的单点热点问题,我我的的一些看法,也只是初步构想,尚未进入彻底的实践中,还但愿各位大神多指点一二!

秒杀问题的总体性解决方案

二 解决方案
     从上面的背景分析,解决热点数据并发更新须要注意核心问题: 减小直接对db层数据热点的并发更新,本文从业务和数据库的设计层面来规划.同时也但愿你们提更好的解决思路。
1 前端层面
   前端是整个流量的入口, 正常业务访问时系统表现平稳,可是当有人恶意请求时,须要加上流控措施,好比常见的
   a 须要用户回答问题,填写验证码,移动图像等等,防止或者减小有机器人来恶意请求。 
   b 页面上采用防止机器人的判断 两秒之内的成功请求一概拒绝。
   c 经过设置nginx ,对同一个ip源的请求次数作限制,防止机器人来申请。 
  优势 有效减小或者防止有人利用机器人恶意请求
  缺点 存在必定的误杀率,错杀了正常的请求。 

2  应用层
    应用程序接收前端前端请求,进行一系列的数据库操做,在咱们规避了恶意请求以后若是仍是有大量的数据库写访问请求,咱们须要
    a 对业务作降级

限制接口的调用次数,下降对数据库的请求压力。

选择不更新请求次数,弱化该商品申请次数的展示。相似于阅读次数,申请次数 ,与金额,库存无关的功能点。
    b 经过异步更新来避免直接写数据库 。
      应用使用分布式缓存(好比tair)来存储某项商品的申请次数或者某人的申请次数,以商品id/user_id 或者将where 条件做为key,申请试用人数为value/符合某项具体条件的 count结果为value, 有用户申请成功则更新申请试用人数。不须要查询和实时写数据库,每隔必定时间/次数将结果写入数据库。
      优势:该方法彻底依赖于缓存,读写速度快,不须要实时更新数据库,减轻数据库并发写的压力;
      缺点:缓存不是100%稳定,很容易丢,即便采用持久化的缓存,在高并发下有时也可能会出现异常,穿透缓存到db ,致使前端业务展示问题。
    
3 数据库层
    a 将热点数据拆分,分在不一样的库不一样的表中,分散热点数据,减轻数据库并发更新热点带来的RT升高和应用链接等待时能保证业务可以正常访问其余商品表,损失局部可用性。 
    优势:实时读写数据库,前端展现数据的准确性。
    缺点:业务逻辑稍显复杂。
   b 限流补丁
     针对某些特定的sql语句 从MySQL 层面加以限制,当系统thread_running达到必定值或者某个sql执行时间超过必定阈值则拒绝该sql的执行。(阿里内部已经实现限流版本)
   c 使用MySQL的 thread pool 功能。在并发较大时,one to one的模式会引发Innodb的mutex锁争用。当前解决方法是经过innodb_thread_concurrency参数,可是该参数自身也存在锁争用,一样影响了MySQL的性能。
优势:thread pool主要从四个方面考虑:减小SQL并发,使得有足够的资源:使用线程组,独立管理:使用优先级队列,限制并发执行的事务:避免死锁。
缺点:在测试过程当中发现,会有大量的链接等待kernel mutex锁,可是持续的压力会致使MySQL的thread running飙高,最终致使MySQL不可用。

解决数据库高并发访问瓶颈问题

## 1、缓存式的Web应用程序架构:

在Web层和db层之间加一层cache层,主要目的:减小数据库读取负担,提升数据读取速度。cache存取的媒介是内存,能够考虑采用分布式的cache层,这样更容易破除内存容量的限制,同时增长了灵活性。

操做步骤:

  1. 在cache中查询是否有数据,若是有,开始更新数据库,没有,直接返回。 2.更新数据库的值,若是成功,发放奖品。若是失败:先查询数据库,而后更新缓存。

2、实现MySQL数据库异步查询实现:

一般状况下在PHP中MySQL查询是串行的,若是能实现MySQL查询的异步化,就能实现多条SQL语句同时执行,这样就能大大地缩短MySQL查询的耗时,提升数据库查询的效率。目前MySQL的异步查询只在MySQLi扩展提供,查询方法分别是:

一、使用MYSQLI_ASYNC模式执行mysqli::query

二、获取异步查询结果:mysqli::reap_async_query

使用mysql异步查询,须要使用mysqlnd做为PHP的MySQL数据库驱动。

使用MySQL异步查询,由于须要给全部查询都建立一个新的链接,而MySQL服务端会为每一个链接建立一个单独的线程进行处理,若是建立的线程过多,则会形成线程切换引发系统负载太高。Swoole中的异步MySQL其原理是经过MYSQLI_ASYNC模式查询,而后获取mysql链接的socket,加入到epoll事件循环中,当数据库返回结果时会回调指定函数,这个过程是彻底异步非阻塞的。

3、MySQL主从读写分离:

当数据库的写压力增长,cache层(如Memcached)只能缓解数据库的读取压力。读写集中在一个数据库上让数据库不堪重负。使用主从复制技术(master-slave模式)来达到读写分离,以提升读写性能和读库的可扩展性。读写分离就是只在主服务器上写,只在从服务器上读,基本原理是让主数据库处理事务性查询,而从数据库处理select查询,数据库复制被用于把事务性查询(增删改)致使的改变动新同步到集群中的从数据库。

MySQL读写分离提高系统性能:

一、主从只负责各自的读和写,极大程度缓解X锁和S锁争用。

二、slave能够配置MyISAM引擎,提高查询性能以及节约系统开销。

三、master直接写是并发的,slave经过主库发送来的binlog恢复数据是异步的。

四、slave能够单独设置一些参数来提高其读的性能。

五、增长冗余,提升可用性。

实现主从分离可使用MySQL中间件如:Atlas

4、分表分库:

在cache层的高速缓存,MySQL的主从复制,读写分离的基础上,这时MySQL主库的写压力开始出现瓶颈,而数据量的持续猛增,因为MyISAM使用表锁,在高并发下会出现严重的锁问题,大量的高并发MySQL应用开始使用InnoDB引擎代替MyISAM。采用Master-Slave复制模式的MySQL架构,只能对数据库的读进行扩展,而对数据的写操做仍是集中在Master上。这时须要对数据库的吞吐能力进一步地扩展,以知足高并发访问与海量数据存储的需求。

对于访问极为频繁且数据量巨大的单表来讲,首先要作的是减小单表的记录条数,以便减小数据查询所需的时间,提升数据库的吞吐,这就是所谓的分表【水平拆分】。在分表以前,首先须要选择适当的分表策略,使得数据可以较为均衡地分布到多张表中,而且不影响正常的查询。

分表可以解决单表数据量过大带来的查询效率降低的问题,可是却没法给数据库的并发处理能力带来质的提高。面对高并发的读写访问,当数据库master服务器没法承载写操做压力时,无论如何扩展Slave服务器都是没有意义的,对数据库进行拆分,从而提升数据库写入能力,即分库【垂直拆分】。

分库分表的理由策略以下:

一、中间变量=user_id % ( 库数量 * 每一个库的表数量 )

二、库=取整(中间变量 / 每一个库的表数量)

三、表=中间变量 % 每一个库的表数量

数据库通过业务拆分及分库分表,虽然查询性能和并发处理能力提升了。可是本来跨表的事务上升为分布式事务;因为记录被切分到不一样的库和不一样的表中,难以进行多表关联查询,而且不能不指定路由字段对数据进行查询。且分库分表后须要进一步对系统进行扩容(路由策略变动)将变得很是不方便,须要从新进行数据迁移。

相关文章
相关标签/搜索