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

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

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

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

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

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

+参数处理时.png

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

+读取音频

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

二、性能处理

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一样,简单易懂

    # 判断是否存在
    cache["voice_store"].has(voice_id)
    # 根据key获取
    cache["voice_store"].get(voice_id)
    # 存储
    cache["voice_store"].set(voice_store_key, voice_body)
方法三: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目录

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

扫码关注云+社区

领取腾讯云代金券