服務網關


微服務架構

使用Spring Cloud Netflix中的Eureka實現了服務注冊中心以及服務注冊與發現;

而服務間通過Ribbon或Feign實現服務的消費以及均衡負載;

通過Spring Cloud Config實現了應用多環境的外部化配置以及版本管理。

為了使得服務集群更為健壯,使用Hystrix的融斷機制來避免在微服務架構中個別服務出現異常時引起的故障蔓延。

內部服務Service A和Service B,他們都會注冊與訂閱服務至Eureka Server,而Open Service是一個對外的服務,通過均衡負載公開至服務調用方

這樣架構需要做的一些事兒以及存在的不足

  • 首先,破壞了服務無狀態特點。為了保證對外服務的安全性,我們需要實現對服務訪問的權限控制,而開放服務的權限控制機制將會貫穿並污染整個開放服務的業務邏輯,這會帶來的最直接問題是,破壞了服務集群中REST API無狀態的特點
  • 其次,無法直接復用既有接口。當我們需要對一個即有的集群內訪問接口,實現外部服務訪問時,我們不得不通過在原有接口上增加校驗邏輯,或增加一個代理調用來實現權限控制,無法直接復用原有的接口。

 為了解決上面這些問題,我們需要將權限控制這樣的東西從我們的服務單元中抽離出去,而最適合這些邏輯的地方就是處於對外訪問最前端的地方,我們需要一個更強大一些的均衡負載器,它就是本文將來介紹的:服務網關。

 通過服務網關統一向外系統提供REST API的過程中,除了具備服務路由、均衡負載功能之外,它還具備了權限控制等功能。Spring Cloud Netflix中的Zuul就擔任了這樣的一個角色,為微服務架構提供了前門保護的作用,同時將權限控制這些較重的非業務邏輯內容遷移到服務路由層面,使得服務集群主體能夠具備更高的可復用性和可測試性。

 

開始使用Zuul

pom.xml依賴

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-zuul</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>

 

應用主類使用@EnableZuulProxy注解開啟Zuul

@EnableZuulProxy //開啟Zuul
@SpringCloudApplication
public class Application {
    public static void main(String[] args) {
        new SpringApplicationBuilder(Application.class).web(true).run(args);
    }
}

 @SpringCloudApplication注解,它整合了@SpringBootApplication@EnableDiscoveryClient@EnableCircuitBreaker,主要目的還是簡化配置

 

 application.properties中配置Zuul應用的基礎信息,如:應用名、服務端口等 

spring.application.name=api-gateway
server.port=5555

服務路由

 通過服務路由的功能,我們在對外提供服務的時候,只需要通過暴露Zuul中配置的調用地址就可以讓調用方統一的來訪問我們的服務,而不需要了解具體提供服務的主機信息了.

 在Zuul中提供了兩種映射方式:

  • 第一種:通過url直接映射,我們可以如下配置:
# 路由到url 規則定義
zuul.routes.api-a-url.path=/api-a-url/**
zuul.routes.api-a-url.url=http://localhost:2222/

該配置,定義了,所有到Zuul的中規則為:/api-a-url/**的訪問都映射到http://localhost:2222/,也就是說當我們訪問http://localhost:5555/api-a-url/add?a=1&b=2的時候,Zuul會將該請求路由到:http://localhost:2222/add?a=1&b=2上。

配置屬性zuul.routes.api-a-url.path中的api-a-url部分為路由的名字,可以任意定義,但是一組映射關系的path和url要相同.

  • 第二種:可以如下配置

通過url映射的方式對於Zuul來說,並不是特別友好,Zuul需要知道我們所有為服務的地址,才能完成所有的映射配置。而實際上,我們在實現微服務架構時,服務名與服務實例地址的關系在eureka server中已經存在了,所以只需要將Zuul注冊到eureka server上去發現其他服務,我們就可以實現對serviceId的映射

zuul.routes.api-a.path=/api-a/**
zuul.routes.api-a.serviceId=service-A

zuul.routes.api-b.path=/api-b/**
zuul.routes.api-b.serviceId=service-B

eureka.client.serviceUrl.defaultZone=http://localhost:1111/eureka/

 

嘗試通過服務網關來訪問service-A和service-B,根據配置的映射關系,分別訪問下面的url

  • http://localhost:5555/api-a/add?a=1&b=2:通過serviceId映射訪問service-A中的add服務
  • http://localhost:5555/api-b/add?a=1&b=2:通過serviceId映射訪問service-B中的add服務
  • http://localhost:5555/api-a-url/add?a=1&b=2:通過url映射訪問service-A中的add服務

推薦使用serviceId的映射方式,除了對Zuul維護上更加友好之外,serviceId映射方式還支持了斷路器,對於服務故障的情況下,可以有效的防止故障蔓延到服務網關上而影響整個系統的對外服務

服務過濾

在完成了服務路由之后,我們對外開放服務還需要一些安全措施來保護客戶端只能訪問它應該訪問到的資源。所以我們需要利用Zuul的過濾器來實現我們對外服務的安全控制。

在服務網關中定義過濾器只需要繼承ZuulFilter抽象類實現其定義的四個抽象函數就可對請求進行攔截與過濾

 

/**
 * 利用Zuul的過濾器來實現我們對外服務的安全控制
 *
 * 定義了一個Zuul過濾器
 * 實現了在請求被路由之前檢查請求中是否有accessToken參數,
 * 若有就進行路由,若沒有就拒絕訪問,返回401 Unauthorized錯誤。
 */
public class AccessFilter extends ZuulFilter {

    private static Logger log = LoggerFactory.getLogger(AccessFilter.class);

    /**
     * 返回一個字符串代表過濾器的類型,在zuul中定義了四種不同生命周期的過濾器類型,具體如下:
     * pre:可以在請求被路由之前調用
     * routing:在路由請求時候被調用
     * post:在routing和error過濾器之后被調用
     * error:處理請求時發生錯誤時被調用
     * 
     * @return
     */
    @Override
    public String filterType() {
        return "pre";
    }

    /**
     * 通過int值來定義過濾器的執行順序
     * @return
     */
    @Override
    public int filterOrder() {
        return 0;
    }

    /**
     * 返回一個boolean類型來判斷該過濾器是否要執行,所以通過此函數可實現過濾器的開關。
     * 直接返回true,所以該過濾器總是生效
     * @return
     */
    @Override
    public boolean shouldFilter() {
        return true;
    }

    /**
     * 過濾器的具體邏輯
     *
     * 通過ctx.setSendZuulResponse(false)令zuul過濾該請求,不對其進行路由,
     * 然后通過ctx.setResponseStatusCode(401)設置了其返回的錯誤碼,
     * 當然我們也可以進一步優化我們的返回,
     * 比如,通過ctx.setResponseBody(body)對返回body內容進行編輯等。 中文亂碼:ctx.getResponse().setContentType("text/html;charset=UTF-8");
     * @return
     */
    @Override
    public Object run() {
        RequestContext ctx = RequestContext.getCurrentContext();
        HttpServletRequest request = ctx.getRequest();

        log.info(String.format("%s request to %s", request.getMethod(), request.getRequestURL().toString()));

        Object accessToken = request.getParameter("accessToken");
        if (accessToken == null) {
            log.warn("access token is empty");
            ctx.setSendZuulResponse(false);// 不對其路由
            ctx.setResponseStatusCode(401);//返回相應的錯誤碼
            return null;
        }
        log.info("access token ok");
        return null;
    }

}

 

 

在實現了自定義過濾器之后,還需要實例化該過濾器才能生效

@EnableZuulProxy
@SpringCloudApplication
public class Application {

    public static void main(String[] args) {
        new SpringApplicationBuilder(Application.class).web(true).run(args);
    }

    //在實現了自定義過濾器之后,還需要實例化該過濾器才能生效
    @Bean
    public AccessFilter accessFilter() {
        return new AccessFilter();
    }

}

啟動該服務網關后,訪問:

  • http://localhost:5555/api-a/add?a=1&b=2:返回401錯誤
  • http://localhost:5555/api-a/add?a=1&b=2&accessToken=token:正確路由到server-A,並返回計算內容

 

filterType生命周期介紹

 

 

 總結一下為什么服務網關是微服務架構的重要部分

  • 不僅僅實現了路由功能來屏蔽諸多服務細節,更實現了服務級別、均衡負載的路由
  • 實現了接口權限校驗與微服務業務邏輯的解耦。通過服務網關中的過濾器,在各生命周期中去校驗請求的內容,將原本在對外服務層做的校驗前移,保證了微服務的無狀態性,同時降低了微服務的測試難度,讓服務本身更集中關注業務邏輯的處理。
  • 實現了斷路器,不會因為具體微服務的故障而導致服務網關的阻塞,依然可以對外服務

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM