微服務架構(gòu)實戰(zhàn)學習(九):服務拆分及服務消費

導讀,前面哆嗦了好幾篇,其實只是講了微服務的一些概念,以及服務的注冊與發(fā)現(xiàn)。但好像還在望其冰山一角。沒錯,接下來我們將望其另一角...

這里我們注意到有幾個問題:
1 我們之前模擬注冊到 Eureka Server 上的 Eureka Client 服務是空白的... 也就是一句業(yè)務代碼都沒有???
2 之前我們提到微服務就是把龐大的雜糅在一起的各個業(yè)務模塊拆分成多個服務獨立開發(fā)、運行、維護,以降低耦合度??墒?,如何拆分?
3 拆分完了之后,服務 A 如何調(diào)用服務 B?

一、服務拆分

微服務拆分(或者服務拆分)并不是一個新鮮的名詞,在軟件工程領(lǐng)域已經(jīng)有不少年頭,而且還有專門針對服務拆分的研究與職位。
這里展開有很多個話題可以研究和討論。當然,此文中服務拆分不是重點。這里,我只是簡要分享一些微服務拆分上的一些原則,而這些也是結(jié)合微服務的特點來拆分,比如從不同的業(yè)務功能模塊、業(yè)務數(shù)據(jù)的相關(guān)性、不同服務的負載情況等等...

具體的拆分細則很多,如果對服務拆分感興趣,可以從網(wǎng)上找到很多優(yōu)秀的作者文章,比如

伯樂在線的服務拆分與架構(gòu)演進的文章: http://blog.jobbole.com/109902/

二、服務示例

基于服務拆分原則,這里舉一個簡單的拆分實例。比如,企業(yè)的內(nèi)部用戶管理就可以拆分為一個單獨的服務。用戶的數(shù)據(jù)可以對于其他系統(tǒng)獨立,而用戶的業(yè)務又可以對應到人力資源管理里。并且,用戶的操作又是企業(yè)內(nèi)部各業(yè)務部門和各系統(tǒng)都需要用到的,如果每個業(yè)務部門都維護一套自己的用戶管理模塊,不僅大量重復工作,而且各部門之間的數(shù)據(jù)還可能不一致而造成某些沖突...

將用戶的數(shù)據(jù)獨立出去,其他系統(tǒng)只需要來調(diào)用對應接口就能進行用戶的相關(guān)操作,而用戶的管理則統(tǒng)一由某一業(yè)務部門進行,比如人力資源部門。

此處,我們模擬一個非常簡單的用戶服務,包含簡單的添加用戶,獲取一個用戶,獲取所有用戶的接口。

1 創(chuàng)建一個 Eureka Client 項目

根據(jù)前面第六篇中的 Eureka Client 項目的創(chuàng)建與注冊先創(chuàng)建一個 Eureka Client 的空白項目 user-service

創(chuàng)建一個 Client 項目

2 準備項目配置

本示例項目使用了 mysql 存儲用戶數(shù)據(jù),使用 spring data jpa (如果對 jpa 不太熟,可自行搜索引擎,它非常簡單,因為它就是方便數(shù)據(jù)庫操作而開發(fā)的)來進行持久層操作。因此,需要添加 MySQL 的依賴,spring-boot-starter-data-jpa 的依賴。另外,spring-boot-starter-web 依賴則是確保本項目能夠作為一個后臺 web 項目運行。

(1) pom.xml
pom.xml 中添加如下依賴

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

(2) application.yml

本配置文件中主要添加了 mysql 的連接信息的配置,jpa 相關(guān)的

# application name
spring:
  application:
    name: user-service
  # mysql jpa config
  datasource:
    url: jdbc:mysql://192.168.174.200:3306/microservice?characterEncoding=UTF-8&autoReconnect=true&useSSL=false
    username: ms
    password: ms123
    dbcp:
      validation-query: SELECT 1
      test-while-idle: true
  jpa:
    generate-ddl: true
    show-sql: true
    hibernate:
      ddl-auto: update
    properties:
      hibernate:
        dialect: org.hibernate.dialect.MySQL5InnoDBDialect

# server monitor port
server:
  port: 8080

# eureka server cluster
eureka:
  client:
    service-url:
      defaultZone: http://hadoop1:8000/eureka/,http://hadoop2:8000/eureka/,http://hadoop3:8000/eureka/

注:

  • 數(shù)據(jù)庫連接配置 spring.datasource.url 的配置請確保你有一臺裝有 MySQL 的數(shù)據(jù)庫并且能夠連接,且已經(jīng)有一個可用的數(shù)據(jù)庫及能夠訪問的數(shù)據(jù)庫賬號
  • Eureka server 的地址是前面我搭建的運行在虛擬機上的集群

(3) 創(chuàng)建實體類

創(chuàng)建一個用戶的實體類,此類對應于我們的數(shù)據(jù)庫中的數(shù)據(jù)表 user,并且表的創(chuàng)建由 spring data jpa 框架進行。

package com.jiangzhuolin.userservice.bean;

import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

import javax.persistence.*;
import java.io.Serializable;
import java.util.Date;

@Entity
@Table(name="user")
@EntityListeners(AuditingEntityListener.class)
public class User implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long id;
    private String username;
    private String password;
    private String phone;
    private String email;
    private String address;

    @CreatedDate
    @Column(name = "create_time")
    private Date createTime;

    @LastModifiedDate
    @Column(name = "update_time")
    private Date updateTime;
... 省略 getter 與 setter 方法

(4) 創(chuàng)建 repository 接口

repository 接口類似于我們的 dao 層的接口,它直接繼承 JpaRepository,好處是我們多數(shù)情況下的簡單的數(shù)據(jù)庫操作完全不用我們自己實現(xiàn),由 jpa 自動幫我們生成對應的數(shù)據(jù)庫操作。當然,我們也可以自己實現(xiàn)更多的自定義的數(shù)據(jù)庫操作,在此不贅述。

package com.jiangzhuolin.userservice.repository;

import com.jiangzhuolin.userservice.bean.User;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface UserRepository extends JpaRepository<User, String> {

    User save(User user);

    User findUserById(long id);

    @Override
    Page<User> findAll(Pageable pageable);
}

(5) 創(chuàng)建對外服務響應類 controller

直接可以對我們的 repository 進行注入從而方便的使用其中的數(shù)據(jù)操作方法。

package com.jiangzhuolin.userservice.controller;

import com.jiangzhuolin.userservice.bean.User;
import com.jiangzhuolin.userservice.repository.UserRepository;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import java.util.List;

@RestController
@RequestMapping(value = "/user")
public class UserController {
    @Resource
    private UserRepository userRepository;

    @RequestMapping(value = "/save", method = RequestMethod.POST)
    @ResponseBody
    public User save(User user) {
        User savedUser = userRepository.save(user);
        savedUser.setPassword("");
        return savedUser;
    }

    @RequestMapping(value = "/{id}", method = RequestMethod.GET)
    @ResponseBody
    public User getOne(@PathVariable("id") long id) {
        User user = userRepository.findUserById(id);
        user.setPassword("");
        return user;
    }

    @RequestMapping(value = "/all")
    @ResponseBody
    public List<User> findAll() {
        List<User> userList = userRepository.findAll();
        for (User user : userList) {
            user.setPassword("");
        }
        return userList;
    }
}

(6) 打包程序
打包方法請參考之前的文章,此處不贅述。

(7) 運行 client 并注冊

將打包后的程序上傳到服務器,此處我上傳的我的兩臺虛擬機環(huán)境 192.168.174.200/201,并執(zhí)行如下命令運行。

[root@hadoop1 user-service]# pwd
/data/user-service
[root@hadoop2 user-service]# nohup java -jar user-service-0.0.1-SNAPSHOT.jar &

注:

  • 確保之前的 Eureka Server 集群是正常運行的

(8) 測試驗證

  • 查看數(shù)據(jù)庫

當我們服務正常啟動以后查看我們的數(shù)據(jù)庫 microservice,發(fā)現(xiàn)已經(jīng)自動生成了 user,與 hibernate_sequence 兩張表。其中 user 對應項目中創(chuàng)建的實體類 User,此表現(xiàn)在為空,另一個表 hibernate_sequence 是用來控制自動生成主鍵的 ID 的序號用的,初始狀態(tài)為 1。

mysql> show tables;
+------------------------+
| Tables_in_microservice |
+------------------------+
| hibernate_sequence     |
| user                   |
+------------------------+
2 rows in set (0.03 sec)

mysql> select * from user;
Empty set

mysql> select * from hibernate_sequence;
+----------+
| next_val |
+----------+
|        1 |
+----------+
1 row in set (0.02 sec)
  • 調(diào)用接口

使用 postman 調(diào)用 http://192.168.174.200:8080/user/save 接口,向數(shù)據(jù)庫中寫入一個用戶。

/user/save 接口

使用 postman 調(diào)用 http://192.168.174.201:8080/user/1 接口,從數(shù)據(jù)庫中讀取剛才寫入的用戶。

/user/{id} 接口

使用 postman 調(diào)用 http://192.168.174.200:8080/user/all 接口,從數(shù)據(jù)庫中讀取所有用戶數(shù)據(jù)。

/user/all 接口

可以看到,之前在 192.168.174.200/201 上運行的兩個 user-service 服務都已經(jīng)正常運行并能夠正常調(diào)用接口。

  • 查看 Eureka Server 上的注冊情況
Eureka Server 后臺注冊情況

可以看到 user-service 的兩個服務都已經(jīng)被注冊到 Eureka Server 集群中。

  • 可以看到 user-service 里有一個 windows 的服務,這是我本地的服務,實際上我已經(jīng)停掉了。另外之前運行過的 client-test 的服務也都停止了,可是為什么這些應用還在 Eureka 后臺顯示呢,上方還有一行紅字,這代表什么呢?
    其實,這就是與上篇中提到的 Eureka 的自我保護機制有關(guān),這個參數(shù)默認為 true 表示打開: eureka.server.enable-self-preservation: true. 至于自我保護的詳情,此處不贅述,可參考上篇文章或在網(wǎng)上搜索。

三、服務消費之 Ribbon

3.1 Ribbon 是干嘛的

從上面的服務調(diào)用可能發(fā)現(xiàn),我們服務調(diào)用不就是使用接口的 URL 地址調(diào)用就行了嗎 (比如使用 http client)?最多就是需要參數(shù)的再傳點參數(shù)?為什么還要把服務調(diào)用單獨拎出來說一說?是吃飽了沒事干嗎?是的,我吃飽了,但事還是挺多的。

從上面我們的服務調(diào)用來看,好像確實沒什么問題,比如我們的物流系統(tǒng)需要我們的用戶信息了,那我們在物流系統(tǒng)中使用 URL 地址調(diào)用不就可以了嗎?不過,事情他就怕一個大字。比如,我們的用戶服務使用的系統(tǒng)很多,調(diào)用頻率也很高,我們想做一些負載均衡,把訪問壓力分散開來,于是我們在多個服務器上啟動了多個用戶服務的進程(此處假如啟了 100 個)。那么問題來了,這 100 個里,你選哪一個服務來用?什么,你說隨緣挑選,那你可真是太棒了。

有人可能會說,用 nginx 做個反向代理并負載均衡呀。可是,這里有個問題,那就是沒有服務的可用性檢測,如果你拿到的是一個異常宕掉的服務,那么調(diào)用就出問題了...

好了好了,不多廢話了,上面的這些問題,服務有效性、負載均衡在 Spring Cloud 里也有解決方案,服務有效性依靠 Eureka 的心跳機制來解決,而負載均衡則是基于 Ribbon 來實現(xiàn)。Ribbon 是一個用于對 HTTP/TCP 請求進行控制的負載均衡客戶端,官網(wǎng)地址:
http://cloud.spring.io/spring-cloud-netflix/multi/multi_spring-cloud-ribbon.html

Ribbon 的詳情此處不表,如有興趣,可向往官網(wǎng)查閱。Ribbon 大概的思路是,先從 Eureka Server 端獲取已注冊的可用服務列表到客戶端,然后根據(jù) Ribbon 實現(xiàn)的各種策略(如輪詢,隨機,過濾無效鏈接等)對服務進行挑選(負載均衡)。

3.2 如何在 Eureka Client 使用 Ribbon

示例:現(xiàn)在我們的物流服務中需要調(diào)用用戶的信用用以完善部分的物流信息與流程。那么,我們怎么從前面的用戶服務中調(diào)用用戶信息呢?

  • 第一步,創(chuàng)建一個物流的 Eureka Client 服務
    此處過程省略,請參考前文 Eureka Client 的創(chuàng)建與注冊。

  • 第二步,在物流服務中調(diào)用 user-service 服務

LogisticsController.java 類如下:

package com.jiangzhuolin.logisticsservice.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

@RestController
@RequestMapping(value = "/logistics/")
public class LogisticsController {

    @Autowired
    private RestTemplate restTemplate;

    @RequestMapping(value = "/message")
    public String message(String userId) {
        String response = restTemplate.getForObject("http://USER-SERVICE/user/{userId}", String.class, userId);
        return response;
    }
}

需要注意的是,RestTemplate 類在 pring boot 1.4 版本以后便不再支持自動注入了,需要進行聲明。參考:
https://stackoverflow.com/questions/28024942/how-to-autowire-resttemplate-using-annotations

此處由于使用的是 2.02 的 spring boot,因此,在啟動類中作了一個簡單的聲明

package com.jiangzhuolin.logisticsservice;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;

@SpringBootApplication
@EnableDiscoveryClient
public class LogisticsServiceApplication {

    public static void main(String[] args) {
        SpringApplication.run(LogisticsServiceApplication.class, args);
    }

    @Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

其中 @Bean 表示聲明一個 Bean 對象,而 @LoadBalanced 表示聲明一個基于 Ribbon 的負載均衡。

  • 第三步,運行程序

此處,我為項目配置監(jiān)聽端口為 8070 并在本機的 IDEA 中直接運行,然后,在瀏覽器中調(diào)用 logistics 的接口 http://localhost:8070/logistics/message?userId=1,得到結(jié)果如下所示:

接口調(diào)用結(jié)果

至此,我們基于 Ribbon 的服務消費示例就完成了,是不是異常地簡單呢~

三、總結(jié)

本文淺顯地聊了服務拆分及服務消費一些場景和使用示例。雖然篇幅挺長,但實際都是粗淺的表面,僅限于使用層面。不過,人類的歷史來看,不都是從淺入深的過程。從樹上掉下蘋果撿起來就啃到發(fā)現(xiàn)萬有引力歷經(jīng)不少年頭,所以,先學怎么用沒毛病。扯遠了,扯遠了...

由此,我們也看出,Spring Cloud 依托于 Spring 的大而全的套件,基本已經(jīng)幫我們實現(xiàn)了很多的造輪子工作,甚至不僅造好了輪子,連車架都造好了,我們只需要上車就走,奔向那詩與遠方...

對不起,串場了,告辭告辭~

本文項目源碼地址:
user-service: https://github.com/jiangzhuolin/user-service
logistics-service: https://github.com/jiangzhuolin/logistics-service

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,882評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,208評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,746評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,666評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,477評論 6 407
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 54,960評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,047評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,200評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,726評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,617評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,807評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,327評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,049評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,425評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,674評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,432評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,769評論 2 372

推薦閱讀更多精彩內(nèi)容