我们都知道HTTP协议本身是无状态的,前后两次请求没有直接关联。
但有些业务功能比较特殊,比如发起一次http请求创建一笔订单,前提要求用户先登录,为了解决这个问题,http协议header中引入了Cookie,存储上下文信息,传递登录状态。
同理,服务器也有状态之分,取决于服务器是否有存储数据,还是纯计算节点
场景:
现在有这么一个业务场景,用户发出请求,指令随机打到了一台服务器,比如174.56.102.101
,但根据索引条件,数据实际存储在 174.56.102.102
或者 174.56.102.103
,此时174.56.102.101
需要将请求转发给真实的目标服务器,以便获取数据。
当然也有一定概率,174.56.102.101
就是真实的数据存储服务器,此时只需要调用本地方法,直接获取数据即可。
思考:
那么问题来了,一个系统会提供很多功能函数,每个函数在执行时,都要先判断数据的真实存储位置,然后再发起远程网络请求,获取数据。这样编写存在大量的代码冗余。
有没有一种方式,只管调用对应的funcion函数
,至于底层真实数据在哪里,由框架层来处理。
我们想到了RPC框架,比如 Dubbo
,对于开发者而言,调用一个远程服务跟调用本地方法一样,简单方便。
如何来设计这个框架层
从大的角色划分来看,分为服务提供方和消费方,首先我们来看看消费方如何设计?
消费方
定义注解类 @RPCReference
,作为Field
字段的属性说明,如果有此标识说明注入的是一个代理类。
@Service
@Slf4j
public class ComputeService {
@RPCReference
private IResourceService iResourceService;
省略。。。。
public Object method(param){
// 正常方法调用
iResourceService.m1();
}
特别说明:
1、IResourceService 需要定义为接口类型
2、根据Spring的IOC注入机制,
iResourceService
指向的是一个代理类实例地址
那么这个代理类如何创建?
首先,定义一个增强类 ConsumerProxyFactory
,实现InvocationHandler
接口
@Slf4j
public class ConsumerProxyFactory implements InvocationHandler {
/**
* 复写InvocationHandler类提供的方法,业务类方法调用会触发执行invoke增强逻辑
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Class<?> clazz = proxy.getClass().getInterfaces()[0];
if (method.getName().contains("toString")) {
return Boolean.TRUE;
}
// 根据args参数做判断
if (当前节点) {
// 调用本地方法
return invodeMethod(clazz, method, args);
}
// 否则走rpc远程调用
// 构造请求体
RPCRequest req = buildRpcReq(clazz, method, args);
// 构造请求头
Map<String, String> headerMap = buildHeaderMap( requestString);
// 访问远程服务器的接口,查询结果
String responseString = HttpClientUtil.postRequest(url, req, headerMap);
// 本地ThreadLocal资源清理、释放
// 反序列化,解析出Return对象
return JSONObject.parseObject(responseString, method.getGenericReturnType());
}
/**
* 执行当前节点的本地方法
*/
private Object invodeMethod(Class<?> clazz, Method method, Object[] args) {
try {
// 根据Class模板查询Bean实例
Object bean =ProviderConte