热更新最佳实践,网站无感知重启方法大全

简介: 热更新最佳实践,网站无感知重启方法大全

1. 热重启的意义

在 Web 开发中,经常需要更新业务,每次更新代码后,都需要重启应用程序才能生效。

但是每次重启都会中断当前的服务,导致已经连接的请求失败,用户会感受到服务中断。

这就要求服务能够在不中断的情况下重启应用并加载新代码,这就是热重启。

热重启的意义主要体现在:

不中断服务,确保系统可用性

热重启可以在不停止服务的情况下生效新代码,确保系统可以正常提供服务,避免因重启而造成的可用性中断。

实时加载新代码,快速迭代

通过热重启可以跳过重启应用的步骤,直接生效新代码,大大提高开发和迭代的效率。

简化部署,无缝上线

热重启可以作为零停机部署的一种手段,简化繁琐的部署流程,实现应用的无缝上线。


 

2. Go 语言中的热重启方法

Go 语言实现热重启主要有以下几种方法

net/http 包的 Server.ListenAndServe 函数

ListenAndServe 函数可以直接创建 HTTP 服务。可通过在另一个 goroutine 中重新调用 ListenAndServe 来重启服务。

gin 等 Web 框架的方法

流行的 Web 框架如 gin 都内置了热重启机制,可以基于框架实现轻松的热重启。

fresh、enduro/restart 等第三方库

这些优秀的第三方库可以让开发者只通过简单的配置就能启用热重启,使用非常方便。

下面先介绍 ListenAndServe 函数实现热重启的方法,然后介绍基于 gin 框架的热重启, 最后给出基于 fresh 库的热重启示例。


 

3. ListenAndServe 函数实现热重启

3.1 创建 HTTP 服务

需创建一个基本的 HTTP 服务。以下是一个简单的例子:


package main
import (  "fmt"  "net/http")
func main() {  http.HandleFunc("/", func(w http.ResponseWriter,   r *http.Request) {    fmt.Fprintf(w, "Hello, World!")  })
  http.ListenAndServe(":8080", nil)}

3.2 另一个 goroutine 中重启服务

为了实现热重启,可在一个新的 goroutine 中监听新的端口:



package main
import (  "fmt"  "net/http"  "os")
func main() {  http.HandleFunc("/", func(w http.ResponseWriter,   r *http.Request) {    fmt.Fprintf(w, "Hello, World!")  })
  go func() {    http.ListenAndServe(":8081", nil)  }()
  http.ListenAndServe(":8080", nil)}

3.3 停止旧的服务

为了避免端口冲突,可在新的 goroutine 中监听新的端口后,关闭旧的服务:


package main
import (  "fmt"  "net/http"  "os"  "os/signal"  "syscall")
func main() {  http.HandleFunc("/", func(w http.ResponseWriter,   r *http.Request) {      fmt.Fprintf(w, "Hello, World!")      })
  // 创建信号通道  sigCh := make(chan os.Signal, 1)  signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
  // 启动新的服务  go func() {    http.ListenAndServe(":8081", nil)  }()
  // 等待信号  <-sigCh}


 

4. Gin 框架实现热重启

4.1 LoadHTMLGlobles

使用 Gin 框架时,可利用其提供的 LoadHTMLGlob 函数来实现热重启。该函数用于加载 HTML 模板文件。


package main
import (  "github.com/gin-gonic/gin")
func main() {  router := gin.Default()
  // 加载HTML模板文件  router.LoadHTMLGlob("templates/*")
  router.GET("/", func(c *gin.Context) {    c.HTML(200, "index.html", gin.H{})  })
  // 启动服务器  router.Run(":8080")}

4.2 Run 方法

Run 方法启动 Gin 服务器,该方法会自动处理热重启


package main
import (  "github.com/gin-gonic/gin")
func main() {  router := gin.Default()
  router.LoadHTMLGlob("templates/*")
  router.GET("/", func(c *gin.Context) {    c.HTML(200, "index.html", gin.H{})  })
  // 使用Run方法启动服务器,支持热重启  router.Run(":8080")}

4.3 信号处理函数

为了更加优雅地处理热重启,可通过信号处理函数来实现


package main
import (  "os"  "os/signal"  "syscall"
  "github.com/gin-gonic/gin")
func main() {  router := gin.Default()
  router.LoadHTMLGlob("templates/*")
  router.GET("/", func(c *gin.Context) {    c.HTML(200, "index.html", gin.H{})  })
  // 监听系统信号  sigCh := make(chan os.Signal, 1)  signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
  go func() {    // 阻塞,等待信号    <-sigCh
    // 执行一些清理工作,然后退出    // 例如关闭数据库连接等    os.Exit(0)  }()
  // 使用Run方法启动服务器,支持热重启  router.Run(":8080")}


 

5. Fresh 库实现热重启

Fresh 是一个专门用于热重启的第三方 Go 库。

5.1 Fresh 的工作原理

Fresh 会启动两个进程,一个父进程和一个子进程。

父进程维护一个 websocket 服务,用于和子进程通信。

子进程会调用运行的 Go web 应用。

当修改了代码,就可以通过 websocket 给子进程发送信号,子进程接收到信号后会重启的应用。

这样就实现了应用的热重启。

5.2 如何使用 Fresh

使用 Fresh 非常简单,只需要在 main 函数调用 Fresh 方法:


import "github.com/gravityblast/fresh"
func main() {  fresh.Fresh(rootHandler)}
func rootHandler(w http.ResponseWriter, r *http.Request) {  //...}

然后在代码修改时按 Ctrl+C,就会触发重启了。

5.3 Fresh 比自定义实现的优点

相比于自定义的热重启实现,Fresh 有以下优点

使用简单,只需要引入库和添加一行代码。

重启快速,平均重启时间 60-100ms。

自动检查代码修改,无需其他操作。


 

6. 问题及解决方案

虽然上面介绍的几种热重启机制可以零停机应用新代码,但是仍有一些需要注意的问题。

6.1 数据状态、连接处理

热重启后程序会重新初始化,需要妥善处理状态和连接的维护,防止数据丢失或连接中断。

解决方法是在停止服务前,保存必要的状态,并等待当前连接处理完再重启,重启后尽量做好状态恢复,重建连接等操作。

6.2 最小化中断时间

要使热重启对服务的影响最小化,需确保重启过程尽可能快,中断时间维持在毫秒级。

解决方法是仅重启必要的组件,其他组件如数据库等保持运行;同时可以通过配置进行预加载加速重启过程。


 

总结

热重启的意义在于不中断服务,实时加载新代码,简化上线部署。

Go 语言中实现热重启的主要方式有:

net/http 的 ListenAndServe 函数

Web 框架如 gin 的内置机制

第三方库 fresh

各实现方式的优缺点:

ListenAndServe 方式可以自行控制逻辑, but 需要自己实现较复杂的控制流程。

Gin 内置的热重启简单易用,但依赖了框架。


Fresh 使用最为简单,重启快速,是实现热重启的好选择。

目录
相关文章
|
算法 关系型数据库 MySQL
drop、truncate 和 delete 的区别
drop、truncate 和 delete 的区别
|
XML 物联网 API
Android Ble蓝牙App(五)数据操作
Android Ble蓝牙App(五)数据操作
1537 0
|
数据采集 机器学习/深度学习 人工智能
大数据分析案例-用RFM模型对客户价值分析(聚类)
大数据分析案例-用RFM模型对客户价值分析(聚类)
1868 0
大数据分析案例-用RFM模型对客户价值分析(聚类)
|
运维 监控 Linux
iofsstat:帮你轻松定位 IO 突高,前因后果一目了然 | 龙蜥技术
磁盘被打满到底是真实的业务需求量上来了呢?还是有什么野进程在占用 IO? iofsstat 帮你精准定位。
iofsstat:帮你轻松定位 IO 突高,前因后果一目了然 | 龙蜥技术
ly~
|
12月前
|
存储 安全 网络安全
云数据库的安全性如何保障?
云数据库的安全性可通过多种方式保障,包括多因素身份验证、基于角色的访问控制及最小权限原则,确保仅有授权用户能访问所需数据;采用SSL/TLS加密传输和存储数据,加强密钥管理,防止数据泄露;定期备份数据并进行异地存储与恢复演练,确保数据完整性;通过审计日志、实时监控及安全分析,及时发现并应对潜在威胁;利用防火墙、入侵检测系统和VPN保护网络安全;选择信誉良好的云服务提供商,确保数据隔离及定期安全更新。
ly~
835 2
|
SQL 存储 数据库
DROP、TRUNCATE 和 DELETE 命令的区别
【8月更文挑战第3天】
1464 4
DROP、TRUNCATE 和 DELETE 命令的区别
|
存储 Prometheus 监控
当 OpenTelemetry 遇上阿里云 Prometheus
本文以构建系统可观测为切入点,对比 OpenTelemetry 与 Prometheus 的相同与差异,重点介绍如何将应用的 OpenTelemetry 指标接入 Prometheus 及背后原理以及介绍阿里云可观测监控 Prometheus 版拥抱 OpenTelemetry及相关落地实践案例。
93461 103
|
关系型数据库 MySQL Shell
进入mysql报错:bash:/bin/mysql:没有那个文件或目录
进入mysql报错:bash:/bin/mysql:没有那个文件或目录
623 4
|
设计模式 消息中间件 存储
18个并发场景的设计模式详解,有没有你的盲区
这些模式在多线程并发编程中非常有用`。在分布式应用中,并发场景无处不在,理解和掌握这些并发模式的编码技巧,有助于我们在开发中解决很多问题,这要把这些与23种设计模式混淆了,虽然像单例模式是同一个,但这个是考虑并发场景下的应用。内容比较多,V哥建议可以收藏起来,即用好查。拜拜了您誒,晚安。
404 1
18个并发场景的设计模式详解,有没有你的盲区
|
应用服务中间件 Docker 容器
docker tomcat时间少8小时问题解决
通过这些步骤,你应该能够解决Docker容器中Tomcat时间少8小时的问题,并确保容器中的时间与主机系统的时间保持一致。请根据你的实际情况和时区要求来调整时区设置。
404 0