【sylar-webserver】10 Rock RPC模块 + 服务发现

33 阅读4分钟

前言

sylar 框架的内容挺多的:

  • 日志库,配置库
  • 协程网络库
  • 异步Redis,Mysql Sqlite3 ORM
  • HTTP1.1/2.0,gRPC,Rock RPC
  • 服务发现,性能监控
  • Module动态加载机制(业务和框架分离)

HTTP相关的网络协议,涉及的前置知识过多,且适用性不强(网上也有很多成熟的轮子,比不过)。

Rock RPC

Rock RPC 往往是长连接-短消息,所以此处Rock只是收、发各用一个协程即可满足需求。并不像Http 一样,分配整个worker。

┌─────────────────────────────────────┐
│          应用层 (Module)            │
├─────────────────────────────────────┤
│       服务层 (RockServer/Client)    │
├─────────────────────────────────────┤
│        流层 (RockStream)            │
├─────────────────────────────────────┤
│       协议层 (RockProtocol)         │
├─────────────────────────────────────┤
│        传输层 (TCP Socket)          │
└─────────────────────────────────────┘

协议层 RockProtocol

消息类型体系
Rock 协议基于 protocol.h 中定义的基础消息类型

  • Request:sn,cmd,time
  • Response:sn,cmd,result(uint32_t),resultStr(str)
  • Notify:notify

rock_protocol.h

  • RockBody: 消息体,body(str),支持 Protobuf 序列化和反序列化。(Protobuf原理 todo)
  • RockRequest : 请求消息,继承自 Request 和 RockBody
  • RockResponse : 响应消息,继承自 Response 和 RockBody
  • RockNotify : 通知消息,继承自 Notify 和 RockBody
  • RockMsgHeader:消息头,magic(0x12 0x21),version(1),flag(标志位,是否gzip压缩),length(消息体长度)
  • RockMessageDecoder:继承自 MessageDecoder抽象类,传入Stream::ptr,具体的读和写方法都在这里,包括 解压缩,序列化,反序列化。 ⭐

服务层 RockServer

  • RockServer:RPC服务端,继承自 TcpServer,重写 HandleClient 方法,处理客户端连接。
    • 创建 RockSession session 会话对象,管理客户端连接。
    • 遍历所有类型为 ROCK 的模块,调用其 onConnect() 方法,通知各个模块有新的客户端连接成功。
    • 相对应的,为 session 会话设置 回调函数(foreach 遍历每个模块的 onDisconnect)。当客户端断开连接时,会通知所有ROCK类型的模块执行清理工作。
    • session设置请求 RequestHandler 处理回调函数。当会话收到客户端发来的请求时,会依次调用所有ROCK模块的 handleRequest 方法来处理请求,直到某个模块返回 true 表示已处理该请求。
    • session设置通知 NotifyHandler 处理回调函数。当会话收到客户端发来的通知时,会依次调用所有ROCK模块的 handleNotify 方法来处理通知,直到某个模块返回 true 表示已处理该通知。
  • RockSession: 继承自 RockStream

流层 RockStream

这里的 模板方法 设计模式值得学习 ⭐

graph TD
    Stream抽象基类 --> |继承| SocketStream
    SocketStream --> |包含| Socket
    SocketStream --> |继承| AsyncStream
    AsyncStream --> |内部struct| SendCtx
    SendCtx --> |继承| Ctx
    AsyncStream --> |继承| RockStream
    RockStream --> |内部struct| RockSendCtx
    RockStream --> |内部struct| RockCtx
    SendCtx --> |继承| RockSendCtx
    Ctx --> |继承| RockCtx
streams
  • socket_stream
    • 包含 Socket,继承 Stream
    • 重写 Stream 的 read() , write() , close() 实际就是调用 Socket 的 read() , write() , close() 方法 (将socket和stream依赖分离,通过socket_stream这种具体的子类实现。⭐)
  • zlib_stream
  • async_stream ⭐
    • 读写分离:读写操作运行在独立的协程中,互不阻塞。
    • 内部 struct:
      • SendCtx 预留 doSend()纯虚函数;
      • Ctx 继承 SendCtx,作为消息响应上下文,提供doRsp()把协程重新添加调度(此时得到了完整响应)。
    • 核心接口:
      • doRecv()纯虚函数(接收数据包返回Ctx)。
      • doRead()循环调用doRecv(),得到ctx后调用Ctx::doRsp()
      • doWrite()循环调用,加锁交换queue队列,SendCtx::doSend()发送数据。

async_stream子类需要提供 Ctx doRecv() 实现;SendCtx子类需要提供 SendCtx::doSend() 实现。
总结:子类提供 接收数据包返回Ctx方法,以及具体Ctx的发送方法。⭐

RockStream
  • RockStream 继承自 AsyncSocketStream
    • 提供 RockStream::request() 方法,用于发送数据。
      • 创建局部变量Ctx::ptr,设置sncmdtimebody
      • 调用 enqueue() 方法,把 Ctx 添加到发送队列中。
      • 添加 定时器,避免协程任务丢失。
      • 当接收完数据,Ctx 里的 Fiber 恢复执行
      • 此时 协程的局部变量一直在 ,所以可以得到请求的结果。(见代码理解)
    • 重写 doRecv(),提供接收数据的方法。返回 Ctx::ptr。(只有RESPONSE才会返回)
      • 如果是RESPONSE,在 m_ctxs(发出的请求上下文记录) 中删除 sn 对应的上下文,并返回该上下文。
      • 如果是NOTIFY,调用 handleNotify 回调
        • 调用 m_notifyHandler 回调 (RockServer::handleClient设置的回调),成功后直接结束,无响应。
      • 如果是REQUEST,调用 handleRequest 回调:
        • 生成相应的 rsp
        • 调用 m_requestHandler 回调,成功后 sendMessage(rsp)
          • RockSendCtx 通过 enqueue() 加入发送队列中。
          • AsyncSocketStream::doWrite 从发送队列里取出 RockSendCtx。通过doSend()发送数据。
  • RockSendCtx 继承自 SendCtx,重写 doSend()
  • RockCtx 继承自 Ctx,重写 doSend()。提供具体的发送逻辑。(序列化-发送数据)

总结:

sequenceDiagram
    User->>RockStrem: request()
    RockStrem->>RockStrem: 生成ctx,添加定时器,发送任务添加到队列
    RockStrem->>AsyncSocketStream: doWrite(),唤醒 队列条件变量。
    AsyncSocketStream->>RockStrem: 通过具体的ctx的doSend()发送数据。
    Note over AsyncSocketStream, RockStrem: 等待接收数据。
    AsyncSocketStream->>AsyncSocketStream: doRead(),不停接收数据。
    AsyncSocketStream->>RockStrem: 通过具体的doRecv()接收数据。
    RockStrem->>RockStrem: doRecv()判断数据包类型,只有响应包才会返回具体结构。通知、请求都通过回调执行。请求处理完后,sendMessage(rsp)
    RockStrem->>AsyncSocketStream: 响应包,通过ctx->doRsp()恢复协程。
    AsyncSocketStream->>RockStrem: 协程恢复,处理Ctx::ptr请求结果。

服务发现

数据结构

  • NSNode
    • 表示单个服务节点,包含 IP、端口、权重信息
    • 使用 IP 和端口生成唯一标识 ID
  • NSNodeSet
    • 按命令字(cmd)组织的节点集合
    • 提供添加、删除、获取节点的方法
    • 使用读写锁保证线程安全
  • NSDomain
    • 表示一个域名下的所有服务
    • 内部使用命令字到节点集合的映射
    • 提供域名级别的服务管理功能
  • NSDomainSet
    • 管理所有域名
    • 提供域名的增删查操作
  • NSClientInfo
    • 维护客户端会话信息
    • 记录客户端注册的节点信息和域名到命令的映射