WebSocket em Go: técnicas avançadas para aplicações real-time
\n\nO desenvolvimento de aplicações real-time em Go é um dos pontos fortes da linguagem. O suporte embutido para concorrência através de goroutines e canais torna Go a escolha ideal para servidores WebSocket. No entanto, quando se trata de sistemas de produção com milhares de conexões simultâneas, o simples exemplo "Hello, WebSocket" deixa de funcionar. Neste artigo, abordaremos técnicas avançadas: desde a otimização do pool de conexões até a escalabilidade através de brokers de mensagens.
\n\n1. Arquitetura de pool de conexões: gerenciando milhares de clientes
\n\nNo exemplo básico, criamos uma goroutine separada para cada conexão. Isso é aceitável para 100-200 clientes, mas com 10.000+ conexões, o consumo de memória e a sobrecarga de troca de contexto tornam-se críticos. A abordagem avançada é usar um pool de conexões com um número fixo de workers e canais.
\n\n// Pool — pool de conexões com limitação de goroutines\ntype Pool struct {\n connections map[*websocket.Conn]bool\n broadcast chan []byte\n register chan *websocket.Conn\n unregister chan *websocket.Conn\n}\n\nfunc NewPool(maxGoroutines int) *Pool {\n return &Pool{\n connections: make(map[*websocket.Conn]bool),\n broadcast: make(chan []byte, 256),\n register: make(chan *websocket.Conn),\n unregister: make(chan *websocket.Conn),\n }\n}\n\nfunc (pool *Pool) Start() {\n for {\n select {\n case conn := <-pool.register:\n pool.connections[conn] = true\n case conn := <-pool.unregister:\n if _, ok := pool.connections[conn]; ok {\n delete(pool.connections, conn)\n conn.Close()\n }\n case message := <-pool.broadcast:\n for conn := range pool.connections {\n go func(c *websocket.Conn) {\n if err := c.WriteMessage(websocket.TextMessage, message); err != nil {\n pool.unregister <- c\n }\n }(conn)\n }\n }\n }\n}\n\nPor que isso funciona? Os canais atuam como buffers, prevenindo bloqueios. Um único select gerencia todos os eventos, e as goroutines para escrita são criadas apenas quando necessário. Isso reduz a carga de pico no GC (coletor de lixo).
\n\n2. Desligamento gracioso e gerenciamento de estado das conexões
\n\nAo reiniciar o servidor, é importante fechar corretamente todas as conexões WebSocket, enviando aos clientes uma mensagem de encerramento. Ignorar esta etapa leva a conexões "penduradas" e vazamentos de memória.
\n\n// Server — servidor WebSocket com desligamento gracioso\ntype Server struct {\n pool *Pool\n httpSrv *http.Server\n}\n\nfunc (s *Server) Shutdown(ctx context.Context) error {\n // 1. Fecha o servidor HTTP (novas conexões não são aceitas)\n if err := s.httpSrv.Shutdown(ctx); err != nil {\n return err\n }\n\n // 2. Envia a todos os clientes uma mensagem de encerramento\n shutdownMsg := []byte(`{"type":"shutdown","message":"server is going down"}`)\n s.pool.broadcast <- shutdownMsg\n\n // 3. Fecha todas as conexões no pool\n for conn := range s.pool.connections {\n conn.WriteMessage(websocket.CloseMessage, \n websocket.FormatCloseMessage(websocket.CloseNormalClosure, "server shutdown"))\n conn.Close()\n }\n\n return nil\n}\n\n// Uso no main\nfunc main() {\n pool := NewPool(1000)\n go pool.Start()\n\n srv := &Server{pool: pool}\n srv.httpSrv = &http.Server{Addr: ":8080"}\n\n // Tratamento de sinais\n go func() {\n sigint := make(chan os.Signal, 1)\n signal.Notify(sigint, os.Interrupt, syscall.SIGTERM)\n <-sigint\n ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)\n defer cancel()\n srv.Shutdown(ctx)\n }()\n\n log.Fatal(srv.httpSrv.ListenAndServ