SpringCloud負載均衡實現(xiàn)定向路由詳情
背景
隨著微服務(wù)項目的迭代,可能一個服務(wù)會有多個實例,這時候就會涉及到負載均衡。然而我們在開發(fā)的時候,肯定希望只啟動一個項目。然后調(diào)試的時候希望負載均衡把請求分配到我們正在開發(fā)測試的服務(wù)實例上面。
如圖所示,我們希望可以指定調(diào)用路徑也就是定向路由。
實現(xiàn)方式
基于ip
這個很好理解,就是開發(fā)者本地正在運行的服務(wù)在nacos上面肯定顯示你本機的ip;那么只要我得到開發(fā)者的ip就能夠根據(jù)這個ip來過濾nacos上面的服務(wù),達到定向路由的效果。
基于nacos的元數(shù)據(jù)
spring:
cloud:
nacos:
discovery:
metadata:
version: "mfine"在yaml中配置nacos元數(shù)據(jù)的version屬性。前端在請求header中添加與其對應(yīng)version屬性,就可以實現(xiàn)服務(wù)過濾也就是定向路由。
實現(xiàn)原理
Gateway服務(wù)
因為gateway底層的不同,所以其負載均衡也與普通服務(wù)的不同,因此要特殊處理。先看gateway中l(wèi)oad balancer組件調(diào)用流程。
首先在gateway中l(wèi)oad balancer本身也是一個過濾器,所以流程如下。
- ReactiveLoadBalancerClientFilter里面有個LoadBalancerClientFactory屬性,通過這個工廠獲取具體的負載均衡器
- LoadBalancerClientFactory會載入LoadBalancerClientConfiguration配置
- LoadBalancerClientConfiguration會初始化我們需要的RoundRobinLoadBalancer,并且會通過構(gòu)造函數(shù)傳入LoadBalancerClientFactory對象。
那我們要做什么呢?其實就是截胡。
- 實現(xiàn)自己的LoadBalancerClientFactory,傳入自己LoadBalancerClientConfiguration。
- 在自己的LoadBalancerClientConfiguration初始化自己的RoundRobinLoadBalancer
- 最后在自己的ReactiveLoadBalancerClientFilter里面?zhèn)魅胱约旱?strong>LoadBalancerClientFactory,獲得自己的負載均衡器。
具體源碼(只放核心)
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("自定義負載均衡器,超時等待異常");
} finally {
lock.unlock();
}
}
// 根據(jù)附加信息過濾服務(wù)
private List<ServiceInstance> filterServiceInstance(ServerWebExchange exchange, List<ServiceInstance> serviceInstances) {
List<ServiceInstance> filteredServices = new ArrayList<>();
// 自動模式
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;
}
// 自動模式
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失敗,無法進行定向路由");
}
return filteredServices;
}MyLoadBalancerClientFactory
public class MyLoadBalancerClientFactory extends NamedContextFactory<LoadBalancerClientSpecification>
implements ReactiveLoadBalancer.Factory<ServiceInstance>{
................
public MyLoadBalancerClientFactory() {
// 傳入自己的自動配置
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);
// 初始化自己的負載均衡器
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里面擴展choose方法,使其接受ServerWebExchange參數(shù)
// 傳入ServerWebExchange我們就可以,獲取請求信息,方便我們過濾
return loadBalancer.choose(exchange);
}
}我的這種實現(xiàn)較為繁瑣,可能大家有更好方式,大家要是有更好更簡單的方法,也可以直接替換掉。
普通服務(wù)
普通服務(wù)實現(xiàn)自定義負載均衡器就很簡單了,實現(xiàn)自定義RoundRobinRule就可以了
@Configuration
public class MyRoundRobinLoadBalancer extends RoundRobinRule {
//不同的使用注入的方式獲取請求信息
@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字段且未獲取到請求者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模式: 獲取請求者ip成功");
} catch (UnknownHostException e) {
log.warn("ip模式: 獲取ip失敗");
}
return servers;
}
........
}深入思考一下,通過注入的方式獲取request信息是否存在多線程安全問題呢?
使用方法
metadata模式
配置yaml:
spring:
application:
name: cloud-order
cloud:
nacos:
discovery:
metadata:
// 重點
version: mfine
celi-dev:
config:
model: "metadata"nacos中服務(wù)元數(shù)據(jù)
然后請求頭中附帶version信息
自定義負載均衡器會通過請求頭中的version去nacos中注冊服務(wù)的元數(shù)據(jù)里面去比對version信息。
ip模式
配置yaml
celi-dev:
config:
model: "ip"- 在header中指定IP
- 依靠請求信息獲取ip
配置yaml就好
此不指定ip的時候,后臺獲取的ip可能不對。取決你本地是否存在多張網(wǎng)卡(虛擬網(wǎng)卡也算),有時候nacos中ip顯示也會是虛擬網(wǎng)卡的ip。
使用前請確認你的服務(wù)在nacos中的ip是多少,然后在header中指定ip,這樣最省事也最穩(wěn)妥。
一般是先從header中獲取ip信息,獲取不到再從request對象中分析。
auto模式
配置yaml,其實可以不配置。
celi-dev:
config:
model: "auto"自動模式默認先使用ip模式獲取不到ip會自動切換metadata模式。
什么都不配置,默認auto模式
總結(jié)
三種模式里面meta模式最繁瑣,心智負擔最重,但是也是最簡單的。ip模式難度在于獲取ip的準確性因此加入指定ip的方式。自動模式則二者結(jié)合。
到此這篇關(guān)于SpringCloud負載均衡實現(xiàn)定向路由詳情的文章就介紹到這了,更多相關(guān)SpringCloud負載均衡內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
在Java的Struts框架中ONGL表達式的基礎(chǔ)使用入門
這篇文章主要介紹了深入解析在Java的Struts框架中ONGL表達式的基礎(chǔ)使用入門,Struts框架是Java的SSH三大web開發(fā)框架之一,需要的朋友可以參考下2015-11-11
Java中ArrayList和LinkedList的區(qū)別
ArrayList和LinkedList在這個方法上存在一定的性能差異,本文就介紹了Java中ArrayList和LinkedList的區(qū)別,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2023-06-06
Springboot手動連接庫并獲取指定表結(jié)構(gòu)的示例代碼
這篇文章主要介紹了Springboot手動連接庫并獲取指定表結(jié)構(gòu)的示例代碼,本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-07-07
必知必會的SpringBoot實現(xiàn)熱部署兩種方式
這篇文章主要為大家介紹了必知必會的SpringBoot實現(xiàn)熱部署兩種方式詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-04-04

