iOS逆向 — 应用重签名学习 (实战总结)

一、前言
在日常正向开发打包提交时,我们都会对应用进行签名,然后上传到App Store。其中签名的这个过程 XCode 已经帮我们做过了,我们只需要配置好证书和描述文件就可以。但是如果我们希望学习其他的应用,进行一些逆向开发,就需要调试其他的应用,而我们首先就需要对这些应用进行重签名。

二、iOS双层签名机制
2.1、加密方式了解
首先,我们先了解下iOS签名中用到的加密方式,RSA & Hash。

**RSA加密:**一种非对称加密方式,也叫现代加密(区别与传统的对称加密)。这种方式的加密会生成一对公私钥,如果公钥加密,则用私钥解密,反之亦然。一般情况下,我们自己会保存私钥。

**对称加密:**通信的两端事先约定好一个密钥,使用密钥对明文进行加密,生成密文的加密方式。这种加密方式的特点是,加密效率高(相对于非对称加密),但一旦密钥泄漏,则安全隐患较大。

**Hash:**把任意长度的输入通过哈希算法变换成固定长度的输出,得到的结果是128位二进制,即32位16进制字符。Hash的特点是对于相同的数据会得到相同的结果,不同的数据一般会有不同结果(hash冲突除外)。因此,可以作为一种信息摘要,或者信息“指纹”,用来做数据识别。

如果你正在面试,或者正准备跳槽,不妨看看我精心总结的面试资料: BAT 大厂*新面试题+答案合集(持续更新中) 来获取一份详细的大厂面试资料 为你的跳槽加薪多一份保障

2.2、App Store应用签名验证
苹果为了保证安全性,除了对iOS系统做了很多安全的设计外,对于应用的验证及安装渠道的控制也做了很多事情。例如,一个应用如何从App Store下载到安装到手机上,会经历下面几个步骤:

1、苹果会生成一对公私钥,私钥苹果自己保存,公钥内置在iOS设备中

2、苹果对应用进行对称加密和签名,并用私钥对签名信息进行加密

上诉两个步骤在App上架完成前就会完成,以下步骤是下载到手机后发生的

3、应用下载到手机后,iOS系统使用公钥对签名信息验证,如果通过则安装到手机

4、在每次应用运行时,对应用进行解密操作

2.3、调试安装的签名验证
以上是从App Store下载应用的验证过程,但我们平时开发过程中的应用安装并非从App Store下载,也没有私钥进行加密,苹果会如何进行验证的呢?首先我们可以看下苹果的需求:

1、不需要从AppStore下载也可以安装

2、为了保证系统的安全性,App的安装不能脱离苹果的控制,需要经过苹果允许才可以安装,且不能导致非开发的App被安装

为了实现这一目的,苹果引入了一个双层签名的机制,大致流程如下图:

%title插图%num

双层签名流程整理下来步骤如下

1、在 Mac 上生成一对公私钥,记为 公钥M 和 私钥M;将包含 公钥M 的CSR文件上传到苹果服务器

2、苹果对 公钥M 做一次Hash签名,使用 公钥A 对签名信息进行一次加密,生成一个包含 公钥M 和 签名信息的证书

3、为了实现控制安装设备数量等需求,苹果生成一个包含证书、App ID、设备信息的描述文件,并返回给Mac电脑

4、在使用XCode打包过程中,使用先对应用包进行一次签名,再使用 私钥M 对签名信息进行一次加密

5、将应用包、加密签名及描述文件打包成安装包,发送到iOS设备,进行图中的第4步操作

三、iOS应用重签名
3.1 应用重签原理
iOS应用重签名:是指对一个应用重新进行签名,使其运行在我们的工程中。

通过上一节关于应用签名原理的介绍,我们发现如果将待签名应用的描述文件替换成我们的描述文件,并使用我们的证书对其再进行一次签名,就可以使得iOS系统认为这是我们自己的工程安装的应用,从而可以对其他应用进行分析与调试。

3.2 应用重签名步骤
我们以微信 7.0.8 的安装包为例进行重签。

3.2.1 前提
进行应用重签时,需要注意必须使用已经脱壳的应用进行,否则会签名失败。我们可以使用otool命令查看应用是否加壳

otool -l 应用可执行文件名称

%title插图%num

如图所示,cryptid 值为0,表示应用已经脱壳,如果值为1,则表示应用未脱壳。

壳:如2.2节所诉,苹果会对应用可执行文件及资源文件等进行加密,俗称加壳。如果想要重签调试应用,则先要解密,这个过程称为脱壳。

3.2.2 应用重签
1、我们需要先创建一个自己的工程,并运行到手机上,运行的目的是将我们的描述文件安装到手机上;同时在Product的应用包中找到我们的描述文件

%title插图%num
2、将脱壳的 WeChat 安装包中的plugin、Watch删除,因为普通的证书无法签名plugin和Watch,删除对重签微信应用也不影响。

%title插图%num

%title插图%num

3、对Frameworks进行重签,微信中包含6个Framework,需要一一进行签名,下图是 WeChat 7.0.8的Framework结构

%title插图%num

如果使用的WeChat 8.0.2版本,其Framework结构如下图,其中的库文件也均需要重签名

%title插图%num

首先使用 security 命令查看电脑上的证书,找到我们自己的证书

%title插图%num

security find-identity -v -p codesigning

使用codesign命令进行farmework的重签名(如果是微信8.0.2版本,会有很多dylib的swift库,这些也需要重签,命令与下文一致)

codesign -fs “证书名” 待签文件

%title插图%num

4、接下来需要进行描述文件的替换,找到*步保存的描述文件,放在 WeChat 的app包内。(如果待签的app包中包含描述文件,直接替换;微信中没有该文件,将我们的描述文件放入即可)。

%title插图%num
5、找到微信的 info.plist 文件,修改bundleId 为我们新建的工程的bundleId

6、查看描述文件,找到授权信息

使用如下命令查看描述文件信息

security cms -Di embedded.mobileprovision (embedded.mobileprovision为描述文件名称)

%title插图%num

拷贝 Entitlements 标签下的 dict标签对信息(保存了应用的授权信息),并保存到一个plist文件中

%title插图%num

此处为了方便在工程中新建了一个plist文件,也可以在其它地方新建。坑点:注意图中红色标记处可能为 R45LY736NP.*,需要手动将 * 替换成自己的 bundleId(这里替换为 ResignDemo),否则会后续在替换包时会失败,导致无法安装

7、将微信app包与上一步对plist文件放在一个文件夹下,使用如下命令对应用包进行重签
codesign -fs “证书” –no-strict –entitlement=plist文件名 xxx.app

%title插图%num

出现上图的 replacing existing signature 表示重签成功。

8、运行工程,将重签的微信安装包替换我们自己的工程的app包

%title插图%num

如图点击 加号,选择刚刚重签的微信app包,将ResignDemo工程的app包替换成如图所示即可。

9、此时手机的ResignDemo会被替换成微信,包括图标和名称都后替换,在XCode – Debug – Attach to process中找到*新WeChat进程,选中后将微信进程附加到 ResignDemo工程上,附加成功后即可进行View Debug,如图所示

%title插图%num

%title插图%num
四、总结
本次以微信为例进行了一次重签名实践,总结下主要步骤如下:

1.删除插件和带有插件的.app包(比如Watch)

2.对Frameworks里面的库进行重签名

3.给可执行文件 +x(可执行)权限(一般不需要,如果没有执行权限时,使用 chmod +x 文件名称 添加权限)

4.添加描述文件(新建工程,真机编译得到)

5.替换BundleID

6.通过授权文件(Entilements)重签.app包

以上就是总结的关于应用重签名的实践知识,如果有不正确的或者理解不到位的地方,欢迎大家指正?

8年iOS开发告诉你,为什么你突破不了自身技术瓶颈?

前言
尤其是在*近一段时间内,感觉一天天的时间过得又慢又快,慢的是感觉复工了以后在公司的8.9个小时简直算是煎熬了,快的是常常感觉时间一天天,一月月的过去了,可是发现自己还在原路踏步走。

看似每天忙成狗,回头却发现,月复一月,日复一日,薪资没涨一点,年龄越来越大了。不知道时间都到哪里去了,明明是每天兢兢业业的工作,可是怎么就得不到提升呢?其实是我们的学习效率太低,没有合理 的规划而已~

总结了一下为什么效率这么低,完全规划不起来呢?
我们在上学以来一直都羡慕那种玩的多但是考的又好的人,他学习的时候你也在学习,他玩的时候你也在学习,但实际上每次考试他都能*你一大截,有的时候我们看似都在安安静静的学习,但是效率却是个很大的差别,你学习两个小时的效果都不一定有别人半个钟头的学习效果好,这就是效率问题,那么为什么学习效率会有如此的差别呢?

在我看来,*重要的原因就是:方法
在这个知识膨胀的时代,每天面对铺面而来的海量信息,我们的学习也不再是按照以往传统的学习模式了,更重要的是我们要进行科学学习,

什么是学习?
学会学习才是根本,如何学会学习,那就要讲求方法了,我们经常看到一些学霸等传奇人物,听他们的经历,学他们的方法,路有千千万,但是你想过没有,也许适合你走的只有一条而已,所以,别人的不一定适合你,适合自己的才是*好的,但是学习效果不佳的原因却大致有以下几个:

没有正向反馈(学习到什么程度自己心里没数) 学习方法不对(用错误的方式去学习) 知识不成体系(学的知识很杂,东一块西一块) 有效时间不多(感觉没有多余的时间用在学习上)

关于“没有正向反馈”
这应该很好理解,这里的没有正向反馈指的就是我们在学习的过程中并不能看到感受到因学习而真正给我们带来有什么不一样的东西,哪怕是一种感觉,又或者是物质上的东西,总之,这是能够让我从内心觉得“学习真好”,可是我们往往却很少遇到。

我们总是希望付出了就能立马看到收获的,一旦达不到自己的期望,我们也就失去了激情,甚至开始抱怨,自然学习效率就会下降,所以学习中,我们一定要对正向反馈加以重视,这里说几点关于我学习中用到的正向反馈吧!

我学习编程有写博客的习惯,当我刚写完一篇博客的时候会有点小小成就感,随后我会发给我认识的一些前辈,很多都给予了肯定,哪怕是鼓励我我也高兴!
有的时候我也会给一些大牛投稿,一旦被采用,那种心情是相当的说不出口的(太高兴了)
在专业上我会在QQ群帮别人解答一些问题或者分享一些知识等
以上都是我的一些正向反馈,这些都能激励我更有动力的去学习,学习,你也应该有自己的正向反馈!

关于“学习方法不对”

这几天特别中意一句话“当你找到了适合自己的努力方式,你就如同万千牛人一样走在了光彩夺目掌声阵阵的人生征途上”,

这句话就强调了方法是多么的重要,有的时候我们很项目那些学霸,觉得是自己智商不够,其实不然,*重要的是那些所谓的学霸都有自己的一套学习方法。我们可以扪心自问,我们大多数人每天都在学习,但是又有多少人真正思考过自己学习方法的问题呢?我只是机械式的去学习而已,并没有想着找寻一套属于自己的方法体系,这就是我们与学霸的差别吧!

前几天在读美国作家本尼迪克特•凯里的《如何学习》一书,读完很受启发,其中书中提到了一种学习方法叫做“分散式学习”,讲述的是利用间隔效应去学习,通俗来说就是我们把大块的学习任务拆分开来去学,书中提到,你花两个小时一口气把一个知识点给学了没有分两天每天一小时的学习效果好,这就是方法

关于学习方法的探讨很多很多,只是我们貌似从来没有在意过,另外,切记只有适合自己的才是*好的,成功的人有很多,但是他们的路你不一定走得了,去寻找属于自己的学习方法论吧!

关于“知识不成体系”
不知道大家在学习的过程中有没有做笔记的习惯,我一直觉得做笔记是非常重要的,记得在高中的时候,我会给每一科都准备一个笔记本,上面写满了笔记,但是后来愈发的觉得笔记很乱,经常是一个知识点在很多页记着,这就导致了很多问题。

我们在回过头来说“知识不成体系”,确实如此,我们每天都在学习,每天都接收了大量的信息,而且这些信息又是零散的,不成体系的,如果我们再不加以记录,很容易遗忘,而且会感觉乱糟糟的,一点也不清晰明了,该怎么办?很简单,将我们学到的知识都记录下来,而且是分门别类的记录下来,将知识体系化,做一个属于自己的体系化知识库,这样我们不仅会对我们学习的知识有个全面的了解,而且非常方便我们的复习巩固等!还可以让我们看到哪地方是自己的弱势,方便弥补!这里以我自己举个例子,方便大家理解!

我在学习iOS的时候会将自己学到知识都分类别的进行整理,将自己学到的知识体系化,例如我会分成如下类别:

底层原理
iOS逆向
源码分析
数据结构和算法
设计模式
组件化
音视频
性能优化
项目管理
脚本辅助开发
再比如我在学习中制作的一个学习思路脑图,由于内容过多,这里就放上一个大概的思路脑图分享给大家,完整高清图可以看文末

%title插图%num

%title插图%num

有了以上的分类记录,我就对iOS的一个大概的知识体系有个了解,以后相关学习都会放在对应的目录下,平常看看,也知道自己哪方面不熟悉,随时复习巩固,做到对学到的知识心中有数,体系化知识构建不可缺少!

关于“有效时间不多”
我们看似每天从早到晚都在学习,但是不知道你与没有仔细的算过,一天里,你真正投在学习上的有效时间少的可怜,科学调查发现,你一天若能有效学习8个小时,你将超越百分之98的人,可是又有多少人做到,在每天的学习过程中,影响我们的太多了,有句话说的很好,当你无意间打开了一个手机网页,这正是你一天堕落的开始,很多细微的事情把我们的有效时间侵蚀的所剩无几。

如何提高自己的有效学习时间,从身边*简单的开始,学习的时候把手机放一边,电脑也不要登qq,静下心来,保持专注,看你一天能有多少有效的学习时间,试试吧!

关于我的iOS学习之路
不知不觉自己已经做了几年开发了,现在虽然是一个公司的TeamLeader,但是我知道必须不断的努力充实自己,才能立于不败之地,由记得刚出来工作的时候感觉自己能牛逼,现在回想起来感觉好无知。懂的越多的时候你才会发现懂的越少。

如果你的知识是一个圆,当你的圆越大时,圆外面的世界也就越大。

在我学习的过程中,*开始是在网上找了很多资料,毕竟这些资料是我们开始*快速的学习方法,这里我放上我这些年在网上收集到的资料,然后再以我的工作经验给大家总结一下,让你们少走些弯路,提取一些目前互联网公司*主流的iOS开发架构技术,希望能帮助到大家!

iOS优化篇之App启动时间优化

前言
*近由于体验感觉我们的app启动时间过长,因此做了APP的启动优化。本次优化主要从三个方面来做了启动时间的优化,main之后的耗时方法优化、premain的+load方法优化、二进制重排优化premain时间。

通常我们对于启动时间的定义为从用户点击app到看到首屏的时间。因此对于启动时间优化就是遵循一个原则:尽早让用户看到首页内容。

app启动过程
iOS应用的启动可分为pre-main阶段和main()阶段,pre-main阶段为main函数执行之前所做的操作,main阶段为main函数到首页展示阶段。其中系统做的事情为:

premain
加载所有依赖的Mach-O文件(递归调用Mach-O加载的方法)
加载动态链接库加载器dyld(dynamic loader)
定位内部、外部指针引用,例如字符串、函数等
加载类扩展(Category)中的方法
C++静态对象加载、调用ObjC的 +load 函数
执行声明为attribute((constructor))的C函数
main
调用main()
调用UIApplicationMain()
调用applicationWillFinishLaunching
通常的premain阶段优化即为删减无用的类方法、减少+load操作、减少attribute((constructor))的C函数、减少启动加载的动态库。而main阶段的优化为将启动时非必要的操作延迟到首页显示之后加载、统计并优化耗时的方法、对于一些可以放在子线程的操作可以尽量不占用主线程。

 

推荐阅读
iOS开发——*新 BAT面试题合集(持续更新中)
一、耗时方法优化
1.统计启动时的耗时方法
我们可以通过Instruments的TimeProfile来统计启动时的主要方法耗时,Call Tree->Hide System Libraries过滤掉系统库可以查看主线程下方法的耗时。

%title插图%num

也可以通过打印时间的方式来统计各个函数的耗时。

 

double launchTime = CFAbsoluteTimeGetCurrent();[SDWebImageManager sharedManager];
NSLog(@”launchTime = %f秒”, CFAbsoluteTimeGetCurrent() – launchTime);
复制代码

这一阶段就是需要对启动过程的业务逻辑进行梳理,确认哪些是可以延迟加载的,哪些可以放在子线程加载,以及哪些是可以懒加载处理的。同时对耗时比较严重的方法进行review并提出优化策略进行优化。

二、+load方法优化以及删减不用的类
2.1 +load方法统计
同样的我们可以通过Instruments来统计启动时所有的+load方法,以及+load方法所用耗时

%title插图%num

我们可以对不必要的+load方法进行优化,比如放在+initialize里。不必要的+load进行删减。

 

2.2 使用__attribute优化+load方法
由于在我们的工程中存在很多的+load方法,而其中一大部分为cell模板注册的+load方法(我们的每一个cell对应一个模板,然后该模板对应一个字符串,在启动时所有的模板方法都在+load中注册对应的字符串即在字典中存储字符串和对应的cell模板,然后动态下发展示对应的cell)。

即存在这种场景,在启动时需要大量的在+load中注册key-value。

此时可以使用__attribute((used, section(“__DATA,”#sectname” “)))的方式在编译时写入”TempSection”的DATA段一个字符串。此字符串为key:value格式的字典转json。对应着key和value。

#ifndef ZYStoreListTemplateSectionName
#define ZYStoreListTemplateSectionName “ZYTempSection”
#endif

#define ZYStoreListTemplateDATA(sectname) __attribute((used, section(“__DATA,”#sectname” “)))

#define ZYStoreListTemplateRegister(templatename,templateclass) \
class NSObject; char * k##templatename##_register ZYStoreListTemplateDATA(ZYTempSection) = “{ \””#templatename”\” : \””#templateclass”\”}”;
/**
通过ZYStoreListTemplateRegister(key,classname)注册处理模板的类名(类必须是ZYStoreListBaseTemplate子类)
【注意事项】
该方式通过__attribute属性在编译期间绑定注册信息,运行时读取速度快,注册信息在首次触发调用时读取,不影响pre-main时间
该方式注册时‘key’字段中不支持除下划线’_’以外的符号
【使用示例】
注册处理模板的类名:@ZYStoreListTemplateRegister(baseTemp,ZYStoreListBaseTemplate)
**/
复制代码

在使用时@ZYStoreListTemplateRegister(baseTemp,ZYStoreListBaseTemplate)即为在编译期间绑定注册信息。

读取使用__attribute在编译期间写入的key-value字符串。 关于__attribute详情可以参考__attribute黑魔法

#pragma mark – *次使用时读取ZYStoreListTemplateSectionName的__DATA所有数据
+ (void)readTemplateDataFromMachO {
//1.根据符号找到所在的mach-o文件信息
Dl_info info;
dladdr((__bridge void *)[self class], &info);

//2.读取__DATA中自定义的ZYStoreListTemplateSectionName数据
#ifndef __LP64__
const struct mach_header *mhp = (struct mach_header*)info.dli_fbase;
unsigned long templateSize = 0;
uint32_t *templateMemory = (uint32_t*)getsectiondata(mhp, “__DATA”, ZYStoreListTemplateSectionName, &templateSize);
#else /* defined(__LP64__) */
const struct mach_header_64 *mhp = (struct mach_header_64*)info.dli_fbase;
unsigned long templateSize = 0;
uint64_t *templateMemory = (uint64_t*)getsectiondata(mhp, “__DATA”, ZYStoreListTemplateSectionName, &templateSize);

#endif /* defined(__LP64__) */

//3.遍历ZYStoreListTemplateSectionName中的协议数据
unsigned long counter = templateSize/sizeof(void*);
for(int idx = 0; idx < counter; ++idx){
char *string = (char*)templateMemory[idx];
NSString *str = [NSString stringWithUTF8String:string];
if(!str)continue;

//NSLog(@”config = %@”, str);
NSData *jsonData = [str dataUsingEncoding:NSUTF8StringEncoding];
NSError *error = nil;
id json = [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:&error];
if (!error) {
if ([json isKindOfClass:[NSDictionary class]] && [json allKeys].count) {
NSString *templatesName = [json allKeys][0];
NSString *templatesClass = [json allValues][0];
if (templatesName && templatesClass) {
[self registerTemplateName:templatesName templateClass:NSClassFromString(templatesClass)];
}
}
}
}
}
复制代码

这样我们就可以优化大量的重复+load方法。而且使用__attribute属性为编译期间绑定注册信息,运行时读取速度快,注册信息在首次触发调用时读取,不影响pre-main时间。

三、二进制重排
自从抖音团队分享了这篇 抖音研发实践:基于二进制文件重排的解决方案 APP启动速度提升超15% 启动优化文章后 , 二进制重排优化 pre-main 阶段的启动时间自此被大家广为流传。

当进程访问一个虚拟内存Page而对应的物理内存却不存在时,会触发一次 缺页中断(Page Fault)。

二进制重排,主要是优化我们启动时需要的函数非常分散在各个页,启动时就会多次Page Fault造成时间的损耗。

3.1 获取Order File
本次主要是通过Clang静态插桩的方式,获取到所有的启动时调用的函数符号,导出为OrderFile。

Target -> Build Setting -> Custom Complier Flags -> Other C Flags添加 -fsanitize-coverage=func,trace-pc-guard参数

然后实现hook代码获取所有启动的函数符号。启动后在首页显示之后,可以通过触发下边-getAllSymbols方法获取所有符号。

#import “dlfcn.h”
#import <libkern/OSAtomic.h>
复制代码

void __sanitizer_cov_trace_pc_guard_init(uint32_t *start,
uint32_t *stop) {
static uint64_t N; // Counter for the guards.
if (start == stop || *start) return; // Initialize only once.
printf(“INIT: %p %p\n”, start, stop);
for (uint32_t *x = start; x < stop; x++)
*x = ++N; // Guards should start from 1.
}

//原子队列
static OSQueueHead symboList = OS_ATOMIC_QUEUE_INIT;
static BOOL isEnd = NO;
//定义符号结构体
typedef struct{
void * pc;
void * next;
}SymbolNode;

void __sanitizer_cov_trace_pc_guard(uint32_t *guard) {
//if (!*guard) return; // Duplicate the guard check.
if (isEnd) {
return;
}
void *PC = __builtin_return_address(0);

SymbolNode * node = malloc(sizeof(SymbolNode));
*node = (SymbolNode){PC,NULL};

//入队
// offsetof 用在这里是为了入队添加下一个节点找到 前一个节点next指针的位置
OSAtomicEnqueue(&symboList, node, offsetof(SymbolNode, next));
}

– (void)getAllSymbols {
isEnd = YES;
NSMutableArray<NSString *> * symbolNames = [NSMutableArray array];
while (true) {
//offsetof 就是针对某个结构体找到某个属性相对这个结构体的偏移量
SymbolNode * node = OSAtomicDequeue(&symboList, offsetof(SymbolNode, next));
if (node == NULL) break;
Dl_info info;
dladdr(node->pc, &info);

NSString * name = @(info.dli_sname);

// 添加 _
BOOL isObjc = [name hasPrefix:@”+[“] || [name hasPrefix:@”-[“];
NSString * symbolName = isObjc ? name : [@”_” stringByAppendingString:name];

//去重
if (![symbolNames containsObject:symbolName]) {
[symbolNames addObject:symbolName];
}
}

//取反
NSArray * symbolAry = [[symbolNames reverseObjectEnumerator] allObjects];
NSLog(@”%@”,symbolAry);

//将结果写入到文件
NSString * funcString = [symbolAry componentsJoinedByString:@”\n”];
NSString * filePath = [NSTemporaryDirectory() stringByAppendingPathComponent:@”linkSymbols.order”];
NSData * fileContents = [funcString dataUsingEncoding:NSUTF8StringEncoding];
BOOL result = [[NSFileManager defaultManager] createFileAtPath:filePath contents:fileContents attributes:nil];
if (result) {
NSLog(@”linkSymbol result %@”,filePath);
}else{
NSLog(@”linkSymbol result文件写入出错”);
}
}

由于我们的工程为pod工程,如果只在主工程里添加other c flags只能获取到主工程层下的所有启动函数,如果要获取所有的包含依赖pod中启动函数符号则需要在每一个pod target设置other c flags参数。

我们可以通过添加pod脚本来对每一个target添加other c flags参数。

在podfile*后添加脚本来为每一个target添加编译参数。注意可以过滤掉Debug环境才加载的库。

post_install do |installer|
pods_project = installer.pods_project
build_settings = Hash[
‘OTHER_CFLAGS’ => ‘-fsanitize-coverage=func,trace-pc-guard’
# ,’OTHER_SWIFT_FLAGS’ => ‘-sanitize=undefined -sanitize-coverage=func’
]

pods_project.targets.each do |target|
# if !target.name.include?(‘Pods-‘)
if !target.name.include?(‘Pods-‘) and target.name != ‘LookinServer’ and target.name != ‘DoraemonKit’ and target.name != ‘DoraemonKit-DoraemonKit’
# 修改build_settings
target.build_configurations.each do |config|
build_settings.each do |pair|
key = pair[0]
value = pair[1]
if config.build_settings[key].nil?
config.build_settings[key] = [”]
end
if !config.build_settings[key].include?(value)
config.build_settings[key] << value
end
end
end

puts ‘[Other C Flags]: ‘ + target.name + ‘ success.’
end
end
end

重新install之后所有的pod target都会添加上other c flags参数。然后就可以获取到所有的函数符号(注意如果是二进制库则还是会获取不到)。

3.1 设置Order File
通过objc的源码可以看到objc也是通过设置order file设置编译顺序的。

我们可以在主工程的Target -> Build Setting -> Linking -> Order File添加上述步骤导出的函数符号列表linkSymbols.order。

$(SRCROOT)/linkSymbols.order 这里可以根据根目录路径然后寻找,不必把orderfile添加到工程bundle里。如果添加到工程里则会被打包到ipa里。我们可以只是放在工程文件夹下,只在编译的时候根据路径引用就可以了。

设置完orderfile之后我们可以通过设置write link map file属性为YES来找到编译时生成的符号($Project)-LinkMap-normal-arm64.txt。 修改完毕后 clean 一下 , 运行工程 , Products – show in finder, 找到 macho 的上上层目录。 找到结尾为arm64.txt的文件并打开。

Intermediates -> project_ios.build -> Debug-iphoneos -> project_ios.build -> project_ios-LinkMap-normal-arm64.txt

($Project)-LinkMap-normal-arm64.txt文件里在#Symbols之后为函数符号链接的顺序,可以验证一下重排是否成功。

*后可以看一下我们重排之后的效果,Instruments下System Trace下Page Fault的次数和耗时:

%title插图%num

%title插图%num

总结
*后在看一下本次优化的效果。图中为iPhone6s Plus重启后*次启动的优化前后截屏。

%title插图%num

ios运行流程

一.    开发过程

正常开发流程是先跟项目经理确认需求,编码,测试部门测试, 修改Bug,直到*后发布。类似,做Ios应用开发,你也应该应用程序的各种状态,以确定在什么情况下,我们应该根据业务需求作那些逻辑处理。 iOS应用程序状态机一共有五种状态:

1. Not running:应用还没有启动,或者应用正在运行但是途中被系统停止。

2. Inactive:当前应用正在前台运行,但是并不接收事件(当前或许正在执行其它代码)。一般每当应用要从一个状态切换到另一个不同的状态时,中途过渡会短暂停留在此状态。唯一在此状态停留时间比较长的情况是:当用户锁屏时,或者系统提示用户去响应某些(诸如电话来电、有未读短信等)事件的时候。

3. Active:当前应用正在前台运行,并且接收事件。这是应用正在前台运行时所处的正常状态。

4. Background:应用处在后台,并且还在执行代码。大多数将要进入Suspended状态的应用,会先短暂进入此状态。然而,对于请求需要额外的执行时间的应用,会在此状态保持更长一段时间。另外,如果一个应用要求启动时直接进入后台运行,这样的应用会直接从Not running状态进入Background状态,中途不会经过Inactive状态。比如没有界面的应用。注此处并不特指没有界面的应用,其实也可以是有界面的应用,只是如果要直接进入background状态的话,该应用界面不会被显示。

5. Suspended:应用处在后台,并且已停止执行代码。系统自动的将应用移入此状态,且在此举之前不会对应用做任何通知。当处在此状态时,应用依然驻留内存但不执行任何程序代码。当系统发生低内存告警时,系统将会将处于Suspended状态的应用清除出内存以为正在前台运行的应用提供足够的内存。

如下图:

%title插图%num%title插图%num

3. 状态转换

   3.1  应用启动-项目启动

  a.开启新项目

当应用启动时,将从Not running状态进入foreground或者直接进入background运行。进入前台时,其实*终是要进到Active状态,中途会先短暂进入到Inactive状态。在应用启动时,系统会创建一个process和一个主thread,并且在主thread中调用main函数,即上面二提到的。 main函数在创建工程时由Xcode自动生成。main函数负责UIApplication对象初始化,及设置UIApplication代理类等等。在应用初始化并准备进到前台运行之前的大部分工作都在main函数中完成。

应用被启动直到前台运行的过程如下图,右侧部分为调用的UIApplication代理类的方法。

%title插图%num

如果你的应用要求启动后直接进入到Backgroundu状态,则对应的启动过程和上稍有区别,主要不同是,上面的应用是进入到active,而你的应用要进入到background,并且处理事件,当没有事件处理时,会被挂起进入到Suspended状态。如下图所示。

%title插图%num

需要注意的一点是:对于从Not running状态直接进入到background状态的应用,在启动进入到background状态时,如果应用有界面,系统仍然会加载用户界面文件,只是不会显示在应用的window上面。
为了在程序中确定你的程序是进入到了foreground还是background,你可以在application:didFinishLaunchingWithOptions:   方法中检测UIApplication类对象的applicationState属性,如果应用进入到了foreground,则属性值为UIApplicationStateInactive,如果进入到了background,则为UIApplicationStateBackground。

检测示例代码:

UIApplicationState state = [UIApplication sharedApplication].applicationState;
return (state==UIApplicationStateActive || state==UIApplicationStateInactive );

注:当应用启动时要求打开一个URL,则此类应用的启动过程和三中的两个图又有稍微区别, 具体如下:

具有自定义URL模式的应用必须能够处理所有传递给它的URLs。所有的URL都是传递给应用的代理来处理,无论当前应用是处在启动阶段或是正在运行running或是在后台background。为了能够处理URL请求,你的应用代理必须实现下面的接口方法:

(1)使用application:didFinishingLaunchingWithOptions:方法检索URL信息,并且决定是否想要打开这个URL,这个方法只有在应用被启动的时候调用。

(2)iOS4.2或更新的版本,使用方法application:openURL:sourceApplication:annotation:方法去打开文件。

(3)iOS4.1或更老的版本,使用方法application:handleOpenURL:方法去打开文件。

b.修改老项目
当URL请求到达时,如果你的应用没在正在运行,则会被启动并且移到前台运行以打开URL。你的application:didFinishingLaunchingWithOptions:方法实现中应该包含从选项字典options dictionary中检索URL并且判断该应用能否打开它的部分。如果能够打开,则返回YES,让方法application:openURL:sourceApplication:annotation:或方法application:handleOpenURL:去处理具体的URL打开过程。对于要求启动时打开URL的应用,启动顺序如下图所示:

%title插图%num%title插图%num

当URL请求到来时,如果你的应用正在background运行或被suspended,它将会被移到前台以打开URL。之后不久,系统将会调用应用代理的application:openURL:sourceApplication:annotation:方法去检测URL并打开它。如果你的应用代理没有实现这个方法(或者当前系统是iOS4.1或更老的版本),系统将会调用应用代理的application:handleOpenURL:方法来代替。下面是唤醒后台或挂起的应用,去打开URL的程序执行流程,如下图所示:

%title插图%num

支持自定义URL模式的应用,可以在应用启动和去处理URL之前,这个过程之间指定不同的启动画面图像。具体细节,请看Apple官方文档iPhoneAppProgrammingGuide.pdf第85页,“Providing Launch Images for Custom URL Schemes”。

3.2  响应中断-特殊任务

     当一个基于警告的中断(诸如电话来电)发生时,应用会暂时从active状态切换到Inactive状态,以给系统提供机会提示用户,让用户决定如何处理。在用户决定如何处理此中断警告之前,应用将一直处于Inactive状态。 在用户做出选择后,当前应用或者回到active状态继续运行,或者直接切换到background状态以让位于其它的应用运行。此种情况下,应用执行流程如下图所示:

%title插图%num%title插图%num

在iOS5中,notification,特指显示banner方式的notification,并不会像上面的中断一样使当前处于active状态的应用切换到Inactive状态。此类通知的banner放置在你的应用窗口的上边沿之上,所以你的应用依然处在active状态,并且继续像以前一样接收touch events。但是,如果用户拉下banner去呈现通知中心内容时,当前应用将会和上面基于警告的中断一样切换到inactive状态。此时应用将一直处于Inactive状态直到用户对拉下的banner通知做出处理,或许仅仅清除通知或者启动另外一个应用。相应的当前应用要么切换回active状态继续运行或者切换到background状态。用户可以通过Settings应用来配置哪些Notifications以banner的形式显示,哪些以alert警告的形式显示。

用户按“休眠/唤醒”键是另外一种类型的中断,这类中断促使应用被deactived,当用户按下“休眠/唤醒”键时,系统除能触摸事件,deactivate当前的应用,并且锁屏。针对使用数据保护进行加密文件的应用,锁屏事件除了上面的deactivated应用,除能触控事件之外还有其它的处理过程。

当中断发生时,会做什么?

对于基于警告的中断将会导致用户暂时对应用失去控制。当前应用继续在前台foreground运行,但是不再接收任何触控事件。(事实上,应用只是不再接收触控类事件,其它类型的事件比如accelerometer事件,和通知Notification,应用仍然接收。)所以为了响应这些变化,应用需要在applicationWillResignActive:方法中做以下工作:

(1)停止timers及终止其它周期性任务。

(2)停止任何正在运行的元数据查询。

(3)不再初始化任何新任务。

(4)暂停电影播放(在AirPlay上的播放除外)

(5)游戏进入暂停状态。

(6)恢复OpenGL ES帧率。

(7)暂停任何正在临界区执行的分发队列或操作队列。(当然,当应用处于inactive状态时,应用仍然可以继续处理网络请求以及其它一些对时间敏感的后台任务)

当应用恢复切换回active状态时,将会在applicationDidBecomeActive:方法中恢复应用被挂起时执行applicationWillResignActive:方法中所做的所有工作。因此,当应用重新被激活reactivate时,应用应该重启timers,恢复任何分发队列,以及恢复OpenGL ES帧率。但是,游戏不应该自动恢复运行,应该继续保持在暂停状态直到用户手动恢复它们。

当用户按下“休眠/唤醒” 键时,带有NSFileProtectionComplete保护选项需要对文件进行保护的应用必须关闭所有对文件的引用。对于带有密码的设备,按下“休眠/唤醒”键时,锁屏,并且强制系统扔掉解密密钥,以使完全保护使能。当屏被锁时,任何尝试访问相应受保护文件的操作都将fail。所以如果你的应用中有此类受保护的文件时,你应该在applicationWillResignActive:方法中关闭所有对这些文件的引用,并且在applicationDidBecomeActive:方法中重新打开对此类文件的引用。

在通话过程中,调整你的应用的UI:

当用户正在接电话,并且返回你的应用继续保持通话,此时状态栏的高度应该增加以反应用户正在通话的事实。相似的,当用户结束通话时,状态栏的高度应该缩减恢复常规高度。处理状态栏高度变化的*好方法是使用view controllers去管理你的应用views。当状态栏frame size改变时,view controllers会自动调整它们所管理的所有内部视图。

如果你的应用因为某些原因而没有使用view controllers,则你应该手动响应状态栏frame size的变化,具体即通过注册UIApplicationDidChangeStatusBarFrameNotification通知来实现。通知处理函数handler应该获取状态栏的高度并且使用这些数据来适度调整当前应用所包含视图的高度。

3.3 切向后台background状态—交给测试部测试

当用户按下”Home”键或者系统启动另外一个应用时,前台foreground应用首先切换到Inactive状态,然后切换到Background状态。此转换将会导致先后调用应用代理的applicationWillResignActive:和applicationDidEnterBackground:方法。在applicationDidEnterBackground:方法返回后,大部分应用在之后不久转入suspended状态。对于请求特定后台background任务的应用,比如播放音乐应用,或者那些请求需要额外执行时间的应用,可能会继续执行更长一段时间。具体流程如下图所示:

%title插图%num

%title插图%num%title插图%num

注:应用从froeground切换到background只有在支持多任务并且运行iOS4.0或更新版本系统的设备上才会发生。所有其它的情况,应用不是切向后台,而是直接终止,并且从内存中清除。

应用切向后台background时应该做什么:

应用可以在applicationDidEnterBackground:方法中做些切向background状态前需要做的一些准备工作,当切向background状态时,所有的应用需要做以下事情:

(1)应用界面快照。当applicationDidEnterBackground:方法返回时,系统保存应用界面的快照,并且使用快照图片作为转换动画。如果在你的应用界面中有涉及到敏感信息的视图,则你应该在applicationDidEnterBackground:方法返回前隐藏或者修改这些视图。

(2)保存用户数据和应用状态信息。所有没有保存的改变都应该在切向background状态前写入磁盘以保存。这一步是必须的,因为你的应用在后台时很有可能因为多种其它原因而被很快kill掉。根据需要你可以在background thread后台线程中执行这些操作。

(3)释放尽可能多的内存资源。

applicationDidEnterBackground:方法允许*多有5秒的时间去完成任何任务然后返回。实际中,此方法应该尽可能快的返回。如果在时间到期之后,此方法没有返回,则应用即被kill掉,并且清除所占用的内存。如果你的应用确实需要更多的时间去执行任务,可以调用beginBackgroundTaskWithExpirationHandler:方法请求后台执行时间,然后启动一个能长期执行任务的线程。无论你是否启动一个执行后台任务的线程,applicationDidEnterBackground:方法都必须在5秒后退出。

注:UIApplicationDidEnterBackgroundNotification通知也会发送,以让应用对此通知感兴趣的部分知道当前应用正切向background状态。你的应用中的对象可以使用默认的通知中心注册这个通知。

依据不同的应用场合,应用切向后台时还有很多其它的事情需要做,比如active状态的Bonjour服务应该暂停,应用应该停止调用OpenGL ES函数。

因为前台应用在使用系统资源和硬件时一直比后台应用具有更高的优先权。运行在后台的应用应该对此差异有心理准备,并且在后台运行时要调整它们的访问资源行为。特别的,当应用切向background时尤其要遵循以下几点:

(1)不要在应用代码中调用任何OpenGL ES的东西。当应用在后台运行时不可以创建EAGLContext对象或者发出任何OpenGL ES绘画命令,使用这些调用将会导致应用立即被kill掉。应用也必须保证先前提交发出的所有命令在应用切向background状态前都已执行完毕。具体细节请参考“OpenGL ES Programming Guide for iOS”中“Implementing a Multitasking-aware OpenGL ES Application”部分。

(2)在应用挂起suspended之前取消所有Bonjour相关的服务。当应用转向后台,并且在被挂起前,应用应该unregister Bonjour服务并且关掉任何和网络服务相关的sockets监听。挂起的应用是没法响应这些服务请求的。如果你的应用不关掉这些和Bonjour相关的服务,当应用被挂起的时候,系统会自动帮你关掉这些服务。

(3)在基于网络sockets的应用中,需要处理连接失败的情况。当你的应用因为某些原因而被挂起时,系统可能会拆除socket连接。只要你的应用对尽可能多的网络错误情况都有很好的处理,像丢掉信号等,此类问题不会导致你的应用出现不正常。当应用从后台退出恢复执行时,如果遇到sockets使用错误,简单的重建socket连接即可。

(4)在切向background状态前保存应用状态。在低内存告警时,后台应用可能会被清除出内存以释放空间。处于suspended状态的应用被优先清除内存,并且在被清除前不会给出任何通知。因此,当应用切入background状态前一定要保存足够多的应用状态信息以便后面恢复时使用。

(5)当切向后台时,释放所有不再需要的内存。如果你的应用保持着一个很大的内存缓存对象(比如图像),则切入后台前,释放所有的对这些缓存对象的引用。

(6)在被挂起前停止使用系统共享资源。使用系统共享资源(比如Address Book或Calendar Data)的应用,在被挂起前必须停止对这些共享资源的使用。对这些资源的使用,前台应用具有更高的优先使用权,如果发现你的应用在被挂起后还没有停止对这些共享资源的使用,则应该将被kill掉。

(7)避免更新应用窗口和视图。当应用处在后台时,应用窗口和视图是不可见的,所以不需要更新它他。尽管在后台创建和操纵窗口和视图对象并不会导致应用被kill掉,但是可以考虑将这些工作推迟到应用返回前台时执行。

(8)响应外部附件连接和失去连接通知。针对和外部附件有通信的应用,当应用切向background状态时,系统会发送一个disconnection通知。应用必须注册此通知并且使用它去关掉当前的附件访问session。当应用返回foreground时,会有一个与之匹配的通知被发送,给应用提供重新建立session的机会。

(9)切向后台时,清除行为警告相关的资源。为了在应用相互切换之间保存应用上下文,当应用切向后台时,系统并不自动dismiss action sheets(UIActionSheet)和alert views(UIAlertView)。由应用设计者去提供具本的清除方案。对于运行在iOS4.0版本之前的应用,在退出时action sheets和alerts仍然被dismiss掉,以让应用的取消处理函数有机会去运行。

(10)切向后台时,移除所有敏感视图信息。因为系统会快照应用界面并且生成应用切换动画,所以带有敏感信息的视图或窗口必须隐藏或移除,具体原因前面已介绍。

(11)应用在后台运行时执行*少量化的工作。系统给后台运行的应用的执行时间和给前台运行的应用相比,通常非常有限。如果应用在后台播放音频或者监测位置变化,则应用应该仅关注此任务,所有不必要的任务都应该被推迟。在后台执行时间过长的应用会被系统throttled back或者直接被kill掉。

当应用因为系统内存告警需要被清除出内存时,应用会调用他的代理的applicationWillTerminate:方法去执行应用退出前的*后的任务。

后台应用的内存使用:

当应用切入background时,每个应用应该释放尽可能多的实际占用的内存。系统尽量尝试在内存中同时保持尽量多的应用,但是当内存即将耗尽时,系统会终止那些挂起suspended的应用以回收内存。然而那些消耗很大数量的内存同时又处于后台background运行的应用会优先被终止。

实事求是的讲,就是当你的应用在不再需要的时候要尽快的移除对那些用过对象的引用。移除引用允许自动引用计数系统去释放对象并且回收内存。然而,如果应用为了改进性能而使用了缓存,则应用应该在切换至后台状态前等待并且释放这些缓存。下面是一些需要回收的对象的例子:

(1)缓存的图像对象

(2)比较大的多媒体文件或数据文件,这些文件可以从磁盘重新装载。

(3)任何应用当前不再需要的对象,并且这些对象后面又可以很容易重新创建。

为了帮助您的应用程序,减少其内存占用,系统会自动释放出许多幕后用于支持您的应用程序的对象。例如:

(1)释放所有的核心动画层的后备存储,以避免这些层继续在屏幕上显示,同时又不改变当前层的属性。并且并不释放层对象自已。

(2)移除所有对缓存图像的引用。(如果应用没有对这些图像强引用,则他们随后即被移除内存)

(3)释放一些系统管理的其它的数据缓存。

3.4 返回前台foreground—修改提交的Bug

      如果应用曾被移入后台,相应的任务被停止,则此时返回前台时可以重启任务继续执行。应用的applicationWillEnterForeground:方法应该恢复所有在applicationDidEnterBackground:方法所做的工作。同时,applicationDidBecomeActive:方法应该继续执行在应用启动时所做的同样的激活任务的操作。应用从后台切入前台的程序流程如下图所示:

%title插图%num

注:如果应用在默认的通知中心注册了UIApplicationWillEnterForegroudNotification通知,则当应用重新进入前台时,该通知也是可用的。

(1)在应用切向前台被唤醒时处理通知队列

被挂起的应用要时刻准备当恢复foreground或background状态时去处理所有的通知队列。因为应用被挂起时不能执行任何代码,因此没有办法处理那些和诸如方向改变、时间改变、偏好改变、以及其它的影响应用的外观的行为或状态等等。为了保证这些改变不丢失,系统将这些相关的通知入队列,并且当应用恢复foreground或background重新执行代码时,立即将这些通知发往应用。为了防止应用恢复时因为通知过多而过载,系统会合并事件并且仅传递一个能够反应自从应用被挂起有网络改变的单个通知。

具体通知合并规则如下表所示:

%title插图%num

大部分通知直接传递给注册它作的observers,然而像方向改变这样的通知很明显是被系统框架解析的,这样的通知以另外的方式传递给应用。

通知队列典型的在任何触控事件和用户输入之前被投递向应用的主运行循环main run loop。大多数应用应该能够足够快的处理这些事件以致于不会造成应用恢复时有任何明显的滞后。然而,如果发现你的应用在从后台恢复时看起来明显呆滞,则可以使用Instruments去确定是否是通知处理代码正在运行而造成了延迟。
一个应用在返回前台时也会接收所有自从上更新后被标记为dirty的视图的更新通知。处于后台background运行的应用也可以调用setNeedsDisplay或setNeedsDisplayInRect:方法去请求视图更新。然而,因为这时界面不可见,所以系统合并这些请求并且只有当应用恢复前台后才去更新视图。

(2)从容的处理本地改变

当应用处于挂起suspended状态时,如果用户改变了当前语言设置,则当应用返回前台的时候可以使用NSCurrentLocalDidChaneNotification通知来强制任何包含本地敏感信息(像日期、时间、数字等等)的视图进行更新。当然,避免本地信息相关的事件处理的*好的方法还是以那种更容易更新视图的方式来写更新视图的代码。比如:

a.使用autoupdatingCurrentLocal类方法来检索NSLocal对象。此方法返回一个本地对象,该对象响应本地改变并且自动更新自已。所以,你不需要再去重新创建它。然后,当本地信息改变时,你的应用仍然需要去刷新那些包含本地信息的视图。

b.无论任何时候本地信息改变时都去重新创建缓存日期或者数字格式对象。

(3)响应应用设置改变

如果应用包含被Settings应用所管理的设置,则应用应该关注NSUserDefaultsDidChangeNotification通知。因为当你的应用处于后台或被挂起状态时,用户可以修改设置,所以你的应用中可以使用这个通知来响应任何重要的设置改变。某些情况下,响应此通知可以帮助关掉一些潜在的安全漏洞。例如,email程序应该响应用户帐户信息的改变,如果不能成功的监测这些改变将会造成一些隐私和安全方面的问题。比如,用户很有可能发送邮件时还是使用的是老用户帐户,即使那个帐户已经不再属于该用户,然而用户确丝毫不情以为用的就是新帐户。当应用接收到该通知时,应用应该重新加载所有和设置相关的东西并且适当的复位用户接口,如果必要的话。比如密码或其它的安全相关的信息改变时,应用应该隐藏任何先前显示的信息并且强制用户输入新密码。

3.5 应用终止-终于结束了

   尽管应用通常被切向后台或被挂起,但是如果有任何下面的情况发生时,应用将被终止并且清除出内存:

(1)应用依赖于 iOS4.0以前的版本OS

(2)应用部署在运行iOS4.0版本操作系统的设备上

(3)当前设备不支持多任务

(4)应用在Info.plist文件中包含UIApplicationExitOnSuspend key。

如果应用将被终止时正在前台或后台运行,系统将会调用应用代理的applicationWillTerminate:方法以使应用能做退出前的任何需要的回收处理。你可以使用此方法保存用户数据或应用状态信息,以供应用随后重新启动恢复状态时使用。该方法*长运行时限为5秒,过期应用即被kill掉并且移除内存。

注:应用当前被suspended时,不会调用 applicationWillTerminate:方法。

即使是使用iOS SDK4或更新的版本SDK开发应用,也应该考虑应用在没有任何通知时被kill掉的情况。用户可以使用多任务UI很明确的kill掉某个应用。除此之外,如果发生内存告警,系统也会从内存中移除应用以释放空间。处于suspended状态的应用被终止时不会有任何通知。但是如果应用当前正在后台background运行,则当应用要被终止时,系统会调用应用代理的applicationWillTerminate:方法。应用不可以在此方法中申请额外的后台执行时间。

3.6  主运行循环main run loop-枯燥的编码阶段

应用主运行循环负责处理所有用户相关的事件。UIApplication对象在应用启动时安装主运行循环并且使用此循环去处理事件和处理基于视图的界面更新。正如名字所表明的,该主运行循环是在应用的主线程app’s main thread中运行的。以此保证所有用户事件是按照它们被接收时的顺序串行的执行。

下图展示了主运行循环的结构以及用户事件如何导致了应用行为。当用户和应用交互时,和这些交互相关的事件由系统自动产生并且借助UIKit设定的特殊端口传递给应用。事件在应用内部以队列的形式存在并且一个一个的被分发到应用的主运行循环去执行。UIApplication对象是*个接收事件的对象,并且决定需要如何处理事件。触控事件通常被分发到应用的主窗口对象,并且*终分发到发生该触控事件的视图上面。其它的事件传递也许会经过各种各样的应用对象而与触控事件传递稍微有所不同。

%title插图%num%title插图%num

在iOS应用中可以传递很多类型的事件。*常见的事件列在下表中:

%title插图%num

%title插图%num

这些事件类型中的大部分通过应用的主运行循环进行传递,但是还有一些并不是的。例如:accelerometer事件直接被传递到应用指定的accelerometer代理对像。关于系统如何处理大多数类型事件,包括touch、remote control、motion、accelerometer,以及gyroscopic事件,详见Event Handling Guide for iOS.

一些像触控、远程控制类的事件,通常被应用的响应对象处理。响应对象存在于应用的任何地方。(UIApplication对象,view对象,view controller对象等等都是响应对象的例子)。大多数事件是以特定的响应对象为目标,但是也可以被传递给其它的响应对象(借助响应链),例如:一个不处理任何事件的view可以将事件传递给它的父view或传递给view controller。

发生在controls类的视图(例如button)上的事件的处理过程和发生在其它类型的views上的触控事件处理过程有些不一样。因为发生在control类的对象上面的交互行为只有非常有限的几种,因此这些交互重新打包进active message并且传递给合适的目标对象。  这种target-action的设计模式,使应用通过control类型的view对象去触发一段自定义代码的执行变得非常容易。

2021年1—6月份全国规模以上工业企业利润同比增长66.9% 两年平均增长20.6%

  1—6月份,全国规模以上工业企业实现利润总额42183.3亿元,同比增长66.9%(按可比口径计算,详见附注二),比2019年1—6月份增长45.5%,两年平均增长20.6%。   1—6月份,规模以上工业企业中,国有控股企业实现利润总额13774.2亿元,同比增长1.12倍;股份制企业实现利润总额29858.5亿元,增长70.4%;外商及港澳台商投资企业实现利润总额11433.6亿元,增长60.7%;私营企业实现利润总额12164.0亿元,增长47.1%。   1—6月份,采矿业实现利润总额3821.1亿元,同比增长1.33倍;制造业实现利润总额35677.3亿元,增长67.3%;电力、热力、燃气及水生产和供应业实现利润总额2684.8亿元,增长16.9%。   1—6月份,在41个工业大类行业中,39个行业利润总额同比增加,1个行业扭亏为盈,1个行业持平。主要行业利润情况如下:有色金属冶炼和压延加工业利润总额同比增长2.73倍,石油和天然气开采业增长2.49倍,黑色金属冶炼和压延加工业增长2.34倍,化学原料和化学制品制造业增长1.77倍,煤炭开采和洗选业增长1.14倍,汽车制造业增长45.2%,计算机、通信和其他电子设备制造业增长45.2%,电气机械和器材制造业增长36.1%,通用设备制造业增长34.5%,专用设备制造业增长31.0%,非金属矿物制品业增长26.7%,电力、热力生产和供应业增长9.5%,农副食品加工业增长5.4%,纺织业增长2.3%,石油、煤炭及其他燃料加工业由同期亏损转为盈利。   1—6月份,规模以上工业企业实现营业收入59.29万亿元,同比增长27.9%;发生营业成本49.54万亿元,增长26.4%;营业收入利润率为7.11%,同比提高1.66个百分点。   6月末,规模以上工业企业资产总计132.63万亿元,同比增长9.3%;负债合计74.96万亿元,增长8.5%;所有者权益合计57.67万亿元,增长10.3%;资产负债率为56.5%,同比降低0.4个百分点。   6月末,规模以上工业企业应收账款17.56万亿元,同比增长13.1%;产成品存货4.95万亿元,增长11.3%。   1—6月份,规模以上工业企业每百元营业收入中的成本为83.54元,同比减少0.98元;每百元营业收入中的费用为8.45元,同比减少0.65元。   6月末,规模以上工业企业每百元资产实现的营业收入为91.4元,同比增加13.2元;人均营业收入为162.2万元,同比增加34.5万元;产成品存货周转天数为17.4天,同比减少2.5天;应收账款平均回收期为51.4天,同比减少5.9天。   6月份,规模以上工业企业实现利润总额7918.0亿元,同比增长20.0%。    表1  2021年1—6月份规模以上工业企业主要财务指标  分 组 营业收入 营业成本 利润总额 1-6月 同比增长 1-6月 同比增长 1-6月 同比增长 (亿元) (%) (亿元) (%) (亿元) (%) 总计 592931.9 27.9 495357.8 26.4 42183.3 66.9 其中:采矿业 23181.5 29.5 15737.9 17.7 3821.1 133.0    制造业 526159.6 28.7 440952.6 27.4 35677.3 67.3    电力、热力、燃气及水生产和供应业 43590.8 18.4 38667.3 19.5 2684.8 16.9 其中:国有控股企业 156516.3 26.9 125704.3 23.9 13774.2 111.9 其中:股份制企业 439550.5 29.1 367471.9 27.5 29858.5 70.4    外商及港澳台商投资企业 136520.7 25.0 113555.6 23.6 11433.6 60.7 其中:私营企业 231892.6 28.6 199915.6 28.0 12164.0 47.1 注: 1.经济类型分组之间存在交叉,故各经济类型企业数据之和大于总计。 2.本表部分指标存在总计不等于分项之和情况,是数据四舍五入所致,未作机械调整。  表2  2021年1—6月份规模以上工业企业经济效益指标  分 组 营业收入利润率 每百元营业收入中的成本 每百元营业收入中的费用 每百元资产实现的营业收入 人均营业收入 资产负债率 产成品存货周转天数 应收账款平均回收期 1-6月 1-6月 1-6月 6月末 6月末 6月末 6月末 6月末 (%) (元) (元) (元) (万元/人) (%) (天) (天) 总计 7.11 83.54 8.45 91.4 162.2 56.5 17.4 51.4 其中:采矿业 16.48 67.89 11.20 44.6 110.3 60.1 12.8 39.7    制造业 6.78 83.81 8.51 107.9 160.6 55.5 19.0 52.2    电力、热力、燃气及水生产和供应业 6.16 88.71 6.24 40.0 259.5 59.3 0.9 48.1 其中:国有控股企业 8.80 80.31 7.18 63.9 251.3 57.0 12.5 40.3 其中:股份制企业 6.79 83.60 8.57 88.8 160.7 57.3 17.6 48.8    外商及港澳台商投资企业 8.37 83.18 8.40 101.7 167.6 53.7 17.5 62.4 其中:私营企业 5.25 86.21 8.43 127.7 133.0 58.6 18.6 48.3  表3  2021年1—6月份规模以上工业企业主要财务指标(分行业)  行 业 营业收入 营业成本 利润总额 1-6月 同比增长 1-6月 同比增长 1-6月 同比增长 (亿元) (%) (亿元) (%) (亿元) (%) 总计 592931.9 27.9 495357.8 26.4 42183.3 66.9  煤炭开采和洗选业 12159.5 30.9 8011.5 20.0 2068.8 113.8  石油和天然气开采业 4152.7 27.9 2547.3 6.2 962.5 248.6  黑色金属矿采选业 2748.2 57.2 1998.6 42.1 380.3 187.9  有色金属矿采选业 1411.8 20.9 976.8 13.6 236.0 83.5  非金属矿采选业 1843.8 19.8 1381.2 18.8 170.5 27.9  开采专业及辅助性活动 858.6 -4.3 816.6 -5.1 2.7 35.0  其他采矿业 7.0 37.3 5.8 48.7 0.3 0.0  农副食品加工业 25476.3 18.8 23275.1 19.7 848.7 5.4  食品制造业 9962.8 12.6 7823.6 13.9 785.0 10.4  酒、饮料和精制茶制造业 8252.7 18.3 5386.3 15.9 1435.8 29.7  烟草制品业 6870.0 7.9 1998.3 4.3 1000.3 14.1  纺织业 11707.5 18.9 10402.5 19.9 446.0 2.3  纺织服装、服饰业 6533.8 13.0 5600.2 12.8 274.0 13.9  皮革、毛皮、羽毛及其制品和制鞋业 5058.8 11.8 4383.5 11.6 243.5 13.5  木材加工和木、竹、藤、棕、草制品业 4393.8 20.5 3967.6 20.7 153.2 13.8  家具制造业 3640.8 29.3 3051.6 29.5 170.4 27.9  造纸和纸制品业 7142.0 24.7 6067.0 23.2 484.3 77.1  印刷和记录媒介复制业 3331.8 18.8 2814.8 19.1 173.7 11.8  文教、工美、体育和娱乐用品制造业 6543.4 27.2 5697.9 27.7 287.1 25.5  石油、煤炭及其他燃料加工业 25861.0 31.7 20779.1 22.8 1794.1 (注1)  化学原料和化学制品制造业 38481.7 35.4 31355.0 30.0 3803.8 176.8  医药制造业 14046.9 28.0 7457.0 17.5 3000.4 88.8  化学纤维制造业 4760.1 35.0 4192.9 28.7 326.6 387.5  橡胶和塑料制品业 13590.3 25.0 11440.7 25.2 812.1 24.9  非金属矿物制品业 29960.6 23.5 24861.7 23.4 2358.0 26.7  黑色金属冶炼和压延加工业 47287.5 49.5 42831.7 45.0 2671.0 234.1  有色金属冶炼和压延加工业 32584.4 38.3 29975.4 34.3 1403.7 272.8  金属制品业 21132.7 34.6 18612.4 34.4 902.0 52.3  通用设备制造业 22112.3 28.3 18210.1 28.4 1516.6 34.5  专用设备制造业 18190.8 25.1 14327.3 25.2 1594.9 31.0  汽车制造业 42891.1 28.0 36689.9 28.6 2876.8 45.2  铁路、船舶、航空航天和其他运输设备制造业 5737.0 20.5 4905.6 21.0 273.1 11.6  电气机械和器材制造业 37817.4 36.4 32331.0 37.4 1993.1 36.1  计算机、通信和其他电子设备制造业 63294.3 22.1 54469.6 20.4 3451.6 45.2  仪器仪表制造业 3996.5 25.7 3012.2 25.1 391.2 25.1  其他制造业 951.1 25.5 802.9 25.6 49.7 21.5  废弃资源综合利用业 3914.1 74.3 3693.8 76.3 128.5 64.7  金属制品、机械和设备修理业 636.0 4.1 536.2 0.9 27.8 35.6  电力、热力生产和供应业 36349.1 17.3 32647.7 18.7 2024.9 9.5  燃气生产和供应业 5451.2 25.3 4711.8 25.0 473.3 48.5  水的生产和供应业 1790.5 20.6 1307.8 19.3 186.7 44.6 注: 1.石油、煤炭及其他燃料加工业上年同期亏损160.7亿元。 2.本表部分指标存在总计不等于分项之和情况,是数据四舍五入所致,未作机械调整。    附注:   一、指标解释及相关说明   1、利润总额:指企业在生产经营过程中各种收入扣除各种耗费后的盈余,反映企业在报告期内实现的盈亏总额。   2、营业收入:指企业从事销售商品、提供劳务和让渡资产使用权等生产经营活动形成的经济利益流入。包括主营业务收入和其他业务收入。   3、营业成本:指企业从事销售商品、提供劳务和让渡资产使用权等生产经营活动发生的实际成本。包括主营业务成本和其他业务成本。营业成本应当与营业收入进行配比。   4、资产总计:指企业过去的交易或者事项形成的、由企业拥有或者控制的、预期会给企业带来经济利益的资源。   5、负债合计:指企业过去的交易或者事项形成的、预期会导致经济利益流出企业的现时义务。   6、所有者权益合计:指企业资产扣除负债后由所有者享有的剩余权益。   7、应收账款:指资产负债表日以摊余成本计量的,企业因销售商品、提供服务等经营活动应收取的款项。   8、产成品存货:指企业报告期末已经加工生产并完成全部生产过程、可以对外销售的制成产品。   9、营业收入利润率=利润总额÷营业收入×100%,单位:%。   10、每百元营业收入中的成本=营业成本÷营业收入×100,单位:元。   11、每百元营业收入中的费用=(销售费用+管理费用+研发费用+财务费用)÷营业收入×100,单位:元。   12、每百元资产实现的营业收入=营业收入÷平均资产÷累计月数×12×100,单位:元。   13、人均营业收入=营业收入÷平均用工人数÷累计月数×12,单位:万元/人。   14、资产负债率=负债合计÷资产总计×100%,单位:%。   15、产成品存货周转天数=360×平均产成品存货÷营业成本×累计月数÷12,单位:天。   16、应收账款平均回收期=360×平均应收账款÷营业收入×累计月数÷12,单位:天。   17、两年平均增速是指以2019年相应同期数为基数,采用几何平均的方法计算的增速。   18、在各表的利润总额同比增长栏中,标“注”的表示上年同期利润总额为负数,即亏损;数值为正数的表明利润同比增长;数值在0至-100%之间(不含0)的表明利润同比下降;下降幅度超过100%的表明由上年同期盈利转为本期亏损;数值为0的表明利润同比持平。   二、规模以上工业企业利润总额、营业收入等指标的增速均按可比口径计算。报告期数据与上年所公布的同指标数据之间有不可比因素,不能直接相比计算增速。其主要原因是:(一)根据统计制度,每年定期对规模以上工业企业调查范围进行调整。每年有部分企业达到规模标准纳入调查范围,也有部分企业因规模变小而退出调查范围,还有新建投产企业、破产、注(吊)销企业等变化。(二)加强统计执法,对统计执法检查中发现的不符合规模以上工业统计要求的企业进行了清理,对相关基数依规进行了修正。(三)加强数据质量管理,剔除跨地区、跨行业重复统计数据。   三、统计范围   规模以上工业企业,即年主营业务收入为2000万元及以上的工业法人单位。   四、调查方法   规模以上工业企业财务状况报表按月进行全面调查(1月份数据免报)。   五、行业分类标准   执行国民经济行业分类标准(GB/T4754-2017),具体请参见http://www.stats.gov.cn/tjsj/tjbz/hyflbz/。 

如何轻松地将可访问LAN的Pod部署到Kubernetes集群上

想要在Kubernetes集群上部署可访问LAN的Pod来达到目的?接下来就展示一下它实际上是多么容易。

%title插图%num

Kubernetes更具挑战性的方面之一是部署到集群,同时使你可以从LAN访问已部署的应用程序和服务。在发现hostNetwork设置之前,我一直为此努力。该特定选项适用于Kubernetes Pod,并使得可以从集群外部访问已部署的Pod。

通过使用此特定设置,窗格中的应用程序能够访问托管窗格的节点上的网络接口。从理论上讲,这意味着您实际上可以从LAN访问这些Pod。

我想向你展示如何进行这项工作。这很容易。

%title插图%num

你需要做的事

完成这项工作只需一个正在运行的Kubernetes集群。该群集可以位于AWS,Google Cloud,您自己的本地硬件或任何其他主机上。如果您还没有群集,则始终可以在数据中心上部署一个群集。

一旦访问了Kubernetes集群,就可以部署一个外部环境或至少您的LAN可以访问的Pod。

%title插图%num

如何创建你的YAML文件

我们要做的*件事是创建一个非常基础的YAML文件,以演示如何使用hostNetworking设置。这个YAML会将一个influxdb pod部署到Kubernetes集群,然后我们可以从集群外部访问它。

在Kubernetes控制器上,使用以下命令创建新的YAML文件:

namo fluxdb.yml

在该新文件中,粘贴以下内容:

apiVersion: v1kind: Podmetadata: name: influxdbspec: hostNetwork: true containers:    -name: influxdb     image: influxdb

这里重要的一点是:

hostNetwork:true

上面的设置为Pod提供了对主机网络接口的必要访问权限。如果没有该设置,则Pod仍将部署,但只能从群集内访问。YAML文件使用单个容器(也称为influxdb)创建一个名为influxdb的Pod,该容器使用influxdb映像。

保存并关闭文件。

%title插图%num

如何部署吊舱

现在,我们将部署新的pod。这是通过以下命令完成的:

Kubectl create -f influxdb.yml

该Pod将部署到群集,但是完成部署将花费一点时间。

%title插图%num

如何测试外部访问

接下来,我们将测试新部署的Pod的外部访问。为此,我们必须找出Pod部署到哪个节点,这还将向我们显示该节点的IP地址。为此,发出命令:

Kubectl create pod influxdb

上面的命令将打印出有关你新部署的Pod的大量信息。我们想要的重要部分位于IPs部分。你应该看到类似以下的行:

IP:192.168.1.112

IP:行将列出已将Pod部署到的节点的IP地址。掌握了这些信息之后,您可以使用以下命令测试连接:

curl -v http:// IP:8086 / ping

其中IP是托管influxdb Pod的节点的IP地址。您收到的响应应包括HTTP / 1.1 204 No Content(图A)。

%title插图%num

恭喜,你刚刚将Pod部署到可通过LAN访问的Kubernetes集群。

是不是并不像你想象中那么难!

原文链接:

How to easily deploy LAN-accessible pods to a Kubernetes cluster

放弃 Windows 后 ,开源操作系统能成为主流桌面系统吗?

在近十几年里,总是能听到世界各地的国家或者地方政府在尝试用开源系统代替Windows作为政府办公系统,尤其在今年微软正式宣布停止对Windows 7的更新维护服务后,很多数年来一直使用的Windows 7系统但现在却不得不放弃的人开始警觉:自己是否太过依赖微软操作系统。

以此为契机,很多个人、地方、甚至国家都开始摸索基于Linux的开源操作系统,希望从此摆脱对微软操作系统的依赖,减少 IT 开支,并巩固自身的数字 和技术主权。

%title插图%num

各国对开源操作系统的尝试

 

  • 德国 

早在2003 年,德国慕尼黑市就通过议会投票,启动了 LiMux 项目(基于 Ubuntu 的“慕尼黑发行版”),计划将所有的政府办公系统和公务员的个人电脑从 Windows 迁移到开源软件平台。到 2013 年,该市政府 80% 的台式机都运行在 LiMux 系统中。

但是,在这个项目实施十年后,它却变成了麻烦:慕尼黑政府的办公人员表示 Linux 严重影响了自己的工作效率。到2017 年,据媒体报道,慕尼黑*终决定重返 Windows,但系统的来回迁移耗费了大量经济成本和时间成本。

  • 土耳其

土耳其开始尝试开源操作系统的时间也很早。在2005 年,由国家资助的一个小团队开始研发 Linux 发行版 Pardus。至2015 年,土耳其一地方政府更是大胆推出更改措施:将原本使用的 Windows 替换为 Pardus 开源系统,包括过渡至 Zimbra 电子邮件服务器以及 PostgreSQL 数据库,并将 Office 切换至 LibreOffice。

近日,该项目迁移负责人 Hüseyin GÜÇ 表示这项实施目前已经比较成熟,整个公务体系内完成了 GNU Linux 及多种其他开源软件的全面替换,使用人员对项目的理解与接纳程度也很高。

  • 韩国

在去年5月,韩国就曾宣布:随着 Windows 7 生命周期的结束,韩国所有政府计算机都将从 Windows 切换到 Linux。而微软正式宣布对Windows 7的停止更新,使得这项计划预计将在今年年底启动。

对韩国来说,针对本土的开源操作系统,有三个主要候选者:由韩国 Hancom、Invesume、Tmax 等本地企业分别开发的基于 Linux 的开源操作系统 Gooroom、HAMONIKR、TMAX OS。

这三个开源操作系统都各有优势,韩国对此也抱有期待。但理想跟现实还是有一定差距,目前市场中的开源系统还缺少一些软件支持,只有解决软件支持问题,才能有效地运行必要的生产力应用程序。

  • 波兰

今年2月,波兰的社会保险公司 ZUS 宣布,与 Linux Polska (波兰开源和数据科学技术的服务商)达成协议,以提供具有三年支持服务的集成服务器虚拟化解决方案,新的解决方案将允许 ZUS 开发和优化 IT 系统。

Linux Polska 同意为 Red Hat 虚拟化,Red Hat Satellite 和 Red Hat Enterprise Linux  软件提供支持。此外,Linux Polska 提供的解决方案还将保证在虚拟机上运行的所有系统之间编译高可用性集群的可能性。

除了 ZUS 之外,波兰的其他部委和中央机构(例如教育部、财政部、行政部、发展部、数字化和中央信息技术中心等)也已经与 Linux Polska 达成了合作。

由以上几个国家对开源操作系统的尝试,不难看出,要将政府办公系统从Windows迁移至开源系统,并不是一件容易的事,投入的时间和成本都颇为巨大,并且结果也可能不尽人意。但土耳其作为其中比较成功的例子,有很多值得参考学习的地方。

对于如何将地方政府迁移到国产开源系统,并且使人们接收这种底层技术和应用的变化,土耳其的项目迁移负责人 Hüseyin GÜÇ 曾在今年8月底总结过其中的经验,希望这些经验可以帮助到那些打算或正在迁移至开源系统的人。

%title插图%num

如何顺利用开源系统取代Windows

Hüseyin GÜÇ 表示从一开始,他的团队就知道这是一个重大却又艰难的过程,从熟悉的Windows转到陌生的Pardus 开源系统,使用感必定大打折扣。因此,他们首先着重的就是对用户的心理疏导和对新系统的课程培训。

  • 教育用户

*阶段:提前发布通告,告知用户即将更换系统的消息,与用户就迁移系统的过渡期进行讨论沟通。在计划启动的初始,就向全体用户提供关于 Linux 平台上 LibreOffice 的课程培训,以此加快用户熟悉开源软件的速度并尽量保持原有的工作效率。

第二阶段:管理员安装开源 LibreOffice 软件,从而为用户全面替换原本的微软Office软件。在实际安装软件前对用户进行全方位培训,*大限度地减少了用户从熟悉的软件(包括操作系统)过渡到新的开源软件时会遇到的问题。

第三阶段:将 LibreOffice 的试用期定为一年,到 2016 年时又再次组织关于 Linux 与 LibreOffice 的用法培训,一旦用户在培训结束时通过了考试,就在他们的计算机上安装 Linux 系统。而对于未能顺利通过考试的用户,就再提供补习课程至考试通过,再使用 Linux 系统。

这三个阶段完成后,操作系统的迁移工作才正式拉开序幕。

  • 迁移步骤

Hüseyin GÜÇ 团队对于迁移采取的是阶段式的方法:

  • 分析
  • 规划
  • ISO创建
  • 测试
  • 试点
  • 生产

这些步骤乍看之下非常常规,但有两个步骤对于迁移的成功至关重要。

*个就是分析。这个分析不仅是对技术的分析,也是对用户心理的分析。通过与用户的交流,了解到了用户对于“未知”的焦虑,未知的系统,未知的软件都让他们担忧。因此为了打破僵局,Hüseyin GÜÇ 团队选择采用与 Windows 风格相近的主题,并将其设定为 Linux 系统的默认窗口管理器。这一举措,果然受到了用户们的积*响应。可见,只要在直观层面保留一点熟悉的元素,人们对底层技术甚至是功能方面的重大转变就会有很好的接受能力。

其次,就是ISO创建。Pardus 系统提供能下载安装的 ISO 资源,但其中包含很多不必要的应用程序。如果直接安装,再逐个删除不必要的应用,同时安装需要的业务应用,无疑会浪费掉大量时间。因此,重新配置了 Pardus GNU/Linux ISO ,将业务应用程序套件纳入其中。通过调整,使安装周期缩短到 15 分钟左右,并且所有必要的业务应用都能在安装系统后立即使用。

  • 收尾工作

随着项目的发展成熟,项目的管理与监控要求也变得愈发清晰,Hüseyin GÜÇ 团队安装了 Lider/Ahenk 服务器以管理 Pardus Linux 客户端,还安装了开源 Zabbix 应用程序以监控服务器与客户端。

如今,已经实现了可以立足单点对数百个 Pardus 客户端进行更新、提供远程支持服务、实施策略,并通过 Zabbix 警报尽早发现问题。这一切又反过来帮助团队快速开发出有效的解决方案。Hüseyin GÜÇ 团队希望迁移项目的可管理性与可持续性。很明显,到目前为止的工作进展一切顺利。

以上便是Hüseyin GÜÇ 所总结的经验,他希望能以此启发那些在投入于开源系统的团队,并具有一定的参考价值。

%title插图%num

用开源操作系统取代Windows的思考

 

其实,在不考虑其他因素的前提下,能够拥有一款国产的开源操作系统无疑是一件幸事。开源系统具有足够的灵活性和性能上的优势,可以从而摆脱对微软操作系统的依赖,节省非常可观的许可费用,并将核心技术掌握在自己手中。

可开发一款操作系统并不难,难的是构建这款开源操作系统的生态。譬如德国慕尼黑,在开源系统沉浮近十年,*终还是重返了Windows的怀抱。

在开源系统及软件的优势越来越被广泛认可后,的确有许多国家在积*采取相关的行动计划,研究如何将行政系统顺利迁移至开源系统。相信只要做好生态的构建与用户的培训,或许不久之后,Linux真的可以成为主流桌面操作系统,让我们拭目以待吧!

如何应对云原生之旅中的安全挑战?

云原生的起源

大约十年前,Netflix公司首次提出了“云原生”,这是一项关于云和计算的技术。这项技术推动了Netflix的发展,帮助他们从一家邮购公司发展成为了全球*受信任以及*大的消费者按需内容提供商之一。Netflix率先开发了云原生技术,并为所有软件开发的重塑、转型和扩展方面提供了宝贵的经验。

Netflix借助云原生技术,更快地向客户提供更多功能,从那时起,每家与软件打交道的公司都希望借鉴Netflix的云原生技术。云原生能够有效地提高业务速度,并可利用容器、Docker、Kubernetes等云原生技术提供自动化和可扩展性。

%title插图%num

云原生之旅

云原生之旅可以归结为三个关键性的决策,而这些决策都可以通过云原生得到解决。

什么是基础设施?

基础设施的基本要求之一是计算机必须具有弹性。此外,基础设施还需要支持其他功能,例如可观察性、可见性、若干托管的服务等。基础设施是一个广泛讨论的话题,我不打算在本文中详细讨论。

选择哪个平台?

云原生平台的选择比较容易,因为基本上Kubernetes已成为运行容器化微服务的默认平台。

如何有效,安全地运行容器化微服务?

这是一个复杂的决定,一般我会推荐Helm。Helm能够帮助你以更简单且可重用的方式安装Kubernetes的服务。以上我推荐的选择都是为了帮助开发人员专注于业务问题,而不必担心平台要求的负担等。

以上就是三个你需要做出的关键决策,而这就是你云原生之旅的起点。

云原生是一段旅程,而不是目的地。

各个公司可以采取多个步骤来推进这段旅程。

云原生的基本原则包括可扩展的应用、弹性架构以及频繁变更的能力。

%title插图%num

在这段旅程中,有三个阶段需要注意:

● *个阶段:主要面向开发人员、采用容器。

● 第二个阶段:主要面向开发运维、部署应用程序。

● 第三个阶段:主要面向业务(端到端)、智能运营。

Vodafone 在“云原生世界”大会上展示了他们的云原生之旅。

%title插图%num

在云原生之旅的中间,Vodafone将“为云做好准备”作为一个中间步骤。

“为云做好准备”的步骤包括向以API为中心转变,自动化应用构建和运行,消除对操作系统的依赖,并通过API实现基础设施即代码(Infrastructure as a Code,即IaaC)。

Vodafone的IT方面似乎比网络方面更成熟。大多数IT功能已经处于“为云做好准备”阶段,但是重新架构VNF以实现容器化,并让它们成为云原生是一项具有挑战性的任务。

云原生之旅面临的共同挑战

保护入口点

保护网络安全是重中之重。拥有一个VPC,使用NAT(NAT用于控制出口,确保没有P地址、节点或对象被泄漏I)。使用RBAC,专用网络等,为了确保每个人都无法访问在Kubernetes集群中运行的API服务器,这些都是必需的。在Kubernetes上运行容器化微服务时,需要使用命名空间。因此,一切都可以归结为监视和控制入口点。

指定机密数据

敏感信息非常重要,因此需要加密,因此我们需要使用机密数据。一个很好模式是在计划或设计Helm Chart时,将机密数据放到外部。然后使用约束,约束是限制集群滥用资源的关键,然后还有安全上下文,它应该是一组指定的策略。*后还有网络策略也是遏制滥用的另一种方式。

掌握微服务

如果你在选定的平台和选定的基础设施上运行微服务,那么可以通过Helm Chart来部署应用程序,从而掌握所有的微服务。有些Pod是单独的,你可以在Pod中建立一个主容器和一个初始化容器,还可以运行一个sidercar。你可以为这些Pod建立多个副本,而且也可以具有依赖项。也许你需要运行一个数据库,也许你需要在同一个集群中运行另一个微服务,而且该依赖项可能也有一个主容器和一个sidercar容器。

可观察性

微服务是云原生架构的基础,但是当你拆分单例应用程序时,可能会创建数十个、甚至数百个微应用程序。这些微应用程序中的每一个都需要进行观察和监视,这是一个很大的挑战。

由于许多微服务都涉及构建现代云原生应用程序,因此快速配置、部署、连续交付、严格的开发运维实践以及整体的监视和可观察性都是必要的。

你可以通过可观察性监视微服务的状况,确保它们的性能和行为。通过工具来掌握系统整体的运行状况和功能非常重要。

自从编程问世以来,日志记录一直被当作可观察性和监视指标的常规方法,但这对于云原生应用程序还不够。

重要的是你能够观察到系统的当前状况。你必须拥有现代化的监视工具SLA,并了解服务水平的稳健程度,以及解决问题和警告的平均时间。

GoCenter、ChartCenter等社区中心都建立了许多微服务。所有这些服务都默认在代码中加入了健康检查,并以此作为提高可观察性的良好实践。

如何以可重复的方式在K8S上部署应用程序?

假设我们在Kubernetes集群上安装了Redis,那么问题就是,我是否可以重用我的安装程序,运行100次,仍然可以获得相同的输出?如果答案是否定的,则表明你的系统存在安全隐患。除了安全问题之外,这也是维护的噩梦。对于微服务,可重用性是关键,而且不知道依赖关系来自何处,那么问题就很严重了。

那么,如何解决这个问题呢?答案很简单:使用包管理器,使用Helm。Helm可以提高可重用性以及可重复性。因此,Helm Chart和Helm Chart的各个版本都可以实现可重复性。

但是,这是真的吗?Helm生态系统是否保证可重复性?

%title插图%num

上面提到的是一些严重的问题,并且与安全问题有很多的联系。因此,Helm生态系统虽然有其一定优势,同时也存在严重的弊端。

随着Kubernetes成为企业在云原生世界默认的容器编排平台,Helm可以帮助我们更轻松地重复安装和升级应用程序。尽管“ Kubernetes + Helm”组合是云原生的入门方法之一,但是仍然缺乏安全性,而ChartCenter满足了持续保护云原生生态系统的需求。

ChartCenter可以作为一种解决方案,帮助大家以可重复的方式提供公共的Helm Chart,从而确保云原生生态系统的安全,同时可以遏制日益增长的安全隐患。

%title插图%num

ChartCenter可以为公开的K8s应用程序的Helm Chart提供安全可靠的来源。目前还没有标准规定生产者如何与云原生生态系统中的消费者共同承担安全隐患,也没有任何咨询说明。为了解决这个问题,我们提出了一个标准,该标准可帮助生产者使用Helm Chart提供安全缓解信息。

下面是一些ChartCenter的独特之处:

1. 中心存储库可以帮助用户轻松地设置客户端,并保持可追溯性。

2. 高可用性和可扩展性服务保障了可靠的生产服务。不可变性可以更进一步确保你放心地使用Chart,即便作者删除了Chart,你依然可以访问。

3. 出色的搜索功能,能够根据命名空间、名称、描述和标签快速找到合适的Chart。

4. 上游依赖:安装Helm Chart会拉取容器镜像以及其他子Chart,其中可能包含关系到安全和许可证的问题,因此我们不建议你使用被弃用或过时的依赖库。生产部署随时可以接收到这些重要的信息,这一点非常关键。

5. 随着Chart快速地发展,了解谁可能会受到更改的影响有助于增进稳定性和信任度。Chart的作者认为在支持向后兼容性和管理重大更改时,该功能非常有帮助性。

6. 安全扫描:ChartCenter会针对存储库中的1.2万个Docker镜像中包含的1.8M个组件进行连续扫描,这些镜像被3万多个Helm Chart版本引用。

现如今,每家公司都希望以高质量和高可靠性来巩固自家的软件产品。云原生之路让各个公司充满信心,相信他们可以快速地发布高质量的产品。Kubernetes和Helm等平台已经发展了很长一段时间,能够帮助软件公司利用云原生原则的力量,例如现代CI/CD、微服务等。希望本文提到的技术能够帮助你克服重重困难,顺利地完成云原生之旅。

原文链接:

https://dzone.com/articles/securing-your-cloud-native-journey

对话阿里云:开源与自研如何共处?

从「鲜为人知」的专业名词,到 2006 年的精准定义,再到如今全面上云时代的「百花齐放」,云计算的发展趟过蛮荒之地,已形成*具规模、高可靠性、通用性的服务。不过,针对过往软件领域中争论已久的开源还是闭源问题,同样在企业业务上云过程中再次出现,云时代,开源与闭源如何形成良好的闭环,开源与自研是否相互矛盾?在开源模式下,商业化之路又该如何走?

近日,在开源界摸爬滚打 20 载有余的老兵,也是国内云时代的引领者 —— 阿里云基础产品事业部技术战略负责人陈绪(花名还剑)接受 CSDN *专访,从阿里云技术战略与开源生态体系等维度,深度解析阿里云的核心竞争力筑造之路以及其背后开源的驱动力,借此,也希望在数字智能化时代,为正走在自主创新道路上的企业、开发者带来一些思考。

%title插图%num

云,未来已来

当美国知名软件工程师 Marc Andreessen 提出「软件正在吞噬世界」的一些年后,在云计算成熟落地之际,业界对这一条线进行填补,*终形成了一个完整的技术食物链,即「开源正在吞噬软件」,而「云正在吞噬开源」,「多云正在吞噬云」。

%title插图%num

(CSDN 付费下载自视觉中国)

虽然对于这一大鱼吃小鱼理论,科技圈中存在一定的争议,但无可否认的是,云计算引领的科技革命,让未来已来,将至已至。

在这一进程中,与国外亚马逊、IBM、Oracle 等老牌云计算玩家相比,中国在云计算领域的入局,要稍晚 2-3 年。而在多年的角逐之后,陈绪表示,从技术水准来看,如今国内外的云已经处于同一个水平。

陈绪,相信很多从业者对于这位大牛也并不陌生。2018 年 7 月,陈绪离开供职十一年的英特尔,只身一人,从北漂转身加入“杭派工程师”之列,成为阿里云的一员,在帮助企业更好地实现上云的同时,也在探索云时代的前沿技术发展。

云浪潮风云起,无论是对于企业,还是个人,是机遇亦是挑战。

%title插图%num

群雄逐鹿的云计算赛场,阿里云的 11 年

中国云计算的发展从概念的炒作到步入正轨,几经兴盛,也曾备受质疑。2009-2013年间,在谁也无法明确说出“云计算”到底是什么之际,业界将这一阶段视为中国云计算的泡沫期,而面对纷涌而至想要从中分一杯羹的众多厂商而言,《浪潮之巅》的作者吴军也曾做过预测,「中国通过竞争剩下的云计算服务商不会超过十家」。

物竞天择,适者生存,让做事相对扎实的一些云计算平台生存下来,而阿里云就是破除云计算这块冰中尤为重要的一家。

在过往的 11 年间,陈绪表示,为向用户提供更加稳定与可靠的服务,阿里云主要围绕三大层面筑造了自己核心竞争力:

  • 技术战略规划。在探索过程中,有很多趋势大家能看到,但是不一定能做到,如软硬件一体化、集成模式、使用云计算指导硬件研发等。在此,阿里云将多年研发的经验回馈给硬件开发商,帮助其设计出云计算领域更加通用的硬件。
  • 开放与开源。整个业界包括中国、美国、以色列等地区有不同的创业公司,每个公司都有不同的理念,软件定义存储、软件定义网络等平台,面向层出不穷的新技术,阿里云不能闭门造车,会与产业伙伴、生态共同构建未来,共同发掘商机与新的技术发展方向。与大家共同提炼出价值,并且将*新技术落地到阿里云产品研发的方向中。
  • 碰撞融合。另外,阿里云也会与高校、科研机构、大客户深度沟通与交流,希望将产-学-研全链路打通,真正让大家能够形成良性互动,让学校老师和科研机构研究者有实际应用场景。业界有一个名为 TTM(Time To Marketing,产品上市周期)的概念,阿里云旨在将 TTM 缩短到*小,这个不仅对阿里云自己有价值,而且能够帮助客户实时体验到世界上*先进的黑科技,也能够真正保持自己的*高竞争力。

陈绪透露,阿里云希望云上的用户真正能够忘记物理硬件出问题的可能性,永远在将系统放在阿里云的同时,业务能够实现永不间断。下一步,阿里云也将采用多种技术手段,如使用达摩院的智能感知和预测技术,即当硬件可能出现问题前可进行无缝迁移,这也被称之为“热迁移”,真正实现无感知、无痛。

同时在软硬一体化趋势下,阿里云也与英特尔进行了深度合作,如采用英特尔的 AEP(Apache Pass)进行开发。陈绪解释道,AEP 是一种新的介质,处于内存和硬盘之间,它不仅比内存便宜,且能够持久的保存数据不丢失。

基于此硬件与阿里云持久化内存 Tair 产品,阿里云发起第二届数据库大赛——Tair 性能挑战赛,希望与 Redis 兼容的 Tair 数据库产品和 AEP 硬件产品进行完美结合,并且通过实际团队在线上近一两年的磨合,帮助客户更好使用这款产品体验新型科技对自己业务带来的实际增值,也希望带来产业的变革。

针对此次 Tair 大赛,陈绪表示,一款项目的成功,一个社区的成功,与其背后的生态息息相关。技术、产品做得好是基本条件,更重要的是需要看其背后有多少人与之同行才是*后成功的标志。Tair 数据库是基于 Redis 修改,而 Redis 作为一款非常流行的开源社区产品,有着广泛的生态基础。

而想让 Redis 产品获得更多客户认可时,仅凭一个公司是很难的,但是通过这个此次大赛,陈绪表示,让感兴趣的人能够进来,让他们能够有机会在阿里云平台之上成功。

%title插图%num

阿里云背后,开源的驱动力

在互联网发展至今的过程中,我们知晓*难或不是研发的过程,而是生态的构建,在这一点上,对于阿里云而言,亦然。

在生态构建背后,开源的应用起到了至关重要的作用。

面对国内开源的发展,陈绪表示,国内开源公司和开源爱好者的一些开源举措确实有很多可圈可点的地方,但在很多基础软件部分,开源或是一个必然趋势。简单来看,当别人用你的产品和你竞争时,其实其竞争力并不强,为什么?因为项目代码的路线图是你制定的,代码修改的能力掌握在你自己手上。在开源社区中,更多的是看重技术能力、持续演进能力,而不是短期的商业利益。

时下,因为国际大形势的变化,存在较强的不可控性,需要提前做好准备,但这不代表今天就要与别人脱钩,这是不对的。

打一个比喻,国际社区可视为一个大海洋,分流后到社区、企业中可将其看做是一个小池塘,池塘和大海之间有一个连接的水道,池塘来源于大海的营养供给,全世界的池塘也为大海不停地贡献并带来了资产增值。但池塘是否能丢弃?亦或直接基于大海?

这肯定是不行的!

毕竟大海的风浪太大,变换无穷,众人难以控制。

因此,陈绪认为,*好的情况是维护好小池塘,并把水道开着,能够进行互通。如果这个小池塘能够抚育出一个大鱼来,则可以把大鱼放到海里去,为全世界所共享。另外,海里营养不停的演化和更新,大家会不停的感受到水位的变化。简单来说,就是要做更好的自己,要想象所有的不可控因素,即使在*端情况发生的情况下,你还是能够把握自己的命运,不至于因为别人的变化导致自己受到损害,这是*基本的底线。

作为创新的源泉,针对开源与闭源、开源与自研是否矛盾的问题,在阿里云看来很简单:

首先,开源与自研发不矛盾。

因为,所谓自研:

  • 要掌握所有源代码;
  • 拥有修改每一行代码的能力;
  • 安全能力。即保证系统不受外来攻击干扰,保证客户的数据不被人窃取、客户的业务不会被中断,这就是自研的概念。

在自主研发的同时,不断地将代码按照周期与大家共享,社区也因之而受益。从长期来看,自主研发和开源兼容是我们长期坚持的两只手,每一只手都不能放,如果放弃自研就放弃了自主的意义,若放弃了开源就放弃了产业的生态兼容性。

在开源从*早弱版权时代,到现在的云原生迭代,陈绪表示,开源关键点在于标准。不同的厂商遵循相应的标准,客户就没有锁定的风险,可以不停地迁移,总是能找到*好的厂商,将自己的业务放上去,用*简单、*便捷、*经济的方式来运营自己的业务。

%title插图%num

中国开源商业化模式之路该如何走?

 

不过,比较遗憾的是,在开源与云原生等主流发展趋势下,目前,国内市场上暂没有一家完全基于开源来进行 IPO 的公司,那么对于中国开源商业化道路究竟该如何走?

虽然暂时还没有基于开源且 IPO 的企业,但已有很多公司正走在这条路上。其次,通过开源,很多中小创业型公司在与上市公司合作过程中提升了自己的估值,也提升了上市公司的估值,从一倍、两倍,甚至高达十倍。而想要建立中国的开源商业模式,或可以从四个方面来逐步实现:

  • 个人获利。这一点,Linux 之父 Linus Torvalds 就是典型的通过开源而收获成功的案例。
  • 公司获利。公司做一个事情并不需要从底下往上搭建一个系统,如你做的是上面的 10%,下面的 90% 是公用的,公用部分不是你的优势也不是真正的获利点,你就可以基于社会的公共平台之上构建你的10%,快速提高自己的迭代效率,公司发展也会更加从中受益,保证新的创新公司层出不穷,企业也是会受益的。
  • 产业获利。产业中有公共的部分,如 Linux 操作系统,任何一家公司完成代码的编写都是难以想象的,而如今有了协同,就有了共同创新的平台,不用新造轮子,这是对产业的帮助。
  • 对国家有重大战略意义。从没有变成有,从零开始变成基于一个平台之上的再创新。对此,陈绪表示,阿里云有一位技术负责人说过,阿里云做超算的目标不是建设世界上的*高峰,而是搭建一个青藏高原,阿里云希望客户在此上构建他们自己的珠穆朗玛峰,这对国家产业的提升、对国家科技实力的发展具有直接的助力。

透过开源,我们目光所及之处或仅为冰山一角,更深层次的潜能仍需我们继续探索,开源之路,任重而道远,陈绪如是说。

 

%title插图%num

开源时代,云原生已至,企业、开发者需如何入局?

 

在云时代,陈绪表示,首先更多的人要真正意识到自己在这场游戏中所处的地位,因为无论你参加游戏与否,该场游戏都会往前推进,不参加的话就错失一次良机,甚至以至于在多年后回想起现在,会发出一声感叹:当时那么好的机会展示在我面前,为什么我不抓住!

其次,通过开源创新、前沿技术加持,阿里云围绕云计算、大数据等维度构造了自己的核心竞争力。面向企业的开源创新,建议小型公司不要过于执着于基础性东西的研究,如底层架设网络、铺设物理硬件等,因为这些在规模化云计算到来之际,已由云计算相关公司完成了。除此之外,很多聚焦云的公司也结合了高端大规模容量存储,并配备了一些灾备技术。

对云计算从业者而言,应该往上看

「云计算时代下,底层 IaaS 技术基本上形成了一种——集约化现象。即 laas 产品种类越来越单纯、越来越集中,那其*基础的功能越来越强、水平越来越高、成本越来越低,这些产品是云公司所擅长的,其规模大了之后打造每个部分的价值和*优的解决方案。

往上走到 PaaS 层时,很难有一个公司能够做到提供世界上所有的PaaS,因为它是纷繁复杂的业务需求。继续往上,SaaS 层更加繁纷复杂,它的种类会更多,在不同领域中,用户的需求是千奇百怪的,定制化的东西让你匪夷所思,所以这也是云计算的开发者和厂商未来的一个发展着力点。因为这些东西做得好,能够获得*大的价值。

整体而言,云计算厂商做的是规模化业务。但是越往上走亦或是更加细分市场才是新兴企业能够获得自己的机会,传统的软件企业能够利用云时代的红利,将原来在线下输出的一些软件变成服务。

从成本和财务运算的维度来说,这也是使用云资源、租用云资源更加符合现代企业的利益,来帮助其实现更大的价值。」

*后,云计算时代已至,「少一些疑虑、多一些勇敢」

深度揭秘:腾讯存储技术发展史

在腾讯内部,负责腾讯存储技术研发的部门,一直被认为是生产技术专家的“黄埔军校”。腾讯不少技术方向的负责人,*早也都出自这个团队。

 

这或许可以看出这只团队的底蕴——对于腾讯来说,存储是几乎所有业务开展的基础,存储技术和架构的迭代是腾讯*关注的技术方向之一。

 

作为海量数据的坚实承载,腾讯存储技术支撑了微信、QQ、QZone、邮件、微云、流媒体等内部业务的快速发展,同时也为快手、OPPO、小红书、海康、猎豹、58同城等几十多万个客户腾讯云上客户提供了可靠的服务,整体数据量高达EB级别。

 

本文中,我们将采访腾讯云存储技术相关负责人,深度揭秘腾讯云存储从零开始,到如今支撑起ZB级数据存储系统的技术演进历程,以及背后一些有趣的故事。

%title插图%num

腾讯存储技术演进历程

 

腾讯存储技术的演进基本上可以分为以下阶段:

1. 海量存储(2005~2010),支撑社交网络,构平台,撑海量

 

腾讯存储技术的发展,起源于2005年前后国内社交网络兴起。QQ、QZone(相册)业务的发展带来了海量的图片、文件、头像等UGC数据。为应对海量存储的挑战,腾讯云开始自研并构建了几套核心存储平台,包括TFS(通用性存储平台),CTFS(数据频删型存储,用于短期临时存储),TDB(基于HDD的键值对存储平台),TSSD(基于SSD的键值对存储平台)。

 

这些自研平台为腾讯对象存储的发展奠定了基础。基于存储SET容量模型,腾讯云构建起标准可控的运营体系,支撑起了QQ空间、微云、QQ等产品的海量存储需求。

2. 跨地分布(2011~2013),分拆大体量业务,搬数据,腾机房

 

2011~2013年,QQ相册等大体量业务的访问量、存储量迅速增长,对深圳的三通机房在机架、带宽方面产生了较大压力。为应对底层机房,带宽等方面的瓶颈,存储平台启动了相册一通点等项目,海量业务数据开始从深圳向西安、杭州、广州、上海等地分布,访问带宽同时调度到天津、南京、东莞等成本更低的一通机房。

 

存储平台在做数据分布搬迁的过程中,自身也在同步迭代演进。比如TFS从3份存储演进为同时支持1/2/3份数据的多副本存储;CTFS从2份存储演进为同时支持2/3份数据存储。TSSD平台也从仅支持单机独享的Uin-Value定制存储演进为支持多租户的通用Key-Value存储。

3. 冷存储(2014~2015),分离冷数据,降份数,省成本

 

腾讯相册、微云、邮件等业务在业务发展中,逐步积累起来较多的UGC历史数据,这些历史数据访问量低、存储量大,对业务的运营成本构成了较大的压力。为了应对冷数据存储的成本挑战,2015年前后,腾讯基于纠删码研发了BTFS平台(Backup-TFS)。对业务进行了数据分层,增量数据访问量大,用TFS存储3份;历史冷数据从TFS平台剥离出来,从3副本存储转向1.33副本的纠删存储,降低了存储成本。这个阶段中,腾讯优化BTFS,使得部分低访问量业务的增量数据也可以直接存储进来。

4. 云存储磨合期(2016~2017),重构系统,兼容接口

 

在磨合期,腾讯云基于已有的存储访问接口和平台架构对外提供服务。经过一段时间的运营,腾讯云发现外部第三方业务在体验、可用性、成本等诸多运营方面有*高的要求。为支撑云的需求场景,腾讯云对存储的接入层和索引层架构进行重构,架构扁平,模块精简,同时开始舍弃私有接口,转为兼容AWS S3接口与功能。重构后存储架构支撑了腾讯云COS近几年的发展。(注:对象存储COS,CloudObjectStorage,是由腾讯云推出的安全稳定、便捷易用、低成本的海量云端存储服务。)

5. 多云时代(2018~),对标行业,比成本,拼质量

 

伴随公有云的逐步普及,用户存储的数据成几何倍数增长,硬件发展日新月异,公有云提供商间的竞争日趋激烈。2017年,腾讯云的数据量突破一个EB,成为腾讯存储历史上的一个标志性节点。

 

AWS S3的存储访问接口已成为公有云上对象存储事实上的行业标准,存储成本、运营质量成为客户上云选择产品提供方的主要考量。

 

在这一阶段,腾讯重新审视了机房,机型等方面的技术演进趋势,重构了公有云对象存储引擎(详解见下文)。新架构采用大容量高密度存储机型,针对存储SET导致的闲置资源分散和隔离等多个主要矛盾,进行了全面应对和优化。

 

先从整体上对腾讯云存储有了一个大致了解后,我们再从各阶段一些重要节点事件,来了解腾讯云存储技术演变背后的逻辑,供业内参考。

 

%title插图%num

七八个人搞了TFS

 

2005年,QQ空间发布,大量存储需求爆发。

 

腾讯云副总裁 、云架构平台部总经理谢明,以及腾讯云架构平台部系统研发中心总监郭振宇,都是腾讯云在2005-2006年开始做统一存储架构时期的重要亲历者。

 

2003-2004年,谷歌发表了俗称“三大件”的论文,即BigTable、GFS和MapReduce,这是从原先的企业级存储向互联网时代大规模使用廉价的分布式存储的一个较早期的实践。

 

受此启发,谢明、郭振宇与团队中七八个人开始做通用性存储平台TFS,把RPC框架和通信协议、存储相关的数据迁移、巡检系统等逐步做起来,第二年初就将这套存储系统用到了 QQ 相册。

 

用上TFS后,QQ相册解除了每天800万张图片的上传限制,结束了那段只有QQ黄钻用户才能无限上传图片的历史,普通用户再也无需凌晨24点守在电脑前抢着上传图片。

 

但与谷歌 GFS 主要面对搜索场景,因此主要解决的问题是大文件存储不同,腾讯的业务里图片较多,小文件才是要解决的主要问题,但是TFS并不适合做小文件存储,因此又创建了KV存储平台——TDB。

 

再往后,腾讯云的业务中又出现了一些需要频繁删除的场景,CTFS应运而生,用于短期的临时存储。这套系统在频删型的场景,如文件中转站中非常实用。积累了一定口碑后,其他业务也逐渐迁移到CTFS。

 

除了这些,团队还针对业务场景进行用户体验优化,例如优化了索引。原先的文件系统索引有很多层,拓展性强,但是每次硬盘要进行多次访问。当文件较大,硬盘多次访问会造成延时。为此,TFS设计之初就力争硬盘只访问一次,把chunkID(段表示)和FID索引放在一起,用户读取索引时就能知道图片在哪块盘上,只要访问一次硬盘就能读取图片,减少磁盘IO次数提升了用户体验。

 

之后,TFS又陆续被用于网络硬盘、QQ邮箱等产品中。

%title插图%num

腾讯史上*大数据搬迁

 

时间到了2009年,腾讯日益扩张的业务与激增的数据量使得存储的访问带宽问题暴露出来。

 

%title插图%num

2009年腾讯存储团队合影

那时,腾讯几乎所有的数据都在深圳,但当时国内的骨干网宽带与现在不可同日而语。QQ相册高峰时占用40-50G,而1G的流量对公司的网络就已经是很大的负担了。因此,在控制带宽高峰的驱动下,腾讯开始考虑把存储向西安搬迁。

 

谢明回忆,当时搬迁的*个设备的数据量是100T。现在看来,这个数字也许并不大,但当时却是腾讯历史上*大的一次数据搬迁,因为那时候基本上没有专线可用,数据搬迁都是趁着半夜公网空闲搬迁过去的!

%title插图%num

微信红包爆发带来存储压力

 

2014-2015 年,移动互联网崛起。

 

在这期间,腾讯也有两件大事发生。一是微信开始流行,2014年除夕的微信红包,连带着朋友圈的图片分享让微信火爆起来。原来在 PC 时代春节期间不怎么值班的腾讯工程师们发现,春节期间不得不安排更多人值班了,以应对除夕和元旦凌晨12点到12点10分这十分钟内,抢红包和图片发布剧增的数据。腾讯的存储系统面临着史无前例的压力。

%title插图%num

2016年腾讯存储团队春节值班零点后留影

 

为此,谢明团队与微信团队沟通,对大型缓存系统、延时通知等技术进行改进。水来土掩,兵来将挡,到2015年腾讯存储已经可以应对除夕夜突发性问题。

 

第二件大事是网络硬盘的发展给存储带来很大的成本压力。作为一项免费服务,如何控制成本成了谢明团队的又一个巨大的挑战。为此,团队于2014年完成并上线了基于纠删码的BTFS平台。

%title插图%num

业务也是技术发展的助推器

 

腾讯云存储技术能力的不断完善,也离不开业务不断提出的挑战。

 

郭振宇举了两个例子来说明当时团队面临的挑战,一个是微信与海外同类产品,如在东南亚人气很高的Line和WhatsApp。为了在竞争中获得优势,微信做了很多事情,比如东南亚用户与国内进行数据沟通时链路很长,团队就基于存储做了加速通道,从而加速了国内外数据传输,速度超过Line和WhatsApp。

 

另一个例子是游戏,QQ农场相信对于每个90后都不陌生,但不一定有人了解,全民偷菜的热情曾让腾讯的服务器数度崩溃。当时,农场访问量巨大,但腾讯还没有全存储系统,TDB是基于SAS盘存储的,在每秒数万的并发访问下,底层存储系统的延时和请求吞吐挑战非常大。

 

腾讯基本上把公司所有闲置服务器都用在QQ农场,但仍远远不够,需要大量采购服务器。团队一方面通过疯狂的机器扩容,另一方面基于数据规模不太大但是访问量*高的业务特点,快速研发了全内存的分布式存储系统,在保障数据安全可靠的前提下,并发访问的性能得到*大提升。快速上线、快速验证,完全自研,团队再次闯关成功,“修为”再上一层楼。

%title插图%num

从开放平台到服务B端,很多事情变了

 

面向B端客户提供服务,是腾讯云存储技术的另一个关键节点。

 

从2010年起,腾讯就开始做开放平台云。2013年,腾讯云正式面向B端用户全面开放。

 

在计算云迅猛发展的大背景下,行业竞争日益激烈,客户可选择面较大,对功能、性能、质量、价格等方面的需求不断增强。

 

此外,从腾讯云自身现状来说,数据量的不断增大,随之带来软硬件成本、运维成本优化的收益日趋增大。

 

同时,行业下游的基础设施,如大型数据中心、新机型、新硬件、各种存储介质、网络环境的不断发展演进。相比十几年前,单个IDC机房从数千台,到腾讯目前超百万装机量超大规模机房;网络从千M网卡,发展到百G网卡,整个基础设施发生了天翻地覆的变化。

 

这些变化,都在呼唤腾讯云在存储系统架构上做出改变。

%title插图%num

YottaStore,打破传统IT存储系统架构

 

腾讯云从2017年开始构思,并于2018年正式启动了下一代云原生对象存储系统设计——YottaStore。

 

名字由来

 

YottaStore这个名字实际上取得很有深意,腾讯云架构平台部存储研发中心总监杨奋强介绍了Yotta这个词背后的含义。

 

对于做存储的同学来说,经常会跟GB、TB、PB、EB这些概念打交道。现在全球互联网非常大的巨头公司的数据量基本都是在EB这个量级。EB上面是ZB,全球互联网巨头数据起来也就几个ZB;ZB上面是YB,也就是YottaByte,目前全世界所有的数据加起来也不会超过一个Yotta。另外这个单词中文译名“有他”,给人安全可靠放心的感觉,系统的Slogan就是“存储有他,能力无限”。

 

所以,Yotta——有他既是腾讯存储同学们对这个系统海量数据存储能力的深厚期望,也是对服务质量和可靠性的巨大承诺。为了实现上述期望和承诺,YottaStore实现了众多业界独一无二的技术突破。

 

技术突破

 

集群规模:YottaStore是一个云原生的数据存储系统,这个系统的理论*限是一个集群可以管理超上千万台服务器。而要管理这上千万台的机器,元数据管理只需要用600G左右的空间,用一台机器就能存下索引结构,这在业界是*无仅有的。

 

资源利用率:当集群规模非常大的时候,其实1%的剩余空间量都是非常非常大的,所以YottaStore的硬盘利用率非常高,加上实时回收机制,有效数据占比达90%以上,这在业界非常少见。

 

另外,由于大集群的全集群均衡能力,服务器资源使用均衡,所以资源使用率也可以做得很高,这样服务器硬件可以*低位配置,所有尖峰流量在这个异常大的池子里,波澜不惊。所以无论是成本、还是服务能力,都很大程度受益于超大规模集群带来的红利。

 

灵活性:YottaStore单集群可以零研发成本同时支持各种不同的冗余模式,像两副本、三副本、四副本、五副本,任意的EC编码,任意的M、加任意的N、任意的算法;单AZ、双AZ、多AZ也都可以灵活支持;另外,整个集群可以自适应各种各样不同的机型,包括JBOD;各种硬盘介质,如磁带、HDD、SSD等,存储的拓扑结构、混合部署也都可以任意指定。这样的灵活性在业界首屈一指。

 

运营能力:以存储节点迭代升级为例,十万百万规模的一个集群,上线升级速度都是一样的,如果是同构的数据格式,分钟级就可以完成整个升级过程;而如果是异构的数据格式,集群可以在短时间内自动将数据格式透明收敛到*新版。

 

可用性:YottaStore开始上线大规模支撑业务的前三个月一直维持百分之百的可用性,到现在一年半了,系统一直单人值周零故障运行,在业界是*少见的。可用性数个9很容易,但是可用性100%非常难。例如机房网络抖动,如果容错做的不够好,就很容易就会出现失败。但是YottaStore在初期上线的很长一段时间都是百分之百的可用性。

 

业务支撑

 

YottaStore是腾讯云对象存储服务COS的数据存储引擎,支撑起整个腾讯云海量数据的存储。COS的标准存储、低频存储、归档存储、多AZ存储等产品形态都是基于YottaStore推出。

 

得益于YottaStore在集群规模、资源利用率、灵活性等方面的技术突破,近期COS重磅推出的深度归档产品刷新了业界存储的*低价:1分钱/GB/月。

 

激烈竞争环境下,成本是存储业务胜出的关键因素之一。YottaStore是如何优化成本的呢?

 

资源利用率提高:基于前文所述的在超大规模集群和超高资源利用率上的技术突破,随着资源利用率的增高,单位存储的成本随之降低。

 

高密硬件适配:随着磁盘容量扩大,单机磁盘数变多密度增高,成本也随之降低;此外,CPU、网卡等新硬件的变化都会导致成本降低。

 

场景优化:针对海量小文件的用户场景,YottaStore采用多种冗余和数据组织策略持续优化成本。

 

此外,对于一套承载着用户业务数据的存储系统来说,数据一个都不能丢、安全可靠是*重要的承诺。在数据可靠性方面,YottaStore在数据冗余、数据巡检、数据修复、数据校验等方面做了很多工作,保障了数据的可靠性。

 

到这里,我们回顾了腾讯云存储技术发展的全部历程,也得以从中一窥互联网大厂在底层存储技术与业务发展紧密结合,“配合作战”成功的秘诀,希望对业内有一定的启发。云时代下,腾讯云存储还将有哪些创新,我们拭目以待。