文章列表

优雅地处理重复请求(并发请求)

作者:微信小助手

<section style="outline: 0px;color: rgb(34, 34, 34);font-family: -apple-system, BlinkMacSystemFont, &quot;Helvetica Neue&quot;, &quot;PingFang SC&quot;, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei UI&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif;letter-spacing: 0.544px;white-space: normal;background-color: rgb(255, 255, 255);font-size: 16px;font-weight: bold;text-align: left;visibility: visible;line-height: 1.5em;" data-mpa-powered-by="yiban.io"> <span style="color: rgb(255, 41, 65);">大家好,我是不才陈某~</span> <br> </section> <section style="margin-top: 10px;margin-bottom: 10px;outline: 0px;color: rgb(34, 34, 34);font-family: -apple-system, BlinkMacSystemFont, &quot;Helvetica Neue&quot;, &quot;PingFang SC&quot;, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei UI&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif;letter-spacing: 0.544px;white-space: normal;background-color: rgb(255, 255, 255);visibility: visible;lin

图解:订单系统从0到1设计思路

作者:微信小助手

<p style="margin-bottom: 10px;margin-top: 10px;"><strong><span style="color: rgb(0, 122, 170);">|| 目录</span></strong><br></p> <ul class="list-paddingleft-1" style="list-style-type: disc;"> <li><p><span style="font-size: 15px;color: rgb(0, 0, 0);"><strong>概述<br></strong></span></p></li> <li><p><span style="font-size: 15px;color: rgb(0, 0, 0);"><strong>订单系统核心功能&nbsp;<br></strong></span></p></li> <li><p><span style="font-size: 15px;color: rgb(0, 0, 0);"><strong>订单系统的发展<br></strong></span></p></li> <li> <section style="margin-bottom: 10px;"> <span style="font-size: 15px;color: rgb(0, 0, 0);"><strong>最后</strong></span> </section></li> </ul> <hr style="border-style: solid;border-width: 1px 0 0;border-color: rgba(0,0,0,0.1);-webkit-transform-origin: 0 0;-webkit-transform: scale(1, 0.5);transform-origin: 0 0;transform: scale(1, 0.5);"> <p><br></p> <section data-mpa-template="t"> <section style="display: flex;justify-content: center;align-items: center;width: 100%;" data-mid=""> <section style="display: flex;justify-content: center;align-items: center;width: 100%;align-items: flex-end;" data-mid=""> <section data-mid="" style="background-image: url(&quot;https://mmbiz.qpic.cn/mmbiz_png/k7pous6XT7IKeIwRkcW2MeV9mhT1ZohbhibGIoG84FNwxmYAcmt5WwNDDgBPAbAxdjFQibrvz1wUR1niczlNNvpTw/640?&amp;wx_fmt=png&quot;);background-position: initial;background-repeat: no-repeat;background-attachment: initial;background-origin: initial;background-clip: initial;background-size: 26px 26px;width: 26px;height: 26px;text-align: center;"> <p style="font-size: 16px;font-family: PingFangSC-Semibold, PingFang SC;font-weight: 600;color: #FFFFFF;line-height: 26px;" data-mid="">1</p> </section> <section style="margin-left: 7px;margin-bottom: 2px;display: flex;justify-content: flex-start;align-items: center;flex-direction: column;" data-mid=""> <section style="padding-right: 6px;padding-left: 6px;margin-bottom: -8px;z-index: 1;" data-mid=""> <p style="font-size: 16px;font-family: PingFangSC-Semibold, PingFang SC;font-weight: bold;color: #FCFDFF;line-height: 22px;letter-spacing: 2px;text-shadow: 1px 1px 0px #2F2F2F, 1px 0px 0px #2F2F2F, -1px 0px 0px #2F2F2F, 0px -1px 0px #2F2F2F, 0px 1px 0px #2F2F2F, -1px -1px 0px #2F2F2F, -1px 1px 0px #2F2F2F, 1px -1px 0px #2F2F2F;" data-mid=""><span style="font-size: 17px;">概 述</span></p> </section> <section style="height: 8px;background: #88BAF5;opacity: 0.5;width: 100%;" data-mid=""> <br> </section> </section> </section> </section> </section> <p><br></p> <h2 style="line-height: 1.5em;margin-bottom: 20px;"><span style="font-size: 15px;">本文主要讲述了在传统电商企业中,订单系统应承载的角色,就订单系统所包含的主要功能模块梳理了设计思路,并对订单系统未来的发展做了一些思考。</span><br></h2> <h3 style="line-height: 1.5em;margin-bottom: 20px;"><span style="color: rgb(0, 122, 170);"><strong><span style="font-size: 15px;">1. 订单系统在企业中的角色</span></strong></span></h3> <section style="line-height: 1.5em;margin-bottom: 20px;"> <span style="font-size: 15px;">在搭建企业订单系统之前,需要先梳理企业整体业务系统之间的关系和订单系统上下游关系,只有划分清业务系统边界,才能确定订单系统的职责与功能,进而保证各系统之间高效简洁的工作。</span> </section> <h3 style="line-height: 1.5em;margin-bottom: 20px;"><span style="color: rgb(0, 122, 170);"><strong><span style="font-size: 15px;">2. 订单系统与各业务系统的关系</span></strong></span></h3> <p><img class="rich_pages wxw-img" data-ratio="0.6710144927536232" src="/upload/e8957f198795acc64de019271c1cec0a.png" data-type="png" data-w="690" height="463" style="border-width: 0px;border-style: initial;border-color: initial;display: block;margin-left: auto;margin-right: auto;cursor: pointer;" width="690"></p> <p style="line-height: 1.5em;margin-bottom: 20px;margin-top: 10px;"><strong><span style="font-size: 15px;">(1)对外系统:</span></strong></p> <section style="line-height: 1.5em;margin-bottom: 20px;"> <span style="font-size: 15px;">所有给企业外部用户使用的系统都在这一层,包括官网、普通用户使用的C端,还包括给商户使用的商家后台和在各个销售渠道进行分销的系统,比如与银行信用卡中心合作、微信合作在合作商的平台露出本企业的产品。这类系统站在与客户接触的最前线,是公司实现商业模式的桥头堡。</span> </section> <section style="line-height: 1.5em;margin-bottom: 20px;"> <span style="font-size: 15px;"><strong>(2)管理中后台:</strong></span> </section> <section style="line-height: 1.5em;margin-bottom: 20px;"> <span style="font-size: 15px;">每个C端的业务形态都会有一个对应的系统模块,如负责管理平台交易的订单系统,管理优惠信息的促销系统,管理平台所有产品的产品系统,以及管理所有对外系统显示内容的内容系统等。</span> </section> <section style="line-height: 1.5em;margin-bottom: 20px;"> <strong><span style="font-size: 15px;">(3)公共服务系统:</span></strong> </section> <section style="line-height: 1.5em;margin-bottom: 20px;"> <span style="font-size: 15px;">随着企业的发展,信息化建设到达一定程度后,企业需要将通用功能服务化、平台化,以保证应用架构的合理性,提升服务效率。这类系统主要给其他应用系统提供基础服务能力支持。</span> </section> <h3 style="line-height: 1.5em;margin-bottom: 20px;"><span style="color: rgb(0, 122, 170);"><strong><span style="font-size: 15px;">3. 订单系统上下游关系</span></strong></span></h3> <p><img class="rich_pages wxw-img" data-ratio="0.33207190160832545" src="/upload/b4092eaf9ca2112d14990f50474126dd.png" data-type="png" data-w="1057" height="252" style="border-width: 0px;border-style: initial;border-color: initial;display: block;margin-left: auto;margin-right: auto;cursor: pointer;" width="759"></p> <p style="line-height: 1.5em;margin-bottom: 20px;margin-top: 10px;"><span style="font-size: 15px;">由此可见,订单系统对上接收用户信息,将用户信息转化为产品订单,同时管理并跟踪订单信息和数据,承载了公司整个交易线的重要对客环节。对下则衔接产品系统、促销系统、仓储系统、会员系统、支付系统等,对整个电商平台起着承上启下的作用。</span></p> <h3 style="line-height: 1.5em;margin-bottom: 20px;"><strong><span style="font-size: 15px;color: rgb(0, 122, 170);">4. 订单系统的业务架构</span></strong></h3> <p><img class="rich_pages wxw-img" data-ratio="0.2254335260115607" src="/upload/360d5948a75c3f05c79b9fd5cd7e73cd.png" data-type="png" data-w="1038" height="165" style="border-width: 0px;border-style: initial;border-color: initial;display: block;margin-left: auto;margin-right: auto;cursor: pointer;" width="732"></p> <p style="line-height: 1.5em;margin-bottom: 20px;margin-top: 10px;"><strong><span style="font-size: 15px;">(1)订单服务</span></strong></p> <section style="line-height: 1.5em;margin-bottom: 20px;"> <span style="font-size: 15px;">该模块的主要功能是用户日常使用的服务和页面,主要有订单列表、订单详情、在线下单等,还包括为公共业务模块提供的多维度订单数据服务。</span> </section> <section style="line-height: 1.5em;margin-bottom: 20px;"> <strong><span style="font-size: 15px;">(2)订单逻辑</span></strong> </section> <section style="line-height: 1.5em;margin-bottom: 20px;"> <span style="font-size: 15px;">订单系统的核心,起着至关重要的作用,在订单系统负责管理订单创建、订单支付、订单生产、订单确认、订单完成、取消订单等订单流程。还涉及到复杂的订单状态规则、订单金额计算规则以及增减库存规则等。在4节核心功能设计中会重点来说。</span> </section> <section style="line-height: 1.5em;margin-bottom: 20px;"> <strong><span style="font-size: 15px;">(3)底层服务</span></strong> </section> <section style="line-height: 1.5em;margin-bottom: 20px;"> <span style="font-size: 15px;">信息化建设达到一定程度的企业,一般会将公司公共服务模块化,比如:产品,会构建对应的产品系统,代码、数据库,接口等相对独立。但是,这也带来了一个问题,比如:订单创建的场景下需要获取的信息分散在各个系统。</span> </section> <section style="line-height: 1.5em;margin-bottom: 20px;"> <span style="font-size: 15px;">如果需要从各个公共服务系统调用:一是会花费大量时间,二是代码的维护成本非常高。因此,订单系统接入所需的公共服务模块接口,在订单系统即可完成对接公共系统的服务。</span> </section> <p><br></p> <section data-mpa-template="t"> <section style="display: flex;justify-content: center;align-items: center;width: 100%;" data-mid=""> <section style="display: flex;justify-content: center;align-items: center;width: 100%;align-items: flex-end;" data-mid=""> <section data-mid="" style="background-image: url(&quot;https://mmbiz.qpic.cn/mmbiz_png/k7pous6XT7IKeIwRkcW2MeV9mhT1ZohbhibGIoG84FNwxmYAcmt5WwNDDgBPAbAxdjFQibrvz1wUR1niczlNNvpTw/640?wx_fmt=png&quot;);background-position: initial;background-repeat: no-repeat;background-attachment: initial;background-origin: initial;background-clip: initial;background-size: 26px 26px;width: 26px;height: 26px;text-align: center;"> <p style="font-size: 16px;font-family: PingFangSC-Semibold, &quot;PingFang SC&quot;;font-weight: 600;color: rgb(255, 255, 255);line-height: 26px;" data-mid="">2</p> </section> <section style="margin-left: 7px;margin-bottom: 2px;display: flex;justify-content: flex-start;align-items: center;flex-direction: column;" data-mid=""> <section style="padding-right: 6px;padding-left: 6px;margin-bottom: -8px;z-index: 1;" data-mid=""> <p style="font-size: 16px;font-family: PingFangSC-Semibold, &quot;PingFang SC&quot;;font-weight: bold;color: rgb(252, 253, 255);line-height: 22px;letter-spacing: 2px;text-shadow: rgb(47, 47, 47) 1px 1px 0px, rgb(47, 47, 47) 1px 0px 0px, rgb(47, 47, 47) -1px 0px 0px, rgb(47, 47, 47) 0px -1px 0px, rgb(47, 47, 47) 0px 1px 0px, rgb(47, 47, 47) -1px -1px 0px, rgb(47, 47, 47) -1px 1px 0px, rgb(47, 47, 47) 1px -1px 0px;" data-mid=""><span style="font-size: 17px;">订单系统核心功能</span></p> </section> <section style="height: 8px;background: rgb(136, 186, 245);opacity: 0.5;width: 100%;" data-mid=""> <br> </section> </section> </section> </section> </section> <p><br></p> <h3 style="line-height: 1.5em;margin-bottom: 20px;"><span style="color: rgb(0, 122, 170);"><strong><span style="font-size: 15px;">1. 订单中所包含的内容信息</span></strong></span></h3> <p><img class="rich_pages wxw-img" data-ratio="0.689922480620155" src="/upload/15919f2d661dd6cf4f78ed592972c6b3.png" data-type="png" data-w="774" height="534" style="border-width: 0px;border-style: initial;border-color: initial;display: block;margin-left: auto;margin-right: auto;cursor: pointer;" width="774"></p> <p style="line-height: 1.5em;margin-bottom: 20px;margin-top: 10px;"><span style="font-size: 15px;">为了使订单系统能够对订单进行高效、精准的管理和跟踪,订单会储存关于产品、优惠、用户、支付信息等一系列的订单实时数据,来和下游系统,如:促销、仓储、物流进行交互。</span></p> <section style="line-height: 1.5em;margin-bottom: 20px;"> <span style="font-size: 15px;">以一个通用B2C商城的订单为例,梳理其包含的信息如下:</span> </section> <section style="line-height: 1.5em;margin-bottom: 20px;"> <span style="font-size: 15px;">这里要注意的是订单类型,随着平台业务的不断发展,品类丰富、交易方式丰富后,需要对订单进行多维度的分类管理,同时订单类型利于订单系统的扩展性。每种订单类型将会对应一套流程及一套状态,便于对订单进行分类管理和复用。</span> </section> <h3 style="line-height: 1.5em;margin-bottom: 20px;"><span style="color: rgb(0, 122, 170);"><strong><span style="font-size: 15px;">2. 流程引擎</span></strong></span></h3> <section style="line-height: 1.5em;margin-bottom: 20px;"> <span style="font-size: 15px;">流程是指从平台角度出发,将订单从创建到完成的整个流转过程进行抽象,从而行程了一套标准流程规则。而不同的产品类型或交易类型在系统中的流程会千差万别,因此为了方便对订单流程进行管理,会组建流程引擎模块。</span> </section> <section style="line-height: 1.5em;margin-bottom: 20px;"> <span style="font-size: 15px;">每套订单流程中会包含正向流程及逆向流程,正向流程可以比作一次顺利的网购体验过程中,后台系统之间的信息流转。逆向流程则是修改订单、取消订单、退款、退货等各种动作引起的后台系统流程,同时每个流程触发的条件又可分为系统触发和人工触发两种场景。</span> </section> <section style="line-height: 1.5em;margin-bottom: 20px;"> <strong><span style="font-size: 15px;">(1)正向流程</span></strong> </section> <section style="line-height: 1.5em;margin-bottom: 20px;"> <span style="font-size: 15px;">以一个通用B2C商城的订单系统为例,根据其实际业务场景,其订单流程可抽象为5大步骤:<strong>订单创建&gt;订单支付&gt;订单生产&gt;订单确认&gt;订单完成</strong>。</span> </section> <section style="line-height: 1.5em;margin-bottom: 20px;"> <span style="font-size: 15px;">而每个步骤的背后,订单是如何在多系统之间交互流转的,可概括如下图:</span> </section> <p><img class="rich_pages wxw-img" data-ratio="0.9544344995931652" src="/upload/ef623ca04d733326f35cbd092e039a66.jpg" data-type="jpeg" data-w="1229" height="660" style="border-width: 0px;border-style: initial;border-color: initial;display: block;margin-left: auto;margin-right: auto;cursor: pointer;" width="692"></p> <p style="line-height: 1.5em;margin-bottom: 20px;margin-top: 10px;"><strong><span style="font-size: 15px;">订单创建:</span></strong></p> <section style="line-height: 1.5em;margin-bottom: 20px;"> <span style="font-size: 15px;">用户下单后,系统需要生成订单,此时需要先获取下单中涉及的商品信息,然后获取该商品所涉及到的优惠信息,如果商品不参与优惠信息,则无此环节。</span> </section> <section style="line-height: 1.5em;margin-bottom: 20px;"> <span style="font-size: 15px;">接着获取该账户的会员权益,这里要注意的是:优惠信息与会员权益的区别,比如:商品满减是优惠信息,SUPER会员全场9.8折指的是会员权益,一个是针对商品,另一个是针对账户。其次就是优惠活动的叠加规则和优先级规则等。</span> </section> <section style="line-height: 1.5em;margin-bottom: 20px;"> <span style="font-size: 15px;">增减库存规则是指订单中的商品,何时从仓储系统中对相应商品库存进行扣除,目前主流有两种方式:</span> </section> <section style="line-height: 1.5em;margin-bottom: 20px;"> <strong><span style="font-size: 15px;">下单减库存——即用户下单成功时减少库存数量</span></strong> </section> <ul class="list-paddingleft-1" style="list-style-type: none;"> <li style="font-size: 15px;"> <section style="line-height: 1.5em;margin-bottom: 20px;"> <span style="font-size: 15px;">优势:用户体验友好,系统逻辑简洁;</span> </section></li> <li style="font-size: 15px;"> <section style="line-height: 1.5em;margin-bottom: 20px;"> <span style="font-size: 15px;">缺点:会导致恶意下单或下单后却不买,使得真正有需求的用户无法购买,影响真实销量。</span> </section></li> </ul> <section style="line-height: 1.5em;margin-bottom: 20px;"> <strong><span style="font-size: 15px;">解决办法:</span></strong> </section> <ol class="list-paddingleft-1"> <li style="font-size: 15px;"> <section style="line-height: 1.5em;margin-bottom: 20px;"> <span style="font-size: 15px;">设置订单有效时间,若订单创建成功N分钟不付款,则订单取消,库存回滚;</span> </section></li> <li style="font-size: 15px;"> <section style="line-height: 1.5em;margin-bottom: 20px;"> <span style="font-size: 15px;">限购,用各种条件来限制买家的购买件数,比如一个账号、一个ip,只能买一件;</span> </section></li> <li style="font-size: 15px;"> <section style="line-height: 1.5em;margin-bottom: 20px;"> <span style="font-size: 15px;">风控,从技术角度进行判断,屏蔽恶意账号,禁止恶意账号购买。</span> </section></li> </ol> <section style="line-height: 1.5em;margin-bottom: 20px;"> <strong><span style="font-size: 15px;">付款减库存——即用户支付完成并反馈给平台后再减少库存数量</span></strong> </section> <ul class="list-paddingleft-1" style="list-style-type: none;"> <li style="font-size: 15px;"> <section style="line-height: 1.5em;margin-bottom: 20px;"> <span style="font-size: 15px;">优势:减少无效订单带来的资源损耗;</span> </section></li> <li style="font-size: 15px;"> <section style="line-height: 1.5em;margin-bottom: 20px;"> <span style="font-size: 15px;">缺点:因第三方支付返回结果存在时差,同一时间多个用户同时付款成功,会导致下单数目超过库存,商家库存不足容易引发断货和投诉,成本增加。</span> </section></li> </ul> <section style="line-height: 1.5em;margin-bottom: 20px;"> <strong><span style="font-size: 15px;">解决办法:</span></strong> </section> <ol class="list-paddingleft-1"> <li style="font-size: 15px;"> <section style="line-height: 1.5em;margin-bottom: 20px;"> <span style="font-size: 15px;">付款前再次校验库存,如确认订单要付款时再验证一次,并友好提示用户库存不足;</span> </section></li> <li style="font-size: 15px;"> <section style="line-height: 1.5em;margin-bottom: 20px;"> <span style="font-size: 15px;">增加提示信息:在商品详情页,订单步骤页面提示不及时付款,不能保证有库存等。</span> </section></li> </ol> <section style="line-height: 1.5em;margin-bottom: 20px;"> <span style="font-size: 15px;">综上所述,两种方式各有优缺点,因此,需结合实际场景进行考虑,如:秒杀、抢购、促销活动等,可使用下单减库存的方式。而对于产品库存量大,并发流量没有那么强的产品使用付款减库存的方式。</span> </section> <section style="line-height: 1.5em;margin-bottom: 20px;"> <span style="font-size: 15px;">将两种方式带入到销售场景中,关联商品类型、促销类型、供需关系等,灵活使用,以充分发挥计算机系统的优势。</span> </section> <section style="line-height: 1.5em;margin-bottom: 20px;"> <strong><span style="font-size: 15px;">订单支付:</span></strong> </section> <section style="line-height: 1.5em;margin-bottom: 20px;"> <span style="font-size: 15px;">用户支付完订单后,需要获取订单的支付信息,包括支付流水号、支付时间等。支付完订单接着就是等商家发货,但在发货过程中,根据平台业务模式的不同,可能会涉及到订单的拆分。</span> </section> <section style="line-height: 1.5em;margin-bottom: 20px;"> <span style="font-size: 15px;">订单拆分一般分两种:</span> </section> <ul class="list-paddingleft-1" style="list-style-type: none;"> <li style="font-size: 15px;"> <section style="line-height: 1.5em;margin-bottom: 20px;"> <span style="font-size: 15px;">一种是用户挑选的商品来自于不同渠道(自营与商家,商家与商家);</span> </section></li> <li style="font-size: 15px;"> <section style="line-height: 1.5em;margin-bottom: 20px;"> <span style="font-size: 15px;">另一种是在SKU层面上拆分订单:不同仓库,不同运输要求的SKU,包裹重量体积限制等因素需要将订单拆分。</span> </section></li> </ul> <section style="line-height: 1.5em;margin-bottom: 20px;"> <span style="font-size: 15px;">订单拆分也是一个相对独立的模块,这里就不详细描述了。</span> </section> <section style="line-height: 1.5em;margin-bottom: 20px;"> <strong><span style="font-size: 15px;">订单生产:</span></strong> <span style="font-size: 15px;">订单生产,是指产品从企业到用户这一流程的概述。如电商平台中,商家发货过程已有一个标准化的流程,订单内容会发送到仓库,仓库对商品进行打单、拣货、包装、交接快递进行配送。</span> </section> <section style="line-height: 1.5em;margin-bottom: 20px;"> <strong><span style="font-size: 15px;">订单确认:</span></strong> <span style="font-size: 15px;">收到货后,订单系统需要在快递被签收后提醒用户对商品做评价。这里要注意,确认收到货不代表交易成功,相反是售后服务的开始。</span> </section> <section style="line-height: 1.5em;margin-bottom: 20px;"> <strong><span style="font-size: 15px;">订单完成:</span></strong> <span style="font-size: 15px;">订单完成是指在收到货X天的状态,此时订单不在售后的支持时间范围内。到此,一个订单的正向流程就算走完了。</span> </section> <section style="line-height: 1.5em;margin-bottom: 20px;"> <strong><span style="font-size: 15px;">(2)逆向流程</span></strong> </section> <p><img class="rich_pages wxw-img" data-ratio="0.41816009557945044" src="/upload/55fce9050f2739459a177a2d82310a7a.png" data-type="png" data-w="837" height="302" style="border-width: 0px;border-style: initial;border-color: initial;display: block;margin-left: auto;margin-right: auto;cursor: pointer;" width="722"></p> <p style="line-height: 1.5em;margin-bottom: 20px;margin-top: 10px;"><span style="font-size: 15px;">上面说到逆向流程是各种修改订单、取消订单、退款、退货等操作,需要梳理清楚这些流程与正向流程的关系,才能理清订单系统完整的订单流程。</span></p> <section style="line-height: 1.5em;margin-bottom: 20px;"> <strong><span style="font-size: 15px;">订单修改:</span></strong> <span style="font-size: 15px;">可梳理订单内信息,根据信息关联程度及业务诉求,设定订单的可修改范围是什么,比如:客户下单后,想修改收货人地址及电话。此时只需对相应数据进行更新即可。</span> </section> <section style="line-height: 1.5em;margin-bottom: 20px;"> <strong><span style="font-size: 15px;">订单取消:</span></strong> <span style="font-size: 15px;">用户提交订单后没有进行支付操作,此时用户原则上属于取消订单,因为还未付款,则比较简单,只需要将原本提交订单时扣减的库存补回,促销优惠中使用的优惠券,权益等视平台规则,进行相应补回。</span> </section> <section style="line-height: 1.5em;margin-bottom: 20px;"> <strong><span style="font-size: 15px;">退款:</span></strong> <span style="font-size: 15px;">用户支付成功后,客户发出退款的诉求后,需商户进行退款审核,双方达成一致后,系统应以退款单的形式完成退款,关联原订单数据。因商品无变化,所以不许考虑与库存系统的交互,仅需考虑促销系统及支付系统交互即可。</span> </section> <section style="line-height: 1.5em;margin-bottom: 20px;"> <strong><span style="font-size: 15px;">退货:</span></strong> <span style="font-size: 15px;">用户支付成功后,客户发出退货的诉求后,需商户进行退款审核,双方达成一致后,需对库存系统进行补回,支付系统、促销系统以退款单形式完成退款。最后,在退款/退货流程中,需结合平台业务场景,考虑优惠分摊的逻辑,在发生退款/退货时,优惠该如何退回的处理规则和流程。</span> </section> <section style="line-height: 1.5em;margin-bottom: 20px;"> <strong><span style="font-size: 15px;">(3)状态机</span></strong> </section> <section style="line-height: 1.5em;margin-bottom: 20px;"> <span style="font-size: 15px;">状态机是管理订单状态逻辑的工具。状态机可归纳为3个要素,即现态、动作、次态。</span> </section> <ol class="list-paddingleft-1"> <li style="font-size: 15px;"> <section style="line-height: 1.5em;margin-bottom: 20px;"> <strong><span style="font-size: 15px;">现态:</span></strong> <span style="font-size: 15px;">是指当前所处的状态。</span> </section></li> <li style="font-size: 15px;"> <section style="line-height: 1.5em;margin-bottom: 20px;"> <strong><span style="font-size: 15px;">动作:</span></strong> <span style="font-size: 15px;">动作执行完毕后,可以迁移到新的状态,也可以仍旧保持原状态。</span> </section></li> <li style="font-size: 15px;"> <section style="line-height: 1.5em;margin-bottom: 20px;"> <strong><span style="font-size: 15px;">次态:</span></strong> <span style="font-size: 15px;">动作满足后要迁往的新状态,“次态”是相对于“现态”而言的,“次态”一旦被激活,就转变成新的“现态”了。</span> </section></li> </ol> <section style="line-height: 1.5em;margin-bottom: 20px;"> <span style="font-size: 15px;">状态机的设计需要结合平台实际业务场景,将状态间的切换细化成了执行了某个动作。</span> </section> <section style="line-height: 1.5em;margin-bottom: 20px;"> <span style="font-size: 15px;">以一个B2C商城的订单系统举例如下:</span> </section> <p><img class="rich_pages wxw-img" data-ratio="0.5304054054054054" src="/upload/e4f15e0c3999df584e5d545bb5607142.png" data-type="png" data-w="888" height="373" style="border-width: 0px;border-style: initial;border-color: initial;display: block;margin-left: auto;margin-right: auto;cursor: pointer;" width="703"></p> <p style="line-height: 1.5em;margin-bottom: 20px;margin-top: 10px;"><span style="font-size: 15px;">订单系统为了高效的对订单进行跟踪和管理,会对订单流程当中的关键节点,抽象出订单状态。而订单状态从不同用户的角度可分为,系统订单状态、商家订单状态、买家订单状态等。</span></p> <section style="line-height: 1.5em;margin-bottom: 20px;"> <span style="font-size: 15px;">对于订单系统来说,订单状态细分的颗粒度越细、越明确,订单系统管理的精度和可靠性就越高,比如:在待付款和待发货两个状态中,订单系统后台会细分为订单超时取消、订单支付失败、订单付款完成等。</span> </section> <section style="line-height: 1.5em;margin-bottom: 20px;"> <span style="font-size: 15px;">因此,订单状态模块中,通常会维护状态映射表,以不同的用户角色对系统订单状态进行重新划分,以满足不同用户的需求。</span> </section> <section style="line-height: 1.5em;margin-bottom: 20px;"> <span style="font-size: 15px;">除此以外,随着电商平台的不断发展,不同的业务类型,所对应的订单状态都会有所区别。所以,订单系统中一般会储存多套状态机,以满足不同的订单类型来使用。</span> </section> <p><br></p> <section data-mpa-template="t"> <section style="display: flex;justify-content: center;align-items: center;width: 100%;" data-mid=""> <section style="display: flex;justify-content: center;align-items: center;width: 100%;align-items: flex-end;" data-mid=""> <section data-mid="" style="background-image: url(&quot;https://mmbiz.qpic.cn/mmbiz_png/k7pous6XT7IKeIwRkcW2MeV9mhT1ZohbhibGIoG84FNwxmYAcmt5WwNDDgBPAbAxdjFQibrvz1wUR1niczlNNvpTw/640?wx_fmt=png&quot;);background-position: initial;background-repeat: no-repeat;background-attachment: initial;background-origin: initial;background-clip: initial;background-size: 26px 26px;width: 26px;height: 26px;text-align: center;"> <p style="font-size: 16px;font-family: PingFangSC-Semibold, PingFang SC;font-weight: 600;color: #FFFFFF;line-height: 26px;" data-mid="">3</p> </section> <section style="margin-left: 7px;margin-bottom: 2px;display: flex;justify-content: flex-start;align-items: center;flex-direction: column;" data-mid=""> <section style="padding-right: 6px;padding-left: 6px;margin-bottom: -8px;z-index: 1;" data-mid=""> <p style="font-size: 16px;font-family: PingFangSC-Semibold, PingFang SC;font-weight: bold;color: #FCFDFF;line-height: 22px;letter-spacing: 2px;text-shadow: 1px 1px 0px #2F2F2F, 1px 0px 0px #2F2F2F, -1px 0px 0px #2F2F2F, 0px -1px 0px #2F2F2F, 0px 1px 0px #2F2F2F, -1px -1px 0px #2F2F2F, -1px 1px 0px #2F2F2F, 1px -1px 0px #2F2F2F;" data-mid=""><span style="font-size: 17px;">订单系统的发展</span></p> </section> <section style="height: 8px;background: #88BAF5;opacity: 0.5;width: 100%;" data-mid=""> <br> </section> </section> </section> </section> </section> <p><br></p> <h2 style="line-height: 1.5em;margin-bottom: 20px;"><span style="font-size: 15px;">订单系统的主体框架,和主要业务模块已基本讲完,那么随着企业的发展,业务量和业务形式不断变化,企业有可能形成多个订单系统并存以满足不同的业务需要的情况。</span><br></h2> <section style="line-height: 1.5em;margin-bottom: 20px;"> <span style="font-size: 15px;">业务系统架构如下:</span> </section> <p><img class="rich_pages wxw-img" data-ratio="0.5919395465994962" src="/upload/54f0e6bac2b4d46dba3920d79788279f.png" data-type="png" data-w="794" height="470" style="border-width: 0px;border-style: initial;border-color: initial;display: block;margin-left: auto;margin-right: auto;cursor: pointer;" width="794"></p> <p style="line-height: 1.5em;margin-bottom: 20px;margin-top: 10px;"><span style="font-size: 15px;">这种状况的出现,将会给平台带来非常大的发展瓶颈,如:</span></p> <section style="line-height: 1.5em;margin-bottom: 20px;"> <span style="font-size: 15px;">三个订单系统,每个订单系统处理不同类型的订单,没有统一的订单销量、订单状态信息,网站前台对订单的状态展示与控制不统一,只能是在网站前台会员中心硬代码维护一套面向会员的统一订单明细与状态数据。</span> </section> <section style="line-height: 1.5em;margin-bottom: 20px;"> <span style="font-size: 15px;">而无线侧上线后,由于不了解前台网站会员中心的订单状态管理逻辑,所以需要把前台网站的订单明细及状态管理再在无线应用侧再实现一遍。</span> </section> <section style="line-height: 1.5em;margin-bottom: 20px;"> <span style="font-size: 15px;">三套后台订单系统与公共业务系统如会员中心、支付与财务、促销工具、客户分单等系统都需要对接一遍,公共业务处理逻辑不统一,一旦逻辑变更多个系统统一个接口都要修改一遍,接口的重复维护开发工作量大。</span> </section> <section style="line-height: 1.5em;margin-bottom: 20px;"> <span style="font-size: 15px;">订单开发目前分到事业部,各个事业部只会考虑自己的逻辑,不会考虑公共架构,只会越走越远。碰到像无线这样的项目,需要对接各个事业部,无线侧应用上线进展慢。</span> </section> <section style="line-height: 1.5em;margin-bottom: 20px;"> <span style="font-size: 15px;">因此未来的订单系统可拆分为订单中心与业务订单系统两个模块,以管理公司所有订单数据,并为各个模块提供统一服务。</span> </section> <section style="line-height: 1.5em;margin-bottom: 20px;"> <span style="font-size: 15px;">业务系统架构如下:</span> </section> <p><img class="rich_pages wxw-img" data-ratio="0.5875" src="/upload/9026d302ca8b7a3f3046bd2389024b65.jpg" data-type="jpeg" data-w="800" height="413" style="border-width: 0px;border-style: initial;border-color: initial;display: block;margin-left: auto;margin-right: auto;cursor: pointer;" width="703"></p> <section style="margin-bottom: 20px;"> <br> </section> <section data-mpa-template="t"> <section style="display: flex;justify-content: center;align-items: center;width: 100%;" data-mid=""> <section style="display: flex;justify-content: center;align-items: center;width: 100%;align-items: flex-end;" data-mid=""> <section data-mid="" style="background-image: url(&quot;https://mmbiz.qpic.cn/mmbiz_png/k7pous6XT7IKeIwRkcW2MeV9mhT1ZohbhibGIoG84FNwxmYAcmt5WwNDDgBPAbAxdjFQibrvz1wUR1niczlNNvpTw/640?wx_fmt=png&quot;);background-position: initial;background-repeat: no-repeat;background-attachment: initial;background-origin: initial;background-clip: initial;background-size: 26px 26px;width: 26px;height: 26px;text-align: center;"> <p style="font-size: 16px;font-family: PingFangSC-Semibold, PingFang SC;font-weight: 600;color: #FFFFFF;line-height: 26px;" data-mid="">4</p> </section> <section style="margin-left: 7px;margin-bottom: 2px;display: flex;justify-content: flex-start;align-items: center;flex-direction: column;" data-mid=""> <section style="padding-right: 6px;padding-left: 6px;margin-bottom: -8px;z-index: 1;" data-mid=""> <p style="font-size: 16px;font-family: PingFangSC-Semibold, PingFang SC;font-weight: bold;color: #FCFDFF;line-height: 22px;letter-spacing: 2px;text-shadow: 1px 1px 0px #2F2F2F, 1px 0px 0px #2F2F2F, -1px 0px 0px #2F2F2F, 0px -1px 0px #2F2F2F, 0px 1px 0px #2F2F2F, -1px -1px 0px #2F2F2F, -1px 1px 0px #2F2F2F, 1px -1px 0px #2F2F2F;" data-mid=""><span style="font-size: 17px;">最 后</span></p> </section> <section style="height: 8px;background: #88BAF5;opacity: 0.5;width: 100%;" data-mid=""> <br> </section> </section> </section> </section> </section> <p><br></p> <section style="margin-bottom: 20px;"> <span style="font-size: 15px;">对于企业订单系统的搭建,并不是要做的大而全、也不是要小而精。而需要结合市场、公司、业务的实际情况来最终制定系统设计方案和产品迭代计划。</span> <br> </section> <section style="margin-bottom: 20px;"> <span style="font-size: 15px;">最终,和公司整体发展相互协调,相辅相成。</span> </section>

mysql单表最多两千万条数据?

作者:微信小助手

<section style="font-size: 16px;color: rgb(62, 62, 62);line-height: 1.6;word-spacing: 1.5px;letter-spacing: 1.5px;font-family: 'Helvetica Neue', Helvetica, 'Hiragino Sans GB', 'Microsoft YaHei', Arial, sans-serif;background-image: linear-gradient(90deg, rgba(50, 0, 0, 0.05) 5%, rgba(0, 0, 0, 0) 5%), linear-gradient(360deg, rgba(50, 0, 0, 0.05) 5%, rgba(0, 0, 0, 0) 5%);background-size: 20px 20px;background-position: center center;" data-mpa-powered-by="yiban.io"> <p style="font-size: inherit;color: inherit;line-height: inherit;margin-top: 1.5em;margin-bottom: 1.5em;"><br></p> <p style="font-size: inherit;color: inherit;line-height: inherit;margin-top: 1.5em;margin-bottom: 1.5em;">故事从好多年前说起。</p> <p style="font-size: inherit;color: inherit;line-height: inherit;margin-top: 1.5em;margin-bottom: 1.5em;">想必大家也听说过数据库单表<strong style="font-size: inherit;line-height: inherit;color: rgb(41, 98, 255);">建议最大2kw</strong>条数据这个说法。如果超过了,性能就会下降得比较厉害。</p> <p style="font-size: inherit;color: inherit;line-height: inherit;margin-top: 1.5em;margin-bottom: 1.5em;">巧了。</p> <p style="font-size: inherit;color: inherit;line-height: inherit;margin-top: 1.5em;margin-bottom: 1.5em;">我也听说过。</p> <p style="font-size: inherit;color: inherit;line-height: inherit;margin-top: 1.5em;margin-bottom: 1.5em;"><strong style="font-size: inherit;line-height: inherit;color: rgb(41, 98, 255);">但我不接受它的建议,硬是单表装了1亿条数据。</strong></p> <p style="font-size: inherit;color: inherit;line-height: inherit;margin-top: 1.5em;margin-bottom: 1.5em;">这时候,我们组里新来的实习生看到了之后,天真无邪的问我:"单表不是建议最大两千万吗?为什么这个表都<strong style="font-size: inherit;line-height: inherit;color: rgb(41, 98, 255);">放了1个亿还不分库分表</strong>"?</p> <p style="font-size: inherit;color: inherit;line-height: inherit;margin-top: 1.5em;margin-bottom: 1.5em;">我能说我是<strong style="font-size: inherit;line-height: inherit;color: rgb(41, 98, 255);">因为懒</strong>吗?我当初设计时哪里想到这表竟然能涨这么快。。。</p> <p style="font-size: inherit;color: inherit;line-height: inherit;margin-top: 1.5em;margin-bottom: 1.5em;">我不能。</p> <p style="font-size: inherit;color: inherit;line-height: inherit;margin-top: 1.5em;margin-bottom: 1.5em;">说了等于承认自己是<strong style="font-size: inherit;line-height: inherit;color: rgb(41, 98, 255);">开发组里的毒瘤</strong>,虽然我确实是,但我<strong style="font-size: inherit;line-height: inherit;color: rgb(41, 98, 255);">不能承认</strong>。</p> <p style="font-size: inherit;color: inherit;line-height: inherit;margin-top: 1.5em;margin-bottom: 1.5em;">我如坐针毡,如芒刺背,如鲠在喉。</p> <p style="font-size: inherit;color: inherit;line-height: inherit;margin-top: 1.5em;margin-bottom: 1.5em;">开始了一波骚操作。</p> <p style="font-size: inherit;color: inherit;line-height: inherit;margin-top: 1.5em;margin-bottom: 1.5em;">"我这么做是有道理的"</p> <p style="font-size: inherit;color: inherit;line-height: inherit;margin-top: 1.5em;margin-bottom: 1.5em;">"虽然这个表很大,但你有没有发现它查询其实还是很快"</p> <p style="font-size: inherit;color: inherit;line-height: inherit;margin-top: 1.5em;margin-bottom: 1.5em;">"这个2kw是个建议值,我们要来看下这个2kw是怎么来的"</p> <p style="font-size: inherit;color: inherit;line-height: inherit;margin-top: 1.5em;margin-bottom: 1.5em;"><br></p> <h3 style="color: inherit;line-height: inherit;margin-top: 1.5em;margin-bottom: 1.5em;font-weight: bold;font-size: 1.3em;border-bottom: 2px solid rgb(21, 101, 192);"><span style="font-size: inherit;line-height: inherit;display: inline-block;font-weight: normal;background: rgb(21, 101, 192);color: rgb(255, 255, 255);padding-top: 3px;padding-right: 10px;padding-left: 10px;border-top-right-radius: 3px;border-top-left-radius: 4px;margin-right: 2px;">数据库单表行数最大多大?</span></h3> <p style="font-size: inherit;color: inherit;line-height: inherit;margin-top: 1.5em;margin-bottom: 1.5em;">我们先看下单表行数理论最大值是多少。</p> <p style="font-size: inherit;color: inherit;line-height: inherit;margin-top: 1.5em;margin-bottom: 1.5em;">建表的SQL是这么写的,</p> <pre style="font-size: inherit;color: inherit;line-height: inherit;"><code style="margin-right: 2px;margin-left: 2px;line-height: 18px;font-size: 14px;word-spacing: 0px;letter-spacing: 0px;font-family: Consolas, Inconsolata, Courier, monospace;border-radius: 10px;color: rgb(169, 183, 198);background: rgb(40, 43, 46);padding: 0.5em;overflow-wrap: normal !important;word-break: normal !important;overflow: auto !important;display: -webkit-box !important;"><span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;">CREATE</span>&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;">TABLE</span>&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(238, 220, 112);overflow-wrap: inherit !important;word-break: inherit !important;">`user`</span>&nbsp;(<br>&nbsp;&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(238, 220, 112);overflow-wrap: inherit !important;word-break: inherit !important;">`id`</span>&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;">int</span>(<span style="font-size: inherit;line-height: inherit;color: rgb(174, 135, 250);overflow-wrap: inherit !important;word-break: inherit !important;">10</span>)&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;">unsigned</span>&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;">NOT</span>&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(174, 135, 250);overflow-wrap: inherit !important;word-break: inherit !important;">NULL</span>&nbsp;AUTO_INCREMENT&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;">COMMENT</span>&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(238, 220, 112);overflow-wrap: inherit !important;word-break: inherit !important;">'主键'</span>,<br>&nbsp;&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(238, 220, 112);overflow-wrap: inherit !important;word-break: inherit !important;">`name`</span>&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;">varchar</span>(<span style="font-size: inherit;line-height: inherit;color: rgb(174, 135, 250);overflow-wrap: inherit !important;word-break: inherit !important;">100</span>)&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;">NOT</span>&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(174, 135, 250);overflow-wrap: inherit !important;word-break: inherit !important;">NULL</span>&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;">DEFAULT</span>&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(238, 220, 112);overflow-wrap: inherit !important;word-break: inherit !important;">''</span>&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;">COMMENT</span>&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(238, 220, 112);overflow-wrap: inherit !important;word-break: inherit !important;">'名字'</span>,<br>&nbsp;&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(238, 220, 112);overflow-wrap: inherit !important;word-break: inherit !important;">`age`</span>&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;">int</span>(<span style="font-size: inherit;line-height: inherit;color: rgb(174, 135, 250);overflow-wrap: inherit !important;word-break: inherit !important;">11</span>)&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;">NOT</span>&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(174, 135, 250);overflow-wrap: inherit !important;word-break: inherit !important;">NULL</span>&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;">DEFAULT</span>&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(238, 220, 112);overflow-wrap: inherit !important;word-break: inherit !important;">'0'</span>&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;">COMMENT</span>&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(238, 220, 112);overflow-wrap: inherit !important;word-break: inherit !important;">'年龄'</span>,<br>&nbsp;&nbsp;PRIMARY&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;">KEY</span>&nbsp;(<span style="font-size: inherit;line-height: inherit;color: rgb(238, 220, 112);overflow-wrap: inherit !important;word-break: inherit !important;">`id`</span>),<br>&nbsp;&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;">KEY</span>&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(238, 220, 112);overflow-wrap: inherit !important;word-break: inherit !important;">`idx_age`</span>&nbsp;(<span style="font-size: inherit;line-height: inherit;color: rgb(238, 220, 112);overflow-wrap: inherit !important;word-break: inherit !important;">`age`</span>)<br>)&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;">ENGINE</span>=<span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;">InnoDB</span>&nbsp;AUTO_INCREMENT=<span style="font-size: inherit;line-height: inherit;color: rgb(174, 135, 250);overflow-wrap: inherit !important;word-break: inherit !important;">100037</span>&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;">DEFAULT</span>&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;">CHARSET</span>=utf8;<br></code></pre> <p style="font-size: inherit;color: inherit;line-height: inherit;margin-top: 1.5em;margin-bottom: 1.5em;">其中id就是主键。主键本身唯一,也就是说主键的大小可以限制表的上限。</p> <p style="font-size: inherit;color: inherit;line-height: inherit;margin-top: 1.5em;margin-bottom: 1.5em;">如果主键声明为<code style="font-size: inherit;line-height: inherit;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(255, 82, 82);background: rgb(248, 248, 248);">int</code>大小,也就是32位,那么能支持2^32-1,也就是<strong style="font-size: inherit;line-height: inherit;color: rgb(41, 98, 255);">21个亿</strong>左右。</p> <p style="font-size: inherit;color: inherit;line-height: inherit;margin-top: 1.5em;margin-bottom: 1.5em;">如果是<code style="font-size: inherit;line-height: inherit;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(255, 82, 82);background: rgb(248, 248, 248);">bigint</code>,那就是2^64-1,但这个<strong style="font-size: inherit;line-height: inherit;color: rgb(41, 98, 255);">数字太大</strong>,一般还没到这个限制之前,<strong style="font-size: inherit;line-height: inherit;color: rgb(41, 98, 255);">磁盘先受不了</strong>。</p> <p style="font-size: inherit;color: inherit;line-height: inherit;margin-top: 1.5em;margin-bottom: 1.5em;">搞离谱点。</p> <p style="font-size: inherit;color: inherit;line-height: inherit;margin-top: 1.5em;margin-bottom: 1.5em;">如果我把主键声明为 <code style="font-size: inherit;line-height: inherit;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(255, 82, 82);background: rgb(248, 248, 248);">tinyint</code>,一个字节,8位,最大2^8-1,也就是<code style="font-size: inherit;line-height: inherit;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(255, 82, 82);background: rgb(248, 248, 248);">255</code>。</p> <pre style="font-size: inherit;color: inherit;line-height: inherit;"><code style="margin-right: 2px;margin-left: 2px;line-height: 18px;font-size: 14px;word-spacing: 0px;letter-spacing: 0px;font-family: Consolas, Inconsolata, Courier, monospace;border-radius: 10px;color: rgb(169, 183, 198);background: rgb(40, 43, 46);padding: 0.5em;overflow-wrap: normal !important;word-break: normal !important;overflow: auto !important;display: -webkit-box !important;"><span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;">CREATE</span>&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;">TABLE</span>&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(238, 220, 112);overflow-wrap: inherit !important;word-break: inherit !important;">`user`</span>&nbsp;(<br>&nbsp;&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(238, 220, 112);overflow-wrap: inherit !important;word-break: inherit !important;">`id`</span>&nbsp;tinyint(<span style="font-size: inherit;line-height: inherit;color: rgb(174, 135, 250);overflow-wrap: inherit !important;word-break: inherit !important;">2</span>)&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;">unsigned</span>&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;">NOT</span>&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(174, 135, 250);overflow-wrap: inherit !important;word-break: inherit !important;">NULL</span>&nbsp;AUTO_INCREMENT&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;">COMMENT</span>&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(238, 220, 112);overflow-wrap: inherit !important;word-break: inherit !important;">'主键'</span>,<br>&nbsp;&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(238, 220, 112);overflow-wrap: inherit !important;word-break: inherit !important;">`name`</span>&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;">varchar</span>(<span style="font-size: inherit;line-height: inherit;color: rgb(174, 135, 250);overflow-wrap: inherit !important;word-break: inherit !important;">100</span>)&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;">NOT</span>&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(174, 135, 250);overflow-wrap: inherit !important;word-break: inherit !important;">NULL</span>&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;">DEFAULT</span>&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(238, 220, 112);overflow-wrap: inherit !important;word-break: inherit !important;">''</span>&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;">COMMENT</span>&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(238, 220, 112);overflow-wrap: inherit !important;word-break: inherit !important;">'名字'</span>,<br>&nbsp;&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(238, 220, 112);overflow-wrap: inherit !important;word-break: inherit !important;">`age`</span>&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;">int</span>(<span style="font-size: inherit;line-height: inherit;color: rgb(174, 135, 250);overflow-wrap: inherit !important;word-break: inherit !important;">11</span>)&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;">NOT</span>&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(174, 135, 250);overflow-wrap: inherit !important;word-break: inherit !important;">NULL</span>&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;">DEFAULT</span>&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(238, 220, 112);overflow-wrap: inherit !important;word-break: inherit !important;">'0'</span>&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;">COMMENT</span>&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(238, 220, 112);overflow-wrap: inherit !important;word-break: inherit !important;">'年龄'</span>,<br>&nbsp;&nbsp;PRIMARY&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;">KEY</span>&nbsp;(<span style="font-size: inherit;line-height: inherit;color: rgb(238, 220, 112);overflow-wrap: inherit !important;word-break: inherit !important;">`id`</span>),<br>&nbsp;&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;">KEY</span>&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(238, 220, 112);overflow-wrap: inherit !important;word-break: inherit !important;">`idx_age`</span>&nbsp;(<span style="font-size: inherit;line-height: inherit;color: rgb(238, 220, 112);overflow-wrap: inherit !important;word-break: inherit !important;">`age`</span>)<br>)&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;">ENGINE</span>=<span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;">InnoDB</span>&nbsp;AUTO_INCREMENT=<span style="font-size: inherit;line-height: inherit;color: rgb(174, 135, 250);overflow-wrap: inherit !important;word-break: inherit !important;">0</span>&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;">DEFAULT</span>&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;">CHARSET</span>=utf8;<br></code></pre> <p style="font-size: inherit;color: inherit;line-height: inherit;margin-top: 1.5em;margin-bottom: 1.5em;">如果我想插入一个id=256的数据,那<strong style="font-size: inherit;line-height: inherit;color: rgb(41, 98, 255);">就会报错</strong>。</p> <pre style="font-size: inherit;color: inherit;line-height: inherit;"><code style="margin-right: 2px;margin-left: 2px;line-height: 18px;font-size: 14px;word-spacing: 0px;letter-spacing: 0px;font-family: Consolas, Inconsolata, Courier, monospace;border-radius: 10px;color: rgb(169, 183, 198);background: rgb(40, 43, 46);padding: 0.5em;overflow-wrap: normal !important;word-break: normal !important;overflow: auto !important;display: -webkit-box !important;">mysql&gt;&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;">INSERT</span>&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;">INTO</span>&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(238, 220, 112);overflow-wrap: inherit !important;word-break: inherit !important;">`tmp`</span>&nbsp;(<span style="font-size: inherit;line-height: inherit;color: rgb(238, 220, 112);overflow-wrap: inherit !important;word-break: inherit !important;">`id`</span>,&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(238, 220, 112);overflow-wrap: inherit !important;word-break: inherit !important;">`name`</span>,&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(238, 220, 112);overflow-wrap: inherit !important;word-break: inherit !important;">`age`</span>)&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;">VALUES</span>&nbsp;(<span style="font-size: inherit;line-height: inherit;color: rgb(174, 135, 250);overflow-wrap: inherit !important;word-break: inherit !important;">256</span>,&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(238, 220, 112);overflow-wrap: inherit !important;word-break: inherit !important;">''</span>,&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(174, 135, 250);overflow-wrap: inherit !important;word-break: inherit !important;">60</span>);<br>ERROR&nbsp;1264&nbsp;(22003):&nbsp;Out&nbsp;of&nbsp;range&nbsp;value&nbsp;for&nbsp;column&nbsp;'id'&nbsp;at&nbsp;row&nbsp;1<br></code></pre> <p style="font-size: inherit;color: inherit;line-height: inherit;margin-top: 1.5em;margin-bottom: 1.5em;">也就是说,<code style="font-size: inherit;line-height: inherit;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(255, 82, 82);background: rgb(248, 248, 248);">tinyint</code>主键限制表内最多255条数据。</p> <p style="font-size: inherit;color: inherit;line-height: inherit;margin-top: 1.5em;margin-bottom: 1.5em;">那除了主键,还有哪些因素会影响行数?</p> <p style="font-size: inherit;color: inherit;line-height: inherit;margin-top: 1.5em;margin-bottom: 1.5em;"><br></p> <h3 style="color: inherit;line-height: inherit;margin-top: 1.5em;margin-bottom: 1.5em;font-weight: bold;font-size: 1.3em;border-bottom: 2px solid rgb(21, 101, 192);"><span style="font-size: inherit;line-height: inherit;display: inline-block;font-weight: normal;background: rgb(21, 101, 192);color: rgb(255, 255, 255);padding-top: 3px;padding-right: 10px;padding-left: 10px;border-top-right-radius: 3px;border-top-left-radius: 4px;margin-right: 2px;">索引的结构</span></h3> <p style="font-size: inherit;color: inherit;line-height: inherit;margin-top: 1.5em;margin-bottom: 1.5em;">索引内部是用的B+树,这个也是八股文老股了,大家估计也背得很熟了。</p> <p style="font-size: inherit;color: inherit;line-height: inherit;margin-top: 1.5em;margin-bottom: 1.5em;">为了不让大家有过于强烈的审丑疲劳,今天我尝试从另外一个角度给大家讲讲这玩意。</p> <p style="font-size: inherit;color: inherit;line-height: inherit;margin-top: 1.5em;margin-bottom: 1.5em;"><br></p> <h4 style="color: inherit;line-height: inherit;margin-top: 1.5em;margin-bottom: 1.5em;font-weight: bold;font-size: 1.2em;"><span style="font-size: inherit;color: inherit;line-height: inherit;">页的结构</span></h4> <p style="font-size: inherit;color: inherit;line-height: inherit;margin-top: 1.5em;margin-bottom: 1.5em;">假设我们有这么一张user数据表。</p> <figure style="font-size: inherit;color: inherit;line-height: inherit;"> <img class="rich_pages wxw-img" data-ratio="0.7" src="/upload/bbda98e5676c0766b381930b4cd83eb.png" data-type="png" data-w="1200" style="font-size: inherit;color: inherit;line-height: inherit;display: block;margin-right: auto;margin-left: auto;" title="user表"> <figcaption style="line-height: inherit;margin-top: 10px;text-align: center;color: rgb(153, 153, 153);font-size: 0.7em;"> user表 </figcaption> </figure> <p style="font-size: inherit;color: inherit;line-height: inherit;margin-top: 1.5em;margin-bottom: 1.5em;">其中id是<strong style="font-size: inherit;line-height: inherit;color: rgb(41, 98, 255);">唯一主键</strong>。</p> <p style="font-size: inherit;color: inherit;line-height: inherit;margin-top: 1.5em;margin-bottom: 1.5em;">这看起来的一行行数据,为了方便,我们后面就叫它们<strong style="font-size: inherit;line-height: inherit;color: rgb(41, 98, 255);">record</strong>吧。</p> <p style="font-size: inherit;color: inherit;line-height: inherit;margin-top: 1.5em;margin-bottom: 1.5em;">这张表看起来就跟个excel表格一样。excel的数据在硬盘上是一个xx.excel的文件。</p> <p style="font-size: inherit;color: inherit;line-height: inherit;margin-top: 1.5em;margin-bottom: 1.5em;">而上面user表数据,在硬盘上其实也是类似,放在了user.<strong style="font-size: inherit;line-height: inherit;color: rgb(41, 98, 255);">ibd</strong>文件下。含义是user表的innodb data文件,专业点,又叫<strong style="font-size: inherit;line-height: inherit;color: rgb(41, 98, 255);">表空间</strong>。</p> <p style="font-size: inherit;color: inherit;line-height: inherit;margin-top: 1.5em;margin-bottom: 1.5em;">虽然在数据表里,它们看起来是挨在一起的。但实际上在user.ibd里他们被分成很多小份的<strong style="font-size: inherit;line-height: inherit;color: rgb(41, 98, 255);">数据页</strong>,每份大小16k。</p> <p style="font-size: inherit;color: inherit;line-height: inherit;margin-top: 1.5em;margin-bottom: 1.5em;">类似于下面这样。</p> <figure style="font-size: inherit;color: inherit;line-height: inherit;"> <img class="rich_pages wxw-img" data-ratio="0.425" src="/upload/6e85ac402ee4d5454e7f4ff8dd375d88.png" data-type="png" data-w="800" style="font-size: inherit;color: inherit;line-height: inherit;display: block;margin-right: auto;margin-left: auto;" title="ibd文件内部有大量的页"> <figcaption style="line-height: inherit;margin-top: 10px;text-align: center;color: rgb(153, 153, 153);font-size: 0.7em;"> ibd文件内部有大量的页 </figcaption> </figure> <p style="font-size: inherit;color: inherit;line-height: inherit;margin-top: 1.5em;margin-bottom: 1.5em;">我们把视角聚焦一下,放到页上面。</p> <p style="font-size: inherit;color: inherit;line-height: inherit;margin-top: 1.5em;margin-bottom: 1.5em;">整个页<code style="font-size: inherit;line-height: inherit;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(255, 82, 82);background: rgb(248, 248, 248);">16k</code>,不大,但record这么多,一页肯定放不下,所以会分开放到很多页里。并且这16k,也不可能全用来放record对吧。</p> <p style="font-size: inherit;color: inherit;line-height: inherit;margin-top: 1.5em;margin-bottom: 1.5em;">因为record们被分成好多份,放到好多页里了,为了唯一标识具体是哪一页,那就需要引入<strong style="font-size: inherit;line-height: inherit;color: rgb(41, 98, 255);">页号</strong>(其实是一个表空间的地址偏移量)。同时为了把这些数据页给关联起来,于是引入了<strong style="font-size: inherit;line-height: inherit;color: rgb(41, 98, 255);">前后指针</strong>,用于指向前后的页。这些都被加到了<strong style="font-size: inherit;line-height: inherit;color: rgb(41, 98, 255);">页头</strong>里。</p> <p style="font-size: inherit;color: inherit;line-height: inherit;margin-top: 1.5em;margin-bottom: 1.5em;">页是需要读写的,16k说小也不小,写一半电源线被拔了也是有可能发生的,所以为了保证数据页的正确性,还引入了校验码。这个被加到了<strong style="font-size: inherit;line-height: inherit;color: rgb(41, 98, 255);">页尾</strong>。</p> <p style="font-size: inherit;color: inherit;line-height: inherit;margin-top: 1.5em;margin-bottom: 1.5em;">那剩下的空间,才是用来放我们的record的。而record如果行数特别多的话,进入到页内时挨个遍历,效率也不太行,所以为这些数据生成了一个<strong style="font-size: inherit;line-height: inherit;color: rgb(41, 98, 255);">页目录</strong>,具体实现细节不重要。只需要知道,它可以通过<strong style="font-size: inherit;line-height: inherit;color: rgb(41, 98, 255);">二分查找</strong>的方式将查找效率<strong style="font-size: inherit;line-height: inherit;color: rgb(41, 98, 255);">从O(n) 变成O(lgn)</strong>。</p> <figure style="font-size: inherit;color: inherit;line-height: inherit;"> <img class="rich_pages wxw-img" data-ratio="0.7209302325581395" src="/upload/9023be0e44eb3fcf8116b638ab9f1e2a.png" data-type="png" data-w="860" style="font-size: inherit;color: inherit;line-height: inherit;display: block;margin-right: auto;margin-left: auto;" title="页结构"> <figcaption style="line-height: inherit;margin-top: 10px;text-align: center;color: rgb(153, 153, 153);font-size: 0.7em;"> 页结构 </figcaption> </figure> <br> <p style="font-size: inherit;color: inherit;line-height: inherit;margin-top: 1.5em;margin-bottom: 1.5em;"><br></p> <h4 style="color: inherit;line-height: inherit;margin-top: 1.5em;margin-bottom: 1.5em;font-weight: bold;font-size: 1.2em;"><span style="font-size: inherit;color: inherit;line-height: inherit;">从页到索引</span></h4> <p style="font-size: inherit;color: inherit;line-height: inherit;margin-top: 1.5em;margin-bottom: 1.5em;">如果想查一条record,我们可以把表空间里每一页都捞出来,再把里面的record捞出来挨个判断是不是我们要找的。</p> <p style="font-size: inherit;color: inherit;line-height: inherit;margin-top: 1.5em;margin-bottom: 1.5em;">行数量小的时候,这么操作也没啥问题。</p> <p style="font-size: inherit;color: inherit;line-height: inherit;margin-top: 1.5em;margin-bottom: 1.5em;"><strong style="font-size: inherit;line-height: inherit;color: rgb(41, 98, 255);">行数量大了,性能就慢了</strong>,于是为了加速搜索,我们可以在每个数据页里选出<strong style="font-size: inherit;line-height: inherit;color: rgb(41, 98, 255);">主键id最小</strong>的record,而且只需要它们的<strong style="font-size: inherit;line-height: inherit;color: rgb(41, 98, 255);">主键id和所在页的页号</strong>。组成<strong style="font-size: inherit;line-height: inherit;color: rgb(41, 98, 255);">新的record</strong>,放入到一个新生成的一个数据页中,这个<strong style="font-size: inherit;line-height: inherit;color: rgb(41, 98, 255);">新数据页跟之前的页结构没啥区别,而且大小还是16k。</strong></p> <p style="font-size: inherit;color: inherit;line-height: inherit;margin-top: 1.5em;margin-bottom: 1.5em;">但为了跟之前的数据页进行区分。数据页里加入了<strong style="font-size: inherit;line-height: inherit;color: rgb(41, 98, 255);">页层级(page level)</strong>的信息,从0开始往上算。于是页与页之间就有了<strong style="font-size: inherit;line-height: inherit;color: rgb(41, 98, 255);">上下层级</strong>的概念,就像下面这样。</p> <figure style="font-size: inherit;color: inherit;line-height: inherit;"> <img class="rich_pages wxw-img" data-ratio="0.6428571428571429" src="/upload/837a4730abde9375e250abaee67b98b3.png" data-type="png" data-w="840" style="font-size: inherit;color: inherit;line-height: inherit;display: block;margin-right: auto;margin-left: auto;" title="两层B+树结构"> <figcaption style="line-height: inherit;margin-top: 10px;text-align: center;color: rgb(153, 153, 153);font-size: 0.7em;"> 两层B+树结构 </figcaption> </figure> <p style="font-size: inherit;color: inherit;line-height: inherit;margin-top: 1.5em;margin-bottom: 1.5em;">突然页跟页之间看起来就像是一棵倒过来的树了。也就是我们常说的<strong style="font-size: inherit;line-height: inherit;color: rgb(41, 98, 255);">B+树</strong>索引。</p> <p style="font-size: inherit;color: inherit;line-height: inherit;margin-top: 1.5em;margin-bottom: 1.5em;">最下面那一层,<strong style="font-size: inherit;line-height: inherit;color: rgb(41, 98, 255);">page level 为0</strong>,也就是所谓的<strong style="font-size: inherit;line-height: inherit;color: rgb(41, 98, 255);">叶子结点</strong>,其余都叫<strong style="font-size: inherit;line-height: inherit;color: rgb(41, 98, 255);">非叶子结点</strong>。</p> <p style="font-size: inherit;color: inherit;line-height: inherit;margin-top: 1.5em;margin-bottom: 1.5em;">上面展示的是<strong style="font-size: inherit;line-height: inherit;color: rgb(41, 98, 255);">两层</strong>的树,如果数据变多了,我们还可以再通过类似的方法,再往上构建一层。就成了<strong style="font-size: inherit;line-height: inherit;color: rgb(41, 98, 255);">三层</strong>的树。</p> <p style="text-align: center;"><img class="rich_pages wxw-img js_insertlocalimg" data-ratio="0.57109375" data-s="300,640" src="/upload/82c973e41235ef0aede7cb2fc9a5d4c2.png" data-type="png" data-w="1280" style=""></p> <figure style="font-size: inherit;color: inherit;line-height: inherit;"> <figcaption style="line-height: inherit;margin-top: 10px;text-align: center;color: rgb(153, 153, 153);font-size: 0.7em;"> 三层B+树结构 </figcaption> </figure> <p style="font-size: inherit;color: inherit;line-height: inherit;margin-top: 1.5em;margin-bottom: 1.5em;"><br></p> <p style="font-size: inherit;color: inherit;line-height: inherit;margin-top: 1.5em;margin-bottom: 1.5em;">那现在我们就可以通过这样一棵B+树加速查询。举个例子。</p> <p style="font-size: inherit;color: inherit;line-height: inherit;margin-top: 1.5em;margin-bottom: 1.5em;">比方说我们想要查找行数据5。会先从顶层页的record们入手。<strong style="font-size: inherit;line-height: inherit;color: rgb(41, 98, 255);">record里包含了主键id和页号(页地址)</strong>。看下图黄色的箭头,向左最小id是1,向右最小id是7。那id=5的数据如果存在,那必定在左边箭头。于是顺着的record的页地址就到了<code style="font-size: inherit;line-height: inherit;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(255, 82, 82);background: rgb(248, 248, 248);">6号</code>数据页里,再判断id=5&gt;4,所以肯定在右边的数据页里,于是加载<code style="font-size: inherit;line-height: inherit;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(255, 82, 82);background: rgb(248, 248, 248);">105号</code>数据页。在数据页里找到id=5的数据行,完成查询。</p> <figure style="font-size: inherit;color: inherit;line-height: inherit;"> <img class="rich_pages wxw-img" data-ratio="0.5714285714285714" src="/upload/dd7d741311e881f2371b8620fcc70f23.png" data-type="png" data-w="1400" style="font-size: inherit;color: inherit;line-height: inherit;display: block;margin-right: auto;margin-left: auto;" title="B+树查询过程"> <figcaption style="line-height: inherit;margin-top: 10px;text-align: center;color: rgb(153, 153, 153);font-size: 0.7em;"> B+树查询过程 </figcaption> </figure> <p style="font-size: inherit;color: inherit;line-height: inherit;margin-top: 1.5em;margin-bottom: 1.5em;">另外需要注意的是,上面的页的页号并不是连续的,它们在磁盘里也不一定是挨在一起的。</p> <p style="font-size: inherit;color: inherit;line-height: inherit;margin-top: 1.5em;margin-bottom: 1.5em;">这个过程中查询了三个页,如果这三个页都在磁盘中(没有被提前加载到内存中),那么<strong style="font-size: inherit;line-height: inherit;color: rgb(41, 98, 255);">最多</strong>需要经历三次<strong style="font-size: inherit;line-height: inherit;color: rgb(41, 98, 255);">磁盘IO查询</strong>,它们才能被加载到内存中。</p> <p style="font-size: inherit;color: inherit;line-height: inherit;margin-top: 1.5em;margin-bottom: 1.5em;"><br></p> <h3 style="color: inherit;line-height: inherit;margin-top: 1.5em;margin-bottom: 1.5em;font-weight: bold;font-size: 1.3em;border-bottom: 2px solid rgb(21, 101, 192);"><span style="font-size: inherit;line-height: inherit;display: inline-block;font-weight: normal;background: rgb(21, 101, 192);color: rgb(255, 255, 255);padding-top: 3px;padding-right: 10px;padding-left: 10px;border-top-right-radius: 3px;border-top-left-radius: 4px;margin-right: 2px;">B+树承载的记录数量</span></h3> <p style="font-size: inherit;color: inherit;line-height: inherit;margin-top: 1.5em;margin-bottom: 1.5em;">从上面的结构里可以看出B+树的<strong style="font-size: inherit;line-height: inherit;color: rgb(41, 98, 255);">最末级叶子结点</strong>里放了record数据。而<strong style="font-size: inherit;line-height: inherit;color: rgb(41, 98, 255);">非叶子结点</strong>里则放了用来加速查询的索引数据。</p> <p style="font-size: inherit;color: inherit;line-height: inherit;margin-top: 1.5em;margin-bottom: 1.5em;">也就是说</p> <p style="font-size: inherit;color: inherit;line-height: inherit;margin-top: 1.5em;margin-bottom: 1.5em;">同样一个16k的页,非叶子节点里每一条数据都指向一个新的页,而新的页有两种可能。</p> <ul style="font-size: inherit;color: inherit;line-height: inherit;padding-left: 32px;" class="list-paddingleft-1"> <li style="font-size: inherit;color: inherit;line-height: inherit;margin-bottom: 0.5em;"><p><span style="font-size: inherit;color: inherit;line-height: inherit;">如果是末级叶子节点的话,那么里面放的就是一行行record数据。</span></p></li> <li style="font-size: inherit;color: inherit;line-height: inherit;margin-bottom: 0.5em;"><p><span style="font-size: inherit;color: inherit;line-height: inherit;">如果是非叶子节点,那么就会循环继续指向新的数据页。</span></p></li> </ul> <p style="font-size: inherit;color: inherit;line-height: inherit;margin-top: 1.5em;margin-bottom: 1.5em;">假设</p> <ul style="font-size: inherit;color: inherit;line-height: inherit;padding-left: 32px;" class="list-paddingleft-1"> <li style="font-size: inherit;color: inherit;line-height: inherit;margin-bottom: 0.5em;"><p>非叶子结点内指向其他内存页的指针数量为<code style="font-size: inherit;line-height: inherit;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(255, 82, 82);background: rgb(248, 248, 248);">x</code></p></li> <li style="font-size: inherit;color: inherit;line-height: inherit;margin-bottom: 0.5em;"><p>叶子节点内能容纳的record数量为<code style="font-size: inherit;line-height: inherit;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(255, 82, 82);background: rgb(248, 248, 248);">y</code></p></li> <li style="font-size: inherit;color: inherit;line-height: inherit;margin-bottom: 0.5em;"><p>B+树的层数为<code style="font-size: inherit;line-height: inherit;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(255, 82, 82);background: rgb(248, 248, 248);">z</code></p></li> </ul> <figure style="font-size: inherit;color: inherit;line-height: inherit;"> <img class="rich_pages wxw-img" data-ratio="0.574468085106383" src="/upload/2944e313d479581aefa27502c0c464a.png" data-type="png" data-w="940" style="font-size: inherit;color: inherit;line-height: inherit;display: block;margin-right: auto;margin-left: auto;" title="总行数的计算方法"> <figcaption style="line-height: inherit;margin-top: 10px;text-align: center;color: rgb(153, 153, 153);font-size: 0.7em;"> 总行数的计算方法 </figcaption> </figure> <p style="font-size: inherit;color: inherit;line-height: inherit;margin-top: 1.5em;margin-bottom: 1.5em;">那这棵B+树放的<strong style="font-size: inherit;line-height: inherit;color: rgb(41, 98, 255);">行数据总量</strong>等于 <code style="font-size: inherit;line-height: inherit;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(255, 82, 82);background: rgb(248, 248, 248);">(x ^ (z-1)) * y</code>。</p> <p style="font-size: inherit;color: inherit;line-height: inherit;margin-top: 1.5em;margin-bottom: 1.5em;"><br></p> <h4 style="color: inherit;line-height: inherit;margin-top: 1.5em;margin-bottom: 1.5em;font-weight: bold;font-size: 1.2em;"><span style="font-size: inherit;color: inherit;line-height: inherit;">x怎么算</span></h4> <p style="font-size: inherit;color: inherit;line-height: inherit;margin-top: 1.5em;margin-bottom: 1.5em;">我们回去看数据页的结构。</p> <figure style="font-size: inherit;color: inherit;line-height: inherit;"> <img class="rich_pages wxw-img" data-ratio="0.7209302325581395" src="/upload/9023be0e44eb3fcf8116b638ab9f1e2a.png" data-type="png" data-w="860" style="font-size: inherit;color: inherit;line-height: inherit;display: block;margin-right: auto;margin-left: auto;" title="页结构"> <figcaption style="line-height: inherit;margin-top: 10px;text-align: center;color: rgb(153, 153, 153);font-size: 0.7em;"> 页结构 </figcaption> </figure> <p style="font-size: inherit;color: inherit;line-height: inherit;margin-top: 1.5em;margin-bottom: 1.5em;">非叶子节点里主要放索引查询相关的数据,放的是主键和指向页号。</p> <p style="font-size: inherit;color: inherit;line-height: inherit;margin-top: 1.5em;margin-bottom: 1.5em;">主键假设是<code style="font-size: inherit;line-height: inherit;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(255, 82, 82);background: rgb(248, 248, 248);">bigint(8Byte)</code>,而页号在源码里叫<code style="font-size: inherit;line-height: inherit;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(255, 82, 82);background: rgb(248, 248, 248);">FIL_PAGE_OFFSET(4Byte)</code>,那么非叶子节点里的一条数据是<code style="font-size: inherit;line-height: inherit;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(255, 82, 82);background: rgb(248, 248, 248);">12Byte</code>左右。</p> <p style="font-size: inherit;color: inherit;line-height: inherit;margin-top: 1.5em;margin-bottom: 1.5em;">整个数据页<code style="font-size: inherit;line-height: inherit;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(255, 82, 82);background: rgb(248, 248, 248);">16k</code>, 页头页尾那部分数据全加起来大概<code style="font-size: inherit;line-height: inherit;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(255, 82, 82);background: rgb(248, 248, 248);">128Byte</code>,加上页目录毛估占<code style="font-size: inherit;line-height: inherit;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(255, 82, 82);background: rgb(248, 248, 248);">1k</code>吧。那剩下的<strong style="font-size: inherit;line-height: inherit;color: rgb(41, 98, 255);">15k</strong>除以<code style="font-size: inherit;line-height: inherit;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(255, 82, 82);background: rgb(248, 248, 248);">12Byte</code>,等于<code style="font-size: inherit;line-height: inherit;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(255, 82, 82);background: rgb(248, 248, 248);">1280</code>,也就是可以指向<strong style="font-size: inherit;line-height: inherit;color: rgb(41, 98, 255);">x=1280页</strong>。</p> <p style="font-size: inherit;color: inherit;line-height: inherit;margin-top: 1.5em;margin-bottom: 1.5em;">我们常说的二叉树指的是一个结点可以发散出两个新的结点。m叉树一个节点能指向m个新的结点。这个指向新节点的操作就叫<strong style="font-size: inherit;line-height: inherit;color: rgb(41, 98, 255);">扇出(fanout)</strong>。</p> <p style="font-size: inherit;color: inherit;line-height: inherit;margin-top: 1.5em;margin-bottom: 1.5em;">而上面的B+树,它能指向1280个新的节点,恐怖如斯,可以说<strong style="font-size: inherit;line-height: inherit;color: rgb(41, 98, 255);">扇出非常高</strong>了。</p> <p style="font-size: inherit;color: inherit;line-height: inherit;margin-top: 1.5em;margin-bottom: 1.5em;"><br></p> <h4 style="color: inherit;line-height: inherit;margin-top: 1.5em;margin-bottom: 1.5em;font-weight: bold;font-size: 1.2em;"><span style="font-size: inherit;color: inherit;line-height: inherit;">y的计算</span></h4> <p style="font-size: inherit;color: inherit;line-height: inherit;margin-top: 1.5em;margin-bottom: 1.5em;">叶子节点和非叶子节点的数据结构是一样的,所以也假设剩下<code style="font-size: inherit;line-height: inherit;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(255, 82, 82);background: rgb(248, 248, 248);">15kb</code>可以发挥。</p> <p style="font-size: inherit;color: inherit;line-height: inherit;margin-top: 1.5em;margin-bottom: 1.5em;">叶子节点里放的是真正的行数据。假设一条行数据<code style="font-size: inherit;line-height: inherit;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(255, 82, 82);background: rgb(248, 248, 248);">1kb</code>,所以一页里能放<strong style="font-size: inherit;line-height: inherit;color: rgb(41, 98, 255);">y=15行</strong>。</p> <p style="font-size: inherit;color: inherit;line-height: inherit;margin-top: 1.5em;margin-bottom: 1.5em;"><br></p> <h4 style="color: inherit;line-height: inherit;margin-top: 1.5em;margin-bottom: 1.5em;font-weight: bold;font-size: 1.2em;"><span style="font-size: inherit;color: inherit;line-height: inherit;">行总数计算</span></h4> <p style="font-size: inherit;color: inherit;line-height: inherit;margin-top: 1.5em;margin-bottom: 1.5em;">回到 &nbsp;<code style="font-size: inherit;line-height: inherit;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(255, 82, 82);background: rgb(248, 248, 248);">(x ^ (z-1)) * y</code> &nbsp; 这个公式。</p> <p style="font-size: inherit;color: inherit;line-height: inherit;margin-top: 1.5em;margin-bottom: 1.5em;">已知<code style="font-size: inherit;line-height: inherit;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(255, 82, 82);background: rgb(248, 248, 248);">x=1280</code>,<code style="font-size: inherit;line-height: inherit;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(255, 82, 82);background: rgb(248, 248, 248);">y=15</code>。</p> <p style="font-size: inherit;color: inherit;line-height: inherit;margin-top: 1.5em;margin-bottom: 1.5em;">假设B+树是<strong style="font-size: inherit;line-height: inherit;color: rgb(41, 98, 255);">两层</strong>,那<code style="font-size: inherit;line-height: inherit;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(255, 82, 82);background: rgb(248, 248, 248);">z=2</code>。则是<code style="font-size: inherit;line-height: inherit;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(255, 82, 82);background: rgb(248, 248, 248);">(1280 ^ (2-1)) * 15 ≈ 2w</code> </p> <p style="font-size: inherit;color: inherit;line-height: inherit;margin-top: 1.5em;margin-bottom: 1.5em;">假设B+树是<strong style="font-size: inherit;line-height: inherit;color: rgb(41, 98, 255);">三层</strong>,那<code style="font-size: inherit;line-height: inherit;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(255, 82, 82);background: rgb(248, 248, 248);">z=3</code>。则是<code style="font-size: inherit;line-height: inherit;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(255, 82, 82);background: rgb(248, 248, 248);">(1280 ^ (3-1)) * 15 ≈ 2.5kw</code></p> <p style="font-size: inherit;color: inherit;line-height: inherit;margin-top: 1.5em;margin-bottom: 1.5em;"><strong style="font-size: inherit;line-height: inherit;color: rgb(41, 98, 255);">这个2.5kw,就是我们常说的单表建议最大行数2kw的由来。</strong>毕竟再加一层,数据就大得有点离谱了。三层数据页对应最多三次磁盘IO,也比较合理。</p> <p style="font-size: inherit;color: inherit;line-height: inherit;margin-top: 1.5em;margin-bottom: 1.5em;"><br></p> <h3 style="color: inherit;line-height: inherit;margin-top: 1.5em;margin-bottom: 1.5em;font-weight: bold;font-size: 1.3em;border-bottom: 2px solid rgb(21, 101, 192);"><span style="font-size: inherit;line-height: inherit;display: inline-block;font-weight: normal;background: rgb(21, 101, 192);color: rgb(255, 255, 255);padding-top: 3px;padding-right: 10px;padding-left: 10px;border-top-right-radius: 3px;border-top-left-radius: 4px;margin-right: 2px;">行数超一亿就慢了吗?</span></h3> <p style="font-size: inherit;color: inherit;line-height: inherit;margin-top: 1.5em;margin-bottom: 1.5em;">上面假设单行数据用了1kb,所以一个数据页能放个15行数据。</p> <p style="font-size: inherit;color: inherit;line-height: inherit;margin-top: 1.5em;margin-bottom: 1.5em;">如果我单行数据用不了这么多,比如只用了<code style="font-size: inherit;line-height: inherit;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(255, 82, 82);background: rgb(248, 248, 248);">250byte</code>。那么单个数据页能放60行数据。</p> <p style="font-size: inherit;color: inherit;line-height: inherit;margin-top: 1.5em;margin-bottom: 1.5em;">那同样是三层B+树,单表支持的行数就是 <code style="font-size: inherit;line-height: inherit;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(255, 82, 82);background: rgb(248, 248, 248);">(1280 ^ (3-1)) * 60 ≈ 1个亿</code>。</p> <p style="font-size: inherit;color: inherit;line-height: inherit;margin-top: 1.5em;margin-bottom: 1.5em;">你看我一个亿的数据,其实也就三层B+树,在这个B+树里要查到某行数据,最多也是三次磁盘IO。所以并不慢。</p> <p style="font-size: inherit;color: inherit;line-height: inherit;margin-top: 1.5em;margin-bottom: 1.5em;">这就很好的解释了文章开头,为什么我单表1个亿,但查询性能没啥大毛病。</p> <p style="font-size: inherit;color: inherit;line-height: inherit;margin-top: 1.5em;margin-bottom: 1.5em;"><br></p> <h3 style="color: inherit;line-height: inherit;margin-top: 1.5em;margin-bottom: 1.5em;font-weight: bold;font-size: 1.3em;border-bottom: 2px solid rgb(21, 101, 192);"><span style="font-size: inherit;line-height: inherit;display: inline-block;font-weight: normal;background: rgb(21, 101, 192);color: rgb(255, 255, 255);padding-top: 3px;padding-right: 10px;padding-left: 10px;border-top-right-radius: 3px;border-top-left-radius: 4px;margin-right: 2px;">B树承载的记录数量</span></h3> <p style="font-size: inherit;color: inherit;line-height: inherit;margin-top: 1.5em;margin-bottom: 1.5em;">既然都聊到这里了,我们就顺着这个话题多聊一些吧。</p> <p style="font-size: inherit;color: inherit;line-height: inherit;margin-top: 1.5em;margin-bottom: 1.5em;">我们都知道,现在mysql的索引都是B+树,而有一种树,跟B+树很像,叫<strong style="font-size: inherit;line-height: inherit;color: rgb(41, 98, 255);">B树,也叫B-树</strong>。</p> <p style="font-size: inherit;color: inherit;line-height: inherit;margin-top: 1.5em;margin-bottom: 1.5em;">它跟B+树最大的区别在于,<strong style="font-size: inherit;line-height: inherit;color: rgb(41, 98, 255);">B+树只在末级叶子结点处放数据表行数据,而B树则会在叶子和非叶子结点上都放。</strong></p> <p style="font-size: inherit;color: inherit;line-height: inherit;margin-top: 1.5em;margin-bottom: 1.5em;">于是,B树的结构就类似这样</p> <figure style="font-size: inherit;color: inherit;line-height: inherit;"> <img class="rich_pages wxw-img" data-ratio="0.5714285714285714" src="/upload/df477e67866b56871709a4cb11bfb022.png" data-type="png" data-w="1400" style="font-size: inherit;color: inherit;line-height: inherit;display: block;margin-right: auto;margin-left: auto;" title="B树结构"> <figcaption style="line-height: inherit;margin-top: 10px;text-align: center;color: rgb(153, 153, 153);font-size: 0.7em;"> B树结构 </figcaption> </figure> <p style="font-size: inherit;color: inherit;line-height: inherit;margin-top: 1.5em;margin-bottom: 1.5em;">B树将行数据都存在非叶子节点上,假设每个数据页还是16kb,掐头去尾每页剩15kb,并且一条数据表行数据还是占1kb,就算不考虑各种页指针的情况下,也只能放个15条数据。<strong style="font-size: inherit;line-height: inherit;color: rgb(41, 98, 255);">数据页扇出明显变少了。</strong></p> <p style="font-size: inherit;color: inherit;line-height: inherit;margin-top: 1.5em;margin-bottom: 1.5em;">计算可承载的总行数的公式也变成了一个<strong style="font-size: inherit;line-height: inherit;color: rgb(41, 98, 255);">等比数列</strong>。</p> <pre style="font-size: inherit;color: inherit;line-height: inherit;"><code style="margin-right: 2px;margin-left: 2px;line-height: 18px;font-size: 14px;word-spacing: 0px;letter-spacing: 0px;font-family: Consolas, Inconsolata, Courier, monospace;border-radius: 10px;color: rgb(169, 183, 198);background: rgb(40, 43, 46);padding: 0.5em;overflow-wrap: normal !important;word-break: normal !important;overflow: auto !important;display: -webkit-box !important;"><span style="font-size: inherit;line-height: inherit;color: rgb(174, 135, 250);overflow-wrap: inherit !important;word-break: inherit !important;">15</span>&nbsp;+&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(174, 135, 250);overflow-wrap: inherit !important;word-break: inherit !important;">15</span>^<span style="font-size: inherit;line-height: inherit;color: rgb(174, 135, 250);overflow-wrap: inherit !important;word-break: inherit !important;">2</span>&nbsp;+<span style="font-size: inherit;line-height: inherit;color: rgb(174, 135, 250);overflow-wrap: inherit !important;word-break: inherit !important;">15</span>^<span style="font-size: inherit;line-height: inherit;color: rgb(174, 135, 250);overflow-wrap: inherit !important;word-break: inherit !important;">3</span>&nbsp;+&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;">...</span>&nbsp;+&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(174, 135, 250);overflow-wrap: inherit !important;word-break: inherit !important;">15</span>^z<br></code></pre> <p style="font-size: inherit;color: inherit;line-height: inherit;margin-top: 1.5em;margin-bottom: 1.5em;">其中<strong style="font-size: inherit;line-height: inherit;color: rgb(41, 98, 255);">z还是层数</strong>的意思。</p> <p style="font-size: inherit;color: inherit;line-height: inherit;margin-top: 1.5em;margin-bottom: 1.5em;">为了能放<code style="font-size: inherit;line-height: inherit;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(255, 82, 82);background: rgb(248, 248, 248);">2kw</code>左右的数据,需要<code style="font-size: inherit;line-height: inherit;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(255, 82, 82);background: rgb(248, 248, 248);">z&gt;=6</code>。也就是树需要有6层,查一次要访问6个页。假设这6个页并不连续,为了查询其中一条数据,最坏情况需要进行<strong style="font-size: inherit;line-height: inherit;color: rgb(41, 98, 255);">6次磁盘IO</strong>。</p> <p style="font-size: inherit;color: inherit;line-height: inherit;margin-top: 1.5em;margin-bottom: 1.5em;">而B+树同样情况下放2kw数据左右,查一次最多是<strong style="font-size: inherit;line-height: inherit;color: rgb(41, 98, 255);">3次磁盘IO。</strong></p> <p style="font-size: inherit;color: inherit;line-height: inherit;margin-top: 1.5em;margin-bottom: 1.5em;">磁盘IO越多则越慢,这两者在性能上差距略大。</p> <p style="font-size: inherit;color: inherit;line-height: inherit;margin-top: 1.5em;margin-bottom: 1.5em;">为此,<strong style="font-size: inherit;line-height: inherit;color: rgb(41, 98, 255);">B+树比B树更适合成为mysql的索引。</strong></p> <p style="font-size: inherit;color: inherit;line-height: inherit;margin-top: 1.5em;margin-bottom: 1.5em;"><br></p> <h3 style="color: inherit;line-height: inherit;margin-top: 1.5em;margin-bottom: 1.5em;font-weight: bold;font-size: 1.3em;border-bottom: 2px solid rgb(21, 101, 192);"><span style="font-size: inherit;line-height: inherit;display: inline-block;font-weight: normal;background: rgb(21, 101, 192);color: rgb(255, 255, 255);padding-top: 3px;padding-right: 10px;padding-left: 10px;border-top-right-radius: 3px;border-top-left-radius: 4px;margin-right: 2px;">总结</span></h3> <ul style="font-size: inherit;color: inherit;line-height: inherit;padding-left: 32px;" class="list-paddingleft-1"> <li style="font-size: inherit;color: inherit;line-height: inherit;margin-bottom: 0.5em;"><p><span style="font-size: inherit;color: inherit;line-height: inherit;">B+树叶子和非叶子结点的数据页都是16k,且数据结构一致,区别在于叶子节点放的是真实的行数据,而非叶子结点放的是主键和下一个页的地址。</span></p></li> <li style="font-size: inherit;color: inherit;line-height: inherit;margin-bottom: 0.5em;"><p><span style="font-size: inherit;color: inherit;line-height: inherit;">B+树一般有两到三层,由于其高扇出,三层就能支持2kw以上的数据,且一次查询最多1~3次磁盘IO,性能也还行。</span></p></li> <li style="font-size: inherit;color: inherit;line-height: inherit;margin-bottom: 0.5em;"><p><span style="font-size: inherit;color: inherit;line-height: inherit;">存储同样量级的数据,B树比B+树层级更高,因此磁盘IO也更多,所以B+树更适合成为mysql索引。</span></p></li> <li style="font-size: inherit;color: inherit;line-height: inherit;margin-bottom: 0.5em;"><p><span style="font-size: inherit;color: inherit;line-height: inherit;">索引结构不会影响单表最大行数,2kw也只是推荐值,超过了这个值可能会导致B+树层级更高,影响查询性能。</span></p></li> <li style="font-size: inherit;color: inherit;line-height: inherit;margin-bottom: 0.5em;"><p><span style="font-size: inherit;color: inherit;line-height: inherit;">单表最大值还受主键大小和磁盘大小限制。</span></p></li> </ul> <p><br></p> <h3 style="margin-top: 1.5em;margin-bottom: 1.5em;font-weight: bold;font-size: 1.3em;color: inherit;line-height: inherit;border-bottom: 2px solid rgb(21, 101, 192);"><span style="margin-right: 2px;padding-top: 3px;padding-right: 10px;padding-left: 10px;font-size: inherit;line-height: inherit;display: inline-block;font-weight: normal;background: rgb(21, 101, 192);color: rgb(255, 255, 255);border-top-right-radius: 3px;border-top-left-radius: 4px;">参考资料</span></h3> <p>《MYSQL内核:INNODB存储引擎 卷1》<br></p> <p><br></p> <h3 style="color: inherit;line-height: inherit;margin-top: 1.5em;margin-bottom: 1.5em;font-weight: bold;font-size: 1.3em;border-bottom: 2px solid rgb(21, 101, 192);"><span style="font-size: inherit;line-height: inherit;display: inline-block;font-weight: normal;background: rgb(21, 101, 192);color: rgb(255, 255, 255);padding-top: 3px;padding-right: 10px;padding-left: 10px;border-top-right-radius: 3px;border-top-left-radius: 4px;margin-right: 2px;">最后</span></h3> <p style="font-size: inherit;color: inherit;line-height: inherit;margin-top: 1.5em;margin-bottom: 1.5em;"><span style="color: inherit;font-size: inherit;">虽然我在单表里塞了1亿条数据,但这个操作的前提是,我很清楚这不会太影响性能。</span><br></p> <p style="font-size: inherit;color: inherit;line-height: inherit;margin-top: 1.5em;margin-bottom: 1.5em;">这波解释,毫无破绽,无懈可击。</p> <p style="font-size: inherit;color: inherit;line-height: inherit;margin-top: 1.5em;margin-bottom: 1.5em;">到这里,连我自己都被自己说服了。想必实习生也是。</p> <figure style="font-size: inherit;color: inherit;line-height: inherit;"></figure> <figure style="font-size: inherit;color: inherit;line-height: inherit;"></figure> </section>

面试官:为什么要尽量避免使用 IN 和 NOT IN?大部分人都会答错!

作者:微信小助手

<p style="line-height: 1.5em;margin-bottom: 1em;"><span style="font-size: 15px;">IN 和 NOT IN 是比较常用的关键字,为什么要尽量避免呢?</span></p> <h3 data-tool="mdnice编辑器" style="line-height: 1.5em;margin-bottom: 1em;"><span style="color: rgb(0, 122, 170);"><strong><span style="color: rgb(0, 122, 170);font-size: 15px;">1. 效率低</span></strong></span></h3> <p style="line-height: 1.5em;margin-bottom: 1em;"><span style="font-size: 15px;">项目中遇到这么个情况:</span></p> <p style="line-height: 1.5em;margin-bottom: 1em;"><span style="font-size: 15px;">t1表 和 t2表 &nbsp;都是150w条数据,600M的样子,都不算大。</span></p> <p style="line-height: 1.5em;margin-bottom: 1em;"><span style="font-size: 15px;">但是这样一句查询 ↓</span></p> <section style="font-size: 16px;color: rgb(62, 62, 62);line-height: 1.6;letter-spacing: 0px;font-family: &quot;Helvetica Neue&quot;, Helvetica, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif;"> <pre style="font-size: inherit;color: inherit;line-height: inherit;"><code style="margin-right: 2px;margin-left: 2px;line-height: 18px;font-size: 14px;letter-spacing: 0px;font-family: Consolas, Inconsolata, Courier, monospace;border-radius: 0px;color: rgb(169, 183, 198);background: rgb(40, 43, 46);padding: 0.5em;overflow-wrap: normal !important;word-break: normal !important;overflow: auto !important;display: -webkit-box !important;"><span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;">select</span>&nbsp;*&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;">from</span>&nbsp;t1&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;">where</span>&nbsp;phone&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;">not</span>&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;">in</span>&nbsp;(<span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;">select</span>&nbsp;phone&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;">from</span>&nbsp;t2)<br></code></pre> </section> <p style="line-height: 1.5em;margin-bottom: 1em;margin-top: 1em;"><span style="font-size: 15px;">直接就把我跑傻了。</span><span style="font-size: 15px;">。</span><span style="font-size: 15px;">。</span><span style="font-size: 15px;"></span></p> <p style="line-height: 1.5em;margin-bottom: 1em;"><span style="font-size: 15px;">十几分钟,检查了一下 &nbsp;phone在两个表都建了索引,字段类型也是一样的。原来 not in 是不能命中索引的。。。。</span></p> <p style="line-height: 1.5em;margin-bottom: 1em;"><span style="font-size: 15px;">改成 NOT EXISTS 之后查询 20s ,效率真的差好多。</span></p> <pre data-tool="mdnice编辑器"> <section style="font-size: 16px;color: rgb(62, 62, 62);line-height: 1.6;letter-spacing: 0px;font-family: &quot;Helvetica Neue&quot;, Helvetica, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif;"> <pre style="font-size: inherit;color: inherit;line-height: inherit;"><code style="margin-right: 2px;margin-left: 2px;line-height: 18px;font-size: 14px;letter-spacing: 0px;font-family: Consolas, Inconsolata, Courier, monospace;border-radius: 0px;color: rgb(169, 183, 198);background: rgb(40, 43, 46);padding: 0.5em;overflow-wrap: normal !important;word-break: normal !important;overflow: auto !important;display: -webkit-box !important;"><span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;">select</span>&nbsp;*&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;">from</span>&nbsp;t1<br><span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;">where</span>&nbsp;&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;">not</span>&nbsp;&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;">EXISTS</span>&nbsp;(<span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;">select</span>&nbsp;phone&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;">from</span>&nbsp;t2&nbsp;&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;">where</span>&nbsp;t1.phone&nbsp;=t2.phone)</code></pre> </section></pre> <h3 data-tool="mdnice编辑器" style="line-height: 1.5em;margin-bottom: 1em;margin-top: 1em;"><strong><span style="font-size: 15px;color: rgb(0, 122, 170);">2. 容易出现问题,或查询结果有误 (不能更严重的缺点)</span></strong></h3> <p style="line-height: 1.5em;margin-bottom: 1em;"><span style="font-size: 15px;">以 IN 为例。</span></p> <p style="line-height: 1.5em;margin-bottom: 1em;"><span style="font-size: 15px;">建两个表:test1 和 test2</span></p> <section style="font-size: 16px;color: rgb(62, 62, 62);line-height: 1.6;letter-spacing: 0px;font-family: &quot;Helvetica Neue&quot;, Helvetica, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif;"> <pre style="font-size: inherit;color: inherit;line-height: inherit;"><code style="margin-right: 2px;margin-left: 2px;line-height: 18px;font-size: 14px;letter-spacing: 0px;font-family: Consolas, Inconsolata, Courier, monospace;border-radius: 0px;color: rgb(169, 183, 198);background: rgb(40, 43, 46);padding: 0.5em;overflow-wrap: normal !important;word-break: normal !important;overflow: auto !important;display: -webkit-box !important;"><span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;">create</span>&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;">table</span>&nbsp;test1&nbsp;(id1&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;">int</span>)<br><span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;">create</span>&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;">table</span>&nbsp;test2&nbsp;(id2&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;">int</span>)<br><br><span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;">insert</span>&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;">into</span>&nbsp;test1&nbsp;(id1)&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;">values</span>&nbsp;(<span style="font-size: inherit;line-height: inherit;color: rgb(174, 135, 250);overflow-wrap: inherit !important;word-break: inherit !important;">1</span>),(<span style="font-size: inherit;line-height: inherit;color: rgb(174, 135, 250);overflow-wrap: inherit !important;word-break: inherit !important;">2</span>),(<span style="font-size: inherit;line-height: inherit;color: rgb(174, 135, 250);overflow-wrap: inherit !important;word-break: inherit !important;">3</span>)<br><span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;">insert</span>&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;">into</span>&nbsp;test2&nbsp;(id2)&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;">values</span>&nbsp;(<span style="font-size: inherit;line-height: inherit;color: rgb(174, 135, 250);overflow-wrap: inherit !important;word-break: inherit !important;">1</span>),(<span style="font-size: inherit;line-height: inherit;color: rgb(174, 135, 250);overflow-wrap: inherit !important;word-break: inherit !important;">2</span>)<br></code></pre> </section> <p style="line-height: 1.5em;margin-bottom: 1em;margin-top: 1em;"><span style="font-size: 15px;">我想要查询,在test2中存在的 &nbsp;test1中的id 。</span><br></p> <p style="line-height: 1.5em;margin-bottom: 1em;"><span style="font-size: 15px;">使用 IN 的一般写法是:</span></p> <pre data-tool="mdnice编辑器"> <section style="font-size: 16px;color: rgb(62, 62, 62);line-height: 1.6;letter-spacing: 0px;font-family: &quot;Helvetica Neue&quot;, Helvetica, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif;"> <pre style="font-size: inherit;color: inherit;line-height: inherit;"><code style="margin-right: 2px;margin-left: 2px;line-height: 18px;font-size: 14px;letter-spacing: 0px;font-family: Consolas, Inconsolata, Courier, monospace;border-radius: 0px;color: rgb(169, 183, 198);background: rgb(40, 43, 46);padding: 0.5em;overflow-wrap: normal !important;word-break: normal !important;overflow: auto !important;display: -webkit-box !important;"><span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;">select</span>&nbsp;id1&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;">from</span>&nbsp;test1<br><span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;">where</span>&nbsp;id1&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;">in</span>&nbsp;(<span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;">select</span>&nbsp;id2&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;">from</span>&nbsp;test2)<br></code></pre> </section><p style="line-height: 1.5em;margin-bottom: 1em;margin-top: 1em;"><span style="font-size: 15px;font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, &quot;Helvetica Neue&quot;, &quot;PingFang SC&quot;, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei UI&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif;">结果是:</span></p></pre> <figure data-tool="mdnice编辑器" style="text-align: center;"> <img class="rich_pages wxw-img" data-ratio="0.8783783783783784" src="/upload/bd5a05588f4dccefb2f004ceeb0e6ef5.jpg" data-type="jpeg" data-w="74"> </figure> <p style="line-height: 1.5em;margin-bottom: 1em;margin-top: 1em;"><span style="font-size: 15px;">OK 木有问题!</span></p> <p style="line-height: 1.5em;margin-bottom: 1em;"><span style="font-size: 15px;">但是如果我一时手滑,写成了:</span></p> <pre data-tool="mdnice编辑器"> <section style="font-size: 16px;color: rgb(62, 62, 62);line-height: 1.6;letter-spacing: 0px;font-family: &quot;Helvetica Neue&quot;, Helvetica, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif;"> <pre style="font-size: inherit;color: inherit;line-height: inherit;"><code style="margin-right: 2px;margin-left: 2px;line-height: 18px;font-size: 14px;letter-spacing: 0px;font-family: Consolas, Inconsolata, Courier, monospace;border-radius: 0px;color: rgb(169, 183, 198);background: rgb(40, 43, 46);padding: 0.5em;overflow-wrap: normal !important;word-break: normal !important;overflow: auto !important;display: -webkit-box !important;"><span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;">select</span>&nbsp;id1&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;">from</span>&nbsp;test1<br><span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;">where</span>&nbsp;id1&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;">in</span>&nbsp;(<span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;">select</span>&nbsp;id1&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;">from</span>&nbsp;test2)<br></code></pre> </section><p style="line-height: 1.5em;margin-bottom: 1em;margin-top: 1em;"><span style="font-size: 15px;font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, &quot;Helvetica Neue&quot;, &quot;PingFang SC&quot;, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei UI&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif;">不小心把id2写成id1了 ,会怎么样呢?</span></p></pre> <p style="line-height: 1.5em;margin-bottom: 1em;"><span style="font-size: 15px;">结果是:</span></p> <figure data-tool="mdnice编辑器" style="text-align: center;"> <img class="rich_pages wxw-img" data-ratio="1.0886075949367089" src="/upload/a43a5d3c3886b512c4932769baac8ad.jpg" data-type="jpeg" data-w="79"> </figure> <p style="line-height: 1.5em;margin-bottom: 1em;margin-top: 1em;"><strong><span style="font-size: 15px;">EXCUSE ME!为什么不报错?</span></strong><span style="font-size: 15px;"></span></p> <p style="line-height: 1.5em;margin-bottom: 1em;"><span style="font-size: 15px;">单独查询&nbsp;select id1 from test2&nbsp;是一定会报错: <strong>消息 207,级别 16,状态 1,第 11 行 列名 'id1' 无效。</strong></span></p> <p style="line-height: 1.5em;margin-bottom: 1em;"><span style="font-size: 15px;">然而使用了IN的子查询就是这么敷衍,直接查出 1 2 3</span></p> <p style="line-height: 1.5em;margin-bottom: 1em;"><span style="font-size: 15px;">这仅仅是容易出错的情况,自己不写错还没啥事儿,下面来看一下 NOT IN 直接查出错误结果的情况:</span></p> <p style="line-height: 1.5em;margin-bottom: 1em;"><span style="font-size: 15px;">给test2插入一个空值:</span></p> <pre data-tool="mdnice编辑器"> <section style="font-size: 16px;color: rgb(62, 62, 62);line-height: 1.6;letter-spacing: 0px;font-family: &quot;Helvetica Neue&quot;, Helvetica, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif;"> <pre style="font-size: inherit;color: inherit;line-height: inherit;"><code style="margin-right: 2px;margin-left: 2px;line-height: 18px;font-size: 14px;letter-spacing: 0px;font-family: Consolas, Inconsolata, Courier, monospace;border-radius: 0px;color: rgb(169, 183, 198);background: rgb(40, 43, 46);padding: 0.5em;overflow-wrap: normal !important;word-break: normal !important;overflow: auto !important;display: -webkit-box !important;"><span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;">insert</span>&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;">into</span>&nbsp;test2&nbsp;(id2)&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;">values</span>&nbsp;(<span style="font-size: inherit;line-height: inherit;color: rgb(174, 135, 250);overflow-wrap: inherit !important;word-break: inherit !important;">NULL</span>)<br></code></pre> </section><p style="line-height: 1.5em;margin-bottom: 1em;margin-top: 1em;"><span style="font-size: 15px;font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, &quot;Helvetica Neue&quot;, &quot;PingFang SC&quot;, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei UI&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif;">我想要查询,在test2中不存在的 test1中的 id 。</span><span style="font-size: 15px;"></span></p></pre> <pre data-tool="mdnice编辑器"> <section style="font-size: 16px;color: rgb(62, 62, 62);line-height: 1.6;letter-spacing: 0px;font-family: &quot;Helvetica Neue&quot;, Helvetica, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif;"> <pre style="font-size: inherit;color: inherit;line-height: inherit;"><code style="margin-right: 2px;margin-left: 2px;line-height: 18px;font-size: 14px;letter-spacing: 0px;font-family: Consolas, Inconsolata, Courier, monospace;border-radius: 0px;color: rgb(169, 183, 198);background: rgb(40, 43, 46);padding: 0.5em;overflow-wrap: normal !important;word-break: normal !important;overflow: auto !important;display: -webkit-box !important;"><span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;">select</span>&nbsp;id1&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;">from</span>&nbsp;test1<br><span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;">where</span>&nbsp;id1&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;">not</span>&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;">in</span>&nbsp;(<span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;">select</span>&nbsp;id2&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;">from</span>&nbsp;test2)<br></code></pre> </section><p style="line-height: 1.5em;margin-bottom: 1em;margin-top: 1em;"><span style="font-size: 15px;font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, &quot;Helvetica Neue&quot;, &quot;PingFang SC&quot;, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei UI&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif;">结果是:</span><span style="font-size: 15px;"></span></p></pre> <figure data-tool="mdnice编辑器" style="text-align: center;"> <img class="rich_pages wxw-img" data-ratio="0.9358974358974359" src="/upload/941d7505bf2a90d61ddcdb12fbe74f19.jpg" data-type="jpeg" data-w="78"> </figure> <p style="line-height: 1.5em;margin-bottom: 1em;margin-top: 1em;"><span style="font-size: 15px;">空白!显然这个结果不是我们想要的。我们想要3。</span></p> <p style="line-height: 1.5em;margin-bottom: 1em;margin-top: 1em;"><strong><span style="font-size: 15px;">为什么会这样呢?</span></strong><span style="font-size: 15px;"></span></p> <p style="line-height: 1.5em;margin-bottom: 1.75em;"><span style="font-size: 15px;">原因是:NULL不等于任何非空的值啊!如果id2只有1和2, 那么3&lt;&gt;1 且 3&lt;&gt;2 所以3输出了,但是 id2包含空值,那么 3也不等于NULL 所以它不会输出。</span></p> <section data-mpa-template="t"> <section data-id="87856" data-color="rgb(191, 17, 18)" data-custom="rgb(191, 17, 18)" style="border-width: 0px;border-style: none;border-color: initial;"> <section style="margin-top: 1em;margin-bottom: 1em;color: rgb(3, 113, 213);font-size: 14px;line-height: 20px;clear: both;border-color: rgb(3, 113, 213);"> <section data-original-title="" title="" style="padding: 5px 10px;display: inline-block;color: rgb(255, 255, 255);font-weight: bold;border-top-left-radius: 3px;border-bottom-left-radius: 3px;background-color: rgb(3, 113, 213);"> 》》 </section> <section data-bgless="lighten" data-brushtype="text" data-bglessp="20%" style="padding: 5px 10px;display: inline-block;color: rgb(255, 255, 255);font-weight: bold;border-top-right-radius: 3px;border-bottom-right-radius: 3px;background-color: rgb(3, 113, 213);"> HOW ? </section> </section> </section> </section> <p><br></p> <h3 data-tool="mdnice编辑器" style="line-height: 1.5em;margin-bottom: 1em;"><span style="color: rgb(0, 122, 170);"><strong><span style="font-size: 15px;">1. 用 EXISTS 或 NOT EXISTS 代替</span></strong></span><span style="font-size: 15px;"></span></h3> <pre data-tool="mdnice编辑器"> <section style="font-size: 16px;color: rgb(62, 62, 62);line-height: 1.6;letter-spacing: 0px;font-family: &quot;Helvetica Neue&quot;, Helvetica, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif;"> <pre style="font-size: inherit;color: inherit;line-height: inherit;"><code style="margin-right: 2px;margin-left: 2px;line-height: 18px;font-size: 14px;letter-spacing: 0px;font-family: Consolas, Inconsolata, Courier, monospace;border-radius: 0px;color: rgb(169, 183, 198);background: rgb(40, 43, 46);padding: 0.5em;overflow-wrap: normal !important;word-break: normal !important;overflow: auto !important;display: -webkit-box !important;"><span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;">select</span>&nbsp;*&nbsp;&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;">from</span>&nbsp;test1<br>&nbsp;&nbsp;&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;">where</span>&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;">EXISTS</span>&nbsp;(<span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;">select</span>&nbsp;*&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;">from</span>&nbsp;test2&nbsp;&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;">where</span>&nbsp;id2&nbsp;=&nbsp;id1&nbsp;)<br><br><span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;">select</span>&nbsp;*&nbsp;&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;">FROM</span>&nbsp;test1<br>&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;">where</span>&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;">NOT</span>&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;">EXISTS</span>&nbsp;(<span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;">select</span>&nbsp;*&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;">from</span>&nbsp;test2&nbsp;&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;">where</span>&nbsp;id2&nbsp;=&nbsp;id1&nbsp;)<br></code></pre> </section><p style="line-height: 1.5em;margin-bottom: 1em;margin-top: 1em;"><span style="color: rgb(0, 122, 170);"><strong><span style="color: rgb(0, 122, 170);font-size: 15px;font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, &quot;Helvetica Neue&quot;, &quot;PingFang SC&quot;, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei UI&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif;">2. 用JOIN 代替</span></strong></span></p></pre> <pre data-tool="mdnice编辑器"> <section style="font-size: 16px;color: rgb(62, 62, 62);line-height: 1.6;letter-spacing: 0px;font-family: &quot;Helvetica Neue&quot;, Helvetica, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif;"> <pre style="font-size: inherit;color: inherit;line-height: inherit;"><code style="margin-right: 2px;margin-left: 2px;line-height: 18px;font-size: 14px;letter-spacing: 0px;font-family: Consolas, Inconsolata, Courier, monospace;border-radius: 0px;color: rgb(169, 183, 198);background: rgb(40, 43, 46);padding: 0.5em;overflow-wrap: normal !important;word-break: normal !important;overflow: auto !important;display: -webkit-box !important;">&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;">select</span>&nbsp;id1&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;">from</span>&nbsp;test1<br>&nbsp;&nbsp;&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;">INNER</span>&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;">JOIN</span>&nbsp;test2&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;">ON</span>&nbsp;id2&nbsp;=&nbsp;id1<br><br>&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;">select</span>&nbsp;id1&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;">from</span>&nbsp;test1<br>&nbsp;&nbsp;&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;">LEFT</span>&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;">JOIN</span>&nbsp;test2&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;">ON</span>&nbsp;id2&nbsp;=&nbsp;id1<br>&nbsp;&nbsp;&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;">where</span>&nbsp;id2&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);overflow-wrap: inherit !important;word-break: inherit !important;">IS</span>&nbsp;<span style="font-size: inherit;line-height: inherit;color: rgb(174, 135, 250);overflow-wrap: inherit !important;word-break: inherit !important;">NULL</span><br></code></pre> </section><p style="line-height: 1.5em;margin-bottom: 1em;margin-top: 1em;"><span style="font-size: 15px;font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, &quot;Helvetica Neue&quot;, &quot;PingFang SC&quot;, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei UI&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif;">妥妥的没有问题了!</span><span style="font-size: 15px;"></span></p></pre> <p style="line-height: 1.5em;margin-bottom: 1em;"><strong><span style="font-size: 15px;">PS:</span></strong><span style="font-size: 15px;">那我们死活都不能用 IN 和 NOT IN 了么?并没有,一位大神曾经说过,如果是&nbsp;时,可以使用。如 IN (0,1,2)。</span></p>

16 张图解 | 淘宝 10年架构演进

作者:微信小助手

<section data-tool="mdnice编辑器" data-website="https://www.mdnice.com" style="padding-right: 10px;padding-left: 10px;text-align: left;line-height: 1.6;letter-spacing: 0.034em;color: rgb(63, 63, 63);font-size: 16px;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, 'PingFang SC', Cambria, Cochin, Georgia, Times, 'Times New Roman', serif;" data-mpa-powered-by="yiban.io"> <p data-tool="mdnice编辑器" style="padding-bottom: 8px;padding-top: 1em;color: rgb(74, 74, 74);line-height: 1.75em;">之前有读者留言让写一篇大型网站的架构演进过程,发现下面这篇文章讲解得很详细,特此分享给大家,相信看完会有所收获。</p> <p data-tool="mdnice编辑器" style="padding-bottom: 8px;padding-top: 1em;color: rgb(74, 74, 74);line-height: 1.75em;">文章在介绍一些基本概念后,按照以下过程阐述了整个架构的演进过程:</p> <ul data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: black;" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 单机架构 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 第一次演进:Tomcat与数据库分开部署 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 第二次演进:引入本地缓存和分布式缓存 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 第三次演进:引入反向代理实现负载均衡 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 第四次演进:数据库读写分离 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 第五次演进:数据库按业务分库 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 第六次演进:把大表拆分为小表 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 第七次演进:使用LVS或F5来使多个Nginx负载均衡 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 第八次演进:通过DNS轮询实现机房间的负载均衡 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 第九次演进:引入NoSQL数据库和搜索引擎等技术 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 第十次演进:大应用拆分为小应用 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 第十一次演进:复用的功能抽离成微服务 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 第十二次演进:引入企业服务总线ESB屏蔽服务接口的访问差异 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 第十三次演进:引入容器化技术实现运行环境隔离与动态服务管理 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 第十四次演进:以云平台承载系统 </section></li> </ul> <blockquote data-tool="mdnice编辑器" style="border-top: none;border-right: none;border-bottom: none;font-size: 0.9em;overflow: auto;background: rgb(227, 242, 253);color: rgb(106, 115, 125);margin-bottom: 20px;margin-top: 20px;padding: 15px 20px;line-height: 27px;border-left-color: rgb(100, 149, 237);"> <p style="line-height: 26px;font-size: 15px;color: rgb(89, 89, 89);">原文作者:huashiou,链接:https://segmentfault.com/a/1190000018626163</p> </blockquote> <h2 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;color: black;border-bottom: 2px solid rgb(65, 105, 225);font-size: 1.3em;"><span style="display: inline-block;background: rgb(65, 105, 225);color: rgb(255, 255, 255);padding: 3px 10px 1px;border-top-right-radius: 3px;border-top-left-radius: 3px;margin-right: 3px;">1. 概述</span><span style="display: inline-block;vertical-align: bottom;border-bottom: 36px solid #efebe9;border-right: 20px solid transparent;"> </span></h2> <p data-tool="mdnice编辑器" style="padding-bottom: 8px;padding-top: 1em;color: rgb(74, 74, 74);line-height: 1.75em;">本文以淘宝作为例子,介绍从一百个到千万级并发情况下服务端的架构的演进过程,同时列举出每个演进阶段会遇到的相关技术,让大家对架构的演进有一个整体的认知,文章最后汇总了一些架构设计的原则。</p> <blockquote data-tool="mdnice编辑器" style="border-top: none;border-right: none;border-bottom: none;font-size: 0.9em;overflow: auto;background: rgb(227, 242, 253);color: rgb(106, 115, 125);margin-bottom: 20px;margin-top: 20px;padding: 15px 20px;line-height: 27px;border-left-color: rgb(100, 149, 237);"> <p style="line-height: 26px;font-size: 15px;color: rgb(89, 89, 89);"><strong style="line-height: 1.75em;color: rgb(48, 79, 254);">特别说明:本文以淘宝为例仅仅是为了便于说明演进过程可能遇到的问题,并非是淘宝真正的技术演进路径</strong></p> </blockquote> <h2 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;color: black;border-bottom: 2px solid rgb(65, 105, 225);font-size: 1.3em;"><span style="display: inline-block;background: rgb(65, 105, 225);color: rgb(255, 255, 255);padding: 3px 10px 1px;border-top-right-radius: 3px;border-top-left-radius: 3px;margin-right: 3px;">2. 基本概念</span><span style="display: inline-block;vertical-align: bottom;border-bottom: 36px solid #efebe9;border-right: 20px solid transparent;"> </span></h2> <p data-tool="mdnice编辑器" style="padding-bottom: 8px;padding-top: 1em;color: rgb(74, 74, 74);line-height: 1.75em;">在介绍架构之前,为了避免部分读者对架构设计中的一些概念不了解,下面对几个最基础的概念进行介绍:</p> <ul data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: black;" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> <strong style="line-height: 1.75em;color: rgb(48, 79, 254);">分布式</strong>系统中的多个模块在不同服务器上部署,即可称为分布式系统,如Tomcat和数据库分别部署在不同的服务器上,或两个相同功能的Tomcat分别部署在不同服务器上 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> <strong style="line-height: 1.75em;color: rgb(48, 79, 254);">高可用</strong>系统中部分节点失效时,其他节点能够接替它继续提供服务,则可认为系统具有高可用性 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> <strong style="line-height: 1.75em;color: rgb(48, 79, 254);">集群</strong>一个特定领域的软件部署在多台服务器上并作为一个整体提供一类服务,这个整体称为集群。如Zookeeper中的Master和Slave分别部署在多台服务器上,共同组成一个整体提供集中配置服务。在常见的集群中,客户端往往能够连接任意一个节点获得服务,并且当集群中一个节点掉线时,其他节点往往能够自动的接替它继续提供服务,这时候说明集群具有高可用性 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> <strong style="line-height: 1.75em;color: rgb(48, 79, 254);">负载均衡</strong>请求发送到系统时,通过某些方式把请求均匀分发到多个节点上,使系统中每个节点能够均匀的处理请求负载,则可认为系统是负载均衡的 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> <strong style="line-height: 1.75em;color: rgb(48, 79, 254);">正向代理和反向代理</strong>系统内部要访问外部网络时,统一通过一个代理服务器把请求转发出去,在外部网络看来就是代理服务器发起的访问,此时代理服务器实现的是正向代理;当外部请求进入系统时,代理服务器把该请求转发到系统中的某台服务器上,对外部请求来说,与之交互的只有代理服务器,此时代理服务器实现的是反向代理。简单来说,正向代理是代理服务器代替系统内部来访问外部网络的过程,反向代理是外部请求访问系统时通过代理服务器转发到内部服务器的过程。 </section></li> </ul> <h2 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;color: black;border-bottom: 2px solid rgb(65, 105, 225);font-size: 1.3em;"><span style="display: inline-block;background: rgb(65, 105, 225);color: rgb(255, 255, 255);padding: 3px 10px 1px;border-top-right-radius: 3px;border-top-left-radius: 3px;margin-right: 3px;">3. 架构演进</span><span style="display: inline-block;vertical-align: bottom;border-bottom: 36px solid #efebe9;border-right: 20px solid transparent;"> </span></h2> <h3 data-tool="mdnice编辑器" style="margin-bottom: 15px;font-weight: bold;color: black;font-size: 20px;margin-top: 1.2em;"><span style="font-size: 17px;color: rgb(65, 105, 225);">3.1 单机架构</span></h3> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-ratio="0.3626943005181347" src="/upload/81934cbd4c06726687113c0c2644c93d.png" data-type="png" data-w="579" style="display: block;margin-right: auto;margin-left: auto;border-radius: 4px;margin-bottom: 25px;"> </figure> <p data-tool="mdnice编辑器" style="padding-bottom: 8px;padding-top: 1em;color: rgb(74, 74, 74);line-height: 1.75em;">以淘宝作为例子。在网站最初时,应用数量与用户数都较少,可以把Tomcat和数据库部署在同一台服务器上。</p> <p data-tool="mdnice编辑器" style="padding-bottom: 8px;padding-top: 1em;color: rgb(74, 74, 74);line-height: 1.75em;">浏览器往www.taobao.com发起请求时,首先经过DNS服务器(域名系统)把域名转换为实际IP地址10.102.4.1,浏览器转而访问该IP对应的Tomcat。</p> <blockquote data-tool="mdnice编辑器" style="border-top: none;border-right: none;border-bottom: none;font-size: 0.9em;overflow: auto;background: rgb(227, 242, 253);color: rgb(106, 115, 125);margin-bottom: 20px;margin-top: 20px;padding: 15px 20px;line-height: 27px;border-left-color: rgb(100, 149, 237);"> <p style="line-height: 26px;font-size: 15px;color: rgb(89, 89, 89);">随着用户数的增长,Tomcat和数据库之间竞争资源,单机性能不足以支撑业务</p> </blockquote> <h3 data-tool="mdnice编辑器" style="margin-bottom: 15px;font-weight: bold;color: black;font-size: 20px;margin-top: 1.2em;"><span style="font-size: 17px;color: rgb(65, 105, 225);">3.2 第一次演进:Tomcat与数据库分开部署</span></h3> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-ratio="0.35628227194492257" src="/upload/900e94644bff2abfbcb2f677a5cf1041.png" data-type="png" data-w="581" style="display: block;margin-right: auto;margin-left: auto;border-radius: 4px;margin-bottom: 25px;"> </figure> <p data-tool="mdnice编辑器" style="padding-bottom: 8px;padding-top: 1em;color: rgb(74, 74, 74);line-height: 1.75em;">Tomcat和数据库分别独占服务器资源,显著提高两者各自性能。</p> <blockquote data-tool="mdnice编辑器" style="border-top: none;border-right: none;border-bottom: none;font-size: 0.9em;overflow: auto;background: rgb(227, 242, 253);color: rgb(106, 115, 125);margin-bottom: 20px;margin-top: 20px;padding: 15px 20px;line-height: 27px;border-left-color: rgb(100, 149, 237);"> <p style="line-height: 26px;font-size: 15px;color: rgb(89, 89, 89);">随着用户数的增长,并发读写数据库成为瓶颈</p> </blockquote> <h3 data-tool="mdnice编辑器" style="margin-bottom: 15px;font-weight: bold;color: black;font-size: 20px;margin-top: 1.2em;"><span style="font-size: 17px;color: rgb(65, 105, 225);">3.3 第二次演进:引入本地缓存和分布式缓存</span></h3> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-ratio="0.5524956970740104" src="/upload/c1d4aed4602053d2e83f41482e2053ce.png" data-type="png" data-w="581" style="display: block;margin-right: auto;margin-left: auto;border-radius: 4px;margin-bottom: 25px;"> </figure> <p data-tool="mdnice编辑器" style="padding-bottom: 8px;padding-top: 1em;color: rgb(74, 74, 74);line-height: 1.75em;">在Tomcat同服务器上或同JVM中增加本地缓存,并在外部增加分布式缓存,缓存热门商品信息或热门商品的html页面等。</p> <p data-tool="mdnice编辑器" style="padding-bottom: 8px;padding-top: 1em;color: rgb(74, 74, 74);line-height: 1.75em;">通过缓存能把绝大多数请求在读写数据库前拦截掉,大大降低数据库压力。</p> <p data-tool="mdnice编辑器" style="padding-bottom: 8px;padding-top: 1em;color: rgb(74, 74, 74);line-height: 1.75em;">其中涉及的技术包括:使用memcached作为本地缓存,使用Redis作为分布式缓存,还会涉及缓存一致性、缓存穿透/击穿、缓存雪崩、热点数据集中失效等问题。</p> <blockquote data-tool="mdnice编辑器" style="border-top: none;border-right: none;border-bottom: none;font-size: 0.9em;overflow: auto;background: rgb(227, 242, 253);color: rgb(106, 115, 125);margin-bottom: 20px;margin-top: 20px;padding: 15px 20px;line-height: 27px;border-left-color: rgb(100, 149, 237);"> <p style="line-height: 26px;font-size: 15px;color: rgb(89, 89, 89);">缓存抗住了大部分的访问请求,随着用户数的增长,并发压力主要落在单机的Tomcat上,响应逐渐变慢</p> </blockquote> <h3 data-tool="mdnice编辑器" style="margin-bottom: 15px;font-weight: bold;color: black;font-size: 20px;margin-top: 1.2em;"><span style="font-size: 17px;color: rgb(65, 105, 225);">3.4 第三次演进:引入反向代理实现负载均衡</span></h3> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-ratio="0.8229426433915212" src="/upload/508d78c6d78cb665b736ddd054cf77f7.png" data-type="png" data-w="401" style="display: block;margin-right: auto;margin-left: auto;border-radius: 4px;margin-bottom: 25px;"> </figure> <p data-tool="mdnice编辑器" style="padding-bottom: 8px;padding-top: 1em;color: rgb(74, 74, 74);line-height: 1.75em;">在多台服务器上分别部署Tomcat,使用反向代理软件(Nginx)把请求均匀分发到每个Tomcat中。</p> <p data-tool="mdnice编辑器" style="padding-bottom: 8px;padding-top: 1em;color: rgb(74, 74, 74);line-height: 1.75em;">此处假设Tomcat最多支持100个并发,Nginx最多支持50000个并发,那么理论上Nginx把请求分发到500个Tomcat上,就能抗住50000个并发。</p> <p data-tool="mdnice编辑器" style="padding-bottom: 8px;padding-top: 1em;color: rgb(74, 74, 74);line-height: 1.75em;">其中涉及的技术包括:Nginx、HAProxy,两者都是工作在网络第七层的反向代理软件,主要支持http协议,还会涉及session共享、文件上传下载的问题。</p> <blockquote data-tool="mdnice编辑器" style="border-top: none;border-right: none;border-bottom: none;font-size: 0.9em;overflow: auto;background: rgb(227, 242, 253);color: rgb(106, 115, 125);margin-bottom: 20px;margin-top: 20px;padding: 15px 20px;line-height: 27px;border-left-color: rgb(100, 149, 237);"> <p style="line-height: 26px;font-size: 15px;color: rgb(89, 89, 89);">反向代理使应用服务器可支持的并发量大大增加,但并发量的增长也意味着更多请求穿透到数据库,单机的数据库最终成为瓶颈</p> </blockquote> <h3 data-tool="mdnice编辑器" style="margin-bottom: 15px;font-weight: bold;color: black;font-size: 20px;margin-top: 1.2em;"><span style="font-size: 17px;color: rgb(65, 105, 225);">3.5 第四次演进:数据库读写分离</span></h3> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-ratio="0.6861826697892272" src="/upload/a76faeff052e20533f4b0bdf2a83e31e.png" data-type="png" data-w="427" style="display: block;margin-right: auto;margin-left: auto;border-radius: 4px;margin-bottom: 25px;"> </figure> <p data-tool="mdnice编辑器" style="padding-bottom: 8px;padding-top: 1em;color: rgb(74, 74, 74);line-height: 1.75em;">把数据库划分为读库和写库,读库可以有多个,通过同步机制把写库的数据同步到读库,对于需要查询最新写入数据场景,可通过在缓存中多写一份,通过缓存获得最新数据。</p> <p data-tool="mdnice编辑器" style="padding-bottom: 8px;padding-top: 1em;color: rgb(74, 74, 74);line-height: 1.75em;">其中涉及的技术包括:Mycat,它是数据库中间件,可通过它来组织数据库的分离读写和分库分表,客户端通过它来访问下层数据库,还会涉及数据同步,数据一致性的问题。</p> <blockquote data-tool="mdnice编辑器" style="border-top: none;border-right: none;border-bottom: none;font-size: 0.9em;overflow: auto;background: rgb(227, 242, 253);color: rgb(106, 115, 125);margin-bottom: 20px;margin-top: 20px;padding: 15px 20px;line-height: 27px;border-left-color: rgb(100, 149, 237);"> <p style="line-height: 26px;font-size: 15px;color: rgb(89, 89, 89);">业务逐渐变多,不同业务之间的访问量差距较大,不同业务直接竞争数据库,相互影响性能</p> </blockquote> <h3 data-tool="mdnice编辑器" style="margin-bottom: 15px;font-weight: bold;color: black;font-size: 20px;margin-top: 1.2em;"><span style="font-size: 17px;color: rgb(65, 105, 225);">3.6 第五次演进:数据库按业务分库</span></h3> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-ratio="0.8230912476722533" src="/upload/fa5df3fd2f3ced39d15d0f4e834ca0d0.png" data-type="png" data-w="537" style="display: block;margin-right: auto;margin-left: auto;border-radius: 4px;margin-bottom: 25px;"> </figure> <p data-tool="mdnice编辑器" style="padding-bottom: 8px;padding-top: 1em;color: rgb(74, 74, 74);line-height: 1.75em;">把不同业务的数据保存到不同的数据库中,使业务之间的资源竞争降低,对于访问量大的业务,可以部署更多的服务器来支撑。</p> <p data-tool="mdnice编辑器" style="padding-bottom: 8px;padding-top: 1em;color: rgb(74, 74, 74);line-height: 1.75em;">这样同时导致跨业务的表无法直接做关联分析,需要通过其他途径来解决,但这不是本文讨论的重点,有兴趣的可以自行搜索解决方案。</p> <blockquote data-tool="mdnice编辑器" style="border-top: none;border-right: none;border-bottom: none;font-size: 0.9em;overflow: auto;background: rgb(227, 242, 253);color: rgb(106, 115, 125);margin-bottom: 20px;margin-top: 20px;padding: 15px 20px;line-height: 27px;border-left-color: rgb(100, 149, 237);"> <p style="line-height: 26px;font-size: 15px;color: rgb(89, 89, 89);">随着用户数的增长,单机的写库会逐渐会达到性能瓶颈</p> </blockquote> <h3 data-tool="mdnice编辑器" style="margin-bottom: 15px;font-weight: bold;color: black;font-size: 20px;margin-top: 1.2em;"><span style="font-size: 17px;color: rgb(65, 105, 225);">3.7 第六次演进:把大表拆分为小表</span></h3> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-ratio="0.7568493150684932" src="/upload/91c4a43269a8626cc6a0f18917bca5b8.png" data-type="png" data-w="584" style="display: block;margin-right: auto;margin-left: auto;border-radius: 4px;margin-bottom: 25px;"> </figure> <p data-tool="mdnice编辑器" style="padding-bottom: 8px;padding-top: 1em;color: rgb(74, 74, 74);line-height: 1.75em;">比如针对评论数据,可按照商品ID进行hash,路由到对应的表中存储;针对支付记录,可按照小时创建表,每个小时表继续拆分为小表,使用用户ID或记录编号来路由数据。</p> <p data-tool="mdnice编辑器" style="padding-bottom: 8px;padding-top: 1em;color: rgb(74, 74, 74);line-height: 1.75em;">只要实时操作的表数据量足够小,请求能够足够均匀的分发到多台服务器上的小表,那数据库就能通过水平扩展的方式来提高性能。</p> <p data-tool="mdnice编辑器" style="padding-bottom: 8px;padding-top: 1em;color: rgb(74, 74, 74);line-height: 1.75em;">其中前面提到的Mycat也支持在大表拆分为小表情况下的访问控制。</p> <p data-tool="mdnice编辑器" style="padding-bottom: 8px;padding-top: 1em;color: rgb(74, 74, 74);line-height: 1.75em;">这种做法显著的增加了数据库运维的难度,对DBA的要求较高。数据库设计到这种结构时,已经可以称为分布式数据库,但是这只是一个逻辑的数据库整体,数据库里不同的组成部分是由不同的组件单独来实现的,如分库分表的管理和请求分发,由Mycat实现,SQL的解析由单机的数据库实现,读写分离可能由网关和消息队列来实现,查询结果的汇总可能由数据库接口层来实现等等,这种架构其实是MPP(大规模并行处理)架构的一类实现。</p> <p data-tool="mdnice编辑器" style="padding-bottom: 8px;padding-top: 1em;color: rgb(74, 74, 74);line-height: 1.75em;">目前开源和商用都已经有不少MPP数据库,开源中比较流行的有Greenplum、TiDB、Postgresql XC、HAWQ等,商用的如南大通用的GBase、睿帆科技的雪球DB、华为的LibrA等等。</p> <p data-tool="mdnice编辑器" style="padding-bottom: 8px;padding-top: 1em;color: rgb(74, 74, 74);line-height: 1.75em;">不同的MPP数据库的侧重点也不一样,如TiDB更侧重于分布式OLTP场景,Greenplum更侧重于分布式OLAP场景,这些MPP数据库基本都提供了类似Postgresql、Oracle、MySQL那样的SQL标准支持能力,能把一个查询解析为分布式的执行计划分发到每台机器上并行执行,最终由数据库本身汇总数据进行返回,也提供了诸如权限管理、分库分表、事务、数据副本等能力,并且大多能够支持100个节点以上的集群,大大降低了数据库运维的成本,并且使数据库也能够实现水平扩展。</p> <blockquote data-tool="mdnice编辑器" style="border-top: none;border-right: none;border-bottom: none;font-size: 0.9em;overflow: auto;background: rgb(227, 242, 253);color: rgb(106, 115, 125);margin-bottom: 20px;margin-top: 20px;padding: 15px 20px;line-height: 27px;border-left-color: rgb(100, 149, 237);"> <p style="line-height: 26px;font-size: 15px;color: rgb(89, 89, 89);">数据库和Tomcat都能够水平扩展,可支撑的并发大幅提高,随着用户数的增长,最终单机的Nginx会成为瓶颈</p> </blockquote> <h3 data-tool="mdnice编辑器" style="margin-bottom: 15px;font-weight: bold;color: black;font-size: 20px;margin-top: 1.2em;"><span style="font-size: 17px;color: rgb(65, 105, 225);">3.8 第七次演进:使用LVS或F5来使多个Nginx负载均衡</span></h3> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-ratio="0.8743633276740238" src="/upload/53024ad1fa941da074fe944726a2489e.png" data-type="png" data-w="589" style="display: block;margin-right: auto;margin-left: auto;border-radius: 4px;margin-bottom: 25px;"> </figure> <p data-tool="mdnice编辑器" style="padding-bottom: 8px;padding-top: 1em;color: rgb(74, 74, 74);line-height: 1.75em;">由于瓶颈在Nginx,因此无法通过两层的Nginx来实现多个Nginx的负载均衡。图中的LVS和F5是工作在网络第四层的负载均衡解决方案,其中LVS是软件,运行在操作系统内核态,可对TCP请求或更高层级的网络协议进行转发,因此支持的协议更丰富,并且性能也远高于Nginx,可假设单机的LVS可支持几十万个并发的请求转发;F5是一种负载均衡硬件,与LVS提供的能力类似,性能比LVS更高,但价格昂贵。</p> <p data-tool="mdnice编辑器" style="padding-bottom: 8px;padding-top: 1em;color: rgb(74, 74, 74);line-height: 1.75em;">由于LVS是单机版的软件,若LVS所在服务器宕机则会导致整个后端系统都无法访问,因此需要有备用节点。</p> <p data-tool="mdnice编辑器" style="padding-bottom: 8px;padding-top: 1em;color: rgb(74, 74, 74);line-height: 1.75em;">可使用keepalived软件模拟出虚拟IP,然后把虚拟IP绑定到多台LVS服务器上,浏览器访问虚拟IP时,会被路由器重定向到真实的LVS服务器,当主LVS服务器宕机时,keepalived软件会自动更新路由器中的路由表,把虚拟IP重定向到另外一台正常的LVS服务器,从而达到LVS服务器高可用的效果。</p> <p data-tool="mdnice编辑器" style="padding-bottom: 8px;padding-top: 1em;color: rgb(74, 74, 74);line-height: 1.75em;">此处需要注意的是,上图中从Nginx层到Tomcat层这样画并不代表全部Nginx都转发请求到全部的Tomcat,在实际使用时,可能会是几个Nginx下面接一部分的Tomcat,这些Nginx之间通过keepalived实现高可用,其他的Nginx接另外的Tomcat,这样可接入的Tomcat数量就能成倍的增加。</p> <blockquote data-tool="mdnice编辑器" style="border-top: none;border-right: none;border-bottom: none;font-size: 0.9em;overflow: auto;background: rgb(227, 242, 253);color: rgb(106, 115, 125);margin-bottom: 20px;margin-top: 20px;padding: 15px 20px;line-height: 27px;border-left-color: rgb(100, 149, 237);"> <p style="line-height: 26px;font-size: 15px;color: rgb(89, 89, 89);">由于LVS也是单机的,随着并发数增长到几十万时,LVS服务器最终会达到瓶颈,此时用户数达到千万甚至上亿级别,用户分布在不同的地区,与服务器机房距离不同,导致了访问的延迟会明显不同</p> </blockquote> <h3 data-tool="mdnice编辑器" style="margin-bottom: 15px;font-weight: bold;color: black;font-size: 20px;margin-top: 1.2em;"><span style="font-size: 17px;color: rgb(65, 105, 225);">3.9 第八次演进:通过DNS轮询实现机房间的负载均衡</span></h3> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-ratio="0.7117486338797814" src="/upload/fb4fe075f73054014cbc892bea7832e6.png" data-type="png" data-w="732" style="display: block;margin-right: auto;margin-left: auto;border-radius: 4px;margin-bottom: 25px;"> </figure> <p data-tool="mdnice编辑器" style="padding-bottom: 8px;padding-top: 1em;color: rgb(74, 74, 74);line-height: 1.75em;">在DNS服务器中可配置一个域名对应多个IP地址,每个IP地址对应到不同的机房里的虚拟IP。</p> <p data-tool="mdnice编辑器" style="padding-bottom: 8px;padding-top: 1em;color: rgb(74, 74, 74);line-height: 1.75em;">当用户访问www.taobao.com时,DNS服务器会使用轮询策略或其他策略,来选择某个IP供用户访问。</p> <p data-tool="mdnice编辑器" style="padding-bottom: 8px;padding-top: 1em;color: rgb(74, 74, 74);line-height: 1.75em;">此方式能实现机房间的负载均衡,至此,系统可做到机房级别的水平扩展,千万级到亿级的并发量都可通过增加机房来解决,系统入口处的请求并发量不再是问题。</p> <blockquote data-tool="mdnice编辑器" style="border-top: none;border-right: none;border-bottom: none;font-size: 0.9em;overflow: auto;background: rgb(227, 242, 253);color: rgb(106, 115, 125);margin-bottom: 20px;margin-top: 20px;padding: 15px 20px;line-height: 27px;border-left-color: rgb(100, 149, 237);"> <p style="line-height: 26px;font-size: 15px;color: rgb(89, 89, 89);">随着数据的丰富程度和业务的发展,检索、分析等需求越来越丰富,单单依靠数据库无法解决如此丰富的需求</p> </blockquote> <h3 data-tool="mdnice编辑器" style="margin-bottom: 15px;font-weight: bold;color: black;font-size: 20px;margin-top: 1.2em;"><span style="font-size: 17px;color: rgb(65, 105, 225);">3.10 第九次演进:引入NoSQL数据库和搜索引擎等技术</span></h3> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-ratio="0.6467153284671533" src="/upload/e3b00dd5f9efd5b3a361a7d54df26a51.png" data-type="png" data-w="685" style="display: block;margin-right: auto;margin-left: auto;border-radius: 4px;margin-bottom: 25px;"> </figure> <p data-tool="mdnice编辑器" style="padding-bottom: 8px;padding-top: 1em;color: rgb(74, 74, 74);line-height: 1.75em;">当数据库中的数据多到一定规模时,数据库就不适用于复杂的查询了,往往只能满足普通查询的场景。</p> <p data-tool="mdnice编辑器" style="padding-bottom: 8px;padding-top: 1em;color: rgb(74, 74, 74);line-height: 1.75em;">对于统计报表场景,在数据量大时不一定能跑出结果,而且在跑复杂查询时会导致其他查询变慢,对于全文检索、可变数据结构等场景,数据库天生不适用。</p> <p data-tool="mdnice编辑器" style="padding-bottom: 8px;padding-top: 1em;color: rgb(74, 74, 74);line-height: 1.75em;">因此需要针对特定的场景,引入合适的解决方案。如对于海量文件存储,可通过分布式文件系统HDFS解决,对于key value类型的数据,可通过HBase和Redis等方案解决,对于全文检索场景,可通过搜索引擎如ElasticSearch解决,对于多维分析场景,可通过Kylin或Druid等方案解决。</p> <p data-tool="mdnice编辑器" style="padding-bottom: 8px;padding-top: 1em;color: rgb(74, 74, 74);line-height: 1.75em;">当然,引入更多组件同时会提高系统的复杂度,不同的组件保存的数据需要同步,需要考虑一致性的问题,需要有更多的运维手段来管理这些组件等。</p> <blockquote data-tool="mdnice编辑器" style="border-top: none;border-right: none;border-bottom: none;font-size: 0.9em;overflow: auto;background: rgb(227, 242, 253);color: rgb(106, 115, 125);margin-bottom: 20px;margin-top: 20px;padding: 15px 20px;line-height: 27px;border-left-color: rgb(100, 149, 237);"> <p style="line-height: 26px;font-size: 15px;color: rgb(89, 89, 89);">引入更多组件解决了丰富的需求,业务维度能够极大扩充,随之而来的是一个应用中包含了太多的业务代码,业务的升级迭代变得困难</p> </blockquote> <h3 data-tool="mdnice编辑器" style="margin-bottom: 15px;font-weight: bold;color: black;font-size: 20px;margin-top: 1.2em;"><span style="font-size: 17px;color: rgb(65, 105, 225);">3.11 第十次演进:大应用拆分为小应用</span></h3> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-ratio="0.6622998544395924" src="/upload/fa1bc85d9ed4d1481b1d1eefeb3bfe3a.png" data-type="png" data-w="687" style="display: block;margin-right: auto;margin-left: auto;border-radius: 4px;margin-bottom: 25px;"> </figure> <p data-tool="mdnice编辑器" style="padding-bottom: 8px;padding-top: 1em;color: rgb(74, 74, 74);line-height: 1.75em;">按照业务板块来划分应用代码,使单个应用的职责更清晰,相互之间可以做到独立升级迭代。</p> <p data-tool="mdnice编辑器" style="padding-bottom: 8px;padding-top: 1em;color: rgb(74, 74, 74);line-height: 1.75em;">这时候应用之间可能会涉及到一些公共配置,可以通过分布式配置中心Zookeeper来解决。</p> <blockquote data-tool="mdnice编辑器" style="border-top: none;border-right: none;border-bottom: none;font-size: 0.9em;overflow: auto;background: rgb(227, 242, 253);color: rgb(106, 115, 125);margin-bottom: 20px;margin-top: 20px;padding: 15px 20px;line-height: 27px;border-left-color: rgb(100, 149, 237);"> <p style="line-height: 26px;font-size: 15px;color: rgb(89, 89, 89);">不同应用之间存在共用的模块,由应用单独管理会导致相同代码存在多份,导致公共功能升级时全部应用代码都要跟着升级</p> </blockquote> <h3 data-tool="mdnice编辑器" style="margin-bottom: 15px;font-weight: bold;color: black;font-size: 20px;margin-top: 1.2em;"><span style="font-size: 17px;color: rgb(65, 105, 225);">3.12 第十一次演进:复用的功能抽离成微服务</span></h3> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-ratio="0.7859237536656891" src="/upload/83baaaca6bcf688e83c31ea4c1264fe4.png" data-type="png" data-w="682" style="display: block;margin-right: auto;margin-left: auto;border-radius: 4px;margin-bottom: 25px;"> </figure> <p data-tool="mdnice编辑器" style="padding-bottom: 8px;padding-top: 1em;color: rgb(74, 74, 74);line-height: 1.75em;">如用户管理、订单、支付、鉴权等功能在多个应用中都存在,那么可以把这些功能的代码单独抽取出来形成一个单独的服务来管理,这样的服务就是所谓的微服务,应用和服务之间通过HTTP、TCP或RPC请求等多种方式来访问公共服务,每个单独的服务都可以由单独的团队来管理。</p> <p data-tool="mdnice编辑器" style="padding-bottom: 8px;padding-top: 1em;color: rgb(74, 74, 74);line-height: 1.75em;">此外,可以通过Dubbo、SpringCloud等框架实现服务治理、限流、熔断、降级等功能,提高服务的稳定性和可用性。</p> <blockquote data-tool="mdnice编辑器" style="border-top: none;border-right: none;border-bottom: none;font-size: 0.9em;overflow: auto;background: rgb(227, 242, 253);color: rgb(106, 115, 125);margin-bottom: 20px;margin-top: 20px;padding: 15px 20px;line-height: 27px;border-left-color: rgb(100, 149, 237);"> <p style="line-height: 26px;font-size: 15px;color: rgb(89, 89, 89);">不同服务的接口访问方式不同,应用代码需要适配多种访问方式才能使用服务,此外,应用访问服务,服务之间也可能相互访问,调用链将会变得非常复杂,逻辑变得混乱</p> </blockquote> <h3 data-tool="mdnice编辑器" style="margin-bottom: 15px;font-weight: bold;color: black;font-size: 20px;margin-top: 1.2em;"><span style="font-size: 17px;color: rgb(65, 105, 225);">3.13 第十二次演进:引入企业服务总线ESB屏蔽服务接口的访问差异</span></h3> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-ratio="0.8556998556998557" src="/upload/354efce740f5ce541214e9ce61fffbd5.png" data-type="png" data-w="693" style="display: block;margin-right: auto;margin-left: auto;border-radius: 4px;margin-bottom: 25px;"> </figure> <p data-tool="mdnice编辑器" style="padding-bottom: 8px;padding-top: 1em;color: rgb(74, 74, 74);line-height: 1.75em;">通过ESB统一进行访问协议转换,应用统一通过ESB来访问后端服务,服务与服务之间也通过ESB来相互调用,以此降低系统的耦合程度。</p> <p data-tool="mdnice编辑器" style="padding-bottom: 8px;padding-top: 1em;color: rgb(74, 74, 74);line-height: 1.75em;">这种单个应用拆分为多个应用,公共服务单独抽取出来来管理,并使用企业消息总线来解除服务之间耦合问题的架构,就是所谓的SOA(面向服务)架构,这种架构与微服务架构容易混淆,因为表现形式十分相似。</p> <p data-tool="mdnice编辑器" style="padding-bottom: 8px;padding-top: 1em;color: rgb(74, 74, 74);line-height: 1.75em;">个人理解,微服务架构更多是指把系统里的公共服务抽取出来单独运维管理的思想,而SOA架构则是指一种拆分服务并使服务接口访问变得统一的架构思想,SOA架构中包含了微服务的思想。</p> <blockquote data-tool="mdnice编辑器" style="border-top: none;border-right: none;border-bottom: none;font-size: 0.9em;overflow: auto;background: rgb(227, 242, 253);color: rgb(106, 115, 125);margin-bottom: 20px;margin-top: 20px;padding: 15px 20px;line-height: 27px;border-left-color: rgb(100, 149, 237);"> <p style="line-height: 26px;font-size: 15px;color: rgb(89, 89, 89);">业务不断发展,应用和服务都会不断变多,应用和服务的部署变得复杂,同一台服务器上部署多个服务还要解决运行环境冲突的问题,此外,对于如大促这类需要动态扩缩容的场景,需要水平扩展服务的性能,就需要在新增的服务上准备运行环境,部署服务等,运维将变得十分困难</p> </blockquote> <h3 data-tool="mdnice编辑器" style="margin-bottom: 15px;font-weight: bold;color: black;font-size: 20px;margin-top: 1.2em;"><span style="font-size: 17px;color: rgb(65, 105, 225);">3.14 第十三次演进:引入容器化技术实现运行环境隔离与动态服务管理</span></h3> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-ratio="0.951937984496124" src="/upload/e0121fc1bd998dc57a8323e4f5f44259.png" data-type="png" data-w="645" style="display: block;margin-right: auto;margin-left: auto;border-radius: 4px;margin-bottom: 25px;"> </figure> <p data-tool="mdnice编辑器" style="padding-bottom: 8px;padding-top: 1em;color: rgb(74, 74, 74);line-height: 1.75em;">目前最流行的容器化技术是Docker,最流行的容器管理服务是Kubernetes(K8S),应用/服务可以打包为Docker镜像,通过K8S来动态分发和部署镜像。</p> <p data-tool="mdnice编辑器" style="padding-bottom: 8px;padding-top: 1em;color: rgb(74, 74, 74);line-height: 1.75em;">Docker镜像可理解为一个能运行你的应用/服务的最小的操作系统,里面放着应用/服务的运行代码,运行环境根据实际的需要设置好。</p> <p data-tool="mdnice编辑器" style="padding-bottom: 8px;padding-top: 1em;color: rgb(74, 74, 74);line-height: 1.75em;">把整个“操作系统”打包为一个镜像后,就可以分发到需要部署相关服务的机器上,直接启动Docker镜像就可以把服务起起来,使服务的部署和运维变得简单。</p> <p data-tool="mdnice编辑器" style="padding-bottom: 8px;padding-top: 1em;color: rgb(74, 74, 74);line-height: 1.75em;">在大促的之前,可以在现有的机器集群上划分出服务器来启动Docker镜像,增强服务的性能,大促过后就可以关闭镜像,对机器上的其他服务不造成影响(在3.14节之前,服务运行在新增机器上需要修改系统配置来适配服务,这会导致机器上其他服务需要的运行环境被破坏)。</p> <blockquote data-tool="mdnice编辑器" style="border-top: none;border-right: none;border-bottom: none;font-size: 0.9em;overflow: auto;background: rgb(227, 242, 253);color: rgb(106, 115, 125);margin-bottom: 20px;margin-top: 20px;padding: 15px 20px;line-height: 27px;border-left-color: rgb(100, 149, 237);"> <p style="line-height: 26px;font-size: 15px;color: rgb(89, 89, 89);"><strong style="line-height: 1.75em;color: rgb(48, 79, 254);">使用容器化技术后服务动态扩缩容问题得以解决,但是机器还是需要公司自身来管理,在非大促的时候,还是需要闲置着大量的机器资源来应对大促,机器自身成本和运维成本都极高,资源利用率低</strong></p> </blockquote> <h3 data-tool="mdnice编辑器" style="margin-bottom: 15px;font-weight: bold;color: black;font-size: 20px;margin-top: 1.2em;"><span style="font-size: 17px;color: rgb(65, 105, 225);">3.15 第十四次演进:以云平台承载系统</span></h3> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-ratio="0.5969945355191257" src="/upload/fa0d25bd97880ddf2b9f4066a4d55374.png" data-type="png" data-w="732" style="display: block;margin-right: auto;margin-left: auto;border-radius: 4px;margin-bottom: 25px;"> </figure> <p data-tool="mdnice编辑器" style="padding-bottom: 8px;padding-top: 1em;color: rgb(74, 74, 74);line-height: 1.75em;">系统可部署到公有云上,利用公有云的海量机器资源,解决动态硬件资源的问题,在大促的时间段里,在云平台中临时申请更多的资源,结合Docker和K8S来快速部署服务,在大促结束后释放资源,真正做到按需付费,资源利用率大大提高,同时大大降低了运维成本。</p> <p data-tool="mdnice编辑器" style="padding-bottom: 8px;padding-top: 1em;color: rgb(74, 74, 74);line-height: 1.75em;">所谓的云平台,就是把海量机器资源,通过统一的资源管理,抽象为一个资源整体,在之上可按需动态申请硬件资源(如CPU、内存、网络等),并且之上提供通用的操作系统,提供常用的技术组件(如Hadoop技术栈,MPP数据库等)供用户使用,甚至提供开发好的应用,用户不需要关系应用内部使用了什么技术,就能够解决需求(如音视频转码服务、邮件服务、个人博客等)。</p> <p data-tool="mdnice编辑器" style="padding-bottom: 8px;padding-top: 1em;color: rgb(74, 74, 74);line-height: 1.75em;">在云平台中会涉及如下几个概念:</p> <ul data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: black;" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> <strong style="line-height: 1.75em;color: rgb(48, 79, 254);">IaaS:</strong>基础设施即服务。对应于上面所说的机器资源统一为资源整体,可动态申请硬件资源的层面; </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> <strong style="line-height: 1.75em;color: rgb(48, 79, 254);">PaaS:</strong>平台即服务。对应于上面所说的提供常用的技术组件方便系统的开发和维护; </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> <strong style="line-height: 1.75em;color: rgb(48, 79, 254);">SaaS:</strong>软件即服务。对应于上面所说的提供开发好的应用或服务,按功能或性能要求付费。 </section></li> </ul> <blockquote data-tool="mdnice编辑器" style="border-top: none;border-right: none;border-bottom: none;font-size: 0.9em;overflow: auto;background: rgb(227, 242, 253);color: rgb(106, 115, 125);margin-bottom: 20px;margin-top: 20px;padding: 15px 20px;line-height: 27px;border-left-color: rgb(100, 149, 237);"> <p style="line-height: 26px;font-size: 15px;color: rgb(89, 89, 89);">至此,以上所提到的从高并发访问问题,到服务的架构和系统实施的层面都有了各自的解决方案,但同时也应该意识到,在上面的介绍中,其实是有意忽略了诸如跨机房数据同步、分布式事务实现等等的实际问题,这些问题以后有机会再拿出来单独讨论</p> </blockquote> <h2 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;color: black;border-bottom: 2px solid rgb(65, 105, 225);font-size: 1.3em;"><span style="display: inline-block;background: rgb(65, 105, 225);color: rgb(255, 255, 255);padding: 3px 10px 1px;border-top-right-radius: 3px;border-top-left-radius: 3px;margin-right: 3px;">4. 架构设计总结</span><span style="display: inline-block;vertical-align: bottom;border-bottom: 36px solid #efebe9;border-right: 20px solid transparent;"> </span></h2> <ul data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: black;" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> <strong style="line-height: 1.75em;color: rgb(48, 79, 254);">架构的调整是否必须按照上述演变路径进行?</strong>不是的,以上所说的架构演变顺序只是针对某个侧面进行单独的改进,在实际场景中,可能同一时间会有几个问题需要解决,或者可能先达到瓶颈的是另外的方面,这时候就应该按照实际问题实际解决。如在政府类的并发量可能不大,但业务可能很丰富的场景,高并发就不是重点解决的问题,此时优先需要的可能会是丰富需求的解决方案。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> <strong style="line-height: 1.75em;color: rgb(48, 79, 254);">对于将要实施的系统,架构应该设计到什么程度?</strong>对于单次实施并且性能指标明确的系统,架构设计到能够支持系统的性能指标要求就足够了,但要留有扩展架构的接口以便不备之需。对于不断发展的系统,如电商平台,应设计到能满足下一阶段用户量和性能指标要求的程度,并根据业务的增长不断的迭代升级架构,以支持更高的并发和更丰富的业务。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> <strong style="line-height: 1.75em;color: rgb(48, 79, 254);">服务端架构和大数据架构有什么区别?</strong>所谓的“大数据”其实是海量数据采集清洗转换、数据存储、数据分析、数据服务等场景解决方案的一个统称,在每一个场景都包含了多种可选的技术,如数据采集有Flume、Sqoop、Kettle等,数据存储有分布式文件系统HDFS、FastDFS,NoSQL数据库HBase、MongoDB等,数据分析有Spark技术栈、机器学习算法等。总的来说大数据架构就是根据业务的需求,整合各种大数据组件组合而成的架构,一般会提供分布式存储、分布式计算、多维分析、数据仓库、机器学习算法等能力。而服务端架构更多指的是应用组织层面的架构,底层能力往往是由大数据架构来提供。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> <strong style="line-height: 1.75em;color: rgb(48, 79, 254);">有没有一些架构设计的原则?</strong> </section></li> <ul style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: black;list-style-type: square;" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> N+1设计。系统中的每个组件都应做到没有单点故障; </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 回滚设计。确保系统可以向前兼容,在系统升级时应能有办法回滚版本; </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 禁用设计。应该提供控制具体功能是否可用的配置,在系统出现故障时能够快速下线功能; </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 监控设计。在设计阶段就要考虑监控的手段; </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 多活数据中心设计。若系统需要极高的高可用,应考虑在多地实施数据中心进行多活,至少在一个机房断电的情况下系统依然可用; </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 采用成熟的技术。刚开发的或开源的技术往往存在很多隐藏的bug,出了问题没有商业支持可能会是一个灾难; </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 资源隔离设计。应避免单一业务占用全部资源; </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 架构应能水平扩展。系统只有做到能水平扩展,才能有效避免瓶颈问题; </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 非核心则购买。非核心功能若需要占用大量的研发资源才能解决,则考虑购买成熟的产品; </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 使用商用硬件。商用硬件能有效降低硬件故障的机率; </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 快速迭代。系统应该快速开发小功能模块,尽快上线进行验证,早日发现问题大大降低系统交付的风险; </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 无状态设计。服务接口应该做成无状态的,当前接口的访问不依赖于接口上次访问的状态。 </section></li> </ul> </ul> <section class="mp_profile_iframe_wrp"> <mpprofile class="js_uneditable custom_select_card mp_profile_iframe" data-pluginname="mpprofile" data-id="MzkzMDI1NjcyOQ==" data-headimg="http://mmbiz.qpic.cn/mmbiz_png/EoJib2tNvVtf7icAmS0BQH6oDVG37Q8NzcfdguS5qAqOhfxvZyIKqmuX5BbnDjynrBbZzktp1EiaeFLzapp1nHysw/0?wx_fmt=png" data-nickname="码哥字节" data-alias="MageByte" data-signature="拥抱硬核技术和对象,面向人民币编程。" data-from="0"></mpprofile> </section> </section>

备份MySQL数据库到阿里云OSS

作者:じ☆ve不哭

> 将MySQL数据库备份到阿里云,单表压缩快速恢复。zip压缩上传,上传速度快,节省oss存储空间 ``` #!/bin/bash PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin export PATH Date=`date +%Y-%m-%d_%H:%M:%S` BucketTime=`date +%Y%m` OldDate=$(date -d "-7 days" "+%Y-%m-%d") ###oss的地址### #Host="oss-cn-beijing.aliyuncs.com" Host="oss-cn-qingdao-internal.aliyuncs.com" ###bucket名字### Bucket="oss-bucket名称" ###Access ID### Id="阿里云oss AccessID" ###Access Key### Key="阿里云oss Access key" OssHost=$Bucket.$Host LoginPath="MySQL loginPath登录设置名称" ###备份数据库账号信息### grep -v 输出排除命令意思是mysql和information_schema不输出 DBS=`mysql --login-path=$LoginPath -Bse "show databases" | grep -v "information_schema" | grep -v "mysql"` ###罗列数据库信息### #========================BackUp SQL======================== CURRENT_TIME=`date +"%Y%m%d%H%M%S"` for dbName in $DBS; do echo "开始处理数据库: $dbName" TABS=`mysql --login-path=$LoginPath -Bse "select table_name from information_schema.tables where table_schema='$dbName'"` mkdir /tmp/$dbName-$CURRENT_TIME echo "创建任务目录: /tmp/$dbName-$CURRENT_TIME" for tabName in $TABS; do # 跳过 if [ "${tabName}" = "跳过指定表的表名" ]; then echo "开始备份表:$tabName" else echo "开始备份表:$tabName" mysqldump --login-path=$LoginPath --skip-lock-tables --set-gtid-purged=OFF $dbName $tabName > /tmp/$dbName-$CURRENT_TIME/$tabName.sql # mysqldump --login-path=$LoginPath --ignore-table=$dbName.忽略备份的表名 --skip-lock-tables --set-gtid-purged=OFF $dbName $tabName > /tmp/$dbName-$CURRENT_TIME/$tabName.sql fi done done # zip -P 密码 /tmp/$dbName.$Date.sql.zip /tmp/$dbName.$Date.sql echo "开始压缩目录:/tmp/$dbName-$CURRENT_TIME" zip -r /tmp/$dbName-$CURRENT_TIME.sql.zip /tmp/$dbName-$CURRENT_TIME ###zip压缩设置的密码### sendData="数据库[$dbName]在$Date执行备份, 操作结果:" if [ -s /tmp/$dbName-$CURRENT_TIME.sql.zip ] ; then source="/tmp/$dbName-$CURRENT_TIME.sql.zip" dest="$BucketTime/SQL/$dbName-$CURRENT_TIME.sql.zip" resource="/${Bucket}/${dest}" contentType=`file -ib ${source} |awk -F ";" '{print $1}'` dateValue="`TZ=GMT env LANG=en_US.UTF-8 date +'%a, %d %b %Y %H:%M:%S GMT'`" stringToSign="PUT\n\n${contentType}\n${dateValue}\n${resource}" signature=`echo -en $stringToSign | openssl sha1 -hmac ${Key} -binary | base64` url=http://${OssHost}/${dest} echo "upload ${source} to ${url}" curl -i -q -X PUT -T "${source}" \ -H "Host: ${OssHost}" \ -H "Date: ${dateValue}" \ -H "Content-Type: ${contentType}" \ -H "Authorization: OSS ${Id}:${signature}" \ ${url} if [ $? -ne 0 ];then # echo -e ""dbName $dbName $Date Fail Upload"" | mutt -s "'dbName $dbName $Date Fail Upload'" zsljava@163.com sendData="$sendData 上传oss失败!" else # echo -e ""dbName $dbName $Date Success"" | mutt -s "'dbName $dbName $Date Success'" zsljava@163.com rm -rf /tmp/$dbName-$CURRENT_TIME* sendData="$sendData 备份成功!" fi else # echo -e ""dbName $dbName $Date Fail Backup "" | mutt -s "'dbName $dbName $Date Fail Backup'" zsljava@163.com sendData="$sendData 备份失败!" fi # 发送钉钉消息 采用关键字白名单直接发送就行了 curl --request POST \ --url '钉钉消息通知群URL' \ --header 'Content-Type: application/json' \ --header 'cache-control: no-cache' \ --data '{"msgtype": "text", "text": { "content": "'"$sendData"'" } }' #========================BackUp SQL======================== ```

Spring Boot 优雅的实现重处理功能

作者:微信小助手

<section data-tool="mdnice编辑器" data-website="https://www.mdnice.com" style="line-height: 1.6;word-break: break-word;text-align: left;padding: 5px;font-size: 16px;color: rgb(53, 53, 53);word-spacing: 0.8px;letter-spacing: 0.8px;border-radius: 16px;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, &quot;PingFang SC&quot;, Cambria, Cochin, Georgia, Times, &quot;Times New Roman&quot;, serif;" data-mpa-powered-by="yiban.io"> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">在实际工作中,重处理是一个非常常见的场景,比如:</p> <ul data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: rgb(248, 57, 41);" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> 发送消息失败。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> 调用远程服务失败。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> 争抢锁失败。 </section></li> </ul> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">这些错误可能是因为网络波动造成的,等待过后重处理就能成功。通常来说,会用<code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(255, 93, 108);">try/catch</code>,<code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(255, 93, 108);">while</code>循环之类的语法来进行重处理,但是这样的做法缺乏统一性,并且不是很方便,要多写很多代码。然而<code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(255, 93, 108);">spring-retry</code>却可以通过注解,在不入侵原有业务逻辑代码的方式下,优雅的实现重处理功能。</p> <h2 data-tool="mdnice编辑器" style="font-weight: bold;color: black;font-size: 22px;margin-top: 20px;margin-right: 10px;"><span style="display: none;"></span><span style="font-size: 18px;color: rgb(34, 34, 34);display: inline-block;padding-left: 10px;border-left: 5px solid rgb(248, 57, 41);">@Retryable是什么?</span></h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">spring系列的<code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(255, 93, 108);">spring-retry</code>是另一个实用程序模块,可以帮助我们以标准方式处理任何特定操作的重试。在<code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(255, 93, 108);">spring-retry</code>中,所有配置都是基于简单注释的。</p> <h2 data-tool="mdnice编辑器" style="font-weight: bold;color: black;font-size: 22px;margin-top: 20px;margin-right: 10px;"><span style="display: none;"></span><span style="font-size: 18px;color: rgb(34, 34, 34);display: inline-block;padding-left: 10px;border-left: 5px solid rgb(248, 57, 41);">使用步骤</span></h2> <h3 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;color: black;font-size: 20px;"><span style="display: none;"></span><span style="font-size: 16px;color: #222;">1. POM依赖</span><span style="display: none;"></span></h3> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;"><span style="display: block;background: url(&quot;https://mmbiz.qpic.cn/mmbiz_svg/A7sq8BD8oexqmcDw0weKQeLicSXAich7jG16eyUm2W9VdeU7Z0qCf64Ymcsl1fpvAaWXhhAHibzYgw7ESqMqYpVVItwtialU7BnO/640?wx_fmt=svg&quot;) 10px 10px / 40px no-repeat rgb(250, 250, 250);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"></span><code style="overflow-x: auto;padding: 16px;color: #383a42;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #fafafa;border-radius: 5px;">&nbsp;<span style="line-height: 26px;">&lt;<span style="color: #e45649;line-height: 26px;">dependency</span>&gt;</span><br>&nbsp;&nbsp;<span style="line-height: 26px;">&lt;<span style="color: #e45649;line-height: 26px;">groupId</span>&gt;</span>org.springframework.retry<span style="line-height: 26px;">&lt;/<span style="color: #e45649;line-height: 26px;">groupId</span>&gt;</span><br>&nbsp;&nbsp;<span style="line-height: 26px;">&lt;<span style="color: #e45649;line-height: 26px;">artifactId</span>&gt;</span>spring-retry<span style="line-height: 26px;">&lt;/<span style="color: #e45649;line-height: 26px;">artifactId</span>&gt;</span><br>&nbsp;<span style="line-height: 26px;">&lt;/<span style="color: #e45649;line-height: 26px;">dependency</span>&gt;</span><br></code></pre> <h3 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;color: black;font-size: 20px;"><span style="display: none;"></span><span style="font-size: 16px;color: #222;">2.启用<code>@Retryable</code></span><span style="display: none;"></span></h3> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;"><span style="display: block;background: url(&quot;https://mmbiz.qpic.cn/mmbiz_svg/A7sq8BD8oexqmcDw0weKQeLicSXAich7jG16eyUm2W9VdeU7Z0qCf64Ymcsl1fpvAaWXhhAHibzYgw7ESqMqYpVVItwtialU7BnO/640?wx_fmt=svg&quot;) 10px 10px / 40px no-repeat rgb(250, 250, 250);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"></span><code style="overflow-x: auto;padding: 16px;color: #383a42;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #fafafa;border-radius: 5px;"><span style="color: #4078f2;line-height: 26px;">@EnableRetry</span><br><span style="color: #4078f2;line-height: 26px;">@SpringBootApplication</span><br><span style="color: #a626a4;line-height: 26px;">public</span>&nbsp;<span style="line-height: 26px;"><span style="color: #a626a4;line-height: 26px;">class</span>&nbsp;<span style="color: #c18401;line-height: 26px;">HelloApplication</span>&nbsp;</span>{<br><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="line-height: 26px;"><span style="color: #a626a4;line-height: 26px;">public</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">static</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">void</span>&nbsp;<span style="color: #4078f2;line-height: 26px;">main</span><span style="line-height: 26px;">(String[]&nbsp;args)</span>&nbsp;</span>{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;SpringApplication.run(HelloApplication<span style="line-height: 26px;">.<span style="color: #a626a4;line-height: 26px;">class</span>,&nbsp;<span style="color: #c18401;line-height: 26px;">args</span>)</span>;<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br><br>}<br></code></pre> <h3 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;color: black;font-size: 20px;"><span style="display: none;"></span><span style="font-size: 16px;color: #222;">3.在方法上添加<code>@Retryable</code></span><span style="display: none;"></span></h3> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;"><span style="display: block;background: url(&quot;https://mmbiz.qpic.cn/mmbiz_svg/A7sq8BD8oexqmcDw0weKQeLicSXAich7jG16eyUm2W9VdeU7Z0qCf64Ymcsl1fpvAaWXhhAHibzYgw7ESqMqYpVVItwtialU7BnO/640?wx_fmt=svg&quot;) 10px 10px / 40px no-repeat rgb(250, 250, 250);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"></span><code style="overflow-x: auto;padding: 16px;color: #383a42;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #fafafa;border-radius: 5px;"><span style="color: #a626a4;line-height: 26px;">import</span>&nbsp;com.mail.elegant.service.TestRetryService;<br><span style="color: #a626a4;line-height: 26px;">import</span>&nbsp;org.springframework.retry.annotation.Backoff;<br><span style="color: #a626a4;line-height: 26px;">import</span>&nbsp;org.springframework.retry.annotation.Retryable;<br><span style="color: #a626a4;line-height: 26px;">import</span>&nbsp;org.springframework.stereotype.Service;<br><span style="color: #a626a4;line-height: 26px;">import</span>&nbsp;java.time.LocalTime;<br>&nbsp;<br><span style="color: #4078f2;line-height: 26px;">@Service</span><br><span style="color: #a626a4;line-height: 26px;">public</span>&nbsp;<span style="line-height: 26px;"><span style="color: #a626a4;line-height: 26px;">class</span>&nbsp;<span style="color: #c18401;line-height: 26px;">TestRetryServiceImpl</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">implements</span>&nbsp;<span style="color: #c18401;line-height: 26px;">TestRetryService</span>&nbsp;</span>{<br>&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #4078f2;line-height: 26px;">@Override</span><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #4078f2;line-height: 26px;">@Retryable</span>(value&nbsp;=&nbsp;Exception<span style="line-height: 26px;">.<span style="color: #a626a4;line-height: 26px;">class</span>,<span style="color: #c18401;line-height: 26px;">maxAttempts</span>&nbsp;</span>=&nbsp;<span style="color: #986801;line-height: 26px;">3</span>,backoff&nbsp;=&nbsp;<span style="color: #4078f2;line-height: 26px;">@Backoff</span>(delay&nbsp;=&nbsp;<span style="color: #986801;line-height: 26px;">2000</span>,multiplier&nbsp;=&nbsp;<span style="color: #986801;line-height: 26px;">1.5</span>))<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="line-height: 26px;"><span style="color: #a626a4;line-height: 26px;">public</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">int</span>&nbsp;<span style="color: #4078f2;line-height: 26px;">test</span><span style="line-height: 26px;">(<span style="color: #a626a4;line-height: 26px;">int</span>&nbsp;code)</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">throws</span>&nbsp;Exception</span>{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.out.println(<span style="color: #50a14f;line-height: 26px;">"test被调用,时间:"</span>+LocalTime.now());<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">if</span>&nbsp;(code==<span style="color: #986801;line-height: 26px;">0</span>){<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">throw</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">new</span>&nbsp;Exception(<span style="color: #50a14f;line-height: 26px;">"情况不对头!"</span>);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.out.println(<span style="color: #50a14f;line-height: 26px;">"test被调用,情况对头了!"</span>);<br>&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">return</span>&nbsp;<span style="color: #986801;line-height: 26px;">200</span>;<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br>}<br></code></pre> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">来简单解释一下注解中几个参数的含义:</p> <ul data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: rgb(248, 57, 41);" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> <code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(255, 93, 108);">value</code>:抛出指定异常才会重试 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> <code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(255, 93, 108);">include</code>:和value一样,默认为空,当exclude也为空时,默认所有异常 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> <code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(255, 93, 108);">exclude</code>:指定不处理的异常 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> <code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(255, 93, 108);">maxAttempts</code>:最大重试次数,默认3次 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> <code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(255, 93, 108);">backoff</code>:重试等待策略,默认使用 <code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(255, 93, 108);">@Backoff</code>, <code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(255, 93, 108);">@Backoff</code>的value默认为1000L,我们设置为2000L; <code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(255, 93, 108);">multiplier</code>(指定延迟倍数)默认为0,表示固定暂停1秒后进行重试,如果把 <code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(255, 93, 108);">multiplier</code>设置为1.5,则第一次重试为2秒,第二次为3秒,第三次为4.5秒。 </section></li> </ul> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;"><span style="font-weight: 700;color: rgb(248, 57, 41);">当重试耗尽时还是失败,会出现什么情况呢?</span></p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">当重试耗尽时,<code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(255, 93, 108);">RetryOperations</code>可以将控制传递给另一个回调,即<code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(255, 93, 108);">RecoveryCallback</code>。<code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(255, 93, 108);">Spring-Retry</code>还提供了<code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(255, 93, 108);">@Recover</code>注解,用于@Retryable重试失败后处理方法。如果不需要回调方法,可以直接不写回调方法,那么实现的效果是,重试次数完了后,如果还是没成功没符合业务判断,就抛出异常。</p> <h3 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;color: black;font-size: 20px;"><span style="display: none;"></span><span style="font-size: 16px;color: #222;">4.@Recover</span><span style="display: none;"></span></h3> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;"><span style="display: block;background: url(&quot;https://mmbiz.qpic.cn/mmbiz_svg/A7sq8BD8oexqmcDw0weKQeLicSXAich7jG16eyUm2W9VdeU7Z0qCf64Ymcsl1fpvAaWXhhAHibzYgw7ESqMqYpVVItwtialU7BnO/640?wx_fmt=svg&quot;) 10px 10px / 40px no-repeat rgb(250, 250, 250);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"></span><code style="overflow-x: auto;padding: 16px;color: #383a42;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #fafafa;border-radius: 5px;"><span style="color: #4078f2;line-height: 26px;">@Recover</span><br><span style="line-height: 26px;"><span style="color: #a626a4;line-height: 26px;">public</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">int</span>&nbsp;<span style="color: #4078f2;line-height: 26px;">recover</span><span style="line-height: 26px;">(Exception&nbsp;e,&nbsp;<span style="color: #a626a4;line-height: 26px;">int</span>&nbsp;code)</span></span>{<br>&nbsp;&nbsp;&nbsp;System.out.println(<span style="color: #50a14f;line-height: 26px;">"回调方法执行!!!!"</span>);<br>&nbsp;&nbsp;&nbsp;<span style="color: #a0a1a7;font-style: italic;line-height: 26px;">//记日志到数据库&nbsp;或者调用其余的方法</span><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">return</span>&nbsp;<span style="color: #986801;line-height: 26px;">400</span>;<br>}<br></code></pre> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">可以看到传参里面写的是 <code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(255, 93, 108);">Exception e</code>,这个是作为回调的接头暗号(重试次数用完了,还是失败,我们抛出这个<code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(255, 93, 108);">Exception e</code>通知触发这个回调方法)。对于<code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(255, 93, 108);">@Recover</code>注解的方法,需要特别注意的是:</p> <ul data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: rgb(248, 57, 41);" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> 方法的返回值必须与 <code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(255, 93, 108);">@Retryable</code>方法一致 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> 方法的第一个参数,必须是Throwable类型的,建议是与 <code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(255, 93, 108);">@Retryable</code>配置的异常一致,其他的参数,需要哪个参数,写进去就可以了( <code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(255, 93, 108);">@Recover</code>方法中有的) </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> 该回调方法与重试方法写在同一个实现类里面 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> 另外,更多面试题,公众号Java精选,回复java面试,获取最新面试题资料。 </section></li> </ul> <h3 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;color: black;font-size: 20px;"><span style="display: none;"></span><span style="font-size: 16px;color: #222;">5. 注意事项</span><span style="display: none;"></span></h3> <ul data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: rgb(248, 57, 41);" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> 由于是基于AOP实现,所以不支持类里自调用方法 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> 如果重试失败需要给 <code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(255, 93, 108);">@Recover</code>注解的方法做后续处理,那这个重试的方法不能有返回值,只能是void </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> 方法内不能使用 <code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(255, 93, 108);">try catch</code>,只能往外抛异常 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> <code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(255, 93, 108);">@Recover</code>注解来开启重试失败后调用的方法(注意,需跟重处理方法在同一个类中),此注解注释的方法参数一定要是 <code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(255, 93, 108);">@Retryable</code>抛出的异常,否则无法识别,可以在该方法中进行日志处理。 </section></li> </ul> <h2 data-tool="mdnice编辑器" style="font-weight: bold;color: black;font-size: 22px;margin-top: 20px;margin-right: 10px;"><span style="display: none;"></span><span style="font-size: 18px;color: rgb(34, 34, 34);display: inline-block;padding-left: 10px;border-left: 5px solid rgb(248, 57, 41);">总结</span></h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">本篇主要简单介绍了Springboot中的的使用,主要的适用场景和注意事项,当需要重试的时候还是很有用的。</p> </section>

聊聊 分布式 WebSocket 集群解决方案

作者:微信小助手

<p data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;padding-top: 8px;padding-bottom: 8px;outline: 0px;max-width: 100%;color: rgb(0, 0, 0);font-family: PingFangSC-Light;text-align: justify;white-space: normal;line-height: 1.75;letter-spacing: 0.2em;font-size: 15px;word-spacing: 0.1em;box-sizing: border-box !important;overflow-wrap: break-word !important;">最近做项目时遇到了需要多用户之间通信的问题,涉及到了WebSocket握手请求,以及集群中WebSocket Session共享的问题。</p> <p data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;padding-top: 8px;padding-bottom: 8px;outline: 0px;max-width: 100%;color: rgb(0, 0, 0);font-family: PingFangSC-Light;text-align: justify;white-space: normal;line-height: 1.75;letter-spacing: 0.2em;font-size: 15px;word-spacing: 0.1em;box-sizing: border-box !important;overflow-wrap: break-word !important;">期间我经过了几天的研究,总结出了几个实现分布式WebSocket集群的办法,从zuul到spring cloud gateway的不同尝试,总结出了这篇文章,希望能帮助到某些人,并且能一起分享这方面的想法与研究。</p> <p data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;padding-top: 8px;padding-bottom: 8px;outline: 0px;max-width: 100%;color: rgb(0, 0, 0);font-family: PingFangSC-Light;text-align: left;white-space: normal;line-height: 1.75;letter-spacing: 0.2em;font-size: 15px;word-spacing: 0.1em;box-sizing: border-box !important;overflow-wrap: break-word !important;">以下是我的场景描述</p> <ul data-tool="mdnice编辑器" class="list-paddingleft-1" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;outline: 0px;max-width: 100%;color: rgb(0, 0, 0);font-family: PingFangSC-Light;font-size: 16px;text-align: left;white-space: normal;overflow-wrap: break-word !important;"> <li style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <section style="margin-top: 5px;margin-bottom: 5px;outline: 0px;max-width: 100%;line-height: 26px;color: rgb(1, 1, 1);font-size: 15px;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <strong style="outline: 0px;max-width: 100%;color: black;box-sizing: border-box !important;overflow-wrap: break-word !important;">资源</strong>:4台服务器。其中只有一台服务器具备ssl认证域名,一台redis+mysql服务器,两台应用服务器(集群) </section></li> <li style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <section style="margin-top: 5px;margin-bottom: 5px;outline: 0px;max-width: 100%;line-height: 26px;color: rgb(1, 1, 1);font-size: 15px;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <strong style="outline: 0px;max-width: 100%;color: black;box-sizing: border-box !important;overflow-wrap: break-word !important;">应用发布限制条件</strong>:由于场景需要,应用场所需要ssl认证的域名才能发布。因此ssl认证的域名服务器用来当api网关,负责https请求与wss(安全认证的ws)连接。俗称https卸载,用户请求https域名服务器(eg:https://oiscircle.com/xxx),但真实访问到的是http+ip地址的形式。只要网关配置高,能handle多个应用 </section></li> <li style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <section style="margin-top: 5px;margin-bottom: 5px;outline: 0px;max-width: 100%;line-height: 26px;color: rgb(1, 1, 1);font-size: 15px;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <strong style="outline: 0px;max-width: 100%;color: black;box-sizing: border-box !important;overflow-wrap: break-word !important;">需求</strong>:用户登录应用,需要与服务器建立wss连接,不同角色之间可以单发消息,也可以群发消息 </section></li> <li style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <section style="margin-top: 5px;margin-bottom: 5px;outline: 0px;max-width: 100%;line-height: 26px;color: rgb(1, 1, 1);font-size: 15px;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <strong style="outline: 0px;max-width: 100%;color: black;box-sizing: border-box !important;overflow-wrap: break-word !important;">集群中的应用服务类型</strong>:每个集群实例都负责http无状态请求服务与ws长连接服务 </section></li> </ul> <h2 data-tool="mdnice编辑器" style="margin-top: 20px;margin-right: 10px;outline: 0px;font-weight: bold;font-size: 22px;max-width: 100%;color: rgb(0, 0, 0);font-family: PingFangSC-Light;text-align: left;white-space: normal;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="color: rgb(255, 41, 65);"><strong><span style="color: rgb(255, 41, 65);font-size: 20px;">| 系统架构图</span></strong></span></h2> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;outline: 0px;max-width: 100%;color: rgb(0, 0, 0);font-family: PingFangSC-Light;font-size: 16px;text-align: left;white-space: normal;display: flex;flex-direction: column;justify-content: center;align-items: center;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <img class="rich_pages wxw-img" data-fileid="100033256" data-ratio="0.5161707632600259" src="/upload/793a598ce88a15fa78306544464a751a.png" data-type="png" data-w="773" style="margin: 3px auto;outline: 0px;border-radius: 0px 0px 5px 5px;display: block;object-fit: contain;box-shadow: rgb(132, 161, 168) 0px 10px 15px;box-sizing: border-box !important;overflow-wrap: break-word !important;visibility: visible !important;width: 677px !important;"> </figure> <p data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;padding-top: 8px;padding-bottom: 8px;outline: 0px;max-width: 100%;color: rgb(0, 0, 0);font-family: PingFangSC-Light;text-align: justify;white-space: normal;line-height: 1.75;letter-spacing: 0.2em;font-size: 15px;word-spacing: 0.1em;box-sizing: border-box !important;overflow-wrap: break-word !important;">在我的实现里,每个应用服务器都负责http and ws请求,其实也可以将ws请求建立的聊天模型单独成立为一个模块。从分布式的角度来看,这两种实现类型差不多,但从实现方便性来说,一个应用服务http+ws请求的方式更为方便。下文会有解释。</p> <p data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;padding-top: 8px;padding-bottom: 8px;outline: 0px;max-width: 100%;color: rgb(0, 0, 0);font-family: PingFangSC-Light;text-align: left;white-space: normal;line-height: 1.75;letter-spacing: 0.2em;font-size: 15px;word-spacing: 0.1em;box-sizing: border-box !important;overflow-wrap: break-word !important;">本文涉及的技术栈:</p> <ul data-tool="mdnice编辑器" class="list-paddingleft-1" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;outline: 0px;max-width: 100%;color: rgb(0, 0, 0);font-family: PingFangSC-Light;font-size: 16px;text-align: left;white-space: normal;overflow-wrap: break-word !important;"> <li style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <section style="margin-top: 5px;margin-bottom: 5px;outline: 0px;max-width: 100%;line-height: 26px;color: rgb(1, 1, 1);font-size: 15px;box-sizing: border-box !important;overflow-wrap: break-word !important;"> Eureka 服务发现与注册 </section></li> <li style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <section style="margin-top: 5px;margin-bottom: 5px;outline: 0px;max-width: 100%;line-height: 26px;color: rgb(1, 1, 1);font-size: 15px;box-sizing: border-box !important;overflow-wrap: break-word !important;"> Redis Session共享 </section></li> <li style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <section style="margin-top: 5px;margin-bottom: 5px;outline: 0px;max-width: 100%;line-height: 26px;color: rgb(1, 1, 1);font-size: 15px;box-sizing: border-box !important;overflow-wrap: break-word !important;"> Redis 消息订阅 </section></li> <li style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <section style="margin-top: 5px;margin-bottom: 5px;outline: 0px;max-width: 100%;line-height: 26px;color: rgb(1, 1, 1);font-size: 15px;box-sizing: border-box !important;overflow-wrap: break-word !important;"> Spring Boot </section></li> <li style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <section style="margin-top: 5px;margin-bottom: 5px;outline: 0px;max-width: 100%;line-height: 26px;color: rgb(1, 1, 1);font-size: 15px;box-sizing: border-box !important;overflow-wrap: break-word !important;"> Zuul 网关 </section></li> <li style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <section style="margin-top: 5px;margin-bottom: 5px;outline: 0px;max-width: 100%;line-height: 26px;color: rgb(1, 1, 1);font-size: 15px;box-sizing: border-box !important;overflow-wrap: break-word !important;"> Spring Cloud Gateway 网关 </section></li> <li style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <section style="margin-top: 5px;margin-bottom: 5px;outline: 0px;max-width: 100%;line-height: 26px;color: rgb(1, 1, 1);font-size: 15px;box-sizing: border-box !important;overflow-wrap: break-word !important;"> Spring WebSocket 处理长连接 </section></li> <li style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <section style="margin-top: 5px;margin-bottom: 5px;outline: 0px;max-width: 100%;line-height: 26px;color: rgb(1, 1, 1);font-size: 15px;box-sizing: border-box !important;overflow-wrap: break-word !important;"> Ribbon 负载均衡 </section></li> <li style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <section style="margin-top: 5px;margin-bottom: 5px;outline: 0px;max-width: 100%;line-height: 26px;color: rgb(1, 1, 1);font-size: 15px;box-sizing: border-box !important;overflow-wrap: break-word !important;"> Netty 多协议NIO网络通信框架 </section></li> <li style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <section style="margin-top: 5px;margin-bottom: 5px;outline: 0px;max-width: 100%;line-height: 26px;color: rgb(1, 1, 1);font-size: 15px;box-sizing: border-box !important;overflow-wrap: break-word !important;"> Consistent Hash 一致性哈希算法 </section></li> </ul> <p data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;padding-top: 8px;padding-bottom: 8px;outline: 0px;max-width: 100%;color: rgb(0, 0, 0);font-family: PingFangSC-Light;text-align: justify;white-space: normal;line-height: 1.75;letter-spacing: 0.2em;font-size: 15px;word-spacing: 0.1em;box-sizing: border-box !important;overflow-wrap: break-word !important;">相信能走到这一步的人都了解过我上面列举的技术栈了,如果还没有,可以先去网上找找入门教程了解一下。下面的内容都与上述技术相关,题主默认大家都了解过了...<span style="outline: 0px;max-width: 100%;letter-spacing: 0.2em;word-spacing: 0.1em;box-sizing: border-box !important;overflow-wrap: break-word !important;"></span></p> <h2 data-tool="mdnice编辑器" style="margin-top: 20px;margin-right: 10px;outline: 0px;font-weight: bold;font-size: 22px;max-width: 100%;color: rgb(0, 0, 0);font-family: PingFangSC-Light;text-align: left;white-space: normal;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="color: rgb(255, 41, 65);"><strong><span style="color: rgb(255, 41, 65);font-size: 20px;">| 技术可行性分析</span></strong></span></h2> <p data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;padding-top: 8px;padding-bottom: 8px;outline: 0px;max-width: 100%;color: rgb(0, 0, 0);font-family: PingFangSC-Light;text-align: justify;white-space: normal;line-height: 1.75;letter-spacing: 0.2em;font-size: 15px;word-spacing: 0.1em;box-sizing: border-box !important;overflow-wrap: break-word !important;">下面我将描述session特性,以及根据这些特性列举出n个解决分布式架构中处理ws请求的集群方案。</p> <p data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;padding-top: 8px;padding-bottom: 8px;outline: 0px;max-width: 100%;color: rgb(0, 0, 0);font-family: PingFangSC-Light;text-align: justify;white-space: normal;line-height: 1.75;letter-spacing: 0.2em;font-size: 15px;word-spacing: 0.1em;box-sizing: border-box !important;overflow-wrap: break-word !important;"><strong style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;">WebSocketSession与HttpSession</strong></p> <p data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;padding-top: 8px;padding-bottom: 8px;outline: 0px;max-width: 100%;color: rgb(0, 0, 0);font-family: PingFangSC-Light;text-align: justify;white-space: normal;line-height: 1.75;letter-spacing: 0.2em;font-size: 15px;word-spacing: 0.1em;box-sizing: border-box !important;overflow-wrap: break-word !important;">在Spring所集成的WebSocket里面,每个ws连接都有一个对应的session:WebSocketSession,在Spring WebSocket中,我们建立ws连接之后可以通过类似这样的方式进行与客户端的通信:</p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;outline: 0px;max-width: 100%;color: rgb(0, 0, 0);font-size: 16px;text-align: left;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="margin-bottom: -7px;outline: 0px;max-width: 100%;display: block;background: url(&quot;https://mmbiz.qpic.cn/mmbiz_svg/bofA1vl6EUZxu3cvFddtMiba3PbAz3khYGg9mtNtCJHyNkNkic7mFPJa3l1xJdBEicrlK2ZXn7ePL5PLYw40dd41XEYtjGbGvQI/640?wx_fmt=svg&quot;) 10px 10px / 40px no-repeat rgb(248, 248, 248);height: 30px;width: 657px;border-radius: 5px;box-sizing: border-box !important;overflow-wrap: break-word !important;"></span><code style="padding: 15px 16px 16px;outline: 0px;max-width: 100%;overflow-x: auto;color: rgb(51, 51, 51);display: -webkit-box;font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;font-size: 12px;background: rgb(248, 248, 248);border-radius: 5px;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="outline: 0px;max-width: 100%;line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="outline: 0px;max-width: 100%;font-weight: bold;line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">protected</span>&nbsp;<span style="outline: 0px;max-width: 100%;font-weight: bold;line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">void</span>&nbsp;<span style="outline: 0px;max-width: 100%;color: rgb(153, 0, 0);font-weight: bold;line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">handleTextMessage</span><span style="outline: 0px;max-width: 100%;line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">(WebSocketSession&nbsp;session,&nbsp;TextMessage&nbsp;message)</span>&nbsp;</span>{<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;">&nbsp;&nbsp;&nbsp;System.out.println(<span style="outline: 0px;max-width: 100%;color: rgb(221, 17, 68);line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">"服务器接收到的消息:&nbsp;"</span>+&nbsp;message&nbsp;);<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;">&nbsp;&nbsp;&nbsp;<span style="outline: 0px;max-width: 100%;color: rgb(153, 153, 136);font-style: italic;line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">//send&nbsp;message&nbsp;to&nbsp;client</span><br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;">&nbsp;&nbsp;&nbsp;session.sendMessage(<span style="outline: 0px;max-width: 100%;font-weight: bold;line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">new</span>&nbsp;TextMessage(<span style="outline: 0px;max-width: 100%;color: rgb(221, 17, 68);line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">"message"</span>));<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;">}<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"></code></pre> <p data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;padding-top: 8px;padding-bottom: 8px;outline: 0px;max-width: 100%;color: rgb(0, 0, 0);font-family: PingFangSC-Light;text-align: justify;white-space: normal;line-height: 1.75;letter-spacing: 0.2em;font-size: 15px;word-spacing: 0.1em;box-sizing: border-box !important;overflow-wrap: break-word !important;">那么问题来了:ws的session无法序列化到redis,因此在集群中,我们无法将所有WebSocketSession都缓存到redis进行session共享。每台服务器都有各自的session。于此相反的是HttpSession,redis可以支持httpsession共享,但是目前没有websocket session共享的方案,因此走redis websocket session共享这条路是行不通的。</p> <p data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;padding-top: 8px;padding-bottom: 8px;outline: 0px;max-width: 100%;color: rgb(0, 0, 0);font-family: PingFangSC-Light;text-align: justify;white-space: normal;line-height: 1.75;letter-spacing: 0.2em;font-size: 15px;word-spacing: 0.1em;box-sizing: border-box !important;overflow-wrap: break-word !important;">有的人可能会想:我可不可以将sessin关键信息缓存到redis,集群中的服务器从redis拿取session关键信息然后重新构建websocket session...我只想说这种方法如果有人能试出来,请告诉我一声...</p> <p data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;padding-top: 8px;padding-bottom: 8px;outline: 0px;max-width: 100%;color: rgb(0, 0, 0);font-family: PingFangSC-Light;text-align: justify;white-space: normal;line-height: 1.75;letter-spacing: 0.2em;font-size: 15px;word-spacing: 0.1em;box-sizing: border-box !important;overflow-wrap: break-word !important;">以上便是websocket session与http session共享的区别,总的来说就是http session共享已经有解决方案了,而且很简单,只要引入相关依赖:<code style="margin-right: 2px;margin-left: 2px;padding: 2px 4px;outline: 0px;max-width: 100%;font-size: 14px;border-radius: 4px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;box-sizing: border-box !important;overflow-wrap: break-word !important;">spring-session-data-redis</code>和<code style="margin-right: 2px;margin-left: 2px;padding: 2px 4px;outline: 0px;max-width: 100%;font-size: 14px;border-radius: 4px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;box-sizing: border-box !important;overflow-wrap: break-word !important;">spring-boot-starter-redis</code>,大家可以从网上找个demo玩一下就知道怎么做了。而websocket session共享的方案由于websocket底层实现的方式,我们无法做到真正的websocket session共享。</p> <h2 data-tool="mdnice编辑器" style="margin-top: 20px;margin-right: 10px;outline: 0px;font-weight: bold;font-size: 22px;max-width: 100%;color: rgb(0, 0, 0);font-family: PingFangSC-Light;text-align: left;white-space: normal;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="color: rgb(255, 41, 65);"><strong><span style="color: rgb(255, 41, 65);font-size: 20px;">| 解决方案的演变</span></strong></span></h2> <p data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;padding-top: 8px;padding-bottom: 8px;outline: 0px;max-width: 100%;color: rgb(0, 0, 0);font-family: PingFangSC-Light;text-align: left;white-space: normal;line-height: 1.75;letter-spacing: 0.2em;font-size: 15px;word-spacing: 0.1em;box-sizing: border-box !important;overflow-wrap: break-word !important;"><strong style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;">Netty与Spring WebSocket</strong></p> <p data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;padding-top: 8px;padding-bottom: 8px;outline: 0px;max-width: 100%;color: rgb(0, 0, 0);font-family: PingFangSC-Light;text-align: justify;white-space: normal;line-height: 1.75;letter-spacing: 0.2em;font-size: 15px;word-spacing: 0.1em;box-sizing: border-box !important;overflow-wrap: break-word !important;">刚开始的时候,我尝试着用netty实现了websocket服务端的搭建。在netty里面,并没有websocket session这样的概念,与其类似的是channel,每一个客户端连接都代表一个channel。前端的ws请求通过netty监听的端口,走websocket协议进行ws握手连接之后,通过一些列的handler(责链模式)进行消息处理。与websocket session类似地,服务端在连接建立后有一个channel,我们可以通过channel进行与客户端的通信</p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;outline: 0px;max-width: 100%;color: rgb(0, 0, 0);font-size: 16px;text-align: left;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="margin-bottom: -7px;outline: 0px;max-width: 100%;display: block;background: url(&quot;https://mmbiz.qpic.cn/mmbiz_svg/bofA1vl6EUZxu3cvFddtMiba3PbAz3khYGg9mtNtCJHyNkNkic7mFPJa3l1xJdBEicrlK2ZXn7ePL5PLYw40dd41XEYtjGbGvQI/640?wx_fmt=svg&quot;) 10px 10px / 40px no-repeat rgb(248, 248, 248);height: 30px;width: 657px;border-radius: 5px;box-sizing: border-box !important;overflow-wrap: break-word !important;"></span><code style="padding: 15px 16px 16px;outline: 0px;max-width: 100%;overflow-x: auto;color: rgb(51, 51, 51);display: -webkit-box;font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;font-size: 12px;background: rgb(248, 248, 248);border-radius: 5px;box-sizing: border-box !important;overflow-wrap: break-word !important;">&nbsp;&nbsp;&nbsp;<span style="outline: 0px;max-width: 100%;color: rgb(153, 153, 136);font-style: italic;line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">/**<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;">&nbsp;&nbsp;&nbsp;&nbsp;*&nbsp;TODO&nbsp;根据服务器传进来的id,分配到不同的group<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;">&nbsp;&nbsp;&nbsp;&nbsp;*/</span><br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;">&nbsp;&nbsp;&nbsp;<span style="outline: 0px;max-width: 100%;font-weight: bold;line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">private</span>&nbsp;<span style="outline: 0px;max-width: 100%;font-weight: bold;line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">static</span>&nbsp;<span style="outline: 0px;max-width: 100%;font-weight: bold;line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">final</span>&nbsp;ChannelGroup&nbsp;GROUP&nbsp;=&nbsp;<span style="outline: 0px;max-width: 100%;font-weight: bold;line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">new</span>&nbsp;DefaultChannelGroup(ImmediateEventExecutor.INSTANCE);<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;">&nbsp;<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;">&nbsp;&nbsp;&nbsp;<span style="outline: 0px;max-width: 100%;color: rgb(153, 153, 153);font-weight: bold;line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">@Override</span><br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;">&nbsp;&nbsp;&nbsp;<span style="outline: 0px;max-width: 100%;line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="outline: 0px;max-width: 100%;font-weight: bold;line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">protected</span>&nbsp;<span style="outline: 0px;max-width: 100%;font-weight: bold;line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">void</span>&nbsp;<span style="outline: 0px;max-width: 100%;color: rgb(153, 0, 0);font-weight: bold;line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">channelRead0</span><span style="outline: 0px;max-width: 100%;line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">(ChannelHandlerContext&nbsp;ctx,&nbsp;TextWebSocketFrame&nbsp;msg)</span>&nbsp;<span style="outline: 0px;max-width: 100%;font-weight: bold;line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">throws</span>&nbsp;Exception&nbsp;</span>{<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="outline: 0px;max-width: 100%;color: rgb(153, 153, 136);font-style: italic;line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">//retain增加引用计数,防止接下来的调用引用失效</span><br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.out.println(<span style="outline: 0px;max-width: 100%;color: rgb(221, 17, 68);line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">"服务器接收到来自&nbsp;"</span>&nbsp;+&nbsp;ctx.channel().id()&nbsp;+&nbsp;<span style="outline: 0px;max-width: 100%;color: rgb(221, 17, 68);line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">"&nbsp;的消息:&nbsp;"</span>&nbsp;+&nbsp;msg.text());<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="outline: 0px;max-width: 100%;color: rgb(153, 153, 136);font-style: italic;line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">//将消息发送给group里面的所有channel,也就是发送消息给客户端</span><br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;GROUP.writeAndFlush(msg.retain());<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;">&nbsp;&nbsp;&nbsp;}<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"></code></pre> <p data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;padding-top: 8px;padding-bottom: 8px;outline: 0px;max-width: 100%;color: rgb(0, 0, 0);font-family: PingFangSC-Light;text-align: justify;white-space: normal;line-height: 1.75;letter-spacing: 0.2em;font-size: 15px;word-spacing: 0.1em;box-sizing: border-box !important;overflow-wrap: break-word !important;">那么,服务端用netty还是用spring websocket?以下我将从几个方面列举这两种实现方式的优缺点。</p> <h4 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;outline: 0px;font-weight: bold;font-size: 18px;max-width: 100%;color: rgb(0, 0, 0);font-family: PingFangSC-Light;text-align: left;white-space: normal;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;">使用netty实现websocket</span></h4> <p data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;padding-top: 8px;padding-bottom: 8px;outline: 0px;max-width: 100%;color: rgb(0, 0, 0);font-family: PingFangSC-Light;text-align: justify;white-space: normal;line-height: 1.75;letter-spacing: 0.2em;font-size: 15px;word-spacing: 0.1em;box-sizing: border-box !important;overflow-wrap: break-word !important;">玩过netty的人都知道netty是的线程模型是nio模型,并发量非常高,spring5之前的网络线程模型是servlet实现的,而servlet不是nio模型,所以在spring5之后,spring的底层网络实现采用了netty。如果我们单独使用netty来开发websocket服务端,速度快是绝对的,但是可能会遇到下列问题:</p> <ol data-tool="mdnice编辑器" class="list-paddingleft-1" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;outline: 0px;max-width: 100%;color: rgb(0, 0, 0);font-family: PingFangSC-Light;font-size: 16px;text-align: left;white-space: normal;overflow-wrap: break-word !important;"> <li style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <section style="margin-top: 5px;margin-bottom: 5px;outline: 0px;max-width: 100%;line-height: 26px;color: rgb(1, 1, 1);font-size: 15px;box-sizing: border-box !important;overflow-wrap: break-word !important;"> 与系统的其他应用集成不方便,在rpc调用的时候,无法享受springcloud里feign服务调用的便利性 </section></li> <li style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <section style="margin-top: 5px;margin-bottom: 5px;outline: 0px;max-width: 100%;line-height: 26px;color: rgb(1, 1, 1);font-size: 15px;box-sizing: border-box !important;overflow-wrap: break-word !important;"> 业务逻辑可能要重复实现 </section></li> <li style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <section style="margin-top: 5px;margin-bottom: 5px;outline: 0px;max-width: 100%;line-height: 26px;color: rgb(1, 1, 1);font-size: 15px;box-sizing: border-box !important;overflow-wrap: break-word !important;"> 使用netty可能需要重复造轮子 </section></li> <li style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <section style="margin-top: 5px;margin-bottom: 5px;outline: 0px;max-width: 100%;line-height: 26px;color: rgb(1, 1, 1);font-size: 15px;box-sizing: border-box !important;overflow-wrap: break-word !important;"> 怎么连接上服务注册中心,也是一件麻烦的事情 </section></li> <li style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <section style="margin-top: 5px;margin-bottom: 5px;outline: 0px;max-width: 100%;line-height: 26px;color: rgb(1, 1, 1);font-size: 15px;box-sizing: border-box !important;overflow-wrap: break-word !important;"> restful服务与ws服务需要分开实现,如果在netty上实现restful服务,有多麻烦可想而知,用spring一站式restful开发相信很多人都习惯了。 </section></li> </ol> <h4 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;outline: 0px;font-weight: bold;font-size: 18px;max-width: 100%;color: rgb(0, 0, 0);font-family: PingFangSC-Light;text-align: left;white-space: normal;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;">使用spring websocket实现ws服务</span></h4> <p data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;padding-top: 8px;padding-bottom: 8px;outline: 0px;max-width: 100%;color: rgb(0, 0, 0);font-family: PingFangSC-Light;text-align: justify;white-space: normal;line-height: 1.75;letter-spacing: 0.2em;font-size: 15px;word-spacing: 0.1em;box-sizing: border-box !important;overflow-wrap: break-word !important;">spring websocket已经被springboot很好地集成了,所以在springboot上开发ws服务非常方便,做法非常简单</p> <p data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;padding-top: 8px;padding-bottom: 8px;outline: 0px;max-width: 100%;color: rgb(0, 0, 0);font-family: PingFangSC-Light;text-align: left;white-space: normal;line-height: 1.75;letter-spacing: 0.2em;font-size: 15px;word-spacing: 0.1em;box-sizing: border-box !important;overflow-wrap: break-word !important;"><strong style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;">第一步:添加依赖</strong></p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;outline: 0px;max-width: 100%;color: rgb(0, 0, 0);font-size: 16px;text-align: left;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="margin-bottom: -7px;outline: 0px;max-width: 100%;display: block;background: url(&quot;https://mmbiz.qpic.cn/mmbiz_svg/bofA1vl6EUZxu3cvFddtMiba3PbAz3khYGg9mtNtCJHyNkNkic7mFPJa3l1xJdBEicrlK2ZXn7ePL5PLYw40dd41XEYtjGbGvQI/640?wx_fmt=svg&quot;) 10px 10px / 40px no-repeat rgb(248, 248, 248);height: 30px;width: 657px;border-radius: 5px;box-sizing: border-box !important;overflow-wrap: break-word !important;"></span><code style="padding: 15px 16px 16px;outline: 0px;max-width: 100%;overflow-x: auto;color: rgb(51, 51, 51);display: -webkit-box;font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;font-size: 12px;background: rgb(248, 248, 248);border-radius: 5px;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="outline: 0px;max-width: 100%;color: rgb(0, 0, 128);line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">&lt;<span style="outline: 0px;max-width: 100%;line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">dependency</span>&gt;</span><br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;">&nbsp;&nbsp;&nbsp;<span style="outline: 0px;max-width: 100%;color: rgb(0, 0, 128);line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">&lt;<span style="outline: 0px;max-width: 100%;line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">groupId</span>&gt;</span>org.springframework.boot<span style="outline: 0px;max-width: 100%;color: rgb(0, 0, 128);line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">&lt;/<span style="outline: 0px;max-width: 100%;line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">groupId</span>&gt;</span><br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;">&nbsp;&nbsp;&nbsp;<span style="outline: 0px;max-width: 100%;color: rgb(0, 0, 128);line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">&lt;<span style="outline: 0px;max-width: 100%;line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">artifactId</span>&gt;</span>spring-boot-starter-websocket<span style="outline: 0px;max-width: 100%;color: rgb(0, 0, 128);line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">&lt;/<span style="outline: 0px;max-width: 100%;line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">artifactId</span>&gt;</span><br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="outline: 0px;max-width: 100%;color: rgb(0, 0, 128);line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">&lt;/<span style="outline: 0px;max-width: 100%;line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">dependency</span>&gt;</span><br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"></code></pre> <p data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;padding-top: 8px;padding-bottom: 8px;outline: 0px;max-width: 100%;color: rgb(0, 0, 0);font-family: PingFangSC-Light;text-align: left;white-space: normal;line-height: 1.75;letter-spacing: 0.2em;font-size: 15px;word-spacing: 0.1em;box-sizing: border-box !important;overflow-wrap: break-word !important;"><strong style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;">第二步:添加配置类</strong></p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;outline: 0px;max-width: 100%;color: rgb(0, 0, 0);font-size: 16px;text-align: left;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="margin-bottom: -7px;outline: 0px;max-width: 100%;display: block;background: url(&quot;https://mmbiz.qpic.cn/mmbiz_svg/bofA1vl6EUZxu3cvFddtMiba3PbAz3khYGg9mtNtCJHyNkNkic7mFPJa3l1xJdBEicrlK2ZXn7ePL5PLYw40dd41XEYtjGbGvQI/640?wx_fmt=svg&quot;) 10px 10px / 40px no-repeat rgb(248, 248, 248);height: 30px;width: 657px;border-radius: 5px;box-sizing: border-box !important;overflow-wrap: break-word !important;"></span><code style="padding: 15px 16px 16px;outline: 0px;max-width: 100%;overflow-x: auto;color: rgb(51, 51, 51);display: -webkit-box;font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;font-size: 12px;background: rgb(248, 248, 248);border-radius: 5px;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="outline: 0px;max-width: 100%;color: rgb(153, 153, 153);font-weight: bold;line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">@Configuration</span><br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="outline: 0px;max-width: 100%;font-weight: bold;line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">public</span>&nbsp;<span style="outline: 0px;max-width: 100%;line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="outline: 0px;max-width: 100%;font-weight: bold;line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">class</span>&nbsp;<span style="outline: 0px;max-width: 100%;color: rgb(68, 85, 136);font-weight: bold;line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">WebSocketConfig</span>&nbsp;<span style="outline: 0px;max-width: 100%;font-weight: bold;line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">implements</span>&nbsp;<span style="outline: 0px;max-width: 100%;color: rgb(68, 85, 136);font-weight: bold;line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">WebSocketConfigurer</span>&nbsp;</span>{<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="outline: 0px;max-width: 100%;color: rgb(153, 153, 153);font-weight: bold;line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">@Override</span><br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="outline: 0px;max-width: 100%;line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="outline: 0px;max-width: 100%;font-weight: bold;line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">public</span>&nbsp;<span style="outline: 0px;max-width: 100%;font-weight: bold;line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">void</span>&nbsp;<span style="outline: 0px;max-width: 100%;color: rgb(153, 0, 0);font-weight: bold;line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">registerWebSocketHandlers</span><span style="outline: 0px;max-width: 100%;line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">(WebSocketHandlerRegistry&nbsp;registry)</span>&nbsp;</span>{<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;">&nbsp;&nbsp;&nbsp;&nbsp;registry.addHandler(myHandler(),&nbsp;<span style="outline: 0px;max-width: 100%;color: rgb(221, 17, 68);line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">"/"</span>)<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;.setAllowedOrigins(<span style="outline: 0px;max-width: 100%;color: rgb(221, 17, 68);line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">"*"</span>);<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;">}<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;">&nbsp;<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="outline: 0px;max-width: 100%;color: rgb(153, 153, 153);font-weight: bold;line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">@Bean</span><br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;">&nbsp;<span style="outline: 0px;max-width: 100%;line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="outline: 0px;max-width: 100%;font-weight: bold;line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">public</span>&nbsp;WebSocketHandler&nbsp;<span style="outline: 0px;max-width: 100%;color: rgb(153, 0, 0);font-weight: bold;line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">myHandler</span><span style="outline: 0px;max-width: 100%;line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">()</span>&nbsp;</span>{<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="outline: 0px;max-width: 100%;font-weight: bold;line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">return</span>&nbsp;<span style="outline: 0px;max-width: 100%;font-weight: bold;line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">new</span>&nbsp;MessageHandler();<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;">&nbsp;}<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;">}<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"></code></pre> <p data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;padding-top: 8px;padding-bottom: 8px;outline: 0px;max-width: 100%;color: rgb(0, 0, 0);font-family: PingFangSC-Light;text-align: left;white-space: normal;line-height: 1.75;letter-spacing: 0.2em;font-size: 15px;word-spacing: 0.1em;box-sizing: border-box !important;overflow-wrap: break-word !important;"><strong style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;">第三步:实现消息监听类</strong></p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;outline: 0px;max-width: 100%;color: rgb(0, 0, 0);font-size: 16px;text-align: left;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="margin-bottom: -7px;outline: 0px;max-width: 100%;display: block;background: url(&quot;https://mmbiz.qpic.cn/mmbiz_svg/bofA1vl6EUZxu3cvFddtMiba3PbAz3khYGg9mtNtCJHyNkNkic7mFPJa3l1xJdBEicrlK2ZXn7ePL5PLYw40dd41XEYtjGbGvQI/640?wx_fmt=svg&quot;) 10px 10px / 40px no-repeat rgb(248, 248, 248);height: 30px;width: 657px;border-radius: 5px;box-sizing: border-box !important;overflow-wrap: break-word !important;"></span><code style="padding: 15px 16px 16px;outline: 0px;max-width: 100%;overflow-x: auto;color: rgb(51, 51, 51);display: -webkit-box;font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;font-size: 12px;background: rgb(248, 248, 248);border-radius: 5px;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="outline: 0px;max-width: 100%;color: rgb(153, 153, 153);font-weight: bold;line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">@Component</span><br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="outline: 0px;max-width: 100%;color: rgb(153, 153, 153);font-weight: bold;line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">@SuppressWarnings</span>(<span style="outline: 0px;max-width: 100%;color: rgb(221, 17, 68);line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">"unchecked"</span>)<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="outline: 0px;max-width: 100%;font-weight: bold;line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">public</span>&nbsp;<span style="outline: 0px;max-width: 100%;line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="outline: 0px;max-width: 100%;font-weight: bold;line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">class</span>&nbsp;<span style="outline: 0px;max-width: 100%;color: rgb(68, 85, 136);font-weight: bold;line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">MessageHandler</span>&nbsp;<span style="outline: 0px;max-width: 100%;font-weight: bold;line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">extends</span>&nbsp;<span style="outline: 0px;max-width: 100%;color: rgb(68, 85, 136);font-weight: bold;line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">TextWebSocketHandler</span>&nbsp;</span>{<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;">&nbsp;&nbsp;&nbsp;<span style="outline: 0px;max-width: 100%;font-weight: bold;line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">private</span>&nbsp;List&lt;WebSocketSession&gt;&nbsp;clients&nbsp;=&nbsp;<span style="outline: 0px;max-width: 100%;font-weight: bold;line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">new</span>&nbsp;ArrayList&lt;&gt;();<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;">&nbsp;<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;">&nbsp;&nbsp;&nbsp;<span style="outline: 0px;max-width: 100%;color: rgb(153, 153, 153);font-weight: bold;line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">@Override</span><br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;">&nbsp;&nbsp;&nbsp;<span style="outline: 0px;max-width: 100%;line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="outline: 0px;max-width: 100%;font-weight: bold;line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">public</span>&nbsp;<span style="outline: 0px;max-width: 100%;font-weight: bold;line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">void</span>&nbsp;<span style="outline: 0px;max-width: 100%;color: rgb(153, 0, 0);font-weight: bold;line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">afterConnectionEstablished</span><span style="outline: 0px;max-width: 100%;line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">(WebSocketSession&nbsp;session)</span>&nbsp;</span>{<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;clients.add(session);<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.out.println(<span style="outline: 0px;max-width: 100%;color: rgb(221, 17, 68);line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">"uri&nbsp;:"</span>&nbsp;+&nbsp;session.getUri());<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.out.println(<span style="outline: 0px;max-width: 100%;color: rgb(221, 17, 68);line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">"连接建立:&nbsp;"</span>&nbsp;+&nbsp;session.getId());<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.out.println(<span style="outline: 0px;max-width: 100%;color: rgb(221, 17, 68);line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">"current&nbsp;seesion:&nbsp;"</span>&nbsp;+&nbsp;clients.size());<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;">&nbsp;&nbsp;&nbsp;}<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;">&nbsp;<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;">&nbsp;&nbsp;&nbsp;<span style="outline: 0px;max-width: 100%;color: rgb(153, 153, 153);font-weight: bold;line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">@Override</span><br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;">&nbsp;&nbsp;&nbsp;<span style="outline: 0px;max-width: 100%;line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="outline: 0px;max-width: 100%;font-weight: bold;line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">public</span>&nbsp;<span style="outline: 0px;max-width: 100%;font-weight: bold;line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">void</span>&nbsp;<span style="outline: 0px;max-width: 100%;color: rgb(153, 0, 0);font-weight: bold;line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">afterConnectionClosed</span><span style="outline: 0px;max-width: 100%;line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">(WebSocketSession&nbsp;session,&nbsp;CloseStatus&nbsp;status)</span>&nbsp;</span>{<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;clients.remove(session);<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.out.println(<span style="outline: 0px;max-width: 100%;color: rgb(221, 17, 68);line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">"断开连接:&nbsp;"</span>&nbsp;+&nbsp;session.getId());<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;">&nbsp;&nbsp;&nbsp;}<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;">&nbsp;<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;">&nbsp;&nbsp;&nbsp;<span style="outline: 0px;max-width: 100%;color: rgb(153, 153, 153);font-weight: bold;line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">@Override</span><br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;">&nbsp;&nbsp;&nbsp;<span style="outline: 0px;max-width: 100%;line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="outline: 0px;max-width: 100%;font-weight: bold;line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">protected</span>&nbsp;<span style="outline: 0px;max-width: 100%;font-weight: bold;line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">void</span>&nbsp;<span style="outline: 0px;max-width: 100%;color: rgb(153, 0, 0);font-weight: bold;line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">handleTextMessage</span><span style="outline: 0px;max-width: 100%;line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">(WebSocketSession&nbsp;session,&nbsp;TextMessage&nbsp;message)</span>&nbsp;</span>{<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;String&nbsp;payload&nbsp;=&nbsp;message.getPayload();<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Map&lt;String,&nbsp;String&gt;&nbsp;map&nbsp;=&nbsp;JSONObject.parseObject(payload,&nbsp;HashMap<span style="outline: 0px;max-width: 100%;line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">.<span style="outline: 0px;max-width: 100%;font-weight: bold;line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">class</span>)</span>;<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.out.println(<span style="outline: 0px;max-width: 100%;color: rgb(221, 17, 68);line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">"接受到的数据"</span>&nbsp;+&nbsp;map);<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;clients.forEach(s&nbsp;-&gt;&nbsp;{<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="outline: 0px;max-width: 100%;font-weight: bold;line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">try</span>&nbsp;{<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.out.println(<span style="outline: 0px;max-width: 100%;color: rgb(221, 17, 68);line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">"发送消息给:&nbsp;"</span>&nbsp;+&nbsp;session.getId());<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;s.sendMessage(<span style="outline: 0px;max-width: 100%;font-weight: bold;line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">new</span>&nbsp;TextMessage(<span style="outline: 0px;max-width: 100%;color: rgb(221, 17, 68);line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">"服务器返回收到的信息,"</span>&nbsp;+&nbsp;payload));<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;<span style="outline: 0px;max-width: 100%;font-weight: bold;line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">catch</span>&nbsp;(Exception&nbsp;e)&nbsp;{<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;e.printStackTrace();<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;});<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;">&nbsp;&nbsp;&nbsp;}<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;">}<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"></code></pre> <p data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;padding-top: 8px;padding-bottom: 8px;outline: 0px;max-width: 100%;color: rgb(0, 0, 0);font-family: PingFangSC-Light;text-align: justify;white-space: normal;line-height: 1.75;letter-spacing: 0.2em;font-size: 15px;word-spacing: 0.1em;box-sizing: border-box !important;overflow-wrap: break-word !important;">从这个demo中,使用spring websocket实现ws服务的便利性大家可想而知了。为了能更好地向spring cloud大家族看齐,我最终采用了spring websocket实现ws服务。</p> <p data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;padding-top: 8px;padding-bottom: 8px;outline: 0px;max-width: 100%;color: rgb(0, 0, 0);font-family: PingFangSC-Light;text-align: justify;white-space: normal;line-height: 1.75;letter-spacing: 0.2em;font-size: 15px;word-spacing: 0.1em;box-sizing: border-box !important;overflow-wrap: break-word !important;">因此我的应用服务架构是这样子的:一个应用既负责restful服务,也负责ws服务。没有将ws服务模块拆分是因为拆分出去要使用feign来进行服务调用。第一本人比较懒惰,第二拆分与不拆分相差在多了一层服务间的io调用,所以就没有这么做了。</p> <h2 data-tool="mdnice编辑器" style="margin-top: 20px;margin-right: 10px;outline: 0px;font-weight: bold;font-size: 22px;max-width: 100%;color: rgb(0, 0, 0);font-family: PingFangSC-Light;text-align: left;white-space: normal;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="color: rgb(255, 41, 65);"><strong><span style="color: rgb(255, 41, 65);font-size: 20px;">| 从zuul技术转型到spring cloud gateway</span></strong></span></h2> <p data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;padding-top: 8px;padding-bottom: 8px;outline: 0px;max-width: 100%;color: rgb(0, 0, 0);font-family: PingFangSC-Light;text-align: justify;white-space: normal;line-height: 1.75;letter-spacing: 0.2em;font-size: 15px;word-spacing: 0.1em;box-sizing: border-box !important;overflow-wrap: break-word !important;">要实现websocket集群,我们必不可免地得从zuul转型到spring cloud gateway。原因如下:</p> <p data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;padding-top: 8px;padding-bottom: 8px;outline: 0px;max-width: 100%;color: rgb(0, 0, 0);font-family: PingFangSC-Light;text-align: justify;white-space: normal;line-height: 1.75;letter-spacing: 0.2em;font-size: 15px;word-spacing: 0.1em;box-sizing: border-box !important;overflow-wrap: break-word !important;">zuul1.0版本不支持websocket转发,zuul 2.0开始支持websocket,zuul2.0几个月前开源了,但是2.0版本没有被spring boot集成,而且文档不健全。因此转型是必须的,同时转型也很容易实现。</p> <p data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;padding-top: 8px;padding-bottom: 8px;outline: 0px;max-width: 100%;color: rgb(0, 0, 0);font-family: PingFangSC-Light;text-align: justify;white-space: normal;line-height: 1.75;letter-spacing: 0.2em;font-size: 15px;word-spacing: 0.1em;box-sizing: border-box !important;overflow-wrap: break-word !important;">在gateway中,为了实现ssl认证和动态路由负载均衡,yml文件中以下的某些配置是必须的,在这里提前避免大家采坑</p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;outline: 0px;max-width: 100%;color: rgb(0, 0, 0);font-size: 16px;text-align: left;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="margin-bottom: -7px;outline: 0px;max-width: 100%;display: block;background: url(&quot;https://mmbiz.qpic.cn/mmbiz_svg/bofA1vl6EUZxu3cvFddtMiba3PbAz3khYGg9mtNtCJHyNkNkic7mFPJa3l1xJdBEicrlK2ZXn7ePL5PLYw40dd41XEYtjGbGvQI/640?wx_fmt=svg&qu

聊聊注册中心

作者:微信小助手

<section data-tool="mdnice编辑器" data-website="https://www.mdnice.com" style="font-size: 16px;color: black;padding-right: 10px;padding-left: 10px;line-height: 1.6;letter-spacing: 0px;word-break: break-word;overflow-wrap: break-word;text-align: left;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, &quot;PingFang SC&quot;, Cambria, Cochin, Georgia, Times, &quot;Times New Roman&quot;, serif;" data-mpa-powered-by="yiban.io"> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-ratio="0.6244075829383886" src="/upload/3b868ae5a1a25aa958480ba7dc91d9a.jpg" data-type="jpeg" data-w="1688" style="display: block;margin-right: auto;margin-left: auto;"> </figure> <blockquote data-tool="mdnice编辑器" style="border-top: none;border-right: none;border-bottom: none;font-size: 0.9em;overflow: auto;color: rgb(106, 115, 125);padding: 10px 10px 10px 20px;margin-bottom: 20px;margin-top: 20px;border-left-color: rgb(239, 112, 96);background: rgb(255, 249, 249);"> <p style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;color: black;line-height: 26px;">讲解5种常用的注册中心,对比其流程和原理,无论是面试还是技术选型,都非常有帮助。</p> </blockquote> <ul data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;" class="list-paddingleft-1"></ul> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">大家好,我是苏三!对于注册中心,在写这篇文章前,我其实只对ETCD有比较深入的了解,但是对于Zookeeper和其它的注册中心了解甚少,甚至都没有考虑过ETCD和Zookeeper是否适合作为注册中心。</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <span style="letter-spacing: 0px;">经过近2周的学习,原来注册中心除了ETCD和Zookeeper,常用的还有Eureka、Nacos、Consul,下面我们就对这些常用的注册中心,初探它们的异同,便于后续技术选型。</span> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">全文接近 <strong>8千字</strong>,有点长,<strong>建议先收藏,再慢慢看</strong>,下面是文章目录:</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-ratio="0.7466802860061287" src="/upload/dd5bb01f97b595ee5d9afb0d5d25ca67.png" data-type="png" data-w="979" style="display: block;margin-right: auto;margin-left: auto;"> </figure> <h1 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;font-size: 24px;"><span style="display: none;"></span>注册中心基本概念</h1> <h2 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;border-bottom: 2px solid rgb(239, 112, 96);font-size: 1.3em;"><span style="display: none;"></span><span style="display: inline-block;background: rgb(239, 112, 96);color: rgb(255, 255, 255);padding: 3px 10px 1px;border-top-right-radius: 3px;border-top-left-radius: 3px;margin-right: 3px;">什么是注册中心?</span><span style="display: inline-block;vertical-align: bottom;border-bottom: 36px solid #efebe9;border-right: 20px solid transparent;"> </span></h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">注册中心主要有三种角色:</p> <ul data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> <strong style="color: black;">服务提供者(RPC Server)</strong>:在启动时,向 Registry 注册自身服务,并向 Registry 定期发送心跳汇报存活状态。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> <strong style="color: black;">服务消费者(RPC Client)</strong>:在启动时,向 Registry 订阅服务,把 Registry 返回的服务节点列表缓存在本地内存中,并与 RPC Sever 建立连接。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> <strong style="color: black;">服务注册中心(Registry)</strong>:用于保存 RPC Server 的注册信息,当 RPC Server 节点发生变更时,Registry 会同步变更,RPC Client 感知后会刷新本地 内存中缓存的服务节点列表。 </section></li> </ul> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">最后,RPC Client 从本地缓存的服务节点列表中,基于负载均衡算法选择一台 RPC Sever 发起调用。</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-ratio="0.5256038647342995" src="/upload/c7aac9dad7a2dced050dc617def7dca2.png" data-type="png" data-w="2070" style="display: block;margin-right: auto;margin-left: auto;"> </figure> <h2 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;border-bottom: 2px solid rgb(239, 112, 96);font-size: 1.3em;"><span style="display: none;"></span><span style="display: inline-block;background: rgb(239, 112, 96);color: rgb(255, 255, 255);padding: 3px 10px 1px;border-top-right-radius: 3px;border-top-left-radius: 3px;margin-right: 3px;">注册中心需要实现功能</span><span style="display: inline-block;vertical-align: bottom;border-bottom: 36px solid #efebe9;border-right: 20px solid transparent;"> </span></h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">根据注册中心原理的描述,注册中心必须实现以下功能,偷个懒,直接贴幅图:</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-ratio="0.40160183066361554" src="/upload/e9e249681b60bd668866ff9faafa618b.png" data-type="png" data-w="1748" style="display: block;margin-right: auto;margin-left: auto;"> </figure> <h1 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;font-size: 24px;"><span style="display: none;"></span>注册中心基础扫盲</h1> <blockquote data-tool="mdnice编辑器" style="border-top: none;border-right: none;border-bottom: none;font-size: 0.9em;overflow: auto;color: rgb(106, 115, 125);padding: 10px 10px 10px 20px;margin-bottom: 20px;margin-top: 20px;border-left-color: rgb(239, 112, 96);background: rgb(255, 249, 249);"> <p style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;color: black;line-height: 26px;">这块知识如果大家已经知道,可以直接跳过,主要是为了扫盲。</p> </blockquote> <h2 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;border-bottom: 2px solid rgb(239, 112, 96);font-size: 1.3em;"><span style="display: none;"></span><span style="display: inline-block;background: rgb(239, 112, 96);color: rgb(255, 255, 255);padding: 3px 10px 1px;border-top-right-radius: 3px;border-top-left-radius: 3px;margin-right: 3px;">CAP理论</span><span style="display: inline-block;vertical-align: bottom;border-bottom: 36px solid #efebe9;border-right: 20px solid transparent;"> </span></h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">CAP理论是分布式架构中重要理论:</p> <ul data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 一致性(Consistency):所有节点在同一时间具有相同的数据; </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 可用性(Availability) :保证每个请求不管成功或者失败都有响应; </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 分隔容忍(Partition tolerance) :系统中任意信息的丢失或失败不会影响系统的继续运作。 </section></li> </ul> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">关于 P 的理解,我觉得是在整个系统中某个部分,挂掉了,或者宕机了,并不影响整个系统的运作或者说使用,而可用性是,某个系统的某个节点挂了,但是并不影响系统的接受或者发出请求。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">CAP 不可能都取,只能取其中2个的原因如下:</p> <ul data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 如果C是第一需求的话,那么会影响A的性能,因为要数据同步,不然请求结果会有差异,但是数据同步会消耗时间,期间可用性就会降低。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 如果A是第一需求,那么只要有一个服务在,就能正常接受请求,但是对于返回结果变不能保证,原因是,在分布式部署的时候,数据一致的过程不可能想切线路那么快。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 再如果,同时满足一致性和可用性,那么分区容错就很难保证了,也就是单点,也是分布式的基本核心。 </section></li> </ul> <h2 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;border-bottom: 2px solid rgb(239, 112, 96);font-size: 1.3em;"><span style="display: none;"></span><span style="display: inline-block;background: rgb(239, 112, 96);color: rgb(255, 255, 255);padding: 3px 10px 1px;border-top-right-radius: 3px;border-top-left-radius: 3px;margin-right: 3px;">分布式系统协议</span><span style="display: inline-block;vertical-align: bottom;border-bottom: 36px solid #efebe9;border-right: 20px solid transparent;"> </span></h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">一致性协议算法主要有Paxos、Raft、ZAB。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">Paxos算法是Leslie Lamport在1990年提出的一种基于消息传递的一致性算法,非常难以理解,基于Paxos协议的数据同步与传统主备方式最大的区别在于:Paxos只需超过半数的副本在线且相互通信正常,就可以保证服务的持续可用,且数据不丢失。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">Raft是斯坦福大学的Diego Ongaro、John Ousterhout两个人以易理解为目标设计的一致性算法,已经有了十几种语言的Raft算法实现框架,较为出名的有etcd,Google的Kubernetes也是用了etcd作为他的服务发现框架。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">Raft是Paxos的简化版,与Paxos相比,Raft强调的是易理解、易实现,Raft和Paxos一样只要保证超过半数的节点正常就能够提供服务。这篇文章 <a href="https://mp.weixin.qq.com/s?__biz=Mzg3OTU5NzQ1Mw==&amp;mid=2247484080&amp;idx=1&amp;sn=24ccb6e7de6d7a274c75296799b57c32&amp;chksm=cf034052f874c944a601a6eb50d524753d7adaf59c0f2326a47a3104302c2544a6901104668f&amp;token=333114218&amp;lang=zh_CN&amp;scene=21#wechat_redirect" style="overflow-wrap: break-word;font-weight: bold;color: rgb(239, 112, 96);border-bottom: 1px solid rgb(239, 112, 96);" data-linktype="2">《ETCD教程-2.Raft协议》</a> 详细讲解了Raft原理,非常有意思,感兴趣的同学可以看看。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">ZooKeeper Atomic Broadcast (ZAB, ZooKeeper原子消息广播协议)是ZooKeeper实现分布式数据一致性的核心算法,ZAB借鉴Paxos算法,但又不像Paxos算法那样,是一种通用的分布式一致性算法,它是一种特别为ZooKeeper专门设计的支持崩溃恢复的原子广播协议。</p> <h1 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;font-size: 24px;"><span style="display: none;"></span>常用注册中心</h1> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">这里主要介绍5种常用的注册中心,分别为<strong>Zookeeper、Eureka、Nacos、Consul和ETCD</strong>。</p> <h2 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;border-bottom: 2px solid rgb(239, 112, 96);font-size: 1.3em;"><span style="display: none;"></span><span style="display: inline-block;background: rgb(239, 112, 96);color: rgb(255, 255, 255);padding: 3px 10px 1px;border-top-right-radius: 3px;border-top-left-radius: 3px;margin-right: 3px;">Zookeeper</span><span style="display: inline-block;vertical-align: bottom;border-bottom: 36px solid #efebe9;border-right: 20px solid transparent;"> </span></h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">这个说起来有点意思的是官方并没有说他是一个注册中心,但是国内Dubbo场景下很多都是使用Zookeeper来完成了注册中心的功能。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">当然这有很多历史原因,这里我们就不追溯了。ZooKeeper是非常经典的服务注册中心中间件,在国内环境下,由于受到Dubbo框架的影响,大部分情况下认为Zookeeper是RPC服务框架下注册中心最好选择,随着Dubbo框架的不断开发优化,和各种注册中心组件的诞生,即使是RPC框架,现在的注册中心也逐步放弃了ZooKeeper。在常用的开发集群环境中,ZooKeeper依然起到十分重要的作用,Java体系中,大部分的集群环境都是依赖ZooKeeper管理服务的各个节点。</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-ratio="0.2836004932182491" src="/upload/11eea2a5a2bff32d9c579efd160e2db5.png" data-type="png" data-w="1622" style="display: block;margin-right: auto;margin-left: auto;"> </figure> <h3 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;font-size: 20px;"><span style="display: none;"></span>Zookeeper如何实现注册中心<span style="display: none;"></span></h3> <blockquote data-tool="mdnice编辑器" style="border-top: none;border-right: none;border-bottom: none;font-size: 0.9em;overflow: auto;color: rgb(106, 115, 125);padding: 10px 10px 10px 20px;margin-bottom: 20px;margin-top: 20px;border-left-color: rgb(239, 112, 96);background: rgb(255, 249, 249);"> <p style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;color: black;line-height: 26px;">具体可参考这篇文章 <a href="https://mp.weixin.qq.com/s?__biz=Mzg3OTU5NzQ1Mw==&amp;mid=2247486840&amp;idx=1&amp;sn=254bb499f1d79a6cc42023f8d143822f&amp;chksm=cf034f9af874c68cc40e4a7da28ad5302173d876fa0b52808644b9d7f4eec10a13712364ef54&amp;scene=21#wechat_redirect" style="overflow-wrap: break-word;font-weight: bold;color: rgb(239, 112, 96);border-bottom: 1px solid rgb(239, 112, 96);" data-linktype="2">《Zookeeper用作注册中心的原理》</a>,下面的内容都出自该文章。</p> </blockquote> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">Zookeeper可以充当一个服务注册表(Service Registry),让多个服务提供者形成一个集群,让服务消费者通过服务注册表获取具体的服务访问地址(Ip+端口)去访问具体的服务提供者。如下图所示:</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-ratio="0.43617021276595747" src="/upload/326f42e04eed88b00c7eaae5da69fc51.png" data-type="png" data-w="2068" style="display: block;margin-right: auto;margin-left: auto;"> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">每当一个服务提供者部署后都要将自己的服务注册到zookeeper的某一路径上: /{service}/{version}/{ip:port} 。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">比如我们的HelloWorldService部署到两台机器,那么Zookeeper上就会创建两条目录:</p> <ul data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> /HelloWorldService/1.0.0/100.19.20.01:16888 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> /HelloWorldService/1.0.0/100.19.20.02:16888 </section></li> </ul> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">这么描述有点不好理解,下图更直观:</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-ratio="0.5300285986653956" src="/upload/28243a726ed25a239b2c39dc9f6ed6c5.png" data-type="png" data-w="2098" style="display: block;margin-right: auto;margin-left: auto;"> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">在zookeeper中,进行服务注册,实际上就是在zookeeper中创建了一个znode节点,该节点存储了该服务的IP、端口、调用方式(协议、序列化方式)等。该节点承担着最重要的职责,它由服务提供者(发布服务时)创建,以供服务消费者获取节点中的信息,从而定位到服务提供者真正网络拓扑位置以及得知如何调用。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;"><strong>RPC服务注册/发现过程简述如下:</strong></p> <ol data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 服务提供者启动时,会将其服务名称,ip地址注册到配置中心。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 服务消费者在第一次调用服务时,会通过注册中心找到相应的服务的IP地址列表,并缓存到本地,以供后续使用。当消费者调用服务时,不会再去请求注册中心,而是直接通过负载均衡算法从IP列表中取一个服务提供者的服务器调用服务。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 当服务提供者的某台服务器宕机或下线时,相应的ip会从服务提供者IP列表中移除。同时,注册中心会将新的服务IP地址列表发送给服务消费者机器,缓存在消费者本机。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 当某个服务的所有服务器都下线了,那么这个服务也就下线了。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 同样,当服务提供者的某台服务器上线时,注册中心会将新的服务IP地址列表发送给服务消费者机器,缓存在消费者本机。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 服务提供方可以根据服务消费者的数量来作为服务下线的依据。 </section></li> </ol> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">zookeeper提供了“心跳检测”功能:<strong>它会定时向各个服务提供者发送一个请求(实际上建立的是一个 socket 长连接),如果长期没有响应,服务中心就认为该服务提供者已经“挂了”,并将其剔除。</strong></p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">比如100.100.0.237这台机器如果宕机了,那么zookeeper上的路径就会只剩/HelloWorldService/1.0.0/100.100.0.238:16888。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">Zookeeper的Watch机制其实就是一种<strong>推拉结合的模式</strong>:</p> <ul data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 服务消费者会去监听相应路径(/HelloWorldService/1.0.0),一旦路径上的数据有任务变化(增加或减少), <strong style="color: black;">Zookeeper只会发送一个事件类型和节点信息给关注的客户端,而不会包括具体的变更内容</strong>,所以事件本身是轻量级的,这就是推的部分。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> <strong style="color: black;">收到变更通知的客户端需要自己去拉变更的数据</strong>,这就是拉的部分。 </section></li> </ul> <h3 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;font-size: 20px;"><span style="display: none;"></span>Zookeeper不适合作为注册中心<span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">作为一个分布式协同服务,ZooKeeper非常好,但是对于Service发现服务来说就不合适了,因为对于Service发现服务来说就算是返回了包含不实的信息的结果也比什么都不返回要好。<strong>所以当向注册中心查询服务列表时,我们可以容忍注册中心返回的是几分钟以前的注册信息,但不能接受服务直接down掉不可用。</strong></p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">但是zk会出现这样一种情况,当master节点因为网络故障与其他节点失去联系时,剩余节点会重新进行leader选举。问题在于,选举leader的时间太长,30 ~ 120s, 且选举期间整个zk集群都是不可用的,这就导致在选举期间注册服务瘫痪。在云部署的环境下,因网络问题使得zk集群失去master节点是较大概率会发生的事,虽然服务能够最终恢复,但是漫长的选举时间导致的注册长期不可用是不能容忍的。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">所以说,<strong>作为注册中心,可用性的要求要高于一致性!</strong></p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">在 CAP 模型中,<strong>Zookeeper整体遵循一致性(CP)原则</strong>,即在任何时候对 Zookeeper 的访问请求能得到一致的数据结果,但是当机器下线或者宕机时,<strong>不能保证服务可用性。</strong></p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">那为什么Zookeeper不使用最终一致性(AP)模型呢?因为这个依赖<strong>Zookeeper的核心算法是ZAB,所有设计都是为了强一致性</strong>。这个对于分布式协调系统,完全没没有毛病,但是<strong>你如果将Zookeeper为分布式协调服务所做的一致性保障,用在注册中心,或者说服务发现场景,这个其实就不合适。</strong></p> <h2 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;border-bottom: 2px solid rgb(239, 112, 96);font-size: 1.3em;"><span style="display: none;"></span><span style="display: inline-block;background: rgb(239, 112, 96);color: rgb(255, 255, 255);padding: 3px 10px 1px;border-top-right-radius: 3px;border-top-left-radius: 3px;margin-right: 3px;">Eureka</span><span style="display: inline-block;vertical-align: bottom;border-bottom: 36px solid #efebe9;border-right: 20px solid transparent;"> </span></h2> <h3 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;font-size: 20px;"><span style="display: none;"></span>Eureka 架构图<span style="display: none;"></span></h3> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-ratio="0.586" src="/upload/94a79d228ed044f667e475bfc0c83bd.jpg" data-type="jpeg" data-w="2000" style="display: block;margin-right: auto;margin-left: auto;"> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">什么,上面这幅图看起来很复杂?那我给你贴个简化版:</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-ratio="0.5892682926829268" src="/upload/b1801124b8a04bc6612d6def56337364.png" data-type="png" data-w="2050" style="display: block;margin-right: auto;margin-left: auto;"> </figure> <h3 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;font-size: 20px;"><span style="display: none;"></span>Eureka 特点<span style="display: none;"></span></h3> <ul data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> <strong style="color: black;">可用性(AP原则)</strong>:Eureka 在设计时就紧遵AP原则,Eureka的集群中,只要有一台Eureka还在,就能保证注册服务可用,只不过查到的信息可能不是最新的(不保证强一致性)。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> <strong style="color: black;">去中心化架构</strong>:Eureka Server 可以运行多个实例来构建集群,不同于 ZooKeeper 的选举 leader 的过程,Eureka Server 采用的是Peer to Peer 对等通信。这是一种去中心化的架构,无 master/slave 之分,每一个 Peer 都是对等的。节点通过彼此互相注册来提高可用性,每个节点需要添加一个或多个有效的 serviceUrl 指向其他节点。每个节点都可被视为其他节点的副本。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> <strong style="color: black;">请求自动切换</strong>:在集群环境中如果某台 Eureka Server 宕机,Eureka Client 的请求会自动切换到新的 Eureka Server 节点上,当宕机的服务器重新恢复后,Eureka 会再次将其纳入到服务器集群管理之中。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> <strong style="color: black;">节点间操作复制</strong>:当节点开始接受客户端请求时,所有的操作都会在节点间进行复制操作,将请求复制到该 Eureka Server 当前所知的其它所有节点中。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> <strong style="color: black;">自动注册&amp;心跳</strong>:当一个新的 Eureka Server 节点启动后,会首先尝试从邻近节点获取所有注册列表信息,并完成初始化。Eureka Server 通过 getEurekaServiceUrls() 方法获取所有的节点,并且会通过心跳契约的方式定期更新。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> <strong style="color: black;">自动下线</strong>:默认情况下,如果 Eureka Server 在一定时间内没有接收到某个服务实例的心跳(默认周期为30秒),Eureka Server 将会注销该实例(默认为90秒, eureka.instance.lease-expiration-duration-in-seconds 进行自定义配置)。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> <strong style="color: black;">保护模式</strong>:当 Eureka Server 节点在短时间内丢失过多的心跳时,那么这个节点就会进入自我保护模式。 </section></li> </ul> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">除了上述特点,Eureka还有一种自我保护机制,如果在15分钟内超过 <strong>85%</strong> 的节点都没有正常的心跳,那么Eureka就认为客户端与注册中心出现了网络故障,此时会出现以下几种情况:</p> <ul data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> Eureka不再从注册表中移除因为长时间没有收到心跳而过期的服务; </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> Eureka仍然能够接受新服务注册和查询请求,但是不会被同步到其它节点上(即保证当前节点依然可用) </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 当网络稳定时,当前实例新注册的信息会被同步到其它节点中。 </section></li> </ul> <h3 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;font-size: 20px;"><span style="display: none;"></span>Eureka工作流程<span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">了解完 Eureka 核心概念,自我保护机制,以及集群内的工作原理后,我们来整体梳理一下 Eureka 的工作流程:</p> <ol data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> Eureka Server 启动成功,等待服务端注册。在启动过程中如果配置了集群,集群之间定时通过 Replicate 同步注册表,每个 Eureka Server 都存在独立完整的服务注册表信息。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> Eureka Client 启动时根据配置的 Eureka Server 地址去注册中心注册服务。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> Eureka Client 会每 30s 向 Eureka Server 发送一次心跳请求,证明客户端服务正常。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 当 Eureka Server 90s 内没有收到 Eureka Client 的心跳,注册中心则认为该节点失效,会注销该实例。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 单位时间内 Eureka Server 统计到有大量的 Eureka Client 没有上送心跳,则认为可能为网络异常,进入自我保护机制,不再剔除没有上送心跳的客户端。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 当 Eureka Client 心跳请求恢复正常之后,Eureka Server 自动退出自我保护模式。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> Eureka Client 定时全量或者增量从注册中心获取服务注册表,并且将获取到的信息缓存到本地。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 服务调用时,Eureka Client 会先从本地缓存找寻调取的服务。如果获取不到,先从注册中心刷新注册表,再同步到本地缓存。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> Eureka Client 获取到目标服务器信息,发起服务调用。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> Eureka Client 程序关闭时向 Eureka Server 发送取消请求,Eureka Server 将实例从注册表中删除。 </section></li> </ol> <blockquote data-tool="mdnice编辑器" style="border-top: none;border-right: none;border-bottom: none;font-size: 0.9em;overflow: auto;color: rgb(106, 115, 125);padding: 10px 10px 10px 20px;margin-bottom: 20px;margin-top: 20px;border-left-color: rgb(239, 112, 96);background: rgb(255, 249, 249);"> <p style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;color: black;line-height: 26px;">通过分析 Eureka 工作原理,我可以明显地感觉到 Eureka 的设计之巧妙,完美地解决了注册中心的稳定性和高可用性。</p> <p style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;color: black;line-height: 26px;">Eureka 为了保障注册中心的高可用性,容忍了数据的非强一致性,服务节点间的数据可能不一致, Client-Server 间的数据可能不一致。<strong>比较适合跨越多机房、对注册中心服务可用性要求较高的使用场景。</strong></p> </blockquote> <h2 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;border-bottom: 2px solid rgb(239, 112, 96);font-size: 1.3em;"><span style="display: none;"></span><span style="display: inline-block;background: rgb(239, 112, 96);color: rgb(255, 255, 255);padding: 3px 10px 1px;border-top-right-radius: 3px;border-top-left-radius: 3px;margin-right: 3px;">Nacos</span><span style="display: inline-block;vertical-align: bottom;border-bottom: 36px solid #efebe9;border-right: 20px solid transparent;"> </span></h2> <blockquote data-tool="mdnice编辑器" style="border-top: none;border-right: none;border-bottom: none;font-size: 0.9em;overflow: auto;color: rgb(106, 115, 125);padding: 10px 10px 10px 20px;margin-bottom: 20px;margin-top: 20px;border-left-color: rgb(239, 112, 96);background: rgb(255, 249, 249);"> <p style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;color: black;line-height: 26px;">以下内容摘抄自Nacos官网:https://nacos.io/zh-cn/docs/what-is-nacos.html</p> </blockquote> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-ratio="0.4449152542372881" src="/upload/40c47a3a23604e1467802a5b932f9a81.png" data-type="png" data-w="1888" style="display: block;margin-right: auto;margin-left: auto;"> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">Nacos 致力于帮助您发现、配置和管理微服务。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据及流量管理。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。Nacos 是构建以“服务”为中心的现代应用架构 (例如微服务范式、云原生范式) 的服务基础设施。</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-ratio="0.6523517382413088" src="/upload/79c4290abac77719686655e7285e533.png" data-type="png" data-w="1956" style="display: block;margin-right: auto;margin-left: auto;"> </figure> <h3 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;font-size: 20px;"><span style="display: none;"></span>Nacos 主要特点<span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;"><strong>服务发现和服务健康监测</strong>:</p> <ul data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> Nacos 支持基于 DNS 和基于 RPC 的服务发现。服务提供者使用原生SDK、OpenAPI、或一个独立的Agent TODO注册 Service 后,服务消费者可以使用DNS TODO 或HTTP&amp;API查找和发现服务。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> Nacos 提供对服务的实时的健康检查,阻止向不健康的主机或服务实例发送请求。Nacos 支持传输层 (PING 或 TCP)和应用层 (如 HTTP、MySQL、用户自定义)的健康检查。对于复杂的云环境和网络拓扑环境中(如 VPC、边缘网络等)服务的健康检查,Nacos 提供了 agent 上报模式和服务端主动检测2种健康检查模式。Nacos 还提供了统一的健康检查仪表盘,帮助您根据健康状态管理服务的可用性及流量。 </section></li> </ul> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;"><strong>动态配置服务</strong>:</p> <ul data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 动态配置服务可以让您以中心化、外部化和动态化的方式管理所有环境的应用配置和服务配置。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 动态配置消除了配置变更时重新部署应用和服务的需要,让配置管理变得更加高效和敏捷。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 配置中心化管理让实现无状态服务变得更简单,让服务按需弹性扩展变得更容易。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> Nacos 提供了一个简洁易用的UI (控制台样例 Demo) 帮助您管理所有的服务和应用的配置。Nacos 还提供包括配置版本跟踪、金丝雀发布、一键回滚配置以及客户端配置更新状态跟踪在内的一系列开箱即用的配置管理特性,帮助您更安全地在生产环境中管理配置变更和降低配置变更带来的风险。 </section></li> </ul> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;"><strong>动态 DNS 服务</strong>:</p> <ul data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 动态 DNS 服务支持权重路由,让您更容易地实现中间层负载均衡、更灵活的路由策略、流量控制以及数据中心内网的简单DNS解析服务。动态DNS服务还能让您更容易地实现以 DNS 协议为基础的服务发现,以帮助您消除耦合到厂商私有服务发现 API 上的风险。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> Nacos 提供了一些简单的 DNS APIs TODO 帮助您管理服务的关联域名和可用的 IP:PORT 列表。 </section></li> </ul> <blockquote data-tool="mdnice编辑器" style="border-top: none;border-right: none;border-bottom: none;font-size: 0.9em;overflow: auto;color: rgb(106, 115, 125);padding: 10px 10px 10px 20px;margin-bottom: 20px;margin-top: 20px;border-left-color: rgb(239, 112, 96);background: rgb(255, 249, 249);"> <p style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;color: black;line-height: 26px;">小节一下:</p> <ul style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: black;" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> Nacos是阿里开源的,支持基于 DNS 和基于 RPC 的服务发现。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> <strong style="color: black;">Nacos的注册中心支持CP也支持AP</strong>,对他来说只是一个命令的切换,随你玩,还支持各种注册中心迁移到Nacos,反正一句话,只要你想要的他就有。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> <strong style="color: black;">Nacos除了服务的注册发现之外,还支持动态配置服务</strong>,一句话概括就是 <strong style="color: black;">Nacos = Spring Cloud注册中心 + Spring Cloud配置中心</strong>。 </section></li> </ul> </blockquote> <h2 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;border-bottom: 2px solid rgb(239, 112, 96);font-size: 1.3em;"><span style="display: none;"></span><span style="display: inline-block;background: rgb(239, 112, 96);color: rgb(255, 255, 255);padding: 3px 10px 1px;border-top-right-radius: 3px;border-top-left-radius: 3px;margin-right: 3px;">Consul</span><span style="display: inline-block;vertical-align: bottom;border-bottom: 36px solid #efebe9;border-right: 20px solid transparent;"> </span></h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">Consul 是 HashiCorp 公司推出的开源工具,用于实现分布式系统的服务发现与配置。与其它分布式服务注册与发现的方案,Consul 的方案更“一站式”,内置了服务注册与发现框 架、分布一致性协议实现、健康检查、Key/Value 存储、多数据中心方案,不再需要依赖其它工具(比如 ZooKeeper 等)。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">Consul 使用起来也较为简单,使用 Go 语言编写,因此具有天然可移植性(支持Linux、windows和Mac OS X);安装包仅包含一个可执行文件,方便部署,与 Docker 等轻量级容器可无缝配合。</p> <h3 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;font-size: 20px;"><span style="display: none;"></span>Consul 的调用过程<span style="display: none;"></span></h3> <ol data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 当 Producer 启动的时候,会向 Consul 发送一个 post 请求,告诉 Consul 自己的 IP 和 Port; </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> Consul 接收到 Producer 的注册后,每隔 10s(默认)会向 Producer 发送一个健康检查的请求,检验 Producer 是否健康; </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 当 Consumer 发送 GET 方式请求 /api/address 到 Producer 时,会先从 Consul 中拿到一个存储服务 IP 和 Port 的临时表,从表中拿到 Producer 的 IP 和 Port 后再发送 GET 方式请求 /api/address; </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 该临时表每隔 10s 会更新,只包含有通过了健康检查的 Producer。 </section></li> </ol> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-ratio="0.5141129032258065" src="/upload/ab3303aefffb02687c630a852613905a.png" data-type="png" data-w="992" style="display: block;margin-right: auto;margin-left: auto;"> </figure> <h3 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;font-size: 20px;"><span style="display: none;"></span>Consul 主要特征<span style="display: none;"></span></h3> <ul data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> CP模型,使用 Raft 算法来保证强一致性,不保证可用性; </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 支持服务注册与发现、健康检查、KV Store功能。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 支持多数据中心,可以避免单数据中心的单点故障,而其部署则需要考虑网络延迟, 分片等情况等。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 支持安全服务通信,Consul可以为服务生成和分发TLS证书,以建立相互的TLS连接。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 支持 http 和 dns 协议接口; </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 官方提供 web 管理界面。 </section></li> </ul> <h3 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;font-size: 20px;"><span style="display: none;"></span>多数据中心<span style="display: none;"></span></h3> <blockquote data-tool="mdnice编辑器" style="border-top: none;border-right: none;border-bottom: none;font-size: 0.9em;overflow: auto;color: rgb(106, 115, 125);padding: 10px 10px 10px 20px;margin-bottom: 20px;margin-top: 20px;border-left-color: rgb(239, 112, 96);background: rgb(255, 249, 249);"> <p style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;color: black;line-height: 26px;">这里纯属了解,学习一下 Consul 的多数据中心是如何实现的。</p> </blockquote> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">Consul支持开箱即用的多数据中心,这意味着用户不需要担心需要建立额外的抽象层让业务扩展到多个区域。</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-ratio="0.9040902679830748" src="/upload/b3c9adecd12105b9219a2d68142b7b93.png" data-type="png" data-w="1418" style="display: block;margin-right: auto;margin-left: auto;"> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">在上图中有两个DataCenter,他们通过Internet互联,同时请注意为了提高通信效率,只有Server节点才加入跨数据中心的通信。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">在单个数据中心中,Consul分为Client和Server两种节点(所有的节点也被称为Agent),Server节点保存数据,Client负责健康检查及转发数据请求到Server;Server节点有一个Leader和多个Follower,Leader节点会将数据同步到Follower,Server的数量推荐是3个或者5个,在Leader挂掉的时候会启动选举机制产生一个新的Leader。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">集群内的Consul节点通过gossip协议(流言协议)维护成员关系,也就是说某个节点了解集群内现在还有哪些节点,这些节点是Client还是Server。单个数据中心的流言协议同时使用TCP和UDP通信,并且都使用8301端口。跨数据中心的流言协议也同时使用TCP和UDP通信,端口使用8302。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">集群内数据的读写请求既可以直接发到Server,也可以通过Client使用RPC转发到Server,请求最终会到达Leader节点,在允许数据延时的情况下,读请求也可以在普通的Server节点完成,集群内数据的读写和复制都是通过TCP的8300端口完成。</p> <h2 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;border-bottom: 2px solid rgb(239, 112, 96);font-size: 1.3em;"><span style="display: none;"></span><span style="display: inline-block;background: rgb(239, 112, 96);color: rgb(255, 255, 255);padding: 3px 10px 1px;border-top-right-radius: 3px;border-top-left-radius: 3px;margin-right: 3px;">ETCD</span><span style="display: inline-block;vertical-align: bottom;border-bottom: 36px solid #efebe9;border-right: 20px solid transparent;"> </span></h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">etcd是一个Go言编写的分布式、高可用的一致性键值存储系统,用于提供可靠的分布式键值存储、配置共享和服务发现等功能。</p> <h3 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;font-size: 20px;"><span style="display: none;"></span>ETCD 特点<span style="display: none;"></span></h3> <ul data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 易使用:基于HTTP+JSON的API让你用curl就可以轻松使用; </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 易部署:使用Go语言编写,跨平台,部署和维护简单; </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 强一致:使用Raft算法充分保证了分布式系统数据的强一致性; </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 高可用:具有容错能力,假设集群有n个节点,当有(n-1)/2节点发送故障,依然能提供服务; </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 持久化:数据更新后,会通过WAL格式数据持久化到磁盘,支持Snapshot快照; </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 快速:每个实例每秒支持一千次写操作,极限写性能可达10K QPS; </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 安全:可选SSL客户认证机制; </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> ETCD 3.0:除了上述功能,还支持gRPC通信、watch机制。 </section></li> </ul> <h3 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;font-size: 20px;"><span style="display: none;"></span>ETCD 框架<span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">etcd主要分为四个部分:</p> <ul data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> HTTP Server:用于处理用户发送的API请求以及其它etcd节点的同步与心跳信息请求。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> Store:用于处理etcd支持的各类功能的事务,包括数据索引、节点状态变更、监控与反馈、事件处理与执行等等,是etcd对用户提供的大多数API功能的具体实现。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> Raft:Raft强一致性算法的具体实现,是etcd的核心。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> WAL:Write Ahead Log(预写式日志),是etcd的数据存储方式。除了在内存中存有所有数据的状态以及节点的索引以外,etcd就通过WAL进行持久化存储。WAL中,所有的数据提交前都会事先记录日志。Snapshot是为了防止数据过多而进行的状态快照;Entry表示存储的具体日志内容。 </section></li> </ul> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-ratio="0.5222052067381318" src="/upload/2ac60476218038114404488d45e0cfe1.png" data-type="png" data-w="1306" style="display: block;margin-right: auto;margin-left: auto;"> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">通常,一个用户的请求发送过来,会经由HTTP Server转发给Store进行具体的事务处理,如果涉及到节点的修改,则交给Raft模块进行状态的变更、日志的记录,然后再同步给别的etcd节点以确认数据提交,最后进行数据的提交,再次同步。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">更多关于ETCD相关知识,可以查看该文章 <a href="https://mp.weixin.qq.com/s?__biz=Mzg3OTU5NzQ1Mw==&amp;mid=2247485759&amp;idx=1&amp;sn=41957e94a2c69426befafd373fbddcc5&amp;chksm=cf034bddf874c2cb52a7aafea5cd194e70308c7d4ad74183db8a36d3747122be1c7a31b84ee3&amp;token=179167416&amp;lang=zh_CN&amp;scene=21#wechat_redirect" style="overflow-wrap: break-word;font-weight: bold;color: rgb(239, 112, 96);border-bottom: 1px solid rgb(239, 112, 96);" data-linktype="2">《肝了一个月的ETCD,从Raft原理到实践》</a></p> <h1 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;font-size: 24px;"><span style="display: none;"></span>注册中心对比&amp;选型</h1> <h2 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;border-bottom: 2px solid rgb(239, 112, 96);font-size: 1.3em;"><span style="display: none;"></span><span style="display: inline-block;background: rgb(239, 112, 96);color: rgb(255, 255, 255);padding: 3px 10px 1px;border-top-right-radius: 3px;border-top-left-radius: 3px;margin-right: 3px;">注册中心对比</span><span style="display: inline-block;vertical-align: bottom;border-bottom: 36px solid #efebe9;border-right: 20px solid transparent;"> </span></h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;"><img class="rich_pages wxw-img" data-ratio="0.6683937823834197" src="/upload/df70743786bdb6c7d84f6071a46516cb.png" data-type="png" data-w="1930" style="display: block;margin-right: auto;margin-left: auto;"><img class="rich_pages wxw-img" data-ratio="0.13975155279503104" src="/upload/31a58c0b0cac8da2366d8da4fef5b33d.png" data-type="png" data-w="1932" style="display: block;margin-right: auto;margin-left: auto;"></p> <ul data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> <strong style="color: black;">服务健康检查</strong>:Euraka 使用时需要显式配置健康检查支持;Zookeeper、Etcd 则在失去了和服务进程的连接情况下任务不健康,而 Consul 相对更为详细点,比如内存是否已使用了90%,文件系统的空间是不是快不足了。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> <strong style="color: black;">多数据中心</strong>:Consul 和 Nacos 都支持,其他的产品则需要额外的开发工作来实现。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> <strong style="color: black;">KV 存储服务</strong>:除了 Eureka,其他几款都能够对外支持 k-v 的存储服务,所以后面会讲到这几款产品追求高一致性的重要原因。而提供存储服务,也能够较好的转化为动态配置服务哦。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> <strong style="color: black;">CAP 理论的取舍</strong>: </section></li> <ul style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: black;list-style-type: square;" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> Eureka 是典型的 AP,Nacos可以配置为 AP,作为分布式场景下的服务发现的产品较为合适,服务发现场景的可用性优先级较高,一致性并不是特别致命。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 而Zookeeper、Etcd、Consul则是 CP 类型牺牲可用性,在服务发现场景并没太大优势; </section></li> </ul> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> <strong style="color: black;">Watch的支持</strong>:Zookeeper 支持服务器端推送变化,其它都通过长轮询的方式来实现变化的感知。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> <strong style="color: black;">自身集群的监控</strong>:除了Zookeeper和Nacos,其它几款都默认支持 metrics,运维者可以搜集并报警这些度量信息达到监控目的。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> <strong style="color: black;">Spring Cloud的集成</strong>:目前都有相对应的 boot starter,提供了集成能力。 </section></li> </ul> <h2 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;border-bottom: 2px solid rgb(239, 112, 96);font-size: 1.3em;"><span style="display: none;"></span><span style="display: inline-block;background: rgb(239, 112, 96);color: rgb(255, 255, 255);padding: 3px 10px 1px;border-top-right-radius: 3px;border-top-left-radius: 3px;margin-right: 3px;">注册中心选型</span><span style="display: inline-block;vertical-align: bottom;border-bottom: 36px solid #efebe9;border-right: 20px solid transparent;"> </span></h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">关于注册中心的对比和选型,其实上面已经讲的非常清楚了,我给出一些个人理解:</p> <ul data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> <strong style="color: black;">关于CP还是AP的选择</strong>:选择 AP,因为可用性高于一致性,所以更倾向 Eureka 和 Nacos;关于Eureka、Nacos如何选择,哪个让我做的事少,我就选择哪个,显然 Nacos 帮我们做了更多的事。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> <strong style="color: black;">技术体系</strong>:Etcd 和 Consul 都是Go开发的,Eureka、Nacos、Zookeeper 和 Zookeeper 都是Java开发的,可能项目属于不同的技术栈,会偏向选择对应的技术体系。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> <strong style="color: black;">高可用</strong>:这几款开源产品都已经考虑如何搭建高可用集群,有些差别而已; </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> <strong style="color: black;">产品的活跃度</strong>:这几款开源产品整体上都比较活跃。 </section></li> </ul> <blockquote data-tool="mdnice编辑器" style="border-top: none;border-right: none;border-bottom: none;font-size: 0.9em;overflow: auto;color: rgb(106, 115, 125);padding: 10px 10px 10px 20px;margin-bottom: 20px;margin-top: 20px;border-left-color: rgb(239, 112, 96);background: rgb(255, 249, 249);"> <p style="font-size: 16px;padding-top: 8px;padding-bottom: 8px;color: black;line-height: 26px;">尽信书则不如无书,因个人能力有限,难免有疏漏和错误之处,如发现bug或者有更好的建议,欢迎批评指正,不吝感激。</p> </blockquote> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;"><br></p> </section> <section class="mp_profile_iframe_wrp"> <mpprofile class="js_uneditable custom_select_card mp_profile_iframe" data-pluginname="mpprofile" data-id="Mzg4NjYyODc4OA==" data-headimg="http://mmbiz.qpic.cn/mmbiz_png/J4jTHmo8Xh6qM32ASOtVbXNoiaegrI26qLRw6r6FTI7dZw6TMT7vecvnjd1O8xSsM5MiajIuQZicxSC6KFK8TMpbg/0?wx_fmt=png" data-nickname="java突击队" data-alias="" data-signature="技术经验分享" data-from="0"></mpprofile> </section>

消息幂等(去重)通用解决方案,RocketMQ

作者:微信小助手

<p style="margin-top: 10px;margin-bottom: 20px;outline: 0px;max-width: 100%;color: rgb(34, 34, 34);font-family: -apple-system, BlinkMacSystemFont, &quot;Helvetica Neue&quot;, &quot;PingFang SC&quot;, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei UI&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif;letter-spacing: 0.544px;text-indent: 0em;white-space: normal;background-color: rgb(255, 255, 255);visibility: visible;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="outline: 0px;max-width: 100%;font-size: 15px;visibility: visible;box-sizing: border-box !important;overflow-wrap: break-word !important;">消息中间件是分布式系统常用的组件,无论是异步化、解耦、削峰等都有广泛的应用价值。我们通常会认为,消息中间件是一个可靠的组件——这里所谓的可靠是指,只要我把消息成功投递到了消息中间件,消息就不会丢失,即消息肯定会至少保证消息能被消费者成功消费一次,这是消息中间件最基本的特性之一,也就是我们常说的“AT LEAST ONCE”,即消息至少会被“成功消费一遍”。</span></p> <p style="margin-top: 10px;margin-bottom: 20px;outline: 0px;max-width: 100%;color: rgb(34, 34, 34);font-family: -apple-system, BlinkMacSystemFont, &quot;Helvetica Neue&quot;, &quot;PingFang SC&quot;, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei UI&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif;letter-spacing: 0.544px;text-indent: 0em;white-space: normal;background-color: rgb(255, 255, 255);visibility: visible;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="outline: 0px;max-width: 100%;font-size: 15px;visibility: visible;box-sizing: border-box !important;overflow-wrap: break-word !important;">举个例子,一个消息M发送到了消息中间件,消息投递到了消费程序A,A接受到了消息,然后进行消费,但在消费到一半的时候程序重启了,这时候这个消息并没有标记为消费成功,这个消息还会继续投递给这个消费者,直到其消费成功了,消息中间件才会停止投递。</span></p> <p style="margin-top: 10px;margin-bottom: 20px;outline: 0px;max-width: 100%;color: rgb(34, 34, 34);font-family: -apple-system, BlinkMacSystemFont, &quot;Helvetica Neue&quot;, &quot;PingFang SC&quot;, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei UI&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif;letter-spacing: 0.544px;text-indent: 0em;white-space: normal;background-color: rgb(255, 255, 255);visibility: visible;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="outline: 0px;max-width: 100%;font-size: 15px;visibility: visible;box-sizing: border-box !important;overflow-wrap: break-word !important;">然而这种可靠的特性导致,消息可能被多次地投递。举个例子,还是刚刚这个例子,程序A接受到这个消息M并完成消费逻辑之后,正想通知消息中间件“我已经消费成功了”的时候,程序就重启了,那么对于消息中间件来说,这个消息并没有成功消费过,所以他还会继续投递。这时候对于应用程序A来说,看起来就是这个消息明明消费成功了,但是消息中间件还在重复投递。</span></p> <section style="outline: 0px;max-width: 100%;color: rgb(34, 34, 34);font-family: -apple-system, BlinkMacSystemFont, &quot;Helvetica Neue&quot;, &quot;PingFang SC&quot;, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei UI&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif;white-space: normal;background-color: rgb(255, 255, 255);vertical-align: inherit;letter-spacing: 1px;line-height: 1.75em;visibility: visible;text-align: center;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <a target="_blank" href="https://mp.weixin.qq.com/s?__biz=MzI4NTM1NDgwNw==&amp;mid=2247509355&amp;idx=2&amp;sn=262da1da2bac3d85bd772498d4382977&amp;scene=21#wechat_redirect" textvalue="120讲SpringBoot&nbsp;源码视频,大小18G" linktype="text" imgurl="" imgdata="null" tab="innerlink" data-linktype="2" wah-hotarea="click" style="outline: 0px;-webkit-tap-highlight-color: rgba(0, 0, 0, 0);cursor: pointer;max-width: 100%;visibility: visible;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="outline: 0px;max-width: 100%;color: rgb(0, 0, 0);font-family: 微软雅黑, &quot;Microsoft YaHei&quot;;font-size: 15px;visibility: visible;text-decoration: underline;box-sizing: border-box !important;overflow-wrap: break-word !important;"><strong style="outline: 0px;max-width: 100%;color: rgb(255, 41, 65);font-family: PingFangSC-Regular, &quot;Helvetica Neue&quot;, Helvetica, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei&quot;, 微软雅黑, Arial, sans-serif;font-size: 18px;white-space: pre-wrap;visibility: visible;box-sizing: border-box !important;overflow-wrap: break-word !important;">120讲SpringBoot&nbsp;源码视频,大小18G</strong></span></a> </section> <section style="outline: 0px;max-width: 100%;color: rgb(34, 34, 34);font-family: -apple-system, BlinkMacSystemFont, &quot;Helvetica Neue&quot;, &quot;PingFang SC&quot;, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei UI&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif;white-space: normal;background-color: rgb(255, 255, 255);vertical-align: inherit;letter-spacing: 1px;line-height: 1.75em;visibility: visible;text-align: center;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <br style="outline: 0px;max-width: 100%;visibility: visible;box-sizing: border-box !important;overflow-wrap: break-word !important;"> </section> <section style="margin-bottom: 0em;outline: 0px;max-width: 100%;color: rgb(34, 34, 34);font-family: -apple-system, BlinkMacSystemFont, &quot;Helvetica Neue&quot;, &quot;PingFang SC&quot;, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei UI&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif;letter-spacing: 0.544px;white-space: normal;background-color: rgb(255, 255, 255);text-align: center;visibility: visible;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <a target="_blank" href="https://mp.weixin.qq.com/s?__biz=MzI4NTM1NDgwNw==&amp;mid=2247509355&amp;idx=2&amp;sn=262da1da2bac3d85bd772498d4382977&amp;scene=21#wechat_redirect" textvalue="120讲SpringBoot&nbsp;源码视频,大小18G" linktype="text" imgurl="" imgdata="null" tab="innerlink" data-linktype="1" wah-hotarea="click" style="outline: 0px;-webkit-tap-highlight-color: rgba(0, 0, 0, 0);cursor: pointer;max-width: 100%;visibility: visible;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span class="js_jump_icon h5_image_link" data-positionback="static" style="outline: 0px;max-width: 100%;line-height: 0;vertical-align: bottom;user-select: none;width: 677px;visibility: visible;box-sizing: border-box !important;overflow-wrap: break-word !important;"><img class="rich_pages wxw-img" src="/upload/d0739850542d7a9a88088f8cb214ba09.png" data-cropx1="0" data-cropx2="1280" data-cropy1="130.6574394463668" data-cropy2="668.7889273356402" data-ratio="0.4212962962962963" data-s="300,640" src="https://mmbiz.qpic.cn/mmbiz_jpg/GpcH5Yqqj0l58YQ55v7RSQXeX1ibialib5EPd6RzkLa9PqdTPczO98GQ5zjKCNRD7xQ6zeML3SickJyJsHfL150KFA/640?wx_fmt=jpeg" data-type="jpeg" data-w="1080" style="outline: 0px;border-width: 0px;border-style: initial;border-color: initial;box-sizing: border-box !important;overflow-wrap: break-word !important;visibility: visible !important;width: 578px !important;"></span></a> </section> <p style="margin-top: 10px;margin-bottom: 20px;outline: 0px;max-width: 100%;color: rgb(34, 34, 34);font-family: -apple-system, BlinkMacSystemFont, &quot;Helvetica Neue&quot;, &quot;PingFang SC&quot;, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei UI&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif;letter-spacing: 0.544px;text-indent: 0em;white-space: normal;background-color: rgb(255, 255, 255);visibility: visible;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="outline: 0px;max-width: 100%;font-size: 15px;visibility: visible;box-sizing: border-box !important;overflow-wrap: break-word !important;">这在RockectMQ的场景来看,就是同一个messageId的消息重复投递下来了。</span></p> <p style="margin-top: 10px;margin-bottom: 20px;outline: 0px;max-width: 100%;color: rgb(34, 34, 34);font-family: -apple-system, BlinkMacSystemFont, &quot;Helvetica Neue&quot;, &quot;PingFang SC&quot;, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei UI&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif;letter-spacing: 0.544px;text-indent: 0em;white-space: normal;background-color: rgb(255, 255, 255);visibility: visible;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="outline: 0px;max-width: 100%;font-size: 15px;visibility: visible;box-sizing: border-box !important;overflow-wrap: break-word !important;">基于消息的投递可靠(消息不丢)是优先级更高的,所以消息不重的任务就会转移到应用程序自我实现,这也是为什么RocketMQ的文档里强调的,消费逻辑需要自我实现幂等。背后的逻辑其实就是:不丢和不重是矛盾的(在分布式场景下),但消息重复是有解决方案的,而消息丢失是很麻烦的。</span></p> <h2 data-remoteid="p_1648954309479" style="margin-top: 10px;margin-bottom: 20px;outline: 0px;max-width: 100%;color: rgb(34, 34, 34);font-family: -apple-system, BlinkMacSystemFont, &quot;Helvetica Neue&quot;, &quot;PingFang SC&quot;, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei UI&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif;letter-spacing: 0.544px;text-indent: 0em;white-space: normal;background-color: rgb(255, 255, 255);visibility: visible;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="outline: 0px;max-width: 100%;font-size: 18px;visibility: visible;color: rgb(255, 169, 0);box-sizing: border-box !important;overflow-wrap: break-word !important;"><strong style="outline: 0px;max-width: 100%;visibility: visible;box-sizing: border-box !important;overflow-wrap: break-word !important;">简单的消息去重解决方案</strong></span></h2> <section style="margin-top: 10px;margin-bottom: 20px;outline: 0px;max-width: 100%;color: rgb(34, 34, 34);font-family: -apple-system, BlinkMacSystemFont, &quot;Helvetica Neue&quot;, &quot;PingFang SC&quot;, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei UI&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif;letter-spacing: 0.544px;text-indent: 0em;white-space: normal;background-color: rgb(255, 255, 255);visibility: visible;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <span style="outline: 0px;max-width: 100%;font-size: 15px;visibility: visible;box-sizing: border-box !important;overflow-wrap: break-word !important;">例如:假设我们业务的消息消费逻辑是:插入某张订单表的数据,然后更新库存:</span> </section> <figure style="margin-bottom: 1.5em;outline: 0px;max-width: 100%;color: rgb(34, 34, 34);letter-spacing: 0.544px;white-space: normal;border-width: 0px;border-style: initial;border-color: initial;font-variant-numeric: inherit;font-stretch: inherit;line-height: inherit;font-family: &quot;PT Serif&quot;, Georgia, Times, &quot;Times New Roman&quot;, serif;font-size: 18.4px;vertical-align: baseline;box-shadow: rgba(0, 0, 0, 0.06) 0px 0px 10px;background: none rgb(248, 248, 248);text-align: start;visibility: visible;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <table width="677"> <tbody style="outline: 0px;max-width: 100%;border-width: 0px;border-style: initial;border-color: initial;font-style: inherit;font-variant: inherit;font-weight: inherit;font-stretch: inherit;line-height: inherit;font-family: inherit;vertical-align: baseline;visibility: visible;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <tr style="outline: 0px;max-width: 100%;border-width: 0px;border-style: initial;border-color: initial;font-style: inherit;font-variant: inherit;font-weight: inherit;font-stretch: inherit;line-height: inherit;font-family: inherit;vertical-align: baseline;visibility: visible;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <td style="padding: 0px;border-width: 0px;border-style: initial;border-color: initial;outline: 0px;word-break: break-all;max-width: 100%;font-style: inherit;font-variant: inherit;font-stretch: inherit;line-height: inherit;font-family: inherit;vertical-align: middle;visibility: visible;overflow-wrap: break-word !important;box-sizing: border-box !important;"><pre style="outline: 0px;max-width: 100%;border-top: none;border-bottom: none;border-left: none;font-style: inherit;font-variant: inherit;font-weight: inherit;font-stretch: inherit;line-height: 1.45em;font-family: Menlo, Monaco, &quot;Andale Mono&quot;, &quot;lucida console&quot;, &quot;Courier New&quot;, monospace;font-size: 13px;vertical-align: baseline;box-shadow: none;border-radius: 0px;color: rgb(147, 161, 161);overflow: auto;text-align: right;text-shadow: rgb(2, 16, 20) 0px -1px;visibility: visible;padding: 0.8em !important;box-sizing: border-box !important;overflow-wrap: break-word !important;border-right: 1px solid rgb(0, 35, 44) !important;"><span style="outline: 0px;max-width: 100%;border-width: 0px;border-style: initial;border-color: initial;font-style: inherit;font-variant: inherit;font-weight: inherit;font-stretch: inherit;line-height: inherit;font-family: inherit;vertical-align: baseline;visibility: visible;box-sizing: border-box !important;overflow-wrap: break-word !important;color: rgb(88, 110, 117) !important;">1</span><br style="outline: 0px;max-width: 100%;visibility: visible;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="outline: 0px;max-width: 100%;border-width: 0px;border-style: initial;border-color: initial;font-style: inherit;font-variant: inherit;font-weight: inherit;font-stretch: inherit;line-height: inherit;font-family: inherit;vertical-align: baseline;visibility: visible;box-sizing: border-box !important;overflow-wrap: break-word !important;color: rgb(88, 110, 117) !important;">2</span><br style="outline: 0px;max-width: 100%;visibility: visible;box-sizing: border-box !important;overflow-wrap: break-word !important;"></pre></td> <td width="757" style="padding: 0px;border-width: 0px;border-style: initial;border-color: initial;outline: 0px;word-break: break-all;max-width: 100%;font-style: inherit;font-variant: inherit;font-stretch: inherit;line-height: inherit;font-family: inherit;vertical-align: middle;visibility: visible;overflow-wrap: break-word !important;box-sizing: border-box !important;"><pre style="outline: 0px;max-width: 100%;border-width: initial;border-style: none;border-color: initial;font-style: inherit;font-variant: inherit;font-weight: inherit;font-stretch: inherit;line-height: 1.45em;font-family: Menlo, Monaco, &quot;Andale Mono&quot;, &quot;lucida console&quot;, &quot;Courier New&quot;, monospace;font-size: 13px;vertical-align: baseline;box-shadow: none;background: none;border-radius: 0px;color: rgb(147, 161, 161);overflow: auto;visibility: visible;box-sizing: border-box !important;overflow-wrap: break-word !important;"><code style="padding: 0.8em;outline: 0px;max-width: 100%;border-width: 0px;border-style: initial;border-color: initial;font-style: inherit;font-variant: inherit;font-weight: inherit;font-stretch: inherit;line-height: 1.45em;vertical-align: baseline;display: block;background: rgb(0, 0, 0);visibility: visible;box-sizing: border-box !important;overflow-wrap: break-word !important;font-family: Menlo, Monaco, &quot;Andale Mono&quot;, &quot;lucida console&quot;, &quot;Courier New&quot;, monospace !important;"><span style="outline: 0px;max-width: 100%;border-width: 0px;border-style: initial;border-color: initial;font-variant: inherit;font-stretch: inherit;line-height: inherit;font-family: inherit;vertical-align: baseline;visibility: visible;box-sizing: border-box !important;overflow-wrap: break-word !important;">insert into t_order values .....<br style="outline: 0px;max-width: 100%;visibility: visible;box-sizing: border-box !important;overflow-wrap: break-word !important;">update t_inv set count = count-1 where good_id = 'good123';</span></code></pre></td> </tr> </tbody> </table> </figure> <p style="margin-top: 10px;margin-bottom: 20px;outline: 0px;max-width: 100%;color: rgb(34, 34, 34);font-family: -apple-system, BlinkMacSystemFont, &quot;Helvetica Neue&quot;, &quot;PingFang SC&quot;, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei UI&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif;letter-spacing: 0.544px;text-indent: 0em;white-space: normal;background-color: rgb(255, 255, 255);visibility: visible;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="outline: 0px;max-width: 100%;font-size: 15px;visibility: visible;box-sizing: border-box !important;overflow-wrap: break-word !important;">要实现消息的幂等,我们可能会采取这样的方案:</span></p> <figure style="margin-bottom: 1.5em;outline: 0px;max-width: 100%;color: rgb(34, 34, 34);letter-spacing: 0.544px;white-space: normal;border-width: 0px;border-style: initial;border-color: initial;font-variant-numeric: inherit;font-stretch: inherit;line-height: inherit;font-family: &quot;PT Serif&quot;, Georgia, Times, &quot;Times New Roman&quot;, serif;font-size: 18.4px;vertical-align: baseline;box-shadow: rgba(0, 0, 0, 0.06) 0px 0px 10px;background: none rgb(248, 248, 248);text-align: start;visibility: visible;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <table width="677"> <tbody style="outline: 0px;max-width: 100%;border-width: 0px;border-style: initial;border-color: initial;font-style: inherit;font-variant: inherit;font-weight: inherit;font-stretch: inherit;line-height: inherit;font-family: inherit;vertical-align: baseline;visibility: visible;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <tr style="outline: 0px;max-width: 100%;border-width: 0px;border-style: initial;border-color: initial;font-style: inherit;font-variant: inherit;font-weight: inherit;font-stretch: inherit;line-height: inherit;font-family: inherit;vertical-align: baseline;visibility: visible;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <td style="padding: 0px;border-width: 0px;border-style: initial;border-color: initial;outline: 0px;word-break: break-all;max-width: 100%;font-style: inherit;font-variant: inherit;font-stretch: inherit;line-height: inherit;font-family: inherit;vertical-align: middle;visibility: visible;overflow-wrap: break-word !important;box-sizing: border-box !important;"><pre style="outline: 0px;max-width: 100%;border-top: none;border-bottom: none;border-left: none;font-style: inherit;font-variant: inherit;font-weight: inherit;font-stretch: inherit;line-height: 1.45em;font-family: Menlo, Monaco, &quot;Andale Mono&quot;, &quot;lucida console&quot;, &quot;Courier New&quot;, monospace;font-size: 13px;vertical-align: baseline;box-shadow: none;border-radius: 0px;color: rgb(147, 161, 161);overflow: auto;text-align: right;text-shadow: rgb(2, 16, 20) 0px -1px;visibility: visible;padding: 0.8em !important;box-sizing: border-box !important;overflow-wrap: break-word !important;border-right: 1px solid rgb(0, 35, 44) !important;"><span style="outline: 0px;max-width: 100%;border-width: 0px;border-style: initial;border-color: initial;font-style: inherit;font-variant: inherit;font-weight: inherit;font-stretch: inherit;line-height: inherit;font-family: inherit;vertical-align: baseline;visibility: visible;box-sizing: border-box !important;overflow-wrap: break-word !important;color: rgb(88, 110, 117) !important;">1</span><br style="outline: 0px;max-width: 100%;visibility: visible;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="outline: 0px;max-width: 100%;border-width: 0px;border-style: initial;border-color: initial;font-style: inherit;font-variant: inherit;font-weight: inherit;font-stretch: inherit;line-height: inherit;font-family: inherit;vertical-align: baseline;visibility: visible;box-sizing: border-box !important;overflow-wrap: break-word !important;color: rgb(88, 110, 117) !important;">2</span><br style="outline: 0px;max-width: 100%;visibility: visible;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="outline: 0px;max-width: 100%;border-width: 0px;border-style: initial;border-color: initial;font-style: inherit;font-variant: inherit;font-weight: inherit;font-stretch: inherit;line-height: inherit;font-family: inherit;vertical-align: baseline;visibility: visible;box-sizing: border-box !important;overflow-wrap: break-word !important;color: rgb(88, 110, 117) !important;">3</span><br style="outline: 0px;max-width: 100%;visibility: visible;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="outline: 0px;max-width: 100%;border-width: 0px;border-style: initial;border-color: initial;font-style: inherit;font-variant: inherit;font-weight: inherit;font-stretch: inherit;line-height: inherit;font-family: inherit;vertical-align: baseline;visibility: visible;box-sizing: border-box !important;overflow-wrap: break-word !important;color: rgb(88, 110, 117) !important;">4</span><br style="outline: 0px;max-width: 100%;visibility: visible;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="outline: 0px;max-width: 100%;border-width: 0px;border-style: initial;border-color: initial;font-style: inherit;font-variant: inherit;font-weight: inherit;font-stretch: inherit;line-height: inherit;font-family: inherit;vertical-align: baseline;visibility: visible;box-sizing: border-box !important;overflow-wrap: break-word !important;color: rgb(88, 110, 117) !important;">5</span><br style="outline: 0px;max-width: 100%;visibility: visible;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="outline: 0px;max-width: 100%;border-width: 0px;border-style: initial;border-color: initial;font-style: inherit;font-variant: inherit;font-weight: inherit;font-stretch: inherit;line-height: inherit;font-family: inherit;vertical-align: baseline;visibility: visible;box-sizing: border-box !important;overflow-wrap: break-word !important;color: rgb(88, 110, 117) !important;">6</span><br style="outline: 0px;max-width: 100%;visibility: visible;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="outline: 0px;max-width: 100%;border-width: 0px;border-style: initial;border-color: initial;font-style: inherit;font-variant: inherit;font-weight: inherit;font-stretch: inherit;line-height: inherit;font-family: inherit;vertical-align: baseline;visibility: visible;box-sizing: border-box !important;overflow-wrap: break-word !important;color: rgb(88, 110, 117) !important;">7</span><br style="outline: 0px;max-width: 100%;visibility: visible;box-sizing: border-box !important;overflow-wrap: break-word !important;"></pre></td> <td width="757" style="padding: 0px;border-width: 0px;border-style: initial;border-color: initial;outline: 0px;word-break: break-all;max-width: 100%;font-style: inherit;font-variant: inherit;font-stretch: inherit;line-height: inherit;font-family: inherit;vertical-align: middle;visibility: visible;overflow-wrap: break-word !important;box-sizing: border-box !important;"><pre style="outline: 0px;max-width: 100%;border-width: initial;border-style: none;border-color: initial;font-style: inherit;font-variant: inherit;font-weight: inherit;font-stretch: inherit;line-height: 1.45em;font-family: Menlo, Monaco, &quot;Andale Mono&quot;, &quot;lucida console&quot;, &quot;Courier New&quot;, monospace;font-size: 13px;vertical-align: baseline;box-shadow: none;background: none;border-radius: 0px;color: rgb(147, 161, 161);overflow: auto;visibility: visible;box-sizing: border-box !important;overflow-wrap: break-word !important;"><code style="padding: 0.8em;outline: 0px;max-width: 100%;border-width: 0px;border-style: initial;border-color: initial;font-style: inherit;font-variant: inherit;font-weight: inherit;font-stretch: inherit;line-height: 1.45em;vertical-align: baseline;display: block;background: rgb(0, 0, 0);visibility: visible;box-sizing: border-box !important;overflow-wrap: break-word !important;font-family: Menlo, Monaco, &quot;Andale Mono&quot;, &quot;lucida console&quot;, &quot;Courier New&quot;, monospace !important;"><span style="outline: 0px;max-width: 100%;border-width: 0px;border-style: initial;border-color: initial;font-variant: inherit;font-stretch: inherit;line-height: inherit;font-family: inherit;vertical-align: baseline;visibility: visible;box-sizing: border-box !important;overflow-wrap: break-word !important;">select * from t_order where order_no = 'order123'<br style="outline: 0px;max-width: 100%;visibility: visible;box-sizing: border-box !important;overflow-wrap: break-word !important;">if(order != null) {<br style="outline: 0px;max-width: 100%;visibility: visible;box-sizing: border-box !important;overflow-wrap: break-word !important;"> return ;//消息重复,直接返回<br style="outline: 0px;max-width: 100%;visibility: visible;box-sizing: border-box !important;overflow-wrap: break-word !important;">}</span></code></pre></td> </tr> </tbody> </table> </figure> <p style="margin-top: 10px;margin-bottom: 20px;outline: 0px;max-width: 100%;color: rgb(34, 34, 34);font-family: -apple-system, BlinkMacSystemFont, &quot;Helvetica Neue&quot;, &quot;PingFang SC&quot;, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei UI&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif;letter-spacing: 0.544px;text-indent: 0em;white-space: normal;background-color: rgb(255, 255, 255);visibility: visible;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="outline: 0px;max-width: 100%;font-size: 15px;visibility: visible;box-sizing: border-box !important;overflow-wrap: break-word !important;">这对于很多情况下,的确能起到不错的效果,但是在并发场景下,还是会有问题。</span></p> <h2 data-remoteid="p_1648954309480" style="margin-top: 10px;margin-bottom: 20px;outline: 0px;max-width: 100%;color: rgb(34, 34, 34);font-family: -apple-system, BlinkMacSystemFont, &quot;Helvetica Neue&quot;, &quot;PingFang SC&quot;, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei UI&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif;letter-spacing: 0.544px;text-indent: 0em;white-space: normal;background-color: rgb(255, 255, 255);visibility: visible;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="outline: 0px;max-width: 100%;font-size: 18px;visibility: visible;color: rgb(255, 169, 0);box-sizing: border-box !important;overflow-wrap: break-word !important;"><strong style="outline: 0px;max-width: 100%;visibility: visible;box-sizing: border-box !important;overflow-wrap: break-wo