源码url: https://github.com/zhzhair/stepsrank-spring-boot.git。

1.创建32个分表,用定时任务插入计步数据模拟用户上传步数;
2.项目启动初始化:将32个表的前200名记录插入mongodb的一个集合(表),清空后插入前200名记录,
并将第200名的步数(阈值)放到redis;
3.上传步数时,当用户的步数大于阈值时,就插入mongodb,否则不插入记录到mongodb;
4.用定时任务每隔10秒删除mongodb表中205名以后的记录;
5.用定时任务每隔1秒更新第200名的步数(阈值)到redis,同时将前200名记录放进redis的队列;
6.查询步数排名先到redis队列,查不到就去mongodb表查。
7.jmeter并发测试看查询性能。

程序设计简述:

技术架构:java8,spring boot2.0.0,mysql,redis,mongodb,mybatis,swagger,jmeter,idea,maven。
  (i)添加测试数据:新建32个表,按照用户id对32取模添加测试数据到不同的表,做定时任务,每秒添加或修改300条记录。表包括user_id和步数step_count两个字段,假设手机每隔一段时间传一次累计步数,如果当日用户有记录,就修改用户的步数(增加新的步数),否则直接添加记录。部分代码如下:
@LogForTask
@Scheduled(cron = "0/1 * * * * ?")
public void uploadStep(){//定时任务每秒添加或修改300条记录
  IntStream.range(0,300).parallel().forEach(i->stepService.uploadStep(32));
}
  (ii)程序设计:在高并发的情况下内存是个问题(out of memory exception!),单个mongodb文档也不能放太多的数据,所以需要设置内存不足就读取磁盘。考虑到第200名的总步数不会减少,并且越往后越“稳定”,所以把它作为阈值就可以给查询的表“瘦身”,从而避免大表排序。
  初始化(即启动项目时):需要将32个表的前200名都放到一个mongodb文档,再将文档前200名替换到该bson文档,同时将第200名的步数存到redis里面,部分代码如下:
@Resource
private StepService stepService;
private static StepService service;
@PostConstruct
public void init(){
  service = this.stepService;
}

public static void main(String[] args) {
SpringApplication.run(StepsApplication.class, args);
  //启动项目初始化排名
  service.recordTopAll(32);
}

@Override
public void recordTopAll(int tableCount) {
  mongoTemplate.dropCollection(StepsTop.class);//删除文档
  IntStream.range(0,tableCount).parallel().forEach(this::insertOneTable);//将MySQL的数据插入到mongo文档
  /*取出前200名放到list,更新mongo文档的数据为当前list的数据*/
  Query query = new Query().with(new Sort(Sort.Direction.DESC,"totalCount")).limit(200);
  List<StepsTop> list = mongoTemplate.find(query,StepsTop.class);
  if(list.isEmpty()) return;
  mongoTemplate.dropCollection(StepsTop.class);
  mongoTemplate.insertAll(list);
  /*redis保存阈值-第200名的步数*/
  int size = Math.min(200,list.size());
  redisTemplate.opsForValue().set(redisKey,String.valueOf(list.get(size - 1).getTotalCount()));
}
  步数上传:redis的数据做定时任务更新,阈值越来越大,每次都将接收到的步数或更新后的步数与阈值比较,比这个阈值大才会去查mongo,然后对mongo文档做更新或插入操作,这个“比较”会非常频繁,但是redis“不惧怕”高并发,我们不必担心。这样就大大地减少了对mongo文档的操作,确保mongo文档数据量很少,之后查询并排序mongo文档的数据就很快了。部分代码如下:
@Override
public void uploadStep(int tableCount) {
  int userId = new Random().nextInt(500_0000);
  int stepCount = 1 + new Random().nextInt(5000);
  Integer count = commonMapper.getStepCount(prefix + userId%tableCount,userId);
  if(count != null){
    commonMapper.updateSteps(prefix + userId%tableCount, userId,count + stepCount);
  }else{
    commonMapper.insertTables(prefix + userId%tableCount, userId, stepCount);
  }
  String tailSteps = redisTemplate.opsForValue().get(redisKey);
  int totalCount = count == null?stepCount:count + stepCount;
  if(tailSteps != null && totalCount > Integer.valueOf(tailSteps)){//步数超过阈值就插入或更新用户的记录
    Query query = new Query(Criteria.where("userId").is(userId));
    if(!mongoTemplate.exists(query,StepsTop.class)){
      StepsTop stepsTop = new StepsTop();
      stepsTop.setUserId(userId);
      stepsTop.setTotalCount(stepCount);
      mongoTemplate.insert(stepsTop);
    }else{
      System.out.println("update: " + tailSteps);
      Update update = new Update();
      update.set("totalStep",totalCount);
      mongoTemplate.upsert(query,update,StepsTop.class);
    }
  }else{
    StepsTop stepsTop = new StepsTop();
    stepsTop.setUserId(userId);
    stepsTop.setTotalCount(stepCount);
    mongoTemplate.insert(stepsTop);
  }
}
  定时任务:每隔10秒更新一次阈值,同时删除mongo文档中200名以外的数据;每隔1秒从mongo查询排好序的前200名的数据push到redis队列,方便从redis取出排名。部分代码如下:
@Override//更新阈值,删除mongo文档中200名以外的数据
public void flushRankAll() {
  // Query query = new Query().with(new Sort(Sort.Direction.DESC,"totalCount")).limit(201);
  // List<StepsTop> list = mongoTemplate.find(query,StepsTop.class);//高并发场景下容易出现内存不足异常:out of memory Exception
  TypedAggregation<StepsTop> aggregation = Aggregation.newAggregation(
    StepsTop.class,
    project("userId", "totalCount"),//查询用到的字段
    sort(Sort.Direction.DESC,"totalCount"),
    limit(200)
  ).withOptions(newAggregationOptions().allowDiskUse(true).build());//内存不足到磁盘读写,应对高并发
  AggregationResults<StepsTop> results = mongoTemplate.aggregate(aggregation, StepsTop.class, StepsTop.class);
  List<StepsTop> list = results.getMappedResults();
  if(list.size() == 201){
    int totalCount = list.get(199).getTotalCount();
    Query query1 = new Query(Criteria.where("totalCount").lt(totalCount));
    mongoTemplate.remove(query1,StepsTop.class);
  }
}
@Override//查询排好序的前200名的数据push到redis队列
public void recordRankAll() {
  // Query query = new Query().with(new Sort(Sort.Direction.DESC,"totalCount")).limit(200);
  // List<StepsTop> list = mongoTemplate.find(query,StepsTop.class);
  TypedAggregation<StepsTop> aggregation = Aggregation.newAggregation(
    StepsTop.class,
    project("userId", "totalCount"),//查询用到的字段
    sort(Sort.Direction.DESC,"totalCount"),
    limit(200)
  ).withOptions(newAggregationOptions().allowDiskUse(true).build());//内存不足到磁盘读写,应对高并发
  AggregationResults<StepsTop> results = mongoTemplate.aggregate(aggregation, StepsTop.class, StepsTop.class);
  List<StepsTop> list = results.getMappedResults();
  if(list.size() == 200){
    Integer stepCount = list.get(199).getTotalCount();
    redisTemplate.opsForValue().set(redisKey,String.valueOf(stepCount));
  }
  if(!list.isEmpty()){
    redisListTemplate.delete(redisQueueKey);
    //noinspection unchecked
    redisListTemplate.opsForList().rightPushAll(redisQueueKey,list);
  }
}
  查询排行榜:现在就简单了,直接到redis队列查询即可,部分代码如下:
@ApiOperation(value = "查询当日总步数排名", notes = "查询当日总步数排名")
@RequestMapping(value = "/getRankAll", method = {RequestMethod.GET}, produces = {MediaType.APPLICATION_JSON_VALUE})
public BaseResponse<List<StepsRankAllResp>> getRankAll(int begin,int pageSize) {
  BaseResponse<List<StepsRankAllResp>> baseResponse = new BaseResponse<>();
  List<StepsRankAllResp> list = stepService.getRankAllFromRedis(begin,pageSize);
  if(list.isEmpty()) list = stepService.getRankAll(begin,pageSize);//redis查不到数据就从Mongo查
  baseResponse.setCode(0);
  baseResponse.setMsg("返回数据成功");
  baseResponse.setData(list);
  return baseResponse;
}
@Override//todo 从redis读取
public List<StepsRankAllResp> getRankAllFromRedis(int begin, int pageSize) {
  List<StepsTop> stepsList = redisListTemplate.opsForList().range(redisQueueKey,begin,pageSize);
  List<StepsRankAllResp> list = new ArrayList<>(stepsList.size());
  for (int i = 0; i < stepsList.size(); i++) {
    StepsRankAllResp stepsRankAllResp = new StepsRankAllResp();
    StepsTop stepsTop = stepsList.get(i);
    BeanUtils.copyProperties(stepsTop,stepsRankAllResp);
    stepsRankAllResp.setRank(begin + i + 1);
    list.add(stepsRankAllResp);
  }
  return list;
}
  jmeter并发测试:访问接口文档--http://localhost:8080/swagger-ui.html/,调接口查询排名,配置调接口5000次,持续5秒,聚合报告如下:

转载于:https://www.cnblogs.com/zhzhair-coding/p/10961861.html

spring boot集成redis和mongodb实现计步排名相关推荐

  1. Spring Boot集成Redis缓存之模拟高并发场景处理

    前言 同样我们以上一篇文章为例子,搭建好环境之后,我欧美可以模拟高并发场景下,我们的缓存效率怎么样,到底能不能解决我们实际项目中的缓存问题.也就是如何解决缓存穿透? Spring Boot集成Redi ...

  2. Spring Boot集成Redis缓存之RedisTemplate的方式

    前言 Spring Boot 集成Redis,将自动配置 RedisTemplate,在需要使用的类中注入RedisTemplate的bean即可使用 @Autowired private Redis ...

  3. Linux 安装Redis-6.2.5,配置及使用(RDB与AOF持久化、sentinel机制、主从复制、Spring Boot 集成 Redis)

    CentOS 7 安装Redis-6.2.5版本 Redis采用的是基于内存的单进程 单线程模型 的KV数据库,由C语言编写.官方提供的数据是可以达到100000+的qps 应用场景: 令牌(Toke ...

  4. Spring Boot 集成 Redis 缓存

    Spring Boot 集成 Redis 缓存 在此章,我们将 SpringBoot 集成 Redis 缓存,Redis是一个开源的,基于内存的数据结构存储,可以用作数据库.缓存和消息代理,在本章仅讲 ...

  5. 【第二十三篇】Spring Boot集成redis

    1.1 简介 REmote DIctionary Server(Redis)是一个由Salvatore Sanfilippo写的key-value存储系统. Redis是一个开源的使用ANSI C语言 ...

  6. Spring Boot集成Redis,这个坑把我害惨了!

    最近项目中使用SpringBoot集成Redis,踩到了一个坑:从Redis中获取数据为null,但实际上Redis中是存在对应的数据的.是什么原因导致此坑的呢? 本文就带大家从SpringBoot集 ...

  7. Spring Boot集成Redis实现缓存

    前言 在此章,我们将 SpringBoot 集成 Redis缓存,Redis是一个开源的,基于内存的数据结构存储,可以用作数据库.缓存和消息代理,在本章仅讲解缓存集成.一键获取源码地址 准备工作 当前 ...

  8. spring boot 集成 redis spring-boot-starter-data-redis 2.1.7.RELEASE jedis: pool: #连接池配置 及踩坑经验

    目录 先上一些踩坑报错,各类报错 @org.springframework.beans.factory.annotation.Autowired(required=true) Error creati ...

  9. springboot 集成redis_一文详解Spring Boot 集成 Redis

    redis设置: 修改redis服务器的配置文件 vim /usr/local/redis/bin/redis.confbind 0.0.0.0 protected-mode no 重新启动redis ...

最新文章

  1. esp32 烧录固件
  2. GDCM:gdcm::XMLDictReader的测试程序
  3. java中保留两位小数(四舍五入后)
  4. 根据上边栏和下边栏的高度进行布局
  5. 12款优秀jQuery Ajax分页插件和教程
  6. python中的@、-、*arg、**kwarg使用方法[探索1]
  7. 图像全参考客观评价算法比较
  8. php基础之MySQL数据排序asc、desc
  9. ACC测试理论--google软件测试之道
  10. 深蓝学院-多传感器融合定位-第3章作业
  11. 你依然是我心中最美丽的彩虹
  12. 【译】JavaScript中的Promises
  13. ubuntu14.04下推荐的工具及插件
  14. WPARAM 与 LPARAM
  15. Expected one result (or null) to be returned by selectOne(), but found: 4
  16. 怎么和你的老板谈工资
  17. 西班牙语dele等级_西班牙语DELE考试分几个等级?难度如何 ?
  18. k近邻算法,朴素贝叶斯算法,分类模型评估,模型调优
  19. php中的run(),PHP daddslashes 使用方法介绍
  20. Python项目体系练习500例(附源代码),练完可就业

热门文章

  1. 2023中国智能客服领域最具商业合作价值企业盘点
  2. 【Java学习】10入门篇之综合实战(对象、IO流、方法等)
  3. 一个关于遗传算法的java小实验(吃豆人)
  4. H5 下拉加载更多(模拟微信聊天记录)
  5. springboot配置mybatis redis缓存
  6. 至PJ初学者!(初学者必看)
  7. 【新课上线】光传输网基础原理!
  8. java byte数组转int数组_Java任意长度byte数组转换为int数组的方法
  9. VR沉浸式安全事故模拟,未来安全教育的最佳选择
  10. 系统服务器更新是什么鬼,OTA升级是什么意思