SpringCloud

Author Avatar
kevin
发表:2022-07-26 15:51:00
修改:2024-10-09 18:30:38

微服务是系统架构上的一种设计风格, 它的主旨是将一个原本独立 的系统拆分成多个小型服务

这些小型服务都在各自独立的进程中运行,服务之 间通过基于 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

seata中文github文档

依赖

<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开启全局事务

一个注解即可

评论