阿里微服务架构的分布式事务GTS
阿里云现在已经提供云上统一的分布式事务解决方案,相对于我们以前自己尝试去通过消息结合最终一致性去解决这个问题,这样统一的解决方案仍然值得借鉴和尝试。我觉得如果系统上了阿里云,可以选择这套框架来使用,对中小企业是一种解放,将更多的精力放到业务中去。现把相关的介绍转载记录如下:
一、背景
全局事务服务GTS上个月开始在阿里云上公测,上周我们也发表了一篇「破解世界性技术难题!GTS让分布式事务简单高效」的文章,引起了业界广泛关注,接受了大量的咨询,故希望通过本文让大家对GTS有更深入的理解。
GTS在目前在阿里内外部已经有较大规模的应用,有100多个用户,其中专有云大用户就有10多个,预计今年将有更大规模的市场需求爆发。
二、用户诉求是什么
分布式事务解决的用户最本质诉求是什么?数据一致
。
大中企业有一个共同的诉求是数据一致,几乎覆盖到各个行业。
比如说零售行业,库存与出货的数据需要保持一致,出货量与库存数据不匹配,显而易见会出问题,拿到订单却没货了,或者有货却下不了订单。
比如说金融行业,转账数据搞错了,A扣款了,B没加上,马上该用户投诉了;A没扣款,B却加上了,产生资损;又比如从总账户中买了基金、股票后余额不对了,等等,都会导致严重问题。
比如说车票购票网,用户退票了,但是他买的那个座位状态还是“已售卖”,造成损失;用户购买了,却已经没座了,更是大问题。
数据一致对各个行业都很重要,但为什么以前不觉着是个普遍需求?
以前多数企业的数据规模、业务复杂度相对较小,很多操作可以单机完成,数据库本地事务可以搞定,所以数据一致问题不那么明显。
随着互联网技术快速发展,数据规模增大,分布式系统越来越普及,采用分布式数据库或者跨多个数据库的应用在中大规模企业普遍存在,服务化也是广泛应用,由于网络的不可靠和机器不可靠,数据不一致问题很容易出现。
三、数据一致性问题的现有解决方案
数据一致性问题是必须解决的,在很多大企业多年前就已经成为突出问题,他们是怎么解决的?有这么几个典型方案:
• a)XA事务方案
• b)柔性事务
• c)基于消息的最终一致
• d)业务补偿与人工订正
方案a,XA协议由Tuxedo首先提出的,并交给X/Open组织,作为资源管理器(数据库)与事务管理器的接口标准。Oracle、Informix、DB2和Sybase等各大数据库厂家都提供对XA的支持。 XA协议采用两阶段提交方式来管理分布式事务。
最主要 缺点 是 性能差,容易成为业务发展瓶颈,所以国内很少用户采用。
优点 是 满足事务ACID,有几个很成熟的产品,比较稳定,典型的是Oracle Tuxedo 和 IBM CICS。但是前景不看好,这么多年缺乏重要技术突破,性能上不去,怎么拥抱互联网。
方案b,柔性事务(遵循BASE理论)是指相对于ACID刚性事务而言的,常见的是TCC型事务(Try/Confirm/Cancel)。
最主要缺点 是 业务侵入性太强,需要大量开发工作进行业务改造,给业务升级、运维都带来困难。
优点 是 业务开发人员可以灵活控制事务逻辑,达到很好性能。在蚂蚁金服有广泛使用。以我的理解,这适合特定领域,很难作为通用方案对外大面积铺开。
方案c,常用办法是通过本地消息表完成,也有一些通过事务消息。主要缺点同样是业务侵入强,需要大量额外开发工作,给业务升级、运维都带来困难。还有一个问题是使用场景受限,有些最终一致无法满足的情况,需要人工干预。优点是扩展性好,可以满足日益扩大的业务,相对b来说开发没那么复杂。这是比较主流的方案,在阿里广泛使用。
方案d,多熟中小企业靠业务补偿与人工订正解决。
缺点是运维、支持投入人力大,优点是简单直接,逻辑不复杂。在业务量不大的情况下能hold住,但业务扩大了就很难应付。
这些问题很明显,为什么没有产品解决?因为技术层面很难,缺乏关键创新,这已经是至少10多年的世界性难题了。这种情况下,GTS横空出世,通过一系列技术创新,希望能彻底解决这些问题。
四、GTS如何解决数据一致问题
从上面分析可以看出,方案b/c/d是因为a的性能满足不了业务需求的无奈之举。
做出一个同样满足事务ACID的强一致的通用分布式事务中间件,并且性能足够,简单易用,才是终极方案,这就是GTS的出发点。GTS产品可以通过技术创新,解决上述方案分析中b、c和d用户的问题,而不是寻求作为方案a用户的替代产品。
事务比较抽象,我举个例子类比下GTS给用户带来了哪些改变。
你每天上班,要经过一条10公里的只有两条车道的马路到达公司。这条路很堵,经常需要两三个小时,上班时间没有保证,这是方案a的问题。
选择一条很绕,长30公里但很少堵车的路,这是选b。上班时间有保证,但是必须早起,付出足够的时间和汽油。
选择一条有点绕,长20公里的山路,路不平,只有suv可以走,这是选c。上面分析了c方案场景受限,对应于交通,是底盘低的小轿车没法开,车型受限。
发扬艰苦奋斗,走路上班,这是选d。
GTS做的是什么?修了一条拥有4条车道的高架桥,没有绕路,还是10公里。不堵车,对事务来说是高性能;不绕路,对事务来说是简单易用,对业务无侵入,不用为事务而重构;没有车型限制,对事务来说是没有功能限制,提供强一致事务。在没有高架桥的时代,高架桥出现对交通来说就是一个颠覆性创新,很多以前看来无解的问题就迎刃而解了,同样的,GTS希望通过创新改变数据一致性处理的行业现状。
通过这个类比相信大家对GTS可以给用户带来的价值会有一定的了解。
五、GTS关键问题剖析
下面我们列出几个关键问题跟大家探讨下。
1)GTS是强一致,还是最终一致?
2)GTS默认隔离级别是“读未提交”,对业务影响大吗?
3)GTS什么场景下不能保证数据严格一致?
(1) 强一致,还是最终一致?
GTS是强一致,不是最终一致。一致性和隔离性对多数用户不好区分,一致性对应事务ACID的“C”,隔离性对应的是“I”。
所谓强一致,在于用户发起事务提交或事务回滚得到确认后,数据已经是一致的。而最终一致,用户发起提交,得到响应说提交成功了,但是未必真的成功,只是说过段时间它最终会数据一致。以转账为例,转成功了你就一定立即可以查到是强一致,最终一致没有这种保证。
强一致这个概念有很多争论,不同人有不同理解,没有权威定义。我们不搞学术化,大家理解实际业务中一致性是什么样子就好了。
隔离性与强一致没有必然联系。比如说把oracle的隔离级别设置为读未提交,这时oracle就不强一致了?显然还是强一致。有人提出串行化的隔离级别才是强一致,那我想问,在生产系统你会把数据库隔离级别设置成串行化吗?显然绝大多数不会,这样理解的话,大家就别搞强一致了。
(2) 隔离级别是“读未提交”,对业务影响大吗?
GTS支持两种隔离级别,“读未提交”和“读已提交”。“读未提交”比“读已提交”有明显性能优势,而且适合绝大多数场景,所以作为我们默认隔离级别。
提起“读未提交”,大家第一反应是有脏读,会影响业务。真正分析下业务,会发现“读未提交”与“读已提交”的差别对业务有实际影响的场景很少。更多的场景是,如果你的业务逻辑在“读未提交”下有问题,在“读已提交”下同样存在问题;反之,“读已提交”下没有问题,在“读未提交”下也很少会存在问题。
还拿通俗易懂的转账为例。假定A、B、C账户各有10000元,账户信息在不同的数据库,有两个并发事务,事务1 A->B 1000元,事务2 A->C 2000元。转账的业务逻辑是,先查出账户余额,再根据转账数目算出一个新余额来,然后用这个新余额去做update。
在“读未提交”情况下,事务1在进行中,事务2读到了A的账户9000元,恰巧这时事务1发生故障回滚了,两个事务都完成后A 7000,B 10000,C 12000。数据不一致少了1000(A+B+C=29000),看来脏读不靠谱,但是再分析下,上面的逻辑在读已提交下,是否也存在问题?
在“读已提交”情况下,事务1在进行中,事务2读到了A的账户10000元,两个事务都成功完成后A 8000,B 11000,C 12000。数据不一致多出1000(A+B+C=31000),看来也不靠谱啊。
问题根源在于上面的应用逻辑有典型的并发处理不当,修改办法很多,比如用select … for update 查询余额,可以锁定数据防止并发修改,或者是用sql中的逻辑运算代替,如 update … set value=value-2000。
我们追求极致性能,引导用户用“读未提交”的思路来写分布式的业务逻辑,让绝大多数SQL得到更好性能,个别SQL必须用“读已提交”时才用。这比默认就用“读已提交”更加合理。
(3) 什么场景下不能保证数据严格一致?
当用户用GTS事务操作数据,同时并发用非GTS方式(比如用oracle sqlplus)更改同一行数据,会影响到数据一致性。这种情况下,GTS校验发现数据被修改了,会发出告警到用户和我们团队,用户可以通过我们提供的工具进行订正(通常是点点按钮就ok了)。
问题的根本在于,GTS阻止不了别的方式写数据。所以,我们建议用户需要用GTS操作的数据,做到用且仅用GTS,混着搞是不安全的。我想这种限制是合理的(别的方案也有类似限制,按规则出牌),已有用户都能接受。
在个别需要混用的特殊场景下,可以选择GTS和非GTS有个时间差的方式。比如有个用户是这样的,通过GTS处理订单,另有一个不走GTS的定时任务负责清理完成的订单。我们建议用户的定时任务加个where条件只处理3分钟前的订单,这样就很安全了,因为业务中设置的GTS超时时长为1分钟,定时任务清理不会影响进行中的事务。
六、GTS-阿里分布式事务解决方案
GTS是一款分布式事务中间件,由阿里巴巴中间件部门研发,可以为微服务架构中的分布式事务提供一站式解决方案。
6.1、GTS的核心优势
性能超强
GTS通过大量创新,解决了事务ACID特性与高性能、高可用、低侵入不可兼得的问题。单事务分支的平均响应时间在2ms左右,3台服务器组成的集群可以支撑3万TPS以上的分布式事务请求。应用侵入性极低
GTS对业务低侵入,业务代码最少只需要添加一行注解(@TxcTransaction
)声明事务即可。业务与事务分离,将微服务从事务中解放出来,微服务关注于业务本身,不再需要考虑反向接口、幂等、回滚策略等复杂问题,极大降低了微服务开发的难度与工作量。完整解决方案
GTS支持多种主流的服务框架,包括EDAS,Dubbo,Spring Cloud等。 有些情况下,应用需要调用第三方系统的接口,而第三方系统没有接入GTS。此时需要用到GTS的MT模式。GTS的MT模式可以等价于TCC模式,用户可以根据自身业务需求自定义每个事务阶段的具体行为。MT模式提供了更多的灵活性,可能性,以达到特殊场景下的自定义优化及特殊功能的实现。容错能力强
GTS解决了XA事务协调器单点问题,实现真正的高可用,可以保证各种异常情况下的严格数据一致。
6.2、GTS的应用场景
GTS可应用在涉及服务调用的多个领域,包括但不限于金融支付、电信、电子商务、快递物流、广告营销、社交、即时通信、手游、视频、物联网、车联网等。
6.3、GTS与微服务的集成
GTS包括客户端(GTS Client)、资源管理器(GTS RM)和事务协调器(GTS Server)三个部分。
GTS Client 主要用来界定事务边界,完成事务的发起与结束。
GTS RM 完成事务分支的创建、提交、回滚等操作。
GTS Server 主要负责分布式事务的整体推进,事务生命周期的管理。
GTS和微服务集成的结构图如下所示,GTS Client需要和业务应用集成部署,RM与微服务集成部署。
6.4、GTS的输出形式
GTS目前有三种输出形式:公有云输出、公网输出、专有云输出。
6.4.1、公有云输出
这种输出形式面向阿里云用户。如果用户的业务系统已经部署到阿里云上,可以申请开通公有云GTS。开通后业务应用即可通过GTS保证服务调用的一致性。这种使用场景下,业务系统和GTS间的网络环境比较理想,达到很好性能。
6.4.2、公网输出
这种输出形式面向于非阿里云的用户,使用更加方便、灵活,业务系统只要能连接互联网即可享受GTS提供的云服务(与公有云输出的差别在于客户端部署于用户本地,而不在云上)。
在正常网络环境下,以包含两个本地事务的全局事务为例,事务完成时间在20ms左右,50个并发就可以轻松实现1000TPS以上分布式事务,对绝大多数业务来说性能是足够的。在公网环境,网络闪断很难完全避免,这种情况下GTS仍能保证服务调用的数据一致性。
具体使用样例使用参见4.7节GTS的工程样例。
6.4.3、专有云输出
这种形式主要面向于已建设了自己专有云平台的大用户,GTS可以直接部署到用户的专有云上,为专有云提供分布式事务服务。目前已经有10多个特大型企业的专有云使用GTS解决分布式事务难题,性能与稳定性经过了用户的严格检测。
6.5、GTS的使用方式
GTS对应用的侵入性非常低,使用也很简单。下面以订单存储应用为例说明。订单业务应用通过调用订单服务和库存服务完成订单业务,服务开发框架为Dubbo。
6.5.1、订单业务应用
在业务函数外围使用 @TxcTransaction
注解即可开启分布式事务。Dubbo应用通过隐藏参数将GTS的事务xid传播到服务端。
@TxcTransaction(timeout = 1000 * 10)
public void Bussiness(OrderService orderService, StockService stockService, String userId) {//获取事务上下文String xid = TxcContext.getCurrentXid();//通过RpcContext将xid传到一个服务端RpcContext.getContext().setAttachment("xid", xid);//执行自己的业务逻辑int productId = new Random().nextInt(100);int productNum = new Random().nextInt(100);OrderDO orderDO = new OrderDO(userId, productId, productNum, new Timestamp(new Date().getTime()));orderService.createOrder(orderDO);//通过RpcContext将xid传到另一个服务端RpcContext.getContext().setAttachment("xid",xid);stockService.updateStock(orderDO);
}
6.5.2、服务提供者
更新库存方法
public int updateStock(OrderDO orderDO) {//获取全局事务ID,并绑定到上下文 String xid = RpcContext.getContext().getAttachment("xid"); TxcContext.bind(xid,null);//执行自己的业务逻辑 int ret = jdbcTemplate.update("update stock set amount = amount - ? where product_id = ?",new Object[]{orderDO.getNumber(), orderDO.getProductId()});TxcContext.unbind();return ret;
}
6.6、GTS的应用情况
GTS目前已经在淘宝、天猫、阿里影业、淘票票、阿里妈妈、1688等阿里各业务系统广泛使用,经受了16年和17年两年双十一海量请求的考验。某线上业务系统最高流量已达十万TPS(每秒钟10万笔事务)。
GTS在公有云和专有云输出后,已经有了100多个线上用户,很多用户通过GTS解决SpringCloud、Dubbo、Edas等服务框架的分布式事务问题。业务领域涉及电力、物流、ETC、烟草、金融、零售、电商、共享出行等十几个行业,得到用户的一致认可。
上图是GTS与SpringCloud集成,应用于某共享出行系统。业务共享出行场景下,通过GTS支撑物联网系统、订单系统、支付系统、运维系统、分析系统等系各统应用的数据一致性,保证海量订单和数千万流水的交易。
6.7、GTS的工程样例
GTS的公有云样例可参考阿里云网站。在公网环境下提供 sample-txc-simple
和 sample-txc-dubbo
两个样例工程。
6.7.1、sample-txc-simple 样例
6.7.1.1、业务逻辑
该样例是GTS的入门sample,案例的业务逻辑是从A账户转账给B账户,其中A和B分别位于两个MySQL数据库中,使用GTS事务保证A和B账户钱的总数始终不变。
6.7.1.2、搭建方法
1) 准备数据库环境
安装MySQL,创建两个数据库db1和db2。在db1和db2中分别创建 txc_undo_log
表(SQL脚本见4.7.3)。在db1库中创建 user_money_a
表,在db2库中创建 user_money_b
表。
2) 下载样例
将sample-txc-simple文件下载到本地,样例中已经包含了GTS的SDK。
3) 修改配置
打开 sample-txc-simple/src/main/resources
目录下的 txc-client-context.xml
,将数据源的url、username、password修改为实际值。
4) 运行样例
在 sample-txc-simple
目录下执行build.sh编译本工程。编译完成后执行run.sh。
6.7.2、sample-txc-dubbo 样例
6.7.2.1、业务逻辑
本案例模拟了用户下订单、减库存的业务逻辑。客户端(Client)通过调用订单服务(OrderService)创建订单,之后通过调用库存服务(StockService)扣库存。其中订单服务读写订单数据库,库存服务读写库存数据库。由 GTS 保证跨服务事务的一致性。
6.7.2.2、搭建方法
1) 准备数据库环境
安装 MySQL,创建两个数据库 db1 和 db2。在 db1 和 db2 中分别创建 txc_undo_log
表。在 db1 库中创建 orders 表,在db2库中创建 stock 表。
2) 下载样例
将样例文件 sample-txc-dubbo
下载到本地机器,样例中已经包含了GTS的SDK。
3) 修改配置
打开 sample-txc-dubbo/src/main/resources
目录,将dubbo-order-service.xml
、dubbo-stock-service.xml
两个文件中数据源的url、username、password修改为实际值。
4) 运行样例
a. 编译程序
在工程根目录执行 build.sh 命令,编译工程。编译后会在 sample-txc-dubbo/client/bin
目录下生成 order_run.sh
、stock_run.sh
、client_run.sh
三个运行脚本对应订单服务、库存服务以及客户端。
b. 运行程序
在根目录执行run.sh,该脚本会依次启动 order_run.sh(订单服务)、stock_run.sh(库存服务)和client_run.sh(客户端程序)。
6.7.2.3、其他说明
样例使用 Multicast
注册中心的声明方式。如果本机使用无线网络,dubbo服务在绑定地址时有可能获取ipv6地址,可以通过jvm启动参数禁用。 方法是配置jvm启动参数 -Djava.net.preferIPv4Stack=true
。
4.7.3、SQL
6.7.3.1、建表 txc_undo_log
CREATE TABLE txc_undo_log (id bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',gmt_create datetime NOT NULL COMMENT '创建时间',gmt_modified datetime NOT NULL COMMENT '修改时间',xid varchar(100) NOT NULL COMMENT '全局事务ID',branch_id bigint(20) NOT NULL COMMENT '分支事务ID',rollback_info longblob NOT NULL COMMENT 'LOG',status int(11) NOT NULL COMMENT '状态',server varchar(32) NOT NULL COMMENT '分支所在DB IP',PRIMARY KEY (id),KEY unionkey (xid,branch_id)
) ENGINE=InnoDB AUTO_INCREMENT=211225994 DEFAULT CHARSET=utf8 COMMENT='事务日志表';
6.7.3.2、建表 user_money_a
CREATE TABLE user_money_a (id int(11) NOT NULL AUTO_INCREMENT,money int(11) DEFAULT NULL,PRIMARY KEY (id)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
6.7.3.3、建表 user_money_b
CREATE TABLE user_money_b (id int(11) NOT NULL AUTO_INCREMENT,money int(11) DEFAULT NULL,PRIMARY KEY (id)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
6.7.3.4、建表 orders
CREATE TABLE orders (id bigint(20) NOT NULL AUTO_INCREMENT,user_id varchar(255) NOT NULL,product_id int(11) NOT NULL,number int(11) NOT NULL,gmt_create timestamp NOT NULL,PRIMARY KEY (id)
) ENGINE=MyISAM AUTO_INCREMENT=351 DEFAULT CHARSET=utf8
6.7.3.5、建表 stock
CREATE TABLE stock (product_id int(11) NOT NULL,price float NOT NULL,amount int(11) NOT NULL,PRIMARY KEY (product_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
七、总结
GTS已经在阿里内部广泛使用,经过了双十一流量高峰的考验。内部成熟后,在专有云和公有云服务了很多用户,很多用户一天事务量在千万/亿级别,解决了业务服务化改造后的分布式事务棘手技术难题。
我们相信,作为既满足事务ACID特性,又具备高性能、高可用、业务侵入性低的分布式事务中间件,未来GTS能帮助更多的开发者,推动行业持续发展。
阿里微服务架构的分布式事务GTS相关推荐
- 阿里微服务架构下分布式事务Seata
转载自 阿里微服务架构下分布式事务Seata Seata 是什么? Seata 是一款开源的分布式事务解决方案,致力于在微服务架构下提供高性能和简单易用的分布式事务服务.在 Seata 开源之前,S ...
- 深入解读微服务架构下分布式事务解决方案
微服务倡导将复杂的单体应用拆分为若干个功能简单.松耦合的服务,这样可以降低开发难度.增强扩展性.便于敏捷开发.概念一经提出迅速火遍全球.当前 Hailo 有160个不同服务构成,NetFlix 有大约 ...
- Java生鲜电商平台-SpringCloud微服务架构中分布式事务解决方案
Java生鲜电商平台-SpringCloud微服务架构中分布式事务解决方案 说明:Java生鲜电商平台中由于采用了微服务架构进行业务的处理,买家,卖家,配送,销售,供应商等进行服务化,但是不可避免存在 ...
- 微服务架构及分布式事务解决方案
微服务架构及分布式事务解决方案 参考文章: (1)微服务架构及分布式事务解决方案 (2)https://www.cnblogs.com/rinack/p/9734551.html 备忘一下.
- 聊聊微服务架构及分布式事务解决方案
转载自 聊聊微服务架构及分布式事务解决方案 分布式事务场景如何设计系统架构及解决数据一致性问题,个人理解最终方案把握以下原则就可以了,那就是:大事务=小事务(原子事务)+异步(消息通知),解决分布 ...
- 微服务架构的分布式事务解决方案
微服务架构的分布式事务解决方案 参考文章: (1)微服务架构的分布式事务解决方案 (2)https://www.cnblogs.com/lyl2016/p/5908613.html 备忘一下.
- 微服务架构的分布式事务解决方案(Dubbo分布式事务处理)
微服务架构的分布式事务解决方案(Dubbo分布式事务处理) 参考文章: (1)微服务架构的分布式事务解决方案(Dubbo分布式事务处理) (2)https://www.cnblogs.com/qiyu ...
- 微服务架构下分布式事务解决方案——阿里GTS
1 微服务的发展 微服务倡导将复杂的单体应用拆分为若干个功能简单.松耦合的服务,这样可以降低开发难度.增强扩展性.便于敏捷开发.当前被越来越多的开发者推崇,很多互联网行业巨头.开源社区等都开始了微服务 ...
- 微服务架构下分布式事务解决方案 —— 阿里GTS
1 微服务的发展 微服务倡导将复杂的单体应用拆分为若干个功能简单.松耦合的服务,这样可以降低开发难度.增强扩展性.便于敏捷开发.当前被越来越多的开发者推崇,很多互联网行业巨头.开源社区等都开始了微服务 ...
最新文章
- mysql 单实例部署_Mysql 数据库单机多实例部署手记
- 其他类似GitBook的在线文档创作平台
- 前端代码是怎样智能生成的?
- AGC016B Colorful Hats(构造)
- cgcs2000大地坐标系地图_为什么要从北京54和西安80统一到CGCS2000?测绘人必知!...
- windows 获取命令执行后的结果_法院判决以后,老赖欠钱不还,递交强制执行申请多久后有结果?...
- html5 判断分享,好程序员HTML5大前端分享之函数篇
- sinr是什么意思_明明是满格信号,可是却不如两三格,这是什么情况?
- Ubuntu14.04安装及配置mysql5.7.19
- 17082 两个有序数序列中找第k小(优先做)
- 周易六十四卦——水火既济卦
- 一个奔四技术人的2020年
- kong的插件: Response Rate Limiting
- 部署测试fabric1.0及源码解析
- 房企猪企扎堆转型做光伏,那什么是光伏?
- C语言函数嵌套定义问题
- 计算机系统:程序Hello
- 2019Android面经 ---已拿网易云音乐内推offer
- 绕不开的一元二次方程
- marathon 安装
热门文章
- 木马开启智能识别?深度解析新型变形恶意软件LokiBot!
- 广电视讯宽带实现无线路由设置
- postgresql分割字符串_postgresql 实现字符串分割字段转列表查询_PostgreSQL_数据库
- 三点钟无眠群不再 区块链“领袖”换代
- Win7 局域网打印机共享设置
- vmware虚拟机连不上网修复过程
- java古典兔子问题c语言,Java递归算法经典实例(经典兔子问题)
- 期末作业系列之设计模式
- JAVA5种字符串拼接
- AMAZING PANDAVERSE:META”无国界,来2.0新征程激活时髦属性