【微服务】服务注册与发现、分布式配置管理 - Consul(day5)

概述

作用

Consul的两大作用就是服务发现和注册分布式配置管理

服务发现在介绍Eureka组件的时候已经进行过详细概述,大概就是将硬编码到服务中的IP地址和端口号进行解耦,从而实现动态扩缩容、容错处理、服务管理等功能,通过服务注册和发现实现的微服务代码就是俩字:太优雅了。

分布式配置管理的意思是将原先配置到项目中的配置信息配置到远程服务器上。原因是因为微服务将应用拆分成了粒度较小的子服务,因此系统中会出现大量的服务,而每个服务都需要必要的配置信息才能运行,所以会出现大量的配置文件。综上所述,一套集中式的、动态的配置管理设施是必不可少的。比如某些配置文件中的内容大部分是相同的,只有极个别是不同的。就拿数据库来说吧,如果每个微服务使用的技术栈相同,则每个微服务关于数据库的配置几乎是相同的,有时候主机迁移了,我希望一次修改、处处生效。对于原先的配置文件来说,就需要重新配置、重新打包上线。但是如果使用了分布式配置管理,那么就可以在远程进行修改,并且,有的分布式配置中心还能共享一套配置文件,这样更进一步减少了运维人员的工作量。

功能

服务发现

健康检测

KV存储

多数据中心

可视化Web界面

安装Consul

  1. 访问官网进行下载,如下图:

  1. 查看版本:consul -version。下载好就是一个exe文件,通过cmd命令进行下载好的目录,输入上述命令即可查看版本。

  1. 启动Consul:consul agent -dev,依旧是下载好的exe文件对应的目录,直接运行上述命令即可启动Consul。

  1. 查看Consul:127.0.0.1:8500,输入网址即可看到Consul的网站。

服务注册与发现

为了后续方便上传码云和复习,因此将学习Eureka和SpringCloudLoadBalancer的模块全部进行改名,并且学习Consul时新建立模块,不和前面的模块进行耦合。

搭建商品服务

建模块

写pom文件

复制代码
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>com.wbz</groupId>
        <artifactId>spring-cloud-test</artifactId>
        <version>1.0-SNAPSHOT</version>
    </parent>

    <artifactId>cloud-consumer-order-consul-81</artifactId>

    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>

        <!--SpringBoot通用模块-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!--Lombok-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

        <!--Druid-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
        </dependency>

        <!--MySQL驱动-->
        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
        </dependency>

        <!--MP-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-spring-boot3-starter</artifactId>
        </dependency>

        <!--Consul-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-consul-discovery</artifactId>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

新增的依赖就是Consul的依赖:

复制代码
        <!--Consul-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-consul-discovery</artifactId>
        </dependency>

写yml文件

复制代码
server:
  port: 81

spring:
  application:
    name: cloud-consumer-order-consul-81

  mvc:
    pathmatch:
      matching-strategy: ant_path_matcher # 路径匹配策略

  datasource:
    url: jdbc:mysql://127.0.0.1:3306/cloud_order?characterEncoding=utf8&useSSL=false&allowPublicKeyRetrieval=true&allowMultiQueries=true
    username: root
    password: root
    driver-class-name: com.mysql.cj.jdbc.Driver

  profiles:
    active: dev

  cloud:
    consul:
      host: localhost
      port: 8500
      discovery:
        service-name: ${spring.application.name}

mybatis-plus:
  configuration:
    map-underscore-to-camel-case: true
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  mapper-locations: classpath:mapper/**Mapper.xml
  type-aliases-package: com.wbz.domain

新增的yml文件就是配置Consul需要的配置文件:

复制代码
spring:
  cloud:
    consul:
      host: localhost
      port: 8500
      discovery:
        service-name: ${spring.application.name}

改启动类

复制代码
/**
 * consul组件的被调用方启动类
 */

@EnableDiscoveryClient // 服务注册
@MapperScan("com.wbz.mapper")
@SpringBootApplication
public class ProductProviderApplicationConsul8101 {

    public static void main(String[] args) {
        SpringApplication.run(ProductProviderApplicationConsul8101.class, args);
    }

}

新增的内容就是@EnableDiscoveryClient注解,用来进行服务注册的。

写业务类

复制代码
// model
/**
 * 产品表
 */
 
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("product_detail")
public class Product {
 
    @TableId
    private Long id;
 
    @TableField
    private String productName;
 
    @TableField
    private Long productPrice;
 
    @TableField
    private Integer state;
 
    @TableField
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private LocalDateTime createTime;
 
    @TableField
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private LocalDateTime updateTime;
 
}
 
// 持久层接口
public interface ProduceMapper extends BaseMapper<Product> {
}
 
// 服务层接口
public interface ProductService extends IService<Product> {
 
    Product getProductById(Long productId);
 
}
 
// 服务层实现类
@Service
public class ProductServiceImpl extends ServiceImpl<ProduceMapper, Product> implements ProductService {
 
    @Override
    public Product getProductById(Long productId) {
        return this.getById(productId);
    }
 
}
 
// 控制层类
@RestController
@RequestMapping("/product")
public class ProductController {
 
    @Resource
    private ProductService productService;
 
    @GetMapping("/query/{productId}")
    public Product getProductById(@PathVariable Long productId) {
        return this.productService.getProductById(productId);
    }
 
}

启动项目之后,出现如下界面就表示商品服务依旧注册到注册中心了:

搭建订单服务

建模块

写pom文件

复制代码
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>com.wbz</groupId>
        <artifactId>spring-cloud-test</artifactId>
        <version>1.0-SNAPSHOT</version>
    </parent>

    <artifactId>cloud-consumer-order-consul-81</artifactId>

    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>

        <!--SpringBoot通用模块-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!--Lombok-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

        <!--Druid-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
        </dependency>

        <!--MySQL驱动-->
        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
        </dependency>

        <!--MP-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-spring-boot3-starter</artifactId>
        </dependency>

        <!--Consul-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-consul-discovery</artifactId>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

pom文件中新增的就是Consul的依赖:

复制代码
        <!--Consul-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-consul-discovery</artifactId>
        </dependency>

写yml文件

复制代码
server:
  port: 81

spring:
  application:
    name: cloud-consumer-order-consul-81

  mvc:
    pathmatch:
      matching-strategy: ant_path_matcher # 路径匹配策略

  datasource:
    url: jdbc:mysql://127.0.0.1:3306/cloud_order?characterEncoding=utf8&useSSL=false&allowPublicKeyRetrieval=true&allowMultiQueries=true
    username: root
    password: root
    driver-class-name: com.mysql.cj.jdbc.Driver

  profiles:
    active: dev

  cloud:
    consul:
      host: localhost
      port: 8500
      discovery:
        service-name: ${spring.application.name}

mybatis-plus:
  configuration:
    map-underscore-to-camel-case: true
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  mapper-locations: classpath:mapper/**Mapper.xml
  type-aliases-package: com.wbz.domain

新增的pom依赖就是配置Consul的内容:

复制代码
spring:
  cloud:
    consul:
      host: localhost
      port: 8500
      discovery:
        service-name: ${spring.application.name}

写主启动类

复制代码
@MapperScan("com.wbz.mapper")
@EnableDiscoveryClient // 服务注册
@SpringBootApplication
public class OrderConsumerApplicationConsul81 {

    public static void main(String[] args) {
        SpringApplication.run(OrderConsumerApplicationConsul81.class, args);
    }

}

新增的内容就是@EnableDiscoveryClient注解,用来进行服务注册的。

写业务类

复制代码
// JavaBean
@Data
@NoArgsConstructor
@AllArgsConstructor
@TableName("order_detail")
public class Order {

    @TableId
    private Long id;

    @TableField
    private Long userId;

    @TableField
    private Long productId;

    @TableField
    private Integer num;

    @TableField
    private Long price;

    @TableField
    private Integer deleteFlag;

    @TableField
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private LocalDateTime createTime;

    @TableField
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private LocalDateTime updateTime;

    @TableField(exist = false)
    private Product product;

}

// RestTemplate控制类
@Configuration
public class RestTemplateConfig {

    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }

}

// mapper层
public interface OrderMapper extends BaseMapper<Order> {
}

// service接口
public interface OrderService extends IService<Order> {

    Order getOrderById(Integer id);

}

// service实现类
@Slf4j
@Service
public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements OrderService {

    @Resource
    private RestTemplate restTemplate;

    private final String PRODUCT_SERVICE_NAME = "cloud-provider-product-consul-8101";

    @Override
    public Order getOrderById(Integer id) {
        // 获取订单
        Order order = this.getById(id);
        // 拼接url
        String url = "http://" + this.PRODUCT_SERVICE_NAME + "/product/query/" + order.getProductId();
        log.info(url);
        // 远程调用
        Product product = this.restTemplate.getForObject(url, Product.class);
        order.setProduct(product);
        // 返回结果
        return order;
    }

}

// controller实现类
@RestController
@RequestMapping("/order")
public class OrderController {

    @Resource
    private OrderService orderService;

    @GetMapping("/query/{id}")
    public Order getOrderById(@PathVariable Integer id) {
        return this.orderService.getOrderById(id);
    }

}

启动项目之后,出现如下界面,就表示启动成功了:

bug

当两个服务都启动之后,就调用127.0.0.1:81/order/query/1来进行调用,查看远程调用是否成功。本来计划着没啥问题,但是,出错了,如下图:

原因是因为Consul自动实现了负载均衡,但是并没有引入负载均衡的依赖,于是我就去查看了一下pom依赖。原来是Consuljar包中带了负载均衡的依赖啊,如下图:

那这就说的通了,直接给RestTempalte加上@LoadBlanced注解。

复制代码
@Configuration
public class RestTemplateConfig {

    @Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }

}

加上注解之后,再次测试,完全没毛病了。

总结

Consul组件跟Eurekal相比,不用再搭建一个Consul服务了。只要简单的三步:

引入依赖、添加配置、启动类添加注解。如果是使用RestTemplate等远程调用的话,再加一个负载均衡的注解即可。非常好用。

Consul对于CAP理论来说,保证的是强一致性。

分布式配置管理

在最上面的概述中,已经将分布式配置管理给简单介绍了一下。总的来说,分布式配置管理就是把一些配置文件放到Consul服务器上,这样可以做到无感刷新配置文件,从而减少开发、运维人员的工作。

bootstrap.yml

application.yml是用户级的资源配置项,bootstrap.yml是系统级的资源配置项,优先级更高。

SpringCloud会创建一个BootStrap Context,作为Spring应用Application Context的父上下文。初始化的时候,BootStrap Context负责从外部源加载配置属性并解析配置,这两个上下文共享一个从外部获取的Environment。

BootStrap属性有高优先级,默认情况下,他们不会被本地配置覆盖。BootStrap Context和Application Context有着不同的约定,所以新增了一个bootstrap.yml文件,保证两个上下文配置的分离。

修改商品服务

添加依赖

复制代码
        <!--Consul配置中心-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-consul-config</artifactId>
        </dependency>
        
        <!--BootStrap-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-bootstrap</artifactId>
        </dependency>

修改配置文件

首先在Consul的Key/Value界面新建文件夹,用来存储配置文件:

在创建的config文件夹下,再创建三个文件:

在每个文件夹下,再创建一个data文件:

在每一个data文件中,再分别加入内容,并且保证格式为yml格式。

注意,创建文件夹时要在名称后面带/,创建文件时则不需要。

其次在商品服务下创建一个bootstrap.yml的文件,然后将原先appliaction.yml的文件全部迁移至bootstrap.yml文件中。

复制代码
server:
  port: 8101

spring:
  application:
    name: cloud-provider-product-consul-8101

  mvc:
    pathmatch:
      matching-strategy: ant_path_matcher # 路径匹配策略

  datasource:
    url: jdbc:mysql://127.0.0.1:3306/cloud_product?characterEncoding=utf8&useSSL=false&allowPublicKeyRetrieval=true&allowMultiQueries=true
    username: root
    password: root
    driver-class-name: com.mysql.cj.jdbc.Driver

  profiles:
    active: dev

  cloud:
    consul:
      host: localhost
      port: 8500
      discovery:
        service-name: ${spring.application.name}
      config:
        profile-separator: '-' # default value is ",",we update '-'
        format: YAML

mybatis-plus:
  configuration:
    map-underscore-to-camel-case: true
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  mapper-locations: classpath:mapper/**Mapper.xml
  type-aliases-package: com.wbz.domain

在配置文件中,新增了如下配置:

复制代码
spring:
  cloud:
    consul:
      config:
        profile-separator: '-' # default value is ",",we update '-'
        format: YAML

修改业务类

在业务类中添加如下代码,用来测试远程调用是否成功。

复制代码
@RestController
@RequestMapping("/product")
public class ProductController {

    @Value("${test}")
    private String test;

    @Value("${info}")
    private String info;

    @GetMapping("/config")
    public String getConfig() {
        return test + " " + info;
    }

}

启动项目之后,输入127.0.0.1:8101/product/config,出现如下界面,表示分布式配置管理成功:

出现上述内容的原因是因为在配置文件中配置的环境为dev环境,如果把环境改为prod环境,那么就会出现如下内容:

总结

为什么配置文件是要写成上述格式?

如下图,为官网的配置格式。大概内容是想要把配置文件放到Consul上,首先要建立一个config文件夹,官方才认为这是配置文件中的内容;其次,对应的服务就要再创建文件夹进行配置,这样官方才认为该文件夹中的文件是某服务的文件。例如在咋们自己配置的文件中,首先创建了一个config文件,然后在下面创建了以cloud-provider-product-consul-8101为开头的三种文件夹。

在三种文件夹中,没有环境配置的表示每个环境都能读取其中的内容,也就是公共配置;后面有dev、prod的则表示是对应环境的配置,当然可以有其他的,这里只是做演示。

官网使用的是逗号来分割服务名和生产环境的,但是我们使用的是-,这是可以在配置文件中进行配置的。当然了,最后的文件夹的data名字也是可以进行配置的,官方默认是data。

修改真正配置

商品服务

首先将原来测试的配置文件、业务类等全部删除,然后只保留dev环境的文件夹,其他文件夹也删除,暂时用不上。

Consul上的配置文件

复制代码
server:
  port: 8101
  
spring:
  mvc:
    pathmatch:
      matching-strategy: ant_path_matcher # 路径匹配策略

  datasource:
    url: jdbc:mysql://127.0.0.1:3306/cloud_product?characterEncoding=utf8&useSSL=false&allowPublicKeyRetrieval=true&allowMultiQueries=true
    username: root
    password: root
    driver-class-name: com.mysql.cj.jdbc.Driver

mybatis-plus:
  configuration:
    map-underscore-to-camel-case: true
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  mapper-locations: classpath:mapper/**Mapper.xml
  type-aliases-package: com.wbz.domain

bootstrap.yml中的配置文件

复制代码
spring:
  application:
    name: cloud-provider-product-consul-8101

  cloud:
    consul:
      host: localhost
      port: 8500
      discovery:
        service-name: ${spring.application.name}
      config:
        profile-separator: '-' # default value is ",",we update '-'
        format: YAML

  profiles:
    active: dev

修改完成之后,可以进行一个简单测试,输入127.0.0.1:8101/product/query/1001,只要出现结果就证明部署成功了。如果在部署过程中出错,可能是业务类没有删除,或者生产环境出错导致的。

订单服务

Consul上的配置文件

复制代码
server:
  port: 81

spring:
  mvc:
    pathmatch:
      matching-strategy: ant_path_matcher # 路径匹配策略

  datasource:
    url: jdbc:mysql://127.0.0.1:3306/cloud_order?characterEncoding=utf8&useSSL=false&allowPublicKeyRetrieval=true&allowMultiQueries=true
    username: root
    password: root
    driver-class-name: com.mysql.cj.jdbc.Driver

mybatis-plus:
  configuration:
    map-underscore-to-camel-case: true
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  mapper-locations: classpath:mapper/**Mapper.xml
  type-aliases-package: com.wbz.domain

bootstrap中的配置文件

复制代码
spring:
  application:
    name: cloud-consumer-order-consul-81

  cloud:
    consul:
      host: localhost
      port: 8500
      discovery:
        service-name: ${spring.application.name}
      config:
        profile-separator: '-'
        format: yaml

  profiles:
    active: dev

切记,在修改的时候要记住先加上依赖,再修改配置文件。

动态刷新

启动项目之后,我们在Consul上的配置文件中进行修改,发现并没有起作用,原因是因为默认的刷新时间为55秒,因此我们修改之后是不会立刻起作用的,我们就需要等待一段时间。

在实际生产中,这个时间太长了,因此我们可以在主启动类上加上@RefreshScope注解,该注解的意思就是动态刷新。

持久化配置

当我们关闭小黑框之后,发现所有的配置都消失了,这是一个致命的错误,因此我们需要把配置持久化。

  1. 新建一个mydata文件夹
  1. 创建一个consul_start.bat的文件
  1. 进入文件,将下述内容添加到文件中,注意下述内容中的文件地址要和自己的一样。

    @echo.服务启动......
    @echo off
    @sc create Consul binpath= "E:\java\spring\springcloud\consul_1.19.1\consul.exe agent -server -ui -bind=127.0.0.1 -client=0.0.0.0 -bootstrap-expect 1 -data-dir E:\java\spring\springcloud\consul_1.19.1\mydata "
    @net start Consul
    @sc config Consul start= AUTO
    @echo.Consul start is OK......success
    @pause

  2. 点击文件,右键管理员方式运行。

  1. 运行成功之后,查看任务管理器,发现consul.exe已经变成了后台进程,那么表示此时就持久化成功了。
相关推荐
Code季风2 小时前
微服务分布式配置中心:Gin Web 服务层与 gRPC 服务层集成 Nacos 实战
分布式·微服务·rpc·架构·go·gin·consul
步、步、为营4 小时前
.net微服务框架dapr保存和获取状态
微服务·架构·.net
guojl7 小时前
微服务OpenFeign源码分析
spring cloud·微服务
guojl7 小时前
微服务OpenFeign使用手册
spring cloud·微服务
Code季风1 天前
Gin Web 层集成 Viper 配置文件和 Zap 日志文件指南(下)
前端·微服务·架构·go·gin
Code季风1 天前
Gin Web 服务集成 Consul:从服务注册到服务发现实践指南(下)
java·前端·微服务·架构·go·gin·consul
掘金-我是哪吒2 天前
分布式微服务系统架构第156集:JavaPlus技术文档平台日更-Java线程池使用指南
java·分布式·微服务·云原生·架构
DavidSoCool2 天前
RabbitMQ使用topic Exchange实现微服务分组订阅
分布式·微服务·rabbitmq
掘金-我是哪吒2 天前
分布式微服务系统架构第158集:JavaPlus技术文档平台日更-JVM基础知识
jvm·分布式·微服务·架构·系统架构