大话程序猿眼里的高并发架构 2016-09-14 YYQ 高并发 高并发 高并发架构 -------------------------------------------------------------------------------- 前言 高并发经常会发生在有大活跃用户量,用户高聚集的业务场景中,如:秒杀活动,定时领取红包等。 为了让业务可以流畅的运行并且给用户一个好的交互体验,我们需要根据业务场景预估达到的并发量等因素,来设计适合自己业务场景的高并发处理方案。 在电商相关产品开发的这些年,我有幸的遇到了并发下的各种坑,这一路摸爬滚打过来有着不少的血泪史,这里进行的总结,作为自己的归档记录,同时分享给大家。 -------------------------------------------------------------------------------- 服务器架构 业务从发展的初期到逐渐成熟,服务器架构也是从相对单一到集群,再到分布式服务。 一个可以支持高并发的服务少不了好的服务器架构,需要有均衡负载,数据库需要主从集群,nosql缓存需要主从集群,静态文件需要上传cdn,这些都是能让业务程序流畅运行的强大后盾。 服务器这块多是需要运维人员来配合搭建,具体我就不多说了,点到为止。 大致需要用到的服务器架构如下: •服务器 ◦均衡负载(如:nginx,阿里云SLB) ◦资源监控 ◦分布式 •数据库 ◦主从分离,集群 ◦DBA 表优化,索引优化,等 ◦分布式 •nosql ◦redis ■主从分离,集群 ◦mongodb ■主从分离,集群 ◦memcache ■主从分离,集群 •cdn ◦html ◦css ◦js ◦image -------------------------------------------------------------------------------- 并发测试 高并发相关的业务,需要进行并发的测试,通过大量的数据分析评估出整个架构可以支撑的并发量。 测试高并发可以使用第三方服务器或者自己测试服务器,利用测试工具进行并发请求测试,分析测试数据得到可以支撑并发数量的评估,这个可以作为一个预警参考,俗话说知己自彼百战不殆。 第三方服务: •阿里云性能测试 并发测试工具: •Apache JMeter •Visual Studio性能负载测试 •Microsoft Web Application Stress Tool -------------------------------------------------------------------------------- 实战方案 通用方案 日用户流量大,但是比较分散,偶尔会有用户高聚的情况; 场景: 用户签到,用户中心,用户订单,等 服务器架构图: 说明: 场景中的这些业务基本是用户进入APP后会操作到的,除了活动日(618,双11,等),这些业务的用户量都不会高聚集,同时这些业务相关的表都是大数据表,业务多是查询操作,所以我们需要减少用户直接命中DB的查询;优先查询缓存,如果缓存不存在,再进行DB查询,将查询结果缓存起来。 更新用户相关缓存需要分布式存储,比如使用用户ID进行hash分组,把用户分布到不同的缓存中,这样一个缓存集合的总量不会很大,不会影响查询效率。 方案如: •用户签到获取积分 ◦计算出用户分布的key,redis hash中查找用户今日签到信息 ◦如果查询到签到信息,返回签到信息 ◦如果没有查询到,DB查询今日是否签到过,如果有签到过,就把签到信息同步redis缓存。 ◦如果DB中也没有查询到今日的签到记录,就进行签到逻辑,操作DB添加今日签到记录,添加签到积分(这整个DB操作是一个事务) ◦缓存签到信息到redis,返回签到信息 ◦注意这里会有并发情况下的逻辑问题,如:一天签到多次,发放多次积分给用户。 ◦我的博文[大话程序猿眼里的高并发]有相关的处理方案。 •用户订单 ◦这里我们只缓存用户第一页的订单信息,一页40条数据,用户一般也只会看第一页的订单数据 ◦用户访问订单列表,如果是第一页读缓存,如果不是读DB ◦计算出用户分布的key,redis hash中查找用户订单信息 ◦如果查询到用户订单信息,返回订单信息 ◦如果不存在就进行DB查询第一页的订单数据,然后缓存redis,返回订单信息 •用户中心 ◦计算出用户分布的key,redis hash中查找用户订单信息 ◦如果查询到用户信息,返回用户信息 ◦如果不存在进行用户DB查询,然后缓存redis,返回用户信息 •其他业务 ◦上面例子多是针对用户存储缓存,如果是公用的缓存数据需要注意一些问题,如下 ◦注意公用的缓存数据需要考虑并发下的可能会导致大量命中DB查询,可以使用管理后台更新缓存,或者DB查询的锁住操作。 ◦我的博文[大话Redis进阶]对更新缓存问题和推荐方案的分享。 以上例子是一个相对简单的高并发架构,并发量不是很高的情况可以很好的支撑,但是随着业务的壮大,用户并发量增加,我们的架构也会进行不断的优化和演变,比如对业务进行服务化,每个服务有自己的并发架构,自己的均衡服务器,分布式数据库,nosql主从集群,如:用户服务、订单服务; -------------------------------------------------------------------------------- 消息队列 秒杀、秒抢等活动业务,用户在瞬间涌入产生高并发请求 场景:定时领取红包,等 服务器架构图: 说明: 场景中的定时领取是一个高并发的业务,像秒杀活动用户会在到点的时间涌入,DB瞬间就接受到一记暴击,hold不住就会宕机,然后影响整个业务; 像这种不是只有查询的操作并且会有高并发的插入或者更新数据的业务,前面提到的通用方案就无法支撑,并发的时候都是直接命中DB; 设计这块业务的时候就会使用消息队列的,可以将参与用户的信息添加到消息队列中,然后再写个多线程程序去消耗队列,给队列中的用户发放红包; 方案如: •定时领取红包 ◦一般习惯使用 redis的 list ◦当用户参与活动,将用户参与信息push到队列中 ◦然后写个多线程程序去pop数据,进行发放红包的业务 ◦这样可以支持高并发下的用户可以正常的参与活动,并且避免数据库服务器宕机的危险 附加: 通过消息队列可以做很多的服务。 如:定时短信发送服务,使用sset(sorted set),发送时间戳作为排序依据,短信数据队列根据时间升序,然后写个程序定时循环去读取sset队列中的第一条,当前时间是否超过发送时间,如果超过就进行短信发送。 -------------------------------------------------------------------------------- 一级缓存 高并发请求连接缓存服务器超出服务器能够接收的请求连接量,部分用户出现建立连接超时无法读取到数据的问题; 因此需要有个方案当高并发时候时候可以减少命中缓存服务器; 这时候就出现了一级缓存的方案,一级缓存就是使用站点服务器缓存去存储数据,注意只存储部分请求量大的数据,并且缓存的数据量要控制,不能过分的使用站点服务器的内存而影响了站点应用程序的正常运行,一级缓存需要设置秒单位的过期时间,具体时间根据业务场景设定,目的是当有高并发请求的时候可以让数据的获取命中到一级缓存,而不用连接缓存nosql数据服务器,减少nosql数据服务器的压力 比如APP首屏商品数据接口,这些数据是公共的不会针对用户自定义,而且这些数据不会频繁的更新,像这种接口的请求量比较大就可以加入一级缓存; 服务器架构图: 合理的规范和使用nosql缓存数据库,根据业务拆分缓存数据库的集群,这样基本可以很好支持业务,一级缓存毕竟是使用站点服务器缓存所以还是要善用。 -------------------------------------------------------------------------------- 静态化数据 高并发请求数据不变化的情况下如果可以不请求自己的服务器获取数据那就可以减少服务器的资源压力。 对于更新频繁度不高,并且数据允许短时间内的延迟,可以通过数据静态化成JSON,XML,HTML等数据文件上传CDN,在拉取数据的时候优先到CDN拉取,如果没有获取到数据再从缓存,数据库中获取,当管理人员操作后台编辑数据再重新生成静态文件上传同步到CDN,这样在高并发的时候可以使数据的获取命中在CDN服务器上。 CDN节点同步有一定的延迟性,所以找一个靠谱的CDN服务器商也很重要 -------------------------------------------------------------------------------- 其他方案 •对于更新频繁度不高的数据,APP,PC浏览器,可以缓存数据到本地,然后每次请求接口的时候上传当前缓存数据的版本号,服务端接收到版本号判断版本号与最新数据版本号是否一致,如果不一样就进行最新数据的查询并返回最新数据和最新版本号,如果一样就返回状态码告知数据已经是最新。减少服务器压力:资源、带宽 未完待续… 有补充的留言给我哦 -------------------------------------------------------------------------------- 感谢大家的支持,领取双11红包,获得推广费用来维持网站的运行,谢谢理解。 [点击领取] 转载请申明原文地址,谢谢合作 如有任何想说的请留言哦,我会根据大家的建议修改有疑义的内容 欢迎大家关注我的 Github -------------------------------------------------------------------------------- 类似 文章 •大话程序猿眼里的高并发 上一篇 大话Redis进阶 下一篇 大话WEB开发必备神器 留言 最新最早最热 42条评论 Onme 9月14日回复顶转发. GoFly mark 9月16日回复顶转发. niming mamecache ? memcache 9月16日回复顶转发. SFLYQ 回复 niming: 写错,不好意思,已经改正! 9月16日回复顶转发. kok 你好我想请问一个问题,也是电商,有一个订单号生成类。我用多线程测试过了不会发生相同订单号情况。 可是在生产环境中还是会出现订单号相同的问题,一直找不到原因。 订单号生成:根据当前时间,orderNumber++ public class OrderNumberGenerate { private static long orderNum = 0l; private static String date; private OrderNumberGenerate() { } /** * 生成订单编号 * * @return */ public static synchronized String getOrderNo() { String str = new SimpleDateFormat("yyMMddHHmm").format(new Date()); if (date == null || !date.equals(str)) { date = str; orderNum = 0l; } orderNum++; long orderNo = Long.parseLong(date) * 10000; orderNo += orderNum; return orderNo + ""; } } 9月17日回复顶转发. SFLYQ 回复 kok: 多线程测试因为程序内存公用,所以orderNum 值不断累加不会重复,但是如果是线上生产环境是不是Web服务器开启多个工作进程,或者多台的Web服务器,每个Web程序管理自己的内存,相同时间可能累加相同orderNum 导致重新重复订单; .NET可以使用GUID会生成不重复字符串, 9月17日回复顶转发. JSLite.io 厉害 9月21日回复顶转发. 奔三路学习网 朕已阅! 9月21日回复顶转发. 小刚 请问下,文中的图片用什么软件画了的,谢谢 9月21日回复顶转发. SFLYQ 回复 小刚: 免费的在线作图网站:processon.com,你看看有很多功能。 9月21日回复顶转发. 杨凯宁 图是用什么工具画的? 9月21日回复顶转发. 康净毅 屌! 9月21日回复顶转发. SFLYQ 回复 杨凯宁: 同上 9月21日回复顶转发. 請叫莪陳先生 你好,我有一个疑问,就是用户订单那里,缓存用户第一页的订单数据进redis,那如果用户又添加了一个订单,这缓存的第一页数据如何更新呢?是在每次用户下订单的时候就相应的更新一次用户第一页订单数据吗?求解惑 9月22日回复顶转发. SFLYQ 回复 請叫莪陳先生: 两种方案: 1.当用户有新订单的时候删除缓存,等到用户进入订单列表时优先读缓存,缓存无数据时就从DB读出最新的订单缓存到Redis中。 2.将需要更新订单的用户加入【更新订单用户队列】中,然后多线程程序去消耗队列,更新用户订单缓存。 根据你的实际的业务情景去选择吧 9月22日回复顶转发. SFLYQ 抛出个问题,就是随着用户的增多,缓存的用户订单数据就越来越多,像这种可以不断累加的数据,当用户可能有一段时间不使用我们产品,或者根本就放弃我们的产品,那这个用户数据还缓存在Redis是不是就是一种资源的浪费? 铭记:Redis只放热数据。 所以我们需要根据业务场景去清理这块冷数据。 9月22日回复顶转发. 請叫莪陳先生 回复 SFLYQ: 嗯,谢谢您的解答。之前一直以为缓存更新是即时的,就像用户更新自己的信息资料一样,只要改了一个地方,就要把用户信息在缓存中的数据释放掉,重新存入一次。原来这里也可以用到队列,学习了。再次感谢~ 9月22日回复顶转发. 张志刚 回复 SFLYQ: 厉害! 9月24日回复顶转发. 张志刚 回复 SFLYQ: Redis不是有自己的API能原子性的控制list的长度吗? 来了一个新的订单,只要把新订单添加进去,那么redis就会根据情况,自动截断原来的,如果不足就无所谓,如果超过的话,就会将最早的一个删除吧! 9月24日回复顶转发. SFLYQ 回复 张志刚: 感谢支持,可以使用 Redis LTRIM 修剪列表长度,根据的业务场景需求选择方案吧。 9月25日回复顶转发. 林淋 回复 SFLYQ: 您好,我是51CTO官方微信负责人,想要转载您这篇文章,可以嘛。 10月10日回复顶转发. SFLYQ 回复 林淋: 欢迎转载,标明来源地址即可,感谢支持! 10月11日回复顶转发. 栋栋八 没看见干货,千篇一律。 10月20日回复顶转发. 宋德福 挺好,支持原创! 10月20日回复顶转发. lolo 感谢楼主分享,希望有更多的好东西分享给大家 10月20日回复顶转发. SFLYQ 回复 栋栋八: 感谢建议 10月20日回复顶转发. SFLYQ 回复 lolo: ,谢谢支持,我会努力的。 10月20日回复顶转发. zx 高并发的解决方案 10月26日回复顶转发. 秦楼月 博客什么时候支持订阅啊!!! 10月26日回复顶转发. 秦楼月 回复 秦楼月: 邮件或者RSS都行 10月26日回复顶转发. 八戒 很受用,谢谢博主!!! 11月2日回复顶转发. 问题 一级缓存在每个服务器都是独立存在的么?如果是的话,那集群中服务器上的一级缓存有可能会都不一样吧 11月4日回复顶转发. SFLYQ 回复 问题: 可以被缓存的数据一般都是变化频率不高的数据,一级缓存过期时间也很短,一般是几秒,当然在数据变化的时候还是有可能几台服务器上的数据有落差,不过很快就会过期从新获取最新。 11月7日回复顶转发. SFLYQ 回复 八戒: 感谢支持 11月7日回复顶转发. SFLYQ 回复 秦楼月: 可以关注我的开发者头条团队号:93719【大话程序猿眼里的WEB开发】,博客更新的时候我会发布在开发者头条的团队号里 11月7日回复顶转发. 漂泊一剑客 博主,用户签到积分这一块,存到DB中的数据,是不是也需要根据用户信息进行hash来分库分表? 11月8日回复顶转发. 漂泊一剑客 博主,抢红包这种业务,也是允许延时的吗,不是一抢到红包,账户上的金额就应该增加的吗?你说先放到一个队列里面,然后开线程去处理,这样会不会,用户收到的反馈是抢红包成功,但账户金额没有增加? 11月8日回复顶转发. 漂泊一剑客 回复 张志刚: 嗯,这个应该是一个更好的办法 11月8日回复顶转发. SFLYQ 回复 漂泊一剑客: 嗯,会有你说的这种情况,但是只要你的消耗队列的程序写的给力,正常情况下可以相对快速的消耗掉队列中的数据,用户体验起来和同步差不多,但是并发量大的时候可能导致延迟的问题。 我们的目的是通过这种方案有效的解决高并发下业务可以正常稳定的进行,有效控制服务器压力,用户体验上可能会有些牺牲。 正对并发量大的这种活动我们活动规则会进行说明奖励会延迟发放 11月10日回复顶转发. SFLYQ 回复 漂泊一剑客: 当你的活跃用户量达到一定的级别时候是可以考虑使用这种方式的,目前我们还是单表 11月10日回复顶转发. 漂泊一剑客 回复 SFLYQ: 多谢回复 11月10日回复顶转发. lucky 回复 kok: 仅用是间戳去生成很有可能重复的,最好配合商品类别,比如05+时间戳+随机数,这样基本不会发生订单重复的情况。如果还要更准确点可以加入对方IP地址,比如05+Date("YYddmmHHmmss")+Md5(IPadress),这样就肯定不重复了。 11月11日回复顶转发. . 社交帐号登录: 微信 微博 QQ 人人 更多» . 说点什么吧… 发布 . 博客站正在使用多说  Content •前言 •服务器架构 •并发测试 •实战方案◦通用方案 ◦消息队列 ◦一级缓存 ◦静态化数据 ◦其他方案 •Similar Posts •Comments ​ 本站记录代码之旅的沿途风景! Contact me at:  本站总访问量次,本站访客数人次,本文总阅读量次 Site powered by Jekyll & Github Pages. Theme designed by HyG.  天猫红包领取