一: 问题提出

  现如今大家写的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服务程序暴力退出导致的业务数据不一致的一种解决方案(优雅退出)的更多相关文章

  1. Phoenix数据覆盖的一种解决方案

    最近在做实时数仓,需要兼顾离线和实时两种查询方式,大致的方案是数据通过binlog抽取,经Phoenix插入,hive映射hbase表:Phoenix创建索引,实时查询Phoenix:离线查询hive ...

  2. 大型DELETE(删除大量数据)的一种解决方案

    通过执行单条DELETE语句来删除一个大型的数据集会有以下的缺点: 1.DELETE语句的操作要被完整地记录到日志中,这要求在事务日志中要有足够的空间以完成整个事务: 2.在删除操作期间(可能会花费很 ...

  3. 解决Redis中数据不一致问题

    redis系列之数据库与缓存数据一致性解决方案 数据库与缓存读写模式策略写完数据库后是否需要马上更新缓存还是直接删除缓存? (1).如果写数据库的值与更新到缓存值是一样的,不需要经过任何的计算,可以马 ...

  4. 演示stop暴力停止线程导致数据不一致的问题,但是有些有趣的发现 (2017-07-03 21:25)

    如注释所言 /** * Created by weiwei22 on 17/7/3. * * 这里主要是为了演示stop导致的数据不一致的问题.stop会暴力的结束线程并释放锁,所以有可能在恰好写了一 ...

  5. pt-osc改表导致数据不一致案例分析

    2016-06-10 李丹 dba流浪猫 我们平时除了解决自己问题外,有时候也会协助圈内人士,进行一些故障排查,此案例就是帮某公司DBA进行的故障分析,因为比较典型,特分享一下,但仅仅是分享发生的过程 ...

  6. Redis面试题记录--缓存双写情况下导致数据不一致问题

    转载自:https://blog.csdn.net/lzhcoder/article/details/79469123 https://blog.csdn.net/u013374645/article ...

  7. 由数据迁移至MongoDB导致的数据不一致问题及解决方案

    故事背景 企业现状 2019年年初,我接到了一个神秘电话,电话那头竟然准确的说出了我的昵称:上海小胖. 我想这事情不简单,就回了句:您好,我是小胖,请问您是? "我就是刚刚加了你微信的 xx ...

  8. P2P小贷网站业务数据流程分享

    P2P小贷网站业务数据流程分享 引言 这是去年年底开发的一个项目,完成后和用户的衔接没有很好的做起来,所以项目就搁浅了.9月以来,看各路P2P风声水起,很是热闹:这里分享下我的设计文档,算是抛砖引玉, ...

  9. 基于netcore实现mongodb和ElasticSearch之间的数据实时同步的工具(Mongo2Es)

    基于netcore实现mongodb和ElasticSearch之间的数据实时同步的工具 支持一对一,一对多,多对一和多对多的数据传输方式. 一对一 - 一个mongodb的collection对应一 ...

随机推荐

  1. 循环中else的用法

    name = 'hello' for x in name: print(x) if x == 'l': break #退出for循环 else: print("==for循环过程中,如果没有 ...

  2. Django REST framework+Vue 打造生鲜超市(十三)

    目录 生鲜超市(一)    生鲜超市(二)    生鲜超市(三) 生鲜超市(四)    生鲜超市(五)    生鲜超市(六) 生鲜超市(七)    生鲜超市(八)    生鲜超市(九) 生鲜超市(十) ...

  3. SQL Server 表的管理_关于表的操作增删查改的操作的详解(案例代码)

    SQL Server 表的管理_关于表的操作增删查改的操作的详解(案例代码) 概述: 表由行和列组成,每个表都必须有个表名. SQL CREATE TABLE 语法 CREATE TABLE tabl ...

  4. SpringMVC中Json数据格式转换

    1    @RequestBody 作用: @RequestBody注解用于读取http请求的内容(字符串),通过springmvc提供的HttpMessageConverter接口将读到的内容转换为 ...

  5. SVN 使用方法

    svn co http://路径(目录或文件的全路径) [本地目录全路径] --username 用户名 --password 密码svn co svn://路径(目录或文件的全路径) [本地目录全路 ...

  6. Android/Linux Thermal Governor之IPA分析与使用

    IPA(Intelligent Power Allocator)模型的核心是利用PID控制器,Thermal Zone的温度作为输入,可分配功耗值作为输出,调节Allocator的频率和电压值. 由P ...

  7. MySQL大量数据入库的性能比较

    单位IM改版了用户聊天内容要存放在数据库. 一般JAVA Insert MySQL有如下几种方式1.自动提交Insert2.事务提交Insert3.批量提交4.使用Load File接口 模拟表结构如 ...

  8. WinForm中DataGridView对XML文件的读取

    转自http://www.cnblogs.com/a1656344531/archive/2012/11/28/2792863.html c#读取XML   XML文件是一种常用的文件格式,例如Win ...

  9. jQuery.on() 函数详解 【转载】

    注意事项 1:on()为指定元素的一个或多个事件绑定事件处理函数.(可传递参数) 2:从jQuery 1.7开始,on()函数提供了绑定事件处理程序所需的所有功能,用于统一取代以前的bind(). d ...

  10. ftp研究

    工作中经常用到ftp,最近闲下心来,仔细研究下ftp这个协议. FTP(文件传输协议)工作原理 目前在网络上,如果你想把文件和其他人共享.最方便的办法莫过于将文件放FTP服务器上,然后其他人通过FTP ...