We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
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是一个新兴的NoSql数据缓存组件,与memcache类似,但是功能却比memcache多一些。 首先,Redis和memcache都是基于内存的,所以读取和写入速度都非常快。但是memcache只支持简单的key-value数据的存储方式,而Redis对key-value ,hash,list,set,SortSet等数据结构有很好的支持。
Redis
NoSql
memcache
key-value
key-value ,hash,list,set,SortSet
下面就Redis在游戏的开发应用中做一些简单的介绍。
在这一点上,redis和memcache是一样的。都是把数据提前放入到内存中。当逻辑处理中需要用到数据时,先从内存中读取,相同的,写的时候也先向内存中写入,然后再操作数据库,以增加数据处理的速度。不同的是,redis带有把数据写入到硬盘的功能,具体的写入策略可以在redis的配置文件中配置。这样当主机突然出现故障时,比如断电,重启机器不会造成数据的丢失。这个在游戏的应用中特别重要。一般在游戏开发中,数据的处理会采用:缓存 + 持久化队列 + 数据库(mysql)的架构。执行的流程是先把数据写入到缓存,然后把需要持久化的数据放入到持久化队列中,启动一个守护线程,从持久化队列中不断的取出数据,并存入或更新到数据库。如果使用memcache这样没有写入到硬盘功能的缓存组件,出现故障时,持久人队列中如果还有没有处理完的数据,那么就会造成数据的丢失,引用玩家的数据出现短暂的回档。当然这些也可以自己开发一些功能去防止,但是增加了开发成本。
redis
mysql
上面说过Redis具有把数据写入到硬盘的功能,而且支持多种数据结构。那么就可以利用Redis的list实现持久化队列,而且当机器出现故障时,不会出现队列中数据丢失的情况,重启之后,数据会自动加载到redis的list之中。 具体实现方法:
list
(1)在Redis中构造一个list存储 (2)一个线程使用Redis的lpush方法,向list的左边加入数据 (3)另外一个线程使用Redis的rpop方法,从list取出数据进行处理,并且从list中删除了取出的数据。这样就实现了一个简单的生产者--消费者模式的队列
lpush
rpop
一般来说,我们操作一个数据的流程是这样的,取出--处理---存储,这样在单线程中操作是没有任务问题的,但是在多线程环境中就不适用了,我们必须考虑数据同步的问题,保证数据操作的原子性。如果在游戏中,对玩家战队的属性进行更新,一般在数据库中都会保存一个TeamInfo表,里面有玩家相应的属性,比如名字,等级,金币,钻石等等。在memcache中保存一个TeamInfo对象,这时玩家获得金币,我们就需要取出玩家所有的属性,然后set金币,完成后再存储整个对象。这个时候就得考虑数据的同步了,如果在操作的时候,另外一个线程B修改了钻石,并完成了存储,而这个时候我把金币修改完成之后,再存储,这时,就出现了数据混乱的结果。考虑数据同步无非也是加锁或乐观同步。不但增加了代码量,还增加了维护的难度。而在Redis中,它支持对hash数据结构的操作。我们可以把玩家的对象按每个字段存储到redis的hash中。
TeamInfo
set
B
hash
当我需要更新金币时,比如增加或减少,我可以使用Redis自带的原子操作方法:hincrby(String key,String field,int value)进行操作,value是正为加,是负为减,这样就简化和避免了一些并发操作,而且这个操做还减少了对数据的操作步骤,因为没有取出,再操作的过程了,只有一次写入。而且在游戏中很少一次更新非常多的字段,如果有这样的情况,下面的方法可以解决。
hincrby(String key,String field,int value)
value
redis提供了一个事务操作的机制,MULTI命令用于开启一个事务,它总是返回OK。 MULTI执行之后, 客户端可以继续向服务器发送任意多条命令, 这些命令不会立即被执行, 而是被放到一个队列中, 当EXEC命令被调用时, 所有队列中的命令才会被执行。 另一方面, 通过调用DISCARD, 客户端可以清空事务队列, 并放弃执行事务。 以下是一个事务例子, 它原子地增加了foo和bar两个键的值:
MULTI
OK
EXEC
DISCARD
foo
bar
> 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命令时,拒绝执行并自动放弃这个事务。
QUEUED
(status reply)
Redis 2.6.5
在游戏开发中,有时候需要我们自己在外部实现乐观锁机制,WATCH命令可以为Redis事务提供check-and-set (CAS)行为,被WATCH的键会被监视,并会发觉这些键是否被改动过了。 如果有至少一个被监视的键在EXEC执行之前被修改了, 那么整个事务都会被取消, EXEC返回空多条批量回复(null multi-bulk reply)来表示事务已经失败。 举个例子, 假设我们需要原子性地为某个值进行增 1 操作(假设INCR不存在)。 首先我们可能会这样做:
WATCH
check-and-set (CAS)
(null multi-bulk reply)
INCR
val = GET mykey val = val + 1 SET mykey $val
上面的这个实现在只有一个客户端的时候可以执行得很好。 但是, 当多个客户端同时对同一个键进行这样的操作时, 就会产生竞争条件。 举个例子, 如果客户端A和B都读取了键原来的值, 比如10, 那么两个客户端都会将键的值设为11, 但正确的结果应该是12 才对。 有了WATCH, 我们就可以轻松地解决这类问题了:
A
WATCH mykey val = GET mykey val = val + 1 MULTI SET mykey $val EXEC
使用上面的代码, 如果在WATCH执行之后,EXEC执行之前, 有其他客户端修改了mykey的值, 那么当前客户端的事务就会失败。 程序需要做的, 就是不断重试这个操作, 直到没有发生碰撞为止。这种形式的锁被称作乐观锁, 它是一种非常强大的锁机制。 并且因为大多数情况下, 不同的客户端会访问不同的键, 碰撞的情况一般都很少, 所以通常并不需要进行重试。
mykey
在游戏服务器中,为了节省性能,我们没有必要把所有玩家的信息都缓存到内存中。比如有一些不常登陆的玩家,那么他的信息就没必要一直呆在缓存中了,需要清除。Redis为这个功能提供了一个方法:expire,它可以为key设置以秒为单位的生命周期,比如设置为300s,那么五分钟之后,这条记录就会在内存中删除。这样不仅可以节省内存,而且增加了服务器的性能
expire
key
游戏服务器中涉及到很多排行信息,比如玩家等级排名、金钱排名、战斗力排名等。 一般情况下仅需要取排名的前N名就可以了,这时可以利用数据库的排序功能,或者自己维护一个元素数量有限的top集合。 但是有时候我们需要每一个玩家的排名,玩家的数量太多,不能利用数据库(全表排序压力太大),自己维护也会比较麻烦。 使用Redis可以很好的解决这个问题。它提供的有序Set,支持每个键值(比如玩家id)拥有一个分数(score),每次往这个set里添加元素,Redis会对其进行排序,修改某一元素的score后,也会更新排序,在获取数据时,可以指定排序范围。 更重要的是,这个排序结果会被保存起来,不用在服务器启动时重新计算。 通过它,排行榜的实时刷新、全服排行都不再成为麻烦事。
id
(score)
score
El mundo es un pañuelo.
The text was updated successfully, but these errors were encountered:
No branches or pull requests
Redis
是一个新兴的NoSql
数据缓存组件,与memcache
类似,但是功能却比memcache
多一些。首先,
Redis
和memcache
都是基于内存的,所以读取和写入速度都非常快。但是memcache
只支持简单的key-value
数据的存储方式,而Redis
对key-value ,hash,list,set,SortSet
等数据结构有很好的支持。下面就Redis在游戏的开发应用中做一些简单的介绍。
数据的缓存
在这一点上,
redis
和memcache
是一样的。都是把数据提前放入到内存中。当逻辑处理中需要用到数据时,先从内存中读取,相同的,写的时候也先向内存中写入,然后再操作数据库,以增加数据处理的速度。不同的是,redis
带有把数据写入到硬盘的功能,具体的写入策略可以在redis
的配置文件中配置。这样当主机突然出现故障时,比如断电,重启机器不会造成数据的丢失。这个在游戏的应用中特别重要。一般在游戏开发中,数据的处理会采用:缓存 + 持久化队列 + 数据库(mysql
)的架构。执行的流程是先把数据写入到缓存,然后把需要持久化的数据放入到持久化队列中,启动一个守护线程,从持久化队列中不断的取出数据,并存入或更新到数据库。如果使用memcache
这样没有写入到硬盘功能的缓存组件,出现故障时,持久人队列中如果还有没有处理完的数据,那么就会造成数据的丢失,引用玩家的数据出现短暂的回档。当然这些也可以自己开发一些功能去防止,但是增加了开发成本。不丢失数据的持久化队列的实现
上面说过
Redis
具有把数据写入到硬盘的功能,而且支持多种数据结构。那么就可以利用Redis
的list
实现持久化队列,而且当机器出现故障时,不会出现队列中数据丢失的情况,重启之后,数据会自动加载到redis
的list
之中。具体实现方法:
对并发操作的控制(跨房间)
一般来说,我们操作一个数据的流程是这样的,取出--处理---存储,这样在单线程中操作是没有任务问题的,但是在多线程环境中就不适用了,我们必须考虑数据同步的问题,保证数据操作的原子性。如果在游戏中,对玩家战队的属性进行更新,一般在数据库中都会保存一个
TeamInfo
表,里面有玩家相应的属性,比如名字,等级,金币,钻石等等。在memcache
中保存一个TeamInfo
对象,这时玩家获得金币,我们就需要取出玩家所有的属性,然后set
金币,完成后再存储整个对象。这个时候就得考虑数据的同步了,如果在操作的时候,另外一个线程B
修改了钻石,并完成了存储,而这个时候我把金币修改完成之后,再存储,这时,就出现了数据混乱的结果。考虑数据同步无非也是加锁或乐观同步。不但增加了代码量,还增加了维护的难度。而在Redis
中,它支持对hash
数据结构的操作。我们可以把玩家的对象按每个字段存储到redis
的hash
中。当我需要更新金币时,比如增加或减少,我可以使用
Redis
自带的原子操作方法:hincrby(String key,String field,int value)
进行操作,value
是正为加,是负为减,这样就简化和避免了一些并发操作,而且这个操做还减少了对数据的操作步骤,因为没有取出,再操作的过程了,只有一次写入。而且在游戏中很少一次更新非常多的字段,如果有这样的情况,下面的方法可以解决。对事务的支持
redis
提供了一个事务操作的机制,MULTI
命令用于开启一个事务,它总是返回OK
。MULTI
执行之后, 客户端可以继续向服务器发送任意多条命令, 这些命令不会立即被执行, 而是被放到一个队列中, 当EXEC
命令被调用时, 所有队列中的命令才会被执行。另一方面, 通过调用
DISCARD
, 客户端可以清空事务队列, 并放弃执行事务。以下是一个事务例子, 它原子地增加了
foo
和bar
两个键的值: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
不存在)。首先我们可能会这样做:
上面的这个实现在只有一个客户端的时候可以执行得很好。 但是, 当多个客户端同时对同一个键进行这样的操作时, 就会产生竞争条件。
举个例子, 如果客户端
A
和B
都读取了键原来的值, 比如10, 那么两个客户端都会将键的值设为11, 但正确的结果应该是12 才对。有了
WATCH
, 我们就可以轻松地解决这类问题了:使用上面的代码, 如果在
WATCH
执行之后,EXEC
执行之前, 有其他客户端修改了mykey
的值, 那么当前客户端的事务就会失败。 程序需要做的, 就是不断重试这个操作, 直到没有发生碰撞为止。这种形式的锁被称作乐观锁, 它是一种非常强大的锁机制。 并且因为大多数情况下, 不同的客户端会访问不同的键, 碰撞的情况一般都很少, 所以通常并不需要进行重试。缓存生命周期的控制
在游戏服务器中,为了节省性能,我们没有必要把所有玩家的信息都缓存到内存中。比如有一些不常登陆的玩家,那么他的信息就没必要一直呆在缓存中了,需要清除。
Redis
为这个功能提供了一个方法:expire
,它可以为key
设置以秒为单位的生命周期,比如设置为300s,那么五分钟之后,这条记录就会在内存中删除。这样不仅可以节省内存,而且增加了服务器的性能排行榜
游戏服务器中涉及到很多排行信息,比如玩家等级排名、金钱排名、战斗力排名等。
一般情况下仅需要取排名的前N名就可以了,这时可以利用数据库的排序功能,或者自己维护一个元素数量有限的top集合。
但是有时候我们需要每一个玩家的排名,玩家的数量太多,不能利用数据库(全表排序压力太大),自己维护也会比较麻烦。
使用
Redis
可以很好的解决这个问题。它提供的有序Set,支持每个键值(比如玩家id
)拥有一个分数(score)
,每次往这个set
里添加元素,Redis
会对其进行排序,修改某一元素的score
后,也会更新排序,在获取数据时,可以指定排序范围。更重要的是,这个排序结果会被保存起来,不用在服务器启动时重新计算。
通过它,排行榜的实时刷新、全服排行都不再成为麻烦事。
El mundo es un pañuelo.
The text was updated successfully, but these errors were encountered: