游戏开发工具

Redis解决游戏多服务器跨服通信问题

引言

前面提到,可以用Redis解决游戏的跨服排行榜的痛点,使用Redis可以让排行榜数据更及时,且更新数据时也不会和其他玩家冲突,可以说是完美的跨服排行榜技术方案。那么,Redis在游戏内还有其他什么妙用?


Redis并不只具备存储数据和读取数据的能力,翻看它的API文档,我惊讶地发现,它有一个发布订阅的功能。

Redis 发布订阅 (pub/sub) 是一种消息通信模式:发送者 (pub) 发送消息,订阅者 (sub) 接收消息。Redis 客户端可以订阅任意数量的频道。

1.jpg

上图展示了多个服务器订阅同一个频道,并且任意服务器可以向频道推送消息。下图则展示了订阅该频道的服务器接受来自频道的消息。这看起来就像是一种电台技术,大家都绑定在同一个频段内,就可以多方互相进行消息通信。

2.jpg


有了这个技术,也就是说所有的游戏服(为了更好地进行负载,一般游戏都会分区分服,降低单服压力)可以进行简单的通信。比如,所有服都绑定了频道【cross-channel】,此时1服推送一条消息到Redis,Redis则会转发给所有绑定了该频道的游戏服,通过解析数据,可以指定任意服务器进行数据处理。


游戏内实战——解决跨服聊天。


第一步,新建一个跨服频道中心,初始化的时候启动消息接收器。

/**
 * 跨服数据中心
 * 聊天发送和订阅
 */
object CrossChannelHall {

    /**
     * Jedis中心
     */
    val jedis = CrossDataCenter.getJedis()

    /**
     * 初始化
     */
    fun init() {
        // 启动消息接收器
        Thread(MessageReceiver()).start()
    }

    /**
     * 推送消息
     */
    fun pushMessage(type: Int, data: Any) {
        val json = Gson().toJson(data)
        jedis.publish(GameContext.MESSAGE_CHANNEL_KEY, Gson().toJson(CrossMessageData(type, json)))
    }
}


自定义消息接收器,就是消息监听器,进行循环监听检查消息,当有消息的时候就进行处理。

/**
 * 消息接收器
 */
class MessageReceiver : Runnable {

    /**
     * Jedis中心
     */
    val jedis = CrossDataCenter.getJedis()

    /**
     * 我的处理器
     */
    private val msgServerRegister = MsgServerRegister()

    /**
     * 绑定处理器
     */
    private fun checkMessage() {
        jedis.subscribe(msgServerRegister, GameContext.MESSAGE_CHANNEL_KEY)
    }

    /**
     * 运行
     */
    override fun run() {
        while (true) {
            checkMessage()
        }
    }
}


同时,还需要一个消息注册器JedisPubSub,用于接受消息,并进行对应的处理。


下面是我自定义的一个消息注册器,根据消息类型进行分类处理,这样后期拓展的时候,只需要添加新的Handler处理器。

/**
 * 消息注册器
 */
class MsgServerRegister : JedisPubSub() {

    /**
     * 处理库
     */
    private val handlerMap = mutableMapOf<Int, IMsgHandler>()

    init {
        handlerMap[MsgCmd.MSG_CHAT] = MsgChatHandler()
    }

    /**
     * 处理函数
     */
    override fun onMessage(channel: String, message: String) {
        try {
            Gson().fromJson(message, CrossMessageData::class.java)?.let { data ->
                handlerMap[data.cmd]?.execute(data.json)
            }
        } catch (e: Exception) {
            e.printStackTrace()
        }
    }
}


下面是自定义的一个消息处理器接口。

interface IMsgHandler {
    fun execute(json: String)
}


具体的跨服聊天消息处理器。收到消息,并推送给当前游戏的所有玩家。

class MsgChatHandler : IMsgHandler 
{
    override fun execute(json: String)
   {
        Gson().fromJson(json, ChatData::class.java)?.let {
            ChatLogger.addChat(it)
            SessionUtil.gameBroadcast(PushCodes.CHAT, it)
        }
    }
}


综上,一个简单的跨服聊天功能就开发完成了。使用Redis开发跨服功能不需要额外的第三方服务器,之前我是自己写了一个跨服服务器进行消息中转,每次有新的内容拓展都需要重新发布更新跨服服务器,极大地增加了维护成本,同时也容易引起游戏版本和跨服版本不一致。现在使用Redis后,就有效规避一致性的问题。


当然,我对这个Redis发布订阅的性能目前并不太了解,能够支撑每秒多少次多少数据量等等,所以使用这个功能的场景还比较少,不敢贸然增加。如果有知道这方面性能的大佬,烦请指点一二。