day02-認識微服務.md
0.學習目標
- 瞭解系統架構的演變
- 瞭解RPC與Http的區別
- 掌握HttpClient的簡單使用
- 知道什麼是SpringCloud
- 獨立搭建Eureka註冊中心
- 獨立配置Robbin負載均衡
1.系統架構演變
隨著網際網路的發展,網站應用的規模不斷擴大。需求的激增,帶來的是技術上的壓力。系統架構也因此也不斷的演進、升級、迭代。從單一應用,到垂直拆分,到分散式服務,到SOA,以及現在火熱的微服務架構,還有在Google帶領下來勢洶湧的Service Mesh。我們到底是該乘坐微服務的船隻駛向遠方,還是偏安一隅得過且過?
其實生活不止眼前的苟且,還有詩和遠方。所以我們今天就回顧歷史,看一看系統架構演變的歷程;把握現在,學習現在最火的技術架構;展望未來,爭取成為一名優秀的Java工程師。
1.1. 集中式架構
當網站流量很小時,只需一個應用,將所有功能都部署在一起,以減少部署節點和成本。此時,用於簡化增刪改查工作量的資料訪問框架(ORM)是影響專案開發的關鍵。
存在的問題:
- 程式碼耦合,開發維護困難
- 無法針對不同模組進行鍼對性優化
- 無法水平擴充套件
- 單點容錯率低,併發能力差
1.2.垂直拆分
當訪問量逐漸增大,單一應用無法滿足需求,此時為了應對更高的併發和業務需求,我們根據業務功能對系統進行拆分:
優點:
- 系統拆分實現了流量分擔,解決了併發問題
- 可以針對不同模組進行優化
- 方便水平擴充套件,負載均衡,容錯率提高
缺點:
- 系統間相互獨立,會有很多重複開發工作,影響開發效率
1.3.分散式服務
當垂直應用越來越多,應用之間互動不可避免,將核心業務抽取出來,作為獨立的服務,逐漸形成穩定的服務中心,使前端應用能更快速的響應多變的市場需求。此時,用於提高業務複用及整合的分散式呼叫是關鍵。
優點:
- 將基礎服務進行了抽取,系統間相互呼叫,提高了程式碼複用和開發效率
缺點:
- 系統間耦合度變高,呼叫關係錯綜複雜,難以維護
1.4.服務治理(SOA)
SOA :面向服務的架構
當服務越來越多,容量的評估,小服務資源的浪費等問題逐漸顯現,此時需增加一個排程中心基於訪問壓力實時管理叢集容量,提高叢集利用率。此時,用於提高機器利用率的資源排程和治理中心(SOA)是關鍵
以前出現了什麼問題?
- 服務越來越多,需要管理每個服務的地址
- 呼叫關係錯綜複雜,難以理清依賴關係
- 服務過多,服務狀態難以管理,無法根據服務情況動態管理
服務治理要做什麼?
- 服務註冊中心,實現服務自動註冊和發現,無需人為記錄服務地址
- 服務自動訂閱,服務列表自動推送,服務呼叫透明化,無需關心依賴關係
- 動態監控服務狀態監控報告,人為控制服務狀態
缺點:
- 服務間會有依賴關係,一旦某個環節出錯會影響較大
- 服務關係複雜,運維、測試部署困難,不符合DevOps思想
1.5.微服務
前面說的SOA,英文翻譯過來是面向服務。微服務,似乎也是服務,都是對系統進行拆分。因此兩者非常容易混淆,但其實缺有一些差別:
微服務的特點:
- 單一職責:微服務中每一個服務都對應唯一的業務能力,做到單一職責
- 微:微服務的服務拆分粒度很小,例如一個使用者管理就可以作為一個服務。每個服務雖小,但“五臟俱全”。
- 面向服務:面向服務是說每個服務都要對外暴露Rest風格服務介面API。並不關心服務的技術實現,做到與平臺和語言無關,也不限定用什麼技術實現,只要提供Rest的介面即可。
- 自治:自治是說服務間互相獨立,互不干擾
- 團隊獨立:每個服務都是一個獨立的開發團隊,人數不能過多。
- 技術獨立:因為是面向服務,提供Rest介面,使用什麼技術沒有別人干涉
- 前後端分離:採用前後端分離開發,提供統一Rest介面,後端不用再為PC、移動段開發不同介面
- 資料庫分離:每個服務都使用自己的資料來源
- 部署獨立,服務間雖然有呼叫,但要做到服務重啟不影響其它服務。有利於持續整合和持續交付。每個服務都是獨立的元件,可複用,可替換,降低耦合,易維護
微服務結構圖:
2.遠端呼叫方式
無論是微服務還是SOA,都面臨著服務間的遠端呼叫。那麼服務間的遠端呼叫方式有哪些呢?
常見的遠端呼叫方式有以下幾種:
-
RPC:Remote Produce Call遠端過程呼叫,類似的還有RMI。自定義資料格式,基於原生TCP通訊,速度快,效率高。早期的webservice,現在熱門的dubbo,都是RPC的典型
-
Http:http其實是一種網路傳輸協議,基於TCP,規定了資料傳輸的格式。現在客戶端瀏覽器與服務端通訊基本都是採用Http協議。也可以用來進行遠端服務呼叫。缺點是訊息封裝臃腫。
現在熱門的Rest風格,就可以通過http協議來實現。
2.1.認識RPC
RPC,即 Remote Procedure Call(遠端過程呼叫),是一個計算機通訊協議。 該協議允許運行於一臺計算機的程式呼叫另一臺計算機的子程式,而程式設計師無需額外地為這個互動作用程式設計。說得通俗一點就是:A計算機提供一個服務,B計算機可以像呼叫自己的本地服務那樣呼叫A計算機的服務。
通過上面的概念,我們可以知道,實現RPC主要是做到兩點:
- 實現遠端呼叫其他計算機的服務
- 要實現遠端呼叫,肯定是通過網路傳輸資料。A程式提供服務,B程式通過網路將請求引數傳遞給A,A本地執行後得到結果,再將結果返回給B程式。這裡需要關注的有兩點:
- 1)採用何種網路通訊協議?
- 現在比較流行的RPC框架,都會採用TCP作為底層傳輸協議
- 2)資料傳輸的格式怎樣?
- 兩個程式進行通訊,必須約定好資料傳輸格式。就好比兩個人聊天,要用同一種語言,否則無法溝通。所以,我們必須定義好請求和響應的格式。另外,資料在網路中傳輸需要進行序列化,所以還需要約定統一的序列化的方式。
- 1)採用何種網路通訊協議?
- 要實現遠端呼叫,肯定是通過網路傳輸資料。A程式提供服務,B程式通過網路將請求引數傳遞給A,A本地執行後得到結果,再將結果返回給B程式。這裡需要關注的有兩點:
- **像呼叫本地服務一樣呼叫遠端服務 **
- 如果僅僅是遠端呼叫,還不算是RPC,因為RPC強調的是過程呼叫,呼叫的過程對使用者而言是應該是透明的,使用者不應該關心呼叫的細節,可以像呼叫本地服務一樣呼叫遠端服務。所以RPC一定要對呼叫的過程進行封裝
RPC呼叫流程圖:
想要了解詳細的RPC實現,給大家推薦一篇文章:自己動手實現RPC
2.2.認識Http
Http協議:超文字傳輸協議,是一種應用層協議。規定了網路傳輸的請求格式、響應格式、資源定位和操作的方式等。但是底層採用什麼網路傳輸協議,並沒有規定,不過現在都是採用TCP協議作為底層傳輸協議。說到這裡,大家可能覺得,Http與RPC的遠端呼叫非常像,都是按照某種規定好的資料格式進行網路通訊,有請求,有響應。沒錯,在這點來看,兩者非常相似,但是還是有一些細微差別。
- RPC並沒有規定資料傳輸格式,這個格式可以任意指定,不同的RPC協議,資料格式不一定相同。
- Http中還定義了資源定位的路徑,RPC中並不需要
- 最重要的一點:RPC需要滿足像呼叫本地服務一樣呼叫遠端服務,也就是對呼叫過程在API層面進行封裝。Http協議沒有這樣的要求,因此請求、響應等細節需要我們自己去實現。
- 優點:RPC方式更加透明,對使用者更方便。Http方式更靈活,沒有規定API和語言,跨語言、跨平臺
- 缺點:RPC方式需要在API層面進行封裝,限制了開發的語言環境。
例如我們通過瀏覽器訪問網站,就是通過Http協議。只不過瀏覽器把請求封裝,發起請求以及接收響應,解析響應的事情都幫我們做了。如果是不通過瀏覽器,那麼這些事情都需要自己去完成。
2.3.如何選擇?
既然兩種方式都可以實現遠端呼叫,我們該如何選擇呢?
- 速度來看,RPC要比http更快,雖然底層都是TCP,但是http協議的資訊往往比較臃腫,不過可以採用gzip壓縮。
- 難度來看,RPC實現較為複雜,http相對比較簡單
- 靈活性來看,http更勝一籌,因為它不關心實現細節,跨平臺、跨語言。
因此,兩者都有不同的使用場景:
- 如果對效率要求更高,並且開發過程使用統一的技術棧,那麼用RPC還是不錯的。
- 如果需要更加靈活,跨語言、跨平臺,顯然http更合適
那麼我們該怎麼選擇呢?
微服務,更加強調的是獨立、自治、靈活。而RPC方式的限制較多,因此微服務框架中,一般都會採用基於Http的Rest風格服務。
3.Http客戶端工具
既然微服務選擇了Http,那麼我們就需要考慮自己來實現對請求和響應的處理。不過開源世界已經有很多的http客戶端工具,能夠幫助我們做這些事情,例如:
- HttpClient
- OKHttp
- URLConnection
接下來,我們就一起了解一款比較流行的客戶端工具:HttpClient
3.1.HttpClient
3.1.1.介紹
HttpClient是Apache公司的產品,是Http Components下的一個元件。
特點:
- 基於標準、純淨的Java語言。實現了Http1.0和Http1.1
- 以可擴充套件的面向物件的結構實現了Http全部的方法(GET, POST, PUT, DELETE, HEAD, OPTIONS, and TRACE)
- 支援HTTPS協議。
- 通過Http代理建立透明的連線。
- 自動處理Set-Cookie中的Cookie。
Rest風格:
- 查詢:GET,/user/12
- 新增:POST, /user
- 修改:PUT, /user
- 刪除:DELTE, /user/12
3.1.2.使用
我們匯入課前資料提供的demo工程:《http-demo》
發起get請求:
@Test
public void testGet() throws IOException {
HttpGet request = new HttpGet("http://www.baidu.com");
String response = this.httpClient.execute(request, new BasicResponseHandler());
System.out.println(response);
}
發起Post請求:
@Test
public void testPost() throws IOException {
HttpPost request = new HttpPost("http://www.oschina.net/");
request.setHeader("User-Agent",
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36");
String response = this.httpClient.execute(request, new BasicResponseHandler());
System.out.println(response);
}
這個介面返回一個User物件
@Test
public void testGetPojo() throws IOException {
HttpGet request = new HttpGet("http://localhost/hello/2");
String response = this.httpClient.execute(request, new BasicResponseHandler());
System.out.println(response);
}
我們實際得到的是一個json字串:
{
"id": 8,
"userName": "liuyan",
"password": "123456",
"name": "柳巖",
"age": 21,
"sex": 2,
"birthday": "1995-08-07T16:00:00.000+0000",
"created": "2014-09-20T03:41:15.000+0000",
"updated": "2014-09-20T03:41:15.000+0000",
"note": "柳巖同學在傳智播客學表演"
}
如果想要得到物件,我們還需要手動進行Json反序列化,這一點比較麻煩。
3.1.3.Json轉換工具
HttpClient請求資料後是json字串,需要我們自己把Json字串反序列化為物件,我們會使用JacksonJson工具來實現。
JacksonJson
是SpringMVC內建的json處理工具,其中有一個ObjectMapper
類,可以方便的實現對json的處理:
物件轉json
// json處理工具
private ObjectMapper mapper = new ObjectMapper();
@Test
public void testJson() throws JsonProcessingException {
User user = new User();
user.setId(8L);
user.setAge(21);
user.setName("柳巖");
user.setUserName("liuyan");
// 序列化
String json = mapper.writeValueAsString(user);
System.out.println("json = " + json);
}
結果:
json轉普通物件
// json處理工具
private ObjectMapper mapper = new ObjectMapper();
@Test
public void testJson() throws IOException {
User user = new User();
user.setId(8L);
user.setAge(21);
user.setName("柳巖");
user.setUserName("liuyan");
// 序列化
String json = mapper.writeValueAsString(user);
// 反序列化,接收兩個引數:json資料,反序列化的目標類位元組碼
User result = mapper.readValue(json, User.class);
System.out.println("result = " + result);
}
結果:
json轉集合
json轉集合比較麻煩,因為你無法同時把集合的class和元素的class同時傳遞到一個引數。
因此Jackson做了一個型別工廠,用來解決這個問題:
// json處理工具
private ObjectMapper mapper = new ObjectMapper();
@Test
public void testJson() throws IOException {
User user = new User();
user.setId(8L);
user.setAge(21);
user.setName("柳巖");
user.setUserName("liuyan");
// 序列化,得到物件集合的json字串
String json = mapper.writeValueAsString(Arrays.asList(user, user));
// 反序列化,接收兩個引數:json資料,反序列化的目標類位元組碼
List<User> users = mapper.readValue(json, mapper.getTypeFactory().constructCollectionType(List.class, User.class));
for (User u : users) {
System.out.println("u = " + u);
}
}
結果:
json轉任意複雜型別
當物件泛型關係複雜時,型別工廠也不好使了。這個時候Jackson提供了TypeReference來接收型別泛型,然後底層通過反射來獲取泛型上的具體型別。實現資料轉換。
// json處理工具
private ObjectMapper mapper = new ObjectMapper();
@Test
public void testJson() throws IOException {
User user = new User();
user.setId(8L);
user.setAge(21);
user.setName("柳巖");
user.setUserName("liuyan");
// 序列化,得到物件集合的json字串
String json = mapper.writeValueAsString(Arrays.asList(user, user));
// 反序列化,接收兩個引數:json資料,反序列化的目標類位元組碼
List<User> users = mapper.readValue(json, new TypeReference<List<User>>(){});
for (User u : users) {
System.out.println("u = " + u);
}
}
結果:
3.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);
}
@Bean
public RestTemplate restTemplate() {
// 預設的RestTemplate,底層是走JDK的URLConnection方式。
return new RestTemplate();
}
}
在測試類中直接@Autowired
注入:
@RunWith(SpringRunner.class)
@SpringBootTest(classes = HttpDemoApplication.class)
public class HttpDemoApplicationTests {
@Autowired
private RestTemplate restTemplate;
@Test
public void httpGet() {
User user = this.restTemplate.getForObject("http://localhost/hello/2", User.class);
System.out.println(user);
}
}
- 通過RestTemplate的getForObject()方法,傳遞url地址及實體類的位元組碼,RestTemplate會自動發起請求,接收響應,並且幫我們對響應結果進行反序列化。
學習完了Http客戶端工具,接下來就可以正式學習微服務了。
4.初始SpringCloud
微服務是一種架構方式,最終肯定需要技術架構去實施。
微服務的實現方式很多,但是最火的莫過於Spring Cloud了。為什麼?
- 後臺硬:作為Spring家族的一員,有整個Spring全家桶靠山,背景十分強大。
- 技術強:Spring作為Java領域的前輩,可以說是功力深厚。有強力的技術團隊支撐,一般人還真比不了
- 群眾基礎好:可以說大多數程式設計師的成長都伴隨著Spring框架,試問:現在有幾家公司開發不用Spring?SpringCloud與Spring的各個框架無縫整合,對大家來說一切都是熟悉的配方,熟悉的味道。
- 使用方便:相信大家都體會到了SpringBoot給我們開發帶來的便利,而SpringCloud完全支援SpringBoot的開發,用很少的配置就能完成微服務框架的搭建
4.1.簡介
Spring最擅長的就是整合,把世界上最好的框架拿過來,整合到自己的專案中。
SpringCloud也是一樣,它將現在非常流行的一些技術整合到一起,實現了諸如:配置管理,服務發現,智慧路由,負載均衡,熔斷器,控制匯流排,叢集狀態等等功能。其主要涉及的元件包括:
netflix
- Eureka:註冊中心
- Zuul:服務閘道器
- Ribbon:負載均衡
- Feign:服務呼叫
- Hystix:熔斷器
以上只是其中一部分,架構圖:
4.2.版本
SpringCloud的版本命名比較特殊,因為它不是一個元件,而是許多元件的集合,它的命名是以A到Z的為首字母的一些單片語成:(版本名稱都是倫敦地鐵站名稱)
我們在專案中,會是以Finchley的版本。
其中包含的元件,也都有各自的版本,如下表:
Component | Edgware.SR3 | Finchley.RC1 | Finchley.BUILD-SNAPSHOT |
---|---|---|---|
spring-cloud-aws | 1.2.2.RELEASE | 2.0.0.RC1 | 2.0.0.BUILD-SNAPSHOT |
spring-cloud-bus | 1.3.2.RELEASE | 2.0.0.RC1 | 2.0.0.BUILD-SNAPSHOT |
spring-cloud-cli | 1.4.1.RELEASE | 2.0.0.RC1 | 2.0.0.BUILD-SNAPSHOT |
spring-cloud-commons | 1.3.3.RELEASE | 2.0.0.RC1 | 2.0.0.BUILD-SNAPSHOT |
spring-cloud-contract | 1.2.4.RELEASE | 2.0.0.RC1 | 2.0.0.BUILD-SNAPSHOT |
spring-cloud-config | 1.4.3.RELEASE | 2.0.0.RC1 | 2.0.0.BUILD-SNAPSHOT |
spring-cloud-netflix | 1.4.4.RELEASE | 2.0.0.RC1 | 2.0.0.BUILD-SNAPSHOT |
spring-cloud-security | 1.2.2.RELEASE | 2.0.0.RC1 | 2.0.0.BUILD-SNAPSHOT |
spring-cloud-cloudfoundry | 1.1.1.RELEASE | 2.0.0.RC1 | 2.0.0.BUILD-SNAPSHOT |
spring-cloud-consul | 1.3.3.RELEASE | 2.0.0.RC1 | 2.0.0.BUILD-SNAPSHOT |
spring-cloud-sleuth | 1.3.3.RELEASE | 2.0.0.RC1 | 2.0.0.BUILD-SNAPSHOT |
spring-cloud-stream | Ditmars.SR3 | Elmhurst.RELEASE | Elmhurst.BUILD-SNAPSHOT |
spring-cloud-zookeeper | 1.2.1.RELEASE | 2.0.0.RC1 | 2.0.0.BUILD-SNAPSHOT |
spring-boot | 1.5.10.RELEASE | 2.0.1.RELEASE | 2.0.0.BUILD-SNAPSHOT |
spring-cloud-task | 1.2.2.RELEASE | 2.0.0.RC1 | 2.0.0.RELEASE |
spring-cloud-vault | 1.1.0.RELEASE | 2.0.0.RC1 | 2.0.0.BUILD-SNAPSHOT |
spring-cloud-gateway | 1.0.1.RELEASE | 2.0.0.RC1 | 2.0.0.BUILD-SNAPSHOT |
spring-cloud-openfeign | 2.0.0.RC1 | 2.0.0.BUILD-SNAPSHOT |
接下來,我們就一一學習SpringCloud中的重要元件。
5.微服務場景模擬
首先,我們需要模擬一個服務呼叫的場景。方便後面學習微服務架構
5.1.建立父工程
編寫專案資訊:
編寫儲存位置:
然後將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">
<modelVersion>4.0.0</modelVersion>
<groupId>cn.itcast.demo</groupId>
<artifactId>cloud-demo</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>pom</packaging>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.1.RELEASE</version>
<relativePath/>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<spring-cloud.version>Finchley.RC1</spring-cloud.version>
<mybatis.starter.version>1.3.2</mybatis.starter.version>
<mapper.starter.version>2.0.2</mapper.starter.version>
<druid.starter.version>1.1.9</druid.starter.version>
<mysql.version>5.1.32</mysql.version>
<pageHelper.starter.version>1.2.3</pageHelper.starter.version>
<leyou.latest.version>1.0.0-SNAPSHOT</leyou.latest.version>
</properties>
<dependencyManagement>
<dependencies>
<!-- springCloud -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- mybatis啟動器 -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>${mybatis.starter.version}</version>
</dependency>
<!-- 通用Mapper啟動器 -->
<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper-spring-boot-starter</artifactId>
<version>${mapper.starter.version}</version>
</dependency>
<!-- 分頁助手啟動器 -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>${pageHelper.starter.version}</version>
</dependency>
<!-- mysql驅動 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<