Jetsever开源项目学习(五)Concurrent学习

梳理一下整体的架构,总的来说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队列当掉

 

posted on 2016-01-06 00:38  gyt929458988  阅读(353)  评论(0编辑  收藏  举报