1. 全局唯一ID
订单id Redis自增策略
2. 库存超卖问题
乐观锁
版本号法,前后比较数据的版本号(开始时读取and修改时)
CAS法,直接用数据当作判断依据
缺点:失败率过高
可以通过直接在操作数据库的sql语句中加入库存大于0的判断直接解决库存超卖
*事务失效
使用 AopContext.currentProxy()
和 通过依赖注入调用当前类的代理对象
*.1 使用 AopContext.currentProxy() 解决
可以使用AopContext.currentProxy()
来获取当前类的代理对象,并调用接口中的事务方法。这样可以确保事务生效。以下是一个示例:
假设你有一个接口MyService
和它的实现类MyServiceImpl
:
methodB
通过 AopContext.currentProxy()
获取当前代理对象,并调用 methodA
,确保事务生效。
- 动态获取代理对象:在运行时动态获取当前代理对象,适用于需要在方法内部获取代理对象的场景。
*.2 依赖注入解决
区别总结
- 配置要求:
AopContext.currentProxy()
需要额外的配置,而依赖注入方式不需要。 - 代码可读性:依赖注入方式通常更直观和易读。
- 灵活性:
AopContext.currentProxy()
在某些情况下可能更灵活,因为它可以在运行时动态获取代理对象。
3. 分布式锁
构造函数传入服务名称和依赖
key:锁前缀+服务名
KEY_PREFIX + name
value: JVM 实例生成UUID+线程ID
要防止不同 JVM 中的不同线程获取相同的锁
ID_PREFIX 是通过 UUID.randomUUID().toString(true) + "-" 生成的。这确保了每个 JVM 实例在启动时生成一个唯一的前缀。
Thread.currentThread().getId() 获取当前线程的 ID。
组合 ID_PREFIX 和线程 ID,形成一个唯一的线程标识符 threadId。
ID_PREFIX + Thread.currentThread().getId()
完整代码:
public class SimpleRedisLock implements ILock {
private String name;
private StringRedisTemplate stringRedisTemplate;
public SimpleRedisLock(String name, StringRedisTemplate stringRedisTemplate) {
this.name = name;
this.stringRedisTemplate = stringRedisTemplate;
}
private static final String KEY_PREFIX = "lock:";
private static final String ID_PREFIX = UUID.randomUUID().toString(true) + "-";
private static final DefaultRedisScript<Long> UNLOCK_SCRIPT;
static {
UNLOCK_SCRIPT = new DefaultRedisScript<>();
UNLOCK_SCRIPT.setLocation(new ClassPathResource("unlock.lua"));
UNLOCK_SCRIPT.setResultType(Long.class);
}
@Override
public boolean tryLock(long timeoutSec) {
// 获取线程标示
String threadId = ID_PREFIX + Thread.currentThread().getId();
// 获取锁
Boolean success = stringRedisTemplate.opsForValue()
.setIfAbsent(KEY_PREFIX + name, threadId, timeoutSec, TimeUnit.SECONDS);
return Boolean.TRUE.equals(success);
}
@Override
public void unlock() {
// 调用lua脚本
stringRedisTemplate.execute(
UNLOCK_SCRIPT,
Collections.singletonList(KEY_PREFIX + name),
ID_PREFIX + Thread.currentThread().getId());
}
}
lua:
-- 比较线程标示与锁中的标示是否一致
if(redis.call('get', KEYS[1]) == ARGV[1]) then
-- 释放锁 del key
return redis.call('del', KEYS[1])
end
return 0
3.1 获取锁
注意:Boolean 是包装类,返回值是 boolean 基本类型,转换会自动拆箱,在进行自动拆箱时,如果包装类对象为 null ,会抛出 NullPointerException,所以使用equals()。
3.2 释放锁
*Lua脚本
通过 Lua 脚本确保获取锁的值和删除锁的操作是原子的,防止了多个线程或进程之间的竞争:
静态变量静态代码块(防止每次调用都需要读取Lua脚本):
使用 stringRedisTemplate.execute
方法来执行 Lua 脚本: