微服務是目前系統開發的一種主流技術架構,而Spring Cloud框架是其中的一種解決方案。本文主要從微服務的基本概念,到Spring Cloud框架包括各個基礎組件使用進行一個簡單介紹。html
傳統的Web應用都是基於單體結構構建的,在單體架構中,全部的UI (用戶接口) 、業務、數據庫訪問邏輯都被打包在一個應用程序中而且部署在一個應用程序服務器上。隨着系統業務發展,應用也隨之變得愈來愈複雜,各類業務邏輯雜糅在一塊兒,耦合度過高,不易擴展和維護。前端
爲了解決這些問題,微服務也就應運而生。微服務容許將一個大型的應用分解爲具備嚴格職責定義的便於管理的服務。每一個服務具備特定的功能,減小系統耦合度。java
另外微服務的誕生與當前互聯網產品業務快速發展、頻繁變化以及互聯網公司的組織架構特色也不無關係。mysql
微服務的理論基礎和核心概念其實在很早以前就有人提出來過。其中比較重要的一個就是康威定律。git
Melvin Conway 在1968年發表的論文《 How Do Committees Invent》中最著名的一句話原文是:github
Organizations which design systems are constrained to produce designs which are copies of the communication structures of these organizations. - Melvin Conway(1967)spring
系統的架構受制於產生這些設計的組織的溝通成本。即組織結構決定系統設計。sql
再聯想到蘋果、谷歌、微軟以及國內的BAT的一些產品設計特色,與他們的組織管理方式確實有很大的關係。數據庫
微服務架構優點:json
微服務架構缺點:
一個小型的、簡單的和解耦的服務=可伸縮的、有彈性的和靈活的應用程序。
使用微服務應該結合具體的應用場景,不能爲了使用而使用。由於微服務是分佈式和細粒度的,它在應用程序之間引入了複雜性,若是是正在構建小型的、部門級的應用程序或具備較小用戶羣的應用程序,構建分佈式系統的代價與構建成功得到的自動化和運維收益相比較高,那就不要考慮使用微服務。同時在使用時應該正確的劃分微服務大小,避免每一個服務承擔太多職責。如何控制每一個服務的粒度也是一個很重要的問題。不止須要進行業務邏輯的分離,還須要考慮服務的運行環境、服務之間的通訊交互、服務的可伸縮性和彈性等問題。
Spring Cloud是基於Spring Boot並集成一系列組件的框架集合。經過將註冊中心、負載均衡、熔斷器、路網網關、配置中心等微服務須要的基礎設施封裝配置成starter,提供給開發人員進行快速集成開發部署。下面經過介紹一些經常使用的組件來對整個Spring Cloud框架的構建有一個簡單的瞭解。
首先建立一個基於Maven的多模塊項目spring-cloud-tutorial,項目結構和父pom.xml配置以下:
爲管理各個服務,須要經過一個服務註冊中心來治理服務提供者和服務消費者之間的調用。打個比方,服務提供者就好比淘寶上面不一樣的賣家,服務消費者就是不一樣的買家。這些買家和賣家的關係是複雜的,都須要在淘寶平臺上先進行註冊登記,才能經過淘寶平臺(註冊中心)進行通訊。
除了Neflix提供的Eureka外,還有Consul、Zookeeper等可做爲服務的註冊中心。在分佈式系統有一個著名的CAP定理,C(Consistency)爲數據一致性,A(Availability)爲服務可用性,P(Partition tolerance)爲網絡分區容錯性,根據定理,分佈式系統只能知足三項中的兩項而不可能知足所有三項。Eureka是基於AP原則構建的,Zookeeper是基於CP原則構建的。Dubbo中大部分是使用Zookeeper做爲服務註冊中心,而Spring Cloud中主要是基於Eureka。
在項目中建立一個server-eureka模塊,模塊pom.xml配置以下:
主要是依賴一個Netflix提供的eureka-server starter。
啓動類配置以下:
增長一個@EnableEurekaServer註解表示開啓Eureka功能。
配置文件以下:
啓動服務後,經過http://localhost:8761進行訪問,就可以看到Eureka提供的可視化管理控制檯:
在這個上面能夠查看註冊的一些服務實例,因爲目前尚未服務註冊,因此爲空。
接下來建立一個service-client模塊,做爲客戶端服務。配置以下:
主要加入netFlix-eureaka-client starter依賴。
其中另一個service-business是一個封裝的底層業務模塊,會被多個其餘模塊引用。
service-business模塊主要建立了一個Employee員工實體類,其餘配置就再也不一一列出。
@Data public class Employee { private String empId; private String name; private String designation; private Sex sex; private LocalDate birthday; private double salary; } public enum Sex { MALE("0"), FEMALE("1"); private String code; Sex(String code) { this.code = code; } public String getCode(){ return code; } }
service-client模塊的啓動類,加上@EnableEurekaClient註解開啓客戶端功能。
配置文件中指定註冊中心的地址。
在service-client啓動後,訪問http://localhost:8761就能看到該服務信息。
另外在controller、service、dao三層分別新增員工、根據工號查詢員工、查詢所有員工列表的相關方法。
爲簡單起見,沒有使用數據庫存儲,直接將數據存在內存當中,其中dao層以下:
service層以下:
controller層以下:
代碼都很邏輯都很簡單,就再也不進行說明。
訪問http://localhost:8001/getAllEmployee可看到員工信息列表:
Ribbon是Netflix開源的一款用於客戶端負載均衡的工具。目前負載均衡主要有兩種方法:
接下來咱們使用Ribbon來實現一個最簡單的負載均衡調用功能。
建立一個service-ribbon模塊,其中Maven配置以下:
application.properties配置:
啓動類SerivceRibbonApplication加上@EnableEurekaClient註解,並注入RestTemplate進行http請求,添加 @LoadBalanced 註解,代表這個 restTemplate 開啓負載均衡功能
新建一個service層調用service-client的接口以下,並新建一個controller調用service。
以前已經啓動了一個8001端口的service-client實例,修改application.properties裏面的端口爲8002,再啓動一個新的客戶端。在IDEA中修改啓動配置項,去掉Signle instance only勾選一個模塊便可重複建立實例。
ServiceRibbonApplication也啓動後,咱們在Eureka的控制檯上面就能夠看到三個註冊服務:
屢次訪問http://localhost:8100/hi,就會交替輸出 hi:8001和 hi:8002,說明實現了負載均衡。
Spring Cloud 有兩種調用服務的方式,一種是 ribbon + restTemplate,另一種是使用聲明式REST客戶端 feign進行接口調用。在通常的Java項目中咱們一般可使用HttpClient、HttpUrlConnection、Okhttp、RestTemplate這幾種主要方式進行 HTTP 請求。Feign經過編寫簡單的接口和插入註解,就會徹底代理HTTP請求。Feign默認使用JDK自帶的HttpURLConnetion進行HTTP請求,也能夠替換成HttpClient、OkHttp等其餘方式。
新建service-feign模塊,Maven配置加入open feign依賴。
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency>
ServiceFeignApplication加上@EnableFeignClients開啓feign功能。
新建一個IEmployeeService接口。
@FeignClient(value = "service-client") public interface IEmployeeService { @GetMapping("/hi") String sayHi(); @GetMapping("/getEmployeeByEmpId") Employee getEmployeeByEmpId(String empId); @PostMapping("/addEmployee") String addEmployee(Employee employee); @GetMapping("/getAllEmployee") List<Employee> getAllEmployee(); }
新建controller調用該接口方法:
@RestController public class EmployeeController { @Autowired IEmployeeService employeeService; @GetMapping("/hi") public String hi(){ return employeeService.sayHi(); } @GetMapping("/getEmployeeByEmpId") public Employee getEmployeeByEmpId(String empId){ return employeeService.getEmployeeByEmpId(empId); } @PostMapping("/addEmployee") public String addEmployee(Employee employee){ return employeeService.addEmployee(employee); } @GetMapping("/getAllEmployee") public List<Employee> getAllEmployee(){ return employeeService.getAllEmployee(); } }
啓動 service-feign,端口8200。屢次訪問http://locahost:8200/hi,交替出現hi:8001和 hi:8002,說明實現了負載均衡。訪問http://locahost:8200/addEmployee,添加參數併發送一個post請求.
在調用getAllEmployee方法,可看到剛纔的請求成功了。
咱們上面已經建立了一個server-eureka、兩個service-client、一個service-ribbon、一個service-feign幾個服務實例,這些服務之間使用的是鏈式調用,鏈式調用中當其中一個服務掛了,其餘的服務就會出現問題。Spring Cloud中能夠採用Hystrix斷路器進行服務容錯處理,當一個服務的調用失敗次數到達必定閾值,斷路器會打開,執行服務調用失敗時的處理,避免連鎖故障。Hystrix可經過HystrixCommand對調用進行隔離,阻止故障的連鎖效應,接口調用失敗可迅速恢復正常後在回退並優雅降級。
Hystrix主要是結合在ribbon或者feign中使用。
首先在service-ribbon中加入依賴
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> </dependency>
在啓動類 ServiceRibbonApplication 加 @EnableHystrix ,啓動Hystrix。
修改EmployeeService,在callHi方法上面添加 @HystrixCommand 註解,fallbackMethod 是熔斷方法,當服務不可用時會執行該方法。
@Service public class EmployeeService { @Autowired RestTemplate restTemplate; @HystrixCommand(fallbackMethod = "error") public String callHi(){ return restTemplate.getForObject("http://service-client/hi",String.class); } public String error(){ return "sorry,something is wrong!"; } public Employee callGetEmployeeByEmpId(String empId){ return restTemplate.getForObject("http://service-client/getEmployeeByEmpId?empId=" + empId,Employee.class); } public String callAddEmployee(Employee employee){ return restTemplate.postForObject("http://service-client/addEmployee",employee,String.class); } public String callAddEmployee2(Employee employee){ return restTemplate.postForEntity("http://service-client/addEmployee",employee,String.class).getBody(); } public List<Employee> callGetAllEmployee(){ return restTemplate.getForObject("http://service-client/getAllEmployee",List.class); } }
關閉兩個service-client服務,重啓service-ribbon服務,訪問http://localhost:8100/hi,因爲調用不到service-client的hi接口,服務不可用,斷路器會迅速執行熔斷方法,輸出」sorry,something is wrong!「。
Feign自動包含了Hystrix依賴,不須要修改Maven配置。但須要在配置文件中開啓該功能:
feign.hystrix.enabled=true
新建一個EmployeeServiceHystrixImpl類,實現IEmployeeService接口。
@Component public class EmployeeServiceHystrixImpl implements IEmployeeService { @Override public String sayHi() { return "sorry,505"; } @Override public Employee getEmployeeByEmpId(String empId) { return null; } @Override public String addEmployee(Employee employee) { return "sorry,505"; } @Override public List<Employee> getAllEmployee() { return null; } }
關閉service-client服務實例,重啓service-feign並訪問http://localhost:8200/hi, 顯示"sorry,505"說明熔斷成功。
隨着業務的發展,服務的增多,可經過API路由聚合內部服務,提供統一對外的API接口給前端進行調用,屏蔽內部實現細節。其中Zuul是一種基於JVM路由和服務端的負載均衡器,其核心是過濾器。使用Zuul可實現動態路由、請求監控、認證鑑權、壓力測試、灰度發佈等功能。
新建一個service-zuul模塊,Maven配置加入zuul依賴
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-zuul</artifactId> </dependency>
ServiceZuulApplication加入@EnableZuulProxy註解開啓Zuul代理
server.port=8300 spring.application.name=service.zuul eureka.client.service-url.defaultZone=http://localhost:8761/eureka/ zuul.routes.api-a.path=/api-a/** zuul.routes.api-a.serviceId=service-ribbon zuul.routes.api-b.path=/api-b/** zuul.routes.api-b.serviceId=service-feign
配置文件分別使用api-a和api-b路由代理servie-ribbon和service-feign兩個服務。
訪問http://localhost:8300/api-a/hi和http://localhost:8300/api-b/hi,結果和直接調用一致,說明路由成功。
Zuul還能夠實現限流、認證等高級功能,這些功能都基於Zuul過濾器。
下面經過繼承ZuulFilter實現一個自定義的過濾器,進行token認證。
@Component public class MyFilter extends ZuulFilter{ /** * filterType:返回一個字符串表明過濾器的類型,在zuul中定義了四種不一樣生命週期的過濾器類型,具體以下: * pre:路由以前 * routing:路由之時 * post: 路由以後 * error:發送錯誤調用 */ @Override public String filterType() { return "pre"; } @Override public int filterOrder() { return 0; } @Override public boolean shouldFilter() { return true; } @Override public Object run() throws ZuulException { RequestContext ctx = RequestContext.getCurrentContext(); HttpServletRequest request = ctx.getRequest(); Object accessToken = request.getParameter("token"); if (accessToken == null){ ctx.setSendZuulResponse(false); ctx.setResponseStatusCode(401); try{ ctx.getResponse().getWriter().write("plz input token"); }catch (Exception e){ System.out.println(e.getMessage()); } return null; } return null; } }
Zuul過濾器總共有四種類型。
http://localhost:8300/api-a/hi?token=123,url地址只有帶上token參數才能訪問成功。
在微服務架構中,服務數量一般從幾十到上百甚至上千。每次修改一個配置都須要多個模塊,再依次重啓每一個服務。將配置集中放到服務端進行管理,統一修改推送到客戶端後實時生效,可以提升效率和減小出錯概率。
Spring Cloud Config 是一個用來爲分佈式系統提供配置集中化管理的服務,分爲客戶端和服務端兩個部分。其餘比較出名的分佈式配置中心就是攜程開源的Apollo框架。
新建一個service-config模塊,Maven配置加上config依賴
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-config-server</artifactId> </dependency>
啓動類添加@EnableConfigServer註解。
在github或者碼雲等」最大同性交友網站「上面新建一個倉庫用於存放配置文件。在master分支放生產環境配置,新增dev、test等分支用於存放不一樣環境的配置。
建立一個config-client.properties文件,master分支輸入datasource=oracle,dev分支輸入datasource=mysql,
做爲配置的服務端。
客戶端配置文件以下:
server.port=8400 spring.application.name=service-config spring.cloud.config.server.git.uri=https://github.com/git-username/spring-cloud-config.git # 公開的倉庫不須要填寫用戶名密碼 spring.cloud.config.server.git.username= spring.cloud.config.server.git.password=
訪問http://localhost:8400/config-client/master,輸出以下,其中source下的就是咱們配置文件中的內容。
{ "name": "config-client", "profiles": [ "master" ], "label": null, "version": "cd79457da44a9bd88e00b31b9ee99ffffd73a052", "state": null, "propertySources": [ { "name": "https://github.com/git-username/spring-cloud-config.git/config-client.properties", "source": { "datasource": "oracle" } } ] }
改成http://localhost:8400/config-client/dev, 返回的datasource=mysql說明配置成功。
其餘的路由訪問規則還有這幾種方式:
/{application}/{profile}[/{label}] :http://localhost:8400/config-client/dev /{application}-{profile}.yml /{label}/{application}-{profile}.yml /{application}-{profile}.properties /{label}/{application}-{profile}.properties : http://localhost:8300/dev/config-client.properties
{application}爲配置文件名config-client,若是文件名爲config-client-pc,則pc就是{profile} ,{label} 指的是資源庫的分支,不填則爲默認分支。
以上就是一次Spring Cloud框架各個組件使用的簡單實踐。另外還有一些sleuth服務跟蹤,JWT/OAuth2服務認證、Spring Boot Admin服務監控等後面再慢慢研究。TBC。。。