API Gateway(APIGW / API 網關),顧名思義,是出如今系統邊界上的一個面向API的、串行集中式的強管控服務,這裏的邊界是企業IT系統的邊界,能夠理解爲企業級應用防火牆
,主要起到隔離外部訪問與內部系統的做用
。在微服務概念的流行以前,API網關就已經誕生了,例如銀行、證券等領域常見的前置機系統,它也是解決訪問認證、報文轉換、訪問統計等問題的。前端
API網關的流行,源於近幾年來,移動應用與企業間互聯需求的興起。移動應用、企業互聯,使得後臺服務支持的對象,從之前單一的Web應用,擴展到多種使用場景,且每種使用場景對後臺服務的要求都不盡相同。這不只增長了後臺服務的響應量,還增長了後臺服務的複雜性。隨着微服務架構概念的提出,API網關成爲了微服務架構的一個標配組件
。java
如上圖所示:網關該具有的最基本的四大功能:統一接入,流量管控,協議適配轉發,安全防禦。mysql
網關的技術選型nginx
SpringCloud-Zuul :git
社區活躍,基於 SrpingCloud 完整生態, 是構建微服務體系前置網關服務的最佳選型.github
Kong : 基於OpenResty的 API 網關服務和網關服務管理層.web
自建網關服務: 如 談談基於 OpenResty 的接口網關設計[https://www.zybuluo.com/yishuailuo/note/844059?utm_source=tool.lu]redis
網關的設計要素算法
系統級別spring
高可用性
均衡負載: 容錯,防止雪崩.
併發控制 : 錯峯流控
動態路由制定和修改
應用級別
監控統計
版本控制
認證 鑑權
數據安全: 防篡改,參數脫敏…
協議轉換: 如 HTTP => RPC協議.
其餘(我的 YY)
基於機器學習, 預測流量高峯.
網關(API Gateway)技術選型
zuul
kong
nginx+lua
網關(API Gateway)的設計要素
限流:實現微服務訪問流量計算,基於流量計算分析進行限流,能夠定義多種限流規則。
緩存:數據緩存。
日誌:日誌記錄。
監控:記錄請求響應數據,api耗時分析,性能監控。
鑑權:權限身份認證。
灰度:線上灰度部署,能夠減少風險。
路由:路由是API網關很核心的模塊功能,此模塊實現根據請求,鎖定目標微服務並將請求進行轉發。
簡單介紹下你的網關實施方案
開發語言:java + groovy,groovy的好處是網關服務不須要重啓就能夠動態的添加filter來實現一些功能;
微服務基礎框架:springboot;
網關基礎組件:netflix zuul;
服務註冊中心:consul;
權限校驗:jwt;
API監控:prometheus + grafana;
API統一日誌收集:logback + ELK;
壓力測試:Jmeter;
你好,請教一下: 1.爲何網關須要數據緩存,是由於須要鑑權,因此須要緩存用戶數據?還有其餘數據須要緩存麼? 2.網關路由方面,只須要作路由的轉發,仍是須要作服務的聚合? 謝謝
好比限流 你須要緩存一些限流的策略,主要是緩存網關功能用到的一些數據,不涉及業務數據。 路由主要是作轉發
目前,咱們業務代碼是多語言的環境,網關則是用go寫的,目前主要是作到了對於HTTP和Thrift的業務服務的轉發(HTTP利用了fasthttp,Thrift用的網關啓動客戶端調用業務服務端的形式)過濾器是環繞的,系通通一的過濾和針對API級別的過濾。雖然用了go比較輕巧,可是目前功能還很值得完善
go語言體積小,性能高。大家這個設計方案不錯,又支持多語言,對技術棧沒有限制,很值得借鑑。
設計要素:
#1,高可用很是重要;
#2,網關須要支持動態修改路由規則;
#3,與服務註冊中心整合,經過註冊中心實現路由轉發;
#4,過濾器鏈適配不一樣的路由。
以上是我的愚見
選型
所使用的網關架構必須靈活,由於咱們可能須要不少與咱們業務相關的定製話的東西
有平臺背書,獲取有足夠的證據證實他是一個能抗的住咱們需求的併發的性能
根據需求選擇最好的方案
設計要素
結構必須靈活,方便擴展
基礎的功能應該由框架提供或者抽象,好比動態路由,權限校驗,限流
個人
咱們使用zuul做爲網關並對他進行了必定定製化的開發,由於咱們使用springcloud技術棧,同時zuul基於filter來處理一切的結構也是很是靈活的,而且由netflix背書。咱們在網關利用filter加入權限校驗,統一訪問日誌記錄,訪問異常請求記錄,聚合請求處理器等相關功能
負載均衡能夠經過在以前加入一個nginx或者dns解析來作,高可用能夠經過keepalived加虛擬ip與nginx結合或者直接與zuul結合來作
1.能處理一些公共的邏輯,好比獲取token
2.能支持動態的修改路由規則
3.對各服務結果和異常進行統一處理後返給調用方
目前實施了幾套方案,本身封裝的gateway層,準備用zuul進行替代
首先是穩定且性能好,能支持海量請求,這次是安全,最後是功能完善且易於擴展。
網關智能路由,請求過濾,日誌記錄,流量控制,權限審查,負載均衡,實時監控,多協議支持等是一個好的網關方案應具有的。
目前公司採用zuul做爲網關的,作了路由轉發,權限控制,再加上自定義日誌記錄功能跟蹤後端服務執行狀況。
zuul多開,前端再加上nginx作端口轉發和靜態緩存,待解決高可用和偶爾超時問題。
在跟蹤後端執行狀況上增長了請求執行時間,後端服務地址,當前用戶,請求參數,錯誤截取等內容,以便後續跟蹤問題。
而後經過logback發給elk進行記錄分析監控
網關的技術選型
1. SpringCloud-Zuul :社區活躍,基於 SrpingCloud 完整生態, 是構建微服務體系前置網關服務的最佳選型.
2. Kong : 基於OpenResty的 API 網關服務和網關服務管理層.
3. Nginx+Lua:成熟度也算能夠
4. 自建網關:成本較高
網關(API Gateway)的設計要素(高可用,安全)
* 性能:API高可用,負載均衡,容錯機制。
* 安全:權限身份認證、脫敏,流量清洗,後端簽名(保證全鏈路可信調用),黑名單(非法調用的限制)。
* 日誌:日誌記錄(spainid,traceid)一旦涉及分佈式,全鏈路跟蹤必不可少。
* 緩存:數據緩存。
* 監控:記錄請求響應數據,api耗時分析,性能監控。
* 限流:流量控制,錯峯流控,目前有漏桶算法、令牌桶算法也能夠定製限流規則。
* 灰度:線上灰度部署,能夠減少風險。
* 路由:動態路由規則。
* 靜態:代理
簡單介紹下你的網關實施方案
* 微服務基礎框架:springboot;
* 網關基礎組件:zuul;
* 服務註冊中心:consul;
* API監控:prometheus + grafana or 自建;
* API統一日誌收集:時序db + ELK;
* 壓力測試:Jmeter,AB,阿里壓測;
網關(API Gateway)技術選型
zuul
nginx
網關(API Gateway)的設計要素
高可用
灰度
負載
限流
反向代理
簡單介紹下你的網關實施方案
Spring Cloud Zuul
開發環境使用Spring cloud 技術棧 使用zuul方便
在網關上作限流 負載 限流。網關做爲整個系統的入口不該該作不少事 身份認證 權限認證等都放在下面的聚合層
(Low逼的見解 大佬別噴)
首先,網關做爲微服務的入口,其併發壓力都在網關,當網關的併發能力沒法支撐用戶量的時候咱們就須要部署多個網關,而後在網關的前面加一個負載均衡服務器,可是在負載均衡服務器的選擇上不能簡單的選擇nginx,由於若是選擇nginx的話,客戶端鏈接nginx,nginx再鏈接網關,網關再鏈接後端的微服務,這樣下來光是TCP鏈接的三次握手就比較耗時,因此咱們應該在網關的前面選用支持四層負載均衡的服務器,譬如Haproxy或者lvs,使用他們的在tcp第一次握手的時候修改源IP地址和目的地址的模式,這樣客戶端至關於直連網關,能夠作到性能最大化。
網關(API Gateway)技術選型
咱們本身寫了一個,利用反射技術,鏈接同一個zk
網關(API Gateway)的設計要素
在庫裏邊配置相應的信息便可
group varchar(255) NOT NULL,
service varchar(255) NOT NULL,
method varchar(255) DEFAULT NULL,
parameter varchar(255) NOT NULL,
value varchar(255) NOT NULL,
is_deleted tinyint(4) NOT NULL DEFAULT ‘0’,
簡單介紹下你的網關實施方案
自有的devops 部署
1.首先部署多臺gateway的網關 固然都是zuul的,好比部署3臺,端口分別爲 90,91,92.
2.使用ngnix代理80端口,而後配置權重把請求分發到90,91,92端口的網關,api網關本身再路由
首先服務網關 = 路由轉發 + 過濾器
一、路由轉發:接收一切外界請求,轉發到後端的微服務上去;
二、過濾器:在服務網關中能夠完成一系列的橫切功能,例如權限校驗、限流以及監控等,這些均可以經過過濾器完成(其實路由轉發也是經過過濾器實現的)。
三、增長了網關,多了一層轉發(本來用戶請求直接訪問open-service便可),性能會降低一些(可是降低不大,一般,網關機器性能會很好,並且網關與open-service的訪問一般是內網訪問,速度很快);
四、 網關的單點問題:在整個網絡調用過程當中,必定會有一個單點,多是網關、nginx、dns服務器等。防止網關單點,能夠在網關層前邊再掛一臺nginx,nginx的性能極高,基本不會掛,這樣以後,網關服務就能夠不斷的添加機器。可是這樣一個請求就轉發了兩次,因此最好的方式是網關單點服務部署在一臺牛逼的機器上),並且nginx與zuul的性能比較其實相差不大,zuul是netflix開源的一個用來作網關的開源框架;
五、網關要儘可能輕。
http://www.spring4all.com/question/62
Nginx是由IgorSysoev爲俄羅斯訪問量第二的Rambler.ru站點開發的,一個高性能的HTTP和反向代理服務器。Ngnix一方面能夠作反向代理,另一方面作能夠作靜態資源服務器。
可是準確的來講,在我看來,這種方案不是真正意義上的網關,並且即便自研網關的目標也是幹掉Ngnix。
Kong是Mashape提供的一款API管理軟件,它自己是基於Ngnix+lua的,但比nginx提供了更簡單的配置方式,數據採用了 ApacheCassandra/PostgreSQL存儲,而且提供了一些優秀的插件,好比驗證,日誌,調用頻次限制等。
Kong的一個很是誘人的地方就是提供了大量的插件來擴展應用,經過設置不一樣的插件能夠爲服務提供各類加強的功能。Kong默認插件插件包括:
優勢:Kong自己也是基於Nginx的,因此在性能和穩定性上都沒有問題。Kong做爲一款商業軟件,在Nginx上作了很擴展工做,並且還有不少付費的商業插件。Kong自己也有付費的企業版,其中包括技術支持、使用培訓服務以及API 分析插件。
缺點:Kong的缺點就是,若是你使用Spring Cloud,Kong如何結合目前已有的服務治理體系?
Zuul 是Netflix公司開源的一個API網關組件,Spring Cloud對其進行二次基於Spring Boot的註解式封裝作到開箱即用。目前來講,結合Sring Cloud提供的服務治理體系,能夠作到請求轉發,根據配置的或者默認的路由規則進行路由和Load Balance,集成Hystrix。詳細能夠參考Spring Cloud Zuul的URL轉發和路由規則。
Spring Cloud Zuul處理每一個請求的方式是針對每一個請求是用一個線程來處理。PS,根據統計數據目前Zuul最多能達到(1000-2000)QPS。使用過Netty的都知道,通常都會使用Boos組和work組,一般狀況下,爲了提升性能,全部請求會被放處處理隊列中,從線程池中選取空閒線程來處理該請求。
Spring Cloud Zuul須要作一些灰度,降級,標籤路由,限流,WAF封禁,須要自定義Filter去或者作一些定製化實現。詳細文章能夠參考在Spring Cloud中實現降級之權重路由和標籤路由
雖然能夠經過自定義Filter實現,咱們想要的功能,可是因爲Zuul自己的設計和基於
單線程的接收請求和轉發處理
,在我看來目前來看Zuul 就顯得很雞肋,隨着Zuul2一直跳票,Spring Cloud推出本身的Spring Cloud Gateway.
The API Gateway is Dead! Long Live the API Gateway!
大意:Zuul已死,Spring Cloud Gateway永生。
A Gateway built on Spring Framework 5.0 and Spring Boot 2.0 providing routing and more。
Spring Cloud Gateway是基於Spring 框架5.0版本和Spring Boot 2.0的版本構建,提供路由等功能。
Spring Cloud GateWay具備如下特徵
因爲Spring 5.0支持Netty,Http2,而Spring Boot 2.0支持Spring 5.0,所以Spring Cloud Gateway支持Netty和Http2瓜熟蒂落。至於2017年Q4季度是否發佈完整的Spring Cloud Gateway咱們拭目以待,
可是至於最終落地看最終使用狀況
。
詳細信息能夠參考:Spring Cloud Gateway離開孵化器的變化
如上圖所示:Kong+Zuul實現的網關方案,在加上阿里雲的SLB,整個調用鏈路多了好幾層,爲何要這麼作呢?發揮Kong+Spring Cloud Zuul各自的優勢造成「聚合網關」。我的不建議這樣使用網關,所以自研網關中間件,顯得尤爲重要。
用Spring Cloud Zuul構建網關其實至關雞肋,好比動態Filter,好比標籤路由,降級,好比動態Filter,好比帶管控審計流程,易操做的UI界面等。
zuul是netfix的api 網關,主要特點有:filter的PRPE(pre,route,post,error)模型、groovy的fitler機制,其中spring cloud對其有比較好的擴展,可是spring cloud對其的擴展感受不是很完美,存在路由規則沒法只能是經過配置文件來存儲,而沒法動態配置的目的,其中有一我的寫了一個starter插件來解決路由規則配置到Cassandra的問題,詳細請看:將路由規則配置到KV分佈式存儲系統Cassandra
這裏主要是作了流控及協議轉化的工做,這裏主要是http->grpc的轉換;
LimitAccessFilter:利用redis令牌桶算法進行流控
GrpcRemoteRouteFilter:http轉化爲grpc的協議轉換
實現動態路由有兩種實現方式:
1.第一是DiscoveryClientRouteLocator的從新覆蓋,推薦是,Spring Cloud整合GRPC,REST協議適配轉發爲內部GRPC服務時採用此種方法擴展修改。
2.第二是實現了RefreshableRouteLocator接口,可以實現動態刷新,能夠參考 spring cloud Zuul動態路由
爲何要基於事件更新,原理以下所示:
在org.springframework.cloud.netflix.zuul.ZuulConfiguration.java中228-250行
@Configuration @EnableConfigurationProperties({ ZuulProperties.class }) @ConditionalOnClass(ZuulServlet.class) // Make sure to get the ServerProperties from the same place as a normal web app would @Import(ServerPropertiesAutoConfiguration.class) public class ZuulConfiguration { //zuul的配置信息,對應了application.properties或yml中的配置信息 @Autowired protected ZuulProperties zuulProperties; @Autowired protected ServerProperties server; @Autowired(required = false) private ErrorController errorController; @Bean public HasFeatures zuulFeature() { return HasFeatures.namedFeature("Zuul (Simple)", ZuulConfiguration.class); } @Bean @ConditionalOnMissingBean(RouteLocator.class) public RouteLocator routeLocator() { //默認配置的實現是SimpleRouteLocator.class return new SimpleRouteLocator(this.server.getServletPrefix(), this.zuulProperties); } @Bean public ZuulController zuulController() { return new ZuulController(); } @Bean public ZuulHandlerMapping zuulHandlerMapping(RouteLocator routes) { ZuulHandlerMapping mapping = new ZuulHandlerMapping(routes, zuulController()); mapping.setErrorController(this.errorController); return mapping; } //註冊了一個路由刷新監聽器,默認實現是ZuulRefreshListener.class @Bean public ApplicationListener<ApplicationEvent> zuulRefreshRoutesListener() { return new ZuulRefreshListener(); } @Bean @ConditionalOnMissingBean(name = "zuulServlet") public ServletRegistrationBean zuulServlet() { ServletRegistrationBean servlet = new ServletRegistrationBean(new ZuulServlet(), this.zuulProperties.getServletPattern()); // The whole point of exposing this servlet is to provide a route that doesn't // buffer requests. servlet.addInitParameter("buffer-requests", "false"); return servlet; } // pre filters @Bean public ServletDetectionFilter servletDetectionFilter() { return new ServletDetectionFilter(); } @Bean public FormBodyWrapperFilter formBodyWrapperFilter() { return new FormBodyWrapperFilter(); } @Bean public DebugFilter debugFilter() { return new DebugFilter(); } @Bean public Servlet30WrapperFilter servlet30WrapperFilter() { return new Servlet30WrapperFilter(); } // post filters @Bean public SendResponseFilter sendResponseFilter() { return new SendResponseFilter(); } @Bean public SendErrorFilter sendErrorFilter() { return new SendErrorFilter(); } @Bean public SendForwardFilter sendForwardFilter() { return new SendForwardFilter(); } @Configuration protected static class ZuulFilterConfiguration { @Autowired private Map<String, ZuulFilter> filters; @Bean public ZuulFilterInitializer zuulFilterInitializer() { return new ZuulFilterInitializer(this.filters); } } private static class ZuulRefreshListener implements ApplicationListener<ApplicationEvent> { @Autowired private ZuulHandlerMapping zuulHandlerMapping; private HeartbeatMonitor heartbeatMonitor = new HeartbeatMonitor(); @Override public void onApplicationEvent(ApplicationEvent event) { if (event instanceof ContextRefreshedEvent || event instanceof RefreshScopeRefreshedEvent || event instanceof RoutesRefreshedEvent) { this.zuulHandlerMapping.setDirty(true); } else if (event instanceof HeartbeatEvent) { if (this.heartbeatMonitor.update(((HeartbeatEvent) event).getValue())) { this.zuulHandlerMapping.setDirty(true); } } } } }
如上所示,當使用ApplicationEventPublisher發送的Event爲ContextRefreshedEvent,RefreshScopeRefreshedEvent,RoutesRefreshedEvent纔會通知Zuul去刷新路由。
此插件針對的spring cloud zuul版本比較老,所以須要對其進行改進,將路由配置能夠配置到mysql這樣的關係型數據庫中,詳細請看Zuul的改動點。
對DiscoveryClientRouteLocator的從新覆蓋,該類的做用就是從yml或屬性文件中讀取路由規則;
具體參看源碼org.springframework.cloud.netflix.zuul.filters.discovery.DiscoveryClientRouteLocator,主要方法以下,淺顯易懂,就不作多餘解釋。
@Override protected LinkedHashMap<String, ZuulRoute> locateRoutes() { LinkedHashMap<String, ZuulRoute> routesMap = new LinkedHashMap<String, ZuulRoute>(); routesMap.putAll(super.locateRoutes()); if (this.discovery != null) { Map<String, ZuulRoute> staticServices = new LinkedHashMap<String, ZuulRoute>(); for (ZuulRoute route : routesMap.values()) { String serviceId = route.getServiceId(); if (serviceId == null) { serviceId = route.getId(); } if (serviceId != null) { staticServices.put(serviceId, route); } } // Add routes for discovery services by default List<String> services = this.discovery.getServices(); String[] ignored = this.properties.getIgnoredServices() .toArray(new String[0]); for (String serviceId : services) { // Ignore specifically ignored services and those that were manually // configured String key = "/" + mapRouteToService(serviceId) + "/**"; if (staticServices.containsKey(serviceId) && staticServices.get(serviceId).getUrl() == null) { // Explicitly configured with no URL, cannot be ignored // all static routes are already in routesMap // Update location using serviceId if location is null ZuulRoute staticRoute = staticServices.get(serviceId); if (!StringUtils.hasText(staticRoute.getLocation())) { staticRoute.setLocation(serviceId); } } if (!PatternMatchUtils.simpleMatch(ignored, serviceId) && !routesMap.containsKey(key)) { // Not ignored routesMap.put(key, new ZuulRoute(key, serviceId)); } } } if (routesMap.get(DEFAULT_ROUTE) != null) { ZuulRoute defaultRoute = routesMap.get(DEFAULT_ROUTE); // Move the defaultServiceId to the end routesMap.remove(DEFAULT_ROUTE); routesMap.put(DEFAULT_ROUTE, defaultRoute); } LinkedHashMap<String, ZuulRoute> values = new LinkedHashMap<>(); for (Entry<String, ZuulRoute> entry : routesMap.entrySet()) { String path = entry.getKey(); // Prepend with slash if not already present. if (!path.startsWith("/")) { path = "/" + path; } if (StringUtils.hasText(this.properties.getPrefix())) { path = this.properties.getPrefix() + path; if (!path.startsWith("/")) { path = "/" + path; } } values.put(path, entry.getValue()); } return values; }
數據變動對網關的穩定性來講,也是一個很大的挑戰。當對路由信息進行CRUD操做以後,須要Spring Cloud Zuul從新刷新路由規則,實現方式經過spring的event來實現。
1.實現基於ApplicationEventPublisherAware的事件生產者的代碼片斷
private ApplicationEventPublisher publisher; publisher.publishEvent(new InstanceRegisteredEvent<>(this, this.environment));
2.Spring Cloud netflix內部的事件消費者
org.springframework.cloud.netflix.zuul.RoutesRefreshedEvent
@SuppressWarnings("serial") public class RoutesRefreshedEvent extends ApplicationEvent { private RouteLocator locator; public RoutesRefreshedEvent(RouteLocator locator) { super(locator); this.locator = locator; } public RouteLocator getLocator() { return this.locator; } }
因爲Spring Cloud Gateway未徹底成熟,並且性能,穩定性等,如今無從考證,沒有使用案例
,基於Spring Cloud Gateway方案構建本身的網關風險比較大
,並且PS不知道到年末是否成熟可用
。故在這裏不作過多說明。
能夠參考架構圖以下:
企業級API網關的設計
微服務與API 網關(上): 爲何須要API網關?
http://blog.csdn.net/u013815546/article/details/68944039
http://www.javashuo.com/article/p-ntexbnsy-gg.html
http://xujin.org/janus/gw-solution/?from=timeline