2016-03-23

搜索博文这个东西,做了一个极其简陋的初版出来,就是用MySQL的like关键字搜标题而已,用带参数的Get发请求,请求的页数如一般的日志流一样在url中。

下一步的想法是把搜索范围改成标题、内容和分类,然后增加一个缓存的功能,对于一个关键字来说肯定有不止一个人会来搜,而且搜索之后翻页的可能性是很大的,所以在对某个关键词搜第一次的时候把全部结果都缓存起来,任何用户再次搜索这个关键词的任何一页都可以直接返回,不用再查询数据库。


2016-03-28

前两天写了一个简单的缓存,看起来工作还算正常。这个缓存实在很简单(简陋),读写锁控制同步,一共三个API,其实最初是四个,获取查询结果和对应的总页数是分开的,但是由于发表新文章、删除文章等操作会使所有缓存失效,所以存在这么一种情况:用户搜索,取得了查询结果,然后我发了新文章,缓存失效,于是得不到对应的总页数。

虽然我这个网站遇到这种并发问题的可能性几乎为0,但是我要假设自己面对各种各样的情况。。。毕竟我是一个严谨上进的程序员嘛。。。

所以把获取查询结果的API设计成了这个样子,需要客户代码传一个List进来接收结果,再返回一个int类型的总页数。

其实可以想到的解决方法也不止这一种,比如写一个wrapper类把查询结果和总页数都包在里面,然后让客户从wrapper里面去getResult,getToTalPageNum。

或者在获取总页数失败之后,重新查询并把结果存入缓存,再尝试获取总页数,直到得到一个正常的结果。但是如何定义这个语义来区分缓存失效之后的查询不到总页数和没有搜索结果造成的查询不到总页数?也许让客户代码去验证查询结果的数目可以区分这两者情况。

还有一点,目前的设计是查询交给Service层去做,缓存只负责存和取,所以这样的话就要强制客户代码按照这个形式编写。如果让Cache来负责查询数据库,客户代码就不用关心这些直接取结果就可以了,但是Cache来负责查询数据好像又不是很符合传统的Service、DAO这样的分层设计,唉所以说我还是太渣。

package com.baobaotao.cache;

import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import org.springframework.stereotype.Component;
import com.baobaotao.domain.BlogArticle;

@Component
public class SearchResultCache {
	private LinkedHashMap<String, List<BlogArticle>> cacheMap = new LinkedHashMap<>();
	private ReadWriteLock rwLock = new ReentrantReadWriteLock();

	private final int MAX_CAPACITY = 50;
	private final int DELETE_ONCE = 10;

	public int getArticles(String keyWord, int pageNo, int pageSize,
			List<BlogArticle> result) {
		if (result == null)
			throw new NullPointerException("the result list can not be null");
		rwLock.readLock().lock();
		int totalPageNo = 0;
		try {
			int begin = (pageNo - 1) * pageSize, end = begin + pageSize;
			List<BlogArticle> allArticles = cacheMap.get(keyWord);
			if (allArticles == null || begin >= allArticles.size()) {
				return 0;
			}
			result.clear();
			System.out.println("cache hit!keyword : " + keyWord);
			result.addAll(allArticles.subList(begin,
					Math.min(end, allArticles.size())));
			totalPageNo = (allArticles.size() - 1) / pageSize + 1;
		} catch (Exception e) {
			// TODO: handle exception
			e.printStackTrace();
		} finally {
			rwLock.readLock().unlock();
		}
		System.out.println(Thread.currentThread().getName());
		return totalPageNo;
	}

	public void putCacheIn(String keyWord, List<BlogArticle> allArticles) {
		rwLock.writeLock().lock();
		try {
			cacheMap.putIfAbsent(keyWord, allArticles);
			if (cacheMap.size() > MAX_CAPACITY) {
				Iterator<String> iterator = cacheMap.keySet().iterator();
				for (int i = 0; i < DELETE_ONCE; i++) {
					iterator.next();
					iterator.remove();
				}
			}
		} catch (Exception e) {
			// TODO: handle exception
			e.printStackTrace();
		} finally {
			rwLock.writeLock().unlock();
		}
	}

	public void invalidAll() {
		rwLock.writeLock().lock();
		try {
			cacheMap.clear();
		} catch (Exception e) {
			// TODO: handle exception
			e.printStackTrace();
		} finally {
			rwLock.writeLock().unlock();
		}
	}
}