当前位置: 首页 > news >正文

架构师的 36 项修炼第08讲:高可用系统架构设计

本课时讲解高可用系统架构,如下图所示,本课时内容主要包括 3 个部分。

  1. 互联网系统可用性度量,即如何用指标来衡量系统的可用性,以及进行可用性管理时的一些手段。

  2. 高可用架构策略,主要包括负载均衡、备份与失效转移、消息队列隔离、限流与降级、异地多活这样几种架构方法。

  3. 高可用运维,如何在开发测试发布以及系统运行过程中,保障系统的高可用,包括自动化部署、自动化监控、自动化测试、预发布测试这几个方面。

系统高可用的挑战

一个互联网应用想要完整地呈现在最终用户的面前,需要经过很多个环节,任何一个环节出了问题,都有可能会导致系统不可用。

 

比如说 DNS 被劫持,域名解析就失败了,系统虽然完好无损,但用户依然不能访问系统。再比如,CDN 服务不可用,前面提过,CDN 服务是用户访问的第一跳,对于大型互联网系统而言,主要的静态资源都是通过 CDN 返回的。如果 CDN 服务不可用,那么大量的用户请求就会到达互联网数据中心,会给互联网数据中心带来巨大的请求负载压力,可能直接导致系统崩溃。还有就是应用服务器及数据库宕机、网络交换机宕机、磁盘损坏、网卡松掉,这样的硬件故障;机房停电了、空调失灵了、光缆被挖掘机挖断了,这些环境故障。以及程序代码 bug 引起的故障,等等。

 

每种故障都是系统不可用的原因之一,在设计系统相关架构的时候,要考虑各个方面的因素。除了系统本身故障导致的可用性问题,还有外部因素导致的系统不可用。比如说系统被黑客攻击了;业务上要做一次大的促销,或者要做一个秒杀的活动,因此带来的访问压力冲击;以及第三方合作伙伴服务不可用等等,各种带来系统故障的原因。

 

系统的高可用架构,说的就是如何去应对这些挑战。

互联网应用可用性的度量

业界通常用多少个 9 来说明互联网应用的可用性。比如说 QQ 的可用性是 4 个 9,就是说 QQ 的服务 99.99% 可用,这句话的意思是 QQ 的服务要保证在其所有的运行时间里只有 0.01% 不可用,也就是说一年大概有 53 分钟不可用。这个 99.99% 就叫做系统的可用性指标,这个值的计算公式是:年度可用性指标= 1 −(不可用时间/年度总时间)×100%。

 

一般说来,两个 9 表示系统基本可用,年度停机时间小于 88 小时;3 个 9 是较高可用,年度停机时间小于 9 个小时;4 个 9 是具有自动恢复能力的高可用,年度停机时间小于 53 分钟;5 个 9 指极高的可用性,年度停机时间小于 5 分钟。事实上对于一个复杂的大型互联网系统而言,对可用性的影响因素是非常多的,能够达到 4 个 9 甚至 5 个 9 的可用性,除了具备过硬的技术、大量的设备资金投入、有责任心的工程师,有时候还需要好运气。

 

我们熟悉的互联网产品的可用性大多是 4 个 9,淘宝、支付宝、微信,差不多都是这样。我们用可用性来描述一个系统是否整体可用,但是实际上很少会出现整个系统在几分钟几个小时内全部不可用的情况,更多的时候是一部分用户全部不可用,或者是全部的用户一部分功能不可用。可用性指标是对系统整体可用性的一个度量。

故障分类

在互联网企业中为了管理好系统的可用性,界定好系统故障以后的责任,通常会用故障分进行管理。一般过程是,根据系统可用性指标换算成故障分,是整个系统的故障分,比如 10万 分,再根据各自团队各个产品各个职能角色承担的责任的不同,会把故障分下发给每个团队,直到每个人,也就是说每个工程师在年初的时候就会收到一个预计的故障分。然后每一次系统出现可用性故障的时候,都会进行故障考核,划定到具体的团队和责任人以后,会扣除他的故障分。如果到了年底的时候,一个工程师,他的故障分被扣为负分,那么就可能会影响他的绩效考核。

 

故障分类的计算方式是用故障时间乘以故障权重来计算得到的。而故障的权重通常是在故障产生以后,根据影响程度,由运营方确定的一个故障权重值。下图是故障权重的示例。

故障处理流程和故障时间

再看一般互联网应用的故障处理流程和故障时间的确定,如下图。

 

      

首先是故障的开始,故障的开始时间是客服报告故障的时间点,或者是监控系统发现故障的时间点,如果客服收到了投诉,说系统不可用,这个时候就开始计算故障时间。或者监控系统发现,用户访问量或者是订单量因系统故障而出现了大幅的下跌,那么监控系统监控到的故障时间点就是故障的开始时间。

 

确定了故障以后,就把故障提交给相关部门的接口人,接口人再把故障现象发送给相关的责任人,责任人接手故障后,进行故障排查和处理。处理完毕以后系统重新启动,或者是代码重新发布上线以后,重新确认系统指标正常或者是功能恢复正常,确认故障处理完毕,这个时间就是故障的结束时间。

 

我们刚才用来计算故障分的故障时间,就是用这个故障结束时间减去开始时间,就是故障时间。这个时间通常以秒为单位,故障处理整个过程是争分夺秒的。故障结束以后,通常要开一个故障复盘会,检讨故障产生的原因,亡羊补牢,避免下次出现类似的故障,同时也要对引起故障的原因进行责任划分,扣除相关责任者的故障分计入绩效考核。

大型系统高可用的一般策略

那么,大型系统高可用的一般策略有哪些?

负载均衡

首先是应用服务器的负载均衡。负载均衡核心要解决的就是通过一个负载均衡服务器,将用户的请求分发给多个应用服务器,将多个应用服务器构建成一个集群,共同对外提供服务。这样的架构可以提高系统的处理能力,以解决高并发用户请求下的系统性能问题。

 

事实上,负载均衡还可以实现系统的高可用。因为用户的请求是通过负载均衡服务器请求分发到不同的应用服务器上的。那么当某个应用服务器宕机的时候,负载均衡服务器可以通过响应超时或者其它的心跳策略,发现这个应用服务器不可用,就可以将请求转发给其它的服务器,保证用户的请求总是能够成功的,整个系统对外看起来是可用的。从而使某个应用的服务器宕机,不会影响到整个系统的可用性。

 

这里面需要注意的一个点是,当我们提到一个应用服务器不可用的时候,并不仅仅是指应用服务器的硬件故障,或者是系统故障导致的系统宕机,更多的可能是应用程序在发布,因为应用程序要发布,必须要关闭以前的应用程序进程,拷贝新的程序代码,重新启动应用程序,这个时间可能需要几分钟或者十几分钟的时间,那么这段时间这台应用服务器对外看起来就是不可用的。

 

而这样的发布在互联网场景中是非常频繁的,一周有几次,甚至一天都有几次这样的发布。应用服务器是需要频繁停机的。所以在架构设计的时候,你不光要考虑到真正的服务器硬件或者系统故障导致的宕级,还要考虑到应用程序发布导致的系统停机,而这个可能更加频繁。

负载均衡实现方法

  • HTTP 重定向负载均衡

比较简单的一种是 HTTP 重定向负载均衡,也就是来自用户的 HTTP 请求到达负载均衡服务器以后,负载均衡服务器根据某种负载均衡算法计算一个新的服务器,通过 HTTP 重定向响应,将新的 IP 地址发送给用户浏览器,用户浏览器收到重定向响应以后,重新发送请求到真正的应用服务器,以此来实现负载均衡。工作原理如下图所示。

      

HTTP 重定向负载均衡的优点是它的设计比较简单,最简单的 HTTP 重定向负载均衡服务器,可能只需要几十行代码就可以完成。但是它的缺点是,用户完成一次访问需要两次请求数据中心,一次请求负载均衡服务器,一次是请求应用服务器;另一个问题是因为响应要重定向到真正的应用服务器,所以需要把应用服务器的 IP 地址暴露给外部用户,这样可能会导致安全性的问题。

  • DNS 负载均衡

另一种实现负载均衡的策略是 DNS 负载均衡。我们知道浏览器访问我们数据中心的时候,通常是用域名进行访问,HTTP 协议则必须知道 IP 地址才能建立通信连接,那么域名是如何转换成 IP 地址的呢?就是通过 DNS 服务器来完成。当用户从浏览器发起发起 HTTP 请求的时候,他输入域名,首先要到 DNS 域名服务器进行域名解析,解析得到 IP 地址以后,用户才能够根据 IP 地址建立 HTTP 连接,访问真正的数据中心的应用服务器,那么就可以在 DNS 域名解析的时候进行负载均衡,不同的浏览器进行解析的时候,返回不同的 IP 地址,从而实现负载均衡。工作原理如下图所示。

 

目前主要的 DNS 服务商和 DNS 软件都支持 DNS 域名解析负载均衡。DNS 域名解析负载均衡的主要问题有两个方面。一方面它依然是要将 Web 服务器的 IP 地址暴露给浏览器,产生安全问题。另一方面,很多时候,DNS 域名解析服务器是在互联网应用系统之外的一个服务器,它由域名解析服务商提供,不在我们的控制范围之内,所以当我们的服务器不可用的时候,DNS 域名解析服务器并不知道,它依然会将用户请求分发过来。而且域名解析并不是每一次请求都进行解析的,即使我们去域名解析服务商的机器上去更新了域名解析对应的 IP 列表,这个更新也不会立即生效,依然会有大量的请求到达我们的应用服务器。那么这些已经宕机的、不可用的服务器就无法完成用户的需求,在用户看起来就是我们的系统不可用。

 

虽然 DNS 域名解析负载均衡有这样的一些问题,但是在实践中大型互联网系统几乎都使用域名解析负载均衡,主要原因是在于,这些大型互联网系统,比如像淘宝、Facebook、百度这些系统,根据域名解析出来的 IP 地址,并不是真正的 Web 服务器 IP 地址,是负载均衡服务器的 IP 地址,也就是说这些大型互联网系统,它们都采用了两级负载均衡机制,DNS 域名解析进行一次负载均衡解析出来的 IP 地址是负载均衡服务器的 IP 地址,然后由负载均衡服务器,再做一次负载均衡,将用户的请求分发到应用服务器,这样的话,我们的应用服务器的 IP 地址就不会暴露出去。同时由于负载均衡服务器通常是比较高可用的,也不存在应用程序发布的问题,所以很少有可用性方面的问题。

  • 反向代理负载均衡

负载均衡的另外一种实现是反向代理负载均衡。我们前面提到用户请求到达数据中心以后,最先到达的就是反向代理服务器。反向代理服务器,除了可以提供请求的缓存功能以外,还可以进行负载均衡,将用户的请求分发到不同的服务器上面去。反向代理是工作在 HTTP 协议层上的一个服务器,所以它代理的也是 HTTP 的请求和响应。而 HTTP 协议相对说来,作为互联网第七层的一个协议,它的协议比较重,效率比较低,所以反向代理负载均衡通常用在小规模的互联网系统上,只有几台或者十几台服务器的规模。工作原理如图所示。

  • IP 层负载均衡(四层负载均衡)

如果规模再大一点的集群,通常就不会再使用反向代理服务器进行负载均衡。在七层网络通讯之下的另外一种负载均衡方法是在 IP 层进行负载均衡,IP 层是网络通讯协议的第四层,所以有时候叫四层负载均衡。它的主要工作原理是当用户的请求到达负载均衡服务器以后,负载均衡服务器会拿到 TCP/IP 的数据包,对数据包的 IP 地址进行转换,修改 IP 地址,将其修改为 Web 服务器的 IP 地址,然后把数据包重新发送出去。如下图所示。

因为 IP 地址已经是 Web 服务器的 IP 地址,所以这个数据包会重新路由到应用服务器上,以此来实现负载均衡。IP 层负载均衡,比我们刚才提到的工作在第七层的反向代理负载均衡效率要高得多。但是它依然有一个缺陷,就是不管是请求还是响应的数据包都要通过负载均衡服务器进行 IP 地址转换,才能够正确地进行数据分发,或者正确地响应到用户的客户端浏览器。请求的数据通常比较小,一个 URL 或者是一个简单的表单,但是响应的数据不管是 HTML 还是图片,JS、CSS 这样的资源文件通常都会比较大,因此负载均衡服务器会成为响应数据的流量瓶颈。

  • 数据链路层负载均衡

为了解决这个问题,将负载均衡的数据传输,再往下放一层,放到了数据链路层,实现数据链路层的负载均衡。在这一层上,负载均衡服务器并不修改数据包的 IP 地址,而是修改网卡的 MAC 地址。而应用服务器和负载均衡服务器都使用相同的虚拟 IP 地址,这样 IP 路由就不会受到影响,但是网卡会根据自己的 MAC 地址选择负载均衡发送到自己的网卡的数据包,交给对应的应用服务器去处理,处理结束以后,当他把响应的数据包发送到网络上的时候,因为 IP 地址没有修改过,所以这个响应会直接到达用户的浏览器,而不会再经过负载均衡服务器。工作原理如下图所示。

这种通信方式我们从上图看是一个三角形,所以也被形象地称为三角模式。数据链路层的负载均衡是目前大型互联网系统中使用得最多的负载均衡方案。在 Linux 内核中也支持数据链路层负载均衡,也就是说可以用一台 Linux 服务器去配置实现数据链路层负载均衡,通过负载均衡实现应用服务器的高可用。

数据库复制与失效转移

数据库的高可用要比应用服务器复杂很多,因为应用服务器是无状态的,请求可以分发到任何一台服务器去处理,而数据库上必须存储有正确的数据才能将请求分发给它。对于数据库的高可用,通常是使用数据库复制与失效转移来完成的。我们在分布式数据库存储这一讲中提到过 MySQL 的主主复制,以及 MySQL 的主从复制。

 

因为有数据复制,所以用户请求可以访问到不同的从服务器上,当某一台从服务器宕机的时候,系统的读操作不会受到影响,实现数据库读操作高可用。而如果实现了主主复制,那么当主服务器宕机的时候,写请求连接到另外一台主服务器上,实现数据库的写操作高可用,而数据库部署的时候,可以同时部署如下图所示这样的主主复制和主从复制,也就是实现数据库的读写都高可用。

   

消息队列隔离

系统高可用的另一种策略是使用消息队列实现异步解耦,即消息队列隔离。我们在分布式消息队列一讲中也提到过这种架构方式的高可用。

 

一方面,消息的生产者和消费者通过消息队列进行隔离,那么如果消费者出现故障的时候,生产者可以继续向消息队列发送消息,而不会感知到消费者的故障,等消费者恢复正常以后再去到消息队列中消费消息,所以从用户处理的视角看,系统一直是可用的。

      

另一方面,由于分布式消息队列具有削峰填谷的作用,所以在高并发的时候,消息的生产者可以将消息缓存在分布式消息队列中,消费者可以慢慢地到消息队列中去处理,而不会将瞬时的高并发负载压力直接施加到整个系统上,导致系统崩溃。

限流和降级

系统高可用的另一个策略是限流和降级。主要针对的是,在高并发场景下,如果系统的访问量超过了系统的承受能力,如何对系统进行保护?

 

限流是指对进入系统的用户请求进行限流处理,如果访问量超过了系统的最大处理能力,就会丢弃一部分的用户请求,保证整个系统可用,保证大部分用户是可以访问系统的。这样虽然有一部分用户的请求被丢弃,产生了部分不可用,但还是好过整个系统崩溃,所有的用户都不可用要好。

 

保护系统的另一种手段就是降级。有一些系统功能是非核心的,但是实际它也给系统产生了非常大的压力,比如说在电商系统中有“确认收货”这个功能,对于大多数互联网电商应用,我们即使是不去确认收货,超时它会自动确认收货。

 

但实际上确认收货这个操作是一个非常重的操作,因为它要更改订单状态,完成支付确认,并进行评价等一系列操作。这些操作都是一些非常重的、对数据库压力很大的操作。如果在系统高并发的时候去完成这些操作,那么会对系统雪上加霜,使系统的处理能力更加恶化。解决办法就是在系统高并发的时候,比如说像淘宝“双11“这样的时候,当天可能整天系统都处于一种极限的高并发访问压力之下,这一天就可以将确认收货、评价这些非核心的功能关闭,将宝贵的系统资源留下来,给正在购物的人,让他们去完成交易。

异地多活机房架构

系统高可用的另一个策略是异地多活的架构。我们前面提到的各种高可用策略,都还是针对一个机房内的系统架构,但是如果整个机房都不可用,比如说机房所在城市遭遇了地震,机房遭遇了火灾或者停电,这样的话,不管我们前面的设计和系统多么的高可用,整个机房都不可访问,看起来系统依然是不可用的。

 

为了解决这个问题,同时也为了提高系统的处理能力和改善用户体验。很多大型互联网应用都采用了异地多活的多机房架构策略,也就是说将数据中心分布在多个不同地点的机房里,这些机房都可以对外提供服务,用户可以连接任何一个机房进行访问。这样每个机房都可以提供完整的系统服务,即使某一个机房不可使用,系统也不会宕机,依然保持可用。


异地多活的架构考虑的一个重点是,用户请求如何分发到不同的机房去。这个主要可以在域名解析的时候完成,也就是用户进行域名解析的时候,会根据就近原则或者其它一些策略,完成用户请求的分发。

 

另一个至关重要的技术点是,因为是多个机房都可以独立对外提供服务,所以也就意味着每个机房都要有完整的数据记录,所以用户在任何一个机房完成的数据操作,都必须要同步传输给其它的机房,需要进行数据实时同步。

 

目前远程数据库同步的解决方法有很多,最需要关注的是数据冲突问题。同一条数据,同时在两个数据中心被修改了,该如何解决?为了解决这种数据冲突的问题,很多异地多活的多机房架构实际上采用的是类似 MySQL 的主主模式,也就是说多个机房在某个时刻是有一个主机房的,某些请求只能到达主机房才能被处理,其它的机房不处理这一类请求,以此来避免关键数据的冲突。

高可用运维

自动化测试

除了高可用的架构,还有保障系统高可用的运维。

 

其中一种高可用的运维是自动化测试。对于一个成熟的互联网系统,任何一次代码变更,都可能需要执行大量的回归测试,才能够保证系统没有 bug,而这种更新又是非常频繁的,如果依赖手工操作,测试效率和测试资源都难以满足如此大量的回归测试需求。所以对于成熟的互联网产品,很多时候采用自动化测试,通过自动化脚本自动对 APP 或者是服务接口进行测试。

 

一开始的时候要写自动化的测试脚本,工作量会比较大,投入也会比较大。但是随着脚本的不断的积累,自动化测试的成本会比手工测试的成本要更低。一般说来,在实践中,对于比较成熟的互联网产品,也就是说每次变更相对影响比较小的互联网产品,使用自动化测试是比较划算的。自动化测试和手工测试的总体成本对比如下图。

自动化监控

还有一种是自动化监控。系统在线上运行的时候,必须要实时的监控系统的各项指标,包括业务指标和技术指标。业务指标包括用户访问量、订单量、查询量这些主要的业务指标,技术指标包括 CPU、磁盘、内存的使用率等。通过这些指标可以实时监控业务是否正常,系统是否正常。如果指标不正常,通过监控报警的手段,通知相关的人员,还可以在自动化监控的基础上去,触发自动化的运维工具,进行自动化的系统修复。

预发布

高可用运维的另一种手段是预发布。虽然在系统上线之前,系统在代码更新以后,要经过测试才会上线,但是还有一些情况在测试环境是无法复现的。比如对第三方服务的调用,数据库结构的变更,以及一些线上的配置参数变更等等,只有线上才能够发现。

 

但是一旦发布到线上以后,如果有这些问题,就会导致系统不可用,解决方法就是进行预发布。在线上的服务器集群里面有一台服务器,是专门的预发布服务器,这台服务器不配置在负载均衡服务器,也就是说外部的用户是无法访问到这台服务器的,但是这台服务器跟其它的应用服务器,使用的配置、连接的数据库、连接的第三方服务都是完全一样的,它是一个完全线上的一个服务器,而这个服务器只有内部的工程师才可以访问到。

 

在系统发布的时候,先发布到这台预发布服务器上,然后工程师通过域名绑定的方式,直接访问这台服务器,进行一些关键的业务操作,看系统是否正常。如果正常,那么就将代码同步到其它的服务器上,这时候外部服务器才能够访问到最新的代码。如果发现问题,那么就可以重新进行修复。这个问题虽然是线上的,但是并不会影响到外部用户的使用。预发布的工作原理如下图。

     

灰度发布

对于大型互联网系统,虽然有前面的各种保障措施,但还是可能发生上线以后用户报告出现故障,对于用户报告的故障或者监控到的故障,就需要对系统进行回滚到原来的代码,系统退回到前一个版本。但是对于大型互联网系统而言,它的服务器特别多,可能有数万台服务器,这个时候即使是进行系统回滚,也可能要花很长的时间。这段时间系统一直处于某种不可用的故障状态。

 

为了避免上述情况,大型互联网系统,会使用一种灰度发布的手段,也就是说每天都只发布一部分服务器,如果出现问题,那么只需要回滚这一部分服务器就可以。发布以后观察一天,第二天再发布一部分服务器。如果没有故障报告,那么就继续发布,如果有故障报告就进行回滚,减少故障的影响力和影响时间。灰度发布流程如下图。

总结回顾

系统可用性是通过可用性指标来进行衡量的。当我们说一个系统 4 个 9 可用的时候,就是指这个系统 99.99% 的时间都是可用的,也就意味着一年中的不可用时间只占 53 分钟。

 

为了对故障进行管理和考核,很多互联网企业还引入了故障分这样一个手段。保障系统高可用的主要策略有应用服务器的负载均衡、数据库的备份与失效转移、消息队列隔离,限流、降级以及异地多活的多机房架构。

 

除了这些高可用的架构策略,还通过一系列的自动化手段,实现运维的高可用,包括自动化测试、自动化监控,预发布以及灰度发布这些手段。

 

本课时内容至此结束,下一课时讲解系统的安全架构。


精选评论

**斌:

学习了

**辉:

学习了。

相关文章:

  • 架构师的 36 项修炼第09讲:系统的安全架构设计
  • 力扣-python-滑动窗口合集
  • 云原生架构之Spring Cloud+Kubernetes配置中心方案
  • eclipse+tomcat部署javaweb项目教程ssm
  • 通过反射加载dll
  • 旅游住宿酒店14页
  • C++设计模式之Prototype原型模式
  • auto 关键字
  • LeetCode精选200道--二叉树篇(三)
  • 学生HTML个人网页作业作品:HTML绿色的化妆品静态网站(web前端网页制作课作业)
  • 力扣 1582. 二进制矩阵中的特殊位置
  • mybatis原理及整合spring原理
  • 数据结构——树形结构
  • R语言ggplot2可视化相关系数图(correlation analysis):通过数据点的大小以及颜色(双色渐变填充)表征相关性的强度
  • 【Java第25期】:File 类的用法和 InputStream, OutputStream 的用法
  • [译] 理解数组在 PHP 内部的实现(给PHP开发者的PHP源码-第四部分)
  • 【编码】-360实习笔试编程题(二)-2016.03.29
  • 345-反转字符串中的元音字母
  • CSS3 聊天气泡框以及 inherit、currentColor 关键字
  • gulp 教程
  • HTTP传输编码增加了传输量,只为解决这一个问题 | 实用 HTTP
  • Java编程基础24——递归练习
  • Lucene解析 - 基本概念
  • Magento 1.x 中文订单打印乱码
  • PAT A1050
  • Spring Cloud(3) - 服务治理: Spring Cloud Eureka
  • SpringBoot 实战 (三) | 配置文件详解
  • 工作手记之html2canvas使用概述
  • 容器服务kubernetes弹性伸缩高级用法
  • 如何用vue打造一个移动端音乐播放器
  • 三分钟教你同步 Visual Studio Code 设置
  • 使用前端开发工具包WijmoJS - 创建自定义DropDownTree控件(包含源代码)
  • 我是如何设计 Upload 上传组件的
  • 怎样选择前端框架
  • 正则表达式-基础知识Review
  • ​​​​​​​ubuntu16.04 fastreid训练过程
  • ​520就是要宠粉,你的心头书我买单
  • ​iOS实时查看App运行日志
  • ​软考-高级-系统架构设计师教程(清华第2版)【第1章-绪论-思维导图】​
  • ​云纳万物 · 数皆有言|2021 七牛云战略发布会启幕,邀您赴约
  • #我与Java虚拟机的故事#连载03:面试过的百度,滴滴,快手都问了这些问题
  • (1)(1.11) SiK Radio v2(一)
  • (12)Linux 常见的三种进程状态
  • (Arcgis)Python编程批量将HDF5文件转换为TIFF格式并应用地理转换和投影信息
  • (Java岗)秋招打卡!一本学历拿下美团、阿里、快手、米哈游offer
  • (动手学习深度学习)第13章 计算机视觉---微调
  • (附源码)ssm基于jsp高校选课系统 毕业设计 291627
  • (附源码)计算机毕业设计ssm电影分享网站
  • (循环依赖问题)学习spring的第九天
  • (转载)VS2010/MFC编程入门之三十四(菜单:VS2010菜单资源详解)
  • .bat批处理(八):各种形式的变量%0、%i、%%i、var、%var%、!var!的含义和区别
  • .java 9 找不到符号_java找不到符号
  • .net 4.0发布后不能正常显示图片问题
  • .Net 6.0 处理跨域的方式
  • /usr/bin/python: can't decompress data; zlib not available 的异常处理