redis的几种应用场景

本文详细介绍了Redis在缓存、分布式锁、计数器和限时业务等方面的使用。作为缓存,Redis能提高性能和并发能力,尤其适用于存储热点数据。在分布式锁的应用中,通过Redission解决并发问题。此外,Redis的incr命令用于实现点赞和转发量的计数,SortedSet可实现排行榜功能。最后,Redis的expire命令在限时业务中发挥重要作用,如优惠活动和验证码的时效控制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

rebis的使用场景

1. 作为缓存

  • 缓存就是数据交换的缓冲区(称作:Cache),当某一硬件要读取数据时,会首先从缓存汇总查询数据,有则直接执行,不存在时从内存中获取。由于缓存的数据比内存快的多,所以缓存的作用就是帮助硬件更快的运行。
  • 缓存往往使用的是RAM(断电既掉的非永久存储),所以在用完后还是会把文件送到硬盘等存储器中永久存储。电脑中最大缓存就是内存条,硬盘上也有16M或者32M的缓存。
  • 高速缓存是用来协调CPU与主存之间存取速度的差异而设置的。一般CPU工作速度高,但内存的工作速度相对较低,为了解决这个问题,通常使用高速缓存,高速缓存的存取速度介于CPU与主存之间。系统将一些CPU在最近几个时间段经常访问的内容存在高速缓存,这样就在一定程度上缓解了由于主存速度低造成的CPU“停工待料”的情况。
  • 缓存就是把一些外存上的数据保存在内存上而已,为什么保存在内存上,我们运行的所有程序里面的变量都是存放在内存中的,所以如果想将值放入内存上,可以通过变量的方式存储。在JAVA中一些缓存一般都是通过Map集合来实现的。
    • 缓存在不同的场景下,作用是不一样的具体举例说明:
      • 操作系统磁盘缓存 ——> 减少磁盘机械操作。
      • 数据库缓存——>减少文件系统IO。
      • 应用程序缓存——>减少对数据库的查询。
      • Web服务器缓存——>减少应用服务器请求。
      • 客户端浏览器缓存——>减少对网站的访问。

1.1 为什么使用缓存

​ (1)提高性能:缓存查询速度必数据库查询速度快(内存vs硬盘)

​ (2)提高并发能力:缓存分担了部分请求,支持更高的并发

1.2 什么样的数据适合放入到缓存中

​ 热点数据

​ 修改频率比较低

​ 安全系数低的

缓存的原理:

怎么使用缓存:

(1)搭建一个springboot+mp的工程

(2)引入redis相关的依赖

(3)配置redis

(4)service代码

2. redis作为分布式锁

2.1代码实现

表示:

  • 先从Redis中读取stock的值,表示商品的库存
  • 判断商品库存是否大于0,如果大于0,则库存减1,然后再保存到Redis缓存中里面去,否则就提示“库存不足”
package com.hong.service.impl;

import com.hong.dao.StockDao;
import com.hong.entity.Stock;
import com.hong.service.StockService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

/**
 * @Author hongCheng
 * @Date 2021/4/16 15:39
 * @Version 1.0
 */
@Service
public class StockServiceImpl implements StockService {

    @Resource
    private StockDao stockDao;

    @Override
    public String findById(Integer productId) {
        //根据id查询stock信息
        Stock stock = stockDao.selectById(productId);
        if (stock.getNum() > 0) {
            //修改数量,数量-1
            stock.setNum(stock.getNum() - 1);
            //根据获取到的stock对象,再根据id修改数量
            stockDao.updateById(stock);
            System.out.println("库存剩余数量:" + stock.getNum());
            return "库存减少成功";
        }
        return "库存不足";
    }
}

可见用压力测试软件在多个线程同时访问,测试结果是有重复的
在这里插入图片描述

2.2 改进①

2.1中从Redis读取、判断值再减1保存到Redis的操作,很容易在并发场景下出问题,可以使用加并发锁synchronized来解决:

package com.hong.service.impl;

import com.hong.dao.StockDao;
import com.hong.entity.Stock;
import com.hong.service.StockService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

/**
 * @Author hongCheng
 * @Date 2021/4/16 15:39
 * @Version 1.0
 */
@Service
public class StockServiceImpl implements StockService {

    @Resource
    private StockDao stockDao;

    @Override
    public String findById(Integer productId) {//加入synchronized同步锁
        synchronized (this) {
            //根据产品id查询信息
        Stock stock = stockDao.selectById(productId);
            if (stock.getNum() > 0) {
                //根据id修改库存
                stock.setNum(stock.getNum() - 1);
                stockDao.updateById(stock);
                System.out.println("库存剩余数量:" + stock.getNum());
                return "库存减少成功";
            }
        }
        return "库存不足";
    }
}

可以看出测试结果没有重复的了
在这里插入图片描述

注意:在Java中关键字synchronized可以保证在同一时刻,只有一个线程可以执行某个方法或某个代码块

2.3 改进②

2.2的代码在单体模式下并没太大问题,但是在分布式或集群架构环境下存在问题

在分布式或集群架构下,synchronized只能保证当前的主机在同一时刻只能有一个线程执行减库存操作,但如图同时有多个请求过来访问的时候,不同主机在同一时刻依然是可以访问减库存接口的,这就导致问题在集群架构下依然存在。

注意:可以使用JMeter来模拟出高并发场景下访问Nginx来测试触发上面的问题

package com.hong.service.impl;

import com.hong.dao.StockDao;
import com.hong.entity.Stock;
import com.hong.service.StockService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

/**
 * @Author hongCheng
 * @Date 2021/4/16 15:39
 * @Version 1.0
 */
@Service
public class StockServiceImpl implements StockService {

    @Resource
    private StockDao stockDao;

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Override
    public String findById(Integer productId) {//加入synchronized同步锁 同步代码块
        //如果redis中存在该key则返回false,不存在返回true---》键库存--》del然后删除key
        Boolean aBoolean = redisTemplate.opsForValue().setIfAbsent("product::" + productId, "dongdong");

        if (aBoolean) {//获取锁了
            //根据产品id查询信息
            Stock stock = stockDao.selectById(productId);
            if (stock.getNum() > 0) {
                //根据id修改库存
                stock.setNum(stock.getNum() - 1);
                stockDao.updateById(stock);
                System.out.println("库存剩余数量:" + stock.getNum());
                redisTemplate.delete("product::" + productId);//释放锁资源
                return "库存减少成功";
            } else {
                redisTemplate.delete("product::" + productId);//释放所资源
                return "库存不足";
            }
        } else {
            System.out.println("服务器正忙,请稍后。。。。。。");
            return "服务器正忙,请稍后。。。。。。";
        }
    }
}

2.4 使用第三方组件redission-----专门用于解决分布式问题

  1. 引入依赖
<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson</artifactId>
    <version>3.13.4</version>
</dependency>
  1. 引入Bean
@Bean
public RedissonClient redissonClient() {
    Config config = new Config();
        config.useSingleServer().setAddress("redis://192.168.31.126:6379").setPassword("654321");
        RedissonClient redissonClient = Redisson.create(config);
        return redissonClient;
}
  1. 代码实现
package com.hong.service.impl;

import com.hong.dao.StockDao;
import com.hong.entity.Stock;
import com.hong.service.StockService;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;

/**
 * @Author hongCheng
 * @Date 2021/4/16 15:39
 * @Version 1.0
 */
@Service
public class StockServiceImpl01 implements StockService {

    @Resource
    private StockDao stockDao;

    @Autowired
    private RedissonClient redisson;

    @Override
    public String findById(Integer productId) {//加入synchronized同步锁 同步代码块
        RLock lock = redisson.getLock("product::" + productId);//获取锁对象
        try {
            lock.tryLock(60,20, TimeUnit.SECONDS); //自己别设置时间。
            Stock stock = stockDao.selectById(productId);
            if (stock.getNum() > 0) {
                //根据id修改库存
                stock.setNum(stock.getNum() - 1);
                stockDao.updateById(stock); //异常发生
//                   int c=10/0;
//                Thread.sleep(35000);
                System.out.println("库存剩余:" + (stock.getNum()));
                return "库存减少成功";
            } else {
                return "库存不足";
            }
        }catch (Exception e){
            throw  new RuntimeException(e.getMessage());
        }
        finally {
            lock.unlock();
        }
    }
}

3 作为点赞量videaId,0 incr(videaId),排行榜,转发量。

什么是计数器,如电商网站商品的浏览量、视频网站视频的播放数等。为了保证数据实时效,每次浏览都得给+1,并发量高时如果每次都请求数据库操作无疑是种挑战和压力。Redis提供的incr命令来实现计数器功能,内存操作,性能非常好,非常适用于这些计数场景 。

关系型数据库在排行榜方面查询速度普遍偏慢,所以可以借助redis的SortedSet进行热点数据的排序。 在奶茶活动中,我们需要展示各个部门的点赞排行榜, 所以我针对每个部门做了一个SortedSet,然后以用户的openid作为上面的username,以用户的点赞数作为上面的score, 然后针对每个用户做一个hash, 通过zrangebyscore就可以按照点赞数获取排行榜,然后再根据username获取用户的hash信息,这个当时在实际运用中性能体验也蛮不错的。

4 限时业务的运用

redis中可以使用expire命令设置一个键的生存时间,到时间后redis会删除它。利用这一特性可以运用在限时的优惠活动信息、手机验证码等业务场景。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值