spring+redis+mysql定时重发
产品需求:发布信息时候,用户可以选择重新发布时间,重新发布间隔时间以及次数,如重发6次时间间隔为2分钟。
实现设计:
可以通过定时查询数据库的发布时间,以及信息中重发次数时间,update数据(或者直接写一个厉害的update语句也可以),但是定时的查询整个数据库表,而且中间还会涉及到运算,无法命中索引,会使数据库压力较大,咨询好多朋友,大多数建议降低数据查询次数,如半个小时update一次,但是这样时间误差会很大,产品无法接受。
在通过仔细调研以之后,计算使用spring定时任务、redis中list结构、update数据实现。当用户发布信息的时候,依据次数生成多个序列的key,并将信息id存储到redis中(如果多个信息,在同一个序列中,直接在list尾部添加数据),spring通过定时任务,每隔10秒依据生成的序列,读取redis中list数据,将list中数据,转化为id1,id2,id3,通过数据库in关键字,实现更新数据。
关键代码:
spring定时任务配置
<bean id="republishCargoJobInvake" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean"> <property name="targetObject" ref="republishCargoJob" /> <property name="targetMethod" value="doRepublish" /> <property name="concurrent" value="false" /> </bean> <bean id="triggerRepublishCargo" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean"> <property name="jobDetail" ref="republishCargoJobInvake" /> <property name="cronExpression" value="0/10 * * * * ?" /> </bean> <bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean"> <property name="triggers"> <list> <ref bean="triggerRepublishCargo" /> </list> </property> </bean>
public class RepublishCargoJob { private Log log = LogFactory.getLog(RepublishCargoJob.class); /** * 货源重发定时任务 */ public void doRepublish(){ //获取WebApplicationContext WebApplicationContext wac = ContextLoader.getCurrentWebApplicationContext(); //获取redis读取序列工具 TimedPublishTask timedPublishTask=(TimedPublishTask) wac.getBean("timedPublishTask"); //获取本次需要更新的id list List<String>list=timedPublishTask.getBatch(); if(list!=null&&list.size()>0){ //update数据库 ResourceCargoInfoDAO resourceCargoInfoDAO=(ResourceCargoInfoDAO) wac.getBean("resourceCargoInfoDAO"); ResourceCargoInfo rci=new ResourceCargoInfo(); rci.setRciPublishDatetime(DateUtils.getDateTime()); ResourceCargoInfoExample resourceCargoInfoExample=new ResourceCargoInfoExample(); resourceCargoInfoExample.createCriteria().andRciIdIn(list).andRciAvalibleStatusEqualTo(1); try { resourceCargoInfoDAO.updateByExampleSelective(rci, resourceCargoInfoExample); } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); log.error("=======重发货源失败======"); } } }
package com.ada.wuliu.common.redis.timertask; import java.util.ArrayList; import java.util.List; import javax.annotation.Resource; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.connection.jedis.JedisConnection; import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; import org.springframework.stereotype.Component; import org.springframework.web.bind.annotation.InitBinder; import com.ada.wuliu.common.dictionaries.utils.DateUtils; import com.ada.wuliu.common.dictionaries.utils.StateCode; import com.ada.wuliu.common.dto.result.SimpleResult; import com.ada.wuliu.common.redis.dao.RedisList; import com.ada.wuliu.common.redis.dao.RedisString; import com.ada.wuliu.common.redis.dto.TimerPublish; import com.ada.wuliu.common.redis.util.SessionImpl_Temp_01; import com.ada.wuliu.common.redis.util.StringUtil; @Component("timedPublishTask") public class TimedPublishTask { //定时任务执行周期 public Integer taskFrequency=10000; //默认任务开始\(^o^)/ private String defaultIncr="1000"; private static String TIMER_PUBLISH_KEY_TODO=null; @Autowired private RedisList redisList; @Autowired private RedisString redisString; private final static Log log = LogFactory.getLog(TimedPublishTask.class); @Resource(name="connectionFactory") private JedisConnectionFactory connectionFactory; public TimedPublishTask(){ initKeyHeader(); } private void initKeyHeader(){ if(TIMER_PUBLISH_KEY_TODO==null){ TIMER_PUBLISH_KEY_TODO="TIMER_PUBLISH_KEY_TODO_CYC_"+StringUtil.getLocalOsName(); } } /** * * @param timerPublish 需要重新发布的对象 */ public SimpleResult addBatch(TimerPublish timerPublish){ JedisConnection connection = connectionFactory.getConnection(); SimpleResult cr = null; try { //获取最新的批次号 String batchNo= redisString.get(TIMER_PUBLISH_KEY_TODO, connection); if(batchNo==null){ batchNo=redisString.incr(TIMER_PUBLISH_KEY_TODO, connection).toString(); } //刷新频率 Integer frequency=timerPublish.getFrequency()/10; //刷新次数 Integer number=timerPublish.getNumber(); //对象key Long key=timerPublish.getKey(); List<Long>list=new ArrayList<Long>(); for(int i=1;i<number+1;i++){ //生成添加的批次号 long addBatchNo=i*frequency/taskFrequency+Long.parseLong(batchNo); addBatchNo=addBatchNo-1; String batchKey=TIMER_PUBLISH_KEY_TODO+addBatchNo; //list 第一个为更新时间 如果更新时间<当前时间10秒则updatelist索引key对应数据 log.info("新添加批次号:======"+batchKey+"====="+key); List<String> ll=redisList.get(batchKey, 1, 2, connection); if(ll==null||ll.size()==0){ redisList.lPush(batchKey, (System.currentTimeMillis()+i*frequency)+"", connection); } redisList.rPush(batchKey, key+"", connection); } return new SimpleResult("成功", StateCode.SUCCESS, StateCode.SUCCESS, true); } catch (Exception e) { log.error("-----TimedPublishTask::addBatch Throwable Error! -----", e); return new SimpleResult("服务器错误", StateCode.SERVER_ERROR, null, false); } catch (Throwable th) { log.error("-----TimedPublishTask::addBatch Throwable Error! -----", th); return new SimpleResult("服务器错误", StateCode.SERVER_ERROR, null, false); } finally { connection.close(); } } /** * * @param batchNo 获取本批次需要更新的对象的key * @return */ public List<String> getBatch(){ JedisConnection connection = connectionFactory.getConnection(); SimpleResult cr = null; try { //获取当前需要更新的批次号 String batchNo= redisString.get(TIMER_PUBLISH_KEY_TODO, connection); //如果没有批次则初始化批次 if(batchNo==null){ batchNo=redisString.incr(TIMER_PUBLISH_KEY_TODO, connection).toString(); } List<String>list=redisList.getAll(TIMER_PUBLISH_KEY_TODO+batchNo, connection); log.info("当前任务"+TIMER_PUBLISH_KEY_TODO+batchNo); //出现掉任务处理 if(list!=null&&list.size()>0){ Long addTime=Long.parseLong(list.get(0)); Long nowTime=System.currentTimeMillis(); if(nowTime-addTime>2*taskFrequency){ log.info("发现队列冗余数据,开始尝试执行========="); //获取偏移量 long offset=(nowTime-addTime)/taskFrequency+1; log.info("偏移量======"+offset); Integer batchNoRe=Integer.parseInt(batchNo); int i=1; if(offset<50){ i=1;} else{i=(int) (offset-50L);} for(;i<offset;i++){ batchNoRe++; log.info("添加任务"+TIMER_PUBLISH_KEY_TODO+batchNoRe); List<String>listre=redisList.getAll(TIMER_PUBLISH_KEY_TODO+batchNoRe, connection); if(listre.size()>0) listre.remove(0); redisString.del(TIMER_PUBLISH_KEY_TODO+batchNoRe, connection); redisString.incr(TIMER_PUBLISH_KEY_TODO, connection); //redisString.incr(TIMER_PUBLISH_KEY_TODO, connection); list.addAll(listre); } } } if(list.size()>0) list.remove(0); log.info("执行批次==========="+TIMER_PUBLISH_KEY_TODO+batchNo+"======VALUES====="+getListStr(list)); redisString.incr(TIMER_PUBLISH_KEY_TODO, connection); redisString.del(TIMER_PUBLISH_KEY_TODO+batchNo, connection); return list; } catch (Exception e) { e.printStackTrace(); log.error("-----TimedPublishTask::getBatch Throwable Error! -----", e); return null; } catch (Throwable th) { log.error("-----TimedPublishTask::getBatch Throwable Error! -----", th); return null; } finally { connection.close(); } } public String getListStr(List<String>list){ if(list==null)return null; StringBuffer sb=new StringBuffer(); for(String s:list){ sb.append(s+","); } return sb.toString(); } }
初始化批次15秒执行一次,需要执行10次
新添加批次号:======TIMER_PUBLISH_KEY_TODO_KTSDKLHL1=====114102091
新添加批次号:======TIMER_PUBLISH_KEY_TODO_KTSDKLHL3=====114102091
新添加批次号:======TIMER_PUBLISH_KEY_TODO_KTSDKLHL4=====114102091
新添加批次号:======TIMER_PUBLISH_KEY_TODO_KTSDKLHL6=====114102091
新添加批次号:======TIMER_PUBLISH_KEY_TODO_KTSDKLHL7=====114102091
新添加批次号:======TIMER_PUBLISH_KEY_TODO_KTSDKLHL9=====114102091
新添加批次号:======TIMER_PUBLISH_KEY_TODO_KTSDKLHL10=====114102091
新添加批次号:======TIMER_PUBLISH_KEY_TODO_KTSDKLHL12=====114102091
新添加批次号:======TIMER_PUBLISH_KEY_TODO_KTSDKLHL13=====114102091
新添加批次号:======TIMER_PUBLISH_KEY_TODO_KTSDKLHL15=====114102091
初始化批次15秒执行一次,需要执行10次
新添加批次号:======TIMER_PUBLISH_KEY_TODO_KTSDKLHL1=====114102095
新添加批次号:======TIMER_PUBLISH_KEY_TODO_KTSDKLHL3=====114102095
新添加批次号:======TIMER_PUBLISH_KEY_TODO_KTSDKLHL4=====114102095
新添加批次号:======TIMER_PUBLISH_KEY_TODO_KTSDKLHL6=====114102095
新添加批次号:======TIMER_PUBLISH_KEY_TODO_KTSDKLHL7=====114102095
新添加批次号:======TIMER_PUBLISH_KEY_TODO_KTSDKLHL9=====114102095
新添加批次号:======TIMER_PUBLISH_KEY_TODO_KTSDKLHL10=====114102095
新添加批次号:======TIMER_PUBLISH_KEY_TODO_KTSDKLHL12=====114102095
新添加批次号:======TIMER_PUBLISH_KEY_TODO_KTSDKLHL13=====114102095
新添加批次号:======TIMER_PUBLISH_KEY_TODO_KTSDKLHL15=====114102095
当前任务TIMER_PUBLISH_KEY_TODO_KTSDKLHLSK1
发现队列冗余数据,开始尝试执行=========
偏移量======5
添加任务TIMER_PUBLISH_KEY_TODO_KTSDKLHLSK2
添加任务TIMER_PUBLISH_KEY_TODO_KTSDKLHLSK3
添加任务TIMER_PUBLISH_KEY_TODO_KTSDKLHLSK4
添加任务TIMER_PUBLISH_KEY_TODO_KTSDKLHLSK5
执行批次===========TIMER_PUBLISH_KEY_TODO_KTSDKLHLSK6======VALUES=====114102091,114102095,
执行批次===========TIMER_PUBLISH_KEY_TODO_KTSDKLHLSK6======VALUES=====114102091,114102095,