C# 程序在 Docker 中响应 Unix 信号
C# 程序在 Docker 中响应 Unix 信号
在 Docker Entry Script 详解中介绍了如何在 shell 脚本中响应 Unix 信号量来实现 Docker 应用优雅的关闭退出, 本文介绍 C# 程序如何在 Docker 中响应 Unix 信号实现优雅的关闭退出。
因为用 Mono 编译出来的程序可以完美的在 Linux/Docker 下运行, 所本文以 Mono 5.4 做为开发环境, 对应的 .Net Framework 版本为 4.6.1 。
假设现在需要运行一个定时任务的程序, 每隔一定时间输出一个 hello, world!
,我们使用 Quartz.Net 来完成这个任务, 代码如下所示:
首先来定义一个 EchoJob
, 向控制台输出 hello, world!
, 代码如下:
public class EchoJob : IJob {
public void Execute(IJobExecutionContext context) {
Console.WriteLine($"{DateTime.Now} Hello, world!");
}
}
接下来使用 Quartz
来配置并启动这个任务, 代码如下:
private static void StartupQuartz() {
Console.WriteLine("Start Quartz");
var factory = new StdSchedulerFactory();
scheduler = factory.GetScheduler();
scheduler.Start();
var job = JobBuilder.Create<EchoJob>()
.WithIdentity("EchoJob", "EchoJob")
.Build();
var trigger = TriggerBuilder.Create()
.WithIdentity("EchoJob-Trigger", "EchoJob")
.WithSimpleSchedule(
x => x.WithInterval(TimeSpan.FromSeconds(5))
.RepeatForever()
)
.StartNow()
.Build();
scheduler.ScheduleJob(job, trigger);
}
当按 Ctrl + C 结束程序或者使用 docker stop
命令停止容器时, 我们希望停止这个任务, 停止任务的代码如下:
private static void ShutdownQuartz() {
Console.WriteLine("Shutdown Quartz");
scheduler.Shutdown();
}
接下来, 问题就来了, 我们的程序如何响应这两个时间呢? 在 Linux 下面, Mono 提供了 Mono.Unix.UnixSignal
来解决这中问题, 我们的程序需要监听两个 Unix 信号, 分别是:
- Mono.Unix.Native.Signum.SIGINT , 按 Ctrl + C 结束程序时发送的信号量;
- Mono.Unix.Native.Signum.SIGTERM , Docker 容器停止时发送的信号量;
根据 Mono 的文档, 监听 Unix 信号量的代码如下:
private static void WaitForExit() {
var signals = new UnixSignal[] {
new UnixSignal(Signum.SIGINT),
new UnixSignal(Signum.SIGTERM)
};
var index = UnixSignal.WaitAny(signals);
var signal = signals[index].Signum;
Console.WriteLine($"Received Signal: {signal}");
}
现在, 我们的程序看起来是这样子的:
class MainClass {
private static IScheduler scheduler = null;
public static void Main(string[] args) {
StartupQuartz();
WaitForExit();
ShutdownQuartz();
}
private static void StartupQuartz() { ... }
private static void ShutdownQuartz() { ... }
private static void WaitForExit() { ... }
}
程序在前台运行, 用 Ctrl + C
方式来结束程序时, 输出如下:
$ mono bin/QuartzDocker.exe
Start Quartz
12/8/2017 11:34:11 AM Hello, world!
12/8/2017 11:34:16 AM Hello, world!
^CReceived Signal: SIGINT
Shutdown Quartz
部署到 Docker 容器, 用 docker stop
停止容器时, 输出如下:
Sending build context to Docker daemon 4.311MB
Step 1/4 : FROM beginor/mono:5.4.1.6
---> 7c736fa9d337
Step 2/4 : COPY bin /app
---> 80ab98a23b28
Step 3/4 : WORKDIR /app
---> bde64015b8b0
Removing intermediate container 299227729a73
Step 4/4 : ENTRYPOINT mono QuartzDocker.exe
---> Running in 4c23abe8f903
---> b299267db381
Removing intermediate container 4c23abe8f903
Successfully built b299267db381
Successfully tagged quartz-test:latest
Start Quartz
12/08/2017 11:39:00 Hello, world!
12/08/2017 11:39:05 Hello, world!
12/08/2017 11:39:10 Hello, world!
12/08/2017 11:39:15 Hello, world!
12/08/2017 11:39:20 Hello, world!
12/08/2017 11:39:25 Hello, world!
12/08/2017 11:39:30 Hello, world!
Received Signal: SIGTERM
Shutdown Quartz
现在, 我们的目的终于达到了。
通常应用程序都会有自己的状态, 在程序结束时, 保存应用程序的状态是非常重要的, 因此应许能够感知结束, 并保存状态是非常重要的。
对于 Docker 来说, 发送 SIGTERM 之后, 默认最多只等待 10 秒钟, 如果 10 秒钟之后还没有退出, 就会被强制关闭。 如果需要修改这个等待时间的话, 则需要在 docker stop 命令添加 --time
选项, 设置等待时间, 比如:
docker stop --time 30 CONTAINER
如果你的开发环境是 Windows , 只安装了 .Net Framework, 找不到
Mono.Posix
引用怎么办, 不要着急, 可以通过 Nuget 来添加 Mono.Posix 包来解决。
- Python爬虫代理IP池
- SSDB图形界面管理工具:phpssdbadmin安装部署
- [Go 语言社区] 初始化内存数据--游戏列表数据
- SSDB安装配置记录
- Python标准库笔记(3) — datetime模块
- Django 1.10中文文档-第一个应用Part4-表单和通用视图
- Python标准库笔记(2) — re模块
- Python爬虫—破解JS加密的Cookie
- Go语言中json转成map结构
- rpc-dubbo简单入门
- Django 1.10中文文档-第一个应用Part3-视图和模板
- Go语言对JSON进行编码和解码
- [Go 语言社区]服务器自测JS 工程
- Django 1.10中文文档-第一个应用Part2-模型和管理站点
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 几个小细节帮你提升java代码运行效率
- spring data操作ES简直不能再香
- 国防科大提出基于可变形三维卷积(D3Dnet)的视频超分辨,代码已开源
- 不要被kafka的异步模式欺骗了
- 给你总结几个ES下最容易踩的坑
- ES系列之利用filter让你的查询效率飞起来
- ES主分片和副本数据大小不一样的情况
- 关于kibana的可视化可能都在这篇文章里了
- ES分页看这篇就够了
- ES系列之原来查看文档数量有这么多姿势
- ES系列之嵌套文档和父子文档
- ES系列之一文带你避开日期类型存在的坑
- ES系列之原来ES的聚合统计不准确啊
- fastjson远程代码执行漏洞问题分析
- 数据库连接池的原理没你想得这么复杂