javascript
SpringCloud(第一部分)
文章目錄
- 1. 系統架構
- 1.1 流動計算架構(SOA)
- 1.2 微服務
- 2. 服務調用方式
- 2.1 RPC和HTTP
- 2.2 Http客戶端工具
- 2.3 Spring的RestTemplate
- 3. 初識SpringCloud
- 3.1 簡介
- 4. 微服務場景模擬
- 4.1 服務提供者
- 4.1.1 Spring腳手架創建工程
- 4.1.2 編寫代碼
- 4.1.2.1 配置
- 4.1.2.2 實體類
- 4.1.2.3 UserMapper
- 4.1.2.4 UserService
- 4.1.2.5 UserController
- 4.1.3 測試
- 4.2 服務調用者
- 4.2.1 創建工程
- 4.2.2 編寫代碼
- 4.2.3 測試
- 4.3 問題總結
- 5. Eureka注冊中心
- 5.1 認識Eureka
- 5.2 原理圖
- 5.3 入門案例
- 5.3.1 搭建EurekaServer
- 5.3.2 注冊到Eureka
- 5.3.2.1 pom.xml
- 5.3.2.2 application.yml
- 5.3.2.3 引導類
- 5.3.3 從Eureka獲取服務
- 5.4 Eureka詳解
- 5.4.1 基礎架構
- 5.4.2 高可用的Eureka Server
- 5.4.3 服務提供者
- 5.4.4 服務消費者
- 5.4.5 失效剔除和自我保護
- 轉到第二部分
1. 系統架構
1.1 流動計算架構(SOA)
SOA :面向服務的架構
當服務越來越多,容量的評估,小服務資源的浪費等問題逐漸顯現,此時需增加一個調度中心基于訪問壓力實時管理集群容量,提高集群利用率。此時,用于提高機器利用率的資源調度和治理中心(SOA)是關鍵
以前出現了什么問題?
- 服務越來越多,需要管理每個服務的地址
- 調用關系錯綜復雜,難以理清依賴關系
- 服務過多,服務狀態難以管理,無法根據服務情況動態管理
服務治理要做什么?
- 服務注冊中心,實現服務自動注冊和發現,無需人為記錄服務地址
- 服務自動訂閱,服務列表自動推送,服務調用透明化,無需關心依賴關系
- 動態監控服務狀態監控報告,人為控制服務狀態
缺點:
- 服務間會有依賴關系,一旦某個環節出錯會影響較大
- 服務關系復雜,運維、測試部署困難,不符合DevOps思想
1.2 微服務
前面說的SOA,英文翻譯過來是面向服務。微服務,似乎也是服務,都是對系統進行拆分。因此兩者非常容易混淆,但其實卻有一些差別:
微服務的特點:
- 單一職責:微服務中每一個服務都對應唯一的業務能力,做到單一職責
- 微:微服務的服務拆分粒度很小,例如一個用戶管理就可以作為一個服務。每個服務雖小,但“五臟俱全”。
- 面向服務:面向服務是說每個服務都要對外暴露Rest風格服務接口API。并不關心服務的技術實現,做到與平臺和語言無關,也不限定用什么技術實現,只要提供Rest的接口即可。
- 自治:自治是說服務間互相獨立,互不干擾
- 團隊獨立:每個服務都是一個獨立的開發團隊,人數不能過多。
- 技術獨立:因為是面向服務,提供Rest接口,使用什么技術沒有別人干涉
- 前后端分離:采用前后端分離開發,提供統一Rest接口,后端不用再為PC、移動段開發不同接口
- 數據庫分離:每個服務都使用自己的數據源
- 部署獨立,服務間雖然有調用,但要做到服務重啟不影響其它服務。有利于持續集成和持續交付。每個服務都是獨立的組件,可復用,可替換,降低耦合,易維護
微服務結構圖:
2. 服務調用方式
2.1 RPC和HTTP
無論是微服務還是SOA,都面臨著服務間的遠程調用。服務間的遠程調用方式有
常見的遠程調用方式有以下2種:
-
RPC:Remote Produce Call遠程過程調用,類似的還有RMI。自定義數據格式,基于原生TCP通信,速度快,效率高。早期的webservice,現在熱門的dubbo,都是RPC的典型代表
-
Http:http其實是一種網絡傳輸協議,基于TCP,規定了數據傳輸的格式。現在客戶端瀏覽器與服務端通信基本都是采用Http協議,也可以用來進行遠程服務調用。缺點是消息封裝臃腫,優勢是對服務的提供和調用方沒有任何技術限定,自由靈活,更符合微服務理念。
現在熱門的Rest風格,就可以通過http協議來實現。
如果公司全部采用Java技術棧,那么使用Dubbo作為微服務架構是一個不錯的選擇。
相反,如果公司的技術棧多樣化,而且你更青睞Spring家族,那么SpringCloud搭建微服務是不二之選。在我們的項目中,我們會選擇SpringCloud套件,因此我們會使用Http方式來實現服務間調用。
2.2 Http客戶端工具
既然微服務選擇了Http,那么我們就需要考慮自己來實現對請求和響應的處理。不過開源世界已經有很多的http客戶端工具,能夠幫助我們做這些事情,例如:
- HttpClient
- OKHttp
- URLConnection
2.3 Spring的RestTemplate
Spring提供了一個RestTemplate模板工具類,對基于Http的客戶端進行了封裝,并且實現了對象與json的序列化和反序列化,非常方便。RestTemplate并沒有限定Http的客戶端類型,而是進行了抽象,目前常用的3種都有支持:
- HttpClient
- OkHttp
- JDK原生的URLConnection(默認的)
首先在項目中注冊一個RestTemplate對象,可以在啟動類位置注冊:
@SpringBootApplication public class HttpDemoApplication {public static void main(String[] args) {SpringApplication.run(HttpDemoApplication.class, args);}@Beanpublic RestTemplate restTemplate() {return new RestTemplate();} }在測試類中直接@Autowired注入:
@RunWith(SpringRunner.class) @SpringBootTest(classes = HttpDemoApplication.class) public class HttpDemoApplicationTests {@Autowiredprivate RestTemplate restTemplate;@Testpublic void httpGet() {// 調用springboot案例中的rest接口User user = this.restTemplate.getForObject("http://localhost/user/1", User.class);System.out.println(user);} }通過RestTemplate的getForObject()方法,傳遞url地址及實體類的字節碼,RestTemplate會自動發起請求,接收響應,并且對響應結果進行反序列化。
3. 初識SpringCloud
微服務是一種架構方式,最終肯定需要技術架構去實施。
微服務的實現方式很多,但是最火的莫過于Spring Cloud了。為什么?
- 后臺硬:作為Spring家族的一員,有整個Spring全家桶靠山,背景十分強大。
- 技術強:Spring作為Java領域的前輩,可以說是功力深厚。有強力的技術團隊支撐,一般人還真比不了
- 群眾基礎好:可以說大多數程序員的成長都伴隨著Spring框架,試問:現在有幾家公司開發不用Spring?SpringCloud與Spring的各個框架無縫整合,對大家來說一切都是熟悉的配方,熟悉的味道。
- 使用方便:相信大家都體會到了SpringBoot給我們開發帶來的便利,而SpringCloud完全支持SpringBoot的開發,用很少的配置就能完成微服務框架的搭建
3.1 簡介
SpringCloud是Spring旗下的項目之一,官網地址:http://projects.spring.io/spring-cloud/
Spring最擅長的就是集成,把世界上最好的框架拿過來,集成到自己的項目中。
SpringCloud也是一樣,它將現在非常流行的一些技術整合到一起,實現了諸如:配置管理,服務發現,智能路由,負載均衡,熔斷器,控制總線,集群狀態等等功能。其主要涉及的組件包括:
- Eureka:服務治理組件,包含服務注冊中心,服務注冊與發現機制的實現。(服務治理,服務注冊/發現)
- Zuul:網關組件,提供智能路由,訪問過濾功能
- Ribbon:客戶端負載均衡的服務調用組件(客戶端負載)
- Feign:服務調用,給予Ribbon和Hystrix的聲明式服務調用組件 (聲明式服務調用)
- Hystrix:容錯管理組件,實現斷路器模式,幫助服務依賴中出現的延遲和為故障提供強大的容錯能力。(熔斷、斷路器,容錯)
架構圖:
4. 微服務場景模擬
4.1 服務提供者
4.1.1 Spring腳手架創建工程
借助于Spring提供的快速搭建工具:
填寫項目信息:
添加web依賴:
添加mybatis依賴:
生成的項目結構,已經包含了引導類
依賴也已經全部自動引入:
因為要使用通用mapper,所以需要手動加一條依賴:
<dependency><groupId>tk.mybatis</groupId><artifactId>mapper-spring-boot-starter</artifactId><version>2.0.4</version></dependency>4.1.2 編寫代碼
4.1.2.1 配置
server:port: 8081 spring:datasource:url: jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghaiusername: rootpassword: 123456 mybatis:type-aliases-package: com.zh.service.pojo4.1.2.2 實體類
要導入lombok依賴
package com.zh.service.pojo; import lombok.Data;@Data public class User {private Long id;private String name;private String pwd; }4.1.2.3 UserMapper
package com.zh.service.mapper;import com.zh.service.pojo.User; import tk.mybatis.mapper.common.Mapper; @org.apache.ibatis.annotations.Mapper public interface UserMapper extends Mapper<User> { }4.1.2.4 UserService
@Service public class UserService {@Autowiredprivate UserMapper userMapper;public User queryById(Long id){return this.userMapper.selectByPrimaryKey(id);} }4.1.2.5 UserController
@Controller @RequestMapping("user") public class UserController {@Autowiredprivate UserService userService;@ResponseBody@GetMapping("{id}")public User queryById(@PathVariable Long id){return this.userService.queryById(id);} }4.1.3 測試
4.2 服務調用者
4.2.1 創建工程
和上述創建步驟類似,但是不需要mybatis相關依賴了
4.2.2 編寫代碼
首先在引導類中注冊RestTemplate:
編寫配置(application.yml):
server:port: 80編寫UserController:
@Controller @RequestMapping("consumer/user") public class UserController {@Autowiredprivate RestTemplate restTemplate;@ResponseBody@GetMappingpublic User queryUserById(@RequestParam Long id){return this.restTemplate.getForObject("http://localhost:8081/user/" + id, User.class);} }pojo對象(User):
@Data public class User {private Long id;private String name;private String pwd; }4.2.3 測試
4.3 問題總結
存在什么問題?
- 在consumer中,我們把url地址硬編碼到了代碼中,不方便后期維護
- consumer需要記憶provider的地址,如果出現變更,可能得不到通知,地址將失效
- consumer不清楚provider的狀態,服務宕機也不知道
- provider只有1臺服務,不具備高可用性
- 即便provider形成集群,consumer還需自己實現負載均衡
其實上面說的問題,概括一下就是分布式服務必然要面臨的問題:
- 服務管理
- 如何自動注冊和發現
- 如何實現狀態監管
- 如何實現動態路由
- 服務如何實現負載均衡
- 服務如何解決容災問題
- 服務如何實現統一配置
5. Eureka注冊中心
5.1 認識Eureka
Eureka就好比是滴滴,負責管理、記錄服務提供者的信息。服務調用者無需自己尋找服務,而是把自己的需求告訴Eureka,然后Eureka會把符合你需求的服務告訴你。
同時,服務提供方與Eureka之間通過“心跳”機制進行監控,當某個服務提供方出現問題,Eureka自然會把它從服務列表中剔除。
這就實現了服務的自動注冊、發現、狀態監控
5.2 原理圖
- Eureka:就是服務注冊中心(可以是一個集群),對外暴露自己的地址- 提供者:啟動后向Eureka注冊自己信息(地址,提供什么服務)
- 消費者:向Eureka訂閱服務,Eureka會將對應服務的所有提供者地址列表發送給消費者,并且定期更新
- 心跳(續約):提供者定期通過http方式向Eureka刷新自己的狀態
5.3 入門案例
5.3.1 搭建EurekaServer
創建一個項目,啟動一個EurekaServer:
依然使用spring提供的快速搭建工具:
選擇依賴:EurekaServer-服務注冊中心依賴,Eureka Discovery-服務提供方和服務消費方。因為,對于eureka來說:服務提供方和服務消費方都屬于客戶端
編寫application.yml配置:
修改引導類,在類上添加@EnableEurekaServer注解:
@SpringBootApplication @EnableEurekaServer // 聲明當前springboot應用是一個eureka服務中心 public class ItcastEurekaApplication {public static void main(String[] args) {SpringApplication.run(ItcastEurekaApplication.class, args);} }啟動服務
5.3.2 注冊到Eureka
注冊服務,就是在服務上添加Eureka的客戶端依賴,客戶端代碼會自動把服務注冊到EurekaServer中。
修改itcast-service-provider工程
具體操作
5.3.2.1 pom.xml
先添加SpringCloud依賴:
<!-- SpringCloud的依賴 --> <dependencyManagement><dependencies><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-dependencies</artifactId><version>Hoxton.SR7</version><type>pom</type><scope>import</scope></dependency></dependencies> </dependencyManagement>然后是eureka的客戶端
<!-- Eureka客戶端 --> <dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency>5.3.2.2 application.yml
server:port: 8081 spring:datasource:url: jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghaiusername: rootpassword: 123456application:name: service-provider mybatis:type-aliases-package: com.zh.service.pojo eureka:client:service-url:defaultZone: http://127.0.0.1:10086/eureka5.3.2.3 引導類
@SpringBootApplication @EnableDiscoveryClient //開啟Eureka客戶端功能 public class ServiceProviderApplication {public static void main(String[] args) {SpringApplication.run(ServiceProviderApplication.class, args);}}重啟項目,訪問Eureka監控頁面查看
service-provider服務已經注冊成功
5.3.3 從Eureka獲取服務
接下來修改service-consumer,嘗試從EurekaServer獲取服務。
方法與消費者類似,只需要在項目中添加EurekaClient依賴,就可以通過服務名稱來獲取信息了!
先添加SpringCloud依賴:
然后是eureka的客戶端
<!-- Eureka客戶端 --> <dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency>修改配置
server:port: 80 spring:application:name: service-consumer eureka:client:service-url:defaultZone: http://localhost:10086/eureka在啟動類開啟Eureka客戶端
@SpringBootApplication @EnableDiscoveryClient public class ServiceConsumerApplication {@Beanpublic RestTemplate restTemplate(){return new RestTemplate();}public static void main(String[] args) {SpringApplication.run(ServiceConsumerApplication.class, args);}}修改UserController代碼,用DiscoveryClient類的方法,根據服務名稱,獲取服務實例:
@Controller @RequestMapping("consumer/user") public class UserController {@Autowiredprivate RestTemplate restTemplate;@Autowiredprivate DiscoveryClient discoveryClient; // eureka客戶端,可以獲取到eureka中服務的信息@ResponseBody@GetMappingpublic User queryUserById(@RequestParam Long id){//根據服務名稱獲取服務實例,有可能是集群,所以是service集合List<ServiceInstance> instances = discoveryClient.getInstances("service-provider");//因為只有一個service-provider 所以獲取第一個實例ServiceInstance serviceInstance = instances.get(0);//獲取ip和端口信息,拼接成服務地址return this.restTemplate.getForObject("http://"+serviceInstance.getHost() +":"+serviceInstance.getPort()+"/user/" + id, User.class);} }啟動測試
5.4 Eureka詳解
5.4.1 基礎架構
Eureka架構中的三個核心角色:
-
服務注冊中心
Eureka的服務端應用,提供服務注冊和發現功能,就是剛剛我們建立的itcast-eureka。
-
服務提供者
提供服務的應用,可以是SpringBoot應用,也可以是其它任意技術實現,只要對外提供的是Rest風格服務即可。本例中就是我們實現的itcast-service-provider。
-
服務消費者
消費應用從注冊中心獲取服務列表,從而得知每個服務方的信息,知道去哪里調用服務方。本例中就是我們實現的itcast-service-consumer。
5.4.2 高可用的Eureka Server
Eureka Server即服務的注冊中心,在剛才的案例中,我們只有一個EurekaServer,事實上EurekaServer也可以是一個集群,形成高可用的Eureka中心。
服務同步
多個Eureka Server之間也會互相注冊為服務,當服務提供者注冊到Eureka Server集群中的某個節點時,該節點會把服務的信息同步給集群中的每個節點,從而實現數據同步。因此,無論客戶端訪問到Eureka Server集群中的任意一個節點,都可以獲取到完整的服務列表信息。
動手搭建高可用的EurekaServer
我們假設要運行兩個EurekaServer的集群,端口分別為:10086和10087。只需要把zh-eureka啟動兩次即可。
1)啟動第一個eurekaServer,我們修改原來的EurekaServer配置:
server:port: 10086 # 端口 spring:application:name: eureka-server # 應用名稱,會在Eureka中顯示 eureka:client:service-url: # 配置其他Eureka服務的地址,而不是自己,比如10087defaultZone: http://127.0.0.1:10087/eureka所謂的高可用注冊中心,其實就是把EurekaServer自己也作為一個服務進行注冊,這樣多個EurekaServer之間就能互相發現對方,從而形成集群。因此我們做了以下修改:
- 把service-url的值改成了另外一臺EurekaServer的地址,而不是自己
啟動報錯,很正常。因為10087服務沒有啟動:
2)啟動第二個eurekaServer,再次修改itcast-eureka的配置:
server:port: 10087 # 端口 spring:application:name: eureka-server # 應用名稱,會在Eureka中顯示 eureka:client:service-url: # 配置其他Eureka服務的地址,而不是自己,比如10087defaultZone: 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
5.4.3 服務提供者
服務提供者要向EurekaServer注冊服務,并且完成服務續約等工作。
服務注冊
服務提供者在啟動時,會檢測配置屬性中的:eureka.client.register-with-eureka=true參數是否正確,事實上默認就是true。如果值確實為true,則會向EurekaServer發起一個Rest請求,并攜帶自己的元數據信息,Eureka Server會把這些信息保存到一個雙層Map結構中。
- 第一層Map的Key就是服務id,一般是配置中的spring.application.name屬性
- 第二層Map的key是服務的實例id。一般host+ serviceId + port,例如:locahost:service-provider:8081
- 值則是服務的實例對象,也就是說一個服務,可以同時啟動多個不同實例,形成集群。
服務續約
在注冊服務完成以后,服務提供者會維持一個心跳(定時向EurekaServer發起Rest請求),告訴EurekaServer:“我還活著”。這個我們稱為服務的續約(renew);
有兩個重要參數可以修改服務續約的行為:
eureka:instance:lease-expiration-duration-in-seconds: 90lease-renewal-interval-in-seconds: 30- lease-renewal-interval-in-seconds:服務續約(renew)的間隔,默認為30秒
- lease-expiration-duration-in-seconds:服務失效時間,默認值90秒
也就是說,默認情況下每個30秒服務會向注冊中心發送一次心跳,證明自己還活著。如果超過90秒沒有發送心跳,EurekaServer就會認為該服務宕機,會從服務列表中移除,這兩個值在生產環境不要修改,默認即可。
但是在開發時,這個值有點太長了,經常我們關掉一個服務,會發現Eureka依然認為服務在活著。所以我們在開發階段可以適當調小。
eureka:instance:lease-expiration-duration-in-seconds: 10 # 10秒即過期lease-renewal-interval-in-seconds: 5 # 5秒一次心跳5.4.4 服務消費者
獲取服務列表
當服務消費者啟動時,會檢測eureka.client.fetch-registry=true參數的值,如果為true,則會拉取Eureka Server服務的列表只讀備份,然后緩存在本地。并且每隔30秒會重新獲取并更新數據。我們可以通過下面的參數來修改:
eureka:client:registry-fetch-interval-seconds: 5生產環境中,我們不需要修改這個值。
但是為了開發環境下,能夠快速得到服務的最新狀態,我們可以將其設置小一點。
5.4.5 失效剔除和自我保護
服務下線
當服務進行正常關閉操作時,它會觸發一個服務下線的REST請求給Eureka Server,告訴服務注冊中心:“我要下線了”。服務中心接受到請求之后,將該服務置為下線狀態。
失效剔除
有些時候,我們的服務提供方并不一定會正常下線,可能因為內存溢出、網絡故障等原因導致服務無法正常工作。Eureka Server需要將這樣的服務剔除出服務列表。因此它會開啟一個定時任務,每隔60秒對所有失效的服務(超過90秒未響應)進行剔除。
可以通過eureka.server.eviction-interval-timer-in-ms參數對其進行修改,單位是毫秒,生產環境不要修改。
這個會對我們開發帶來極大的不變,你對服務重啟,隔了60秒Eureka才反應過來。開發階段可以適當調整,比如:10秒
自我保護
我們關停一個服務,就會在Eureka面板看到一條警告:
這是觸發了Eureka的自我保護機制。當一個服務未按時進行心跳續約時,Eureka會統計最近15分鐘心跳失敗的服務實例的比例是否超過了85%。在生產環境下,因為網絡延遲等原因,心跳失敗實例的比例很有可能超標,但是此時就把服務剔除列表并不妥當,因為服務可能沒有宕機。Eureka就會把當前實例的注冊信息保護起來,不予剔除。生產環境下這很有效,保證了大多數服務依然可用。
但是這給我們的開發帶來了麻煩, 因此開發階段我們都會關閉自我保護模式:(itcast-eureka)
eureka:server:enable-self-preservation: false # 關閉自我保護模式(缺省為打開)eviction-interval-timer-in-ms: 1000 # 掃描失效服務的間隔時間(缺省為60*1000ms)轉到第二部分
總結
以上是生活随笔為你收集整理的SpringCloud(第一部分)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: SpringCloud(第二部分)
- 下一篇: java.lang.ClassNotFo