一次静态资源URL请求到源站的过程可以大体分为三段:// ASpectJ插桩@Aspectpublic 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前缀 |
| image.xxx.com | /image |
| product.xxx.com | /product |
| community.xxx.com | /community |
/** * 收敛域名逻辑,进行域名与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;}
两种方案各有优劣,具体对比如下表:| 回源方案 | 优点 | 不足 |
| CDN边缘脚本重定向 | CDN服务侧实现,无需依赖阿里云OSS镜像回源 | 边缘脚本开发和维护成本较大执行重定向逻辑对性能有一定影响边缘脚本迭代上线的稳定性风险较大 |
| 阿里云OSS镜像回源 | 仅需进行镜像回源配置,无开发成本镜像回源可以将多个OSS资源迁移到统一OSS,对OSS进行了收敛镜像回源仅全网首次,后续请求直接返回资源副本,性能影响忽略不计 | 依赖阿里云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
}]
}
| 阶段 | 事项 |
| App端新版本开发及测试阶段 | T1测试环境AB实验开关关闭,通过白名单命中实验。 |
| 测试通过后到灰度前 | T1测试环境AB实验组开50%,观察所有安装测试包的内部同事是否有问题反馈。 |
| App灰度期间 | 线上AB实验组和对照组各开1%,观察线上稳定性。 |
| 正式发布 | 实验组和对照组逐步放量5%,10%,30%,50%。 |
| 实验组和对照组各开50%后 | 观察实验组与对照组数据,在此期间进行待收敛域名按单域名维度收敛放量。 |
| 待收敛域名按单域名维度收敛全量后 | 实验组扩大放量70%,90%,100%。 |
| 阶段 | 事项 |
| 放量前 | 制定灰度放量时间表。拉灰度放量通知群,便于线上变更及时通知运维及各业务方。配置OSS镜像回源规则,通知测试同学进行线下验证,包括: 命中AB实验组,域名收敛功能生效,使用拼接新URL且资源访问正常。未命中AB实验,域名收敛功能不生效,使用原URL且资源访问正常。 |
| 放量中 | 按放量时间表操作配置中心,发布新灰度量。线上变更同步到灰度放量通知群,at相关方共同关注各项指标。同步测试同学进行线上环境回归验证。观察各监控平台指标1个小时。 |
| 放量后 | 持续关注各项监控指标及用户反馈。 |
{
"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
}
/**
* 判断客户端网络是否正常
*/
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提供的回源热门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;
}
很好奇,就使用rubyonrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提
导读:随着叮咚买菜业务的发展,不同的业务场景对数据分析提出了不同的需求,他们希望引入一款实时OLAP数据库,构建一个灵活的多维实时查询和分析的平台,统一数据的接入和查询方案,解决各业务线对数据高效实时查询和精细化运营的需求。经过调研选型,最终引入ApacheDoris作为最终的OLAP分析引擎,Doris作为核心的OLAP引擎支持复杂地分析操作、提供多维的数据视图,在叮咚买菜数十个业务场景中广泛应用。作者|叮咚买菜资深数据工程师韩青叮咚买菜创立于2017年5月,是一家专注美好食物的创业公司。叮咚买菜专注吃的事业,为满足更多人“想吃什么”而努力,通过美好食材的供应、美好滋味的开发以及美食品牌的孵
我认为我的问题最好用一个例子来描述。假设我有一个名为“Thing”的简单模型,它有一些简单数据类型的属性。像...Thing-foo:string-goo:string-bar:int这并不难。数据库表将包含具有这三个属性的三列,我可以使用@thing.foo或@thing.bar之类的东西访问它们。但我要解决的问题是当“foo”或“goo”不再包含在简单数据类型中时会发生什么?假设foo和goo代表相同类型的对象。也就是说,它们都是“Whazit”的实例,只是数据不同。所以现在事情可能看起来像这样......Thing-bar:int但是现在有一个新的模型叫做“Whazit”,看起来
我有一个要在我的Rails3项目中使用的数组扩展方法。它应该住在哪里?我有一个应用程序/类,我最初把它放在(array_extensions.rb)中,在我的config/application.rb中我加载路径:config.autoload_paths+=%W(#{Rails.root}/应用程序/类)。但是,当我转到railsconsole时,未加载扩展。是否有一个预定义的位置可以放置我的Rails3扩展方法?或者,一种预先定义的方式来添加它们?我知道Rails有自己的数组扩展方法。我应该将我的添加到active_support/core_ext/array/conversion
参见下面的示例,我想最好使用第二种方法,但第一种也可以。哪种方法最好,使用另一种的后果是什么?classTestdefstartp"started"endtest=Test.newtest.startendclassTest2defstartp"started"endendtest2=Test2.newtest2.start 最佳答案 我肯定会说第二种变体更有意义。第一个不会导致错误,但对象实例化完全过时且毫无意义。外部变量在类的范围内不可见:var="string"classAvar=A.newendputsvar#=>strin
如果我构建了一个应用程序来访问来自Gmail、Twitter和Facebook的一些数据,并且我希望用户只需输入一次他们的身份验证信息,并且在几天或几周后重置,那会怎样是在Ruby中动态执行此操作的最佳方法吗?我看到很多人只是拥有他们客户/用户凭证的配置文件,如下所示:gmail_account:username:myClientpassword:myClientsPassword这看起来a)非常不安全,b)如果我想为成千上万的用户存储此类信息,它就无法工作。推荐的方法是什么?我希望能够在这些服务之上构建一个界面,因此每次用户进行交易时都必须输入凭据是不可行的。
我正在使用Devise在Rails应用程序中,并希望通过API公开一些模型数据,但应该像应用程序一样限制对API的访问。$curlhttp://myapp.com/api/v1/sales/7.json{"error":"Youneedtosigninorsignupbeforecontinuing."}很明显。在这种情况下是否有访问API的最佳实践?我更喜欢一步验证+获取数据,但这只是为了让客户的工作更轻松。他们将使用JQuery在客户端提取数据。感谢您提供任何信息!凡妮莎 最佳答案 我建议您按照以下帖子中的选项2:使用APIke
我正在开发一个Rails2.3.1网站。在整个网站中,我需要一个用于在各种页面(主页、创建帖子页面、帖子列表页面、评论列表页面等)上创建帖子的表单——只要说这个表单需要在由各种Controller)。这些页面中的每一个都显示在相应的Controller/操作中检索到的各种其他信息。例如,主页列出了最新的10篇文章、从数据库中提取的内容等。因此,我已将帖子创建表单移动到它自己的部分中,并将该部分包含在所有必要的页面中。请注意,部分POST中的表单到/questions(路由到PostsController::create——这是默认的Rails行为)。我遇到的问题是当Posts表单没有正
我希望Ruby的解析器会进行这种微不足道的优化,但似乎并没有(谈到YARV实现,Ruby1.9.x、2.0.0):require'benchmark'deffib1a,b=0,1whileb由于这两种方法除了在第二种方法中使用预定义常量而不是常量表达式外是相同的,因此Ruby解释器似乎在每个循环中一次又一次地计算幂常数。是否有一些Material说明为什么Ruby根本不进行这种基本优化或只在某些特定情况下进行? 最佳答案 很抱歉给出了另一个答案,但我不想删除或编辑我之前的答案,因为它下面有有趣的讨论。正如JörgWMittag所说,
我正在尝试从数据库中读取大量单元格(超过100.000个)并将它们写入VPSUbuntu服务器上的csv文件。碰巧服务器没有足够的内存。我正在考虑一次读取5000行并将它们写入文件,然后再读取5000行,等等。我应该如何重构我当前的代码以使内存不会被完全消耗?这是我的代码:defwrite_rows(emails)File.open(file_path,"w+")do|f|f该函数由sidekiqworker调用:write_rows(user.emails)感谢您的帮助! 最佳答案 这里的问题是,当您调用emails.each时,