sbc(二)高可用Eureka+宣告式服務呼叫

crossoverJie發表於2019-02-13

前言

上一篇簡單入門了SpringBoot+SpringCloud 構建微服務。但只能算是一個demo級別的應用。
這次會按照實際生產要求來搭建這套服務。

Swagger應用

上次提到我們呼叫自己的http介面的時候採用的是PostMan來模擬請求,這個在平時除錯時自然沒有什麼問題,但當我們需要和前端聯調開發的時候效率就比較低了。

通常來說現在前後端分離的專案一般都是後端介面先行。

後端大大們先把介面定義好(入參和出參),前端大大們來確定是否滿足要求,可以了之後後端才開始著手寫實現,這樣整體效率要高上許多。

但也會帶來一個問題:在介面定義階段頻繁變更介面定義而沒有一個文件或類似的東西來記錄,那麼雙方的溝通加上前端的除錯都是比較困難的。

基於這個需求網上有各種解決方案,比如阿里的rap就是一個不錯的例子。

但是springCould為我們在提供了一種在開發springCloud專案下更方便的工具swagger

實際效果如下:

01.png
01.png

配置swagger

sbc-order為例我將專案分為了三個模組:

├── order                                    // Order服務實現  
│   ├── src/main
├── order-api                                // 對內API
│   ├── src/main
├── order-client                             // 對外的clientAPI
│   ├── src/main
├── .gitignore                               
├── LICENSE                
├── README.md複製程式碼

因為實現都寫在order模組中,所以只需要在該模組中配置即可。

首先需要加入依賴,由於我在order模組中依賴了:

<dependency>
    <groupId>com.crossoverJie</groupId>
    <artifactId>order-api</artifactId>
    <version>${target.version}</version>
</dependency>複製程式碼

order-api又依賴了:

<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger2</artifactId>
    <scope>compile</scope>
</dependency>
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger-ui</artifactId>
    <scope>compile</scope>
</dependency>複製程式碼

接著需要配置一個SwaggerConfig

@Configuration
@EnableSwagger2
/** 是否開啟swagger **/
@ConditionalOnExpression("`${swagger.enable}` == `true`")
public class SwaggerConfig {


    @Bean
    public Docket createRestApi() {
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                .select()
                .apis(RequestHandlerSelectors.basePackage("com.crossoverJie.sbcorder.controller"))
                .paths(PathSelectors.any())
                .build();
    }

    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                .title("sbc order api")
                .description("sbc order api")
                .termsOfServiceUrl("http://crossoverJie.top")
                .contact("crossoverJie")
                .version("1.0.0")
                .build();
    }

}複製程式碼

其實就是配置swagger的一些基本資訊。
之後啟動專案,在位址列輸入http://ip:port/swagger-ui.html#/即可進入。
可以看到如上圖所示的介面列表,點選如下圖所示的引數例子即可進行介面呼叫。

02.jpg
02.jpg

自定義開關Swagger

swagger的便利能給我們帶來很多好處,但稍有不慎也可能出現問題。

比如如果在生產環境還能通過IP訪問swagger的話那後果可是不堪設想的。
所以我們需要靈活控制swagger的開關。

這點可以利用spring的條件化配置(條件化配置可以配置存在於應用中,一旦滿足一些特定的條件時就取消這些配置)來實現這一功能:

@ConditionalOnExpression("`${swagger.enable}` == `true`")複製程式碼

該註解的意思是給定的SpEL表示式計算結果為true時才會建立swaggerbean

swagger.enable這個配置則是配置在application.properties中:

# 是否開啟swagger
swagger.enable = true複製程式碼

這樣當我們在生產環境時只需要將該配置改為false即可。

ps:更多spring條件化配置:

@ConditionalOnBean                 //配置了某個特定Bean
@ConditionalOnMissingBean          //沒有配置特定的Bean
@ConditionalOnClass                //Classpath裡有指定的類
@ConditionalOnMissingClass         //Classpath裡缺少指定的類
@ConditionalOnExpression           //給定的Spring Expression Language(SpEL)表示式計算結果為true
@ConditionalOnJava                 //Java的版本匹配特定值或者一個範圍值
@ConditionalOnJndi                 //引數中給定的JNDI位置必須存在一個,如果沒有給引數,則要有JNDI InitialContext
@ConditionalOnProperty             //指定的配置屬性要有一個明確的值
@ConditionalOnResource             //Classpath裡有指定的資源
@ConditionalOnWebApplication       //這是一個Web應用程式
@ConditionalOnNotWebApplication    //這不是一個Web應用程式
(參考SpringBoot實戰)複製程式碼

高可用Eureka

在上一篇中是用Eureka來做了服務註冊中心,所有的生產者都往它註冊服務,消費者又通過它來獲取服務。

但是之前講到的都是單節點,這在生產環境風險巨大,我們必須做到註冊中心的高可用,搭建Eureka叢集。

這裡簡單起見就搭建兩個Eureka,思路則是這兩個Eureka都把自己當成應用向對方註冊,這樣就可以構成一個高可用的服務註冊中心。

在實際生產環節中會是每個註冊中心一臺伺服器,為了演示起見,我就在本地啟動兩個註冊中心,但是埠不一樣。

首先需要在本地配置一個host:

127.0.0.1 node1 node2複製程式碼

這樣不論是訪問node1還是node2都可以在本機呼叫的到(當然不配置host也可以,只是需要通過IP來訪問,這樣看起來不是那麼明顯)。

並給sbc-service新增了兩個配置檔案:

application-node1.properties:

spring.application.name=sbc-service
server.port=8888
eureka.instance.hostname=node1

## 不向註冊中心註冊自己
#eureka.client.register-with-eureka=false
#
## 不需要檢索服務
#eureka.client.fetch-registry=false
eureka.client.serviceUrl.defaultZone=http://node2:9999/eureka/複製程式碼

application-node2.properties:

spring.application.name=sbc-service
server.port=9999
eureka.instance.hostname=node2

## 不向註冊中心註冊自己
#eureka.client.register-with-eureka=false
#
## 不需要檢索服務
#eureka.client.fetch-registry=false
eureka.client.serviceUrl.defaultZone=http://node1:8888/eureka/複製程式碼

其中最重要的就是:

eureka.client.serviceUrl.defaultZone=http://node2:9999/eureka/
eureka.client.serviceUrl.defaultZone=http://node1:8888/eureka/複製程式碼

兩個應用互相註冊。

啟動的時候我們按照:
java -jar sbc-service-1.0.0-SNAPSHOT.jar --spring.profiles.active=node1啟動,就會按照傳入的node1或者是node2去讀取application-node1.properties,application-node2.properties這兩個配置檔案(配置檔案必須按照application-{name}.properties的方式命名)。

分別啟動兩個註冊中心可以看到以下:

03.jpg
03.jpg

04.jpg
04.jpg

可以看到兩個註冊中心以及互相註冊了。
在服務註冊的時候只需要將兩個地址都加上即可:
eureka.client.serviceUrl.defaultZone=http://node1:8888/eureka/,http://node2:9999/eureka/

在服務呼叫的時候可以嘗試關閉其中一個,正常情況下依然是可以呼叫到服務的。

Feign宣告式呼叫

接下來談談服務呼叫,上次提到可以用ribbon來進行服務呼叫,但是明顯很不方便,不如像之前rpc呼叫那樣簡單直接。

為此這次使用Feign來進行宣告式呼叫,就像呼叫一個普通方法那樣簡單。

order-client

片頭說到我將應用分成了三個模組order、order-api、order-client,其中的client模組就是關鍵。

來看看其中的內容,只有一個介面:

@RequestMapping(value="/orderService")
@FeignClient(name="sbc-order")
@RibbonClient
public interface OrderServiceClient extends OrderService{


    @ApiOperation("獲取訂單號")
    @RequestMapping(value = "/getOrderNo", method = RequestMethod.POST)
    BaseResponse<OrderNoResVO> getOrderNo(@RequestBody OrderNoReqVO orderNoReq) ;
}複製程式碼

@FeignClient這個註解要注意下,其中的name的是自己應用的應用名稱,在
application.properties中的spring.application.name配置

其中繼承了一個OrderServiceorder-api模組中,來看看order-api中的內容。

order-api

其中也只有一個介面:

@RestController
@Api("訂單服務API")
@RequestMapping(value = "/orderService")
@Validated
public interface OrderService {

    @ApiOperation("獲取訂單號")
    @RequestMapping(value = "/getOrderNo", method = RequestMethod.POST)
    BaseResponse<OrderNoResVO> getOrderNo(@RequestBody OrderNoReqVO orderNoReq) ;
}複製程式碼

這個介面有兩個目的。

  1. 給真正的controller來進行實現。
  2. client介面進行繼承。

類關係如下:

05.jpg
05.jpg

註解這些都沒什麼好說的,一看就懂。

order

order則是具體介面實現的模組,就和平時寫controller一樣。
來看看如何使用client進行宣告式呼叫:

這次看看sbc-user這個專案,在裡邊呼叫了sbc-order的服務。
其中的user模組依賴了order-client:

<dependency>
    <groupId>com.crossoverJie</groupId>
    <artifactId>order-client</artifactId>
</dependency>複製程式碼

具體呼叫:

    @Autowired
    private OrderServiceClient orderServiceClient ;

    @Override
    public BaseResponse<UserResVO> getUserByFeign(@RequestBody UserReqVO userReq) {
        //呼叫遠端服務
        OrderNoReqVO vo = new OrderNoReqVO() ;
        vo.setReqNo(userReq.getReqNo());
        BaseResponse<OrderNoResVO> orderNo = orderServiceClient.getOrderNo(vo);

        logger.info("遠端返回:"+JSON.toJSONString(orderNo));

        UserRes userRes = new UserRes() ;
        userRes.setUserId(123);
        userRes.setUserName("張三");

        userRes.setReqNo(userReq.getReqNo());
        userRes.setCode(StatusEnum.SUCCESS.getCode());
        userRes.setMessage("成功");

        return userRes ;
    }複製程式碼

可以看到只需要將order-client包中的Order服務注入進來即可。

sbc-clientswagger中進行呼叫:

06.jpg
06.jpg


07.jpg
07.jpg

由於我並沒傳appId所以order服務返回的錯誤。

總結

當一個應用需要對外暴露介面時著需要按照以上方式提供一個client包更消費者使用。

其實應用本身也是需要做高可用的,和Eureka高可用一樣,再不同的伺服器上再啟一個或多個服務並註冊到Eureka叢集中即可。

後續還會繼續談到zuul閘道器,容錯,斷路器等內容,歡迎拍磚討論。

專案:github.com/crossoverJi…

部落格:crossoverjie.top

weixinchat.jpg
weixinchat.jpg

相關文章