前言
在dubbo多服务开发的时候经常有这种问题,比如有用户服务,订单服务,商品服务,消息服务,日志服务等等吧,反正服务很多的情况下,在新增一个简单的功能,在代码调试时需要启动所有相关的服务。
比如调试任服务都需要用户服务进行登录,日志服务进行记录日志,消息服务进行短信发送,但是这些服务只是使用就好,并不进行代码的修改。然而这种情况,也在本地启动服务,会造成内存的浪费,笔者开发的时候随随便便一个小功能都需要启动4个服务,16G的内存瞬间就没了,很是难受。
一、解决思路
百度了好多资料,有的说进行服务的分组,或者进行直接提供者,也试过,但是比如这次开发用到了a,b,c服务,明天开发另外一个功能需要x,y,z服务,每次都要配置好麻烦。而且一旦手残发到线上,那就是要被骂死了。
我的实现思路是,每次进行远程调用时,都加一个调用标识——起始ip信息
,这个起始的ip在每次远程调用时都进行传递,然后修改负载均衡规则,优先选择为起始ip的服务。
如上,所有的开发者,全部注册到一个注册中心,比如前端vue进行调用时,api做为入口(api对各个服务进行调用)
- 和前端同学进行联调,前端同学链接开发B的机器进行接口调用。
- api寻找用户服务,进行登录(携带了开发B的ip)
- api寻找订单服务,查询订单(携带开发B的ip),这里有可能会调用到开发A的订单服务,毕竟是同一个注册中心。
- 订单服务去商品服务,查询订单下的商品,订单服务发现自己不是起始调用者,优先选择起始ip的服务,所以选择调用开发B的商品服务。
实现这个步骤很简单,只需要两步
- 新增一个调用的拦截添加起始ip信息
- 自定义路由优先选择本地地址。
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文件,生成使用另一个。如果大家在开发的时候使用更好的开发方式,希望大家可以多多指点。