Amazon CloudFront 部署小指南(五)- 使用 Amazon 边缘技术优化游戏内资源更新发布...

b6edfd5811de9837dbe2421aec3a924d.gif

内容简介

游戏内资源包括玩家的装备/弹药/材料等素材,对游戏内资源的发布和更新是游戏运营商的一个常规业务流程,使用频率会十分高,所以游戏运营商希望该流程可以做到简化和可控。针对这个需求,我们设计了 3 个架构,面向游戏内资源更新发布的 3 个典型场景,来优化游戏内资源更新发布流程。3 个场景分别为:

  • 游戏内资源灰度更新

  • 游戏内资源分渠道投放

  • 游戏内资源黑白名单

该 3 个架构围绕亚马逊云科技 Amazon CloudFront 服务 — CloudFront 边缘计算服务 — WAF 服务展开实现。

场景一:游戏内资源灰度更新

游戏在运营过程中会有资源更新的需求,用一个简单模型来描述:老的资源 A 需要替换成成新的资源 B。不过这里有一个痛点,B 资源在推广到所有 CDN 之前,需要内部或者小范围确认资源的正确性。因此需要一种方式来保证 B 资源哪怕在源端更新了,只有受限的访问;大部分的玩家还是访问的还是 CDN 缓存内的资源 A。等 B 资源验证通过后,才会通过 CDN 刷新推广到所有的节点。

这个需求实现的难点在于:在客户端不做任何修改的情况下,使用相同的 URL 将少量用户请求引导资源 B;资源 A 和资源 B 不能共享缓存,以免引起资源访问冲突。我们采用 CloudFront Continuous Deployment 来实现这个需求:

8a2c1c20ef75ee97fd5cd90c392f03e7.jpeg

配置步骤如下:

  1. 创建 Staging Distribution

  2. 功能验证

  3. 调度流量

  4. 正式上线

CloudFront 持续部署功能在实现上比较直观,用户可以通过控制请求配比方式实现金丝雀式的资源更新;在成本上相比于部署 CloudFront 边缘脚本 CloudFront Function 会更低一些。

创建 Staging Distribution

在 CloudFront 生产 distribution 下,点击“create staging distribution”

d46c92737bcfda2983fe5e4dd477a767.png

可以输入 Staging Distribution 的描述,然后点击“Next”

1b3f8dacbba2e213cba45f8708a4e7ea.jpeg

创建一个新的源站。用户可以对应创建一个新的源站部署资源 B(使用相同目录结构);也可以在现有源站上将资源 B 部署到另外一个目录中,然后创建新的 Host 容器,并且在 DNS 服务器上创建指向新的 Host 容器的记录

93b79b687b464d369e8332b6ba57fa74.jpeg

c27565c44ae1f612e44ad81a127c3bba.png

*注意:我们在新的 origin 中改写了 origin path,使得客户端无需做变更。

流量配置选择“header-based ”比例先选择为 0

548fc8b589b343e3da06fe6808c8fd4b.jpeg

创建 Staging Distribution

be6257f0f8bf6a3835d5e14d6e7e4189.jpeg

至此 Staging Distribution 创建完毕,等待 10 分钟左右部署完毕

e5fd56f6f9d160df7fd8dbe99b216d80.png

功能验证

可以使用命令行工具 curl 进行验证:

resource_b 下载:

curl -H 'aws-cf-cd-resource:b' -v -o /dev/null 'https://www.company.com/resource'

左滑查看更多

resource_a 下载:

curl -H -v -o /dev/null 'https://www.company.com/resource'

左滑查看更多

通过观察下载的文件特征如字节数或者 ETAG 判断是否下载了不同内容的资源对象。

调度流量

点击‘Edit Policy’

21b4b0a012813e7b13eed3da24f0a269.png

在弹出窗口中修改流量调度策略,调度 1%的流量进行灰度发布

3a4571ad9ce4d2fce4f68ac1220370da.jpeg

063d907e8ca48f86aa043baae3a3ce49.png

正式上线

用户在源站正式将 Resource B 覆盖 Resource A 后,需要在 CloudFront 上进行缓存刷新,并删除 Staging Distribution

bfc190ca29d462c16d31511a7f95cfba.jpeg

759e6a8f4b0441cafe62dd58a0e31fa5.jpeg

场景二:游戏内资源分渠道投放

游戏在运营过程中会有针对不同渠道投放不同资源的需求,用一个简单模型描述:对于资源 A 和资源 B,从不同渠道进来的玩家会看到不同的资源。这里的痛点为,客户需要针对头部某个字段或者 url 中某个关键字快速匹配对应的资源。

内容概述

在这个方案构建中,我们将利用到 CloudFront Function 检查请求头部的字段,并根据匹配到的字段进行回源 URL Path 替换,此方案的好处在于,URL Path Rewrite 对于最终用户是无感知的,符合不同渠道进来的玩家对于某个相同的资源(比如资源 A),回源获取到的内容将会不同。

配置步骤如下:

  1. 创建 CloudFront Function

  2. 创建 Behavior 匹配存在不同渠道的 URL Path

  3. 实验验证及结论

创建 CloudFront Function

在此需求中,不同渠道的所获取的内容不同,意味着在源站侧(如 S3)上的路径信息不同,如用户请求的文件路径统一为/sample.file,而不同渠道在源服务上所对应的文件真实路径为/a_vendor/sapme.file 

或是/b_vendor/sample.file,我们可以根据不同渠道(携带不同的请求头信息),来对请求头值进行提取,并进行回源 URL 替换,具体 CloudFront Function 参考代码如下:

function handler(event) {// 获取请求头var request = event.request;var headers = event.request.headers;var originUrl = event.request.uri;// 检查 x-vendor-type 请求头是否存在if (headers['x-vendor-type']) {var targetObject = headers['x-vendor-type'].value//修改回源 URL Path event.request.uri = '/' + targetObject + originUrl;}return request;
}

左滑查看更多

*注:此处 header 的值不直接取而是先进行判断的原因是,某些客户端可能不会携带 x-vendor-type 这个头,如果不增加判断直接取值,那么 header 不存在的情况将会导致 CloudFront Function 报错,在您部署代码时也可以进行相应的考虑。

CloudFront Function 代码保存完毕后,我们可以在调试界面进行调试并观察效果:

071f49643310ce291860825f5e62b7eb.png

提交调试信息后,可以看到回源路径成功被修改,如下图:

80f004c14a0fe0f481986cb67a905b57.png

调试无误后对 CloudFront 代码进行部署:

b6ea13476e01b0587b216183a9c35ad2.jpeg

创建 Behavior 匹配

存在不同渠道的 URL Path

此处,为了方便展示效果,我们在 CloudFront 中的 Behavior 根据需求,进行路径匹配,此处的匹配以“/abtest-demo*”进行展示。

d1a929fc5154b8d770256e294216e810.jpeg

由于此处仅做 URL 改写,不同渠道针对相同 URL 将会拿到不同的内容,此处您还需要在 Behavior 中针对不同的请求头进行缓存上的区分,您可以自定义 Cache Policy,并将 x-vendor-object 请求头作为 Cache Key 的一部分,以进行缓存区分:

1a02a27601f4fd96d85b9e24726942b4.jpeg

自定义 Cache Policy 后,在 Behavior 中进行应用:

3c4d62f77b706f0f6cead9c7b9d4ee07.jpeg

在 origin policy 中,需要将自定义的请求头进行透传,以便 CloudFront Function 可以进行请求头的检查,您可以自定义 Origin Policy,也可以选择 AllViewer 将自定义请求头进行透传。

由于该改写逻辑运行的 event type 是 Viewer Request,您需要在 Behavior 定义中在正确的 event 中应用上述部署的 CloudFront Function 逻辑。

44d2fe195707a786066095791b94cfd2.jpeg

实验验证及结论

在源服务侧,我们分别针对 A 和 B 渠道存放不同版本的文件,以供调试,此处示例文件对应关系如下:

渠道 A :a_vendor

渠道 B :b_vendor

携带对应的请求头,多次请求后并观测效果:

//在请求头中携带 A 渠道特殊标识,回源真实路径为/a_vendor/example.file
% curl -v http://cloudfront-function.xxxxxxx.com/example.file -H 'x-vendor-type: a_vendor' 2>&1 | egrep 'X-Cache|This content'
< X-Cache: Hit from cloudfront
This content is for a vendor!//在请求头中携带 B 渠道特殊标识,回源真实路径为/a_vendor/example.file
% curl -v http://cloudfront-function.xxxxxxx.com/example.file -H 'x-vendor-type: b_vendor' 2>&1 | egrep 'X-Cache|This content'
< X-Cache: Hit from cloudfront
This content is for b vendor!//在请求头中不携带渠道特殊标识,回源真实路径为/example.file
% curl -v http://cloudfront-function.xxxxxxx.com/example.file 2>&1 | egrep 'X-Cache|This content' 
< X-Cache: Hit from cloudfront
This content is for general usage without vendor!

左滑查看更多

通过以上的演示,我们成功的验证了以下三个设置需求:

  1. 您可以清楚的看到,针对相同的 URL path   为/example.file,通过不同的 x-vendor-type 指定内容,我们成功的改写了回源 URL,并根据获取到了不同渠道所对应的内容。

  2. 如果客户端不是通过渠道发起的请求,不携带特殊标识头,也可以成功拿到内容。

  3. 在命中缓存的情况下,由于 Cache Policy 将 x-vendor-type 作为缓存的一部分,在 URL 相同的情况下,请求依然可以按照预期命中到各自对应缓存。

场景三:游戏内资源黑白名单

场景描述

游戏在运营过程中会有不同玩家有不同访问行为的需求,用一个简单模型描述:对于玩家 A 需要放在白名单中,对于玩家 B 需要放置在黑名单中。

用一个更具体的场景描述:假设需要通过 WAF web ACL 去匹配出 user-agent 中包含 “PostmanRuntime/x.xx.x” 的请求,并且拒绝该请求的访问。

对于上述场景,可以通过检查请求 header 重的 User-Agent 来实现。对于请求来说,通常会经过几个链路:客户端→公网→Amazon CDN 接入点→ 原站。那么将检查 header 的压力放在 CDN 处是比较好的选择,可以有效的减少原站的压力。在 CDN 侧检测也有几种方式。最先想到的是在 CDN 上用 edge function/lambda 来实现。这里会带来的挑战是需要是维护代码。另外一种比较好的方式是利用 WAF 对于规则来进行检查。WAF 在配置 Web ACL 的时候,需要配置允许和阻止的条件。通常包含的条件有:

  • 请求是否表现为包含恶意脚本允许或阻止请求 → 跨站点脚本匹配条件

  • 请求源自的 IP 地址允许或阻止请求 →IP 匹配条件

  • 请求源自的国家/地区允许或阻止请求 →  地理匹配条件

  • 请求是否超过指定长度允许或阻止请求 → 大小约束条件

  • 请求是否表现为包含恶意 SQL 代码允许或阻止请求 →SQL 注入匹配条件

  • 出现在请求中的字符串允许或阻止请求 →字符串匹配条件

  • 出现在请求中的正则表达式模式允许或阻止请求 → 正则表达式匹配条件

默认情况下,Amazon WAF 过滤器不检查 HTTP 请求参数是否存在。要检查 HTTP 请求参数是否存在,可以采取下面两种自定义规则中的任意一种:

  • 使用字符串匹配规则

  • 使用正则表达式匹配规则

环境准备

在测试之前我们需要做如下步骤:

1. 进入 Amazon Website Service Console 界面,选择 WAF 服务,然后在左边导航栏选择 WAF ACLs。

2. 创建一个 Rule,根据业务需求命名。然后保护的对象可以是 CDN 也可以是负载均衡器之类的对外服务。资源部分可以现在就填写,也可以等待 Rule 创建完毕后再绑定。

dba413162bb6d86b0d80411e7ea5d5d1.png

3. 完成 Web ACL 的信息录入之后,在第二部我们选择自定义规则。

a9e5477ef4b9ee3032a50c25a2fc568d.png

4. 在自定义规则中,我们选择 Rule Builder。并且设置好 Rule 的名字。

5. 在 inspect 中,选择 All headers,header content 选择默认的所有 keys and values,当然可以根据业务需求只监控特定的 header 和 content。

8a7dcf936dcdea1890e49d8b139697bf.jpeg

字符串匹配规则

对于字符串匹配规则,Web ACL 提供如下几种方式:

  • Exact matches string:严格匹配字符串

  • Start swith string:匹配字符串开头

  • Ends with string:匹配字符串结尾

  • Contains string/word:包含某个字符串

根据需求,使用 Start with string 即可,设置如下:

07c5df01f8f3719306b8860f08187349.jpeg

正确表达式匹配规则

正则表达式规则,Web ACL 提供两种匹配方式:

regex pattern set:正则表达式集合,可以提前设置,在面板中选取即可

regular expression:正则表达式,临时设置

这里我们选择第二种方式,其设置参考如下

59c6ce77bd3446d973667b6e10c9393e.jpeg

最终规则的 Json 格式参考

{"Name": "StringMatch","Priority": 0,"Action": {"Block": {}},"VisibilityConfig": {"SampledRequestsEnabled": true,"CloudWatchMetricsEnabled": true,"MetricName": "StringMatch"},"Statement": {"OrStatement": {"Statements": [{"ByteMatchStatement": {"FieldToMatch": {"Headers": {"MatchScope": "ALL","MatchPattern": {"All": {}},"OversizeHandling": "CONTINUE"}},"PositionalConstraint": "STARTS_WITH","SearchString": "PostmanRuntime","TextTransformations": [{"Type": "NONE","Priority": 0}]}},{"RegexMatchStatement": {"FieldToMatch": {"Headers": {"MatchScope": "ALL","MatchPattern": {"All": {}},"OversizeHandling": "CONTINUE"}},"TextTransformations": [{"Type": "NONE","Priority": 0}],"RegexString": "/PostmanRuntime[ \\/]+([0-9\\.]+)/gi"}}]}}
}

左滑查看更多

规则测试

规则建立好后,我们需要将它运用在保护的资源中。假设源站是一个网站,前面为一个 CDN,它只是返回一个 text=ok。

测试如下(User-Agent 的值为 X-UnrealEngine-Agent):

7a3297c099be1ff99fe1b4658fc1baf3.jpeg

当请求头,包含了 

PostmanRuntime/x.xx.x,可以看到请求被 block

b6a57a695a90bcac5c1bde3b3bbf5e06.jpeg

在后台的界面中,也可以统计到过去 5 分钟的 WAF 的表现情况

144d042c25b2e5d1ff0eb7b553a727b5.jpeg

可以通过以上实验,WAF 对于 CDN 的保护提供了十分灵活的配置规则,可以满足各种不同场景的需求。更进一步,它解放了 CDN 去做规则匹配和检查的压力,让 CDN 去做它更擅长的业务。

总结

通过亚马逊云科技 CloudFront 服务 — CloudFront 边缘计算服务 — WAF 服务,游戏运营商可以在不改动客户端的情况下,完成游戏内资源在预设条件下的更新发布,提高了业务部署的敏捷性和简易性。

亚马逊云科技 CloudFront 

部署小指南系列文章

Amazon CloudFront 部署小指南(一)- 快速构建 CDN 内容分发:

https://aws.amazon.com/cn/blogs/china/amazon-cloudfront-deployment-handbook-part-one/

Amazon CloudFront 部署小指南 (二)- 进阶部署:

https://aws.amazon.com/cn/blogs/china/amazon-cloudfront-deployment-handbook-part-two/

Amazon CloudFront 部署小指南 (三)- 持续部署:

https://aws.amazon.com/cn/blogs/china/amazon-cloudfront-deployment-handbook-part-three/

Amazon CloudFront 部署小指南(四)- CloudFront Function 基础与诊断:

https://aws.amazon.com/cn/blogs/china/amazon-cloudfront-deployment-handbook-part-four/

Amazon CloudFront 部署小指南(六)- Lambda@Edge 基础与诊断:

https://aws.amazon.com/cn/blogs/china/amazon-cloudfront-deployment-handbook-part-six/

本篇作者

b783fcdcbafc13378d04523d519fd578.jpeg

叶明

亚马逊云科技边缘产品架构师,负责 CloudFront 和 Global Accelerator 服务在中国和全球的市场拓展,专注于互联网用户访问云上服务的感受的优化以及数据洞察。在互联网基础设施领域有丰富的实践经验。

a799db5f6f258445cf528b4b9f3690c7.jpeg

万曦

亚马逊云科技解决方案架构师,负责基于亚马逊云科技的云计算方案的咨询和架构设计。坚实的 Amazon Builder 文化拥抱者。拥有超过 12 年的游戏研发经验,参与过数个游戏项目的管理和开发,对于游戏行业有深度理解和见解。

7ec66f9a2bf33c10f4d6ac3667de4cd4.jpeg

王骏兴

亚马逊云科技边缘产品架构师,负责亚马逊云科技 Edge 服务领域在中国的技术推广。在 CDN 内容分发以及 WAF 领域拥有多年实战经验,专注于边缘服务设计以及体验优化。

bc19f81ae56a9bc6ba8bb2e3ede91962.gif

26e59c5bdfdd213910a76eac3da13034.gif

听说,点完下面4个按钮

就不会碰到bug了!

0531c7f488fc5f08beaa481d94a8fcd5.gif

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/87635.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Django】招聘面试管理01 创建项目运行项目

文章目录 前言一、创建项目二、运行项目三、访问后台管理页面四、配置项总结 前言 跟着视频学一学&#xff0c;记录一下。 一、创建项目 照着步骤创建虚拟环境&#xff0c;安装Django等依赖包&#xff0c;创建项目&#xff1a;【Django学习】01 项目创建、结构及命令 > d…

计算机网络实验1:网络命令学习

文章目录 1. 主要教学内容2. ping命令2.1 发送ping测试报文2.2 检测TCP/IP 3. tracert命令4. ipconfig命令5. netstat命令6. arp命令7. net命令8. netsh命令 1. 主要教学内容 实验内容&#xff1a;学习ping、tracert、ipconfig等常用的网络命令。所需学时&#xff1a;1。重难点…

微服务实战项目-学成在线-项目部署

微服务实战项目-学成在线-项目部署 1 什么是DevOps 一个软件的生命周期包括&#xff1a;需求分析阶、设计、开发、测试、上线、维护、升级、废弃。 通过示例说明如下&#xff1a; 1、产品人员进行需求分析 2、设计人员进行软件架构设计和模块设计。 3、每个模块的开发人员…

Vue输入框或者选择框无效,或者有延迟

问题剖析 使用Vue这种成熟好用的框架&#xff0c;一般出现奇奇怪怪的问题都是因为操作不当导致的&#xff0c;例如没有合理调用组件、组件位置不正确、没有合理定义组件或者变量、样式使用不当等等... 解决方案 如果你也出现了输入框输入东西&#xff0c;但是没有效果…

c语言每日一练(5)

前言&#xff1a;每日一练系列&#xff0c;每一期都包含5道选择题&#xff0c;2道编程题&#xff0c;博主会尽可能详细地进行讲解&#xff0c;令初学者也能听的清晰。每日一练系列会持续更新&#xff0c;暑假时三天之内必有一更&#xff0c;到了开学之后&#xff0c;将看学业情…

零代码编程:用ChatGPT对Excel文件批量重命名

文件夹下面有几百个Excel文件&#xff0c;希望去掉开头的“【企查查】专利-”&#xff0c;去掉结尾的电话&#xff0c;然后在后面统一加上“发明专利列表”这几个字。 可以在ChatGPT中这样输入提示词&#xff1a; 你是一个Python编程专家&#xff0c;要完成一个文件标题重命名…

【CI/CD】Git Flow 分支模型

Git Flow 分支模型 1.前言 Git Flow 模型&#xff08;本文所阐述的分支模型&#xff09;构思于 2010 年&#xff0c;也就是 Git 诞生后不久&#xff0c;距今已有 10 多年。在这 10 多年中&#xff0c;Git Flow 在许多软件团队中大受欢迎。 在这 10 多年里&#xff0c;Git 本身…

窥探系列之Mybatis-plus XML分页查询

mybatisPlus分页查总数 Page类在mybatisPlus中用于分页查询&#xff0c;继承Pagination类&#xff0c;Pagination类的searchCount字段控制是否查询总记录数 顺着看哪里用到了searchCount&#xff1a; com.baomidou.mybatisplus.plugins.PaginationInterceptor 是mybatisPlus…

微服务与Nacos概述-2

微服务间消息传递 微服务是一种软件开发架构&#xff0c;它将一个大型应用程序拆分为一系列小型、独立的服务。每个服务都可以独立开发、部署和扩展&#xff0c;并通过轻量级的通信机制进行交互。 应用开发 common模块中包含服务提供者和服务消费者共享的内容 provider模块是…

Android14操作系统全新功能发布,允许用户撤销全屏权限

最新发布的Android 14操作系统带来了一系列全新功能和改进&#xff0c;其中之一是新增了选项&#xff0c;让用户能够撤销应用的全屏权限。这样一来&#xff0c;用户可以阻止一些应用在全屏模式下隐藏状态栏和导航栏&#xff0c;从而更方便地查看时间、电量和其他信息。 此外&a…

Java程序猿搬砖笔记(十六)

文章目录 狂神说-Elasticsearch 7.6入门学习笔记Windows Elasticsearch IK分词器插件启动报错Elasticsearch的ik分词器自定义字典myDict.dic的编码格式需要为UTF-8,否则无效Elasticsearch使用term查询无数据返回的原因Elasticsearch如果没给映射&#xff0c;字段默认使用standa…

【高频面试题】JVM篇

文章目录 一、JVM组成1.什么是程序计数器2.什么是Java堆&#xff1f;3.能不能介绍一下方法区(元空间&#xff09;4.你听过直接内存吗5.什么是虚拟机栈6.垃圾回收是否涉及栈内存&#xff1f;7.栈内存分配越大越好吗&#xff1f;8.方法内的局部变量是否线程安全&#xff1f;9.什么…

【VSCode】报错:出现段错误解决办法 (Segmentation fault)

VScode报错&#xff1a;Segmentation fault (core dumped)的解决办法 解决Program received signal SIGSEGV, Segmentation fault.的辛酸 Linux环境下段错误的产生原因及调试方法小结 Linux下的段错误Segmentationfault产生的原因及调试方法经典.pdf 在程序中&#xff0c;TF…

【云原生】Kubernetes节点亲和性分配 Pod

目录 1 给节点添加标签 2 根据选择节点标签指派 pod 到指定节点[nodeSelector] 3 根据节点名称指派 pod 到指定节点[nodeName] 4 根据 亲和性和反亲和性 指派 pod 到指定节点 5 节点亲和性权重 6 pod 间亲和性和反亲和性及权重 7 污点和容忍度 8 Pod 拓扑分布约束 官方…

Django快速入门

文章目录 一、安装1.创建虚拟环境&#xff08;virtualenv和virtualenvwrapper&#xff09;2. 安装django 二、改解释器三、创建一个Django项目四、项目目录项目同名文件夹/settings.py 五、测试服务器启动六、数据迁移七、创建应用八、基本视图1. 返回响应 response2. 渲染模板…

jpa查询返回自定义对象、返回指定VO、POJO

jpa查询返回自定义对象、返回指定VO、POJO jpa查询返回自定义对象、返回指定VO、POJO&#xff0c;JPA查询前会做大量处理&#xff0c;还有线程通知的操作。若并发大&#xff0c;处理性能直线下降。但是jpa就因为做了大量处理&#xff0c;对多数据库兼容极好&#xff0c;操作方…

MySQL的第一篇文章——了解数据库、简单的SQL语句

目录 学习目标 第一章 介绍数据库 1. 数据库概述 2. MySQL概述 第二章 MySQL的使用 1. MySQL服务的启动 2. 客户端连接MySQL 2.1 命令行客户端 第三章 SQL的介绍 1. 什么是SQL 2. SQL的分类 3. MySQL的语法规范和要求 第四章 DDL操作数据库 1. 创建数据库 2. 查…

word横向页面侧面页码设置及转pdf后横线变竖线的解决方案

在处理材料的时候&#xff0c;会遇到同一个文档里自某一页开始&#xff0c;页面布局是横向的&#xff0c;这时候页码要设置在侧面&#xff0c;方法是双击页脚&#xff0c;然后在word工具栏上选择“插入”——>“文本框”——>“绘制竖版文本框”&#xff0c;然后在页面左…

【MFC】10.MFC六大机制:RTTI(运行时类型识别),动态创建机制,窗口切分,子类化-笔记

运行时类信息&#xff08;RTTI&#xff09; C: ##是拼接 #是替换成字符串 // RTTI.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。 // #include <iostream> #include <afxwin.h>#ifdef _DEBUG #define new DEBUG_NEW #endifCWinApp th…

面试笔记:Android 架构岗,一次4小时4面的体验

作者&#xff1a;橘子树 此次面试一共4面4小时&#xff0c;中间只有几分钟间隔。对持续的面试状态考验还是蛮大的。 关于面试的心态&#xff0c;保持悲观的乐观主义心态比较好。面前做面试准备时保持悲观&#xff0c;尽可能的做足准备。面后积极做复盘&#xff0c;乐观的接受最…