目录
一、Feign
在前面的学习中,使用了Ribbon的负载均衡功能,大大简化了远程调用时的代码:
String url = "http://userService/user/" + id;
User user = restTemplate.getForObject(url, User.class)
如果就学到这里,可能以后需要编写类似的大量重复代码,格式基本相同,无非参数不一样。有没有更优雅的方式,来对这些代码再次优化呢?这就是接下来要学的Feign的功能了。
1.1Feign简介
Feign也叫伪装,https://github.com/OpenFeign/feign
Feign是一个声明式的Web服务客户端,使得编写web服务客户端变得非常容易,只需要创建一个接口,然后在上面添加注解即可。
Feign可以把Rest的请求进行隐藏,伪装成类似SpringMVC的Controller一样。你不用再自己拼接url,拼接参数等等操作,一切都交给Feign去做。
1.2快速入门
接下里我们使用Feign来实现consumerDemo中的调用服务功能,实现步骤如下:
- 导入Feign启动器依赖
- 开启Feign功能
- 编写Feign客户端
- 编写处理器ConsumerFeignController,注入Feign客户端并使用
- 测试
第一步我们还是在consumerDemo中导入启动器依赖,
<!--feign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
第二步我们在ConsumerApplication启动类上开启Feign功能,
@SpringCloudApplication
@EnableFeignClients//开启Feign
public class ConsumerApplication {
//...
}
第三步我们编写一个Feign的客户端,里面定义好一个接口方法,指定服务名和地址,声明好以后Feign会自动帮我们实现接口方法,
@FeignClient("userService")//标志当前类为Feign客户端,为userService服务进行url拼接
public interface UserClient {
@GetMapping(value = "/user/{id}")
User findById(@PathVariable("id") Long id);//这里的@PathVariable("id")一定要指定参数为id,否则可能会报错UserClient在controller类中注入异常
}
第四步我们编写处理器ConsumerFeignController,注入Feign客户端并使用,
@RestController
@RequestMapping("/consumerFeign")
public class ConsumerFeignController {
@Autowired
private UserClient userClient;
@GetMapping("/{id}")
public User findById(@PathVariable Long id){
return userClient.findById(id);
}
}
我们启动consumerDemo的启动类(eurekaServer和userService要提前启动),输入url,
1.3负载均衡
Feign中本身已经集成了Ribbon依赖和自动配置,
因此不需要额外引入依赖,也不需要再注册RestTemplate 对象。
而且Fegin内置的ribbon默认设置了请求超时时长,默认是1000,我们可以通过手动配置来修改这个超时时长:
ribbon:
ReadTimeout: 2000 # 读取超时时长 2000ms
ConnectTimeout: 1000 # 建立连接的超时时长 1000ms
因为ribbon内部有重试机制,一旦超时,会自动重新发起请求。
如果不希望重试,可以修改consumerDemo的application.yml添加如下配置:
ribbon:
ConnectTimeout: 1000 # 连接超时时长
ReadTimeout: 2000 # 数据通信超时时长
MaxAutoRetries: 0 # 当前服务器的重试次数
MaxAutoRetriesNextServer: 0 # 重试多少次服务
OkToRetryOnAllOperations: false # 是否对所有的请求方式都重试
然后我们在userService的服务中, 添加一个休眠的代码,然后启动服务器查看,
Thread.sleep(2000);//休眠2s
1.4Hystrix支持
Feign默认也有对Hystrix的集成:
所以我们可以在Feign中实现熔断,首先要在consumerDemo中开启熔断功能,
feign:
hystrix:
enabled: true # 开启Feign的熔断功能
但是,Feign中的Fallback配置不像Ribbon中那样简单了。
1)首先要定义一个类UserClientFallback,实现UserClient接口,作为该服务降级的处理类,
@Component//将当前类注入到Spring容器中
class UserClientFallback implements UserClient {
@Override
public User findById(Long id) {
User user=new User();
user.setId(id);
user.setUsername("用户异常");
return user;
}
}
2)然后在UserClient接口中,指定刚才编写的实现类为我们的服务降级处理类,
@FeignClient(value = "userService", fallback = UserClientFallback.class)//标志当前类为Feign客户端,为userService服务进行url拼接,指定UserClientFallback为服务降级处理类
这样当请求服务出错时会执行服务降级,执行服务降级处理类里面重写的方法。
3)重启测试
重启启动consumerDemo启动类,然后在页面访问:http://localhost:8080/consumerFeign/1,可以看到当服务出现异常时,执行的是我们指定的服务降级处理方法,
1.5请求压缩
SpringCloud Feign 支持对请求和响应中的数据进行GZIP压缩,以减少通信过程中的性能损耗。
通过下面的参数即可开启请求与响应的压缩功能:
feign:
compression:
request:
enabled: true # 开启请求压缩
response:
enabled: true # 开启响应压缩
同时,我们也可以对请求的数据类型,以及触发压缩的大小下限进行设置:
feign:
compression:
request:
enabled: true # 开启请求压缩
mime-types: text/html,application/xml,application/json # 设置压缩的数据类型
min-request-size: 2048 # 设置触发压缩的大小下限
注:上面的数据类型、压缩大小下限均为默认值。
1.6日志级别
我们知道通过logging.level.xx=debug 来设置日志级别。
然而这个配置对Fegin客户端而言不会产生效果。因为@FeignClient 注解修改的客户端在被代理时,都会创建一个新的Fegin.Logger实例。我们需要额外指定这个日志的级别才可以。
1)在consumerDemo 的配置文件中设置com.laotang包下的日志级别都为debug,
logging:
level:
com.laotang: debug # 设置com.laotang包下的日志级别都是debug
2)在consumerDemo 编写FeignConfig配置类,定义日志级别,
@Configuration//标志当前类为一个配置类
public class FeignConfig {
@Bean//获取配置的级别
public Logger.Level feignLoggerLevel(){
return Logger.Level.FULL;//记录所有请求和相应的明细,包括头信息、请求体、元数据
}
}
这里指定的Level级别是FULL,Feign支持4种级别:
- NONE:不记录任何日志信息,这是默认值。
- BASIC:仅记录请求的方法,URL以及响应状态码和执行时间
- HEADERS:在BASIC的基础上,额外记录了请求和响应的头信息
- FULL:记录所有请求和响应的明细,包括头信息、请求体、元数据。
3)在consumerDemo 的UserClient 接口类上的@FeignClient注解中指定配置类,
@FeignClient(value = "userService", //标志当前类为Feign客户端,为userService服务进行url拼接
fallback = UserClientFallback.class, //指定UserClientFallback为服务降级处理类
configuration = FeignConfig.class)//指定配置类为FeignConfig
public interface UserClient {
@GetMapping(value = "/user/{id}")
User findById(@PathVariable("id") Long id);
}
4)重启项目,访问url即可看到日志内容出现,
二、Spring Cloud Gateway网关
2.1简介
- Spring Cloud Gateway是Spring官网基于Spring 5.0、 SpringBoot 2.0、Project Reactor等技术开发的网关服务
- Spring Cloud Gateway基于Filter链提供网关基本功能:安全、监控/埋点、限流等
- Spring Cloud Gateway为微服务架构提供简单、有效且统一的API路由管理方式
- Spring Cloud Gateway是替代Netflix Zuul的一套解决方案
Spring Cloud Gateway组件的核心是一系列的过滤器,通过这些过滤器可以将客户端发送的请求转发(路由)到对应的微服务。网关的核心功能是:过滤和路由。
Spring Cloud Gateway是加在整个微服务最前沿的防火墙和代理器,隐藏微服务结点IP端口信息,从而加强安全保护。Spring Cloud Gateway本身也是一个微服务,需要注册到Eureka服务注册中心。
2.2Gateway加入后的架构
不管是来自于客户端(PC或移动端)的请求,还是服务内部调用。一切对服务的请求都可经过网关,然后再由网关来实现 鉴权、动态路由等等操作。(内部调用可以不经过Gateway用Feign)
Gateway就是我们服务的统一入口。
2.3核心概念
- 路由(route)路由信息的组成:由一个ID、一个目的URL、一组断言工厂、一组Filter组成。如果路由断言为真,说明请求URL和配置路由匹配。
- 断言(Predicate)Spring Cloud Gateway中的断言函数输入类型是Spring 5.0框架中的ServerWebExchange。Spring Cloud Gateway的断言函数允许开发者去定义匹配来自于Http Request中的任何信息比如请求头和参数。
- 过滤器(Filter)一个标准的Spring WebFilter。 Spring Cloud Gateway中的Filter分为两种类型的Filter,分别是Gateway Filter和Global Filter。过滤器Filter将会对请求和响应进行修改处理。
2.4快速入门
我们之前实现了Feign作为我们的Web服务客户端,访问查询用户的服务,
现在我们试试如何通过网关Gateway将包含有/user的请求路由到查询用户服务上,步骤如下:
- 创建工程,添加启动器依赖
- 编写启动引导类和配置文件
- 修改配置文件,设置路由信息
- 启动测试
第一步我们新建一个工程,添加gateway和eureka的启动器依赖,
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
第二步我们编写启动引导类,
@SpringBootApplication
@EnableDiscoveryClient//开启eureka客户端发现功能
public class GateWayApplication {
public static void main(String[] args) {
SpringApplication.run(GateWayApplication.class, args);
}
}
接着是配置application.yml配置文件,
server:
port: 10010 # 端口
spring:
application:
name: gatewayServer # 应用名称
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka # 指定eureka的注册地址
instance:
prefer-ip-address: true # 通过ip注册到eureka服务中心上
第三步修改配置文件,设置路由信息
spring:
cloud: # 配置gateway路由信息
gateway:
routes:
- id: userService-route # 路由id,可以任意
uri: http://127.0.0.1:9091 # 代理的服务地址
predicates: # 路由断言,可以匹配映射路径
- Path=/user/** # 访问网关时路径包含Path的值,则跳转到对应的url中,并自动拼接Path的值
第四步我们启动服务,访问http://localhost:10010/user/2
这样我们就实现了对真正提供服务的地址隐藏保护。
2.5面向服务的路由
在刚才的路由规则中,把路径对应的服务地址写死了,如果同一服务有多个实例的话,这样做显然不合理。
routes:
- uri: http://127.0.0.1:9091
我们应该根据服务的名称,去Eureka注册中心查找服务对应的所有实例列表,从而进行动态路由,在Spring Cloud Gateway中可以通过配置动态路由解决,
spring:
cloud: # 配置gateway路由信息
gateway:
routes:
- id: userService-route # 路由id,可以任意
#uri: http://127.0.0.1:9091 # 代理的服务地址
uri: lb://userService # 代理的服务地址;lb表示从eureka中获取具体服务,后面接服务名(该服务必须在eureka中注册)
predicates: # 路由断言,可以匹配映射路径
- Path=/user/** # 访问网关时路径包含Path的值,则跳转到对应的url中,并自动拼接Path的值
路由配置中uri所用的协议为lb时(以uri: lb://userService为例),gateway网关将使用LoadBalancerClient 把 userService 通过 eureka 解析为实际的主机和端口,并进行 ribbon 负载均衡
重启服务发现还是可以正常使用,
2.6路由前缀处理
假设提供服务的地址为:http://localhost:9091/user/1,我们可以对请求地址添加或去除前缀,
- 添加前缀:对请求地址添加前缀路径之后,再作为代理的服务地址
- http://localhost:10010/1 —> http://localhost:9091/user/1 :添加前缀路径/user
- 去除前缀:将请求地址中路径去除一些前缀路径之后,再作为代理的服务地址
- http://localhost:10010/pre/user/1 —> http://localhost:9091/user/1 :去除前缀路径/pre
1、添加前缀
在gateway中可以通过配置路由的过滤器PrefixPath,实现映射路径中地址的添加,
spring:
cloud: # 配置gateway路由信息
gateway:
routes:
- id: userService-route # 路由id,可以任意
#uri: http://127.0.0.1:9091 # 代理的服务地址
uri: lb://userService # 代理的服务地址;lb表示从eureka中获取具体服务,后面接服务名(该服务必须在eureka中注册)
predicates: # 路由断言,可以匹配映射路径
- Path=/** # 访问网关时路径包含Path的值,则跳转到对应的url中,并自动拼接Path的值
filters:
- PrefixPath=/user # 添加请求路径的前缀
注意我们要将Path修改为"/**",否则不携带/user路径的请求就进入不到当前的路由,也就无法添加前缀,配置好了之后我们重启服务,访问http://localhost:10010/1,
2、去除前缀
在gateway中可以通过配置路由的过滤器StripPrefix,实现映射路径中地址的去除,
spring:
cloud: # 配置gateway路由信息
gateway:
routes:
- id: userService-route # 路由id,可以任意
#uri: http://127.0.0.1:9091 # 代理的服务地址
uri: lb://userService # 代理的服务地址;lb表示从eureka中获取具体服务,后面接服务名(该服务必须在eureka中注册)
predicates: # 路由断言,可以匹配映射路径
- Path=/** # 访问网关时路径包含Path的值,则跳转到对应的url中,并自动拼接Path的值
filters:
- StripPrefix=1 # 表示过滤1个路径,2表示过滤2个路径...(如果为2,则将localhost:10010/a/b/user/1 过滤为localhost:10010/user/1)
通过 StripPrefix 来指定了路由要去掉的前缀个数:
- StripPrefix=1:http://localhost:10010/a/user/1 —> http://localhost:9091/user/1
- StripPrefix=2:http://localhost:10010/a/b/user/1 —> http://localhost:9091/user/1
改好以后我们重启服务,访问http://localhost:10010/a/user/1,
2.7过滤器
2.7.1简介
Gateway作为网关的其中一个重要功能,就是实现请求的鉴权。
而这个动作往往是通过网关提供的过滤器来实现的。前面的路由前缀功能也是使用过滤器实现的。
Gateway自带过滤器有几十个,常见自带过滤器有:
过滤器名称 | 说明 |
---|---|
AddRequestHeader | 对匹配上的请求加上Header |
AddRequestParameters | 对匹配上的请求路由添加参数 |
AddResponseHeader | 对从网关返回的响应添加Header |
StripPrefix | 对匹配上的请求路径去除前缀 |
详细说明可以参考官网链接,我们添加请求头过滤器,在请求头中添加指定属性,
按照官网的方法修改配置文件,
spring:
cloud: # 配置gateway路由信息
gateway:
routes:
- id: userService-route # 路由id,可以任意
#uri: http://127.0.0.1:9091 # 代理的服务地址
uri: lb://userService # 代理的服务地址;lb表示从eureka中获取具体服务,后面接服务名(该服务必须在eureka中注册)
predicates: # 路由断言,可以匹配映射路径
- Path=/a/user/** # 访问网关时路径包含Path的值,则跳转到对应的url中,并自动拼接Path的值
filters:
- StripPrefix=1 # 表示过滤1个路径,2表示过滤2个路径...(如果为2,则将localhost:10010/a/b/user/1 过滤为localhost:10010/user/1)
default-filters: # 添加默认的过滤器,对所有的路由都生效
- AddResponseHeader=myname, tz # 添加响应头信息,格式为:AddResponseHeader=属性名, 属性值
然后重启项目访问url,查看请求头信息,可以看到配置的属性已经添加进去了,
过滤器类型:Gateway在实现方式上,有两种过滤器
- 局部过滤器:通过spring.cloud.gateway.routes.filters 配置在具体路由下,只作用在当前路由上
- 自带的过滤器都可以配置或者自定义按照自带过滤器的方式。如果配置spring.cloud.gateway.default-filters 上会对所有路由生效也算是全局的过滤器;但是这些过滤器的实现上都是要实现GatewayFilterFactory接口。
- 全局过滤器:不需要在配置文件中配置,作用在所有的路由上;实现 GlobalFilter 接口即可。
2.7.2执行声明周期
Spring Cloud Gateway 的 Filter 的生命周期也类似Spring MVC的拦截器有两个:“pre” 和 “post”。pre 和 post 分别会在请求被执行前调用和被执行后调用。
这里的 pre 和 post 可以通过过滤器的GatewayFilterChain 执行filter方法前后来实现。
2.7.3使用场景
常见的应用场景如下:
- 请求鉴权:一般GatewayFilterChain 执行filter方法前,如果发现没有访问权限,直接就返回空
- 异常处理:一般GatewayFilterChain 执行filter方法后,记录异常并返回
- 服务调用时长统计: GatewayFilterChain 执行filter方法前后根据时间统计
2.8自定义过滤器
2.8.1自定义局部过滤器
假设现在需要这样一个过滤器,该过滤器可以将http://localhost:10010/a/user/1?name=test中的参数name的值获取到并输出到控制台,并且参数名是可变的,也就是不一定每次都是name;需要可以通过配置过滤器的时候做到配置参数名。
第一步我们编写自定义过滤器类,实现GatewayFilterFactory接口(也可以继承AbstractGatewayFilterFactory类,该类实现了上述的接口),
@Component
public class MyParamGatewayFilterFactory extends AbstractGatewayFilterFactory<MyParamGatewayFilterFactory.Config> {
static final String PARAM_NAME = "param";
public MyParamGatewayFilterFactory() {
super(Config.class);
}
public List<String> shortcutFieldOrder() {
return Arrays.asList(PARAM_NAME);
}
@Override
public GatewayFilter apply(Config config) {
return (exchange, chain) -> {
//获取请求参数中param对应的参数名,及对应的参数值
ServerHttpRequest request = exchange.getRequest();
if (request.getQueryParams().containsKey(config.param)) {
request.getQueryParams().get(config.param).forEach(value -> System.out.printf("局部过滤器%s=%s\n", config.param, value));
}
return chain.filter(exchange);
};
}
public static class Config {
private String param;//对应在配置过滤器时指定的参数名
public String getParam() {
return param;
}
public void setParam(String param) {
this.param = param;
}
}
}
第二步我们在application.yml中配置过滤器,
spring:
cloud: # 配置gateway路由信息
gateway:
routes:
- id: userService-route # 路由id,可以任意
uri: lb://userService # 代理的服务地址;lb表示从eureka中获取具体服务,后面接服务名(该服务必须在eureka中注册)
predicates: # 路由断言,可以匹配映射路径
- Path=/a/user/** # 访问网关时路径包含Path的值,则跳转到对应的url中,并自动拼接Path的值
filters:
- StripPrefix=1 # 表示过滤1个路径,2表示过滤2个路径...(如果为2,则将localhost:10010/a/b/user/1 过滤为localhost:10010/user/1)
- MyParam=name # 自定义过滤器为MyParam(对应的类必须名为MyParamGatewayFilterFactory),获取请求路径中name参数的值
第三步我们进行测试,访问http://localhost:10010/a/user/1?name=test检查后台输出,
2.8.2自定义全局过滤器
我们模拟一个登录的校验。基本逻辑:如果请求中有token参数,则认为请求有效,放行。
全局过滤器就不用配置,所以我们编写全局过滤器实现 GlobalFilter 接口即可,这里我们还是先一个Ordered接口,该接口是用来指定执行顺序的。
@Component
public class MyGlobalFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
System.out.println("---------全局过滤器MyGlobalFilter--------");
String token = exchange.getRequest().getQueryParams().getFirst("token");
if (StringUtils.isBlank(token)){//如果token参数为空
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);//设置响应状态码为未授权
return exchange.getResponse().setComplete();//不再继续执行下去了
}
return chain.filter(exchange);//如果有token参数则继续执行
}
@Override
public int getOrder() {
return 1;//值越小,越先执行
}
}
重新启动,分别携带和不携带token参数进行访问,
2.9负载均衡和熔断
Gateway中默认就已经集成了Ribbon负载均衡和Hystrix熔断机制。
但是所有的超时策略都是使用默认值,比如熔断超时时间只有1S,很容易就触发了。因此建议手动进行配置:
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 6000
ribbon:
ConnectTimeout: 1000
ReadTimeout: 2000
MaxAutoRetries: 0
MaxAutoRetriesNextServer: 0
2.10Gateway跨域配置
一般网关都是所有微服务的统一入口,必然在被调用的时候会出现跨域问题。
跨域:在js请求访问中,如果访问的地址与当前服务器的域名、ip或者端口号不一致则称为跨域请求。若不解决则不能获取到对应地址的返回结果。
例如:从在http://localhost:9091/中的js访问 http://localhost:9092/的数据,因为端口不同,所以也是跨域请求。
在访问Spring Cloud Gateway网关服务器的时候,出现跨域问题的话,可以在网关服务器中通过配置解决,允许哪些服务是可以跨域请求的。具体配置如下:
spring:
cloud: # 配置gateway路由信息
gateway:
routes:
- id: userService-route # 路由id,可以任意
uri: lb://userService # 代理的服务地址;lb表示从eureka中获取具体服务,后面接服务名(该服务必须在eureka中注册)
predicates: # 路由断言,可以匹配映射路径
- Path=/a/user/** # 访问网关时路径包含Path的值,则跳转到对应的url中,并自动拼接Path的值
filters:
- StripPrefix=1 # 表示过滤1个路径,2表示过滤2个路径...(如果为2,则将localhost:10010/a/b/user/1 过滤为localhost:10010/user/1)
- MyParam=name # 自定义过滤器为MyParam(对应的类必须名为MyParamGatewayFilterFactory),获取请求路径中name参数的值
default-filters: # 添加默认的过滤器,对所有的路由都生效
- AddResponseHeader=myname, tz # 添加响应头信息,格式为:AddResponseHeader=属性名, 属性值
globalcors:
corsConfigurations:
'[/**]': # 表示对所有访问到网关服务器的请求地址
allowedOrigins: # 指定允许访问的服务器地址
- "http://docs.spring.io"
allowedMethods: # 指定允许访问的方式
- GET
上述配置表示:可以允许来自 http://docs.spring.io 的get请求方式获取服务数据,其中
- allowedOrigins:指定允许访问的服务器地址,如:http://localhost:10000 也是可以的。
- '[/**]':表示对所有访问到网关服务器的请求地址
详情可以查看官网具体说明。
2.11Gateway的高可用
Gateway的高可用即启动多个Gateway服务,自动注册到Eureka,形成集群。
如果是服务内部访问,访问Gateway,自动负载均衡,没问题。但是,Gateway更多是外部访问,PC端、移动端等。它们无法通过Eureka进行负载均衡,那么该怎么办?
此时,可以使用其它的服务网关,来对Gateway进行代理。比如:Nginx
2.12Gateway和Feign的区别
Gateway网关一般直接给终端请求使用,而Feign一般用在微服务之间调用。
- Gateway 作为整个应用的流量入口,接收所有的请求,如PC、移动端等,并且将不同的请求转发至不同的处理微服务模块,其作用可视为nginx;大部分情况下用作权限鉴定、服务端流量控制
- Feign 则是将当前微服务的部分服务接口暴露出来,并且主要用于各个微服务之间的服务调用
三、Spring Cloud Config分布式配置中心
3.1简介
在分布式系统中,由于服务数量非常多,配置文件分散在不同的微服务项目中,管理不方便。为了方便配置文件集中管理,需要分布式配置中心组件。在Spring Cloud中,提供了Spring Cloud Config,它支持配置文件放在配置服务的本地,也支持放在远程Git仓库(GitHub、Gitee码云)。
使用Spring Cloud Config配置中心后的架构如下图:
配置中心本质上也是一个微服务,同样需要注册到Eureka服务注册中心。
配置中心作用:可以通过修改在git仓库中的配置文件,实现其它所有微服务的配置文件的修改。
接下来我们搭建一个配置中心微服务,用于集中管理修改配置文件。
3.2Git配置管理
为了能把配置文件集中管理,我们选择在Gitee代码托管平台管理我们的配置文件。不用GitHub的原因是太慢了,毕竟服务器在国外,Gitee服务器在国内。
3.2.1创建远程仓库
首先要使用码云上的私有远程git仓库需要先注册帐号;请先自行访问网站并注册帐号,然后使用帐号登录码云控制台并创建公开仓库。
3.2.2创建配置文件
创建好仓库以后我们在仓库里面创建配置文件,配置文件的命名方式:{application}-{profile}.yml 或 {application}-{profile}.properties
- application:应用名称
- profile:区分开发环境、测试环境和生产环境等
如user-dev.yml,表示用户微服务开发环境下使用的配置文件。
这里将userService工程的配置文件application.yml文件的内容复制作为user-dev.yml文件的内容:
3.3搭建配置中心微服务
接下来我们先创建配置中心微服务工程,添加依赖,
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
第二步我们生成对应的启动引导类,
@SpringBootApplication
@EnableConfigServer//开启配置服务
public class ConfigServerApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigServerApplication.class, args);
}
}
还有我们的application.yml配置文件,
server:
port: 12000 # 指定configServer的端口号
spring:
application:
name: configServer # 项目的名称
cloud:
config:
server:
git:
uri: https://gitee.com/aoylaotang/spring-cloud-config-test.git # 指定git的远程仓库地址
username: 自己的账户 # gitee的账户名(如果仓库为私密的则需要给账户和密码)
password: 自己的密码 # gitee的密码
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka # 指定eureka注册中心的地址,将configServer注册到eureka上
然后我们启动引导类,访问http://localhost:12000/user-dev.yml,
3.4获取配置中心配置
前面已经完成了配置中心微服务的搭建,下面我们就需要改造一下用户微服务userService ,配置文件信息不再由微服务项目提供,而是从配置中心获取。即将userService的配置文件删除,直接从配置中心configServer中获取。
如下对 userService 工程进行改造:
第一步对 userService 工程中的pom.xml文件添加依赖,
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
<version>2.1.1.RELEASE</version>
</dependency>
第二步我们修改一下配置文件,删除userService 工程的application.yml 文件(因为该文件将从配置中心获取),创建userService 工程 bootstrap.yml 配置文件(该文件也是springboot的默认配置文件,其经常配置一些项目中固定的配置项,如果是经常变动的应该配置到application.yml中 )
spring:
cloud:
config:
name: user # 要与仓库中的配置文件application保持一致,即{user}-dev.yml
profile: dev # 要与仓库中的配置文件profile保持一致,即user-{dev}.yml
label: master # 要与仓库中的配置文件所属版本(分支)一致
discovery:
enabled: true # 使用配置中心来获取配置文件
service-id: configServer # 配置中心服务名
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka # 指定eureka注册中心的地址,将configServer注册到eureka上
配置好了之后我们重启userService服务,然后访问http://localhost:9091/user/1,可以证明userService服务获取到了gitee远程仓库的配置文件application.yml,并提供了正确的服务,
四、Spring Cloud Bus服务总线
4.1问题
前面已经完成了将微服务中的配置文件集中存储在远程Git仓库,并且通过配置中心微服务从Git仓库拉取配置文件,当用户微服务启动时会连接配置中心获取配置信息从而启动用户微服务。
如果我们更新Git仓库中的配置文件,那用户微服务是否可以及时接收到新的配置信息并更新呢?
接下来我们修改一下gitee仓库的user-dev.yml文件,添加一个属性test.name,
修改完成之后,我们在userService的controller处理类方法中加一个语句,把配置文件中新增的test.name配置项的值输出,
@RestController//相当于@ResponseBody + @Controller
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
@Value("${test.name}")//获取配置文件中的值
private String name;
@GetMapping("/{id}")
public User findById(@PathVariable Long id){
System.out.println("配置文件中test.name="+name);//输出新增的配置项的值
return userService.findById(id);
}
}
我们重新启动用户服务userService ;然后修改Git仓库中的配置信息,访问用户微服务,
可以看到正确获取到了配置项的值,如果我们此时在gitee上再次修改配置项的值
再次访问用户微服务,发现获取到的test.name还是tangzhe,
要想获取到修改后的值,必须重启userService服务才行。
如果想在不重启微服务的情况下更新配置该如何实现呢? 可以使用Spring Cloud Bus来实现配置的自动更新。
需要注意的是Spring Cloud Bus底层是基于RabbitMQ实现的,默认使用本地的消息队列服务,所以需要提前启动本地RabbitMQ服务(安装RabbitMQ以后才有),如下:
4.2Spring Cloud Bus简介
Spring Cloud Bus是用轻量的消息代理将分布式的节点连接起来,可以用于广播配置文件的更改或者服务的监控管理。也就是消息总线可以为微服务做监控,也可以实现应用程序之间相互通信。
Spring Cloud Bus可选的消息代理有RabbitMQ和Kafka。
下图是添加了Spring Cloud Bus后的Spring Cloud项目架构,
当用户修改了配置中心的配置文件时,Spring Cloud Bus会将改变的消息传到RabbitMQ消息代理中,而我们的微服务对RabbitMQ一直监听,监听到配置改变消息后,这些服务会自动再从配置中心获取配置文件,从而达到动态更新。
4.3Spring Cloud Bus应用
我们现在利用Spring Cloud Bus,当在gitee远程仓库中修改user-dev.yml配置文件时,不重启userService服务也可以即使获取到更新后的配置文件信息。实现步骤如下:
- 启动RabbitMQ服务
- 修改配置中心configServer
- 修改服务提供工程userService
第一步我们启动RabbitMQ服务,打开安装路径下的sbin目录,双击rabbitmq-server.bat启动服务器
第二步我们修改配置中心configServer,添加Spring Cloud Bus和RabbitMQ的依赖,
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-bus</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream-binder-rabbit</artifactId>
</dependency>
并且在其配置文件中添加如下配置信息,
spring:
rabbitmq: # 配置rabbitmq信息;如果都与默认值一致,则不需要配置(下面的都是默认值)
host: localhost
port: 5672
username: guest
password: guest
management:
endpoints:
web:
exposure:
include: bus-refresh # 暴露触发消息总线的地址
第三步我们修改userService服务,在userService中同样需要添加相关依赖,
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-bus</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream-binder-rabbit</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
然后我们修改userService 项目的bootstrap.yml,加上rabbitmq的配置信息,
spring:
rabbitmq: # 配置rabbitmq信息;如果都与默认值一致,则不需要配置(下面的都是默认值)
host: localhost
port: 5672
username: guest
password: guest
我们还需要修改一下controller处理类,添加@RefreshScope 注解,这样就可以动态获取配置,
@RefreshScope//刷新配置,获取配置文件中最新的值
public class UserController{
//...
}
最后我们重启configServer和userService启动类,此时我们gitee上面的配置文件test.name还是为原来的值,
我们获取以后输出test.name的值,
我们对远程仓库上的test.name的值进行修改,修改为tangzhe2222,此时注意,我们要告诉配置中心我们修改了配置,通过http://127.0.0.1:12000/actuator/bus-refresh进行刷新(这里我们使用HTTP模拟插件Advanced Rest Client进行模拟请求发送,安装过程可见ARC安装教程)
然后配置中心通过消息总线通知到RabbitMQ,服务监听到RabbitMQ中有配置更新的消息,所以服务会自动重新获取配置文件,从而读取到修改后的配置项的值,
点击发送得到这样一个状态码,就说明成功了,
接着我们重新访问服务(此时没有对服务进行重启操作),可以看到读取到了最新的配置项