SpringCloud~

帮你轻松入门SpringCloud~


1 微服务概述

1.1什么是微服务

如idea中使用maven建立的一个个moudle,它具体是使用SpringBoot开发的一个小模块,专业的事交给专业的模块来做,每个模块完成一个具体的任务或功能。


1.2 什么是微服务架构

它将单一应用程序划分成一组小的服务,服务之间相互协调,相互配合,服务与服务之间采用轻量级的通信机制(如Rest(基于HTTP)、RPC(dubbo))互相协作,每个服务都围绕着具体的业务进行构建,并且能够独立部署在生产环境中(一个个jar包(springboot)),每个服务可以选择自己合适的语言、工具对其进行构建


1.3 微服务优缺点

优点:

1.单一职责原则;

2.服务之间低耦合,服务内部高内聚;

3.开发简单,效率高,一个服务专一的干一件事;

4.每个服务在开发阶段或部署阶段都是独立的,可以有自己的数据库

5.每个服务可以选择使用不同的语言开发;

6.微服务只是业务逻辑的代码,不会和HTML、CSS或其他的页面混合;


缺点:

1.开发人员要处理分布式系统的复杂性;

2.多个服务运维,运维成本压力增大;

3.系统部署依赖问题;

4.服务间通信成本问题;

5.数据一致性问题;

6.系统集成测试问题;

7.性能和监控问题;

1.4 微服务技术栈




2 SpringCloud(Spring官网:https://spring.io/)

2.1 SpringBoot和SpringCloud的关系

    1.SpringBoot专注于开发单个个体微服务

     2.SpringCloud是一种生态,关注全局的微服务协调整理治理的框架,它将SpringBoot开发的一个个单体微服务,整合并管理起来,为各个微服务提供:配置管理、服务发现、断路器、路由、为代理、事件总栈、全局锁、决策竞选、分布式会话等等集成服务;

     3.SpringBoot可以离开SpringCloud独立使用,开发项目,但SpringCloud离不开SpringBoot,属于依赖关系;



2.3 Dubbo和SpringCloud技术选型

     1.Dubbo关注的领域是SpringCloud的一个子集。专注于服务的治理,其在服务治理、灰度发布、流量分发方面比SpringCloud更全面。但SpringCloud是一种生态,覆盖整个微服务领域。

     2.Dubbo使用RPC调用效率高一些,SpringCloud使用REST(HTTP)调用效率低,使用更简单。


     扩展RPC和REST

     1.RPC原理:socekt+动态代理,自定义协议传输,性能高,就像调用本地方法一样简单

     2.REST原理:HTTP,性能不如RPC,耦合性低,不用关心接口实现细节,相对更规范,更标准,跨语言支持

     3.选择:

RPC使用于内网服务调用,对外提供服务请走REST。

      IO密集的服务调用用RPC,低频服务用REST。

      服务调用过于密集与复杂,RPC就比较使用


2.2 SpringCloud能干嘛?

     1. 分布式/版本控制配置(父工程中的依赖版本管理)

     2.服务注册与发现(eureka)

    3. API网关,路由(zuul)

     4. 服务到服务的调用(REST)

     5.负载均衡配置(ribbon、feign)

     6.断路器(hystrix)

     7.分布式消息管理(可以集成MQ服务)

     8.监控(dashboard 流监控)

     9.统一配置(springCloud-Config远程读取git仓库)

     …



2.3 SpringCloud版本选择





3 EureKa服务注册中心(类似Dubbo的注册中心zooKeeper)

3.1 基本原理

     1.Eureka([juˈriːkə])采用了C-S(客户端-服务端)的架构设计,EurekaServer作为服务注册功能的服务器,他是服务注册中心;

     2.而系统中的其他微服务,使用Eureka的客户端连接到EurekaServer并维持心跳连接。这样系统的维护人员就可以通过EurekaServer(有监控页面)来监控各个微服务运行是否正常,SpringCloud的一些其他模块(如zuul)就可以通过EurekaServer来发现系统中的其他微服务,执行相关的逻辑。


     3.与Dubbo的架构对比


  4. EurekaServer提供服务注册,各个节点启动后,会在EurekaServer中进行注册,其服务注册表中将会储存所有的服务节点的信息。

  5. EurekaClient是一个java客户端,用户简化EurekaServer的交互,客户端同时具备一个默认是轮询负载算法的负载均衡器。在应用启动后,将会向EurekaServer发送心跳(默认周期为30s)。如果EurekaServer在多个心跳周期内没有接收到某个节点的心跳,EurekaServer将会移除这个服务节点(默认周期为90s,开启自动保护机制会保留节点的信息)

3.2 自我保护机制(好死不如赖活着)

一句话总结:某时刻某一个微服务不可用,eureka不会立刻清理,依旧会对该微服务的信息进行保存!

[if !supportLists]1.    [endif]默认情况下,当EurekaServer在一定时间内没有收到节点实例的心跳,便会把该实例从注册表中删除(默认是90秒),但是短时间内丢失大量的实例心跳,便会触发EurekaServer的自我保护机制,在Eureka管理界面会出现红色的警告。eureka认为虽然收不到实例的心跳,但它认为实例还是健康的,eureka会保护这些实例,不会把它们从注册表中删掉。

[if !supportLists]2.    [endif]该保护机制的目的是避免网络连接故障,在发生网络故障时,微服务和注册中心之间无法正常通信,但服务本身是健康的,不应该注销该服务,如果eureka因网络故障而把微服务误删了,那即使网络恢复了,该微服务也不会重新注册到eureka

server了,因为只有在微服务启动的时候才会发起注册请求,后面只会发送心跳和服务列表请求,这样的话,该实例虽然是运行着,但永远不会被其它服务所感知。所以,eureka server在短时间内丢失过多的客户端心跳时,会进入自我保护模式,该模式下,eureka会保护注册表中的信息,不在注销任何微服务,当网络故障恢复后,eureka会自动退出保护模式。自我保护模式可以让集群更加健壮。

[if !supportLists]3.    [endif]但是我们在开发测试阶段,需要频繁地重启发布,如果触发了保护机制,则旧的服务实例没有被删除,这时请求有可能跑到旧的实例中,而该实例已经关闭了,这就导致请求错误,影响开发测试。所以,在开发测试阶段,我们可以把自我保护模式关闭,只需在eureka server配置文件中加上如下配置即可:eureka.server.enable-self-preservation=false【不推荐关闭自我保护机制】


3.3 基于注册进来的微服务,获取一些配置信息,得到具体的微服务

服务发现:

1.主启动类中加入@EnableDiscoveryClient注解

2.controller中注入DiscoveryClient


3.4 EureKa 配置(yml)

  1.eureka服务端(先导依赖,再写yml,最后加启动类注解@EnableEurekaServer)


  server:

   port: 7001


# Eureka配置

eureka:

 instance:

    #Eureka服务端的实例名字

   hostname: 127.0.0.1

 client:

    #表示是否向 Eureka 注册中心注册自己(这个模块本身是服务器,所以不需要)

   register-with-eureka: false

    #fetch-registry如果为false,则表示自己为注册中心,客户端的化为ture

   fetch-registry: false

    #Eureka监控页面~

   service-url:

     defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/


 2.eureka客户端(先导依赖,再写yml, 最后加启动类注解@EnableEurekaClient)

  eureka:

 client:

  service-url:

   defaultZone: http://localhost:7001/eureka/


3.实例化Id配置(一个微服务有自己的实例Id,可以配置相同的服务端的application实例名字)

eureka:

 instance:

  instance-id: springcloud-provider-dept8001

  prefer-ip-adress:true#微服务实例显示真实IP


 4.actuator完善监控信息(添加依赖,配置YML)

info:

 app.name: ronin

 company.name: ourway


3.4 EureKa 集群环境配置


1.初始化:

新建sprincloud-eureka-7002、springcloud-eureka-7003模块

1.分别添加依赖

2.yml配置(与上述7001相同)

3.启动类注解都加上@EnableEurekaServer



2.集群成员相互关联

 在三个server模板中的yml配置defaultZone中互相关联其他两个模板

 如7001:

defaultZone:http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/


3.在服务提供者修改服务注册中心地址(把另外两个注册中心加上)

defaultZone:http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/


这样集群就搭建好了,就可以把一个微服务挂在到三个服务器上了


3.5 EureKa与ZooKeeper区别

  1.了解CAP

[if !supportLists]·        [endif]C (Consistency) 强一致性

[if !supportLists]·        [endif]A (Availability) 可用性

[if !supportLists]·        [endif]P (Partition tolerance) 分区容错性

   CAP只能三取二:CA、AP(EureKa)、CP(Zookeeper)

1.一个分布式系统不可能同时很好的满足一致性,可用性和分区容错性这三个需求

2.根据CAP原理,将NoSQL数据库分成了满足CA原则,满足CP原则和满足AP原则三大类

3.CA:单点集群,满足一致性,可用性的系统,通常可扩展性较差

4.CP:满足一致性,分区容错的系统,通常性能不是特别高

5.AP:满足可用性,分区容错的系统,通常可能对一致性要求低一些


2. EureKa保证的是AP

 Eureka各个节点都是平等的,几个节点挂掉不会影响正常节点的工作,剩余的节点依然可以提供注册和查询服务。而Eureka的客户端在向某个Eureka注册时,如果发现连接失败,则会自动切换至其他节点,只要有一台Eureka还在,就能保住注册服务的可用性,只不过查到的信息可能不是最新的,除此之外,Eureka还有之中自我保护机制,如果在15分钟内超过85%的节点都没有正常的心跳,那么Eureka就认为客户端与注册中心出现了网络故障,此时会出现以下几种情况:


1.Eureka不在从注册列表中移除因为长时间没收到心跳而应该过期的服务

2.Eureka仍然能够接受新服务的注册和查询请求,但是不会被同步到其他节点上 (即保证当前节点依然可用)

3.当网络稳定时,当前实例新的注册信息会被同步到其他节点中


3. Zookeeper保证的是CP

当向注册中心查询服务列表时,我们可以容忍注册中心返回的是几分钟以前的注册信息,但不能接收服务直接down掉不可用。也就是说,服务注册功能对一致性的要求要高于可用性。但zookeeper会出现这样一种情况,当master节点因为网络故障与其他节点失去联系时,剩余节点会重新进行leader选举。问题在于,选举leader的时间太长,30-120s,且选举期间整个zookeeper集群是不可用的,这就导致在选举期间注册服务瘫痪。在云部署的环境下,因为网络问题使得zookeeper集群失去master节点是较大概率发生的事件,虽然服务最终能够恢复,但是,漫长的选举时间导致注册长期不可用,是不可容忍的。


因此,Eureka可以很好的应对因网络故障导致部分节点失去联系的情况,而不会像zookeeper那样使整个注册服务瘫痪

 

 

4 Ribbon负载均衡(基于客户端)

4.1 Ribbon

  前话:我们一个注册中心的某个实例中,会有不同的机器来提供这个服务(不同的数据库,不同的微服务注册成同一个eureka实例)


 Ribbon会自动的帮助我们基于某种规则(负载均衡算法,如轮询,随机(hash)负载均衡算法可以自定义(IRule接口))去连接那些注册中心的微服务机器。

负载均衡简单分类:

1.集中式LB(即负载均衡(LoadBalancer) )

即在服务的提供方和消费方之间使用独立的LB设施,如Nginx(反向代理服务器),由该设施负责把访问请求通过某种策略转发至服务的提供方!

进程式LB

2.将LB逻辑集成到消费方,消费方从服务注册中心获知有哪些地址可用,然后自己再从这些地址中选出一个合适的服务器。

Ribbon 就属于进程内LB,它只是一个类库,集成于消费方进程,消费方通过它来获取到服务提供方的地址!

 

4.2 集成Ribbon

 1.消费者客户端添加依赖Ribbon和Eureka(ribbon要从注册中心获取实例)依赖

 2.主启动类加上@EnableEurekaClient

 3.自定义Spring配置类@Configuration,注入RestTemplate类,并在其方法上加上@loadBalanced (因为springcould服务之间是通过rest来通信的)

 4.修改controller,直接通过服务名(注册中心实例名application)来访问



4.3 自定义负载均衡算法

 1.在上述4.2.3的自定义配置类中,注入IRule 类:

   IRule:

    * RoundRobinRule轮询策略

    * RandomRule随机策略

    * AvailabilityFilteringRule:会先过滤掉,跳闸,访问故障的服务~,对剩下的进行轮询

     * RetryRule:会先按照轮询获取服务,如果服务获取失败,则会在指定的时间内进行,重试

   @Bean

   Public IRule myRule(){

      return new MyRule(); //使用我们自定义的算法类

}


注意:该包不要和主启动类的包同级,要跟启动类所在包同级


2. 主启动类开启负载均衡并指定自定义的MyRule配置类

  @RibbonClient(name="SPRINGCLOUD-PROVIDER-DEPT",configuration=MyRule.class)


3.自定的规则(参考ribbon默认的代码规则自己稍有改动)

public class MyRandomRule extends AbstractLoadBalancerRule {}


5 Feign负载均衡(类似controller调用service)

调用微服务访问两种方法

1.微服务名字 【ribbon】

2.接口和注解 【feign】(默认集成了ribbon,并且通过轮询实现了客户端的负载均衡)


5.1 使用Feign

还是老套路

[if !supportLists]1.    [endif]引入pom依赖(openfeign)

[if !supportLists]2.    [endif]配置文件修改(这里不需要修改)

[if !supportLists]3.     [endif]创建一个service接口,通过@FeignClient("服务名"),以及@RequestMapping(value="服务提供url",method=url类型(RequestMethod.GET等))。

[if !supportLists]4.    [endif]主启动类添加注解(@EnableXXXX)

// feign客户端注解,并指定要扫描的包以及配置接口DeptClientService

@EnableFeignClients(basePackages = {"com.haust.springcloud"})

// 切记不要加这个注解,不然会出现404访问不到

//@ComponentScan("com.haust.springcloud")

[if !supportLists]5.    [endif]当前controller直接调用上述3接口直接获取微服务接口即可实现业务



[if !supportLists]5.2     [endif]Feign和Ribbon该如何选择?根据个人习惯而定,如果喜欢REST风格使用Ribbon;如果喜欢社区版的面向接口风格使用Feign.


6 HyStrix 服务熔断

复杂分布式体系结构中的应用程序有数十个依赖关系,每个依赖关系在某些时候将不可避免失败!

6.1 服务雪崩

  多个微服务之间调用的时候,假设微服务A调用微服务B和C,微服务B和C又调用其他的微服务,这就是所谓的“扇出”。如果扇出的链路上某个微服务的调用响应时间过长,或者不可用,对微服务A的调用就会占用越来越多的系统资源,进而造成后面微服务B和C的崩溃,最终系统崩溃,这就是所谓的“雪崩效应”

   解决方案:需要对故障和延迟进行隔离和管理,以达到单个依赖关系的失败而不影响整个应用程序或系统运行,需要顾全大局,弃车保帅!


6.2 什么是HyStrix?(断路器)

   HyStrix是一个应用于处理分布式系统的延迟和容错的开源库,在某个服务单元出现问题的情况下,向调用方返回一个服务预期的,可处理的备选响应(FallBack),而不是长时间的等待或者抛出调用方法无法处理的异常。

6.3 HyStrix之服务熔断

当扇出链路的某个微服务不可用或响应时间太长时,会进行服务的降级,进而熔断该节点微服务的调用,快速返回错误的响应信息(FallBack)。检测到该节点微服务调用响应正常后恢复调用链路。Hystrix会监控微服务间调用的状况,当失败的调用到一定阀值5秒内20次调用失败,就会启动熔断机制。(@HyStrixCommand)

服务熔断很好的解决了服务雪崩的问题,起到快速失败的结果;快速失败后,能够根据一定的算法动态试探所依赖对象是否恢复。


入门实例(老套路)

[if !supportLists]1.    [endif]导入hystrix依赖

[if !supportLists]2.    [endif]配置文件修改(这里不需要修改)

[if !supportLists]3.    [endif]controller方法上加上@HyStrixCommand(fallbackMethod=”备选方法名”)

[if !supportLists]4.    [endif]编写备选方法

[if !supportLists]5.     [endif]主启动类添加熔断注解@EnableCircuitBreaker


因此,为了避免因某个微服务后台出现异常或错误而导致整个应用或网页报错,使用熔断是必要的!

 

6.4 HyStrix之服务降级

服务降级是指 当前服务器压力剧增的情况下,根据实际业务情况及流量,对一些服务和页面有策略的不处理,从而释放服务器资源以保证核心业务正常运作或高效运作。

 尽可能把系统资源让给优先级高的服务

     使用场景:当整个微服务架构整体的负载超出了预设的上线阀值或者即将到来的流量预计讲过超过预设的阀值时,为了保证重要的或基本的服务能正常运行,将一些不重要或不紧急的任务进行服务的延迟使用或暂停使用,如一件商品访问量剧增,可关闭相关商品的推荐。

     服务降级需要考虑的问题:

[if !supportLists]1. [endif]哪些服务是核心服务,非核心业务

[if !supportLists]2. [endif]哪些服务可以支持降级,降级策略是什么

[if !supportLists]3. [endif]除服务降级之外是否存在更复杂的业务方通背景,策略是什么


     自动降级分类:

     1.超时降级:主要配置超时时间和超时重试次数和机制,使用异步机制探测恢复情况

     2.失败次数降级:主要是一些不稳定的api,当失败调用次数达到一定阀值自动降级

     3.故障降级:比如要调用的远程服务挂掉了(网络故障,DNS故障,rpc服务抛出异常),则可以直接降级。降级后的处理方案有:默认值(比如库存服务挂了,返回默认现货)、兜底数据(比如广告服务挂了,返回提前准备好的一些静态页面)、缓存(之间暂存的一些缓存数据)

     4.限流降级:秒杀或抢购一些商品时,此时可能会因为访问量太大而导致系统崩溃,此时会使用限流来进行限制访问量,当达到限流阀值,后续请求会被降级。降级后的处理方案有:排队页面(将用户导流到排队页面等一会重试)、无货(直接告诉用户没货了)、错误页(如活动太火爆了,稍后重试)


     入门案例

[if !supportLists]1.    [endif]在客户端新建降级配置类(implements FallbackFactory接口)

[if !supportLists]2.    [endif]在feign的接口类中指定降级配置类

@FeignClient(value="SPRINGCLOUD-PROVIDER-DEPT",fallbackFactory=DeptClientServiceFallBackFactory.class)//fallbackFactory指定降级配置类

[if !supportLists]3.    [endif]在yml中配置降级

fegin:

 hystrix:

  enabled: true


6.5 服务熔断和降级的区别

1.服务熔断-》服务端:某个服务超时或异常,引起熔断,类似保险丝(自我熔断)

2.服务降级-》客户端:从整体网站请求负载考虑,当某个服务熔断或关闭之后,服务将不再被调用,此时在客户端我们可以准备一个FallbackFactory,返回一个默认的值(缺省值)

3.实现方式不太一样,服务降级具有代码侵入性(由控制器完成/或自动降级),熔断一版称为自我熔断


限流:限制并发的请求访问量,超过阀值则拒绝;

降级:服务分优先级,牺牲非核心业务(不可用),保证核心服务稳定;从整体负荷考虑

熔断:依赖的下游故障触发熔断,避免系统发生崩溃;使用备份策略,系统自动执行和恢复


6.6 HyStrix Dashboard流监控(独立服务:监控其他服务的状态)

   1.编写Dashboard服务

     1)导入依赖(hystrix-dashboard)

     2)主启动类中添加@EnableHystrixDashboard

 3)访问自己Dashboard的页面


2.在其他服务中

 1)主启动类中添加监控(增加一个servlet,死代码)

   @Bean

    public ServletRegistrationBeanhystrixMetricsStreamServlet(){

        ServletRegistrationBeanregistrationBean = new ServletRegistrationBean(new HystrixMetricsStreamServlet());

        //访问该页面就是监控页面

       registrationBean.addUrlMappings("/actuator/hystrix.stream");

        return registrationBean;

    }


[if !supportLists]4.    [endif]在Dashboard页面输入“配置监控服务的ip+端口”+/actuator/hystrix.stream进行监控


[if !supportLists]5.    [endif]效果如下图


7 Zuul 路由网关

7.1 Zuul


Zull包含了对请求的路由(用来跳转的)和过滤两个最主要功能:

[if !supportLists]1.   [endif]路由:将外部的请求转发到具体的微服务实例上,是实现外部访问统一入口的基础。

[if !supportLists]2.    [endif]过滤:对请求的处理过程进行干预,是实现请求校验,服务聚合等功能的基础


  Zuul和Eureka进行整合,将Zuul自身注册为Eureka服务治理下的应用,同时从Eureka中获得其他服务的信息,也即以后访问的微服务都是通过Zuul跳转后获得


7.2 Zuul 示例

1.导入依赖(也要导入eureka依赖)

2.配置YML文件

 zuul:

      routes:

       mydept(自定义名称).serviceId:

springcloud-provider-dept #eureka注册中心服务实例名application

        mydept.path: /mydept/**

      ignored-services: “*”  #忽略,隐藏全部的服务名称,只能从上述路由进去

      #设置公共的前缀

      Prefix: /zhang


# 原来访问路由eg:http://www.cspStudy.com:9527/springcloud-provider-dept/dept/get/1

  # zull路由配置后访问路由eg:http://www.cspstudy.com:9527/zhang /mydept/dept/get/1


     3.主启动类中添加开启zuul注解@EnableZuulProxy


8 Config 分布式远程配置(git)

   这么多的微服务,每个服务都有必要的配置信息,使用Config 服务,集中式,动态管理配置,服务器存储后端默认实现使用git,因此它轻松支持标签版本的配置环境(dev,test,…),运维人员可以通过链条git来更改版本,不必去依次动每个服务



同样,springcloud config也分为服务端和客户端两部分:

  1.服务端也称为分布式配置中心,它是一个独立的微服务应用,用来连接配置服务器并为客户端提供获取配置信息,加密,解密信息等访问接口。


  2.客户端则是通过指定的配置中心来管理应用资源,以及与业务相关的配置内容,并在启动的时候从配置中心获取和加载配置信息。配置服务器默认采用git来存储配置信息,这样就有助于对环境配置进行版本管理。并且可用通过git客户端工具来方便的管理和访问配置内容。


能干嘛?

1.集中式管理配置文件

2.不同环境,不同配置,动态化的配置更新,分环境部署,比如/dev /test/prod /beta /release

3.运行期间动态调整配置,不再需要在每个服务部署的机器上编写配置文件,服务会向配置中心统一拉取配置自己的信息

4.当配置发生变动时,服务不需要重启,即可感知到配置的变化,并应用新的配置(热部署只要rebuild)

5.将配置信息以REST接口的形式暴露


8.1 Config示例

   1.新建config server (导入依赖,更改配置,主启动加上@EnableConfigServer)

 server:

  port: 3344


spring:

  application:

    name:springcloud-config-server

  #连接码云远程仓库

  cloud:

    config:

      server:

        git:

          #注意是https的而不是ssh

          uri:https://gitee.com/cao_shi_peng/springcloud-config.git

            #通过 config-server可以连接到git,访问其中的资源以及配置~


# 不加这个配置会报Cannot execute request on any

known server 这个错:连接Eureka服务端地址不对

# 或者直接注释掉eureka依赖 这里暂时用不到eureka

eureka:

  client:

    register-with-eureka:false

    fetch-registry: false


2. HTTP服务具有以下格式的资源:(label为分支名)

   /{application}/{profile}[/{label}]

/{application}-{profile}.yml

/{label}/{application}-{profile}.yml

/{application}-{profile}.properties

/{label}/{application}-{profile}.properties


[if !supportLists]3.    [endif]各个服务作为客户端去获取config server的配置信息

[if !supportLists]1.    [endif]导入依赖

[if !supportLists]2.    [endif]注意!这里导入两个yml文件(有点像类加载器)

bootstrap.yml 是系统级别的配置

# 系统级别的配置

spring:

  cloud:

    config:

      name: config-client #需要从git上读取的资源名称,不要后缀

      profile: dev

      label: master

      uri:http://localhost:3344

application.yml 是用户级别的配置

# 用户级别的配置

spring:

                 application:

      name: springcloud-config-client

      4.controller下通过@value读取配置进行测试

9 怎么保证服务之间的数据一致性

以我做过的门户发布平台举例:

 租户注销需要执行一下几个步骤

 1.更改租户微服务中的状态

 2.注销网站微服务中该租户使用的网站


方案一:

  租户服务中要调网站服务的功能,用的restTemplate,加上普通的事务就可

方案二:

  引入消息中间件,服务之间加一层消息中间件,rabbitMQ中有手动确认接收机制

方案三:如果有多个数据源,这方面spring也可以集成atomiko实现分布式事务。

你可能感兴趣的:(SpringCloud~)