5. 服务注册发现、配置中心集一体的 Spring Cloud Consul

作者: 风筝 / 2023-06-02

Java, JDK, Spring Cloud, Spring Cloud Consul, 配置中心

前面讲了 Eureka 和 Spring Cloud Config,今天介绍一个全能选手 「Consul」。它是 HashiCorp 公司推出,用于提供服务发现和服务配置的工具。用 go 语言开发,具有很好的可移植性。被 Spring Cloud 纳入其中,Eureka 停止新版本开发,更多的想让开发者使用 Consul 来作为服务注册发现使用。

Consul 提供的功能包括如下几个:

服务发现

Consul 让服务注册和服务发现(通过 DNS 和 HTTP 接口)更加简单,甚至对于外部服务(例如SaaS)注册也一样。

故障检测

通过健康检查,服务发现可以防止请求被路由到不健康的主机,并且可以使服务容易断开(不再提供服务)。

多数据中心

Consul 不需要复杂的配置即可简便的扩展到多个数据中心,查找其它数据中心的服务或者只请求当前数据中心的服务。

键值存储

灵活的键值存储,提供动态配置、特征标记、协作、leader 选举等功能,通过长轮询实现配置改变的即时通知。

Spring Cloud Consul 将 Consul 进行自动配置和进一步封装。

Spring Cloud Consul 可替代已有的 Spring Cloud Eureka,也就是当做服务注册发现框架使用。并且 Eureka 2.x 版本也已经停止开发,并且 Spring Cloud 官方也建议用 Spring Cloud Consul 来替代,当然如果已经用了 Eureka 在项目中也没有关系,Eureka 已经足够稳定,正常使用没有任何问题。

Spring Cloud Consul 可替代已有的 Spring Cloud Config ,也就是当做配置中心使用。

Spring Cloud Consul 主要用作服务注册发现,并且官方建议替代 Eureka,那么它肯定具有 Eureka 或其他框架不具备的优势,下面看一下对比它和其他服务发现方式做的一下对比(摘自网络):

功能点euerkaConsulzookeeperetcd
服务健康检查可配支持服务状态,内存,硬盘等(弱)长连接,keepalive连接心跳
多数据中心支持
kv 存储服务支持支持支持
一致性raftpaxosraft
capap(高可用、分区容错)ca(数据一致、高可用)cpcp
使用接口(多语言能力)http(sidecar)支持 http 和 dns客户端http/grpc
watch 支持支持 long polling/大部分增量全量/支持long polling支持支持 long polling
自身监控metricsmetricsmetrics
安全acl /httpsaclhttps 支持(弱)
spring cloud 集成已支持已支持已支持已支持

Consul 采用 raft 算法来保证数据的强一致性,如此带来的优势很明显,相应的也带来了一些牺牲:

  1. 服务注册相比 Eureka 会稍慢一些。因为 Consul 的 raft 协议要求必须过半数的节点都写入成功才认为注册成功;
  2. Leader挂掉时,重新选举期间整个 consul 不可用,以此保证了强一致性但牺牲了可用性。

Consul 的安装和启动

与 Eureka 不同,Consul 需要独立安装,可以到官网(https://www.consul.io/downloads.html)下载。具体操作系统的安装方式不同,可参考官网。

Consul 提供了一系列的参数,用于在命令行执行。Consul 默认提供了 web UI 界面来查看配置。通过访问 server 的 8500 端口可以访问到 web UI 控制台。

开发过程中,我们可以通过命令 consul agent -dev 来启动开发模式,启动成功后,访问 localhost:8500 可以看到当前 consul 的所有服务。如下图:

更多的在生成环境的部署可自行搜索相关介绍,这里暂时只用 dev 模式启动,用来介绍 Spring Cloud Consul 的使用。

实现服务提供者

1、引用 spring-cloud-consul

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-consul-all</artifactId>
</dependency>
<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>
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-consul-dependencies</artifactId>
            <version>2.1.0.M2</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

2、设置 consul 相关配置,在 bootstrap.yml 配置文件中,配置如下:

spring:
  cloud:
    consul:
      discovery:
        service-name: consul-provider  ## 服务提供者名称
      host: localhost                  ## consul 所在服务地址
      port: 8500					   ## consul 端口

3、设置 server 相关配置,在 application.yml 配置文件中,配置如下:

spring:
  application:
    name: consul-provider
server:
  port: 5000

endpoints:
  health:
    sensitive: false
  restart:
    enabled: true
  shutdown:
    enabled: true

management:
  security:
    enabled: false

4、增加一个 RestController ,写两个测试服务方法

@RestController
@Slf4j
public class HelloController {

    @Autowired
    private DiscoveryClient discoveryClient;

    @GetMapping(value = "test")
    public String test(){
        List<String> services = discoveryClient.getServices();
        for(String s : services){
            log.info(s);
        }
        return "hello spring cloud!";
    }

    @GetMapping(value = "nice")
    public String nice(){
        List<String> services = discoveryClient.getServices();
        for(String s : services){
            log.info("gogogo" + s);
        }
        return "nice to meet you!";
    }
}

5、Spring boot 启动类

@SpringBootApplication
@EnableDiscoveryClient
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

@EnableDiscoveryClient 注解标示这是一个 client 端。

启动这个服务提供者,打开 http://localhost:8500 可以看到这个服务

##实现服务消费者

1、引用相关 maven 包,除了引用与上面服务提供者相同的包外,还引用了 openFeign

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

2、bootstrap.yml 配置,因为作为服务消费者,所以设置不注册到 consul

spring:
  cloud:
    consul:
      discovery:
        register: false

3、application.yml 配置

spring:
  application:
    name: consul-customer
server:
  port: 5001

endpoints:
  health:
    sensitive: false
  restart:
    enabled: true
  shutdown:
    enabled: true

management:
  security:
    enabled: false

4、项目启动类

@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class Application {

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

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

使用 @EnableDiscoveryClient 注解表示作为服务 client 端,@EnableFeignClients 启用 openFeign 。

5、新建一个 openFeign 服务接口

@FeignClient(value = "consul-provider")
public interface IHelloService {

    @RequestMapping(value = "/hello")
    String hello();

    @RequestMapping(value = "nice")
    String nice();
}

对应服务提供者中提供的两个 RESTful 接口地址

6、实现一个 RestController 来访问服务提供者开放出来的服务

@RestController
public class ConsumerController {

    @Autowired
    private LoadBalancerClient loadBalancer;

    @Autowired
    private DiscoveryClient discoveryClient;


    @Autowired
    private RestTemplate restTemplate;

    @Autowired
    private IHelloService helloService;

    private final static String SERVICE_NAME = "consul-provider";


    /**
     * 使用普通的 RestTemplate 方法访问服务
     *
     * @return
     */
    @GetMapping(value = "test")
    public Object test() {
        String result = restTemplate.getForObject("http://"+SERVICE_NAME + "/test", String.class);
        System.out.println(result);
        return result;
    }

    /**
     * 使用 openFeign 方式访问服务
     *
     * @return
     */
    @GetMapping(value = "feign")
    public Object feign() {
        String s = helloService.nice();
        return s;
    }
    

    /**
     * 获取所有服务实例
     *
     * @return
     */
    @GetMapping(value = "/services")
    public Object services() {
        return discoveryClient.getInstances(SERVICE_NAME);
    }

    /**
     * 从所有服务中选择一个服务(轮询)
     */
    @GetMapping(value = "/choose")
    public Object choose() {
        return loadBalancer.choose(SERVICE_NAME).getUri().toString();
    }
}

启动消费者程序,然后访问对应的 RESTful 接口,可以得到对应的结果。

实现高可用服务提供者

线上的微服务最好不要是单点形式,接下来通过配置来启动两个服务提供者,只要保证 service-name 相同,就表示这是同一个服务。

1、 bootstrap.yml 配置不变

spring:
  cloud:
    consul:
      discovery:
        service-name: consul-provider
      host: localhost
      port: 8500

2、application.yml 修改为如下配置

spring:
  profiles:
    active: consul-provider1
endpoints:
  health:
    sensitive: false
  restart:
    enabled: true
  shutdown:
    enabled: true

management:
  security:
    enabled: false

---
spring:
  profiles: consul-provider1
  application:
    name: consul-provider1

server:
  port: 5000

---
spring:
  profiles: consul-provider2
  application:
    name: consul-provider2

server:
  port: 5002

3、之后启动的时候加上 vm 参数。分别加上参数:

-Dspring.profiles.active=consul-provider1

-Dspring.profiles.active=consul-provider2

分别在 5000 端口和 5002 端口启动服务提供者 consul-provider

4、最后仍然访问消费者的 RESTful 接口地址,可以在服务提供者后台看到每次请求调用的服务实例。

用作配置中心

我们知道,Spring Cloud Config 提供了配置中心的功能,但是需要配合 git、svn 或外部存储(例如各种数据库),那么既然使用了 Consul ,就可以使用 Consul 提供的配置中心功能,并且不需要额外的 git 、svn、数据库等配合使用。

接下来,简单介绍一下 Spring Cloud Consul 如何用作配置中心。Consul 支持 yaml 和 properties 格式的配置文件内容,本例中以 yaml 格式为例。

1、引用相关的 maven 包

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-consul-config</artifactId>
</dependency>
<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>

2、bootstrap.yml 配置,这里主要设置有关 config 的参数

spring:
  cloud:
    consul:
      config:
        enabled: true    # 启用配置中心
        format: yaml     # 指定配置格式为 yaml
        data-key: mysql_config # 也就是 consul 中 key/value 中的 key
        prefix: config         # 可以理解为配置文件所在的最外层目录
        defaultContext: consul-config  # 可以理解为 mysql_config 的上级目录
      discovery:
        register: false

对应到 consul 上,key/value 里的配置如下:

3、application.yml 配置文件内容

spring:
  application:
    name: consul-config
server:
  port: 5008

endpoints:
  health:
    sensitive: false
  restart:
    enabled: true
  shutdown:
    enabled: true

management:
  security:
    enabled: false

4、定义配置文件实体类,指定 @ConfigurationProperties 注解,指定前缀为 mysql,也就是 key/value 配置文件中的顶层 key。

@Component
@ConfigurationProperties(prefix = "mysql")
public class MySqlComplexConfig {

    public static class UserInfo{

        private String username;

        private String password;

        public String getUsername() {
            return username;
        }

        public void setUsername(String username) {
            this.username = username;
        }

        public String getPassword() {
            return password;
        }

        public void setPassword(String password) {
            this.password = password;
        }

        @Override
        public String toString() {
            return "UserInfo{" +
                    "username='" + username + '\'' +
                    ", password='" + password + '\'' +
                    '}';
        }
    }

    private String host;

    private UserInfo user;


    public String getHost() {
        return host;
    }

    public void setHost(String host) {
        this.host = host;
    }

    public UserInfo getUser() {
        return user;
    }

    public void setUser(UserInfo user) {
        this.user = user;
    }

}

5、新建一个 RestController 来获取输出 value 内容

@RestController
@Slf4j
public class ConfigController {

    @Autowired
    private MySqlConfig mySqlConfig;

    @Autowired
    private MySqlComplexConfig mySqlComplexConfig;


    @GetMapping(value = "mysqlhost")
    public String getMysqlHost(){
        return mySqlConfig.getHost();
    }

    @GetMapping(value = "mysqluser")
    public String getMysqlUser(){
        log.info(mySqlComplexConfig.getHost());
        MySqlComplexConfig.UserInfo userInfo = mySqlComplexConfig.getUser();
        return userInfo.toString();
    }

}

6、最后,启动应用,访问 RestController 中的 RESTful 接口即可看到配置文件内容。

与 Spring Cloud Config 相比,Consul 在控制台修改配置后,会立即更新,不用再结合 Spring Cloud Bus 之类的配合了。

源码地址

相关文章

4. Spring Cloud Config 实现配置中心,看这一篇就够了
6. Spring Cloud 系列之 Spring Cloud Stream
0. Spring Cloud 是什么
1. Spring Cloud Eureka 实现服务注册与发现
2. Spring Cloud Eureka 实现安全控制
风筝

作者

风筝

古时的风筝,一个平庸的程序员,主语言 Java,第二语言 Python,其实学 Python 的时间比 Java 还要早。喜欢写博客,写博客的过程能加深自己对一个知识点的理解,同时还可以分享给他人。喜欢做一些小东西,所以也会一些前端的东西,React、JavaScript、CSS 都会一些,做一些小工具还够用。