此方案仅适用于k8s环境中部署的SpringCloud微服务程序. 中心思想是:利用kt connect+Proxifier将本机的网络代理到k8s服务器中,自定义负载均衡策略识别http请求中的标识(我使用的是header中的特殊值)匹配,微服务注册到nacos中的元数据值,将请求路由到指定的服务器中.具体如下.答题思路就是以上思路具体中也要看项目使用的gateway是什么来具体做,本项目使用的网关是gateway负载均衡策略为 Ribbon

1.下载kubectl(下载)到本地的环境变量中并将k8s服务器中的config文件(/root/.kube/config) 复制到本地的 C:\Users\joker\.kube 文件目录下,修改 server 为k8s集群的IP,

2.下载kt connect(下载),加压缩重名名文件为ktctl.exe 到本机的环境变量中

3. 在Windows中使用 ktctl --debug --image=registry.cn-hangzhou.aliyuncs.com/rdc-incubator/kt-connect-shadow:stable --namespace=p-lms connect --method=socks5 #这里的namespace指定微服务所在的namespace中

4. 本机安装 Proxifier 设置代理信息到 ktctl打开的端口 (上一步如果在项目根目录下执行并且安装了https://plugins.jetbrains.com/plugin/13482-jvm-inject 插件后,本步骤可以不需要, 我这边是想看到具体的流量流向所以才做如下步骤)

配置如下:

点击代理服务器:配置如下

点击代理规则:这里不是所有流量都需要走k8s集群只需要代理k8s集群流量即可(因为我k8s集群中项目的pod IP是10.开头的在这里如下配置即可)

4.启动微服务,在nacos中注册一个flag值:方案如下

spring:
  cloud:
    nacos:
      discovery:
        metadata:
          developer: moren
          excludeOther: true

:在nacos中注册了一个元数据,在nacos的为服务中是如下显示:后面再负载均衡的时候就是取出这个数据和header中的值做对比然后分发流量的 excludeOther 的意思是不允许其他人访问本地的项目

5. 修改gateway和feign的负载均衡策略,根据前端传入的header 中的值路由到不同服务器

6. 微服务模块创建 RibbonConfig.java类

package org.jeecg.config;


import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.RandomRule;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;

/**
 * 如何将RestTemplate 和 Ribbon 相结合进行负载均衡?
 * 只需要在程序的IOC容器中注入一个 RestTemplate 的 Bean,并在这个 Bean 上加上 @LoadBalanced 注解(负载均衡注解)
 * 此时 RestTemplate 就结合 Ribbon 开启了负载均衡功能
 *
 */

@Configuration
@Slf4j
public class RibbonConfig {
    @Value("${spring.balance.method}")
    private String balanceMethod;

    @Bean
    @LoadBalanced
    public RestTemplate restTemplate(ClientHttpRequestFactory factory){
        return new RestTemplate(factory);
    }
    @Bean
    public ClientHttpRequestFactory simpleClientHttpRequestFactory() {
        SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
        factory.setReadTimeout(5000);//ms
        factory.setConnectTimeout(15000);//ms
        return factory;
    }
    /**
     * 更改 负载均衡策略算法
     * RandomRule #配置规则 随机
     * RoundRobinRule #配置规则 轮询
     * RetryRule #配置规则 重试
     * WeightedResponseTimeRule #配置规则 响应时间权重
     * 也可以自定义负载均衡策略的类
     * @return
     */
    @Bean
    @Scope("prototype")
    public IRule myRule(){
        log.info("选择的负载均衡算法:{}",balanceMethod);
        if (balanceMethod.equals("developer")) {
            return new DeveloperLocalDebugRule();//自定义的Rule
        }else{
            try {
                return (IRule) Class.forName(balanceMethod).newInstance();
            } catch (Exception e) {
                e.printStackTrace();

            }
        }
        log.info("未选择负载均衡策略,使用RandomRule");
        return new RandomRule();
    }
}

7 编写 DeveloperLocalDebugRule.java



import com.alibaba.cloud.nacos.ribbon.NacosServer;
import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.AbstractLoadBalancerRule;
import com.netflix.loadbalancer.ILoadBalancer;
import com.netflix.loadbalancer.Server;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomUtils;
import org.apache.commons.lang3.StringUtils;
import org.jeecg.common.constant.CommonConstant;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.List;
import java.util.Map;


@Slf4j
public class DeveloperLocalDebugRule extends AbstractLoadBalancerRule {
    @Override
    public void initWithNiwsConfig(IClientConfig iClientConfig) {

    }

    @Override
    public Server choose(Object key) {
        return choose(getLoadBalancer(), key);
    }

    /**
     * Randomly choose from all living servers
     */
    //@edu.umd.cs.findbugs.annotations.SuppressWarnings(value = "RCN_REDUNDANT_NULLCHECK_OF_NULL_VALUE")
    //从服务清单中随机选择一个服务实例
    @SuppressWarnings(value = "RCN_REDUNDANT_NULLCHECK_OF_NULL_VALUE")
    public Server choose(ILoadBalancer lb, Object key) {
        if (lb == null) {
            return null;
        }
        log.debug("进入开发者路由模式:{} lb:{}",lb.getClass(),lb);

        ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        String developer = null;
        if(servletRequestAttributes != null) {
            HttpServletRequest request = servletRequestAttributes.getRequest();
            developer = request.getHeader(CommonConstant.DEVELOPER);
            log.info("根据线程获取:{}" , developer);
        }else{
            developer = DeveloperInfoHolder.get();
            DeveloperInfoHolder.clear();
            log.info("从Holder中获取:{}",developer);
        }
        log.info("获取的开发人员信息:{}",developer);

        List<Server> upList = lb.getReachableServers();
        List<Server> allList = lb.getAllServers();
        log.debug("总服务器数量:{}  可达服务器数量:{}",allList.size(),upList.size());
        for (Server server : allList) {
            log.debug("节点{} 端口:{} 内容:{}",server.getHost(),server
            .getPort(),server.getMetaInfo());
        }
        int serverCount = upList.size();
        if (serverCount == 0) {
            log.info("无可用服务器");
            return null;
        }
        Server server = null;
        //如果没有开发者标志
        if(StringUtils.isEmpty(developer)){
            log.debug("未发现开发者信息");
            server = chooseNoExcludeOther(upList);
            log.info("随机转发服务器为:{}",server.getHost());
            return server;
        }
        log.debug("获取到开发者参数:{}",developer);
        for (Server upServer : upList) {
            //响应中断事件
            if (Thread.interrupted()) {
                return null;
            }
            if(upServer instanceof NacosServer){
                Map<String,String> mata = ((NacosServer) upServer).getMetadata();
                String mata_developer = mata.get(CommonConstant.DEVELOPER);
                if(developer.equals(mata_developer)){
                    server = upServer;
                    if(server.isAlive()){
                        log.info("获取到开发者服务器:{}:{}", server.getHost(),server.getPort());
                        return server;
                    }
                }else{
                    //让一下线程
                    Thread.yield();
                }
            }
        }
        log.debug("未查找到{}开发服务器,将采用随机服务器",developer);
        //正常情况下,每次都应该可以选择一个服务实例
        server = chooseNoExcludeOther(upList);
        log.info("随机转发服务器为:{}",server.getHost());
        return server;
    }

    /**
     * 找一个不排除的
     * @param upList
     * @return
     */
    private Server chooseNoExcludeOther(List<Server> upList) {
        for (Server upServer : upList) {
            Map<String,String> mata = ((NacosServer) upServer).getMetadata();
            String mata_developer = mata.get("excludeOther");
            if(StringUtils.isEmpty(mata_developer) || !"true".equals(mata_developer)){
                return  upServer;
            }
        }
        return null;
    }

    private Server randomChoose(List<Server> servers) {
        int randomIndex = RandomUtils.nextInt(0,servers.size());
        Server server = servers.get(randomIndex);
        log.debug("随机一个访问服务器:{} IP:{} PORT:{}",randomIndex,server.getHost(),server.getPort());
        return server;
    }

}

编辑FeignConfig.java类


import feign.Feign;
import feign.Logger;
import feign.RequestInterceptor;
import feign.codec.Encoder;
import feign.form.spring.SpringFormEncoder;
import lombok.extern.slf4j.Slf4j;
import org.jeecg.common.constant.CommonConstant;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.cloud.openfeign.FeignAutoConfiguration;
import org.springframework.cloud.openfeign.support.SpringEncoder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.context.annotation.Scope;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;


@ConditionalOnClass(Feign.class)
@AutoConfigureBefore(FeignAutoConfiguration.class)
@Slf4j
@Configuration
public class FeignConfig {

    @Bean
    public RequestInterceptor requestInterceptor() {
        return requestTemplate -> {
            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            if (null != attributes) {
                HttpServletRequest request = attributes.getRequest();
                log.info("Feign request: {}", request.getRequestURI());
                // 将token信息放入header中
                String token = request.getHeader(CommonConstant.X_ACCESS_TOKEN);
                if(token==null){
                    token = request.getParameter("token");
                }
                log.info("Feign request token: {}", token);
                requestTemplate.header(CommonConstant.X_ACCESS_TOKEN, token);
                //将开发者信息放到header中.主要是这里每个微服务中的feign调用的时候都会集成这个header
                String developer = request.getHeader(CommonConstant.DEVELOPER);
                if(developer==null){
                    developer = request.getParameter(CommonConstant.DEVELOPER);
                }
                log.info("Feign request developer: {}", developer);
                requestTemplate.header(CommonConstant.DEVELOPER, developer);
            }
        };
    }



    /**
     * Feign 客户端的日志记录,默认级别为NONE
     * Logger.Level 的具体级别如下:
     * NONE:不记录任何信息
     * BASIC:仅记录请求方法、URL以及响应状态码和执行时间
     * HEADERS:除了记录 BASIC级别的信息外,还会记录请求和响应的头信息
     * FULL:记录所有请求与响应的明细,包括头信息、请求体、元数据
     */
    @Bean
    Logger.Level feignLoggerLevel() {
        return Logger.Level.FULL;
    }

    /**
     * Feign支持文件上传
     * @param messageConverters
     * @return
     */
    @Bean
    @Primary
    @Scope("prototype")
    public Encoder multipartFormEncoder(ObjectFactory<HttpMessageConverters> messageConverters) {
        return new SpringFormEncoder(new SpringEncoder(messageConverters));
    }
}

编写GlobalDeveloperLoadBalancerClientFilter.java 类


import lombok.extern.slf4j.Slf4j;
import org.jeecg.config.DeveloperInfoHolder;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.cloud.gateway.config.LoadBalancerProperties;
import org.springframework.cloud.gateway.filter.LoadBalancerClientFilter;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import java.util.List;

@Component
@Slf4j
public class GlobalDeveloperLoadBalancerClientFilter extends LoadBalancerClientFilter {

    public GlobalDeveloperLoadBalancerClientFilter(LoadBalancerClient loadBalancer, LoadBalancerProperties properties) {
        super(loadBalancer, properties);
    }

    protected ServiceInstance choose(ServerWebExchange exchange) {
        try{
            log.info("拦截Developer");
            List<String> developer = exchange.getRequest().getHeaders().get("developer");
            if(developer!=null && !developer.isEmpty()) {
                log.info(String.format("请求的developer:%s", developer));
                DeveloperInfoHolder.set(developer.get(0));
            }
        }catch (Exception ex){
            log.error("获取developer出错,忽略此问题!");
        }
        return super.choose(exchange);
    }
}

在yml中添加nacos的元数据

spring:
  cloud:
    nacos:
      discovery:
        metadata:
          developer: moren
          excludeOther: true

此时在前端调用后端微服务的时候增加developer的header就能路由到developer指定值的服务器中了

Logo

K8S/Kubernetes社区为您提供最前沿的新闻资讯和知识内容

更多推荐