RPC中间件对比

服务化实战之 dubbo、dubbox、motan、thrift、grpc等RPC框架比较及选型

概述

前段时间项目要做服务化,所以我比较了现在流行的几大RPC框架的优缺点以及使用场景,最终结合本身项目的实际情况选择了使用dubbox作为rpc基础服务框架。下面就简单介绍一下RPC框架技术选型的过程。

RPC简述

该系列文章将讲述以下RPC框架的helloword实例以及其实现原理简述,由于每一种RPC框架的原理实现不同且都比较复杂,如果想深入研究还请自行到官网或者其他技术博客学习。
RPC框架职责
RPC框架要向调用方屏蔽各种复杂性,要向服务提供方也屏蔽各类复杂性:

  • 调用方感觉就像调用本地函数一样
  • 服务提供方感觉就像实现一个本地函数一样来实现服务

RPC框架是架构(微)服务化的首要基础组件,它能大大降低架构微服务化的成本,提高调用方与服务提供方的研发效率,屏蔽跨进程调用函数(服务)的各类复杂细节
RPC框架的职责是:让调用方感觉就像调用本地函数一样调用远端函数、让服务提供方感觉就像实现一个本地函数一样来实现服务

对于不是很理解服务化的同学可以看看这两篇文章:
服务化架构的演进与实践
浅谈服务化架构

服务化的好处
互联网架构为什么要做服务化?

服务架构的拆分原则

来源:李林峰的文章:华为内部如何实施微服务架构?基本就靠这5大原则

服务拆分原则:围绕业务功能进行垂直和水平拆分。大小粒度是难点,也是团队争论的焦点。

不好的实践

  • 以代码量作为衡量标准,例如500行以内。
  • 拆分的粒度越小越好,例如以单个资源的操作粒度为划分原则。

建议的原则

  • 功能完整性、职责单一性。
  • 粒度适中,团队可接受。
  • 迭代演进,非一蹴而就。
  • API的版本兼容性优先考虑。

代码量多少不能作为衡量微服务划分是否合理的原则,因为我们知道同样一个服务,功能本身的复杂性不同,代码量也不同。还有一点需要重点强调,在项目刚开始的时候,不要期望微服务的划分一蹴而就。
微服务架构的演进,应该是一个循序渐进的过程。在一个公司、一个项目组,它也需要一个循序渐进的演进过程。一开始划不好,没有关系。当演进到一个阶段时,微服务的部署、测试和运维等成本都非常低的时候,这对于你的团队来说就是一个好的微服务。

服务架构的开发原则

微服务的开发还会面临依赖滞后的问题。例如:A要做一个身份证号码校验,依赖服务提供者B。由于B把身份证号码校验服务的开发优先级排的比较低,无法满足A的交付时间点。A会面临要么等待,要么自己实现一个身份证号码校验功能。

以前单体架构的时候,大家需要什么,往往喜欢自己写什么,这其实是没有太严重的依赖问题。但是到了微服务时代,微服务是一个团队或者一个小组提供的,这个时候一定没有办法在某一个时刻同时把所有的服务都提供出来,“需求实现滞后”是必然存在的。

一个好的实践策略就是接口先行,语言中立,服务提供者和消费者解耦,并行开发,提升产能。无论有多少个服务,首先需要把接口识别和定义出来,然后双方基于接口进行契约驱动开发,利用Mock服务提供者和消费者,互相解耦,并行开发,实现依赖解耦。

采用契约驱动开发,如果需求不稳定或者经常变化,就会面临一个接口契约频繁变更的问题。对于服务提供者,不能因为担心接口变更而迟迟不对外提供接口,对于消费者要拥抱变更,而不是抱怨和抵触。要解决这个问题,一种比较好的实践就是管理 + 技术双管齐下:

  • 允许接口变更,但是对变更的频度要做严格管控。
  • 提供全在线的API文档服务(例如Swagger UI),将离线的API文档转成全在线、互动式的API文档服务。
  • API变更的主动通知机制,要让所有消费该API的消费者能够及时感知到API的变更。
  • 契约驱动测试,用于对兼容性做回归测试。

服务架构的测试原则

微服务开发完成之后需要对其进行测试。微服务的测试包括单元测试、接口测试、集成测试和行为测试等,其中最重要的就是契约测试:
这里写图片描述

利用微服务框架提供的Mock机制,可以分别生成模拟消费者的客户端测试桩和提供者的服务端测试桩,双方可以基于Mock测试桩对微服务的接口契约进行测试,双方都不需要等待对方功能代码开发完成,实现了并行开发和测试,提高了微服务的构建效率。基于接口的契约测试还能快速的发现不兼容的接口变更,例如修改字段类型、删除字段等。

服务架构的部署原则

测试完成之后,需要对微服务进行自动化部署。微服务的部署原则:独立部署和生命周期管理、基础设施自动化。需要有一套类似于CI/CD的流水线来做基础设施自动化,具体可以参考Netflix开源的微服务持续交付流水线Spinnaker:
这里写图片描述

最后一起看下微服务的运行容器:微部署可以部署在Dorker容器、PaaS平台(VM)或者物理机上。使用Docker部署微服务会带来很多优先:

  • 一致的环境,线上线下环境一致。
  • 避免对特定云基础设施提供商的依赖。
  • 降低运维团队负担。
  • 高性能接近裸机性能。
  • 多租户。

相比于传统的物理机部署,微服务可以由PaaS平台实现微服务自动化部署和生命周期管理。除了部署和运维自动化,微服务云化之后还可以充分享受到更灵活的资源调度:

  • 云的弹性和敏捷。
  • 云的动态性和资源隔离。

服务架构的治理原则

服务部署上线之后,最重要的工作就是服务治理。微服务治理原则:线上治理、实时动态生效。
微服务常用的治理策略:

  • 流量控制:动态、静态流控制。
  • 服务降级。
  • 超时控制。
  • 优先级调度。
  • 流量迁移。
  • 调用链跟踪和分析。
  • 服务路由。
  • 服务上线审批、下线通知。
  • SLA策略控制。
  • 微服务治理模型如下所示:

这里写图片描述

最上层是为服务治理的UI界面,提供在线、配置化的治理界面供运维人员使用。SDK层是提供了微服务治理的各种接口,供服务治理Portal调用。最下面的就是被治理的微服务集群,集群各节点会监听服务治理的操作去做实时刷新。例如:修改了流控阈值之后,服务治理服务会把新的流控的阈值刷到服务注册中心,服务提供者和消费者监听到阈值变更之后,获取新的阈值并刷新到内存中,实现实时生效。由于目前服务治理策略数据量不是特别大,所以可以将服务治理的数据放到服务注册中心(例如etcd/ZooKeeper),没有必要再单独做一套。

服务最佳实践

介绍完微服务实施之后,下面我们一起学习下微服务的最佳实践。
服务路由:本地短路策略。关键技术点:优先调用本JVM内部服务提供者,其次是相同主机或者VM的,最后是跨网络调用。通过本地短路,可以避免远程调用的网络开销,降低服务调用时延、提升成功率。原理如下所示:

这里写图片描述
服务调用方式:同步调用、异步调用、并行调用。一次服务调用,通常就意味着会挂一个服务调用线程。采用异步调用,可以避免线程阻塞,提升系统的吞吐量和可靠性。但是在实际项目中异步调用也有一些缺点,导致使用不是特别广泛:
需要写异步回调逻辑,与传统的接口调用使用方式不一致,开发难度大一些。
一些场景下需要缓存上下文信息,引入可靠性问题。
并行调用适用于多个服务调用没有上下文依赖,逻辑上可以并行处理,类似JDK的Fork/Join, 并行服务调用涉及到同步转异步、异步转同步、结果汇聚等,技术实现难度较大,目前很多服务框架并不支持。采用并行服务调用,可以把传统串行的服务调用优化成并行处理,能够极大的缩短服务调用时延。

微服务故障隔离:线程级、进程级、容器级、VM级、物理机级等。关键技术点:

  • 支持服务部署到不同线程/线程池中。
  • 核心服务和非核心服务隔离部署。
  • 为了防止线程膨胀,支持共享和独占两种线程池策略。

这里写图片描述

谈到分布式,就绕不开事务一致性问题:大部分业务可以通过最终一致性来解决,极少部分需要采用强一致性。
这里写图片描述
具体的策略如下:

  • 最终一致性,可以基于消息中间件实现。
  • 强一致性,使用TCC框架。服务框架本身不会直接提供“分布式事务”,往往根据实际需要迁入分布式事务框架来支持分布式事务。

微服务的性能三要素:

  • I/O模型,这个通常会选用非堵塞的,Java里面可能用java原生的。
  • 线程调度模型。
  • 序列化方式。

公司内部服务化,对性能要求较高的场景,建议使用异步非阻塞I/O(Netty) + 二进制序列化(Thrift压缩二进制等) + Reactor线程调度模型。
这里写图片描述
最后我们一起看下微服务的接口兼容性原则:技术保障、管理协同。

  • 制定并严格执行《微服务前向兼容性规范》,避免发生不兼容修改或者私自修改不通知周边的情况。
  • 接口兼容性技术保障:例如Thrift的IDL,支持新增、修改和删除字段、字段定义位置无关性,码流支持乱序等。
  • 持续交付流水线的每日构建和契约化驱动测试,能够快速识别和发现不兼容。

现在流行的RPC框架:

服务治理型

  • dubbo
  • dubbox
  • motan

多语言型

  • grpc
  • thrift
  • avro
  • Protocol Buffers (google)
    这里写图片描述

上图来自于dubbo。服务治理型RPC框架结构大多如此,大致分为服务提供者,服务消费者,注册中心,监控报警中心几大模块。

服务性能

在服务化,或者微服务化过程中,首先考虑的问题就是性能问题,因为在服务化之后,会增加以下额外的性能开销:

  1. 客户端需要对消息进行序列化,主要占用CPU计算资源。
  2. 序列化时需要创建二进制数组,耗费JVM堆内存或者堆外内存。
  3. 客户端需要将序列化之后的二进制数组发送给服务端,占用网络带宽资源。
  4. 服务端读取到码流之后,需要将请求数据报反序列化成请求对象,占用CPU计算资源。
  5. 服务端通过反射的方式调用服务提供者实现类,反射本身对性能影响就比较大。
  6. 服务端将响应结果序列化,占用CPU计算资源。
  7. 服务端将应答码流发送给客户端,占用网络带宽资源。
  8. 客户端读取应答码流,反序列化成响应消息,占用CPU资源。

RPC框架高性能设计

要想提高效率,除了硬件的提升,主要考虑以下三个方面:

  1. I/O调度模型:同步阻塞I/O(BIO)还是非阻塞I/O(NIO)。
  2. 序列化框架的选择:文本协议、二进制协议或压缩二进制协议。
  3. 线程调度模型:串行调度还是并行调度,锁竞争还是无锁化算法。

IO调度现在主流的就是netty。
高性能序列化目前性能最好的是ice,google 的 pb协议,FB的thrift协议等
线程没啥好说的,肯定多线程了。当然也可以是AKKA(java)

总结

综上所述,服务化是现在大型互联网公司主流的架构模式,现在还有更流行的微服务,docker部署等等。

个人建议采用dubbox,集成其他各种协议,在该系列文章最后有各个协议的性能对比。

之所以建议采用dubbox是因为,dubbox有比价完善的服务治理模型,其包含ZK注册中心,服务监控等,可以很方便的为我们服务。
虽然dubbo本身不支持多语言,但是我们可以集成其他的序列化协议,比如thrift、avro,使其可以支持多种入门语言,让部门间的协作沟通更加灵活方便

当然,在实际使用过程中,尤其是集成其他协议的过程中,肯定需要对协议本身有比较深入的了解,才能正确的使用。

motan

新浪微博开源的RPC框架

helloword示例直接去官网下载运行即可

github地址:https://github.com/weibocom/motan
文档地址:https://github.com/weibocom/motan/wiki/zh_quickstart
用户指南;https://github.com/weibocom/motan/wiki/zh_userguide

# grpc

中文版官方文档:gRPC 官方文档中文版

helloWord示例,我就是根据这个文章做的,写得挺详细的:rpc框架之gRPC 学习 – hello world

grpc原理: grpc原理分析

dubbo

dubbo 已经与12年年底停止维护升级,忽略

thrift

请参考我写的另一篇文章:thrift学习笔记(一) thrift简介及第一个helloword程序

dubbox

dubbox 是当当团队基于dubbo升级的一个版本。是一个分布式的服务架构,可直接用于生产环境作为SOA服务框架。
dubbo官网首页:http://dubbo.io/ 上面有详细的用户指南和官方文档,介绍的比较详细,这里不再赘述。

当当官方的github地址:https://github.com/dangdangdotcom/dubbox

升级为spring4.X(及其他依赖组件)版本dubbox的github的地
址:https://github.com/yjmyzz/dubbox

参考资料【博客:菩提树下的杨过 的文章写得非常全面,介绍的已经非常详细了】:
dubbox升级spring到4.x及添加log4j2支持
分布式服务框架 dubbo/dubbox 入门示例
dubbox 的各种管理和监管
dubbo/dubbox 增加原生thrift及avro支持

# 各个RPC框架性能比较

测试环境

jdk7
win7 64位
idea
个人笔记本配置:
这里写图片描述

person对象:

private int age;
private String name;
private boolean sex;
private int childrenCount;
  • 1
  • 2
  • 3
  • 4

测试数据,入参:

private int ageStart;
private int ageEnd;
  • 1
  • 2

返回值:

Person.PersonBuilder builder = Person.builder();
List<Person> list = new ArrayList<Person>();
for (short i = 0; i < 10; i++) {
    list.add(builder.age(i).childrenCount(i).name("test" + i).sex(true).build());
}
return list;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

各协议测试使用的配置

  • grpc


rpc getPersonList (yjmyzz.grpc.study.dto.QueryParameter) returns (yjmyzz.grpc.study.dto.PersonList) {}

  • motan

<motan:basicService export="demoMotan:8002"
group="motan-demo-rpc" accessLog="false" shareChannel="true" module="motan-demo-rpc"
application="myMotanDemo" registry="registry" id="serviceBasicConfig"/>

  • dubbox
<dubbo:protocol name="dubbo" serialization="kryo" optimizer="com.alibaba.dubbo.demo.SerializationOptimizerImpl"/>

<dubbo:service interface="com.alibaba.dubbo.demo.person.PersonService" ref="personService" protocol="dubbo"/>
  • 1
  • 2
  • 3
  • thrift

TNonblockingServer + TFramedTransport

测试结果


rgpc 100000 次NettyServer调用,耗时:53102毫秒,平均1883次/秒 【简单grpc】
rgpc 100000 次NettyServer调用,耗时:52138毫秒,平均1917次/秒 【简单grpc】
rgpc 100000 次NettyServer调用,耗时:51800毫秒,平均1930次/秒 【简单grpc】
rgpc 100000 次NettyServer调用,耗时:51313毫秒,平均1948次/秒 【简单grpc】

rgpc 100000 次NettyServer调用,耗时:56812毫秒,平均1760次/秒[2016-10-08 19:17:31] Dubbo service server started! 【dubbox.kryo】
rgpc 100000 次NettyServer调用,耗时:55133毫秒,平均1813次/秒[2016-10-08 19:18:42] Dubbo service server started!【dubbox.kryo】
rgpc 100000 次NettyServer调用,耗时:52280毫秒,平均1912次/秒[2016-10-08 19:20:01] Dubbo service server started! 【dubbox.kryo】

rgpc 100000 次NettyServer调用,耗时:44414毫秒,平均2251次/秒[2016-10-08 19:13:34] Dubbo service server started! 【dubbox.fst】
rgpc 100000 次NettyServer调用,耗时:44805毫秒,平均2231次/秒[2016-10-08 19:12:25] Dubbo service server started! 【dubbox.fst】
rgpc 100000 次NettyServer调用,耗时:46245毫秒,平均2162次/秒[2016-10-08 19:14:43] Dubbo service server started! 【dubbox.fst】

rgpc 100000 次NettyServer调用,耗时:12203毫秒,平均8194次/秒[2016-10-09 19:52:34] Dubbo service server started!【dubbox.thrift】
rgpc 100000 次NettyServer调用,耗时:14142毫秒,平均7071次/秒[2016-10-09 19:30:17] Dubbo service server started!【dubbox.thrift】
rgpc 100000 次NettyServer调用,耗时:13762毫秒,平均7266次/秒[2016-10-09 19:30:43] Dubbo service server started!【dubbox.thrift】


rgpc 100000 次NettyServer调用,耗时:44334毫秒,平均2255次/秒 【motan】
rgpc 100000 次NettyServer调用,耗时:37844毫秒,平均2642次/秒 【motan】
rgpc 100000 次NettyServer调用,耗时:39007毫秒,平均2563次/秒 【motan】
rgpc 100000 次NettyServer调用,耗时:38610毫秒,平均2590次/秒 【motan】
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

测试结果说明

使用的自己的笔记本电脑测试的,测试的方式可能不太专业,但能够说明问题。
通过上面结果可以看到,thrift的性能最好,而且是相当的好
  • 1
  • 2
  • 3

网上其他人做的测试

ice-dubbo-thrift-grpc性能测试对比
RPC框架的性能比较

总结

影响RPC性能的因素主要有:

  • 序列化性能
  • IO性能
  • 线程模式

序列化的话,肯定是Google的PB协议和thrift最好,IO和线程的话,先流行的性能比较好的都是采用多线程非阻塞IO。

grpc是Google出品,使用了PB协议,但是由于它出现的比较晚,还不怎么成熟,而且采用http协议,非常适合现在的微服务,不过性能上差了许多,而且像服务治理与监控都需要额外的开发工作,所以放弃grpc。
thrift和grpc一样,性能优越,但是开发难度相比较于dubbox和motan也是高了一点点,需要编写proto文件(其实对于程序员来说这算不上难度)。像服务治理与监控也是需要额外的开发工作。
dubbo比较老了,直接弃用。
dubbox和后来的motan都比较适合我们的团队。dubbox后来经过当当的开发,引入了rest风格的http协议,并且还支持kryo/fst/dubbo/thrift等其他协议,而且其他团队也在使用dubbo,集成方便,服务治理监控功能齐全,所以最终采用dubbox。

其实我个人而言还是喜欢thrift,毕竟性嫩优越,在大型分布式系统中,哪怕一点点性能提升累计在一起也是很可观的。不过再具体选型的过程中还要结合团队目前的状况和团队其他开发人员的接受程度进行取舍。

分布式数据库中间件对比

1. 什么是中间件

传统的架构模式就是 应用连接数据库直接对数据进行访问,这种架构特点就是简单方便。

但是随着目前数据量不断的增大我们就遇到了问题:

  • 单个表数据量太大
  • 单个库数据量太大
  • 单台数据量服务器压力很大
  • 读写速度遇到瓶颈

当面临以上问题时,我们会想到的第一种解决方式就是 向上扩展(scale up) 简单来说就是不断增加硬件性能。这种方式只能暂时解决问题,当业务量不断增长时还是解决不了问题。特别是淘宝,facebook,youtube这种业务成线性,甚至指数级上升的情况

此时我们不得不依赖于第二种方式: 水平扩展 。 直接增加机器,把数据库放到不同服务器上,在应用到数据库之间加一个proxy进行路由,这样就可以解决上面的问题了。

2. 中间件与读写分离

很多人都会把中间件认为是读写分离,其实读写分离只是中间件可以提供的一种功能,最主要的功能还是在于他可以 分库分表 ,下面是一个读写分离的示意图:

分布式数据库中间件对比总结

上面的图可以看出,红线代表写请求,绿线代表读请求。这就是一个简单的读写分离,下面我们在看看分库分表中间件。

分布式数据库中间件对比总结

上面这幅图就可以看出中间件作用,比如下面的这个SQL:

[sql] view plain copy

  1. <span class=“operator”><span class=“keyword”>select</span> * <span class=“keyword”>from</span> table_name <span class=“keyword”>where</span> id = <span class=“number”>1</span>;</span>

按照中间件分库分表算法,此SQL将发送到DB1节点,由DB1这个MySQL负责解析和获取id=1的数据,并通过中间件返回给客户端。而在读写分离结构中并没有这些分库分表规则, 他只能在众多读节点中load balance随机进行分发,它要求各个节点都要存放一份完整的数据。

3.各类中间件比较

目前市面上中间件种类很多种 先看下各种中间件背景:

分布式数据库中间件对比总结

Cobar:

阿里巴巴B2B开发的关系型分布式系统,管理将近3000个MySQL实例。 在阿里经受住了考验,后面由于作者的走开的原因cobar没有人维护 了,阿里也开发了tddl替代cobar。

MyCAT:

社区爱好者在阿里cobar基础上进行二次开发,解决了cobar当时存 在的一些问题,并且加入了许多新的功能在其中。目前MyCAT社区活 跃度很高,目前已经有一些公司在使用MyCAT。总体来说支持度比 较高,也会一直维护下去,

OneProxy:

数据库界大牛,前支付宝数据库团队领导楼总开发,基于mysql官方 的proxy思想利用c进行开发的,OneProxy是一款商业收费的中间件, 楼总舍去了一些功能点,专注在性能和稳定性上。有朋友测试过说在 高并发下很稳定。

Vitess:

这个中间件是Youtube生产在使用的,但是架构很复杂。 与以往中间件不同,使用Vitess应用改动比较大要 使用他提供语言的API接口,我们可以借鉴他其中的一些设计思想。

Kingshard:

Kingshard是前360Atlas中间件开发团队的陈菲利用业务时间 用go语言开发的,目前参与开发的人员有3个左右, 目前来看还不是成熟可以使用的产品,需要在不断完善。

Atlas:

360团队基于mysql proxy 把lua用C改写。原有版本是支持分表, 目前已经放出了分库分表版本。在网上看到一些朋友经常说在高并 发下会经常挂掉,如果大家要使用需要提前做好测试。

MaxScale与MySQL Route:

这两个中间件都算是官方的吧,MaxScale是mariadb (MySQL原作者维护的一个版本)研发的,目前版本不支持分库分表。

MySQL Route是现在MySQL 官方Oracle公司发布出来的一个中间件。

这两个中间件后面也会跟进测试下,看下效果如何。

Proxy式架构和客户端式架构的优劣

 

  Proxy式架构 客户端式架构
优点 1, 集中式管理监控和升级维护方便

2, 解决连接数问题

1.     应用直连数据库性能高

2.     无需中间层集群,没有额外成本开销

劣势 1, 需要中间层集群,有硬件成本开销

2, 多一跳(hop)有一定性能损失

3,中间层做数据合并,需要做隔离机制

1.     客户端和应用耦合,管理监控和升级维护麻烦

2.     不解决连接数问题

 

消息中间件对比

rabbitMQ、activeMQ、zeroMQ、Kafka、Redis 比较

Kafka作为时下最流行的开源消息系统,被广泛地应用在数据缓冲、异步通信、汇集日志、系统解耦等方面。相比较于RocketMQ等其他常见消息系统,Kafka在保障了大部分功能特性的同时,还提供了超一流的读写性能。

针对Kafka性能方面进行简单分析,相关数据请参考:https://segmentfault.com/a/1190000003985468,下面介绍一下Kafka的架构和涉及到的名词:

  1. Topic:用于划分Message的逻辑概念,一个Topic可以分布在多个Broker上。
  2. Partition:是Kafka中横向扩展和一切并行化的基础,每个Topic都至少被切分为1个Partition。
  3. Offset:消息在Partition中的编号,编号顺序不跨Partition。
  4. Consumer:用于从Broker中取出/消费Message。
  5. Producer:用于往Broker中发送/生产Message。
  6. Replication:Kafka支持以Partition为单位对Message进行冗余备份,每个Partition都可以配置至少1个Replication(当仅1个Replication时即仅该Partition本身)。
  7. Leader:每个Replication集合中的Partition都会选出一个唯一的Leader,所有的读写请求都由Leader处理。其他Replicas从Leader处把数据更新同步到本地,过程类似大家熟悉的MySQL中的Binlog同步。
  8. Broker:Kafka中使用Broker来接受Producer和Consumer的请求,并把Message持久化到本地磁盘。每个Cluster当中会选举出一个Broker来担任Controller,负责处理Partition的Leader选举,协调Partition迁移等工作。
  9. ISR(In-Sync Replica):是Replicas的一个子集,表示目前Alive且与Leader能够“Catch-up”的Replicas集合。由于读写都是首先落到Leader上,所以一般来说通过同步机制从Leader上拉取数据的Replica都会和Leader有一些延迟(包括了延迟时间和延迟条数两个维度),任意一个超过阈值都会把该Replica踢出ISR。每个Partition都有它自己独立的ISR。

更多关于Kafka的数据,参考:https://segmentfault.com/a/1190000003985468

****************************************************

****************************************************

Kafka是一种分布式的,基于发布/订阅的消息系统。主要设计目标如下:
以时间复杂度为O(1)的方式提供消息持久化能力,即使对TB级以上数据也能保证常数时间复杂度的访问性能。
高吞吐率。即使在非常廉价的商用机器上也能做到单机支持每秒100K条以上消息的传输。
支持Kafka Server间的消息分区,及分布式消费,同时保证每个Partition内的消息顺序传输。
同时支持离线数据处理和实时数据处理。
Scale out:支持在线水平扩展。
RabbitMQ
RabbitMQ是使用Erlang编写的一个开源的消息队列,本身支持很多的协议:AMQP,XMPP, SMTP, STOMP,也正因如此,它非常重量级,更适合于企业级的开发。同时实现了Broker构架,这意味着消息在发送给客户端时先在中心队列排队。对路由,负载均衡或者数据持久化都有很好的支持。

Redis
Redis是一个基于Key-Value对的NoSQL数据库,开发维护很活跃。虽然它是一个Key-Value数据库存储系统,但它本身支持MQ功能,所以完全可以当做一个轻量级的队列服务来使用。对于RabbitMQ和Redis的入队和出队操作,各执行100万次,每10万次记录一次执行时间。测试数据分为128Bytes、512Bytes、1K和10K四个不同大小的数据。实验表明:入队时,当数据比较小时Redis的性能要高于RabbitMQ,而如果数据大小超过了10K,Redis则慢的无法忍受;出队时,无论数据大小,Redis都表现出非常好的性能,而RabbitMQ的出队性能则远低于Redis。

ZeroMQ
ZeroMQ号称最快的消息队列系统,尤其针对大吞吐量的需求场景。ZeroMQ能够实现RabbitMQ不擅长的高级/复杂的队列,但是开发人员需要自己组合多种技术框架,技术上的复杂度是对这MQ能够应用成功的挑战。ZeroMQ具有一个独特的非中间件的模式,你不需要安装和运行一个消息服务器或中间件,因为你的应用程序将扮演这个服务器角色。你只需要简单的引用ZeroMQ程序库,可以使用NuGet安装,然后你就可以愉快的在应用程序之间发送消息了。但是ZeroMQ仅提供非持久性的队列,也就是说如果宕机,数据将会丢失。其中,Twitter的Storm 0.9.0以前的版本中默认使用ZeroMQ作为数据流的传输(Storm从0.9版本开始同时支持ZeroMQ和Netty作为传输模块)。

ActiveMQ
ActiveMQ是Apache下的一个子项目。 类似于ZeroMQ,它能够以代理人和点对点的技术实现队列。同时类似于RabbitMQ,它少量代码就可以高效地实现高级应用场景。

Kafka/Jafka
Kafka是Apache下的一个子项目,是一个高性能跨语言分布式发布/订阅消息队列系统,而Jafka是在Kafka之上孵化而来的,即Kafka的一个升级版。具有以下特性:快速持久化,可以在O(1)的系统开销下进行消息持久化;高吞吐,在一台普通的服务器上既可以达到10W/s的吞吐速率;完全的分布式系统,Broker、Producer、Consumer都原生自动支持分布式,自动实现负载均衡;支持Hadoop数据并行加载,对于像Hadoop的一样的日志数据和离线分析系统,但又要求实时处理的限制,这是一个可行的解决方案。Kafka通过Hadoop的并行加载机制统一了在线和离线的消息处理。Apache Kafka相对于ActiveMQ是一个非常轻量级的消息系统,除了性能非常好之外,还是一个工作良好的分布式系统。

以上转自:http://www.infoq.com/cn/articles/kafka-analysis-part-1/

****************************************************

****************************************************

 

什么是Kafka?

引用官方原文:  “ Kafka is a distributed, partitioned, replicated commit log service. ”

它提供了一个非常特殊的消息机制,不同于传统的mq。

官网:https://kafka.apache.org


它与传统的mq区别?

  • 更快!单机上万TPS
  • 传统的MQ,消息被消化掉后会被mq删除,而kafka中消息被消化后不会被删除,而是到配置的expire时间后,才删除
  • 传统的MQ,消息的Offset是由MQ维护,而kafka中消息的Offset是由客户端自己维护
  • 分布式,把写入压力均摊到各个节点。可以通过增加节点降低压力

基本术语

为方便理解,我用对比传统MQ的方式阐述这些基本术语。

Producer
Consumer

这两个与传统的MQ一样,不解释了

Topic

Kafka中的topic其实对应传统MQ的channel,即消息管道,例如同一业务用同一根管道

Broker

集群中的KafkaServer,用来提供Partition服务

Partition

假如说传统的MQ,传输消息的通道(channel)是一条双车道公路,那么Kafka中,Topic就是一个N车道的高速公路。每个车道都可以行车,而每个车道就是Partition。

  • 一个Topic中可以有一个或多个partition。
  • 一个Broker上可以跑一个或多个Partition。集群中尽量保证partition的均匀分布,例如定义了一个有3个partition的topic,而只有两个broker,那么一个broker上跑两个partition,而另一个是1个。但是如果有3个broker,必然是3个broker上各跑一个partition。
  • Partition中严格按照消息进入的顺序排序
  • 一个从Producer发送来的消息,只会进入Topic的某一个Partition(除非特殊实现Producer要求消息进入所有Partition)
  • Consumer可以自己决定从哪个Partition读取数据
Offset

单个Partition中的消息的顺序ID,例如第一个进入的Offset为0,第二个为1,以此类推。传统的MQ,Offset是由MQ自己维护,而kafka是由client维护

Replica

Kafka从0.8版本开始,支持消息的HA,通过消息复制的方式。在创建时,我们可以指定一个topic有几个partition,以及每个partition有几个复制。复制的过程有同步和异步两种,根据性能需要选取。 正常情况下,写和读都是访问leader,只有当leader挂掉或者手动要求重新选举,kafka会从几个复制中选举新的leader。

Kafka会统计replica与leader的同步情况。当一个replica与leader数据相差不大,会被认为是一个”in-sync” replica。只有”in-sync” replica才有资格参与重新选举。

ConsumerGroup

一个或多个Consumer构成一个ConsumerGroup,一个消息应该只能被同一个ConsumerGroup中的一个Consumer消化掉,但是可以同时发送到不同ConsumerGroup。

通常的做法,一个Consumer去对应一个Partition。

传统MQ中有queuing(消息)和publish-subscribe(订阅)模式,Kafka中也支持:

  • 当所有Consumer具有相同的ConsumerGroup时,该ConsumerGroup中只有一个Consumer能收到消息,就是 queuing 模式
  • 当所有Consumer具有不同的ConsumerGroup时,每个ConsumerGroup会收到相同的消息,就是 publish-subscribe 模式

基本交互原理

每个Topic被创建后,在zookeeper上存放有其metadata,包含其分区信息、replica信息、LogAndOffset等
默认路径/brokers/topics/<topic_id>/partitions/<partition_index>/state

Producer可以通过zookeeper获得topic的broker信息,从而得知需要往哪写数据。

Consumer也从zookeeper上获得该信息,从而得知要监听哪个partition。


基本CLI操作

1. 创建Topic

./kafka-create-topic.sh –zookeeper 10.1.110.21:2181 –replica 2 –partition 3 –topic test

2. 查看Topic信息

./kafka-list-topic.sh –topic test –zookeeper 10.1.110.24:2181

3. 增加Partition

./kafka-add-partitions.sh –partition 4 –topic test –zookeeper 10.1.110.24:2181

更多命令参见:https://cwiki.apache.org/confluence/display/KAFKA/Replication+tools


创建一个Producer

Kafka提供了java api,Producer特别的简单,举传输byte[] 为例

Properties p = new Properties();
props.put("metadata.broker.list", "10.1.110.21:9092");
ProducerConfig config = new ProducerConfig(props);
Producer producer = new Producer<String, byte[]>(config);
producer.send(byte[] msg);

更具体的参见:https://cwiki.apache.org/confluence/display/KAFKA/0.8.0+Producer+Example


创建一个Consumer

Kafka提供了两种java的Consumer API:High Level Consumer和Simple Consumer

看上去前者似乎要更牛B一点,事实上,前者做了更多的封装,比后者要Simple的多……

具体例子我就不写了,参见

High Level Consumer: https://cwiki.apache.org/confluence/display/KAFKA/Consumer+Group+Example

Simple Consumer: https://cwiki.apache.org/confluence/display/KAFKA/0.8.0+SimpleConsumer+Example

摘自:http://www.tuicool.com/articles/ruUzum

 

****************************************************

****************************************************

如何保证kafka的高容错性?

  1. producer不使用批量接口,并采用同步模型持久化消息。
  2. consumer不采用批量化,每消费一次就更新offset
ActiveMq RabbitMq Kafka
producer容错,是否会丢数据 有ack模型,也有事务模型,保证至少不会丢数据。ack模型可能会有重复消息,事务模型则保证完全一致 批量形式下,可能会丢数据。 非批量形式下, 1. 使用同步模式,可能会有重复数据。 2. 异步模式,则可能会丢数据。
consumer容错,是否会丢数据 有ack模型,数据不会丢,但可能会重复处理数据。 批量形式下,可能会丢数据。非批量形式下,可能会重复处理数据。(ZK写offset是异步的)
架构模型 基于JMS协议 基于AMQP模型,比较成熟,但更新超慢。RabbitMQ的broker由Exchange,Binding,queue组成,其中exchange和binding组成了消息的路由键;客户端Producer通过连接channel和server进行通信,Consumer从queue获取消息进行消费(长连接,queue有消息会推送到consumer端,consumer循环从输入流读取数据)。rabbitMQ以broker为中心;有消息的确认机制 producer,broker,consumer,以consumer为中心,消息的消费信息保存的客户端consumer上,consumer根据消费的点,从broker上批量pull数据;无消息确认机制。
吞吐量 rabbitMQ在吞吐量方面稍逊于kafka,他们的出发点不一样,rabbitMQ支持对消息的可靠的传递,支持事务,不支持批量的操作;基于存储的可靠性的要求存储可以采用内存或者硬盘。 kafka具有高的吞吐量,内部采用消息的批量处理,zero-copy机制,数据的存储和获取是本地磁盘顺序批量操作,具有O(1)的复杂度,消息处理的效率很高
可用性 rabbitMQ支持miror的queue,主queue失效,miror queue接管 kafka的broker支持主备模式
集群负载均衡 rabbitMQ的负载均衡需要单独的loadbalancer进行支持 kafka采用zookeeper对集群中的broker、consumer进行管理,可以注册topic到zookeeper上;通过zookeeper的协调机制,producer保存对应topic的broker信息,可以随机或者轮询发送到broker上;并且producer可以基于语义指定分片,消息发送到broker的某分片上

参考:http://www.liaoqiqi.com/post/227

 

****************************************************

****************************************************

 

注:下文转载自:http://blog.csdn.net/linsongbin1/article/details/47781187

MQ框架非常之多,比较流行的有RabbitMq、ActiveMq、ZeroMq、kafka。这几种MQ到底应该选择哪个?要根据自己项目的业务场景和需求。下面我列出这些MQ之间的对比数据和资料。

第一部分:RabbitMQ,ActiveMq,ZeroMq比较

1、 TPS比较 一

ZeroMq 最好,RabbitMq 次之, ActiveMq 最差。这个结论来自于以下这篇文章。

http://blog.x-aeon.com/2013/04/10/a-quick-message-queue-benchmark-activemq-rabbitmq-hornetq-qpid-apollo/

测试环境:

Model: Dell Studio 1749

CPU: Intel Core i3 @ 2.40 GHz

RAM: 4 Gb

OS: Windows 7 64 bits

其中包括持久化消息和瞬时消息的测试。注意这篇文章里面提到的MQ,都是采用默认配置的,并无调优。

更多的统计图请参看我提供的文章url。

 

 

2、TPS比较

ZeroMq 最好,RabbitMq次之, ActiveMq最差。这个结论来自于一下这篇文章。http://www.cnblogs.com/amityat/archive/2011/08/31/2160293.html 

 

显示的是发送和接受的每秒钟的消息数。整个过程共产生1百万条1K的消息。测试的执行是在一个Windows Vista上进行的。

 

3、持久化消息比较

      zeroMq不支持,activeMq和rabbitMq都支持。持久化消息主要是指:MQ down或者MQ所在的服务器down了,消息不会丢失的机制。

 

4、技术点:可靠性、灵活的路由、集群、事务、高可用的队列、消息排序、问题追踪、可视化管理工具、插件系统、社区

 

RabbitMq最好,ActiveMq次之,ZeroMq最差。当然ZeroMq也可以做到,不过自己必须手动写代码实现,代码量不小。尤其是可靠性中的:持久性、投递确认、发布者证实和高可用性。

所以在可靠性和可用性上,RabbitMQ是首选,虽然ActiveMQ也具备,但是它性能不及RabbitMQ。

 

 5、高并发

从实现语言来看,RabbitMQ最高,原因是它的实现语言是天生具备高并发高可用的erlang语言。

 

 

总结:

按照目前网络上的资料,RabbitMQ、activeM、zeroMQ三者中,综合来看,RabbitMQ是首选。下面提供一篇文章,是淘宝使用RabbitMQ的心得,可以参看一些业务场景。

http://www.docin.com/p-462677246.html

 

 

第二部分:kafka和RabbitMQ的比较

 

关于这两种MQ的比较,网上的资料并不多,最权威的的是kafka的提交者写一篇文章。http://www.quora.com/What-are-the-differences-between-Apache-Kafka-and-RabbitMQ

里面提到的要点:

1、  RabbitMq比kafka成熟,在可用性上,稳定性上,可靠性上,RabbitMq超过kafka

2、  Kafka设计的初衷就是处理日志的,可以看做是一个日志系统,针对性很强,所以它并没有具备一个成熟MQ应该具备的特性

3、  Kafka的性能(吞吐量、tps)比RabbitMq要强,这篇文章的作者认为,两者在这方面没有可比性。

这里在附上两篇文章,也是关于kafka和RabbitMq之间的比较的:

1、http://www.mrhaoting.com/?p=139

2、http://www.liaoqiqi.com/post/227

 

总结:

两者对比后,我仍然是选择RabbitMq,性能其实是很强劲的,同时具备了一个成熟的MQ应该具有的特性,我们无需重新发明轮子。

ehcache memcache redis对比

 Ehcache

在java项目广泛的使用。它是一个开源的、设计于提高在数据从RDBMS中取出来的高花费、高延迟采取的一种缓存方案。正因为Ehcache具有健壮性(基于java开发)、被认证(具有apache 2.0  license)、充满特色(稍后会详细介绍),所以被用于大型复杂分布式web application的各个节点中。

什么特色?

1.  够快

Ehcache的发行有一段时长了,经过几年的努力和不计其数的性能测试,Ehcache终被设计于large, high concurrency systems.

2. 够简单

开发者提供的接口非常简单明了,从Ehcache的搭建到运用运行仅仅需要的是你宝贵的几分钟。其实很多开发者都不知道自己用在用Ehcache,Ehcache被广泛的运用于其他的开源项目

比如:hibernate

3.够袖珍

关于这点的特性,官方给了一个很可爱的名字small foot print ,一般Ehcache的发布版本不会到2M,V 2.2.3  才 668KB。

4. 够轻量

核心程序仅仅依赖slf4j这一个包,没有之一!

5.好扩展

Ehcache提供了对大数据的内存和硬盘的存储,最近版本允许多实例、保存对象高灵活性、提供LRU、LFU、FIFO淘汰算法,基础属性支持热配置、支持的插件多

6.监听器

缓存管理器监听器 (CacheManagerListener)和 缓存监听器(CacheEvenListener),做一些统计或数据一致性广播挺好用的

如何使用?

够简单就是Ehcache的一大特色,自然用起来just so easy!

贴一段基本使用代码

CacheManager manager = CacheManager.newInstance("src/config/ehcache.xml");
Ehcache cache = new Cache("testCache", 5000, false, false, 5, 2);
cacheManager.addCache(cache);
代码中有个ehcache.xml文件,现在来介绍一下这个文件中的一些属性
  1.        name:缓存名称。
  2.        maxElementsInMemory:缓存最大个数。
  3.        eternal:对象是否永久有效,一但设置了,timeout将不起作用。
  4.        timeToIdleSeconds:设置对象在失效前的允许闲置时间(单位:秒)。仅当eternal=false对象不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大。
  5.        timeToLiveSeconds:设置对象在失效前允许存活时间,最大时间介于创建时间和失效时间之间。仅当eternal=false对象不是永久有效时使用,默认是0.,也就是对象存活时 间无穷大。
  6.        overflowToDisk:当内存中对象数量达到maxElementsInMemory时,Ehcache将会对象写到磁盘中。
  7.        diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区。
  8.        maxElementsOnDisk:硬盘最大缓存个数。
  9.        diskPersistent:是否缓存虚拟机重启期数据 Whether the disk store persists between restarts of the Virtual Machine. The default value is false.
  10.        diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒。
  11.        memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU。你可以设置为 FIFO或是LFU。
  12.        clearOnFlush:内存数量最大时是否清除。

 

memcache

memcache 是一种高性能、分布式对象缓存系统,最初设计于缓解动态网站数据库加载数据的延迟性,你可以把它想象成一个大的内存HashTable,就是一个key-value键值缓存。Danga Interactive为了LiveJournal所发展的,以BSD license释放的一套开放源代码软件。

1.依赖

memcache C语言所编写,依赖于最近版本的GCC和libevent。GCC是它的编译器,同事基于libevent做socket io。在安装memcache时保证你的系统同事具备有这两个环境。

2.多线程支持

memcache支持多个cpu同时工作,在memcache安装文件下有个叫threads.txt中特别说明,By default, memcached is compiled as a single-threaded application.默认是单线程编译安装,如果你需要多线程则需要修改./configure –enable-threads,为了支持多核系统,前提是你的系统必须具有多线程工作模式。开启多线程工作的线程数默认是4,如果线程数超过cpu数容易发生操作死锁的概率。结合自己业务模式选择才能做到物尽其用。

3.高性能

通过libevent完成socket 的通讯,理论上性能的瓶颈落在网卡上。

简单安装:

1.分别把memcached和libevent下载回来,放到 /tmp 目录下:

# cd /tmp

# wget http://www.danga.com/memcached/dist/memcached-1.2.0.tar.gz

# wget http://www.monkey.org/~provos/libevent-1.2.tar.gz

2.先安装libevent:

# tar zxvf libevent-1.2.tar.gz

# cd libevent-1.2

# ./configure -prefix=/usr

# make (如果遇到提示gcc 没有安装则先安装gcc)

# make install

3.测试libevent是否安装成功:

# ls -al /usr/lib | grep libevent

lrwxrwxrwx 1 root root 21 11?? 12 17:38 libevent-1.2.so.1 -> libevent-1.2.so.1.0.3

-rwxr-xr-x 1 root root 263546 11?? 12 17:38 libevent-1.2.so.1.0.3

-rw-r-r- 1 root root 454156 11?? 12 17:38 libevent.a

-rwxr-xr-x 1 root root 811 11?? 12 17:38 libevent.la

lrwxrwxrwx 1 root root 21 11?? 12 17:38 libevent.so -> libevent-1.2.so.1.0.3

还不错,都安装上了。

4.安装memcached,同时需要安装中指定libevent的安装位置:

# cd /tmp

# tar zxvf memcached-1.2.0.tar.gz

# cd memcached-1.2.0

# ./configure -with-libevent=/usr

# make

# make install

如果中间出现报错,请仔细检查错误信息,按照错误信息来配置或者增加相应的库或者路径。

安装完成后会把memcached放到 /usr/local/bin/memcached ,

5.测试是否成功安装memcached:

# ls -al /usr/local/bin/mem*

-rwxr-xr-x 1 root root 137986 11?? 12 17:39 /usr/local/bin/memcached

-rwxr-xr-x 1 root root 140179 11?? 12 17:39 /usr/local/bin/memcached-debug

启动memcache服务

 

启动Memcached服务:

1.启动Memcache的服务器端:

# /usr/local/bin/memcached -d -m 8096 -u root -l 192.168.77.105 -p 12000 -c 256 -P /tmp/memcached.pid

-d选项是启动一个守护进程,

-m是分配给Memcache使用的内存数量,单位是MB,我这里是8096MB,

-u是运行Memcache的用户,我这里是root,

-l是监听的服务器IP地址,如果有多个地址的话,我这里指定了服务器的IP地址192.168.77.105,

-p是设置Memcache监听的端口,我这里设置了12000,最好是1024以上的端口,

-c选项是最大运行的并发连接数,默认是1024,我这里设置了256,按照你服务器的负载量来设定,

-P是设置保存Memcache的pid文件,我这里是保存在 /tmp/memcached.pid,

2.如果要结束Memcache进程,执行:

# cat /tmp/memcached.pid 或者 ps -aux | grep memcache   (找到对应的进程id号)

# kill 进程id号

也可以启动多个守护进程,不过端口不能重复。

memcache 的连接

telnet  ip   port

注意连接之前需要再memcache服务端把memcache的防火墙规则加上

-A RH-Firewall-1-INPUT -m state –state NEW -m tcp -p tcp –dport 3306 -j ACCEPT

重新加载防火墙规则

service iptables restart

OK ,现在应该就可以连上memcache了

在客户端输入stats 查看memcache的状态信息

pid              memcache服务器的进程ID

uptime      服务器已经运行的秒数

time           服务器当前的unix时间戳

version     memcache版本

pointer_size         当前操作系统的指针大小(32位系统一般是32bit)

rusage_user          进程的累计用户时间

rusage_system    进程的累计系统时间

curr_items            服务器当前存储的items数量

total_items           从服务器启动以后存储的items总数量

bytes                       当前服务器存储items占用的字节数

curr_connections        当前打开着的连接数

total_connections        从服务器启动以后曾经打开过的连接数

connection_structures          服务器分配的连接构造数

cmd_get get命令          (获取)总请求次数

cmd_set set命令          (保存)总请求次数

get_hits          总命中次数

get_misses        总未命中次数

evictions     为获取空闲内存而删除的items数(分配给memcache的空间用满后需要删除旧的items来得到空间分配给新的items)

bytes_read    读取字节数(请求字节数)

bytes_written     总发送字节数(结果字节数)

limit_maxbytes     分配给memcache的内存大小(字节)

threads         当前线程数


redis

redis是在memcache之后编写的,大家经常把这两者做比较,如果说它是个key-value store 的话但是它具有丰富的数据类型,我想暂时把它叫做缓存数据流中心,就像现在物流中心那样,order、package、store、classification、distribute、end。现在还很流行的LAMP PHP架构 不知道和 redis+mysql 或者 redis + mongodb的性能比较(听群里的人说mongodb分片不稳定)。

先说说reidis的特性

1. 支持持久化

redis的本地持久化支持两种方式:RDB和AOF。RDB 在redis.conf配置文件里配置持久化触发器,AOF指的是redis没增加一条记录都会保存到持久化文件中(保存的是这条记录的生成命令),如果不是用redis做DB用的话还会不要开AOF ,数据太庞大了,重启恢复的时候是一个巨大的工程!

2.丰富的数据类型

redis 支持 String 、Lists、sets、sorted sets、hashes 多种数据类型,新浪微博会使用redis做nosql主要也是它具有这些类型,时间排序、职能排序、我的微博、发给我的这些功能List 和 sorted set

的强大操作功能息息相关

3.高性能

这点跟memcache很想象,内存操作的级别是毫秒级的比硬盘操作秒级操作自然高效不少,较少了磁头寻道、数据读取、页面交换这些高开销的操作!这也是NOSQL冒出来的原因吧,应该是高性能

是基于RDBMS的衍生产品,虽然RDBMS也具有缓存结构,但是始终在app层面不是我们想要的那么操控的。

4.replication

redis提供主从复制方案,跟mysql一样增量复制而且复制的实现都很相似,这个复制跟AOF有点类似复制的是新增记录命令,主库新增记录将新增脚本发送给从库,从库根据脚本生成记录,这个过程非常快,就看网络了,一般主从都是在同一个局域网,所以可以说redis的主从近似及时同步,同事它还支持一主多从,动态添加从库,从库数量没有限制。 主从库搭建,我觉得还是采用网状模式,如果使用链式(master-slave-slave-slave-slave·····)如果第一个slave出现宕机重启,首先从master 接收 数据恢复脚本,这个是阻塞的,如果主库数据几TB的情况恢复过程得花上一段时间,在这个过程中其他的slave就无法和主库同步了。

5.更新快

这点好像从我接触到redis到目前为止 已经发了大版本就4个,小版本没算过。redis作者是个非常积极的人,无论是邮件提问还是论坛发帖,他都能及时耐心的为你解答,维护度很高。有人维护的话,让我们用的也省心和放心。目前作者对redis 的主导开发方向是redis的集群方向。

redis的安装

redis的安装其实还是挺简单的,总的来说就三步:下载tar包,解压tar包,安装。

不过最近我在2.6.7后用centos 5.5 32bit 时碰到一个安装问题,下面我就用图片分享下安装过程碰到的问题,在redis 文件夹内执行make时有个如下的错 undefined reference to ‘__sync_add_and_fetch_4’

上网找了了好多最后在  https://github.com/antirez/redis/issues/736 找到解决方案,write CFLAGS= -march=i686 on src/Makefile head!

记得要把刚安装失败的文件删除,重新解压新的安装文件,修改Makefile文件,再make安装。就不会发现原来那个错误了

关于redis的一些属性注释和基本类型操作在上一篇redis 的开胃菜有详细的说明,这里就不再重复累赘了(实质是想偷懒 ,哈哈!)

最后,把memcache和redis放在一起不得不会让人想到两者的比较,谁快谁好用啊,群里面已经为这个事打架很久了,我就把我看到的在这里跟大家分享下。

在别人发了一个memcache性能比redis好很多后,redis 作者 antirez 发表了一篇博文,主要是说到如何给redis 和 memcache 做压力测试,文中讲到有个人说许多开源软件都应该丢进厕所,因为他们的压力测试脚本太2了,作者对这个说明了一番。redis  vs  memcache is  definitely an apple to apple comparison。 呵呵,很明确吧,两者的比较是不是有点鸡蛋挑骨头的效果,作者在相同的运行环境做了三次测试取多好的值,得到的结果如下图:

需要申明的是此次测试在单核心处理的过程的数据,memcache是支持多核心多线程操作的(默认没开)所以在默认情况下上图具有参考意义,若然则memcache快于redis。那为什么redis不支持多线程多核心处理呢?作者也发表了一下自己的看法,首先是多线程不变于bug的修复,其实是不易软件的扩展,还有数据一致性问题因为redis所有的操作都是原子操作,作者用到一个词nightmare 噩梦,呵呵!  当然不支持多线程操作,肯定也有他的弊端的比如性能想必必然差,作者从2.2版本后专注redis cluster的方向开发来缓解其性能上的弊端,说白了就是纵向不行,横向提高。

SSDB 和 Redis 的优缺点各有哪些

redis是内存数据库,ssdb是面向硬盘的存储,二者在存储格式和读写方式上有着根本的不同。前面回答里提到的zrevrange 和 zrevrangebyscore慢,而zrange 和 zrangebyscore 还能接受,其实就是说逆序遍历比顺序遍历慢得多,其根本原因就在于逆序遍历的时候,会多一个“记录头部”定位的过程,需要不断尝试去定位到两条记录的“分界点”,而顺序遍历的时候则不需要,因为读完一条记录直接就到了下一条记录的“分界点”,并且像rocksdb之类的存储引擎都会把数据长度保存在记录的元信息里,只需要按长度读取数据就可以了。redis则不存在类似问题,因为它是完全基于指针和偏移量在内存中进行寻址来读取数据的,寻址效率高了好多个数量级。

ssdb貌似就是一个个人项目,但代码质量还是不错的,整个设计思想比较简洁。

ssdb的主从复制效率很低。binlog和数据是分开存储的,日志冗余较多,由于ssdb本身要在多线程条件下才能发挥出更好的性能,为了使多个线程在写入binlog时能保证操作顺序和原子性,ssdb的binlog数据结构上用了一把全局锁,可想而知,这里的锁竞争会很影响性能。

另外,ssdb默认也没有集群管理的支持。

ssdb的好处,和swapdb(github.com/JRHZRD/swapd)一样,都可以省钱。如果有需要,可以尝试swapdb,它结合了redis和ssdb的优点,实现了基于LFU的热度统计和冷热交换,做到了低成本和高性能的高平衡。

redis的好处,那就多了。缺点就是纯内存,比用SSD花钱。

Ehcache配置参数简介

< defaultCache 

     maxElementsInMemory = “10000”
maxElementsOnDisk = “0”
eternal = “true”
overflowToDisk = “true”
diskPersistent = “false”
timeToIdleSeconds = “0”
timeToLiveSeconds = “0”
diskSpoolBufferSizeMB = “50”
diskExpiryThreadIntervalSeconds = “120”
memoryStoreEvictionPolicy = “LFU”
/> 
< cache   name = “myCache”
maxElementsInMemory = “100”
maxElementsOnDisk = “0”
eternal = “false”
overflowToDisk = “false”
diskPersistent = “false”
timeToIdleSeconds = “120”
timeToLiveSeconds = “120”
diskSpoolBufferSizeMB = “50”
diskExpiryThreadIntervalSeconds = “120”
memoryStoreEvictionPolicy = “FIFO”
/> 
diskStore :指定数据存储位置,可指定磁盘中的文件夹位置
defaultCache : 默认的管理策略

以下属性是必须的:
name: Cache的名称,必须是唯一的(ehcache会把这个cache放到HashMap里)。
maxElementsInMemory: 在内存中缓存的element的最大数目。
maxElementsOnDisk: 在磁盘上缓存的element的最大数目,默认值为0,表示不限制。
eternal: 设定缓存的elements是否永远不过期。如果为true,则缓存的数据始终有效,如果为false那么还要根据timeToIdleSeconds,timeToLiveSeconds判断。
overflowToDisk: 如果内存中数据超过内存限制,是否要缓存到磁盘上。
以下属性是可选的:
timeToIdleSeconds: 对象空闲时间,指对象在多长时间没有被访问就会失效。只对eternal为false的有效。默认值0,表示一直可以访问。
timeToLiveSeconds: 对象存活时间,指对象从创建到失效所需要的时间。只对eternal为false的有效。默认值0,表示一直可以访问。
diskPersistent: 是否在磁盘上持久化。指重启jvm后,数据是否有效。默认为false。
diskExpiryThreadIntervalSeconds: 对象检测线程运行时间间隔。标识对象状态的线程多长时间运行一次。
diskSpoolBufferSizeMB: DiskStore使用的磁盘大小,默认值30MB。每个cache使用各自的DiskStore。 
memoryStoreEvictionPolicy:
 如果内存中数据超过内存限制,向磁盘缓存时的策略。默认值LRU,可选FIFO、LFU。
缓存的3 种清空策略 :
FIFO ,first in first out (先进先出).
LFU , Less Frequently Used (最少使用).意思是一直以来最少被使用的。缓存的元素有一个hit 属性,hit 值最小的将会被清出缓存。
LRU ,Least Recently Used(最近最少使用). (ehcache 默认值).缓存的元素有一个时间戳,当缓存容量满了,而又需要腾出地方来缓存新的元素的时候,那么现有缓存元素中时间戳离当前时间最远的元素将被清出缓存。

Saturn

Saturn使用使用Java语言开发、分布式的架构,使用ZooKeeper进行分布式协调的工作。它主要是为了解决大规模任务调度的相关的高可用、分片、配置和监控的问题。基于的场景是这样的:业务开发团队希望我们为业务开发者提供这样一个公共的任务调度接入平台,业务团队将业务逻辑封装成任务,发布到这个公共的平台,然后在这个平台进行任务的配置、资源的配置、任务的控制和管理、任务执行状态监控、异常任务展现和告警。业务团队不用关注资源的调度,任务的触发,分片的划分,以及高可用、负载均衡、超时控制等功能的实现。这个平台还要支持多种作业类型(Java、PHP、Shell、HTTP服务),以及支持多种触发规则(定时触发,消息触发)。另外业务团队还要求接入非常方便,开发任务非常简捷,版本升级简单,不会额外引入其它第三方依赖包。

定时任务的分布式调度

单机定式任务调度的问题

在很多应用系统中我们常常要定时执行一些任务。比如,订单系统的超时状态判断、缓存数据的定时更新、定式给用户发邮件,甚至是一些定期计算的报表等等。常见的处理方式有线程的while(true) 和sleep组合、使用Timer定时器触发任务又或者是使用quartz框架。貌似这些方法可以完美的解决方案,为什么还需要分布式呢?主要有如下两点原因:

1.高可用:单机版的定式任务调度只能在一台机器上运行,如果程序或者系统出现异常就会导致功能不可用。虽然可以在单机程序实现的足够稳定,但始终有机会遇到非程序引起的故障,而这个对于一个系统的核心功能来说是不可接受的。

2.单机处理极限:原本1分钟内需要处理1万个订单,但是现在需要1分钟内处理10万个订单;原来一个统计需要1小时,现在业务方需要10分钟就统计出来。你也许会说,你也可以多线程、单机多进程处理。的确,多线程并行处理可以提高单位时间的处理效率,但是单机能力毕竟有限(主要是CPU、内存和磁盘),始终会有单机处理不过来的情况。

这个时候就需要分布式的定时任务来实现了。业内常用的分布式定式任务解决方案主要有quartz、淘宝的TBSchedule和当当的elastic-job。

quartz的集群解决方案

quartz的单机版本大家应该比较熟悉,它的集群方案是使用数据库来实现的。集群架构如下:

上图三个节点在数据库中都拥有同一份Job定义,如果某一个节点失效,那么Job会在其他节点上执行。由于三个节点上的Job执行代码是一样的,那么怎么保证只有在一台机器上触发呢?答案是使用了数据库锁。在quartz的集群解决方案里有张表scheduler_locks,quartz采用了悲观锁的方式对triggers表进行行加锁,以保证任务同步的正确性。一旦某一个节点上面的线程获取了该锁,那么这个Job就会在这台机器上被执行,同时这个锁就会被这台机器占用。同时另外一台机器也会想要触发这个任务,但是锁已经被占用了,就只能等待,直到这个锁被释放。之后会看trigger状态,如果已经被执行了,则不会执行了。

简单地说,quartz的分布式调度策略是以数据库为边界资源的一种异步策略。各个调度器都遵守一个基于数据库锁的操作规则从而保证了操作的唯一性。同时多个节点的异步运行保证了服务的可靠。但这种策略有自己的局限性:集群特性对于高CPU使用率的任务效果很好,但是对于大量的短任务,各个节点都会抢占数据库锁,这样就出现大量的线程等待资源。这种情况随着节点的增加会越来越严重。

另外,quartz的分布式只是解决了高可用的问题,并没有解决任务分片的问题,还是会有单机处理的极限。

TBSchedule

TBSchedule是一款非常优秀的高性能分布式调度框架,广泛应用于阿里巴巴、淘宝、支付宝、京东、聚美、汽车之家、国美等很多互联网企业的流程调度系统。tbschedule在时间调度方面虽然没有quartz强大,但是它支持分片功能。和quartz不同的是,tbschedule使用ZooKeeper来实现任务调度的高可用和分片。

TBSchedule的分布式机制是通过灵活的Sharding方式实现的,分片的规则由客户端决定,比如可以按所有数据的ID按10取模分片、按月份分片等等。TBSchedule的宿主服务器可以进行动态扩容和资源回收,这个特点主要是因为它后端依赖的ZooKeeper,这里的ZooKeeper对于TBSchedule来说是一个NoSQL,用于存储策略、任务、心跳信息数据,它的数据结构类似文件系统的目录结构,它的节点有临时节点、持久节点之分。调度引擎启动后,随着业务量数据量的增多,当前Cluster可能不能满足目前的处理需求,那么就需要增加服务器数量,一个新的服务器上线后会在ZooKeeper中创建一个代表当前服务器的一个唯一性路径(临时节点),并且新上线的服务器会和ZooKeeper保持长连接,当通信断开后,节点会自动摘除。

TBSchedule会定时扫描当前服务器的数量,重新进行任务分配。TBSchedule不仅提供了服务端的高性能调度服务,还提供了一个scheduleConsole的war包,随着宿主应用的部署直接部署到服务器,可以通过web的方式对调度的任务、策略进行监控管理,以及实时更新调整。

elastic-job

Elastic-Job当当开源的分布式调度解决方案,由两个相互独立的子项目Elastic-Job-Lite和Elastic-Job-Cloud组成。Elastic-Job-Lite定位为轻量级无中心化解决方案,使用jar包的形式提供分布式任务的协调服务。一般我们只要使用Elastic-Job-Lite就好。

Elastic-Job-Lite并没有宿主程序,而是基于部署作业框架的程序在到达相应时间点时各自触发调度。它的开发也比较简单,引用Jar包实现一些方法即可,最后编译成Jar包运行。Elastic-Job-Lite的分布式部署全靠ZooKeeper来同步状态和原数据。实现高可用的任务只需将分片总数设置为1,并把开发的Jar包部署于多个服务器上执行,任务将会以1主N从的方式执行。一旦本次执行任务的服务器崩溃,其他执行任务的服务器将会在下次作业启动时选择一个替补执行。如果开启了失效转移,那么功能效果更好,可以保证在本次作业执行时崩溃,备机之一立即启动替补执行。

Elastic-Job-Lite的任务分片也是通过ZooKeeper来实现,Elastic-Job并不直接提供数据处理的功能,框架只会将分片项分配至各个运行中的作业服务器,开发者需要自行处理分片项与真实数据的对应关系。框架也预置了一些分片策略:平均分配算法策略,作业名哈希值奇偶数算法策略,轮转分片策略。同时也提供了自定义分片策略的接口。

另外Elastic-Job-Lite还提供了一个任务监控和管理界面:Elastic-Job-Lite-Console。它和Elastic-Job-Lite是两个完全不关联的应用程序,使用ZooKeeper来交换数据,管理人员可以通过这个界面查看、监控和管理Elastic-Job-Lite的任务,必要的时候还能手动触发任务。

elastic-job-lite-console

 

elastic-job结合了quartz非常优秀的时间调度功能,并且利用ZooKeeper实现了灵活的分片策略。除此之外,还加入了大量实用的监控和管理功能,以及其开源社区活跃、文档齐全、代码优雅等优点,是分布式任务调度框架的推荐选择。

 

Saturn

Saturn是唯品会在github开源的一款分布式任务调度产品。它是基于当当elastic-job来开发的,其上完善了一些功能和添加了一些新的feature。目前在github上开源大半年,470个star。Saturn的任务可以用多种语言开发比如python、Go、Shell、Java、Php。其在唯品会内部已经发部署350+个节点,每天任务调度4000多万次。同时,管理和统计也是它的亮点。

image

image

 

有兴趣的同学可以从https://github.com/vipshop/Saturn 上了解更加详细的信息。

sharding-JDBC

分库分表

环境:

db:mysql

部署方式:一主二从

插入

public class PreciseShardingTableAlgorithm implements PreciseShardingAlgorithm<String> {
    private static final Logger logger = LoggerFactory.getLogger(PreciseShardingTableAlgorithm.class);

    @Override
    public String doSharding(Collection<String> availableTargetNames, PreciseShardingValue<String> shardingValue) {
        for (String each : availableTargetNames) {
            logger.info("LogicTableName: {},ColumnName: {}, Value(id): {}",
                    shardingValue.getLogicTableName(),
                    shardingValue.getColumnName(),
                    shardingValue.getValue()
            );
            if (each.endsWith(shardingValue.getValue().substring(0, 6))) {
                logger.info("ActualLogicTableName: {},LogicTableName: {},ColumnName: {}, Value(id): {}",
                        each,
                        shardingValue.getLogicTableName(),
                        shardingValue.getColumnName(),
                        shardingValue.getValue()
                );
                return each;
            }
        }
        throw new UnsupportedOperationException();
    }

<?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:context="http://www.springframework.org/schema/context"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:sharding="http://shardingjdbc.io/schema/shardingjdbc/sharding"
       xmlns:master-slave="http://shardingjdbc.io/schema/shardingjdbc/masterslave"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                        http://www.springframework.org/schema/beans/spring-beans.xsd
                        http://www.springframework.org/schema/context
                        http://www.springframework.org/schema/context/spring-context.xsd
                        http://www.springframework.org/schema/tx
                        http://www.springframework.org/schema/tx/spring-tx.xsd
                        http://shardingjdbc.io/schema/shardingjdbc/sharding
                        http://shardingjdbc.io/schema/shardingjdbc/sharding/sharding.xsd
                        http://shardingjdbc.io/schema/shardingjdbc/masterslave
                        http://shardingjdbc.io/schema/shardingjdbc/masterslave/master-slave.xsd">


    <import resource="multids.xml"/>
    <bean id="randomStrategy"
          class="io.shardingjdbc.core.api.algorithm.masterslave.RandomMasterSlaveLoadBalanceAlgorithm"/>

    <master-slave:data-source id="ds_ms_0" master-data-source-name="ds_master_0"
                              slave-data-source-names="ds_master_0_slave_0, ds_master_0_slave_1"
                              strategy-ref="randomStrategy"/>


    <sharding:standard-strategy id="orderTableStrategy" sharding-column="orderId"
                                precise-algorithm-class="XXX.PreciseShardingTableAlgorithm"/>
    <sharding:complex-strategy id="orderTableStrategySelect" sharding-columns="orderId"
                                algorithm-class="XXX.ComplexKeysShardingTableAlgorithm"/>

    <sharding:data-source id="examplds">
        <sharding:sharding-rule data-source-names="ds_ms_0">
            <sharding:table-rules>
                <sharding:table-rule logic-table="XXX"
                                     actual-data-nodes="ds_ms_0.XXX${4..6}"
                                     table-strategy-ref="orderTableStrategy"/>
                <sharding:table-rule logic-table="XXX"
                                     actual-data-nodes="ds_ms_0.XXX${4..6}"
                                     table-strategy-ref="orderTableStrategySelect"/>
            </sharding:table-rules>
        </sharding:sharding-rule>
        <sharding:props>
            <prop key="sql.show">
                true
            </prop>
        </sharding:props>
    </sharding:data-source>
</beans>
<?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:context="http://www.springframework.org/schema/context"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:sharding="http://shardingjdbc.io/schema/shardingjdbc/sharding"
       xmlns:master-slave="http://shardingjdbc.io/schema/shardingjdbc/masterslave"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                        http://www.springframework.org/schema/beans/spring-beans.xsd
                        http://www.springframework.org/schema/context
                        http://www.springframework.org/schema/context/spring-context.xsd
                        http://www.springframework.org/schema/tx
                        http://www.springframework.org/schema/tx/spring-tx.xsd
                        http://shardingjdbc.io/schema/shardingjdbc/sharding
                        http://shardingjdbc.io/schema/shardingjdbc/sharding/sharding.xsd
                        http://shardingjdbc.io/schema/shardingjdbc/masterslave
                        http://shardingjdbc.io/schema/shardingjdbc/masterslave/master-slave.xsd">

    <bean id="ds_master_0" class="com.zaxxer.hikari.HikariDataSource" destroy-method="close">
        <property name="driverClassName" value="${jdbc.XXX.ds_master_0.driver}"/>
        <property name="jdbcUrl" value="${jdbc.XXX.ds_master_0.url}"/>
        <property name="username" value="${jdbc.XXX.ds_master_0.username}"/>
        <property name="password" value="${jdbc.XXX.ds_master_0.password}"/>
        <property name="maximumPoolSize" value="${jdbc.XXX.ds_master_0.maximumPoolSize}"/>
    </bean>

    <bean id="ds_master_0_slave_0" class="com.zaxxer.hikari.HikariDataSource" destroy-method="close">
        <property name="driverClassName" value="${jdbc.XXX.ds_master_0_slave_0.driver}"/>
        <property name="jdbcUrl" value="${jdbc.XXX.ds_master_0_slave_0.url}"/>
        <property name="username" value="${jdbc.XXX.ds_master_0_slave_0.username}"/>
        <property name="password" value="${jdbc.XXX.ds_master_0_slave_0.password}"/>
        <property name="maximumPoolSize" value="${jdbc.XXX.ds_master_0_slave_0.maximumPoolSize}"/>
    </bean>

    <bean id="ds_master_0_slave_1" class="com.zaxxer.hikari.HikariDataSource" destroy-method="close">
        <property name="driverClassName" value="${jdbc.XXX.ds_master_0_slave_1.driver}"/>
        <property name="jdbcUrl" value="${jdbc.XXX.ds_master_0_slave_1.url}"/>
        <property name="username" value="${jdbc.XXX.ds_master_0_slave_1.username}"/>
        <property name="password" value="${jdbc.XXX.ds_master_0_slave_1.password}"/>
        <property name="maximumPoolSize" value="${jdbc.XXX.ds_master_0_slave_1.maximumPoolSize}"/>
    </bean>
</beans>
<?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:context="http://www.springframework.org/schema/context"
   xmlns:util="http://www.springframework.org/schema/util" 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-4.0.xsd 
         http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
         http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.0.xsd
         http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
         http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd">

    <description>Spring DataSource configuration</description>
    <import resource="sharding.xml"/>

    <!-- transaction manager -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="examplds" />
    </bean>

    <tx:annotation-driven transaction-manager="transactionManager" mode="aspectj"/>

    <!-- mybatis factory -->
    <bean id="zzexampleSqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="examplds"/>
        <property name="configLocation" value="classpath:mybatis.xml"/>
        <property name="mapperLocations" value="classpath*:mapper/**/*.xml"/>
    </bean>

    <!-- scan mappers under the dao dir -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="sqlSessionFactoryBeanName" value="zzexampleSqlSessionFactory"/>
        <property name="basePackage" value="XXX.mapper"/>
    </bean>

</beans>