若是說用Spring Boot+Spring MVC是開發單體應用(或單體服務)的利器,那麼Spring Boot+Spring MVC+Spring Cloud將是開發分佈式應用(快速構建微服務)的又一法寶,相信你們若是看到我近期總結的《JAVA WEB快速入門》系列文章,對Spring Boot+Spring MVC應該是比較熟悉了吧,從本文開始,一塊兒來熟悉Spring Cloud、玩轉Spring Cloud,至於什麼是Spring Cloud?我這裏就再也不介紹了,網上資源太多了,好比:大話Spring Cloud、SpringCloud是什麼?,固然介紹Spring Cloud系列文章也比較多(好比:http://www.javashuo.com/article/p-vpguiuwq-dw.html),你們也能夠參考,我這裏只是結合當前最新的Spring Boot、Spring MVC、Spring Cloud來從新演練一遍,把重要的知識點、遇到的一些坑分享出來,一來是爲本身作記錄(所謂「好記性不如爛筆頭」),二來能夠避免你們學習時走彎路,又由於介紹Spring Cloud文章實在太多了,故玩轉Spring Cloud系列文章更多的是以把實現的DEMO代碼一步步貼出來,一些組件名詞我就再也不詳細解釋了,而後對於涉及的重要知識點及踩坑點進行說明,以便你們能夠:知其然還能知其因此然。(注:全部示例代碼均採用IDEA IDE編寫)html
1、實現eureka server(註冊中心)java
1.1.經過IDEA來建立一個空的spring boot項目(類型是:maven-archtype-quickstart,這樣最精簡,固然若是你使用webapp項目也是能夠,只是認爲沒有必要)。node
建立步驟有2種,第一種是使用maven建立: maven->maven-archtype-quickstart,而後手動添加相關的spring boot依賴;第二種是使用spring initializer->填寫項目參數->選擇相關依賴(可直接選擇spring cloud相關依賴,如:eureka,這樣就一步到位,這裏所有先不選),最終的初始POM XML以下:git
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>cn.zuowenjun.cloud</groupId> <artifactId>eurekaserver</artifactId> <version>1.0-SNAPSHOT</version> <name>eurekaserver</name> <!-- FIXME change it to the project's website --> <url>http://www.zuowenjun.cn</url> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> </properties> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.3.RELEASE</version> </parent> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
如上所示(若是不是請改爲這樣,若是隻是多點依賴不要緊,固然我認爲此時只須要這麼多的依賴便可,多了也無用),咱們只是有spring boot的POM依賴,並無spring cloud的相關依賴。github
1.2添加spring cloud相關依賴,以下所示:(添加了dependencyManagement節點,並配置spring-cloud-dependencies pom import依賴,目的是:便於依賴繼承,與parent節點功能相似,添加具體依賴時,若包含在parent中或pom import依賴中則無需版本號,可以保證組件的一致性,詳見:https://blog.csdn.net/mn960mn/article/details/50894022,相反若是沒有配置spring-cloud-dependencies pom import依賴,則添加具體依賴時須要指定version版本號,並且須要注意各依賴組件間的兼容性問題,以下面我把version註釋掉)web
<dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Greenwich.RELEASE</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> ... ...其它原有依賴 <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter</artifactId> <!--<version>2.1.0.RELEASE</version>--> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> <!--<version>2.1.0.RELEASE</version>--> </dependency> </dependencies>
1.3.在resouces目錄下(若沒有請建立,注意設爲souces root目錄,方法:右鍵文件夾->Mark directory as->souces root)建立application.yml(或application.properties,本文示例所有使用yml),添加以下配置:算法
server:
port: 8800
spring:
applcation:
name: demo-eurekaserver
# config detail:https://www.jianshu.com/p/98f4e5f6bca7 or https://blog.csdn.net/wo18237095579/article/details/83276352
eureka:
instance:
hostname: eurekaserver1 #實例主機名,集羣時須要且惟一
server:
enable-self-preservation: true #自我保護,正式環境不要這麼作
eviction-interval-timer-in-ms: 5000 #按期清理失效節點,默認60s
peer-eureka-nodes-update-interval-ms: 6000 #同步更新節點頻率,默認10min
renewal-percent-threshold: 0.49 #默認0.85
response-cache-auto-expiration-in-seconds: 30
client:
registerWithEureka: false
fetchRegistry: false
serviceUrl:
defaultZone: http://localhost:${server.port}/eureka/
1.4.在spring boot 啓動類中添加@EnableEurekaServer便可,以下代碼:spring
package cn.zuowenjun.cloud; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer; @EnableEurekaServer @SpringBootApplication public class App { public static void main( String[] args ) { SpringApplication.run(App.class, args); } }
整個項目結構以下圖示,啓動後瀏覽地址:http://localhost:8800/,會出現spring eureka的主頁,就代表eureka server成功了。apache
2、實現service provider(含eureka client)--服務提供者架構
【即:具體微服務項目,註冊服務信息,暴露API】,固然也有可能同時是service consumer【服務消費者】,須要遠程調用其它服務
2.1.參照1.1方式建立一個空的spring boot項目,而後添加spring cloud 相關依賴(這裏主要是:eureka-client【實現服務自動發現與註冊】、web【即:springMVC,實現服務API】),POM XML添加配置以下:
<dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Greenwich.RELEASE</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!--當.yml配置不生效時,應添加snakeyaml依賴,但通常spring-boot-starter中默認有此依賴,非spring boot項目須要添加--> <dependency> <groupId>org.yaml</groupId> <artifactId>snakeyaml</artifactId> <version>1.23</version> </dependency> </dependencies>
2.2.在application.yml文件中添加以下配置(若沒有請參見1.3法建立):注意spring.application.name,這個是服務實例名,註冊及服務消費時均需使用該名稱
server:
port: 8801
spring:
application:
name: helloservice
ip: localhost #自定義配置,在demo代碼中有用到
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8800/eureka/
3.3.編寫controller 服務相關代碼,在spring boot啓動類上添加@EnableDiscoveryClient註解,具體完整實現代碼以下:(除了@EnableDiscoveryClient註解,基餘代碼與普通的spring MVC項目代碼均相同)
//controller: package cn.zuowenjun.cloud.controller; import cn.zuowenjun.cloud.model.Result; import org.springframework.beans.factory.annotation.Value; import org.springframework.cloud.client.discovery.DiscoveryClient; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class DemoController { @Value("${spring.application.name}") private String serviceName; @Value("${spring.application.ip}") private String address; @Value("${server.port}") private String port; @Autowired DiscoveryClient discoveryClient; @GetMapping(value = "/") public String index(){ return "demo service"; } @RequestMapping("/hello") public Object hello(){ return discoveryClient.getServices(); } @RequestMapping("/info") public Result info(){ Result result = new Result(); result.setServiceName(serviceName); result.setHost(String.format("%s:%s", address, port)); result.setMessage("hello"); return result; } @RequestMapping(value = "/multiply/{a}/{b}") public Result multiply(@PathVariable("a") int a,@PathVariable("b") int b){ Result result = new Result(); result.setServiceName(serviceName); result.setHost(String.format("%s:%s", address, port)); result.setMessage("ok"); result.setContent(a * b); return result; } } //model: package cn.zuowenjun.cloud.model; public class Result { private int code; private String message; private Object content; private String serviceName; private String host; public int getCode() { return code; } public void setCode(int code) { this.code = code; } public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } public Object getContent() { return content; } public void setContent(Object content) { this.content = content; } public String getServiceName() { return serviceName; } public void setServiceName(String serviceName) { this.serviceName = serviceName; } public String getHost() { return host; } public void setHost(String host) { this.host = host; } } //App spring boot啓動類: package cn.zuowenjun.cloud; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; @EnableDiscoveryClient @SpringBootApplication public class App { public static void main( String[] args ) { SpringApplication.run(App.class, args); } }
完成上述步驟後即實現了服務提供者項目,完整項目結構以下圖示,啓動運行http://localhost:8801/multiply/324/561(只需關注這個服務方法,後面服務消費會調用這個方法) ,能夠看到正常響應出JSON結果,如:"code":0,"message":"ok","content":181764,"serviceName":"helloservice","host":"localhost:8801"}
爲了後面服務消費者能體驗出負載均衡的效果,能夠把該項目再以另外一個端口(server.port=8802)從新啓動運行一個實例(IDEA啓動多個實例的方法請參見:https://blog.csdn.net/forezp/article/details/76408139,最後不必定要改yml中的port配置,也能夠直接在Edit Configuration--> program argements中指定:--server.port=8802便可,原理與直接經過命令:java -jar xxx --server.port=8802相似),這樣就會有兩個服務提供者了,若是查看eureka server主頁(http://localhost:8800/)會在Instances currently registered with Eureka列表中展現出2個服務實例信息,以下圖示:
3、實現service consumer(含eureka client)--服務消費者
【即:須要調用微服務API的項目,相對eureka,service provider來說,就是客戶端,消費方】,固然也有多是service provider【服務提供者】,暴露服務API給其它微服務項目
3.0.參照1.1方式建立一個空的spring boot項目,而後添加spring cloud 相關依賴(這裏僅先是:eureka-client【實現服務自動發現與註冊】、web【即:springMVC,實現服務API】),POM XML添加配置以下:
<properties> <java.version>1.8</java.version> <spring-cloud.version>Greenwich.RELEASE</spring-cloud.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</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>
3.1方式一:使用restTemplate+ribbon實現服務消費(負載均衡調用遠程服務)
3.1.1.在POM XML中添加spring-cloud-starter-netflix-ribbon依賴,以下:
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-ribbon</artifactId> </dependency>
3.1.2.編寫controller相關代碼(含遠程服務調用類HelloService),修改spring boot 啓動類,具體完整實現代碼以下:
//spring boot啓動類: package cn.zuowenjun.cloud; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.cloud.openfeign.EnableFeignClients; import org.springframework.context.annotation.Bean; import org.springframework.web.client.RestTemplate; @EnableDiscoveryClient @SpringBootApplication class EurekaclientconsumerApplication { public static void main(String[] args) { SpringApplication.run(EurekaclientconsumerApplication.class, args); } @LoadBalanced @Bean public RestTemplate restTemplate(){ return new RestTemplate(); } } //controller: package cn.zuowenjun.cloud.controller; import cn.zuowenjun.cloud.service.HelloRemoteService; import cn.zuowenjun.cloud.service.HelloService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @RestController public class HelloController { @Autowired private HelloService helloService; @RequestMapping("/x") public Object multiplyForRestTemplate(@RequestParam int a, @RequestParam int b) { return helloService.multiply(a,b); } } //HelloService(遠程服務代理類) : package cn.zuowenjun.cloud.service; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.web.client.RestTemplate; @Service public class HelloService { @Autowired private RestTemplate restTemplate; @Value("${spring.application.helloServiceProvider}") private String helloServiceName; public Object multiply(int a,int b){ String url="http://"+ helloServiceName +"/multiply/" + a +"/" + b; return restTemplate.getForObject(url,String.class); } }
如上代碼中最核心的是:HelloService類,經過這個類遠程調用【消費】註冊在eureka server上對應的服務API,而這個類中最核心的對象是:RestTemplate,而這個又是經過在spring boot啓動類(EurekaclientconsumerApplication)中經過代碼注入到Spring IOC容器中的(固然也能夠自定義一個config類而後統一寫BEAN注入的方法),重點請看這個restTemplate Bean註冊方法上面的註解:@LoadBalanced,這個就是實現負載均衡(默認是採用輪詢的負載均衡算法,還有其它的負載均衡Rule),就這麼簡單嗎?是的,用起來簡單,但內部實現仍是很是複雜的,Ribbon的運行原理詳見:深刻理解Ribbon之源碼解析,核心思路是:RestTemplate內部維護了一個被@LoadBalance註解的RestTemplate列表,而這些RestTemplate列表又被添加了LoadBalancerInterceptor攔截器,而LoadBalancerInterceptor內部又使用了LoadBalancerClient,而LoadBalancerClient(實現類:RibbonLoadBalancerClient)具體選擇服務實例的邏輯又由ILoadBalancer來處理,ILoadBalancer經過配置IRule、IPing等信息,向EurekaClient獲取註冊列表的信息,並定時向EurekaClient發送「ping」心跳,進而檢查是否更新了服務列表,最後獲得註冊服務實例列表後,ILoadBalancer根據IRule的策略進行負載均衡。
3.1.3.在application.yml文件中添加以下配置(若沒有請參見1.3法建立):
server:
port: 8666
spring:
application:
name: ribbonclient
helloServiceProvider: helloservice #自定義配置,指定訪問遠程服務名稱,固然也能夠寫死在代碼中
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8800/eureka/ #指向eureka server
完成上述步驟即實現了一個基於Ribbon的負載均衡服務消費者(客戶端)項目。
3.2方式二:使用feign實現服務消費(負載均衡調用遠程服務調用)
咱們仍然基於3.1節原有項目基礎上實現基於feign的負載均衡服務調用,注意feign的底層仍然使用了Ribbon。固然也能夠單首創一個新的spring boot項目(參照第一節介紹)而後再按下文步驟操做便可。
3.2.1.在POM XML中添加spring-cloud-starter-openfeign依賴,配置以下:
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> </dependencies>
3.2.2.在spring boot啓動類(EurekaclientconsumerApplication)上添加:@EnableFeignClients 註解,而後在cn.zuowenjun.cloud.service包中添加自定義HelloRemoteService,這個就是遠程服務調用接口類(或稱:客戶端代理類【接口】),這個就是與3.1中定義的HelloService做用徹底相似,只是實現方式不一樣而矣,最後在controller中添加一個新的API ACTION方法,以即可以調用HelloRemoteService中的服務方法,完整實現代碼以下:
//spring boot啓動類 package cn.zuowenjun.cloud; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration; import org.springframework.cloud.client.loadbalancer.LoadBalancerInterceptor; import org.springframework.cloud.openfeign.EnableFeignClients; import org.springframework.context.annotation.Bean; import org.springframework.web.client.RestTemplate; @EnableDiscoveryClient @SpringBootApplication @EnableFeignClients(basePackages = "cn.zuowenjun.cloud.service") // 若是啓動類不在根目錄須要指定basePackages,不然不須要 class EurekaclientconsumerApplication { public static void main(String[] args) { SpringApplication.run(EurekaclientconsumerApplication.class, args); } @LoadBalanced @Bean public RestTemplate restTemplate(){ return new RestTemplate(); } } //HelloRemoteService: package cn.zuowenjun.cloud.service; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.stereotype.Service; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; /* * bug-refer https://blog.csdn.net/zlh313_01/article/details/80309144 * bug-refer https://blog.csdn.net/alinyua/article/details/80070890 */ @FeignClient(name= "helloservice") public interface HelloRemoteService { @RequestMapping("/multiply/{a}/{b}") Object multiply(@PathVariable("a") int a, @PathVariable("b") int b); } //controller: package cn.zuowenjun.cloud.controller; import cn.zuowenjun.cloud.service.HelloRemoteService; import cn.zuowenjun.cloud.service.HelloService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @RestController public class HelloController { @Autowired private HelloService helloService; @Autowired private HelloRemoteService helloRemoteService; @RequestMapping("/x") public Object multiplyForRestTemplate(@RequestParam int a, @RequestParam int b) { return helloService.multiply(a,b); } @RequestMapping("/multiply/{a}/{b}") public Object multiplyForFeignClient(@PathVariable int a, @PathVariable int b) { return helloRemoteService.multiply(a,b); } }
如上代碼HelloRemoteService是重點,須要注意:
a.必需是interface,由於@FeignClient註解只能用於interface中,並且很顯然HelloRemoteService 是遠程調用,本地不該有實現的,若是知道原理就更明白這個接口只是爲了生成可供restTemplate調用的URL方法而矣;
b.@FeignClient註解的name(別名屬性)或value必填,這個就是須要遠程調用服務的應用名稱【即:代表消費哪一個服務】
c.接口中定義的方法應與遠程服務的controller中的方法保持一致(方法簽名,註解),同時注意方法上的一些映射請求的註解,如:@RequestMapping,這些與咱們在spring MVC用法相同,但含義卻不相同,spring MVC是指處理請求路徑,而這裏是調用請求路徑,這個路徑必需與服務提供者API 的對應的ACITON方法上的保持相同,不然將沒法成功發送請求。常見問題及解決辦法可參見:https://blog.csdn.net/zlh313_01/article/details/80309144
3.2.3.application.yml配置與3.1.3配置相同,即保持不變便可,最後啓動項目便可(如今這個項目同時包含了Ribbon與Feign的負載均衡遠程調用服務的方式),經過屢次訪問:http://localhost:8666/x?a=數字&b=數字 (基於Ribbon實現)、http://localhost:8666/multiply/數字/數字(基於Feign實現)能夠看到遠程調用服務成功(即:消費服務成功)。
FeignClient的運行原理詳見:深刻理解Feign之源碼解析,核心思路是:spring boot項目啓動時檢查@EnableFeignClients,如有則掃描被@FeignClient註解接口並注入到spring IOC容器中,而後在請求被@ FeignCleint標註的接口方法時,會經過JDK動態代理來生成具體的RequesTemplate,RequesTemplate又會生成Request,Request交給Client去處理,最後Client被封裝到LoadBalanceClient類,這個類Ribbon中的LoadBalancerClient相同,後面的負載均衡的處理請求相同。
項目結構及遠程調用效果以下圖所示:
、
、
4、下面分享相關可參考的博文資料連接:
Spring Cloud之Eureka服務註冊與發現(概念原理篇)
Spring Cloud Netflix - Eureka Server源碼閱讀
提示:本文相關示例項目代碼已上傳GITHUB,地址以下:
https://github.com/zuowj/learning-demos/tree/master/java/demo-eurekaserver
https://github.com/zuowj/learning-demos/tree/master/java/demo-eurekaclient
https://github.com/zuowj/learning-demos/tree/master/java/demo-eurekaclientconsumer
說明:文中如有不足之處歡迎指出,碼字不易,請多支持,謝謝!