- Dubbo示例
package com.alibaba.dubbo.demo.consumer;
import com.alibaba.dubbo.demo.DemoService;
import com.alibaba.dubbo.demo.DemoService2;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Consumer {
public static void main(String[] args) {
//Prevent to get IPV6 address,this way only work in debug mode
//But you can pass use -Djava.net.preferIPv4Stack=true,then it work well whether in debug mode or not
System.setProperty("java.net.preferIPv4Stack", "true");
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(new String[]{"META-INF/spring/dubbo-demo-consumer.xml"});
context.start();
DemoService demoService = (DemoService) context.getBean("demoService"); // get remote service proxy
DemoService2 demoService2 = (DemoService2) context.getBean("demoService2");
while (true) {
try {
Thread.sleep(1000);
String hello = demoService.sayHello("world"); // call remote method
System.out.println(hello); // get result
String world2 = demoService2.sayHello2("world2");
System.out.println(world2); // get result
} catch (Throwable throwable) {
throwable.printStackTrace();
}
}
}
}
这是Dubbo消费者的入口,我们主要关注:
String hello = demoService.sayHello("world"); // call remote method
针对这段代码进行Debug,查看调用栈,会发现如下逻辑:
main
-> sayHello
-> com.alibaba.dubbo.rpc.proxy.InvokerInvocationHandler#invoke
-> com.alibaba.dubbo.rpc.cluster.support.wrapper.MockClusterInvoker#invoke
-> com.alibaba.dubbo.rpc.cluster.support.AbstractClusterInvoker#invoke
-> com.alibaba.dubbo.rpc.cluster.support.AbstractClusterInvoker#list
-> com.alibaba.dubbo.rpc.cluster.directory.AbstractDirectory#list
下面主要来分析 com.alibaba.dubbo.rpc.cluster.directory.AbstractDirectory#list的逻辑:
public List<Invoker<T>> list(Invocation invocation) throws RpcException {
if (destroyed) {
throw new RpcException("Directory already destroyed .url: " + getUrl());
}
List<Invoker<T>> invokers = doList(invocation);
List<Router> localRouters = this.routers; // local reference
if (localRouters != null && localRouters.size() > 0) {
for (Router router : localRouters) {
try {
if (router.getUrl() == null || router.getUrl().getParameter(Constants.RUNTIME_KEY, false)) {
invokers = router.route(invokers, getConsumerUrl(), invocation);
}
} catch (Throwable t) {
logger.error("Failed to execute router: " + getUrl() + ", cause: " + t.getMessage(), t);
}
}
}
return invokers;
}
上面的代码中你会发现里面的localRouters,那这个localRouters的值是怎么来的呢?
请查看com.alibaba.dubbo.rpc.cluster.directory.AbstractDirectory#setRouters
protected void setRouters(List<Router> routers) {
// copy list
routers = routers == null ? new ArrayList<Router>() : new ArrayList<Router>(routers);
// append url router
String routerkey = url.getParameter(Constants.ROUTER_KEY);
if (routerkey != null && routerkey.length() > 0) {
RouterFactory routerFactory = ExtensionLoader.getExtensionLoader(RouterFactory.class).getExtension(routerkey);
routers.add(routerFactory.getRouter(url));
}
// append mock invoker selector
routers.add(new MockInvokersSelector());
// 添加AppRouter
routers.add(new AppRouter());
Collections.sort(routers);
this.routers = routers;
}
从上面的代码可看到主要逻辑在
if (routerkey != null && routerkey.length() > 0) {
RouterFactory routerFactory = ExtensionLoader.getExtensionLoader(RouterFactory.class).getExtension(routerkey);
routers.add(routerFactory.getRouter(url));
}
这是根据SPI的方式进行注入router的,所在我们要是想自定义Router,那入口就在这里。我们可以采用SPI的方式,也可以采用硬编码的方式,上面的
// append mock invoker selector
routers.add(new MockInvokersSelector());
// 添加AppRouter
routers.add(new AppRouter());
Collections.sort(routers);
this.routers = routers;
这段代码就是硬编码的方式,其中AppRouter我是硬编码进去的。
可以再AppRouter中写我们想要扩展的逻辑。先看一下我自定义的AppRouter的代码(目前只是最简易的版本)
public class AppRouter implements Router {
public <T> List<Invoker<T>> route(final List<Invoker<T>> invokers, URL url, final Invocation invocation) throws RpcException {
List<Invoker<T>> result=new ArrayList<Invoker<T>>();
for (Invoker invoker : invokers) {
String providerIp=getIp(invoker.getUrl());
if("101.200.164.246".equals(providerIp)){
result.add(invoker);
}
}
System.out.println("url=" + url);
return result;
}
private String getIp(URL url) {
String sURL=url.toString();
int begin = sURL.indexOf("://");
int end = sURL.lastIndexOf("/");
String substring = sURL.substring(begin+3, end);
String[] split = substring.split(":");
String ip=split[0];
return ip;
}
@Override
public URL getUrl() {
return null;
}
@Override
public int compareTo(Router o) {
return 1;
}
}
AppRouter实现了Router接口(Router接口代码很简单,可以自行查询源码),主要逻辑写在route方法中,上面的代码我们也能看到,dubbo会调用router的route方法。
我们在写router方法的时候可以从配置中心(或者是别的地方)获取我们的规则,然后按照规则进行consumer和provider的筛选,这样就可很简单的完成我们需要的路由功能。
- 我们的目标
其实我们的目标是很简单的,只是想根据ip来进行路由。但是dubbo目前(dubbo2.7之前的版本)的功能不能支持。dubbo2.7可以支持,但是现在属于dev版本,存在很多问题。我们只需要定义规则为 192.168.110.1 => 192.168.110.7 ,这样的规则意思很明确,就是要 192.168.110.1 只能访问 192.168.110.7,不能访问别的机器,这样就指定死了,我们可以很方便的实现dubbo的灰度发布。
- 实现目标
2中所说的目标,我们可以继续调研,看需要什么功能,有多少种路由规则等。我们可根据这些需求来实现AppRouter。
4.参考链接
Dubbo官网
关于Dubbo版本选取
Dubbo路由扩展
Dubbo路由源码分析