这个示例的TCP套接字编程事件序列是否安全?

huangapple go评论88阅读模式
英文:

Is this example tcp socket programming sequence of events safe?

问题

我计划使用两个服务。

  1. 用Ruby编写的HTTP REST服务。
  2. 用Go编写的JSON RPC服务。

Ruby服务将会打开一个TCP套接字连接到Go的JSON RPC服务。对于每个收到的HTTP请求,它都会执行这个操作。它将通过套接字向Go服务发送一些数据,然后该服务将通过套接字将相应的数据发送回来。

Go代码

Go服务的代码可能如下所示(简化版):

srv := new(service.App) // 这将公开一个Process方法

rpc.Register(srv)

listener, err := net.Listen("tcp", ":8080")
if err != nil {
	// 处理错误
}

for {
	conn, err := listener.Accept()
	if err != nil {
		// 处理错误
	}

	go jsonrpc.ServeConn(conn)
}

注意我们使用goroutine来处理传入的连接,以便我们可以并发处理请求。

Ruby代码

下面是一个简单的Ruby代码片段,演示了我发送数据到Go服务的方式(理论上):

require "socket"
require "json"

socket = TCPSocket.new "localhost", "8080"

b = {
  :method => "App.Process",
  :params => [{ :Config => JSON.generate({ :foo => :bar }) }],
  :id     => "0"
}

socket.write(JSON.dump(b))

response = JSON.load socket.readline

我的担忧是:这个事件序列是否安全?

我不是在问这是否是“线程安全”的,因为我不担心在go例程之间操作共享内存。我更关心的是我的Ruby HTTP服务是否能得到它期望的数据?

如果我有两个并行的请求进入我的HTTP服务(或者也许Ruby应用程序在负载均衡器后面运行,因此不同的HTTP服务实例正在处理多个请求),那么我可能会让实例A发送消息Foo到Go服务,而实例B发送消息Bar。

Go服务内部的业务逻辑将根据其输入返回不同的响应,因此我希望确保Ruby实例A得到Foo的正确响应,实例B得到Bar的正确响应。

我假设套接字连接更像是一个队列,如果实例A首先向Go服务发出请求,然后B也发出请求,但由于某种原因B的响应更快,那么Go服务将把B的响应写入套接字,而Ruby应用程序的实例A将读取错误的套接字数据(这显然只是一种可能的“场景”,考虑到我可能会“幸运地”让实例B在实例A之前读取套接字数据)。

解决方案?

我不确定是否有一个“简单”的解决方案来解决这个问题。除非我不使用TCP套接字或RPC,而是依赖于Go服务中的标准HTTP。但我希望获得TCP的性能和较少的开销。

我担心这个设计可能会变得更加复杂,可能需要实现一个外部队列来同步Ruby服务的响应。

可能是因为我的Ruby服务的性质是基本上同步的(HTTP响应/请求),所以我别无选择,只能切换到Go服务的HTTP。

但是我想先向社区请教一下,以防我漏掉了一些明显的东西。

英文:

I plan on having two services.

  1. HTTP REST service written in Ruby
  2. JSON RPC service written in Go

The Ruby service will open a TCP socket connection to a Go JSON RPC service. It'll do this for each incoming HTTP request it receives. It will send some data over the socket to the Go service and that service will subsequently send back the corresponding data back down the socket.

Go code

The Go service go would look something like this (simplified):

srv := new(service.App) // this would expose a Process method

rpc.Register(srv)

listener, err := net.Listen("tcp", ":8080")
if err != nil {
	// handle error
}

for {
	conn, err := listener.Accept()
	if err != nil {
		// handle error
	}

	go jsonrpc.ServeConn(conn)
}

Notice we serve the incoming connection using a goroutine, so we can handle requests concurrently.

Ruby code

Below is a simple snippet of Ruby code that demonstrates (in theory) the way I would send data to the Go service:

require "socket"
require "json"

socket = TCPSocket.new "localhost", "8080"

b = {
  :method => "App.Process",
  :params => [{ :Config => JSON.generate({ :foo => :bar }) }],
  :id     => "0"
}

socket.write(JSON.dump(b))

response = JSON.load socket.readline

My concern is: will this be a safe sequence of events?

I'm not asking if this will be 'thread safe', because i'm not worried about manipulating shared memory across the go routines. I'm more concerned around whether my Ruby HTTP service will get back the data it's expecting?

If I have two parallel requests coming into my HTTP Service (or maybe the Ruby app is hosted behind a load balancer and so different instances of the HTTP service is handling multiple requests), then I could have instance A send the message Foo to the Go service; while instance B sends the message Bar.

The business logic inside the Go service will return different responses depending on its input so I want to be sure that Ruby instance A gets back the correct response for Foo, and B gets back the correct response for Bar.

I assume a socket connection is more like a queue in that if instance A makes a request to the Go service first and then B does, but B is quicker responding for whatever reason, then the Go service will write the response for B to the socket and instance A of the Ruby app will end up reading in the wrong socket data (this is obviously just one possible scenario considering that I could get lucky and have instance B read the socket data before instance A does).

Solutions?

I'm not sure if there is simple solution to this problem. Unless I don't use a TCP socket or RPC and instead rely on standard HTTP in the Go service. But I wanted the performance and less overhead of TCP.

I'm worried the design could get more complicated by maybe having to implement an external queue as a way of synchronising the responses with the Ruby service.

It maybe because the nature of my Ruby service is fundamentally synchronous (HTTP response/request) that I have no option but to switch to HTTP for the Go service.

But wanted to double check with the community first just in case I'm missing something obvious.

答案1

得分: 0

是的,如果每次都创建一个新的连接,这是安全的。

但是,你的方法存在一些潜在问题:

  1. 建立TCP连接的成本相对较高,所以你可能希望使用连接池来重用连接。
  2. 如果同时发起太多的请求,会耗尽端口/打开的文件描述符,导致程序崩溃。
  3. 你没有设置任何超时,所以可能会出现无法完成的孤立TCP连接(可能是因为Go端出现问题或网络问题)。

我认为你最好使用HTTP(尽管有开销),因为已经有库来处理这些问题。HTTP也更容易调试,因为你可以使用curl命令测试端点。

个人而言,我可能会选择gRPC

英文:

Yes this is safe if you create a new connection every time.

That said there are latent issues with your approach:

  1. TCP connections are rather expensive to establish, so you probably want to re-use connections with a connection pool
  2. If you make too many simultaneous requests you will exhaust ports/open file descriptors which will cause your program to crash
  3. You don't have any timeouts in place, so it's possible to end up with orphaned TCP connections which never complete (either because of something bad on the Go side, or network problems)

I think you'd be better off using HTTP (despite the overhead) since libraries are already written to cope with these problems. HTTP is also much more debuggable since you can just curl an endpoint to test it.

Personally I'd probably go with gRPC.

huangapple
  • 本文由 发表于 2016年4月10日 01:19:11
  • 转载请务必保留本文链接:https://go.coder-hub.com/36520190.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定