redis订阅和发布使用场景,基于redis来实现博客订阅量排名

2020-04-27 18:22栏目:编程
TAG: java

排行榜功能是一个很普遍的需求。
设想在一个游戏中,有上百万的玩家数据,如果现在需要你根据玩家的经验值整理一个前20名的排行榜,你会怎么做呢?
Redis 发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息。
 
首先介绍下,会用到的几个命令及其它们的作用:
详情看官网(redis中文)
zrange? 查看排行榜 (升序)
zrevrange 查看排行榜 (降序)
zadd 添加一个数据
zrem 删除一个数据
zrank 获取排名(升序)
zrevrank 获取排名 (降序)
 
1. 创建一个与redis服务器的单连接工具类:
 
redis订阅和发布性能
2. 模拟实现博客订阅排名:
 
/**
 * 测试阅读博客文章排名
 *
 * 将文章得到的支持票数乘以一个常量,然后加上文章的发布时间,得到的结果便是文章的评分
 *
     思考:
     需要记录文章的评分,文章标题,网址,发布文章的用户,发布时间,投票数量等消息
     都需要存储在redis中,所以面对多个数据组成的一个文章,我们采用hash结构来存储相关的数据
     article => hash key 这里采用article:87654来区分不同的文章key
     title => 标题
     link => 网站链接
     poster => 发布人
     time => 发布时间
     votes => 文章投票数
     // 文章需要采用文章 + : + 编号的形式来标识,使用zset来保证文章发布时间的有序性,有序通过分值来标识的发布时间
     // 因此我们需要一个zset集合来存储文章编号和发布时间
     // 为了防止用户对一个文章进行多次投票,网站需要为每一篇文章记录投票的用户名单,这里可以采用set集合来进行存储
     // set保证了用户的唯一性,set总数便是投票数votes
     // 为了节约内存,当一篇文章发布一周,则不允许对其投票,文章的投票数会被记录,文章投票的用户信息则会被删除
 *
 *
 *
 * @date 2020-01-02
 * @since
 */
public class BlogRanking {
 
    /**
     * 常量 = 864000 / 200 一天的秒数初一展示一天所需的支持票数200
     * 意味着文章每获得一个支持票,则评分增加432
     */
    private static final Integer CONSTANT = 432;
 
    private static final String INIT_ZERO = "0";
 
    /**
     * 文章id
     */
    private static final String ARTICLE_ID = "article:%d";
 
    /**
     * 随机的用户id
     */
    private static final String USER_ID = "user:%d";
 
    /**
     * 文章所处的订阅用户信息列表
     */
    private static final String ARTICLE_USERS = "%s:users";
 
    /**
     * 用于标识文章-时间集合,zset的key
     */
    private static final String ARTICLE_TIME_LIST = "article_time_list";
 
    /**
     * 用于标识文章-积分集合zset的可以
     */
    private static final String ARTICLE_SCORE_LIST = "article_score_list";
 
    /**
     * 定义初始值的文章数
     */
    private static final String ARTICLE_NUM = "article_num";
 
    /**
     * 标题
     */
    private static final String TITLE = "title";
 
    /**
     * 文章链接
     */
    private static final String LINK = "link";
 
    /**
     * 发布人
     */
    private static final String POSTER = "poster";
 
    /**
     * 发布时间
     */
    private static final String TIME = "time";
 
    /**
     * 用户订阅数
     */
    private static final String VOTES = "votes";
 
    /**
     * 一周的毫秒数
     */
    private static final Integer WEEK_TIME = 7 * 24 * 60 * 60 * 1000;
 
    /**
     * 随机生成一个用户id
     * 限制在1-500
     *
     * @date 2020-01-02
     * @updateDate 2020-01-02
     * @param
     * @return
     */
    public static String getRandomUser() {
        return String.format(USER_ID, new Random().nextInt(10));
    }
 
    /**
     * 发布文章,每新增一篇文章则+1,最大为200篇文章
     *
     * @date 2020-01-02
     * @updateDate 2020-01-02
     * @param
     * @return
     */
    public static void postArticle(Article article) {
        Jedis client = RedisConnection.getInstance();
        String articleId = String.format(ARTICLE_ID, client.incr(ARTICLE_NUM));
        // 将新增的文章加入到zset集合中,时间为当前时间的毫秒数,用于标记文章的顺序
        // 获取当前时间的毫秒数
        long time = System.currentTimeMillis();
        client.zadd(ARTICLE_TIME_LIST, time, articleId);
 
        // 新增文章 + 积分的有序zset集合
        client.zadd(ARTICLE_SCORE_LIST, 0, articleId);
 
        // 新增文章的标题等相关内容
        client.hset(articleId, TITLE, article.getTitle());
        client.hset(articleId, LINK, article.getLink());
        client.hset(articleId, POSTER, article.getPoster());
        client.hset(articleId, TIME, String.valueOf(time));
        client.hset(articleId, VOTES, INIT_ZERO);
        // 添加文章订阅的用户信息列表集合,采用set来进行存储
        client.sadd(String.format(ARTICLE_USERS, articleId), INIT_ZERO);
    }
    /**
     * 随机投票
     *
     * @date 2020-01-02
     * @updateDate 2020-01-02
     * @param
     * @return
     */
    public static void vote(String articleId, String userId) {
        Jedis client = RedisConnection.getInstance();
 
        // 获取文章订阅用户列表
        String articleUsers = String.format(ARTICLE_USERS, articleId);
 
        // 校验发布时间是否超过一周,超过则拒绝投票并删除文章关联的用户投票信息
        long now = System.currentTimeMillis();
        long time = Long.valueOf(client.hget(articleId, TIME)) + WEEK_TIME;
 
        // 如果当前时间大于time则代表已经过去一周
        if (now > time) {
            System.out.println("文章ID[" + articleId + "]发布已经超过一周,不可再进行投票");
            // 判断订阅用户信息列表是否存在,存在则移除
            if (client.exists(articleUsers)) {
                client.del(articleUsers);
                System.out.println("移除文章所投票的用户信息列表成功");
            }
            return;
        }
        // 校验当前文章是否存在,不存在则报错
        if (!client.exists(articleId)) {
            System.out.println("不存在此文章信息,无法进行投票");
            return;
        }
 
        // 校验当前人是否已经进行过投递,存在则不允许再次投票
        if (client.sismember(articleUsers, userId)) {
            System.out.println("用户[" + userId + "]已经投票了该文章,不可重复投票");
            return;
        }
        // 新增评分
        client.zincrby(ARTICLE_SCORE_LIST, CONSTANT, articleId);
 
        // 添加投票数
        client.hincrBy(articleId, VOTES, 1);
 
        // 添加用户数到用户列表中
        client.sadd(articleUsers, userId);
    }
 
    /**
     * 第三步:获取最新的10个排名文章的相关信息
     *
     * @date 2020-01-02
     * @updateDate 2020-01-02
     * @param
     * @return
     */
    public static List<Article> listRanking() {
        Jedis client = RedisConnection.getInstance();
        // 获取文章排名前10的信息
        Set<String> set = client.zrevrange(ARTICLE_SCORE_LIST, 0, 10);
        return set.stream().map(member -> {
            Map<String, String> map = client.hgetAll(member);
            String json = JSON.toJSONString(map);
            return JSON.parseObject(json, Article.class);
        }).collect(Collectors.toList());
    }
 
    /**
     * 第一步:创建500篇文章
     *
     * @date 2020-01-02
     * @updateDate 2020-01-02
     * @param
     * @return
     */
    public static void createArticle() {
        for (int i = 1; i <= 500; i++) {
            Article article = new Article();
            article.setLink(String.format("www.xiaofeifei%d.com", i));
            article.setPoster(String.format("xiaofeifei%d", i));
            String title = i % 2 == 0 ? "我爱读书%d" : "我爱学习%d";
            article.setTitle(String.format(title, i));
            BlogRanking.postArticle(article);
        }
    }
 
    /**
     * 第二步:随机为文章添加访问量和积分
     *
     * @date 2020-01-02
     * @updateDate 2020-01-02
     * @param
     * @return
     */
    public static void randomAddScore() {
        // 第二步随机进行100次用户订阅文章
            for (int i = 1; i <= 100; i++) {
                BlogRanking.vote(String.format(ARTICLE_ID, i), BlogRanking.getRandomUser());
            }
 
 
    }
 
    public static void main(String[] args) {
        List<Article> articles = BlogRanking.listRanking();
        articles.forEach(System.out::println);
        System.out.println("success");
    }
 
 
}
 
/**
 * 创建一个文章类,用户记录文章的相关信息
 */
@Data
class Article {
 
    /**
     * 标题
     */
    private String title;
 
    /**
     * 网址链接
     */
    private String link;
 
    /**
     * 发布人
     */
    private String poster;
 
    /**
     * 投票数
     */
    private Integer votes;
 
}
 
 
测试结果如下:
redis订阅和发布 消息推送
 
在海量数据与高并发的场景下,Redis是一个更好的选择。本文基于Redis实现的滚动榜,不论滚动周期多长,都只需要常数(3)次数的写操作,有较好的性能和可扩展性。且通过离线+在线的双预生成机制,确保了榜单实时生效,可用性较强。

本文来自网络,不代表山斋月平台立场,转载请注明出处: https://www.shanzhaiyue.top