Redis的优缺点
优点
- redis是基于内存结构 性能极高(读110000次/秒,写81000次/秒)
- redis基于键值对存储 但是支持多种数据类型
- redis的所有操作都是原子性 可以通过lua脚本将多个操作和为一个原子操作(Redis的事务)
- redis是基于单线程操作 但是其多路复用实现了高性能读写
缺点
- 缓存数据与数据库数据必须通过两次操作才能保持数据的一致性
- 使用缓存会存在缓存穿透、缓存击穿、缓存雪崩等问题 需要处理
- redis可以作为数据库使用进行数据帝持久存储 存在数据丢失的风险
Redis基于Linux的安装和配置
安装
#下载包
[root@ljw software]# wget http://download.redis.io/releases/redis-5.0.5.tar.gz
#安装编译器
[root@ljw software]# yum -y install gcc
#解压redis安装包
[root@ljw servers]# tar -zxvf redis-5.0.5.tar.gz -C /export/servers/
#编译
[root@ljw redis-5.0.5]# make MALLOC=libc
#安装服务
[root@ljw redis-5.0.5]# make install
#启动服务(后台保持运行)
[root@ljw redis-5.0.5]# redis-server &
#启动命令行
[root@ljw redis-5.0.5]# redis-cli
#默认配置启动redis
[root@ljw redis-5.0.5]# redis-server
#使用 redis.conf 配置文件启动 不默认启动(可同时启动多个端口)
[root@ljw redis-5.0.5]# redis-server redis.conf
配置
常用reids配置
## 设置redis实例(服务)为守护模式,默认值为no,可以设置为yes
daemonize no/yes
## 设置当前redis实例启动之后自动保存进程id的文件路径
## 当启动一个redis服务时,redis.conf对应的pidfile项也会随之一起启动一个端口
## 如果多个服务同用一个pidfile配置项,后启动的服务会把前一个服务启动的端口覆盖
## 启动多个redis服务时 需要配置redis.conf的port和profile
pidfile /var/run/redis_6379.pid
## 设置reids实例的启动端口(默认6379)
port 6380
## 设置当前redis实例是否开启保护模式
protected-mode yes
## 允许访问实例的ip地址(protected-mode yes的前提下)
bind 127.0.0.1
## 设置连接密码
requirepass 123456
## 设置redis实例中数据库的个数(默认16个,编号0-15)
databases 16
## 设置最大并发数
maxclients 10000
## 设置客户端和redis建立连接的空闲时间(设置为0表示客户端不关闭就不关闭)
timeout 0
Redis的基本使用
1.Redis存储的数据结构
Redis是以键值对形式进行存储的,但是value支持多种数据类型
2.string常用指令
127.0.0.1:6379> set key1 value1
OK
127.0.0.1:6379> get key1
"value1"
# 批量添加
127.0.0.1:6379> mset key2 value2 key3 value3
# 批量取值
127.0.0.1:6379> mget key1 key2 key3
1) "value1"
2) "value2"
3) "value3"
# 自增和自减
incr key ## 在key对应的value上自增 +1
decr key ## 在key对应的value上自减 -1
incrby key v ## 在key对应的value上自增 +v
decrby key v ## 在key对应的value上自减 -v
# 添加键值对,并设置过期时间(TTL)
setex key [time](seconds) value
# 设置值,如果key不存在泽添加成功,如果key存在则添加失败(不做修改操作)
setnx key value
# 在指定的key对应的value拼接字符串
append key value
# 获取key对应的字符串长度
strlen key
3.hash常用指令
## 向key对应的hash中添加键值对
hset key field value
## 向key对应的hash获取field对应的值
hget key field
## 向key对应的hash结构中批量添加键值对
hmset key f1 v1 [f2 v2]
## 向key对应的hash批量获取field对应的值
hmset key f1 [f2]
## 再key对应的hash中的field对应的value上加v
hincrby key field v
## 获取key对应的hash中所有的键值对
hgetall key
## 获取key对应的hash中的所有的field
hkeys key
## 获取key对应的hash中的所有的value
hvals key
## 检查key对应的bash中是否右指定的field
hexists key field
## 获取key对应的hash中键值对的个数
hlen key
## 向key对应的hash结构中添加f-v 如果是field在hash中已经存在 则添加失败
hsetnx key field value
4.list常用指令
对于list数据结构,支持两侧存取数据
同侧存取:实现了堆栈结构,后进先出
异侧存取:实现了队列结构,先进先出
## 存储数据
lpush key value #在key对应的列表的左侧添加数据value
rpush key value #在key对应的列表的右侧添加数据value
## 获取数据
lpop key #从key对应的列表的左侧取一个值
rpop key #从key对应的列表的右侧取一个值
## 修改数据
lset key index value #修改key对应的列表的索引位置的数据(索引从左往右,从0开始)
## 查看key对应的列表中索引从start到stop中所有的值
lrange key start stop
## 查看key对应的列表中index索引对应的值
lindex key index
## 获取key对应的列表中的元素的个数
llen key
## 从kye对应的列表中截取key在[start,stop]范围的值,不在次范围的数据一律被清除掉
ltrim key start stop
## 从k1右侧取出一个数据存放到k2的左侧
rpoplpush k1 k2
5.set常用指令
## 存储元素,在key对应的集合中添加元素
sadd key v1 [v2 v3 v4...]
## 遍历key对应的集合中所有的元素
smembers key
## 随机从key对应的结合中获取一个值(出栈)
spop key
## 交集
sinter key1 key2
## 并集
sunion key1 key2
## 差集
sdiff key1 key2
## 从key对应的集合中移出指定的value
srem key value
## 检查key对应的结合中是否有指定的value
sismember key value
6.zset常用指令
zset 有序不可重复集合
## 存储数据(score存储位置必须是数值,可以是float类型,member元素不允许重复)
zadd key score member [score member...]
## 查看key对应的有序集合中索引[start,stop]数据--按照score值由小到大
## [start,stop]不是指score,而是元素在有序集合中的索引
zrange key start stop
## 查看member元素在key对应的有序集合中的索引
zscore key member
## 获取key对应的zset中的元素的个数
zcard key
## 获取key对应的zset中,score在[min,max]范围内的member个数
zcount key min max
## 从key对应的zset中移除指定的member
zrem key6 member
## 查看key对应的有序集合中索引[start,stop]数据--按照score值从小到大
zrevrange key start stop
常用指令
key相关指令
## 查看redis中满足pattern规则的所有key(查看所有key)(keys *)
keys pattern
## 查看指定的key是否存在
exists key
## 删除指定的key-value对
del key
## 获取当前key的存活时间(如果没有设置过期返回-1,设置过期并且已经过期返回-2)
ttl key
## 设置键值对过期时间
expire key seconds
pexpire key milliseconds
## 取消键值对过期时间
persist key
db常用指令
redis的键值对是存储在数据库中的--db
redids中有默认的16个db 编号0-15
## 切换数据库
select index
## 将键值对从当前db移动到目标db
move key index
## 清空当前数据库数据
flushdb
## 清空所有数据库的k-v
flushall
## 查看当前db中k-v的个数
dbsize
## 获取最后一次持久化操作时间
lastsave
Redis的持久化
redis基于内存操作,但作为一个数据库也具备数据的持久化能力,但是为了实现高效的读取操作,并不会即时进行数据的持久化,而是按照一定的规则进行持久化操作的--持久化策略
redis提供了2种持久化策略
- RDB(Redis DataBase)
- AOF(Append Only File)
RDB
在满足特定的redis操作条件时,将内存中的数据以数据快照的形式存储到db文件中
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7dHWb7Jo-1649410132050)(C:\Users\asus\AppData\Roaming\Typora\typora-user-images\image-20220401210644089.png)]
-
原理
RDB是redis默认的持久化策略,当redis中的写操作达到指定的次数,同时距离上一次持久化达到指定的时间时,就会将redis内存中的数据生成数据快照,保存到指定的db文件中
-
默认触发持久化条件
- 900s 1次:当操作达到1次,900s就会进行持久化
- 300s 10次:当操作达到10次,300s就会进行持久化
- 60s 10000次:当操作达到10000次,60s就会进行持久化
-
可以通过修改redis.conf来设置RDB持久化策略的触发条件
# RDB持久化策略开关 rdbcompression yes # 配置redis的持久化策略 save 900 1 save 300 10 save 60 10000 # 指定RDB数据存储的文件 dbfilename dump.rdb
-
RDB持久化细节分析
- 缺点
- 如果redis出现故障,会存在数据丢失的风险,丢失上一次持久化之后的操作数据
- RDB采用的是数据快照形式进行持久化,不适合实时性持久化
- 如果数据量巨大,在RDB持久化过程中生成数据快照的子进程执行时间过长,会导致redis卡顿,因此时间周期设置不宜过短
- 优点
- 但是在数据量比较小的情况下,执行速度比较快
- RDB是以数据快照的形式进行保存的,我们可以通过拷贝rdb文件轻松实现redis数据移植
- 缺点
AOF
Append Only File,当达到设定触发条件时,将redis执行的写操作指令存储到AOF文件中
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ttvY2DN4-1649410132053)(C:\Users\asus\AppData\Roaming\Typora\typora-user-images\image-20220401212328019.png)]
-
原理
Redis将每一个成功的写操作写入到aof文件中,当redis重启动时候执行aof文件中的指令恢复数据
-
配置
## 开启AOF appendonly yes ## 设置触发条件(三选一) appendfsync always ## 只要进行成功的写操作后,就执行aof appendfsync everysec ## 每秒进行一次aof appendfsync no ## 让redis决定执行aof ## 设置aof文件路径 appendfilename 'appendonly.aof'
-
AOF细节分析
- 也可以通过拷贝aof文件进行redis数据移植
- aof存储的指令,而且会对指令进行整理;而rdb是直接生成数据快照;数据量不大的时候RDB比较快。
- aof是对指令文件进行增量更新,更适合实时性持久化
- redis官方建议同事开启两种持久化策略,如果同时存在aof文件和rdb文件的情况下,aof优先
JAVA应用连接Redis
在普通Maven工程连接Redis
JAVA应用连接Redis 首先要将我们的Redis设置允许远程连接
- 修改redis-6379.conf
## 关闭保护模式
protected-mode no
## 将bind注释(如果不注释,默认为bind 127.0.0.1 只能本机访问)
# bind 127.0.0.1
## 密码可以设置 也可以不设置
# requirepass 123456
- 重启redis
redis-server redis-6379.conf
-
阿里云安全组设置放行6379端口
-
添加Jedis依赖和Gson依赖
<dependencies>
<!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.google.code.gson/gson -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.5</version>
</dependency>
</dependencies>
- 使用案例
public static void main(String[] args) {
Product ad = new Product("101","ad钙",2.5);
//1.连接redis
Jedis jedis = new Jedis("120.25.179.232", 6379);
//如果有设置密码
//jedis.auth("123456")
//2.操作
String set = jedis.set(ad.getProductID(), new Gson().toJson(ad));
System.out.println(set);
//3.关闭
jedis.close();
}
在springboot工程连接Redis
Spring Data Redis, part of the larger Spring Data family, provides easy configuration and access to Redis from Spring applications. It offers both low-level and high-level abstractions for interacting with the store, freeing the user from infrastructural concerns.
Spring Data Redis依赖中 ,提供了链接redis的客户端
RedisTemplate
StringRedisTemplate
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Vx7xahuw-1649410132053)(C:\Users\asus\AppData\Roaming\Typora\typora-user-images\image-20220402012705498.png)]
- 配置redis(application.yml)
spring:
redis:
database: 16
host: 120.25.179.232
port: 6379
- 案例
直接在service层注入 RedisTemplate 或 StringRedisTemplate 就可以使用此对象完成reids操作
spring data redis
不同数据结构添加操作
//1.string(常用) set key value
stringRedisTemplate.boundValueOps(ad.getProductId()).set(s);
//2.hash(常用)
//stringRedisTemplate.boundHashOps("products").put(ad.getProductId(),s);
//3.List
//stringRedisTemplate.boundListOps("list").leftPush("aaa");
//4.set(无序)
//stringRedisTemplate.boundSetOps("s1").add("v1");
//5.zset(有序)
//stringRedisTemplate.boundZSetOps("z1").add("v1",1.2);
不同数据结构取值操作
//string
String s = stringRedisTemplate.boundValueOps(productId).get();
//hash
Object products = stringRedisTemplate.boundHashOps("products").get("103");
//list
String list = stringRedisTemplate.boundListOps("list").leftPop();
String list1 = stringRedisTemplate.boundListOps("list").rightPop();
String list2 = stringRedisTemplate.boundListOps("list").index(1);
//set
Set<String> s1 = stringRedisTemplate.boundSetOps("s1").members();
//zest
Set<String> z1 = stringRedisTemplate.boundZSetOps("z1").range(0, 5)
string类型的操作方法
//添加数据时指定过期时间300s setex key 300 value
stringRedisTemplate.boundValueOps(ad.getProductId()).set(s,300);
//设置key过期时间 300L(L表示long类型数据) expire key 300
stringRedisTemplate.boundValueOps(ad.getProductId()).expire(300L, TimeUnit.SECONDS);
//添加数据 setnx key value 是否存在
Boolean aBoolean = stringRedisTemplate.boundValueOps(ad.getProductId()).setIfAbsent(s);
使用Redis缓存数据库数据
Redis作为缓存的使用流程
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7nK7dhCW-1649410132054)(C:\Users\asus\AppData\Roaming\Typora\typora-user-images\image-20220405233646760.png)]
在项目中使用Reids缓存商品详情
在Service层添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
在Mapper层配置配置文件
#由于Service层是依赖于Mapper层 所以可以配置在Mapper层
spring:
datasource:
druid:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://120.25.179.232:3306/legoforever?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT
username: root
password: adminroot
redis:
port: 6379
host: 120.25.179.232
database: 0
json格式转list列表
String productImgs = (String) stringRedisTemplate.boundHashOps("products").get(productId);
JavaType javaType = objectMapper.getTypeFactory().constructParametricType(ArrayList.class, LgProductImg.class);
List<LgProductImg> productImgs1=objectMapper.readValue(productImgs,javaType);
案例
说明:在电商系统中,为了提高商品详情的查询速度,减少对数据库的并发访问压力,我们可以使用redis来缓存商品详情,
@Autowired
private StringRedisTemplate stringRedisTemplate;
private ObjectMapper objectMapper= new ObjectMapper();
@Override
public ResultVO listRecommendProducts() {
List<LgProductVO> productVOS = lgProductMapper.selectRecommendProducts();
ResultVO resultVO = new ResultVO(ResStatus.OK, "success", productVOS);
return resultVO;
}
@Transactional(propagation = Propagation.SUPPORTS)
public ResultVO getProductBasicInfo(String productId) {
try {
// 1.根据商品ID查询redis
String productInfo = (String) stringRedisTemplate.boundHashOps("lgProducts").get(productId);
// 2.如果redis查询到了商品信息,直接返回给控制器
if (productInfo!=null){
LgProduct lgProduct = objectMapper.readValue(productInfo, LgProduct.class);
//从redis查询商品的图片
String productImgs = (String) stringRedisTemplate.boundHashOps("lgProductImgs").get(productId);
JavaType javaType = objectMapper.getTypeFactory().constructParametricType(ArrayList.class, LgProductImg.class);
List<LgProductImg> productImgs1=objectMapper.readValue(productImgs,javaType);
//从redis查询商品的套餐
String productSkus = (String) stringRedisTemplate.boundHashOps("lgProductSkus").get(productId);
JavaType javaType1 = objectMapper.getTypeFactory().constructParametricType(ArrayList.class, LgProductSku.class);
List<LgProductSku> productSkus1=objectMapper.readValue(productSkus,javaType1);
//封装商品、图片、套餐
HashMap<String, Object> basicInfo = new HashMap<>();
basicInfo.put("lgProducts", lgProduct);
basicInfo.put("lgProductImgs", productImgs1);
basicInfo.put("lgProductSkus", productSkus1);
return new ResultVO(ResStatus.OK,"success",basicInfo);
}else {
// 3.如果redis没有查询到了商品信息,查询数据库
//1.商品基本信息
Example example = new Example(LgProduct.class);
Example.Criteria criteria = example.createCriteria();
criteria.andEqualTo("lgProductId", productId);
criteria.andEqualTo("lgProductStatus", 1);//状态为1代表上架商品
List<LgProduct> lgProducts = lgProductMapper.selectByExample(example);
if (lgProducts.size() > 0) {
//商品信息
LgProduct lgProduct = lgProducts.get(0);
String jsonStr = objectMapper.writeValueAsString(lgProduct);
stringRedisTemplate.boundHashOps("lgProducts").put(productId,jsonStr);
//商品图片
Example example1 = new Example(LgProductImg.class);
Example.Criteria criteria1 = example1.createCriteria();
criteria1.andEqualTo("lgProductImgItemId", productId);
List<LgProductImg> lgProductImgs = lgProductImgMapper.selectByExample(example1);
stringRedisTemplate.boundHashOps("lgProductImgs").put(productId,objectMapper.writeValueAsString(lgProductImgs));
//商品套餐
Example example2 = new Example(LgProductSku.class);
Example.Criteria criteria2 = example2.createCriteria();
criteria2.andEqualTo("lgProductSkuProductId", productId);
criteria2.andEqualTo("lgProductSkuStatus", 1);
List<LgProductSku> lgProductSkus = lgProductSkuMapper.selectByExample(example2);
stringRedisTemplate.boundHashOps("lgProductSkus").put(productId,objectMapper.writeValueAsString(lgProductSkus));
HashMap<String, Object> basicInfo = new HashMap<>();
basicInfo.put("lgProducts", lgProducts.get(0));
basicInfo.put("lgProductImgs", lgProductImgs);
basicInfo.put("lgProductSkus", lgProductSkus);
return new ResultVO(ResStatus.OK, "success", basicInfo);
}
}
}catch (Exception e) {
}
return new ResultVO(ResStatus.NO, "查询的商品不存在", null);
}
页面静态化
在电商系统中,为了提高商品详情的查询速度,减少对数据库的并发访问压力,我们可以使用redis来缓存商品详情,除此之外我们还可以使用
页面静态化
技术来达到次目的
页面静态化:将数据库的每条数据结合模板生成单独的HTML文件进行存储(一条数据对应一个独立的HTML文件),当用户访问数据时,直接访问不同的静态HTML文件即可
使用Redis缓存平台页面数据
什么样的数据适合用缓存
因为缓存中的数据需要进行数据一致性的维护,当数据库数据发生变化后,要同步更新缓存中的数据
因此对于数据库的写操作交少,频繁查询数据库的数据更适合缓存
对于可能会发生修改,但是对于数据一致性要求不高的数据也适合缓存
使用Redis做缓存使用存在的问题
使用redis作为缓存存在高并发场景下可能出现缓存击穿、缓存穿透、缓存雪崩等问题
页面静态化
在电商系统中,为了提高商品详情的查询速度,减少对数据库的并发访问压力,我们可以使用redis来缓存商品详情,除此之外我们还可以使用
页面静态化
技术来达到次目的
页面静态化:将数据库的每条数据结合模板生成单独的HTML文件进行存储(一条数据对应一个独立的HTML文件),当用户访问数据时,直接访问不同的静态HTML文件即可
使用Redis缓存平台页面数据
什么样的数据适合用缓存
因为缓存中的数据需要进行数据一致性的维护,当数据库数据发生变化后,要同步更新缓存中的数据
因此对于数据库的写操作交少,频繁查询数据库的数据更适合缓存
对于可能会发生修改,但是对于数据一致性要求不高的数据也适合缓存
使用Redis做缓存使用存在的问题
使用redis作为缓存存在高并发场景下可能出现缓存击穿、缓存穿透、缓存雪崩等问题
缓存击穿
未完待续