赞
踩
首先redis是一种NoSql数据库,也被称为数据结构服务器。通俗点来讲,就是一种运行在内存的数据库。需要说明的是Redis是开源的,可基于内存也可持久化的键值数据库,支持多种语言API 。
因为其是基于内存的,所以运行速度非常快,在速度上一般是常用关系性数据库的几倍到几十倍。通过测试,Redis可以在1s内完成10万次的读写,一般将常用的数据存储在Redis中,代替关系型数据库的查询访问,以提高网站性能。
Jedis就是与Redis连接的驱动。和Jedis类似的驱动还有Lettuce、Jredis和Srp,不过都不及Jedis 常用,或已被淘汰。
在spring中提供了一个底层的RedisConnectionFactory接口,由该接口可以生成一个RedisConnection接口对象,而Redisconnection接口对象是对 Redis底层接口的封装。同时在Spring中提供了Jedisconnection对象,该对象封装了原有的 Jedis ( redis . clientsjedis . Jedis ) 对象,Jedisconnection 是Redisconnection 接口的实现类.具体可看下图:
创建RedisConnectionFactory
package com.gsb.manager.api.redis; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.connection.RedisStandaloneConfiguration; import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.serializer.RedisSerializer; import redis.clients.jedis.JedisPoolConfig; /** * @ClassName: RedisConfig * @Description: * @Date:2019/4/26 11:14 **/ @Configuration public class RedisConfig { private RedisConnectionFactory connectionFactory = null; /** * 创建 RedisConnectionFactory 对象 * @return */ @Bean(name = "redisConnectionFactory") public RedisConnectionFactory innitRedisConnectionFactory() { if (this.connectionFactory != null) return this.connectionFactory; JedisPoolConfig poolConfig = new JedisPoolConfig(); // 最大空闲数 poolConfig.setMaxIdle(50); // 最大连接数 poolConfig.setMaxTotal(80); // 最大等待毫秒数 poolConfig.setMaxWaitMillis(2000); // 创建 Jedis 连接工厂 JedisConnectionFactory connectionFactory = new JedisConnectionFactory(poolConfig); // 获取单机(服务器)的Redis配置 RedisStandaloneConfiguration rCfg = connectionFactory.getStandaloneConfiguration(); // IP connectionFactory.setHostName("192.168.11.141"); // 端口 connectionFactory.setPort(6379); // 密码m connectionFactory.setPassword("admin123456"); this.connectionFactory = connectionFactory; return connectionFactory; } }
在结合Spring或SpringBoot 使用Redis时,我们使用最多的一个类应该是RedisTemplate了,该类对很多具体功能都做了封装。例如:RedisTemplate会自动从上面所说的RedisConnectionFactory工厂中获取连接,然后执行相关的Redis命令,最后关闭Redis连接。
Redis 是基于字符串存储的NoSql,而Java是基于对象的语言,所以对象是无法直接存储到redis中的,不过Java提供了序列化机制,使用中只要实现了java.io.Serializable接口,就代表类的对象能够进行序列化,通过将类对象进行序列化就能得到二进制字符串,redis便能将类对象以字符串进行存储。
同时,Java也可以将那些二进制字符串通过反序列化转为对象。spring 提供了RedisSerializer接口,该接口中比较常用的是serialize方法,该方法能把可序列化的对象转换成二进制字符串。对于序列化,要注意的还有StringRedisSerializer和JdkSerializationRedisSerializer,其中JdkSerializationRedisSerializer 是 RedisTemplate默认的序列化器,对对象进行序列化和反序列化。StringRedisSerializer可以直接从RedisTemplate中获取。
示例代码:
package com.gsb.manager.api.redis; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.connection.RedisStandaloneConfiguration; import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.serializer.RedisSerializer; import redis.clients.jedis.JedisPoolConfig; /** * @ClassName: RedisConfig * @Description: * @Date:2019/4/26 11:14 **/ @Configuration public class RedisConfig { private RedisConnectionFactory connectionFactory = null; /** * 创建 RedisConnectionFactory 对象 * @return */ @Bean(name = "redisConnectionFactory") public RedisConnectionFactory innitRedisConnectionFactory() { if (this.connectionFactory != null) return this.connectionFactory; JedisPoolConfig poolConfig = new JedisPoolConfig(); // 最大空闲数 poolConfig.setMaxIdle(50); // 最大连接数 poolConfig.setMaxTotal(80); // 最大等待毫秒数 poolConfig.setMaxWaitMillis(2000); // 创建 Jedis 连接工厂 JedisConnectionFactory connectionFactory = new JedisConnectionFactory(poolConfig); // 获取单机(服务器)的Redis配置 RedisStandaloneConfiguration rCfg = connectionFactory.getStandaloneConfiguration(); // IP connectionFactory.setHostName("192.168.11.141"); // 端口 connectionFactory.setPort(6379); // 密码m connectionFactory.setPassword("admin123456"); this.connectionFactory = connectionFactory; return connectionFactory; } /** * 创建 RedisTemplate * @return */ @Bean(name = "redisTemplate") public RedisTemplate<Object, Object> initRedisTemplate() { RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>(); // RedisTemplate 会自动初始化 StringRedisSerializer,所以这里直接取值获取 RedisSerializer stringRedisSerializer = redisTemplate.getStringSerializer(); // 设置字符串序列化器,这样Spring 就会把Redis 的 key 当作字符串处理了 redisTemplate.setKeySerializer(stringRedisSerializer); redisTemplate.setHashKeySerializer(stringRedisSerializer); redisTemplate.setHashValueSerializer(stringRedisSerializer); redisTemplate.setConnectionFactory(innitRedisConnectionFactory()); return redisTemplate; } }
SessionCallback 和 RedisCallback 两个接口都是让RedisTemplate进行回调,通过它们可以在同一条连接上执行多个Redis命令,避免了执行一条redis命令创建一个连接的尴尬境地。
对比SessionCallback 和 RedisCallback两个接口,SessionCallback对开发者比较友好,提供更为高级的API,可读性更优良,在实际开发中基本都会优先选择使用它。RedisCallback接口比较底层,需要处理的内容比较多,同时其可读性比较差
示例代码:
package com.gsb.manager.api.redis; import org.springframework.dao.DataAccessException; import org.springframework.data.redis.connection.RedisConnection; import org.springframework.data.redis.core.RedisCallback; import org.springframework.data.redis.core.RedisOperations; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.SessionCallback; /** * @ClassName: CallbackExample * @Description: * @Date:2019/4/26 16:46 **/ public class CallbackExample { /** * 使用RedisCallback * 需要处理底层的转换规则,如果不考虑改写底层,尽量不使用RedisCallback * @param redisTemplate */ public void useRedisCallback(RedisTemplate redisTemplate) { redisTemplate.execute(new RedisCallback() { @Override public Object doInRedis(RedisConnection connection) throws DataAccessException { connection.set("key_1".getBytes(), "value_1".getBytes()); connection.hSet("hash_1".getBytes(), "field".getBytes(), "hvalue_1".getBytes()); return null; } }); } /** * 使用 SessionCallback * 高级友好接口,一般情况下优先使用 SessionCallback * @param redisTemplate */ public void useSessionCallback(RedisTemplate redisTemplate) { redisTemplate.execute(new SessionCallback() { @Override public Object execute(RedisOperations operations) throws DataAccessException { operations.opsForValue().set("key_1", "value_1"); operations.opsForHash().put("hash_1", "field", "hvalue"); return null; } }); } }
这里主要说明Redis的5种数据类型的字符串(String)、散列(Hash)、列表(List)、集合(Set)、有序集合(Zset)。
字符串(String):
string 是 redis 最基本的类型,一个 key 对应一个 value。二进制安全, redis 的 string 可以包含任何数据。比如jpg图片或者序列化的对象。string 类型的值最大能存储 512MB。
散列(Hash):
Redis 的 hash 是一个键值(key=>value)对集合。是一个 string 类型的 field 和 value 的映射表,hash 特别适合用于存储对象。每个 hash 可以存储 232 -1 键值对(40多亿)。
代码示例:
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.BoundHashOperations; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import redis.clients.jedis.Jedis; import java.util.HashMap; import java.util.Map; /** * @ClassName: RedisDataTypeController * @Description: * @Date:2019/4/26 17:43 **/ @Controller @RequestMapping("/redis") public class RedisDataTypeController { @Autowired private StringRedisTemplate stringRedisTemplate; @Autowired private RedisTemplate redisTemplate; @RequestMapping("/stringAndHash") @ResponseBody public Map<String, Object> stringAndHash() { /** redis 赋值 String字符串类型 */ redisTemplate.opsForValue().set("key_1", "value_1"); /** 这里默认使用了JDK的序列化器,故redis 保存时不是整数,不能运算 */ redisTemplate.opsForValue().set("int_key", "1"); stringRedisTemplate.opsForValue().set("int_num", "1"); /** 运算 */ stringRedisTemplate.opsForValue().increment("int_num", 1); /** 获取底层 Jedis 连接 */ Jedis jedis = (Jedis) stringRedisTemplate.getConnectionFactory() .getConnection().getNativeConnection(); /** 减1操作,该命令RedisTemplate不支持,故要先获取底层的连接,然后操作 */ jedis.decr("int_num"); Map<String, String> hash = new HashMap<String, String>(); hash.put("hash_key_1", "hash_value_1"); hash.put("hash_key_2", "hash_value_2"); hash.put("hash_key_3", "hash_value_3"); /** 存储一个散列数据类型 */ stringRedisTemplate.opsForHash().putAll("hash", hash); /** 新增一个字段 */ stringRedisTemplate.opsForHash() .put("hash", "hash_key_4", "hash_value_4"); /** 绑定散列操作的key,便可以对同一个散列数据类型进行连续操作 */ BoundHashOperations hashOperations = stringRedisTemplate.boundHashOps("hash"); /** 删除两个字段 */ hashOperations.delete("hash_key_1", "hash_key_2"); /** 新增一个字段 */ hashOperations.put("hash_key_5", "hash_value_5"); Map<String, Object> map = new HashMap<String, Object>(); map.put("success", true); return map; } }
列表(List):
Redis的列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或尾部(右边)。列表最多可存储 232 - 1 元素 (4294967295, 每个列表可存储40多亿)。
代码示例:
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.BoundHashOperations; import org.springframework.data.redis.core.BoundListOperations; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import redis.clients.jedis.Jedis; import java.util.HashMap; import java.util.List; import java.util.Map; /** * @ClassName: RedisDataTypeController * @Description: * @Date:2019/4/26 17:43 **/ @Controller @RequestMapping("/redis") public class RedisDataTypeController { @Autowired private StringRedisTemplate stringRedisTemplate; @Autowired private RedisTemplate redisTemplate; /** * 链表 List * @return */ @RequestMapping("/list") @ResponseBody public Map<String, Object> listType() { /** 插入两个列表,注意他们在链表的顺序 * 链表从左到右顺序分别为: v10、v8、v6、v4、v2 */ stringRedisTemplate.opsForList().leftPushAll("list_1", "v2", "v4", "v6", "v8", "v10"); /** 链表从左到右顺序分别为: v1、v2、v3、v4、v5、v6 */ /** 绑定list_2 链表操作 */ BoundListOperations listOperations = stringRedisTemplate.boundListOps("list_2"); /** 从右边弹出一个成员 */ Object result1 = listOperations.rightPop(); /** 获取定位元素, Redis 从 0开始己算,这里值为 v2 */ Object result2= listOperations.index(1); /** 从左边插入链表 */ listOperations.leftPush("v0"); /** 求链表长度 */ Long size = listOperations.size(); /** 求链表下标区间成员,整个链表下标范围为0 到size-1,这里不取最后一个元素 */ List elements = listOperations.range(0, size-2); Map<String, Object> map = new HashMap<String, Object>(); map.put("success", true); return map; } }
集合(Set):
Redis 的 Set 是 String 类型的无序集合。集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是O(1)。集合中最大的成员数为 232 - 1(4294967295, 每个集合可存储40多亿个成员)。
sadd 命令:
命令 :sadd key member 。添加一个 string 元素到 key 对应的 set 集合中,成功返回1,如果元素已经在集合中返回 0,如果 key 对应的 set 不存在则返回错误。如添加了两次,但根据集合内元素的唯一性,第二次插入的元素将被忽略。
代码示例:
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.*; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import redis.clients.jedis.Jedis; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; /** * @ClassName: RedisDataTypeController * @Description: * @Date:2019/4/26 17:43 **/ @Controller @RequestMapping("/redis") public class RedisDataTypeController { @Autowired private StringRedisTemplate stringRedisTemplate; @Autowired private RedisTemplate redisTemplate; /** * 集合 Set * @return */ @RequestMapping("/set") @ResponseBody public Map<String, Object> setType() { /** 这里v1重复两次,因为集合不允许重复,所以只插入5个成员到 set 中 */ stringRedisTemplate.opsForSet().add("set_1", "v1", "v1", "v2", "v3", "v4", "v5"); stringRedisTemplate.opsForSet().add("set_2", "v2", "v4", "v6", "v8", "v10"); /** 绑定set_1 集合操作 */ BoundSetOperations setOperations = stringRedisTemplate.boundSetOps("set_1"); /** 增加两个元素 */ setOperations.add("v6", "v7"); /** 删除两个元素 */ setOperations.remove("v1", "v7"); /** 返回所有元素 */ Set set_1 = setOperations.members(); /** 求成员数 */ Long size = setOperations.size(); /** 求交集 */ Set inter = setOperations.intersect("set_2"); /** 求交集,并且用新集合 inter 保存 */ setOperations.intersectAndStore("set_2", "inter"); /** 求差集 */ Set diff = setOperations.diff("set_2"); /** 求差集,并且用新集合 diff 保存 */ setOperations.diffAndStore("set_2", "diff"); /** 求并集 */ Set union = setOperations.union("set_2"); /** 求并集,并且用新集合 union 保存 */ setOperations.unionAndStore("set_2", "union"); Map<String, Object> map = new HashMap<String, Object>(); map.put("success", true); return map; } }
有序集合(Zset):
Redis 的 zset 和 set 一样也是 String 类型元素的集合,且不允许重复的成员。不同的是每个元素都会关联一个double类型的分数。redis正是通过分数来为集合中的成员进行从小到大的排序。zset的成员是唯一的,但分数(score)却可以重复。
zadd 命令:
命令:zadd key score member 。添加元素到集合,元素在集合中存在则更新对应score
代码示例:
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.connection.RedisZSetCommands; import org.springframework.data.redis.core.*; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import redis.clients.jedis.Jedis; import java.util.*; /** * @ClassName: RedisDataTypeController * @Description: * @Date:2019/4/26 17:43 **/ @Controller @RequestMapping("/redis") public class RedisDataTypeController { @Autowired private StringRedisTemplate stringRedisTemplate; @Autowired private RedisTemplate redisTemplate; /** * 有序集合 Zset * @return */ @RequestMapping("/zset") @ResponseBody public Map<String, Object> zsetType() { Set<ZSetOperations.TypedTuple<String>> typedTupleSet = new HashSet<>(); for (int i=1; i<=9; i++) { // 分数 double score = i*0.1; // 创建一个TypedTuple对象,存入值和分数 ZSetOperations.TypedTuple<String> typedTuple = new DefaultTypedTuple<String>("value"+i, score); typedTupleSet.add(typedTuple); } // 往有序集合插入元素 stringRedisTemplate.opsForZSet().add("zset_1", typedTupleSet); // 绑定 zset_1 有序集合操作 BoundZSetOperations<String, String> zSetOperations = stringRedisTemplate.boundZSetOps("set_1"); // 增加一个元素 zSetOperations.add("value10", 0.27); Set<String> setRange = zSetOperations.range(1, 6); // 按分数排序获取有序集合 Set<String> setScore = zSetOperations.rangeByScore(0.2, 0.6); // 定义值范围 RedisZSetCommands.Range range = new RedisZSetCommands.Range(); range.gt("value3"); // 大于 value3 // range.gte("value3"); // 大于等于value3 // range.lt("value8"); // 小于value8 range.lte("value8"); // 小于等于value8 // 按值排序,注意这个排序是按字符串排序 Set<String> setLex = zSetOperations.rangeByLex(range); // 删除元素 zSetOperations.remove("value9", "value2"); // 求分数 Double score = zSetOperations.score("value8"); // 在下标区间下,按分数排序,同时返回value 和 score Set<ZSetOperations.TypedTuple<String>> scoreSet = zSetOperations.rangeByScoreWithScores(1, 6); // 按从大到小排序 Set<String> reverseSet = zSetOperations.reverseRange(2, 8); Map<String, Object> map = new HashMap<String, Object>(); map.put("success", true); return map; } }
Redis虽是NoSql型数据库,但是也支持事务。前面说过,在一个Redis连接中执行多个命令,一般考虑使用sessionBackcall来达到目的。同时,在Redis中使用事务,通常的命令组合是watch…multi…exec.下面仔细讲解一下watch…multi…exec.
watch 命令 : 可以监控Redis的一些键(key值);
multi 命令 : 是开始事务,这里开始事物后客户端的命令并不会马上执行,而是被存放在一个队列中。这里需要在注意,这种情况下我们执行一些返回数据的命令,Redis也是不会马上执行,而是将命令放在一个队列中,所以此时调用Redis的命令,结果都是返回null.
exec 命令 : 是执行事务。但是需要注意的是,它在队列命令执行前会判断被watch监控的Redis的键的数据是否发生过变化,即便是赋予与之前相同的值也会被判定为是改变过。如果它判定发生了改变,那Redis会取消事务,否则就会执行事务。Redis在执行事务时,只会全执行或全部不执行,而且不会被其他客户端打断,这样做的目的就是保证了Redis事物下数据的一致性。
下面是Redis事物执行的流程图:
示例代码:
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.DataAccessException; import org.springframework.data.redis.core.RedisOperations; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.SessionCallback; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import java.util.HashMap; import java.util.List; import java.util.Map; /** * @ClassName: RedisTransactionController * @Description: * @Date:2019/4/28 9:14 **/ @Controller public class RedisTransactionController { @Autowired private RedisTemplate redisTemplate; /** * Redis 事务示例 * @return */ @RequestMapping("/multi") @ResponseBody public Map<String, Object> executeMulti() { redisTemplate.opsForValue().set("key_1", "value_1"); List list = (List)redisTemplate.execute(new SessionCallback() { @Override public Object execute(RedisOperations operations) throws DataAccessException { /** 设置要监控的 key 值 */ operations.watch("key_1"); /** 开启事务,在exec命令执行前,全部都只是进入到队列中 */ operations.multi(); operations.opsForValue().set("key_2", "value_2"); // operations.opsForValue().increment("key_1", 1); // 这里是特殊说明A步 /** 这里获取值将为 null,因为 Redis 只是把命令放入队列 */ Object value2 = operations.opsForValue().get("key_2"); operations.opsForValue().set("key_3", "value_3"); operations.opsForValue().set("key_4", "value_4"); /** 执行 exec 命令,会先判断key_1是否在监控后被修改过, * 如果是则不执行事物,否则执行事物 */ return operations.exec(); } } ); System.out.println(list); Map<String, Object> map = new HashMap<String, Object>(); map.put("success", true); return map; } }
需要注意的是:
代码示例中,特殊说明A步,如果注释取消,让代码运行,因为key_1是一个字符串,故这里的代码是对字符串加1,会运行出错抛出异常。然后我们去Redis服务器查询key_2和key_3的值,会发现他们均已经有值。
由此可以看出Redis事务是先让命令进入队列,所以一开始它并没有检测到运算命令是否能成功,只有在exec命令执行时,才能发现错误。对于错误的命令Redis只会报出错误,而错误后面的命令依然会被执行,所以key_2和key_3均存在数据。该点务必要注意,是Redis和数据库事物不一样的地方。
如有偏差部分,请大神指点交流
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。