唯一标识生成与验证方案设计
- 唯一标识生成:
- 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}"
- 验证方案:
- 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;
}
业务场景中的应用
- 商品库存扣减:
- 在扣减库存前,先获取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
- 订单生成:
- 与库存扣减类似,获取锁并使用唯一标识验证。
- 生成订单信息并保存到数据库。
- 操作完成后释放锁。
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;
}
}
}
- 支付:
- 获取锁并验证唯一标识。
- 调用支付接口进行支付操作。
- 根据支付结果更新订单状态,操作完成后释放锁。
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
考虑因素
- 高并发:使用UUID或时间戳 + 随机数生成唯一标识,保证在高并发场景下的唯一性。Redis的原子操作确保锁的获取和释放是线程安全的。
- 网络波动:设置合理的锁过期时间,防止因网络波动导致锁无法释放。在解锁时验证唯一标识,防止误解锁。
- 业务逻辑耦合:每个操作(库存扣减、订单生成、支付)都独立获取和释放锁,减少业务逻辑之间的耦合。唯一标识的使用使得每个操作在锁的层面上可以独立验证和控制。
- 操作间对锁的依赖关系:由于不同操作可能依赖相同的资源(如库存),通过统一的锁机制和唯一标识验证,确保操作的顺序性和原子性。例如,在库存扣减完成并释放锁后,订单生成才能获取锁进行操作。