文章目錄
- spring-cloud 核心功能建構
- spring cloud 基本項目依賴
- Eureka注冊中心
-
- 添加Eureka注冊中心服務
-
- 添加依賴——eureka服務端
- 增加啟動類注解
- 配置application.yml檔案
- 注冊方添加erueka用戶端依賴
- 注冊方啟動類添加注解
- 調用服務寫法一
- 注冊方配置erueka注冊中心位址
- erueka 進階配置
-
- 高可用叢集
- 服務注冊
- 服務續約
- 服務失效
- 服務重新整理
- 自我保護
- 失效剔除
- Ribbon 負載均衡器
-
- 引入相關依賴
- 使用RibbonLoadBalancerClient動态擷取執行個體
- 調用服務寫法二————使用Ribbon攔截器攔截RestTemplate
- Ribbon 進階配置
-
- 負載均衡政策
- 重試機制
- Hystrix 降級熔斷
-
- 添加Hytrix依賴
- 啟動類開啟Hystrix注解
-
- @EnableHystrix
- @EnableCircuitBreaker
- @SpringCloudApplication
- 設定接口熔斷回調
-
- 局部熔斷回調
- 公共熔斷回調
- 進階配置
-
- 全局逾時時間
- 局部逾時時間
- 熔斷器請求量
- 出錯比例
- 休眠時間窗
- feign
-
- 基本使用
-
- 導入依賴
- 啟動類添加注解
- 添加遠端調用的服務消費端端接口
- 遠端調用方式三————面向接口調用
- feign 整合 Ribbon 和 Hystrix 重試和熔斷
-
- ribbon 和 Hystrix相關配置
- 建立failback工廠作為熔斷回調
- 添加遠端調用的服務消費端端接口
- Zuul網關
-
- 基本使用
-
- 添加依賴
- 啟動類開啟Zuul網關注解
- 進階配置
-
- 基于服務的路由配置
- 忽略服務
- 局部字首忽略
- 全局通路字首
- 整合Ribbon 和 Hystrix
- 自定義過濾器
spring-cloud 核心功能建構
源碼參考:https://github.com/Autom-liu/spring-cloud-demo
spring cloud 基本項目依賴
父工程項目pom.xml
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.1.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<spring-cloud.version>Finchley.SR1</spring-cloud.version>
<mapper.starter.version>2.0.3</mapper.starter.version>
<mysql.version>5.1.32</mysql.version>
<pageHelper.starter.version>1.2.5</pageHelper.starter.version>
</properties>
<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>
<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper-spring-boot-starter</artifactId>
<version>${mapper.starter.version}</version>
</dependency>
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>${pageHelper.starter.version}</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
Eureka注冊中心
添加Eureka注冊中心服務
添加依賴——eureka服務端
建立新的子工程,添加如下依賴:
pom.xml:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
增加啟動類注解
@EnableEurekaServer
@SpringBootApplication
public class EurekaServer {
public static void main(String[] args) throws Exception {
SpringApplication.run(EurekaServer.class, args);
}
}
配置application.yml檔案
server:
port: 10086
spring:
application:
name: eureka-server
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka
instance:
prefer-ip-address: true # 固定ip通路,目前還沒看到生效
ip-address: 127.0.0.1 # 固定ip通路,目前還沒看到生效
注冊方添加erueka用戶端依賴
Erueka作為注冊中心,其他往它注冊的服務都是用戶端,無論它是提供方還是消費方
pom.xml
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
注冊方啟動類添加注解
注冊方啟動類需要添加服務發現注解
@EnableDiscoveryClient
調用服務寫法一
調用服務的其中一種寫法,就是通過
DiscoveryClient
類手動從注冊中心擷取執行個體清單
拿到執行個體,拼接URL,發起請求:
@RestController
@RequestMapping("consumer")
public class ConsumerController {
@Autowired
private RestTemplate restTemplate;
@Autowired
private DiscoveryClient discoverClient;
@GetMapping("/{id}")
public User queryById(@PathVariable("id") String id) {
// 根據服務id擷取執行個體清單
List<ServiceInstance> instances = discoverClient.getInstances("user-service");
// 從執行個體清單中拿到執行個體
ServiceInstance instance = instances.get(0);
// 構造URL
String url = "http://"+ instance.getHost() +":"+ instance.getPort() +"/user/" + id;
// 發起請求
User user = restTemplate.getForObject(url, User.class);
return user;
}
}
這種方式感到很low有沒有?
注冊方配置erueka注冊中心位址
需要注意的是一定要給每個服務命名,即
spring.application.name
spring:
application:
name: user-consumer
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka
instance:
prefer-ip-address: true
ip-address: 127.0.0.1
erueka 進階配置
高可用叢集
eureka.client.service-url.defaultZone 值配置多個即可
服務注冊
其實就是配置是否自己作為服務注冊,預設注冊自己,可以配置不注冊自己
eureka.register-with-eureka: false
服務續約
注冊中心和注冊方維持的心跳機制,幾秒告知一次健康狀态
eureka.instance.lease-renewal-interval-in-seconds
開發環境小一點,生産環境預設即可,不宜太小也不宜太大
服務失效
當注冊方遲遲未心跳時,配置幾秒認為它不存活了
eureka.instance.lease-expiration-duration-in-seconds
服務重新整理
預設情況下會緩存服務清單的資料,隔一定時間重新拉取更新,通過該參數可以修改拉取時間,在開發環境下通常設定小一點,生産環境下不需要更改 機關秒
eureka.client.registry-fetch-interval-seconds
自我保護
spring cloud注重CAP原則中的A和P,簡單來說即不輕易相信服務挂了,即它會排查一切可能,隻有确定100%認為它挂了它才剔除,否則隻要有存活的可能,都不會放棄,哪怕它真的挂了…
開啟自我保護,當服務沒有正常保持心跳時,注冊中心并不認為它挂了,因為存在網絡延遲的可能,是以要統計次數及比例,隻有超過50%(或多少的~)才可以認為它真的挂了。
這樣能有效防止注冊中心誤判(服務挂沒挂)
一般不需要關閉,非要關閉就進行如下配置為
false
即可:
eureka.server.enable-self-preservation
失效剔除
前面說到,注冊中心不會輕易判定一個服務說挂就挂的,是以哪怕注冊中心真的認為服務挂了,也要緩沖一個時間,給它最後一次機會,實在不給面子了,才會把它剔除。
是以失效剔除也是需要有時間的,配置如下:機關毫秒
eureka.server.eviction-interval-timer-in-ms
Ribbon 負載均衡器
引入相關依賴
在服務消費方引用
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
使用RibbonLoadBalancerClient動态擷取執行個體
@RestController
@RequestMapping("consumer")
public class ConsumerController {
@Autowired
private RestTemplate restTemplate;
@Autowired
private RibbonLoadBalancerClient ribbonClient;
@GetMapping("/{id}")
public User queryById(@PathVariable("id") String id) {
// 使用ribbon負載均衡器可以直接id執行個體
ServiceInstance instance = ribbonClient.choose("user-service");
String url = "http://"+ instance.getHost() +":"+ instance.getPort() +"/user/" + id;
User user = restTemplate.getForObject(url, User.class);
return user;
}
}
調用服務寫法二————使用Ribbon攔截器攔截RestTemplate
第二種寫法,即使用Ribbon攔截器攔截RestTemplate,它對RestTemplate做了一次代理,省去了拼接URL麻煩,直接調用通路即可
首先要将注冊到spring中的restTemplate添加注解标記
@Bean
@LoadBalanced // 使用該注解,會對restTemplate所有請求進行一個攔截處理url
public RestTemplate restTemplate() {
return new RestTemplate();
}
這樣就可以不再通過RibbonLoadBalancerClient去擷取,而是直接展現在URL上,在RestTemplate發起請求之前重寫URL
@RestController
@RequestMapping("consumer")
public class ConsumerController {
@Autowired
private RestTemplate restTemplate;
@GetMapping("/{id}")
public User queryById(@PathVariable("id") String id) {
// 将服務id直接寫進url上
String url = "http://user-service/user/" + id;
User user = restTemplate.getForObject(url, User.class);
return user;
}
}
Ribbon 進階配置
負載均衡政策
Ribbon預設負載均衡政策是輪詢的,可以通過如下配置修改負載均衡政策
[service-id].ribbon.NFLoadBalancerRuleClassName
其中
service-id
是服務提供方的應用名稱,即
spring.application.name
配置的名稱
負載均衡政策都在
com.netflix.loadbalancer
包下:
其中有如下幾種政策:
- RoundRobinRule
- AvailabilityFilteringRule
- WeightedResponseTimeRule
- ZoneAvoidanceRule
- BestAvailableRule
- RandomRule
- Retry
也可以自定義政策,這個上網去查了
重試機制
ribbon重試機制個人感覺有問題,沒成功過,或偶爾成功過
某些人說要加入如下依賴:
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
但另外一些人又說那是老版本的做法,這裡用的是新版本的,不知道會不會問題。
但大多數主要就做如下配置即可的:
以下這些配置均是在
AbstractRibbonCommand
類源碼中有對應指明
[service-id].ribbon.ConnectTimeOut Ribbon連接配接逾時時間
[service-id].ribbon.ReadTimeout Ribbon讀取資料逾時時間
[service-id].ribbon.OkToRetryOnAllOperations 是否對所有操作都進行重試
[service-id].ribbon.MaxAutoRetriesNextServer 切換執行個體的重試次數
[service-id].ribbon.MaxAutoRetries 對目前執行個體的重試次數
但請務必先開啟spring cloud重試功能:
spring.cloud.loadbalancer.retry.enabled: true
Hystrix 降級熔斷
所謂熔斷,即服務超出一定時長未響應及時回報使用者,避免因為阻塞占用連接配接。
添加Hytrix依賴
pom.xml
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
啟動類開啟Hystrix注解
@EnableHystrix
這個注解僅僅隻是Hystrix的基本功能,并不通用
@EnableCircuitBreaker
這個注解包含了服務熔斷,服務降級等處理,更為通用
@SpringCloudApplication
這個注解其實是
@SpringBootApplication
,
@EnableDiscoveryClient
,
@EnableCircuitBreaker
,三者合一,是spring cloud項目開發的基本注解!!
設定接口熔斷回調
局部熔斷回調
使用
@HystrixCommand
的
fallbackMethod
屬性配置,具體看如下代碼:
@RestController
@RequestMapping("consumer")
public class ConsumerController {
@Autowired
private RestTemplate restTemplate;
@Autowired
private LoadBalancerClient ribbonClient;
@GetMapping("/{id}")
@HystrixCommand(fallbackMethod = "queryFailback")
public Result<User> queryById(@PathVariable("id") String id) {
ServiceInstance instance = ribbonClient.choose("user-service");
String host = instance.getHost();
int port = instance.getPort();
String url = String.format("http://%s:%d/user/%s", host, port, id);
System.out.println(url);
User user = restTemplate.getForObject(url, User.class);
return Result.success(user);
}
public Result<User> queryFailback(String id) {
return Result.error("伺服器繁忙,請稍後再試....");
}
}
公共熔斷回調
所謂公共熔斷回調即對整個類生效,隻需要在類上配置注解
@DefaultProperties
的
defaultFallback
屬性,在需要熔斷的方法上加上
@HystrixCommand
即可
參考代碼如下:
@RestController
@RequestMapping("consumer")
@DefaultProperties(defaultFallback = "defaultFailback")
public class ConsumerController {
@Autowired
private RestTemplate restTemplate;
@Autowired
private LoadBalancerClient ribbonClient;
@GetMapping("/{id}")
@HystrixCommand
public Result<User> queryById(@PathVariable("id") String id) {
ServiceInstance instance = ribbonClient.choose("user-service");
String host = instance.getHost();
int port = instance.getPort();
String url = String.format("http://%s:%d/user/%s", host, port, id);
System.out.println(url);
User user = restTemplate.getForObject(url, User.class);
return Result.success(user);
}
public Result<User> defaultFailback() {
return Result.error("伺服器繁忙,請稍後再試試....");
}
}
進階配置
以下這些配置其實都在
HystrixCommandProperties
類源碼中有對應指出的,并不是亂來的。
全局逾時時間
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds
局部逾時時間
接口方法上加上如下注解:
@HystrixCommand(commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "6000")
})
熔斷器請求量
熔斷器會在一定的請求次數後對請求進行分析統計,并跳入下一個狀态,比如統計失敗次數過多,将進入熔斷狀态。
通過如下配置項進行配置:
circuitBreaker.requestVolumeThreshold
出錯比例
前面說到達到一定的請求進行一次統計,比如統計失敗次數過多,将進入熔斷狀态,這裡的過多,其實是根據一定比例來判定的,即出錯比例
可以通過如下配置項進行配置:
circuitBreaker.errorThresholdPercentage
休眠時間窗
熔斷器熔斷後并不是完事了,還要嘗試恢複,是以會在一定的時間後從熔斷狀态變為半開放狀态,測試目前服務叢集可用性,這個時間即休眠時間窗
可用通過如下配置項進行配置:
circuitBreaker.sleepWindowInMilliseconds
通過注解的方式配置,結合局部逾時時間、熔斷請求量、休眠時間窗、出錯比例一起使用,最終配置的結果如下:
@HystrixCommand(commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "6000"),
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "10"),
@HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds", value = "10000"),
@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "60"),
})
feign
基本使用
feign基本功能即代替restTemplate寫法,注意它隻是遠端調用的工具,封裝以簡化代碼開發而已的,并沒有改變遠端調用的工作。
導入依賴
feign 已經包含了ribbon和hystrix依賴,但是為了全面,将同時導入也無所謂
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
啟動類添加注解
啟動類添加如下注解開啟feign用戶端
@EnableFeignClients
添加遠端調用的服務消費端端接口
feign 遠端調用其實是通過配置熟悉的
spring mvc
文法來構造URL,代替restTemplate方式的調用。
方式就是以接口的形式定義相關配置,如URI,傳回值,參數清單等。這個接口方法的定義保持和
spring mvc
端的
web
層方法一緻。
@FeignClient("user-service")
@RequestMapping("/user")
public interface UserClient {
@GetMapping("/{id}")
User queryById(@PathVariable("id") String id);
}
遠端調用方式三————面向接口調用
前面說到兩種遠端調用的方式,都是需要顯式指出URL,調用restTemplate,這樣寫看似代碼邏輯清晰,簡單明了。但是大量寫死的代碼造成代碼備援,邏輯重複,可維護性差。是以就出現了最終的高端寫法:
面向接口調用,是一個相當抽象的過程,看不出來内部使用什麼實作的。方式也很簡單,不需要RestTempalte,也不需要LoadBalancerClient,直接注入前面自定義的配置接口即可,具體參考如下代碼:
@RestController
@RequestMapping("consumer")
public class ConsumerController {
@Autowired
private UserClient userClient;
@GetMapping("/{id}")
public Result<User> queryById(@PathVariable("id") String id) {
User user = userClient.queryById(id);
return Result.success(user);
}
}
讓你完全看不出遠端調用的痕迹,這隻是一種寫法而已,底層仍然時想注冊中心發起遠端調用的。
這種寫法說好聽點就是減少代碼備援,避免代碼重複,但其實就是裝逼式寫法,增加了了解難度,看得懂的人秀,看不懂的人怎麼也别想不懂… _
feign 整合 Ribbon 和 Hystrix 重試和熔斷
溫馨提示:不建議使用,因為沒有成功的。可能是我技術問題
feign 本身提供了 Ribbon 和 Hystrix,它就相當于是spring cloud家族的一個內建工具,是以導入依賴的時候不需要
ribbon
的相關依賴(
Hystrix
還是要的,因為剛出版,沒有完全內建)
ribbon 和 Hystrix相關配置
大多數和原來一樣的,隻不過要開啟feign
spring:
application:
name: user-consumer
cloud:
loadbalancer:
retry:
enabled: true # 開啟spring cloud 重試功能
user-service:
ribbon:
# NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule # 更換負載均衡政策
ConnectTimeout: 250 # Ribbon連接配接逾時時間
ReadTimeout: 1000 # Ribbon讀取資料逾時時間
OkToRetryOnAllOperations: true # 是否對所有操作都進行重試
MaxAutoRetriesNextServer: 100 # 切換執行個體的重試次數
MaxAutoRetries: 1 # 對目前執行個體的重試次數
feign:
hystrix:
enabled: true # 開啟feign的hystrix熔斷
ribbon:
ConnectTimeout: 500 # 連接配接逾時時長
ReadTimeout: 1000 # 讀取逾時時長
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 6000 # 設定hystrix的逾時時間
建立failback工廠作為熔斷回調
建議用工廠模式,不然不容易成功
@Component
public class UserClientFallbackFactory implements FallbackFactory<UserClient> {
@Override
public UserClient create(Throwable cause) {
return new UserClient() {
@Override
public User queryById(String id) {
return null;
}
};
}
}
添加遠端調用的服務消費端端接口
和之前一樣,使用接口,但是需要在@FeignClient注解上添加多一個fallbackFactory 屬性:
@FeignClient(value = “user-service”, fallbackFactory = UserClientFallbackFactory.class)
注意,feign的熔斷不走hystrix的流程,是以feign配置和hystrix配置是會沖突的,是以如果要用
feign
的熔斷功能的話,就要把原來的hystrix熔斷相關的注解去掉,不然不會成功。(雖然就沒成功過!)
Zuul網關
是主要的對外接口,外部必須通過網關才能通路具體的微服務,網關同時具有服務聚合的功能。
簡單來說,就是Controller
基本使用
網關作為單獨的項目存在,也是
spring boot
服務
添加依賴
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
啟動類開啟Zuul網關注解
@EnableZuulProxy
注意使用ZuulProxy而不是ZuulApplication,功能更完善
進階配置
基于服務的路由配置
所謂路由配置即URI到服務的映射關系配置
這其實是Zuul的預設配置,預設它會将所有的服務名稱作為字首到URI上去,是以即使不配置,也可照常通路。
但是有時候需求,并不要求服務名稱作為URI字首通路時,就需要指定配置了。
zuul:
routes:
user-service:
path: /user-service/**
serviceId: user-service
忽略服務
有時候并不希望有些服務對外使用,但是網關預設是全部注冊上的,是以需要配置忽略的服務
zuul:
routes:
user-service: /user/**
ignored-services:
- user-consumer
局部字首忽略
通路 zuul.routes.[service-id].path 是需要帶字首的
zuul:
routes:
user-service:
path: /user/**
serviceId: user-service
strip-prefix: false
全局通路字首
zuul.prefix: /api
整合Ribbon 和 Hystrix
配置其實和之前一個樣,隻需要加上如下配置即可
zuul.retryable
zuul:
retryable: true # 開啟重試
ribbon:
ConnectTimeout: 1000
ReadTimeout: 1000
MaxAutoRetries: 0
MaxAutoRetriesNextServer: 100
OkToRetryOnAllOperations: true
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 6000 # 設定hystrix的逾時時間
自定義過濾器
先直接看示範
@Component
public class LoginFilter extends ZuulFilter {
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() throws ZuulException {
RequestContext context = RequestContext.getCurrentContext();
HttpServletRequest request = context.getRequest();
String token = request.getParameter("token");
if(StringUtils.isBlank(token)) {
// 不存在,則攔截
context.setSendZuulResponse(false);
HttpServletResponse response = context.getResponse();
response.setStatus(HttpStatus.FORBIDDEN.value());
response.setContentType("text/json;charset=UTF-8");
context.setResponse(response);
// context.setResponseStatusCode(HttpStatus.FORBIDDEN.value());
context.setResponseBody(JSON.toJSONString(Result.error("沒有權限")));
}
return null;
}
@Override
public String filterType() {
return FilterConstants.PRE_TYPE;
}
@Override
public int filterOrder() {
return FilterConstants.PRE_DECORATION_FILTER_ORDER - 1;
}
}