我是怎么从30个并发平均每个2000毫秒 到 300个并发平均每个178毫秒的

本文分享了将服务器性能从30并发平均2000毫秒优化至300并发平均178毫秒的实战经验,包括减少log打印、优化Redis交互、处理耗时操作、大文件缓存策略及异步处理技巧。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

最近一个多月一直在做服务器的性能优化,老大的要求是要做到300个并发,控制在200毫秒以内,就说说我最近做的内容吧。
从30个并发平均每个2000毫秒 到 300个并发平均每个178毫秒
简单介绍一下做了那些优化:
01、减少log日志的打印
02、减少redis的交互
03、耗时操作的处理
04、大文件信息的存储
05、python的缓存机制
06、异步处理非返回操作

一、定位耗时操作 -- 唯一变量法

由于之前一直定位错误,导致了很多弯路,现发现一个比较好的定位方法 --- 逐个函数定位
需要借用压测工具:wrk 或 jmeter

测试性能顺序:纯服务器性能 -- 加上参数处理时的性能 -- 加上第一个函数时的性能 -- 加上第二个函数时的性能 等等
如果在添加某一个步骤时性能变差很多,说明里有问题,需要仔细排查
简单放两个对比图

纯服务器性能 + 参数处理时的性能

纯服务器性能 + 参数处理时的性能 + 读取音频

我们发现两个性能测试只差一个读取音频函数 但形成却相差很多,说明在读取音频这里是一个巨大的耗时,那么就要想办法处理掉

二、性能处理

01、我们可能会感觉打印一个log不会是耗时操作,但通过唯一变量法发现 打印log也是耗时的,因为要控制在200ms以内,那就是任何耗时的都要深思熟虑,于是减少log的打印

02、当对redis做读取操作时,每次读取都要花费几毫秒,那就想办法优化甚至怎么减少redis的读取:

优化:
a、当能确认并必确认这是第一个存储并不需要获取时,就可以减少一次获取,直接存储。
b、要使用redis的缓存池
c、使用redis的通道法
减少判断:
先假想代码处理流程,中间用到了几次redis的读取,然后通过redis的INFO commandstats 命令,定位redis的耗时,以及有没有多余的操作

redis的耗时


数据信息为:操作次数 – 总耗时 – 平均耗时
这样我们就能清清楚楚的看到用到了几次读写操作,分别耗时多少
具体详见:
redis的使用,以及耗时定位

03+04+05、耗时操作的处理
一般指:mysql的读取 -- I/O操作
当频繁性的使用一个数据时就要想着做缓存处理,缓存也会考虑处理时间,个人感觉处理时间(如有不对,请斧正):
本地磁盘 > redis > 内存 > 机制化内存
通过”定位耗时操作 -- 唯一变量法“ 得知,音频文件的读取是一个很耗时的操作,那么就做缓存处理。

方法一:redis缓存

说到缓存数据,首先想到了内存性数据库redis,于是想办法将音频存至redis中,操作很简单,以音频名称为key值 -- 读取的信息为value进行存储(注意类型为bytes类型) + 过期时间(redis的存储大小为512M)
很快代码写完了,那就测测效果吧,一次效果还不错,提升了不少,但还是很耗时,而且与想象的相差很多,预想存储redis,读取都是几毫秒 最多也就10+毫秒的时间,为什么测试结果与预想结果查那么多,在redis读取那里加上时间,测一下读取时间,一看打印时间都在80+以上有的甚至到达150+,后来发现原因:数据过大,读取缓慢

方法二:cacheout缓存

于是将音频的数据存至内存中,发小效果不错,几乎不耗时,达到了理想状态。
转念一想,数据会一直累加与服务器内存,导致整个服务器增加,于是查找有效的缓存机制,就找到了cacheout缓存
它可以设置同时设置多个缓存,并且可以设置缓存机制(优先策略),设置有效条目数 以及 设置有效时间, 大多数操作基本和redis一样,简单易懂

方法三:redis + cacheout

(主要考虑到负载均衡,可能会有多个服务,但会公用一个redis)
按理说现在已经完全达到了要求,对音频(根据url下载的)的处理已经最优化了,但有一个问题是音频文件还一直存在于服务器内,增加内存,那就想办法移除。
于是就有了这个redis + cacheout的想法。
存储:
URL下载音频 -- 读取音频 -- 将音频信息存至redis 和 缓存中 -- 删除音频文件
读取:
获取音频名称 -- 缓存查找 -- redis查找 -- URL下载存储

06、异步处理非返回操作
一次请求处理中,打印log和发送日志,以及一些I/O是避免不了的,所以我们会用到子线程异步存储,让这些耗时的去一边处理,不影响主线的处理。
另加一个小点-如果你的用户请求是有顺序的,那么在存储redis时也可以用一下时间差,但一定要把握好!
我这里用的是twisted的threads

threads.deferToThread(save_user_info, "voice_body", voice, 1)

附送:使同步阻塞函数秒表非阻塞异步并发函数--twisted框架
一、使同步函数秒变异步并发函数
如果需要返回值, 如run2()函数
给请求函数添加装饰器@inlineCallbacks
并使用yield进行接收返回值

@inlineCallbacks
def run2():
    """
    主函数
    """
    # 将耗时函数放入另一个线程执行,返回一个deferred对象
     d = yield (threads.deferToThread(largeFibonnaciNumber))

     # 等待返回的结果 再做处理
     print(d)

二、如果不需要返回值可以使用addCallback回调函数 如run()函数

def run():
    """
    主函数
    """
    # 将耗时函数放入另一个线程执行,返回一个deferred对象
    d = threads.deferToThread(largeFibonnaciNumber)
    # 添加回调函数
    d.addCallback(fibonacciCallback)

具体代码见项目中的other目录

Git代码: 公众号后台回复 python_sanic

<think>好的,用户想了解JDK8中System.currentTimeMillis()的用途以及如何用它获取毫秒级时间戳。首先,我需要回顾一下这个方法的用途,它主要用于获取当前时间与Unix纪元时间的毫秒差。然后,具体到JDK8中的使用方式,可能需要说明它的基本用法和示例代码。 接下来,用户可能需要知道如何在实际代码中调用这个方法。比如,直接调用System.currentTimeMillis(),存储返回的long值。这时候应该给出一个简单的Java代码示例,比如打印当前时间戳,或者计算执行时间。 另外,用户可能关心这个方法在JDK8中的性能问题,因为在之前的引用中提到高并发下可能有性能影响。需要解释一下为什么会有这样的问题,比如每次调用都涉及系统调用,可能导致上下文切换,尤其是在Linux系统中通过gettimeofday实现。这里可以引用提到的源码部分,说明其底层实现。 还需要注意提到这个方法的一些特性,比如返回值的粒度取决于操作系统,可能不是精确到毫秒,某些系统可能只有10毫秒的精度。同时,对比一下System.nanoTime()的不同之处,虽然用户没有直接问,但可能作为相关知识补充。 用户的问题还涉及到如何正确获取毫秒数,所以要强调直接使用返回的long值就是毫秒数,无需额外转换。但如果是需要转换成日期格式的话,可能需要用到SimpleDateFormat或者Java 8的DateTimeFormatter,不过用户的问题可能更侧重于获取时间戳而不是格式化。 另外,根据引用中的信息,可能需要提到在高并发场景下的优化方法,比如缓存时间戳,或者使用单一后台线程更新时间,避免频繁系统调用。虽然用户没有直接问优化,但作为相关问题可能有用。 需要确保回答结构清晰,分点说明用途、使用方法、示例代码、注意事项,以及可能的性能考量。同时,引用相关的资料来支持解释,比如引用[1]和[4]提到的源码和性能问题,引用[2]和[3]关于方法的基本描述和使用示例。 最后,生成相关问题,比如性能影响的原因、与nanoTime的区别、优化方法等,帮助用户进一步探索。需要确保问题与主题相关,并且基于用户可能的兴趣点。</think>### JDK8中System.currentTimeMillis()的用途与使用方法 #### 1. 核心用途 `System.currentTimeMillis()` 是Java中获取**当前时间戳**的基础方法,其返回值为**UTC时间1970年1月1日0时0分0秒(Unix纪元)到当前时刻的毫秒数**。主要应用场景包括: - **性能统计**:计算代码执行耗时(如方法前后调用差值)[^4] - **时间戳生成**:为日志、数据库记录、分布式ID(如雪花算法)提供时间基准[^4] - **日期计算**:结合`java.util.Date`或`java.time`包进行日期转换与运算[^3] #### 2. 获取毫秒数的实现 在JDK8中直接调用该方法即可获得毫秒级时间戳: ```java long timestamp = System.currentTimeMillis(); System.out.println("当前时间戳:" + timestamp); // 输出示例:1717224000123 ``` #### 3. 底层原理与性能特性 - **底层实现**:通过JNI调用操作系统的`gettimeofday()`(Linux)或类似函数,获取秒级和微秒级时间后计算毫秒值[^1] - **性能问题**:每次调用涉及**用户态到内核态的切换**,高并发场景下频繁调用可能导致性能瓶颈(如每秒百万次调用时延迟显著增加)[^4] - **时间精度**:依赖操作系统支持,某些系统最小单位为10毫秒(如Windows早期版本)[^2] #### 4. 使用示例 ##### 示例1:计算代码执行时间 ```java long start = System.currentTimeMillis(); // 执行待测代码 Thread.sleep(1000); long end = System.currentTimeMillis(); System.out.println("耗时:" + (end - start) + "ms"); // 输出:耗时:1000ms ``` ##### 示例2:生成格式化时间 ```java SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String dateStr = sdf.format(new Date(System.currentTimeMillis())); System.out.println(dateStr); // 输出:2024-07-01 15:20:00 ``` #### 5. 注意事项 - **替代方案**:若需要更高精度(纳秒级)但不要求绝对时间,可改用`System.nanoTime()`[^2] - **并发优化**:高并发场景建议缓存时间戳(如每毫秒更新一次缓存值),减少系统调用次数[^4] - **时区无关性**:返回值始终基于UTC时区,需手动处理时区转换
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

阿三先生

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值