系统生态化后,最头疼的事请就是强依赖上游系统提供的服务。当服务链路太长,开发过程要求部署整套服务,这个需要的资源是巨大的。最近着手解决这个问题,今天记录下来。
在解决这个问题的过程中,得到三种解决方案。如下:
- dubbo提供的服务降级(本地伪装)
- dubbo客户端本地存根
- 手动替换
我选择的是第三种,至于为什么选择第三种,是因为前两种都需要修改原有的代码来实现,以及我还需要一个开关来控制是否需要调用mock服务。所以最后选择自己实现。
dubbo提供的服务降级
dubbo本身是提供降级服务的,不过比较简单,在官方解释中,也叫做本地伪装。
服务降级,设计是,在当服务提供方宕机或者超时后,客户端不抛出异常,而是通过mock数据返回所设定的值。
在spring配置文件中,按照以下方式配置:
1
| <dubbo:reference interface="com.ofcoder.IWelcome" mock="com.ofcoder.WelcomeMock" />
|
或者:
1
| @Reference(mock="com.ofcoder.WelcomeMock")
|
然后你需要提供所指定的Mock类,该类要求继承所需要降级的接口。
1 2 3 4 5
| public class WelcomeMock implements IWelcome { public String sayHello(String name) { return "hey guy."; } }
|
那么当上游服务调用失败后,则会自动触发降级服务。mock的值还支持return、throw、force等关键字。
return
使用 return 来返回一个字符串表示的对象,作为 Mock 的返回值。合法的字符串可以是:
- empty: 代表空,基本类型的默认值,或者集合类的空值
- null: null
- true: true
- false: false
- JSON 格式: 反序列化 JSON 所得到的对象
throw
使用 throw 来返回一个 Exception 对象,作为 Mock 的返回值。
抛出默认的RPCException
1
| <dubbo:reference interface="com.ofcoder.IWelcome" mock="throw" />
|
抛出指定的Exception:
1
| <dubbo:reference interface="com.ofcoder.IWelcome" mock="throw com.ofcoder.MockException" />
|
force
force用来强制执行降级服务,这种情况不会调用远程服务。
强制抛异常:
1
| <dubbo:reference interface="com.ofcoder.IWelcome" mock="force:throw com.ofcoder.MockException" />
|
dubbo客户端存根
降级是在服务调用之后,根据调用结果判断是否需要返回mock数据,而存根(Stub)是在调用之前增强服务。可以理解成AOP增强。
实现原理是由,dubbo把客户端某个接口生成Proxy实例,通过Stub的构造方法传给Stub对象,这个Stub对象由用户实现,用户可根据具体场景判断是否需要继续调用上游服务。可用来ThreadLocal缓存,提前验证参数,调用失败后伪造容错数据等等。
如果将服务降级加进来,调用过程可以归纳为:xxxStub -> xxxProxy -> xxxMock
使用方式跟降级很相似:
1
| <dubbo:service interface="com.ofcoder.IWelcome" stub="com.ofcoder.WelcomeStub" />
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| public class WelcomeStub implements IWelcome { private final IWelcome welcome; public WelcomeStub(IWelcome welcome){ this.welcome = welcome; } public String sayHello(String name) { try { return welcome.sayHello(name); } catch (Exception e) { return "hey guys"; } } }
|
手动替换
实现过程是,在spring的IOC初始化之后,扫描@Reference注解,将用该注解标注的接口,使用mocktio生成mock对象,然后替换掉spring中bean,并反射将mock值塞入指定对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| @Component public class DubboMock implements CommandLineRunner { private static final Logger logger = LoggerFactory.getLogger(DubboMock.class); private static final String SCANNER_PACKAGE = "com.ofcoder.remote"; @Value("${ofcoder.mock.dubbo.enable}") private boolean mockEnable;
@Autowired private ApplicationContext applicationContext;
private void registerMock() { DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory) applicationContext.getAutowireCapableBeanFactory();
IWelcome welcome = Mockito.mock(IWelcome.class); Mockito.doReturn(new Result(ResultEnum.SUCCESS)).when(welcome).sayHello(Mockito.any()); defaultListableBeanFactory.registerSingleton(IWelcome.class.getCanonicalName(), welcome); }
private void doMock() { Reflections reflections = new Reflections(new ConfigurationBuilder().setUrls(ClasspathHelper .forPackage(SCANNER_PACKAGE)).setScanners(new FieldAnnotationsScanner()));
Set<Field> fields = reflections.getFieldsAnnotatedWith(Reference.class); for (Field field : fields) { Class<?> declaringClass = field.getDeclaringClass(); Class<?> fieldClass = field.getType(); logger.info("======declaringClass:{}, fieldClass:{}=====", declaringClass, fieldClass);
try { Object target = applicationContext.getBean(declaringClass); Object rpcObj = applicationContext.getBean(fieldClass); field.setAccessible(true); field.set(target, rpcObj); logger.info("======mock {}.{} success. this is a thing worth cheering.=====", declaringClass.getName(), field.getName()); } catch (Exception e) { logger.warn(String.format("======mock %s.%s occur exception. e.getMessage: %s", declaringClass.getSimpleName(), field.getName(), e.getMessage())); } } }
@Override public void run(String... args) throws Exception {
if (!mockEnable) { return; }
registerMock();
doMock(); } }
|
这样做有一个缺点,在启动的时候需要上游系统提供了服务,不然dubbo会报no provider的错误,这里建议启动时不检查服务
1
| @Reference(check = false)
|