Redis分布式缓存、秒杀

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

目录

一、单点Redis的问题

1、数据丢失问题

Redis数据持久化。

2、并发能力问题

大家主从集群实现读写分离。

3、故障恢复问题

利用Redis哨兵实现健康检测和自动恢复。

4、存储能力问题

搭建分片集群利用插槽机制实现动态扩容。

二、RDB

RDB全称Redis Database Backup fileRedis数据备份文件也被叫做Redis数据快照。简单来说就是把内存中的所有数据都记录到磁盘中。当Redis实例故障重启后从磁盘读取快照文件恢复数据。
快照文件称为RDB文件默认是保存在当前运行目录。

Redis内部有触发RDB的机制可以在redis.conf文件中找到格式如下

900秒内如果至少有1个key被修改则执行bgsave  如果是save "" 则表示禁用RDB
save 900 1  
save 300 10  
save 60 10000 

bgsave开始时会fork主进程得到子进程子进程共享主进程的内存数据。完成fork后读取内存数据并写入 RDB 文件。

fork采用的是copy-on-write技术

  • 当主进程执行读操作时访问共享内存
  • 当主进程执行写操作时则会拷贝一份数据执行写操作

RDB方式bgsave的基本流程

  1. fork主进程得到一个子进程共享内存空间
  2. 子进程读取内存数据并写入新的RDB文件
  3. 用新RDB文件替换旧的RDB文件
    在这里插入图片描述

RDB会在什么时候执行save 60 1000代表什么含义

  • 默认是服务停止时
  • 代表60秒内至少执行1000次修改则触发RDB

RDB的缺点

  • RDB执行间隔时间长两次RDB之间写入数据有丢失的风险
  • fork子进程、压缩、写出RDB文件都比较耗时

三、AOF

AOF全称为Append Only File追加文件。Redis处理的每一个写命令都会记录在AOF文件可以看做是命令日志文件。

AOF默认是关闭的需要修改redis.conf配置文件来开启AOF

# 是否开启AOF功能默认是不开启no
appendonly yes
# AOF文件的名称
appendfilename "appendonly.aof"

AOF的命令记录的频率也可以通过redis.conf文件来配

# 表示每执行一次写命令立即记录到AOF文件
appendfsync always 
# 写命令执行完先放入AOF缓冲区然后表示每隔1秒将缓冲区数据写到AOF文件是默认方案
appendfsync everysec 
# 写命令执行完先放入AOF缓冲区由操作系统决定何时将缓冲区内容写回磁盘
appendfsync no
配置项刷盘时机优点缺点
Always同步刷盘可靠性高几乎不丢数据性能影响大
everysec每秒刷盘性能适中最多丢失一分钟的数据
no操作系统控制性能最好可靠性较差可能丢失大量数据

因为是记录命令AOF文件会比RDB文件大的多。而且AOF会记录对同一个key的多次写操作但只有最后一次写操作才有意义。通过执行bgrewriteaof命令可以让AOF文件执行重写功能用最少的命令达到相同效果。

set id 1
set name nezha
set id 2

bgrewriteaof

mset name nezha id 2

Redis也会在触发阈值时自动去重写AOF文件。阈值也可以在redis.conf中配置

# AOF文件比上次文件 增长超过多少百分比则触发重写auto-aof-rewrite-percentage 100# AOF文件体积最小多大以上才触发重写 auto-aof-rewrite-min-size 64mb 

RDB和AOF各有自己的优缺点如果对数据安全性要求较高在实际开发中往往会结合两者来使用。

RDBAOF
持久化方式定时对整个内存做快照记录每一次执行的命令
数据完整性不完整两次备份之间会丢失相对完整取决于刷盘策略
文件大小会有压缩文件体积小记录命令文件体积很大
宕机恢复速度很快
数据恢复优先级低因为数据完整性不低高因为数据完整性更高
系统资源占用高大量CPU和内存消耗低主要是磁盘IO资源但AOF重写时会占用大量CPU和内存资源
使用场景可以容忍数分钟的数据丢失追求更快的启动速度对数据安全性要求较高常见

四、Redis优化秒杀流程

1、秒杀步骤

  1. 查询优惠券
  2. 判断秒杀商品库存
  3. 查询订单
  4. 校验一人一单
  5. 减库存
  6. 创建订单

在这里插入图片描述

2、Redis优化秒杀步骤

  1. 新增秒杀的优惠券将优惠券信息保存到Redis中
  2. 基于Lua脚本判断秒杀商品库存一人一单决定用户是否秒杀成功
  3. 如果秒杀成功将优惠券id、用户id、商品id封装到阻塞队列中
  4. 开启异步任务不断从阻塞队列中读取信息实现异步下单功能
    在这里插入图片描述

3、秒杀的lua脚本

-- 1.参数列表
-- 1.1.优惠券id
local voucherId = ARGV[1]
-- 1.2.用户id
local userId = ARGV[2]
-- 1.3.订单id
local orderId = ARGV[3]

-- 2.数据key
-- 2.1.库存key
local stockKey = 'seckill:stock:' .. voucherId
-- 2.2.订单key
local orderKey = 'seckill:order:' .. voucherId

-- 3.脚本业务
-- 3.1.判断库存是否充足 get stockKey
if(tonumber(redis.call('get', stockKey)) <= 0) then
    -- 3.2.库存不足返回1
    return 1
end
-- 3.2.判断用户是否下单 SISMEMBER orderKey userId
if(redis.call('sismember', orderKey, userId) == 1) then
    -- 3.3.存在说明是重复下单返回2
    return 2
end
-- 3.4.扣库存 incrby stockKey -1
redis.call('incrby', stockKey, -1)
-- 3.5.下单保存用户sadd orderKey userId
redis.call('sadd', orderKey, userId)
-- 3.6.发送消息到队列中 XADD stream.orders * k1 v1 k2 v2 ...
redis.call('xadd', 'stream.orders', '*', 'userId', userId, 'voucherId', voucherId, 'id', orderId)
return 0

4、调用秒杀的lua脚本

public Result seckillVoucher(Long voucherId) {
     Long userId = UserHolder.getUser().getId();
     long orderId = redisIdWorker.nextId("order");
     // 1.执行lua脚本
     Long result = stringRedisTemplate.execute(
             SECKILL_SCRIPT,
             Collections.emptyList(),
             voucherId.toString(), userId.toString(), String.valueOf(orderId)
     );
     int r = result.intValue();
     // 2.判断结果是否为0
     if (r != 0) {
         // 2.1.不为0 代表没有购买资格
         return Result.fail(r == 1 ? "库存不足" : "不能重复下单");
     }
     // 3.返回订单id
     return Result.ok(orderId);
 }

5、通过线程池操作阻塞队列

// 线程池
private static final ExecutorService SECKILL_ORDER_EXECUTOR = Executors.newSingleThreadExecutor();

/**
* 在类初始化完成后执行
*/
@PostConstruct
private void init() {
    SECKILL_ORDER_EXECUTOR.submit(new VoucherOrderHandler());
}

// 阻塞队列
private BlockingQueue<VoucherOrder> orderTasks = new ArrayBlockingQueue<>(1024 * 1024);
private class OrderHandler implements Runnable{

    @Override
    public void run() {
        while (true){
            try {
                doSomething();
            } catch (Exception e) {
                log.error("处理订单异常", e);
            }
        }
    }
}

五、基于Redis实现共享session登录

基于session实现登录
在这里插入图片描述
基于Redis实现共享session登录

public class RefreshTokenInterceptor implements HandlerInterceptor {

    private StringRedisTemplate stringRedisTemplate;

    public RefreshTokenInterceptor(StringRedisTemplate stringRedisTemplate) {
        this.stringRedisTemplate = stringRedisTemplate;
    }

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 1、获取请求头中的token
        String token = request.getHeader("authorization");
        if (StrUtil.isBlank(token)) {
            return true;
        }
        // 2、基于TOKEN获取redis中的用户
        String key  = LOGIN_USER_KEY + token;
        Map<Object, Object> userMap = stringRedisTemplate.opsForHash().entries(key);
        // 3、判断用户是否存在
        if (userMap.isEmpty()) {
            return true;
        }
        // 5、将查询到的hash数据转为UserDTO
        UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);
        // 6、存在保存用户信息到 ThreadLocal
        UserHolder.saveUser(userDTO);
        // 7、刷新token有效期
        stringRedisTemplate.expire(key, LOGIN_USER_TTL, TimeUnit.MINUTES);
        // 8、放行
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        // 移除用户
        UserHolder.removeUser();
    }
}

在这里插入图片描述



NoSQL数据库进阶实战

NoSQL数据库进阶实战1那些年学过的NoSQL基础

NoSQL数据库进阶实战2NoSQL数据存储模式

Redis缓存穿透、击穿、雪崩到底是个啥7张图告诉你

Redis分布式锁的实现方式

哪吒精品系列文章

Java学习路线总结搬砖工逆袭Java架构师

10万字208道Java经典面试题总结(附答案)

Java基础教程系列

Java高并发编程系列

数据库进阶实战系列
在这里插入图片描述

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
标签: redis

“Redis分布式缓存、秒杀” 的相关文章