目录
场景描述:
平台接入了大量的图片,稳定情况下大概1200张/秒;现有架构下内部系统需要先将这部分数据落地并生成结构化URL存储到分布式存储中。同时,需要将已经落地的图片上传至三个第三方平台。目前遇到的问题是推送服务大面积报错,原因主要是图片下载失败,目前的存储/读取流程如下:
主要问题表现在,从接入到存储出现大面积写入失败,下载服务也大面积失败,报错信息是从存储集群获取连接失败;
历史优化措施
之前是做过一些优化,稳定了一段时间,随着发送的第三发越来越多,最终难以维持。
-
针对存储集群的连接进行了优化,针对客户端连接进行了优化
-
存储层面针对读写进行了分离
可行性分析
硬件设备
166个STORAGE +4Tracker ,之前已经分别针对Storage和Tracker进行了多次优化,Tracker进行了读写分离(两读两写);
图片下载服务有8台;
流量分析
平均单张图片大小:150 K
平均每条记录图片数
-
200KB全景图+4KB号牌特征图+110KB车牌特征图
-
5张
QPS:300/S【每条记录3-5张图片,后边统一为4】
数据推送方:3
输入总流量:200M/S
输出总流量:200*3=600M/S 【估算,应用程序访问的忽略】
单个下载服务流量=600M /8=75M/S=150张/秒【在增加数据推送可能会以倍数递增】
峰值预估【单个服务】
实时场景: 300/S
积压1分钟]:1800/S =74*60=9000张/S
积压5分钟:9000/S=540000张/S [135000记录/S]
消费者限流:1000/S 【限流的阀值是要保证积压趋向于缓解】
缓存预估
缓存最近5分钟的图片;
缓存总大小: 150 K*4*300*60*5=5.2G
单个服务缓存大小: 28125/8=6.5G 【加上服务自用内存大体上可以分配8G左右的内存】;
缓存总记录数:300*4*5*60=360000张
单个服务缓存记录数:45000
其它粒度依此类推;
峰值流量处理
上传服务是分布式服务,部分场景下允许积压,极容易出现并发瞬间到达数万级别. 很容易在启动时就将下载服务DOWN掉。
合理的解决方案是进行必要的限流,由于使用了缓存,在缓存预热期间和缓存预热后预估的吞吐量差距较大,极端场景下会出现数量级的差异。
限流需要考虑针对总吞吐量的限流,用来做服务级别的保护;同时要考虑资源级别的保护,对图片下载来说,可并发的存储访问数应该受到保护,避免将峰值流量传递到分布式存储系统,导致更严重的灾难。
可行性方案
源端转发
在图片数据未落地前直接进行转发操作;需要将部分逻辑前置;因为图片未落地降低了很大一部分由于存储的IO开销和网络延迟;
【新版的接口可以预留该模块,需要时允许进行扩展】;
分布式缓存
需要一定规模的缓存集群,分布式缓存由于跨主机的数据访问,相比本地缓存效率要低一些;通常对缓存的数据没有特殊要求,需要结合具体的实现来进行选择;
【新版URL设计中去除了与服务主机的强绑定关系,未来可以规划一个可插拔的缓存接口便于在大流量场景下的快速接入】
本地缓存
相比之下本地缓存效率更高,分布式环境下某些资源并不是每次都通过同一个本地节点进行访问,需要权衡本地缓存与分布式缓存的适用性;
我们当前的版本中图片同下载服务存在强绑定关系,基于本地缓存效果更明显
可行性实践
案例1
问题分析
现在表现出以下问题,下面结合以前的问题整理下:
- 实时数据接入量在300条/秒,每条记录3-5张图片,合计每秒需要存储1200张左右图片;每张图片大小平均在150KB左右;每秒写入流量在200M左右【稳定情况下】
- 实时数据推送,目前一条过车记录的图片【字节】,需要给三个第三方推送,输出输出总流量大概在500M/秒【稳定情况下】
- 曾经多次针对连接进行过优化(连接池、短连接),存储层面也进行或多次优化,系统现在仍然不稳定,最近一次出问题是由于服务器下电重启;
- 上传服务与下载服务竞争存储连接资源,如果下载出现峰值严重影响入库,直接导致入库操作失败;
基于Guava的本地缓存
现有设计中所有图片路径中绑定了唯一的图片下载服务实例,基于此在考虑对整体影响的情况下,在图片下载服务中使用本地缓存的方式可以大大提高实时图片读取的效率。
基于Guava的令牌桶限流算法
参数配置
- JVM 配置
-Xms4096m -Xmx8000m
- 缓存参数配置
#图片下载缓存优化参数
#最大缓存图片数
imagedownload.cache.maxSize=15000
#并发访问数量
imagedownload.cache.concurrencyLevel=8
#初始缓存大小
imagedownload.cache.initSize=500
#缓存过期时间[单位/S]
imagedownload.cache.liveTime=300
#图片下载限流配置
#接口限流
imagedownload.limit.permitsPerSecondForRequest=2000
#访问存储限流
imagedownload.limit.permitsPerSecondForDownload=100