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表达式,更加冗长丑陋而已。另外要注意,上面的代码仅仅是简化后的演示样例代码,并不是真实代码。

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

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

时间: 2024-11-08 09:23:43

Java8 Lambda表达应用 -- 单线程游戏server+异步数据库操作的相关文章

Java8 Lambda表达式应用案例 -- 单线程游戏服务器+异步数据库操作

前段时间我们游戏服务器的开发环境升级到了Java8,这两天我又把服务器的线程模型重新设计了一下,用上了Lambda表达式.Lambda表达式确实能够大幅简化Java代码,特别是丑陋不堪的匿名内部类,这篇文章主要就是想和大家分享这一点. 线程模型 首先简单介绍一下我们游戏服务器的线程模型,大致如下图所示: Netty线程池只处理消息的收发,当Netty收到消息之后,会交给游戏逻辑线程处理.由于是单线程在处理游戏逻辑,所以每一个消息必须很快处理完,也就是说,不能有数据库等耗时操作,不然逻辑线程很可能

SQL SERVER C#数据库操作类

using System;using System.Collections;using System.Collections.Specialized;using System.Data;using System.Data.SqlClient;using System.Configuration; namespace LiTianPing.SQLServerDAL //可以修改成实际项目的命名空间名称{ /**//// <summary> /// Copyright (C) 2004-2008

[C#对sql操作]C#对sql server 2008数据库操作

using System.Data; using System.Data.SqlClient SqlConnection conn = new SqlConnection(System.Configuration.ConfigurationManager.ConnectionStrings[SiteClass.dbname].ToString()); SqlCommand cmd = new SqlCommand("dbo.Backorder", conn); conn.Open();

在tornado中使用异步mysql操作

在使用tornado框架进行开发的过程中,发现tornado的mysql数据库操作并不是一步的,造成了所有用户行为的堵塞.tornado本身是一个异步的框架,要求所有的操作都应该是异步的,但是数据库这一层就把整个服务器都拖住了. ##查找到的解决办法: 使用异步的mysql操作库. 查找了一下,有两个比较完善的异步操作库一个是AsyncTorndb,国人自己写的异步操作,看了一下,好像不错的样子,但是没有响应的测试用例,不敢用. 一个是Tornado-MySQL是对PyMySQL的异步化的一个库

JAVA8给我带了什么——lambda表达

这此年来我一直从事.NET的开发.对于JAVA我内心深处还是很向往的.当然这并不是说我不喜欢.NET.只是觉得JAVA也许才是笔者最后的归处.MK公司是以.NET起家的.而笔者也因为兄弟的原因转行.NET.虽然有时候还是会拿起JAVA相关的知识回味一下.尽可能的不让自己忘记.但是时代的进步却把我狠狠甩到了后面去.现在笔者终于离开了M公司.我想回去做JAVA,却发现笔者已经跟不上JAVA时候.在笔者转行.NET的时候,JAVA的版本才到 1.6.现在都1.8了.主要的是这个段时间发现很大的变化.所

【Java学习笔记之三十一】详解Java8 lambda表达式

Java 8 发布日期是2014年3月18日,这次开创性的发布在Java社区引发了不少讨论,并让大家感到激动.特性之一便是随同发布的lambda表达式,它将允许我们将行为传到函数里.在Java 8之前,如果想将行为传入函数,仅有的选择就是匿名类,需要6行代码.而定义行为最重要的那行代码,却混在中间不够突出.Lambda表达式取代了匿名类,取消了模板,允许用函数式风格编写代码.这样有时可读性更好,表达更清晰.在Java生态系统中,函数式表达与对面向对象的全面支持是个激动人心的进步.将进一步促进并行

Java8 lambda表达式10个示例

本文由 ImportNew Java 8 刚于几周前发布,日期是2014年3月18日,这次开创性的发布在Java社区引发了不少讨论,并让大家感到激动.特性之一便是随同发布的lambda表达式,它将允许我们将行为传到函数里.在Java 8之前,如果想将行为传入函数,仅有的选择就是匿名类,需要6行代码.而定义行为最重要的那行代码,却混在中间不够突出.Lambda表达式取代了匿名类,取消了模板,允许用函数式风格编写代码.这样有时可读性更好,表达更清晰.在Java生态系统中,函数式表达与对面向对象的全面

Java8 Lambda表达式详解手册及实例

先贩卖一下焦虑,Java8发于2014年3月18日,距离现在已经快6年了,如果你对Java8的新特性还没有应用,甚至还一无所知,那你真得关注公众号"程序新视界",好好系列的学习一下Java8的新特性.Lambda表达式已经在新框架中普通使用了,如果你对Lambda还一无所知,真得认真学习一下本篇文章了. 现在进入正题Java8的Lambda,首先看一下发音 ([?l?md?])表达式.注意该词的发音,b是不发音的,da发[d?]音. 为什么要引入Lambda表达式 简单的来说,引入La

Java8 Lambda表达式深入学习(4) -- Java8实现方式

前几篇文章讨论了函数式接口和Lambda表达式语法.invokedynamic指令,以及Groovy2如何利用indy指令.本篇文章在前面几篇的基础之上,简要介绍Java8底层是如何实现Lambda表达式的. 示例代码 本文将以下面的代码为例展开讨论: import java.util.Arrays; import java.util.List; public class LambdaImplTest { public static void main(String[] args) { m1(A