SpringBoot 集成 Spring Data Redis 及其应用

RedisTemplate 操作 Reids

代码示例-添加依赖

pom.xml 中添加 Redis 依赖

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

代码示例-StringRedisTemplate 工具类

/**
 * @Description: redis工具类
 * @Date: 2018/12/3 17:05
 * @Author: lxz
 */
@Component
public class RedisComponent {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    public boolean hasKey(String key) {
        return this.stringRedisTemplate.hasKey(key);
    }

    /**
     * 实现命令:TTL key,以秒为单位,返回给定 key的剩余生存时间(TTL, time to live)。
     *
     * @param key
     * @return
     */
    public long ttl(String key) {
        return stringRedisTemplate.getExpire(key);
    }

    /**
     * 实现命令:expire 设置过期时间,单位秒
     *
     * @param key
     * @return
     */
    public void expire(String key, long timeout) {
        stringRedisTemplate.expire(key, timeout, TimeUnit.SECONDS);
    }

    /**
     * 实现命令:INCR key,增加key一次
     *
     * @param key
     * @return
     */
    public long incr(String key, long delta) {
        return stringRedisTemplate.opsForValue().increment(key, delta);
    }

    /**
     * 实现命令:KEYS pattern,查找所有符合给定模式 pattern的 key
     */
    public Set<String> keys(String pattern) {
        return stringRedisTemplate.keys(pattern);
    }

    /**
     * 实现命令:DEL key,删除一个key
     *
     * @param key
     */
    public void del(String key) {
        stringRedisTemplate.delete(key);
    }

    // String(字符串)

    /**
     * 实现命令:SET key value,设置一个key-value(将字符串值 value关联到 key)
     *
     * @param key
     * @param value
     */
    public void set(String key, String value) {
        stringRedisTemplate.opsForValue().set(key, value);
    }

    /**
     * 实现命令:SET key value EX seconds,设置key-value和超时时间(秒)
     *
     * @param key
     * @param value
     * @param timeout (以秒为单位)
     */
    public void set(String key, String value, long timeout) {
        stringRedisTemplate.opsForValue().set(key, value, timeout, TimeUnit.SECONDS);
    }

    /**
     * 实现命令:GET key,返回 key所关联的字符串值。
     *
     * @param key
     * @return value
     */
    public String get(String key) {
        return (String)stringRedisTemplate.opsForValue().get(key);
    }

    // Hash(哈希表)

    /**
     * 实现命令:HSET key field value,将哈希表 key中的域 field的值设为 value
     *
     * @param key
     * @param field
     * @param value
     */
    public void hset(String key, String field, Object value) {
        stringRedisTemplate.opsForHash().put(key, field, value);
    }

    /**
     * 实现命令:HGET key field,返回哈希表 key中给定域 field的值
     *
     * @param key
     * @param field
     * @return
     */
    public String hget(String key, String field) {
        return (String)stringRedisTemplate.opsForHash().get(key, field);
    }

    /**
     * 实现命令:HDEL key field [field ...],删除哈希表 key 中的一个或多个指定域,不存在的域将被忽略。
     *
     * @param key
     * @param fields
     */
    public void hdel(String key, Object... fields) {
        stringRedisTemplate.opsForHash().delete(key, fields);
    }

    /**
     * 实现命令:HGETALL key,返回哈希表 key中,所有的域和值。
     *
     * @param key
     * @return
     */
    public Map<Object, Object> hgetall(String key) {
        return stringRedisTemplate.opsForHash().entries(key);
    }

    // List(列表)

    /**
     * 实现命令:LPUSH key value,将一个值 value插入到列表 key的表头
     *
     * @param key
     * @param value
     * @return 执行 LPUSH命令后,列表的长度。
     */
    public long lpush(String key, String value) {
        return stringRedisTemplate.opsForList().leftPush(key, value);
    }

    /**
     * 实现命令:LPOP key,移除并返回列表 key的头元素。
     *
     * @param key
     * @return 列表key的头元素。
     */
    public String lpop(String key) {
        return (String)stringRedisTemplate.opsForList().leftPop(key);
    }

    /**
     * 实现命令:RPUSH key value,将一个值 value插入到列表 key的表尾(最右边)。
     *
     * @param key
     * @param value
     * @return 执行 LPUSH命令后,列表的长度。
     */
    public long rpush(String key, String value) {
        return stringRedisTemplate.opsForList().rightPush(key, value);
    }
}

StringRedisTemplate 其它的操作方法

  • opsForValue:操作 Key Value 类型
  • opsForValue:操作 Key Value 类型
  • opsForList:操作 List 类型
  • opsForHash:操作 Hash 类型
  • opsForSet:操作 Set 类型
  • opsForZSet:操作 opsForZSet 类型

代码示例-StringRedisTemplate 示例

@Autowired
private RedisComponent redisComponent;

@RequestMapping("/test")
public void test() {
    redisComponent.set("stringRedisTemplate-key", "stringRedisTemplate-value");
}

Repository 操作Redis

定义一个数据存储的实体类,添加注解@RedisHash,@Id

  1. @RedisHash 是 Hash 的名称
  2. @Id 能够自动生成亦可以赋值

代码示例-数据存储实体类

/**
 * @Description: 数据存储的实体类
 * @Date: 2018/12/3 18:01
 * @Author: lxz
 */
@RedisHash("persons")
public class Person implements Serializable {
	@Id
	private Long id;
	private String firstName;
	private String lastName;

	// 注意,配置了初始化序列号记得实体类中增加无参构造,否则会报错: springdataredis Could not read JSON: Cannot construct instance of...
	public Person() {}

	public Person(Long id, String firstName, String lastName) {
		this.id = id;
		this.firstName = firstName;
		this.lastName = lastName;
	}

	public Long getId() {
		return id;
	}

	public void setId(Long id) {
		this.id = id;
	}

	public String getFirstName() {
		return firstName;
	}

	public void setFirstName(String firstName) {
		this.firstName = firstName;
	}

	public String getLastName() {
		return lastName;
	}

	public void setLastName(String lastName) {
		this.lastName = lastName;
	}

	@Override
	public String toString() {
		return "Person{" + "id=" + id + ", firstName='" + firstName + '\'' + ", lastName='" + lastName + '\'' + '}';
	}
}

代码示例-定义 Repository 接口

/**
 * @Description: Repository 接口
 * @Date: 2018/12/3 18:04
 * @Author: lxz
 */
public interface PersonRepository extends CrudRepository<Person, Long> {}

代码示例-Repository 操作redis

@RequestMapping("/test")
    public void test() {
        Person person1 = new Person((long)1, "罗 ", "小小");
        Person person2 = new Person((long)2, "罗 ", "大大");
        Person person3 = new Person((long)3, "罗 ", "超大");
        personRepository.save(person1);
        // 注意,保存到 Redis 中会变成两部分,一部分是一个set,里面存储的是所有数据的 ID值
        Object o1 = personRepository.findById(person1.getId()).get();
        // 设置过期时间。注意,设置set不会影响到下面hash值
        redisComponent.expire("persons", 20);
        System.out.println(o1.toString());
        List<Person> personList = new ArrayList<>();
        personList.add(person1);
        personList.add(person2);
        personList.add(person3);
        personRepository.saveAll(personList);
        // 设置过期时间,设置Id为1的过期时间
        redisComponent.expire("persons:1", 20);
        Object o2 = personRepository.findById(person2.getId()).get();
        System.out.println(o2.toString());
        long count = personRepository.count();
        System.out.println(count);
        List<Long> idList = new ArrayList<>();
        idList.add((long)1);
        idList.add((long)3);
        personRepository.findAllById(idList).forEach(System.out::println);
    }

数据图-Repository 操作redis

在这里插入图片描述
在这里插入图片描述

Spring Cache 利用注解方式实现缓存数据,能够使用SpEL定义缓存的Key及多种条件判断

代码示例-在启动类中添加注解@EnableCaching

@SpringBootApplication
@EnableCaching

public class MyFamilyCacheApplication {
    public static void main(String[] args) {
        SpringApplication.run(MyFamilyCacheApplication.class, args);
    }
}

常用的注解

  • @Cacheable:用于查询的时候缓存数据
  • @CachePut:用于对数据修改的时候修改缓存中的数据
  • @CacheEvict:用于对数据删除的时候清除缓存中的数据
  • @Caching 将多种缓存操作分组
  • @CacheConfig 类级别的缓存注解,允许共享缓存名称

自定义Redis的配置

  • 初始化redisTemplate,设置默认的序列化方式为 Json
  • 初始化cacheManager,设置默认的序列化方式为 Json;缓存的默认超时时间
  • 配置缓存 Key 的自动生成方式

代码示例-Redis的配置

/*****
 * @Description: redis配置文件【注意】配置了初始化序列号记得实体类中增加无参构造,否则会报错: springdataredis Could not read JSON: Cannot construct
 *               instance of...
 * @Date: 2018/12/3 17:08
 * @Author: lxz
 */
@Configuration
public class MyRedisConfig extends CachingConfigurerSupport {

    private Logger logger = LoggerFactory.getLogger(MyRedisConfig.class);

    /**
     * @Description 初始化redisTemplate,设置默认的序列化方式为 Json
     * @Params [redisConnectionFactory]
     * @Return
     * @CreateTime 2018/12/5
     * @Author lxz
     */
    @Bean(name = "redisTemplate")
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        redisTemplate.setKeySerializer(keySerializer());
        redisTemplate.setHashKeySerializer(keySerializer());
        redisTemplate.setValueSerializer(valueSerializer());
        redisTemplate.setHashValueSerializer(valueSerializer());
        return redisTemplate;
    }

    /**
     * @Description 初始化cacheManager,设置默认的序列化方式为 Json;缓存的默认超时时间
     * @Params [redisConnectionFactory]
     * @Return
     * @CreateTime 2018/12/5
     * @Author lxz
     */
    @Primary
    @Bean
    public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
        // 缓存配置对象
        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);
        RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig();
        redisCacheConfiguration = redisCacheConfiguration.entryTtl(Duration.ofMinutes(30L)) // 设置缓存的默认超时时间:30分钟
            .disableCachingNullValues() // 如果是空值,不缓存
            .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(keySerializer())) // 设置key序列化器
            .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer((valueSerializer()))); // 设置value序列化器
        return RedisCacheManager.builder(RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory))
            .cacheDefaults(redisCacheConfiguration).build();
    }

    private RedisSerializer<String> keySerializer() {
        return new StringRedisSerializer();
    }

    private RedisSerializer<Object> valueSerializer() {
        return new GenericJackson2JsonRedisSerializer();
    }

    /**
     * @Description 配置缓存 Key 的自动生成方式,这里是用类名+方法名+参数来生成缓存的 Key
     * @Params []
     * @Return
     * @CreateTime 2018/12/5
     * @Author lxz
     */
    @Override
    @Bean
    public KeyGenerator keyGenerator() {
        return new KeyGenerator() {
            @Override
            public Object generate(Object target, Method method, Object... params) {
                StringBuilder sb = new StringBuilder();
                sb.append(target.getClass().getName());
                sb.append(":" + method.getName());
                for (Object obj : params) {
                    sb.append(":" + obj.toString());
                }
                return sb.toString();
            }
        };
    }

    /**
     * @Description保证 redis 服务器出现连接等问题的时候不影响程序的正常运行, 使得能够出问题 时不用缓存, 继续执行业务逻辑去查询
     * @Params []
     * @Return
     * @CreateTime 2018/12/6
     * @Author lxz
     */
    @Override
    @Bean
    public CacheErrorHandler errorHandler() {
        // 异常处理,当Redis发生异常时,打印日志,但是程序正常走
        logger.info("初始化 -> [{}]", "Redis CacheErrorHandler");
        CacheErrorHandler cacheErrorHandler = new CacheErrorHandler() {
            @Override
            public void handleCacheGetError(RuntimeException e, Cache cache, Object key) {
                logger.error("Redis occur handleCacheGetError:key -> [{}]", key, e);
            }

            @Override
            public void handleCachePutError(RuntimeException e, Cache cache, Object key, Object value) {
                logger.error("Redis occur handleCachePutError:key -> [{}];value -> [{}]", key, value, e);
            }

            @Override
            public void handleCacheEvictError(RuntimeException e, Cache cache, Object key) {
                logger.error("Redis occur handleCacheEvictError:key -> [{}]", key, e);
            }

            @Override
            public void handleCacheClearError(RuntimeException e, Cache cache) {
                logger.error("Redis occur handleCacheClearError:", e);
            }
        };
        return cacheErrorHandler;
    }
}

代码示例-Dao层

/**
* @Description: spring Cache 缓存数据
* @Date: 2018/12/4 16:56
* @Author: lxz
*/
@Repository
@CacheConfig(cacheNames = "person")
public class PersonDao {
	
	//使用MyRedisConfig中配置的key
	//@Cacheable(value = "get", keyGenerator = "keyGenerator")
	@Cacheable(value = "get", key = "'key-' + #randomInt")
    public Person get(int randomInt) {
        Person person = new Person((long)randomInt, "我是姓" + randomInt, "我是名" + randomInt);
        return person;
    }
}

代码示例-Service层

@Autowired
private PersonDao personDao;
   public void test() {
       int randomInt = new Random().nextInt(10);
       personDao.get(randomInt);
       Person person = personDao.get(randomInt);
       System.out.println(person.toString());
   }

数据图-EnableCaching

在这里插入图片描述
在这里插入图片描述

自定义缓存工具类

  • 代码省略~~~

缓存穿透

什么是缓存穿透?

  • 缓存可以说是我们对数据库的一道保护墙,缓存穿透相当于是冲破了我们的保护墙, 每个缓存都有一个缓存的 Key, 当相同的 Key 过来时,我们就直接取缓存中的数据返回给调用方,而不用去查询数据库,如果调用方传来的永远都是我们缓存中不存在的 Key,这样每 次都需要去数据库中查询一次,就会导致数据库压力增大,这样缓存就失去意义了,这就是所谓的缓存穿透。

缓存穿透的危害

  • 当大量的请求过来时, 首先会从缓 存巾去寻找数据,当缓存中没有对应的数据时又转到了数据库中去寻找,瞬时数据库的压
    力会很大,相当于没有用到缓存,同时还增加了去缓存中查找数据的时间。

解决方案

  • 如果查询数据库也为空的时候,把这个 key 缓存起来,这样在下次请求过来的时候就可以走缓存了。 当然这种方案有个弊端,那就是请求过来的 key 必须大部分相同,如果受到攻击的话,每次的 key 肯定不是固定的,只要不固定 key,这个方案就没用。
  • 可以用缓存 k町的规则来做一些限制,当然这种只适合特定的使用场景,比如我们 查询商品信息,我们商品存储在 Mongodb 中 , Mongodb 有一个 id 是自动生成的, 它有一定的生成规则,如果是直接根据 id 查询商品,在查询之前我们可以对这个 id 做认证,看是不是符合规范,当不符合的时候就直接返回默认的值,既不用去缓存 中查询,也不用操作数据库了。 这种方案可以解决一部分问题,使用场景比较少。
  • 利用布隆过滤器来实现对缓存 key 的检验,需要将所有可能缓存的数据 Hash 到一个 足够大的 BitSet 中,在缓存之前先从布隆过滤器中判断这个 key 是否存在,然后做 对应的操作

缓存雪崩

什么是缓存雪崩?

  • 就是在某一时刻,大量缓存同时失效导致所有请求都去查询数据库,导致数 据库压力过大,然后挂掉的情况。 缓存穿透比较严重的时候也会导致缓存雪崩的发生。

缓存雪崩的危害

  • 缓存雪崩最乐观的情况是存储层能抗住,但是用户体验会受到影响,数据返回慢,当压力过大时会导致存储层直接挂掉,整个系统都受影响。 对于要做到 99.99% 高可用的产品,是绝对不允许缓存雪崩的发生。

解决方案

  • 缓存存储高可用, 比如 Redis 集群,这样就能防止某台 Redis 挂掉之后所有缓存丢失 导致的雪崩问题。
  • 缓存失效时间要设计好,不同的数据有不同的有效期,尽量保证不要在同一时间失 效,统一去规划有效期,让失效时间分布均匀即可。
  • 对于一些热门数据的持续读取,这种缓存数据也可以采取定时更新的方式来刷新缓 存,避免自动失效。
  • 服务限流和接口限流,如果服务和接口都有限流机制,就算缓存全部失效了,但是 请求的总量是有限制的,可以在承受范围之内,这样短时间内系统响应慢点,但不 至于挂掉,影响整个系统。
  • 从数据库获取缓存需要的数据时加锁控制 ,本地锁或者分布式锁都可以。 当所有请 求都不能命中缓存,这就是我们之前讲的缓存穿透,这时候要去数据库中查询,如 果同时并发的量大,也是会导致雪崩的发生,我们可以在对数据库查询的地方进行 加锁控制,不要让所有请求都过去,这样可以保证存储服务不挂掉。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值