背景

最近查看nginx log排查问题时,意外中发现重量级的主页 list api 304比例已暴跌至不到1%,之前该比例长期维持在30%以上,近期也未改动过相关逻辑,跟进后最终发现是服务端本地cache混用导致的问题。

304优化原因

app每次冷启初始化时都会请求重量级的HTTP主页list api,其会拉取全量1000个item(游戏关卡)组成的list数据一次性返回,返回的响应数据经gzip压缩之后依然有将近300KB大小(解压后近3MB),单api如此大的数据传输对于网络带宽和传输速度都有明显的影响。

该api返回包括静态数据和动态数据部分,静态数据变动一般由运营在后台配置修改导致,一周变动次数有限,动态数据则和用户自身游玩行为有关,整体而言对于该api相当一部分用户多次请求得到的数据应该都是相同的,所以去年和客户端一起对该api进行了HTTP 304优化。

304对客户端请求耗时与带宽影响分析

之前测试了日本与美国东海岸之间主页list api返回304与完整300KB大小的耗时对比,两者耗时相差超过600ms--作为对比服务端处理耗时upstream_response_time一直稳定在100ms左右。

从网络协议与数据传输的角度解释这一现象,C/S 首先建立TCP连接,而后基于TCP建立HTTPS连接,服务端收到客户端完整请求并通过一系列业务逻辑生成最终返回数据-无论是否启用304到这一步为止流程都是一样的。

接下来返回数据这一步就差别大了,如果返回数据很少--如1KB,一个TCP包即可将完整响应数据发挥给客户端,日本到美国东海岸ping耗时(RTT)约为160~200ms(以下取160ms为例),网络良好的情况下客户端将在服务端发送数据包后80ms收到该响应,而如果300KB数据,则需要至少200个TCP包(根据RFC6928定义TCP滑动窗口初始值为10MSS,后续根据网络情况会动态调整),最终体现为客户端完全收到300KB响应数据需要4个乃至更多个RTT(>=640ms)的时间,也就是300KB数据传输上比1KB数据传输要多耗时至少560ms,而实际上包越多,在TCP通信过程中发生丢包、重传、拥塞控制等意外情况的概率也就越大,其受网络不稳定因素影响而耗时更长的可能也要大得多。

至于所需带宽对比则很明显接近1:300。

去年对该接口做完304支持优化后,当时的效果是超过30%的请求都可以通过304直接返回,一个简单的304优化就能使该接口使用带宽资源降低近30%,对于美国地区用户而言超过30%请求耗时减少600+ms,平均耗时降低200+ms,应该说还是相当划算的。

304比例异常暴跌

测试环境尝试复现

偶然发现线上304比例暴跌后,首先尝试在测试环境复现问题,结果测试环境居然没法复现出来,无论是浏览器、curl命令还是app直接请求,结果都显示同一用户多次请求会正常返回304,陷入沉思==!

线上环境复现

测试环境无法复现,直接尝试线上环境验证,结果发现问题必现--对于同一个用户的连续两次请求,简单粗暴将两次api请求返回的数据copy到本地比对其变化,发现1000个item中有个别item有会多余的字段,看到字段名立刻明白了问题所在:本地cache被混用了。

本地cache的引入

对于该api返回的1000个item,每个item都需要单独调用normalize函数进行一系列的处理--如配置资源地址到完整资源包格式转换、动态文案组装等,即便单个item normalize调用只需要0.1ms,*1000之后也会变成100ms,所以服务端对于每个item的normalize结果都做了一个短期的本地cache,命中缓存的情况下1000个normalize所花费的时间由100ms减少成了数ms,这个优化已经上线很长时间,直到最近才发现它可能会导致304机制的失效。

对normalize函数启用本地cache的Python代码实现大概类似如下:

class ItemModel:
local_item_cache = LocalCache(10000)
@local_item_cache.cache(60)
def normalize(self, phone_type, item_id):
# 资源包格式转换、动态文案组装等
...
return self

cache混用问题的引入

近期app新增加了一个游戏模式,服务端对于该模式下的用户每次均会从一个100+item的池子中按一定策略随机选定一个返回,该item在返回前一样会调用normalize函数,如果命中本地cache直接返回,否则执行normalize函数并将结果存入本地cache后再返回。

问题出在这里:主页list item normalize与新游戏模式下item normalize使用的是同一个本地cache,出于节约拷贝开销的考虑,本地cache命中返回的其实是一个对象引用,而新模式在normalize之后,还会针对该游戏模式新增数个额外字段、并修改部分已有字段的内容-会有部分随机数值策略,这会直接修改掉底层本地cache缓存的实际对象内容,这样主页list api执行item的normalize时也会读到这些被额外修改过的缓存对象,同一个用户两次主页 list api请求如果读取到的1000 item中存在任意一个item受到新游戏模式修改影响就可能导致最终数据不一致--另外线上Python服务为多主机、多进程部署,每个进程都会维护自己独立的本地cache,此种情况下不同进程本地cache缓存的对象数据基本都是不一致的,用户请求可能落在任意进程上更加重了这种可能性--其返回的数据etag也会变化,也就不可能触发304机制了。

测试环境为什么无法复现呢?

因为测试环境就几个内部人员,同时存在请求主页list和新游戏模式游玩用户的时候很少--简单来说就是并发度不够,所以绝大部分情况下该问题很难触发。

后续处理

新游戏模式每次随机一个item返回其实并无太大性能开销,而且其请求量也并不算高,所以直接不使用本地cache每次都完整执行normalize函数即可,暂时也没必要为其单独开辟一个cache专用。

按以上方案处理上线后,主页list api的304比例直接恢复至30%~50%之间浮动。

转载请注明出处,原文地址:https://www.cnblogs.com/AcAc-t/p/http_304_and_local_cache_bug.html

参考

https://www.cnblogs.com/xiaolincoding/p/12732052.html

https://blog.csdn.net/sinat_20184565/article/details/104851413

https://www.cnblogs.com/acac-t/p/http_304_and_local_cache_bug.html

一个重量级HTTP api的304优化分析与突发失效问题解决的更多相关文章

  1. 一个扩展搜索API的优化过程

    概述 API 是一个服务的门面,就像衣装是人的形象一样. 优雅的 API 设计,能让业务方使用起来倍儿爽,提升开发效率,降低维护成本:糟糕的 API 设计,则让业务方遭心,陷入混沌. 本文将展示一个扩 ...

  2. 如何设计一个优秀的API(转载)

    最近在整理框架的一些 API,觉得很有必要总结一下 API 兼容性的设计.下图是我自己当下的一些总结,慢慢维护: 网上搜索了一下,一个多月前,“标点符”已经发布了下面这篇文章,觉得写得非常不错,转载于 ...

  3. 如何设计一个优秀的API(转)

    到目前为止,已经负责API接近两年了,这两年中发现现有的API存在的问题越来越多,但很多API一旦发布后就不再能修改了,即时升级和维护是必须的.一旦API发生变化,就可能对相关的调用者带来巨大的代价, ...

  4. [daily][optimize] 一个小python程序的性能优化 (python类型转换函数引申的性能优化)

    前天,20161012,到望京面试.第四个职位,终于进了二面.好么,结果人力安排完了面试时间竟然没有通知我,也没有收到短信邀请.如果没有短信邀请门口的保安大哥是不让我进去大厦的.然后,我在11号接到了 ...

  5. Mysql 索引优化分析

    MySQL索引优化分析 为什么你写的sql查询慢?为什么你建的索引常失效?通过本章内容,你将学会MySQL性能下降的原因,索引的简介,索引创建的原则,explain命令的使用,以及explain输出字 ...

  6. iOS离屏渲染之优化分析

    在进行iOS的应用开发过程中,有时候会出现卡顿的问题,虽然iOS设备的性能越来越高,但是卡顿的问题还是有可能会出现,而离屏渲染是造成卡顿的原因之一.因此,本文主要分析一下离屏渲染产生的原因及避免的方法 ...

  7. mySql索引优化分析

    MySQL索引优化分析 为什么你写的sql查询慢?为什么你建的索引常失效?通过本章内容,你将学会MySQL性能下降的原因,索引的简介,索引创建的原则,explain命令的使用,以及explain输出字 ...

  8. 一次 group by + order by 性能优化分析

    一次 group by + order by 性能优化分析 最近通过一个日志表做排行的时候发现特别卡,最后问题得到了解决,梳理一些索引和MySQL执行过程的经验,但是最后还是有5个谜题没解开,希望大家 ...

  9. 如何设计一个优秀的API

    如何设计一个优秀的API - 文章 - 伯乐在线 http://blog.jobbole.com/42317/ 如何设计一个优秀的API - 标点符 https://www.biaodianfu.co ...

  10. 【MySQL 高级】索引优化分析

    MySQL高级 索引优化分析 SQL 的效率问题 出现性能下降,SQL 执行慢,执行时间长,等待时间长等情况,可能的原因有: 查询语句写的不好 索引失效 单值索引:在 user 表中给 name 属性 ...

随机推荐

  1. 前端查询天气的html

    <html> <script src="https://code.jquery.com/jquery-3.5.1.min.js"></script&g ...

  2. 如何根据oops函数偏移快速定位源码?

    如何根据函数偏移快速定位源码? 在内核栈的输出中,你一定注意到每一个函数的输出格式都是函数名+偏移量,而这儿的偏移就是调用下一个函数的位置.那么,能不能根据函数名+偏移量直接定位源码的位置呢? 答案是 ...

  3. BigCode 背后的大规模数据去重

    目标受众 本文面向对大规模文档去重感兴趣,且对散列 (hashing) .图 (graph) 及文本处理有一定了解的读者. 动机 老话说得好: 垃圾进,垃圾出 (garbage in, garbage ...

  4. PostgreSQL 10 文档: PostgreSQL 客户端工具

    PostgreSQL 客户端应用   这部份包含PostgreSQL客户端应用和工具的参考信息.不是所有这些命令都是通用工具,某些需要特殊权限.这些应用的共同特征是它们可以被运行在任何主机上,而不管数 ...

  5. 王道oj/problem20

    网址:oj.lgwenda.com/problem/20 思路:层次建树,用递归的方法前序遍历 代码: #define _CRT_SECURE_NO_WARNINGS#include <stdi ...

  6. [docker]封装python的docker镜像

    前言 基于alpine的python镜像封装. docker pull python:3.10-alpine 准备 requirements.txt内容: fastapi uvicorn server ...

  7. [pandas]从多个文件中构建dataframe

    按列从多个文件中构建 假设有两个csv文件,列不相同,需要整合为一个dataframe,使用glob模块: from glob import glob import pandas as pd # gl ...

  8. 使用 Vue 实现页面访问拦截

    目录 1 Vue 路由与导航守卫 1.1 Vue 路由简介 1.2 导航守卫概述 2 实现访问拦截的核心概念 2.1 路由守卫介绍 2.1.1 前置守卫(beforeEach) 2.1.2 后置钩子( ...

  9. 微服务集成redis并通过redis实现排行榜的功能

    默认你已经看过我之前的教程了,并且拥有上个教程完成的项目, 之前的教程 https://www.cnblogs.com/leafstar/p/17638933.html 由于redis的安装网上教程很 ...

  10. 半导体行业通信标准SECS/GEM协议一看就懂的

    半导体行业通信标准SECS/GEM透析 HSMS通信的设备端通常为客户端(Equipment)(也可称为Active 在通信中主动连接对方的),工厂会部署服务端(Host)(也可称为Passive 被 ...