Spring Cloud 微服务教程(二)
文章目录
- Spring Cloud 微服务教程(二)
- 1.教程大纲
- 2.Feign客户端-声明式REST调用
- 3.服务网关 Spring Cloud Zuul
- 4.分布式配置中心 Spring Cloud Config
- 5.消息总线 Spring Cloud Bus
- 6.整合consul&zookeeper作为注册中心
- 7.整合swagger API管理
1.教程大纲
1、Feign声明式客户端(REST调用)
2、服务网关 Spring Cloud Zuul
3、分布式配置中心Spring Cloud Config
4、消息总线 Spring Cloud Bus
5、整合consul、zookeeper作为注册中心
6、整合swagger 统一管理API
2.Feign客户端-声明式REST调用
2.1.分析
之前《SpringCloud教程(一)》中我们通过RestTemplate调用REST服务,代码是这样的:
@HystrixCommand(fallbackMethod = "queryItemByIdFallbackMethod")
public Item queryItemById(Long id) {
String itemUrl = "http://app-item/item/{id}";
Item result = restTemplate.getForObject(itemUrl, Item.class, id);
return result;
}
- 1
- 2
- 3
- 4
- 5
- 6
虽然使用了Ribbon和Hystrix可以实现负载均衡和容错处理,但是这个编码在实现大量业务时会显得太过于冗余(如,多参数的URL拼接)。
思考:有没有更加优雅的实现呢?
2.2.Feign的简介
项目主页:https://github.com/OpenFeign/feign
2.3.快速入门
在订单微服务microservice order中增加对Feign的支持。
2.3.1.导入依赖
<!--springboot 整合fegnin客户端-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
- 1
- 2
- 3
- 4
- 5
2.3.2.创建一个ItemFeignClient接口
package com.zpc.order.feign;
import com.zpc.order.entity.Item;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
/**
* 申明这是一个Feign客户端,并且指明服务id
* @author Evan
*/
@FeignClient(value = "app-item")
public interface ItemFeignClient {
/**
* 这里定义了类似于SpringMVC用法的方法,就可以进行RESTful方式的调用了
*
* @param id
* @return
*/
@RequestMapping(value = "/item/{id}", method = RequestMethod.GET)
Item queryItemById(@PathVariable("id") Long id);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
2.3.3.改造ItemService
@Autowired
private ItemFeignClient itemFeignClient;
@HystrixCommand(fallbackMethod = “queryItemByIdFallbackMethod”)
public Item queryItemById3(Long id) {
String itemUrl = “http://app-item/item/{id}”;
Item result = itemFeignClient.queryItemById(id);
System.out.println("===========HystrixCommand queryItemById-线程池名称:" + Thread.currentThread().getName() + “订单系统调用商品服务,result:” + result);
return result;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
2.3.4.在启动类中添加 @EnableFeignClients 注解
package com.zpc.order.runner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.hystrix.EnableHystrix;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.http.client.OkHttp3ClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;
@SpringBootApplication//申明这是一个Spring Boot项目
@EnableEurekaClient
@EnableHystrix
@EnableFeignClients(basePackages =“com.zpc.order.feign”)
@ComponentScan(basePackages = {“com.zpc.order.controller”, “com.zpc.order.service”,“com.zpc.order.properties”})//手动指定bean扫描范围
public class OrderApp {
public static void main(String[] args) {
SpringApplication.run(OrderApp.class, args);
}
/**
* 向Spring容器中定义RestTemplate对象
* @return
*/
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate(new OkHttp3ClientHttpRequestFactory());
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
2.3.5.重启测试
测试结果,一切正常。
2.4.到底发生了什么?
@Autowired
private ItemFeignClient itemFeignClient;
@HystrixCommand(fallbackMethod = “queryItemByIdFallbackMethod”)
public Item queryItemById3(Long id) {
String itemUrl = “http://app-item/item/{id}”;
//Item result = restTemplate.getForObject(itemUrl, Item.class, id);
Item result = itemFeignClient.queryItemById(id);
return result;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
写这样的代码,就可以访问RESTful服务啦?
流程分析:
1、由于我们在入口程序使用了@EnableFeignClients
注解,Spring启动后会扫描标注了@FeignClient
注解的接口,然后生成代理类。
2、我们在@FeignClient
接口中指定了value,其实就是指定了在Eureka
中的服务名称。
3、在FeignClient
中的定义方法以及使用了SpringMVC
的注解,Feign
就会根据注解中的内容生成对应的URL,然后基于Ribbon
的负载均衡去调用REST
服务。
为什么使用的是SpringMVC
的注解?
i.其实,Feign是有自己的注解的@RequestLine
,是因为Spring Cloud对Feign做了增强,兼容了SpringMVC的注解,使我们的学习成本更低
ii.专业的解释是这样的:
org.springframework.cloud.netflix.feign.FeignClientsConfiguration
设置的默认契约是SpringMVC契约。
2.5.Feign的多参数构造
服务方参数列表使用注解@RequestParam
、@PathVariable
修饰
2.6.设置统一的hystrix fallback接口
参考:http://www.ityouknow.com/springcloud/2017/05/16/springcloud-hystrix.html
一般在实际开发中fallback 方法不会直接写在接口方法所在类里,那样太杂乱,例如之前订单工程中的写法:
@HystrixCommand(fallbackMethod = "queryItemByIdFallbackMethod")
public Item queryItemById3(Long id) {
Item result = itemFeignClient.queryItemById(id);
System.out.println("===========HystrixCommand queryItemById-线程池名称:" + Thread.currentThread().getName() + "订单系统调用商品服务,result:" + result);
return result;
}
public Item queryItemByIdFallbackMethod(Long id) {
return new Item(id, “查询商品信息出错!”, null, null, null);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
Order工程改进(将fallback方法放到类中):
1.不在方法上使用@HystrixCommand
注解
//@HystrixCommand(fallbackMethod = "queryItemByIdFallbackMethod")
public Item queryItemById3(Long id) {
Item result = itemFeignClient.queryItemById(id);
System.out.println("===========HystrixCommand queryItemById-线程池名称:" + Thread.currentThread().getName() + "订单系统调用商品服务,result:" + result);
return result;
}
- 1
- 2
- 3
- 4
- 5
- 6
2.创建回调类
package com.zpc.order.fallback;
import com.zpc.order.entity.Item;
import com.zpc.order.feign.ItemFeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.PathVariable;
/**
* 此类中的方法专门用于服务降级,该类一般要实现调用远程服务的接口(这样保证方法名一致)
*/
@Component
public class ItemServiceFallback implements ItemFeignClient {
/**
* 服务降级的方法要和原方法一致(名称、参数列表)
* @param id
* @return
*/
@Override
public Item queryItemById(@PathVariable("id") Long id) {
return new Item(null, "服务降级方法queryItemById", null, "服务降级方法queryItemById", null);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
3.在Feign客户端中添加fallback
属性
package com.zpc.order.feign;
import com.zpc.order.entity.Item;
import com.zpc.order.fallback.ItemServiceFallback;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
/**
* 申明这是一个Feign客户端,并且指明服务id
* 实际开发中ItemFeignClient一般直接继承(extends)服务提供方的接口以避免代码重复(例如Item工程会以jar包的形式提供ItemService接口)
* @author Evan
*/
@FeignClient(value = "app-item",fallback = ItemServiceFallback.class)
public interface ItemFeignClient{
/**
* 这里定义了类似于SpringMVC用法的方法,就可以进行RESTful方式的调用了
*
* @param id
* @return
*/
@RequestMapping(value = "/item/{id}", method = RequestMethod.GET)
Item queryItemById(@PathVariable("id") Long id);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
4.配置文件中开启hystrix
#开启hystrix断路器
feign:
hystrix:
enabled: true
- 1
- 2
- 3
- 4
5.修改启动类,增加包扫描
@ComponentScan(basePackages = {"com.zpc.order.controller", "com.zpc.order.service","com.zpc.order.properties","com.zpc.order.fallback"})//手动指定bean扫描范围
- 1
6.重新启动应用测试
服务都正常的情况:
停止Item服务,访问Order服务没有配置hystrix熔断的普通方法:
停止Item服务,访问Order服务中配置了hystrix熔断的方法:
配置fallback类OK!
3.服务网关 Spring Cloud Zuul
3.1.分析
通过前面的学习,使用Spring Cloud实现微服务的架构基本成型,大致是这样的:
-
我们使用
Spring Cloud Netflix
中的Eureka作为服务注册中心完成服务注册与发现;而服务间通过Feign
和Ribbon
实现服务的消费以及负载均衡;通过Spring Cloud Config
实现了应用多环境的外部化配置以及版本管理。为了使得服务集群更为健壮,使用Hystrix
的融断机制来避免在微服务架构中个别服务出现异常时引起的故障蔓延。 -
思考:在该架构中,我们的服务集群包含:内部服务Service A和Service B,他们都会注册与订阅服务至
Eureka Server
,而Open Service是一个对外的服务,通过负载均衡公开至服务调用方。我们把焦点聚集在对外服务这块,这样的实现是否合理,或者是否有更好的实现方式呢?
先来说说这样架构需要做的一些事儿以及存在的不足:
- 首先,破坏了服务无状态特点。
为了保证对外服务的安全性,我们需要实现对服务访问的权限控制,而开放服务的权限控制机制将会贯穿并污染整个开放服务的业务逻辑,这会带来的最直接问题是,破坏了服务集群中REST API无状态的特点。
从具体开发和测试的角度来说,在工作中除了要考虑实际的业务逻辑之外,还需要额外可续对接口访问的控制处理。 - 其次,无法直接复用既有接口。
当我们需要对一个既有的集群内的接口,实现外部访问时,我们不得不通过在原有接口上增加校验逻辑,或增加一个代理调用来实现权限控制,无法直接复用原有的接口。
面对类似上面的问题,我们要如何解决呢? 答案是:服务网关
!
-
为了解决上面这些问题,我们需要将权限控制、日志收集这样的东西从我们的服务单元中抽离出去,而最适合这些逻辑的地方就是处于对外访问最前端的地方,我们需要一个更强大一些的均衡负载器 服务网关。
-
服务网关是微服务架构中一个不可或缺的部分。通过服务网关统一向外系统提供REST API的过程中,除了具备服务路由、负载均衡功能之外,它还具备了权限控制等功能。Spring Cloud Netflix中的Zuul就担任了这样的一个角色,为微服务架构提供了前门保护的作用,同时将权限控制这些较重的非业务逻辑内容迁移到服务路由层面,使得服务集群主体能够具备更高的可复用性和可测试性。
3.2.Zuul的简介
官网:https://github.com/Netflix/zuul
3.3.使用Zuul之后的架构
从架构图中可以看出,客户端请求微服务时,先经过Zuul之后再请求,这样就可以将一些类似于校验的业务逻辑放到zuul中完成。
而微服务自身只需要关注自己的业务逻辑即可。当然在Zuul上层也可以搭建Nginx、F5
等负载均衡设施。
3.4.快速入门
3.4.1.创建工程microsrvice-api-gateway
3.4.2.导入依赖
<?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>
<groupId>com.zpc.microservice</groupId> <artifactId>microservice-api-gateway</artifactId> <version>1.0-SNAPSHOT</version> <packaging>jar</packaging> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.6.RELEASE</version> </parent> <dependencyManagement> <dependencies> <!-- 导入Spring Cloud的依赖管理 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Finchley.SR1</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies>
<!–整合zuul网关–>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
3.4.3.编写启动类ApiGatewayApplication
package com.zpc.gateway;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
@EnableZuulProxy
@SpringBootApplication
public class ApiGatewayApplication {
public static void main(String[] args) {
SpringApplication.run(ApiGatewayApplication.class, args);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
3.4.4.编写application.yml文件
server:
port: 8087 #服务端口
spring:
application:
name: app-zuul-gateway #指定服务名
- 1
- 2
- 3
- 4
- 5
3.4.5.编写路由规则
我们编写路由规则:
server:
port: 8087 #服务端口
spring:
application:
name: app-zuul-gateway #指定服务名
zuul:
routes: #定义服务转发规则
item-service: #item-service这个名字任意取的
path: /item-service/** #配置请求URL的请求规则
url: http://127.0.0.1:8081 #真正的微服务地址,path匹配的请求都转发到这里
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
3.4.6.启动测试
同时启动商品微服务工程、网关工程。
可以看到,已经通过zuul访问到了商品微服务。
3.5.面向服务的路由
在快速入门中我们已经通过了URL映射,访问到了商品微服务。这样做会存在一个问题,就是,如果商品微服务的地址发生了改变怎么办?
很自然的能够想到,不应该配置具体的url而是走Eureka注册中心获取地址。
3.5.1.添加Eureka服务的依赖
<!--整合eureka客户端-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
- 1
- 2
- 3
- 4
- 5
3.5.2.修改application.yml配置文件
server:
port: 8087 #服务端口
spring:
application:
name: app-zuul-gateway #指定服务名
###服务注册到eureka注册中心的地址
eureka:
client:
service-url:
defaultZone: http://zpc:123456@127.0.0.1:8100/eureka/,http://zpc:123456@127.0.0.1:9100/eureka/
###因为该应用为服务提供者,是eureka的一个客户端,需要注册到注册中心
register-with-eureka: true
###是否需要从eureka上检索服务
fetch-registry: true
instance:
prefer-ip-address: true #将自己的ip地址注册到Eureka服务中
ip-address: 127.0.0.1
instance-id: ${spring.application.name}###${server.port} #指定实例id
zuul:
routes: #定义服务转发规则
item-service: #item-service这个名字是任意写的
path: /item-service/** #匹配item-service的请求app-item服务
#url: http://127.0.0.1:8081 #真正的微服务地址
serviceid: app-item
order-service: #名字尽量和业务系统相关
path: /order-service/** #匹配order-service的请求app-order服务
serviceid: app-order
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
3.5.3.启动测试
启动Eureka注册中心、Item服务、order服务、网管服务4个工程。
查看Eureka注册中:
发现已经有gateway在注册中心了。
接下来测试,功能是否正常:
发现一切正常。
3.6.zuul配置详解
3.6.1.指定服务id
3.6.2.忽略指定服务
3.6.3.忽略所有服务,只是有路由指定
3.6.4.同时配置path和url
3.6.5.面向服务配置,不破坏Hystrix、Ribbon特性
zuul:
routes: #定义服务转发规则
item-service: #item-service这个名字是任意写的
path: /item-service/** #匹配item-service的url路径请求app-item服务
#url: http://127.0.0.1:8081 #真正的微服务地址
serviceid: app-item
order-service: #名字尽量和业务系统相关
path: /order-service/** #匹配order-service的url路径请求app-order服务
serviceid: app-order #指定Eureka注册中心的服务id
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
3.6.6.使用正则表达式指定路由规则
3.6.7.路由前缀
3.6.8.忽略某些路径
3.7.过滤器
过滤器是Zuul的重要组件。
3.7.1.过滤器ZuulFilter
ZuulFilter是一个抽象类,其实现类需要实现4个方法:
- shouldFilter:返回一个Boolean值,判断该过滤器是否需要执行。返回true执行,返回false不执行。
- run:过滤器的具体业务逻辑。
- filterType:返回字符串代表过滤器的类型
a)pre:请求在被路由之前执行
b)routing:在路由请求时调用
c)post:在routing和errror过滤器之后调用
d)error:处理请求时发生错误调用 - filterOrder:通过返回的int值来定义过滤器的执行顺序,数字越小优先级越高。
3.7.2.执行流程
3.8.过滤器实战
需求:通过编写过滤器实现用户是否登录的检查。
实现:通过判断请求中是否有token,如果有认为就是已经登录的,如果没有就认为是非法请求,响应401.
3.8.1.编写UserLoginZuulFilter
package com.zpc.gateway.filter; import com.netflix.zuul.ZuulFilter; import com.netflix.zuul.context.RequestContext; import org.apache.commons.lang.StringUtils; import org.springframework.stereotype.Component; import javax.servlet.http.HttpServletRequest; /** * 网关过滤器 * 加入到Spring容器 * @author Evan */ @Component public class UserLoginZuulFilter extends ZuulFilter {
@Override public boolean shouldFilter() { return true; // 该过滤器需要执行 } @Override public Object run() { //编写业务逻辑 RequestContext requestContext = RequestContext.getCurrentContext(); HttpServletRequest request = requestContext.getRequest(); String token = request.getParameter("token"); if(StringUtils.isEmpty(token)){ requestContext.setSendZuulResponse(false); // 过滤该请求,不对其进行路由 requestContext.setResponseStatusCode(401); // 设置响应状态码 requestContext.setResponseBody(" token is empty!!"); // 设置响应状态码 return null; } return null; } @Override public String filterType() { return "pre"; // 设置过滤器类型为:pre } @Override public int filterOrder() { return 0;// 设置执行顺序为0 }
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
入口程序:
package com.zpc.gateway.runner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
import org.springframework.context.annotation.ComponentScan;
@EnableZuulProxy
@SpringBootApplication
@ComponentScan(basePackages = “com.zpc.gateway.filter”)
public class ApiGatewayApplication {
public static void main(String[] args) {
SpringApplication.run(ApiGatewayApplication.class, args);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
3.8.2.启动测试
带token正常访问:
可以看到过滤器已经生效。
3.9.默认开启ribbon
Zuul网关默认开启了 ribbon 负载均衡,可以修改端口,启动多个Item服务进行测试,不断刷新浏览器请求:
http://localhost:8087/item-service/item/2?token=1
发现多个Item服务被轮询的访问:
service port:8080
service port:8081
3.10.动态网关
将yml配置文件中的路由配置信息存在配置中心中可以实现网关的动态配置。请见4.8
节。
3.11.网关集群
Zuul可以配合nginx搭建网关集群。只要在nginx的配置文件nginx.conf
里配置多个zuul地址:
http {
upstream myhttpServer{
#配置多个zuul地址
server localhost:8087;
server localhost:8086;
}
server {
listen 80;
server_name www.zpc.com;
location / {
proxy_pass http://myhttpServer;
index index.html index.htm;
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
架构图:
浏览器访问:http://www.zpc.com/item-service/item/2?token=1
4.分布式配置中心 Spring Cloud Config
4.1.之前的配置文件用法存在什么问题?
- 我们开发项目时,需要有很多的配置项需要写在配置文件中,如:数据库的连接信息等。
- 这样看似没有问题,但是我们想一想,如果我们的项目已经启动运行,那么数据库服务器的ip地址发生了改变,我们该怎么办?
- 如果真是这样,我们的应用需要重新修改配置文件,然后重新启动,如果应用数量庞大,那么这个维护成本就太大了!
- 有没有好的办法解决呢?当然是有的,
Spring Cloud Config
提供了这样的功能,可以让我们统一管理配置文件,以及实时同步更新,并不需要重新启动应用程序。
4.2.Spring Cloud Config 简介
Config Server是一个可横向扩展、集中式的配置服务器,它用于集中管理应用程序各个环境下的配置,默认使用Git存储配置文件内容,也可以使用SVN存储,或者是本地文件存储。
Config Client是Config Server的客户端,用于操作存储在Config Server中的配置内容。微服务在启动时会请求Config Server获取配置文件的内容,请求到后再启动容器。
使用Spring Cloud Config的架构:
4.3.快速入门 – 搭建Config Server
4.3.1.准备3个配置文件,推送到Git服务器
准备3个文件(也可以使用yml文件):
microservice-dev.properties
microservice-prod.properties
microservice-test.properties
- 1
- 2
- 3
该文件的命名规则是:{application}-{profile}.properties
其内容是(另外2个文件内容稍有不同即可):
jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true
jdbc.username=root
jdbc.password=123456
- 1
- 2
- 3
- 4
推送文件到git服务器,这里使用的是码云,当然也可以使用github或者使用svn。使用码云创建一个项目(私有项目需要账号密码)
4.3.2.创建工程microservice-config-server
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>
<groupId>com.zpc.microservice</groupId> <artifactId>microservice-config-server</artifactId> <version>1.0-SNAPSHOT</version> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.6.RELEASE</version> </parent> <dependencyManagement> <dependencies> <!-- 导入Spring Cloud的依赖管理 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Finchley.SR1</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <!--整合配置中心--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-config-server</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>
</project>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
4.3.3.编写入口ConfigApplication
package com.zpc.configcenter;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.config.server.EnableConfigServer;
/**
* 开启配置服务
*/
@EnableConfigServer
@SpringBootApplication
public class ConfigApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigApplication.class, args);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
4.3.4.编写application.yml
server:
port: 7788 #服务端口
spring:
application:
name: microservice-config-server #指定服务名
cloud:
config:
server:
git: #配置git仓库地址
uri: https://gitee.com/hellpzpc/myspconfig.git
search-paths:
- myspringcloudconfig #配置文件目录地址
username: 3747xxxx@qq.com #码云账号(公有项目不需要设置)
password: m86xxx #码云密码(公有项目不需要设置)
label: master #分支名称
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
4.3.5.启动测试
测试已经看到了配置文件的内容。
请求配置文件的规则如下:
/{application}/{profile}/[label]
/{application}-{profile}.yml
/{label}/{application}-{profile}.yml
/{application}-{profile}.properties
/{label}/{application}-{profile}.properties
- 1
- 2
- 3
- 4
- 5
其中{label}是指分支,默认是master。
4.4.快速入门 – 搭建Config Client
我们在microservice-item项目中添加Config Client的支持。来读取JDBC的配置文件的内容。
4.4.1.导入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-client</artifactId>
</dependency>
- 1
- 2
- 3
- 4
4.4.2.新增配置文件bootstrap.yml
spring:
cloud:
config:
uri: http://127.0.0.1:7788/ #配置中心的地址
profile: dev #对应配置服务中的{profile}
label: master #对应的分支
- 1
- 2
- 3
- 4
- 5
- 6
4.4.3.编写JdbcConfigBean
编写对象通过@Value
注解读取Config Server中的值。
package com.zpc.item.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class JdbcConfigBean {
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
@Value("${jdbc.driverClassName}")
private String driverClassName;
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
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;
}
public String getDriverClassName() {
return driverClassName;
}
public void setDriverClassName(String driverClassName) {
this.driverClassName = driverClassName;
}
@Override
public String toString() {
return "JdbcConfigBean [url=" + url + ", username=" + username
+ ", password=" + password + ", driverClassName="
+ driverClassName + "]";
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
4.4.4.编写测试方法进行测试
在ItemController中增加testconfig方法:
package com.zpc.item.controller; import com.zpc.item.config.JdbcConfigBean; import com.zpc.item.entity.Item; import com.zpc.item.service.ItemService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RestController; /** * @author Evan */ @RestController public class ItemController { @Value("${server.port}") private String port; @Autowired private ItemService itemService; @Autowired private JdbcConfigBean jdbcConfigBean; /** * 对外提供接口服务,查询商品信息 * * @param id * @return */ @GetMapping(value = "item/{id}") public Item queryItemById(@PathVariable("id") Long id) { System.out.println("service port:"+port); return this.itemService.queryItemById(id); }
@GetMapping(value = "testconfig") public String testconfig(){ return this.jdbcConfigBean.toString(); }
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
4.4.5.启动测试
测试结果显示,已经从Config Server中获取到配置文件的内容。
4.5.手动更新运行中的配置文件
如果git服务器中的配置文件更新了怎么办?正在运行的应用中的配置内容如何更新?
4.5.1.测试
现在我们更改git服务器中的配置文件内容,端口改成8888:
然后刷新Config Server地址观察:
可以看到这里查询到的是新的数据,端口已经更新为8888。
但是在Config Client中测试:
看到依然是旧的数据。
如何才能在重启应用的情况下,获取到最新的配置文件内容呢? – 为Config Client添加refresh支持。
4.5.2.加入依赖
使用actuator 监控中心完成刷新功能:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
- 1
- 2
- 3
- 4
4.5.3.为JdbcConfigBean添加@RefreshScope注解
需要为动态更新配置内容的bean添加@RefreshScope注解。
@Component
@RefreshScope
public class JdbcConfigBean {...//代码省略}
- 1
- 2
- 3
4.5.4.修改application.yml文件
###服务端口号(本身是一个web项目)
server:
port: 8080
###起个名字作为服务名称(该服务注册到eureka注册中心的名称,比如商品服务)
spring:
application:
name: app-item
###服务注册到eureka注册中心的地址
eureka:
client:
service-url:
defaultZone: http://zpc:123456@127.0.0.1:8100/eureka/,http://zpc:123456@127.0.0.1:9100/eureka/
###因为该应用为服务提供者,是eureka的一个客户端,需要注册到注册中心
register-with-eureka: true
###是否需要从eureka上检索服务
fetch-registry: true
instance:
prefer-ip-address: true #将自己的ip地址注册到Eureka服务中
ip-address: 127.0.0.1
instance-id: ${spring.application.name}###${server.port} #指定实例id
#开启所有端点
management:
endpoints:
web:
exposure:
include: "*"
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
4.5.5.重启做测试
加了actuator依赖后,启动项目控制台输出一行:
2018-11-04 21:17:19.749 INFO 187328 --- [main] s.b.a.e.w.s.WebMvcEndpointHandlerMapping : Mapped "{[/actuator/refresh],methods=[POST],produces=[application/vnd.spring-boot.actuator.v2+json || application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.web.servlet.AbstractWebMvcEndpointHandlerMapping$OperationHandler.handle(javax.servlet.http.HttpServletRequest,java.util.Map<java.lang.String, java.lang.String>)
- 1
为了测试,需要手动发起post请求actuator接口:
POST请求监控中心 http://localhost:8080/actuator/refresh
使用接口测试工具(例如postman)发送post请求actuator/refresh来更新配置内容:
响应:
刷新可以看到有配置文件内容更新了。
可以看到应该更新了最新的配置文件内容,我们也就实现了在未重启项目的情况下实现了动态修改配置文件内容。
但是,项目已经发布上线了,不可能人为的守在服务器前,发现有更新了就手动请求监控中心的refresh接口。
是否自动完成呢?
4.6.借助与git的webhook(web钩子)实现自动更新
码云、github等git服务器提供了web hook功能,意思是,在仓库中的资源发生更新时会通知给谁,这里的谁是一个url地址。
可以在命令行查看服务器ip地址,把外部(例如码云)能访问到的你的服务器ip设置为推送地址。
接下来进行测试,更新配置文件的内容。
测试的结果会发现,配置文件内容会动态更到Bean中。
(要使自己的服务能够被外部访问到需要做端口映射)
总结下流程:
4.7.Config Client配置优化-注册到Eureka中
4.7.1.分析
在 microservice-item中作为Config Client,在配置文件中了写死了Config Server的地址:
spring:
cloud:
config:
name: microservice #对应配置中心的应用名称,不写默认是本应用名,即spring.application.name
uri: http://127.0.0.1:7788/
profile: dev #对应配置服务中的{profile}
label: master #对应的分支
- 1
- 2
- 3
- 4
- 5
- 6
- 7
这样的硬编码是不好的,如果配置中心的ip地址发生了改变,那么久需要重新修改并且重启应用了。
想想,有什么好的办法解决呢? 如果将Config Server作为一个微服务,并且将其注册的Eureka中,是不是就可以不用硬编码了?
4.7.2.在 microservice-config-server中添加Eureka的依赖
<!--整合eureka客户端-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
- 1
- 2
- 3
- 4
- 5
4.7.3.修改application.yml文件
server:
port: 7788 #服务端口
spring:
application:
name: microservice-config-server #指定服务名
cloud:
config:
server:
git: #配置git仓库地址
uri: https://gitee.com/hellpzpc/myspconfig.git
search-paths:
- myspringcloudconfig #配置文件目录地址
username: 3xxxx@qq.com #码云账号(公有项目不需要设置)
password: mx8xxx2 #码云密码(公有项目不需要设置)
label: master #分支
###服务注册到eureka注册中心的地址
eureka:
client:
service-url:
defaultZone: http://zpc:123456@127.0.0.1:8100/eureka/,http://zpc:123456@127.0.0.1:9100/eureka/
###因为该应用为服务提供者,是eureka的一个客户端,需要注册到注册中心
register-with-eureka: true
###是否需要从eureka上检索服务
fetch-registry: true
instance:
prefer-ip-address: true #将自己的ip地址注册到Eureka服务中
instance-id: ${spring.application.name}###${server.port} #指定实例id
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
4.7.4.ConfigApplication添加@EnableEurekaClient注解
/**
* 开启配置服务
*/
@EnableConfigServer
@SpringBootApplication
@EnableEurekaClient
public class ConfigApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigApplication.class, args);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
4.7.5.在microservice-item中修改bootstrap.yml配置
###服务注册到eureka注册中心的地址
eureka:
client:
service-url:
defaultZone: http://zpc:123456@127.0.0.1:8100/eureka/,http://zpc:123456@127.0.0.1:9100/eureka/
###因为该应用为服务提供者,是eureka的一个客户端,需要注册到注册中心
register-with-eureka: true
###是否需要从eureka上检索服务
fetch-registry: true
instance:
prefer-ip-address: true #将自己的ip地址注册到Eureka服务中
ip-address: 127.0.0.1
instance-id: ${spring.application.name}###${server.port} #指定实例id
spring:
cloud:
config:
name: microservice #对应配置中心的应用名称,默认是本应用名,即spring.application.name,该名称要和git中的配置一致
#uri: http://127.0.0.1:7788/
profile: dev #对应配置服务中的{profile}
label: master #对应的分支
discovery:
enabled: true #启用发现服务功能
service-id: microservice-config-server #指定配置中心工程的名称
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
疑问:在application.yml中以及配置Eureka的信息,为什么在bootstrap.yml还需要配置?
因为在Spring Boot中bootstrap.yml在application.yml之前加载,所以即使在application.yml中以及配置Eureka的信息,是使用不了的,所以需要在bootstrap.yml中配置Eureka的信息。
4.7.6.测试
端口改成9999:
发post请求手动刷新:
查看最新配置:
测试结果,一切正常。这就完美解决了硬编码的问题。
4.8.动态网关
传统方式将zuul路由规则配置在配置文件中,如果修改了路由规则,无疑需要重启服务器。可以将配置文件存在配置中心,利用SpringCloud Config实现动态路由规则添加。
1.在git服务器上创建配置文件service-zuul-dev.yml,注释掉网关工程yml文件中的zuul配置,把配置拷到service-zuul-dev.yml中。
注释掉网关工程microservice-api-gateway中的配置,并开启actuator:
2.在网关工程的pom中引入actuator依赖:
<!--开启监控中心-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
- 1
- 2
- 3
- 4
- 5
3.在网关工程新建bootstrap.yml,启用spring cloud config 功能:
###服务注册到eureka注册中心的地址
eureka:
client:
service-url:
defaultZone: http://zpc:123456@127.0.0.1:8100/eureka/,http://zpc:123456@127.0.0.1:9100/eureka/
###因为该应用为服务提供者,是eureka的一个客户端,需要注册到注册中心
register-with-eureka: true
###是否需要从eureka上检索服务
fetch-registry: true
instance:
prefer-ip-address: true #将自己的ip地址注册到Eureka服务中
ip-address: 127.0.0.1
instance-id: ${spring.application.name}###${server.port} #指定实例id
spring:
cloud:
config:
name: service-zuul #对应配置中心的应用名称,默认是本应用名,即spring.application.name,该名称要和git中的配置一致
#uri: http://127.0.0.1:7788/
profile: dev #对应配置服务中的{profile}
label: master #对应的分支
discovery:
enabled: true #启用发现服务功能
service-id: microservice-config-server #指定配置中心工程的名称
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
4.在网关工程启动类增加@RefreshScope注解,支持手动刷新
package com.zpc.gateway.runner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
import org.springframework.cloud.netflix.zuul.filters.ZuulProperties;
import org.springframework.context.annotation.ComponentScan;
@EnableZuulProxy
@SpringBootApplication
@ComponentScan(basePackages = “com.zpc.gateway.filter”)
public class ApiGatewayApplication {
public static void main(String[] args) {
SpringApplication.run(ApiGatewayApplication.class, args);
}
@RefreshScope
@ConfigurationProperties("zuul")
public ZuulProperties zuulProperties(){
ZuulProperties properties = new ZuulProperties();
System.out.println("properties:"+properties);
return properties ;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
5.启动配置中心(microservice-config-server)工程,查看能否访问git上的配置文件:
6.启动网关工程(microservice-api-gateway)、订单工程(microservice-order)、商品工程(microservice-item)测试。
Eureka:
通过Zuul网关访问订单的服务OK:
通过Zuul网关访问商品的服务OK:
7.修改git中的配置文件,手动发post请求刷新,看修改是否生效
修改了serviceid为不存在的服务app-itemXX:
刷新:
再次通过zuul访问item的服务:
8.把配置文件改回来,再发post请求http://localhost:8087/actuator/refresh,再次测试:
OK了,说明通过配置中心动态设置zuul配置生效:
5.消息总线 Spring Cloud Bus
5.1.分析
-
虽然通过Git服务器的web hook可以实现自动更新,但是,如果Config Client有很多的话,那么需要在web hook中维护很多地址,如果是手动刷新的话还得一个一个地发送POST请求,这显然是不现实的做法。
-
有没有更好的方案呢? 我们非常容易想到消息队列中的Topic,没错! 通过消息实现通知。
5.2.Spring Cloud Bus消息总线的简介
目前Spring Cloud Bus消息总线只是实现了对RabbitMQ以及Kafka的支持。
5.3.RabbitMQ的安装
参考《RabbitMQ教程》 https://blog.csdn.net/hellozpc/article/details/81436980
5.4.使用Spring Cloud Bus的架构
5.5.实现
5.5.1.在 microservice-item添加依赖
<!--整合Spring Cloud Bus-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
- 1
- 2
- 3
- 4
- 5
5.5.2.在bootstrap.yml添加rabbitmq的配置
###服务注册到eureka注册中心的地址
eureka:
client:
service-url:
defaultZone: http://zpc:123456@127.0.0.1:8100/eureka/,http://zpc:123456@127.0.0.1:9100/eureka/
###因为该应用为服务提供者,是eureka的一个客户端,需要注册到注册中心
register-with-eureka: true
###是否需要从eureka上检索服务
fetch-registry: true
instance:
prefer-ip-address: true #将自己的ip地址注册到Eureka服务中
ip-address: 127.0.0.1
instance-id: ${spring.application.name}###${server.port} #指定实例id
spring:
cloud:
config:
name: microservice #对应配置中心的应用名称,默认是本应用名,即spring.application.name,该名称要和git中的配置一致
#uri: http://127.0.0.1:7788/
profile: dev #对应配置服务中的{profile}
label: master #对应的分支
discovery:
enabled: true #启用发现服务功能
service-id: microservice-config-server #指定配置中心工程的名称(在Eureka中的服务名)
rabbitmq: #RabbitMQ相关的配置
host: 127.0.0.1
port: 5672
username: guest
password: guest
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
5.5.3.修改web hook的地址
在启动Item工程时会看到这样的日志:
2018-11-12 21:43:47.722 INFO 221032 --- [main] s.b.a.e.w.s.WebMvcEndpointHandlerMapping : Mapped "{[/actuator/bus-refresh],methods=[POST]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.web.servlet.AbstractWebMvcEndpointHandlerMapping$OperationHandler.handle(javax.servlet.http.HttpServletRequest,java.util.Map<java.lang.String, java.lang.String>)
- 1
说明刷新的地址/bus-refresh是由Spring Cloud Bus来处理,之前的/refresh依然是由以前的逻辑处理。
所以要修改Git服务器中的web hook的地址:(修改或者添加都可以)
5.5.4.启动测试
首先需要保证Rabbit的服务开启:
启动rabbit管理工具:
查看是否启动成功:
启动Item工程后查看RabbitMQ中的交换机:
再看队列:
接着,将 microservice-item的端口改成8082,再启动一个 microservice-item实例,进行测试。
发现,有2个队列,分别都绑定到springCloudBus的交换机。
接下里,修改配置文件的内容进行测试。
可以看到8081和8082这2个实例查询到的信息都是一样的。
接下来,修改配置文件内容将9999改成8888:
结果显示,都是获取到最新的数据。
在测试时,会发现,由于Git服务器的web钩子推送到8181,所以8081的更新快一些,而8082更新就相对慢一些。
5.5.5.流程总结
更新文件到git服务器,Git服务器通过web钩子通知到8081的/bus/refresh,8081的实例将消息发送到springCloudBus的交换机,由于8081的队列页绑定到交换机,所以8082也获取到了更新的通知,然后去Config Server获取最新的数据。
5.6.架构优化
在前面实现的架构中,发现8081这个实例不仅仅是提供了商品查询的服务,还负责发送更新的消息到RabbitMQ。
这其实是违反了微服务架构中的职责单一的原则。
其实这个架构是可以改进的,就是将原有的Config Server不仅仅提供配置查询的服务,而且还要负责更新消息的发送。
5.6.1.在microservice-config-server中导入依赖
<!--整合Spring Cloud Bus-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
- 1
- 2
- 3
- 4
- 5
5.6.2.修改application.yml配置文件
server:
port: 7788 #服务端口
spring:
application:
name: microservice-config-server #指定服务名
cloud:
config:
server:
git: #配置git仓库地址
uri: https://gitee.com/hellpzpc/myspconfig.git
search-paths:
- myspringcloudconfig #配置文件目录地址
username: 3747xxxx@qq.com #码云账号(公有项目不需要设置)
password: myxxxx #码云密码(公有项目不需要设置)
label: master #分支
rabbitmq: #RabbitMQ相关的配置
host: 127.0.0.1
port: 5672
username: guest
password: guest
###服务注册到eureka注册中心的地址
eureka:
client:
service-url:
defaultZone: http://zpc:123456@127.0.0.1:8100/eureka/,http://zpc:123456@127.0.0.1:9100/eureka/
###因为该应用为服务提供者,是eureka的一个客户端,需要注册到注册中心
register-with-eureka: true
###是否需要从eureka上检索服务
fetch-registry: true
instance:
prefer-ip-address: true #将自己的ip地址注册到Eureka服务中
instance-id: ${spring.application.name}###${server.port} #指定实例id
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
5.6.3.修改Git中的web钩子
5.6.4.重启测试
测试结果,和之前一样,可以同步更新到8081和8082。
6.整合consul&zookeeper作为注册中心
由于Eureka 2.0已结宣称闭源,因此可以考试使用其它注册中心组件,例如zk、consul。
6.1.整合zookeeper作为注册中心
6.1.1.修改商品(Item)微服务项目
第一步 添加依赖(注意zk和Eureka不能共存,请先注释掉Eureka的pom依赖)
<!--springboot 整合zookeeper客户端(注意保证服务端和客户端zk版本一致)-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
</dependency>
- 1
- 2
- 3
- 4
- 5
第二步 修改yml配置文件,添加zk作为注册中心(注意zk和Eureka不能共存,请先注释掉Eureka的配置)
###服务端口号(本身是一个web项目)
server:
port: 8080
###起个名字作为服务名称(该服务注册到eureka注册中心的名称,比如商品服务)
spring:
application:
name: app-item
###服务注册到zookeeper注册中心的地址
cloud:
zookeeper:
connect-string: 127.0.0.1:2181
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
第三步 修改入口程序
@EnableEurekaClient
改为:
@EnableDiscoveryClient
6.1.2.修改订单(order)微服务项目
第一步 添加依赖(注意zk和Eureka不能共存,请先注释掉Eureka的pom依赖)
<!--springboot 整合zookeeper客户端(注意保证服务端和客户端zk版本一致)-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
</dependency>
- 1
- 2
- 3
- 4
- 5
第二步 修改yml配置文件,添加zk作为注册中心(注意zk和Eureka不能共存,请先注释掉Eureka的配置)
###服务端口号(本身是一个web项目)
server:
port: 8080
###起个名字作为服务名称(该服务注册到eureka注册中心的名称,比如商品服务)
spring:
application:
name: app-order
###服务注册到zookeeper注册中心的地址
cloud:
zookeeper:
connect-string: 127.0.0.1:2181
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
第三步 修改入口程序
@EnableEurekaClient
改为:
@EnableDiscoveryClient
6.1.3.启动zookeeper、商品工程、订单工程
运行zookeeper目录\bin\zkServer.cmd
注意项目中引入的zookeeper jar包版本要和你本地运行的zk服务器一致!!否则拒绝连接!笔者使用的是zookeeper-3.5.3-beta
通过ZooInspector查看注册的服务(两个临时节点):
6.1.4. 测试功能
切换到zk注册中心一切OK!
6.2.整合consul作为注册中心
6.2.1.Consul环境搭建
1)下载
https://www.consul.io/downloads.html
2)启动
进入到consul目录,命令行运行:
consul agent -dev -ui -node=test
-dev开发服务器模式启动,-node设置节点名称为test,-ui可以用界面访问(默认就能访问)
3)访问
http://localhost:8500
6.2.2.修改商品(Item)微服务项目
第一步 引入依赖 (务必注释掉其他注册中心的pom依赖)
<!--springboot 整合consul客户端-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>
- 1
- 2
- 3
- 4
- 5
第二步 修改yml配置文件(务必注释掉其他注册中心的配置项)
###服务端口号(本身是一个web项目)
server:
port: 8081
###起个名字作为服务名称(该服务注册到eureka注册中心的名称,比如商品服务)
spring:
application:
name: app-item
###服务注册到consul注册中心的地址
###spring.cloud.consul.host/port配置的是Consul注册中心(Consul服务器)的地址和端口(Server节点和Client节点都可以),Spring Cloud Consul调用 Consul HTTP REST 接口,进行服务注册
###spring.cloud.consul.discovery.hostname配置 Spring Boot 服务的主机地址,也可以不进行配置,默认本机地址。
###healthCheckPath: /health 指定健康检查的url地址(保证该接口地址返回http 2xx 返回码,当然也可以忽略健康检查register-health-check: false)
cloud:
consul:
host: 127.0.0.1
port: 8500
discovery:
healthCheckPath: /health
healthCheckInterval: 5s
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
第三步 ItemController增加健康检查接口(即一个正常返回http 200的接口,如果consul健康检查失败,则服务不可调用)
/**
* 健康检查
* @return
*/
@RequestMapping("/health")
public String healthCheck() {
return "OK";
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
第四步 入口程序中@EnableEurekaClient换成@EnableDiscoveryClient
6.2.3.修改订单(order)微服务项目
第一步 引入依赖 (务必注释掉其他注册中心的pom依赖)
<!--springboot 整合consul客户端-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>
- 1
- 2
- 3
- 4
- 5
第二步 修改yml配置文件(务必注释掉其他注册中心的配置项)
###服务端口号(本身是一个web项目)
server:
port: 8082
###起个名字作为服务名称(该服务注册到eureka注册中心的名称,比如商品服务)
spring:
application:
name: app-order
###服务注册到consul注册中心的地址
###spring.cloud.consul.host/port配置的是Consul注册中心(Consul服务器)的地址和端口(Server节点和Client节点都可以),Spring Cloud Consul调用 Consul HTTP REST 接口,进行服务注册
###spring.cloud.consul.discovery.hostname配置 Spring Boot 服务的主机地址,也可以不进行配置,默认本机地址。
###healthCheckPath: /health 指定健康检查的url地址(保证该接口地址返回http 2xx 返回码,当然也可以忽略健康检查register-health-check: false)
cloud:
consul:
host: 127.0.0.1
port: 8500
discovery:
healthCheckPath: /health
healthCheckInterval: 5s
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
第三步 OrderController增加健康检查接口(即一个正常返回http 200的接口,如果consul健康检查失败,则服务不可调用)
/**
* 健康检查
* @return
*/
@RequestMapping("/health")
public String healthCheck() {
return "OK";
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
第四步 入口程序中@EnableEurekaClient
换成@EnableDiscoveryClient
6.2.4.启动consul、商品工程、订单工程
保证健康检查全部OK,否则服务调不通!
6.2.5.测试功能
切换到consul注册中心一切OK!
7.整合swagger API管理
Swagger是一款能够让你更好地书写API文档的框架。它可以生成一个具有互动性的API控制台,方便前后端开发人员面向接口交流、接口测试。
7.1.快速入门
在商品系统(microservice-item)中做演示
1.引入依赖
<!--整合swagger -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.8.0</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.8.0</version>
</dependency>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
2.配置Swagger
package com.zpc.item.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
@Configuration
@EnableSwagger2//开启swagger2
public class SwaggerConfig {
@Bean
public Docket createRestApi() {
//配置swagger 生成API的扫描范围
return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo()).select().apis(RequestHandlerSelectors.basePackage("com.zpc.item")).paths(PathSelectors.any()).build();
}
/**
* 创建api文档信息
* @return
*/
private ApiInfo apiInfo() {
return new ApiInfoBuilder().title("鸟鹏的专栏").description("SpringCloud教程").termsOfServiceUrl("https://blog.csdn.net/hellozpc").version("1.0").build();
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
3.编写Controller入口
package com.zpc.item.controller; import io.swagger.annotations.Api; import io.swagger.annotations.ApiImplicitParam; import io.swagger.annotations.ApiOperation; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RestController; import java.math.BigDecimal; @Api("SwaggerDemo控制器") @RestController public class SwaggerController {
@ApiOperation("Swagger演示") @GetMapping("/swaggerIndex") public String swaggerIndex(String msg) { return "This is swaggerIndex!" + msg; } @ApiOperation("获取商品详情") @ApiImplicitParam(name = "itemName", value = "商品名称", required = true, dataType = "String") @PostMapping("/getItemInfo") public String getItemInfo(String itemName) { return "商品名:" + itemName + " 商品价格:" + new BigDecimal(Math.random() * 100).setScale(2, BigDecimal.ROUND_HALF_UP); }
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
4.启动工程,访问http://localhost:8081/swagger-ui.html
在这个页面可以执行交互式的测试:
7.2.Zuul整合Swagger统一管理微服务Api
在一个入口统一管理所有微服务(订单服务、商品服务等)项目的API。实现方式:zuul
1.在订单服务(microservice-order)中也引入swagger
这次直接使用springboot提供的starter,不使用7.1那种方式。
<!--引入swagger-->
<dependency>
<groupId>com.spring4all</groupId>
<artifactId>swagger-spring-boot-starter</artifactId>
<version>1.7.0.RELEASE</version>
</dependency>
- 1
- 2
- 3
- 4
- 5
- 6
创建Controller入口
package com.zpc.order.controller; import com.spring4all.swagger.EnableSwagger2Doc; import io.swagger.annotations.Api; import io.swagger.annotations.ApiImplicitParam; import io.swagger.annotations.ApiOperation; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RestController; import java.math.BigDecimal; @EnableSwagger2Doc @Api("订单服务接口") @RestController public class OrderApiController { @ApiOperation("提交订单") @ApiImplicitParam(name = "orderNo", value = "订单号", required = true, dataType = "String") @PostMapping("/putOrderInfo") public String putOrderInfo(String orderNo) { return "订单:" + orderNo + " 已提交,商品总价:" + new BigDecimal(Math.random() * 1000).setScale(2, BigDecimal.ROUND_HALF_UP); }
@ApiOperation("获取订单详情") @ApiImplicitParam(name = "orderNo", value = "订单号", required = true, dataType = "String") @GetMapping("/getOrderInfo") public String getOrderInfo(String orderNo) { return "订单:" + orderNo + " 商品总价格:" + new BigDecimal(Math.random() * 1000).setScale(2, BigDecimal.ROUND_HALF_UP); }
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
yml中配置扫描范围
#配置Swagger接口扫描范围
swagger:
base-package: com.zpc.order
- 1
- 2
- 3
2.在网关服务中也引入Swagger
<!--引入swagger-->
<dependency>
<groupId>com.spring4all</groupId>
<artifactId>swagger-spring-boot-starter</artifactId>
<version>1.7.0.RELEASE</version>
</dependency>
- 1
- 2
- 3
- 4
- 5
- 6
3.修改网关服务的启动类
增加一个内部类DocumentationConfig
package com.zpc.gateway.runner;
import com.spring4all.swagger.EnableSwagger2Doc;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
import org.springframework.cloud.netflix.zuul.filters.ZuulProperties;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;
import springfox.documentation.swagger.web.SwaggerResource;
import springfox.documentation.swagger.web.SwaggerResourcesProvider;
import java.util.ArrayList;
import java.util.List;
@EnableZuulProxy
@EnableSwagger2Doc
@SpringBootApplication
@ComponentScan(basePackages = “com.zpc.gateway.filter”)
public class ApiGatewayApplication {
public static void main(String[] args) {
SpringApplication.run(ApiGatewayApplication.class, args);
}
@RefreshScope
@ConfigurationProperties("zuul")
public ZuulProperties zuulProperties(){
ZuulProperties properties = new ZuulProperties();
System.out.println("properties:"+properties);
return properties ;
}
/**
* 配置Swagger
*/
@Component
@Primary
class DocumentationConfig implements SwaggerResourcesProvider{
@Override
public List<SwaggerResource> get() {
List resource=new ArrayList<>();
//name可以随便写,location前缀要与zuul配置的path一致。zuul开了token验证,要加上token,否则不用加?token=1
resource.add(swaggerResource("myapp-item","/item-service/v2/api-docs?token=1","2.0"));
resource.add(swaggerResource("myapp-order","/order-service/v2/api-docs?token=1","2.0"));
return resource;
}
//name可以随便写,location前缀要与zuul配置的path一致
private SwaggerResource swaggerResource(String name,String location,String version){
SwaggerResource swaggerResource=new SwaggerResource();
swaggerResource.setName(name);
swaggerResource.setLocation(location);
swaggerResource.setSwaggerVersion(version);
return swaggerResource;
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
4.测试
启动Eureka注册中心、item服务、order服务、网关服务开始测试
直接通过网关的入口地址http://localhost:8087/swagger-ui.html?token=1
统一访问所有微服务的api,界面右上角可以选择。
</div><div data-report-view="{"mod":"1585297308_001","dest":"https://blog.csdn.net/zpcandzhj/article/details/84144453","extend1":"pc"}"><div></div></div>
<link href="https://csdnimg.cn/release/phoenix/mdeditor/markdown_views-60ecaf1f42.css" rel="stylesheet">
<div class="more-toolbox">
<div class="left-toolbox">
<ul class="toolbox-list">
<li class="tool-item tool-active is-like "><a href="javascript:;"><svg class="icon" aria-hidden="true">
<use xlink:href="#csdnc-thumbsup"></use>
</svg><span class="name">点赞</span>
<span class="count">45</span>
</a></li>
<li class="tool-item tool-active is-collection "><a href="javascript:;" data-report-click="{"mod":"popu_824"}"><svg class="icon" aria-hidden="true">
<use xlink:href="#icon-csdnc-Collection-G"></use>
</svg><span class="name">收藏</span></a></li>
<li class="tool-item tool-active is-share"><a href="javascript:;" data-report-click="{"mod":"1582594662_002"}"><svg class="icon" aria-hidden="true">
<use xlink:href="#icon-csdnc-fenxiang"></use>
</svg>分享</a></li>
<!--打赏开始-->
<!--打赏结束-->
<li class="tool-item tool-more">
<a>
<svg t="1575545411852" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5717" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M179.176 499.222m-113.245 0a113.245 113.245 0 1 0 226.49 0 113.245 113.245 0 1 0-226.49 0Z" p-id="5718"></path><path d="M509.684 499.222m-113.245 0a113.245 113.245 0 1 0 226.49 0 113.245 113.245 0 1 0-226.49 0Z" p-id="5719"></path><path d="M846.175 499.222m-113.245 0a113.245 113.245 0 1 0 226.49 0 113.245 113.245 0 1 0-226.49 0Z" p-id="5720"></path></svg>
</a>
<ul class="more-box">
<li class="item"><a class="article-report">文章举报</a></li>
</ul>
</li>
</ul>
</div>
</div>
<div class="person-messagebox">
<div class="left-message"><a href="https://blog.csdn.net/zpcandzhj">
<img src="https://profile.csdnimg.cn/5/C/E/3_zpcandzhj" class="avatar_pic" username="zpcandzhj">
<img src="https://g.csdnimg.cn/static/user-reg-year/2x/9.png" class="user-years">
</a></div>
<div class="middle-message">
<div class="title"><span class="tit"><a href="https://blog.csdn.net/zpcandzhj" data-report-click="{"mod":"popu_379"}" target="_blank">niaobirdfly</a></span>
</div>
<div class="text"><span>发布了138 篇原创文章</span> · <span>获赞 1238</span> · <span>访问量 99万+</span></div>
</div>
<div class="right-message">
<a href="https://bbs.csdn.net/topics/395527229" target="_blank" class="btn btn-sm btn-red-hollow bt-button personal-messageboard">他的留言板
</a>
<a class="btn btn-sm bt-button personal-watch" data-report-click="{"mod":"popu_379"}">关注</a>
</div>
</div>
</div>