注意:本节代码基于《第七章 企业项目开发--本地缓存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>

说明:这里给出了完整版,实际上只做了以下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>

说明:在该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}

说明:这里需要从根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. public static boolean toBoolean(String str, boolean defaultValue) {
  77. if(str == null) {
  78. return defaultValue;
  79. }
  80. return Boolean.parseBoolean(str);
  81. }
  82.  
  83. /**
  84. * 测试
  85. */
  86. public static void main(String[] args) {
  87. Properties props = FileUtil.loadProps("cache_config.properties");
  88. //System.out.println(props);
  89. System.out.println(props.getProperty("memcached.servers", "123"));//从属性文件中读取string
  90. System.out.println(FileUtil.getInt(props, "httpclient.max.conn.per.route2", 10));//属性文件中没有这个key
  91. }
  92. }

说明:对于该类的介绍与注意点,参看"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. }

说明:在该类中定义一些缓存的前缀

作用:

  • 防止缓存键值重复(通常情况下,每一种业务对应一种前缀
  • 以起到根据前缀分类的作用(后者主要是在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. import net.rubyeye.xmemcached.MemcachedClient;
  15. import net.rubyeye.xmemcached.MemcachedClientBuilder;
  16. import net.rubyeye.xmemcached.XMemcachedClientBuilder;
  17. import net.rubyeye.xmemcached.command.BinaryCommandFactory;
  18. import net.rubyeye.xmemcached.exception.MemcachedException;
  19. import net.rubyeye.xmemcached.impl.KetamaMemcachedSessionLocator;
  20. import net.rubyeye.xmemcached.transcoders.CompressionMode;
  21. import net.rubyeye.xmemcached.transcoders.SerializingTranscoder;
  22. import net.rubyeye.xmemcached.utils.AddrUtil;
  23.  
  24. /**
  25. * memcached工具类(基于Xmemcached实现)
  26. */
  27. public class MemcachedUtil {
  28. private static Map<Integer, MemcachedClient> clientMap
  29. = new HashMap<Integer, MemcachedClient>();//client的集合
  30. private static int maxClient = 3;
  31. private static int expireTime = 900;//900s(默认的缓存过期时间)
  32. private static int maxConnectionPoolSize = 1;//每个客户端池子的连接数
  33. private static long op_time = 2000L;//操作超时时间
  34.  
  35. private static final String KEY_SPLIT = "-";//用于隔开缓存前缀与缓存键值
  36.  
  37. /**
  38. * 构建MemcachedClient的map集合
  39. */
  40. static{
  41. //读取配置文件
  42. Properties props = FileUtil.loadProps("cache_config.properties");
  43. String servers = props.getProperty("memcached.servers", "127.0.0.1:11211");//获取memcached servers集合
  44. maxClient = FileUtil.getInt(props, "", maxClient);
  45. expireTime = FileUtil.getInt(props, "memcached.expiretime", expireTime);
  46. maxConnectionPoolSize = FileUtil.getInt(props, "memcached.connection.poolsize", maxConnectionPoolSize);
  47. op_time = FileUtil.getLong(props, "memcached.op.timeout", op_time);
  48.  
  49. if(StringUtils.isNotBlank(servers)){
  50. MemcachedClientBuilder builder = new XMemcachedClientBuilder(AddrUtil.getAddresses(servers));
  51. builder.setConnectionPoolSize(1);//这个默认也是1
  52. builder.setSessionLocator(new KetamaMemcachedSessionLocator(true));//使用一致性hash算法
  53.  
  54. SerializingTranscoder transcoder = new SerializingTranscoder(1024*1024);//序列化转换器,指定最大的数据大小1M
  55. transcoder.setCharset("UTF-8");//默认为UTF-8,这里可去掉
  56. transcoder.setCompressionThreshold(1024*1024);//单位:字节,压缩边界值,任何一个大于该边界值(这里是:1M)的数据都要进行压缩
  57. transcoder.setCompressionMode(CompressionMode.GZIP);//压缩算法
  58.  
  59. builder.setTranscoder(transcoder);
  60. builder.setCommandFactory(new BinaryCommandFactory());//命令工厂
  61.  
  62. //构建10个MemcachedCient,并放入clientMap
  63. for(int i=0;i<maxClient;i++){
  64. try {
  65. MemcachedClient client = builder.build();
  66. client.setOpTimeout(op_time);//设置操作超时时间,默认为1s
  67. clientMap.put(i, client);
  68. } catch (IOException e) {
  69. e.printStackTrace();
  70. }
  71. }
  72. }
  73. }
  74.  
  75. /**
  76. * 从MemcachedClient中取出一个MemcachedClient
  77. */
  78. public static MemcachedClient getMemcachedClient(){
  79. /*
  80. * Math.random():产生[0,1)之间的小数
  81. * Math.random()*maxClient:[0~maxClient)之间的小数
  82. * (int)(Math.random()*maxClient):[0~maxClient)之间的整数
  83. */
  84. return clientMap.get((int)(Math.random()*maxClient));
  85. }
  86.  
  87. /**
  88. * 设置缓存
  89. * @param keyPrefix 缓存的键的前缀
  90. * @param key 缓存的键
  91. * @param value 缓存的值
  92. * @param exp 缓存过期时间
  93. */
  94. public static void setCacheWithNoReply(CachePrefix keyPrefix,
  95. String key,
  96. Object value,
  97. int exp){
  98. try {
  99. MemcachedClient client = getMemcachedClient();
  100. client.addWithNoReply(keyPrefix+KEY_SPLIT+key, exp, value);
  101. } catch (InterruptedException e) {
  102. e.printStackTrace();
  103. } catch (MemcachedException e) {
  104. e.printStackTrace();
  105. }
  106. }
  107.  
  108. /**
  109. * 设置缓存
  110. * @param exp 缓存过期时间(默认时间)
  111. */
  112. public static void setCacheWithNoReply(CachePrefix keyPrefix,
  113. String key,
  114. Object value){
  115. setCacheWithNoReply(keyPrefix, key, value, expireTime);
  116. }
  117.  
  118. /**
  119. * 设置缓存,并返回缓存成功与否
  120. * 注意:
  121. * 1、设置已经设置过的key-value,将会返回false
  122. */
  123. public static boolean setCache(CachePrefix keyPrefix,
  124. String key,
  125. Object value,
  126. int exp){
  127. boolean setCacheSuccess = false;
  128. try {
  129. MemcachedClient client = getMemcachedClient();
  130. setCacheSuccess = client.add(keyPrefix+KEY_SPLIT+key, exp, value);
  131. } catch (TimeoutException e) {
  132. e.printStackTrace();
  133. } catch (InterruptedException e) {
  134. e.printStackTrace();
  135. } catch (MemcachedException e) {
  136. e.printStackTrace();
  137. }
  138. return setCacheSuccess;
  139. }
  140.  
  141. /**
  142. * 设置缓存,并返回缓存成功与否(缓存超时时间采用默认)
  143. * @param key
  144. * @param value
  145. */
  146. public static boolean setCache(CachePrefix keyPrefix,
  147. String key,
  148. Object value){
  149. return setCache(keyPrefix, key, value, expireTime);
  150. }
  151.  
  152. /**
  153. * 获取缓存
  154. */
  155. public static Object getCache(CachePrefix keyPrefix, String key){
  156. Object value = null;
  157. try {
  158. MemcachedClient client = getMemcachedClient();
  159. value = client.get(keyPrefix+KEY_SPLIT+key);
  160. } catch (TimeoutException e) {
  161. e.printStackTrace();
  162. } catch (InterruptedException e) {
  163. e.printStackTrace();
  164. } catch (MemcachedException e) {
  165. e.printStackTrace();
  166. }
  167. return value;
  168. }
  169.  
  170. /**
  171. * 删除缓存
  172. */
  173. public static void removeCacheWithNoReply(CachePrefix keyPrefix, String key){
  174. try {
  175. MemcachedClient client = getMemcachedClient();
  176. client.deleteWithNoReply(keyPrefix+KEY_SPLIT+key);
  177. } catch (InterruptedException e) {
  178. e.printStackTrace();
  179. } catch (MemcachedException e) {
  180. e.printStackTrace();
  181. }
  182. }
  183.  
  184. /**
  185. * 删除缓存,并返回删除成功与否
  186. */
  187. public static boolean removeCache(CachePrefix keyPrefix, String key){
  188. boolean removeCacheSuccess = false;
  189. try {
  190. MemcachedClient client = getMemcachedClient();
  191. removeCacheSuccess = client.delete(keyPrefix+KEY_SPLIT+key);
  192. } catch (TimeoutException e) {
  193. e.printStackTrace();
  194. } catch (InterruptedException e) {
  195. e.printStackTrace();
  196. } catch (MemcachedException e) {
  197. e.printStackTrace();
  198. }
  199. return removeCacheSuccess;
  200. }
  201.  
  202. /**
  203. * 测试
  204. * @param args
  205. */
  206. public static void main(String[] args) {
  207. /*for(int i=0;i<100;i++){
  208. System.out.println(Math.random());
  209. }*/
  210. System.out.println(MemcachedUtil.setCache(CachePrefix.USER_MANAGEMENT,"hello4", "world"));
  211. System.out.println(MemcachedUtil.getCache(CachePrefix.USER_MANAGEMENT,"hello4"));
  212. /*System.out.println(MemcachedUtil.getCache("hello2"));
  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. }
  218. }

首先给出该类的一个图(不知道该怎么称呼这个图)

说明:上述的图可以根据源代码的追踪画出来;该类是基于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>

说明:只引入了上边的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. }

说明:这里只添加了让该类实现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);

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

3.3.4、AdminDao

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

说明:添加了上述方法。

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. }

说明:添加了上述方法。

注意:

上述方法是缓存使用中最常见的模式,即"从缓存获取-->若没有,从数据库查询,存入缓存-->返回数据",这就是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. }

说明:下边这个方法就是该模块中唯一添加的一个方法。

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

第八章 企业项目开发--分布式缓存memcached的更多相关文章

  1. 企业项目开发--分布式缓存memcached(3)

    此文已由作者赵计刚授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 3.3.ssmm0-data 结构: 3.3.1.pom.xml  1 <?xml version=& ...

  2. 企业项目开发--分布式缓存Redis

    第九章 企业项目开发--分布式缓存Redis(1) 注意:本章代码将会建立在上一章的代码基础上,上一章链接<第八章 企业项目开发--分布式缓存memcached> 1.为什么用Redis ...

  3. 第九章 企业项目开发--分布式缓存Redis(1)

    注意:本章代码将会建立在上一章的代码基础上,上一章链接<第八章 企业项目开发--分布式缓存memcached> 1.为什么用Redis 1.1.为什么用分布式缓存(或者说本地缓存存在的问题 ...

  4. 第十章 企业项目开发--分布式缓存Redis(2)

    注意:本章代码是在上一章的基础上进行添加修改,上一章链接<第九章 企业项目开发--分布式缓存Redis(1)> 上一章说了ShardedJedisPool的创建过程,以及redis五种数据 ...

  5. 第七章 企业项目开发--本地缓存guava cache

    1.在实际项目开发中,会使用到很多缓存技术,而且数据库的设计一般也会依赖于有缓存的情况下设计. 常用的缓存分两种:本地缓存和分布式缓存. 常用的本地缓存是guava cache,本章主要介绍guava ...

  6. 企业项目开发--本地缓存guava cache(1)

    此文已由作者赵计刚授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 1.在实际项目开发中,会使用到很多缓存技术,而且数据库的设计一般也会依赖于有缓存的情况下设计. 常用的缓存分 ...

  7. 企业项目开发--本地缓存guava cache(2)

    此文已由作者赵计刚授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. AdminCacheKey: package com.xxx.vo.userManagement; /** ...

  8. 第十一章 企业项目开发--消息队列activemq

    注意:本章代码基于 第十章 企业项目开发--分布式缓存Redis(2) 代码的github地址:https://github.com/zhaojigang/ssmm0 消息队列是分布式系统中实现RPC ...

  9. CYQ.Data V5 分布式缓存MemCached应用开发介绍

    前言 今天大伙还在热议关于.NET Core的东西,我只想说一句:在.NET 跨平台叫了这么多年间,其实人们期待的是一个知名的跨平台案例,而不是一堆能跨平台的消息. 好,回头说说框架: 在框架完成数据 ...

随机推荐

  1. 对于家政020 APP平台如何走出资本寒冬?

    成都亿合科技小编了解到,随着O2O烧钱大战过去,网络上流传的一份O2O项目死亡名单上显示,近年来,汽车.社区.旅游.教育等16个领域的多个O2O项目关门大吉,仅外卖餐饮O2O项目倒闭的就有十几个.只有 ...

  2. Neither the JAVA_HOME nor the JRE_HOME environment variable is defined At least one of these environment variable is needed to run this program

    开启tomcat时出现以上错误 // 进入root帐户 在普通用户也可以 sudo -s //用编辑器打开.bashrc文件 gedit .bashrc 在普通用户下注意加上sudo //在最后一行添 ...

  3. Oracle常用操作-----(二)

    Oracle主要类型函数: 单行函数:只能输入一行结果,返回一个结果.常见的单行函数有: 字符函数 数字函数 转换函数 日期函数 2.聚合函数:同时可以对多行数据进行操作,并返回一个结果.(AVG.S ...

  4. 解决Ruby在IE11中报Unable to get browser (Selenium::WebDriver::Error::NoSuchWindowError)的错误

    转载地址:http://www.tuicool.com/articles/BRnqeu2 I was updating the browser WebDrivers for    Seleno    ...

  5. 0512 Scrum 4.0

    看板设计 每日例会时间定于下午放学回到宿舍,地点是在宿舍外的走廊或宿舍里,特殊情况待定: 团队开会照片 任务认领: 首页设计-------王俊杰 鸡汤版面-------列志华 论“汤”版面------ ...

  6. opencv的学习笔记2

    继续昨晚的学习总结,昨晚看到轨迹条的创建就没有看下去了,今天继续: 1.轨迹条的创建: 轨迹条往往会和一个回调函数配合使用,当轨迹条发生改变,就调用这个轨迹条的回调函数 int createTrack ...

  7. 初识Python第二天(1)

    在Python中,一切事物都是对象,对象是基于类创建的,对象继承了类的属性,方法等. 一.传递参数 1.1新建python文件,名为twoday_args.py,输出以下代码 import sys p ...

  8. Object类型与Array类型

    总结--JS中的引用类型: Object类型,Array类型,Boolean类型,Number类型,String类型,Date类型, Function类型,RegExp类型,单体内置对象(Global ...

  9. js与jsp

    jsp :j2ee 中的一样模版技术,运行于服务器端javascript :一种运行于客户端的脚本语言,动态性.JavaScript是一种采用事件驱动的脚本语言,它不需要经过Web服务器就可以对用户的 ...

  10. Js 实现tab切换效果

    今天商城系统的后台要添加一个Tab切换的效果,一开始没有思路想要自己去实践这个效果 从网上找jquery 已经有了很好看的案例,实现之后我来学习下思路是如何完成的