有状态服务的安全启动、优雅退出

 

背景:

1、对于有就绪状态的服务来说,进程起来了并不等同于服务就绪了,该服务能否提供服务强依赖于就绪状态
2、服务的运行状态不是由服务进程决定的,而是由外部的状态开关去动态配置(start\handle\stop)
3、如果服务从启动到进入就绪状态,需要较长的时间,在这个期间频繁的下发启动命令,就可能重复的申请启动资源,如连接池、线程池等
4、同理,如果服务从运行到停止也需要较长时间,在次期间频繁下发停止命令,就可能产生死锁或者更高的系统负载
5、在服务处于4的临界状态,在并发量比较高的场景,依然会有大量请求处于处理中的情况,此时服务停止将会丢弃这些处理中的请求,做不到优雅

 

因此在服务开发的时候,可能会遇到服务频繁启动或者频繁销毁的情况,需要分配和回收服务的资源;同时如果在服务关闭处理的不优雅的情况,还可能会丢失数据,因此服务设计的时候,需要考虑如何优雅的对服务进行启停。

 

package main

import (
    "fmt"
    "os"
    "os/signal"
    "sync"
    "syscall"
)

func main() {
    svr := MyService{}
    svr.Start()

    // 监听终止信号
    signalCh := make(chan os.Signal, 1)
    signal.Notify(signalCh, syscall.SIGINT, syscall.SIGTERM)

    // 等待接收终止信号
    <-signalCh

    svr.Stop()

    fmt.Println("Exiting program...")
    os.Exit(0)
}

type MyService struct {
    isOpen bool
    rwLock sync.RWMutex // 读共享,写排斥
    stop   int32
    once   sync.Once
}

func (s *MyService) Start() {
    s.rwLock.Lock()
    defer s.rwLock.Unlock()
    if s.isOpen {
        return
    }

    // 初始化资源

    s.isOpen = true
}

func (s *MyService) Stop() {
    s.once.Do(func() {
        // 获取写锁。如果有其他goroutine持有读锁或写锁,当前goroutine会阻塞直到获取到写锁为止
        s.rwLock.Lock()
        defer s.rwLock.Unlock()
        if !s.isOpen {
            return
        }

        // 释放资源

        s.isOpen = false
    })
}

func (s *MyService) Handle() {
    // 获取读锁。如果有其他goroutine持有写锁,当前goroutine会阻塞直到获取到读锁为止
    s.rwLock.RLock()
    defer s.rwLock.RUnlock()

    if !s.isOpen {
        return
    }
    // 业务处理
}

 

posted @ 2023-08-23 20:14  知道了呀~  阅读(23)  评论(0编辑  收藏  举报