在微服務架構中,後端服務每每不直接開放給調用端,而是經過一個公共網關根據請求的url,路由到相應的服務。 在網關中能夠作一些服務調用的前置處理,好比權限驗證。也能夠經過動態路由,提供多個版本的api接口。
spring cloud 提供的技術棧中,使用netflix zuul來做爲服務網關。前端
新建一個maven工程,修改pom.xml引入 spring cloud 依賴:java
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.4.2.RELEASE</version> </parent> <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> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Camden.SR2</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>
在 resources 目錄中建立 application.yml 配置文件,在配置文件內容:git
spring: application: name: @project.artifactId@ server: port: 80 eureka: client: serviceUrl: defaultZone: http://localhost:8000/eureka/ zuul: routes: add-service-demo: path: /add-service/** serviceId: add-service-demo
這裏主要關注 zuul 相關的配置,path 定義了須要路由的url,serviceId 和註冊中心中的application 相對應,定義了路由到哪一個服務。(這裏 serviceId 應該換行和 path 同級,oschina 的markdown格式顯示有問題)
在 java 目錄中建立一個包 demo ,在包中建立啓動入口 ServiceGatewayApplication.javaspring
@EnableDiscoveryClient @SpringBootApplication @EnableZuulProxy public class ServiceGatewayApplication { public static void main(String[] args) { SpringApplication.run(ServiceGatewayApplication.class, args); } }
到這裏一個簡單的服務網關就配置好了,先啓動註冊中心 service-registry-demo,而後啓動兩個 add-service-demo 工程,分別映射到 8100 和 8101 端口,而後再啓動剛剛配置好的服務網關 service-gateway-demo。
啓動完成後訪問註冊中心頁面 http://localhost:8000
,能夠看到註冊了兩個 add-service-demo 和一個 service-gateway-demo:docker
在瀏覽器中訪問 http://localhost/add-service/add?a=1&b=2
,能夠看到返回結果:shell
{ msg: "操做成功", result: 3, code: 200 }
屢次訪問,查看 add-service-demo 的控制檯輸出,能夠看到服務網關對請求分發作了負載均衡。apache
在對外提供rest接口時,常常會遇到跨域問題,尤爲是使用先後端分離架構時。
能夠在服務端使用cors技術,解決前端的跨域問題。這裏咱們在網關層解決跨域問題。
修改 ServiceGatewayApplication.java ,增長一個CorsFilter,代碼以下:後端
[@Bean](https://my.oschina.net/bean) public CorsFilter corsFilter() { final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); final CorsConfiguration config = new CorsConfiguration(); config.setAllowCredentials(true); config.addAllowedOrigin("*"); config.addAllowedHeader("*"); config.addAllowedMethod("OPTIONS"); config.addAllowedMethod("HEAD"); config.addAllowedMethod("GET"); config.addAllowedMethod("PUT"); config.addAllowedMethod("POST"); config.addAllowedMethod("DELETE"); config.addAllowedMethod("PATCH"); source.registerCorsConfiguration("/**", config); return new CorsFilter(source); }
這樣服務端的接口就能夠支持跨域訪問了。api
自定義filter也很簡單,只須要繼承 ZuulFilter 就能夠了。
在demo包下新建一個filter的子包,用來存放自定義的filter類。新建一個filter類 MyFilter 繼承 ZuulFilter:跨域
@Component public class MyFilter extends ZuulFilter { @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(); String ip = getIpAddr(request); System.out.println("收到來自IP爲: '" + ip + "'的請求"); return null; } private String getIpAddr(HttpServletRequest request) { String ip = request.getHeader("x-forwarded-for"); if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("Proxy-Client-IP"); } if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("WL-Proxy-Client-IP"); } if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getRemoteAddr(); } return ip; } }
這個filter的邏輯很簡單,就是從 HttpServletRequest 對象中獲取請求IP,並打印出來。
而後在 ServiceGatewayApplication.java 中增長 MyFilter 的配置:
@Bean public MyFilter myFilter() { return new MyFilter(); }
從新啓動 service-gateway-demo,再次發起請求,會看到控制檯打印的IP。
咱們回過頭看一下,MyFilter 這個類從 ZuulFilter 繼承以後作了哪些處理。
首先覆寫 ZuulFilter 中的4個方法,分別爲 filterType、filterOrder、 shouldFilter、 run。這4個方法看名字就知道有什麼做用:
filterType
定義了filter的類型
pre
表示在請求被路由以前調用route
請求被路由時調用,時機比pre
晚post
在路由完成後調用error
發生錯誤時調用filterOrder
定義過濾器的執行順序,值小的先執行shouldFilter
是否須要過濾run
過濾器的具體執行邏輯demo源碼 spring-cloud-1.0/service-gateway-demo
複製 application.yml,重命名爲 application-docker.yml,修改 defaultZone爲:
eureka: client: serviceUrl: defaultZone: http://service-registry:8000/eureka/
這裏修改了 defaultZone 的訪問url,如何修改取決於部署docker容器時的 --link 參數, --link 可讓兩個容器之間互相通訊。
修改 application.yml 中的 spring 節點爲:
spring: application: name: @project.artifactId@ profiles: active: @activatedProperties@
這裏增長了 profiles 的配置,在maven打包時選擇不一樣的profile,加載不一樣的配置文件。
在pom.xml文件中增長:
<properties> <java.version>1.8</java.version> <!-- 指定java版本 --> <!-- 鏡像前綴,推送鏡像到遠程庫時須要,這裏配置了一個阿里雲的私有庫 --> <docker.image.prefix> registry.cn-hangzhou.aliyuncs.com/ztecs </docker.image.prefix> <!-- docker鏡像的tag --> <docker.tag>demo</docker.tag> <!-- 激活的profile --> <activatedProperties></activatedProperties> </properties> <profiles> <!-- docker環境 --> <profile> <id>docker</id> <properties> <activatedProperties>docker</activatedProperties> <docker.tag>docker-demo-${project.version}</docker.tag> </properties> </profile> </profiles> <build> <defaultGoal>install</defaultGoal> <finalName>${project.artifactId}</finalName> <resources> <resource> <directory>src/main/resources</directory> <filtering>true</filtering> </resource> </resources> <plugins> <!-- 配置spring boot maven插件,把項目打包成可運行的jar包 --> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <executable>true</executable> </configuration> </plugin> <!-- 打包時跳過單元測試 --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <configuration> <skipTests>true</skipTests> </configuration> </plugin> <!-- 配置docker maven插件,綁定install生命週期,在運行maven install時生成docker鏡像 --> <plugin> <groupId>com.spotify</groupId> <artifactId>docker-maven-plugin</artifactId> <version>0.4.13</version> <executions> <execution> <phase>install</phase> <goals> <goal>build</goal> <goal>tag</goal> </goals> </execution> </executions> <configuration> <!-- 修改這裏的docker節點ip,須要打開docker節點的遠程管理端口2375, 具體如何配置能夠參照以前的docker安裝和配置的文章 --> <dockerHost>http://docker節點ip:2375</dockerHost> <imageName>${docker.image.prefix}/${project.build.finalName}</imageName> <baseImage>java</baseImage> <!-- 這裏的entryPoint定義了容器啓動時的運行命令,容器啓動時運行 java -jar 包名 , -Djava.security.egd這個配置解決tomcat8啓動時, 由於須要收集環境噪聲來生成安全隨機數致使啓動過慢的問題--> <entryPoint> ["java", "-Djava.security.egd=file:/dev/./urandom", "-jar", "/${project.build.finalName}.jar"] </entryPoint> <resources> <resource> <targetPath>/</targetPath> <directory>${project.build.directory}</directory> <include>${project.build.finalName}.jar</include> </resource> </resources> <image>${docker.image.prefix}/${project.build.finalName}</image> <newName>${docker.image.prefix}/${project.build.finalName}:${docker.tag}</newName> <forceTags>true</forceTags> <!-- 若是須要在生成鏡像時推送到遠程庫,pushImage設爲true --> <pushImage>false</pushImage> </configuration> </plugin> </plugins> </build>
選擇 docker
profile,運行 mvn install -P docker
,打包項目並生成docker鏡像,注意docker-maven-plugin中的 <entryPoint>
標籤裏的內容不能換行,不然在生成docker鏡像的時候會報錯。
運行成功後,登陸docker節點,運行 docker images
應該能夠看到剛纔打包生成的鏡像了。
在前一篇中,已經啓動了 service-registry-demo 和 add-service-demo,而且在兩個容器之間創建了鏈接。這裏啓動 service-gateway-demo
docker run -d --name service-gateway-demo --publish 80:80 --link service-registry-demo:service-registry \ --link add-service-demo --volume /etc/localtime:/etc/localtime \ registry.cn-hangzhou.aliyuncs.com/ztecs/service-gateway-demo:docker-demo-1.0
這裏比起上一篇啓動 add-service-demo 容器的命令,有兩個 --link ,分別鏈接了 service-registry-demo 和 add-service-demo,由於服務網關不只須要註冊到服務註冊中心,還須要和後端提供的服務進行鏈接。
啓動完成以後,訪問註冊中心的頁面 http://宿主機IP:8000
查看服務註冊信息,能夠發現 service-gateway-demo 也註冊成功了。
這時候就能夠經過網關訪問 add-service-demo 提供的服務了。
注意:在前一篇啓動 add-service-demo 時使用了 --publish
把端口映射到了宿主機,在部署服務網關的狀況下,後端服務就不須要映射到宿主機了,全部對服務的訪問都經過網關進行路由,避免透過網關直接訪問。
能夠把3條啓動命令封裝到一個shell裏:
docker run -d --name service-registry-demo --publish 8000:8000 \ --volume /etc/localtime:/etc/localtime \ registry.cn-hangzhou.aliyuncs.com/ztecs/service-registry-demo:docker-demo-1.0 echo 'sleep 30s to next step...' sleep 30s docker run -d --name add-service-demo --link service-registry-demo:service-registry \ --volume /etc/localtime:/etc/localtime \ registry.cn-hangzhou.aliyuncs.com/ztecs/add-service-demo:docker-demo-1.0 docker run -d --name service-gateway-demo --publish 80:80 --link service-registry-demo:service-registry \ --link add-service-demo --volume /etc/localtime:/etc/localtime \ registry.cn-hangzhou.aliyuncs.com/ztecs/service-gateway-demo:docker-demo-1.0
這裏的 sleep 30s
是爲了讓 service-registry-demo 啓動完成以後再啓動 add-service-demo 和 service-gateway-demo。
在啓動完成以後,經過網關訪問接口時,可能會報錯 Load balancer does not have available server for client: add-service-demo
這是由於 service-gateway-demo 和 add-service-demo 同時啓動,service-gateway-demo 在向註冊中心註冊時,add-service-demo 可能尚未來得及註冊,致使 service-gateway-demo 獲取不到 add-service-demo 的註冊信息,過個幾十秒再訪問就能夠了。
目前咱們已經成功搭建了 服務註冊中心、 服務網關、 後端服務,也建立了兩個服務調用者 ribbon 和 feign。
配置中心 和 斷路器 尚未涉及,配置中心 因爲 spring cloud bus 須要用到消息隊列 rabbitmq 或 kafka,在進行配置中心的開發以前,須要先部署消息隊列。
斷路器 的功能會和 hystrix-dashboard 斷路監控一塊兒放出,包括 turbine 、 zipkin、 spring cloud sleuth 服務調用追蹤,這些都屬於服務端異常監控範疇。
因爲配置中心和服務追蹤都涉及到消息隊列,下一篇先脫離 spring cloud,介紹一下 docker 環境下的 rabbitmq 部署、AMQP 協議、以及使用 spring AMQP 進行消息收發。