|  | @@ -1,301 +0,0 @@
 | 
	
		
			
				|  |  | -package com.tzld.piaoquan.recommend.server.framework.recaller.provider;
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -import com.google.common.base.Optional;
 | 
	
		
			
				|  |  | -import com.google.common.cache.CacheBuilder;
 | 
	
		
			
				|  |  | -import com.google.common.cache.CacheLoader;
 | 
	
		
			
				|  |  | -import com.google.common.cache.LoadingCache;
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -import com.tzld.piaoquan.recommend.server.framework.utils.FixedThreadPoolHelper;
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -import com.tzld.piaoquan.recommend.server.framework.utils.IndexUtils;
 | 
	
		
			
				|  |  | -import com.tzld.piaoquan.recommend.server.framework.utils.RedisSmartClient;
 | 
	
		
			
				|  |  | -import org.apache.commons.lang.exception.ExceptionUtils;
 | 
	
		
			
				|  |  | -import org.apache.commons.lang.math.RandomUtils;
 | 
	
		
			
				|  |  | -import org.slf4j.Logger;
 | 
	
		
			
				|  |  | -import org.slf4j.LoggerFactory;
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -import java.util.concurrent.BlockingQueue;
 | 
	
		
			
				|  |  | -import java.util.concurrent.ExecutionException;
 | 
	
		
			
				|  |  | -import java.util.concurrent.ExecutorService;
 | 
	
		
			
				|  |  | -import java.util.concurrent.Executors;
 | 
	
		
			
				|  |  | -import java.util.concurrent.LinkedBlockingQueue;
 | 
	
		
			
				|  |  | -import java.util.concurrent.ScheduledExecutorService;
 | 
	
		
			
				|  |  | -import java.util.concurrent.TimeUnit;
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -/**
 | 
	
		
			
				|  |  | - * Generalized item access to Redis.
 | 
	
		
			
				|  |  | - */
 | 
	
		
			
				|  |  | -public class ItemProvider<InMemoryItem> {
 | 
	
		
			
				|  |  | -    private static final Logger logger = LoggerFactory.getLogger(ItemProvider.class);
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -    private static final int REDIS_DEFAULT_TTL = 7 * 24 * 60 * 60;
 | 
	
		
			
				|  |  | -    private static final long CACHE_TIMEOUT_MS = 15 * 60 * 1000L;
 | 
	
		
			
				|  |  | -    private static final long CACHE_MAXIMUMSIZE = 80 * 10000; // default 80w
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -    private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
 | 
	
		
			
				|  |  | -    private final ExecutorService itemRefreshExecutorService = new FixedThreadPoolHelper(4, "itemRefresh").getThreadPoolExecutor();
 | 
	
		
			
				|  |  | -    // 不能为 static
 | 
	
		
			
				|  |  | -    private final BlockingQueue<String> asyncRefreshQueue = new LinkedBlockingQueue<String>(100000);
 | 
	
		
			
				|  |  | -    private final RedisSmartClient client;
 | 
	
		
			
				|  |  | -    private final String prefix;
 | 
	
		
			
				|  |  | -    private final LoadingCache<String, CacheEntry<InMemoryItem>> cache;
 | 
	
		
			
				|  |  | -    private final long cacheMaximumSize;
 | 
	
		
			
				|  |  | -    private int redisTtl = REDIS_DEFAULT_TTL;
 | 
	
		
			
				|  |  | -    private long cacheTimeout = CACHE_TIMEOUT_MS;
 | 
	
		
			
				|  |  | -    private long refreshDynamicInterval = CACHE_TIMEOUT_MS;
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -    /**
 | 
	
		
			
				|  |  | -     * 构造带有本地 cache 的 item provider, 使用redis作为底层存储,
 | 
	
		
			
				|  |  | -     * 本地cache基于 Guava Cache 构建, 配置失效时间与异步刷新时间;
 | 
	
		
			
				|  |  | -     *
 | 
	
		
			
				|  |  | -     * @param client         redis-cluster client
 | 
	
		
			
				|  |  | -     * @param prefix         业务标志
 | 
	
		
			
				|  |  | -     * @param cacheTimeOutMs 缓存失效时间
 | 
	
		
			
				|  |  | -     */
 | 
	
		
			
				|  |  | -    public ItemProvider(RedisSmartClient client,
 | 
	
		
			
				|  |  | -                        String prefix,
 | 
	
		
			
				|  |  | -                        long cacheTimeOutMs,
 | 
	
		
			
				|  |  | -                        long refreshDynamicInterval) {
 | 
	
		
			
				|  |  | -        this(client,
 | 
	
		
			
				|  |  | -                prefix,
 | 
	
		
			
				|  |  | -                cacheTimeOutMs,
 | 
	
		
			
				|  |  | -                refreshDynamicInterval,
 | 
	
		
			
				|  |  | -                CACHE_MAXIMUMSIZE,
 | 
	
		
			
				|  |  | -                REDIS_DEFAULT_TTL,
 | 
	
		
			
				|  |  | -                TimeUnit.SECONDS);
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -    /**
 | 
	
		
			
				|  |  | -     * 构造带有本地 cache 的 item provider, 使用redis作为底层存储,
 | 
	
		
			
				|  |  | -     * 本地cache基于 Guava Cache 构建, 配置失效时间与异步刷新时间;
 | 
	
		
			
				|  |  | -     *
 | 
	
		
			
				|  |  | -     * @param client         redis-cluster client
 | 
	
		
			
				|  |  | -     * @param prefix         业务标志
 | 
	
		
			
				|  |  | -     * @param cacheTimeOutMs 缓存失效时间
 | 
	
		
			
				|  |  | -     * @param redisKeyTtl
 | 
	
		
			
				|  |  | -     * @param redisTtlUnit
 | 
	
		
			
				|  |  | -     */
 | 
	
		
			
				|  |  | -    public ItemProvider(RedisSmartClient client,
 | 
	
		
			
				|  |  | -                        String prefix,
 | 
	
		
			
				|  |  | -                        long cacheTimeOutMs,
 | 
	
		
			
				|  |  | -                        long refreshDynamicInterval,
 | 
	
		
			
				|  |  | -                        final long cacheMaximumSize,
 | 
	
		
			
				|  |  | -                        long redisKeyTtl,
 | 
	
		
			
				|  |  | -                        TimeUnit redisTtlUnit) {
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        this.cacheTimeout = cacheTimeOutMs;
 | 
	
		
			
				|  |  | -        this.refreshDynamicInterval = refreshDynamicInterval;
 | 
	
		
			
				|  |  | -        this.redisTtl = (int) redisTtlUnit.toSeconds(redisKeyTtl);
 | 
	
		
			
				|  |  | -        this.cacheMaximumSize = cacheMaximumSize;
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        this.client = client;
 | 
	
		
			
				|  |  | -        this.prefix = prefix;
 | 
	
		
			
				|  |  | -        this.cache = CacheBuilder.newBuilder()
 | 
	
		
			
				|  |  | -                .expireAfterWrite((long) (this.cacheTimeout * 1.4), TimeUnit.MILLISECONDS)
 | 
	
		
			
				|  |  | -                .maximumSize(cacheMaximumSize)
 | 
	
		
			
				|  |  | -                .build(new CacheLoader<String, CacheEntry<InMemoryItem>>() {
 | 
	
		
			
				|  |  | -                    @Override
 | 
	
		
			
				|  |  | -                    public CacheEntry<InMemoryItem> load(String key) throws Exception {
 | 
	
		
			
				|  |  | -                        if (key == null) {
 | 
	
		
			
				|  |  | -                            return null;
 | 
	
		
			
				|  |  | -                        } else {
 | 
	
		
			
				|  |  | -                            return loadItem(key);
 | 
	
		
			
				|  |  | -                        }
 | 
	
		
			
				|  |  | -                    }
 | 
	
		
			
				|  |  | -                });
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        // cache refresh
 | 
	
		
			
				|  |  | -        for (int i = 0; i < 4; i++) {
 | 
	
		
			
				|  |  | -            itemRefreshExecutorService.execute(new AsyncRefreshCacheRunnable());
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -    public ItemProvider(RedisSmartClient client, String prefix) {
 | 
	
		
			
				|  |  | -        this(client, prefix, CACHE_TIMEOUT_MS, CACHE_TIMEOUT_MS);
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -    public RedisSmartClient getClient() {
 | 
	
		
			
				|  |  | -        return client;
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -    public InMemoryItem deserialize(byte[] bytes){
 | 
	
		
			
				|  |  | -        return null;
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -    /**
 | 
	
		
			
				|  |  | -     * 初始时定义 expire time = System.currentTimeMillis()+cacheTimeout
 | 
	
		
			
				|  |  | -     * @param id
 | 
	
		
			
				|  |  | -     * @return
 | 
	
		
			
				|  |  | -     */
 | 
	
		
			
				|  |  | -    private CacheEntry<InMemoryItem> loadItem(String id) throws Exception {
 | 
	
		
			
				|  |  | -        long start = System.nanoTime();
 | 
	
		
			
				|  |  | -        byte[] bytes = client.get(IndexUtils.convertKey(prefix + id));
 | 
	
		
			
				|  |  | -        CacheEntry<InMemoryItem> result;
 | 
	
		
			
				|  |  | -        if (bytes == null) {
 | 
	
		
			
				|  |  | -            result = null;
 | 
	
		
			
				|  |  | -        } else {
 | 
	
		
			
				|  |  | -            result = makeCacheEntry(id, Optional.of(deserialize(bytes)));
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        return result;
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -    public long dbSize() {
 | 
	
		
			
				|  |  | -        return this.cache.size();
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -    private CacheEntry<InMemoryItem> makeCacheEntry(String itemId, Optional<InMemoryItem> t) {
 | 
	
		
			
				|  |  | -        long expireTime = System.currentTimeMillis() + cacheTimeout + RandomUtils.nextInt(10 * 60 * 1000);
 | 
	
		
			
				|  |  | -        long dynamicExpireTime = System.currentTimeMillis() + refreshDynamicInterval;
 | 
	
		
			
				|  |  | -        return new CacheEntry<InMemoryItem>(itemId, expireTime, dynamicExpireTime, t);
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -    public CacheEntry<InMemoryItem> getCacheEntry(String id) throws ExecutionException {
 | 
	
		
			
				|  |  | -        return cache.get(id);
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -    public Optional<InMemoryItem> getDirect(String id) throws Exception {
 | 
	
		
			
				|  |  | -        CacheEntry<InMemoryItem> cacheEntry = loadItem(id);
 | 
	
		
			
				|  |  | -        return cacheEntry.getItem();
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -    public Optional<InMemoryItem> get(String id) throws ExecutionException {
 | 
	
		
			
				|  |  | -        CacheEntry<InMemoryItem> result = cache.get(id);
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        if (result == null || !result.getItem().isPresent()) {
 | 
	
		
			
				|  |  | -            cache.invalidate(id);
 | 
	
		
			
				|  |  | -            return null;
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        // minimum expire time, update key expire time
 | 
	
		
			
				|  |  | -        if (result.getExpireTime() <= 0) {
 | 
	
		
			
				|  |  | -            result = makeCacheEntry(id, result.getItem());
 | 
	
		
			
				|  |  | -            cache.put(id, result);
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        // check expire and refresh
 | 
	
		
			
				|  |  | -        long currentTime = System.currentTimeMillis();
 | 
	
		
			
				|  |  | -        if ((result.getDynamicFeatureExpireTime() > 0 && currentTime > result.getDynamicFeatureExpireTime()) ||
 | 
	
		
			
				|  |  | -                (result.getExpireTime() > 0 && currentTime > result.getExpireTime())) {
 | 
	
		
			
				|  |  | -            boolean ret = asyncRefreshQueue.offer(id);
 | 
	
		
			
				|  |  | -            if (logger.isDebugEnabled()) {
 | 
	
		
			
				|  |  | -                logger.debug("offer async queue itemid [{}], result [{}]", id, ret);
 | 
	
		
			
				|  |  | -            }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -            if (asyncRefreshQueue.size() > CACHE_MAXIMUMSIZE) {
 | 
	
		
			
				|  |  | -                logger.warn("item backgroud blocking queue length [{}], it is danger", asyncRefreshQueue.size());
 | 
	
		
			
				|  |  | -            }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -            if (!ret) {
 | 
	
		
			
				|  |  | -                logger.error("item backgroud offer blocking queue error, itemid [{}], with queue length [{}]", id, asyncRefreshQueue.size());
 | 
	
		
			
				|  |  | -            }
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        return result.getItem();
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -    public String setLocalCache(String id, InMemoryItem t) throws ExecutionException {
 | 
	
		
			
				|  |  | -        long start = System.nanoTime();
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        // put to local cache
 | 
	
		
			
				|  |  | -        CacheEntry<InMemoryItem> result = makeCacheEntry(id, Optional.of(t));
 | 
	
		
			
				|  |  | -        cache.put(id, result);
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        return "";
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -    public String set(String id, InMemoryItem t) throws Exception {
 | 
	
		
			
				|  |  | -        long start = System.nanoTime();
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        // set redis
 | 
	
		
			
				|  |  | -        String strKey = prefix + id;
 | 
	
		
			
				|  |  | -        String ret = client.setex(IndexUtils.convertKey(strKey), this.redisTtl, t.toString().getBytes());
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        // put to cache
 | 
	
		
			
				|  |  | -        CacheEntry<InMemoryItem> result = makeCacheEntry(id, Optional.of(t));
 | 
	
		
			
				|  |  | -        cache.put(id, result);
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        return ret;
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -    public long remove(String id) throws Exception {
 | 
	
		
			
				|  |  | -        long start = System.nanoTime();
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        // del redis
 | 
	
		
			
				|  |  | -        long affectRow = client.del(IndexUtils.convertKey(prefix + id));
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        // del cache
 | 
	
		
			
				|  |  | -        cache.invalidate(id);
 | 
	
		
			
				|  |  | -        return affectRow;
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -    /**
 | 
	
		
			
				|  |  | -     * 通过 blocking queue 异步刷新即将到期的item
 | 
	
		
			
				|  |  | -     */
 | 
	
		
			
				|  |  | -    public class AsyncRefreshCacheRunnable implements Runnable {
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        @Override
 | 
	
		
			
				|  |  | -        public void run() {
 | 
	
		
			
				|  |  | -            while (true) {
 | 
	
		
			
				|  |  | -                String itemId = "";
 | 
	
		
			
				|  |  | -                try {
 | 
	
		
			
				|  |  | -                    itemId = asyncRefreshQueue.take(); // take async item, blocked when no item
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -                    final long currentTimeMillis = System.currentTimeMillis();
 | 
	
		
			
				|  |  | -                    if (logger.isDebugEnabled()) {
 | 
	
		
			
				|  |  | -                        logger.debug("async refresh thread get itemid [{}]", itemId); // get itemid
 | 
	
		
			
				|  |  | -                    }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -                    CacheEntry<InMemoryItem> entry = cache.get(itemId);
 | 
	
		
			
				|  |  | -                    // 全量刷新
 | 
	
		
			
				|  |  | -                    if (currentTimeMillis > entry.getExpireTime()) {
 | 
	
		
			
				|  |  | -                        refreshStaticFeatures(entry);
 | 
	
		
			
				|  |  | -                    }
 | 
	
		
			
				|  |  | -                    // 刷新动态特征
 | 
	
		
			
				|  |  | -                    if (currentTimeMillis > entry.getDynamicFeatureExpireTime()) {
 | 
	
		
			
				|  |  | -                        refreshDynamicFeatures(entry);
 | 
	
		
			
				|  |  | -                    }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -                    long endTime = System.currentTimeMillis();
 | 
	
		
			
				|  |  | -                    if (logger.isDebugEnabled()) {
 | 
	
		
			
				|  |  | -                        logger.debug("async refresh items spenttime [{}]", endTime - currentTimeMillis);
 | 
	
		
			
				|  |  | -                    }
 | 
	
		
			
				|  |  | -                } catch (Exception e) {
 | 
	
		
			
				|  |  | -                    logger.error("async refresh item error, itemId [{}], [{}]", itemId, ExceptionUtils.getFullStackTrace(e));
 | 
	
		
			
				|  |  | -                }
 | 
	
		
			
				|  |  | -            }
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        public void refreshStaticFeatures(CacheEntry<InMemoryItem> entry) {
 | 
	
		
			
				|  |  | -            try {
 | 
	
		
			
				|  |  | -                cache.refresh(entry.getItemId());
 | 
	
		
			
				|  |  | -                if (logger.isDebugEnabled()) {
 | 
	
		
			
				|  |  | -                    logger.debug("async refresh item [{}]", entry.getItemId());
 | 
	
		
			
				|  |  | -                }
 | 
	
		
			
				|  |  | -            } catch (Exception e) {
 | 
	
		
			
				|  |  | -                logger.error("async refresh item error, itemId [{}], exception [{}]", entry.getItemId(), ExceptionUtils.getFullStackTrace(e));
 | 
	
		
			
				|  |  | -            }
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        public void refreshDynamicFeatures(CacheEntry<InMemoryItem> entry) {
 | 
	
		
			
				|  |  | -            try {
 | 
	
		
			
				|  |  | -                // reload dynamic items and update expireTime
 | 
	
		
			
				|  |  | -                entry.setItem(Optional.of(entry.getItem().get()));
 | 
	
		
			
				|  |  | -                entry.setDynamicFeatureExpireTime(System.currentTimeMillis() + refreshDynamicInterval);
 | 
	
		
			
				|  |  | -                if (logger.isDebugEnabled()) {
 | 
	
		
			
				|  |  | -                    logger.debug("async refresh items dynamic features [{}]", entry.getItemId());
 | 
	
		
			
				|  |  | -                }
 | 
	
		
			
				|  |  | -            } catch (Exception e) {
 | 
	
		
			
				|  |  | -                logger.error("async refresh item dynamic features error, itemId [{}], exception [{}]",
 | 
	
		
			
				|  |  | -                        entry.getItemId(), ExceptionUtils.getFullStackTrace(e));
 | 
	
		
			
				|  |  | -            }
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  | -}
 |