In addition to Weibo, there is also WeChat
Please pay attention
WeChat public account
Shulou
2025-01-24 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Servers >
Share
Shulou(Shulou.com)05/31 Report--
This article analyzes "how Google designs Ruby Serverless Runtime". The content is detailed and easy to understand. Friends who are interested in "how Google designs Ruby Serverless Runtime" can follow the editor's idea to read it slowly and deeply. I hope it will be helpful to you after reading. Let's follow the editor to learn more about "how Google designs Ruby Serverless Runtime".
A way to realize Ruby Serverless
Providing Ruby support for Serverless products is much more complex than you might expect. From the most basic point of view, the language runtime is just an installation of Ruby, and to be sure, it is not difficult to configure the Ruby image and install it on VM. However, when you add "Serverless" to it, things get more complicated. Severless is not just about automatic maintenance and expansion. This is a completely different way of thinking about computing resources, which runs counter to much of what we have learned over the past 15 years about deploying Ruby applications. When Google Cloud's Ruby team took on the task of designing the Ruby runtime for Cloud Functions, we also took on the daunting task of proposing a Ruby way to implement Serverless. While sticking to the Ruby habits, practices, and tools that our community is familiar with, we must also rethink how to develop web applications at almost every level, from code to dependency, persistence, testing, and so on.
This article will examine our approach to five different aspects of design: functional syntax, concurrency and lifecycle, testing, dependencies, and standards. In each case, we will strike a balance between the importance of being loyal to the nature of Ruby and the desire to embrace the new Serverless paradigm. We have worked very hard to maintain continuity with the traditional way Ruby works, and we have also learned from other Google Cloud Functions language runtimes and the precedents set by other cloud providers' Serverless products. However, in a few cases, we choose to take a different approach. We did this because we felt that the current approach either abused language capabilities or misled and encouraged misconceptions about Serverless application development.
Some decisions may eventually turn out to be wrong. That's why I'm providing this article now. Discuss what we have done and begin to discuss the way we practice Serverless application development as a Ruby community. The good news is that Ruby is a very flexible language, and as our learning and needs develop, we will have many opportunities to adapt to it.
So let's take a look at some of the initial design decisions and tradeoffs we made and the reasons for these decisions.
Functional Ruby
Function as a Service (FaaS) is currently one of the more popular Serverless paradigms. Google Cloud Functions is just an implementation. Many other major cloud providers have their own FaaS products and also have open source implementations.
The idea, of course, is to use a programming model that is not Web server-centric, but function-centric: stateless code snippets that take input parameters and return results. This seems to be a simple and almost obvious terminology change, but it actually has far-reaching implications.
The first challenge for Ruby is that, unlike many other programming languages, functions are not first-class citizens in Ruby. Ruby is first of all an object-oriented language. When we write the code and encapsulate it in def, we are writing a method that runs in response to a message sent to the object. This is an important difference because the objects and classes that make up the method invocation context are not part of the Serverless abstraction. Therefore, their existence can complicate Serverless applications and even mislead us when we write applications.
For example, some FaaS frameworks allow you to use def to write functions at the top level of the Ruby file:
Def handler (event:, context:) "Hello, world!" end
Although this code looks simple, it is important to remember what it actually does. It adds this "function" as a private method of the Object class, which is the base class of the Ruby class hierarchy. In other words, almost every object in the Ruby virtual machine has "functions" added. (of course, unless the application changes the master object and class context when loading the file, this technique poses other risks.) At best, this breaks the encapsulation and single responsibility. In the worst case, it can interfere with application functionality, dependencies, and even Ruby standard libraries. This is why this "top-level" approach is common in simple single-file Ruby scripts and Rakefiles, but is not recommended in large Ruby applications.
The Google Ruby team thought the problem was serious, so we chose a different syntax to write the function in blocks:
Require "functions_framework" FunctionsFramework.http ("handler") do | request | "Hello, world!" end
This provides a Ruby-like way to define functions without modifying the Object base class. It also has some side benefits:
The name (in this case, "handler") is just a string parameter. It doesn't have to be a legitimate Ruby method name, nor do you have to worry about it colliding with the Ruby keyword.
Chunks have more traditional lexical scope than methods, so their behavior is more similar to functions in other languages.
Block syntax makes it easier to manage function definitions. For example, you can clean "undefine" functions, which is important for testing.
Of course, there is a trade-off. Where:
The grammar is a little tedious.
It requires a library to provide an interface for defining functions as blocks. Here, Ruby follows the other languages of Cloud Functions's runtime by using the Functions Framework library. )
We believe that in order to achieve the goal of correctly distinguishing functions, these tradeoffs are worth it.
Share or not share
Concurrency is difficult. This is a key observation point of Serverless design (especially function as a service): we live in a concurrent world and we need a variety of ways to deal with it. The function paradigm solves the concurrency problem by insisting that functions do not share state (except through an external persistence system, such as queues or databases). This is actually another reason why we choose to use block syntax instead of method syntax. Method, which carries states as instance variables that may not work properly in a stateless FaaS environment. Avoidance is a subtle but effective grammatical method that can prevent the practice of what we know to be problematic.
That is, what if you need to share resources, such as database connection pooling? When to initialize these resources and how to access them?
To do this, the Ruby runtime supports startup functions that initialize resources and pass them to function callers. Importantly, startup functions can create resources, while ordinary functions can only read them.
Require "functions_framework" # Use an on_startup block to initialize a shared client and store it in# the global shared data.FunctionsFramework.on_startup do require "google/cloud/storage" set_global: storage_client, Google::Cloud::Storage.newend# The shared storage_client can be accessed by all function invocations# via the global shared data.FunctionsFramework.http "storage_example" >
Notice that we chose to define special methods global and set_global to interact with global resources. By the way, these are not methods on Object, but methods on specific classes used as function contexts. Similarly, we can use more traditional idioms, such as Ruby global variables, or even constructors and instance variables, to pass information from the startup code to the function caller. However, these grammars may convey the wrong things. It's not normal for us to write shared data in ordinary Ruby classes and methods, but it's dangerous (even if possible) to write shared data in Serverless's functions, and we think it's important to syntactically emphasize differences. These special methods are deliberate design decisions to prevent dangerous practices when concurrency exists.
Test is the first
A strong testing culture is at the heart of the Ruby community. Popular frameworks, such as Rails, acknowledge this and encourage proactive testing by providing test tools and scaffolding as part of the framework, which is also followed by Google Cloud Functions's Ruby runtime, providing testing tools for Serverless functions.
The FaaS paradigm is actually very suitable for testing. The function is inherently easy to test, simply passing in parameters and asserting the result. In particular, you do not need to start the web server to run the tests, because the web server is not part of the abstraction. Apart from the fact that the Ruby runtime provides a helper method module for creating HTTP requests and cloud event objects for use as input, most tests are very easy to write.
However, one of the main testing challenges we encounter is related to testing initialization code. Indeed, this is a problem that members of the Google Ruby team encounter when using other frameworks, including Rails: it is difficult to test the initialization process of the application, because the initialization of the framework usually takes place outside the test, before they are run. Therefore, we designed a test method to isolate the entire life cycle of the function, including initialization. This allows us to run initialization in the test, or even repeat it multiple times, allowing different aspects of the test:
Require "minitest/autorun" require "functions_framework/testing" class MyTest
< Minitest::Test # Include testing helper methods include FunctionsFramework::Testing def test_startup_tasks # Run the lifecycle, and test the startup tasks in isolation. load_temporary "app.rb" do globals = run_startup_tasks "storage_example" assert_kind_of Google::Cloud::Storage, globals[:storage_client] end end def test_storage_request # Rerun the entire lifecycle, including the startup tasks, and # test a function call. load_temporary "app.rb" do request = make_get_request "https://example.com/foo" response = call_http "storage_example", request assert_equal 200, response.status end endend load_temporary 方法在沙箱中加载函数定义,将它们及其初始化与其他测试运行隔离开来。该方法和其他 helper 方法定义在 FunctionsFramework::Testing 模块中,可以包含在 minitest 或 rspec 测试中。 到目前为止,我们只为 Ruby 运行时提供了基本的测试工具,我希望随着用户开发更多的应用程序和识别出更多常见的测试模式,我们会在工具集中大量增加这些工具。但我坚信测试工具是任何库的重要组成部分,特别是那些声称是框架或运行时的库,所以它是我们设计的核心部分。 可依赖的运行时 大多数重要的 Ruby 应用程序都需要第三方 gems。对于使用 Google Cloud Functions 的 Ruby 应用程序,我们至少需要一个 gem,即 functions_framework,它提供了编写函数的 Ruby 接口。您可能还需要其他 gems 来处理数据、进行身份验证并与其他服务集成等等。依赖项管理是任何运行时框架的关键部分。 我们围绕依赖项管理做出了几个设计决策。而第一个也是最重要的就是拥抱 Bundler。 我知道这听起来有点无聊。现在大多数 Ruby 应用程序都在使用 Bundler,而且很少有替代方案,很少有广泛使用的。但我们实际上更进一步,将 Bundler 深入到我们的基础架构中,要求应用程序使用它来处理云函数。我们这么做是因为,确切地知道应用将如何管理它的依赖关系将允许我们实现一些重要的优化。 对于一个好的 FaaS 系统来说,部署和冷启动的速度至关重要。在 serverless 的世界中,您的代码可能会快速连续地更新、部署和拆除许多次,因此消除瓶颈(如解析和安装依赖项)是至关重要的。因为我们为依赖项管理标准化了一个系统,所以我们能够主动地缓存依赖项。我们认为,实现这样的缓存所带来的性能提升,以及 Rubygems.org 基础架构所减少的负载,远远超过了不能使用 Bundler 的替代方案所带来的灵活性降低。 Google Cloud Functions 的 Ruby 运行时的另一个特性,或者可能是怪癖,是如果 gem lockfile 丢失或不一致,部署将失败。我们需要这个 Gemfile.lock 在部署时存在。这是执行最佳实践的另一个决策。如果在部署期间重新解析了锁文件,那么您的构建可能是不可重复的,并且您可能没有针对测试时使用的相同依赖项运行。我们通过要求一个最新的 Gemfile.lock 来避免这个问题。同样,我们能够强制执行这一点,因为我们需要使用 Bundler。 新旧标准 最后,好的设计依赖于标准和现有技术。为了在 Ruby 中定义健壮的函数,我们不得不进行一些创新,但在表示函数参数时,已经有现成的库或新兴标准可供遵循。 例如,在近期内,许多函数将响应 web hook,并需要关于传入 HTTP 请求的信息。设计一个表示 HTTP 请求的类并不困难,但是 Ruby 社区已经有了用于这类事情的标准 API: Rack。我们采用 Rack 请求类作为事件参数,并支持标准的 Rack 响应作为返回值。 require "functions_framework" FunctionsFramework.http "http_example">This not only provides a familiar API, but also makes it easy to integrate with other Rack-based libraries. For example, it's easy to put Sinatra applications on top of cloud functions because they all support Rack.
In the long run, we increasingly want function as a Service (Faas) as a component of the event system. Event-based architectures are rapidly gaining popularity, often around event queues such as Apache Kafka. A key element of the event architecture is the standard way to describe the event itself, which is understood by event senders, agents, transports, and consumers.
Google Cloud Functions supports CNCF CloudEvents, an emerging standard for describing and delivering events. In addition to HTTP requests, cloud functions can also receive data in the form of CloudEvent, and the runtime will even convert some legacy event types to CloudEvent when calling the function.
Require "functions_framework" FunctionsFramework.cloud_event "my_handler" do | event | # event is a CloudEvent object defined by the cloud_events gem logger.info "I received a CloudEvent of type # {event.type}!" end
In order to support the CloudEvent,Google Ruby team working closely with the CNCF Serverless working group in Ruby, it even volunteered to take over the development of Ruby SDK for CloudEvent. It's a lot of work, but we think it's important to be able to use the official, standard Ruby interface, even if we have to implement it ourselves.
So much for sharing about how Google designs Ruby Serverless Runtime. I hope the above content can improve everyone. If you want to learn more knowledge, please pay more attention to the editor's updates. Thank you for following the website!
Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.
Views: 0
*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.
Continue with the installation of the previous hadoop.First, install zookooper1. Decompress zookoope
"Every 5-10 years, there's a rare product, a really special, very unusual product that's the most un
© 2024 shulou.com SLNews company. All rights reserved.