先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7
深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年最新大数据全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上大数据知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新
如果你需要这些资料,可以添加V获取:vip204888 (备注大数据)
正文
*/
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;
}
}
>
> **测试存入Redis**
>
>
>
@Autowired
private RedisIdWorker redisIdWorker;
private ExecutorService es = Executors.newFixedThreadPool(500);
@Test
public void testWorkerId() throws InterruptedException {
CountDownLatch latch = new CountDownLatch(300);
Runnable task = () -> {
for (int i = 0; i < 100; i++) {
long id = redisIdWorker.nextId(“order”);
System.out.println("id = " + id);
}
latch.countDown();
};
long begin = System.currentTimeMillis();
for (int i = 0; i < 300; i++) {
es.submit(task);
}
latch.await();
long end = System.currentTimeMillis();
System.out.println("times = " + (end- begin));
}
**这里用到了 CountDownlatch,简单的介绍一下:**
**CountDownLatch**名为信号枪:主要的作用是**同步协调在多线程的等待于唤醒问题**
我们如果没有CountDownLatch ,那么由于程序是异步的,当异步程序没有执行完时,主线程就已经执行完了,然后我们期望的是分线程全部走完之后,主线程再走,所以我们此时需要使用到CountDownLatch
CountDownLatch 中有两个最重要的方法
* countDown
* await
**await 是阻塞方法,**我们担心线程没有执行完时,main线程就执行,所以可以**使用await就阻塞主线程**, 那么什么时候main线程不在阻塞呢? **当 CountDownLatch 内部维护的变量为0时,就不再阻塞,直接放行**。
什么时候 **CountDownLatch 维护的变量变为0 呢,我们只需要调用一次countDown ,内部变量就减少1**,我们让分线程和变量绑定, 执行完一个分线程就减少一个变量,当分线程全部走完,CountDownLatch 维护的变量就是0,此时await就不再阻塞,统计出来的时间也就是所有分线程执行完后的时间。
## 二、环境准备
**需要搭建登录环境,`基础环境代码和sql文件`均已上传 GitCode 链接:[基础环境和SQL]( )**
## 三、实现秒杀下单
>
> **添加优惠卷**
>
>
>
**VoucherServiceImpl 核心代码**
@Service
public class VoucherServiceImpl extends ServiceImpl<VoucherMapper, Voucher> implements IVoucherService {
// 该类无代码,直接MyBatis-Plus继承实现类 即可,自动完成持久化
@Autowired
private ISeckillVoucherService seckillVoucherService;
@Override
public ResultBean<List<Voucher>> queryVoucherOfShop(Long shopId) {
// 查询优惠券信息
List<Voucher> vouchers = getBaseMapper().queryVoucherOfShop(shopId);
// 返回结果
return ResultBean.create(0, "success", vouchers);
}
@Override
public void addSeckillVoucher(Voucher voucher) {
// 保存优惠券
save(voucher);
// 保存秒杀信息
SeckillVoucher seckillVoucher = new SeckillVoucher();
seckillVoucher.setVoucherId(voucher.getId());
seckillVoucher.setStock(voucher.getStock());
seckillVoucher.setBeginTime(voucher.getBeginTime());
seckillVoucher.setEndTime(voucher.getEndTime());
seckillVoucherService.save(seckillVoucher);
}
}
**VoucherController 接口层**
@RestController
@CrossOrigin
@RequestMapping(“/voucher”)
public class VoucherController {
@Autowired
private IVoucherService voucherService;
/\*\*
* 新增秒杀券
* @param voucher 优惠券信息,包含秒杀信息
* @return 优惠券id
*/
@PostMapping(“seckill”)
public ResultBean addSeckillVoucher(@RequestBody Voucher voucher) {
voucherService.addSeckillVoucher(voucher);
return Result.ok(voucher.getId());
}
}
>
> **编写下单业务**
>
>
>
**VoucherOrderServiceImpl 优惠卷订单核心业务类**
@Service
public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService {
@Autowired
private ISeckillVoucherService seckillVoucherService;
@Autowired
private RedisIdWorker redisIdWorker;
@Override
@Transactional
public Result seckillVoucher(Long voucherId) {
//1. 查询优惠卷
SeckillVoucher seckillVoucher = seckillVoucherService.getById(voucherId);
//2. 判断秒杀是否开始 开始时间大于当前时间表示未开始抢购
if (seckillVoucher.getBeginTime().isAfter(LocalDateTime.now())) {
return Result.fail("秒杀尚未开始!");
}
//3. 判断秒杀是否结束
if (seckillVoucher.getEndTime().isBefore(LocalDateTime.now())) {
return Result.fail("秒杀已经结束!");
}
//4. 判断库存是否充足
if (seckillVoucher.getStock() < 1) {
return Result.fail("库存不足!");
}
Long userId = UserHolder.getUser().getId();
//5. 查询订单
//5.1 查询订单
int count = query().eq("user\_id", userId).eq("voucher\_id", voucherId).count();
//5.2 判断并返回
if (count > 0) {
return Result.fail("用户已经购买过!");
}
//6. 扣减库存
boolean success = seckillVoucherService.update().setSql("stock = stock -1")
.eq("voucher\_id", voucherId).update();
if (!success) {
return Result.fail("库存不足!");
}
//7. 创建订单
VoucherOrder voucherOrder = new VoucherOrder();
long orderId = redisIdWorker.nextId("order");
voucherOrder.setId(orderId);
voucherOrder.setUserId(userId);
voucherOrder.setVoucherId(voucherId);
save(voucherOrder);
//8. 返回订单id
return Result.ok(orderId);
}
}
**VoucherOrderController 接口层**
@RestController
@CrossOrigin
@RequestMapping(“/voucher_order”)
public class VoucherOrderController {
@Autowired
private IVoucherOrderService voucherOrderService;
@PostMapping("seckill/{id}")
public Result seckillVoucher(@PathVariable("id") Long voucherId) {
return voucherOrderService.seckillVoucher(voucherId);
}
}
>
> **测试抢购秒杀优惠卷**
>
>
>
>
> **ApiFox 新增以下接口**
>
>
>
**添加秒杀卷**
![在这里插入图片描述](https://img-blog.csdnimg.cn/edb6602eafbb4d46802767ffbf381b93.png#pic_center)
**测试返回成功即可。**
**抢购秒杀优惠卷接口**
![在这里插入图片描述](https://img-blog.csdnimg.cn/51f3f7c487314ec2b3f5c3f526987009.png#pic_center)
**测试无误,抢购成功!**
## 四、库存超卖问题
### ⏳问题分析
有关超卖问题分析:在我们原有代码中是这么写的
if (voucher.getStock() < 1) {
// 库存不足
return Result.fail(“库存不足!”);
}
//5,扣减库存
boolean success = seckillVoucherService.update()
.setSql(“stock= stock -1”)
.eq(“voucher_id”, voucherId).update();
if (!success) {
//扣减库存
return Result.fail(“库存不足!”);
}
假设线程1过来查询库存,判断出来库存大于1,正准备去扣减库存,但是还没有来得及去扣减,此时线程2过来,线程2也去查询库存,发现这个数量一定也大于1,那么这两个线程都会去扣减库存,最终多个线程相当于一起去扣减库存,此时就会出现库存的超卖问题。
![在这里插入图片描述](https://img-blog.csdnimg.cn/8365d26021ce4721a7c3719f440b744e.png#pic_center)
**超卖问题是典型的多线程安全问题,** 这种情况下**常见的解决方案就是 加 锁**:而对于加锁,我们**通常有两种解决方案**:
![在这里插入图片描述](https://img-blog.csdnimg.cn/91f627935f114893a6a45efdf162b8c5.png#pic_center)
**悲观锁:**
悲观锁**可以实现对于数据的串行化执行**,比如syn,和lock都是悲观锁的代表,同时,**悲观锁中又可以再细分为公平锁,非公平锁,可重入锁,等等**
**乐观锁:**
**会有一个版本号,每次操作数据会对版本号+1,再提交回数据时,会去校验是否比之前的版本大1 ,如果大1 ,则进行操作成功**,这套机制的核心逻辑在于,\*\*如果在操作过程中,版本号只比原来大1 ,那么就意味着操作过程中没有人对他进行过修改,他的操作就是安全的,\*\*如果不大1,则数据被修改过,当然乐观锁还有一些变种的处理方式比如cas
乐观锁的典型代表:就是**CAS**,利用CAS**进行无锁化机制加锁**,varNum是操作前读取的内存值,while中的var1+var2 是预估值,如果预估值 == 内存值,则代表中间没有被人修改过,此时就将新值去替换 内存值
其中do while 是为了在操作失败时,再次进行自旋操作,即把之前的逻辑再操作一次。
int varNum;
do {
varNum = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
我们**采用的方式为:**
在操作时,**对版本号进行+1 操作,然后要求version 如果是1 的情况下,才能操作**,那么第一个线程在操作后,数据库中的version变成了2,但是他自己满足version=1 ,所以没有问题,此时线程2执行,**线程2 最后也需要加上条件version =1 ,但是现在由于线程1已经操作过了,所以线程2,操作时就不满足version=1 的条件了,所以线程2无法执行成功**
![在这里插入图片描述](https://img-blog.csdnimg.cn/4295db7b1b4a4dc5a621507a65f61961.png#pic_center)
### ⌚ 乐观锁解决库存超卖
**加入以下代码解决超卖问题**
之前的方式要修改前后都保持一致,但是这样我们分析过,成功的概率太低,所以我们的乐观锁需要变一下,改成stock大于0 即可
boolean success = seckillVoucherService.update()
.setSql(“stock= stock -1”)
.eq(“voucher_id”, voucherId).update().gt(“stock”,0); //where id = ? and stock > 0
**知识拓展**
针对**CAS中的自旋压力过大,我们可以使用Longaddr这个类去解决**
Java8 **提供的一个对AtomicLong改进后的一个类,LongAdder**
**大量线程并发更新一个原子性的时候,天然的问题就是自旋**,会导致并发性问题,当然这也比我们直接使用syn来的好
所以利用这么一个类,LongAdder来进行优化
如果获取某个值,**则会对cell和base的值进行递增,最后返回一个完整的值**
![在这里插入图片描述](https://img-blog.csdnimg.cn/206944222f9541f491c5538c6d289896.png#pic_center)
**以上的解决方式,依然有些问题,下面使用Jmeter进行测试**
### ✅Jmeter 测试
**添加线程组**
![在这里插入图片描述](https://img-blog.csdnimg.cn/900592dfc72c41089c0bee153bc4d8b0.png#pic_center)
**添加JSON断言,我们认为返回结果为false的就是请求失败**
在线程组右击选择断言 --> JSON 断言
![在这里插入图片描述](https://img-blog.csdnimg.cn/031068a41d4c4567a99290f8c2de24ab.png#pic_center)
**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**
**需要这份系统化的资料的朋友,可以添加V获取:vip204888 (备注大数据)**
![img](https://img-blog.csdnimg.cn/img_convert/c6888b67741d653635a7aae2ebc433e6.png)
**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**
c41089c0bee153bc4d8b0.png#pic_center)
**添加JSON断言,我们认为返回结果为false的就是请求失败**
在线程组右击选择断言 --> JSON 断言
![在这里插入图片描述](https://img-blog.csdnimg.cn/031068a41d4c4567a99290f8c2de24ab.png#pic_center)
**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**
**需要这份系统化的资料的朋友,可以添加V获取:vip204888 (备注大数据)**
[外链图片转存中...(img-nStDQU4w-1713402347132)]
**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**