dubbo服务分组、限流措施以及服务熔断降级

订单模块问题

1、 订单模块的横向和纵向拆表。

在电商平台中订单表中的数据会越来越多,为了更好的业务扩招,需要对数据库表进行拆分。

横向拆分就是根据不同的订单类型拆分为服装订单表、家电订单表和其他订单表。
纵向拆分按年份拆分,例如2018年一个表,2020年一个表。

在数据库表拆分之后,当需要数据间从多个表中查找,这就需要dubbo的提供的特性服务分组分组聚合

dubbo服务分组

一个接口实现了多个不同数据库表间的查询,在dubbo中可以用group区分。

服务端

服务端中添加group属性,自定义group名称。



客户端

客户端需要调用哪个服务端提供的group组,就在reference中通过group属性,声明哪一个。

如果客户端只需要调用feedback组,这里只需要声明feedback组即可,



也可以通过group=“*”, 调用任意一个可以组的实现。


dubbo分组聚合

上面只是对服务进行分组,而dubbo还提供了,服务调用的结果聚合。比如菜单服务,接口一样,但有多种实现,用group区分,现在消费方需从每种group中调用一次返回结果,合并结果返回,这样就可以实现聚合菜单项。

客户端配置

如果需要所有分组进行聚合,只需要将group设置为*, 添加merger属性为true


合并指定分组,如果只需要将aaa,bbb进行结果合并,示例如下:


指定方法合并结果,其他未指定的方法,将只调用一个group。


    

某个方法不合并结果,其它都合并结果。


    

指定合并策略,缺省根据返回值类型自动匹配,如果同一类型有两个合并器时,需指定合并器的名称。


    

如果两个服务的返回值类型不一样,需要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、然后将剩下的一半提供者升级为新版本。

老版本服务提供者配置:


新版本服务提供者配置:


老版本服务消费者配置:


新版本服务消费者配置:


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


3、服务限流

在生产环境中,比如服务器只能处理1万个连接请求,如果超过1万个连接请求,就会给服务器造成压力,导致系统不可用。为了保证服务的高可用,我们需要对请求进行限流。

  • 限流措施是系统高可用的一种手段。
  • 使用dubbo并发与连接控制进行限流。(实际中不推荐使用)
  • 使用漏桶法和令牌桶算法进行限流。
漏桶算法

将所有请求,保存在一个队列桶中,然后以固定的频率,从桶中取出请求。

令牌桶算法

下面是从网上找的两张图来描述令牌桶算法,令牌以固定频率向桶中添加令牌,如果桶满了,令牌丢弃。
当有请求进来时,从桶中拿到令牌,才能进行访问。如果拿不到令牌拒绝访问。

dubbo服务分组、限流措施以及服务熔断降级_第1张图片
dubbo服务分组、限流措施以及服务熔断降级_第2张图片
漏桶算法和令牌桶算法的区别

如果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、 添加依赖

  
        
            org.springframework.cloud
            spring-cloud-starter-netflix-hystrix
            2.0.0.RELEASE
        
        
            org.springframework.boot
            spring-boot-starter-actuator
        
        
            org.springframework.cloud
            spring-cloud-starter-netflix-hystrix-dashboard
            2.0.0.RELEASE
        

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("抱歉,下单人太多,请稍后再试!");
    }

你可能感兴趣的:(dubbo)