笔记(上):mysql-DuplicateUpdate和java的threadpool的“死锁“
创始人
2024-03-15 14:43:19
0

今天给大家讲讲最近2个有意思的issue,分享一下我学到的

  • mysql DuplicateUpdate的用法要注意的点
  • java的threadpool使用不当会造成“死锁”问题

mysql DuplicateUpdate的用法要注意的点

有个issue说遇到了一个这样的问题,

这个朋友使用我开源的job调度框架 https://github.com/yuzd/Hangfire.HttpJob

存储用的是mysql,采用的实现是 https://github.com/arnoldasgudas/Hangfire.MySqlStorage

set表的id是自增主键,正常理解 都是慢慢自增上去的,但是发现是大幅度跳跃式的自增, 真相是什么?

首先针对这个问题,首先我们搞清楚在hangfire中和storage相关的部分如下

 

image

  • hangfire server调度依赖storage
  • storage抽象出来一层api(解耦)
  • 第三方扩展(不关心具体的storage实现)
  • 不同的storage具体实现(比如mysql,sqlserver等)

Hangfire.Httpjob其实只是依赖了storage api那一层,也没有能力去直接写sql去执行, 只能用api去操作hangfire的那几张表(比如set表)

那么问题肯定不是在扩展层,而是得去看看mysqlstorage的实现源码,针对set表的处理逻辑

https://github.com/arnoldasgudas/Hangfire.MySqlStorage/blob/0bd1016f715c8c6617ce22fb7b2ce5b6c328d2fb/Hangfire.MySql/MySqlWriteOnlyTransaction.cs#L155

public override void AddToSet(string key, string value, double score){Logger.TraceFormat("AddToSet key={0} value={1}", key, value);AcquireSetLock();QueueCommand(x => x.Execute($"INSERT INTO `{_storageOptions.TablesPrefix}Set` (`Key`, `Value`, `Score`) " +"VALUES (@Key, @Value, @Score) " +"ON DUPLICATE KEY UPDATE `Score` = @Score",new { key, value, score }));}

这里是用了ON DUPLICATE KEY UPDATE 的语句

这个语法是在mysql 4.1(2005)引入的,意思是 insert的时候遇到主键已存在 就执行后面 的update

但是就是这个功能 会造成自增主键成跳跃式增长,增长跨度和SQL的执行次数成正比

根据朋友提供的截图

 

image

虽说是会跳跃,但是这个增长也太夸张了

打上断点调试发现

是hangfire server 不断的在调用,目的是把下一次执行时间(秒级别的时间戳)写到set表中

 

image 

image 

image

打上日志可以看到有非常多相同值的调用,这仅仅是一个job,这个自增速度得再乘以job的个数,难怪了

既然找到原因了,就提个PR 修改下

public override void AddToSet(string key, string value, double score){Logger.TraceFormat("AddToSet key={0} value={1}", key, value);AcquireSetLock();QueueCommand(x =>{var sql = "";if (key == "recurring-jobs") // 只发现这个key存在这个问题{// key+value是uniq 改成先update 如果没有成功 再insertsql = $"UPDATE `{_storageOptions.TablesPrefix}Set` set `Score` = @score where `Key` = @key and `Value` = @value";var updateRt = x.Execute(sql, new { score = score, key = key, value = value });if (updateRt < 1){sql = $"INSERT INTO `{_storageOptions.TablesPrefix}Set` (`Key`, `Value`, `Score`) " +"VALUES (@Key, @Value, @Score) ";x.Execute(sql,new { key, value, score });}}else{sql = $"INSERT INTO `{_storageOptions.TablesPrefix}Set` (`Key`, `Value`, `Score`) " +"VALUES (@Key, @Value, @Score) " +"ON DUPLICATE KEY UPDATE `Score` = @Score";x.Execute(sql,new { key, value, score });}//Console.WriteLine(sql + " ==> " + key + "@" + value + "@" + score);});}

改完之后测试,id自增一切正常:

 

image

注意上面演示的mysql存储是用的官方推荐的,

但是但是建议使用mysql作为存储的使用

 https://github.com/MiloszKrajewski/Hangfire.Storage.MySql

官方推荐的版本有死锁的bug,有主键自增膨胀(归根到底还是没有控制好锁) 参考issue:

  • https://github.com/arnoldasgudas/Hangfire.MySqlStorage/issues/63
  • https://github.com/arnoldasgudas/Hangfire.MySqlStorage/pull/97

java的threadpool使用不当会造成“死锁”问题

 

 image

这个原因先说出来: threadpool的线程被占用完后,再来的task会往queue里面丢,如果这个时候在这个pool的线程里面 future.get()的话会导致task runner(执行器)被堵住,没人从队列里面取任务了~

(简单来说就是 线程在wait future返回,而这个future在queue里面苦苦等待新释放的线程去执行,就像死锁一样,我在等你的结果,而结果在等待着被执行)

好家伙,这个场景有点熟悉,因为我在项目中也用过Future.get()// 虽说有设置timeout

但是这个问题的重要一点是,这种花式“死锁” jvm是检测不出来的,下面有测试

相关内容

热门资讯

招商证券:已审议通过市值管理制... 有投资者在互动平台向招商证券提问:“为什么股价一直承压,有促进公司股价回升的计划吗?后续改革是否有计...
中缅泰联合清剿!952名涉电诈... 中缅泰联合开展清剿缅甸妙瓦底地区 赌诈园区行动 952名缅甸妙瓦底地区涉电诈犯罪嫌疑人 被押解回国 ...
鑫铂股份:正在研究市值管理相关... 证券之星消息,鑫铂股份(003038)12月26日在投资者关系平台上答复投资者关心的问题。 投资者提...
敦化市黑石乡卫生院开展医保缴费... 为进一步优化医保服务,提升辖区居民对医保政策的知晓率,12月24日,敦化市黑石乡卫生院组织医保工作人...
八道沟人民法庭:调解继承纠纷,... 八道沟人民法庭扎根基层沃土,以新时代枫桥式人民法庭的宝贵经验为指引,积极践行马锡武式“就地办案、司法...
【科技部:开展创新积分制政策实... 【科技部:开展创新积分制政策实施“揭榜挂帅”】科技部办公厅发布关于开展创新积分制“揭榜挂帅”的通知。...
明阳电气:已制定《市值管理制度... 证券之星消息,明阳电气(301291)12月26日在投资者关系平台上答复投资者关心的问题。 投资者提...
长白法院秉持高效便民理念,以柔... 长白县人民法院诉讼服务中心紧跟时代步伐,扎实做好先行调解工作,牢牢筑起维护社会和谐稳定的前沿防线。该...
安徽含山:政策上门零距离 惠农... 人民网记者 李希蒙 “我都快八十了,从没生过病,每年花400块买医保钱图个啥?”在“联通惠民 合作兴...