Redis:游戏业务中的使用

本文介绍一下我在多个游戏项目中,对Redis使用的经验。Redis是个好东西,可以用在不同的业务场景。

0 Redis是什么

Redis最大的特点是所有的数据都存放在内存里,它常用于数据库、cache和消息队列等场景。

1 Redis特性

读写性能高

首先,redis数据都在内存里,其次,整体来看可以把redis看成一个大hashmap,所以基于key索引数据都可以在O(1)时间复杂度内完成,所以一个Redis节点(Redis进程)的QPS可以达到10w,特别适合作为传统数据库(mongo/mysql)的补充提升读写效率。

单线程

所有的读写请求都是单线程按照顺序执行的,每个请求都是原子的,不会被打断。

分布式

Redis支持分布式集群,比如阿里云会在集群的多个节点前面放一个负载均衡,就可以像访问单节点一样访问redis集群。

如下图所示,是我们在阿里云的一个16节点redis集群。

Redis:游戏业务中的使用_第1张图片

阿里云的redis集群

由于阿里云提供的redis集群节点数量总是有上限的(其实可以做到无上限),因此为了获得更好和更灵活的水平扩展能力,我们游戏在业务层又增加了一层分布式功能。在操作redis的同时,可以传入一个参数作为index通过hash映射到某一个redis集群上。

持久化

因为支持持久化,若同时开启RDB和AOF持久化选项,可以作为持久化数据库保存数据,重启redis数据不会丢失。

阿里云同时开启了RDB和AOF(存疑)。

少量数据的持久化存储可以使用redis,但是大量数据不可以。因为redis会把数据全部放到内存里,所以对于大量数量,内存成本太高。

比如好友关系这类关系信息属于多个玩家之间的信息,保存在玩家身上也不合适。而且这类信息使用比较频繁并且数据量不大,就可以持久化存储在redis中。

其他高级特性

Redis还有一些高级特性:

  • 事务:watch关键词。
  • 多命令:可以将多个命令同时发给某个redis节点,降低网络通信时间。
  • lua脚本:通过lua脚本,可以将一些redis操作包装成原子操作。
  • 发布/订阅:可以用于消息分发。
  • stream:redis抄kafka做的消息队列,4.0的功能...

2 Redis数据结构

redis整体来看存储的是一个很大的hashmap,其中key是一个字符串,value可以有多种数据结构。

value支持String、Hash、List、Set、SortedSet五种数据存储结构,可以实现不同的业务需求:·

  • String:存储简单的int\string
  • List:列表/队列,也可以作为消息队列使用。
  • Hash:一个key/value的键值对,这里的value只支持简单的类型(int\string)
  • Set:元素不会重复的集合。
  • SortedSet:有序集合,元素不会重复,同时元素会有一个score值,这个集合会根据score进行实时排序。常用的需求是排行榜。

大家使用redis时,使用方式的就是通过接口操作这些数据结构,所以建议大家看一下redis的基本操作 http://doc.redisfans.com/

3 Redis在游戏业务中的常见应用

一般来说,游戏会使用mongo或者mysql存储各类游戏信息,但这种数据库将数据存在硬盘里,读些速度比较慢。而redis将数据放在内存里,读写速度可以提高一个数量级。因此,可以配合mongo/mysql使用。

Cache

redis最常见的功能就是作为cache使用,游戏中很多逻辑如果去读mongodb,会有性能问题,所以使用redis作为缓存,降低对mongodb的读写。

此外,还有些需求可以使用Reids作为缓存,比如玩家个人简要信息。当我们查看其他玩家信息时,往往不需要全量信息,只需要部分信息(在线状态、等级、部分信息等),而这些信息要是去读取mongo会产生非常大的压力。因此,可以将指定的玩家信息放在Redis里,提高访问效率。

业务状态/数据存储

因为redis本身就支持持久化存储,所以redis在一定程度上可以替代mongo。

那么,什么数据适合存在mongo,什么数据适合存在redis呢?

对于一个数据应该放在redis还是mongo中存储,主要考虑两个方面:1.数据量。2.数据访问频率。

若数据量较小,比如记录哪些区域聊天频道需要关闭,这种信息一般使用redis记录即可。

若数据量较大但不是特别大,但数据访问频率比较高,比如玩家好友关系信息,那么一般也可以使用redis存储。

若数据量特别大,比如玩家/家族全量信息,一般使用mongo进行存储。

无状态服务开发支持

一般游戏服务器都是有状态的,也就是说内存里是有数据的。而无状态的服务的意思是内存里不保存数据(有可能在执行逻辑过程中有些临时变量)。

关于有状态无状态的描述,可以参考 https://blog.csdn.net/yinxiangbing/article/details/53353940

内存里不保存状态,那么状态存在哪里呢?redis就可以保存状态的地方。

举个例子,我们要开发一个组队服务,那么就需要一个组队匹配池,我们可以将匹配池保存在服务内存中(有状态),当然也可以将这个匹配池信息保存在redis中(无状态)。

无状态(信息保存在redis)的好处如下:

  • 若我们将匹配池保存在内存中,那么这个业务性能的瓶颈最大是一个进程,哪怕使用多线程,那么天花板就是一台服务器。而若使用redis存储数据,我们就可以写多个进程来执行业务逻辑,性能瓶颈就不再存在,理论上就不会出现性能瓶颈。
  • 若数据放在内存里,机器和进程是有概率crash,数据也就丢失了。而redis可以使用主从切换,数据不会丢失,容错能力强。

坏处:成本高、编程麻烦。

我在网易曾经写过一套微服务框架,其实就是用的无状态的原理写游戏逻辑。为服务框架非常适合写非战斗逻辑,对大DAU游戏可以提供非常强力的支持。后面有机会可以写文章介绍下思想。

多进程/线程/线程之间的共享数据存储

有些不同的进程需要共享数据,可以使用redis存储这些共享数据。

比如有个游戏需求是全服玩家给最喜爱的角色投票,所有玩家在不同的进程都需要操作这些信息,就可以将这些信息保存在redis中进行共享。

临时数据管理

redis的数据支持生命周期,所以若一些数据是临时的(比如活动数据,活动结束后就不需要了),就可以将数据存在redis中,并且给一个生存时间(通过EXPIRE命令),当时间超过生存时间就会自动删除。

排行榜

Redis的SortedSet非常适合做排行榜功能,在几年前Redis不火时,如何写一个好用的游戏排行榜还是一个课题,近两年就再也没有人提这个事情了,没啥好说的,用redis就好了。

分布式锁

不同进程中的逻辑若希望使用锁写同步逻辑,可以基于redis的SETNX命令实现。

参考: https://juejin.im/post/5b737b9b518825613d3894f4

消息队列

游戏中不同的进程之间的通信,也可以使用redis作为消息队列来通信。

比如给游戏服务器发一个控制命令,就可以将命令存在redis的list中,游戏服务器实时检查redis是否有命令,有则pop出来执行。

Redis能否作为消息队列使用是一个常常被争论的话题,因为有更专业的消息队列,比如Kfaka。我曾经服务的一个百万级DAU项目曾经使用Redis作为简单功能的消息队列(只有消息传输功能),由此可见Redis作为消息队列在生产环境使用问题并不大。

4 Redis使用注意事项

避免出现大key,通过小key进行分散

大key表示一个key对应的value非常大。比如,我们希望记录每个玩家的当前在线状态,若我们创建了一个key为online_status的hashmap,这个hashmap的格式为uid:status。那么,所有的玩家的在线信息都保存在这个key中。这种情况,就会导致online_status这个key对应的value极大。

redis虽然没有限制一个key对应的value的大小(应该没有),但是不建议使用大key。正常的使用方式是将每个玩家的信息作为一个字符串直接保存在redis中,比如key为online_status_{uid},value为是否在线。

原因是redis基于key来做的分布式,若创建了一个大key,就会导致分布式功能失效,所有的请求都会到达同一个redis节点,导致这个节点卡顿,其他节点空闲。

Bigkey没办法检测

避免hotkey

hotkey的含义是某个key读写频率很高,特别繁忙。

需要避免这种情况的原因和大key类似,会导致分布式失效,某些节点卡顿。

hotkey在Redis4.0版本有检测方式

给key增加前缀

大家需要创建一个新的key前,需要保证key在所有业务中是独一无二的,因此key最好有namespace的概念。

我们可以通过给key前面加一个前缀,来避免key重复的情况。比如我们需要记录玩家在线信息,key设计为player:online_status,而当我们又需要记录家族的活跃状态,key设计为family:online_status

(举个栗子,家族活跃状态应该叫active_status更合适...)

5 redis冷热数据分离

上文说过,Redis不适合做大数据的持久化存储,因为Redis会将所有数据都放在内存里,相比硬盘存储,成本极高(作为一个成功游戏项目都觉得贵)。

其实有一种折中方案,就是将数据分为冷数据(表示长时间不活跃的数据,比如流失玩家数据)和热数据(活跃数据,比如经常登录玩家信息),将冷数据放在硬盘里,将热数据放在内存里。然后当长期不活跃玩家回流后,就将这个玩家的数据从冷数据转为热数据,从硬盘中转到内存中,玩家长期不在线则把他的数据从热数据转为冷数据,从内存中转到硬盘中。这样的话,内存成本就会降低很多。

之前项目同事曾经尝试实现过这个功能。就是写一个proxy,后面挂一个redis和mongo,proxy实现一套redis协议解析,并且实现冷热数据切换和管理。冷数据存在mongo,当访问冷数据时将数据加载到redis中转为热数据,热数据超过一个ttl后,又变为冷数据存到mongo中。

这个有一些问题,比如redis数据和mongo数据格式如何对应等,如何保证数据一致性和高可用性等。此外,对redis的使用API各种限制也比较多。

腾讯云有一个类似的产品TcaplusDB, 但是不支持Redis命令,据说腾讯游戏内部用的都是这个东西(腾讯的同学可以说说)。腾讯还有一个类似的开源项目DCache,估计就是TcaplusDB的开源产品。

阿里云已经有Redis冷热数据分离的beta版本,等成熟了,甚至可以一定程度上取代mongodb。我认为这个项目用在游戏行业非常合适,关注已久。

云数据库Redis_混合型存储_冷热数据分离_节约成本​promotion.aliyun.com/ntms/act/hybridstore.html​编辑

你可能感兴趣的:(redis,游戏,数据库)