月度归档:2016年07月

从敏捷开发到敏捷运维DevOps将带来革命

你听说过DevOps一词,或者听说过敏捷运维这个运动么?人们越来越意识到传统意义上的开发行为和运维行为存在脱节现象,从而导致冲突和低效,因此DevOps应运而生。传统的工作流程中,开发和运维之间存在很多的沟通错位而造成部署上的问题,由此,DevOps理念应运而生。

【51CTO精选译文】如果你对IT管理感兴趣,尤其是对Web运维感兴趣,那么最近一定会注意到“DevOps”这一热词的出现。现在#DevOps标签频繁出现在微博客Twitter上,同时DevOps相关的技术交流聚会也在慢慢增多。

在许多方面,DevOps是一个集合性概念,指的是能够理顺开发和运维之间相互配合关系的任何事物(51CTO编辑注:在英文中,Developer指开发者,Operations指运维,所以DevOps一词本身含有开发+运维的意思)。但是DevOps背后的理念要比上述说法更深远。

什么是DevOps?

人们越来越意识到传统意义上的开发行为和运维行为存在脱节现象,从而导致冲突和低效,因此DevOps应运而生。

正如李·汤普森(Lee Thompson)和安德鲁·谢福尔(Andrew Shafer)所言,在开发和运维之间存在一面“混乱之墙”。相互冲突的动机、流程和工具导致了这面“墙”的存在。

相互冲突的动机、流程和工具导致了这面“墙”的存在
开发与运维之间的“混乱之墙”

以开发为中心的人通常认为,变化会带来回报。企业依靠他们来应对不断变化的需求。因此他们被鼓励尽可能进行变革。

而运维人员则往往视变化为敌人。企业依靠他们维持正常业务运维和实施让企业赚钱的服务。由于变化会影响稳定性和可靠性,运维业务有理由对它说不。我们已经多次听到过如下统计数字:在所有宕机事件中有80%情况是源于自杀式的改变(根据51CTO之前进行的调查,很多时候,仅仅是给系统应用补丁就会造成生产服务器无法正常重启)。

开发人员和运维人员认识世界的方法,以及各自所处的角色,存在根本性的差别。他们都认为自己的做法是正确的。的确,孤立的来看他们都是正确的。

更糟糕的是,开发和运维团队通常处于公司组织架构的不同部分,通常具有不同管理者的和竞争关系,而且通常工作在不同的地点。

开发和运维团队通常处于公司组织架构的不同部分
开发与运维通常工作在不同的地点

让混乱之墙更坚固的还包括开发和运维工具之间的错位。看一下开发者要求和日常使用的常见工具,再看一下系统管理员,你会发现两者存在很大不同,开发人员没有兴趣使用运维人员的工具,反之亦然;而且两部分工具之间也不存在重要的集成。即使在某些工具类型上有一些重叠之处,使用方式也完全不同。

开发者要求和日常使用的常见工具
开发与运维常用工具的不集成

当应用程序变动需要从开发团队推向运维团队时,混乱之墙的存在则将变得更加明显。有人将其称为一个“版本发布(Release)”,有人则称其为一次“部署(deployment)”,但有一件事情是公认的,问题可能会随之而来。下图虽然是一个抽象化场景,但是如果你经历过这一过程,一定会感觉到它的真实性。

版本发布与部署
应用程序变动从开发到运维

开发人员把一个软件版本“扔”给墙对面的运维人员。后者拿到该版本产品后开始准备将其部署。运维人员手动修改由开发者提供的部署脚本或创建自己的脚本。他们还需要修改配置文件来适应与开发环境大不相同的真实生产环境。最完美的情况是,他们重复在此前环境中已完成的工作;而糟糕的情况是,他们将引入或发现新的漏洞。

运维人员然后开始进行他们自认为正确的部署过程。由于开发和运维之间的脚本、配置、过程和环境存在差别,这一部署过程实际上也是首次被执行。当然,期间如果发生一个问题,开发人员会被要求来帮助进行排障。运维人员会说开发团队给的产品存在问题。而开发人员则会回应称该产品在他们的环境下运行良好,因此一定是运维人员在部署的过程中做错了什么。由于配置、文件存储位置和过程的不同,开发人员诊断问题也并非一件易事。

没有一个可靠的方式来把环境回滚到此前已知的正常状态。本来应该一帆风顺的部署过程最后变成一场救火行动,经过反复测试之后才让生产环境恢复到正常状态。

本来应该一帆风顺的部署过程最后变成一场救火行动
本来应该一帆风顺的部署过程最后变成一场救火行动

部署阶段已经非常明显的需要DevOps理念来解决问题,但需要DevOps的绝不仅仅是这一阶段。正如约翰·阿尔斯帕瓦(John Allspaw)所指出的那样,开发和运维之间的协作需求在部署之前就已存在,同时也会在部署之后的长时间之内继续存在。

DevOps所带来的好处

DevOps是一个非常强大的概念,因为它可以在众多不同层面上产生共鸣。

从开发或运维的一线人员的观点来看,DevOps可以让他们从众多烦恼中解脱出来。它虽然不是具有魔力的万灵药,但是如果你能够让DevOps奏效,则会节省大量时间,而且不至于打击自己的士气。显而易见,投入精力将DevOps落到实处,我们应该会更加高效、更加敏捷和减少挫败感。有些人可能会反驳称DevOps是一个遥不可及的目标,但这并非说我们不应该去尝试实现它。

DevOps会节省大量的时间
DevOps会节省大量的时间

对于企业来说,DevOps直接有助于实现两个强大战略性企业品质,“业务敏捷性”和“IT融合”。它们可能不是IT人士所担忧的事情,但是却应该获得掌握财政大权的管理者的注意。

IT融合的一个简单定义是,“企业渴望达到的一个状态,能够高效的使用信息技术来达到企业目标——通常是提高公司业绩或市场竞争力。”

通过从共同企业目标角度出发来校准开发和运维的职责和流程,DevOps有助于实现IT融合。开发和运维人员需要明白,它们仅仅是一个统一业务流程中的一部分。DevOps思想确保个体决策和行为应力求支持和改进这个统一的业务流程,无论你是来自哪一个组织架构。

DevOps有助于实现IT融合
DevOps有助于实现IT融合

业务敏捷性的一个简单定义是,“一个机构以高效、经济的方式迅速适应市场和环境变化的能力。”

当然对于开发人员来说,“敏捷”有专门的含义(参考51CTO开发频道的专题:初探敏捷开发),但目标是非常类似的。敏捷开发方法旨在保持软件开发工作与客户/公司的目标同步,尽管需求不断变化,也可以产生高品质软件。对于多数机构来说,迭代项目管理方法Scrum是敏捷的代名词。

Scrum
Scrum

业务敏捷性承诺,在企业权益集团作出决策和开发者进行响应之间能够紧密互动和快速反馈。看一下一家运转良好的敏捷开发团体的产品,你会看到一个与业务需求保持一致的稳定持续改进。

但是,当你从企业角度回顾一下整个开发-运维周期,敏捷方法的相关优势通常会变得非常模糊。混乱之墙导致了应用程序生命周期的分裂。开发和运维分别按照不同的节奏进行。实际上,产品部署之间的长期间隔使得一个团体的敏捷工作变成了它一直试图避免的瀑布生命周期。当存在混乱之墙时,无论开发团体有多么敏捷,改变企业缓慢和迟钝的特点是极其困难的。

敏捷开发与企业结构的不同步
敏捷的开发与瀑布式企业结构的步调不同

DevOps使得敏捷开发的优势可以体现在机构层面上。通过考虑到快速、反应灵敏但稳定的业务运维,使其能够与开发过程的创新保持同步,DevOps可以做到这一点。

如果你希望在自己的组织内建立一个DevOps项目,务必牢记“IT融合” 和“业务敏捷性”。

如何将DevOps落到实处?

和多数新出现的话题一样,发现问题的共性特点要比找到解决方案容易的多。

要想实现DevOps相关解决方案,以下三方面需要关注:

1、评价和鼓励改变文化

改变文化和激励系统从来不是一件易事。但是,如果你不改变企业文化,兑现DevOps的承诺将非常困难。考察一个企业的主导文化时,你需要紧密关注如何评价和判断企业业绩。评价的内容将影响和刺激行为的发生。开发-运维生命周期中的所有当事方需要明白,在更大的企业流程中自己只是其中一部分。个体和团队的成功都要放在整个开发-运维生命周期内来进行评价。对于许多机构来说,这是一个转变,不再是孤立的来进行业绩评价,每一个团队不再是基于自己的团队来评价和判断业绩好坏。

2、统一标准化的流程

这是DevOps的一个重要主题,整个开发-运维生命周期必须被看作一个端对端过流程。流程的不同阶段可以采取不同的方法,只要这些流程可以被组合到一起创建一个统一的流程。与评价和激励的问题相似的是,实现这个统一的流程时每个组织可能会有略微不同的需求。

3、统一的工具

这是大多数DevOps讨论一直在关注的领域。这一点不令人吃惊,因为当技术专家在考虑解决一个问题时,第一反应往往就是直接跳转到工具讨论上。如果你关注Puppet、Chef或ControlTier等工具社区,那么你可能已经意识到人们对在开发和运维工具之间建立桥梁的重大关注。“基础设施即代码(Infrastructure as code)”、“模型驱动自动化(model driven automation)”和“持续性部署(continuous deployment)”都是可以划归DevOps旗下的概念。

关于把DevOps变为现实需要哪些类型的工具,杰克·索罗夫曼(Jake Sorofman)提出如下建议:

一个版本控制软件库

它可以确保所有系统产品在整个版本发布生命周期中被很好的定义,且能够实现一致性共享,同时保持最新信息。开发和QA机构能够从中取得相同平台版本,生产机构部署已经被QA机构验证过的相同版本。

深层模型系统

它的版本系统清晰的描述了软件系统相关的所有组件、策略和依赖性,从而可以简单的根据需要复制一个系统或在无冲突的情况下引入变化。

人工任务的自动化

在依赖关系发现、系统构造、配置、更新和回滚等过程中,减少人工干涉。自动操作变为高速、无冲突和大规模系统管理的命令和控制基础。

在从开发到运维的生命周期中存在许多不同的工具。工具选择和执行决策需要根据它们对端到端生命周期的影响来决定。

关于DevOps的澄清

现在某些系统管理员正在试图把自己的岗位名称改为“DevOps”。但是,DevOps不应该是一个单一的位置或职称。把DevOps变成一个新职位名称或特定角色是一件非常危险的事情。例如这会导致以下错误端点:你是一个DBA?或者是一个安全专家?那么不用担心DevOps,因为那是DevOps团队的问题。

设想一下,你不会说“我需要招聘一个Agile”或“我需要招聘一个Scrum”或“我需要招聘一个ITIL”,而只是会说需要招聘了解这些概念或方法的开发人员、项目经理、测试人员或系统管理员。DevOps也是同样道理。

与DevOps具有相同理念的术语很多,例如敏捷运维(Agile Operations)、敏捷基础设施(Agile Infrastructure)和Dev2Ops。还有很多人虽然没有提及“DevOps”,但却在遵循着类似的理念。

原文:http://dev2ops.org/blog/2010/2/22/what-is-devops.html

Spring事务管理中@Transactional的参数配置

来源:http://blog.csdn.net/zsm653983/article/details/8103466

Spring作为低侵入的Java EE框架之一,能够很好地与其他框架进行整合,其中Spring与Hibernate的整合实现的事务管理是常用的一种功能。  所谓事务,就必须具备ACID特性,即原子性、一致性、隔离性和持久性

注意@Transactional 注解及其支持类所提供的功能最低要求使用Java 5(Tiger)。

除了基于XML文件的声明式事务配置外,你也可以采用基于注解式的事务配置方法。直接在Java源代码中声明事务语义的做法让事务声明和将受其影响的代码距离更近了,而且一般来说不会有不恰当的耦合的风险,因为,使用事务性的代码几乎总是被部署在事务环境中。

下面的例子很好地演示了 @Transactional 注解的易用性,随后解释其中的细节。先看看其中的类定义:

  1. <!-- the service class that we want to make transactional -->
  2. @Transactional
  3. public class DefaultFooService implements FooService {
  4.        Foo getFoo(String fooName);
  5.        Foo getFoo(String fooName, String barName);
  6.        void insertFoo(Foo foo);
  7.        void updateFoo(Foo foo);
  8. }

当上述的POJO定义在Spring IoC容器里时,上述bean实例仅仅通过 行xml配置就可以使它具有事务性的。如下:

  1. <!-- from the file 'context.xml' -->
  2. <?xml version="1.0" encoding="UTF-8"?>
  3. <beans xmlns="http://www.springframework.org/schema/beans"
  4.        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  5.        xmlns:aop="http://www.springframework.org/schema/aop"
  6.        xmlns:tx="http://www.springframework.org/schema/tx"
  7.        xsi:schemaLocation="
  8.        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
  9.        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd
  10.        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd">
  11.   <!-- this is the service object that we want to make transactional -->
  12.   <bean id="fooService" class="x.y.service.DefaultFooService"/>
  13.   <!-- enable the configuration of transactional behavior based on annotations -->
  14.   <tx:annotation-driven transaction-manager="txManager"/>
  15.   <!-- a PlatformTransactionManager is still required -->
  16.   <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
  17.     <!-- (this dependency is defined somewhere else) -->
  18.     <property name="dataSource" ref="dataSource"/>
  19.   </bean>
  20.   <!-- other <bean/> definitions here -->
  21. </beans>

提示

实际上,如果你用 'transactionManager' 来定义 PlatformTransactionManager bean的名字的话,你就可以忽略<tx:annotation-driven/> 标签里的 'transaction-manager' 属性。 如果 PlatformTransactionManager bean你要通过其它名称来注入的话,你必须用 'transaction-manager' 属性来指定它,如上所示。

@Transactional 注解可以被应用于接口定义和接口方法、类定义和类的 public 方法上。然而,请注意仅仅@Transactional 注解的出现不足于开启事务行为,它仅仅 是一种元数据,能够被可以识别 @Transactional 注解和上述的配置适当的具有事务行为的beans所使用。上面的例子中,其实正是 <tx:annotation-driven/>元素的出现 开启 了事务行为。

Spring团队的建议是你在具体的类(或类的方法)上使用 @Transactional 注解,而不要使用在类所要实现的任何接口上。你当然可以在接口上使用 @Transactional 注解,但是这将只能当你设置了基于接口的代理时它才生效。因为注解是 不能继承 的,这就意味着如果你正在使用基于类的代理时,那么事务的设置将不能被基于类的代理所识别,而且对象也将不会被事务代理所包装(将被确认为严重的)。因此,请接受Spring团队的建议并且在具体的类上使用 @Transactional 注解。

注意

当使用 @Transactional 风格的进行声明式事务定义时,你可以通过 <tx:annotation-driven/> 元素的 "proxy-target-class" 属性值来控制是基于接口的还是基于类的代理被创建。如果 "proxy-target-class" 属值被设置为 "true",那么基于类的代理将起作用(这时需要CGLIB库cglib.jar在CLASSPATH中)。如果 "proxy-target-class" 属值被设置为 "false" 或者这个属性被省略,那么标准的JDK基于接口的代理将起作用。

在多数情形下,方法的事务设置将被优先执行。在下列情况下,例如: DefaultFooService 类被注解为只读事务,但是,这个类中的 updateFoo(Foo) 方法的 @Transactional 注解的事务设置将优先于类级别注解的事务设置。

[java] view plain copy

print?

  1. @Transactional(readOnly = true)
  2. public class DefaultFooService implements FooService {
  3.     public Foo getFoo(String fooName) {
  4.         // do something
  5.     }
  6.     // these settings have precedence for this method
  7.     @Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)
  8.     public void updateFoo(Foo foo) {
  9.         // do something
  10.     }
  11. }

9.5.6.1. @Transactional 有关的设置

@Transactional 注解是用来指定接口、类或方法必须拥有事务语义的元数据。 如:“当一个方法开始调用时就开启一个新的只读事务,并停止掉任何现存的事务”。 默认的 @Transactional 设置如下:

  • 事务传播设置是 PROPAGATION_REQUIRED
  • 事务隔离级别是 ISOLATION_DEFAULT
  • 事务是 读/写
  • 事务超时默认是依赖于事务系统的,或者事务超时没有被支持。
  • 任何 RuntimeException 将触发事务回滚,但是任何 checked Exception 将不触发事务回滚

这些默认的设置当然也是可以被改变的。 @Transactional 注解的各种属性设置总结如下:

 

表 9.2. @Transactional 注解的属性

属性类型描述
传播性枚举型:Propagation可选的传播性设置
隔离性枚举型:Isolation可选的隔离性级别(默认值:ISOLATION_DEFAULT
只读性布尔型读写型事务 vs. 只读型事务
超时int型(以秒为单位)事务超时
回滚异常类(rollbackFor)一组 Class 类的实例,必须是Throwable 的子类一组异常类,遇到时 必须 进行回滚。默认情况下checked exceptions不进行回滚,仅unchecked exceptions(即RuntimeException的子类)才进行事务回滚。
回滚异常类名(rollbackForClassname)一组 Class 类的名字,必须是Throwable的子类一组异常类名,遇到时 必须 进行回滚
不回滚异常类(noRollbackFor)一组 Class 类的实例,必须是Throwable 的子类一组异常类,遇到时 必须不 回滚。
不回滚异常类名(noRollbackForClassname)一组 Class 类的名字,必须是Throwable 的子类一组异常类,遇到时 必须不 回滚

9.5.7. 插入事务操作

考虑这样的情况,你有一个类的实例,而且希望 同时插入事务性通知(advice)和一些简单的剖析(profiling)通知。那么,在<tx:annotation-driven/>环境中该怎么做?

我们调用 updateFoo(Foo) 方法时希望这样:

  • 配置的剖析切面(profiling aspect)开始启动,
  • 然后进入事务通知(根据配置创建一个新事务或加入一个已经存在的事务),
  • 然后执行原始对象的方法,
  • 然后事务提交(我们假定这里一切正常),
  • 最后剖析切面报告整个事务方法执行过程花了多少时间。

注意

这章不是专门讲述AOP的任何细节(除了应用于事务方面的之外)。请参考 第 6 章 使用Spring进行面向切面编程(AOP) 章以获得对各种AOP配置及其一般概念的详细叙述。

这里有一份简单的剖析切面(profiling aspect)的代码。(注意,通知的顺序是由 Ordered 接口来控制的。要想了解更多细节,请参考 第 6.2.4.7 节 “通知(Advice)顺序” 节。)

  1. package x.y;
  2. import org.aspectj.lang.ProceedingJoinPoint;
  3. import org.springframework.util.StopWatch;
  4. import org.springframework.core.Ordered;
  5. public class SimpleProfiler implements Ordered {
  6.     private int order;
  7.     // allows us to control the ordering of advice
  8.     public int getOrder() {
  9.         return this.order;
  10.     }
  11.     public void setOrder(int order) {
  12.         this.order = order;
  13.     }
  14.     // this method is the around advice
  15.     public Object profile(ProceedingJoinPoint call) throws Throwable {
  16.         Object returnValue;
  17.         StopWatch clock = new StopWatch(getClass().getName());
  18.         try {
  19.             clock.start(call.toShortString());
  20.             returnValue = call.proceed();
  21.         } finally {
  22.             clock.stop();
  23.             System.out.println(clock.prettyPrint());
  24.         }
  25.         return returnValue;
  26.     }
  27. }

这里是帮助满足我们上述要求的配置数据。

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3.         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4.         xmlns:aop="http://www.springframework.org/schema/aop"
  5.         xmlns:tx="http://www.springframework.org/schema/tx"
  6.         xsi:schemaLocation="
  7.    http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
  8.    http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd
  9.    http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd">
  10.     <bean id="fooService" class="x.y.service.DefaultFooService"/>
  11.     <!-- this is the aspect -->
  12.     <bean id="profiler" class="x.y.SimpleProfiler">
  13.         <!-- execute before the transactional advice (hence the lower order number) -->
  14.         <property name="order" value="1"/>
  15.     </bean>
  16.     <tx:annotation-driven transaction-manager="txManager"/>
  17.     <aop:config>
  18.         <!-- this advice will execute around the transactional advice -->
  19.         <aop:aspect id="profilingAspect" ref="profiler">
  20.             <aop:pointcut id="serviceMethodWithReturnValue" expression="execution(!void x.y..*Service.*(..))"/>
  21.             <aop:around method="profile" pointcut-ref="serviceMethodWithReturnValue"/>
  22.         </aop:aspect>
  23.     </aop:config>
  24.     <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
  25.         <property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"/>
  26.         <property name="url" value="jdbc:oracle:thin:@rj-t42:1521:elvis"/>
  27.         <property name="username" value="scott"/>
  28.         <property name="password" value="tiger"/>
  29.     </bean>
  30.     <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
  31.         <property name="dataSource" ref="dataSource"/>
  32.     </bean>
  33. </beans>

上面配置的结果将获得到一个拥有剖析和事务方面的 按那样的顺序 应用于它上面的 'fooService' bean。 许多附加的方面的配置将一起达到这样的效果。

最后,下面的一些示例演示了使用纯XML声明的方法来达到上面一样的设置效果。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="
   http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
   http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd
   http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd">

    <bean id="fooService" class="x.y.service.DefaultFooService"/>

    <!-- the profiling advice -->
    <bean id="profiler" class="x.y.SimpleProfiler">
        <!-- execute before the transactional advice (hence the lower order number) -->
        <property name="order" value="1"/>
    </bean>

    <aop:config>

        <aop:pointcut id="entryPointMethod" expression="execution(* x.y..*Service.*(..))"/>

        <!-- will execute after the profiling advice (c.f. the order attribute) -->
       
        <aop:advisor advice-ref="txAdvice" pointcut-ref="entryPointMethod"order="2"/> 
        
        <!-- order value is higher than the profiling aspect -->

        <aop:aspect id="profilingAspect" ref="profiler">
            <aop:pointcut id="serviceMethodWithReturnValue" expression="execution(!void x.y..*Service.*(..))"/>
            <aop:around method="profile" pointcut-ref="serviceMethodWithReturnValue"/>
        </aop:aspect>

    </aop:config>

    <tx:advice id="txAdvice" transaction-manager="txManager">
        <tx:attributes>
            <tx:method name="get*" read-only="true"/>
            <tx:method name="*"/>
        </tx:attributes>
    </tx:advice>

    <!-- other <bean/> definitions such as a DataSource and a PlatformTransactionManager here -->

</beans>

上面配置的结果是创建了一个 'fooService' bean,剖析方面和事务方面被 依照顺序 施加其上。如果我们希望剖析通知在目标方法执行之前 后于 事务通知执行,而且在目标方法执行之后 先于 事务通知,我们可以简单地交换两个通知bean的order值。

如果配置中包含更多的方面,它们将以同样的方式受到影响。

9.5.8. 结合AspectJ使用 @Transactional

通过AspectJ切面,你也可以在Spring容器之外使用Spring框架的 @Transactional 功能。要使用这项功能你必须先给相应的类和方法加上 @Transactional注解,然后把 spring-aspects.jar 文件中定义的org.springframework.transaction.aspectj.AnnotationTransactionAspect 切面连接进(织入)你的应用。同样,该切面必须配置一个事务管理器。你当然可以通过Spring框架容器来处理注入,但因为我们这里关注于在Spring容器之外运行应用,我们将向你展示如何通过手动书写代码来完成。

注意

在我们继续之前,你可能需要好好读一下前面的第 9.5.6 节 “使用 @Transactional” 和 第 6 章 使用Spring进行面向切面编程(AOP) 两章。

  1. // construct an appropriate transaction manager 
  2. DataSourceTransactionManager txManager = new DataSourceTransactionManager(getDataSource());
  3. // configure the AnnotationTransactionAspect to use it; this must be done before executing any transactional methods
  4. AnnotationTransactionAspect.aspectOf().setTransactionManager (txManager);

注意

使用此切面(aspect),你必须在 实现 类(和/或类里的方法)、而 不是 类的任何所实现的接口上面进行注解。AspectJ遵循Java的接口上的注解 不被继承 的规则。

类上的 @Transactional 注解指定了类里的任何 public 方法执行的默认事务语义。

类里的方法的 @Transactional 将覆盖掉类注解的默认事务语义(如何存在的话)。 publicprotected和默认可见的方法可能都被注解。直接对 protected和默认可见的方法进行注解,让这些方法在执行时去获取所定义的事务划分是唯一的途径。

要把 AnnotationTransactionAspect 织入你的应用,你或者基于AspectJ构建你的应用(参考 AspectJ Development Guide),或者采取“载入时织入”(load-time weaving),参考 第 6.8.4 节 “在Spring应用中使用AspectJ Load-time weaving(LTW)” 获得关于使用AspectJ进行“载入时织入”的讨论。

maven-shade-plugin对可执行java工程及其全部依赖jar进行打包

现在基本上都是采用maven来进行开发管理,我有一个需求是需要把通过maven管理的java工程打成可执行的jar包,这样也就是说必需把工程依赖 的jar包也一起打包。而使用maven默认的package命令构建的jar包中只包括了工程自身的class文件,并没有包括依赖的jar包。我们可 以通过配置插件来对工程进行打包,pom具体配置如下:

maven-assembly-plugin (使用此插件会有一些问题)

Xml代码
  1. <plugin>
  2.     <artifactId>maven-assembly-plugin</artifactId>
  3.     <configuration>
  4.         <appendAssemblyId>false</appendAssemblyId>
  5.         <descriptorRefs>
  6.             <descriptorRef>jar-with-dependencies</descriptorRef>
  7.         </descriptorRefs>
  8.         <archive>
  9.             <manifest>
  10.                 <mainClass>com.chenzhou.examples.Main</mainClass>
  11.             </manifest>
  12.         </archive>
  13.     </configuration>
  14.     <executions>
  15.         <execution>
  16.             <id>make-assembly</id>
  17.             <phase>package</phase>
  18.             <goals>
  19.                 <goal>assembly</goal>
  20.             </goals>
  21.         </execution>
  22.     </executions>
  23. </plugin>

其 中<mainClass></mainClass>的值表示此工程的入口类,也就是包含main方法的类,在我的例子中就是 com.chenzhou.examples.Main。配置完pom后可以通过执行mvn assembly:assembly命令来启动插件进行构建。构建成功后会生成jar包,这样我们就可以在命令行中通过java -jar XXX.jar来运行jar件了。

不过使用此插件会有一些问题:我在工程中依赖了spring框架的jar包,我打包成功后使用命令来调用jar包时报错如下(内网环境):

Shell代码
  1. org.xml.sax.SAXParseException: schema_reference.4: Failed to read schema document 'http://www.springframework.org/schema/beans/spring-beans-3.0.xsd', because 1) could not find the document; 2) the document could not be read; 3) the root element of the document is not <xsd:schema>.

关于此问题报错的原因,我在网上找到一篇文章对此有比较详细的解释:http://blog.csdn.net/bluishglc/article/details/7596118 简单来说就是spring在启动时会加载xsd文件,它首先会到本地查找xsd文件(一般都会包含在spring的jar包中),如果找不到则会到xml头部定义的url指定路径下中去寻找xsd,如果找不到则会报错。

附:在spring jar包下的META-INF文件夹中都会包含一个spring.schemas文件,其中就包含了对xsd文件的路径定义,具体如下图所示:

spring-aop.jar包下META-INF文件夹下的内容

图:spring-aop.jar包下META-INF文件夹下的内容

spring.schemas文件内容

图:spring.schemas文件内容

由于我的工程是在内网,所以通过url路径去寻找肯定是找不到的,但是比较奇怪的是既然spring的jar包中都会包含,那为什么还是找不到呢?

原来这是assembly插件的一个bug,具体情况参见:http://jira.codehaus.org/browse/MASSEMBLY-360

该bug产生的原因如下:工程一般依赖了很多的jar包,而被依赖的jar又会依赖其他的jar包,这样,当工程中依赖到不同的版本的spring时,在 使用assembly进行打包时,只能将某一个版本jar包下的spring.schemas文件放入最终打出的jar包里,这就有可能遗漏了一些版本的 xsd的本地映射,所以会报错。

所以一般推荐使用另外的一个插件来进行打包,插件名称为:maven- shade-plugin,shade插件打包时在对spring.schemas文件处理上,它能够将所有jar里的spring.schemas文件 进行合并,在最终生成的单一jar包里,spring.schemas包含了所有出现过的版本的集合,要使用shade插件,必须在pom进行如下配置:

 

Xml代码
  1. <plugin>
  2.     <groupId>org.apache.maven.plugins</groupId>
  3.     <artifactId>maven-shade-plugin</artifactId>
  4.     <version>1.4</version>
  5.     <executions>
  6.         <execution>
  7.             <phase>package</phase>
  8.             <goals>
  9.                 <goal>shade</goal>
  10.             </goals>
  11.             <configuration>
  12.                 <transformers>
  13.                     <transformer
  14.                         implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
  15.                         <resource>META-INF/spring.handlers</resource>
  16.                     </transformer>
  17.                     <transformer
  18.                         implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
  19.                         <mainClass>com.chenzhou.examples.Main</mainClass>
  20.                     </transformer>
  21.                     <transformer
  22.                         implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
  23.                         <resource>META-INF/spring.schemas</resource>
  24.                     </transformer>
  25.                 </transformers>
  26.             </configuration>
  27.         </execution>
  28.     </executions>
  29. </plugin>

上面配置文件中有一段定义:

 

  1. <transformer
  2.     implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
  3.    
    <
    resource>META-INF/spring.handlers</resource>
  4. </transformer>
  5. <transformer
  6.    implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
  7.   
    <
    resource>META-INF/spring.schemas</resource>
  8. </transformer>

上面这段配置意思是把spring.handlers和spring.schemas文件以append方式加入到构建的jar包中,这样就不会出现xsd找不到的情况。

配置完pom后,调用mvn clean install命令进行构建,构建成功后打开工程target目录,发现生成了2个jar包,一个为:original-XXX-0.0.1- SNAPSHOT.jar,另一个为:XXX-0.0.1-SNAPSHOT.jar,其中original...jar里只包含了工程自己的class 文件,而另外的一个jar包则包含了工程本身以及所有依赖的jar包的class文件。我们只需要使用第二个jar包就可以了。

来源:http://chenzhou123520.iteye.com/blog/1706242

TCP 系统调用序列

从内核到应用程序级别的函数调用序列

TCP/IP 编程接口提供各种系统调用,以帮助您有效地使用该协议。TCP 堆栈代码数量繁多,深入到内核级别的完整调用序列可以帮助您了解 TCP 堆栈。在本文中,将回顾和学习关于 TCP 调用序列的详细信息,其中包括对 FreeBSD 的引用,以及在用户级进行系统调用后在 TCP 堆栈中发生的重要函数调用。


引言

典型的 TCP 客户机和服务器应用程序通过发布 TCP 系统调用序列来获取某些函数。这些系统调用包括 socket ()bind ()listen ()accept ()send ()receive()。本文介绍在应用程序发布 TCP 系统调用时在较低级别中发生的情况,如图 1 所示。

图 1. TCP 应用程序进行的普通调用序列

TCP 应用程序进行的普通调用序列图 2 显示了 TCP 系统调用在物理链路上发出之前进行传播的各个层。

图 2. TCP 系统调用的各个层

TCP 系统调用的各个层套接字层接收进行的任何 TCP 系统调用。套接字层验证 TCP 应用程序传递的参数的正确性。这是一个独立于协议 的层,因为尚未将协议连接到调用中。

套接字层下面是协议层,该层包含协议的实际实现(本例中为 TCP)。当套接字层对协议层进行调用时,将确保对两个层之间共享的数据结构具有独占访问权限。这样做是为了避免任何数据结构损坏。

各种网络设备驱动程序在接口层运行,该层从物理链路接收数据,并向物理链路传输数据。

每个套接字具有一个套接字队列,并且每个接口具有一个用于数据通信的接口队列。不过,对于整个协议层,只有一个称为 IP 输入队列的协议队列。接口层通过此 IP 输入队列将数据输入到协议层。协议层使用相应的接口队列将数据输出到接口。

在本文中,将学习以下系统调用:

  • Socket
  • Bind
  • Listen
  • Accept
  • Connect
  • Shutdown
  • Close
  • Send
  • Receive

Socket

socket (struct proc *p, struct socket_args *uap, int retval)
struct sock_args 
{
int domain, 
int type,
int protocol;
};

socket 系统调用中:

  • p 是一个指针,指向进行 socket 调用的进程的 proc 结构。
  • uap 是一个指向 socket_args 结构的指针,该结构包含传递到 socket 系统调用中的进程的参数。
  • retval 是系统调用的返回值。

socket 系统调用通过分配新的描述符创建新的套接字。将新的描述符返回到调用进程。任何后续的系统调用都使用创建的套接字标识。socket 系统调用还向创建的套接字描述符分配协议。

domaintypeprotocol 参数值指定系列、类型和协议,以分配给创建的套接字。图 3 显示了调用序列。

图 3. 用于 socket 系统调用的调用序列

用于 socket 系统调用的调用序列从进程检索参数后,socket 函数调用 socreate 函数。socreate 函数根据进程指定的参数发现指向协议切换 protsw 结构的指针。socreate 函数然后分配新的套接字结构。然后进行协议特定的调用 pr_usrreq,进而切换到与套接字描述符关联的相应协议特定的请求。pr_usrreq 函数的原型为:

int  pr_usrreq(struct socket *so , int req, struct mbuf  *m0 , *m1 , *m2);

pr_usrreq 函数中:

  • so 是指向套接字结构的指针。
  • req 的功能是标识请求。本例中为 PRU_ATTACH。
  • m0m1m2 是指向 mbuf 结构的指针。值因请求而异。

pr_usrreq 函数为大约 16 个请求提供服务。

tcp_usrreq() 函数调用 tcp_attach( ),以处理 PRU_ATTACH 请求。要分配 Internet 协议控制块,可调用 in_pcballoc()。在 in_pcballoc 中,调用了内核的内存分配器函数,该函数将内存分配给 Internet 控制块。完成所有必要的 Internet 控制块结构指针初始化之后,该控制返回到 tcp_attach()

分配新的 TCP 控制块,并在 tcp_newtcpcb() 中初始化。它还初始化所有的 TCP 定时器变量,并且控制返回到 tcp_attach()。现在套接字状态初始化为 CLOSED。在返回到 tcp_usrreq 函数时,创建套接字描述符,以指向套接字的 TCP 控制块。

Internet 控制块是双向链接的循环链表,其指针指向套接字结构,同时套接字结构的 so_pcb 部分指向 Internet 控制块结构。Internet 控制块还具有指向 TCP 控制块的指针。有关 Internet 控制块和 TCP 控制块结构的更详细信息,请参见参考资料部分。

Bind

bind (struct proc *p, struct bind_args *uap, int *retval)
   struct bind_args 
   {   int s;
       caddr_t name;
       int namelen;
   };

bind 系统调用函数中:

  • s 是套接字描述符。
  • name 是指向包含网络传输地址的缓冲区的指针。
  • namelen 是缓冲区的大小。

bind 系统调用将本地网络传输地址与套接字关联。对于客户端进程,发布 bind 调用不是强制的。当客户端进程发布 connect 系统调用时,内核负责执行隐式绑定。服务器进程接受连接或启动与客户端的通信之前,发布显式绑定请求通常是必需的。

bind 调用将进程指定的本地地址复制到 mbuf,并调用 sobind,后者则根据请求使用 PRU_BIND 调用 tcp_usrreq()tcp_usrreq() 中的切换实例调用 in_pcbbind(),后者将本地地址和端口号绑定到套接字。in_pcbbind 函数首先执行一些完整性检查,以确保不绑定套接字两次,并且至少一个接口分配了 IP 地址。in_pcbbind 负责隐式和显式绑定。

如果对 in_pcbbind()(指向 sockaddr_in 结构的指针)的调用中的第二个参数为非空,则发生显式绑定。其他情况下,则发生隐式绑定。对于显式绑定,在绑定的 IP 地址上执行检查,并相应设置套接字选项。

图 4. 用于 bind 系统调用的调用序列

用于 bind 系统调用的调用序列如果指定的本地端口是一个非零值,则对超级用户特权进行检查,以确定绑定是否位于保留的端口(例如,根据 Berkley 约定,端口号 < 1024)。然后调用 in_pcblookup(),以便查找具有提到的本地 IP 地址和本地端口号的控制块。in_pcblookup() 验证本地地址和端口对是否仍未使用。如果 in_pcbbind() 中的第二个参数是 NULL,或本地端口是零,则控制失败,并检查临时端口(例如,根据 Berkley 约定,1024 < 端口号 < 5000)。然后调用 in_pcblookup(),以验证发现的端口是否未使用。

Listen

listen (struct proc *p, struct listen_args *uap, int *retval)
struct listen_args
{ int s;
   int backlog;
};

listen 系统调用中:

  • s 是套接字描述符。
  • backlog 是套接字上的连接数的队列限制。

listen 调用指示协议,服务器进程准备接受套接字上任何新传入的连接。存在一个可以排列的连接数限制,在该连接数之后,忽略任何进一步的连接请求。

listen 系统调用使用套接字描述符和 listen 调用中指定的backlog 值调用 solistensolisten 仅使用 PRU_LISTEN 作为请求调用 tcp_usrreq 函数。在 tcp_usrreq() 函数的切换语句中,PRU_LISTEN 的实例检查套接字是否绑定到端口。如果端口为零,则调用 in_pcbbind(),将套接字绑定到一个端口(按照 Bind 部分中的描述)。

如果端口上已存在侦听的套接字,则将套接字的状态更改为 LISTEN。通常,所有的服务器进程都侦听众所周知的端口号。很少调用 in_pcbbind 来执行服务器进程的隐式绑定。图 5 显示了侦听的调用序列。

图 5. 用于 listen 系统调用的调用序列

用于 listen 系统调用的调用序列

Accept

accept(struct proc *p, struct accept_args *uap, int *retval);
 struct  accept_args 
{
	int s;
	caddr_t name;
	int *anamelen;
};

accept 系统调用中:

  • s 是套接字描述符。
  • name 是缓冲区(OUT 参数),它包含外来主机的网络传输地址。
  • anamelenname 缓冲区的大小。

accept 系统调用是等待传入连接的阻塞调用。处理连接请求后,accept 将返回新的套接字描述符。将此新的套接字连接到客户端,使另外一个套接字 s 保持 LISTEN 状态,以接受进一步连接。

图 6. 用于 accept 系统调用的调用序列

用于 accept 系统调用的调用序列accept 调用首先验证参数,并等待要到达的连接请求。在此之前,函数在 while 循环中阻塞。新的连接到达后,协议层唤醒服务器进程。Accept 然后检查函数阻塞时发生的任何套接字错误。如果存在任何套接字错误,则函数返回,并继续从队列拾取新的连接并调用 soaccept。在 soaccept() 中调用 tcp_usrreq () 函数,并将请求作为 PRU_ACCEPT。tcp_usrreq 函数中的切换调用 in_setpeeraddr(),后者从协议控制块复制外来 IP 地址和外来端口号,并将其返回到服务器进程。

Connect

connect (struct proc *p, struct connect_args *uap, int *retval);
struct connect_args 
{
    int s;
    caddr_t name;
    int namelen;
};

connect 系统调用中:

  • s 是套接字描述符。
  • name 是指向具有外来 IP/端口地址对的缓冲区的指针。
  • namelen 是缓冲区的长度。

客户端进程通常调用 connect 系统调用,以连接到服务器进程。如果在初始化连接之前,客户端进程没有显式发布 bind 系统调用,则堆栈负责本地套接字上的隐式绑定。

connect 系统调用将外来地址(需要将连接请求发送到地址)从进程复制到内核,并调用 soconnect()。从 soconnect() 返回时,connect() 函数进入睡眠状体,直到协议层将其唤醒,并指示连接是 ESTABLISHED 或套接字上存在错误。soconnect() 函数检查套接字的有效状态,并使用 PRU_CONNECT 作为请求调用 pr_usrreq()

tcp_usrreq() 函数中的切换实例检查套接字与本地端口的绑定。如果未绑定套接字,则调用执行隐式绑定的 in_pcbbind()。然后调用 in_pcbconnect(),以获取到达目的地的路线,发现必须输出套接字的接口,并验证 connect() 指定的外来套接字对(IP 地址和端口号)是否唯一。然后使用外来 IP 地址和端口号更新其 Internet 控制块,并返回到 PRU_CONNECT 示例语句。

tcp_usrreq () 现在调用 soisconnecting (),它可以将客户端主机上的套接字的状态设置为 SYN_SENT。调用函数 tcp_output,将 SYN 包输出到网络。控制现在返回到 connect() 函数,该函数处于睡眠状态,直到协议层唤醒 — 指示连接现在是 ESTABLISHED,或套接字上存在错误。

图 7. 用于 connect 系统调用的调用序列

用于 connect 系统调用的调用序列

3 向 TCP 握手

图 8、图 9 和图 10 显示了客户端发布 connect 和服务器发布 accept 以指示和建立 TCP 连接时的调用序列。

图 8. 用于 SYN 包的流序列

用于 SYN 包的流序列当客户端发布 connect 时,在协议层调用 tcp_output() 函数,将 SYN 包输出到接口。如图 9 所示,soconnect 现在返回到 connect() 函数,并进入睡眠状态。客户端上的套接字状态现在是 SYN_SENT。接口层调用 if_output()(实际上是接口特定的输出函数),将包发送到 n/w。

目的地(服务器)上的接口接收传入 SYN 包,将其放在 ipintrq 队列中,并引发软件中断。包然后由调用 tcp_input 例程的 ipintr() 获取。tcp_input() 在 s/w 中断时执行,并从 ipintrq 拾取 SYN 包,对其进行处理,并将部分完成的套接字连接放入完成的套接字队列。服务器端的套接字状态现在是 SYN_RCVD。每次处理后,tcp_input() 例程都调用 tcp_output()(如果需要将响应套接字发送到另一端)。

图 9. 用于 SYN ACK 包的流序列

用于 SYN ACK 包的流序列处理 SYN 后,服务器使用 tcp_output ()ip_output ()if_output () 序列发送 SYN ACK 包。客户端上的 n/w 接口接收此包,将其放在 ipintrq 中,并引发 s/w 中断。同样,ipintr () 从 ipintrq 获取该包,并将其传递到客户端 TCP 堆栈上的 tcp_input () 例程。包现在是经过处理的,并调用了 soisconnected (),它唤醒连接调用。客户端上的套接字状态现在已建立。

图 10. 用于 ACK 包的流序列

用于 ACK 包的流序列客户端上的 tcp_input () 例程处理 SYN ACK 包,并调用 tcp_output () 将 ACK 包发回到服务器。服务器端上的 tcp_input () 处理此 ACK 包,并调用 soisconnected ()。此函数从未完成的套接字队列移除套接字,并将其放入完成的套接字队列,然后调用 Wakeup (),以唤醒 accept 调用。服务器端的套接字现在已建立。

Shutdown

shutdown (struct proc *p, struct shutdown_args *uap, int *retval);
Struct shutdown_args
{
	int s;
	int how;
}

shutdown 系统调用中:

  • s 是套接字描述符。
  • how 指定将关闭哪一部分连接。how 的值 0、1 和 2 分别指定关闭连接的读取部分、写入部分和同时关闭连接的读取及写入部分。

shutdown 系统调用关闭连接的任意一端或两端。如果需要关闭读取部分,则会丢弃接收缓冲区中存在的任何数据,并关闭该端的连接。对写入部分,TCP 发送任何剩余的数据,然后终止连接的写入端。

图 11. 用于 shutdown 系统调用的调用序列

用于 shutdown 系统调用的调用序列如果需要关闭连接的读取部分,则 soshutdown() 函数调用 sorflush()sorflush() 标记套接字以拒绝任何传入的包,并释放保存的任何系统资源。

如果需要关闭连接的写入部分,则调用 tcp_usrreq(),并将 PRU_SHUTDOWN 作为请求。PRU_SHUTDOWN 的切换实例根据当前的状态调用 tcp_usrclosed() 函数,以更新套接字的状态。TCP/IP 状态图表可以帮助了解套接字在任何给定的时间存在的不同状态。如果从 tcp_usrclosed() 返回时需要发送 FIN,则调用 tcp_output() 将其发送到接口。

Close

soo_close(struct file *fp , struct proc *p);

close 系统调用中:

  • fp 是指向文件结构的指针。
  • p 是一个指向调用进程的 proc 结构的指针。

close 系统调用可关闭或中止套接字上任何挂起的连接。

soo_close() 仅调用 so_close() 函数,该函数首先检查要关闭的套接字是否为侦听套接字(正在接收传入连接的套接字)。如果是,则遍历两个套接字队列,以检查任何挂起的连接。对每个挂起的连接,将调用 soabort() 以发布 tcp_usrreq(),并将 PRU_ABORT 用作请求。此切换实例调用 tcp_drop() 以检查套接字的状态。

如果状态是 SYN_RCVD,则通过将状态设置为 CLOSED 并调用 tcp_output() 发送 RST 段。tcp_close() 函数然后关闭套接字。tcp_close 函数更新路由度量结构的三个变量,然后释放套接字持有的资源。

如果套接字不是侦听套接字,则控制开始使用 soclose(),以检查是否已存在附加到套接字的控制块。如果不存在,则 sofree() 释放套接字。如果存在,则调用具有 PRU_DETACH 的 tcp_usrreq() 将协议与套接字分离。PRU_DETACH 的切换实例调用 tcp_disconnect(),以检查连接状态是否为 ESTABLISHED。如果不是,则 tcp_disconnect() 调用 tcp_close(),以释放 Internet 和控制块。否则,tcp_disconnect() 检查延迟时间和延迟套接字选项。如果设置了该选项,并且延迟时间为零,则调用 tcp_drop()。如果未设置,则调用 tcp_usrclosed(),以设置套接字的状态,并调用 tcp_output()(如果需要发送 FIN 段)。

图 12 显示了 TCP 应用程序发布 close 系统调用时发生的重要调用。

图 12. 用于 close 系统调用的调用序列

用于 close 系统调用的调用序列

Send

sendmsg ( struct proc*p, struct sendmsg_args *uap, int retval);
struct sendmsg_args
{	
   int s;
   caddr_t msg;
   int flags;
};

send 系统调用中:

  • s 是套接字描述符。
  • msg 是指向 msghdr 结构的指针。
  • flags 是控制信息。

n/w 接口上有四个要发送数据的系统调用:writewritevsendtosendmsg。本文仅讨论 sendmsg() 系统调用。所有的四个调用最终调用 sosend()。尽管 send(进程调用的库函数)、sendtosendmsg 系统调用仅可以对套接字描述符操作,但 writewritev 系统调用则可以对任何类型的描述符操作。

图 13. 用于 sendmsg 的调用序列

用于 sendmsg 的调用序列sendmsg 系统调用将从进程发送的消息复制到内核空间,并调用 sendit()。在 sendit() 中,将初始化一个结构,以便从进程将输出收集到内核中的内存缓冲区。还可以将地址和控制信息从进程复制到内核,然后调用 sosend(),以执行以下四项任务:

  • 基于 sendit() 函数传递的值初始化各种参数。
  • 验证套接字的条件和连接的状态,并确定传递消息和报告错误所需的空间。
  • 分配内存并从进程复制数据。
  • 使协议特定的调用将数据发送到网络。

然后调用 tcp_usrreq(),并根据进程指定的标志,控制切换到 PRU_SEND 或 PRU_SENDOOB(以发送带区外数据)。对于 PRU_SENDOOB,发送缓冲区大小可以超过 512 字节,将释放任何分配的内存并中断控制。否则,sbappend()tcp_output() 函数由 PRU_SEND 和 PRU_SENDOOB 调用。sbappend() 在发送缓冲区的末尾添加数据,并且 tcp_output() 将该段发送到接口。

Receive

recvmsg(struct proc *p, struct recvmsg_args *uap , int *retval);
struct recvmsg_args 
{
 int s,
 struct msghdr *msg,
 int flags,
};

receive 系统调用中:

  • s 是套接字描述符。
  • msg 是指向 msghdr 结构的指针。
  • flags 指定控制信息。

有四个系统调用可以用于从连接接收数据:readreadvrecvfromrecvmsg。尽管 recv(进程使用的库函数)、recvfromrecvmsg 仅可以对套接字描述符操作,但 readreadv 可以对任何种类的描述符操作。所有的 read 系统调用最终调用 soreceive()

图 14 显示了用于 recvmsg 系统调用的调用序列。recvmsg()recvit() 函数初始化各种数组和结构,将接收的数据从内核发送到进程。recvit() 调用 soreceive(),以便将接收的数据从套接字缓冲区传输到接收缓冲区进程。soreceive() 函数执行各种检查,如:

  • 是否设置了 MSG_OOB 标志。
  • 进程是否尝试接收数据。
  • 是否应该阻塞,直到足够的数据到达。
  • 将读取数据传输到进程。
  • 检查数据是带区外数据还是常规数据,并进行相应的处理。
  • 当数据接收完成后通知协议。
图 14. 用于 recvmsg 的调用序列

用于 recvmsg 的调用序列当设置 MSG_OOB 标志时或数据接收完成后,soreceive() 函数进行与协议相关的请求。在接收带区外数据的情况下,协议层检查不同的条件,以验证接收的数据是否为带区外数据,然后将其返回到套接字层。在后一种情况中,协议层调用 tcp_output(),将窗口更新段发送到网络。它通知另一端任何空间都可用于接收数据。

结束语

在本文中,您学习了触发低级别调用以完成某些任务的最重要的 TCP 函数调用。图中的调用序列显示了内核级 TCP 调用的简要概述。本文是了解 FreeBSD TCP/IP 堆栈组织的很好起点。

Hadoop的SecondaryNameNode、CheckpointNode、BackupNode、HA、Federation

来源:http://www.mamicode.com/info-detail-670006.html

一、SecondaryNameNode

Secondary NameNode不是NameNode的备份。它的作用是:定期合并fsimage与edits文件,并推送给NameNode,以及辅助恢复NameNode。
SNN的作用现在(Hadoop2.x)可以被两个节点替换CheckpointNode和BackupNode。
CheckpointNode可以理解为与Secondary NameNode的作用一致。
BackupNode:NameNode的完全备份。
配置文件:core-site.xml
fs.checkpoint.period、fs.checkpoint,size、
fs.checkpoint.dir、fs.checkpoint.edits.dir

Secondary NameNode定期合并流程,如下图所示。
技术分享

[root@master current]# more VERSION
#Mon May 04 15:06:37 CST 2015
namespaceID=1967523381
clusterID=CID-bdf70043-346a-439b-87de-cee402d13fa4
cTime=0
storageType=NAME_NODE
blockpoolID=BP-510666760-172.23.253.20-1430213820023
layoutVersion=-47

VERSION文件保存了HDFS的版本号
layoutVersion是一个负整数,保存了HDFS的持续化在硬盘上的数据
结构的格式版本号
namespaceID是文件系统的唯一标识符,在文件系统初次格式化时生成的。
cTime此处为0
storageType表示此文件夹中保存的是元数据节点的数据结构

NameNode进程死了,并且存放NameNode元数据信息目录下的数据丢失了,怎么恢复?

1、删除SNN存放数据目录下in_use.lock文件
2、执行恢复命令hadoop namenode -importCheckpoint
3、启动namenode hadoop-daemon.sh start namenode
4、进行校验检查根目录是否健康hadoop fsck /
5、查看数据 hadoop fs -lsr /
至此,NameNode元数据恢复成功。

二、CheckpointNode

CheckpointNode和SecondaryNameNode的作用以及配置完全相同。
启动命令:hdfs namenode -checkpoint

配置文件:core-site.xml
fs.checkpoint.period、fs.checkpoint,size、
fs.checkpoint.dir、fs.checkpoint.edits.dir

三、BackupNode

提供了一个真正意义上的备用节点。
BackupNode在内存中维护了一份从NameNode同步过来的fsimage,同时它还从namenode接收edits文件的日志流,并把它们持久化硬盘。
BackupNode在内存中维护与NameNode一样的Matadata数据。
启动命令:hdfs namenode -backup

配置文件:hdfs-site.xml
dfs.backup.address、dfs.backup.http.address

[root@master current]# hdfs namenode --help
Usage: java NameNode [-backup] | [-checkpoint] | [-format [-clusterid cid ] [-force] [-nonInteractive] ] | [-upgrade] | [-rollback] | [-finalize] | [-importCheckpoint] | [-initializeSharedEdits] | bootstrapStandby] | [-recover [ -force ] ]

四、HDFS HA的自动failover

技术分享
HDFS的HA,指的是在一个集群中存在两个NameNode, 分别运行在独立的物理节点上。在任何时间点, 只有一个NameNodes是处于Active状态,另一种是在Standby状态。

Active NameNode负责所有的客户端的操作,而Standby NameNode用来同步Active NameNode的状态信息,以提供快速的故障恢复能力。

为了保证Active NN与Standby NN节点状态同步,即元数据保持一致。除了DataNode需要向两个NN发送block位置信息外,还构建了一组独立的守护进程”JournalNodes” ,用来同步FsEdits信息。当Active NN执行任何有关命名空间的修改,它需要持久化到一半以上的JournalNodes上。而Standby NN负责观察JNs的变化,读取从Active NN发送过来的FsEdits信息,并更新其内部的命名空间。

一旦Active NN遇到错误, Standby NN需要保证从JNs中读出了全部的
FsEdits, 然后切换成Active状态。

在这个图里,我们可以看出HA的大致架构,其设计上的考虑包括:
利用共享存储来在两个NN间同步edits信息。
以前的HDFS是share nothing but NN,现在NN又share storage,这样其实是转移了单点故障的位置,但中高端的存储设备内部都有各种RAID以及冗余硬件包括电源以及网卡等,比服务器的可靠性还是略有提高。
通过NN内部每次元数据变动后的flush操作,加上NFS的close-to-open,数据的一致性得到了保证。社区现在也试图把元数据存储放到 BookKeeper上,以去除对共享存储的依赖, Cloudera也提供了Quorum Journal Manager(QJM)的实现和代码。
DataNode同时向两个NN汇报块信息。这是让Standby NN保持集群最新状态的必需步骤。
用于监视和控制NN进程的FailoverController进程,显然,我们不能在NN进程内进行心跳等信息同步,最简单的原因,一次FullGC就可以让NN挂起
十几分钟,所以,必须要有一个独立的短小精悍的watchdog来专门负责监控。这也是一个松耦合的设计,便于扩展或更改,目前版本里是用 ZooKeeper(以下简称ZK)来做同步锁,但用户可以方便的把这个ZooKeeper FailoverController(以下简称ZKFC)替换为其他的HA方案或leader选举
方案。
隔离(Fencing),防止脑裂,就是保证在任何时候只有一个主NN,包括三个方面:
共享存储fencing,确保只有一个NN可以写入edits。
客户端fencing,确保只有一个NN可以响应客户端的请求。
DataNode fencing,确保只有一个NN可以向DN下发命令,譬如删除块,复制块等等。

五、HDFS2的Federation

HDFS Federation设计可解决单一命名空间存在的以下几个问题:
1 、HDFS集群扩展性。多个NameNode分管一部分目录,使得一个集群可以扩展到更多节点,不再像Hadoop1.x中那样由于内存的限制制约文件存储数目。
2、性能更高效。多个NameNode管理不同的数据,且同时对外提供服务,将为用户提供更高的读写吞吐率。
3、良好的隔离性。用户可根据需要将不同业务数据交由不同NameNode管理,这样不同业务之间影响很小。

技术分享

技术分享

由上图,我们可以看到多个NN共用一个集群里DN上的存储资源,每个NN都可以单独对外提供服务每个NN都会定义一个存储池,有单独的id,每个DN都为所有存储池提供存储。
DN会按照存储池id向其对应的NN汇报块信息,同时, DN会向所有NN汇报本地存储可用资源情况
如果需要在客户端方便的访问若干个NN上的资源,可以使用客户端挂载表,把不同的目录映射到不同的NN,但NN上必须存在相应的目录。
这样设计的好处有:
改动最小,向前兼容。
现有的NN无需任何配置改动。
如果现有的客户端只连某台NN的话,代码和配置也无需改动。
分离命名空间管理和块存储管理。
提供良好扩展性的同时允许其他文件系统或应用直接使用块存储池。
统一的块存储管理保证了资源利用率。
可以只通过防火墙配置达到一定的文件访问隔离,而无需使用复杂的Kerberos认证
客户端挂载表通过路径自动对应NN使Federation的配置改动对应用透明。