`

微服务化之----熔断和隔离

阅读更多

背景:

 

最近在负责一个数据中心的搭建工作,业务场景较多,比如:历史、实时数据计算和查询,汇总分析数据,以及基于数据与业务相结合提供智能推荐服务。对于每个不同的业务场景,所需要存储介质以及容量都不尽相同。比如:实时数据依赖redis,历史数据依赖hbase、mysql、mongdo,用户画像数据依赖es,redis等等。如果不进行微服务化拆分,其中任何一个组件出现问题,整个数据服务工程将都会有瘫痪的风险。

 

面临的问题:所有功能都被柔和在一个工程里,牵一发动全身,业务扩展难度大;相互干扰,每次新业务上线不仅需要验证新功能,还需要验证以前的功能是否受影响;另外 在大促期间也不易于做灾备、熔断和隔离。

 

由于以上原因,最近对整个系统架构做了微服务化拆分。首先看下老系统架构。

 

老系统架构:

 

 

 

 

可以看到这里典型的MVC架构(springmvc),业务代码彼此交织在一起,不便于扩展;各种服务相互依赖,假如hbass或redis整个数据中心将无法正常工作。

 

微服务化架构:

 

再看下微服务拆分后的系统架构

 

 

名称解释jsf:京东自己开发的RPC框架,类似于淘宝的duboo(图中虚线箭头表示jsf接口调用)

 

可以看到这里已经将原来的一个大工程,拆成8个独立的子工程:5个jsf工程 + 3个web服务工程。每个子工程只负责自己独立的业务逻辑,有自己独立的redis集群,数据彼此隔离。

 

Jsf服务子工程集(5个):目前第一期是根据存储介质的不同进行拆分。后续还可以无限扩展,比如即将接入的ES jsf服务、mogodb jsf 服务等。对于每种不同的存储介质,还可以按照业务进行垂直和水平拆分。

 

 

mysql jsf工程(目前1个):mysql主要用于存放数据量不大的汇总数据,以及用户信息数据。目前数据量还比较小,这里只需要一个mysql服务即可满足需求。如果有新业务加入,可以按照对mysql进行垂直拆分,拆分成几个业务独立的数据库,对每个数据库再新建jsf工程,最终形成多个mysql jsf工程集对外提供服务。如下:

 

 

 

 

如果某个业务mysql表数据量暴增,再进行水平拆分,根据实际情况拆分为多库多表。借助淘宝开源的mycat、DRDS分库分表中间件也很容易实现。

 

 

 

 

hbase jsf工程集(4个):为每个不同的业务申请不同的hbase实例(一个实例可以简单理解成mysql的一个数据库)。这里以实例为单位进行服务化,如果一个实例挂掉,也不会影响到其他实例。做到业务数据完全隔离。

 

其他 jsf工程:ES或mongodb等,待接入。

 

至此,简单的服务化拆分已经完成。下面来看微服务怎么做 隔离、熔断、限流。

 

隔离:

 

按照上述方法进行微服务拆分,已经在不同的数据存储介质和不同业务级别完成了服务隔离。保证其中某个服务坏掉,不会影响到其他服务。

 

利用jsf提供的filter功能可以很方便的为各个子服务做自动熔断、限流功能(主流的RPC框架都具备filter功能,如dubbo)。filter一般都采用“责任链模式”,其实"代理模式"也可以做,但"责任链模式"更方便扩展,不同的任务可以分配到不同的层级的filter。下面主要对自动熔断、限流进行讲解。

 

自动熔断:

 

要做自动熔断,我们首先实现的是统一异常捕获。先来看下我们以前jsf服务工程service实现代码:

 

/**
 * 定义服务接口
 * Created by gantianxing on 2017/5/18.
 */
public interface TestMysqlJsf {
 
    ResultModel getById(int id); //根据用户id获取用户信息
 
    ResultModel getByPage();//分页获取用户信息
}
 
/**
 * 服务实现类
 * Created by gantianxing on 2017/5/18.
 */
public class TestMysqlJsfImpl implements TestMysqlJsf{
    private static final Log log = LogFactory.getLog(TestMysqlJsfImpl.class);
 
    @Override
    public ResultModel getById(int id) {
        ResultModel resultModel = new ResultModel();
 
        try {
            DataUser userinfo = null;
            //省略各种计算代码
            resultModel.addAttribute("userInfo",userinfo);
        } catch (Exception e){
            resultModel.fail("xxxxxxxxxxx异常");
            log.error("xxxxxxxxxxx异常",e);
        }
        return resultModel;
    }
 
    @Override
    public ResultModel getByPage() {
        ResultModel resultModel = new ResultModel();
        try {
            List<DataUser> userList = null;
            //省略各种计算代码
            resultModel.addAttribute("userList",userList);
        } catch (Exception e){
            resultModel.fail("xxxxxxxxxxx异常");
            log.error("xxxxxxxxxxx异常",e);
        }
        return resultModel;
    }
}

 

 

以mysql对应的jsf服务工程为例,在该工程里有无数个类似TestMysqlJsf的服务接口。可以看到实现类TestMysqlJsfImpl里为了保证返回给接口调用方的信息足够友好,每个方法体里都加了try{} catch catch (Exception e),捕获所有的异常(避免抛出给调用方)。

这种重复的try catch 遍布所有的接口实现类的每个方法,费时费力,而且也不雅观。

 

怎么能更优雅的实现呢,这里采用的是在服务入口处统一添加一条filter list。首先来看我们第一个filter,统一异常处理filter:

/**
 * Created by gantianxing on 2017/5/18.
 */
public class HandleExceptionFilter extends AbstractFilter {
    private final static Log log = LogFactory.getLog(HandleExceptionFilter.class);
 
    /**
     * 统一异常处理过滤器
     * @param request jsf接口请求信息
     * @return response 如果出现异常,动态构造错误返回信息。
     */
    @Override
    public ResponseMessage invoke(RequestMessage request) {
        ResponseMessage response = null;
        try {
            response = getNext().invoke(request); // 调用链自动往下层执行,直到真实的服务接口被调用
 
        }catch (Exception e){
            String methodName = request.xxxxx().getMethodName(); //获取调用方法名
            String clazzName = request.xxxxx().getClazzName();//获取调用类名
            response = MessageBuilder.buildResponse(request); // 自己构造返回对象
            ResultModel resultModel = new ResultModel();// 动态构造异常返回
            resultModel.fail("接口调用异常:" + e.getMessage());
            response.setResponse(resultModel);
            log.error("接口调用异常:" + clazzName + ":" + methodName, e);
        }
        return response;
    }
}

 

通过配置保证,这层filter在所有业务方法调用之前,首先被调用,其中的response = getNext().invoke(request) 会根据配置再去调用到真实的接口实现方法。

这样所有的“运行时异常”都可以在统一这个过滤器中处理,所有service实现类都不再需要再添加类似try{} catch catch (Exception e) ("非运行时异常"除外)这样的代码,只需处理"非运行时异常"即可。

 

另外,方法性能监控也可以做到这层filter,统一对每个方法性能监控进行埋点,防止出现整个工程到处都是监控埋点的情况(牵涉公司业务太多,代码里没有体现)。

 

有了HandleExceptionFilter这个统一异常处理过滤器之后,上面的TestMysqlJsfImpl可以改为:

 

/**
 * Created by gantianxing on 2017/5/18.
 */
public class TestMysqlJsfImpl implements TestMysqlJsf{
    private static final Log log = LogFactory.getLog(TestMysqlJsfImpl.class);
 
    @Override
    public ResultModel getById(int id) {
        ResultModel resultModel = new ResultModel();
        DataUser userinfo = null;
        //省略各种计算代码
        resultModel.addAttribute("userInfo",userinfo);
        return resultModel;
    }
 
    @Override
    public ResultModel getByPage() {
        ResultModel resultModel = new ResultModel();
        List<DataUser> userList = null;
        //省略各种计算代码
        resultModel.addAttribute("userList",userList);
        return resultModel;
    }
}

 

 

没有了千篇一律的try catch是不是舒服了很多:-D

 

有人要说了,你这跟“自动熔断”有什么关系。别急,我们先看下,为什么要熔断,无非就是服务内部大范围出现异常时自动断开服务,快速的告诉接口调用方“目前服务不可用”。比如:连接数据库超时,或者服务内部调用其他外部接口调用超时等,当某一类异常达到一定的阀指时进行熔断。

 

我们的做法是,在过滤器filter中捕获这些异常,并对某一类异常进行计数,当异常到达一定阀值修改熔断开关为开启状态。将HandleExceptionFilter改造如下:

 

/**
 * Created by gantianxing on 2017/5/18.
 */
public class HandleExceptionFilter extends AbstractFilter {
    private final static Log log = LogFactory.getLog(HandleExceptionFilter.class);
 
    private static AtomicInteger errorcount = new AtomicInteger(0);//错误计数器
 
    @Resource
    private Redis redis;
 
    public void reLoadCount(){
        errorcount.set(0); //故障解除后,手动置0计数器。
    }
 
    /**
     * 统一异常处理过滤器
     * @param request jsf接口请求信息
     * @return response 如果出现异常,动态构造错误返回信息。
     */
    @Override
    public ResponseMessage invoke(RequestMessage request) {
        ResponseMessage response = null;
        try {
            response = getNext().invoke(request); // 调用链自动往下层执行,直到真实的服务接口被调用
        }catch (Exception e){
 
            if (e instanceof ConnectTimeoutException){
                errorcount.getAndIncrement();//原子 +1
                if(errorcount.get() > 50){ //错误次数大于50 熔断器开启
                    redis.setStr("circuit_breaker","on");
                }
            }
            String methodName = request.xxxxx().getMethodName(); //获取调用方法名
            String clazzName = request.xxxxx().getClazzName();//获取调用类名
            response = MessageBuilder.buildResponse(request); // 自己构造返回对象
            ResultModel resultModel = new ResultModel();// 动态构造异常返回
            resultModel.fail("接口调用异常:" + e.getMessage());
            response.setResponse(resultModel);
            log.error("接口调用异常:" + clazzName + ":" + methodName, e);
        }
        return response;
    }
}

每次失败都对异常计数器+1,每次真实接口调用前 先检查异常次数是否到达阀值。

 

在HandleExceptionFilter这层过滤器中只是把熔断开关开启,我们还需要新建一个熔断过滤器filter 添加到HandleExceptionFilter的上层:取名为CircuitBreakerFilter。 它的主要职责:如果熔断开关已经开启,直接返回错误提示;否则继续调用责任链往下执行HandleExceptionFilter。

 

 

/**
 * Created by gantianxing on 2017/5/18.
 */
public class CircuitBreakerFilter extends AbstractFilter {
    private final static Log log = LogFactory.getLog(HandleExceptionFilter.class);
 
    @Resource
    private Redis redis; //redis服务
 
    /**
     * 熔断 过滤器
     * @param request
     * @return
     */
    @Override
    public ResponseMessage invoke(RequestMessage request) {
        ResponseMessage response = null;
        if ("on".equals(redis.get("circuit_breaker"))){
            ResultModel resultModel = new ResultModel();
            resultModel.fail("请求已熔断,请联系服务通过方");
            response.setResponse(resultModel); // 返回结果
            log.info("请求已熔断");
        }else{
            response = getNext().invoke(request); // 调用链往下层执行:HandleExceptionFilter
        }
        return response;
    }
}

 

至此自动熔断实现已经讲完。这里是全熔断实现,如果要实现半熔断,可以再添加“状态模式”实现。

 

自动限流:

 

采用类似"自动熔断"的做法,也可以实现"自动限流",新建CurrentLimitFilter:

/**
 * Created by gantianxing on 2017/5/19.
 */
public class CurrentLimitFilter extends AbstractFilter {
 
    private final static Log log = LogFactory.getLog(CurrentLimitFilter.class);
 
    private static AtomicInteger count = new AtomicInteger(0);
 
    /**
     * 自动限流器
     * @param request
     * @return
     */
    @Override
    public ResponseMessage invoke(RequestMessage request) {
        ResponseMessage response = null;
 
        if (count.get() > 100) { //最高并发超过100 自动限流
            ResultModel resultModel = new ResultModel();
            resultModel.fail("请求到到上限");
            response = MessageBuilder.buildResponse(request); // 自己构造返回对象
            response.setResponse(resultModel); // 返回结果
            log.info("请求到到上限");
        }else{
            count.getAndIncrement(); //进入方法调用,并发计数器+1
            response = getNext().invoke(request); // 自动往下层执行
            count.getAndDecrement();//结束方法调用,并发计数器-1
            return null;
        }
        return response;
    }
}

 

主要采用AtomicInteger做计数器,当进入方法调用时+1,当结束方法调用时-1. 计数器get()方法获取的值即为 该服务器的并发量,如果并发量超过100(根据自己的业务、服务器性能自己配置),则进行限流。当并发量小于100,又自动恢复正常。我们暂且称之为:“丢弃式限流”。

 

这种限流措施,会对调用方产生一定负面影响。有人说,为什么不使用MQ(消息队列)进行限流,还可以保证数据不丢失。其实这是两种不同的手段,针对不同的业务场景。

 

MQ异步限流:适用于后端逻辑处理业务,无需及时向客户端返回处理结果,允许处理请求暂时积压,延迟处理(重要数据要求必须被处理)。比如 订单积压,一般都是采用MQ。

 

丢弃式限流:适用于需及时返回的前端业务,比如一个状态查询,前端页面要求及时返回查询结果(哪怕是错误的也行),否则页面就会被卡住,是一种大促常用降级手段。当服务端并发到达上限时,及时返回一条提示信息,用户再次刷新页面,有可能会得到正常结果。做到保护服务端的同时(预防调用链“雪崩”),让前端也能及时的得到响应。

 

采用“丢弃式限流”、或者直接熔断,可以避免“雪崩”问题。

 

 

 

最后 再把三个过滤器串联起来(注意顺序),限流过滤器放在调用链第一层,熔断过滤器放在第二层,统一异常处理过滤器放在第三层。最终调用流程如下:

 

 

 

简单总结下:

隔离:要做隔离,一种好的方法就是微服务拆分,让每个小业务成为孤岛,即便是其中一个业务挂掉,其他服务依然可以正常运行。

熔断、限流:在微服务化的基础上可以很方便的做熔断和限流。采用“责任链模式”在服务入口处进行统一封装即可。如果你采用的RPC框架不原生支持filter,可以自己实现一个“责任链模式”融合进去。

扩容:在微服务化后,只需对压力大的子服务进行针对性扩容;对重要的服务数据采用主从备份。总之,可以对不同的自服务灵活的采用不同的灾备手段。

 

最后说下,微服务的缺点:

1、不方便联调测试,需要启动多个服务。解决办法,在开发环境部署一整套服务,开发本机只启动一个正在开发的服务与之进行联调。

2、不方便事务控制,各个子服务构成了一个分布式环境,在需要事务的地方,必须做分布式事务控制。解决办法,对于mysql等关系型分库分布数据库,可以采用mycat等中间件;对于跨服务的,可以采用MQ的事务机制;其他办法,如日志+人工干预等。总之:分布式事务根据业务场景做到“最终一致性”即可。

 

 

以后再总结下分布式事务,这次就到这里吧。

2
1
分享到:
评论
4 楼 moon_walker 2017-06-06  
q315506754 写道
“老系统架构”中最后一句话有问题

不过还是写得很好,让我这种没接触过高并发的拓展了视野

语句好像有点不通,已修改,谢谢指正。
3 楼 q315506754 2017-06-06  
“老系统架构”中最后一句话有问题

不过还是写得很好,让我这种没接触过高并发的拓展了视野
2 楼 moon_walker 2017-05-23  
halloffame 写道
讲个分布式事务吧

这周末吧
1 楼 halloffame 2017-05-23  
讲个分布式事务吧

相关推荐

    京东微服务实践--杰夫服务框架.zip

    在实践中,京东可能还结合了Docker和Kubernetes等容器技术,实现了微服务的自动化部署和管理。Docker提供轻量级的虚拟化环境,让每个服务都能在一个隔离的环境中运行;而Kubernetes则负责集群的管理和编排,确保服务...

    自定义微服务熔断处理机制

    通过学习和实践这个“自定义微服务熔断处理机制”的例子,开发者不仅能掌握Spring Cloud的基础应用,还能深入理解服务容错设计的重要性和实现方式。这对于初学者来说,是迈进微服务领域的一大步。而对于经验丰富的...

    微服务架构专题-SpringBoot.pptx

    - **SpringBoot急速入门**:SpringBoot由Pivotal团队开发,旨在简化Spring应用的初始化和开发流程。通过自动配置和起步依赖,它消除了传统Spring项目中的大量XML配置。SpringBoot项目的核心在于@SpringBoot...

    反应式微服务框架Flower-flower.zip

    4. **熔断和降级**:借鉴Hystrix的设计,Flower提供了服务熔断和降级功能,当服务出现故障时,能够快速隔离故障服务,防止整个系统崩溃。 5. **弹性扩展**:微服务架构使得Flower可以轻松地进行水平扩展,只需增加...

    快手微服务架构体系实践共41页.pdf.zip

    2. **服务治理**:包括服务注册与发现、负载均衡、熔断和降级策略等,这些都是确保服务高可用和容错性的重要手段。快手可能详细介绍了他们使用的具体服务治理框架和技术实践。 3. **数据一致性**:在微服务环境下,...

    基于SpringBoot+Dubbo的微服务框架-SpringBoot-Dubbo-Docker-Jenkins.zip

    SpringBoot简化了应用开发,Dubbo实现了服务间的通信和治理,Docker保证了服务的隔离和部署的便捷性,而Jenkins则确保了整个流程的自动化。这种组合可以促进敏捷开发,提高开发效率,同时降低运维复杂度,是现代云...

    Micro 微服务实践-micro-starter.zip

    6. **容器化与编排**:Docker用于封装和隔离服务,Kubernetes或Docker Swarm进行服务编排,管理容器的生命周期和网络。 7. **数据一致性**:在分布式系统中,保持数据一致性是个挑战。可以采用事件驱动架构,通过...

    轻松搞定微服务spring-cloud-microservice.zip

    Hystrix还提供了降级、隔离和熔断策略,以确保系统的稳定性和弹性。 4. **智能路由**:Zuul是Spring Cloud提供的边缘服务和API网关,它能对请求进行路由,过滤,安全控制等功能。作为微服务架构的入口,Zuul可以对...

    微服务open-cloud-master.zip

    深入研究这个"open-cloud-master"项目,你将有机会学习如何设置和运行一个完整的微服务架构,包括服务的注册与发现、熔断机制、API路由、配置管理、以及如何通过Docker容器化服务进行部署。同时,你还可以了解到如何...

    基本微服务-hystrix-仪表板

    【标题】:“基本微服务-hystrix-仪表板”指的是使用Hystrix库构建的微服务监控工具,它提供了一个可视化的界面,用于展示微服务架构中的服务容错和熔断状态。 【描述】:Hystrix是Netflix开源的一个Java库,设计...

    藏经阁-spring cloud微服务架构设计与开发实践-222.pdf

    - **Hystrix**:断路器模式的实现,防止服务雪崩,提供熔断、降级、隔离功能。 - **Zuul**:API网关,处理所有微服务的外部请求,提供路由转发、过滤等功能。 - **Spring Cloud Config**:用于集中化管理微服务的...

    后端业务系统的微服务化改造.docx

    在当前数字化时代,后端业务系统的复杂性和规模不断增长,传统的单体架构逐渐暴露出其局限性,如扩展性差、部署困难、故障隔离不足等问题。微服务化改造成为了应对这些问题的有效解决方案。本文将深入探讨微服务化...

    微服务项目-黑马商城项目

    6. **容器化与持续集成/持续部署(CI/CD)**:虽然文件列表中没有直接提及,但微服务项目通常会使用Docker进行服务的容器化,以便于部署和隔离环境。同时,使用Jenkins或GitLab CI/CD等工具进行自动化构建和部署,提高...

    xmljava系统源码-jplogiccloud:jplogiccloud微服务架构平台-微服务快速开发脚手架实践(包含微服务架构(监控+分布

    内含功能"麻雀虽小五脏俱全",可以基于平台组件拓展很多产品应用场景的实现(其中涵盖微服务架构(监控+分布式配置+服务熔断+服务资源隔离+服务限流+反爬限制+多级缓存架构等)+大数据(实时计算与存储)+多种对象...

    springclound 微服务-springcloud-learn.zip

    3. **Hystrix**: Netflix Hystrix是一个延迟和容错库,旨在通过隔离请求线程、短路、超时、回退和熔断机制,防止级联故障,提高系统的弹性。在微服务架构中,Hystrix有助于实现服务降级和容错管理。 4. **Ribbon**:...

    藏经阁-微服务治理技术白皮书-379页

    书中详细阐述了微服务治理的演进历程,从最初的SDK治理,到轻量级隔离容器Pandora,再到无侵入式的Java Agent和Service Mesh,揭示了服务治理在开发、测试、运维、高可用等多个层面的关键能力。 书中提到,微服务...

    【SpringCloud】最全最细!微服务框架-springcloud微服务架构

    - **易于扩展和故障隔离**:独立的服务可以根据需要进行水平扩展,同时也便于故障隔离。 ##### 1.3 总结 通过比较单体架构和微服务架构的特点,我们可以看到后者在可维护性、扩展性和灵活性等方面具有明显的优势。...

    java面试题_微服务--dubbo(41题).zip

    默认使用的是基于TCP的Dubbo协议,它提供了高效的二进制序列化和低延迟的通信。 5. **负载均衡策略**:Dubbo内置了多种负载均衡策略,如随机、轮询、最少活跃调用数、一致性哈希等,它们用于在多台服务实例间分发...

    微服务架构之网关1

    【微服务架构之网关1】的讨论集中在API网关的概念、作用以及统一API网关的设计策略上。API网关是微服务架构中一个至关重要的组成部分,它作为系统的统一入口,负责处理各种非业务逻辑的功能,如权限验证、流量控制、...

    十次方微服务开发v1.1--第7章1

    Spring Boot的特点在于默认配置和自动化,让开发者能够快速构建单个微服务。而Spring Cloud则在此基础上,关注于服务治理的全局框架,提供了包括服务发现、配置中心、负载均衡、熔断机制等一系列微服务治理功能。 ...

Global site tag (gtag.js) - Google Analytics