背景说明:微服务架构下,开发者有一套本地环境及测试环境(nacos分别各有一套开发(本地)环境的命名空间及测试环境的命名空间,测试环境的服务注册在测试环境命名空间,本地启的应用注册在开发环境命名空间);例如共有5个服务,本地开发功能时,实际改动1个服务,但是整个流程涉及其他另外2个服务,这时可以只启动实际改动的服务,需要用到的关联服务调用测试环境服务,可减少本地资源消耗
目的:本地开发环境服务启动时优先调用本地服务,未启动时调用指定的测试环境服务
详细介绍:共有vp-cloud-ring、vp-cloud-onkey、vp-cloud-area、vp-cloud-bm、vp-cloud-common等模块,其中vp-cloud-common属于微服务公共模块,会被其他业务模块包含,需在vp-cloud-common模块中增加如下配置类:
import cn.**.vrbt.common.utils.IpUtil;
import cn.**.vrbt.config.lb.LoadBalanceConfig.MyRibbonConfig;
import com.netflix.client.config.CommonClientConfigKey;
import com.netflix.client.config.DefaultClientConfigImpl;
import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.ILoadBalancer;
import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.Server;
import com.netflix.loadbalancer.ZoneAvoidanceRule;
import java.util.Random;
import org.springframework.cloud.netflix.ribbon.RibbonClient;
import org.springframework.cloud.netflix.ribbon.RibbonClientName;
import org.springframework.cloud.netflix.ribbon.RibbonClients;
import org.springframework.context.annotation.Primary;
import org.springframework.context.annotation.Profile;
import org.springframework.util.CollectionUtils;
/**
* 开发环境下优先路由到本机IP服务实例
* “dev”即本地开发环境后缀
* RibbonClients中的都是具体的微服务,有多少就添加多少
*/
@Profile("dev")
@Configuration
@RibbonClients(value = {
@RibbonClient(name = LoadBalanceConfig.AREA_PROVIDER, configuration = MyRibbonConfig.class),
@RibbonClient(name = LoadBalanceConfig.RING_PROVIDER, configuration = MyRibbonConfig.class),
@RibbonClient(name = LoadBalanceConfig.ONEKEY_PROVIDER, configuration = MyRibbonConfig.class),
@RibbonClient(name = LoadBalanceConfig.BIZ_PROVIDER, configuration = MyRibbonConfig.class),
@RibbonClient(name = LoadBalanceConfig.BM_PROVIDER, configuration = MyRibbonConfig.class)
})
public class LoadBalanceConfig {
/** 具体的服务 */
public static final String AREA_PROVIDER = "vp-cloud-area-provider";
public static final String RING_PROVIDER = "vp-cloud-ring-provider";
public static final String ONEKEY_PROVIDER = "vp-cloud-onekey-provider";
public static final String BIZ_PROVIDER = "vp-cloud-biz-provider";
public static final String BM_PROVIDER = "vp-cloud-bm-provider";
private static final Map<String, Server> testServer = new HashMap<>();
/** 指明测试环境服务的IP端口 */
static {
testServer.put(AREA_PROVIDER, new Server("172.**.**.77:8080"));
testServer.put(RING_PROVIDER, new Server("172.**.**.77:8082"));
testServer.put(ONEKEY_PROVIDER, new Server("172.**.**.77:8084"));
testServer.put(BIZ_PROVIDER, new Server("172.**.**.77:8085"));
testServer.put(BM_PROVIDER, new Server("172.**.**.77:8086"));
}
public static class MyRibbonConfig {
private final Logger logger = LoggerFactory.getLogger(LoadBalanceConfig.class);
@RibbonClientName
private String name;
@Bean
@Primary
public IClientConfig devConfig() {
logger.info("开发环境: dev, 客户端: {}, IP: {}, 使用自定义的Ribbon配置 优先进入本地服务", this.name,
IpUtil.getFirstLocalIp());
DefaultClientConfigImpl config = new DefaultClientConfigImpl();
config.loadProperties(this.name);
config.set(CommonClientConfigKey.ConnectTimeout, 1000);
config.set(CommonClientConfigKey.ReadTimeout, 4000);
config.set(CommonClientConfigKey.GZipPayload, true);
config.set(CommonClientConfigKey.NFLoadBalancerPingInterval, 5);
config.set(CommonClientConfigKey.PoolMinThreads, 4);
config.set(CommonClientConfigKey.MaxAutoRetries, 0);
return config;
}
/**
* 负载策略bean, 开发环境下为自定义的负载策略, 否则为默认的轮询策略
*/
@Bean
@Primary
public IRule devRule() {
return new MyRule(name);
}
}
/**
* 自定义Rule, 在原来的基础上如果有本地同IP的服务则优先进入本地的服务, 需要查询本地IP, 确保注册的IP为VPN的IP, Nacos默认选取第一个网卡的IP,
* 如果首个网卡不是VPN可在配置文件指定${spring.cloud.nacos.discovery.network-interface}的对应网卡(不建议, 提交代码需要修改),
* 建议把前面的网卡在设备管理器中卸载或重排序, 具体操作自行Google或百度
*/
public static class MyRule extends ZoneAvoidanceRule {
private final Random random = new Random();
private final String name;
private final Logger log = LoggerFactory.getLogger(MyRule.class);
private static final String localIp = IpUtil.getFirstLocalIp();
public MyRule(String name) {
super();
this.name = name;
}
public MyRule() {
super();
this.name = "";
}
public MyRule(ILoadBalancer lb) {
this();
this.setLoadBalancer(lb);
}
@Override
public Server choose(Object key) {
ILoadBalancer lb = getLoadBalancer();
List<Server> allEligbleServers = this.getPredicate()
.getEligibleServers(lb.getAllServers(), key);
List<Server> localServers = allEligbleServers.stream().collect(
Collectors.groupingBy(Server::getHost)).get(localIp);
if (!CollectionUtils.isEmpty(localServers)) {
return localServers.get(random.nextInt(localServers.size()));
}
// 开发环境有不同ip尝试连接
// if (!CollectionUtils.isEmpty(allEligbleServers)) {
// Server server = allEligbleServers.get(random.nextInt(allEligbleServers.size()));
// InetSocketAddress inetSocketAddress = new InetSocketAddress(server.getHost(), server.getPort());
// Socket socket = new Socket();
// try {
// socket.connect(inetSocketAddress, 50);
// socket.close();
// return server;
// } catch (Exception e) {
// log.warn("服务不可及: {}-{}", name, server.getHostPort());
// }
// }
Server server = LoadBalanceConfig.testServer.get(name);
if (Objects.nonNull(server)) {
log.warn("本地未启动: {}, 尝试路由到测试环境: {}", name, server.getHostPort());
}
return server;
}
}
}
使用到的工具类:
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.util.Enumeration;
/**
* IP工具类
*/
public class IpUtil {
public static String ip;
/**
* 多IP处理,可以得到最终ip
*/
public static String getLocalIp() {
if (ip == null) {
String localip = null;// 本地IP,如果没有配置外网IP则返回它
String netip = null;// 外网IP
try {
Enumeration<NetworkInterface> netInterfaces = NetworkInterface.getNetworkInterfaces();
InetAddress ip = null;
boolean finded = false;// 是否找到外网IP
while (netInterfaces.hasMoreElements() && !finded) {
NetworkInterface ni = netInterfaces.nextElement();
Enumeration<InetAddress> address = ni.getInetAddresses();
while (address.hasMoreElements()) {
ip = address.nextElement();
if (!ip.isSiteLocalAddress() && !ip.isLoopbackAddress()
&& ip.getHostAddress().indexOf(":") == -1) {// 外网IP
netip = ip.getHostAddress();
finded = true;
break;
} else if (ip.isSiteLocalAddress() && !ip.isLoopbackAddress()
&& ip.getHostAddress().indexOf(":") == -1) {// 内网IP
localip = ip.getHostAddress();
}
}
}
} catch (SocketException e) {
e.printStackTrace();
}
if (netip != null && !"".equals(netip)) {
ip = netip;
} else {
ip = localip;
}
}
return ip;
}
/**
* 获取第一个网卡ip, Nacos注册默认使用第一个
*/
public static String getFirstLocalIp() {
String localip = null;// 本地IP,如果没有配置外网IP则返回它
String netip = null;// 外网IP
try {
Enumeration<NetworkInterface> netInterfaces = NetworkInterface.getNetworkInterfaces();
InetAddress inetAddress = null;
boolean finded = false;// 是否找到外网IP
while (netInterfaces.hasMoreElements() && !finded) {
NetworkInterface ni = netInterfaces.nextElement();
Enumeration<InetAddress> address = ni.getInetAddresses();
while (address.hasMoreElements()) {
inetAddress = address.nextElement();
if (!inetAddress.isSiteLocalAddress() && !inetAddress.isLoopbackAddress()
&& !inetAddress.getHostAddress().contains(":")) {// 外网IP
netip = inetAddress.getHostAddress();
finded = true;
break;
} else if (inetAddress.isSiteLocalAddress() && !inetAddress.isLoopbackAddress()
&& !inetAddress.getHostAddress().contains(":")) {// 内网IP
return inetAddress.getHostAddress();
}
}
}
} catch (SocketException e) {
e.printStackTrace();
}
if (netip != null && !"".equals(netip)) {
ip = netip;
} else {
ip = localip;
}
return ip;
}
public static String getLocalIp(boolean refresh) {
ip = null;
return getLocalIp();
}
public static void main(String[] args) {
System.out.println("本机IP:" + IpUtil.getLocalIp());
}
}