服務雪崩的6種解決方案(基於ribbon)

快些兒發表於2020-11-12

第一節,服務雪崩簡介

服務雪崩就是:一個服務不可用,導致一系列服務不可用,而這種後果往往無法預料。

造成雪崩原因可以歸結為以下三個:
1,服務提供者不可用(硬體故障,程式bug,快取擊穿,使用者大量請求)
2,重試加大流量(使用者重試,程式碼邏輯重試)
3,服務呼叫者不可用(同步等待造成的資源耗盡)

解決方案有如下5個,其中隔離包括兩種:
1,降級:超時降級,資源不足時(執行緒或訊號量)降級,降級後可以配合降級介面放回託底資料。實現一個fallback方法,當請求後端服務出現異常的時候,可以使用fallback方法返回的值;
2,隔離(執行緒池隔離和訊號量隔離):限制呼叫分散式服務的資源使用,某一個呼叫的服務出現問題不會影響其他服務呼叫;
3,熔斷:當失敗率(如因網路故障或超時 造成的失敗率高)達到閾值自動觸發降級,熔斷器觸發的快速失敗會進行快速恢復;
4,快取:提供了請求快取;
5,請求合併:提供了請求合併。

下面的雪崩解決方案是基於ribbon的,即在傳送請求的時候,需要自己手工構造遠端呼叫。

解決方案

服務降級

場景:比如一個訂單系統請求一個庫存系統,一個請求發過去,由於各種原因,網路超時,在規定的時間內沒有返回響應結果,這個時候更多的請求過來了,不斷地請求庫存服務,不斷的建立執行緒,由於沒有返回,也就沒有資源的釋放。時間一長,必將耗盡系統資源,導致系統崩潰。本來你的訂單系統好好的,但是請求了一個有問題的庫存系統,導致訂單系統也崩潰了,然後訂單系統又不斷呼叫其他的依賴系統,然後導致其他依賴系統也崩潰了,造成服務雪崩。這個時候hystrix可以實現快速失敗。
如果hystrix在一段時間內偵測到許多類似的錯誤,會強迫其以後的多個呼叫快速失敗,不再訪問遠端伺服器,從而防止應用程式不斷地嘗試執行可能會失敗的操作而導致系統資源被耗盡。這時hystrix進行fallback操作來服務降級。
Fallback相當於是降級操作。對於查詢操作,我們可以實現一個fallback方法,當請求後端服務出現異常的時候,可以使用fallback方法返回的值。fallback方法的返回值一般是預設的值或者是從快取中得到的值,通知後面的請求服務暫時不可用了。

package com.twf.e.book.consumer.hystrix.ribbon.fallback.service;

import java.util.ArrayList;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
import com.netflix.hystrix.contrib.javanica.conf.HystrixPropertiesManager;
import com.twf.e.book.consumer.hystrix.ribbon.fallback.domain.Product;

@Service
public class ProductService {
	
	@Autowired
	private LoadBalancerClient loadBalancerClient; // ribbon的負載均衡客戶端

	// @HystrixCommand這個註解指定了fallbackMethod為fallback。
	@HystrixCommand(fallbackMethod="fallback",commandProperties={
			@HystrixProperty(name=HystrixPropertiesManager.FALLBACK_ISOLATION_SEMAPHORE_MAX_CONCURRENT_REQUESTS,value="15")
	})
	public List<Product> listProduct() {
		ServiceInstance si = loadBalancerClient.choose("e-book-product");
		StringBuffer sb = new StringBuffer("");
		sb.append("http://");
		sb.append(si.getHost());
		sb.append(":");
		sb.append(si.getPort());
		sb.append("/product/list");
		System.out.println(sb.toString());
		
		RestTemplate restTemplate = new RestTemplate();
		ParameterizedTypeReference<List<Product>> typeRef = new ParameterizedTypeReference<List<Product>>(){};
		ResponseEntity<List<Product>> resp = restTemplate.exchange(sb.toString(), HttpMethod.GET, null, typeRef);
		List<Product> plist = resp.getBody();		
		return plist;
	}
	
	// 當呼叫遠端服務出現異常的時候,會呼叫這個方法
	public List<Product> fallback() {
		List<Product> list = new ArrayList<Product>();
		list.add(new Product(-1,"fallback"));
		return list;
	}
}

服務請求快取

比如一個請求過來請求我userId=1的資料,你後面的請求也過來請求同樣的資料,這時我不會繼續走原來的那條請求鏈路了,而是把第一次請求快取過了,把第一次的請求結果返回給後面的請求,這樣在一定程度上減少了請求的次數,降低網路資源的壓力,減少雪崩發生的概率。

@EnableCaching  // 開啟快取
@EnableCircuitBreaker  // 開啟服務降級斷路器
@EnableEurekaClient
@SpringBootApplication
public class ConsumerHystrixRibbonCacheApplication {

	public static void main(String[] args) {
		SpringApplication.run(ConsumerHystrixRibbonCacheApplication.class, args);
	}
}

在這裡插入圖片描述

服務請求合併

請求合併就是將單個請求合併成一個請求,去呼叫服務提供者,從而降低服務提供者負載的,一種應對高併發的解決方法。

在傳統的執行緒池中,有多少個請求就啟動多少個執行緒,比如上圖有6個請求就會啟動6條執行緒,這6條執行緒再去請求provider。如果一下子來了1000個請求,那麼執行緒池中的佇列就會被撐爆。這樣怎麼辦呢?我們就來做一個請求合併。

現在將6個請求合併成一個請求,再由執行緒池執行,這樣就可以解決執行緒池被撐爆的局面。
原理:通過請求合併器設定延遲時間,將延遲時間內的多個請求的請求引數取出來,封裝成一個引數list,作為batchMethod指定的方法(比如該方法名稱為batchProduct)的引數,然後呼叫這個batchProduct()方法。返回的物件list再通過一個方法(mapResponseToRequests方法),按照請求的次序將結果物件對應的裝到Request對應的Response中返回結果。

package com.twf.e.book.consumer.hystrix.ribbon.batch.service;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Future;

import org.springframework.stereotype.Service;

import com.netflix.hystrix.contrib.javanica.annotation.HystrixCollapser;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
import com.twf.e.book.consumer.hystrix.ribbon.batch.domain.Product;

@Service
public class ProductService {

	// 利用hystrix合併請求
	@HystrixCollapser(batchMethod = "batchProduct", scope = com.netflix.hystrix.HystrixCollapser.Scope.GLOBAL, collapserProperties = {
			// 請求時間間隔在20ms之內的請求會被合併為一個請求
			@HystrixProperty(name = "timerDelayInMilliseconds", value = "20"),
			// 設定觸發批處理執行之前,在批處理中允許的最大請求數
			@HystrixProperty(name = "maxRequestsInBatch", value = "200") })
	public Future<Product> getProduct(Integer id) {
		System.out.println("---------" + id + "----------");

		return null;
	}

	@HystrixCommand
	public List<Product> batchProduct(List<Integer> ids) {
		for (Integer id : ids) {
			System.out.println(id);
		}
		List<Product> list = new ArrayList<Product>();
		list.add(new Product(1, "登山包"));
		list.add(new Product(2, "登山杖"));
		list.add(new Product(3, "衝鋒衣"));
		list.add(new Product(4, "帳篷"));
		list.add(new Product(5, "睡袋"));
		list.add(new Product(6, "登山鞋"));
		return list;
	}
}

服務熔斷

熔斷機制相當於電路的跳閘功能。
例如:我們可以配置熔斷策略為當請求錯誤比例在10s內>50%時,該服務將進入熔斷狀態,後續請求都會進入fallback。

隔離技術之執行緒池隔離

場景:consumer端有一個執行緒池,裡面有兩個介面,短時間內介面A有10萬次請求,介面B有10次請求,由於介面A的大併發量導致整個執行緒池癱瘓,也就會導致B介面的不可用。最後整個consumer端不可用。
解決的方法:進行執行緒池隔離。
給A介面單獨設定一個執行緒池,給B介面也設定一個執行緒池,這樣當A介面癱瘓之後,不會影響B介面的執行。

通過每次都開啟一個單獨執行緒執行。它的隔離是通過執行緒池,即每個隔離粒度都是個執行緒池,互相不干擾。
執行緒池隔離方式,等於多了一層的保護措施,可以通過hytrix直接設定超時,超時後直接返回。

package com.twf.e.book.consumer.hystrix.ribbon.threadpool.service;

import java.util.ArrayList;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
import com.twf.e.book.consumer.hystrix.ribbon.threadpool.domain.Product;

@Service
public class ProductService {
	
	@Autowired
	private LoadBalancerClient loadBalancerClient; // ribbon的負載均衡客戶端


	@HystrixCommand(groupKey="e-book-product",commandKey="listProduct",
			threadPoolKey="e-book-product",
			threadPoolProperties={
					@HystrixProperty(name="coreSize",value="30"), // 執行緒池大小
					@HystrixProperty(name="maxQueueSize",value="100"), // 最大佇列長度
					@HystrixProperty(name="keepAliveTimeMinutes",value="2"), // 執行緒存活時間
					@HystrixProperty(name="queueSizeRejectionThreshold",value="15") // 拒絕請求
			},
			fallbackMethod = "fallback")
	public List<Product> listProduct() {
		ServiceInstance si = loadBalancerClient.choose("e-book-product");
		StringBuffer sb = new StringBuffer("");
		sb.append("http://");
		sb.append(si.getHost());
		sb.append(":");
		sb.append(si.getPort());
		sb.append("/product/list");
		System.out.println(sb.toString());
		
		RestTemplate restTemplate = new RestTemplate();
		ParameterizedTypeReference<List<Product>> typeRef = new ParameterizedTypeReference<List<Product>>(){};
		ResponseEntity<List<Product>> resp = restTemplate.exchange(sb.toString(), HttpMethod.GET, null, typeRef);
		List<Product> plist = resp.getBody();		
		return plist;
	}
	
	// 當呼叫遠端服務出現異常的時候,會呼叫這個方法
	public List<Product> fallback() {
		List<Product> list = new ArrayList<Product>();
		list.add(new Product(-1,"fallback"));
		return list;
	}
}

轉載自:
https://blog.csdn.net/tanwenfang/article/details/86478786
todo,還有很多,這只是我挑能懂的複製了些

相關文章