最终一致性(eventual consistency)引

一致性设计在分布式系统中是一个重要问题。如果一个系统同时使用多个子数据系统来存储与读取数据,就必须设计满足功能需求的一致性定义。如果系统对不同数据子系统进行操作的结果不一致,不但可能会使用户困惑,更可能引发更严重的数据问题或系统错误。一致性有多种级别,适用于不同的业务场景。对于金融等对数据一致性要求较高的行业,传统的事务可以提供较高的一致性保证。对于分布式系统等对性能(performance)和可用性(availability)要求较高的场景,牺牲一定的强一致性来换取更好的用户体验也可接受。最终一致性的具体行为根据实际需求不同,并无严格而精确的定义。本文仅作为一个引子,介绍最终一致性及与其相关的一些基本概念,方便读者对最终一致性有初步的认识,以便进一步阅读其他文章与资料,设计适合自身业务需求的系统数据一致性。

衍生数据系统

衍生数据(derived data)指的是可以由另一些数据通过某种可以复现的处理来再次生成的数据。通常,衍生数据用于加速读取数据库中的某些数据。索引(index)和缓存(cache)都是典型的衍生数据。如果衍生数据丢失,仍可以通过原始数据再次生成。

单个数据库系统中的索引属于其所存储数据的衍生数据。有时,数据库的索引可能会由于性能或成本考量而被维护在另一个系统之中,此时,该索引系统即为一种衍生数据系统。该索引系统中的数据可以通过数据库中存储的数据重新生成(即使往往开销过大而不实际),反之则不行。这种情况下,如何维护源数据库数据与索引数据库系统之间数据的一致性,值得系统设计者考虑。

事务与 ACID

事务(transaction)是一种传统的确保数据一致性的方式。在一个系统中,各种类型的错误都可能会破坏数据的一致性。网络问题、硬件故障、远程服务不可用等原因都会导致数据操作部分或彻底失败。

事务可以将一组数据读写操作合并为一组,构成一个逻辑单元,作为一个单独的数据操作执行。一次事务操作要么完全成功(提交),要么彻底失败(中止并回滚)。如果事务失败,它能被安全地再次执行。

ACID 是原子性(atomicity)、一致性(consistency)、隔离性(isolation)与持久性(durability)的首字母缩写,它通常用来描述事务的性质。

原子性减轻了多步操作中途出错时造成的问题。如果一此事务失败,原子性将确保没有数据发生变化,事务可以被安全地重新提交。

一致性事实上不是数据库的一种属性,而由具体的应用需求定义。一个应用程序可以借助原子性与隔离性来实现一致性,而这可能不仅仅是单个数据库系统的问题。一致性有多种等级,在此不具体展开介绍。

隔离性与竞争(race condition)及并发(concurrency)有关,用于解决多个事务同时执行时的相互干涉。

持久性在数据库系统中指数据应当在事务提交成功后能安全而长久地被保存,且不会因系统故障而丢失。

事务可以在很大程度上确保数据质量。然而,最近流行的 NoSQL 数据库大多不提供事务支持,以免它过高的性能开销损害它们试图提供的高可用性。 从事务的角度来看,数据存储与索引是不同的数据库对象。 许多数据库系统,包括微软 Azure Cosmos DB 等分布式数据库都提供了单对象事务支持,但并未提供完全的多对象事务支持。

异构分布式事务

异构分布式事务( heterogeneous distributed transaction)指的是事务的参与者可能由两种或多种不同的技术组成。例如,由不同供应商提供的数据库,各司其职的数据库与索引系统,甚至是消息分发系统等非数据库系统。异构分布式事务也需要满足提交(commit)的原子性,这比实现数据库内部的事务更为困难。

两阶段提交(2PC,Two-Phase Commit)是一种著名的强一致性保证。对于一次事务请求,所有子系统都将确保数据被写入或最新的数据被读取。对于由一个索引系统和一个数据库系统构成的异构分布式系统,在完成一次两阶段提交后,索引系统中存储的数据必然能反映数据库系统的最新情况,对数据库系统的更新操作也总会反应在索引系统中。

重试策略

重试(retry)即再次执行某一操作。在设计数据系统时,重试策略也是一个需要考虑的问题。不恰当的重试可能会导致以下问题:

  • 如果重试请求的响应因网络问题而没能返回,再次重试可能会使开销倍增
  • 如果问题在于系统超负载,重试可能会导致问题进一步恶化
  • 重试只能解决暂时性问题(如死锁、隔离性被违反/isolation violation,或短暂的网络故障等),因此在重试前有必要判断重试是否可能解决问题

因此,在设计重试策略时,超时、最大重试次数、两次重试之间的间隔等都需要纳入考虑。

最终一致性与BASE

与事务提供的 ACID 属性不同,最终一致性提供的一致性保证也被称为 BASE(Basic Available, Soft state, Eventual consistency)。BASE 确保如果系统之后没有更多的数据操作,最终对系统的所有访问都将返回相同的结果。BASE 更多地是确保系统使用可用,但在系统数据收敛之前,它可能返回不同的结果。 最终一致性的实际行为由具体应用定义,无法统一阐述。

采用最终一致性的数据系统通常不要求数据操作失败时执行回滚(rollback)。用户或系统日志将得知操作失败,但在另一次成功的操作之前,数据的不一致问题并不会被自动修复。

之所以最终一致性会出现,很大程度上是因为如今的网路应用常常要处理规模巨大的数据与请求。此时,更强的一致性往往并不现实,高可用性反而是更系统更核心的设计目标。

_

以上是讨论最终一致性之前可能需要了解的一些前置概念的简述,以帮助读者快速了解系统数据一致性设计时的一些主题,并能有目标地查阅更多资料与文献。水平有限,如果纰漏,欢迎批评指正。

在 Ubuntu 中安装 Jenkins

Jenkins 是目前流行的 CI 平台,终于得闲,便想到试用看看,略作笔记。

官方文档: https://wiki.jenkins-ci.org/display/JENKINS/Installing+Jenkins+on+Ubuntu

Jenkins 依赖于 JDK 和 JRE,可以通过以下代码安装。

安装完成后,继续安装 Jenkins 本身。

Jenkins 的默认端口为 8080,在安装完成后即可使用。

 

常见 Java 框架、库与工具简介

本文将不定期更新一些常见的 Java 框架与库,或是简单的介绍与感想。

框架

  • Spring – 文档与实例丰富;适合大中型系统
  • Play Framework – 轻量;适合 RESTful;源代码修改后无需重启编译器与服务器;内置 JSON 库
  • Dropwizard

服务器(Server)

IDE

标准 API

模板引擎(Template engine)

数据库(Database)

对象映射工具(Object mapping)

测试工具(Testing)

日志工具(Logging)

库(Library)

编译工具(Build tool)

其他工具

JVM 语言

参考链接:Awesome Java

初版条目的分类选取引用于 disc99 的博客文章《Javaを使うなら知っておきたい技術、フレームワーク、ライブラリ、ツールまとめ》。

SOAP 与 REST 简单比较

SOAP
SOAP是一种协议。
SOAP是Simple Object Access Protocol的缩写,用于传输小规模数据。SOAP消息为XML格式,通常以HTTP协议发送(亦通过TCP/IP)。
由于采用了XML格式,SOAP需要定义数据的类型与功能。如果要传输二进制数据,则必须先以base64格式对其编码。WSDL、XSDs、WS-Addressing等协议或技术都与之相关。
SOAP由于可以同时控制服务器与客户端的行为,因此经常被用于内部网络API。

REST
REST是一种传输架构,与协议无关。
REST是Representational State Transfer的缩写,可以较为灵活地在客户端与服务器之间传输数据,格式可以是JSON、XML甚至纯文本,机制较SOAP更为轻量。
REST采用通常的HTTP方法(如HTTP GET、HTTP PUT等)传输,而无需借助XML。只要框架支持HTTP,就能实现REST。二进制数据也可以通过请求传输。不过需要注意,REST并没有与CRUD方法一一对应。
REST由于简单轻量,且格式更为灵活,不少公网API都采用了这种方式。

在选择SOAP还是REST时,可以考虑以下几点:

REST的优势:

  • 实现简单
  • 学习曲线平缓
  • 传输效率更高(无需定义XML)
  • 传输速度更快(没有引入额外的处理)
  • 与其他一些网络技术在设计理念上有共通之处

REST的劣势:

  • 是否需要独立于平台、框架或协议(REST依赖HTTP)
  • 是否需要在分布式企业环境中运作(REST是直接的点对点通信)
  • 是否需要限定传输格式
  • 是否需要使用WS标准提供的扩展功能(如项目已有的代码或接口)
  • 是否需要利用内建的错误处理机制
  • 是否希望利用语言或框架自带的自动化机制来创建接口

Java 非访问修饰符简述

 

abstract

用于定义抽象方法。抽象方法仅具有签名(方法名、参数与返回值),需进一步覆盖使用。包含抽象方法的类称为抽象类,同样需要添加 abstract 修饰符。继承抽象类时必须覆盖其中所有的抽象方法。interface 指令也起到了抽象的功能,因此常省略重复使用 abstract。

 

static

用于定义静态成员(字段或方法)。静态成员无需实例化即可访问。

 

final

final 方法无法覆盖、final 字段无法再次赋值、final 类无法被继承。

 

transient

transient 对象不会被序列化。有时,为了将对象通过网络传输或存入数据库,需要将序列化(如将其转换为字节数组形式)。对于一些临时变量,或其他一些特殊原因,可通过 transient 修饰符是成员免于序列化。

 

volatile

多线程处理中,各线程的成员将各自分配缓存。volatile 修饰符强制所有线程中的成员共用同一缓存,共享相同的值。

 

synchronized

synchronized 修饰符使成员无法被多个线程同时访问,必须依次进行(同步处理)。如果方法分属不同实例,synchronized 将无法确保处理同步。

 

native

native 修饰符表示方法由非 Java 语言实现,因此,方法体将被省略。随着 Java 性能的改善,native 的应用范围也逐渐缩小。

 

strictfp

strictfp 修饰符要求程序严格遵循 IEEE 754 标准对浮点小数进行计算。此时,不同环境中浮点数运算的误差不复存在,但处理速度将有所下降。

Java 的内存大小与垃圾回收简述

一些 Java 程序在高负荷时性能会出现显著下降。一个可能的原因是没有设定恰当的内存大小(memory sizing),导致垃圾回收(Garbage collection,下称 GC)频繁执行。即使 CPU 满负荷运作,性能依然较差。

JVM 的内存分 Java 专用区域与 C Heap 两部分,其中前者又分为维护 Java 对象的 Java Heap 区域与维护类信息的 Permanent 区域。一些采用了分代收集机制(Generational Garbage Collection,或称 Generation Scavenging)的 GC 系统还将 Java Heap 进一步细分为 Eden、Survivor 与 Old 区域。Eden 维护生成的对象,GC 时未被回收的对象将被移入 Survivor,长期存在的对象进一步进入 Old 区域。

对于分代收集,GC 可分为 Full GC 与 Copy GC,其中,Copy GC 仅针对 New 区域(Eden+Survivor),Full GC 则针对所有区域。为防止 GC 过于频繁,我们需要设置合适的内存大小。常见的情况有以下这些:

Copy GC

  • Eden 区域空间不足

Full GC

  • Eden 区域空间不足时,Old 区域的可用内存大小不足以容纳 New 区域的已使用内存
  • New 区域与 Old 区域的可用内存空间不足
  • GC 后 Old 区域的剩余空间过低
  • GC 后 Old 区域的空间增加
  • Permanent 区域的可用空间不足

在程序执行过程中,Permanent 区域很少发生变化,因此我们因主要考虑 Full GC 的前两种情况。未避免 Old 区域的空间增加,建议将 Java Heap 的初始值与最大值设为同一值。

通常,Full GC 更为耗时,因此我们应主要考虑减少 Full GC,令 Copy GC 可以解决大部分回收。为此,Old 区域的内存大小是一个关键。

Old 区域的内存大小应与 Java 程序占用内存、Java 服务器占用内存,及 New 区域的内存大小,之和,相当。

举例来说,对于 Web 程序,事务处理占用的内存可以被立即释放,会话占用的内存则需要长期保存。因此后者应归为 Java 程序占用内存。

New 区域的内存大小也需要专门考虑,尤其是 Survivor 区域的大小十分重要。如果 Survivor 区域的内存大小不足,对象就需要被移动至 Old 区域。如果这些对象的生存周期不够长,就会进一步引发 Full GC。增加 Survivor 区域可以缓解该问题。然而,如果 Survivor 区域过大,Copy GC 的处理时间就会增加。因此,应当设置一个阀值,将经历 Copy GC 次数超过该值的对象移植 Old 区域。应尽可能避免 Survivor 区域已满,对象从 Eden 区域直接被移动至 Ole 区域的情况。为此,需要在 Copy GC 发生后检查 Survivor 区域是否被完全占用,以及移动至 Old 区域的对象年龄(生存次数)是否合适。如有可能,应为 Survivor 区域分配较大的内存大小,以适应需求。具体的内存大小估算方式,可以查找其他相关文章。

Eden 区域的内存大小将直接影响 Copy GC 的频率。通常,每发生 10-20 次 Copy GC 后执行一次 Full GC 较为合适。

Documentum 上传大尺寸文件

说明:

Documentum API (DFC) 提供了多种文件上传方法,如果需要上传的文件较大,则需要选择合适的方法。

其中,SetContent()及相关扩展方法需要使用 Byte 数组构造文件内容,因此会消耗较多内存,抛出 java.lang.OutOfMemoryError: Java heap space 异常。可以通过配置 JRE 的 Xms 参数(例:-vmargs -Xmx1024M 表示为 JVM 分配最少1024MB内存)增加内存分配以在一定程度上缓解该问题,但如果系统内存不足,将无法启动程序。

另一方面,SetFile()及相关扩展方法可以接收文件路径,无需将整个文件载入内存,但却需要耗费额外存储空间。应记得在完成处理后删除临时文件。

顺便一提,造成 heap space 不足通常可能有以下原因:线程过多、 java.io.File 类的 deleteOnExit() 方法维护的文件信息过多、大量 I/O 操作、java.util.zip 包的 Deflater 与 Inflater 类的构造函数在 end() 方法被调用前保存了大量信息、java.nio.ByteBuffer 类的 allocateDirect() 或 java.nio.channels.FileChannel类的 map() 方法生成了大量 Buffer 对象实例。

Java 开发笔记

作为分类目录的一个补充,在这里按照功能类别对 Java 开发中的一点心得和笔记作一个索引。其中部分是根据自己在查找网络资料时找到的内容的整理与演绎,在此感谢所有那些无私分享经验的人们。

基本

线程

垃圾回收

其他

基于 Eclipse + Tomcat 的 Java Servlet Web 开发环境搭建与配置

2014年8月26日更新

本文应用的软件环境

  • Windows 8.1 Pro (64bit)
  • Eclipse IDE for Java EE Developers (Ver.4.4 Luna)
  • Tomcat 8.0.9
  • Java 7 Update 55 (64bit)

概览

  • 安装 Eclipse
  • 安装 Tomcat
  • 安装 JDK
  • 新建 HelloWorld Servlet

安装 Eclipse

从 http://www.eclipse.org/downloads/ 下载包含了 WTP (Web Tools Platform)的 Eclipse IDE for Java EE Developers。将下载得到的压缩包解压至任意路径。

安装 Tomcat

从 http://tomcat.apache.org/ 下载 Binary Distributions – Core – zip 文件。将下载得到的压缩包解压至任意路径。

安装 JDK

从 http://www.oracle.com/technetwork/java/javase/downloads/ 下载 JDK 并根据提示安装。

新建 HelloWorld Servlet

启动 Eclipse。

从菜单选择 File – New – Project – Web – Dynamic Web Project。

填写任意 [Project Name],此处采用 TomcatTest。选择 [New Runtime] – [Apache – Apache Tomcat v8.0] – [Create a new local server] – [Next]。

[Browse] 至 Tomcat 的解压路径。

在之后的 Web Module 页面勾选 [Generate web.xml deployment descriptor]。

点击 [Finish] 完成配置。

右键单击 workspace 中列出的项目 TomcatTest,选择 [Properties] – [Resource] – [Text file encoding] – [Other] – [UTF-8] 以避免字符编码问题。

单击 workspace 中列出的项目 TomcatTest,从菜单选择 [File] – [New] – [Other] – [Web] – [Servlet]。填写任意 [Class Name] (此处采用 HelloWorld)与 [Java Package]。

在新建的 HelloWorld.java 中的 doGet 方法中添加如下语句:

response.getWriter().write(“Hello, World!”);

在 Eclipse 的 Servers 标签页中右键单击先前创建的 Tomcat v8.0,选择 [Add and remove],将 TomcatTest 项目从 Available 栏移动至 Configured 栏。点击 [Finish]。

右键单击 Servers 标签页的 Tomcat v8.0,选择 [Start] 启动。

在浏览器中输入 http://localhost:8080/TomcatTest/HelloWorld 以确认 Tomcat 是否正常运行。页面应显示文字“Hello World!”。

对 TomcatTest 项目的 WebContent 文件夹右键单击,选择 [New] – [Other] – [Web] – [JSP File]。之后可以在浏览器中测试访问该页面。