Eureka和Zookeeper很類似,它是SpringCloud框架中的服務注冊及發(fā)現(xiàn)組件找筝。所有的微服務在使用過程中會向Eureka進行注冊,而后客戶端利用Eureka獲取服務的信息(即服務的發(fā)現(xiàn))究流。雖然SpringCloud支持Zookeeper予跌,不過官方并不建議使用Zookeeper,而是推薦使用Eureka蒿叠。
為什么要使用Eureka
對于這個問題其實可以引申為:在RPC框架或服務治理框架中明垢,為什么要使用服務發(fā)現(xiàn)組件?
在沒有使用服務注冊和發(fā)現(xiàn)組件情況中市咽,客戶端如果想要調用服務存在以下缺點
- 需要記錄大量的真實服務地址痊银;
- 客戶端需要實現(xiàn)負載;
- 無法確認某一服務是否可用施绎;
而服務注冊和發(fā)現(xiàn)組件可以幫助客戶端解決這些問題溯革。
創(chuàng)建Eureka服務
和Zookeeper提供了單獨的安裝包不同,目前還沒發(fā)現(xiàn)Eureka官方提供單獨的安裝包來運行谷醉。我們可以將Eureka的依賴引入單獨的工程中致稀,然后部署運行該工程即可將Eureka的服務啟動起來。另外Eureka即能創(chuàng)建單機版又能創(chuàng)建集群版俱尼,下面分別介紹一下單機版Eureka和集群版Eureka如何搭建抖单。
搭建單機版Eureka
- 引入相關依賴
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<!-- SpringCloud是基于SpringBoot的,所以要引入SpringBoot的依賴 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
- 在配置文件中加入Eureka相關的配置
server:
port: 7001
eureka:
instance: # eureak實例定義
hostname: 127.0.0.1 # 定義Eureka實例所在的主機名稱
- 在啟動類中加入
@EnableEurekaServer
注解
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@SpringBootApplication
@EnableEurekaServer // 啟動Eureka服務
public class Eureka_7001_StartSpringCloudApplication {
public static void main(String[] args) {
SpringApplication.run(Eureka_7001_StartSpringCloudApplication.class,args);
}
}
-
啟動應用遇八,打開瀏覽器訪問http://127.0.0.1:7001/
Eureka控臺
此時可以看到Eureka已經(jīng)啟動矛绘,但是此時如果觀察后臺會發(fā)現(xiàn)有如下ERROR日志
2018-05-24 21:25:52.500 ERROR 10360 --- [tbeatExecutor-0] com.netflix.discovery.DiscoveryClient : DiscoveryClient_UNKNOWN/localhost:7001 - was unable to send heartbeat!
com.netflix.discovery.shared.transport.TransportException: Cannot execute request on any known server
at com.netflix.discovery.shared.transport.decorator.RetryableEurekaHttpClient.execute(RetryableEurekaHttpClient.java:111) ~[eureka-client-1.6.2.jar:1.6.2]
at com.netflix.discovery.shared.transport.decorator.EurekaHttpClientDecorator.sendHeartBeat(EurekaHttpClientDecorator.java:89) ~[eureka-client-1.6.2.jar:1.6.2]
at com.netflix.discovery.shared.transport.decorator.EurekaHttpClientDecorator$3.execute(EurekaHttpClientDecorator.java:92) ~[eureka-client-1.6.2.jar:1.6.2]
at com.netflix.discovery.shared.transport.decorator.SessionedEurekaHttpClient.execute(SessionedEurekaHttpClient.java:77) ~[eureka-client-1.6.2.jar:1.6.2]
at com.netflix.discovery.shared.transport.decorator.EurekaHttpClientDecorator.sendHeartBeat(EurekaHttpClientDecorator.java:89) ~[eureka-client-1.6.2.jar:1.6.2]
at com.netflix.discovery.DiscoveryClient.renew(DiscoveryClient.java:815) ~[eureka-client-1.6.2.jar:1.6.2]
at com.netflix.discovery.DiscoveryClient$HeartbeatThread.run(DiscoveryClient.java:1379) [eureka-client-1.6.2.jar:1.6.2]
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:471) [na:1.7.0_79]
at java.util.concurrent.FutureTask.run(FutureTask.java:262) [na:1.7.0_79]
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145) [na:1.7.0_79]
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615) [na:1.7.0_79]
at java.lang.Thread.run(Thread.java:745) [na:1.7.0_79]
2018-05-24 21:25:53.890 INFO 10360 --- [nfoReplicator-0] com.netflix.discovery.DiscoveryClient : DiscoveryClient_UNKNOWN/localhost:7001: registering service...
2018-05-24 21:25:55.903 ERROR 10360 --- [nfoReplicator-0] c.n.d.s.t.d.RedirectingEurekaHttpClient : Request execution error
com.sun.jersey.api.client.ClientHandlerException: java.net.ConnectException: Connection refused: connect
at com.sun.jersey.client.apache4.ApacheHttpClient4Handler.handle(ApacheHttpClient4Handler.java:187) ~[jersey-apache-client4-1.19.1.jar:1.19.1]
at com.sun.jersey.api.client.filter.GZIPContentEncodingFilter.handle(GZIPContentEncodingFilter.java:123) ~[jersey-client-1.19.1.jar:1.19.1]
at com.netflix.discovery.EurekaIdentityHeaderFilter.handle(EurekaIdentityHeaderFilter.java:27) ~[eureka-client-1.6.2.jar:1.6.2]
at com.sun.jersey.api.client.Client.handle(Client.java:652) ~[jersey-client-1.19.1.jar:1.19.1]
at com.sun.jersey.api.client.WebResource.handle(WebResource.java:682) ~[jersey-client-1.19.1.jar:1.19.1]
at com.sun.jersey.api.client.WebResource.access$200(WebResource.java:74) ~[jersey-client-1.19.1.jar:1.19.1]
at com.sun.jersey.api.client.WebResource$Builder.post(WebResource.java:570) ~[jersey-client-1.19.1.jar:1.19.1]
at com.netflix.discovery.shared.transport.jersey.AbstractJerseyEurekaHttpClient.register(AbstractJerseyEurekaHttpClient.java:56) ~[eureka-client-1.6.2.jar:1.6.2]
at com.netflix.discovery.shared.transport.decorator.EurekaHttpClientDecorator$1.execute(EurekaHttpClientDecorator.java:59) [eureka-client-1.6.2.jar:1.6.2]
at com.netflix.discovery.shared.transport.decorator.MetricsCollectingEurekaHttpClient.execute(MetricsCollectingEurekaHttpClient.java:73) ~[eureka-client-1.6.2.jar:1.6.2]
at com.netflix.discovery.shared.transport.decorator.EurekaHttpClientDecorator.register(EurekaHttpClientDecorator.java:56) [eureka-client-1.6.2.jar:1.6.2]
at com.netflix.discovery.shared.transport.decorator.EurekaHttpClientDecorator$1.execute(EurekaHttpClientDecorator.java:59) [eureka-client-1.6.2.jar:1.6.2]
at com.netflix.discovery.shared.transport.decorator.RedirectingEurekaHttpClient.executeOnNewServer(RedirectingEurekaHttpClient.java:118) ~[eureka-client-1.6.2.jar:1.6.2]
at com.netflix.discovery.shared.transport.decorator.RedirectingEurekaHttpClient.execute(RedirectingEurekaHttpClient.java:79) ~[eureka-client-1.6.2.jar:1.6.2]
at com.netflix.discovery.shared.transport.decorator.EurekaHttpClientDecorator.register(EurekaHttpClientDecorator.java:56) [eureka-client-1.6.2.jar:1.6.2]
at com.netflix.discovery.shared.transport.decorator.EurekaHttpClientDecorator$1.execute(EurekaHttpClientDecorator.java:59) [eureka-client-1.6.2.jar:1.6.2]
at com.netflix.discovery.shared.transport.decorator.RetryableEurekaHttpClient.execute(RetryableEurekaHttpClient.java:119) [eureka-client-1.6.2.jar:1.6.2]
at com.netflix.discovery.shared.transport.decorator.EurekaHttpClientDecorator.register(EurekaHttpClientDecorator.java:56) [eureka-client-1.6.2.jar:1.6.2]
at com.netflix.discovery.shared.transport.decorator.EurekaHttpClientDecorator$1.execute(EurekaHttpClientDecorator.java:59) [eureka-client-1.6.2.jar:1.6.2]
at com.netflix.discovery.shared.transport.decorator.SessionedEurekaHttpClient.execute(SessionedEurekaHttpClient.java:77) [eureka-client-1.6.2.jar:1.6.2]
at com.netflix.discovery.shared.transport.decorator.EurekaHttpClientDecorator.register(EurekaHttpClientDecorator.java:56) [eureka-client-1.6.2.jar:1.6.2]
at com.netflix.discovery.DiscoveryClient.register(DiscoveryClient.java:798) [eureka-client-1.6.2.jar:1.6.2]
at com.netflix.discovery.InstanceInfoReplicator.run(InstanceInfoReplicator.java:104) [eureka-client-1.6.2.jar:1.6.2]
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:471) [na:1.7.0_79]
at java.util.concurrent.FutureTask.run(FutureTask.java:262) [na:1.7.0_79]
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.java:178) [na:1.7.0_79]
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:292) [na:1.7.0_79]
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145) [na:1.7.0_79]
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615) [na:1.7.0_79]
at java.lang.Thread.run(Thread.java:745) [na:1.7.0_79]
Caused by: java.net.ConnectException: Connection refused: connect
at java.net.DualStackPlainSocketImpl.waitForConnect(Native Method) ~[na:1.7.0_79]
at java.net.DualStackPlainSocketImpl.socketConnect(DualStackPlainSocketImpl.java:85) ~[na:1.7.0_79]
at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:339) ~[na:1.7.0_79]
at java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:200) ~[na:1.7.0_79]
at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:182) ~[na:1.7.0_79]
at java.net.PlainSocketImpl.connect(PlainSocketImpl.java:172) ~[na:1.7.0_79]
at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:392) ~[na:1.7.0_79]
at java.net.Socket.connect(Socket.java:579) ~[na:1.7.0_79]
at org.apache.http.conn.scheme.PlainSocketFactory.connectSocket(PlainSocketFactory.java:121) ~[httpclient-4.5.3.jar:4.5.3]
at org.apache.http.impl.conn.DefaultClientConnectionOperator.openConnection(DefaultClientConnectionOperator.java:180) ~[httpclient-4.5.3.jar:4.5.3]
at org.apache.http.impl.conn.AbstractPoolEntry.open(AbstractPoolEntry.java:144) ~[httpclient-4.5.3.jar:4.5.3]
at org.apache.http.impl.conn.AbstractPooledConnAdapter.open(AbstractPooledConnAdapter.java:134) ~[httpclient-4.5.3.jar:4.5.3]
at org.apache.http.impl.client.DefaultRequestDirector.tryConnect(DefaultRequestDirector.java:610) ~[httpclient-4.5.3.jar:4.5.3]
at org.apache.http.impl.client.DefaultRequestDirector.execute(DefaultRequestDirector.java:445) ~[httpclient-4.5.3.jar:4.5.3]
at org.apache.http.impl.client.AbstractHttpClient.doExecute(AbstractHttpClient.java:835) ~[httpclient-4.5.3.jar:4.5.3]
at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:118) ~[httpclient-4.5.3.jar:4.5.3]
at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:56) ~[httpclient-4.5.3.jar:4.5.3]
at com.sun.jersey.client.apache4.ApacheHttpClient4Handler.handle(ApacheHttpClient4Handler.java:173) ~[jersey-apache-client4-1.19.1.jar:1.19.1]
... 29 common frames omitted
- 去除Error日志
當然你可以忽略該錯誤日志,繼續(xù)使用Eureka刃永。但是作為有潔癖的程序員看到報錯信息當然是無法忍受的货矮,那么如何干掉這些錯誤日志中,我們需要將配置文件改成如下內容即可
server:
port: 7001
eureka:
client: # 客戶端進行Eureka注冊的配置
service-url:
defaultZone: http://127.0.0.1:7001/eureka
register-with-eureka: false # 當前的微服務不注冊到eureka之中
fetch-registry: false # 不通過eureka獲取注冊信息
instance: # eureak實例定義
hostname: 127.0.0.1 # 定義Eureka實例所在的主機名稱
搭建集群版Eureka
在實際應用中斯够,任何服務都不會選擇單實例部署囚玫,因為存在單點故障問題喧锦,線上的服務要想高可用必須要搭建集群。在Eureka當中抓督,任何服務都可以作為服務提供者去Eureka進行注冊裸违,這當然包括Eureka服務端本身。Eureka集群的搭建正是利用了這個特性本昏。
- Eureka-server-1做為服務提供者向Eureka-server-2和Eureka-server-3中進行注冊供汛;
- Eureka-server-2做為服務提供者向Eureka-server-1和Eureka-server-3中進行注冊;
- Eureka-server-3做為服務提供者向Eureka-server-1和Eureka-server-2中進行注冊涌穆;
我們用3個節(jié)點搭建集群怔昨,這3個節(jié)點的內容除配置文件之外其余的都和單機版相同。下面我們來看看這3個節(jié)點的具體配置宿稀。
Eureka-server-1節(jié)點的配置
server:
port: 7001
eureka:
#server:
client: # 客戶端進行Eureka注冊的配置
service-url:
defaultZone: http://eureka-7002.com:7002/eureka,http://eureka-7003.com:7003/eureka
register-with-eureka: false # 當前的微服務不注冊到eureka之中
fetch-registry: false # 不通過eureka獲取注冊信息
instance: # eureak實例定義
hostname: eureka-7001.com # 定義Eureka實例所在的主機名稱
spring:
application:
name: eureka-7001.com
Eureka-server-2節(jié)點的配置
server:
port: 7002
eureka:
client: # 客戶端進行Eureka注冊的配置
service-url:
defaultZone: http://eureka-7001.com:7001/eureka,http://eureka-7003.com:7003/eureka
register-with-eureka: false # 當前的微服務不注冊到eureka之中
fetch-registry: false # 不通過eureka獲取注冊信息
instance:
hostname: eureka-7002.com
spring:
application:
name: eureka-7002.com
Eureka-server-3節(jié)點的配置
server:
port: 7003
eureka:
client:
defalutZone: http://eureka-7001.com:7001/eureka,http://eureka-7002.com:7002/eureka
register-with-eureka: false
fetch-registry: false
instance:
hostname: eureka-7003.com
spring:
application:
name: eureka-7003.com
啟動3個節(jié)點的Eureka服務趁舀,登錄http://eureka-7001.com:7001/查看7001的Eureka控臺,在集群信息中可以看到集群中的另外兩個節(jié)點信息祝沸。
Eureka服務詳解
Eureka可以分為Eureka服務端和Eureka客戶端矮烹,Eureka服務端即服務注冊中心,Eureka客戶端包含兩個角色:服務提供者和服務消費者罩锐。Eureka的主要功能是服務治理
服務提供者的功能
- 注冊服務
- 續(xù)約
- 服務下線通知
服務消費者的功能
- 獲取服務列表
- 調用服務
服務注冊中心的功能
- 服務提供者信息同步
- 失效剔除
- 自我保護
這里我們先重點看看服務注冊中心的功能奉狈。
自我保護
在Eureka的控臺中,我們可能會經(jīng)成螅看到以下信息仁期,該信息表明Eureka的自我保護機制被觸發(fā)了。默認情況下竭恬,如果Eureka Server在一定時間內沒有接收到某個微服務實例的心跳跛蛋,Eureka Server將會注銷該實例(默認90秒)。但是當網(wǎng)絡分區(qū)故障發(fā)生時痊硕,微服務與Eureka Server之間無法正常通信赊级,以上行為可能變得非常危險了——因為微服務本身其實是健康的,此時本不應該注銷這個微服務岔绸。
Eureka通過“自我保護模式”來解決這個問題——當Eureka Server節(jié)點在短時間內丟失過多客戶端時(可能發(fā)生了網(wǎng)絡分區(qū)故障)理逊,那么這個節(jié)點就會進入自我保護模式。一旦進入該模式亭螟,Eureka Server就會保護服務注冊表中的信息挡鞍,不再刪除服務注冊表中的數(shù)據(jù)(也就是不會注銷任何微服務)骑歹。當網(wǎng)絡故障恢復后预烙,該Eureka Server節(jié)點會自動退出自我保護模式。
可以通過eureka: server:enable-self-preservation: false
將自我保護機制關閉道媚,但一般不建議將其關閉扁掸。
失效剔除
正常下線時翘县,服務提供者會發(fā)送下線通知給注冊中心,注冊中心能正常處理這種情況谴分。如果服務非正常下線的話锈麸,注冊中心又該如何處理呢?Eureka Server在啟動的時候會創(chuàng)建一個定時任務每分鐘掃描一篇服務清單牺蹄,如果發(fā)現(xiàn)有服務超過90秒沒有發(fā)送過心跳就將該服務信息剔除出去忘伞。
服務信息同步
當服務提供者將自己的信息注冊給某個注冊中心,該注冊中心就會將此服務信息同步到集群中的其他注冊中心上沙兰,從而實現(xiàn)注冊中心間的服務同步氓奈。
服務提供者
我們知道服務提供者有三個主要功能
- 注冊服務
- 續(xù)約
- 服務下線通知
注冊服務
- 引入相關依賴
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
- 在application.yml文件中對Eureka客戶端進行配置
eureka:
client: # 客戶端進行Eureka注冊的配置
service-url:
defaultZone: http://127.0.0.1:7001/eureka
- 在啟動類上追加Eureka客戶端啟用的注解
@EnableEurekaClient
@SpringBootApplication
@EnableEurekaClient
public class Dept_8001_StartSpringCloudApplication {
public static void main(String[] args) {
SpringApplication.run(Dept_8001_StartSpringCloudApplication.class, args);
}
}
- 啟動服務提供者,由于加入了
@EnableEurekaClient
注解及配置了Eureka服務的連接地址鼎天,所以會自動向Eureka注冊中心進行服務的注冊舀奶。此時訪問Eureka的控臺,可以看到以下信息
注冊的服務
最左側的Application表示服務的標記符斋射,服務的調用方正是通過該標識符對服務發(fā)起調用育勺。此時它的值為UNKNOWN這顯然不符合我們的要求,該值的內容取自application.yml文件中的spring.application.name罗岖,所以我們可以通過設置spring.application.name對其進行修改涧至。最右邊UP表示當前服務是活著的(DOWN表示服務不可用),UP邊上有個超鏈接桑包,點擊這個鏈接我們可以看到該服務提供者的詳細信息化借。這個詳細信息是在服務提供者的配置文件中進行配置的。 - 配置詳細的服務信息
server:
port: 8001
eureka:
client:
service-url:
defaultZone: http://eureka-7001.com:7001/eureka
instance:
instance-id: dept-8001.com # 在信息列表時顯示主機名稱
prefer-ip-address: true # 訪問的路徑變?yōu)?IP 地址
info:
app.name: spring-cloud-demo
company.name: zgc
build.artifactId: $project.artifactId$
build.version: $project.verson$
如果現(xiàn)在要想查看所有的微服務詳細信息捡多,還需要修改 pom.xml 文件蓖康,追加監(jiān)控配置:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot- starter-actuator</artifactId>
</dependency>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven- resources-plugin</artifactId>
<configuration>
<delimiters>
<delimiter>$</delimiter>
</delimiters>
</configuration>
</plugin>
此時再點擊查看服務詳情的info信息,可以看到如下信息
續(xù)約
服務注冊完之后垒手,服務提供者和Eureka注冊中心之間會維持心跳來告知注冊中心蒜焊,服務還活著。我們把該操作稱為服務續(xù)約(Renew)科贬,下列兩個配置和續(xù)約有關
eureka:
instance:
lease-renewal-interval-in-seconds: 30 #每30秒會向Eureka Server發(fā)起Renew操作
lease-expiration-duration-in-seconds: 90 #服務失效時間泳梆。默認是90秒,也就是如果Eureka Server在90秒內沒有接收到來自Service Provider的Renew操作榜掌,就會把Service Provider剔除优妙。
服務下線
當服務提供者進行正常的關閉操作時,會觸發(fā)一個服務下線的REST請求給Eureka注冊中心憎账。Eureka服務端在收到請求之后套硼,將該服務狀態(tài)設置為下線(DOWN),并把該線下通知廣播出去。
服務消費者
服務消費者的主要功能
- 獲取服務列表
- 調用服務
服務發(fā)現(xiàn)
服務發(fā)現(xiàn)不僅能應用在服務消費者中還能應用在服務提供者中胞皱。
- 在Eureka的客戶端程序中注入
DiscoveryClient
類邪意,借助該類可以幫助我們自動獲取服務的列表信息
@Autowired
private DiscoveryClient client ; // 進行Eureka的發(fā)現(xiàn)服務
@RequestMapping("/discover")
public Object discover() { // 直接返回發(fā)現(xiàn)服務信息
return this.client ;
}
- 在啟動類中加入
@EnableDiscoveryClient
注解九妈。(貌似不加入也能夠生效)
調用服務
服務消費者通過服務標識符獲取具體的服務提供者信息,由服務消費者自己決定具體調用哪個服務提供者雾鬼。所以服務消費者通常要維護負載均衡算法萌朱,在SpringCloud中提供了Ribbon組件進行客戶端的負載調度。
參考連接
Eureka 客戶端和服務端間的交互