//ICBC中賬戶card001轉出300元
@Test
public void tranfer(){
EventLog eventLog = new EventLog();
eventLog.setAmount(new BigDecimal(300));
eventLog.setFromcard("card001");
eventLog.setTocard("card002");
eventLog.setEventstate(EventState.NEW);
eventLog.setTransferDate(new Date());
eventLogService.transfer(eventLog,new BigDecimal(300));
}
/**
* 在排它鎖場景下數據更新,保證數據的可靠性
*/
@Override
public void updateEventstateById(String id, EventState eventState) {
EventLog eventLog=findEventLog4Update(id);
eventLog.setEventstate(eventState);
emJ1.merge(eventLog);
}
/**
* 實現排它鎖查詢
*/
@Override
public EventLog findEventLog4Update(String id){
EventLog eventLog=emJ1.find(EventLog.class, id, LockModeType.PESSIMISTIC_WRITE);
return eventLog;
}
@Service
public class EventLogService {
@Autowired
private EventLogRepository eventLogRepository;
@Resource(name="jmsQueueMessagingTemplate")
private JmsMessagingTemplate jmsQueueMessagingTemplate;
@Autowired
@Qualifier("icbc2boc")
private Queue icbc2boc;
....
/**
* 根據eventstate獲取EventLog數據集
* @param eventstate
* @return
*/
@Transactional(transactionManager="transactionManager1",propagation=Propagation.SUPPORTS,readOnly=true)
public List<EventLog> findByEventState(EventState eventstate){
return eventLogRepository.findByEventstate(eventstate);
}
/**
* XA事務
* @param id
* @param eventstate
*/
@Transactional(transactionManager="transactionManagerJTA",propagation=Propagation.REQUIRES_NEW)
public void transferToMQ(EventLog eventLog,EventState eventstate,CountDownLatch countDownLatch){
try {
System.out.println(Thread.currentThread().getName()+"本次處理數據:"+eventLog.getFromcard()+"、"+eventLog.getEventstate());
//再次數據庫查詢判斷,此時用到排它鎖--在兩個定時任務連續執行,一旦出現程序提交事務命令至數據庫,
//但數據庫還未執行,此時我們全表查詢的結果中當前數據行仍爲修改前數據,故會造成重複消費
eventLog=eventLogRepository.findEventLog4Update(eventLog.getId());
if(EventState.Publish.equals(eventLog.getEventstate())){
System.out.println(Thread.currentThread().getName()+"數據:"+eventLog.getFromcard()+"無需處理");
return;
}
//payload
jmsQueueMessagingTemplate.convertAndSend(icbc2boc,eventLog);
eventLogRepository.updateEventstateById(eventLog.getId(), eventstate);
//構造異常場景驗證XA事務
if(eventLog.getFromcard()==null){
System.out.println(Thread.currentThread().getName()+"數據異常,不處理");
System.out.println(1/0);
}else{
System.out.println(Thread.currentThread().getName()+":"+eventLog.getFromcard()+"數據處理成功");
}
} finally {
countDownLatch.countDown();
}
}
}
/**
* 轉出任務
* @author song
*/
@PersistJobDataAfterExecution
@DisallowConcurrentExecution //保證每次任務執行完畢,設置爲串行執行
public class TransferJob extends QuartzJobBean {
private Logger logger=LoggerFactory.getLogger(TransferJob.class);
@Autowired
@Qualifier("quartzThreadPool")
private ThreadPoolTaskExecutor quartzThreadPool;
@Autowired
private EventLogService eventLogService;
@Override
protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
logger.info("本次批處理開始");
//獲取所有未發送狀態的Event
List<EventLog> list=eventLogService.findByEventState(EventState.NEW);
//
final CountDownLatch countDownLatch=new CountDownLatch(list.size());
//遍歷發送
for(final EventLog eventLog:list){
//通過線程池提交任務執行,大大提高處理集合效率
quartzThreadPool.submit(new Runnable() {
@Override
public void run() {
eventLogService.transferToMQ(eventLog,EventState.Publish,countDownLatch);
}
});
}
//保證所有線程執行完成後退出
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
logger.info("本次批處理完成");
}
}
@Bean(name="tranferJob")
public JobDetailFactoryBean tranferJob(){
JobDetailFactoryBean factoryBean=new JobDetailFactoryBean();
//定義任務類
factoryBean.setJobClass(TransferJob.class);
//表示任務完成之後是否依然保留到數據庫,默認false
factoryBean.setDurability(true);
//爲Ture時當Quartz服務被中止後,再次啓動或集羣中其他機器接手任務時會嘗試恢復執行之前未完成的所有任務,默認false
factoryBean.setRequestsRecovery(true);
return factoryBean;
}
/**
* 註冊job1的觸發器
* @return
*/
@Bean(name="transferJobTrigger")
public CronTriggerFactoryBean transferJobTrigger(){
//觸發器
CronTriggerFactoryBean cronTriggerFactoryBean = new CronTriggerFactoryBean();
cronTriggerFactoryBean.setCronExpression("*/5 * * * * ?");
cronTriggerFactoryBean.setJobDetail(tranferJob().getObject());
//調度工廠實例化後,經過5秒開始執行調度
cronTriggerFactoryBean.setStartDelay(30000);
cronTriggerFactoryBean.setGroup("tranfer");
cronTriggerFactoryBean.setName("tranfer");
return cronTriggerFactoryBean;
}
/**
* 調度工廠,加載觸發器,並設置自動啓動、啓動時延
* @return
*/
@Bean(name="transferSchedulerFactoryBean")
public SchedulerFactoryBean transferSchedulerFactoryBean(){
//調度工廠
SchedulerFactoryBean schedulerFactoryBean= new SchedulerFactoryBean();
schedulerFactoryBean.setConfigLocation(new ClassPathResource("quartz.properties"));
schedulerFactoryBean.setApplicationContextSchedulerContextKey("applicationContextKey");
//集羣Cluster下設置dataSource
// schedulerFactoryBean.setDataSource(dataSource);
//QuartzScheduler啓動時更新己存在的Job,不用每次修改targetObject後刪除qrtz_job_details表對應記錄了
schedulerFactoryBean.setOverwriteExistingJobs(true);
//QuartzScheduler延時啓動20S,應用啓動完後 QuartzScheduler 再啓動
schedulerFactoryBean.setStartupDelay(20);
//自動啓動
schedulerFactoryBean.setAutoStartup(true);
schedulerFactoryBean.setTriggers(transferJobTrigger().getObject());
//自定義的JobFactory解決job中service的bean注入
schedulerFactoryBean.setJobFactory(jobFactory);
return schedulerFactoryBean;
}
/**
* 用於處理待轉賬數據發至消息隊列的線程池
* @return
*/
@Bean(name="quartzThreadPool")
public ThreadPoolTaskExecutor getThreadPoolTaskExecutor(){
ThreadPoolTaskExecutor pool=new ThreadPoolTaskExecutor();
pool.setCorePoolSize(10);
pool.setQueueCapacity(100);
pool.setMaxPoolSize(10);
pool.setKeepAliveSeconds(10);
//避免應用關閉,任務沒有執行完成,起到shutdownhook鉤子的作用
pool.setWaitForTasksToCompleteOnShutdown(true);
//空閒時核心線程也不退出
pool.setAllowCoreThreadTimeOut(false);
//設置拒絕策略,不可執行的任務將被拋棄
pool.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardPolicy());
return pool;
}
@Configuration
public class JmsMessageConfiguration {
@Autowired
@Qualifier(value="jmsQueueTemplate")
private JmsTemplate jmsQueueTemplate;
/**
* 定義點對點隊列
* @return
*/
@Bean(name="icbc2boc")
public Queue queue() {
return new ActiveMQQueue("icbc2boc");
}
/**
* 創建處理隊列消息模板
* @return
*/
@Bean(name="jmsQueueMessagingTemplate")
public JmsMessagingTemplate jmsQueueMessagingTemplate() {
JmsMessagingTemplate jmsMessagingTemplate =new JmsMessagingTemplate(jmsQueueTemplate);
//通過MappingJackson2MessageConverter實現Object轉換
jmsMessagingTemplate.setMessageConverter(new MappingJackson2MessageConverter());
return new JmsMessagingTemplate(jmsQueueTemplate);
}
}
@Component
public class TraferIn {
@Autowired
@Qualifier("icbc2boc")
private Queue queue;
@Autowired
@Qualifier("jmsQueueMessagingTemplate")
private JmsMessagingTemplate jmsQueueMessagingTemplate;
@Autowired
private EventLogService eventLogService;
/**
* 定義監聽轉賬事件監聽
* @param text
* @throws Exception
*/
@JmsListener(destination = "icbc2boc",containerFactory="jmsListenerContainerFactory4Queue")//ActiveMQ.DLQ
public void receiveQueue(EventLog eventLog) throws Exception {
System.out.println("接受到的事件數據:"+eventLog.toString());
eventLogService.mq2transfer(eventLog, new BigDecimal(300));
}
}
/**
* XA事務
* @param eventLog
* @param amount
*/
@Transactional(transactionManager="transactionManagerJTA",propagation=Propagation.REQUIRED)
public void mq2transfer(EventLog eventLog,BigDecimal amount){
//保存事件日誌
eventLogRepository.saveEvetLog(eventLog);
// System.out.println(1/0);
}
/**
* 採用分佈式事務數據源保存事件
*/
@Override
public EventLog saveEvetLog(EventLog eventLog) {
return emJ1.merge(eventLog);
}
/**
* 實現排它鎖
* @param id
* @return
*/
@Query(value="select t.id from t_card t where t.id=:id for update",nativeQuery=true)
void findCard4UpdateById(@Param("id")String id);
/**
* 更新EventLog狀態
* @param id
* @param eventstate
* @return
*/
@Modifying
@Query(value = "update EventLog e set e.eventstate=:eventstate where e.id = :id ")
int updateEventstateById(@Param("id")String id,@Param("eventstate")EventState eventstate);
/**
* 根據EventState查詢所有EventLog
* @param EventState
* @return
*/
List<EventLog> findByEventstate(EventState eventState);
/**
* 執行原生語句實現更新
*/
@Override
public int executeUpdateNativeSQL(String strSQL) {
return em1.createNativeQuery(strSQL, Integer.class).executeUpdate();
}
/**
* 本地事務
* @param id
* @param eventstate
*/
@Transactional(transactionManager="transactionManager1",propagation=Propagation.REQUIRES_NEW)
public void transfer(EventLog eventLog,EventState eventstate,CountDownLatch countDownLatch){
try {
System.out.println(Thread.currentThread().getName()+"本次處理數據轉入賬號:"+eventLog.getTocard()+"、"+eventLog.getEventstate());
//通過樂觀鎖方式再次判斷,保證事件的可靠消息,僅在極端情況下會出現重複消費,故採用樂觀鎖
int updateCount=eventLogRepository.updateEventstateById(eventLog.getId(),eventstate);
//如果等於則表明已經處理過
if(updateCount==0){
System.out.println(Thread.currentThread().getName()+"數據收款卡號:"+eventLog.getTocard()+"無需處理");
return;
}
//沒有被處理過需要繼續更新賬戶金額
//更新查詢,採用排它鎖方式,避免在多線程任務下,出現多個線程修改同一個卡號,從而事務冪等性
eventLogRepository.findCard4UpdateById(eventLog.getTocard());
//更新賬戶信息,轉入累加,屬於非冪等操作
eventLogRepository.executeUpdateNativeSQL("update t_card set amount=amount+"+eventLog.getAmount()+" where id='"+eventLog.getTocard()+"'");
// System.out.println(1/0);
System.out.println(Thread.currentThread().getName()+":"+eventLog.getFromcard()+"數據處理成功");
} finally {
countDownLatch.countDown();
}
}
@PersistJobDataAfterExecution
@DisallowConcurrentExecution //保證每次任務執行完畢,設置爲串行執行
public class TransferJob extends QuartzJobBean {
private Logger logger=LoggerFactory.getLogger(TransferJob.class);
@Autowired
@Qualifier("quartzThreadPool")
private ThreadPoolTaskExecutor quartzThreadPool;
@Autowired
private EventLogService eventLogService;
@Override
protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
logger.info("本次批處理開始");
//獲取所有未發送狀態的Event
List<EventLog> list=eventLogService.findByEventState(EventState.NEW);
//
final CountDownLatch countDownLatch=new CountDownLatch(list.size());
//遍歷發送
for(final EventLog eventLog:list){
//通過線程池提交任務執行,大大提高處理集合效率
quartzThreadPool.submit(new Runnable() {
@Override
public void run() {
eventLogService.transfer(eventLog, EventState.Publish, countDownLatch);
}
});
}
//保證所有線程執行完成後退出
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
logger.info("本次批處理完成");
}
}