id生成系统和mp条件简化

目录

场景引入:

 有哪些生成id的方式?

1.UUID

2.雪花算法方案 

3.数据库生成

 4.美团Leaf方案

Leaf-segment数据库方案

使用场景:

美团leaf的docker镜像安装

在leaf.properties中配置数据库的信息

创建sl_leaf数据库脚本:

测试:

业务封装

使用

mp-条件简化


场景引入:

我们在业务里需要经常的创建id号,什么是id号?

id号就是在复杂分布式系统中,往往需要对大量的数据和消息进行唯一标识。

如在美团点评的金融、支付、餐饮、酒店、猫眼电影等产品的系统中,数据日渐增长,对数据分库分表后需要有一个唯一ID来标识一条数据或消息,数据库的自增ID显然不能满足需求;特别一点的如订单、骑手、优惠券也都需要有唯一ID做标识。此时一个能够生成全局唯一ID的系统是非常必要的。概括下来,那业务系统对ID号的要求有哪些呢?

  1. 全局唯一性:不能出现重复的ID号,既然是唯一标识,这是最基本的要求。
  2. 趋势递增:在MySQL InnoDB引擎中使用的是聚集索引,由于多数RDBMS使用B-tree的数据结构来存储索引数据,在主键的选择上面我们应该尽量使用有序的主键保证写入性能。
  3. 单调递增:保证下一个ID一定大于上一个ID,例如事务版本号、IM增量消息、排序等特殊需求。
  4. 信息安全:如果ID是连续的,恶意用户的扒取工作就非常容易做了,直接按照顺序下载指定URL即可;如果是订单号就更危险了,竞对可以直接知道我们一天的单量。所以在一些应用场景下,会需要ID无规则、不规则。

上述123对应三类不同的场景,3和4需求还是互斥的,无法使用同一个方案满足。

由此总结下一个ID生成系统应该做到如下几点:

  1. 平均延迟和TP999延迟都要尽可能低;(获取id号的时间需要99.9%<2ms)
  2. 可用性5个9;(获取id号的请求需要99.999%发送成功)
  3. 高QPS。(一秒内要可以处理多个请求)

 有哪些生成id的方式?

1.UUID

UUID(Universally Unique Identifier)的标准型式包含32个16进制数字,以连字号分为五段,形式为8-4-4-4-12的36个字符,示例:550e8400-e29b-41d4-a716-446655440000,到目前为止业界一共有5种方式生成UUID。

优点:

  • 性能非常高:本地生成,没有网络消耗。

缺点:

  • 不易于存储:UUID太长,16字节128位,通常以36长度的字符串表示,很多场景不适用。
  • 信息不安全:基于MAC地址生成UUID的算法可能会造成MAC地址泄露,这个漏洞曾被用于寻找梅丽莎病毒的制作者位置。

  • ID作为主键时在特定的环境会存在一些问题,比如做DB主键的场景下,UUID就非常不适用:

  • ① MySQL官方有明确的建议主键要尽量越短越好[4],36个字符长度的UUID不符合要求。

  • ② 对MySQL索引不利:如果作为数据库主键,在InnoDB引擎下,UUID的无序性可能会引起数据位置频繁变动,严重影响性能。

2.雪花算法方案 

这种方案大致来说是一种以划分命名空间(UUID也算,由于比较常见,所以单独分析)来生成ID的一种算法,这种方案把64-bit分别划分成多段,分开来标示机器、时间等,比如在snowflake中的64-bit分别表示如下图所示:

41-bit的时间可以表示(1L<<41)/(1000L*3600*24*365)=69年的时间,10-bit机器可以分别表示1024台机器。如果我们对IDC划分有需求,还可以将10-bit分5-bit给IDC,分5-bit给工作机器。这样就可以表示32个IDC,每个IDC下可以有32台机器,可以根据自身需求定义。12个自增序列号可以表示2^12个ID,理论上snowflake方案的QPS约为409.6w/s,这种分配方式可以保证在任何一个IDC的任何一台机器在任意毫秒内生成的ID都是不同的。

这种方式的优缺点是:

优点:

  • 毫秒数在高位,自增序列在低位,整个ID都是趋势递增的。
  • 不依赖数据库等第三方系统,以服务的方式部署,稳定性更高,生成ID的性能也是非常高的。
  • 可以根据自身业务特性分配bit位,非常灵活。

缺点:

  • 强依赖机器时钟,如果机器上时钟回拨,会导致发号重复或者服务会处于不可用状态。

 雪花算法多用于数据库中的主键id生成,如mybatis-plus的默认产生主键id的方式就是雪花算法。

3.数据库生成

以MySQL举例,利用给字段设置auto_increment_incrementauto_increment_offset来保证ID自增,每次业务使用下列SQL读写MySQL得到ID号。

这种方案的优缺点如下:

优点:

  • 非常简单,利用现有数据库系统的功能实现,成本小,有DBA专业维护。
  • ID号单调自增,可以实现一些对ID有特殊要求的业务。

缺点:

  • 强依赖DB,当DB异常时整个系统不可用,属于致命问题。配置主从复制可以尽可能的增加可用性,但是数据一致性在特殊情况下难以保证。主从切换时的不一致可能会导致重复发号。
  • ID发号性能瓶颈限制在单台MySQL的读写性能。

 4.美团Leaf方案

Leaf-segment数据库方案

第一种Leaf-segment方案,在使用数据库的方案上,做了如下改变: - 原方案每次获取ID都得读写一次数据库,造成数据库压力大。改为利用proxy server批量获取,每次获取一个segment(step决定大小)号段的值。用完之后再去数据库获取新的号段,可以大大的减轻数据库的压力。 - 各个业务不同的发号需求用biz_tag字段来区分,每个biz-tag的ID获取相互隔离,互不影响。如果以后有性能需求需要对数据库扩容,不需要上述描述的复杂的扩容操作,只需要对biz_tag分库分表就行。

字段说明:

  • biz_tag:业务标签,用来区分业务
  • max_id:表示该biz_tag目前所被分配的ID号段的最大值
  • step:表示每次分配的号段长度。如果把step设置得足够大,比如1000,那么只有当1000个号被消耗完了之后才会去重新读写一次数据库。
  • description:描述
  • update_time:更新时间

 test_tag在第一台Leaf机器上是1~1000的号段,当这个号段用完时,会去加载另一个长度为step=1000的号段,假设另外两台号段都没有更新,这个时候第一台机器新加载的号段就应该是3001~4000。同时数据库对应的biz_tag这条数据的max_id会从3000被更新成4000,更新号段的SQL语句如下:

这种模式有以下优缺点:

优点:

  • Leaf服务可以很方便的线性扩展,性能完全能够支撑大多数业务场景。
  • ID号码是趋势递增的8byte的64位数字,满足上述数据库存储的主键要求。
  • 容灾性高:Leaf服务内部有号段缓存,即使DB宕机,短时间内Leaf仍能正常对外提供服务。
  • 可以自定义max_id的大小,非常方便业务从原有的ID方式上迁移过来。

缺点:

  • ID号码不够随机,能够泄露发号数量的信息,不太安全。
  • TP999数据波动大,当号段使用完之后还是会hang在更新数据库的I/O上,tg999数据会出现偶尔的尖刺。(就是使用完当前字段后,需要去数据库申请新的字段,所以会突然导致性能开销突然增大)
  • DB宕机会造成整个系统不可用。

如何解决TP999数据波动大

Leaf 取号段的时机是在号段消耗完的时候进行的,也就意味着号段临界点的ID下发时间取决于下一次从DB取回号段的时间,并且在这期间进来的请求也会因为DB号段没有取回来,导致线程阻塞。如果请求DB的网络和DB的性能稳定,这种情况对系统的影响是不大的,但是假如取DB的时候网络发生抖动,或者DB发生慢查询就会导致整个系统的响应时间变慢。

为此,我们希望DB取号段的过程能够jie做到无阻塞,不需要在DB取号段的时候阻塞请求线程,即当号段消费到某个点时就异步的把下一个号段加载到内存中。而不需要等到号段用尽的时候才去更新号段(类似于饥饿模式,不要等到当前号段全部消费完之后才去数据库申请新的号段,而是当前字段消耗到10%时,就新开一个线程去数据库申请新的号段,并把这些新的号段存于第二个缓存区中,当第一个缓存区的号段使用完时,可以直接使用第二个缓存区的号段,以此重复)。这样做就可以很大程度上的降低系统的TP999指标。详细实现如下图所示:

 

采用双buffer的方式,Leaf服务内部有两个号段缓存区segment。当前号段已下发10%时,如果下一个号段未更新,则另启一个更新线程去更新下一个号段。当前号段全部下发完后,如果下个号段准备好了则切换到下个号段为当前segment接着下发,循环往复。

  • 每个biz-tag都有消费速度监控,通常推荐segment长度设置为服务高峰期发号QPS的600倍(10分钟),这样即使DB宕机,Leaf仍能持续发号10-20分钟不受影响。

  • 每次请求来临时都会判断下个号段的状态,从而更新此号段,所以偶尔的网络抖动不会影响下个号段的更新。

使用场景:

现在我们的业务需要生成特定格式的id,如生成一个运单id,需要加HL(前缀)+13个数字 ,显然雪花算法生成的19为数字id是不符合的,所以我们使用美团leaf。

美团leaf的docker镜像安装

docker run \
-d \
-v /itcast/meituan-leaf/leaf.properties:/app/conf/leaf.properties \
--name meituan-leaf \
-p 28838:8080 \
--restart=always \
registry.cn-hangzhou.aliyuncs.com/itheima/meituan-leaf:1.0.1

在leaf.properties中配置数据库的信息

leaf.name=leaf-server
leaf.segment.enable=true
leaf.jdbc.url=jdbc:mysql://192.168.150.101:3306/sl_leaf?useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true&useSSL=false
leaf.jdbc.username=root
leaf.jdbc.password=123

leaf.snowflake.enable=false
leaf.snowflake.zk.address=
leaf.snowflake.port=

创建sl_leaf数据库脚本:

CREATE TABLE `leaf_alloc` (
  `biz_tag` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '',
  `max_id` bigint NOT NULL DEFAULT '1',
  `step` int NOT NULL,
  `description` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL,
  `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`biz_tag`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
-- 插入运单号生成规划数据
INSERT INTO `leaf_alloc` (`biz_tag`, `max_id`, `step`, `description`, `update_time`) VALUES ('transport_order', 10000000000, 100, 'Test leaf Segment Mode Get Id', '2022-07-07 11:32:16');

测试:

# transport_order 与 biz_tag字段的值相同
http://192.168.150.101:28838/api/segment/get/transport_order
#监控
http://192.168.150.101:28838/cache

 获取了第一个id,此时max_id最大为100,因为是直接获取了100的号段

当获取到10%时

根据双缓存,已经又获取了100号段的id存在于第二个缓存区中 

 

业务封装

@Service
public class IdService {

    @Value("${sl.id.leaf:}")
    private String leafUrl;

    /**
     * 生成自定义id
     *
     * @param idEnum id配置
     * @return id值
     */
    public String getId(IdEnum idEnum) {
        String idStr = this.doGet(idEnum);
        return idEnum.getPrefix() + idStr;
    }

    private String doGet(IdEnum idEnum) {
        if (StrUtil.isEmpty(this.leafUrl)) {
            throw new SLException("生成id,sl.id.leaf配置不能为空.");
        }
        //访问leaf服务获取id
        String url = StrUtil.format("{}/api/{}/get/{}", this.leafUrl, idEnum.getType(), idEnum.getBiz());
        //设置超时时间为10s
        HttpResponse httpResponse = HttpRequest.get(url)
                .setReadTimeout(10000)
                .execute();
        if (httpResponse.isOk()) {
            return httpResponse.body();
        }
        throw new SLException(StrUtil.format("访问leaf服务出错,leafUrl = {}, idEnum = {}", this.leafUrl, idEnum));
    }

}
public enum IdEnum implements BaseEnum {

    TRANSPORT_ORDER(1, "运单号", "transport_order", "segment", "SL");

    private Integer code;
    private String value;
    private String biz; //业务名称
    private String type; //类型:自增长(segment),雪花id(snowflake)
    private String prefix;//id前缀

    IdEnum(Integer code, String value, String biz, String type, String prefix) {
        this.code = code;
        this.value = value;
        this.biz = biz;
        this.type = type;
        this.prefix = prefix;
    }

    @Override
    public Integer getCode() {
        return this.code;
    }

    @Override
    public String getValue() {
        return this.value;
    }

    public String getBiz() {
        return biz;
    }

    public String getType() {
        return type;
    }

    public String getPrefix() {
        return prefix;
    }

    @Override
    public String toString() {
        final StringBuffer sb = new StringBuffer("IdEnum{");
        sb.append("code=").append(code);
        sb.append(", value='").append(value).append('\'');
        sb.append(", biz='").append(biz).append('\'');
        sb.append(", type='").append(type).append('\'');
        sb.append(", prefix='").append(prefix).append('\'');
        sb.append('}');
        return sb.toString();
    }
}
sl:
  id:
    leaf: 192.168.150.101:28838 #指定美团leaf服务地址

使用

@Resource
private IdService idservice
 
public void test(){
    String id = idService.getId(IdEunm.TRANSPORT_ORDER);
}

mp-条件简化

  //    如果运单id不为空,根据id模糊查询
       

wrapper.like(ObjectUtil.isNotEmpty(transportOrderQueryDTO.getId()),

TransportOrderEntity::getId,transportOrderQueryDTO.getId());

只有这个ObjectUtil.isNotEmpty(transportOrderQueryDTO.getId())条件为true时,才会去进行模糊查询

如果不这样写,需要这样子写

if(ObjectUtil.isNotEmpty(transportOrderQueryDTO.getId()){

wrapper.like(TransportOrderEntity::getId,transportOrderQueryDTO.getId());

}

看起来比较冗余

    @Override
    public Page<TransportOrderEntity> findByPage(TransportOrderQueryDTO transportOrderQueryDTO) {
        // TODO day06 分页查询运单

        // 封装分页查询参数
        Page<TransportOrderEntity> page = new Page<>(transportOrderQueryDTO.getPage(),transportOrderQueryDTO.getPageSize());
        // 设置查询条件
        LambdaQueryWrapper<TransportOrderEntity> wrapper = Wrappers.<TransportOrderEntity>lambdaQuery();
        //    如果运单id不为空,根据id模糊查询
        wrapper.like(ObjectUtil.isNotEmpty(transportOrderQueryDTO.getId()),TransportOrderEntity::getId,transportOrderQueryDTO.getId());
        //    如果订单orderId不为空,根据orderId模糊查询
        wrapper.like(ObjectUtil.isNotEmpty(transportOrderQueryDTO.getOrderId()),TransportOrderEntity::getOrderId,transportOrderQueryDTO.getOrderId());
        //    如果运单状态不为空,根据运单状态等值查询
        wrapper.eq(ObjectUtil.isNotEmpty(transportOrderQueryDTO.getStatus()),TransportOrderEntity::getStatus,transportOrderQueryDTO.getStatus());
        //    如果运单调度状态不为空,根据运单调度状态等值查询
        wrapper.eq(ObjectUtil.isNotEmpty(transportOrderQueryDTO.getSchedulingStatus()),TransportOrderEntity::getSchedulingStatus,transportOrderQueryDTO.getSchedulingStatus());
        //    如果运单起点网点ID不为空,根据起点网点ID等值查询
        wrapper.eq(ObjectUtil.isNotEmpty(transportOrderQueryDTO.getStartAgencyId()),TransportOrderEntity::getStartAgencyId,transportOrderQueryDTO.getStartAgencyId());
        //    如果运单终点网点ID不为空,根据终点网点ID等值查询
        wrapper.eq(ObjectUtil.isNotEmpty(transportOrderQueryDTO.getEndAgencyId()),TransportOrderEntity::getEndAgencyId,transportOrderQueryDTO.getEndAgencyId());
        //    如果运单当前网点ID不为空,根据当前网点ID等值查询
        wrapper.eq(ObjectUtil.isNotEmpty(transportOrderQueryDTO.getCurrentAgencyId()),TransportOrderEntity::getCurrentAgencyId,transportOrderQueryDTO.getCurrentAgencyId());
        wrapper.orderByDesc(TransportOrderEntity::getCreated);

        return super.page(page,wrapper);
    }

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

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

相关文章

网络安全推荐的视频教程 网络安全系列

第一章 网络安全概述 1.2.1 网络安全概念P4 网络安全是指网络系统的硬件、软件及其系统中的数据受到保护&#xff0c;不因偶然的或恶意的原因而遭到破坏、更改、泄露&#xff0c;系统连续可靠正常地运行&#xff0c;网络服务不中断。 1.2.3 网络安全的种类P5 &#xff08;1…

内网下,Ubuntu (24.10) 离线安装docker最新版教程

一般在数据比较敏感的情况下&#xff0c;是无法使用网络的&#xff0c;而对于Ubuntu系统来说&#xff0c;怎么离线安装docker呢&#xff1f; 下面我给大家来讲一下&#xff1a; 采用二进制安装&#xff1a; 1.下载docker离线包 官网下载&#xff1a; Index of linux/static…

基于SpringBoot+Vue的老年人体检管理系统的设计与实现(源码+SQL脚本+LW+部署讲解等)

专注于大学生项目实战开发,讲解,毕业答疑辅导&#xff0c;欢迎高校老师/同行前辈交流合作✌。 技术范围&#xff1a;SpringBoot、Vue、SSM、HLMT、小程序、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容&#xff1a;…

AI工具篇:利用DeepSeek+Kimi 辅助生成综述汇报PPT

随着科研和学术报告需求的增加&#xff0c;如何高效地准备一份结构清晰、内容充实的PPT已成为许多研究者的挑战。 传统的PPT制作过程繁琐&#xff0c;需要大量文献收集、数据分析和设计工作&#xff0c;而AI工具能够帮助提升效率&#xff0c;减少重复劳动。 本文将介绍如何使用…

【Spring详解一】Spring整体架构和环境搭建

一、Spring整体架构和环境搭建 1.1 Spring的整体架构 Spring框架是一个分层架构&#xff0c;包含一系列功能要素&#xff0c;被分为大约20个模块 Spring核心容器&#xff1a;包含Core、Bean、Context、Expression Language模块 Core &#xff1a;其他组件的基本核心&#xff…

为什么WP建站更适合于谷歌SEO优化?

在当今数字时代&#xff0c;建立一个网站似乎变得容易&#xff0c;但要构建一个真正能够带来流量和订单的网站却并非易事。特别是在谷歌SEO优化方面&#xff0c;不同的建站程序在SEO支持方面的效果差异显著。对于希望提升搜索引擎表现的用户来说&#xff0c;WordPress无疑是最佳…

Vue 项目中逐步引入 TypeScript 的类型检查

在现有的 Vue 项目中逐步引入 TypeScript 的类型检查 本文源于一道面试题&#xff1a;注&#xff1a;两种问法一个意思哈&#xff01;&#xff01; 问题一&#xff1a;“ 老项目Js写的&#xff0c;如何轻量方式享受 ts 类型&#xff1f;” 问题二&#xff1a;“如何 在现有的 …

win10系统上的虚拟机安装麒麟V10系统提示找不到操作系统

目录预览 一、问题描述二、原因分析三、解决方案四、参考链接 一、问题描述 win10系统上的虚拟机安装麒麟V10系统提示找不到操作系统&#xff0c;报错&#xff1a;Operating System not found 二、原因分析 国产系统&#xff0c;需要注意的点&#xff1a; 需要看你的系统类…

Spring中Bean的四种实例化方法

Bean的四种实例化方法 Bean是Spring核心的概念&#xff0c;另外一个核心的概念是AOP。官网上&#xff0c;Bean的解释是&#xff1a; In Spring, the objects that form the backbone of your application and that are managed by the Spring IoC container are called beans…

聚焦工控物联网网关

一、引言 在工业物联网高速发展的当下&#xff0c;工控物联网网关作为连接工业现场设备与上层管理系统、云平台的关键组件&#xff0c;其兼容性与可扩展性至关重要。工业生产新技术、新设备不断涌现&#xff0c;企业数字化转型需求持续增长&#xff0c;网关的适配与扩展能力直…

李宏毅机器学习笔记:【6.Optimization、Adaptive Learning Rate】

Optimization 1.Adaptive Learning Rate2.不同的参数需要不同的学习率3.Root Mean Square4.RMSProp5.Adam6.learning rate scheduling7.warm up总结 critical point不一定是你在训练一个network时候遇到的最大的障碍。 1.Adaptive Learning Rate 也就是我们要给每个参数不同的…

Redis7——基础篇(三)

前言&#xff1a;此篇文章系本人学习过程中记录下来的笔记&#xff0c;里面难免会有不少欠缺的地方&#xff0c;诚心期待大家多多给予指教。 基础篇&#xff1a; Redis&#xff08;一&#xff09;Redis&#xff08;二&#xff09; 接上期内容&#xff1a;上期完成了Redis的基本…

[LeetCode]day25 151.翻转字符串里的单词

题目链接 题目链接 给你一个字符串 s &#xff0c;请你反转字符串中 单词 的顺序。 单词 是由非空格字符组成的字符串。s 中使用至少一个空格将字符串中的 单词 分隔开。 返回 单词 顺序颠倒且 单词 之间用单个空格连接的结果字符串。 注意&#xff1a;输入字符串 s中可能…

Visual Studio Code的下载安装与汉化

1.下载安装 Visual Studio Code的下载安装十分简单&#xff0c;在本电脑的应用商店直接下载安装----注意这是社区版-----一般社区版就足够用了---另外注意更改安装地址 2.下载插件 重启后就是中文版本了

华为昇腾 910B 部署 DeepSeek-R1 蒸馏系列模型详细指南

本文记录 在 华为昇腾 910B(65GB) * 8 上 部署 DeepSeekR1 蒸馏系列模型&#xff08;14B、32B&#xff09;全过程与测试结果。 NPU&#xff1a;910B3 (65GB) * 8 &#xff08;910B 有三个版本 910B1、2、3&#xff09; 模型&#xff1a;DeepSeek-R1-Distill-Qwen-14B、DeepSeek…

【MySQL排错 】mysql: command not found 数据库安装后无法加载的解决办法

【MySQL排错 】mysql: command not found 数据库安装后无法加载的解决办法 A Solution to Solve Error - mysql: command not found After The Installation of MySQL Community Server By JacksonML 本文简要介绍如何在macOS安装完毕MySQL数据库服务器后&#xff0c;针对无…

Spring-GPT智谱清言AI项目(附源码)

一、项目介绍 本项目是Spring AI第三方调用整合智谱请言&#xff08;官网是&#xff1a;https://open.bigmodel.cn&#xff09;的案例&#xff0c;回答响应流式输出显示&#xff0c;这里使用的是免费模型&#xff0c;需要其他模型可以去 https://www.bigmodel.cn/pricing 切换…

用deepseek学大模型08-卷积神经网络(CNN)

yuanbao.tencent.com 从入门到精通卷积神经网络(CNN),着重介绍的目标函数&#xff0c;损失函数&#xff0c;梯度下降 标量和矩阵形式的数学推导&#xff0c;pytorch真实能跑的代码案例以及模型,数据&#xff0c;预测结果的可视化展示&#xff0c; 模型应用场景和优缺点&#xf…

deepseek-v3在阿里云和腾讯云的使用中的差异

随着deepseek在各大云商上线&#xff0c;试用了下阿里云和腾讯云的deepseek服务&#xff0c;在回答经典数学问题9.9和9.11谁大时&#xff0c;发现还是有差异的。将相关的问题记录如下。 1、问题表现 笔者使用的openai的官方sdk go-openai。 因本文中测验主要使用阿里云和腾讯…

React 与 Vue 对比指南 - 上

React 与 Vue 对比指南 - 上 本文将展示如何在 React 和 Vue 中实现常见功能&#xff0c;从基础渲染到高级状态管理 Hello 分别使用 react 和 vue 写一个 Hello World&#xff01; react export default () > {return <div>Hello World!</div>; }vue <…