package com.taobao.yugong.positioner; import java.io.File; import java.io.IOException; import java.nio.charset.Charset; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import org.apache.commons.io.FileUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.util.Assert; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.serializer.SerializerFeature; import com.taobao.yugong.common.model.position.Position; import com.taobao.yugong.exception.YuGongException; /** * 基于文件刷新的position实现 * * <pre> * 策略: * 1. 先写内存,然后定时刷新数据到File * 2. 数据采取overwrite模式(只保留最后一次) * </pre> * * @author agapple 2013-9-22 下午3:37:13 */ public class FileMixedRecordPositioner extends MemoryRecordPositioner implements RecordPositioner { private static final Logger logger = LoggerFactory.getLogger(FileMixedRecordPositioner.class); private static final Charset charset = Charset.forName("UTF-8"); private File dataDir; private String dataFileName = "position.dat"; private File dataFile; private ScheduledExecutorService executor; private long period = 100; // 单位ms private AtomicBoolean needFlush = new AtomicBoolean(false); private AtomicBoolean needReload = new AtomicBoolean(true); public void start() { super.start(); Assert.notNull(dataDir); if (!dataDir.exists()) { try { FileUtils.forceMkdir(dataDir); } catch (IOException e) { throw new YuGongException(e); } } if (!dataDir.canRead() || !dataDir.canWrite()) { throw new YuGongException("dir[" + dataDir.getPath() + "] can not read/write"); } dataFile = new File(dataDir, dataFileName); executor = Executors.newScheduledThreadPool(1); // 启动定时工作任务 executor.scheduleAtFixedRate(new Runnable() { public void run() { try { // 定时将内存中的最新值刷到file中,多次变更只刷一次 if (needFlush.compareAndSet(true, false)) { flushDataToFile(dataFile, getLatest()); } } catch (Throwable e) { // ignore logger.error("period update position failed!", e); } } }, period, period, TimeUnit.MILLISECONDS); } public void stop() { super.stop(); flushDataToFile(dataFile, super.getLatest()); executor.shutdownNow(); } public void persist(Position position) { needFlush.set(true); super.persist(position); } public Position getLatest() { if (needReload.compareAndSet(true, false)) { Position position = loadDataFromFile(dataFile); super.persist(position); return position; } else { return super.getLatest(); } } // ============================ helper method ====================== private void flushDataToFile(File dataFile, Position position) { if (position != null) { String json = JSON.toJSONString(position, SerializerFeature.WriteClassName, SerializerFeature.WriteNullListAsEmpty); try { FileUtils.writeStringToFile(dataFile, json); } catch (IOException e) { throw new YuGongException(e); } } } private Position loadDataFromFile(File dataFile) { try { if (!dataFile.exists()) { return null; } String json = FileUtils.readFileToString(dataFile, charset.name()); return JSON.parseObject(json, Position.class); } catch (IOException e) { throw new YuGongException(e); } } public void setDataDir(File dataDir) { this.dataDir = dataDir; } public void setDataFileName(String dataFileName) { this.dataFileName = dataFileName; } public void setPeriod(long period) { this.period = period; } }