<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
隨著微服務專案的迭代,可能一個服務會有多個範例,這時候就會涉及到負載均衡。然而我們在開發的時候,肯定希望只啟動一個專案。然後偵錯的時候希望負載均衡把請求分配到我們正在開發測試的服務範例上面。
如圖所示,我們希望可以指定呼叫路徑也就是定向路由。
這個很好理解,就是開發者本地正在執行的服務在nacos上面肯定顯示你本機的ip;那麼只要我得到開發者的ip就能夠根據這個ip來過濾nacos上面的服務,達到定向路由的效果。
spring: cloud: nacos: discovery: metadata: version: "mfine"
在yaml中設定nacos後設資料的version屬性。前端在請求header中新增與其對應version屬性,就可以實現服務過濾也就是定向路由。
因為gateway底層的不同,所以其負載均衡也與普通服務的不同,因此要特殊處理。先看gateway中load balancer元件呼叫流程。
首先在gateway中load balancer本身也是一個過濾器,所以流程如下。
那我們要做什麼呢?其實就是截胡。
具體原始碼(只放核心)
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(); } } // 根據附加資訊過濾服務 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("未發現符合ip或metadata.version服務,將採用原始服務集合"); 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; } //後設資料模式 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引數 // 傳入ServerWebExchange我們就可以,獲取請求資訊,方便我們過濾 return loadBalancer.choose(exchange); } }
我的這種實現較為繁瑣,可能大家有更好方式,大家要是有更好更簡單的方法,也可以直接替換掉。
普通服務實現自定義負載均衡器就很簡單了,實現自定義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資訊是否存在多執行緒安全問題呢?
設定yaml:
spring: application: name: cloud-order cloud: nacos: discovery: metadata: // 重點 version: mfine celi-dev: config: model: "metadata"
nacos中服務後設資料
然後請求頭中附帶version資訊
自定義負載均衡器會通過請求頭中的version去nacos中註冊服務的後設資料裡面去比對version資訊。
設定yaml
celi-dev: config: model: "ip"
設定yaml就好
此不指定ip的時候,後臺獲取的ip可能不對。取決你本地是否存在多張網路卡(虛擬網路卡也算),有時候nacos中ip顯示也會是虛擬網路卡的ip。
使用前請確認你的服務在nacos中的ip是多少,然後在header中指定ip,這樣最省事也最穩妥。
一般是先從header中獲取ip資訊,獲取不到再從request物件中分析。
設定yaml,其實可以不設定。
celi-dev: config: model: "auto"
自動模式預設先使用ip模式獲取不到ip會自動切換metadata模式。
什麼都不設定,預設auto模式
三種模式裡面meta模式最繁瑣,心智負擔最重,但是也是最簡單的。ip模式難度在於獲取ip的準確性因此加入指定ip的方式。自動模式則二者結合。
到此這篇關於SpringCloud負載均衡實現定向路由詳情的文章就介紹到這了,更多相關SpringCloud負載均衡內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45