关于java异步的问题。
有一张邮件表,存储三个字段:
发送人,发送是否成功,发送内容
在spring mvc中有一个task,每5秒轮询一次这个表,查询出未发送成功的数据,并进行再次发送,发送成功后再更新表记录设置为发送成功。
这里存在一个问题,如果第一次轮询发现有未发送成功的邮件并进行发送,但是在发送的过程中开始了5秒后的第二次轮询,由于第一次没发送完所以表记录还是未发生成功,这时候会造成发送重复邮件。
请教有什么建议的解决方案吗?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(6)
我说一个方案吧:
再增加一个字段status字段标记当前邮件的处理状态,比如status=0 未处理,status=1代表正在处理,status=2表示处理完成。
每次轮询未发送的邮件WHERE status=0 AND isSend = false
开始发送邮件更新status=1
异步发送完成更新status=2
其实你这个问题的症结在于发送是否成功这个字段只能表达是/否两种场景
如果你使用的是0/5 ? 这种表达式,并且发送右键是同步的(这里理解为可以拿到发送的结果),即使发送邮件超时也是没关系的,因为一个5秒执行不完的时候,会等到下一个周期才开始执行。
例如最近五次的运行时间为
如果在2018/1/7 17:32:45秒的任务到了2018/1/7 17:32:50还没有执行完成,将不会再触发2018/1/7 17:32:50的任务,而是等到2018/1/7 17:32:55的时候才会继续执行。
看你的应用架构:
如果是单应用(如一个tomcat),那就简单啦而且方法很多,总的来说是全部协作在内存中处理啦:
第一种方法:
1.1 使用同步的集合保存正在处理的数据(数据结构自己选),集合有list, set, map,同步的话,可以自己实现synchronized或使用ConcurrentHashMap等同步的集合。
1.2 每次你的task去处理任务时,从获取数据库时排除掉同步集合里的数据。怕遇到并发的问题的话,可以采用二次校验,从数据库拿出来后,判断同步集合里是否已经存在此数据了
1.3 task处理完数据后,就更新回去。
第二种方法:
2.1 使用队列,如LinkedBlockingDeque,每次task从数据库拿出数据(不更新数据库数据),放入队列中,然后由一个线程池里的单线程来处理队列中的数据,由这单线程来更新数据。
如果是集群分布式的,可采用以下方法,总的来说是利用中间件来进行同步(锁定):
第一种方法:
task拿出数据后,将数据库中对应的数据更新为中间状态(如正在处理),然后进行处理该数据。当下次task去处理任务时,从获取数据库时排除掉状态为中间状态(正在处理)的数据。其实这个方法也有可能遇到并发问题,可以使用乐观锁的形式来处理数据,将数据更新为中间状态时,采用乐观锁,保证数据未被改变过。
第二种方法(类似上述单应用的第一种方法,只不过此次不是使用本地的内存,而是使用redis的内存)
task拿出数据后(不更新数据库数据),将此数据放入redis中,然后进行处理该数据。当下次task去处理任务时,从获取数据库时排除掉redis里的数据,然后再处理数据。
印象中java里是有同步锁来解决多线程的安全问题的,你可以百度一下
首先安排5s轮询并不合理,队列大小不确定,timtout不确定,线程池不确定。所以执行一次发送话费的时间不确定。
个人觉得理想的情况是每一次轮询执行完成后安排一个计划任务(前提是自己邮件服务器执行发送,如果第三方服务发送邮件也是异步的,可能不能获取邮件是否发送成功),这样不会存在冲突。
单独轮询,然后添加到队列的话,重复不可避免的。比如说在轮询过程中,有一个邮件发送成功了,而在状态更新到数据库之前已经查询了状态,这就不可避免要重复了。这种情况要避免的话,要增加中间环节,增加了程序复杂度,而且也不能保证百分百。