游戏服务器业务处理线程管理

 二维码 1063
发表时间:2018-10-27 23:02

QQ截图20190719233114.png

点击图片,支持一下

在游戏服务器的框架设计中,最重要的就是管理业务逻辑处理。当收到客户端的请求消息时,服务器如何辨认这个消息是什么请求,怎么样保证同一个用户请求处理的顺序性?怎么样提高并发性?这些都是在底层框架中要解决的问题。这里分享一种做法,有用者取之。

1,定义消息Id

给客户端与服务器交互的消息定义一个唯一的消息id,通过消息的id,处理对应的业务请求,比如1001 代表登陆,1002 代表签到,1003代表抽卡,等。

2,管理消息Id与处理方法的映射

当服务器解析出客户端的请求拿到消息号之后,怎么做相应处理呢?有的是这样做的:


1.png


这两种方法不是不可以,如果是请求不多还行,如果大一点的游戏,几十上百个请求,一个类中几百行if或switch,看起来就心烦,再加上团队间协作开发,多人修改导致提交git冲突等问题。

一种解决方法是,设计一个统一的接口,每个请求对应一个接口,把id和接口映射起来,放到一个map中。例如:


2.png


这种方式只需要每增加一个消息,增加一个接口实现即可,但是有个问题是每次都需要手动添加消息号和接口的映射,很不爽,我们可以使用注解的反射解决这个问题。

我的项目是使用spring boot管理的,所以使用了spring的特性,从容器中获取实例,因为spring已经在启动的时候扫描了所有注解。

先看定义的注解:



3.png


然后在服务器启动的时候,使用程序自动映射消息号和接口


5.png



这样增加新请求的时候,只需要增加新接口实现即可,而旧的代码不需要修改,也没有长长的if和switch判断。如果觉得RequestCode也需要修改,可以去掉,换成接口,每个人可以定义自己的消息枚举。

如果觉得一个接口处理一个消息有点浪费的话,也可以像web的controller那样,把处理定位到方法上面。即一个方法对应一个处理的消息id.服务器启动的时候缓存方法:

6.png


3,管理业务线程

要保证同一个用户的消息处理都是顺序性的,一般有几种方式,1,加锁,2,使用队列,生产者消费者模式,3,在同一个线程中处理。使用锁,在高并发的时候导致cpu上下文切换频繁,性能下降,我们可以采用netty的方式,同一个连接的所有消息都在同一个线程中处理。一服务器处理的线程并不是越多越好,见:https://www.cnblogs.com/wgslucky/p/9749990.html   所以在处理业务的时候最好不要有io操作。那不可避免的io操作,比如查数据库,更新数据,我们放在别的单独线程处理。这里先说业务处理,它操作的都是内存中的数据,所以速度很快,不会卡玩家。

首先创建一个线程处理器的类,用于管业务线程数:


7.png


这里根据用户的userId,取余固定一个线程处理器,所以同一个用户的请求都放到这个线程处理器中。

在收到消息后


package com.xinyue.interview.dispatch;

import java.util.List;
import java.util.concurrent.TimeUnit;
import javax.annotation.PostConstruct;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.alibaba.fastjson.JSONObject;
import com.xinyue.interview.controller.protocol.HeadInfo;
import com.xinyue.interview.controller.protocol.LogicMessage;
import com.xinyue.interview.exception.ServerError;
import com.xinyue.interview.exception.ServerErrorException;
import com.xinyue.interview.logic.manager.AccountManager;
import com.xinyue.interview.logic.manager.EntityManagerFactory;
import com.xinyue.interview.logic.manager.IEntityManager;
import com.xinyue.interview.utils.concurrent.LogicEventExecutorGroup;
import io.netty.util.concurrent.EventExecutor;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.Promise;

@Service
public class LogicRequestDispatch {

    private Logger logger = LoggerFactory.getLogger(LogicRequestDispatch.class);
    @Autowired
    private LogicRequestMappingManager logicRequestMappingManager;
    private LogicEventExecutorGroup executorGroup;

    @PostConstruct
    public void init() {
        //初始化4个线程处理器处理业务逻辑
        int nthreads = 4;
        this.executorGroup = new LogicEventExecutorGroup(nthreads);
    }

    public void fireRead(LogicRequestContext ctx, JSONObject param) {
        long userId = ctx.getUserId();
        EventExecutor executor = executorGroup.select(userId);
        executor.execute(() -> {
            try {
                // 检测是否登陆成功
                this.checkLogin(ctx.getHeadInfo());
                // 调用业务方法
                Integer commandId = ctx.getCommandId();
                logicRequestMappingManager.callMethod(commandId, ctx, param);
            } catch (Throwable e) {
                LogicMessage response = null;
                if (e instanceof ServerErrorException) {
                    ServerErrorException errorException = (ServerErrorException) e;
                    response = new LogicMessage(errorException);
                } else {
                    response = new LogicMessage(ServerError.SYSTEM_ERROR);
                }
                ctx.writeAndflush(response);
            }
        });
    }

    private void checkLogin(HeadInfo headInfo) {
        long userId = headInfo.getUserId();
        AccountManager accountManager = EntityManagerFactory.getManager(userId, AccountManager.class);
        if (accountManager == null) {
            ServerError.throwError(ServerError.USER_NOT_LOGIN, "userId:{}", userId);
            return;
        }
        String token = headInfo.getToken();
        if (!accountManager.checkToken(token)) {
            ServerError.throwError(ServerError.USER_NOT_LOGIN, "userId:{},token错误", userId);
            return;
        }
    }

    /**
     *
     * <p>
     * Description:初始化相应的数据管理类到对应的逻辑线程中
     * </p>
     *
     * @param userId
     * @param managers
     * @author wgs
     * @date 2018年10月19日 下午4:39:31
     *
     */
    public void initEntityManager(long userId, List<? extends IEntityManager> managers) {
        EventExecutor executor = executorGroup.select(userId);
        executor.execute(() -> {
            try {
                EntityManagerFactory.init(userId, managers);
            } catch (Exception e) {
                logger.error("初始化EntityManager出错", e);
            }
        });
        executor.scheduleAtFixedRate(() -> {
            EntityManagerFactory.flushDB();
        }, 5, 5, TimeUnit.MINUTES);
    }

    public Future<Object> fireEvent(long selectKey, Object param, Promise<Object> promise) {

        return promise;
    }

}




这样就实现了业务处理保证在同一个线程中。




QQ截图20181027223340.png