Redis事务+Redis锁机制+Redis解决秒杀问题

redis事务

redis事务简介:

redis事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。

事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。

redis事务的主要作用就是串联多个命令防止别的命令插队。

事务命令:

Multi、Exec、discard

输入 Multi 命令后,输入的命令会依次进入命令队列中(这个阶段是组队阶段);直到输入

Exec,所有输入的命令会依次执行(这个阶段是执行阶段。类似先进先出)。

在 Multi 中,如果中途不想再继续事务(或是出现了错误),可以输入 discard 命令来取消。

redis事务对错误的处理

事务的错误处理:

①组队时有任意命令报告出现错误,执行时整个队列会被取消。会返回一个nil。

②组队时没有命令报告出现错误,执 行时发现有命令报错,则错误命令不执行,其他命令照旧执行,不会回滚。

redis的命令是原子性的,事务是非原子性的。

redis事务特性

redis 事务的三特性:

单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。

没有隔离级别的概念:队列中的命令没有提交之前都不会实际被执行,因为事务提交前任何指令都不会被实际执行。

不保证原子性:事务中如果有一条命令执行失败,其后的命令仍然会被执行,没有回滚。

redis锁

事务冲突

问题:如一个账号同时发出三笔交易请求,当金额单项不超过余额,总额超过余额时。

解决方式:锁(悲观锁、乐观锁)

悲观锁:

当操作一个 key 时,给它上锁,使别人不能操作,只有当自己操作完解锁后,别人才能操作 key。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁、表锁等,读锁、写锁等,都是在操作之前先上锁。

缺点:效率低。

乐观锁:

每次拿数据都假设没有其他人修改,不去上锁,当到了更新操作,会去判断这个数据有没有在此期间被修改。

如同时操作一个数据,都会获得当前数据的版本号,当其中一个操作成功数据后,给数据加版本号,其他人修改时,发现数据版本号不一致,操作失败。抢票就是乐观锁的应用。

乐观锁适用于多读应用类型,这样可以提高吞吐量。redis就是利用这种 check-and-set 机制实现事务的。

执行 mutli 之前,先执行 watch key1 [key2],可以监视一个(或多个)key,如果在事务执行之前这个(或多个)key 被其他命令改动,事务执行将不成功。

unwatch:取消对key的所有监视。

如果在执行 watch 命令之后,exec 命令或 discard 命令先被执行了的话,那么就不需要再执行 unwatch 了。

redis实现秒杀

代码实现秒杀思想

秒杀案例:在多请求多并发情况下,会出现超卖、请求超时问题、库存遗留问题。

请求超时解决方案:用连接池(通过连接池得到 jedis 对象)

超卖解决方案:使用 watch 监视操作 key ,使用事务 multi 进行组队,用 exec 执行事务。判断exec 返回的值,如果为 null 提示秒杀失败

库存遗留解决方案:用redis lua脚本实现基于缓存的分布式锁。(乐观锁造成库存遗留、连接超时问题。redis中默认不能直接使用悲观锁。)可以用LUA脚本语言解决。

Lua脚本

Lua:

一个小巧的脚本语言,Lua脚本可以很容易的被 c/c++ 代码调用,也可以反过来调用 c/c++

的函数,Lua 并没有提供强大的库,一个完整的 Lua 解释器不过 200k,所以 Lua 不适合作

为开发独立应用程序的语言,而是作为嵌入式脚本语言。

很多应用程序、游戏使用LUA作为自己的嵌入式脚本语言,以此来实现可配置性、可扩展性。

比如魔兽世界、博德之门、愤怒的小鸟等众多游戏插件或外挂。

lua脚本 在 redis中的优势:

可以将复杂多步骤的 redis 操作,写为一个脚本,一次提交给 redis 执行,减少反复连接

redis 的次数,提升性能。

lua 脚本类似 redis 事务,有一定的原子性,不会被其他命令插队,可以完成一些 redis 事务

性的操作。

但是注意 redis 的 lua 脚本功能,只有在 redis 2.6 以上的版本才可以使用。

利用 lua 脚本淘汰用户,解决超卖问题。

redis 2.6 版本以后,通过 lua 脚本解决秒杀问题,实际上是利用 redis 单线程的特性,用

任务队列的方式解决多任务并发问题。

解决超卖、连接池超时、库存依赖问题脚本:

    local userid=keys[1];    // 传入参数
    local prodid=keys[2];
    local qtkey="sk:"..prodid.":qt";    // 拼接库存key、用户key
    local userskey="sk:"..prodid.":usr";
    # 调用redis中sismenber
    local userExists=redis.call("sismenber",userskey,userid);    
    if tonumber(userExists)==1 then    # 看对应用户是否已经买过商品
        return 2;    # 已经秒杀到商品,不能再买第二次
    end
    # 调用redis中get 方法
    local num=redis.call("get",qtkey);
    if tonumber(num)<=0 then    # 看 num 是否小于等于0
        return 0;    # 满足条件,返回0
    else
        redis.call("decr",qtkey);    # 库存减一
        redis.call("sadd",userskey,userid);    
    end
    return 1;    # 表示秒杀成功
Redis事务+Redis锁机制+Redis解决秒杀问题_第1张图片

java 类调用:

    # 建连接池
    JedisPoll jedisPool = JedisPoolUtil.getJedisPoolInstance();
    Jedis jedis = jedisPool.getResource();
    # 调用lua脚本 LuaScript中存放lua脚本(LuaScript = "lua脚本内容")
    String shal = jedis.scriptLoad(LuaScript);
    Object result = jedis.evalsha(shal,2,userid,prodid);
Redis事务+Redis锁机制+Redis解决秒杀问题_第2张图片

你可能感兴趣的:(redis,redis,数据库)