轉自 http://oaklet.iteye.com/blog/309037
關於緩存的話題,在罈子裏已經有很多討論,簡單的來說,如果一個應用中80% 的時間內都在訪問20% 的數據,那麼,這時候就應該使用緩存了。
測試環境:MySQL 5.0.22,
jdk1.6.0_07,
ehcache-1.6.0-beta2,
mysql-connector-java-3.1.14
測試表:
- CREATE TABLE TEST
- (
- TEST_ID BIGINT,
- TEST_NAME VARCHAR(50),
- TEST_TIME TIMESTAMP,
- TEST_VALUE DECIMAL(10, 3)
- );
支持類:
- public class Util {
- public static Random rand = new Random();
- public static String atoz = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
- public static String genString(int length) {
- StringBuilder re = new StringBuilder(length);
- re.append(atoz.charAt(rand.nextInt(52)));
- for (int i = 0; i < length; i++) {
- re.append(atoz.charAt(rand.nextInt(62)));
- }
- return re.toString();
- }
- public static double genDouble() {
- double d1 = 5120 * rand.nextDouble();
- double d2 = 1024000 * rand.nextDouble();
- return d1 + d2;
- }
- }
插入測試數據:
- public static void traditionalInsert(int total) throws Exception {
- Thread.sleep(3000);
- Timestamp current = new Timestamp(System.currentTimeMillis());
- String currentStr = dateFormat.format(current);
- System.out.println(currentStr);
- Connection conn = DriverManager.getConnection(dbURL, user, pass);
- try {
- long begin = System.currentTimeMillis();
- conn.setAutoCommit(false);
- String sql = "INSERT INTO TEST (TEST_ID,TEST_NAME,TEST_TIME,TEST_VALUE) VALUES (?, ?, ?, ?)";
- PreparedStatement ps = conn.prepareStatement(sql);
- for (int i = 1; i <= total; i++) {
- ps.setLong(1, i);
- ps.setString(2, Util.genString(33));
- ps.setTimestamp(3, current);
- ps.setBigDecimal(4, new BigDecimal(Util.genDouble()));
- ps.addBatch();
- if ((i % 500) == 0) {
- ps.executeBatch();
- }
- }
- ps.executeBatch();
- conn.commit();
- long end = System.currentTimeMillis();
- System.out.printf("Count:%d Time:%d\n", total, (end - begin));
- } catch (Exception ex) {
- ex.printStackTrace();
- conn.rollback();
- } finally {
- conn.close();
- }
- }
使用的javaBean:
- public class TEST implements Serializable {
- private static final long serialVersionUID = 1L;
- public Long TEST_ID;
- public String TEST_NAME;
- public Timestamp TEST_TIME;
- public BigDecimal TEST_VALUE;
- @Override
- public String toString() {
- return String.format("ID:%s,,,NAME:%s", TEST_ID, TEST_NAME);
- }
- }
先試一下緩存到字典中:
- public static HashMap<Long, TEST> simpleCache() throws Exception {
- HashMap<Long, TEST> cacheid = new HashMap<Long, TEST>();
- Class.forName(dbDriver);
- Connection conn = DriverManager.getConnection(dbURL, user, pass);
- try {
- long begin = System.currentTimeMillis();
- Statement s = conn.createStatement();
- String sql = "SELECT TEST_ID,TEST_NAME,TEST_TIME,TEST_VALUE FROM TEST";
- ResultSet querySet = s.executeQuery(sql);
- for (int i = 1; querySet.next(); i++) {
- TEST curr = new TEST();
- curr.TEST_ID = querySet.getLong(1);
- curr.TEST_NAME = querySet.getString(2);
- curr.TEST_TIME = querySet.getTimestamp(3);
- curr.TEST_VALUE = querySet.getBigDecimal(4);
- cacheid.put(curr.TEST_ID, curr);
- }
- long end = System.currentTimeMillis();
- System.out.printf("Time:%d\n", (end - begin));
- } catch (Exception ex) {
- ex.printStackTrace();
- } finally {
- conn.close();
- }
- return cacheid;
- }
緩存到字典中,寫法比較簡單,使用方便,缺點就是緩存數據量比較少,一般緩存10W就有可能把jvm的緩存給佔完了。用ehcache就可以解決緩存數據太少的問題。
一個簡單的配置:
- <ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:noNamespaceSchemaLocation="ehcache.xsd">
- <diskStore path="java.io.tmpdir"/>
- <defaultCache
- maxElementsInMemory="10000"
- maxElementsOnDisk="0"
- eternal="true"
- overflowToDisk="true"
- diskPersistent="false"
- timeToIdleSeconds="0"
- timeToLiveSeconds="0"
- diskSpoolBufferSizeMB="50"
- diskExpiryThreadIntervalSeconds="120"
- memoryStoreEvictionPolicy="LFU"
- />
- <cache name="demoCache"
- maxElementsInMemory="100"
- maxElementsOnDisk="0"
- eternal="false"
- overflowToDisk="false"
- diskPersistent="false"
- timeToIdleSeconds="119"
- timeToLiveSeconds="119"
- diskSpoolBufferSizeMB="50"
- diskExpiryThreadIntervalSeconds="120"
- memoryStoreEvictionPolicy="FIFO"
- />
- </ehcache>
Cache配置中的幾個屬性:
name:Cache的名稱,必須是唯一的(ehcache會把這個cache放到HashMap裏)。
maxElementsInMemory:內存中保持的對象數量。
maxElementsOnDisk:DiskStore中保持的對象數量,默認值爲0,表示不限制。
eternal:是否是永恆數據,如果是,則它的超時設置會被忽略。
overflowToDisk:如果內存中數據數量超過maxElementsInMemory限制,是否要緩存到磁盤上。
timeToIdleSeconds:對象空閒時間,指對象在多長時間沒有被訪問就會失效。只對eternal爲false的有效。默認值0,表示一直可以訪問。
timeToLiveSeconds:對象存活時間,指對象從創建到失效所需要的時間。只對eternal爲false的有效。默認值0,表示一直可以訪問。
diskPersistent:是否在磁盤上持久化。指重啓jvm後,數據是否有效。默認爲false。
diskExpiryThreadIntervalSeconds:對象檢測線程運行時間間隔。標識對象狀態的線程多長時間運行一次。
diskSpoolBufferSizeMB:DiskStore使用的磁盤大小,默認值30MB。每個cache使用各自的DiskStore。
memoryStoreEvictionPolicy:如果內存中數據超過內存限制,向磁盤緩存時的策略。默認值LRU,可選FIFO、LFU。
獲取配置中的demoCache:
- CacheManager manager = CacheManager.create("ehcache.xml");
- Cache demo = manager.getCache("demoCache");
往cache中加入數據:
- public static void ehcache() throws Exception {
- CacheManager manager = CacheManager.create("ehcache.xml");
- manager.addCache("TEST_ID.TEST");
- Cache cid = manager.getCache("TEST_ID.TEST");
- Class.forName(dbDriver);
- Connection conn = DriverManager.getConnection(dbURL, user, pass);
- try {
- long begin = System.currentTimeMillis();
- Statement s = conn.createStatement();
- String sql = "SELECT TEST_ID,TEST_NAME,TEST_TIME,TEST_VALUE FROM TEST";
- ResultSet querySet = s.executeQuery(sql);
- for (int i = 1; querySet.next(); i++) {
- TEST curr = new TEST();
- curr.TEST_ID = querySet.getLong(1);
- curr.TEST_NAME = querySet.getString(2);
- curr.TEST_TIME = querySet.getTimestamp(3);
- curr.TEST_VALUE = querySet.getBigDecimal(4);
- cid.put(new Element(curr.TEST_ID, curr));
- }
- long end = System.currentTimeMillis();
- System.out.printf("Time:%d\n", (end - begin));
- } catch (Exception ex) {
- ex.printStackTrace();
- } finally {
- conn.close();
- }
- }
這裏在CacheManager中直接加入了一個叫TEST_ID.TEST的cache。因爲只給了一個名字,所以系統會把defaultCache的設置給它clone一份。
使用方法,像字典一樣使用就行:
- Cache cid = manager.getCache("TEST_ID.TEST");
- Element e5120 = cid.get(new Long(5120));
- System.out.println(e5120.getValue());
ehcache中數據是以java對象的形式存在的,使用了java的序列化保存到磁盤,所以保存的對象要實現Serializable接口。ehcache還可以支持分佈式緩存。