微服務之Spring Cloud簡單入門

微服務是目前系統開發的一種主流技術架構,而Spring Cloud框架是其中的一種解決方案。本文主要從微服務的基本概念,到Spring Cloud框架包括各個基礎組件使用進行一個簡單介紹。html

什麼是微服務?

傳統的Web應用都是基於單體結構構建的,在單體架構中,全部的UI (用戶接口) 、業務、數據庫訪問邏輯都被打包在一個應用程序中而且部署在一個應用程序服務器上。隨着系統業務發展,應用也隨之變得愈來愈複雜,各類業務邏輯雜糅在一塊兒,耦合度過高,不易擴展和維護。前端

爲了解決這些問題,微服務也就應運而生。微服務容許將一個大型的應用分解爲具備嚴格職責定義的便於管理的服務。每一個服務具備特定的功能,減小系統耦合度。java

另外微服務的誕生與當前互聯網產品業務快速發展、頻繁變化以及互聯網公司的組織架構特色也不無關係。mysql

微服務的理論基礎和核心概念其實在很早以前就有人提出來過。其中比較重要的一個就是康威定律git

Melvin Conway 在1968年發表的論文《 How Do Committees Invent》中最著名的一句話原文是:github

Organizations which design systems are constrained to produce designs which are copies of the communication structures of these organizations. - Melvin Conway(1967)spring

系統的架構受制於產生這些設計的組織的溝通成本。即組織結構決定系統設計sql

再聯想到蘋果、谷歌、微軟以及國內的BAT的一些產品設計特色,與他們的組織管理方式確實有很大的關係。數據庫

微服務架構的優點和缺點

微服務架構優點:json

  1. 服務獨立部署,職責專注。每一個服務都是一個單獨的項目模塊,可獨立部署,耦合度低。
  2. 適合敏捷開發。隨着業務發展變化和開發團隊人員增多,將系統拆分紅不一樣服務,每一個開發人員負責專門的服務,有利於產品的快速迭代部署。
  3. 動態按需擴展。因爲每一個人服務可不依賴其餘服務獨立部署,業務擴展時,可直接增長新的服務獨立部署。
  4. 可用複用性高。每一個服務提供REST風格的接口服務,服務之間可重用相同的基礎接口服務。

微服務架構缺點:

  1. 分佈式部署,增長系統複雜性。每一個模塊獨立部署,經過HTTP進行通訊,會產生不少網絡問題、容錯問題。
  2. 數據一致性問題,使用多個數據源須要考慮分佈式事務處理。
  3. 測試運維難度提高。隨着服務的增長修改,服務之間的調用改變,對服務的測試、監控都都變得更加複雜。

一個小型的、簡單的和解耦的服務=可伸縮的、有彈性的和靈活的應用程序。

使用微服務應該結合具體的應用場景,不能爲了使用而使用。由於微服務是分佈式細粒度的,它在應用程序之間引入了複雜性,若是是正在構建小型的、部門級的應用程序或具備較小用戶羣的應用程序,構建分佈式系統的代價與構建成功得到的自動化和運維收益相比較高,那就不要考慮使用微服務。同時在使用時應該正確的劃分微服務大小,避免每一個服務承擔太多職責。如何控制每一個服務的粒度也是一個很重要的問題。不止須要進行業務邏輯的分離,還須要考慮服務的運行環境、服務之間的通訊交互、服務的可伸縮性和彈性等問題。

Spring Cloud介紹

Spring Cloud是基於Spring Boot並集成一系列組件的框架集合。經過將註冊中心、負載均衡、熔斷器、路網網關、配置中心等微服務須要的基礎設施封裝配置成starter,提供給開發人員進行快速集成開發部署。下面經過介紹一些經常使用的組件來對整個Spring Cloud框架的構建有一個簡單的瞭解。

首先建立一個基於Maven的多模塊項目spring-cloud-tutorial,項目結構和父pom.xml配置以下:

項目目錄結構

SpringCloud0

Eureka——服務註冊中心

爲管理各個服務,須要經過一個服務註冊中心來治理服務提供者和服務消費者之間的調用。打個比方,服務提供者就好比淘寶上面不一樣的賣家,服務消費者就是不一樣的買家。這些買家和賣家的關係是複雜的,都須要在淘寶平臺上先進行註冊登記,才能經過淘寶平臺(註冊中心)進行通訊。

除了Neflix提供的Eureka外,還有Consul、Zookeeper等可做爲服務的註冊中心。在分佈式系統有一個著名的CAP定理,C(Consistency)爲數據一致性,A(Availability)爲服務可用性,P(Partition tolerance)爲網絡分區容錯性,根據定理,分佈式系統只能知足三項中的兩項而不可能知足所有三項。Eureka是基於AP原則構建的,Zookeeper是基於CP原則構建的。Dubbo中大部分是使用Zookeeper做爲服務註冊中心,而Spring Cloud中主要是基於Eureka。

在項目中建立一個server-eureka模塊,模塊pom.xml配置以下:

SpringCloud1

主要是依賴一個Netflix提供的eureka-server starter。

啓動類配置以下:

SpringCloud2

增長一個@EnableEurekaServer註解表示開啓Eureka功能。

配置文件以下:

SpringCloud3

啓動服務後,經過http://localhost:8761進行訪問,就可以看到Eureka提供的可視化管理控制檯:

SpringCloud4

在這個上面能夠查看註冊的一些服務實例,因爲目前尚未服務註冊,因此爲空。

接下來建立一個service-client模塊,做爲客戶端服務。配置以下:

SpringCloud5

主要加入netFlix-eureaka-client starter依賴。

其中另一個service-business是一個封裝的底層業務模塊,會被多個其餘模塊引用。

service-business模塊主要建立了一個Employee員工實體類,其餘配置就再也不一一列出。

@Data
public class Employee {
    private String empId;
    private String name;
    private String designation;
    private Sex sex;
    private LocalDate birthday;
    private double salary;
}

public enum Sex {
    MALE("0"),
    FEMALE("1");

    private String code;

    Sex(String code) {
        this.code = code;
    }

    public String getCode(){
        return code;
    }
}

service-client模塊的啓動類,加上@EnableEurekaClient註解開啓客戶端功能。

SpringCloud8

SpringCloud9

配置文件中指定註冊中心的地址。

在service-client啓動後,訪問http://localhost:8761就能看到該服務信息。

另外在controller、service、dao三層分別新增員工、根據工號查詢員工、查詢所有員工列表的相關方法。

爲簡單起見,沒有使用數據庫存儲,直接將數據存在內存當中,其中dao層以下:

SpringCloud10

service層以下:

SpringCloud11

controller層以下:

SpringCloud12

代碼都很邏輯都很簡單,就再也不進行說明。

訪問http://localhost:8001/getAllEmployee可看到員工信息列表:

SpringCloud13

Ribbon——客戶端負載均衡

Ribbon是Netflix開源的一款用於客戶端負載均衡的工具。目前負載均衡主要有兩種方法:

  1. 集中式負載均衡。在服務端和消費者中間使用獨立的代理方式進行負載。又分爲硬件(例如F5)方式和軟件(例如Ngnix)兩種。
  2. 客戶端負載均衡。客戶端根據本身的請求狀況作負載。

接下來咱們使用Ribbon來實現一個最簡單的負載均衡調用功能。

建立一個service-ribbon模塊,其中Maven配置以下:

SpringCloud14

application.properties配置:

SpringCloud15

啓動類SerivceRibbonApplication加上@EnableEurekaClient註解,並注入RestTemplate進行http請求,添加 @LoadBalanced 註解,代表這個 restTemplate 開啓負載均衡功能

SpringCloud16

新建一個service層調用service-client的接口以下,並新建一個controller調用service。

SpringCloud17

以前已經啓動了一個8001端口的service-client實例,修改application.properties裏面的端口爲8002,再啓動一個新的客戶端。在IDEA中修改啓動配置項,去掉Signle instance only勾選一個模塊便可重複建立實例。

SpringCloud17

ServiceRibbonApplication也啓動後,咱們在Eureka的控制檯上面就能夠看到三個註冊服務:

SpringCloud18

屢次訪問http://localhost:8100/hi,就會交替輸出 hi:8001和 hi:8002,說明實現了負載均衡。

Feign——聲明式REST客戶端

Spring Cloud 有兩種調用服務的方式,一種是 ribbon + restTemplate,另一種是使用聲明式REST客戶端 feign進行接口調用。在通常的Java項目中咱們一般可使用HttpClient、HttpUrlConnection、Okhttp、RestTemplate這幾種主要方式進行 HTTP 請求。Feign經過編寫簡單的接口和插入註解,就會徹底代理HTTP請求。Feign默認使用JDK自帶的HttpURLConnetion進行HTTP請求,也能夠替換成HttpClient、OkHttp等其餘方式。

新建service-feign模塊,Maven配置加入open feign依賴。

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

ServiceFeignApplication加上@EnableFeignClients開啓feign功能。

新建一個IEmployeeService接口。

@FeignClient(value = "service-client")
public interface IEmployeeService {
    @GetMapping("/hi")
    String sayHi();
    
    @GetMapping("/getEmployeeByEmpId")
    Employee getEmployeeByEmpId(String empId);

    @PostMapping("/addEmployee")
    String addEmployee(Employee employee);

    @GetMapping("/getAllEmployee")
    List<Employee> getAllEmployee();
}

新建controller調用該接口方法:

@RestController
public class EmployeeController {

    @Autowired
    IEmployeeService employeeService;
    @GetMapping("/hi")
    public String hi(){
        return employeeService.sayHi();
    }
    @GetMapping("/getEmployeeByEmpId")
    public Employee getEmployeeByEmpId(String empId){
        return employeeService.getEmployeeByEmpId(empId);
    }

    @PostMapping("/addEmployee")
    public String addEmployee(Employee employee){
        return employeeService.addEmployee(employee);
    }

    @GetMapping("/getAllEmployee")
    public List<Employee> getAllEmployee(){
        return employeeService.getAllEmployee();
    }
}

啓動 service-feign,端口8200。屢次訪問http://locahost:8200/hi,交替出現hi:8001和 hi:8002,說明實現了負載均衡。訪問http://locahost:8200/addEmployee,添加參數併發送一個post請求.

SpringCloud19

在調用getAllEmployee方法,可看到剛纔的請求成功了。

SpringCloud20

Hystrix——服務容錯機制

Ribbon + Hysteria

咱們上面已經建立了一個server-eureka、兩個service-client、一個service-ribbon、一個service-feign幾個服務實例,這些服務之間使用的是鏈式調用,鏈式調用中當其中一個服務掛了,其餘的服務就會出現問題。Spring Cloud中能夠採用Hystrix斷路器進行服務容錯處理,當一個服務的調用失敗次數到達必定閾值,斷路器會打開,執行服務調用失敗時的處理,避免連鎖故障。Hystrix可經過HystrixCommand對調用進行隔離,阻止故障的連鎖效應,接口調用失敗可迅速恢復正常後在回退並優雅降級。

Hystrix主要是結合在ribbon或者feign中使用。

首先在service-ribbon中加入依賴

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>

在啓動類 ServiceRibbonApplication 加 @EnableHystrix ,啓動Hystrix。

修改EmployeeService,在callHi方法上面添加 @HystrixCommand 註解,fallbackMethod 是熔斷方法,當服務不可用時會執行該方法。

@Service
public class EmployeeService {
    @Autowired
    RestTemplate restTemplate;

    @HystrixCommand(fallbackMethod = "error")
    public String callHi(){
        return restTemplate.getForObject("http://service-client/hi",String.class);
    }

    public String error(){
        return "sorry,something is wrong!";
    }
  
    public Employee callGetEmployeeByEmpId(String empId){
        return restTemplate.getForObject("http://service-client/getEmployeeByEmpId?empId=" + empId,Employee.class);
    }

    public String callAddEmployee(Employee employee){
        return restTemplate.postForObject("http://service-client/addEmployee",employee,String.class);
    }

    public String callAddEmployee2(Employee employee){
        return restTemplate.postForEntity("http://service-client/addEmployee",employee,String.class).getBody();
    }

    public List<Employee> callGetAllEmployee(){
        return restTemplate.getForObject("http://service-client/getAllEmployee",List.class);
    }
}

關閉兩個service-client服務,重啓service-ribbon服務,訪問http://localhost:8100/hi,因爲調用不到service-client的hi接口,服務不可用,斷路器會迅速執行熔斷方法,輸出」sorry,something is wrong!「。

Feign + Hystrix

Feign自動包含了Hystrix依賴,不須要修改Maven配置。但須要在配置文件中開啓該功能:

feign.hystrix.enabled=true

新建一個EmployeeServiceHystrixImpl類,實現IEmployeeService接口。

@Component
public class EmployeeServiceHystrixImpl implements IEmployeeService {

    @Override
    public String sayHi() {
        return "sorry,505";
    }
    @Override
    public Employee getEmployeeByEmpId(String empId) {
        return null;
    }

    @Override
    public String addEmployee(Employee employee) {
        return "sorry,505";
    }

    @Override
    public List<Employee> getAllEmployee() {
        return null;
    }
}

關閉service-client服務實例,重啓service-feign並訪問http://localhost:8200/hi, 顯示"sorry,505"說明熔斷成功。

Zuul——API路由網關

隨着業務的發展,服務的增多,可經過API路由聚合內部服務,提供統一對外的API接口給前端進行調用,屏蔽內部實現細節。其中Zuul是一種基於JVM路由和服務端的負載均衡器,其核心是過濾器。使用Zuul可實現動態路由、請求監控、認證鑑權、壓力測試、灰度發佈等功能。

新建一個service-zuul模塊,Maven配置加入zuul依賴

<dependency>
   <groupId>org.springframework.cloud</groupId>
   <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>

ServiceZuulApplication加入@EnableZuulProxy註解開啓Zuul代理

server.port=8300
spring.application.name=service.zuul
eureka.client.service-url.defaultZone=http://localhost:8761/eureka/

zuul.routes.api-a.path=/api-a/**
zuul.routes.api-a.serviceId=service-ribbon
zuul.routes.api-b.path=/api-b/**
zuul.routes.api-b.serviceId=service-feign

配置文件分別使用api-a和api-b路由代理servie-ribbon和service-feign兩個服務。

訪問http://localhost:8300/api-a/hi和http://localhost:8300/api-b/hi,結果和直接調用一致,說明路由成功。

Zuul還能夠實現限流、認證等高級功能,這些功能都基於Zuul過濾器。

下面經過繼承ZuulFilter實現一個自定義的過濾器,進行token認證。

@Component
public class MyFilter extends ZuulFilter{
    /**
     * filterType:返回一個字符串表明過濾器的類型,在zuul中定義了四種不一樣生命週期的過濾器類型,具體以下:
     * pre:路由以前
     * routing:路由之時
     * post: 路由以後
     * error:發送錯誤調用
     */
    @Override
    public String filterType() {
        return "pre";
    }

    @Override
    public int filterOrder() {
        return 0;
    }

    @Override
    public boolean shouldFilter() {
        return true;
    }

    @Override
    public Object run() throws ZuulException {
        RequestContext ctx = RequestContext.getCurrentContext();
        HttpServletRequest request = ctx.getRequest();
        Object accessToken = request.getParameter("token");
        if (accessToken == null){
            ctx.setSendZuulResponse(false);
            ctx.setResponseStatusCode(401);
            try{
                ctx.getResponse().getWriter().write("plz input token");
            }catch (Exception e){
                System.out.println(e.getMessage());
            }
            return null;
        }

        return null;
    }
}

Zuul過濾器總共有四種類型。

  • pre: 在請求被路由以前調用,可用於身份認證。
  • route:在路由請求時調用。適用於灰度發佈場景。
  • post:在請求路由到具體服務以後執行。適用於添加響應頭,記錄響應日誌等場景。
  • error:處理請求發生錯誤時調用。可用統一記錄錯誤日誌。

http://localhost:8300/api-a/hi?token=123,url地址只有帶上token參數才能訪問成功。

Config——分佈式配置中心

在微服務架構中,服務數量一般從幾十到上百甚至上千。每次修改一個配置都須要多個模塊,再依次重啓每一個服務。將配置集中放到服務端進行管理,統一修改推送到客戶端後實時生效,可以提升效率和減小出錯概率。

Spring Cloud Config 是一個用來爲分佈式系統提供配置集中化管理的服務,分爲客戶端和服務端兩個部分。其餘比較出名的分佈式配置中心就是攜程開源的Apollo框架。

新建一個service-config模塊,Maven配置加上config依賴

<dependency>
   <groupId>org.springframework.cloud</groupId>
   <artifactId>spring-cloud-config-server</artifactId>
</dependency>

啓動類添加@EnableConfigServer註解。

在github或者碼雲等」最大同性交友網站「上面新建一個倉庫用於存放配置文件。在master分支放生產環境配置,新增dev、test等分支用於存放不一樣環境的配置。

建立一個config-client.properties文件,master分支輸入datasource=oracle,dev分支輸入datasource=mysql,

做爲配置的服務端。

客戶端配置文件以下:

server.port=8400
spring.application.name=service-config
spring.cloud.config.server.git.uri=https://github.com/git-username/spring-cloud-config.git
# 公開的倉庫不須要填寫用戶名密碼
spring.cloud.config.server.git.username=
spring.cloud.config.server.git.password=

訪問http://localhost:8400/config-client/master,輸出以下,其中source下的就是咱們配置文件中的內容。

{
"name": "config-client",
"profiles": [
    "master"
],
"label": null,
"version": "cd79457da44a9bd88e00b31b9ee99ffffd73a052",
"state": null,
"propertySources": [
    {
        "name": "https://github.com/git-username/spring-cloud-config.git/config-client.properties",
        "source": {
            "datasource": "oracle"
        }
    }
]
}

改成http://localhost:8400/config-client/dev, 返回的datasource=mysql說明配置成功。

其餘的路由訪問規則還有這幾種方式:

/{application}/{profile}[/{label}] :http://localhost:8400/config-client/dev
/{application}-{profile}.yml
/{label}/{application}-{profile}.yml
/{application}-{profile}.properties   
/{label}/{application}-{profile}.properties : http://localhost:8300/dev/config-client.properties

{application}爲配置文件名config-client,若是文件名爲config-client-pc,則pc就是{profile} ,{label} 指的是資源庫的分支,不填則爲默認分支。

以上就是一次Spring Cloud框架各個組件使用的簡單實踐。另外還有一些sleuth服務跟蹤,JWT/OAuth2服務認證、Spring Boot Admin服務監控等後面再慢慢研究。TBC。。。

參考資料

  1. 約翰.卡內爾《Spring微服務實戰》.人民郵電出版社.2018-6
  2. 尹吉歡.《Sprng Cloud微服務入門、實戰與進階》.機械工業出版社.2019-5
相關文章
相關標籤/搜索