SpringCloud進(jìn)階
1. 服務(wù)之間的通訊
使用支付服務(wù)調(diào)用用戶服務(wù)
-
服務(wù)與服務(wù)之間遠(yuǎn)程調(diào)用,可以用json的格式返回數(shù)據(jù),也可以返回一個對象!
支付服務(wù)與用戶服務(wù)都使用User來封裝對象。
抽取User作為一個公共模塊,支付服務(wù)與用戶服務(wù)依賴于它
①:創(chuàng)建一個模塊
package com.hannfengyi; /** * User: Han * Date: 2020/2/14 * Time: 9:54 * Description: */ public class User { private Long id; private String username; private String intro; public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getIntro() { return intro; } public void setIntro(String intro) { this.intro = intro; } public User(Long id, String username, String intro) { this.id = id; this.username = username; this.intro = intro; } public User() { } }②:支付模塊與用戶模塊引用它,封裝數(shù)據(jù)
<dependency> <groupId>com.hanfengyi</groupId> <artifactId>springcloud-user-common</artifactId> <version>1.0-SNAPSHOT</version> </dependency>③:瀏覽器訪問支付微服務(wù),支付微服務(wù)調(diào)用用戶微服務(wù),用戶微服務(wù)把數(shù)據(jù)返回給支付微服務(wù),支付微服務(wù)把數(shù)據(jù)響應(yīng)瀏覽器!
需要在java代碼中發(fā)送http請求,使用Spring集成的RestTemplate工具類,它是基于Restful風(fēng)格的http工具!
在支付微服務(wù)中配置類中注冊RestTemplate,交給Spring管理
@SpringBootApplication public class PayServerApplication { public static void main( String[] args ) { SpringApplication.run(PayServerApplication.class); } @LoadBalanced @Bean public RestTemplate restTemplate(){ return new RestTemplate(); } }@LoadBalanced表示作為負(fù)載均衡器
在支付微服務(wù)控制器注入RestTemplate使用http發(fā)送請求!
@RestController public class PayController { @Autowired private RestTemplate restTemplate; @GetMapping("/pay/user/{id}") public User pay(@PathVariable("id") Long id){ String url = "http://localhost:1000/user/"+id; return restTemplate.getForObject(url,User.class); } }瀏覽器訪問支付微服務(wù)http://localhost:1000/user/1測試,可以看到響應(yīng)!
2. 負(fù)載均衡器-Ribbon
上面用戶服務(wù)與支付服務(wù)之間通信,僅僅是兩臺服務(wù)之間的通信,本身是沒有注冊到eureka(注冊中心)的
對用戶模塊做集群,使用Ribbon客戶端負(fù)載均衡器分發(fā)請求到不同的用戶服務(wù)!
Ribbon與ngnix的區(qū)別
兩者都是負(fù)載均衡器,做請求分發(fā),ngnix是服務(wù)端負(fù)載均衡器,是一個獨立的服務(wù),Ribbon是客戶端負(fù)載均衡器,集成在客戶端中!
Ribbon與Feign的工作原理
支付服務(wù)調(diào)用用戶服務(wù),要指定用戶服務(wù)服務(wù)名“user-server”,用戶服務(wù)做了集群之后,"user-server"對應(yīng)兩個服務(wù),分別有不同的ip和端口,支付服務(wù)使用Ribbon通過服務(wù)名“user-server”尋找到兩個服務(wù),然后按照負(fù)載均衡算法(輪詢、隨機(jī)...)對用戶服務(wù)發(fā)起請求!
①:對用戶服務(wù)做集群,創(chuàng)建一個新的用戶服務(wù),與之前的用戶服務(wù)代碼一樣!
修改端口號為1001
eureka:
client:
serviceUrl:
defaultZone: http://localhost:3000/eureka/,http://localhost:3001/eureka/ #注冊中心服務(wù)端的注冊地址
instance:
prefer-ip-address: true
instance-id: user-server:1001 #使用ip進(jìn)行注冊
server:
port: 1001
spring:
application:
name: user-server
兩個服務(wù)的spring.application.name必須一致,表示在一個集群
②:支付服務(wù)集成Ribbon
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
③:Ribbon已經(jīng)集成了RestTemplate,但是要使用@LoadBalanced注解賦予它具備負(fù)載均衡的能力,在支付微服務(wù)配置類中配置!
@SpringBootApplication
public class PayServerApplication
{
public static void main( String[] args )
{
SpringApplication.run(PayServerApplication.class);
}
@LoadBalanced
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
④:修改url,在user-server這個集群中尋找服務(wù)
@RestController
public class PayController {
@Autowired
private RestTemplate restTemplate;
@GetMapping("/pay/user/{id}")
public User pay(@PathVariable("id") Long id){
String url = "http://user-server/user/"+id;
return restTemplate.getForObject(url,User.class);
}
}
3. 負(fù)載均衡器-Feign
Feign:掉用本地接口的方式調(diào)用遠(yuǎn)程服務(wù),與Ribbon的不同的是,解決了參數(shù)過多拼接url帶來的麻煩!
-
新建一個Order服務(wù),使用Feign負(fù)載均衡器,同樣的訪問用戶服務(wù)!
①:導(dǎo)入Feign的依賴
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency>②:配置端口號
eureka: client: serviceUrl: defaultZone: http://localhost:3000/eureka/,http://localhost:3001/eureka/ #注冊中心服務(wù)端的注冊地址 instance: prefer-ip-address: true instance-id: pay-server:4000 #使用ip進(jìn)行注冊 server: port: 4000 spring: application: name: order-server③:創(chuàng)建一個接口
@FeignClient(value = "user-server") public interface UserFeignClient { @GetMapping(value = "/user/{id}") User user(@PathVariable("id") Long id); }@FeignClient(value = "user-server"),參數(shù)需要目標(biāo)服務(wù)名,通過服務(wù)名去注冊中心找到目標(biāo)服務(wù),再根據(jù)@GetMapping(value = "/user/{id}")資源路徑找到目標(biāo)服務(wù)的Controller!
④:Controller中注入接口
@RestController public class OrderController { @Autowired private UserFeignClient userFeignClient; @GetMapping("/order/user/{id}") public User order(@PathVariable("id") Long id){ return userFeignClient.user(id); } }在瀏覽器中訪問http://localhost:4000/order/user/1 該路徑時,會創(chuàng)建UserFeignClient這個接口的代理對象,調(diào)用user方法,在這個方法中,F(xiàn)eign負(fù)載均衡器會根據(jù)指定的服務(wù)名和資源路徑去尋找到多臺服務(wù),此時采用輪詢或者隨機(jī)等算法,對其中某臺服務(wù)發(fā)起調(diào)用!
⑤:修改Feign的算法為隨機(jī)算法,只需要在配置類中配置一個Bean即可
@Bean public IRule randomRule(){ return new RandomRule(); }4. Hystrix斷路器(熔斷器)
4.1:概述
Hystrix:用來對微服務(wù)做隔離和監(jiān)控,防止一個微服務(wù)故障,導(dǎo)致整個微服務(wù)群被拖垮引發(fā)的雪崩效應(yīng)!
Hystrix通過如下機(jī)制來解決雪崩效應(yīng)問題 :
①:資源隔離(限制請求數(shù)量):包括線程池隔離和信號量隔離,限制調(diào)用分布式服務(wù)的資源使用,某一個調(diào)用的服務(wù)出現(xiàn)問題不會影響其他服務(wù)調(diào)用。
線程池隔離:通過線程池中線程數(shù)量來限制請求并發(fā)量!
信號量隔離:使用計數(shù)器計數(shù)的方式,當(dāng)請求數(shù)量達(dá)到被設(shè)置的閥值的時候,限制請求并發(fā)量!
②:熔斷:當(dāng)請求失敗率達(dá)到閥值自動觸發(fā)降級(如因網(wǎng)絡(luò)故障/超時造成的失敗率高),
正常情況下,斷路器處于關(guān)閉狀態(tài)(Closed),
如果調(diào)用持續(xù)出錯或者超時,電路被打開進(jìn)入熔斷狀態(tài)(Open),后續(xù)一段時間內(nèi)的所有調(diào)用都會被拒絕(Fail Fast),
一段時間以后,保護(hù)器會嘗試進(jìn)入半熔斷狀態(tài)(Half-Open),允許少量請求進(jìn)來嘗試,
? 如果調(diào)用仍然失敗,則回到熔斷狀態(tài)
? 如果調(diào)用成功,則回到電路閉合狀態(tài);
③:降級機(jī)制:超時降級、資源不足時(線程或信號量)降級,降級后可以配合降級接口返回托底數(shù)據(jù)。
④:緩存:提供了請求緩存、請求合并實現(xiàn)。
4.2:Ribbon實現(xiàn)
1.在消費(fèi)者服務(wù)集成Hystrix,不在提供者集成,如果用戶服務(wù)故障,那么Hystrix也會故障,不能起到熔斷的作用!
在支付服務(wù)中導(dǎo)入Hystrix依賴
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> </dependency>2.在配置類中開啟Hystrix功能
@SpringBootApplication @EnableCircuitBreaker public class PayServerApplication { public static void main( String[] args ) { SpringApplication.run(PayServerApplication.class); } @LoadBalanced @Bean public RestTemplate restTemplate(){ return new RestTemplate(); } }@EnableCircuitBreaker注解的作用開啟Hystrix功能
3.在需要發(fā)生熔斷的服務(wù)方法中使用注解 @HystrixCommand(fallbackMethod = "ErrorMethod"),如果發(fā)生錯誤,會執(zhí)行指定的托底方法,觸發(fā)熔斷機(jī)制,返回托底數(shù)據(jù)!
@RestController public class PayController { @Autowired private RestTemplate restTemplate; @GetMapping("/pay/user/{id}") @HystrixCommand(fallbackMethod = "ErrorMethod") public User pay(@PathVariable("id") Long id){ // String url = "http://localhost:1000/user/"+id; String url = "http://user-server/user/"+id; return restTemplate.getForObject(url,User.class); } public User ErrorMethod(@PathVariable("id") Long id){ return new User(-1L, "無此用戶", "用戶服務(wù)錯誤!"); } }上面這個例子表示在訪問user-server服務(wù)集群的時候,如果發(fā)生異常,或者該服務(wù)不可用,則會執(zhí)行ErrorMethod該托底方法中的代碼!
4.3:Feign的實現(xiàn)
因為Feign已經(jīng)集成了Hystrix,不需要導(dǎo)入依賴
①:在application.yml中開啟Hystrix
feign: hystrix: enabled: true②:在Feign用于訪問服務(wù)的接口中@FeignClient注解中添加屬性fallback用來指定托底方法
@FeignClient(value = "user-server",fallback = UserFeignClientFallBack.class) public interface UserFeignClient { @GetMapping(value = "/user/{id}") User user(@PathVariable("id") Long id); }③:因為fallback的屬性要求是一個字節(jié)碼對象,需要創(chuàng)建一個類,必須實現(xiàn)該接口,重寫方法,重寫的方法就是托底方法,該類必須交給Spring管理!
@Component public class UserFeignClientFallBack implements UserFeignClient { @Override public User user(Long id) { return new User(-1l, "用戶不存在", "用戶服務(wù)異常!"); } }5. Zuul網(wǎng)關(guān)
Zuul網(wǎng)關(guān):作為整個微服務(wù)群的請求入口,可以保護(hù)微服務(wù)群的安全,可以通過Zuul來實現(xiàn)權(quán)限校驗、限流、日志、監(jiān)控、負(fù)載均衡等。
Zuul作為一個獨立的應(yīng)用,默認(rèn)集成了Ribbon,其本質(zhì)是一個servlet
1.集成Zuul
①:新建一個模塊,創(chuàng)建Zuul服務(wù)
②:導(dǎo)入依賴
<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.cloud</groupId> <artifactId>spring-cloud-starter-netflix-zuul</artifactId> </dependency>③:在配置類中使用@EnableZuulProxy開啟Zuul
@SpringBootApplication @EnableDiscoveryClient @EnableEurekaClient @EnableZuulProxy public class ZuulServer5000 { public static void main( String[] args ) { SpringApplication.run(ZuulServer5000.class); } }④:application.yml的配置
eureka: client: serviceUrl: defaultZone: http://localhost:3000/eureka/,http://localhost:3001/eureka/ #注冊中心服務(wù)端的注冊地址 instance: prefer-ip-address: true instance-id: zuul-server:5000 #使用ip進(jìn)行注冊 server: port: 5000 spring: application: name: zuul-serverZuul是需要注冊到注冊中心的,此時通過瀏覽器訪問用戶服務(wù)http://localhost:5000/user-server/user/1 ,請求到達(dá)Zuul,Zuul會通過服務(wù)名user-server去注冊中心尋找到目標(biāo)服務(wù),通過Ribbon負(fù)載均衡器發(fā)起遠(yuǎn)程調(diào)用!
2.Zuul訪問地址的優(yōu)化
通過上面的例子可以看出,如果我們需要訪問某個服務(wù),需要在地址欄中暴露出服務(wù)名字,顯然這是不好的,可以在application.yml中配置一下屬性,通過指定的別名來訪問!
zuul: ignoredServices: '*' routes: pay-server: /pay/** user-server: /user/** order-server: /order/**ignoredServices:表示忽略使用服務(wù)名的方式訪問
routes:可以指定訪問某個服務(wù)名使用的資源路徑!
現(xiàn)在如果要訪問用戶服務(wù)可以通過http://localhost:5000/user/user/1來進(jìn)行訪問!這里的user就已經(jīng)代表了user-server!
3.Hystrix超時處理
使用Zuul網(wǎng)關(guān)發(fā)起請求,發(fā)生異常,觸發(fā)熔斷機(jī)制,執(zhí)行托底方法超時在application.yml中配置
zuul: host: connect-timeout-millis: 150000 #HTTP連接超時要比Hystrix的大 socket-timeout-millis: 150000 #socket超時 ribbon: #ribbon超時 ReadTimeout: 50000 ConnectTimeout: 50000 hystrix: command: default: execution: isolation: thread: timeoutInMilliseconds: 1000006. filter實現(xiàn)登錄攔截
- 自定義類繼承ZuulFilter重寫核心方法!把該類交給spring容器管理!
@Component public class LoginZuulFilter extends ZuulFilter { @Override public String filterType() { return FilterConstants.PRE_TYPE; } @Override public int filterOrder() { return 1; } @Override public boolean shouldFilter() { //1.獲得當(dāng)前請求上下文 RequestContext currentContext = RequestContext.getCurrentContext(); //2.根據(jù)當(dāng)前請求上下文獲得請求對象 HttpServletRequest request = currentContext.getRequest(); //3.拿到請求頭的uri路徑 String requestURI = request.getRequestURI(); //4.如果訪問路徑是"/login",則不做校驗 if(StringUtils.isEmpty(requestURI) && requestURI.endsWith("login")){ return false; } return true; } @Override public Object run() throws ZuulException { //1.獲得當(dāng)前請求上下文 RequestContext currentContext = RequestContext.getCurrentContext(); //2.根據(jù)當(dāng)前請求上下文獲得請求對象,和響應(yīng)對象 HttpServletRequest request = currentContext.getRequest(); HttpServletResponse response = currentContext.getResponse(); //3.拿到請求頭的token令牌 String token = request.getHeader("token"); Map<String,Object> hashmap = new HashMap<>(); //4.如果沒有token信息,返回提示信息 if(StringUtils.isEmpty(token)){ hashmap.put("success", false); hashmap.put("message","當(dāng)前用戶還未登錄"); try { response.getWriter().print(JSONObject.toJSONString(hashmap)); //5.阻止請求繼續(xù)往下執(zhí)行 currentContext.setSendZuulResponse(false); } catch (IOException e) { e.printStackTrace(); } } return null; } }使用阿里巴巴的fastjson可以將對象轉(zhuǎn)換為json格式
<dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.62</version> </dependency>
shouldFilter:返回一個Boolean值,判斷該過濾器是否需要執(zhí)行。返回true執(zhí)行,返回false不執(zhí)行。
run:過濾器的具體業(yè)務(wù)邏輯。
-
filterType:返回字符串,代表過濾器的類型。包含以下4種:
pre:請求在被路由之前執(zhí)行
routing:在路由請求時調(diào)用
post:在routing和errror過濾器之后調(diào)用
error:處理請求時發(fā)生錯誤調(diào)用
filterOrder:通過返回的int值來定義過濾器的執(zhí)行順序,數(shù)字越小優(yōu)先級越高。
- 過濾器執(zhí)行周期

正常流程:
- 請求到達(dá)首先會經(jīng)過pre類型過濾器,而后到達(dá)routing類型,進(jìn)行路由,請求就到達(dá)真正的服務(wù)提供者,執(zhí)行請求,返回結(jié)果后,會到達(dá)post過濾器。而后返回響應(yīng)。
- 異常流程:
- 整個過程中,pre或者routing過濾器出現(xiàn)異常,都會直接進(jìn)入error過濾器,再error處理完畢后,會將請求交給POST過濾器,最后返回給用戶。
- 如果是error過濾器自己出現(xiàn)異常,最終也會進(jìn)入POST過濾器,而后返回。
- 如果是POST過濾器出現(xiàn)異常,會跳轉(zhuǎn)到error過濾器,但是與pre和routing不同的時,請求不會再到達(dá)POST過濾器了。
7. SpringCloud Config分布式配置中心
7.1 概述
在分布式系統(tǒng)中,由于服務(wù)數(shù)量巨多,為了方便服務(wù)配置文件統(tǒng)一管理,實時更新,所以需要分布式配置中心組件。在Spring Cloud中,有分布式配置中心組件spring cloud config ,它支持配置服務(wù)放在配置服務(wù)的內(nèi)存中(即本地),也支持放在遠(yuǎn)程Git倉庫中。在spring cloud config 組件中,分兩個角色,一是config server,二是config client。
7.2 作用
①:集中管理配置文件
②:動態(tài)化的配置更新,不同環(huán)境不同部署
③:運(yùn)行時動態(tài)調(diào)整配置,不需要在部署服務(wù)的時候編寫配置文件,服務(wù)會向配置中心統(tǒng)一拉取自己的配置信息
④:配置發(fā)生變動時,服務(wù)不需要重啟就可以感知配置變化,自動應(yīng)用最新的配置
⑤:配置信息以restful接口的方式暴露
7.3 與git/svn集成
①:在github/碼云上創(chuàng)建倉庫,創(chuàng)建所需服務(wù)的配置文件
②:新建一個config-server獨立的應(yīng)用,maven中導(dǎo)入依賴
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
③:在配置類中使用注解@EnableConfigServer開啟配置中心支持
@SpringBootApplication
@EnableConfigServer
public class ConfigServerApplication
{
public static void main( String[] args )
{
SpringApplication.run(ConfigServerApplication.class);
}
}
④:在配置文件application.yml中配置git的配置,拉取配置文件
eureka:
client:
serviceUrl:
defaultZone: http://localhost:3000/eureka/,http://localhost:3001/eureka/ #注冊中心服務(wù)端的注冊地址
instance:
prefer-ip-address: true
instance-id: config-server:6000 #使用ip進(jìn)行注冊
server:
port: 6000
spring:
application:
name: config-server
cloud:
config:
server:
git:
uri: https://gitee.com/immerseshe/spring-cloud-config.git
username: ***
password: ***
這里的url要指向倉庫地址
⑤:測試,訪問http://localhost:6000/文件名.yml,會返回配置數(shù)據(jù)!
如果遇到 此網(wǎng)址使用了一個通常用于網(wǎng)絡(luò)瀏覽以外目的的端口。出于安全原因,F(xiàn)irefox 取消了該請求。錯誤
解決方式:
在Firefox地址欄輸入 about:config,
右鍵新建一個字符串鍵:
首選項名稱 填寫 network.security.ports.banned.override 。
值 的填寫有三種方式:
a、如果只有單個端口號時,只接輸入的端口號即可,如 6666 ;
b、如果要填寫多個端口號時,端口號之間用逗號隔開,如:6666,7777,8888 ;
c、一個一個添加端口號特別麻煩,在能保證安全的前提下,還簡化直接輸入 0-65535 。
添加端口號允許訪問!
7.4 客戶端配置
在服務(wù)中配置不再使用自身的配置文件,而是去配置中心拉取配置信息!
①:導(dǎo)入config客戶端依賴
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
②:刪除掉原來的application.yml,創(chuàng)建bootstrap.yml,該配置文件相比較application.yml的優(yōu)先級更高。在配置文件中指向配置中心的服務(wù)地址,配置文件名字和環(huán)境
spring:
cloud:
config:
name: order-server
profile: dev
label: master
uri: http://localhost:6000
③:測試,先在git上修改配置,修改端口號,啟動服務(wù),查看端口是否變化!