有做过3D可视化大屏的朋友嘛,推荐方案有哪些? |
作者 Zach Holman
本文为 Coding 用户协作翻译,转载请注明来源。如果你对本文的翻译有建议,欢迎提交 Pull Request 。
无论你何时对自己的代码库做出改动,总会伴随着要破坏一些东西的风险。
没有人喜欢宕机, 没有人喜欢暴躁的用户, 也没有人喜欢生气的经理,所以部署新代码到生产环境变成颇具压力的一个环节。
你完全没必要对它有压力,我将在这里重复一遍又一遍这句话:
你的部署应该尽可能单调、直接、毫无压力。
部署新功能到生产环境中应该像在 Hacker News 开始一场关于 用 spaces 还是 tabs 的口水战一样简单。它应该足够简单到让新员工理解,它应该为防止错误而生,它应该在第一个最终用户看到新代码前被很好地测试过。
这是一篇高层次谈论部署的文章,包含了:协作,安全和速度等,在底层方面也讲了很多,但这些都是很难跨语言进行概括,并且说实话,比起在高层次技术方面有很多更密切的问题要去解决,我更喜欢谈论团队如何协同工作,而部署是与其他人协作最关键的一部分。我认为你值得花时间并不时地来评估你团队的状况。
有很多来自我在 GitHub 任职 5 年的经验,和去年我与大大小小科技公司提供建议和咨询的经验,对在提高他们的部署工作流程的重点上(已经从“非常可敬”到归纳于“我觉得这服务器已经着火了”)。我特别推荐一个初创公司, Dockbit ,其产品是旨在正视部署中的合作,等等。这篇文章来源于许多关于我和它们团队的谈话,我认为写下来许多部署难题中的不同部分会大有益处的。
我很感激一些来自不同公司的朋友给予这篇文章的校队和帮忙,并提供各自在部署上不同的观点: Corey Donohoe (Heroku) , Jesse Toth (GitHub) , Aman Gupta (GitHub) , 和 Paul Betts (Slack) 。我不断的发现不同的公司可能采取很有趣的不同路径,但一般都集中在合作,风险和谨慎这种基础方面,我觉得有东西在这里具有普遍性。
不管怎么说,对于这个漫长的导语我感到很抱歉,但无论如何这篇文章将是很长的,请尽力读完吧, lol.
目标
难道部署不是一个已经解决的问题吗?
准备
开始为部署做准备:测试,feature flags 和你的开发协作方式
分支
为你的代码设置建立分支是部署最基本的部分。在部署新代码时你能把其中导致任何可能意外后果的部分分离出来。开始思考部署分支,自动部署 master 分支,蓝/绿部署。
控制
部署的核心。你如何才能控制被发布的代码。处理在部署和合并中不同的权限结构,为你的部署建立一套审计跟踪,通过部署锁定和部署队列让一切有序。
监视
Cool,你的代码已经在生产环境了。现在你可以关心的你的部署在不同方面的监视指标,并且最终做出是否要为你的改动回滚代码的决定。
结论
"我们学到了什么, Palmer ?"
"先生我不知道。"
"我 TM 也不知道。我猜我们学到的就是,不要再这么做了。"
"是的,先生。"
How to Deploy Software was originally published on March 1, 2016.
如果你说的是拉取代码并将它们传送到不同的服务器上,那么事情已经解决的很漂亮并且这些事情相当无聊。你已经获得了 Ruby 中的 Capistrano(一种远程服务器自动化工具), Python 中的 Fabric(Python 的一个类库以及命令行工具),Node 中的 Shipit (Javascript 写的一个通用自动化和发布工具),以及所有的亚马逊云服务,甚至是 FTP 也貌似会存在好几个世纪,因此工具现在真的不是问题。
如果我们此时有了相当不错的工具,为什么部署会出错呢?为什么人们总是发布 bug 呢?为什么总是存在宕机的情况?我们可都是写完美代码的完美程序员啊,这真是见鬼了。
很明显,事情总是始料未及地发生,所以我认为部署应该是中小型公司应关注的有趣领域,其他领域没有如此好的投入产出比。你可以做好尽早处理和应对问题的工作流程吗?你可以使用不同的工具来更简单地进行协助部署吗?
这不是工具问题,这是处理的问题。
我对很多很多创业公司讲,过去的几年,还没有一个从组织的角度看上去“好的”部署工作流。
你无需负责部署的专人,无需特定一个部署日期,无需在每次部署时动用所有人手。你只需要采取一些聪明的办法。
在跑之前你必须走起来。我认为初创公司有一个很时髦的方面就是它们都用着最酷并且最新的部署工具,但是当你切进去观察他们的处理时,会发现他们花了 80% 的时间在处理基础上。如果他们从开始时就是流水化的,每件事都会恰当地处理并且更为迅速。
测试是前期一个最简单的地方。在一些浅显的依赖处理中这不是一个必需的步骤,不过却对其着有着巨大的影响。
这里很多技巧取决于你的语言,平台或者框架,不过普遍的建议是测试你的代码 ,并提高测试速度。
我最喜欢引用 Ryan Tomayko 在 GitHub 的内部测试文档里写的:
我们能让好的测试变快却不能让快的测试变好。
所以以一个好的基础开始:做个好的测试,别吝啬这点,因为它影响一切的方向。
一旦你开始有一个值得依靠的质量测试套件,尽管在开始时得花钱。如果你有任何形式的收入或者你们幕后团队的资助,几乎头号你应该花钱的地方就是你应该运行测试的地方。如果你使用 Travis CI 或者 CircleCI ,如果你能运行并行编译构建那么就重复今天所做的。如果你需要在特定的硬件上运行,那就买巨大的服务器。
我见过一些公司通过迁移到更加快的测试套件上使其获得了最重要的生产力优势,而你也能赚到,因为它影响迭代反馈周期,缩短依赖时间,增加开发者的幸福感并且使其成为一种惯性。在问题解决方案上花钱吧:因为服务器很便宜,但程序员却不。
我在 Twitter上 做过一个非正式的投票 问我的 followers 他们的测试套件究竟跑得有多快。诚然,很难解释那些微小服务,语言差异上居然有惊人数目的人从来没有做过测试。不过在全栈和更快的单元测试者对决中,它表现的还是非常明显。大多数人在 push 后至少要等待5分钟才能看到构建状态。
快究竟指多快呢? 当我在 GitHub 时,测试一般在 2-3 分钟内跑完。我们并没有很多集成测试,所以允许以相对较快的速度测试,但是实际上你测试的越快,你就能越快的得到开发人员的循环反馈。
有许多的项目旨在帮助你并行化构建项目。在 Ruby 里有 parallel_tests 和 test-queue 。比如你的测试没有相互完全独立,以不同的方式编写测试是一个很好的方法,如果情况相反,你也应该好好处理它。
这一切的另一个方面是开始看你的代码并且把它转化来支持多渠道部署代码路径。
重复一遍,我们的目标是你的部署应该尽可能单调,直接,无压力。部署任何新代码的自然压力是代码运行出你无法预见的问题,你最终影响到用户的行为(即他们经历的停机时间和错误)。即使你有宇宙中最好的程序员,糟糕的代码将最终被部署。不管这些坏代码是影响 100% 的用户或只是一个对你们非常重要的用户。
用一个简单的方法来处理,那就是 Feature Flag 。 Feature Flag 已经出现很久了,至少,技术上讲,是自从 if 语句发明开始的,而我记得第一次真正听到有公司的使用 Feature Flag 是 Flickr 在 2009 年的一篇文章。Flipping Out
这允许我们开启我们正在开发的功能而不影响其他开发人员正在开发的功能。它也可以让我们打开或关闭测试单独的功能。
只有你可以看到,或只有你的团队可以查看 flags,或所有员工都能看是两件不同的事:你可以在现实世界使用真实的数据测试代码,并确保一切工作正常;你还可以得到真正的关于该功能正式发布时得到关于性能和风险的 benchmarks。
所有这一切的对你准备部署新的功能是大大有利的,你需要做的就是修改一行代码为 true ,然后每个人都看到了新的代码。这使得通常吓人的新版本部署变为为单调,直接,且无压力。
作为一个额外的步骤,Feature Flag 提供了一个很好的方式来证明你的即将代码部署不会对性能和可靠性产生不利影响。最近几年一些新的工具能帮助你做到这个。
我在几年前的演讲文章 《Move Fast and Break Nothing》 中谈过,它的主要内容是在生产环境运行 Feature Flag 的两个代码路径中,并且只返回旧代码的结果,观察你引进的新代码与你即将更换的新代码的表现。一旦你有了这些数据,你可以确保你不会破坏任何事情。部署将变得单调,直接,无压力。
GitHub 上一个叫做 Scientist 的开源 Ruby 库能抽象的帮助你很多。这个库已经这点上被移植到最受欢迎的语言上,所以如果你感兴趣,它可能很值得你花时间去看一看。
另一种方法是灰度发布。一旦你对要部署的代码是准确无误充满信心,首先你仍然谨慎地仅公开到一小部分用户来进行双重检查和三重检查,直到没有产生什么破坏。破坏 5% 用户的体验比破坏 100% 用户的体验要好得多。
有大量旨在帮助你的库,从 Ruby 中的 Rollout , Java 中 Togglz ,到 JavaScript 中的 fflip ,和许多其他的。还有很多初创公司在为这个问题提供解决方案,比如 LaunchDarkly 。
另外值得一提的是,这并不仅仅是 Web 的事情。本地应用也可以从中获益良多。粗略看下 GroundControl 这个 iOS 中处理表现的库就懂了。
在代码构建上自我感觉良好?赞,我们现在来跳出这点,开始讨论下部署。
很多围绕部署的组织问题被部署者与其他人缺少沟通阻碍着。你需要每个人了解了你即将上线的代码的方方面面,在做这个同时避免踩到她人的脚趾。
这里有几个可以帮到你的有趣的方式,它们都取决于部署的最简单元:分支。
分支,我是指 Git,Mercurial 等版本控制系统的分支。先切出一个分支,在该分支上编程,然后推送代码到你喜爱的代码托管平台(如 GitLab,Bitbucket,Coding 等)
你也应该使用 Pull Requests,Merge Request,或其他 code review 工具去评审写好的代码。部署环节必须要协作,代码评审是非常重要的一部分。我们稍后会进一步说明这块。
代码评审这个话题太大,太复杂,且依你的团队和风险状况不同。我认为这里有几个重要的问题需要所有的团队去思考:
你的分支你负责。 我见过的成功型公司都有这个理念,即部署代码失败的最终负责人是写这个代码的人。他们不把部署失败的责任归结于部署上线的人然后就去起床吃饭,当然这些人应该参与代码评审,但最重要的是你要对你自己的代码负责。如果它(编译)失败了,你自己去解决.......而不是你可怜的 ops 团队。所以不要搞砸它。
尽早开始并经常性进行评审。 你不需要完成一个分支后再去请求评审。如果你可以发起一个关于预期代码的评审请求,比如,花了 20 分钟在这上面然后被通知说 “不,我们不用做这个了” 远远比之后花两周时间写这个代码更好。
总是需要某人来评审代码。 你可以依靠团队来做这个,但有一双专门负责评审的眼睛是非常有帮助的。对于结构化程度高的公司,你可能要明确指派人来负责代码评审,并要求他们在代码完前就开始 review。对于结构化程度较低的公司,你可以指派不用的团队看看谁最可以帮到你。在光谱的两端,你设定的期望是有人在猛冲前给你搭把手,或独自部署代码。
这里有个关于代码评审的老段子。无论何时你开启了一个关于 6 行代码的评审请求,你总会得到很多同事关于这 6 行代码的指指点点。但当你 push 了一个花了几周时间的代码分支,你常常会得到一个很快回复的:赞,我看行!
基本上,程序员常常都是一群很讨厌的懒虫。
但你可以利用其作为你的优势,通过:使用尽快,较小的分支和 Pull Request。让代码小到可以很容易让人随时切入并进行评审。如果你写了大型的分支,这需要别人花很长时间去 review,同时拖慢了整个开发的进度。
如何让代码更小?这时之前说的 feature flags 就派上用场了。当 2014 年我团队的三个人重建 GitHub 的 Issues 时,我们向 Production 推送了大约上百数量的使用 Feature Flag 的小型 Pull Requests。我们部署了很多小单元(在其“完美”之前)。这让代码评审更简单,同时让部署更快,更早看到线上的产品状况。
你需要快速并频繁地部署。十人规模的团队可以每天无忧地部署至少 7-15 个分支。重复一遍,diff 越小,部署就越单调,越直接,越无压力。
当你准备好部署你的新代码,你应该总是在合并代码前部署你的分支。注意"总是"。
查看整个代码库作为事实的记录。你的 master 分支(或你指定的任何的默认主分支)应该作为你的生产环境的绝对镜像。换句话说,你需要确保你的主分支是“没问题的”,就是该分支没有任何已知的问题。
分支是个大问题。如果你合并你的分支到 master 然后就部署 master 分支,你无法简单地判断代码是否正常,“没问题”分支是无需做任何恶心的代码回滚操作的分支。
这不一定是火箭科学才需要的事情,但如果你的部署搞坏了网站,最后你就需要反思下了。你需要一个简单的方法。
这就是为什么你的部署工具应该支持你部署分支是很重要的。一旦你确认你的性能没有波动,没有稳定性问题,功能可用性在预期内,你就可以 merge 它了。这么做的原因不是为了确保事情可行,而是防止事情不可行。当其出错时,你的解决方案应该是单调,直接且无压力的:重新部署 master 分支即可。就是这样。你回到了“没问题”的状态。
重要的是要对你的“已知状态”有清晰的定义,最简单的方法就是定一个简单的不出错的规则:
除非你正在测试一个分支,所有部署到生产环境的都始终由 master 分支体现。
我见过的最简单的方法是保持在 master 分支设置自动部署。这是超简单的规则组,它鼓励大家向分支做出最无风险的提交。
这里有很多工具平台可用,如 Heroku 可以自动部署最新分支。CI 工具如 Travis CI (译者注:国内有 flow.ci )也可以帮你自动部署。或私有部署的 Heaven 和 hubot-deploy-tools (我们稍后会提到)也可以帮到你。
自动部署在你 merge 你工作的分支到 master 分支时也有帮助。你的工具应该可以选取一个新的修订并重新再次部署网站。尽管软件内容没变(你在有效的部署同一套代码),SHA-1 值变化了,这使生产环境的已知状态变得更加明确(再次重申下,master 分支是已知状态)。
Martin Fowler 曾经在 2010 年的文章推崇过 蓝绿部署(很值得一读)。在其中,Fowler 谈论到使用两种理想的生产环境的理念,即他说的“蓝”和“绿”。蓝意味着在线的生产环境,绿代表空闲的生产环境。你可以部署到绿色集群,确认一切正常运行后,通过无缝切换(如负载均衡)切换到蓝色集群。如此,生产环境收到了没有风险的代码。
自动部署的一个挑战就是切换,将软件从测试的最后环节检出到生产环境。
这是一个非常强大的想法,而日益普及的虚拟化,容器技术和(可以很容易地扔掉,被遗忘的)自有环境使它变得更加强大。除了一个简单的蓝色/绿色的部署,你也可以让生成产环境流动起来,因为一切都是虚拟的。
这里有很多解决方法,从灾备恢复到在用户看到它前附加时间测试关键功能,但我最喜欢的是附加功能使用代码。
使用新代码是在产品开发周期非常重要的。当然,很多问题应提前在代码审查或通过自动测试找到了,但如果你正在尝试做真正的产品,有时很难预测直到你已经长时间试过了真实的数据。这就是为什么蓝绿部署比有一个简单的临时服务器更重要,其数据可能过时了或完全捏造。
更重要的是,如果你需要你的代码部署到特定的环境中,你就可以在早期开始就引入不同利益相关者。不是每个人都有技术能力把你的代码拉取到他们的计算机上,并在本地安装你的代码 - 而且这也是不应该的!比如,如果你能给你的会记部门展示你的新的上线情况的屏幕,在整个公司看到它之前,他们可以给你一些关于它的现实反馈,这可以帮你在早期找到很多的错误和问题。
不管你用不用 Heroku,看一下他们生态系统中的“Review Apps”理念:apps 直接从一个 Pull Request 进行部署,直接上线而不是截图或大幅的关于“这就是它上线后的样子”的描述。让更多人尽早参与进来而不是你之后试图用烂的产品说服他们。
你看,当我在谈一个创业公司的组织方式时,我是完全嬉皮自由雅痞的:我笃信开发者的自主性,用一种自下而上的方法去开发,注重人而不是管理。我认为这会让大家更快乐且让产品更好。但在部署时,嗯哼,这是非常重要的,属于 all-or-nothing 的需要做好的事情,所以我觉得这里加入管控是合理的。
幸运的是,部署工具就是加入限制,从而把大家从压力中解放出来,所以如果你做对了将大大获益,而不是常人说的这将是阻碍。换句话说,你的流程应该促使事情搞定,而不是阻碍它。
我惊讶一些竟然不可以很快拿到审计日志的创业公司。尽管可能会有一些聊天记录可查,但这不应该是你需要时拿不出来的东西。
审计跟踪的好处就是你预见到的:你可以找出是何时何地何人部署的。当你之后遇到问题时,你可以回滚到某一节点,这将节省不少时间。
很多服务都提供了这类的部署日志。如 Amazon CodeDeploy 和 Dockbit,提供了广义上的部署工具并提供了很好的追踪工具。GitHub 杰出的部署 API (译者注:Coding.net 也提供了 部署 API)也是很好的从 Pull Request 部署集成到你的外部系统的好办法。
如果你在专家模式,在你的部署和部署时间需要插入很多数据库和服务,如 InfluxDB,Grafana,Librato 或 Graphite。可以在给定指标和部署层指标中对比是非常强大的:起初看到一个意外的指标增加或许让你好奇,但当那是一次部署在发生时,你就不会感到意外了。
如果你走到了在一个代码库中有很多人的这一步,你自然会遇到有很多人在某时都准备部署各自代码的状况。当然同时部署多个分支到生产环境是可行的,但我建议,当你走到这一步时,你需要些处理这种情况的工具。部署锁定就是我们要了解的第一个东西。
部署锁定基本是你已经预料到的东西:锁定生产环境以便大家可以依次进行部署。这里有很多方法可行,但最重要的是你要让这 可见。
实现这一目标的最简单办法就是通过聊天。一个常见的方式可以是设置部署命令锁定生产环境,比如:
/deploy <app>/<branch> to <environment>
i.e.,
/deploy api/new-permissions to production
这使大家都明白你在部署什么。我见过一些使用 Slack 的公司在 Slack 的部署聊天室里说:我在部署...!我觉得这是没有必要的,这只会分散你的同事。这里只要把信息扔进聊天室就够了。如果你之后忘了做也可以添加一条额外命令让系统返回目前生产环境的状态。
这里有很多简单方法可以把这套工作流插入你的聊天室。Dockbit 有一个 Slack 集成。也有一个开源解决方案叫作 SlashDeploy 可以集成 GitHub 和 Slack。(译者注:国内有 bearychat.com 提供了类似服务)
我还见过一些特制的关于这一步的网页版工具。Slack 有个自定义的内部 App 提供了可视化的部署。Pinterest 有一个开源的基于网页的部署系统。你可以将锁定的理念延伸到其他方面,这取决于如何使你的团队最高效工作。
一旦部署分支被 merge 到 master 分支,生产环境应该自动解锁以便下一个人进行操作。
这里也有一定的锁定礼仪。你当然不希望大家等待一个粗心的程序员忘记了解锁生产环境。自动解锁工具就派上用场了,比如,你也可以设置定时提醒部署人员其生产环境是否被锁定超过了 10 分钟。宗旨就是:拉完屎赶紧走。
一旦你有很多部署要安排且你有很多人员准备部署,你显然会有一些部署争论。对于这一点,从你内心深处的英国绅士特色中选择,形成一个部署队列。
一个部署队列有几个部分:1)如果需要等待,把你的名字添加到末尾,2)允许有人插队 (有些非常重要的部署需要立即执行,你需要允许这样做)
部署队列的唯一问题就是有太多人排队部署了。GitHub 从过去一年至今都面临这个问题;在周一人人都想部署他们的变更,部署列表看起来可以持续一个小时或更久。我不是特别提倡微服务,但我认为部署队列有一个好处就是你可以从雄伟的巨石中劈东西了。
有很多方法可以限制使用部署权限的人。
两步验证是一个选项。最好你的雇员聊天帐号不会被公开,最好他们在他们电脑上有其他安全措施(全盘加密,强密码等等),但是如果你要安心的话,最好要求他们开启两步验证。
或许你已经有提供两步验证服务的聊天服务商,如 Campfire 和 Slack。(译者注:Coding.net 也提供了 两步验证 ) 如果你需要在部署前进行两步验证,你可以在其流程中增加两步验证。
另一种可行的处理方法是,我称权限外的调查员为“骑猎枪”。我见过很多拥有正式或非正式的流程或工具去保证至少有一位高级开发人员参与每一步部署。这里没有理由不这么做,比如,要求你的部署人员和高级开发人员(骑猎枪)去确认代码可以部署。
一旦你部署完你的代码,便是时候开始验你是否真的做了你所想做的。
无论是更新前端、后端或其他任何代码,每次部署都必须符合同一个策略方针。你必须查看网站是否还正常运行着,是否性能突然变得更糟糕,是否产生更多误码率,亦或者有更多反馈的问题等等。所以说精简那个策略方针将对你非常有利。
对于上述的不同方面,如果多个信息来源,试着比如在最终确认部署的时候给每个仪表盘中加一个链接。这样每次都能提醒大家观察并验证这些变更是否对度量指数产生了负面影响。
理想状态下,应从一个来源里获取信息。这样更容易指引,比如一个新员工在第一次部署的时候该观察重要的度量指数。比如 Printerest 的 Teletraan 在一个界面里就包含了所有的信息。
有很多可以收集的度量指数将有助于你判断刚刚部署得是否成功。
当然最显著的是误码率。如果它突然急速上升,意味着你可能得重新部署 master 分支并且修复这些问题。这些过程可以自动化实现,甚至可以设定一个阈值,如误码率超了就自动重新部署。如果你确定 master 是一个对你来说熟知并且可以回滚的分支,那么自动回滚将变得更容易,一旦你部署后就触发大量异常则自动回滚部署。
部署本身也是个很有意思的度量,值得放在手上。一个很好的例子就是纵览过去一年的部署情况,可以帮助你了解部署的节奏是在放大,或者让你了解它慢下来的原因。你可以进一步收集是谁在部署,谁导致了错码,并开发一种能够检测团队开发者是否可靠的方法。
最后一步需要做的家务活就是清理。
Feature Toggles 是最糟糕的技术债之一 对这进行了讨论,虽然这个标题有点激进。如果你正在构建一些有 feature flags 以及人员发展的项目,你将面临长期使你代码库的变得更复杂化的风险:
用管道与脚手架逻辑支持代码分支是一种令人讨厌的技术债务,因为自此以后每个功能开关都要引入。Feature flags 使得代码更脆弱,很难测试、理解、维护、支持,也更不安全。
你不需要部署完就立刻清理;如果你有一个新功能或者 bug 修复的需求,你应该花时间在监测系统指标,而不是立刻删除代码,尽管部署后不久你还是得这样做。如果你有一个重大版本发布, 你可以在一天或一星期后回顾并删除那些已经不用的代码。我喜欢做的一件事就是准备两个 pull request:一个是切换 Feature flags (比如,开放该功能给所有人),另一个是清除所有的你引入的冗余代码。当我确保我没破坏任何事情并且看上去不错的话,我就可以合并第二个 pull request 而不需再更多的考虑或开发。
你还需要给自己庆祝一番:因为这个终极信号意味着你们已经成功地完成了这个项目。所有人都会喜欢看到 diff 几乎都变红的状态,删除代码的确是件很开心的事情。
当你完成任务后,你同样可以删除分支,这肯定是不会错的。但如果你用的是 GitHub 的 pull request,你通常可以保留删除的分支,这样相当于从分支列表中删除了该分支,但其实不会有任何的数据丢失。这个过程同样也可以自动完成:定期执行一个脚本,检查你那些陈旧的已合并到 master 的分支并且删除它们。(译者注:Coding.net 的 Merge Request 也提供了 merge 后自动删除分支功能)
我只对两种事情感到情绪激动:一个是一张动人的照片:山顶上一只金毛寻回犬倚着它最好的朋友,面朝大海,看夕阳西下;还有就是部署工作流。我如此关心这件事是因为它是整个比赛最关键的一部分,在一天结束的时候,我只关心两件事情:同事的感觉是怎么样的,我工作的产品是怎么样的。对我来说其他一切皆源于这两方面。
部署可以造成压力和挫折,尤其当你的公司开发节奏是迟缓的,也可以减缓和阻止你添加新功能、为用户修复 BUG。
我认为思考这些是值得的,优化自己的工作流也是值得的。花一些时间让自己的部署变得尽可能单调、直接、无压力,是会得到回报的。
(完)
你可能会感兴趣的文章:
过早客微信公众号:guozaoke • 过早客新浪微博:@过早客 • 广告投放合作微信:fullygroup50 鄂ICP备2021016276号-2 • 鄂公网安备42018502001446号