Spring Cloud構(gòu)建微服務(wù)架構(gòu)之七 服務(wù)網(wǎng)關(guān)

本文完全參照并抄寫了翟永超博客: Spring Cloud構(gòu)建微服務(wù)架構(gòu)(五)服務(wù)網(wǎng)關(guān)
服務(wù)網(wǎng)關(guān)是微服務(wù)架構(gòu)中一個非常重要的部分。通過服務(wù)網(wǎng)關(guān)統(tǒng)一向外系統(tǒng)提供REST API的過程中,除了具備服務(wù)路由、均衡負(fù)載功能之外,它還具備了權(quán)限控制等功能。Spring Cloud Netflix中的Zuul就擔(dān)任了這樣的一個角色,為微服務(wù)架構(gòu)提供了前門保護(hù)的作用,同時將權(quán)限控制這些較重的非業(yè)務(wù)邏輯內(nèi)容遷移到服務(wù)路由層面,使得服務(wù)集群主體能夠具備更高的可復(fù)用性和可測試性。

我們通過實例例子來使用一下Zuul來作為服務(wù)的路有功能。

準(zhǔn)備工作

在使用Zuul之前,需要先構(gòu)建一個服務(wù)注冊中心、以及兩個簡單的服務(wù),構(gòu)建了一個service-A,一個service-B。然后啟動eureka-server和這兩個服務(wù)。通過訪問eureka-server,我們可以看到service-A和service-B已經(jīng)注冊到了服務(wù)中心。

開始使用Zuul

  1. 引入依賴spring-cloud-starter-zuul、spring-cloud-starter-eureka,如果不是通過指定serviceId的方式,eureka依賴不需要,但是為了對服務(wù)集群細(xì)節(jié)的透明性,還是用serviceId來避免直接引用url的方式吧。
<properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
        <spring-cloud.version>Dalston.RELEASE</spring-cloud.version>
    </properties>

    <dependencies>
        <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>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
  1. 應(yīng)用主類使用@EnableZuulProxy注解開啟Zuul
@EnableZuulProxy
@SpringCloudApplication
public class ZuulGatewayApplication {

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

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

  1. application.properties中配置Zuul應(yīng)用的基礎(chǔ)信息,如:應(yīng)用名、服務(wù)端口等。
spring.application.name=api-gateway
server.port=5555

Zuul配置
完成上面的工作后,Zuul已經(jīng)可以運行了,但是如何讓它為我們的微服務(wù)集群服務(wù),還需要我們另行配置,下面詳細(xì)的介紹一些常用配置內(nèi)容。
服務(wù)路由
通過服務(wù)路由的功能,我們在對外提供服務(wù)的時候,只需要通過暴露Zuul中配置的調(diào)用地址就可以讓調(diào)用方統(tǒng)一的來訪問我們的服務(wù),而不需要了解具體提供服務(wù)的主機(jī)信息了。
在Zuul中提供了兩種映射方式:
通過url直接映射,我們可以如下配置:

# routes to url
zuul.routes.api-a-url.path=/api-a-url/**
zuul.routes.api-a-url.url=http://localhost:2222/

該配置,定義了,所有到Zuul的中規(guī)則為:/api-a-url/**
的訪問都映射到http://localhost:2222/
上,也就是說當(dāng)我們訪問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部分為路由的名字,可以任意定義,但是一組映射關(guān)系的path和url要相同,下面講serviceId時候也是如此。
通過url映射的方式對于Zuul來說,并不是特別友好,Zuul需要知道我們所有為服務(wù)的地址,才能完成所有的映射配置。而實際上,我們在實現(xiàn)微服務(wù)架構(gòu)時,服務(wù)名與服務(wù)實例地址的關(guān)系在eureka server中已經(jīng)存在了,所以只需要將Zuul注冊到eureka server上去發(fā)現(xiàn)其他服務(wù),我們就可以實現(xiàn)對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/

針對我們在準(zhǔn)備工作中實現(xiàn)的兩個微服務(wù)service-A和service-B,定義了兩個路由api-a和api-b來分別映射。另外為了讓Zuul能發(fā)現(xiàn)service-A和service-B,也加入了eureka的配置。
接下來,我們將eureka-server、service-A、service-B以及這里用Zuul實現(xiàn)的服務(wù)網(wǎng)關(guān)啟動起來,在eureka-server的控制頁面中,我們可以看到分別注冊了service-A、service-B以及api-gateway

圖片.png

嘗試通過服務(wù)網(wǎng)關(guān)來訪問service-A和service-B,根據(jù)配置的映射關(guān)系,分別訪問下面的url
http://localhost:5555/api-a/add?a=1&b=2:通過serviceId映射訪問service-A中的add服務(wù)
http://localhost:5555/api-b/add?a=1&b=2:通過serviceId映射訪問service-B中的add服務(wù)
http://localhost:5555/api-a-url/add?a=1&b=2:通過url映射訪問service-A中的add服務(wù)

注意:推薦使用serviceId的映射方式,除了對Zuul維護(hù)上更加友好之外,serviceId映射方式還支持了斷路器,對于服務(wù)故障的情況下,可以有效的防止故障蔓延到服務(wù)網(wǎng)關(guān)上而影響整個系統(tǒng)的對外服務(wù)

服務(wù)過濾
在完成了服務(wù)路由之后,我們對外開放服務(wù)還需要一些安全措施來保護(hù)客戶端只能訪問它應(yīng)該訪問到的資源。所以我們需要利用Zuul的過濾器來實現(xiàn)我們對外服務(wù)的安全控制。
在服務(wù)網(wǎng)關(guān)中定義過濾器只需要繼承ZuulFilter
抽象類實現(xiàn)其定義的四個抽象函數(shù)就可對請求進(jìn)行攔截與過濾。
下面的例子,定義了一個Zuul過濾器,實現(xiàn)了在請求被路由之前檢查請求中是否有accessToken
參數(shù),若有就進(jìn)行路由,若沒有就拒絕訪問,返回401 Unauthorized錯誤。

public class AccessFilter extends ZuulFilter  {

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

    @Override
    public String filterType() {
        return "pre";
    }

    @Override
    public int filterOrder() {
        return 0;
    }

    @Override
    public boolean shouldFilter() {
        return true;
    }

    @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;
    }

}

自定義過濾器的實現(xiàn),需要繼承ZuulFilter,需要重寫實現(xiàn)下面四個方法:

1. filterType:返回一個字符串代表過濾器的類型,在zuul中定義了四種不同生命周期的過濾器類型,具體如下:
    pre:可以在請求被路由之前調(diào)用
    routing:在路由請求時候被調(diào)用
    post:在routing和error過濾器之后被調(diào)用
    error:處理請求時發(fā)生錯誤時被調(diào)用
2. filterOrder:通過int值來定義過濾器的執(zhí)行順序
3. shouldFilter:返回一個boolean類型來判斷該過濾器是否要執(zhí)行,所以通過此函數(shù)可實現(xiàn)過濾器的開關(guān)。在上例中,我們直接返回true,所以該過濾器總是生效。
4. run:過濾器的具體邏輯。需要注意,這里我們通過ctx.setSendZuulResponse(false)令zuul過濾該請求,不對其進(jìn)行路由,然后通過ctx.setResponseStatusCode(401)設(shè)置了其返回的錯誤碼,當(dāng)然我們也可以進(jìn)一步優(yōu)化我們的返回,比如,通過ctx.setResponseBody(body)對返回body內(nèi)容進(jìn)行編輯等。

在實現(xiàn)了自定義過濾器之后,還需要實例化該過濾器才能生效,我們只需要在應(yīng)用主類中增加如下內(nèi)容:

@EnableZuulProxy
@SpringCloudApplication
public class ZuulGatewayApplication {

    public static void main(String[] args) {
        //SpringApplication.run(ZuulGatewayApplication.class, args);
        new SpringApplicationBuilder(ZuulGatewayApplication.class).web(true).run(args);
    }
    
    @Bean
    public AccessFilter accessFilter() {
        return new AccessFilter();
    }
}

啟動該服務(wù)網(wǎng)關(guān)后,訪問:

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,并返回計算內(nèi)容

工程請參見:
ComputeService-A
ComputeService-B
zuul-gateway

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,002評論 6 542
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,400評論 3 429
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,136評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,714評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 72,452評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,818評論 1 328
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,812評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,997評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,552評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 41,292評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,510評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,035評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,721評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,121評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,429評論 1 294
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,235評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 48,480評論 2 379

推薦閱讀更多精彩內(nèi)容