• Java8 Lambda表达应用 -- 单线程游戏server+异步数据库操作


    前段时间我们游戏server升级到开发环境Java8,这些天,我再次server的线程模型再次设计了一下,耗费Lambda表情

    LambdaJava代码。特别是丑陋不堪的匿名内部类,这篇文章主要就是想和大家分享这一点。

    线程模型

    首先简介一下我们游戏server的线程模型。大致例如以下图所看到的:

    Netty线程池仅仅处理消息的收发,当Netty收到消息之后。会交给游戏逻辑线程处理。因为是单线程在处理游戏逻辑,所以每个消息必须非常快处理完。也就是说,不能有数据库等耗时操作。不然逻辑线程非常可能会被卡住。为了不卡住逻辑线程,数据库操作由单独的线程池来处理。

    逻辑线程发起数据库操作后便马上返回继续处理其它消息。数据库线程池处理完成后。再通知逻辑线程,从而达到了异步数据库操作的效果。

    GameAction

    Netty部分的网络代码,在收到消息后。会依据消息找到相应的Action,然后运行。

    详细代码省略,以下是简化后的GameAction的代码:

    public abstract class GameAction {
        
        /**
         * 在逻辑线程里处理消息.
         * @param gs
         * @param req 请求消息
         */
        public void execute(GameSession gs, Object req) {
            GameLogicExecutor.execute(() -> {
                doExecute(gs, req);
            });
        }
        
        // 子类实现
        public abstract void doExecute(GameSession gs, Object req);
        
    }
    
    execute()方法里,使用了Lambda表达式来实现Runnable接口。

    GameLogicExecutor

    GameLogicExecutor是游戏逻辑运行线程,代码例如以下所看到的:

    import java.util.concurrent.Executor;
    import java.util.concurrent.Executors;
    
    /**
     * 游戏逻辑线程.
     */
    public class GameLogicExecutor {
        
        // todo 限制队列长度
        private static final Executor executor = Executors.newSingleThreadExecutor();
        
        /**
         * 把游戏逻辑放到队列.
         * @param gameLogic 
         */
        public static void execute(Runnable gameLogic) {
            executor.execute(gameLogic);
        }
        
    }

    GetPlayerListAction

    以下看一个详细的GameAction实现,这个Action依据用户ID返回用户创建的玩家列表。代码例如以下:

    import java.util.List;
    
    public class GetPlayerListAction extends GameAction {
    
        @Override
        public void doExecute(GameSession gs, Object req) {
            int userId = (Integer) req;
            PlayerDao.getPlayerList(userId, (List<Player> players) -> {
                gs.write(players);
            });
        }
        
    }
    
    如果请求參数是玩家ID。doExecute()方法并没有等待数据库操作。而是立即就返回了。传递给DAO的回调对象是个Lambda表达式,在回调方法里,玩家列表通过GameSession被写到client(这仅仅是演示,实际的响应消息可能是protobuf或JSON)。

    PlayerDao

    import java.util.ArrayList;
    import java.util.List;
    
    public class PlayerDao {
        
        public static void getPlayerList(int userId, DbOpCallback<List<Player>> cb) {
            DbOpExecutor.execute(() -> {
                try {
                    List<Player> players = getPlayerList(userId);
                    cb.ok(players);
                } catch (Exception e) {
                    cb.fail(e);
                }
            });
        }
        
        // 耗时的数据库操作
        private static List<Player> getPlayerList(int userId) {
            return new ArrayList<>();
        }
        
    }

    getPlayerList()方法接收两个參数。第一个參数是用户ID,第二个參数是个callback,当数据库操作完成(成功或失败)时。会通知这个callback。DAO内部使用的可能是JDBC或MyBatis等,总之是耗时的操作,由数据库线程池运行。

    DbOpExecutor

    DbOpExecutor的代码比較简单。和GameLogicExecutor相似,例如以下所看到的:

    import java.util.concurrent.Executor;
    import java.util.concurrent.Executors;
    
    /**
     * 数据库操作线程池.
     */
    public class DbOpExecutor {
        
        // todo 依据cpu数等确定线程数
        private static final Executor executor = Executors.newFixedThreadPool(4);
        
        /**
         * 把数据库操作放进队列.
         * @param dbOp 
         */
        public static void execute(Runnable dbOp) {
            executor.execute(dbOp);
        }
        
    }
    

    DbOpCallback

    最后看一下DbOpCallback代码:

    /**
     * 数据库操作回调.
     * @param <T>
     */
    @FunctionalInterface
    public interface DbOpCallback<T> {
        
        /**
         * 处理数据库返回结果.
         * @param result 
         */
        void handleResult(T result);
        
        /**
         * 数据库操作正常结束.
         * @param result 
         */
        default void ok(T result) {
            // 在游戏逻辑线程里处理结果
            GameLogicExecutor.execute(() -> {
                try {
                    handleResult(result);
                } catch (Exception e) {
                    // todo 处理异常
                }
            });
        }
        
        /**
         * 数据库操作出现异常.
         * @param e 
         */
        default void fail(Exception e) {
            // todo 处理异常
        }
        
    }
    

    @FunctionalInterface说明这是一个函数式接口,简单的说,就是仅仅有一个抽象方法的接口。接口的default方法是Java8的新语法,详细请參考Java8相关方面的资料。ok()方法确保数据库操作的结果是在逻辑线程里处理。


    结论

    上面的代码。在Java8之前用匿名内部类也是能够写的,仅仅是相比Lambda表达式,更加冗长丑陋而已。另外要注意,上面的代码仅仅是简化后的演示样例代码,并不是真实代码。

    假上面的代码设想到自己的项目,还需要注意的是异常处理。

    版权声明:本文博主原创文章,博客,未经同意不得转载。

  • 相关阅读:
    编辑器漏洞
    csrf漏洞
    sdram控制2
    简易sdram控制1
    8051核
    AD7928
    FIR滤波器的verilog实现方法
    fft_cepin
    fft_fft_control
    FFT_fifo
  • 原文地址:https://www.cnblogs.com/bhlsheji/p/4865674.html
Copyright © 2020-2023  润新知