每次收到点击请求,将文章id放进一个队列里,而后开启一个轮询服务,隔五秒去update到数据库中。每次查询到的文章点击量,有多是未及时更新的(时效性要求不高)。java
使用ConcurrentLinkedQueue 做为队列,由于它线程安全, 长度足够知足须要数据库
public class CargroupService implements ICargroupService { private final static Log log = LogFactory.getLog(CargroupService.class); /** 文章访问队列 **/ public static ConcurrentLinkedQueue<String> articleClickQueue = new ConcurrentLinkedQueue<String>(); /** 定时更新点击量线程 **/ ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1); /** 更新线程是否启动 **/ private static volatile boolean isStartScheduled = false; @Override public void getArticleDetail(ArticleQuery query) { // 利用线程安全的队列来保存被点击的文章,而后定时更新数据库 String queueStr = dbInfo.getId()+";"+ip; articleClickQueue.offer(queueStr); if (isStartScheduled == false) { log.debug("初始化文章点击量线程"); synchronized (CargroupService.class) { if (isStartScheduled == false) { isStartScheduled = true; ArticleClickThread thread = new ArticleClickThread(articleService, labelInfoService); scheduledExecutorService.scheduleAtFixedRate(thread, 0, CommonPropertiesConstants.COLLECT_CLICKNUM_SENONDS, TimeUnit.SECONDS); } } } } }
保存线程代码安全
public class ArticleClickThread implements Runnable { private final static Log log = LogFactory.getLog(ArticleClickThread.class); @Override public void run() { List<Map<String, Integer>> updateList = new ArrayList<Map<String, Integer>>(); Map<Integer, Integer> map = new HashMap<Integer, Integer>(); while (!CargroupService.articleClickQueue.isEmpty()) { String articleIdAndIp = CargroupService.articleClickQueue.poll(); String[] strs = StringUtils.split(articleIdAndIp, ";"); Integer id = Integer.parseInt(strs[0]); String ip = strs[1]; String articleIdAndIpRedisKey = RpcToolUtils.getArticleIdAndIpRedisKey(String.valueOf(id), ip); boolean exit = false; try { exit = RedisDataSource.existsObject(articleIdAndIpRedisKey); } catch (Exception e) { log.error("Redis服务挂了---------- !" + e); exit = false; e.printStackTrace(); } if (exit == false) { try { // 把该ip访问的文章记录,1天内从新点击或者刷新,不计数 RedisDataSource.setObject(articleIdAndIpRedisKey, "1", 24*60*60); } catch (Exception e) { e.printStackTrace(); } if (!map.containsKey(id)) { map.put(id, 1); } else { map.put(id, map.get(id) + 1); } } } for (Integer idKey : map.keySet()) { Map<String, Integer> paramMap = new HashMap<String, Integer>(); paramMap.put("articleId", idKey); paramMap.put("clickTimes", map.get(idKey)); updateList.add(paramMap); } if (updateList != null && updateList.size() > 0) { articleService.batchUpdateClickNum(updateList); labelInfoService.batchUpdateClickNum(updateList); } log.debug("完成轮询任务:保存文章点击量"); }
第一次启动线程后,每次点击文章只需 往队列里扔文章id,等待被轮询线程保存到数据库。ide