Spring Cloud 与 Consul 的整合使用

王洪岐,现任某 IT 公司架构师、IT 写作者。精通多门编程语言,主攻 Java 后台开发,10 年项目经验,希望在 IT 领域发挥自己的光辉,给大家做一个启发或者引导。

文章正文

Spring Cloud 与 Consul 的整合使用

1. 背景

Spring Cloud 热度日益提升,注册中心、配置中心的选型是一个必然面对的问题。 Eureka 2.0 开源工作宣告停止,Zookeeper 略显笨重,相比之下,Consul 就是很多业务场景下较好的选择。Consul 部署简单,兼具注册中心和配置中心,Go 语言带来了高效,可集群部署能实现高可用,综合来看是 Spring Cloud 的较好搭配。

本场 Chat 中,作者将与大家分享 Spring Cloud 选择 Consul 作为注册中心和配置中心的整合过程,并结合实例分享应用实践及感悟。

适合有意向或正在使用 Consul 作为 Spring Cloud 配套的 Java 开发人员。

通过本场 Chat 你将学了解到如下内容:

  • Spring Cloud 采用 Consul 作为注册中心该如何实现
  • Spring Cloud 采用 Consul 作为配置中心该如何实现

2. 整体架构

Consul 作为服务注册中心的思路是各个服务将自身注册到 Consul,调用方从 Consul 获取特定服务的提供方列表,再访问服务提供方进行服务的调用。当 Consul 中某个服务的提供方进行增减时,调用方会通过 Consul 感知到这一变化并进行负载轮询列表更新,实现服务的在线扩容。

Consul 作为配置中心的思路是各个服务启动后先连接 Consul 服务,并从其 KV 存储中按优先级获取相应的配置项,并将配置加载到程序中,可以形象地比喻为将 application.yml 配置服务化。当 Consul 中配置更新时,依赖的服务可以定期检测到这个变化并应用到程序中,实现在线配置更新。

3. Consul 搭建

3.1 dev 模式运行

Consul 官网:https://www.consul.io/

官方文档:https://www.consul.io/docs/index.html

在 Consul 官网上 https://www.consul.io/downloads.html 下载相对应的系统版本,本文以 CentOS7 为例,将下载下来的包解压后,运行 consul 文件即可启动 dev 模式的 Consul 服务。

 chmod 777 ./consul
 ./consul
3.2 正常模式单机运行

dev 模式是不进行数据存储的,线上环境需要开启可存储数据的模式,非dev模式的单节点命令示例如下:

nohup ./consul-service/consul agent -server -bootstrap-expect 1 -data-dir ./consul-service/data -config-dir=./consul-service/config -client=0.0.0.0 -ui >./consul-service/consul.out&

简单解释下主要参数:

  • server : 定义 agent 运行在 server 模式。
  • bootstrap-expect :在一个 datacenter 中期望提供的 server 节点数目,当该值提供的时候,consul 一直等到达到指定 server 数目的时候才会引导整个集群,该标记不能和 bootstrap 共用。
  • bind:该地址用来在集群内部的通讯,集群内的所有节点到地址都必须是可达的,默认是 0.0.0.0。
  • data-dir:数据存储目录。
  • config-dir:配置目录。
  • client:Consul 服务侦听地址,这个地址提供 HTTP、DNS、RPC 等服务,默认是 127.0.0.1 所以不对外提供服务,如果你要对外提供服务改成 0.0.0.0。
  • ui:启动 Web 管理页面
3.3 集群运行

第一台机器 IP 为 172.17.0.3,3 台机器分别执行,均启动 ui:

consul agent -server -bootstrap-expect 3 -ui -node=node1 -client 0.0.0.0
consul agent -server -bootstrap-expect 3 -ui -node=node2 -client 0.0.0.0 -join 172.17.0.3
consul agent -server -bootstrap-expect 3 -ui -node=node3 -client 0.0.0.0 -join 172.17.0.3

此时三台机器上的数据会进行同步,如果需要保障客户端访问集群的可靠性,需要在中间加一层 nginx 或其他负载,配置方式此处略。

4. Consul 作为服务注册中心

本示例以 IDEA+Maven 的方式进行开发。

4.1 服务端

首先是添加 pom.xml 的依赖。

<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    <java.version>1.8</java.version>
    <spring-cloud.version>Finchley.SR1</spring-cloud.version>
</properties>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-consul-discovery</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>${spring-cloud.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

然后是配置 application.yml。

server:
  port: 9201

spring:
  application:
    name: springtest-service
  message:
    encoding: UTF-8
  cloud:
    consul:
      host: 127.0.0.3
      port: 8500
      discovery:
        register: true
        instance-id: ${spring.application.name}:${spring.cloud.client.ip-address}:${server.port}
        service-name: ${spring.application.name}
        port: ${server.port}
        healthCheckPath: /actuator/health
        healthCheckInterval: 15s

警告:如果您使用Spring Cloud Consul Config,上述值将需要放置在 bootstrap.yml 而不是 application.yml 中。

consul 段说明

host:consul 服务的 IP

port:consul 服务的端口

discovery 段配置说明

register:是否注册到配置中心

instance-id:实例唯一 ID

service-name:服务名

port:端口

healthCheckPath:健康检查访问路径,consul 服务器会定时访问次服务的该 url 以检测是否健康

healthCheckInterval:健康检查时间间隔

最后上代码 application.java。

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@EnableDiscoveryClient
@RestController
@SpringBootApplication
public class SpringtestServerApplication {

    @Autowired
    private DiscoveryClient discoveryClient;

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

    /**
     * 获取所有服务
     */
    @RequestMapping("/services")
    public Object services() {
        return discoveryClient.getServices();
    }

    @RequestMapping("/home")
    public String home() {
        return "Hello World";
    }
}

启动后,Consul 的 Service 列表中就会出现这个服务。

此时访问 http://localhost:9201/home 会输出 Hello World。

4.2 客户端

先是配置依赖 pom.xml。

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-consul-discovery</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

接下来是配置文件 application.yml。

server:
  port: 9202

spring:
  application:
    name: springtest-client
  cloud:
    consul:
      host: 192.168.140.128
      port: 8500
      discovery:
        register: false

最后上代码 application.java。

@EnableDiscoveryClient
@RestController
@SpringBootApplication
public class SpringtestClientApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringtestClientApplication.class, args);
    }
    @Autowired
    private LoadBalancerClient loadBalancer;

    @Autowired
    private DiscoveryClient discoveryClient;

    @Autowired
    RestTemplate restTemplate;

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

    /**
     * 从所有服务中选择一个服务(轮询)
     */
    @RequestMapping("/discover")
    public Object discover() {
        return loadBalancer.choose("springtest-service").getUri().toString();
    }

    /**
     * 获取所有服务
     */
    @RequestMapping("/services")
    public Object services() {
        return discoveryClient.getInstances("springtest-service");
    }

    @RequestMapping("test")
    public String test(){
        String result= restTemplate.getForEntity("http://springtest-service/home",String.class).getBody();
        return result;
    }
}

一般常用 restTemplate.getForEntity("http://springtest-service/home",String.class) 这种方式调用,方便快捷。

此时访问 http://localhost:9202/test 会输出 Hello World。

5. Consul 作为配置中心

5.1 配置

Consul 添加 kv 的方式:http://ip:8500/ 进入 Web 管理页面,点击 key/value 进入 kv 管理页面,点击右上角 create 创建 kv。

consul 页面上添加 key 为:

config/springtest-service/data

value 为:

test:
  testValue:  consul--asdf34
testConfig:
  test-value: consul123--asdf34

生产环境中访问 Web 管理页面比较困难,此时需要通过命令去维护,一种方式是直接用 Consul 的命令去操作,但是需要运行在有 Consul 程序的机器上,还有另外一种通用的方式就是通过 webapi 访问:

获取

curl http://ip:8500/v1/kv/config/application/data

key 为 config/application/data

返回值:

[
    {
        "LockIndex": 0,
        "Key": "config/application/data",
        "Flags": 0,
        "Value": "dGVzdDoKICB0ZXN0VmFsdWU6ICBhcHBiYXNlLS1hc2RmMzQKdGVzdENvbmZpZzoKICB0ZXN0LXZhbHVlOiBhcHBiYXNlLS1hc2RmMzQ=",
        "CreateIndex": 1983705,
        "ModifyIndex": 1983711
    }
]

其中 value 是 base64 加密的,需要进行解密。

echo "dGVzdDoKICB0ZXN0VmFsdWU6ICBhcHBiYXNlLS1hc2RmMzQKdGVzdENvbmZpZzoKICB0ZXN0LXZhbHVlOiBhcHBiYXNlLS1hc2RmMzQ=" | base64 -d

得到如下明文结果:

test:
  testValue:  appbase--asdf34
testConfig:
  test-value: appbase--asdf34

完整的一次性获取命令:

 curl  http://ip:8500/v1/kv/config/application/data |grep "Value" | sed 's/        "Value": "//' | sed 's/",//' |base64 -d 

赋值

​    curl \
​        -X PUT \
​        -d "aaa1231
​    1234324qasd" \
​        http://ip:8500/v1/kv/my-key

删除

​    curl -X DELETE   http://ip:8500/v1/kv/my-key
5.2 开发实现

本示例以 IDEA+Maven 的方式进行开发。

首先是添加 pom.xml 的依赖。

<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-actuator</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-configuration-processor</artifactId>
</dependency>
<!--服务发现依赖-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>
<!--用于consul配置-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-consul-config</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.6</version>
</dependency>

然后是配置文件 bootstrap.yml。

server:
  port: 9201

spring:
  application:
    name: springtest-service
  profiles:
    active: dev
  cloud:
    consul:
      host: 127.0.0.1
      port: 8500
      discovery:
        register: true
        instance-id: ${spring.application.name}:${spring.cloud.client.ip-address}:${server.port}
        service-name: ${spring.application.name}
        port: 9201
        healthCheckPath: /actuator/health
        healthCheckInterval: 15s
      config:
        enabled: true
        format: YAML
        prefix: config
        defaultContext: application
        profileSeparator: ','
        data-key: data

注:discovery 段是添加到 consul 注册中心的。

config 段说明: enabled:true 允许配置中心

format:YAML 表示 consul 中的 key-value 中的 value 内容,采用 YAML 格式,据说有四种 YAML PROPERTIES KEY-VALUE FILES

prefix: config 表示 consul 用于存储配置的文件夹根目录名为 config

defaultContext: application 表示配置文件对应的默认应用名称(优先获取当前服务名称配置,没有的到 application 里找)

profileSeparator: ',' 表示如果有多个 profile(eg:开发环境 dev,测试环境 test……),则 key 名中的 profile 与 defaultContext 之间,用什么分隔符来表示(例如 config/springtest-service,dev/data)

data-key: data 表示最后一层节点的 key 值名称,一般默认为 data

接下来是一个配置读取类 TestConfig.java。

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;

@ConfigurationProperties(prefix = "test-config")
@Configuration
@Data
public class TestConfig {
    private String testValue;
}

最后是一个测试程序 App.java。

App.java

@EnableDiscoveryClient
@RestController
@SpringBootApplication
public class SpringtestServerApplication {

    @Autowired
    private DiscoveryClient discoveryClient;

    @Value("${test.testValue}")
    private String testValue;
    @Autowired
    private TestConfig  testConfig;

    public static void main(String[] args) {
        SpringApplication.run(SpringtestServerApplication.class, args);
    }
    @RequestMapping("/test")
    public String test(String id) {
        return "test"+id+"/"+testValue+"/"+testConfig.getTestValue();
    }
}

这里面采用了两种获取配置的方式:@Value 和 @ConfigurationProperties,都能进行配置获取,但是后者可以实现配置更新后动态更新。

5.3 配置优先级

优先级上面的最高。

config/testApp,dev/
config/testApp/
config/application,dev/
config/application/

6. 总结

综上,讲述了 Consul 如何部署, Spring Cloud 采用 Consul 作为注册中心和配置中心该如何实现,希望能够给大家带来帮助。


拓展阅读: 《案例上手 Spring 全家桶》

本文首发于 GitChat,未经授权不得转载,转载需与 GitChat 联系。

作者正在撰写中...
内容互动
写评论
加载更多
评论文章
× 订阅 Java 精选频道
¥ 元/月
订阅即可免费阅读所有精选内容