基础篇
介绍
Redis常见命令
SpringBoot接入Redis
<!-- 集成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-boot-starter-data-redis默认使用的就是lettuce客户端,与老牌的Jedis客户端相比,Lettuce功能更加强大,不仅解决了线程安全的问题,还支持异步和响应式编程,支持集群,Sentinel,管道和编码器等等功能。
如果我们想要使用jedis客户端怎么办呢?就需要排除lettuce这个依赖,再引入jedis的相关依赖就可以了。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<exclusions>
<exclusion>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
spring:
redis:
host: 192.168.57.130
port: 6379
# Redis服务器连接密码(默认为空)
password: 123456
database: 0
# 默认使用 lettuce 连接池,如果用jedis,这里lettuce替换为jedis即可
lettuce:
pool:
# 连接池最大连接数(使用负值表示没有限制)
max-active: 8
# 连接池最大阻塞时间(使用负值表示没有限制)
max-wait: -1
# 连接池最大空闲连接
max-idle: 8
# 连接池最小空闲连接
min-idle: 0
# 连接超时时间
timeout: 0
连接池需要导入一个依赖
commons-pool2才会生效。
lettuce和jedis丝滑切换的原理
SpringBoot框架能通过各种starter进行无缝集成一个主要原因是其自动化配置功能。所谓自动化配置就是springBoot本身已经预先设置好了一些常用框架的整合类。然后通过类似于ConditionOn这样的条件判断注解,去辨别你的项目中是否有相关的类(或配置)了,进而进行相关配置的初始化。springBoot预设的自动化配置类都位于spring-boot-autoconfigure这个包中,只要我们搭建了springBoot的项目,这个包就会被引入进来。
这里可以看到这个RedisAutoConfiguration的配置类,打开看下:
再次打开引入的配置类看下:
-
lettuce
-
Jedis
至此就明白为什么我们把lettuce去掉引入jedis的依赖后,马上就采用jedis进行redis操作了。
封装RedisUtil工具类
package com.lsqingfeng.springboot.utils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
@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的序列化就是我们把对象存入到redis中到底以什么方式存储的,可以是二进制数据,可以是xml也可以是json。比如说我们经常会将POJO 对象存储到 Redis 中,一般情况下会使用 JSON 方式序列化成字符串,存储到 Redis 中 。
Redis本身提供了以下一种序列化的方式:
- GenericToStringSerializer: 可以将任何对象泛化为字符串并序列化
- Jackson2JsonRedisSerializer: 跟JacksonJsonRedisSerializer实际上是一样的
- JacksonJsonRedisSerializer: 序列化object对象为json字符串
- JdkSerializationRedisSerializer: 序列化java对象
- StringRedisSerializer: 简单的字符串序列化 如果我们存储的是String类型,默认使用的是StringRedisSerializer 这种序列化方式。如果我们存储的是对象,默认使用的是 JdkSerializationRedisSerializer,也就是Jdk的序列化方式(通过ObjectOutputStream和ObjectInputStream实现,缺点是我们无法直观看到存储的对象内容)。
我们可以根据redis操作的不同数据类型,设置对应的序列化方式。
通过观察RedisTemplate的源码我们就可以看出来,默认使用的是JdkSerializationRedisSerializer. 这种序列化最大的问题就是存入对象后,我们很难直观看到存储的内容,很不方便我们排查问题:
而一般我们最经常使用的对象序列化方式是: Jackson2JsonRedisSerializer
设置序列化方式的主要方法就是我们在配置类中,自己来创建RedisTemplate对象,并在创建的过程中指定对应的序列化方式。
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
// 创建Template
RedisTemplate<String, Object> template = new RedisTemplate();
// 设置连接工厂
template.setConnectionFactory(factory);
// 设置序列化工具
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
// key采用String的序列化方式
template.setKeySerializer(stringRedisSerializer);
// hash的key也采用String的序列化方式
template.setHashKeySerializer(stringRedisSerializer);
// value序列化方式采用jackson
template.setValueSerializer(jackson2JsonRedisSerializer);
// hash的value序列化方式采用jackson
template.setHashValueSerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
}
实战篇
经典用例
- 会话管理:Redis 是一种高效的会话存储方案,能够存储用户的会话数据,并提供快速的读写访问。通过在 Redis 中存储会话数据,我们可以实现分布式和可扩展的会话管理。
- 缓存应用:Redis 最常见的用途之一是作为缓存存储。它能够把频繁访问的数据存储在内存中,从而显著提升访问速度和性能。使用 Redis 缓存数据结果,可以减少对后端数据源的频繁访问,加速应用响应时间。
- 分布式锁:Redis 的原子操作和高性能特性使其成为实现分布式锁的理想选择。利用 Redis 的 SETNX(SET if Not eXists)命令,可以实现基于 Redis 的分布式锁,有效协调多进程或服务器对共享资源的访问。
- 计数器功能:Redis 支持原子操作,适合用作高效的计数器。它可用于跟踪网站访问次数、统计应用中的事件频率等。Redis 的原子递增和递减操作确保计数器的更新不受并发操作干扰。
- 限流器:Redis 可作为限流器,控制对特定操作或资源的访问频率。通过结合计数器和过期特性,Redis 支持基于时间窗口的限流策略,有助于控制系统请求速率,防止过载。
- 全局唯一标识符:Redis 能生成全局唯一标识符(GUID),用于唯一识别实体或对象。它通过自增功能或 UUID 算法来生成全局唯一的 ID。
- 购物车功能:Redis 可作为购物车数据存储方案,存储用户选择的商品信息,并提供快速读写操作。通过 Redis 存储购物车数据,可以实现高性能和可扩展的购物车功能。
- 用户留存分析:Redis 能存储并跟踪用户活动信息,用于分析用户留存率。通过记录用户行为数据和时间戳,可以计算和追踪用户在特定时间段内的留存率。
- 消息队列:Redis 的发布/订阅功能使其可作为轻量级的消息队列解决方案。它适用于在不同应用程序或服务间传递消息,并支持多订阅者模式。
- 实时排行榜:Redis 可用于实现实时排行榜。通过将用户得分或指标存储在有序集合中,并利用 Redis 的排序功能,可以方便地计算和更新排行榜,支持实时排名查询。
1、短信登录
基于session的短信登录
分布式系统存在session共享问题,可用redis来解决。
基于Redis实现共享session登录
2、用户查询缓存
3、优惠卷秒杀
4、分布式锁
5、分布式锁-redission
6、秒杀优化
7、redis消息队列
8、达人探店
9、好友关注
10、附近商户
11、用户签到
12、UV统计
高级篇
集群
多级缓存
分布式缓存
持久化
集群
Redis 支持三种不同的集群模式:主从模式、哨兵模式和Cluster模式,各具特色,应对不同的应用场景。 展,有效提高了内存利用率和写入性能,适用于更大规模和更高要求的数据处理场景。总体来说,Cluster模式为Redis集群的性能和扩展性提供了重要的支撑。
| 特性/配置 | 主从复制模式 | 哨兵模式 | Cluster模式(推荐) |
|---|---|---|---|
| 主要目的 | 数据备份与读写分离 | 高可用性和故障自动切换 | 高并发和数据分散处理 |
| 架构 | 一个主节点和多个从节点 | 监控主从结构并自动切换 | 多个主节点,数据分片 |
| 数据复制 | 主节点到从节点 | 监控并管理主从复制 | 每个主节点管理自己的数据集 |
| 故障转移机制 | 手动或哨兵自动切换 | 自动故障转移 | 自动处理节点故障 |
| 可伸缩性 | 有限,依赖主节点 | 为主从结构增加高可用性 | 高,因为数据分布式处理 |
| 使用场景 | 数据备份和读扩展 | 关键应用的高可用性 | 大规模应用的高性能需求 |
| 设置复杂度 | 相对简单 | 中等,需配置哨兵 | 复杂,需规划数据分区 |
2、 Redis 主从复制
spring:
redis:
cluster:
nodes:
- 192.168.57.130:6379
- 192.168.57.131:6379
- 192.168.57.132:6379
password: 12345
timeout: 10s