netcore服务程序暴力退出导致的业务数据不一致的一种解决方案(优雅退出)
一: 问题提出
现如今大家写的netcore程序大多部署在linux平台上,而且服务程序里面可能会做各种复杂的操作,涉及到多数据源(mysql,redis,kafka)。成功部署成后台
进程之后,你以为这样就万事大吉了? 却不知当你更新代码时,暴力的kill掉这个进程导致你的业务出现数据不一致,业务流程被破坏等等问题。比如下面这段代码:
1. TestService
public class TestService
{
public static void Run()
{
while (true)
{
Console.WriteLine($"{DateTime.Now}: 1. 获取mysql");
System.Threading.Thread.Sleep();
Console.WriteLine($"{DateTime.Now}: 2. 获取redis");
System.Threading.Thread.Sleep();
Console.WriteLine($"{DateTime.Now}: 3. 更新monogdb");
System.Threading.Thread.Sleep();
Console.WriteLine($"{DateTime.Now}: 4. 通知kafka");
System.Threading.Thread.Sleep();
Console.WriteLine($"{DateTime.Now}: 5. 所有业务处理完毕");
System.Threading.Thread.Sleep();
}
}
}
2. Main程序
public static void Main(string[] args)
{
var bgtask = Task.Run(() => { TestService.Run(); }); bgtask.Wait();
}
这里不考虑程序的健壮性,只表达这里可能出现的问题,当程序退出后,这里必然会遇到TestService.Run方法出现未执行完的情况,导致数据不一致,比如下
面我简单的部署了一下,可以看到程序到了 5:03:24s之后就结束了,显然破坏了业务逻辑。程序没有完整的执行结束,那问题该怎么解决呢?
[root@localhost netcore]# nohup dotnet ConsoleApp4.dll &
[]
[root@localhost netcore]# nohup: ignoring input and appending output to ‘nohup.out’ [root@localhost netcore]# ps -ef | grep dotnet
root : pts/ :: dotnet ConsoleApp4.dll
root : pts/ :: grep --color=auto dotnet
[root@localhost netcore]# kill
[root@localhost netcore]# tail nohup.out
// :: PM: . 获取redis
// :: PM: . 更新monogdb
// :: PM: . 通知kafka
// :: PM: . 所有业务处理完毕
// :: PM: . 获取mysql
// :: PM: . 获取redis
// :: PM: . 更新monogdb
// :: PM: . 通知kafka
// :: PM: . 所有业务处理完毕
// :: PM: . 获取mysql
[]+ Done nohup dotnet ConsoleApp4.dll
[root@localhost netcore]#
二:思考 kill 命令
要解决这个问题,大家一定要从kill命令入手, 在centos上进行kill -x pid 的时候,不知道有多少人了解了这个命令,除了常见的 kill -9 pid ,其实还有很多其
他的数字,则代表其他的意思,可以通过kill -l 看一下。
[root@localhost ~]# kill -l
) SIGHUP ) SIGINT ) SIGQUIT ) SIGILL ) SIGTRAP
) SIGABRT ) SIGBUS ) SIGFPE ) SIGKILL ) SIGUSR1
) SIGSEGV ) SIGUSR2 ) SIGPIPE ) SIGALRM ) SIGTERM
) SIGSTKFLT ) SIGCHLD ) SIGCONT ) SIGSTOP ) SIGTSTP
) SIGTTIN ) SIGTTOU ) SIGURG ) SIGXCPU ) SIGXFSZ
) SIGVTALRM ) SIGPROF ) SIGWINCH ) SIGIO ) SIGPWR
) SIGSYS ) SIGRTMIN ) SIGRTMIN+ ) SIGRTMIN+ ) SIGRTMIN+
) SIGRTMIN+ ) SIGRTMIN+ ) SIGRTMIN+ ) SIGRTMIN+ ) SIGRTMIN+
) SIGRTMIN+ ) SIGRTMIN+ ) SIGRTMIN+ ) SIGRTMIN+ ) SIGRTMIN+
) SIGRTMIN+ ) SIGRTMIN+ ) SIGRTMAX- ) SIGRTMAX- ) SIGRTMAX-
) SIGRTMAX- ) SIGRTMAX- ) SIGRTMAX- ) SIGRTMAX- ) SIGRTMAX-
) SIGRTMAX- ) SIGRTMAX- ) SIGRTMAX- ) SIGRTMAX- ) SIGRTMAX-
) SIGRTMAX- ) SIGRTMAX
其中里面的
2. SIGNIT (Ctrl+C)
3. SIGQUIT (退出)
9. SIGKILL (强制终止)
15. SIGTERM (终止)
都可以让程序退出,好了,线索出来了,那我能不能让程序捕获到kill命令发出的这Sigxxx信号呢??? 通过寻找资料之后的一阵浑身痉挛,你明白了原来只有
-9是不能让程序捕获到,其他的程序都能捕获,那么既然能捕获,我就可以在捕获的事件中做程序的安全退出。 大概的脑图就像下面这样:

三:研究如何捕获
在core 2.0之后,获取sigterm就非常简单了,可以在当前应用程序域中挂载一个ProcessExit 事件,在ProcessExit中让应用程序安全的退出。

然后还有一个问题就是,如何在ProcessExit中通知TestService结束执行呢? 这里就用到了CancellationTokenSource 这种线程安全的取消协调机制,思考之后
画出来的脑图大概是这个样子,不一定对,但是逻辑大概出来了。。。

四: 问题解决
有了上面的脑图,写起代码就快啦~~~
1. Main函数
public static void Main(string[] args)
{
var cts = new CancellationTokenSource(); var bgtask = Task.Run(() => { TestService.Run(cts.Token); }); AppDomain.CurrentDomain.ProcessExit += (s, e) =>
{
Console.WriteLine($"{DateTime.Now} 后台测试服务,准备进行资源清理!"); cts.Cancel(); //设置IsCancellationRequested=true,让TestService今早结束
bgtask.Wait(); //等待 testService 结束执行 Console.WriteLine($"{DateTime.Now} 恭喜,Test服务程序已正常退出!"); Environment.Exit();
}; Console.WriteLine($"{DateTime.Now} 后端服务程序正常启动!"); bgtask.Wait();
}
Main函数中做了如上的变更,将CancellationToken传递给 Run方法,这样当我执行Cancel的时候,Run方法就能感知到Token的变化,然后就是调用Wait等待
TestService执行结束。
2. TestService
public class TestService
{
public static void Run(CancellationToken token)
{
while (true)
{
if (token.IsCancellationRequested) break; Console.WriteLine($"{DateTime.Now}: 1. 获取mysql");
System.Threading.Thread.Sleep();
Console.WriteLine($"{DateTime.Now}: 2. 获取redis");
System.Threading.Thread.Sleep();
Console.WriteLine($"{DateTime.Now}: 3. 更新monogdb");
System.Threading.Thread.Sleep();
Console.WriteLine($"{DateTime.Now}: 4. 通知kafka");
System.Threading.Thread.Sleep();
Console.WriteLine($"{DateTime.Now}: 5. 所有业务处理完毕");
System.Threading.Thread.Sleep();
}
}
}
TestService的while循环里面,在周期轮训的开头,加上一个IsCancellationRequested的判断,如果Cancel()方法被调用,IsCancellationRequested就会变
成true,从而让本方法感知到外界让我结束,所以本逻辑就不再进行下一个周期了,从而保证业务逻辑的完整。
五:部署
为了更好的表达效果,我加了很多的日志,还是采用nohup的模式来观察一下程序的流转过程。
[root@localhost netcore]# nohup dotnet ConsoleApp1.dll &
[]
[root@localhost netcore]# nohup: ignoring input and appending output to ‘nohup.out’ [root@localhost netcore]# ps -ef | grep dotnet
root : pts/ :: dotnet ConsoleApp1.dll
root : pts/ :: grep --color=auto dotnet
[]- Done nohup dotnet ConsoleApp1.dll
[root@localhost netcore]# kill
[root@localhost netcore]# tail - nohup.out
// :: PM: . 获取mysql
// :: PM 后端服务程序正常启动!
// :: PM: . 获取redis
// :: PM: . 更新monogdb
// :: PM: . 通知kafka
// :: PM: . 所有业务处理完毕
// :: PM: . 获取mysql
// :: PM: . 获取redis
// :: PM: . 更新monogdb
// :: PM: . 通知kafka
// :: PM: . 所有业务处理完毕
// :: PM: . 获取mysql
// :: PM: . 获取redis
// :: PM: . 更新monogdb
// :: PM: . 通知kafka
// :: PM: . 所有业务处理完毕
// :: PM: . 获取mysql
// :: PM: . 获取redis
// :: PM 后台测试服务,准备进行资源清理!
// :: PM: . 更新monogdb
// :: PM: . 通知kafka
// :: PM: . 所有业务处理完毕
// :: PM 恭喜,Test服务程序已正常退出!
大家可以清楚的看到,5:11:49 收到了system给过来的kill通知,但是程序还是等到了5:11:57才真正的结束自己,这样是不是就保证了业务流程免遭破坏呢?
好了,本篇就说到这里,希望对你有帮助。
netcore服务程序暴力退出导致的业务数据不一致的一种解决方案(优雅退出)的更多相关文章
- Phoenix数据覆盖的一种解决方案
最近在做实时数仓,需要兼顾离线和实时两种查询方式,大致的方案是数据通过binlog抽取,经Phoenix插入,hive映射hbase表:Phoenix创建索引,实时查询Phoenix:离线查询hive ...
- 大型DELETE(删除大量数据)的一种解决方案
通过执行单条DELETE语句来删除一个大型的数据集会有以下的缺点: 1.DELETE语句的操作要被完整地记录到日志中,这要求在事务日志中要有足够的空间以完成整个事务: 2.在删除操作期间(可能会花费很 ...
- 解决Redis中数据不一致问题
redis系列之数据库与缓存数据一致性解决方案 数据库与缓存读写模式策略写完数据库后是否需要马上更新缓存还是直接删除缓存? (1).如果写数据库的值与更新到缓存值是一样的,不需要经过任何的计算,可以马 ...
- 演示stop暴力停止线程导致数据不一致的问题,但是有些有趣的发现 (2017-07-03 21:25)
如注释所言 /** * Created by weiwei22 on 17/7/3. * * 这里主要是为了演示stop导致的数据不一致的问题.stop会暴力的结束线程并释放锁,所以有可能在恰好写了一 ...
- pt-osc改表导致数据不一致案例分析
2016-06-10 李丹 dba流浪猫 我们平时除了解决自己问题外,有时候也会协助圈内人士,进行一些故障排查,此案例就是帮某公司DBA进行的故障分析,因为比较典型,特分享一下,但仅仅是分享发生的过程 ...
- Redis面试题记录--缓存双写情况下导致数据不一致问题
转载自:https://blog.csdn.net/lzhcoder/article/details/79469123 https://blog.csdn.net/u013374645/article ...
- 由数据迁移至MongoDB导致的数据不一致问题及解决方案
故事背景 企业现状 2019年年初,我接到了一个神秘电话,电话那头竟然准确的说出了我的昵称:上海小胖. 我想这事情不简单,就回了句:您好,我是小胖,请问您是? "我就是刚刚加了你微信的 xx ...
- P2P小贷网站业务数据流程分享
P2P小贷网站业务数据流程分享 引言 这是去年年底开发的一个项目,完成后和用户的衔接没有很好的做起来,所以项目就搁浅了.9月以来,看各路P2P风声水起,很是热闹:这里分享下我的设计文档,算是抛砖引玉, ...
- 基于netcore实现mongodb和ElasticSearch之间的数据实时同步的工具(Mongo2Es)
基于netcore实现mongodb和ElasticSearch之间的数据实时同步的工具 支持一对一,一对多,多对一和多对多的数据传输方式. 一对一 - 一个mongodb的collection对应一 ...
随机推荐
- System.Drawing.image 与ImageSource 互转
private BitmapSource bs(Bitmap bt) { IntPtr ip = bt.GetHbitmap(); BitmapSource bitmapSource = System ...
- python爬虫入门(七)Scrapy框架之Spider类
Spider类 Spider类定义了如何爬取某个(或某些)网站.包括了爬取的动作(例如:是否跟进链接)以及如何从网页的内容中提取结构化数据(爬取item). 换句话说,Spider就是您定义爬取的动作 ...
- Springboot 框架学习
Springboot 框架学习 前言 Spring Boot是Spring 官方的顶级项目之一,她的其他小伙伴还有Spring Cloud.Spring Framework.Spring Data等等 ...
- SSM-SpringMVC-04:SpringMVC深入浅出理解HandleMapping(源码刨析)
------------吾亦无他,唯手熟尔,谦卑若愚,好学若饥------------- 先从概念理解,从中央调度器,携带参数request,调度到HandleMapping处理器映射器,处理器映射器 ...
- Java判断字符串是否为数字的自定义方法
//方法一:用JAVA自带的函数 public static boolean isNumeric(String str){ for (int i = str.length();--i>=0;){ ...
- Java 并行与并发
Java 并行与并发 注意两个词:并行(Concurrent) 并发(Parallel) 并行:是逻辑上同时发生,指在某一个时间内同时运行多个程序 并发:是物理上同时发生,指在某一个时间点同时运行多个 ...
- bootstrap table的样式
<style> table{ border: 1px solid #ddd; background-color: transparent; border-spacing:; border- ...
- ImageMagick简介、GraphicsMagick、命令行使用示例
http://elf8848.iteye.com/blog/382528 ImageMagick资料 ------------------------------------------------- ...
- java.util.ConcurrentModificationException异常的解决
问题复现: List<String> list = new ArrayList<>();list.add("11");list.add("55&q ...
- java正则使用方法
import java.util.regex.Matcher;import java.util.regex.Pattern; public class RegexMatches{ public ...