先说一下想法,小公司开发项目,参考若依框架使用的
spring-cloud-starter-gateway
和spring-cloud-starter-alibaba-nacos
, 用到了nacos的配置中心和注册中心,有多个模块(每个模块都是一个服务)。
想本地开发,且和其他同事不互相影响的情况下,有同事建议 本地部署 完整的环境(nacos、需要的模块、前端等),除了 数据库和redis共用。这种方案也能用,就是占用自己电脑资源太多了(ps 自己电脑摸鱼的硬件资源就少了)。
我通过看nacos相关的教程,发现可以通过 配置中心 和 注册中心 的namespace
命名不同进行隔离,这样可以不用部署nacos服务。
继续分析发现,网关模块 默认的 负载均衡ReactorServiceInstanceLoadBalancer
的实现RoundRobinLoadBalancer
是通过轮询 访问服务的,可以 改造 负载均衡 来实现 本地开发环境最小化(只允许开发的服务模块),其他服务用 测试环境提供。
当前改造只适用请求通过网关,如果模块服务之间 直连调用,就需要在每个模块增加类似处理。暂不考虑
(ps 本人小白一枚springCloudGateway使用的少)本文章限定在这个项目里面,如果自己项目环境不一样,不保证能拿来直接用。
实现截图-不想看原理的先食用结果
参考
本来想通过搜索和AI找到解决方案,结果代码抄下来每一个能实现的。要么没有引用的类,要么无法实现自己的想法。所以就不贴了。
每个项目的依赖和版本情况都不一样,最好的方法是 找到框架的核心实现类,在跟踪调试和找到引用与初始化的地方。想办法自定义实现注入-这种方式最好,毕竟都是框架给留的口子。实在不行,就只能通过魔改或劫持的方式实现了。
原理图-设想
改造前请求流程
这种情况,适用于 非开发环境,简单高效
改造后请求流程
改造后的,适用于 本地开发,通过网关只请求自己的服务,或者自己没有只请求测试环境(简单的测试环境所有服务一台机器上)。尽最大可能不影响 其他同事开发。
图中没有画 锁定请求服务IP的情况(这块代码没测试)。
项目框架主要依赖
参考时候请注意 依赖包版本是否差距过大
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
<version>3.1.4</version>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>2021.0.4.0</version>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
<version>2021.0.4.0</version>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
<version>2021.0.4.0</version>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-sentinel-gateway</artifactId>
<version>2021.0.4.0</version>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
<version>1.8.5</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-loadbalancer</artifactId>
<version>3.1.5</version>
</dependency>
自定义实现
自定义负载均衡类
根据 org.springframework.cloud.loadbalancer.core.RoundRobinLoadBalancer
轮询策略 改造,这块代码倒还好实现。TestLocalLockIpLoadBalancer
直接注入spring中,ObjectProvider<ServiceInstanceListSupplier>
获取不到服务列表,看了spring实现,要完全替换需要注入好几个类,有点麻烦。最后改成运行时替换。
自定义路由负载均衡,负载均衡策略:
- 先根据请求IP查询IP相同的服务
- 再找和网关IP相同的服务
- 最后轮询其他IP服务
package *.*.*.config;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.*;
import org.springframework.cloud.loadbalancer.core.NoopServiceInstanceListSupplier;
import org.springframework.cloud.loadbalancer.core.ReactorServiceInstanceLoadBalancer;
import org.springframework.cloud.loadbalancer.core.SelectedInstanceCallback;
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
import org.springframework.http.HttpHeaders;
import reactor.core.publisher.Mono;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 自定义路由负载均衡->初始化,负载均衡策略: 先找请求IP查询IP相同的服务、再找和网关IP相同的服务、最后轮询其他IP服务
*
* @see org.springframework.cloud.loadbalancer.core.RoundRobinLoadBalancer
* @author z
* @date 2025/1/10
*/
public class TestLocalLockIpLoadBalancer implements ReactorServiceInstanceLoadBalancer {
private static final Logger log = LoggerFactory.getLogger(TestLocalLockIpLoadBalancer.class);
final AtomicInteger position;
final String serviceId;
ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider;
private String localIp;
public TestLocalLockIpLoadBalancer setLocalIp(String localIp) {
this.localIp = localIp; return this; }
public TestLocalLockIpLoadBalancer(ObjectProvider<ServiceInstanceListSupplier> provide, String serviceId) {
this(provide, serviceId, new Random().nextInt(1000));
}
public TestLocalLockIpLoadBalancer(ObjectProvider<ServiceInstanceListSupplier> provide, String serviceId