想了很久,什么才是微服务?找了一大圈,各种定义都有,书和搜索好像没能给出一个标准答案,那就从源头抓起:
原文(点击原文查看)中的第二段定义就是这一小段话:
In short, the microservice architectural style [1] is an approach to developing a single application as a suite of small services, each running in its own process and communicating with lightweight mechanisms, often an HTTP resource API. These services are built around business capabilities and independently deployable by fully automated deployment machinery. There is a bare minimum of centralized management of these services, which may be written in different programming languages and use different data storage technologies.
中文翻译过来就是如下:
“微服务是一种架构模式,它提倡将单一应用程序划分成一组小的服务,每个服务运行在其独立的进程中,服务与服务间采用轻量级的通信机制互相协作(通常是基于HTTP协议的RESTful API)。每个服务都围绕着具体业务进行构建,并且能够被充分自动化的独立部署。对这些服务有一个最低限度的集中式的管理,可以使用不同的开发语言进行开发和使用不同的数据存储技术。”
有点抽象,这好像不是我要的答案,不好写啊,因为它首先是一种架构模式,架构必定首先得设计,谈论到设计,哪还有什么标准,谈论最多的是风格,理念,思想。所以微服务没有标准,硬要说标准,以上这段抽象的原话就是标准,遵从了以上的原则,它就是微服务。
但是设计的产品总归是要给人使用的,好用是第一需求,包含了快速稳定可扩展等等;好看也很重要,颜值高的东西谁都喜欢,谁都更喜欢用(就用秒杀服务,搜索服务,推荐服务比喻好看吧),而用的人多了,好用和好看不停的循环促进。微服务架构也是如此,所以说微服务是围绕着具体业务进行构建的,你的具体业务就是产品。
微服务从来就不是突然冒出来的,从软件设计的MVC,到分布式,到SOA,到微服务到service mesh(微服务2.0),它就是不断的为了满足业务需求被设计和演化出来的。
从定义上看微服务就是实现分布式架构的一种方式,分布式系统是要满足高并发需求的,而满足高并发也可以前后端分离+nginx或者lvs,但这种粗粒度的扩张其实早已满足不了现代需求,高并发只是满足微服务的最低需求,篇幅有限,详细的不讨论,个人认为现代微服务更多的需求是快和效率。
所以微服务架构本质上它就是构建你的具体业务架构,好了,废话还是少点,想一篇文章彻底搞定微服务是不现实的,这篇既不是纯理论(理论书籍可以查看《领域驱动设计》,《微服务设计》两本书),也非纯代码(github上有大量的java和go语言开源代码)文章,也许只是作为一个从业技术人员分享一点浅薄知识的水文,如果能留下一点思想那就更好了,直入主题,定义中第一步该做的事情就是划分。
好了,该如何划分或者拆分,新的业务就是划分,现有业务就是拆分。
那么问题来了,该如何拆?
书中专家告诉我们要划分“领域”,大致意思是:把领域划分成一个个子领域,按“限界上下文”寻找边界,按边界拆。(唉!专家有时候就是这么不接地气)
《领域驱动设计》中是这样的:
或者这样(如果只是简单这样,那肯定是错的,为什么错,看看互相调用REST红箭头,下文会讲到):
istio(service mesh)官方文档上是这样的(官方文档可能一般大多都是专家写的吧)。service mesh也被定义为微服务2.0
是不是有点懵,个人觉得其实他们都对,只是把细节给隐去了而已,所以PPT上的东西看看就好。
其实对于经历过mvc,soa演变过程的人来说,好像都不是事!
mvc(module,view,control)本质上就是把代码给一层层松耦合的方式给分开(逻辑上的水平拆分),soa(面向服务编程),就是把一个个服务(例如:用户,订单,库存,支付)这些服务松耦合的方式给拆开(逻辑上的垂直拆分),那微服务怎么拆,2种思想结合,把系统水平拆分和垂直拆分成一个个小的服务,直接画张图吧
画图水平就这样了。。。将就着吧,典型的电商互联网简约架构。其实大公司在前端和API网关层之间一般还有负载均衡层(DNS,F5,LVS)和Nginx反向代理层。
水平拆分和垂直拆分,貌似垂直拆分看着更简单,其实垂直拆分更难,甚至难度不是一个数量级的,下文会慢慢延伸到,垂直拆分将会带来的问题以及怎么去解决,特别是数据处理层面。图上API网关层及以上不涉及到拆分。API网关层随后会讲到,DB层用的数据库也不必纠结,随便写了几种DB,先看拆分问题
前后端分离的优点好处不用多说,但也不是绝对,比如网站排名seo,前端也是块超大的内容,水平有限,少BB_。
回归主题,API网关层是整个后端的出入口,请求从上往下调用,再从下往上返回,通常在上层的叫上游,下层的叫下游。
同层之间不要相互调用,比如业务逻辑层中用户服务和商品服务互相调用,如果业务非得需要调用,那就调用对方服务的下游,比如用户服务调用商品数据访问服务。至于为什么,从网络角度上讲就叫不能有环路,广播风暴去了解下,严重了直接引起系统雪崩。但是这样又会引起代码的重复,比如用户服务中需要实现商品服务的部分功能,如果是调用多了,其实这就是服务边界自然而然的产生了,那就在业务逻辑层中再下沉一层基础服务。
好像终于要开始边界问题了,那就直接拿支付清算业务聊聊吧。
支付清算系统该如何划分业务,例如:给你如下选择:
很多人都有着理想中的完美领域划分,划分完美了一劳永逸。其实我觉得以上拆分都对,只是对应业务发展的不同阶段而已,一个成熟的业务其实你自然而然的知道边界在哪里,一个新业务或者不熟悉的业务讨论再多的边界都是多余(但认识边界还是需要的),所以说,架构要围绕业务构建,没有设计就是最好的设计你听听就好,过度的设计肯定也走不远,你将会一直在不停的重构。就好比计划经济和市场经济一样,这个过程中,设计和演化是相辅相成的,强行的钻牛角尖,现实终将狠狠的打你的脸。最终你也许会发现微服务架构就是一个追求平衡的调节器。
上图中基础业务逻辑层就是下沉的一层基础服务,去解决同层之间相互调用的问题,也是处理边界的一种方法,其中。。。省略了很多业务,比如:国库业务,非税业务,公积金业务等等,详细的业务不再讨论,金融类业务相对比较难理解,那就将就着吧,我相信还是有很多人能看懂的,再举个边界的例子,比如上图中如果对公转账个人,和个人转账对公业务如何处理边界,那就在业务逻辑层上游再上浮一层聚合层吧,比如就叫人民币业务,就不再画图了(其实很多API网关层含有聚合服务的功能,我个人觉得并不优雅,某些业务场景甚至带来严重问题,写到网关层再说吧)。有句话叫:没有是再增加一层解决不了的问题,如果有,那就再增加一层。想想好像也挺有道理的。网上有很多关于互联网大公司的中台的概念,比如用户中台,数据中台等等,但仔细想想中台的定义,你会明白它到底在哪一层。
好了,划分问题差不多了,有关更专业的理论,感兴趣的还是去看书吧,《领域驱动设计》和《微服务设计》还是很不错的,其实以上篇幅关于划分问题也远没这么简单,但这样的划分和处理边界在工程实践中能解决绝大部分问题。而更难的是你的组织结构是否适合这样的架构设计,组织合适的人去完美协调的处理一件大事情本身就是困难的事,这需要管理层,领导层的管理能力和大智慧,不是么?很多时候架构设计强行为了迎合组织架构而调整,个人认为并不可取,可行性的业务战略和业务演化才是正确的方向。 想想淘宝以前的支付业务和现在的支付宝,以前的淘宝二手业务和现在的闲鱼,京东的物流,腾讯的游戏,比比皆是,不瞎扯了,有点跑题了。
好像漏了微服务中的“微”,有人把微服务定义为很小,单个服务高内聚,2周内可以重构一个微服务;也有人认为微服务它就是个服务,一个服务就是微服务;这种概念强行定义的标准看看就好,《微服务设计》和《cloud native go》这2本书中有关于“微”的不同角度的详细的描述,而根本问题在于根据你组织结构和业务架构划分合适大小就好,和不同的阶段选择适合自己的才是最好的。
总之,如何划分或者拆分,大方向上的思想就是水平拆分和垂直拆分,在处理大规模增长上,这种思想比比皆是,不管是微观还是宏观上;比如数据结构中的hashmap,数组就是水平增长,链表垂直增加, 又比如数据库的分库分表(数据访问层再说),网络上的OSI7层模型,再一个个连接起来形成网络,云计算的弹性调度(根据监控设置的阈值指标自动伸缩)也是如此,大到公司组织架构(想想那些大公司的组织架构的频繁变动),甚至一个国家的政府部门架构(想想国地税的合并,银保监的合并不也是在处理边界问题么)。
为何好像很多废话讲拆分,因为拆分真的很重要,微服务定义中第一步讲的就是拆分,其实微服务拆分后带来的一系列问题,这些问题有:
如何解决这些问题?那就一个个来。
网上关于api网关层写的我觉得挺复杂的,对于不是互联网应用的人不是很好理解,看到网关2字,搞网络的可能相对更好理解,你就参照你家里的路由器去理解,API网关层就是干的路由器类似的活。
所以API网关很忙,压力很大,结果你还要让它管理业务逻辑的聚合,所以你觉得合不合适,所以正确的姿势是把服务聚合给下沉到业务逻辑层。
个人觉得对于某些传统业务模型,网关当然也并非需要,或者把服务的聚合做在API网关也没关系,当然并非所有传统业务都是如此,特别是面对C端的,比如12306,所以微服务讨论你是传统公司和互联网公司没有意义,讨论它的业务场景更有价值。
常见的开源API网关框架:
大名顶顶的spring cloud zuul ,但zuul2好像闭源了,此外还有nginx+lua实现的kong,国人开源的orange,悟空api,golang写的tyk,而比较大的互联网公司API网关层都是根据自己的业务场景自己实现的,核心功能就是实现动态路由功能,代码实现就是用动态代理和反射去实现路由。从框架层面理解就是web框架的靠前的一部分功能和动态代理。
说到RESTFUL ,那就连跨网络远程通讯一起,回到开头在看下微服务中定义的服务间的通讯机制,通常是用RESTFUL,那常用的通讯机制有哪几种,目前有3种,如下:
而轻量级的基本可以排除MQ,MQ的主要作用不在于服务间的调用,但它的作用可大了,也很有意思,稍后会有很多地方讲到MQ。RESTFUL它就是一种提供了原则和约束的风格,基于http协议用URL去定位资源。
其实很多框架和产品都说基于RESTFUL,但100%完全遵守RESTFUL风格的很少,就连K8S都不完全是(当然,人家提供的api何其多,100%也不可能),其实简单理解RESTFUL不难,看它URL中是不是只有名词,用HTTP动词(GET,POST,PUT,DELETE)描述操作。而K8S中的声明式是什么呢?也是基于RESTFUL风格,其实我个人理解一句话概括就可以了:让你有种当老板(领导)的感觉。,一般大boss都不会和你瞎bb,比如人家只会和你说我下午有个会,他不会告诉你怎么安排会议室,要做些什么准备,又比如大老板今年定个小目标,先挣个一个亿,他不会去问今年已经挣了多少,再去挣多少,甚至怎么挣。RESTFUL是好东西,比如简单,轻量,基于HTTP等等,既然是http,给多语言开发提供了基础。http也是未来的方向,但是它目前缺点也很明显,比如:并发相对RPC来说要低,延迟高,有人拿http和rpc做过测试,通常并发量和延迟要相差一倍,而http用于工程实践来说,还需要用web框架,启动慢。这对于某些业务场景下是不可接受的,比如:互联网大公司,人家的服务规模很大,调用链很长,并发低和延迟高是不可接受的,又比如金融业务,并发量稍低也许可以接受,但延迟高是不行的。
RPC种类很多,比如jsonrpc,通过json数据格式传输,虽然json是各种语言都支持的标准格式,但目前基本也被抛弃,原因就在于json序列化性能低,而高性能的rpc的必要条件就在于数据序列化方式都是二进制的,通常都是采用protobuff二进制协议。性能好的代价,带来的问题就是耦合度相对比较高,重客户端,实现不同语言的通讯很难,而要实现不同语言的客户端和维护,这代价太大。所以就有了grpc,基于http2,protobuff二进制协议,netty通讯框架,把定义好的proto文件生成各个语言的客户端提供不同语言间的通讯,它最新的v3版本性能几乎可以追平tcp方式的rpc。有兴趣的可以网上找找grpc和dubbo rpc性能对比等等,而具体的使用方式查看github上grpc的example文件夹下的例子。
所以采用微服务的一般都是前后端通讯用RESTFUL,过了API网关层,绝大部分都是RPC调用。
接下来一个问题,什么是同步架构,什么是异步架构?
至于同步和异步这里不解释了,也不去区分BIO和AIO,越到底层越复杂,异步理解起来不难,实现可不简单,可以看看大名鼎鼎netty nio通讯框架,目前几乎大部分的上层应用框架通讯层面都是使用它的。
先来看一个问题,一个请求分2种,一个请求读,一个请求写,哪个需要异步处理的,请求读用异步是没有意义的,不理解自己想想,至于请求写,在哪些业务场景下用到异步架构?给你列举几个,比如买个东西,点个外卖等写个评价,发个朋友圈,微博,短视频等等,那怎么才能架设异步架构?MQ来了。
先来看下MQ主要的好处:
其中,消息的存储能力,消息丢失概率,处理能力,堆积能力,能否顺序消费,支不支持事务,批量消息是否支持,推拉模式,可用性和扩展性,一致性处理等等问题都是MQ需要处理的问题。
而MQ的选择,其它的能力这里先不谈,等到数据一致性处理的时候再说吧,这里先说异步问题,MQ异步问题本质上就是一次RPC调用变成两次RPC调用,中间数据转存了下。
回看下上面画的图,那么微服务异步架构该把MQ放在哪一层?
其实放在任何一层都可以把同步架构变成异步架构,但通常的做法都是放在API网关层和业务逻辑层之间,为何放在这一层,下面的秒杀架构再继续,不然需要直接引入很多知识太复杂。再顺便抛出个MQ应用场景,想想你的灾备机房,以及异地多活_,那就再抛一个较简单问题,为什么MQ的消息处理能力这么强,数据库的处理能力为什么相对这么弱鸡,都是比较有意思的问题,想想他们的应用场景,好好去了解下随机读写和顺序读写,本质问题其实并不难。而我们有时候需要的是停下来思考,问问为什么?
说到同步异步问题,顺便聊聊支付上的实时交易和普通交易,这个问题曾经一度让我觉得困惑。
首先,什么才是实时交易,什么才是普通交易,后来我想明白了,其实从技术角度上很难定义,它本身就是个业务问题,我转账到对方账户的钱“马上”就能到账,或者从其它银行账户“立刻”取钱到现在的账户,它就是实时交易。而普通交易就是钱到帐的生效时间延后,一般至少需要隔日。而如果你要从技术角度上去严格区分实时交易很难定义,一般能想到的同步的就是实时交易,异步的就是普通交易,可这样真的准确么?(这里先不谈安全性问题)例如:银行本票,所谓本票,是要见票即付的,可从技术角度,本票的业务流程真的必须是同步的么?而有时普通的交易为什么也能很快到账呢?它不应该是异步的么?所以,实时交易和普通交易,它就是业务角度问题,与同步异步技术关系不大,在整个交易的局部范围内它可以是实时的,比如实时清算,比如高频交易,但从整个业务角度,实现实时交易的根本原因在于第三方的信用背书。本不想谈业务的,说这么多其实也只是想说明,技术不能脱离业务,微服务架构需要围绕业务建设。
其实业务逻辑没啥好说的,微服务划分中,主要讲的都是业务逻辑和边界,而服务多了后,怎么管理?其实大家都知道是注册中心,可我要说的是你真的了解注册中心么?市面上有多种相似功能的产品出现时,人们往往希望知道这些产品相比较的优劣,包含了哪些功能,往往停留在表面的功能对比上,对架构或者性能并没有非常深入的探讨。
先来看看业界主流的注册中心对比:
功能 | Eureka | consul | Zookeeper | etcd |
---|---|---|---|---|
健康检查 | Client Beat | TCP/HTTP/gRPC | Keep Alive | HTTP/gRPC |
监听支持 | 支持 | 支持 | 支持 | 支持 |
负载均衡 | Ribbon | Fabio | — | — |
CAP | AP | CP+AP | CP | CP |
一致性协议 | 定制p2p | raft | zab | raft |
客户端接口 | java/http | 多语言client/http/dns | 多语言client | 多语言客户端/http/grpc |
多数据中心 | 不支持 | 支持 | 不支持 | 不支持 |
。。。 |
以上4种可能是相对比较常用,社区较好的注册中心,拿来做的对比,如果是你,你怎么选择?真的看哪个支持的功能多?哪个用的人多?其实最重要的是根据你自己的业务需求选择,首要关注的是CAP,CAP定理可以说是整个分布式理论的灵魂。其实大部分人可能都知道CAP,但看过知道后很可能也就忽略了,那就再简单阐述下:C:Consistency 一致性,A:Availability 可用性,P:Partition tolerance分区容忍性,3个要素中只能选择2个,稍微认识的可能会知道分布式系统要二选一,首先分布式系统肯定要满足P,因为跨网络通讯了,分区容忍性必须满足,其它只能2选1, 只能选择 CP(一致性 + 分区容忍性) 或者 AP (可用性 + 分区容忍性)架构,在一致性和可用性做折中选择。
先来看几个问题?为什么dubbo微服务框架要用zk做注册中心?,k8s要用etcd做注册中心?,为什么有些人说eureka性能最好最快?,为什么zk,eureka,etcd等不能多机房部署?,为何zk,etcd注册中心服务规模上去后可用性将大大降低,甚至不可用?这些问题在你理解CAP定理后,甚至你能理解所有的分布式系统,包括分布式MQ,分布式缓存,分布式数据库,分布式存储。
先来看看服务注册和发现的场景,不管是服务注册还是服务发现,最终都是要处理一致性的问题,只是过程到底是要强一致性,还是弱一致性。
例如:在频繁的服务注册和服务下线的过程中,各中心节点先处理各服务的一致性再返回给调用方结果,貌似完美,客户端当场得到正确的节点服务信息。但问题出在中心节点在处理一致性的过程中,发生网络分区了,不管是重新再选举主节点,还是裂脑彻底挂了,为了保证强一致性,整个服务注册集群在这过程不可用。这可不太好玩,不是么?
在服务注册和服务下线的过程中,各个客户端向注册中心请求时,都是注册中心立刻给最新的节点服务信息,不管这个服务信息是不是最新的,如果是最新的,最好,一切正常,如果不是,那就照常调用,比如有的服务下线了,调用失败,那就客户端重试或者剔除,最终节点服务信息都将更新到正确状态。对业务影响的情况有限,顶多造成多调用几次的资源消耗。
所以服务注册中心也得看业务场景,我到底是强一致性重要,还是可用性重要,很明显,业务可用性相对更重要一点,特别是对于大型的互联网公司而言,那绝对必须是AP模型。而CP模型也在不断的改善,比如在服务的客户端中缓存了节点服务信息,一旦注册中心不可用,整个服务调用过程还是可用状态,只是暂时不能服务注册和下线了。CP模型用在比如支付等业务场景下,可能是更好的选择。
其实从规模上说,对于服务数量少于1k的情况下,哪种注册中心都不会有问题,但是服务数量达到一定数量级后,比如上W级别的服务,这种情况下CP模型的注册中心可能都不满足了,直接不能用了,原因就在于一致性的处理过程,不停的处理心跳,数据的一致性保证,选主节点的过程,master节点性能是有限度的。特别是如果中心节点还跨机房的情况下。目前强一致性协议主要有paxos和raft,zk注册中心就是用的paxos的简化版zab,consul和etcd都是用的raft,有兴趣的可以看看raft协议,网上有一个raft动画,很不错,送上链接,paxos协议相对较早,而且理解难度较大,现在的大部分用raft协议。
好了,现在我相信大部分能理解刚才抛出的几个问题了,其中还有几个细节顺便说下
其实注册中心还有另外3个选择,coreDNS,nacos,pilot;coreDNS目前可以在K8S中看到,service就是通过dns找到对应的服务,但它太简单了,不太适合注册中心;pilot是istio的注册中心。nacos不错,同时支持AP和CP模型,阿里开源的注册中心和配置中心,因spring cloud的3大核心组件闭源,nacos可能是spring cloud用户和dubbo用户的最佳选择,而且提供K8S和istio的支持,这点很关键,这个是未来,目前只支持java,缺点是目前暂时缺少其它语言客户端的支持,比如GO,这点对我来说太遗憾了,保留关注吧。
负载均衡其实没什么好说的,nginx,lvs了解的理解起来一点都不难,有兴趣的可以去理解他们的算法,rr,wrr,hash等。从语言角度来说,用interface去直接简单实现个也不难,但这里有个有意思的问题,直连的负载均衡和中间代理的负载均衡如何选择,比较有意思的就是你的微服务如果上K8S(这也是可能必须将走的路),负载均衡如何处理,K8S中的service也是有lvs和iptables负载均衡的,所以spring cloud要和k8s很好的融合是困难的。
限流和熔断这2者,主要目的其实都是为了快速失败,防止整个系统大量阻塞造成雪崩,但应用场景不一样,限流主要用于API网关层,在API网关层其实还维护着一个请求队列,当请求队列排满的时候,多余的请求直接快速失败返回null,由前端去处理,其实这就是为了满足大并发的需求,想想你双11抢购,12306抢票,春节抢红包的时候,你的请求真的都是打到后端处理的么?这是实现秒杀架构的最重要的根本。难点是维护这个请求队列到底需要多大,小了体验不好,大了系统可能直接雪崩了,这个只能根据实际业务情况慢慢调整。 那熔断是什么,先来一个问题,服务A,服务B,注册中心C,服务A和服务B到注册中心C,都没问题,我能断定A调用B,就没问题? 不管是agent的探针检测到服务的IP,端口都没问题就肯定是正常的?试问只要是程序,哪有没bug的,死循环,内存溢出等问题不是能通过IP和端口就能确定异常的,熔断就是处理这类异常的,它会统计每隔一段时间,服务的上游调用下游的失败情况,如果超过一个阈值,就认为这个服务是异常的,那就熔断,让上游关闭该下游的服务,简单理解就是和家里电源断路器一样一样的,熔断粒度分别有节点,服务和方法级别,并且需要手动开关。熔断器的实现可以看看hystrix实现方法和思想。
再想想负载均衡和熔断会带来什么问题?负载均衡在网络不稳,断断续续的情况下会碰到什么问题?熔断服务后会带来什么问题?问题就在如何处理一致性问题
在说数据访问层及DB之前,先来聊一致性的问题,这个问题可以说是整个微服务的核心必要问题,在单机时代,通常都是通过数据库ACID保证数据的一致性,但微服务中,因为跨网络,数据被分散了,如何保证数据的一致性是个最大的难点,特别涉及到重要数据问题,比如钱。
先来一个网络问题,一个网络通讯,从发起方来讲,能得到几种结果?答案是2种,我收到应答了和我没收到应答。从发送业务请求角度来说,会有几种结果?我觉得是3种,1.成功,2.失败,3.不确定。成功和失败很好理解,都收到明确结果了,这个不确定状态我们剔除业务定义的问题,还有什么?超时或者返回的通讯错。可以说大部分的一致性问题都是由它引起的。有人说在一个局部内网环境下,网络相对很稳定,不容易出错,在传统单机架构中,是不容易出错,但在分布式系统中,每个微服务都是通过网络通讯的,单次的网络通讯比如成功概率为99%,1次业务调用比如10个服务,中间不出网络问题的概率大约只有90%,如果更多的服务呢?这还只是1次调用,每天你有多少的请求?,道理和硬件的损坏概率一样。所以怎么办?再发一遍,好了,第一个问题,因网络延迟等原因,“再发一次”的场景不就是类似并发的问题么,而有些并发操作就可能引起幂等问题。
这2个字不要害怕,真的不难,就一句话,一次或者多次相同的操作产生具有相同的副作用。再通俗点,就是任意次相同的操作产生的结果相同。这个操作分为请求读和请求写,对应的数据操作就是增删改查(CRUD),请求读(查询)天然幂等,新增不幂等,修改和删除看情况,比如修改账户余额,“update account set banance=banance-100”,它就不是幂等,“update account set banance=banance-100 where banance=500”,它就是幂等,同理:我删除账户金额最高的一个就不幂等,我删除一个固定的账户它就是幂等的。在并发和分布式场景下,那如何让它幂等?本质问题是让这些多次执行不幂等的操作,我就让它执行1次,多余的次数抛弃,方法就是给它加把锁,让它串行去执行,只能执行1次。这里不要去陷入业务问题,业务允许的多次相同操作由业务去决定。
锁分为乐观锁和悲观锁(抽象概念的锁)。这里就把互斥锁就认为是悲观锁。
为什么叫乐观锁,首先它乐观的先认为没有并发冲突问题,个人理解就是基于可变条件的修改,如果成功了,ok;不成功,那说明这个条件在这过程中变化了,那就再查询下最新的这个条件再修改,循环执行到成功修改。CAS(compaie and swap)就是乐观锁,很多人知道elasticsearch搜索引擎,但它首先是个分布式nosql数据库,elasticsearch解决并发问题就是用的CAS乐观锁。CAS很好,但有2个问题:1. 适合读多写少的场景,如果写很多,查询的最新结果再更新一直不成功。2. ABA的问题;详细的自己科普,简单点说就是原来是A,但中间经过一些列变化又回到了A,这时候更新是可以成功的,但此A非彼A,这如果仅仅是数值场景没问题,但如果比如是链表,栈的情况,那就不一样了,他们上下是有关联的,所以一般还要加个version状态,version每次递增。只要version不一样,虽然A一样,也是不同的。
相反,它悲观的认为所有的操作都会引起并发冲突问题,语言层面的mutex,操作系统,mysql的锁这里就不多讨论了,有兴趣的自己去看看,比较有意思的给2个问题:1. 语言层面是有读写锁mutex.rwlock,在并发场景下,默认的slice(list),map等数据结构都是不安全的,想想它的使用场景 2. mysql的锁,哪种级别是在down机的情况下,数据还是安全的,为什么?
这里只说在分布式场景下,怎么才能保证互斥锁。网上有人大量用redis的setnx做分布式锁,但它有什么问题?首先你得先解决redis单点问题,其次想想CAP定理,而redis集群它就是个AP模型,你让AP模型去解决互斥锁这种CP需求?肯定不是个好的方案,况且redis锁的续租怎么处理呢?所以redis做分布式锁不是最好的方案。那就找CP模型,而更合适的就是etcd。业务处理过程大致如下:
- 业务申请锁,调用api时提供key(全局唯一ID)和ttl过期时间
- etcd生成一个当前锁的唯一凭证uuid,把key,ttl,uuid去写入etcd
- 检查etcd中有没有这个key,没有就写入成功拿到了锁,有就写入失败,拿锁失败了
- 拿到锁,心跳线程维持一个比ttl小的时间,将key续租
- 调用如果正常结束,释放锁,清除key;如果异常,等待ttl时间过期,释放资源
详细可参考etcd分布式锁demo,这里还有个CP模型问题,如果etcd彻底挂了咋办?没办法,切换到备的一套etcd集群,2套etcd集群整体看就是个AP,保证可用性,毕竟不用处理数据同步问题,对于使用K8S来说,etcd是重点,它是整个K8S的重点,api server,所有的操作几乎都离不开它,从go语言层面看,etcd的源码是值得一看的,不然K8S也不会用它。
分布式锁的常用应用场景是什么?
这又是个涵盖范围很大的问题。事务的具体定义和4大特性ACID,以及本地事务自己去科普吧,毕竟是说微服务的。由于网络划分问题,数据已经不是存储在单一实例上了,本地事务已经失效,那只能通过分布式事务去解决,分布式事务分为刚性事务和柔性事务2种:
它的特性就是保证强一致性,对应于CAP定理的CP,它遵循XA规范标准,具体内容这里不详细叙述,刚性事务的应用场景不多,原因就在于它的缺点太多,典型的实现有二阶段提交(2PC),首先需要有个主协调器去协调,进行准备投票阶段,都没问题进入下一阶段,各节点执行事务操作,写redo和undo日志,但并不提交事务,如果都执行成功,给协调器反馈yes,再commit,成功就OK,失败执行undo或者redo。 核心思想就是对每一个事务都采用先尝试后提交的处理方式,处理后所有的读操作都要能获得最新的数据。缺点有:
mysql就有XA规范的二阶段提交,官方给的数据是同等条件下和本地事务性能相差10倍,这可是不能接受的。3PC也是如此,不再叙述。有人认为像高频交易,实时清算等场景下用XA,我个人保留观点,10倍的性能差距我觉得是不可接受的,宁愿用硬件提升,本地事务和软件架构去处理该类问题。如果只是10%的性能损失,那还可以接受。
其实哪怕支付场景,我也不觉得要用刚性事务,其中绝大部分的支付清算系统并非想象的需要强一致性,当然实时清算的是强一致性,但也只是针对中心的局部问题,从整个支付过程,它也不是强一致性的,毕竟还有客户端的业务系统,你并不能保证客户端的过程也是一个强一致性的过程,而实时清算的系统为什么都是贷记并且有业务时间?考虑的更多的是业务安全问题,但带来的可能是体验,业务扩展,效率等问题,好了,到此打住,不在继续讨论下去了,个人认为在额度控制的基础上保持近实时就可以。只代表个人观点_。
所谓柔性事务,它其实就是对强一致性的妥协,做不到CP,那就只能AP,再去想办法做到一致性,所以就有了在CAP定理之后的BASE理论。它就是在AP的基础上去满足一致性要求。
BASE理论:
架构模型有长事务和短事务:
这个模型其实全都是由业务方实现的,每个都需要实现相应的try-confirm-cancel接口实现,
- try第一阶段,尝试执行任务,完成业务检查,预留业务资源,也可理解为冻结资源。
- confirm 执行真正的业务,不再尝试了。
- cancel 释放掉try阶段冻结的资源。
怎么理解?主要在第一阶段,比如A贷记转账B100元,那就先try阶段,检查各业务要素,没问题就冻结A账户100块,然后就进行2或3,全部正常执行确认100块扣除或者取消操作解除100块的冻结,这个操作过程要满足幂等,保证数据一致性。
相比XA刚性事务,好处是资源锁的粒度变小了,不再是全局锁定。不再有单点协调器,由业务方控制。但缺点也有,想想业务方要实现tcc每个阶段的子接口,如果是长事务的话代码量吓人,成本较大。
saga模型把一个长分布式事务拆分为多个本地事务,每个本地事务都有对应的确认和取消接口,其实和TCC类似,首先区别在于saga模型没有try这个阶段,那就带来了什么问题?隔离性的问题,ACID中的“I”没了,这又回到并发的问题了,如何处理?那就在它的上游业务层控制并发,怎么控制?加锁串行。saga适用于长事务,比较有意思的是任何一个本地子事务失败了怎么办,这时候肯定是幂等操作补偿,而它可以有2个方向去补偿,向confirm方向去补偿失败的子事务,和调用cancel去回滚成功的子事务,而具体用哪种取决于你的业务。
saga模型在微服务中是必然要去好好理解的模型,虽然它带来了补偿方案的复杂性,2种补偿方式更是带来了调用链追踪的困难度。但这是解决长事务的关键,实际案例就比如下单整个过程,简单的就扣减库存,然后创建订单;复杂了有积分,信用,红包,会员,优惠券,物流这些都确认后才能下单,中间任何一个小的事务处理失败,就进行接口幂等补偿。
在短事务之前,首先要来看看MQ。首先来看看有哪些开源MQ?RocketMQ,ActiveMQ,ZeroMQ,kafka,RocketMQ,NSQ等等,常见的就有这么多,但你分布式场景下不太合适用单点MQ吧,虽然有集群方案,但性能,可用性等等考虑进去,可用的分布式MQ也就剩下NSQ,RocketMQ和kafka三种MQ了,NSQ很好,简单,高性能,还是go语言写的,个人很喜欢,但考虑到现实,它的功能是受限的,比如不支持事务,不支持顺序等等让它的使用场景受限,但用在普通大量日志传输中还是杠杠的。那还剩下kafka和RocketMQ,首先kafka的事务了解有限,业务场景大多应用在高吞吐量的日志传输上,其次kafka用到了zk,把各节点,生产者和消费者都注册到zk中,虽然安装配置不难,但它有点重了,zk是个CP模型,要是在处理事务的时候挂了,好像不太好,当然仅仅代表个人意见。所以还剩下RocketMQ(java太复杂庞大了,个人也不会,但最喜欢的java应用就包含RocketMQ和上面提到的ElasticSearch,又都支持GO客户端),首先它是支持同步双写刷盘的,保证了消息不丢,而且官方给的性能损耗比异步刷盘降低10%,这是可以接受的,毕竟在重要业务场景下这很重要,还有它出名的原因在于支持事务,至于RocketMQ的架构,顺序消费,推拉模式,重发机制,群组,广播模式等概念还是去看看apache rocketMQ官方文档吧,其实RocketMQ早期也是用的zk做注册中心,后来重写了AP模型的name server做为注册中心,把各组件给注册进去。
RocketMQ是通过类似于prepare先发给broker一条half message,而这条half message暂时并不发送给消费端,执行完本地事务,再发送commit消息给broker确认,这时候broker才会把这条消息发送给消费端。最后如果发送commit失败了,超时时间过后,broker会主动到生产端查询该业务情况,成功就再commit发送消费端,失败就取消发送。RocketMQ通过双11等这么多年的洗礼和考验,你还有何顾虑和害怕的。
RocketMQ事务的整个机制可是经典中的经典,详细的思想还是值得好好看看的,但也不是没有缺点,需要消息生产者提供消息回查接口,业务侵入还是挺大的。
可能有些人觉得MQ不好用,丢数据等等,那可能不一定是MQ的问题,而是你姿势不对,处理数据库和MQ的一致性问题其实也是个AP问题,和处理数据库和redis的问题是类似的,本质上都和时序无关,因为它无法保证强一致性,只能去通过最终一致性去解决,不理解去科普下。所以正确姿势是处理最终一致性问题,在本地数据库中新增一张消息表,业务处理和生成的消息做为一个本地事务放入数据库中,生产者从本地消息表拉取消息并发送MQ,消息表有状态字段,发送成功,失败,超时等记录状态,再补发处理。
利用本地事务再发送MQ的方案具有通用型,也不需要回查接口,业务侵入性较小,大部分处理短事务都是采用这种方案。
不管用事务MQ还是本地事务消息发送MQ,在消费端都需要去处理幂等问题保持一致性。
最后,分布式事务方案送上一张对比表:
对比 | 2PC | 3PC | TCC | SAGA | 事务MQ | 本地消息+MQ |
---|---|---|---|---|---|---|
一致性 | 强 | 强 | 弱 | 弱 | 弱 | 弱 |
复杂性 | 高 | 高 | 高 | 中 | 中 | 低 |
性能 | 低 | 低 | 中 | 中 | 高 | 中 |
维护成本 | 中 | 中 | 高 | 中 | 中 | 高 |
好了,一致性问题到此要结束了。
关于CAP更高层次的理解,我觉得可以看看这篇文章,超棒!点击查看
好了,处理微服务的一致性是必要核心问题,处理分布式环境下数据一致性从来都没那么简单,不信就再接着来。嗯,数据访问层,这层是干什么的?它最主要的作用就是安全性和屏蔽上层的复杂性:
直接最主要的问题,数据库分库分表如何处理。先想一想,所有的表都能拆分么?答案是理论上是的,到实际业务上,有些还真拆不了,一个个来。
都知道有垂直拆分和水平拆分,垂直拆分根据列拆分,水平拆分就是把多行的拆分,理论上都是可以拆的。
实际情况要看原表设计方案:
垂直拆分并非必须,如设计不合理的列给拆开,一些长的文本信息等等,让text内容和id去关联。
水平拆分一般3种:
1和2的场景不再啰嗦,这里主要说3,这里这个唯一标识如何处理,这里也有几种情况,数据库自增主键ID,这个性能很好,但在实际业务场景中很少用自增ID做主键的,因为意义不大,一般都是用有意义的自定义id做主键,比如user表中的user_id或者手机号,或者身份证号做主键,那么问题来了,如果按照user_id取模分表,而我生产业务中需要根据手机号处理业务,如何处理?比如你的用户很多,好几个亿的用户。扫描全部的用户表?肯定是不行的,而这时候要做的是创建映射表,把手机号和user_id给映射起来,根据手机号去找user_id,再去处理业务,而映射表也是需要分库分表的,那根据身份证号处理业务也需要映射,也要分库分表,那更复杂的业务场景呢?
而好的业务处理系统,需要有一个全局唯一ID去处理业务,比如:订单号,根据这个唯一订单号去处理业务问题,而传统的业务系统大多复杂就复杂在这个设计问题,并没有一个唯一ID去处理业务,而需要根据订单,金额,账户等等,各种索引和组合索引去处理各个业务需求,你去拆分看看。关键在于没有一个全局唯一ID。
如何处理全局唯一ID?
所以支付系统能否也去解决这个问题呢?这里只从技术角度去说:
- 设计和规范请求方的流水号,让流水号不重复,毕竟请求方的数量是有限的,这个流水号规则是可控的可以做到不唯一,相同的就认为业务重复。
- 把1次支付给它拆解,先prepare再commit,prepare的过程生成全局唯一ID,并且把这个ID返回给请求方,请求方确认后commit。
这些拆分完就ok了?如果原先4张表,现在不够,要扩充到8张表,你的取模都不对了,而你的业务系统是24小时运行的,你如何处理这些数据?凌晨来个停机公告?详细的可能以后空了会写篇"数据处理篇"。这里就不写了。
有人可能会说用一致性Hash,一致性Hash虽好,但它带来了复杂性,同时某些情况下,它有点致命,比如某个或者某些节点突然的网络时坏的情况下,你仔细想想。
好了,分库分表可并不好玩,尽量能不分就不分,别人家分库分表是被迫的,你还主动往上凑?当然现在情况好多了,直接用分布式DB去解决问题,而不错的就有PingCAP的TiDB。
越接近数据的地方,就越难处理,而我认为首先需要去理解数据库,而非会很多命令操作很多数据库,这些需要肌肉记忆的东西相对是能较快上手的,而思想更重要。比如:你了解数据库的索引么,B+树呢?LRU是什么?mysql的LRU又有什么不同?为什么说数据库存数据到一定量级要拆分?而有些人说单表达到300W要拆,有人说500W,而又有人说5KW要拆?mysql锁的4种方式有何区别?数据库数据的存储方式?为何要有redo和undo日志?redo和undo的日志为何反而比存数据更快?好了,这些基础问题在这里不讨论。而现在因那些大厂处理数据库的方式彻底改变了,为什么叫数据库?根本的想法就是让数据库好好的去快速处理数据的存取问题,而非一站式解决数据处理的问题,计算和存取尽量分开,把数据计算问题上移到上游,而你如果还沉浸在写复杂的表关联,存储过程,用触发器,临时表,内置函数等等也许你得改变思想了。
分布式存储和分布式数据库,既然都是存储数据的,那肯定要保证强一致性的,不是么,在业务场景下,数据存取都不一致,可能也就失去了它最原本的作用;而谁家产品在这个基础上尽量做到高性能,稳定,可靠,那就是本事的问题。而强一致性的问题必然会导致可用性的降低,这也是分布式存储和数据库要解决的难点,而我认为处理思路有如下3点:1. 增加健壮性,从网络层面下手,让强一致性不容易出现不可用状态。2. 类似于mysql主从或者增加MQ发送到消费端一样,再增加一套,组合成可用性,尽量降低损失。3. 让不可用控制在一定范围内,防止整个不可用。 这些都涉及到大量的基础研发,难度和成本不是一般的公司能处理的了的。
存储分为3大类:块存储,文件存储,对象存储。而分布式存储开源的其实并不多,ceph可能是最出名的一个,但个人认为它太复杂了,复杂的算法,复杂的设计,复杂的代码,又有几人可以拍胸脯保证他能搞定ceph,反正我个人了解有限,paxos算法保证一致性,一致性hash去解决节点增加删除,各种平衡各个节点的数据,庞大的C++的代码量,还都支持3种存储,如何保证可靠安全是个极大的挑战,而从反馈来看,ceph块存储rbd相对稳定。ceph的文件存储和对象存储呵呵。当然某些大厂还是能搞定的,但他们的ceph还是那个ceph么?
而现在都在云原生时代了,很多的数据存储问题可以通过对象存储解决,比如虚拟机镜像,数据备份,docker镜像,大小图片文件等等。而个人比较看好Minio(go语言写的),github上star已经达到恐怖的19K,而它的作者就是原来写glusterFS分布式文件存储的作者,minio写了简化版的raft协议保证一致性,从而保证N/2的情况下仍然可读,大于N/2的情况下可读写。但不能集群添加节点扩充,所以要在一开始就设计好节点和容量,这样屏蔽了添加节点带来的复杂性,数据要扩容那就再增加集群。思路上和K8S的联合类似,同时在安全性上是以对象为单位的,把不可用隔离到对象级别。虽然使用的反馈的不是很多,但上次看到篇文章,生产使用效果反馈不错。
目前情况有反馈很好的TiDB(也是go写的), 其中就用了etcd保证一致性,关键它兼容大部分的mysql语法,同时提供事务和分析的支持,从各个大厂反馈,最新版的性能和稳定性很不错,github star高达21k,完整的文档和易于理解的架构。其实在TiDB之前,codis也是他们的开源作品,很好的一个redis集群,有兴趣的可以了解了解。
至于腾讯开源的分布式数据库(TBase)和华为开源的分布式数据库(GaussDB)太新了,了解有限。
到次,微服务就结束了么?显然不够,以上部分是微服务核心必要问题。还有配置管理问题,部署问题,监控问题,日志问题,业务链路追踪问题。但个人觉得这些问题在相对传统基础设施下的解决方案都有点“牵强”,有些方案可能已经过时了,特别是上了docker和k8s后,这些方案再硬套上去是不行的。也不知道为啥有人把K8S也放入微服务架构的比较中,虽然它们也有相似之处,但是要处理的问题和方向个人觉得是完全不同的。所以,这些问题会更简略方式说吧。
其实不管是Minio,TiDB,codis,还是上面提到的Nacos,Etcd,consul,RocketMQ,ElasticSearch,snowflake,api gateway都是优秀的开源产品,官网都有较完整的架构图和说明文档和手册,在这里放入架构图和列出它们提供的功能等等并没多大意义,至于更多的基于Docker,K8S,Service Mesh云原生开源应用就更多,而我这里简单引出部分只是想说明优秀的东西很多,将来会更多,知识是永远学不完的,而分布式系统你怎么去理解它,它有什么优缺点及以上一致性的方案和CAP定理,以及基于这些选择适合自己的我认为才更重要的,一点点个人见解,见笑。
为何需要配置管理?你那么多服务,配置怎么解决,生产配置,测试配置,状态配置等等,不可能微服务一台台手工配置,再重启服务吧?那是要死人的。所以需要一个集中式的配置管理中心,同时需要满足配置的修改不能让服务重启。写过代码的都知道套路,在main入口都需要一次性先加载日志文件,数据库等等各种配置,如果要修改配置,只能到相应的配置文件中修改配置再重启程序生效。而要解决不能重启服务的情况下动态加载配置生效,你需要主动通知触发修改内存中该值或者watch这个配置,有变动就修改内存中该配置的值,从而达到动态生效。而一般这个配置都是保存在KV存储中的。所以etcd,consul,nacos,包括K8S中的configMap 都有配置中心功能,而专用的配置中心还有携程开源的apollo,但它可能更适合java和基于Eureka注册中心的生态中,也较完整和重量。
这个属于DevOps的范畴,这里在微服务1.0中的典型应用有Gitlab的CI/CD,以及Jinkins,但个人觉得在云原生中,Jinkins虽好,但还是太复杂太重了,流程也并非很适合云原生应用。持续集成和持续部署的问题是个复杂的问题,目前CI相对成熟。
传统的选择可能是zabbix,但小米开源的Open-falcon和云原生的promtheus(普罗米修斯,包括上面的阿波罗,啥时候出个雅典娜应用啊_)分布式监控系统更合适,而K8S中的HPA弹性调度现在就是基于promtheus监控阈值实现的。
大家可能都知道构建ELK(elasticsearch,logstash,kibana)日志系统,但现在的ELK已经并非以上这3样开源应用就能构建日志系统了,个人觉得它更是个日志系统解决方案概念,如果只是这3样应用软件部署在K8S系统上你可以试试,把整个系统拖垮了都有可能,想想java的logstash如果部署在每个docker或者pod中,它会把你整个资源给吸光。
我觉得链路追踪很重要,而在微服务1.0中,不了解链路追踪可能会遇上不少的坑,想想你怎么才能快速追踪异常业务,定位服务处理时间,延迟,性能问题,追踪必不可少,分区后如何理顺服务之间的关系,必然少不了数据传输中的埋点,所以数据报文中的traceid,spanid,parentid等埋点必不可少。不管是在报文头或者报文体中。常用的开源链路追踪有zipkin,pinpoint,skywalking。
为什么把service mesh叫微服务2.0,本质上它还是微服务,那和微服务1.0有和区别?
在微服务中,监控,日志,链路追踪必不可少。没有这些,你的微服务可能随时会分崩离析。到此,你的微服务在以上这些基础下,已经可以跑起来了,但这就完美了么?你将很有可能面对一系列坑,首先是你可能支持不了跨语言的整合,各开发人员水平能力的问题,想想开发人员需要了解多少的包?处理服务注册和发现,配置中心,负载均衡,限流熔断,服务重试,链路追踪等等,这些应用包和你的业务耦合在了一起。如果还碰到某个或某些包需要升级或者变更的情况,这些是微服务1.0最大的痛点,所以service mesh来了。
如果直接看service mesh其实是很难理解的,那service mesh如何解决微服务1.0的痛点,本质上是一个复杂的耦合性问题,个人的理解就是类似于微服务划分边界的处理方案,再“下沉一层”,把那些服务注册,服务重试,配置中心,负载均衡,熔断限流,日志收集,链路追踪,监控等等再下沉,和业务代码解耦,让代码层干类似于声明式的活,而下沉的一层干类似于调度器的活。再画张简图对比理解下吧:
让service专注于业务代码的事情,其它的事情交给sidecar去完成,类似于让服务当老板的感觉,我只需要知道我调用其它哪个服务,其它就交给sidecar去做吧。这个sidecar是标准叫法,有解释为mesh的,有proxy的,在istio中叫envoy。而一个个sidecar组成了一个数据平面。而服务的注册中心,配置中心,监控,日志,链路追踪,安全认证,灰度发布等等认为在另一个平面,叫控制平面。是否霍然开朗?_
这里其中可能唯一的疑惑是service和sidecar分开后,那不又涉及到网络通讯问题,分区问题,一致性问题等等,又回到了起点,其实service和sidecar的要求是一起部署在本地,这样就不涉及到分区问题。在K8S和istio中,这个envoy就是通过注入(inject)的方式塞进pod中,而pod就是k8s的最小单位。而pilot就是服务注册中心的角色,还有灰度发布,超时重试等管理,mixer就是收集envoy各种数据,citadel用于各种认证安全,galley用于获取k8s各种配置,校验,分发的组件,这4大组件构成了控制平面。整个istio和k8s完整的配合。
如果业务系统上线K8S和service mesh后,一次调用变成3次,当前有很大的介意在性能的影响,但其中2次是本地调用,总体和带来的收益是值得的,有关更多可以看看大佬的分享,像美团,闲鱼,B站等等,其实无状态的应用上K8S和service mesh难度并非很大,难点都在于有状态的服务,而大厂的数据量很大,这些有状态的应用以目前情况下,很少有看到全部上K8S和service mesh(当然也许我孤陋寡闻)。有状态服务部署到K8S上是有相当难度的,远比直接部署难,虽然现在有helm和operator。
service mesh简单到此结束,目前最流行的service mesh就是istio,官网有个很好的bookist的举例,可以去看看,当然,istio虽说可以支持各种方式接入,但目前结合最好的就是K8S,毕竟是"一窝"的。当然还有sofamesh也不错,它解决了一些istio的单点和复杂性问题,至于mesos等等其它的,我也不知道。
秒杀,搜索,推荐这3大业务是互联网企业应用的的3大核心系统,你可以打开你手机各类APP,仔细观察这3大业务比比皆是,再联系微服务,无处不在。
这里的“秒杀”并不仅仅是秒杀商品,想想12306,春节红包,它们本质上都是秒杀,突然的大流量进入请求有限的资源,这种并发需求和大量的不限定资源的并发场景难度是不一样的,前几天看到朋友圈和公众号上一篇go语言写的12306的秒杀架构文章,会golang的有兴趣的可以看看,它是否可行?是否解决了一致性的问题?
秒杀架构,从业务角度看根本没解决根本问题,比如:秒杀商品就那么多件,春节火车票就那么多张,春节红包就那么多个,满足不了那么多人的需求。从技术角度上讲,突然大流量的请求这些有限资源,我的目的是让系统不崩。如何去解决系统不崩,有个很形象的模型去解释:“漏斗”。通过微服务一层层不断的去削去这些请求,最终只能让漏斗下面的有限数量的请求通过到达数据库层面请求到这些资源。因为上游的处理能力相对总是大于下游的处理能力的,虽然可以通过弹性水平扩张性能。架构思想如下:通过API网关层的队列抛去大部分的请求,再通过MQ削峰填谷到达业务逻辑层,去掉那些不合格的请求,黑名单,不满足要求的新用户,不合规的IP地址等等,再去随机或者网络先后等再抛去一部分请求,最终只有有限的请求到达DB请求到数据,同时要保证不超卖问题必然需要DB去保持强一致性。但读请求查看剩余数量可不一定是实时准确的,数据库是相对脆弱的,读和写必然要去分开来满足并发需求。而读请求一般都是从redis中读取的,db中最新的剩余数量异步方式写入redis中,所以读请求的数据极大概率是不准确的,但不影响最终的业务。难点就在于不让这个漏斗的上口破了,那就直接造成雪崩了。所以你可以看到12306变态的验证,支付宝和微信等分流。春节抢红包时一大堆的卡片,这些卡片你以为真的请求到了后端,可能压根都还没到达API网关层,在CDN层面甚至APP客户端上就预先给你生成好了,这里的架构图就不画了。
所以那篇文章看到试图通过redis AP最终一致性问题要去解决CP问题,是比较困难的,很多人在说最终一致性,那想想这个最终一致性到底最终到什么时间才一致?这个时间差是相对不确定的,在某些业务场景下,这个时间差是有要求的,比如火车票的最终一致性不可能等火车都开过了才解决一致吧?又比如,支付清算,这个清算是有场次和时间限定的,某些清算必须当天5点前结束的,在这之前必须解决一致性的问题。所以,大概率的解决最终一致性必须有完整的监控和核对及线下处理方式去保障。
搜索系统(这里的搜索是垂直搜索,非通用搜索)和推荐系统这2个系统是相对最复杂的了,远不只是微服务架构的问题,这里就水几句话吧,从微服务架构角度上,简单点就是召回层对应于数据访问层,排序层对应业务逻辑层,现代搜索和推荐应用了大量的大数据和人工智能技术,打开你的APP首页,直接各种推荐扑面而来,其实衡量一个推荐的牛逼看它是否越来越懂你,本质问题是你这个userid在不断的被打标签。你再仔细搜搜商品,往下拉取试试,你也会收到惊喜。好了,搜索和推荐就水到这里,其中个人觉得elasticsearch很有意思和值得去应用的,这里就不聊了。其实各种技术一直都在我们身边,不管大数据,人工智能,云计算,微服务等等,打开你的APP直接观察,看看他们的业务划分,业务场景,业务聚合,购买,支付流程,技术人员别被各种商品和视频内容给蒙住双眼。
好了,微服务到此结束,不知能否称为篇“土味”微服务思想篇,其中尽量用简单语句和例子把那些可能比较难懂的给解释清楚,其中包含了个人的一点点理解,但毕竟用相对白话去描述那些词语,限于个人能力,可能有些地方未必准确或描述不到位,那就将就了;至于要说能不能称为“干货”,那就只能在于你的理解。其中微服务还有很多内容没涉及或内容很少,比如安全授权问题,DevOps,监控,日志,链路追踪,各种测试问题,这里不涉及或者简单带过主要是这些每一个都包含大量的知识,不是一点点文字能给说完的,而在微服务1.0中有些不好解决和不够优雅,而大厂都是基于他们自己的需求和业务开发。啰哩啰嗦的原来写了这么多字,超出了原来的用时预期,以后也不会写这种“又长又臭”的思想篇了,但写写k8s,istio,golang等也不错。最后,这世界变化太快,各种浮躁和焦虑,知识和技术是永远学不完的,有时我们需要的是停下来好好的想一想,人生不也一样如此么?