Redis的基本使用与进阶知识
Redis是采用键值对key和value来存储数据的一种非关系型数据库,因为其数据是存储在内存中的,所以读写效率高,常用于的场景有:海量数据的读写、数据高并发的读写、数据缓存等。
一、基本数据类型
Redis数据库有五种基本数据类型,现在新版本Redis又新增了三种数据类型
Bitmaps
、HyperLogLog
、Geospatial
,这里不作说明,只简要说明五种基本数据类型。
1. String
字符串类型,存储的 value 数据为字符串。
2. List
列表类型,存储的 value 数据为一个列表,有序列表,其数据排列的顺序与数据添加的顺序相同。
3. Hash
哈希类型,存储的 value 数据为一个对象,对象内也是采用 key 和 value 进行存储数据的,类似于Java中的 map 数据类型或者Python中的字典 dict 类型。
4. Set
集合类型,存储的 value 数据为一个集合,该集合是 无序 的,集合中不允许有重复值,会自动去重。
5. Zset
有序集合类型,存储的 value 数据也是集合,该集合是 `有序` 的,通过给每个 value 设置 score 值,按照每个 value 的 score 值进行排序,同时该集合也是不包含重复值的,自动去重。
二、基本操作命令
1. String
String字符串类型中的常用命令操作
# 设置key为k1,值为10
set k1 10
# 获取key为k1的值
get k1
# 对key为k1的value值进行+1
incr k1
# 对key为k1的value值进行+20
incrby k1 20
# 对key为k1的value值进行-1
decr k1
# 对key为k1的value值进行-20
decrby k1 20
# 设置k1的过期时间为20s
expire k1 20
# 设置k2的值为v2,过期时间为30s
setex k2 30 v2
# 查看k2的过期时间(-1表示永久有效,-2表示已失效,其他数字表示剩余过期时间)
ttl k2
# 设置k2的值为v2,若k2已存在,则不设置,否则设置。
setnx k2 v2
# 同时设置多个key和value
mset k1 v1 k2 v2
# 同时获取多个key的value
mget k1 k2
2. List
List列表类型中的常用命令操作
# 从左边依次添加数据至t1列表中,结果为 3 2 1
lpush t1 1 2 3
# 从右边依次添加数据至t2列表中,结果为 1 2 3
rpush t2 1 2 3
# 从t1列表中获取索引从0到-1的所有value值,索引0代表第一个,索引-1代表最后一个
lrange t1 0 -1
# 从左边依次删除t1列表中的值
lpop t1
# 从右边依次删除t1列表中的值
rpop t1
# 获取t1列表的长度
llen t1
3. Hash
Hash类型中的常用命令操作
# 设置key为k1中的id值等于1001,一次只能设置一个
hset k1 id 1001
# 设置key为k1中的name=lucy,age=20,一次可以设置多个值
hmset k1 name lucy age 20
# 获取k1中id的值
hget k1 id
# 一次性获取k1中的id、name、age值
hmget k1 id name age
# 查看key为k1中的所有属性key 结果-> id name age
hkeys k1
# 查看key为k1中的所有属性key所对应的值 结果-> 1001 lucy 20
hvals k1
# 对key为k1中的age值+2
hincrby k1 age 2
4. Set
Set集合类型中的常用命令操作
# 添加 1 2 3 元素到t1集合中
sadd t1 1 2 3
# 删除t1集合中的3元素
srem t1 3
# 查看t1集合中是否包含4这个元素
sismember t1 4
# 查看t1集合中的所有元素
smembers t1
5. Zset
Zset有序集合类型中的常用命令操作
# 添加 java c++ php 到t1集合中,并指定相应的score值为 100 200 300
zadd t1 100 java 200 c++ 300 php
# 删除t1集合中的java元素
zrem t1 java
# 查看t1集合中索引从0到-1的所有元素(索引0代表第一个元素,索引-1代表最后一个元素)
# 结果 "java" "c++" "php" (按照score值进行从小到大排序)
zrange t1 0 -1
# 查看t1集合中索引从0到-1的所有元素,并显示相应的score值。
# 结果 "java" 100 "c++" 200 "php" 300
zrange t1 0 -1 withscores
# 查看t1集合中索引从0到-1的所有元素,并按倒序(从大到小)排序
# 结果 "php" "c++" "java"
zrevrange t1 0 -1
# 查看t1集合中索引从0到-1的所有元素,按倒序(从大到小)排序,并显示相应的score值。
# 结果 "php" 300 "c++" 200 "java" 100
zrevrange t1 0 -1 withscores
补充其他通用命令
# 查看符合正则表达式的所有key -> (keys pattern)
# 查看所有的key
keys *
# 删除对应的key
del key
# 设置key的过期时间为20s
expire key 20
# 查看key的过期时间(-1表示永久有效,-2表示已失效,其他数字表示剩余过期时间)
ttl key
三、进阶知识
1. 数据持久化
由于Redis的数据是写入到内存中,没有对数据进行持久化操作,当服务器重启时,数据就会丢失,所以需要对数据进行持久化操作,写入到磁盘中。
-
RDB模式
:根据自定义设置的时间间隔,将满足条件的数据写入到磁盘中进行备份。 优缺点:效率高,但最后一次时间间隔内的数据可能会丢失。
-
AOF模式
:将所有的写操作记录到日志文件中,服务器重启时,根据日志文件来恢复数据。 优缺点:能够完全保证数据的可靠性,但是数据恢复比较慢。
2. 主从复制
一台服务器作为主服务器,只负责写操作,其他服务器作为主服务器的从服务器,只负责读数据。
优点:读写分离、容灾快速恢复
缺点:复制延时(从机同步数据有一定的延迟)
-
一仆二主
: 一台主机连接着多台从机 -
薪火相传
: 一台主机连接着一台从机,从机下面又连接着一台从机 -
反客为主
: 当主机宕机之后,其中的一台从机输入命令slaveof no one
升级为主机(可以使用哨兵模式自动完成)
-
哨兵模式
: 监测主机的运行状态,当主机宕机之后,将其中的一台从机升级为主机。
3. 集群
集群是为了保证服务的高可用性,当某些服务器宕机之后,还可以继续提供服务。
优点:
1. 能够解决容量不够的问题 。
2. 能够解决高并发读写的问题,分摊服务器压力。 2. 去中心化配置(任何一台服务器都可以作为入口服务器来进行读写)
4. 缓存穿透
缓存穿透是指查询一个不存在的数据时,由于Redis未命中,则会去数据库中查询该数据,当数据库查询不到该数据时,不会写入缓存,导致每次查询不存在的数据时,都会去访问数据库,造成缓存穿透。
解决办法:
- 将查询到的空值设置到缓存中(设置过期时间)
- 设置可访问的白名单
- 采用布隆过滤器
5. 缓存击穿
缓存击穿是指,当访问一些比较热门的key时,而此时的key又过期了,导致大量请求数据库,数据库宕机。
6. 缓存雪崩
缓存雪崩是指,在极少的时间段内,查询的大量key都过期了,导致大量请求数据库,数据库宕机。
7. 分布式锁
分布式锁可以保证同步操作,比如抢购、秒杀,保证不会产生超卖等问题。JAVA代码如下:
public class SellServiceImpl implements SellService {
@Autowired
private RedisTemplate redisTemplate;
/**
* 库存总量
*/
private static final Integer TOTAL = 1000;
/**
* 例如国庆大甩卖 图书大甩卖 库存 1000 件
* 存放商品信息
*/
static Map<String, Integer> products;
/**
* 抢购成功者信息
*/
static Map<String, String> orders;
static {
products = new HashMap<>();
orders = new HashMap<>();
// 设置商品名称及库存
products.put("book", TOTAL);
}
/**
* 第一种方法 synchronized 锁机制,解决高并发产生的超卖问题 但效率大大降低 不推荐使用
* 第二种方法 使用 Redis 分布式锁,解决高并发产生的超卖问题 并且效率相对高很多
*/
@Override
public String orderGoods(String productId) {
String lockValue = UUID.randomUUID().toString();
// 设置锁,并设置3s过期时间,防止手动释放锁之前程序出错,导致锁一直未释放。
Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", lockValue, 3, TimeUnit.SECONDS);
// 加锁失败 说明有人正在使用
if (!lock) {
return "抢购失败,请再试试吧...";
}
//先获取商品余量
int number = products.get(productId);
if(number <= 0){
return "商品已抢购完,请您下次再来,谢谢您的理解...";
}else {
//模拟下单(不同用户拥有不同ID)
orders.put(UUID.randomUUID().toString(), productId);
//减库存
number = number - 1;
//模拟延迟
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
products.put(productId, number);
}
log.info("共抢购 {} 件,抢购详情:{}", orders.size(), orders);
// 手动解锁
String value = (String) redisTemplate.opsForValue().get("lock");
// 确保释放的锁是同一次设置的锁
if (lockValue.equals(value)){
redisTemplate.opsForValue().getOperations().delete("lock");
}
return "抢购成功!";
}
@Override
public String queryGoods(String productId) {
return "国庆图书大甩卖,库存 " +
TOTAL + " 件,现余 " +
products.get(productId) + " 件,已被抢购 " +
orders.size() + " 件";
}
}
四、Redis整合SpringBoot使用
1. 引入依赖
<!-- Redis依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2. 配置YML文件
spring:
redis:
host: 127.0.0.1
port: 6379
password: 123456
database: 0
3. 配置RedisTemplate
RedisTemplate无需配置也可以使用,只不过没有对key和value进行序列化操作,会导致存储到Redis数据库中的数据为16进制编码格式。
package cn.quicklyweb.flea.config;
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.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
/**
* 自定义RedisTemplate
*/
@Configuration
public class RedisTemplateConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
// 创建RedisTemplate实例
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
// 创建Redis的String字符串序列化对象
RedisSerializer<String> stringSerializer = new StringRedisSerializer();
// 创建Redis的Json字符串序列化对象
GenericJackson2JsonRedisSerializer genericJackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
// key采用String的序列化方式
redisTemplate.setKeySerializer(stringSerializer);
// hash的key也采用String的序列化方式
redisTemplate.setHashKeySerializer(stringSerializer);
// value采用Jackson的Json序列化方式
redisTemplate.setValueSerializer(genericJackson2JsonRedisSerializer);
// hash的value也采用Jackson的Json序列化方式
redisTemplate.setHashValueSerializer(genericJackson2JsonRedisSerializer);
// 设置默认的序列化器为Jackson的Json序列化方式
redisTemplate.setDefaultSerializer(genericJackson2JsonRedisSerializer);
// 设置连接工厂
redisTemplate.setConnectionFactory(factory);
return redisTemplate;
}
}
4. 使用
// 直接注入后便可操作Redis数据库了
@Autowired
private RedisTemplate redisTemplate
redisTemplate.opsForValue.set("key", "value")
redisTemplate.opsForValue.get("key")