生产环境中秒杀接口并发量剧增与负载优化策略探讨


✨✨谢谢大家捧场,祝屏幕前的小伙伴们每天都有好运相伴左右,一定要天天开心哦!✨✨ 
🎈🎈作者主页: 喔的嘛呀🎈🎈

目录

引言

1. 实施限流措施

1.1 令牌桶算法:

1.2 漏桶算法:

1.3 使用限流框架:

2. 优化数据库操作

2.1. 索引优化

2.2. 批量操作减少交互次数:

2.3. 避免全表扫描:

2.4使用InnoDB引擎:

2.5优化事务范围:

3. 使用缓存技术

3.1. 选择合适的缓存系统:

3.2. 缓存热门商品信息:

3.3. 缓存热门商品列表:

3.4. 使用缓存刷新策略:

3.5. 使用缓存预热:

4. 异步处理订单

4.1. 选择合适的消息队列系统:

4.2. 订单生成异步化:

4.3. 异步处理订单消息监听:

4.4. 保证消息的可靠性:

4.5. 处理失败消息的重试和补偿:

5. 水平扩展

5.1. 负载均衡:

5.2. 横向添加服务器节点:

5.3. 数据库水平分片:

5.4. 服务的横向拆分:

5.5. 监控和自动化:

5.6. 弹性计算和容灾:

6、 灰度发布和回滚

7、总结



引言

在生产环境中,秒杀活动通常是一项高并发的任务,因为大量用户在同一时刻竞相购买限量商品。这种高并发可能导致服务器压力剧增,造成系统崩溃或响应缓慢。为了解决这一问题,我们可以采用一系列优化措施,下面详细介绍一下

1. 实施限流措施

实施限流措施是在高并发场景下保护系统稳定性的关键步骤。限流可以控制请求的并发访问量,避免系统因瞬时高并发而崩溃。以下是一些实施限流措施的方法:

1.1 令牌桶算法:

令牌桶算法是一种常用的限流算法,它基于令牌桶的概念,系统以固定的速率往令牌桶中放入令牌,而接口访问时需要获取令牌,没有令牌的请求将被拒绝。

public class TokenBucket {
    private int capacity; // 桶的容量
    private int tokens;   // 当前令牌数量
    private long lastRefillTime; // 上次令牌刷新时间

    public TokenBucket(int capacity, int tokensPerSecond) {
        this.capacity = capacity;
        this.tokens = capacity;
        this.lastRefillTime = System.currentTimeMillis();
        scheduleRefill(tokensPerSecond);
    }

    public synchronized boolean tryConsume() {
        refill();
        if (tokens > 0) {
            tokens--;
            return true;
        } else {
            return false;
        }
    }

    private void refill() {
        long now = System.currentTimeMillis();
        if (now > lastRefillTime) {
            long elapsedTime = now - lastRefillTime;
            int tokensToAdd = (int) (elapsedTime / 1000); // 每秒放入令牌
            tokens = Math.min(capacity, tokens + tokensToAdd);
            lastRefillTime = now;
        }
    }

    private void scheduleRefill(int tokensPerSecond) {
        ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
        scheduler.scheduleAtFixedRate(() -> refill(), 1, 1, TimeUnit.SECONDS);
    }
}

1.2 漏桶算法:

漏桶算法是另一种常用的限流算法,它模拟一个漏桶,请求以恒定的速度流入漏桶,漏桶以固定的速度漏水。如果桶满了,多余的请求将被丢弃或排队。

public class LeakyBucket {
    private int capacity; // 桶的容量
    private int water;    // 当前水量
    private long lastLeakTime; // 上次漏水时间

    public LeakyBucket(int capacity, int leaksPerSecond) {
        this.capacity = capacity;
        this.water = 0;
        this.lastLeakTime = System.currentTimeMillis();
        scheduleLeak(leaksPerSecond);
    }

    public synchronized boolean tryConsume() {
        leak();
        if (water > 0) {
            water--;
            return true;
        } else {
            return false;
        }
    }

    private void leak() {
        long now = System.currentTimeMillis();
        if (now > lastLeakTime) {
            long elapsedTime = now - lastLeakTime;
            int leaks = (int) (elapsedTime / 1000); // 每秒漏水
            water = Math.max(0, water - leaks);
            lastLeakTime = now;
        }
    }

    private void scheduleLeak(int leaksPerSecond) {
        ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
        scheduler.scheduleAtFixedRate(() -> leak(), 1, 1, TimeUnit.SECONDS);
    }
}

1.3 使用限流框架:

除了手动实现限流算法外,也可以使用一些成熟的限流框架,如Guava RateLimiter、Spring Cloud Gateway等,它们提供了简便的接口和配置,可以快速实施限流措施。

// 使用Guava RateLimiter
RateLimiter rateLimiter = RateLimiter.create(10.0); // 每秒放入10个令牌
if (rateLimiter.tryAcquire()) {
    // 执行业务逻辑
} else {
    // 请求被限流
}

限流的选择要根据系统的实际情况、业务需求和性能测试来确定。限流是保护系统稳定性的有效手段,但需要注意的是,过于严格的限流可能影响到用户体验,因此需要在系统性能和用户体验之间找到平衡。

2. 优化数据库操作

优化数据库操作是提高系统性能的重要一环,尤其在高并发的秒杀场景下,数据库操作的效率直接影响系统的响应速度。以下是一些优化数据库操作的方法:

2.1. 索引优化

确保数据库表的关键字段上建立了适当的索引,特别是在经常用于查询和更新的字段上。索引可以加速查询操作。

-- 为product表的id和stock字段创建索引
CREATE INDEX idx_product_id ON product(id);
CREATE INDEX idx_product_stock ON product(stock);

2.2. 批量操作减少交互次数:

减少与数据库的交互次数,使用批量操作来提高性能,特别是在更新库存等操作时。

// 批量更新库存
public class ProductService {
    public void updateProductStockBatch(List<Integer> productIds) {
        try (Connection connection = dataSource.getConnection();
             PreparedStatement statement = connection.prepareStatement("UPDATE product SET stock = stock - 1 WHERE id = ?")) {
            for (int productId : productIds) {
                statement.setInt(1, productId);
                statement.addBatch();
            }
            statement.executeBatch();
        } catch (SQLException e) {
            // 异常处理...
        }
    }
}

2.3. 避免全表扫描:

使用合适的查询条件和索引,避免全表扫描,提高查询效率。

// 使用索引进行查询和更新
public class ProductService {
    public Product getSeckillProductInfo(int productId) {
        String sql = "SELECT * FROM product WHERE id = ? FOR UPDATE";
        // 执行查询...
    }

    public boolean updateProductStock(int productId) {
        String sql = "UPDATE product SET stock = stock - 1 WHERE id = ?";
        // 执行更新...
    }
}

2.4使用InnoDB引擎:

InnoDB引擎支持行级锁和事务,适合高并发的秒杀场景。

-- 将商品表的引擎设置为InnoDB
ALTER TABLE product ENGINE=InnoDB;

2.5优化事务范围:

减小事务的范围,尽量在生成订单等操作之前提交事务,减少锁的持有时间。

// 代码示例(优化事务范围)
public class SeckillService {
    @Transactional
    public void handleSeckillRequest() {
        // 处理秒杀请求逻辑
        // ...

        // 生成订单数据
        OrderData orderData = generateOrderData();

        // 提交事务
        orderService.createOrder(orderData);
    }
}

以上是在数据库层面进行秒杀接口的深度优化的一些建议。这些建议包括索引优化、数据库引擎选择、事务控制、查询优化等方面(这只是数据库优化其中的几点),通过合理的配置和优化,可以提高系统的并发处理能力,确保秒杀活动的顺利进行。

3. 使用缓存技术

增加缓存层,将热门数据缓存起来,减轻数据库压力,提高读取速度。

3.1. 选择合适的缓存系统:

选择适合自己业务场景的缓存系统,常见的包括:

  • Redis: 支持多种数据结构,适用于缓存和计数器等场景。
  • Memcached: 简单高效,适用于简单的键值缓存。
  • Ehcache: Java本地缓存库,适用于单节点缓存。

3.2. 缓存热门商品信息:

将热门商品信息缓存在缓存中,避免每次请求都访问数据库。

// 商品信息缓存
public class ProductCache {
    private static final Cache<Integer, Product> productCache = Caffeine.newBuilder()
            .maximumSize(1000) // 缓存容量
            .expireAfterWrite(10, TimeUnit.MINUTES) // 缓存过期时间
            .build();

    public Product getSeckillProductInfo(int productId) {
        Product product = productCache.get(productId, key -> ProductDAO.get(productId));
        return product;
    }
}

3.3. 缓存热门商品列表:

缓存热门商品列表,避免频繁查询数据库。

// 热门商品列表缓存
public class PopularProductCache {
    private static final Cache<String, List<Product>> popularProductCache = Caffeine.newBuilder()
            .maximumSize(10) // 缓存容量
            .expireAfterWrite(5, TimeUnit.MINUTES) // 缓存过期时间
            .build();

    public List<Product> getPopularProducts() {
        return popularProductCache.get("popular", key -> ProductDAO.getPopularProducts());
    }
}

3.4. 使用缓存刷新策略:

设置合适的缓存刷新策略,确保缓存中的数据保持与数据库一致。

// 商品信息缓存刷新策略
public class ProductCache {
    private static final Cache<Integer, Product> productCache = Caffeine.newBuilder()
            .maximumSize(1000) // 缓存容量
            .expireAfterWrite(10, TimeUnit.MINUTES) // 缓存过期时间
            .refreshAfterWrite(1, TimeUnit.MINUTES) // 刷新间隔
            .build();

    public Product getSeckillProductInfo(int productId) {
        return productCache.get(productId, key -> refreshProductInfo(productId));
    }

    private Product refreshProductInfo(int productId) {
        // 从数据库中重新加载商品信息
        return ProductDAO.get(productId);
    }
}

3.5. 使用缓存预热:

在系统启动时,预先加载热门数据到缓存中,提高系统初始性能。

// 缓存预热
public class CacheWarmUp {
    @PostConstruct
    public void warmUpCache() {
        // 预先加载热门商品信息到缓存中
        List<Integer> popularProductIds = ProductDAO.getPopularProductIds();
        for (int productId : popularProductIds) {
            ProductCache.getSeckillProductInfo(productId);
        }
    }
}

以上是一些使用缓存技术的具体建议,通过合理选择缓存系统、缓存热门数据、设置刷新策略和预热缓存,可以显著提高系统的读取速度,降低对数据库的压力,特别在高并发的秒杀场景下,是确保系统稳定性和性能的重要手段。

4. 异步处理订单

在高并发的秒杀场景下,异步处理订单是一种有效的策略,可以降低同步处理的压力,提高系统的吞吐量。以下是一些关于如何异步处理订单的具体建议:

4.1. 选择合适的消息队列系统:

选择适合业务需求的消息队列系统,常见的包括:

  • RabbitMQ: 稳定可靠,支持多种消息模型。
  • Kafka: 高吞吐量,适用于分布式系统。
  • ActiveMQ: 开源的JMS消息队列,支持多种传输协议。

4.2. 订单生成异步化:

将订单生成过程异步化,将订单信息发送到消息队列,由后台异步处理。

// 订单生成异步化
public class OrderService {
    @Autowired
    private RabbitTemplate rabbitTemplate;

    public void createOrderAsync(OrderData orderData) {
        rabbitTemplate.convertAndSend("order-exchange", "order.create", orderData);
    }
}

4.3. 异步处理订单消息监听:

设置订单处理的消息监听器,监听消息队列中的订单消息,并进行处理。

// 异步处理订单消息监听
@Component
public class OrderMessageListener {

    @RabbitListener(queues = "order.create.queue")
    public void handleMessage(OrderData orderData) {
        // 处理订单逻辑
        // ...
    }
}

4.4. 保证消息的可靠性:

设置消息队列的确认机制,保证消息的可靠性投递。

// RabbitMQ消息确认机制配置
@Configuration
public class RabbitMQConfig {

    @Bean
    public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
        RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
        rabbitTemplate.setConfirmCallback((correlationData, ack, cause) -> {
            if (!ack) {
                // 消息发送失败的处理逻辑
            }
        });
        return rabbitTemplate;
    }
}

4.5. 处理失败消息的重试和补偿:

设置消息队列的重试机制和补偿机制,确保订单处理失败时能够及时重试或进行补偿。

// RabbitMQ消息重试和补偿配置
@Configuration
public class RabbitMQConfig {

    @Bean
    public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory(ConnectionFactory connectionFactory) {
        SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
        factory.setConnectionFactory(connectionFactory);
        factory.setConcurrentConsumers(3); // 并发消费者数量
        factory.setMaxConcurrentConsumers(10); // 最大并发消费者数量
        factory.setAcknowledgeMode(AcknowledgeMode.MANUAL); // 手动确认模式
        factory.setDefaultRequeueRejected(false); // 不重新入队列
        factory.setErrorHandler(new ConditionalRejectingErrorHandler(new CustomFatalExceptionStrategy())); // 自定义异常处理策略
        factory.setRetryTemplate(new RetryTemplate()); // 重试模板
        return factory;
    }
}

通过异步处理订单,可以将订单生成和处理过程解耦,提高系统的吞吐量和性能。选择合适的消息队列系统、确保消息的可靠性、设置消息的监听器和确认机制,以及处理失败消息的重试和补偿,都是确保异步处理订单稳定可靠的关键步骤。

5. 水平扩展

 考虑对系统进行水平扩展,通过增加服务器节点来分担负载。

5.1. 负载均衡:

使用负载均衡器,将流量均匀分发到多个服务器节点上,避免单一节点负载过重。

  • 硬件负载均衡器: 使用专门的硬件设备,如F5、Citrix等。
  • 软件负载均衡器: 使用软件实现,如Nginx、HAProxy等。

5.2. 横向添加服务器节点:

逐步增加服务器节点,将请求分发到不同的节点上,实现水平扩展。

  • 云服务提供商: 使用云服务提供商的弹性伸缩功能,根据需求动态增加或减少节点。
  • 容器化: 使用容器化技术,如Docker、Kubernetes等,方便快速部署和扩展。

5.3. 数据库水平分片:

对于数据库,考虑水平分片,将数据分布在不同的数据库节点上,减轻数据库压力。

  • 分库分表: 将数据按照一定规则划分到不同的数据库或表中。
  • 数据库读写分离: 将读和写分布在不同的数据库节点上,提高数据库并发能力。

5.4. 服务的横向拆分:

将系统中的服务进行横向拆分,拆分成多个微服务,每个微服务可以独立部署和扩展。

  • 微服务架构: 使用微服务架构,如Spring Cloud、Dubbo等,将系统拆分成多个小服务。
  • API网关: 使用API网关来统一管理和分发请求到不同的微服务。

5.5. 监控和自动化:

确保系统的监控和自动化机制,及时发现节点故障和负载情况,自动进行水平扩展。

  • 监控工具: 使用监控工具,如Prometheus、Grafana等,实时监测系统的状态。
  • 自动化脚本: 编写自动化脚本,根据负载情况自动进行节点的增加或减少。

5.6. 弹性计算和容灾:

考虑系统的弹性计算和容灾能力,确保在节点故障时系统依然可用。

  • 弹性伸缩: 根据负载情况动态调整节点数量,确保系统弹性。
  • 容灾方案: 设计容灾方案,保证系统在某个节点或区域发生故障时能够继续提供服务。

通过以上水平扩展的方法,可以有效应对系统负载的增加,提高系统的可用性、性能和弹性。在实际应用中,根据具体业务需求和系统特点选择和实施这些扩展方法。

6、 灰度发布和回滚

在采取一些重要的优化或改动时,通过灰度发布逐步验证新的方案,并建立回滚机制,确保在出现问题时能够迅速回退。

7、总结

这些方案的选择取决于具体的业务场景、系统架构和实际问题的症结。通常需要进行系统性能测试,综合考虑系统的可伸缩性、容错性和稳定性,寻找出一个最适合当前情况的综合性解决方案。

祝屏幕前的帅哥美女们,每天都好运爆棚!笑口常开!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/541451.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

精品资料-2024护网HVV实战教程资料合集(共20章)

以下是资料目录&#xff0c;如需下载&#xff0c;请前往星球获取&#xff1a;https://t.zsxq.com/19vwYrf4t 精品推荐&#xff0c;2024护网HVV实战教程资料合集&#xff0c;压缩包内涵大量实战资料&#xff0c;共20章。星球内会持续更新教程包。 01-HW介绍.zip 02-HTTP&Bu…

第六季:RTSP协议详解与实时流视频预览(2)

目录 前言1 网络传输基本知识1.1 OSI七层模型1.2 TCP/IP四层模型1.3 总结 2 RTSP协议详解 前言 经过上篇文章的学习&#xff0c;我们已经了解了H.264标准的基本组成与原理&#xff0c;知道了发送的数据的格式与组成&#xff0c;即NALU。但是数据的发送要通过RTSP协议进行发送&…

Project Euler_Problem 160_Factorial Trailing Digits_费马小定理,威尔逊定理,左右互搏

原题目&#xff1a; 题目大意&#xff1a;1e12的阶乘&#xff0c;不算末尾的0&#xff0c;后5位数字为多少 解题思路&#xff1a; 暴力运算也能算&#xff0c;就是有点慢&#xff0c;优化过后可能也得算个几十分钟 这里考虑使用威尔逊定理费马小定理 用这个方法我们就可以得到…

使用Riverpod在Flutter中创建Todo列表

使用Riverpod在Flutter中创建Todo列表 视频 https://youtu.be/mlbeSD1KSIo https://www.bilibili.com/video/BV1jj42197c8/ 前言 原文 https://ducafecat.com/blog/flutter-todo-list-with-riverpod-guide-02 学习如何使用Riverpod在Flutter中构建一个功能完整的Todo列表应用…

VMware EXSI

VMware ESXI是一款行业领先、专门构建的裸机hypervisor。 它直接通过安装在物理服务器上并将其划分为多个逻辑服务器&#xff0c;也就是虚拟机。 创建过程&#xff1a;就是F11Enter 输入账号密码 添加网卡 配置网卡地址 vsphere Client工具来连接EXSI EXSI资源分割&…

4. Django 探究FBV视图

4. 探究FBV视图 视图(Views)是Django的MTV架构模式的V部分, 主要负责处理用户请求和生成相应的响应内容, 然后在页面或其他类型文档中显示. 也可以理解为视图是MVC架构里面的C部分(控制器), 主要处理功能和业务上的逻辑. 我们习惯使用视图函数处理HTTP请求, 即在视图里定义def…

c++的学习之路:23、多态(2)

摘要 多态的抽象类、原理以及虚表。 目录 摘要 一、抽象类 1、 概念 2、接口继承和实现继承 二、多态的原理 1、虚函数表 2、动态绑定与静态绑定 三、单继承和多继承关系的虚函数表 1、单继承中的虚函数表 2、多继承中的虚函数表 四、思维导图 一、抽象类 1、 概…

移动硬盘(PSSD)中文件占用空间远大于文件大小

定义 文件的大小&#xff1a;文件内容实际具有的字节数&#xff0c;它以Byte为衡量单位&#xff0c;只要文件内容和格式不发生变化&#xff0c;文件大小就不会发生变化。 文件占用空间&#xff1a;文件在磁盘上的所占空间&#xff0c;它最小的计量单位是“簇(Cluster)”。 为…

LeetCode-72. 编辑距离【字符串 动态规划】

LeetCode-72. 编辑距离【字符串 动态规划】 题目描述&#xff1a;解题思路一&#xff1a;动规五部曲解题思路二&#xff1a;动态规划【版本二】解题思路三&#xff1a;0 题目描述&#xff1a; 给你两个单词 word1 和 word2&#xff0c; 请返回将 word1 转换成 word2 所使用的最…

HarmonyOS实战开发-自定义分享

介绍 自定义分享主要是发送方将文本&#xff0c;链接&#xff0c;图片三种类型分享给三方应用,同时能够在三方应用中展示。本示例使用数据请求 实现网络资源的获取&#xff0c;使用屏幕截屏 实现屏幕的截取&#xff0c;使用文件管理 实现对文件&#xff0c;文件目录的管理&…

A Learning-Based Approach for IP Geolocation(2010年)

下载地址:Towards IP geolocation using delay and topology measurements | Proceedings of the 6th ACM SIGCOMM conference on Internet measurement 被引次数:185 Eriksson B, Barford P, Sommers J, et al. A learning-based approach for IP geolocation[C]//Passive …

Linux上的可执行文件在Windows上是不能运行的

一、概要 1、可执行文件的格式 Linux上的可执行文件是elf格式的 Windows上的可执行文件是exe格式的 Linux上的可执行文件在Windows上是不能运行的 2、程序的普通构建与静态构建 普通构建&#xff1a; 一个.c文件&#xff0c;用gcc命令编译成可执行文件(程序)&#xff0c…

打开游戏缺少dll文件怎么办,dll文件一键修复方法

在我们日常操作电脑&#xff0c;经常会遇到各种各样的问题。比如想玩一会游戏的时候&#xff0c;电脑屏幕上却赫然弹出一则令人颇为扫兴的提示&#xff1a;“打开游戏缺少dll文件”。这个问题可能会让我们感到困惑和沮丧&#xff0c;但是幸运的是&#xff0c;有很多方法可以帮助…

AI论文速读 |(图腾) TOTEM:通用时间序列分析的token化时间序列嵌入表示

题目&#xff1a;TOTEM: TOkenized Time Series EMbeddings for General Time Series Analysis 作者&#xff1a;Sabera Talukder ; Yisong Yue ; Georgia Gkioxari 机构&#xff1a;加州理工学院&#xff08;Caltech&#xff09; 网址&#xff1a;https://arxiv.org/abs/24…

Unity DOTS1.0 入门(3) System与SystemGroup 概述

System与SystemGroup 概述 System System是提供一种代码逻辑,改变组件的数据状态,从一个状态到另外一个状态System在main thread里面运行, system.Update方法每一帧执行一次(其他线程中运行的就是JobSystem的事情了&#xff09;System是通过一个System Group这个体系来决定它…

【计算机毕业设计】企业销售人员培训——后附源码

&#x1f389;**欢迎来到琛哥的技术世界&#xff01;**&#x1f389; &#x1f4d8; 博主小档案&#xff1a; 琛哥&#xff0c;一名来自世界500强的资深程序猿&#xff0c;毕业于国内知名985高校。 &#x1f527; 技术专长&#xff1a; 琛哥在深度学习任务中展现出卓越的能力&a…

《积极情绪的力量》 - 三余书屋 3ysw.net

积极情绪的力量 大家好&#xff0c;今天我们解读的这本书名为《积极情绪的力量》。在情绪的世界里&#xff0c;我们可以分为积极和消极两类&#xff0c;但让人留下深刻印象的是&#xff0c;许多人更容易体验到消极情绪&#xff0c;如抑郁、恐惧、焦虑、挫败和烦躁等。这并非令…

【JavaSE进阶】00-基础语法(13-14章) 01-面向对象 02-数组 03-常用类 04-异常处理

13 第十三章 方法覆盖和多态(Polymorphism)★★★★★ 13.1 章节目标与知识框架 13.1.1 章节目标 理解在什么情况下我们需要进行方法覆盖&#xff1f;掌握在满足什么条件的时候构成方法覆盖&#xff1f;什么是多态&#xff0c;代码怎么写&#xff1f;向上转型和向下转型都是…

谷歌推出全新AI代码辅助工具Code Assist,挑战GitHub Copilot|TodayAI

在其Cloud Next大会上&#xff0c;谷歌推出了一款名为Code Assist的AI驱动代码完成工具。该工具原名为Duet AI&#xff0c;现增强了功能并与流行的编辑器兼容。 Code Assist不仅与GitHub的Copilot Enterprise直接竞争&#xff0c;还以百万级的token上下文窗口自豪&#xff0c;…

Vue3.4 中自定义组件 v-model 双向数据绑定

父组件如下: 虽然下面userName没有使用 v-model.userName"userName"的写法,它默认与子组件中 defineModel();中不指定参数的变量对应, 只能有一个不指定名称,否则会报如下错 <template><div>{{ userName}}- {{ age}}- {{ sex }}<!-- 自定义子组件 Per…