伙伴匹配系统(六)


笔记

1.开发主页(默认推荐和自己兴趣相当的用户)

2.优化主页的性能(缓存 + 定时任务 + 分布式锁)

开发主页

最简单:直接 list 列表 模拟 1000 万个用户,再去查询

导入数据

  1. 用可视化界面:适合一次性导入、数据量可控
  2. 写程序:for 循环,建议分批,不要一把梭哈(可以用接口来控制)要保证可控、幂等,注意线上环境和测试环境是有区别的导入 1000 万条,for i 1000w
  3. 执行 SQL 语句:适用于小数据量

编写一次性任务

for 循环插入数据的问题:

  1. 建立和释放数据库链接(批量查询解决)
  2. for 循环是绝对线性的(并发)

并发要注意执行的先后顺序无所谓,不要用到非并发类的集合    private ExecutorService executorService = new ThreadPoolExecutor(16, 1000, 10000, TimeUnit.MINUTES, new ArrayBlockingQueue<>(10000));

// CPU 密集型:分配的核心线程数 = CPU - 1
// IO 密集型:分配的核心线程数可以大于 CPU 核数

数据库慢?预先把数据查出来,放到一个更快读取的地方,不用再查数据库了。(缓存) 预加载缓存,定时更新缓存。(定时任务) 多个机器都要执行任务么?(分布式锁:控制同一时间只有一台机器去执行定时任务,其他机器不用重复执行了)

一、开发页面

1.启动前后端项目

进入搜索页面选择数据库里存在的标签搜索,发现搜索为空,这是因为我们取了两次响应的data,自然搜不到,修改如下代码: image.png 刷新页面,成功展现数据库里被筛选的数据

2.编写主页(直接list列表)

在后端controller层编写接口去实现显示推荐页面的功能

/**
 * 推荐页面
 * @param request
 * @return
 */
@GetMapping("/recommend")
public BaseResponse<List<User>> recommendUsers(HttpServletRequest request){
    QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    List<User> userList = userService.list(queryWrapper);
    List<User> list = userList.stream().map(user -> userService.getSafetyUser(user)).collect(Collectors.toList());
    return ResultUtils.success(list);
}

刷新页面显示 image.png 但是页面显示还有一定问题,下面有一块不显示,在前端修改样式 basicLayout.vue里面添加以下样式 image.png 我们发现有几个页面都用到了列表组件,所以我们提取出公共组件,来减少代码的编写 在公共组件包里添加UserCardList,并复制主页里面的模板修改为如下

<template>
  <van-card
      v-for="user in userList"
      :desc="user.profile"
      :title="`${user.username} (${user.planetCode})`"
      :thumb="user.avatarUrl"
  >
    <template #tags>
      <van-tag plain type="danger" v-for="tag in user.tags" style="margin-right: 8px; margin-top: 8px" >
        {{ tag }}
      </van-tag>
    </template>
    <template #footer>
      <van-button size="mini">联系我</van-button>
    </template>
  </van-card>
</template>

<script setup lang="ts">
import {UserType} from "../models/user";

interface UserCardListProps{
  userList: UserType[];
}

const props= withDefaults(defineProps<UserCardListProps>(),{
  //@ts-ignore
  userList: [] as UserType[]
});

</script>

<style scoped>

</style>

测试一下,主页和用户结果页面应该显示正常

3.模拟1000万用户,再次进行查询

我们需要插入数据: 1.用可视化界面:适合一次性导入、数据量可控 由于编码,主键以及某些字段的问题(id,createtime等),演示插入失败,这里不推荐 2.写程序:for 循环,建议分批,不要一把梭哈,这里演示了两种插入数据的方法 首先创建测试方法文件InsertUsersTest,编写批量查询解决

package com.yupi.usercenter.service;

import com.yupi.usercenter.model.domain.User;

import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.util.StopWatch;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;

@SpringBootTest
public class InsertUsersTest {

    @Resource
    private UserService userService;

    //private ExecutorService executorService = new ThreadPoolExecutor(40,1000,10000, TimeUnit.MINUTES,new ArrayBlockingQueue<>(10000));

    @Test
    public void doInsertUsers(){
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        final int INSERT_NUM=100000;
        List<User> userList = new ArrayList<>();
        for (int i = 0; i < INSERT_NUM; i++) {
            User user = new User();
            user.setUsername("假数据");
            user.setUserAccount("fakeaccount");
            user.setAvatarUrl("https://img1.baidu.com/it/u=1645832847,2375824523&fm=253&fmt=auto&app=138&f=JPEG?w=480&h=480");
            user.setGender(0);
            user.setUserPassword("231313123");
            user.setPhone("1231312");
            user.setEmail("12331234@qq.com");
            user.setUserStatus(0);
            user.setUserRole(0);
            user.setPlanetCode("213123");
            user.setTags("[]");
            userList.add(user);
        }
        userService.saveBatch(userList,10000);
        stopWatch.stop();
        System.out.println(stopWatch.getTotalTimeMillis());
    }

}

并发执行,这里的线程可自定义或者用idea默认的,两种方法的区别是,自定义可以跑满线程,而默认的只能跑CPU核数-1,代码区别:就是在异步执行处加上自定义的线程名

private ExecutorService executorService = new ThreadPoolExecutor(40,1000,10000, TimeUnit.MINUTES,new ArrayBlockingQueue<>(10000));

@Test
    public void doConcurrencyInsertUsers(){
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        //分10组
        int batchSize = 5000;
        int j=0;
        List<CompletableFuture<Void>> futureList = new ArrayList<>();
        for (int i = 0; i < 20; i++) {
            List<User> userList = new ArrayList<>();
            while (true){
                j++;
                User user = new User();
                user.setUsername("假数据");
                user.setUserAccount("fakeaccount");
                user.setAvatarUrl("https://img1.baidu.com/it/u=1645832847,2375824523&fm=253&fmt=auto&app=138&f=JPEG?w=480&h=480");
                user.setGender(0);
                user.setUserPassword("231313123");
                user.setPhone("1231312");
                user.setEmail("12331234@qq.com");
                user.setUserStatus(0);
                user.setUserRole(0);
                user.setPlanetCode("213123");
                user.setTags("[]");
                userList.add(user);
                if (j % batchSize==0){
                    break;
                }
            }
            //异步执行
            CompletableFuture<Void> future = CompletableFuture.runAsync(() ->{
                System.out.println("threadName:"+Thread.currentThread().getName());
                userService.saveBatch(userList,batchSize);
            },executorService);
            futureList.add(future);
        }
        CompletableFuture.allOf(futureList.toArray(new CompletableFuture[]{})).join();

        stopWatch.stop();
        System.out.println(stopWatch.getTotalTimeMillis());
    }

若使用默认线程池,删去 image.png 现在启动前后端,查看主页,发现搜查不出,这是因为数据太多需要分页,修改后端接口方法

/**
 * 推荐页面
 * @param request
 * @return
 */
@GetMapping("/recommend")
public BaseResponse<Page<User>> recommendUsers(long pageSize,long pageNum, HttpServletRequest request){
    QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    Page<User> userList = userService.page(new Page<>(pageNum, pageSize), queryWrapper);
    return ResultUtils.success(userList);
}

同时还要引入mybatis的分页插件配置,直接复制文档到config目录

package com.yupi.usercenter.config;

import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
@MapperScan("com.yupi.usercenter.mapper")
public class MybatisPlusConfig {

    /**
     * 新的分页插件,一缓和二缓遵循mybatis的规则,需要设置 MybatisConfiguration#useDeprecatedExecutor = false 避免缓存出现问题(该属性会在旧插件移除后一同移除)
     */
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.H2));
        return interceptor;
    }

}

主要不要忘了把扫包的路径改为自己的 现在去修改前端主页 image.png 刷新页面,成功展现8条数据 image.png

over !!!!!!!!!!!!!


Author: qwq小小舒
Reprint policy: All articles in this blog are used except for special statements CC BY 4.0 reprint policy. If reproduced, please indicate source qwq小小舒 !
  TOC