场景:本地开发中,所有开发人员都启动了多个微服务,在进行远程调用时,可能请求的是其他开发人员的服务,导致本地联调debug效率低下。
SpringCloud新版本中负载均衡器由原来的ribbon替换为Spring自己开发的Loadbalancer,默认只提供了2中负载均衡策略:RandomLoadBalancer 和 RoundRobinLoadBalancer。SpringCloud Alibaba Nacos 2021.1版本提供了基于Nacos注册中心的轮询策略 NacosLoadBalancer 是基于权重的策略。
NacosLoadBalancer的权重策略默认是关闭的,如果要使用基于权重的负载策略要手动开启。
如果未给服务器设置权重,建议不要使用基于权重的策略,因为如果微服务的权重都相同,相当于随机
修改配置文件配置
#开启nacos的负载均衡策略
spring.cloud.loadbalancer.nacos.enabled=true
/**
* 修改Nacos的负载均衡策略,主要是为了将请求路由到本地服务中,方便开发
* @author guojunwang
* @date 2022-05-18 14:17
*/
public class CustomNacosLoadBalancer extends NacosLoadBalancer implements ReactorServiceInstanceLoadBalancer {
@Value("${spring.profiles.active}")
private String profilesActive;
private static final Logger log = LoggerFactory.getLogger(NacosLoadBalancer.class);
private final String serviceId;
private ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider;
private final NacosDiscoveryProperties nacosDiscoveryProperties;
public CustomNacosLoadBalancer(ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider,
String serviceId, NacosDiscoveryProperties nacosDiscoveryProperties) {
super(serviceInstanceListSupplierProvider,serviceId,nacosDiscoveryProperties);
this.serviceId = serviceId;
this.serviceInstanceListSupplierProvider = serviceInstanceListSupplierProvider;
this.nacosDiscoveryProperties = nacosDiscoveryProperties;
}
@Override
public Mono<Response<ServiceInstance>> choose(Request request) {
ServiceInstanceListSupplier supplier = serviceInstanceListSupplierProvider
.getIfAvailable(NoopServiceInstanceListSupplier::new);
return supplier.get().next().map(this::getInstanceResponse);
}
private Response<ServiceInstance> getInstanceResponse(List<ServiceInstance> serviceInstances) {
if (serviceInstances.isEmpty()) {
log.warn("No servers available for service: " + this.serviceId);
return new EmptyResponse();
}
try {
String clusterName = this.nacosDiscoveryProperties.getClusterName();
List<ServiceInstance> instancesToChoose = serviceInstances;
if (StringUtils.isNotBlank(clusterName)) {
List<ServiceInstance> sameClusterInstances = serviceInstances.stream()
.filter(serviceInstance -> {
String cluster = serviceInstance.getMetadata()
.get("nacos.cluster");
return StringUtils.equals(cluster, clusterName);
}).collect(Collectors.toList());
if (!CollectionUtils.isEmpty(sameClusterInstances)) {
instancesToChoose = sameClusterInstances;
}
} else {
log.warn(
"A cross-cluster call occurs,name = {}, clusterName = {}, instance = {}",
serviceId, clusterName, serviceInstances);
}
//本地开发模式路由会本机
if(StrUtil.equals(SpringUtil.getProperty("spring.profiles.active"),"dev")){
//如果有本机的服务,优先选择本地服务
List<ServiceInstance> localServiceInstance = instancesToChoose.stream().filter(i -> IpUtil.isLocal(i.getHost())).collect(Collectors.toList());
//使用本地服务进行选举
if (CollUtil.isNotEmpty(localServiceInstance)) {
instancesToChoose = localServiceInstance;
log.info("开发模式,负载均衡器优先选择本机服务调用==>{}", instancesToChoose.stream().map( is ->{
String msg =" serviceId=%s,host=%s,port=%s ";
return String.format(msg, is.getServiceId(), is.getHost(), is.getPort());
}).collect(Collectors.toList()));
}
}
//权重选取
ServiceInstance instance = NacosBalancer.getHostByRandomWeight3(instancesToChoose);
return new DefaultResponse(instance);
}
catch (Exception e) {
log.warn("NacosLoadBalancer error", e);
return null;
}
}
}
用到的IpUtil工具类
需要在pom加入依赖
<dependency>
<groupId>javax.servletgroupId>
<artifactId>servlet-apiartifactId>
<version>2.5version>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-webartifactId>
<optional>trueoptional>
dependency>
@Slf4j
public class IpUtil {
/**
* localAddresses 减少运行耗时,初始化时吧本机地址缓存起来
* 缺点,网络改变时,需要重启
*/
private static List<String> localAddresses = new ArrayList<>();
static {
try {
Enumeration<NetworkInterface> enumNI = NetworkInterface.getNetworkInterfaces();
while ( enumNI.hasMoreElements() ){
NetworkInterface ifc = enumNI.nextElement();
if( ifc.isUp() ){
Enumeration<InetAddress> enumAdds = ifc.getInetAddresses();
while ( enumAdds.hasMoreElements() ){
InetAddress addr = enumAdds.nextElement();
localAddresses.add(addr.getHostAddress());
}
}
}
localAddresses.add("localhost");
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 判断是否同一个地址,无论是主机名还是ip地址
* @param host1
* @param host2
* @return
*/
public static boolean sameAddress(String host1,String host2){
if(StrUtil.isBlank(host1) || StrUtil.isBlank(host2)){
return false;
}
try {
if(isLocal(host1)){
host1 = InetAddress.getLocalHost().getHostName();
}
if(isLocal(host2)){
host2 = InetAddress.getLocalHost().getHostName();
}
InetAddress addr1 = InetAddress.getByName(host1);
InetAddress addr2 = InetAddress.getByName(host2);
return addr1.getHostAddress().equals(addr2.getHostAddress());
} catch (UnknownHostException e) {
e.printStackTrace();
return false;
}
}
public static boolean isLocal(String host){
return localAddresses.contains(host);
}
/***
* 获取客户端ip地址(可以穿透代理)
* @param request
* @return
*/
public static String getClientIpAddr(HttpServletRequest request) {
String ip = request.getHeader("X-Forwarded-For");
if (!isValidIp(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (!isValidIp(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (!isValidIp(ip)) {
ip = request.getHeader("HTTP_X_FORWARDED_FOR");
}
if (!isValidIp(ip)) {
ip = request.getHeader("HTTP_X_FORWARDED");
}
if (!isValidIp(ip)) {
ip = request.getHeader("HTTP_X_CLUSTER_CLIENT_IP");
}
if (!isValidIp(ip)) {
ip = request.getHeader("HTTP_CLIENT_IP");
}
if (!isValidIp(ip)) {
ip = request.getHeader("HTTP_FORWARDED_FOR");
}
if (!isValidIp(ip)) {
ip = request.getHeader("HTTP_FORWARDED");
}
if (!isValidIp(ip)) {
ip = request.getHeader("HTTP_VIA");
}
if (!isValidIp(ip)) {
ip = request.getHeader("REMOTE_ADDR");
}
if (!isValidIp(ip)) {
ip = request.getRemoteAddr();
}
return ip;
}
public static String getClientIpAddr(ServerHttpRequest request) {
String ip = request.getHeaders().getFirst("X-Forwarded-For");
if (!isValidIp(ip)) {
ip = request.getHeaders().getFirst("Proxy-Client-IP");
}
if (!isValidIp(ip)) {
ip =request.getHeaders().getFirst("WL-Proxy-Client-IP");
}
if (!isValidIp(ip)) {
ip = request.getHeaders().getFirst("HTTP_X_FORWARDED_FOR");
}
if (!isValidIp(ip)) {
ip = request.getHeaders().getFirst("HTTP_X_FORWARDED");
}
if (!isValidIp(ip)) {
ip = request.getHeaders().getFirst("HTTP_X_CLUSTER_CLIENT_IP");
}
if (!isValidIp(ip)) {
ip = request.getHeaders().getFirst("HTTP_CLIENT_IP");
}
if (!isValidIp(ip)) {
ip = request.getHeaders().getFirst("HTTP_FORWARDED_FOR");
}
if (!isValidIp(ip)) {
ip = request.getHeaders().getFirst("HTTP_FORWARDED");
}
if (!isValidIp(ip)) {
ip = request.getHeaders().getFirst("HTTP_VIA");
}
if (!isValidIp(ip)) {
ip = request.getHeaders().getFirst("REMOTE_ADDR");
}
if (!isValidIp(ip)) {
ip = request.getRemoteAddress().getHostName();
}
return ip;
}
private static boolean isValidIp(String ip){
return !(ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip));
}
/**
* 获取tomcat内置服务监听端口
* @return
*/
public static int getTomcatPort() {
try{
MBeanServer beanServer = ManagementFactory.getPlatformMBeanServer();
Set<ObjectName> objectNames = beanServer.queryNames(new ObjectName("*:type=Connector,*"),
Query.match(Query.attr("protocol"), Query.value("HTTP/1.1")));
String port = objectNames.iterator().next().getKeyProperty("port");
return Integer.valueOf(port);
}catch (Exception e){
log.error("get tomcat port fail", e);
}
return 0;
}
}
还需要编写配置类:
@LoadBalancerClients(defaultConfiguration = NacosLoadBalancerConfig.class)
public class NacosLoadBalancerConfig {
@Bean
public ReactorLoadBalancer<ServiceInstance> nacosLoadBalancer(Environment environment,
LoadBalancerClientFactory loadBalancerClientFactory,NacosDiscoveryProperties configProperties)
{
String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
return new CustomNacosLoadBalancer(loadBalancerClientFactory
.getLazyProvider(name, ServiceInstanceListSupplier.class), name,configProperties);
}
}