Redis
1.安装
1.官网下载redis-7.0.10.tar.gz
2.传到Linux的/usr/local目录中
cd /usr/local
#解压
tar -zxvf redis-7.0.10.tar.gz
#redis-7.0.10
cd redis-7.0.10
#使用make命令
make&&make install
#进入文件
cd redis.conf
#修改配置
daemonize yes
protected-mode no
#bind 127.0.0.1
自己添加requirepass *****
#完成后重启
#启动redis
redis-server redis.conf
#启动redis客户端
redis-cli -a ***** -p 6379
#查看
ps -ef |grep redis
#关闭
redis-cli -a 111111 shutdown
2.key的基本命令
查看所有key: keys *
查看key类型:type key
查看是否存在:exists key | exists key1,key2,key3
切换Redis库:select (index)
设定过期时间:expire key seconds
查看剩余过期:ttl key (永久返回-1,已经过期返回-2)
查看数据库的数量:dbsize
清空当前数据库:flushdb
清空所有数据库:fludb
删除key:del key
3.基本数据类型
string
获取长度:strlen k1
追加字符:append k1 [value]
添加单个:set k1 [value]
获取单个:get k1
秒过期时间:set k1 [value] ex [过期时间]
毫秒过期时间:set k1 v1 px [过期时间]
秒单位时间戳:set k1 v1 exat 【过了当前时间戳就为过期】
毫秒单位时间戳:set k1 v1 pxat【时间戳】
不存在时添加:set k1 v1 nx
存在的时候:set k1 v1 xx
保存过期时间:set k1 v1 keepttl
先get后set:getset k1 【value】
不存在时添加:setnx k1 v1
批处理
添加多个:mset ka va kb vb
获取多个:mget ka kb
不存在时添加多个:msetnx ka va kc vc 必须全部成功
获取指定位置:getrange k1 【sta】【 end 】
设置指定位置:setrange k1 【index】【 value】
加减
对数字类型
自增:incr k1
自减:decr k1
增量20:incrby k1 【增量】
减量20:decrby k1 【减量】
list
左边加入:lpush list1 【elements.......】
右边加入:rpush list1 【elements.......】
左边遍历:lrange list1 【sta】【end】
左边出栈:lpop list1 【出栈个数】
右边出栈:rpop list1 【出栈个数】
索引取值:lindex list1 【index】
获取长度:llen list1
删除指定个数指定元素:lrem list1 【个数】【元素】
截取指定长度赋值给list:ltrim list1 【sta】【end】
原子操作尾出头插:rpoplpush list1 list2
指定下标左边加入:lset list1 【index】【element】
左边指定元素前后加入:linsert list1 【after/before】【element】【java】
hash
添加对象:hset 【user:001】 name zhangsan age lisi
获取对象:hget 【user:001】 name
获取全部参数:hgetall 【user:001】
获取多个:hmget 【user:001】 name age
属性值增加:hincrby 【user:001】 age 【增量】
属性值增加: hincrbyfloat 【user:001】 age 【增量】
添加对象属性如果不存在:hsetnx user:001 name zhangsan
删除对象元素:hdel user:001 name
set
添加 sadd s1 【elements......】 //只有abcd四个元素
获取 smembers s1
判断是否存在 sismember s1 【元素】 //1
获取长度 scard s1
删除元素 srem s1 【elements】
随机产生元素 srandmember s1 【产生个数】
随机弹出一个元素 spop s1 【数字】
移动集合a到b smove s1 s2 【元素】
集合运算
s1 【1 2 3 4 5 6】 s2 【4 5 6 7 8 9】
交集 sinter s1 s2 //4 5 6
并集 sunion s1 s2 //123456
差集 sdiff s1 s2 //以一为主 为123 以二为主 为 789
zset
添加元素:zadd z1 【score】【value】【score】【value】
查看数量:zcard z1
返回指定score范围的数据数量:zcount z1 【min】【max】
返回指定范围数据 zrange z1 【起始】【结尾】
返回指定范围反转的数据:zrevrange z1 【起始】【结尾】
返回指定score范围的数据:zrangebyscore z1 【min】【max】加 ( 表示不包括
返回元素分数:zscore z1 【元素】
删除元素:zrem z1 【元素】
增加元素分数:zincrby 【增数】 【元素】
返回元素的排名位置:zrank z1 【元素】
返回反转的排名位置:zrevrank z1 【元素】
bitmap:签到
给指定key的offset赋值0|1:setbit 【element1】 【位置】【 状态01】
HyperLogLog:UA统计
添加元素:PFADD key element [element …]
返回储存在给定键的 HyperLogLog 的近似基数:PFCOUNT key
合并多个HyperLogLog于一个HyperLogLog:
PFMERGE destkey sourcekey [sourcekey …]
GEO
黑马点评文件
链接https://pan.baidu.com/s/1189u6u4icQYHg_9_7ovWmA?pwd=eh11
提取码:eh11
4.Jedis
使用
1.引入依赖
<dependencies>
<!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>4.2.3</version>
</dependency>
</dependencies>
2.引入日志
<!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-log4j12 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.21</version>
<scope>test</scope>
</dependency>
3.引入junit5
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.9.1</version>
<scope>test</scope>
</dependency>
4.建立连接,执行方法,关闭连接
// 创建Jedis对象传入参数ip地址,redis端口号
Jedis jedis = new Jedis("8.130.103.195", 6379);
// 配置密码
jedis.auth("aaaaaa");
jedis.set("k1","v1");
// 关闭Redis
jedis.close();
连接池
public class JedisConnectionFactory{
private static JedisPool jedisPool;
static{
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
jedisPoolConfig.setMaxTotal(8);
jedisPoolConfig.setMaxIdle(8);
jedisPoolConfig.setMinIdle(2);
new jedisPool = JedisPool(jedisPoolConfig,
"8.130.103.195",6379,1000,"417403");
}
public static Jedis getJedis(){
return jedosPool.getResource();
}
}
5.SpringDataRedis
1.使用:
引入依赖
<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:
redis:
host: 8.130.114.137
port: 6379
password: aaaaaa
lettuce:
pool:
max-active: 8
max-idle: 8
min-idle: 0
自动注入
@Autowared
private RedisTemplate redisTemplate;
6.SpringRedisTemplate
1.使用
引入依赖
<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>
配置application.peoperties
spring.redis.host=8.130.114.137
spring.redis.database=1
spring.redis.port= 6379
spring.redis.password=417403
spring.redis.lettuce.pool.max-active= 10
spring.redis.lettuce.pool.max-idle=10
spring.redis.lettuce.pool.max-wait=1
spring.redis.lettuce.pool.time-between-eviction-runs=0
spring.redis.lettuce.shutdown-timeout=5000
spring.jackson.default-property-inclusion=non_null
注入SpringRedisTemplate
2.区别
StringRedisTemplate与RedisTemplate
两者的关系是StringRedisTemplate继承RedisTemplate。
两者的数据是不共通的;也就是说StringRedisTemplate只能管理StringRedisTemplate里面的数据,RedisTemplate只能管理RedisTemplate中的数据。
SDR默认采用的序列化策略有两种,一种是String的序列化策略,一种是JDK的序列化策略。
StringRedisTemplate默认采用的是String的序列化策略,保存的key和value都是采用此策略序列化保存的。
RedisTemplate默认采用的是JDK的序列化策略,保存的key和value都是采用此策略序列化保存的。
7.Redis缓存策略
0.最佳方案
1.更新策略
低一致性业务,建议配置最大内存占用和淘汰策略。
高一致性业务,可以结合超时剔除和主动更新。
算法剔除:不用管理,当内存不足时,redis淘汰机制会淘汰部分数据。
超时剔除:给缓存设置过期时间,过期后自动删除。
主动更新:需要管理,根据业务逻辑进行更新
2.主动更新策略
1.Cache Aside Pattern:
由缓存的调用者,在更新数据库时同时更新。
2.Read/wirte Through Pattern:
将缓存和数据库整合为一个业务,由服务器来维护,无需关心一致性。
3.Write Behind Caching Pattern:
将写入业务保留在缓存中,再由其他线程异步将数据持久化到数据库中。
3.Cache Aside Pattern
删除缓存操作
更新缓存:每次更新数据库后更新缓存,没有读业务,更新浪费
删除缓存:当数据库更新时删除缓存,需要使用再次从数据库读取**
保证缓存与数据库业务操作一致性
放在同一个事物中
CacheAsidePattern
先更新数据库,再删除缓存
4.缓存穿透
什么是缓存穿透:
每次从缓存中都查不到数据,而需要查询数据库,同时数据库中也没有查到该数据,也没法放入缓存。也就是说,每次这个用户请求过来的时候,都要查询一次数据库。给数据库造成压力
解决方案:
使用缓存空对象:
优点:实现方便,维护方便
缺点:可能造成额外内存浪费,造成短期不一致
布隆过滤:
优点:内存占用少,没有多余key
缺点:实现复杂,底层依靠哈希,最后转换为二进制存在误判。
5.缓存雪崩
什么是缓存雪崩:
缓存雪崩是指当大量的缓存key同时过期时或者Redsi宕机时,大量的请求到数据库,给数据库造成压力。
如何解决:
给缓存添加随机的TTL过期时间,集群部署,给业务降级,添加多级缓存。
6.缓存击穿
缓存击是什么
指当热点数据过期时,大量的请求到服务器,给服务器造成压力。
如何解决
添加互斥锁
设置逻辑过期
8.优惠券秒杀
1.订单的id生成
private static final long BEGIN_TIMESTAMP = 1640995200L;
private static final int COUNT_BITS = 32;
public RedisIdGenerator(StringRedisTemplate stringRedisTemplate) {
this.stringRedisTemplate = stringRedisTemplate;
}
private final StringRedisTemplate stringRedisTemplate;
/**
*
* @param redisCachePrefix 业务前缀,区分业务
* @return
*/
public Long idGenerate(String redisCachePrefix){
// 生成时间戳
LocalDateTime now = LocalDateTime.now();
long nowSecond = now.toEpochSecond(ZoneOffset.UTC);
long beginTimestamp = BEGIN_TIMESTAMP;
long timestamp = nowSecond - BEGIN_TIMESTAMP;
String data = now.format(DateTimeFormatter.ofPattern("yyyy:mm:dd"));
// 生成序列号 redis自增id
Long count = stringRedisTemplate.opsForValue().increment("icr:"+redisCachePrefix+":"+data);
// 拼接
return timestamp << COUNT_BITS | count;
}
2.秒杀下单
防止超卖,使用乐观锁,悲观锁。
乐观锁:给stock字段作为乐观锁版本,每次只要在操作时,再次判断当前的值与刚才读取的值是否相同
防止黄牛:
使用悲观锁,将每个用户的id上锁,再去查询库中相同对象,相同订单是否存在
Long userId = UserHolder.getUser().getId();
// 如果字符常量池中已经包含一个等于此String对象的字符串,则返回常量池中字符串的引用
// 对于任意两个字符串 s 和 t,当且仅当 s.equals(t) 为 true 时,s.intern() == t.intern() 才为 true
synchronized (userId.toString().intern()) {
IVoucherOrderService proxy = (IVoucherOrderService) AopContext.currentProxy();
return proxy.createVoucherOrder(voucherId);
}
Integer count = query().eq("user_id", userId).eq("voucher_id", voucherId).count();
if (count > 0){
return Result.fail("用户已经购买过一次了!");
}
9.分布式锁
1.分布式锁解决集群下锁的问题
使用redis的setnx,对用户或者线程进行控制
/**
* 获取锁
* @param timeOutExpire
* @return
*/
@Override
public boolean tryLock(Long timeOutExpire) {
Boolean aBoolean = stringRedisTemplate
.opsForValue()
.setIfAbsent(LOCK_PREFIX+name, String.valueOf(Thread.currentThread().getId()), timeOutExpire, TimeUnit.MINUTES);
return Boolean.TRUE.equals(aBoolean);
}
2.解决分布式误删问题
在释放锁的时候,添加一层判断,当前线程对象和锁对象是否为同一个
3.解决redis分布式事务原子性
使用lua脚本,通过redis调用lua脚本,使释放锁的判断和删除操作都一次执行,保证原子性。
4.可重入锁
同一个线程的锁可以多次复用,Redis实现可重入锁,是通过使用hset结构,key为锁名称,value为当前线程标识,和锁的个数
转行程序员
10.单机Redis问题
10.Redis持久化
1.RDB
1.什么是RDB
RDB(Redis DataBase),是redis默认的存储方式,RDB方式是通过快照( snapshotting )完成的。它保存的是某一时刻的数据并不关注过程。RDB保存redis某一时刻的数据的快照
2.触发方式
Redis服务器主动关闭时,当操作符合快照规则时,执行主从复制时。
3.配置
4.bgsave执行流程
5.优缺点
优点:
1.RDB是基于二进制存储的,占用空间小,便于传给slaver
2.主进程fork一个紫禁城,可以最大化Redis性能
3.使用RDB恢复文件较快
缺点:
1.不保证数据完整性,最后一次快照后面的数据会丢失
2.父进程在fork子进程时是阻塞的,如果主进程较大,可能会阻塞。
6.禁用RDB
#在Redis.cof中添加
save ""
2.AOF
1.什么是AOF
Redis默认情况下是不开启的。开启AOF持久化后Redis 将所有对数据库进行过写入的命令(及其参数)(RESP)记录到 AOF 文件, 以此达到记录数据库状态的目的,这样当Redis重启后只要按顺序回放这些命令就会恢复到原始状态了。AOF会记录过程,RDB只管结果
2.配置conf
3.AOF执行流程
4.AOF重写
当AOF体积超过配置中的阈值时,会自动在后台子进程对AOF文件重写。
也可以使用bgrewriteaof命令
运行时打开aof
redis-cli> CONFIG SET appendonly yes
5.AOF优缺点
优点
- 充分保证数据的持久化,正确的配置一般最多丢失1秒的数据
- aof 文件内容是以Redis协议格式保存, 易读
缺点
- aof 文件通常大于 rdb 文件
- 速度会慢于rdb, 具体得看具体fsyn策略
- 重新启动redis时会极低的概率会导致无法将数据集恢复成保存时的原样(概率极低, 但确实出现过)
3.RDB和AOF区别
Redis
1.安装
1.官网下载redis-7.0.10.tar.gz
2.传到Linux的/usr/local目录中
cd /usr/local
#解压
tar -zxvf redis-7.0.10.tar.gz
#redis-7.0.10
cd redis-7.0.10
#使用make命令
make&&make install
#进入文件
cd redis.conf
#修改配置
daemonize yes
protected-mode no
#bind 127.0.0.1
自己添加requirepass *****
#完成后重启
#启动redis
redis-server redis.conf
#启动redis客户端
redis-cli -a ***** -p 6379
#查看
ps -ef |grep redis
#关闭
redis-cli -a 111111 shutdown
2.key的基本命令
查看所有key: keys *
查看key类型:type key
查看是否存在:exists key | exists key1,key2,key3
切换Redis库:select (index)
设定过期时间:expire key seconds
查看剩余过期:ttl key (永久返回-1,已经过期返回-2)
查看数据库的数量:dbsize
清空当前数据库:flushdb
清空所有数据库:fludb
删除key:del key
3.基本数据类型
string
获取长度:strlen k1
追加字符:append k1 [value]
添加单个:set k1 [value]
获取单个:get k1
秒过期时间:set k1 [value] ex [过期时间]
毫秒过期时间:set k1 v1 px [过期时间]
秒单位时间戳:set k1 v1 exat 【过了当前时间戳就为过期】
毫秒单位时间戳:set k1 v1 pxat【时间戳】
不存在时添加:set k1 v1 nx
存在的时候:set k1 v1 xx
保存过期时间:set k1 v1 keepttl
先get后set:getset k1 【value】
不存在时添加:setnx k1 v1
批处理
添加多个:mset ka va kb vb
获取多个:mget ka kb
不存在时添加多个:msetnx ka va kc vc 必须全部成功
获取指定位置:getrange k1 【sta】【 end 】
设置指定位置:setrange k1 【index】【 value】
加减
对数字类型
自增:incr k1
自减:decr k1
增量20:incrby k1 【增量】
减量20:decrby k1 【减量】
list
左边加入:lpush list1 【elements.......】
右边加入:rpush list1 【elements.......】
左边遍历:lrange list1 【sta】【end】
左边出栈:lpop list1 【出栈个数】
右边出栈:rpop list1 【出栈个数】
索引取值:lindex list1 【index】
获取长度:llen list1
删除指定个数指定元素:lrem list1 【个数】【元素】
截取指定长度赋值给list:ltrim list1 【sta】【end】
原子操作尾出头插:rpoplpush list1 list2
指定下标左边加入:lset list1 【index】【element】
左边指定元素前后加入:linsert list1 【after/before】【element】【java】
hash
添加对象:hset 【user:001】 name zhangsan age lisi
获取对象:hget 【user:001】 name
获取全部参数:hgetall 【user:001】
获取多个:hmget 【user:001】 name age
属性值增加:hincrby 【user:001】 age 【增量】
属性值增加: hincrbyfloat 【user:001】 age 【增量】
添加对象属性如果不存在:hsetnx user:001 name zhangsan
删除对象元素:hdel user:001 name
set
添加 sadd s1 【elements......】 //只有abcd四个元素
获取 smembers s1
判断是否存在 sismember s1 【元素】 //1
获取长度 scard s1
删除元素 srem s1 【elements】
随机产生元素 srandmember s1 【产生个数】
随机弹出一个元素 spop s1 【数字】
移动集合a到b smove s1 s2 【元素】
集合运算
s1 【1 2 3 4 5 6】 s2 【4 5 6 7 8 9】
交集 sinter s1 s2 //4 5 6
并集 sunion s1 s2 //123456
差集 sdiff s1 s2 //以一为主 为123 以二为主 为 789
zset
添加元素:zadd z1 【score】【value】【score】【value】
查看数量:zcard z1
返回指定score范围的数据数量:zcount z1 【min】【max】
返回指定范围数据 zrange z1 【起始】【结尾】
返回指定范围反转的数据:zrevrange z1 【起始】【结尾】
返回指定score范围的数据:zrangebyscore z1 【min】【max】加 ( 表示不包括
返回元素分数:zscore z1 【元素】
删除元素:zrem z1 【元素】
增加元素分数:zincrby 【增数】 【元素】
返回元素的排名位置:zrank z1 【元素】
返回反转的排名位置:zrevrank z1 【元素】
bitmap:签到
给指定key的offset赋值0|1:setbit 【element1】 【位置】【 状态01】
HyperLogLog:UA统计
添加元素:PFADD key element [element …]
返回储存在给定键的 HyperLogLog 的近似基数:PFCOUNT key
合并多个HyperLogLog于一个HyperLogLog:
PFMERGE destkey sourcekey [sourcekey …]
GEO
黑马点评文件
链接https://pan.baidu.com/s/1189u6u4icQYHg_9_7ovWmA?pwd=eh11
提取码:eh11
4.Jedis
使用
1.引入依赖
<dependencies>
<!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>4.2.3</version>
</dependency>
</dependencies>
2.引入日志
<!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-log4j12 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.21</version>
<scope>test</scope>
</dependency>
3.引入junit5
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.9.1</version>
<scope>test</scope>
</dependency>
4.建立连接,执行方法,关闭连接
// 创建Jedis对象传入参数ip地址,redis端口号
Jedis jedis = new Jedis("8.130.103.195", 6379);
// 配置密码
jedis.auth("aaaaaa");
jedis.set("k1","v1");
// 关闭Redis
jedis.close();
连接池
public class JedisConnectionFactory{
private static JedisPool jedisPool;
static{
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
jedisPoolConfig.setMaxTotal(8);
jedisPoolConfig.setMaxIdle(8);
jedisPoolConfig.setMinIdle(2);
new jedisPool = JedisPool(jedisPoolConfig,
"8.130.103.195",6379,1000,"417403");
}
public static Jedis getJedis(){
return jedosPool.getResource();
}
}
5.SpringDataRedis
1.使用:
引入依赖
<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:
redis:
host: 8.130.114.137
port: 6379
password: aaaaaa
lettuce:
pool:
max-active: 8
max-idle: 8
min-idle: 0
自动注入
@Autowared
private RedisTemplate redisTemplate;
6.SpringRedisTemplate
1.使用
引入依赖
<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>
配置application.peoperties
spring.redis.host=8.130.114.137
spring.redis.database=1
spring.redis.port= 6379
spring.redis.password=417403
spring.redis.lettuce.pool.max-active= 10
spring.redis.lettuce.pool.max-idle=10
spring.redis.lettuce.pool.max-wait=1
spring.redis.lettuce.pool.time-between-eviction-runs=0
spring.redis.lettuce.shutdown-timeout=5000
spring.jackson.default-property-inclusion=non_null
注入SpringRedisTemplate
2.区别
StringRedisTemplate与RedisTemplate
两者的关系是StringRedisTemplate继承RedisTemplate。
两者的数据是不共通的;也就是说StringRedisTemplate只能管理StringRedisTemplate里面的数据,RedisTemplate只能管理RedisTemplate中的数据。
SDR默认采用的序列化策略有两种,一种是String的序列化策略,一种是JDK的序列化策略。
StringRedisTemplate默认采用的是String的序列化策略,保存的key和value都是采用此策略序列化保存的。
RedisTemplate默认采用的是JDK的序列化策略,保存的key和value都是采用此策略序列化保存的。
7.Redis缓存策略
0.最佳方案
1.更新策略
低一致性业务,建议配置最大内存占用和淘汰策略。
高一致性业务,可以结合超时剔除和主动更新。
算法剔除:不用管理,当内存不足时,redis淘汰机制会淘汰部分数据。
超时剔除:给缓存设置过期时间,过期后自动删除。
主动更新:需要管理,根据业务逻辑进行更新
2.主动更新策略
1.Cache Aside Pattern:
由缓存的调用者,在更新数据库时同时更新。
2.Read/wirte Through Pattern:
将缓存和数据库整合为一个业务,由服务器来维护,无需关心一致性。
3.Write Behind Caching Pattern:
将写入业务保留在缓存中,再由其他线程异步将数据持久化到数据库中。
3.Cache Aside Pattern
删除缓存操作
更新缓存:每次更新数据库后更新缓存,没有读业务,更新浪费
删除缓存:当数据库更新时删除缓存,需要使用再次从数据库读取**
保证缓存与数据库业务操作一致性
放在同一个事物中
CacheAsidePattern
先更新数据库,再删除缓存
4.缓存穿透
什么是缓存穿透:
每次从缓存中都查不到数据,而需要查询数据库,同时数据库中也没有查到该数据,也没法放入缓存。也就是说,每次这个用户请求过来的时候,都要查询一次数据库。给数据库造成压力
解决方案:
使用缓存空对象:
优点:实现方便,维护方便
缺点:可能造成额外内存浪费,造成短期不一致
布隆过滤:
优点:内存占用少,没有多余key
缺点:实现复杂,底层依靠哈希,最后转换为二进制存在误判。
5.缓存雪崩
什么是缓存雪崩:
缓存雪崩是指当大量的缓存key同时过期时或者Redsi宕机时,大量的请求到数据库,给数据库造成压力。
如何解决:
给缓存添加随机的TTL过期时间,集群部署,给业务降级,添加多级缓存。
6.缓存击穿
缓存击是什么
指当热点数据过期时,大量的请求到服务器,给服务器造成压力。
如何解决
添加互斥锁
设置逻辑过期
8.优惠券秒杀
1.订单的id生成
private static final long BEGIN_TIMESTAMP = 1640995200L;
private static final int COUNT_BITS = 32;
public RedisIdGenerator(StringRedisTemplate stringRedisTemplate) {
this.stringRedisTemplate = stringRedisTemplate;
}
private final StringRedisTemplate stringRedisTemplate;
/**
*
* @param redisCachePrefix 业务前缀,区分业务
* @return
*/
public Long idGenerate(String redisCachePrefix){
// 生成时间戳
LocalDateTime now = LocalDateTime.now();
long nowSecond = now.toEpochSecond(ZoneOffset.UTC);
long beginTimestamp = BEGIN_TIMESTAMP;
long timestamp = nowSecond - BEGIN_TIMESTAMP;
String data = now.format(DateTimeFormatter.ofPattern("yyyy:mm:dd"));
// 生成序列号 redis自增id
Long count = stringRedisTemplate.opsForValue().increment("icr:"+redisCachePrefix+":"+data);
// 拼接
return timestamp << COUNT_BITS | count;
}
2.秒杀下单
防止超卖,使用乐观锁,悲观锁。
乐观锁:给stock字段作为乐观锁版本,每次只要在操作时,再次判断当前的值与刚才读取的值是否相同
防止黄牛:
使用悲观锁,将每个用户的id上锁,再去查询库中相同对象,相同订单是否存在
Long userId = UserHolder.getUser().getId();
// 如果字符常量池中已经包含一个等于此String对象的字符串,则返回常量池中字符串的引用
// 对于任意两个字符串 s 和 t,当且仅当 s.equals(t) 为 true 时,s.intern() == t.intern() 才为 true
synchronized (userId.toString().intern()) {
IVoucherOrderService proxy = (IVoucherOrderService) AopContext.currentProxy();
return proxy.createVoucherOrder(voucherId);
}
Integer count = query().eq("user_id", userId).eq("voucher_id", voucherId).count();
if (count > 0){
return Result.fail("用户已经购买过一次了!");
}
9.分布式锁
1.分布式锁解决集群下锁的问题
使用redis的setnx,对用户或者线程进行控制
/**
* 获取锁
* @param timeOutExpire
* @return
*/
@Override
public boolean tryLock(Long timeOutExpire) {
Boolean aBoolean = stringRedisTemplate
.opsForValue()
.setIfAbsent(LOCK_PREFIX+name, String.valueOf(Thread.currentThread().getId()), timeOutExpire, TimeUnit.MINUTES);
return Boolean.TRUE.equals(aBoolean);
}
2.解决分布式误删问题
在释放锁的时候,添加一层判断,当前线程对象和锁对象是否为同一个
3.解决redis分布式事务原子性
使用lua脚本,通过redis调用lua脚本,使释放锁的判断和删除操作都一次执行,保证原子性。
4.可重入锁
同一个线程的锁可以多次复用,Redis实现可重入锁,是通过使用hset结构,key为锁名称,value为当前线程标识,和锁的个数
转行程序员
10.单机Redis问题
10.Redis持久化
1.RDB
1.什么是RDB
RDB(Redis DataBase),是redis默认的存储方式,RDB方式是通过快照( snapshotting )完成的。它保存的是某一时刻的数据并不关注过程。RDB保存redis某一时刻的数据的快照
2.触发方式
Redis服务器主动关闭时,当操作符合快照规则时,执行主从复制时。
3.配置
4.bgsave执行流程
5.优缺点
优点:
1.RDB是基于二进制存储的,占用空间小,便于传给slaver
2.主进程fork一个紫禁城,可以最大化Redis性能
3.使用RDB恢复文件较快
缺点:
1.不保证数据完整性,最后一次快照后面的数据会丢失
2.父进程在fork子进程时是阻塞的,如果主进程较大,可能会阻塞。
6.禁用RDB
#在Redis.cof中添加
save ""
2.AOF
1.什么是AOF
Redis默认情况下是不开启的。开启AOF持久化后Redis 将所有对数据库进行过写入的命令(及其参数)(RESP)记录到 AOF 文件, 以此达到记录数据库状态的目的,这样当Redis重启后只要按顺序回放这些命令就会恢复到原始状态了。AOF会记录过程,RDB只管结果
2.配置conf
3.AOF执行流程
4.AOF重写
当AOF体积超过配置中的阈值时,会自动在后台子进程对AOF文件重写。
也可以使用bgrewriteaof命令
运行时打开aof
redis-cli> CONFIG SET appendonly yes
5.AOF优缺点
优点
- 充分保证数据的持久化,正确的配置一般最多丢失1秒的数据
- aof 文件内容是以Redis协议格式保存, 易读
缺点
- aof 文件通常大于 rdb 文件
- 速度会慢于rdb, 具体得看具体fsyn策略
- 重新启动redis时会极低的概率会导致无法将数据集恢复成保存时的原样(概率极低, 但确实出现过)
3.RDB和AOF区别
1.缓存更新策略
1.算法剔除:不用管理,当redis内存不足时,会根据redis淘汰机制回收部分内存
2.超时剔除:设置超时时间,过期自动清除
3.主动更新:需要管理,数据库更新时,需要对缓存进行操作。
1.缓存与数据库双写一致
实现方式:需要对数据进行操作时,先修改数据库,再删除缓存。
注意:要保持事务原子性@Transactional
@Transactional
public Result update(Shop shop) {
Long shopId = shop.getId();
// 对店铺进行判断,不能为空
if (shopId == null) {
return Result.fail("店铺id不能为空");
}
// 对店铺进行更新
updateById(shop);
// 删除缓存
stringRedisTemplate.delete(CACHE_SHOP_KEY+shopId);
return Result.ok();
}
2.缓存穿透
什么是缓存穿透:
当缓存和数据库中都没数据信息时,请求就会直接对数据库进行访问,对数据库造成压力
如何解决:
缓存空对象,布隆过滤,增强id复杂度,避免被猜测,数据格式校验,作权限限制。
缓存空对象:
@Override
public Result getShopInfo(Long id) {
//在redis中查询(判空)
String jsonShop = stringRedisTemplate.opsForValue().get(CACHE_SHOP_KEY + id);
//存在,返回
if (StrUtil.isNotBlank(jsonShop)) {
//***如果jsonShop不为 空 或者不为 ""
Shop shop = JSONUtil.toBean(jsonShop, Shop.class);
return Result.ok(shop);
}
//***说明为 空 或者为 ""
if (jsonShop != null){//如果为 ""
return Result.fail("商铺信息不存在!!");
}
//不存在,数据库中查询
Shop shop = getById(id);
//不存在,file("商铺不存在!!")
if (shop == null) {
stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY + id,"",CACHE_NULL_TTL, TimeUnit.MINUTES);
return Result.fail("暂无此商铺信息!!");
}
//存在,写入redis
stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY + id,JSONUtil.toJsonStr(shop),CACHE_SHOP_TTL, TimeUnit.MINUTES);
//返回数据
return Result.ok(shop);
}
3.缓存雪崩
什么是缓存雪崩
是指再某一时刻大量的缓存key同时失效或者Redsi服务宕机,导致大量请求到数据库,造成巨大压力
解决方案
给不同的key的过期时间设置TTL随机值
利用Redis集群提高服务可用性
给缓存业务添加降级限流策略
给业务添加多级缓存(nginx->jvm->redis)
4.缓存击穿
缓存击是什么
指当热点数据过期时,大量的请求到服务器,给服务器造成压力。
如何解决
添加互斥锁,设置逻辑过期
5.分布式锁
6.优化
1.异步秒杀
项目使用的是所有的读写都由数据库完成,给数据库带来巨大压力,性能降低。为了解决这种问题,我将购买判断业务放在了redis缓存中,当业务请求发起的时候,会先通过哟lua脚本,在redis中判断库存是否充足,用户是否已经购买过。一系列完成之后,再创建订单,将订单保存再阻塞队列中,异步开启一个新线程,线程内部不断从阻塞队列获取订单任务,并调用函数执行,再去请求数据库,扣减库存,最后将订单保存在数据库。
创建秒杀券并同步到Redis
@Transactional
public void addSeckillVoucher(Voucher voucher) {
// 保存优惠券
save(voucher);
// 保存秒杀信息
SeckillVoucher seckillVoucher = new SeckillVoucher();
seckillVoucher.setVoucherId(voucher.getId());
seckillVoucher.setStock(voucher.getStock());
seckillVoucher.setBeginTime(voucher.getBeginTime());
seckillVoucher.setEndTime(voucher.getEndTime());
seckillVoucherService.save(seckillVoucher);
//将秒杀券添加到redis缓存
stringRedisTemplate.opsForValue().set(SECKILL_STOCK_KEY+voucher.getId(),String.valueOf(voucher.getStock()));
}
Redis中lua脚本
体检初始化lua脚本到内存
private static final DefaultRedisScript SECKILL_SCRIPT;
static {
SECKILL_SCRIPT = new DefaultRedisScript();
SECKILL_SCRIPT.setLocation(new ClassPathResource("seckill.lua"));
SECKILL_SCRIPT.setResultType(Long.class);
}
lua脚本
--参数列表
--1.1优惠券id:查询库存是否充足
local voucherId = ARGV[1]
--1.2用户id:用户是否已经购买过
local userId = ARGV[2]
--key列表
--优惠券key
local stockKey = 'seckill:stock:' .. voucherId
--订单key
local orderKey = 'seckill:order:' .. voucherId .. ':' .. userId
--lua脚本命令
--判断库存是否充足
if(tonumber(redis.call('get',stockKey)) <= 0) then
-- 不足返回1
return 1
end
--充足则判断用户是否下单
if(redis.call('sismember',orderKey,userId) == 1) then
--存在则说明已经下单,返回2
return 2
end
--扣库存
redis.call('incrby',stockKey,-1)
--下单
redis.call('sadd',orderKey,userId)
return 0
判断脚本执行结果的返回值
将订单添加到阻塞队列中
// 为零将订单存入阻塞队列
// 6. 创建订单
VoucherOrder voucherOrder = new VoucherOrder();
// 6.1 订单id
long orderId = redisIdGenerator.idGenerate("order");
voucherOrder.setId(orderId);
// 6.2 用户id
voucherOrder.setUserId(userId);
// 6.3代金券id
voucherOrder.setVoucherId(voucherId);
orderTasks.add(voucherOrder);
proxy = (IVoucherOrderService) AopContext.currentProxy();
return Result.ok(orderId);
7.Redis消息队列
1.基于List数据类型的消息队列
1.基于PubSub数据类型的消息队列
1.基于Stream数据类型的消息队列
8.最佳实践
1.key的设计
1.遵循:业务名:数据名:id,可读性强
2.长度不超过44,当key为纯数字时,会使用int存储,当长度大于44字节,会从embstr变为raw,raw是指,不是一段完整的内存空间,以指针的形式指向另一个空间,会造成内存碎片。
浙公网安备 33010602011771号