2015年8月8日星期六

MySQL-Proxy

MySQL-Proxy是处在你的MySQL数据库客户和服务端之间的程序,它可以监测、分析或改变它们的通信,它还支持嵌入性脚本语言Lua。这个代理可以用来分析、监控和变换(transform)通信数据,它支持非常广泛的使用场景:
  • 负载平衡和故障转移处理
  • 查询分析和日志
  • SQL宏(SQL macros)
  • 查询重写(query rewriting)
  • 执行shell命令
简单的说,MySQL Proxy就是一个连接池,负责将前台应用的连接请求转发给后台的数据库,并且通过使用lua脚本,可以实现复杂的连接控制和过滤,从而实现读写分离和负载平衡。对于应用来说,MySQL Proxy是完全透明的,应用则只需要连接到MySQL Proxy的监听端口即可。当然,这样proxy机器可能成为单点失效,但完全可以使用多个proxy机器做为冗余,在应用服务器的连接池配置中配置到多个proxy的连接参数即可。
MySQL Proxy更强大的一项功能是实现“读写分离(Read/Write Splitting)”。基本的原理是让主数据库处理事务性查询,而从数据库处理SELECT查询。数据库复制被用来把事务性查询导致的变更同步到集群中的从数据库。


读写分离:
http://imysql.cn/2008_04_23_quick_startup_with_mysql_proxy
http://blog.csdn.net/yueliangdao0608/article/details/2398655

Gossip协议

1.背景

Gossip算法又被称为反熵(Anti-Entropy),熵是物理学上的一个概念,代表杂乱无章,而反熵就是在杂乱无章中寻求一致,这充分说明了Gossip的特点:在一个有界网络中,每个节点都随机地与其他节点通信,经过一番杂乱无章的通信,最终所有节点的状态都会达成一致。每个节点可能知道所有其他节点,也可能仅知道几个邻居节点,只要这些节可以通过网络连通,最终他们的状态都是一致的,当然这也是疫情传播的特点。

要注意到的一点是,即使有的节点因宕机而重启,有新节点加入,但经过一段时间后,这些节点的状态也会与其他节点达成一致,也就是说,Gossip天然具有分布式容错的优点。

Gossip是一个带冗余的容错算法,更进一步,Gossip是一个最终一致性算法。虽然无法保证在某个时刻所有节点状态一致,但可以保证在”最终“所有节点一致,”最终“是一个现实中存在,但理论上无法证明的时间点。

因为Gossip不要求节点知道所有其他节点,因此又具有去中心化的特点,节点之间完全对等,不需要任何的中心节点。实际上Gossip可以用于众多能接受“最终一致性”的领域:失败检测、路由同步、Pub/Sub、动态负载均衡。例如,Cassandra集群没有中心节点,各个节点的地位完全相同,它们通过gossip协议维护集群的状态。通过gossip,每个节点都能知道集群中包含哪些节点,以及这些节点的状态,这使得Cassandra集群中的任何一个节点都可以完成任意key的路由,任意一个节点不可用都不会造成灾难性的后果。

但Gossip的缺点也很明显,冗余通信会对网路带宽、CPU资源造成很大的负载,而这些负载又受限于通信频率,该频率又影响着算法收敛的速度,后面我们会讲在各种场合下的优化方法。

2.基本概念
gossip分为两种. 本文只讨论anti-entropy
■anti-entropy 只要数据不同步,就开始同步数据
■rumor mongering 每隔固定的时间同步数据

Gossip中的每个节点维护一组状态,状态可以用一个key/value对表示,还附带一个版本号,版本号大的为更新的状态。信息达到同步的时间大概是log(N),这里N表示节点的数量。

为了保证一致性,规定数据的value及version只有宿主节点才能修改,其他节点只能间接通过Gossip协议来请求数据对应的宿主节点修改,即m (p)只能由有节点p来修改。

anti-entropy协议通过版本号大小来对数据进行更新。

两个节点(A、B)之间存在三种通信方式:
■push-gossip: A节点将数据推送给B节点,B节点更新A中比自己新的数据

■pull-gossip:A仅将摘要数据 (node,key,value,version)推送给B,B根据摘要数据来选择那些版本号比A高的数据推送给A,A更新本地。

■push-pull gossip:与pull类似,只是多了一步,A再将本地比B新的数据推送给B,B更新本地。

如果把两个节点数据同步一次定义为一个周期,则在一个周期内,push需通信1次,pull需2次,push/pull则需3次。从效果上来讲,push/pull最好,理论上一个周期内可以使两个节点完全一致。直观上也感觉,push/pull的收敛速度是最快的。

3.算法样例
Cassandra内部有一个Gossiper,每隔一秒运行一次(在Gossiper.java的start方法中),按照以下规则向其他节点发送同步消息:

1、随机取一个当前活着的节点,并向它发送同步请求

2、向随机一台不可达的机器发送同步请求

3、如果第一步中所选择的节点不是seed,或者当前活着的节点数少于seed数,则向随意一台seed发送同步请求

如果没有这个判断,考虑这样一种场景,有4台机器,{A, B, C, D},并且配置了它们都是seed,如果它们同时启动,可能会出现这样的情形:
1、A节点起来,发现没有活着的节点,走到第三步,和任意一个种子同步,假设选择了B
2、B节点和A完成同步,则认为A活着,它将和A同步,由于A是种子,B将不再和其他种子同步
3、C节点起来,发现没有活着的节点,同样走到第三步,和任意一个种子同步,假设这次选择了D
4、C节点和D完成同步,认为D活着,则它将和D同步,由于D也是种子,所以C也不再和其他种子同步

这时就形成了两个孤岛,A和B互相同步,C和D之间互相同步,但是{A,B}和{C,D}之间将不再互相同步,它们也就不知道对方的存在了。
加入第二个判断后,A和B同步完,发现只有一个节点活着,但是seed有4个,这时会再和任意一个seed通信,从而打破这个孤岛。

参考:
http://f.dataguru.cn/thread-163038-1-1.html
http://blog.163.com/liaoxiangui@126/blog/static/795696402012121112831272/

2015年8月5日星期三

Spring Data初见

Spring Data 作为SpringSource的其中一个父项目, 旨在统一和简化对各类型持久化存储, 而不拘泥于是关系型数据库还是NoSQL 数据存储。无论是哪种持久化存储, 数据访问对象(或称作为DAO,即Data Access Objects)通常都会提供对单一域对象的CRUD (创建、读取、更新、删除)操作、查询方法、排序和分页方法等。Spring Data则提供了基于这些层面的统一接口(CrudRepository,PagingAndSortingRepository)以及对持久化存储的实现。
Spring Data包括许多子项目: Spring Data 项目旨在为大家提供一种通用的编码模式。数据访问对象实现了对物理数据层的抽象, 为编写查询方法提供了方便。通过对象映射, 实现域对象和持续化存储之间的转换, 而模板提供的是对底层存储实体的访问实现。

Spring Data JPA:

Spring Data JPA 的发布包包含Spring Data Commons 和 Spring Data JPA 两个发布包,Commons 是 Spring Data 的公共基础包。
使用 Spring Data JPA 进行持久层开发大致需要的三个步骤:

1.声明持久层的接口,该接口继承 Repository,Repository 是一个标记型接口,它不包含任何方法,当然如果有需要,Spring Data 也提供了若干 Repository 子接口,其中定义了一些常用的增删改查,以及分页相关的方法。

2.在接口中声明需要的业务方法。Spring Data 将根据给定的策略来为其生成实现代码。

3.在 Spring 配置文件中增加一行声明,让 Spring 为声明的接口创建代理对象。配置了 <jpa:repositories> 后,Spring 初始化容器时将会扫描 base-package 指定的包目录及其子目录,为继承 Repository 或其子接口的接口创建代理对象,并将代理对象注册为 Spring Bean,业务层便可以通过 Spring 自动封装的特性来直接使用该对象。

此外,<jpa:repository> 还提供了一些属性和子标签,便于做更细粒度的控制。可以在 <jpa:repository> 内部使用 <context:include-filter>、<context:exclude-filter> 来过滤掉一些不希望被扫描到的接口。

接口继承
持久层接口继承 Repository 并不是唯一选择。Repository 接口是 Spring Data 的一个核心接口,它不提供任何方法,开发者需要在自己定义的接口中声明需要的方法。与继承 Repository 等价的一种方式,就是在持久层接口上使用 @RepositoryDefinition 注解,并为其指定 domainClass 和 idClass 属性。

1.如果持久层接口较多,且每一个接口都需要声明相似的增删改查方法,直接继承 Repository 就显得有些啰嗦,这时可以继承 CrudRepository,它会自动为域对象创建增删改查方法,供业务层直接使用。开发者只是多写了 "Crud" 四个字母,即刻便为域对象提供了开箱即用的十个增删改查方法。

2.使用 CrudRepository 也有副作用,它可能暴露了你不希望暴露给业务层的方法。比如某些接口你只希望提供增加的操作而不希望提供删除的方法。针对这种情况,开发者只能退回到 Repository 接口,然后到 CrudRepository 中把希望保留的方法声明复制到自定义的接口中即可.

3.分页查询和排序是持久层常用的功能,Spring Data 为此提供了 PagingAndSortingRepository 接口,它继承自 CrudRepository 接口,在 CrudRepository 基础上新增了两个与分页有关的方法。但是,我们很少会将自定义的持久层接口直接继承自 PagingAndSortingRepository,而是在继承 Repository 或 CrudRepository 的基础上,在自己声明的方法参数列表最后增加一个 Pageable 或 Sort 类型的参数,用于指定分页或排序信息即可,这比直接使用 PagingAndSortingRepository 提供了更大的灵活性。

4.JpaRepository 是继承自 PagingAndSortingRepository 的针对 JPA 技术提供的接口,它在父接口的基础上,提供了其他一些方法,比如 flush(),saveAndFlush(),deleteInBatch() 等。如果有这样的需求,则可以继承该接口。

查询方式

1.通过解析方法名创建查询
框架在进行方法名解析时,会先把方法名多余的前缀截取掉,比如 find、findBy、read、readBy、get、getBy,然后对剩下部分进行解析。并且如果方法的最后一个参数是 Sort 或者 Pageable 类型,也会提取相关的信息,以便按规则进行排序或者分页查询。

2.使用 @Query 创建查询
@Query 注解的使用非常简单,只需在声明的方法上面标注该注解,同时提供一个 JP QL 查询语句即可,如下所示:

3.通过调用 JPA 命名查询语句创建查询
命名查询是 JPA 提供的一种将查询语句从方法体中独立出来,以供多个方法共用的功能。Spring Data JPA 对命名查询也提供了很好的支持。用户只需要按照 JPA 规范在 orm.xml 文件或者在代码中使用 @NamedQuery(或 @NamedNativeQuery)定义好查询语句,唯一要做的就是为该语句命名时,需要满足”DomainClass.methodName()”的命名规则。假设定义了如下接口
Spring Data JPA 对事务的支持
默认情况下,Spring Data JPA 实现的方法都是使用事务的。针对查询类型的方法,其等价于 @Transactional(readOnly=true);增删改类型的方法,等价于 @Transactional。可以看出,除了将查询的方法设为只读事务外,其他事务属性均采用默认值。
如果用户觉得有必要,可以在接口方法上使用 @Transactional 显式指定事务属性,该值覆盖 Spring Data JPA 提供的默认值。同时,开发者也可以在业务层方法上使用 @Transactional 指定事务属性,这主要针对一个业务层方法多次调用持久层方法的情况。持久层的事务会根据设置的事务传播行为来决定是挂起业务层事务还是加入业务层的事务。具体 @Transactional 的使用可以参考Spring的参考文档。

2015年8月2日星期日

Java序列化框架FST

FST(fast-serialization)是一种快速高效的Java序列化框架,文档:https://github.com/RuedigerMoeller/fast-serialization/wiki.

概述:
  进行Java序列化,最重要的是序列化的格式,常用的有bson ,msgpack,protobuffer等。然而这些格式需要额外的配置文件或者注解等,需要对使用者的代码产生侵入,或者产生额外的工作。好的序列化框架应该只通过class定义信息一种渠道获取信息。FST使用Kson格式存储数据,KSON通过反射获取类定义的元数据信息,无需其他配置文件。
  具体而言,KSON是一种json的扩展格式,将object进行序列化存储。

In C++, one needs to sacrifice performance in order to get convenience, in Java one needs to sacrifice convenience in order to get performance

  FST通过反射获取类信息,反射的效率并不高,因此可以在启动的时候,对类信息进行注册,并缓存类的metaData信息,提高效率。
  另外,为了节约内存,减少GC停顿,FST提出了FST Structs的概念,允许将结构化的数据存储在连续的内存块中,减少内存碎片。连续的内存可以通过byte[] array或者使用native的方式在java的堆外进行申请(使用unsafe类型即可)。
  FST Structs的原因:

  • 内存更便宜了,但是随着堆内内存的加大,GC的停顿时间更长了;
  • Java中的结构化数据存储在堆内性能消耗很大,例如,A String member of a class with value "A" requires 60 to 80 bytes on the heap (depends on VM version and 32/64 bit), which means >90% memory overhead.Frequently your 60 GB on-heap data structures boil down to some 5GB stored in packed structs.
  • 利于进行跨进程调用。


FST将meta信息保存在序列化后的数据中的优点是decode时,不需要访问类信息。这一点在跨语言时,特别有用。然后存在的问题就是所有序列化后的类型中均包含元数据信息,及其冗余。 因此,根据不同的使用场景,FST并非完全cover。

FST的限制:
  • stick with pojos,个人认为只有对pojo进行序列化才是比较合理的。 
  • supported collections: Map's, List's 
  • Use strong typing in class definition
自己实现序列化工具时,需要注意的点:
  • 序列化基础类型时,注意大小端,否则linux和windows将会出现不兼容现象; 
  • 注意读取注解,避免序列化不需要的Field; 
  • 注意扩展使用off-Heap Map/Memory Map Files; 
  • 注意循环引用的处理。

2015年7月28日星期二

Java线程池相关-ForkJoinPool

当一个问题被拆分成两个或者多个子问题的时候,需要启动多个子线程去执行,在必要的情况下会迭代的依次启动下去。这里就产生了一些线程之间的依赖,这个大的问题需要等待它的子问题线程返回,因此需要某些机制来保证他们的同步。ThreadPoolExecutor默认使用的线程池是期望他们所有执行的任务都是不相关的,可以尽可能的并行执行。

ForkJoinPool有一个特点是work stealing。每个工作线程都有自己的工作队列,这是使用deque来实现的。当一个任务划分一个新线程时,它将自己推到 deque 的头部。当一个任务执行与另一个未完成任务的合并操作时,它会将另一个任务推到队列头部并执行,而不会休眠以等待另一任务完成(像 Thread.join() 的操作一样)。当线程的任务队列为空,它将尝试从另一个线程的 deque 的尾部 窃取另一个任务。如果我们用传统的ThreadPoolExecutor则比较难用上work stealing的技术。
Fork/Join 模式有自己的适用范围。如果一个应用能被分解成多个子任务,并且组合多个子任务的结果就能够获得最终的答案,那么这个应用就适合用 Fork/Join 模式来解决。



ForkJoinPool核心的添加是新的ForkJoinPool执行者,专门执行实现了ForkJoinTask接口的实例。ForkJoinTask对象支持创建子任务来等待子任务完成。有了这些清晰的语义,当一个任务正在等待另一个任务完成并且有待执行的任务时,executor就能够通过”偷取”任务,在内部的线程池里分发任务。

ForkJoinTask对象主要有两个重要的方法:
  • fork()方法允许ForkJoinTask任务异步执行,也允许一个新的ForkJoinTask从存在的ForkJoinTask中被启动。
  • 反过来, join()方法允许一个ForkJoinTask等待另一个ForkJoinTask执行完成。
如图所示,通过fork()和join()实现任务间的相互合作。注意fork()和join()方法名称不应该与POSIX中的进程能够复制自己的过程相混淆。fork()只会让ForkJoinPool调度一个新的任务,而不会创建子虚拟机。


有两种类型的ForkJoinTask的定义:
  • RecursiveAction的实例代表执行没有返回结果。
  • 相反,RecursiveTask会有返回值。

通常,RecursiveTask是首选的,因为大部分分而治之的算法会在数据集上计算后返回结果。对于任务的执行,不同的同步和异步选项是可选的,这样就可以实现复杂的模式。
注意点:ForkJoinTask对应的fork/join任务应该是纯内存算法,而没有I/O操作。此外,应该尽可能避免通过共享状态来进行任务间的通信,因为这通常意味着加锁会被执行。理想情况下,仅当一个任务fork另一个任务或一个任务join另一个任务时才进行任务通信。

ForkJoinPool系统介绍:http://ifeve.com/fork-and-join-java/
Java线程池概述:http://blog.csdn.net/dm_vincent/article/details/39505977
详解介绍了线程池的线程数目如何设置以及普通线程池和ForkJoinPool的区别的使用场景。

ForkJoinPool简单源码解析:http://blog.csdn.net/aesop_wubo/article/details/10300273

ForkJoinPool介绍:
http://www.ibm.com/developerworks/cn/java/j-jtp11137.html
http://www.ibm.com/developerworks/cn/java/j-lo-forkjoin/index.html

2015年7月19日星期日

J2Cache学习记录

视频介绍:http://v.youku.com/v_show/id_XNzAzMTY5MjUy.html
简单介绍:http://www.oschina.net/question/12_140335
吐槽:http://blog.csdn.net/chenleixing/article/details/44629005


问题:
为什么只使用独立的Java缓存框架Ehcache不行?
Ehcache这类缓存框架,是一个进程内的缓存框架,如果是集群的话会导致缓存数据不同步。
缓存数据不同步有两个解决办法:
1. 集中式缓存,直接将 Ehcache 换成 Redis 这类产品。如果访问量不大是没问题,访问量大,大量的缓存数据访问使得应用服务器和缓存服务器之间的网络I/O成为瓶颈。
2. Ehcache 的分布式,节点间大量的数据复制带来额外的开销,在节点多的情况下此问题越发严重。
因此,出现了两级缓存框架J2Cache,引入集中式缓存Redis,通过进程内的Ehcache 缓存来缓解网络 I/O 瓶颈。同时也降低集中式缓存服务器的压力。

拓扑结构:

数据流:
缓存同步:

源码阅读:
J2Cache使用JGroups进行组播。https://github.com/belaban/JGroups
使用FST进行对象序列化。https://github.com/RuedigerMoeller/fast-serialization

(更新中)

2015年7月15日星期三

Hive调优-控制hive中的mapper和reducer数量

一 控制hive任务中的mapper数

1. 通常情况下,作业会通过input的目录产生一个或者多个map任务。
主要的决定因素有: input的文件总个数,input的文件大小,集群设置的文件块大小(目前为128M, 可在hive中通过set dfs.block.size;命令查看到,该参数不能自定义修改);

2. 举例:
a) 假设input目录下有1个文件a,大小为780M,那么hadoop会将该文件a分隔成7个块(6个128m的块和1个12m的块),从而产生7个map数
b) 假设input目录下有3个文件a,b,c,大小分别为10m,20m,130m,那么hadoop会分隔成4个块(10m,20m,128m,2m),从而产生4个map数
即,如果文件大于块大小(128m),那么会拆分,如果小于块大小,则把该文件当成一个块。

3. 是不是map数越多越好?
答案是否定的。如果一个任务有很多小文件(远远小于块大小128m),则每个小文件也会被当做一个块,用一个map任务来完成,
而一个map任务启动和初始化的时间远远大于逻辑处理的时间,就会造成很大的资源浪费。
而且,同时可执行的map数是受限的。
Map数越多也造成了巨大的资源浪费。

4. 是不是保证每个map处理接近128m的文件块,即map数越少越好?
答案也是否定的。比如有一个127m的文件,正常会用一个map去完成,但这个文件只有一个或者两个小字段,却有几千万的记录;如果map处理的逻辑比较复杂,用一个map任务去做,肯定也比较耗时。 
任务逻辑复杂
二 控制hive任务中的reducer数

1. Hive自己如何确定reduce数:
reduce个数的设定极大影响任务执行效率,不指定reduce个数的情况下,Hive会猜测确定一个reduce个数,基于以下两个设定: 
hive.exec.reducers.bytes.per.reducer --每个reduce任务处理的数据量,默认为1000^3=1G
hive.exec.reducers.max   --每个任务最大的reduce数,默认为999
计算reducer数的公式很简单N=min(参数2,总输入数据量/参数1)
即,如果reduce的输入(map的输出)总大小不超过1G,那么只会有一个reduce任务;

2. 调整reducer的个数
方法一:调整hive.exec.reducers.bytes.per.reducer参数的值;
set hive.exec.reducers.bytes.per.reducer=500000000;   --减少每个reducer处理的数据量
方法二;
set mapred.reduce.tasks = 15;
3. reducer个数并不是越多越好;
启动和初始化reduce也会消耗时间和资源; 另外,有多少个reduce,就会有多少个输出文件,如果生成了很多个小文件,那么如果这些小文件作为下一个任务的输入,则也会出现小文件过多的问题;

4. 只有一个reduce的情况; 
只有一个reduce任务的情况,除了数据量小于hive.exec.reducers.bytes.per.reducer参数值的情况外,还有以下原因:
a) 没有group by的汇总,比如把select pt,count(1) from popt_tbaccountcopy_mes where pt = '2012-07-04' group by pt; 写成 select count(1) from popt_tbaccountcopy_mes where pt = '2012-07-04';
b) Order by 和 笛卡尔积
因为这些操作都是全局的,所以hadoop不得不用一个reduce去完成;