配合K8S 本地调试Springcloud微服务中的某一个模块
此方案仅适用于k8s环境中部署的SpringCloud微服务程序. 中心思想是:利用kt connect+Proxifier将本机的网络代理到k8s服务器中,自定义负载均衡策略识别http请求中的标识(我使用的是header中的特殊值)匹配,微服务注册到nacos中的元数据值,将请求路由到指定的服务器中.具体如下.答题思路就是以上思路具体中也要看项目使用的gateway是什么来具体做,本项目使用的
此方案仅适用于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指定值的服务器中了
更多推荐
所有评论(0)