Springboot 集成redis

本文介绍了如何在SpringBoot中集成Redis,包括本地Redis的安装、配置、使用Redis做缓存(注解方式和RedisTemplate方式)、缓存击穿解决方案、查找Redis中key值的方法以及如何利用Redis实现分布式锁。详细讲解了RedisConnectionFactory、cacheManager的配置,以及RedisTemplate的基本使用和Bloom Filter的应用。

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


许多工程中都需要引入redis作为缓存来减轻SQL数据库的压力,本文简单介绍了工程中集成redis的基本方法,包括使用Spring提供的@Cacheable, @CachePut注解的方式,以及直接使用redisTemplate来操作redis库的方式来做缓存,并简单讲述了缓存空值和布隆过滤器的使用。

本地安装redis

在此不赘述。

redis 在springboot中的基本配置

application.yaml中基本配置:

spring:
  redis:
    host: 127.0.0.1
    port: 6379
    timeout: 3000
    pool:
      max-active: 8
      max-wait: -1
      max-idle: 8
      min-idle: 0

redisConfig bean配置

RedisConnectionFactory Bean

与1中的redis配置相对应,需要在Redis的Configuration文件中配置RedisConnectionFactory

    @Value("${spring.redis.host}")
    private String host;
    @Value("${spring.redis.port}")
    private int port;
    @Value("${spring.redis.timeout}")
    private int timeout;
    @Value("${spring.redis.pool.max-active}")
    private int maxActive;
    @Value("${spring.redis.pool.max-wait}")
    private int maxWait;
    @Value("${spring.redis.pool.max-idle}")
    private int maxIdle;
    @Value("${spring.redis.pool.min-idle}")
    private int minIdle;

    @Bean
    public JedisConnectionFactory redisConnectionFactory() {
        JedisConnectionFactory factory = new JedisConnectionFactory();
        factory.setHostName(host);
        factory.setPort(port);
        factory.setTimeout(timeout); 
        factory.setPassword(password);
        factory.getPoolConfig().setMaxIdle(maxIdle);
        factory.getPoolConfig().setMinIdle(minIdle);
        factory.getPoolConfig().setMaxTotal(maxActive);
        factory.getPoolConfig().setMaxWaitMillis(maxWait);
        return factory;
    }

使用redis做缓存

注解方式使用redis做缓存

cacheManager Bean

    @Bean
    public CacheManager cacheManager(RedisConnectionFactory factory) {
        RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofSeconds(300)); //缓存在redis的生存时间
        Set<String> cacheNames = new HashSet<>();
        cacheNames.add("user"); //Append a {@link Set} of cache names to be pre initialized with current {@link RedisCacheConfiguration}. 具体表现为cache 在redis中的key值前缀,形如 user::xxxxxx
        cacheNames.add("auth-user");
        return RedisCacheManager.builder(RedisCacheWriter.nonLockingRedisCacheWriter(factory))
                .cacheDefaults(redisCacheConfiguration).initialCacheNames(cacheNames).build();
    }

Controller

    @RequestMapping("/redis-get/{id}")
    @Cacheable(cacheNames = "user", key = "#id")
    public User redisGet(@PathVariable int id) {
        log.info("real process.");
        return pgService.getUser(id);
    }

    @PostMapping("/redis-update")
    @CachePut(cacheNames = "user", key = "#user.id")
    public User redisUpdate(@RequestBody User user) {
        log.info("update real process.");
        pgService.updateUser(user);
        return user;
    }

返回对象需要序列化

返回值必须要序列化,否则抛异常。

2020-03-27 17:18:16.986 ERROR 32488 --- [nio-8184-exec-3] c.h.m.u.RestControllerExceptionAdviser   : exception happen.

org.springframework.data.redis.serializer.SerializationException: Cannot serialize; nested exception is org.springframework.core.serializer.support.SerializationFailedException: Failed to serialize object using DefaultSerializer; nested exception is java.lang.IllegalArgumentException: DefaultSerializer requires a Serializable payload but received an object of type [com.hxb.mybatis.entity.User]
	at org.springframework.data.redis.serializer.JdkSerializationRedisSerializer.serialize(JdkSerializationRedisSerializer.java:96) ~[spring-data-redis-2.2.1.RELEASE.jar:2.2.1.RELEASE]
	... 63 common frames omitted
Caused by: java.lang.IllegalArgumentException: DefaultSerializer requires a Serializable payload but received an object of type [com.hxb.mybatis.entity.User]
	at org.springframework.core.serializer.DefaultSerializer.serialize(DefaultSerializer.java:43) ~[spring-core-5.2.3.RELEASE.jar:5.2.3.RELEASE]
	at org.springframework.core.serializer.support.SerializingConverter.convert(SerializingConverter.java:63) ~[spring-core-5.2.3.RELEASE.jar:5.2.3.RELEASE]
	... 65 common frames omitted

更新操作需要与创建(查询)操作返回类型一致

更新操作需要与创建(查询)操作返回类型一致才能做更新。

测试结果

多次触发rest,发现只有首次触发rest才会去pg库中查询:
在这里插入图片描述
在这里插入图片描述

@Cacheable, CachePut支持缓存击穿

  1. 实际上注解的方式做缓存,是有防止缓存击穿的保护机制的,当在PG库中查询不到对应的值时,会保存空值到redis中。在这里插入图片描述

RedisTemplate方式做缓存

redisTemlate Bean

主要需要指定ObjectMapper

    @Bean
    public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory factory) {
        StringRedisTemplate template = new StringRedisTemplate(factory);
        setSerializer(template); //设置ObjectMapper
        template.afterPropertiesSet();
        return template;
    }

    private void setSerializer(StringRedisTemplate template) {
        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);
        template.setValueSerializer(jackson2JsonRedisSerializer);
    }

基本使用方法

在需要操作Redis处,直接注入RedisTemplate即可。

    @Autowired
    private RedisTemplate redisTemplate;

    public void set(User user) {
        redisTemplate.opsForValue().set("user_" + user.getId(), user);
    }

    public User get(int key) {
        return (User) redisTemplate.opsForValue().get("user_" + key);
    }

缓存穿透

使用redisTemplate的方式直接操作redis需要用户在业务逻辑中自行加入缓存击穿控制的逻辑。一般两种方式:缓存空值,Bloom Filter

缓存空值

缓存空值,即在PG库中查询不到数据时,回写空值到redis库中,生存时间可以设置稍小于合法值的生存时间。

 private boolean isExistsInDb(String authName, String password) {
        Auth specifyAuthFromRedis = redisService.getSpecifyAuth(authName);

        if (specifyAuthFromRedis == null) {
            Auth specifyAuthFromPg = pgService.getSpecifyAuth(authName);
            if (specifyAuthFromPg == null) {
                log.warn("Auth {} does not exist in both redis and pg, insert null into redis to protect pg.", authName);
                updateRedis(authName, null, 60);
                return false;
            }
            if (specifyAuthFromPg.getPassword() != null && password.equals(specifyAuthFromPg.getPassword())) {
                log.debug("Auth {} exists in pg, insert into redis.", authName);
                updateRedis(authName, password, 120);
                return true;
            }
            log.debug("wrong password.");
            return false;
        } else {
            if (Objects.isNull(specifyAuthFromRedis.getPassword())) {
                log.warn("DB protection.");
                return false;
            } else {
                return password.equals(specifyAuthFromRedis.getPassword());
            }
        }
    }
Bloom Filter

Bloom Filter是将用户key值映射到位数组LockFreeBitArray中,相较于用户自行维护一个HashTable可以大大减少内存。

@Service
@Slf4j
public class BloomFilterUtil {
    private BloomFilter<String> bloomFilter = BloomFilter.create(Funnels.stringFunnel(Charset.defaultCharset()), 10000);

    public boolean put(String key) {
        log.debug("put key {}.", key);
        return bloomFilter.put(key);
    }

    public boolean exists(String key) {
        log.debug("check exists of key {}.", key);
        return bloomFilter.mightContain(key);
    }
}

查找redis中key值

使用keys pattern

 private Set<String> getAuthKeys(String pattern) {
 	return redisTemplate.keys(pattern);
 }

使用scan cursor [MATCH pattern] [COUNT count]

实际项目一般不允许使用keys pattern这种命令,原因在于redis的单线程,keys指令会导致线程阻塞一段时间,直到指令执行完毕,服务才能恢复。可以使用scan多次查询来减少阻塞时间。

public Set<String> scan(String pattern, int count) {
        log.info("scan for pattern {}, count {}.", pattern, count);
        RedisConnection redisConnection = redisTemplate.getConnectionFactory().getConnection();
        Set<String> keySet = new HashSet<>();
        Cursor<byte[]> cursor = redisConnection.scan(new ScanOptions.ScanOptionsBuilder().match(pattern).count(count).build());
        while (cursor.hasNext()) {
            keySet.add(new String(cursor.next()));
        }
        if (keySet.isEmpty()) {
            log.warn("There is no matched key for [{}].", pattern);
        }
        return keySet;
  }

使用redis做分布式锁

使用setnx实现

  1. redis中基本命令setnx,即当key值不存在时,才可以设置成功,所以可以使用setnx来实现分布式锁。
  2. 由于使用setnx做锁时,释放锁的操作是直接使用delete(key)来实现的,所以为了防止B线程错误释放掉A线程锁持有的锁,在加锁过程中,每个线程可以携带唯一的UUID作为setnx的value,在释放操作时,使用该uuid先做判断,再释放。
@Slf4j
@Service
public class RedisLockUtil {

    private static final String LOCK_PREFIX = "lock:";

    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * @param locKName       redis lock name
     * @param acquireTimeout max time for waiting lock
     * @param timeout        release lock time
     * @return identifier 
     */
    public String lock(String locKName, Duration acquireTimeout, Duration timeout) {
        String identifier = UUID.randomUUID().toString();
        String lockKey = LOCK_PREFIX + locKName;

        long end = System.currentTimeMillis() + acquireTimeout.toMillis();
        while (System.currentTimeMillis() < end) {
            if (Objects.equals(redisTemplate.opsForValue().setIfAbsent(lockKey, identifier, timeout), Boolean.TRUE)) {
                return identifier;
            }
            try {
                Thread.sleep(acquireTimeout.toMillis() / 10);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
        return null;
    }

    /**
     * Unlock according to value equals.
     */
    public boolean releaseLock(String lockName, String identifier) {
        String lockKey = LOCK_PREFIX + lockName;
        if (identifier.equals(redisTemplate.opsForValue().get(lockKey))) {
            return redisTemplate.delete(lockKey);
        }
        return false;
    }
}

Redisson

添加maven依赖

		<!--redisson-->
        <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson-spring-boot-starter</artifactId>
            <version>3.10.4</version>
        </dependency>

使用Redsson Lock

@Slf4j
@Service
public class RedisLockUtil {

    private static final String LOCK_SUFFIX = "lock:";

    @Autowired
    private RedissonClient redissonClient;
    
    public void lockByRedisson(String lockName, long leaseTime) {
        RLock lock = redissonClient.getLock(LOCK_SUFFIX + lockName);
        lock.lock(leaseTime, TimeUnit.SECONDS);
        log.debug("acquire lock success.");
    }

    public void releaseRedissonLock(String lockName) {
        RLock lock = redissonClient.getLock(LOCK_SUFFIX + lockName);
        lock.unlock();
        log.debug("release lock success.");
    }
}
Spring Boot 项目中集成 Redis 数据库,主要涉及以下几个步骤:引入依赖、配置 Redis 连接信息、使用 `RedisTemplate` 或 `StringRedisTemplate` 来操作 Redis 数据库。 ### 引入 Redis 依赖 在 `pom.xml` 文件中添加以下依赖以集成 Redis: ```xml <!-- Redis 依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <!-- 连接池依赖 --> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> </dependency> ``` 这些依赖项将帮助封装与 Redis 交互的底层操作,并提供连接池功能以优化与 Redis连接管理,提高性能[^1]。 ### 配置 Redis 连接 在 `application.properties` 或 `application.yml` 文件中配置 Redis连接信息。以下是 `application.properties` 的示例配置: ```properties # Redis 配置 spring.redis.host=localhost spring.redis.port=6379 spring.redis.lettuce.pool.max-active=8 spring.redis.lettuce.pool.max-idle=8 spring.redis.lettuce.pool.min-idle=2 spring.redis.lettuce.pool.max-wait=2000ms ``` 如果使用 `application.yml`,则配置如下: ```yaml spring: redis: host: localhost port: 6379 lettuce: pool: max-active: 8 max-idle: 8 min-idle: 2 max-wait: 2000ms ``` 这些配置项用于指定 Redis 服务器的地址、端口以及连接池的相关参数。 ### 使用 RedisTemplate 操作 RedisSpring Boot 应用程序中,可以通过注入 `RedisTemplate` 来操作 Redis 数据库。下面是一个简单的示例,展示如何使用 `RedisTemplate` 设置和获取数据: ```java import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service; @Service public class RedisService { private final RedisTemplate<String, Object> redisTemplate; public RedisService(RedisTemplate<String, Object> redisTemplate) { this.redisTemplate = redisTemplate; } public void set(String key, Object value) { redisTemplate.opsForValue().set(key, value); } public Object get(String key) { return redisTemplate.opsForValue().get(key); } } ``` 在这个示例中,`RedisTemplate` 被用来执行基本的键值对操作。`opsForValue()` 方法返回一个 `ValueOperations` 对象,可以用来设置和获取字符串类型的值。 ### 分布式会话场景(Redis-Session) 对于需要支持分布式会话的应用程序,可以利用 Spring Session 提供的功能,通过 Redis 来存储会话信息。这通常涉及到额外的配置和依赖项,但可以极大地简化跨多个服务实例的会话管理。 通过以上步骤,可以在 Spring Boot 项目中成功集成 Redis 数据库,并利用其提供的高性能数据访问能力。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值