springboot + redis

本篇简述springboot集成redis,基于redis哨兵模式。

sproingboot 集成组件三步曲(参见springboot+JPA介绍:依赖、配置、封装)

(假定springboot脚手架已搭建完成并成功运行,可参考历史分享springboot+mybatis)

1. maven添加redis依赖

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

2. redis 配置

2.1 yml

#spring
spring:

#redis
  redis:
    host: 192.168.2.9
    port: 6379
    password: CacheDB123
    jedis:
      pool:
        max-active: 8
        max-wait: 6000
        max-idle: 5
        min-idle: 1
    # 哨兵配置
    sentinel:
      master: mymaster
      nodes: 192.168.2.9:6379,192.168.2.10:6379

2.2 RedisTemplate config

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
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.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@EnableCaching
@Configuration
public class RedisConfig extends CachingConfigurerSupport {

    /**
     * redis模板
     * @param connectionFactory
     * @return
     */
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(connectionFactory);

        // 使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值(默认使用JDK的序列化方式)
        Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(objectMapper);

        // 使用StringRedisSerializer来序列化和反序列化redis的key值
        RedisSerializer<String> redisSerializer = new StringRedisSerializer();
        // key
        redisTemplate.setKeySerializer(redisSerializer);
        redisTemplate.setHashKeySerializer(redisSerializer);
        // value
        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
        redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);

        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }

}

3. redis 功能封装

3.1 RedisService

import java.util.concurrent.TimeUnit;

public interface RedisService {

    /** 缓存的有效时长 **/
    long TIME_ONE_SECOND = 1; // 1秒
    long TIME_ONE_MINUTE = 60 * TIME_ONE_SECOND; // 1分
    long TIME_ONE_HOUR = 60 * TIME_ONE_MINUTE; // 1小时
    long TIME_ONE_DAY = 24 * TIME_ONE_HOUR; // 1天
    long TIME_ONE_MONTH = 30 * TIME_ONE_DAY; // 1个月

    /**
     * 获取缓存剩余时间
     * @param keys
     * @return 秒
     */
    long getExpire(String... keys);

    /**
     * 缓存数据 并 设置有效期
     * @param value
     * @param exp
     * @param unit
     * @param keys
     * @param <V>
     * @return
     */
    <V> boolean setByKey(V value, long exp, TimeUnit unit, String... keys);

    /**
     * 缓存数据 并 设置有效期(默认时间单位为秒)
     * @param value
     * @param exp
     * @param keys
     * @param <V>
     * @return
     */
    <V> boolean setByKey(V value, long exp, String... keys);

    /**
     * 缓存数据(默认存储一个月)
     * @param value
     * @param keys
     * @param <V>
     * @return
     */
    <V> boolean setByKey(V value, String... keys);

    /**
     * incr key
     * 原子递增
     * @param key
     * @param value
     * @return
     */
    long increment(String key, long value);

    /**
     * incr key
     * 原子递增
     * @param key
     * @param value
     * @param exp
     * @return
     */
    long increment(String key, long value, long exp);

    /**
     * rpush = right push (rpush key value)
     * 添加元素到List集合的尾部(默认存储时间单位为秒)
     * @param key
     * @param value 一个元素 或 一个List集合
     * @param exp
     * @param <V>
     * @return
     */
    <V> boolean rpush(String key, V value, long exp);

    /**
     * lpush = left push (lpush key value)
     * 添加元素到List集合的头部(默认存储时间单位为秒)
     * @param key
     * @param value 一个元素 或 一个List集合
     * @param exp
     * @param <V>
     * @return
     */
    <V> boolean lpush(String key, V value, long exp);

    /**
     * lrange = list range (lrange key 0 -1 即获取全部列表数据)
     * 获取List集合数据
     * @param key
     * @param <V>
     * @return
     */
    <V> V lrange(String key);

    /**
     * sadd key value
     * 添加元素到Set集合(默认存储时间单位为秒)
     * @param key
     * @param value 一个元素 或 一个Set集合
     * @param exp
     * @param <V>
     * @return
     */
    <V> boolean sadd(String key, V value, long exp);

    /**
     * smembers key
     * 获取Set集合数据
     * @param key
     * @param <V>
     * @return
     */
    <V> V smembers(String key);

    /**
     * zadd key score value (score为有序索引序列)
     * 添加元素到ZSet集合(有序)(默认存储时间单位为秒)
     * @param key
     * @param value 一个元素 或 一个Set集合
     * @param exp
     * @param <V>
     * @return
     */
    <V> boolean zadd(String key, V value, long exp);

    /**
     * zrange key 0 -1
     * 获取ZSet集合数据
     * @param key
     * @param <V>
     * @return
     */
    <V> V zrange(String key);

    /**
     * 获取Redis缓存数据
     * @param keys
     * @param <V>
     * @return
     */
    <V> V getByKey(String... keys);

    /**
     * 删除Redis缓存数据
     * @param keys
     * @return
     */
    boolean delByKey(String... keys);

    /**
     * 通过 Redis 发布订阅消息
     * @param channel
     * @param message
     */
    void convertAndSend(String channel, Object message);

}

3.2 RedisServiceImpl

import com.alibaba.fastjson.JSON;
import com.example.demo.service.RedisService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.util.Arrays;
import java.util.concurrent.TimeUnit;

@Slf4j
@Service
public class RedisServiceImpl implements RedisService {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    /**
     * 拼接缓存数据的key
     * @param keys
     * @return
     */
    private String getMemKey(String... keys){
        if(keys == null || keys.length == 0){
            return null;
        }

        if(keys.length == 1){
            return keys[0];
        }

        StringBuffer memKey = new StringBuffer();
        Arrays.stream(keys).forEach(key -> {
            memKey.append(":");
            memKey.append(key);
        });

        return memKey.toString().replaceFirst(":", "");
    }

    /**
     * 设置缓存有效期
     * @param key
     * @param exp 默认一个月
     * @param unit 默认为秒
     * @return
     */
    private boolean expireByKey(String key, long exp, TimeUnit unit){
        if (exp == 0) {
            exp = RedisService.TIME_ONE_MONTH;
        }
        if (unit == null) {
            unit = TimeUnit.SECONDS;
        }

        return redisTemplate.expire(key, exp, unit);
    }

    @Override
    public long getExpire(String... keys) {
        String memKey = this.getMemKey(keys);
        Long expire = redisTemplate.getExpire(memKey);

        log.info("获取缓存数据剩余有效期:key-{}, value-{}", memKey, expire);

        return expire == null ? 0 : expire;
    }

    @Override
    public <V> boolean setByKey(V value, long exp, TimeUnit unit, String... keys) {
        String memKey = this.getMemKey(keys);
        redisTemplate.opsForValue().set(memKey, value);
        return this.expireByKey(memKey, exp, unit);
    }

    @Override
    public <V> boolean setByKey(V value, long exp, String... keys) {
        return setByKey(value, exp, TimeUnit.SECONDS, keys);
    }

    @Override
    public <V> boolean setByKey(V value, String... keys) {
        return setByKey(value, 0, keys);
    }

    @Override
    public long increment(String key, long value) {
        // 永不过期
        return redisTemplate.opsForValue().increment(key, value);
    }

    @Override
    public long increment(String key, long value, long exp) {
        long afterIncrementValue = redisTemplate.opsForValue().increment(key, value);
        this.expireByKey(key, exp, TimeUnit.SECONDS);
        return afterIncrementValue;
    }

    @Override
    public <V> boolean rpush(String key, V value, long exp) {
        redisTemplate.opsForList().rightPush(key, value);
        return this.expireByKey(key, exp, TimeUnit.SECONDS);
    }

    @Override
    public <V> boolean lpush(String key, V value, long exp) {
        redisTemplate.opsForList().leftPush(key, value);
        return this.expireByKey(key, exp, TimeUnit.SECONDS);
    }

    @Override
    public <V> V lrange(String key) {
        log.debug("获取缓存数据:key-{}", key);
        return (V) redisTemplate.opsForList().range(key, 0, -1);
    }

    @Override
    public <V> boolean sadd(String key, V value, long exp) {
        redisTemplate.opsForSet().add(key, value);
        return this.expireByKey(key, exp, TimeUnit.SECONDS);
    }

    @Override
    public <V> V smembers(String key) {
        log.debug("获取缓存数据:key-{}", key);
        return (V) redisTemplate.opsForSet().members(key);
    }

    @Override
    public <V> boolean zadd(String key, V value, long exp) {
        redisTemplate.opsForZSet().add(key, value, Math.random());
        return this.expireByKey(key, exp, TimeUnit.SECONDS);
    }

    @Override
    public <V> V zrange(String key) {
        log.debug("获取缓存数据:key-{}", key);
        return (V) redisTemplate.opsForZSet().range(key, 0, -1);
    }

    @Override
    @SuppressWarnings("unchecked")
    public <V> V getByKey(String... keys) {
        String memKey = this.getMemKey(keys);
        V value = (V) redisTemplate.opsForValue().get(memKey);

        log.info("获取缓存数据:key-{}, value-{}", memKey, JSON.toJSONString(value));

        return value;
    }

    @Override
    public boolean delByKey(String... keys) {
        return redisTemplate.opsForValue().getOperations().delete(this.getMemKey(keys));
    }

    @Override
    public void convertAndSend(String channel, Object message) {
        redisTemplate.convertAndSend(channel, message);
    }

}

写在最后:

简单提下缓存三点问题:

  1. 缓存穿透:Redis及DB中都未查到数据,击穿Redis穿透DB,频繁高频请求会影响DB。可以缓存一定时长有效期的空值来拦截恶意攻击请求。
  2. 缓存击穿:某一热点数据过期,短时间内请求击穿Redis,直达DB,DB峰值飙升。可以设置热点数据永不过期。
  3. 缓存雪崩:大量热点数据在短时间内几乎同时过期了,DB瞬时压力陡升爆掉。同上可以设置热点数据永不过期或有效期随机错开。

缓存中间件Redis的生产应用模式,一般有主从模式,sentinel哨兵模式,cluster集群模式。后面有空了会在服务运维专栏聊聊各种模式的搭建及测试应用。

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值