淘東電商項目(78) -秒殺系統(服務保護)

引言

本文代碼已提交至Github(版本號:dd8742a8348b4c64a8ca794d544a3271e94365a9),有興趣的同學可以下載來看看:https://github.com/ylw-github/taodong-shop

秒殺系統的代碼在前面博客已經實現了,有興趣的同學可以參閱下:

秒殺的邏輯代碼完成了,剩下還有一個問題,就是關於服務的保護了,本文來講解。

本文目錄結構:
l____引言
l____ 1.服務保護
l________ 1.1 限流
l________ 1.2 降級、熔斷、限流概念
l____ 2.代碼
l________ 2.1 網關限流
l________ 2.2 服務保護
l____ 3.測試
l________ 3.1 網關限流測試
l________ 3.2 服務保護測試

1.服務保護

服務保護在《互聯網併發與安全》欄目有講解過,有興趣的同學可以參閱下:

下面來簡單的描述下。

1.1 限流

常見限流算法常用的限流算法有:令牌桶算法、漏桶算法

  • 令牌桶算法:在秒殺活動中,用戶的請求速率是不固定的,這裏我們假定爲10r/s,令牌按照5個每秒的速率放入令牌桶,桶中最多存放20個令牌。仔細想想,是不是總有那麼一部分請求被丟棄。
  • 漏桶算法:漏桶算法的主要目的是控制數據注入到網絡的速率,平滑網絡上的突發流量。漏桶算法提供了一種機制,通過它,突發流量可以被整形以便爲網絡提供一個穩定的流量

市面上常用實現限流框架:

  • Nginx+Lua、Guava、hystrix等

1.2 降級、熔斷、限流概念

服務雪崩效應:服務雪崩效應產生與服務堆積在同一個線程池中,因爲所有的請求都是同一個線程池進行處理,這時候如果在高併發情況下,所有的請求全部訪問同一個接口,這時候可能會導致其他服務沒有線程進行接受請求,這就是服務雪崩效應效應。

服務降級:在高併發情況下,防止用戶一直等待,使用服務降級方式(直接返回一個友好的提示給客戶端,調用fallBack方法)。

服務熔斷:熔斷機制目的爲了保護服務,在高併發的情況下,如果請求達到一定極限(可以自己設置闊值)如果流量超出了設置閾值,讓後直接拒絕訪問,保護當前服務。使用服務降級方式返回一個友好提示,服務熔斷和服務降級一起使用。

服務隔離:因爲默認情況下,只有一個線程池會維護所有的服務接口,如果大量的請求訪問同一個接口,達到tomcat 線程池默認極限,可能會導致其他服務無法訪問。

解決服務雪崩效應:使用服務隔離機制(線程池方式和信號量),使用線程池方式實現隔離的原理: 相當於每個接口(服務)都有自己獨立的線程池,因爲每個線程池互不影響,這樣的話就可以解決服務雪崩效應。

  • 線程池隔離:每個服務接口,都有自己獨立的線程池,每個線程池互不影響。
  • 信號量隔離:使用一個原子計數器(或信號量)來記錄當前有多少個線程在運行,當請求進來時先判斷計數器的數值,若超過設置的最大線程個數則拒絕該請求,若不超過則通行,這時候計數器+1,請求返回成功後計數器-1。

2.代碼

2.1 網關限流

網關限流,淘東電商項目採用的是基於谷歌RateLimiter實現限流。Google的Guava工具包中就提供了一個限流工具類——RateLimiter,本文也是通過使用該工具類來實現限流功能,RateLimiter是基於“令牌桶算法”來實現限流的。之前的的網關是使用“責任鏈設計模式”來重新構造了,本文也使用責任鏈模式添加RateLimiter來實現限流。

①添加 guava maven依賴:

<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
</dependency>

②添加限流Handler:

/**
 * description: 服務限流
 * create by: YangLinWei
 * create time: 2020/5/26 3:00 下午
 */
@Component
@Slf4j
public class CurrentLimitHandler extends BaseHandler implements GatewayHandler {
    private RateLimiter rateLimiter = RateLimiter.create(1);

    @Autowired
    private GenerateToken generateToken;


    @Override
    public Boolean service(RequestContext ctx, String ipAddres, HttpServletRequest request, HttpServletResponse response) {
        // 1.用戶限流頻率設置 每秒中限制1個請求
        boolean tryAcquire = rateLimiter.tryAcquire(0, TimeUnit.SECONDS);
        if (!tryAcquire) {
            resultError(ctx, "There are too many people snapping up goods now. Please wait a moment!");
            return Boolean.FALSE;
        }
        // 2.使用redis限制用戶訪問頻率
        String seckillId = request.getParameter("seckillId");
        String seckillToken = generateToken.getListKeyToken(seckillId + "");
        if (StringUtils.isEmpty(seckillToken)) {
            log.info(">>>seckillId:{}, The second kill has sold out, please come again next time!", seckillId);
            resultError(ctx, "The second kill has sold out, please come again next time!");
            return Boolean.FALSE;
        }
        if (gatewayHandler != null) {
            gatewayHandler.service(ctx, ipAddres, request, response);
        }
        return Boolean.TRUE;
    }
}

③工廠定義網關過濾步驟:

public class FactoryHandler {

    public static GatewayHandler getHandler() {
        // 1.黑名單攔截
        GatewayHandler handler1 = (GatewayHandler) SpringContextUtil.getBean("blackListHandler");

        // 2.API接口參數接口驗籤
        GatewayHandler handler2 = (GatewayHandler) SpringContextUtil.getBean("verifySignHandler");
        handler1.setNextHandler(handler2);

        // 3.參數過濾
        GatewayHandler handler3 = (GatewayHandler) SpringContextUtil.getBean("filterParamHandler");
        handler1.setNextHandler(handler3);

        //4.服務限流
        GatewayHandler handler4 = (GatewayHandler) SpringContextUtil.getBean("currentLimitHandler");
        handler3.setNextHandler(handler4);

        //5.驗證accessToken
        GatewayHandler handler5 = (GatewayHandler) SpringContextUtil.getBean("apiAuthorityHandler");
        handler4.setNextHandler(handler5);

        return handler1;
    }
}

2.2 服務保護

①添加Hystrix maven依賴:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>

②啓動類開啓Hystrix
在這裏插入圖片描述
③服務接口保護

@Override
@Transactional
@HystrixCommand(fallbackMethod = "spikeFallback")
public BaseResponse<JSONObject> spike(String phone, Long seckillId) {
	// 1.參數驗證
	if (StringUtils.isEmpty(phone)) {
		return setResultError("手機號碼不能爲空!");
	}
	if (seckillId == null) {
		return setResultError("商品庫存id不能爲空!");
	}
	// 2.從redis從獲取對應的秒殺token
	String seckillToken = generateToken.getListKeyToken(seckillId + "");
	if (StringUtils.isEmpty(seckillToken)) {
		log.info(">>>seckillId:{}, 親,該秒殺已經售空,請下次再來!", seckillId);
		return setResultError("親,該秒殺已經售空,請下次再來!");
	}

	// 3.獲取到秒殺token之後,異步放入mq中實現修改商品的庫存
	sendSeckillMsg(seckillId, phone);
	return setResultSuccess("正在排隊中.......");
}

private BaseResponse<JSONObject> spikeFallback(String phone, Long seckillId) {
	return setResultError("服務器忙,請稍後重試!");
}

3.測試

3.1 網關限流測試

瀏覽器訪問http://localhost/api-spike/spike?phone=13800000001&seckillId=100001,可以看到:
在這裏插入圖片描述
在1秒鐘之內繼續訪問,可以看到限流如下:
在這裏插入圖片描述

3.2 服務保護測試

服務保護測試採用JMeter來測試,由於測試條件的不允許,本文不再演示。如果要看演示的效果,可以參考之前寫的博客《微服務技術系列教程(22) - SpringCloud- 服務保護機制Hystrix》

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章