在上一篇文章中,我们看到,消息是直接在mina的io线程中处理的。这样做有一个非常严重的缺陷,如果业务处理比较耗时,那么io线程接受消息的速度就会下降,严重影响io的吞吐量。
典型的,我们应该另起线程池,专门用于异步地处理玩家的请求消息。
在我之前的一篇文章(游戏服务端线程模型——无锁处理玩家请求),谈到可以通过某种映射,将玩家的请求分发到特定的线程进行处理,这样可以避免同一个玩家的请求需要进行线程同步。
在那篇文章,我们采用的映射策略是——将玩家的角色id与工作线程总数进行求模映射,这种模型其实是一种简单的策略。在极端的情况下,会造成非常多的玩家请求在同一条线程上(登录的玩家id不具有负载均衡性)。
采用什么映射策略,跟游戏的类型定位的联系非常之大。
举个例子,如果游戏的类型是一款MMORPG(大型多人在线游戏),场景地图非常大,游戏的战斗发生在服务端,pvp同步策略采用状态同步,这样的战斗方案为了减少锁竞争,往往要求同一张地图的所有玩家请求在一条线程上。特别的,由于战斗发生在服务端,怪物的行为,场景定时任务的执行,也保证在同一条线程上。所以,这类游戏的请求消息映射策略往往跟地图id挂钩。
另外一些游戏类型,比如休闲游戏,或者虽然是rpg游戏,但战斗发生在客户端(服务端只做检验),映射策略跟场景没关系,只需保证负载均衡即可。
本文采用的映射策略是第二种,因为战斗发生在服务端的设计难度非常大 =。=
为了达到负载均衡,我们可以在客户端链路的创建时,为该Session创建一个自增长的索引号。这样每一个新的玩家就是轮询地映射到下一条工作线程。
在IoHandler类的sessionCreated(IoSession session)方法,我们增加这样的逻辑
@Override
public void sessionCreated(IoSession session) {
//显示客户端的ip和端口
System.out.println(session.getRemoteAddress().toString());
session.setAttributeIfAbsent(SessionProperties.DISTRIBUTE_KEY,
SessionManager.INSTANCE.getNextDistributeKey());
}
其中SessionManager.getNextDistributeKey()是一个原子变量的自增长器。
1. 定义可分发的任务接口 IDistributeTask.java
package com.kingston.net.dispatch;
/**
* 可分发的任务接口
* @author kingston
*/
public interface IDistributeTask {
/**
* 分发的工作线程索引
* @return
*/
int distributeKey();
/**
* 获取名字
* @return
*/
String getName();
/**
* 执行业务
*/
void action();
}
2. AbstractDistributeTask抽象类是IDistributeTask接口的一个骨架实现,实现部分抽象方法
package com.kingston.net.context;
import com.kingston.net.dispatch.IDistributeTask;
public abstract class AbstractDistributeTask implements IDistributeTask{
/** 消息分发器的索引 */
protected int distributeKey;
/** 业务开始执行的毫秒数 */
private long startMillis;
/** 业务结束执行的毫秒数 */
private long endMillis;
public String getName() {
return this.getClass().getSimpleName();
}
public int distributeKey() {
return distributeKey;
}
public long getStartMillis() {
return startMillis;
}
public void markStartMillis() {
this.startMillis = System.currentTimeMillis();
}
public long getEndMillis() {
return endMillis;
}
public void markEndMillis() {
this.endMillis = System.currentTimeMillis();
}
}
3. 消息任务实体(MessageTask.java),用于封装业务执行的相关参数,继承自AbstractDistributeTask类。
package com.kingston.net.context;
import java.lang.reflect.Method;
import com.kingston.net.Message;
public class MessageTask extends AbstractDistributeTask {
private long playerId;
/** 消息实体 */
private Message message;
/** 消息处理器 */
private Object handler;
private Method method;
/** 处理器方法的参数 */
private Object[] params;
public static MessageTask valueOf(int distributeKey, Object handler,
Method method, Object[] params) {
MessageTask msgTask = new MessageTask();
msgTask.distributeKey = distributeKey;
msgTask.handler = handler;
msgTask.method = method;
msgTask.params = params;
return msgTask;
}
@Override
public void action() {
try{
method.invoke(handler, params);
}catch(Exception e){
}
}
public long getPlayerId() {
return playerId;
}
public Message getMessage() {
return message;
}
public Object getHandler() {
return handler;
}
public Method getMethod() {
return method;
}
public Object[] getParams() {
return params;
}
@Override
public String toString() {
return this.getName() + "[" + handler.getClass().getName() + "@" + method.getName() + "]";
}
}
package com.kingston.net.context;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import edu.emory.mathcs.backport.java.util.concurrent.atomic.AtomicBoolean;
/**
* 消息任务处理器
* @author kingston
*/
public enum TaskHandlerContext {
/** 单例 */
INSTANCE;
private final int CORE_SIZE = Runtime.getRuntime().availableProcessors();
/** 工作者线程池 */
private final List workerPool = new ArrayList<>();
private final AtomicBoolean run = new AtomicBoolean(true);
public void initialize() {
for (int i=0; i taskQueue = new LinkedBlockingQueue<>();
TaskWorker(int index) {
this.workerIndex = index;
}
public void addTask(AbstractDistributeTask task) {
this.taskQueue.add(task);
}
@Override
public void run() {
//死循环读消息
while(run.get()) {
try {
AbstractDistributeTask task = taskQueue.take();
task.markStartMillis();
task.action();
task.markEndMillis();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
try {
//通过反射,
cmdExecutor.getMethod().invoke(controller, params);
}catch(Exception e) {
}
采用生产者模型后,我们只需要改成
int distributeKey = (int)session.getAttribute(SessionProperties.DISTRIBUTE_KEY);
TaskHandlerContext.INSTANCE.acceptTask(
MessageTask.valueOf(distributeKey, controller, cmdExecutor.getMethod(), params));