Quantcast
Channel: PayMoon贝明实验室
Viewing all 130 articles
Browse latest View live

构建Java 高性能编程

$
0
0
最近的机器内存又爆满了,除了新增机器内存外,还应该好好review一下我们的代码,有很多代码编写过于随意化,这些不好的习惯或对程序语言的不了解是应该好好打压打压了。 本文参考网络资源总结的一些在Java编程中尽可能要做到的一些地方。
  1. 尽量在合适的场合使用单例 使用单例可以减轻加载的负担,缩短加载的时间,提高加载的效率,但并不是所有地方都适用于单例,简单来说,单例主要适用于以下三个方面: (1)控制资源的使用,通过线程同步来控制资源的并发访问; (2)控制实例的产生,以达到节约资源的目的; (3)控制数据共享,在不建立直接关联的条件下,让多个不相关的进程或线程之间实现通信。
  2. 尽量避免随意使用静态变量 要知道,当某个对象被定义为stataic的变量所引用,那么GC通常是不会回收这个对象所占有的内存,如 public class A{ static B b = new B();} 此时静态变量b的生命周期与A类同步,如果A类不会卸载,那么b对象会常驻内存,直到程序终止。
  3. 尽量避免过多过常的创建Java对象 尽量避免在经常调用的方法,循环中new对象,由于系统不仅要花费时间来创建对象,而且还要花时间对这些对象进行垃圾回收和处理,在我们可以控制的范围内,最大限度的重用对象,最好能用基本的数据类型或数组来替代对象。
  4. 尽量使用final修饰符 带有final修饰符的类是不可派生的。在Java核心API中,有许多应用final的例子,例如java.lang.String。为String类指定final防止使用者覆盖length()方法。另外,如果一个类是final的,则该类所有方法都是final的。Java编译器会寻找机会内联(inline)所有的final方法(这和具体的编译器实现有关)。此举能够使性能平均提高50%。
  5. 尽量使用局部变量 调用方法时传递的参数以及在调用中创建的临时变量都保存在栈(Stack)中,速度较快。其他变量,如静态变量、实例变量等,都在堆(Heap)中创建,速度较慢。
  6. 尽量处理好包装类型和基本类型两者的使用场所 虽然包装类型和基本类型在使用过程中是可以相互转换,但它们两者所产生的内存区域是完全不同的,基本类型数据产生和处理都在栈中处理,包装类型是对象,是在堆中产生实例。 在集合类对象中,有对象方面需要的处理适用包装类型,其他的处理提倡使用基本类型。
  7. 慎用synchronized,尽量减小synchronize的方法 都知道,实现同步是要很大的系统开销作为代价的,甚至可能造成死锁,所以尽量避免无谓的同步控制。synchronize方法被调用时,直接会把当前对象锁了,在方法执行完之前其他线程无法调用当前对象的其他方法。所以synchronize的方法尽量小,并且应尽量使用方法同步代替代码块同步。
  8. 尽量使用StringBuilder和StringBuffer进行字符串连接 这个就不多讲了。
  9. 尽量不要使用finalize方法 实际上,将资源清理放在finalize方法中完成是非常不好的选择,由于GC的工作量很大,尤其是回收Young代内存时,大都会引起应用程序暂停,所以再选择使用finalize方法进行资源清理,会导致GC负担更大,程序运行效率更差。
  10. 尽量使用基本数据类型代替对象 String str = "hello"; 上面这种方式会创建一个“hello”字符串,而且JVM的字符缓存池还会缓存这个字符串; String str = new String("hello"); 此时程序除创建字符串外,str所引用的String对象底层还包含一个char[]数组,这个char[]数组依次存放了h,e,l,l,o
  11. 单线程应尽量使用HashMap、ArrayList HashTable、Vector等使用了同步机制,降低了性能。
  12. 尽量合理的创建HashMap 当你要创建一个比较大的hashMap时,要充分利用另一个构造函数public HashMap(int initialCapacity, float loadFactor)避免HashMap多次进行了hash重构,扩容是一件很耗费性能的事,在默认中initialCapacity只有16,而loadFactor是 0.75,需要多大的容量,你最好能准确的估计你所需要的最佳大小,同样的Hashtable,Vectors也是一样的道理。
  13. 尽量减少对变量的重复计算 如 for(int i=0;i<list.size();i++)应该改为 for(int i=0,len=list.size();i<len;i++)并且在循环中应该避免使用复杂的表达式,在循环中,循环条件会被反复计算,如果不使用复杂表达式,而使循环条件值不变的话,程序将会运行的更快。
  14. 尽量避免不必要的创建 如 A a = new A(); if(i==1){list.add(a);}应该改为 if(i==1){ A a = new A();list.add(a);}
  15. 尽量在finally块中释放资源 程序中使用到的资源应当被释放,以避免资源泄漏。这最好在finally块中去做。不管程序执行的结果如何,finally块总是会执行的,以确保资源的正确关闭。
  16. 尽量使用移位来代替'a/b'的操作 “/”是一个代价很高的操作,使用移位的操作将会更快和更有效 如 int num = a / 4; int num = a / 8;应该改为 int num = a 》 2; int num = a 》 3;但注意的是使用移位应添加注释,因为移位操作不直观,比较难理解。
  17. 尽量使用移位来代替'a*b'的操作 同样的,对于'*'操作,使用移位的操作将会更快和更有效 如 int num = a * 4; int num = a * 8;应该改为 int num = a 《 2; int num = a 《 3;
  18. 尽量确定StringBuffer的容量 StringBuffer的构造器会创建一个默认大小(通常是16)的字符数组。在使用中,如果超出这个大小,就会重新分配内存,创建一个更大的数组,并将原先的数组复制过来,再丢弃旧的数组。在大多数情况下,你可以在创建StringBuffer的时候指定大小,这样就避免了在容量不够的时候自动增长,以提高性能。 如: StringBuffer buffer = new StringBuffer(1000);
  19. 尽量早释放无用对象的引用 大部分时间,方法局部引用变量所引用的对象会随着方法结束而变成垃圾,因此,大部分时候程序无需将局部,引用变量显式设为null. 例如: Public void test(){ Object obj = new Object(); …… Obj=null; }上面这个就没必要了,随着方法test()的执行完成,程序中obj引用变量的作用域就结束了。但是如果是改成下面: Public void test(){ Object obj = new Object(); …… Obj=null;//执行耗时,耗内存操作;或调用耗时,耗内存的方法 …… } 这时候就有必要将obj赋值为null,可以尽早的释放对Object对象的引用。
  20. 尽量避免使用二维数组 二维数据占用的内存空间比一维数组多得多,大概10倍以上。
  21. 尽量避免使用split 除非是必须的,否则应该避免使用split,split由于支持正则表达式,所以效率比较低,如果是频繁的几十,几百万的调用将会耗费大量资源,如果确实需要频繁的调用split,可以考虑使用apache的StringUtils.split(string,char),频繁split的可以缓存结果。
  22. ArrayList&LinkedList 一个是线性表,一个是链表,一句话,随机查询尽量使用ArrayList,ArrayList优于LinkedList,LinkedList还要移动指针,添加删除的操作LinkedList优于ArrayList,ArrayList还要移动数据,不过这是理论性分析,事实未必如此,重要的是理解好2者的数据结构,对症下药。
  23. 尽量使用System.arraycopy()代替通过来循环复制数组 System.arraycopy()要比通过循环来复制数组快的多。
  24. 尽量缓存经常使用的对象 尽可能将经常使用的对象进行缓存,可以使用数组,或HashMap的容器来进行缓存,但这种方式可能导致系统占用过多的缓存,性能下降,推荐可以使用一些第三方的开源工具,如EhCache,Oscache进行缓存,他们基本都实现了FIFO/FLU等缓存算法。
  25. 尽量避免非常大的内存分配 有时候问题不是由当时的堆状态造成的,而是因为分配失败造成的。分配的内存块都必须是连续的,而随着堆越来越满,找到较大的连续块越来越困难。
  26. 慎用异常 当创建一个异常时,需要收集一个栈跟踪(stack track),这个栈跟踪用于描述异常是在何处创建的。构建这些栈跟踪时需要为运行时栈做一份快照,正是这一部分开销很大。当需要创建一个Exception时,JVM不得不说:先别动,我想就您现在的样子存一份快照,所以暂时停止入栈和出栈操作。栈跟踪不只包含运行时栈中的一两个元素,而是包含这个栈中的每一个元素。 如果您创建一个Exception,那么就得付出代价。好在捕获异常开销不大,因此可以使用try-catch将核心内容包起来。从技术上讲,您甚至可以随意地抛出异常,而不用花费很大的代价。招致性能损失的并不是throw操作(尽管在没有预先创建异常的情况下就抛出异常是有点不寻常)。真正要花代价的是创建异常。幸运的是,好的编程习惯已教会我们,不应该不管三七二十一就抛出异常。异常是为异常的情况而设计的,使用时也应该牢记这一原则。

https优缺点

申请免费HTTPS证书

$
0
0
开通全站 ssl验证需要证书,这里只说明免费的证书:
  • Let’s Encrypt
    • 是国外一个公共的免费SSL项目,由 Linux 基金会托管,它的来头不小,由Mozilla、思科、Akamai、IdenTrust和EFF等组织发起
    • 免费三个月,三个月后重新申请即可
  • startSSL
    • 个人免费一年
  • 沃通
    • 博主申请的这个,免费两年
    • 证书其实是startSSL发布的
沃通申请太简单,这里就略过了。

MySQL 主从复制 搭建及配置的坑

$
0
0
这不是配置的教程,而是配置过程中的坑解决。我在配置过程中,很多MySQL的使用,最用到才知道是坑。有的好解决,有的需要想一些办法

简明教程

1 centos 6.5 下载mysql 5.7,并在3个节点安装DB(安装或升级过程中的my.cnf配置>) mysqld: unknown variable ‘default-character-set=utf8’ – PayMoon贝明实验室 http://www.paymoon.com:8001/index.php/2016/06/03/mysqld-unknown-variable-default-character-setutf8/ 2 更改密码及远程访问授权(1 安全模式 2 5.7改密码新命令 3 host>%) 更改DB mysql 密码万能法 http://www.paymoon.com:8001/index.php/2016/04/11/update-db-mysql-password/ 3 开始主从复制,配置主的my.cnf [crayon-5763ec4796a2a778422120/] http://valleylord.github.io/post/201601-mycat-readwrite/ 然后重启主Mysql,并登陆,运行以下命令,
1
2
3
4
5
6
7
8
9
10
mysql> GRANT REPLICATION SLAVE ON *.* to 'root'@'%' identified by '111111';
Query OK, 0 rows affected, 1 warning (0.05 sec)
mysql> show master status;
+------------------+----------+--------------+------------------+-------------------+
| File | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set |
+------------------+----------+--------------+------------------+-------------------+
| mysql-bin.000007 | 429 | | | |
+------------------+----------+--------------+------------------+-------------------+
1 row in set (0.00 sec)
4 配置从的my.cnf及命令行操作 [crayon-5763ec4796a35251481718/] 注意,server-id一定不能与主Mysql相同,这是表示服务器的唯一id。然后重启从Mysql,登陆,运行如下命令,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
mysql> change master to master_host='master-1',master_port=3306,master_user='root',master_password='111111',master_log_file='mysql-bin.000007',master_log_pos=429;
Query OK, 0 rows affected, 2 warnings (0.44 sec)
mysql> start slave;
Query OK, 0 rows affected (0.02 sec)
mysql> show slave status\G
*************************** 1. row ***************************
Slave_IO_State: Waiting for master to send event
Master_Host: master-1
Master_User: root
Master_Port: 3306
Connect_Retry: 60
Master_Log_File: mysql-bin.000007
Read_Master_Log_Pos: 429
Relay_Log_File: 1d508b1aa846-relay-bin.000002
Relay_Log_Pos: 595
......
注意,以上命令中的master_log_file='mysql-bin.000007',master_log_pos=429要与主Mysql中的show master status结果一致。另外,如果配置change master的时候配置错了,需要先运行stop slave停止slave,再运行reset slave清空slave的配置,然后再从头开始配置。

坑说明

1 版本的选择。最好选5.7的,因为5.7有gtid,性能比5.6快三倍。当然最低5.6,如果不幸还在用5.1的,那么有一些参数的更改,必须要重启。望周知~ 2 5.7密码问题 5.7 中增强了密码的校验,这涉及到密码的更改,权限分配中等所有使用到密码的情况。 比如在主从设置中 [crayon-5763ec4796a41410433869/] 就会报密码不安全。 解决办法两个: AAA: 更改密码长度,不适合 at least 8 characters long update mysql.user set authentication_string=password('testtesttesttest') where user='root' BBB: (推荐)更改校验为Low,长度为1 [crayon-5763ec4796a49165918549/] Change MySQL password policy level - Qiita http://qiita.com/liubin/items/3722ab10a73154863bd4 3 不会被复制的语句 有些语句不会被复制,注意程序里面不要用这些sql 原理是表任务级别的操作,DB的不会 mysql主从复制原理介绍2 - fighting-cluber的个人页面 - 开源中国社区 http://my.oschina.net/zijian1315/blog/202599 Mysql数据库主从心得整理 - 王伟 - 51CTO技术博客 http://wangwei007.blog.51cto.com/68019/965575 4 mysql主从复制遇到错误就停止复制的解决办法》跳过错误配置 mysql主从复制跳过错误 - seteor的专栏 - 博客频道 - CSDN.NET http://blog.csdn.net/seteor/article/details/17264633

Mysql 冷备数据的一些常规流程及命令

$
0
0
因为是冷备的数据,所以都需要手工操作 1 线上大数据dbOL bigData.sql> 导出到线上机器某目录下; [crayon-5763ec4795b0c340208129/] 2 sftp 传输到test机上 [crayon-5763ec4795b1a123556881/] 3 test 机 mysql 导入此sql的表数据 [crayon-5763ec4795b20065944963/] 4 按月份分为几个表 4.1 首先先建相同结构的表_月份 [crayon-5763ec4795b25233917209/] 假设建表名为 bigTable_2016_04 4.2 把bigTable表进行分拆 [crayon-5763ec4795b2a250310499/] 5 把按月的表导出sql [crayon-5763ec4795b2f161064183/] 6 sftp 链接到线上 同step 2 7 线上db于导入这几个表sql。 同step 3 完成表的按时间查询 8 线上原表删除当前月分以前的数据 [crayon-5763ec4795b34216802682/] Ref:MySQL数据库(表)的导入导出(备份和还原) - 跌打滚爬中向前的专栏 - 博客频道 - CSDN.NET http://blog.csdn.net/deutschester/article/details/6866842

ERROR 1862 (HY000): Your password has expired

$
0
0
在MySQL的版本5.6中,可能会遇到这个问题

ERROR 1862 (HY000): Your password has expired

原因是MySQL做了处理,看官方文档的处理办法:

更改my.cnf

disconnect_on_expired_password=false

更改mysql.user表中字段:password_expired设置为N

一个是设置过期能够重新连接,一个是控制密码不过期。

在mysql 5.7中,

mysql.user表中字段:password_expired都为N,应该

不存在这个问题

jedis的JedisPoolConfig没MaxActive方法的解决办法

$
0
0
搜索redis utils 工具类,jedis 工具类,redis封装类等,网上一篇篇的,但是copy下来,很多不能用,其中一些不能用的原因是因为网上的jedis都是旧版本,而在新版本2.2以后,很多方法和属性已经过期或者删了。 JedisPoolConfig.setMaxActive就是其中一个 那么替换方法就是

config.setMaxTotal(500);

对应的旧版MaxWait则为:

  config.setMaxWaitMillis(100000);

一个比较全面的RedisUtils 工具类如下   http://www.paymoon.com:8001/index.php/2016/07/20/share-redisutils-redisclient/

分享一个RedisUtils/RedisClient 工具类

$
0
0
包括了常用的和高级的用法 git地址: Fork from Git 代码如下: [crayon-579016da614d0053180184/]  

linux下进程的进程最大数、最大线程数、进程打开的文件数和ulimit命令修改硬件资源限制

$
0
0

How to increase ulimit open file and user processes in Linux/Centos/RHEL

Max Number of ulimit open file : It's provide open file resource availability in linux    Increase max number of ulimit open file in Linux. 1- Step :  open the sysctl.conf  and add this line  fs.file-max = 65536 vi /etc/sysctl.conf   add end of line fs.file-max = 65536 save and exit. 2. Step : vi /etc/security/limits.conf  and add below the mentioned *          soft     nproc          65535 *          hard     nproc          65535 *          soft     nofile         65535 *          hard     nofile         65535 save and exit check max open file ulimit [root@localhost# ulimit -a core file size          (blocks, -c) 0 data seg size           (kbytes, -d) unlimited scheduling priority             (-e) 0 file size               (blocks, -f) unlimited pending signals                 (-i) 127358 max locked memory       (kbytes, -l) 64 max memory size         (kbytes, -m) unlimited open files                      (-n) 65535 pipe size            (512 bytes, -p) 8 POSIX message queues     (bytes, -q) 819200 real-time priority              (-r) 0 stack size              (kbytes, -s) 10240 cpu time               (seconds, -t) unlimited max user processes              (-u) 1024 virtual memory          (kbytes, -v) unlimited file locks                      (-x) unlimited Increase max user processes in Linux Follow the step: vi /etc/security/limits.conf  and add below the menstioed *          soft     nproc          65535 *          hard     nproc          65535 *          soft     nofile         65535 *          hard     nofile         65535 and  vi /etc/security/limits.d/90-nproc.conf *          soft     nproc          65535 *          hard     nproc          65535 *          soft     nofile         65535 *          hard     nofile         65535 save and exit check the user max processes ulimit [root@localhost# ulimit -a core file size          (blocks, -c) 0 data seg size           (kbytes, -d) unlimited scheduling priority             (-e) 0 file size               (blocks, -f) unlimited pending signals                 (-i) 127358 max locked memory       (kbytes, -l) 64 max memory size         (kbytes, -m) unlimited open files                      (-n) 65535 pipe size            (512 bytes, -p) 8 POSIX message queues     (bytes, -q) 819200 real-time priority              (-r) 0 stack size              (kbytes, -s) 10240 cpu time               (seconds, -t) unlimited max user processes              (-u) 65535 virtual memory          (kbytes, -v) unlimited file locks                      (-x) unlimited After make changes need to reboot system.

Kubernetes和Mesos集成实战部署

$
0
0
Kubernetes是一个跨多个计算节点的管理容器化应用的系统,它提供了一系列基本的功能,如应用的自动化部署,维护和扩展等。Mesos是Apache下的开源分布式资源管理框架,它被称为是分布式系统的内核。把Kubernetes运行在Mesos集群之上,可以和其他的框架共享集群资源,提高集群资源的利用率。本文是“Kubernetes和Mesos集成指南”系列文章第一篇:实战部署。 现在Kubernetes官方提供的部署基于Mesos的Kubernetes集群的文档相对简单,对于不熟悉Mesos的Kubernetes开发者或者用户来说,按照那个文档提供的说明进行部署,可能会比较困难,并且会遇到一些坑。文本是基于作者长期贡献Mesos和Kubernetes社区的开发经验, 分享给大家实战部署基于Mesos的Kubernetes集群。同时部署Kubernetes附加的一些服务,比如DNS和Dashboard等。

搭建基于Mesos的Kubernetes集群

部署架构

本文笔者将利用三台虚拟机来演示如何部署基于Mesos的Kubernetes集群,各个节点上的集群服务组件如下图所示: 图片描述 另外为了让大家可以尽快体验kubernetes和Mesos最新的特性,以及同时对kubernetes用户和开发者同时具有借鉴意义,我选择最新的社区代码来编译安装基于Mesos的kubernetes集群,如果读者想使用一个稳定的release版本,你可以下载对应的tar包,然后按照相同的步骤进行。 另外因为集群会有多个节点,开发者可能会修改部分源代码,为了在修改了源码之后,使修改可以快速便捷的在所有的机器上生效,我们将在wyq01.ibm.com这个机器上架设NFS服务,把它作为编译的机器,用来编译Mesos和kubernetes源代码。然后将编译的安装包目录 mount到其他的计算节点上。这样在修改了Mesos或者kubernetes的代码之后,只需要在wyq01.ibm.com进行编译然后只需要在另外两台计算节点上重启相应的服务就可以生效。

安装步骤

1.准备环境 准备三台Ubuntu 14.40的环境(物理机和虚拟机都可以),配置DNS或者/etc/hosts文件来保证相互通过机器名可以访问,并且关闭防火墙。 2.在每一个节点上安装Docker 最新版本的kubernetes支持多种容器的运行时,比如Docker,Rocket,CNI等,本文以最主流的Docker最为例子来演示。分别登陆这三台机器,执行以下命令安装Docker: [crayon-57981de5ed302906348006/] Client: [crayon-57981de5ed310329526394/] Server: [crayon-57981de5ed316220360583/] 在本演示环境中,笔者安装的是当时最新的docker版本,建议使用最新版本,如果你的机器上已经安装Docker,请检查kubernetes官方文档,查看你安装的Docker是不是符合要求。 另外由于你所在环境的限制,可能需要对Docker配置网络代理才可以在docker hub或者其他仓库中下载镜像: 编辑/etc/default/docker文件,在此文件中添加http_proxy的配置, 如下所示: [crayon-57981de5ed31c756063038/] 3.在master节点上编译 Mesos源代码 Mesos官方目前没有提供Mesos的安装包,需要自己下载源码包进行build。本文以Mesos最新的代码为例进行编译安装, 登陆master节点wyq01.ibm.com执行以下步骤进行编译: 首先需要安装运行 Mesos 必须的第三方软件包: [crayon-57981de5ed321513653895/] 下载源码包进行编译安装: [crayon-57981de5ed327053814055/] 编译安装完成之后,就可以在如下目录看到编译之后的二进制包和相关的管理脚本: [crayon-57981de5ed32c015401957/] 我们将用mesos-daemon.sh脚本来启动Mesos服务,之前需要对配置: 编辑/opt/mesosinstall/usr/local/sbin/mesos-daemon.sh文件:
  • 修改prefix变量的值为:/opt/mesosinstall/usr/local
  • 在prefix的下一行添加行:
[crayon-57981de5ed334576596148/] 4.在master节点上编译Kubernetes源代码 登陆master节点wyq01.ibm.com执行以下步骤进行编译: 安装Golang: 由于我们需要在wyq01.ibm.com上编译kubernetes,它是由GO语言编写,所以需要在wyq01.ibm.com上安装Golang来支持kubernetes的编译。 注: 对不同的kubernetes版本,它对golang的版本都有所要求,建议安装前查看官方文档。 本文笔者使用最新的版本: [crayon-57981de5ed33a607854920/] 编辑/etc/profile,添加环境变量: export PATH=$PATH:/usr/local/go/bin 安装验证: [crayon-57981de5ed33f669255850/] 下载Kubernetes源码,进行编译: [crayon-57981de5ed344456583881/] 这个编译可能需要几分钟时间,根据你的机器性能而定。编译完成之后,可以在如下目录中看到软件包: [crayon-57981de5ed349271318660/] 注: 在编译的时候,如果你按照官方的步骤进行编译可能会遇到如下的错误: [crayon-57981de5ed355893643707/] 现在社区中有一个issue在跟踪这个问题,参见https://github.com/kubernetes/kubernetes/issues/28890 获取详细的解决方案。 5.在master节点上安装NFS 为了节省安装时间,我们不需要在三台机器上分别编译 Mesos,可以直接将build的安装目录拷贝到其他两台机器上即可。但是特别是对于一个Mesos贡献者来说,我们需要定期的和社区最新的代码进行同步和重新build,所以为了避免每次重复的拷贝,我们采用共享文件的方式。 登陆wyq01.ibm.com执行如下命令安装配置NFS: [crayon-57981de5ed35b300017177/] 6.在两台计算节点配置Mesos运行的环境和NFS 登陆wyq02.ibm.com和wyq03.ibm.com,执行如下命令: 在两台计算节点上安装运行Mesos需要的安装包: [crayon-57981de5ed360309352846/] Mount Mesos安装目录: [crayon-57981de5ed366162928066/] 7.启动Mesos集群 登陆wyq01.ibm.com启动Mesos master: [crayon-57981de5ed36d989786328/] 登陆wyq02.ibm.com和wyq03.ibm.com启动Mesos agent: [crayon-57981de5ed373994413661/] 注意:在kubernetes集成Mesos的环境中,在启动task的时候,Mesos fetcher会首先从Master节点下载kubernetes对应的exectuor km的二进制包,因为这个包相对比较大,当你的网络质量比较差的时候可能会导致下载或者executor接入超时,所以请务必调整如下两个参数: –registry_fetch_timeout:下载的超时时间,默认为1分钟,建议修改。 –executor_registration_timeout:Mesos executor注册到Mesos Agent的时间,默认为1分钟,建议修改。 验证Mesos集群是否安装成功:打开Mesos portal: http://wyq01.ibm.com:5050 查看Agents节点的信息。 以上的步骤只是部署了一个非高可用的Mesos集群,如果你想部署一个高可用的Mesos集群,请继续参考如下步骤,如果不需要,请直接到下章节。 因为Mesos高可用集群依赖一个zookeeper集群,所以首先需要安装一个zookeeper集群,本文笔者将以一个节点的zookeeper集群为例,如果你想安装一个多个节点的高可用的zookeeper集群,请参见笔者的纪录: https://github.com/gradywang/Dockerfiles/tree/master/zookeeper/official 执行如下命令在事先准备好的机器上,部署zookeeper服务,比如也可以是集群的一个机器wyq01.ibm.com: [crayon-57981de5ed38c930698210/] 然后在事先准备好的多个运行Mesos master的机器上,执行以下命令在这些机器上启动Mesos master: [crayon-57981de5ed393122482246/] 运行以下命令,启动所有的Mesos Agent: [crayon-57981de5ed399305585725/] 8.在Master节点上启动和配置kubernetes服务 登陆wyq01.ibm.com节点,配置kubernetes管理服务:
  • 设置环境变量:
[crayon-57981de5ed39f376673605/] 注意:如果你的kubernetes将运行在一个高可用的Mesos集群上,那么请修改MESOS_MASTER的环境变量,如下: [crayon-57981de5ed3a5387112096/]
  • 启动etcd服务:
[crayon-57981de5ed3aa746794360/]
  • 安装flannel:
[crayon-57981de5ed3b0184738537/] 在ETCD中配置flannel网络: [crayon-57981de5ed3b5359094965/]
  • 配置Docker:
[crayon-57981de5ed3ba519478023/] 配置DOCKER_OPTS: [crayon-57981de5ed3bf398153986/]
  • 启动kubernetes服务:
启动Apiserver: [crayon-57981de5ed3c4276618435/] 启动controller manager: [crayon-57981de5ed3ca753935443/] 启动scheduler: [crayon-57981de5ed3cf512667201/] 查看进程是否启动成功,如果失败,请检查对应的日志: [crayon-57981de5ed3d4199744228/] 配置kubectl ~/.kube/config,验证集群是否安装成功 [crayon-57981de5ed3df249704221/] 9.在Compute节点上配置kubernetes相关服务 登陆wyq02.ibm.com和wyq03.ibm.com节点,配置kubernetes相关服务:
  • 设置环境变量:
[crayon-57981de5ed3e5739216115/]
  • 安装flannel:
在Compute几点上安装flannel,可以让kubernetes集群中的不同计算节点上的服务或者POD相互连通: [crayon-57981de5ed3ea362133722/]
  • 配置Docker:
[crayon-57981de5ed3ef838542331/] 配置DOCKER_OPTS: [crayon-57981de5ed3f4084974421/] 10.安装部署Kubernetes重要的附加服务 默认情况下,kubernetes不带DNS和Dashboard服务,我们需要在安装完Kubernetes之后另外进行部署。Kubernetes的DNS和Dashboard是以POD的方式运行的,这样的化它们就可以被kubernetes进行管理。 登陆wyq01.ibm.com 节点,安装部署Kubernetes DNS和Dashboard: 安装部署DNS: [crayon-57981de5ed3f9066543791/] 创建kubernetes DNS的RC: [crayon-57981de5ed3fe453335677/] 编辑/tmp/skydns-rc.yaml文件,为kubernetes DNS容器指定kubernetes master的URL,例如: [crayon-57981de5ed404579842424/] 创建kubernetes DNS的Service: [crayon-57981de5ed409156192616/] 安装部署Dashboard: [crayon-57981de5ed40e297467705/] 编辑/tmp/dashboard-controller.yaml文件,为Dashboard容器指定Kubernetes Apiserver的地址,例如: [crayon-57981de5ed412646179493/] 创建Dashboard的RC: [crayon-57981de5ed417327915381/] 创建Dashboard的Service: [crayon-57981de5ed41c703091506/] 获取Dashboard的URL进行验证: [crayon-57981de5ed421431553949/] 访问Kubernetes的Dashboard:http://9.111.255.10:8888/api/v1/proxy/namespaces/kube-system/services/kubernetes-dashboard 如果页面访问成功,则安装成功。

13 个问题带你深入了解 Mesos

$
0
0
Apache Mesos 是一个集群管理器,提供了有效的、跨分布式应用或框架的资源隔离和共享,可以运行 Hadoop、MPI、Hypertable、Spark。

13 个问题带你深入了解 Mesos

(问答来自 OSChina 开源中国社区第 100 期高手问答 —— Apache Mesos) (本文链接 http://www.paymoon.com:8001/index.php/2016/07/28/13-items-about-mesos/ ‎) Q1:对大多数人来说还不知道什么是 Mesos,请介绍下他是干什么的,有什么用,怎么用? A1:你好, Mesos 在国内的资料目前虽然不多,但是你随便百度,谷歌一下,还是有一些的。这里我想拿一个例子来解释 Mesos,假设某公司需要频繁进行大数据计算,该任务运行时需要 N 多 CPU 和内存,为了满足这个需求,我们有两种思路: 思路一)使用小型机,单机即可为任务提供足够 的资源; 思路二)分布式计算,即提供一批普通配置的机器(计算节点),也就是集群,将计算任务拆分到各机器上计算,然后汇总结果。 思路二是当前正在流行的做法,这种方式的优点不再多说。为了达到思路二的要求,我们需要建立数据中心(集群)。进一步,为了充分利用数据中心(集群)的资源(譬如为不同的任务分配不同资源,按任务优先级分配资源等),我们就需要一个工具来进行整个数据中心资源的管理、分配等, 这个工具就是 Mesos。 与 Mesos 类似的工具还有 YARN. 除此之外, Mesos 不仅为计算任务 Offer 资源, 它也支持运行长时任务(譬如 Web应用)。目前国外好多互联网公司都在使用 Mesos 来作为它们的集群管理工具,这里是一个 Powered by Mesos list: https://mesos.apache.org/documentation/latest/powered-by-mesos/ Q2:我们现在用 Cloudera 这套,能简单介绍下 Mesos 和 Cloudera 的差别吗? A2:Mesos 的主要目标就是去帮助管理不同框架(或者应用栈)间的集群资源。比如说,有一个业务需要在同一个物理集群上同时运行Hadoop,Storm及 Spark。这种情况下,现有的调度器是无法完成跨框架间的如此细粒度的资源共享的。Hadoop 的 YARN 调度器是一个中央调度器,它可以允许多个框架 运行在一个集群里。 但是,要使用框架特定的算法或者调度策略的话就变得很难了,因为多个框架间只有一种调度算法。比如说,MPI 使用的是组调度算法,而 Spark 用的是延迟调度。它们两个同时运行在一个集群上会导致供求关系的冲突。还有一个办法就是将集群物理拆分成多个小的集群,然后将不同的框架独立地 运行在这些小集群上。再有一个方法就是为每个框架分配一组虚拟机。正如Regola 和 Ducom 所说的,虚拟化被认为是一个性能瓶颈,尤其是在高性能计算 (HPC)系统中。这正是 Mesos 适合的场景——它允许用户跨框架来管理集群资源。 Mesos 是一个双层调度器。在第一层中,Mesos 将一定的资源提供(以容器的形式)给对应的框架。框架在第二层接收到资源后,会运行自己的调度算法来 将任务分配到 Mesos 所提供的这些资源上。和 Hadoop YARN 的这种中央调度器相比,或许它在集群资源使用方面并不是那么高效。但是它带来了灵活性——比如说,多个框架实例可以运行在一个集群里。这是现有的这些调度器都无法实现的。就算是 Hadoop YARN 也只是尽量争取在同一个集群上支持类似 MPI 这样的第三方框架而已。更重要的是,随着新框架的诞生,比如说 Samza 最近就被 LinkedIn 开源出来了——有了 Mesos 这些新框架可以试验性地部署到现有的集群上,和其它的框架和平共处。 Q3:您好,Mesos 有哪些典型的应用场景?看了一些介绍,说是能做 Docker 的编排服务。与 OpenStack 这样的云平台管理物理机 CPU、内存,Cloudera Manager 管理 Hadoop 集群服务有什么区别? A3:现在 Mesos 的应用场景非常多,譬如 1)Spark on Mesos (这是标配 ) 2)Jenkins on Mesos 3)Mesos 做 docker 的编排服务等。 与 OpenStack 相比, 首先,物理机,虚拟机都可以作为 Mesos 的集群节点;其次, 粒度不同, Mesos 的基本计算单元是容器(LXC) , 而 OpenStack 的是 VM(听说现在也支持Docker 容器技术了),前者资源利用率更高;最后,轻量级,Mesos 只负责 Offer 资源给Framework,不负责调度资源。 OpenStack 更贴近于 IaaS 层,而 Mesos 在 IaaS 之上。所以有人称其为 DCOS,或者分布式操作系统。 Q4:各方面边界在哪,有什么优劣势,谢谢。 A4:优点 资源管理策略 Dominant Resource Fairness(DRF), 这是 Mesos 的核心,也是我们把Mesos 比作分布式系统 Kernel 的根本原因。通俗讲,Mesos 能够保证集群内的所有用户有平等的机会使用集群内的资源,这里的资源包括 CPU,内存,磁盘等等。很多人拿 Mesos跟 k8s 相比,我对 k8s 了解不深,但是,我认为这两者侧重点不同不能做比较,k8s 只是负责容器编排而不是集群资源管理。不能因为都可以管理 Docker,我们就把它们混为一谈。 轻量级。相对于 YARN,Mesos只负责 Offer 资源给 Framework,不负责调度资源。这样,理论上,我们可以让各种东西使用 Mesos 集群资源,而不像 YARN 只拘泥于 Hadoop,我们需要做的是开发调度器(Mesos Framework)。 提高分布式集群的资源利用率:这是一个 Generic 的优点。从某些方面来说,所有的集群管理工具都是为了提高资源利用率。VM 的出现,催生了 IaaS;容器的出现,催生了 K8s, Mesos 等等。简单讲,同样多的资源,我们利用 IaaS 把它们拆成 VM 与 利用 K8s/Mesos 把它们拆成容器,显然后者的资源利用率更高。(这里我没有讨论安全的问题,我们假设内部子网环境不需要考虑这个。) 缺点 门槛太高。只部署一套 Mesos,你啥都干不了,为了使用它,你需要不同的 Mesos Framework,像 Marathon,Chronos,Spark 等等。或者自己写 Framework 来调度 Mesos给的资源,这让大家望而却步。 目前对 Stateful Service 的支持不够。Mesos 集群目前无法进行数据持久化。0.23 版本增加了 Persistent resource 和 Dynamic reserver,数据持久化问题将得到改善。 脏活累活不会少。Team 在使用 Mesos 前期很乐观,认为搞定了 Mesos,我们的运维同学能轻松很多。然而,根本不是那么回事儿,集群节点的优化,磁盘,网络的设置,等等这些,Mesos 是不会帮你干的。使用初期,运维的工作量不仅没有减轻,反而更重了。Mesos 项目还在紧锣密鼓的开发中,很多功能还不完善。 Q5:我想请教下,如果要做一个云服务平台,Mesos 和 Kubernates 怎么去选型 A5:目前的现状是 Mesos 和 K8s 的生态圈各自都发展的比较好,丢弃哪一个都很吃亏。不如按你个人的喜好,先选择一个投下去先用起来。比如有的厂商支持直接一键部署,这样太方便了。可以快速体验 Mesos 的好处。 这个要看你的具体需求。据我所知, K8s 目前只支持 Docker 而且鲜有生产环境的用例; 而 Mesos 不需要你的应用包到 Docker 里面并且其经历过生产环境的考验。 但是, 反过来, K8s 的社区更加活跃,其正在高速发展中,前景非常好。 当然,上述都不是关键, 一个好用的云平台更多的是要有好的产品理念。 Q6:对于长时间任务,有没有好的调度器算法或者策略 A6:长任务是依靠马拉松 Marathon 框架,对于 Docker,Mesos + Marathon 基本上是现在最成熟的分布式运行框架。长任务是依靠马拉松 Marathon 框架,对于 Docker,Mesos + Marathon 基本上是现在最成熟的分布式运行框架。 Q7:请问下 Mesos 和 Docker 结合,Mesos 只是能解决资源分配问题对么? A7:对的,Mesos 负责资源分配,需要有个东东负责 Docker 的任务调度,这样就能将 Docker实例自动下发到集群中运行。这个组件叫马拉松 Marathon。Mesos + Marathon 基本上现在最稳定的 Docker 集群化调度框架 Q8:Mesos 现在可以逐渐应用到生产环境了? A8:Mesos 早就可以应用到生产环境了, 国外的 Airbnb, Apple, Uber, Twitter,国内的携程,爱奇艺,还有我们公司数人科技都在生产环境使用了 Mesos。 你在这里可以看到使用 Mesos 的列表 https://mesos.apache.org/documentation/latest/powered-by-mesos/ Q9:Mesos 和 Zookeeper 有什么关联吗? A9:Zookeeper 是一个为分布式应用提供一致性服务的软件, 而 Mesos 是一个分布式应用。所以在生产环境,我们需要使用 Zookeeper 来为 Mesos 提供一致性服务。 Q10:Mesos,Swarm,Kubernetes 之间有没有竞争关系?虽然这三家都说互相支持,但是这样做会不会太啰嗦了? A10:Swarm 与 K8s 有很多交叉。 Mesos 更多的是 Focus 在资源管理上, 只是恰好可以使用 Container 做资源隔离。竞争与否,还需要看社区的走向吧。 Q11:你好,看了看这个框架想请教几个问题: 1.这个框架是否自带日志搜集模块? 2.这个框架能否进行性能统计? 3.这个框架在某个节点资源耗尽时可否自动切换?如果所有节点资源耗尽是否容易崩溃,自恢复能力如何? 4.这个框架可否配置负载均衡? 谢谢:) A11:
  1. 带日志模块,但是功能比较简单,没有一个全局的展示
  2. 可以进行性能统计
  3. Mesos 是根据当前的集群资源统计来决定给调度器分配多少资源的,资源耗尽只会导致新的应用无法部署,不会影响正在运行的东西。
  4. 可以配置负载均衡。 并且 Mesos 本身也有多 Master 机制
Q12:请问 Mesos 怎样决定分配多少资源?分配的资源什么时候回收? A12:Mesos 与其它的集群管理工具不同, Mesos 本身不负责分配资源,它只是将当前集群的剩余资源提供给注册到它的调度器,由调度器本身来决定使用多少资源,以及合适释放资源。 Q13:假设集群里有 3 台服务器,每台服务器可用内存 16G,现在调度器要运行一个任务需要24G 内存,那么 Mesos 是把整个集群的 48G 内存当成一个整体来提供,还是会向调度器提供每台服务器剩余的内存,也就是说下面两种情况哪种才是正确的: 1. 调度器先申请节点1的 16G 内存,再申请节点 2 的 8G 内存,用哪个节点的内存完全由调度器控制 2. 调度器一次过申请 24G 内存,由 Mesos 控制具体是用了哪个节点的内存。有可能是每个节点都分配了 8G;也有可能是一个节点 16G,另一个节点 8G A13:看过 DPark 实现 Mesos 的调度器。你一个任务需要 24G 内存,这个任务就需要拆分才可以调度起来。每个小任务需要 16G 以下的内存。才能通过调度器,调度到具体服务器。 调度器一般都是把任务调度到文件所在的机器上。由调度器控制使用哪里的资源, Mesos 告诉调度器哪些资源可用。 阅读完这 13 个问答,希望可以让你对 Mesos 的认识更深,并用于项目实践,分享更多地经验给 Mesos 爱好者:)

推荐算法概览(一)

$
0
0
原文: Overview of Recommender Algorithms 本文:http://www.paymoon.com:8001/index.php/2016/07/28/the-components-of-a-recommender-system 作者: MAYA.HRISTAKEVA 译者: 孙薇 责编: 钱曙光,关注架构和算法领域,寻求报道或者投稿请发邮件qianshg@csdn.net,另有「CSDN 高级架构师群」,内有诸多知名互联网公司的大牛架构师,欢迎架构师加微信qshuguang2008申请入群,备注姓名+公司+职位。 为推荐系统选择正确的推荐算法非常重要,而可用的算法很多,想要找到最适合所处理问题的算法还是很有难度的。这些算法每种都各有优劣,也各有局限,因此在作出决策前我们应当对其做以衡量。在实践中,我们很可能需要测试多种算法,以便找出最适合用户的那种;了解这些算法的概念以及工作原理,对它们有个直观印象将会很有帮助。 推荐算法通常是在推荐模型中实现的,而推荐模型会负责收集诸如用户偏好、物品描述这些可用作推荐凭借的数据,据此预测特定用户组可能感兴趣的物品。 主要的推荐算法系列有四个(表格1-4):
  • 协同过滤(Collaborative Filtering)的推荐算法
  • 基于内容过滤(Content-based Filtering)的推荐算法
  • 混合型推荐算法
  • 流行度推荐算法
此外,还有很多高级或非传统的方式,可参见表格5。 本文是系列文中的第一篇,将会以表格形式来介绍推荐算法的主要分类,包括算法简介、典型的输入内容、常见的形式及其优劣。在系列文的第二与第三篇中,我们将会更详细地介绍各种算法的不同,以便让大家更深入地理解其工作原理。本文的某些内容是基于一篇2014年的推荐算法2014教程《推荐问题再探(Recommender Problem Revisited)》来撰写的,该文的作者是Xavier Amatriain表格一:协同过滤推荐算法概览 图片描述 表格二:基于内容过滤的推荐算法概览 图片描述 表格三:混合方式的推荐算法概览 图片描述 表格四:流行度推荐算法概览 图片描述 表格五:高级或“非传统”推荐算法概览 图片描述
第一部分原文:Overview of Recommender Algorithms – Part 1

推荐算法概览(二)

本文是系列文中的第二篇,将会列出推荐算法的备忘列表,介绍推荐算法的主要分类。在本文中,我们会更详细地介绍协同过滤推荐算法,并讨论其优劣,以便大家更深刻地理解其工作原理。 协同过滤(CF)推荐算法会寻找用户的行为模式,并据此创建用户专属的推荐内容。这种算法会根据系统中的用户使用数据——比如用户对读过书籍的评论来确定用户对其喜爱程度。关键概念在于:如果两名用户对于某件物品的评分方式类似,那么他们对于某个新物品的评分很可能也是相似的。值得注意的是:这种算法无需再额外依赖于物品信息(比如描述、元数据等)或者用户信息(比如感兴趣的物品、统计数据等)。协同过滤推荐算法可分为两类:基于邻域的与基于模型的。在前一种算法(也就是基于内存的协同过滤推荐算法)中,用户-物品评分可直接用以预测新物品的评分。而基于模型的算法则通过评分来研究预测性的模型,再根据模型对新物品作出预测。大致理念就是通过机器学习算法,在数据中找出模式,并将用户与物品间的互动方式模式化。 基于邻域的协同过滤则着眼于物品之间的关系(即基于物品的协同过滤)或者用户之间的关系(基于用户的协同过滤)。
  • 基于用户的协同过滤是探索对物品拥有相似品味的用户,并基于彼此喜爱的物品来进行互推。
  • 基于物品的协同过滤是用户喜爱的物品,推荐类似的东西。而这种相似性建立在物品同时出现的基础上,比如购买了x物品的用户也购买了y物品。
首先,在执行基于物品的协同过滤前,我们先看一个基于用户的协同过滤案例。 假设我们有一些用户已经表达了他们对某些书籍的偏好,他们越喜欢某本书,对这本书的评分也越高(评分范围是1分到5分)。我们可以在一个矩阵中重现他们的这种偏好,用行代表用户,用列代表书籍。 图片描述
图一:用户书籍偏好所有偏好的范围都是1分到5分,5分是最高(也就是最喜欢的)。第一个用户(行1)给第一本书(列1)的评分为4分,如果某个单元格为空,代表着用户并未对这本书作出评价。
在基于用户的协同过滤算法中,我们要做的第一件事就是根据用户对书籍的偏好,计算出他们彼此间的相似度。我们从某个单独用户的角度来看一下这个问题,以图一中第一行的用户为例。通常我们会将每个用户都作为向量(或者数组),其中包含了用户对物品的偏好。通过多种类似的指标对用户进行对比是相当直接的。在本例中,我们会使用余弦相似点。我们将第一位用户与其他五位相对比,可以发现第一位与其他用户的相似度有多少(图二)。就像大多相似度指标一样,向量之间的相似度越高,彼此也就越相似。在本例中,第一位用户与其中两位有两本相同的书籍,相似度较高;与另两位只有一本相同书籍,相似度较低;与最后一位没有相同书籍,相似度为零。 图片描述
图二:第一位用户与其他用户的相似性。可以在一个单独的维度中绘制用户间的余弦相似性。
更常见的情况下,我们可以计算出每名用户与所有用户的相似程度,并在相似性矩阵中表现出来(图三)。这是一个对称矩阵,也就是说其中一些有用的属性是可以执行数学函数运算的。单元格的背景色表明了用户彼此间的相似程度,红色越深则相似度越高。 图片描述
图三:用户间的相似矩阵,每个用户的相似度是基于用户阅读书籍间的相似性。
现在,我们准备使用基于用户的协同过滤来生成给用户的推荐。对于特定的用户来说,这代表着找出与其相似性最高的用户,并根据这些类似用户喜爱的物品进行推荐,具体要参照用户相似程度来加权。我们先以第一个用户为例,为其生成一些推荐。首先我们找到与这名用户相似程度最高的n名用户,删除这名用户已经喜欢过的书籍,再对最相似用户阅读过的书籍进行加权,之后将所有结果加在一起。在本例中,我们假设n=2,也就是说取两名与第一位用户最相似的用户,以生成推荐结果,这两名用户分别是用户2及用户3(图四)。由于第一名用户已经对书籍1和书籍5做出了评分,推荐结果生成书籍3(分数4.5)及书籍4(分数3)。 图片描述
图四:为一名用户生成推荐。我们取这两名最相似的用户所阅读的书籍,进行加权,然后对这名用户尚未评分的书籍进行推荐。
现在我们对基于用户的协同过滤有了更深刻的理解,之后来看一个基于物品的协同过滤的案例。我们还是用同一组用户(图一)为例。 在基于物品的协同过滤中,与基于用户的协同过滤类似,我们要做的第一件事就是计算相似度矩阵。但这一回,我们想要针对物品而非用户来看看它们之间的相似性。与之前类似,我们以书籍作为喜爱者的向量(或数组),将其与余弦相似度函数相对比,从而揭示出某本书籍与其他书籍之间的相似程度。由于同一组用户给出的评分大致类似,位于列1的第一本书与位于列5的第五本书相似度是最高的(图五)。其次是相似度排名第三的书籍,有两位相同的用户喜爱;排名第四和第二的书籍只有一位共同读者;而排名最后的书籍由于没有共同读者,相似度为零。 图片描述
图五:第一本书与其他书籍的对比。书籍通过所阅读用户的评价来表现。通过余弦相似度指标(0-1)来进行对比,相似度越高,两本书就越相似。
我们还可以在相似矩阵中展示出所有书籍彼此间的相似程度(图六)。同样以背景颜色区分了两本书彼此间的相似程度,红色越深相似程度也越高。 图片描述
图六:书籍的相似矩阵
现在我们知道每本书彼此间的相似程度了,可以为用户生成推荐结果。在基于物品的协同过滤中,我们根据用户此前曾评过分的物品,推荐与其最为相似的物品。在案例中,第一位用户获得的推荐结果为第三本书籍,然后是第六本(图七)。同样地,我们只取与用户之前评论过的书籍最相似的两本书。 图片描述
图七:为某位用户生成推荐结果。我们取到他们之前评论过的书籍目录,找出与每本书籍最相似的两本,再对用户尚未评论过的书籍进行推荐。
根据上述描述,基于用户与基于物品的协同过滤似乎非常类似,因此能得出不同的结果这一点确实很有意思。即便在上例中,这两种方式都能为同一名用户得出不同的推荐结果,尽管两者的输入内容是相同的。在构建推荐时,这两种形式的协同过滤方式都是值得考虑的。尽管在向外行描述时,这两种方法看起来非常类似,但实际上它们能得出非常不同的推荐结果,从而为用户带来完全不同的体验。 由于简单高效,且生成的推荐结果准确、个性化,邻域方法也是相当受欢迎的。但由于要计算(用户或物品间的)相似度,随着用户或物品数量的增长,也会出现一些伸缩性方面的局限。在最糟的情况下,需要计算O(m*n),但在现实中情况略好一些,只要计算O(m+n)即可,部分原因在于利用了数据的稀疏度。尽管稀疏度有助于扩展实现,但同时也为基于邻域的方法提出了挑战,因为在海量的物品中,仅有少量是有用户评论过的。例如,Mendeley系统中有数百万篇文章,而一名用户也许只读过其中几百篇。两名各读过100篇文章的用户具有相似度的可能性仅为0.0002(在5000万篇文章的目录中)。 基于模型的协同过滤方式可以克服基于邻域方法的限制。与使用用户-物品评分直接预测新物品评分的邻域方式不同,基于模型的方法则使用评分来研究预测性模型,并根据模型来预测新物品。大致理念就是通过机器学习算法,在数据中找出模式,并将用户与物品间的互动方式模式化。总体来讲,基于模型的协同过滤方式是构建协同过滤更高级的算法。很多不同的算法都能用来构建模型,以进行预测;例如贝叶斯网络、集群、分类、回归、矩阵因式分解、受限波尔兹曼机等,这些技术其中有些在获得Netflix Prize奖项时起到了关键性作用。Netflix在2006年到2009年间举办竞赛,当时还为能够生成准确度超过其系统10%的推荐系统制作团队提供100万美元的大奖。胜出的解决方案是一套综合了逾100种不同算法模型,并在生产环境中采用了矩阵因式分解与受限玻尔兹曼机的方法。 矩阵因式分解(比如奇异值分解、SVD++)将物品与用户都转化为同一个隐空间,表现了用户与物品间的底层互动(图八)。矩阵因式分解背后的原理在于:其潜在特性代表了用户如何对物品进行评分。根据用户与物品的潜在表现,我们就可以预测用户对未评分的物品的喜爱程度。 图片描述
图八:矩阵分解算法的演示,用户偏好矩阵可以分解为用户主题矩阵乘以物品主题矩阵。
在表一中,我们列出了邻域算法与基于模型的协同过滤算法的关键优劣点。由于协同过滤算法只依赖于用户的使用数据,想要生成足够优秀的推荐结果无需对技术工作有太多了解,但这种算法也有其局限。例如,CF更容易推荐流行物品,因此为品味独特的用户推荐物品时就会比较困难(即对其感兴趣的物品可能不具有太多的使用数据),也就是流行度偏好的问题,这一点通常可以通过基于内容的过滤算法解决。CF算法更重要的一个限制就是所谓的“冷启动问题”——系统无法为没有或使用行为很少的用户提供推荐(也就是新用户的问题),也无法为没有或使用行为很少的物品提供推荐(也就是新物品的问题)。新用户的“冷启动问题”可以通过流行度和混合算法来解决,而新物品问题可以通过基于内容过滤或multi-armed bandit推荐算法(即探索-利用)来解决。在下篇文章中我们会详细讨论其中一些算法。 本文中,我们介绍了三种基本的协同过滤算法实现。基于物品、基于用户的协同过滤算法,以及矩阵分解算法之间的区别都很细微,通常很难简单地解释其差异。理解这些算法间的差异有助于我们选择推荐系统最适合的算法。在下篇文章中,我们会继续深入探讨推荐系统的流行算法。
第二部分原文:Overview of Recommender Algorithms – Part 2

推荐算法概览(三)

本文是系列文中的第三篇。第一篇文章通过列表形式介绍了推荐算法的主要分类,第二篇文章介绍了不同类型的协同过滤算法,强调了其间的一些细微差别。在本文中,我们将会更加详细地介绍基于内容的过滤算法并讨论其优缺点,以更好地理解其工作原理。 基于内容的过滤算法会推荐与用户最喜欢的物品类似的那些。但是,与协同过滤算法不同,这种算法是根据内容(比如标题、年份、描述),而不是人们使用物品的方式来总结其类似程度的。例如,如果某个用户喜欢电影《魔戒》的第一部和第二部,那么推荐系统会通过标题关键字向用户推荐《魔戒》的第三部。在基于内容的过滤算法中,会假设每个物品都有足够的描述信息可作为特征向量(y)(比如标题、年代、描述),而这些特征向量会被用来创建用户偏好模型。各种信息检索(比如tf-idf)以及机器学习技术(比如朴素贝叶斯算法、支持向量机、决策树等)都可用于生成用户模型,之后再根据模型来进行推荐。 假设我们有一些用户已经表达了他们对某些书籍的偏好,他们越喜欢某本书,对这本书的评分也越高(评分范围是1分到5分)。我们可以在一个矩阵中重现他们的这种偏好,用行代表用户,用列代表书籍。 图片描述
图一:用户书籍偏好所有偏好的范围都是1分到5分,5分是最高的(也就是最喜欢的)。第一个用户(行1)给第一本书(列1)的评分为4分,如果某个单元格为空,代表着用户并未对这本书作出评价。
在基于内容的协同过滤算法中,我们要做的第一件事就是根据内容,计算出书籍之间的相似度。在本例中,我们使用了书籍标题中的关键字(图二),这只是为了简化而已。在实际中我们还可以使用更多的属性。 图片描述
图二:用户已经评论过的书籍标题
首先,通常我们要从内容中删除停止词(比如语法词、过于常见的词),然后用代表出现哪些词汇的向量(或数组)对书籍进行表示(图三),这就是所谓的向量空间表示图片描述
图三:使用标题的词汇如果在标题中有这个词,我们以1为标记,否则为空。
有了这个表格,我们就可以使用各种相似指标直接对比各本书籍。在本例中,我们会使用余弦相似点。当我们使用第一本书籍,将其与其他五本书籍对比时,就能看到第一本书籍与其他书籍的相似程度(图四)。就像大多相似度指标一样,向量之间的相似度越高,彼此也就越相似。在本例中,第一本书与其他三本书都很类似,都有两个共同的词汇(推荐和系统)。而标题越短,两本书的相似程度越高,这也在情理之中,因为这样一来,不相同的词汇也就越少。鉴于完全没有共同词汇,第一本书与其他书籍中的两本完全没有类似的地方。 图片描述
图四:第一本书与其他书籍间的相似性在单个维度中通过两本书之间的余弦相似度就能绘制出来。
我们还可以在相似矩阵中展示出所有书籍彼此间的相似程度(图五)。单元格的背景色表明了用户彼此间的相似程度,红色越深相似度越高。 图片描述
图五:书籍间的相似矩阵,每个相似点都是基于书籍向量表示之间的余弦相似度。
现在我们知道每本书彼此间的相似程度了,可以为用户生成推荐结果。与基于物品的协同过滤方式类似,我们在之前的文章中也介绍过,推荐系统会根据用户之前评价过的书籍,来推荐其他书籍中相似度最高的。区别在于:相似度是基于书籍内容的,准确来说是标题,而不是根据使用数据。在本例中,系统会给第一个用户推荐第六本书,之后是第四本书(图六)。同样地,我们只取与用户之前评论过的书籍最相似的两本书。 图片描述
图六:为某个用户生成推荐结果。我们取到他们之前评论过的书籍目录,找出与每本书籍最相似的两本,再对用户尚未评论过的书籍进行推荐。
基于内容的算法解决了协同过滤算法的某些限制,尤其能协助我们克服流行度偏见,以及新物品的冷启动问题,而这些我们已经在协同过滤的部分中讨论过了。然而,值得注意的是:纯粹基于内容的推荐系统通常在执行时效果不如那些基于使用数据的系统(比如协同过滤算法)。基于内容过滤的算法也会有过度专业化的问题,系统可能会向用户推荐过多相同类型的物品(比如获得所有《魔戒》电影的推荐),而不会推荐那些虽然类型不同,但是用户也感兴趣的物品。最后,基于内容的算法在实现时只会使用物品元数据中所含的词汇(比如标题、描述年份),更容易推荐更多相同的内容,限制了用户探索发现这些词汇之外的内容。关于基于内容过滤的优劣总结见表二。
第三部分原文:Overview of Recommender Algorithms – Part 3

推荐算法概览(四)

本文是系列文中的第四篇。第一篇文章通过列表形式介绍了推荐算法的主要分类,第二篇文章介绍了不同类型的协同过滤算法,强调了其间的一些细微差别,在第三篇中我们详细介绍了基于内容的过滤算法。本文将会讨论基于之前提过算法而形成的混合型推荐系统,也会简单讨论如何利用流行度来解决一些协同过滤算法与基于内容过滤算法的限制。 混合算法结合了用户及物品的内容特性以及使用数据,以利用这两类数据的优点。结合了A算法与B算法的某个混合型推荐系统会尝试利用A算法的优点以解决B算法的缺点。例如,协同过滤算法存在新物品的问题,也就是说这种算法无法推荐用户未评价或使用过的物品。因为是基于内容(特性)预测的,这一点并不会对基于内容的算法产生限制。而结合了协同过滤与基于内容过滤算法的混合型推荐系统能够解决单个算法中的一些限制,比如冷启动的问题与流行度偏好的问题。表一列出了一些不同的方法,包括如何结合两种甚至更多基础推荐系统技术,以创建新的混合型系统。 图片描述
表一:结合两种甚至更多的基础推荐算法,以创建新混合算法的不同方式。
假设我们有一些用户已经表达了他们对某些书籍的偏好,他们越喜欢某本书,对这本书的评分也越高(评分级别分别为1-5)。我们可以在一个矩阵中重现他们的这种偏好,用行代表用户,用列代表书籍。 图片描述
图一:用户书籍偏好所有偏好的范围都是1分到5分,5分是最高的(也就是最喜欢的)。第一名用户(行1)对第一本书(列1)的评分为4分,如果某个单元格为空,代表着用户并未对这本书作出评价。
在本文所属系列的第二篇中,我们给出了两个案例,包括如何使用基于物品及基于用户的协同过滤算法来计算推荐结果;在本文所属系列的第三篇中,我们演示了如何使用基于内容的过滤算法来生成推荐结果。现在我们将这三种不同的算法结合起来,生成一种全新的混合型推荐结果。我们会使用加权法(表一)结合多种技术得出的结果,之后这三种算法便可按照不同的权值(根据重要性不同)结合得出一组新的推荐结果。 我们先以第一个用户为例,为其生成一些推荐。首先我们会根据第二篇中的基于用户的协同过滤算法,基于物品的协同过滤算法,以及第三篇中基于内容过滤的算法,各自生成推荐结果。值得注意的是,在这个小案例中,这三种方法为同一名用户生成的推荐结果有着轻微的差异,尽管输入的内容是完全相同的。 图片描述
图二:为某个用户生成的推荐结果——分别使用基于用户的协同过滤算法,基于物品的协同过滤算法,以及基于内容的过滤算法。
下一步,我们使用加权混合推荐算法为指定用户生成推荐结果,加权值分别为:基于用户的协同过滤算法40%,基于物品的协同过滤算法30%,基于内容过滤的算法30%(图三)。在这个案例中,系统会向用户推荐他们从未看过的所有三本书,而使用单个算法只会推荐其中两本。 图片描述
图三:使用加权混合推荐系统为某个用户生成推荐结果,具体权值参见上文。
尽管混合型算法解决了CF与CB算法的一些重大挑战与限制(见图三),但在系统中平衡不同的算法也需要很多工作量。另一种结合单个推荐算法的方式是使用集成方法,关于如何结合不同算法得出的结果,我们研究得出了一个函数。值得注意的是:通常集成算法不仅结合了不同的算法,还结合了根据同一种算法得出的不同变体与模型。例如:获得Netflix Prize奖项的解决方案包含了从10多种算法(流行度、邻域算法、矩阵分解算法、受限波尔兹曼机、回归等等)得出的100多种不同的模型,并通过迭代决策树(GBDT)将这些算法与模型结合在一起。 另外,基于流行度的算法对于新用户的冷启动问题来说也是一个优秀的解决方案。这些算法通过某些流行度的测量标准,比如下载最多的或者购买最多的,来对物品进行排名,并将这些流行度最高的物品推荐给新用户。当拥有合适的流行度衡量指标时,这个办法虽然基础却很有效,通常可以为其他算法提供很好的基线标准。流行度算法也可以单独作为算法使用,以引导推荐系统在换到其他更切合用户兴趣点的算法(比如协同过滤算法以及基于内容过滤的算法)前获得足够的活跃度与使用量。流行度模型也可以引入混合算法中,从而解决新用户的冷启动问题。
第四部分原文:Overview of Recommender Algorithms – Part 4

推荐算法概览(五)

本文是推荐算法系列文中的第五篇。第一篇文章通过列表形式介绍了推荐算法的主要分类,第二篇文章介绍了不同类型的协同过滤算法,强调了其间的一些细微差别,在第三篇中我们详细介绍了基于内容的过滤算法,在第四篇中我们讲解了混合型推荐系统以及基于流行度的算法。在本篇中,我们会对简单了解一下如何对一些高级的推荐算法做以选择,再回顾一下基础算法得出的推荐结果差异有多大,以便本系列完美收官。 除了我们截至目前提到的一些更为传统的推荐系统算法之外(比如流行度算法协同过滤算法基于内容过滤的算法混合型算法),还有许多其他算法也可用于加强推荐系统的功能,包括有:
  • 深度学习算法
  • 社会化推荐
  • 基于机器学习的排序方法
  • Multi-armed bandits推荐算法(探索/利用)
  • 情景感知推荐(张量分解&分解机)
这些更为高级的非传统算法对于将现有推荐系统的质量推向更高层次很有好处,但理解起来也更困难,推荐工具的支持上也不够。在实践中,我们总要权衡实现高级算法的代价与对基础算法的增益相比较是否值得。根据经验来看,基础算法还能使用很久,为一些很优秀的产品提供服务。 在本系列文中,我们希望介绍一些常见的推荐模块算法,包括基于用户的协同过滤算法,基于物品的协同过滤算法,基于内容的过滤算法,以及混合型算法。我们使用了一个案例,说明了这四种不同算法在输入数据相同时,应用于同一个案例时,为同一个用户生成的不同推荐结果(图一)。当应用于真实世界的大型数据时,这样的效应依然存在,因此决定使用哪种算法时,需要考虑相关的优缺点以及执行效果。 图片描述
图一:四种推荐系统算法全部使用同一组数据,却得出了不同的结果。左侧我们给出了一个矩阵,标明了用户对大量物品的不同偏好,并列出了可以推荐的物品名称。在中间,我们展示了这四种不同算法如何为第一名用户生成推荐结果(也就是用户偏好矩阵中第一行的用户)。就像相似矩阵中展示的那样,这些算法对相似度有不同的定义。右侧则是每种推荐算法生成的一些物品,从上到下分别按照四种算法的介绍顺序来排序。
在实践中,一般如果在推荐模型中使用协同过滤算法,就不会犯太大错误。协同过滤算法似乎比其他算法更优秀,但在冷启动用户及物品方面会有问题,因此通常会使用基于内容的算法作为辅助。如果有时间的话,使用混合型算法就可以结合协同过滤及基于内容过滤算法优势了。将这些基础算法放在一起当然是个好办法,甚至比高级算法还要更好。 最后这一点值得牢记:推荐模型只是五个推荐系统组件中的一个。与所有组件类似,正确设置并努力建立模型非常重要,不过选择数据集、处理、后处理、在线模块及用户界面也同样重要。正如我们再三强调的那样,算法只是推荐系统的一部分,整个产品应当将你的决策纳入考量。

Zookeeper通信模型/架构/流程

$
0
0
本文的主题就是讲解Zookeeper通信模型,本节将通过一个概要图来说明Zookeeper的通信模型。 本文地址:http://www.paymoon.com:8001/index.php/2016/07/29/zookeepercommunication-model-process-architecture/
   

Zookeeper的通信架构

在Zookeeper整个系统中,有3中角色的服务,client、Follower、leader。其中client负责发起应用的请求,Follower接受client发起的请求,参与事务的确认过程,在leader crash后的leader选择。而leader主要承担事务的协调,当然leader也可以承担接收客户请求的功能,为了方便描述,后面的描述都是client与Follower之间的通信,如果Zookeeper的配置支持leader接收client的请求,client与leader的通信跟client与Follower的通信模式完全一样。Follower与leader之间的角色可能在某一时刻进行转换。一个Follower在leader crash掉以后可能被集群(Quorum)的Follower选举为leader。而一个leader在crash后,再次加入集群(Quorum)将作为Follower角色存在。在一个集群(Quorum)中,除了在选举leader的过程中没有Follower和leader的区分外,其他任何时刻都只有1个leader和多个Follower。Client、Follower和leader之间的通信架构如下:   Client与Follower之间 为了使客户端具有较高的吞吐量,Client与Follower之间采用NIO的通信方式。当client需要与Zookeeper service打交道时,首先读取配置文件确定集群内的所有server列表,按照一定的load balance算法选取一个Follower作为一个通信目标。这样client和Follower之间就有了一条由NIO模式构成的通信通道。这条通道会一直保持到client关闭session或者因为client或Follower任一方因某种原因异常中断通信连接。正常情况下, client与Follower在没有请求发起的时候都有心跳检测。      Follower与leader之间 Follower与leader之间的通信主要是因为Follower接收到像(create, delete, setData, setACL, createSession, closeSession, sync)这样一些需要让leader来协调最终结果的命令,将会导致Follower与leader之间产生通信。由于leader与Follower之间的关系式一对多的关系,非常适合client/server模式,因此他们之间是采用c/s模式,由leader创建一个socket server,监听各Follower的协调请求。    集群在选择leader过程中 由于在选择leader过程中没有leader,在集群中的任何一个成员都需要与其他所有成员进行通信,当集群的成员变得很大时,这个通信量是很大的。选择leader的过程发生在Zookeeper系统刚刚启动或者是leader失去联系后,选择leader过程中将不能处理用户的请求,为了提高系统的可用性,一定要尽量减少这个过程的时间。选择哪种方式让他们可用快速得到选择结果呢?Zookeeper在这个过程中采用了策略模式,可用动态插入选择leader的算法。系统默认提供了3种选择算法,AuthFastLeaderElection,FastLeaderElection,LeaderElection。其中AuthFastLeaderElection和LeaderElection采用UDP模式进行通信,而FastLeaderElection仍然采用tcp/ip模式。在Zookeeper新的版本中,新增了一个learner角色,减少选择leader的参与人数。使得选择过程更快。一般说来Zookeeper leader的选择过程都非常快,通常<200ms。  

Zookeeper的通信流程

要详细了解Zookeeper的通信流程,我们首先得了解Zookeeper提供哪些客户端的接口,我们按照具有相同的通信流程的接口进行分组:  

Zookeeper系统管理命令

Zookeeper的系统管理接口是指用来查看Zookeeper运行状态的一些命令,他们都是具有4字母构成的命令格式。主要包括:
  1. ruok:发送此命令可以测试zookeeper是否运行正常。
  2. dump:dump server端所有存活session的Ephemeral(临时)node信息。
  3. stat:获取连接server的服务器端的状态及连接该server的所有客服端的状态信息。
  4. reqs: 获取当前客户端已经提交但还未返回的请求。
  5. stmk:开启或关闭Zookeeper的trace level.
  6. gtmk:获取当前Zookeeper的trace level是否开启。
  7. envi: 获取Zookeeper的java相关的环境变量。
  8. srst:重置server端的统计状态
当用户发送这些命令的到server时,由于这些请求只与连接的server相关,没有业务处理逻辑,非常简单。Zookeeper对这些命令采用最快的效率进行处理。这些命令发送到server端只占用一个4字节的int类型来表示不同命令,没有采用字符串处理。当服务器端接收到这些命令,立刻返回结果。   Session创建 任何客户端的业务请求都是基于session存在的前提下。Session是维持client与Follower之间的一条通信通道,并维持他们之间从创建开始后的所有状态。当启动一个Zookeeper client的时候,首先按照一定的算法查找出follower, 然后与Follower建立起NIO连接。当连接建立好后,发送create session的命令,让server端为该连接创建一个维护该连接状态的对象session。当server收到create session命令,先从本地的session列表中查找看是否已经存在有相同sessionId,则关闭原session重新创建新的session。创建session的过程将需要发送到Leader,再由leader通知其他follower,大部分Follower都将此操作记录到本地日志再通知leader后,leader发送commit命令给所有Follower,连接客户端的Follower返回创建成功的session响应。Leader与Follower之间的协调过程将在后面的做详细讲解。当客户端成功创建好session后,其他的业务命令就可以正常处理了。   Zookeeper查询命令 Zookeeper查询命令主要用来查询服务器端的数据,不会更改服务器端的数据。所有的查询命令都可以即刻从client连接的server立即返回,不需要leader进行协调,因此查询命令得到的数据有可能是过期数据。但由于任何数据的修改,leader都会将更改的结果发布给所有的Follower,因此一般说来,Follower的数据是可以得到及时的更新。这些查询命令包括以下这些命令:
  1. exists:判断指定path的node是否存在,如果存在则返回true,否则返回false.
  2. getData:从指定path获取该node的数据
  3. getACL:获取指定pathACL
  4. getChildren:获取指定pathnode的所有孩子结点。
所有的查询命令都可以指定watcher,通过它来跟踪指定path的数据变化。一旦指定的数据发生变化(create,delete,modified,children_changed),服务器将会发送命令来回调注册的watcher. Watcher详细的讲解将在Zookeeper的Watcher中单独讲解。   Zookeeper修改命令 Zookeeper修改命令主要是用来修改节点数据或结构,或者权限信息。任何修改命令都需要提交到leader进行协调,协调完成后才返回。修改命令主要包括:
  1. 1.   createSession请求server创建一个session
  2. 2.   create创建一个节点
  3. 3.   delete删除一个节点
  4. 4.   setData修改一个节点的数据
  5. 5.   setACL修改一个节点的ACL
  6. 6.   closeSession请求server关闭session
我们根据前面的通信图知道,任何修改命令都需要leader协调。 在leader的协调过程中,需要3次leader与Follower之间的来回请求响应。并且在此过程中还会涉及事务日志的记录,更糟糕的情况是还有take snapshot的操作。因此此过程可能比较耗时。但Zookeeper的通信中最大特点是异步的,如果请求是连续不断的,Zookeeper的处理是集中处理逻辑,然后批量发送,批量的大小也是有控制的。如果请求量不大,则即刻发送。这样当负载很大时也能保证很大的吞吐量,时效性也在一定程度上进行了保证。  

zookeeper server端的业务处理-processor链

  <!--EndFragment--> Zookeeper通过链式的processor来处理业务请求,每个processor负责处理特定的功能。不同的Zookeeper角色的服务器processor链是不一样的,以下分别介绍standalone Zookeeper server, leader和Follower不同的processor链。   Zookeeper中的processor
  1. AckRequestProcessor:当leader从向Follower发送proposal后,Follower将发送一个Ack响应,leader收到Ack响应后,将会调用这个Processor进行处理。它主要负责检查请求是否已经达到了多数Follower的确认,如果满足条件,则提交commitProcessor进行commit处理
  2. CommitProcessor:从commited队列中处理已经由leader协调好并commit的请求或者从请求队列中取出那些无需leader协调的请求进行下一步处理。
  3. FinalRequestProcessor:任何请求的处理都需要经过这个processor,这是请求处理的最后一个Processor,主要负责根据不同的请求包装不同的类型的响应包。当然Follower与leader之间协调后的请求由于没有client连接,将不需要发送响应(代码体现在if (request.cnxn == null) {return;})。
  4. FollowerRequestProcessor:Follower processor链上的第一个,主要负责将修改请求和同步请求发往leader进行协调。
  5. PrepRequestProcessor:在leader和standalone server上作为第一Processor,主要作用对于所有的修改命令生成changelog。
  6. ProposalRequestProcessor:leader用来将请求包装为proposal向Follower请求确认。
  7. SendAckRequestProcessor:Follower用来向leader发送Ack响应的处理。
  8. SyncRequestProcessor:负责将已经commit的事务写到事务日志以及take snapshot.
  9. ToBeAppliedRequestProcessor:负责将tobeApplied队列的中request转移到下一个请求进行处理。
  Standalone zookeeper processor链     Leader processor链 Follower processor链      

Java中的锁

$
0
0
 

锁的释放-获取建立的happens before 关系

锁是java并发编程中最重要的同步机制。锁除了让临界区互斥执行外,还可以让释放锁的线程向获取同一个锁的线程发送消息。 下面是锁释放-获取的示例代码: [crayon-57a16201169be118085470/] 假设线程A执行writer()方法,随后线程B执行reader()方法。根据happens before规则,这个过程包含的happens before 关系可以分为两类:
  1. 根据程序次序规则,1 happens before 2, 2 happens before 3; 4 happens before 5, 5 happens before 6。
  2. 根据监视器锁规则,3 happens before 4。
  3. 根据happens before 的传递性,2 happens before 5。
上述happens before 关系的图形化表现形式如下: 在上图中,每一个箭头链接的两个节点,代表了一个happens before 关系。黑色箭头表示程序顺序规则;橙色箭头表示监视器锁规则;蓝色箭头表示组合这些规则后提供的happens before保证。 上图表示在线程A释放了锁之后,随后线程B获取同一个锁。在上图中,2 happens before 5。因此,线程A在释放锁之前所有可见的共享变量,在线程B获取同一个锁之后,将立刻变得对B线程可见。

锁释放和获取的内存语义

当线程释放锁时,JMM会把该线程对应的本地内存中的共享变量刷新到主内存中。以上面的MonitorExample程序为例,A线程释放锁后,共享数据的状态示意图如下: 当线程获取锁时,JMM会把该线程对应的本地内存置为无效。从而使得被监视器保护的临界区代码必须要从主内存中去读取共享变量。下面是锁获取的状态示意图: 对比锁释放-获取的内存语义与volatile写-读的内存语义,可以看出:锁释放与volatile写有相同的内存语义;锁获取与volatile读有相同的内存语义。 下面对锁释放和锁获取的内存语义做个总结:
  • 线程A释放一个锁,实质上是线程A向接下来将要获取这个锁的某个线程发出了(线程A对共享变量所做修改的)消息。
  • 线程B获取一个锁,实质上是线程B接收了之前某个线程发出的(在释放这个锁之前对共享变量所做修改的)消息。
  • 线程A释放锁,随后线程B获取这个锁,这个过程实质上是线程A通过主内存向线程B发送消息。

锁内存语义的实现

本文将借助ReentrantLock的源代码,来分析锁内存语义的具体实现机制。 请看下面的示例代码: [crayon-57a16201169d1062683555/] 在ReentrantLock中,调用lock()方法获取锁;调用unlock()方法释放锁。 ReentrantLock的实现依赖于java同步器框架AbstractQueuedSynchronizer(本文简称之为AQS)。AQS使用一个整型的volatile变量(命名为state)来维护同步状态,马上我们会看到,这个volatile变量是ReentrantLock内存语义实现的关键。 下面是ReentrantLock的类图(仅画出与本文相关的部分): ReentrantLock分为公平锁和非公平锁,我们首先分析公平锁。 使用公平锁时,加锁方法lock()的方法调用轨迹如下:
  1. ReentrantLock : lock()
  2. FairSync : lock()
  3. AbstractQueuedSynchronizer : acquire(int arg)
  4. ReentrantLock : tryAcquire(int acquires)
在第4步真正开始加锁,下面是该方法的源代码: [crayon-57a16201169dd386353285/] 从上面源代码中我们可以看出,加锁方法首先读volatile变量state。 在使用公平锁时,解锁方法unlock()的方法调用轨迹如下:
  1. ReentrantLock : unlock()
  2. AbstractQueuedSynchronizer : release(int arg)
  3. Sync : tryRelease(int releases)
在第3步真正开始释放锁,下面是该方法的源代码: [crayon-57a16201169fc028432270/] 从上面的源代码我们可以看出,在释放锁的最后写volatile变量state。 公平锁在释放锁的最后写volatile变量state;在获取锁时首先读这个volatile变量。根据volatile的happens-before规则,释放锁的线程在写volatile变量之前可见的共享变量,在获取锁的线程读取同一个volatile变量后将立即变的对获取锁的线程可见。 现在我们分析非公平锁的内存语义的实现。 非公平锁的释放和公平锁完全一样,所以这里仅仅分析非公平锁的获取。 使用公平锁时,加锁方法lock()的方法调用轨迹如下:
  1. ReentrantLock : lock()
  2. NonfairSync : lock()
  3. AbstractQueuedSynchronizer : compareAndSetState(int expect, int update)
在第3步真正开始加锁,下面是该方法的源代码: [crayon-57a1620116a05246837879/] 该方法以原子操作的方式更新state变量,本文把java的compareAndSet()方法调用简称为CAS。JDK文档对该方法的说明如下:如果当前状态值等于预期值,则以原子方式将同步状态设置为给定的更新值。此操作具有 volatile 读和写的内存语义。 这里我们分别从编译器和处理器的角度来分析,CAS如何同时具有volatile读和volatile写的内存语义。 前文我们提到过,编译器不会对volatile读与volatile读后面的任意内存操作重排序;编译器不会对volatile写与volatile写前面的任意内存操作重排序。组合这两个条件,意味着为了同时实现volatile读和volatile写的内存语义,编译器不能对CAS与CAS前面和后面的任意内存操作重排序。 下面我们来分析在常见的intel x86处理器中,CAS是如何同时具有volatile读和volatile写的内存语义的。 下面是sun.misc.Unsafe类的compareAndSwapInt()方法的源代码: [crayon-57a1620116a0b145115283/] 可以看到这是个本地方法调用。这个本地方法在openjdk中依次调用的c++代码为:unsafe.cpp,atomic.cpp和atomicwindowsx86.inline.hpp。这个本地方法的最终实现在openjdk的如下位置:openjdk-7-fcs-src-b147-27jun2011\openjdk\hotspot\src\oscpu\windowsx86\vm\ atomicwindowsx86.inline.hpp(对应于windows操作系统,X86处理器)。下面是对应于intel x86处理器的源代码的片段: [crayon-57a1620116a11934893649/] 如上面源代码所示,程序会根据当前处理器的类型来决定是否为cmpxchg指令添加lock前缀。如果程序是在多处理器上运行,就为cmpxchg指令加上lock前缀(lock cmpxchg)。反之,如果程序是在单处理器上运行,就省略lock前缀(单处理器自身会维护单处理器内的顺序一致性,不需要lock前缀提供的内存屏障效果)。 intel的手册对lock前缀的说明如下:
  1. 确保对内存的读-改-写操作原子执行。在Pentium及Pentium之前的处理器中,带有lock前缀的指令在执行期间会锁住总线,使得其他处理器暂时无法通过总线访问内存。很显然,这会带来昂贵的开销。从Pentium 4,Intel Xeon及P6处理器开始,intel在原有总线锁的基础上做了一个很有意义的优化:如果要访问的内存区域(area of memory)在lock前缀指令执行期间已经在处理器内部的缓存中被锁定(即包含该内存区域的缓存行当前处于独占或以修改状态),并且该内存区域被完全包含在单个缓存行(cache line)中,那么处理器将直接执行该指令。由于在指令执行期间该缓存行会一直被锁定,其它处理器无法读/写该指令要访问的内存区域,因此能保证指令执行的原子性。这个操作过程叫做缓存锁定(cache locking),缓存锁定将大大降低lock前缀指令的执行开销,但是当多处理器之间的竞争程度很高或者指令访问的内存地址未对齐时,仍然会锁住总线。
  2. 禁止该指令与之前和之后的读和写指令重排序。
  3. 把写缓冲区中的所有数据刷新到内存中。
上面的第2点和第3点所具有的内存屏障效果,足以同时实现volatile读和volatile写的内存语义。 经过上面的这些分析,现在我们终于能明白为什么JDK文档说CAS同时具有volatile读和volatile写的内存语义了。 现在对公平锁和非公平锁的内存语义做个总结:
  • 公平锁和非公平锁释放时,最后都要写一个volatile变量state。
  • 公平锁获取时,首先会去读这个volatile变量。
  • 非公平锁获取时,首先会用CAS更新这个volatile变量,这个操作同时具有volatile读和volatile写的内存语义。
从本文对ReentrantLock的分析可以看出,锁释放-获取的内存语义的实现至少有下面两种方式:
  1. 利用volatile变量的写-读所具有的内存语义。
  2. 利用CAS所附带的volatile读和volatile写的内存语义。

concurrent包的实现

由于java的CAS同时具有 volatile 读和volatile写的内存语义,因此Java线程之间的通信现在有了下面四种方式:
  1. A线程写volatile变量,随后B线程读这个volatile变量。
  2. A线程写volatile变量,随后B线程用CAS更新这个volatile变量。
  3. A线程用CAS更新一个volatile变量,随后B线程用CAS更新这个volatile变量。
  4. A线程用CAS更新一个volatile变量,随后B线程读这个volatile变量。
Java的CAS会使用现代处理器上提供的高效机器级别原子指令,这些原子指令以原子方式对内存执行读-改-写操作,这是在多处理器中实现同步的关键(从本质上来说,能够支持原子性读-改-写指令的计算机器,是顺序计算图灵机的异步等价机器,因此任何现代的多处理器都会去支持某种能对内存执行原子性读-改-写操作的原子指令)。同时,volatile变量的读/写和CAS可以实现线程之间的通信。把这些特性整合在一起,就形成了整个concurrent包得以实现的基石。如果我们仔细分析concurrent包的源代码实现,会发现一个通用化的实现模式:
  1. 首先,声明共享变量为volatile;
  2. 然后,使用CAS的原子条件更新来实现线程之间的同步;
  3. 同时,配合以volatile的读/写和CAS所具有的volatile读和写的内存语义来实现线程之间的通信。
AQS,非阻塞数据结构和原子变量类(java.util.concurrent.atomic包中的类),这些concurrent包中的基础类都是使用这种模式来实现的,而concurrent包中的高层类又是依赖于这些基础类来实现的。从整体来看,concurrent包的实现示意图如下:

参考文献

  1.         Concurrent Programming in Java: Design Principles and Pattern
  2.         JSR 133 (Java Memory Model) FAQ
  3.         JSR-133: Java Memory Model and Thread Specification
  4.         Java Concurrency in Practice
  5.         Java™ Platform, Standard Edition 6 API Specification
  6.         The JSR-133 Cookbook for Compiler Writers
  7.         Intel® 64 and IA-32 ArchitecturesvSoftware Developer’s Manual Volume 3A: System Programming Guide, Part 1
  8.         The Art of Multiprocessor Programming

通过分析 JDK 源代码研究 Hash 存储机制

$
0
0
散列(Hash)在java中是怎么样的一个体现? HashMap 和 HashSet 是 Java Collection Framework 的两个重要成员,其中 HashMap 是 Map 接口的常用实现类,HashSet 是 Set 接口的常用实现类。虽然 HashMap 和 HashSet 实现的接口规范不同,但它们底层的 Hash 存储机制完全一样,甚至 HashSet 本身就采用 HashMap 来实现的。

通过 HashMap、HashSet 的源代码分析其 Hash 存储机制

集合和引用

本文地址:http://www.paymoon.com:8001/index.php/2016/07/30/j-lo-hash/ 就像引用类型的数组一样,当我们把 Java 对象放入数组之时,并不是真正的把 Java 对象放入数组中,只是把对象的引用放入数组中,每个数组元素都是一个引用变量。
实际上,HashSet 和 HashMap 之间有很多相似之处,对于 HashSet 而言,系统采用 Hash 算法决定集合元素的存储位置,这样可以保证能快速存、取集合元素;对于 HashMap 而言,系统 key-value 当成一个整体进行处理,系统总是根据 Hash 算法来计算 key-value 的存储位置,这样可以保证能快速存、取 Map 的 key-value 对。 在介绍集合存储之前需要指出一点:虽然集合号称存储的是 Java 对象,但实际上并不会真正将 Java 对象放入 Set 集合中,只是在 Set 集合中保留这些对象的引用而言。也就是说:Java 集合实际上是多个引用变量所组成的集合,这些引用变量指向实际的 Java 对象。

HashMap 的存储实现

当程序试图将多个 key-value 放入 HashMap 中时,以如下代码片段为例:
[crayon-57a16201159b4541621885/]
HashMap 采用一种所谓的“Hash 算法”来决定每个元素的存储位置。 当程序执行 map.put("语文" , 80.0); 时,系统将调用"语文"的 hashCode() 方法得到其 hashCode 值——每个 Java 对象都有 hashCode() 方法,都可通过该方法获得它的 hashCode 值。得到这个对象的 hashCode 值之后,系统会根据该 hashCode 值来决定该元素的存储位置。 我们可以看 HashMap 类的 put(K key , V value) 方法的源代码:
[crayon-57a16201159c2557608538/]

JDK 源码

在 JDK 安装目录下可以找到一个 src.zip 压缩文件,该文件里包含了 Java 基础类库的所有源文件。只要读者有学习兴趣,随时可以打开这份压缩文件来阅读 Java 类库的源代码,这对提高读者的编程能力是非常有帮助的。需要指出的是:src.zip 中包含的源代码并没有包含像上文中的中文注释,这些注释是笔者自己添加进去的。
上面程序中用到了一个重要的内部接口:Map.Entry,每个 Map.Entry 其实就是一个 key-value 对。从上面程序中可以看出:当系统决定存储 HashMap 中的 key-value 对时,完全没有考虑 Entry 中的 value,仅仅只是根据 key 来计算并决定每个 Entry 的存储位置。这也说明了前面的结论:我们完全可以把 Map 集合中的 value 当成 key 的附属,当系统决定了 key 的存储位置之后,value 随之保存在那里即可。 上面方法提供了一个根据 hashCode() 返回值来计算 Hash 码的方法:hash(),这个方法是一个纯粹的数学计算,其方法如下:
[crayon-57a16201159c9518517748/]
对于任意给定的对象,只要它的 hashCode() 返回值相同,那么程序调用 hash(int h) 方法所计算得到的 Hash 码值总是相同的。接下来程序会调用 indexFor(int h, int length) 方法来计算该对象应该保存在 table 数组的哪个索引处。indexFor(int h, int length) 方法的代码如下:
[crayon-57a16201159cf262777735/]
这个方法非常巧妙,它总是通过 h &(table.length -1) 来得到该对象的保存位置——而 HashMap 底层数组的长度总是 2 的 n 次方,这一点可参看后面关于 HashMap 构造器的介绍。 当 length 总是 2 的倍数时,h & (length-1)将是一个非常巧妙的设计:假设 h=5,length=16, 那么 h & length - 1 将得到 5;如果 h=6,length=16, 那么 h & length - 1 将得到 6 ……如果 h=15,length=16, 那么 h & length - 1 将得到 15;但是当 h=16 时 , length=16 时,那么 h & length - 1 将得到 0 了;当 h=17 时 , length=16 时,那么 h & length - 1 将得到 1 了……这样保证计算得到的索引值总是位于 table 数组的索引之内。 根据上面 put 方法的源代码可以看出,当程序试图将一个 key-value 对放入 HashMap 中时,程序首先根据该 key 的 hashCode() 返回值决定该 Entry 的存储位置:如果两个 Entry 的 key 的 hashCode() 返回值相同,那它们的存储位置相同。如果这两个 Entry 的 key 通过 equals 比较返回 true,新添加 Entry 的 value 将覆盖集合中原有 Entry 的 value,但 key 不会覆盖。如果这两个 Entry 的 key 通过 equals 比较返回 false,新添加的 Entry 将与集合中原有 Entry 形成 Entry 链,而且新添加的 Entry 位于 Entry 链的头部——具体说明继续看 addEntry() 方法的说明。 当向 HashMap 中添加 key-value 对,由其 key 的 hashCode() 返回值决定该 key-value 对(就是 Entry 对象)的存储位置。当两个 Entry 对象的 key 的 hashCode() 返回值相同时,将由 key 通过 eqauls() 比较值决定是采用覆盖行为(返回 true),还是产生 Entry 链(返回 false)。 上面程序中还调用了 addEntry(hash, key, value, i); 代码,其中 addEntry 是 HashMap 提供的一个包访问权限的方法,该方法仅用于添加一个 key-value 对。下面是该方法的代码:
[crayon-57a16201159d5595826196/]
上面方法的代码很简单,但其中包含了一个非常优雅的设计:系统总是将新添加的 Entry 对象放入 table 数组的 bucketIndex 索引处——如果 bucketIndex 索引处已经有了一个 Entry 对象,那新添加的 Entry 对象指向原有的 Entry 对象(产生一个 Entry 链),如果 bucketIndex 索引处没有 Entry 对象,也就是上面程序①号代码的 e 变量是 null,也就是新放入的 Entry 对象指向 null,也就是没有产生 Entry 链。

Hash 算法的性能选项

根据上面代码可以看出,在同一个 bucket 存储 Entry 链的情况下,新放入的 Entry 总是位于 bucket 中,而最早放入该 bucket 中的 Entry 则位于这个 Entry 链的最末端。 上面程序中还有这样两个变量:
  • size:该变量保存了该 HashMap 中所包含的 key-value 对的数量。
  • threshold:该变量包含了 HashMap 能容纳的 key-value 对的极限,它的值等于 HashMap 的容量乘以负载因子(load factor)。
从上面程序中②号代码可以看出,当 size++ >= threshold 时,HashMap 会自动调用 resize 方法扩充 HashMap 的容量。每扩充一次,HashMap 的容量就增大一倍。 上面程序中使用的 table 其实就是一个普通数组,每个数组都有一个固定的长度,这个数组的长度就是 HashMap 的容量。HashMap 包含如下几个构造器:
  • HashMap():构建一个初始容量为 16,负载因子为 0.75 的 HashMap。
  • HashMap(int initialCapacity):构建一个初始容量为 initialCapacity,负载因子为 0.75 的 HashMap。
  • HashMap(int initialCapacity, float loadFactor):以指定初始容量、指定的负载因子创建一个 HashMap。
当创建一个 HashMap 时,系统会自动创建一个 table 数组来保存 HashMap 中的 Entry,下面是 HashMap 中一个构造器的代码:
[crayon-57a16201159dd367699604/]
上面代码中粗体字代码包含了一个简洁的代码实现:找出大于 initialCapacity 的、最小的 2 的 n 次方值,并将其作为 HashMap 的实际容量(由 capacity 变量保存)。例如给定 initialCapacity 为 10,那么该 HashMap 的实际容量就是 16。

initialCapacity 与 HashTable 的容量

创建 HashMap 时指定的 initialCapacity 并不等于 HashMap 的实际容量,通常来说,HashMap 的实际容量总比 initialCapacity 大一些,除非我们指定的 initialCapacity 参数值恰好是 2 的 n 次方。当然,掌握了 HashMap 容量分配的知识之后,应该在创建 HashMap 时将 initialCapacity 参数值指定为 2 的 n 次方,这样可以减少系统的计算开销。
程序①号代码处可以看到:table 的实质就是一个数组,一个长度为 capacity 的数组。 对于 HashMap 及其子类而言,它们采用 Hash 算法来决定集合中元素的存储位置。当系统开始初始化 HashMap 时,系统会创建一个长度为 capacity 的 Entry 数组,这个数组里可以存储元素的位置被称为“桶(bucket)”,每个 bucket 都有其指定索引,系统可以根据其索引快速访问该 bucket 里存储的元素。 无论何时,HashMap 的每个“桶”只存储一个元素(也就是一个 Entry),由于 Entry 对象可以包含一个引用变量(就是 Entry 构造器的的最后一个参数)用于指向下一个 Entry,因此可能出现的情况是:HashMap 的 bucket 中只有一个 Entry,但这个 Entry 指向另一个 Entry ——这就形成了一个 Entry 链。如图 1 所示:
图 1. HashMap 的存储示意
图 1. HashMap 的存储示意

HashMap 的读取实现

当 HashMap 的每个 bucket 里存储的 Entry 只是单个 Entry ——也就是没有通过指针产生 Entry 链时,此时的 HashMap 具有最好的性能:当程序通过 key 取出对应 value 时,系统只要先计算出该 key 的 hashCode() 返回值,在根据该 hashCode 返回值找出该 key 在 table 数组中的索引,然后取出该索引处的 Entry,最后返回该 key 对应的 value 即可。看 HashMap 类的 get(K key) 方法代码:
[crayon-57a16201159e5770578001/]
从上面代码中可以看出,如果 HashMap 的每个 bucket 里只有一个 Entry 时,HashMap 可以根据索引、快速地取出该 bucket 里的 Entry;在发生“Hash 冲突”的情况下,单个 bucket 里存储的不是一个 Entry,而是一个 Entry 链,系统只能必须按顺序遍历每个 Entry,直到找到想搜索的 Entry 为止——如果恰好要搜索的 Entry 位于该 Entry 链的最末端(该 Entry 是最早放入该 bucket 中),那系统必须循环到最后才能找到该元素。 归纳起来简单地说,HashMap 在底层将 key-value 当成一个整体进行处理,这个整体就是一个 Entry 对象。HashMap 底层采用一个 Entry[] 数组来保存所有的 key-value 对,当需要存储一个 Entry 对象时,会根据 Hash 算法来决定其存储位置;当需要取出一个 Entry 时,也会根据 Hash 算法找到其存储位置,直接取出该 Entry。由此可见:HashMap 之所以能快速存、取它所包含的 Entry,完全类似于现实生活中母亲从小教我们的:不同的东西要放在不同的位置,需要时才能快速找到它。 当创建 HashMap 时,有一个默认的负载因子(load factor),其默认值为 0.75,这是时间和空间成本上一种折衷:增大负载因子可以减少 Hash 表(就是那个 Entry 数组)所占用的内存空间,但会增加查询数据的时间开销,而查询是最频繁的的操作(HashMap 的 get() 与 put() 方法都要用到查询);减小负载因子会提高数据查询的性能,但会增加 Hash 表所占用的内存空间。 掌握了上面知识之后,我们可以在创建 HashMap 时根据实际需要适当地调整 load factor 的值;如果程序比较关心空间开销、内存比较紧张,可以适当地增加负载因子;如果程序比较关心时间开销,内存比较宽裕则可以适当的减少负载因子。通常情况下,程序员无需改变负载因子的值。 如果开始就知道 HashMap 会保存多个 key-value 对,可以在创建时就使用较大的初始化容量,如果 HashMap 中 Entry 的数量一直不会超过极限容量(capacity * load factor),HashMap 就无需调用 resize() 方法重新分配 table 数组,从而保证较好的性能。当然,开始就将初始容量设置太高可能会浪费空间(系统需要创建一个长度为 capacity 的 Entry 数组),因此创建 HashMap 时初始化容量设置也需要小心对待。

HashSet 的实现

对于 HashSet 而言,它是基于 HashMap 实现的,HashSet 底层采用 HashMap 来保存所有元素,因此 HashSet 的实现比较简单,查看 HashSet 的源代码,可以看到如下代码:
[crayon-57a16201159ed063328282/]
由上面源程序可以看出,HashSet 的实现其实非常简单,它只是封装了一个 HashMap 对象来存储所有的集合元素,所有放入 HashSet 中的集合元素实际上由 HashMap 的 key 来保存,而 HashMap 的 value 则存储了一个 PRESENT,它是一个静态的 Object 对象。 HashSet 的绝大部分方法都是通过调用 HashMap 的方法来实现的,因此 HashSet 和 HashMap 两个集合在实现本质上是相同的。

HashMap 的 put 与 HashSet 的 add

由于 HashSet 的 add() 方法添加集合元素时实际上转变为调用 HashMap 的 put() 方法来添加 key-value 对,当新放入 HashMap 的 Entry 中 key 与集合中原有 Entry 的 key 相同(hashCode() 返回值相等,通过 equals 比较也返回 true),新添加的 Entry 的 value 将覆盖原来 Entry 的 value,但 key 不会有任何改变,因此如果向 HashSet 中添加一个已经存在的元素,新添加的集合元素(底层由 HashMap 的 key 保存)不会覆盖已有的集合元素。
掌握上面理论知识之后,接下来看一个示例程序,测试一下自己是否真正掌握了 HashMap 和 HashSet 集合的功能。
[crayon-57a16201159f5001259806/]
上面程序中向 HashSet 里添加了一个 new Name("abc", "123") 对象之后,立即通过程序判断该 HashSet 是否包含一个 new Name("abc", "123") 对象。粗看上去,很容易以为该程序会输出 true。 实际运行上面程序将看到程序输出 false,这是因为 HashSet 判断两个对象相等的标准除了要求通过 equals() 方法比较返回 true 之外,还要求两个对象的 hashCode() 返回值相等。而上面程序没有重写 Name 类的 hashCode() 方法,两个 Name 对象的 hashCode() 返回值并不相同,因此 HashSet 会把它们当成 2 个对象处理,因此程序返回 false。 由此可见,当我们试图把某个类的对象当成 HashMap 的 key,或试图将这个类的对象放入 HashSet 中保存时,重写该类的 equals(Object obj) 方法和 hashCode() 方法很重要,而且这两个方法的返回值必须保持一致:当该类的两个的 hashCode() 返回值相同时,它们通过 equals() 方法比较也应该返回 true。通常来说,所有参与计算 hashCode() 返回值的关键属性,都应该用于作为 equals() 比较的标准。

hashCode() 和 equals()

关于如何正确地重写某个类的 hashCode() 方法和 equals() 方法,请参考疯狂 Java 体系的《疯狂 Java 讲义》一书中相关内容。
如下程序就正确重写了 Name 类的 hashCode() 和 equals() 方法,程序如下:
[crayon-57a16201159fc127056159/]
上面程序中提供了一个 Name 类,该 Name 类重写了 equals() 和 toString() 两个方法,这两个方法都是根据 Name 类的 first 实例变量来判断的,当两个 Name 对象的 first 实例变量相等时,这两个 Name 对象的 hashCode() 返回值也相同,通过 equals() 比较也会返回 true。 程序主方法先将第一个 Name 对象添加到 HashSet 中,该 Name 对象的 first 实例变量值为"abc",接着程序再次试图将一个 first 为"abc"的 Name 对象添加到 HashSet 中,很明显,此时没法将新的 Name 对象添加到该 HashSet 中,因为此处试图添加的 Name 对象的 first 也是" abc",HashSet 会判断此处新增的 Name 对象与原有的 Name 对象相同,因此无法添加进入,程序在①号代码处输出 set 集合时将看到该集合里只包含一个 Name 对象,就是第一个、last 为"123"的 Name 对象。

参考资料


分类与回归区别是什么

$
0
0
Supervised learning problems are categorized into "regression" and "classification" problems. In a regression problem, we are trying to predict results within a continuous output, meaning that we are trying to map input variables to some continuous function. In a classification problem, we are instead trying to predict results in adiscrete output. In other words, we are trying to map input variables into discrete categories. Example:
Given data about the size of houses on the real estate market, try to predict their price. Price as a function of size is a continuous output, so this is a regression problem. We could turn this example into a classification problem by instead making our output about whether the house "sells for more or less than the asking price." Here we are classifying the houses based on price into two discretecategories.

Dubbo实现RPC调用使用入门

$
0
0
使用Dubbo进行远程调用实现服务交互,它支持多种协议,如Hessian、HTTP、RMI、Memcached、Redis、Thrift等等。由于Dubbo将这些协议的实现进行了封装了,无论是服务端(开发服务)还是客户端(调用服务),都不需要关心协议的细节,只需要在配置中指定使用的协议即可,从而保证了服务提供方与服务消费方之间的透明。 另外,如果我们使用Dubbo的服务注册中心组件,这样服务提供方将服务发布到注册的中心,只是将服务的名称暴露给外部,而服务消费方只需要知道注册中心和服务提供方提供的服务名称,就能够透明地调用服务,后面我们会看到具体提供服务和消费服务的配置内容,使得双方之间交互的透明化。 示例场景 我们给出一个示例的应用场景: 服务方提供一个搜索服务,对服务方来说,它基于SolrCloud构建了搜索服务,包含两个集群,ZooKeeper集群和Solr集群,然后在前端通过Nginx来进行反向代理,达到负载均衡的目的。 服务消费方就是调用服务进行查询,给出查询条件(满足Solr的REST-like接口)。 应用设计 基于上面的示例场景,我们打算使用ZooKeeper集群作为服务注册中心。注册中心会暴露给服务提供方和服务消费方,所以注册服务的时候,服务先提供方只需要提供Nginx的地址给注册中心,但是注册中心并不会把这个地址暴露给服务消费方,如图所示: provider-registry-consumer 我们先定义一下,通信双方需要使用的接口,如下所示:
[crayon-57b54a0041018709843271/]
基于上图中的设计,下面我们分别详细说明Provider和Consumer的设计及实现。
  • Provider服务设计
Provider所发布的服务组件,包含了一个SolrCloud集群,在SolrCloud集群前端又加了一个反向代理层,使用Nginx来均衡负载。Provider的搜索服务系统,设计如下图所示: solrcloud-cluster 上图中,实际Nginx中将请求直接转发内部的Web Servers上,在这个过程中,使用ZooKeeper来进行协调:从多个分片(Shard)服务器上并行搜索,最后合并结果。我们看一下Nginx配置的内容片段:
[crayon-57b54a0041027458202143/]
一共配置了3台Solr服务器,因为SolrCloud集群中每一个节点都可以接收搜索请求,然后由整个集群去并行搜索。最后,我们要通过Dubbo服务框架来基于已有的系统来开发搜索服务,并通过Dubbo的注册中心来发布服务。 首先需要实现服务接口,实现代码如下所示: [crayon-57b54a004102e601325944/]
对应的Dubbo配置文件为search-provider.xml,内容如下所示: [crayon-57b54a0041035690446787/]
上面,Dubbo服务注册中心指定ZooKeeper的地址:zookeeper://slave1:2188?backup=slave3:2188,slave4:2188,使用Dubbo协议。配置服务接口的时候,可以按照Spring的Bean的配置方式来配置,注入需要的内容,我们这里指定了搜索集群的Nginx反向代理地址http://nginx-lbserver/solr-cloud/
  • Consumer调用服务设计
这个就比较简单了,拷贝服务接口,同时要配置一下Dubbo的配置文件,写个简单的客户端调用就可以实现。客户端实现的Java代码如下所示: [crayon-57b54a0041040331063362/]
查询的时候,需要提供查询字符串,符合Solr语法,例如“q=上海&fl=*&fq=building_type:1”。配置文件,我们使用search-consumer.xml,内容如下所示: [crayon-57b54a004104b586818437/]
运行说明 首先保证服务注册中心的ZooKeeper集群正常运行,然后启动SolrSearchServer,启动的时候直接将服务注册到ZooKeeper集群存储中,可以通过ZooKeeper的客户端脚本来查看注册的服务数据。一切正常以后,可以启动运行客户端SearchConsumer,调用SolrSearchServer所实现的远程搜索服务。 参考链接
 本文链接:http://www.paymoon.com:8001/index.php/2016/08/10/how-to-use-dubbo/
Dubbo资源购买:淘宝:九元学社

Dubbo与Zookeeper整合视频教程(分布式架构╋第三方支付)史上最全

https://item.taobao.com/item.htm?spm=a230r.1.14.1.cSZCed&id=532129570206&ns=1&abbucket=20#detail

分布式系统(Distributed System)资料

$
0
0
介绍:这是一篇介绍在动态网络里面实现分布式系统重构的paper.论文的作者(导师)是MIT读博的时候是做分布式系统的研究的,现在在NUS带学生,不仅仅是分布式系统,还有无线网络.如果感兴趣可以去他的主页了解. 介绍:分布式编程实验室,他们发表的很多的paper,其中不仅仅是学术研究,还有一些工业界应用的论文. 介绍:麻省理工的分布式系统理论主页,作者南希·林奇在2002年证明了CAP理论,并且著《分布式算法》一书. 介绍:分布式系统搭建初期的一些建议 介绍:分布式计算原理课程 介绍:Google全球分布式数据介绍,中文版 介绍:Algolia的分布式搜索网络的体系架构介绍 介绍:构建高可用分布式Key-Value存储系统 介绍:Nanomsg和Bond的分布式搜索引擎 介绍:使用MongoDB和Mongothon进行分布式处理 介绍:分布式数据库中把ACID与BASE结合使用. 介绍:理解的Paxos的分布式系统,参考阅读:关于Paxos的历史 介绍:There is No Now Problems with simultaneity in distributed systems 介绍:伦敦大学学院分布式系统课程课件. 介绍:分布式系统电子书籍. 介绍:卡内基梅隆大学春季分布式课程主页 介绍: 电子书,分布式系统概念与设计(第五版) 介绍:这是一位台湾网友 ccshih 的文字,短短的篇幅介绍了分布式系统的若干要点。pdf 介绍:清华大学分布式系统课程主页,里面的schedule栏目有很多宝贵的资源 介绍:免费的在线分布式系统书籍 介绍:Quora上面的一篇关于学习分布式计算的资源. 介绍:这个是第一个全球意义上的分布式数据库,也是Google的作品。其中介绍了很多一致性方面的设计考虑,为了简单的逻辑设计,还采用了原子钟,同样在分布式系统方面具有很强的借鉴意义. 介绍:Google的统面向松散耦合的分布式系统的锁服务,这篇论文详细介绍了Google的分布式锁实现机制Chubby。Chubby是一个基于文件实现的分布式锁,Google的Bigtable、Mapreduce和Spanner服务都是在这个基础上构建的,所以Chubby实际上是Google分布式事务的基础,具有非常高的参考价值。另外,著名的zookeeper就是基于Chubby的开源实现.推荐The google stack,Youtube:The Chubby lock service for loosely-coupled distributed systems 介绍:这篇论文是SOSP2007的Best Paper,阐述了一种构建分布式文件系统的范式方法,个人感觉非常有用。淘宝在构建TFS、OceanBase和Tair这些系统时都充分参考了这篇论文. 介绍:Ebook:Data-Intensive Text Processing with MapReduce. 介绍:Design and Implementation of a Query Processor for a Trusted Distributed Data Base Management System. 介绍:分布式查询入门. 介绍:分布式系统和api总结. 介绍:分布式系统阅读论文,此外还推荐github上面的一个论文列表The Distributed Reader 介绍:Replication, atomicity and order in distributed systems 介绍:2015年MIT分布式系统课程主页,这次用Golang作为授课语言。6.824 Distributed Systems课程主页 介绍:免费分布式系统电子书。 介绍:斯坦福开源的分布式文件系统。 介绍:Google论文:设计一个高可用的全球分布式存储系统。 介绍:对于分区数据库的分布式事务处理。 介绍:Distributed Systems Building Block: Flake Ids. 介绍:Google Code University课程,如何设计一个分布式系统。 介绍:KVM的分布式存储系统. 介绍:分布式系统课程列表,包括数据库、算法等. 介绍:来自百度的分布式表格系统. 介绍:分布式系统的在线电子书. 介绍:分布式系统资料,此外还推荐Various articles about distributed systems. 介绍:Designs, Lessons and Advice from Building Large Distributed Systems. 介绍:Testing a distributed system can be trying even under the best of circumstances. 介绍: 基于普通服务器构建超大规模文件系统的典型案例,主要面向大文件和批处理系统, 设计简单而实用。 GFS是google的重要基础设施, 大数据的基石, 也是Hadoop HDFS的参考对象。 主要技术特点包括: 假设硬件故障是常态(容错能力强), 64MB大块, 单Master设计,Lease/链式复制, 支持追加写不支持随机写. 介绍:支持PB数据量级的多维非关系型大表, 在google内部应用广泛,大数据的奠基作品之一 , Hbase就是参考BigTable设计。 Bigtable的主要技术特点包括: 基于GFS实现数据高可靠, 使用非原地更新技术(LSM树)实现数据修改, 通过range分区并实现自动伸缩等.中文版 介绍:面向log-based存储的强一致的主从复制协议, 具有较强实用性。 这篇文章系统地讲述了主从复制系统应该考虑的问题, 能加深对主从强一致复制的理解程度。 技术特点: 支持强一致主从复制协议, 允许多种存储实现, 分布式的故障检测/Lease/集群成员管理方法. 介绍:分布式存储论文:支持强一直的链式复制方法, 支持从多个副本读取数据,实现code. 介绍:Facebook分布式Blob存储,主要用于存储图片. 主要技术特色:小文件合并成大文件,小文件元数据放在内存因此读写只需一次IO. 介绍: 微软的分布式存储平台, 除了支持类S3对象存储,还支持表格、队列等数据模型. 主要技术特点:采用Stream/Partition两层设计(类似BigTable);写错(写满)就封存Extent,使得副本字节一致, 简化了选主和恢复操作; 将S3对象存储、表格、队列、块设备等融入到统一的底层存储架构中. 介绍:从工程实现角度说明了Paxo在chubby系统的应用, 是理解Paxo协议及其应用场景的必备论文。 主要技术特点: paxo协议, replicated log, multi-paxo.参考阅读:关于Paxos的历史 介绍:Amazon设计的高可用的kv系统,主要技术特点:综和运用一致性哈希,vector clock,最终一致性构建一个高可用的kv系统, 可应用于amazon购物车场景.新内容来自分布式存储必读论文 介绍:分布式存储系统中的副本存储问题. 介绍:分布式存储系统架构. 介绍:开源分布式文件系统Chirp,对于想深入研究的开发者可以阅读文章的相关Papers. 介绍:经典论文分布式时钟顺序的实现原理. 介绍:面向软件错误构建可靠的分布式系统,中文笔记. 介绍:MapReduce:超大集群的简单数据处理. 介绍:麻省理工的分布式计算课程主页,里面的ppt和阅读列表很多干货. 介绍:分布式系统Styx的架构剖析. 介绍:Quora上面的一个问答:有哪些关于分布式计算学习的好资源. 介绍:下一代分布式k-v存储数据库. 介绍:分布式系统归根结底还是需要操作系统的知识,这是耶鲁大学的操作系统概念书籍首页,里面有提供了第8版的在线电子版和最新的学习操作系统指南,学习分布式最好先学习操作系统. 介绍:分布式系统Log剖析,非常的详细与精彩. 中文翻译 | 中文版笔记. 介绍:分布式系统基础之操作系统学习指南. 介绍:分布式系统领域经典论文翻译集. 介绍:分布式系统性能维护. 介绍:计算机科学,自底向上,小到机器码,大到操作系统内部体系架构,学习操作系统的另一个在线好材料. 介绍:<操作系统:三部曲>在线电子书,虚拟、并发、持续. 介绍:数据库系统经典论文阅读列,此外推送github上面的db reading. 介绍:Unix System Administration ebook. 介绍:分布式系统经典论文. 介绍:计算机系统概念,以分布式为主.此外推荐Introduction to Operating Systems笔记 介绍:推荐康奈尔大学的教授EMİN GÜN SİRER的主页,他的研究项目有分布式,数据存储。例如HyperDex数据库就是他的其中一个项目之一. 介绍:来自卡内基梅隆如何构建可扩展的、安全、高可用性的分布式文件系统,其他papers. 介绍:分布式机器学习常用库. 介绍:介绍了如何构建仓储式数据中心,尤其是对于现在的云计算,分布式学习来说很有帮助.本书是Synthesis Lectures on Computer Architecture系列的书籍之一,这套丛书还有 《The Memory System》,《Automatic Parallelization》,《Computer Architecture Techniques for Power Efficiency》,《Performance Analysis and Tuning for General Purpose Graphics Processing Units》,《Introduction to Reconfigurable Supercomputing》 等 介绍:来自芬兰赫尔辛基的分布式系统课程课件:什么是分布式,复制,一致性,容错,同步,通信. 介绍:分布式数据库TiDB,Golang开发. 介绍:课程资料:大规模系统. 介绍:使用MapReduce进行大规模分布式集群环境下并行L-BFGS. 介绍:Twitter是如何构建高性能分布式日志的. 介绍:在分布式系统中某个组件彻底死了影响很小,但半死不活(网络/磁盘),对整个系统却是毁灭性的. 介绍:来自百度的分布式数据库. 介绍:SequoiaDB分布式文档数据库开源. 介绍:这个网址里收集了一堆各TOP大学分布式相关的课程. 介绍:这个网站是Raft算法的作者为教授Paxos和Raft算法做的,其中有两个视频链接,分别讲上述两个算法.参考阅读:关于Paxos的历史 介绍:A Scalable Content-Addressable Network. 介绍:这个项目其实是一本书( The Architecture of Open Source Applications)的源代码附录,是一堆大牛合写的. 介绍:这只是一个课程主页,没有上课的视频,但是并不影响你跟着它上课:每一周读两篇课程指定的论文,读完之后看lecture-notes里对该论文内容的讨论,回答里面的问题来加深理解,最后在课程lab里把所看的论文实现。当你把这门课的作业刷完后,你会发现自己实现了一个分布式数据库. 介绍:使用go开发的分布式文件系统. 介绍:Quora上关于学习分布式的资源问答. 介绍:SeaweedFS是使用go开发的分布式文件系统项目,代码简单,逻辑清晰. 介绍:Codis 是一个分布式 Redis 解决方案, 对于上层的应用来说, 连接到 Codis Proxy 和连接原生的 Redis Server 没有明显的区别 介绍:Coordination Avoidance In Distributed Databases. 介绍:本文以TiDB 源码为例. 介绍:分布式系统概念梳理,为分布式系统涉及的主要概念进行了梳理. 介绍:使用Redis实现分布式锁. 介绍: 斯坦福2014年秋季分布式课程. 介绍: 分布式的“读原子性”. 介绍: 大数据分布式机器学习的策略与原理. 介绍: 分布式CAP法则. 介绍: 新手如何步入分布式存储系统. 介绍: 分布式存储系统Cassandra剖析,推荐白皮书Introduction to Apache Cassandra. 介绍: 分布式系统学习资源. 介绍: 一些高性能TCP黑客技巧. 介绍:分布式系统性能提升. 介绍:Benjamin Reed 和 Flavio P.Junqueira 所著论文,对Zab算法进行了介绍,zab算法是Zookeeper保持数据一致性的核心,在国内有很多公司都使用zookeeper做为分布式的解决方案.推荐与此相关的一篇文章ZooKeeper’s atomic broadcast protocol: Theory and practice. 介绍:可扩展的分布式文件系统ZFS,The Zettabyte File System,End-to-end Data Integrity for File Systems: A ZFS Case Study. 介绍:分布式Haskell在当前web中的应用. 介绍:POPL2016的论文,关于分布式系统一致性选择的论述,POPL所接受的论文,github上已经有人整理. 介绍:Paxos让分布式更简单.译文.参考阅读:关于Paxos的历史 介绍:分布式系统一致性协议:Paxos.参考阅读:关于Paxos的历史 介绍:事务提交的一致性探讨. 介绍:在《The Part-Time Parliament》中描述了基本协议的交互过程。在基本协议的基础上完善各种问题得到了最终的议会协议。 为了让人更容易理解《The Part-Time Parliament》中描述的Paxos算法,Lamport在2001发表了《Paxos Made Simple》,以更平直的口头语言描述了Paxos,而没有包含正式的证明和数学术语。《Paxos Made Simple》中,将算法的参与者更细致的划分成了几个角色:Proposer、Acceptor、Learner。另外还有Leader和Client.参考阅读:关于Paxos的历史 介绍:看这篇论文时可以先看看理解Paxos Made Practical. 介绍:PaxosLease:实现租约的无盘Paxos算法,译文. 介绍:Paxos算法实现,译文,同时推荐42 Paxos Made Moderately Complex. 介绍:Hadoop学习清单. 介绍:Hadoop学习清单. 介绍:NoSQL知识清单,里面不仅仅包含了数据库阅读清单还包含了分布式系统资料. 介绍:Raft可视化图帮助理解分布式一致性 介绍:Etcd分布式Key-Value存储引擎 介绍:理解peer-to-peer系统中的可用性究竟是指什么.同时推荐基于 Peer-to-Peer 的分布式存储系统的设计 介绍:经典论文 介绍:并行处理的编程语音 介绍:此篇论文对HDFS,MooseFS,iRODS,Ceph,GlusterFS,Lustre六个存储系统做了详细分析.如果是自己研发对应的存储系统推荐先阅读此篇论文 介绍:分布式文件系统综述
  • 《Concepts of Concurrent Programming》
介绍:并行编程的概念,同时推荐卡内基梅隆FTP 介绍:并发控制性能建模:选择与意义 介绍:ebook分布式系统概念与设计 介绍:分布式系统设计的形式方法 介绍:互斥和选举算法 介绍:经典论文 介绍:如何构建一个安全可靠的分布式系统,About the Author,Bibliography:文献资料,章节访问把链接最后的01换成01-27即可 介绍:卡内基梅隆大学的分布式系统博士生课程主页,有很丰富的资料 介绍:Dapper,大规模分布式系统的跟踪系统,译文,译文对照 介绍:伯克利大学计算机系统进阶课程,内容有深度,涵盖分布式,数据库等内容 介绍:PB级分布式系统构建/扩展经验 介绍:伯克利大学计算机系统课程:操作系统与系统编程 介绍:MDCC主要解决跨数据中心的一致性问题中间件,一种新的协议 介绍:google公开对外发表的分布式系统与并行计算论文 介绍:分布式文件系统HDFS架构 介绍:分布式 Key/Value数据库 介绍:是著名的Ceph的负载平衡策略,文中提出的几种策略都值得尝试,比较赞的一点是可以对照代码体会和实践,如果你还需要了解可以看看Ceph:一个 Linux PB 级分布式文件系统,除此以外,论文的引用部分也挺值得阅读的,同时推荐Ceph: A Scalable, High-Performance Distributed File System 介绍:Surrento的冷热平衡策略就采用了延迟写技术 介绍:对于分布式存储系统的元数据管理. 介绍:服务器端的I/O协调并行文件系统处理,网络,文件存储等都会涉及到IO操作.不过里面涉及到很多技巧性的思路在实践时需要斟酌 介绍:分布式文件系统概念与应用 介绍:加利福尼亚大学的研究生操作系统课程主页,论文很值得阅读 介绍:Yahoo出品的流式计算系统,目前最流行的两大流式计算系统之一(另一个是storm),Yahoo的主要广告计算平台 介绍:Google的大规模图计算系统,相当长一段时间是Google PageRank的主要计算系统,对开源的影响也很大(包括GraphLab和GraphChi) 介绍:CMU基于图计算的分布式机器学习框架,目前已经成立了专门的商业公司,在分布式机器学习上很有两把刷子,其单机版的GraphChi在百万维度的矩阵分解都只需要2~3分钟; 介绍:这篇论文是Google 2013年发表的,介绍了F1的架构思路,13年时就开始支撑Google的AdWords业务,另外两篇介绍文章F1 - The Fault-Tolerant Distributed RDBMS Supporting Google's Ad Business .Google NewSQL之F1 介绍:CockroachDB :一个可伸缩的、跨地域复制的,且支持事务的数据存储,InfoQ介绍,Design and Architecture of CockroachDb
  • 《Multi-Paxos: An Implementation and Evaluation》
介绍:Multi-Paxos实现与总结,此外推荐Paxos/Multi-paxos Algorithm,Multi-Paxos Example,地址:ftp://ftp.cs.washington.edu/tr/2009/09/UW-CSE-09-09-02.PDF 介绍:一致性协议zab分析 介绍:分布式哈希算法论文,扩展阅读Introduction to Distributed Hash Tables,Distributed Hash Tables 介绍:分布式hash表性能的Churn问题 介绍:分布式系统的CAP问题,推荐Perspectives on the CAP Theorem.对CAP理论的解析文章,PODC ppt,A plain english introduction to CAP Theorem,IEEE Computer issue on the CAP Theorem 介绍:闪存存储文件系统F2FS 介绍:微软发表的关于i/o访问优化论文 介绍:虚拟内存文件系统tmpfs 介绍:Linux B-tree文件系统. 介绍:Akamai是全球最大的云计算机平台之一,承载了全球15-30%网络流量,如果你是做CDN或者是云服务,这个里面的论文会给你很有帮助.例如这几天看facebook开源的osquery。找到通过db的方式运维,找到Keeping Track of 70,000+ Servers: The Akamai Query System这篇论文,先看论文领会思想,然后再使用工具osquery实践 介绍:来自eBay 的解决方案,译文Base: 一种Acid的替代方案,应用案例参考保证分布式系统数据一致性的6种方案 介绍:Jim Waldo和Sam Kendall等人共同撰写了一篇非常有名的论文“分布式计算备忘录”,这篇论文在Reddit上被人推荐为“每个程序员都应当至少读上两篇”的论文。在这篇论文中,作者表示“忽略本地计算与分布式计算之间的区别是一种危险的思想”,特别指出了Emerald、Argus、DCOM以及CORBA的设计问题。作者将这些设计问题归纳为“三个错误的原则”: “对于某个应用来说,无论它的部署环境如何,总有一种单一的、自然的面向对象设计可以符合其需求。” “故障与性能问题与某个应用的组件实现直接相关,在最初的设计中无需考虑这些问题。” “对象的接口与使用对象的上下文无关”. 介绍:分布式系统领域经典论文列表. 介绍:Consistent Hashing算法描述. 介绍:SIGMOD是世界上最有名的数据库会议之一,最具有权威性,收录论文审核非常严格.2016年的SIGMOD 会议照常进行,上面收录了今年SIGMOD收录的论文,把题目输入google中加上pdf就能找到,很多论文值得阅读,SIGMOD 2015 介绍:耶鲁大学的分布式系统理论课程笔记 介绍:分布式系统文档资源(可下载) 介绍:数据库系统剖析,这本书是由伯克利大学的Joseph M. Hellerstein和M. Stonebraker合著的一篇论文.对数据库剖析很有深度.除此以外还有一篇文章Architecture of a Database System。数据库系统架构,厦门大学的数据库实验室教授林子雨组织过翻译 介绍:数据库关系模型论文 介绍:中国人民大学数据研究实验室推荐的数据库领域论文 介绍:构建可扩展的分布式信息管理系统 介绍:Haskell中的分布式系统开发 介绍:Google使用Borg进行大规模集群的管理,伯克利大学ppt介绍,中文版 介绍:并发编程(Concurrency Programming)资料,主要涵盖lock free数据结构实现、内存回收方法、memory model等备份链接 密码: xc5j 介绍:Nancy Lynch's的分布式算法研究生课程讲义 介绍:分布式算法主题模型. 介绍:世界上非常有名的推荐系统会议,我比较推荐接收的PAPER Fork from Git

在动态网络下实现分布式共享存储,动态共享存储

$
0
0

在动态网络下实现分布式共享存储,动态共享存储

  在动态网络下实现分布式共享存储 http://cacm.acm.org/magazines/2014/6/175173-implementing-distributed-shared-memory-for-dynamic-networks 译者序 共享内存系统是普通单机程序开发人员熟悉的开发范式,通过简单的使用读、写命令,就能确保将我们需要的值从内存中放入和读取出来,数据的一致性等问题,在单机系统中,开发人员根本不需要考虑,比如你不需要考虑当你进行了i=i+1后,再获取i的值时,i的值可能还没有来的及变化,因为这些都已经在读写原语的原子性中被考虑了,然而在分布式环境下,由于数据出现多个副本,且副本的数量有可能动态增加和减少,要实现同样的功能,又能保证读写性能,就需要新的算法和实现。本文介绍了分布式环境下实现共享内存模型会遇到的各种问题和挑战,针对不同问题,介绍多种算法,并比较其优劣性。最终选取两种实现进行实际测评。本文是对现阶段该领域研究现状的总体介绍,通过阅读该文,我们能够了解动态分布式共享内存研究的前沿状况,了解该领域中的挑战与机遇,是有志于分布式领域的架构人员,开发人员,研究人员的必读资料。  

要点透视

 
  • 动态共享存储(DSM)系统支持通过读,写操作存取对象,同时需要在底层分布系统的各种不确定性干扰的条件下,保证数据一致性与可用性
  • 开发人员能够使用DSM来实现基于共享内存范式的分布式系统,并将他们的工作重点放在系统功能上,不用过分操心底层的信息设置,异步操作以及失败机制。
  • 动态共享存储系统实现了对象副本集合的透明化、运行时重配置,并支持多种副本模式的实现,包含从基于存储网络节点的实现,以及类似ad hoc网络中基于移动设备的实现。
读(Reading),写('riting),算('rithmetic),所谓3R依然是大多数人类智力活动的基础,同时,3R也是现代计算科技的重要组成本分。实际上,无论图灵机还是冯诺依曼体系,都遵循读、写、算模型,所有投入实际使用的单处理器实现,都基于3R进行工作。随着网络科技的发展,通讯(communication)日益成为了重要系统活动。然而在高层次的抽象上,使用读、写、算模型进行思考仍然显然更为自然。        虽然很难想象分布式系统——比如基于万维网(World Wide Web)的应用——不需要通讯参与其中,我们还是常常把基于浏览器的应用程序简单想象成 获取数据(即读取数据),进行计算,存储(即写入)结果三个阶段。在本文中,我们主要讨论分布式系统中可共享读写的数据的存储,并聚焦于能够在动态环境下提供弹性及一致性的实现,所谓动态环境是指底层由电脑和网络组成的、容易受到各种因素干扰的分布式系统。 这些干扰包括:某些电脑退出工作,动态改变系统中参与计算的工作集合,通讯链路的失败和延迟。 共享内存服务是大多数信息时代系统的核心,我们这里所详细分析的共享内存系统支持两种存取操作:读(read)操作获取对象的当前值,写(write)操作替换对象的旧值为当前新值。操作中涉及的对象往往被叫做寄存器(registers)。虽然我们不打算包含更多复杂的对象语意,比如事务或者整合的读改写操作,但是任何分布式系统实现都需要接受下面谈到的挑战。 想象一个中心服务器类型的存储系统实现,服务器接收客户端请求并对数据进行处理,然后回复。虽然概念简单,但这种实现会产生两个主要的问题。第一个是中心服务器是性能瓶颈,第二个是服务器本身是单点故障。这种实现方式下,服务质量随着客户端的增加显著下降,如果服务器崩溃,服务将无法使用(想象一下网络新闻服务如果设计为中心服务器结构,其结果将难堪重任) 因此系统的重中之重在于保持可用。这就意味着他必须具备一定的容错性,比如,系统必须可以屏蔽掉某些服务器的错误或者通讯失败。系统必须支持大量并发访问,并不会导致剧烈的性能下降。唯一能保证可用性的方法只有冗余,也就是说使用多个服务器,并在服务器间制造对象内容的副本。另外数据复制必须从地理上分散开来,并位于不同的网络位置,才能让系统屏蔽某些数据服务器子集的失效。 保证数据长期有效也非常关键。一个存储系统可能容许一些服务器失效,但是长远来说,我们可以想象,所有服务器都有可能被更换,因为没有服务器是永远可靠,或者因为计划中升级。此外,在移动环境下,比如搜救或者军事行动中,很可能需要将数据从一个服务器集合迁移到另外一个,使得数据能够根据需要移动。无论我们关注的是数据的长期有效性还是移动能力,存储系统必须要提供数据的实时无缝迁移:没有谁可以让世界停止运转,以便等待他慢慢重新配置系统以应对系统失效或者环境变化。 由数据副本带来的主要问题是一致性问题,系统怎样才能从多个副本中找到最新值?这个问题在单服务器中心的实现中不会出现:一台唯一的服务器永远包含最新的值。在多数据副本的情况下,有可能需要询问每一个副本才能找到对象的最新值,但是这个办法代价太大,并且也做不到容错,因为必须要求每个节点都能被访问。无论如何,所以的实现对客户端都应该是透明的,客户端幻想着正在操作一个单副本对象,并且所有的存取操作都一个接一个按顺序执行,任何读取操作都能返回前一个写入操作的结果,并且该值和前一个读操作所获得的值至少一样新。总的来说,对象的行为,从外部看起来,必须和对象的抽象序列化数据类型一致,这样在开发使用这些对象的程序时才能放心的使用这些对象类型。一致性的概念通过原子性(atomicity28)或者等效的线性一致性(linearizability.25)来实现。 毋庸置疑,原子性是的最方便的一致性概念(是一致性中最强的概念),我们也注意到,基于效率的考量,一些相对较弱的一致性概念也被提出并实现。比如在多处理器内存系统领域,出现了几个不是那个直观的定义,他们的出现主要是基于一个有趣的理念:“没有人能找到解决一致性的方法,但是内存访问速度很快。”原子性提供最强的保障,也导致其实现花销高于较弱的一致性保障4 。Eric Brewer猜想没有分布式系统可以同时实现一致性,可用性,分区容忍性;这就是著名的“CAP猜想”,这个猜想现在已被证明成为了定理23 。因此在一些情况下,较弱的一致性模型需要被考虑8 。尽管如此,我们认为实现简单和直观的原子一致性仍然非常重要。 这种情况让人想起了编程语言发展的造期,汇编语言被认为更加优秀,因为他能产生更有效率的代码,或图形界面的早期,命令行界面被认为更优秀,因为他们消耗更少的资源。可想而知,在原子性领域的类似争论迟早也尘埃落定。当故障是间歇性并且存在时间较短的时候,我们可以像处理长时间网络延迟一样处理它,把恢复工作交给底层的网络模型。如果故障是永久性的,一致性仍然可以通过将服务限制在主要分区(partion)来实现。在最近的一次演讲中30 ,ACM的 2008图灵奖获得者Barbara Liskov说,虽然原子性实现代价确实较高,但如果我们不保证原子性,开发人员会非常头痛。 有趣的是已经有存储系统提供了原子化的 读-改-写 操作原语,这种存取原语的实现比我们涉及的读写原语要强大得多,不过实现代价也非常高,其中核心部分原子化的更新操作,有多种不同实现,有的将系统的一部分改为单一写模型(比如微软的Zure9),或者依靠始终时钟同步硬件(如Google Spanner13),或依赖如vector clocks这样的复杂机制来解决事件的顺序问题(Google's Spanner13)。我们对读写原子性的阐述展示了分布式存储系统面临的一般性挑战。

分布式和一致性

这里,我们将描述一个综合的的分布式环境,该环境实现一致的共享内存访问服务。

分布式平台建模

我们将该系统建模为一些互相连接的电脑或节点,互相之间使用点对点消息进行通讯。每个节点都有一个唯一的标示符(比如IP地址)、本地存储并能进行本地计算。计算中,某个节点可能随时故障 ,节点故障意味着:节点不进行任何内部计算,不发送任何消息,任何发送给它的消息都不会被成功接收。 系统是异步的,节点不读取全球时钟,也没有同步机制。这意味着不同节点的处理速度是随机的,节点也不知道本地计算任务的时间上限。消息的延迟也是随机的,节点也不知道消息延迟的上限(尽管限制可能存在)。因此,算法不能假设知道全球时间或者延迟,因为不同节点的处理速度和延迟都不确定。 我们假设消息在传输中是有序的。消息不会自发的出错,重复或者生成。任意一条被收到的消息一定在这之前被发送过。消息不会无故丢失,但是消息的丢失可以被看做是长时间的延迟的后果(如何利用重发机制或者gossip协议建设一个可信的通信服务不是本文的讨论范围) 我们将分布式网络系统分成动态和静态的两类,分类标准如下。静态网络系统中,节点的集合是固定的,每个节点可能知道其他所有参与节点的信息。节点崩溃(或者主动退出)会导致节点被从系统中删除。动态为网络系统的节点数是不固定的,随着时间变化,由于节点崩溃,退出以及新节点加入,系统的参与节点有可能完全变化,而且这些变化可能随时发生。 我们先不假设动态环境中的故障程度,留待解决方案完整提出后再行讨论。无论动态与否,系统的一致性都需要被保留。计算过程产生的干扰可能对所讨论的内存服务的性能产生负向影响,不过内存的存取操作在某些假设被满足的情况下一定会终止。比如,当超过半数数据副本被激活,且网络延迟可控,静态内存服务将保证内存操作停止。动态内存服务不需要超过半数数据副本被激活,但是要求在特定时间内,多数数据副本处于激活状态(比如某法定数量(quorums))。如果这些假设不能被满足,内存服务需要依然保证操作的原子性,不过操作所需的时间会显著增加,或永远无法停止。 总的来说,我们认为所有参与工作的节点都是“好同志”,他们既有能力,又愿意参与工作,而且不会有意无意的捣乱---故意进行错误的计算。因此我们不用采取措施来处理恶意行为。如果你认为参与者有可能干坏事,我们建议对这方面感兴趣的读者阅读相关的文章,Rodrigues et al.,35  Martin and Alvisi.34 分布式共享内存和一致性。一个分布式共享内存服务利用网络节点和节点间通讯,模拟一段共享的内存空间,空间包含一系列可以读写的对象(常常被叫做寄存器(registers)),服务使用多个数据副本来保证数据的存在性和可用性。对客户端来说,服务是隐形的。对象的内容被复制到多台不同的服务器或者副本主机上,客户端发起读写操作。发起读操作的客户端被称作阅读者(readers),发起写操作的客户端被称作写入者(writers),一个客户端可能既是reader又是writer. 为了响应客户端请求,服务端发起一个包含与副本主机间通讯。这个通讯协议就是内存系统能够保证一致性的关键所在。原子一致性要求每个操作“缩水”为一个序列化点,这些序列化点的执行顺序必须和真实操作的顺序一致,最终的操作结果必须与序列化操作的结果一致。具体来讲,当一个读操作在一个写操作完成后被调用的话,读操作的返回值必须是那上述写操作写入的值,或者是上述写操作之后,读操作之前其他写操作的值。另外,如果读操作在另外一个读操作之后被调用,其返回值必须与前一个读相同,或者是一个比刚才的值更”新”的值。(在ACM 数字图书馆的附录里有更正式的论述)。由于这些自然属性,原子性是最简便和直观的一致性保证。 数据拷贝引起的最大问题就是一致性,我们怎么保证系统在不同拷贝间找到最新的值? Lamport 28 引入了原子寄存器(registers)。Herlihy 和 Wing26 ,提出了一个等效的定义,线性化性(linearizability),这个也将原子性扩展到了任意的数据类型。由于原子性是关于一致性最强的概念,提供最有用的访问语意,所以我们聚焦于原子性的共享内存系统。

基础工具:静态化网络系统条件下的共享内存系统

为静态环境设计的算法仍然需要适应一些动态的行为,比如有限的异步性、暂时故障、永久崩溃等等。我们在这里先讨论静态环境下的共享内存服务有两个原因:首先,为讨论动态内存打下一个基础,同时聚焦某些实现原子性的方法,这些方法在动态服务中也会用到。第一个针对静态内存共享系统综合考察来自Chockler et al.12 。他的著作中提到的拷贝存取算法可以作为我们动态系统的基础材料,然而为静态系统设计的算法不能被直接用的动态系统,因为他们缺乏处理拷贝集合变化的能力。Attiya, Bar-Noy 和 Dolev,3 提出了处理共享内存的原始算法,这个算法被称作ABD算法,这项工作使得他们于2011年获得了Dijkstra 奖。 这个算法实现了原子内存,同时拷贝提升容错能力和可用性。给定的总拷贝数是n,系统能够容忍 f 个拷贝失效,n>2f,。我们的报告包含Lynch and Shvartsman32对原始算法做出的改进。 每个副本主机i 都保存寄存器(register)的本地值,valuei 以及一个附加到副本上的tag,tagi = 〈seq, pid〉,seq是一个序列号,pid是写入节点的唯一标识。Tags按照词典顺序进行比较排序。每一次新的写入都分配一个唯一的tag,发起节点的id被用来打破可能出现的无限循环。这些tags被用来确定写操作的顺序,也能同时决定相应读操作的返回顺序。 共享内存服务的客户端应该幻想自己在使用一个单拷贝对象[?],对此对象的所有存取都是序列化的. 读取(Read)和写入(Write)操作类似,每一个操作都由两个阶段组成,一个获取(Get)阶段,用于从多个拷贝中获取信息,一个放入(Put)阶段,将信息传播到所有可用拷贝中。每一个阶段的协议都保证超过半数的可用节点参与通讯交换:首先信息被发送到所有可用拷贝主机,接着所有可用拷贝主机的响应会被收集起来。前面提到过由于n>2f,所以一定存在超过半数的未故障节点。因此,每个阶段会在一轮通讯后停止,任意一个操作,会在两轮通讯后结束。这种实现方式的正确性,即原子性是这样被保证的:对于任意两个连续操作,至少一个正确的副本会在第一个操作的put阶段中被更新,又被第二个操作的get阶段所读取(译者:两次操作至少共享一个节点);这就保证了第二个操作总会“看见”最近的前置操作的结果。我们可以发现,在与不同主机集合通讯时,[?]需要要求任意两个集合之间有交集。如果等待超过半数的可用主机响应过于消耗资源和时间,可以设定一个法定数量系统(quorum system).32,38 (详细和完整的伪代码可以在ACM数字图书馆的附件中找到)

在动态网络环境中模拟共享内存系统

接下来,我们介绍几种能够在更加动态的系统中提供一致性共享内存的方法,更加动态的系统是指不仅节点可能崩溃或者自发的退出服务,而且新的节点可能随时加入服务。总的来说,对象拷贝集合的数量可能随着时间进行变化,最终迁移到完全不同的拷贝主机集合上。ABD算法不能应用在这个场景中,因为它建立在原有的拷贝主机集合一直可用的基础上。为了能够在动态环境中使用类似ABD方法,必须要提供方法对复制主机的集合进行管理,并保证阅读者(reader)和写入者(writer)能访问可用的集合。 有必要指出,处理动态环境以及管理节点集合并不直接解决内存服务一致性的问题。这些问题代表着动态分布计算领域面临的更加广泛的挑战。这也启发我们:实现共享内存的一致性有时可以使用分布式的基础工具,比如那些用来管理参与节点集合的工具,提供通讯原语的工具以及在动态分布式环境中获取共识(consensus)的工具。Aguilera et al.1提供的教程中涵盖了其中的若干主题。 我们首先从获取共识(consensus)的问题开始,因为它通过建立共同的操作顺序,为实现内存服务的原子性提供了自然的基础。也因为共识(consensus)也被用于原子化内存实现的其他方面。接着,我们我们将提出组通讯服务(GCS)解决方案:使用强通讯原语,比如完全排序广播,来对操作排序。最后,我们聚焦一些方法,这些方法通过用显式的拷贝主机集合管理,可以用于扩展ABD算法至动态环境。

共识

在分布式环境中如何协调并达成一致是计算机科学的基本问题。在分布式环境下达成一致的问题被称作共识问题。由于不同节点提供了多个参考值,一组进程需要对该值达成一致。 任何解决方案都必须保证下面几个属性:【译者,此处定义翻译参考pixos算法调研报告】一致性:至多有一个值被通过,即两个不同节点不能够通过两个不同的值;合法性:如果一个值被通过,那么这个值一定是由某个节点提出过的。即只有被提议过的值才会被通过;终结性:每个未发生故障的节点都会最终通过某个值,即一致性算法能够取得进展。共识在设计分布式系统时,是一个强大的工具,33,但是在异步系统中,共识问题的难度也臭名昭著,因为当一个进程出现问题时,终结性很难被保证19 ;所以共识必须小心使用(一个解决共识的方法是引入“错误探测器”,它能提供或者说限制关于节点的信息10。 共识算法能直接被应用于原子化的数据服务,我们只需要让参与者对全局所有的操作顺序取得一致.29 。无论使用哪种共识的实现,正确性(一致性)都能够被保证,但是了解下层系统的特征能够指导我们选择性能更好的实现(要知道各种实现的精妙之处,请参见e Lynch33)。无论如何,每个操作都使用共识是一个笨拙的实现方式,特别是一些干扰会延迟甚至阻止操作终止。因此使用共识时,需要尽量避免与某些的内存操作一同使用,并使得操作不影响共识的终结。 我们应该注意到,实现共识比实现原子操作更为复杂,特别是两个或者两个以上操作的共识问题不能通过原子读写寄存器来实现31。

组通讯服务

在分布式系统中最重要的基础材料就是组通讯服务(GCSs)5 ,GCS使得在不同节点上运行的操作共同以组的方式工作。操作通过GCS多播服务发送消息到所有组成员以实现分组协作。GCSs 负责保证消息传输的顺序和可靠性。 GCS的基础是组成员服务。每个进程,在每个时间都有一个唯一的组的视图(view),视图包含该组中的操作列表。视图可以随着时间变化,有可能在不同的进程中视图也不相同。GCS引入的另一个重要的概念是虚拟同步性 (virtual synchrony )【译者,参考wiki文章】,其核心要求是通过两个连续的视图同时处理的操作发送同样的消息集合。这样接收者就能根据消息、组成员,应用程序预设规则进行协同合作5。 GCS提供一个方法来实现动态网络的共享内存。其方法如下,例如,在GCS视图同步(view-synchronous GCS)18基础上实现一个全局的、完全排序的多播服务(发给每个视图的消息都附加了一个总顺序,每个参与者收到一个包含这个顺序的前缀)。有序组播是用来对存储器存取操作进行排序,以产生原子性的内存。这种解决方案的主要缺点是,对于大多数GCS实现,形成一个新的视图需要大量的时间,在此期间客户端的内存操作会被延迟(或终止)。新视图的形成通常需要几个回合的沟通,在典型的GCS实现中,即使只有一个节点故障,性能下降也非常明显。在内存服务中,最好能保证读写操作在重构(reconfiguration)时能正常进行,具备一定程度容错而又不产生性能下降是最理想的。 另一种方法是将GCS与ABD算法整合。例如,由De Prisco et al.14所描述的动态原始配置(dynamic primary configuration GCS ) , 在每个配置中使用技术Attiya3以实现原子内存。在这里,一个配置与一个Quorum系统组相结合。配置可以随时改变,由于系统的动态性质或因为需要一个不同的法定人(quorum)数系统,就像和其他以GCS为基础的解决方案一样,读取和写入在重构时被延迟。最后,任何新的配置与以前的配置有交集。这就影响重构的灵活性,可能需要过渡的配置的变化以达到最终所需目的。 Birman6提出了一个关于动态服务复制的通用方法Paxos29 ,这种重构模型结合了虚拟同步(virtual synchrony )与状态机副本,以解决共识问题。

dynastore算法

dynastore2是多writer/多reader的动态原子存储服务的实现。它集成了ABD算法,并允许副本主机集合重构,而且不需要共识的使用。 参加者从一个默认的本地配置开始,即一套副本主机。该算法支持三种操作:读,写,和重新配置。读和写操作分为两个阶段,如果不考虑重新配置,协议与ABD类似的:它使用超过半数的ABD的副本,每个副本保持对象的值和相关的标签。 参与者使用重配置操作(reconfig)来修改当前配置,(+,i)表示副本主机i加入,(–,J),表示主机j移除,reconfig分为两个阶段,阶段一使用一个分布式的弱快照服务发表由update原语引起的变化,阶段二通过scan原语获得由配置中的其他成员提交的变化。快照服务本身不是原子的,它不会为update操作进行全局排序,同时scan操作不能保证反映所有以前完成的update。然而,快照服务足够建立一套有向无环图(DAG),作为参与者的一种状态,存储于每个参与者中。图的顶点对应一种配置,图的边意味这对配置的修改【译者注:c1->(+,i)->c2-(-,j)->c3 c是配置,c1通过添加i节点成为c2,再通过一出j节点成为c3】。 Reconfig需要遍历这个DAG,DAG代表了配置的变更过程。每次遍历过程中,DAG都有可能被反复多次更新,因为不同主机的更新。假设超过半数的参与主机没被移除也没有崩溃,就能确保有某条通过DAG路径在所有主机之间都是相同的。有趣的是,尽管主机本身不了解这个共同的路径,但通过遍历所有的路径同样能确保这条共同的路径也被遍历。遍历过程终止于汇聚节点。为了确保重构过程能够终止,必须假设“存在有限数量的重新配置”。 现在我们回到reconfig的两个阶段。第一阶段的目标是类似于ABD的 获取(Get) 阶段:发现该对象的最新的值-标签对。第二阶段的目标是类似于ABD的Put阶段:传达最新的值-标签对到超过半数的主机。主要的区别是,这两个阶段运行的上下文分别变成了对配置进行增量修改,以及发现其他参与者提交的配置修改。这个过程最终将“引导”出可能的新配置。这个过程是这样实现的:通过遍历DAG的所有可能的路径-也就是配置-从而保证共同路径也被遍历,从而获得最终的新配置。 最后,我们再讨论下read-write操作。Read的实现和reconfig基本相同,不同的地方是:(a)配置更改的集合是空的,(b)所发现的值返回给客户端。Write也类似reconfig,差异是:(a)变更集是空的,(B)一个新的,更高的标签在第一个阶段的完成被产生,(c)新的标记值对在第二阶段传播。请注意,读写操作的配置更改的集合是空的,但两个操作都可以发现其他地方提交的变化,并帮助引导配置的修改。如reconfig操作一样,超过半数节点保持稳定工作保证协议能能顺利运行,有限的重构保证操作能正确终结。 值得重申的是Dyna Store的实现不包含关于重构的获得一致的协议。另一方面,向配置中增加和删除和单个节点,可能导致较大的开销,与之相比,直接用一个完整的配置替换原有配置性能可能更高。因此,read和write操作的延迟更加依赖重构的速度(我们以后将在讨论dynadisk时详细讨论)。最后,为了保证终止,dynastore假设的重新配置是有限的并最终会停止。

Rambo 架构

兰博是支持多读多写对象的动态存储服务24;Rambo是“基本对象的可重构原子内存”的缩写 【Reconfigurable Atomic Memory for Basic Objects】。该算法使用的配置由一组的副本主机加一个定义在这些主机上Quorum系统组成,Rambo通过替换配置来实现重构。值得注意的是,任何quorum配置可以在任何时间安装,不同的quroum配置不需要有非空交集。该算法保证了在所有执行的原子性。 没有重构时,算法类似于ABD 算法3(Lynch and Shvartsman32):每两个阶段都会与当前配置中一个完整的quorum进行交互。新的参与者通过与至少一个现有的参与者进行消息握手(不需要重新配置)。 任何参与者可能会在任何时间崩溃。为使服务的长期运行,quorum可以重新配置。重新配置与读写操作并行进行,并不会直接影响这些操作。此外,多个重构过程可能同时进行。重构涉及两个不相关的协议:用于引入和更新新的配置的组件叫做Recon,回收废弃的配置的组件叫做垃圾处理。 详细来说,每个参与者都保持一个配置地图,或CMAP,存储的配置序列,在节点i,cmapi(k)表示他的第k个配置。 Recon不断提出的新配置,旧的配置又被垃圾回收,这使得序列不断演变。不同参与者CMAP中的内容可能不同,但是Recon始终发出唯一的新配置K,该配置被存储在每个cmapi(K)。这是这样做到的,在c = cmapi(K–1)这个配置中的任何节点i可以在任何时间提出了一个新的配置。不同的配置建议通过执行c成员间的共识以达成和解(在这里,共识可以通过使用某个版本paxos来实现29)。 虽然取得共识的过程可能是缓慢的,在某些情况下,甚至有可能不会终止,但是这不延迟的读写操作(只需保证在操作过程中中至少一个quorum是完整的)。注意该结构的成员可能或可能不知道对象的最新值。升级协议负责垃圾收集旧的配置以及传播最新的对象信息。在这里,一个两阶阶段算法首先通知每个旧配置中的某个quorum有新的配置,然后获取对象的最新值并广播到新配置中的quorum里,并删除过时的配置。 参与者的cmap中可能拥有多个活动配置(尚未被垃圾收集的),如果配置发生的太快,或如果配置升级滞后。在这种情况下,读写操作的行为如下。第一阶段从活动配置中的quorums收集的信息;第二阶段向活动配置中的quorum广播信息。请注意,在每一个阶段,新的配置可能会被发现的。为了解决这个问题,每一阶段都是由一个涉及每个活动配置的定点条件而终止。 存储器存取操作从配置解耦使得读写操作一定会终止,即使Recon的过程可能很缓慢:由于其使用的共识。重构本身涉及到两个独立的活动:通过共识产生的一致的配置序列,然后升级过程最后完成重构。在某些情况下整合这两种活动,可能是有利于提高性能的,例如,RDS服务.11 最后,Rambo 可以视为改进和优化的基础框架,其中有的改进已经有原型系统实现,例如Georgiou et al.20,接下来我们描述了一个对兰博架构的改造:移动Ad Hoc网络。

Ad hoc网络

Ad hoc网络中不使用预先存在的基础设施;相反,移动的节点以及从源到目的地的路径通讯组成了整个网络。 GeoQuorums是一个能够在移动节点的活动随意性很大的物理平台上实现原子共享内存的方法。 GeoQuorums的可以被看作是一个两层的系统,上层实现了动态副本的存储系统,底层基于由移动节点组成的固定焦点(focal point)来实现对象复制。   所谓焦点(Focal point)指地理上的区域,在这个区域中移动节点(设备)常常产生聚集。焦点可能是一个路口,观景台,或沙漠中的绿洲。在焦点附近的移动节点共同组成一个固定的虚拟对象,称为焦点对象(focal point object)。每个focal point均支持本地广播服务,LBCAST,该协议能提供可靠的全序广播(totally ordered broadcast)。LBCAST用来实现一种复制状态机,这种机制能容忍的移动节点加入和离开。如果所有移动节点都离开焦点,焦点对象将失效。 其次,该方法在焦点上定义了一组quorum系统。每个quorum系统涉及两个集合,称为get-quorum和put-quorums,每个get-quorum都和put-quorum有交集。quorums使得焦点能容忍有限数量的故障节点。由于性能的原因,或由于移动节点周期性的迁移,可以使用不同的的quorum系统。 为了促进焦点对象(focal point objects)通信,GeoQuorums假定 GeoCast 服务(如在Imielinski26中一样) 能可靠的使消息传递到一个焦点(focal point)的地理区域。使用此设定,你可以使用兰博框架作为原子存储器系统的顶层,而是用focal point 作为副本主机。出于简单性和效率的考虑,GeoQuorums的方法需要做做额外的修改。第一是如何处理重新配置,第二如何影响读写操作的处理。 无论底层的分布式平台产生怎样的扰动,动态原子性存储服务都需要提供数据的一致性和可用性。 GeoQuorums第一次引入了一种不依赖共识的重新配置。该算法实现在有限数量的预定配置之间的重新配置.算法使用类似Rambo升级协议(update protocal)的两阶段协议取代了共识。在第一阶段,调用者联系所有配置中的完整的get-querum和put-quorum(注意,针对每个focal point最多只需要一次消息交互,即使配置的数量可能非常大)。接着,在第二阶段,信息被传送到下一个配置中任意一个完整的put-quorum。 GeoQuorums实现了一个改进的读写操作,允许一些操作只在一个阶段完成。对于写操作,这是通过全球定位系统(GPS)时钟为写入值产生的标签来实现的,这个标签可以将写入排序。这避免了在其它实现中确定最高的标签的阶段,而且写入协议执行只是一个单一的put阶段,该操作与当前的配置中的任意put-quorum进行交互。如果写操作检测到并行重配置操作,将会等待每个配置的响应(包括至多一次与每个focal point 的关联)。 一旦写入完成,标签就被确定。对于读取,该协议涉及一个或两个阶段。第一,获取(get),从一个完整的get-quorum获得标签最大的值;如果一个并行重构操作被监测到,这个阶段也会等待每个配置中的一个get-quorum进行响应(每个focal point最多一次消息交换)。一旦获得阶段完成,并确定了获得最大的标签,读(read)操作将终止。否则,读(read)操作使用放入(put)阶段将最大标签值发送到一些put-quorum。关于已确认标签上的信息会被系统广播以实现单阶段读操作。 使用分离的get-quorum和put-quorum使得我们能够根据系统读写操作的平衡来调优系统。当有更多的写操作时,系统可以重新配置以使用较大的get-quorum和较小的put-quorum,因为write只有使用put-quorums。反之亦然 GeoQuorums使用实时时间戳(由GPS提供)以加快读写操作,允许写和读在一个阶段内完成。最后,假设有一套固定的focal points限制了系统的可发展性,但允许算法在重构操作时不使用共识。

从理论到实践

(DSM)精确的一致性保证、对故障的容忍、在动态的环境中工作的能力促使研究人员构建了一些探索性的实现,比如我们已经提到过的,探索性的Rambo变种实现。在这里,我们介绍另外两个实现。第一个是一个从兰博算法思想派生的分布式的磁盘阵列36 。第二个是基于dynastore算法的实现37 。 联合砖块阵列(FAB)36 是由HP实验室开发和评估的存储系统。FAB主要是磁盘存储方案,存储的基本对象的逻辑块(logical blocks)。FAB的实现目标是从工作负载、故障处理、无干扰的请求恢复等各个方面超越传统的主从(Master-Slave)副本分配模式,FAB系统中使用的计算机被称为“砖块”(brick),通常配备普通商用的磁盘和网络接口。为实现分布,FAB将存储切为若干逻辑存储块,使用擦除码(erasure-coding)算法复制每个逻辑块到bricks子集。 该系统是基于多数仲裁(majority quorum【译者:超过半数的提议将成为最终提议】)制度,在确保服务寿命的同时,一个重构算法在砖块添加或删除时移动数据。客户端通过发送一个请求到砖块指定的逻辑块实现读写操作。砖块使用Rambo算法确定存储块被存储在那些砖块上,并进行读写操作,同时使用标记-值对实现对写操作的排序。写操作需要两个阶段完成,读操作需要一个或者两个阶段:当所有砖块返回的值标签都相同,只需一个阶段就能完成读操作。为了提高效率reader向已激活的空闲砖块请求存储块,只向quorum中的其他成员获取tag。 对FAB实现的评估结果表明:Fab性能类似于集中式的解决方案,同时能提供连续的服务和高可用性。 Dynadisk 37是用于评估的dynastore算法实现。测试在局域网环境下进行,采用以数据为中心的方法,在副本主机采用被动网络存储设备。 DynaDisk的设计支持在无共识的条件下重新配置服务,或采用共识进行部分同步。 评估结果表明,在无重构的情况下,两个版本算法有着类似的读写延迟,当多个重构同时发生时,异步的无共识方法的延迟有明显增加。这是由于无共识算法必须检查多种可能的配置,而整合了共识的重构算法通常只需要检查一个所有节点一致认可的配置。在无共识的DynaDisk版本中,重构延迟有少量下降,当多个重构操作同时发生时。在这种情况下,有共识的DynaDisk需要更长时间来获得共识。

讨论

我们提出了动态分布式系统中实现自动一致的存储服务的几种方法。在这样的系统中参与节点集合的变化可能由于节点故障、自愿退出或计划的节点更新。我们专注于原子一致性,因为这个概念足够直观和自然,它对内包装多个副本,对外提供了如同操作单一副本一样的串行操作。这样的服务更适合构建分布式应用程序,特别是考虑到使用共享内存的编程范式比使用消息传递范式更容易设计分布式算法。 本文中提到的方法只是众多分布式存储服务实现方法的代表。这些方法,每一个都有优点和缺点。例如,基于共识的解决方案,虽然概念上很简单,但通常在特定的阶段需要协调员的参与【译者:协调以获取共识】,其性能可能会严重依赖于可用的协调员。即使大多数主机无故障,协调员故障时,也会出现大量的延迟。组通信服务(GCS)是构建高可用性低延迟网络中的有效工具,但是当视图(view)发生变化时开销很高,即使故障的数目较小但持续时间较长。理论上更加优雅的方法,比如dynastore实现增量节点修改、Rambo实现quorum增量替换、GeoQuorums实现focal point,虽然实现更复杂,但具有更大的灵活性,并允许系统进行特定的优化。能否实现良好的性能,同时也取决于部署平台稳定,故障率的假设,以及一些其他因素,比如FAB中容忍性的考量。 不管如何对副本主机集合进行重构,何时进行重构也是必须解决的挑战。决定权可以被交个单个主机,例如,在任意一个节点加入或退出服务都要求进行重构。虽然在当扰动不多且副本集合较小时,这种方法简单有效,但当节点不断加入和离开服务时,这种方法可能会造成不必要的开销,即使一些核心的主机是稳定的足以提供良好的服务。另一种方法是把决策何时重新配置的权利交给另一个分布式服务,通过观察和推理性预判,找到重构的最佳时机。这是一个更复杂的解决方案,但它具有提供优质的服务的潜力。另外,还可以考虑选择一组合适的主机。系统并不需要同意每一个希望成为复制主机的节点的申请。在这里,决定何时重新配置的外部服务,同时也可以用来选择设置节点的目标。请注意,目标集的不要求共识,所有的存储服务都能够处理多个目标集同时被提交的的情况。 无论副本主机故障的幅度和频率,动态原子共享内存服务都需要保证所有执行的一致性。但是读写操作能否终止,就需参考故障的条件了。对于静态的系统,一般来说这个限制很容易描述:在这里,主机的任何少数子集都是是允许失败。动态系统的故障模式的约束更加复杂,取决于具体的算法的方法;读者可以参考引用的文章以了解详情。 随着云服务的出现,分布式存储服务将继续吸引人们的注意。技术挑战和性能方面的开销,造成了现有的分布式存储解决方案回避原子一致性保证。商业解决方案,如谷歌的文件系统(GFS22 ),亚马逊的Dynamo15 ,和facebook的Cassandra,28 提供直观性和保证性稍弱的服务。但是在本问中讨论的概念也得到了一些实际系统的回应。例如,共识技术在GFS22 中使用,以保证系统的正确配置,正如在Rambo中实现的一样;Spanner13 中的全球时间用,原理类似GeoQuorums;Dynamo15 的副本访问协议,采用了quorum,正如我们在本文中给出的一样。这些例子为我们追寻动态系统中的数据一致性算法提供了动力。 一致性存储系统一直是活跃的研究领域和开发领域,有充分的理由相信,一旦出现具有优越的容错性,同时又具备高性能的动态存储系统,将在构建复杂的分布式应用程序中发挥重要的作用。分布式应用程序对一致性和高性能的需求将不断催生对原子的读/写存储器需求。

致谢

我们的工作部分由NSF award 1017232支持。我们谢谢Jennifer Welch以及其他无数不知道姓名的评论者们的真知灼见。 引用 1. Aguilera, M., Keidar, I., Martin, J.-P. and Shraer, A. Reconfiguring replicated atomic storage: A tutorial.Bulletin of the EATCS 102 (Oct. 2010), 84–108. 2. Aguilera, M.K., Keidar, I., Malkhi, D. and Shraer, A. Dynamic atomic storage without consensus. JACM 58(Apr. 2011), 7:1–7:32. 3. Attiya, H., Bar-Noy, A. and Dolev, D. Sharing memory robustly in message-passing systems. JACM 42, 1 (Jan. 1995), 124–142. 4. Attiya, H. and Welch, J.L. Sequential consistency versus linearizability. ACM Trans. Comput. Syst. 12, 2 (May 1994), 91–122. 5. Birman, K. A history of the virtual synchrony replication model. Replication: Theory and Practice, LNCSvol. 5959 (2010), 91–120. 6. Birman, K., Malkhi, D. and Renesse, R.V. Virtually synchronous methodology for dynamic service replication. Technical report, MSR-TR-2010-151, Microsoft Research, 2010. 7. Brewer, E.A. Towards robust distributed systems, July 2000. 8. Brewer, E.A. Pushing the cap: Strategies for consistency and availability. IEEE Computer 45, 2 (2012), 23–29. 9. Calder, B. et al. Windows azure storage: A highly available cloud storage service with strong consistency. InProceedings of SOSP '11 (Oct 23-26, 2011), 143–157. 10. Chandra, T.D., Hadzilacos, V. and Toueg, S. The weakest failure detector for solving consensus. JACM(1996), 685–722. 11. Chockler, G., Gilbert, S., Gramoli, V., Musial, P.M. and Shvartsman, A.A. Reconfigurable distributed storage for dynamic networks. J. Parallel and Distributed Computing 69, 1 (2009), 100–116. 12. Chockler, G., Guerraoui, R., Keidar, I. and Vukolić, M. Reliable distributed storage. IEEE Computer, 2008. 13. Corbett, J.C. et al. Spanner: Google's globally distributed database. In Proceedings of the 10th USENIX Symp. On Operating Sys. Design and Implementation (2012), 251–264. 14. De Prisco, R., Fekete, A., Lynch, N.A. and Shvartsman, A.A. A dynamic primary configuration group communication service. In Proceedings of the 13th Int-l Symposium on Distributed Computing. Springer-Verlag, 1999, 64–78. 15. DeCandia, G. et al. Dynamo: Amazon's highly available key-value store. In Proceedings of SIGOPS Oper. Syst. Rev. 41, 6 (Oct. 2007), 205–220. 16. Dolev, S., Gilbert, S., Lynch, N., Shvartsman, A. and Welch, J. GeoQuorums: Implementing atomic memory in ad hoc networks. In Proceedings of the 17th International Symposium on Distributed Computing(2003), 306–320. 17. Dutta, P., Guerraoui, R., Levy, R.R. and Vukolić, M. Fast access to distributed atomic memory. SIAM J. Comput. 39, 8 (Dec. 2010), 3752–3783. 18. Fekete, A., Lynch, N. and Shvartsman, A. Specifying and using a partitionable group communication service. ACM Trans. Comput. Syst. 19, 2 (2001), 171–216. 19. Fischer, M.J., Lynch, N.A. and Paterson, M.S. Impossibility of distributed consensus with one faulty process. JACM 32, 2 (1985), 374-382. 20. Georgiou, C., Musial, P.M. and Shvartsman, A.A. Developing a consistent domain-oriented distributed object service. IEEE Transactions of Parallel and Distributed Systems 20, 11 (2009), 1567–1585. 21. Georgiou, C., Musial, P.M. and Shvartsman, A.A. Fault-tolerant semifast implementations of atomic read/write registers. J. Parallel and Distributed Computing 69, 1 (Jan. 2009), 62–79. 22. Ghemawat, S., Gobioff, H. and Leung, S.-T. The Google File System. In Proceedings of the 19th ACM Symposium on Operating Systems Principles (2003), 29–43. 23. Gilbert, S. and Lynch, N. Brewer's conjecture and the feasibility of consistent, available, partition-tolerant web services. SIGACT News 33 (June 2002), 51–59. 24. Gilbert, S., Lynch, N. and Shvartsman, A. RAMBO: A robust, reconfigurable atomic memory service for dynamic networks. Distributed Computing 23, 4, (Dec. 2010), 225–272. 25. Herlihy, M.P. and Wing, J.M. Linearizability: A correctness condition for concurrent objects. ACM Trans. Programming Languages and Systems 12, 3 (July 1990), 463–492. 26. Imieliński, T. and Navas, J.C. GPS-based geographic addressing, routing, and resource discovery.Commun. ACM 42, 4 (Apr. 1999), 86–92. 27. Lakshman, A. and Malik, P. Cassandra: A decentralized structured storage system. SIGOPS Oper. Syst. Rev. 44, 2 (Apr. 2010), 35–40. 28. Lamport, L. On interprocess communication. Part I: Basic formalism. Distributed Computing 2, 1 (1986), 77–85. 29. Lamport, L. The part-time parliament. ACM Trans. Comput. Syst. 16, 2 (1998), 133–169. 30. Liskov, B. The power of abstraction. In Proceedings of the 24th Int-l Symposium Distributed Computing. N.A. Lynch and A.A. Shvartsman, Eds. LNCS, vol. 6343, Springer, 2010. 31. Loui, M.C. and Abu-Amara, H.H. Memory requirements for agreement among unreliable asynchronous processes. In Parallel and Distributed Computing, Vol 4 of Advances in Computing Research. F.P. Preparata, Ed. JAI Press, Greenwich, Conn., 1987, 163–183. 32. Lynch, N. and Shvartsman, A. Robust emulation of shared memory using dynamic quorum-acknowledged broadcasts. In Symposium on Fault-Tolerant Computing. IEEE, 1997, 272–281. 33. Lynch, N.A. Distributed Algorithms. Morgan Kaufmann Publishers Inc., 1996. 34. Martin, J.-P. and Alvisi, L. A framework for dynamic byzantine storage. In Proc. Intl. Conf. on Dependable Systems and Networks, 2004. 35. Rodrigues, R., Liskov, B., Chen, K., Liskov, M. and Schultz, D. Automatic reconfiguration for large-scale reliable storage systems. IEEE Trans. on Dependable and Secure Computing 9, 2 (2012), 145–158. 36. Saito, Y., Frølund, S., Veitch, A., Merchant, A. and Spence, S. Fab: Building distributed enterprise disk arrays from commodity components. SIGARCH Comput. Archit. News 32, 5 (Oct. 2004), 48–58. 37. Shraer, A. Martin, J.-P., Malkhi, D. and Keidar, I. Data-centric reconfiguration with network-attached disks. In Proceedings of the 4th Int'l Workshop on Large Scale Distributed Systems and Middleware (2010), ACM, 22–26. 38. Vukolić, M. Quorum systems: With applications to storage and consensus. Synthesis Lectures on Distributed Computing Theory 3, (Jan. 3, 2012). 1–146. Back to Top

作者

Peter Musial (pmmusial@csail.mit.edu) 是EMC的主要程序员,同时也是麻省理工下属的CSAIL 的主要程序员 Nicolas Nicolaou (nicolasn@cs.ucy.ac.cy) 是塞浦路斯大学计算机科学系的客座讲师。 Alexander A. Shvartsman (aas@cse.uconn.edu) 是康乃提卡大学计算机科学与工程系的教授 ©2014 ACM  0001-0782/14/06 first page. Copyright for components of this work owned by others than ACM must be honored. Abstracting with credit is permitted. To copy otherwise, to republish, to post on servers, or to redistribute to lists, requires prior specific permission and/or fee. Request permission to publish from permissions@acm.org or fax (212) 869-0481. The Digital Library is published by the Association for Computing Machinery. Copyright © 2014 ACM, Inc.

Spark体系架构/内存分配/集群模式JVM分配/Partition

$
0
0
最近看到一篇关于Spark架构的博文,作者是 Alexey Grishchenko。看过Alexey博文的同学应该都知道,他对Spark理解地非常深入,读完他的 “spark-architecture” 这篇博文,有种醍醐灌顶的感觉,从JVM内存分配到Spark集群的资源管理,步步深入,感触颇多。因此,在周末的业余时间里,将此文的核心内容译成中文,并在这里与大家分享。如在翻译过程中有文字上的表达纰漏,还请大家指出。 首先来看一张Spark 1.3.0 官方给出的图片,如下: 在这张图中,你会看到很多的术语 ,诸如“executor”, “task”, “cache”, “Worker Node” 等。原作者表示,在他开始学spark的时候,上述图是唯一一张可以找到的图片(Spark 1.3.0),形势很不乐观。更加不幸地是,这张图并没有很好地表达出Spark内在的一些概念。因此,通过不断地学习,作者将自己所学的知识整理成一个系列,而此文仅是其中的一篇。下面进入核心要点。

Spark 内存分配

在你的cluster或是local machine上正常运行的任何Spark程序都是一个JVM进程。对于任何的JVM进程,你都可以使用-Xmx-Xms配置它的堆大小(heap size)。问题是:这些进程是如何使用它的堆内存(heap memory)以及为何需要它呢?下面围绕这个问题慢慢展开。 首先来看看下面这张Spark JVM堆内存分配图: Spark-Heap-Usage.png

Heap Size

默认情况下,Spark启动时会初始化512M的JVM 堆内存。处于安全角度以及避免OOM错误,Spark只允许使用90%的的堆内存,该参数可以通过Spark的spark.storage.safetyFraction参数进行控制。 OK,你可能听说Spark是基于内存的工具,它允许你将数据存在内存中。如果你读过作者的 Spark Misconceptions 这篇文章,那么你应该知道Spark其实不是真正的基于内存(in-memory)的工具。它仅仅是在LRU cache (http://en.wikipedia.org/wiki/Cache_algorithms) 过程中使用内存。所以一部分的内存用在数据缓存上,这部分通常占安全堆内存(90%)的60%,该参数也可以通过配置spark.storage.memoryFraction进行控制。因此,如果你想知道在Spark中可以缓存多少数据,你可以通过对所有executor的堆大小求和,然后乘以safetyFractionstorage.memoryFraction即可,默认情况下是0.9 * 0.6 = 0.54,即总的堆内存的54%可供Spark使用。

Shuffle Memory

接下来谈谈shuffle memory,计算公式是 “Heap Size” * spark.shuffle.safetyFraction * spark.shuffle.memoryFractionspark.shuffle.safetyFraction的默认值是 0.8 或80%, spark.shuffle.memoryFraction的默认值是0.2或20%,所以你最后可以用于shuffle的JVM heap 内存大小是 0.8*0.2=0.16,即总heap size的16%。 问题是Spark是如何来使用这部分内存呢?官方的Github上面有更详细的解释(https://github.com/apache/spark/blob/branch-1.3/core/src/main/scala/org/apache/spark/shuffle/ShuffleMemoryManager.scala)。总得来说,Spark将这部分memory 用于Shuffle阶段调用其他的具体task。当shuffle执行之后,有时你需要对数据进行sort。在sort阶段,通常你还需要一个类似缓冲的buffer来存储已经排序好的数据(谨记,不能修改已经LRU cache中的数据,因为这些数据可能会再次使用)。因此,需要一定数量的RAM来存储已经sorted的数据块。如果你没有足够的memory用来排序,该怎么做呢?在wikipedia 搜一下“external sorting” (外排序),仔细研读一下即可。外排序允许你对块对数据块进行分类,然后将最后的结果合并到一起。

unroll Memory

关于RAM最后要讲到”unroll” memory,用于unroll 进程的内存总量计算公式为:spark.storage.unrollFraction * spark.storage.memoryFraction *spark.storage.safetyFraction。默认情况下是 0.2 * 0.6 * 0.9 = 0.108, 即10.8%的heap size。 当你需要在内存中将数据块展开的时候使用它。为什么需要 unroll 操作呢?在Spark中,允许以 序列化(serialized )和反序列化(deserialized) 两种方式存储数据,而对于序列化后的数据是无法直接使用的,所以在使用时必须对其进行unroll操作,因此这部分RAM是用于unrolling操作的内存。unroll memory 与storage RAM 是共享的,也就是当你在对数据执行unroll操作时,如果需要内存,而这个时候内存却不够,那么可能会致使撤销存储在 Spark LRU cache中少些数据块。

Spark 集群模式JVM分配

OK,通过上面的讲解,我们应该对Spark进程有了进一步的理解,并且已经知道它是如何利用JVM进程中的内存。现在切换到集群上,以YARN模式为例。 Spark-Architecture-On-YARN 在YARN集群里,它有一个YARN ResourceMananger 守护进程控制着集群资源(也就是memory),还有一系列运行在集群各个节点的YARN Node Managers控制着节点资源的使用。从YARN的角度来看,每个节点可以看做是可分配的RAM池,当你向ResourceManager发送request请求资源时,它会返回一些NodeManager信息,这些NodeManager将会为你提供execution container,而每个execution container 都是一个你发送请求时指定的heap size的JVM进程。JVM的位置是由 YARN ResourceMananger 管理的,你没有控制权限。如果某个节点有64GB的RAM被YARN控制着(可通过设置yarn-site.xml 配置文件中参数 yarn.nodemanager.resource.memory-mb ),当你请求10个4G内存的executors时,这些executors可能运行在同一个节点上,即便你的集群跟大也无济于事。 当以YARN模式启动spark集群时,你可以指定executors的数量(-num-executors 或者 spark.executor.instances参数),可以指定每个executor 固有的内存大小(-executor-memory 或者 spark.executor.memory),可以指定每个executor使用的cpu核数(-executor-cores 或者 spark.executor.cores),可以指定分配给每个task的core的数量(spark.task.cpus),还可以指定 driver 上使用的内存(-driver-memory 或者 spark.driver.memory)。 当你在集群上执行应用程序时,job程序会被切分成多个stages,每个stage又会被切分成多个task,每个task单独调度,可以把每个executor的JVM进程看做是task执行槽池,每个executor 会给你的task设置 spark.executor.cores/ spark.task.cpus execution个执行槽。例如,在集群的YARN NodeManager中运行有12个节点,每个节点有64G内存和32个CPU核(16个超线程物理core)。每个节点可以启动2个26G内存的executor(剩下的RAM用于系统程序、YARN NM 和DataNode),每个executor有12个cpu核可以用于执行task(剩下的用于系统程序、YARN NM 和DataNode),这样整个集群可以处理 12 machines * 2 executors per machine * 12 cores per executor / 1 core = 288 个task 执行槽,这意味着你的spark集群可以同时跑288个task,几乎充分利用了所有的资源。整个集群用于缓存数据的内存有0.9 spark.storage.safetyFraction * 0.6 spark.storage.memoryFraction * 12 machines * 2 executors per machine * 26 GB per executor = 336.96 GB. 实际上没有那么多,但在大多数情况下,已经足够了。 到这里,大概已经了解了spark是如何使用JVM的内存,并且知道什么是集群的执行槽。而关于task,它是Spark执行的工作单元,并且作为exector JVM 进程中的一个thread执行。这也是为什么Spark job启动时间快的原因,在JVM中启动一个线程比启动一个单独的JVM进程块,而在Hadoop中执行MapReduce应用会启动多个JVM进程。

Spark Partition

下面来谈谈Spark的另一个抽象概念”partition”。在Spark程序运行过程中,所有的数据都会被切分成多个Partion。问题是一个parition是什么并且如何决定partition的数量呢?首先Partition的大小完全依赖于你的数据源。在Spark中,大部分用于读取数据的method都可以指定生成的RDD中Partition数量。当你从hdfs上读取一个文件时,你会使用Hadoop的InputFormat来指定,默认情况下InputFormat返回每个InputSplit都会映射到RDD中的一个Partition上。对于HDFS上的大部分文件,每个数据块都会生成一个InputSplit,大小近似为64 MB/128 MB的数据。近似情况下,HDFS上数据的块边界是按字节来算的(64MB一个块),但是当数据被处理时,它会按记录进行切分。对于文本文件来说切分的字符就是换行符,对于sequence文件,它以块结束等等。比较特殊的是压缩文件,由于整个文件被压缩了,因此不能按行进行切分了,整个文件只有一个inputsplit,这样spark中也会只有一个parition,在处理的时候需要手动对它进行repatition。 本文是对 Alexey Grishchenko 的 Distributed Systems Architecture 系列的第一篇文章核心要点的翻译,原作者的第二篇文章是关于shuffle的,【原文链接】,第三篇文章是关于memory 管理模式的,【原文链接】,极力推荐。 原文:spark-architecture 翻译原文:D.W's Diary

相关阅读

  1. https://0x0fff.com/spark-misconceptions/
  2. http://en.wikipedia.org/wiki/Cache_algorithms
  3. https://0x0fff.com/spark-architecture-shuffle/
  4. https://0x0fff.com/spark-memory-management/
  5. https://0x0fff.com/spark-architecture/
  6. http://en.wikipedia.org/wiki/External_sorting
Viewing all 130 articles
Browse latest View live