在前文中已讲到,有赞的h5页面中,静态资源分为两种,一种是有赞统一的css/js等资源,一种是商家端独有的商品图片等资源。
针对这两种资源类型,我们采取了不同的策略:如何发现资源变化、如何更新资源缓存?
先让我们看一下我们整体的系统运行图:
图片的上半部分描述的是商家独有的图片等资源的更新过程; 而有赞的统一的css/js等资源,则是采用后台定时任务来刷新,监控前端发布。
每一个静态资源(css/js/图片等)都会在我们的goldwing后台系统中生成一条数据库记录,记录它的唯一key、路径、修改时间、所属业务、所属商家等信息。如:
每当资源变化后,根据一定的策略,都会先更新到这个数据库,客户端APP根据lastUpdateTime
发起sync
增量查询,就能拉取所有已变化的资源列表。根据这份列表将资源下载到本地,更新缓存即可。
有赞的前端一般都有固定的发布窗口,每次发布可能会引起一些css/js等静态资源的变更。
而所有当天的总的css/js资源配置都会有一个清单文件在前端指定的git代码库中。
根据上面的两个条件,只要在发布窗口之后,从git代码库读取当天最新的css/js资源清单文件,将每天记录与金翅数据库中已有记录做对比,更新数据即可。我们在金翅的后台做了定时任务管理,可以很方便的触发这类型的定时任务:
对于商家端特有的图片资源(如:商品图片),并没有一个统一资源清单来记录当前最新的资源列表,也没有一个固定的时间窗口来发布这类资源(商家随时有可能上传新的商品图片)。
所以针对商家端特有资源的更新与发现,必须采用在线实时、闭环反馈的自学习方式——使金翅系统具备自动发现新资源、自动更新缓存的能力。
仔细观察金翅的系统架构与资源运行图,app1
与app1'
是来自同一个商家端的两个用户。
假如:现在这个商家上传了新的商品图片r1
、更新了店铺的首页或者商品详情页面。 用户app1
先打开了这个h5页面p1
,那们在app1
加载静态资源过程中会发现这个新的资源r1
,在资源的缓存池中是不存在的。那么就有理由相信,r1
可能是一个新的资源。
app1
将r1
上传到金翅后台,更新资源数据库。
当app1'
启动,发起资源sync
查询,就能将r1
这个资源预先下载到自己的缓存中。当app1'
真正开始加载p1
这个页面,它所需要的r1
这个资源,已经下载好了。
我们再来回顾上述过程,对于用户app1
来讲,他其实没有获得静态资源r1
的加速体验,但他发现了r1
这个新资源,并把这个知识贡献给了金翅后台。
对于用户app1'
来讲,他从app1
的贡献受益。还有更多的类似于app1'
的用户他们都从第一个发现r1
这个新资源的app1
这里受益。
我们有更多的像app1
这样的用户,可能去发现渚如r2
、r3
、r4
。。。这类的新资源。然后把这些新资源贡献给其他用户。我们把这类贡献知识的用户称为种子用户。
理论上来讲,只要有足够多的种子用户,遍历了这个商家端的所有页面,那么所有新的资源变化都会被发现。在此之后的所有静态资源请求,都会命中缓存。(当然,现实条件中,我们要考虑用户的网络、存储代价,并不会下载所有静态资源,也不会在非wifi情况下下载图片)
我们不能让这个商家端的所有用户都能做upload
操作,这不仅是浪费用户流量,也会对金翅的后台服务器造成巨大压力和许多不必要的网络请求。
比如:一个商家端有一亿个用户,难道要让这一亿个用户都去上传自己未命中的新资源吗?一个商家端一段时间内的新资源个数肯定是有限的,最多几百个而已。以一亿对几百,这样上传的资源重复率可见会有多高,根本不值得也无必要。
所以我们抽象了种子用户的概念,比如从上述商家端的一亿个用户中选择30个用户,他们可以上传自己发现的新资源。如果这30个用户选得合适,基本就可以将这个商家端所有的新资源全部找出来了,而后台的压力也大减小了。
关于种子用户的选择策略,首先得清楚,什么类型的种子用户算是比较优质的?那些经常打开页面、页面路径进入得比较深的就是合适的优质种子用户来源。完全可能根据这个特点选取新的种子用户,淘汰旧的种子用户。
我们在金翅的后台系统中,对种子用户会下发一个证书,作为种子用户的标识。这个证书的字段包括:用户端的uuid、生成时间、过期时间等。金翅客户端根据这个证书的有无及有效性来判断是否可以上传未命中的新资源。证书的下发是随着资源sync
接口一起返回的,因为每个用户都需要调用sync
,省去一次网络请求。
我们不能对所有的页面做上述upload
策略,因为有许多页面是用户独有的、隐私的。比如:客服聊天面、购物车、订单页。这些页面里的图片等静态资源只有这个用户才有。
所有upload
模块需要对页面做一些限制,只有满足条件的页面才能执行。
有赞的页面链接中,可以根据页面链接的模式来判断是什么页面类型。比如:店铺主页一定会匹配(?<=v2\/showcase\/homepage\?).*alias=(\w+)
模式,商品详情页则会匹配(?<=v2\/goods\/)(\w+)
。只有当前页面链接能够匹配限定的模式才能执行upload
策略。
在金翅的管理控制台可以动态的配置upload
开关及允许upload
的url链接模式: 上述配置实际允许了店铺首页/微页面、商品详情等页面。
对于未命中的资源,还有两个问题要处理。
WebViewClient.shouldInterceptRequest(view, url)
要求加载链接为url
的这个资源时,本地资源缓存中没有,咋办?直接返回null
,让WebView
自己去下载?然后为了下次缓存能够命中,在native端再去下载一份放到缓存中?两倍的流量消耗!
为了减少流量消耗,我们采用 native 代理 webview 请求的方案,在代理过程中将缓存写入磁盘
在 Android 端我们使用OkHttp
或URLConnection
代理这个url
资源的请求,构造inpustream wrapper
,一边向WebView
返回数据流,一边将文件写到本地缓存中。
在 iOS 端我们使用 NSURLProtocol 代理 WebView 中的资源请求,在将 data 返回给 client 的同时,写入本地文件。
public class InputStreamWrapper extends InputStream {
private InputStream inputStream; // 从OkHttp获取的资源文件网络输入流
private FileOutputStream storingOutputStream; // 要存入的本地缓存文件输出流
@Override
public int read(final byte[] b, int off, int len) throws IOException {
int c = inputStream.read(b, off, len);
if (c != -1) {
storingOutputStream.write(b, off, c);
}
return c;
}
@Override
public void close() throws IOException {
super.close();
int bufLen = 4096;
byte[] buf = new byte[bufLen];
int c;
// the stream may have not been read to end
// 注:对于一些大的资源文件(300k以上),
// 从WebView调过来的read方法可能未读完所有数据,这里需要确保将网络输入流读完。
// 防止写到本地的资源不完整,造成加载错误。
while((c = inputStream.read(buf, 0, bufLen)) != -1) {
storingOutputStream.write(buf, 0, c);
}
inputStream.close()
storingOutputStream.close();
storingOutputStream = null;
}
}
这里对页面加载性能影响是比较小的,因为read()
和close()
方法都是在WebView
的子线程中完成,往文件流写的操作也是异步的。
当然可以将此资源所有文本内容下载完后再整体转码再交给WebView
,但这就丧失了WebView
流式渐进解析的优势。
我们采用了将网络输入流InputStream
和输出流(缓存文件及WebView
端读取)做了一次转接,套在PipedOutputStream
和PipedInputStream
两端,将网络输入流读入,转换成UTF-8编码,再通过PipedOutputStream
输出给PipedInputStream
交给WebView
读取。
在资源缓存中,缓存数据占用的存储超过预先设定值时,我们采用了最近使用的LRU队列来淘汰老的资源。确保我们的资源缓存足够轻巧、有效!
并且,css/js等资源与图片资源是在两个分开的LRU队列中。因为它们的大小、数量、更新速度差异较大。分开维护,以免将一些重要的资源挤掉。
当系统上线之后,怎么知道系统运行的好坏?我们对资源命中率及白屏时间做了统计。
资源命中率的计算倒好说,当前加载的页面,命中了多少,未命中的多少。一清二白,很好计算。
对于白屏时间的统计就要麻烦得多了,先看下图:
这是一个html加载解析的全过程、各个阶段。在WebView
的window.performance.timing
对象中,包含上述所有字段的属性值。各阶段的口径定义如下:
通过jsbridge方法读取到window.performance.timing
对象后,将responseStart-navigationStart
计为白屏时间。
有赞的静态资源的缓存设计是相当复杂的!要处理实时、及商家端特有性等诸多问题。更考虑到了资源缓存的发现、生成、统计、清理等完整的生命周期,及stream引流优化等。
这套静态资源缓存体系,即解决了二次及以后打开 页面的资源命中问题,也解决首次打开的命中,完整提升资源命中率。
借助静态资源缓存链路的建设,我们将金翅的移动端、java后端、nodejs管理端全平台都搭建起来了。为实现html的加速、动态化配置管理、应用接入建立了良好的根基。
欢迎关注我们的公众号
Original url: Access
Created at: 2019-09-26 18:06:03
Category: default
Tags: none
未标明原创文章均为采集,版权归作者所有,转载无需和我联系,请注明原出处,南摩阿彌陀佛,知识,不只知道,要得到
java windows火焰图_mob64ca12ec8020的技术博客_51CTO博客 - 在windows下不可行,不知道作者是怎样搞的 监听SpringBoot 服务启动成功事件并打印信息_监听springboot启动完毕-CSDN博客 SpringBoot中就绪探针和存活探针_management.endpoint.health.probes.enabled-CSDN博客 u2u转换板 - 嘉立创EDA开源硬件平台 Spring Boot 项目的轻量级 HTTP 客户端 retrofit 框架,快来试试它!_Java精选-CSDN博客 手把手教你打造一套最牛的知识笔记管理系统! - 知乎 - 想法有重合-理论可参考 安宇雨 闲鱼 机械键盘 客制化 开贴记录 文本 linux 使用find命令查找包含某字符串的文件_beijihukk的博客-CSDN博客_find 查找字符串 ---- mac 也适用 安宇雨 打字音 记录集合 B站 bilibili 自行搭建 开坑 真正的客制化 安宇雨 黑苹果开坑 查找工具包maven pom 引用地 工具网站 Dantelis 介绍的玩轴入坑攻略 --- 关于轴的一些说法 --- 非官方 ---- 心得而已 --- 长期开坑更新 [本人问题][新开坑位]关于自动化测试的工具与平台应用 机械键盘 开团 网站记录 -- 能做一个收集的程序就好了 不过现在没时间 -- 信息大多是在群里发的 - 你要让垃圾佬 都去一个地方看难度也是很大的 精神支柱 [超级前台]sprinbboot maven superdesk-app 记录 [信息有用] [环境准备] [基本完成] [sebp/elk] 给已创建的Docker容器增加新的端口映射 - qq_30599553的博客 - CSDN博客 [正在研究] Elasticsearch, Logstash, Kibana (ELK) Docker image documentation elasticsearch centos 安装记录 及 启动手记 正式服务器 39 elasticsearch 问题合集 不断更新 6.1.1 | 6.5.1 两个版本 博客程序 - 测试 - bug记录 等等问题 laravel的启动过程解析 - lpfuture - 博客园 OAuth2 Server PHP 用 Laravel 搭建带 OAuth2 验证的 RESTful 服务 | Laravel China 社区 - 高品质的 Laravel 和 PHP 开发者社区 利用Laravel 搭建oauth2 API接口 附 Unauthenticated 解决办法 - 煮茶的博客 - SegmentFault 思否 使用 OAuth2-Server-php 搭建 OAuth2 Server - 午时的海 - 博客园 基于PHP构建OAuth 2.0 服务端 认证平台 - Endv - 博客园 Laravel 的 Artisan 命令行工具 Laravel 的文件系统和云存储功能集成 浅谈Chromium中的设计模式--终--Observer模式 浅谈Chromium中的设计模式--二--pre/post和Delegate模式 浅谈Chromium中的设计模式--一--Chromium中模块分层和进程模型 DeepMind 4 Hacking Yourself README.md update 20211011
Laravel China 简书 知乎 博客园 CSDN博客 开源中国 Go Further Ryan是菜鸟 | LNMP技术栈笔记 云栖社区-阿里云 Netflix技术博客 Techie Delight Linkedin技术博客 Dropbox技术博客 Facebook技术博客 淘宝中间件团队 美团技术博客 360技术博客 古巷博客 - 一个专注于分享的不正常博客 软件测试知识传播 - 测试窝 有赞技术团队 阮一峰 语雀 静觅丨崔庆才的个人博客 软件测试从业者综合能力提升 - isTester IBM Java 开发 使用开放 Java 生态系统开发现代应用程序 pengdai 一个强大的博主 HTML5资源教程 | 分享HTML5开发资源和开发教程 蘑菇博客 - 专注于技术分享的博客平台 个人博客-leapMie 流星007 CSDN博客 - 舍其小伙伴 稀土掘金 Go 技术论坛 | Golang / Go 语言中国知识社区
最新评论