Category Archives: JAVA

从零开始搭建创业公司后台技术栈

有点眼晕,以上只是我们会用到的一些语言的合集,而且只是语言层面的一部分,就整个后台技术栈来说,这只是一个开始,从语言开始,还有很多很多的内容。今天要说的后台是大后台的概念,放在服务器上的东西都属于后台的东西,比如使用的框架,语言,数据库,服务,操作系统等等,整个后台技术栈我的理解包括4个层面的内容:

  • 语言: 用了哪些开发语言,如:c++/java/go/php/python/ruby等等;
  • 组件:用了哪些组件,如:MQ组件,数据库组件等等;
  • 流程:怎样的流程和规范,如:开发流程,项目流程,发布流程,监控告警流程,代码规范等等;
  • 系统:系统化建设,上面的流程需要有系统来保证,如:规范发布流程的发布系统,代码管理系统等等;

结合以上的的4个层面的内容,整个后台技术栈的结构如图2所示:

[图2 后台技术栈结构]

以上的这些内容都需要我们从零开始搭建,在创业公司,没有大公司那些完善的基础设施,需要我们从开源界,从云服务商甚至有些需要自己去组合,去拼装,去开发一个适合自己的组件或系统以达成我们的目标。咱们一个个系统和组件的做选型,最终形成我们的后台技术栈。

一、各系统组件选型

1、项目管理/Bug管理/问题管理

项目管理软件是整个业务的需求,问题,流程等等的集中地,大家的跨部门沟通协同大多依赖于项目管理工具。有一些 SAAS 的项目管理服务可以使用,但是很多时间不满足需求,此时我们可以选择一些开源的项目,这些项目本身有一定的定制能力,有丰富的插件可以使用,一般的创业公司需求基本上都能得到满足,常用的项目如下:

  • Redmine: 用 Ruby 开发的,有较多的插件可以使用,能自定义字段,集成了项目管理,BUG 问题跟踪,WIKI 等功能,不过好多插件 N 年没有更新了;
  • Phabricator: 用 PHP 开发的,facebook 之前的内部工具,开发这工具的哥们离职后自己搞了一个公司专门做这个软件,集成了代码托管, Code Review,任务管理,文档管理,问题跟踪等功能,强烈推荐较敏捷的团队使用;
  • Jira:用 Java 开发的,有用户故事,task 拆分,燃尽图等等,可以做项目管理,也可以应用于跨部门沟通场景,较强大;
  • 悟空CRM :这个不是项目管理,这个是客户管理,之所以在这里提出来,是因为在 To B 的创业公司里面,往往是以客户为核心来做事情的,可以将项目管理和问题跟进的在悟空 CRM 上面来做,他的开源版本已经基本实现了 CR< 的核心 功能,还带有一个任务管理功能,用于问题跟进,不过用这个的话,还是需要另一个项目管理的软件协助,顺便说一嘴,这个系统的代码写得很难维护,只能适用于客户规模小(1万以内)时。

2、DNS

DNS 是一个很通用的服务,创业公司基本上选择一个合适的云厂商就行了,国内主要是两家:

  • 阿里万网:阿里 2014 年收购了万网,整合了其域名服务,最终形成了现在的阿里万网,其中就包含 DNS 这块的服务;
  • 腾讯 DNSPod: 腾讯 2012 年以 4000 万收购 DNSPod 100% 股份,主要提供域名解析和一些防护功能;

如果你的业务是在国内,主要就是这两家,选 一个就好,像今日头条这样的企业用的也是 DNSPod 的服务,除非一些特殊的原因才需要自建,比如一些 CDN 厂商,或者对区域有特殊限制的。要实惠一点用阿里最便宜的基础版就好了,要成功率高一些,还是用DNSPod 的贵的那种。

在国外还是选择亚马逊吧,阿里的 DNS 服务只有在日本和美国有节点,东南亚最近才开始部点, DNSPod 也只有美国和日本,像一些出海的企业,其选择的云服务基本都是亚马逊。

如果是线上产品,DNS 强烈建议用付费版,阿里的那几十块钱的付费版基本可以满足需求。如果还需要一些按省份或按区域调试的逻辑,则需要加钱,一年也就几百块,省钱省力。

如果是国外,优先选择亚马逊,如果需要国内外互通并且有自己的 APP 的话,建议还是自己实现一些容灾逻辑或者智能调度,因为没有一个现成的 DNS 服务能同时较好的满足国内外场景,或者用多个域名,不同的域名走不同的 DNS 。

3、LB(负载均衡)

LB(负载均衡)是一个通用服务,一般云厂商的 LB 服务基本都会如下功能:

  • 支持四层协议请求(包括 TCP、UDP 协议);
  • 支持七层协议请求(包括 HTTP、HTTPS 协议);
  • 集中化的证书管理系统支持 HTTPS 协议;
  • 健康检查;

如果你线上的服务机器都是用的云服务,并且是在同一个云服务商的话,可以直接使用云服务商提供的 LB 服务,如阿里云的 SLB,腾讯云的 CLB, 亚马逊 的 ELB 等等。如果是自建机房基本都是 LVS + Nginx。

4、CDN

CDN 现在已经是一个很红很红的市场,基本上只能挣一些辛苦钱,都是贴着成本在卖。国内以网宿为龙头,他们家占据整个国内市场份额的40%以上,后面就是腾讯,阿里。网宿有很大一部分是因为直播的兴起而崛起。

国外,Amazon 和 Akamai 合起来占比大概在 50%,曾经的国际市场老大 Akamai 拥有全球超一半的份额,在 Amazon CDN入局后,份额跌去了将近 20%,众多中小企业都转向后者,Akamai 也是无能为力。

国内出海的 CDN 厂商,更多的是为国内的出海企业服务,三家大一点的 CDN 服务商里面也就网宿的节点多一些,但是也多不了多少。阿里和腾讯还处于前期阶段,仅少部分国家有节点。

就创业公司来说,CDN 用腾讯云或阿里云即可,其相关系统较完善,能轻松接入,网宿在系统支持层面相对较弱一些,而且还贵一些。并且,当流量上来后,CDN 不能只用一家,需要用多家,不同的 CDN 在全国的节点覆盖不一样,而且针对不同的客户云厂商内部有些区分客户集群,并不是全节点覆盖(但有些云厂商说自己是全网节点),除了节点覆盖的问题,多 CDN 也在一定程度上起到容灾的作用。

5、RPC框架

维基百科对 RPC 的定义是:远程过程调用(Remote Procedure Call,RPC)是一个计算机通信协议。该协议允许运行于一台计算机的程序调用另一台计算机的子程序,而程序员无需额外地为这个交互作用编程。

通俗来讲,一个完整的RPC调用过程,就是 Server 端实现了一个函数,客户端使用 RPC 框架提供的接口,调用这个函数的实现,并获取返回值的过程。

业界 RPC 框架大致分为两大流派,一种侧重跨语言调用,另一种是偏重服务治理。

跨语言调用型的 RPC 框架有 Thrift、gRPC、Hessian、Hprose 等。这类 RPC 框架侧重于服务的跨语言调用,能够支持大部分的语言进行语言无关的调用,非常适合多语言调用场景。但这类框架没有服务发现相关机制,实际使用时需要代理层进行请求转发和负载均衡策略控制。

其中,gRPC 是 Google 开发的高性能、通用的开源 RPC 框架,其由 Google 主要面向移动应用开发并基于 HTTP/2 协议标准而设计,基于 ProtoBuf(Protocol Buffers) 序列化协议开发,且支持众多开发语言。本身它不是分布式的,所以要实现框架的功能需要进一步的开发。

Hprose(High Performance Remote Object Service Engine) 是一个 MIT 开源许可的新型轻量级跨语言跨平台的面向对象的高性能远程动态通讯中间件。

服务治理型的 RPC 框架的特点是功能丰富,提供高性能的远程调用、服务发现及服务治理能力,适用于大型服务的服务解耦及服务治理,对于特定语言(Java)的项目可以实现透明化接入。缺点是语言耦合度较高,跨语言支持难度较大。国内常见的冶理型 RPC 框架如下:

  • Dubbo: Dubbo 是阿里巴巴公司开源的一个 Java 高性能优秀的服务框架,使得应用可通过高性能的 RPC 实现服务的输出和输入功能,可以和 Spring 框架无缝集成。当年在淘宝内部,Dubbo 由于跟淘宝另一个类似的框架 HSF 有竞争关系,导致 Dubbo 团队解散,最近又活过来了,有专职同学投入。
  • DubboX: DubboX 是由当当在基于 Dubbo 框架扩展的一个 RPC 框架,支持 REST 风格的远程调用、Kryo/FST 序列化,增加了一些新的feature。
  • Motan: Motan 是新浪微博开源的一个 Java 框架。它诞生的比较晚,起于 2013 年,2016 年 5 月开源。Motan 在微博平台中已经广泛应用,每天为数百个服务完成近千亿次的调用。
  • rpcx: rpcx 是一个类似阿里巴巴 Dubbo 和微博  Motan 的分布式的 RPC 服务框架,基于 Golang net/rpc 实现。但是 rpcx 基本只有一个人在维护,没有完善的社区,使用前要慎重,之前做 Golang 的 RPC 选型时也有考虑这个,最终还是放弃了,选择了 gRPC,如果想自己自研一个 RPC 框架,可以参考学习一下。

6、名字发现/服务发现

名字发现和服务发现分为两种模式,一个是客户端发现模式,一种是服务端发现模式。

框架中常用的服务发现是客户端发现模式。

所谓服务端发现模式是指客户端通过一个负载均衡器向服务发送请求,负载均衡器查询服务注册表并把请求路由到一台可用的服务实例上。现在常用的负载均衡器都是此类模式,常用于微服务中。

所有的名字发现和服务发现都要依赖于一个可用性非常高的服务注册表,业界常用的服务注册表有如下三个:

  • etcd,一个高可用、分布式、一致性、key-value方式的存储,被用在分享配置和服务发现中。两个著名的项目使用了它:k8s和Cloud Foundry。
  • consul,一个发现和配置服务的工具,为客户端注册和发现服务提供了API,Consul还可以通过执行健康检查决定服务的可用性。
  • Apache Zookeeper,是一个广泛使用、高性能的针对分布式应用的协调服务。Apache Zookeeper本来是 Hadoop 的子工程,现在已经是顶级工程了。

除此之外也可以自己实现服务实现,或者用 Redis 也行,只是需要自己实现高可用性。

7、关系数据库

关系数据库分为两种,一种是传统关系数据,如 Oracle, MySQL,Maria, DB2,PostgreSQL 等等,另一种是 NewSQL,即至少要满足以下五点的新型关系数据库:

  1. 完整地支持SQL,支持JOIN / GROUP BY /子查询等复杂SQL查询;
  2. 支持传统数据标配的 ACID 事务,支持强隔离级别。
  3. 具有弹性伸缩的能力,扩容缩容对于业务层完全透明。
  4. 真正的高可用,异地多活、故障恢复的过程不需要人为的接入,系统能够自动地容灾和进行强一致的数据恢复。
  5. 具备一定的大数据分析能力

传统关系数据库用得最多的是 MySQL,成熟,稳定,一些基本的需求都能满足,在一定数据量级之前基本单机传统数据库都可以搞定,而且现在较多的开源系统都是基于 MySQL,开箱即用,再加上主从同步和前端缓存,百万 pv 的应用都可以搞定了。不过 CentOS 7 已经放弃了 MySQL,而改使用 MariaDB。MariaDB 数据库管理系统是 MySQ L的一个分支,主要由开源社区在维护,采用GPL 授权许可。开发这个分支的原因之一是:甲骨文公司收购了 MySQL 后,有将 MySQ L闭源的潜在风险,因此社区采用分支的方式来避开这个风险。

在 Google 发布了  F1: A Distributed SQL Database That Scales 和  Spanner: Google’s Globally-Distributed Databasa 之后,业界开始流行起 NewSQL。于是有了 CockroachDB,于是有了 奇叔公司的 TiDB。国内已经有比较多的公司使用 TiDB,之前在创业公司时在大数据分析时已经开始应用 TiDB,当时应用的主要原因是 MySQL 要使用分库分表,逻辑开发比较复杂,扩展性不够。

8、NoSQL

NoSQL 顾名思义就是 Not-Only SQL,也有人说是 No – SQL, 个人偏向于Not – Only SQL,它并不是用来替代关系库,而是作为关系型数据库的补充而存在。

常见 NoSQL 有4个类型:

  1. 键值,适用于内容缓存,适合混合工作负载并发高扩展要求大的数据集,其优点是简单,查询速度快,缺点是缺少结构化数据,常见的有 Redis, Memcache, BerkeleyDB 和 Voldemort 等等;
  2. 列式,以列簇式存储,将同一列数据存在一起,常见于分布式的文件系统,其中以 Hbase,Cassandra 为代表。Cassandra 多用于写多读少的场景,国内用得比较多的有 360,大概 1500 台机器的集群,国外大规模使用的公司比较多,如 Ebay,Instagram,Apple 和沃尔玛等等;
  3. 文档,数据存储方案非常适用承载大量不相关且结构差别很大的复杂信息。性能介于 kv 和关系数据库之间,它的灵感来于 lotus notes,常见的有 MongoDB,CouchDB 等等;
  4. 图形,图形数据库擅长处理任何涉及关系的状况。社交网络,推荐系统等。专注于构建关系图谱,需要对整个图做计算才能得出结果,不容易做分布式的集群方案,常见的有 Neo4J,InfoGrid 等。

除了以上4种类型,还有一些特种的数据库,如对象数据库,XML 数据库,这些都有针对性对某些存储类型做了优化的数据库。

在实际应用场景中,何时使用关系数据库,何时使用 NoSQL,使用哪种类型的数据库,这是我们在做架构选型时一个非常重要的考量,甚至会影响整个架构的方案。

9、消息中间件

消息中间件在后台系统中是必不可少的一个组件,一般我们会在以下场景中使用消息中间件:

  • 异步处理:异步处理是使用消息中间件的一个主要原因,在工作中最常见的异步场景有用户注册成功后需要发送注册成功邮件、缓存过期时先返回老的数据,然后异步更新缓存、异步写日志等等;通过异步处理,可以减少主流程的等待响应时间,让非主流程或者非重要业务通过消息中间件做集中的异步处理。
  • 系统解耦:比如在电商系统中,当用户成功支付完成订单后,需要将支付结果给通知ERP系统、发票系统、WMS、推荐系统、搜索系统、风控系统等进行业务处理;这些业务处理不需要实时处理、不需要强一致,只需要最终一致性即可,因此可以通过消息中间件进行系统解耦。通过这种系统解耦还可以应对未来不明确的系统需求。
  • 削峰填谷:当系统遇到大流量时,监控图上会看到一个一个的山峰样的流量图,通过使用消息中间件将大流量的请求放入队列,通过消费者程序将队列中的处理请求慢慢消化,达到消峰填谷的效果。最典型的场景是秒杀系统,在电商的秒杀系统中下单服务往往会是系统的瓶颈,因为下单需要对库存等做数据库操作,需要保证强一致性,此时使用消息中间件进行下单排队和流控,让下单服务慢慢把队列中的单处理完,保护下单服务,以达到削峰填谷的作用。

业界消息中间件是一个非常通用的东西,大家在做选型时有使用开源的,也有自己造轮子的,甚至有直接用 MySQL 或 Redis 做队列的,关键看是否满足你的需求,如果是使用开源的项目,以下的表格在选型时可以参考:

[图3]

以上图的纬度为:名字 成熟度所属社区/公司 文档 授权方式 开发语言支持的协议 客户端支持的语言 性能 持久化 事务 集群 负载均衡 管理界面 部署方式 评价

10 、代 码管理

代码是互联网创业公司的命脉之一,代码管理很重要,常见的考量点包括两块:

  • 安全和权限管理,将代码放到内网并且对于关系公司命脉的核心代码做严格的代码控制和机器的物理隔离;
  • 代码管理工具,Git 作为代码管理的不二之选,你值得拥有。Gitlab 是当今最火的开源 Git 托管服务端,没有之一,虽然有企业版,但是其社区版基本能满足我们大部分需求,结合 Gerrit 做 Code review,基本就完美了。当然 Gitlab 也有代码对比,但没Gerrit 直观。Gerrit 比 Gitlab 提供了更好的代码检查界面与主线管理体验,更适合在对代码质量有高要求的文化下使用。

11、持续集成

持续集成简,称 CI(continuous integration), 是一种软件开发实践,即团队开发成员经常集成他们的工作,每天可能会发生多次集成。每次集成都通过自动化的构建(包括编译,发布,自动化测试)来验证,从而尽早地发现集成错误。持续集成为研发流程提供了代码分支管理/比对、编译、检查、发布物输出等基础工作,为测试的覆盖率版本编译、生成等提供统一支持。

业界免费的持续集成工具中系统我们有如下一些选择:

  • Jenkins:Jjava写的 有强大的插件机制,MIT协议开源 (免费,定制化程度高,它可以在多台机器上进行分布式地构建和负载测试)。Jenkins可以算是无所不能,基本没有 Jenkins 做不了的,无论从小型团队到大型团队 Jenkins 都可以搞定。 不过如果要大规模使用,还是需要有人力来学习和维护。
  • TeamCity: TeamCity与Jenkins相比使用更加友好,也是一个高度可定制化的平台。但是用的人多了,TeamCity就要收费了。
  • Strider: Strider 是一个开源的持续集成和部署平台,使用 Node.js 实现,存储使用的是 MongoDB,BSD 许可证,概念上类似 Travis 和Jenkins。
  • GitLabCI:从GitLab8.0开始,GitLab CI 就已经集成在 GitLab,我们只要在项目中添加一个 .gitlab-ci.yml 文件,然后添加一个Runner,即可进行持续集成。并且 Gitlab 与 Docker 有着非常好的相互协作的能力。免费版与付费版本不同可以参见这里: https://about.gitlab.com/products/feature-comparison/
  • Travis:Travis 和 Github 强关联;闭源代码使用 SaaS 还需考虑安全问题; 不可定制;开源项目免费,其它收费;
  • Go: Go是ThoughtWorks公司最新的Cruise Control的化身。除了 ThoughtWorks 提供的商业支持,Go是免费的。它适用于Windows,Mac和各种Linux发行版。

12、日志系统

日志系统一般包括打日志,采集,中转,收集,存储,分析,呈现,搜索还有分发等。一些特殊的如染色,全链条跟踪或者监控都可能需要依赖于日志系统实现。日志系统的建设不仅仅是工具的建设,还有规范和组件的建设,最好一些基本的日志在框架和组件层面加就行了,比如全链接跟踪之类的。

对于常规日志系统ELK能满足大部分的需求,ELK 包括如下组件:

  • ElasticSearch 是个开源分布式搜索引擎,它的特点有:分布式,零配置,自动发现,索引自动分片,索引副本机制,restful风格接口,多数据源,自动搜索负载等。
  • Logstash 是一个完全开源的工具,它可以对你的日志进行收集、分析,并将其存储供以后使用。
  • Kibana 是一个开源和免费的工具,它可以为 Logstash 和 ElasticSearch 提供的日志分析友好的 Web 界面,可以帮助汇总、分析和搜索重要数据日志。

Filebeat 已经完全替代了 Logstash-Forwarder 成为新一代的日志采集器,同时鉴于它轻量、安全等特点,越来越多人开始使用它。

因为免费的 ELK 没有任何安全机制,所以这里使用了 Nginx 作反向代理,避免用户直接访问 Kibana 服务器。加上配置 Nginx 实现简单的用户认证,一定程度上提高安全性。另外,Nginx 本身具有负载均衡的作用,能够提高系统访问性能。ELK 架构如图4所示:

[图4] ELK 流程图

对于有实时计算的需求,可以使用 Flume+Kafka+Storm+MySQL方案,一 般架构如图5所示:

[图5] 实时分析系统架构图

其中:

  • Flume 是一个分布式、可靠、和高可用的海量日志采集、聚合和传输的日志收集系统,支持在日志系统中定制各类数据发送方,用于收集数据;同时,Flume 提供对数据进行简单处理,并写到各种数据接受方(可定制)的能力。
  • Kafka 是由 Apache 软件基金会开发的一个开源流处理平台,由 Scala 和 Java 编写。其本质上是一个“按照分布式事务日志架构的大规模发布/订阅消息队列”,它以可水平扩展和高吞吐率而被广泛使用。

Kafka 追求的是高吞吐量、高负载,Flume 追求的是数据的多样性,二者结合起来简直完美。

13、监控系统

监控系统只包含与后台相关的,这里主要是两块,一个是操作系统层的监控,比如机器负载,IO,网络流量,CPU,内存等操作系统指标的监控。另一个是服务质量和业务质量的监控,比如服务的可用性,成功率,失败率,容量,QPS 等等。常见业务的监控系统先有操作系统层面的监控(这部分较成熟),然后扩展出其它监控,如 zabbix,小米的 open-falcon,也有一出来就是两者都支持的,如 prometheu s。如果对业务监控要求比较高一些,在创业选型中建议可以优先考虑 prometheus。这里有一个有趣的分布,如图6所示

[图6 监控系统分布]

亚洲区域使用 zabbix 较多,而美洲和欧洲,以及澳大利亚使用 prometheus 居多,换句话说,英文国家地区(发达国家?)使用prometheus 较多。

Prometheus 是由 SoundCloud 开发的开源监控报警系统和时序列数据库( TSDB )。Prometheus 使用 Go 语言开发,是 Google BorgMon 监控系统的开源版本。相对于其它监控系统使用的 push 数据的方式,prometheus 使用的是 pull 的方式,其架构如图7所示:

[图7] prometheus架构图

如上图所示,prometheus 包含的主要组件如下:

  • Prometheus Server 主要负责数据采集和存储,提供 PromQL 查询语言的支持。Server 通过配置文件、文本文件、Zookeeper、Consul、DNS SRV Lookup等方式指定抓取目标。根据这些目标会,Server 定时去抓取 metric s数据,每个抓取目标需要暴露一个 http 服务的接口给它定时抓取。
  • 客户端SDK:官方提供的客户端类库有 go、java、scala、python、ruby,其他还有很多第三方开发的类库,支持 nodejs、php、erlang 等。
  • Push Gateway 支持临时性 Job 主动推送指标的中间网关。
  • Exporter Exporter 是Prometheus的一类数据采集组件的总称。它负责从目标处搜集数据,并将其转化为 Prometheus 支持的格式。与传统的数据采集组件不同的是,它并不向中央服务器发送数据,而是等待中央服务器主动前来抓取。Prometheus提供多种类型的 Exporter 用于采集各种不同服务的运行状态。目前支持的有数据库、硬件、消息中间件、存储系统、HTTP服务器、JMX等。
  • alertmanager:是一个单独的服务,可以支持 Prometheus 的查询语句,提供十分灵活的报警方式。
  • Prometheus HTTP API的查询方式,自定义所需要的输出。
  • Grafana 是一套开源的分析监视平台,支持 Graphite, InfluxDB, OpenTSDB, Prometheus, Elasticsearch, CloudWatch 等数据源,其 UI 非常漂亮且高度定制化。

创业公司选择 Prometheus + Grafana 的方案,再加上统一的服务框架(如 gRPC ),可以满足大部分中小团队的监控需求。

14、配置系统

随着程序功能的日益复杂,程序的配置日益增多:各种功能的开关、降级开关,灰度开关,参数的配置、服务器的地址、数据库配置等等,除此之外,对后台程序配置的要求也越来越高:配置修改后实时生效,灰度发布,分环境、分用户,分集群管理配置,完善的权限、审核机制等等,在这样的大环境下,传统的通过配置文件、数据库等方式已经越来越无法满足开发人员对配置管理的需求,业界有如下两种方案:

  • 基于 zk 和 etcd,支持界面和 api ,用数据库来保存版本历史,预案,走审核流程,最后下发到 zk 或 etcd 这种有推送能力的存储里(服务注册本身也是用 zk 或 etcd,选型就一块了)。客户端都直接和 zk 或 etcd 打交道。至于灰度发布,各家不同,有一种实现是同时发布一个需要灰度的 IP 列表,客户端监听到配置节点变化时,对比一下自己是否属于该列表。PHP 这种无状态的语言和其他 zk/etcd 不支持的语言,只好自己在客户端的机器上起一个 Agent 来监听变化,再写到配置文件或共享内存,如 360 的 Qconf。
  • 基于运维自动化的配置文件的推送,审核流程,配置数据管理和方案一类似,下发时生成配置文件,基于运维自动化工具如Puppet,Ansible 推送到每个客户端,而应用则定时重新读取这个外部的配置文件,灰度发布在下发配置时指定IP列表。

创业公司前期不需要这种复杂,直接上 zk,弄一个界面管理 zk 的内容,记录一下所有人的操作日志,程序直连 zk,或者或者用Qconf 等基于 zk 优化后的方案。

15、发布系统/部署系统

从软件生产的层面看,代码到最终服务的典型流程如图8所示:

[图8 流程图]

从上图中可以看出,从开发人员写下代码到服务最终用户是一个漫长过程,整体可以分成三个阶段:

  • 从代码(Code)到成品库(Artifact)这个阶段主要对开发人员的代码做持续构建并把构建产生的制品集中管理,是为部署系统准备输入内容的阶段。
  • 从制品到可运行服务 这个阶段主要完成制品部署到指定环境,是部署系统的最基本工作内容。
  • 从开发环境到最终生产环境 这个阶段主要完成一次变更在不同环境的迁移,是部署系统上线最终服务的核心能力。

发布系统集成了制品管理,发布流程,权限控制,线上环境版本变更,灰度发布,线上服务回滚等几方面的内容,是开发人员工作结晶最终呈现的重要通道。开源的项目中没有完全满足的项目,如果只是 Web 类项目,Walle、Piplin 都是可用的,但是功能不太满足,创业初期可以集成 Jenkins + Gitlab + Walle (可以考虑两天时间完善一下),以上方案基本包括 制品管理,发布流程,权限控制,线上环境版本变更,灰度发布(需要自己实现),线上服务回滚等功能。

16、跳板机

跳板机面对的是需求是要有一种能满足角色管理与授权审批、信息资源访问控制、操作记录和审计、系统变更和维护控制要求,并生成一些统计报表配合管理规范来不断提升IT内控的合规性,能对运维人员操作行为的进行控制和审计,对误操作、违规操作导致的操作事故,快速定位原因和责任人。其功能模块一般包括:帐户管理、认证管理、授权管理、审计管理等等

开源项目中,Jumpserver 能够实现跳板机常见需求,如授权、用户管理、服务器基本信息记录等,同时又可批量执行脚本等功能;其中录像回放、命令搜索、实时监控等特点,又能帮助运维人员回溯操作历史,方便查找操作痕迹,便于管理其他人员对服务器的操作控制。

17、机器管理

机器管理的工具选择的考量可以包含以下三个方面:

  1. 是否简单,是否需要每台机器部署agent(客户端)
  2. 语言的选择(puppet/chef vsansible/saltstack)开源技术,不看官网不足以熟练,不懂源码不足以精通;Puppet、Chef基于Ruby开发,ansible、saltstack基于python开发的
  3. 速度的选择(ansiblevssaltstack) ansible基于SSH协议传输数据,Saltstack使用消息队列zeroMQ传输数据;大规模并发的能力对于几十台-200台规模的兄弟来讲,ansible的性能也可接受,如果一次操作上千台,用salt好一些。

如图9所示:

[图9 机器管理软件对比]

一般创业公司选择 Ansible 能解决大部问题,其简单,不需要安装额外的客户端,可以从命令行来运行,不需要使用配置文件。至于比较复杂的任务,Ansible 配置通过名为 Playbook 的配置文件中的 YAML 语法来加以处理。Playbook 还可以使用模板来扩展其功能。

二、创业公司的选择

1、选择合适的语言

  • 选择团队熟悉的/能掌控的,创业公司人少事多,无太多冗余让研发团队熟悉新的语言,能快速上手,能快速出活,出了问题能快速解决的问题的语言才是好的选择。
  • 选择更现代一些的,这里的现代是指语言本身已经完成一些之前需要特殊处理的特性,比如内存管理,线程等等。
  • 选择开源轮子多的或者社区活跃度高的,这个原则是为了保证在开发过程中减少投入,有稳定可靠的轮子可以使用,遇到问题可以在网上快速搜索到答案。
  • 选择好招人的 一门合适的语言会让创业团队减少招聘的成本,快速招到合适的人。
  • 选择能让人有兴趣的 与上面一点相关,让人感兴趣,在后面留人时有用。

2、选择合适的组件和云服务商

  • 选择靠谱的云服务商;
  • 选择云服务商的组件;
  • 选择成熟的开源组件,而不是最新出的组件;
  • 选择采用在一线互联网公司落地并且开源的,且在社区内形成良好口碑的产品;
  • 开源社区活跃度;

选择靠谱的云服务商,其实这是一个伪命题,因为哪个服务商都不靠谱,他们所承诺的那些可用性问题基本上都会在你的身上发生,这里我们还是需要自己做一些工作,比如多服务商备份,如用CDN,你一定不要只选一家,至少选两家,一个是灾备,保持后台切换的能力,另一个是多点覆盖,不同的服务商在CDN节点上的资源是不一样的。

选择了云服务商以后,就会有很多的产品你可以选择了,比较存储,队列这些都会有现成的产品,这个时候就纠结了,是用呢?还是自己在云主机上搭呢?在这里我的建议是前期先用云服务商的,大了后再自己搞,这样会少掉很多运维的事情,但是这里要多了解一下云服务商的组件特性以及一些坑,比如他们内网会经常断开,他们升级也会闪断,所以在业务侧要做好容错和规避。

关于开源组件,尽可能选择成熟的,成熟的组件经历了时间的考验,基本不会出大的问题,并且有成套的配套工具,出了问题在网上也可以很快的找到答案,你所遇到的坑基本上都有人踩过了。

3、制定流程和规范

  • 制定开发的规范,代码及代码分支管理规范,关键性代码仅少数人有权限;
  • 制定发布流程规范,从发布系统落地;
  • 制定运维规范;
  • 制定数据库操作规范,收拢数据库操作权限;
  • 制定告警处理流程,做到告警有人看有人处理;
  • 制定汇报机制,晨会/周报;

4、自研和选型合适的辅助系统

所有的流程和规范都需要用系统来固化,否则就是空中楼阁,如何选择这些系统呢?参照上个章节咱们那些开源的,对比一下选择的语言,组件之类的,选择一个最合适的即可。

比如项目管理的,看下自己是什么类型的公司,开发的节奏是怎样的,瀑布,敏捷的 按项目划分,还是按客户划分等等,平时是按项目组织还是按任务组织等等

比如日志系统,之前是打的文本,那么上一个elk,规范化一些日志组件,基本上很长一段时间内不用考虑日志系统的问题,最多拆分一下或者扩容一下。等到组织大了,自己搞一个日志系统。

比如代码管理,项目管理系统这些都放内网,安全,在互联网公司来说,属于命脉了,命脉的东西还是放在别人拿不到或很难拿到的地方会比较靠谱一些。

5、选择过程中需要思考的问题

技术栈的选择有点像做出了某种承诺,在一定的时间内这种承诺没法改变,于是我们需要在选择的时候有一些思考。

看前面内容,有一个词出现了三次,合适,选择是合适的,不是最好,也不是最新,是最合适,适合是针对当下,这种选择是最合适的吗?比如用 Go 这条线的东西,技术比较新,业界组件储备够吗?组织内的人员储备够吗?学习成本多少?写出来的东西能满足业务性能要求吗?能满足时间要求吗?

向未来看一眼,在一年到三年内,我们需要做出改变吗?技术栈要做根本性的改变吗?如果组织发展很快,在 200 人,500 人时,现有的技术栈是否需要大动?

创业过程中需要考虑成本,这里的成本不仅仅是花费多少钱,付出多少工资,有时更重要的是时间成本,很多业务在创业时大家拼的就是时间,就是一个时间窗,过了就没你什么事儿了。

三、基于云的创业公司后台技术架构

结合上面内容的考量,在对一个个系统和组件的做选型之后,以云服务为基础,一个创业公司的后台技术架构如图10所示:

[图10 后台技术架构]

参考资料

http://database.51cto.com/art/201109/291781.htm

https://zh.wikipedia.org/wiki/Kafka

https://prometheus.io/docs/introduction/overview/

http://deadline.top/2016/11/23/配置中心那点事/

http://blog.fit2cloud.com/2016/01/26/deployment-system.html

from:http://www.phppan.com/2018/04/svr-stack/

How to Install Apache Tomcat on CentOS

Apache Tomcat is an open source Java Servlet implementation developed by the Apache Software Foundation. Beside Java Servlets, Tomcat implements several other Java server technologies including  JavaServer Pages (JSP), Java Expression Language, and Java WebSocket. Tomcat provides an HTTP Web Server for Java applications with support for HTTP/2, OpenSSL for JSSE and TLS virtual hosting.

In this tutorial, I will show you how to install and configure Apache Tomcat 8.5 on a CentOS 7 server and how to install and configure Java on a CentOS server which is one of the prerequisites for Tomcat.

Prerequisites

  • Server with CentOS 7 – 64bit
  • 2 GB or more RAM (Recommended)
  • Root Privileges on the server

Step 1 – Install Java (JRE and JDK)

In this step, we will install the Java JRE and JDK from the CentOS repository. We will install Java 1.8.11 on the server with the yum command.

Run this command to install Java JRE and JDK from CentOS repository with yum:

yum -y install java-1.8.0-openjdk.x86_64 java-1.8.0-openjdk-devel.x86_64

It will take some time, wait until the installation finished.

Then you should check the Java version with the command below:

java -version

You should see results similar to the ones below:

openjdk version “1.8.0_111”
OpenJDK Runtime Environment (build 1.8.0_111-b15)
OpenJDK 64-Bit Server VM (build 25.111-b15, mixed mode)

Check the Java version

Step 2 – Configure the Java Home Environment

In the first step, we’ve installed Java. Now we need to configure the JAVA_HOME environment variable on the CentOS server so that Java applications can find the right Java version and Tomcat requires the JAVA_HOME environment to be setup properly, so we need to configure it.

Before we configure the JAVA_HOME environment, we need to know where the Java directory is. Check the Java directory with the command below:

sudo update-alternatives –config java

Java directory = “/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.111-1.b15.el7_2.x86_64/jre

Then edit the environment file with vim:

vim /etc/environment

Add the JAVA_HOME environment variable by adding the configuration below:

JAVA_HOME=”/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.111-1.b15.el7_2.x86_64/jre”

Save the /etc/environment file and exit vim.

Next, edit the .bash_profile file and add the JAVA_HOME variable as well:

vim ~/.bash_profile

At the end of the file, paste the configuration below:

export JAVA_HOME=/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.111-1.b15.el7_2.x86_64/jre
export PATH=$JAVA_HOME/bin:$PATH

Save the file, then reload the bash_profile file.

source ~/.bash_profile

Make sure there is no error, Finally check the JAVA_HOME environment variable:

echo $JAVA_HOME

You will see Java path directory.

Setup the Java home environment variable

Step 3 – Install Apache Tomcat 8.5

In this step, we will install Apache Tomcat under the user tomcat (which we have to create first).

Create a user and group named tomcat:

groupadd tomcat
useradd -s /bin/false -g tomcat -d /opt/tomcat tomcat

Note:
-s /bin/false = disable shell access
-g tomcat = assign new user to the group tomcat
-d /opt/tomcat = define the home directory for the user

Next, go to the /opt directory and download tomcat with the wget command:

cd /opt/
wget http://mirror.wanxp.id/apache/tomcat/tomcat-8/v8.5.6/bin/apache-tomcat-8.5.6.tar.gz

Extract Tomcat and move all the files and directories that are in the ‘apache-tomcat-8.5.6’ directory to the ‘tomcat’ directory.

tar -xzvf apache-tomcat-8.5.6.tar.gz
mv apache-tomcat-8.5.6/* tomcat/

Now change the owner of the tomcat directory to the tomcat user and group.

chown -hR tomcat:tomcat tomcat

Step 4 – Test Apache Tomcat

In step 3, we installed and configure tomcat. In this step, we just want to run a short test to make sure there are no errors.

Go to the tomcat/bin directory and run the command ‘startup.sh’ to test Apache Tomcat:

cd /opt/tomcat/bin/
./startup.sh

Make sure the result is ‘Tomcat started’.

Tomcat is using port 8080 now, check the open port on the server with the netstat command.

netstat -plntu

Check that Tomcat has been started with netstat

Or visit the server IP address with port 8080 – in my case 192.168.1.120:8080 – with a web browser. You will see the Apache Tomcat default page.

Test Apache Tomcat with a Browser

Next, stop Apache Tomcat and because we will run it Tomcat with a systemd service file in the final configuration. Make sure the tomcat directory is owned by the tomcat user and group.

cd /opt/tomcat/bin/
./shutdown.sh
chown -hR tomcat:tomcat /opt/tomcat/

Shutdown Apache Tomcat server test.

Step 5 – Setup Apache Tomcat Service

In this tutorial, we will run Apache Tomcat as tomcat user with a systemd service file for easy starting and stopping of the service. So the next step is to create a ‘tomcat.service’ file.

Go to the systemd system directory and create a new file ‘tomcat.service’.

cd /etc/systemd/system/
vim tomcat.service

Paste the configuration below:

Save the file and exit vim.

Reload the systemd daemon, then start and add the Apache Tomcat service at boot time.

systemctl daemon-reload
systemctl start tomcat
systemctl enable tomcat

Now check that tomcat is running by checking the open port 8080.

netstat -plntu

And check the tomcat status, make sure the service is active.

systemctl status tomcat

Check Tomcat service started with Systemd

Step 6 – Configure Apache Tomcat Users

In this step, we will configure the users for Apache Tomcat. Tomcat is installed, and it’s running by default on port 8080, we can access it with a web browser, but we can not access the site-manager dashboard yet. To enable and configure Tomcat users, edit the file ‘tomcat-users.xml’.

Go to the tomcat configuration directory and edit the tomcat-users.xml file with vim.

cd /opt/tomcat/conf/
vim tomcat-users.xml

Create a new line under line 43 and paste configuration below:

Save the file and exit vim.

Next, go to the manager directory and edit the context.xml file.

cd /opt/tomcat/webapps/manager/META-INF/
vim context.xml

Comment out line 19 and 20.

<Context antiResourceLocking=”false” privileged=”true” >
<!–  <Valve className=”org.apache.catalina.valves.RemoteAddrValve”
allow=”127\.\d+\.\d+\.\d+|::1|0:0:0:0:0:0:0:1″ /> –>
</Context>

Save the file and exit vim.

Go to the host-manager directory and edit the context.xml file again.

cd /opt/tomcat/webapps/host-manager/META-INF/
vim context.xml

Comment out again line 19 and 20.

<Context antiResourceLocking=”false” privileged=”true” >
<!–  <Valve className=”org.apache.catalina.valves.RemoteAddrValve”
allow=”127\.\d+\.\d+\.\d+|::1|0:0:0:0:0:0:0:1″ /> –>
</Context>

Save the file and exit, then restart tomcat.

systemctl restart tomcat

Step 7 – Configure Firewalld

In CentOS 7, we have a default firewall tool named firewalld. It replaces the iptables interface and connects to the Netfilter kernel code.

In this step, we will start the firewalld service and open port 8080 so we can access the Apache Tomcat server from the outside of the network.

Start the firewalld service and add it to start at boot time with the systemctl command.

systemctl start firewalld
systemctl enable firewalld

Next, add the apache tomcat port 8080 to the firewall with the firewall-cmd command, and reload the firewalld service.

firewall-cmd –zone=public –permanent –add-port=8080/tcp
firewall-cmd –reload

Check that all the services are available in the firewall and check that the Apache Tomcat port 8080 is open.

firewall-cmd –list-ports
firewall-cmd –list-services

Apache Tomcat port 8080 is accessible from outside of the network, and the ssh port is open by default as well.

Start Apache Tomcat Service with Systemd

Step 8 – Testing

Open your web browser and type in your server IP with port 8080. You will see the Apache Tomcat default page.

http://192.168.1.120:8080

Apache Tomcat Home page

Go to the manager dashboard with URL below:

http://192.168.1.120:8080/manager/html

Type in the admin username ‘admin‘ with password ‘mypassword‘, the configuration that we made on step 5.

Apache Tomcat Manager Dashboard

Now go to the host-manager dashboard with URL below:

http://192.168.1.120:8080/host-manager/html

Enter the admin user and password that you set in step 5, you will see the Tomcat Virtual host Manager.

Apache Tomcat Virtual Host Manager Dashboard

Apache Tomcat 8.5 has been installed on a CentOS 7 Server.

from:https://www.howtoforge.com/tutorial/how-to-install-tomcat-on-centos/

JVM 堆内存和非堆内存

堆和非堆内存

按照官方的说法:“Java 虚拟机具有一个堆(Heap),堆是运行时数据区域,所有类实例和数组的内存均从此处分配。堆是在 Java 虚拟机启动时创建的。”“在JVM中堆之外的内存称为非堆内存(Non-heap memory)”。

JVM主要管理两种类型的内存:堆和非堆。

Heap memory Code Cache
Eden Space
Survivor Space
Tenured Gen
non-heap memory Perm Gen
native heap?(I guess)

堆内存

Java 虚拟机具有一个堆,堆是运行时数据区域,所有类实例和数组的内存均从此处分配。堆是在 Java 虚拟机启动时创建的。对象的堆内存由称为垃圾回收器的自动内存管理系统回收。

堆的大小可以固定,也可以扩大和缩小。堆的内存不需要是连续空间。

非堆内存

Java 虚拟机管理堆之外的内存(称为非堆内存)。

Java 虚拟机具有一个由所有线程共享的方法区。方法区属于非堆内存。它存储每个类结构,如运行时常数池、字段和方法数据,以及方法和构造方法的代码。它是在 Java 虚拟机启动时创建的。

方法区在逻辑上属于堆,但 Java 虚拟机实现可以选择不对其进行回收或压缩。与堆类似,方法区的大小可以固定,也可以扩大和缩小。方法区的内存不需要是连续空间。

除了方法区外,Java 虚拟机实现可能需要用于内部处理或优化的内存,这种内存也是非堆内存。例如,JIT 编译器需要内存来存储从 Java 虚拟机代码转换而来的本机代码,从而获得高性能。

几个基本概念

PermGen space:全称是Permanent Generation space,即永久代。就是说是永久保存的区域,用于存放Class和Meta信息,Class在被Load的时候被放入该区域,GC(Garbage Collection)应该不会对PermGen space进行清理,所以如果你的APP会LOAD很多CLASS的话,就很可能出现PermGen space错误。

Heap space:存放Instance。

Java Heap分为3个区,Young即新生代,Old即老生代和Permanent。

Young保存刚实例化的对象。当该区被填满时,GC会将对象移到Old区。Permanent区则负责保存反射对象。

堆内存分配

  • JVM初始分配的堆内存由-Xms指定,默认是物理内存的1/64;
  • JVM最大分配的堆内存由-Xmx指定,默认是物理内存的1/4。
  • 默认空余堆内存小于40%时,JVM就会增大堆直到-Xmx的最大限制;
  • 空余堆内存大于70%时,JVM会减少堆直到-Xms的最小限制。
  • 因此服务器一般设置-Xms、-Xmx 相等以避免在每次GC 后调整堆的大小。
  • 说明:如果-Xmx 不指定或者指定偏小,应用可能会导致java.lang.OutOfMemory错误,此错误来自JVM,不是Throwable的,无法用try…catch捕捉。

非堆内存分配

  • JVM使用-XX:PermSize设置非堆内存初始值,默认是物理内存的1/64;
  • 由XX:MaxPermSize设置最大非堆内存的大小,默认是物理内存的1/4。
  1. 还有一说:MaxPermSize缺省值和-server -client选项相关,-server选项下默认MaxPermSize为64m,-client选项下默认MaxPermSize为32m。这个我没有实验。
  • XX:MaxPermSize设置过小会导致java.lang.OutOfMemoryError: PermGen space 就是内存益出。
  • 为什么会内存益出:
  1. 这一部分内存用于存放Class和Meta的信息,Class在被 Load的时候被放入PermGen space区域,它和存放Instance的Heap区域不同。
  2. GC(Garbage Collection)不会在主程序运行期对PermGen space进行清理,所以如果你的APP会LOAD很多CLASS 的话,就很可能出现PermGen space错误。
  • 这种错误常见在web服务器对JSP进行pre compile的时候。

JVM内存限制(最大值)

  • 首先JVM内存限制于实际的最大物理内存,假设物理内存无限大的话,JVM内存的最大值跟操作系统有很大的关系。简单的说就32位处理器虽然可控内存空间有4GB,但是具体的操作系统会给一个限制,这个限制一般是2GB-3GB(一般来说Windows系统下为1.5G-2G,Linux系统下为2G-3G),而64bit以上的处理器就不会有限制了。
  • 为什么有的机器我将-Xmx和-XX:MaxPermSize都设置为512M之后Eclipse可以启动,而有些机器无法启动?

通过上面对JVM内存管理的介绍我们已经了解到JVM内存包含两种:堆内存和非堆内存,另外JVM最大内存首先取决于实际的物理内存和操作系统。所以说设置VM参数导致程序无法启动主要有以下几种原因:

  1. 参数中-Xms的值大于-Xmx,或者-XX:PermSize的值大于-XX:MaxPermSize;
  2. -Xmx的值和-XX:MaxPermSize的总和超过了JVM内存的最大限制,比如当前操作系统最大内存限制,或者实际的物理内存等等。说到实际物理内存这里需要说明一点的是,如果你的内存是1024MB,但实际系统中用到的并不可能是1024MB,因为有一部分被硬件占用了。
  • 如果你有一个双核的CPU,也许可以尝试这个参数: -XX:+UseParallelGC 让GC可以更快的执行。(只是JDK 5里对GC新增加的参数)
  • 如果你的WEB APP下都用了大量的第三方jar,其大小超过了服务器jvm默认的大小,那么就会产生内存益出问题了。解决方法: 设置MaxPermSize大小。
  1. 增加服务器启动的JVM参数设置: -Xms128m -Xmx256m -XX:PermSize=128M -XX:MaxNewSize=256m -XX:MaxPermSize=256m
  2. 如tomcat,修改TOMCAT_HOME/bin/catalina.sh,在echo “Using CATALINA_BASE: $CATALINA_BASE”上面加入以下行:JAVA_OPTS=”-server -XX:PermSize=64M -XX:MaxPermSize=128m
  • 建议:将相同的第三方jar文件移置到tomcat/shared/lib目录下,这样可以减少jar 文档重复占用内存

JVM内存设置参数

  • 内存设置参数
设置项 说明
-Xms512m 表示JVM初始分配的堆内存大小为512m(JVM Heap(堆内存)最小尺寸,初始分配)
-Xmx1024m JVM最大允许分配的堆内存大小为1024m,按需分配(JVM Heap(堆内存)最大允许的尺寸,按需分配)
-XX:PermSize=512M JVM初始分配的非堆内存
-XX:MaxPermSize=1024M JVM最大允许分配的非堆内存,按需分配
-XX:NewSize/-XX:MaxNewSize 定义YOUNG段的尺寸,NewSize为JVM启动时YOUNG的内存大小;
MaxNewSize为最大可占用的YOUNG内存大小。
-XX:SurvivorRatio 设置YOUNG代中Survivor空间和Eden空间的比例
  • 说明:
  1. 如果-Xmx不指定或者指定偏小,应用可能会导致java.lang.OutOfMemory错误,此错误来自JVM不是Throwable的,无法用try…catch捕捉。
  2. PermSize和MaxPermSize指明虚拟机为java永久生成对象(Permanate generation)如,class对象、方法对象这些可反射(reflective)对象分配内存限制,这些内存不包括在Heap(堆内存)区之中。
  3. -XX:MaxPermSize分配过小会导致:java.lang.OutOfMemoryError: PermGen space。
  4. MaxPermSize缺省值和-server -client选项相关:-server选项下默认MaxPermSize为64m、-client选项下默认MaxPermSize为32m。
  • 申请一块内存的过程
  1. JVM会试图为相关Java对象在Eden中初始化一块内存区域
  2. 当Eden空间足够时,内存申请结束。否则到下一步
  3. JVM试图释放在Eden中所有不活跃的对象(这属于1或更高级的垃圾回收);释放后若Eden空间仍然不足以放入新对象,则试图将部分Eden中活跃对象放入Survivor区/OLD区
  4. Survivor区被用来作为Eden及OLD的中间交换区域,当OLD区空间足够时,Survivor区的对象会被移到Old区,否则会被保留在Survivor区
  5. 当OLD区空间不够时,JVM会在OLD区进行完全的垃圾收集(0级)
  6. 完全垃圾收集后,若Survivor及OLD区仍然无法存放从Eden复制过来的部分对象,导致JVM无法在Eden区为新对象创建内存区域,则出现”out of memory错误”

resin服务器典型的响应时间优先型的jvm配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
-Xmx2000M -Xms2000M -Xmn500M
-XX:PermSize=250M -XX:MaxPermSize=250M
-Xss256K
-XX:+DisableExplicitGC
-XX:SurvivorRatio=1
-XX:+UseConcMarkSweepGC
-XX:+UseParNewGC
-XX:+CMSParallelRemarkEnabled
-XX:+UseCMSCompactAtFullCollection
-XX:CMSFullGCsBeforeCompaction=0
-XX:+CMSClassUnloadingEnabled
-XX:LargePageSizeInBytes=128M
-XX:+UseFastAccessorMethods
-XX:+UseCMSInitiatingOccupancyOnly
-XX:CMSInitiatingOccupancyFraction=60
-XX:SoftRefLRUPolicyMSPerMB=0
-XX:+PrintClassHistogram
-XX:+PrintGCDetails
-XX:+PrintGCTimeStamps
-XX:+PrintHeapAtGC
-Xloggc:log/gc.log

内存回收算法

Java中有四种不同的回收算法,对应的启动参数为:

1
2
3
4
–XX:+UseSerialGC
–XX:+UseParallelGC
–XX:+UseParallelOldGC
–XX:+UseConcMarkSweepGC

Serial Collector

大部分平台或者强制 java -client 默认会使用这种。

young generation算法 = serial

old generation算法 = serial (mark-sweep-compact)

这种方法的缺点很明显, stop-the-world, 速度慢。服务器应用不推荐使用。

Parallel Collector

在linux x64上默认是这种,其他平台要加 java -server 参数才会默认选用这种。

young = parallel,多个thread同时copy

old = mark-sweep-compact = 1

优点:新生代回收更快。因为系统大部分时间做的gc都是新生代的,这样提高了throughput(cpu用于非gc时间)

缺点:当运行在8G/16G server上old generation live object太多时候pause time过长

Parallel Compact Collector (ParallelOld)

young = parallel = 2

old = parallel,分成多个独立的单元,如果单元中live object少则回收,多则跳过

优点:old old generation上性能较 parallel 方式有提高

缺点:大部分server系统old generation内存占用会达到60%-80%, 没有那么多理想的单元live object很少方便迅速回收,同时compact方面开销比起parallel并没明显减少。

Concurrent Mark-Sweep(CMS) Collector

young generation = parallel collector = 2

old = cms

同时不做 compact 操作。

优点:pause time会降低, pause敏感但CPU有空闲的场景需要建议使用策略4.

缺点:cpu占用过多,cpu密集型服务器不适合。另外碎片太多,每个object的存储都要通过链表连续跳n个地方,空间浪费问题也会增大。

内存监控方法

  • jmap -heap 查看java 堆(heap)使用情况
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
jmap -heap pid
 
using thread-local object allocation.
 
Parallel GC with 4 thread(s)   #GC 方式
 
Heap Configuration:  #堆内存初始化配置
 
MinHeapFreeRatio=40  #对应jvm启动参数-XX:MinHeapFreeRatio设置JVM堆最小空闲比率(default 40)
MaxHeapFreeRatio=70  #对应jvm启动参数 -XX:MaxHeapFreeRatio设置JVM堆最大空闲比率(default 70)
MaxHeapSize=512.0MB  #对应jvm启动参数-XX:MaxHeapSize=设置JVM堆的最大大小
NewSize  = 1.0MB     #对应jvm启动参数-XX:NewSize=设置JVM堆的‘新生代’的默认大小
MaxNewSize =4095MB   #对应jvm启动参数-XX:MaxNewSize=设置JVM堆的‘新生代’的最大大小
OldSize  = 4.0MB     #对应jvm启动参数-XX:OldSize=<value>:设置JVM堆的‘老生代’的大小
NewRatio  = 8        #对应jvm启动参数-XX:NewRatio=:‘新生代’和‘老生代’的大小比率
SurvivorRatio = 8    #对应jvm启动参数-XX:SurvivorRatio=设置年轻代中Eden区与Survivor区的大小比值
PermSize= 16.0MB     #对应jvm启动参数-XX:PermSize=<value>:设置JVM堆的‘永生代’的初始大小
MaxPermSize=64.0MB   #对应jvm启动参数-XX:MaxPermSize=<value>:设置JVM堆的‘永生代’的最大大小
 
Heap Usage:          #堆内存分步
 
PS Young Generation
 
Eden Space:         #Eden区内存分布
 
capacity = 20381696 (19.4375MB)             #Eden区总容量
used     = 20370032 (19.426376342773438MB)  #Eden区已使用
free     = 11664 (0.0111236572265625MB)     #Eden区剩余容量
99.94277218147106% used                     #Eden区使用比率
 
From Space:        #其中一个Survivor区的内存分布
 
capacity = 8519680 (8.125MB)
used     = 32768 (0.03125MB)
free     = 8486912 (8.09375MB)
0.38461538461538464% used
 
To Space:          #另一个Survivor区的内存分布
 
capacity = 9306112 (8.875MB)
used     = 0 (0.0MB)
free     = 9306112 (8.875MB)
0.0% used
 
PS Old Generation  #当前的Old区内存分布
 
capacity = 366280704 (349.3125MB)
used     = 322179848 (307.25464630126953MB)
free     = 44100856 (42.05785369873047MB)
87.95982001825573% used
 
PS Perm Generation #当前的 “永生代” 内存分布
 
capacity = 32243712 (30.75MB)
used     = 28918584 (27.57891082763672MB)
free     = 3325128 (3.1710891723632812MB)
89.68751488662348% used
  • JVM内存监控工具
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<%@ page import="java.lang.management.*" %>
<%@ page import="java.util.*" %>
<html>
<head>
  <title>JVM Memory Monitor</title>
</head>
<body>
<table border="0" width="100%">
    <tr><td colspan="2" align="center"><h3>Memory MXBean</h3></td></tr>
    <tr><td width="200">Heap Memory Usage</td><td><%=ManagementFactory.getMemoryMXBean().getHeapMemoryUsage()%></td></tr>
    <tr><td>Non-Heap Memory Usage</td><td><%=ManagementFactory.getMemoryMXBean().getNonHeapMemoryUsage()%></td></tr>
    <tr><td colspan="2"> </td></tr>
    <tr><td colspan="2" align="center"><h3>Memory Pool MXBeans</h3></td></tr>
<%
        Iterator iter = ManagementFactory.getMemoryPoolMXBeans().iterator();
        while (iter.hasNext()) {
            MemoryPoolMXBean item = (MemoryPoolMXBean) iter.next();
%>
<tr><td colspan="2">
    <table border="0" width="100%" style="border: 1px #98AAB1 solid;">
        <tr><td colspan="2" align="center"><b><%= item.getName() %></b></td></tr>
        <tr><td width="200">Type</td><td><%= item.getType() %></td></tr>
        <tr><td>Usage</td><td><%= item.getUsage() %></td></tr>
        <tr><td>Peak Usage</td><td><%= item.getPeakUsage() %></td></tr>
        <tr><td>Collection Usage</td><td><%= item.getCollectionUsage() %></td></tr>
    </table>
</td></tr>
<tr><td colspan="2"> </td></tr>
<%} %>
</table>
</body>
</html>

from:http://www.importnew.com/27645.html

Tomcat服务不能启动Jacob的问题

Tomcat作为服务运行时,JACOB调用Excel失败解决方案 – – ITeye博客

问题表现:

1,当将Tomcat以命令行的方式运行时,JACOB可以正常调用Excel。

2,当将Tomcat以Windows服务的方式启动时,会导致调用不成功,表现就是程序卡在调用那一步。

3,如果仔细查看Tomcat日志会发现以下错误:

com.jacob.com.ComFailException: Invoke of: OpenSource: Microsoft Office Excel Description: Microsoft Office Excel

• 文件名称或路径不存在。
• 文件正被其他程序使用。
• 您正要保存的工作簿与当前打开的工作簿同名。

如果你遇到了以上问题,恭喜你又被微软的垃^圾软件给坑了。

解决方案:

1,如果是windows 2008 操作系统

请参考这2篇文章:

http://stackoverflow.com/questions/16731037/excel-cant-read-file-written-by-java-process-when-running-as-windows-service

http://bytes.com/topic/c-sharp/answers/819740-c-service-excel-application-workbooks-open-fails-when-called-service#post3514712

如果打不开,请自行翻^^墙.或直接看下边介绍:

如果你是64位操作系统

请在C:\Windows\SysWOW64\config\systemprofile\目录下新建一个Desktop的文件夹

如果是32位操作系统

请在C:\Windows\System32\config\systemprofile\目录下新建一个Desktop的文件夹

2,如果你经过以上步骤还是不行,握个手吧,我试了之后也还是不行,请继续往下看

3,请参考这2篇文章:

http://stackoverflow.com/questions/3658936/office-2007-is-unable-to-open-files-when-called-through-jacob-from-a-service

http://bytes.com/topic/c-sharp/answers/819740-c-service-excel-application-workbooks-open-fails-when-called-service#post3466746

如果打不开,请自行翻^^墙.或直接看下边介绍:

4,首先打开任务管理器,结束Excel.exe*32的进程,

5,停止你的tomcat服务,

6,运行”dcomcnfg”

7,在新打开的窗口里,依次展开:“控制台根节点”–>“组件服务”–>”计算机”–>“我的电脑”–>”DCOM配置”,

8,在里面找到一个名为”Microsoft Excel Application”的节点

9,右键单击该节点,选属性 ,切换到标识选项卡

改为“交互式用户”,最后点击确定完成

如果在第8步里,你没有找到”Microsoft Excel Application”的节点,恭喜你,又被微软这个垃圾公司给坑了,据说是微软忘了在windows2008 64位的dcom配置里加入32位程序,诅****咒微软1000000遍。

请参考:

http://blogs.technet.com/b/the_microsoft_excel_support_team_blog/archive/2012/11/12/microsoft-excel-does-not-appear-in-dcom-configuration-snap-in.aspx

http://social.msdn.microsoft.com/Forums/office/en-US/cba17567-6371-4a66-a33a-5b36093864d2/dcom-component-services-missing-help?forum=worddev

不想看的直接看下面介绍:

10,关闭刚才打开的dcom配置的窗口

11,在命令行模式下,切换到

C:\WINDOWS\SysWOW64这个目录

11,输入 mmc comexp.msc /32 这个命令

12,此时会再次弹出刚才的那个dcom窗口,重复7~9的步骤

最后再次以服务的方式启动Tomcat,运行你的程序再行测试,应该就可以了。

from:http://wowtianwen.iteye.com/blog/1952913

Spring

1. Spring框架的作用

  • 轻量:Spring是轻量级的,基本的版本大小为2MB
  • 控制反转:Spring通过控制反转实现了松散耦合,对象们给出它们的依赖,而不是创建或查找依赖的对象们。
  • 面向切面的编程AOP:Spring支持面向切面的编程,并且把应用业务逻辑和系统服务分开。
  • 容器:Spring包含并管理应用中对象的生命周期和配置
  • MVC框架: Spring-MVC
  • 事务管理:Spring提供一个持续的事务管理接口,可以扩展到上至本地事务下至全局事务JTA
  • 异常处理:Spring提供方便的API把具体技术相关的异常

2. Spring的组成

Spring由7个模块组成:

  • Spring Core: 核心容器提供 Spring 框架的基本功能。核心容器的主要组件是BeanFactory,它是工厂模式的实现。BeanFactory 使用控制反转 (IOC) 模式将应用程序的配置和依赖性规范与实际的应用程序代码分开。
  • Spring 上下文:Spring 上下文是一个配置文件,向 Spring 框架提供上下文信息。Spring 上下文包括企业服务,例如 JNDI、EJB、电子邮件、国际化、校验和调度功能。
  • Spring AOP:通过配置管理特性,Spring AOP 模块直接将面向方面的编程功能集成到了 Spring 框架中。所以,可以很容易地使 Spring 框架管理的任何对象支持 AOP。Spring AOP 模块为基于 Spring 的应用程序中的对象提供了事务管理服务。通过使用 Spring AOP,不用依赖 EJB 组件,就可以将声明性事务管理集成到应用程序中。
  • Spring DAO:JDBC DAO 抽象层提供了有意义的异常层次结构,可用该结构来管理异常处理和不同数据库供应商抛出的错误消息。异常层次结构简化了错误处理,并且极大地降低了需要编写的异常代码数量(例如打开和关闭连接)。Spring DAO 的面向 JDBC 的异常遵从通用的 DAO 异常层次结构。
  • Spring ORM:Spring 框架插入了若干个 ORM 框架,从而提供了 ORM 的对象关系工具,其中包括 JDO、Hibernate 和 iBatis SQL Map。所有这些都遵从 Spring 的通用事务和 DAO 异常层次结构。
  • Spring Web 模块:Web 上下文模块建立在应用程序上下文模块之上,为基于 Web 的应用程序提供了上下文。所以,Spring 框架支持与 Jakarta Struts 的集成。Web 模块还简化了处理多部分请求以及将请求参数绑定到域对象的工作。
  • Spring MVC 框架:MVC 框架是一个全功能的构建 Web 应用程序的 MVC 实现。通过策略接口,MVC 框架变成为高度可配置的,MVC 容纳了大量视图技术,其中包括 JSP、Velocity、Tiles、iText 和 POI。

3. Spring容器

Sping的容器可以分为两种类型

  1. BeanFactory:(org.springframework.beans.factory.BeanFactory接口定义)是最简答的容器,提供了基本的DI支持。最常用的BeanFactory实现就是XmlBeanFactory类,它根据XML文件中的定义加载beans,该容器从XML文件读取配置元数据并用它去创建一个完全配置的系统或应用。
  2. ApplicationContext应用上下文:(org.springframework.context.ApplicationContext)基于BeanFactory之上构建,并提供面向应用的服务。

4. ApplicationContext通常的实现

  • ClassPathXmlApplicationContext:从类路径下的XML配置文件中加载上下文定义,把应用上下文定义文件当做类资源。
  • FileSystemXmlApplicationContext:读取文件系统下的XML配置文件并加载上下文定义。
  • XmlWebApplicationContext:读取Web应用下的XML配置文件并装载上下文定义。
1
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");

5. IOC & DI

Inversion of Control, 一般分为两种类型:依赖注入DI(Dependency Injection)和依赖查找(Dependency Lookup).依赖注入应用比较广泛。

Spring IOC负责创建对象,管理对象(DI),装配对象,配置对象,并且管理这些对象的整个生命周期。

优点:把应用的代码量降到最低。容器测试,最小的代价和最小的侵入性使松散耦合得以实现。IOC容器支持加载服务时的饿汉式初始化和懒加载。
DI依赖注入是IOC的一个方面,是个通常的概念,它有多种解释。这概念是说你不用床架对象,而只需要描述它如何被创建。你不在代码里直接组装你的组件和服务,但是要在配置文件里描述组件需要哪些服务,之后一个IOC容器辅助把他们组装起来。
IOC的注入方式:1. 构造器依赖注入;2. Setter方法注入。

6. 如何给spring容器提供配置元数据

  • XML配置文件
  • 基于注解的配置
  • 基于Java的配置@Configuration, @Bean

7. bean标签中的属性:

  • id
  • name
  • class
  • init-method:Bean实例化后会立刻调用的方法
  • destory-method:Bean从容器移除和销毁前,会调用的方法
  • factory-method:运行我们调用一个指定的静态方法,从而代替构造方法来创建一个类的实例。
  • scope:Bean的作用域,包括singleton(默认),prototype(每次调用都创建一个实例), request,session, global-session(注意spring中的单例bean不是线程安全的)
  • autowired:自动装配 byName, byType, constructor, autodetect(首先阐释使用constructor自动装配,如果没有发现与构造器相匹配的Bean时,Spring将尝试使用byType自动装配)

8. beans标签中相关属性

default-init-method
default-destory-method
default-autowire:默认为none,应用于Spring配置文件中的所有Bean,注意这里不是指Spring应用上下文,因为你可以定义多个配置文件

9. Bean的生命周期

1) 创建Bean的实例(factory-method, autowireConstrutor)
2) 属性注入(autowireByName, autowireByType)
3) 初始化Bean

3.1 激活Aware方法:(invokeAwaresMethods)Spring中提供了一些Aware相关接口,比如BeanNameAware, BeanFactoryAware, ApplicationContextAware等,实现这些Aware接口的bean在被初始化之后,可以取得一些相对应的资源。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
private void invokeAwareMethods(final String beanName, final Object bean){
    if(bean instanceof Aware)
    {
        if(bean instanceof BeanNameAware){
            ((BeanNameAware) bean).setBeanName(beanName);
        }
        if(bean instanceof BeanClassLoaderAware){
            ((BeanClassLoaderAware) bean).setBeanClassLoader(getBeanClassLoader());
        }
        if(bean instanceof BeanFactoryAware){
            ((BeanFactoryAware) bean).setBeanFactory(AbstactAutowire CapableBeanFactory.this);
        }
    }
}

3.2 处理器的应用(BeanPostProcessor接口):调用客户自定义初始化方法前以及调用自定义初始化方法后分别会调用BeanPostProcessor的postProcessBeforeInitialization和postProcessAfterInitialization方法,使用户可以根据自己的业务需求进行响应的处理。

3.3 激活自定义的init方法(init-method & 自定义实现InitializingBean接口)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
protected Object initializeBean(final String beanName, final Object bean, RootBeanDefinetion mbd){
    if(System.getSecurityManager() != null){
        AccessController.doPrivileged(new PrivilegedAction<Object>(){
            @Override
            public Object run()
            {
                invokeAwareMethods(beanName,bean);
                return null;
            }
        });
    }
    else{
        //对特殊的bean处理:Aware, BeanClassLoaderAware, BeanFactoryAware
        invokeAwareMethods(beanName,bean);
    }
    Object wrappedBean = bean;
    if(mbd == null !! !mbd.isSynthetic()){
        wrappedBean = applyBeanPostProcessorsBeforeInitialization(wappedBean,beanName);
    }
    try{
        invokeInitMethods(beanName, wappedBean, mbd);
    }
    catch(Throwable ex){
        throw new BeanCreationException((mbd != null ? mbd.getResourceDescription():null),beanName,"Invocation of init method failed",ex);
    }
    if(mbd == null || !mbd.isSynthetic()){
        wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
    }
    return wappedBean;
}

4) 使用Bean。 驻留在应用的上下文中,直到该应用上下文被销毁。
5) 销毁(destory-mthod & 实现DisposableBean接口)

Or represent like this:

1. Bean的构造
2. 调用setXXX()方法设置Bean的属性
3. 调用BeanNameAware的setBeanName()
4. 调用BeanFactoryAware的setBeanFactory()方法
5. 调用BeanPostProcessor的postProcessBeforeInitialization()方法
6. 调用InitializingBean的afterPropertiesSet()方法
7. 调用自定义的初始化方法
8. 调用BeanPostProcessor类的postProcessAfterInitialization()方法
9. 调用DisposableBean的destroy()方法
10. 调用自定义的销毁方法

10. Spring中注入集合

  1. <list>允许值相同
  2. <set>不允许值相同
  3. <map><entry key=”” value=”“></map>键和值都可以为任意类型,key, key-ref, value-ref, value可以任意搭配
  4. <props><prop key=”“>XXX</prop></props>键和值都只能是String类型

11. 装配空值

1
<property name="xxx"><null/></property>

12. 自动装配(autowiring)

有助于减少甚至消除配置<property>和<constructor-arg>元素,让Spring自动识别如何装配Bean的依赖关系。<context:annotation-config/>
与之对应的是:自动检测(autodiscovery),比自动装配更近了一步,让Spring能够自动识别哪些类需要被配置成SpringBean,从而减少对<bean>元素的使用。<context:component-scan>

13. 注解

Spring容器默认禁用注解装配。最简单的开启方式<context:annotation-config/>。
Spring支持的几种不同的用于自动装配的注解:

  • Spring自带的@Autowired注解
  • JSR-330的@Inject注解
  • JSR-250的@Resource注解

14. @Autowired

@Autowired具有强契约特征,其所标注的属性或参数必须是可装配的。如果没有Bean可以装配到@Autowired所标注的属性或参数中,自动装配就会失败,抛出NoSuchBeanDefinitionException.
属性不一定非要装配,null值也是可以接受的。在这种场景下可以通过设置@Autowired的required属性为false来配置自动装配是可选的,如:

1
2
@Autowired(required=false)
private Object obj;

注意required属性可以用于@Autowired注解所使用的任意地方。但是当使用构造器装配时,只有一个构造器可以将@Autowired的required属性设置为true。其他使用@Autowired注解所标注的构造器只能将required属性设置为false。此外,当使用@Autowired标注多个构造器时,Spring就会从所有满足装配条件的构造器中选择入参最多的那个构造器。
可以使用@Qualifier明确指定要装配的Bean.如下:

1
2
3
@Autowired
@Qualifier("objName")
private Object obj;

15. 自定义的限定器

1
2
3
4
@Target({ElementType.FIELF, ElementType.PARAMETER, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @Interface SpecialQualifier{}

此时,可以通过自定义的@SpecialQualifier注解来代替@Qualifier来标注,也可以和@Autowired一起使用:

1
2
3
@Autowired
@SpecialQualifier
private Object obj;

此时,Spring会把自动装配的范围缩小到被@SpecialQualifier标注的Bean中。如果被@SpecialQualifier标注的Bean有多个,我们还可以通过自定义的另一个限定器@SpecialQualifier2来进一步缩小范围。

16. @Autowired优缺点

Spring的@Autowired注解是减少Spring XML配置的一种方式。但是它的类会映入对Spring的特定依赖(即使依赖只是一个注解)。

17. @Inject

和@Autowired注解一样,@Inject可以用来自动装配属性、方法和构造器;与@Autowired不同的是,@Inject没有required属性。因此@Inject注解所标注的依赖关系必须存在,如果不存在,则会抛出异常。

18. @Named

相对于@Autowired对应的Qualifier,@Inject所对应的是@Named注解。

1
2
3
@Inject
@Named("objName")
private Object obj;

19. SpEL表达式

语法形式在#{}中使用表达式,如:

1
<property name="count" value="#{5}"/>

20. @Value

@Value是一个新的装配注解,可以让我们使用注解装配String类型的值和基本类型的值,如int, boolean。我们可以通过@Value直接标注某个属性,方法或者方法参数,并传入一个String类型的表达式来装配属性,如:

1
2
@Value("Eruption")
private String song;

@Value可以配合SpEL表达式一起使用,譬如有些情况下需要读取properties文件中的内容,可以使用:

1
@Value("#{configProperties['ora_driver']}")

详细可以参考Spring+Mybatis多数据源配置(三)——Spring如何获取Properties文件的信息

21. 自动检测Bean

<context:component-scan>元素除了完成与<context:annotation-config>一样的工作,还允许Spring自动检测Bean和定义Bean.<context:component-scan>元素会扫描指定的包和其所有子包,如下:

1
<context:component-scan base-package="com.zzh.dao" />

22. 为自动检测标注Bean

默认情况下,查找使用构造型(stereotype)注解所标注的类,这些特殊的注解如下:
– @Component:通用的构造型注解,标志此类为Spring组件
– @Controller:标识将该类定义为SpringMVC controller
– @Repository:标识将该类定义为数据仓库
– @Service:标识将该类定义为服务
以@Component为例:

1
2
@Component
public class Guitar implements Intrument{}

这里@Component会自动注册Guitar 为Spring Bean,并设置默认的Bean的Id为guitar,首字母大写变小写。注意如果第一个和第二个字母都是大写,默认的Bean的id会有特殊处理。
也可以指定Bean的Id如:

1
2
@Component("guitarOne")
public class Guitar implements Intrument{}

23. AOP

面向切面的编程AOP,是一种编程技术,允许程序模块化横向切割关注点,或横切典型的责任划分,如日志和事务管理。

AOP的核心是切面,它将多个类的通用行为封装成可重用的模块,该模块含有一组API提供横切功能。比如,一个日志模块可以被称作日志的AOP切面。根据需求的不同,一个应用程序可以有若干切面。在SpringAOP中,切面通过带有@Aspect注解的类实现。

关注点是应用中的一个模块的行为,一个关注点可能会被定义成一个我们想实现的一个功能。

横切关注点一个关注点,此关注点是整个应用都会使用的功能,并影响整个应用,比如日志,安全和数据传输,几乎应用的每个模块都需要的功能。因此这些都属于横切关注点。

连接点代表一个应用程序的某个位置,在这个位置我们可以插入一个AOP切面,它实际上是个应用程序执行Spring AOP的位置。

切点是一个或一组连接点,通知将在这些位置执行。可以通过表达式或匹配的方式指明切入点。

引入运行我们在已存在的类中添加新的方法和属性。

24. AOP通知

通知是个在方法执行前后要做的动作,实际上是程序执行时要通过SpringAOP框架触发的代码
Spring切面可以应用五种类型的通知:
before:前置通知,在一个方法执行前被调用。@Before
after: 在方法执行之后调用的通知,无论方法执行是否成功。@After
after-returning: 仅当方法成功完成后执行的通知。@AfterReturning
after-throwing: 在方法抛出异常退出时执行的通知。@AfterThrowing
around: 在方法执行之前和之后调用的通知。@Around

25. Spring的事务类型

编程式事务管理:这意味你通过编程的方式管理事务,给你带来极大的灵活性,但是难维护。
声明式事务管理:这意味着你可以将业务代码和事务管理分离,你只需用注解和XML配置来管理事务。

26. ACID

  1. Atomic原子性:事务是由一个或多个活动所组成的一个工作单元。原子性确保事务中的所有操作全部发生或者全部不发生。
  2. Consistent一致性:一旦事务完成,系统必须确保它所建模的业务处于一致的状态
  3. Isolated隔离线:事务允许多个用户对象头的数据进行操作,每个用户的操作不会与其他用户纠缠在一起。
  4. Durable持久性:一旦事务完成,事务的结果应该持久化,这样就能从任何的系统崩溃中恢复过来。

27. JDBC事务

如果在应用程序中直接使用JDBC来进行持久化,譬如博主采用的是Mybatis,DataSourceTransactionManager会为你处理事务边界。譬如:

1
2
3
4
5
6
7
8
9
10
11
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
    destroy-method="close">
    <property name="driverClassName" value="${driver}" />
    <property name="url" value="${url}" />
    <property name="username" value="zzh" />
    <property name="password" value="zzh" />
    <property name="validationQuery" value="SELECT 1"/>
</bean>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/>
</bean>

28. JTA事务

如果你的事务需要跨多个事务资源(例如:两个或多个数据库;或者如Sping+ActiveMQ整合需要将ActiveMQ和数据库的事务整合起来),就需要使用JtaTransactionManager:

1
<bean id="jtaTransactionManager"class="org.springframework.transaction.jta.JtaTransactionManager"/>

JtaTransactionManager将事务管理的职责委托给了一个JTA的实现。JTA规定了应用程序与一个或多个数据源之间协调事务的标准API。transactionManagerName属性指明了要在JNDI上查找的JTA事务管理器。
JtaTransactionManager将事务管理的职责委托给javax.transaction.UserTransaction和javax.transaction.TransactionManager对象。通过UserTransaction.commit()方法来提交事务。类似地,如果事务失败,UserTransaction的rollback()方法将会被调用。

29. 声明式事务

尽管Spring提供了多种声明式事务的机制,但是所有的方式都依赖这五个参数来控制如何管理事务策略。因此,如果要在Spring中声明事务策略,就要理解这些参数。(@Transactional)

1. 隔离级别(isolation)

  • ISOLATION_DEFAULT: 使用底层数据库预设的隔离层级
  • ISOLATION_READ_COMMITTED: 允许事务读取其他并行的事务已经送出(Commit)的数据字段,可以防止Dirty read问题
  • ISOLATION_READ_UNCOMMITTED: 允许事务读取其他并行的事务还没送出的数据,会发生Dirty、Nonrepeatable、Phantom read等问题
  • ISOLATION_REPEATABLE_READ: 要求多次读取的数据必须相同,除非事务本身更新数据,可防止Dirty、Nonrepeatable read问题
  • ISOLATION_SERIALIZABLE: 完整的隔离层级,可防止Dirty、Nonrepeatable、Phantom read等问题,会锁定对应的数据表格,因而有效率问题

2. 传播行为(propagation)

  • PROPAGATION_REQUIRED–支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。
  • PROPAGATION_SUPPORTS–支持当前事务,如果当前没有事务,就以非事务方式执行。
  • PROPAGATION_MANDATORY–支持当前事务,如果当前没有事务,就抛出异常。
  • PROPAGATION_REQUIRES_NEW–新建事务,如果当前存在事务,把当前事务挂起。
  • PROPAGATION_NOT_SUPPORTED–以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
  • PROPAGATION_NEVER–以非事务方式执行,如果当前存在事务,则抛出异常。
  • PROPAGATION_NESTED–如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则进行与PROPAGATION_REQUIRED类似的操作。

3. 只读(read-only)

如果事务只进行读取的动作,则可以利用底层数据库在只读操作时发生的一些最佳化动作,由于这个动作利用到数据库在只读的事务操作最佳化,因而必须在事务中才有效,也就是说要搭配传播行为PROPAGATION_REQUIRED、PROPAGATION_REQUIRES_NEW、PROPAGATION_NESTED来设置。

4. 事务超时(timeout)

有的事务操作可能延续很长一段的时间,事务本身可能关联到数据表的锁定,因而长时间的事务操作会有效率上的问题,对于过长的事务操作,考虑Roll back事务并要求重新操作,而不是无限时的等待事务完成。 可以设置事务超时期间,计时是从事务开始时,所以这个设置必须搭配传播行为PROPAGATION_REQUIRED、PROPAGATION_REQUIRES_NEW、PROPAGATION_NESTED来设置。

5. 回滚规则(rollback-for, no-rollback-for)

rollback-for指事务对于那些检查型异常应当回滚而不提交;no-rollback-for指事务对于那些异常应当继续运行而不回滚。默认情况下,Spring声明事务对所有的运行时异常都进行回滚。

1
2
3
4
5
<tx:advice id="txAdvice" transaction-manager="transactionManager">
    <tx:attributes>
        <tx:method name="*" />
    </tx:attributes>
</tx:advice>

30. SpringMVC

核心架构的具体流程:

  1. 首先用户发送请求——>DispatcherServlet,前端控制器收到请求后自己不进行处理,而是委托给其他的解析器进行处理,作为统一访问点,进行全局的流程控制;
  2. DispatcherServlet——>HandlerMapping, HandlerMapping将会把请求映射为HandlerExecutionChain对象(包含一个Handler处理器(页面控制器)对象、多个HandlerInterceptor拦截器)对象,通过这种策略模式,很容易添加新的映射策略;
  3. DispatcherServlet——>HandlerAdapter,HandlerAdapter将会把处理器包装为适配器,从而支持多种类型的处理器,即适配器设计模式的应用,从而很容易支持很多类型的处理器;
  4. HandlerAdapter——>处理器功能处理方法的调用,HandlerAdapter将会根据适配的结果调用真正的处理器的功能处理方法,完成功能处理;并返回一个ModelAndView对象(包含模型数据、逻辑视图名);
  5. ModelAndView的逻辑视图名——> ViewResolver, ViewResolver将把逻辑视图名解析为具体的View,通过这种策略模式,很容易更换其他视图技术;
  6. View——>渲染,View会根据传进来的Model模型数据进行渲染,此处的Model实际是一个Map数据结构,因此很容易支持其他视图技术;
  7. 返回控制权给DispatcherServlet,由DispatcherServlet返回响应给用户,到此一个流程结束。

31. DispatcherServlet

SpringMVC的核心是DispatcherServlet,这个Servlet充当SpringMVC的前端控制器。与其他Servlet一样,DispatcherServlet必须在Web应用程序的web.xml文件中进行配置。

1
2
3
4
5
<servlet>
    <servlet-name>viewspace</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <load-on-startup>2</load-on-startup>
</servlet>

默认情况下,DispatcherServlet在加载时会从一个基于这个Servlet名字的XML文件中加载Spring应用上下文。因为servlet的名字是viewspace,所以配置文件的名称为viewspace-servlet.xml。
接下来,必须申明DispatcherServlet处理那些URL:

1
2
3
4
<servlet-mapping>
    <servlet-name>viewspace</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>

通过将DispatcherServlet映射到/,声明了它会作为默认的servlet并且会处理所有的请求,包括对静态资源的请求。
可以配置:

1
2
3
4
5
6
<mvc:resources mapping="/images/**" location="/images/"
    cache-period="31556926" />
<mvc:resources mapping="/js/**" location="/js/"
    cache-period="31556926" />
<mvc:resources mapping="/css/**" location="/css/"
    cache-period="31556926" />

处理静态资源。

32. 配置HandlerMapping

Spring自带了多个处理器映射实现:

  • BeanNameUrlHandlerMapping:根据控制器Bean的名字将控制器映射到URL。
  • ControllerBeanNameHandlerMapping:与BeanNameUrlHandlerMapping类似,根据控制器Bean的名字将控制器映射到URL。使用该处理器映射实现,Bean的名字不需要遵循URL的约定。
  • ControllerClassNameHandlerMapping:通过使用控制器的类名作为URL基础将控制器映射到URL。
  • DefaultAnnotationHandlerMapping:将请求映射给使用@RequestingMapping注解的控制器和控制器方法。
  • SimpleUrlHandlerMapping:使用定义在Spring应用上下文的熟悉集合将控制器映射到URL。
  • 使用如上这些处理器映射通常只需在Spring中配置一个Bean。如果没有找到处理器映射Bean,DisapatchServlet将创建并使用BeanNameUrlHandlerMapping和DefaultAnnotationHandlerMapping。我们一般使用基于注解的控制器类。
1
2
3
4
<mvc:annotation-driven />
<bean id="defaultAnnotationHandlerMapping"
    class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
</bean>

在构建控制器的时候,我们还需要使用注解将请求参数绑定到控制器的方法参数上进行校验以及信息转换。提供注解驱动的特性。

33. 配置HandlerAdapter

1
2
<bean id="annotationMethodHandlerAdapter"
class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping" />

34. 配置视图

在SpringMVC中大量使用了约定优于配置的开发模式。InternalResourceViewResolver就是一个面向约定的元素。它将逻辑视图名称解析为View对象,而该对象将渲染的任务委托给Web应用程序上下文中的一个模板。

1
2
3
4
5
6
7
8
<!-- 配置视图解析器,将ModelAndView及字符串解析为具体的页面 -->
<bean
    class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="viewClass"
        value="org.springframework.web.servlet.view.JstlView" />
    <property name="prefix" value="/WEB-INF/jsp/" />
    <property name="suffix" value=".jsp" />
</bean>

当DispatcherServlet要求InternalResourceViewResolver解析视图的时候,它将获取一个逻辑视图名称,添加”/WEB-INF/jsp/”前缀和”.jsp”后缀。等待的结果就是渲染输出的JSP路径。在内部,InternalResourceViewResolver接下来会将这个路径传递给View对象,View对象将请求传递给JSP.

参考文献

1. 《Sping In Action》 Craig Walls
2. 69道Spring面试题和答案
3. Sping+ActiveMQ整合