一: 问题提出

  现如今大家写的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. js 写21点

    ======================================= var count = 0; function cc(card) {// Only change code below ...

  2. Oracle-12:伪列rowid和rownum

    ------------吾亦无他,唯手熟尔,谦卑若愚,好学若饥------------- 伪列:不真实存储在真表中,但是我们可以查询到不能对伪列进行增删改操作! 分页可以用rownum来分!!!!!! ...

  3. Linux时间子系统之七:定时器的应用--msleep(),hrtimer_nanosleep()

    我们已经在前面几章介绍了低分辨率定时器和高精度定时器的实现原理,内核为了方便其它子系统,在时间子系统中提供了一些用于延时或调度的API,例如msleep,hrtimer_nanosleep等等,这些A ...

  4. When to use next() and return next() in Node.js

    Some people always write return next() is to ensure that the execution stops after triggering the ca ...

  5. django(权限、认证)系统——User模型

    在Django的世界中,在权限管理中有内置的Authentication系统.用来管理帐户,组,和许可.还有基于cookie的用户session.这篇blog主要用来探讨这套内置的Authentica ...

  6. distinct的用法

    1.  Distinct 位置 单独的distinct只能放在开头,否则报错,语法错误 例:SELECT Sid,DISTINCT(Sscore) score from t_student; [SQL ...

  7. Hibernate用注解生成表

    User.java实体来 package com.tao.pojo; import javax.persistence.Column; //用注解的方式生成表 import javax.persist ...

  8. 【codeforces 516B】Drazil and Tiles

    题目链接: http://codeforces.com/problemset/problem/516/B 题解: 首先可以得到一个以‘.’为点的无向图,当存在一个点没有边时,无解.然后如果这个图边双联 ...

  9. bzoj 3239 poj 2417 BSGS

    BSGS算法,预处理出ϕ(c)−−−−√内的a的幂,每次再一块一块的往上找,转移时将b乘上逆元,哈希表里O(1)查询即可 #include<cstdio> #include<cstr ...

  10. 硬木地板 JDFZ1667

    Description 举行计算机科学家盛宴的大厅的地板为M×N (1<=M<=9, 1<=N<=9)的矩形.现在必须要铺上硬木地板砖.可以使用的地板砖形状有两种:1) 2×1 ...