SlideShare a Scribd company logo
1 of 50
Spring Cloud
Eureka & OpenFeign
實務應用經驗
關於一個從微服務這個雷區生存下來的
純軟體公司的那些事
Rhys Chang
Agenda
微服務
SpringCloud
問題與使用經驗
01
02
03
微服務
01
簡單講一下微服務
所以用Java來寫微服務的話…
傳統單體服務 → 微服務
= 一個Java專案 → 很多個小小的Java專案
= 一個war檔 → 很多個小小的war
所以用Java來寫微服務的話…
傳統單體服務 → 微服務
= 一個Java專案 → 很多個小小的Java專案
= 一個war檔 → 很多個小小的war
要搞定的東西其實還不少
佈屬在哪?網內互打變成網外互打
服務如何切分?架構如何切分?
簡單來說,後來變成這樣
改用Spring-Boot
Run在Docker上
以K8S管理叢集
然後問題就發生了
RestfulAPI
Service B
http://???????/service/dosomething
傳統思維上,兩服務透過API呼叫,需要IP
Service A
其實解決辦法很簡單
http://ServiceName/service/dosomething
Too young, too simple
1. 考量到不買K8S這類平台的用戶
2. 開發的技術比佈署的技術早決定
3. Swarm與K8S在當時還在競爭
設計微服務架構當時
並沒有以K8S為運行考量
小結一下
1. 專案底層使用Spring Boot
 透過JIB包成image
2. 不依賴K8S或是Swarm的原生機制
 保留佈署彈性,使客戶有選擇空間
綜上原因,SpringCloud成為當時微服務架構的底層機制
SpringCloud
02
講一下SpringCloud
Eureka跟Feign在哪?
Eureka 在這
Feign 在這
1
2
3
Eureka
服務的註冊與發現
OpenFeign
宣告式的Restful Client
問題與使用經驗
03
狀況一、Get 參數的傳遞
@GetMapping(“/foo”)
public void composite(Bar bar) {
client.bar(bar);
}
@Getter
@Setter
public class Bar {
private long id;
private String name;
}
呼叫: /foo?id=1&name=aaa
@FeignClient("foo")
public interface FooClient {
@GetMapping("/bar")
public DemoObject composite(Bar bar);
}
Controller FeignClient
狀況一、Get 參數的傳遞
@GetMapping(“/foo”)
public void composite(Bar bar) {
client.bar(bar);
}
@Getter
@Setter
public class Bar {
private long id;
private String name;
}
呼叫: /foo?id=1&name=aaa
@FeignClient("foo")
public interface FooClient {
@GetMapping("/bar")
public DemoObject composite(Bar bar);
}
Controller FeignClient
feign.FeignException$MethodNotAllowed:
status 405 reading FooClient#bar(Bar)
狀況一、Get 參數的傳遞
FeignClient的參數預設都會被視為Body
然而 Get 無法擁有 MessageBody
因此被轉成 Post 所導致
@FeignClient("foo")
public interface FooClient {
@GetMapping("/bar")
public DemoObject composite(Bar bar);
}
feign.FeignException$MethodNotAllowed:
status 405 reading FooClient#bar(Bar)
狀況一、Get 參數的傳遞
若參數為基礎型別或String,則宣告 @RequestParam 即可
但若是如本案例為一Pojo,則需宣告為 @SpringQueryMap
@FeignClient("foo")
public interface FooClient {
@GetMapping("/bar")
public DemoObject composite(@SpringQueryMap Bar bar);
}
狀況一、Get 參數的傳遞
可是 我們當時找不到這方法
因為我們2015就開始打底層了,但…
對應版本差不多是
spring-cloud-openfeign-core:2.1.x
狀況一、Get 參數的傳遞
因此如果你像我們一樣,只能用1.x.x
@Bean
public FeignObjectEncoder feignObjectEncoder(
ObjectFactory<HttpMessageConverters> messageConverters, ObjectMapper objectMapper) {
return new FeignObjectEncoder(new SpringEncoder(messageConverters), objectMapper);
}
那就得土砲一系列的 FeignObjectEncoder
public class FeignObjectEncoder implements feign.codec.Encoder
1. 維持處理原本的邏輯, 2. Object to QueryString, 3. Body清空
狀況一、 Get 參數的傳遞
需於FeignClient的參數上增加宣告
否則會在Get階段就會因為多了body而轉成Post
造成 405 Mathod Not Allowed
@RequestParam @SpringQueryMap
如果版本是1.x.x
得自製FeignObjectEncoder
在裡面處理Object > QueryString,並將Body處理為Empty
狀況二、錯誤訊息傳遞
A B C
當C發生Exception時,A會回傳甚麼出去?
三個Service 透過FeignClient連續呼叫
狀況二、錯誤訊息傳遞
A B C
{
"timestamp": "2019-10-02T09:43:07.872+0000",
"status": 500,
"error": "Internal Server Error",
"message": "status 500 reading B#foo(Bar)",
"path": "/foo"
}
三個Service 透過FeignClient連續呼叫
狀況二、錯誤訊息傳遞
A B C
三個Service 透過FeignClient連續呼叫
{
"timestamp": "2019-10-02T09:43:07.872+0000",
"status": 500,
"error": "Internal Server Error",
"message": "status 500 reading A#foo(Bar)",
"path": "/foo"
}
狀況二、錯誤訊息傳遞
A B C
三個Service 透過FeignClient連續呼叫
{
"timestamp": "2019-10-02T09:43:07.872+0000",
"status": 500,
"error": "Internal Server Error",
"message": "status 500 reading A#foo(Bar)",
"path": "/foo"
}
狀況二、錯誤訊息傳遞
[ab20a3667c0df961][Service-C] Exception: message…
[ab20a3667c0df961][Service-B] status 500 reading C#foo(Bar)
[ab20a3667c0df961][Service-A] status 500 reading B#foo(Bar)
透過TraceID 與ServiceName 可以很快抓到錯誤的迭代
如果真的有…
狀況二、錯誤訊息傳遞
{
"timestamp": "2019-10-02T09:43:07.872+0000",
"status": 500,
"error": "Internal Server Error",
"message": "status 500 reading B#foo(Bar)",
"path": "/foo"
}
protected FeignException(int status, String message, Throwable cause, byte[] content) {
super(message, cause);
this.status = status;
this.content = content;
}
實際上針對所有類型的呼叫錯誤都會被包裝成FeignException
原本的message會被塞到content
新的message只會是 "status %s reading %s"
並且SpringMVC會很好心的幫你只把Message傳出去
狀況二、錯誤訊息傳遞
這時候其實從Content還能拿到C的訊息
B進行FeignClient呼叫
C thorw Exception > SpringMVC response
B將Exception包裝為 FeignException > SpringMCV response
A接收到 FeignException "status 500 reading B"
由於B沒有將Content包入Response,因此此處已經拿不到C真正的錯誤訊息
狀況二、錯誤訊息傳遞
@ExceptionHandler
public ResponseEntity<Object> handleInternalClientException(
FeignException ex, WebRequest request) {
return ResponseEntity.status(ex.status()).body(ex.content());
}
只要於A及B實作此ExceptionHandler
即可將content作為body向外傳輸
FeignException.message 可以視需求決定是否併入body
狀況二、錯誤訊息傳遞
• FeignException的包裝位於
feign.codec.ErrorDecoder
• Response的包裝位於
o.s.w.s.m.m.a.RequestMappingHandlerAdapter
多個Service間互相呼叫
會有Exception與Response交互包裝後遺失原本訊息的問題
實作 ExceptionHandler 可解決此問題
但其實更建議建置一套健康完整的日誌蒐集系統
狀況三、本機流程測試
狀況三、本機流程測試
公司有一個伺服器叢集,啟動所有服務
service X Nserviceserviceservice
狀況三、本機流程測試
Service
A
開發的工程師在自己電腦啟需要的服務,沒啟動的就用公司開好的
工程師:Foo
Service
C
Service
B
Service
B
狀況三、本機流程測試
Service
A
這種狀況,在Eureka跟Ribbon存在的環境下,可以設定
eureka.instance.metadataMap.zone
工程師:Foo
Service
C
Service
B
Service
B
狀況三、本機流程測試
Service
A
預設情形下,同個Zone會盡可能地去呼叫同個Zone
沒有同個Zone的情況下才會呼叫所有已知的Service
工程師:Foo
Service
C
Service
B
Service
B
Zone: default
可呼叫範圍
Zone: FOO
可呼叫範圍
狀況三、本機流程測試
Service
A
預設情形下,同個Zone會盡可能地去呼叫同個Zone
沒有同個Zone的情況下才會呼叫所有已知的Service
工程師:Foo
Service
C
Service
B
Service
B
Zone: default
可呼叫範圍
Zone: FOO
可呼叫範圍
狀況三、本機流程測試
Service
A
原因在於,我們工程師不只一個
而且大家開發的項目不會相同
工程師:Bar
Service
C
Service
B
A
紅線是理想狀況下 Bar 的程式要呼叫的流程
D
工程師:Foo
CB
狀況三、本機流程測試
Service
A
原因在於,我們工程師不只一個
而且大家開發的項目不會相同
工程師:Bar
Service
C
Service
B
A
紅線是理想狀況下 Bar 的程式要呼叫的流程
D
工程師:Foo
CB
狀況三、本機流程測試
Service
A
Service
C
Service
B
工程師:Bar
AD
工程師:Foo
CB
Eureka
Ans. 所有的已知Service
更完整的來說,是Eureka已知的Service
然而所有人都會註冊到公司的Eureka上
否則無法發現公司的Service
狀況三、本機流程測試
Service
A
Service
C
Service
B
工程師:Bar
AD
工程師:Foo
CB
Eureka
Ans. 所有的已知Service
更完整的來說,是Eureka已知的Service
然而所有人都會註冊到公司的Eureka上
否則無法發現公司的Service
狀況三、本機流程測試
Bar的Service
實際上Zone的機制是由Ribbon及Eureka共同完成的
狀況三、本機流程測試
解決方法:客製 ZonePreferenceServerListFilter
public class CustomZoneFilter extends ZonePreferenceServerListFilter {
@Override
public List<Server> getFilteredListOfServers(List<Server> servers) {
final List<Server> filteredServers = super.getFilteredListOfServers(servers);
// 從filteredServers中以自訂的邏輯篩選出可以呼叫的Server
return callableServers;
}
}
@Bean
public ServerListFilter<Server> onlyMyZoneFilter() {
return new CustomZoneFilter();
}
狀況三、本機流程測試
Server中有Zone屬性,而從Properties中可以取得該Service當前的Zone
有Zone取同Zone,否則只取defaultZone;沒Zone只取defaultZone
public class CustomZoneFilter extends ZonePreferenceServerListFilter {
@Override
public List<Server> getFilteredListOfServers(List<Server> servers) {
final List<Server> filteredServers = super.getFilteredListOfServers(servers);
// 從filteredServers中以自訂的邏輯篩選出可以呼叫的Server
return callableServers;
}
}
狀況三、本機流程測試
太多微服務本機跑不動
> 用Zone可以只開部分在本機
Zone互相干擾
> 實作 ZonePreferenceServerListFilter
最佳解法
> 做好Mock,模擬沒有開起來的服務
結語
主要適用SpringFramework4 + SpringBoot 1.x
大部分的SpringCloud專案,都有對應的原生解法
Q&A

More Related Content

Similar to Usage Experience of Spring Cloud Eureka & OpenFeign

深入研究 Windows 系統服務 效能調校與故障排除
深入研究 Windows 系統服務    效能調校與故障排除深入研究 Windows 系統服務    效能調校與故障排除
深入研究 Windows 系統服務 效能調校與故障排除
5045033
 
安博士Asec 2010年4月安全报告
安博士Asec 2010年4月安全报告安博士Asec 2010年4月安全报告
安博士Asec 2010年4月安全报告
ahnlabchina
 

Similar to Usage Experience of Spring Cloud Eureka & OpenFeign (20)

ASP.NET Core 2.1設計新思維與新發展
ASP.NET  Core 2.1設計新思維與新發展ASP.NET  Core 2.1設計新思維與新發展
ASP.NET Core 2.1設計新思維與新發展
 
深入研究 Windows 系統服務 效能調校與故障排除
深入研究 Windows 系統服務    效能調校與故障排除深入研究 Windows 系統服務    效能調校與故障排除
深入研究 Windows 系統服務 效能調校與故障排除
 
从网格计算到云计算
从网格计算到云计算从网格计算到云计算
从网格计算到云计算
 
twMVC#22 | 一個微信專案從0到.000的效能調教之路
twMVC#22 | 一個微信專案從0到.000的效能調教之路twMVC#22 | 一個微信專案從0到.000的效能調教之路
twMVC#22 | 一個微信專案從0到.000的效能調教之路
 
[xKungFoo2012]Web Service Hack
[xKungFoo2012]Web Service Hack[xKungFoo2012]Web Service Hack
[xKungFoo2012]Web Service Hack
 
云起龙骧系列课程(4) - Live Services开发实战_黄继佳
云起龙骧系列课程(4) - Live Services开发实战_黄继佳云起龙骧系列课程(4) - Live Services开发实战_黄继佳
云起龙骧系列课程(4) - Live Services开发实战_黄继佳
 
04.uliweb更多话题介绍
04.uliweb更多话题介绍04.uliweb更多话题介绍
04.uliweb更多话题介绍
 
Supersonic Subatomic Quarkus accelerate cloud native development
Supersonic Subatomic Quarkus accelerate cloud native developmentSupersonic Subatomic Quarkus accelerate cloud native development
Supersonic Subatomic Quarkus accelerate cloud native development
 
一個微信專案從0到000的效能調教
一個微信專案從0到000的效能調教一個微信專案從0到000的效能調教
一個微信專案從0到000的效能調教
 
ASP.NET Core MVC 2.2從開發到測試 - Development & Unit Testing
ASP.NET Core MVC 2.2從開發到測試 - Development & Unit TestingASP.NET Core MVC 2.2從開發到測試 - Development & Unit Testing
ASP.NET Core MVC 2.2從開發到測試 - Development & Unit Testing
 
Non-MVC Web Framework
Non-MVC Web FrameworkNon-MVC Web Framework
Non-MVC Web Framework
 
Clientside attack using HoneyClient Technology
Clientside attack using HoneyClient TechnologyClientside attack using HoneyClient Technology
Clientside attack using HoneyClient Technology
 
Beyond rails server
Beyond rails serverBeyond rails server
Beyond rails server
 
Aws reinvent 2015 - day 2
Aws reinvent 2015 - day 2Aws reinvent 2015 - day 2
Aws reinvent 2015 - day 2
 
2018 LINE Tech Pulse 直通國際分享
2018 LINE Tech Pulse 直通國際分享2018 LINE Tech Pulse 直通國際分享
2018 LINE Tech Pulse 直通國際分享
 
與大師對談: 轉移到微服務架構必經之路 ~ 系統與資料庫重構
與大師對談: 轉移到微服務架構必經之路~ 系統與資料庫重構與大師對談: 轉移到微服務架構必經之路~ 系統與資料庫重構
與大師對談: 轉移到微服務架構必經之路 ~ 系統與資料庫重構
 
安博士Asec 2010年4月安全报告
安博士Asec 2010年4月安全报告安博士Asec 2010年4月安全报告
安博士Asec 2010年4月安全报告
 
SignalR實戰技巧 twmvc#17
SignalR實戰技巧 twmvc#17 SignalR實戰技巧 twmvc#17
SignalR實戰技巧 twmvc#17
 
黑站騎士
黑站騎士黑站騎士
黑站騎士
 
Java API for WebSocket 實作介紹
Java API for WebSocket 實作介紹Java API for WebSocket 實作介紹
Java API for WebSocket 實作介紹
 

Usage Experience of Spring Cloud Eureka & OpenFeign

Editor's Notes

  1. 說明微服務與傳統單一架構 1. Java 包檔方式的差異 2. 佈署方式的差異
  2. 微服務如何用Java實作 **下一張有梗
  3. 開發面:Service之間的溝通 method > protocol 管理面:佈署方式的差異(這麼多的App怎麼管理?) 設計面:Service的職責(Gateway, Composit, Base…) 設計面:Domain方面的切分
  4. 公司目前的作法 SpringBoot撰寫專案 透過JIB包成Image Run在Docker上 用K8S管理叢集 **下一張講但書
  5. 開發第一步首先遇到的問題,Service間的呼叫 (此處已假定為是以同步方式透過Restful呼叫) 然而剛剛有說是佈在K8S上,所以…
  6. K8S內有Service層作為LoadBalance可直接以service name呼叫 口述一下K8S關於Service>Pod>Container的東西 然而這在當時行不通…
  7. 1. 考量到不買K8S這類平台的用戶 2. 開發的技術比佈署的技術早決定 3. Swarm與K8S在當時還在競爭
  8. 將話題導引到SpringCloud上 ** 下一張換章節
  9. 我們所使用的SpringCloud有哪些部分 Services, ConfigServer, ServiceDiscovery 描述一下各個存在的職責與目的
  10. 同步呼叫的執行過程,帶出Eureka與Feign的存在,職責 下一張再講他們各自的操作
  11. Eureka:服務的註冊(server),與發現(client),會配合Ribbon使用 Feign:RestfulAPI包裝 ** 下一張換章節
  12. 整個微服務專案建置的過程中發生了很多狀況 這次針對幾個代表性的狀況描述一下
  13. 主要是Feign的問題 傳遞Get參數時,若沒有指定annotation,會被塞入
  14. @SpringQueryMap於底層會透過QueryMapEncode對參數進行序列化
  15. 2015年建置底層 然而這些功能2018年12月才加進來
  16. 所以當時我們的作法是自己實作 FeignObjectEncoder 實作內容 非Get仍走原本的行 Get時將參數處理成QueryString 且要清空Body,否則還是會被轉為Post
  17. 小結一下
  18. Service透過Fiegn連續呼叫
  19. 只會知道A的Feign報錯了 ** 下一頁有梗
  20. ** 下一頁有梗
  21. 回頭描述一下有Log的樣子 而且這實際上不算是問題,因為每個Service理應都只專注在自己的事情上
  22. FeignException的建構子 其中message就是"status %s reading %s" 而真正的ExceptionMessage則在content
  23. 以發生順序的方式講解訊息被包掉的原因 一個原因是FeignException的結構 另一個原因是SpringMVC處理Exception的方式
  24. 只要讓MVC包Exception訊息時,碰到FeignException就多處理content的資訊
  25. 小結一下
  26. 開發時需要流程測試,但是服務切太多了,一台電腦根本跑不動
  27. 我司解法,在一個叢及環境上開出這些服務
  28. 這些服務全部都能夠對外獨立呼叫
  29. 實際上這是透過Eureka做到的
  30. 講一下Zone的作用方式 ** 下一張有梗
  31. FOO開了BC BAR開了DA 由於BAR沒有開C,所以這C理應要呼叫公司的叢集環境,但… ** 下一張有梗
  32. 會打所有已知的Service Eureka的所有已知,包含了公司叢集、BAR、理所當然也包含FOO ** 下一張有梗
  33. 說明一下Eureka跟Ribbon的機制
  34. 白話:自製Zone選擇Service的規則,然後註冊為SpringBean 下一張才講實作內容
  35. 實作內容
  36. 小結一下
  37. 總結,同時說明SpringCloud與K8S元生解決方案的對應
  38. 公司招生中