写点什么

为 Ubuntu 设计快速缩略图服务

2015 年 8 月 19 日

最近, James Henstridge Xavi Garcia Mena Michi Henning 为 Ubuntu 和 Ubuntu Touch 实现了一个快速、可伸缩的缩略图服务

简介

手机和桌面应用都有很多场景需要使用缩略图服务。同时,有许多媒体类型需要生成缩略图,比如图片、音乐、视频等。为每种媒体类型设计独立的 API 会增加开发成本,且生成这些缩略图也需要消耗大量 CPU 资源,网络传输缩略图会消耗不少带宽。本文主要介绍一种通用的缩略图服务,它为开发者屏蔽了上述这些复杂内容,通过缓存等措施大大提升了缩略图服务的性能。

系统架构

外部 API

缩略图服务对外提供了三种 API。

  • QML API:通过注册为 QQuickAsyncImageProvider 的提供者,使得调用者能够通过传入特定的 URI 和参数,获取特定大小的本地或者远程缩略图文件

  • Qt API:提供了三个函数获取特定类型的缩略图

    • QSharedPointer getThumbnail(QString const& filePath, QSize const& requestedSize);
    • QSharedPointer getAlbumArt(QString const& artist, QString const& album, QSize const& requestedSize);
    • QSharedPointer getArtistArt(QString const& artist, QString const& album, QSize const& requestedSize);

    其中 getThumbnail 函数从本地媒体文件提取缩略图,getAlbumArt 和 getArtistArt 函数从远程图片服务器获取。这几个函数返回的 Request 对象,提供 downloadFinished 信号,调用者可以连接该信号异步获取缩略图数据。

  • DBus API:缩略图服务通过 DBus 注册了两个接口,分别为 com.canonical.ThumbnailerAdmin 和 com.canonical.Thumbnailer。前者提供了对缓存的操作和缓存状态查询,后者提供了 GetAlbumArt、GetArtistArt 和 GetThumbnail 三个函数,对应 Qt API 中的三个函数,获取不同类型的缩略图。

同时,缩略图服务还提供了命令行工具thumbnailer-admin,是得能够通过命令行操作上述两个 DBus 接口。
为了节省资源,DBus 接口由 DBus 服务启动,在 30 秒空闲后关闭。

图片提取模块

图片提取模块包括图中的音频、视频提取器,下载器和图片提取器。

音频、视频提取器使用 GStreamer 来解码音频和视频文件。由于 GStreamer 自身的稳定性问题,部分解码器可能会导致程序挂起失去响应,因此将和 GStreamer 交互部分单独封装成独立的可执行程序vs-thumb。主服务通过管道的形式和它交互,很好的避免了因为解码器崩溃导致的稳定性问题。

下载器提供 download_album 和 download_artist 两个异步函数,通过 Qt 的 QNetworkAccessManager 组件从 dash.ubuntu.com 下载图片。

图片提取器使用图片转换模块从本地图片中提取缩略图图片。

图片缩放和转换模块

图片缩放和转换模块主要负责将图片转换和缩放成 JPEG 格式的最终图片文件。该模块使用 Gdk-Pixbuf 库进行转换。

针对 JPEG 图片,图片缩放模块会通过 libexif 库试图读取图片的 EXIF 信息。如果图片的 EXIF 信息包含缩略图,且该缩略图的大小不小于目标缩略图大小,则图片缩放模块会使用 EXIF 中的缩略图进行缩放,以提高性能。

磁盘缓存模块

磁盘缓存包括三部分,全尺寸图片缓存、缩略图缓存、失败缓存。

  • 全尺寸缓存:主要保存从远程图片服务器获取的图片和从音频、视频文件中提取的图片。由于这些来源的图片获取成本比较高,这些图片以原始尺寸进行保存。默认该缓存大小为 50MB,采用最近最少使用方式进行替换,可以通过修改 data/com.canonical.Unity.Thumbnailer.gschema.xml 文件中的 full-size-cache-size 节点数据来重新设置缓存大小。
  • 缩略图缓存:保存为调用者生成的指定尺寸的缩略图。该缓存大小默认为 100MB,同样可以通过修改 thumbnail-cache-size 节点数据来重新设置缩略图缓存大小。
  • 失败缓存:保存由于异常导致缩略图提取失败的项。对于远程文件,可能是因为无法获取远程文件(文件不存在、授权失败等);对于本地文件,可能是因为文件损坏或者音频文件不包含插图等。失败缓存主要用于减少对已知错误的重复尝试,对于该缓存中的内容,采用最近最少使用和指定过期时间的方式控制缓存失效。

由于使用了三个缓存,缩略图服务在返回指定大小缩略图时的查找流程大致为:

  1. 检查缩略图缓存中是否已经存在指定大小的图片。如果存在则直接返回。
  2. 检查全尺寸缓存中是否有该图片的全尺寸副本。如果存在则使用全尺寸图片进行缩放,将缩放完成的缩略图添加到缩略图缓存后返回。
  3. 检查失败缓存中是否有对应的项,如果有则直接返回错误。
  4. 尝试从远程下载或者从原始文件提取缩略图。如果失败,则加入到失败缓存中,并返回错误。
  5. 如果原始文件是从远程下载,或者从音频、视频流中提取,将源文件加入到全尺寸缓存中。
  6. 缩放图片文件到指定大小,加入到缩略图缓存中,并返回。

性能提升

缩略图服务的主要耗时在基于网络的下载和基于 CPU 的图片提取。因此对性能的提升,主要考虑在网络 IO 和 CPU 利用率上。为了避免这两个耗时的操作阻塞其他请求,特别是能够可以从缓存模块中快速响应的请求,下载和图片抽取被放在独立的事件循环中,利用 Qt 的信号和槽机制,将耗时请求异步化。

对于缓存的测试,使用配置为Intel Ivy Bridge i7-3770k 3.5 GHz 处理器和256GB 固态硬盘的机器,测试数据为使用60 字节长度字符串作为键,使用平均大小为20KB 的随机二进制数据作为值,缓存大小为100MB。

缓存写入时间为大约2.8 秒,然后测试场景为80% 的缓存命中率,当缓存未命中时,在缓存中插入新的数据,触发缓存按照最近最少使用模式进行数据替换。在10 万次循环中,缓存每秒返回约4800 个“快照”,聚合读写吞吐率在每秒93MB。如果将缓存命中率提升到90%,每秒返回的记录数接近翻倍,达到了每秒7100 条记录。 [1]

总结

缩略图服务中的每个模块都有清晰的接口定义。支持新的媒体类型或者新的远程图片服务扩展非常容易,不会影响到现有代码。

为了尽可能的利用硬件资源,缩略图服务在针对长时间操作的异步接口使用了多线程的方式,提高系统 IO 利用率。

缩略图服务目前使用在 Ubuntu Touch 系统上,为图库、相机、音乐和其他使用媒体缩略图的应用提供缩略图服务。


感谢徐川对本文的审校。

给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ @丁晓昀),微信(微信号: InfoQChina )关注我们,并与我们的编辑和其他读者朋友交流(欢迎加入 InfoQ 读者交流群)。

2015 年 8 月 19 日 19:121088

评论

发布
暂无评论
发现更多内容

Lindorm云原生数据库 - 让数字时代IT运维系统“灵动”起来

许力

DevOps APM Data Lake AIOPS

JVM-技术专题-类加载机制

李浩宇/Alex

Java JVM

企业开发遇到瓶颈,何不换个新思路?快速开发了解一下

Marilyn

敏捷开发 快速开发

阿里P8大牛呕心沥血总结整理的《Java面经手册》,通过实践的方式向你深度讲解Java核心知识点

Java成神之路

Java 阿里巴巴 程序员 面试 编程语言

阿里内部《Java架构进阶宝典》,总结了基础、进阶、架构三个阶段的知识点

Java架构之路

Java 程序员 面试 算法 编程语言

有了TA,领域外企业里的小IT团队,也能轻松搞定大型项目

Marilyn

敏捷开发 快速开发

The story of programmers in famous enterprises.

Marilyn

敏捷开发 快速开发

高难度对话读书笔记—聆听篇

wo是一棵草

区块链USDT支付系统开发需要多少费用?USDT跨境支付

135深圳3055源中瑞8032

一线城市年轻人生活工作实录(程序员篇)

Marilyn

敏捷开发 开发者工具 快速开发

区块链钱包软件开发费用,区块链多币种钱包

135深圳3055源中瑞8032

图扑软件联手阿里Lindorm数据库开启工业物联超融合存储模式

许力

IoT AIOT

JVM-技术专题-深入理解内存结构

李浩宇/Alex

Java JVM

五年Java开发经验,4面阿里成功拿下offer,分享一下个人面经!

Java架构之路

Java 程序员 面试 算法 编程语言

年轻人大企打拼多年,刚升迁便遇巨大阻力难以解决,到底如何才能在职场中幸存?

Marilyn

敏捷开发 快速开发

来喽,来喽,Python 3.9正式版发布了~~~

华为云开发者社区

Python 编程

医院HIS故障,险引发人命关天大危机,竟被程序员轻松解决!

Marilyn

微前端之如何拆解React巨石应用 qiankun

SugarTurboS

项目管理 架构 React 微前端 前端性能优化

在线教育企业迎来“秋招”大考,数字用户体验成胜负关键手

BonreeAPM

运维 APM 在线教育 AIOPS 用户体验

华为云专家讲述知识图谱构建流程及方法

华为云开发者社区

华为 数据 知识图谱

Go语言内存管理三部曲(一)内存分配原理

网管

go 内存管理 内存布局

医疗AI系统构建(1)one-hot编码

刘旭东

人工智能 学习 医疗AI one-hot

一线城市年轻人生活工作录(业务员篇)

Marilyn

敏捷开发 快速开发

JVM-技术专题-对象的实例化过程

李浩宇/Alex

Java JVM

JVM-技术专题-Java类文件结构

李浩宇/Alex

Java JVM

阿里面试官纯手打:金九银十跳槽必会Java核心知识点笔记整理

Java架构追梦

Java 数据库 架构 面试 微服务

数字货币交易所开发,币币交易源码

135深圳3055源中瑞8032

百度人工智能OCR调用调试过程

tuuezzy

WSDM Cup 2020大赛金牌参赛方案全解析

华为云开发者社区

大数据 搜索 信息

架构师训练营 1 期第 4 周:系统架构 - 作业

piercebn

极客大学架构师训练营

合约跟单交易系统开发,一键智能跟单软件

135深圳3055源中瑞8032

NLP领域的2020年大事记及2021展望

NLP领域的2020年大事记及2021展望

为Ubuntu设计快速缩略图服务-InfoQ