dubbo服务接口设计的几个建议
尽量不用独立的多个参数
比如我们有个dubbo的服务接口是这样定义的,
public interface UserService {
String sayHello1(String name);
}
服务实现示例如下:
public class UserServiceImpl implements UserService {
public String sayHello1(String s) {
return "hello1 " + s + "!";
}
然后我们中间服务进行升级,需要增加一个入参,把接口改成了如下的方式:
public interface UserService {
String sayHello1(String name, int age);
}
服务实现改成了:
public class UserServiceImpl implements UserService {
public String sayHello1(String s, int age) {
return "hello1 " + s + age + "!";
}
服务上线后,调用方肯定会报错,因为整个接口声明都变了,相当于两个完全不同的接口。
那如何解决呢?其实很简单。服务接口的参数类型最好是封装类,增加参数的话只是在这个类增加一个字段。示例如下:
public interface UserService {
String sayHello1(String name);
String sayHello2(SayWorldRequest request);
封装类的定义:
public class SayWorldRequest implements Serializable {
private static final long serialVersionUID = 1L;
String name;
int age;
...
接口最好带有版本信息
当一个接口实现,出现不兼容升级时很有用。可以用版本号过渡,版本号不同的服务相互间不引用。
还是通过示例来说明:
假设服务端的要对接口sayHello1重写,和原来的方法不兼容,我们重写写个实现类,然后实现sayHello1方法,
public class UserServiceImpl2 implements UserService {
public String sayHello1(String s) {
return "hello1 new" + s + "!";
}
然后暴露服务的时候指定不同的版本,
<bean id="userService" class="com.dubbo.provider.UserServiceImpl"/>
<bean id="userService2" class="com.dubbo.provider.UserServiceImpl2"/>
<dubbo:service interface="com.dubbo.api.UserService" ref="userService" version="1.0"/>
<dubbo:service interface="com.dubbo.api.UserService" ref="userService2" version="1.1"/>
客户端调用的时候,可以通过指定版本号调用不同的服务,
<!-- 引用服务配置 -->
<dubbo:reference id="userServiceClient" interface="com.dubbo.api.UserService" cluster="failfast" check="false" version="1.1"/>
聪明如你可能想到了,利用版本号的机制可以实现灰度发布,举个例子:
<!-- 机器A提供1.0.0版本服务 -->
<dubbo:service interface="com.dubbo.api.UserService" version="1.0.0" />
<!-- 机器B提供2.0.0版本服务 -->
<dubbo:service interface="com.dubbo.api.UserService" version="2.0.0" />
<!-- 机器C消费1.0.0版本服务 -->
<dubbo:reference id="userService" interface="com.dubbo.api.UserService" version="1.0.0" />
<!-- 机器D消费2.0.0版本服务 -->
<dubbo:reference id="userService" interface="com.dubbo.api.UserService" version="2.0.0" />
尽量少用枚举类型作为参数
《阿里巴巴java开发手册》有这么一条规定:
【强制】二方库里可以定义枚举类型,参数可以使用枚举类型,但是接口返回值不允许使用枚 举类型或者包含枚举类型的 POJO 对象。
为什么有这样的规定呢?
先给你看个例子:
接口定义,
public interface UserService {
SayWorldResponse sayHello3(SayWorldRequest request);
}
响应类定义,
public class SayWorldResponse implements Serializable {
private static final long serialVersionUID = 1L;
String name;
int age;
EnumColor enumColor;
//省略其它部分
枚举定义,
public enum EnumColor {
RED("red", 1),
GREEN("green", 2),
BLACK("black", 3),
YELLOW("yellow", 4);
//省略其它部分
服务实现,
public class UserServiceImpl implements UserService {
//省略其它部分
public SayWorldResponse sayHello3(SayWorldRequest request) {
SayWorldResponse response = new SayWorldResponse();
response.setAge(100);
response.setName("response");
response.setEnumColor(EnumColor.YELLOW);
return response;
}
}
然后系统上线,客户端正常调用,一切风平浪静。
但是软件总会升级,过了一段时间,我们需要在枚举里新增一个类型,并且服务端在某些场景下会使用这个字段返回给客户端。代码如下:
public enum EnumColor {
RED("red", 1),
GREEN("green", 2),
BLACK("black", 3),
YELLOW("yellow", 4),
BLACK("black", 5);
//省略其它部分
服务实现,
public class UserServiceImpl implements UserService {
//省略其它部分
public SayWorldResponse sayHello3(SayWorldRequest request) {
SayWorldResponse response = new SayWorldResponse();
response.setAge(100);
response.setName("response");
if (条件成立) {
response.setEnumColor(EnumColor.BLACK);
} else {
response.setEnumColor(EnumColor.YELLOW);
}
return response;
}
}
这个时候就相当于埋下了一颗雷,服务端上线后,当客户端调用sayHello3方法触发条件返回 EnunColor.BLACK时,客户端就会报如下的错误:
org.springframework.web.util.NestedServletException: Request processing failed; nested exception is com.alibaba.dubbo.rpc.RpcException: Failfast invoke providers dubbo://10.204.246.56:28511/com.dubbo.api.UserService2?anyhost=true&application=dubbo-consumer&check=false&cluster=failfast&default.check=false&default.cluster=failfast&dubbo=2.5.3&interface=com.dubbo.api.UserService&methods=sayHello1,sayHello2,sayHello3&pid=18056&revision=1.1-SNAPSHOT&side=consumer×tamp=1576573801502&version=1.1 RandomLoadBalance select from all providers [com.alibaba.dubbo.registry.integration.RegistryDirectory$InvokerDelegete@23c21e07] for service com.dubbo.api.UserService method sayHello3 on consumer 10.204.246.56 use dubbo version 2.5.3, but no luck to perform the invocation. Last error is: Failed to invoke remote method: sayHello3, provider: dubbo://10.204.246.56:28511/com.dubbo.api.UserService2?anyhost=true&application=dubbo-consumer&check=false&cluster=failfast&default.check=false&default.cluster=failfast&dubbo=2.5.3&interface=com.dubbo.api.UserService&methods=sayHello1,sayHello2,sayHello3&pid=18056&revision=1.1-SNAPSHOT&side=consumer×tamp=1576573801502&version=1.1, cause: com.alibaba.com.caucho.hessian.io.HessianFieldException: com.dubbo.api.SayWorldResponse.enumColor: java.lang.reflect.InvocationTargetException
com.alibaba.com.caucho.hessian.io.HessianFieldException: com.dubbo.api.SayWorldResponse.enumColor: java.lang.reflect.InvocationTargetException
at com.alibaba.com.caucho.hessian.io.JavaDeserializer.logDeserializeError(JavaDeserializer.java:671)
at com.alibaba.com.caucho.hessian.io.JavaDeserializer$ObjectFieldDeserializer.deserialize(JavaDeserializer.java:400)
at com.alibaba.com.caucho.hessian.io.JavaDeserializer.readObject(JavaDeserializer.java:233)
at com.alibaba.com.caucho.hessian.io.JavaDeserializer.readObject(JavaDeserializer.java:157)
at com.alibaba.com.caucho.hessian.io.Hessian2Input.readObjectInstance(Hessian2Input.java:2067)
at com.alibaba.com.caucho.hessian.io.Hessian2Input.readObject(Hessian2Input.java:1592)
at com.alibaba.com.caucho.hessian.io.Hessian2Input.readObject(Hessian2Input.java:1576)
...
很明显,这是一个RPC调用过程中反序列的错误。为什么会有这个错误呢?解释下:
在Java中,对Enum类型的序列化与其他对象类型的序列化有所不同的,官方的说明是这样的:
Enum constants are serialized differently than ordinary serializable or externalizable objects. The serialized form of an enum constant consists solely of its name; field values of the constant are not present in the form. To serialize an enum constant, ObjectOutputStream writes the value returned by the enum constant's name method. To deserialize an enum constant, ObjectInputStream reads the constant name from the stream; the deserialized constant is then obtained by calling the java.lang.Enum.valueOf method, passing the constant's enum type along with the received constant name as arguments. Like other serializable or externalizable objects, enum constants can function as the targets of back references appearing subsequently in the serialization stream. The process by which enum constants are serialized cannot be customized: any class-specific writeObject, readObject, readObjectNoData, writeReplace, and readResolve methods defined by enum types are ignored during serialization and deserialization. Similarly, any serialPersistentFields or serialVersionUID field declarations are also ignored--all enum types have a fixedserialVersionUID of 0L. Documenting serializable fields and data for enum types is unnecessary, since there is no variation in the type of data sent.
大概意思就是说,在序列化的时候Java仅仅是将枚举对象的name属性输出到结果中,反序列化的时候则是通过java.lang.Enum的valueOf方法来根据名字查找枚举对象。同时,编译器是不允许任何对这种序列化机制的定制的,因此禁用了writeObject、readObject、readObjectNoData、writeReplace和readResolve等方法。
所以,在远程方法调用过程中,如果我们发布的客户端接口返回值中使用了枚举类型,那么服务端在升级过程中比如在接口的返回结果的枚举类型中添加了新的枚举值,那就会导致仍然在使用老的客户端的那些应用出现调用失败的情况。
那么有没有解决办法呢?当然,方法就是客户端和服务端都升级,都引用最新的API声明。不过往往实际的项目中很难保证这一点(不同的团队维护),所以我们还是要尽量避免RPC接口的返回值里面包含枚举定义。
总结
- 接口定义尽量使用封装类作为入参,避免日后需要新增参数带来不便
- 暴露服务尽量使用版本约束,方便以后升级
- 接口的返回值尽量不适用枚举,否则容易引起反序列化的问题
- LVS负载均衡下session共享的实现方式-持久化连接
- Centos6.9下RabbitMQ集群部署记录
- “AS3.0高级动画编程”学习:第三章等角投影(下)
- zabbix监控主机cpu达到80%后报警
- ssh登陆服务器locale告警的处理方法
- as3.0中如何阻止事件冒泡?
- Nginx+upstream针对后端服务器容错的运维笔记
- MongoDB日常运维操作命令小结
- flash builder代码格式化以及其它快捷键
- Repeater,DataList,DataGrid
- flash/flex builder在IE中stage.stageWidth始终为0的解决办法
- MongoDB集群运维笔记
- 水晶报表WEB方式下不打印的问题
- 针对负载均衡集群中的session解决方案的总结
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 算法工程师-SQL进阶:神奇的自连接与子查询
- 算法工程师-SQL进阶:温柔的陷阱-NULL
- SpringBoot 异步任务处理
- SpringBoot整合 ActiveMQ快速入门 实现点对点推送
- Tomcat部署SpringBoot war包
- Vue之插槽Slot理解
- Docker六脉神剑 (三) 编写Dockerfile构建nginx镜像并推送到远程仓库给其他人使用
- 快速学习UML类图查看
- 设计模式 | 抽象工厂模式
- 设计模式 | 单例模式
- macOS 安装软件已损坏无法打开解决办法 (真好用!)
- nginx 配置反向代理
- ES6新特性速查表
- React-Native Android打包
- React-Native iOS打包