第八章 企業項目開發--分布式緩存memcached


注意:本節代碼基於《第七章 企業項目開發--本地緩存guava cache

1、本地緩存的問題

  • 本地緩存速度一開始高於分布式緩存,但是隨着其緩存數量的增加,所占內存越來越大,系統運行內存越來越小,最后系統會被拖慢(這一點與第二點聯系起來)
  • 本地緩存存於本機,其緩存數量與大小受本機內存大小限制
  • 本地緩存存於本機,其他機器的訪問不到這樣的緩存

解決方案:分布式緩存

  • Jboss cache:緩存還存於本機,但是會同步更新到其他機器(解決了第三個問題,解決不了第一和第二個問題),如果緩存機器數量很多,同步更新很耗時
  • memcached:緩存存於其他機器,理論上緩存數量與大小無限(因為集群可伸縮),且不需要同步,所以即使緩存機器數量很多,也無所謂,但是這樣就會造成單點故障問題,最簡單易行的解決方案是緩存備份,即緩存至少存兩份。

 

2、memcached Java客戶端的選用

當下常用的三種memcached Java客戶端:

  • Memcached Client for Java:memcached官方提供,基於Java BIO實現
  • SpyMemcached:基於Java NIO
  • XMemcached:基於Java NIO,並發性能優於XMemcached,實際上SpyMemcached性能也很高

三者的實驗比較結果:

http://xmemcached.googlecode.com/svn/trunk/benchmark/benchmark.html

所以,我們選用XMemcached來實現客戶端的編寫。

 

3、代碼

在原來的代碼結構上,我增加了一個ssmm0-cache模塊,專門用於放置分布式緩存相關(memcached、redis、spring cache)的代碼。

項目整體結構:

說明:怎樣新建maven項目,並加入原來項目,最后引入eclipse,見第一章《第一章 企業項目開發--maven+springmvc+spring+mybatis+velocity整合

 

3.1、ssmm0

pom.xml

  1 <?xml version="1.0" encoding="UTF-8"?>
  2 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  3     xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  4 
  5     <modelVersion>4.0.0</modelVersion>
  6 
  7     <groupId>com.xxx</groupId>
  8     <artifactId>ssmm0</artifactId>
  9     <version>1.0-SNAPSHOT</version>
 10 
 11     <name>ssmm0</name>
 12     <packaging>pom</packaging><!-- 父模塊 -->
 13 
 14     <!-- 管理子模塊 -->
 15     <modules>
 16         <module>userManagement</module><!-- 具體業務1-人員管理系統 -->
 17         <module>data</module><!-- 封裝數據操作 -->
 18         <module>cache</module><!-- 緩存模塊 -->
 19     </modules>
 20 
 21     <properties>
 22         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
 23         <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
 24     </properties>
 25 
 26     <!-- dependencyManagement不會引入實際的依賴,只是作為一個依賴池,供其和其子類使用 -->
 27     <dependencyManagement>
 28         <dependencies>
 29             <!-- json -->
 30             <dependency>
 31                 <groupId>com.alibaba</groupId>
 32                 <artifactId>fastjson</artifactId>
 33                 <version>1.1.39</version>
 34             </dependency>
 35             <!-- servlet -->
 36             <dependency>
 37                 <groupId>javax.servlet</groupId>
 38                 <artifactId>javax.servlet-api</artifactId>
 39                 <version>3.0.1</version>
 40                 <scope>provided</scope>
 41             </dependency>
 42             <!-- spring -->
 43             <dependency>
 44                 <groupId>org.springframework</groupId>
 45                 <artifactId>spring-core</artifactId>
 46                 <version>3.2.6.RELEASE</version>
 47             </dependency>
 48             <dependency>
 49                 <groupId>org.springframework</groupId>
 50                 <artifactId>spring-beans</artifactId>
 51                 <version>3.2.6.RELEASE</version>
 52             </dependency>
 53             <dependency>
 54                 <groupId>org.springframework</groupId>
 55                 <artifactId>spring-context</artifactId>
 56                 <version>3.2.6.RELEASE</version>
 57             </dependency>
 58             <dependency>
 59                 <groupId>org.springframework</groupId>
 60                 <artifactId>spring-web</artifactId>
 61                 <version>3.2.6.RELEASE</version>
 62             </dependency>
 63             <dependency>
 64                 <groupId>org.springframework</groupId>
 65                 <artifactId>spring-webmvc</artifactId>
 66                 <version>3.2.6.RELEASE</version>
 67             </dependency>
 68             <!-- 這個是使用velocity的必備包 -->
 69             <dependency>
 70                 <groupId>org.springframework</groupId>
 71                 <artifactId>spring-context-support</artifactId>
 72                 <version>3.2.6.RELEASE</version>
 73             </dependency>
 74             <!-- mysql -->
 75             <dependency>
 76                 <groupId>mysql</groupId>
 77                 <artifactId>mysql-connector-java</artifactId>
 78                 <version>5.1.27</version>
 79                 <scope>runtime</scope>
 80             </dependency>
 81             <!-- 數據源 -->
 82             <dependency>
 83                 <groupId>org.apache.tomcat</groupId>
 84                 <artifactId>tomcat-jdbc</artifactId>
 85                 <version>7.0.47</version>
 86             </dependency>
 87             <!-- mybatis -->
 88             <dependency>
 89                 <groupId>org.mybatis</groupId>
 90                 <artifactId>mybatis</artifactId>
 91                 <version>3.1.1</version>
 92             </dependency>
 93             <dependency>
 94                 <groupId>org.mybatis</groupId>
 95                 <artifactId>mybatis-spring</artifactId>
 96                 <version>1.1.1</version>
 97             </dependency>
 98             <!-- velocity -->
 99             <dependency>
100                 <groupId>org.apache.velocity</groupId>
101                 <artifactId>velocity</artifactId>
102                 <version>1.5</version>
103             </dependency>
104             <dependency>
105                 <groupId>velocity-tools</groupId>
106                 <artifactId>velocity-tools-generic</artifactId>
107                 <version>1.2</version>
108             </dependency>
109             <!-- 用於加解密 -->
110             <dependency>
111                 <groupId>commons-codec</groupId>
112                 <artifactId>commons-codec</artifactId>
113                 <version>1.7</version>
114             </dependency>
115             <dependency>
116                 <groupId>org.bouncycastle</groupId>
117                 <artifactId>bcprov-jdk15on</artifactId>
118                 <version>1.47</version>
119             </dependency>
120             <!-- 集合工具類 -->
121             <dependency>
122                 <groupId>org.apache.commons</groupId>
123                 <artifactId>commons-collections4</artifactId>
124                 <version>4.0</version>
125             </dependency>
126             <!-- 字符串處理類 -->
127             <dependency>
128                 <groupId>org.apache.commons</groupId>
129                 <artifactId>commons-lang3</artifactId>
130                 <version>3.4</version>
131             </dependency>
132             <!-- http -->
133             <dependency>
134                 <groupId>org.apache.httpcomponents</groupId>
135                 <artifactId>httpclient</artifactId>
136                 <version>4.2.6</version>
137             </dependency>
138         </dependencies>
139     </dependencyManagement>
140 
141     <!-- 引入實際依賴 -->
142     <dependencies>
143         <!-- json -->
144         <dependency>
145             <groupId>com.alibaba</groupId>
146             <artifactId>fastjson</artifactId>
147         </dependency>
148         <!-- spring -->
149         <dependency>
150             <groupId>org.springframework</groupId>
151             <artifactId>spring-core</artifactId>
152         </dependency>
153         <dependency>
154             <groupId>org.springframework</groupId>
155             <artifactId>spring-beans</artifactId>
156         </dependency>
157         <dependency>
158             <groupId>org.springframework</groupId>
159             <artifactId>spring-context</artifactId>
160         </dependency>
161         <!-- 集合工具類 -->
162         <dependency>
163             <groupId>org.apache.commons</groupId>
164             <artifactId>commons-collections4</artifactId>
165         </dependency>
166         <!-- 字符串處理類 -->
167         <dependency>
168             <groupId>org.apache.commons</groupId>
169             <artifactId>commons-lang3</artifactId>
170         </dependency>
171     </dependencies>
172 
173     <build>
174         <resources>
175             <!-- 這里配置了這一塊兒true,才可以讓指定文件(這里是src/main/resources/spring-data.xml)讀到pom.xml中的配置信息 
176                 , 值得注意的是,如果src/main/resources下還有其他文件,而你不想讓其讀pom.xml, 你還必須得把src/main/resources下的其余文件再配置一遍,配置為false(不可讀pom.xml), 
177                 如下邊的注釋那樣,否則,會報這些文件找不到的錯誤 
178             -->
179             <resource>
180                 <directory>src/main/resources</directory>
181                 <filtering>true</filtering>
182                 <includes>
183                     <include>*.xml</include>
184                     <include>*.properties</include>
185                 </includes>
186             </resource>
187             <!-- 
188             <resource> 
189                 <directory>src/main/resources</directory> 
190                 <filtering>false</filtering>   
191                 <includes> 
192                     <include>*.properties</include> 
193                 </includes> 
194             </resource> 
195             -->
196             <resource> 
197                 <directory>src/main/resources</directory> 
198                 <filtering>false</filtering>   
199                 <includes> 
200                 <!-- 這里如果不加這一條,那么在spring-data.xml中配置的xml將找不到classpath:mapper/admin/AdminMapper.xml -->
201                     <include>mapper/**/*.xml</include> 
202                 </includes> 
203             </resource> 
204         </resources>
205     </build>
206 
207     <!-- 
208         profiles可以定義多個profile,然后每個profile對應不同的激活條件和配置信息,從而達到不同環境使用不同配置信息的效果 
209         注意兩點: 
210         1)<activeByDefault>true</activeByDefault>這種情況表示服務器啟動的時候就采用這一套env(在這里,就是prod) 
211         2)當我們啟動服務器后,想采用開發模式,需切換maven的env為dev,如果env的配置本身就是dev,需要將env換成rc或prod,點擊apply,然后再將env切換成dev,點擊apply才行 
212     -->
213     <profiles>
214         <!-- 開發env -->
215         <profile>
216             <id>dev</id>
217             <activation>
218                 <!-- 這里為了測試方便,改為了true,在上線的時候一定要改成false,否則線上使用的就是這一套dev的環境了 -->
219                 <activeByDefault>true</activeByDefault>
220                 <property>
221                     <name>env</name>
222                     <value>dev</value>
223                 </property>
224             </activation>
225             <properties>
226                 <env>dev</env>
227 
228                 <jdbc.driverClassName>com.mysql.jdbc.Driver</jdbc.driverClassName>
229                 <!--
230                      對於jdbc.url中內容的配置,如果需要配置 &amp;時,有兩種方法:
231                     1)如下邊這樣,使用<![CDATA[XXX]]>包起來 
232                     2)使用jdbc.properties文件來讀取此pom.xml,然后spring.xml再讀取jdbc.properties文件 顯然,前者更方便,而且還省了一個jdbc.properties的文件,但是,有的時候,還是會用后者的; 
233                     在使用后者的時候,注意三點:
234                     1)需要修改上邊的build中的內容 
235                     2)需要在spring.xml中配置<context:property-placeholder location="classpath:jdbc.properties"/> 
236                     3)將jdbc.properties放在ssmm0-data項目中,之后需要將ssmm0-data項目的env配置為dev 
237                 -->
238                 <jdbc.url><![CDATA[jdbc:mysql://127.0.0.1:3306/blog?zeroDateTimeBehavior=convertToNull&amp;useUnicode=true&amp;characterEncoding=utf-8]]></jdbc.url>
239                 <jdbc.username>root</jdbc.username>
240                 <jdbc.password>123456</jdbc.password>
241                 
242                 <!-- memcache,多台服務器之間需要使用空格隔開,而不要使用英文逗號隔開,因為Xmemcached的AddrUtil源碼是根據空格隔開的 -->
243                 <memcached.servers><![CDATA[127.0.0.1:11211]]></memcached.servers>
244                 <memcached.max.client>10</memcached.max.client><!-- 最多的客戶端數 -->
245                 <memcached.expiretime>900</memcached.expiretime><!-- 過期時間900s -->
246                 <memcached.hash.consistent>true</memcached.hash.consistent><!-- 是否使用一致性hash算法 -->
247                 <memcached.connection.poolsize>1</memcached.connection.poolsize><!-- 每個客戶端池子的連接數 -->
248                 <memcached.op.timeout>2000</memcached.op.timeout><!-- 操作超時時間 -->
249             </properties>
250         </profile>
251         <!-- 預上線env -->
252         <profile>
253             <id>rc</id>
254             <activation>
255                 <activeByDefault>false</activeByDefault>
256                 <property>
257                     <name>env</name>
258                     <value>rc</value>
259                 </property>
260             </activation>
261             <properties>
262                 <env>rc</env>
263 
264                 <jdbc.driverClassName>com.mysql.jdbc.Driver</jdbc.driverClassName>
265                 <!-- 假設的一個地址 -->
266                 <jdbc.url><![CDATA[jdbc:mysql://10.10.10.100:3306/blog?zeroDateTimeBehavior=convertToNull&amp;useUnicode=true&amp;characterEncoding=utf-8]]></jdbc.url>
267                 <jdbc.username>root2</jdbc.username>
268                 <jdbc.password>1234562</jdbc.password>
269             </properties>
270         </profile>
271         <!-- 線上env -->
272         <profile>
273             <id>prod</id>
274             <activation>
275                 <!-- 這里為了測試方便,改為了false,在上線的時候一定要改成true,否則線上使用的就不是這一套環境了 -->
276                 <activeByDefault>false</activeByDefault>
277                 <property>
278                     <name>env</name>
279                     <value>prod</value>
280                 </property>
281             </activation>
282             <properties>
283                 <env>prod</env>
284 
285                 <jdbc.driverClassName>com.mysql.jdbc.Driver</jdbc.driverClassName>
286                 <!-- 假設的一個地址 -->
287                 <jdbc.url><![CDATA[jdbc:mysql://99.99.99.999:3307/blog?zeroDateTimeBehavior=convertToNull&amp;useUnicode=true&amp;characterEncoding=utf-8]]></jdbc.url>
288                 <jdbc.username>sadhijhqwui</jdbc.username>
289                 <jdbc.password>zxczkchwihcznk=</jdbc.password>
290             </properties>
291         </profile>
292     </profiles>
293 </project>
View Code

說明:這里給出了完整版,實際上只做了以下5點改動:

  • 新增cache子module
  • 引入了commons-lang3的jar包,該jar封裝了一些對於字符串的處理方法,eg.isBlank()
  • <resources>部分因為要將該pom.xml中的配置讀取到ssmm0-cache模塊的properties文件中去,所以添加了過濾目錄
  • 在dev環境下配置了與memcached相關的服務器列表及參數
  • 最后一點,只是為了測試方便,將dev設為默認選用的環境,而prod不是,在實際上線之前,一定要改回來

注意:

  • 在pom.xml中配置memcached服務器列表時,要以"ip1:port1 ip2:port2..."這樣的形式,即每台服務器之間用空格隔開,這與Xmemcached的AddrUtil讀取服務器列表的方式有關。

 

3.2、ssmm0-cache

模塊結構:

3.2.1、pom.xml

 1 <?xml version="1.0" encoding="UTF-8"?>
 2 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 3     xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
 4 
 5     <modelVersion>4.0.0</modelVersion>
 6 
 7     <!-- 指定父模塊 -->
 8     <parent>
 9         <groupId>com.xxx</groupId>
10         <artifactId>ssmm0</artifactId>
11         <version>1.0-SNAPSHOT</version>
12     </parent>
13 
14     <groupId>com.xxx.ssmm0</groupId>
15     <artifactId>ssmm0-cache</artifactId>
16 
17     <name>ssmm0-cache</name>
18     <packaging>jar</packaging>
19 
20     <!-- 引入實際依賴 -->
21     <dependencies>
22         <!-- memcached -->
23         <dependency>
24             <groupId>com.googlecode.xmemcached</groupId>
25             <artifactId>xmemcached</artifactId>
26             <version>1.4.3</version>
27         </dependency>
28     </dependencies>
29 </project>
View Code

說明:在該pom.xml中引入了xmemcached的jar包,注意該jar依賴於slf4j.jar,如果不是用maven的話,需要手動導入slf4j.jar,但是用maven的話,maven自己會導入相關的依賴包。

3.2.2、cache_config.properties

 1 #memcached配置#
 2 #memcached服務器集群
 3 memcached.servers = ${memcached.servers}
 4 #緩存過期時間
 5 memcached.expiretime = ${memcached.expiretime}
 6 #是否使用一致性hash算法
 7 memcached.hash.consistent = ${memcached.hash.consistent}
 8 #memcached的最大客戶端數量
 9 memcached.max.client = ${memcached.max.client}
10 #每個客戶端池子的連接數
11 memcached.connection.poolsize = ${memcached.connection.poolsize}
12 #操作超時時間
13 memcached.op.timeout = ${memcached.op.timeout}
View Code

說明:這里需要從根pom.xml(即ssmm0的pom.xml)中讀取信息,所以在根pom.xml的資源過濾部分有相關的修改

3.2.3、FileUtil.java

 1 package com.xxx.cache.util;
 2 
 3 import java.io.IOException;
 4 import java.io.InputStream;
 5 import java.util.Properties;
 6 
 7 import org.apache.commons.lang3.math.NumberUtils;
 8 
 9 /**
10  * 文件操作工具類
11  */
12 public class FileUtil {
13     
14     /**
15      * 加載屬性文件*.properties
16      * @param fileName 不是屬性全路徑名稱,而是相對於類路徑的名稱
17      */
18     public static Properties loadProps(String fileName){
19         Properties props = null;
20         InputStream is = null;
21         
22         try {
23             is = Thread.currentThread().getContextClassLoader().getResourceAsStream(fileName);//獲取類路徑下的fileName文件,並且轉化為輸入流
24             if(is != null){
25                 props = new Properties();
26                 props.load(is);    //加載屬性文件
27             }
28         } catch (Exception e) {
29             e.printStackTrace();
30         }finally{
31             if(is!=null){
32                 try {
33                     is.close();
34                 } catch (IOException e) {
35                     e.printStackTrace();
36                 }
37             }
38         }
39         
40         return props;
41     }
42     
43     /*
44      * 從屬性文件中獲取int型數據
45      */
46     public static int getInt(Properties props, String key, int defaultValue){
47         int value = defaultValue;
48         if(props.containsKey(key)){                                //屬性文件中是否包含給定鍵值
49             value = NumberUtils.toInt(props.getProperty(key), defaultValue);//從屬性文件中取出給定鍵值的value,並且轉換為int型
50         }
51         return value;
52     }
53     
54     /*
55      * 從屬性文件中獲取long型數據
56      */
57     public static long getLong(Properties props, String key, long defaultValue){
58         long value = defaultValue;
59         if(props.containsKey(key)){                                //屬性文件中是否包含給定鍵值
60             value = NumberUtils.toLong(props.getProperty(key), defaultValue);//從屬性文件中取出給定鍵值的value,並且轉換為int型
61         }
62         return value;
63     }
64     
65     /*
66      * 從屬性文件中獲取boolean型數據
67      */
68     public static boolean getBoolean(Properties props, String key, boolean defaultValue){
69         boolean value = defaultValue;
70         if(props.containsKey(key)){                                //屬性文件中是否包含給定鍵值
71             value = toBoolean(props.getProperty(key), defaultValue);
72         }
73         return value;
74     }
75     
76     
77     public static boolean toBoolean(String str, boolean defaultValue) {
78         if(str == null) {
79             return defaultValue;
80         }
81         return Boolean.parseBoolean(str);
82     }
83     
84     /**
85      * 測試
86      */
87     public static void main(String[] args) {
88         Properties props = FileUtil.loadProps("cache_config.properties");
89         //System.out.println(props);
90         System.out.println(props.getProperty("memcached.servers", "123"));//從屬性文件中讀取string
91         System.out.println(FileUtil.getInt(props, "httpclient.max.conn.per.route2", 10));//屬性文件中沒有這個key
92     }
93 }
View Code

說明:對於該類的介紹與注意點,參看"Java文件相關"系列的《第一章 屬性文件操作工具類

在該類中,添加了一些方法

  • 從屬性文件中將String轉化為long型數據,與String轉int一樣,使用NumberUtil即可
  • 從屬性文件中將String轉化為Boolean型數據,直接參考NumberUtil的相關源代碼進行編寫即可
  • 從屬性文件中讀取String,props.getProperties(String key, String defaultValue)即可

注意:

  • 如果直接運行該類中的main方法來讀取properties文件中由根pom.xml傳來的參數,可能讀取不到;可以通過后邊的MemcachedUtil中的main方法來獲取,或者直接通過最后的瀏覽器訪問來獲取就好

3.2.4、CachePrefix.java

 1 package com.xxx.cache.util;
 2 
 3 /**
 4  * 在該類中定義一些緩存的前綴
 5  * 方式:
 6  * 1、定義一個類,在其中添加靜態常量
 7  * 2、使用枚舉類(這是最好的方式)
 8  * 作用:
 9  * 1、防止緩存鍵值重復(通常情況下,每一種業務對應一種前綴)
10  * 2、可以起到根據前綴分類的作用
11  * 后者主要是在memcached中實現類似於redis的實現。
12  */
13 public enum CachePrefix {
14     USER_MANAGEMENT,    //人員管理業務類緩存前綴
15     HOTEL_MANAGEMENT;    //酒店管理業務類緩存前綴
16 }
View Code

說明:在該類中定義一些緩存的前綴

作用:

  • 防止緩存鍵值重復(通常情況下,每一種業務對應一種前綴
  • 以起到根據前綴分類的作用(后者主要是在memcached中實現類似於redis的實現),但是其實memcached可以通過使用命名空間來實現分類,具體的參看我的"Java緩存相關"系列中后續的文章

注意:定義常量類有兩種方式

  • 普通類,里邊使用static final常量
  • 枚舉類(推薦),這里就是使用了枚舉類

關於枚舉類與普通類做常量類的優缺點對比,查看《effective Java:第二版》第30條,或者參考我"Java高效使用"系列中后續的文章

3.2.5、MemcachedUtil

  1 package com.xxx.cache.memcached;
  2 
  3 import java.io.IOException;
  4 import java.util.HashMap;
  5 import java.util.Map;
  6 import java.util.Properties;
  7 import java.util.concurrent.TimeoutException;
  8 
  9 import org.apache.commons.lang3.StringUtils;
 10 
 11 import com.xxx.cache.util.CachePrefix;
 12 import com.xxx.cache.util.FileUtil;
 13 
 14 
 15 import net.rubyeye.xmemcached.MemcachedClient;
 16 import net.rubyeye.xmemcached.MemcachedClientBuilder;
 17 import net.rubyeye.xmemcached.XMemcachedClientBuilder;
 18 import net.rubyeye.xmemcached.command.BinaryCommandFactory;
 19 import net.rubyeye.xmemcached.exception.MemcachedException;
 20 import net.rubyeye.xmemcached.impl.KetamaMemcachedSessionLocator;
 21 import net.rubyeye.xmemcached.transcoders.CompressionMode;
 22 import net.rubyeye.xmemcached.transcoders.SerializingTranscoder;
 23 import net.rubyeye.xmemcached.utils.AddrUtil;
 24 
 25 /**
 26  * memcached工具類(基於Xmemcached實現)
 27  */
 28 public class MemcachedUtil {
 29     private static Map<Integer, MemcachedClient> clientMap 
 30                         = new HashMap<Integer, MemcachedClient>();//client的集合
 31     private static int maxClient = 3;
 32     private static int expireTime = 900;//900s(默認的緩存過期時間)
 33     private static int maxConnectionPoolSize = 1;//每個客戶端池子的連接數
 34     private static long op_time = 2000L;//操作超時時間
 35     
 36     private static final String KEY_SPLIT = "-";//用於隔開緩存前綴與緩存鍵值
 37     
 38     /**
 39      * 構建MemcachedClient的map集合
 40      */
 41     static{
 42         //讀取配置文件
 43         Properties props = FileUtil.loadProps("cache_config.properties");
 44         String servers = props.getProperty("memcached.servers", "127.0.0.1:11211");//獲取memcached servers集合
 45         maxClient = FileUtil.getInt(props, "", maxClient);
 46         expireTime = FileUtil.getInt(props, "memcached.expiretime", expireTime);
 47         maxConnectionPoolSize = FileUtil.getInt(props, "memcached.connection.poolsize", maxConnectionPoolSize);
 48         op_time = FileUtil.getLong(props, "memcached.op.timeout", op_time);
 49         
 50         if(StringUtils.isNotBlank(servers)){
 51             MemcachedClientBuilder builder = new XMemcachedClientBuilder(AddrUtil.getAddresses(servers));
 52             builder.setConnectionPoolSize(1);//這個默認也是1
 53             builder.setSessionLocator(new KetamaMemcachedSessionLocator(true));//使用一致性hash算法
 54             
 55             SerializingTranscoder transcoder = new SerializingTranscoder(1024*1024);//序列化轉換器,指定最大的數據大小1M
 56             transcoder.setCharset("UTF-8");//默認為UTF-8,這里可去掉
 57             transcoder.setCompressionThreshold(1024*1024);//單位:字節,壓縮邊界值,任何一個大於該邊界值(這里是:1M)的數據都要進行壓縮
 58             transcoder.setCompressionMode(CompressionMode.GZIP);//壓縮算法
 59             
 60             builder.setTranscoder(transcoder);
 61             builder.setCommandFactory(new BinaryCommandFactory());//命令工廠
 62             
 63             //構建10個MemcachedCient,並放入clientMap
 64             for(int i=0;i<maxClient;i++){
 65                 try {
 66                     MemcachedClient client = builder.build();
 67                     client.setOpTimeout(op_time);//設置操作超時時間,默認為1s
 68                     clientMap.put(i, client);
 69                 } catch (IOException e) {
 70                     e.printStackTrace();
 71                 }
 72             }
 73         }
 74     }
 75     
 76     /**
 77      * 從MemcachedClient中取出一個MemcachedClient
 78      */
 79     public static MemcachedClient getMemcachedClient(){
 80         /*
 81          * Math.random():產生[0,1)之間的小數
 82          * Math.random()*maxClient:[0~maxClient)之間的小數
 83          * (int)(Math.random()*maxClient):[0~maxClient)之間的整數
 84          */
 85         return clientMap.get((int)(Math.random()*maxClient));
 86     }
 87     
 88     /**
 89      * 設置緩存
 90      * @param keyPrefix    緩存的鍵的前綴
 91      * @param key                 緩存的鍵
 92      * @param value               緩存的值
 93      * @param exp                   緩存過期時間
 94      */
 95     public static void setCacheWithNoReply(CachePrefix keyPrefix, 
 96                                            String key, 
 97                                            Object value, 
 98                                            int exp){
 99         try {
100             MemcachedClient client = getMemcachedClient();
101             client.addWithNoReply(keyPrefix+KEY_SPLIT+key, exp, value);
102         } catch (InterruptedException e) {
103             e.printStackTrace();
104         } catch (MemcachedException e) {
105             e.printStackTrace();
106         }
107     }
108     
109     /**
110      * 設置緩存
111      * @param exp       緩存過期時間(默認時間)
112      */
113     public static void setCacheWithNoReply(CachePrefix keyPrefix,
114                                            String key,
115                                            Object value){
116         setCacheWithNoReply(keyPrefix, key, value, expireTime);
117     }
118     
119     /**
120      * 設置緩存,並返回緩存成功與否
121      * 注意:
122      * 1、設置已經設置過的key-value,將會返回false
123      */
124     public static boolean setCache(CachePrefix keyPrefix,
125                                    String key,
126                                    Object value,
127                                    int exp){
128         boolean setCacheSuccess = false; 
129         try {
130             MemcachedClient client = getMemcachedClient();
131             setCacheSuccess = client.add(keyPrefix+KEY_SPLIT+key, exp, value);
132         } catch (TimeoutException e) {
133             e.printStackTrace();
134         } catch (InterruptedException e) {
135             e.printStackTrace();
136         } catch (MemcachedException e) {
137             e.printStackTrace();
138         }
139         return setCacheSuccess;
140     }
141     
142     /**
143      * 設置緩存,並返回緩存成功與否(緩存超時時間采用默認)
144      * @param key
145      * @param value
146      */
147     public static boolean setCache(CachePrefix keyPrefix,
148                                    String key, 
149                                    Object value){
150         return setCache(keyPrefix, key, value, expireTime);
151     }
152     
153     /**
154      * 獲取緩存
155      */
156     public static Object getCache(CachePrefix keyPrefix, String key){
157         Object value = null;
158         try {
159             MemcachedClient client = getMemcachedClient();
160             value = client.get(keyPrefix+KEY_SPLIT+key);
161         } catch (TimeoutException e) {
162             e.printStackTrace();
163         } catch (InterruptedException e) {
164             e.printStackTrace();
165         } catch (MemcachedException e) {
166             e.printStackTrace();
167         }
168         return value;
169     }
170     
171     /**
172      * 刪除緩存
173      */
174     public static void removeCacheWithNoReply(CachePrefix keyPrefix, String key){
175         try {
176             MemcachedClient client = getMemcachedClient();
177             client.deleteWithNoReply(keyPrefix+KEY_SPLIT+key);
178         } catch (InterruptedException e) {
179             e.printStackTrace();
180         } catch (MemcachedException e) {
181             e.printStackTrace();
182         }
183     }
184     
185     /**
186      * 刪除緩存,並返回刪除成功與否
187      */
188     public static boolean removeCache(CachePrefix keyPrefix, String key){
189         boolean removeCacheSuccess = false; 
190         try {
191             MemcachedClient client = getMemcachedClient();
192             removeCacheSuccess = client.delete(keyPrefix+KEY_SPLIT+key);
193         } catch (TimeoutException e) {
194             e.printStackTrace();
195         } catch (InterruptedException e) {
196             e.printStackTrace();
197         } catch (MemcachedException e) {
198             e.printStackTrace();
199         }
200         return removeCacheSuccess;
201     }
202     
203     /**
204      * 測試
205      * @param args
206      */
207     public static void main(String[] args) {
208         /*for(int i=0;i<100;i++){
209             System.out.println(Math.random());
210         }*/
211         System.out.println(MemcachedUtil.setCache(CachePrefix.USER_MANAGEMENT,"hello4", "world"));
212         System.out.println(MemcachedUtil.getCache(CachePrefix.USER_MANAGEMENT,"hello4"));
213         /*System.out.println(MemcachedUtil.getCache("hello2"));
214         System.out.println(MemcachedUtil.getCache("hello2"));
215         System.out.println(MemcachedUtil.getCache("hello2"));
216         System.out.println(MemcachedUtil.getCache("hello2"));
217         System.out.println(MemcachedUtil.getCache("hello2"));*/
218     }
219 }
View Code

首先給出該類的一個圖(不知道該怎么稱呼這個圖)

說明:上述的圖可以根據源代碼的追蹤畫出來;該類是基於XMemcached實現了的一個工具類,主要包含以下6個部分

  • 屬性默認值的指定
  • static靜態塊中讀取屬性文件,並與前邊的默認值一起來指定最終參數值
  • 根據服務器列表構建MemcachedClientBuilder,配置builder的相關屬性,包括序列化轉化器、二進制協議工廠等
  • 通過上述的MemcachedClientBuilder構建指定個數(這里是3個)的MemcachedClient客戶端,存於clientMap中
  • 提供從clientMap獲取MemcachedClient的方法(這里是隨機獲取)
  • 提供緩存的基本操作,增刪查操作

注意:

  • 我們提供了三個MemcachedClient,那是不是說明同時只能處理三個並發請求呢?不是,Xmemcached基於Java NIO,每一個MemcachedClient都會啟動一個reactor線程和一些工作線程,基於IO多路復用技術,一個reactor線程理論上可以接受極高的並發量,甚至可以說成,該reactor線程可以接過所有到達memcached的請求,然后通過事件機制(類似於觀察者模式)將這些請求派發給工作線程,進行相應的操作。關於Java NIO、reactor線程模型、事件機制等可以參看《netty權威指南(第2版)》。
  • 正如上邊所說,每啟動一個MemcachedClient,就必須啟動一個reactor線程和一些工作線程,這其實是一個昂貴的操作,所以構建多個客戶端是比較昂貴的,基於此,XMemcached提供了池化操作,即一個配置參數(setConnectionPoolSize),但是在實際使用中發現,當配置該參數>1的情況下會發生線程死鎖現象,所以還是采用多客戶端的方式吧!在我們的實際使用中,10個客戶端接收百萬級請求絕對是輕輕松松的!
  • 這里需要注意的是,服務器列表的配置必須用空格隔開,原理查看AddrUtil的源代碼
  • 關於序列化與反序列化發生的時機,請參看《http://blog.csdn.net/tang9140/article/details/43445511》或者我的"Java緩存相關"的后續文章
  • Xmemcached提供了很多的緩存操作API,這些API我會在"Java緩存相關"的后續文章介紹,這里想說,如果你需要提供很多的API方法,那么推薦將上述"說明"中的前5部分寫在一個類中(MemcachedFactory),將第6部分(緩存相關操作)寫在一個類中(MemcachedUtil),這樣會很清晰
  • 其他屬性的配置,我也會在"Java緩存相關"的后續文章介紹

在這里,需要裝一個memcached服務器了。

我們就簡要的裝一個windows版本的,我發了一個再雲盤上,鏈接:http://pan.baidu.com/s/1dDMlov3,下載后,解壓,

兩種使用方法:

A、雙擊解壓后的"x86"(32位)或"x64"(64位)文件夾中的memcached.exe,跳出窗口即可。

B、在C:\Windows\System32\cmd.exe右擊"以管理員身份運行"-->在命令窗口進入E:\memcached\x86目錄中-->"memcached.exe -d install"-->之后去"本地服務"看看是不是已經有memcached server的服務了,如果已經有了,說明安裝成功-->之后啟動服務,兩種方式:

B1、手工在"本地服務部分"啟動

B2、命令窗口下"memcached.exe -p 11211 -m 32 -c 1024 -d start",該方法可以指定參數啟動memcached服務,-p表示端口,-m表示分配的內存,-c表示最大的並發連接數

當然,我們在實際使用中,會裝在Linux系統上,同時也需要指定一系列參數,例如分配的最大內存、最大並發數等等。

3.3、ssmm0-data

結構:

3.3.1、pom.xml

 1 <?xml version="1.0" encoding="UTF-8"?>
 2 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 3     xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
 4 
 5     <modelVersion>4.0.0</modelVersion>
 6 
 7     <!-- 指定父模塊 -->
 8     <parent>
 9         <groupId>com.xxx</groupId>
10         <artifactId>ssmm0</artifactId>
11         <version>1.0-SNAPSHOT</version>
12     </parent>
13 
14     <groupId>com.xxx.ssmm0</groupId>
15     <artifactId>ssmm0-data</artifactId>
16 
17     <name>ssmm0-data</name>
18     <packaging>jar</packaging><!-- 只是作為其他模塊使用的工具 -->
19 
20     <!-- 引入實際依賴 -->
21     <dependencies>
22         <!-- mysql -->
23         <dependency>
24             <groupId>mysql</groupId>
25             <artifactId>mysql-connector-java</artifactId>
26         </dependency>
27         <!-- 數據源 -->
28         <dependency>
29             <groupId>org.apache.tomcat</groupId>
30             <artifactId>tomcat-jdbc</artifactId>
31         </dependency>
32         <!-- mybatis -->
33         <dependency>
34             <groupId>org.mybatis</groupId>
35             <artifactId>mybatis</artifactId>
36         </dependency>
37         <dependency>
38             <groupId>org.mybatis</groupId>
39             <artifactId>mybatis-spring</artifactId>
40         </dependency>
41         <!-- servlet --><!-- 為了會用cookie -->
42         <dependency>
43             <groupId>javax.servlet</groupId>
44             <artifactId>javax.servlet-api</artifactId>
45         </dependency>
46         <!-- bc-加密 -->
47         <dependency>
48             <groupId>org.bouncycastle</groupId>
49             <artifactId>bcprov-jdk15on</artifactId>
50         </dependency>
51         <!-- cc加密 -->
52         <dependency>
53             <groupId>commons-codec</groupId>
54             <artifactId>commons-codec</artifactId>
55         </dependency>
56         <!-- guava cache -->
57         <dependency>
58             <groupId>com.google.guava</groupId>
59             <artifactId>guava</artifactId>
60             <version>14.0.1</version>
61         </dependency>
62         <!-- 引入自定義cache模塊 -->
63         <dependency>
64             <groupId>com.xxx.ssmm0</groupId>
65             <artifactId>ssmm0-cache</artifactId>
66             <version>1.0-SNAPSHOT</version>
67         </dependency>
68     </dependencies>
69 </project>
View Code

說明:只引入了上邊的ssmm0-cache模塊。

3.3.2、Admin

 1 package com.xxx.model.userManagement;
 2 
 3 import java.io.Serializable;
 4 
 5 import com.alibaba.fastjson.JSON;
 6 
 7 /**
 8  * 管理員 
 9  * 這里序列化,是為了向xmemcached中存儲,否則會報異常;
10  * 當然除了用序列化之外,還可以將admin對象轉化為json串,然后進行存儲
11  */
12 public class Admin implements Serializable{
13     
14     private static final long serialVersionUID = 7149009421720474527L;
15     
16     private int id;
17     private String username;
18     private String password;
19 
20     public int getId() {
21         return id;
22     }
23 
24     public void setId(int id) {
25         this.id = id;
26     }
27 
28     public String getUsername() {
29         return username;
30     }
31 
32     public void setUsername(String username) {
33         this.username = username;
34     }
35 
36     public String getPassword() {
37         return password;
38     }
39 
40     public void setPassword(String password) {
41         this.password = password;
42     }
43     
44     //將json串轉為Admin
45     public static Admin parseJsonToAdmin(String jsonStr){
46         try {
47             return JSON.parseObject(jsonStr, Admin.class);
48         } catch (Exception e) {
49             e.printStackTrace();
50             return null;
51         }
52     }
53     
54     //將當前實例轉化為json串
55     public String toJson(){
56         return JSON.toJSONString(this);
57     }
58 }
View Code

說明:這里只添加了讓該類實現java.io.Serializable接口,添加了序列號

注意:在實際使用中,把對象存入緩存有兩種方式

  • 序列化:使用上述的方式,或者使用其他序列化方式
  • 將對象轉化為json串,在該類中,有兩個方法:一個將Admin-->Json,一個將Json-->Admin

這兩種方式都可以,只是Java默認的序列化效率低且生成的碼流大,但是使用方便,當然第二種方式使用也相當簡單。

關於各種序列化的方式以及優缺點對比,查看《netty權威指南(第2版)》,或者查看我的"Java高效使用"系列的后續文章

3.3.3、AdminMapper

1     /**************memcached**************/
2 
3     @Select("SELECT * FROM userinfo WHERE id = #{id}")
4     @Results(value = { 
5             @Result(id = true, column = "id", property = "id"),
6             @Result(column = "username", property = "username"),
7             @Result(column = "password", property = "password") })
8     public Admin selectById(@Param("id") int id);
View Code

說明:添加了上述按照ID查找用戶的方法。

3.3.4、AdminDao

1     /******************memcached********************/
2     public Admin getUserById(int id){
3         return adminMapper.selectById(id);
4     }
View Code

說明:添加了上述方法。

3.3.5、AdminService

 1     /*********************memcached********************/
 2     public Admin findAdminById(int id) {
 3         //從緩存中獲取數據
 4         Admin admin = (Admin)MemcachedUtil.getCache(CachePrefix.USER_MANAGEMENT, String.valueOf(id));
 5         //若緩存中有,直接返回
 6         if(admin != null){
 7             return admin;
 8         }
 9         //若緩存中沒有,從數據庫查詢
10         admin = adminDao.getUserById(id);
11         //若查詢出的數據不為null
12         if(admin!=null){
13             //將數據存入緩存
14             MemcachedUtil.setCacheWithNoReply(CachePrefix.USER_MANAGEMENT, String.valueOf(id), admin);
15         }
16         //返回從數據庫查詢的admin(當然也可能數據庫中也沒有,就是null)
17         return admin;
18     }
View Code

說明:添加了上述方法。

注意:

上述方法是緩存使用中最常見的模式,即"從緩存獲取-->若沒有,從數據庫查詢,存入緩存-->返回數據",這就是guava cache的get(Object key)使用一個方法完成的原子操作。

 

3.4、ssmm0-userManagement

在該模塊中,只在一個類中添加了一個方法。

AdminController.java

1     /**
2      * 根據id查找Admin
3      */
4     @ResponseBody
5     @RequestMapping("/findAdminById")
6     public Admin findAdminById(@RequestParam(value="id") int id){
7         
8         return adminService.findAdminById(id);
9     }
View Code

說明:下邊這個方法就是該模塊中唯一添加的一個方法。

 

4、測試

在瀏覽器輸入"localhost:8080/ssmm0-userManagement/admin/findAdminById?id=1",這樣就可以測試緩存,具體測試方式看《第七章 企業項目開發--本地緩存guava cache

這里要說明的是兩點:

  • 由於在根pom.xml文件中將dev改成了服務器啟動后默認使用的環境,所以在之后的測試中,不需要再修改環境了,但是實際上線時,一定要將prod改成默認環境才行
  • 我想在上述URL中不輸入項目名ssmm0-userManagement也可以訪問相關資源,使用如下方式:run as-->run configurations..-->Context參數改為"/"即可

關於memcached的相關內容和Xmemcached的相關內容,請參看下邊鏈接或者我的"Java緩存相關"的后續文章:

http://code.google.com/p/memcached/wiki/NewStart?tm=6

https://code.google.com/p/xmemcached/wiki/User_Guide_zh


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM