背景:
当我们使用微服务时,若想在本地联调就需要启动多个服务,为了避免本地启动过多服务,现将注册中心等基础服务共用。当我们在服务A开发时,都是注册到同一个nacos,这样本地和开发环境的服务A就会同时存在,当调用服务时就会使用负载均衡选择服务,导致我们无法正常调试接口。这时我们可以选择使用灰度版本来进行服务的选择。
具体实现步骤如下:
这样我们服务注册到nacos中点击 服务列表 会发现服务中都会带VERSION
spring: cloud: nacos: discovery: metadata: VERSION: zhangsan
public interface GrayLoadBalancer { /** * 根据serviceId 筛选可用服务 * @param serviceId 服务ID * @param request 当前请求 * @return ServiceInstance */ ServiceInstance choose(String serviceId, ServerHttpRequest request); }
import lombok.extern.slf4j.Slf4j; import org.apache.http.util.Asserts; import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.loadbalancer.DefaultResponse; import org.springframework.cloud.client.loadbalancer.LoadBalancerUriTools; import org.springframework.cloud.client.loadbalancer.Response; import org.springframework.cloud.gateway.config.LoadBalancerProperties; import org.springframework.cloud.gateway.filter.GatewayFilterChain; import org.springframework.cloud.gateway.filter.ReactiveLoadBalancerClientFilter; import org.springframework.cloud.gateway.support.DelegatingServiceInstance; import org.springframework.cloud.gateway.support.NotFoundException; import org.springframework.cloud.gateway.support.ServerWebExchangeUtils; import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory; import org.springframework.stereotype.Component; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; import java.net.URI; @Slf4j @Component public class GrayReactiveLoadBalancerClientFilter extends ReactiveLoadBalancerClientFilter { private final static String SCHEME = "lb"; private static final int LOAD_BALANCER_CLIENT_FILTER_ORDER = 10150; private final GrayLoadBalancer grayLoadBalancer; private final LoadBalancerProperties loadBalancerProperties; public GrayReactiveLoadBalancerClientFilter(LoadBalancerClientFactory clientFactory, LoadBalancerProperties loadBalancerProperties, GrayLoadBalancer grayLoadBalancer) { super(clientFactory, loadBalancerProperties); this.loadBalancerProperties = loadBalancerProperties; this.grayLoadBalancer = grayLoadBalancer; } @Override public int getOrder() { return LOAD_BALANCER_CLIENT_FILTER_ORDER; } @Override public Monofilter(ServerWebExchange exchange, GatewayFilterChain chain) { URI url = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR); String schemePrefix = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_SCHEME_PREFIX_ATTR); // 直接放行 if (url == null || (!SCHEME.equals(url.getScheme()) && !SCHEME.equals(schemePrefix))) { return chain.filter(exchange); } // 保留原始url ServerWebExchangeUtils.addOriginalRequestUrl(exchange, url); if (log.isTraceEnabled()) { log.trace(ReactiveLoadBalancerClientFilter.class.getSimpleName() + " url before: " + url); } return choose(exchange).doOnNext(response -> { if (!response.hasServer()) { throw NotFoundException.create(loadBalancerProperties.isUse404(), "Unable to find instance for " + url.getHost()); } URI uri = exchange.getRequest().getURI(); // if the `lb: ` mechanism was used, use ` ` as the default, // if the loadbalancer doesn't provide one. String overrideScheme = null; if (schemePrefix != null) { overrideScheme = url.getScheme(); } DelegatingServiceInstance serviceInstance = new DelegatingServiceInstance(response.getServer(), overrideScheme); URI requestUrl = LoadBalancerUriTools.reconstructURI(serviceInstance, uri); if (log.isTraceEnabled()) { log.trace("LoadBalancerClientFilter url chosen: " + requestUrl); } exchange.getAttributes().put(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR, requestUrl); }).then(chain.filter(exchange)); } /** * 获取实例 * @param exchange ServerWebExchange * @return ServiceInstance */ private Mono > choose(ServerWebExchange exchange) { URI uri = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR); Asserts.notNull(uri, "uri"); ServiceInstance serviceInstance = grayLoadBalancer.choose(uri.getHost(), exchange.getRequest()); return Mono.just(new DefaultResponse(serviceInstance)); } }
当我们调用服务带版本号时会优先匹配带版本号的服务,若找不到则会随机选择一个服务
import cn.hutool.core.collection.CollUtil; import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.RandomUtil; import cn.hutool.core.util.StrUtil; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.discovery.DiscoveryClient; import org.springframework.cloud.gateway.support.NotFoundException; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.stereotype.Component; import java.util.List; import java.util.stream.Collectors; @Slf4j @RequiredArgsConstructor @Component public class VersionGrayLoadBalancer implements GrayLoadBalancer { private final DiscoveryClient discoveryClient; /** * 根据serviceId 筛选可用服务 * @param serviceId 服务ID * @param request 当前请求 * @return ServiceInstance */ @Override public ServiceInstance choose(String serviceId, ServerHttpRequest request) { Listinstances = discoveryClient.getInstances(serviceId); // 注册中心无实例 抛出异常 if (CollUtil.isEmpty(instances)) { log.warn("No instance available for {}", serviceId); throw new NotFoundException("No instance available for " + serviceId); } // 获取请求version,无则随机返回可用实例 String reqVersion = request.getHeaders().getFirst(CommonConstant.VERSION); if (StrUtil.isBlank(reqVersion)) { return instances.get(RandomUtil.randomInt(instances.size())); } // 遍历可以实例元数据,若匹配则返回此实例 List availableList = instances.stream() .filter(instance -> reqVersion .equalsIgnoreCase(MapUtil.getStr(instance.getMetadata(), CommonConstant.VERSION))) .collect(Collectors.toList()); if (CollUtil.isEmpty(availableList)) { return instances.get(RandomUtil.randomInt(instances.size())); } return availableList.get(RandomUtil.randomInt(availableList.size())); } }
很明显的一个现象,除了一些老项目,现在 Java 后端项目基本都是基于 Spring Boot 进行开发,毕竟它这么好用以及天然微服务友好。不夸张的说,Spring Boot 是 Java 后端领域最最最重要的技术之一,熟练掌握它对于 Java 程序员至关重要。
这篇文章我会推荐一些优质的 Spring Boot 实战项目,帮助大家深入学习 Spring Boot。项目质量的话,大家可以放心。
对于下面的开源项目,可以这样说每一个开源项目都有很多可以优化的地方。如果你想真正学到东西的话,建议不光要把项目跑起来更要去优化!
项目文档+源码获取方式:戳此传送门即可获取
eladmin 是一款基于 Spring Boot 2.1.0 、 Jpa、 Spring Security、redis、Vue 的前后端分离的后台管理系统,项目采用分模块开发方式, 权限控制采用 RBAC,支持数据字典与数据权限管理,支持一键生成前后端代码,支持动态路由。
这个开源项目基本稳定,并且后续作者还会继续优化。并且,完全开源!这个真的要为原作者点个赞,如果大家觉得这个项目有用的话,建议可以稍微捐赠一下原作者支持一下。后端整理代码质量、表设计等各个方面来说都是很不错的。前后端分离,前端使用的是国内常用的 vue 框架,也比较容易上手。
后台首页
角色管理页面
一位朋友的项目,非常不错,值得推荐!
mall 这个项目的话,是一套电商系统,包括前台商城系统及后台管理系统,基于 SpringBoot+MyBatis 实现,采用 Docker 容器化部署。
前台商城系统包含首页门户、商品推荐、商品搜索、商品展示、购物车、订单流程、会员中心、客户服务、帮助中心等模块。 后台管理系统包含商品管理、订单管理、会员管理、促销管理、运营管理、内容管理、统计报表、财务管理、权限管理、设置等模块。
另外,这个项目还提供了详细的文档,帮助你进一步学习。
江南一点雨大佬的力作。整个项目不论是前端还是后端的代码质量都比较高,非常值得学习。
然后,vhr(微人事)这个项目的话,是一个前后端分离的人力资源管理系统,后端基于 SpringBoot 开发,前端基于 Vue 开发,并且,项目加入常见的企业级应用所涉及到的技术点,例如 Redis、RabbitMQ 等。
另外,这个项目提供了非常详细的文档。
基于 Spring Boot 2.X 的开源项目。favorites-web(云收藏)是一个使用 Spring Boot 构建的开源网站,可以让用户在线随时随地收藏的一个网站,在网站上分类整理收藏的网站或者文章。
开源论坛、问答系统,现有功能提问、回复、通知、最新、最热、消除零回复功能。功能持续更新中…… 技术栈 Spring、Spring Boot、MyBatis、MySQL/H2、Bootstrap。
目前这个写在简历上的重复率还好,自己稍微改造一下还是很有潜力的。
提供一套基于 Spring Boot-Shiro-Vue 的权限管理思路.前后端都加以控制,做到按钮/接口级别的权限
项目文档+源码获取方式 :关注下方【公众浩】可免费获取