有状态服务的安全启动、优雅退出
背景:
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 } // 业务处理 }
等风起的那一天,我已准备好一切