"Это седьмой день моего участия в ноябрьском испытании обновлений, ознакомьтесь с подробностями события:Вызов последнего обновления 2021 г."
Введение?
Закрытые, все еще такие элегантные?
На самом деле, есть еще один термин для плавного завершения работы, который называется «плавный выход». Если вы планируете построить свои собственные колеса, изящное закрывание будет первым пунктом знаний, который вам нужно будет освоить.
В жизни, если есть существительное, которое действительно слишком трудно понять, мы могли бы также увидеть, что является противоположностью этого существительного.
Возьмем простой пример
- Graceful Shutdown: использование функции завершения работы операционной системы для выключения компьютера.
- Менее изящное завершение работы: прямое отключение питания и перезапуск
У некоторых учащихся могут возникнуть вопросы: Обычно я выключаю и перезагружаю компьютер, когда он зависает, и не вижу серьезных проблем?
Компьютеры и компьютерные телосложения не могут быть обобщены, если на вашем компьютере установленаLinux
Система, если это будет другой сервер, если вы принудительно перезагрузите, вы, скорее всего, потеряете некоторые данные.Если это производственная среда, то приготовьтесь нести ведро и убегать.
Зачем?
Если вы когда-нибудь думали сделатьMySQL
илиRedis
Для настройки более или менее были затронуты следующие параметры:
-
MySQL
изsync_binlog
Параметры для управления поведением устройств хранения, которые сохраняют данные двоичного журнала. -
Redis
изappendfysnc
параметры для контроляRedis
Поведение сохраняемых журналов AOF на устройствах хранения данных.
Причина для вышеуказанных параметров заключается в том, что сохранение данных на устройстве хранения требует относительно много времени.Linux
Принятые меры оптимизации заключаются в том, что когда вы добавляете данные в файл, они будут временно храниться в системном кеше, а затем сохраняться на устройстве хранения партиями после ожидания возможности. если процесс не указывает использоватьDirectIO
путь или позвонитеfsync
, операционная система будет активно записывать данные на запоминающее устройство.
следовательноMySQL
иRedis
Параметры настройки были открыты для управления поведением сохранения журнала, и вина была возложена на программистов.
sync_binlog
Значение обычно равно 1, т. е. данные бинарного журнала сохраняются на устройстве хранения немедленно каждый раз, когда транзакция фиксируется.
изящное завершение работы
Теперь из примера с менее изящным завершением работы вы можете увидеть, что делает изящное завершение работы:
- Дайте программе завершить незавершенную работу (например: зафиксировать транзакцию, сохранить журнал и т. д.).
Однако нам также нужно добавить ограничение:
- Когда программа решает корректно закрыться, она больше не может принимать запросы.
Если вы не перестанете обрабатывать новые запросы, это никогда не закончится
Изящное отключение пулов потоков
Пул потоков (ThreadPoolExecutor
)существуетJDK
Он занимает важное место в параллельном пакете, мы можем посмотреть, как такой важный базовый компонент справляется с корректным завершением работы.
Этот класс открывает разрешение программисту, нужно ли его изящно закрыть, и предоставляет два метода, а именно:
-
ThreadPoolExecutor.shutdown
Этот метод установит состояние пула потоков вSHUTDOWN
, и больше не будет принимать отправку новых задач, но позволит потокам в пуле потоков выполнять все отправленные задачи. -
ThreadPoolExecutor.shutdownNow
Этот метод установит состояние пула потоков вSTOP
, и больше не принимать отправку новых задач, и немедленно отправлять сигнал прерывания всем потокам в пуле потоков, и напрямую игнорировать задачи, которые были отправлены в пул потоков, но еще не запущены.
изящный процесс выключения
Как красиво закрыть процесс?Для начала нам нужно выяснить, при каких обстоятельствах процесс будет закрыт:
- Активное завершение работы (обычно не закрывается активно для процесса, предоставляющего услуги внешнему миру)
- Программа дает сбой, например, бизнес выдает исключение, которое не обрабатывается должным образом, в результате чего исключение выдается на самый внешний уровень и не обрабатывается, что приводит к сбою программы.
- Процесс получил сигнал завершения работы от операционной системы (например, нажатие Ctrl+C)
В приложениях уровня предприятия процесс обычно имеет не только бизнес-логику, но и службы журналов/службы MQ/службы эксплуатации и обслуживания, разработанные для бизнеса, поэтому, когда у бизнеса возникает проблема, которая может привести к сбою процесса, мы необходимо передать другим службам сообщение о том, что процесс вот-вот будет закрыт, и вызвать метод корректного завершения работы, предоставляемый этими службами, а затем выйти из процесса после выполнения всех вышеперечисленных мер.Сообщения доставлены или потреблены и т. д.
Если вы убиваете процесс напрямую (kill -9), нет необходимости обсуждать корректное завершение работы.
Мы берем Golang в качестве примера, чтобы описать, как изящно завершить процесс.Во-первых, нам нужно абстрагировать службы в процессе, чтобы добиться управления жизненным циклом.Каждый поставщик услуг должен предоставитьServe
иShutdown
метод.
type Service interface {
Serve(ctx context.Context) error
Shutdown() error
}
Далее мы определяемServiceGroup
справлятьсяService
жизненный цикл, когдаService
Ошибка в работе или приеме системного сигналаSIGINT
(триггер Ctrl+C) иSIGTREM
(убить без параметров),ServiceGroup
будет нести ответственность за закрытие отключений, управляемых этимService
и позвониShutdown
метод.
type ServiceGroup struct {
ctx context.Context
cancel func()
services []Service
}
func NewServiceGroup(ctx context.Context) *ServiceGroup {
g := ServiceGroup{}
g.ctx, g.cancel = context.WithCancel(ctx)
return &g
}
func (s *ServiceGroup) Add(service Service) {
s.services = append(s.services, service)
}
func (s *ServiceGroup) run(service Service) (err error) {
defer func() {
if r := recover(); r != nil {
err = r.(error)
}
}()
err = service.Serve(s.ctx)
return
}
func (s *ServiceGroup) watchDog() {
signalChan := make(chan os.Signal, 1)
signal.Notify(signalChan, syscall.SIGINT, syscall.SIGTERM)
for {
select {
case <- signalChan:
// 接收到系统信号, 通知停止服务
s.cancel()
goto CLOSE
case <- s.ctx.Done():
// 上下文被取消
goto CLOSE
}
}
CLOSE:
for _, service := range s.services {
if err := service.Shutdown(); err != nil {
fmt.Printf("shutdown failed err: %s", err)
}
}
}
func (s *ServiceGroup) ServeAll() {
var wg sync.WaitGroup
for idx := range s.services {
service := s.services[idx]
wg.Add(1)
go func() {
defer wg.Done()
if err := s.run(service); err != nil {
fmt.Println("服务异常, 进入退出流程!")
s.cancel()
}
}()
}
wg.Add(1)
go func() {
defer wg.Done()
s.watchDog()
}()
wg.Wait()
}
Далее мы определяем случайныйpanic
бизнес-услуги и услуги журнала.
type BusinessService struct {
}
func (b *BusinessService) Serve(ctx context.Context) (err error) {
times := 0
for {
fmt.Printf("业务运行中 %d\n", times)
select {
case <- ctx.Done():
fmt.Printf("BusinessService receive cancel signal\n")
return
default:
if n := rand.Intn(256); n > 200 {
panic(fmt.Errorf("random panic on %d", n))
}
}
time.Sleep(time.Millisecond * time.Duration(rand.Intn(1000)))
times++
}
return
}
func (b *BusinessService) Shutdown() error {
fmt.Println("业务服务, 关闭!")
return nil
}
type LogService struct {
buffer []string
}
func (l *LogService) Serve(ctx context.Context) (err error) {
for {
select {
case <- ctx.Done():
return
default:
// 投递日志到消息队列
time.Sleep(time.Millisecond * time.Duration(rand.Intn(500)))
l.buffer = append(l.buffer, fmt.Sprintf("Time: %d", time.Now().Unix()))
}
}
}
func (l *LogService) Shutdown() (err error) {
fmt.Printf("日志服务, 关闭! 有[%d]条日志待发送\n", len(l.buffer))
if len(l.buffer) == 0 {
return
}
for _, log := range l.buffer {
// 发送日志或者持久化到硬盘
fmt.Printf("Send Log [%s]\n", log)
}
fmt.Println("缓冲区日志清理完毕")
return
}
бегать
func main() {
rand.Seed(time.Now().Unix())
ctx := context.Background()
g := NewServiceGroup(ctx)
g.Add(&LogService{})
g.Add(&BusinessService{})
g.ServeAll()
}
Результат запуска выглядит следующим образом:
В приведенном выше коде еще много оптимизаций, и читатели могут улучшить его самостоятельно. если доступноerrorgroup
управлять сервисом иShutdwon
Вы также можете передать контекст для управления тайм-аутом.
Суммировать
Что такое изящное завершение работы?
- Позвольте программе сделать работу, которая была отправлена, но не сделана
- новых запросов больше нет