【商城实战(22)】商城性能优化秘籍:从前端到后端的实战攻略

【商城实战】专栏重磅来袭!这是一份专为开发者与电商从业者打造的超详细指南。从项目基础搭建,运用 uniapp、Element Plus、SpringBoot 搭建商城框架,到用户、商品、订单等核心模块开发,再到性能优化、安全加固、多端适配,乃至运营推广策略,102 章内容层层递进。无论是想深入钻研技术细节,还是探寻商城运营之道,本专栏都能提供从 0 到 1 的系统讲解,助力你打造独具竞争力的电商平台,开启电商实战之旅。

目录


一、前端性能瓶颈定位

1.1 工具选择

在前端性能分析中,Chrome Devtools 是一款强大且常用的工具。它提供了一系列功能,帮助开发者深入了解页面的性能状况。例如,其 Performance 面板能够记录页面加载和交互过程中的性能数据,精确到各个阶段的时间消耗、函数调用栈等,让开发者对页面性能有直观且详细的认识。此外,还有 Lighthouse 等工具,能对页面进行全面的性能审计,并给出优化建议,从多个维度评估页面性能,如性能、可访问性、最佳实践和 SEO 等。

1.2 前端性能问题分析

在 uniapp 中,由于其跨平台特性,代码需要适配多种终端,这可能导致代码复杂度增加,从而引发性能问题。例如,大量的 DOM 操作会占用较多的系统资源,导致页面渲染缓慢;复杂的数据绑定关系,在数据更新时可能触发不必要的重新渲染,增加渲染耗时。在 Element plus 中,组件的过度使用或者不合理使用也可能带来性能瓶颈。比如,在一个页面中使用过多的表格组件,且表格数据量较大时,可能会出现渲染卡顿的情况;组件的样式计算过于复杂,也会增加浏览器的计算负担,导致页面加载缓慢。

1.3 实现源码展示

以下是利用 Chrome Devtools 定位前端性能瓶颈的步骤及示例代码:

  1. 打开 Chrome 浏览器,访问商城页面。
  2. 按下 F12 键,打开开发者工具,切换到 Performance 面板。
  3. 点击 Record 按钮,开始记录性能数据。
  4. 进行页面加载、操作等行为,如点击按钮、切换页面等。
  5. 操作完成后,点击 Stop 按钮,停止记录。

此时,Performance 面板会展示详细的性能数据。例如,在一个 uniapp 页面中,我们怀疑某个列表渲染存在性能问题。通过 Performance 面板的分析,发现渲染函数执行时间较长。对应的代码片段如下:

typescript 复制代码
<template>
  <view>
    <view v-for="(item, index) in list" :key="index">
      {{ item.name }}
    </view>
  </view>
</template>

<script>
export default {
  data() {
    return {
      list: []
    };
  },
  async created() {
    // 模拟从后端获取数据
    const res = await fetchData();
    this.list = res.data;
  }
};
</script>

通过分析 Performance 面板的数据,我们可以看到在渲染列表时,v - for 循环的渲染过程耗时较长。这可能是因为没有使用合适的 key 值,导致 Vue 在更新列表时无法高效地进行差异对比,从而进行了不必要的重新渲染。将 key 值改为 item.id 后,重新测试,发现渲染性能得到了提升。

在 Element plus 中,对于一个包含大量数据的表格组件,我们可以通过以下代码和操作来定位性能问题:

typescript 复制代码
<template>
  <el - table :data="tableData" border style="width: 100%">
    <el - table - column prop="name" label="姓名"></el - table - column>
    <el - table - column prop="age" label="年龄"></el - table - column>
  </el - table>
</template>

<script>
import { ref } from 'vue';
export default {
  setup() {
    const tableData = ref([]);
    // 模拟从后端获取数据
    const fetchData = async () => {
      // 假设这里是从后端获取数据的逻辑
      return { data: [] };
    };
    const initData = async () => {
      const res = await fetchData();
      tableData.value = res.data;
    };
    initData();
    return {
      tableData
    };
  }
};
</script>

使用 Chrome Devtools 的 Performance 面板记录性能数据后,在火焰图中可以观察到表格渲染相关的函数调用栈和执行时间。如果发现表格渲染时间过长,可以进一步优化数据加载方式,如采用分页加载、虚拟列表等技术,提高表格的渲染性能。

二、后端数据库查询优化

2.1 索引优化

索引是数据库中一种重要的数据结构,它类似于书籍的目录,能够极大地提高数据查询的效率。在 MySQL 中,常见的索引类型有 B - Tree 索引、Hash 索引等,其中 B - Tree 索引最为常用。B - Tree 索引以 B - Tree 数据结构为基础,通过对数据进行排序和分层存储,使得查询时能够快速定位到目标数据所在的位置。例如,在商城的商品表中,如果经常需要根据商品 ID 查询商品信息,为商品 ID 字段创建 B - Tree 索引后,数据库在查询时就可以直接通过索引定位到对应的商品记录,而无需全表扫描,从而大大提高查询速度。

在商城业务中,根据不同的查询场景,需要创建合适的索引。比如,在订单表中,经常会根据用户 ID 和订单状态查询订单信息,此时可以创建一个包含用户 ID 和订单状态的复合索引。创建复合索引的 SQL 语句如下:

sql 复制代码
ALTER TABLE orders ADD INDEX idx_user_status (user_id, status);

这样,当执行查询语句SELECT * FROM orders WHERE user_id =? AND status =?时,数据库就可以利用这个复合索引快速定位到符合条件的订单记录,提高查询效率。需要注意的是,在创建索引时,要避免过度创建索引,因为索引会占用额外的磁盘空间,并且在数据插入、更新和删除时,也需要维护索引结构,会增加一定的开销。

2.2 分页技术

分页是指将大数据集分割成一系列较小的数据块(即页面),每次仅加载其中一部分数据供用户查看。在商城系统中,当展示商品列表、订单列表等大量数据时,分页技术尤为重要。它不仅可以减少单次查询的数据量,降低网络传输负担和数据库负载,还能提高页面加载速度,使用户操作更加流畅。

在 Spring Boot 结合 Mybatis - plus 实现分页查询时,首先需要配置分页插件。在 Spring Boot 的配置类中添加如下代码:

java 复制代码
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MyBatisPlusConfig {
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
        return interceptor;
    }
}

上述代码创建了一个MybatisPlusInterceptor拦截器,并添加了PaginationInnerInterceptor分页插件,用于实现分页功能。

在 Service 层中,使用分页插件进行分页查询的代码示例如下:

java 复制代码
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

@Service
public class ProductService {
    @Resource
    private ProductMapper productMapper;

    public IPage<Product> getProductPage(int current, int size) {
        Page<Product> page = new Page<>(current, size);
        return productMapper.selectPage(page, null);
    }
}

在上述代码中,Page类是 Mybatis - plus 提供的分页对象,通过构造函数传入当前页码current和每页显示的记录数size。productMapper.selectPage(page, null)方法执行分页查询,其中null表示没有查询条件,如果有查询条件,可以传入相应的QueryWrapper对象。

2.3 实现源码示例

假设我们有一个商品表product,包含字段id、name、price、category_id等。以下是优化后的数据库查询语句示例,以及相关 mapper 接口和 Service 层代码。

Mapper 接口:

java 复制代码
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.demo.entity.Product;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface ProductMapper extends BaseMapper<Product> {
}

这里使用 Mybatis - plus 的BaseMapper接口,无需手动编写 SQL 语句,即可实现基本的 CRUD 操作。

Service 层代码:

java 复制代码
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.List;

@Service
public class ProductService {
    @Resource
    private ProductMapper productMapper;

    // 根据分类ID和价格范围查询商品列表并分页
    public IPage<Product> getProductListByCategoryAndPrice(int categoryId, double minPrice, double maxPrice, int current, int size) {
        Page<Product> page = new Page<>(current, size);
        QueryWrapper<Product> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("category_id", categoryId)
               .between("price", minPrice, maxPrice);
        return productMapper.selectPage(page, queryWrapper);
    }

    // 查询热门商品列表(假设热门商品是销量前10的商品)
    public List<Product> getHotProductList() {
        QueryWrapper<Product> queryWrapper = new QueryWrapper<>();
        queryWrapper.orderByDesc("sales_volume")
               .last("limit 10");
        return productMapper.selectList(queryWrapper);
    }
}

在上述代码中,getProductListByCategoryAndPrice方法根据分类 ID 和价格范围进行分页查询,getHotProductList方法查询销量前 10 的热门商品列表。通过合理使用索引和分页技术,以及 Mybatis - plus 提供的强大功能,能够有效地提高数据库查询效率,提升商城系统的性能。

三、缓存技术应用

3.1 Redis 缓存介绍

Redis 是一个开源的、基于内存的高性能键值对存储系统,它支持多种数据结构,如字符串、哈希、列表、集合和有序集合等。Redis 的主要特点和优势使其在商城系统的缓存应用中表现出色。

首先,Redis 具有极高的读写速度。由于数据存储在内存中,其读写操作的时间复杂度通常为 O (1),这使得它能够快速响应数据请求,大大提高了商城系统的访问速度。在高并发的情况下,Redis 能够轻松应对大量的读请求,减少数据库的压力。

其次,Redis 支持丰富的数据结构。在商城中,我们可以利用哈希结构存储商品信息,将商品 ID 作为键,商品的各种属性(如名称、价格、库存等)作为哈希字段和值,这样可以方便地获取和更新商品的各个属性。利用有序集合可以实现商品的排行榜功能,根据商品的销量、评分等指标进行排序。

此外,Redis 还提供了持久化功能,通过 RDB(Redis Database Backup)和 AOF(Append Only File)两种方式,可以将内存中的数据保存到磁盘上,以便在服务器重启后能够恢复数据,保证了数据的安全性和可靠性。

3.2 缓存策略制定

在商城系统中,制定合理的缓存策略至关重要。常见的缓存更新策略有以下几种:

  1. 内存淘汰策略:当 Redis 内存达到设定的最大内存时,会自动触发淘汰机制。常见的淘汰策略有 LRU(Least Recently Used,最近最少使用)、LFU(Least Frequently Used,最不经常使用)、Random(随机)等。LRU 策略会优先淘汰最近最少使用的数据,适用于热点数据经常变化的场景;LFU 策略根据数据的使用频率来淘汰,更适合那些使用频率相对稳定的场景;Random 策略则随机选择数据进行淘汰,在一些对数据访问没有明显规律的场景中可以使用。
  2. 超时剔除策略:为缓存数据设置过期时间,当数据过期时,Redis 会自动将其删除。在商城中,对于一些时效性较强的数据,如限时促销活动信息,可以设置较短的过期时间,保证数据的实时性。同时,在设置过期时间时,可以适当添加随机值,避免大量数据同时过期导致的缓存雪崩问题。
  3. 主动更新策略:当数据库中的数据发生变化时,主动更新缓存中的数据。在商品信息更新时,先更新数据库,然后立即删除或更新对应的缓存数据。为了保证缓存与数据库操作的一致性,在单体系统中,可以将缓存与数据库操作放在一个事务中;在分布式系统中,可以利用 TCC(Try - Confirm - Cancel)等分布式事务方案。

3.3 实现源码解析

在 Spring Boot 项目中集成 Redis 缓存,首先需要在pom.xml文件中添加 Redis 依赖:

xml 复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

然后,在application.properties文件中配置 Redis 连接信息:

yaml 复制代码
spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.password=
spring.redis.database=0

接下来,配置 RedisTemplate,用于操作 Redis 缓存。创建一个配置类RedisConfig:

java 复制代码
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration
public class RedisConfig {
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(factory);

        // 使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值(默认使用JDK的序列化方式)
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);

        // 使用StringRedisSerializer来序列化和反序列化redis的key值
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        template.setKeySerializer(stringRedisSerializer);
        template.setHashKeySerializer(stringRedisSerializer);
        template.setValueSerializer(jackson2JsonRedisSerializer);
        template.setHashValueSerializer(jackson2JsonRedisSerializer);

        template.afterPropertiesSet();
        return template;
    }
}

在业务代码中,使用@Cacheable、@CacheEvict、@CachePut等注解来实现缓存功能。例如,在商品服务中,查询热门商品列表时使用缓存:

java 复制代码
import com.example.demo.entity.Product;
import com.example.demo.service.ProductService;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class ProductServiceImpl implements ProductService {
    // 假设这里有一个查询热门商品列表的方法
    @Cacheable(value = "hotProductList", key = "#root.methodName")
    @Override
    public List<Product> getHotProductList() {
        // 实际查询数据库的逻辑
        // 这里省略具体实现
        return null;
    }
}

在上述代码中,@Cacheable注解表示该方法的返回值会被缓存起来。value属性指定缓存的名称,key属性指定缓存的键,这里使用#root.methodName表示以方法名作为缓存键。当第一次调用该方法时,会执行方法体内的数据库查询逻辑,并将结果存入缓存;后续再次调用该方法时,如果缓存中存在对应的数据,则直接从缓存中获取,不再执行数据库查询操作,从而提高了系统的性能。

四、总结与展望

商城性能优化是一个持续且系统的工程,涉及前端、后端以及缓存等多个关键领域。通过利用 Chrome Devtools 等工具对前端性能瓶颈进行精准定位,优化后端数据库查询语句,合理应用索引、分页技术,以及引入 Redis 缓存技术来缓存热门数据,我们能够显著提升商城系统的性能和用户体验。

持续优化对于商城系统的长期稳定发展至关重要。随着业务的不断增长和用户需求的日益多样化,商城系统面临的性能挑战也会不断变化。我们需要持续关注系统的性能指标,定期进行性能测试和分析,及时发现潜在的性能问题,并采取相应的优化措施。

未来,随着技术的不断进步,商城性能优化也将迎来更多的机遇和挑战。在前端方面,WebAssembly 等新兴技术的发展可能会为前端性能带来质的飞跃;在后端,分布式数据库、云原生技术等的应用将为系统的扩展性和性能优化提供更多的可能性;缓存技术也将不断演进,如 Redis 的集群部署、缓存一致性算法的改进等,将进一步提升缓存的性能和可靠性。我们应紧跟技术发展的步伐,不断探索和应用新的技术和方法,持续优化商城系统的性能,为用户提供更加高效、稳定的购物体验。

相关推荐
酱学编程2 小时前
redis 延迟双删
数据库·redis·缓存
不知名美食探索家3 小时前
【11】Redis快速安装与Golang实战指南
redis·golang·bootstrap
怪兽也会哭哭5 小时前
网页部署到宝塔服务器上,发送请求报错?org.springframework.data.redis.RedisSystemException,让我来看看
运维·服务器·redis
沸材5 小时前
Redis——实现消息队列
数据库·redis·消息队列
菜鸟起航ing7 小时前
【Java面试系列】Spring Boot微服务架构下的分布式事务解决方案与性能优化详解 - 3-5年Java开发必备知识
java·spring boot·微服务·性能优化·分布式事务
小薛博客7 小时前
架构设计之Redisson分布式锁-可重入同步锁(一)
java·redis·分布式
dr李四维8 小时前
解决缓存穿透的布隆过滤器与布谷鸟过滤器:谁更适合你的应用场景?
redis·算法·缓存·哈希算法·缓存穿透·布隆过滤器·布谷鸟过滤器
axinawang8 小时前
spring boot 整合redis
spring boot·redis·bootstrap
桂月二二9 小时前
Vue3性能优化终极指南:编译策略、运行时调优与全链路监控
性能优化
Pitayafruit11 小时前
【📕分布式锁通关指南 09】源码剖析redisson之公平锁的实现
redis·分布式·后端