手把手搭建springboot项目05-springboot整合Redis及其业务场景

news2024/11/27 18:44:13

目录

  • 前言
  • 一、食用步骤
    • 1.1 安装步骤
      • 1.1.1 客户端安装
      • 1.2 添加依赖
      • 1.3 修改配置
      • 1.4 项目使用
      • 1.5 序列化
  • 二、应用场景
    • 2.1 缓存
    • 2.2.分布式锁
      • 2.2.1 redis实现
      • 2.2.2 使用Redisson 作为分布式锁
    • 2.3 全局ID、计数器、限流
    • 2.4 购物车
    • 2.5 消息队列 (List)
    • 2.6 点赞、签到、打卡 (Set)
    • 2.7 筛选(Set)
    • 2.8 排行榜


前言

在日常的Java开发中,Redis是使用频次非常高的一个nosql数据库,数据以key-value键值对的形式存储在内存中,可以做缓存,分布式锁等。

spring-data-redis属于spring-data,提供给spring项目对于redis的操作,里边主要封装了jedis和lettuce两个客户端。


一、食用步骤

1.1 安装步骤

1.1.1 客户端安装

https://redis.io/docs/getting-started/installation/

1.2 添加依赖

<!-- redis依赖  -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

默认使用lettuce客户端
在这里插入图片描述

1.3 修改配置

spring:
  redis:
    host: localhost
    port: 6379
    password: 
    database: 0

redis客户端配置连接池

<!-- redis依赖commons-pool 这个依赖一定要添加 -->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
</dependency>

1.4 项目使用

通过spring-data-redis中为我们提供的 RedisTemplate 这个类操作redis服务器

@RestController
public class RedisController {
	@Autowired
    private RedisTemplate redisTemplate;

    @GetMapping("save")
    public void save(String key, String value){
        redisTemplate.opsForValue().set(key, value);
    }
}

1.5 序列化

运行后,发现redis里的值不直观,不利于排查问题
在这里插入图片描述
原因是RedisTemplate默认的序列化方式导致的,需要重新配置序列化方式

经常使用的对象序列化方式是: Jackson2JsonRedisSerializer

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration
public class RedisConfig {
    @Bean(name = "redisTemplate")
    public RedisTemplate<String, Object> getRedisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<String, Object>();
        redisTemplate.setConnectionFactory(factory);
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        // key的序列化类型
        redisTemplate.setKeySerializer(stringRedisSerializer); 
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance ,
                ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
        jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
        jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
        // value的序列化类型
        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer); 
        redisTemplate.setHashKeySerializer(stringRedisSerializer);
        redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }
}

在这里插入图片描述

二、应用场景

2.1 缓存

  1. String字符串类型: 动态变长字符串
  2. List列表类型:类似LinkedList前后插入和删除速度快
  3. Hash:类似Hashmap数组+链表的数据结构
  4. Set集合类型:类似HashSet,键值是无序唯一
  5. Zset有序集合:Set和Map的结合体,既能保证key唯一、又能根据value做排序

2.2.分布式锁

2.2.1 redis实现

在分布式环境下我们需要保证某一方法同一时刻只能被一个线程执行,或者多个服务的定时任务只能执行一次。

原理:SETNX

redis> SETNX mykey "Hello"
(integer) 1
redis> SETNX mykey "World"
(integer) 0
redis> GET mykey
"Hello"

NX :表示key不存在的时候,才能set成功,也即保证只有第一个客户端请求才能获得锁,而其他客户端请求只能等其释放锁,才能获取。

分布式锁具备的特征

  1. 互斥:任意时刻只能有一个客户端持有锁
  2. 锁超时释放:锁超时要释放,防止资源浪费和死锁
  3. 可重入性:一个线程如果获取了锁之后,可以再次对其请求加锁。(锁续期)
  4. 高性能和高可用:加锁和解锁需要开销尽可能低,同时也要保证高可用,避免分布式锁失效。
  5. 安全性:锁只能被持有的客户端删除,不能被其他客户端删除

场景伪代码:

//获取锁
ifSETNX key "lock" == 1{ 
	//设置过期时间
    expire(key,100);
    try {
        业务;
    }catch(){
    }finally {
       //释放锁
       del(key);
    }
}

以上代码,有以下问题:

  1. setnx和expire不是原子操作,会导致key永久存在
  2. 当线程a锁过期释放了,业务还没执行完,b抢到锁执行方法,此时线程a方法执行完后,把b抢到的锁给释放了…达不到锁的标准

代码如下(示例):

public String testLock() {
        //uuid保证只能释放当前线程的锁
        String uuid = UUID.randomUUID().toString();
        //推荐使用setIfAbsent这样redis在set的时候是单线程的,原子的,不会存在短时间重复set的问题。
        Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", uuid, 300, TimeUnit.SECONDS);
        if (Boolean.TRUE.equals(lock)) {
            System.out.println("获取分布式锁成功...");
            try {
                System.out.println("加锁成功...执行业务");
            } finally {
                // lua 脚本解锁
                String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
                // 删除redis.call('get', "lock") 的值和uuid相等的锁,成功返回1 失败返回 0
                redisTemplate.execute(new DefaultRedisScript<>(script, Long.class), Collections.singletonList("lock"), uuid);
            }
            return "执行完成";
        } 
        return "获取分布式锁失败";
    }

2.2.2 使用Redisson 作为分布式锁

官方文档:https://github.com/redisson/redisson/wiki

引入依赖

 <dependency>
   <groupId>org.redisson</groupId>
   <artifactId>redisson</artifactId>
   <version>3.11.1</version>
</dependency>

配置redission

import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MyRedissonConfig {
    //所有对 Redisson 的使用都是通过 RedissonClient
    @Bean(destroyMethod = "shutdown")
    public RedissonClient redisson() {
        // 1、创建配置
        Config config = new Config();
        // 路径要加上 redis:// 或者 rediss:// 
        //单节点模式
        config.useSingleServer().setAddress("redis://192.168.3.25:6379");
        // 2、根据 Config 创建出 RedissonClient 实例
        return Redisson.create(config);
    }
}

使用

@Autowired
RedissonClient redisson;

public List<User> getUser() {
    // 1. 获取一把锁
    RLock lock = redisson.getLock("lock");
    // 2. 加锁, 阻塞式等待 默认30s 会自动续期
    // 指定过期时间就不会自动续期
    lock.lock();
    try {
        Thread.sleep(10000);
        System.out.println("加锁成功,执行业务...");
    } catch (Exception e) {
        System.out.println("报错啦...");
    } finally {
        // 3. 解锁 假设解锁代码没有运行,Redisson也不会出现死锁
        lock.unlock();
    }
    return userServiceImpl.queryAll();
}

特点

  1. redission继承了juc的锁
  2. 调用lock方法后,会调用renewExpiration() 开启后台线程『看门狗』,30 / 3 = 10s后自动续期到30s。
  3. 锁的自动续期,如果业务耗时长,运行期间自动给锁续期 ,所以不用担心业务时间过长,锁自动过期被删掉;
//读写锁 锁粒度细,运行越快
RReadWriteLock readWriteLock = redisson.getReadWriteLock("lock");
//读锁 非阻塞
RLock rLock = readWriteLock.readLock();
rLock.lock();
rLock.unlock();
//写锁 阻塞,数据被写锁占有,读锁会被阻塞
RLock wLock = readWriteLock.writeLock();
wLock.lock();
wLock.unlock();

2.3 全局ID、计数器、限流

利用incrby的原子性

  • 计数器:阅读量、点赞数
  • 限流:访问者的ip和其他信息作为key,访问一次增加一次计数,超过次数则返回false
Long incr = redisTemplate.opsForValue().increment("incrlock");

2.4 购物车

public void testStringCart() {
    //添加购物车 +1
    Car.CartItem cartItem = new Car.CartItem(1L, "iphone13 256G 蓝色",new BigDecimal(100), 1);
    Car carSave = new Car(Collections.singletonList(cartItem));
    redisTemplate.opsForValue().set("user_001", JSONObject.toJSONString(carSave));
    //删除 -1 skuId = 1
    Car car = JSON.parseObject((String) redisTemplate.opsForValue().get("user_001"), Car.class);
    //记得判空
    car.getItems().forEach(
           i -> {
               if(Objects.equals(i.getSkuId(), 1L)){
                   i.setCount(i.getCount() - 1);
               }
           } 
    );
    //删除后保存新的购物车
    redisTemplate.opsForValue().set("user_001", JSONObject.toJSONString(car));
}

2.5 消息队列 (List)

//左推
redisTemplate.opsForList().leftPushAll("list", "西瓜1","西瓜2","西瓜3");
//右取 西瓜1 =》 "西瓜2" =》 "西瓜3" 
redisTemplate.opsForList().rightPop("list");

2.6 点赞、签到、打卡 (Set)

//user_001给新闻01点赞
redisTemplate.opsForSet().add("like:news01","user_001");
redisTemplate.opsForSet().add("like:news01","user_001");
redisTemplate.opsForSet().add("like:news01","user_002");
//set大小为2
redisTemplate.opsForSet().size("like:news01");
//点赞的所有用户 [user_002, user_001]
redisTemplate.opsForSet().members("like:news01");

2.7 筛选(Set)

  • (用户关注)我关注的人也关注了他
  • 可能认识的人
  • 商品筛选
//user1给新闻点赞
redisTemplate.opsForSet().add("like:news01","user_001");
redisTemplate.opsForSet().add("like:news01","user_002");
redisTemplate.opsForSet().add("like:news02","user_001");
redisTemplate.opsForSet().add("like:news02","user_003");
//获取新闻点赞的用户差集 => [user_002]
System.out.println(redisTemplate.opsForSet().difference("like:news01", "like:news02"));
//获取新闻点赞的用户交集 => [user_001]
System.out.println(redisTemplate.opsForSet().intersect("like:news01", "like:news02"));
//获取新闻点赞的用户并集 => [user_002, user_001, user_003]
System.out.println(redisTemplate.opsForSet().union("like:news01", "like:news02"));

2.8 排行榜

点击率排行榜

示例

public void testZset() {
    //用户点击新闻自增1
    Double news_01 = redisTemplate.opsForZSet().incrementScore("hot:20230222", "news_01", 1);
    //初始化ZSet
    redisTemplate.opsForZSet().add("hot:20230222","news_01", 700);
    redisTemplate.opsForZSet().add("hot:20230222","news_02", 1700);
    redisTemplate.opsForZSet().add("hot:20230222","news_03", 3700);
    redisTemplate.opsForZSet().add("hot:20230222","news_04", 500);
    //获取新闻点击最多的3条
    //[DefaultTypedTuple [score=3700.0, value=news_03],
    // DefaultTypedTuple [score=1700.0, value=news_02], 
    // DefaultTypedTuple [score=700.0, value=news_01]]
    //reverseRangeWithScores 分数倒序从下标0取到2 
    System.out.println(redisTemplate.opsForZSet().reverseRangeWithScores("hot:20230222", 0,2));
}

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

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

相关文章

如何弄小程序?公司企业可以这样做小程序

公司企业现在对于小程序的需求已经是刚需了&#xff0c;即使已经有官网的情况下&#xff0c;也会考虑再弄一个小程序来做小程序官网。那么公司企业如何弄小程序呢&#xff1f;下面跟大家说说方法。 流程一、找小程序服务商 由于一些公司企业并不像现在的互联网公司企业那样有…

考试系统 (springboot+vue前后端分离)

系统图片 下载链接 地址&#xff1a; http://www.gxcode.top/code 介绍 一款多角色在线培训考试系统&#xff0c;系统集成了用户管理、角色管理、部门管理、题库管理、试题管理、试题导入导出、考试管理、在线考试、错题训练等功能&#xff0c;考试流程完善。 技术栈 Spr…

Java8中@Contended和伪共享

Java8引入了Contented这个新的注解来减少伪共享(False Sharing)的发生。 sun.misc.Contended注解是被设计用来解决伪共享问题的 文章目录1.缓存行2.伪共享(False Sharing)2.1 CPU的缓存机制3.填充(Padding)4.Contended方式4.总结1.缓存行 CPU读取内存数据时并非一次只读一个字…

ATTCK实战系列——红队实战(二)

网络配置 网卡&#xff1a; WEB&#xff1a; PC&#xff1a; DC&#xff1a; IPWEB10.10.10.80&#xff08;内&#xff09;/192.168.111.80&#xff08;外&#xff09;PC10.10.10.201&#xff08;内&#xff09;/192.168.111.201&#xff08;外&#xff09;DC10.10.10.10物理机…

评论字数统计案例、评论回车发布、 Tab 栏切换、验证码倒计时、显示与隐藏密码——DOM事件

目录 一、DOM事件 1. 评论字数统计案例 2. 评论回车发布 3. Tab 栏切换 4. 验证码倒计时 5. 显示与隐藏密码 一、DOM事件 1. 评论字数统计案例 该案例中的显示输入字数及最大字数模块.wrapper .total 刚开始是看不见的&#xff0c;使用的是不透明度&#xff08;opacit…

量化交易-单因子分析-alphalens

1. 数据准备 1.1 计算因子IC重要函数 def get_clean_factor_and_forward_returns(factor,prices,groupbyNone,binning_by_groupFalse,quantiles5,binsNone,periods(1, 5, 10),filter_zscore20,groupby_labelsNone,max_loss0.35,zero_awareFalse,cumulative_returnsTrue)facto…

Nginx优化与防盗链

Nginx优化与防盗链 &#x1f4d2;博客主页&#xff1a; 微笑的段嘉许博客主页 &#x1f4bb;微信公众号&#xff1a;微笑的段嘉许 &#x1f389;欢迎关注&#x1f50e;点赞&#x1f44d;收藏⭐留言&#x1f4dd; &#x1f4cc;本文由微笑的段嘉许原创&#xff01; &#x1f4c…

图文并茂详解NAT协议(含实例分析)

什么是 NAT 协议 我们的计算机要想访问互联网上的信息&#xff0c;就需要一个地址&#xff0c;而且这个地址是大家&#xff08;其他主机&#xff09;所认可的&#xff0c;是公共的&#xff0c;这个地址也叫做公有 IP 地址。 与之相对的&#xff0c;除了公有 IP 地址外&#x…

Python自动化测试框架【Allure-pytest功能特性介绍】

Python自动化测试框架【Allure-pytest功能特性介绍】 目录&#xff1a;导读 前言 生成报告 测试代码 目录结构 Allure特性 Environment Categories Fixtures and Finalizers allure.attach 总结 写在最后 前言 Allure框架是一个灵活的轻量级多语言测试报告工具&am…

【PMP考试最新解读】第七版《PMBOK》应该如何备考?(含最新资料)

PMP新版大纲加入了ACP敏捷管理的内容&#xff0c;而且还不少&#xff0c;敏捷混合题型占到了 50%&#xff0c;前不久官方也发了通知8月启用第七版《PMBOK》&#xff0c;大家都觉得考试难度提升了&#xff0c;我从新考纲考完下来&#xff0c;最开始也被折磨过一段时间&#xff0…

玩游戏用哪个牌子的蓝牙耳机最好?打游戏无延迟的蓝牙耳机

作为一名苦逼的打工人&#xff0c;从早忙到黑&#xff0c;每天最期待的事莫过于下班回到家或是周末闲暇时光&#xff0c;来两把王者或吃鸡&#xff0c;配合无线游戏耳机&#xff0c;效果直接拉满&#xff0c;下面分享几款打游戏无延迟的蓝牙耳机。 一、南卡小音舱蓝牙耳机 蓝…

孪生工厂:机械臂加工产线 HMI 监控界面

2018 年&#xff0c;世界经济论坛(WEF)携手麦肯锡公司共同倡议并正式启动了全球“灯塔工厂网络项目”(Lighthouse Network)&#xff0c;共同遴选率先应用工业革命 4.0 技术实现企业盈利和持续发展的创新者与示范者。这就使得工厂系统需要对各流水线及生产运行成本方面进行多角度…

设计模式(十五)-面向对象概念

软件设计&#xff08;十五&#xff09;-UML建模&#xff08;下&#xff09;https://blog.csdn.net/ke1ying/article/details/129152487 一、设计原则 1、单一职责&#xff1a;设计目的单一的类。 2、开放-封闭原则&#xff1a;对扩展开放&#xff0c;对修改关闭。 3、里氏替…

微服务架构中的缓存设计浅析

在微服务架构中&#xff0c;缓存中间件越来越成为不可或缺的组件&#xff0c;下面聊聊微服务环境下的缓存设计。 1、简介 缓存在应用软件架构中是提高性能最直接的方式&#xff0c;如下 假设应用程序将数据存储在Mysql中&#xff0c;众所周知Mysql会将数据存储在硬盘上以防止…

git 拉取远程分支到本地

目录&#xff1a;***&#xff01;本小作者&#xff0c;是将终端和Git的可视化插件结合使用&#xff0c;刚接触的可以自习看一下&#xff0c;内容简单&#xff0c;避免弯路&#xff01;***一&#xff0c;简单了解远程分支1&#xff0c;连接远程&#xff1a;2&#xff0c;提交&am…

SpringBoot使用validator进行参数校验

Validated、Valid和BindingResultBean Validation是Java定义的一套基于注解的数据校验规范&#xff0c;比如Null、NotNull、Pattern等&#xff0c;它们位于 javax.validation.constraints这个包下。hibernate validator是对这个规范的实现&#xff0c;并增加了一些其他校验注解…

业务流程建模标注(BPMN)详细介绍

1、基本信息摘要&#xff1a;该文章的目的是对BPMN(Business Process Modeling Notation)的概要描述和介绍。描述基本的BPMN符号&#xff0c;包括这些图元如何组合成一个业务流程图&#xff08;Business Process Diagram&#xff09;2、BPMN简介2.1概述该文章的目的是对BPMN(Bu…

pytest之fixture用法

特点及优势1、命令灵活&#xff1a;对于setup.teardown&#xff0c;可以不起这两个名字2、数据共享&#xff1a;在conftest.py配置里写的方法可以实现数据共享&#xff0c;不需要import导入&#xff0c;可以跨文件共享3、scope的层次及神奇的yield组合相当于各种setup和teardow…

MySQL —— 表的操作

文章目录1. 创建表2. 查看表结构3. 修改表3.1 向表中插入数据3.2删除表中的数据3.3 修改表的性质3.3.1 添加字段3.3.2 修改字段的长度3.3.3 删除字段3.3.4 修改字段名3.3.5 修改表名4. 删除表5. 备份表前言&#xff1a; 本文会详细的讲解&#xff0c;在MySQL中表的操作。1. 创建…

Linux基础命令-uname显示系统内核信息

前言 这个命令主要是显示系统内核的相关信息&#xff0c;一般需要查看内核信息才会使用到这个命令&#xff0c;一起来看看吧。 一 命令的介绍 uname命令来自于英文词组“Unix name”的缩写&#xff0c;其功能是用于查看系统主机名、内核及硬件架构等信息。如果不加任务参数&am…