[TOC]

0x00 前言

软件开发流程:

  • 软件需求分析,软件要做什么功能
  • 软件页面原型设计,把实现功能画出功能图
  • 把原型转换为静态页面,把功能图转换成html页面
  • 数据库设计和技术选型
  • 系统的编码
  • 系统的测试
  • 系统的部署和维护

异常处理:

  • 每个servlet单独处理(项目简单的情况下)
  • 使用BaseServlet统一处理异常,如果有需要Servlet可单独处理;

0x01 项目开发

(1)项目开发流程

  • 业务理解
  • 需求分析: 功能描述
  • 概要设计: 用例图表示
  • 详细设计: 业务流程
  • 业务模型设计: 例如:Java Web开发的MVC+三层架构Web层、Service层、Dao层
  • 技术方案讨论: 相关技术
  • 数据库设计
  • 文档输出
  • coding
  • 代码review
  • 功能自测
  • 功能联调
  • 提测
  • 上线
    WeiyiGeek.项目开发流程

    WeiyiGeek.项目开发流程

Java - Web 开发基本目录结构

1
2
3
4
5
6
7
top.weiyigeek.dao.impl #Dao接口以及实现类
top.weiyigeek.service.impl #Service接口以及实现类
top.weiyigeek.web.base #基类
top.weiyigeek.web.filter #过滤器
top.weiyigeek.web.servlet #Servler
top.weiyigeek.utils #工具类
top.weiyigeek.domain #JavaBean


0x02 应用开发十二要素

描述:The Twelve-Factor App 即应用的十二要素,它包含SaaS应用程序现代开发的实践标准和部署规范,并特别关注于应用程序如何保持良性成长,开发者之间如何进行有效的代码协作,以及如何 避免软件污染 。
它适用于任何 SaaS 应用的开发人员以及部署和管理此类应用的运维工程师学习;
参考地址:https://12factor.net/zh_cn/

简单介绍
描述:如今软件通常会作为一种服务来交付(特别是最近这几年火得一塌涂地的微服务相关的应用),它们被称为网络应用程序,或软件即服务(SaaS);

它为构建如下的 SaaS 应用提供了方法论:

  • 1.标准化的流程配置:从而使新的开发者花费最少的学习成本加入这个项目;
  • 2.实现最大可移植性:应用部署不受限操作系统;
  • 3.云计算平台部署:在服务器和系统管理方面节省资源,并且保证一定的环境安全;
  • 4.持续集成与交付:将开发环境和生产环境的差异降至最低,让开发参与开发运维DevOps之中;
  • 5.应用的低耦合模块化:在工具、架构和开发流程不发生明显变化的前提实现扩展


12-Factors(要素一览)

  • I. 基准代码:一份基准代码(Codebase)多份部署(deploy)

Q:什么是基准代码(Codebase)?
答:在类似 SVN 这样的集中式版本控制系统中,基准代码就是指控制系统中的这一份代码库;而在 Git 那样的分布式版本控制系统中,基准代码则是指最上游的那份代码库。

Q:基准代码和应用之间的关系?
答:他们总是保持着一一对应的关系,但是一旦有多个基准代码,就不能称为一个应用而是一个分布式系统(每一个组件都看做一个应用),多个应用共享一份基准代码是有悖于 12-Factor 原则的;
解决方案: 是将共享的代码拆分为独立的类库,然后使用依赖管理策略去加载它们。

Q:什么是多份部署?
答: 尽管每个应用只建议对应一份基准代码,但是可以同时存在多份部署(每份部署相当于运行了一个应用的实例),通常会有一个生产环境,一个或多个预发布环境启动应用实例做压测与安全测试,它们都共享一份基准代码,我们就认为它们只是相同应用的不同部署而已。

WeiyiGeek.code-deploys

WeiyiGeek.code-deploys


  • II. 依赖: 显式声明依赖关系( dependency )
    描述: 大多数编程语言都会提供一个打包系统,用来为各个类库提供打包服务,就像 Perl 的 CPAN 或是 Ruby 的 Rubygems 以及 Node.js 的 npm ,还比如 Python 的 pip ;满足12要素的应用同样不会隐式依赖某些系统工具,如 ImageMagick 或是curl;

    通过打包系统安装的类库可以是系统级的(称之为 “site packages”),或仅供某个应用程序使用,部署在相应的目录中(称之为 “vendoring” 或 “bunding”)。

Q:什么是依赖清单?
答: 常规的应用它应该通过依赖清单确切地声明所有依赖项,以便达到快速运行测试与开发环境,比如Python项目中需要建立一个requirements.txt项目依赖管理,然后使用pip install -r requirements.txt自动安装所有依赖包;

Q:显式声明依赖有何优点?
答: 为新进开发者简化了环境配置流程。新进开发者可以检出应用程序的基准代码,安装编程语言环境(Java / Node.js)和它对应的依赖管理工具(Maven / npm),只需通过一个构建命令来安装所有的依赖项即可开始工作;
例如,Ruby/Bundler 下使用 bundle install,而 Clojure/Leiningen 则是 lein deps。

Q:什么是依赖隔离?
答:在运行过程中通过依赖隔离工具来确保程序不会调用系统中存在但清单中未声明的依赖项。这一做法会统一应用到生产和开发环境,并且建议依赖声明和依赖隔离必须一起使用,否则无法满足 12-Factor 规范。
例如,Ruby 的 Bundler 使用 Gemfile 作为依赖项声明清单,使用 bundle exec 来进行依赖隔离。Python 中则可分别使用两种工具 – Pip 用作依赖声明,Virtualenv 用作依赖隔离(过滤)。


  • III. 配置: 在环境中存储配置(Configured)
    通常,应用的配置在不同部署环境 (预发布、生产环境、开发环境等等)间会有很大差异。这其中包括:数据库,Memcached,以及其他 后端服务 的配置, 第三方服务的证书,如 Amazon S3、Twitter 等,每份部署特有的配置,如域名等;

Q:建议将配置存放于何处?
答: 推荐将应用的配置存储于环境变量中( env vars, env ),12-Factor 所要求的代码和配置严格分离,即配置文件在各部署间存在大幅差异,代码却完全一致,此时将配置排除在代码之外而不用担心会暴露任何敏感的信息,比如:上传到git仓库实时在项目中指定.ignore文件中忽略上传的文件。

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


  • IV. 后端服务: 把后端服务(backing services)当作附加资源

Q:什么是本地后端服务?
答: 后端服务是指程序运行所需要的通过网络调用的各种服务,如数据库(MySQL,CouchDB),消息/队列系统(RabbitMQ,Beanstalkd),SMTP 邮件发送服务(Postfix),以及缓存系统(Memcached)他们通常由部署应用程序的系统管理员一起管理。

Q:什么是第三方发布和管理的服务?
答: 应用程序有可能使用了第三方发布和管理的服务,满足 12-Factor 应用不会区别对待本地或第三方服务。
示例:SMTP(例如 Postmark),数据收集服务(例如 New Relic 或 Loggly),数据存储服务(如 Amazon S3),以及使用 API 访问的服务(例如 Twitter, Google Maps, Last.fm)或者腾讯的对象存储对象服务(Object Storage Service,简称 OSS)。

Q:应用是如何看待两种服务资源?
答: 对应用程序而言两种都是附加资源,通过一个 url 或是其他存储在配置中的服务定位/服务证书来获取数据,满足12-Factor 应用的任意部署,都应该可以在不进行任何代码改动的情况下,将本地 MySQL 数据库换成第三方服务(例如 Amazon RDS)。类似的本地 SMTP 服务应该也可以和第三方 SMTP 服务(例如 Postmark / 腾讯企业邮箱 )互换达到修改配置中的资源地址来切换应用使用的相关服务;

WeiyiGeek.服务资源

WeiyiGeek.服务资源

Q:12-Factor 应用将这些数据库都视作附加资源(附属的部署保持松耦合), 每个不同的后端服务是一份资源,并且部署可以按需加载或卸载资源。
例如,一个 MySQL 数据库是一个资源,两个 MySQL 数据库(用来数据分区)就被当作是 2 个不同的资源。
例如,如果应用的数据库服务由于硬件问题出现异常,管理员可以从最近的备份中恢复一个数据库,卸载当前的数据库,然后加载新的数据库整个过程都不需要修改代码(只要满足程序最低的运行限制)。


  • V. 构建,发布,运行:严格分离构建和运行

    基准代码转化为一份部署(非开发环境)需要以下三个阶段也就是我们所说CI/CD(持续集成与交互):
    1.构建阶段:是指将代码仓库转化为可执行包的过程。构建时会使用指定版本的代码,获取和打包 依赖项,编译成二进制文件和资源文件。
    2.发布阶段:会将构建的结果和当前部署所需 配置 相结合,并能够立刻在运行环境中投入使用。
    3.运行阶段:(或者说“运行时”)是指针对选定的发布版本,在执行环境中启动一系列应用程序 进程。
    举例来说,直接修改处于运行状态的代码是非常不可取的做法,因为这些修改很难再同步回构建步骤。

    WeiyiGeek.Release版本会一直按照特定序号延续下去

    WeiyiGeek.Release版本会一直按照特定序号延续下去

Q:应用严格区分构建,发布,运行这三个步骤的优点和区别?
答: 构建部署工具通常都提供了发布管理工具,最引人注目的功能是退回至较旧的发布版本,比如SVN与Git提供了撤销提交或者创建修改BUG分支,然后再进行合并之后提交给代码仓库
发布的版本就像一本只能追加的账本,一旦发布就不可修改,任何的变动都应该产生一个新的发布版本。每一个发布版本必须对应一个唯一的发布 ID,例如可以使用发布时的时间戳(2011-04-06-20:32:17),亦或是一个增长的数字(v100);
运行阶段不一定需要人为触发而是可以自动进行(进程服务保活即Windows上的Services,msc服务,Linux上的systemd服务),但是在新的代码在部署之前,要开发人员触发构建操作便于错误信息能够立刻展示在开发人员面前,从而得到妥善处理;


  • VI. 进程:以一个或多个无状态进程运行应用
    描述:运行环境中,应用程序通常是以一个和多个进程运行的,12-Factor 应用的进程必须无状态且无共享(防止安全问题) 。任何需要持久化的数据都要存储在后端服务内,比如数据库。

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

Q:什么是”粘性 session”?
答:指将用户 session 中的数据缓存至某进程的内存中,并将同一用户的后续请求路由到同一个进程,这是不符合12要素应用开发规范的。
解决办法:Session 中的数据应该保存在诸如 Memcached 或 Redis 这样的带有过期时间的缓存中。


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

Q:本地与线上环境应用访问差异?
本地环境中,开发人员通过类似http://localhost:5000/的地址来访问服务。
在线上环境中,请求统一发送至公共域名而后路由至绑定了端口的网络进程。

Q:如何规范端口绑定标准?
答: 通常的实现思路是将网络服务器类库通过依赖声明载入应用即由部署者确定应用的运行端口。
解决方式完全由用户端,确切的说应该是应用的代码发起请求。和运行环境约定好绑定的端口即可处理这些请求,虽然说也是部署者确定暴露的外部端口,但是需要运行容器内部中的应用提供保留的内部端口(比如:SpringCloud开发的项目可指定 exec 端口)。
端口绑定优点: 意味着一个应用可以成为另外一个应用的后端服务 ,调用方将服务方提供的相应 URL 当作资源存入配置以备将来调用。


  • VIII. 并发: 通过进程模型进行扩展
    描述:在 12-factor 应用中进程是一等公民,任何计算机程序一旦启动,就会生成一个或多个进程。在互联网应用采用多种进程运行方式,即进程是开发人员可以操作的最小单位。
    例如,PHP 进程作为 Apache 的子进程存在,随请求按需启动。Java 进程则采取了相反的方式,在程序启动之初 JVM 就提供了一个超级进程储备了大量的系统资源(CPU 和内存),并通过多线程实现内部的并发管理。

Q:12-factor应用的进程借鉴说明?
答:主要借鉴于 unix 守护进程模型,开发人员可以运用这个模型去设计应用架构,将不同的工作分配给不同的进程类型 。
上述进程模型会在系统急需扩展时大放异彩 , 12-Factor 应用的进程所具备的无共享,水平分区的特性意味着添加并发会变得简单而稳妥。这些进程的类型以及每个类型中进程的数量就被称作进程构成
例如,HTTP 请求可以交给 web 进程来处理,而常驻的后台工作则交由 worker 进程负责。

WeiyiGeek.进程

WeiyiGeek.进程

Q:特殊进程的说明?
答:个别较为特殊的进程,例如通过虚拟机的线程处理并发的内部运算,或是使用诸如 EventMachine, Twisted, Node.js 的异步/事件触发模型。但一台独立的虚拟机的扩展有瓶颈(垂直扩展),所以应用程序必须可以在多台物理机器间跨进程工作。

Q:12-Factor 应用的进程不需要守护进程或是写入 PID 文件(MySQL进程)。
答:应该借助操作系统的进程管理器(例如systemd ,分布式的进程管理云平台,或是类似 Foreman 的工具以及supervisor工具等进行进程保活),来管理输出流 ,响应崩溃的进程,以及处理用户触发的重启和关闭超级进程的请求。


  • IX. 易处理: 快速启动和优雅终止可最大化健壮性
    描述:12-Factor 应用的进程是易处理(disposable)的,意思是说它们可以瞬间开启或停止(比如Docker容器)。有利于快速、弹性的伸缩应用,迅速部署变化的代码 或 配置,稳健的部署应用(比如K8容器编排管理工具)。

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

Q:进程一旦接收终止信号(SIGTERM)就会优雅的终止,是指停止监听服务的端口,即拒绝所有新的请求,并继续执行当前已接收的请求然后退出;
对于 worker 进程来说,优雅终止是指将当前任务退回队列。例如,RabbitMQ 中,worker 可以发送一个NACK信号

Q:进程还应当在面对突然死亡时保持健壮,例如底层硬件故障, 虽然这种情况比起优雅终止来说少之又少,但终究有可能发生; 一种推荐的方式是使用一个健壮的后端队列,例如 Beanstalkd 它可以在客户端断开或超时后自动退回任务(以及supervisor和应用自动重启)。
综上所述: 12-Factor 应用都应该可以设计能够应对意外的、不优雅的终结。


  • X. 开发环境与线上环境等价:尽可能的保持开发,预发布,线上环境相同

Q:传统应用问题的表现再那些方面?
答: 开发环境(即开发人员的本地部署)和线上环境(外部用户访问的真实部署)之间存在着很多差异,主要表现在以下方面:
(1) 时间差异: 开发人员正在编写的代码可能需要几天,几周,甚至几个月才会上线。
(2) 人员差异: 开发人员编写代码,运维人员部署代码。
(3) 工具差异: 开发人员或许使用 Nginx,SQLite,OSX,而线上环境使用 Apache,MySQL 以及 Linux。

Q:12-Factor 应用与传统应用之间的对比区别
答:12-Factor应用想要做到持续部署就必须缩小本地与线上差异
(1) 缩小时间差异:开发人员可以几小时,甚至几分钟就部署代码。
(2) 缩小人员差异:开发人员不只要编写代码,更应该密切参与部署过程以及代码在线上的表现。
(3) 缩小工具差异:尽量保证开发环境以及线上环境的一致性。

传统应用 12-Factor 应用

每次部署间隔 | 数周| 几小时
开发人员 vs 运维人员 |不同的人 | 相同的人
开发环境 vs 线上环境 |不同 | 尽量接近

Q:12-Factor 应用的开发人员应该反对在不同环境间使用不同的后端服务:即使适配器已经可以几乎消除使用上的差异, 但是不同的后端服务意味着会突然出现的不兼容,从而导致测试、预发布都正常的代码在线上出现问题。
答: 但是随着容器技术的出现使得移植后端服务以及应用的所有部署变得简单,前提是开发、预发布以及线上环境,都应该使用同一个后端服务的相同版本。


  • XI. 日志: 把日志当作事件流
    描述:12-factor应用本身从不考虑存储自己的输出流。 不应该试图去写或者管理日志文件。相反,每一个运行的进程都会直接的标准输出(stdout)事件流。开发环境中开发人员可以通过这些数据流,实时在终端看到应用的活动。在预发布或线上部署中,每个进程的输出流由运行环境截获,并将其他输出流整理在一起,然后一并发送给一个或多个最终的处理程序,用于查看或是长期存档。

Q:使用日志的好处?它是如何存储的?
答: 日志应该是事件流的汇总,将所有运行中进程和后端服务的输出流按照时间顺序收集起来, 方便我们快速回溯应用的相关问题。
日志使得应用程序运行的动作变得透明。在基于服务器的环境中,日志通常被写在硬盘的一个文件里,但这只是一种输出格式,并且日志没有确定开始和结束,但随着应用在运行会持续的增加。


Q:日志使用输出流的优点?
答: 除了将日志输入到终端中观察也可以将其事件流输出至文件之中,但是都不方便运维或者开发人员进行进一步的数据处理查看,所以这时我们可以利用输出流发送到借助类似于 Splunk 这样的日志索引及分析系统,或 Hadoop/Hive 这样的通用数据存储系统查看应用的历史活动,它们提供了强大而灵活的功能,包括:

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


  • XII. 管理进程:后台管理任务当作一次性进程运行

Q:进程构成(process formation)有何作用?
答: 指用来处理应用的常规业务(比如处理 web 请求)的一组进程。与此不同,开发人员经常希望执行一些管理或维护应用的一次性任务,例如:
(1) 运行数据移植(Django 中的 manage.py migrate, Rails 中的 rake db:migrate)。
(2) 运行一个控制台(也被称为 REPL shell),来执行一些代码或是针对线上数据库做一些检查。大多数语言都通过解释器提供了一个 REPL 工具(python 或 perl) ,或是其他命令(Ruby 使用 irb, Rails 使用 rails console)。
(3) 运行一些提交到代码仓库的一次性脚本。

Q:如何将后台管理任务进行一次性进程运行?
答: 一次性管理进程应该和正常的常驻进程使用同样的环境,并且管理进程和任何其他的进程一样使用相同的代码和配置 ,基于某个发布版本运行(后台管理代码应该随其他应用程序代码一起发布,从而避免同步问题。
进程类型应该使用同样的依赖隔离技术,比如在Docker运行环境中我们可以在构建镜像的Dockerfile文件以及entrypoint.sh脚本中使用 shell 命令调用一次性管理进程。
Tips: 在容器构建中采用 exec 运行应用进程默认pid为1,这非常便于容器的启动与停止(实际是方便容器中的系统应用发送SIGTERM信号给系统)