Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

redis在游戏开发中的应用 #45

Open
losophy opened this issue Feb 22, 2021 · 0 comments
Open

redis在游戏开发中的应用 #45

losophy opened this issue Feb 22, 2021 · 0 comments
Labels

Comments

@losophy
Copy link
Owner

losophy commented Feb 22, 2021

Redis是一个新兴的NoSql数据缓存组件,与memcache类似,但是功能却比memcache多一些。
首先,Redismemcache都是基于内存的,所以读取和写入速度都非常快。但是memcache只支持简单的key-value数据的存储方式,而Rediskey-value ,hash,list,set,SortSet等数据结构有很好的支持。

下面就Redis在游戏的开发应用中做一些简单的介绍。

数据的缓存

在这一点上,redismemcache是一样的。都是把数据提前放入到内存中。当逻辑处理中需要用到数据时,先从内存中读取,相同的,写的时候也先向内存中写入,然后再操作数据库,以增加数据处理的速度。不同的是,redis带有把数据写入到硬盘的功能,具体的写入策略可以在redis的配置文件中配置。这样当主机突然出现故障时,比如断电,重启机器不会造成数据的丢失。这个在游戏的应用中特别重要。一般在游戏开发中,数据的处理会采用:缓存 + 持久化队列 + 数据库(mysql)的架构。执行的流程是先把数据写入到缓存,然后把需要持久化的数据放入到持久化队列中,启动一个守护线程,从持久化队列中不断的取出数据,并存入或更新到数据库。如果使用memcache这样没有写入到硬盘功能的缓存组件,出现故障时,持久人队列中如果还有没有处理完的数据,那么就会造成数据的丢失,引用玩家的数据出现短暂的回档。当然这些也可以自己开发一些功能去防止,但是增加了开发成本。

不丢失数据的持久化队列的实现

上面说过Redis具有把数据写入到硬盘的功能,而且支持多种数据结构。那么就可以利用Redislist实现持久化队列,而且当机器出现故障时,不会出现队列中数据丢失的情况,重启之后,数据会自动加载到redislist之中。
具体实现方法:

(1)在Redis中构造一个list存储
(2)一个线程使用Redislpush方法,向list的左边加入数据
(3)另外一个线程使用Redisrpop方法,从list取出数据进行处理,并且从list中删除了取出的数据。这样就实现了一个简单的生产者--消费者模式的队列

对并发操作的控制(跨房间)

一般来说,我们操作一个数据的流程是这样的,取出--处理---存储,这样在单线程中操作是没有任务问题的,但是在多线程环境中就不适用了,我们必须考虑数据同步的问题,保证数据操作的原子性。如果在游戏中,对玩家战队的属性进行更新,一般在数据库中都会保存一个TeamInfo表,里面有玩家相应的属性,比如名字,等级,金币,钻石等等。在memcache中保存一个TeamInfo对象,这时玩家获得金币,我们就需要取出玩家所有的属性,然后set金币,完成后再存储整个对象。这个时候就得考虑数据的同步了,如果在操作的时候,另外一个线程B修改了钻石,并完成了存储,而这个时候我把金币修改完成之后,再存储,这时,就出现了数据混乱的结果。考虑数据同步无非也是加锁或乐观同步。不但增加了代码量,还增加了维护的难度。而在Redis中,它支持对hash数据结构的操作。我们可以把玩家的对象按每个字段存储到redishash中。

当我需要更新金币时,比如增加或减少,我可以使用Redis自带的原子操作方法:hincrby(String key,String field,int value)进行操作,value是正为加,是负为减,这样就简化和避免了一些并发操作,而且这个操做还减少了对数据的操作步骤,因为没有取出,再操作的过程了,只有一次写入。而且在游戏中很少一次更新非常多的字段,如果有这样的情况,下面的方法可以解决。

对事务的支持

redis提供了一个事务操作的机制,MULTI命令用于开启一个事务,它总是返回OK
MULTI执行之后, 客户端可以继续向服务器发送任意多条命令, 这些命令不会立即被执行, 而是被放到一个队列中, 当EXEC命令被调用时, 所有队列中的命令才会被执行。
另一方面, 通过调用DISCARD, 客户端可以清空事务队列, 并放弃执行事务。
以下是一个事务例子, 它原子地增加了foobar两个键的值:

> MULTI
OK
> INCR foo
QUEUED
> INCR bar
QUEUED
> EXEC
1) (integer) 1
2) (integer) 1

EXEC命令的回复是一个数组, 数组中的每个元素都是执行事务中的命令所产生的回复。 其中, 回复元素的先后顺序和命令发送的先后顺序一致。当客户端处于事务状态时, 所有传入的命令都会返回一个内容为QUEUED的状态回复(status reply), 这些被入队的命令将在EXEC命令被调用时执行。从Redis 2.6.5开始,服务器会对命令入队失败的情况进行记录,并在客户端调用 EXEC命令时,拒绝执行并自动放弃这个事务。

提供外部的CAS行为,实现乐观锁机制

在游戏开发中,有时候需要我们自己在外部实现乐观锁机制,WATCH命令可以为Redis事务提供check-and-set (CAS)行为,被WATCH的键会被监视,并会发觉这些键是否被改动过了。 如果有至少一个被监视的键在EXEC执行之前被修改了, 那么整个事务都会被取消, EXEC返回空多条批量回复(null multi-bulk reply)来表示事务已经失败。
举个例子, 假设我们需要原子性地为某个值进行增 1 操作(假设INCR不存在)。
首先我们可能会这样做:

val = GET mykey
val = val + 1
SET mykey $val

上面的这个实现在只有一个客户端的时候可以执行得很好。 但是, 当多个客户端同时对同一个键进行这样的操作时, 就会产生竞争条件。
举个例子, 如果客户端AB都读取了键原来的值, 比如10, 那么两个客户端都会将键的值设为11, 但正确的结果应该是12 才对。
有了WATCH, 我们就可以轻松地解决这类问题了:

WATCH mykey
val = GET mykey
val = val + 1
MULTI
SET mykey $val
EXEC

使用上面的代码, 如果在WATCH执行之后,EXEC执行之前, 有其他客户端修改了mykey的值, 那么当前客户端的事务就会失败。 程序需要做的, 就是不断重试这个操作, 直到没有发生碰撞为止。这种形式的锁被称作乐观锁, 它是一种非常强大的锁机制。 并且因为大多数情况下, 不同的客户端会访问不同的键, 碰撞的情况一般都很少, 所以通常并不需要进行重试。

缓存生命周期的控制

在游戏服务器中,为了节省性能,我们没有必要把所有玩家的信息都缓存到内存中。比如有一些不常登陆的玩家,那么他的信息就没必要一直呆在缓存中了,需要清除。Redis为这个功能提供了一个方法:expire,它可以为key设置以秒为单位的生命周期,比如设置为300s,那么五分钟之后,这条记录就会在内存中删除。这样不仅可以节省内存,而且增加了服务器的性能

排行榜

游戏服务器中涉及到很多排行信息,比如玩家等级排名、金钱排名、战斗力排名等。
一般情况下仅需要取排名的前N名就可以了,这时可以利用数据库的排序功能,或者自己维护一个元素数量有限的top集合。
但是有时候我们需要每一个玩家的排名,玩家的数量太多,不能利用数据库(全表排序压力太大),自己维护也会比较麻烦。
使用Redis可以很好的解决这个问题。它提供的有序Set,支持每个键值(比如玩家id)拥有一个分数(score),每次往这个set里添加元素,Redis会对其进行排序,修改某一元素的score后,也会更新排序,在获取数据时,可以指定排序范围。
更重要的是,这个排序结果会被保存起来,不用在服务器启动时重新计算。
通过它,排行榜的实时刷新、全服排行都不再成为麻烦事。


El mundo es un pañuelo.

@losophy losophy added the redis label Feb 22, 2021
@losophy losophy changed the title Redis:在游戏开发中的应用 Redis在游戏开发中的应用 Feb 22, 2021
@losophy losophy changed the title Redis在游戏开发中的应用 redis在游戏开发中的应用 Feb 22, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant