您的位置:首页 > 其它

服务降级及dubbo中的实现示例

2016-11-26 19:51 597 查看
经历过12306抢票的人应该经常会遇到这个问题:在抢票高峰的时候,明明票还有,但是查询出来的列表却是为空的(如果没票列表也应该会呈现);等高峰过后再查询,列表又恢复正常。个人猜测应该是查询过程中出现了问题,要么超时,要么网络问题导致查询失败采用的服务降级处理。所以,最终呈现给用户的并不是内部系统出错之类的提示,而是一个空的列表。

服务降级,当服务器压力剧增时,根据当前业务情况及流量对一些服务和页面有策略的降级,以此缓解了服务器资源压力,以保证核心任务的正常运行,同时也保证了部分甚至大部分客户得到正确响应。

服务降级实施策略

页面拒绝服务:页面提示由于服务繁忙此服务暂停。跳转到varnish或nginx的一个静态页面。

服务接口拒绝服务:无用户特定信息的页面能访问,提示服务器繁忙。页面内容也可在缓存(Varnish)或CDN内获取。

延迟持久化:页面访问照常,但是涉及记录变更,会提示稍晚能看到结果,将数据记录到异步队列或log,服务恢复后执行。

随机拒绝服务:服务接口随机拒绝服务,让用户重试,目前较少采用。因为用户体验不佳。

服务降级埋点

消息中间件

所有API调用请求可以使用消息中间件进行控制,这可以有效地处理请求流量的增长,避免服务压力过大,导致服务雪崩效应。缺点是,增加了服务处理和业务处理的复杂性。

前端页面

指定网址不可访问(NGINX等load balance来处理),处理简单。缺点是用户体验非常不好。

底层数据层

拒绝所有增删改动作,只允许查询。此时用户可以查看所有的查询处理页面和业务,但无法进行有更新修改的操作。

管理方式

直觉管理方式:运维人员可以指定哪些模块降级。

当服务器检测到压力增大,服务器监测自动发送通知给运维人员,运维人员根据自己或相关人员判断后通过配置平台设置当前运行等级来降级。降级首先可以对非核心业务进行接口降级。如果效果不显著,开始对一些页面进行降级,以此保证核心功能的正常运行。

分级管理方式:运维人员无需关心业务细节,直接按级别降低即可。

业务确定好对应业务的优先级别,指定好分级降级方案。当服务器检测到压力增大,服务检测自动发送通知给运维人员。运维人员根据情况选择运行等级.

而各个应用还可以根据自己的级别自动判断是否工作,如何拒绝

在dubbo平台上实现服务降级

最后,在dubbo中想实现服务降级,需要怎么样做可以实现?

dubbo中的处理

dubbo开发中,可能由于服务没有启动或者网络不通,调用中会出现RpcException,也就是远程调用失败。如果是服务启动顺序的问题,可能加工check=”false”的配置可以得到很好的解决。但是,如果是服务宕掉或者并发数太高导致的RpcException该如何处理?

查看dubbo的官方文档,可以发现有个mock的配置,mock只在出现非业务异常(比如超时,网络异常等)时执行。mock的配置支持两种,一种为boolean值,默认的为false。如果配置为true,则缺省使用mock类名,即类名+Mock后缀;另外一种则是配置”return null”,可以很简单的忽略掉异常。

一个示例

服务接口

/**接口定义*/
public interface IUser {

public void addUser(User u);

public User getUserById(int id);

}

/**实现类*/
public class UserImpl implements IUser {

private static List<User> USER_LIST = new ArrayList<User>();

static{
for(int i=0;i<10;i++){
User u = new User();
u.setAddress("address"+i);
u.setId(i);
u.setName("name"+i);

USER_LIST.add(u);
}
}

public void addUser(User u) {
USER_LIST.add(u);
System.out.println("total:"+USER_LIST.size());
}

public User getUserById(int id) {
for(int i=0;i<USER_LIST.size();i++){
if(USER_LIST.get(i).getId() == id){
return USER_LIST.get(i);
}
}
return null;
}
}


服务提供方的配置:

dubbo-provider.xml

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"

xsi:schemaLocation="http://www.springframework.org/schema/beans        http://www.springframework.org/schema/beans/spring-beans.xsd        http://code.alibabatech.com/schema/dubbo        http://code.alibabatech.com/schema/dubbo/dubbo.xsd"> 
<!-- 提供方应用信息,用于计算依赖关系 -->

<dubbo:application name="hello-world-app" />

<!-- 使用multicast广播注册中心暴露服务地址 -->

<dubbo:registry address="zookeeper://127.0.0.1:2181" />

<!-- 用dubbo协议在20880端口暴露服务 -->

<dubbo:protocol name="dubbo" port="20880" />

<!-- 声明需要暴露的服务接口 -->

<dubbo:service interface="com.dubbosample.iface.IUser" ref="userImpl" timeout="10000" />

<!-- 和本地bean一样实现服务 -->

<bean id="userImpl" class="com.dubbosample.ifaceimpl.UserImpl" />

</beans>


服务调用方的配置:

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"

xsi:schemaLocation="http://www.springframework.org/schema/beans        http://www.springframework.org/schema/beans/spring-beans.xsd        http://code.alibabatech.com/schema/dubbo        http://code.alibabatech.com/schema/dubbo/dubbo.xsd"> 
<!-- 消费方应用名,用于计算依赖关系,不是匹配条件,不要与提供方一样 -->

<dubbo:application name="dubbo-consumer"  />

<dubbo:registry address="zookeeper://127.0.0.1:2181" />

<!-- 生成远程服务代理,可以和本地bean一样使用demoService -->

<dubbo:reference id="iUser" interface="com.dubbosample.iface.IUser"  timeout="10000" check="false" mock="return null">

</dubbo:reference>

</beans>


服务调用的测试代码:

public static void main(String[] args) throws Exception{

ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(new String[] {"classpath:dubbo-consumer.xml"});

context.start();

IUser iUser = (IUser)context.getBean("iUser");

User u = new User();

u.setAddress("aaa");

u.setId(311);

u.setName("n3");

iUser.addUser(u);

System.out.println(iUser.getUserById(1));

}


测试时,如果服务启动,则程序按照预期的运行正常;如果服务没启动,则此时运行程序,程序并未报错,输出数据为null。

说明

通过以上的例子可以知道,通过mock的配置,可以很好的实现dubbo服务降级。但是,仔细查看上面的例子会发现,IUser本身定义了两个接口,一个是新增用户,一个是根据id查询用户信息。对于根据id查询用户信息,在调用失败的时候返回null很好理解,可能是由于验证失败或者记录删除了,但是对于新增用户,可能就需要抛出具体的业务信息,否则程序无法处理后续的业务,包括页面弹出”添加成功“或者列表刷新的时候无法查看到最新的记录,这样体验将会非常不好。所以,如果要有较好的区分,可以通过以下的方式,可以更好的实现降级:

(1)将接口进行归类,分成查询操作类、变更操作类:对于查询的操作分为一个接口类,变更的归类为其他的接口类,这样对于查询的可以使用mock=”return null”进行降级操作;对于变更类的操作接口,可以仍旧使用try……catch进行异常捕获处理;

(2)配置mock=”true”,同时mock实现接口,接口名要注意命名规范:接口名+Mock后缀。此时如果调用失败会调用Mock实现。mock实现需要保证有无参的构造方法。

配置mock=”true”的情况,对于上面的例子即在IUser的同个路径下,添加类IUserMock,实现如下:

public class IUserMock implements IUser {

@Override

public void addUser(User u) {

throw new RuntimeException("add user fail!");

}

@Override

public User getUserById(int id) {

return null;

}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: