我只改五行代码,接口性能提升了 10 倍!

news2024/11/25 20:42:26

背景

某公司的一个 ToB 系统,因为客户使用的也不多,没啥并发要求,就一直没有经过压测。这两天来了一个“大客户”,对并发量提出了要求:核心接口与几个重点使用场景单节点吞吐量要满足最低500/s的要求。
 

图片

当时一想,500/s吞吐量还不简单。Tomcat按照 100 个线程,那就是单线程 1S 内处理 5 个请求,200ms 处理一个请求即可。这个没有问题,平时接口响应时间大部分都 100ms 左右,还不是分分钟满足的事情。然而压测一开,100 的并发,吞吐量居然只有 50 ...

图片

而且再一查,100 的并发,CPU 使用率居然接近 80% ...

从上图可以看到几个重要的信息:

  • 最小值:表示我们非并发场景单次接口响应时长。还不足100ms。挺好!

  • 最大值:并发场景下,由于各种锁或者其他串行操作,导致部分请求等待时长增加,接口整体响应时间变长。5秒钟。有点过分了!!!

  • 再一看百分位,大部分的请求响应时间都在4s。无语了!!!

图片

所以 1s 钟的 吞吐量 单节点只有 50 。距离 500 差了 10 倍。难受!!!!

分析过程

定位“慢”原因

这里暂时先忽略 CPU 占用率高的问题

首先平均响应时间这么慢,肯定是有阻塞。先确定阻塞位置。重点检查几处:

  • 锁 (同步锁、分布式锁、数据库锁)

  • 耗时操作 (链接耗时、SQL耗时)

结合这些先配置耗时埋点。

  • 接口响应时长统计。超过 500ms 打印告警日志。

  • 接口内部远程调用耗时统计。200ms 打印告警日志。

  • Redis 访问耗时。超过 10ms 打印告警日志。

  • SQL 执行耗时。超过 100ms 打印告警日志。

上述配置生效后,通过日志排查到接口存在慢SQL。具体SQL类似与这种:

<!-- 主要类似与库存扣减 每次-1 type 只有有限的几种且该表一共就几条数据(一种一条记录)-->
<!-- 压测时可以认为 type = 1 是写死的 -->
update table set field = field - 1 where type = 1 and filed > 1;

上述 SQL 相当于并发操作同一条数据,肯定存在锁等待。日志显示此处的等待耗时占接口总耗时 80% 以上。

二话不说先改为敬。因为是压测环境,直接改为异步执行,确认一下效果。

PS:当时心里是这么想的:妥了,大功告成。就是这里的问题!绝壁是这个原因!优化一下就解决了。当然,如果这么简单就没有必要写这篇文章了...
 

图片

优化后的效果:

图片

嗯... emm... 好!这个优化还是很明显的,提升提升了近 2 倍。

此时已经感觉到有些不对了,慢 SQL 已经解决了(异步了~ 随便吧~ 你执行 10s 我也不管了),虽然对吞吐量的提升没有预期的效果。但是数据是不会骗人的。

  • 最大值:已经从 5s -> 2s

  • 百分位值:4s -> 1s

这已经是很大的提升了。

继续定位“慢”的原因

通过第一阶段的“优化”,我们距离目标近了很多。废话不多说,继续下一步的排查。

我们继续看日志,此时日志出现类似下边这种情况:

2023-01-04 15:17:05:347 INFO **.**.**.***.50 [TID: 1s22s72s8ws9w00] **********************
2023-01-04 15:17:05:348 INFO **.**.**.***.21 [TID: 1s22s72s8ws9w00] **********************
2023-01-04 15:17:05:350 INFO **.**.**.***.47 [TID: 1s22s72s8ws9w00] **********************

2023-01-04 15:17:05:465 INFO **.**.**.***.234 [TID: 1s22s72s8ws9w00] **********************
2023-01-04 15:17:05:467 INFO **.**.**.***.123 [TID: 1s22s72s8ws9w00] **********************

2023-01-04 15:17:05:581 INFO **.**.**.***.451 [TID: 1s22s72s8ws9w00] **********************

2023-01-04 15:17:05:702 INFO **.**.**.***.72 [TID: 1s22s72s8ws9w00] **********************

前三行 info 日志没有问题,间隔很小。第 4 ~ 第 5,第 6 ~ 第 7,第 7 ~ 第 8 很明显有百毫秒的耗时。检查代码发现,这部分没有任何耗时操作。那么这段时间干什么了呢?

  • 发生了线程切换,换其他线程执行其他任务了。(线程太多了)

  • 日志打印太多了,压测5分钟日志量500M。(记得日志打印太多是有很大影响的)

  • STW。(但是日志还在输出,所以前两种可能性很高,而且一般不会停顿百毫秒)

按照这三个思路做了以下操作:

  • 首先,提升日志打印级别到 DEBUG。emm... 提升不大,好像增加了 10 左右。

  • 然后,拆线程 @Async 注解使用线程池,控制代码线程池数量(之前存在3个线程池,统一配置的核心线程数为100)结合业务,服务总核心线程数控制在 50 以内,同步增加阻塞最大大小。结果还可以,提升了 50,接近 200 了。

  • 最后,观察 JVM 的 GC 日志,发现 YGC 频次4/s,没有 FGC。1 分钟内 GC 时间不到 1s,很明显不是 GC 问题,不过发现JVM内存太小只有 512M,直接给了 4G。吞吐量没啥提升,YGC频次降低为 2 秒 1 次。

唉,一顿操作猛如虎。
 

图片

PS:其实中间还对数据库参数一通瞎搞,这里不多说了。

其实也不是没有收获,至少在减少服务线程数量后还是有一定收获的。另外,已经关注到了另外一个点:CPU 使用率,减少了线程数量后,CPU 的使用率并没有明显的下降,这里是很有问题的,当时认为 CPU 的使用率主要与开启的线程数量有关,之前线程多,CPU 使用率较高可以理解。但是,在砍掉了一大半的线程后,依然居高不下这就很奇怪了。

此时关注的重点开始从代码“慢”方向转移到“CPU高”方向。

定位 CPU 使用率高的原因

CPU 的使用率高,通常与线程数相关肯定是没有问题的。当时对居高不下的原因考虑可能有以下两点:

  • 有额外的线程存在。

  • 代码有部分 CPU 密集操作。

然后继续一顿操作:

  • 观察服务活跃线程数。

  • 观察有无 CPU 占用率较高线程。

在观察过程中发现,没有明显 CPU 占用较高线程。所有线程基本都在 10% 以内。类似于下图,不过有很多线程。

图片

没有很高就证明大家都很正常,只是多而已...

此时没有下一步的排查思路了。当时想着,算了打印一下堆栈看看吧,看看到底干了啥~

在看的过程中发现这段日志:

"http-nio-6071-exec-9" #82 daemon prio=5 os_prio=0 tid=0x00007fea9aed1000 nid=0x62 runnable [0x00007fe934cf4000]
   java.lang.Thread.State: RUNNABLE
 at org.springframework.core.annotation.AnnotationUtils.getValue(AnnotationUtils.java:1058)
 at org.springframework.aop.aspectj.annotation.AbstractAspectJAdvisorFactory$AspectJAnnotation.resolveExpression(AbstractAspectJAdvisorFactory.java:216)
 at org.springframework.aop.aspectj.annotation.AbstractAspectJAdvisorFactory$AspectJAnnotation.<init>(AbstractAspectJAdvisorFactory.java:197)
 at org.springframework.aop.aspectj.annotation.AbstractAspectJAdvisorFactory.findAnnotation(AbstractAspectJAdvisorFactory.java:147)
 at org.springframework.aop.aspectj.annotation.AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod(AbstractAspectJAdvisorFactory.java:135)
 at org.springframework.aop.aspectj.annotation.ReflectiveAspectJAdvisorFactory.getAdvice(ReflectiveAspectJAdvisorFactory.java:244)
 at org.springframework.aop.aspectj.annotation.InstantiationModelAwarePointcutAdvisorImpl.instantiateAdvice(InstantiationModelAwarePointcutAdvisorImpl.java:149)
 at org.springframework.aop.aspectj.annotation.InstantiationModelAwarePointcutAdvisorImpl.<init>(InstantiationModelAwarePointcutAdvisorImpl.java:113)
 at org.springframework.aop.aspectj.annotation.ReflectiveAspectJAdvisorFactory.getAdvisor(ReflectiveAspectJAdvisorFactory.java:213)
 at org.springframework.aop.aspectj.annotation.ReflectiveAspectJAdvisorFactory.getAdvisors(ReflectiveAspectJAdvisorFactory.java:144)
 at org.springframework.aop.aspectj.annotation.BeanFactoryAspectJAdvisorsBuilder.buildAspectJAdvisors(BeanFactoryAspectJAdvisorsBuilder.java:149)
 at org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator.findCandidateAdvisors(AnnotationAwareAspectJAutoProxyCreator.java:95)
 at org.springframework.aop.aspectj.autoproxy.AspectJAwareAdvisorAutoProxyCreator.shouldSkip(AspectJAwareAdvisorAutoProxyCreator.java:101)
 at org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator.wrapIfNecessary(AbstractAutoProxyCreator.java:333)
 at org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator.postProcessAfterInitialization(AbstractAutoProxyCreator.java:291)
 at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsAfterInitialization(AbstractAutowireCapableBeanFactory.java:455)
 at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1808)
 at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:620)
 at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:542)
 at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:353)
 at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:233)
 at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveNamedBean(DefaultListableBeanFactory.java:1282)
 at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveNamedBean(DefaultListableBeanFactory.java:1243)
 at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveBean(DefaultListableBeanFactory.java:494)
 at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:349)
 at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:342)
 at cn.hutool.extra.spring.SpringUtil.getBean(SpringUtil.java:117)
        ......  
        ......

上边的堆栈发现了一个点:在执行 getBean 的时候,执行了 createBean 方法。我们都知道 Spring 托管的 Bean 都是提前实例化好放在 IOC 容器中的。createBean 要做的事情有很多,比如 Bean 的初始化,依赖注入其他类,而且中间还有一些前后置处理器执行、代理检查等等,总之是一个耗时方法,所以都是在程序启动时去扫描,加载,完成 Bean 的初始化。

而我们在运行程序线程堆栈中发现了这个操作。而且通过检索发现竟然有近 200 处。

通过堆栈信息很快定位到执行位置:

<!--BeanUtils 是 hutool 工具类。也是从IOC容器获取Bean 等价于 @Autowired 注解 -->
RedisTool redisTool = BeanUtils.getBean(RedisMaster.class);

而RedisMaster类

@Component
@Scope("prototype")
public class RedisMaster implements IRedisTool {
    // ......
}

没错就是用了多例。而且使用的地方是 Redis(系统使用 Jedis 客户端,Jedis 并非线程安全,每次使用都需要新的实例),接口对 Redis 的使用还是比较频繁的,一个接口得有 10 次左右获取 Redis 数据。也就是说执行 10 次左右的 createBean 逻辑 ...

叹气!!

图片

赶紧改代码,直接使用万能的 new。

在看结果之前还有一点需要提一下,由于系统有大量统计耗时的操作。实现方式是通过:

long start = System.currentTimeMillis();
// ......
long end = System.currentTimeMillis();
long runTime = start - end;

或者 Hutool 提供的 StopWatch:

当时还误以为该方式能够降低性能的影响,但是实际上也只是一层封装。底层使用的是 System.nanoTime()。

StopWatch watch = new StopWatch();
watch.start();
// ......
watch.stop();
System.out.println(watch.getTotalTimeMillis());

而这种在并发量高的情况下,对性能影响还是比较大的,特别在服务器使用了一些特定时钟的情况下。这里就不多说,感兴趣的可以自行搜索一下。

最终结果:

图片

排查涉及的命令如下:

  • 查询服务进程CPU情况:top –Hp pid

  • 查询JVM GC相关参数:jstat -gc pid 2000  (对 pid [进程号] 每隔 2s 输出一次日志)

  • 打印当前堆栈信息:jstack -l pid >> stack.log

总结

结果是好的,过程是曲折的。总的来说还是知识的欠缺,文章看起来还算顺畅,但都是事后诸葛亮,不对,应该是时候臭皮匠。基本都是边查资料边分析边操作,前后花费了4天时间,尝试了很多。

  • Mysql :  Buffer Pool 、Change Buffer 、Redo Log 大小、双一配置...

  • 代码 :  异步执行,线程池参数调整,tomcat 配置,Druid连接池配置...

  • JVM : 内存大小,分配,垃圾收集器都想换...

总归一通瞎搞,能想到的都试试。后续还需要多了解一些性能优化知识,至少要做到排查思路清晰,不瞎搞。

最后5行代码有哪些:

new Redis实例:1 耗时统计:3 SQL异步执行 @Async:1(上图最终的结果是包含该部分的,时间原因未对SQL进行处理,后续会考虑Redis原子操作+定时同步数据库方式来进行,避免同时操数据库)

TODO

问题虽然解决了。但是原理还不清楚,需要继续深挖。

为什么 createBean 对性能影响这么大?

如果影响这么大,Spring 为什么还要有多例?首先非并发场景速度还是很快的。这个毋庸置疑。毕竟接口响应时间不足 50 ms。所以问题一定出在,并发 createBean 同一对象的锁等待场景。根据堆栈日志,翻了一下 Spring 源码,果然发现这里出现了同步锁。相信锁肯定不止一处。

图片

org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean

图片

System.currentTimeMillis 并发度多少才会对性能产生影响,影响有多大?

很多公司(包括大厂)在业务代码中,还是会频繁的使用 System.currentTimeMillis 获取时间戳。比如:时间字段赋值场景。所以,性能影响肯定会有,但是影响的门槛是不是很高。

继续学习性能优化知识

  • 吞吐量与什么有关?

首先,接口响应时长。直接影响因素还是接口响应时长,响应时间越短,吞吐量越高。一个接口响应时间 100ms,那么 1s 就能处理 10 次。

其次,线程数。现在都是多线程环境,如果同时 10 个线程处理请求,那么吞吐量又能增加 10 倍。当然由于 CPU 资源有限,所以线程数也会受限。理论上,在 CPU 资源利用率较低的场景,调大 tomcat 线程数,以及并发数,能够有效的提升吞吐量。

最后,高性能代码。无论接口响应时长,还是 CPU 资源利用率,都依赖于我们的代码,要做高性能的方案设计,以及高性能的代码实现,任重而道远。

  • CPU使用率的高低与哪些因素有关?

CPU 使用率的高低,本质还是由线程数,以及 CPU 使用时间决定的。

假如一台 10 核的机器,运行一个单线程的应用程序。正常这个单线程的应用程序会交给一个 CPU 核心去运行,此时占用率就是 10%。而现在应用程序都是多线程的,因此一个应用程序可能需要全部的 CPU 核心来执行,此时就会达到 100%。

此外,以单线程应用程序为例,大部分情况下,我们还涉及到访问 Redis/Mysql、RPC 请求等一些阻塞等待操作,那么 CPU 就不是时刻在工作的。所以阻塞等待的时间越长,CPU 利用率也会越低。也正是因为如此,为了充分的利用 CPU 资源,多线程也就应运而生(一个线程虽然阻塞了,但是 CPU 别闲着,赶紧去运行其他的线程)。

一个服务线程数在多少比较合适(算上 Tomcat,最终的线程数量是 226),执行过程中发现即使tomcat线程数量是 100,活跃线程数也很少超过 50,整个压测过程基本维持在 20 左右。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/753876.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C++ IO流

文章目录 C语言的输入与输出流是什么?CIO流C标准IO流C文件流 stringstream的简单介绍 C语言的输入与输出 在C语言中,我们使用最频繁的输入输出方式为: scanf 和 printf. scanf : 从输入设备(键盘)读取数据,并将值存放在变量中.printf: 将指定的文字/字符串输出到标准输出设备…

数据库约束与表的关系(数据库系列4)

目录 前言&#xff1a; 1.数据库的约束 1.1约束类型 1.1.1 not null 1.1.2 unique 唯一约束 1.1.3 default 默认值约束 1.1.4 primary key 主键约束 1.1.5 foreign key 外键约束 2.表的关系 2.1 一对一 2.2 一对多 2.3 多对一 3.新增 4.聚合查询 4.1聚合函数 4.…

Pinecone - 向量数据库

文章目录 关于 PineconeRoadMapSemantic SearchChatbots购买查看 API Key创建索引代码调用安装库 pinecone-client查看已经创建的索引创建索引插入数据获取索引统计分析信息查询索引,获取相似向量删除索引关于 Pinecone 官网 : https://www.pinecone.i

利用layui构建OA系统基本操作

一.编写方法&#xff08;增加&#xff0c;删除&#xff0c;修改&#xff0c;查询&#xff09; 通过继承BaseDao来实现通用&#xff0c;从而减少代码量&#xff0c;提高小路 1.增加 public int add(User user) throws Exception {String sql "insert into t_oa_user(name…

Windows7中使用SRS集成音视频一对一通话

SRS早就具备了SFU的能力&#xff0c;比如一对一通话、多人通话、直播连麦等等。在沟通中&#xff0c;一对一是常用而且典型的场景&#xff0c; 让我们一起来看看如何用SRS做直播和RTC一体化的一对一通话。 一、启动windows7-docker 二、拉取SRS镜像 执行命令:docker pull oss…

修改jar包中的文件内容

文章目录 导引查找是否存在需要修改的文件vim命令修改配置文件jar命令替换jar包中的文件(也可新增)解压jar包&#xff0c;修改后重新打包jar修改clas文件jar命令参数 导引 首先问问为什么要直接修改jar包中的文件&#xff0c;而不是重新打包&#xff0c;在非必要的情况下&…

【动手学深度学习】pytorch-参数管理

pytorch-参数管理 概述 我们的目标是找到使损失函数最小化的模型参数值。 经过训练后&#xff0c;我们将需要使用这些参数来做出未来的预测。 此外&#xff0c;有时我们希望提取参数&#xff0c;以便在其他环境中复用它们&#xff0c; 将模型保存下来&#xff0c;以便它可以在…

python数据挖掘基础环境安装和使用

文章目录 一&#xff0e;安装python环境二、库的安装2.1 使用pip命令安装virtualenvv扩展&#xff1a;cmd无法使用pip&#xff0c;报错&#xff1a;Fatal error in launcher: Unable to create process using ... 2.2 安装virtualenvwrapper-win2.3 新建一个用于人工智能环境的…

腾讯云 API 3.0(V3版签名) 通用接口 Delphi 版

目录 一、腾讯云API 3.0 简介&#xff1a; 二、Delphi 接口函数说明&#xff1a; 1. Delphi 接口包含的单元&#xff1a; 2. 同步调用和异步调用的区别&#xff1a; 3. 程序调用示例&#xff1a; 三、Delphi 版腾讯云API 3.0 版接口函数下载 四、演示程序录播 下载源程序…

DB2数据库SQL将不同行做合计

DB2数据库SQL将不同行做合计 案例&#xff1a; 将’GL’和’RZ’做合计&#xff0c;其他的不动。 SELECT SALE_TYPE,ROUND(CAST(SUM(aatp_weight) AS DOUBLE),2) AS aatp_weight FROM( SELECT CASE WHEN SALE_TYPE GL THEN RZ ELSE SALE_TYPE END AS SALE_TYPE, DEMAND_NUM / …

基于NXP iMX8MP处理器M7核心LVGL移植

By Toradex胡珊逢 LVGL (Light and Versatile Graphics Library)是一个轻量级的开源图形库&#xff0c;采用 C 或者 MicroPython 语言开发。可以在资源有限的 MCU 上轻松地绘制图形界面。Verdin iMX8M Plus 模块的处理器除了 Cortex-A53 核心外&#xff0c;还具有一个 Cortex-M…

如何运营校园外卖跑腿小程序

运营校园外卖跑腿小程序需要考虑多个方面&#xff0c;包括市场调研、合作伙伴选择、用户获取与留存、服务管理和推广等。下面是一些关键步骤和策略&#xff1a; 市场调研&#xff1a; 在开始运营之前&#xff0c;进行市场调研是非常重要的。了解目标用户的需求和习惯&#xf…

HarmonyOS学习路之方舟开发框架—方舟开发框架(ArkUI)概述

方舟开发框架&#xff08;简称ArkUI&#xff09;为HarmonyOS应用的UI开发提供了完整的基础设施&#xff0c;包括简洁的UI语法、丰富的UI功能&#xff08;组件、布局、动画以及交互事件&#xff09;&#xff0c;以及实时界面预览工具等&#xff0c;可以支持开发者进行可视化界面…

JVM系列(6)——类加载器详解双亲委派

一、类加载器 类加载器是一个负责加载类的对象&#xff0c;用于实现类加载过程中的加载这一步。 主要作用就是加载 Java 类的字节码&#xff08; .class 文件&#xff09;到 JVM 中&#xff08;在内存中生成一个代表该类的 Class 对象&#xff09;。 加载过程可以看 JVM系列&a…

人工智能-反向传播

前面阐述过&#xff0c;在设计好一个神经网络后&#xff0c;参数的数量可能会达到百万级别&#xff0c;利用梯度下降去更新参数计算复杂&#xff0c;算力不足&#xff0c;因此需要一种有效计算梯度的方法&#xff0c;这种方法就是辛顿提出的反向传播&#xff08;简称BP&#xf…

【算法基础】搜索与图论

DFS 全排列问题 842. 排列数字 - AcWing题库 #include<bits/stdc.h> using namespace std; const int N10; int n; int path[N]; bool st[N]; void dfs(int x) {if(x>n){for(int i1;i<n;i) cout<<path[i]<<" ";cout<<endl;return ;…

高级测试工程师求职之路:从笔试到面试,我经历了什么?

最近行业里有个苦涩的笑话&#xff1a;公司扛过了之前的三年&#xff0c;没扛过摘下最近的一年&#xff0c;真是让人想笑又笑不出来。年前听说政策的变化&#xff0c;大家都满怀希望觉得年后行情一片大好&#xff0c;工作岗位激增&#xff0c;至少能有更多的机会拥抱未来。然而…

【每日一题】979. 在二叉树中分配硬币

【每日一题】979. 在二叉树中分配硬币 979. 在二叉树中分配硬币题目描述解题思路 979. 在二叉树中分配硬币 题目描述 给你一个有 n 个结点的二叉树的根结点 root &#xff0c;其中树中每个结点 node 都对应有 node.val 枚硬币。整棵树上一共有 n 枚硬币。 在一次移动中&…

计算机体系结构基础知识介绍之线程级并行性及其利用

线程级并行&#xff08;Thread Level Parallelism&#xff0c;TLP&#xff09;是指在多个处理器或多个核心上同时执行多个线程&#xff0c;从而提高程序的性能和吞吐量。线程级并行的基本原理是利用程序中的数据或任务的并行性&#xff0c;将程序划分为若干个相对独立的子任务&…

c++游戏小技巧7:system 综合

目录 1.日常前言&#xff1a; 2.system 1.换标题&#xff1a;title 2.更改运行框大小 mode cons 3.清屏 cls 4.关机 shutdown 1.电脑多累啊&#xff0c;让他休息一下吧(直接关机) 2.电脑虽然很累&#xff0c;但是还想工作一会再睡(定时关机) 3.不让电脑休息的极其不友善…