redis

自己整理,用到啥整理啥

五大数据类型

set

常用命令

sadd <key><value1><value2> ..... 	将一个或多个 member 元素加入到集合 key 中,已经存在的 member 元素将被忽略
smembers <key>		取出该集合的所有值。
sismember <key><value>		判断集合<key>是否为含有该<value>值,有1,没有0
scard<key>		返回该集合的元素个数。

srem <key><value1><value2> .... 删除集合中的某个元素。
spop <key>	随机从该集合中吐出一个值。
srandmember <key><n>	随机从该集合中取出n个值。不会从集合中删除 。
smove <source><destination>value	把集合中一个值从一个集合移动到另一个集合
sinter <key1><key2>	返回两个集合的交集元素。
sunion <key1><key2>返回两个集合的并集元素。
sdiff <key1><key2>返回两个集合的差集元素(key1中的,不包含key2中的)

string

set   <key><value>添加键值对
get   <key>查询对应键值
append  <key><value>将给定的<value> 追加到原值的末尾,返回字符串的长度
strlen  <key>获得值的长度
setnx  <key><value>只有在 key 不存在时    设置 key 的值

incr  <key>
将 key 中储存的数字值增1
只能对数字值操作,如果为空,新增值为1

decr  <key>
将 key 中储存的数字值减1
只能对数字值操作,如果为空,新增值为-1


incrby / decrby  <key><步长>将 key 中储存的数字值增减。自定义步长。

解决秒杀问题

int uid = new Random().nextInt(10000);
//        int uid = 1000;
SessionCallback<Object> callback = new SessionCallback<Object>() {
    @Override
    public List<Object> execute(RedisOperations operations) {
        //先上乐观锁
        operations.watch("commodity:1101");
        //判断是否有货存
        String s = redisTemplate.opsForValue().get("commodity:1101");
        int anInt = Integer.parseInt(s);
        if (anInt < 1) {
            System.out.println("没货了");
            return null;
        }
        //判断用户是否存在
        Boolean member = redisTemplate.opsForSet().isMember("snapped:1101", uid + "");
        if (member) {
            System.out.println("用户已存在");
            return null;
        } else {
            operations.multi();
            operations.opsForValue().decrement("commodity:1101");
            operations.opsForSet().add("snapped:1101", uid + "");
            //运行
            return operations.exec();
        }
    }
};

List execute = (List) redisTemplate.execute(callback);
if (execute != null && execute.size() > 0) {
    System.out.println("秒杀成功");
}
System.out.println("秒杀失败");
return "";

需要注意的问题

  • springboot使用的redisTemplate进行jedis操作,与直接用jedis操作略有不同

  • 使用事务只能用SessionCallback

  • 注意逻辑,先上乐观锁,再看其他的,判断用户是否以及存在和是否有货不需要事务

    • 先上乐观锁:判断要处在乐观锁之内,如果乐观锁放在了第一个else里,会出现刚刚进去时显示有货,然后开枪导致出现负数,因为后面不保证是否有货了,而保证的是否那个时刻只有一个人可以抢上

    • 如下图,这一瞬间可能发送多次这个问题

    • 未命名

    • ==先上乐观锁好处==:因为乐观锁在所有操作之前,假设只有一个剩余了,一旦有人抢上,其他的的操作都将无效

库存遗留问题

秒杀时人多货少,大概率所有货都会卖出

但是一旦货量比较多,在使用乐观锁锁了后,会导致一瞬间结束后,因为乐观锁导致大量用户没抢上却还有存货,此事可以使用lua脚本配合使用

lua脚本

local userid=KEYS[1]; 
local prodid=KEYS[2];
local qtkey="commodity:"..prodid;
local usersKey="snapped"..prodid; 
local userExists=redis.call("sismember",usersKey,userid);
if tonumber(userExists)==1 then 
  return 2;
end
local num= redis.call("get" ,qtkey);
if tonumber(num)<=0 then 
  return 0; 
else 
  redis.call("decr",qtkey);
  redis.call("sadd",usersKey,userid);
end
return 1;

java代码 使用spring-boot-data-redis-starter

先添加一个DefaultRedisScript

@Bean
public DefaultRedisScript<Long> redisScript() {
    DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
    redisScript.setScriptSource(new StaticScriptSource(script));
    redisScript.setResultType(Long.class);
    return redisScript;
}

使用

==注意要使用long而不是integer,lua中的int对应java中的long==

Long execute = redisTemplate.execute(defaultRedisScript,List.of(uid.toString(),"1101"));

Last updated