阿里二面:外部接口大量超时,把整个系统拖垮,引发雪崩!如何解决?熔断...

大家好,我是Tom哥~

互联网+ 时代,业务数字化已经蔓延到你能想到的各个行业。各种业务功能、营销玩法越来越多,系统也越来越复杂。

面对不断复杂的业务系统,脑子越来越不够用了

于是 聪明的人们 提出了 微服务 的设计思想

本着 复杂的事情简单化 的原则,我们将一个大的系统拆分成若干个子系统,每个 子系统 职责单一,按 DDD 的设计理念,承载一个子域的业务建设。

于是,人们可以将精力聚焦,专心完成某一个业务点的深度建设。

多个微服务系统之间通过 RPC 框架(如:dubbo、spring cloud、gRPC 等)完成了串联,但随着调用量越来越大,人们发现服务与服务之间的稳定性变得越来越重要

举个例子:

Service D 挂了,响应很慢

Service G 和 Service F ,都依赖 Service D,也会受到牵连,对外响应也会变慢

影响层层向上传递,Service A 和 Service B 也会被拖垮

最后,引发雪崩效应,系统的故障影响面会越来越大

为了解决这种问题,我们需要引入 熔断 机制。“当断则断,不受其乱。当断不断,必受其难”

什么是熔断?

熔断,其实是对调用链路中某个资源出现不稳定状态时(如:调用超时或异常比例升高),对这个资源的调用进行限制,让请求快速失败,避免影响到其它的资源而导致级联错误。

当资源被降级后,在接下来的降级时间窗口内,对该资源的调用都自动熔断(默认是抛出 BlockException

目前市面上的熔断框架很多,如:SentinelHystrixResilience4j 等,这些框架的设计理念都差不多。

本文重点讲下 Sentinel 是如何在项目中使用的

Sentinel (分布式系统的流量防卫兵) 是阿里开源的一套用于服务容错的综合性解决方案。它以流量为切入点, 从流量控制熔断降级系统负载保护等多个维度来保护服务的稳定性。

核心分为两部分:

1、核心库(Java 客户端):能够运行在所有 Java 环境,对 Dubbo 、Spring Cloud 等框架也有较好的支持。

2、控制台(Dashboard):基于 Spring Boot 开发,打包后可以直接运行。

Sentinel 熔断种类:

RT 响应时间

异常数

异常比例

Sentinel 安装

首先,官网下载 sentinel 控制台安装包

下载地址:https://github.com/alibaba/Sentinel/releases

下载 Jar 包后,打开终端,运行命令

java -Dserver.port=8180 -Dcsp.sentinel.dashboard.server=localhost:8180 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard-1.8.1.jar

登陆Sentinal控制台:

默认用户和密码都是 sentinel ,登录成功后的界面如下,先来个直观感受

控制台配置熔断规则:

这里表示熔断策略选择 慢调用比例,响应时间超过200毫秒则标记为慢请求。如果在一个1000 ms的统计周期内(可自行调整),慢请求比例超过30%且数量超过3个,则对后续请求进行熔断,熔断时长为10秒钟,10秒以后恢复正常。

注解式接入

接入非常简单,只需要提前在控制台配置好资源规则,然后在代码中添加 @SentinelResource注解即可。

// 资源名称为handle1 
@RequestMapping("/handle1")
@SentinelResource(value = "handle1", blockHandler = "blockHandlerTestHandler")
public String handle1(String params) { 
    // 业务逻辑处理
    return "success";
}

// 接口方法 handle1 的 兜底方法
public String blockHandlerTestHandler(String params, BlockException blockException) {
    return "兜底返回";
}

达到阈值后,系统的默认提示是一段英文,很不友好,我们可以自定义兜底方法。在@SentinelResource注解中进一步配置 blockHandlerfallback 属性字段

blockHandler:主观层面,如果被限流或熔断,则调用该方法,进行兜底处理

fallback:对业务的异常兜底,比如,执行过程中抛了各种Exception,则调用该方法,进行兜底处理

通过上面两层兜底,可以让Sentinel 框架更加人性化,体验更好。

注意:注解式开发,需要添加在方法上,作用域范围相对固定。下面的项目实战中,我们也可以采用 显示 形式,可以灵活圈定代码块范围。

项目实战

我们这边有个项目,考虑到客户的部署成本,想做一个轻量级方案,需求如下:

既想引入框架的熔断功能,又不想部署控制台

拦截点相对收拢,类似与dubbo消费端远程访问一样,在代理类的远程通讯位置做拦截处理

整理了一份大厂常考面试题,这份pdf包括 Java基础、Java并发、JVM、MySQL、Redis、Spring、MyBatis、Kafka、设计模式等面试题,分享给大家。

下载地址:百度云链接:https://pan.baidu.com/s/1XHT4ppXTp430MEMW2D0-Bg 提取码: s3ab

概要方案--流程图:

1、我们通过 Proxy.newProxyInstance 为所有的接口创建了代理子类

2、所有对代理子类的方法调用全部收拢到 InvocationHandler

3、我们讲类名和方法名做一个拼接,然后去 熔断规则表查询,看是否配置了规则

4、如果没有,那么走常规则远程调用逻辑

5、如果有,将远程调用逻辑纳入 Sentinel 的监控管辖

6、如果触发了 熔断机制,则直接抛出 BlockException ,上层业务拦截异常,做特殊处理,比如:修饰下给用户更合适的文案提示。

熔断状态机:

核心的代码逻辑,继续往下看

首先,引入 Sentinel 的依赖包:

<!-- 限流、熔断框架 -->
<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-core</artifactId>
    <version>1.8.3</version>
</dependency>

熔断规则表设计:

CREATE TABLE `degrade_rule` (
  `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
  `resource_name` varchar(256) NOT NULL COMMENT '资源名称',
  `count` double NOT NULL COMMENT '慢调用时长,单位 毫秒',
  `slow_ratio_threshold` double NOT NULL COMMENT '慢调用比例阈值',
  `min_request_amount` int NOT NULL COMMENT '熔断触发的最小请求数',
  `stat_interval` int NOT NULL COMMENT '统计时长,单位 毫秒',
  `time_window` int NOT NULL COMMENT '熔断时长,单位为 s',
  `created_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `updated_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
  PRIMARY KEY (`id`) USING BTREE,
  UNIQUE KEY `uk_resource_name` (`resource_name`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb3 COMMENT='熔断规则表';

由于放弃了部署控制台,我们只能自己管理熔断规则的各个属性值。可以按企业内部管理后台风格,开发页面管理这些规则。

当然,早期可以采用更简单粗暴方式,在数据库表手动初始化数据。如果要调整规则,走 SQL 订正。

为了尽可能实时感知规则表数据变更,开发了定时任务,每 10 秒运行一次。

@Scheduled(cron = "0/10 * * * * ? ")
public void loadDegradeRule() {

    List<DegradeRuleDO> degradeRuleDOList = degradeRuleDao.queryAllRule();
    if (CollectionUtils.isEmpty(degradeRuleDOList)) {
        return;
    }

    String newMd5Hex = DigestUtils.md5Hex(JSON.toJSONString(degradeRuleDOList));
    if (StringUtils.isBlank(newMd5Hex) || StringUtils.equals(lastMd5Hex, newMd5Hex)) {
        return;
    }
    List<DegradeRule> rules = null;
    List<String> resourceNameList = new ArrayList<>();
    rules = degradeRuleDOList.stream().map(degradeRuleDO -> {
         //资源名,即规则的作用对象
        DegradeRule rule = new DegradeRule(degradeRuleDO.getResourceName()) 
                // 熔断策略,支持慢调用比例/异常比例/异常数策略
                .setGrade(CircuitBreakerStrategy.SLOW_REQUEST_RATIO.getType())
                //慢调用比例模式下为慢调用临界 RT(超出该值计为慢调用);异常比例/异常数模式下为对应的阈值
                .setCount(degradeRuleDO.getCount())
                // 熔断时长,单位为 s
                .setTimeWindow(degradeRuleDO.getTimeWindow())
                // 慢调用比例阈值
                .setSlowRatioThreshold(degradeRuleDO.getSlowRatioThreshold())
                //熔断触发的最小请求数,请求数小于该值时即使异常比率超出阈值也不会熔断
                .setMinRequestAmount(degradeRuleDO.getMinRequestAmount())
                //统计时长(单位为 ms)
                .setStatIntervalMs(degradeRuleDO.getStatInterval());
        resourceNameList.add(degradeRuleDO.getResourceName());
        return rule;
    }).collect(Collectors.toList());

    if (CollectionUtils.isNotEmpty(rules)) {
        DegradeRuleManager.loadRules(rules);
        ConsumerProxyFactory.resourceNameList = resourceNameList;
        lastMd5Hex = newMd5Hex;
    }

    log.error("[DegradeRuleConfig] 熔断规则加载: " + rules);
}

考虑到规则变更频率不会很高,没有必要每次都DegradeRuleManager.loadRules重新加载规则。这里设计了个小窍门

DigestUtils.md5Hex(JSON.toJSONString(degradeRuleDOList));

对查询的规则内容 JSON 序列化,然后计算其md5摘要,如果跟上一次的结果一致,说明这期间没有变更,直接 return ,不做处理。

定义子类,实现了 InvocationHandler 接口。通过 Proxy.newProxyInstance 为目标接口创建一个代理子类。

这样,每次调用接口方法,实际都是在调用 invoke 方法

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
 Class<?> clazz = proxy.getClass().getInterfaces()[0];
 String urlCode = clazz.getName() + "#" + method.getName();
 if (resourceNameList.contains(urlCode)) {
        // 增加熔断处理
        Entry entry = null;
        try {
            entry = SphU.entry(urlCode);
            // 远程网络调用,获取结果
            responseString = HttpClientUtil.postJsonRequest(url, header, body);
        } catch (BlockException blockException) {
            // 触发熔断
            log.error("degrade trigger !  remote url :{} ", urlCode);
            throw new DegradeBlockExcetion(urlCode);
        } finally {
            if (entry != null) {
                entry.exit();
            }
        } 
     } else {
          // 常规处理,不走熔断判断逻辑
          // 省略
    }    
}

实验数据:

上面案例的源代码已经上传到 GitHub,关注公众号:微观技术,回复关键词:「1819」 即可获取


关于我:

Tom哥计算机研究生,校招进阿里,期间还拿了百度、华为、中兴、腾讯 等6家大厂offer,P7 技术专家。出过专利CSDN博客专家

多年的大厂浸染,参加多次淘宝双11大促活动,在系统架构方面有丰富经验,沉淀总结在微信公众号:微观技术

他专注于微服务、高并发、高性能缓存、分布式架构、高可用、团队管理等方面,喜欢挖掘开源框架亮点设计,内容都是面试官喜欢考察的强烈推荐关注一波。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容