Redis
- 1.什么是Redis
- [2 Redis为什么这么快](#2 Redis为什么这么快)
- 3.Redis有哪些数据类型
-
- [3.1 string](#3.1 string)
-
- [3.1.1 可以存储的值](#3.1.1 可以存储的值)
- [3.1.2 操作](#3.1.2 操作)
- [3.1.3 命令](#3.1.3 命令)
- [3.1.4 使用场景](#3.1.4 使用场景)
- [3.2 List列表](#3.2 List列表)
-
- [3.2.1 命令](#3.2.1 命令)
- [3.2.2 使用技巧](#3.2.2 使用技巧)
- [3.2.3 使用场景](#3.2.3 使用场景)
- [3.3 Set集合](#3.3 Set集合)
-
- [3.3.1 命令](#3.3.1 命令)
- [3.3.2 使用场景](#3.3.2 使用场景)
- [3.4 Zset集合](#3.4 Zset集合)
-
- [3.4.1 特点](#3.4.1 特点)
- [3.4.2 命令](#3.4.2 命令)
- 4.Redis实现Session共享
-
- [4.1 演示session不共享的情况](#4.1 演示session不共享的情况)
- [4.2 Redis实现session共享的具体操作(SpringBoot框架下演示)](#4.2 Redis实现session共享的具体操作(SpringBoot框架下演示))
- [4.3 优点](#4.3 优点)
- 5.Redis的16种常见应用场景
-
- [5.1 缓存](#5.1 缓存)
- [5.2 session共享](#5.2 session共享)
- [5.3 分布式锁](#5.3 分布式锁)
-
- [5.3.1 方式一](#5.3.1 方式一)
- 6.SpringBoot整合Redis
-
- [6.1 依赖引入](#6.1 依赖引入)
- [6.2 API](#6.2 API)
- [6.3 配置文件](#6.3 配置文件)
1.什么是Redis
Redis是一个用C语言编写, 开源高性能非关系型(NoSQL)的键值对数据库
Redis五种基本数据类型
2 Redis为什么这么快
- 数据存在内存中: 内存的读写操作是磁盘(数据库)的一百倍左右
- 使用C语言实现: C语言更底层, 执行速度更快
- 单线程执行
- 没有多线程竞争锁的性能消耗。
- 没有多线程导致的切换而消耗CPU
3.Redis有哪些数据类型
3.1 string
3.1.1 可以存储的值
- 字符串
- 整数
- 浮点型
当我们将整数、浮点数存储为String类型时, Redis会将其转换为字符串形式(浮点数不会发生精度丢失)
3.1.2 操作
- 对字符串或者字符串的一部分进行操作
- 对整数、浮点数执行自增或者自减操作
3.1.3 命令
- 基本操作
set (key) (value)
:设置键值对setnx (key) (value)
:设置键值对(防止覆盖)- 如果key存在, 不设置, 返回0
- 如果key不存在, 设置, 返回1
get(key)
: 获取key对应的valuegetset (key) (value)
: 先get再set, 返回旧值, 如果没有旧值则返回nilappend (key) (value)
: 向指定的key的value后追加字符串del (key)
: 删除keystrlen (key)
: 获得key对应值的字符串的长度
- 数字value的加减
incr (key)
:value + 1decr (key)
:value - 1incrby (key) (number)
:value + numberdecrby (key) (number)
:value - number
- 获取或者设置指定范围内的值
getrange (key) (begin) (end)
:获取[begin,end]下标范围内的值,如果是(0,1)就是获取所有值setrange (key) (begin) (xxxx)
:从begin下标开始设置xxx值,将原有的替换掉
- 设置键值过期时间
setex (key) (seconds) expire
:设置键过期时间ttl (key)
:查看key剩余存活时间
- 同时设置或获取多个key-value
mset (key1) (value1) (key2) (value2)
:用于同时设置一个或多个 key-value 对mget (key1) (key2)
:返回所有(一个或多个)给定 key 的值(如果某个key不存在,不存在的key返回null)msetnx(key1) (value1) (key2) (value2)
:当所有 key 都成功设置,返回 1 。 如果有一个key设置失败,所有的key设置都会失败,返回 0 。原子操作
3.1.4 使用场景
1.缓存
最常见的使用场景, 主要得益于其高性能(内存操作)、灵活的数据结构(支持5种结构)等等
- 频繁读取的数据库查询: 当某个数据库查询被频繁执行, 可以将查询结果缓存到Redis中, 较少对数据库的访问
- 会话缓存: 将用户会话存储到Redis中, 特别是大规模的web应用, 这可以提高会话管理的性能和可扩展性
- 用户身份证验证信息缓存: 缓存用户的身份证令牌或者其他身份验证信息, 减少身份验证请求对数据库的负担
- 页面内存缓存: 对于动态生成的页面内容, 可以将完整的HTML缓存到Redis中, 降低渲染时间, 提高网站性能
哪些内容适合进行缓存
- 频繁访问: 通常是应用中频繁访问的数据
- 相对稳定: 在一段时间内不经常变化
- 持续时间长: 通常具备较长的生命周期
2.计数器
Redis是基于内存的键值对存储系统, 读写速度非常快, 性能很高, 能以非常低的延迟处理大量的计数请求, 同时Redis是单线程的, 一个命令执行完才会执行下一个, 不会出现线程安全问题
- 网站访问计数: 统计网站、特定页面的点击次数, 每当用户访问网站或点击某个页面时, 通过Redis的INCR递增
- 用户行为计数: 跟踪用户在应用中的各种行为, 例如发布文章、点赞、评论等. 对每种行为创建一个计数器, 可以方便记录用户活动
- 实时统计: 在实时应用中,需要快速地获取某个指标的实时统计数据,例如活跃用户数、实时销售额等。使用Redis的计数器可以很容易地实现这些实时统计。
3.session共享
session + redis实现session共享
3.2 List列表
List其实是双端链表, 使用List结构可以轻松实现消息排队功能, 它可以轻松的完成对链表首位的操作, 来实现各个数据结构的功能
3.2.1 命令
RPUSH (KEY) (VALUE)
: 将指定值推到列表右端LPUSH (KEY) (VALUE)
: 将指定值推到列表左端RPOP (KEY)
: 从列表右端弹出一个值, 并返回被弹出的值LPOP (KEY)
: 从列表坐端弹出一个值, 并返回被弹出的值LRANGE (KEY) 0 1
: 获取列表在指定索引范围上的所有值(此处使用0 1演示)LINDEX (KEY) 1
: 通过索引获取列表中的元素(此处使用1演示)LTRIM (KEY) 1 2
: 对一个列表进行修剪, 让列表只保留指定区间内的元素, 不在指定区间之内的元素都被删除BRPOP (key) 3
: 阻塞式右端弹出操作
3.2.2 使用技巧
- LPUSH + LPOP = Stack(栈) 先进后出
- LPUSH + RPOP = Queue(队列) 先进先出
- LPUSH + LTRIM = Capped Collection(有限集合)
- LPUSH + BRPOP=Message Queue(消息队列)
3.2.3 使用场景
- 微博TimeLine: 有人发布微博, 用lpush加入时间轴, 展示新的列表信息
- 简易的消息队列(不支持重复消费、不支持消费确认等功能)
3.3 Set集合
Redis的Set是String类型的无序集合, 集合成员不可重复, 底层是通过哈希表实现
3.3.1 命令
SADD key value
: 向集合添加一个或多个成员SCARD key
: 获取集合中的成员个数SMEMBER key member
: 返回集合中所有的成员SISMEMBER key member
: 判断member元素是否是集合中的成员
3.3.2 使用场景
- 标签(tag), 给用户添加标签, 或者用户给消息添加标签,这样有同一标签或者类似标签的可以给推荐关注的事或者关注的人。
- 点赞,或点踩,收藏等,可以放到set中实现
3.4 Zset集合
Zset和Set基本一致, 不过Zset的每个元素会关联一个double类型的权重参数(score), 使得集合中的元素可以按score进行有序排列
3.4.1 特点
有序集合的成员是唯一的, 但是分数score是可以重复的
3.4.2 命令
ZADD key score member [score member ...]
: 将一个或多个成员及其分数值加入到有序集合中ZREM key member [member ...]
: 从有序集合中移除一个或多个成员ZCARD key
: 获取有序集合的成员数ZRANGE key start end [WITHSCORES]
: 按照socre从小到大的顺序, 返回有序集合中索引值在start和end范围内的成员以及其分数ZREVRANGE key start end [WITHSCORES]
: 按照socre从大到小的顺序, 返回有序集合中索引值在start和end范围内的成员以及其分数ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count]
: 返回有序集合中指定分数区间的成员, 按分值从小到大进行返回, LIMIT offset count用于限制返回结果的数量, 可以指定offset(偏移量)和count(返回的成员数量)ZREVRANGEBYSCORE key max min [WITHSCORES] [LIMIT offset count]
: 返回有序集合中分数在给定范围内的所有成员,按分数从大到小排序。ZREMRANGEBYRANK key start stop
: 移除有序集合中给定排名范围内的所有成员。ZREMRANGEBYSCORE key min max
: 移除有序集合中给定分数范围内的所有成员。
4.Redis实现Session共享
session是存储在服务器内存中的, 不同服务器之间无法共享session
如果想要实现session共享, 解决思路: 一台服务器登录成功后, 生成的session存入共享介质中
4.1 演示session不共享的情况
有下面这段代码, 提供了两个接口, 一个往session中塞值, 一个从session中取值
java
@GetMapping("set")
public ApiResponse<Void> set(HttpServletRequest request) {
HttpSession session = request.getSession();
session.setAttribute("name", "jack");
return ApiResponse.success();
}
@GetMapping("get")
public ApiResponse<String> get(HttpServletRequest request) {
HttpSession session = request.getSession();
String name = (String)session.getAttribute("name");
return ApiResponse.success(name);
}
我们启动两个服务, 端口分别为8090和8091(模拟两台服务器分别运行)
- 在不同的浏览器上分别访问这两个端口, 防止cookie覆盖
- 先访问8090端口的
set
接口, 再访问8090端口的get
接口, 得到'jack'
- 换一个浏览器访问8091端口的get接口, 得到null
这个结果很直白的说明了两个服务器中的session是独立的
注意: 必须使用两个浏览器, 不然会导致cookie覆盖, 请求8091端口的get方法时, 得到的cookie会覆盖之前得到的cookie, 导致再访问8090端口的get方法, 携带的是8091端口返回的cookie
4.2 Redis实现session共享的具体操作(SpringBoot框架下演示)
- 引入redis依赖以及spring-session-data-redis(自动将session存储到redis)
xml
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-data-redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<!-- <version>交给SpringBoot</version> -->
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.session/spring-session-data-redis -->
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
<!-- <version>交给SpringBoot</version> -->
</dependency>
- 在application.yml文件中配置连接redis和session相关配置
yml
server:
port: 8091
spring:
# session配置
session:
timeout: 86400 # 设置session失效时间
store-type: redis # 修改spring-session存储配置,默认存储到服务器内存中,现在设置存到redis中(关键)
# redis配置
redis:
port: 6379 # redis的端口号
host: 127.0.0.1 # 我的云服务器ip
database: 0 # 设置存入redis的哪一个库(默认是0)
关键配置就一个: store-type: redis
, 只要配置了这个, 那么代码中产生的session都会存放到redis中, 而非自己内存中
- 模拟单个会话, 多个服务器的场景, 使用同一个浏览器访问
8090服务
和8091服务
, 首先访问8090服务
的set接口
, 再访问8090服务
的get接口
, 得到'jack'
- 紧接着, 使用同一个浏览器, 访问
8091服务
的get接口
, 这样是为了保证两个请求属于同一个会话. 本次访问携带的cookie是访问8090服务
时响应的cookie, 我们看看同一个cookie, 能不能在不同的服务中找到同一个session, 结果表明, 得到了"jack"
其实在我们第一个请求时, 就会生成session, 并将session存入了Redis, 我们可以通过Redis看到这个session的结构, 它是以Hash结构存储的
4.3 优点
使用redis依赖以及spring-session-data-redis实现多服务实例session共享的优点在于:
- 操作简单: 跟平常在servlet中操作session一样, 不需要知道怎么将session存入redis、session在redis中的存储结构是什么、怎么从redis中取session这一类的问题
- 性能高: redis是基于内存的, 读写速度很快, 通过将Session数据存储在Redis中,可以显著提高Session访问的速度,特别是在处理大量用户并发访问的情况下
5.Redis的16种常见应用场景
5.1 缓存
例如报表数据、明星热搜缓存、对象缓存、全页缓存、可以提升热点数据的访问数据.
5.2 session共享
如上例
5.3 分布式锁
多线程竞争同一资源时, 我们通常会使用锁机制(synchronized或者ReentrantLock)来保证线程安全, 但在微服务架构下, 多个服务同时对一条数据进行修改, 就不用使用这些方法了
通常我们会使用Redis来实现分布式锁, 因为Redis是一个单独的非业务服务, 不受其他业务的影响.
所有服务都可以向redis发送写入命令, 但只有一个可以写入成功, 那么这个写入成功的命令即获得了锁, 其他未成功写入的服务, 则进行其他处理
5.3.1 方式一
使用redis的setnx命令
来实现分布式加锁
- 使用
setnx命令
进行加锁- 如果key不存在, 就往redis中, 设置值, 并返回true
java
@Service
public class RedisService {
@Autowired
private RedisTemplate<String, Object> template;
// 分布式锁的Key, 类似于lock(Obj)中的Obj, 这个锁的唯一把钥匙
private static final String REDIS_LOCK = "lock";
public String redisLock() {
try {
/**
* 加锁, 实质调用了setnx指令
* 如果key不存在, 设置成功, 并返回true
* 如果key存在, 设置失败, 并返回false
* value可以使用随机值, 此处用0, 并不会影响锁
*/
Boolean flag = template.opsForValue().setIfAbsent(REDIS_LOCK, "0");
// 加锁失败, 执行相应业务
if (!flag) {
return "抢锁失败";
}
// ---------- 抢锁成功, 执行业务逻辑 ---------
// ---------- 执行业务逻辑结束 --------------
return "执行结束";
} finally {
// 需要在finally中释放锁, 这样即使执行业务逻辑时发生异常, 依旧可以释放锁
template.delete(REDIS_LOCK);
}
}
}
6.SpringBoot整合Redis
SpringDataRedis提供了统一API RedisTemplate来操作Redis, 支持Redis的发布订阅模式、Redis哨兵、Redis集群等等
6.1 依赖引入
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- redis依赖commons-pool 这个依赖一定要添加 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
SpringDataRedis使用了Apache Commons Pool2作为连接池的实现, 重用数据库连接池, 提高性能和资源利用率
6.2 API
6.3 配置文件
yml
spring:
redis:
# Redis服务器地址
host: 19.1.5.11
# Redis服务器端口号
port: 6379
# 使用的数据库索引,默认是0
database: 0
# 连接超时时间
timeout: 1800000
# 设置密码
password: "123456"
lettuce:
pool:
# 连接池最大阻塞等待时间(使用负值表示没有限制) 默认-1
max-wait: -1
# 连接池中的最大空闲连接 默认8
max-idle: 8
# 连接池中的最小空闲连接 默认0
min-idle: 0
# 连接池最大连接数 默认8 ,负数表示没有限制
max-active: 20