前言

    在dubbo多服务开发的时候经常有这种问题,比如有用户服务,订单服务,商品服务,消息服务,日志服务等等吧,反正服务很多的情况下,在新增一个简单的功能,在代码调试时需要启动所有相关的服务。
    比如调试任服务都需要用户服务进行登录,日志服务进行记录日志,消息服务进行短信发送,但是这些服务只是使用就好,并不进行代码的修改。然而这种情况,也在本地启动服务,会造成内存的浪费,笔者开发的时候随随便便一个小功能都需要启动4个服务,16G的内存瞬间就没了,很是难受。


一、解决思路

    百度了好多资料,有的说进行服务的分组,或者进行直接提供者,也试过,但是比如这次开发用到了a,b,c服务,明天开发另外一个功能需要x,y,z服务,每次都要配置好麻烦。而且一旦手残发到线上,那就是要被骂死了。
    我的实现思路是,每次进行远程调用时,都加一个调用标识——起始ip信息,这个起始的ip在每次远程调用时都进行传递,然后修改负载均衡规则,优先选择为起始ip的服务。
【dubbo系列】dubbo多服务本地开发调试_zookeeper

如上,所有的开发者,全部注册到一个注册中心,比如前端vue进行调用时,api做为入口(api对各个服务进行调用)

  1. 和前端同学进行联调,前端同学链接开发B的机器进行接口调用。
  2. api寻找用户服务,进行登录(携带了开发B的ip)
  3. api寻找订单服务,查询订单(携带开发B的ip),这里有可能会调用到开发A的订单服务,毕竟是同一个注册中心。
  4. 订单服务去商品服务,查询订单下的商品,订单服务发现自己不是起始调用者,优先选择起始ip的服务,所以选择调用开发B的商品服务。

实现这个步骤很简单,只需要两步

  1. 新增一个调用的拦截添加起始ip信息
  2. 自定义路由优先选择本地地址。
二、代码

1.自定义拦截器

代码如下(示例):

public class DevFilter implements Filter {
    private static final Log logger = 
    LogFactory.getLog(DevLoadBalance.class);
    /**
     * 每次进行远程条用都经过此方法,加入开始ip的标识
     */
    public Result invoke(Invoker<?> invoker, Invocation invocation) 
    throws RpcException {
        logger.info("dev filter start");
        //判断是否存在开始ip
        Object fromIp = RpcContext.getContext().getAttachment(DevDubboConstants.START_IP);
        if(fromIp==null){
            try {
                //第一次调用时,设置开始ip
                fromIp = InetAddress.getLocalHost().getHostAddress();
            } catch (UnknownHostException e) {
                e.printStackTrace();
            }
        }
        logger.info("dev filter set startip is "+fromIp);
        //设置ip信息,这种设置信息,可以在服务提供者进行获取。
        //这种方式是dubbo的隐式传参  有兴趣的参考官方
        // http://dubbo.apache.org/zh-cn/docs/user/demos/attachment.html
 		//当然还有url中进行拼接上下文信息,有兴趣的可以任意选择。
        RpcContext.getContext().setAttachment(DevDubboConstants.START_IP,fromIp.toString());
        return invoker.invoke(invocation);
    }
}

dubbo拦截器代码处理好之后需要在resources\META-INF\dubbo目录下新增一个com.alibaba.dubbo.rpc.Filter文件,内容如下
dev=com.dubbo.router.filter.DevFilter
详细参考官方拦截器配置说明
注意:spi文件名要和Filter的包路径一样,官方写的是org.apache.dubbo.rpc.Filter,实际情况要根据自己的版本进行自行调整。

2.自定义负载规则

代码如下(示例):

 private static final Log logger = 
 LogFactory.getLog(DevLoadBalance.class);
    public static final String NAME = "dev";

    public <T> Invoker<T> select(List<Invoker<T>> list, URL url, Invocation invocation) throws RpcException {
        logger.info("loadBalance select start");
        //获取起始ip信息
        Object fromIp = RpcContext.getContext().getAttachment(DevDubboConstants.START_IP);
        if(fromIp==null){
            try {
                //如果没有获取到起始ip,则优先选择自己的ip
                fromIp = InetAddress.getLocalHost().getHostAddress();
            } catch (UnknownHostException e) {
                e.printStackTrace();
            }
        }
        logger.info("loadBalance select ip is:"+fromIp);
        //选择起始ip的服务,或者自己的服务进行调用
        for(Invoker<T> invoker:list){
            if(invoker.getUrl().getIp().equals(fromIp.toString())){
                return invoker;
            }
        }
        logger.info("select super loadBalance");
        //如果起始ip没有启动服务,自己也没有启动,则使用默认规则
        return super.select(list,url,invocation);
    }

resources\META-INF\dubbo路径下新建com.alibaba.dubbo.rpc.cluster.LoadBalance内容如下
dev=com.dubbo.router.loadbalance.DevLoadBalance

官方负载均衡配置说明,注意LoadBalance的包路径,根据自己版本自行修改哈。

3.环境配置

     实现逻辑大概就是这样,有自己的想法可以自己优化哈,下载工程,通过maven进行打包,引入。
yml配置示例:loadbalance选择dev,filter选择dev

dubbo:
  application:
    name: shop-api #服务明后才能
  registry:
    address: zookeeper://192.168.16.128:2181 #zookeeper服务地址
  provider:
    loadbalance: dev
  consumer:
    filter: dev
server:
  port: 8281  #项目端口

java API配置示例:

 public static void main(String[] args) {
        // 当前应用配置
        ApplicationConfig application = new ApplicationConfig();
        application.setName("yyy");
        // 连接注册中心配置
        RegistryConfig registry = new RegistryConfig();
        registry.setAddress("zookeeper://127.0.0.1:2181");
        ReferenceConfig<IUserService> reference = new ReferenceConfig<IUserService>(); 
        reference.setApplication(application);
        reference.setRegistry(registry); // 多个注册中心可以用setRegistries()
        reference.setInterface(IUserService.class);
        reference.setVersion("1.0.0");
        reference.setLoadbalance("dev");
        reference.setFilter("dev");
        // 和本地bean一样使用xxxService
        IUserService iUserService = reference.get();
        iUserService.getUserInfo("111");
    }

总结

     笔者使用的yml配置,两个yml文件,测试环境使用dev文件,生成使用另一个。如果大家在开发的时候使用更好的开发方式,希望大家可以多多指点。