java&redis实现缓存&缓存预热

java&redis实现缓存

缓存概述

  1. 缓存:从数据库(磁盘)中取数据到前端展示,速度很慢。为了提高速度可以使用缓存,即把数据预先查出来,放到一个更快读取的介质,比如内存,不用在从数据库很慢的查。
  2. 预加载缓存:定时更新缓存,为了不让第一次使用次此系统时数据加载很慢,可以在选一个用户访问较少的时间,定时加载缓存。
  3. 分布式锁:控制同一时间只用一台机器去执行定时任务,同一份代码不用在多个机器加载缓存。
  4. 缓存的实现
    • Redis(分布式缓存)
    • Memcached(分布式缓存)
    • Etcd(云原生架构的分布式存储,公共的存储配置,扩容能力强)
    • ehcache(单机)
    • 本地缓存(java内存Map)
    • Caffeine(java内存缓存性能高)
    • Google Guava

单机缓存和分布式缓存

  • 单机本地缓存:在同一个进程内的内存空间中缓存数据,数据读写都是在同一个进程内完成;
  • 分布式缓存:一个独立部署的进程并且一般都是与应用进程部署在不同的机器,故需要通过网络来完成分布式缓存的数据读写操作的数据传输。

Redis

Redis定义:

NoSQL(非关系型数据库)
key-value键值对存储系统(区别于mysql的键值对数据库)

☆Redis的数据结构(5种基本+高级)

  1. 基本
    1. String字符串:name:“erha”
    2. List列表 : names:[“erha”,“erha02”](和数组的区别:列表的长度是不固定的,数组长度固定的)
    3. Set集合:names: [“erha”,“erha02”] (值不能重复)
    4. Hash哈希:nameAge:{“erha”:1,“haer02”:2}(键不能重复)
    5. Zset集合:names:{erha-99,erha02-100}(值从小到大排序)
  2. 高级
    1. bloomfilter(布隆过滤器,从大量的数据种快速过滤值,比如邮箱黑名单)
    2. geo(计算地理位置)
    3. hyperloglog(pv/uv)大数据统计
    4. pub/sub(发布订阅,类似消息队列)
    5. BitMap(把数据以10001110001的方式存储,存储大量可以压缩的值)

Redis在java中的实现方式(Spring Data Redis , Lettuce,Jedis 和Redisson)

不同的场景使用不同的实现方式,
1. 如果用spring框架,并且没有过多的定制化要求,可以使用spring Data Redis,最方便
2. 如果使用的不是Spring,并且追求简单,没有过高的性能要求,可以用jedis + jedis Pool
3. 如果不是spring,并且追求高性能高定制化可以使用Lettuce,支持异步,连接池
4. 如果项目是分布式的,需要用到一些分布式的特征(比如分布式锁,分布式集合),可以使用redisson
  1. 使用Spring Data Redis 实现

    1. spring Data:通用的数据访问框架,定义了一组增删改查的接口,操做mysql,redis,jpa等数据库,通过应入不同的数据库依赖实现对不同数据库的操作。
    2. 引入Redis依赖
      <dependency>
          <groupId>org.springframework.bootgroupId>
          <artifactId>spring-boot-starter-data-redisartifactId>
          <version>2.6.4version>
      dependency>
      
    3. 配置Redis地址
        # Redis 配置
        redis:
          port: 6379
          host: localhost
          database: 0
      
    4. Redis增删改查示例【如何使用Redis依赖中的增删改查接口,使用redis提供的,操作redis的类和对象】
      //操作redis的类
      @SpringBootTest
      public class RedisTest {
      
          //操作redis的对象
          @Resource
          private RedisTemplate redisTemplate;
      
          @Test
          void test(){
                  ValueOperations valueOperations = redisTemplate.opsForValue();
      	        //增
      	        valueOperations.set("erhaString","fish");
      	        valueOperations.set("erhaInt",1);
      	        valueOperations.set("erhaDouble",2.0);
      	        User user = new User();
      	        user.setId(001);
      	        user.setUsername("testRedisString");
      	        valueOperations.set("erhaUser",user);
      	        //查
      	        Object erha = valueOperations.get("erhaString");
      	        Assert.assertTrue("fish".equals((String) erha));
      	        erha = valueOperations.get("erhaInt");
      	        Assert.assertTrue(1== (int)erha);
      	        erha = valueOperations.get("erhaDouble");
      	        Assert.assertTrue(2.0 == (Double) erha);
      	        erha = valueOperations.get("erhaUser");
      	        System.out.println(valueOperations.get("erhaUser"));
      		    }			
      	}
      
      注:为了解决序列化问题,自编代码实现序列化配置
      @Configuration
      public class RedisTemplateConfig {
      
          @Bean
          public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory connectionFactory){
              RedisTemplate<String,Object> redisTemplate = new RedisTemplate<>();
              redisTemplate.setConnectionFactory(connectionFactory);
              redisTemplate.setKeySerializer(new StringRedisSerializer());
              return redisTemplate;
          }
      }
      
    5. 在项目首页推荐出提取缓存
      1. 设计缓存key,不同用户看到的数据不同。systemId:moduleId:func:options(不和别人冲突)
        lack:user:recommed:userId
      2. 用缓存实现主页推荐
        /**
         * 首页推荐接口(分页查询)
         * @param request
         * @return
         */
        @GetMapping("/recommend")
        public BaseResponse<Page<User>> recommendUsers(long pageSize,long pageNum,HttpServletRequest request) {
            //先获取当前登录对象
            User loginUser = userService.getLoginUser(request);
            ValueOperations<String, Object> valueOperations = redisTemplate.opsForValue();
            //判断有无缓存,有直接读缓存
            String redisKey = String.format("lack:user:recommed:userId:%s",loginUser.getId());
            Page<User> userPage =(Page<User>)redisTemplate.opsForValue().get(redisKey);
            if(userPage != null){
                return ResultUtils.success(userPage);
            }
            //无缓存查数据库
            QueryWrapper<User> queryWrapper = new QueryWrapper<>();
            userPage = userService.page(new Page<>(pageNum,pageSize),queryWrapper);
            //写缓存
            try {
            	//redis的内存不能无限增加,一定要设置过期时间
                valueOperations.set(redisKey,userPage,30000, TimeUnit.MILLISECONDS);
            } catch (Exception e) {
                log.error("redis set key error",e);
            }
            return ResultUtils.success(userPage);
        }
        
    6. 优化=>缓存预热
      1. 缓存预热解决的问题:

        • 让第一个触发缓存的用户也能很快获取到推荐用户【提前把第一个用户需要的数据准备好,当用户一登录立即显示即可】
        • 例如双十一这样可预期的场景,使用缓存预热降低数据库的压力,保护数据库。
      2. 缓存预热的优缺点:

        • 优点:让用户始终访问很快
        • 缺点:增加开发成本(额外开发);占用空间;如果预热时机和时间错了,有可能缓存的数据不正确。
      3. 缓存预热的意义:

        • 提高用户的访问速度
        • 保护数据库
      4. 缓存预热的注意点:

        • 缓存空间不能太大,要预留给其他缓存空间
        • 缓存数据的周期(每天一次)
      5. 实现缓存预热

        • 手动模拟触发
        • 定时触发(定时任务实现,每天刷新所有用户的推荐列表)
          1. ☆Spring Schedule(spring boot默认整合)

            1. 主类开启@EnableScheduling
            2. 给要定时执行的方法添加@Scheduled注解,编写方法
              /**
              * 缓存预热任务
              * 
              */
              @Slf4j
              @Component
              public class PreCacheJob {
              @Resource
              private UserService userService;
              @Resource
              private RedisTemplate<String, Object> redisTemplate;
              //重点用户
              private List<Long> mainUserList = Arrays.asList(2l);
              //每天执行,预热推荐用户
              @Scheduled(cron = "0 31 19 * * ? ")
              public void doCacheRecommendUser() {
              for (Long userId : mainUserList) {
              QueryWrapper<User> queryWrapper = new QueryWrapper<>();
              Page<User> userPage = userService.page(new Page<>(1, 20), queryWrapper);
              String redisKey = String.format("yupao:user:recommend:%s", userId);
              ValueOperations<String, Object> valueOperations =
              redisTemplate.opsForValue();
              //写缓存
              try {
              valueOperations.set(redisKey, userPage, 30000, TimeUnit.MILLISECONDS);
              } catch (Exception e) {
              log.error("redis set key error", e);
              		}
              }
              }
              }
              
          2. Quartz(独立于spring存在的定时任务框架)

          3. XXL-job分布式任务调度平台(界面+sdk)

  2. Jedis
    独立于spring操作redis
    redis的Java客户端

  3. Lettuce
    高阶的操作redis的java客户端

  4. 和Redisson
    分布式操作redis的java客户端,像在本地使用集合一样操作redis

你可能感兴趣的:(缓存,java,redis)