springcloud + nacos实现共用基础服务(灰度版本)

背景:

当我们使用微服务时,若想在本地联调就需要启动多个服务,为了避免本地启动过多服务,现将注册中心等基础服务共用。当我们在服务A开发时,都是注册到同一个nacos,这样本地和开发环境的服务A就会同时存在,当调用服务时就会使用负载均衡选择服务,导致我们无法正常调试接口。这时我们可以选择使用灰度版本来进行服务的选择。

springcloud + nacos实现共用基础服务(灰度版本)_第1张图片

具体实现步骤如下:

1、我们在本地配置文件中添加版本头

这样我们服务注册到nacos中点击 服务列表 会发现服务中都会带VERSION

springcloud + nacos实现共用基础服务(灰度版本)_第2张图片

spring:
  cloud:
    nacos:
      discovery:
        metadata:
          VERSION: zhangsan

2、添加灰度服务接口

public interface GrayLoadBalancer {

	/**
	 * 根据serviceId 筛选可用服务
	 * @param serviceId 服务ID
	 * @param request 当前请求
	 * @return ServiceInstance
	 */
	ServiceInstance choose(String serviceId, ServerHttpRequest request);

}

3、灰度过滤器

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 Mono filter(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));
    }

}

4、基于客户端版本号灰度路由

当我们调用服务带版本号时会优先匹配带版本号的服务,若找不到则会随机选择一个服务

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) {
		List instances = 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。项目质量的话,大家可以放心。

Spring Boot 实战项目/脚手架推荐

对于下面的开源项目,可以这样说每一个开源项目都有很多可以优化的地方。如果你想真正学到东西的话,建议不光要把项目跑起来更要去优化!

项目文档+源码获取方式:戳此传送门即可获取

1.eladmin(9.4k star)

eladmin 是一款基于 Spring Boot 2.1.0 、 Jpa、 Spring Security、redis、Vue 的前后端分离的后台管理系统,项目采用分模块开发方式, 权限控制采用 RBAC,支持数据字典与数据权限管理,支持一键生成前后端代码,支持动态路由。

这个开源项目基本稳定,并且后续作者还会继续优化。并且,完全开源!这个真的要为原作者点个赞,如果大家觉得这个项目有用的话,建议可以稍微捐赠一下原作者支持一下。后端整理代码质量、表设计等各个方面来说都是很不错的。前后端分离,前端使用的是国内常用的 vue 框架,也比较容易上手。

springcloud + nacos实现共用基础服务(灰度版本)_第3张图片

springcloud + nacos实现共用基础服务(灰度版本)_第4张图片

后台首页

springcloud + nacos实现共用基础服务(灰度版本)_第5张图片

角色管理页面

2.mall(36.1k star)

一位朋友的项目,非常不错,值得推荐!

mall 这个项目的话,是一套电商系统,包括前台商城系统及后台管理系统,基于 SpringBoot+MyBatis 实现,采用 Docker 容器化部署。

前台商城系统包含首页门户、商品推荐、商品搜索、商品展示、购物车、订单流程、会员中心、客户服务、帮助中心等模块。 后台管理系统包含商品管理、订单管理、会员管理、促销管理、运营管理、内容管理、统计报表、财务管理、权限管理、设置等模块。

另外,这个项目还提供了详细的文档,帮助你进一步学习。

springcloud + nacos实现共用基础服务(灰度版本)_第6张图片

springcloud + nacos实现共用基础服务(灰度版本)_第7张图片

3.vhr(16.9k star)

江南一点雨大佬的力作。整个项目不论是前端还是后端的代码质量都比较高,非常值得学习。

然后,vhr(微人事)这个项目的话,是一个前后端分离的人力资源管理系统,后端基于 SpringBoot 开发,前端基于 Vue 开发,并且,项目加入常见的企业级应用所涉及到的技术点,例如 Redis、RabbitMQ 等。

另外,这个项目提供了非常详细的文档。

springcloud + nacos实现共用基础服务(灰度版本)_第8张图片

4.favorites-web(3.9k star)

基于 Spring Boot 2.X 的开源项目。favorites-web(云收藏)是一个使用 Spring Boot 构建的开源网站,可以让用户在线随时随地收藏的一个网站,在网站上分类整理收藏的网站或者文章。

springcloud + nacos实现共用基础服务(灰度版本)_第9张图片

5.community(0.8k star)

开源论坛、问答系统,现有功能提问、回复、通知、最新、最热、消除零回复功能。功能持续更新中…… 技术栈 Spring、Spring Boot、MyBatis、MySQL/H2、Bootstrap。

目前这个写在简历上的重复率还好,自己稍微改造一下还是很有潜力的。

springcloud + nacos实现共用基础服务(灰度版本)_第10张图片

6.SpringBoot-Shiro-Vue(2.7k star)

提供一套基于 Spring Boot-Shiro-Vue 的权限管理思路.前后端都加以控制,做到按钮/接口级别的权限

springcloud + nacos实现共用基础服务(灰度版本)_第11张图片

项目文档+源码获取方式 :关注下方【公众浩】可免费获取

 

你可能感兴趣的:(spring,cloud,微服务,java)