天天看点

springCloud Consul+Config示例

Consul下载
  • 由于官网下载很慢 https://www.consul.io/downloads.html
  • 采用Docker下载
docker pull consul
           
安装和运行
docker run -d --name consul -p 8500:8500 consul
           
进入容器
-- 此处需要用sh执行 因为容器是 alpine 的
docker exec -it 镜像id sh
           
Consul控制台
  • 访问 http://localhost:8500
    springCloud Consul+Config示例
Consul服务端
  • pom.xml依赖
<dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-consul-discovery</artifactId>
            <version>2.1.2.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- 此处不能少 Consul健康检查需要通过Consul控制台得知 -->
        <!-- 异常如下 ClientException: Load balancer does not have available server for client: consul-provider -->
		<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
    </dependencies>
           
  • application.yml配置
server:
  port: 9081
spring:
  application:
    name: consul-provider
  cloud:
    consul:
      host: localhost
      port: 8500
      discovery:
        service-name: ${spring.application.name}
           
  • ConsulProviderApplication启动类
package com.zbj.consul.provider;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

/**
 * ConsulProviderApplication
 *
 * @author weigang
 * @create 2019-08-10
 **/
@SpringBootApplication
@EnableDiscoveryClient
public class ConsulProviderApplication {
    public static void main(String[] args) {
        SpringApplication.run(ConsulProviderApplication.class, args);
    }
}
           
  • ConsulProviderController提供服务
package com.zbj.consul.provider;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

/**
 * ConsulProviderController
 *
 * @author weigang
 * @create 2019-08-10
 **/
@RestController
@RequestMapping("/consul/provider")
public class ConsulProviderController {

    @Value("${server.port}")
    private Integer port;

    @RequestMapping(value = "/hello", method = {RequestMethod.GET})
    public String hello(String world) {
        return "hello " + world + " " + port;
    }
}
           
  • 执行ConsulProviderApplication类main方法

    服务已经注册到Consul上(下图中红框中表示健康检测有问题,后面告知解决方案)

    springCloud Consul+Config示例
Consul客户端
  • pom.xml依赖(当前只作为消费者 则actuator依赖可以不需要)
<dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-consul-discovery</artifactId>
            <version>2.1.2.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
            <version>2.1.2.RELEASE</version>
        </dependency>
           
  • application.yml配置
server:
  port: 9083
spring:
  application:
    name: consul-consuler
  cloud:
    consul:
      host: localhost
      port: 8500
      discovery:
        service-name: ${spring.application.name}
        enabled: true
        #设置不需要注册到 consul 中
        register: false

# feign启用hystrix,才能熔断、降级
feign:
  hystrix:
    enabled: true
           
  • ConsulConsumerApplication启动类
package com.zbj.consul.consumer;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;

/**
 * ConsulConsumerApplication
 *
 * @author weigang
 * @create 2019-08-10
 **/
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class ConsulConsumerApplication {
    public static void main(String[] args) {
        SpringApplication.run(ConsulConsumerApplication.class, args);
    }

    @Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}
           
  • ConsulConsumerController对外提供服务
package com.zbj.consul.consumer;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

/**
 * ConsulConsumerController
 *
 * @author weigang
 * @create 2019-08-10
 **/
@RestController
@RequestMapping("/consul/consumer")
public class ConsulConsumerController {

    @Autowired
    private HelloService helloService;

    @Autowired
    private DiscoveryClient discoveryClient;

    @Autowired
    private LoadBalancerClient loadBalancerClient;

    @Autowired
    private RestTemplate restTemplate;

    @GetMapping("/hello")
    public String hello(@RequestParam(defaultValue = "world", required = false) String world) {
        return helloService.hello(world);
    }

    @GetMapping("/hello2")
    public String hello2(@RequestParam(defaultValue = "world", required = false) String world) {
        return restTemplate.getForObject("http://consul-provider/consul/provider/hello?world=" + world, String.class);
    }

    /**
     * 获取所有服务
     *
     * @return
     */
    @GetMapping("/services")
    public Object services() {
        return discoveryClient.getInstances("consul-provider");
    }

    /**
     * 从所有服务中选择一个服务(轮询)
     */
    @GetMapping("/discover")
    public Object discover() {
        // 不能调用 choose() 返回null 原因-> 提供者pom.xml需要依赖actuator
        return loadBalancerClient.choose("consul-provider").getUri().toString();
    }
}
           
  • Service层
package com.zbj.consul.consumer;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
 * HelloService
 *
 * @author weigang
 * @create 2019-08-10
 **/
@Component
public class HelloService {

    @Autowired
    private ConsulFeignClient consulFeignClient;

    public String hello(String world) {
        return consulFeignClient.sayHelloFromClientConsul(world);
    }
}
           
  • Feign客户端
package com.zbj.consul.consumer;

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;

/**
 * ConsulFeignClient
 *
 * @author weigang
 * @create 2019-08-10
 **/
@FeignClient(value = "consul-provider", fallback = FeignClientFallBack.class)
public interface ConsulFeignClient {

    /**
     * 调用提供者服务
     * 方法没有@RequestParam注解 则抛出异常 feign.FeignException$MethodNotAllowed: status 405 reading ConsulFeignClient#sayHelloFromClientConsul(String)
     *
     * @param world
     * @return
     */
    @RequestMapping(value = "/consul/provider/hello", method = {RequestMethod.GET})
    String sayHelloFromClientConsul(@RequestParam String world);
}
           
  • Feign熔断器
package com.zbj.consul.consumer;

import org.springframework.stereotype.Component;

/**
 * FeignClientFallBack
 *
 * @author weigang
 * @create 2019-08-10
 **/
@Component
public class FeignClientFallBack implements ConsulFeignClient {

    @Override
    public String sayHelloFromClientConsul(String world) {
        return "hello error";
    }
}
           
  • 启动 ConsulConsumerApplication
  • 访问 http://localhost:9083/consul/consumer/hello (FeignClient)
    springCloud Consul+Config示例
  • 访问 http://localhost:9083/consul/consumer/hello2 (RESTful)
    springCloud Consul+Config示例
服务注册与发现问题集锦
  • ConsulFeignClient.sayHelloFromClientConsul() 参数取消@RequestParam, 取消@FeignClient属性fallback, 如下图
    springCloud Consul+Config示例
  • 异常描述: feign.FeignException$MethodNotAllowed: status 405 reading ConsulFeignClient#sayHelloFromClientConsul(String)
    springCloud Consul+Config示例
    • 原因分析: 在Feign调用的时候,接收的参数需要加上@Requestparam注解
  • ConsulFeignClient类上加注解 @RequestMapping("/consul/provider")
    • 当提供者对外服务类上有注解时存在这种需求
    • 异常描述: Caused by: java.lang.IllegalStateException: Ambiguous mapping. Cannot map ‘com.zbj.consul.consumer.ConsulFeignClient’ method
    • 原因分析: 应用于整个提供者,再类上加某个提供者的@RequestMapping显然不合适(个人认为 勿喷)
    • 解决办法: 在FeignClient对应方法上加全路径 如下图
      springCloud Consul+Config示例
  • 注释提供者pom.xml文件中 spring-boot-starter-actuator 依赖, 取消@FeignClient属性fallback
    • 异常描述: com.netflix.client.ClientException: Load balancer does not have available server for client: consul-provider
    • 普通解决方法 错误网上搜了很多文章,提供办法最多的是在配置文件里加入一下代码
      ribbon.eureka.enabled=false
      -- 但此处没有使用eureka
                 
    • 以及如下方式 (没有测试过)
# 这个BaseRemote是加了@FeignClient注解的类
# 服务提供者的地址,不是服务注册中心的地址
BaseRemote.ribbon.listOfServers=http://localhost:8086
           
* 异常解析: 直说出现Load balancer does not have available server for client这个错误的情况比较多,意思是负载均衡找不到那个服务,后面跟着服务名,配置错了,某一个名字写错了都可能触发,只要让他找不到就报这个错。上述解决办法适合一部分情况,手动配置多个服务以及负载均衡策略等
* 原因分析: 无意间进入下图位置,依次进入
           
springCloud Consul+Config示例

* 最终看到下图链接中有访问 /actuator/health: 404 ,则加入actuator依赖即可

springCloud Consul+Config示例
Consul Config(服务配置中心)
此处基于consul-provider改造 pom.xml增加依赖
           
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-consul-config</artifactId>
    <version>2.1.2.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-configuration-processor</artifactId>
    <optional>true</optional>
</dependency>
           
  • application.yml增加环境配置
spring:
  profiles:
    active: test
           
  • 新建bootstrap.xml配置
spring:
  cloud:
    consul:
      host: localhost
      port: 8500
      discovery:
        service-name: ${spring.application.name}
      #      关于spring.cloud.consul.config的配置项描述如下:
      #
      #      enabled 设置config是否启用,默认为true
      #      format 设置配置的值的格式,可以yaml和properties
      #      prefix 设置配的基本目录,比如config
      #      defaultContext 设置默认的配置,此处为应用名
      #      profileSeparator profiles配置分隔符,默认为‘,’
      #      date-key为应用配置的key名字,值为整个应用配置的字符串
      config:
        enabled: true
        format: YAML
        prefix: config
        profile-separator: ":"
        data-key: data
        default-context: ${spring.application.name}
  application:
    name: consul-provider
           
  • 由于要读取application.name,则将discovery配置迁到bootstrap.xml中
  • 访问K/V存储界面 即 http://localhost:8500/ui/dc1/kv 创建一条记录
    • key为 config/consul-provider:test/data
    • value如下
    # 换行不能按tab键 启动报错 
    # 最后复制项有个空格(bar:后有一个空格)
    foo:
      bar: 123123
               
  • 新建API 读取配置中心值
package com.zbj.consul.provider;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * FooBarController
 *
 * @author weigang
 * @create 2019-08-10
 **/
@RestController
// Spring Cloud Comsul Config默认就支持动态刷新,只需要在需要动态刷新的类上加上@RefreshScope注解即可
@RefreshScope
public class FooBarController {

    @Value("${foo.bar}")
    String fooBar;

    @Autowired
    private Foo foo;

    @GetMapping("/foo")
    public String foo() {
        return fooBar;
    }

    @GetMapping("/foo1")
    public String foo1() {
        return foo.getBar();
    }

    public String getFooBar() {
        return fooBar;
    }

    public void setFooBar(String fooBar) {
        this.fooBar = fooBar;
    }
}
           
  • 访问 http://localhost:9081/foo 或者 http://localhost:9081/foo1
    springCloud Consul+Config示例
    都是刚刚配置的值
  • 现修改管理界面配置
    springCloud Consul+Config示例
  • 配置已经动态刷新
    springCloud Consul+Config示例
Consul配置中心问题集锦
  • yaml配置中不能使用TAB
    springCloud Consul+Config示例
    springCloud Consul+Config示例
  • spring.cloud.consul.config配置需要配置在bootstrap.xml中
    • 详见 https://blog.csdn.net/snow_7/article/details/88391192
  • 注意界面配置K/V的属性值与代码保持一致
    springCloud Consul+Config示例
    代码读取为 foo.bar 界面配置为 foot.bar 导致读取失败
注意事项
  • consul支持的KV存储的Value值不能超过512KB
  • Consul的dev模式,所有数据都存储在内存中,重启Consul的时候会导致所有数据丢失,在正式的环境中,Consul的数据会持久化,数据不会丢失
源码地址
  • https://gitee.com/weigang200820/spring-cloud-demo consul-provider和consul-consumer 模块
参考博文
  • https://blog.csdn.net/yp090416/article/details/79262176 (docker exec: “/bin/bash”: stat /bin/bash: no such file or directory)
  • https://blog.csdn.net/weixin_44685751/article/details/92605561 (status 405 reading)
  • https://blog.csdn.net/DT_Zhangshuo/article/details/93028990 (Load balancer does not have available server for client)
  • https://blog.csdn.net/snow_7/article/details/88391192 (配置文件bootstrap与application的作用以及区别)
  • https://blog.csdn.net/love_zngy/article/details/82216696

继续阅读