1. 程式人生 > >spring cloud demo eureka and zuul

spring cloud demo eureka and zuul

Spring Cloud簡介

        Spring Cloud是一個基於Spring Boot實現的雲應用開發工具,它為基於JVM的雲應用開發中的配置管理、服務發現、斷路器、智慧路由、微代理、控制匯流排、全域性鎖、決策競選、分散式會話和叢集狀態管理等操作提供了一種簡單的開發方式。

        Spring Cloud包含了多個子專案(針對分散式系統中涉及的多個不同開源產品),比如:Spring Cloud Config、Spring Cloud Netflix、Spring Cloud CloudFoundry、Spring Cloud AWS、Spring Cloud Security、Spring Cloud Commons、Spring Cloud Zookeeper、Spring Cloud CLI等專案。

        本文將介紹基於springBoot2.0正式版的springCloud的微服務搭建以及需要注意的細節.

1.服務註冊與發現

        在簡單介紹了Spring Cloud和微服務架構之後,下面迴歸本文的主旨內容,如何使用Spring Cloud搭建服務註冊與發現模組。

        這裡我們會用到Spring Cloud Netflix,該專案是Spring Cloud的子專案之一,主要內容是對Netflix公司一系列開源產品的包裝,它為Spring Boot應用提供了自配置的Netflix OSS整合。通過一些簡單的註解,開發者就可以快速的在應用中配置一下常用模組並構建龐大的分散式系統。它主要提供的模組包括:服務發現(Eureka),斷路器(Hystrix),智慧路有(Zuul),客戶端負載均衡(Ribbon)等。

        所以,我們這裡的核心內容就是服務發現模組:Eureka。下面我們動手來做一些嘗試。

建立“服務註冊中心”

 1.1 建立springboot專案

http://start.spring.io/ 自定義spring boot線上maven構建工具很方便(預設2.0版本)

1.2 修改pom檔案,新增spring cloud 依賴如下

	<dependencies>
		<!-- springCloud 配置 -->
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-config</artifactId>
		</dependency>	
	
		<!-- springCloud注測中心服務 -->
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-netflix-eureka-server</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.3 在啟動類上加上註解 如下

        通過@EnableEurekaServer註解啟動一個服務註冊中心提供給其他應用進行對話。這一步非常的簡單,只需要在一個普通的Spring Boot應用中新增這個註解就能開啟此功能,比如下面的例子:

@SpringBootApplication
@EnableEurekaServer
public class Demo5Application {
 
	public static void main(String[] args) {
		SpringApplication.run(Demo5Application.class, args);
	}
}

1.4 配置檔案application.properties

        在預設設定下,該服務註冊中心也會將自己作為客戶端來嘗試註冊它自己,所以我們需要禁用它的客戶端註冊行為,只需要在application.properties中問增加如下配置:

#註冊中心服務ID
spring.application.name=compute-server
 
#埠號
server.port=1111
# eureka.client.registerWithEureka :表示是否將自己註冊到Eureka Server,預設為true。
# 由於當前這個應用就是Eureka Server,故而設為false  
eureka.client.register-with-eureka=false
# eureka.client.fetchRegistry :表示是否從Eureka Server獲取註冊資訊,預設為true。因為這是一個單點的Eureka Server,
# 不需要同步其他的Eureka Server節點的資料,故而設為false。  
eureka.client.fetch-registry=false
# eureka.client.serviceUrl.defaultZone :設定與Eureka Server互動的地址,查詢服務和註冊服務都需要依賴這個地址。預設是
eureka.client.serviceUrl.defaultZone=http://localhost:1111/eureka/
1.5配置檔案application.properties

        為了與後續要進行註冊的服務區分,這裡將服務註冊中心的埠通過server.port屬性設定為1111

可以看到下面的頁面,其中還沒有發現任何服務:

2.搭建服務端

    2.1 建立springboot專案同上

    2.2 修改pom.xml檔案,新增spring cloud 依賴如下(紅色標記一定要新增)

        <dependencies>
	
		    <dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter</artifactId>
		</dependency>
	
	
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-config</artifactId>
		</dependency>
	
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
		</dependency>
		 
 
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
		
		
		<dependency>    
                        <groupId>org.springframework.boot</groupId>    
                        <artifactId>spring-boot-starter-web</artifactId>    
                </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>

2.3 在啟動類上加上註解 如下

        最後在主類中通過加上@EnableEurekaClient 註解,該註解能啟用Eureka中的對註冊中心註冊服務實現,才能實現Controller中對服務資訊的輸出。

@SpringBootApplication
@EnableEurekaClient  
public class Demo3Application {
 
	public static void main(String[] args) {
		SpringApplication.run(Demo3Application.class, args);
	}
}

2.4 配置檔案application.properties

#服務名稱  
spring.application.name=compute-service1  
#埠號  
server.port=2222  
#在註冊中心中進行註冊
eureka.client.serviceUrl.defaultZone=http://localhost:1111/eureka/ 
#啟動服務發現的功能,開啟了才能呼叫其它服務 
spring.cloud.config.discovery.enabled=true
#發現的服務的名字--對應注測中心的服務名字
spring.cloud.config.discovery.serviceId=compute-server 

        通過spring.application.name屬性,我們可以指定微服務的名稱後續在呼叫的時候只需要使用該名稱就可以進行服務的訪問。

    eureka.client.serviceUrl.defaultZone屬性對應服務註冊中心的配置內容,指定服務註冊中心的位置。

          為了在本機上測試區分服務提供方和服務註冊中心,使用server.port屬性設定不同的埠。

2.5 啟動該專案

        可以看到,我們定義的服務被註冊了。如下圖所示:

3 spring cloud 路由閘道器服務---Zuul

        在使用Zuul之前,我們先構建一個服務註冊中心、以及兩個簡單的服務,比如:我構建了一個service-A,一個service-B。然後啟動eureka-server和這兩個服務。通過訪問eureka-server,我們可以看到service-A和service-B已經註冊到了服務中心。

服務service-A和service-B的配置都一樣並實現在註冊中心註冊,只有埠號不一樣而已如下所示:

service-A:

並在service-A:下構建一個controller如下:

package com.example.demo.controller;
 
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
 
@RestController
public class MyController {
	@RequestMapping(value = "/info" ,method = RequestMethod.GET)
    public String info() {  
        return "hello I am is spring-serviceA"; //測試程式碼直接返回一個字串,不再呼叫service層等等。  
    }
}

service-B:

並在service-B下構建一個controller如下:

package com.example.demo.controller;
 
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
 
@RestController
public class MyController {
	@RequestMapping(value = "/info" ,method = RequestMethod.GET)
    public String info() {  
        return "hello I am is spring-service"; //測試程式碼直接返回一個字串,不再呼叫service層等等。  
    }
}

    3.1 建立 spring boot專案 同上

    3.2 修改pom.xml檔案,新增spring cloud 依賴如下

	<dependencies>
		<!-- springBoot 核心 -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter</artifactId>
		</dependency>
		
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-config</artifactId>
		</dependency>
	
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
		</dependency>
		 
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
		</dependency>
 
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>

3.3 在啟動類上加上註解 如下

  • 應用主類使用@EnableZuulProxy註解開啟Zuul
  • 這裡用了@SpringCloudApplication註解,之前沒有提過,通過原始碼我們看到,它整合了@SpringBootApplication、@EnableEurekaClient、@EnableCircuitBreaker,主要目的還是簡化配置。這幾個註解的具體作用這裡就不做詳細介紹了,之前的文章已經都介紹過。
@EnableZuulProxy
@SpringCloudApplication	
public class Demo4Application {
 
	public static void main(String[] args) {
		SpringApplication.run(Demo4Application.class, args);
	}
}

3.4 配置檔案application.properties

application.properties中配置eureka服務註冊中心

eureka.client.serviceUrl.defaultZone=http://localhost:1111/eureka/  
server.port=3333  
spring.application.name=service-zuul  
#表示只要訪問以/api-a/開頭的多層目錄都可以路由到 id為compute-service的服務上  
zuul.routes.compute-service=/api-a/**  

#表示只要訪問以/api-a/開頭的多層目錄都可以路由到 id為compute-service1的服務上
zuul.routes.compute-service1=/api-a/**
上面的一行等同於下面的兩行
zuul.routes.api-a.path=/api-a/**
zuul.routes.api-a.serviceId=compute-service1

萬用字元 含義 舉例 解釋
? 匹配任意單個字元 /feign-consumer/? 匹配/feign-consumer/a,/feign-consumer/b,/feign-consumer/c等
* 匹配任意數量的字元 /feign-consumer/* 匹配/feign-consumer/aaa,feign-consumer/bbb,/feign-consumer/ccc等,無法匹配/feign-consumer/a/b/c
** 匹配任意數量的字元 /feign-consumer/* 匹配/feign-consumer/aaa,feign-consumer/bbb,/feign-consumer/ccc等,也可以匹配/feign-consumer/a/b/c

  3.4 啟動專案 測試

每次重新整理訪問都會在下面結果輪詢顯示:

這就簡單實現了springCloud的負載均衡.

  3.5 服務過濾

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

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

        比如下面的例子,定義了一個Zuul過濾器,實現了在請求被路由之前檢查請求中是否有accessToken引數,若有就進行路由,若沒有就拒絕訪問,返回401 Unauthorized錯誤。

package com.example.demo.filter;
 
import javax.servlet.http.HttpServletRequest;
 
import org.springframework.util.StringUtils;
 
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
 
public class AccessFilter  extends ZuulFilter {
	@Override
	public Object run() {
		RequestContext ctx = RequestContext.getCurrentContext();  
        HttpServletRequest request = ctx.getRequest();
        System.out.println(String.format("%s demoFilter request to %s", request.getMethod(), request.getRequestURL().toString()));
        String username = request.getParameter("username");// 獲取請求的引數 
        if(!StringUtils.isEmpty(username)&&username.equals("lilei")){//通過
        	ctx.setSendZuulResponse(true);// 對該請求進行路由  
            ctx.setResponseStatusCode(200);  
            ctx.set("isSuccess", true);// 設值,讓下一個Filter看到上一個Filter的狀態  
            return null;  
        }else{
        	ctx.setSendZuulResponse(false);// 過濾該請求,不對其進行路由  
            ctx.setResponseStatusCode(401);// 返回錯誤碼  
            ctx.setResponseBody("{\"result\":\"username is not correct!\"}");// 返回錯誤內容  
            ctx.set("isSuccess", false);  
            return null;
        }
	}
 
	@Override
	public boolean shouldFilter() {
		return true;// 是否執行該過濾器,此處為true,說明需要過濾
	}
 
	@Override
	public int filterOrder() {
		return 0;// 優先順序為0,數字越大,優先順序越低
	}
	
	
	/*
	pre:可以在請求被路由之前呼叫
	route:在路由請求時候被呼叫
	post:在route和error過濾器之後被呼叫
	error:處理請求時發生錯誤時被呼叫
	*/
	@Override
	public String filterType() {
		return "pre";// 前置過濾器   
	}
 
}

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

  • filterType:返回一個字串代表過濾器的型別,在zuul中定義了四種不同生命週期的過濾器型別,具體如下:
    • pre:可以在請求被路由之前呼叫
    • routing:在路由請求時候被呼叫
    • post:在routing和error過濾器之後被呼叫
    • error:處理請求時發生錯誤時被呼叫
  • filterOrder:通過int值來定義過濾器的執行順序
  • shouldFilter:返回一個boolean型別來判斷該過濾器是否要執行,所以通過此函式可實現過濾器的開關。在上例中,我們直接返回true,所以該過濾器總是生效。
  • run:過濾器的具體邏輯。需要注意,這裡我們通過ctx.setSendZuulResponse(false)令zuul過濾該請求,不對其進行路由,然後通過ctx.setResponseStatusCode(401)設定了其返回的錯誤碼,當然我們也可以進一步優化我們的返回,比如,通過ctx.setResponseBody(body)對返回body內容進行編輯等。

在實現了自定義過濾器之後,還需要例項化該過濾器才能生效,我們只需要在應用主類中增加如下內容:

提示如下錯誤:被過濾器給攔截了

則可以正常訪問

專案原始碼: