Eureka注冊(cè)不上或注冊(cè)后IP不對(duì)(多網(wǎng)卡的坑及解決)
一、問題發(fā)現(xiàn)
使用SpringCloud一套的微服務(wù)項(xiàng)目在開發(fā)測(cè)試環(huán)境都再正常不過了,到生產(chǎn)部署的時(shí)候啟動(dòng)服務(wù)就死活無法啟動(dòng),去看啟動(dòng)日志發(fā)現(xiàn),在獲取配置中心配置時(shí)連接不到配置中心了,報(bào)了一個(gè)Host Unreachable
的錯(cuò)。
按道理來說這個(gè)錯(cuò)很簡(jiǎn)單,就是網(wǎng)絡(luò)不通導(dǎo)致的。
但是問題就出現(xiàn)在這兒,我直接ping注冊(cè)中心和配置中心的IP是通的,沒有問題。
再仔細(xì)一看才發(fā)現(xiàn)事情并不簡(jiǎn)單,我們生產(chǎn)環(huán)境開放的是一套10.21.xx.xx的網(wǎng)段IP,但是日志中卻去尋找29.192.xx.xx去了,打開eureka控制臺(tái)發(fā)現(xiàn)注冊(cè)到注冊(cè)中心上的配置中心確實(shí)是29.192.xx.xx,并且注冊(cè)中心顯示的自己的IP也是29.192.xx.xx。
配置中心啟動(dòng)沒問題是因?yàn)樗妥?cè)中心在一臺(tái)機(jī)器上,所以用什么樣的ip都無關(guān)緊要,其他服務(wù)想要拉取配置中心所在機(jī)器的配置就拉不到了。
問題是我們其他服務(wù)在配置eureka client的時(shí)候填寫的eureka server地址確實(shí)都是10.21.xx.xx,為什么注冊(cè)中心會(huì)自動(dòng)改成29.192.xx.xx。
咨詢了客戶方之后才知道,29.192.xx.xx這個(gè)IP是用來監(jiān)控服務(wù)器用的,各服務(wù)器之間都通過這個(gè)IP發(fā)送心跳來保持在線狀態(tài),不能做業(yè)務(wù)使用。
二、撥云見日
知道這個(gè)IP從哪兒來下一步就要分析解決了,先查看網(wǎng)絡(luò)配置
確實(shí)從網(wǎng)卡配置順序上來說eureka client選取了第一塊網(wǎng)卡配置的IP向注冊(cè)中心注冊(cè),這就導(dǎo)致了無法連接的問題。
那么問題就指向了eureka client是如何選取網(wǎng)卡IP進(jìn)行注冊(cè)的以及如何能讓eureka client根據(jù)我們的意愿選擇我們想要的IP進(jìn)行注冊(cè)。
去官網(wǎng)查找并沒有找到關(guān)于eureka client是如何選取網(wǎng)卡IP的描述,那么就只能去扒源碼了。
最終找到是在com.netflix.appinfo包下的InstanceInfo類封裝了本機(jī)信息,其中就包括了IP地址的獲取方法。
在 Spring Cloud 環(huán)境下,Eureka Client并沒有自己實(shí)現(xiàn)探測(cè)本機(jī)IP的邏輯,而是交給Spring的InetUtils工具類的findFirstNonLoopbackAddress()方法完成的,下邊貼出這個(gè)方法的源碼:
public InetAddress findFirstNonLoopbackAddress() { InetAddress result = null; try { int lowest = Integer.MAX_VALUE; for (Enumeration<NetworkInterface> nics = NetworkInterface .getNetworkInterfaces(); nics.hasMoreElements();) { NetworkInterface ifc = nics.nextElement(); if (ifc.isUp()) { log.trace("Testing interface: " + ifc.getDisplayName()); if (ifc.getIndex() < lowest || result == null) { lowest = ifc.getIndex(); } else if (result != null) { continue; } // @formatter:off if (!ignoreInterface(ifc.getDisplayName())) { for (Enumeration<InetAddress> addrs = ifc .getInetAddresses(); addrs.hasMoreElements();) { InetAddress address = addrs.nextElement(); if (address instanceof Inet4Address && !address.isLoopbackAddress() && isPreferredAddress(address)) { log.trace("Found non-loopback interface: " + ifc.getDisplayName()); result = address; } } } // @formatter:on } } } catch (IOException ex) { log.error("Cannot get first non-loopback address", ex); } if (result != null) { return result; } try { return InetAddress.getLocalHost(); } catch (UnknownHostException e) { log.warn("Unable to retrieve localhost"); } return null; }
這個(gè)方法中通過NetworkInterface接口獲取到網(wǎng)卡的列表信息進(jìn)行循環(huán)獲取,首先判斷是否啟用(如果網(wǎng)卡禁用再獲取IP自然就沒意義),在啟用狀態(tài)下拿到網(wǎng)卡的索引值(目的是為了獲取網(wǎng)卡的最小索引值),最后還要判斷是否在忽略列表中,如果不在忽略列表才能選用。在這一系列的操作過后如果沒能獲取到最終結(jié)果,那么最后就會(huì)調(diào)用jdk的getLocalHost()方法來獲取IP地址并返回。
總體來說,這個(gè)工具類會(huì)獲取所有網(wǎng)卡,依次進(jìn)行遍歷,取ip地址合理、索引值最小、已經(jīng)啟動(dòng)且不在忽略列表的網(wǎng)卡的ip地址作為結(jié)果。如果仍然沒有找到合適的IP, 那么就將InetAddress.getLocalHost()做為最后的fallback方案。
三、開刀治病
? 有了源碼的加持,想要達(dá)到我們最終獲取指定IP的目的就條條大路通羅馬了。
1、忽略指定網(wǎng)卡
? 在bootstrap.yml中添加忽略屬性
spring.cloud.inetutils.gnored-interfaces[0]=ens161 # 忽略ens161, 支持正則表達(dá)式
注意,不能在application.yml中添加,玩過SpringCloud的應(yīng)該都懂。
2、禁用無關(guān)網(wǎng)卡
? 如上面網(wǎng)卡信息圖即禁用掉ens161和ens256,最終只保留ens224網(wǎng)卡生效,這樣一來獲取到啟用的網(wǎng)卡也就只有一塊了。
查看網(wǎng)卡信息(生產(chǎn)環(huán)境最終沒敢禁用,以下拿我本地環(huán)境測(cè)試)
[melonrind@melonrind ~]$ ifconfig
enp0s3: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 10.0.2.15 netmask 255.255.255.0 broadcast 10.0.2.255
inet6 fe80::9ee2:1871:6417:19a9 prefixlen 64 scopeid 0x20<link>
ether 08:00:27:75:08:94 txqueuelen 1000 (Ethernet)
RX packets 44098 bytes 59104330 (56.3 MiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 21246 bytes 1370966 (1.3 MiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0enp0s8: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 192.168.56.200 netmask 255.255.255.0 broadcast 192.168.56.255
inet6 fe80::1c58:28df:b483:fb7a prefixlen 64 scopeid 0x20<link>
ether 08:00:27:f3:69:ec txqueuelen 1000 (Ethernet)
RX packets 333237 bytes 148991996 (142.0 MiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 396257 bytes 138439800 (132.0 MiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0lo: flags=73<UP,LOOPBACK,RUNNING> mtu 65536
inet 127.0.0.1 netmask 255.0.0.0
inet6 ::1 prefixlen 128 scopeid 0x10<host>
loop txqueuelen 1000 (Local Loopback)
RX packets 224153 bytes 56176690 (53.5 MiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 224153 bytes 56176690 (53.5 MiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
[melonrind@melonrind ~]$ nmcli con sh
NAME UUID TYPE DEVICE
enp0s3 e0a2f8ed-112e-41ed-9f35-89502e325c18 ethernet enp0s3
enp0s8 221018b1-6e99-48fc-8c3d-fe7dee581328 ethernet enp0s8
這里就可以禁用掉enp0s3網(wǎng)卡,保留enp0s8
[root@melonrind ~]# ifdown enp0s3 Device 'enp0s3' successfully disconnected.
啟用可以用如下命令
[root@melonrind ~]# ifup enp0s3
3、配置host
? 當(dāng)網(wǎng)查遍歷邏輯都沒有找到合適ip時(shí)會(huì)走JDK的InetAddress.getLocalHost()。該方法會(huì)返回當(dāng)前主機(jī)的hostname, 然后會(huì)根據(jù)hostname解析出對(duì)應(yīng)的ip。
因此如果確認(rèn)沒有找到合適的IP的情況下,可以配置本機(jī)的hostname和/etc/hosts文件,直接將本機(jī)的主機(jī)名映射到指定IP地址。
4、手工指定實(shí)例IP
eureka client在啟動(dòng)時(shí)可以對(duì)該eureka client的實(shí)例進(jìn)行配置,因此這里也可以自己指定IP地址。
可以添加如下配置:
# 指定此實(shí)例的ip eureka.instance.ip-address=${你指定的ip地址} # 注冊(cè)時(shí)使用ip而不是主機(jī)名 eureka.instance.prefer-ip-address=true
不過該配置需要添加在eureka client配置之上,形如:
eureka: instance: ip-address: 192.168.56.1 prefer-ip-address: true client: register-with-eureka: true fetch-registry: true service-url: defaultZone: http://192.168.56.1:9130/eureka registry-fetch-interval-seconds: 30 eureka-server-connect-timeout-seconds: 5 eureka-server-read-timeout-seconds: 5 filter-only-up-instances: true eureka-connection-idle-timeout-seconds: 30 eureka-server-total-connections: 200 eureka-server-total-connections-per-host: 50
5、服務(wù)啟動(dòng)時(shí)指定IP
在不方便修改配置文件時(shí)可以選用此方式(我就是用此方式解決),在服務(wù)啟動(dòng)時(shí)添加參數(shù):
java -jar -Dspring.cloud.inetutils.preferred-networks=192.168.56.1 ...
總結(jié)
至此,該問題得到解決,又是驚心動(dòng)魄的一天。
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
ThreadPoolExecutor參數(shù)含義及源碼執(zhí)行流程詳解
這篇文章主要為大家介紹了ThreadPoolExecutor參數(shù)含義及源碼執(zhí)行流程詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-11-11java中instanceof與Class的等價(jià)性代碼示例
這篇文章主要介紹了java中instanceof與Class的等價(jià)性代碼示例,小編覺得還是挺不錯(cuò)的,具有一定借鑒價(jià)值,需要的朋友可以參考下2018-01-01Java實(shí)現(xiàn)根據(jù)sql動(dòng)態(tài)查詢并下載數(shù)據(jù)到excel
這篇文章主要為大家詳細(xì)介紹了如何使用Java實(shí)現(xiàn)根據(jù)sql動(dòng)態(tài)查詢并下載數(shù)據(jù)到excel的功能,文中的示例代碼講解詳細(xì),有需要的可以參考下2024-04-04java讀取JSON文件的多種實(shí)現(xiàn)方式
這篇文章主要介紹了java讀取JSON文件的多種實(shí)現(xiàn)方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-06-06Java 高并發(fā)二:多線程基礎(chǔ)詳細(xì)介紹
本文主要介紹Java 高并發(fā)多線程的知識(shí),這里整理詳細(xì)的資料來解釋線程的知識(shí),有需要的學(xué)習(xí)高并發(fā)的朋友可以參考下2016-09-09Java異常處理操作 Throwable、Exception、Error
這篇文章主要介紹了Java異常處理操作 Throwable、Exception、Error,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-06-06在Java的Spring框架的程序中使用JDBC API操作數(shù)據(jù)庫(kù)
這篇文章主要介紹了在Java的Spring框架的程序中使用JDBC API操作數(shù)據(jù)庫(kù)的方法,并通過示例展示了其存儲(chǔ)過程以及基本SQL語(yǔ)句的應(yīng)用,需要的朋友可以參考下2015-12-12