it_lv 2 weeks ago
parent
commit
aaedc3e510

BIN
assets/6.订单结算提交0001.png


BIN
assets/image-20210718160907166.png


BIN
assets/image-20210718194040498.png


BIN
assets/image-20231019144130113.png


+ 234 - 93
简历汇总参考技术描述.md

@@ -336,7 +336,7 @@
 
 解决方案:
 
-服务器端应对高并发手段:**1.缓存**  **2.线程池+异步**   **3.限流**   **4.SQL优化** **5.搭建集群**
+服务器端应对高并发手段:**1.缓存**  **2.线程池+异步**   **3.限流**   **4.SQL优化** **5.搭建集群  6.JVM调优** 
 
 创建线程方式:
 
@@ -725,7 +725,7 @@
 
 订单核心业务:将**虚拟物品**换成**钱**过程,用户通过订单途径购买VIP会员,专辑,声音,支付方式支持:余额支付;在线支付(微信支付)付款后商家账户入账,用户账户付款。
 
-- 步骤一:订单结算
+- **步骤一:订单结算**
 
   - VIP会员
 
@@ -743,15 +743,17 @@
   
   ![image-20231018104258648](assets/image-20231018104258648.png)
 
+![6.订单结算提交0001](assets/6.订单结算提交0001.png)
+
 除了展示“商品”信息外,还需要在订单确认页面中采用隐藏域方式,流水号、跟本次订单签名信息
 
-策略+工厂模式:处理不同商品类别结算逻辑;不同虚拟物品发货
+**策略+工厂模式**:处理不同商品类别结算逻辑;不同虚拟物品发货
 
 流水号:订单确认页渲染接口服务端,采用UUID生成本次订单流水号字符串,将流水号存入Redis设置ttl-5分钟。
 
 签   名:订单确认页面中将所有订单VO参数参与加密 对称加密算法(排序好订单vo所有值+"秘钥") 得到对应签名值
 
-- 步骤二:订单提交(余额支付)
+- **步骤二:订单提交(余额支付)**
 
 > 业务流程:“商品”三种:1.VIP会员 2.专辑 3.声音,其中VIP会员及专辑支持两种付款方式(余额跟微信),声音仅支持余额付款。当选择余额支付,首先**业务校验**(一、验证流水号**采用lua脚本(判断跟删除原子性)**验证流水号及删除流水号。二、验签,采用订单确认页相同对称加密算法对订单VO信息+秘钥再次进行签名比对两次签名值是否一致);经过业务校验后,`核心业务1`:保存订单,保存订单其中订单编号(确保全局唯一:采用年月日+雪花算法);保存订单商品明细列表;保存优惠明细列表,此时订单状态:**未支付。**`核心业务2`:扣减账户余额且增加账户变动日志,只要余额扣减成功将地订单状态修改为:**已支付**。`核心业务3`:虚拟物品发货,专辑/声音新增购买记录即可(订单详情页面中声音付费标识-查询用户购买情况);VIP会员新增会员购买记录,更新用户VIP标识及延长会员过期时间。
 >
@@ -841,13 +843,16 @@
 
 
 
-#### 12. 采用策略模式+工厂模式优化不同类型商品处理虚拟物品发货业务
+#### 12. 采用策略模式+工厂模式优化不同类型商品订单确认逻辑以及虚拟物品发货业务
+
+关键:业务代码中出现大量if else if 判断,且后续还会不断增加大量业务判断,采用策略模式+工厂模式解决。
 
-背景:目前项目中提供三种商品类别(产品经理规划后续可能还会进一步丰富会员体系,增加其他商品类别-视频)如果仍然采用传统if else if判断处理不同商品类别,代码可维护性很差,扩展性很差。 特点:不同类型商品“发货”逻辑各不一样,采用行为型设计模式:策略模式 为了能具备“开闭原则”采用工厂统一管理所有策略,提供策略标识返回策略实现类。
+背景:目前项目中提供三种商品类别(产品经理规划后续可能还会进一步丰富会员体系,增加其他商品类别-视频)如果仍然采用传统if else if判断处理不同商品类别,代码可维护性很差,扩展性很差。 特点:不同类型商品“
+结算",“发货”逻辑各不一样,采用行为型设计模式:策略模式 为了能具备“开闭原则”,所有策略实现类交给工厂统一管理所有策略,提供策略标识返回策略实现类。
 
 > 1. 抽象策略类接口 :处理虚拟物品发货-新增购买记录逻辑方法
-> 2. 在抽象类接口新增不同策略实现类,bean对象ID跟前端提交购买项目类型一致,完善新增购买记录业务逻辑
-> 3. 提供工厂对象,采用Spring自动注入 将策略接口类型下 所有策略实现类对象注入 Map<Bean对象ID,策略实现类对象>;提供方法根据购买项目类型返回策略实现类
+> 2. 在抽象类接口新增不同策略实现类,bean对象ID跟前端提交购买项目类型一致(@Component("Bean的名称")),完善新增购买记录业务逻辑
+> 3. 提供工厂对象,采用Spring自动注入 将策略接口类型下所有策略实现类对象注入 Map<**Bean对象ID****策略实现类对象**>;提供方法根据购买项目类型返回策略实现类
 > 4. 处理不同购买项目业务代码;根据前端提交购买类型,精确调用具体策略实现类对象方法。
 > 5. 扩展性提高,如果再有新的项目类型,只需要
 >    1. 提供新策略实现类,自动注入工厂中Map中(所有策略实现类对象)完善新项目类型业务逻辑
@@ -991,11 +996,11 @@
 
 背景:各项订单状态变更全局配置,订单超时多久未支付改为关闭;订单支付成功后多久未评论自动好评。下单购买VIP会员,专辑,声音选择微信支付,如果超过规定时间(60分钟),且未支付将订单自动关闭。
 
-延迟消息:方案
+延迟消息:选择RabbitMQ延迟插件方案
 
 - RabbitMQ  
 
-  > 方式一:死信队列   消息成为死信条件:队列满;消息超时;
+  > 方式一:死信队列   消息成为死信条件:队列满;**消息超时**消费方拒绝
   >
   > ![image-20240605153700282](assets/image-20240605153700282.png)
   >
@@ -1018,9 +1023,8 @@
   >   1. 项目启动后开启单一线程线程池,专门负责监听阻塞队列中消息
   >   2. 获取需要检查订单ID对应订单支付状态,如果仍然是未支付,将订单修改为关闭。该笔订单不允许支付
   >
-  > ![image-20240605154208938](assets/image-20240605154208938.png)
 
- ![image-20240706165333043](assets/image-20240706165333043.png)
+  ![image-20240706165333043](assets/image-20240706165333043.png)
 
 #### 15. 采用分布式任务调度框架Xxl-Job进行定时更新会员状态、排行榜数据更新、腾讯音频审核结果、布隆过滤器扩容等
 
@@ -1069,42 +1073,87 @@
 
 #### 16. 使用RabbitMQ消息中间件来实现应用的异步,解耦,削峰以及数据的最终一致性,掌握消息可靠方案,消息幂等方案
 
-- 消息可靠性方案:
+##### 消息可靠性方案:双端确认+持久化+重试(死信队列)+人工
 
-- 消息幂等性:
+RabbitMQ提供了publisher confirm机制来避免消息发送到MQ过程中丢失。这种机制必须给每个消息指定一个唯一ID。消息发送到MQ以后,会返回一个结果给发送者,表示消息是否处理成功。
 
-  幂等性就是指Producer不论向Broker发送多少次重复数据,Broker端都只会持久化一条,保证了不重复。或者无论broker同一消息向消费者投递多少次,只处理一次。
+返回结果有两种方式:
 
-  > 生产者:发送消息失败触发重试,Broker服务器确保生产者幂等性,生产者发送消息或者重试消息同一消息Sequence序号相同。
-  >
-  > 消费者:
-  >
-  > 消息者业务处理抛出异常,Broker默认重复投递消息10次。
-  >
-  > 设置为自动提交,消息被监听到后,业务正常处理(事务已提交)未来得及提交offset,服务宕机了,服务重启后,继续从上次记录offset拉取消息,消息重复获取
-  >
-  > 方案:
-  >
-  > - 生产者端发送消息设置消息唯一标识,消费者端监听到消息后将消息唯一标识set k v nx 存入Redis第一次存储成功处理业务(有异常+删除key),同一消息重复接收,后续set nx 失败。
-  > - 本身业务数据中含有标识数据(例如:库存商品编号,订单编号)利用标识构建set nx 对应的key
+- publisher-confirm
+  - 消息成功投递到交换机,返回ack
+  - 消息未投递到交换机,返回nack
+- publisher-return
+  - 消息投递到交换机了,但是没有路由到队列。返回ACK,及路由失败原因。
+
+![image-20210718160907166](assets/image-20210718160907166.png)
+
+生产者确认可以确保消息投递到RabbitMQ的队列中,但是消息发送到RabbitMQ以后(消息存放在内存中),如果突然宕机,也可能导致消息丢失。
+
+要想确保消息在RabbitMQ中安全保存,必须开启消息持久化机制。
+
+- 交换机持久化
+- 队列持久化
+- 消息持久化
+
+RabbitMQ是**阅后即焚**机制,RabbitMQ默认为自动确认,只要MQ将消息投递给消费者,自动完成确认消息被消费者消费后会立刻删除。
+
+RabbitMQ消费者开启手动确认,是通过消费者回执来确认消费者是否成功处理消息的:消费者获取消息后,应该向RabbitMQ发送ACK回执,表明自己正常已经处理消息。
+
+**开启消费失败本地重试机制:**原因:当消费者出现异常后,消息会不断requeue(重入队)到队列,再重新发送给消费者,然后再次异常,再次requeue,无限循环,导致mq的消息处理飙升,带来不必要的压力:
+
+- 在重试达到上限后,SpringAMQP会抛出异常AmqpRejectAndDontRequeueException,说明本地重试触发了
+- 查看RabbitMQ控制台,发现消息被删除了,说明最后SpringAMQP返回的是ack,mq删除消息了
+
+达到最大重试次数后,消息会被丢弃,这是由SpringAMQP内部机制决定的。
+
+在开启重试模式后,重试次数耗尽,如果消息依然失败,则需要有**MessageRecovery接口来**处理,它包含三种不同的实现:
+
+- RejectAndDontRequeueRecoverer:重试耗尽后,直接reject,丢弃消息。默认就是这种方式
+
+- ImmediateRequeueMessageRecoverer:重试耗尽后,返回nack,消息重新入队
+
+- RepublishMessageRecoverer:重试耗尽后,将失败消息投递到指定的交换机
+
+
+
+比较优雅的一种处理方案是RepublishMessageRecoverer,失败后将消息投递到一个指定的,专门存放异常消息的队列,后续由人工集中处理。
+
+> 总结:如何确保RabbitMQ消息的可靠性?
+>
+> - 开启生产者确认机制,确保生产者的消息能到达队列
+> - 开启持久化功能,确保消息未消费前在队列中不会丢失
+> - 开启消费者确认机制为auto,由spring确认消息处理成功后完成ack
+> - 开启消费者失败重试机制,并设置MessageRecoverer,多次重试失败后将消息投递到异常交换机,交由人工处理
+
+参考:https://blog.csdn.net/p393975269/article/details/129836229
+
+##### 消息幂等性:
+
+幂等性问题场景:生产者进行失败重试,或者由于网络问题导致RabbitMQ多次投递消息到消费者
+
+> 方案:
+>
+> - 生产者端发送消息设置消息唯一标识,消费者端监听到消息后将消息唯一标识set k v nx 存入Redis第一次存储成功处理业务(有异常+删除key),同一消息重复接收,后续set nx 失败。
+> - 本身业务数据中含有标识数据(例如:库存商品编号,订单编号)利用标识构建set nx 对应的key
+>   
 
 #### 17. 熟悉MySQL执行计划,对负责业务模块SQL进行优化
 
-**背景:**
+目标:要求所有业务SQL执行计划type至少达到Range级别
 
-通过接口,QA测试反馈接口不达标、开启MySQL慢查询日志(默认是关闭的)设置最大忍耐时间,读取慢查询日志获取耗时久SQL、链路追踪定位耗时较长接口。
+**背景:**
 
-目标:要求所有业务SQL至少达到Range级别
+发现问题SQL途径:通过接口测试,QA测试人员压测反馈接口不达标、开启MySQL慢查询日志(默认是关闭的)设置最大忍耐时间,读取慢查询日志获取耗时久SQL、链路追踪定位耗时较长接口。
 
-1. 通过explain查看SQL执行计划
+1. 通过explain查看SQL执行计划,重要参考指标
 
    | id       | 代表"几趟"查询 ID相同自上而下执行,ID值越大优先级越高,越先被执行 |
    | -------- | ------------------------------------------------------------ |
-   | **type** | const > eq_ref > ref >range>index>all                        |
-   | key      | 使用索引                                                     |
-   | key_len  | 使用索引字节数                                               |
+   | **type** | const > eq_ref > ref >**range**>index>all                    |
+   | key      | 真实使用索引                                                   |
+   | key_len  | 使用索引字节数,多列复合索引                                 |
    | rows     | 预计检查行数 值约小约好                                      |
-   | filter   | 实际跟预计检查行号百分比:值越高约好                         |
+   | filter   | 实际跟预计检查行数百分比:值越高约好                        |
    | Extra    | 额外信息:索引库覆盖;索引下推;文件排序等重要参考指标       |
 
 2. 合理设置表中索引
@@ -1115,20 +1164,165 @@
 
    2. 多表
 
-      > 内连接: Inner join 内部优化器会自动识别谁是驱动、被驱动表
+      > 内连接: Inner join 内部优化器会自动识别谁是驱动(小表)、被驱动表(大表)被驱动表关联字段建立索引,在where条件列上建立索引
       >
       > 外连接: 建议在被驱动表[从表,选择大表]上创建索引
       >
       > 子查询:建议使用关联查询替换
       >
-      > 覆盖索引 using Index
+      > 覆盖索引 using Index 按需查询列,限制查询列
 
    3. 其他
 
       > 1. 去掉 * 目的就是尽量还用覆盖索引
       > 2.  在where或on后面的条件加索引
 
-#### 18. Nginx
+#### 18. 项目中遇到的一些难点/参与过JVM调优吗/亦可作为亮点
+
+首先明确JVM调优的目的:**一是减少Full gc的次数,二是缩短一次Full gc的时间**
+
+> 背景:服务部署到线上,每日9-10点优惠购开启期间,用户反馈访问订单业务相关接口会明显的卡顿,接口耗时较长。运营反馈问题到研发部。这里我们采用阿里**Arthas**这个**JVM内存诊断工具**,通过**dashboard**命令定时显示JVM的运行状况,其中观察GC的次数及GC的耗时情况。发现在活动期间订单服务实例发生频繁的FullGC,每隔十分钟左右就会触发一次FullGC,由于在此期间只有下单接口被高并发调用,所以重点的关注点就在下单,分析主线下单业务定位找到原因所在。设置合适的JVM参数减少GC次数或减少GC耗时,达到JVM调优目的。
+
+- **1.分析核心主线业务**
+
+  9-10点期间举办优惠促销活动,订单服务集群中的单一服务实例服务器配置2核4G,单个节点处理请求TPS为300/s。该接口背后涉及到订单,优惠券,积分,库存等相关业务处理。同时订单服务还包含订单查询、删除、优惠券查询,积分查询等接口。
+
+- **2.估算涉及对象大小**
+
+  一般大的对象最多几十个上百个字段,我们假设一个订单对象大小1KB,这已经算比较大的,一般对象不这么大。按照TPS 300/s**估算每秒就会有300KB的订单对象生成**。还会涉及库存,优惠券,积分等其他相关对象;同时还包括其他的订单业务操作;既然是估算,我们肯定要放大一下才可靠。**故进行冗余20倍** **=300KB*20**
+
+  约等于6M。**同时还应该包含用户其他操作,订单查询,订单删除,再冗余10倍=6M*10=60MB对象,1秒后成为垃圾**
+
+- **3.画出对象内存模型**
+
+   ![image-20240708093927816](assets/image-20240708093927816.png)
+
+根据目前的业务场景每秒有60M对象产生进去Eden区,那么13秒就会占满Eden区域,发生minor gc(YGC),这些订单对象其中90%其实已经是垃圾对象了,因为在gc的那一刻,这些对象肯定还有一部分的业务操作还没有完成,所以他们不会被回收,我们这里假设每次minor gc都有60M对象还不会被回收------->60兆左右对象进入到幸存者0区
+
+这60M对象会从Eden区移到S0区域,这里的60M虽然小于100M,同样会直接被移入老年代。原因:**JVM对象动态年龄判断**,就是如果你移动的这批对象超过了Surviror区的50%,同样会把这批对象移入老年代。
+
+结果:每13秒就会有60M的对象被移入老年代,那么大概5~7分钟老年代的2G就会被占满,那么老年代一满就要发生full gc(STW现象),但是full gc使我们最不愿意看到的结果。
+
+- **4.调整合适JVM参数调优**
+
+我们这个系统其实没有很多会长久存在的对象,也就是老不死的对象,我们放在老年代的那些60M的对象,在一两秒后其实都会变为垃圾对象,在下一次full gc时都会被回收掉,那么我们这种业务场景,完全没必要给老年代设置2G的内存,根本用不到。我们完全可以把年轻代设置的大一些;
+
+![image-20240708094255714](assets/image-20240708094255714.png)
+
+此时需要25~30秒把伊甸园区放满,放满minor gc后有60M对象不被回收,要移到S0区,这时60M<200M/2,是可以移入S0区域的,下一次伊甸园区再放满做minor gc的时候,这时这60M对象所对应的订单已经生成了,已经变成了垃圾对象,是可以直接被回收的,所以没有什么对象是需要被移入老年代的。
+
+那么这么一设置的话,这个系统是不是正常情况下基本不会再发生full gc了呢?就算发生,也是很久才会一次了。
+
+常用的JVM运行参数: https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html
+
+```sh
+nohup java -jar -Duser.timezone=GMT+8 -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=128m -Xms1024m -Xmx1024m -Xss256k -XX:SurvivorRatio=8 -XX:+UseG1GC  -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/opt/errorDump.hprof  app.jar > nohop.log &
+```
+
+**最后,我们项目选择JDK17的另一重要原因**
+
+垃圾回收器的暂停问题对实时响应要求较高的服务来说,一直是个痛点, CMS和G1等主流垃圾回收器的数十毫秒乃至上百毫秒的暂停时间相当致命。此外,调优门槛也相对较高,需要对垃圾回收器的内部机制有一定的了解,才能够进行有效的调优。随着ZGC的出现, 使得这一痛点彻底解决, ZGC 最初在 JDK 11 中作为实验性功能引入,并在 JDK 15 中宣布为生产就绪, **由于JDK17才是比较正式提供给大众实用的LTS支持版本**,而且一部分公司已经在使用,所以我们最新项目使用JDK17。
+
+ZGC 作为一款低延迟垃圾收集器,旨在满足以下目标:
+
+-  8MB到16TB的堆大小支持
+- 10ms最大GC暂时
+-  最糟糕的情况下吞吐量会降低15%
+
+选择JDK17的不可拒绝的理由:低延迟的业务需求,毫秒级耗时的GC。开启ZGC的JVM参数:-XX:+UseZGC
+
+#### 19. RabbitMQ消息积压问题如何解决
+
+引起积压原因:
+
+a)消息生产过快
+在流量较大的情况下,生产者发送消息速率大于消费者消费消息速率.
+
+b)消费者处理能力不足
+
+消费端业务复杂,耗时长.
+系统资源限制,例如 CPU、内存、磁盘I/O 限制消费者处理速度.
+消费者在处理消息时出现异常,导致消息无法被正确处理和确认.
+服务器端配置过低
+c)网络问题
+由于网络抖动,消费者没有及时反馈 ack/nack,导致消息不断重发.
+
+d)消费者代码逻辑异常,引发重试
+消费者配置了手动 ack + requeue= true,导致一旦由于消费者代码逻辑引发异常,就会造成消息不断重新入队,不断重试,进而导致消息积压.
+
+解决方案:
+
+a)提高消费者效率:
+
+- 提高消费者的数量,比如新增机器.
+
+- 如果消费端业务分散耗时,可以考虑使用 CompletableFuture+线程池 实现多线程异步编排.
+
+- 消息引发异常时,考虑配置重试机制,或者转入死信队列.
+
+b)限制生产者速率:
+
+使用限流工具阿里Sentinel,限制消息发送速率的上限.设置消息过期时间. 如果消息过期没有消费,可以配置死信队列,不仅避免消息丢失,还减少了主队列的压力.
+
+
+
+当生产者发送消息的速度超过了消费者处理消息的速度,就会导致队列中的消息堆积,直到队列存储消息达到上限。之后发送的消息就会成为死信,可能会被丢弃,这就是消息堆积问题。
+
+
+
+![image-20210718194040498](assets/image-20210718194040498.png)
+
+
+
+
+
+解决消息堆积有两种思路:
+
+- 增加更多消费者,提高消费速度。也就是我们之前说的work queue模式
+- 扩大队列容积,提高堆积上限
+
+要提升队列容积,把消息保存在内存中显然是不行的。
+
+从RabbitMQ的3.6.0版本开始,就增加了Lazy Queues的概念,也就是**惰性队列**。惰性队列的特征如下:
+
+- 接收到消息后直接存入磁盘而非内存
+- 消费者要消费消息时才会从磁盘中读取并加载到内存
+- 支持数百万条的消息存储
+
+> 总结:消息堆积问题的解决方案?
+>
+> **消费者端**:
+>
+> - 队列上绑定多个消费者,提高消费速度
+> - 消费者端,内部通过CompletableFuture+线程池 多线程并行处理消息
+> - 使用惰性队列,可以再mq中保存更多消息(将消息存入磁盘)
+> - 使用集群
+>
+> **生产者端**:
+>
+> - 限制生产速率 - 限流
+>
+> **RabbitMQ服务端** 使用惰性队列
+>
+> 惰性队列的优点有哪些?
+>
+> - 基于磁盘存储,消息上限高
+> - 没有间歇性的page-out,处理较稳定
+>
+> 惰性队列的缺点有哪些?
+>
+> - 基于磁盘存储,消息时效性会降低
+> - 性能受限于磁盘的IO
+
+#### 20. RabbitMQ中确保有序
+
+例如:订单业务处理全部由MQ处理,同一笔订单处理顺序: 创建->支付->发货
+
+全局有序:提供交换机+队列+**提供一个消费者**
+
+#### 21. Nginx
+
+*Nginx* (engine x) 是一个高性能的HTTP和反向代理web服务器,动(反向代理)静(负载静态资源)分离
 
 **Nginx可以健康检查和故障重试**
 
@@ -1151,7 +1345,7 @@ upstream test {
 - bbs.abc.com,映射到 html/bbs
 - blog.abc.com 映射到 html/blog
 
-```
+```shell
 http {
     include       mime.types;
     default_type  application/octet-stream;
@@ -1231,57 +1425,4 @@ upstream serverGroup2 {                    # 定义负载均衡设备的ip和状
     }
 ```
 
-#### 19. 项目中遇到的一些难点/参与过JVM调优吗/亦可作为亮点
-
-首先明确JVM调优的目的:**一是减少Full gc的次数,二是缩短一次Full gc的时间**
-
-> 背景:服务部署到线上,每日9-10点优惠购开启期间,用户反馈访问订单业务相关接口会明显的卡顿,接口耗时较长。运营反馈问题到研发部。这里我们采用阿里**Arthas**这个**JVM内存诊断工具**,通过**dashboard**命令定时显示JVM的运行状况,其中观察GC的次数及GC的耗时情况。发现在活动期间订单服务实例发生频繁的FullGC,每隔十分钟左右就会触发一次FullGC,由于在此期间只有下单接口被高并发调用,所以重点的关注点就在下单,分析主线下单业务定位找到原因所在。设置合适的JVM参数减少GC次数或减少GC耗时,达到JVM调优目的。
-
-- **1.分析核心主线业务**
-
-  9-10点期间举办优惠促销活动,订单服务集群中的单一服务实例服务器配置2核4G,单个节点处理请求TPS为300/s。该接口背后涉及到订单,优惠券,积分,库存等相关业务处理。同时订单服务还包含订单查询、删除、优惠券查询,积分查询等接口。
-
-- **2.估算涉及对象大小**
-
-  一般大的对象最多几十个上百个字段,我们假设一个订单对象大小1KB,这已经算比较大的,一般对象不这么大。按照TPS 300/s**估算每秒就会有300KB的订单对象生成**。还会涉及库存,优惠券,积分等其他相关对象;同时还包括其他的订单业务操作;既然是估算,我们肯定要放大一下才可靠。**故进行冗余20倍** **=300KB*20**
-
-  约等于6M。**同时还应该包含用户其他操作,订单查询,订单删除,再冗余10倍=6M*10=60MB对象,1秒后成为垃圾**
-
-- **3.画出对象内存模型**
-
-   ![image-20240708093927816](assets/image-20240708093927816.png)
-
-根据目前的业务场景每秒有60M对象产生进去Eden区,那么13秒就会占满Eden区域,发生minor gc(YGC),这些订单对象其中90%其实已经是垃圾对象了,因为在gc的那一刻,这些对象肯定还有一部分的业务操作还没有完成,所以他们不会被回收,我们这里假设每次minor gc都有60M对象还不会被回收------->60兆左右对象进入到幸存者0区
-
-这60M对象会从Eden区移到S0区域,这里的60M虽然小于100M,同样会直接被移入老年代。原因:**JVM对象动态年龄判断**,就是如果你移动的这批对象超过了Surviror区的50%,同样会把这批对象移入老年代。
-
-结果:每13秒就会有60M的对象被移入老年代,那么大概5~7分钟老年代的2G就会被占满,那么老年代一满就要发生full gc(STW现象),但是full gc使我们最不愿意看到的结果。
-
-- **4.调整合适JVM参数调优**
-
-我们这个系统其实没有很多会长久存在的对象,也就是老不死的对象,我们放在老年代的那些60M的对象,在一两秒后其实都会变为垃圾对象,在下一次full gc时都会被回收掉,那么我们这种业务场景,完全没必要给老年代设置2G的内存,根本用不到。我们完全可以把年轻代设置的大一些;
-
-![image-20240708094255714](assets/image-20240708094255714.png)
-
-此时需要25~30秒把伊甸园区放满,放满minor gc后有60M对象不被回收,要移到S0区,这时60M<200M/2,是可以移入S0区域的,下一次伊甸园区再放满做minor gc的时候,这时这60M对象所对应的订单已经生成了,已经变成了垃圾对象,是可以直接被回收的,所以没有什么对象是需要被移入老年代的。
-
-那么这么一设置的话,这个系统是不是正常情况下基本不会再发生full gc了呢?就算发生,也是很久才会一次了。
-
-常用的JVM运行参数:
-
-```sh
-nohup java -jar -Duser.timezone=GMT+8 -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=128m -Xms1024m -Xmx1024m -Xss256k -XX:SurvivorRatio=8 -XX:+UseG1GC  -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/opt/errorDump.hprof  app.jar > nohop.log &
-```
-
-**最后,我们项目选择JDK17的另一重要原因**
-
-垃圾回收器的暂停问题对实时响应要求较高的服务来说,一直是个痛点, CMS和G1等主流垃圾回收器的数十毫秒乃至上百毫秒的暂停时间相当致命。此外,调优门槛也相对较高,需要对垃圾回收器的内部机制有一定的了解,才能够进行有效的调优。随着ZGC的出现, 使得这一痛点彻底解决, ZGC 最初在 JDK 11 中作为实验性功能引入,并在 JDK 15 中宣布为生产就绪, 由于JDK17才是比较正式提供给大众实用的LTS支持版本,而且一部分公司已经在使用,所以我们最新项目使用JDK17。
-
-ZGC 作为一款低延迟垃圾收集器,旨在满足以下目标:
-
--  8MB到16TB的堆大小支持
-- 10ms最大GC暂时
--  最糟糕的情况下吞吐量会降低15%
-
-选择JDK17的不可拒绝的理由:低延迟的业务需求,毫秒级耗时的GC。开启ZGC的JVM参数:-XX:+UseZGC
-
+####