簡介
-
什麼是服務網關?
實現統一入口,接收所有的請求,並根據定義的規則轉發到相應的服務上。
在此過程中還可以完成系統中一些通用統一的工作,如權限校驗,限流等。 -
Zuul就是NetFlix提供的一個服務網關,用於實現路由、過濾器等功能。
Netflix uses Zuul for the following:
- Authentication
- Insights
- Stress Testing
- Canary Testing
- Dynamic Routing
- Service Migration
- Load Shedding
- Security
- Static Response handling
- Active/Active traffic management
以下示例均基於SpringCloud的Greenwich.SR1版本,且需要依賴到之前介紹SpringCloud相關的文章
基礎依賴
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
</dependencies>
Zuul
和其他組件一樣,啓動一個SpringBoot的應用,啓用Zuul需要在啓動類中增加 @EnableZuulProxy 註解。
@SpringBootApplication
@EnableZuulProxy
class ZuulServerStarter
fun main(args: Array<String>) {
runApplication<ZuulServerStarter>(*args)
}
配置application.yml
也是需要指定Eureka註冊中心。
server:
port: 6606
eureka:
instance:
hostname: localhost
client:
serviceUrl:
defaultZone: http://localhost:6600/eureka/
spring:
application:
name: zuul-server
我們先把介紹Ribbon文章中的服務都啓動。
接下來我們將Zuul分爲路由和過濾器兩個部分來說。
在application.yml
增加以下Zuul的配置。
zuul:
routes:
# 可以直接指定服務的URL
api:
path: /api/**
url: http://localhost:6603/
# 也可以指定服務的Id,一般爲服務名。
lbapi:
path: /lbapi/**
serviceId: ribbon-client
則將/api
的請求轉發到端口爲6603的Eureka-Client上。將/lbapi
的請求轉發到Ribbon-Client上。
啓動應用,多次訪問http://localhost:6606/api/hello?name=czb1n
,頁面只會顯示response from 6603: hello czb1n.
,
而訪問http://localhost:6606/lbapi/hello?name=czb1n
,頁面會輪流顯示response from 6603: hello czb1n.
和response from 6604: hello czb1n.
。
通過查看各個服務的日誌,也能印證配置路由轉發成功。
增加actuator的配置
management:
endpoints:
web:
exposure:
include: routes
訪問http://localhost:6606/actuator/routes
可以查看路由列表。
{
"/api/**": "http://localhost:6603/",
"/lbapi/**": "ribbon-client",
"/eureka-client/**": "eureka-client",
"/ribbon-client/**": "ribbon-client"
}
-
2. 過濾器
- Zuul有提供一些默認的過濾器,在acturator的include配置項中增加filters後,訪問
http://localhost:6606/actuator/filters
可以查看。
- Zuul有提供一些默認的過濾器,在acturator的include配置項中增加filters後,訪問
{
"error": [{
"class": "org.springframework.cloud.netflix.zuul.filters.post.SendErrorFilter",
"order": 0,
"disabled": false,
"static": true
}],
"post": [{
"class": "org.springframework.cloud.netflix.zuul.filters.post.SendResponseFilter",
"order": 1000,
"disabled": false,
"static": true
}],
"pre": [{
"class": "org.springframework.cloud.netflix.zuul.filters.pre.DebugFilter",
"order": 1,
"disabled": false,
"static": true
}, {
"class": "org.springframework.cloud.netflix.zuul.filters.pre.FormBodyWrapperFilter",
"order": -1,
"disabled": false,
"static": true
}, {
"class": "org.springframework.cloud.netflix.zuul.filters.pre.Servlet30WrapperFilter",
"order": -2,
"disabled": false,
"static": true
}, {
"class": "org.springframework.cloud.netflix.zuul.filters.pre.ServletDetectionFilter",
"order": -3,
"disabled": false,
"static": true
}, {
"class": "org.springframework.cloud.netflix.zuul.filters.pre.PreDecorationFilter",
"order": 5,
"disabled": false,
"static": true
}],
"route": [{
"class": "org.springframework.cloud.netflix.zuul.filters.route.SimpleHostRoutingFilter",
"order": 100,
"disabled": false,
"static": true
}, {
"class": "org.springframework.cloud.netflix.zuul.filters.route.RibbonRoutingFilter",
"order": 10,
"disabled": false,
"static": true
}, {
"class": "org.springframework.cloud.netflix.zuul.filters.route.SendForwardFilter",
"order": 500,
"disabled": false,
"static": true
}]
}
- 自定義過濾器可以通過創建繼承ZuulFilter的Bean來實現。
@Component
class DemoFilter : ZuulFilter() {
// 當 shouldFilter() 爲true時執行。
override fun run(): Any? {
// 這裏只是舉個例子來模擬權限驗證。
// 當請求參數中的 name 不爲 czb1n 時則認爲沒有訪問權限。
val ctx = RequestContext.getCurrentContext()
val request = ctx.request
val response = ctx.response
if (request.getParameter("name") != "czb1n") {
ctx.responseStatusCode = HttpStatus.UNAUTHORIZED.value()
// 默認爲 true ,爲 false 則會過濾此請求,不會分配到路由。
ctx.setSendZuulResponse(false)
response.writer.write("Sorry, I don't know you.")
}
return null
}
override fun shouldFilter(): Boolean {
// 是否需要執行過濾
return true
}
override fun filterType(): String {
// 過濾器的類型
return PRE_TYPE
}
override fun filterOrder(): Int {
// 過濾器執行的順序,由小到大。
return 0
}
}
重新啓動,訪問http://localhost:6606/api/hello?name=czb1n
結果與原來一致,訪問http://localhost:6606/api/hello?name=anyone
則會顯示Sorry, I don't know you.
- 除了一般的過濾器以外,Zuul還包含了Hystrix,要爲路由配置fallback可以通過創建一個繼承FallbackProvider的Bean來實現。
@Component
class DemoFallbackProvider : FallbackProvider {
override fun getRoute(): String {
// 匹配所有的路由,如果要匹配單個路由則需要返回路由的Id
return "*"
}
override fun fallbackResponse(route: String?, cause: Throwable?): ClientHttpResponse {
return object : ClientHttpResponse {
@Throws(IOException::class)
override fun getStatusCode(): HttpStatus {
return HttpStatus.OK
}
@Throws(IOException::class)
override fun getRawStatusCode(): Int {
return HttpStatus.OK.value()
}
@Throws(IOException::class)
override fun getStatusText(): String {
return HttpStatus.OK.reasonPhrase
}
override fun close() {}
@Throws(IOException::class)
override fun getBody(): InputStream {
return ByteArrayInputStream("fallback response.".toByteArray())
}
override fun getHeaders(): HttpHeaders {
return HttpHeaders()
}
}
}
}
重啓Zuul-Server後,停掉Ribbon-Client服務。
再訪問http://localhost:6606/lbapi/hello?name=czb1n
頁面會顯示fallback response.
。
其他
示例代碼地址: https://github.com/czb1n/learn-spring-cloud-with-kotlin