задний план
Если веб-сервис может изолировать свои изменения и внешние сервисы, он дружелюбен к стабильности и доступности сервиса, поэтому есть что-то изящное, и реализация варьируется, но принципы примерно схожи. Для конкретной реализации, которую я использую, пожалуйста, посмотриGitHub.com/жизнь CG-кода/…, Я обнаружил явление в тесте.При выполнении изящного горячего обновления службы я обнаружил, что клиент golang будет время от времени появляться EOF, читать сброс соединения пиром, закрывать соединение в режиме ожидания и т. д., поэтому мне нужно объединить свой собственный тестовый код и анализ явлений.По внутренним причинам эта статья используется как место для общественного обсуждения.Я надеюсь, что кто-то может дать хорошие предложения или исправить мои проблемы, чтобы учиться и развиваться вместе.
В заключение
Хотя веб-служба может достигать изящных возможностей, она не идеальна.У клиента иногда будут возникать проблемы с подключением, независимо от степени параллелизма.
тестовая среда
golang client
гоанг версия 1.10
http протокол 1.1
Это долгое соединение? Да/Нет Пробовал оба варианта
Параллельный номер 1, 30 были опробованы
Количество отправлений за одно соединение — 1 1000. Эксперимент с количеством раз — 1, проблем с подключением на стороне клиента не обнаружено.
Метод запроса отправляет строку из десяти байтов
golang server
Голанг версии 1.10
Данные ответа собственный номер процесса, около 7 байт
анализ проблемы
код клиента голанг
package main
import (
"net/http"
log "github.com/sirupsen/logrus"
"io/ioutil"
"fmt"
"bytes"
"sync"
)
func main() {
var wg sync.WaitGroup
var count int
var rw sync.RWMutex
TEST:
for i := 0; i < 30; i++ {
wg.Add(1)
go func () {
defer wg.Done()
tr := http.Transport{DisableKeepAlives: false}
client := &http.Client{Transport: &tr}
for i := 0; i < 1000; i++ {
f, err := ioutil.ReadFile("data")
if err != nil {
fmt.Println("read file err", err)
return
}
fmt.Println(len(f))
reader := bytes.NewReader(f)
rw.Lock()
count += 1
index := count
rw.Unlock()
resp, err := client.Post("http://0.0.0.0:8888", "application/x-www-form-urlencoded", reader)
if err != nil {
rw.RLock()
currentCount := count
rw.RUnlock()
log.Fatal(err, index, currentCount)
}
defer resp.Body.Close()
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Fatal(err)
}
log.Printf("data[%s]", string(data))
}
}()
}
wg.Wait()
goto TEST
}
код сервера голанг
package main
import (
graceful "github.com/cgCodeLife/graceful2"
"net/http"
log "github.com/sirupsen/logrus"
"io/ioutil"
"fmt"
"os"
"strconv"
)
func main() {
server := graceful.NewServer()
handler := http.HandlerFunc(handle)
server.Register("0.0.0.0:8888", handler)
err := server.Run()
if err != nil {
log.Fatal(err)
}
}
func handle(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
_, err := ioutil.ReadAll(r.Body)
if err != nil {
fmt.Println("read body error[%s] pid[%d]", err, os.Getpid())
}
w.Write([]byte(strconv.Itoa(os.Getpid())))
}
Скриншот экспериментальной части
1 запрос на подключение 1 одновременный параллелизм — 1 случай
1 запрос на подключение 1000 раз параллелизма - это 1 случай
1 запрос на подключение 1 параллелизм равен 30 (ресурсы подключения должны быть исчерпаны, но не возникают проблемы с подключением, такие как EOF, сброс и т. д.)
1 запрос на подключение 1000 раз одновременность 30
Вот краткое описание принципа грациозности, который я использую. Это режим мастер-воркер. Мастер является резидентным. Он только обрабатывает сигналы и отправляет сигналы завершения, как рабочий. Рабочий отвечает за веб-сервисы. После получения сигнала, он выполнит операцию выключения Логика такова.
Посмотрите на код выключения src/net/http/server.go, строка 2536 начинается
// shutdownPollInterval is how often we poll for quiescence
// during Server.Shutdown. This is lower during tests, to
// speed up tests.
// Ideally we could find a solution that doesn't involve polling,
// but which also doesn't have a high runtime cost (and doesn't
// involve any contentious mutexes), but that is left as an
// exercise for the reader.
var shutdownPollInterval = 500 * time.Millisecond
// Shutdown gracefully shuts down the server without interrupting any
// active connections. Shutdown works by first closing all open
// listeners, then closing all idle connections, and then waiting
// indefinitely for connections to return to idle and then shut down.
// If the provided context expires before the shutdown is complete,
// Shutdown returns the context's error, otherwise it returns any
// error returned from closing the Server's underlying Listener(s).
//
// When Shutdown is called, Serve, ListenAndServe, and
// ListenAndServeTLS immediately return ErrServerClosed. Make sure the
// program doesn't exit and waits instead for Shutdown to return.
//
// Shutdown does not attempt to close nor wait for hijacked
// connections such as WebSockets. The caller of Shutdown should
// separately notify such long-lived connections of shutdown and wait
// for them to close, if desired. See RegisterOnShutdown for a way to
// register shutdown notification functions.
func (srv *Server) Shutdown(ctx context.Context) error {
atomic.AddInt32(&srv.inShutdown, 1)
defer atomic.AddInt32(&srv.inShutdown, -1)
srv.mu.Lock()
lnerr := srv.closeListenersLocked()
srv.closeDoneChanLocked()
for _, f := range srv.onShutdown {
go f()
}
srv.mu.Unlock()
ticker := time.NewTicker(shutdownPollInterval)
defer ticker.Stop()
for {
if srv.closeIdleConns() {
return lnerr
}
select {
case <-ctx.Done():
return ctx.Err()
case <-ticker.C:
}
}
}
выключение в основном делает две вещи
1. Прекратить прослушивание
2. Закройте все неиспользуемые соединения
Откуда взялось простаивающее соединение?Установите соединение в активное при запросе, а затем установите его в состояние ожидания после обработки текущего запроса.Поэтому я понимаю, что если есть несколько запросов на соединение, будет проще отключить сканирование Когда запрос был бездействующим, запрос, отправленный клиентом, был сброшен при его закрытии, поэтому есть подозрение, что golang закрыл канал чтения и записи сокета fd в то же время, когда он был закрыт (по этой причине я делаю отдельно на выключение).провёл небольшой эксперимент,nuggets.capable/post/684490…), поэтому в этом случае будет проблема с подключением клиента.
проверять
1 постоянно передает информацию о пакетном соединении: 888 — порт сервера
Очевидно, что соединение с сервером не было разорвано, и служба пропала.
1 интервал соединения 1 секунда для непрерывной отправки информации:
полные четыре волны
решение
На самом деле, я понимаю, что нецелесообразно полагаться на сервер, чтобы полностью избежать этой проблемы, потому что у клиента есть инициатива отправлять или нет, поэтому в этом случае клиенту необходимо сотрудничать с сетью для решения проблемы. .
Ссылаться на
изящное решение golang:blog.cloudflare.com/graceful-up…