前言
上一篇簡單入門了SpringBoot+SpringCloud 構建微服務。但只能算是一個
demo
級別的應用。
這次會按照實際生產要求來搭建這套服務。
Swagger應用
上次提到我們呼叫自己的http
介面的時候採用的是PostMan
來模擬請求,這個在平時除錯時自然沒有什麼問題,但當我們需要和前端聯調開發的時候效率就比較低了。
通常來說現在前後端分離的專案一般都是後端介面先行。
後端大大們先把介面定義好(入參和出參),前端大大們來確定是否滿足要求,可以了之後後端才開始著手寫實現,這樣整體效率要高上許多。
但也會帶來一個問題:在介面定義階段頻繁變更介面定義而沒有一個文件或類似的東西來記錄,那麼雙方的溝通加上前端的除錯都是比較困難的。
基於這個需求網上有各種解決方案,比如阿里的rap就是一個不錯的例子。
但是springCould
為我們在提供了一種在開發springCloud
專案下更方便的工具swagger
。
實際效果如下:
配置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#/
即可進入。
可以看到如上圖所示的介面列表,點選如下圖所示的引數例子即可進行介面呼叫。
自定義開關Swagger
swagger
的便利能給我們帶來很多好處,但稍有不慎也可能出現問題。
比如如果在生產環境還能通過IP訪問swagger
的話那後果可是不堪設想的。
所以我們需要靈活控制swagger
的開關。
這點可以利用spring的條件化配置(條件化配置可以配置存在於應用中,一旦滿足一些特定的條件時就取消這些配置)
來實現這一功能:
@ConditionalOnExpression("`${swagger.enable}` == `true`")複製程式碼
該註解的意思是給定的SpEL表示式計算結果為true
時才會建立swagger
的bean
。
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的方式命名
)。
分別啟動兩個註冊中心可以看到以下:
可以看到兩個註冊中心以及互相註冊了。
在服務註冊的時候只需要將兩個地址都加上即可: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配置
。
其中繼承了一個OrderService
在order-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) ;
}複製程式碼
這個介面有兩個目的。
- 給真正的
controller
來進行實現。 - 給
client
介面進行繼承。
類關係如下:
註解這些都沒什麼好說的,一看就懂。
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-client
的swagger
中進行呼叫:
由於我並沒傳appId
所以order
服務返回的錯誤。
總結
當一個應用需要對外暴露介面時著需要按照以上方式提供一個
client
包更消費者使用。
其實應用本身也是需要做高可用的,和Eureka
高可用一樣,再不同的伺服器上再啟一個或多個服務並註冊到Eureka
叢集中即可。
後續還會繼續談到zuul閘道器,容錯,斷路器
等內容,歡迎拍磚討論。
部落格:crossoverjie.top。