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.
 
 
 

10 KiB

客户数据预加载解决方案

一、当前系统性能问题分析

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依赖

<!-- pom.xml -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

1.2 配置Redis连接

# application.yaml
spring:
  redis:
    host: localhost
    port: 6379
    password:
    database: 0

1.3 实现缓存服务

// 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 实现数据预加载服务

// 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 修改服务层代码,使用缓存

// 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 实现数据变更监听

// 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 前端预加载逻辑

// 在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 前端缓存读取逻辑

// 获取客户数据,优先从缓存读取
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缓存,前端预加载和缓存数据,以及实现数据变更监听,系统的数据加载速度将得到显著提升,同时降低数据库压力和网络延迟,改善用户体验。

该方案具有良好的扩展性和可维护性,可以根据系统的实际运行情况进行调整和优化,为系统的长期稳定运行提供保障。