Redis缓存+DB方式实现app或者小程序上展示文章的分页(redis分页)

业务介绍
新闻头条查看小程序、用户以浏览为主,由于用户读取较多,考虑到数据库压力。小程序各分类下的文章前500 篇缓存(根据业务自定义最大值),如果缓存中存在则直接从缓存中取。若不存在则取自DB,并根据是否在自定义最大值区间内判断是否需要更新到缓存

图示功能类似在这里插入图片描述

注:省略Redis整合的代码详情见之前发的Redis相关的文章

文章实体类

/**
 * 文章实体类
 */
public class TArticle implements Serializable {

    private static final long serialVersionUID = 1L;

    /**
     * 文章id
     */
    private String articleId;

    /**
     * 文章标题
     */
    private String articleTitle;

    /**
     * 文章作者
     */
    private String articleAuthor;

    /**
     * 文章内容
     */
    private String articleContent;

    /**
     * 排序分值,分值越大 越靠前
     */
    private Double sore;

    /**
     * 状态 0:删除 1:上架中 2:待上架
     */
    private Integer status;

    /**
     * 新建时间
     */
    private Date createTime;

    /**
     * 更新时间
     */
    private Date updateTime;
	//省略get/set方法
}

常量类

/**
 * 常量
 */
public class DemoConstant {
    //文章缓存Key前缀
    public static String ARTICLE_LIST_KEY = "article_list_key_";
}

文章新增

注:文章新增过程保存到db同时,更新到缓存
流程图
在这里插入图片描述

文章新增参数BO

/**
 * 文章保存 参数 bo
 */
public class TArticleSveBO implements Serializable {
    private static final long serialVersionUID = 1L;
    /**
     * 文章标题
     */
    private String articleTitle;
    /**
     * 文章作者
     */
    private String articleAuthor;
    /**
     * 文章内容
     */
    private String articleContent;

    /**
     * 排序分值
     * 一般使用程序生成,目前演示就通过参数传了
     */
    private Double sore;
    /**
     * 所属分类 id
     * 同一片文章可以归属多个分类
     */
    private List<String> categoryIds;
  
  //省略 get/set方法
	
}

文章新增接口实现

import com.boot.redis.constant.DemoConstant;
import com.boot.redis.jredis.RedisCache;
import com.boot.redis.persistence.bo.TArticleSveBO;
import com.boot.redis.persistence.entity.TArticle;
import com.boot.redis.service.TArticlePutService;
import com.boot.redis.util.DemoDateUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import java.util.Date;
/**
 * 文章新增并且新增缓存
 */
@Service
public class TArticlePutServiceImpl implements TArticlePutService {
    private Logger LOGGER = LoggerFactory.getLogger(TArticlePutServiceImpl.class);
    @Autowired
    private RedisCache redisCache;
    /**
     * 自定义最大值
     */
    private static final Integer MAX_COUNT = 50;
    /**
     * 保存文章
     * 并且保存有序集合到redis
     *
     * @param sveBO 文章保存参数bo
     */
    @Override
    public void saveArticle(TArticleSveBO sveBO) {
        if (CollectionUtils.isEmpty(sveBO.getCategoryIds())) {
            throw new RuntimeException("所属分类不为空");
        }
        TArticle tArticle = this.buildTArticle(sveBO);
        //保存到db
        LOGGER.info("此处省略保存到db的操作");
        //按照不同的分类 进行保存
        //保持同一分类下的缓存文章不超过 最大值
        for (String str : sveBO.getCategoryIds()) {
            //key=固定key+分类id
            String key = DemoConstant.ARTICLE_LIST_KEY.concat(str);
            //保存到redis
            redisCache.zAddByScore(key, tArticle, tArticle.getSore());
            //如果超出文章缓存保存最大值则移除超出最大长度的文章
            this.removeCacheOver(key);
        }
    }
    /**
     * 如果超出文章缓存保存最大值
     * 按照分值移除分值小的
     */
    private void removeCacheOver(String key) {
        int currentCount = (int) redisCache.zCard(key);
        LOGGER.info("当前集合总长度 currentCount={}", currentCount);
        if (currentCount > MAX_COUNT) {
            LOGGER.info("超出总长度执行删除");
            int dValue = currentCount - MAX_COUNT;
            LOGGER.info("超出部分移除 0-{}", dValue);
            redisCache.zRemRangeByRank(key, 0, dValue);
        }
    }
    /**
     * 构建文章实体
     */
    private TArticle buildTArticle(TArticleSveBO sveBO) {
        //模拟数据
        TArticle tArticle = new TArticle();
        tArticle.setArticleId(String.valueOf(sveBO.getSore().intValue()));
        tArticle.setArticleAuthor(sveBO.getArticleAuthor());
        tArticle.setArticleTitle(sveBO.getArticleTitle());
        tArticle.setArticleContent(sveBO.getArticleContent());
        //这里演示状态就写死了
        tArticle.setStatus(1);
        //业务中可以自定义排序分值,或者使用自增id也可以
        tArticle.setSore(sveBO.getSore());
        tArticle.setCreateTime(DemoDateUtil.endOfDay(new Date()));
        tArticle.setUpdateTime(DemoDateUtil.endOfDay(new Date()));
        return tArticle;
    }
}

新增文章测试

@SpringBootTest
@RunWith(SpringRunner.class)
public class ArticlePutTest {
    private Logger LOGGER = LoggerFactory.getLogger(ArticlePutTest.class);
    @Autowired
    private TArticlePutService tArticlePutService;
    /**
     * 测试文章新增 并保存到Redis
     */
    @Test
    public void testAddArticle() {
        List<String> categoryIds = new ArrayList<>();
        categoryIds.add("1001");
        categoryIds.add("1002");
        categoryIds.add("1003");
        for (int i = 0; i < 50; i++) {
            TArticleSveBO sveBO = new TArticleSveBO();
            sveBO.setArticleAuthor("作者 " + String.valueOf(i));
            sveBO.setArticleTitle("标题 " + String.valueOf(i));
            sveBO.setArticleContent("文章正文 " + String.valueOf(i));
            sveBO.setCategoryIds(categoryIds);
            //模拟分值
            sveBO.setSore(Double.valueOf(i));
            tArticlePutService.saveArticle(sveBO);
        }
    }
}

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

文章程序端查询

分页查询参数BO

/**
 * 文章分页查询 参数 bo
 */
public class TArticlePageQueryBO implements Serializable {
    private static final long serialVersionUID = 1L;
    /**
     * 所属分类id
     */
    private String categoryId;
    /**
     * 当前页
     */
    private Integer pageNo;
    /**
     * 页容量
     */
    private Integer pageSize
	//省略get/set方法
}

文章查询

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

文章查询接口实现

import com.boot.redis.constant.DemoConstant;
import com.boot.redis.jredis.RedisCache;
import com.boot.redis.persistence.bo.TArticlePageQueryBO;
import com.boot.redis.persistence.entity.TArticle;
import com.boot.redis.service.TArticleGetService;
import com.boot.redis.util.DemoDateUtil;
import com.boot.redis.util.PageUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 文章获取接口
 */
@Service
public class TArticleGetServiceImpl implements TArticleGetService {
    private Logger LOGGER = LoggerFactory.getLogger(TArticleGetServiceImpl.class);
    /**
     * 自定义缓存中存放最大值
     */
    private static final int MAX_COUNT = 50;
    /**
     * 公平锁
     */
    private ReentrantLock articleListLock = new ReentrantLock();
    @Autowired
    private RedisCache redisCache;

    /**
     * 分页获取文章信息
     * 注:由于小程序 不需要返回分页信息总total,数据是下滑翻页的
     *
     * @param queryBO 文章查询bo
     * @return 文章集合
     */
    @Override
    public List<TArticle> getArticleList(TArticlePageQueryBO queryBO) {
        int currentNo = queryBO.getPageNo();
        int pageSize = queryBO.getPageSize();
        String categoryId = queryBO.getCategoryId();

        //计算开始位置
        int start = PageUtil.getStart(currentNo, pageSize);
        //计算结束位置
        int end = PageUtil.getEnd(start, pageSize);
        LOGGER.info("start={},end={}", start, end);
        //判断缓存中是否存在
        boolean isExistCache = end < MAX_COUNT;
        if (isExistCache) {
            LOGGER.info("未超过缓存最大值尝试缓存中提取数据");
            return this.getArticleFromCache(categoryId, start, end);
        }
        LOGGER.info("最大值超出缓存值则从缓存中提取数据");
        return this.getArticleFromDb(categoryId, currentNo, pageSize);
    }
    /**
     * 缓存中提取
     */
    private List<TArticle> getArticleFromCache(String categoryId, int start, int end) {
        //key=固定key+分类id
        String key = DemoConstant.ARTICLE_LIST_KEY.concat(categoryId);
        LOGGER.info("缓存key={}", key);
        List<TArticle> articleDOList = redisCache.zRevRange(key, TArticle.class, start, end);
        //判断缓存中是否存在
        if (!CollectionUtils.isEmpty(articleDOList)) {
            LOGGER.info("缓存中存在直接返回缓存中的数据 size={}", articleDOList.size());
            return articleDOList;
        }
        //添加锁允许单线程访问
        articleListLock.lock();
        try {
            //尝试再次从缓存中提取
            //防止其它用户访问后已经添加到缓存中
            articleDOList = redisCache.zRevRange(key, TArticle.class, start, end);
            //判断缓存中是否存在
            if (!CollectionUtils.isEmpty(articleDOList)) {
                LOGGER.info("再次尝试缓存中获取");
                LOGGER.info("缓存中存在直接返回缓存中的数据 size={}",
                        articleDOList.size());
                return articleDOList;
            }
            LOGGER.info("缓存中不存在查询db");
            articleDOList = this.getArticleFromDb(categoryId, start, end);
            //保存到缓存
            this.doSaveToCache(categoryId, articleDOList);
            return articleDOList;
        } finally {
            articleListLock.unlock();
        }
    }
    /**
     * 保存到缓存
     */
    private void doSaveToCache(String categoryId, List<TArticle> articleDbList) {
        LOGGER.info("执行保存到缓存开始");
        if (CollectionUtils.isEmpty(articleDbList)) {
            return;
        }
        //key=固定key+分类id
        String key = DemoConstant.ARTICLE_LIST_KEY.concat(categoryId);
        for (TArticle tArticle : articleDbList) {
            //保存到redis
            redisCache.zAddByScore(key, tArticle, tArticle.getSore());
        }
        LOGGER.info("当前集合总长度 currentCount={}", redisCache.zCard(key));
        LOGGER.info("执行保存到缓存结束");
    }

    /**
     * 从 db 中取数据
     * 此处做模拟数据
     */
    private List<TArticle> getArticleFromDb(String categoryId, int currentNo, int pageSize) {
        LOGGER.info("从 db 中取数据 currentNo={},pageSize={}", currentNo, pageSize);
        List<TArticle> articleDOList = new ArrayList<>();
        //计算页数 按照分数倒叙
        int start = MAX_COUNT - (currentNo * pageSize);
        int end = MAX_COUNT / currentNo - 1;
        System.out.println("模拟数据 start" + start + " end" + end);
        //模拟数据
        TArticle tArticle;
        //倒叙
        for (int i = start; i <= end; i++) {
            tArticle = new TArticle();
            tArticle.setArticleId(String.valueOf(i));
            tArticle.setArticleAuthor("作者 " + i);
            tArticle.setArticleTitle("标题 " + i);
            tArticle.setArticleContent("文章正文 " + i);
            tArticle.setStatus(1);
            tArticle.setSore(Double.valueOf(i));
            tArticle.setCreateTime(DemoDateUtil.endOfDay(new Date()));
            tArticle.setUpdateTime(DemoDateUtil.endOfDay(new Date()));
            articleDOList.add(tArticle);
        }
        return articleDOList;
    }
}

查询文章测试

@SpringBootTest
@RunWith(SpringRunner.class)
public class ArticleGetTest {
    private Logger LOGGER = LoggerFactory.getLogger(ArticleGetTest.class);
    @Autowired
    private TArticleGetService tArticleGetService;
    @Autowired
    private RedisCache redisCache;

    @Test
    public void testGetArticle() {
        String categoryId = "1001";

        //key=固定key+分类id
        String key = DemoConstant.ARTICLE_LIST_KEY.concat(categoryId);
        //获取目前集合长度
        int currentCount = (int) redisCache.zCard(key);
        System.out.println("目前长度 size={}" + currentCount);

        TArticlePageQueryBO queryBO = new TArticlePageQueryBO();
        queryBO.setCategoryId(categoryId);
        queryBO.setPageNo(5);
        queryBO.setPageSize(10);
        List<TArticle> tArticleList =
                tArticleGetService.getArticleList(queryBO);
        if (!CollectionUtils.isEmpty(tArticleList)) {
            for (TArticle tArticle : tArticleList) {
                System.out.println(tArticle);
            }
        }
    }
}

输出结果
注:按照分值从大到小查询,第五页 即下标40-49
在这里插入图片描述

转载于:https://www.cnblogs.com/mengq0815/p/10596082.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值