You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
314 lines
10 KiB
314 lines
10 KiB
|
3 months ago
|
# 客户数据预加载解决方案
|
||
|
|
|
||
|
|
## 一、当前系统性能问题分析
|
||
|
|
|
||
|
|
### 1. 数据加载模式
|
||
|
|
- **请求-响应模式**:当前系统使用传统的请求-响应模式,每次前端请求数据时,后端才会查询数据库并返回结果
|
||
|
|
- **多次数据库查询**:单个客户数据请求涉及多个数据库查询(基本信息、联系人、购物车项、负责人等)
|
||
|
|
- **缺少缓存机制**:系统中没有实现任何缓存机制,所有数据都直接从数据库查询
|
||
|
|
- **前端频繁请求**:作为客户关系管理系统,前端需要频繁请求客户数据
|
||
|
|
|
||
|
|
### 2. 性能瓶颈
|
||
|
|
- **数据库查询开销大**:每次请求都需要执行多次数据库查询
|
||
|
|
- **网络延迟**:频繁的HTTP请求导致网络延迟累积
|
||
|
|
- **数据重复传输**:相同数据可能被多次请求和传输
|
||
|
|
- **缺少数据预加载**:没有在用户需要之前预先加载数据
|
||
|
|
|
||
|
|
## 二、预加载解决方案设计
|
||
|
|
|
||
|
|
### 1. 后端预加载策略
|
||
|
|
|
||
|
|
#### 1.1 添加缓存层
|
||
|
|
- **使用Redis作为缓存存储**:高性能、高可用的键值存储
|
||
|
|
- **实现数据预加载服务**:定期将热点数据加载到缓存中
|
||
|
|
- **合理设置过期时间**:根据数据类型和更新频率设置不同的过期时间
|
||
|
|
|
||
|
|
#### 1.2 优化数据库查询
|
||
|
|
- **实现数据预聚合**:减少查询次数,提高查询效率
|
||
|
|
- **使用JOIN查询**:替代多次单表查询,减少数据库交互次数
|
||
|
|
- **添加索引优化**:为频繁查询的字段添加索引
|
||
|
|
|
||
|
|
#### 1.3 实现数据预加载服务
|
||
|
|
- **定时任务预加载**:定期预加载热点数据
|
||
|
|
- **数据变更监听**:当数据发生变化时及时更新缓存
|
||
|
|
- **分层预加载**:为不同类型的用户预加载不同的数据
|
||
|
|
|
||
|
|
### 2. 前端预加载策略
|
||
|
|
|
||
|
|
#### 2.1 实现前端缓存
|
||
|
|
- **使用localStorage/sessionStorage**:缓存不经常变化的数据
|
||
|
|
- **数据预加载机制**:在用户访问页面之前加载数据
|
||
|
|
- **数据过期机制**:定期更新缓存,确保数据新鲜度
|
||
|
|
|
||
|
|
#### 2.2 优化请求策略
|
||
|
|
- **请求合并**:减少HTTP请求次数
|
||
|
|
- **请求预加载**:在用户可能访问的数据之前加载
|
||
|
|
- **懒加载**:只加载当前需要的数据,减少初始加载时间
|
||
|
|
|
||
|
|
#### 2.3 WebSocket实时更新
|
||
|
|
- **建立WebSocket连接**:实现数据实时更新
|
||
|
|
- **后端主动推送**:当数据发生变化时,主动推送给前端
|
||
|
|
- **减少轮询请求**:降低服务器压力,提高响应速度
|
||
|
|
|
||
|
|
## 三、具体实施方案
|
||
|
|
|
||
|
|
### 1. 后端实现
|
||
|
|
|
||
|
|
#### 1.1 添加Redis依赖
|
||
|
|
```xml
|
||
|
|
<!-- pom.xml -->
|
||
|
|
<dependency>
|
||
|
|
<groupId>org.springframework.boot</groupId>
|
||
|
|
<artifactId>spring-boot-starter-data-redis</artifactId>
|
||
|
|
</dependency>
|
||
|
|
```
|
||
|
|
|
||
|
|
#### 1.2 配置Redis连接
|
||
|
|
```yaml
|
||
|
|
# application.yaml
|
||
|
|
spring:
|
||
|
|
redis:
|
||
|
|
host: localhost
|
||
|
|
port: 6379
|
||
|
|
password:
|
||
|
|
database: 0
|
||
|
|
```
|
||
|
|
|
||
|
|
#### 1.3 实现缓存服务
|
||
|
|
```java
|
||
|
|
// CacheService.java
|
||
|
|
@Service
|
||
|
|
public class CacheService {
|
||
|
|
|
||
|
|
@Autowired
|
||
|
|
private RedisTemplate<String, Object> redisTemplate;
|
||
|
|
|
||
|
|
// 缓存数据
|
||
|
|
public void cacheData(String key, Object data, long expireTime, TimeUnit timeUnit) {
|
||
|
|
redisTemplate.opsForValue().set(key, data, expireTime, timeUnit);
|
||
|
|
}
|
||
|
|
|
||
|
|
// 获取缓存数据
|
||
|
|
public <T> T getCachedData(String key, Class<T> clazz) {
|
||
|
|
Object data = redisTemplate.opsForValue().get(key);
|
||
|
|
return clazz.cast(data);
|
||
|
|
}
|
||
|
|
|
||
|
|
// 删除缓存数据
|
||
|
|
public void deleteCachedData(String key) {
|
||
|
|
redisTemplate.delete(key);
|
||
|
|
}
|
||
|
|
|
||
|
|
// 检查缓存是否存在
|
||
|
|
public boolean isCached(String key) {
|
||
|
|
return redisTemplate.hasKey(key);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
#### 1.4 实现数据预加载服务
|
||
|
|
```java
|
||
|
|
// PreloadService.java
|
||
|
|
@Service
|
||
|
|
public class PreloadService {
|
||
|
|
|
||
|
|
@Autowired
|
||
|
|
private UsersMapper usersMapper;
|
||
|
|
|
||
|
|
@Autowired
|
||
|
|
private CacheService cacheService;
|
||
|
|
|
||
|
|
// 预加载热点客户数据 - 每5分钟执行一次
|
||
|
|
@Scheduled(fixedRate = 300000)
|
||
|
|
public void preloadHotCustomers() {
|
||
|
|
List<UserProductCartDTO> hotCustomers = usersMapper.selectHotCustomers();
|
||
|
|
for (UserProductCartDTO customer : hotCustomers) {
|
||
|
|
String key = "customer:" + customer.getUserId();
|
||
|
|
cacheService.cacheData(key, customer, 1, TimeUnit.HOURS);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// 预加载客户列表数据 - 每10分钟执行一次
|
||
|
|
@Scheduled(fixedRate = 600000)
|
||
|
|
public void preloadCustomerList() {
|
||
|
|
List<UserProductCartDTO> customerList = usersMapper.selectAllCustomers();
|
||
|
|
cacheService.cacheData("customer:list", customerList, 30, TimeUnit.MINUTES);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
#### 1.5 修改服务层代码,使用缓存
|
||
|
|
```java
|
||
|
|
// CustomerService.java
|
||
|
|
@Service
|
||
|
|
public class CustomerService {
|
||
|
|
|
||
|
|
@Autowired
|
||
|
|
private UsersMapper usersMapper;
|
||
|
|
|
||
|
|
@Autowired
|
||
|
|
private CacheService cacheService;
|
||
|
|
|
||
|
|
// 获取客户数据,优先从缓存获取
|
||
|
|
public UserProductCartDTO getCustomerById(String userId) {
|
||
|
|
String cacheKey = "customer:" + userId;
|
||
|
|
|
||
|
|
// 尝试从缓存获取
|
||
|
|
UserProductCartDTO customer = cacheService.getCachedData(cacheKey, UserProductCartDTO.class);
|
||
|
|
if (customer != null) {
|
||
|
|
return customer;
|
||
|
|
}
|
||
|
|
|
||
|
|
// 缓存未命中,从数据库查询
|
||
|
|
customer = usersMapper.selectById(userId);
|
||
|
|
|
||
|
|
// 将查询结果存入缓存
|
||
|
|
if (customer != null) {
|
||
|
|
cacheService.cacheData(cacheKey, customer, 1, TimeUnit.HOURS);
|
||
|
|
}
|
||
|
|
|
||
|
|
return customer;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
#### 1.6 实现数据变更监听
|
||
|
|
```java
|
||
|
|
// DataChangeAspect.java
|
||
|
|
@Aspect
|
||
|
|
@Component
|
||
|
|
public class DataChangeAspect {
|
||
|
|
|
||
|
|
@Autowired
|
||
|
|
private CacheService cacheService;
|
||
|
|
|
||
|
|
// 监听数据更新方法,清除相关缓存
|
||
|
|
@AfterReturning(pointcut = "execution(* com.example.web.service.*Service.update*(..))")
|
||
|
|
public void afterUpdate(JoinPoint joinPoint) {
|
||
|
|
// 清除相关缓存
|
||
|
|
Object[] args = joinPoint.getArgs();
|
||
|
|
for (Object arg : args) {
|
||
|
|
if (arg instanceof UserProductCartDTO) {
|
||
|
|
UserProductCartDTO customer = (UserProductCartDTO) arg;
|
||
|
|
String cacheKey = "customer:" + customer.getUserId();
|
||
|
|
cacheService.deleteCachedData(cacheKey);
|
||
|
|
}
|
||
|
|
// 清除客户列表缓存
|
||
|
|
cacheService.deleteCachedData("customer:list");
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### 2. 前端实现
|
||
|
|
|
||
|
|
#### 2.1 前端预加载逻辑
|
||
|
|
```javascript
|
||
|
|
// 在mainapp-sells.html和mainapp-supplys.html中添加
|
||
|
|
// 预加载客户数据
|
||
|
|
function preloadCustomerData() {
|
||
|
|
// 预加载客户列表
|
||
|
|
fetch('/api/customers/list')
|
||
|
|
.then(response => response.json())
|
||
|
|
.then(data => {
|
||
|
|
if (data.success) {
|
||
|
|
// 将数据存入localStorage
|
||
|
|
localStorage.setItem('customerList', JSON.stringify(data.data));
|
||
|
|
localStorage.setItem('customerListExpire', Date.now() + 30 * 60 * 1000); // 30分钟过期
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
// 预加载热点客户数据
|
||
|
|
fetch('/api/customers/hot')
|
||
|
|
.then(response => response.json())
|
||
|
|
.then(data => {
|
||
|
|
if (data.success) {
|
||
|
|
// 将数据存入localStorage
|
||
|
|
localStorage.setItem('hotCustomers', JSON.stringify(data.data));
|
||
|
|
localStorage.setItem('hotCustomersExpire', Date.now() + 60 * 60 * 1000); // 1小时过期
|
||
|
|
}
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
// 页面加载完成后预加载数据
|
||
|
|
window.addEventListener('load', preloadCustomerData);
|
||
|
|
|
||
|
|
// 在用户可能访问的页面之前预加载数据
|
||
|
|
document.addEventListener('click', function(e) {
|
||
|
|
if (e.target.matches('[data-preload]')) {
|
||
|
|
const preloadUrl = e.target.getAttribute('data-preload');
|
||
|
|
fetch(preloadUrl)
|
||
|
|
.then(response => response.json())
|
||
|
|
.then(data => {
|
||
|
|
// 将数据存入sessionStorage
|
||
|
|
sessionStorage.setItem(preloadUrl, JSON.stringify(data));
|
||
|
|
});
|
||
|
|
}
|
||
|
|
});
|
||
|
|
```
|
||
|
|
|
||
|
|
#### 2.2 前端缓存读取逻辑
|
||
|
|
```javascript
|
||
|
|
// 获取客户数据,优先从缓存读取
|
||
|
|
function getCustomerData(userId) {
|
||
|
|
// 尝试从localStorage获取
|
||
|
|
const cachedData = localStorage.getItem('customerList');
|
||
|
|
const expireTime = localStorage.getItem('customerListExpire');
|
||
|
|
|
||
|
|
if (cachedData && expireTime && Date.now() < parseInt(expireTime)) {
|
||
|
|
const customerList = JSON.parse(cachedData);
|
||
|
|
const customer = customerList.find(c => c.userId === userId);
|
||
|
|
if (customer) {
|
||
|
|
return Promise.resolve(customer);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// 缓存未命中,从服务器获取
|
||
|
|
return fetch(`/api/customers/${userId}`)
|
||
|
|
.then(response => response.json())
|
||
|
|
.then(data => {
|
||
|
|
if (data.success) {
|
||
|
|
return data.data;
|
||
|
|
}
|
||
|
|
throw new Error(data.message);
|
||
|
|
});
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
## 四、预期效果
|
||
|
|
|
||
|
|
### 1. 性能提升
|
||
|
|
- **数据加载速度**:预计提升50%-90%
|
||
|
|
- **数据库查询次数**:预计减少60%-80%
|
||
|
|
- **前端响应时间**:预计提升40%-70%
|
||
|
|
- **服务器压力**:预计降低50%-70%
|
||
|
|
|
||
|
|
### 2. 用户体验改善
|
||
|
|
- **页面加载更快**:减少用户等待时间
|
||
|
|
- **数据响应更迅速**:提升系统交互流畅度
|
||
|
|
- **减少页面卡顿**:优化数据加载机制
|
||
|
|
- **支持更多并发用户**:提高系统吞吐量
|
||
|
|
|
||
|
|
## 五、实施步骤
|
||
|
|
|
||
|
|
1. **安装并配置Redis服务器**
|
||
|
|
2. **添加Redis依赖和配置**
|
||
|
|
3. **实现缓存服务和预加载服务**
|
||
|
|
4. **修改现有服务层代码,使用缓存**
|
||
|
|
5. **实现数据变更监听**
|
||
|
|
6. **修改前端代码,实现预加载和缓存**
|
||
|
|
7. **测试和优化**
|
||
|
|
|
||
|
|
## 六、注意事项
|
||
|
|
|
||
|
|
1. **数据一致性**:确保缓存数据与数据库数据的一致性
|
||
|
|
2. **缓存过期策略**:根据数据更新频率设置合理的过期时间
|
||
|
|
3. **内存管理**:监控Redis内存使用情况,避免内存溢出
|
||
|
|
4. **错误处理**:实现缓存失效时的降级策略
|
||
|
|
5. **性能监控**:添加性能监控,持续优化系统性能
|
||
|
|
|
||
|
|
## 七、总结
|
||
|
|
|
||
|
|
本预加载解决方案通过引入缓存机制和预加载策略,将有效解决当前系统数据加载慢的问题。通过后端预加载热点数据到Redis缓存,前端预加载和缓存数据,以及实现数据变更监听,系统的数据加载速度将得到显著提升,同时降低数据库压力和网络延迟,改善用户体验。
|
||
|
|
|
||
|
|
该方案具有良好的扩展性和可维护性,可以根据系统的实际运行情况进行调整和优化,为系统的长期稳定运行提供保障。
|