redis的使用场景

news2024/9/27 15:27:44

1. redis的使用场景

redis使用场景的案例:
   [1]热点数据的缓存
   [2]分布式锁
   [3]短信业务(登录注册时)

2. redis实现注册登录功能

在这里插入图片描述

代码

在发送验证码时,先判断数据库是否有该手机号,有则发送验证码(此时redis缓存中有发送过该验证码,则返回已发送,防止多次发送验证码)并储存到redis中(手机号作为唯一的在加上过期时间)。

在登录验证手机号和验证码时,根据输入的手机号(唯一key在redis中查询)是否跟输入的验证码(不为空)时相同,登录成功,并删除该redis数据(验证码只能作为一次登录)。

@Autowired
    private StringRedisTemplate redisTemplate;
    @GetMapping("send")
    public R send(String phone) throws Exception {
        //1. 校验手机号是否存在---连接数据库
        if(phone.equals("15137437506")||phone.equals("15959715454")){
            if(redisTemplate.hasKey("code::"+phone)){
                return new R(500,"验证码已发送",null);
            }
            //2. 发生验证码
            String code = SendMsgUtil.sendCode(phone);
            //3. 保存验证码到redis.
            redisTemplate.opsForValue().set("code::"+phone,code,5, TimeUnit.MICROSECONDS);
            return new R(200,"发送成功",null);
        }

        return new R(500,"手机号未注册",null);
    }


    @PostMapping("login")
    public R login(@RequestBody LoginVo loginVo){
        //1. 校验验证码
        String code = redisTemplate.opsForValue().get("code::" + loginVo.getPhone());
        String phone = loginVo.getPhone();
        if(StringUtils.hasText(loginVo.getCode()) && loginVo.getCode().equals(code)){
            if(phone.equals("18839986970")||phone.equals("15137437506")){
                redisTemplate.delete("code::"+phone);
                return new R(200,"登录成功",null);
            }else{
                return new R(500,"手机号错误",null);
            }
        }
        return new R(500,"验证码错误",null);
    }

3. 热点数据缓存

为了把一些经常访问的数据,放入缓存中以减少对数据库的访问频率。从而减少数据库的压力,提高程序的性能。【内存中存储】

3.1 缓存的原理

在这里插入图片描述

3.2 java使用redis如何实现缓存功能

在增删改查中模拟redis缓存

@Service
public class StockServiceImpl02 implements StockService {
    @Autowired
    private StockDao stockDao;
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    @Override
    public Stock getById(Integer id) {
        //1.查询redis缓存是否命中
        ValueOperations<String, Object> forValue = redisTemplate.opsForValue();
        Object o = forValue.get("stock::" + id);
        //System.out.println(o);
        //表示缓存命中
        if(o!=null){
            return (Stock) o;
        }
        //查询数据库
        Stock stock = stockDao.selectById(id);
        if(stock!=null){
            forValue.set("stock::" + id,stock);
        }
        return stock;
    }

    @Override
    public Stock insert(Stock stock) {
        int insert = stockDao.insert(stock);
        return stock;
    }

    @Override
    public Stock update(Stock stock) {
        //修改数据库
        int i = stockDao.updateById(stock);
        if(i>0){
            //修改缓存
            redisTemplate.opsForValue().set("stock::"+stock.getProductid(),stock);
        }
        return stock;
    }

    @Override
    public int delete(Integer productid) {
        int i = stockDao.deleteById(productid);
        if(i>0){
            //删除缓存
            redisTemplate.delete("stock::"+productid);
        }
        return i;
    }
}

发现在查询时会访问redis缓存,如果命中则直接返回数据,未命中则查询数据库并放入redis缓存;在修改时保持数据一致,需要redis中数据一起改变;在删除时,数据库和redis缓存一起删除。

3.2 使用缓存注解完成缓存功能

使用AOP面向切面编程—spring缓存使用的组件

配置文件

 @Bean
    public CacheManager cacheManager(RedisConnectionFactory factory) {
        RedisSerializer<String> redisSerializer = new StringRedisSerializer();
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        //解决查询缓存转换异常的问题
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        // 配置序列化(解决乱码的问题),过期时间600秒
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ofSeconds(600)) //缓存过期10分钟 ---- 业务需求。
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))//设置key的序列化方式
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer)) //设置value的序列化
                .disableCachingNullValues();
        RedisCacheManager cacheManager = RedisCacheManager.builder(factory)
                .cacheDefaults(config)
                .build();
        return cacheManager;
    }

在主函数上需要启动:@EnableCaching

修改上述代码

@Service
public class StockServiceImpl implements StockService {
    @Autowired
    private StockDao stockDao;
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;


    //Cacheable:表示查询时使用的注解。 cacheNames:缓存的名称  key:缓存的唯一表示值
    //   1. 查询缓存中是否存在名称为cacheNames::key的值
    //   2. 如果存在则方法不会执行
    //   3. 如果不存在则执行方法体并把方法的返回结果放入缓存中cacheNames::key
    @Cacheable(cacheNames ={ "stock"}, key = "#id")
    @Override
    public Stock getById(Integer id) {
        Stock stock = stockDao.selectById(id);
        return stock;
    }

    @Override
    public Stock insert(Stock stock) {
        int insert = stockDao.insert(stock);
        return stock;
    }


    //CachePut:表示修改时使用的注解.
    // 1. 先执行方法体
    // 2. 把方法的返回结果放入缓存中
    @CachePut(cacheNames = "stock", key = "#stock.productid")
    @Override
    public Stock update(Stock stock) {
        int i = stockDao.updateById(stock);
        return stock;
    }

    //CacheEvict:表示删除时使用的注解
    // 1. 先执行方法体
    // 2. 把缓存中名称为cacheNames::key的值删除
    @CacheEvict(cacheNames = "stock", key = "#productid")
    @Override
    public int delete(Integer productid) {
        int i = stockDao.deleteById(productid);
        return i;
    }
}

4. 分布式锁

为了模拟高并发:---使用jmeter压测工具

模拟客户端请求环境

在这里插入图片描述

 public String decrement(Integer productid) {
        //根据id查询商品的库存
    int num = stockDao.findById(productid);
    if (num > 0) {
        //修改库存
        stockDao.update(productid);
        System.out.println("商品编号为:" + productid + "的商品库存剩余:" + (num - 1) + "个");
                return "商品编号为:" + productid + "的商品库存剩余:" + (num - 1) + "个";
     } else {
         System.out.println("商品编号为:" + productid + "的商品库存不足。");
         return "商品编号为:" + productid + "的商品库存不足。";
            }
        }
}

运行结果:发现出现超卖重卖现象

在这里插入图片描述

解决办法:我们使用synchronized或者lock锁

public String decrement(Integer productid) {
    //根据id查询商品的库存
    synchronized (this) {
    int num = stockDao.findById(productid);
    if (num > 0) {
        //修改库存
        stockDao.update(productid);
        System.out.println("商品编号为:" + productid + "的商品库存剩余:" + (num - 1) + "个");
                return "商品编号为:" + productid + "的商品库存剩余:" + (num - 1) + "个";
     } else {
         System.out.println("商品编号为:" + productid + "的商品库存不足。");
         return "商品编号为:" + productid + "的商品库存不足。";
            }
          }
     }
}

再次测试:发现超卖重卖现象没有发生

在这里插入图片描述

上面使用syn和lock虽然解决了并发问题,但是我们未来项目部署时可能要部署集群模式。

在springboot模拟项目集群

在这里插入图片描述

在这里插入图片描述

接下来使用nginx代理集群

在nginx.conf配置文件中设置代理服务器

在这里插入图片描述

启动nginx,再次压测发现依旧会超卖

在这里插入图片描述

在多线程环境中上述的锁对象是本地锁,每个服务器都有本地锁,导致锁不是唯一的

通过压测发现本地锁 无效了。使用redis解决分布式锁文件

在这里插入图片描述

引入redis依赖和配置

		<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
spring.redis.host=172.16.7.110
spring.redis.port=6379

修改原有代码

Redis提供了一个命令setnx 可以来实现分布式锁,该命令只在键 key 不存在的情况下 将键 key 的值设置为 value ,若键 key 已经存在, 则 SETNX 命令不做任何动作。根据这一特性我们就可以制定Redis实现分布式锁的方案了。

@Autowired
private StringRedisTemplate redisTemplate;
    //
public String decrement(Integer productid) {
    ValueOperations<String, String> opsForValue = redisTemplate.opsForValue();
     //1.获取共享锁资源
    Boolean flag = opsForValue.setIfAbsent("product::" + productid, "1111", 30, TimeUnit.SECONDS);
     //表示获取锁成功
    if(flag) {
        try {
            //根据id查询商品的库存
            int num = stockDao.findById(productid);
       if (num > 0) {
         //修改库存
        stockDao.update(productid);
        System.out.println("商品编号为:" + productid + "的商品库存剩余:" + (num - 1) + "个");
        return "商品编号为:" + productid + "的商品库存剩余:" + (num - 1) + "个";
       } else {
         System.out.println("商品编号为:" + productid + "的商品库存不足。");
         return "商品编号为:" + productid + "的商品库存不足。";
            }
        }finally {
                //释放锁资源
                redisTemplate.delete("product::"+productid);
            }
        }else{
            //休眠100毫秒 在继续抢锁
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            return decrement(productid);
        }
    }

再次压测

在这里插入图片描述

上述发现并没有超卖重卖,,但是

锁超时问题
这里有一个问题,如果获取到锁的服务在执行方法体(释放锁的时候)宕机了,那锁不就释放不了么,别的服务也就没办法获取到锁,就造成了死锁。

使用redisson

在执行方法体的时候锁的时间到了或者宕机,watch dog会检测持有锁的进程给其增加锁时间(大约3次如果还没执行完或者宕机,该线程会结束)可以自动删除锁,别的服务就可以获取锁了,

引入依赖

    <!--ression依赖-->
        <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson</artifactId>
            <version>3.24.3</version>
        </dependency>

设置配置文件

@Configuration
public class RedissonConfig {

    @Bean
    public RedissonClient redisson(){
        Config config = new Config();
//        //连接的为redis集群
//        config.useClusterServers()
//                // use "rediss://" for SSL connection
//                .addNodeAddress("redis://127.0.0.1:7181","","","")
//        ;
        //连接单机
        config.useSingleServer().setAddress("redis://192.168.111.188:6379");
        RedissonClient redisson = Redisson.create(config);
        return redisson;
    }
}

修改原有代码

 @Autowired
  private RedissonClient redisson;

  public String decrement(Integer productid) {
      RLock lock = redisson.getLock("product::" + productid);
      lock.lock();
      try {
          int num = stockDao.findById(productid);
          if (num > 0) {
              //修改库存
         stockDao.update(productid);
         System.out.println("商品编号为:" + productid + "的商品库存剩余:" + (num - 1) + "个");
         return "商品编号为:" + productid + "的商品库存剩余:" + (num - 1) + "个";
          } else {
              System.out.println("商品编号为:" + productid + "的商品库存不足。");
              return "商品编号为:" + productid + "的商品库存不足。";
          }
      } finally {
          lock.unlock();
      }
   }

可以解决该问题。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1949351.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于微信小程序+SpringBoot+Vue的自习室选座与门禁系统(带1w+文档)

基于微信小程序SpringBootVue的自习室选座与门禁系统(带1w文档) 基于微信小程序SpringBootVue的自习室选座与门禁系统(带1w文档) 本课题研究的研学自习室选座与门禁系统让用户在小程序端查看座位&#xff0c;预定座位&#xff0c;支付座位价格&#xff0c;该系统让用户预定座位…

Jmeter三种方式获取数组中多个数据并将其当做下个接口参数入参【附带JSON提取器和CSV格式化】

目录 一、传统方式-JOSN提取器获取接口返回值 1、接口调用获取返回值 2、添加JSON提取器 3、调试程序查看结果 4、添加循环控制器 5、设置count计数器 6、添加请求 7、执行请求 二、CSV参数化 1、将结果写入后置处理程序 2、设置循环处理器 3、添加CSV文件 4、设置…

【机器学习】用Jupyter Notebook实现并探索单变量线性回归的代价函数以及遇到的一些问题

引言 在机器学习中&#xff0c;代价函数&#xff08;Cost Function&#xff09;是一个用于衡量模型预测值与实际值之间差异的函数。在监督学习中&#xff0c;代价函数是评估模型性能的关键工具&#xff0c;它可以帮助我们了解模型在训练数据上的表现&#xff0c;并通过优化过程…

IPD推行成功的核心要素(十五)项目管理提升IPD相关项目交付效率和用户体验

研发项目往往包含很多复杂的流程和具体的细节。因此&#xff0c;一套完整且标准的研发项目管理制度和流程对项目的推进至关重要。研发项目管理是成功推动创新和技术发展的关键因素。然而在实际管理中&#xff0c;研发项目管理常常面临着需求不确定、技术风险、人员素质、成本和…

PyTorch安装CUDA标准流程(可解决大部分GPU无法使用问题)

最近一段时间在研究PyTorch中的GPU的使用方法&#xff0c;之前曾经安装过CUDA&#xff0c;不过在PyTorch中调用CUDA时无法使用。考虑到是版本不兼容问题&#xff0c;卸载后尝试了其他的版本&#xff0c;依旧没有能解决问题&#xff0c;指导查阅了很多资料后才找到了解决方案。 …

uni-app声生命周期

应用的生命周期函数在App.vue页面 onLaunch:当uni-app初始化完成时触发&#xff08;全局触发一次&#xff09; onShow:当uni-app启动&#xff0c;或从后台进入前台时显示 onHide:当uni-app从前台进入后台 onError:当uni-app报错时触发,异常信息为err 页面的生命周期 onLoad…

数据治理之“财务一张表”

前言 信息技术的发展&#xff0c;伴随企业业务系统的纷纷建设&#xff0c;提升业务处理效率的同时&#xff0c;也将企业的整体主价值链流程分成了一段一段的业务子流程&#xff0c;很多情况下存在数据上报延迟、业务协作不顺畅、计划反馈不及时、库存积压占资多……都可以从数据…

20240725java的Controller、DAO、DO、Mapper、Service层、反射、AOP注解等内容的学习

在Java开发中&#xff0c;‌controller、‌dao、‌do、‌mapper等概念通常与MVC&#xff08;‌Model-View-Controller&#xff09;‌架构和分层设计相关。‌这些概念各自承担着不同的职责&#xff0c;‌共同协作以构建和运行一个应用程序。‌以下是这些概念的解释&#xff1a;‌…

深度学习趋同性的量化探索:以多模态学习与联合嵌入为例

深度学习趋同性的量化探索&#xff1a;以多模态学习与联合嵌入为例 参考文献 据说是2024年最好的人工智能论文&#xff0c;是否有划时代的意义&#xff1f; [2405.07987] The Platonic Representation Hypothesis (arxiv.org) ​arxiv.org/abs/2405.07987 趋同性的量化表达 …

OAK相机支持的图像传感器有哪些?

相机支持的传感器 在 RVC2 上&#xff0c;固件必须具有传感器配置才能支持给定的相机传感器。目前&#xff0c;我们支持下面列出的相机传感器的开箱即用&#xff08;固件中&#xff09;传感器配置。 名称 分辨率 传感器类型 尺寸 最大 帧率 IMX378 40563040 彩色 1/2.…

产品经理-简历的筛选标准(22)

什么是简历 简要地描述过往的经历—一份简历的核心要素就是介绍你所经历过的事情 因此准备简历的关键是“简”“要”二字&#xff1a; 一方面是你挑选出来的事情&#xff0c;一定是重要的、能给予你所谋求的位置提供竞争力的事情&#xff1b;另一方面是在描述这件事情的时候 要…

《Java初阶数据结构》----6.<优先级队列之PriorityQueue底层:堆>

前言 大家好&#xff0c;我目前在学习java。之前也学了一段时间&#xff0c;但是没有发布博客。时间过的真的很快。我会利用好这个暑假&#xff0c;来复习之前学过的内容&#xff0c;并整理好之前写过的博客进行发布。如果博客中有错误或者没有读懂的地方。热烈欢迎大家在评论区…

Golang | Leetcode Golang题解之第290题单词规律

题目&#xff1a; 题解&#xff1a; func wordPattern(pattern string, s string) bool {word2ch : map[string]byte{}ch2word : map[byte]string{}words : strings.Split(s, " ")if len(pattern) ! len(words) {return false}for i, word : range words {ch : patt…

【Python实战因果推断】56_因果推理概论6

目录 Causal Quantities: An Example Bias Causal Quantities: An Example 让我们看看在我们的商业问题中&#xff0c;你如何定义这些量。首先&#xff0c;你要注意到&#xff0c;你永远无法知道价格削减&#xff08;即促销活动&#xff09;对某个特定商家的确切影响&#xf…

算法 定长按组翻转链表

一、题目 已知一个链表的头部head&#xff0c;每k个结点为一组&#xff0c;按组翻转。要求返回翻转后的头部 k是一个正整数&#xff0c;它的值小于等于链表长度。如果节点总数不是k的整数倍&#xff0c;则剩余的结点保留原来的顺序。示例如下&#xff1a; &#xff08;要求不…

数据集成工具之kettle

Kettle 是一个用于数据集成的开源工具&#xff0c;由 Pentaho 开发&#xff0c;现已由 Hitachi Vantara 维护。Kettle 的全名是 Pentaho Data Integration (PDI)&#xff0c;主要用于数据提取、转换和加载&#xff08;ETL&#xff09;过程。 1. 核心组件 Spoon: 图形化的设计工…

【MetaGPT系列】【MetaGPT完全实践宝典——多智能体实践】

目录 前言一、智能体1-1、Agent概述1-2、Agent与ChatGPT的区别 二、多智能体框架MetaGPT2-1、安装&配置2-2、使用已有的Agent&#xff08;ProductManager&#xff09;2-3、多智能体系统介绍2-4、多智能体案例分析2-4-1、构建智能体团队2-4-2、动作/行为 定义2-4-3、角色/智…

mysql面试(六)

前言 本章节详细讲解了一下mysql执行计划相关的属性释义&#xff0c;以及不同sql所出现的不同效果 执行计划 一条查询语句经过mysql查询优化器的各种基于成本和各种规则优化之后&#xff0c;会生成一个所谓的 执行计划&#xff0c;这个执行计划展示了这条查询语句具体查询方…

Qt自定义MessageToast

效果&#xff1a; 文字长度自适应&#xff0c;自动居中到parent&#xff0c;会透明渐变消失。 CustomToast::MessageToast(QS("最多添加50张图片"),this);1. CustomToast.h #pragma once#include <QFrame>class CustomToast : public QFrame {Q_OBJECT pub…

广义线性模型(2)线性回归

线性回归算法应该是大多数人机器学习之路上的第一站&#xff0c;因为线性回归算法原理简单清晰&#xff0c;但却囊括了拟合、优化等等经典的机器学习思想。 说到线性回归&#xff0c;我们得先说说回归与分类、线性与非线性这些概念的区别。 一 分类与回归的区别 机器学习中的…