开发主页
- 开发主页(默认推荐和自己兴趣相当的用户)
- 优化主页的性能(缓存 + 定时任务 + 分布式锁)
前端recommend
最简单:直接 list 列表
cv searchResult页面的代码 修改
后端接口
数据内容下边距修改
提取用户信息卡片
便于复用组件
新建文件夹components和文件UserCardList.vue,将主页用户信息卡片提取出来。主页和用户信息搜索页进行引用。
<template>
<van-card v-for="user in props.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 "../assets/user";
interface UserCardListProps{
userList:userType;
}
const props=withDefaults(defineProps<UserCardListProps>(),{
//@ts-ignore
userList:[] as userType,
})
</script>
使用用户信息组件
<user-card-list :user-list="userList"></user-card-list>
后台数据库存放大量数据
模拟 1000 万个用户,再去查询
- 用可视化界面:适合一次性导入、数据量可控
- 写程序:for 循环,建议分批,不要一把梭哈(可以用接口来控制)
要保证可控、幂等,注意线上环境和测试环境是有区别的导入 1000 万条,for i 1000w - 执行 SQL 语句:适用于小数据量
导入导出
可视化导出
这里自己选择导出的文件类型和导出的地方路径。(尽量用CSV,exsl格式的话因为编码因为会乱码。)
可视化导入
选择要导入的文件
(导入有风险,自己要想清楚用何种方式导入数据)
上述演示失败
使用代码导入导出数据
CPU 密集型:分配的核心线程数 = CPU - 1
IO 密集型:分配的核心线程数可以大于 CPU 核数
定时任务 && 线性插入
开启定时任务注解。
新建InsertUser.java
编写定时任务代码并进行测试(这里的定时取巧,尽量别用,注释掉。)
// @Scheduled(initialDelay = 5000,fixedRate = Long.MAX_VALUE )
@Component
public class InsertUsers {
@Resource
private UserMapper userMapper;
/**
* 循环插入用户
*/
// @Scheduled(initialDelay = 5000,fixedRate = Long.MAX_VALUE )
public void doInsertUser() {
//统计用户执行时间
StopWatch stopWatch = new StopWatch();
stopWatch.start();
final int INSERT_NUM = 1000;
for (int i = 0; i < INSERT_NUM; i++) {
User user = new User();
user.setUsername("模拟用户");
user.setUserAccount("fakeUser");
user.setAvatarUrl("https://pic.imgdb.cn/item/65b8f58d871b83018a468905.png");
user.setProfile("我是一个假用户");
user.setGender(0);
user.setPassword("12345678");
user.setUserPhone("123456789108");
user.setEmail("shayu-yusha@qq.com");
user.setStatus(0);
user.setUserRole(0);
user.setPlanetCode("931");
user.setTags("[前端er,JavaEr]");
userMapper.insert(user);
}
stopWatch.stop();
// 输出任务执行时间
System.out.println( stopWatch.getLastTaskTimeMillis());
}
}
for 循环插入数据的问题:
- 建立和释放数据库链接,每次都要访问(----批量查询解决)
- for 循环是绝对线性的(—并发解决 )
批量查询插入
/**
* 循环插入用户 耗时:7260ms
* 批量插入用户 1000 耗时: 4751ms
*/
@Test
public void doInsertUser() {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
final int INSERT_NUM = 1000;
List<User> userList = new ArrayList<>();
for (int i = 0; i < INSERT_NUM; i++) {
User user = new User();
user.setUsername("模拟用户");
user.setUserAccount("fakeUser");
user.setAvatarUrl("https://pic.imgdb.cn/item/65b8f58d871b83018a468905.png");
user.setProfile("我是一个假用户");
user.setGender(0);
user.setPassword("12345678");
user.setUserPhone("123456789108");
user.setEmail("shayu-yusha@qq.com");
user.setStatus(0);
user.setUserRole(0);
user.setPlanetCode("931");
user.setTags("[前端er,JavaEr]");
userList.add(user);
}
//每隔100条数据插入一次
userService.saveBatch(userList,100);
stopWatch.stop();
System.out.println( stopWatch.getLastTaskTimeMillis());
}
并发插入
并发执行,
这里的线程可自定义或者用idea默认的,
两种方法的区别是,自定义可以跑满线程,而默认的只能跑CPU核数-1,
代码区别:就是在异步执行处加上自定义的线程名
用户插入单元测试,注意打包时要删掉或忽略,不然打一次包就插入一次
并发要注意执行的先后顺序无所谓,不要用到非并发类的集合
自定义线程池
private ExecutorService executorService = new ThreadPoolExecutor(16, 1000, 10000, TimeUnit.MINUTES, new ArrayBlockingQueue<>(10000));
//线程设置
private ExecutorService executorService = new ThreadPoolExecutor(16, 1000, 10000, TimeUnit.MINUTES, new ArrayBlockingQueue<>(10000));
/**
* 并发批量插入用户 100000 耗时: 26830ms
*/
@Test
public void doConcurrencyInsertUser() {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
final int INSERT_NUM = 100000;
// 分十组
int j = 0;
//批量插入数据的大小
int batchSize = 5000;
List<CompletableFuture<Void>> futureList = new ArrayList<>();
// i 要根据数据量和插入批量来计算需要循环的次数。
for (int i = 0; i < INSERT_NUM/batchSize; i++) {
List<User> userList = new ArrayList<>();
while (true){
j++;
User user = new User();
user.setUsername("模拟用户");
user.setUserAccount("fakeUser");
user.setAvatarUrl("https://pic.imgdb.cn/item/65b8f58d871b83018a468905.png");
user.setProfile("我是一个假用户");
user.setGender(0);
user.setPassword("12345678");
user.setUserPhone("123456789108");
user.setEmail("shayu-yusha@qq.com");
user.setStatus(0);
user.setUserRole(0);
user.setPlanetCode("931");
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.getLastTaskTimeMillis());
}
分页查询
重启项目,前端主页用户信息会刷不出来,因为数据量过大。
后端接口修改
(注意一下这里page的依赖是mybatis的)
添加mybatis,注意扫描的包要修改。
前端修改
效果
TODO
数据库慢?预先把数据查出来,放到一个更快读取的地方,不用再查数据库了。(缓存)
预加载缓存,定时更新缓存。(定时任务)
多个机器都要执行任务么?(分布式锁:控制同一时间只有一台机器去执行定时任务,其他机器不用重复执行了)