手记

当Golang遇到高并发秒杀,世界开始变得简单

2019-06-11 23:16:079676浏览

Cap

3实战 · 3手记 · 3推荐

     遇到GO语言也是偶尔的一次机会,工作上做架构相关的事情,对新发展比较火爆的语言肯定要关注下。就这样步入了GO语言的世界,GO给我带来了全新的体验;

     一直做一件事情的人往往会被一件事情所困,开始实践GO语言的时候总感觉哪哪都别扭,特别是把结构体当成类,还有结构体的继承,写面向对想多了开始还真扭不过来。不过写的多了渐渐地也习惯了,甚至感觉越来越顺眼。

     在熟悉了GO一段时间后,也停止了一会,脑子里一直在想着GO的简洁(代码风格一致,用起来简练不拖泥带水,部署极其简单,编译速度用快来形容),能给项目系统和架构带来哪些改变。但是当时一时半会儿还真没想到有多大帮助,后来系统逐步开始用GO实现,用的多了慢慢地发现:

  • 咦~,部署再也不要搭建依赖环境了(不部署环境发布,刚开始还真有点不习惯,总感觉少来点什么?);

  • 别人写的代码看着也没有抵触心理,虽然说有的代码不是自己写的,仔细阅读的时候就感觉像是自己写的,写JAVA 和PHP的时候 阅读代码哪是从内心拒绝的(你应该懂的痛?~);

  • 开发某个微服务接口一切变得简单了,写个main,写个接口,提交,秒秒种就上线了~;

     上面主要说了自己和GO的相遇还有GO给我带来的初步影响,就在某天晚上我脑袋里突然蹦出一个想法,GO语言用简洁带来诸多提升,从事的架构系统是否也能利用GO来简化,降低系统复杂度?这个想法从蹦出来,就一直在我脑袋里徘徊。回想刚开始从事架构相关事情的时候,一看到讲中间件有什么原理,有什么优化能提高性能就非常兴奋,埋头苦研直到自己感觉掌握(当然现在也要),然后就信誓旦旦的用到系统中,感觉系统架构用中间件组件越多越好,越多越显得自己做的架构有多厉害。但是随着越做越久你就会发现,系统能用就行,不能为了架构而架构。从传统技术栈上做减法同样需要深厚的功底,因为只有充分知道技术原理才能替换,也才敢想敢干,不出问题。常言道:“条条大陆通罗马”,从个人较角度讲架构要适合当时的业务,达到用尽可能少的代价完成尽可能多的事情就算达到架构设计的目标了,就比如消耗服务器资源相同情况下能支撑好几倍甚至好几十倍的请求,达到相同请求量的情况下,实现和部署非常简单可操作等。GO的思想这段时间就一直这么潜移默化地影响这我。

       直到我们公司决定做微服务,刚开始我们用java做微服务。后来想了下其它语言是否也能做微服务,自然而然的就想到了GO,经过一番折腾后感觉还可以,就用GO开始写点微服务,写着写着就发现Golang 写微服务还挺顺手,效率那个高啊,也不用封装tomcat(这东西放jar包里,感觉特别不合适),二进制不用系统依赖都能在docker里面跑(不信你试试),就这样打心底来说越来越喜欢GO,后续的系统啥的也想用GO重构(写多了java多了遇到这么方便的真是感觉发现了春天)GO已经深入的影响了对系统的架构的看法。

       但究竟什么是架构,架构师又是这么炼成的 ?在这里分享下架构师炼成的八段艰辛:

  • 第一段:搬砖分为能办好砖和只会搬砖(有一批人留在了搬砖上,剩下的继续发展);

  • 第二段:能搬砖的可以分为要了解原理的和编程就是搬砖的;

  • 第三段:了解原理的又分为不断研究的和一知半解的;

  • 第四段:不断研究也能分成两种研究深入有广度和蜻蜓点水没广度的;

  • 第五段:有深度有广度的又分为纯技术型和业务型(开始分化);

  • 第六段:业务型要求有良好沟通,对系统和需求有一定设计把控能力的;

  • 第七段:这层很容易出现单纯使用堆中间件搭出“架构”系统的(初级架构师);

  • 第八段:这层很重要的一点,考虑问题足够细致、全面、善于沟通。做底层实现,理解底层原理,从业务,研发,测试,部署,维护升级多角度出发,因地制宜搭出的“架构”是为了开发效率,为了运行效率,为了开发质量,为了业务灵活和运行稳定,为了维护方便等等这样的人,可称为架构师(有这方面经验的回想下,有多少架构是表面上看着漂亮,实际用着难受的?)。

       在做秒杀系统的时候,刚开始想到也最容易想到的传统技术栈就是,分布式session,Redis集群,分布式缓存,nginx反向代理&nginx深层优化,机器优化,消息队列。没错Cap老师当年也是想到这些,也认为这些“成熟”的技术能应用到我们秒杀系统中没问题,况且公司内部讨论都感觉这些没问题。而且做了深入的研究比如:

      1.分布式Session 我们化了很大的力气,用Nginx+web+redis集群的方式来保存Session达到Session验证的目的,单点Session这块的架构就如下图所示:

        乍一看没问题,都是这么做的,真的大流量来了,就加Nginx,web服务器,也满足横向扩展,但是流量达到上亿级别以后我门的成本有点大,来回的网络请求session的时间我们也想压榨(那时候总感觉哪里不对),nginx在高流量下也时不时出现504。

        2.分布式缓存,这东西看着很简单,但是在大流量高并发系统里面是个非常复杂的事情,主要能遇到以下一些问题;

            1)缓存一致性(如果用这个的话,处理不好会出现超卖);

            2)缓存穿透;

            3)缓存雪崩;

            4)缓存黑洞问题:

            5)狗桩效应;

            .....还有许多需要注意的事情,总之分布式缓存用途很广泛也很有价值,但是要建立一个能够满足高并发,大流量的分布式缓存系统需要极强的技术团队支持,实现起来也特别的复杂这里就不在赘述;

         然而,我们看似没有问题的解决方案存在巨大的问题,实现“太不容易了”,门槛那个是高啊。用起来资源成本也是蛮高的,Session要满足横向扩展,web缓存也要满足横向扩展,包括redis集群也要满足,只能是不断的加集群;

         举个简单的例子(抛去其它因素,为了说明架构的重要性),假设我们现在单台机器,接口有5000QPS的处理能力(8核8G),要处理秒杀的上亿流量,秒杀开始瞬时流量按照300W预估,平均等待按照时间7秒计算(接口容忍到3.5WQPS),得出的结果也就是我们系统,就接口这块需要将近90台,这还没有算其它同等要求的的配套服务器,这些配套服务器简单说明如下;

         1.首先nginx层面要有同等级处理能力(每台不优化大概2WQPS,预估20台左右);

         2.web接口同样要扩展到相应等级(预估的90台);

         3.Reids集群多商品有情况下有左右(单独商品,大量访问量的情况下集群也没用);

         上面是分布式Session要满足的机器需求,还没算上分布式缓存,同学们大概可以感觉到做个和做好秒杀系统有多么不容易了,部署的复杂度也还没考虑。实施起来真是复杂,经验4-5年一两个人短时间还真完成不了。能想出其它的办法来简化吗?答案是:有的。首先要理解原理,其次要从架构上做减法。不用就没有必要优化。

          要从哪些地方优化呢?

          1)Session在秒杀并发里,根据权限验证的原理是可以省略的;

          2)我们可以不用分布式缓存(用接口代替);

          3)把nginx也省了(降低运维难度);

          4)redis集群也可以省略;

          这么一除二去,秒杀怎么做啊?用java和php不是不能实现,而是实现起来可以比较吃力,但是现在可以结合GO语言特性来给我们提供新的思路;

          1.Nginx 可以省略,直接用GO启动端口暴露服务(这里可以省去nginx);

          2.采用cookie方式或者wt方式来代替分布式Session方案,服务器端代码层面验证;

          3.超卖采用接口形式代替redis集群;

          4.负载均衡采用廉价的SLB(避免了Nginx);

          秒杀优化以后总统架构可以展示为下图结构,详细做法请参阅《Go高并发秒杀实践》:

                     

          从改造后的结果来看我们主要列下:

          1.web应用都采用Go部署进制文件的方法,centos服务器不需要按照任何依赖(极大的方便了部署):

          2.采用SLB廉价实用方案避免了nginx的反向代理,甚至高级lua脚本也避免了;

          3.redis集群在这里也被避免了,采用接口的方式提供服务;

          整个技术栈只需要会:GO,RabbitMQ,Mysql就能完成高并发秒杀系统,首先从易操作上就很诱人;下面大家可以在调整后的架构上推演下(目前单机GO提供web接口,在未经过优化的情况下大概能达到2WQPS):

         1.单机GO接口权限验证性能提高4倍,部署复杂度可以说已经接近与零(无nginx,无redis集群,运行也无依赖环境);

         2.随着流量等级的不断提高只需要添加web服务器和数量控制服务器,没有其它服务开销(扩展性上可以说是尽最大的可能降低了成本,成正比例增长模式,且无性能瓶颈);

         3.对比传统方案,服务器数量降低不止4倍(大家可以在原有的基础上计算下);

         上面的解决方案是在GO语言的简单的基础上实现的,恰巧GO部署方便提高了易实现性,有恰好GO能提供web服务减少了外部依赖(本身web务性能也很高)。有了GO的两个恰到好处的特性,在配合架构上减法操作,让秒杀系统整体实现不在是件难事,当然java和PHP都可以做上图中调整后的秒杀架构,但是从多方面考虑来讲,总统还是GO优势明显点。

          回首看方案感觉是Go带动了思维模式的改变,本身简单带动了实现简单,本身高效稳定带动了架构优化。好的系统架构就应该简单,高效,易实现;

        

         

         

       

        

      

   


24人推荐
随时随地看视频
慕课网APP

热门评论

给老师提点建议

1、商品数量减少和插入订单表时应该使用事务处理,如果失败则需要将原先getOne接口中的数量加回。

2、可否请老师详细的说明下rabbitMq在高并发情况下的抗压能力?  如果出现队列进程崩溃数据丢失,导致getOne接口的数量和mysql的数量不一致,该如何解决?(此时用户收到了秒杀成功的信息,但是实际并未下单,体验非常不好) 你们实际项目中既然有这么大的瞬间流量,那么应该有对应类似补单的处理操作,请问这个是如何做的?  以我的经验来看,应该是需要有一个预下单的操作,在秒杀成功的写入队列的同时,将预下单的信息(用户,商品数量等)保存,这样无论是rabbitMq崩了还是mysql崩了,都可以通过类似补单的操作迅速解决问题。

添加负载均衡器代理数量控制接口 那商品的数量怎么判断是否卖完呢

redis集群在这里也被避免了,采用接口的方式提供服务


我想请问下,这边省去了Redis,采用接口提供服务,但是这样的话,会对接口造成巨大的压力啊。
另外Redis原来作用是缓存用户session的,这全部都走接口去校验的话,接口那端服务压力比较大啊

查看全部评论