SpringCloud負(fù)載均衡實(shí)現(xiàn)定向路由詳情
背景
隨著微服務(wù)項(xiàng)目的迭代,可能一個(gè)服務(wù)會(huì)有多個(gè)實(shí)例,這時(shí)候就會(huì)涉及到負(fù)載均衡。然而我們?cè)陂_發(fā)的時(shí)候,肯定希望只啟動(dòng)一個(gè)項(xiàng)目。然后調(diào)試的時(shí)候希望負(fù)載均衡把請(qǐng)求分配到我們正在開發(fā)測(cè)試的服務(wù)實(shí)例上面。
如圖所示,我們希望可以指定調(diào)用路徑也就是定向路由。
實(shí)現(xiàn)方式
基于ip
這個(gè)很好理解,就是開發(fā)者本地正在運(yùn)行的服務(wù)在nacos上面肯定顯示你本機(jī)的ip;那么只要我得到開發(fā)者的ip就能夠根據(jù)這個(gè)ip來過濾nacos上面的服務(wù),達(dá)到定向路由的效果。
基于nacos的元數(shù)據(jù)
spring:
cloud:
nacos:
discovery:
metadata:
version: "mfine"在yaml中配置nacos元數(shù)據(jù)的version屬性。前端在請(qǐng)求header中添加與其對(duì)應(yīng)version屬性,就可以實(shí)現(xiàn)服務(wù)過濾也就是定向路由。
實(shí)現(xiàn)原理
Gateway服務(wù)
因?yàn)間ateway底層的不同,所以其負(fù)載均衡也與普通服務(wù)的不同,因此要特殊處理。先看gateway中l(wèi)oad balancer組件調(diào)用流程。
首先在gateway中l(wèi)oad balancer本身也是一個(gè)過濾器,所以流程如下。
- ReactiveLoadBalancerClientFilter里面有個(gè)LoadBalancerClientFactory屬性,通過這個(gè)工廠獲取具體的負(fù)載均衡器
- LoadBalancerClientFactory會(huì)載入LoadBalancerClientConfiguration配置
- LoadBalancerClientConfiguration會(huì)初始化我們需要的RoundRobinLoadBalancer,并且會(huì)通過構(gòu)造函數(shù)傳入LoadBalancerClientFactory對(duì)象。
那我們要做什么呢?其實(shí)就是截胡。
- 實(shí)現(xiàn)自己的LoadBalancerClientFactory,傳入自己LoadBalancerClientConfiguration。
- 在自己的LoadBalancerClientConfiguration初始化自己的RoundRobinLoadBalancer
- 最后在自己的ReactiveLoadBalancerClientFilter里面?zhèn)魅胱约旱?strong>LoadBalancerClientFactory,獲得自己的負(fù)載均衡器。
具體源碼(只放核心)
MyRoundRobinLoadBalancer
private Response<ServiceInstance> getInstanceResponse(
List<ServiceInstance> instances, ServerWebExchange exchange) {
if (instances.isEmpty()) {
log.warn("No servers available for service: " + this.serviceId);
return new EmptyResponse();
}
try {
//可重入鎖
if (this.lock.tryLock(10, TimeUnit.SECONDS))
instances = this.filterServiceInstance(exchange, instances);
// TODO: enforce order?
int pos = Math.abs(this.position.incrementAndGet());
ServiceInstance instance = instances.get(pos % instances.size());
return new DefaultResponse(instance);
} catch (InterruptedException e) {
throw new RuntimeException("自定義負(fù)載均衡器,超時(shí)等待異常");
} finally {
lock.unlock();
}
}
// 根據(jù)附加信息過濾服務(wù)
private List<ServiceInstance> filterServiceInstance(ServerWebExchange exchange, List<ServiceInstance> serviceInstances) {
List<ServiceInstance> filteredServices = new ArrayList<>();
// 自動(dòng)模式
if (DevConfigEnum.AUTO.getCode().equals(this.properties.getModel())) {
filteredServices = autoModel(exchange, serviceInstances);
}
// ip 模式
if (this.properties.getModel().equals(DevConfigEnum.IP.getCode())) {
filteredServices = ipModel(exchange, serviceInstances);
}
// metadata 模式
if (this.properties.getModel().equals(DevConfigEnum.METADATA.getCode())) {
filteredServices = metadataModel(exchange, serviceInstances);
}
if (filteredServices.isEmpty()) {
log.info("未發(fā)現(xiàn)符合ip或metadata.version服務(wù),將采用原始服務(wù)集合");
return serviceInstances;
}
return filteredServices;
}
// 自動(dòng)模式
private List<ServiceInstance> autoModel(ServerWebExchange exchange, List<ServiceInstance> serviceInstances) {
List<ServiceInstance> filteredServices;
filteredServices = ipModel(exchange, serviceInstances);
if (filteredServices.isEmpty()) {
filteredServices = metadataModel(exchange, serviceInstances);
}
return filteredServices;
}
//元數(shù)據(jù)模式
private List<ServiceInstance> metadataModel(ServerWebExchange exchange, List<ServiceInstance> serviceInstances) {
String version = exchange.getRequest().getHeaders().getFirst("version");
List<ServiceInstance> filteredServices = new ArrayList<>();
if (version != null) {
log.info("version模式:獲取metadata.version成功");
filteredServices = serviceInstances.stream().filter(instance -> {
String metaVersion = instance.getMetadata().get("version");
if (metaVersion == null) {
return false;
}
return metaVersion.equals(version);
}).collect(Collectors.toList());
}
return filteredServices;
}
// ip模式
private List<ServiceInstance> ipModel(ServerWebExchange exchange, List<ServiceInstance> serviceInstances) {
List<ServiceInstance> filteredServices = new ArrayList<>();
try {
String ipAddress = exchange.getRequest().getHeaders().getFirst("ip");
if (ipAddress == null) {
ipAddress = IPUtils.getIpAddress(exchange.getRequest());
}
log.warn("ip模式:獲取ip成功");
String finalIpAddress = ipAddress;
filteredServices = serviceInstances.stream().filter(item -> item.getHost().equals(finalIpAddress))
.collect(Collectors.toList());
} catch (UnknownHostException e) {
log.warn("ip模式:獲取ip失敗,無法進(jìn)行定向路由");
}
return filteredServices;
}MyLoadBalancerClientFactory
public class MyLoadBalancerClientFactory extends NamedContextFactory<LoadBalancerClientSpecification>
implements ReactiveLoadBalancer.Factory<ServiceInstance>{
................
public MyLoadBalancerClientFactory() {
// 傳入自己的自動(dòng)配置
super(MyLoadBalancerClientConfiguration.class, NAMESPACE, PROPERTY_NAME);
}
...........
}MyLoadBalancerClientConfiguration
@Configuration
public class MyLoadBalancerClientConfiguration {
@Autowired
private MicroServiceDevConfigProperties microServiceDevConfigProperties;
@Bean
public ReactorServiceInstanceLoadBalancer reactiveLoadBalancer(Environment environment, LoadBalancerClientFactory loadBalancerClientFactory) {
String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
// 初始化自己的負(fù)載均衡器
return new MyRoundRobinLoadBalancer(loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class), name, 1000,microServiceDevConfigProperties);
}
}@Configuration
public class MyReactiveLoadBalancerClientFilter extends ReactiveLoadBalancerClientFilter {
private static final Log log = LogFactory
.getLog(ReactiveLoadBalancerClientFilter.class);
private static final int LOAD_BALANCER_CLIENT_FILTER_ORDER = 10150;
private final MyLoadBalancerClientFactory clientFactory;
private LoadBalancerProperties properties;
// 注入自己的LoadBalancerClientFactory
public MyReactiveLoadBalancerClientFilter(MyLoadBalancerClientFactory clientFactory, LoadBalancerProperties properties) {
super(null, null);
this.clientFactory = clientFactory;
this.properties = properties;
}
........
private Mono<Response<ServiceInstance>> choose(ServerWebExchange exchange) {
URI uri = exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR);
//獲得自己load balancer
MyRoundRobinLoadBalancer loadBalancer = this.clientFactory
.getInstance(uri.getHost(), MyRoundRobinLoadBalancer.class);
if (loadBalancer == null) {
throw new NotFoundException("No loadbalancer available for " + uri.getHost());
}
// 在自己的load balancer里面擴(kuò)展choose方法,使其接受ServerWebExchange參數(shù)
// 傳入ServerWebExchange我們就可以,獲取請(qǐng)求信息,方便我們過濾
return loadBalancer.choose(exchange);
}
}我的這種實(shí)現(xiàn)較為繁瑣,可能大家有更好方式,大家要是有更好更簡(jiǎn)單的方法,也可以直接替換掉。
普通服務(wù)
普通服務(wù)實(shí)現(xiàn)自定義負(fù)載均衡器就很簡(jiǎn)單了,實(shí)現(xiàn)自定義RoundRobinRule就可以了
@Configuration
public class MyRoundRobinLoadBalancer extends RoundRobinRule {
//不同的使用注入的方式獲取請(qǐng)求信息
@Autowired
private HttpServletRequest request;
.....
private List<Server> filterServers(List<Server> reachableServers) {
List<Server> servers = new ArrayList<>();
if (this.properties.getModel().equals(DevConfigEnum.AUTO.getCode())) {
servers = ipModel(reachableServers);
if (servers.isEmpty()) {
servers = metadataModel(reachableServers);
}
}
if (this.properties.getModel().equals(DevConfigEnum.IP.getCode())) {
servers = ipModel(reachableServers);
}
if (this.properties.getModel().equals(DevConfigEnum.METADATA.getCode())) {
servers = metadataModel(reachableServers);
}
if (servers.isEmpty()) {
return reachableServers;
}
return servers;
}
private List<Server> metadataModel(List<Server> reachableServers) {
String version = request.getHeader("version");
List<Server> servers = new ArrayList<>();
if (version != null) {
log.info("metadata模式: 獲取version成功");
servers = reachableServers.stream().filter(item -> {
NacosServer nacosServer = (NacosServer) item;
String metaVersion = nacosServer.getMetadata().get("version");
if (metaVersion == null) {
return false;
}
return metaVersion.equals(version);
}).collect(Collectors.toList());
} else {
log.warn("metadata模式: header中無version字段且未獲取到請(qǐng)求者ip");
}
return servers;
}
private List<Server> ipModel(List<Server> reachableServers) {
List<Server> servers = new ArrayList<>();
try {
String ip = this.request.getHeader("ip");
if (ip == null) {
ip = IPUtils.getIpAddress(request);
}
String finalIp = ip;
servers = reachableServers.stream().filter(item -> item.getHost().equals(finalIp)).collect(Collectors.toList());
log.info("ip模式: 獲取請(qǐng)求者ip成功");
} catch (UnknownHostException e) {
log.warn("ip模式: 獲取ip失敗");
}
return servers;
}
........
}深入思考一下,通過注入的方式獲取request信息是否存在多線程安全問題呢?
使用方法
metadata模式
配置yaml:
spring:
application:
name: cloud-order
cloud:
nacos:
discovery:
metadata:
// 重點(diǎn)
version: mfine
celi-dev:
config:
model: "metadata"nacos中服務(wù)元數(shù)據(jù)
然后請(qǐng)求頭中附帶version信息
自定義負(fù)載均衡器會(huì)通過請(qǐng)求頭中的version去nacos中注冊(cè)服務(wù)的元數(shù)據(jù)里面去比對(duì)version信息。
ip模式
配置yaml
celi-dev:
config:
model: "ip"- 在header中指定IP
- 依靠請(qǐng)求信息獲取ip
配置yaml就好
此不指定ip的時(shí)候,后臺(tái)獲取的ip可能不對(duì)。取決你本地是否存在多張網(wǎng)卡(虛擬網(wǎng)卡也算),有時(shí)候nacos中ip顯示也會(huì)是虛擬網(wǎng)卡的ip。
使用前請(qǐng)確認(rèn)你的服務(wù)在nacos中的ip是多少,然后在header中指定ip,這樣最省事也最穩(wěn)妥。
一般是先從header中獲取ip信息,獲取不到再?gòu)膔equest對(duì)象中分析。
auto模式
配置yaml,其實(shí)可以不配置。
celi-dev:
config:
model: "auto"自動(dòng)模式默認(rèn)先使用ip模式獲取不到ip會(huì)自動(dòng)切換metadata模式。
什么都不配置,默認(rèn)auto模式
總結(jié)
三種模式里面meta模式最繁瑣,心智負(fù)擔(dān)最重,但是也是最簡(jiǎn)單的。ip模式難度在于獲取ip的準(zhǔn)確性因此加入指定ip的方式。自動(dòng)模式則二者結(jié)合。
到此這篇關(guān)于SpringCloud負(fù)載均衡實(shí)現(xiàn)定向路由詳情的文章就介紹到這了,更多相關(guān)SpringCloud負(fù)載均衡內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java?限制前端重復(fù)請(qǐng)求的實(shí)例代碼
這篇文章主要介紹了Java?限制前端重復(fù)請(qǐng)求,文中給大家提到了JAVA利用自定義本地鎖解決重復(fù)提交的問題,通過實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下2022-08-08
在Java的Struts框架中ONGL表達(dá)式的基礎(chǔ)使用入門
這篇文章主要介紹了深入解析在Java的Struts框架中ONGL表達(dá)式的基礎(chǔ)使用入門,Struts框架是Java的SSH三大web開發(fā)框架之一,需要的朋友可以參考下2015-11-11
Java中ArrayList和LinkedList的區(qū)別
ArrayList和LinkedList在這個(gè)方法上存在一定的性能差異,本文就介紹了Java中ArrayList和LinkedList的區(qū)別,對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-06-06
Springboot手動(dòng)連接庫(kù)并獲取指定表結(jié)構(gòu)的示例代碼
這篇文章主要介紹了Springboot手動(dòng)連接庫(kù)并獲取指定表結(jié)構(gòu)的示例代碼,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-07-07
必知必會(huì)的SpringBoot實(shí)現(xiàn)熱部署兩種方式
這篇文章主要為大家介紹了必知必會(huì)的SpringBoot實(shí)現(xiàn)熱部署兩種方式詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-04-04

