热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

世界上本没有架构,建设的需求多了便有了架构

世界上本没有架构,建设的需求多了便有了架构作者:刘海锋,编辑:林师授,51CTO技术栈整编本文选自《CTO说》,你不能不看的
640?wx_fmt=jpeg


世界上本没有架构,建设的需求多了便有了架构


作者:刘海锋 , 编辑:林师授 , 51CTO技术栈 整编

本文选自《CTO说》,你不能不看的CTO养成记。


架构这个词,源于建筑学,它基本的架构是建设的方法,怎么样盖这个楼,就是整个实施的过程都需要架构。后来有了IT,有了互联网,最后就有了做软件需要架构,做硬件需要架构,需要设计一些东西。因为系统需要不停地建设,后来有了一些方法和思路,于是就有了架构。


基础架构英文叫 Infrastructure,又是架构的子领域,相当于是比较基础的东西,能够服务于各种应用的底层平台。其实这个很难去定义它,从业务的角度来说,下面所有的 IT 都是它的基础架构,因为它认为这些东西都是为它服务的。在研发的系统里面,也就是研发的团队,也可以认为各种底层平台是基础架构。


做这些事情的过程中,我自己有一些思考或者一些经验,跟大家分享一下。


非结构化存储——静态内容


刚到京东时,各个团队已经比较成型,我面临的第一个问题就是如何选择项目和方向,这是特别重要的。我最终聚焦在一个方向,那就是存储。因为存储是整个计算机领域最基础的一个问题,也是一个大互联网公司中特别重要的核心之一。


所以先把方向分到存储这个领域。而存储粗略地又可分成两类,一类叫非结构化的存储,一类叫结构化存储,结构化存储其实主要是数据库。


对于大部分的公司来说,结构化的存储量是比较小的,而且一般传统的关系数据库就可以比较好地支撑业务。另外从团队上来说,任何一家公司,上了一定规模以后,DBA 团队相对来说还是训练比较有素,系统有一些问题时,他们能够及时响应,也会使整体业务得到较好的支持。


对于京东来说,非结构化的数据量还挺大,有很多显著的特点。

  • 图片非常重要,因为商家要上传,它的可靠性以及性能很重要,消费者在浏览过程中,稳定、流畅的体验也都很关键。

  • 京东自营占了很大的比例,京东有众多的库房,每个库房每天要产生海量的运单,这些运单在内部库房流转的数据都是用非结构化的文本来描述,这个量比图片还要大。


当时有两个团队在做这个事情,并且基于当时的业务量和规模,技术层面对于业务的支撑是足够的。但是可以预见,随着公司业务的快速发展、规模的扩大、机器量的增多,如果不做系统的升级和改变,未来可能会出现很严重的问题。


我当时分别和这两个负责的团队进行了交流,一个是图片管理团队,一个是物流运营团队,不管是图片、订单的文本还是库房的流水的报文,每一条都不大,大概是几KB到几十KB,顶多几MB,但是每天的量都很多,对可靠性要求很高,绝对不能丢。这也是公司非常重要的资产,我最初的愿景是希望做很多的分布式存储


我做了模拟的曲线用来说服大家,其中有每天机器数作为一个维度、日均上传图片作为一个维度,日均消费者浏览图片作为一个维度等,后来发现随着业务的增长,如果依然使用目前的系统,故障数目会成倍增加。


推动的过程遇到了一些波折,但好在最终大家都接受了新的方案。

分享给大家其中一个比较有趣的过程

众所周知,做第一个吃螃蟹的人是需要很大的勇气的,大家都在犹豫的过程中,我们用了一个小小的策略,就是告诉每个团队,他们并不是第一个吃螃蟹的人,另外一个团队已经品尝了“螃蟹”, 并且觉得非常美味,用这种方法最终才得到大家的支持。


在内部达成一致后,我开始自主研发做第一个非结构化存储,它有很高的可靠性,非常好维护,性能各方面都很好,这是第一版系统,起名叫JFS(京东文件系统)。


后面做图片系统还是比较复杂,因为优先不考虑大文件,更多的是小文件,这个工作要从原来的系统里把所有的数据迁过来。


在迁移的过程中,要对历史数据做校验。原来的老系统因为历史比较久,再加上有很多故障,数据已经不一致。在这个修复过程中,发现图片有丢失,而这个图片并不是孤零零地在核心机房单独的图片系统,而是跟CDN 结合的。


因为最终的用户体验并不仅仅取决于原站的存储,还取决于 CDN 的效果。正好借着这个机会,对 CDN 有一个理解。后来我发现公司还有很多优化的空间,经过一段时间的发展和磨合,和 CDN 的团队互相也有了很多了解和共识,最终去跟他们沟通要把 CDN 重新优化。


最后我们做了一个项目,大概用了一个季度的时间把这个原站的货源次数降低了80%,从每天大概的两亿多,降到了每天的四五千万。


回过头来看

其实所有的问题都是方向的问题,就是抓一个大头,抓一个重点,在一点上取得突破。而且当时从研发的角度来说,做海量小文件的丰富存储,相对还是比较简单,研发投入不是那么大,但取得了非常好的效果。


结构化存储——动态内容


JFS 项目从2014上半年由我团队的一个负责人去做,持续地做下来,到2016年有一个团队在做这个事情。到目前为止,大家在京东看到的所有动态的内容,几乎都是由它来支撑的。


动态的内容是什么呢?

比如说商品的介绍、商品的价格、搜索和推荐的最终结果,还有缓存、广告等等很多。这些都是动态内容,更多的是文本,相对来说比较小,这些都是由这个系统来支撑。为了用结构化存储去支持公司的动态内容,我们用了不同的一个思路把内存当成持久的存储。


这个事情是怎么引发的呢?

当时的背景是这样,在2014年的上半年,我们开始做一个新的方向,想解决公司动态内容的结构和半结构化数据存储的问题。


那时候和很多公司一样,主要的方式都是 MySQL 数据库,前面加一个 case 来解决。后来发现,这个 case 的维护其实挺麻烦,MySQL 可扩展性方面也是一个问题。最后经过很多研究,和大家一起来合作解决缓存的问题。


从过去的发展可以看到一个情况,大概2007年、2008年时,服务器的内存基本是 2GB 到 4GB,后来很快8GB、16GB、32GB、128GB,现在很多公司,包括我们京东,采购的主流机型都是 256GB 的内存。内存越来越大,也越来越便宜。经过一些分析和预测之后,你会看到所有的公司,不管是美国公司还是中国的公司,都在比较激进地使用缓存。


而且稍微算算,你就会发现,这些动态的内容和这种结构和半结构化的数据,其总量加起来其实是很少。就以商品为例,每一条商品介绍大概是几KB。就算你有十亿的商品,也就是1G,1G乘以几KB,这才几TB。你每台机器是 256GB 内存,即便算上副本之后,其实也用不了多少资源。


我估计不久之后,很多公司都会采购 512GB 到 1TB 这么大内存的机器。于是我就规划这么一个项目,完全以内存为中心,让磁盘去做归档,数据驻留在内存里面。通过记日志加上快照,保证可靠性。


把这个方向明确之后持续去做,而在这里面还有很多技术的路线选择。当时选择一个方式是兼容 Redis 的API。它的优势是已经有不少人在用,数据类型也比较灵活,但缺点在于它是一个单机的软件,不是一个分布式的。最后我们来分期建设一个分布式的系统,聚合很多大内存的机器,把它做成一个共享的资源池,提供给很多的业务。


比如说公司大部分的业务是 Java 系统和 Java 客户端,也有 C++,还有一些小语言,用一个代理把它做成一个比较高质量的分布式的系统,能够去自动检测故障。


从2014年上半年开始,我们重点投入到这个项目并将团队进行扩充。到2015年夏天时,这个系统已经成为公司特别重要的基础设施。


京东有大概四五千台的大内存的机器,去支撑公司的业务,把几乎所有动态内容,如搜索推荐结果的 case,还有商品的介绍、价格、库存等这些动态都用它来存,性能非常好。虽然起初成本看起来稍微高了一点,但慢慢地这个成本在逐渐降低。


其实有的项目或者系统建设,

是需要去通过历史去预测未来会发生什么,最后去选择一些不同的方法,这样做的项目会比较有意义,有可能会取得更大的成功。但是在做这些事情之前还是先要做充分的调研,在技术平台,很多东西其实不是凭空想出来的,更多的要从公司的业务、众多的应用开发中去发现一些问题,把它沉淀下来做成系统,再把系统做成共享和服务。


中间件体系


中间件这个词也是很模糊的词汇,那么中间件是什么呢?


对于一些企业,特别是电商还有一些比较传统的大规模的业务模式的企业里,能够把应用开发中的一些范式提炼出来,能够抽象出来,把它做成一个大的软件系统,给所有的应用使用,这个统称为中间件。


电商里面有两个中间件特别的重要:

第一个叫消息队列。大家都知道单机的操作系统里面,比如说管道,你可以把这个命令输出的东西,想办法通过这个管道传给另一个命令,还可以通过符号,把很多进程连成一个并行处理。当然还有什么共享信号量,总之都相当于一个消息的队列。


在业务系统里面,也经常用这种东西,它不是单一的,其实是应用和应用,或者服务和服务之间的东西,它需要用消息队列串起来。因为很多事情,特别是在电商这块,不可能在一个环节把它完成。举例说购买商品并付钱这个事。


我在手机上,建立连接、付钱,它在一个一个环节里面,就把你这个付钱的请求处理完。过程是收到你付钱的请求,做很多校验,传给下游,下游一看,他是不是付钱了,这个用户是一个合法的用户,还是我们需要去做风控的用户,可能又传给下一个系统。下一个系统要检查,这个商品有没有库存,还能不能卖。如果能卖,又传给下一个环节,下一个环节可能负责调用银行的接口。再下游的环节收到回调之后,我们再寄一个东西用来对账,这个是付钱的过程。


加入购物车的过程也是,其实并不是只有一个接收问题用的请求。你通过手机发出请求,把商品加入购物车,其实也是有很多环节。你加入购物车的请求,给这个服务。服务可能检查你这个购物车是否太满,是不是还能加。有的环节可能检查库存,有的环节可能要做风险的控制。所以在复杂的应用系统里,经常需要消息队列把很多的东西做一个异步的串联。


这个用的特别多,尤其电商行业对消息队列的要求也是极高的,要求消息绝对不能丢,要非常的可靠,性能要求也很高。


在2014年,公司中消息队列用的比较纷繁复杂,有的团队用开源的,有的可能是自己写一个简单的,有的可能用数据库来做一个消息队列。


基于当时的团队和技术力量,我们已经有能力和实力做一个非常高质量的、自主研发的消息队列,把公司整个消息队列的技术方向统一起来,服务所有的业务。这个工程相对来说很大,但是最后的收益是很高的。


我们能够用集中一个团队的力量,维护好这样一个共用的供应链的服务,让所有的团队去演绎他们的这种运维、各方面的服务,让所有人受益。


第二个中间件的项目叫服务框架,这个是在2014年上半年开始做,到2015年年底,项目全部实施完,公司所有的应用,特别是重要的电商应用全部都接入了。


那么这个出发点是什么呢?很多服务端的开发,它有很多共有特征,都要写一个服务器,收到网络请求,把这些请求定义一些格式,去做反序列化,处理完成之后,再序列化发送。


其实这个事情本来没有什么,但是因为公司大了之后,业务越来越复杂,应用系统就很多。应用系统一多,就要做 SOA。SOA 是把这个服务进一步的拆分,在大的企业里,可能有成千上万个服务。如果每一个人都要开发自己的非业务逻辑的技术部分,那么技术成本太高了。而且做 SOA,需要统一把服务做命名,做管理,做监控,还有一些管理上的约定,需要一个统一的服务框架来做。


当时公司还没有实现真正的统一,通过团队各自维护。所以我们就做这么一件事情,把一开始定位 JavaIDC 的服务,后续又扩展成多语言,用一个框架把这种服务端的开发统一的提供出来。


这个项目的益处是,一方面能够便于工程师快速的开发服务,另外一方面,有助于去做服务的注册,发现、管理、监控等等。这个项目真的是影响公司所有的应用,因为大部分的应用都要用这个来发布、管理,几乎用于公司所有的应用服务上。


弹性计算云


这里是讲内部的私有云建设,而私有云更多的是虚拟化等技术。这其实比较适合在规模不大的公司,而现在要在一个规模很大的公司进行底层系统再次实施,就会比较复杂。


打个比方来说,它比较像在一个房间里,已经站了很多人,现在要换地毯,这事情挑战是很大的。但是为什么要做这个事情呢?

一方面有很多技术的因素,举一个简单的例子,资源交付的时间过长,作为一家电商企业,经常会有大促,如大家熟知的 6.18、双 11 等,促销就要加机器、加资源,但是物理机器的交付时间还是很长。


另外是公司的整体资源利用率不高。从资源的管理来说,任何一个数据中心里面,物理机都已经按团队、按应用的小块进行了划分。比如说这个是订单团队的一些机器、网站的一些机器等,而这些机器并没有划分成统一的资源使用。


其实按团队、应用来分,也给管理带来很高的成本。比如分机器的时候,准确的评估应该给哪个团队、哪个应用多少机器是一件非常困难且棘手的问题。分多了会导致资源的浪费,分少了会导致业务的运行出现严重的问题。在2014年年底的时候,这个项目已经提升到公司技术战略层面。


起初推动项目的时候,也遇到了一些小波折。比如原来每一个人捧着一百台机器,每台机器上有多大 CPU、内存,都在大家手中实实在在地握着,比较有安全感。如果这个项目实施后,变成了好像机器都不是我的了,有种安全感的缺失。所以关键的难点,是如何让大家相信这件事情有意义,有价值,对他有好处。


实施之初,我们采用了让一部分人先富起来。

把一些团队先“骗”上来,形成一个示范效应。那选择先“骗”谁?经过再三思考后,我选定了网站成为第一个“受害者”,网站主要负责京东的首页、单体页、详情页等。选择它的原因在于,一方面大家都觉得它在业务中蛮重要,而且量很大;另外一方面是,在技术上也比较适合,比较好切。因为网站比较规整。整个的架构流量进来,通过Web服务器展现网页,从后台取数据和图片。而正好图片是我带的团队做的,跟动态内容已经有很好的配合。

2015年的上半年,我和团队用了整整半年的时间重点来做这个项目。网站的完成后,其他团队也都陆续开始实施。


到目前为止,除了存储类、数据库类的,全公司所有的应用都用Docker来发布。


机器学习


我很有幸在2013年、2014年、2015年三年干了比较大的四个领域,分别是存储(非结构化存储和结构化存储)、中间件体系、内部的云,都是比较大的项目。2016年年初,我做了很多的思考和研究,觉得在做好系统建设并持续维护好之余,我得做一点新的事了。


结合技术热点和公司需求,我开始做分布式的机器学习,把它平台化去支持比较多的业务。


这里面大家可能会问两个问题,机器学习是不是相对高冷,有的人或者很多产品是不是不太需要。


其实这个问题得这么来看,还是那句话,历史洞察未来。比较早的时候,一般程序员都不会写数据库,只有专业的DBA才会写存储过程。但是现在只要是一个码农,他都会写存储过程。大概十年以前,只有数据科学家,才会写。而现在一般的程序员,都需要写,这是一种必备的技能。


机器学习也一样,可能前两年,主要是一些博士毕业的人,可以搞一些机器学习和训练模型。我觉得接下来,可能很多公司,不论大小,对工程师来说,都需要用一些机器学习的方法来做一些工作。所以在一家有一定规模的公司里面,一定会出现一个情况,就像数据库平台化、中间件平台化、存储平台化等一样。


平台化一方面会预先训练很多模型,积累很多算法。另外一方面,会让这个东西形成体系,去更好地支持业务。所以现在主要在做的是机器学习平台化,找到一些点。比如智能客服、网站内容更多的个性化营销。


个性化营销什么意思?有一个用户想买一台彩电,他经常会浏览这个电视,但是一直没买。这个时候,其实可以通过数据处理,一些模型训练识别出来,用户是想买这台电视,有哪些特征等,然后预测,他是因为什么原因没有购买?如果是价格原因,可以为用户发放专属的优惠券,从而促使用户完成购买决策。这是京东专享已经实现的功能。


另外再讲一个订单履约,用机器学习来降低公司运营成本的有趣例子。大家都知道,我们有时候在电商平台下了单,其实也是可能取消的,而且这个取消一般来说都是在下单时间之后,基本上买完之后,立刻就后悔了。因为一开始他没付出,但花了钱,又很心疼,但是这种下单完成后又取消订单的行为,其实对电商平台运营成本有很大的影响。


为什么?原来的流程是这样的:你下了订单,买了手机,订单文本生成了。到了OFC模块,订单就下到库房。库房工作人员就扫码,把这个手机拿出来了。拿出来之后,如果用户过一会取消了,这等于之前的流程就白折腾了,相当耗费库房的资源。


所以我们通过机器学习做了这样一个事情——订单稳定。什么意思呢?结合用户的很多特征,比如说这个人的收入水平,在过去购物历史上是不是喜欢取消订单。如果有一个人在电商网站上买过一万次,没有取消过。那么接下来,他购物时应该也不会取消。我们判别这个用户是一个花钱绝对不会反悔的人。如果有一个用户购买过一百次东西,取消过20回,那么接下来这个用户购物后再取消的可能性也是很大的,这是用户的一个特征。


另外的特征可以根据订单的商品、价格维度等。如果某款手机,历史上很多人买了之后,都点了取消,那么它的被取消概率肯定还是高。单价高的商品被取消的概率也可能高,比如买一卷卫生纸,其实很少有人取消。所以就可以训练一个模型来预测:用户在什么情况下、下单后多长时间可能产生取消动作。


如果我们预测用户可能在十分钟左右取消,那么我们给他冷静十分钟。如果他没取消,我们再继续他的订单。做这个事情就会给公司节省不少成本。


这是其中的一方面改进,陆续还有一些其他项目。比如说我们会对用户的商品评论做情感分析,做更好的商品评论的排序,这都是使用机器学习系统。


写在最后

最后,想和大家分享一下,其实所有的技术、所有的项目,特别是一些大的系统,设计开发仅仅是第一步。把它设计好、开发好,最后推出来,这个项目并没有完成。所有项目都不是一锤子买卖,后续如何持续有效地维护它,改进它,让它更好服务于业务才是最重要的。


另外就是客户关系和服务意识也非常重要,所有人或者项目都是有客户的,不管你是在研发团队里,处于哪一个层次或做哪一个方向的,其实你都有客户,客户的关系和服务意识至关重要。


对于技术的负责人,不管是经理、总监还是CTO,都需要对于业务和技术都要有一定敏锐度和前瞻性。心比天高,脚踏实地!简言之可以想的比较高、比较远,但是事情还是需要脚踏实地地去做。


作者简介:

刘海锋,现任京东商城总架构师,基础平台负责人。2013年加入京东,曾担任京东云平台总架构师。曾担任首届京东架构委员会主任,推动各个技术团队的横向交流与合作。曾获得“2014年京东集团风云人物”奖,带领团队连续获得“2014与2015年研发体系优秀团队”称号。目前主要负责软件定义数据中心与容器集群、存储与数据库技术、机器学习应用、商城整体架构与运维等技术方向,并负责指挥618和双11技术备战。

长按订阅更多精彩▼

640?wx_fmt=jpeg



推荐阅读
  • 为了评估精心优化的模型与策略在实际环境中的表现,Google对其实验框架进行了全面升级,旨在实现更高效、更精准和更快速的在线测试。新的框架支持更多的实验场景,提供更好的数据洞察,并显著缩短了实验周期,从而加速产品迭代和优化过程。 ... [详细]
  • 为了向用户提供虚拟应用程序,通常会在基础架构中部署StoreFront或Web Interface。为了确保安全的远程访问,通常需要在DMZ中配置Secure Gateway或Access Gateway。本文详细对比了这两种界面工具的功能特性,包括用户管理、安全性、性能优化等方面,为企业选择合适的解决方案提供了全面的参考。 ... [详细]
  • 近年来,BPM(业务流程管理)系统在国内市场逐渐普及,多家厂商在这一领域崭露头角。本文将对当前主要的BPM厂商进行概述,并分析其各自的优势。目前,市场上较为成熟的BPM产品主要分为两类:一类是综合型厂商,如IBM和SAP,这些企业在整体解决方案方面具有明显优势;另一类则是专注于BPM领域的专业厂商,它们在特定行业或应用场景中表现出色。通过对比分析,本文旨在为企业选择合适的BPM系统提供参考。 ... [详细]
  • 2016-2017学年《网络安全实战》第三次作业
    2016-2017学年《网络安全实战》第三次作业总结了教材中关于网络信息收集技术的内容。本章主要探讨了网络踩点、网络扫描和网络查点三个关键步骤。其中,网络踩点旨在通过公开渠道收集目标信息,为后续的安全测试奠定基础,而不涉及实际的入侵行为。 ... [详细]
  • TypeScript 实战分享:Google 工程师深度解析 TypeScript 开发经验与心得
    TypeScript 实战分享:Google 工程师深度解析 TypeScript 开发经验与心得 ... [详细]
  • 如果程序使用Go语言编写并涉及单向或双向TLS认证,可能会遭受CPU拒绝服务攻击(DoS)。本文深入分析了CVE-2018-16875漏洞,探讨其成因、影响及防范措施,为开发者提供全面的安全指导。 ... [详细]
  • 免费赠送《Python Selenium WebDriver 3.0 自动化测试框架实战指南》电子书资源
    免费赠送《Python Selenium WebDriver 3.0 自动化测试框架实战指南》电子书资源 ... [详细]
  • Hadoop 2.6 主要由 HDFS 和 YARN 两大部分组成,其中 YARN 包含了运行在 ResourceManager 的 JVM 中的组件以及在 NodeManager 中运行的部分。本文深入探讨了 Hadoop 2.6 日志文件的解析方法,并详细介绍了 MapReduce 日志管理的最佳实践,旨在帮助用户更好地理解和优化日志处理流程,提高系统运维效率。 ... [详细]
  • 在C语言中,定义一个包含学号、姓名和年龄的学生信息结构体,并遵循严格的命名规范。首先,初始化结构体变量的所有成员为默认值,然后将其学号设为88,姓名设为“liming”,年龄设为25。最后,在控制台上输出该结构体变量的详细信息,以验证数据的正确性。例如,使用 `typedef struct Student` 定义结构体类型。 ... [详细]
  • 在项目开发过程中,掌握一些关键的Linux命令至关重要。例如,使用 `Ctrl+C` 可以立即终止当前正在执行的命令;通过 `ps -ef | grep ias` 可以查看特定服务的进程信息,包括进程ID(PID)和JVM参数(如内存分配和远程连接端口);而 `netstat -apn | more` 则用于显示网络连接状态,帮助开发者监控和调试网络服务。这些命令不仅提高了开发效率,还能有效解决运行时的各种问题。 ... [详细]
  • 本文深入剖析了jQuery的架构设计与实现原理。jQuery的总体结构采用了一个自执行匿名函数的形式,该函数接收`window`和`undefined`作为参数,并在内部定义了一个局部的jQuery副本,以确保其内部变量和方法不会污染全局命名空间。这种设计不仅提高了代码的封装性和安全性,还使得jQuery能够更好地与其他JavaScript库兼容。通过详细分析这一架构,读者可以更好地理解jQuery的核心机制及其高效运行的原理。 ... [详细]
  • 2023年必备的六大Web3安全交互策略与实践
    2023年必备的六大Web3安全交互策略与实践 ... [详细]
  • Java 模式原型在游戏服务器架构中的应用与优化 ... [详细]
  • 程序员如何高效开发软件:实用技巧与方法
    在软件开发领域,如何提高开发效率是每个程序员关注的重点。应用软件开发涉及商业和日常生活等多个方面,其核心在于提升软件的实用性和用户体验。相较于纯粹的技术要求,应用软件更注重功能的实现和用户需求的满足。众多软件开发公司致力于这一领域,不断探索和实践高效的开发方法和技术,以确保软件的高质量交付。不同类型的应用软件,如办公自动化、财务管理、娱乐休闲等,都需根据具体应用场景进行定制化开发,以满足用户的多样化需求。 ... [详细]
  • 构建高可用性Spark分布式集群:大数据环境下的最佳实践
    在构建高可用性的Spark分布式集群过程中,确保所有节点之间的无密码登录是至关重要的一步。通过在每个节点上生成SSH密钥对(使用 `ssh-keygen -t rsa` 命令并保持默认设置),可以实现这一目标。此外,还需将生成的公钥分发到所有节点的 `~/.ssh/authorized_keys` 文件中,以确保节点间的无缝通信。为了进一步提升集群的稳定性和性能,建议采用负载均衡和故障恢复机制,并定期进行系统监控和维护。 ... [详细]
author-avatar
通天论坛it技术
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有