Zinx-V0.8消息队列及多任务机制
之前的zinx框架 是1个链接对应1个Reader和1个Writer, 1个消息对应一个Handler,如果说1个客户端发送10个消息 1w客户端 就10w消息,—> 10w Handler的go程来处理业务,cpu就会在10w handler之间进行切换,影响性能,这时就需要制定一个worker处理业务的工作池的机制。
1.在MsgHandler中添加WorkPoolSize(工作池)和TaskQueue(消息队列)
type MsgHandler struct {
//存放路由集合的map
Apis map[uint32] ziface.IRouter //就是开发者全部的业务,消息ID和业务的对应关系
//负责Worker取任务的消息队列 一个worker对应一个任务队列
TaskQueue []chan ziface.IRequest
//worker工作池的worker数量
WorkerPoolSize uint32
}
在初始化的时候将WorkPoolSize和TaskQueue初始化,这里可以让用户自己进行配置
func NewMsgHandler() ziface.IMsgHandler {
//给map开辟头空间
return &MsgHandler{
Apis:make(map[uint32]ziface.IRouter),
WorkerPoolSize:config.GlobalObject.WorkerPoolSize,
TaskQueue:make([]chan ziface.IRequest, config.GlobalObject.WorkerPoolSize),//切片的初始化
}
}
2.在实现层中实现一个真正处理业务的方法
//一个worker真正处理业务的 goroutine函数
func (mh *MsgHandler) startOneWorker(workerID int, taskQueue chan ziface.IRequest) {
fmt.Println(" worker ID = ", workerID , " is starting... ")
//不断的从对应的管道 等待数据
for {
select {
case req := <-taskQueue:
mh.DoMsgHandler(req)
}
}
}
3.抽象层和实现层分别添加启动工作池和将消息添加到工作池的方法
type IMsgHandler interface {
//添加路由到map集合中
AddRouter(msgID uint32, router IRouter)
//调度路由, 根据MsgID
DoMsgHandler(request IRequest)
//启动Worker工作池
StartWorkerPool()
//将消息添加到Worker工作池中 (将消息发送给对应的消息队列)
SendMsgToTaskQueue(request IRequest)
}
//启动Worker工作池 (在整个server服务中 只启动一次)
func (mh *MsgHandler) StartWorkerPool() {
fmt.Println("WorkPool is started..")
//根据WorkerPoolSize 创建worker goroutine
for i := 0; i < int(mh.WorkerPoolSize); i++ {
//开启一个workergoroutine
//1 给当前Worker所绑定消息channel对象 开辟空间 第0个worker 就用第0个Channel
//给channel 进行开辟空间
mh.TaskQueue[i] = make(chan ziface.IRequest, config.GlobalObject.MaxWorkerTaskLen)
//2 启动一个Worker,阻塞等待消息从对应的管道中进来
go mh.startOneWorker(i, mh.TaskQueue[i])
}
}
//将消息添加到Worker工作池中 (将消息发送给对应的消息队列)
//应该是Reader来调用的
func (mh *MsgHandler) SendMsgToTaskQueue(request ziface.IRequest) {
//1 将消息 平均分配给worker 确定当前的request到底要给哪个worker来处理
//1个客户端绑定一个worker来处理
workerID := request.GetConnection().GetConnID() % mh.WorkerPoolSize
//2 直接将 request 发送给对应的worker的taskqueue
mh.TaskQueue[workerID] <- request
}
这里是用每个Connection的id对工作池数量取余得到一个workerID,将request写入TaskQueue下标为workerID的channel中,这样就相对来说实现了将request轮询分配给每个工作池。
4.在Connection刚开始也就是还没有监听之前开启工作池
func (s *Server) Start() {
fmt.Printf("[start] Server Linstenner at IP :%s, Port :%d, is starting..\n", s.IP, s.Port)
//0 启动worker工作池
s.MsgHandler.StartWorkerPool()
//1 创建套接字 :得到一个TCP的addr
addr, err := net.ResolveTCPAddr(s.IPVersion, fmt.Sprintf("%s:%d", s.IP, s.Port))
if err != nil {
fmt.Println("resolve tcp addr error:", err)
return
}
......
}
5.将连接的每个request添加到消息队列
func (c *Connection) StartReader() {
//从对端读数据
fmt.Println("[Reader Goroutine isStarted]...")
defer fmt.Println("[Reader Goroutine Stop...] connID = ", c.ConnID, "Reader is exit, remote addr is = ", c.GetRemoteAddr().String())
defer c.Stop()
for {
......
//将读出来的msg 组装一个request
//将当前一次性得到的对端客户端请求的数据 封装成一个Request
req := NewReqeust(c, msg)
//将req交给worker工作池来处理
if config.GlobalObject.WorkerPoolSize > 0 {
c.MsgHandler.SendMsgToTaskQueue(req)
} else {
go c.MsgHandler.DoMsgHandler(req)
}
}
}