// ASpectJ插桩
@Aspect
public class OkHttpAspect {
/**
* 在okhttp3.OkHttpClient.Builder.new(..)方法后插桩,
* 可以保证OkHttpClient使用new方法或buidler方法创建都被覆盖
*/
@After("execution(okhttp3.OkHttpClient.Builder.new(..))")
public void addInterceptor(JoinPoint joinPoint) {
OkHttpClient.Builder target = (OkHttpClient.Builder) joinPoint.getTarget();
addMergeHostInterceptor(target);
}
/**
* 添加CDN域名收敛拦截器MergeHostInterceptor
*/
private void addMergeHostInterceptor(OkHttpClient.Builder builder){
// 避免重复添加,判断当前是否已经添加了拦截器,如果已添加则返回
for (Interceptor interceptor : builder.interceptors()) {
if (interceptor instanceof MergeHostInterceptor) {
return;
}
}
builder.addInterceptor(new MergeHostInterceptor());
}
}
原域名与Path前缀的一一映射表
原域名 | Path前缀 |
image.xxx.com | /image |
product.xxx.com | /product |
community.xxx.com | /community |
先替换image.xxx.com域名为统一域名cdn.xxx.com,再插入path前缀/image生成新URL。
原URL与新URL对比如下:
https://image.xxx.com/xxx.jpg替换为https://cdn.xxx.com/image/xxx.jpg
/**
* 收敛域名逻辑,进行域名与path映射
*
* @param urlStr 原url
* @param sourceHost 原域名
* @param targetHost 统一域名
* @param pathPrefix 原域名映射的path前缀
* @return 拼接好的新url
*/
public static String replaceMergeHostUrl(String urlStr, String sourceHost,
String targetHost, String pathPrefix) {
if (!TextUtils.isEmpty(urlStr)) {
//替换域名
urlStr = urlStr.replaceFirst(sourceHost, targetHost);
if (!TextUtils.isEmpty(pathPrefix)) {
//插入path前缀
StringBuilder urlStrBuilder = new StringBuilder(urlStr);
int offset = urlStr.indexOf(targetHost) + targetHost.length();
urlStrBuilder.insert(offset, pathPrefix);
urlStr = urlStrBuilder.toString();
}
}
return urlStr;
}
2.3 CDN服务侧分发源站
CDN边缘脚本阿里云官方文档:https://help.aliyun.com/document_detail/126588.html
编写CDN边缘脚本并部署在CDN服务节点上,支持将请求通过重定向的方式还原转发到源OSS。
方案2:阿里云OSS镜像回源
镜像回源阿里云官方文档:https://help.aliyun.com/document_detail/409627.html
对统一OSS配置镜像回源规则,当统一OSS不存在静态资源时发生404错误,触发镜像回源到源OSS。从源站成功拉取资源后在统一OSS存储一份副本,下次访问时统一OSS便直接返回存储的资源副本,不再触发镜像回源。
镜像回源原理示意图(来源于阿里云官方文档)
两种方案各有优劣,具体对比如下表:
回源方案 | 优点 | 不足 |
CDN边缘脚本重定向 |
|
|
阿里云OSS镜像回源 |
|
|
从改造成本及性能影响两方面考虑,最终选择“阿里云OSS镜像回源”作为CDN服务侧分发源站方案。
在统一OSS镜像回源配置规则中匹配path前缀,映射还原为原域名对应的源OSS,实现准确镜像回源到源OSS。
Path前缀与源OSS的一一映射表如下:
Path前缀 | 源OSS |
/image | image_oss |
/product | product_oss |
/community | community_oss |
实现客户端侧收敛、CDN服务侧分发源站后,再来看下客户端通过CDN服务请求静态资源的示例图:
左侧红框中为客户端侧收敛完成多对一改造的示例,右侧红框中为服务侧OSS镜像回源完成一对多改造的示例,架构上基本已经实现CDN域名收敛的目标。但我们还需要考虑如何保证功能上线阶段的稳定性及上线后域名收敛的灵活性。
客户端 | key | value | 描述 |
Android | merge_host_android | 1-开启,0-关闭(默认) | Android端CDN域名收敛 AB实验开关 |
iOS | merge_host_ios | 1-开启,0-关闭(默认) | iOS端CDN域名收敛 AB实验开关 |
在CDN域名收敛功能的关键代码逻辑处预埋日志(支持采样),上报到阿里云日志服务SLS,在SLS平台配置监控告警,便于及时发现线上异常进行处理。
CDN域名收敛功能代码级监控埋点定义
字段 | 描述 | 类型 | 是否必填 | 值 |
bi_id | 业务描述 | String | 必填 | "mergeHost" |
section | 代码执行关键点 | String | 必填 | "init_config_data" |
desc | 描述 | String | 可选 | "域名收敛数据初始化" |
host | 当前host | String | 可选 | "cdn.xxx.com" |
url | 当前url | String | 可选 | |
originHost | 原始host | String | 可选 | "image.xxx.com" |
code | http状态码 | int | 可选 | 5xx |
stack | 报错堆栈信息 | String | 可选 |
通过支持待收敛域名单独放量保证灵活性
客户端配置中心下发待收敛域名列表配置数据,按单域名维度支持待收敛域名动态下发及逐步放量。
{
"mergeHostConfigs": [{ //待收敛的CDN域名列表
"host": "image.xxx.com", //原域名
"pathPrefix": "/image", //原域名一一映射的path前缀
"rate": 1 //单域名收敛灰度率
}, {
"host": "product.xxx.com",
"pathPrefix": "/product",
"rate": 0.5
}, {
"host": "community.xxx.com",
"pathPrefix": "/community",
"rate": 0.1
}]
}
开发、测试、发布阶段的灰度放量流程
AB实验放量流程
阶段 | 事项 |
App端新版本开发及测试阶段 | T1测试环境AB实验开关关闭,通过白名单命中实验。 |
测试通过后到灰度前 | T1测试环境AB实验组开50%,观察所有安装测试包的内部同事是否有问题反馈。 |
App灰度期间 | 线上AB实验组和对照组各开1%,观察线上稳定性。 |
正式发布 | 实验组和对照组逐步放量5%,10%,30%,50%。 |
实验组和对照组各开50%后 | 观察实验组与对照组数据,在此期间进行待收敛域名按单域名维度收敛放量。 |
待收敛域名按单域名维度收敛全量后 | 实验组扩大放量70%,90%,100%。 |
阶段 | 事项 |
放量前 |
|
放量中 |
|
放量后 |
|
得物作为电商平台,无论是大促活动(如618,七夕节,双十一,双十二等)还是日常服务,都需要给用户提供稳定可靠的CDN服务。
阿里云CDN服务SLA仅支持99.9%,即每月存在43分钟线上服务不可用的风险 阿里云CDN服务SLA官方文档:http://terms.aliyun.com/legal-agreement/terms/suit_bu1_ali_cloud/suit_bu1_ali_cloud201803050950_21147.html?spm=a2c4g.11186623.0.0.7af85fcey4BKBZ
{
"hostListConfig": [{//CDN域名列表
"host": "cdn.xxx.com", //主域名
"rate": 1
},{
"host": "cdn-ss.xxx.com", //同厂商备用域名
"rate": 1 //单域名灰度率
},{
"host": "cdn-bak.xxx.com", //多厂商域名
"rate": 1 //单域名灰度率
}],
"reviveProbeInterval":600000, //域名复活探测时间间隔,单位ms
"configRequestInterval":600000, //配置兜底接口请求时间间隔,单位ms
"configVersion":1, //配置内容版本号,每变更一次配置就需要版本号+1
"publicIpList":["223.5.5.5","180.76.76.76"] //国内公共DNS服务IP
}
客户端网络状态判断方法:
通过 InetAddress.isReachable函数(Android)或者 ping命令(如:ping -c 1 223.5.5.5) 检测国内公用的DNS服务中 (配置下发IP列表,如:223.5.5.5, 180.76.76.76 ) 至少有一个能ping成功。
如果ping成功,说明客户端网络正常;如果ping失败,说明客户端网络异常。
/**
* 判断客户端网络是否正常
*/
public static boolean isNetworkStatusNormal() {
//IP列表中有一个公共IP可触达则认为网络状态正常
for (int i = 0; i < publicIpList.size(); i++) {
String ip = publicIpList.get(i);
try {
if (!TextUtils.isEmpty(ip)) {
InetAddress inetAddress = InetAddress.getByName(ip);
boolean result = inetAddress.isReachable(3000);
if (result) {
return true;
}
}
} catch (IOException e) {
DuLogger.t(TAG).e(ip + " test is bad, error e = " + e);
}
}
return false;
}
如果当前CDN域名不可用,遍历CDN域名列表,获取下一个可用CDN域名作为新的当前CDN域名。再次替换URL的域名为新的当前域名,发起静态资源网络请求,实现端侧域名自动容灾降级支持同厂商、多厂商容灾的能力。
如果CDN域名列表中所有域名都不可用,则执行兜底逻辑,还原使用CDN域名收敛前的原URL,再次发起请求。
域名自动容灾降级流程图
通过阿里云CDN提供的回源热门URL接口API查询热门URL的回源次数,然后统计Path维度回源次数。
Path维度监控报表示例
获取配置数据的专用API接口
接口定义:/app/config-center/module-config
请求方式:Post
//客户端获取最新的配置数据
private ConfigModel getConfigData() {
ConfigModel configModel = DataSource.getConfigCenterData();
ConfigModel apiConfigModel = DataSource.getConfigApiData();
//使用配置中心下发配置数据与兜底接口下发配置数据中最新的数据
if (configModel != null && apiConfigModel != null) {
if (configModel.configVersion < apiConfigModel.configVersion) {
configModel = apiConfigModel;
}
} else if (configModel == null && apiConfigModel != null) {
configModel = apiConfigModel;
}
return configModel;
}
CDN域名收敛功能上线后,TCP连接复用率明显提升,DNS解析失败、TCP建连失败导致的异常次数明显减少,双端异常率均有明显降低
支持阿里云&腾讯云多厂商容灾能力,用户网络正常情况下访问阿里云CDN服务请求资源失败(http code 5xx,或socket失败),自动重试切换备用域名走腾讯云CDN请求资源,SLA从单厂商99.9%提升到99.99%+。
HTTPDNS成本降低
阿里云HttpDNS服务成本降低24%
全网首次请求示例URL:https://cdn.xxx.com/image-cdn/app/xxx/identify/du_android_w1160_h2062.jpeg?x-oss-process=image//resize,m_lfit,w_260,h_470 二次请求示例URL:
https://cdn.xxx.com/image-cdn/app/xxx/identify/du_android_w1160_h2062.jpeg?x-oss-process=image//resize,m_lfit,w_760,h_1500
如示例URL,原图是一张宽1160、高2062的图片,因客户端View(宽260,高470)展示的需要,拼接了阿里云图片裁剪参数"x-oss-process=image//resize,m_lfit,w_260,h_470"。命中CDN域名收敛灰度后,全网首次使用替换为统一域名的新URL请求该图片。CDN服务无此URL缓存,回源到统一OSS,统一OSS触发镜像回源,携带请求参数给源OSS。源OSS会根据请求参数中的图片裁剪参数返回宽259、高473的缩略图给统一OSS,统一OSS将该缩略图存储做为原图副本。
通过CDN域名收敛我们不仅收获了CDN网络性能、稳定性上的提升,而且实现了多个域名的统一和规范,大大降低了后续CDN域名的优化和维护复杂度。另外,也支持了CDN主域名的容灾能力,保证了线上服务的稳定性。
*文/Aix
关注得物技术,每周一三五晚18:30更新技术干货