全局唯一ID
在使用数据库自增id时,常存在一些问题:
1、id的规律性太明显,易被盗取信息
2、受单表数据量的限制
满足以上性质提供id生成器
显然我们满足其所需要的的性质
Redis实现全局唯一id
基于上述的ID实现封装类
public class RedisIdWorker {
/**
* 开始时间戳
*/
private static final long BEGIN_TIMESTAMP = 1640995200L;
/**
* 序列号的位数
*/
private static final int COUNT_BITS = 32;
private StringRedisTemplate stringRedisTemplate;
public RedisIdWorker(StringRedisTemplate stringRedisTemplate) {
this.stringRedisTemplate = stringRedisTemplate;
}
public long nextId(String keyPrefix) {
// 1.生成时间戳
LocalDateTime now = LocalDateTime.now();
long nowSecond = now.toEpochSecond(ZoneOffset.UTC);
long timestamp = nowSecond - BEGIN_TIMESTAMP;
// 2.生成序列号
// 2.1.获取当前日期,精确到天
String date = now.format(DateTimeFormatter.ofPattern("yyyy:MM:dd"));
// 2.2.自增长
long count = stringRedisTemplate.opsForValue().increment("icr:" + keyPrefix + ":" + date);
// 3.拼接并返回
return timestamp << COUNT_BITS | count;
}
}
其中的count的生成方式值得思考
对于自增长的值,我们不可以使其随意的自增长,在后续加一个data
data可以方便我们统计当天(月 / 年)的生产量(订单量)
秒杀超卖
分析
在访问量比较大时,多线程中由于数据库的访问时间比较长,会出现库存为负数的情况
在多线程中,通常使用加锁的方式解决,但是由于悲观锁会导致性能大幅度的降低,相当于只能一个线程访问数据库,会大大影响用户体验,所以常常用乐观锁解决,关于悲观锁和乐观锁,可以去看看os
下面使用乐观锁的解决方式
很显然,这样我们可以在多线程中,既不影响用户体验,也基本解决了超卖问题,可以发现,version的作用完全可以用stock代替,所以就出现了cas法(Compare And Swap)
但是基于上述的cas法时,实践操作之后,失败率却大大提高了,原因时乐观锁的的设置需要stock要求过高,所以我们可以将stock的判断设置成大于0即可,实践操作之后发现成功率大幅度提升
下面提供代码实现
public Result seckillVoucher(Long voucherId) {
// 1.查询优惠券
SeckillVoucher voucher = seckillVoucherService.getById(voucherId);
// 2.判断秒杀是否开始
if (voucher.getBeginTime().isAfter(LocalDateTime.now())) {
// 尚未开始
return Result.fail("秒杀尚未开始!");
}
// 3.判断秒杀是否已经结束
if (voucher.getEndTime().isBefore(LocalDateTime.now())) {
// 尚未开始
return Result.fail("秒杀已经结束!");
}
// 4.判断库存是否充足
if (voucher.getStock() < 1) {
// 库存不足
return Result.fail("库存不足!");
}
// 5.扣减库存
boolean success = seckillVoucherService.update()
.setSql("stock = stock - 1") // set stock = stock - 1
.eq("voucher_id", voucherId()).gt("stock", voucher.getStock()) // where id = ? and stock > 0
.update();
if (!success) {
// 扣减失败
return Result.fail("库存不足");
}
//6.创建订单
...
return createVoucherOrder(voucherId);
}
总结
实现一人一单
== 实际业务中我们要注意以下几点情况 ==
1.首先我们想到的肯定是判断该用户是否已经下单过 -----在高并发下,一个用户如果同时发送多个请求,乐观锁的失误率也会大大增加
2.所以想到使用悲观锁控制,注意加锁的位置,如果直接加载数据库处理位置,整个程序将成为串行,会大大影响体验。
3.这里的悲观锁的位置要考虑spring的事务处理 == !!! ==
但是在集群模式下,同一个用户多次发送请求时
就需要一个锁来管理所有的jvm 对一个用户判断时,考虑所有的jvm中这个用户是否可以操作