1. 程式人生 > >day02-認識微服務.md

day02-認識微服務.md

0.學習目標

  • 瞭解系統架構的演變
  • 瞭解RPC與Http的區別
  • 掌握HttpClient的簡單使用
  • 知道什麼是SpringCloud
  • 獨立搭建Eureka註冊中心
  • 獨立配置Robbin負載均衡

1.系統架構演變

隨著網際網路的發展,網站應用的規模不斷擴大。需求的激增,帶來的是技術上的壓力。系統架構也因此也不斷的演進、升級、迭代。從單一應用,到垂直拆分,到分散式服務,到SOA,以及現在火熱的微服務架構,還有在Google帶領下來勢洶湧的Service Mesh。我們到底是該乘坐微服務的船隻駛向遠方,還是偏安一隅得過且過?

其實生活不止眼前的苟且,還有詩和遠方。所以我們今天就回顧歷史,看一看系統架構演變的歷程;把握現在,學習現在最火的技術架構;展望未來,爭取成為一名優秀的Java工程師。

1.1. 集中式架構

當網站流量很小時,只需一個應用,將所有功能都部署在一起,以減少部署節點和成本。此時,用於簡化增刪改查工作量的資料訪問框架(ORM)是影響專案開發的關鍵。

1525529091749

存在的問題:

  • 程式碼耦合,開發維護困難
  • 無法針對不同模組進行鍼對性優化
  • 無法水平擴充套件
  • 單點容錯率低,併發能力差

1.2.垂直拆分

當訪問量逐漸增大,單一應用無法滿足需求,此時為了應對更高的併發和業務需求,我們根據業務功能對系統進行拆分:

1525529671801

優點:

  • 系統拆分實現了流量分擔,解決了併發問題
  • 可以針對不同模組進行優化
  • 方便水平擴充套件,負載均衡,容錯率提高

缺點:

  • 系統間相互獨立,會有很多重複開發工作,影響開發效率

1.3.分散式服務

當垂直應用越來越多,應用之間互動不可避免,將核心業務抽取出來,作為獨立的服務,逐漸形成穩定的服務中心,使前端應用能更快速的響應多變的市場需求。此時,用於提高業務複用及整合的分散式呼叫是關鍵。

1525530657919

優點:

  • 將基礎服務進行了抽取,系統間相互呼叫,提高了程式碼複用和開發效率

缺點:

  • 系統間耦合度變高,呼叫關係錯綜複雜,難以維護

1.4.服務治理(SOA)

SOA :面向服務的架構

當服務越來越多,容量的評估,小服務資源的浪費等問題逐漸顯現,此時需增加一個排程中心基於訪問壓力實時管理叢集容量,提高叢集利用率。此時,用於提高機器利用率的資源排程和治理中心(SOA)是關鍵

1525530804753

以前出現了什麼問題?

  • 服務越來越多,需要管理每個服務的地址
  • 呼叫關係錯綜複雜,難以理清依賴關係
  • 服務過多,服務狀態難以管理,無法根據服務情況動態管理

服務治理要做什麼?

  • 服務註冊中心,實現服務自動註冊和發現,無需人為記錄服務地址
  • 服務自動訂閱,服務列表自動推送,服務呼叫透明化,無需關心依賴關係
  • 動態監控服務狀態監控報告,人為控制服務狀態

缺點:

  • 服務間會有依賴關係,一旦某個環節出錯會影響較大
  • 服務關係複雜,運維、測試部署困難,不符合DevOps思想

1.5.微服務

前面說的SOA,英文翻譯過來是面向服務。微服務,似乎也是服務,都是對系統進行拆分。因此兩者非常容易混淆,但其實缺有一些差別:

1525532344817

微服務的特點:

  • 單一職責:微服務中每一個服務都對應唯一的業務能力,做到單一職責
  • 微:微服務的服務拆分粒度很小,例如一個使用者管理就可以作為一個服務。每個服務雖小,但“五臟俱全”。
  • 面向服務:面向服務是說每個服務都要對外暴露Rest風格服務介面API。並不關心服務的技術實現,做到與平臺和語言無關,也不限定用什麼技術實現,只要提供Rest的介面即可。
  • 自治:自治是說服務間互相獨立,互不干擾
    • 團隊獨立:每個服務都是一個獨立的開發團隊,人數不能過多。
    • 技術獨立:因為是面向服務,提供Rest介面,使用什麼技術沒有別人干涉
    • 前後端分離:採用前後端分離開發,提供統一Rest介面,後端不用再為PC、移動段開發不同介面
    • 資料庫分離:每個服務都使用自己的資料來源
    • 部署獨立,服務間雖然有呼叫,但要做到服務重啟不影響其它服務。有利於持續整合和持續交付。每個服務都是獨立的元件,可複用,可替換,降低耦合,易維護

微服務結構圖:

1526860071166

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)資料傳輸的格式怎樣?
        • 兩個程式進行通訊,必須約定好資料傳輸格式。就好比兩個人聊天,要用同一種語言,否則無法溝通。所以,我們必須定義好請求和響應的格式。另外,資料在網路中傳輸需要進行序列化,所以還需要約定統一的序列化的方式。
  • **像呼叫本地服務一樣呼叫遠端服務 **
    • 如果僅僅是遠端呼叫,還不算是RPC,因為RPC強調的是過程呼叫,呼叫的過程對使用者而言是應該是透明的,使用者不應該關心呼叫的細節,可以像呼叫本地服務一樣呼叫遠端服務。所以RPC一定要對呼叫的過程進行封裝

RPC呼叫流程圖:

1525568965976

想要了解詳細的RPC實現,給大家推薦一篇文章:自己動手實現RPC

2.2.認識Http

Http協議:超文字傳輸協議,是一種應用層協議。規定了網路傳輸的請求格式、響應格式、資源定位和操作的方式等。但是底層採用什麼網路傳輸協議,並沒有規定,不過現在都是採用TCP協議作為底層傳輸協議。說到這裡,大家可能覺得,Http與RPC的遠端呼叫非常像,都是按照某種規定好的資料格式進行網路通訊,有請求,有響應。沒錯,在這點來看,兩者非常相似,但是還是有一些細微差別。

  • RPC並沒有規定資料傳輸格式,這個格式可以任意指定,不同的RPC協議,資料格式不一定相同。
  • Http中還定義了資源定位的路徑,RPC中並不需要
  • 最重要的一點:RPC需要滿足像呼叫本地服務一樣呼叫遠端服務,也就是對呼叫過程在API層面進行封裝。Http協議沒有這樣的要求,因此請求、響應等細節需要我們自己去實現。
    • 優點:RPC方式更加透明,對使用者更方便。Http方式更靈活,沒有規定API和語言,跨語言、跨平臺
    • 缺點:RPC方式需要在API層面進行封裝,限制了開發的語言環境。

例如我們通過瀏覽器訪問網站,就是通過Http協議。只不過瀏覽器把請求封裝,發起請求以及接收響應,解析響應的事情都幫我們做了。如果是不通過瀏覽器,那麼這些事情都需要自己去完成。

1525569352313

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下的一個元件。

1525570921966

特點:

  • 基於標準、純淨的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》

1529051599689

發起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);
    }

結果:

1526877496885

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);
}

結果:

1526877647406

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);
    }
}

結果:

1526877995530

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);
    }
}

結果:

1526877988488

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會自動發起請求,接收響應,並且幫我們對響應結果進行反序列化。

1525573702492

學習完了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:熔斷器

以上只是其中一部分,架構圖:

1525575656796

4.2.版本

SpringCloud的版本命名比較特殊,因為它不是一個元件,而是許多元件的集合,它的命名是以A到Z的為首字母的一些單片語成:(版本名稱都是倫敦地鐵站名稱)

1525575903675

我們在專案中,會是以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.建立父工程

1529042834656

編寫專案資訊:

529042868833

編寫儲存位置:

1529042934471

然後將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>
            <