MST

星途 面试题库

面试题:复杂业务下Redis分布式锁唯一标识的设计与实现

现在有一个复杂的电商业务场景,涉及到商品库存扣减、订单生成以及支付等多个关键操作,需要使用Redis分布式锁保证数据一致性和操作的原子性。请详细设计一个唯一标识生成与验证方案,不仅要考虑高并发、网络波动等常见问题,还要考虑业务逻辑上的耦合以及不同操作间对锁的依赖关系,并给出具体的代码实现示例(语言不限)。
34.2万 热度难度
数据库Redis

知识考点

AI 面试

面试题答案

一键面试

唯一标识生成与验证方案设计

  1. 唯一标识生成
    • UUID:通用唯一识别码(UUID)是一种由数字和字母组成的128位标识符,具有全球唯一性。在Java中可以使用java.util.UUID类来生成UUID,例如UUID.randomUUID().toString()。它不依赖于特定的业务逻辑,生成速度快,适用于高并发场景。
    • 时间戳 + 随机数:使用当前时间戳(精确到毫秒)加上一个随机数生成唯一标识。时间戳保证了标识在一定时间范围内的单调性,随机数增加了标识的唯一性。在Python中可以这样实现:
import time
import random

timestamp = int(time.time() * 1000)
random_num = random.randint(100000, 999999)
unique_id = f"{timestamp}{random_num}"
  1. 验证方案
    • Redis原子操作验证:在获取Redis分布式锁时,将生成的唯一标识作为锁的值进行设置。例如,在Java中使用Jedis客户端:
import redis.clients.jedis.Jedis;

public class RedisLockUtil {
    private static final String LOCK_SUCCESS = "OK";
    private static final String SET_IF_NOT_EXIST = "NX";
    private static final String SET_WITH_EXPIRE_TIME = "PX";

    public static boolean tryLock(Jedis jedis, String lockKey, String uniqueValue, int expireTime) {
        String result = jedis.set(lockKey, uniqueValue, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);
        return LOCK_SUCCESS.equals(result);
    }
}
- **解锁验证**:在释放锁时,需要验证当前操作的唯一标识与锁的值是否一致,防止误解锁。同样在Java中:
public static boolean unlock(Jedis jedis, String lockKey, String uniqueValue) {
    String script = "if redis.call('GET', KEYS[1]) == ARGV[1] then return redis.call('DEL', KEYS[1]) else return 0 end";
    Object result = jedis.eval(script, 1, lockKey, uniqueValue);
    return Integer.parseInt(result.toString()) == 1;
}

业务场景中的应用

  1. 商品库存扣减
    • 在扣减库存前,先获取Redis分布式锁,使用唯一标识作为锁的值。
    • 验证库存是否足够,然后进行扣减操作。
    • 操作完成后,验证唯一标识并释放锁。
import redis

r = redis.Redis(host='localhost', port=6379, db=0)

def deduct_stock(product_id, quantity, unique_id):
    lock_key = f"stock:{product_id}:lock"
    if r.set(lock_key, unique_id, nx=True, ex=10):  # 获取锁,设置过期时间10秒
        try:
            stock = int(r.get(f"product:{product_id}:stock"))
            if stock >= quantity:
                r.decrby(f"product:{product_id}:stock", quantity)
                return True
            else:
                return False
        finally:
            if r.get(lock_key).decode('utf-8') == unique_id:
                r.delete(lock_key)  # 释放锁
    else:
        return False
  1. 订单生成
    • 与库存扣减类似,获取锁并使用唯一标识验证。
    • 生成订单信息并保存到数据库。
    • 操作完成后释放锁。
import redis.clients.jedis.Jedis;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;

public class OrderService {
    private static final String DB_URL = "jdbc:mysql://localhost:3306/yourdb";
    private static final String DB_USER = "youruser";
    private static final String DB_PASSWORD = "yourpassword";

    public static boolean createOrder(String orderInfo, String uniqueId) {
        Jedis jedis = new Jedis("localhost", 6379);
        String lockKey = "order:create:lock";
        if (RedisLockUtil.tryLock(jedis, lockKey, uniqueId, 10000)) {
            try {
                Connection conn = DriverManager.getConnection(DB_URL, DB_USER, DB_PASSWORD);
                String sql = "INSERT INTO orders (order_info) VALUES (?)";
                PreparedStatement pstmt = conn.prepareStatement(sql);
                pstmt.setString(1, orderInfo);
                int rowsInserted = pstmt.executeUpdate();
                return rowsInserted > 0;
            } catch (SQLException e) {
                e.printStackTrace();
                return false;
            } finally {
                if (RedisLockUtil.unlock(jedis, lockKey, uniqueId)) {
                    jedis.close();
                }
            }
        } else {
            return false;
        }
    }
}
  1. 支付
    • 获取锁并验证唯一标识。
    • 调用支付接口进行支付操作。
    • 根据支付结果更新订单状态,操作完成后释放锁。
import requests

def process_payment(order_id, amount, unique_id):
    lock_key = f"payment:{order_id}:lock"
    if r.set(lock_key, unique_id, nx=True, ex=10):
        try:
            # 模拟支付接口调用
            response = requests.post('https://payment.gateway.com/pay', data={
                'order_id': order_id,
                'amount': amount
            })
            if response.status_code == 200:
                # 更新订单状态为已支付
                r.set(f"order:{order_id}:status", "paid")
                return True
            else:
                return False
        finally:
            if r.get(lock_key).decode('utf-8') == unique_id:
                r.delete(lock_key)
    else:
        return False

考虑因素

  1. 高并发:使用UUID或时间戳 + 随机数生成唯一标识,保证在高并发场景下的唯一性。Redis的原子操作确保锁的获取和释放是线程安全的。
  2. 网络波动:设置合理的锁过期时间,防止因网络波动导致锁无法释放。在解锁时验证唯一标识,防止误解锁。
  3. 业务逻辑耦合:每个操作(库存扣减、订单生成、支付)都独立获取和释放锁,减少业务逻辑之间的耦合。唯一标识的使用使得每个操作在锁的层面上可以独立验证和控制。
  4. 操作间对锁的依赖关系:由于不同操作可能依赖相同的资源(如库存),通过统一的锁机制和唯一标识验证,确保操作的顺序性和原子性。例如,在库存扣减完成并释放锁后,订单生成才能获取锁进行操作。