Как обрабатывать клиентский параллелизм с Go s net .TCPConn - PullRequest
1 голос
/ 28 мая 2020

У меня есть старый сервер, с которым я общаюсь по TCP. Я написал Client для связи. Он отлично работает через командную строку в однопоточной среде, но при обработке одновременных вызовов он, очевидно, не работает из-за единого общего TCPConn.

Как сеанс работает на этом устаревшем сервере, я должен сделать следующее шаги:

  • Открыть TCPConn
  • Вход (аутентификация на сервере)
  • Вызов нескольких отдельных команд (ie AddUser, GroupAdd) - Их может быть много, и они
  • Выйти
  • Закрыть TCPConn

Несмотря на наличие идентификатора сеанса, все это должно происходить через одно и то же TCP-соединение.

Это сработало в течение многих лет, но теперь, когда сервер API становится более загруженным, если клиенты одновременно обращаются к API, связь через TCPConn нарушается. Очевидно, почему это происходит. Теперь я ищу идеальное решение, чтобы этого не произошло.

Моей первой мыслью было прозрачно создать пул соединений, как это делают драйверы SQL. Проблема здесь в том, что, я считаю, что каждый SQL Query(), QueryRow(), и c может работать независимо, поэтому драйвер может получить TCPConn из пула соединений, запросить, а затем вернуть TCPConn в пул . Однако в моем случае мне нужно удерживать этот TCPConn на время выполнения нескольких запросов. Из-за этого он не может быть действительно прозрачным, поскольку мне нужно вручную возвращать TCPConn обратно в пул в каждой службе, поскольку клиент не может вернуть его после каждого запроса.

Вторая мысль, которая кажется вроде брутто - это поместить все эти logi c в каждый обработчик (получить conn, создать экземпляр Client и создать один или несколько Service (s)). Это кажется излишним, и поскольку количество обработчиков продолжает расти, это всего лишь тонны шаблонов. Это будет иметь освобожденное соединение из пула в каждом обработчике, а также Client и Service в каждом обработчике, а затем возврат соединения в пул в конце.

Вот пример кода ниже .

client.go

package whatever

import (
        "fmt"
        "log"
        "net"
        "net/http"
)

// Response from Client
type Response []byte

type Client struct {
        conn      *net.TCPConn
        username  string
        password  string
        sessionID string
}

// Open and Close the TCPConn
func (c *Client) open() error  { return nil }
func (c *Client) close() error { return nil }

// Login and Logout will call open and close respectively.  Login calls Run()
// with user/pass and gets back a sessionID which is set on the struct.
func (c *Client) Login() error  { return nil }
func (c *Client) Logout() error { return nil }

// Run sends commands to the server over the Client.conn and returns a Repsonse
// byte slice.
func (c *Client) Run(cmd string) (Response, error) { return Response{}, nil }

service.go

// UserService provides services to act on users
type UserService struct {
        client *Client
}

// AddUserGroup adds a user and then adds them to GroupA. 
func (s *UserService) AddUserGroup() error {
        err := s.Client.Login()
        if err != nil {
                return err
        }
        defer s.Client.Logout()

        resp, err := s.Client.Run("CreateUser Bob 46 'Los Angeles'")
        if err != nil {
                return err
        }

        userID := resp[1]

        cmd := fmt.Sprintf("GroupAdd %v GroupA", userID)
        resp, _ := s.Client.Run(cmd)
        if err != nil {
                return err
        }

        return nil
}

this-works-command-line.go

func main() {
        c := Client{username: "user", "password": "pass"}

        svc := UserService{client: &c}
        err := svc.AddUserGroup()
        if err != nil {
                log.Fatal(err)
        }
}

this-does-not-work-api.go

type Server struct {
        Svc *UserService
}

func (s *Server) AddUserGroupHandler() http.HandlerFunc {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
                err := s.Svc.AddUserGroup()
                if err != nil {
                        w.WriteHeader(http.StatusInternalServerError)
                        return
                }
                w.WriteHeader(http.StatusCreated)
        })
}

func main() {
        c := &Client{username: "user", password: "pass"}
        svc := &UserService{client: c}

        server := Server{Svc: svc}
        err := svc.AddUserGroup()
        if err != nil {
                log.Fatal(err)
        }

        http.Handle("/foo", server.AddUserGroupHandler())
        log.Fatal(http.ListenAndServe(":8080", nil))
}

Опять же, если эта конечная точка будет поражена несколько раз, тот же TCPConn будет использован и вызовет Login() или что-то еще, пока обрабатывается предыдущий вызов.

Кажется, я не могу найти общий шаблон, чтобы справиться с этим. Моя последняя мысль - просто принять входящий запрос и вставить его в БД или redis, а потом попросить рабочую службу забрать его.

Возможно, я просто не использую правильную терминологию для поиска.

Любая помощь приветствуется.

...