搜索

首页  >  问答  >  正文

spring-data-redis - redis并发计数

说明:
用redis计数器存取用户的操作次数,最多3次,但是并发会超过3次,下面的代码对吗?

BoundValueOperations<String, String> operations = redisTemplate.boundValueOps("key1");
        String key1 = operations.get();
        if (StringUtils.isEmpty(key1)) {
            service.do();//这里是业务逻辑操作成功之后,计数器加1
            operations.increment(1);
        } else {
            if (Integer.parseInt(key1) < 2) {
                service.do();//这里是业务逻辑操作成功之后,计数器加1
                operations.increment(1);
            }
        }
phpcn_u1582phpcn_u15822798 天前1141

全部回复(2)我来回复

  • 世界只因有你

    世界只因有你2017-04-25 09:05:18

    对于这种计数的业务,应当先计数,再做业务。如果业务完成,发现计数器已经大于限定值了,就傻眼了。

    我的方案是:
    由于是并发导致的数据不一致,可以考虑用 Redis 的 INCR 命令增 1,并获得最新值(这是一次操作,所以不会造成不一致):

    • 如果大于 3,说明已经超过了,结束;

    • 小于等于3,再执行业务(可能还要考虑业务执行失败回滚计数器)。

    回复
    0
  • ringa_lee

    ringa_lee2017-04-25 09:05:18

    通过java客户端实现上述功能有一定的缺陷,在高并发的情况下,数据可能有不一致的情况;建议使用lua脚本封装整个逻辑,保证操作的原子性;可以通过SCRIPT LOAD的方式将脚本缓存到服务器,通过sha1校验值+参数(Key,ARG)来执行,减轻网络传输;

    给你个示例:
    1.功能需求:key如果不存在,则set并返回false,并设置超时时间和count=1;达到超时时间或者达到指定的count数,返回false 并重新设置超时时间和count=1;如果没有达到超时时间 & 没有达到指定的count,返回true,并incr count

    redis.lua脚本如下:

    local key = KEYS[1];
    local expire = tonumber(KEYS[2]);
    local number = tonumber(KEYS[3]);
    local count = tonumber(redis.call("GET",key));
    if count == nil then
            redis.call("SETEX",KEYS[1],expire,"1");
            return false;
    else
            if count +1 >= number then;
                    redis.call("SETEX",KEYS[1],expire,"1");
                    return false;
            else
                    redis.call("INCR",KEYS[1]);
                    return true;
            end
    end
    

    java代码如下:

       public static final String REDIS_LUA = "redis.call('select',1);local key = KEYS[1];local expire = tonumber(KEYS[2]);" +
                "local number = tonumber(KEYS[3]);local count = tonumber(redis.call('GET',key));" +
                "if count == nil then redis.call('SETEX',KEYS[1],expire,'1');return 0;" +
                "else if count +1 >= number then redis.call('SETEX',KEYS[1],expire,'1');return 0;" +
                "else redis.call('INCR',KEYS[1]);return 1;end;end;";
        
        private static final String redisScript;
        
    
        static {
            try {
                Conf.load();
            } catch (Exception e) {
                e.printStackTrace();
            }
            jedisPool = new JedisPool(Conf.getRedisHost(), Conf.getRedisPort());
            redisScript = loadLuaScript();
        }
       private static String loadLuaScript() {
            Jedis jedis = null;
            String redisScript = null;
            try {
                jedis = jedisPool.getResource();
                redisScript = jedis.scriptLoad(REDIS_LUA);
            } catch (Exception e) {
                if (jedis != null) {
                    jedisPool.returnResource(jedis);
                }
            } finally {
                if (jedis != null) {
                    jedisPool.returnResource(jedis);
                }
            }
            return redisScript;
        }
        
        
      
    调用脚本
     Object result = jedis.evalsha(redisScript, ARGS_LENGTH
                        , key, expireTime, countVal);

    回复
    0
  • 取消回复