最近在学习redis,在网上查了些文章,利用他人已有的知识,总结写下了这篇文章,大部分内容还是引用别人的文章内容。经过测试发现spring-data-redis现在有的版本只能支持reids 2.6和2.8版本,更高版本尚未支持。还是直接使用jedis比较灵活。
redis
- redis安装
redis的安装过程在以前的博文中已经详细介绍 linux下安装redis并自启动
- jedis
jedis下载地址:https://github.com/xetorthio/jedis
jedis社区地址:https://groups.google.com/forum/#!forum/jedis_redis
最简单的Jedis访问redis
- jedis官方示例
1、键值读取
Jedis jedis = new Jedis("redis服务器ip地址", 6379); jedis.set("foo", "123456"); String value = jedis.get("foo"); System.out.println("____________value="+value);
2、集群读取
Set<HostAndPort> jedisClusterNodes = new HashSet<HostAndPort>(); //Jedis Cluster will attempt to discover cluster nodes automatically jedisClusterNodes.add(new HostAndPort("127.0.0.1", 7379)); JedisCluster jc = new JedisCluster(jedisClusterNodes); jc.set("foo", "bar"); String value = jc.get("foo");
jedis 池和sharding基本示例
redis的基本配置信息
将以下内容放在redis.properties或者文件中,后面有关的.properties文件的内容都跟下面一样的内容。
redis.host=192.168.1.100 redis.port=6379 redis.pass=123456 redis.default.db=0 redis.timeout=100000//客户端超时时间单位是毫秒 redis.maxActive=300// 最大连接数 redis.maxIdle=100//最大空闲数 redis.maxWait=1000//最大建立连接等待时间 redis.testOnBorrow=true redis.testOnReturn=true;
java读取配置文件
ResourceBundle bundle = ResourceBundle.getBundle("redis"); if (bundle == null) { throw new IllegalArgumentException( "[redis.properties] is not found!"); }
redis池
jedisc池需要commons-pool.jar的支持。
- 池对象的获取与回收
在没有使用spring-data-redis的情况下,需要手工获取池对象,并在使用完毕后放回对象池中。
在使用redis池,需要通过以下代码方式从pool中获取资源。
jedisPool.getResource()
资源使用完毕后需要放入pool中
jedisPool.returnResource(jedis);
具体的示例代码
public class MyJedisPool { // jedis池 private static JedisPool pool; // 静态代码初始化池配置 static { // 加载redis配置文件 ResourceBundle bundle = ResourceBundle.getBundle("redis"); if (bundle == null) { throw new IllegalArgumentException("[redis.properties] is not found!"); } // 创建jedis池配置实例 JedisPoolConfig config = new JedisPoolConfig(); // 设置池配置项值 config.setMaxActive(Integer.valueOf(bundle.getString("redis.pool.maxActive"))); config.setMaxIdle(Integer.valueOf(bundle.getString("redis.pool.maxIdle"))); config.setMaxWait(Long.valueOf(bundle.getString("redis.pool.maxWait"))); config.setTestOnBorrow(Boolean.valueOf(bundle.getString("redis.pool.testOnBorrow"))); config.setTestOnReturn(Boolean.valueOf(bundle.getString("redis.pool.testOnReturn"))); //根据配置实例化jedis池 pool = new JedisPool(config, bundle.getString("redis.ip"), Integer.valueOf(bundle.getString("redis.port"))); } /** * 测试jedis池方法 */ public static void test1() { // 从jedis池中获取一个jedis实例 Jedis jedis = pool.getResource(); // 获取jedis实例后可以对redis服务进行一系列的操作 jedis.set("name", "xmong"); System.out.println(jedis.get("name")); jedis.del("name"); System.out.println(jedis.exists("name")); // 释放对象池,即获取jedis实例使用后要将对象还回去 pool.returnResource(jedis); } }
redis分布式
- 分布式配置信息
#redis1服务器ip # Redis1.ip=172.30.5.113 #redis2服务器ip # Redis2.ip=172.30.5.117 #redis服务器端口号# redis.port=6379
- 分布式读取方法
public class MyJedisPool { // jedis池 private static JedisPool pool; // shardedJedis池 private static ShardedJedisPool shardPool; // 静态代码初始化池配置 static { // 加载redis配置文件 ResourceBundle bundle = ResourceBundle.getBundle("redis"); if (bundle == null) { throw new IllegalArgumentException("[redis.properties] is not found!"); } // 创建jedis池配置实例 JedisPoolConfig config = new JedisPoolConfig(); // 设置池配置项值 config.setMaxActive(Integer.valueOf(bundle.getString("redis.pool.maxActive"))); config.setMaxIdle(Integer.valueOf(bundle.getString("redis.pool.maxIdle"))); config.setMaxWait(Long.valueOf(bundle.getString("redis.pool.maxWait"))); config.setTestOnBorrow(Boolean.valueOf(bundle.getString("redis.pool.testOnBorrow"))); config.setTestOnReturn(Boolean.valueOf(bundle.getString("redis.pool.testOnReturn"))); // 根据配置实例化jedis池 // pool = new JedisPool(config, bundle.getString("redis.ip"), // Integer.valueOf(bundle.getString("redis.port"))); // 创建多个redis共享服务 JedisShardInfo jedisShardInfo1 = new JedisShardInfo(bundle.getString("redis1.ip"), Integer.valueOf(bundle.getString("redis.port"))); JedisShardInfo jedisShardInfo2 = new JedisShardInfo(bundle.getString("redis2.ip"), Integer.valueOf(bundle.getString("redis.port"))); List<JedisShardInfo> list = new LinkedList<JedisShardInfo>(); list.add(jedisShardInfo1); list.add(jedisShardInfo2); // 根据配置文件,创建shared池实例 shardPool = new ShardedJedisPool(config, list); } /** * 测试jedis池方法 */ public static void test1() { // 从jedis池中获取一个jedis实例 Jedis jedis = pool.getResource(); // 获取jedis实例后可以对redis服务进行一系列的操作 jedis.set("name", "xmong"); System.out.println(jedis.get("name")); jedis.del("name"); System.out.println(jedis.exists("name")); // 释放对象池,即获取jedis实例使用后要将对象还回去 pool.returnResource(jedis); } /** * 测试shardedJedis池方法 */ public static void test2() { // 从shard池中获取shardJedis实例 ShardedJedis shardJedis = shardPool.getResource(); // 向redis服务插入两个key-value对象 shardJedis.set("aaa", "xmong_aaa"); System.out.println(shardJedis.get("aaa")); shardJedis.set("zzz", "xmong_zzz"); System.out.println(shardJedis.get("zzz")); // 释放资源 shardPool.returnResource(shardJedis); } public static void main(String[] args) { // test1();//执行test1方法 test2();// 执行test2方法 } }
spinrg 整合redis
spring结合redis配置连接池
<bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig"> <property name="maxActive" value="32"></property> <property name="maxIdle" value="6"></property> <property name="maxWait" value="15000"></property> <property name="minEvictableIdleTimeMillis" value="300000"></property> <property name="numTestsPerEvictionRun" value="3"></property> <property name="timeBetweenEvictionRunsMillis" value="60000"></property> <property name="whenExhaustedAction" value="1"></property> </bean> <bean id="jedisPool" class="redis.clients.jedis.JedisPool" destroy-method="destroy"> <!-- config --> <constructor-arg ref="jedisPoolConfig"></constructor-arg> <!-- host --> <constructor-arg value="127.0.0.1"></constructor-arg> <!-- port --> <constructor-arg value="6379"></constructor-arg> <!-- timeout --> <constructor-arg value="15000"></constructor-arg> <!-- password --> <constructor-arg value="0123456"></constructor-arg> <!-- database index --> <constructor-arg value="12"></constructor-arg> </bean>
测试类
public static void main(String[] args) { //resources/beans.xml ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:beans.xml"); JedisPool jedisPool = (JedisPool)context.getBean("jedisPool"); Jedis client = jedisPool.getResource(); try{ client.select(0); client.set("k1", "v1"); System.out.println(client.get("k1")); }catch(Exception e){ e.printStackTrace(); }finally{ jedisPool.returnResource(client);//must be } }
new ClassPathXmlApplicationContext("classpath:beans.xml");是直接读取beans.xml文件,因此需要将上面的配置内容放在beans.xml中,来获取bean对象实例。
spring 结合redis sharding
<context:property-placeholder location="classpath:redis.properties" /> <context:component-scan base-package="com.d.work.main"> </context:component-scan> <context:component-scan base-package="com.d.work.redis"> </context:component-scan> <bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig"> <property name="maxActive" value="50" /> <property name="maxIdle" value="8" /> <property name="maxWait" value="1000" /> <property name="testOnBorrow" value="true"/> <property name="testOnReturn" value="true"/> <!-- <property name="testWhileIdle" value="true"/> --> </bean> <bean id="shardedJedisPool" class="redis.clients.jedis.ShardedJedisPool" scope="singleton"> <constructor-arg index="0" ref="jedisPoolConfig" /> <constructor-arg index="1"> <list> <bean class="redis.clients.jedis.JedisShardInfo"> <constructor-arg name="host" value="${redis.host}" /> <constructor-arg name="port" value="${redis.port}" /> <constructor-arg name="timeout" value="${redis.timeout}" /> <constructor-arg name="weight" value="1" /> </bean> </list> </constructor-arg> </bean>
Spring-data-redis
spring 提供jsmTemplement,jdbcTemplement,redisTemplement等类似模板。spring 通过context:property-placeholder实现导入配置文件,context:property-placeholder 标签用来导入properties文件。从而替换${redis.maxIdle}这样的变量。要使用spring-data-redis,需要下载spring-data-redis-1.5.1.RELEASE.jar
spring-data-redis针对redis提供了以下的特性
- 连接池自动管理
提供了一个高度封装的“RedisTemplate”类
- 针对jedis客户端中大量api进行了归类封装,将同一类型操作封装为operation接口
ValueOperations:简单K-V操作
SetOperations:set类型数据操作
ZSetOperations:zset类型数据操作
HashOperations:针对map类型的数据操作
ListOperations:针对list类型的数据操作
- 提供了对key的“bound”(绑定)便捷化操作API,可以通过bound封装指定的key,然后进行一系列的操作而无须“显式”的再次指定Key,即BoundKeyOperations:
BoundValueOperations
BoundSetOperations
BoundListOperations
BoundSetOperations
BoundHashOperations
- 将事务操作封装,有容器控制。
- 针对数据的“序列化/反序列化”,提供了多种可选择策略(RedisSerializer)
JdkSerializationRedisSerializer:POJO对象的存取场景,使用JDK本身序列化机制,将pojo类通过ObjectInputStream/ObjectOutputStream进行序列化操作,最终redis-server中将存储字节序列。是目前最常用的序列化策略。
StringRedisSerializer:Key或者value为字符串的场景,根据指定的charset对数据的字节序列编码成string,是“new String(bytes, charset)”和“string.getBytes(charset)”的直接封装。是最轻量级和高效的策略。
JacksonJsonRedisSerializer:jackson-json工具提供了javabean与json之间的转换能力,可以将pojo实例序列化成json格式存储在redis中,也可以将json格式的数据转换成pojo实例。因为jackson工具在序列化和反序列化时,需要明确指定Class类型,因此此策略封装起来稍微复杂。【需要jackson-mapper-asl工具支持】
OxmSerializer:提供了将javabean与xml之间的转换能力,目前可用的三方支持包括jaxb,apache-xmlbeans;redis存储的数据将是xml工具。不过使用此策略,编程将会有些难度,而且效率最低;不建议使用。【需要spring-oxm模块的支持】
针对“序列化和发序列化”中JdkSerializationRedisSerializer和StringRedisSerializer是最基础的策略,原则上,我们可以将数据存储为任何格式以便应用程序存取和解析(其中应用包括app,hadoop等其他工具),不过在设计时仍然不推荐直接使用“JacksonJsonRedisSerializer”和“OxmSerializer”,因为无论是json还是xml,他们本身仍然是String。
如果你的数据需要被第三方工具解析,那么数据应该使用StringRedisSerializer而不是JdkSerializationRedisSerializer。
如果你的数据格式必须为json或者xml,那么在编程级别,在redisTemplate配置中仍然使用StringRedisSerializer,在存储之前或者读取之后,使用“SerializationUtils”工具转换转换成json或者xml,请参见下文实例。
- 基于设计模式,和JMS开发思路,将pub/sub的API设计进行了封装,使开发更加便捷。
- spring-data-redis中,并没有对sharding提供良好的封装,如果你的架构是基于sharding,那么你需要自己去实现,这也是sdr和jedis相比,唯一缺少的特性。
spinrg-data-redis配置与调用
示例一
<bean id="propertyConfigurerRedis" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="order" value="1" /> <property name="ignoreUnresolvablePlaceholders" value="true" /> <property name="locations"> <list> <value>classpath:config/redis-manager-config.properties</value> </list> </property> </bean> <!-- jedis pool配置 --> <bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig"> <property name="maxActive" value="${redis.maxActive}" /> <property name="maxIdle" value="${redis.maxIdle}" /> <property name="maxWait" value="${redis.maxWait}" /> <property name="testOnBorrow" value="${redis.testOnBorrow}" /> </bean> <!-- spring data redis --> <bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"> <property name="usePool" value="true"></property> <property name="hostName" value="${redis.host}" /> <property name="port" value="${redis.port}" /> <property name="password" value="${redis.pass}" /> <property name="timeout" value="${redis.timeout}" /> <property name="database" value="${redis.default.db}"></property> <constructor-arg index="0" ref="jedisPoolConfig" /> </bean> <bean id="redisTemplate" class="org.springframework.data.redis.core.StringRedisTemplate"> <property name="connectionFactory" ref="jedisConnectionFactory" /> </bean>
public class RedisBase { private StringRedisTemplate template; /** * @return the template */ public StringRedisTemplate getTemplate() { return template; } /** * @param template the template to set */ public void setTemplate(StringRedisTemplate template) { this.template = template; } }
示例二
<bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig"> <property name="maxActive" value="32"></property> <property name="maxIdle" value="6"></property> <property name="maxWait" value="15000"></property> <property name="minEvictableIdleTimeMillis" value="300000"></property> <property name="numTestsPerEvictionRun" value="3"></property> <property name="timeBetweenEvictionRunsMillis" value="60000"></property> <property name="whenExhaustedAction" value="1"></property> </bean> <bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory" destroy-method="destroy"> <property name="poolConfig" ref="jedisPoolConfig"></property> <property name="hostName" value="127.0.0.1"></property> <property name="port" value="6379"></property> <property name="password" value="0123456"></property> <property name="timeout" value="15000"></property> <property name="usePool" value="true"></property> </bean> <bean id="jedisTemplate" class="org.springframework.data.redis.core.RedisTemplate"> <property name="connectionFactory" ref="jedisConnectionFactory"></property> <property name="keySerializer"> <bean class="org.springframework.data.redis.serializer.StringRedisSerializer"/> </property> <property name="valueSerializer"> <bean class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer"/> </property> </bean>
public class SpringDataRedisTestMain { /** * @param args */ public static void main(String[] args) { ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring-redis-beans.xml"); RedisTemplate redisTemplate = (RedisTemplate)context.getBean("jedisTemplate"); //其中key采取了StringRedisSerializer //其中value采取JdkSerializationRedisSerializer ValueOperations<String, User> valueOper = redisTemplate.opsForValue(); User u1 = new User("zhangsan",12); User u2 = new User("lisi",25); valueOper.set("u:u1", u1); valueOper.set("u:u2", u2); System.out.println(valueOper.get("u:u1").getName()); System.out.println(valueOper.get("u:u2").getName()); } /** * 如果使用jdk序列化方式,bean必须实现Serializable,且提供getter/setter方法 * @author qing * */ static class User implements Serializable{ /** * */ private static final long serialVersionUID = -3766780183428993793L; private String name; private Date created; private int age; public User(){} public User(String name,int age){ this.name = name; this.age = age; this.created = new Date(); } public String getName() { return name; } public void setName(String name) { this.name = name; } public Date getCreated() { return created; } public void setCreated(Date created) { this.created = created; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } } }
如果你使用过jedisPool连接池,在数据操作之前,你需要pool.getResource()即从连接池中获取“链接资源”(Jedis),在操作之后,你需要(必须)调用pool.returnResource()将资源归还个连接池。但是,spring-data-redis中,我们似乎并没有直接操作pool,那么spring是如何做到pool管理的呢??一句话:spring的“看门绝技”--callback。
public <T> T execute(RedisCallback<T> action):这个方法是redisTemplate中执行操作的底层方法,任何基于redisTemplate之上的调用(比如,valueOperations)最终都会被封装成RedisCallback,redisTemplate在execute方法中将会直接使用jedis客户端API进行与server通信,而且在如果使用了连接池,则会在操作之后执行returnSource。
java读取bean方式
方式一
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring-redis-beans.xml"); RedisTemplate redisTemplate = (RedisTemplate)context.getBean("jedisTemplate");
方式二
ApplicationContext ctx = new FileSystemXmlApplicationContext("classpath:setup/applicationContext.xml"); cachedClient = (MemCachedClient)ctx.getBean("memcachedClient");
Spring-data-redis: pub/sub消息订阅
redis可以用来做消息订阅操作。
第一步配置
<bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig"> <property name="maxActive" value="32"></property> <property name="maxIdle" value="6"></property> <property name="maxWait" value="15000"></property> <property name="minEvictableIdleTimeMillis" value="300000"></property> <property name="numTestsPerEvictionRun" value="3"></property> <property name="timeBetweenEvictionRunsMillis" value="60000"></property> <property name="whenExhaustedAction" value="1"></property> </bean> <bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory" destroy-method="destroy"> <property name="poolConfig" ref="jedisPoolConfig"></property> <property name="hostName" value="127.0.0.1"></property> <property name="port" value="6379"></property> <property name="password" value="0123456"></property> <property name="timeout" value="15000"></property> <property name="usePool" value="true"></property> </bean> <bean id="jedisTemplate" class="org.springframework.data.redis.core.RedisTemplate"> <property name="connectionFactory" ref="jedisConnectionFactory"></property> <property name="defaultSerializer"> <bean class="org.springframework.data.redis.serializer.StringRedisSerializer"/> </property> </bean> <bean id="topcMessageListener" class="com.sample.redis.sdr.TopicMessageListener"> <property name="redisTemplate" ref="jedisTemplate"></property> </bean> <bean id="topicContainer" class="org.springframework.data.redis.listener.RedisMessageListenerContainer" destroy-method="destroy"> <property name="connectionFactory" ref="jedisConnectionFactory"/> <property name="taskExecutor"><!-- 此处有个奇怪的问题,无法正确使用其他类型的Executor --> <bean class="org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler"> <property name="poolSize" value="3"></property> </bean> </property> <property name="messageListeners"> <map> <entry key-ref="topcMessageListener"> <bean class="org.springframework.data.redis.listener.ChannelTopic"> <constructor-arg value="user:topic"/> </bean> </entry> </map> </property> </bean>
第二部:发布消息
String channel = "user:topic"; //其中channel必须为string,而且“序列化”策略也是StringSerializer //消息内容,将会根据配置文件中指定的valueSerializer进行序列化 //本例中,默认全部采用StringSerializer //那么在消息的subscribe端也要对“发序列化”保持一致。 redisTemplate.convertAndSend(channel, "from app 1");
第三部:消息接收
public class TopicMessageListener implements MessageListener { private RedisTemplate redisTemplate; public void setRedisTemplate(RedisTemplate redisTemplate) { this.redisTemplate = redisTemplate; } @Override public void onMessage(Message message, byte[] pattern) { byte[] body = message.getBody();//请使用valueSerializer byte[] channel = message.getChannel(); //请参考配置文件,本例中key,value的序列化方式均为string。 //其中key必须为stringSerializer。和redisTemplate.convertAndSend对应 String itemValue = (String)redisTemplate.getValueSerializer().deserialize(body); String topic = (String)redisTemplate.getStringSerializer().deserialize(channel); //... } }
spring-data-redis 事物处理
以下示例显示spring-data-redis实现事物处理,并通过回调方法实现返回数据
//execute a transaction List<Object> txResults = redisTemplate.execute(new SessionCallback<List<Object>>() { public List<Object> execute(RedisOperations operations) throws DataAccessException { operations.multi(); operations.opsForSet().add("key", "value1"); // This will contain the results of all ops in the transaction return operations.exec(); } }); System.out.println("Number of items added to set: " + txResults.get(0));
Spring-data-redis: serializer实例
sdr提供了4种内置的serializer:
1、JdkSerializationRedisSerializer:使用JDK的序列化手段(serializable接口,ObjectInputStrean,ObjectOutputStream),数据以字节流存储
2、StringRedisSerializer:字符串编码,数据以string存储
3、JacksonJsonRedisSerializer:json格式存储
4、OxmSerializer:xml格式存储
其中JdkSerializationRedisSerializer和StringRedisSerializer是最基础的序列化策略,其中“JacksonJsonRedisSerializer”与“OxmSerializer”都是基于stirng存储,因此它们是较为“高级”的序列化(最终还是使用string解析以及构建java对象)。
RedisTemplate中需要声明4种serializer,默认为“JdkSerializationRedisSerializer”:
1) keySerializer :对于普通K-V操作时,key采取的序列化策略
2) valueSerializer:value采取的序列化策略
3) hashKeySerializer: 在hash数据结构中,hash-key的序列化策略
4) hashValueSerializer:hash-value的序列化策略
无论如何,建议key/hashKey采用StringRedisSerializer。
配置JdkSerializationRedisSerializer/StringRedisSerializer
<bean id="jedisTemplate" class="org.springframework.data.redis.core.RedisTemplate"> <property name="connectionFactory" ref="jedisConnectionFactory"></property> <property name="keySerializer"> <bean class="org.springframework.data.redis.serializer.StringRedisSerializer"/> </property> <property name="hashKeySerializer"> <bean class="org.springframework.data.redis.serializer.StringRedisSerializer"/> </property> <property name="valueSerializer"> <bean class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer"/> </property> <property name="hashValueSerializer"> <bean class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer"/> </property> </bean>
ValueOperations<String, User> valueOper = redisTemplate.opsForValue(); User user = new User("zhangsan",12); valueOper.set("user:1", user); System.out.println(valueOper.get("user:1").getName());
题外话
spring framework4.X.X版本与spring framework3.X.X有些区别:
spring framework 4.X.X需要jackjson2.0,同时 org.springframework.http.converter.json.MappingJacksonHttpMessageConverter改为 MappingJacksonHttpMessageConverter已经改为MappingJackson2HttpMessageConverter
spring framework 4.X.X配置中使用
<mvc:annotation-driven/>
不再需要手工配置bean
<bean class ="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter" > <property name="messageConverters"> <list> <ref bean="mappingJackson2HttpMessageConverter" /> </list> </property> </bean> <bean name="mappingJackson2HttpMessageConverter" class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"> <property name="supportedMediaTypes"> <list> <value>text/html;charset=UTF-8</value> </list> </property> </bean>
参考资料
http://snowolf.iteye.com/blog/1630697
http://my.oschina.net/gccr/blog/307725
http://blog.163.com/asd_wll/blog/static/210310402013654528316/
http://wenku.baidu.com/link?url=fuS8aw93_4_Qvv8WBgazt5eZGiDhv1Np5vCyB8qBUVdWIUxI47IaA5opzI3vwhWth7MrF1KiJn_o1aBvWmFdeNxbmbcSnyCTEd54C0iLLEC
http://my.oschina.net/gccr/blog/307725
http://shift-alt-ctrl.iteye.com/blog/1886831
http://www.cnblogs.com/liuling/p/2014-4-19-04.html
http://www.cnblogs.com/tankaixiong/p/3660075.html
http://blog.csdn.net/neubuffer/article/details/17003909
http://blog.csdn.net/liuzhigang1237/article/details/8283797
http://shift-alt-ctrl.iteye.com/blog/1887370
http://shift-alt-ctrl.iteye.com/blog/1887473
http://shift-alt-ctrl.iteye.com/blog/1887644
http://shift-alt-ctrl.iteye.com/blog/1887700
http://shift-alt-ctrl.iteye.com/blog/1886831
http://shift-alt-ctrl.iteye.com/blog/1885910
http://www.open-open.com/lib/view/open1385173126448.html
http://blog.csdn.net/A_lele123/article/details/43406547
http://javacrazyer.iteye.com/blog/1840161
http://redis.readthedocs.org/en/2.4/index.html