MST
星途 面试题库

面试题:如何利用Redis EVALSHA命令优化高并发场景下的资源竞争问题

假设在一个高并发的抢购系统中,商品库存存储在Redis中,描述如何通过Redis EVALSHA命令结合Lua脚本来避免超卖问题,包括Lua脚本的大致逻辑、EVALSHA命令的参数传递及可能遇到的问题与解决方案。
37.7万 热度难度
数据库Redis

知识考点

AI 面试

面试题答案

一键面试
  1. Lua脚本大致逻辑
    • 获取商品库存:使用redis.call('GET', key)获取商品库存键对应的值,这个值表示当前商品的剩余库存。
    • 判断库存是否足够:通过if tonumber(stock) >= 1 then判断库存是否大于等于1(假设每次抢购1个商品)。
    • 库存足够时减库存:如果库存足够,使用redis.call('DECR', key)减少库存。
    • 返回结果:库存足够返回1(表示抢购成功),库存不足返回0(表示抢购失败)。 示例Lua脚本如下:
    local key = KEYS[1]
    local stock = redis.call('GET', key)
    if tonumber(stock) >= 1 then
        redis.call('DECR', key)
        return 1
    else
        return 0
    end
    
  2. EVALSHA命令的参数传递
    • 计算脚本的SHA1值:在客户端使用redis-cli --eval script.lua key, arg命令(假设脚本保存在script.lua文件中),或者在编程语言中通过相应的Redis库函数获取脚本的SHA1值。例如在Python中使用redis - py库:
    import redis
    r = redis.Redis()
    script = """
    local key = KEYS[1]
    local stock = redis.call('GET', key)
    if tonumber(stock) >= 1 then
        redis.call('DECR', key)
        return 1
    else
        return 0
    end
    """
    sha1 = r.script_load(script)
    
    • 使用EVALSHA命令:在高并发场景下,使用EVALSHA sha1 numkeys key [key...] arg [arg...]格式调用。其中sha1是前面计算得到的脚本SHA1值,numkeys是键参数的数量(通常为1,因为只有商品库存这一个键),key是商品库存的键名,arg这里没有用到(因为逻辑简单,不需要额外参数)。例如在Python中:
    result = r.evalsha(sha1, 1, 'product_stock')
    
  3. 可能遇到的问题与解决方案
    • 脚本未加载问题:如果在执行EVALSHA命令时,Redis服务器上还未加载该脚本(可能由于重启等原因),会返回NOSCRIPT错误。解决方案是在捕获到NOSCRIPT错误时,重新使用SCRIPT LOAD命令加载脚本,然后再次执行EVALSHA。例如在Python中:
    try:
        result = r.evalsha(sha1, 1, 'product_stock')
    except redis.ResponseError as e:
        if e.args[0].startswith('NOSCRIPT'):
            r.script_load(script)
            result = r.evalsha(sha1, 1, 'product_stock')
    
    • 并发控制不当:虽然Lua脚本在Redis中是原子执行的,但如果多个客户端同时获取到库存足够的判断,然后由于网络延迟等原因,库存减少操作在不同时间点执行,可能还是会导致超卖。解决方案是确保所有的库存操作都通过这个Lua脚本执行,避免在其他地方对库存进行操作。
    • 库存数据类型问题:如果库存初始值设置不是数字类型,Lua脚本中tonumber(stock)会返回nil,导致逻辑错误。解决方案是在初始化库存时,确保使用正确的数字类型设置库存值,例如在Python中使用r.set('product_stock', 100, ex = 3600)设置初始库存为100且设置过期时间(如果有需求)。