AWS云使用100条宝贵经验分享
在今天的文章中,我整理出了大量当初曾经错过、而至今仍将我追悔莫及的Amazon Web Services(简称AWS)使用心得。在几年来的实践当中,我通过在AWS之上新手构建及部署各类应用程序而积累到了这些经验。虽然内容有些杂乱,但相信仍然能给各位带来一点启示。
从物理服务器向“云环境”转移的过程不仅仅是一项技术任务,同时也意味着我们的思维方式需要作出针对性的转变。总体而言,在物理环境下我们需要关注的只是每一台独立主机;
它们各自拥有自己的静态IP,我们能够对其分别加以监控。而一旦其中一台发生故障,我们必须尽最大可能让其快速恢复运转。大家可以以为只要将基础设施转移到AWS环境之下,就能直接享受到“云”技术带来的种种收益了。遗憾的是,事情可没那么简单(相信我,我亲身尝试过了)。在AWS环境之下,我们必须转变思维,而且这方面的任务往往不像技术难题那么容易被察觉。因此,受到了SehropeSarkuni最近一篇帖子的启发,我将自己几年来积累得出的AWS使用心得汇总于此,而且说实话、我真希望自己当初刚刚接触AWS时能有人告诉我这些宝贵经验。这些心得总结自我在AWS之上部署个人及工作应用程序时的亲身感受,其中一部分属于需要高度关注的“疑难杂症”(我自己就是直接受害者),而另一部分则是我听其他朋友说起过、并随后亲自确认有效的解决方案。不过总体而言,为了积累这些经验,我确实付出了相当惨痛的代价:)
应用程序开发
千万不要把应用程序状态保存在自己的服务器上。
之所以这么说,是因为一旦我们的服务器发生故障,那么应用程序状态很可能也随之彻底消失。有鉴于此,会话应当被存储在一套数据库(或者其它某些集中式存储体系、memcached或者redis当中)而非本地文件系统内。日志信息应当通过系统日志(或者其它类似方案)进行处理,并被发送至远程位置加以保存。上传内容应当直接指向S3(举例来说,不要将其存储在本地文件系统内,并通过其它流程随后迁移到S3)。再有,任何已经处理过或者需要长期运行的任务都应该通过异步队列(SQS非常适合处理此类任务)来实现。
编辑点评:对于S3上传内容而言,HN用户Krallin指出,我们可以彻底避免其与自有服务器的接触,并利用预签名URL保证用户的上传数据被直接发送至S3当中。
将额外信息保存在日志当中。
日志记录通常包含有时间戳以及pid等信息。大家也可能希望将实例id、服务区域、可用区以及环境(例如分步环境或者生产环境等)添加进来,而这些都能在日后的调试工作中作为参考。大家可以从instance metadata service当中获取到这些信息。我所采用的方法是将这些信息作为引导脚本的组成部分,并将其以文件形式存储在文件系统当中(例如/env/az或者/env/region等)。这样一来,我就用不着持续查询元数据服务来获取这些信息了。大家应当确保这些信息能够在实例重新启动时得到正确更新,毕竟我们都不希望在保存AMI时发现其中的数据还跟上次完全一样,这肯定属于非正常状况。如果我们需要与AWS进行交互,请在当前语言中使用对应SDK。
千万不要试图自己动手。我当初就犯过这个错误,因为我认为自己只是单纯需要向S3上传内容,但随着后续服务的持续增加、我发现自己的决定简直愚蠢至极。AWS
SDK的编写质量很高,能够自动处理验证、处理重试逻辑,而且由Amazon官方负责维护与迭代。此外,如果大家使用EC2
IAM角色(大家绝对应该这么做,这一点我们后面会进一步提到),那么该SDK将帮助我们自动获取到正确的证书。
利用工具查看应用程序日志。
大家应当采用管理员工具、系统日志查看器或者其它方案,从而帮助自己在无需在运行中实例内使用SSH的方式查看当前实时日志信息。如果大家拥有集中式日志记录系统(我强烈建议大家使用此类系统),那么当然希望能在不使用SSH的情况下完成日志内容查看任务。很明显,将SSH引入正处于运行状态的应用程序实例会引发诸多弊端。
运营心得
如果将SSH引入自己的服务器,那么自动化机制恐怕将无法生效。
在全部服务器上禁用SSH访问。
这听起来确实有点疯狂,我知道,但在大家的安全组当中、请务必确保端口22不向任何人开放。如果各位想从今天的文章中获得什么启示,那请千万牢记以下一点:如果将SSH引入自己的服务器,那么自动化机制恐怕将无法生效。从防火墙级别(而非服务器本身)禁用SSH有助于整套框架实现思维转变,因为这样一来我们就能了解到哪些区域需要进行自动化改造,同时大家也能更轻松地恢复访问来解决当前面临的问题。在意识到再也不必将SSH引入实例之后,大家肯定会像我一样感到浑身轻松。没错,这是我在实践中了解到的最惊世骇俗、但也却具实用性的心得。
编辑点评:很多人对这项心得表现出了高度关注(HackerNews网站上还出现了不少值得一读的评论意见),因此我们要在这里多说几句。我个人也会通过禁用入站SSH来蒙骗自动化机制(哦,我只是SSH一下来修复某个问题,马上就撤)。当然,如果我需要在某个实例中进行主动调试,那么我仍然可以在安全组中将其重新启用,因为有时候我们确实没有其它办法来调试特定问题。另外,具体情况还取决于我们实际使用的应用程序类型:如果大家应用程序的正常运行要求各位能力通过SSH将信息传递至服务器,那么将其禁用肯定不是什么好主意。这种阻断进站SSH的办法确实适合我,也迫使我对自己的自动化机制加以精心打理,不过必须承认、这种方式并不适合每一位用户。
服务器只是暂时性手段,没必要太过关注。我们要关注的仅仅是服务本身。
如果某台服务器出现了故障,大家完全没必要对其太过关注。这是我在利用AWS来替代物理服务器之后,亲身获得的最直接的便利成效。一般来讲,如果一台物理服务器无法正常工作,技术人员总会暂时陷入恐慌。但在AWS当中,大家就完全不必担心了,因为自动伸缩机制会很快帮我们建立起新的实例。Netflix公司在此基础之上还跨出了更具前瞻意义的步伐,他们组建了Simian
Army团队,并尝试Chaos Monkey等极为激进的测试项目——它会随机关闭生产环境下的某些实例(他们还利用Chaos
Gorilla项目随机关闭可用区。我甚至得到消息,说是Chaos
Kong项目会直接关闭基础设施大区……)。总而言之,我想表达的意思是:服务器总会发生故障,但这不该影响到我们的应用程序。
不要为服务器提供静态/弹性IP。
对于一款典型的Web应用程序,大家应当将一切部署在负载均衡机制之下,并在不同可用区之间对资源使用情况加以平衡。虽然我也遇到过一些需要使用弹性IP机制的情况,但为了尽可能提高自动伸缩效果,大家还是应该利用负载均衡机制取代在每个实例中使用独有IP的作法。
将自动化普及到各个角落。
不只是AWS,自动化机制应该作为整体运营工作中的通用性指导方针,其中包括恢复、部署以及故障转移等等。软件包与操作系统更新都应该由自动化方案所管理,具体可表现为bash脚本或者Chef/Puppet等。我们不该把主要精力放在这些琐碎的杂事上。正如前文中所提到,大家还需要确保禁用SSH访问,从而快速了解到自己的执行流程中还有哪些方面没有实现自动化改造。请记住前面提到的重要原则:如果将SSH引入自己的服务器,那么自动化机制恐怕将无法生效。
每位员工都要拥有一个IAM账户。永远不要登录主账户。
通常情况下,我们会为服务设置一个“运营账户”,而整个运营团队都将共享登录密码。但在AWS当中,大家当然不希望遵循同样的处理方式。每位员工都要拥有一个IAM账户,其中提供与其需求相符的操作权限(也就是最低权限)。IAM用户已经可以控制基础设施中的一切。截至本文撰写时,IAM用户惟一无法访问的就是计费页面中的部分内容。
如果大家希望进一步保护自己的账户,请确保为每位员工启用多因素验证机制(大家可以使用谷歌Authenticator)。我听说有些用户会将MFA令牌交付给两名员工,并将密码内容交付给另外两名员工,这样主账户在执行任意操作时、都至少需要两名员工的同意。对我来说这种作法有点小题大做,但也许您所在的企业确实需要如此高强度的控制机制。
我最后一次从CloudWatch收到操作警告大约是在一年之前……
将警告信息转化为通知内容。
如果大家已经将一切步骤正确部署到位,那么运行状态检查机制应该会自动清除故障实例并生成新的实例。在收到CloudWatch警告信息时,我们一般不需要采取任何行动,因为所有应对措施都能以自动化方式实现。如果大家得到警告称相关问题需要手动干预,那么请做好事后检查、看看能不能找到一种可以在未来通过自动化方式将其解决的途径。我最后一次从CloudWatch收到操作警告大约是在一年之前,而且对于任何一位运营人员来说、能够不再被警告消息打扰了清梦都应该算是天大的好消息。
计费机制
建立起细化计费警告机制。
大家应当始终设定至少一套计费警告机制,但其内容却可以非常简单——例如只在我们超出了当月资源用量限额时发出提醒。如果大家希望早点掌握计费指数的动向,则需要一套更为完备的细化方案。我解决这个问题的办法是以星期为单位设定预期使用量。如此一来,假如第一周的警告额度为1000美元,那么第二周应该为2000美元,第三周为3000美元,依此类推。如果第二周的警告在当月的14号或者15号之前就被触发,那么我就能推定肯定发生了什么异常状况。如果想更进一步地实现细化控制,大家可以为每项服务设置独立的警告方案,这样就能快速了解到底是哪项服务把我们的资源预算早早耗尽了。如果大家每个月都在固定使用某几项服务,而且其中一些非常稳定、另一些则起伏较大,那么这种方法能够收到良好的成效。在此类情况下,我们不妨为稳定服务设置每周独立警告,同时为起伏较大的服务设置周期更长的整体警告。当然,如果各项服务的资源使用量都很稳定,那么上述方法就有点过分谨慎了,毕竟只要看一眼CloudWatch、大家就能马上了解到底是哪项服务出了问题。
安全性保障
使用EC2角色,不要为任何应用程序提供IAM账户。
如果大家在应用程序当中内置有AWS证书,那么肯定属于“处理失当”的状况了。前面之所以强调在开发语言中使用AWS
SDK,最重要的理由之一就是我们能够轻松借此使用EC2
IAM角色。角色的设计思路在于,允许大家为特定角色指定其所必需的恰当权限,而后将该角色分配至对应EC2实例当中。而无论我们何时将AWS
SDK应用至实例当中,都不必再指定任何验证凭证。相反,该SDK将回收我们在设置当中为该角色指定权限所使用的任何临时性证书。整个流程都会以透明化方式处理,非常安全而且极具实用性。
将权限分配至组,而非用户。
管理用户往往是件令人头痛的事情,特别是在使用Active
Directory或者其它一些已经与IAM相整合的外部验证机制时,而且这类作法的实际效果也不够理想(当然也有效果不错的情况)。不过我发现更轻松的办法,即只将权限分配至组而非个别用户,这将大大降低权限的管理难度。很明显,相较于面向每位独立用户来查看为其分配的权限,以组为单位进行权限交付能够帮助我们更好地把握系统整体概况。
设置自动化安全审计机制。
在日常工作中,很重要的一点是追踪基础设施内安全设置的各项变更。实现这一目标的办法之一在于首先建立起一套安全审计角色(即JSON模板)。这一角色会对账户之内与安全相关的各类设置进行只读访问。在此之后,大家就可以利用这一类似于Python脚本的方案达到目标了,它将能够对账户中的所有条目进行审查并生成一整套与配置相关的规范比对结果。我们可以设置一个cronjob来运行该脚本,并将输出结果与上一次的结果相比较。其中的差异之处就代表着我们安全配置方案当中所出现的变化。这种方式非常实用,而且能够通过电子邮件将变更信息通知给大家。
使用CloudTrail来记录审计日志。
CloudTrail通过通过API或者S3存储桶内的Web控制台记录所有执行过的操作活动。利用版本控制机制设置存储桶可以确保任何人都无法修改这些日志内容,而我们自己则可以随后对账户内的全部变更进行彻底审计。我们当然不希望遇到需要审计日志内容的状况,但正所谓有备无患,准备好这么一套方案还是很有必要的。S3
在存储桶内的SSL名称中使用“-”而非“.”。
如果大家希望在SSL之上使用自己的存储桶,那么使用“.”作为名称的组成部分将导致证书不匹配错误。一旦存储桶创建完成,我们将无法再对其名称进行更改,所以大家只能将一切复制到新的存储桶当中。
我发现文件系统就像大型政府部门一样“可靠”。
避免载入文件系统(例如FUSE)。
我发现在被用于关键性应用程序时,这些文件系统就像大型政府部门那样“可靠”。总之,使用SDK作为替代方案更加明智。
我们用不着在S3之前使用CloudFront(但这样确实能够起到助益)。
编辑评论:根据Hacker News网站用户的最佳反馈内容,我们对这条心得作出了部分修改。如果大家对于可扩展能力比较看重,那么最好是将用户直接引导至S3
URL而非使用CloudFront。S3能够实现任意水平的容量扩展(虽然一部分用户报告称其无法实现实时规模扩展),因此这也是充分发挥这一优势的最佳思路。除此之外,更新内容在S3中的起效速度也够快,虽然我们仍然需要在使用CDN查看变更时等等TTL的响应(不过我相信大家现在已经能够在CloudFront中获得0延迟TTL,所以这一点也许存在争议)。
如果大家需要良好的速度表现,或者需要处理对传输带宽要求极高的数据(例如超过10TB),那么大家可能更希望使用像CloudFront这样的CDN方案与S3相配合。CloudFront能够极大提升全球各地用户的访问速度,因为它会将相关内容复制到各边缘位置。根据实际用例的不同,大家可以通过较低数量的请求实现高传输带宽(10TB以上),并借此降低使用成本——这是因为相较于S3传输带宽,当传输总量高于10TB时CloudFront的传输成本每GB比其低0.010美元。不过CloudFront的单次请求成本较直接访问S3中的文件却要略高一点。根据具体使用模式,传输带宽的成本节约额度也许会超过单一请求所带来的额外成本。因为在直接访问S3存储内容时,我们只需以较低频度从S3获取数据(远较正常使用情况更低),所以成本也就更加低廉。AWS官方提供的CloudFront说明文档解释了如何将其与S3配合使用,感兴趣的朋友可以点击此处查看细节信息。
在密钥开头使用随机字符串。
这初看起来似乎有点难以理解,不过S3所采取的一大实现细节在于,Amazon会利用对象密钥来检测某个文件到底保存在S3中的哪个物理位置。因此如果多个文件使用同样的前缀,那么它们最终很可能会被保存在同一块磁盘当中。通过设置随机性密钥前缀,我们能够更好地确保自己的对象文件被分散在各个位置,从而进一步提升安全性。
EC2/VPC
别忘了使用标签!
几乎每项服务都提供标签功能,别忘了善加利用!它们非常适用于进行内容归类,从而大大简化后续出现的搜索与分组工作。大家也可以利用这些标签在自己的实例中触发特定行为,例如env-debug标签可以确保应用程序在部署之后进入调试模式。
我遇到过这类问题,感觉很不好,大家千万不要重蹈覆辙!
在非自动伸缩实例中使用中止保护。以后你会感激我的建议的,绝对。
如果大家拥有某些不具备自动伸缩机制的一次性实例,则应当尽可能同时使用中止保护,从而避免某些人无意中将这些实例给删除掉。我就遇到过这类问题,感觉很不好,大家千万不要重蹈覆辙!
使用VPC。
当初我刚刚开始接触AWS时,VPC要么还不存在、要么就是被我给忽视了。虽然刚开始上手感觉很糟糕,但一旦熟悉了它的特性并能够顺畅使用,它绝对能带来令人喜出望外的便捷效果。它能够在EC2之上提供各类附加功能,事实证明设置VPC花掉的时间完全物有所值——甚至物超所值。首先,大家可以利用ACL从网络层面上控制流量,此外我们还可以修改实例大小及安全组等等,同时无需中止当前实例。大家能够指定出口防火墙规则(在正常情况下,我们无法控制来自EC2的离站流量)。不过最大的助益在于,它能为我们的实例提供独立的私有子网,这就彻底将其他人排除在外,从而构成了额外的保护层。不要像我当初那样傻等着了,立刻使用VPC并让一切变得更加轻松。
如果大家对于VPC抱有兴趣,我强烈建议各位观看《百万数据包的一日之期》这段资料。
利用保留实例功能来节约大量成本。
保留实例的本质就是将一部分资金节约下来,从而使其保持较低的资源耗费速率。事实证明,保留实例相较于按需实例能够帮助我们省下大量经费。因此,如果大家意识到只需要继续保持某个实例运行一到三年,那么保留实例功能将是各位的最佳选择。保留实例属于AWS当中的一类纯逻辑概念,大家用不着将某个实例指定为需保留对象。我们要做的就是设定好保留实例的类型与大小,接下来任何符合这一标准的实例都将处于低速运转加低成本支出的运行模式之下。
锁定安全组。
如果有可能,千万不要使用0.0.0.0/0,确保使用特定规则来限制用户对实例的访问。举例来说,如果大家的实例处于ELB之后,各位应该将安全组设定为只允许接受来自ELB的流量,而非来自0.0.0.0/0的流量。大家可以通过将“amazon-elb/amazon-elb-sg”写入CIDR的方式实现这一目标(它会自动帮大家完成其余的工作)。如果大家需要为一部分来自其它实例的访问请求向特定端口放行,也不要使用它们的对应IP,而最好利用其安全组标识符来替代(只需要输入‘sg-’,其它的部分将自动完成)。
不要保留无关的弹性IP。
我们所创建的任意弹性IP都会成为计费项目,无论其与实例是否相关,因此请确保在使用过后将其及时清理掉。
ELB
在负载均衡器上中止SSL。
大家需要将自己的SSL证书信息添加到ELB当中,但在服务器上中止SSL能够消除由此带来的常规资源消耗,从而提高运行速度。除此之外,如果大家需要上传自己的SSL证书,则可以经由HTTPS流量实现、而负载均衡器会为我们的请求添加额外的标头(例如x-forwarded-for等),这有助于我们了解最终用户究竟是何许人也。相比之下,如果我们经由TCP流量实现,那么这些标头将不会被添加进来、相关信息自然也就无从获取。
如果打算执行高强度流量,请对ELB进行预热。
对于ELB来说,容量扩展是需要一段时间周期才能完成的。如果大家意识到自己将会迎来流量峰值(例如销售门票或者召开大型活动等),则需要提前对ELB进行预热。我们可以主动增加流量规模,这样ELB就会提前进行容量添加,从而避免实际流量来临时发生卡顿。不过AWS建议大家最好是与服务人员取得联系,而非自行对负载均衡器进行预热。或者,大家也可以在EC2实例当中安装自己熟悉的负载均衡软件并加以使用(例如HAProxy等)。
ElastiCache
使用配置端点而非独立节点端点。
通常情况下,大家需要让应用程序识别出各可用Memcached节点的存在。但如果大家希望以动态方式实现容量扩展,那么这种作法可能会带来新的问题,因为我们将需要采取多种方式才能保证应用程序识别到容量的变化。比较简便的解决方案在于使用配置端点,这意味着使用Memcached库的AWS版本来对新节点自动发现机制加以抽象并剥离。AWS使用指南中提供了更多与缓存节点自动发现议题相关的解答,感兴趣的朋友可以点击此处查看细节信息。
RDS
为故障转移设置事件订阅机制。
如果大家正在使用多可用区方案,那么这可能是一种被各位所忽略、但在相关需求出现时却又悔之晚矣的重要解决方案。
CloudWatch
使用CLI工具。
要利用Web控制台创建警报机制,我们可能需要面临大量繁的工作,特别是在设置众多内容类似的警报信息时,因为我们无法直接“克隆”现有警报并仅对其中一小部分作出修改。在这种情况下,利用CLI工具实现脚本化效果能够帮助各位节约大量宝贵时间。
使用免费监测指标。
CloudWatch监控工具以免费方式提供各类监测指标(包括传输带宽、CPU使用率等等),而且用户能够获取到最多两周的历史数据。有鉴于此,我们根本用不着花钱购置自己的工具来实现系统监控。但如果大家需要超过两周的历史数据记录,那没办法,只能使用第三方或者自行开发的监控方案了。使用自定义监测指标。
如果大家希望监控免费指标之外的其它项目,则可以将自己的定制指标信息发送至CloudWatch,并使用相关警报与图形功能。这不仅可以用于追踪诸如磁盘空间使用量等状况,同时也可以根据实际需求自行设定应用程序监测方向。感兴趣的朋友可以点击此处查看AWS提供的发布自定义监测指标页面。
使用详细监测机制。
这项服务每月每实例的使用成本约为3.5美元,但其提供的丰富额外信息绝对能够值回票价。1分钟的细化监测甚至好过5分钟的粗放查看。大家可以借此了解到某次时长5分钟的崩溃到底是因何而起,同时以非常明确的方式将其以1分钟图表的方式显示出来。也许这项功能并不适用于每闰用户,但就我个人而言、它确保帮我弄清了不少原本神秘的疑难杂症。
自动伸缩
利用INSUFFICIENT_DATA以及ALARM进行规模缩减。
对于规模缩减操作而言,大家需要确保能够在不具备指标数据甚至触发机制本身出现问题时仍能正常实现规模缩减。举例来说,如果大家的某款应用程序平时始终处于低流量状态,但突然迎来了流量峰值,大家肯定希望其能够在峰值结束且流量中止后将规模恢复至原有水平。如果没有流量作为参考,大家可以使用INSUFFICIENT_DATA来替代ALARM作为低流量阈值情况下的规模缩减执行手段。
使用ELB运行状态检查取代EC2运行状态检查。
在创建扩展组时系统会提供这样一个配置选项,大家能够指定是否使用标准的EC2检查机制(当该实例接入网络时),或者使用自己的ELB运行状态检查机制。ELB运行状态检查机制能够提供更出色的灵活性。如果大家的运行状态检查机制发生故障,那么该实例将被移出负载均衡池,在这种情况下大家当然希望能够通过自动伸缩中止该实例并创建新的实例加以替代。如果大家没有利用ELB检查设置出扩展组,那么上述功能将无法实现。AWS在说明文档当中就添加运行状态检查机制作出了详尽说明,感兴趣的朋友可以点击此处进行查看。
只使用ELB预先配置的可用区。
如果大家将扩展组添加到多个可用区内,请确保自己的ELB配置能够正确使用全部可用区,否则在容量发生向上扩展时,负载均衡器将无法正确识别出这些可用区。
不要在同一个组内使用多个扩展触发器。
如果大家在同一个自动伸缩组内选择了多种用于触发规模调整的CloudWatch警报机制,那么它们可能无法如各位的预期那样正常起效。举例来说,假设大家添加的触发器会在CPU使用率过高或者入站网络流量强度过大时进行向上扩展,并在情况相反时对规模进行缩减。但在使用过程中,我们往往会发现CPU使用率持续增加,但入站网络流量却保持不变。这时高CPU负载触发器会进行容量扩展操作,但低入站流量警报却会立即触发规模缩减操作。根据各自执行周期的不同,二者之间的作用很可能会被抵消,导致问题无法得到切实解决。因此,如果大家希望使用多种触发器方案,请务必选择多自动伸缩组的方式。
IAM
使用IAM角色。
不要面向应用程序创建用户,而尽可能使用IAM角色。它们能够简化所有相关工作,并保证业务环境的安全水平。创建应用程序用户只会带来故障点(如果有人不小心删除了API密钥),而且令管理工作更加难以完成。
用户可以拥有多个API密钥。
如果某些员工在同时处理多个项目,又或者大家希望能够利用一次性密钥对某些内容进行测试,那么这种方式将非常实用。我们完全不必担心真正的密钥被泄露出去。
IAM用户可以使用多因素身份验证,请善加利用!
启用多因素验证机制能够为我们的IAM用户提供额外的安全层。我们的主账户肯定应该引入这项机制,面只要有可能、普通IAM用户亦应当享受到由此带来的保障。
Route53
使用ALIAS(即别名)记录。
ALIAS记录会将我们的记录组直接链接到特定AWS资源处(即可以将某个域映射至某个S3存储桶),但其关键在于我们用不着为了ALIAS查找而支付费用。因此与CNAME这类付费机制不同,ALIAS记录不会增加任何额外成本。另外,与CNAME不同,大家可以在自己的区索引当中使用ALIAS。感兴趣的朋友可以点击此处查看AWS官方提供的ALIAS资源记录集创建说明页面。弹性MapReduce
在S3中为Hive结果指定一个目录。
如果大家打算利用Hive将结果输出至S3,则必须在存储桶内为其指定一个目录——而非存储桶根目录。否则我们很可能遭遇NullPointerException,而且完全找不到引发这一问题的理由。