SaaS应用12原则

  • 1、What
    • 1.1 什么是 "12 Factor App" ?
    • 1.2 "12 Factor App" 分别是什么?
  • 2、Why
  • 3、How
    • 3.1 基准代码
    • 3.2 依赖
    • 3.3 配置
    • 3.4 后端服务
    • 3.5 构建,发布,运行
    • 3.6 进程
    • 3.7 端口绑定
    • 3.8 并发
    • 3.9 易处理
    • 3.10 开发环境与线上环境等价
    • 3.11 日志
    • 3.12 管理进程
  • 4、Sample

1、What

1.1 什么是 “12 Factor App” ?

目前,软件通常作为一种服务来进行交付,他们被称之为网络应用程序,或软件即服务(Saas), 12-Factor 为构建Saas 应用提供了方法论。

1.2 “12 Factor App” 分别是什么?

# 因素 描述
1 基准代码 一份基准代码,可以进行多份部署
2 依赖 显示声明所有的依赖关系
3 配置 在环境中存储配置
4 后端服务 把后端服务当做附加资源
5 构建,发布,运行 严格分离构建和运行
6 进程 以一个或多个无状态进程运行应用
7 端口绑定 通过端口绑定提供服务
8 并发 通过进程模型进行扩展
9 易处理 快速启动和优雅终止可最大化健壮性
10 开发环境与线上环境等价 尽可能的保持开发,预发布,线上的环境相同
11 日志 把日志当做事件流
12 管理进程 后台管理任务当做一次性进程运行

2、Why

“12-factor app” 是为构建可扩展、高性能、独立且最具弹性的云原生应用程序而定义的一种方法或一组原则:
The 12-factor app is a methodology or set of principles defined for building the scalable and performant, independent, and most resilient cloud-native applications:

  • 使用声明性格式进行设置自动化,从而最大限度地减少新进开发人员加入项目的时间和成本
    Use declarative formats for setup automation, which minimizes time and cost for new developers joining the project
  • 与底层操作系统松耦合的状态,为执行环境之间提供了最大的可移植性
    Have a clean contract with the underlying operating system, offering maximum portability between execution environments
  • 适合部署在现代云平台上
    Are suitable for deployment on modern cloud platforms
  • 最大限度地减少开发环境和生产环境之间的差异,实现持续部署以获得最大的敏捷性
    Minimize divergence between development and production, enabling continuous deployment for maximum agility
  • 无需对工具、架构或开发实践进行重大更改即进行扩展
    Can scale up without significant changes to tooling, architecture, or development practices

3、How

3.1 基准代码

软件开发中,版本控制尤为重要,常用的如Git,SVN等。一份用来跟踪代码所有修订版本的数据库被称作:代码库。
在类似 SVN 这样的集中式版本控制系统中,基准代码 就是指控制系统中的这一份代码库;而在 Git 那样的分布式版本控制系统中,基准代码 则是指最上游的那份代码库。
基准代码和应用之间总是保持一一对应的关系:

  • 一旦有多个基准代码,就不能称为一个应用,而是一个分布式系统。分布式系统中的每一个组件都是一个应用,每一个应用可以分别使用 12-Factor 进行开发。
  • 多个应用共享一份基准代码是有悖于 12-Factor 原则的。解决方案是将共享的代码拆分为独立的类库,然后使用依赖管理策略去加载它们。

尽管每个应用只对应一份基准代码,但可以同时存在多份部署。每份部署相当于运行了一个应用的实例。通常会有一个生产环境,一个或多个预发布环境。此外,每个开发人员都会在自己本地环境运行一个应用实例,这些都相当于一份部署。

所有部署的基准代码相同,但每份部署可以使用其不同的版本。比如,开发人员可能有一些提交还没有同步至预发布环境;预发布环境也有一些提交没有同步至生产环境。但它们都共享一份基准代码,我们就认为它们只是相同应用的不同部署而已。

3.2 依赖

大多数编程语言都会提供一个打包系统,用来为各个类库提供打包服务。12-Factor规则下的应用程序不会隐式依赖系统级的类库。 它一定通过依赖清单,确切地声明所有依赖项。此外,在运行过程中通过依赖隔离工具来确保程序不会调用系统中存在但清单中未声明的依赖项。这一做法会统一应用到生产和开发环境。

显式声明依赖的优点之一是为新进开发者简化了环境配置流程。新进开发者可以检出应用程序的基准代码,安装编程语言环境和它对应的依赖管理工具,只需通过一个构建命令来安装所有的依赖项,即可开始工作。

12-Factor 应用同样不会隐式依赖某些系统工具,如 ImageMagick 或是curl。即使这些工具存在于几乎所有系统,但终究无法保证所有未来的系统都能支持应用顺利运行,或是能够和应用兼容。如果应用必须使用到某些系统工具,那么这些工具应该被包含在应用之中。

3.3 配置

通常,引用的配置在不同部署环境中会有很大差异,有些应用在代码中使用了常量保存配置,这就与12-Factor所要求的代码的代码和配置严格分离大相径庭。配置文件在各部署间存在大幅差异,代码却完全一致。判断一个应用是否正确地将配置排除在代码之外,一个简单的方法是看该应用的基准代码是否可以立刻开源,而不用担心会暴露任何敏感的信息。

一个解决方法是使用配置文件,但不把它们纳入版本控制系统,就像 Rails 的 config/database.yml 。这相对于在代码中使用常量已经是长足进步,但仍然有缺点:总是会不小心将配置文件签入了代码库;配置文件也可能会分散在不同的目录,并有着不同的格式,这让找出一个地方来统一管理所有配置变的不太现实。更糟的是,这些格式通常是语言或框架特定的。

12-Factor推荐将应用的配置存储于 环境变量 中( env vars, env )。环境变量可以非常方便地在不同的部署间做修改,却不动一行代码;与配置文件不同,不小心把它们签入代码库的概率微乎其微;与一些传统的解决配置问题的机制(比如 Java 的属性配置文件)相比,环境变量与语言和系统无关。

目前特别流行的docker是可以很容易地满足这个需求

12-Factor推荐将应用的配置存储于 环境变量 中,保证配置排除在代码之外,有如下好处:

  • 环境变量是一种清楚、容易理解和标准化的配置方法
  • 环境变量可以非常方便地在不同的部署间做修改,却不动一行代码
  • 与配置文件不同,不小心把它们签入代码库的概率微乎其微
  • 与一些传统的解决配置问题的机制(比如 Java 的属性配置文件)相比,环境变量与语言和系统无关
  • 存储在环境变量中的另一个好处是,方便和Docker配合使用

配置管理的另一个方面是分组。有时应用会将配置按照特定部署进行分组(或叫做“环境”),随着项目的不断深入,开发人员可能还会添加他们自己的环境,这将导致各种配置组合的激增,从而给管理部署增加了很多不确定因素。

2-Factor 应用中,环境变量的粒度要足够小,且相对独立。它们永远也不会组合成一个所谓的“环境”,而是独立存在于每个部署之中。当应用程序不断扩展,需要更多种类的部署时,这种配置管理方式能够做到平滑过渡。

3.4 后端服务

后端服务是指程序运行所需要的通过网络调用的各种服务,如数据库(MySQL,CouchDB),消息/队列系统(RabbitMQ,Beanstalkd),SMTP 邮件发送服务(Postfix),以及缓存系统(Memcached)。

类似数据库的后端服务,通常由部署应用程序的系统管理员一起管理。除了本地服务之外,应用程序有可能使用了第三方发布和管理的服务。12-Factor 应用不会区别对待本地或第三方服务。 对应用程序而言,两种都是附加资源,通过一个 url 或是其他存储在 配置 中的服务定位/服务证书来获取数据。

每个不同的后端服务是一份资源 。例如,一个 MySQL 数据库是一个资源,两个 MySQL 数据库(用来数据分区)就被当作是 2 个不同的资源。12-Factor 应用将这些数据库都视作附加资源,这些资源和它们附属的部署保持松耦合。例如,如果应用的数据库服务由于硬件问题出现异常,管理员可以从最近的备份中恢复一个数据库,卸载当前的数据库,然后加载新的数据库,整个过程仅需修改配置中的资源地址,都不需要修改代码即可完成。

3.5 构建,发布,运行

基准代码 转化为一份部署(非开发环境)需要以下三个阶段:

  • 构建阶段 是指将代码仓库转化为可执行包的过程。构建时会使用指定版本的代码,获取和打包 依赖项,编译成二进制文件和资源文件。
  • 发布阶段 会将构建的结果和当前部署所需配置相结合,并能够立刻在运行环境中投入使用。
  • 运行阶段 (或者说“运行时”)是指针对选定的发布版本,在执行环境中启动一系列应用程序进程。

12-factor 应用严格区分构建,发布,运行这三个步骤。直接修改处于运行状态的代码是非常不可取的做法,因为这些修改很难再同步回构建步骤。部署工具通常都提供了发布管理工具,最引人注目的功能是退回至较旧的发布版本。并且,每一个发布版本必须对应一个唯一的发布 ID,发布的版本就像一本只能追加的账本,一旦发布就不可修改,任何的变动都应该产生一个新的发布版本。

新的代码在部署之前,需要开发人员触发构建操作。但是,运行阶段不一定需要人为触发,而是可以自动进行。如服务器重启,或是进程管理器重启了一个崩溃的进程。因此,运行阶段应该保持尽可能少的模块,这样假设半夜发生系统故障而开发人员又捉襟见肘也不会引起太大问题。构建阶段是可以相对复杂一些的,因为错误信息能够立刻展示在开发人员面前,从而得到妥善处理。

3.6 进程

运行环境中,应用程序通常是以一个或多个进程运行的。12-Factor 应用的进程必须无状态且无共享任何需要持久化的数据都要存储在后端服务内,比如数据库。

内存区域或磁盘空间可以作为进程在做某种事务型操作时的缓存,例如下载一个很大的文件,对其操作并将结果写入数据库的过程。12-Factor应用根本不用考虑这些缓存的内容是不是可以保留给之后的请求来使用,这是因为应用启动了多种类型的进程,将来的请求多半会由其他进程来服务。即使在只有一个进程的情形下,先前保存的数据(内存或文件系统中)也会因为重启(如代码部署、配置更改、或运行环境将进程调度至另一个物理区域执行)而丢失。

一些互联网系统依赖于粘性 session(sticky session), 这是指将用户 session 中的数据缓存至某进程的内存中,并将同一用户的后续请求路由到同一个进程。粘性 session 是 12-Factor 极力反对的,因为我们不应该在进程内存中存放数据,而且也无法确保客户端下次的请求也会被路由到该进程中去。Session 中的数据应该保存在诸如 Memcached 或 Redis 这样的带有过期时间的缓存中

3.7 端口绑定

互联网应用有时会运行于服务器的容器之中。12-Factor 应用完全自我加载而不依赖于任何网络服务器就可以创建一个面向网络的服务。互联网应用通过端口绑定来提供服务,并监听发送至该端口的请求。

端口绑定这种方式也意味着一个应用可以成为另外一个应用的后端服务,调用方将服务方提供的相应 URL 当作资源存入配置以备将来调用。

原则总结:

  • 应用内嵌 HTTP 库,如 Tomcat、Jetty等,例如 Spring Boot 的应用
  • 直接绑定端口对外提供服务,不依赖外部服务容器运行,例如 Dubbo
  • 通过在环境变量中声明,服务可以作为其他服务的依赖

需要注意的是,如果在一个宿主机中部署多个应用实例,就不能将一个宿主机端口映射到多个容器端口(端口冲突),解决方法是在这之上加一个负载均衡,负载宿主机的不同端口服务所对应的不同容器

3.8 并发

任何计算机程序,一旦启动,就会生成一个或多个进程。在Java开发中,在Java程序启动之初 JVM 就提供了一个超级进程储备了大量的系统资源(CPU 和内存),并通过多线程实现内部的并发管理。

在 12-factor 应用中,进程是一等公民。12-Factor 应用的进程主要借鉴于 unix 守护进程模型。开发人员可以运用这个模型去设计应用架构,将不同的工作分配给不同的进程类型。

举例来说,HTTP 请求可以交给 web 进程来处理,而常驻的后台工作则交由 worker 进程负责,定时任务交由 clock 来处理,这样扩展每一类的进程就非常方便,如下图所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6asQqlnE-1664186699924)(/.attachments/12-Factor_并发模型-8f89d4af-8796-48c8-8502-2f24a54f36d5.jpg)]

上述进程模型会在系统急需扩展时大放异彩。 12-Factor 应用的进程所具备的无共享,水平分区的特性 意味着添加并发会变得简单而稳妥。这些进程的类型以及每个类型中进程的数量就被称作进程构成 。

总结:

  • 进程内仍然可以进行多线程或时间模型
  • 因为服务都是无状态的,所以横向扩展很容易,依赖底层平台就能实现,不需要技术难度高的多线程编码
  • 进程永远都不应该让自己变成守护进程

3.9 易处理

12-Factor 应用的进程是易处理(disposable)的,意思是说它们可以瞬间开启或停止。这有利于快速、弹性的伸缩应用,迅速部署变化的代码或配置,稳健的部署应用。

进程应当追求最小启动时间 。理想状态下,进程从敲下命令到真正启动并等待请求的时间应该只需很短的时间。更少的启动时间提供了更敏捷的发布以及扩展过程,此外还增加了健壮性,因为进程管理器可以在授权情形下容易的将进程搬到新的物理机器上。

进程一旦接收到终止信号(SIGTERM) 就会优雅的终止。就网络进程而言,优雅终止是指停止监听服务的端口,即拒绝所有新的请求,并继续执行当前已接收的请求,然后退出。此类型的进程所隐含的要求是HTTP请求大多都很短(不会超过几秒钟),而在长时间轮询中,客户端在丢失连接后应该马上尝试重连。

进程还应当在面对突然死亡时保持健壮,例如底层硬件故障。虽然这种情况比起优雅终止来说少之又少,但终究有可能发生。一种推荐的方式是使用一个健壮的后端队列,例如 Beanstalkd ,它可以在客户端断开或超时后自动退回任务。无论如何,12-Factor 应用都应该可以设计能够应对意外的、不优雅的终结。Crash-only design 将这种概念转化为合乎逻辑的理论。

3.10 开发环境与线上环境等价

从以往经验来看,开发环境(即开发人员的本地 部署)和线上环境(外部用户访问的真实部署)之间存在着很多差异。这些差异表现在以下三个方面:

  • 时间差异: 开发人员正在编写的代码可能需要几天,几周,甚至几个月才会上线。
  • 人员差异: 开发人员编写代码,运维人员部署代码。
  • 工具差异: 开发人员或许使用 Nginx,SQLite,OS X,而线上环境使用 Apache,MySQL 以及 Linux。

12-Factor 应用想要做到持续部署就必须缩小本地与线上差异。

后端服务是保持开发与线上等价的重要部分,例如数据库,队列系统,以及缓存。许多语言都提供了简化获取后端服务的类库,例如不同类型服务的适配器。开发人员有时会觉得在本地环境中使用轻量的后端服务具有很强的吸引力,而那些更重量级的健壮的后端服务应该使用在生产环境。

12-Factor 应用的开发人员应该反对在不同环境间使用不同的后端服务 ,即使适配器已经可以几乎消除使用上的差异。这是因为,不同的后端服务意味着会突然出现不兼容,从而导致测试、预发布都正常的代码在线上出现问题。这些错误会给持续部署带来阻力。从应用程序的生命周期来看,消除这种阻力需要花费很大的代价。

不同后端服务的适配器仍然是有用的,因为它们可以使移植后端服务变得简单。但应用的所有部署,这其中包括开发、预发布以及线上环境,都应该使用同一个后端服务的相同版本。

3.11 日志

日志使得应用程序运行的动作变得透明。在基于服务器的环境中,日志通常被写在硬盘的一个文件里,但这只是一种输出格式。日志应该是事件流的汇总,将所有运行中进程和后端服务的输出流按照时间顺序收集起来。尽管在回溯问题时可能需要看很多行,日志最原始的格式确实是一个事件一行。日志没有确定开始和结束,但随着应用在运行会持续的增加。

12-factor 应用本身从不考虑存储自己的输出流。不应该试图去写或者管理日志文件。相反,每一个运行的进程都会直接的标准输出(stdout)事件流。开发环境中,开发人员可以通过这些数据流,实时在终端看到应用的活动。

在预发布或线上部署中,每个进程的输出流由运行环境截获,并将其他输出流整理在一起,然后一并发送给一个或多个最终的处理程序,用于查看或是长期存档。这些存档路径对于应用来说不可见也不可配置,而是完全交给程序的运行环境管理。这些事件流可以输出至文件,或者在终端实时观察。最重要的,输出流可以发送到 Splunk 这样的日志索引及分析系统,或 Hadoop/Hive 这样的通用数据存储系统。这些系统为查看应用的历史活动提供了强大而灵活的功能,包括:

  • 找出过去一段时间特殊的事件。
  • 图形化一个大规模的趋势,比如每分钟的请求量。
  • 根据用户定义的条件实时触发警报,比如每分钟的报错超过某个警戒线。

3.12 管理进程

进程构成(process formation)是指用来处理应用的常规业务(比如处理 web 请求)的一组进程。与此不同,开发人员经常希望执行一些管理或维护应用的一次性任务,例如:

  • 运行数据移植
  • 运行一个控制台(也被称为 REPL shell),来执行一些代码或是针对线上数据库做一些检查。大多数语言都通过解释器提供了一个 REPL 工具,或是其他命令
  • 运行一些提交到代码仓库的一次性脚本。

一次性管理进程应该和正常的 常驻进程 使用同样的环境。这些管理进程和任何其他的进程一样使用相同的 代码 和 配置 ,基于某个 发布版本 运行。后台管理代码应该随其他应用程序代码一起发布,从而避免同步问题。

所有进程类型应该使用同样的依赖隔离 技术。12-factor 尤其青睐那些提供了 REPL shell 的语言,因为那会让运行一次性脚本变得简单。在本地部署中,开发人员直接在命令行使用 shell 命令调用一次性管理进程。在线上部署中,开发人员依旧可以使用ssh或是运行环境提供的其他机制来运行这样的进程。

4、Sample

以之前的项目为例:

  • 基准代码: 项目采用 Git 进行代码管理,采用 Feature Branch 的分支管理策略。 dev 分支作为开发分支,每次的开发任务都从dev 分支切出任务分支进行开发,开发完成后,将任务分支代码再合并到dev 分支中。
  • 依赖: 采用了 Maven 管理我们项目中用到的所有依赖,没有其他私有依赖。只需要根据分支拉取对应的代码,导入到开发工具中,就会自动下载项目用到的所有依赖,启动我们的服务。
  • 配置: 项目采用配置文件的方式管理项目中用到的一些公共配置信息。单独创建了一个的 config_file 的工程项目,专门存储项目中用到的配置文件信息,当新增或修改时,直接修改对应的配置信息即可,不需要改动代码。使用Sring Cloud Config 构建运行。
  • 后端服务: 项目中用到的数据库,MQ,缓存等信息都是一种附加的资源,他们与我们的开发的项目之间应该是个松耦合的关系。例如当我们需要更换MySQL的数据库时,我们可以直接通过更改配置文件中对应的 MySQL 服务的配置信息来更换我们项目中连接使用的数据库。
  • 构建,发布,运行: 当开发完毕后,会在 Jenkins 上根据自己的开发分支进行打包成docker镜像,然后将服务发布到测试环境中,经过测试没有问题后,我们会将我们分支上的代码统一合并到dev分支中,然后 Jenkins 重新使用dev分支进行打包,将服务发布到生产环境中。如果有新的需求任务或者bug的出现,我们会创建另外的任务,然后根据任务进行开发,测试,部署等一系列操作。我们不能直接修改已经发布的服务信息,可以继续追加一个版本,将问题修改后重新测试发布即可。
  • 进程: 每一个发布的服务都是一个进程
  • 端口绑定: 每一个服务在环境中都对一个一个唯一的端口号,请求到服务器上的请求数据,会根据特定的解析方式分发到不同的服务中去。服务通过 k8s 职工的service 配置服务的端口信息,最后通过 Ingress 将端口映射出来供前端请求调用。
  • 并发: 一个环境可以有多个服务进行部署,通常我们的一个请求可能会涉及到多个服务之间的调用。或者当我们需要一个新的服务时,只需要端口号等特殊信息不同,即可再次进行部署,横向扩展十分方便。
  • 易处理: 项目部署采用 docker 镜像部署的方式发布运行,开发完成后,先通过 Jenkins 将服务打包成可执行的镜像,在对应的服务中,选择将发布的镜像版本即可。这种发布服务的当时,可以快速的部署我们的项目,实现易处理的特点。
  • 开发环境与线上环境等价: 开发过程中,我们有专门的开发环境供我们开发使用,通过DockFile 配置环境信息,当开发完成后,将对应的服务发布到测试环境中,供测试人员进行测试,验证。所有功能都测试通过后,就可以将测试环境中运行的相同版本的服务在生产环境中发布运行。首先,保证了测试环境与生产环境要相同,当在测试环境中测试无误后,就可以向生产环境发布了。
  • 日志: 每一个发布的服务都会有自己的日志记录,当我们的程序发生错误或者服务宕机的时候,我们可以通过 Ranach 平台查看日志文件来快速定位发生的问题。 同时,项目中,我们采用了 elasticsearch + Kanba 的方式集成了日志系统,可以直接通过网页来查看我们需要的日志信息。
  • 管理进程: 当需要手动进行一些操作的时候,通过 k8s 直接进入相关的 pod 中,直接通过命令进行操作。

SaaS应用12原则相关推荐

  1. SaaS应用12原则:简介

    简介 如今,软件通常会作为一种服务来交付,它们被称为网络应用程序,或软件即服务(SaaS).12-Factor(12 原则,或 12 要素)为构建如下的 SaaS 应用提供了方法论: 使用标准化流程自 ...

  2. 敏捷开发流程之Scrum:3个角色、5个会议、12原则

    本文主要从Scrum的定义和目的.敏捷宣言.Scrum中的人员角色.Scrum开发流程.敏捷的12原则等几方面帮助大家理解Scrum敏捷开发的全过程. 一.Scrum的定义和目的 Scrum是一个用于 ...

  3. SAAS产品设计原则及产品架构特点

    什么是 SaaS ? Saas软件模式让软件变得和水.电.气一样只需每月缴纳固定的费用即可享受服务. SaaS(software as aservice),软件即服务,是一种软件交付和销售方式-订阅许 ...

  4. 敏捷软件开发 12 原则

    作为 <现代软件工程> 的一个作业,  我要求同学们把 英文的敏捷开发原则 翻译成中文并解释. 大部分同学都提供了持续重构, 不断提高的版本. 技术翻译专家余晟老师也对其中较难翻译的三条原 ...

  5. Saas 应用12个架构规范

    引言 如今,软件通常会作为一种服务来交付,它们被称为网络应用程序,或软件即服务(SaaS).12-Factor 为构建如下的 SaaS 应用提供了方法论: 使用标准化流程自动配置,从而使新的开发者花费 ...

  6. 四大价值观和12准则

    四大价值观: 个体和交互胜过过程和工具: 客户合作胜过合同谈判: 工作的软件胜过全面的文档: 响应变化重于遵循计划. 12 原则: 准则1:Our highest priority is to sat ...

  7. 「中国好SaaS」报名进行中!「面基」买方关键决策人

     关注ITValue,看企业级最新鲜.最价值报道! 始于2016年,业内口碑爆棚的"中国好SaaS"第3季重装升级.在往届"好SaaS"中,数十家企业通过参加& ...

  8. ACP_1_敏捷价值观原则

    生生不息,"折腾"不止:Java晋升指北,让天下没有难学的技术:视频教程资源共享,学习不难,坚持不难,坚持学习很难: >>>> 一.引论 1.1 课程整体介 ...

  9. 12因子应用文档(一)

    原文地址: https://12factor.net/12factor... 介绍 现在软件通常交付为一个服务:可以叫web应用,或软件即服务(SaaS).12因子是一种构建SaaS应用的方法: 用声 ...

最新文章

  1. 优雅地分离tableview回调
  2. Leetcode 255. Verify Preorder Sequence in Binary Search Tree
  3. excel 电阻并联计算_电阻器的构成及取代原则
  4. 使用不同的膨胀和腐蚀方法对图像进行处理
  5. Netty的并发编程实践1:正确使用锁
  6. 好用的 curl 抓取 页面的封装函数
  7. UESTC 574 High-level ancients
  8. Weblogic部署项目三种方式
  9. #paypay付款测试#
  10. 【idea配置】idea右下角不显示版本分支
  11. java 定义类私有类_关于java:为什么不能将一个类定义为protected?
  12. 佑道医生集团获风和资本数千万A轮投资,6个月实现盈亏平衡
  13. wsl 2 中安装docker
  14. Zemax基础知识7--衍射知识(一)
  15. 深入理解原子操作-底层基础
  16. 怎么把Word转换成PDF?这几种转换神器分享给你
  17. IDEA如何配置Tomcat?
  18. wineqq之deepin wine系列(Linux版QQ总算有了完美无暇的方案)
  19. J storm战队成员_DOTA2J.Storm战队介绍-DOTA2MD迪士尼Major预选赛J.Storm战队介绍_牛游戏网攻略...
  20. 计蒜客 Emptying the Baltic (BFS+Dijkstra)

热门文章

  1. 听李天飞《大话西游》有感
  2. 十大开源Web应用安全测试工具
  3. 《夏洛特烦恼》观后感
  4. 当乐app官方下载android,当乐下载安卓最新版_手机官方版免费安装下载_豌豆荚
  5. 1135: [POI2009]Lyz
  6. 语句摘抄——第22周
  7. Study「Photoshop」:勾线图
  8. 「产品读书」增长黑客:创业公司的用户与收入增长秘籍
  9. python测试驱动开发_使用Python进行测试驱动开发的简单介绍
  10. 计算机选购知识点,购买笔记本电脑应参考的20个知识点