大厂五剑客之redis基础分布式缓存彻底解决方案---04redis实战排行榜---二周目

15 篇文章 0 订阅

7-1:ZSetOperations重要api讲解(sortedSet)

    1、排行榜:
             排行榜功能是一个很普遍的需求。使用 Redis 中有序集合的特性来实现排行榜是又好又快的选择。
             一般排行榜都是有实效性的,比如“用户积分榜”,游戏中活跃度排行榜,游戏装备排行榜等。

          面临问题:
             数据库设计复杂,并发数较高,数据要求实时性高   

    2、redis实现排行榜api讲解

         排行榜对应redis的sortSet。

步骤:

 1.redisService      查看现成的方法。    

/**
     * 有序集合添加
     *
     * @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);
    }

参照这个:输入的是三个参数  第一个是有序集合的名称  第二个是值  第三个是排行的名次

2.创建RangService拷贝redisService 方法

3.写controller

跑一个结果:

http://localhost:8080/addScore?uid=1&score=15

http://localhost:8080/addScore?uid=2&score=5

http://localhost:8080/addScore?uid=3&score=10

增加分数:

http://localhost:8080/increScore?uid=3&score=10

源码对应:

public Double incrementScore(K key, V value, double delta) {
        byte[] rawKey = this.rawKey(key);
        byte[] rawValue = this.rawValue(value);
        return (Double)this.execute((connection) -> {
            return connection.zIncrBy(rawKey, delta, rawValue);
        }, true);
    }

底层的方法:

 public void incrementScore(String key, Object value, double scoure) {
        ZSetOperations<String, Object> zset = redisTemplate.opsForZSet();
        zset.incrementScore(key, value, scoure);
    }

获取排名

http://localhost:8080/rank?uid=3

底层的方法:

public Long zRank(String key, Object value) {
        ZSetOperations<String, Object> zset = redisTemplate.opsForZSet();
        return zset.rank(key,value);
    }

分数小排名高。

http://localhost:8080/scoreByRange?start=0&end=-1

底层的方法:

 public Set<ZSetOperations.TypedTuple<Object>> zRankWithScore(String key, long start,long end) {
        ZSetOperations<String, Object> zset = redisTemplate.opsForZSet();
        Set<ZSetOperations.TypedTuple<Object>> ret = zset.rangeWithScores(key,start,end);
        return ret;
    }

      对应这个方法。

7-2:浅谈mysql数据库表设计过程中几个关键要点

    数据库的表:score_flow积分流水表   用于查top100 查询用户的排名

                          user_score用户积分表   特定用户的排名,通过用户的id看排名                     

 1、表设计过程中应该注意的点即数据类型 
              1)更小的通常更好 :控制字节长度
              2)使用合适的数据类型:如tinyint只占8个位,(3)100以内的数值。

                   char(死的)与varchar(可变长的,多一位存字节的长度255上限)的对比,用于定长数据的存储,如uuid,不需要额外一位存长度。

CREATE TABLE `score_flow` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id',
  `score` bigint(19) unsigned(非负整数) NOT NULL COMMENT '用户积分流水',
  `user_id` int(11) unsigned NOT NULL COMMENT '用户主键id',
  `user_name` varchar(30) NOT NULL DEFAULT '' COMMENT '用户姓名',
  PRIMARY KEY (`id`),
  KEY `idx_userid` (`user_id`)
) ENGINE=InnoDB AUTO_INCREMENT=13 DEFAULT CHARSET=utf8mb4;

              3)尽量避免NULL     NOT NULL DEFAULT ”“/0 
                  NULL的列会让索引统计和值比较都更复杂。可为NULL的列会占据更多的磁盘空间,在Mysql中也需要更多复杂的处理程序,会使所索引失效或者命中率变低。
        2、索引设计过程中应该注意的点

              1)选择唯一性索引
                   唯一性索引的值是唯一的,可以更快速的通过该索引来确定某条记录

                   唯一索引的场景:就是首先你不加的话你在程序中要先查,再加就会效率很慢。

                   唯一索引的话就会快速定位。

                   保证物理唯一。

              2)为经常需要排序、分组和联合操作的字段建立索引
                  经常需要ORDER BY、GROUP BY、DISTINCT和UNION等操作的字段,排序操作会浪费很多时间

              3)常作为查询条件的字段建立索引
                  如果某个字段经常用来做查询条件,那么该字段的查询速度会影响整个表的查询速度        
    
              4)数据少的地方不必建立索引,指的是数据的种类少如state只有0和1  索引是很占内存的
         
        3、sql优化,explain查看执行计划

                 
 
              1) 能够用BETWEEN的就不要用IN 

              2) 能够用DISTINCT的就不用GROUP BY

              3)  避免数据强转 where id=1和where id='1'功能是一样的

              4) 学会采用explain查看执行计划

7-3:sql优化,explain查看执行计划(扫描行数会影响CPU运行,占用大量内存)     

续:org.mybatis.generator配置讲解    

        引入:
        <dependency>
        <groupId>org.mybatis.generator</groupId>
        <artifactId>mybatis-generator-core</artifactId>
        <scope>test</scope>
        <version>1.3.2</version>
        <optional>true</optional>
        </dependency>

        <dependency>
        <groupId>commons-io</groupId>
        <artifactId>commons-io</artifactId>
        <version>2.5</version>
        </dependency>  

  最后弄了个神奇的东西类似于代码生成器,这个用的时候再看。

7-4:用户实现排行榜的三大接口?

         1、添加用户积分
         2、获取top N 排行   redisService新增方法reverseRangeWithScores()  索引查看有序数组小的在前面
         3、根据用户ID获取排行 zset.rank(key,value),key为set的名称,value为用户id  id查看排名

         使用场景:

---------------------------------------

         添加积分:

  /**
     * 添加用户积分
     *
     * @param uid
     * @param score
     */
    public void increSaleSocre(String uid, Integer score) {
        User user = userMapper.find(uid);
        if (user == null) {
            return;
        }
        int uidInt = Integer.parseInt(uid);
        long socreLong = Long.parseLong(score + "");
        String name = user.getUserName();
        String key = uid + ":" + name;
        // 每次的积分增加
        scoreFlowMapper.insertSelective(new ScoreFlow(socreLong, uidInt, name));
        // 积分增加总数
        userScoreMapper.insertSelective(new UserScore(uidInt, socreLong, name));
        redisService.incrementScore(SALESCORE, key, score);//只在redis更新分数不存数据库
    }

这个是在数据库添加积分在redis也添加积分。

第一个是set的id就是哪个set  第二个是set的key  第三个是set的积分

------------------------

查询用户在排行榜里面的积分:

这个直接在redis里面查不需要数据库里面查。

这方法注意了。

  

// 查询排名 分数小的在前面
public Map<String, Object> userRank(String uid, String name) {
        Map<String, Object> retMap = new LinkedHashMap<>();
        String key = uid + ":" + name;
        Integer rank = redisService.zRank(SALESCORE, key).intValue();//rank查询排行,0开始,小的在前面。
        Long score = redisService.zSetScore(SALESCORE, key).longValue();//获得分数
        retMap.put("userId", uid);
        retMap.put("score", score);
        retMap.put("rank", rank);
        return retMap;
    }

------------------------------  

获取top,根据排名查询

  public List<Map<String, Object>> reverseZRankWithRank(long start, long end) {
        Set<ZSetOperations.TypedTuple<Object>> setObj = redisService.reverseZRankWithRank(SALESCORE, start, end);//传的是积分
        List<Map<String, Object>> mapList = setObj.stream().map(objectTypedTuple -> {
            Map<String, Object> map = new LinkedHashMap<>();
            map.put("userId", objectTypedTuple.getValue().toString().split(":")[0]);
            map.put("userName", objectTypedTuple.getValue().toString().split(":")[1]);
            map.put("score", objectTypedTuple.getScore());
            return map;
        }).collect(Collectors.toList());
        return mapList;
    }

  -----------------------------------------

测试三个方法。

  http://localhost:8080/sale/increScore?uid=1&score=5  uid是用户的id  score是分数

  http://localhost:8080/sale/increScore?uid=2&score=6

   http://localhost:8080/sale/increScore?uid=3&score=7

  http://localhost:8080/sale/increScore?uid=4&score=8

   http://localhost:8080/sale/increScore?uid=5&score=9

   http://localhost:8080/sale/increScore?uid=6&score=10

  

 redis存的是用户id:用户姓名  积分的加和是在redis里面加的。

  redis和数据库存的值  

  查某个人的积分

   http://localhost:8080/sale/userScore?uid=1&name=one

  

  获取排行榜的前几名的用户

  http://localhost:8080/sale/top?start=0&end=10

7-5 :redis项目实战之排行榜实现--重点

       疑问?redis数据丢失的时候怎么办?

       初始化加载做同步。实现将1000万用户load到缓存,用户请求判断该用户是不是缓存你的用户。

 4、springboot项目初始化加载讲解?
         场景:将一千万用户白名单load缓存,用户请求的时候判断该用户是否是缓存里面的用户。
             1、springboot实现初始化加载配置(实现缓存预热)。

 public void rankSaleAdd() {
        UserScoreExample example = new UserScoreExample();
        example.setOrderByClause("id desc");
        List<UserScore> userScores = userScoreMapper.selectByExample(example);
        userScores.forEach(userScore -> {
            String key = userScore.getUserId() + ":" + userScore.getName();
            redisService.zAdd(SALESCORE, key, userScore.getUserScore());
        });
    }

 数据预热的代码。很简单就是遍历下。

2、采用实现springboot ApplicationRunner,里面只有一个run方法。

                 这个是shangguigu的
                该方法仅在SpringApplication.run(…)完成之前调用,此时外部可以访问了,都放在容器中,然后去遍历调用的。服务已经对外了执行重写方法的时候。

                解释:https://blog.csdn.net/jdd92/article/details/81053404

 3.采用实现InitializingBean,里面只有一个方法。 
                InitializingBean接口为bean提供了初始化方法的方式,它只包括afterPropertiesSet()方法。
                在spring初始化bean的时候,如果bean实现了InitializingBean接口,
                在对象的所有属性被初始化后之后才会调用afterPropertiesSet()方法,还没启动完毕。

                在执行重写的方法的时候所有的服务还没有对外。

                解释:https://www.cnblogs.com/weiqihome/p/8922937.html

初始化同步redis数据
         初始化完成再放入请求

             具体做法:将

             数据load到缓存中。

              1.写方法  

                              

               2..首先会清空redis中所有的数据,手动的清空。

               3..重启springboot

                用另一个接口:

                 

                     

@Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("======enter init bean=======");
        this.rankSaleAdd();
    }

  两个方法的区别:

   第二种方法,在初始化预热的过程中用户请求是进不来的。

    

    第一种方法,在进行初始化预热的时候是可以请求通的。 

   -----------------------------------

总结:存的是userId,和userName,加上score。

    此博客的遗留问题:org.mybatis.generator使用                                       

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值