目录
一、Config 远程配置
1.1 config 介绍
微服务意味着要将单体应用中的业务拆分成一个个子服务,每个服务的粒度相对较小,因此系统中会出现大 量的服务。 由于每个服务都需要必要的配置信息才能运行,所以一套集中式的,动态的配置管理设施是必不可少的。
Spring Cloud 提供了 ConfigServer来解决这个问题,Spring Cloud Config 为微服务架构中的微服务提供集中化的外部配置支持,配置服务器为各个不同微服 务应用的所有环境提供了一个中心化的外部配置。
一个使用微服务架构的应用系统可能会包括成百上千个微服务,配置各部相同,需求各不相同:
- 不同环境不同配置:例如数据源在不同的环境(开发,测试,生产)是不同的
- 运行期间可以动态调整。例如根据各个微服务的负载状况,动态调整数据源连接池大小或者熔断阀 值,并且调整时不停止微服务(配置修改后可以自动更新)
使用 Spring Cloud Alibaba Nacos Config,您可以在 Nacos Server 集中管理你 Spring Cloud 应用的外 部属性配置。
1.2 bootstrap.yml 配置文件
SpringBoot 默认支持properties和YAML两种格式的配置文件。
- bootstrap.yml(bootstrap.properties)用来程序引导时执行,应用于更加早期配置信息读取,如可以使用来配置application.yml中使用到参数等
- application.yml(application.properties) 应用程序特有配置信息,可以用来配置后续各个模块中需使用 的公共参数等。
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
二、Gateway 网关
2.1 gateway 介绍
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可以对请求和响应进行处理
2.2 gateway 使用
创建网关服务:
添加依赖 :
<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>
2.2.1 方式一
本地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服务的接口方法。但是这样还是不太方便,建议使用方法二动态路由远程配置👇
2.2.2 方式二(动态路由)
如果是跳到该目录的小帅和小美请看上面的服务创建与依赖配置哦!
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可访问不同服务的接口实现路由转发、处理请求和响应。