项目实战(Redis数据库应用)

61. 在项目中使用Redis

由于Redis的存取效率非常高,在开发实践中,通常会将一些数据从关系型数据库(例如MySQL)中读取出来,并写入到Redis中,后续,当需要访问相关数据时,将优先从Redis中读取所需的数据,以此,可以提高数据的读取效率,并且,对一定程度的保护关系型数据库。

一旦使用Redis后,相关的数据就会同时存在于关系型数据和Redis中,即同一个数据有2份或更多(如果你使用了更多的Redis服务或其它数据处理技术),则可能出现数据不同步的问题!例如,当修改了或删除了关系型数据库中的数据,那Redis中的数据应该如何处理?同时更新?还是无视数据的变化?如果最终出现了关系型数据库和Redis中的数据不同的问题,则称之为“数据一致性问题”。

关于数据可能存在不一致的问题,首先,你必须知道,并不是所有的数据都必须同步,也就是说,当关系型数据库中的数据变化后,如果Redis中的数据没有同步发生变化,则Redis中的数据可以视为是“不准确的”,这个问题在许多应用场景中是可以接受的!例如热门话题的排行榜,或车票的余票数量、商品的库存余量等。

通常,应该Redis的前提应该是:

  • 高频率访问的数据
    • 例如热门榜单
  • 修改频率非常低的数据
    • 例如电商平台中商品的类别
  • 对数据的“准确性”(一致性)要求不高的
    • 例如商品的库存余量

62. 应用Redis

在项目中应用Redis主要需要实现:

  • 将数据从MySQL中读出
    • 已经由Mapper实现
  • 【XX时】向Redis中写入
  • 当需要读取数据时,将原本的从MySQL中读取数据改为从Redis中读取

推荐创建专门用于读写Redis的组件,则在项目的根包下创建repo.IBrandRedisRepository接口:

public interface IBrandRedisRepository {}

并在项目的根包下创建repo.impl.BrandRedisRepositoryImpl类,实现以上接口,并在类上添加@Repository注解:

@Repository
public class BrandRedisRepositoryImpl implements IBrandRedisRepository {}

然后,在IBrandRedisRepository接口中添加抽象方法:

package cn.tedu.csmall.product.repo;

import cn.tedu.csmall.product.pojo.vo.BrandListItemVO;
import cn.tedu.csmall.product.pojo.vo.BrandStandardVO;

import java.util.List;

/**
 * 处理品牌缓存的数据访问接口
 *
 * @author java@tedu.cn
 * @version 0.0.1
 */
public interface IBrandRedisRepository {

    /**
     * 品牌数据项在Redis中的Key前缀
     */
    String BRAND_ITEM_KEY_PREFIX = "brand:item:";
    /**
     * 品牌列表在Redis中的Key
     */
    String BRAND_LIST_KEY = "brand:list";

    /**
     * 向Redis中写入品牌数据
     *
     * @param brandStandardVO 品牌数据
     */
    void save(BrandStandardVO brandStandardVO);

    /**
     * 向Redis中写入品牌列表
     *
     * @param brands 品牌列表
     */
    void save(List<BrandListItemVO> brands);

    /**
     * 从Redis中读取品牌数据
     *
     * @param id 品牌id
     * @return 匹配的品牌数据,如果没有匹配的数据,则返回null
     */
    BrandStandardVO get(Long id);

    /**
     * 从Redis中读取品牌列表
     *
     * @return 品牌列表
     */
    List<BrandListItemVO> list();

    /**
     * 从Redis中读取品牌列表
     *
     * @param start 读取数据的起始下标
     * @param end   读取数据的截止下标
     * @return 品牌列表
     */
    List<BrandListItemVO> list(long start, long end);

}

并在BrandRedisRepositoryImpl中实现以上方法:

package cn.tedu.csmall.product.repo.impl;

import cn.tedu.csmall.product.pojo.vo.BrandListItemVO;
import cn.tedu.csmall.product.pojo.vo.BrandStandardVO;
import cn.tedu.csmall.product.repo.IBrandRedisRepository;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.ListOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Repository;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

/**
 * 处理品牌缓存的数据访问实现类
 *
 * @author java@tedu.cn
 * @version 0.0.1
 */
@Slf4j
@Repository
public class BrandRedisRepositoryImpl implements IBrandRedisRepository {

    @Autowired
    RedisTemplate<String, Serializable> redisTemplate;

    public BrandRedisRepositoryImpl() {
        log.debug("创建处理缓存的数据访问对象:BrandRedisRepositoryImpl");
    }

    @Override
    public void save(BrandStandardVO brandStandardVO) {
        log.debug("准备向Redis中写入数据:{}", brandStandardVO);
        String key = getItemKey(brandStandardVO.getId());
        redisTemplate.opsForValue().set(key, brandStandardVO);
    }

    @Override
    public void save(List<BrandListItemVO> brands) {
        String key = getListKey();
        ListOperations<String, Serializable> ops = redisTemplate.opsForList();
        for (BrandListItemVO brand : brands) {
            ops.rightPush(key, brand);
        }
    }

    @Override
    public BrandStandardVO get(Long id) {
        String key = getItemKey(id);
        Serializable serializable = redisTemplate.opsForValue().get(key);
        if (serializable != null) {
            if (serializable instanceof BrandStandardVO) {
                return (BrandStandardVO) serializable;
            }
        }
        return null;
    }

    @Override
    public List<BrandListItemVO> list() {
        long start = 0;
        long end = -1;
        return list(start, end);
    }

    @Override
    public List<BrandListItemVO> list(long start, long end) {
        String key = getListKey();
        ListOperations<String, Serializable> ops = redisTemplate.opsForList();
        List<Serializable> list = ops.range(key, start, end);
        List<BrandListItemVO> brands = new ArrayList<>();
        for (Serializable item : list) {
            brands.add((BrandListItemVO) item);
        }
        return brands;
    }

    private String getItemKey(Long id) {
        return BRAND_ITEM_KEY_PREFIX + id;
    }

    private String getListKey() {
        return BRAND_LIST_KEY;
    }

}

完成后,在src/test/java的根包下创建repo.BrandRedisRepositoryTests测试类,编写并执行测试:

package cn.tedu.csmall.product.repo;

import cn.tedu.csmall.product.pojo.entity.Brand;
import cn.tedu.csmall.product.pojo.vo.BrandListItemVO;
import cn.tedu.csmall.product.pojo.vo.BrandStandardVO;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.ArrayList;
import java.util.List;

@Slf4j
@SpringBootTest
public class BrandRedisRepositoryTests {

    @Autowired
    IBrandRedisRepository repository;

    @Test
    void testSave() {
        BrandStandardVO brand = new BrandStandardVO();
        brand.setId(1L);
        brand.setName("华为");

        repository.save(brand);
        log.debug("向Redis中写入数据完成!");
    }

    @Test
    void testSaveList() {
        List<BrandListItemVO> brands = new ArrayList<>();
        for (int i = 1; i <= 8; i++) {
            BrandListItemVO brand = new BrandListItemVO();
            brand.setId(i + 0L);
            brand.setName("测试品牌" + i);
            brands.add(brand);
        }

        repository.save(brands);
        log.debug("向Redis中写入列表数据完成!");
    }

    @Test
    void testGet() {
        Long id = 10000L;
        Object result = repository.get(id);
        log.debug("从Redis中读取【id={}】的数据,结果:{}", id, result);
    }

    @Test
    void testList() {
        List<?> list = repository.list();
        log.debug("从Redis中读取列表,列表中的数据的数量:{}", list.size());
        for (Object item : list) {
            log.debug("{}", item);
        }
    }

    @Test
    void testListRange() {
        long start = 2;
        long end = 5;
        List<?> list = repository.list(start, end);
        log.debug("从Redis中读取列表,列表中的数据的数量:{}", list.size());
        for (Object item : list) {
            log.debug("{}", item);
        }
    }

}

关于在项目中应用Redis,首先考虑何时将MySQL中的数据读取出来并写入到Redis中!常见的策略有:

  • 直接尝试从Redis中读取数据,如果Redis中无此数据,则从MySQL中读取并写入到Redis
    • 从运行机制上,类似单例模式中的懒汉式
  • 当项目启动时,就直接从MySQL中读取数据并写入到Redis
    • 从运行机制上,类似单例模式中的饿汉式
    • 这种做法通常称之为“缓存预热”

当使用缓存预热的处理机制时,需要使得某段代码是项目启动时就自动执行的,可以自定义组件类实现AppliacationRunner接口,重写其中的run()方法,此方法将在项目启动完成之后自动调用。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

专注摸鱼的汪

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值