學習SpringCloud之服務網關Zuul

簡介

  • 什麼是服務網關?
    實現統一入口,接收所有的請求,並根據定義的規則轉發到相應的服務上。
    在此過程中還可以完成系統中一些通用統一的工作,如權限校驗,限流等。

  • 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分爲路由和過濾器兩個部分來說。

  • 1. 路由

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可以查看。
{
	"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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章