你的位置:首页 > Java教程

[Java教程]第八章 企业项目开发


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

 1 <??> 2 <project ="http://maven.apache.org/POM/4.0.0" ="http://www.w3.org/2001/ 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.176         , 值得注意的是,如果src/main/resources下还有其他文件,而你不想让其读pom.177         如下边的注释那样,否则,会报这些文件找不到的错误 178       -->179       <resource>180         <directory>src/main/resources</directory>181         <filtering>true</filtering>182         <includes>183           <include>*.</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.-->201           <include>mapper/**/*.</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.233           在使用后者的时候,注意三点:234           1)需要修改上边的build中的内容 235           2)需要在spring.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.
  • 在dev环境下配置了与memcached相关的服务器列表及参数
  • 最后一点,只是为了测试方便,将dev设为默认选用的环境,而prod不是,在实际上线之前,一定要改回来

注意:

  • 在pom.每台服务器之间用空格隔开,这与Xmemcached的AddrUtil读取服务器列表的方式有关。

 

3.2、ssmm0-cache

模块结构:

3.2.1、pom.

 1 <??> 2 <project ="http://maven.apache.org/POM/4.0.0" ="http://www.w3.org/2001/ 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.

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.

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    * 加载属性文件*.properties16    * @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"));//从属性文件中读取string91     System.out.println(FileUtil.getInt(props, "httpclient.max.conn.per.route2", 10));//属性文件中没有这个key92   }93 }

View Code

说明:对于该类的介绍与注意点,参看"Java文件相关"系列的《第一章 属性文件操作工具类》

在该类中,添加了一些方法

  • 从属性文件中将String转化为long型数据,与String转int一样,使用NumberUtil即可
  • 从属性文件中将String转化为Boolean型数据,直接参考NumberUtil的相关源代码进行编写即可
  • 从属性文件中读取String,props.getProperties(String key, String defaultValue)即可

注意:

  • 如果直接运行该类中的main方法来读取properties文件中由根pom.

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,将会返回false123   */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 key145    * @param value146   */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 args206   */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,下载后,解压,双击解压后的"x86"(32位)或"x64"(64位)文件夹中的memcached.exe,跳出窗口即可。

当然,我们在实际使用中,会装在Linux系统上,同时还需要制定一系列参数,例如分配的最大内存、最大并发数等等。

3.3、ssmm0-data

结构:

3.3.1、pom.

 1 <??> 2 <project ="http://maven.apache.org/POM/4.0.0" ="http://www.w3.org/2001/ 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串转为Admin45   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     //若查询出的数据不为null12     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查找Admin3   */4   @ResponseBody5   @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.
  • 我想在上述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