想让一个服务器运行一个进程是相当普遍的,无论是网站、计算,还是其他你能想象到的东西。
不过,在设备的核心功能之外,往往还有许多其他你可能感兴趣的东西。关于进程是否仍在运行,什么服务器在运行,进程中出现了什么错误,以及对外提供的一般信息都是常见的例子。
除此以外,对进程的控制可以是令人难以置信的强大。无论是能够轻松地启动和停止进程而不需要重新部署整个服务器,还是能够轻松地控制正在运行的进程。
不过,在程序中加入这类控制可能很复杂。一个典型的方法是定义HTTP端点,允许执行某些动作。例如,你会有一个/health 端点,它可以被轮询,以查看服务器和进程是否已经启动和运行。或者,你可以在某个地方设置一个Webhook端点,然后你可以配置你的进程来发送更新。
然而,这都需要设置和额外的工作。如果你设置了一个服务器来处理请求,你现在需要处理请求的认证、数据格式、将请求送入你的核心功能,等等。如果你想使用webhooks,你需要做的事情也差不多,只是要取消认证验证,以便在你的核心流程本身中拥有内置的端点。
进入websocketd
值得庆幸的是,有一个神奇的工具正好可以解决这个问题,它就是websocketd。Websocketd由Joe Walnes创建,是一个Go守护服务。实际上,它允许你环绕一个进程,通过托管外部可用的WebSocket服务器,使stdin和stdout与WebSockets可用。与服务器的每个新连接都会启动一个新进程。
例如,如果你有一个你希望能够运行的文件,my-executable.sh ,你可以在终端运行以下命令。
websocketd --port=8080 ./my-executable.sh
这将在8080端口启动一个WebSocket服务器,客户端可以连接到该服务器。一旦有客户这样做,可执行文件的一个实例就会启动,stdin和stdout会被重新定向到WebSocket连接。
通过使用websocketd,你现在可以随时启动一个进程,并通过WebSockets返回任何统计数据/错误/信息。然后,你可以根据需要将其传递给其他系统,或做出相应的反应。
然而,我们还可以增加许多额外的功能,以进一步提升这一功能。几个想法是。
- 每个进程和服务器上的内置健康信息
- 每个进程的 "多对多 "通信,允许相关的客户从一个进程中消费数据
- 认证机制,以确保只有合法的客户能够访问并与服务器通信
- 仅仅是在不支持WebSockets的情况下获得发送给客户的信息的机制
- 没有办法检查先前的通信或重新建立丢失的连接
用Ably扩展websocketd
虽然上述所有内容都很难通过简单地扩展现有的WebSockets实现,但幸运的是,我们可以利用一些东西来为我们处理这一切;Ably。Ably是一个平台,可以通过Pub/Sub系统在任何数量的系统之间进行通信,支持多协议,有历史记录,能够看到谁在连接,等等。
通过用Ably代替WebSocket服务器,我们能够获得所有这些功能,此外还能消除托管WebSocket服务器的负担。相反,服务器需要做的是维持与Ably的单一连接,而Ably将处理其余的复杂性和负载。
通过对websocketd库的一些改动,我们可以大规模地扩大其效用。所有的代码都将采用Go语言,我们将使用Ably Go客户端库与Ably进行交互。
为什么是Go?
Go是一种高效、可靠的编程语言,其核心是基于可扩展性和并发性的前提。Go有效地实现了 Pub/Sub通信,用自己的通道在goroutine之间进行通信,多个goroutine可以同时发布和订阅这些通道。Go的这一核心设计使其成为最适合将这种Pub/Sub功能扩展到网络化Pub/Sub通信的语言之一。
使用Ably Channels的WebSocket连接
第一个变化是删除所有的WebSocket服务器代码;我们将不需要它。相反,我们可以直接实例化一个连接到Ably。你将需要一个有Ably应用的Ably账户来连接,使用Ably API Key来验证。有了这个,你只需要下面的代码。
client, err := ably.NewRealtime(
ably.WithKey(‘YOUR_ABLY_API_KEY’),
ably.WithClientID("ablyDInstanceA")
)
这样我们就有了一个连接,并准备好订阅和发布渠道,与外部客户进行沟通。你可能还注意到我们指定了一个ClientID作为连接的一部分。这将被其他客户端用来识别这个连接是来自一个AblyD实例。理想情况下,如果我们想扩展我们的实现,我们可以用它来识别不同服务器上的多个AblyD的实例。
转移到通道
与其让一个连接的建立决定一个进程的启动,不如利用一个通道来启动进程。如果我们在Ably中创建一个名为command 的通道,我们可以允许任何客户端向其发送消息以启动一个进程。
这样做的一个主要好处是,它使我们在启动进程时很容易添加额外的参数。例如,我们可以与 "开始 "消息一起发送一个有效载荷,内容如下。
{
“Args”: “--debug=verbose --other-arg=20”
}
这可以作为我们设置进程的一部分来传递,并允许我们的进程的独特配置根据需要来运行。为了让我们的AblyD实例接收这些命令,我们所要做的就是订阅command 频道。
ablyCommandChannel.Subscribe(context.Background(), "start", func(msg *ably.Message) {
// Start up
}
同样,我们也可以为每个进程创建一个通道,用于进程的输入和输出。这样一来,客户端就拥有了我们最初从WebSocket服务器获得的所有功能。现在,让我们再接再厉
启动和使用AblyD进程的步骤
Pub/Sub的好处
由于Ably是一个Pub/Sub系统,我们可以让任何数量的设备从一个流程中消费,这意味着如果你有多个系统需要它们,它们都可以从一个Ably通道中获得,而不需要从服务器端做任何额外的事情。如果你需要将数据发送到多个分析工具和数据库,你只需从一个渠道获取数据。你还可以通过消息名称进行过滤,这意味着每个工具只能考虑与其功能相关的消息。
历史记录
Ably通道允许客户通过历史记录检索过去的消息。这意味着,如果数据的消费者在某种程度上出现了连接中断,或者在启动时需要检索以前发送的数据,那么这都是天生支持的,不需要额外的工作。
协议+无服务器
一般来说,WebSockets是在系统之间进行通信的好方法,但它并不总是正确的答案。也许,根据一个流程的输出,我们想激活一个无服务器功能,或将日志存储到数据库中。同样地,我们可能需要与只支持其他协议的服务共享数据。
由于Ably与协议无关,我们通过WebSockets发布到它里面并不重要。客户端可以通过WebSockets、MQTT、SSE或甚至REST请求来订阅Ably通道,Ably会处理每个协议的转换和复杂性,以实现无缝通信。
此外,通过Ably的集成功能,可以将消息从通道发送到无服务器功能、Webhook端点等。由于所有这些功能都是在Ably通道中固有的,因此在开发代码时不需要做任何额外的工作,只需使用您选择的协议订阅一个通道或从Ably网站上设置一个集成,您就可以开始工作了。
认证
Ably还配备了一个成熟的认证系统,允许精细控制客户可以使用哪些通道,以及他们可以在这些通道上执行哪些操作。
这意味着你可以确保那些只能从某些行动中读入数据的服务,只能以只订阅的权限访问这些通道。同样地,只能进行某些请求或更改的服务,只能访问发布到适当的命令通道。
有了完整的JWT支持,就可以非常容易地保持证书的安全性和短命性,使这些通信渠道尽可能地受到限制和安全。
存在感
Ably的一个主要工具,我们可以在AblyD中使用,那就是查看谁在一个频道中处于活动状态。当一个AblyD会话在一个服务器上开始时,它将加入输出通道的Presence,这意味着任何能够访问该通道的人都可以检查该服务器是否可用。
这个Presence状态也有一个附加的数据元素,它可以用来表示服务器的当前状态。例如,它可以指定活动、可用、繁忙、关闭等。通道的存在集的变化可以使用实时协议来订阅,这意味着客户可以对进程的状态有即时的反馈。这种事件的一个例子是。
{
"action": "present",
"id": "e-nKgn7VHj:1:0",
"timestamp": 1627403743288,
"clientId": "ablyDProcessA",
"connectionId": "e-nKgn7VHj",
"data": {
"MaxInstances": 20,
"Instances": {
"79597": "Running"
}
}
}
这可以让其他设备进一步了解服务器和进程的健康和状态,这些都是AblyD的一部分。
例子
通过WebSockets的基本订阅
说了这么多,让我们试着使用它。首先,你需要从GitHub上克隆它。在仓库中,有一个可执行文件样本,你可以在/examples/bash/count.sh中运行。这个文件是一个非常简单的bash脚本,将打印出1到10的数字。
for ((COUNT = 1; COUNT <= 10; COUNT++))
do
echo $COUNT
sleep 0.5
done
在AblyD文件夹中,我们可以运行下面的程序来启动它。确保将YOUR_API_KEY替换为你将要使用的Ably应用中的Ably API密钥。
$ ./ablyD --apikey=YOUR_API_KEY ./examples/bash/count.sh
这将启动AplyD,运行的进程是我们的count.sh文件。它正在等待一个启动信息被发布到command 频道。
在同一个文件夹中,还有一个叫做test.html的文件,它包含了一个通过Ably与我们的服务器进行交互的基本页面。有一个按钮,你可以按下它来请求启动一个进程,来自服务器stdout的响应将出现在按钮的下面。
就这么简单!我们可以有多个实例同时运行,我们可以有多个客户端订阅服务器的相同输出,也可以使用需要输入的更复杂的脚本进程。
AblyD客户端库
为了使与AblyD实例的交互和管理尽可能简单,我们还做了一个简单的库,它围绕着我们的Ably JS库,提供简单的逻辑抽象,使事情变得简单。我们在npm上提供了它,所以要获得它,你只需要使用。
npm install ablyd-client
要启动和监听一个实例的变化,你只需要。
const AblydClient = require('ablyd-client');
let client = new AblydClient(authDetails);
client.startNewProcess((err, newProcess) => {
newProcess.subscribe((msg) => {
console.log(msg.data);
});
});
假设你有一个AblyD实例在运行,你应该看到消息出现在你的控制台中
向webhook端点发送消息
有了我们的简单用例,让我们再加点料。通过Ably,我们可以设置集成规则,允许在一个通道上发送的任何消息被自动发送到一个外部服务。这可能是一个无服务器事件,一个webhook,或任何其他的选择。
我们可以使用Ably Control API以编程方式设置这些规则。有了创建动态规则的能力,我们可以创建一个程序,生成一个集成规则,然后在我们的AblyD实例中启动一个流程。任何由AblyD实例发布到其输出通道的消息将被自动发送到我们在规则中定义的webhook。
集成规则已被正确设置,准备好向我们所需的端点发送消息了!
你可以在AblyD GitHub仓库的examples/webhook文件夹中找到这个演示和更多细节。
总结
如果你需要对设备上运行的进程进行外部控制或访问,那么AblyD可能是一个对你有用的工具。AblyD有一系列的功能,如pub/sub、历史和存在,对于扩展你的功能和对你的进程进行安全、强大的控制和洞察力来说,AblyD可以说是非常强大。
AblyD可以通过以下命令与Homebrew一起使用。
$ brew install ably-labs/homebrew-tap/ablyd
如果你对功能和工具有任何问题或要求,请在Github资源库中提出问题,或与我们联系。我希望使用这个核心功能来创建一个协调工具,它可以检测服务器负载或进程死亡,并处理每个进程的替换。
如果你有任何有趣的想法或项目想使用这个功能,请在@ablyrealtime向我们展示。