这个系列不像我的那些个“保姆式”教程,那些保姆式教程我一周最多可以写8篇,因为太简单了。充其量花的时间就是用“看漫画”的方式去组织我的截图和尽量少文字多Sample。
而高性能系统建设系列这一块不仅仅只有代码,相反它甚至不会多写代码更多的是实战、科学依据、方法论、博弈学、技术管理学甚至到“哲学”。
因此每一篇都力求:精确还原现象、准确描述手法以及结果不能再让我趟过的血坑再让读者去趟一遍了。
各位要知道,我正在负责的系统有600多万行代码(我没有包括前端代码-小程序、APP),只是中台业务模块。我们的设计目标是百家店、>400万用户、日活30W、可随时应对1万个TPS、5万个QPS这么一个业务目标。
因为没有这个业务目标企业很难存活。到了这个业务目标,那么至少我们说:每天粗茶淡饭,偶尔每半年我吃个涮羊肉还是够了的。
因此,在这种规模的系统中,开发人员几十号,一切代码都不属于什么高科技,难就难在技术管理、技术架构。
这边多说一句在这种规模的项目中,你可以感受到什么叫真正的架构。
很多人认为架构师不就是比高级开发还要高级点的开发吗?
你错了。架构考虑的问题不仅仅是代码,而是结合着本企业的实际业务同时他自己手上对于同样的一个问题已经具备了不下3种、5种甚至10种到达目标的手段。而他需要在“成本、进度、风险、是否有利于企业可长期生存发展”中选择出最佳的一条“道路”的取舍。
这是企业级IT架构师需要考虑的,因此优秀的架构师从不考虑什么个人利益,他只考虑团队、企业的利益。
因此这样的文章非常难写,因此这样的文章也不可能高产。所以笔者也请各位见谅,这个系列势必出的比较慢,因为每一篇都是实战且成功、行之有效的技术架构、管理手法的叙述。
各位还记得这个架构图不?
它分为六层削峰、四道防线。
特别是最最底层的深蓝色区域,我们又做了:五纵三横的互联网式的应用架构设计。
我们从这篇开始会展开这些点。
今天要叙述的还是在于HTTP请求。我们在之前的每秒万级并发的互联网交易系统的技术全架构中讲到过一个HTTP请求引发的“血案”。
在那个“故事”里,由于最早的一批开发失控,多处HTTP请求没有及时释放资源导致了Http请求泄漏达2,000多处最终导致整个系统的响应、吞吐率上不去。
其实Http请求这一块来说防止资源泄漏只是基本的必要的手段,它一点没有技术含量。碰到问题时就要去做整改,哪怕2,000处、哪怕1万处都需要一个个把它们纠正过来。
唯一难在如何在现有生产上、又不能把全资源投入到整改中、又要完成即定日常业务叠代目标这么一种“取巧”的技术管理手段而己。
而实际Http请求的使用场景还有另外几个点,我在上一篇中虽然提到,但未作深入展开,但也是值得时刻把它们形成明文规范和团队日常作息中去要贯彻的。
而一旦这些点养成了团队良好习惯并融入到了团队基因后,你的系统将会杜绝这一层情况的出现。因此它是属于5纵3横中的其中“一纵”TO B端当成TO C端来设计“的内容。而好玩的是-请允许我在此用“恐怖”两个字来说,这5纵3横中的每一个内容又都是一个“自循环”,即每一个点都又贯彻着5纵3纵。接下来我们就来看5纵3横中拿TO B端当成TO C端来设计这一纵来说事。
微服务模块架构中,业务原子折分越细越好,肯定是有其好处。网上一些反微服务文章的作者要么就是没有经历过实战、要么就是接手“屎山”但没有勇气、毅力坚持到把“屎山”变成金山最后整天怨天尤人的抱怨。
微服务没有错、业务原子粒度折分必须细。
这也就随之带来一些问题,特别典型的例子我举几个各位就知道了。
面临互联网的系统来说最可怕的就是流量。这个流量分解成具体的内容其实就是HTTP请求。
我们都知道,Nginx可以单机处理上万个并发。这点没错。
可是太多架构师只知其一不知其二。
那就是他们都把Nginx的本身性能当成了单纯流量、并发来看待问题而忽视了另一个重要的事务即:业务的事务处理过程。
这边所谓的业务就是指:首页打开->从DB或者从NoSQL或者从缓存加载10几个业务接口(甚至几十个业务接口)->每个业务接口都有取数据、运算、转换格式的系统资源开销->每个开销需要的耗时的累计这么一个“业务事务”。
这一个事务比如说“首页打开”就可能包括了几十个这样的事务,而每一个事务都在耗时。当它们加在一起耗费的时间就是一次首页打开的“速度”。
这个速度的快慢决定了你这一个业务的“响应速度”。
如果你的速度越慢,那么没有“事务”的nginx可以一秒处理完上万个Http请求。而有“事务”的http请求一旦进来可能你的nginx每一台只能最多处理几百个请求。
这就是为什么“纯技术架构无用论”而同时“技术架构又是决定论”并存的矛盾点所在。
没有事务时单台NG可以处理上万个请求,这是不懂技术的技术管理高管说出来的内容,他说了对,也说了不对。
那么实际你们的系统只能做出一个NG处理100个并发Http请求,此时就要反思你的这一根事务到底是在怎么请求了。
各位想一下,一个NG,它的超时是在多少?一般我们会设1秒超时,5秒左右的keep alive。稍微弱一些呢你设个2秒超时、10秒keep alive已经不得了了。
现在你告诉我,你的NG一秒只能处理100个并发,超过了100个并发,大量产生了HTTP ERROR、FAIL RATE>10%?为什么呢?
我们来看一个实际例子。
在后台运营管理平台时不时我们要导出所有持有“传说级名枪”系列的会员导致了前台APP在那一刻卡死个5-10分钟。
我们看了一下导出的数据,在500万会员里不超过5%拥有“传说级名枪”的人数,这才多少人呢?这才25万会员啊。
25万数据导出一下会卡死个5-10分钟?我曾经在2012年博客就介绍过用:多线程、多Worker非阻塞线程队列,导100万数据进系统或者出系统不过20分钟,内存使用恒定在250兆、CPU耗时不会超过10%使用率啊?怎么会做成这样呢?
我们来看设计:
不要笑!
团队一大,有极个别程序员在做这种看似简单的不能再简单的业务功能时都能做出这种“德性”出来。
这个问题其实它的表现过程如下逻辑推断发生:
这就是整件事发生的过程。
贯穿着:现有生产业务不能影响、日常叠代不能只一味为技术债让步。熊掌和鱼兼得法,然后我们把问题的治理分成三个阶段:
临时阶段-3天内上线
使用线程,固定住后端导出只有10个线程,每个线程内一个HTTP请求,这一个线程池一轮后sleep(80-100ms)-根据实际监控值。然后再来一轮,这样做根本上先“止血”,改动很小。付出的代价就是TO B端业务在操作导出时要多等一会时间。
短期治理-7天内上线
TO B端业务觉得导出一下要等40多分钟,1小时,有点慢,向CIO提出了投诉。IT技术出场。此时我们又想起了我之前我在《高性能零售IT系统的建设05-从0打造一个每秒万级并发的互联网交易系统的技术全架构》中讲到过的:你把一个含有几万个小文件每一个10几K的文件夹往百度网盘去上传耗时1个多小时。而你把这个目录打成一个百来兆的ZIP文件,此时再往百度网盘一扔也就两秒内完成上传的这么一个比喻。我们把getMemberById(int id)扩成了一个getMemberByIDS(List ids),一批传过去300个id,相应的会员和武器的匹配处的服务、武器主数据服务都新做一个批量接口。这个改动非致命的,又是新代码,实施很快。然后重构这个后台点击:导出按钮。
导出时此时我就启5个线程,线程少了,每个线程300个一批查出来,5个线程并发的就是一秒5次HTTP请求,每一次可以取出1,500条记录。
结果改造后我们发觉,哎呀,这个太爽了,整体硬件耗费减少了百倍,导出速度从原来的一次40分钟成了1分钟内完成导出。同时这个改动它产生的对现有生产、手头正在叠代任务的冲突微乎其微。
中长期治理-14天内上线
以上,一个例子,它只是5纵中的1纵,本身这个调整就贯穿了:5纵3横。
这又是5纵里的一个纵,对不对?来看问题。
商城的购物车页有一个:最快配送时间。这个是根据用户进入购物车时当前门店的经纬度、选购商品的重量、选择的配送方式送到相关的物流方我们假设说:美团,即调用美团这根配送时间预测HTTP接口后返回的一个值,这个值反应到APP上就是:你的订单预计在X分钟内送达。
而美团对于这个接口其实是有自己的限流,或者美团有时在更改时也会产生BUG,或者网络抖动,那么它也会调用出错。不要以为大厂就不会出错。
好,此时这个接口一旦出错,整个购物车卡死,然后购物车卡死还不算它还要卡会员、卡首页、卡订单。
我们来看设计
因为这边的getTimeWithDistance接口是在后台用HttpClient发起的。而这个HttpClient没有设超时。各位去看看HttpClient的源码,如果你不设超时,它默认是多少秒?
我告诉你们,我们拿APM看到当这个雪崩产生时,getTimeWithDistance一个个请求都是红色的60秒。
然后系统就开始来了:
甭和我提什么用restTemplate、用什么FeignClient。这和用什么客户端有毛线关系。它归于底层就是HttpClient。而HttpClient有两个基本的参数:
于是我们的方案诞生了,和例子一一样,分为临时、短期、中长期几个递进的方案。
这是微服务的考虑,因此我在面试时经常会问一些简历上或者嘴上说了满嘴泡沫的侯选人:你写熟练使用spring cloud,请问你在什么场景下使用spring cloud微服务?是什么事促使你使用微服务?微服务的本质是什么?喏,以上就是例子。
再来一个例子。
这个例子也挺有意思,我们经历了上述两个业务场景后我们都知道了,http请求要控制住,http请求单根不可以无限等、“不行了”就得断开。
于是我们碰到了一个这样的问题。
零售IT系统大都都是从传统ERP开始转过来的,少不了MDM和SAP这种Legacy System的存在。而传统型零售系统的变化是由MDM主数据系统发起的,它每天都会有库存、变价、调价的“跑批处理”发生。
有时一个变动可能会发生在中午或者是下午。此时往往会发生:主数据一下行直接卡死整个前端APP和小程序。
各位如果一直在大厂里或者是来自于ALI和盒马,你们可能没有过这种经历因为它们的系统天生no legacy,但如果你是来自于一些特别又是一些国内巨头型传统企业,我告诉你,这种事真的没有人少碰到。
而我在若干年前问了一圈,竟然高达90%的这个圈子的CTO碰到这种事就是:忍!或者告诉业务:你这种行为最好发生在mid night吧,别发生在早上。
我们知道711超市,它在2016年就已经完成了“第6代门店系统”,还有ALI系的一些先进的新零售门店,它们是一种“走商”,走商即:物品找人而不是:坐等客户自己来找你。因此它们的系统变价格做活动促销经常会伴随着周边的舆情、天气变化而变化。
譬如说:我周边有一个虹口足球场,30分钟后它会举办一场中国女子足球对巴西的赛事。此时系统马上会告诉周边我的那20家店的店内管理系统:给我囤红牛、囤可乐、囤咖啡、囤热狗,50倍于平常日进货的量给我囤。价格采取A+B 看过711-零售的本质一书的读者或许读到过书中:一条街有两边,两边相距不过20米,一边卖的是黄金吐司而另一边就卖的是芥末吐司。 为什么?因为就算是同一条街,街的两边的客流情况也是不一样的,如果同一样的价格、同一样的货品那企不是自己本企业内在搞同质化竞争吗?新零售为了消除这种同质化竞争就势必要搞差异化竞争。而差异化竞争中有一种打法叫:实时变价。 好家伙,现在实时变价好不容易这个能力我有了,然后我发觉我们的系统是以下这样的设计,来看,这不死菜了。 此时,IT就陷入了一个:如果我不断开或者我不把这么大的一个数据去做限流排队固定HTTP线程处理,我的业务模块就会因为每小时或者随时的一个主数据到SAP到业务系统的变价行为被打爆系统HTTP请求并造成HTTP请求泛滥的梗。如果断开,我又面临着库存、财务帐不平天天要007,累死累活,这还做什么IT呢! 面对这样的场景,我们怎么办? 其实这个问题在于以下这么几点: 经过上述这么一个改造,我们让Legacy的改动减少到最小的量,同时因为有了sharding机制它扩宽了我们并行处理数据的处理能力,同时又使用了微服务熔断、限流防止我们的业务模块被来自于TO B的流量打爆,又可以做到on error retry。 瞧,以上的三个例子,就是我说的:虽然它只是属于五纵中的一纵,但是每一纵依然会把五纵三横全部给“滚”一遍的典型场景。 因此这一纵衍生出来了一个“横”。 即:横三、代码设计规范包括: 其中我摘选几条HTTP使用规范给各位作参考: 这只是20多条里的重要6条,然后把规范像PUA、洗脑一下没事拎出来和全体开发定期的Review、宣导,以把这些“血得来的教训”深入到每一个程序员的基因中。 要改变码农必须从“基因”下手改造。这就是我们的技术管理手段。 结束本此篇章!
来看治理
HTTP治理需要形成规范而不能每次来一个案例处理一个