SpringCloud
微服务是系统架构上的一种设计风格, 它的主旨是将一个原本独立 的系统拆分成多个小型服务
这些小型服务都在各自独立的进程中运行,服务之 间通过基于 HTTP 的 RESTful API 进行通信协作;
分布式和微服务区别
分步式和微服务目的不同
分布式架构:访问量很大一台机器承受不了,或者是成本问题,不得不使用多台机器来完成服务的部署;
微服务:各个模块拆分开来,不会被互相影响,比如模块的升级或者出现BUG或者是重构等等都不要影响到其他模块,微服务它是可以在一台机器上部署;
分布式也是微服务的一种,微服务也属于分布式;
分步式常用的是dubbo使用的是RPC(远程调用协议)
微服务常用的是http协议
入门案例
创建父工程依赖
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<!--父工程-->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.2.RELEASE</version>
</parent>
<!--SpringCloud包依赖管理-->
<!-- dependencyManagement标签只负责管理版本,不负责下载-->
<dependencyManagement>
<dependencies>
<!--spring cloud Hoxton.SR1,boot版本必须2.2以上-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.SR1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<!-- 需要另外写dependencies标签-->
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.22</version>
</dependency>
</dependencies>
创建提供者
user-provider
1.准备表结构
2.创建工程
3.引入依赖
4.创建Pojo,需要配置JPA的注解
5.创建Dao,需要继承JpaRepository<T,ID>
6.创建Service,并调用Dao
7.创建Controller,并调用Service
8.创建application.yml文件
9.创建启动类
导入依赖
<!--依赖包-->
<dependencies>
<!--JPA包-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!--web起步包-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--MySQL驱动包-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
</dependencies>
实体类
/**
* ClassName: User
* Description:
* date: 2021/12/14 11:40
* 实体类,除了id,同名字段可以省略
* @author Yee
* @since JDK 1.8
*/
@Table
@Entity(name = "tb_user")
@Data
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;//主键id
private String username;//用户名
private String password;//密码
private String name;//姓名
private Integer age;//年龄
private Integer sex;//性别 1男性,2女性
private Date birthday; //出生日期
private Date created; //创建时间
private Date updated; //更新时间
private String note;//备注
}
业务层UserServiceImpl
// 根据ID查询用户信息
public User findByUserId(Integer id) {
return userDao.findById(id).get();
}
控制层UserController
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
//根据ID查询用户信息
@GetMapping("/{id}")
public User finById(@PathVariable("id") Integer id){
return userService.findByUserId(id);
}
}
application.yml 配置
server:
port: 8888
spring:
datasource: #数据源
url: jdbc:mysql://localhost:3306/springcloud?serverTimezone=UTC
driver-class-name: com.mysql.cj.jdbc.Driver
password: root
username: root
创建消费者
1.创建工程
2.引入依赖
3.创建Pojo
4.创建启动类,同时创建RestTemplate对象,并交给SpringIOC容器管理
5.创建application.yml文件,指定端口
6.编写Controller,在Controller中通过RestTemplate调用user-provider的服务
pom.xml依赖
<!--依赖包-->
<dependencies>
<!--web起步依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
创建User对象,不是实体类
创建启动引导类
UserConsumerApplication
@SpringBootApplication
public class UserConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(UserConsumerApplication.class,args);
}
//创建restTemplate实例,用来请求远程链接
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
application.yml 配置
server:
port: 9999
控制层UserController
@RestController
@RequestMapping("/consumer")
public class UserController {
@Autowired
private RestTemplate restTemplate;
//在user-consumer服务中通过RestTemplate调用user-provider服务
@GetMapping("/{id}")
public User queryById(@PathVariable("id") Integer id){
String url = "http://localhost:8888/user/"+id;
//json会自动转换为实体类
return restTemplate.getForObject(url,User.class);
}
}
案例小结
- 在服务消费者中,我们把url地址硬编码到代码中,不方便后期维护
- 在服务消费者中,不清楚服务提供者的状态
- 服务消费者还需要自己实现负载均衡
注册中心(后期阿里云替代)
启动报错信息不用理,去注册页面看看服务有没有启动
注册中心:服务的管理,注册和发现、状态监管、动态路由。
Eureka与服务之间通过心跳
机制进行监控;提供者定期通过http方式向Eureka刷新自己的状态
依赖
<!--依赖包-->
<dependencies>
<!--eureka-server依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
</dependencies>
application.yml 配置
server:
port: 7001 #端口号
spring:
application:
name: eureka-server # 应用名称,会在Eureka中作为服务的id标识(serviceId)
eureka:
client:
register-with-eureka: false #是否将自己注册到Eureka中
fetch-registry: false #是否从eureka中获取服务信息
service-url:
defaultZone: http://localhost:7001/eureka # EurekaServer的地址
启动类
@SpringBootApplication
@EnableEurekaServer //开启Eureka服务
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class,args);
}
}
提供者服务
1.引入eureka客户端依赖包
2.在application.yml中配置Eureka服务地址
3.在启动类上添加@EnableDiscoveryClient或者@EnableEurekaClient
依赖
<!--eureka客户端-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
配置Eureka服务地址
配置文件中末尾加入
application:
name: user-provider #服务的名字,不同的应用,名字不同,如果是集群,名字需要相同
#指定eureka服务地址
eureka:
client:
service-url:
# EurekaServer的地址
defaultZone: http://localhost:7001/eureka
启动类上添加注解
@SpringBootApplication
@EnableDiscoveryClient //开启Eureka客户端发现功能,注册中心只能是Eureka
消费者-注册服务中心
<!--eureka客户端-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
application.yml中配置
spring:
application:
name: user-consumer #服务名字
#指定eureka服务地址
eureka:
client:
service-url:
# EurekaServer的地址
defaultZone: http://localhost:7001/eureka
启动类上添加注解
@SpringBootApplication
@EnableDiscoveryClient //开启Eureka客户端发现功能,注册中心只能是Eureka
消费者通过Eureka访问提供者
还没搭设集群,当前只有一个服务,使用0下标获取即可
//用来发现注册中心服务对象
@Autowired
private DiscoveryClient discoveryClient;
@GetMapping("/{id}")
public User queryById(@PathVariable("id") Integer id){
// String url = "http://localhost:8888/user/"+id;
//动态获取服务,服务名就是提供者的应用名
List<ServiceInstance> instances = discoveryClient.getInstances("user-provider");
//还没搭设集群,当前只有一个服务,使用0下标获取即可
ServiceInstance serviceInstance = instances.get(0);
String url = "http://"+serviceInstance.getHost()+":"+serviceInstance.getPort()+"/user/"+id;
//json会自动转换为实体类
return restTemplate.getForObject(url,User.class);
}
负载均衡
想要做负载均衡,服务至少2个以上
这里复制提供者模拟多个提供者
user-provider复制一份,名字改成user-provider-demo
修改pox的
<artifactId>user-provider-demo</artifactId>
修改父工程springcloud-parent的pom.xml
添加user-provider-demo1
将user-provider-demo1
的application.yml中的端口改成6666(随意)
记住 ,拷贝的demo那个项目,端口一定要换,但是服务的名字千万不要换
客户端开启负载均衡
在RestTemplate的配置方法上添加@LoadBalanced
注解即可
//创建restTemplate实例
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
不再手动获取ip和端口,而是直接通过服务名称调用
//在user-consumer服务中通过RestTemplate调用user-provider服务
@GetMapping("/{id}")
public User queryById(@PathVariable("id") Integer id){
String url = "http://user-provider/user/"+id;
//json会自动转换为实体类
return restTemplate.getForObject(url,User.class);
}
原理
底层组件LoadBalancerInterceptor根据Service
名称,获取了服务实例ip和端口
这个类会对 RestTemplate
的请求进行拦截,然后从Eureka
根据服务id获取服务列表,随后利用负载均衡算法得到真正服务地址信息,替换服务id。
熔断器
熔断器 Spring Cloud Hystrix
当依赖服务不可用时,当前服务启动自我保护功能,从而避免发生雪崩效应
雪崩效应:
服务资源耗尽,无法继续对外提供服务。服务与服务之间的依赖性,故障会传播,
会对整个微服务系统造成不可估量的严重后果
user-consumer加入依赖
<!--熔断器-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
启动类加入注解
@SpringCloudApplication //复合注解
已经包含以下三个注解
- @SpringBootApplication
- @EnableDiscoveryClient //开启Eureka客户端发现功能
- @EnableCircuitBreaker //开启熔断
服务降级处理
某个方法发生故障,则返回默认的数据给用户,此时叫服务降级。
其他方法可用,该方法不可用 ,只是降低权限
局部方法熔断
消费者控制器添加方法,名字随意
//服务降级处理方法
//熔断方法的返回值和参数列表需和原方法一致,否则报错
public User failBack(Integer id){
User user = new User();
user.setUsername("服务熔断");
return user;
}
在有可能发生问题的方法上添加降级处理调用
@HystrixCommand(fallbackMethod = "failBack") //注入降级方法
全局方法熔断
消费者控制类上添加注解和方法
@DefaultProperties(defaultFallback="failBack") //在类上,指明统一的失败降级方法
//服务降级处理方法
//类注解方法不需要参数
public User failBack(){
User user = new User();
user.setUsername("服务熔断");
return user;
}
Cloud Feign
-
集成Ribbon的负载均衡功能
-
集成了Hystrix的熔断器功能
-
支持请求压缩
-
大大简化了远程调用的代码,同时功能还增强啦
-
Feign以更加优雅的方式编写远程调用代码,并简化重复代码
- 替代RestTemplate
user-consumer
中添加依赖
<!--配置feign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
创建Feign客户端
user-consumer创建接口
//提供者名称,第二个参数是回调函数
@FeignClient(value = "user-provider",fallback = UserClientFallback.class)
public interface UserClient {
//根据ID查询用户信息,这里把提供者方法写全
@GetMapping("/user/{id}")
public User finById(@PathVariable("id") Integer id);
}
编写控制层
@RestController
@RequestMapping("/feign")
public class ConsumerFeignController {
@Autowired
private UserClient userClient;
@GetMapping("/{id}")
public User queryById(@PathVariable(value = "id")Integer id){
return userClient.finById(id);
}
}
开启Feign
user-consumer
的启动类添加@EnableFeignClients
注解
熔断器支持
user-consumer
配置文件 开启 feign
熔断器支持
feign:
hystrix:
enabled: true # 开启Feign的熔断功能
熔断降级类创建
user-consumer
,创建一个类实现刚才编写的UserClient(feign客户端)
@Component
public class UserClientFallback implements UserClient {
@Override
public User finById(Integer id) {
User user = new User();
user.setUsername("服务熔断");
return user;
}
}
Cloud Gateway
微服务架构提供一种简单有效统一的API路由管理方式。替代Netflix Zuul
创建一个子工程 gateway-service
<dependencies>
<!--网关依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!-- Eureka客户端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
</dependencies>
启动类
/**
* ClassName: GatewayApplication
* Description:
* date: 2021/12/16 9:34
* 对尤里卡来说,网关也是一个微服务,也有注册
* @author Yee
* @since JDK 1.8
*/
@SpringBootApplication
@EnableDiscoveryClient
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class,args);
}
}
application.yml
配置
server:
port: 4444
eureka:
client:
service-url:
defaultZone: http://localhost:7001/eureka
spring:
application:
name: gateway-service
网关配置一个路由功能和负载均衡
如果用户请求的路径是以/user
开始,则路由到user-provider
服务去
spring:
cloud:
gateway:
routes:
- id: service-route
uri: lb://user-provider #lb=Load Balance,填写集群名
predicates: # 当满足这种条件后才会被转发
- Path=/user/** # 路由拦截的地址配置(断言)
-
Route(路由)
:路由是网关的基本单元,由ID、URI、一组Predicate、一组Filter组成,根据Predicate进行匹配转发。 -
Predicate(谓语、断言)
:路由转发的判断条件 -
Filter(过滤器)
:过滤器是路由转发请求时所经过的过滤逻辑,可用于修改请求、响应内容。 -
不管是来自客户端的请求,还是服务内部调用。一切对服务的请求都可经过网关。
-
网关实现鉴权、动态路由等等操作。
-
Gateway就是我们服务的统一入口
配置中心
Nacos介绍,注册中心+配置中心的组合
替代Eureka做服务注册中心
替代Config做服务配置中心
Eureka已经停止更新,nacos是替代它的
下载地址: https://github.com/alibaba/nacos/releases/tag/1.1.4
进入bin 目录,双击启动 nacos
请求地址:http://localhost:8848/nacos
用户名和密码都是:nacos
入门案例
创建父工程cloud-alibaba
<packaging>pom</packaging>
<!-- 统一管理jar包版本 -->
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<junit.version>4.12</junit.version>
<log4j.version>1.2.17</log4j.version>
<lombok.version>1.16.18</lombok.version>
</properties>
<!-- 子模块继承之后,提供作用:锁定版本+子modlue不用写groupId和version -->
<dependencyManagement>
<dependencies>
<!--spring boot 2.2.2-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.2.2.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--spring cloud Hoxton.SR1-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.SR1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--spring cloud alibaba 2.1.0.RELEASE-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.1.0.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>${log4j.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
<optional>true</optional>
</dependency>
</dependencies>
</dependencyManagement>
提供者
provider-payment9001
<dependencies>
<!--SpringCloud ailibaba nacos -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- SpringBoot整合Web组件 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--日常通用jar包配置-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
创建application.yml
配置文件
server:
port: 9001
spring:
application:
name: nacos-server # 应用名称
cloud:
nacos:
discovery:
server-addr: localhost:8848 #配置Nacos的注册中心地址
management:
endpoints:
web:
exposure:
include: '*' #开启监控
启动类PaymentMain9001
@SpringBootApplication
@EnableDiscoveryClient
public class PaymentMain9001 {
public static void main(String[] args) {
SpringApplication.run(PaymentMain9001.class,args);
}
}
创建 控制类PaymentController
@RestController
@RequestMapping("/payment")
public class PaymentController {
@Value("${server.port}")
private String serverPort;
@GetMapping("/{id}")
public String getPayment(@PathVariable("id") Integer id){
return "serverPort: "+ serverPort+" id"+id;
}
}
创建提供者2
--为集群做准备
复制第一个提供者改名为provider-payment9002
pom文件修改名字
<artifactId>provider-payment9002</artifactId>
配置文件修改端口号
server:
port: 9002
父工程
pox文件添加
<module>provider-payment9002</module>
创建消费者
consumer-nacos项目
修改pom
<dependencies>
<!--SpringCloud ailibaba nacos -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- SpringBoot整合Web组件 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--日常通用jar包配置-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
application.yml
文件
server:
port: 83
spring:
application:
name: consumer-nacos
cloud:
nacos:
discovery:
server-addr: localhost:8848
service-url: #自己自定义注入的字段
nacos-user-service: http://nacos-server
创建启动类
@SpringBootApplication
@EnableDiscoveryClient
public class NacosMain83 {
public static void main(String[] args) {
SpringApplication.run(NacosMain83.class,args);
}
}
创建 配置类ApplicationContextConfig
@Configuration
public class ApplicationContextConfig {
@Bean
@LoadBalanced
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
}
创建配置中心项目config-nacos
pom文件
<dependencies>
<!--nacos-config-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!--nacos-discovery-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--web + actuator-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--一般基础配置-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
配置 bootstrap.yml
文件---全局配置
#系统级别配置文件
server:
port: 3377
spring:
application:
name: config-nacos # 应用程序名
cloud:
nacos:
discovery:
server-addr: localhost:8848 #Nacos服务注册中心地址
config: # 配置中心取名规则:配置应用名+环境+后缀 config-nacos-dev.yaml
server-addr: localhost:8848 #Nacos作为配置中心地址
file-extension: yaml #指定yaml格式的配置 , 从nacos配置中心读取yaml配置文件
配置 application.yml 文件
spring:
profiles:
active: dev # 表示开发环境
#active: test # 表示测试环境
#active: product # 表示生产环境
配置启动类ConfigClientMain3377
@SpringBootApplication
@EnableDiscoveryClient
public class ConfigClientMain3377 {
public static void main(String[] args) {
SpringApplication.run(ConfigClientMain3377.class,args);
}
}
配置控制类
/**
* ClassName: ConfigClientController
* Description:
* date: 2021/12/16 11:12
* 在数据中心配置yaml文件,可以远程获得数据
* @author Yee
* @since JDK 1.8
*/
@RestController
@RefreshScope //支持nacos动态刷新功能
@RequestMapping("/config")
public class ConfigClientController {
//获得配置中心数据,通过这个控制器可以获得在配置中心设置的yaml文件配置
@Value("${config.info}")
private String configInfo;
@GetMapping
public String getConfigInfo() {
return configInfo;
}
}
在Nacos中添加配置信息
左边配置列表,右边加号
id:应用的名字-环境-后缀
配置格式yaml
自定义格式
分布式事务
事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于不同的分布式系统的不同节点之上,且属于不同的应用
分布式事务就是为了保证不同数据库的数据一致性。
CAP定理
**C (一致性):**所有节点在同一时间具有相同的数据
**A (可用性):**服务必须一直处于可用的状态
**P (网络分区容错性):**网络节点之间无法通信的情况下,节点被隔离,产生了网络分区,分区容错的意思是,区间通信可能失败
- CAP原则的就是要么AP,要么CP,要么AC,但是不存在CAP。不能同时满足三个特性
- 分布式系统设计中AP的应用较多
- 满足可用性和分区容错,当出现分区,同时为了保证可用性,必须让节点继续对外服务,这样必然导致失去原子性。
分布式解决
Seata阿里开源的一个分布式事务框架
对业务无侵入:即减少技术架构上的微服务化所带来的分布式事务问题对业务的侵入
高性能:减少分布式事务解决方案所带来的性能消耗
设计思路是将一个分布式事务可以理解成一个全局事务,下面挂了若干个分支事务,而一个分支事务是一个满足 ACID 的本地事务,因此我们可以操作分布式事务像操作本地事务一样
XID:全局事务的唯一标识,由 ip:port:sequence 组成;
Transaction Coordinator (TC):事务协调器,维护全局事务的运行状态,负责协调并驱动全局事务的提交或回滚;
Transaction Manager (TM ):控制全局事务的边界,负责开启一个全局事务,并最终发起全局提交或全局回滚的决议;
Resource Manager (RM):控制分支事务,负责分支注册、状态汇报,并接收事务协调器的指令,驱动分支(本地)事务的提交和回滚;
Spring Cloud 快速集成 Seata
依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
</dependency>
配置入口文件是 registry.conf
固定内容
registry {
# file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
type = "file"
nacos {
serverAddr = "localhost"
namespace = "public"
cluster = "default"
}
eureka {
serviceUrl = "http://localhost:8761/eureka"
application = "default"
weight = "1"
}
redis {
serverAddr = "localhost:6379"
db = "0"
}
zk {
cluster = "default"
serverAddr = "127.0.0.1:2181"
session.timeout = 6000
connect.timeout = 2000
}
consul {
cluster = "default"
serverAddr = "127.0.0.1:8500"
}
etcd3 {
cluster = "default"
serverAddr = "http://localhost:2379"
}
sofa {
serverAddr = "127.0.0.1:9603"
application = "default"
region = "DEFAULT_ZONE"
datacenter = "DefaultDataCenter"
cluster = "default"
group = "SEATA_GROUP"
addressWaitTime = "3000"
}
file {
name = "file.conf"
}
}
config {
# file、nacos 、apollo、zk、consul、etcd3
type = "file"
nacos {
serverAddr = "localhost"
namespace = "public"
cluster = "default"
}
consul {
serverAddr = "127.0.0.1:8500"
}
apollo {
app.id = "seata-server"
apollo.meta = "http://192.168.1.204:8801"
}
zk {
serverAddr = "127.0.0.1:2181"
session.timeout = 6000
connect.timeout = 2000
}
etcd3 {
serverAddr = "http://localhost:2379"
}
file {
name = "file.conf"
}
}
在 registry
中可以指定具体配置的形式,默认使用 file 类型
file.conf
固定内容
transport {
# tcp udt unix-domain-socket
type = "TCP"
#NIO NATIVE
server = "NIO"
#enable heartbeat
heartbeat = true
#thread factory for netty
thread-factory {
boss-thread-prefix = "NettyBoss"
worker-thread-prefix = "NettyServerNIOWorker"
server-executor-thread-prefix = "NettyServerBizHandler"
share-boss-worker = false
client-selector-thread-prefix = "NettyClientSelector"
client-selector-thread-size = 1
client-worker-thread-prefix = "NettyClientWorkerThread"
# netty boss thread size,will not be used for UDT
boss-thread-size = 1
#auto default pin or 8
worker-thread-size = 8
}
shutdown {
# when destroy server, wait seconds
wait = 3
}
serialization = "seata"
compressor = "none"
}
service {
#vgroup->rgroup
vgroupMapping.my_test_tx_group = "default"
#only support single node
default.grouplist = "127.0.0.1:8091"
#degrade current not support
enableDegrade = false
#disable
disable = false
#unit ms,s,m,h,d represents milliseconds, seconds, minutes, hours, days, default permanent
max.commit.retry.timeout = "-1"
max.rollback.retry.timeout = "-1"
}
client {
async.commit.buffer.limit = 10000
lock {
retry.internal = 10
retry.times = 30
}
report.retry.count = 5
}
## transaction log store
store {
## store mode: file、db
mode = "file"
## file store
file {
dir = "sessionStore"
# branch session size , if exceeded first try compress lockkey, still exceeded throws exceptions
max-branch-session-size = 16384
# globe session size , if exceeded throws exceptions
max-global-session-size = 512
# file buffer size , if exceeded allocate new buffer
file-write-buffer-cache-size = 16384
# when recover batch read size
session.reload.read_size = 100
# async, sync
flush-disk-mode = async
}
## database store
db {
## the implement of javax.sql.DataSource, such as DruidDataSource(druid)/BasicDataSource(dbcp) etc.
datasource = "dbcp"
## mysql/oracle/h2/oceanbase etc.
db-type = "mysql"
url = "jdbc:mysql://127.0.0.1:3306/seata"
user = "mysql"
password = "mysql"
min-conn = 1
max-conn = 3
global.table = "global_table"
branch.table = "branch_table"
lock-table = "lock_table"
query-limit = 100
}
}
lock {
## the lock store mode: local、remote
mode = "remote"
local {
## store locks in user's database
}
remote {
## store locks in the seata's server
}
}
recovery {
committing-retry-delay = 30
asyn-committing-retry-delay = 30
rollbacking-retry-delay = 30
timeout-retry-delay = 30
}
transaction {
undo.data.validation = true
undo.log.serialization = "jackson"
}
## metrics settings
metrics {
enabled = false
registry-type = "compact"
# multi exporters use comma divided
exporter-list = "prometheus"
exporter-prometheus-port = 9898
}
注入数据源
如果是通用mapper可以使用
@Configuration
public class DataSourceProxyConfig {
@Bean
@ConfigurationProperties(prefix = "spring.datasource")
public DataSource dataSource() {
return new DruidDataSource();
}
@Bean
public DataSourceProxy dataSourceProxy(DataSource dataSource) {
return new DataSourceProxy(dataSource);
}
@Bean
public SqlSessionFactory sqlSessionFactoryBean(DataSourceProxy dataSourceProxy) throws Exception {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSourceProxy);
return sqlSessionFactoryBean.getObject();
}
}
springJPA
@Configuration
public class DataSourceProxyConfig {
@Bean
@ConfigurationProperties(prefix = "spring.datasource")
public DruidDataSource druidDataSource() {
return new DruidDataSource();
}
@Primary
@Bean
public DataSourceProxy dataSource(DruidDataSource druidDataSource) {
return new DataSourceProxy(druidDataSource);
}
}
修改每个微服务的application.yml 配置,配置通信指定的组名( my_test_tx_group)
添加 undo_log 表
每个微服务对应的数据库中需要创建一张undo_log表。
CREATE TABLE `undo_log`
(
`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
`branch_id` BIGINT(20) NOT NULL,
`xid` VARCHAR(100) NOT NULL,
`context` VARCHAR(128) NOT NULL,
`rollback_info` LONGBLOB NOT NULL,
`log_status` INT(11) NOT NULL,
`log_created` DATETIME NOT NULL,
`log_modified` DATETIME NOT NULL,
`ext` VARCHAR(100) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`)
) ENGINE = InnoDB
AUTO_INCREMENT = 1
DEFAULT CHARSET = utf8
启动 Seata-Server
在 https://github.com/seata/seata/releases 下载相应版本的 Seata-Server解压启动
使用@GlobalTransactional
开启事务
在业务的发起方的方法上使用@GlobalTransactional
开启全局事务
一个注解即可