第017课:Spring Boot 集成Redis

1、Redis介绍

Redis 是一个速度非常快的非关系数据库(non-relational database),它可以存储键(key)与 5 种不同类型的值(value)之间的映射(mapping),可以将存储在内存的键值对数据持久化到硬盘,可以使用复制特性来扩展读性能,还可以使用客户端分片来扩展写性能。

为了满足高性能,Redis 采用内存(in-memory)数据集(dataset)。根据使用场景,可以通过每隔一段时间转储数据集到磁盘,或者追加每条命令到日志来持久化。持久化也可以被禁用,如果只是需要一个功能丰富,网络化的内存缓存。

Redis 是一款非常优秀的高性能缓存中间件,被广泛的使用在各互联网公司中,Spring Boot 对 Redis 的操作提供了很多的支持,让我们可以非常方便的去集成。Redis 拥有丰富的数据类型,方便我们在不同的业务场景中去使用,特别是提供了很多内置的高效集合操作,非常方便我们在业务中使用。

2、安装Redis

本节课程将在windows上安装redis,并做演示。首先到官网上下载redis安装包,安装即可(安装步骤请问度娘)

开启

安装完后启动redis:控制台进入到安装目录,输入:.\redis-server.exe

 

控制台运行

控制台输入:redis-cli.exe -h 127.0.0.1 -p 6379

 

控制台使用

 

3、数据模型

数据模型包括:字符串、哈希、列表、集合、有序集合五种

list列表

Redis list 的应用场景非常多,也是 Redis 最重要的数据结构之一。使用 List 可以轻松的实现一个队列,List 典型的应用场景就是消息队列,可以利用 list 的 PUSH 操作,将任务存在 list 中,然后工作线程再用 POP 操作将任务取出进行执行。

Redis list 的实现为一个双向链表,即可以支持反向查找和遍历,更方便操作,不过带来了部分额外的内存开销,Redis 内部的很多实现,包括发送缓冲队列等也都是用的这个数据结构。

set集合

Redis set 对外提供的功能与 list 类似是一个列表的功能,特殊之处在于 set 是可以自动排重的,当需要存储一个列表数据,又不希望出现重复数据时,set 是一个很好的选择,并且 set 提供了判断某个成员是否在一个 set 集合内的重要接口,这个也是 list 所不能提供的。

set 的内部实现是一个 value 永远为 null 的 HashMap,实际就是通过计算 hash 的方式来快速排重,这也是 set 能提供判断一个成员是否在集合内的原因。

zset有序集合

Redis sorted set 的内部使用 HashMap 和跳跃表(SkipList)来保证数据的存储和有序,HashMap 里放的是成员到 score 的映射,而跳跃表里存放的是所有的成员,排序依据是 HashMap 里存的 score,使用跳跃表的结构可以获得比较高的查找效率,并且在实现上比较简单。

4、注解

三个注解:@Cacheable、@CacheEvict、@CachePut

  • @Cacheable (是用来声明方法是可缓存的)

当执行到一个被 @Cacheable 注解的方法时,Spring 首先检查 condition 条件是否满足,如果不满足,执行方法,返回;如果满足,在缓存空间中查找使用 key 存储的对象,如果找到,将找到的结果返回,如果没有找到执行方法,将方法的返回值以 key-value 对象的方式存入缓存中,然后方法返回。

  • @CachePut (保证缓存中的数据和数据库同步)

与 @Cacheable 不同的是使用 @CachePut 标注的方法在执行前,不会去检查缓存中是否存在之前执行过的结果,而是每次都会执行该方法,并将执行结果以键值对的形式存入指定的缓存中。

  • @CacheEvict (Spring 会在调用该方法之前清除缓存中的指定元素)

用来标注在需要清除缓存元素的方法或类上的。当标记在一个类上时表示其中所有的方法的执行都会触发缓存的清除操作。

属性:value、key、condition、allEntries 和 beforeInvocation。其中 value、key 和 condition 的语义与 @Cacheable 对应的属性类似。即 value 表示清除操作是发生在哪些 Cache 上的(对应 Cache 的名称);key 表示需要清除的是哪个 key,如未指定则会使用默认策略生成的 key;condition 表示清除操作发生的条件。allEntries 是 boolean 类型,表示是否需要清除缓存中的所有元素。beforeInvocation为当我们指定该属性值为 true 时,Spring 会在调用该方法之前清除缓存中的指定元素。

5、springbooat集成redis

5.1 引入依赖

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

5.2 application配置

# 服务端口号
server:
  port: 8080

context-path: /

spring:
  application:
    name: springboot-redis
  # 数据库配置
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/db_redis?useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true&useSSL=false&serverTimezone=Asia/Shanghai
    username: root
    password: 123456
    hikari:
      maximum-pool-size: 10 # 最大连接池数
      max-lifetime: 1770000
  #redis相关配置
  redis:
    database: 0
    # 配置redis的主机地址
    host: 127.0.0.1
    port: 6379
    password:
    timeout: 5000
    jedis:
      pool:
        # 连接池中的最大空闲连接,默认值是8。
        max-idle: 500
        # 连接池中的最小空闲连接,默认值是0。
        min-idle: 50
        # 如果赋值为-1,则表示不限制;如果pool已经分配了maxActive个jedis实例,则此时pool的状态为exhausted(耗尽)
        max-active: 1000
        # 等待可用连接的最大时间,单位毫秒,默认值为-1,表示永不超时。如果超过等待时间,则直接抛出JedisConnectionException
        max-wait: 2000

mybatis:
  # 指定别名设置的包为所有entity
  type-aliases-package: com.bowen.entity
  configuration:
    map-underscore-to-camel-case: true # 驼峰命名规范
  mapper-locations: # mapper映射文件位置
    - classpath:mapper/*.xml

5.3 缓存配置

在这里我们可以为 Redis 设置一些全局配置,比如配置主键的生产策略 KeyGenerator,如不配置会默认使用参数名作为主键。

/**
 * <h3>springboot-study</h3>
 * <p>缓存配置</p>
 * @author : zhang.bw
 * @date : 2020-07-17 21:37
 **/
@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport {

    @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();
            }
        };
    }

    @Bean
    public CacheManager cacheManager(RedisConnectionFactory factory) {
        RedisCacheManager rcm = RedisCacheManager.builder(factory).build();
        //设置缓存过期时间
        //rcm.setDefaultExpiration(60);//秒
        return rcm;
    }
}

注意:我们使用了注解:@EnableCaching来开启缓存。在配置 CacheManager 的方法中,也可以配置缓存默认的过期时间。

5.4 使用(注入RedisTemplate)

/**
 * <h3>springboot-study</h3>
 * <p>调用redisTemplate</p>
 * @author : zhang.bw
 * @date : 2020-07-17 21:37
 **/
@RunWith(SpringRunner.class)
@SpringBootTest
public class TestRedisTemplate {

    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * String缓存存取测试
     */
    @Test
    public void testString()  {
        redisTemplate.opsForValue().set("bowen", "公众号【猿码天地】");
        redisTemplate.opsForValue().set("bowen", "hello redis!");
        System.out.println(redisTemplate.opsForValue().get("bowen"));
    }
}

在这个单元测试中,使用redisTemplate,存储了一个字符串"公众号【猿码天地】",存储之后获取进行验证。多次进行 set 相同的 key,键对应的值会被覆盖。

总结:

从上面的整个流程来看,使用spring-boot-starter-data-redis只需要三步(引入依赖包、application配置、缓存配置-全局配置&主键生产策略&过期时间等、使用-注入RedisTemplate)就可以快速的集成 Redis 进行操作。

5.5 对redisTemplate进行封装

为方便实际生产应用,我们可以对redisTemplate进行封装,在使用时直接注入封装好的bean即可。

/**
 * <h3>springboot-study</h3>
 * <p>对redisTemplate进行封装</p>
 * @author : zhang.bw
 * @date : 2020-07-17 21:37
 **/
@Service
public class RedisService {

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

    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * set value
     * @param key
     * @param value
     * @return
     */
    public boolean set(final String key, Object value) {
        boolean result = false;
        try {
            ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue();
            operations.set(key, value);
            result = true;
        } catch (Exception e) {
            logger.error("set error: key {}, value {}",key,value,e);
        }
        return result;
    }

    /**
     * set value with expireTime
     * @param key
     * @param value
     * @param expireTime
     * @return
     */
    public boolean set(final String key, Object value, Long expireTime) {
        boolean result = false;
        try {
            ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue();
            operations.set(key, value);
            redisTemplate.expire(key, expireTime, TimeUnit.SECONDS);
            result = true;
        } catch (Exception e) {
            logger.error("set error: key {}, value {},expireTime {}",key,value,expireTime,e);
        }
        return result;
    }

    /**
     * @param key
     * @return
     */
    public boolean exists(final String key) {
        return redisTemplate.hasKey(key);
    }

    /**
     * @param key
     * @return
     */
    public Object get(final String key) {
        Object result = null;
        ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue();
        result = operations.get(key);
        return result;
    }

    /**
     * remove single key
     * @param key
     */
    public void remove(final String key) {
        if (exists(key)) {
            redisTemplate.delete(key);
        }
    }

    /**
     * batch delete
     * @param keys
     */
    public void remove(final String... keys) {
        for (String key : keys) {
            remove(key);
        }
    }

    /**
     * batch delete with pattern
     * @param pattern
     */
    public void removePattern(final String pattern) {
        Set<Serializable> keys = redisTemplate.keys(pattern);
        if (keys.size() > 0) {
            redisTemplate.delete(keys);
        }
    }

    /**
     * hash set
     * @param key
     * @param hashKey
     * @param value
     */
    public void hashSet(String key, Object hashKey, Object value){
        HashOperations<String, Object, Object> hash = redisTemplate.opsForHash();
        hash.put(key,hashKey,value);
    }

    /**
     * hash get
     * @param key
     * @param hashKey
     * @return
     */
    public Object hashGet(String key, Object hashKey){
        HashOperations<String, Object, Object> hash = redisTemplate.opsForHash();
        return hash.get(key,hashKey);
    }

    /**
     *  list push
     * @param k
     * @param v
     */
    public void push(String k,Object v){
        ListOperations<String, Object> list = redisTemplate.opsForList();
        list.rightPush(k,v);
    }

    /**
     *  list range
     * @param k
     * @param l
     * @param l1
     * @return
     */
    public List<Object> range(String k, long l, long l1){
        ListOperations<String, Object> list = redisTemplate.opsForList();
        return list.range(k,l,l1);
    }

    /**
     *  set add
     * @param key
     * @param value
     */
    public void setAdd(String key,Object value){
        SetOperations<String, Object> set = redisTemplate.opsForSet();
        set.add(key,value);
    }

    /**
     * set get
     * @param key
     * @return
     */
    public Set<Object> setMembers(String key){
        SetOperations<String, Object> set = redisTemplate.opsForSet();
        return set.members(key);
    }

    /**
     * ordered set add
     * @param key
     * @param value
     * @param scoure
     */
    public void zAdd(String key,Object value,double scoure){
        ZSetOperations<String, Object> zset = redisTemplate.opsForZSet();
        zset.add(key,value,scoure);
    }

    /**
     * rangeByScore
     * @param key
     * @param scoure
     * @param scoure1
     * @return
     */
    public Set<Object> rangeByScore(String key,double scoure,double scoure1){
        ZSetOperations<String, Object> zset = redisTemplate.opsForZSet();
        return zset.rangeByScore(key, scoure, scoure1);
    }

}

使用测试:

新建TestRedisService测试类,注入封装好的RedisService,分别对String缓存存取测试、Object缓存存取测试。发现可以正常存取缓存数据,测试成功。

/**
 * <h3>springboot-study</h3>
 * <p>调用封装后的redisTemplate</p>
 * @author : zhang.bw
 * @date : 2020-07-17 21:37
 **/
@RunWith(SpringRunner.class)
@SpringBootTest
public class TestRedisService {

    @Autowired
    private RedisService redisService;

    /**
     * String缓存存取测试
     * @throws Exception
     */
    @Test
    public void testString() throws Exception {
        redisService.set("bowen", "公众号【猿码天地】");
        System.out.println(redisService.get("bowen"));
    }

    /**
     * Object缓存存取测试
     * @throws Exception
     */
    @Test
    public void testObj() throws Exception {
        User user= new User(1L, "猿码天地", "公众号【猿码天地】");
        redisService.set("user",user);
        User result=(User) redisService.get("user");
        System.out.println(result.toString());
    }

}

6、使用redis常见问题

缓存数据库的双写一致性的问题

 

缓存雪崩问题

 

缓存穿透问题

 

缓存的并发竞争问题

 

7、总结

本节课程主要对redis进行了介绍,包括:安装Redis、数据模型、注解、springbooat集成redis并使用、使用redis常见问题。本课程主要是起到抛砖引玉的作用,希望各位网友能认真消化,并不断探索redis的奥秘。

源码下载地址:关注公众号【猿码天地】并回复 springboot 获取

文章推荐

第014课:Spring Boot 事务管理

第015课:Spring Boot 监听器

第012课:Spring Boot 全局异常处理

第013课:Spring Boot 切面AOP处理

第011课:Spring Boot 集成Thymeleaf模板引擎

第016课:Spring Boot 拦截器

第008课:Spring Boot 返回JSON数据格式封装

第010课:Spring Boot 集成Swagger接口文档

第009课:Spring Boot 使用slf4j日志记录

第006课:Spring Boot集成MyBatis

第007课:Spring Boot MyBatis Druid 多数据源配置

第004课:Spring Boot 项目属性配置

第005课:Spring Boot 中MVC支持

第003课:Spring Boot 快速体验 Web 开发

扫描二维码关注公众号 : 猿码天地

你多学一样本事,就少说一句求人的话,现在的努力,是为了以后的不求别人,实力是最强的底气。记住,活着不是靠泪水博得同情,而是靠汗水赢得掌声。

——《写给程序员朋友》

猜你喜欢

转载自blog.csdn.net/zbw125/article/details/107925829
今日推荐