Apache ServiceComb Pack micro-distributed data service solutions eventual consistency

https://github.com/OpenSagas-csharp/servicecomb-pack-csharp

Saga Basic Guide

Use pre-conditions explain

If you have not yet understood the students of Saga students can refer to the official Chinese Saga address address , and can refer to one of the contributors of this project WithLin of a Chinese article on the following address: Address , articles progressive approach about the importance of a distributed transaction service in the micro-scenes, as well as Saga roughly implementation and follow-up reflection on distributed transactions

  • You need to be available to a local or remote database (mysql or postpresql) as Saga persistent persistent storage distributed transaction events, of course, as long as the official support of the Database Provider to the specific idea database configuration as shown below, note the name of the database the same as your real name database

alpha-server-database-setting.png

  • Must successfully started alpha-server, resulting in build and deployment environment is quite troublesome, the latter will provide the official image upload docker hub available to everyone to use, refer to the diagram starts successfully

alpha-server.png

  • Alternatively saga simultaneously provides visual interface UI, direct idea to start saga-web

  • Support dockercompose: root directory offers a dockercompose file is required only if dockercompose up -d can be in the root directory of the project, the above operation lets you set up the debugging environment of interest.

Fun begin distributed transaction Saga

Clone of the current project, and then use the VS2017 open solutions targeted to sample directory, you will see three instances of the application as shown below, this app is based on TargetFramework = netcoreapp2.0, it is necessary to start the appropriate environment

sample-app-test.png

下面对上图做一个基本介绍,假定现在我们有三个微服务,分别是 Booking-预定服务,Car-订车服务,Hotel-酒店服务,相信大家一看便知,三者的从属关系以及在现实社会中的关联关系,下面我们的分布式事务一致性测试将在这几个app中完成,现在我们分别对项目做一定的初始化工作

services.AddOmegaCore(option =>
{
    // your alpha-server address option.GrpcServerAddress = "localhost:8080"; // your app identification option.InstanceId = "Booking123"; // your app name option.ServiceName = "Booking"; });

这里需要对三个项目都做如上所示的基本配置即可,现在一直都配置就绪了,下面开始我们的分布式事务的测试吧...

分布式事务场景测试

下面将会针对正常以及异常情况分别测试

正常情况测试

由 Booking 发起预定汽车和预定酒店的服务,且假设三个服务均可以正常访问的情况,正常启动如下图所示: apps.png

        [HttpGet, SagaStart] // SagaStart 顾名思义标记为分布式事务开始的地方
        [Route("book1")] public ActionResult Book() { // init basic httpclient var httpClient = new HttpClient(); // mark a reservation of car httpClient.GetAsync("http://localhost:5002/api/values"); // book a hotel httpClient.GetAsync("http://localhost:5003/api/values"); // your busniess code // for example save the order to your database return Ok("ok"); }

请求结果返回"ok",结果我们看看数据记录的是什么东西,相信也是大家比较关心的,看懂了数据库也就了解了分布式事务的阶段性事件存储,下面直接上图:

db-txevents.png

我们从上图很明显的就能看出来服务的先后关系以及服务之间的依赖关系,同时LocalTxId标记了每个一个过程的唯一ID,其中[type]需要注重说明一下,标记了每个动作了的状态同时也是判断每个微服务是否成功是否需要补偿的重要标准( 特别说明: sample项目中的预定车辆以及预定酒店是模拟操作,具体可以参见各自项目代码 )

节点服务异常情况

这里我为什么要说节点服务异常呢?相信经历过微服务的同学就知道,错综复杂的服务之间的调用,就会增加耦合以及某个节点服务出现异常导致整个调用连失败的情况,所以基于如此我们下面测试每个阶段所带来的情况分析

        [HttpGet, SagaStart]
        [Route("book1")] public ActionResult Book1() { // throw new a exception for test throw new DbUpdateException("I'm a dbUpdateException", new Exception()); // init basic httpclient var httpClient = new HttpClient(); // mark a reservation of car httpClient.GetAsync("http://localhost:5002/api/values").Wait(); // book a hotel httpClient.GetAsync("http://localhost:5003/api/values").Wait(); return Ok("ok"); }

这里只是记录[Book1]本身服务的生命周期,因为还没有请求car和hotel,下面截图也验证我的预期结果:

test-sagastart-exception

测试Car-Service调用异常

这里需要特别说明的是,需要 httpClient 等待微服务调用结果,这样car-service出现调用异常,我们的框架才会感知到,才会上报通知事务管理,最后终止或者回滚事务链条

Omega.Sample.Booking -> ValuesController:

        [HttpGet, SagaStart]
        [Route("book2")] public ActionResult Book2() { // init basic httpclient var httpClient = new HttpClient(); // mark a reservation of car , this will be throw a exception from car-service httpClient.GetAsync("http://localhost:5002/api/values").Wait(); // book a hotel httpClient.GetAsync("http://localhost:5003/api/values").Wait(); return Ok("ok"); }

Omega.Sample.Car -> ValuesController :

        [HttpGet]
        public IEnumerable<string> Get()
        {
            CarBookingService carBookingService = new CarBookingService(); var carbook = new CarBooking() { Id = 1, Amount = 1, Name = "WithLin" }; carBookingService.Order(carbook); return new string[] { "value1", "value2" }; }

Omega.Sample.Car -> CarBookingService:

    public class CarBookingService
    {
        private readonly ConcurrentDictionary<int, CarBooking> _bookings = new ConcurrentDictionary<int, CarBooking>(); [Compensable(nameof(CancelCar))] public void Order(CarBooking carBooking) { carBooking.Confirm(); _bookings.TryAdd(carBooking.Id, carBooking); // throw new Exception throw new Exception("test car serivice error"); } void CancelCar(CarBooking booking) { _bookings.TryGetValue(booking.Id, out var carBooking); carBooking?.Cancel(); } }

果不其然我们的[Book2]方式直接向我们抛出了异常,上图说明:

car-service-exception.jpg

那么再来看看数据是否和我们预期感觉一样讷,相信聪明的小伙伴应该知道套路是什么了:

CarAbortDatabase.png

关于 car-service 的红框状态描述相信大家就很清楚了,历经了开始->中止->结束,最后整个 Booking 方式完成

测试Hotel-Service调用异常

预期调用 Hotel-Service 异常,但是我们的 car-service 调用成功,这个时候我们需要 car-service 通过补偿的方式撤销调用 car-service 带来的数据或者状态的变化,达到要么全部成功,要么全部失败的结果,实现最终一致性

Omega.Sample.Booking -> ValuesController:

        [HttpGet, SagaStart]
        [Route("book")] public async Task<ActionResult> Book() { // init basic httpclient var httpClient = new HttpClient(); // mark a reservation of car await httpClient.GetAsync("http://localhost:5002/api/values"); // book a hotel await httpClient.GetAsync("http://localhost:5003/api/values"); return Ok("ok"); }

Omega.Sample.Car -> ValuesController :

        [HttpGet]
        public IEnumerable<string> Get()
        {
            CarBookingService carBookingService = new CarBookingService(); var carbook = new CarBooking() { Id = 1, Amount = 1, Name = "WithLin" }; carBookingService.Order(carbook); return new string[] { "value1", "value2" }; }

Omega.Sample.Car -> CarBookingService:

这里需要特别说明 [Compensable(nameof(CancelCar))] 此标记是指示补偿或者回滚时的方法,当然它的执行是在事务官发起通知的时候执行,具体参考下图断点命中

    public class CarBookingService
    {
        private readonly ConcurrentDictionary<int, CarBooking> _bookings = new ConcurrentDictionary<int, CarBooking>(); [Compensable(nameof(CancelCar))] public void Order(CarBooking carBooking) { carBooking.Confirm(); _bookings.TryAdd(carBooking.Id, carBooking); //throw new Exception("test car serivice error"); } void CancelCar(CarBooking booking) { _bookings.TryGetValue(booking.Id, out var carBooking); carBooking?.Cancel(); } }

Omega.Sample.Hotel -> ValuesController :

        [HttpGet]
        public IEnumerable<string> Get()
        {
            HotelBookingService bookingService = new HotelBookingService(); HotelBooking hotelBooking = new HotelBooking() { Id = 1, Amount = 10, Name = "test" }; bookingService.Order(hotelBooking); return new string[] { "value1", "value2" }; }

Omega.Sample.Hotel -> HotelBookingService:

注意这里因为 booking.Amount > 2 将会触发异常导致服务调用错误

        [Compensable(nameof(CancelHotel))]
        public void Order(HotelBooking booking) { if (booking.Amount > 2) { throw new ArgumentException("can not order the rooms large than two"); } booking.Confirm(); _bookings.TryAdd(booking.Id, booking); }

Car-Service 补偿方法触发执行命中断点如下图:

DebugPicture.png

数据库事务链条状态图(聪明的你观察到了那个补偿事件的记录了讷):

HotleAbort-Scenario.png

测试Booking-Service 最后调用异常

这里我们最后再来测试一下,如下情况,也是前面的 car-service hotel-service 调用都没有问题,但是最后 booking-service 提交的出现了未知异常(例如网络抖动.数据库闪断之类),代码我就不贴太多了,展示 booking-service 即可

        [HttpGet, SagaStart]
        [Route("book")] public async Task<ActionResult> Book() { // init basic httpclient var httpClient = new HttpClient(); // mark a reservation of car (no exception) await httpClient.GetAsync("http://localhost:5002/api/values"); // book a hotel (no exception) await httpClient.GetAsync("http://localhost:5003/api/values"); throw new Exception("just test unknown exception"); return Ok("ok"); }

理所当然的如下图所示:

关于 Command 表大致用来存储需要补偿的命令的,相应的alpha-server有定时服务在刷这个表,一直补偿成功为止,来保证最终的分布式事务的一致性

CarAndCompensate.png

相信你已经注意到了我们的两个预定服务都触发了补偿逻辑(红框所示)

BookingAbortDatabase.png

如何在服务超时情况维持分布式事务测试

[HttpGet, SagaStart(TimeOut = 3)]
        [Route("book3")] public ActionResult Book3() { // init basic httpclient var httpClient = new HttpClient(); // mark a reservation of car , this will be throw a exception from car-service httpClient.GetAsync("http://localhost:5002/api/values").Wait(); // book a hotel httpClient.GetAsync("http://localhost:5003/api/values").Wait(); Thread.Sleep(5000); return Ok("ok"); }

事件表详情 timeoutdatabase-txEvent.png 超时表详情 timeoutdatabase-txEvent.png 补偿表详情 timeoutdatabase-txCompenste.png

Guess you like

Origin www.cnblogs.com/mschen/p/11856558.html