dubbo服務分組、限流措施以及服務熔斷降級

訂單模塊問題

1、 訂單模塊的橫向和縱向拆表。

在電商平臺中訂單表中的數據會越來越多,爲了更好的業務擴招,需要對數據庫表進行拆分。

橫向拆分就是根據不同的訂單類型拆分爲服裝訂單表、家電訂單表和其他訂單表。
縱向拆分按年份拆分,例如2018年一個表,2020年一個表。

在數據庫表拆分之後,當需要數據間從多個表中查找,這就需要dubbo的提供的特性服務分組分組聚合

dubbo服務分組

一個接口實現了多個不同數據庫表間的查詢,在dubbo中可以用group區分。

服務端

服務端中添加group屬性,自定義group名稱。

<dubbo:service group="feedback" interface="com.xxx.IndexService" />
<dubbo:service group="member" interface="com.xxx.IndexService" />
客戶端

客戶端需要調用哪個服務端提供的group組,就在reference中通過group屬性,聲明哪一個。

如果客戶端只需要調用feedback組,這裏只需要聲明feedback組即可,

<dubbo:reference id="feedbackIndexService" group="feedback" interface="com.xxx.IndexService" />
<dubbo:reference id="memberIndexService" group="member" interface="com.xxx.IndexService" />

也可以通過group=“*”, 調用任意一個可以組的實現。

<dubbo:reference id="barService" interface="com.foo.BarService" group="*" />
dubbo分組聚合

上面只是對服務進行分組,而dubbo還提供了,服務調用的結果聚合。比如菜單服務,接口一樣,但有多種實現,用group區分,現在消費方需從每種group中調用一次返回結果,合併結果返回,這樣就可以實現聚合菜單項。

客戶端配置

如果需要所有分組進行聚合,只需要將group設置爲*, 添加merger屬性爲true

<dubbo:reference interface="com.xxx.MenuService" group="*" merger="true" />

合併指定分組,如果只需要將aaa,bbb進行結果合併,示例如下:

<dubbo:reference interface="com.xxx.MenuService" group="aaa,bbb" merger="true" />

指定方法合併結果,其他未指定的方法,將只調用一個group。

<dubbo:reference interface="com.xxx.MenuService" group="*">
    <dubbo:method name="getMenuItems" merger="true" />
</dubbo:reference>

某個方法不合並結果,其它都合併結果。

<dubbo:reference interface="com.xxx.MenuService" group="*" merger="true">
    <dubbo:method name="getMenuItems" merger="false" />
</dubbo:reference>

指定合併策略,缺省根據返回值類型自動匹配,如果同一類型有兩個合併器時,需指定合併器的名稱。

<dubbo:reference interface="com.xxx.MenuService" group="*">
    <dubbo:method name="getMenuItems" merger="mymerge" />
</dubbo:reference>

如果兩個服務的返回值類型不一樣,需要merger="mymerge"通過指定一個合併器的類型。

dubbo提供的合併器有:

org.apache.dubbo.rpc.cluster.merger.ArrayMerger
org.apache.dubbo.rpc.cluster.merger.ListMerger
org.apache.dubbo.rpc.cluster.merger.SetMerger
org.apache.dubbo.rpc.cluster.merger.MapMerger

2、如何保證多版本的藍綠上線

在正式環境中,新版本上線時,會和老版本共同發佈在正式業務中,在正式環境中小範圍的測試一段時間,測試新版本的穩定性。
可以通過分組,進行服務的隔離調用,也可以通過dubbo中的多版本,來控制服務間的隔離。

dubbo多版本

當一個接口實現,出現不兼容升級時,可以用版本號過渡,版本號不同的服務間不引用。

可以按照以下的步驟進行版本遷移:

  • 0.在低壓力時間段,先升級一半提供者爲新版本。
  • 1、再將所有消費者升級爲新版本。
  • 2、然後將剩下的一半提供者升級爲新版本。

老版本服務提供者配置:

<dubbo:service interface="com.foo.BarService" version="1.0.0" />

新版本服務提供者配置:

<dubbo:service interface="com.foo.BarService" version="2.0.0" />

老版本服務消費者配置:

<dubbo:reference id="barService" interface="com.foo.BarService" version="1.0.0" />

新版本服務消費者配置:

<dubbo:reference id="barService" interface="com.foo.BarService" version="2.0.0" />

如果不需要區分版本,可以按照以下的方式配置 [1]:

<dubbo:reference id="barService" interface="com.foo.BarService" version="*" />

3、服務限流

在生產環境中,比如服務器只能處理1萬個連接請求,如果超過1萬個連接請求,就會給服務器造成壓力,導致系統不可用。爲了保證服務的高可用,我們需要對請求進行限流。

  • 限流措施是系統高可用的一種手段。
  • 使用dubbo併發與連接控制進行限流。(實際中不推薦使用)
  • 使用漏桶法和令牌桶算法進行限流。
漏桶算法

將所有請求,保存在一個隊列桶中,然後以固定的頻率,從桶中取出請求。

令牌桶算法

下面是從網上找的兩張圖來描述令牌桶算法,令牌以固定頻率向桶中添加令牌,如果桶滿了,令牌丟棄。
當有請求進來時,從桶中拿到令牌,才能進行訪問。如果拿不到令牌拒絕訪問。

在這裏插入圖片描述
在這裏插入圖片描述
漏桶算法和令牌桶算法的區別

如果10秒內,沒有任何請求,突然進來1萬個請求,漏桶算法還是以固定的頻率進行請求,而令牌算法桶中如果有1萬個令牌,這1萬個請求就會都請求成功。後續再有別的請求時,就會被拒絕。令牌桶算法對業務峯值有很好的承載能力。

示例: 簡單實現一個令牌桶算法。

public class TokenBucket {
    //桶的容量
    private int bucketNums = 100;
    //流入速度
    private int rate = 1;
    // 當前令牌數量
    private int nowTokens;
    // 時間
    private long timestamp;

    private long getNowTime() {
        return System.currentTimeMillis();
    }

    private int min(int tokens) {
        if (bucketNums > tokens) {
            return tokens;
        } else {
            return bucketNums;
        }
    }
    public boolean getToke() {
        // 記錄來拿令牌的時間
        long nowTime = getNowTime();
        //        添加令牌 【判斷該有多少個令牌】
        nowTokens = nowTokens + (int)((nowTime - timestamp) * rate);
        // 添加以後的令牌數量與桶的容量那個小
        nowTokens = min(nowTokens);
        // 修改拿令牌的時間
        timestamp = nowTime;
        // 判斷令牌是否足夠。
        if (nowTokens >= 1) {
            nowTokens -= 1;
            return true;
        } else {
            return false;
        }
    }

    public static void main(String[] args) {
        TokenBucket tokenBucker = new TokenBucket();
    }
}

業務中的使用:請求進來時,去獲取令牌,獲取成功,返回true。

    private static TokenBucket bucket = new TokenBucket();
    /**
     *  下訂單購票
     * @return
     */
    @RequestMapping(value = "buyTickets", method = RequestMethod.POST)
    public ResponseVO buyTickets(Integer fieldId, String soldSeats, String seatsName) {
        try {
            if (bucket.getToke()) {
                // 驗證售出的票是否爲真。
                boolean trueSeats = orderServiceApi.isTrueSeats(fieldId + "", soldSeats);
                // 已經銷售的座位裏,有沒有這些座位。
                boolean notSoldSeats = orderServiceApi.isNotSoldSeats(fieldId + "", soldSeats);
                // 驗證上述兩個內容有一個不爲真,則不創建訂單信息。
                if (trueSeats && notSoldSeats) {
                    // 創建訂單信息
                    String userId = CurrentUser.getUserId();
                    if (StringUtils.isBlank(userId)) {
                        return ResponseVO.serviceFail("用戶未登錄!");
                    }
                    OrderVO orderVO = orderServiceApi.saveOrderInfo(fieldId, soldSeats, seatsName, userId);
                    if (orderVO == null) {
                        log.error("購票業務異常");
                        return ResponseVO.serviceFail("購票業務異常");
                    } else {
                        return ResponseVO.success(orderVO);
                    }
                } else {
                    log.error("購票業務異常");
                    return ResponseVO.serviceFail("訂單中座位編號有問題");
                }
            } else {
              return ResponseVO.serviceFail("購票人數過多,請稍後再試!");
            }

        } catch (Exception e) {
           log.error("購票業務異常", e);
           return ResponseVO.serviceFail("購票業務異常");
        }

    }

4、Hystrix熔斷降級

在多服務環境中,比如訂單服務會調用商品服務、用戶服務、積分服務多個服務,如果商品服務掛掉了或者服務調用超時等問題,下訂單服務就會因爲長時間請求導致下單失敗,導致用戶體驗不佳。此時我們就可以通過熔斷降級的方式,不是直接給用戶反回一個異常,而是返回一個網絡不佳呀,或者其他問題。給用戶一個折中、最小影響的方案返回。

hystrix 應用示例

1、 添加依賴

  <!-- hystrix依賴 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
            <version>2.0.0.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
            <version>2.0.0.RELEASE</version>
        </dependency>

2、 啓動類添加註解

@EnableHystrixDashboard
@EnableCircuitBreaker
@EnableHystrix
@SpringBootApplication(scanBasePackages = {"com.stylefeng.guns"})
@EnableDubboConfiguration
@EnableAsync
@EnableHystrixDashboard
@EnableCircuitBreaker
@EnableHystrix
public class GatewayApplication {

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

3、 在需要熔斷的方法上,添加註解配置和添加降級回調方法。

    @HystrixCommand(fallbackMethod = "error", commandProperties = {
            @HystrixProperty(name = "execution.isolation.strategy", value = "THREAD"),
            @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "4000"),
            @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "10"),
            @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "50")
    },
            threadPoolProperties = { // hystrix 中線程池配置
                    @HystrixProperty(name = "coreSize", value = "1"),
                    @HystrixProperty(name = "maxQueueSize", value = "10"),
                    @HystrixProperty(name = "keepAliveTimeMinutes", value = "1000"),
                    @HystrixProperty(name = "queueSizeRejectionThreshold", value = "8"),
                    @HystrixProperty(name = "metrics.rollingStats.numBuckets", value = "12"),
                    @HystrixProperty(name = "metrics.rollingStats.timeInMilliseconds", value = "1500")
            })
    @RequestMapping(value = "buyTickets", method = RequestMethod.POST)
    public ResponseVO buyTickets(Integer fieldId, String soldSeats, String seatsName) {
        try {


            if (bucket.getToke()) {
                // 驗證售出的票是否爲真。
                boolean trueSeats = orderServiceApi.isTrueSeats(fieldId + "", soldSeats);
                // 已經銷售的座位裏,有沒有這些座位。
                boolean notSoldSeats = orderServiceApi.isNotSoldSeats(fieldId + "", soldSeats);
                // 驗證上述兩個內容有一個不爲真,則不創建訂單信息。
                if (trueSeats && notSoldSeats) {
                    // 創建訂單信息
                    String userId = CurrentUser.getUserId();
                    if (StringUtils.isBlank(userId)) {
                        return ResponseVO.serviceFail("用戶未登錄!");
                    }
                    OrderVO orderVO = orderServiceApi.saveOrderInfo(fieldId, soldSeats, seatsName, userId);
                    if (orderVO == null) {
                        log.error("購票業務異常");
                        return ResponseVO.serviceFail("購票業務異常");
                    } else {
                        return ResponseVO.success(orderVO);
                    }
                } else {
                    log.error("購票業務異常");
                    return ResponseVO.serviceFail("訂單中座位編號有問題");
                }
            } else {
                return ResponseVO.serviceFail("購票人數過多,請稍後再試!");
            }

        } catch (Exception e) {
            log.error("購票業務異常", e);
            return ResponseVO.serviceFail("購票業務異常");
        }
    }

自定義熔斷方法,和fallbackMethod屬性的值和定義的方法名一致。

    /**
     *  註解在哪個方法上,返回值和方法參數一定要一致。
     * @param fieldId
     * @param soldSeats
     * @param seatsName
     * @return
     */
    public ResponseVO error(Integer fieldId, String soldSeats, String seatsName) {
        return ResponseVO.serviceFail("抱歉,下單人太多,請稍後再試!");
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章