黑马点评项目遇到的部分问题

目录

  • [1. Invalid default value for 'begin_time'报错](#1. Invalid default value for ‘begin_time‘报错)
  • [2. [ThreadLocal](https://blog.csdn.net/u010445301/article/details/111322569)](#2. ThreadLocal)
  • [3. 悲观锁实现单体一人一单超卖问题](#3. 悲观锁实现单体一人一单超卖问题)
  • [4. redisson](#4. redisson)
  • [5. 回顾秒杀优化](#5. 回顾秒杀优化)
  • [6. Nginx 负载均衡](#6. Nginx 负载均衡)

1. Invalid default value for 'begin_time'报错

  • mysql⽇期时间设置默认0000-00-0000:00:00出错。
    • DEFAULT '0000-00-00 00:00:00'(零时间戳),这不满足sql_mode中的NO_ZERO_DATE而报错。
    • sql_mode有两种,一种是空值,一种是严格模式,会给出很多默认设置。在MySQL5.7之后默认使用严格模式。
    • NO_ZERO_DATE:若设置该值,MySQL数据库不允许插入零日期,插入零日期会抛出错误而不是警告。
    • 在命令行中设置sql_mode:
      SET SESSION sql_mode = 'ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION';

2. ThreadLocal

  • remove 方法,直接将 ThreadLocal 对应的值从当前线程 Thread 中的 ThreadLocalMap 中删除。为什么要删除,这涉及到内存泄漏的问题。
  • ThreadLocalMap 中使用的 key 为 ThreadLocal 的弱引用, 弱引用的特点是,如果这个对象只存在弱引用,那么在下一次垃圾回收的时候必然会被清理掉。
  • 所以如果 ThreadLocal 没有被外部强引用的情况下,在垃圾回收的时候会被清理掉的,这样一来 ThreadLocalMap中使用这个 ThreadLocal 的 key 也会被清理掉。但是, value 是强引用,不会被清理,这样一来就会出现 key 为 null 的 value。

3. 悲观锁实现单体一人一单超卖问题

  • 乐观锁适合判断数据更新问题,而当前是判断是否存在,所以可以使用悲观锁解决。

  • 锁的范围尽量小, synchronized尽量锁代码块而不是方法,锁的范围越大性能越低。

  • 锁的对象一定是一个不变的值,不能直接锁 Long 类型的 userId,每请求一次都会创建一个新的 userId 对象,synchronized 要锁不变的值, 所以要将 Long 类型的 userId 通过 toString() 方法转成 String 类型的 userId, toString底层是直接 new 一个新的 String 对象,还是在变, 所以要用 intern() 方法从常量池中寻找与当前字符串值一致的字符串对象, 这样就能保障一个用户发送多次请求,每次请求的 userId 都是不变的,从而完成锁的效果。

  • 要锁整个事务,而不是锁事务内部的代码。如果我们锁住事务内部的代码会导致其它线程能够进入事务,当我们事务还未提交,锁一旦释放,仍然会存在超卖问题。

  • Spring 的 @Transactional 注解要想事务生效,必须使用动态代理。在同一个类中,一个方法调用另外一个有注解(比如@Async,@Transational)的方法,注解是不会生效的,所以我们需要创建一个代理对象,使用代理对象来调用方法。

    • spring 在扫描bean的时候会扫描方法上是否包含@Transactional注解,如果包含,spring会为这个bean动态地生成一个子类(即代理类,proxy),代理类是继承原来那个bean的。此时,当这个有注解的方法被调用的时候,实际上是由代理类来调用的,代理类在调用之前就会启动transaction。然而,如果这个有注解的方法是被同一个类中的其他方法调用的,那么该方法的调用并没有通过代理类,而是直接通过原来的那个bean,所以就不会启动transaction,我们看到的现象就是@Transactional注解无效。

    • 让代理对象生效的步骤:

      • 引入 AOP 依赖,动态代理是AOP 的常见实现之一
      xml 复制代码
      <dependency>
          <groupId>org.aspectj</groupId>
          <artifactId>aspectjweaver</artifactId>
      </dependency>
      • 暴露动态代理对象,默认是关闭的
      java 复制代码
      	@EnableAspectJAutoProxy(exposeProxy = true)
  • 对于集群下一人一单的并发安全问题,由于每个tomcat 都有一个属于自己的 jvm,此时这个synchronized锁会失效,synchronized是本地锁,只能提供线程级别的同步,每个JVM中都有一把synchronized锁,不能跨 JVM 进行上锁,当一个线程进入被 synchronized 关键字修饰的方法或代码块时,它会尝试获取对象的内置锁(也称为监视器锁)。如果该锁没有被其他线程占用,则当前线程获得锁,可以继续执行代码;否则,当前线程将进入阻塞状态,直到获取到锁为止。而现在我们是创建了两个节点,也就意味着有两个JVM,所以synchronized会失效! 原文链接

  • try...finally...确保发生异常时锁能够释放,注意这给地方不要使用catch,A事务方法内部调用B事务方法,A事务方法不能够直接catch,否则会导致事务失效。

    java 复制代码
        // 3、创建订单(使用分布式锁)
        Long userId = ThreadLocalUtls.getUser().getId();
        SimpleRedisLock lock = new SimpleRedisLock(stringRedisTemplate, "order:" + userId);
        boolean isLock = lock.tryLock(1200);
        if (!isLock) {
            // 索取锁失败,重试或者直接抛异常(这个业务是一人一单,所以直接返回失败信息)
            return Result.fail("一人只能下一单");
        }
        try {
            // 索取锁成功,创建代理对象,使用代理对象调用第三方事务方法, 防止事务失效
            IVoucherOrderService proxy = (IVoucherOrderService) AopContext.currentProxy();
            return proxy.createVoucherOrder(userId, voucherId);
        } finally {
            lock.unlock();
        }

4. redisson

  • 可重入:利用hash 结构记录线程id 和重入次数
  • 可重试:利用信号量和 PubSub 功能实现等待、唤醒、获取锁失败的重试机制
  • 超时续约:利用 watchDog ,每个一段时间(releaseTime / 3) 重置超时时间
    获取锁,根据订阅发的通知,在自己获取锁时,判断自己的剩余时间,去监听获取。
    避免业务未完成锁超时释放发问题,采用看门狗的机制,每过一段时间去重置有效期.
  • 主从一致性问题:利用 Redission 的 multiLock ,多个独立的 Redis 节点, 必须在所有节点都获取重入锁,才算获取锁成功

5. 回顾秒杀优化

遇到自增 ID 问题,通过实现分布式ID解决了问题;后面我们在单体系统下遇到了一人多单超卖问题,我们通过乐观锁解决了;我们对业务进行了变更,将一人多单变成了一人一单,结果在高并发场景下同一用户发送相同请求仍然出现了超卖问题,我们通过悲观锁解决了;由于用户量的激增,我们将单体系统升级成了集群,结果由于锁只能在一个JVM中可见导致又出现了,在高并发场景下同一用户发送下单请求出现超卖问题,我们通过实现分布式锁成功解决集群下的超卖问题;由于我们最开始实现的分布式锁比较简单,会出现超时释放导致超卖问题,我们通过给锁添加线程标识成功解决了;但是释放锁时,判断锁是否是当前线程 和 删除锁两个操作不是原子性的,可能导致超卖问题,我们通过将两个操作封装到一个Lua脚本成功解决了;为了解决锁的不可重入性,我们通过将锁以hash结构的形式存储,每次释放锁都value-1,获取锁value+1,从而实现锁的可重入性,并且将释放锁和获取锁的操作封装到Lua脚本中以确保原子性。最最后,我们发现可以直接使用现有比较成熟的方案Redisson来解决上诉出现的所有问题,不可重试、不可重入、超时释放、原子性等问题Redisson都提供相对应的解决方法。
原文链接

6. Nginx 负载均衡

搭建集群环境时,修改 Nginx 配置后要重启。 nginx.exe -s reload

相关推荐
chuanauc2 分钟前
Kubernets K8s 学习
java·学习·kubernetes
满分观察网友z4 分钟前
从一次手滑,我洞悉了用户输入的所有可能性(3330. 找到初始输入字符串 I)
算法
一头生产的驴18 分钟前
java整合itext pdf实现自定义PDF文件格式导出
java·spring boot·pdf·itextpdf
YuTaoShao25 分钟前
【LeetCode 热题 100】73. 矩阵置零——(解法二)空间复杂度 O(1)
java·算法·leetcode·矩阵
Heartoxx26 分钟前
c语言-指针(数组)练习2
c语言·数据结构·算法
zzywxc78728 分钟前
AI 正在深度重构软件开发的底层逻辑和全生命周期,从技术演进、流程重构和未来趋势三个维度进行系统性分析
java·大数据·开发语言·人工智能·spring
大熊背40 分钟前
图像处理专业书籍以及网络资源总结
人工智能·算法·microsoft
满分观察网友z44 分钟前
别怕树!一层一层剥开它的心:用BFS/DFS优雅计算层平均值(637. 二叉树的层平均值)
算法
杰克尼2 小时前
1. 两数之和 (leetcode)
数据结构·算法·leetcode
YuTaoShao3 小时前
【LeetCode 热题 100】56. 合并区间——排序+遍历
java·算法·leetcode·职场和发展