服务器分为应用服务器和什么
by Yan Cui
崔燕
为什么要对无服务器应用单一责任原则 (Why you should apply the single responsibility principle to serverless)
A funny moment (at 38:50) happened during Tim Bray’s session (SRV306) at re:invent 2017. Tim asked the audience if we should have many single-purposed functions, or fewer monolithic functions, and there was an equal split in opinions.
在re:invent 2017上Tim Bray的会议( SRV306 )期间发生了一个有趣的时刻(38:50)。Tim问听众我们是否应该拥有许多单一用途的功能,或更少的整体功能,并且意见分歧是相等的。
This was a moment that challenged my belief, as I’ve been brought up on the SOLID principles.
这是挑战我的信念的那一刻,因为我已经养成SOLID原则。
- Single Responsibility Principle 单一责任原则
- Open/Closed Principle 开/关原则
- Liskov Substitution Principle 里斯科夫替代原则
- Interface Segregation Principle 接口隔离原理
- Dependency Inversion Principle 依赖倒置原则
I have, for a long time, believed that following the Single Responsibility Principle (SRP) is a no-brainer.
很长一段时间以来,我一直相信遵循单一职责原则 (SRP)毫无疑问。
That prompted this closer examination of the arguments from both sides.
这促使双方仔细研究了双方的论点。
Full disclosure: I am biased in this debate. If you find flaws in my thinking, or disagree with my views, please point them out in the comments.
全面披露: 在这场辩论中,我有偏见。 如果您发现我的想法有缺陷或不同意我的观点,请在评论中指出。
By “monolithic functions,” I mean functions that have internal branching logic. These functions can do one of several things based on the invocation event.
“单片函数”是指具有内部分支逻辑的函数。 这些函数可以根据调用事件执行以下操作之一。
For example, you can have one function handle all the endpoints for an API. The function would perform a different action based on the path
and method
parameters.
例如,您可以让一个函数处理API的所有端点。 该函数将根据path
和method
参数执行不同的操作。
module.exports.handler = (event, context, cb) => { const path = event.path; const method = event.httpMethod;
if (path === '/user' && method === 'GET') { .. // get user } else if (path === '/user' && method === 'DELETE') { .. // delete user } else if (path === '/user' && method === 'POST') { .. // create user } else if { .. // other endpoints & methods }}
You can’t rationally reason about and compare solutions without first understanding the problem and what qualities are most desirable in a solution.
在没有先了解问题以及解决方案中最需要什么素质的情况下,您无法合理地推理和比较解决方案。
And when I hear complaints such as that “having so many functions is hard to manage,” I wonder what does manage entail?
当我听到的抱怨,诸如“有这么多的功能是很难管理,”我不知道是什么管理意味着什么?
- Is it to find specific functions you’re looking for? 是否可以找到您要查找的特定功能?
- Is it to discover what functions you have? 是为了发现您具有什么功能?
- Does this become a problem when you have 10 functions or 100 functions? 当您有10个功能或100个功能时,这会成为问题吗?
- Does it become a problem only when you have more developers working on them than you’re able to keep track of? 仅当您有更多的开发人员在工作,而不是能够跟踪时,这才成为问题吗?
Drawing from my own experiences, the problem has less to do with what functions we have. Rather, it’s about discovering what features and capabilities we possess through these functions.
根据我自己的经验,该问题与我们所具有的功能无关。 而是要通过这些功能发现我们拥有哪些功能。
After all, a Lambda function, like a Docker container, is a conduit to deliver some business feature or capability.
毕竟,Lambda函数(例如Docker容器)是传递某些业务功能或特性的渠道。
You wouldn’t ask “Do we have a get-user-by-facebook-id
function?” since you would need to know what the function was called without even knowing if the capability exists and if it’s captured by a Lambda function. Instead, you would probably ask, “Do we have a Lambda function that can find a user based on his/her facebook ID?”
您不会问“我们有一个get-user-by-facebook-id
功能吗?” 因为您甚至不知道该功能是否存在以及是否由Lambda函数捕获,就需要知道该函数被调用了什么。 取而代之的是,您可能会问: “我们是否有一个Lambda函数可以根据其Facebook ID查找用户?”
So the real problem is that, given that we have a complex system that consists of many features and capabilities, that is maintained by many teams of developers, how do we organize these features and capabilities into Lambda functions so that it’s optimized towards:
因此,真正的问题在于,鉴于我们有一个由许多开发人员维护的,由许多功能组成的复杂系统, 我们如何将这些功能组织到Lambda函数中,以便针对以下方面进行优化:
discoverability: how do I find out what features and capabilities exist in our system?
问题:我如何找出系统中存在哪些功能?
debugging: how do I quickly identify and locate the code I need to look at to debug a problem? For example, there are errors in system X’s logs, where do I find the relevant code to start debugging the system?
debugging :如何快速识别和定位调试问题所需的代码 例如,系统X的日志中有错误,在哪里可以找到相关代码以开始调试系统?
scaling the team: how do I minimize friction and grow the engineering team while maintaining the code?
扩大团队规模 :如何在维护代码的同时最大程度地减少摩擦并发展工程团队?
Below are the qualities that are most important to me. With this knowledge, I can compare different approaches and see which is best suited for me.
以下是对我来说最重要的素质。 有了这些知识,我可以比较不同的方法,看看哪种最适合 我的 。
You might care about different qualities. For example, you might not care about scaling the team, but cost is an important consideration for you. Whatever they might be, it’s helpful to make those design goals explicit. You should also make sure they’re shared with and understood by your team.
您可能会关心不同的品质。 例如,您可能并不关心团队规模,但是成本是您的重要考虑因素。 无论它们是什么,明确那些设计目标都是有帮助的。 您还应该确保团队共享和理解它们。
可发现性 (Discoverability)
The lack of discoverability is not a new problem. According to Simon Wardley, it’s rather rampant in both government as well as the private sector. Most organizations lack a systematic way for teams to share and discover each other’s work.
缺乏可发现性不是一个新问题。 根据西蒙·沃德利(Simon Wardley)的说法,无论是政府部门还是私营部门,这种情况都相当普遍。 大多数组织缺乏团队共享和发现彼此工作的系统方法。
As stated earlier, discovery is about finding out what capabilities are available through your functions. Knowing what functions you have is not enough.
如前所述,发现是要找出通过您的功能可以使用的功能。 仅仅知道您拥有什么功能还不够。
An argument I often hear for monolithic functions is that it reduces the number of functions, which makes them easier to manage.
我经常听到的关于整体函数的论点是,它减少了函数的数量,这使它们更易于管理。
On the surface, this seems to make sense. But the more I think about it, the more the argument appears flawed. The number of functions would only be an impediment IF we try to manage them by hand rather than using the tools available to us already.
从表面上看,这似乎是有道理的。 但是我想得越多,争论就越有缺陷。 如果我们尝试手动管理功能,而不是使用已经可用的工具,那么功能的数量将成为一个障碍。
After all, we are able to locate books by their content in a huge physical space with tens of thousands of books. Using the library analogy, with the tools available to us, we can catalogue our functions and make them easy to search.
毕竟,我们能够根据书本的内容在具有成千上万本书的巨大物理空间中查找书本。 使用类库,使用我们可用的工具,我们可以对函数进行分类并使其易于搜索。
For example, the Serverless framework enforces a simple naming convention of {service}-{stage}-{function}
. This simple convention makes it easy to find related functions by prefix. If I want to find all the functions that are part of a user
API, I can do that by searching for user-api
.
例如, 无服务器框架强制执行{service}-{stage}-{function}
的简单命名约定。 这个简单的约定使按前缀查找相关功能变得容易。 如果我想找到user
API的所有功能,可以通过搜索user-api
。
With tags, we can catalogue functions across multiple dimensions. For example, we can catalogue using environment, feature name, event source, author, and so on.
使用标签,我们可以在多个维度上对函数进行分类。 例如,我们可以使用环境,功能名称,事件源,作者等进行分类。
If you have a rough idea of what you’re looking for, then the number of functions is not an impediment to your ability to discover what’s there.
如果您对要查找的内容有一个粗略的了解,那么功能的数目并不妨碍您发现其中的内容。
With single-purposed functions, the capabilities of the user-api
is immediately obvious. I can see from the relevant functions that I have the basic CRUD capabilities, because there are corresponding functions for each.
使用单一用途的功能, user-api
功能立即显而易见。 从相关功能中可以看到我具有基本的CRUD功能,因为每个功能都有相应的功能。
With a monolithic function, however, it’s not so straightforward. There is only one function, but what can this function do? I’ll have to either look at the code myself, or consult with the author of the function. For me, this makes for poor discoverability.
但是,具有单片函数并不是那么简单。 只有一个功能,但是该功能可以做什么? 我将不得不自己看代码,或者咨询函数的作者。 对我来说,这使可发现性差。
Because of this, I mark the monolithic approach down on discoverability.
因此,我将整体方法标记为可发现性。
But, having more functions means there are more pages for you to scroll through. This can be laborious if you just want to browse and see what functions are there.
但是,拥有更多功能意味着您可以滚动浏览更多页面。 如果您只想浏览并查看其中有什么功能,这可能会很麻烦。
Although, in my experience, this has never been a problem per se. Thanks to the Serverless framework’s naming convention, all related functions are close together. It’s actually quite nice to see what each group of functions can do, rather than having to guess what goes on inside a monolithic function.
尽管以我的经验来看,这本身从来不是问题。 由于无服务器框架的命名约定,所有相关功能都紧密结合在一起。 看到每组函数可以做什么实际上很不错,而不必猜测单个函数内部发生了什么。
But, it can be a pain to scroll through everything when you have thousands of functions. So, I’m going to penalize single-purposed functions for that.
但是,当您具有数千个功能时,滚动浏览所有内容可能会很痛苦。 因此,我将为此惩罚单用途函数。
At that level of complexity, though, packing more capabilities into each function would only make the system more difficult to understand. Say you have a thousand functions, and you know what each does at a glance. Wouldn’t it be simpler if you replace them with a hundred functions, but you can’t tell what each does?
但是,在这种复杂性级别上,将更多功能打包到每个功能中只会使系统更加难以理解。 假设您有上千种功能,那么您一眼就能知道它们的功能。 如果用一百个函数代替它们会更简单,但是您不知道每个函数是什么?
调试 (Debugging)
For debugging, the relevant question is whether having fewer functions makes it easier to identify and locate the bug.
对于调试,相关的问题是功能是否较少,是否可以更轻松地识别和定位错误。
In my experience, the path from an error to the relevant function and repo is the same, regardless of whether the function does one thing or many things.
以我的经验,从错误到相关功能以及回购的路径是相同的,而不管该功能是做一件事情还是做很多事情。
The difference is how to find the relevant code inside the repo for the problems you’re investigating.
区别在于如何在仓库中找到要调查的相关代码。
A monolithic function has more branching logic. So it would take more cognitive effort to follow through to the code that is relevant to the problem at hand.
单片函数具有更多分支逻辑。 因此,要花更多的精力才能完成与手头问题相关的代码。
For that, I’ll mark monolithic functions down slightly. Of course, we’re talking about a minimal difference here, which is why the penalty is also minimal.
为此,我将单片函数标记为略微降低。 当然,我们在这里谈论的是最小的差异,这就是为什么罚款也最小的原因。
缩放比例 (Scaling)
In the early days of microservices, one of the arguments for microservices was that it makes scaling easier.
在微服务的早期,微服务的观点之一是它使扩展变得更容易。
But that’s not the case!
但是事实并非如此!
If you know how to scale a system, then you can scale a monolith as easily as you can scale a microservice.
如果您知道如何扩展系统,则可以像扩展微服务一样轻松地扩展整体。
I say that as someone who has built monolithic backend systems for games that had a million Daily Active Users (DAU). Supercell, the creator of top grossing games like Clash of Clans and Clash Royale, have well over 100 million DAU. The backend systems for these games are all monoliths, and Supercell has no problems scaling these systems.
我说的是,他为拥有百万每日活跃用户(DAU)的游戏构建了整体后端系统。 超级单体 ,顶部卖座的游戏,如部落冲突 , 冲突皇家的创造者,拥有超过1亿的DAU。 这些游戏的后端系统都是独石, Supercell扩展这些系统没有问题。
Instead, tech giants such as Amazon and Google taught us that microservices make it easier to scale in a different dimension — our engineering team.
取而代之的是,诸如亚马逊和谷歌之类的技术巨头告诉我们,微服务使我们更容易在不同维度上进行扩展-我们的工程团队。
This style of architecture allows us to create boundaries within our system, around features and capabilities. It allows our engineering teams to scale the complexity of what they build, because they can more easily build on top of the work that others have created before them.
这种体系结构样式使我们能够在系统中围绕功能部件创建边界。 它使我们的工程团队能够扩展其构建的复杂性,因为他们可以更轻松地在其他人之前创建的工作之上进行构建。
Take Google’s Cloud Datastore as an example. The engineers in that team were able to produce a sophisticated service by building on top of many layers of services. Each layer provides a powerful abstractions the next layer can leverage.
以Google的Cloud Datastore为例。 该团队中的工程师能够通过在多层服务之上构建服务来提供复杂的服务 。 每层都提供了下一层可以利用的强大抽象。
These boundaries give us a greater division of labour. Which allows more engineers to work on the system by giving them areas where they can work in relative isolation. This way, they don’t trip over each other with merge conflicts, integration problems, and so on.
这些界限使我们更加分工。 通过给他们可以相对隔离的工作区域,使更多的工程师可以在系统上工作。 这样,它们不会因合并冲突,集成问题等而相互绊倒。
Michael Nygard also wrote a nice article that explains this benefit from another angle: that these boundaries and isolation helps us reduce the overhead of sharing mental models.
迈克尔·尼加德(Michael Nygard)也写了一篇不错的文章 ,从另一个角度解释了这一好处:这些边界和隔离有助于我们减少共享心理模型的开销。
“If you have a high coherence penalty and too many people, then the team as a whole moves slower… It’s about reducing the overhead of sharing mental models.”
“如果你有一个高相干性和惩罚的人太多了,那么球队的整体移动速度较慢......这是关于减少共享心智模型的开销 。”
- Michael Nygard
-迈克尔·尼加德(Michael Nygard)
Having lots of single-purposed functions is perhaps the pinnacle of that division of tasks. You lose that division a little when you move to monolithic functions. Although in practice, you probably won’t have so many developers working on the same project that you feel the pain.
具有许多单一用途的功能可能是该任务划分的顶峰。 当您转向单片函数时,您会稍微失去该划分。 尽管在实践中,您可能不会有那么多开发人员在同一个项目上工作,但您会感到痛苦。
Restricting a function to doing one thing also helps limit how complex a function can become. To make something more complex, you would compose these simple functions together via other means, such as with AWS Step Functions.
将功能限制为只能执行一项操作也有助于限制功能的复杂程度。 为了使事情变得更复杂,您可以通过其他方法(例如,AWS Step Functions)将这些简单的函数组合在一起。
I’ll mark monolithic functions down for losing some division of labour, and for raising the complexity ceiling of a function.
我将单片函数标记为降低失去某些分工并提高函数的复杂性上限。
结论 (Conclusion)
Based on the criteria that are important to me, having many single-purposed functions is the better way to go. But I do not see this as a hard and fast rule.
根据对 我来说 很 重要 的 标准 ,拥有许多单一用途的功能是更好的选择。 但是我不认为这是一成不变的规则。
Like everyone else, I come preloaded with a set of predispositions and biases formed from my experiences, which likely do not exactly reflect yours. I’m not asking you to agree with me. Though I do hope you appreciate the process of working out what’s important to you so you can go about finding the right approach for you.
像其他所有人一样,我充满了根据我的经验形成的一系列倾向和偏见,这些倾向和偏见可能无法完全反映您的经验。 我不是要你同意我的观点。 尽管我确实希望您对确定对您来说重要的过程感到赞赏,以便您可以为自己找到正确的方法。
But what about cold starts? Wouldn’t monolithic functions help you reduce the number of cold starts?
但是冷启动呢? 单片函数是否可以帮助您减少冷启动次数?
The short answer is no, they don’t help you with cold starts in any meaningful way. It’s also the wrong place to optimize for cold starts. If you’re interested in the longer version of this answer, then please read my other post here.
简短的答案是没有,它们不会以任何有意义的方式帮助您进行冷启动。 这也是为冷启动进行优化的错误位置。 如果您对此答案的较长版本感兴趣,请在此处阅读我的其他文章。
And lastly, having smaller surface areas with single-purposed functions reduces the attack surface. You can give each function the exact permission it needs and nothing more. This is an important, but often underappreciated advantage of single-purposed functions.
最后,具有单一用途功能的较小表面积可减少攻击面。 您可以给每个功能所需的确切权限,仅此而已。 这是单一用途功能的重要但通常不为人知的优势。
服务器分为应用服务器和什么