目录
????????微服务意味着要将单体应用中的业务拆分成一个个子服务,每个服务的粒度相对较小,因此系统中会出现大 量的服务。 由于每个服务都需要必要的配置信息才能运行,所以一套集中式的,动态的配置管理设施是必不可少的。
????????Spring Cloud 提供了 ConfigServer来解决这个问题,Spring Cloud Config 为微服务架构中的微服务提供集中化的外部配置支持,配置服务器为各个不同微服 务应用的所有环境提供了一个中心化的外部配置。
一个使用微服务架构的应用系统可能会包括成百上千个微服务,配置各部相同,需求各不相同:
????????使用 Spring Cloud Alibaba Nacos Config,您可以在 Nacos Server 集中管理你 Spring Cloud 应用的外 部属性配置。?
SpringBoot 默认支持properties和YAML两种格式的配置文件。
bootstrap.yml 先于 application.yml 加载 !!!
1、添加依赖
<!--config配置管理-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
2、添加bootsrap.yml配置
server:
port: 8080
spring:
application:
name: basketball
cloud:
nacos:
server-addr: localhost:8848
config:
server-addr: ${spring.cloud.nacos.server-addr}
#指定nacos配置中心地址
prefix: ${spring.application.name}
file-extension: yaml # 使用的 nacos 配置集的 dataId 的文件拓展名,同时也是Nacos 配置集的配置格式,默认为 properties
group: DEFAULT_GROUP # 使用的 Nacos 配置分组,默认为 DEFAULT_GROUP
# 共享配置集数组
shared-configs:
- data-id: redis.yml
group: DEFAULT_GROUP # 使用的 Nacos 配置分组,默认为 DEFAULT_GROUP
refresh: true # 是否自动刷新配置,默认为 fals
- data-id: basketball.yml
group: DEFAULT_GROUP # 使用的 Nacos 配置分组,默认为 DEFAULT_GROUP
refresh: true # 是否自动刷新配置,默认为 false
# 使用的 nacos 的命名空间(指定项目环境),
namespace: 363830b6-d72e-4868-a028-6be9c2cfd267
3、对应Nacos也添加配置信息
redis.yml:
basketball.yml:
4、编写两个配置类读取Nacos配置文件属性值
5、编写测试接口类
package com.example.basketball.controller;
import com.example.basketball.config.EmailProperies;
import com.example.basketball.config.RedisProperies;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import pojo.dto.IkunDto;
import java.util.Map;
@Slf4j
@RestController
@RequestMapping("/user")
public class ConfigController {
@Autowired
private EmailProperies emailProperies;
@Autowired
private RedisProperies redisProperies;
/*编写测试接口读取远程配置信息*/
@RequestMapping("/test1")
public Object test01() {
return emailProperies;
}
@RequestMapping("/test2")
public Object test02() {
return redisProperies;
}
}
6、启动类添加刷新配置注解
7、测试
? ? ? ? 这样我们就能将一些基本配置信息添加到Nacot进行远程配置管理,其实这里还有一个缺点:如果我们要个更换环境需要修改指定的nacos命名空间。在这我们其实可以不写,把项目导成jar包,直接设置指定命名空间运行项目:
cmd指令示例:
java -jar xxx.jar --spring.cloud.nacos.config.namespace=xxx
????????Spring Cloud Gateway是Spring官方基于Spring5.0、SpringBoot2.0和Project Reactor等技术开发的网关,旨在为微服务框架提供一种简单而有效的统一的API路由管理方式,统一访问接口。
????????Spring Cloud Gateway作为Spring Cloud生态体系中的网关,目标是替代Netflix的Zuul,其不仅提供统 一的路由方式,并且基于Filter链的方式提供了网关基本的功能,例如:安全、监控/埋点和限流等等。 它是基于Netty的响应式开发模式。
1??路由(route):路由是网关最基础的部分,路由信息由一个ID,一个目的URL、一组断言(predicates)工厂和一 组Filter组成。如果断言为真,则说明请求URL和配置的路由匹配。
2??断言(Predicate):Java8中的断言函数,Spring Cloud Gateway中的断言函数输入类型是 Spring5.0框架中的ServerWebExchange。Spring Cloud Gateway中的断言函数允许开发者去定义匹配 来自http Request中的任何信息,比如请求头和参数等。
3??过滤器(Filter):一个标准的Spring WebFilter,Spring Cloud Gateway中的Filter分为两种类型: Gateway Filter和Global Filter。过滤器Filter可以对请求和响应进行处理
创建网关服务:
添加依赖?:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!--阿里json解析-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.35</version>
</dependency>
</dependencies>
本地yml配置
server:
port: 8082
spring:
application:
name: gateway
cloud:
nacos:
server-addr: localhost:8848
gateway:
routes:
#路由标识(id:标识,具有唯一性)配合读取类使用
- id: user-basketball-api
#目标服务地址(uri:地址,请求转发后的地址),会自动从注册中心获得服务的IP,不需要手动写死
uri: lb://ikun
#优先级,越小越优先
order: 999
#路由条件(predicates:断言)
predicates:
# 路径匹配,
- Path=/kun/**
filters:
#路径前缀删除示例
- StripPrefix=1
????????这样同时启动gateway和ikun(消费者)服务,通过网关端口8082路径kun就能访问ikun服务的接口方法。但是这样还是不太方便,建议使用方法二动态路由远程配置👇
如果是跳到该目录的小帅和小美请看上面的服务创建与依赖配置哦!?
1、修改yml配置 :
server:
port: 8082
spring:
application:
name: gateway
cloud:
nacos:
server-addr: localhost:8848
#自定义读取配置
gateway:
nacos:
server-addr: ${spring.cloud.nacos.server-addr}
#namespace: xxx-xx-xx-xx (指定环境命名空降如[dev,test,pro...])
data-id: dynamic-routing.json
group: DEFAULT_GROUP
2、添加Nacos远程配置信息
{
"refreshGatewayRoute": true,
"routeList": [
{
"id": "basketball-api",
"predicates": [
{
"name": "Path",
"args": {
"_genkey_0": "/ball/**"
}
}
],
"filters": [
{
"name": "StripPrefix",
"args": {
"_genkey_0": "1"
}
}
],
"uri": "lb://basketball",
"order": 0
},
{
"id": "ikun-api",
"predicates": [
{
"name": "Path",
"args": {
"_genkey_0": "/kun/**"
}
}
],
"filters": [
{
"name": "StripPrefix",
"args": {
"_genkey_0": "1"
}
}
],
"uri": "lb://ikun",
"order": 0
}
]
}
?
3、添加相关远程配置解析类
GatewayNacosProperties:
package com.example.gateway.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@SuppressWarnings("all")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
@ConfigurationProperties(prefix = "gateway.nacos")
@Component
public class GatewayNacosProperties {
private String serverAddr; //nacos服务地址
private String dataId; //配置文件名称
private String namespace; //指定命名空间
private String group; //分组
}
RouteEntity:
package com.example.gateway.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import java.util.ArrayList;
import java.util.List;
/**
* @author 云村小威
*/
@SuppressWarnings("all")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
public class RouteEntity {
//路由id
private String id;
//路由断言集合
private List<PredicateEntity> predicates = new ArrayList<>();
//路由过滤器集合
private List<FilterEntity> filters = new ArrayList<>();
//路由转发的目标uri
private String uri;
//路由执行的顺序
private int order = 0;
}
PredicateEntity:
package com.example.gateway.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* @author 云村小威
*/
@SuppressWarnings("all")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
public class PredicateEntity {
//断言对应的Name
private String name;
//断言规则
private Map<String, String> args = new LinkedHashMap<>();
}
FilterEntity:
package com.example.gateway.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* @author 云村小威
*/
@SuppressWarnings("all")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
public class FilterEntity {
//过滤器对应的Name
private String name;
//路由规则
private Map<String, String> args = new LinkedHashMap<>();
}
?
DynamicRoutingConfig :?🌟
package com.example.gateway;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.nacos.api.NacosFactory;
import com.alibaba.nacos.api.PropertyKeyConst;
import com.alibaba.nacos.api.config.ConfigService;
import com.alibaba.nacos.api.config.listener.Listener;
import com.alibaba.nacos.api.exception.NacosException;
import com.example.gateway.pojo.FilterEntity;
import com.example.gateway.pojo.GatewayNacosProperties;
import com.example.gateway.pojo.PredicateEntity;
import com.example.gateway.pojo.RouteEntity;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
import org.springframework.cloud.gateway.filter.FilterDefinition;
import org.springframework.cloud.gateway.handler.predicate.PredicateDefinition;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionWriter;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import org.springframework.web.util.UriComponentsBuilder;
import reactor.core.publisher.Mono;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.Executor;
/**
* 此类实现了Spring Cloud Gateway + nacos 的动态路由,
* 它实现一个Spring提供的事件推送接口ApplicationEventPublisherAware
*/
@SuppressWarnings("all")
@Slf4j
@Component
public class DynamicRoutingConfig implements ApplicationEventPublisherAware {
@Autowired
private RouteDefinitionWriter routeDefinitionWriter;
@Autowired
private GatewayNacosProperties gatewayProperties;
@Autowired
private ObjectMapper mapper;
private ApplicationEventPublisher applicationEventPublisher;
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.applicationEventPublisher = applicationEventPublisher;
}
/**
* 这个方法主要负责监听Nacos的配置变化,这里先使用参数构建一个ConfigService,
* 再使用ConfigService开启一个监听,
* 并且在监听的方法中刷新路由信息。
*/
@Bean
public void refreshRouting() throws NacosException {
//创建Properties配置类
Properties properties = new Properties();
System.out.println(gatewayProperties);
//设置nacos的服务器地址,从配置类GatewayProperties中获取
properties.put(PropertyKeyConst.SERVER_ADDR, gatewayProperties.getServerAddr());
//设置nacos的命名空间,表示从具体的命名空间中获取配置信息,不填代表默认从public获得
if (gatewayProperties.getNamespace() != null) {
properties.put(PropertyKeyConst.NAMESPACE, gatewayProperties.getNamespace());
}
//根据Properties配置创建ConfigService类
ConfigService configService = NacosFactory.createConfigService(properties);
//获得nacos中已有的路由配置
String json = configService.getConfig(gatewayProperties.getDataId(), gatewayProperties.getGroup(), 5000);
this.parseJson(json);
//添加监听器,监听nacos中的数据修改事件
configService.addListener(gatewayProperties.getDataId(), gatewayProperties.getGroup(), new Listener() {
@Override
public Executor getExecutor() {
return null;
}
/**
* 用于接收远端nacos中数据修改后的回调方法
*/
@Override
public void receiveConfigInfo(String configInfo) {
log.warn(configInfo);
//获取nacos中修改的数据并进行转换
parseJson(configInfo);
}
});
}
/**
* 解析从nacos读取的路由配置信息(json格式)
*/
public void parseJson(String json) {
log.warn("从Nacos返回的路由配置(JSON格式):" + json);
boolean refreshGatewayRoute = JSONObject.parseObject(json).getBoolean("refreshGatewayRoute");
if (refreshGatewayRoute) {
List<RouteEntity> list = JSON.parseArray(JSONObject.parseObject(json).getString("routeList")).toJavaList(RouteEntity.class);
for (RouteEntity route : list) {
update(assembleRouteDefinition(route));
}
} else {
log.warn("路由未发生变更");
}
}
/**
* 路由更新
*/
public void update(RouteDefinition routeDefinition) {
try {
this.routeDefinitionWriter.delete(Mono.just(routeDefinition.getId()));
log.warn("路由删除成功:" + routeDefinition.getId());
} catch (Exception e) {
log.error(e.getMessage(), e);
}
try {
routeDefinitionWriter.save(Mono.just(routeDefinition)).subscribe();
this.applicationEventPublisher.publishEvent(new RefreshRoutesEvent(this));
log.warn("路由更新成功:" + routeDefinition.getId());
} catch (Exception e) {
log.error(e.getMessage(), e);
}
}
/**
* 路由定义
*/
public RouteDefinition assembleRouteDefinition(RouteEntity routeEntity) {
RouteDefinition definition = new RouteDefinition();
// ID
definition.setId(routeEntity.getId());
// Predicates
List<PredicateDefinition> pdList = new ArrayList<>();
for (PredicateEntity predicateEntity : routeEntity.getPredicates()) {
PredicateDefinition predicateDefinition = new PredicateDefinition();
predicateDefinition.setArgs(predicateEntity.getArgs());
predicateDefinition.setName(predicateEntity.getName());
pdList.add(predicateDefinition);
}
definition.setPredicates(pdList);
// Filters
List<FilterDefinition> fdList = new ArrayList<>();
for (FilterEntity filterEntity : routeEntity.getFilters()) {
FilterDefinition filterDefinition = new FilterDefinition();
filterDefinition.setArgs(filterEntity.getArgs());
filterDefinition.setName(filterEntity.getName());
fdList.add(filterDefinition);
}
definition.setFilters(fdList);
// URI
URI uri = UriComponentsBuilder.fromUriString(routeEntity.getUri()).build().toUri();
definition.setUri(uri);
return definition;
}
}
?
4、接口调用测试
????????这样就可以通过配置统一管理服务接口, 如下图所示;通过网关端口8082可访问不同服务的接口实现路由转发、处理请求和响应。
?
?