Redis 入门浅谈
1、什么是redis,为什么要使用redis?
首先redis是一种NoSql数据库,也被称为数据结构服务器。通俗点来讲,就是一种运行在内存的数据库。需要说明的是Redis是开源的,可基于内存也可持久化的键值数据库,支持多种语言API 。
因为其是基于内存的,所以运行速度非常快,在速度上一般是常用关系性数据库的几倍到几十倍。通过测试,Redis可以在1s内完成10万次的读写,一般将常用的数据存储在Redis中,代替关系型数据库的查询访问,以提高网站性能。
2、关于Redis 和 Jedis
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;
}
}
3、RedisTemplate
在结合Spring或SpringBoot 使用Redis时,我们使用最多的一个类应该是RedisTemplate了,该类对很多具体功能都做了封装。例如:RedisTemplate会自动从上面所说的RedisConnectionFactory工厂中获取连接,然后执行相关的Redis命令,最后关闭Redis连接。
4、关于 Redis 存储 Java 对象的问题
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;
}
}
5、关于SessionCallback 和 RedisCallback 接口
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;
}
});
}
}
6、redis的数据类型
这里主要说明Redis的5种数据类型的字符串(String)、散列(Hash)、列表(List)、集合(Set)、有序集合(Zset)。
6.1 字符串(String)和 散列(Hash)
字符串(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;
}
}
6.2 列表(List)
列表(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;
}
}
6.3 集合(Set)
集合(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;
}
}
6.4 有序集合(Zset)
有序集合(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;
}
}
7、关于Redis的事务问题
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和数据库事物不一样的地方。
如有偏差部分,请大神指点交流