学习并实践-自定义 zuul 网关的负载策略
2020 年开年困苦, 在此希望疫情早日消散, 广大人民群众身体安康
1.起因
因为疫情的原因, 部门同事都在家办公, 在协同开发的时候, 不可避免的会出现下面的问题:
A 和 B 为前端开发人员, 需要与 C, D 两位后端服务人员一对一进行接口调试(A 对 C, B 对 D)
C 和 D 都在 eureka 注册了自己的服务: demo-server, 此时, A 与 B 访问网关, 请求 demo-server 的接口, 则会出现, A-C A-D B-C B-D, 因为 zuul 网关默认的负载均衡策略为轮询机制
2.分析
为了解决此问题, 使前后端开发可定制匹配, 学习并配置了网关的负载策略
zuul 底层使用的是 ribbon 的负载组件, ribbon 可以从 eureka 获取服务列表, 所以想要自定义负载, 得从 ribbon 入手, 查看并搜索 loadBalance 相关的类, 可以看到 AbstractLoadBalancerRule 这个类, 查看类树, 可以看到顶级接口为 IRule
1
2
3
4
5
6
7
8
9
10
11
12
/**
* 决定最终的服务提供者
*/
public Server choose(Object key);
/**
* 设置负载均衡器
*/
public void setLoadBalancer(ILoadBalancer lb);
/**
* 拿到负载均衡器
*/
public ILoadBalancer getLoadBalancer();
debug 分析, 得知 ribbon 默认的负载策略为: ZoneAvoidanceRule, 这个类继承自 AbstractLoadBalancerRule, AbstractLoadBalancerRule中存在 ILoadBalancer 属性, 可以获取负载器, 负载器中又可以获取服务列表等信息
所以我们只需要继承ZoneAvoidanceRule, 重写 choose 方法即可
ribbon 默认的负载器为: DynamicServerListLoadBalancer, 其中包括节点健康状态服务支持, 节点状态的更新(ServerListUpdater), 负载策略, 可用区域的分析, 可以从此类详细了解 ribbon 的运行机制
3.解决
自定义路由策略, 继承ZoneAvoidanceRule, 当有特定标记的请求过来时, 走定义的服务, 如果没有, 则还是走默认的路由策略
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
import com.gomyck.util.ResponseWriter;
import com.gomyck.util.StringJudge;
import com.gomyck.util.TokenTake;
import com.netflix.loadbalancer.ILoadBalancer;
import com.netflix.loadbalancer.Server;
import com.netflix.loadbalancer.ZoneAvoidanceRule;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
/**
* @author gomyck
* @version 1.0.0
* @contact qq: 474798383
* @blog https://blog.gomyck.com
* @since 2020-03-16
*/
public class CkLoadBalanceRule extends ZoneAvoidanceRule {
Logger log = LoggerFactory.getLogger(CkLoadBalanceRule.class);
@Override
public Server choose(Object key) {
ILoadBalancer loadBalancer = getLoadBalancer();
List<Server> allServers = loadBalancer.getReachableServers();
String ckDev = TokenTake.getToken(ResponseWriter.getRequest(), "CkDev");
try {//todo 兼容了同一个前端项目 存在请求多个服务的情况, 只对比名字, 忽略项目名称, 在此拿到的服务列表为已匹配的路由表, 不需要根据服务名二次匹配
if (StringJudge.notNull(ckDev)) {
log.info("CkLoadBalanceRule will route server name is: " + ckDev);
final String _ckDev = ckDev.split("-")[0];
Server server = allServers.stream().filter(e -> {
String instanceId = e.getMetaInfo().getInstanceId().split("-")[0];
return _ckDev.equals(instanceId);
}).findFirst().get();
if (server.isAlive() && (server.isReadyToServe())) {
return (server);
} else {
log.error("server :" + server.getMetaInfo().getAppName() + " is not available!");
}
}
} catch (Exception e) {
}
return super.choose(key);
}
}
在 zuul 服务的 yml 文件中配置需要使用该负载策略的服务 ID, 格式为:
1
2
3
server-name:
ribbon:
NFLoadBalancerRuleClassName: com.gomyck.gateway.config.CkLoadBalanceRule
4.疑惑解答
在最开始, 我把自定义负载策略注册成 bean, 发现所有的路由都失效了, debug 发现, 策略 bean 拿到的服务, 缺失, 注释掉 bean, 查看之前的运行逻辑, 发现每个服务都对应一个路由策略实例, 也就是每个服务对应各自的 IRule 实例
得出结论:
路由策略为原型模式, 个人猜测是为了适配不同的服务, 避免所有策略都在一个 bean 中, 这样耦合度太高, 不容易做拆分, 而且遇到个性化的策略, 原型模式解决更得心应手一些