此方案仅适用于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到本机的环境变量中
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打开的端口
6.启动微服务,在nacos中注册一个flag值:
5. 修改gateway和feign的负载均衡策略,根据前端传入的header 中的值路由到不同服务器
6. 微服务模块创建 RibbonConfig.java类
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.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
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;
/**
* 要求每台服务器被调用5次才能轮询下一个,也就是说以前每台机器一次,现在每台机器5次。
*/
@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) {
log.info("进入开发者路由模式:{}",lb.getClass());
ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
String developer = null;
if(servletRequestAttributes != null) {
log.info("获取当前线程:{}" , servletRequestAttributes);
HttpServletRequest request = servletRequestAttributes.getRequest();
developer = request.getHeader(CommonConstant.DEVELOPER);
}else{ #网关gateway无法获取request.这里网上很多原因.所以在gateway覆写了LoadBalancerClientFilter类为GlobalDeveloperLoadBalancerClientFilter
developer = DeveloperInfoHolder.get();
}
log.info("获取的开发人员信息:{}",developer);
if (lb == null) {
return null;
}
List<Server> upList = lb.getReachableServers();
List<Server> allList = lb.getAllServers();
log.info("总服务器数量:{} 可达服务器数量:{}",allList.size(),upList.size());
for (Server server : allList) {
log.info("节点{} 端口:{} 内容:{}",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.info("未发现开发者信息");
return randomChoose(upList);
}
log.info("获取到开发者参数:{}",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.info("未查找到{}开发服务器,将采用随机服务器",developer);
//正常情况下,每次都应该可以选择一个服务实例
server = randomChoose(upList);
log.info("随机转发服务器为:{}",server.getHost());
return server;
}
private Server randomChoose(List<Server> servers) {
int randomIndex = RandomUtils.nextInt(0,servers.size());
return servers.get(randomIndex);
}
}
编辑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.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
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.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.filter.LoadBalancerClientFilter;
import org.springframework.cloud.gateway.support.DelegatingServiceInstance;
import org.springframework.cloud.gateway.support.NotFoundException;
import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.net.URI;
import java.util.ArrayList;
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{
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
此时在前端调用后端微服务的时候增加developer的header就能路由到developer指定值的服务器中了