前言
Wangle中的Service代表一个远程服务(方法),熟悉RPC的朋友肯定知道这就是一个简单的RPC,当然,和一些常见的RPC框架比如:hessian、protobuf、thrift、grpc甚至是soa框架dubbo等相比而言,Wangle中的RPC实现的非常简单,但是这个简单的系统也实现了RPC中的大部分概念,这对于我们熟悉RPC原理和更好的使用Wange编写业务代码是大有裨益的。
Service
Service本质是一个要暴露的远程服务的接口,它的定义很简单,重载了“()”运算符,目的是为了使用本 地调用的方式来发起一次远程调用:
template <typename Req, typename Resp = Req>
class Service {
public:
// 开始远程调用
virtual folly::Future<Resp> operator()(Req request) = 0;
virtual ~Service() = default;
virtual folly::Future<folly::Unit> close() {
return folly::makeFuture();
}
virtual bool isAvailable() {
return true;
}
};
如果要实现一个远程服务,只需要继承Service并实现其纯虚方法,如果你曾经使用或者熟悉其他RPC框架,比如dubbo,那么一个远程服务的接口都是业务定义的(称之为Provider),同时业务方Provider会提供接口的实现版本,最终还会将接口的jar包提供给业务调用方(称之为Consumer)。Provider接口里面定义了各种不同的方法,而Wangle的Service接口只有一个重载"()"方法,参数为Req返回值为Resp,这会不会限制RPC的使用呢?其实不会,这里的设计思想有点类似dubbo中的泛化引用和泛化实现,如果想在没有模型类元的情况下暴露一个泛化服务,那么在dubbo中只需要实现这样一个接口就可以了:
public interface GenericService {
/**
* 泛化调用
*
* @param method 方法名,如:findPerson,如果有重载方法,需带上参数列表,如:findPerson(java.lang.String)
* @param parameterTypes 参数类型
* @param args 参数列表
* @return 返回值
* @throws Throwable 方法抛出的异常
*/
Object $invoke(String method, String[] parameterTypes, Object[] args) throws GenericException;
}
Service本身是一个模板类,两个模板参数分别为请求和响应类型,因此这个RPC系统就有了很大的灵活和自由度,比如可以在Req封装要调用的实际方法,在远端拿到Req之后散转调用实际的方法即可。这也是dubbo泛化实现的设计思想。
Filter
Filter在一个RPC框架中非常常见和重要,它通常用于在一次实际调用过程中完成一些单一的、业务无关的功能(类似AOP),同样还是拿dubbo作为例子,dubbo中处处可见Filter的身影,比如链路跟踪日志埋点、监控数据统计、并发限流、负载均衡、路由等功能全部都是Filter的方式实现。
Wangle中的ServiceFilter本质就是一个装饰器模式的使用,其定义如下:
template <typename ReqA, typename RespA,typename ReqB = ReqA, typename RespB = RespA>
class ServiceFilter : public Service<ReqA, RespA> {
public:
explicit ServiceFilter(std::shared_ptr<Service<ReqB, RespB>> service) : service_(service) {}
virtual ~ServiceFilter() = default;
virtual folly::Future<folly::Unit> close() override {
// 默认透传
return service_->close();
}
virtual bool isAvailable() override {
// 默认透传
return service_->isAvailable();
}
protected:
// 被装饰的服务,通过构造器注入
std::shared_ptr<Service<ReqB, RespB>> service_;
};
这里需要关注的是,ServiceFilter有四个模板参数,其中ReqA和RespA表示该Filter处理的请求和响应数据类型,而ReqB和RespB表示该Filter所装饰的Service的请求和响应数据类型,也就是Filter是可以对数据类型进行转换的。
下面提供一个Filter的实现例子,比如为Service指定执行线程,这个功能也很常见,比如可以做服务之间的线程池隔离。代码如下:
template <typename Req, typename Resp = Req>
class ExecutorFilter : public ServiceFilter<Req, Resp> {
public:
explicit ExecutorFilter(std::shared_ptr<folly::Executor> exe,std::shared_ptr<Service<Req, Resp>> service)
: ServiceFilter<Req, Resp>(service), exe_(exe) {}
folly::Future<Resp> operator()(Req req) override {
// 通过via切换线程
return via(exe_.get()).then([ req = std::move(req), this ]() mutable {
return (*this->service_)(std::move(req));
});
}
private:
// 执行线程
std::shared_ptr<folly::Executor> exe_;
};
简单的通过via便可以保证Future的回调函数在指定的线程中执行,对于folly中的Future/Promise感到迷惑的同学可以参见我的另一篇博文:https://my.oschina.net/fileoptions/blog/881798
Dispatcher
Dispatcher意思是一次请求的派发,一次RPC请求是从client开始发起的,因此,Dispatcher就分为客户端派发和服务端的派发,它们的不同之处在于:客户端的派发是会进行网络远程传输的,而服务端的派发实际上只是在调用一个本地服务。
首先看一下客户端的派发。
1、ClientDispatcher
ClientDispatcherBase是客户端派发器的基类,它本质是一个Service还是一个Handler,其定义如下:
template <typename Pipeline, typename Req, typename Resp = Req>
class ClientDispatcherBase : public HandlerAdapter<Resp, Req>, public Service<Req, Resp> {
public:
typedef typename HandlerAdapter<Resp, Req>::Context Context;
~ClientDispatcherBase() {
if (pipeline_) {
try {
// 把自己从Pipeline中删除
pipeline_->remove(this).finalize();
} catch (const std::invalid_argument& e) {
// not in pipeline; this is fine
}
}
}
void setPipeline(Pipeline* pipeline) {
try {
pipeline->template remove<ClientDispatcherBase>();
} catch (const std::invalid_argument& e) {
// no existing dispatcher; this is fine
}
pipeline_ = pipeline;
// 把自己添加到Pipeline
pipeline_->addBack(this);
pipeline_->finalize();
}
virtual folly::Future<folly::Unit> close() override {
return HandlerAdapter<Resp, Req>::close(this->getContext());
}
virtual folly::Future<folly::Unit> close(Context* ctx) override {
return HandlerAdapter<Resp, Req>::close(ctx);
}
protected:
Pipeline* pipeline_{nullptr};
};
ClientDispatcherBase有两个子类,SerialClientDispatcher和PipelinedClientDispatcher,它们的区别是:SerialClientDispatcher每一次只允许发起一次请求,而PipelinedClientDispatcher可以同时发起多次请求。
template <typename Pipeline, typename Req, typename Resp = Req>
class SerialClientDispatcher : public ClientDispatcherBase<Pipeline, Req, Resp> {
public:
typedef typename HandlerAdapter<Resp, Req>::Context Context;
// 接收response
void read(Context*, Resp in) override {
// p_必须已经初始化
DCHECK(p_);
// 填充Promise
p_->setValue(std::move(in));
// 清楚p_初始化标记,为下次请求做准备
p_ = folly::none;
}
// 发送request
virtual folly::Future<Resp> operator()(Req arg) override {
// p_必须是第一次初始化
CHECK(!p_);
DCHECK(this->pipeline_);
// 创建Promise
p_ = folly::Promise<Resp>();
// 获取结果Future
auto f = p_->getFuture();
this->pipeline_->write(std::move(arg));
return f;
}
private:
folly::Optional<folly::Promise<Resp>> p_;// 注意Optional标记p_是否初始化过
};
SerialClientDispatcher实现了Service的“()”方法,使用Pipeline把请求发送到网络上,同时返回结果Future给调用者,同样,当read到响应时,使用响应结果填充Promise即可。代码中的注释也清晰的给出了为什么SerialClientDispatcher一次只能发起一次请求的原因。
明白了SerialClientDispatcher,那么PipelinedClientDispatcher就很简单了,只要使用一个队列把Promise缓存起来,就可以同时发起多次RPC调用了,是的,就是这么实现的:
template <typename Pipeline, typename Req, typename Resp = Req>
class PipelinedClientDispatcher : public ClientDispatcherBase<Pipeline, Req, Resp> {
public:
typedef typename HandlerAdapter<Resp, Req>::Context Context;
void read(Context*, Resp in) override {
// 队列大小至少为1
DCHECK(p_.size() >= 1);
// 按顺序取出promise
auto p = std::move(p_.front());
p_.pop_front();
p.setValue(std::move(in));
}
virtual folly::Future<Resp> operator()(Req arg) override {
DCHECK(this->pipeline_);
folly::Promise<Resp> p;
auto f = p.getFuture();
// 添加到队列
p_.push_back(std::move(p));
this->pipeline_->write(std::move(arg));
return f;
}
private:
std::deque<folly::Promise<Resp>> p_;//区别
};
2、SerialServerDispatcher
服务端的派发器就要简单很多(无需发起网络传输),和客户端相对应,客户端也有SerialServerDispatcher和PipelinedServerDispatcher,不过服务端还多了一种:MultiplexServerDispatcher,MultiplexServerDispatcher可以多路派发处理请求,和PipelinedServerDispatcher不同之处在于,MultiplexServerDispatcher在响应请求时的顺序是任意的,而PipelinedServerDispatcher必须按照请求的顺序进行结果响应。
下面先来看一下SerialServerDispatcher的实现:
template <typename Req, typename Resp = Req>
class SerialServerDispatcher : public HandlerAdapter<Req, Resp> {
public:
typedef typename HandlerAdapter<Req, Resp>::Context Context;
explicit SerialServerDispatcher(Service<Req, Resp>* service) : service_(service) {}
void read(Context* ctx, Req in) override {
auto resp = (*service_)(std::move(in)).get();// 开始调用,同步
ctx->fireWrite(std::move(resp));// 写回响应
}
private:
Service<Req, Resp>* service_;
};
上面的代码注意两点:调用时本地的调用,其次是使用Future的get同步等待调用结果,因此不能发起并发调用。
下面先来看一下PipelinedServerDispatcher的实现:
template <typename Req, typename Resp = Req>
class PipelinedServerDispatcher : public HandlerAdapter<Req, Resp> {
public:
typedef typename HandlerAdapter<Req, Resp>::Context Context;
explicit PipelinedServerDispatcher(Service<Req, Resp>* service)
: service_(service) {}
void read(Context*, Req in) override {
// 更新请求Id
auto requestId = requestId_++;
// 异步调用
(*service_)(std::move(in)).then([requestId, this](Resp & resp) {
responses_[requestId] = resp;// 加入映射
sendResponses();// 发送响应
});
}
void sendResponses() {
// 上次发送的请求id加1就是本次请求id,根据id得到响应
auto search = responses_.find(lastWrittenId_ + 1);
while (search != responses_.end()) {
Resp resp = std::move(search->second);
responses_.erase(search->first);
// 响应网络传输
this->getContext()->fireWrite(std::move(resp));
// 更新
lastWrittenId_++;
// 是否还有响应没有发送
search = responses_.find(lastWrittenId_ + 1);
}
}
private:
Service<Req, Resp>* service_;
uint32_t requestId_{1};// 请求id
std::unordered_map<uint32_t, Resp> responses_;// 请求id和响应的映射
uint32_t lastWrittenId_{0};// 上一次响应的请求id
};
PipelinedServerDispatcher的实现技巧就在于,它会为每个请求附加一个请求Id,Id是自增的,这样可以异步的按照顺序发送响应了。
最后来看MultiplexServerDispatcher,MultiplexServerDispatcher在read到一个请求之后立刻发起异步调用,每一次异步调用结束之后直接发送响应,也就是响应的发送顺序和请求没有直接对应关系,那么客户端如何知道响应对应哪一个请求呢?其实这里的解决方案还是类似请求Id,只不过这里的请求Id是由业务提供,也就是隐藏在请求和响应数据结构中。
template <typename Req, typename Resp = Req>
class MultiplexServerDispatcher : public HandlerAdapter<Req, Resp> {
public:
typedef typename HandlerAdapter<Req, Resp>::Context Context;
explicit MultiplexServerDispatcher(Service<Req, Resp>* service) : service_(service) {}
// 接收request
void read(Context* ctx, Req in) override {
// 开始调用
(*service_)(std::move(in)).then([ctx](Resp resp) {
ctx->fireWrite(std::move(resp));
});
}
private:
Service<Req, Resp>* service_;
};
最后,声明一点,Wangle的Server只是部分完成了一个简单的RPC,诸如RPC功能中最重要的服务发现它并没有提供,当然你可以使用redis、zookeeper来达到相应功能。
本系列文章
Wangle源码分析:EventBaseHandler、AsyncSocketHandler
Wangle源码分析:Pipeline、Handler、Context