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这个错误的情况比较多,意思是负载均衡找不到那个服务,后面跟着服务名,配置错了,某一个名字写错了都可能触发,只要让他找不到就报这个错。上述解决办法适合一部分情况,手动配置多个服务以及负载均衡策略等
* 原因分析: 无意间进入下图位置,依次进入
* 最终看到下图链接中有访问 /actuator/health: 404 ,则加入actuator依赖即可
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的属性值与代码保持一致 代码读取为 foo.bar 界面配置为 foot.bar 导致读取失败
springCloud Consul+Config示例
注意事项
- 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