Redis介绍
Redis是什么
- Redis 是一个开源(BSD 许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件
- Redis 支持多种类型的数据结构,如 字符串(strings),散列(hashes), 列表(lists), 集合(sets), 有序集合(sorted sets) ,范围查询, bitmaps, hyperloglogs 和 地理空间(geospatial) 索引半径查询
- Redis 内置了复制(replication),LUA 脚本(Lua scripting),LRU 驱动事件(LRU eviction),事务(transactions)和不同级别的 磁盘持久化(persistence)
- Redis 通过 哨兵(Sentinel) 和自动分区(Cluster)提供高可用性(high availability)
Redis特性
-
速度快
-
单节点读 110000次/s,写81000次/s
-
数据存放内存中
-
用 C 语言实现,离操作系统更近
-
单线程架构,6.0 开始支持多线程(CPU、IO 读写负荷)
-
-
持久化
- 数据的更新将异步地保存到硬盘(RDB 和 AOF)
-
多种数据结构
- 不仅仅支持简单的 key-value 类型数据,还支持:字符串、hash、列表、集合、有序集合
-
支持多种编程语言
-
功能丰富
- HyperLogLog、GEO、发布订阅、Lua脚本、事务、Pipeline、Bitmaps,key 过期
-
简单稳定
- 源码少、单线程模型
-
主从复制
-
Redis 支持数据的备份(master-slave)与集群(分片存储),以及拥有哨兵监控机制。
-
Redis 的所有操作都是原子性的,同时 Redis 还支持对几个操作合并后的原子性执行。
Redis经典使用场景
- 缓存
- 计数器
- 消息队列
- 排行榜
- 社交网络
- 分布式锁
- 全局唯一自增id
- ….
Redis高并发原理
-
Redis 是纯内存数据库,一般都是简单的存取操作,线程占用的时间很多,时间的花费主要集中在 IO 上,所以读取速度快
-
Redis 使用的是非阻塞 IO,IO 多路复用,使用了单线程来轮询描述符,将数据库的开、关、读、写都转换成了事件,减少了线程切换时上下文的切换和竞争
-
Redis 采用了单线程的模型,保证了每个操作的原子性,也减少了线程的上下文切换和竞争
-
Redis 存储结构多样化,不同的数据结构对数据存储进行了优化,如压缩表,对短数据进行压缩存储,再如,跳表,使用有序的数据结构加快读取的速度
-
Redis 采用自己实现的事件分离器,效率比较高,内部采用非阻塞的执行方式,吞吐能力比较大
Redis安装参考我写的另外一篇文章:Docker常用命令以及软件安装
Redis数据结构与命令使用
可以看到每种数据结构都有两种以上的内部编码实现,例如 list 数据结构包含了 linkedlist 和 ziplist 两种内部编码。同时,有些内部编码,例如 ziplist, 可以作为多种外部数据结构的内部实现,可以通过 object encoding
命令查询内部编码:
object encoding xxx # xxx 为键名
Redis 所有的数据结构都是以唯一的 key 字符串作为名称,然后通过这个唯一 key 值来获取相应的 value 数据。不同类型的数据结构的差异就在于 value 的结构不一样
常用全局命令
- keys:查看所有键
- dbsize:键总数
- exists key:检查键是否存在
- del key [key …]:删除键
- expire key seconds:键过期
- ttl key: 通过 ttl 命令观察键键的剩余过期时间
- type key:键的数据结构类型
字符串String
- 字符串 string 是 Redis 最简单的数据结构
- Redis 的字符串是动态字符串,是可以修改的字符串,内部结构实现上类似于 Java 的 ArrayList,采用预分配冗余空间的方式来减少内存的频繁分配。
字符串结构使用非常广泛,一个常见的用途就是缓存用户信息。我们将用户信息结构体 使用 JSON 序列化成字符串,然后将序列化后的字符串塞进 Redis 来缓存。同样,取用户 信息会经过一次反序列化的过程
常用字符串命令
-
set key value
[ex seconds][px milliseconds] [nx|xx]
: 设置值,返回 ok 表示成功- ex seconds:为键设置秒级过期时间。
- px milliseconds:为键设置毫秒级过期时间。
- nx:键必须不存在,才可以设置成功,用于添加。可单独用 setnx 命令替代
- xx:与 nx 相反,键必须存在,才可以设置成功,用于更新。可单独用 setxx 命令替代
-
get key:获取值
-
mset key value [key value …]:批量设置值,批量操作命令可以有效提高业务处理效率
-
mget key [key …]:批量获取值,批量操作命令可以有效提高业务处理效率
-
incr key:计数,返回结果分 3 种情况:
- 值不是整数,返回错误。
- 值是整数,返回自增后的结果。
- 键不存在,按照值为 0 自增,返回结果为 1。
-
decr(自减)、incrby(自增指定数字)、 decrby(自减指定数字)
字符串使用场景
-
缓存数据,提高查询性能。比如存储登录用户信息、电商中存储商品信息
-
可以做计数器(想知道什么时候封锁一个 IP 地址(访问超过几次)),短信限流
-
共享 Session,例如:一个分布式 Web 服务将用户的 Session 信息(例如用户登录信息)保存在各自服务器中,这样会造成一个问题,出于负载均衡的考虑,分布式服务会将用户的访问均衡到不同服务器上,用户刷新一次访问可 能会发现需要重新登录,为了解决这个问题,可以使用 Redis 将用户的 Session 进行集中管理,在这种模式下只要保证 Redis 是高可用和扩展性的,每次用户 更新或者查询登录信息都直接从 Redis 中集中获取
哈希Hash
哈希相当于 Java 中的 HashMap,以及 Js 中的 Map,内部是无序字典。实现原理跟 HashMap 一致。一个哈希表有多个节点,每个节点保存一个键值对。
与 Java 中的 HashMap 不同的是,rehash 的方式不一样,因为 Java 的 HashMap 在字典很大时,rehash 是个耗时的操作,需要一次性全部 rehash。
Redis 为了高性能,不能堵塞服务,所以采用了渐进式 rehash 策略。
渐进式 rehash 会在 rehash 的同时,保留新旧两个 hash 结构,查询时会同时查询两个 hash 结构,然后在后续的定时任务中以及 hash 操作指令中,循序渐进地将旧 hash 的内容一点点迁移到新的 hash 结构中。当搬迁完成了,就会使用新的 hash 结构取而代之。
当 hash 移除了最后一个元素之后,该数据结构自动被删除,内存被回收。
常用哈希命令
- hset key field value:设置值
- hget key field:获取值
- hdel key field [field …]:删除 field
- hlen key:计算 field 个数
- hmset key field value [field value …]:批量设置 field-value
- hmget key field [field …]:批量获取 field-value
- hexists key field:判断 field 是否存在
- hkeys key:获取所有 field
- hvals key:获取所有 value
- hgetall key:获取所有的 field-value
- incrbyfloat 和 hincrbyfloat:就像 incrby 和 incrbyfloat 命令一样,但是它们的作 用域是 filed
哈希使用场景
- Hash 也可以同于对象存储,比如存储用户信息,与字符串不一样的是,字符串是需要将对象进行序列化(比如 json 序列化)之后才能保存,而 Hash 则可以讲用户对象的每个字段单独存储,这样就能节省序列化和反序列的时间
- 保存用户的购买记录,比如 key 为用户 id,field 为商品 id,value 为商品数量。同样还可以用于购物车数据的存储,比如 key 为用户 id,field 为商品 id,value 为购买数量等等
列表List
Redis 中的 lists 相当于 Java 中的 LinkedList,实现原理是一个双向链表(其底层是一个快速列表),即可以支持反向查找和遍历,更方便操作。插入和删除操作非常快,时间复杂度为 O(1),但是索引定位很慢,时间复杂度为 O(n)
常用列表命令
-
rpush key value [value …]:从右边插入元素
-
lpush key value [value …]:从左边插入元素
-
linsert key before|after pivot value:向某个元素前或者后插入元素
-
lrange key start end:获取指定范围内的元素列表,
lrange key 0 -1
可以从左到右获取列表的所有元素 -
lindex key index:获取列表指定索引下标的元素
-
llen key:获取列表长度
-
lpop key:从列表左侧弹出元素
-
rpop key:从列表右侧弹出
-
lrem key count value:删除指定元素,lrem 命令会从列表中找到等于 value 的元素进行删除,根据 count 的不同 分为三种情况:
- ·count>0,从左到右,删除最多 count 个元素。
- count<0,从右到左,删除最多 count 绝对值个元素。
- count=0,删除所有。
-
ltrim key start end:按照索引范围修剪列表
-
lset key index newValue:修改指定索引下标的元素
-
blpop key [key …] timeout 和 brpop key [key …] timeout:阻塞式弹出
列表使用场景
-
热销榜,文章列表
-
实现工作队列(利用 lists 的 push 操作,将任务存在 lists 中,然后工作线程再用 pop 操作将任务取出进行执行 ),例如消息队列
-
最新列表,比如最新评论
使用参考:
- lpush+lpop=Stack(栈)
- lpush+rpop=Queue(队列)
- lpsh+ltrim=Capped Collection(有限集合)
- lpush+brpop=Message Queue(消息队列)
集合Set和有序集合zSet
Redis 的集合相当于 Java 语言里面的 HashSet 和 JS 里面的 Set,它内部的键值对是无序的唯一的。Set 集合中最后一个 value 被移除后,数据结构自动删除,内存被回收。
zset 可能是 Redis 提供的最为特色的数据结构,它也是在面试中面试官最爱问的数据结构。它类似于 Java 的 SortedSet 和 HashMap 的结合体,一方面它是一个 set,保证了内部 value 的唯一性,另一方面它可以给每个 value 赋予一个 score,代表这个 value 的排序权重。它的内部实现用的是一种叫着「跳跃列表」的数据结构
常用集合命令
- sadd key element [element …]:添加元素,返回结果为添加成功的元素个数
- srem key element [element …]:删除元素,返回结果为成功删除元素个数
- smembers key:获取所有元素
- sismember key element:判断元素是否在集合中,如果给定元素 element 在集合内返回 1,反之返回 0
- scard key:计算元素个数,scard 的时间复杂度为 O(1),它不会遍历集合所有元素
- spop key:从集合随机弹出元素,从 3.2 版本开始,spop 也支持[count]参数。
- srandmember key [count]:随机从集合返回指定个数元素,[count]是可选参数,如果不写默认为 1
- sinter key [key …]:求多个集合的交集
- suinon key [key …]:求多个集合的并集
- sdiff key [key …]:求多个集合的差集
常用有序集合命令
-
zadd key score member [score member …]:添加成员,返回结果代表成功添加成员的个数。Redis3.2 为 zadd 命令添加了 nx、xx、ch、incr 四个选项:
- nx:member 必须不存在,才可以设置成功,用于添加
- xx:member 必须存在,才可以设置成功,用于更新
- ch:返回此次操作后,有序集合元素和分数发生变化的个数
- incr:对 score 做增加,相当于后面介绍的 zincrby
-
zcard key:计算成员个数
-
zscore key member:计算某个成员的分数
-
zrank key member 和 zrevrank key member:计算成员的排名,zrank 是从分数从低到高返回排名,zrevrank 反之
-
zrem key member [member …]:删除成员
-
zincrby key increment member:增加成员的分数
-
zrange key start end [withscores] 和 zrevrange key start end [withscores]:返回指定排名范围的成员,zrange 是从低到高返回,zrevrange 反之。
-
zrangebyscore key min max [withscores][limit offset count]
和zrevrangebyscore key max min [withscores][limit offset count]
返回指定分数范围的成员,其中 zrangebyscore 按照分数从低到高返回,zrevrangebyscore 反之 -
zcount key min max:返回指定分数范围成员个数
-
zremrangebyrank key start end:删除指定排名内的升序元素
-
zremrangebyscore key min max:删除指定分数范围的成员
-
zinterstore 和 zunionstore 命令求集合的交集和并集,可用参数比较多,可用到再查文档
有序集合相比集合提供了排序字段,但是也产生了代价,zadd 的时间 复杂度为 O(log(n)),sadd 的时间复杂度为 O(1)
集合和有序集合使用场景
-
给用户添加标签
-
给标签添加用户
-
根据某个权重进行排序的队列的场景,比如游戏积分排行榜,设置优先级的任务列表,学生成绩表等
关于跳跃列表
跳跃列表就是一种层级制,最下面一层所有的元素都会串起来。然后每隔几个元素挑选出一个代表来,再将这几个代表使用另外一级指针串起来。然后在这些代表里再挑出二级代表,再串起来。最终就形成了金字塔结构,如图:
跳表具有如下的性质:
- 由多层组成,最底层为第1层,次底层为第2层,以此类推。层数不会超过一个固定的最大值Lmax。
- 每层都是一个有头节点的有序链表,第1层的链表包含跳表中的所有元素。
- 如果某个元素在第k层出现,那么在第1~k-1层也必定都会出现,但会按一定的概率p在第k+1层出现。
Spring整合redis
Redis 官方推荐的 Java 客户端有 Jedis、lettuce 和 Redisson
Spring Boot 很好地支持了 Redis,可以在项目中使用 SpringData 进行 Redis 数据操作
使用Jedis
- 引入依赖
<!-- 引入 Redis 依赖,注意版本-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependence>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependence>
- 添加配置
## Redis 缓存配置信息
# 主机名称
spring.redis.host=127.0.0.1
# 端口号
spring.redis.port=6379
# 认证密码
spring.redis.password=
# 连接超时时间
spring.redis.timeout=500
# 默认数据库
spring.redis.database=0
# 最大连接数
spring.redis.jedis.pool.max-active=100
# 最大等待连接超时时间
spring.redis.jedis.pool.max-wait=-1
# 最大维持连接数
spring.redis.jedis.pool.max-idle=8
# 最小维持连接数
spring.redis.jedis.pool.min-idle=0
- 编写redis配置类
@EnableCaching
@Configuration
public class RedisConfig {
@Value("${spring.redis.host}")
private String host;
@Value("${spring.redis.database}")
private Integer database;
@Value("${spring.redis.port}")
private Integer port;
@Value("${spring.redis.password}")
private String pwd;
@Primary
@Bean(name = "jedisPoolConfig")
@ConfigurationProperties(prefix = "spring.redis.pool")
public JedisPoolConfig jedisPoolConfig() {
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
jedisPoolConfig.setMaxWaitMillis(10000);
//poolConfig.setNumTestsPerEvictionRun(5);
return jedisPoolConfig;
}
@Bean
public RedisConnectionFactory redisConnectionFactory(JedisPoolConfig jedisPoolConfig) {
RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration();
redisStandaloneConfiguration.setHostName(host);
redisStandaloneConfiguration.setDatabase(database);
redisStandaloneConfiguration.setPassword(pwd);
redisStandaloneConfiguration.setPort(port);
JedisClientConfiguration.JedisPoolingClientConfigurationBuilder jpcb = (JedisClientConfiguration.JedisPoolingClientConfigurationBuilder) JedisClientConfiguration.builder();
jpcb.poolConfig(jedisPoolConfig);
JedisClientConfiguration jedisClientConfiguration = jpcb.build();
return new JedisConnectionFactory(redisStandaloneConfiguration, jedisClientConfiguration);
}
/**
* 配置redisTemplate针对不同key和value场景下不同序列化的方式
*
* @param factory Redis连接工厂
* @return
*/
@Primary
@Bean(name = "redisTemplate")
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
template.setKeySerializer(stringRedisSerializer);
template.setHashKeySerializer(stringRedisSerializer);
Jackson2JsonRedisSerializer redisSerializer = new Jackson2JsonRedisSerializer(Object.class);
template.setValueSerializer(redisSerializer);
template.setHashValueSerializer(redisSerializer);
template.afterPropertiesSet();
return template;
}
//手动封装redisTemplate方法
@Bean
IGlobalCache cache(RedisTemplate redisTemplate) {
return new AppRedisCacheManager(redisTemplate);
}
}
- 对redisTemplate方法进行封装
/**
* 系统全局Cache接口,具体缓存方式需要实现该接口
*/
public interface IGlobalCache {
/**
* 指定缓存失效时间
*
* @param key 键
* @param time 时间(秒)
* @return
*/
boolean expire(String key, long time);
/**
* @param key 键 不能为null
* @return 时间(秒) 返回0代表为永久有效
*/
long getExpire(String key);
/**
* 判断key是否存在
*
* @param key 键
* @return true 存在 false不存在
*/
boolean hasKey(String key);
/**
* 删除缓存
*
* @param key 可以传一个值 或多个
*/
void del(String... key);
// ============================String=============================
/**
* 普通缓存获取
*
* @param key 键
* @return 值
*/
Object get(String key);
/**
* 普通缓存放入
*
* @param key 键
* @param value 值
* @return true成功 false失败
*/
boolean set(String key, Object value);
/**
* 普通缓存放入并设置时间
*
* @param key 键
* @param value 值
* @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期
* @return true成功 false 失败
*/
boolean set(String key, Object value, long time);
/**
* 递增
*
* @param key 键
* @param delta 要增加几(大于0)
* @return
*/
long incr(String key, long delta);
/**
* 递减
*
* @param key 键
* @param delta 要减少几(小于0)
* @return
*/
long decr(String key, long delta);
/**
* HashGet
*
* @param key 键 不能为null
* @param item 项 不能为null
* @return 值
*/
Object hget(String key, String item);
/**
* 获取hashKey对应的所有键值
*
* @param key 键
* @return 对应的多个键值
*/
Map<Object, Object> hmget(String key);
/**
* HashSet
*
* @param key 键
* @param map 对应多个键值
* @return true 成功 false 失败
*/
boolean hmset(String key, Map<String, Object> map);
/**
* HashSet 并设置时间
*
* @param key 键
* @param map 对应多个键值
* @param time 时间(秒)
* @return true成功 false失败
*/
boolean hmset(String key, Map<String, Object> map, long time);
/**
* 向一张hash表中放入数据,如果不存在将创建
*
* @param key 键
* @param item 项
* @param value 值
* @return true 成功 false失败
*/
boolean hset(String key, String item, Object value);
/**
* 向一张hash表中放入数据,如果不存在将创建
*
* @param key 键
* @param item 项
* @param value 值
* @param time 时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间
* @return true 成功 false失败
*/
boolean hset(String key, String item, Object value, long time);
/**
* 删除hash表中的值
*
* @param key 键 不能为null
* @param item 项 可以使多个 不能为null
*/
void hdel(String key, Object... item);
/**
* 判断hash表中是否有该项的值
*
* @param key 键 不能为null
* @param item 项 不能为null
* @return true 存在 false不存在
*/
boolean hHasKey(String key, String item);
/**
* hash递增 如果不存在,就会创建一个 并把新增后的值返回
*
* @param key 键
* @param item 项
* @param by 要增加几(大于0)
* @return
*/
double hincr(String key, String item, double by);
/**
* hash递减
*
* @param key 键
* @param item 项
* @param by 要减少记(小于0)
* @return
*/
double hdecr(String key, String item, double by);
/**
* 根据key获取Set中的所有值
*
* @param key 键
* @return
*/
Set<Object> sGet(String key);
/**
* 根据value从一个set中查询,是否存在
*
* @param key 键
* @param value 值
* @return true 存在 false不存在
*/
boolean sHasKey(String key, Object value);
/**
* 将数据放入set缓存
*
* @param key 键
* @param values 值 可以是多个
* @return 成功个数
*/
long sSet(String key, Object... values);
/**
* 将set数据放入缓存
*
* @param key 键
* @param time 时间(秒)
* @param values 值 可以是多个
* @return 成功个数
*/
long sSetAndTime(String key, long time, Object... values);
/**
* 获取set缓存的长度
*
* @param key 键
* @return
*/
long sGetSetSize(String key);
/**
* 移除值为value的
*
* @param key 键
* @param values 值 可以是多个
* @return 移除的个数
*/
long setRemove(String key, Object... values);
/**
* 获取list缓存的内容
*
* @param key 键
* @param start 开始
* @param end 结束 0 到 -1代表所有值
* @return
*/
List<Object> lGet(String key, long start, long end);
/**
* 获取list缓存的长度
*
* @param key 键
* @return
*/
long lGetListSize(String key);
/**
* 通过索引 获取list中的值
*
* @param key 键
* @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推
* @return
*/
Object lGetIndex(String key, long index);
/**
* 将list放入缓存
*
* @param key 键
* @param value 值
* @return
*/
boolean lSet(String key, Object value);
/**
* 将list放入缓存
*
* @param key 键
* @param value 值
* @return
*/
boolean lSet(String key, Object value, long time);
/**
* 将list放入缓存
*
* @param key 键
* @param value 值
* @param time 时间(秒)
* @return
*/
boolean lSetAll(String key, List<Object> value);
/**
* 将list放入缓存
*
* @param key 键
* @param value 值
* @param time 时间(秒)
* @return
*/
boolean lSetAll(String key, List<Object> value, long time);
/**
* 将list放入缓存
*
* @param key 键
* @param value 值
* @return
*/
boolean rSet(String key, Object value);
/**
* 将list放入缓存
*
* @param key 键
* @param value 值
* @param time 时间(秒)
* @return
*/
boolean rSet(String key, Object value, long time);
/**
* 将list放入缓存
*
* @param key 键
* @param value 值
* @return
*/
boolean rSetAll(String key, List<Object> value);
/**
* 将list放入缓存
*
* @param key 键
* @param value 值
* @param time 时间(秒)
* @return
*/
boolean rSetAll(String key, List<Object> value, long time);
/**
* 根据索引修改list中的某条数据
*
* @param key 键
* @param index 索引
* @param value 值
* @return
*/
boolean lUpdateIndex(String key, long index, Object value);
/**
* 移除N个值为value
*
* @param key 键
* @param count 移除多少个
* @param value 值
* @return 移除的个数
*/
long lRemove(String key, long count, Object value);
/**
* 从redis集合中移除[start,end]之间的元素
*
* @param key
* @param stard
* @param end
* @return
*/
void rangeRemove(String key, Long stard, Long end);
/**
* 返回当前redisTemplate
*
* @return
*/
RedisTemplate getRedisTemplate();
}
对应的实现类
/**
* @description: 移动端Redis缓存实现类
**/
@Getter
@AllArgsConstructor
public final class AppRedisCacheManager implements IGlobalCache {
private RedisTemplate<String, Object> redisTemplate;
@Override
public boolean expire(String key, long time) {
try {
if (time > 0) {
redisTemplate.expire(key, time, TimeUnit.SECONDS);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
@Override
public long getExpire(String key) {
return redisTemplate.getExpire(key, TimeUnit.SECONDS);
}
@Override
public boolean hasKey(String key) {
try {
return redisTemplate.hasKey(key);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
@Override
@SuppressWarnings("unchecked")
public void del(String... key) {
if (key != null && key.length > 0) {
if (key.length == 1) {
redisTemplate.delete(key[0]);
} else {
redisTemplate.delete(CollectionUtils.arrayToList(key));
}
}
}
@Override
public Object get(String key) {
return key == null ? null : redisTemplate.opsForValue().get(key);
}
@Override
public boolean set(String key, Object value) {
try {
redisTemplate.opsForValue().set(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
@Override
public boolean set(String key, Object value, long time) {
try {
if (time > 0) {
redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
} else {
set(key, value);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
@Override
public long incr(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("递增因子必须大于0");
}
return redisTemplate.opsForValue().increment(key, delta);
}
@Override
public long decr(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("递减因子必须大于0");
}
return redisTemplate.opsForValue().increment(key, -delta);
}
@Override
public Object hget(String key, String item) {
return redisTemplate.opsForHash().get(key, item);
}
@Override
public Map<Object, Object> hmget(String key) {
return redisTemplate.opsForHash().entries(key);
}
@Override
public boolean hmset(String key, Map<String, Object> map) {
try {
redisTemplate.opsForHash().putAll(key, map);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
@Override
public boolean hmset(String key, Map<String, Object> map, long time) {
try {
redisTemplate.opsForHash().putAll(key, map);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
@Override
public boolean hset(String key, String item, Object value) {
try {
redisTemplate.opsForHash().put(key, item, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
@Override
public boolean hset(String key, String item, Object value, long time) {
try {
redisTemplate.opsForHash().put(key, item, value);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
@Override
public void hdel(String key, Object... item) {
redisTemplate.opsForHash().delete(key, item);
}
@Override
public boolean hHasKey(String key, String item) {
return redisTemplate.opsForHash().hasKey(key, item);
}
@Override
public double hincr(String key, String item, double by) {
return redisTemplate.opsForHash().increment(key, item, by);
}
@Override
public double hdecr(String key, String item, double by) {
return redisTemplate.opsForHash().increment(key, item, -by);
}
@Override
public Set<Object> sGet(String key) {
try {
return redisTemplate.opsForSet().members(key);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
@Override
public boolean sHasKey(String key, Object value) {
try {
return redisTemplate.opsForSet().isMember(key, value);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
@Override
public long sSet(String key, Object... values) {
try {
return redisTemplate.opsForSet().add(key, values);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
@Override
public long sSetAndTime(String key, long time, Object... values) {
try {
Long count = redisTemplate.opsForSet().add(key, values);
if (time > 0) {
expire(key, time);
}
return count;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
@Override
public long sGetSetSize(String key) {
try {
return redisTemplate.opsForSet().size(key);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
@Override
public long setRemove(String key, Object... values) {
try {
Long count = redisTemplate.opsForSet().remove(key, values);
return count;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
@Override
public List<Object> lGet(String key, long start, long end) {
try {
return redisTemplate.opsForList().range(key, start, end);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
@Override
public long lGetListSize(String key) {
try {
return redisTemplate.opsForList().size(key);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
@Override
public Object lGetIndex(String key, long index) {
try {
return redisTemplate.opsForList().index(key, index);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
@Override
public boolean lSetAll(String key, List<Object> value) {
try {
redisTemplate.opsForList().leftPushAll(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
@Override
public boolean lSet(String key, Object value) {
try {
redisTemplate.opsForList().leftPushIfPresent(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
@Override
public boolean lSet(String key, Object value, long time) {
try {
redisTemplate.opsForList().leftPush(key, value);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
@Override
public boolean lSetAll(String key, List<Object> value, long time) {
try {
redisTemplate.opsForList().leftPushAll(key, value);
if (time > 0)
expire(key, time);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
@Override
public boolean rSet(String key, Object value) {
try {
redisTemplate.opsForList().rightPush(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
@Override
public boolean rSet(String key, Object value, long time) {
try {
redisTemplate.opsForList().rightPush(key, value);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
@Override
public boolean rSetAll(String key, List<Object> value) {
try {
redisTemplate.opsForList().rightPushAll(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
@Override
public boolean rSetAll(String key, List<Object> value, long time) {
try {
redisTemplate.opsForList().rightPushAll(key, value);
if (time > 0)
expire(key, time);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
@Override
public boolean lUpdateIndex(String key, long index, Object value) {
try {
redisTemplate.opsForList().set(key, index, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
@Override
public long lRemove(String key, long count, Object value) {
try {
Long remove = redisTemplate.opsForList().remove(key, count, value);
return remove;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
@Override
public void rangeRemove(String key, Long stard, Long end) {
try {
redisTemplate.opsForList().trim(key, stard, end);
} catch (Exception e) {
e.printStackTrace();
}
}
}
Unexpected end of stream.; nested exception is redis.clients.jedis.exceptions.JedisConnectionException: Unexpected end of stream错误解决:
- JedisConnectionException: Unexpected end of stream这个异常是由于redis服务器端设置了5分钟关闭空闲连接,但是连接池还认为该链接有效,继续使用导致的结果
- 使用spring boot、spring data redis这些库,默认会开启空闲连接检测,每30秒执行一次,如果PING命令没有返回PONG,或者60秒内该连接还是空闲,就会被清理释放,一次释放一个(默认)所以正常情况下是不会出现这个异常的
- 有一种可能是,一下新建了很多连接,一分钟只释放一个,5分钟后空闲连接仍然没有释放完,这时就会报异常,因此需要调整每次释放资源个数(参数:numTestsPerEvictionRun)
- 由于spring boot通过application.properties方式,只能设置max-active、max-wait、max-idle、min-idle这四个参数哈。如果要设置numTestsPerEvictionRun,需要通过代码
- 配置
spring.redis.numTestsPerEvictionRun = 5 spring.redis.maxActive = 50 spring.redis.maxIdle = 50 spring.redis.minIdle = 0
- 配置类中设置对应的值
使用lettuce
- 引入依赖
<!-- 集成redis依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
- 配置文件
spring:
redis:
host: 10.255.144.111
port: 6379
password: 123456
database: 0
lettuce:
pool:
max-idle: 16
max-active: 32
min-idle: 8
- 封装工具类(可选)
/**
* @className: RedisUtil
*/
@Component
public class RedisUtil {
@Autowired
private RedisTemplate redisTemplate;
/**
* 给一个指定的 key 值附加过期时间
*
* @param key
* @param time
* @return
*/
public boolean expire(String key, long time) {
return redisTemplate.expire(key, time, TimeUnit.SECONDS);
}
/**
* 根据key 获取过期时间
*
* @param key
* @return
*/
public long getTime(String key) {
return redisTemplate.getExpire(key, TimeUnit.SECONDS);
}
/**
* 根据key 获取过期时间
*
* @param key
* @return
*/
public boolean hasKey(String key) {
return redisTemplate.hasKey(key);
}
/**
* 移除指定key 的过期时间
*
* @param key
* @return
*/
public boolean persist(String key) {
return redisTemplate.boundValueOps(key).persist();
}
//- - - - - - - - - - - - - - - - - - - - - String类型 - - - - - - - - - - - - - - - - - - - -
/**
* 根据key获取值
*
* @param key 键
* @return 值
*/
public Object get(String key) {
return key == null ? null : redisTemplate.opsForValue().get(key);
}
/**
* 将值放入缓存
*
* @param key 键
* @param value 值
* @return true成功 false 失败
*/
public void set(String key, String value) {
redisTemplate.opsForValue().set(key, value);
}
/**
* 将值放入缓存并设置时间
*
* @param key 键
* @param value 值
* @param time 时间(秒) -1为无期限
* @return true成功 false 失败
*/
public void set(String key, String value, long time) {
if (time > 0) {
redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
} else {
redisTemplate.opsForValue().set(key, value);
}
}
/**
* 批量添加 key (重复的键会覆盖)
*
* @param keyAndValue
*/
public void batchSet(Map<String, String> keyAndValue) {
redisTemplate.opsForValue().multiSet(keyAndValue);
}
/**
* 批量添加 key-value 只有在键不存在时,才添加
* map 中只要有一个key存在,则全部不添加
*
* @param keyAndValue
*/
public void batchSetIfAbsent(Map<String, String> keyAndValue) {
redisTemplate.opsForValue().multiSetIfAbsent(keyAndValue);
}
/**
* 对一个 key-value 的值进行加减操作,
* 如果该 key 不存在 将创建一个key 并赋值该 number
* 如果 key 存在,但 value 不是长整型 ,将报错
*
* @param key
* @param number
*/
public Long increment(String key, long number) {
return redisTemplate.opsForValue().increment(key, number);
}
/**
* 对一个 key-value 的值进行加减操作,
* 如果该 key 不存在 将创建一个key 并赋值该 number
* 如果 key 存在,但 value 不是 纯数字 ,将报错
*
* @param key
* @param number
*/
public Double increment(String key, double number) {
return redisTemplate.opsForValue().increment(key, number);
}
//- - - - - - - - - - - - - - - - - - - - - set类型 - - - - - - - - - - - - - - - - - - - -
/**
* 将数据放入set缓存
*
* @param key 键
* @return
*/
public void sSet(String key, String value) {
redisTemplate.opsForSet().add(key, value);
}
/**
* 获取变量中的值
*
* @param key 键
* @return
*/
public Set<Object> members(String key) {
return redisTemplate.opsForSet().members(key);
}
/**
* 随机获取变量中指定个数的元素
*
* @param key 键
* @param count 值
* @return
*/
public void randomMembers(String key, long count) {
redisTemplate.opsForSet().randomMembers(key, count);
}
/**
* 随机获取变量中的元素
*
* @param key 键
* @return
*/
public Object randomMember(String key) {
return redisTemplate.opsForSet().randomMember(key);
}
/**
* 弹出变量中的元素
*
* @param key 键
* @return
*/
public Object pop(String key) {
return redisTemplate.opsForSet().pop("setValue");
}
/**
* 获取变量中值的长度
*
* @param key 键
* @return
*/
public long size(String key) {
return redisTemplate.opsForSet().size(key);
}
/**
* 根据value从一个set中查询,是否存在
*
* @param key 键
* @param value 值
* @return true 存在 false不存在
*/
public boolean sHasKey(String key, Object value) {
return redisTemplate.opsForSet().isMember(key, value);
}
/**
* 检查给定的元素是否在变量中。
*
* @param key 键
* @param obj 元素对象
* @return
*/
public boolean isMember(String key, Object obj) {
return redisTemplate.opsForSet().isMember(key, obj);
}
/**
* 转移变量的元素值到目的变量。
*
* @param key 键
* @param value 元素对象
* @param destKey 元素对象
* @return
*/
public boolean move(String key, String value, String destKey) {
return redisTemplate.opsForSet().move(key, value, destKey);
}
/**
* 批量移除set缓存中元素
*
* @param key 键
* @param values 值
* @return
*/
public void remove(String key, Object... values) {
redisTemplate.opsForSet().remove(key, values);
}
/**
* 通过给定的key求2个set变量的差值
*
* @param key 键
* @param destKey 键
* @return
*/
public Set<Set> difference(String key, String destKey) {
return redisTemplate.opsForSet().difference(key, destKey);
}
//- - - - - - - - - - - - - - - - - - - - - hash类型 - - - - - - - - - - - - - - - - - - - -
/**
* 加入缓存
*
* @param key 键
* @param map 键
* @return
*/
public void add(String key, Map<String, String> map) {
redisTemplate.opsForHash().putAll(key, map);
}
/**
* 获取 key 下的 所有 hashkey 和 value
*
* @param key 键
* @return
*/
public Map<Object, Object> getHashEntries(String key) {
return redisTemplate.opsForHash().entries(key);
}
/**
* 验证指定 key 下 有没有指定的 hashkey
*
* @param key
* @param hashKey
* @return
*/
public boolean hashKey(String key, String hashKey) {
return redisTemplate.opsForHash().hasKey(key, hashKey);
}
/**
* 获取指定key的值string
*
* @param key 键
* @param key2 键
* @return
*/
public String getMapString(String key, String key2) {
return redisTemplate.opsForHash().get("map1", "key1").toString();
}
/**
* 获取指定的值Int
*
* @param key 键
* @param key2 键
* @return
*/
public Integer getMapInt(String key, String key2) {
return (Integer) redisTemplate.opsForHash().get("map1", "key1");
}
/**
* 弹出元素并删除
*
* @param key 键
* @return
*/
public String popValue(String key) {
return redisTemplate.opsForSet().pop(key).toString();
}
/**
* 删除指定 hash 的 HashKey
*
* @param key
* @param hashKeys
* @return 删除成功的 数量
*/
public Long delete(String key, String... hashKeys) {
return redisTemplate.opsForHash().delete(key, hashKeys);
}
/**
* 给指定 hash 的 hashkey 做增减操作
*
* @param key
* @param hashKey
* @param number
* @return
*/
public Long increment(String key, String hashKey, long number) {
return redisTemplate.opsForHash().increment(key, hashKey, number);
}
/**
* 给指定 hash 的 hashkey 做增减操作
*
* @param key
* @param hashKey
* @param number
* @return
*/
public Double increment(String key, String hashKey, Double number) {
return redisTemplate.opsForHash().increment(key, hashKey, number);
}
/**
* 获取 key 下的 所有 hashkey 字段
*
* @param key
* @return
*/
public Set<Object> hashKeys(String key) {
return redisTemplate.opsForHash().keys(key);
}
/**
* 获取指定 hash 下面的 键值对 数量
*
* @param key
* @return
*/
public Long hashSize(String key) {
return redisTemplate.opsForHash().size(key);
}
//- - - - - - - - - - - - - - - - - - - - - list类型 - - - - - - - - - - - - - - - - - - - -
/**
* 在变量左边添加元素值
*
* @param key
* @param value
* @return
*/
public void leftPush(String key, Object value) {
redisTemplate.opsForList().leftPush(key, value);
}
/**
* 获取集合指定位置的值。
*
* @param key
* @param index
* @return
*/
public Object index(String key, long index) {
return redisTemplate.opsForList().index("list", 1);
}
/**
* 获取指定区间的值。
*
* @param key
* @param start
* @param end
* @return
*/
public List<Object> range(String key, long start, long end) {
return redisTemplate.opsForList().range(key, start, end);
}
/**
* 把最后一个参数值放到指定集合的第一个出现中间参数的前面,
* 如果中间参数值存在的话。
*
* @param key
* @param pivot
* @param value
* @return
*/
public void leftPush(String key, String pivot, String value) {
redisTemplate.opsForList().leftPush(key, pivot, value);
}
/**
* 向左边批量添加参数元素。
*
* @param key
* @param values
* @return
*/
public void leftPushAll(String key, String... values) {
// redisTemplate.opsForList().leftPushAll(key,"w","x","y");
redisTemplate.opsForList().leftPushAll(key, values);
}
/**
* 向集合最右边添加元素。
*
* @param key
* @param value
* @return
*/
public void leftPushAll(String key, String value) {
redisTemplate.opsForList().rightPush(key, value);
}
/**
* 向左边批量添加参数元素。
*
* @param key
* @param values
* @return
*/
public void rightPushAll(String key, String... values) {
//redisTemplate.opsForList().leftPushAll(key,"w","x","y");
redisTemplate.opsForList().rightPushAll(key, values);
}
/**
* 向已存在的集合中添加元素。
*
* @param key
* @param value
* @return
*/
public void rightPushIfPresent(String key, Object value) {
redisTemplate.opsForList().rightPushIfPresent(key, value);
}
/**
* 向已存在的集合中添加元素。
*
* @param key
* @return
*/
public long listLength(String key) {
return redisTemplate.opsForList().size(key);
}
/**
* 移除集合中的左边第一个元素。
*
* @param key
* @return
*/
public void leftPop(String key) {
redisTemplate.opsForList().leftPop(key);
}
/**
* 移除集合中左边的元素在等待的时间里,如果超过等待的时间仍没有元素则退出。
*
* @param key
* @return
*/
public void leftPop(String key, long timeout, TimeUnit unit) {
redisTemplate.opsForList().leftPop(key, timeout, unit);
}
/**
* 移除集合中右边的元素。
*
* @param key
* @return
*/
public void rightPop(String key) {
redisTemplate.opsForList().rightPop(key);
}
/**
* 移除集合中右边的元素在等待的时间里,如果超过等待的时间仍没有元素则退出。
*
* @param key
* @return
*/
public void rightPop(String key, long timeout, TimeUnit unit) {
redisTemplate.opsForList().rightPop(key, timeout, unit);
}
}
- 序列化
Redis本身提供了一下一种序列化的方式:
- GenericToStringSerializer: 可以将任何对象泛化为字符串并序列化
- Jackson2JsonRedisSerializer: 跟JacksonJsonRedisSerializer实际上是一样的
- JacksonJsonRedisSerializer: 序列化object对象为json字符串
- JdkSerializationRedisSerializer: 序列化java对象
- StringRedisSerializer: 简单的字符串序列化
配置类制定序列化方式:
@Configuration
public class RedisConfig {
@Bean(name = "redisTemplate")
public RedisTemplate<String, Object> getRedisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<String, Object>();
redisTemplate.setConnectionFactory(factory);
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
redisTemplate.setKeySerializer(stringRedisSerializer); // key的序列化类型
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
// 方法过期,改为下面代码
// objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance ,
ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer); // value的序列化类型
redisTemplate.setHashKeySerializer(stringRedisSerializer);
redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
}