文章目录
Redis简介
全程 Remote Dictionary Server 远程词典服务器
Redis是一种基于内存的 key-value
结构数据库 ,将数据存储与内存,读写性能更好
MySQL的数据存储方式是二维表
Redis 的特征
- 键值(key-value)型,value支持多种不同的数据结构,功能丰富
- 单线程,每个命令具备原子性
- 低延迟,速度快(基于内存、IO多路复用、良好的编码)
- 支持数据持久化(Redis会定期将数据从内存持久化到磁盘)
- 支持主从集群(从节点备份主节点数据)、分片集群
- 支持多语言客户端
Redis安装
linux下安装redis(超详细,每一步命令都有命令截图及运行截图)_linux、 安装redis-优快云博客
更推荐 yum 安装
sudo yum install -y redis
- 启动 redis
systemctl start redis
配置文件一般在
/etc/redis
的redis.conf
中,修改redis.conf
文件后直接重启 redis 即可systemctl restart redis
- 停止 redis 服务
systemctl stop redis
redis-server 是redis服务器
redis-cli 是redis客户端
Redis启动命令
在 redis/bin
目录下
启动Redis服务
./redis-server redis.conf
停止Redis服务
ctrl + c
redis.conf 是Redis的配置文件,端口号等修改可以在redis.conf中修改
启动Redis客户端
先启动服务,再启动客户端
./redis-cli
远程连接 redis
关闭 redis 的主机绑定与保护模式
- 修改
redis.conf
# 注释绑定主机
#bind 127.0.0.1 -::1
# 关闭保护模式
protected-mode no
- 重启 redis 服务
./redis-server redis.conf
使用 Redis Insight 连接
下载地址:https://redis.io/insight/
Redis 常用数据类型
基本类型
- 字符串(String) :普通字符串
- 可以包含任何数据,比如 jpg 图片或者序列化的对象。
- 一个键最大能存储 512MB。
- 哈希(Hash) : 键值对集合
- 一个 key 类似于 MySQL 中的一张表
- 也称为散列
- 哈希是一个字符串字段和字符串值之间的映射表,用来表示对象。
- 列表(List) : 字符串列表
- 可以看做是一个双向链表结构
- 按照插入顺序排序。
- 添加一个元素到列表的头部(左边)或者尾部(右边)。
- 常用来实现队列,因为它支持两端的推入和弹出操作。
- 集合(Set) : 字符串的无序集合
- 元素不可重复,无序,类似 Java 中的 HashSet
- 通过哈希表实现,所以添加、删除、查找的复杂度都是 O(1)。
- 支持交集、并集、差集,非常适合用来做去重。
- 有序集合(Sorted Set) : 字符串的有序集合
- 元素不可重复
- 每个元素都会关联一个浮点数分数(double类型),用于排序,从小到大的排序
- 查询速度快
特殊类型
本质还是字符串
- 位图(Bitmap) : 字符串
- 可以视为由比特构成的数组,通过位操作命令来处理。
- 适用于实现如在线用户统计等功能。
- HyperLogLog : 用作基数统计的算法
- 占用空间小,无论统计的元素数量是多少,所需的内存大小固定为 12KB。
- 地理空间(Geo) : 计算两地点之间的距离
Redis中key的层级格式
因为Redis中没有 表 的概念,为区分不同业务模块的key,推荐按照格式存储
项目名:业务名:类型:id
Redis命令
Redis常用命令(超详细整理)_redis命令-优快云博客
不区分大小写
字符串(String操作命令
key–value
- string:普通字符串
- int:整数类型,可以做自增、自减操作
- float:浮点类型,可以做自增、自减操作
底层都是字节数组形式存储,但是编码方式不同,比如可以将一张图片编译为字节形式上传,最大不超过 512 M
添加或修改指定key的值
set key value
批量添加多个 string 类型的键值对
mset key1 value1 key2 value2 ...
获取指定key的值
get key
获取多个 string 类型的值
mget key1 key2 key3 ...
整形的 key 自增1
incr key 1
#如果 key 对应的 value 的值原本为1,则在执行完这条命令后值就变为2
#值可以为负数
整形的key自增并指定步长
incrby key 3
#如果 key 对应的 value 的值原本为1,则在执行完这条命令后值就变为4
#值可以为负数
浮点型数字自增并指定步长
incrbyfloat key 0.5
#如果 key 对应的 value 的值原本为0.1,则在执行完这条命令后值就变为0.6
设置指定key的值与过期时间(秒数)
如手机验证码,设置过期时间
TTL 有效时间
setex key seconds value
#或者
set key value ex seconds
设置指定key的值,但是只有在key不存在时设置
如分布式锁
setnx key value
key不存在时返回null
哈希(Hash)操作命令
key–field–value
添加或修改名为 key 的哈希表中的字段field的值
hset key field value
#例如
hset dir1:dir2:dir3 name fishpie
hset dir1:dir2:dir3 age 20
添加多组数据(注意添加与修改的区别)
hmset dir1:dir2:dir3 name tom age 20 sex man
获取存储在哈希表中指定字段的值
hget key field
#例如
hget dir1:dir2:dir3 name
获取多个字段
hmget dir1:dir2:dir3 name age sex
获取一个hash类型中key中所有的 field 和 value
hgetall dir1:dir2:dir3
获取一个hash类型的key中所有field
hkeys dir1:dir2:dir3
获取一个hash类型的key中所有的value
hvals dir1:dir2:dir3
设置指定字段field自增长
hincrby dir1:dir2:dir3 age 2
# age += 2
添加一个hash类型的key的field值,前提是这个field不存在,否则不执行
hsetnx dir1:dir2:dir3:1 name shark #执行
hsetnx dir1:dir2:dir3 name fishpie #不执行
hsetnx dir1:dir2:dir3 name shark #执行
#注意区分是field存不存在,而不是值的问题
删除存储在哈希表中的指定字段
hdel key field
列表(List)操作命令
key–value1 value2 value3
将一个值或多个值插入到列表头(左侧第一个元素)
lpush key value1 ...
移出并返回列表左侧的第一个元素,没有则返回null
lpop key
#阻塞,等待100秒来执行这个语句
blpop key 100
获取列表指定范围内的元素
类似与SQL中的分页查询
lrange key start stop
#查询列表中的所有元素
lrange key 0 -1 #此处-1相当于最后一个元素
向列表右侧插入一个或多个元素
rpush key value1 ...
移出并获取列表最后一个元素(右侧第一个元素)
注意,此处的数据结构为队列,先进先出
栈是先进后出
rpop key
#阻塞,等待100秒来执行这个语句
brpop key 100
获取列表 key 的长度
llen key
集合(Set)操作命令
一个集合对应一个或多个成员
一个集合内的元素不能重复,但是不同集合的元素可以重复(共有元素)
key— value1
–value2
向集合添加一个或多个成员
sadd key member1 [member2]
判断一个元素是否存在于set中
sismember key member
#元素存在返回1,否则返回0
返回集合中所有成员
smembers key
获取集合成员数
scard key
返回给定所有集合的交集
sinter key1 [key2]
返回集合间的差集
sdiff s1 s2
#返回集合 s1 中存在,而 s2 中不存在的元素
返回所有给定集合的并集
sunion key1 [key2]
删除集合中一个或多个成员
srem key member1 [member2]
#删除成功返回1,否则返回0
有序集合(Sorted Set)操作命令
key – 0.1 value1
– 1.0 value2
向有序集合添加一个或多个成员,如果已存在则更新其score值
zadd key score1 member1 [score2 member2]
通过索引区间返回有序集合中指定区间内的成员
zrange key start stop
# start 开始索引
# stop 结束索引
按照score排序后,获取指定 score 范围内的元素
zrangebyscore key min max
# score 数值小的在上面
统计 score 值在指定范围内的所有元素的个数
zcount key start stop
# start 开始索引
# stop 结束索引
有序集合中对指定成员的分数加上增量 increment
zincrby key increment member
获取 sorted set 中指定元素的 score 值
zscore key member
获取 sorted set 中的指定元素的排名
zrank key member
获取 sorted set 中元素个数
zcard key
移出有序集合中的一个或多个成员
zrem key member [member ...]
求差集、交集、并集
zdiff
zinter
zunion
通用命令
切换数据源
#index为数据源索引 0--15
select index
查找所有符合给定模式(pattern)的key
keys pattern
#查找Redis中所有的 key
#不建议使用,因为Redis是单线程,可能会因为这个查询阻塞请求
keys *
#查找以 set 开头的key有哪些
keys set*
检查给定的 key 是否存在
exists key
返回 key 所存储的值的类型
type key
删除 key ,前提是 key 存在
del key
设置 / 查看key的有效期
- 数据存入 Redis 时推荐全部设置有效期
#为名为 key 的键设置有效期为20秒
expire key 20
#查看名为 key 的键的剩余时间
ttl key age
#如果一个键 key 未设置有效期,则使用 ttl 命令后显示 -1
#表示永久有效
在Java中使用Redis
使用 Spring Date Redis ,兼容了Jedis 与 lettuce
API | 返回值类型 | 说明 |
---|---|---|
redisTemplate.opsForValue() | ValueOperations | 操作 String 类型数据 |
redisTemplate.opsForHash() | HashOperations | 操作 Hash 类型数据 |
redisTemplate.opsForList() | ListOperations | 操作 List 类型数据 |
redisTemplate.opsForSet() | SetOperations | 操作 Set 类型数据 |
redisTemplate.opsForZSet() | ZSetOperations | 操作 SortedSet 类型数据 |
redisTemplate | 通用命令 |
操作步骤 :
- 导入Spring Date Redis 的 maven 坐标
<!--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>
- 配置Redis数据源
spring:
redis:
database: ${example.redis.database}
host: ${example.redis.host}
port: ${example.redis.port}
password: *****
lettuce:
pool:
max-active: 8 #最大连接
max-idle: 8 #最大空闲连接
min-idle: 0 #最小空闲连接
max-wait: 100 #连接等待时间
-
RedisTemplate序列化
方案一:
- 自定义 RedisTemplate
- 修改RedisTemplate序列化器为 GenericJackson2JsonRedisSerializer
@Configuration
@Slf4j
public class RedisConfiguration {
@Bean
public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory){
log.info("开始创建redis模版对象...");
//创建 RedisTemplate 对象
RedisTemplate<String, Object> redisTemplate = new RedisTemplate();
//设置 Redis 连接工厂对象
redisTemplate.setConnectionFactory(redisConnectionFactory);
//创建 JSON 序列化工具
GenericJackson2JsonRedisSerializer jsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
//设置redis key的序列化器,目的是所见即所得
redisTemplate.setKeySerializer(RedisSerializer.string());
redisTemplate.setHashKeySerializer(RedisSerializer.string());
//设置redis value的序列化
redisTemplate.setValueSerializer(jsonRedisSerializer);
redisTemplate.setHashValueSerializer(jsonRedisSerializer);
return redisTemplate;
}
}
- 方案二:(节省内存空间)
- 使用 StringRedisTemplate
- 写入Redis时,手动把对象序列化为JSON
- 读取Redis时,手动把读取到的JSON反序列化为对象
@Autowired
private StringRedisTemplate stringRedisTemplate;
private static final ObjectMapper mapper = new ObjectMapper();
@Test
void testSaveUser() throws JsonProcessingException{
//创建对象(假设存在这样一个User实体类)
User user = new User("fishpie",21);
//手动序列化,将 user 对象转为json字符串
String json = mapper.writeValueAsString(user);
//写入数据
stringRedisTemplate.opsForValue().set("user:200",json);
//获取数据
String jsonUser = stringRedisTemplate.opsForValue().get("user:200");
//手动反序列化
User user1 = mapper.readValue(jsonUser,User.class);
System.out.println("user1 = " + user1);
}
StringRedisTemplate
是RedisTemplate
的一个子类,它在RedisTemplate
的基础上做了部分优化,默认采用String序列化策略,只能用于操作Redis中的String类型数据由于StringRedisTemplate默认采用String序列化策略,所以使用时不需要再指定序列化/反序列化方式,可以直接传入及接收String数据。
两者的 JavaAPI 一致
-
通过RedisTemplate或StringRedisTemplate对象操作Redis
//一下的操作以 StringRedisTemplate 为例
对于String操作命令
@Autowired
//使用 stringRedisTemplate 时,键值对只能以 String 字符串的形式存储
private StringRedisTemplate stringRedisTemplate;
/**
* stringRedisTemplate对String类型数据的常用操作
*/
@Test
public void testString(){
//使用 stringRedisTemplate 时,键值对只能以 String 字符串的形式存储
ValueOperations<String, String> forValue = stringRedisTemplate.opsForValue();
//添加或修改指定 key 的值
forValue.set("name","fish pie");
//获取指定 key 的值
forValue.get("name");
//如果 key 不存在则设置字符串值
forValue.setIfAbsent("age","20");
//先获取 key 的值,再设置新的字符串值
forValue.getAndSet("age","21");
//将key对应的值加步长(如果key不存在,则先设为0再执行加1操作)
forValue.increment("age",-1);
//将值value追加到指定key字符串值的末尾
forValue.append("name","pie");
//获取key对应字符串的长度
forValue.size("name");
}
对于Hash操作命令
@Autowired
//使用 stringRedisTemplate 时,键值对只能以 String 字符串的形式存储
private StringRedisTemplate stringRedisTemplate;
/**
* stringRedisTemplate对Hash类型数据的常用操作
*/
@Test
public void testHash(){
HashOperations<String, Object, Object> forHash = stringRedisTemplate.opsForHash();
//向指定Hash黄总添加或更新一个字段值对
forHash.put("userGroup1","name","fish pie");
forHash.put("userGroup1","age","20");
//批量多个键值对
HashMap<Object, Object> map = new HashMap<>();
map.put("name","fish pie brave");
map.put("age","20");
forHash.putAll("userGroup2",map);
//获取一个hash中一个字段的value
forHash.get("userGroup1","name");
//获取一个hash中所有的键值对
forHash.entries("userGroup1");
//判断hash中是否存在某个字段
Boolean hasKey = forHash.hasKey("userGroup2", "age");
//指定hash中某个字段值进行增加或减少
forHash.increment("userGroup2","age",-1); //是了,越活越年轻
//获取指定hash中字段的数量
forHash.size("userGroup2");
}
对于List操作命令
@Autowired
//使用 stringRedisTemplate 时,键值对只能以 String 字符串的形式存储
private StringRedisTemplate stringRedisTemplate;
/**
* stringRedisTemplate对List类型数据的常用操作
*/
@Test
public void testList(){
ListOperations<String, String> forList = stringRedisTemplate.opsForList();
//将一个或多个值推入列表左侧
forList.leftPush("list1","value1");
forList.leftPushAll("list1","value2","value3");
//将一个或多个值推入列表右侧
forList.rightPush("list2","value1");
forList.rightPushAll("list2","value1","value2");
//移出列表左侧第一个元素并返回
forList.leftPop("list1");
//移出列表右侧第一个元素并返回
forList.rightPop("list2");
//获取一个列表中指定索引位置的元素
forList.index("list1",0);
//获取一个列表中指定范围内的元素
forList.range("list1",0,2);
forList.range("list1",0,-1); //直接是获取列表list1的所有元素
//修剪某个列表,只保留指定范围内的元素
forList.trim("list1",0,2);
//设置列表中指定索引位置的元素值
forList.set("list1",2,"valueBeenReplace_2");
//获取一个列表的长度
forList.size("list1");
//从列表中移除指定数量的值
//从左侧移出2个list1中值为value1的元素,并返回移出元素的个数
Long l = forList.remove("list1", 2, "value1");
//从右侧移出2个list1中值为value1的元素,并返回移出元素的个数
Long r = forList.remove("list1", -2, "value1");
}
对于Set操作命令
@Autowired
//使用 stringRedisTemplate 时,键值对只能以 String 字符串的形式存储
private StringRedisTemplate stringRedisTemplate;
/**
* stringRedisTemplate对Set类型数据的常用操作
*/
@Test
public void testSet(){
SetOperations<String, String> forSet = stringRedisTemplate.opsForSet();
//向某个集合中添加一个或多个成员
forSet.add("set1","value1","value2","value3","value4");
forSet.add("set2","value1","value2","value3","value4");
//向某个集合中移出一个或多个成员
forSet.remove("set1","value1","value2");
//获取集合中所有的成员
Set<String> set1 = forSet.members("set1");
//判断给定的值是否是集合的成员
Boolean isMember = forSet.isMember("set1", "value3");
//从某个集合中移出并返回一个随机元素
forSet.pop("set1");
//获取某个集合中元素的个数
forSet.size("set1");
//获取两个集合的并集
forSet.union("set1","set2");
//获取两个集合的交集
forSet.intersect("set1","set2");
//获取给定集合set1与set2的差集
forSet.difference("set1","set2");
}
对于有序集合
@Autowired
//使用 stringRedisTemplate 时,键值对只能以 String 字符串的形式存储
private StringRedisTemplate stringRedisTemplate;
/**
* stringRedisTemplate对SortedSet类型数据的常用操作
*/
public void testZSet(){
ZSetOperations<String, String> forZSet = stringRedisTemplate.opsForZSet();
//向某个有序集合中添加一个成员及其分数
forZSet.add("zset1","value1",1.0);
forZSet.add("zset1","value2",2.0);
forZSet.add("zset1","value3",99.0);
forZSet.add("zset1","value4",98.0);
forZSet.add("zset1","value5",80.0);
//从某个有序集合中移出一个或多个成员
forZSet.remove("zset1","value1","value2");
//增加某个有序集合中指定成员的分数
forZSet.incrementScore("zset1","value3",10.0);
//获取某个有序集合中指定范围的成员
//这里是获取所有zset1中的所有成员(正序),分数从小到大
forZSet.range("zset1",0,-1);
//按照分数从小到大排序返回排名从1到3的所有元素
forZSet.range("zset1",0,2);
//获取某个有序集合中指定范围内的成员,并以逆序返回结果
//这里是获取所有zset1中的所有成员(逆序),
forZSet.reverseRange("zset1",0,-1);
//返回某个有序集合指定分数范围内的元素
forZSet.rangeByScore("zset1",80,100);
//返回某个有序集合中指定元素的排名(正序)
//分数从小到大第几个
forZSet.rank("zset1","value5");
//返回某个有序集合中指定元素的排名(逆序)
//分数从大到小第几个
forZSet.reverseRank("zset1","value5");
//获取有序集合中指定成员的分数
forZSet.score("zset1","value5");
}
通用命令操作
@Autowired
//使用 stringRedisTemplate 时,键值对只能以 String 字符串的形式存储
private StringRedisTemplate stringRedisTemplate;
@Test
public void testCommon(){
//查询所有的key
redisTemplate.keys("*");
//判断key是否存在
redisTemplate.hasKey("name");
//获取key的种类
for(Object key : keys){
redisTemplate.type(key);
sout
}
//删除指定key
redisTemplate.delete("mylist");
}
Redis适用的业务类型
以下仅提出理论,并无实际代码示范
短信登录
缓存数据
缓存的概念
缓存就是数据交换的缓冲区(Cache),是临时存储数据的地方,一般读写性能很高
缓存大小也是CPU的性能指标之一
可以将需要查询的数据存入Redis中达到缓存的目的
缓存的应用场景
- 缓存的作用
- 降低后端负载
- 提高读写效率,降低响应时间
- 缓存的成本
- 数据一致性成本,缓存存储与内存,磁盘中的数据与内存中的数据一致性问题需要解决
- 代码维护成本
- 运维成本
缓存的更新策略
- 内存淘汰(Redis自身机制)
- 超时剔除
- 主动更新
在主动更新缓存时,推荐先操作数据库,再删除缓存
缓存穿透
客户端请求的数据在缓存中和数据库中都不存在,这样缓存永远不会生效,这些请求都会打到数据库
大量的请求查询不存在的数据,会对数据库造成很大的压力,可能导致数据库崩溃
缓存穿透解决方案:
- 缓存空对象
缺点:
为内存带来额外压力
可能会造成短暂的数据不一致性
- 布隆过滤
在缓存之前,先使用布隆过滤器判断数据是否存在。布隆过滤器是一种高效的数据结构,可以快速判断一个元素是否在集合中。如果布隆过滤器判断数据不存在,就直接返回
有点类似于双重校验机制
- 增加 id 复杂度
从根源解决用户请求
缓存雪崩
同一时间段大量的缓存key同时失效或者Redis服务器宕机,导致大量请求到达数据库,带来巨大压力
数据库无法同时承受如此大的查询压力,从而导致数据库崩溃,进而导致整个系统崩溃
缓存雪崩的解决方案:
-
缓存高可用
实现Redis的高可用
部署多个服务器,避免单节点故障
-
缓存预热
提前将热点数据加入到缓存,避免大量请求直接到达数据库
定时刷新缓存(定时预热)
-
避免同时过期
设置不同的缓存过期时间,避免同一时间大量缓存失效
(可以在原有的过期时间基础增加一个随机值,使得每个缓存的过期时间分散开来)
-
限流降级
缓存失效后,通过加锁或者队列的方式控制对数据库的访问量,避免数据库承受过大压力
设置合理的阈值,当请求达到阈值时进行降级处理,如返回默认数据,提示错误等
-
数据库优化
提高查询性能(索引,SQL语句,分库分表)
-
多级缓存
在Redis之前再加一层缓存(如Nginx),减小Redis的压力
-
监控报警
对缓存的访问量、命中率、错误率进行实时监控,异常及时报警,对问题采取特化方案
缓存击穿
小型但是致命的雪崩
一个热点的Key在缓存中失效时,大量的请求同时访问该Key,由于缓存中没有数据,所有的请求都会落到数据库上,对数据库造成巨大的压力,导致数据库宕机
热点key的缓存建立较慢
缓存击穿的解决方案:
-
互斥锁
热点缓存失效时,先使用互斥锁对 key 加锁,只让一个请求去读取数据库数据并再次建立缓存,其他的请求等待缓存生效时再读取缓存,可以有效防止数据库宕机,但是会很大程度影响用户体验
-
永不过期
将热点数据的缓存设置为永不过期,但是要保证数据一致性的问题
-
数据预热
热点缓存快要失效前,主动对热点数据进行数据库查询并更新缓存
-
双缓存
对同一个热点数据建立一个短期缓存和一个长期缓存,当短期缓存失效时,从长期缓存中读取数据并重建短期缓存,当长期缓存也失效时再从数据库中查询并建立缓存,(减少数据库的查询频率)
-
限流
就是限流
分布式锁
在集群条件下,每个服务器都会有一台JVM运行Tomcat,Synchronized
只能锁住当前服务器的此台JVM的Tomcat下的一个线程,所以要引入分布式锁
满足分布式系统或集群模式下多进程可见并且互斥的锁
通过Redis实现分布式锁的方法:
接口 ILock tryLock() / unlock
-
获取锁
-
互斥:确保只能有一个线程获取锁
-
非阻塞:尝试一次,成功返回true,失败返回 false
尝试获取锁 tryLock()
#添加锁,NX是互斥,EX是设置超时时间 set lock thread1 nx ex 10 不拆分为 set lock thread expire lock 10 的目的是为了保证起原子性,防止Redis还未执行给 key 设置 TTL 而宕机!
-
-
释放锁
-
手动释放
unlock()
-
超时释放
#释放锁 del lock
-
锁的线程问题
解决方案:在一个线程释放锁时判断锁的标识是否一致(看看现在的锁还是不是自己一开始拿的锁)
解决锁的线程问题:
- 获取锁时存入线程标识(使用UUID)
- 在释放锁时先获取锁中的线程标识,判断是否与当前线程标识一致
- 如果一致则释放锁
- 如果不一致则不释放锁
uuid是区分不同服务器的,线程id是区分相同服务器的