如何用java 建立一个分布式系统

如何用java 建立一个分布式系统,第1张

分布式架构的演进
系统架构演化历程-初始阶段架构
初始阶段 的小型系统 应用程序、数据库、文件等所有的资源都在一台服务器上通俗称为LAMP
特征:
应用程序、数据库、文件等所有的资源都在一台服务器上。
描述:
通常服务器 *** 作系统使用Linux,应用程序使用PHP开发,然后部署在Apache上,数据库使用MySQL,汇集各种免费开源软件以及一台廉价服务器就可以开始系统的发展之路了。
系统架构演化历程-应用服务和数据服务分离
好景不长,发现随着系统访问量的再度增加,webserver机器的压力在高峰期会上升到比较高,这个时候开始考虑增加一台webserver
特征:
应用程序、数据库、文件分别部署在独立的资源上。
描述:
数据量增加,单台服务器性能及存储空间不足,需要将应用和数据分离,并发处理能力和数据存储空间得到了很大改善。
系统架构演化历程-使用缓存改善性能
特征:
数据库中访问较集中的一小部分数据存储在缓存服务器中,减少数据库的访问次数,降低数据库的访问压力。
描述:
系统访问特点遵循二八定律,即80%的业务访问集中在20%的数据上。
缓存分为本地缓存和远程分布式缓存,本地缓存访问速度更快但缓存数据量有限,同时存在与应用程序争用内存的情况。
系统架构演化历程-使用应用服务器集群
在做完分库分表这些工作后,数据库上的压力已经降到比较低了,又开始过着每天看着访问量暴增的幸福生活了,突然有一天,发现系统的访问又开始有变慢的趋势了,这个时候首先查看数据库,压力一切正常,之后查看webserver,发现apache阻塞了很多的请求,而应用服务器对每个请求也是比较快的,看来 是请求数太高导致需要排队等待,响应速度变慢
特征:
多台服务器通过负载均衡同时向外部提供服务,解决单台服务器处理能力和存储空间上限的问题。
描述:
使用集群是系统解决高并发、海量数据问题的常用手段。通过向集群中追加资源,提升系统的并发处理能力,使得服务器的负载压力不再成为整个系统的瓶颈。
系统架构演化历程-数据库读写分离
享受了一段时间的系统访问量高速增长的幸福后,发现系统又开始变慢了,这次又是什么状况呢,经过查找,发现数据库写入、更新的这些 *** 作的部分数据库连接的资源竞争非常激烈,导致了系统变慢
特征:
多台服务器通过负载均衡同时向外部提供服务,解决单台服务器处理能力和存储空间上限的问题。
描述:
使用集群是系统解决高并发、海量数据问题的常用手段。通过向集群中追加资源,使得服务器的负载压力不在成为整个系统的瓶颈。
系统架构演化历程-反向代理和CDN加速
特征:
采用CDN和反向代理加快系统的 访问速度。
描述:
为了应付复杂的网络环境和不同地区用户的访问,通过CDN和反向代理加快用户访问的速度,同时减轻后端服务器的负载压力。CDN与反向代理的基本原理都是缓存。
系统架构演化历程-分布式文件系统和分布式数据库
随着系统的不断运行,数据量开始大幅度增长,这个时候发现分库后查询仍然会有些慢,于是按照分库的思想开始做分表的工作
特征:
数据库采用分布式数据库,文件系统采用分布式文件系统。
描述:
任何强大的单一服务器都满足不了大型系统持续增长的业务需求,数据库读写分离随着业务的发展最终也将无法满足需求,需要使用分布式数据库及分布式文件系统来支撑。
分布式数据库是系统数据库拆分的最后方法,只有在单表数据规模非常庞大的时候才使用,更常用的数据库拆分手段是业务分库,将不同的业务数据库部署在不同的物理服务器上。
系统架构演化历程-使用NoSQL和搜索引擎
特征:
系统引入NoSQL数据库及搜索引擎。
描述:
随着业务越来越复杂,对数据存储和检索的需求也越来越复杂,系统需要采用一些非关系型数据库如NoSQL和分数据库查询技术如搜索引擎。应用服务器通过统一数据访问模块访问各种数据,减轻应用程序管理诸多数据源的麻烦。
系统架构演化历程-业务拆分
特征:
系统上按照业务进行拆分改造,应用服务器按照业务区分进行分别部署。
描述:
为了应对日益复杂的业务场景,通常使用分而治之的手段将整个系统业务分成不同的产品线,应用之间通过超链接建立关系,也可以通过消息队列进行数据分发,当然更多的还是通过访问同一个数据存储系统来构成一个关联的完整系统。
纵向拆分:
将一个大应用拆分为多个小应用,如果新业务较为独立,那么就直接将其设计部署为一个独立的Web应用系统
纵向拆分相对较为简单,通过梳理业务,将较少相关的业务剥离即可。
横向拆分:将复用的业务拆分出来,独立部署为分布式服务,新增业务只需要调用这些分布式服务
横向拆分需要识别可复用的业务,设计服务接口,规范服务依赖关系。
系统架构演化历程-分布式服务
特征:
公共的应用模块被提取出来,部署在分布式服务器上供应用服务器调用。
描述:
随着业务越拆越小,应用系统整体复杂程度呈指数级上升,由于所有应用要和所有数据库系统连接,最终导致数据库连接资源不足,拒绝服务。
Q:分布式服务应用会面临哪些问题?
A:
(1) 当服务越来越多时,服务URL配置管理变得非常困难,F5硬件负载均衡器的单点压力也越来越大。
(2) 当进一步发展,服务间依赖关系变得错踪复杂,甚至分不清哪个应用要在哪个应用之前启动,架构师都不能完整的描述应用的架构关系。
(3) 接着,服务的调用量越来越大,服务的容量问题就暴露出来,这个服务需要多少机器支撑?什么时候该加机器?
(4) 服务多了,沟通成本也开始上升,调某个服务失败该找谁?服务的参数都有什么约定?
(5) 一个服务有多个业务消费者,如何确保服务质量?
(6) 随着服务的不停升级,总有些意想不到的事发生,比如cache写错了导致内存溢出,故障不可避免,每次核心服务一挂,影响一大片,人心慌慌,如何控制故障的影响面?服务是否可以功能降级?或者资源劣化?
Java分布式应用技术基础
分布式服务下的关键技术:消息队列架构
消息对列通过消息对象分解系统耦合性,不同子系统处理同一个消息
分布式服务下的关键技术:消息队列原理
分布式服务下的关键技术:服务框架架构
服务框架通过接口分解系统耦合性,不同子系统通过相同的接口描述进行服务启用
服务框架是一个点对点模型
服务框架面向同构系统
适合:移动应用、互联网应用、外部系统
分布式服务下的关键技术:服务框架原理
分布式服务下的关键技术:服务总线架构
服务总线同服务框架一样,均是通过接口分解系统耦合性,不同子系统通过相同的接口描述进行服务启用
服务总线是一个总线式的模型
服务总线面向同构、异构系统
适合:内部系统
分布式服务下的关键技术:服务总线原理
分布式架构下系统间交互的5种通信模式
request/response模式(同步模式):客户端发起请求一直阻塞到服务端返回请求为止。
Callback(异步模式):客户端发送一个RPC请求给服务器,服务端处理后再发送一个消息给消息发送端提供的callback端点,此类情况非常合适以下场景:A组件发送RPC请求给B,B处理完成后,需要通知A组件做后续处理。
Future模式:客户端发送完请求后,继续做自己的事情,返回一个包含消息结果的Future对象。客户端需要使用返回结果时,使用Future对象的get(),如果此时没有结果返回的话,会一直阻塞到有结果返回为止。
Oneway模式:客户端调用完继续执行,不管接收端是否成功。
Reliable模式:为保证通信可靠,将借助于消息中心来实现消息的可靠送达,请求将做持久化存储,在接收方在线时做送达,并由消息中心保证异常重试。
五种通信模式的实现方式-同步点对点服务模式
五种通信模式的实现方式-异步点对点消息模式1
五种通信模式的实现方式-异步点对点消息模式2
五种通信模式的实现方式-异步广播消息模式
分布式架构下的服务治理
服务治理是服务框架/服务总线的核心功能。所谓服务治理,是指服务的提供方和消费方达成一致的约定,保证服务的高质量。服务治理功能可以解决将某些特定流量引入某一批机器,以及限制某些非法消费者的恶意访问,并在提供者处理量达到一定程度是,拒绝接受新的访问。
基于服务框架Dubbo的服务治理-服务管理
可以知道你的系统,对外提供了多少服务,可以对服务进行升级、降级、停用、权重调整等 *** 作
可以知道你提供的服务,谁在使用,因业务需求,可以对该消费者实施屏蔽、停用等 *** 作
基于服务框架Dubbo的服务治理-服务监控
可以统计服务的每秒请求数、平均响应时间、调用量、峰值时间等,作为服务集群规划、性能调优的参考指标。
基于服务框架Dubbo的服务治理-服务路由
基于服务框架Dubbo的服务治理-服务保护
基于服务总线OSB的服务治理-功能介绍
基于服务总线OSB的服务治理
Q:Dubbo到底是神马?
A:
淘宝开源的高性能和透明化的RPC远程调用服务框架
SOA服务治理方案
Q:Dubbo原理是?
A:
-结束-

分布式:服务分散部署在不同服务器组成一个整体应用,分散压力,解决高并发。

假设访问量特别大,就可以做成分布式,将一个大项目拆分出来单独运行。跟cdn一样的机制。

Redis分布式:将redis中的数据分布到不同的服务器上,每台服务器存储不同内容。Mysql集群是每台服务器都存放相同数据。分布式部署:系统应用部署在2台或以上服务器或虚拟机上,服务间通过RPC、WCF(包含WebService)等交互,即可称作分布式部署。微服务也算作分布式的一种,反之则不然。分布式优点:1、将模块拆分,使用接口通信,降低模块之间的耦合度。2、将项目拆分成若干个子项目,不同团队负责不同子项目。3、增加功能时只需再加一个子项目,调用其它系统接口即可。4、可灵活进行分布式部署。5、提高代码的复用性,比如service层,如果不采用分布式rest服务方式架构,在手机Wap商城、微信商城、PC、Android、ios每个端都要写一个service层逻辑,开发量大,难以维护和一起升级,此时可采用分布式rest服务方式共用一个service层。缺点:系统之间交互要使用远程通信,接口开发增大工作量,但利大于弊。微服务:可单独部署运行的微小服务,一个服务只完成单一功能分散能力,服务之间通过RPC等交互,至少有一个数据库。用户量过大高并发时,建议将应用拆解为多个子系统,各自隔离,独立负责功能。缺点:服务数量大,后期运维较难。分布式、微服务区别:分布式依赖整体组合,是系统的部署方式;微服务是架构设计方式,粒度更小,服务之间耦合度更低。独立小团队负责,敏捷性更高。集群:多台服务器复制部署相同应用,由负载均衡共同对外提供服务,逻辑功能仍是单体应用。项目如果跑在一台机器上,这台机器如果出现故障,或者用户请求量比较高一台机器支撑不住,网站可能就访问不了。那怎么解决呢?就需要使用多台机器,复制部署一样的程序,让几个机器同时运行网站。那怎么分发请求到所有机器上?所以负载均衡的概念就出现了。负载均衡:将请求分发以分摊服务器压力。基于反向代理能将所有的请求根据指定的策略算法,分发到不同的服务器上。实现负载均衡常用Nginx、LVS。负载均衡服务器出现问题了怎么办?所有冗余的概念就出现了。冗余:两台或多台服务器,一个主服务器,一个从服务器。假设一个主服务器的负载均衡服务器出现问题,从服务器能替代主服务器来继续负载均衡。实现的方式就是使用Keepalive来抢占虚拟主机。双机双工模式:目前Cluster(集群)的一种形式,两台服务器均为活动状态,同时运行相同的应用,保证整体的性能,也实现了负载均衡和互为备份。WEB服务器或FTP服务器等用此种方式比较多。实现多台服务器代码(文件)同步方案:1、负载均衡中实现代码同步rsync。2、rsync+inotify逐一文件监听并实时同步。3、实现redis共享session。

集群就是很多的服务器来实现一种功能,向mysql,很多的服务器都安装mysql,负载均衡就是用来调节的,比方说有很多的用户都在访问读取数据,可是读取a服务器的mysql多,而读取别的服务器的mysql就少,负载均衡把访问a的用户转给b一部分,防止a由于访问量过大造成宕机什么的。分布式就是有很多把服务器的相同功能分别部署在很多太计算机上,然后每个地方放上几台,这几台负责提供本地的服务,并且和总的服务器连接,保持数据传递。

本文基于对redis、zookpeer、rocketmq、elasticsearch学习总结,对于分布式系统学习,一定绕不开一个点,那就是CAP定理。什么是CAP定理,我这里简单的复制摘抄一下百度上的文案。

CAP原则又称CAP定理,指的是在一个分布式系统中,一致性(Consistency)、可用性(Availability)、分区容错性(Partition tolerance)。CAP 原则指的是,这三个要素最多只能同时实现两点,不可能三者兼顾。

说明一下上面的三个要素各代表的含义:

CAP定理说明上述的三个要素不能兼顾,最多只能满足其中的两个要素,在分布式系统中,一般都是保证分区容错性,而在一致性和可用性之间做取舍。因此存在CP、AP两种分布式集群的实现。

CP集群,即满足一致性和分区容错性,如zookpeer

AP集群,即满足可用性和分区容错性,如redis-cluster

下面,针对与上述的CP和AP问题,我们展开话题。

对于分布式系统,学习了解多了之后,发现其内在的解决方案基本上都是一样的,所谓万变不离其中。总结一下大体在于以下几步:

数据分片,很多分布式系统尤其是中间件服务,一般都会涉及高并发,数据量大的问题,如redis-cluster、recketmq,以及被大家熟知的Elasticsearch。针对于大数据量高并发的问题,若不做处理,服务器的性能将会成为服务的瓶颈,解决的方案之一便是数据分片,将大数据量在集群中按照一定的规则分片,使数据按照一定的规则分布集群的不同服务器上,以减轻单个服务器的压力,保证服务集群的可用性。

redis-cluster的数据分片是通过redis-cluster的哈希槽来实现的,redis-cluster有16384个哈希槽,这个数量是固定的,根据集群中服务器的数量可以手动的调配每个服务上存放的hash槽的数量,哈希槽之间是相互独立的,因此对集群的扩展提供了便利。

rocketmq的分片和topic紧密相关,在使用rocketmq中,无论是消息的生产者还是消费者都需要注册订阅一个topic。在rocketmq集群中,集群中的broker保存这个topic下数据的一部分,也就是topic的其中一个数据分片。当然,rocketmq不仅将一个topic下的数据分片到多个broker上,而且,一个broker上的topic数据还可以被分为多个queue,这是因为rocketmq中,一个queue只能被一个consumer消费,若是consumer的数量多于queue的数量,没有绑定queue的consumer将不能消费数据。

elasticsearch的数据分片在我看来和mysql的分库分表原理是一样的,elasticsearch中,每一个索引都相当于mysql的一个表,将一个索引分成多个shard放在不同的节点上,每个shard存储一部分数据。elasticsearch将数据进行分片,这样可以支持集群的横向扩展,同时,多个节点提供服务可以提高系统的效率和吞吐量。

综上所述,数据分片的一般都有两个好处,一个是支持集群的横向扩展,而是提升服务的吞吐量和性能。数据分片解决了以上两个问题,但是若是集群中一个节点发生宕机,或者因为网络原因和集群断开链接,那么这部分的数据分片甚至整个集群都会不可用,如何解决这个问题,就需要用到数据备份和主备切换。

数据分片的策略 了解了数据分片之后,需要了解以下数据分片的策略,根据集群提供服务的性质不同,可以采用的数据分片策略也各有不同,下面是我学习后的总结:

说到这里,会发现其实这种分片策略和负载均衡的策略还是挺相似的。

数据备份,举个例子来说,我有两台电脑A、电脑B,A用于工作,B用于游戏,我写了一篇文章,保存在电脑上电脑上,若是某一天我的电脑A磁盘坏了,那我这篇文章就找不到了,即便我现在还有电脑B,我也没有办法在对文章进行编辑。但是若是我在之前,就将文章拷贝了一份放在电脑B上,那么现在,我用电脑B就可以对文件进行编辑修改。

举这个例子,我的目的就是为了说明数据备份对于集群可用性的意义,例子中,我的两台电脑可以认为是集群中两台服务器,两台服务器一开始提供的服务可能不相同,A电脑提供的就是编辑文章的服务,数据备份的意义就在于,当原本提供服务的服务器宕机损坏,集群中另外的服务器仍然可以根据已经备份的数据提供相同的服务,而不会影响到用户的工作。

数据备份的目的就是不发生单点问题的措施之一,但是若是数据备份的策略不合适,备份的时机不对,那么备份的数据时效性也是问题。还是从例子出发,这里的文章每次都是我手动从A电脑拷贝到B电脑,这是我的备份策略,若是我选择每天晚上才拷贝一次,那么若是A电脑在我拷贝之前坏了,当天的文章编辑数据就丢失了,采用手动的方式备份,这种备份方式耗时耗力且不可控,而在分布式集群中,不同的系统采用了不同的备份策略,下面一一来说明。

首先明确一点,在分布式集群中,不可能采用人工手动备份,一定是系统程序按照一定的规则自动备份,就好像我将AB连在一起,写个程序,让A电脑自动把文章同步到B电脑。数据备份的方式分为两种:

这里以redis-cluster和zookeeper举例。

在redis-cluster中,当一台新的slave节点加入时,会出发数据同步,需要将主节点的数据同步到从节点。这时根据从节点的状态有两种同步方案:完整重同步 和 部分重同步

完整重同步既是将主节点的全部数据都复制给新的slave节点。大致流程为,当一个新的节点加入进来时,发送PSYNC命令给主节点并携带slave节点自身的信息(重点是复制偏移量),主节点会根据slave传过来的信息判断是完整重同步还是部分重同步,如何判断与数据同步时的复制缓冲区有关,更细节不展开介绍。

相对于redis-cluster,zookeeper中的数据同步有四种方式,和redis-cluster完整重同步和部分重同步相似的SNAP(全量同步)和DIFF(增量同步),以及zk事务处理相关的TRUNC(仅回滚同步)、TRUNC+DIFF(回滚+增量同步)

当节点已经加入集群,成为集群中的从节点,只要不断开连接,一般都只需要进行增量同步,不过系统同步的范围和方式有所差异,大致分为下面六种:

下面还是以具体服务来举例: redis-cluster中,主从复制采用的是异步复制的方式,master节点在做数据变更之后,会由一个异步线程将数据变更同步给slave节点,这是通过push的方式。当redis28之后,slave会周期的获取最新的数据,加入了pull方式。无论是master还是slave,在进行数据同步时,不会阻塞正常的应用请求。所以redis-cluster的主从复制,是异步备份+最终一致性的备份。

elasticsearch的主从复制可以手动设置同步备份或者异步备份,数据备份时不要求强一致性,而是主分片(primary shard)会维护一份需要同步的(replica shard)分片列表,这个分片列表同步完成,则认为数据备份完成,需要注意的是,这里的主从复制不是节点的更新数据,而是分片的更新数据。

rocketmq的主从复制和elasticsearch类似,也可以分为同步备份和异步备份,不同的是rocketetmq的数据备份采用的是pull的方式,从节点会通过HAConnection链接主动向主节点发送待拉取数据偏移量,待主节点返回节点更新数据信息,更新从节点数据偏移量,如此重复。

zookeeper的数据备份则是通过ZAB协议,通过消息广播的方式同步数据到从节点。

当数据备份后,主从节点上就有了相同的数据,为了提升服务的性能,那么可以采用读写分离的方式。主节点提供数据写服务,从节点提供读服务,可以有效的分担主节点的服务器压力。可以进行数据分片的系统,如:redis、rocketmq、elasticsearch,一般都可以配置一主多从、多主多从的集群架构。

读写分离之后,主节点提供写服务,从节点只提供读服务,因此若是主节点发生宕机,从节点依然可以提供读服务,但是服务无法更新数据,这时候就要进行主从切换。早起,主从切换可以由人工手动完成,不过随着技术发展,主从切换已经成为集群的必备功能。想要实现主从切换,必须要解决两个问题:

解决这个问题,需要额外再引入一个角色,相当于是一个监视者的角色,能够长期的对主节点进行监视,若是只有一个监视者,可能会发生误判,所以还需要一套机制去保证当监视者说主节点宕机,那么主节点是真的宕机,否则集群会出现脑裂问题。

以redis为例,在redis的哨兵模式中,这个监视者的角色是一个个哨兵实例,而在redis-cluster架构中,这个监视者的角色是redis实例自己。

在redis哨兵模式中,哨兵集群中的哨兵实例会定期和redis实例进行通信(ping),监视redis实例的在线情况,若是其中一台哨兵发现redis实例master故障,那么该哨兵会将该master状态改为主观下线,并通知其他哨兵,当哨兵集群中达到配置数量的哨兵实例认为该master都为主观下线状态,这时会将master修改为客观下线状态,并开始触发后续的故障转移。

在redis-cluster模式中,集群中的每一个节点都可以和其他节点通讯(ping),当某一个节点A发现主节点B下线了,A会将该主节点B设为疑似下线状态。集群中的节点会通过互发消息维护信息,当另一个节点C收到A的消息时,会将A对B节点的判断记录在C节点的维护信息下,这个信息可以理解为A说C疑似下线了。若是有其他节点发送C的状态信息,A同样也会记录。当某一个节点如C发现记录的B节点信息中,超过半数的主节点都认为B下线了,那么C就会将B节点状态修改为已下线状态,并广播消息给集群的其他节点,开始后续的故障转移。

上面就是redis的两种分布式模式故障检测的方案。大致可以归结为,监视节点会和被监视节点进行通讯,感知被监视节点的状态;监视节点之间也会进行通讯,同步信息。为了防止集群出现脑裂,对于某个主节点的故障判断会十分的谨慎,需要达到一定数量的监视节点都认为主节点故障时,才会认为主节点真的故障,从而触发故障转移。

在rocketmq集群模式中,nameserver扮演着监视者的角色(不同于其他系统,nameserver并不负责集群的主从切换,rocketmq 45之前不支持自动主从切换,45之后,通过dledger实现自动的故障转移)。在elasticsearch集群中,elasticsearch实例本身在扮演监视者角色。zookeeper也是实例本身扮演监视者的角色。

故障转移就是当集群发现集群中的主节点/从节点发生故障之后的处理,从节点比较简单,直接将从节点下线即可,主节点的故障转移流程比较复杂,各个系统根据系统的功能和架构有不同的实现方式,共同点是选举出的主节点一定是集群中数据最新的最完善的节点。

选举过程大致如下:

首先选举成功的条件时集群中具有投票权限的超过半数的节点投票一致,通过某一个节点成为主节点。

开始一轮选举时,定义为一个纪元,用一个自增的id表示。

候选节点将带着纪元id,以及自身信息作为投票申请广播给集群给可投票的节点。

具有投票权限的节点投票只要满足两个条件:1自身在最新纪元没有给投过票 2节点发送过来的投票申请时最新纪元的(如何判断时最新纪元,则是判断一下节点之前通过申请的纪元id是否小于当前申请的纪元id)。

半数以上的投票节点通过某一个候选节点成为leader节点,则leader产生。

若是一个纪元没有产生主节点,则候选节点进入随机的休眠,并且开启下一个纪元,知道产生leader节点。

在zk集群经过崩溃恢复模式之后,需要保证:1已经提交的事务不能丢失 2未被提交的事务不能出现。如何保证以上两点,zk服务集群中维护了zxid,zxid也可以看作是一个自增的id,集群中每产生一个新事物,zxid就会增加。zxid有64位,前32位维护了集群主节点变更情况,每重新选举出一个新的主节点则增加,后32位维护在新的主节点集群下事务的id,产生一个新事物则增加。

ZAB的选举模式有很多种,我主要了解了默认,也是推荐的FastLeaderElection模式,在这个模式下,我会以集群中一台参与选举的服务器的视角来模拟选主的过程;

我是一台zk服务器,我现在很慌,因为我的leader服务器不见了,作为一个有梦想的follower,我也要参加leader的选举,为了这次选举我要准备:myid(在集群中标识是这台服务器的id),zxid(本台服务器保存的最新事务id),logicClock(本台服务器发起的第几轮投票)

首先我会自己选自己,这得自信。于是我将自身的选举信息[myid, zxid]放到自己的收票箱,然后将我的选举信息还有我的选举轮次logicClock广播给其他服务器进行PK

作为一个有原则的服务器,我们的选举也是有原则的,当我收到别人的选举信息时,我也会将他和我自己的选举信息进行PK,PK的原则如下:

经过这一系列的PK,终于选出了我心中的leader服务器,要广播给其他服务器。

超过半数的服务器都同意某一台服务器成为leader,选举结束了。


欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zz/13055773.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-05-30
下一篇 2023-05-30

发表评论

登录后才能评论

评论列表(0条)

保存