Redis高级

一:Redis的持久化

(1)什么是Redis的持久化

        Redis持久化是指将Redis的数据保存到硬盘中,以便在服务器重启或崩溃后能够恢复数据。由于Redis默认将数据保存在内存中,如果不进行持久化操作,重启服务器将导致数据丢失。为了解决这个问题,Redis提供了两种持久化方式:RDB持久化和AOF持久化。

(2)两种持久化策略

        ①、AOF持久化

        AOF持久化是通过将Redis执行的每个写操作追加到一个AOF文件中来实现的。默认每秒对数据进行持久化。

         ②、RDB持久化 

        RDB持久化是通过将Redis的数据快照保存到一个RDB文件中来实现的。按条件触发持久化操作,满足任意一个条件。

        1) 900 1 900秒中修改1次

        2) 300 10 300秒中修改10次

        3) 60 10000 60秒中修改10000次

 (3)配置方法

可以在redis.conf中配置持久化,如:RDB

 启动AOF的配置

appendonly yes   开启AOF  
appendfsync everysec  每秒保存

(4)选择持久化策略 

我们如何选择RDB和AOF呢?视业务场景而定:

  • 允许少量数据丢失,性能要求高,选择RDB

  • 只允许很少数据丢失,选择AOF

  • 几乎不允许数据丢失,选择RDB + AOF

二:Redis淘汰策略

        Redis中的数据太多可能导致内存溢出,Redis会根据情况淘汰一些数据。 Redis的内存上限:64位系统,上限就是内存上限;32位系统,最大是4G

①、设置Redis最大内存

        在配置文件redis.conf 中,可以通过参数 maxmemory <bytes> 来设定最大内存,不设定该参数默认是无限制的,但是通常会设定其为物理内存的四分之三。

max-memory  配置0就是无上限(默认)

②、设置内存淘汰方式 

        当现有内存大于 maxmemory 时,便会触发redis主动淘汰内存方式,通过设置 maxmemory-policy ,有如下几种淘汰方式:

  • Noeviction(默认):当内存不足时,Redis不会删除任何键,而是直接返回错误信息。
  • Volatile-ttl:Redis会优先淘汰剩余过期时间较短的键,以释放内存空间。

  • alkeys-lru (推荐l):使用LRU算法淘汰比较少使用的键 LRU算法:Least Recently                   Used 最近最少使用算法,淘汰长期不用的缓存

               LFU算法:Least Frequently Used 频率最少使用算法,淘汰使用频率少的缓存

  • volatile-lru: 在过期的键中淘汰较少使用的

  • allkeys-random: 加入键的时候如果过限,在所有键中随机淘汰

  • volatile-random: 加入键的时候如果过限,在过期键中随机淘汰

  • allkeys-lfu:从所有键中逐渐使用频率最少的键

  • volatile-lfu:从所有配置了过期时间的键中驱逐使用频率最少的键

三:Redis并发问题

(1)Redis缓存作用

        Redis缓存在提高应用程序性能、减轻后端负载和网络压力、提供临时数据存储和实现数据共享等方面起着重要作用。它可以帮助提升应用程序的可伸缩性、性能和用户体验。作用有:

  • 提升系统的性能

           Redis基于内存,IO效率远高于MySQL数据库

  • 减少数据库的压力

           Redis处理很多请求,减少MySQL的请求量,避免MySQL压力过大导致宕机

  • 提供临时数据存储

           Redis缓存还可以用于临时数据的存储,如会话数据、访问令牌、临时计数器等。这些数据通常不需要永久保存,并且频繁读写访问。使用Redis缓存可以方便地对这些临时数据进行快速、高效的存取,而无需频繁访问持久化存储。

  • 缓解网络压力

          通过将数据存储在与应用程序相同的服务器中,Redis缓存可以减少对远程资源的访问,从而减轻网络传输压力。这对于分布式系统、微服务架构或跨地域应用程序尤为重要,因为网络延迟和带宽限制可能成为性能瓶颈。

 (2)Redis使用的流程

        

 (4)为什么要使用缓存

        把经常查询的数据,很少修改的数据存放到缓存中,减少访问数据库,降低数据库压力并且缓存一般都是内存,访问速度比较快。

(5)并发问题介绍

并发问题,大量并发量访问服务器,可能导致问题:

问题原因解决方案
雪崩

1. Redis热点数据同时过期,大量请求全部打到MySQL,MySQL宕机

2. 单个Redis服务出现问题或重启

1. 将热点数据过期时间设置为随机值,避免同时过期 2. 配置Redis集群,解决单点故障问题
击穿

大量并发请求访问Redis同一个数据,还没有向Redis保存,有大量线程同时访问,导致MySQL压力过大

通过上锁(双检锁)实现线程同步执行
穿透大量请求访问MySQL没有的数据,Redis缓存无法命中,导致数据库压力过大

1. 在Redis中保存空对象,给空对象设置过期时间

2. 使用布隆过滤器筛选掉不存在的数据

①击穿问题

        缓存击穿是指,对于一些设置了过期时间的key,如果这些可能会在某些时间点被超高并发地访问,是一种非常热点的数据。如果这个在大量请求同时进来前正好失效,那么所有对这个的数据查询都落到,我们称为缓存击穿。

解决方法:

  • 设置热点数据永远不过期。
  • 加分布式锁,让未获取到分布式锁的线程自旋操作,缓解数据库的压力。
 线程并发的案例
@Service
public class StudentServiceImpl extends ServiceImpl<StudentMapper, Student> implements StudentService {

    public static final String PREFIX = "Student-";

    @Autowired
    private StudentMapper studentMapper;

    @Autowired
    private RedisTemplate<String,Object> redisTemplate; 
   @Override
    public Student getStudentById(Long id) {
        //获得字符串操作对象
        ValueOperations<String, Object> ops = redisTemplate.opsForValue();
        //先查询Redis
        Student stu = (Student) ops.get(PREFIX + id);
        //如果Redis缓存存在数据,就直接返回
        if(stu != null){
            System.out.println("Redis查到,返回" + stu);
            return stu;
        }
        //如果Redis没有查到数据,就查询MySQL
        stu = studentMapper.selectById(id);
        //MySQL查到数据,就保存到Redis
        if(stu != null){
            System.out.println("MySQL查到,返回" + stu);
            ops.set(PREFIX + id,stu);
            return stu;
        }
        //MySQL没有数据,就返回null
        System.out.println("MySQL没有数据,就返回null");
        return null;
    }
}
@RestController
public class StudentController {

    @Autowired
    private StudentService studentService;

    @GetMapping("/student/{id}")
    public ResponseResult<Student> getStudentById(@PathVariable Long id){
        return ResponseResult.ok(studentService.getStudentById(id));
    }
}
 JMeter工具的使用

 1)添加线程组

         

2) 配置线程数量
 3) 添加http测试

         

 4) 配置http连接
 5) 添加结果视图

 击穿问题的解决方案

        使用了双检锁DCL机制优化方法

@Override
    public Student getStudentById(Long id) {
        //获得字符串操作对象
        ValueOperations<String, Object> ops = redisTemplate.opsForValue();
        //先查询Redis
        Student stu = (Student) ops.get(PREFIX + id);
        if(stu == null) {
            synchronized (this) {
                System.out.println("进入了同步锁");
                //先查询Redis
                stu = (Student) ops.get(PREFIX + id);
                //如果Redis缓存存在数据,就直接返回
                if (stu != null) {
                    System.out.println("Redis查到,返回" + stu);
                    return stu;
                }
                //如果Redis没有查到数据,就查询MySQL
                stu = studentMapper.selectById(id);
                //MySQL查到数据,就保存到Redis
                if (stu != null) {
                    System.out.println("MySQL查到,返回" + stu);
                    ops.set(PREFIX + id, stu);
                    return stu;
                }
                //MySQL没有数据,就返回null
                System.out.println("MySQL没有数据,就返回null");
            }
        }else {
            System.out.println("没有执行同步锁");
        }
        return stu;
    }

②穿透问题

        缓存穿透是指,指查询一个一定不存在的数据,由于缓存是不命中,将去查询数据库,但是数据库也无此记录,我们没有将这次查询的写入缓存,这将导致这个不存在的数据每次请求都要到存储层去查询,失去了缓存的意义。

解决方法:

  • 接口层增加校验,如用户鉴权校验,或者对做基础校验,的直接拦截;
  • 从缓存取不到的数据,在数据库中也没有取到,这时也可以将存为null,缓存有效时间可以设置短点,如30秒(设置太长会导致正常情况也没法使用),这样可以防止攻击用户反复用同一个id暴力攻击。
  • 使用布隆过滤器

例子: 

 解决方案1 : 保存空对象设置过期时间

@Override
    public Student getStudentById(Long id) {
        //获得字符串操作对象
        ValueOperations<String, Object> ops = redisTemplate.opsForValue();
        //先查询Redis,如果存在数据就不执行同步代码块,直接返回
        Student stu = (Student) ops.get(PREFIX + id);
        if(stu == null) {
            synchronized (this) {
                System.out.println("进入了同步锁");
                //先查询Redis
                stu = (Student) ops.get(PREFIX + id);
                //如果Redis缓存存在数据,就直接返回
                if (stu != null) {
                    System.out.println("Redis查到,返回" + stu);
                    return stu;
                }
                //如果Redis没有查到数据,就查询MySQL
                stu = studentMapper.selectById(id);
                //MySQL查到数据,就保存到Redis
                if (stu != null) {
                    System.out.println("MySQL查到,返回" + stu);
                    ops.set(PREFIX + id, stu);
                    return stu;
                }else {
                    //MySQL没有数据,在Redis保存空对象,设置过期时间
                    System.out.println("MySQL没有数据");
                    Student student = new Student();
                    ops.set(PREFIX + id, student,5, TimeUnit.SECONDS);
                }
            }
        }else {
            System.out.println("没有执行同步锁");
        }
        return stu;
    }

解决方法2:使用布隆过滤器

布隆过滤器

        布隆过滤器(Bloom Filter)是1970年由布隆提出的。它实际上是一个很长的二进制向量和一系列随机映射函数。布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都比一般的算法要好的多,缺点是有一定的误识别率和删除困难。

布隆过滤器的优点:

  • 时间复杂度低,增加和查询元素的时间复杂为O(N),(N为哈希函数的个数,通常情况比较小)
  • 保密性强,布隆过滤器不存储元素本身
  • 存储空间小,如果允许存在一定的误判,布隆过滤器是非常节省空间的(相比其他数据结构如Set集合)

 布隆过滤器的缺点:

  • 有点一定的误判率,但是可以通过调整参数来降低
  • 无法获取元素本身
  • 很难删除元素

判断不存在的数据一定不存在,判断存在的数据可能不存在。  

使用布隆过滤器:

1) 将数据保存到布隆过滤器中

2) 使用布隆过滤器进行判断

Redission

        Redission是一个基于Redis的分布式Java对象与服务的开源框架。它是Redisson项目的一部分,提供了丰富的分布式Java对象、集合、锁、信号量、闭锁、消息队列和分布式服务等功能。Redission旨在简化分布式系统的开发,并提供高性能的分布式数据结构和分布式服务的支持。

 基于Redis工具包,提供大量功能,包含:

  1. 布隆过滤器

  2. 分布式锁

  3. 分布式原子类

1)引入依赖
<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson-spring-boot-starter</artifactId>
    <version>3.16.6</version>
</dependency>
2 )  创建布隆过滤器
@Configuration
public class RedissonConfig {

    @Bean
    public RBloomFilter<String> bloomFilter(){
        Config config = new Config();
        config.setTransportMode(TransportMode.NIO);
        SingleServerConfig singleServerConfig = config.useSingleServer();
        //可以用"rediss://"来启用SSL连接
        singleServerConfig.setAddress("redis://127.0.0.1:6379");
        RedissonClient redisson = Redisson.create(config);
        //创建布隆过滤器
        RBloomFilter<String> bloomFilter = redisson.getBloomFilter("student-filter");
        //初始化 参数1 向量长度 参数2 误识别率
        bloomFilter.tryInit(10000000L,0.03);
        return bloomFilter;
    }
}
3) 将数据的id保存到过滤器中
@Autowired
    private RBloomFilter<String> rBloomFilter;

    /**
     * 初始化布隆过滤器
     * @return
     */
    @GetMapping("init-student-filter")
    public ResponseResult<String> initStudentFilter(){
        List<Student> list = studentService.list();
        //将所有id保存到过滤器中
        list.forEach(student -> {
            rBloomFilter.add(String.valueOf(student.getStuId()));
        });
        return ResponseResult.ok("ok");
    }
4) 使用过滤器排除不存在的数据
@Autowired
    private RBloomFilter<String> rBloomFilter;

    @Override
    public Student getStudentById(Long id) {
        //获得字符串操作对象
        ValueOperations<String, Object> ops = redisTemplate.opsForValue();
        //先查询Redis
        Student stu = (Student) ops.get(PREFIX + id);
        if(stu == null) {
            synchronized (this) {
                System.out.println("进入了同步锁");
                //先查询Redis
                stu = (Student) ops.get(PREFIX + id);
                //如果Redis缓存存在数据,就直接返回
                if (stu != null) {
                    System.out.println("Redis查到,返回" + stu);
                    return stu;
                }
                //使用布隆过滤器判断数据库中是否存在该id
                if(rBloomFilter.contains(String.valueOf(id))) {
                    //如果Redis没有查到数据,就查询MySQL
                    stu = studentMapper.selectById(id);
                    //MySQL查到数据,就保存到Redis
                    if (stu != null) {
                        System.out.println("MySQL查到,返回" + stu);
                        ops.set(PREFIX + id, stu);
                        return stu;
                    }
                }else{
                    System.out.println("布隆过滤器判断id数据库不存在,直接返回");
                }
            }
        }else {
            System.out.println("没有执行同步锁");
        }
        return stu;
    }

四、总结

        Redis是一个基于内存的数据存储系统,由于内存有限,当内存不足时,Redis需要选择一些数据进行淘汰,以释放出空闲内存空间。

        Redis持久化并不一定是必需的,取决于业务需求和数据的重要性。在某些应用场景下,可以权衡性能和数据安全,选择不进行持久化。

        Redis在处理并发问题时需要考虑竞态条件、脏读、丢失更新、死锁和数据竞争等情况,并采取相应的措施来确保数据的一致性和完整性

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值