package qunar.tc.qconfig.server.config.cache; import com.google.common.base.Optional; import com.google.common.base.Stopwatch; import com.google.common.collect.Maps; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; import qunar.tc.qconfig.server.dao.PushConfigVersionDao; import qunar.tc.qconfig.server.support.monitor.Monitor; import qunar.tc.qconfig.servercommon.bean.ConfigMeta; import qunar.tc.qconfig.servercommon.bean.PushConfigVersionItem; import javax.annotation.PostConstruct; import javax.annotation.Resource; import java.util.List; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; /** * @author zhenyu.nie created on 2018 2018/5/24 14:51 */ @Service public class CachePushConfigVersionServiceImpl implements CachePushConfigVersionService { private static final Logger logger = LoggerFactory.getLogger(CachePushConfigVersionServiceImpl.class); private volatile ConcurrentMap<Key, Version> cache = Maps.newConcurrentMap(); @Resource private PushConfigVersionDao pushConfigVersionDao; @PostConstruct public void init() { freshPushVersionCache(); ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(); scheduledExecutorService.scheduleWithFixedDelay(new Runnable() { @Override public void run() { Thread.currentThread().setName("fresh-push-version-thread"); try { freshPushVersionCache(); } catch (Throwable e) { logger.error("fresh push version error", e); } } }, 3, 3, TimeUnit.MINUTES); } private void freshPushVersionCache() { Stopwatch stopwatch = Stopwatch.createStarted(); try { logger.info("fresh push version cache"); List<PushConfigVersionItem> pushItems = pushConfigVersionDao.select(); ConcurrentMap<Key, Version> newCache = new ConcurrentHashMap<Key, Version>(pushItems.size()); for (PushConfigVersionItem pushItem : pushItems) { newCache.put(new Key(pushItem.getMeta(), pushItem.getIp()), new Version(pushItem.getVersion())); } this.cache = newCache; logger.info("fresh push version cache successOf, count [{}]", pushItems.size()); } finally { Monitor.freshPushVersionCache.update(stopwatch.elapsed().toMillis(), TimeUnit.MILLISECONDS); } } @Override public Optional<Long> getVersion(ConfigMeta meta, String ip) { Version version = cache.get(new Key(meta, ip)); return version != null ? Optional.of(version.get()) : Optional.<Long>absent(); } @Override public void update(PushConfigVersionItem item) { Key key = new Key(item.getMeta(), item.getIp()); long newVersion = item.getVersion(); Version old = cache.putIfAbsent(key, new Version(newVersion)); if (old != null) { old.update(newVersion); } } private static class Key { private final ConfigMeta meta; private final String ip; public Key(ConfigMeta meta, String ip) { this.meta = meta; this.ip = ip; } public ConfigMeta getMeta() { return meta; } public String getIp() { return ip; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Key key = (Key) o; if (meta != null ? !meta.equals(key.meta) : key.meta != null) return false; return ip != null ? ip.equals(key.ip) : key.ip == null; } @Override public int hashCode() { int result = meta != null ? meta.hashCode() : 0; result = 31 * result + (ip != null ? ip.hashCode() : 0); return result; } @Override public String toString() { return "Key{" + "meta=" + meta + ", ip='" + ip + '\'' + '}'; } } private static class Version { private volatile long version; public Version(long version) { this.version = version; } public void update(long newVersion) { if (newVersion > version) { synchronized (this) { if (newVersion > version) { version = newVersion; } } } } public long get() { return version; } } }