Distributed systems for fun and profit读书笔记

这两天读了一个比较有意思的系列文章,Distributed systems for fun and profit,简单的做下读书笔记。

文章目的有二:

  • 梳理一些关键概念
  • 总结梳理分布式里的一些细节思想

在作者看来,分布式编程就是用来关注

  • 信息以光速传递
  • 不相干的个体不相关连的失败

换句话说,其核心就是处理distance,并且不止一个。希望读过该文章之后,能够对distance, time,consistency models三者之间的相互影响,有个比较感性的认识。

###Distributed systems at a high level

Distributed programming is the art of solving the same problem that you can solve on a single computer using multiple computers.

任何计算机系统,需要完成两个任务:

  • 存储 (Storage)
  • 计算 (Computation)

Cluster size

performance gap. 机器的增长带来的性能增长是非线性的

希望达到的目标:Scalability

is the ability of a system, network, or process, to handle a growing amount of work in a capable manner or its ability to be enlarged to accommodate that growth.

关注的三点:

  • Size scalability
  • Geographic scalability
  • Administrative scalability:

A scalable system is one that continues to meet the needs of its users as scale increases. There are two particularly relevant aspects - performance and availability - which can be measured in various ways.

一个扩展性很强的系统,就是可以持续的满足其用户的大规模增长。这里有两个特殊的相关概念,performance,availability,他们可以从多种方式来度量

Performance (and latency)

Performance is characterized by the amount of useful work accomplished by a computer system compared to the time and resources used.

体现在:

  • 低延迟 Short response time/low latency for a given piece of work
  • 高吞度 High throughput (rate of processing work)
  • 低资源利用率 Low utilization of computing resource(s)

Latency The state of being latent; delay, a period between the initiation of something and the occurrence.

Availability (and fault tolerance)

the proportion of time a system is in a functioning condition. If a user cannot access the system, it is said to be unavailable.

Availability = uptime / (uptime + downtime)

Availability % How much downtime is allowed per year?
90% (“one nine”) More than a month
99% (“two nines”) Less than 4 days
99.9% (“three nines”) Less than 9 hours
99.99% (“four nines”) Less than an hour
99.999% (“five nines”) ~ 5 minutes
99.9999% (“six nines”) ~ 31 seconds

高效使用Xcode[译]

翻译来源Supercharging Your Xcode Efficiency

You’ve all seen the all-star Hollywood programmer hacking through the mainframe, fingers racing on the keyboard while terminals fly across the screen. If you’ve ever wanted to be like that, you’re in the right place!
This tutorial will teach you how to be more like that programmer, in Xcode. Call it what you like — magic, mad skillz, pure luck or hacks, there is no doubt you’ll feel much cooler (and have improved Xcode efficiency) after following along this tutorial, and maybe even save the world from destruction with your newly found prowess.
你曾经在电影里看到一些伟大的黑客程序员操作大型机,手指扫过键盘,屏幕里的终端便飞驰而过,如果你也希望像他们那样,今天你来对地方了!

Learn some cool Xcode Tricks!

MySQL批量写入

MySQL批量写入,通常可以使用JDBCTemplate的batchUpdate

1
2
3
       public int [] batchUpdate (String sql, final BatchPreparedStatementSetter pss) throws DataAccessException {

}

使用后,针对批量操作,jdbc driver会render成批量语句发送给MySQL。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() {
@Override
public void setValues(PreparedStatement ps, int i) throws SQLException {
Log log = logs.get(i);
ps.setLong(1,log.getKeyid());
ps.setLong(2,log.getUserid());
ps.setLong(3,log.getPlanid());
ps.setLong(4,log.getUnitid());
ps.setLong(5,log.getLevel());
ps.setInt(6,log.getType());
}
@Override
public int getBatchSize() {
return logs.size();
}
});

今天一个线上case排查中发现,最终没有生效,SQL仍然是一条一条的发送出去,整体的性能下降明显。

查阅资料发现,原来MySQL默认是不支持batch的,jdbc driver虽然提供了batch接口,但是默认并没有开启,需要给JDBC Connection增加配置参数rewriteBatchedStatements=true,示例配置:

jdbc:mysql://10.10.10.38:5858?characterEncoding=gbk&rewriteBatchedStatements=true

PrepareStatement在执行executeBatch的时候,会对该参数进行判断,来进行批量操作。

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
public int[] executeBatch() throws SQLException {
synchronized (checkClosed().getConnectionMutex()) {

if (this.connection.isReadOnly()) {
throw new SQLException(Messages.getString("PreparedStatement.25") //$NON-NLS-1$
+ Messages.getString("PreparedStatement.26"), //$NON-NLS-1$
SQLError.SQL_STATE_ILLEGAL_ARGUMENT);
}

if (this.batchedArgs == null || this.batchedArgs.size() == 0) {
return new int[0];
}

// we timeout the entire batch, not individual statements
int batchTimeout = this.timeoutInMillis;
this.timeoutInMillis = 0;

resetCancelledState();

try {
statementBegins();

clearWarnings();

if (!this.batchHasPlainStatements
&& this.connection.getRewriteBatchedStatements()) {


if (canRewriteAsMultiValueInsertAtSqlLevel()) {
return executeBatchedInserts(batchTimeout);
}

if (this.connection.versionMeetsMinimum(4, 1, 0)
&& !this.batchHasPlainStatements
&& this.batchedArgs != null
&& this.batchedArgs.size() > 3 /* cost of option setting rt-wise */) {
return executePreparedBatchAsMultiStatement(batchTimeout);
}
}

return executeBatchSerially(batchTimeout);
} finally {
this.statementExecuting.set(false);

clearBatch();
}
}
}

需要指出的时,该参数在JDBC 5.1.8开始才开始支持,5.1.17进行了优化,如果采用该机制,期望采用5.1.17+的版本。

搭建博客环境

今天在Mac下搭建了Hexo环境,先说说搭建环境方面的问题。

###环境准备


Mac环境下最好先安装homeblew,避免后续的各种麻烦,安装过程非常简单:

1
$ ruby -e "$(curl -fsSL https://raw.github.com/Homebrew/homebrew/go/install)"

然后用blew安装Node.js环境。

1
$ blew install node

接下来进行hexo的安装

1
$ npm install -g hexo

简单的等待后,一切就绪,然后可以进行相关目录,初始化博客目录。

1
2
3
$ hexo init github-blog
$ cd github-blog
$ npm install

接下来就可以对_config.yml等文件进行配置,进行博客撰写了。

###博客发布


配置github信息

deploy:
  type: github
  repository: https://github.com/home3k/home3k.github.io.git
  branch: master

接下来进行发布即可。

1
$ hexo deploy --generate

起步

有一段时间,经常会写写东西。不过,如梭带来了浮躁,浮躁带来的懒惰,慢慢地,文字就停了。

从今天开始,采用Markdown+hexo写自己的独立博客。之前的博客内容,也全部迁移过来。技术博客托管在Github上,生活博客托管在Gitcafe上。

希望自己能够坚持写下去,加油!

1
$ blog start

初始Vert.x

Vert.x框架最近比较火,它号称JVM上的Node.js。

http://vertx.io/

它目前支持JavaScript,Groovy,Ruby,Java,正在对Clojure,Scala,Python等。跟node.js一样,它是异步的、基于EDA的架构,跟Node.js一样,它拥有良好的并发及消息传递性能。

它支持HTTP/HTTPS,同时支持WebSockets,sockjs等高级协议。

其官方的benchmark相当漂亮:

Vert.x Benchmark


###event-driven模型

在vert.x内部,每一个vert.x实例称为一个verticle。verticle被bind到一个统一的event bus上,进行事件循环。其中一部分verticle是提供一些共享的lib(如共享的event handler),这种verticle被称为busmod。

通过event bus,Verticle可与运行在相同或不同vert.x实例中的其他verticle进行通信(基于Actor model)。

Vert.x verticle


###vert.x的线程模型

在vert.x中,每一个verticle(busmod)都被绑定到一个特殊的线程,所以在对verticle进行开发时,无需关注他的线程安全性。因此,对于vert.x instance,它管理着一个Thread Set,每个线程都维护了一个event loop。vert.x内部通过一个线程池,选择将一个event循环分配给具体的verticle。具体的线程响应模型采用了Reactor pattern

###内部实现

从其事件处理流程上很容易想到netty,vert.x在底层确实使用了netty。通过netty来处理高并发响应,reactor式的分派请求。vertx的ClusterManager基于Hazelcast进行轻量级实现。

具体细节还得看代码细节,有机会再分享https://github.com/vert-x/vert.x

简单的实现demo

1
2
3
4
5
6
7
8
9
Vertx vertx = Vertx .newVertx();
vertx.createHttpServer().requestHandler(
new Handler< HttpServerRequest>() {
public void handle(HttpServerRequest req) {
String file = req.path.equals( "/" ) ? "index.html"
: req.path;
req.response.sendFile( "webroot/" + file);
}
}).listen(8080);

DirectMemory源码分析

最近对cache相关进行了调研,看了一下off-heap cache DirectMemory源码,对其进行如下梳理:
源码路径:https://github.com/raffaeleguidi/DirectMemory

Java cache通常的做法是通过缓存对象报错在heap,通过一定的持久化机制保存在disk。为了防止缓存对象被gc,通常用弱引用等wrap一下。

考虑到heap容量,缓存达到一定的容量必然会发生gc(full),由于full gc的STW,当heap容量达到10g以上时的pause time几乎是无法容忍的。

为了防止gc带来的性能问题,部分cache系统开始使用off-heap机制,通过对堆外内存的自主管理,防止额外的性能问题。这里面比较典型的是ehcache被terracotta收购后推出的BigMemory(http://www.ehcache.org/documentation/user-guide/bigmemory)

根据terracotta测试,BigMemory在350G+的场景下,表现良好。

由于BigMemory不开源,有个开源版的DirectMemory,实现跟其比较类似。

DirectMemory

DirectMemory代码结构比较简单:

DirectMemory代码结构

下面结合其源码及数据的put操作,对其进行介绍:

Cache

Cache作为入口,它通过ConcurrentMap 维护了String->Pointer,该Map的作用,后面会有介绍。该map通过google guava框架的MapMaker创建(可以方便的进行超时事件等)

Cache作为DirectMemory的入口,暴露了缓存操作的大部分接口,如put retrieve free等

需要指出的是,为了防止cache实例进入heap,cache的创建,属性同时通过static创建的。这也导致Cache在同一个JVM中是singleton的,无法根据需求创建多个cache

Cache层对对象的序列化是通过serialization包下的序列化器进行的,默认采用的是ProtoStuff。

下面是其put操作代码,从代码可以看到,Cache层主要通过调用memory包下的MemoryManager相应接口,进行cache的各种操作。同时操作后更新其本层Map

1
2
3
4
5
 public static Pointer putByteArray (String key, byte[] payload, int expiresIn) {
Pointer ptr = MemoryManager. store(payload, expiresIn);
map.put(key, ptr);
return ptr;
}


MemoryManager

MemoryManager维护了

1
2
public static List<OffHeapMemoryBuffer> buffers = new Vector<OffHeapMemoryBuffer>();
public static OffHeapMemoryBuffer activeBuffer = null;

activeBuffer即为当前buffer,如果当前buffer满了,则跳到buffers的下一个buffer中,这些buffer在buffers中,很显然,通过对buffer的分片,可以较好的提高并发度。

下面的代码是MemoryManager进行store操作的过程,里面用很明显的逻辑进行了切(分)片操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
        public static Pointer store(byte[] payload, int expiresIn) {
Pointer p = activeBuffer .store(payload, expiresIn);
if (p == null) {
if (activeBuffer.bufferNumber+1 == buffers.size()) {
return null ;
} else {
// try next buffer
activeBuffer = buffers.get(activeBuffer.bufferNumber+1);
p = activeBuffer .store(payload, expiresIn);
}
}
return p;
}

MemoryManager中维持的Buffer是OffHeapMemoryBuffer。

OffHeapMemoryBuffer

OffHeapMemoryBuffer是off-heap写入的核心组件,其核心为一个

1
protected ByteBuffer buffer ;

通过ByteBuffer.allocateDirect(capacity),生成的DirectByteBuffer。作为NIO提供的Buffer,它可以更高效的进行off-heap操作。

为了更高效的进行buffer读写,它提供了

1
public List<Pointer> pointers = new ArrayList<Pointer>();。

对于Pointer对象,其核心字段:

1
2
3
4
5
 public int start ;  //buffer的开始位置
public int end ; //buffer的结束位置
public boolean free ; //pointer是否可用
public int bufferNumber ; //所属的buffer number
.....

从本质上讲,它标识了一个buffer分片。通过将数据存储在这些细粒度分片上,可以更好的进行数据获取,并可以围绕key进行必要的操作,例如:Cache中包含一个key-Pointer的ConcurrentHashMap,这样在进行retrieve或update操作时,可以通过key先获得Pointer,然后可以通过pointer快速地对buffer进行操作。

初始化时,Pointer为buffer大小。每次store的时候,先查找是否有free的Pointer,如果存在,执行slice,将原来的pointer切分出一块数据大小的新的pointer,封装pointer属性,然后slice buffer,并忘buffer里写数据。同时将报错数据的pointer放入到pointers中。

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
private synchronized Pointer store(byte[] payload, long expiresIn, long expires) {
Pointer goodOne = firstMatch(payload. length);

if (goodOne == null ) {
throw new NullPointerException("did not find a suitable buffer");
}

Pointer fresh = slice(goodOne, payload. length);


fresh. created = System.currentTimeMillis();
if (expiresIn > 0) {
fresh. expiresIn = expiresIn;
fresh. expires = 0;
} else if (expires > 0) {
fresh. expiresIn = 0;
fresh. expires = expires;
}

fresh. free = false ;
used.addAndGet(payload.length );
ByteBuffer buf = buffer.slice();
buf.position(fresh. start);
try {
buf.put(payload);
} catch (BufferOverflowException e) {
// RpG not convincing - let's fix it later
goodOne. start = fresh.start ;
goodOne. end = buffer .limit();
return null ;
}
pointers.add(fresh);
return fresh;
}


其他

  1. 从MemoryManager store操作代码可以看到,当store操作时,如果当前buffer空间不足时,直接进行换切片操作,显然如果数据非常大时,OffHeapMemoryBuffer存在空间浪费。同时纵观其代码,其内存管理比较粗,目前基本只涉及过期处理,LFU策略等。Pointer空间释放后,也没有必要的合并操作。内存空间浪费应该是个问题。
  2. key过期处理时,对Pointer进行查找时,在设计上采用了JoSQL框架,通过SQL方式获得Pointer,代码比较明晰。