目錄
2:新建一個子module:eurekaserver作爲Eureka的註冊中心:
3:新建服務提供者的模塊 eureka-service.producer
4:新建服務調用方模塊 eureka-service.consumer
代碼地址:代碼地址
一:Eureka簡介
Eureka 是 Netflix 開發的,一個基於 REST 服務的,服務註冊與發現的組件。它在爲服務中注意要用於服務管理:
1:自動註冊和發現。2:實現狀態監管 3:實現動態路由 4:實現負載均衡
它主要包括兩個組件:Eureka Server 和 Eureka Client
- Eureka Client:一個Java客戶端,用於簡化與 Eureka Server 的交互(通常就是微服務中的客戶端和服務端)
- Eureka Server:提供服務註冊和發現的能力(通常就是微服務中的註冊中心)
各個微服務啓動時,會通過 Eureka Client 向 Eureka Server 註冊自己,Eureka Server 會存儲該服務的信息
也就是說,每個微服務的客戶端和服務端,都會註冊到 Eureka Server,這就衍生出了微服務相互識別的話題
- 同步:每個 Eureka Server 同時也是 Eureka Client(邏輯上的)
多個 Eureka Server 之間通過複製的方式完成服務註冊表的同步,形成 Eureka 的高可用 - 識別:Eureka Client 會緩存 Eureka Server 中的信息
即使所有 Eureka Server 節點都宕掉,服務消費者仍可使用緩存中的信息找到服務提供者 - 續約:微服務會週期性(默認30s)地向 Eureka Server 發送心跳以Renew(續約)信息(類似於heartbeat)
- 續期:Eureka Server 會定期(默認60s)執行一次失效服務檢測功能
它會檢查超過一定時間(默認90s)沒有Renew的微服務,發現則會註銷該微服務節點
描述註冊中心,服務提供方,客戶端消費者的示意圖如下:
Eureka: 就是服務註冊中心(可以是一個集羣),對外暴露及自己的地址。
提供者: 啓動後向Eureka註冊自己的信息。
消費者:向Eureka訂閱服務,Eureka會將對應服務的所有提供者地址列表發送給消費者,並定時更新。
心跳(續約):提供者定期通過http方式向Eureka刷新自己的狀態,Eureka會更新註冊的服務是否可用。
二:demo搭建
注意!!!搭建微服務項目要注意springclound和springboot的版本匹配問題,如果版本不匹配會出現很多奇怪的問題,本人在使用的時候
被折騰了好久,我整理一下他們的版本匹配,下面pom中的版本使用匹配都是實踐可行的。
spring-boot-starter-parent | spring-cloud-dependencies | ||||
---|---|---|---|---|---|
版本號 | 發佈日期 | 版本號 | 發佈日期 | ||
1.5.2.RELEASE | 2017年3月 | 穩定版 | Dalston.RC1 | 2017年未知月 | |
1.5.9.RELEASE | 2017年11月 | 穩定版 | Edgware.RELEASE | 2017年11月 | 穩定版 |
1.5.16.RELEASE | Edgware.SR5 | ||||
1.5.20.RELEASE | Edgware.SR5 | ||||
2.0.2.RELEASE | 2018年5月 | Finchley.BUILD-SNAPSHOT | 2018年未知月 | ||
2.0.3.RELEASE | Finchley.RELEASE | ||||
2.0.6.RELEASE | Finchley.SR2 | ||||
2.1.4.RELEASE | Greenwich.SR1 |
利用maven搭建父子工程:
1:父工程就是個普通maven工程,沒有使用任何模板,在pom.xml裏使用瞭如下配置,因爲後面要用它做其它項目,所以多了一點:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>Eureka-Demo</groupId>
<artifactId>demo</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<modules>
<module>eurekaserver</module>
<module>serviceproducer</module>
</modules>
<properties>
<java.version>1.8</java.version>
<spring-clound.version>Finchley.SR1</spring-clound.version>
<mapper.starter.version>2.0.3</mapper.starter.version>
<mysql.version>5.1.32</mysql.version>
<springboot.version>2.0.3.RELEASE</springboot.version>
<pageHelper.starter.version>1.2.5</pageHelper.starter.version>
</properties>
<dependencyManagement>
<dependencies>
<!--SpringClound-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-clound.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--通用mapper啓動器-->
<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper-spring-boot-starter</artifactId>
<version>${mapper.starter.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>${springboot.version}</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
2:新建一個子module:eurekaserver作爲Eureka的註冊中心:
註冊中心的pom.xml中引入:
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
</dependencies>
3:新建springboot啓動類,因爲沒有使用springIO模板,springboot啓動類沒有自動生成。
@SpringBootApplication
@EnableEurekaServer
public class EurekaApplication {
public static void main (String [] args){
SpringApplication.run(EurekaApplication.class);
return;
}
}
注意,啓動類上的註解和平常的Springboot項目啓動類多了一個用於指明是個Eureka服務的註解:@EnableEurekaServer
爲了避免默認端口號衝突在application.yml中修改端口:
server:
port: 8010
啓動報錯如下:
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'gsonBuilder' defined in class path resource [org/springframework/boot/autoconfigure/gson/GsonAutoConfiguration.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.google.gson.GsonBuilder]: Factory method 'gsonBuilder' threw exception; nested exception is java.lang.BootstrapMethodError: java.lang.NoSuchMethodError: com.google.gson.GsonBuilder.setLenient()Lcom/google/gson/GsonBuilder;
at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:590) ~[spring-beans-5.0.8.RELEASE.jar:5.0.8.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1247) ~[spring-beans-5.0.8.RELEASE.jar:5.0.8.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1096) ~[spring-beans-5.0.8.RELEASE.jar:5.0.8.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:535) ~[spring-beans-5.0.8.RELEASE.jar:5.0.8.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:495) ~[spring-beans-5.0.8.RELEASE.jar:5.0.8.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:317) ~[spring-beans-5.0.8.RELEASE.jar:5.0.8.RELEASE]
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222) ~[spring-beans-5.0.8.RELEASE.jar:5.0.8.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:315) ~[spring-beans-5.0.8.RELEASE.jar:5.0.8.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199) ~[spring-beans-5.0.8.RELEASE.jar:5.0.8.RELEASE]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:759) ~[spring-beans-5.0.8.RELEASE.jar:5.0.8.RELEASE]
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:869) ~[spring-context-5.0.8.RELEASE.jar:5.0.8.RELEASE]
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:550) ~[spring-context-5.0.8.RELEASE.jar:5.0.8.RELEASE]
at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:140) ~[spring-boot-2.0.4.RELEASE.jar:2.0.4.RELEASE]
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:762) [spring-boot-2.0.4.RELEASE.jar:2.0.4.RELEASE]
at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:398) [spring-boot-2.0.4.RELEASE.jar:2.0.4.RELEASE]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:330) [spring-boot-2.0.4.RELEASE.jar:2.0.4.RELEASE]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1258) [spring-boot-2.0.4.RELEASE.jar:2.0.4.RELEASE]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1246) [spring-boot-2.0.4.RELEASE.jar:2.0.4.RELEASE]
at com.eureka.EurekaApplication.main(EurekaApplication.java:14) [classes/:na]
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.google.gson.GsonBuilder]: Factory method 'gsonBuilder' threw exception; nested exception is java.lang.BootstrapMethodError: java.lang.NoSuchMethodError: com.google.gson.GsonBuilder.setLenient()Lcom/google/gson/GsonBuilder;
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:185) ~[spring-beans-5.0.8.RELEASE.jar:5.0.8.RELEASE]
at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:582) ~[spring-beans-5.0.8.RELEASE.jar:5.0.8.RELEASE]
... 18 common frames omitted
檢查引入的依賴包,發現下面有如下的jar包,是這個jar包版本的問題,可以把這個jar包改爲直接依賴版本號提升一下,但是對於我們這個項目來說不需要它,所以可以直接排除它:
在註冊中心的pom.xml中排除它:
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
<exclusions>
<exclusion>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
再次啓動發現還是報錯,而且一直在報,但是報錯信息變了,不是上面gson的問題了:發現是Eureka客戶端的問題。
但是我們發現項目已經啓動了,我們先試着訪問一下:竟然成功了:
那報錯的原因是爲啥呢?
原因在於:Eureka作爲註冊中心,一般不允許掛掉,所以一般都會做成集羣,做成集羣之後,註冊中心之間就需要相互監聽,也就需要相互註冊,所以註冊中心也是邏輯上的Eureka客戶端。我們看到我們註冊中心模塊只引入了eureka-server的依賴,但是引入的jar中卻包含了客戶端的內容
後臺一直在重複報錯,連接拒絕,一直在重試一直timeout,我們debug可以看到它連接的地址是:
爲了不報錯我們需要把它這個默認的地址改掉 ,改爲我們自己的地址,我們沒有做集羣,只能自己註冊自己。
eureka中有個屬性可以修改這個地址:注意這個屬性值是map類型的,從下面的源碼中可以看出。
server:
port: 8010
eureka:
client:
service-url:
defaultZone: http://localhost:8010/eureka/
我們可以看到客戶端配置bean中這個屬性的默認值就是嘗試連接本地的8761端口地址。
通過上面的設置,我們再來重啓一下。發現還是會報一次錯誤,而不是一直報錯,這就正常了,因爲我們自己註冊了自己,當啓動的時候嘗試請求我們的註冊中心,但是我們註冊中心在啓動,所以會報一次錯,當註冊中心啓動完成之後,就不會再報錯了。
頁面內容如下:
但是我們發現當前註冊到Eureka的實例應用名確實unknown的,這不是我們所想看到的,我們可以通過如下設置給我們的服務起名稱:
spring:
application:
name: eureka-server
添加後的結果如下:
3:新建服務提供者的模塊 eureka-service.producer
1)引入Eureka客戶端的依賴:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
<exclusions>
<exclusion>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
</exclusion>
</exclusions>
</dependency>
2)添加啓動類入口
/**
* 注意這裏的註解也可以是@EnableEurekaClient
* 但是推薦使用 @EnableDiscoveryClient ,因爲@EnableEurekaClient只能把服務註冊到
* Eureka註冊中心,但是註冊中心不止Eureka一個,springboot也可以使用其它
* 註冊中心,比如:Zookeeper等。但是用@EnableDiscoveryClient就可以同時向其它
* 註冊中心註冊
* */
@EnableDiscoveryClient
@SpringBootApplication
public class ServiceApplication {
public static void main(String[] arg){
SpringApplication.run(ServiceApplication.class);
}
}
3)添加配置:同註冊中心的配置,配置註冊的地址,定義服務的名字,修改啓動端口號。
spring:
datasource:
hikari:
jdbc-url: jdbc:mysql://127.0.0.1:3306/db_1?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
username: root
password: root
application:
name: eureka-service.producer
eureka:
client:
service-url:
defaultZone: http://localhost:8010/eureka/
server:
port: 8011
啓動服務提供者模塊後訪問註冊中心可以看到註冊成功了。
但是我們發現Status下面的鏈接地址卻是如上箭頭所示的那樣,這是因爲我們註冊的服務都是本地地址,如果註冊的服務發佈在不同的服務器上,上面就不能這樣顯示了。
如果服務提供者和註冊中心服務在同一臺機器上,大家都是localhost的域名,那就沒問題,能發現服務;
如果服務提供者和註冊中心服務不在同一臺機器上,會報一個沒找到服務的異常,原因是根據localhost和端口沒有找到相應的服務。
此時,一種解決方法是讓服務提供者上報自己的 ip地址,並顯示在Eureka的註冊列表中。
我們在註冊中心模塊和服務提供者模塊的配置文件中都配置如下屬性,來上報自己的ip地址,因爲我們都是在本地啓動的,所以配置的還是localhost.
#註冊中心的配置
server:
port: 8010
eureka:
instance:
prefer-ip-address: true
instance-id: 127.0.0.1:8010
#服務提供者的配置
eureka:
instance:
prefer-ip-address: true
instance-id: 127.0.0.1:8011
重啓訪問頁面如下:
4:新建服務調用方模塊 eureka-service.consumer
配置內容同服務提供方的內容
1:新建一個模塊導入依賴:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
<exclusions>
<exclusion>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
2:啓動類配置
@SpringBootApplication
@EnableDiscoveryClient
public class ConsumerApplication {
public static void main (String [] arg){
SpringApplication.run(ConsumerApplication.class);
}
}
3:配置文件application.yml配置
server:
port: 8012
eureka:
client:
service-url:
defaultZone: http://localhost:8010/eureka/
instance:
prefer-ip-address: true
instance-id: 127.0.0.1:8012
spring:
application:
name: eureka-service.consumer
啓動之後,刷新頁面。看到服務註冊已經成功了。
三:在Eureka中調用遠程服務
這裏使用http的方式調用遠程,當然也可以使用其它技術比如RPC等。
1:我在EUREKA-SERVICE.PRODUCER模塊中寫了一個如下接口: 用於返回所有的產品信息
訪問地址:http://localhost:8011/getProducts
@RestController
public class ApplicationRestController {
@Autowired
private ProductService productService;
@GetMapping("/getProducts")
public String getAllProduct(){
return productService.getAllProduct().toString();
}
}
我想在EUREKA-SERVICE.CONSUMER中調用,如果按以往 做法,在這裏面寫個controller然後直接調用http://localhost:8011/getProducts就可以得到結果,但是我們現在要從Eureka註冊中心獲取服務的提供者進行請求。
我們在EUREKA-SERVICE.CONSUMER使用如下方式調用:
@RestController
public class ConsumerController {
@Autowired
RestTemplate restTemplate;
@Autowired
DiscoveryClient discoveryClient;
@GetMapping("/getAllProduct")
public String getAllProduct(){
/**第一步獲取服務實例。
* 參數是實例id(服務id),也就是我們定義的spring.application.name參數
* 返回結果是個集合,因爲我們的服務會做集羣,不止一個,但是服務id都爲同一個,
* 當一個服務比如:serviceproducer這個服務,發佈到多個服務器上時,在註冊中心的頁面上的
* Availability Zones就會有多個。
* 我們根據服務id也就時頁面上的 Application得到其中一個實例。
* */
List<ServiceInstance> instances = discoveryClient.getInstances("eureka-service.producer");
//第二步: 從實例中獲取服務的具體地址,我們這裏只有一個服務,所以只用第一個
ServiceInstance serviceInstance = instances.get(0);
//第三步: 從服務實例中獲取ip+port
String host = serviceInstance.getHost();
int port = serviceInstance.getPort();
String uri="http://"+host+":"+port+"/getProducts";
String vos = restTemplate.getForObject(uri, String.class);
// List<ProductVo> productVos = Arrays.asList(forObject);
return vos;
}
}