首页 > Microservice

说完了分布式事务最核心的思想(2PC) –> [探秘分布式解决方案: 分布式事务——从核心思想之2PC(两阶段提交)开始]    http://skypyb.com/2019/11/jishu/1149/

 

那么现在进入到更加复杂的场景。像这种跨库调用之类的,一线互联网公司早就不玩这一套东西了。这都9102年了,上来就是微服务架构。

我这么多服务,你整个啥跨库调用呢?一个服务可能同时调用多个其他的服务。这多个其他的服务中都要执行SQL语句,修改落实到服务所对应的数据库之中。

 

 

 

微服务下的服务调用

还是以一个订单支付的流程为例,只不过由之前讲解2PC的单机多库示例改为了微服务示例。

微服务架构下,用户下单的流程将会变成这个样子:

微服务下的订单服务

 

这里边每一个 “服务” 都是一个系统,都是会单独部署的。

可以见得这个复杂度上升的非常大。比如说我用户在下单购买,调用了订单服务的接口,此时订单接口的方法内逻辑不单单只是订单服务自己操作自己的数据库。

订单还得去调用其他的服务的接口。比如库存、积分、仓储 等。

 

那么,在微服务架构下,服务之间互相通过接口进行通信,如何才能在一个 “事务” 中保证数据一致性?

如果出现这种情况   比如订单支付状态已经修改完毕、库存扣除完毕,但是加分却失败了, 这样子咋回滚?

 

那么就轮到新的解决方案出场了。那就是 TCC (Try-Confirm-Cancel)

TCC全名为 两阶段补偿型方案。是现在的互联网大厂主流的分布式事务解决方案之一。而TCC,就是脱胎于2PC,是作为2PC思想在微服务领域的子集被使用着。

所以说为啥2PC是核心思想,不懂2PC,那TCC是肯定明白不了的。 要是懂了TCC的原理,那么前置条件必然是已经懂了2PC。

 

 

 

TCC原理思想

而TCC的原理相比于2PC,有这样一种行为上的变更:

2PC的行为是:  1、预提交 2、实际提交

TCC将其改为了: 1、预留资源 2、确认资源

 

什么叫做预留资源?什么叫做确认资源?

还是以上面我画的的订单活动图为例子,预留的资源,那就是 订单状态、库存数、积分数、出库单

那我们在TCC的方案下进行事物的话,第一阶段就是将这几个资源给他锁定住。然后在第二阶段进行一个资源的确认,即提交/回滚

你如果知道2PC的思想,我想这完全不难以理解。下面是TCC方案的状态图:

TCC-STATE

 

在TCC方案下,在第一阶段中的修改,修改的并不是实际期望的结果,而是期望结果之前的一个中间值。

所以,每个服务都要提供两个接口,一个用于第一阶段的资源预留行为,另一个用于确认/回滚。

(其实你也可以就搞一个接口,然后参数传个对象,由对象中的属性来决定做什么操作也不是不行)

 

比如更改订单,我不可能一下就将这个订单给改成已支付。毕竟现在这个方法还跑到一半,不能完全确定之后的业务是否正常进行。

所以,在此取了个中间态,将订单改为 “更新中” 。

库存、积分等服务也和订单相同。 当所有的接口均调用完成后,即所有的资源预留操作均完成。则在这时进行确认行为。

如果说所有的操作都返回了成功,那么就将订单状态改为已支付,然后将冻结的库存真正减掉、积分真正加上、草稿出库单改为生效出库单。 (confirm)

而反之,若是其中有任意一个接口失败了,那么就对应执行 calcel 操作,将所有改变的状态通通回滚。

 

 

 

如何预留资源

而有的人可能会好奇,什么冻结库存之类的玩意如何实现?

这个其实也很简单,比如你库存表本来只有一个库存数字段。但你现在要用这种TCC方案了,那么就给他再加上一个冻结库存数字段

本来来说,你某个商品库存数是10 , 那么用户再下单完成支付的这个流程中,假设买了1个商品,那么原来的逻辑就得改一下。

正常你买1个商品,那库存就是10-1=9 咯,所以将库存数改为9。而在TCC下,你就不将他改为9,而是在冻结库存数上+1,即数据库状态变为 库存数10,冻结库存数1

那么在其他的用户购买时,你返回给前端的库存数应该就是 库存数10 减去 冻结库存数1 等于现在只有9个库存能够被使用。

用户他不知道冻结不冻结,但是你的程序里边是知道的。用这种逻辑那是一点没有问题。

在你支付接口里边所有的预留操作成功之后,那么就如上所述,将库存字段真正减掉,也就是将10库存真正减1,将冻结库存还原,这是第二阶段该做的事情。

 

说到这里,总没人问第二阶段要是某环节错了怎么办吧? 要是有这疑问的那肯定是没看我上一篇分布式事务文章。

这里也简单的讲一句:本身失败的概率就极小,要是操作失败了肯定是会重试、并且记录事务日志的。状态一个不对劲就由人肉团队、定时任务来进行补偿。

 

 

TCC讲解完毕,那么到底TCC和2PC有着哪些区别呢?

TCC其实说白了,其实也和两阶段提交差不多。就是要开发新的接口。

毕竟是在微服务架构下,复杂度上升很多,要在这种架构下来进行分布式事务本身就要比单机应用考虑的方面更广。

而TCC和2PC之间最主要还有这么一个区别:

在很复杂的、跨了很多数据库的业务场景下,2PC的锁粒度非常大。因为所有的数据库锁均被应用所持有,直到事务完成。性能会很低,根本顶不住大并发。

而TCC的话,从整个系统上来看,吞吐量会好非常多,因为在TCC方案下的的复杂调用链之中,实际上每个数据库操作,都是commit了的,换而言之就是锁释放了并不是一个加在全局的大锁,而是将锁粒度控制在每一个系统的每一个接口里。嘿,有没有想到ConcurrentHashMap。

 

 

 

说在最后

如果将TCC这种解决方案称之为 “术” 。那么2PC就是所谓的”道”

只要你学会了”道”   那么一切基于此道的术相信都难不倒你了。

当然,像这种TCC的实现肯定是有现成的给你用。毕竟自己来搞这么一个框架也是蛮复杂的,不推荐自个造轮子。

业界开源实现也挺多比如ByteTCC、seata、tcc-transaction等,使用方面就不贴了,基本都是配置。

而除了TCC,其实还有像是最大努力通知型方案可靠消息最终一致性方案这些基于最终一致性的分布式事务解决方案。也可以网上搜搜了解一下。

 

阅读全文

为什么要有分布式事务?

本地单机事务,有点工作经验的肯定不陌生,主要用于处理操作量大,复杂度高的数据,一般都由数据库自己实现。

开启一个事务,进行了多个对数据库进行更新(增删改)的语句后,可以自由的选择 commit rollback 来结束事务。

可以从根源上保证多个数据集合的同步。

 

比如一个订单系统,用户支付订单后,订单表的订单状态被改为已支付,同时,库存表中商品剩余数量减一。

若是没有事务机制,在订单库状态修改成功后,由于库存不够减、网络波动、程序bug、数据库挂掉等原因导致数据库中库存减一的SQL语句执行失败; 那这样到最后就会出现实际库存早就没有了,但用户却支付成功的重大事件,即超卖了。

所幸。只要你开了事物,那么事务的原子性确保让你多个语句作为一个原子操作来执行,要么同时成功要么同时失败,在库存-1 失败的同时你订单修改也失败了。所以在单机简单应用中一般不存在这种问题。

 

可是,在稍微复杂一点的场景中,数据库自带的事务机制也有着无能为力的时候。

仍然是一个订单系统,这回场景为由于系统用户激增,入驻商家也越来越多。在日渐庞大的数据量下出于性能考虑决定对数据库进行拆分,由原来的一个库,拆分为一个订单库、一个库存库

你的程序里边,一个下单方法中会执行两个库的SQL语句,在这样的逻辑下,普通的事务还有用么?

就像这样:

订单服务-1

 

那肯定是没有用的。

这种场景中必须要实现跨库事务才能够满足企业级生产需求。

 

要如何实现这种跨库事务? 这里引出分布式事务中的重点思想:阶段提交

 

两阶段提交协议(2PC)

2PC 引入一个事务管理器来作为事务的协调者。

在程序执行复杂数据操作时,开启事务、SQL语句执行完后,先不进行commit。先将执行的结果告知给事务管理器

这个时候,再执行其他数据库的SQL语句。当这个事务中,所有的SQL语句全部执行完毕后,即事务管理器已经得到所有此次事务中SQL语句的执行结果后。

再集体通知所有的数据库进行commit 或 rollback。

 

以下单流程来模拟的话。

前置条件:已引入事务管理器

此时,用户一个支付请求过来。先执行订单库的修改语句,将用户的订单改为已支付。

执行成功后,不提交事务。再开启库存库的事务、执行库存库的库存修改语句,将库存减一。注意,这是两个不同数据库的不同事务。

这个时候数据库的事务隔离机制就将数据锁住了,包括订单库的订单、库存的数量。但是还没有提交。

在每个SQL执行完成之后,都会将执行完成的信息提交给事务管理器。

那么,当订单、库存都修改成功,事务管理器之中都收到了执行成功信息,确认好了本次事务中的所有语句都执行完成后、立即通知所有数据库进行 commit 操作。

任何一个语句执行失败,事务管理器就会通知所有数据库进行rollback。

 

活动图如下所示:

2PC-01

 

2PC中的底层实现思想,就是将原先的单次提交操作分为两个阶段,即 1、预提交 2、实际提交

看起来是不是很简单。大道至简,实际上只要理解了两阶段提交的思想内核,那么任何分布式事务的解决方案都难不倒你了。

因为TCC、消息事务等事务解决方案,都是从2PC衍生的。

 

 

那么问题来了,2PC凭什么可以解决分布式事务问题?

有人可能也有疑问。2PC好像不是那么严谨?

比如: 事务管理器的通知失败了咋搞,假设事务管理器通知订单库的commit成功了,但是通知库存库去commit的时候失败了。那不还是有问题。

这里就必须说明一下分布式事务解决方案的重点。 那就是: 分布式事务不可能100%解决

不信你去问阿里巴巴的员工、Google的员工,去问他们:”你们公司的分布式事务能不能百分百成功呀?” 肯定不可能,起码现在9102年在这个地球上是没有哪家公司敢说自己的分布式事务是百分百成功的。

虽说分布式事务不可能百分百解决,但是可以尽量提高成功概率,比如让你的成功概率接近99.9999%,即让你的系统尽可能的高可用。

 

 

而2PC就是及其有效的提高成功率的方法

这里要先说明,这个事务管理器并不是一个单独的服务,而是内嵌在你程序里的组件。这玩意挂了你程序也挂了,所以不要操心事务管理器挂掉的问题。

以订单为例,其实最容易出现的问题主要是 库存不够减、数据库挂了、网络波动

而在使用2PC的情况下,你的所有SQL都执行成功了,但是事务管理器commit失败了,是个什么概念啊?

要知道在程序的世界里,你别说分两个阶段,你分再多也就几毫秒的时间。

在下单这里预提交成功后,可以显而易见的得出几个结论:

1、所有SQL都执行成功了,所以数据库在执行SQL语句时状态是正常的、服务器和数据库之间的网络也是正常的

2、由于库存库的SQL执行成功,所以库存够减,不可能出现库存不够的问题

 

你跟我说你预提交完,这个时候事务管理器刚通知完订单库commit成功,在提交库存库减库存commit时数据库挂掉了? 或者网络波动导致通知失败了?明明前1ms还好好的?

wdnmd

咳咳,不否认有这种概率啊。可是对于2PC来说,你通知commit失败后,是会重试的。基本上都不会有问题。

那么,要是一直重试都不成功呢?比如挖掘机在你提交库存库commit时,将网线挖断了。emmmmmm,有这种疑问也实属正常,毕竟啥情况都可能发生是不。

这种情况出现确实没有别的办法。一般来说都会记日志打log,之后要么后台通过定时任务补偿,要么人工介入

像一线互联网公司啊,都有大量的一些人工团队专门处理这种极端情况下出现的数据不一致问题,还会让研发工程师写一些什么脚本之类的去回滚、回复数据。

 

 

综上所述。回过头再品一品两阶段提交的思想。为什么说2PC可以提高分布式事务的成功概率,相信已经了然于心了。

而像是这种2PC的具体实现,那肯定也是不用你自己操心的。在java的世界中这种框架已经有不少了。比如开源框架 atomikos,就可以帮你解决这种跨库事务问题,当然,底层就是用的2PC思想做的。

至于如何具体在代码里实现,这里就不贴了。搜索引擎搜一下atomikos可以搜到一大堆。基本上在Spring里边也就是引入依赖后配置一下,然后编程式事务/声明式事务轻松搞定。还可以找一下、了解一下我这篇博客没说明(因为感觉没什么必要)的XA/JTA 接口规范等。

阅读全文

 

在微服务架构中,网关是必不可少的重要组件。

这关系到了客户端“如何访问”每个服务。

以前主流的方式就是使用Netfilx的Zuul组件。但是,因为某些奇妙的原因,Netfilx全家桶都停止维护了。

我之前也写过Zuul的配置和使用方法:

微服务路由解决方案: “Zuul” 服务搭建;以及自定义Zuul过滤器的代码编写。

Spring Cloud Netflix Zuul 使用自带的 Hystrix 实现回退机制 , 并实现请求在Zuul内部的聚合功能

 

那么,既然Zuul这套东西不维护了,迟早也就是抛弃的份。这时候就到Gateway上场了。

说起Getway的工作流程,大概是这样子的: 客户端向 Spring Cloud Gateway 发出请求。然后在 Gateway Handler Mapping 中找到与请求相匹配的路由,将其发送到 Gateway Web Handler。Handler 再通过指定的过滤器链来将请求发送到我们实际的服务执行业务逻辑,然后最终返回结果。

这么一说…是不是感觉这套流程和 SpringMVC 有点像。这么一看一下子Gateway的流程就感觉铭然于心了有木有。

 

进入正题,开始上手 Spring Cloud Getway 的配置和使用。

当然,路由网关,也是一个项目。

难么必不可少的 项目依赖管理配置文件、应用配置文件、启动类 是肯定需要的。

而他,真的就只需要这几个东西就能完成基本的路由、负载均衡功能。

 

既然要建立项目,pom.xml就是第一个。

Spring Cloud Getway 有一个特别的地方。

那就是他不使用 Web 作为服务器,而是使用 WebFlux 作为服务器。

基于此,那么 spring-boot-starter-web 这依赖就不能上了。

pom.xml完成体如下:

这pom.xml真是又丑又长。我怎么就用了maven没用gradle呢? 不禁陷入了深深的思考

既然是Alibaba体系,那么Sentinel、Nacos等依赖也是需要的。 和之前写过的几个Spring Cloud Alibaba组件的使用结合到一起。

在此之上去掉web依赖,引入 org.springframework.cloud:spring-cloud-starter-gateway 依赖就可以了。

由于过滤器等功能依然需要 Servlet 支持,故这里在依赖一个 javax.servlet:javax.servlet-api

 

 

pom.xml搞定,接下来就是最最最最重要的 application.yml:

 

其他的不说,最主要的是spring.cloud.gateway.route下面的配置。

在这里有几个很重要的点,要重点说一下。

id:  这个是路由ID,值可以随便写,但是一定要保证在整个微服务中此值是唯一的

uri:   lb://是固定写法,表示开启负载均衡。后边跟着的是你注册进nacos的其他服务名字

predicates: 这个是谓词。谓词这个词语一说出来很多人都会蒙圈。但是如果是熟悉JDK1.8的人肯定对这个有印象,Stream API里filter()方法入参就是一个Predicate,返回一个boolean类型的值。虽说Gateway里的和Strean API里的肯定不一样。但是可以这么理解,他是为了判断请求是否满足一个条件而存在的。而我设置的 – Method=GET,POST 表示匹配 GET 和 POST 请求,这俩种类型的请求就可以路由。

 

predicate当然不止Method这一个玩意,他总共有这么些设置:

After | After=2017-01-20T17:42:47.789-07:00[America/Denver]
Before | Before=2017-01-20T17:42:47.789-07:00[America/Denver]
Between | 2017-01-20T17:42:47.789-07:00[America/Denver], 2017-01-21T17:42:47.789-07:00[America/Denver]
Cookie | Cookie=chocolate, ch.p
Header | Header=X-Request-Id, \d+
Host | Host=**.somehost.org
Method | Method=GET
Path | Path=/foo/{segment}
Query | Query=baz
RemoteAddr | RemoteAddr=192.168.1.1/24

参考: https://cloud.spring.io/spring-cloud-gateway/2.0.x/single/spring-cloud-gateway.html#gateway-request-predicates-factories

 

 

那么现在

nacos也注册进去了

setinel熔断也开了

路由设置也配好了

还要什么自行车,一个启动类,给他 Run 起来,就可以搞起

 

注意,访问路径是: http://路由网关IP:路由网关Port/服务名/**  (和Zuul一样的)

随便调用个接口,能够成功获取响应就是成功啦!多开几个同样的服务跑起来就会知道,负载均衡也是实现了的。

至此 Spring Cloud Gateway 结合Spring Cloud Alibaba全家桶的路由功能、负载均衡功能已经配置完毕。

阅读全文

说起Feign,那又是老东西了啊

我以前写Netfilx的时候就详细讲过Feign这玩意,其实用起来基本是一样的。

以前的文章地址 : 分布式微服务项目如何使用 Feign 实现声明式 REST 调用,以及自定义 Feign 配置

 

虽说切换到了Alibaba,但是也没啥变化,Feign该怎么用就怎么用,这就不详细说了。主要还是Sentinel

其实Sentinel 和Hystrix在代码里写起来也是一样的

首先肯定是要上pom.xml配置起来。加上对应的依赖。

 

接着写个Feign接口

调用的话就是这样子调用:

 

哎,观察我的NacosHelloFeign类,这里可以看到,我这用了一个fallback回退,这个回退指定的就是Sentinel 的实现,其实写起来和特么的Hystrix一模一样。不得不说SpringCloud这一点是真的做得好。

 

当然,配置肯定是得配置的,他也不会直接就给你用了。

Sentinel 虽说是适配了 Feign 组件。但默认是关闭的。需要在配置文件中配置打开它,在配置文件增加以下代码:

 

其实到现在,整个Feign的使用+熔断限流机制就已经配完了。

不过Sentinel 比起Hystrix真正优越的地方还没来呢。

那就是: 控制台

这个控制台功能丰富、UI好看,比Hystrix那是高到不知道哪里去了。

要是用控制台,又不得不下载代码、打包、编译、运行。

 

所幸,我们有神器docker。我在docker hub 上找了个镜像,直接用就可以了,镜像地址:https://hub.docker.com/r/bladex/sentinel-dashboard

直接执以下命令,将sentinel控制台起开绑到8858端口

 

然后自己的yml文件也得改一下,毕竟都上控制台了,也得把自己这个服务给注册进去。

全部的yml文件如下所示:

可以看到配置里有一个 sentinel.transport.port属性,这个属性的意思是在自己本体这个服务里边,在开个新的端口专门用来和 Sentinel 控制台交互

Sentinel 控制台自身也有,默认端口则是8719

 

到这里就差不多了,所有东西打开,然后就可以访问 http://ip:8858 进入Sentinel 控制台,默认账号密码都是sentinel

有一点还需要注意,那就是为了确保客户端有访问量,Sentinel 会在客户端首次调用的时候进行初始化,开始向控制台发送心跳包。意思就是说你一个服务注册进我这来,首先还是看不到的。

你得先经过别人的请求,我才会去监控你,所以在服务刚启动的时候进入Sentinel 控制台找不到自己的服务是很正常的,只要启动时没有报错就不会有问题。

 

 

阅读全文

 

中秋佳节当天,人在异乡,无法和家人团聚,既然如此 不如提升一波自己的姿势水平,免得浪费这假期大好光阴。

说道微服务,现在可不流行Netfilx那一套了, 现在都9102年9月了,Spring Cloud Aliabba都已经孵化完毕,再加上Netfilx将他旗下的项目通通打入冷宫,现在在不学习SpringCloudAlibaba体系就赶不上时代的潮流了。 Netfilx那套体系我都搭过demo 写过博客,各种配置啊注意点啊都挺详细的,感兴趣的可以翻我以前的文章。

 

 

https://github.com/alibaba/spring-cloud-alibaba 这是 SpringCloudAlibaba 的项目 git。

这篇文章主要是开个SpringCloudAliabba篇的头,正式开始SpringCloudAlibaba的学习。

 

 

废话不多说,进入正题。

今天就从微服务最重要的地方开始,当然,也只能从这个地方开始。

那就是服务治理。

 

在 Spring Cloud Netflix 使用的是Eureka,而SpringCloudAlibaba 给我们提供的就是Nacos。

而且,Nacos比Eureka 更加强大,他还可以用来当配置中心。

 

这里我就简单的先完成他基本的职责:服务注册与发现。

首先nacos和Eureka有点不同。

他这个应用。不是我们自己写的,Eureka这个东西他需要我们自己创建一个应用。然后在编写代码,然后再将项目打包,在启动。

Nacos这个东西他和Zipkin有点像,是那种直接下载下来运行就完事了的。

 

我这儿用Docker来运行一个单机的 Nacos 镜像添加到8848端口(默认端口),当然,集群是肯定可以集群的,我这先不玩。

对了,不知道为啥我用网易的docker镜像地址下载会卡住,之后换了alibaba的来下就没问题了。

nacos登陆地址:http://ip:8848/nacos   默认账号密码是nacos/nacos

 

开启Nacos服务完毕后,就要开始写服务提供者和服务消费者了。

其实这俩都差不多,反正注册进服务治理中心了,A可以调用B的服务,自然B也可以调用A的服务,导入的配置也一样,我这就以服务消费者来举例子,避免贴两份基本相同的代码了。

 

我这使用的是最新的版本,要用就用最新的,不然没意思。我之前写的Netflix项目也是使用的最新的,当时用的是SpringCloud F版第三版

现在这个项目用的是SpringCloud G版第三版,SpringBoot 2.1.8.RELEASE

 

先创个依赖项目进行最基本的统一依赖管理

 

这个项目集成了SpringBoot 然后导入了SpringCloud的依赖和SpringCloudAlibaba的依赖。

依赖项目写完之后就是正式开工编写服务。

这个服务就直接继承我刚刚写的依赖项目就可以了。

 

然后在其中,引入web、actuator、test几个SpringBoot依赖。当然还有必不可少的SpringCloudAlibaba Nacos 依赖。

启动类:

 

这个启动类用了个 @EnableDiscoveryClient 这个注解,该注解是SpringCloud内置的,Nacos实现了这个注解,导入了Nacos的依赖的话就代表要注册进Nacos里边。

这样的好处就是以后要是换个新的服务治理中心,代码都不用改就可以轻易的更换,完美符合里式替换原则

 

 

既然能注册进去了,那接着还是老规矩,来个RestTemplate进行远程服务的调用,@LoadBalanced代表负载均衡,这些东西我以前的文章都有讲过啊,这儿就不详细说了。

 

这里就是编写一个控制层,来进行接口对外暴露,内部就是使用RestTemplate+LoadBalancerClient 来进行负载均衡的调用其他服务。

 

当然,yml是肯定要写的。

 

对了,这个management.endpoints 下边端点一定得打开。

因为 SpringCloudAlibaba 的 Nacos 在实现的时候提供了一个 EndPoint, EndPoint 的访问地址为 http://ip:port/actuator/nacos-discovery。

这玩意和服务注册发现息息相关,要是Nacos访问不了这个端点,那你就注册不了。

 

其实到这就差不多了,服务已经可以注册进Nacos里了,然后微服务之间的互相调用也是没有问题的,负载均衡也可以实现。

都没啥好说的,只要学会SpringCloud的思想,组件从一套换成另一套也就几十分钟的时间。

阅读全文
EA PLAYER &

历史记录 [ 注意:部分数据仅限于当前浏览器 ]清空

      00:00/00:00