利用redis实现缓存、发布订阅、分布式锁功能

news2024/11/13 9:39:14

Redis是一个内存键值存储数据库,通常用于缓存、会话管理、消息队列等场景。以下是一些常见的Redis使用场景:

1.缓存:将常用的数据缓存在Redis中,以减少对数据库的访问次数,提高应用程序的性能。

2.会话管理:使用Redis来存储用户的会话数据,以提高应用程序的并发处理能力。

3.发布/订阅系统:使用Redis的发布/订阅功能来实现实时通知、消息推送等功能。

4.分布式锁:使用Redis的分布式锁来实现分布式系统中的互斥访问控制。

5.任务队列:使用Redis的列表或队列来实现异步任务处理、延迟任务等功能。

Jedis是Java开发人员常用的Redis客户端库,它提供了简单易用的API来操作Redis数据库,Jedis支持Redis的所有功能,包括字符串、列表、集合、有序集合等数据结构,以及事务、Lua脚本等高级功能。单同时需要注意Jedis采用了单线程模型,不能充分利用多核CPU的性能,Jedis需要手动管理连接池和资源回收等问题,容易出现内存泄漏等问题,Jedis缺乏异步API的支持,不能很好地处理高并发请求,Jedis的同步调用模型容易出现阻塞问题,需要使用异步模型或者连接池等技术来解决。Jedis本质上是直接连接的redis server,如果在多线程环境下是非线程安全的,这个时候只有使用连接池,为每个Jedis实例增加物理连接。

除了Jedis,Lettuce是一个高性能的Redis客户端库,基于Netty框架,提供了异步和响应式API来操作Redis数据库,且支持单机、主从、集群等多种Redis部署方式,另外,采用了连接池和自动重连等技术,能够自动管理连接和资源回收等问题。

前面是一些概念介绍,接下来看看,在实际项目中,如何采用spring boot和redis实现缓存机制。对于Spring boot,如果程序中没有定义类型为CacheManager的Bean组件或是名为cacheResolver的CacheResolver缓存解析器,Spring Boot将按顺序选择并启用以下缓存组件1.Generic,2.JCache (JSR-107)(EhCache 3、Hazelcast、Infinispan等),3.EhCache 2.x,4.Hazelcast,5.Infinispan,6.Couchbase,7.Redis,8.Caffeine,9.Simple。实际上,Spring Boot默认缓存管理中,没有添加任何缓存管理组件能实现缓存管理。因为开启缓存管理后,Spring Boot会按照上述列表顺序查找有效的缓存组件进行缓存管理,如果没有任何缓存组件,会默认使用最后一个Simple缓存组件进行管理。Simple缓存组件是Spring Boot默认的缓存管理组件,它默认使用内存中的ConcurrentMap进行缓存存储,所以在没有添加任何第三方缓存组件的情况下,可以实现内存中的缓存管理,但是不推荐使用这种缓存管理方式。当在Spring Boot默认缓存管理的基础上引入Redis缓存组件,即在pom.xml文件中添加Spring Data Redis依赖启动器后,SpringBoot会使用RedisCacheConfigratioin当做生效的自动配置类进行缓存相关的自动装配,容器中使用的缓存管理器是RedisCacheManager, 这个缓存管理器创建的Cache为RedisCache, 进而操控redis进行数据的缓存。所以,如果要采用redis实现缓存,首先需要引入“spring-boot-starter-data-redis”的依赖,接着在application.properties中配置redis服务相关的配置,如下图所示,另外,还需要创建RedisConfig配置类,配置类中定义cacheManager和redisCacheTemplate,如下图所示。

为什么需要在配置类中定义redisCacheTemplate和cacheManager呢?因为默认情况下,缓存的对象的类要实现Serializable接口,是以JDK序列化数据存储在Redis中,如果想实现JSON格式存入缓存中,那么就需要进行序列化,这里使用GenericJackson2JsonRedisSerializer类对value就行序列化。

最后在需要进行缓存的类或者方法上添加Cache相关的注解,即可实现缓存功能。常用注解以及使用场景如下所示:

@Cacheable:可以标记在一个方法上,也可以标记在一个类上。当标记在一个方法上时表示该方法是支持缓存的,当标记在一个类上时则表示该类所有的方法都是支持缓存的。对于一个支持缓存的方法,Spring会在其被调用后将其返回值缓存起来,以保证下次利用同样的参数来执行该方法时可以直接从缓存中获取结果,而不需要再次执行该方法。该注解一般用在查询方法上。

@CachePut:也可以声明一个方法支持缓存功能。与@Cacheable不同的是使用@CachePut标注的方法在执行前不会去检查缓存中是否存在之前执行过的结果,而是每次都会执行该方法,并将执行结果以键值对的形式存入指定的缓存中。该注解一般用在新增方法上。

@CacheEvict:是用来标注在需要清除缓存元素的方法或类上的。当标记在一个类上时表示其中所有的方法的执行都会触发缓存的清除操作。该注解一般用在更新和删除方法上。

@EnableCaching:开启缓存功能,一般放在启动类上或者自定义的RedisConfig配置类上。部分代码片段如下所示,所有代码细节可以查看Demo。

下载Demo后,配置成本地Mysql的用户名、密码等信息,调用服务的post接口添加数据,再调用get接口获取数据,当从数据库中获取时,会打印的sql日志,具体如下所示。继续用get接口获取数据,则会从缓存中获取,日志中不再打印sql信息,当超过缓存失效时间20second后,又会从数据库中获取。

上面介绍了如何使用redis实现缓存,接下来看看发布/订阅功能,采用Redis实现发布订阅有很多优点,例如:

  1. 轻量级:Redis是一个内存数据库,所以它非常适合作为轻量级消息代理使用。因为Redis不需要像传统的消息中间件那样持久化数据,所以它可以非常快速地处理消息。

  1. 简单易用:使用Redis实现发布订阅非常简单,只需要几行代码就可以实现。

  1. 可扩展性:由于Redis可以作为集群部署,所以它的扩展性非常好。它可以轻松地处理大量消息,并且可以在需要时添加更多的Redis节点。

  1. 高性能:Redis是一个非常快速的内存数据库,所以它可以处理大量消息并提供快速的响应时间。

当然,相比于专门的消息中间件来传递message,消息中间件会有下面的一些优点。

  1. 持久性:与Redis不同,大多数消息中间件都提供消息持久化功能,这意味着即使应用程序在消息被发送后崩溃,消息也不会丢失。

  1. 多样性:消息中间件通常提供多种协议,例如AMQP、MQTT和STOMP等,可以让应用程序使用不同的协议进行通信。

  1. 可靠性:与Redis不同,大多数消息中间件具有复杂的可靠性机制,例如事务处理和消息确认,以确保消息被传递并正确处理。

  1. 高可用性:消息中间件通常提供集群部署,可以在节点故障时提供高可用性。

综上所述,使用Redis实现发布订阅是一个简单、轻量级、快速的解决方案,适合处理大量非关键性消息。而使用消息中间件则适用于更复杂的应用场景,例如需要可靠性保证、持久性和多样性的场景。那么如何使用spring boot,redis实现发布订阅功能呢?非常简单,具体步骤如下所示:

一:添加“spring-boot-starter-data-redis”的依赖

二:在application.properties文件中配置redis的host/port等信息

三:添加配置类信息和创建publish和subscribe服务,具体代码如下所示:在配置类中创建MessageListenerAdapter,MessageListenerAdapter是Spring AMQP中的一个类,它用于将消息侦听器(MessageListener)适配到处理具体消息的方法上。在代码的demo代码中是适配到onReceive()方法上。为什么需要配置MessageListenerAdapter呢?因为在Spring AMQP中,消息侦听器负责处理接收到的消息,但是要正确处理消息,必须了解消息的内容和格式,这使得编写消息处理逻辑变得复杂。MessageListenerAdapter就是为了简化这个过程而设计的。使用MessageListenerAdapter,可以将一个POJO对象的方法适配成消息侦听器,这样就不需要显式地编写MessageListener了。在适配过程中,MessageListenerAdapter将负责将消息转换为方法的参数,并将方法的返回值转换为消息。

此外,还配置了RedisMessageContainer,RedisMessageListenerContainer在Spring应用中使用Redis消息监听器时是必须配置的,它可以帮助自动管理Redis连接和连接池、自动订阅和取消订阅Redis频道或模式、自动分发Redis消息给相应的监听器处理,简化了代码的编写和维护,并确保了应用的可靠性和稳定性。

另外,还配置了ReactiveRedisTemplate,redistemplate是Spring Data Redis中提供的Redis客户端,用于进行与Redis的交互。

更多代码细节可查看demo,启动应用程序后,调用post接口"http://localhost:8080/api/news/publish",模拟往频道上发送消息,查看日志信息,可以看到订阅端打印了发送的消息,说明发布和订阅消息成功。

除了实现发布订阅功能,还可以借助redis实现分布式锁功能。分布式锁是在分布式系统中协调并发访问共享资源的一种常用机制,其主要目的是保证多个进程或线程在访问共享资源时的正确性和一致性。在分布式系统中,多个进程或线程可能会同时访问同一个共享资源,如数据库、缓存、文件等,如果不采取特殊的处理机制,可能会出现以下问题:

  1. 竞态条件:当多个进程或线程同时读写同一共享资源时,可能会出现互相干扰、顺序不确定等问题,导致程序运行不稳定或出现异常。

  1. 脏数据:当一个进程或线程正在修改某个共享资源时,如果另一个进程或线程也同时对其进行操作,可能会导致数据出现不一致的情况。

  1. 死锁:当多个进程或线程相互等待对方释放锁时,可能会出现死锁的情况,导致程序无法正常运行。

分布式锁就是解决上面这些问题的有效方法,实现的分布式锁应该具备如下的特点:

1、在分布式系统环境下,一个方法在同一时间只能被一个机器的一个线程执行;

2、高可用的获取锁与释放锁;

3、高性能的获取锁与释放锁;

4、具备可重入特性;

5、具备锁失效机制,防止死锁;

  1. 具备非阻塞锁特性,即没有获取到锁将直接返回获取锁失败。

常见的分布式锁实现方式包括基于数据库、ZooKeeper、Redis等技术实现,其中Redis是最常用的分布式锁实现方式之一。下面提供了利用redis实现分布式锁的部分代码片段。首先,创建RedisDistributedLock对象,在RedisDistributedLock中定义了tryLock()和unLock()方法,即添加锁和释放锁两个方法。对于tryLock(),实际是使用"redisTemplate.opsForValue().setIfAbsent"来实现添加锁的功能,该方法是 Spring Data Redis 提供的一个 Redis 命令封装方法,用于将 key/value 存储到 Redis 中,仅在该 key 不存在时才执行存储操作,并且该方法是线程安全的,可以用来实现分布式锁。释放锁是通过lua脚本实现,具体代码如下所示:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Component;

import java.util.Collections;
import java.util.concurrent.TimeUnit;

@Component
public class RedisDistributedLock {

    private static final long DEFAULT_EXPIRE_TIME = 30000L; // 默认过期时间30秒
    private static final long DEFAULT_TRY_LOCK_TIMEOUT = 5000L; // 默认尝试获取锁的超时时间5秒
    private static final String UNLOCK_LUA_SCRIPT = "if redis.call('get', KEYS[1]) == ARGV[1] then\n" +
            "    return redis.call('del', KEYS[1])\n" +
            "else\n" +
            "    return 0\n" +
            "end";

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    /**
     * 尝试获取锁
     *
     * @param lockKey    锁的key
     * @param requestId  请求id,用于标识加锁的客户端
     * @param expireTime 锁的过期时间,单位毫秒
     * @param tryTimeout 尝试获取锁的超时时间,单位毫秒
     * @return 是否获取到锁
     */
    public boolean tryLock(String lockKey, String requestId, long expireTime, long tryTimeout) {
        long startTime = System.currentTimeMillis();
        while (System.currentTimeMillis() - startTime < tryTimeout) {
            Boolean locked = redisTemplate.opsForValue().setIfAbsent(lockKey, requestId, expireTime, TimeUnit.MILLISECONDS);
            if (locked != null && locked) {
                return true;
            }
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
        return false;
    }

    /**
     * 尝试获取锁,使用默认的过期时间和尝试获取锁的超时时间
     *
     * @param lockKey   锁的key
     * @param requestId 请求id,用于标识加锁的客户端
     * @return 是否获取到锁
     */
    public boolean tryLock(String lockKey, String requestId) {
        return tryLock(lockKey, requestId, DEFAULT_EXPIRE_TIME, DEFAULT_TRY_LOCK_TIMEOUT);
    }

    /**
     * 释放锁
     *
     * @param lockKey   锁的key
     * @param requestId 请求id,用于标识加锁的客户端
     * @return 是否释放成功
     */
    public boolean unlock(String lockKey, String requestId) {
        RedisScript<Long> script = new DefaultRedisScript<>(UNLOCK_LUA_SCRIPT, Long.class);
        Long result = redisTemplate.execute(script, Collections.singletonList(lockKey), requestId);
        return result != null && result == 1;
    }
}

创建了RedisDistributedLock对象后,在实际业务代码中就可以调用添加锁和释放锁的方法完成分布式锁业务场景。在执行数据修改操作前调用tryLock()方法添加锁,执行完操作后,再释放锁。

@Autowired
private RedisDistributedLock redisDistributedLock;

public void doSomething(String key) {
    String token = redisDistributedLock.tryLock(key); // 尝试获取锁
    if (token == null) {
        // 获取锁失败
        return;
    }
    try {
        // 执行业务逻辑
    } finally {
        redisDistributedLock.releaseLock(key, token); // 释放锁
    }
}

以上就是通过redis实现分布式锁的主要代码片段,代码补充完整后,可通过性能测试工具并发访问接口,查看数据是否正常。

可以看到,采用spring boot框架,引入“spring-boot-starter-data-redis”依赖,借助已经封装好的注解或者对象,可以很方便的通过redis实现缓存、发布订阅、分布式锁功能。

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

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

相关文章

用Python采集热门城市景点数据并简单制作数据可视化图

前言 嗨喽~大家好呀&#xff0c;这里是魔王呐 ❤ ~! 模块使用&#xff1a; 爬虫部分: requests parsel csv 数据分析部分: pandas pyecharts 如何安装模块: win R 输入 cmd 输入安装命令&#xff1a; pip install 模块名 回车 pycharm里面安装 terminal 输入安装命令…

OSI七层网络模型

应用层 定义了各种应用协议规范数据格式&#xff1a;HTTP协议、HTTPS协议、FTP协议、DNS协议、TFTP、SMTP等等。 表示层 翻译工作。提供一种公共语言、通信。 会话层 1、可以从校验点继续恢复数据进行重传。——大文件 2、自动收发&#xff0c;自动寻址的功能。 传输层 1、…

Qt下实现不规则形状窗口显示

文章目录前言一、资源文件的添加二、初始化窗口三、重写paintEvent函数实现窗口重绘四、重写QMouseEvent相关函数实现不规则窗口的移动及关闭五、demo完整代码六、下载链接总结前言 本文实现了Qt下显示两个不规则形状的窗口demo&#xff0c;其中有Qt的窗口对话框和QPaintEvent…

基于minikube快速搭建kubernetes单节点环境

一、说明 本文主要介绍在 Centos7 环境下基于 Minikube 来快速部署 Kubernetes 单节点集群环境&#xff0c;并在浏览器上访问部署在 k8s 上的 dashboard 服务。 二、Minikube 介绍 Minikube 是一个基于go语言开发&#xff0c;易于在本地运行 Kubernetes 的工具&#xff0c;可…

《mysql技术内幕:innodb存储引擎》笔记

任何时候Why都比What重要&#xff1b;不要相信任何的“神话”,学会自己思考&#xff1b;不要墨守成规,大部分人都知道的事情可能是错误的&#xff1b;不要相信网上的传言,去测试,根据自己的实践做出决定&#xff1b;花时间充分地思考,敢于提出质疑。1.MYSQL被设计为一个单进程多…

(三十七)vue 项目中常用的2个Ajax库

文章目录axios实现vue-resource实现上一篇&#xff1a;&#xff08;三十六&#xff09;Vue解决Ajax跨域问题 先看一个github搜索案例 有一个搜索框&#xff0c;输入相关用户名&#xff0c;就能模糊搜索出用户&#xff0c;展现到下方 第一步&#xff1a;我们用到了第三方样式库…

36、基于51单片机频率计 LCD 1602显示系统设计

摘要 数字频率计是一种基本的测量仪器。它被广泛应用于航天、电子、测控等领域&#xff0c;还被应用在计算机及各种数学仪表中。一般采用的是十进制数字&#xff0c;显示被测信号频率。基本功能是测量正弦信号&#xff0c;方波信号以及其他各种单位时间内变坏的物理量。由于其…

【沐风老师】为你推荐18个免费3dMAX插件和脚本

在所有 3D 软件中&#xff0c;3dMax的历史特别引人注目&#xff0c;有许多有趣的里程碑和突破点。自 1990 年推出以来&#xff0c;3dMax 一直是Autodesk公司的支柱产品之一。作为行业标准&#xff0c;它是高端项目和各种高级用途的首选 3D 软件之一。 如果你已经在使用3dMax&a…

一站式元数据治理平台——Datahub入门宝典

随着数字化转型的工作推进&#xff0c;数据治理的工作已经被越来越多的公司提上了日程。作为新一代的元数据管理平台&#xff0c;Datahub在近一年的时间里发展迅猛&#xff0c;大有取代老牌元数据管理工具Atlas之势。国内Datahub的资料非常少&#xff0c;大部分公司想使用Datah…

大数据开发-数据仓库介绍

目录标题1、数据仓库1.1 数仓为何而来&#xff1f;1.2 数据仓库的主要特性1.3 数据仓库与数据库的区别1.4 数据仓库三层架构1.5 实战-美团酒旅数仓建设1.6 ETL、ELT1.6.1 ETL1.6.2 ELT1、数据仓库 数据仓库Data Warehouse 简称DH&#xff0c;数据仓库的目的是构建面向分析的集…

Centos7.9源码编译安装dpdk

近日遇到一个需求&#xff0c;需要在Centos7.9上源码编译安装dpdk-21.11.2&#xff0c;并且dpdk的安装目录为/opt/。安装过程较为复杂&#xff0c;特此记录一下&#xff0c;方便日后查看。 相关配置参数编译环境虚拟机Centos7.9内核版本3.10.0-957dpdk版本21.11.2DPDK是INTEL公…

Java并发编程面试题——JUC专题

文章目录一、AQS高频问题1.1 AQS是什么&#xff1f;1.2 唤醒线程时&#xff0c;AQS为什么从后往前遍历&#xff1f;1.3 AQS为什么用双向链表&#xff0c;&#xff08;为啥不用单向链表&#xff09;&#xff1f;1.4 AQS为什么要有一个虚拟的head节点1.5 ReentrantLock的底层实现…

组合实现多类别分割(含实战代码)

来源&#xff1a;投稿 作者&#xff1a;AI浩 编辑&#xff1a;学姐 摘要 segmentation_models_pytorch是一款非常优秀的图像分割库&#xff0c;albumentations是一款非常优秀的图像增强库&#xff0c;这篇文章将这两款优秀结合起来实现多类别的图像分割算法。数据集选用CamVid…

Java实现JDBC工具类DbUtils的抽取及程序实现数据库的增删改操作

封装DbUtils 工具类 不知道我们发现没有&#xff0c;不管是对数据库进行查询&#xff0c;还是标准的JDBC 步骤&#xff0c;其开端都是先实现JDBC 的加载注册&#xff0c;接着是获取数据库的连接&#xff0c;最后都是实现关闭连接&#xff0c;释放资源的操作。那我们何不直接把…

起薪2万的爬虫工程师,Python需要学到什么程度才可以就业?

爬虫工程师的的薪资为20K起&#xff0c;当然&#xff0c;因为大数据&#xff0c;薪资也将一路上扬。那么&#xff0c;Python需要学到什么程度呢?今天我们来看看3位前辈的回答。 1、前段时间快要毕业&#xff0c;而我又不想找自己的老本行Java开发了&#xff0c;所以面了很多P…

【Python】用sympy判断函数的单调性和极值

文章目录单调性和奇异性连续性、极值、周期、不动点单调性和奇异性 sympy.calculus.singularities提供了4个关于单调性判定的函数&#xff0c;分别是 is_decreasing(expr, interval, symbolNone) is_increasing(expr, interval, symbolNone) is_strictly_decreasing(expr, in…

【期末复习】例题讲解Dijkstra算法

使用场景Dijkstra算法用于解决单源点最短路径问题&#xff0c;即给一个顶点作为源点&#xff0c;依次求它到图中其他n-1个顶点的最短距离。例题讲解Dijkstra算法将图中所有顶点分成两部分&#xff0c;第一部分是已知到源点最短距离的顶点Known(K)&#xff0c;第二部分是不知道到…

教你如何搭建人事OA-考勤管理系统,demo可分享

1、简介1.1、案例简介本文将介绍&#xff0c;如何搭建人事OA-考勤管理。1.2、应用场景可查看员工考勤的数据统计&#xff0c;可进行考勤签到、补签、请假、加班、调休等流程。2、设置方法2.1、表单搭建1&#xff09;新建表单【考勤签到】&#xff0c;字段设置如下&#xff1a;名…

VS Code Spring 全新功能来了!

大家好&#xff0c;欢迎来到我们 2023 年的第一篇博客&#xff01;我们想与您分享几个与 Spring 插件、代码编辑和性能相关的激动人心的更新&#xff0c;让我们开始吧&#xff01; Spring 插件包的新入门演练 演练&#xff08;Walkthrough&#xff09; 是一种多步骤、向导式的体…

跨境进口税费计算

以前搞跨境进口的时候&#xff0c;需要计算商品税费。税费计算比较复杂&#xff0c;把信息整理了一下&#xff0c;分享给大家。 一、基础知识 1.1税费类型 BBC/BC&#xff1a;跨境电商进口综合税 关税*0 (消费税进口环节增值税)*70%CC&#xff1a;行邮税&#xff0c;税额低…