在 IOS DeepLink 方案中服务端只是负责重定向到一个 H5 中间页,因此不再赘述,下面我们主要介绍下安卓的 DeepLink 方案。
概要设计
投放链接设计
投放链接是投放到各个渠道的下载链接,需要考虑以下几点:
各个渠道链接规则不一样,保证我们链接规则能够覆盖所有渠道
通过我们的调研有些渠道只支持 Get 请求,有些渠道不允许带参数,有些渠道必须以.apk 进行结尾
投放方便,链接投放出去之后不需要再改动
由于投放链接是给到一些自媒体创作者,在给出链接之后能够保证从始至终都能下到最新的APP
充分利用 CDN
转转 APP、找靓机 APP 的包百兆左右,为了保证服务的稳定性同样为了节约带宽,尽量发挥 CDN 的作用把绝大多数请求让 CDN 服务器来进行处理返回
(1)兼容版本1.0
考虑到兼容各个渠道,某些渠道必须以 apk 结尾、某些渠道不支持Get请求带参数,采用什么方式?
既然不能带参数,那我们的参数信息可以直接拼到path中,参数以某种规则组装,服务端解析,需要的信息包括 APP 类型、渠道信息、DeepLink 链接信息、版本号等,简要设计出的投放链接 1.0 大致如下:
https://apk.zhuanstatic.com/deeplink/{appType}_{channel}_{version}_{deepLink}.apk
appType: APP 类型,目前支持转转和找靓机,可扩展,如 zhuanzhuan
channel:渠道类型,根据每个投放渠道单独设置渠道 id,如 douyin666
version:APP 版本号,如 9.0.0
deepLink:deepLink 信息,目前传输 deepLinkId,deepLinkId 和端内跳转链接的映射关系由后台维护,服务端通过映射关系拿到跳转链接打入 apk 包中,如 huishou
(2)升级版本2.0
1.0的版本号是直接写到 path 中的,这会造成很多隐患
这显然是不合理的,针对以上两点我们必然需要删掉 version,替代方案可以让服务端在处理下载请求的时候通过其他方式拿到版本信息,修正后的投放链接 2.0 如下:
https://apk.zhuanstatic.com/deeplink/{appType}_{channel}_{deepLink}.apk
(3)最终版本3.0
2.0中没有了版本信息进而导致相同的渠道投放链接是一致的,只要 CDN 中有老版本APP的缓存,下载的是缓存的老版本APP,无法获取最新APP
因此我们考虑中间做一次重定向,通过一个不接入 CDN 的固定链接去重定向到一个接入 CDN 的带版本号的链接,这样问题就迎刃而解了,因此投放链接 3.0 应运而生:
https://apk.zhuanzhuan.com/deeplink/{appType}_{channel}_{deepLink}.apk
https://apk.zhuanstatic.com/deeplink/{appType}_{channel}_{version}_{deepLink}.apk
apk.zhuanzhuan.com 不走 CDN,只是将链接中的版本号补全并重定向到走 CDN 的 apk.zhuanstatic.com ,这样在投放链接不变的情况下能保证用户下载到最新的包。
打包&下载
投放链接设计好之后,通过投放链接可以解析到一些参数信息,比如:
https://apk.zhuanstatic.com/deeplink/zhuanzhuan_douyin666_9.0.0_huishou.apk我们知道用户下载的是douyin666渠道转转9.0.0版本的包,并且APP打开后需要跳转回收的页面。
下载渠道包服务端逻辑主要分为两大块,第一部分是拿到相应版本的原始包,然后通过 APK Signature Scheme v2 方式将渠道号和 DeepLink 跳转链接打入原始包中获得渠道包,将渠道包提供给用户进行下载。
为了能应对 APP 升级和渠道投放带来的流量,尤其是 CDN 中还没有缓存的时候,避免大量请求将我们服务打垮,所以需要引入本地缓存,如何引入?
首先我们分析下服务端的主要逻辑找出不可变的数据,第一原始包肯定是不变的,第二在原始包相同的情况下如果 channel 和 deepLink 跳转链接是一致的,那我们打包出来的渠道包也相应是不可变的,因此我们可以针对这两部分来进行缓存。
接下来我们分析缓存选型以及缓存策略,本地缓存的组件有好多可选的,比如 Caffeine Cache、Guava Cache 等,网上关于他们的测评如下:
读场景性能对比
可以看到在读场景下,caffeine cache 是当之无愧的王者,而且我们的场景基本是接近 100%的读,所以我们优先选择了 Caffeine Cache。
以下是两个本地缓存策略介绍:
(1)一级缓存(渠道包)
/**
* 缓存高频渠道包文件
*/
private static final Cache<String, byte[]> channelFinalAppCache = CacheBuilder
.newBuilder()
.expireAfterAccess(1, TimeUnit.DAYS)
.maximumSize(15)
.build();
左右滑动查看完整代码
渠道包的缓存 key 是 appType+version+channel+deepLink,由于 channel 和 deepLink 组合的众多,通过分析之前的下载数据缓存最高频的 15 个渠道包就基本满足 90%以上的请求而且不至于占用太多的内存,而为了获取最高频的 15 个渠道,我们通过大数据平台以 T+1 的方式将渠道数据更新到数据库中,DeepLink服务通过定时任务读取数据库中的渠道数据刷新缓存。
(2)二级缓存(原始包)
/**
* 缓存原始包文件
*/
private static final Cache<String, byte[]> channelAppCache = CacheBuilder
.newBuilder()
.expireAfterAccess(2, TimeUnit.DAYS)
.maximumSize(10)
.build();
原始包的缓存 key 是 appType+version,由于我们只下载最新版本的包, APP 类型暂时只有转转和找靓机,所以我们设置最大数量 10 是足够的,在我们应用启动的时候会对这个缓存进行初始化,以避免第一次用户下载速度过慢,并在之后监听APP的发版信息,新版本更新后刷新缓存。
总结
DeepLink 服务支撑了新媒体投放以及 APP 内置更新的下载能力,为了保证服务稳定性和性能,除上述缓存策略外,还有其他策略来协同。
APP 发新版本时会进行 CDN 预热,将下载量高的渠道包缓存到 CDN 中,以使大部分流量能够在 CDN 服务器被消化,即使有突发流量打过来也会有限流规则过滤流量以保证服务的稳定性。
由于公众号平台改变了推送规则,如果你想多看到我们的文章,记得点一下在看和星标哦~