梳理一下整体的架构,总的来说jetsever采用生产—消费者并行模型,建立在Executor framework上:
1.每一个Lane包含一个名字string和线程池(ExecutorService),线程池其实就相当于worker集合
2.每一个Lane有一个计数器(AtomicInteger),用来记录进入这个Lane的session的数量,session集合其实就相当于相当于任务队列
3.每一个GameRoom属于一个Lane(也就是一个Lane中可以有多个GameRoom)。每当添加一个新的session,就根据其所属的GameRoom放到相应的Lane中,这样同一个GameRoom中sessions就会在一个线程中进行处理(一个Lane其实就相是一个线程,ManagedExecutor是调用newSingleThreadExecutor创建了单线程线程池),有利于session间的交互。但是不同的GameRoom中的Session数量可能不同,导致处理器核心负载不均(一个线程是在一个处理器上执行的)。
首先看一下Lane这个泛型接口的定义
package org.menacheri.jetserver.concurrent; public interface Lane<ID_TYPE,UNDERLYING_LANE> { boolean isOnSameLane(ID_TYPE currentLane); ID_TYPE getId(); UNDERLYING_LANE getUnderlyingLane(); }
ID_TYPE是Lane的名字(string),UNDERLYING_LANE是使用的线程池模型(ExecutorService)
DefaultLane是Lane接口的一个实现,可以学习一下泛型的实现方法:
package org.menacheri.jetserver.concurrent; import java.util.concurrent.ExecutorService; public class DefaultLane implements Lane<String,ExecutorService> { final String laneName; final ExecutorService exec; public DefaultLane(String threadName, ExecutorService exec) { this.laneName = threadName; this.exec = exec; } @Override public boolean isOnSameLane(String currentThread) { return currentThread.equals(laneName); } @Override public ExecutorService getUnderlyingLane() { return exec; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((laneName == null) ? 0 : laneName.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; DefaultLane other = (DefaultLane) obj; if (laneName == null) { if (other.laneName != null) return false; } else if (!laneName.equals(other.laneName)) return false; return true; } @Override public String getId() { return laneName; } }
接下来看一下Lanes和LaneStragery这两个实现,可以重点学习下枚举类的用法:
package org.menacheri.jetserver.concurrent; import java.util.concurrent.ExecutorService; import java.util.concurrent.ThreadFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public enum Lanes { LANES; final String serverCores = System.getProperty("jet.lanes"); final int numOfCores; final Lane<String, ExecutorService>[] jetLanes; @SuppressWarnings("unchecked") Lanes() { final Logger LOG = LoggerFactory.getLogger(Lanes.class); int cores = 1; if (null != serverCores) { try { cores = Integer.parseInt(serverCores); } catch (NumberFormatException e) { LOG.warn("Invalid server cores {} passed in, going to ignore",serverCores); // ignore; } } numOfCores = cores; jetLanes = new Lane[cores]; ThreadFactory threadFactory = new NamedThreadFactory("Lane",true); for (int i = 1; i <= cores; i++) { DefaultLane defaultLane = new DefaultLane("Lane[" + i + "]", ManagedExecutor.newSingleThreadExecutor(threadFactory)); jetLanes[i - 1] = defaultLane; } } public Lane<String, ExecutorService>[] getJetLanes() { return jetLanes; } public int getNumOfCores() { return numOfCores; } }
package org.menacheri.jetserver.concurrent; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.atomic.AtomicInteger; import org.menacheri.jetserver.app.GameRoom; /** * A session choosing a Lane can be done based on a strategy. The enumeration * already has 2 default implementations. Users can implement their own * sophisticated ones based on use case. * * @author Abraham Menacherry * * @param <LANE_ID_TYPE> * @param <UNDERLYING_LANE> * @param <GROUP> */ public interface LaneStrategy<LANE_ID_TYPE, UNDERLYING_LANE, GROUP> { Lane<LANE_ID_TYPE, UNDERLYING_LANE> chooseLane(GROUP group); public enum LaneStrategies implements LaneStrategy<String, ExecutorService, GameRoom> { ROUND_ROBIN { final AtomicInteger currentLane = new AtomicInteger(0); final int laneSize = lanes.length; @Override public Lane<String, ExecutorService> chooseLane(GameRoom group) { currentLane.compareAndSet(laneSize, 0); return lanes[currentLane.getAndIncrement()]; } }, /** * This Strategy groups sessions by GameRoom on a lane. Each time a * session is added, it will check for the session's game room and * return back the lane on which the GameRoom is operating. This way all * sessions will be running on the same thread and inter-session * messaging will be fast synchronous message calls. The disadvantage is * that if there are some GameRooms with huge number of sessions and * some with few, possibility of uneven load on multiple CPU cores is * possible. * */ GROUP_BY_ROOM { private final ConcurrentMap<GameRoom, Lane<String, ExecutorService>> roomLaneMap = new ConcurrentHashMap<GameRoom, Lane<String, ExecutorService>>(); private final ConcurrentMap<Lane<String, ExecutorService>, AtomicInteger> laneSessionCounter = new ConcurrentHashMap<Lane<String, ExecutorService>, AtomicInteger>(); @Override public Lane<String, ExecutorService> chooseLane(GameRoom room) { Lane<String, ExecutorService> lane = roomLaneMap.get(room); if (null == lane) { synchronized (laneSessionCounter) { if (laneSessionCounter.isEmpty()) { for (Lane<String, ExecutorService> theLane : lanes) { laneSessionCounter.put(theLane, new AtomicInteger(0)); } } Set<Lane<String, ExecutorService>> laneSet = laneSessionCounter .keySet(); int min = 0; for (Lane<String, ExecutorService> theLane : laneSet) { AtomicInteger counter = laneSessionCounter .get(theLane); int numOfSessions = counter.get(); // if numOfSessions is 0, then this lane/thread/core // is not tagged to a gameroom and it can be used. if (numOfSessions == 0) { lane = theLane; break; } else { // reset min for first time. if (min == 0) { min = numOfSessions; lane = theLane; } // If numOfSessions is less than min then // replace min with that value, also set the // lane. if (numOfSessions < min) { min = numOfSessions; lane = theLane; } } } roomLaneMap.put(room, lane); } } // A new session has chosen the lane, hence the session counter // needs to be incremented. laneSessionCounter.get(lane).incrementAndGet(); // TODO how to reduce count on session close? return lane; } }; final Lane<String, ExecutorService>[] lanes = Lanes.LANES .getJetLanes(); } }
类似的代码 DeliveryGuaranty.java
package org.menacheri.jetserver.communication; /** * The delivery guaranty for the underlying network transport protocol. * Implementations should be immutable. * * @author Abraham Menacherry * */ public interface DeliveryGuaranty { public enum DeliveryGuarantyOptions implements DeliveryGuaranty { RELIABLE(0),FAST(1); final int guaranty; DeliveryGuarantyOptions(int guaranty) { this.guaranty = guaranty; } public int getGuaranty(){ return guaranty; } } /** * Return the associated integer guaranty constant. * * @return returns the integer guaranty. */ public int getGuaranty(); }
枚举类用法的学习:
1.每一个常量(大写或小写均可)代表一个枚举类
2.枚举类需要根据构造器传入特定的参数(没有显示构造器则调用默认构造器,也就是无参构造器)
3.枚举类花括号中的内容是每个类私有的域和方法,外部则为所有枚举类共有的域与方法(可以是静态域和实例域)
4.枚举类间用逗号给开,最后一个枚举类结尾用分号
5.加载枚举类的同时也加载了每个枚举值(访问枚举值的方法:枚举类.枚举值)
(1)final Lane<String,ExecutorService>[] lanes = Lanes.LANES.getJetLanes()说明加载Lanes的时候已经加载了LANES这个类(Lanes运用枚举类实现了单例模式)
(2)GameRoomSession中:laneStrategy = LaneStrategies.GROUP_BY_ROOM;
6.每个枚举类对象应各自实现接口,如果枚举类继承了某个接口
7.LaneStragery是一个泛型接口,其内部定义了一个实现LaneStragery接口的枚举类,包括了两个枚举值,对应两种session的分配策略()
8.首先将PlaySession根据其所属的GameRoom进行聚集,再用ConcurrentHashMap将ExecutorService和GameRoom关联起来,而不是直接将PlaySession和ExecutorService关联起来,因为ConcurrentHashMap比较耗费内存,所以只是和GameRoom进行关联,每个GameRoomSession里面又有一个PlaySession集合
简单总结下伪码的形式:
public enum 类名 extends 类名 implement 接口 { 枚举类1{特有的域与方法(可以重写实例方法)},枚举类2{特有的域与方法},.....枚举类n{特有的域与方法};
所有枚举类都具有的域与方法(构造方法,实例方法,声明,属性,抽象方法,静态方法等); }
ManagedExecutor.java
package org.menacheri.jetserver.concurrent; import java.util.ArrayList; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ThreadFactory; public class ManagedExecutor { public static final List<ExecutorService> EXECUTOR_SERVICES = new ArrayList<ExecutorService>(); static{ Runtime.getRuntime().addShutdownHook(new Thread(){ @Override public void run() { for(ExecutorService service: EXECUTOR_SERVICES){ service.shutdown(); } } }); } public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) { final ExecutorService exec = Executors.newSingleThreadExecutor(threadFactory); EXECUTOR_SERVICES.add(exec); return exec; } }
1.MangedExecutor这个类是用来管理执行线程(工作线程队列)的,也就是List<ExecutorService>服务队列,每当添加一个工作线程的时候,都要加入这个唯一的工作队列当中,也是类名(ManagedExecutor)的由来
2.需要添加shutdown回调函数,也就是在JVM结束的时候将整个ExecutorService队列当掉