美文网首页Java游戏服务器开发
游戏服务器缓存之Redis缓存正确使用

游戏服务器缓存之Redis缓存正确使用

作者: 王广帅 | 来源:发表于2020-01-12 14:33 被阅读0次

    在游戏服务器开发中,为了更快速的获取游戏玩家的数据,一般都会把数据存储在Redis之中,做为一级缓存。数据加载的过程是一般是这样的:

    1. 先从Redis中获取数据库。如果有数据直接返回,不用再查询数据库。
    2. 如果Redis没有数据,再查询数据库,将查询到的数据先缓存到Redis一份,再返回给调用者。
    3. 如果数据库也没有数据,直接返回null。
      我在刚开始使用Redis做游戏服务器缓存的时候,就是这样做的。但是随着工作经验的积累和解决的Bug越来越多,如果代码实现的不够慎密,还是会出现各种问题的,而且现在面试官也喜欢问关于缓存的问题。做为做缓存,一般要解决的就是两个大问题。

    缓存穿透

    所谓的缓存穿透就是发生在第一步上面:先从缓存查询,如果缓存不存在,再查询数据库。这个时候,如果某些数据是一份公共数据,很多游戏玩家并发来查询,都去redis查了一下,发现没有,就都去数据库查。这个时候,压力就全部在数据库上面了。如果数据库扛着住还好,如果扛不住,就有可能导致数据库超载。
    解决缓存穿透的方法也很简单,在收到第一次查询redis时,如果redis查询出来为空,这个时间对从数据库查询的操作加锁,如果从数据库查出来了,并缓存到了redis之中,这时别的查询操作就可以从redis中获取数据了。如果从数据库查出来也是空的,这个时候,可以给redis提供一个默认值,这样,其它查询出来的值就是这个默认值 ,如果判断是默认值,表示不有数据,返回null,这样也不会再查数据库了。

    缓存雪崩(缓存击穿)

    这种现象也是出现在大并发的情景下。比如Redis缓存了很多玩家的活动数据,但是这一大批玩家很长时间都没有登录了,而在redis中的活跃数据已过期,被redis自动删除了。突然间,运营又搞了一个老玩家拉回的活动,很多长时间不登录的人又都回来登录游戏了,这个时候,一大批用户的活动数据需要会从数据库拉取。极端情况导致数据库超载。

    解决这个问题的方法

    1. 缓存的过期时间可以稍微长一些,长时间真正流失的玩家可能也不会回来了。
    2. 对数据库的操作需要添加限流,这个一般的数据库连接池已经做了,可以指定同一时间内,最多有多少连接操作数据库。

    提供一个防止缓存穿透的方法

    package com.mygame.redis;
    
    import java.time.Duration;
    import java.util.function.Function;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.data.redis.core.StringRedisTemplate;
    import org.springframework.stereotype.Service;
    
    /**
     * 
     * @ClassName: RedisCacheTemplate
     * @Description: 这是一个redis缓存的模板。在redis做为缓存的时候,需要防止缓存的雪崩,穿透
     * @author: wang guang shuai
     * @date: 2020年1月9日 下午5:11:53
     */
    @Service
    public class RedisCacheTemplate {
        private static final String DefaultRedisNullValue = "#-#";
        @Autowired
        private StringRedisTemplate redisTemplate;
        /**
         * 
         * <p>
         * Description:第一次会从redis中获取,如果redis中没有此值,从db中获取
         * </p>
         * 
         * @param redisKey
         * @param param
         * @param duration
         * @param selectFromDB
         * @return
         * @author wang guang shuai
         * @date 2020年1月9日 下午8:07:52
         *
         */
        public String getValue(String redisKey, String param, Duration duration, Function<String, String> selectFromDB) {
            String value = redisTemplate.opsForValue().get(redisKey);
            if (value == null) {
                // 加锁,防止缓存穿透和击穿
                synchronized (redisKey.intern()) {
                    // 二次检测
                    value = redisTemplate.opsForValue().get(redisKey);
                    if (value == null) {// 如果等于空,从数据库取
                        value = selectFromDB.apply(param);//redis没有,从数据库获取
                        if (value == null) {// 如果数据库还是没有,说明是真的没有,添加空标记,将来有值时,会覆盖掉
                            value = DefaultRedisNullValue;
                        }
                        // 将取到的值缓存到redis中。
                        if (duration != null) {
                            redisTemplate.opsForValue().set(redisKey, value, duration);
                        } else {
                            redisTemplate.opsForValue().set(redisKey, value);
                        }
                    }
                }
            }
            if (value.equals(DefaultRedisNullValue)) {
                return null;
            }
            return value;
        }
    }
    
    

    这个方法提供了一个函数方法参数,如果从redis中加载不到,就从这个接口中获取,具体的实现可以由业务决定,比如从数据库,或第三方服务之类的。

    欢迎关注

    相关文章

      网友评论

        本文标题:游戏服务器缓存之Redis缓存正确使用

        本文链接:https://www.haomeiwen.com/subject/whnpactx.html