目录
一、Config 远程配置
1.1 config 介绍
1.2 bootstrap.yml 配置文件
二、Gateway 网关
2.1 gateway 介绍
2.2 gateway 使用
2.2.1 方式一
2.2.2 方式二(动态路由)
微服务意味着要将单体应用中的业务拆分成一个个子服务,每个服务的粒度相对较小,因此系统中会出现大 量的服务。 由于每个服务都需要必要的配置信息才能运行,所以一套集中式的,动态的配置管理设施是必不可少的。
Spring Cloud 提供了 ConfigServer来解决这个问题,Spring Cloud Config 为微服务架构中的微服务提供集中化的外部配置支持,配置服务器为各个不同微服 务应用的所有环境提供了一个中心化的外部配置。
一个使用微服务架构的应用系统可能会包括成百上千个微服务,配置各部相同,需求各不相同:
使用 Spring Cloud Alibaba Nacos Config,您可以在 Nacos Server 集中管理你 Spring Cloud 应用的外 部属性配置。
SpringBoot 默认支持properties和YAML两种格式的配置文件。
bootstrap.yml 先于 application.yml 加载 !!!
1、添加依赖
com.alibaba.cloud
spring-cloud-starter-alibaba-nacos-config
org.springframework.cloud
spring-cloud-starter-bootstrap
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可以对请求和响应进行处理
创建网关服务:
添加依赖 :
org.springframework.boot
spring-boot-starter-webflux
org.springframework.cloud
spring-cloud-starter-gateway
com.alibaba
fastjson
1.2.35
本地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 predicates = new ArrayList<>();
//路由过滤器集合
private List 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 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 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 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 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 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可访问不同服务的接口实现路由转发、处理请求和响应。