本項目的筆記和資料的Download,請點擊這一句話自行獲取。html
day01-springboot(理論篇) ;day01-springboot(實踐篇)java
day02-springcloud(理論篇一) ;day02-springcloud(理論篇二) ;day02-springcloud(理論篇三:搭建Eureka註冊中心) ;day02-springcloud(理論篇四)mysql
首先咱們來解決第一個問題,服務的管理。web
問題分析spring
在剛纔的案例中,user-service對外提供服務,須要對外暴露本身的地址。而consumer(調用者)須要記錄服務提供者的地址。未來地址出現變動,還須要及時更新。這在服務較少的時候並不以爲有什麼,可是在如今日益複雜的互聯網環境,一個項目確定會拆分出十幾,甚至數十個微服務。此時若是還人爲管理地址,不只開發困難,未來測試、發佈上線都會很是麻煩,這與DevOps的思想是背道而馳的。sql
網約車數據庫
這就比如是 網約車出現之前,人們出門叫車只能叫出租車。一些私家車想作出租卻沒有資格,被稱爲黑車。而不少人想要約車,可是無奈出租車太少,不方便。私家車不少卻不敢攔,並且滿大街的車,誰知道哪一個纔是願意載人的。一個想要,一個願意給,就是缺乏引導,缺少管理。apache
此時滴滴這樣的網約車平臺出現了,全部想載客的私家車所有到滴滴注冊,記錄你的車型(服務類型),身份信息(聯繫方式)。這樣提供服務的私家車,在滴滴那裏都能找到,一目瞭然。瀏覽器
此時要叫車的人,只須要打開APP,輸入你的目的地,選擇車型(服務類型),滴滴自動安排一個符合需求的車到你面前,爲你服務,完美!緩存
Eureka作什麼?
Eureka就比如是滴滴,負責管理、記錄服務提供者的信息。服務調用者無需本身尋找服務,而是把本身的需求告訴Eureka,而後Eureka會把符合你需求的服務告訴你。
同時,服務提供方與Eureka之間經過「心跳」
機制進行監控,當某個服務提供方出現問題,Eureka天然會把它從服務列表中剔除。
這就實現了服務的自動註冊、發現、狀態監控。
基本架構:
- Eureka:就是服務註冊中心(能夠是一個集羣),對外暴露本身的地址
- 提供者:啓動後向Eureka註冊本身信息(地址,提供什麼服務)
- 消費者:向Eureka訂閱服務,Eureka會將對應服務的全部提供者地址列表發送給消費者,而且按期更新
- 心跳(續約):提供者按期經過http方式向Eureka刷新本身的狀態
上一篇博客中,我練習的代碼是分紅獨立開發的2個Project,爲了和蘋果版視頻課中張翼虎的項目結構一致,我這裏重構項目結構,使用Maven的pom聚合功能管理父工程和子模塊。
cloud-demo的 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> <!-- 子模塊天生繼承父工程,可使用父工程全部資源。 子模塊之間天生是沒有任何關係的。 父子工程直接不用創建關係,繼承關係是先天的,不須要手動創建。 平級直接的引用叫依賴,依賴不是先天的,依賴是須要後天創建的。 --> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.9.RELEASE</version> <!--兼容spring cloud Finchley.SR4--> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.leyou.demo</groupId> <artifactId>cloud-demo</artifactId> <version>1.0-SNAPSHOT</version> <packaging>pom</packaging> <!-- 統一管理打包JDK版本和編碼格式 --> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties> <!-- SpringCloud依賴,必定要放到dependencyManagement中,起到管理版本的做用 --> <dependencyManagement> <dependencies> <!-- SpringCloud版本,使用比較新的F系列 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Finchley.SR4</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <!--springboot測試起步依賴--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <!--SpringBoot熱部署開發模式--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> </dependency> <!--至關於springMVC的起步依賴--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> <modules> <module>user-service-demo</module> <module>user-consumer-demo</module> <module>eureka-server-demo</module> </modules> <!-- Spring的倉庫地址 --> <repositories> <repository> <id>spring-milestones</id> <name>Spring Milestones</name> <url>https://repo.spring.io/milestone</url> <snapshots> <enabled>false</enabled> </snapshots> </repository> </repositories> </project>
user-service-demo的 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 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>com.leyou.demo</groupId> <artifactId>cloud-demo</artifactId> <version>1.0-SNAPSHOT</version> </parent> <artifactId>user-service-demo</artifactId> <!-- 統一管理jar包版本 --> <properties> <mybatis.version>3.4.6</mybatis.version> <mysql.version>8.0.17</mysql.version> <mapper.version>2.1.5</mapper.version> <hikaricp.version>3.4.1</hikaricp.version> </properties> <!--此處必須有jar包版本鎖定,不然和通用Mapper有版本衝突--> <dependencyManagement> <dependencies> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>${mybatis.version}</version> </dependency> <!-- HikariCP數據庫鏈接池 --> <dependency> <groupId>com.zaxxer</groupId> <artifactId>HikariCP</artifactId> <version>${hikaricp.version}</version> </dependency> </dependencies> </dependencyManagement> <dependencies> <!--DAO層相關的數據庫驅動--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>${mysql.version}</version> </dependency> <!-- 通用mapper的啓動器 --> <dependency> <groupId>tk.mybatis</groupId> <artifactId>mapper-spring-boot-starter</artifactId> <version>${mapper.version}</version> </dependency> <!-- Eureka註冊的客戶端 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> </dependencies> </project>
user-consumer-demo 的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> <parent> <artifactId>cloud-demo</artifactId> <groupId>com.leyou.demo</groupId> <version>1.0-SNAPSHOT</version> </parent> <artifactId>user-consumer-demo</artifactId> <dependencies> <!-- 添加OkHttp支持 --> <dependency> <groupId>com.squareup.okhttp3</groupId> <artifactId>okhttp</artifactId> <version>4.2.1</version> </dependency> <!-- Eureka註冊中心的客戶端 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> </dependencies> </project>
客戶端瀏覽器GET請求發送的數據用 , 隔開在這裏至關於List<Long>集合。
@RestController @RequestMapping("consume") public class ConsumerController { @Autowired private UserService userService; @GetMapping //http://localhost:8080/consume?ids=1,7,2 public List<User> consume(@RequestParam("ids") List<Long> ids) { return this.userService.querUserByIds(ids); } }
接下來咱們建立一個項目,啓動一個EurekaServer:
在IDEA中先用鼠標選中cloud-demo父工程,而後選擇 new一個 Module ,建立一個普通的Maven子模塊項目。
完整的Pom文件:
<?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"> <parent> <artifactId>cloud-demo</artifactId> <groupId>com.leyou.demo</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>eureka-server-demo</artifactId> <properties> <!-- SpringCloud版本,是較新的F系列 --> <spring-cloud.version>Finchley.SR4</spring-cloud.version> </properties> <dependencyManagement> <dependencies> <!-- SpringCloud依賴,必定要放到dependencyManagement中,起到管理版本的做用便可 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <!--增長eureka-server服務端的依賴--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> <version>2.1.3.RELEASE</version> </dependency> </dependencies> <repositories> <repository> <id>spring-milestones</id> <name>Spring Milestones</name> <url>https://repo.spring.io/milestone</url> <snapshots> <enabled>false</enabled> </snapshots> </repository> </repositories> </project>
啓動類添加@EnableEurekaServer,啓用註冊中心服務
@SpringBootApplication @EnableEurekaServer // 聲明這個應用是一個EurekaServer public class EurekaDemoApplication { public static void main(String[] args) { SpringApplication.run(EurekaDemoApplication.class, args); } }
配置application.yml
# 端口號 server: port: 10086 spring: application: name: eureka-server # 應用名稱,會在Eureka中顯示 eureka: client: #是否開啓註冊服務,由於這裏若是爲true表示本身註冊本身 register-with-eureka: false # 是否註冊本身的信息到EurekaServer,默認是true fetch-registry: false # 是否拉取其它服務的信息,默認是true service-url: # EurekaServer的地址,如今是本身的地址,若是是集羣,須要加上其它Server的地址。 defaultZone: http://127.0.0.1:${server.port}/eureka #Eureka實例名,集羣中根據這裏相互識別 instance: hostname: eureka0
而後啓動springboot工程,從瀏覽器訪問啓動服務訪問:http://127.0.0.1:10086/eureka,能夠看到以下界面,由於尚未註冊服務,因此應用列表是空的。
註冊服務,就是在服務上添加Eureka的客戶端依賴,客戶端代碼會自動把服務註冊到EurekaServer中。
咱們在user-service-demo中添加Eureka客戶端依賴:
先添加SpringCloud依賴(能夠直接添加到父工程的pom.xml):
<!-- SpringCloud依賴,必定要放到dependencyManagement中,起到管理版本的做用 --> <dependencyManagement> <dependencies> <!-- SpringCloud版本,使用比較新的F系列 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Finchley.SR4</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>
<!-- Spring的倉庫地址 --> <repositories> <repository> <id>spring-milestones</id> <name>Spring Milestones</name> <url>https://repo.spring.io/milestone</url> <snapshots> <enabled>false</enabled> </snapshots> </repository> </repositories>
而後是Eureka客戶端(必須複製到提供service的子模塊本身的pom.xml):
<!-- Eureka客戶端 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency>
在啓動類上開啓Eureka客戶端功能
經過添加
@EnableDiscoveryClient
來開啓Eureka客戶端功能
編寫配置
application.yml
spring: datasource: #四項鍊接配置 driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/leyou?useUniode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai username: root password: root #鏈接池類型 type: com.zaxxer.hikari.HikariDataSource #鏈接池個性化配置 hikari: maximum-pool-size: 20 minimum-idle: 10 application: name: user-service # 應用名稱 eureka: client: service-url: # EurekaServer地址 defaultZone: http://127.0.0.1:10086/eureka instance: prefer-ip-address: true # 當調用getHostname獲取實例的hostname時,返回ip而不是host名稱 ip-address: 127.0.0.1 # 指定本身的ip信息,不指定的話會本身尋找 mybatis: #配置entity實體類的映射掃描包 type-aliases-package: com.leyou.demo.userservice.pojo configuration: #配置下劃線轉駝峯規則 map-underscore-to-camel-case: true #tomcat服務器端口 server: port: 8081
注意:
重啓項目,訪問Eureka監控頁面查看
咱們發現user-service服務已經註冊成功了。
接下來咱們修改consumer-demo,嘗試從EurekaServer獲取服務。
方法與消費者相似,只須要在項目中添加EurekaClient依賴,就能夠經過服務名稱來獲取信息了!
1)添加依賴:
先添加SpringCloud依賴:
而後是Eureka客戶端:
<dependencies> <!-- 添加OkHttp支持 --> <dependency> <groupId>com.squareup.okhttp3</groupId> <artifactId>okhttp</artifactId> <version>4.2.1</version> </dependency> <!-- Eureka客戶端 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> <version>2.0.4.RELEASE</version> </dependency> </dependencies>
2)在啓動類開啓Eureka客戶端
@SpringBootApplication @EnableDiscoveryClient //打開Eureka客戶端 public class UserConsumerDemoApplication { public static void main(String[] args) { SpringApplication.run(UserConsumerDemoApplication.class, args); } @Bean public RestTemplate restTemplate() { //註冊誰,就用@Bean註解返回,誰的一個匿名對象 // 此次咱們使用了OkHttp客戶端,只須要注入工廠便可 return new RestTemplate(new OkHttp3ClientHttpRequestFactory()); } }
3)修改配置:
application.yml
server: port: 8080 spring: application: name: consumer # 應用名稱 eureka: client: service-url: # EurekaServer地址 defaultZone: http://127.0.0.1:10086/eureka instance: prefer-ip-address: true # 當其它服務獲取地址時提供ip而不是hostname ip-address: 127.0.0.1 # 指定本身的ip信息,不指定的話會本身尋找
4)修改代碼,用DiscoveryClient類的方法,根據服務名稱,獲取服務實例:
@Service public class UserServiceClient { @Autowired private RestTemplate restTemplate; @Autowired private DiscoveryClient discoveryClient;// Eureka客戶端,能夠獲取到服務實例信息 public List<User> queryUserByIds(List<Long> ids) { List<User> users = new ArrayList<>(); // String baseUrl = "http://localhost:8081/user/"; // 根據服務名稱,獲取服務實例 List<ServiceInstance> instances = discoveryClient.getInstances("user-service"); // 由於只有一個UserService,所以咱們直接get(0)獲取 ServiceInstance instance = instances.get(0); // 獲取ip和端口信息 String baseUrl = "http://"+instance.getHost() + ":" + instance.getPort()+"/user/"; ids.forEach(id -> { // 咱們測試屢次查詢, users.add(this.restTemplate.getForObject(baseUrl + id, User.class)); // 每次間隔500毫秒 try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } }); return users; } }
接下來咱們詳細講解Eureka的原理及配置。
Eureka架構中的三個核心角色:
服務註冊中心
Eureka的服務端應用,提供服務註冊和發現功能,就是剛剛咱們創建的eureka-demo
服務提供者
提供服務的應用,能夠是SpringBoot應用,也能夠是其它任意技術實現,只要對外提供的是Rest風格服務便可。本例中就是咱們實現的user-service-demo
服務消費者
消費應用從註冊中心獲取服務列表,從而得知每一個服務方的信息,知道去哪裏調用服務方。本例中就是咱們實現的consumer-demo
Eureka Server即服務的註冊中心,在剛纔的案例中,咱們只有一個EurekaServer,事實上EurekaServer也能夠是一個集羣,造成高可用的Eureka中心。
服務同步原理
多個Eureka Server之間也會互相註冊爲服務,當服務提供者註冊到Eureka Server集羣中的某個節點時,該節點會把服務的信息同步給集羣中的每一個節點,從而實現數據同步。所以,不管客戶端訪問到Eureka Server集羣中的任意一個節點,均可以獲取到完整的服務列表信息。
動手搭建高可用的EurekaServer
咱們假設要搭建兩條EurekaServer的集羣,端口分別爲:10086和10087
1)咱們修改原來的EurekaServer配置:
server: port: 10086 # 端口 spring: application: name: eureka-server # 應用名稱,會在Eureka中顯示 eureka: client: service-url: # 配置其餘Eureka服務的地址,而不是本身,好比10087 defaultZone: http://127.0.0.1:10087/eureka
所謂的高可用註冊中心,其實就是把EurekaServer本身也做爲一個服務進行註冊,這樣多個EurekaServer之間就能互相發現對方,從而造成集羣。所以咱們作了如下修改:
2)另一臺配置剛好相反:
server: port: 10087 # 端口 spring: application: name: eureka-server # 應用名稱,會在Eureka中顯示 eureka: client: service-url: # 配置其餘Eureka服務的地址,而不是本身,好比10087 defaultZone: http://127.0.0.1:10086/eureka
注意:idea中一個應用不能啓動兩次,咱們須要從新配置一個啓動器:
而後啓動便可。
3)啓動測試:
4)客戶端註冊服務到集羣
由於EurekaServer不止一個,所以註冊服務的時候,service-url參數須要變化:
eureka: client: service-url: # EurekaServer地址,多個地址以','隔開 defaultZone: http://127.0.0.1:10086/eureka,http://127.0.0.1:10087/eureka
服務提供者要向EurekaServer註冊服務,而且完成服務續約等工做。
服務註冊
服務提供者在啓動時,會檢測配置屬性中的:eureka.client.register-with-erueka=true
參數是否正確,事實上默認就是true。若是值確實爲true,則會向EurekaServer發起一個Rest請求,並攜帶本身的元數據信息,Eureka Server會把這些信息保存到一個雙層Map結構中。第一層Map的Key就是服務名稱,第二層Map的key是服務的實例id。
服務續約
在註冊服務完成之後,服務提供者會維持一個心跳(定時向EurekaServer發起Rest請求),告訴EurekaServer:「我還活着」。這個咱們稱爲服務的續約(renew);
有兩個重要參數能夠修改服務續約的行爲:
eureka: instance: lease-expiration-duration-in-seconds: 90 lease-renewal-interval-in-seconds: 30
也就是說,默認狀況下每一個30秒服務會向註冊中心發送一次心跳,證實本身還活着。若是超過90秒沒有發送心跳,EurekaServer就會認爲該服務宕機,會從服務列表中移除,這兩個值在生產環境不要修改,默認便可。
可是在開發時,這個值有點太長了,常常咱們關掉一個服務,會發現Eureka依然認爲服務在活着。因此咱們在開發階段能夠適當調小。
eureka: instance: lease-expiration-duration-in-seconds: 10 # 10秒即過時 lease-renewal-interval-in-seconds: 5 # 5秒一次心跳
實例id
先來看一下服務狀態信息:
在Eureka監控頁面,查看服務註冊信息:
在status一列中,顯示如下信息:
${hostname} + ${spring.application.name} + ${server.port}
示例的名稱(instance-id)是區分同一服務的不一樣實例的惟一標準,所以不能重複。咱們能夠經過instance-id屬性來修改它的構成:
eureka:
instance:
instance-id: ${spring.application.name}:${server.port}
獲取服務列表
當服務消費者啓動時,會檢測eureka.client.fetch-registry=true
參數的值,若是爲true,則會從Eureka Server服務的列表只讀備份,而後緩存在本地。而且每隔30秒
會從新獲取並更新數據。咱們能夠經過下面的參數來修改:
eureka: client: registry-fetch-interval-seconds: 5
生產環境中,咱們不須要修改這個值。
可是爲了開發環境下,可以快速獲得服務的最新狀態,咱們能夠適當將其設置小一點。
失效剔除
有些時候,咱們的服務提供方並不必定會正常下線,可能由於內存溢出、網絡故障等緣由致使服務沒法正常工做。Eureka Server須要將這樣的服務剔除出服務列表。所以它會開啓一個定時任務,每隔60秒對全部失效的服務(超過90秒未響應)進行剔除。
能夠經過eureka.server.eviction-interval-timer-in-ms
參數對其進行修改,單位是毫秒,生成環境不要修改。
這個會對咱們開發帶來極大的不變,你對服務重啓,隔了60秒Eureka才反應過來。開發階段能夠適當調整,好比10S
自我保護
咱們關停一個服務,就會在Eureka面板看到一條警告:
這是觸發了Eureka的自我保護機制。當一個服務未按時進行心跳續約時,Eureka會統計最近15分鐘心跳失敗的服務實例的比例是否超過了85%。在生產環境下,由於網絡延遲等緣由,心跳失敗實例的比例頗有可能超標,可是此時就把服務剔除列表並不穩當,由於服務可能沒有宕機。Eureka就會把當前實例的註冊信息保護起來,不予剔除。生產環境下這頗有效,保證了大多數服務依然可用。
可是這給咱們的開發帶來了麻煩, 所以開發階段咱們都會關閉自我保護模式:
eureka: server: enable-self-preservation: false # 關閉自我保護模式(缺省爲打開) eviction-interval-timer-in-ms: 1000 # 掃描失效服務的間隔時間(缺省爲60*1000ms)
=============================================
參考資料:
Spring Cloud升級最新Finchley版本的全部坑
SpringCloud組件---Eureka Server詳細啓動過程及圖解
end