缓存Redis
参考资料:
学习目标
- Redis安装[单机版和集群版本]
- Redis常用的数据类型和使用场景
- SpringBoot整合Redis(缓存数据序列化)
- 使用Redis实现分布式锁
1.Redis安装
1.1 单机版
1.安装环境
- redis-3.0.0.tar.gz
- CentOS7
2.安装步骤
#Redis是C语言开发的,所以在安装Redis的时候需要C语言编译.使用yum在线安装gcc
yum install gcc-c++
- redis的源码包上传到linux系统
- 使用
tar -zxvf redis-3.0.0.tar.gz
进行解压 - 编译。进入redis源码目录,输入
make
命令进行编译 - 安装。make install PREFIX=/usr/local/redis
PREFIX参数指定redis的安装目录。一般软件安装到/usr目录下
3.启动和连接
- 前端启动.
- 后端启动
解压后的redis文件中有个redis.conf复制到redis/bin目录下
修改配置文件:/user/local/redis/bin/redis.conf
把daemonize no
修改成daemonize yes
#./redis-server redis.conf //启动命令
# ps aux | grep redis //查看redis进程
# ./redis-cli -h 192.168.0.200 -p 6379 //使用redis-cli连接 如果设置了密码记得加入"参数: -a"
# kill -9 id //使用kill -9 关闭redis
Redis的数据保存在内存中。我们需要把数据持久化到磁盘中.Redis默认支持的持久化使用的是快照形式:
RDB
另一种持久化方案:
AOF
,把所有对redis数据库操作的命令,增删改操作的命令。保存到文件中。数据库恢复时把所有的命令执行一遍即可
修改/usr/local/redis/bin/redis.conf
文件:
RDB
:
AOF
:
两种持久化方案同时开启使用AOF文件来恢复数据库
Linux命令:
- cp /usr/local/redis-3.0.0/redis.conf /usr/local/redis/bin -r //-r 递归复制
- mkdir redis #创建redis目录
- tar -zxvf redis-3.0.0.tar.gz /usr/local/redis
- 查看命令:less / more
- 动态监控文件变化:tail -f /user/local/log/a.log
4.Redis持久化方式
-
RDB
快照[默认方式] -
AOF
:把所有对redis数据库操作的命令,增删改操作的命令。保存到文件中。数据库恢复时把所有的命令执行一遍即可
两种持久化方案同时开启使用AOF文件来恢复数据库
1.2 集群版本
1.Redis主从复制
- Redis的复制功能是支持多个数据库之间的数据同步
- 主数据库
master
,从数据库slave
- 从数据库一般是只读的,并接收主数据库同步过来的数据
- 一个主数据库可以有多个从数据库,而一个从数据库只能有一个主数据库
通过redis的复制功能实现数据库的读写分离,提高服务器的负载能力。
主数据库进行写操作,从数据库负责读操作
过程:
- 当一个从数据库启动时,会向主数据库
发送sync命令
- 主数据库接
收到sync命令
后会开始在后台保存快照
(执行RDB操作),并将保存期间接收到的命令缓存起来
- 当
快照完成后
,redis会将快照文件和所有缓存的命令
发送给从数据库 - 从数据库收到后,会
载入快照文件并执行收到的缓存的命令
修改redis.conf:
修改从redis中的 redis.conf文件:
- 主服务器的ip地址和端口号:
slaveof 192.168.0.200 6379
- 如果主redis配置了密码需要在加入一条配置:
masterauth 123456
2.Redis集群
2.Redis Key设计
2.1 分段设计
使用冒号把 key 中要表达的多种含义分开表示
- 把表名转化为 key 前缀
- 主键名(或其他常用于搜索的字段)
- 主键值
- 要存储的字段
这样设计的目的是为了防止KEY
重复
2.2 例子
用户表:user
id | name | |
---|---|---|
1 | zhangsan | 892295771@qq.com |
2 | lisi | 15723254410@qq.com |
根据用户 id 查询用户邮箱地址,可以选择把邮箱地址这个数据存到 redis 中
set user:id:1:email 892295771@qq.com
set user:id:2:email 15723254410@qq.com
3.Redis常用数据类型
常用的数据类型:
string字符串(可以为整形、浮点型和字符串,统称为元素)
key – valuehash(hash的key必须是唯一的)
key – key02 -valueset(各不同的元素)
sort set 有序集合
- “list(对列,元素可以重复,先入先出原则)`
3.1 String数据类型
1.简介
String
关心的是二进制的字符串,不关心具体的格式,可以用来存储JSON
或者JPEG
图片格式的字符串
2.数据模型
key-value
结构,key
是某个数据在Redis
中的唯一标识,value
是具体的数据
Key | Value |
---|---|
name | zx |
age | 22 |
key 不能重复,如果重复会覆盖前面的值
127.0.0.1:6379> set name zx
OK
127.0.0.1:6379> get name
"zx"
127.0.0.1:6379> set name kevin
OK
127.0.0.1:6379> get name
"kevin"
127.0.0.1:6379>
3.使用场景
(1)存储某个字段
set user:id:1:email 892295771@qq.com
(2)存储对象
value
为json
格式的字符串
set user:id:1 {
"id":1,"name":"zx","email":"892295771@qq.com"}
(3)生成自增ID
当 redis 的 string 类型的值为整数形式时,redis 可以把它当做是整数一样进行自增(incr)自减(decr)操作。由于 redis 所有的操作都是原子性(redis单线程)的,所以不必担心多客户端连接时可能出现的事务问题。
incr
对值进行加1操作,如果不是整数,返回错误;如果不存在,按照从0开始decr
或incr
;incrby
、decryby
:递增和递减整数值,可以指定增减的数值,格式:incrby key值 正负数
格式:incr key值
格式:incrby key值 正负数
如果增减的是负数,相当于就是在进行做减操作
应用场景:视频播放次数、点赞次数
(4)共享session
数据共享的功能,redis作为单独的应用软件用来存储一些共享数据供多个实例访问。单点登录的一个具体实现
(5)自动定时过期删除
set key value [ex seconds] [px millseconds] [nx|xx]
ex seconds: 键过期时间[单位为秒]
px milliseconds: 为键设置毫秒级过期时间
nx: 键必须不存在才可以设置成功,用于添加
xx: 键必须存在,才可以设置成功,用于更新
127.0.0.1:6379> set key-expire-time "value" ex 20
(6)批量操作
**mset,mget:**批量设置和获取命令,在操作多个key的时候可以节省网络传输时间
3.2 Hash数据类型
1.简介
hash
的 Key
是一个唯一值,Value
部分是一个 HashMap
的结构。
2.数据模型
大key:(小key:value)
和字符串很像,基本上redis对字符串操作的命令,Redis的Hash一般也存在,不过在命令前多个一个h
3.应用场景
一些关系型数据库中不是特别复杂的表,也无需复杂的关系查询,可以使用Redis的Hash来存储,也可以用Hash做表数据缓存.
比如:缓存购物车
hmset user:1 name zj email 123456@163.com
hset key field value
hsetnx key field value //与setnx命令一样,不存在则设置值,用于添加,作用在field上面
hget key field //获取值
hdel key field // 删除值
hlen key //子酸field的个数
hmset key field value [filed value] //批量设置field-value
hexists key field //判断filed是否存在
hkeys key //获取所有的field
hvals key //获取所有的value
hgetall key //获取所有的field-value ,如果元素数较多会存在阻塞redis的可能
hincreby key filed
3.4.list数据类型
1.简介
list 是按照插入顺序排序的字符串链表,可以在头部和尾部插入新的元素(双向链表实现,两端添加元素的时间复杂度为 O(1))。插入元素时,如果 key 不存在,redis 会为该 key 创建一个新的链表,如果链表中所有的元素都被移除,该 key 也会从 redis 中移除
2.数据模型
链表用来存储多个有序的字符串,一个链表最多可以存储2^32 – 1个元素,在redis中可以对列表的两端插入push和弹出pop,还可以取指定范围的元素。常见操作时用 lpush 命令在 list 头部插入元素, 用 rpop 命令在 list 尾取出数据。
rpush key value [value...] //从右插入元素
lpush key value [value...] //从左边插入元素
lrange key start end //获取指定范围的元素列表
lindex key index //获取列表指定索引下标的元素
llen key //获取列表的长度
lpop key // 从列表左侧弹出元素
rpop key // 从列表右侧弹出元素
lrem key count value //从列表中找到等于value的元素,并进行删除,根据count的不同有不同的情况
lset key index newValue //修改指定索引下标的元素
blpop key timeout //阻塞式左弹出key
brpop key timeout //阻塞式右弹出key
3. 应用场景
(1) 消息队列
redis 的 list 数据类型对于大部分使用者来说,是实现队列服务的最经济,最简单的方式。我司使用redis做消息队列,lpush 、rpop命令,实现先进先出,如果消费失败客户端把key再放回去,消费成功就remove掉。
(2) “最新内容”
因为 list 结构的数据查询两端附近的数据性能非常好,所以适合一些需要获取最新数据的场景,比如新闻类应用的 “最近新闻”。
4.优化建议
(1)数据插入建议
list 是链表结构,所有如果在头部和尾部插入数据,性能会非常高,不受链表长度的影响;但如果在链表中插入数据,性能就会越来越差。
####5.使用口诀:
lpush + lpop 栈
lpush + rpop 队列
lpush + ltrim = 有限集合
lpush + brpop = 消息队列
3.5.set 数据类型
1. 简介
set 数据类型是一个集合(没有排序,不重复),可以对 set 类型的数据进行添加、删除、判断是否存在等操作(时间复杂度是 O(1) )
set 集合不允许数据重复,如果添加的数据在 set 中已经存在,将只保留一份。
set 类型提供了多个 set 之间的聚合运算,如求交、并、差集,这些操作在 redis 内部完成,效率很高。
2.数据模型
sadd key value [value...] //添加元素
srem key value [value...] //删除元素
scard key //计算元素的个数
sismember key value //判断元素是否在集合中
srandmember key [count] //随机从集合中返回指定个数的元素,不写默认为1
spop key //从集合随机取出元素
smembers key //获取集合内的所有元素
sinter key1 key2 //求集合的交集
sunion key1 key2 //求集合的并集
sdiff key1 key2 //求集合的差集
3.应用场景
set 类型的特点是——不重复且无序的一组数据,并且具有丰富的计算功能,在一些特定的场景中可以高效的解决一般关系型数据库不方便做的工作。
(1)共同好友
社交类应用中,获取两个人或多个人的共同好友,两个人或多个人共同关注的微博这样类似的功能,用 MySQL 的话操作很复杂,可以把每个人的好友 id 存到集合中,获取共同好友的操作就可以简单到一个取交集的命令就搞定。
// 这里为了方便阅读,把 id 替换成姓名
sadd user:wade james melo paul kobe
sadd user:james wade melo paul kobe
sadd user:paul wade james melo kobe
sadd user:melo wade james paul kobe
// 获取 wade 和 james 的共同好友
sinter user:wade user:james
/* 输出:
* 1) "kobe"
* 2) "paul"
* 3) "melo"
*/
// 获取香蕉四兄弟的共同好友
sinter user:wade user:james user:paul user:melo
/* 输出:
* 1) "kobe"
*/
/*
类似的需求还有很多 , 必须把每个标签下的文章 id 存到集合中,可以很容易的求出几个不同标签下的共同文章;
把每个人的爱好存到集合中,可以很容易的求出几个人的共同爱好。
*/
(2)唯一ip
跟踪一些具有唯一性的一些数据,比如访问某一博客的唯一ip地址的信息,我们仅需要在每次访问的时候,将ip存入redis中。利用服务器端聚合操作方便高效的特性,维护数据对象之间的关联关系。
3.6.sorted set 数据类型
1.简介
在 set 的基础上给集合中每个元素关联了一个分数,往有序集合中插入数据时会自动根据这个分数排序。它保留了元素不能重复的特性,并且元素是有序的。[不重复并且是有顺序]
2.基本操作
zadd key score member //score是可以重复的,添加key的时候指定分数
zcard key //计算成员个数
zscore key member //计算某个成员的分数
zrank key member //计算成员排名,从低到高
zrevrank key member //计算成员排名,从高到低
zrem key member [member...] //删除成员
zincrby key increnment member //增加成员的分数
zrange key start end [withscores] //从低到高返回指定排名的分数
zrevrange key start end [withscores] //从高到低返回
zrangebyscore key min max [withscores] [limit offset count] //按照分数从低到高返回
zrevrange score key min max [withscores] [limit offset count] //按照分数从高到低返回成员
withscore 代表返回的时候带上成员的分数
...还有求交集,并集等操作
3,应用场景
在集合类型的场景上加入排序就是有序集合的应用场景了
(1)根据好友的“亲密度”排序显示好友列表。
// 用元素的分数(score)表示与好友的亲密度
zadd user:kobe 80 james 90 wade 85 melo 90 paul
// 根据“亲密度”给好友排序
zrevrange user:kobe 0 -1
/**
* 输出:
* 1) "wade"
* 2) "paul"
* 3) "melo"
* 4) "james"
*/
// 增加好友的亲密度
zincrby user:kobe 15 james
// 再次根据“亲密度”给好友排序
zrevrange user:kobe 0 -1
/**
* 输出:
* 1) "james"
* 2) "wade"
* 3) "paul"
* 2) "melo"
*/
//类似的需求还出现在根据文章的阅读量或点赞量对文章列表排序
(2)排行榜
– 实效性
从排行榜的实效性上划分,主要分为:
- 实时榜:基于当前一段时间内数据的实时更新,进行排行。例如:当前一小时内游戏热度实时榜,当前一小时内明星送花实时榜等
- 历史榜:基于历史一段周期内的数据,进行排行。例如:日榜(今天看昨天的),周榜(上一周的),月榜(上个月的),年榜(上一年的)
– 业务数据类型
从需要排行的数据类型上划分,主要分为:
- 单类型数据排行榜:是指需要排行的主体不需要区分类型,例如,所有用户积分排行,所有公贡献值排行,所有游戏热度排行等
- 多类型(复合类型)数据排行榜:是指需要排行的主体在排行中要求有类型上的区分,例如:竞技类游戏热度排行、体育类游戏热度排行、MOBA类游戏操作性排行、角色/回合/卡牌三类游戏热度排行等
– 展示唯度
从榜单的最终展示唯度上划分,主要分为:
- 单唯度:是指选择展示的排行榜就是基于一个唯度下的排行,例如前面提到的MOBA类游戏操作性排行榜,就仅展示所有MOBA类游戏按操作性的评分排行
- 多唯度:是指选择展示的排行榜还有多种唯度供用户选择,仍然以前面的MOBA类游戏为例,唯度除了操作性,还有音效评分排行,难易度评分排行,画面评分排行等。
– 展示数据量
从需要展示的数据量上划分,主要分为:
- topN数据:只要求展示topN条排行纪录,例如:最火MOBA游戏top20
- 全量数据:要求展示所有数据的排行,例如:所有用户的积分排行
3.7总结
结构类型 | 结构存储的值 | 结构的读写能力 |
---|---|---|
String | 可以是字符串、整数或者浮点数 | 对整个字符串或者字符串的其中一部分执行操作;对象和浮点数执行自增(increment)或者自减(decrement) |
List | 一个链表,链表上的每个节点都包含了一个字符串 | 从链表的两端推入或者弹出元素;根据偏移量对链表进行修剪(trim);读取单个或者多个元素;根据值来查找或者移除元素 |
Set | 包含字符串的无序收集器(unorderedcollection),并且被包含的每个字符串都是独一无二的、各不相同 | 添加、获取、移除单个元素;检查一个元素是否存在于某个集合中;计算交集、并集、差集;从集合里卖弄随机获取元素 |
Hash | 包含键值对的无序散列表 | 添加、获取、移除单个键值对;获取所有键值对 |
Zset | 字符串成员(member)与浮点数分值(score)之间的有序映射,元素的排列顺序由分值的大小决定 | 添加、获取、删除单个元素;根据分值范围(range)或者成员来获取元素 |
4.SpringBoot整合Redis
使用RedisTemplate 使用 Redis 缓存,SpringBoot自动帮我们在容器中生成了一个RedisTemplate<Object,Object>
和一个StringRedisTemplate<String,Object>
。[查看RedisAutoConfiguration源码]
.这个RedisTemplate没有设置数据存在Redis时,key及value的序列化方式。看到这个**@ConditionalOnMissingBean注解后,就知道如果Spring容器中有了RedisTemplate对象了,这个自动配置的RedisTemplate不会实例化**。因此我们可以直接自己写个配置类,配置RedisTemplate。
@Configuration
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
@Import({
LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {
@Bean
@ConditionalOnMissingBean(name = "redisTemplate")
public RedisTemplate<Object, Object> redisTemplate(
RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
@Bean
@ConditionalOnMissingBean
public StringRedisTemplate stringRedisTemplate(
RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
}
4.1 引入pom相关依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 整合redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- springboot测试 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
4.2 application.yml配置
spring:
redis:
port: 6379
database: 0
host: 127.0.0.1
password:
jedis:
pool:
max-active: 8
max-wait: -1ms
max-idle: 8
min-idle: 0
timeout: 5000ms
4.3 自定义RedisConfig类
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import java.net.UnknownHostException;
@Configuration
public class RedisConfig {
@Bean
@ConditionalOnMissingBean(name = "redisTemplate")
public RedisTemplate<String, Object> redisTemplate(
RedisConnectionFactory redisConnectionFactory)
throws UnknownHostException {
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<Object>(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
template.setConnectionFactory(redisConnectionFactory);
template.setKeySerializer(jackson2JsonRedisSerializer);
template.setValueSerializer(jackson2JsonRedisSerializer);
template.setHashKeySerializer(jackson2JsonRedisSerializer);
template.setHashValueSerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
@Bean
@ConditionalOnMissingBean(StringRedisTemplate.class)
public StringRedisTemplate stringRedisTemplate(
RedisConnectionFactory redisConnectionFactory)
throws UnknownHostException {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
}
4.4 自定义RedisUtils工具类
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
@Component
public final class RedisUtil {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
// =============================common============================
/** * 指定缓存失效时间 * @param key 键 * @param time 时间(秒) * @return */
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;
}
}
/** * 根据key 获取过期时间 * @param key 键 不能为null * @return 时间(秒) 返回0代表为永久有效 */
public long getExpire(String key) {
return redisTemplate.getExpire(key, TimeUnit.SECONDS);
}
/** * 判断key是否存在 * @param key 键 * @return true 存在 false不存在 */
public boolean hasKey(String key) {
try {
return redisTemplate.hasKey(key);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/** * 删除缓存 * @param key 可以传一个值 或多个 */
@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));
}
}
}
// ============================String=============================
/** * 普通缓存获取 * @param key 键 * @return 值 */
public Object get(String key) {
return key == null ? null : redisTemplate.opsForValue().get(key);
}
/** * 普通缓存放入 * @param key 键 * @param value 值 * @return true成功 false失败 */
public boolean set(String key, Object value) {
try {
redisTemplate.opsForValue().set(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/** * 普通缓存放入并设置时间 * @param key 键 * @param value 值 * @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期 * @return true成功 false 失败 */
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;
}
}
/** * 递增 * @param key 键 * @param delta 要增加几(大于0) * @return */
public long incr(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("递增因子必须大于0");
}
return redisTemplate.opsForValue().increment(key, delta);
}
/** * 递减 * @param key 键 * @param delta 要减少几(小于0) * @return */
public long decr(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("递减因子必须大于0");
}
return redisTemplate.opsForValue().increment(key, -delta);
}
// ================================Map=================================
/** * HashGet * @param key 键 不能为null * @param item 项 不能为null * @return 值 */
public Object hget(String key, String item) {
return redisTemplate.opsForHash().get(key, item);
}
/** * 获取hashKey对应的所有键值 * @param key 键 * @return 对应的多个键值 */
public Map<Object, Object> hmget(String key) {
return redisTemplate.opsForHash().entries(key);
}
/** * HashSet * @param key 键 * @param map 对应多个键值 * @return true 成功 false 失败 */
public boolean hmset(String key, Map<String, Object> map) {
try {
redisTemplate.opsForHash().putAll(key, map);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/** * HashSet 并设置时间 * @param key 键 * @pa map 对应多个键值 * @param time 时间(秒) * @return true成功 false失败 */
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;
}
}
/** * 向一张hash表中放入数据,如果不存在将创建 * @param key 键 * @param item 项 * @param value 值 * @return true 成功 false失败 */
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;
}
}
/** * 向一张hash表中放入数据,如果不存在将创建 * @param key 键 * @param item 项 * @param value 值 * @param time 时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间 * @return true 成功 false失败 */
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();
rrn false;
}
}
/** * 删除hash表中的值 * @param key 键 不能为null * @param item 项 可以使多个 不能为null */
public void hdel(String key, Object... item) {
redisTemplate.opsForHash().delete(key, item);
}
/** * 判断hash表中是否有该项的值 * @param key 键 不能为null * @param item 项 不能为null * @return true 存在 false不存在 */
public boolean hHasKey(String key, String item) {
return redisTemplate.opsForHash().hasKey(key, item);
}
/** * hash递增 如果不存在,就会创建一个 并把新增后的值返回 * @param key 键 * @param item 项 * @param by 要增加几(大于0) * @return */
public double hincr(String key, String item, double by) {
return redisTemplate.opsForHash().increment(key, item, by);
}
/** * hash递减 * @param key 键 * @param item 项 * @param by 要减少记(小于0) * @return */
public double hdecr(String key, String item, double by) {
return redisTemplate.opsForHash().increment(key, item, -by);
}
// ============================set=============================
/** * 根据key获取Set中的所有值 * @param key 键 * @return */
public Set<Object> sGet(String key) {
try {
return redisTemplate.opsForSet().members(key);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/** * 根据value从一个set中查询,是否存在 * @param key 键 * @param value 值 * @return true 存在 false不存在 */
public boolean sHasKey(String key, Object value) {
try {
return redisTemplate.opsForSet().isMember(key, value);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/** * 放入set缓存 * @pa key 键 * @pa values 值 可以是多个 * @ren 成功个数 */
publicng sSet(String key, Object... values) {
try {
return redisTemplate.opsForSet().add(key, values);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/** * 将set数据放入缓存 * @param key 键 * @param time 时间(秒) * @param values 值 可以是多个 * @return 成功个数 */
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;
}
}
/** * 获取set缓存的长度 * @param key 键 * @return */
public long sGetSetSize(String key) {
try {
return redisTemplate.opsForSet().size(key);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/** * 移除值为value的 * @param key 键 * @param values 值 可以是多个 * @return 移除的个数 */
public long setRemove(String key, Object... values) {
try {
Long count = redisTemplate.opsForSet().remove(key, values);
return count;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
// ===============================list=================================
/** * 获取list缓存的内容 * @param key 键 * @param start 开始 * @param end 结束 0 到 -1代表所有值 * @return */
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;
}
}
/** * 获取list缓存的长度 * @param key 键 * @return */
public long lGetListSize(String key) {
try {
return redisTemplate.opsForList().size(key);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/** * 通过索引 获取list中的值 * @param key 键 * @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推 * @return */
public Object lGetIndex(String key, long index) {
try {
return redisTemplate.opsForList().index(key, index);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/** * 将list放入缓存 * @param key 键 * @param value 值 * @param time 时间(秒) * @return */
public boolean lSet(String key, Object value) {
try {
redisTemplate.opsForList().rightPush(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/** * 将list放入缓存 * @param key 键 * @param value 值 * @param time 时间(秒) * @return */
public boolean lSet(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;
}
}
/** * 将list放入缓存 * @param key 键 * @param value 值 * @param time 时间(秒) * @return */
public boolean lSet(String key, List<Object> value) {
try {
redisTemplate.opsForList().rightPushAll(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/** * 将list放入缓存 * * @param key 键 * @param value 值 * @param time 时间(秒) * @return */
public boolean lSet(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;
}
}
/** * 根据索引修改list中的某条数据 * @param key 键 * @param index 索引 * @param value 值 * @return */
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;
}
}
/** * 移除N个值为value * @param key 键 * @param count 移除多少个 * @param value 值 * @return 移除的个数 */
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;
}
}
}
4.5 StringRedisTemplate 和RedisTemplate
1.RedisTemplate 对五种数据结构分别定义了操作
-
redisTemplate.opsForValue();
操作字符串
-
redisTemplate.opsForHash();
操作hash
-
redisTemplate.opsForList();
操作list
-
redisTemplate.opsForSet();
操作set
-
redisTemplate.opsForZSet();
操作有序set
如果操作字符串的话,建议用 StringRedisTemplate
。
2.StringRedisTemplate 与 RedisTemplate 的区别
- StringRedisTemplate 继承了 RedisTemplate。
- RedisTemplate 是一个泛型类,而 StringRedisTemplate 则不是。
- StringRedisTemplate 只能对 key=String,value=String 的键值对进行操作,RedisTemplate 可以对任何类型的 key-value 键值对操作。
- 他们各自序列化的方式不同,但最终都是得到了一个字节数组,殊途同归,StringRedisTemplate 使用的是 StringRedisSerializer 类;RedisTemplate 使用的是 JdkSerializationRedisSerializer 类。反序列化,则是一个得到 String,一个得到 Object
- 两者的数据是不共通的,StringRedisTemplate 只能管理 StringRedisTemplate 里面的数据,RedisTemplate 只能管理 RedisTemplate中 的数据。
@SpringBootTest
@RunWith(SpringRunner.class)
public class RedisTemplateTest {
@Autowired
private RedisTemplate redisTemplate;
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Test
public void test1(){
redisTemplate.opsForValue().set("name","zhangsan");
String name = (String)redisTemplate.opsForValue().get("name");
System.out.println(name);
}
@Test
public void test2(){
stringRedisTemplate.opsForValue().set("name","zhangsan");
String name = stringRedisTemplate.opsForValue().get("name");
System.out.println(name);
}
@Test
public void test3(){
redisTemplate.opsForHash().put("produce","1","电视机");
redisTemplate.opsForHash().put("produce","2","冰箱");
redisTemplate.opsForHash().put("produce","3","彩电");
redisTemplate.opsForHash().put("produce","4","自行车");
String name = (String) redisTemplate.opsForHash().get("produce", "4");
System.out.println(name);
}
@Test
public void test4(){
redisTemplate.opsForList().leftPush("name","zhangfei");
redisTemplate.opsForList().leftPush("name","liubei");
redisTemplate.opsForList().leftPush("name","guanyu");
List names = redisTemplate.opsForList().range("name", 0, -1);
for (Object name : names) {
System.out.println(name);
}
}
}
5.Reids分布式锁
5.1 setnx命令
实现分布式锁之前先看两个 Redis 命令:
-
SETNX
将
key
设置值为value
,如果key
不存在,这种情况下等同SET命令。 当key
存在时,什么也不做。SETNX
是”SETif Not eXists”的简写。返回值Integer reply, 特定值:
1
如果key被设置了0
如果key没有被设置
例子
redis> SETNX mykey "Hello"
(integer) 1
redis> SETNX mykey "World"
(integer) 0
redis> GET mykey
"Hello"
redis>
-
GETSET
自动将key对应到value并且返回原来key对应的value。如果key存在但是对应的value不是字符串,就返回错误。
设计模式
GETSET可以和INCR一起使用实现支持重置的计数功能。举个例子:每当有事件发生的时候,一段程序都会调用INCR给key mycounter加1,但是有时我们需要获取计数器的值,并且自动将其重置为0。这可以通过GETSET mycounter “0”来实现:
INCR mycounter GETSET mycounter "0" GET mycounter
返回值
bulk-string-reply: 返回之前的旧值,如果之前
Key
不存在将返回nil
。例子
redis> INCR mycounter (integer) 1 redis> GETSET mycounter "0" "1" redis> GET mycounter "0" redis>
这两个命令在 java 中对应为 setIfAbsent
和 getAndSet
5.2 分布式锁的实现:
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
@Component
@Slf4j
public class RedisLock {
@Autowired
StringRedisTemplate redisTemplate;
/** * 加锁 * @param key * @param value 当前时间 + 超时时间 * @return */
public boolean lock(String key, String value){
if (redisTemplate.opsForValue().setIfAbsent(key, value)){
return true;
}
//解决死锁,且当多个线程同时来时,只会让一个线程拿到锁
String currentValue = redisTemplate.opsForValue().get(key);
//如果过期
if (!StringUtils.isEmpty(currentValue) &&
Long.parseLong(currentValue) < System.currentTimeMillis()){
//获取上一个锁的时间
String oldValue = redisTemplate.opsForValue().getAndSet(key, value);
if (StringUtils.isEmpty(oldValue) && oldValue.equals(currentValue)){
return true;
}
}
return false;
}
/** * 解锁 * @param key * @param value */
public void unlock(String key, String value){
try {
String currentValue = redisTemplate.opsForValue().get(key);
redisTemplate.opsForValue().getOperations().delete(key);
}catch (Exception e){
log.error("【redis锁】解锁失败, {}", e);
}
}
}
使用:
@SpringBootTest
@RunWith(SpringRunner.class)
public class LockTest {
/** * 模拟秒杀 */
@Autowired
RedisLock redisLock;
//超时时间10s
private static final int TIMEOUT = 10 * 1000;
@Test
public void secKill(){
String productId="1";
long time = System.currentTimeMillis() + TIMEOUT;
//加锁
if (!redisLock.lock(productId, String.valueOf(time))){
throw new RuntimeException("人太多了,等会儿再试吧~");
}
//具体的秒杀逻辑
System.out.println("秒杀的业务逻辑");
//解锁
redisLock.unlock(productId, String.valueOf(time));
}
}
发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/100736.html原文链接:https://javaforall.cn
【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛
【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...