Wangle源码分析:Service

前言

      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源码分析:Service

Wangle源码分析:ServerBootstrap

Wangle源码分析:编解码Handler 

Wangle源码分析:EventBaseHandler、AsyncSocketHandler 

Wangle源码分析:Pipeline、Handler、Context

Wangle源码分析:ClientBootstrap

    

转载于:https://my.oschina.net/fileoptions/blog/882234

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值