springcloud斷路器異常處理

最近團(tuán)隊(duì)從dubbo切換到springcloud件炉,自己碰到的一些問(wèn)題,特別是這個(gè)很常見(jiàn)的調(diào)用異常愕撰,做一些分析刹衫。

springcloud 微服務(wù)框架有各種組件,可以搭建一個(gè)完整的微服務(wù)應(yīng)用搞挣。包括

  • 注冊(cè) 中心:eureka 或者 consul

  • 服務(wù)提供者:各種 provider

  • 服務(wù)消費(fèi)者:各種 consumer

  • 網(wǎng) 關(guān):zuul

這里一定會(huì)碰到的問(wèn)題就是, consumer 調(diào)用 provider, provider 內(nèi)部出現(xiàn)異常了带迟,consumer 如何拿到具體的異常信息并且返回給頁(yè)面。

為什么要返回具體的異常信息囱桨?

異常不具體的話仓犬,什么都是提示服務(wù)器異常,那說(shuō)了等于沒(méi)說(shuō)舍肠,誰(shuí)特么不知道是服務(wù)器異常搀继。如果提示具體信息的話,比如訂單號(hào)已存在翠语,訂單不存在叽躯,扣費(fèi)失敗,sku不存在肌括,一眼就可以定位到問(wèn)題在哪里点骑,不用去翻日志找半天。
現(xiàn)成的方案就是使用 hystrix 斷路器的功能


image.png

斷路器的使用 :

1 設(shè)置 fallback


@FeignClient(name = ProviderServiceName.SERVICE_NAME, fallbackFactory=CommentServiceFallbackFactory.class)
public interface CommentService extends CommentBridge {

    //現(xiàn)在測(cè)試 調(diào)用這個(gè)方法谍夭,provider出現(xiàn)異常
    @PostMapping(value="/save")
    void save(@RequestBody Comment comment);
}

save 方法沒(méi)有返回值說(shuō)明:

對(duì)于這種可以沒(méi)有返回值的方法調(diào)用黑滴,有些人認(rèn)為要加上返回值 response, 然后在消費(fèi)者的代碼里面來(lái)判斷返回值是否成功。其實(shí)大可不必紧索,微服務(wù)之間的調(diào)用也是服務(wù)調(diào)用袁辈,相當(dāng)于調(diào)用一個(gè)方法而已,沒(méi)有拋出異常就可以認(rèn)為是執(zhí)行成功的齐板,有異常的話都程序停止執(zhí)行吵瞻,事務(wù)回滾了葛菇。為什么還要加 response, 在里面搞一個(gè)所謂的 狀態(tài)碼來(lái)判斷呢甘磨,它是微服務(wù),你卻把它當(dāng)成 http rest 接口來(lái)使用,它有的功能你不用眯停,這完全就是沒(méi)有領(lǐng)會(huì)微服務(wù)的概念济舆。

2 編寫(xiě) fallback 類

@Component
@Slf4j
public class CommentServiceFallbackFactory implements feign.hystrix.FallbackFactory<CommentService> {
    
    @Override
    public CommentService create(Throwable cause) {
        //cause是調(diào)用時(shí)出現(xiàn)的異常信息
        final String message = cause.getMessage();
        return new CommentService() {
            @Override
            public Page<Comment> page(CommentParameter parameter) {
                return null;
            }
            
            @Override
            public void save(Comment comment) {
                log.error("進(jìn)程pid: " + ManagementFactory.getRuntimeMXBean().getName());
                log.error("線程: " + Thread.currentThread().getName());

                //這里拋出異常嘗試消費(fèi)者的全局異常處理器捕獲
                throw new BizException(message);
            }
        };
    }
}

3 嘗試捕獲 BizException(message)

@Component
@Slf4j
public class GlobalExceptionHandler implements HandlerExceptionResolver, Ordered {
    
    private static final String ERROR_MESSAGE = "服務(wù)器掛掉了";
    
    @Override
    public int getOrder() {
        return 0;
    }
    
    @ResponseBody
    @Nullable
    @Override
    public ModelAndView resolveException(HttpServletRequest request,HttpServletResponse response,
                                         @Nullable Object handler, Exception ex) {
        log.error("進(jìn)程pid: " + ManagementFactory.getRuntimeMXBean().getName());
        log.error("線程: " + Thread.currentThread().getName());
    }
}

4 服務(wù)提供者里面直接拋異常

@RestController
public class CommentProviderController implements CommentBridge {
    
    @Resource
    private CommentRepository commentRepository;

    @Override
    public void save(Comment comment) {
        throw new BizException("provider service 拋出的異常");
    }

經(jīng)過(guò)測(cè)試,CommentServiceFallbackFactory 的 save 方法拋出的異常是無(wú)法被捕獲器捕獲到的莺债,這樣就沒(méi)法通過(guò) fallback 方法去控制異常的展示滋觉,返回签夭。


消費(fèi)者無(wú)法捕獲斷路器里面拋出的異常是因?yàn)檫@是2個(gè)不同的線程,不同線程之間的異常是不會(huì)互相影響的椎侠,上面那個(gè)是斷路器處理回滾的線程第租,下面那個(gè)是很典型的 servlet 線程.png

如果按照上面的方式來(lái)使用斷路器的話,這種使用方式完全是不可用的我纪,一個(gè)是 每個(gè)服務(wù)類里面要配置 fallbackFactory 慎宾,有多少個(gè)服務(wù)類就要對(duì)應(yīng)的寫(xiě)多少個(gè)回滾類,寫(xiě)到你吐血浅悉。第二個(gè)是斷路器里面拿到的 provider 的異常信息趟据,如何傳遞給消費(fèi)者,可以考慮用線程的等待通知機(jī)制术健,但是這么玩就不是微服務(wù)了汹碱。

現(xiàn)在有2種方式可以讓具體的異常信息逐級(jí)上報(bào),返回給頁(yè)面

1 全局異常處理里面解析捕獲到的異常信息荞估,直接返回到頁(yè)面

@Component
@Slf4j
public class GlobalExceptionHandler implements HandlerExceptionResolver, Ordered {
    
    private static final String ERROR_MESSAGE = "服務(wù)器掛掉了";
    
    @Override
    public int getOrder() {
        return 0;
    }
    
    @ResponseBody
    @Nullable
    @Override
    public ModelAndView resolveException(HttpServletRequest request,
                                         HttpServletResponse response,
                                         @Nullable Object handler,
                                         Exception ex) {
        log.error("進(jìn)程pid: " + ManagementFactory.getRuntimeMXBean().getName());
        log.error("線程: " + Thread.currentThread().getName());
        
        ModelAndView modelAndView = new ModelAndView();
        MappingJackson2JsonView mappingJackson2JsonView = new MappingJackson2JsonView();
        Map<String, Object> attributes = new HashMap<>(2);
        attributes.put("succeed", false);
        
        String errorMessage = null;
        
        if (ex instanceof MethodArgumentNotValidException) {
            MethodArgumentNotValidException validException = (MethodArgumentNotValidException) ex;
            FieldError fieldError = validException.getBindingResult().getFieldErrors().get(0);
            errorMessage = fieldError.getField() + " " + fieldError.getDefaultMessage();
        }
        else if (ex instanceof HystrixRuntimeException) {
            Throwable throwable = ex.getCause();
            if (throwable instanceof FeignException) {
                String content = StringUtils.substringBetween(throwable.getMessage(), "{", "}");
                JsonObject jsonpObject = new JsonParser().parse("{" + content + "}")
                                                         .getAsJsonObject();
                errorMessage = jsonpObject.get("message").getAsString();
            }
        }
        else {
            errorMessage = ERROR_MESSAGE;
        }
        attributes.put("message", errorMessage);
        mappingJackson2JsonView.setAttributesMap(attributes);
        modelAndView.setView(mappingJackson2JsonView);
        return modelAndView;
    }
}

2 配置 feign 的 ErrorDecoder

@Component
public class FeignErrorDecoder implements ErrorDecoder{

    @Override
    public Exception decode(String methodKey, Response response){
        
        //在這里解析 response 的結(jié)果并返回的異常信息可以被全局異常處理捕獲到
        return new Exception(response.getMessage());
    }
}

最終的效果就是這樣,調(diào)用消費(fèi)者的接口咳促,消費(fèi)者再去調(diào)用 提供者的接口,提供者處理時(shí)出現(xiàn)異常(參考上面第四部那個(gè)圖)

把異常信息作為結(jié)果返回


image.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末勘伺,一起剝皮案震驚了整個(gè)濱河市等缀,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌娇昙,老刑警劉巖尺迂,帶你破解...
    沈念sama閱讀 212,080評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異冒掌,居然都是意外死亡噪裕,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,422評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門股毫,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)膳音,“玉大人,你說(shuō)我怎么就攤上這事铃诬〖老荩” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 157,630評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵趣席,是天一觀的道長(zhǎng)兵志。 經(jīng)常有香客問(wèn)我,道長(zhǎng)宣肚,這世上最難降的妖魔是什么想罕? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,554評(píng)論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮霉涨,結(jié)果婚禮上按价,老公的妹妹穿的比我還像新娘惭适。我一直安慰自己,他們只是感情好楼镐,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,662評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布癞志。 她就那樣靜靜地躺著,像睡著了一般框产。 火紅的嫁衣襯著肌膚如雪今阳。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,856評(píng)論 1 290
  • 那天茅信,我揣著相機(jī)與錄音盾舌,去河邊找鬼。 笑死蘸鲸,一個(gè)胖子當(dāng)著我的面吹牛妖谴,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播酌摇,決...
    沈念sama閱讀 39,014評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼膝舅,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了窑多?” 一聲冷哼從身側(cè)響起仍稀,我...
    開(kāi)封第一講書(shū)人閱讀 37,752評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎埂息,沒(méi)想到半個(gè)月后技潘,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,212評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡千康,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,541評(píng)論 2 327
  • 正文 我和宋清朗相戀三年享幽,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片拾弃。...
    茶點(diǎn)故事閱讀 38,687評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡值桩,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出豪椿,到底是詐尸還是另有隱情奔坟,我是刑警寧澤,帶...
    沈念sama閱讀 34,347評(píng)論 4 331
  • 正文 年R本政府宣布搭盾,位于F島的核電站咳秉,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏增蹭。R本人自食惡果不足惜滴某,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,973評(píng)論 3 315
  • 文/蒙蒙 一磅摹、第九天 我趴在偏房一處隱蔽的房頂上張望滋迈。 院中可真熱鬧霎奢,春花似錦、人聲如沸饼灿。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,777評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)碍彭。三九已至晤硕,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間庇忌,已是汗流浹背舞箍。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,006評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留皆疹,地道東北人疏橄。 一個(gè)月前我還...
    沈念sama閱讀 46,406評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像略就,于是被迫代替她去往敵國(guó)和親捎迫。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,576評(píng)論 2 349