Spring-Data-Redis项目(简称SDR)对Redis的Key-Value数据存储操作提供了更高层次的抽象,类似于Spring Framework对JDBC支持一样。
项目主页:http://projects.spring.io/spring-data-redis/
项目文档:http://docs.spring.io/spring-data/redis/docs/1.5.0.RELEASE/reference/html/
本文主要介绍Spring Data Redis的实际使用。
1.Spring Data Redis 1.5新特性
增加了Redis HyperLogLog命令PFADD,PFCOUNT,PFMERGE
可以使用Jackson基于RedisSerializer对Java类型序列化
使用PropertySource配置Redis Sentinel连接,目前仅Jedis客户端支持
2.Spring Data Redis ?
Spring Data Redis使得在Spring应用中读写Redis数据库更加容易。
Spring Data Redis提供了四种Redis服务的Java客户端包的集成,分别是Jedis, JRedis, SRP and Lettuce
3.版本要求
Spring Data Redis1.2.x要求JDK1.6+,Spring Framwork3.2.8+
Key-Value存储服务Redis 2.6.x+
4.搭建环境
本文假设已经安装完成了Redis服务,并成功运行。
创建maven项目,添加依赖的Jar,本文主要使用jedis
org.springframework.data spring-data-redis 1.5.0.RELEASE
redis.clients jedis 2.6.2
5.连接Redis服务
在Spring Data Redis中通过org.springframework.data.redis.connection包中的RedisConnection和RedisConnectionFactory类来获取Redis连接。
5.1配置JedisConnectionFactory
5.2配置Jredis,SRP,Lettuce的配置和上面配置类似,只需要配置对应的RedisConnectionFactory接口实现接口,它门位于如下包中:
5.3关于RedisConnectionFactory注意问题
上面四种连接器并不是都支持Redis的所有特性,它们之间有差异性,如果调用方法在Connection API中不支持则抛出“UnsupportedOperationException”异常,具体情况需要了解对应的Redis Java客户端Jar的实现。
5.4关于Redis Sentinel(这里暂称为:哨兵)支持
Redis Sentinel监听主服务,再主服务发生故障时能够切换至从服务,将从服务升为主服务来保证故障恢复,使用该功能需要在JedisConnectionFactory设置RedisSentinelConfiguration属性,目前Jedis对Redis Sentinel提供支持。
编码方式如下:
public RedisConnectionFactory jedisConnectionFactory() { RedisSentinelConfiguration sentinelConfig = new RedisSentinelConfiguration() .master("mymaster") .sentinel("127.0.0.1", 26379) .sentinel("127.0.0.1", 26380); return new JedisConnectionFactory(sentinelConfig); }
在Spring容器中配置:
192.168.88.153:26379 192.168.88.153:26380 192.168.88.153:26382
注意:在配置Redis的sentinel.conf文件时注意使用外部可以访问的ip地址,因为当redis-sentinel服务和redis-server在同一台机器的时候,主服务发生变化时配置文件中将主服务ip变为127.0.0.1,这样外部就无法访问了。如果应用程序,Redis服务在同一台机器则不存在这样的隐患,具体情况则更加实际的网络环境。
配置好之后,在实例化JedisConnectionFactory之后,可见如下日志:
2015-4-1 17:29:30 redis.clients.jedis.JedisSentinelPool initSentinels 信息: Trying to find master from available Sentinels... 2015-4-1 17:29:30 redis.clients.jedis.JedisSentinelPool initSentinels 信息: Redis master running at 192.168.88.153:6384, starting Sentinel listeners... 2015-4-1 17:29:30 redis.clients.jedis.JedisSentinelPool initPool 信息: Created JedisPool to master at 192.168.88.153:6384
实验环境中192.168.88.153:6384的Redis实例是主服务。
5.5下面通过一组代码展示具体使用
@FixMethodOrder(MethodSorters.NAME_ASCENDING) public class TestJedis { public static ApplicationContext ctx; public static JedisConnectionFactory jedisConnetionFactory; public JedisConnection jedisConnection; @SuppressWarnings("unchecked") @BeforeClass public static void setBeforeClass() { ctx = new ClassPathXmlApplicationContext("spring-redis.xml"); jedisConnetionFactory = (JedisConnectionFactory) ctx .getBean("jedisConnectionFactory"); } @Before public void setBefore() { jedisConnection = jedisConnetionFactory.getConnection(); } @After public void setAfter() { jedisConnection.close(); } private void print(Collectionc) { for (Iterator iter = c.iterator(); iter.hasNext();) { RedisServer rs = (RedisServer) iter.next(); System.out.println(rs.getHost() + ":" + rs.getPort()); } } // 简单测试JedisConnection @Ignore @Test public void test1() { if (!jedisConnection.exists(new String("zz").getBytes())) { jedisConnection.set(new String("zz").getBytes(), new String("zz").getBytes()); } } @Ignore @Test public void test2() { Set keys = jedisConnection.keys(new String("*").getBytes()); for (Iterator iter = keys.iterator(); iter.hasNext();) { System.out.println(new String(iter.next())); } } // 测试Sentinel @Ignore @Test public void test3() throws InterruptedException { if (jedisConnetionFactory.getSentinelConnection().isOpen()) { Collection c = jedisConnetionFactory .getSentinelConnection().masters(); print(c); RedisNode rn = new RedisNode("192.168.88.153", 6380); rn.setName("mymaster"); c = jedisConnetionFactory.getSentinelConnection().slaves(rn); print(c); } for (int i = 0; i < 1000; i++) { jedisConnection.set(new String("k" + i).getBytes(), new String("v" + i).getBytes()); Thread.sleep(1000); } Set keys = jedisConnection.keys(new String("k*").getBytes()); Assert.assertEquals(1000, keys.size()); } }
6.RedisTemplate支持
熟悉Spring的JdbcTemplate对象的话,应该大概能猜出来RedisTemplate的作用了,RedisTemplate对象对RedisConnection进行了封装,它提供了连接管理,序列化等功能,它对Redis的交互进行了更高层次的抽象。另外还提供了Redis操作命令的操作视图,这极大的方便和简化了Redis的操作。
下表是具体的操作视图接口类介绍:
Key类型操作 |
|
ValueOperations | Redis String/Value 操作 |
ListOperations | Redis List 操作 |
SetOperations | Redis Set 操作 |
ZSetOperations | Redis Sort Set 操作 |
HashOperations | Redis Hash 操作 |
Value约束操作 | |
BoundValueOperations | Redis String/Value key 约束 |
BoundListOperations | Redis List key 约束 |
BoundSetOperations | Redis Set key 约束 |
BoundZSetOperations | Redis Sort Set key 约束 |
BoundHashOperations | Redis Hash key 约束 |
在org.springframework.data.redis.core包中对表中的接口都提供了相应的默认实现。
6.1RedisSerializer
Spring Data Redis提供了对Key-Value的序列号,在使用RedisTemplate对象是默认使用JdkSerializationRedisSerializer实现。还提供了其它的序列化实现如:Jackson2JsonRedisSerializer,JacksonJsonRedisSerializer,GenericToStringSerializer,StringRedisSerializer,OxmSerializer。
另外用户可以提供自己的序列化实现
6.2配置RedisTemplate
这里配置了RedisTemplate和StringRedisTemplate,不同之处在于StringRedisTemplate的Key-Value序列化使用的是StringRedisSerializer。使用StringRedisTemplate操作Redis之后的结果是读友好的。
另外对Hash类型而言,还有对应的HashKey序列化(其对应于Hash类型的字段名)。
6.3RedisTemplate的使用
// 测试RedisTemplate,自主处理key的可读性(String序列号) @Ignore @Test public void test4() { String key = "spring"; ListOperationslop = redisTemplate.opsForList(); RedisSerializer serializer = new StringRedisSerializer(); redisTemplate.setKeySerializer(serializer); redisTemplate.setValueSerializer(serializer); // rt.setDefaultSerializer(serializer); lop.leftPush(key, "aaa"); lop.leftPush(key, "bbb"); long size = lop.size(key); // rt.boundListOps(key).size(); Assert.assertEquals(2, size); } // 测试便捷对象StringRedisTemplate @Ignore @Test public void test5() { ValueOperations vop = stringRedisTemplate.opsForValue(); String key = "string_redis_template"; String v = "use StringRedisTemplate set k v"; vop.set(key, v); String value = vop.get(key); Assert.assertEquals(v, value); }
具体使用那种序列化策略则更加存储的Key-Value内容做权衡即可。
通过RedisTemplate中的方法的参数RedisCallback回调接口来获取RedisConnection,进一步操作Redis,比如事务控制。需要注意的是如果使用StringRedisTemplate则返回的是StringRedisConnection对象。
测试RedisCallback代码示例:
// 测试Callback @Ignore @Test public void test61() { Long dbsize = (Long) stringRedisTemplate .execute(new RedisCallback
测试SessionCallback代码示例:
@Test public void test62() { List
说明:
在事务中的操作返回都是null,因此不能在execute中对操作的结果进行处理。比如这里get("hkey","k1")的结果。
Hash操作中如果字段存在则返回false(redis中是0),不存在返回true(redis中是1)与字段对应的值是否更新无关联。
exec()方法返回List
中的对象对应事务中执行的每条命令的返回结果。
7.执行Lua脚本
Redis中执行Lua脚本开发思路:http://aiilive.blog.51cto.com/1925756/1626372
Spring Data Redis中执行Lua脚本更加便利,下面示例展示使用RedisTemplate对象执行Lua脚本。
// 测试Lua脚本 @Ignore @Test public void test71() { Listkeys = new ArrayList (); RedisScript script = new DefaultRedisScript ( "local size = redis.call('dbsize'); return size;", Long.class); Long dbsize = stringRedisTemplate .execute(script, keys, new Object[] {}); System.out.println("sha1:" + script.getSha1()); System.out.println("Lua:" + script.getScriptAsString()); System.out.println("dbsize:" + dbsize); } @Test public void test72() { DefaultRedisScript script = new DefaultRedisScript (); /** * isexistskey.lua内容如下: * * return tonumber(redis.call("exists",KEYS[1])) == 1; */ script.setScriptSource(new ResourceScriptSource(new ClassPathResource( "/isexistskey.lua"))); script.setResultType(Boolean.class);// Must Set System.out.println("script:" + script.getScriptAsString()); Boolean isExist = stringRedisTemplate.execute(script, Collections.singletonList("k2"), new Object[] {}); Assert.assertTrue(isExist); }
8.支持类操作
在org.springframework.data.redis.support包中提供了各种可重用组件,这些组件可以应用到Redis存储,如atomic计数,JDK集合,Redis的类型集合(RedisList,RedisSet等)
@Ignore @Test public void test8() { RedisAtomicInteger rai = new RedisAtomicInteger("redis:atomic", jedisConnetionFactory); System.out.println(rai.get()); } // 测试Redis Collection @Ignore @Test public void test9() { @SuppressWarnings("unchecked") RedisListredisList = (RedisList ) ctx .getBean("springList"); redisList.clear(); redisList.addFirst("china"); redisList.add("in"); redisList.add("go"); redisList.addLast("made"); System.out.println(redisList.getKey()); }