学习资料声明
黑马程序员的Redis学习视频:黑马程序员Redis入门到实战教程
需要用到的知识:linux(推荐韩顺平老师的教程,学到p30,创建好虚拟机和简单的几个命令就好。)SSM。SpringBoot。
还用到了MybatisPlus(还没学。)
全局唯一ID
加锁解决超卖问题
集群:一人一单并发安全问题
有多个JVM,每个JVM都有自己的锁管理。
如何让多个JVM使用一个锁监视器。
redis实现分布式锁
获取锁和释放锁 setnx,del。如何实现安全性,设置超时时间。但是NX和EX分开会导致错误,所以用SET替代。
Q1:释放锁加强:增加标识,在释放锁之前进行验证,保证释放的是自己的锁。
为什么会释放别人的锁,因为我们设置的key是基于线程id来构造的,集群情况下会出现线程id一样的情况,所以会出现释放别人锁的情况。因此,使用uuid作为标识。(value的比较)
Q2:这个点没看懂,为什么能在超时释放锁的时候获取锁?redis事务确实不能保证原子性
lua脚本实现原子化。
redisson分布式锁的原理
如何解决主从一致的问题?
方法一,取消主从机制,向所有节点发送获取锁请求,只有从所有节点都得到锁算是真正的获取锁。multilock。联锁
优化秒杀功能
解决思路:设置两个部分,验证资格和写数据库。异步执行。
在这一部分实现的代码:
@Slf4j
@Service
public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService {
@Resource
private ISeckillVoucherService seckillVoucherService;
@Resource
private RedisIdWorker redisIdWorker;
@Resource
private StringRedisTemplate stringRedisTemplate;
@Resource
private RedissonClient redissonClient;
private static final DefaultRedisScript<Long> SECKILL_SCRIPT;
static{
SECKILL_SCRIPT = new DefaultRedisScript<>();
SECKILL_SCRIPT.setLocation(new ClassPathResource("seckill.lua"));
SECKILL_SCRIPT.setResultType(Long.class);
}
//阻塞队列
private BlockingQueue<VoucherOrder> orderTasks = new ArrayBlockingQueue<>(1024*1024);
//线程池
private static final ExecutorService SECKILL_ORDER_EXCUTOR = Executors.newSingleThreadExecutor();
//PostConstruct满足在大类启动后,里面执行init
@PostConstruct
private void init(){
SECKILL_ORDER_EXCUTOR.submit(new VoucherOrderHandler());
}
//放在阻塞队列里面的?
private class VoucherOrderHandler implements Runnable{
@Override
public void run() {
try {
VoucherOrder voucherOrder = orderTasks.take();
handleVoucherOrder(voucherOrder);
} catch (Exception e) {
log.error("处理订单异常", e);
}
}
}
private void handleVoucherOrder(VoucherOrder voucherOrder) {
Long userId = voucherOrder.getUserId();
RLock lock = redissonClient.getLock("lock:order:" + userId);
boolean isLock = lock.tryLock();
if (!isLock){
log.error("不允许重复下单!");
return;
}
try{
proxy.createVoucherOrder(voucherOrder);
}finally {
lock.unlock();
}
}
private IVoucherOrderService proxy;
@Override
public Result seckillVoucher(Long voucherId) {
Long userId = UserHolder.getUser().getId();
Long result = stringRedisTemplate.execute(
SECKILL_SCRIPT,
Collections.emptyList(),
voucherId.toString(),
userId.toString()
);
int r = result.intValue();
if (r != 0){
return Result.fail(r==1 ? "库存不足":"不能重复下单");
}
//返回值为0,证明可以下单。
VoucherOrder voucherOrder = new VoucherOrder();
long orderId = redisIdWorker.nextId("order");
voucherOrder.setId(orderId);
voucherOrder.setUserId(userId);
voucherOrder.setVoucherId(voucherId);
save(voucherOrder);
//阻塞队列,什么叫做阻塞队列?就是当有人向队列要元素的时候,队列为空,那么就等待,等待有元素才行
orderTasks.add(voucherOrder);
//获取代理对象,因为接下来子线程要处理阻塞队列的内容,所以子线程无法取得父线程的代理。
proxy = (IVoucherOrderService) AopContext.currentProxy();
return Result.ok(orderId);
}
@Transactional
public void createVoucherOrder(VoucherOrder voucherOrder){
Long userId = voucherOrder.getUserId();
int count = query().eq("user_id", userId).eq("voucher_id", voucherOrder.getVoucherId()).count();
if (count > 0) {
log.error("用户已经购买过一次了!");
}
boolean success = seckillVoucherService.update()
.setSql("stock = stock -1")
.eq("voucher_id", voucherOrder.getVoucherId()).gt("stock", 0)
.update();
if (!success){
log.error("库存不足");
}
save(voucherOrder);
}
}
优缺点:
这一部分,自己的错误,主要集中在lua脚本上。之后的学习,真的不要再偷懒不跟着测试功能了。
消息队列