SpringBoot使用Redis(事务异步add + 更新)

1,简单介绍redis

Redis(Remote Dictionary Server)是一个开源的内存中数据结构存储系统。

主要特点:

  • 内存存储: Redis 主要将数据存储在内存中,因此读写速度非常快,适合需要高性能的应用场景。

  • 持久化: Redis 支持将内存中的数据持久化到磁盘,可以通过RDB快照(Snapshotting)或者日志(Append-only file, AOF)的方式实现数据持久化,保证数据在服务重启后不丢失。

  • 数据结构多样性: Redis 不仅仅是键值存储(key-value store),还支持多种复杂的数据结构,每种数据结构都有专门的操作命令,使得开发者可以根据需求选择合适的数据结构存储数据。

  • 原子操作: Redis 提供的命令支持原子操作,可以确保复杂操作的原子性,避免并发问题。

  • 分布式: Redis 提供了主从复制(Replication)和分片(Sharding)等功能,支持构建高可用、高扩展性的分布式系统。

  • 高性能: Redis 使用单线程模型来处理请求,避免了多线程间的锁竞争和上下文切换开销,因此能够达到很高的单实例性能。

  • 丰富的扩展功能: Redis 提供了许多扩展功能,如事务(Transaction)、发布订阅(Pub/Sub)、Lua 脚本扩展、过期自动删除等,使其在不同场景下有更广泛的应用。

2,如何在实际项目中使用

正常项目中使用思路

  • 使用redis目的是为了提高我们接口响应速度,所以经常用于查询的数据可以用redis
  • 必须考虑保证redis与数据库的数据一致性
  • 作为辅助提升响应的手段,redis故障不能影响服务自身的业务,也就是不能导致服务故障
  • 主要是为了获取而用,所以redis数据的add,delete等操作应该由多线程进行

redis与数据库的数据一致性,后面会有出别的文章。这里主要展示如何使用

引入依赖

根据自己springBoot的版本选择合适的版本

java 复制代码
 <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-data-redis</artifactId>
      <version>2.5.4</version>
    </dependency>
    <!-- 使用连接池进行管理redis -->
    <dependency>
      <groupId>org.apache.commons</groupId>
      <artifactId>commons-pool2</artifactId>
      <version>2.11.1</version>
    </dependency>
    <dependency>
      <groupId>redis.clients</groupId>
      <artifactId>jedis</artifactId>
      <version>3.8.0</version>
    </dependency>

编写配置文件

java 复制代码
# redis 连接信息
spring.redis.host=127.0.0.1
spring.redis.port=6379
# 这里为了信息安全使用了加密
spring.redis.password={decrypt}cm9qZXIxMjM0
# redis 连接池的配置信息
spring.redis.jedis.pool.max-active=10
spring.redis.jedis.pool.max-idle=10
spring.redis.jedis.pool.min-idle=2
spring.redis.jedis.pool.max-wait=1500ms

关于如何在springboot中使用配置文件的加解密请查看

SpringBoot启动自动解密加密配置项_springboot environment 配置项解密-CSDN博客

编写config类

java 复制代码
package com.luojie.config;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisClusterConfiguration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.jedis.JedisClientConfiguration;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import redis.clients.jedis.JedisPoolConfig;

import java.time.Duration;

@Configuration
public class RedisConfig {

    @Value("${spring.redis.host}")
    private String redisHost;

    @Value("${spring.redis.port}")
    private int redisPort;

    @Value("${spring.redis.password}")
    private String redisPassword;

    @Value("${spring.redis.jedis.pool.max-active}")
    private int maxTotal;

    @Value("${spring.redis.jedis.pool.max-idle}")
    private int maxIdle;

    @Value("${spring.redis.jedis.pool.min-idle}")
    private int minIdle;

    @Value("${spring.redis.jedis.pool.max-wait}")
    private Duration maxWaitMillis;

    /**
     * Redis 连接工厂配置
     * @return
     */
    @Bean
    public RedisConnectionFactory redisConnectionFactory() {
        if (redisHost.contains(",")) {
            // 配置集群 Redis 连接工厂
            return clusterConnectionFactory();
        } else {
            //  配置单机 Redis 连接工厂
            return standaloneConnectionFactory();
        }
    }

    /**
     * 当redis为单机情况时
     * @return
     */
    private RedisConnectionFactory standaloneConnectionFactory() {
        // RedisStandaloneConfiguration 设置单机 Redis 配置
        RedisStandaloneConfiguration config = new RedisStandaloneConfiguration();
        config.setHostName(redisHost);
        config.setPort(redisPort);
        config.setPassword(redisPassword);

        // JedisClientConfiguration 配置 Jedis 连接
        JedisClientConfiguration.JedisClientConfigurationBuilder jedisClientConfiguration = JedisClientConfiguration.builder();
        jedisClientConfiguration.connectTimeout(Duration.ofMillis(3500)); // connection timeout
        // JedisPoolConfig 配置连接池参数
        JedisPoolConfig poolConfig = new JedisPoolConfig();
        poolConfig.setMaxTotal(maxTotal);
        poolConfig.setMaxIdle(maxIdle);
        poolConfig.setMinIdle(minIdle);
        poolConfig.setMaxWaitMillis(maxWaitMillis.toMillis());

        jedisClientConfiguration.usePooling().poolConfig(poolConfig);
        // 使用 JedisConnectionFactory 创建连接工厂
        return new JedisConnectionFactory(config, jedisClientConfiguration.build());
    }

    private RedisConnectionFactory clusterConnectionFactory() {
        // RedisClusterConfiguration 设置集群 Redis 配置
        RedisClusterConfiguration config = new RedisClusterConfiguration();
        for (String node : redisHost.split(",")) {
            String[] parts = node.split(":");
            config.clusterNode(parts[0], Integer.parseInt(parts[1]));
        }
        config.setPassword(redisPassword);

        // JedisClientConfiguration 配置 Jedis 连接
        JedisClientConfiguration.JedisClientConfigurationBuilder jedisClientConfiguration = JedisClientConfiguration.builder();
        jedisClientConfiguration.connectTimeout(Duration.ofMillis(3500)); // connection timeout

        // JedisPoolConfig 配置连接池参数
        JedisPoolConfig poolConfig = new JedisPoolConfig();
        poolConfig.setMaxTotal(maxTotal);
        poolConfig.setMaxIdle(maxIdle);
        poolConfig.setMinIdle(minIdle);
        poolConfig.setMaxWaitMillis(maxWaitMillis.toMillis());

        jedisClientConfiguration.usePooling().poolConfig(poolConfig);

        // 使用 JedisConnectionFactory 创建连接工厂
        return new JedisConnectionFactory(config, jedisClientConfiguration.build());
    }

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(redisConnectionFactory);
        
        // 使用StringRedisSerializer序列化和反序列化redis的key值
        template.setKeySerializer(new StringRedisSerializer());
        template.setHashKeySerializer(new StringRedisSerializer());

        // 使用GenericJackson2JsonRedisSerializer序列化和反序列化redis的value值
        template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
        template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());

        template.afterPropertiesSet();
        return template;
    }
}

编写redisTemplateUtil工具类

java 复制代码
package com.luojie.util;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.util.concurrent.TimeUnit;

/**
 * redis 工具类
 */
@Component
@Slf4j
public class RedisServiceUtil {

    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * 普通string类型,设置值
     *
     * @param key
     * @param value
     */
    public void set(String key, String value) {
        try {
            redisTemplate.opsForValue().set(key, value);
        } catch (Exception e) {
            log.error("fail set redis key:{},value:{}, errorMsg:{}", key, value, e.getMessage());
        }
    }

    /**
     * 普通string类型,设置值并设置超时时间
     *
     * @param key
     * @param value
     */
    public void setWithExpire(String key, String value, int time, TimeUnit timeUnit) {
        try {
            redisTemplate.opsForValue().set(key, value, time, timeUnit);
        } catch (Exception e) {
            log.error("fail set redis key with expire:{},value:{}, errorMsg:{}", key,
                    value, e.getMessage());
        }
    }

    /**
     * 普通string类型,获取值
     *
     * @param key
     */
    public String get(String key) {
        try {
            return (String) redisTemplate.opsForValue().get(key);
        } catch (Exception e) {
            log.error("fail get redis key:{}, errorMsg:{}", key, e.getMessage());
        }
        return null;
    }

    /**
     * 普通string类型,删除key
     *
     * @param key
     */
    public void delete(String key) {
        try {
            redisTemplate.delete(key);
        } catch (Exception e) {
            log.error("fail delete redis key:{}, errorMsg:{}", key, e.getMessage());
        }
    }

    /**
     * 普通string类型,判断key是否存在
     *
     * @param key
     */
    public boolean exists(String key) {
        try {
            return redisTemplate.hasKey(key);
        } catch (Exception e) {
            log.error("fail exists redis key:{}, errorMsg:{}", key, e.getMessage());
        }
        return false;
    }

    /**
     * 普通string类型,为某个key单独设置超时时间
     *
     * @param key
     */
    public void expire(String key, int seconds, TimeUnit timeUnit) {
        try {
            redisTemplate.expire(key, seconds, timeUnit);
        } catch (Exception e) {
            log.error("fail expire redis key:{}, errorMsg:{}", key, e.getMessage());
        }
    }

    /**
     * 普通string类型,获取key的超时时间
     *
     * @param key
     */
    public Long getExpire(String key) {
        try {
            return redisTemplate.getExpire(key);
        } catch (Exception e) {
            log.error("fail getExpire redis key:{}, errorMsg:{}", key, e.getMessage());
        }
        return null;
    }

    /**
     * hash类型,设置值
     *
     * @param key
     * @param value
     */
    public void setHash(String key, String field, String value) {
        try {
            redisTemplate.opsForHash().put(key, field, value);
        } catch (Exception e) {
            log.error("fail setHash redis key:{}, errorMsg:{}", key, e.getMessage());
        }
    }

    /**
     * hash类型,设置值
     *
     * @param key
     * @param value
     */
    public void setHashWithExpire(String key, String field, String value, int time, TimeUnit timeUnit) {
        try {
            redisTemplate.opsForHash().put(key, field, value);
            redisTemplate.expire(key, time, timeUnit);
        } catch (Exception e) {
            log.error("fail setHash with expire redis key:{}, errorMsg:{}", key, e.getMessage());
        }
    }

    /**
     * hash类型,获取值
     *
     * @param key
     */
    public String getHash(String key, String field) {
        try {
            return (String) redisTemplate.opsForHash().get(key, field);
        } catch (Exception e) {
            log.error("fail getHash redis key:{}, errorMsg:{}", key, e.getMessage());
        }
        return null;
    }

    /**
     * hash类型,删除
     *
     * @param key
     */
    public void deleteHash(String key, String field) {
        try {
            redisTemplate.opsForHash().delete(key, field);
        } catch (Exception e) {
            log.error("fail deleteHash redis key:{}, errorMsg:{}", key, e.getMessage());
        }
    }
}

------往上为基本功能线------

根据上面的,直接使用controller进行访问调用已经可以用了。往下这里做一下功能升级。

主要用到异步 + event

如果想详细了解异步,了解event监听器请参考

Spring的监听器使用(实用,直接拿去修改可用)-CSDN博客

创建数据module类

java 复制代码
package com.luojie.moudle;

import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.NonNull;

import java.util.concurrent.TimeUnit;

@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
@NoArgsConstructor
public class RedisEventModule {

    @NonNull
    private String key;

    private String value;

    private String field;

    private int timeout;

    private TimeUnit timeoutUnit;
}

创建event类

复制代码
BaseRedisEvent
java 复制代码
package com.luojie.event;

import org.springframework.context.ApplicationEvent;

public abstract class BaseRedisEvent extends ApplicationEvent {
    public BaseRedisEvent(Object source) {
        super(source);
    }
}
复制代码
RedisAddEvent
java 复制代码
package com.luojie.event;

import com.luojie.moudle.RedisEventModule;
import org.apache.commons.lang3.StringUtils;

public class RedisAddEvent extends BaseRedisEvent {

    public RedisEventModule eventModule;

    public RedisAddEvent(RedisEventModule source) {
        super(source);
        if (StringUtils.isBlank(source.getValue())) {
            throw new IllegalArgumentException("redis value can't be empty");
        }
        this.eventModule = source;
    }
}
复制代码
RedisDeleteEvent
java 复制代码
package com.luojie.event;

import com.luojie.moudle.RedisEventModule;

public class RedisDeleteEvent extends BaseRedisEvent{

    public RedisEventModule eventModule;

    public RedisDeleteEvent(RedisEventModule source) {
        super(source);
        this.eventModule = source;
    }
}

创建监听类

java 复制代码
package com.luojie.config.eventListener;

import com.luojie.event.BaseRedisEvent;
import com.luojie.event.RedisAddEvent;
import com.luojie.event.RedisDeleteEvent;
import com.luojie.moudle.RedisEventModule;
import com.luojie.util.RedisServiceUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

@Component
@Slf4j
public class RedisListener implements ApplicationListener<BaseRedisEvent> {

    @Autowired
    private RedisServiceUtil redisServiceUtil;

    @Override
    @Async("asyncExecutor") // 使用异步线程池进行处理
    public void onApplicationEvent(BaseRedisEvent redisEvent) {
        if (redisEvent instanceof RedisAddEvent) {
            handleAddEvent((RedisAddEvent) redisEvent);
        } else if (redisEvent instanceof RedisDeleteEvent) {
            handleDeleteEvent((RedisDeleteEvent) redisEvent);
        }
    }

    private void handleAddEvent(RedisAddEvent redisEvent) {
        log.info("RedisAddEvent:{}", redisEvent);
        RedisEventModule module = redisEvent.eventModule;
        if (StringUtils.isNotBlank(module.getField()) && module.getTimeout() != 0) {
            redisServiceUtil.setHashWithExpire(module.getKey(), module.getField(), module.getValue(),
                    module.getTimeout(), module.getTimeoutUnit());
        } else if (StringUtils.isNotBlank(module.getField())) {
            redisServiceUtil.setHash(module.getKey(), module.getField(), module.getValue());
        } else if (module.getTimeout() != 0){
            redisServiceUtil.setWithExpire(module.getKey(), module.getValue(),
                    module.getTimeout(), module.getTimeoutUnit());
        } else {
            redisServiceUtil.set(module.getKey(), module.getValue());
        }
    }

    private void handleDeleteEvent(RedisDeleteEvent redisEvent) {
        log.info("RedisDeleteEvent:{}", redisEvent);
        RedisEventModule module = redisEvent.eventModule;
        if (StringUtils.isNotBlank(module.getField()) ) {
            redisServiceUtil.deleteHash(module.getKey(), module.getField());
        } else {
            redisServiceUtil.delete(module.getKey());
        }
    }

}

异步线程池类

java 复制代码
package com.luojie.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;

@Configuration
public class AsyncPools {

    @Bean(name = "asyncExecutor")
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(10); // 设置核心线程数
        executor.setMaxPoolSize(20); // 设置最大线程数
        executor.setQueueCapacity(100); // 设置队列容量
        executor.setThreadNamePrefix("async-executor-"); // 设置线程名前缀
        executor.setKeepAliveSeconds(5000);// 设置线程最大等待时间
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());// 设置线程拒绝策略
        executor.initialize();
        return executor;
    }

}

3,创建controller测试使用

controller

java 复制代码
package com.luojie.controller;

import com.luojie.event.RedisAddEvent;
import com.luojie.event.RedisDeleteEvent;
import com.luojie.event.RojerEvent;
import com.luojie.moudle.RedisEventModule;
import com.luojie.util.RedisServiceUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class RedisTestController {

    @Autowired
    private ApplicationEventPublisher eventPublisher;

    @Autowired
    private RedisServiceUtil redisServiceUtil;

    @PostMapping("/redis/test1")
    public Object redisTest1(@RequestBody RedisEventModule module) throws InterruptedException {
        // 测试普通string
        eventPublisher.publishEvent(new RedisAddEvent(module));
        // 获取刚存入的value
        Thread.sleep(3000);
        String re = redisServiceUtil.get(module.getKey());
        System.out.println(re);
        // 测试删除
        eventPublisher.publishEvent(new RedisDeleteEvent(module));
        Thread.sleep(3000);
        System.out.println("删除之后re的值: " + redisServiceUtil.get(module.getKey()));
        return re;
    }

    @PostMapping("/redis/test2")
    public Object redisTest2(@RequestBody RedisEventModule module) throws InterruptedException {
        // 测试普通string
        eventPublisher.publishEvent(new RedisAddEvent(module));
        // 获取刚存入的value
        Thread.sleep(3000);
        String hash = redisServiceUtil.getHash(module.getKey(), module.getField());
        System.out.println(hash);
        eventPublisher.publishEvent(new RedisDeleteEvent(module));
        Thread.sleep(3000);
        System.out.println("删除之后re的值: " + redisServiceUtil.getHash(module.getKey(),
                module.getField()));
        return hash;
    }
}

测试字符串存入删除情况

存入值已获取

已存入值删除

测试成功

测试hash测存入和删除

存入后正常获取

存入值删除成功

测试成功

相关推荐
未来之窗软件服务1 分钟前
Cacti 未经身份验证SQL注入漏洞
android·数据库·sql·服务器安全
fengye20716127 分钟前
在MYSQL中导入cookbook.sql文件
数据库·mysql·adb
hudawei99631 分钟前
flutter缓存网络视频到本地,可离线观看
flutter·缓存·音视频
小哈里37 分钟前
【pypi镜像源】使用devpi实现python镜像源代理(缓存加速,私有仓库,版本控制)
开发语言·python·缓存·镜像源·pypi
CircleMouse1 小时前
基于 RedisTemplate 的分页缓存设计
java·开发语言·后端·spring·缓存
Ailovelearning1 小时前
neo4j框架:ubuntu系统中neo4j安装与使用教程
数据库·neo4j
_星辰大海乀2 小时前
表的设计、聚合函数
java·数据结构·数据库·sql·mysql·数据库开发
獨枭2 小时前
使用 163 邮箱实现 Spring Boot 邮箱验证码登录
java·spring boot·后端
维基框架2 小时前
Spring Boot 封装 MinIO 工具
java·spring boot·后端
秋野酱2 小时前
基于javaweb的SpringBoot酒店管理系统设计与实现(源码+文档+部署讲解)
java·spring boot·后端