Dropbox And Google

两段新闻

Dropbox的成功大部分归功于 Python,这个语言可以使我们快速迭代开发。然而,为了支持日益增长的用户量,我们的基础设施日渐成熟,这时我们开始寻找一种更为高效的方式来改变系统规模。大约在一年前,我们作出决定,把对于性能要求很苛刻的后台部分从Python迁移到了Go语言,以提供更好的并发支持和更快的运行速度。一个规模很小的工程师团队做出了大量的努力,这背后大约是200,000(二十万)行Go语言代码。此时,我们成功地把架构的大部分迁移到了Go语言。

还记得 朱老师谈到未来的计算模式,是无限输入(输入不会停止,如互联网数据)的。今年的Google I/O大会上,宣布 Google已停用MapReduce计算模式(框架),改用 Cloud Dataflow。“Cloudflow 处理整个数据流,而MapReduce 处理单一的流”。具体参见下面 引用。

新的事物总是层出不穷。。。 加油

Dropbox 来自 http://blog.jobbole.com/71414/

Google 来自 http://blog.jobbole.com/72145/

Use SSH Secure Shell login QingCloud

在青云上创建主机后,为了安全,平台默认使用公私钥的方式登录主机。

青云 ssh设置的方法参考 http://www.chenshake.com/page/8/

设置好后,可以从网站上下载一个私钥文件。这个私钥文件是OpenSSH格式的,SSH Secure Shell和 Putty 客户端无法使用。因为 SSH Secure Shell只能使用SSH格式,用不了 OpenSSH格式,需要转换并导入。

解决办法如下:

1 下载一个工具 puttygen 

2 运行puttygen , 点击load按钮,导入从青云上下载的私钥文件

如果给Putty用,点击“Save Private key” ,保存为 newfile.ppk ,然后通过该文件来使用putty

如果给 SSH Secure Shell用,点击菜单栏的 “Conversions”,然后 “export ssh.com key”, 会生成一个私钥文件,文件名自己取 如 newsshfile. 生成好后,还需要一个公钥文件。点击界面上的 “Save public key” 即可生成一个 公钥文件。注意文件名为 刚才的私钥文件名后面加.pub后缀, 如 newsshfile.pub.  公私钥文件要放在同一个文件夹。

3 打开 SSH Secure Shell , 点击菜单栏的 Edit,子菜单 Settings,然后是 Global Setting下的User Authentication下的Keys项,打开后 点击 “Import”按钮,选择 newsshfile.pub 文件 即可 导入。

4 登录,选择 public key即可。

ps:

收费软件 XShell 直接支持 OpenSSH 和 SSH两种格式。

Linux下登录根本不用考虑该问题。。。 ssh -i private_file username@host 即可

参考:

http://unix.stackexchange.com/questions/84060/convert-openssh-private-key-into-ssh2-private-key

sql inject

今天实验室答辩时,师弟提到了sql注入。正好,用图书馆的检索系统,发现速度很慢。首先想了下怎样可以优化,找了下nosql多关键字检索,还真有这样的一篇论文。但我还是更好奇图书馆的数据到底有多少了,怎么可以这么慢。于是测了下。结果如下,确实书还挺多的,五十多万册。

20140528014821

 

这还是只是一部分,不过应该是绝大部分了,只漏了一小丁点。学校这么有钱,还用的是Java,配套数据库应该是Oracle啊,也不至于。。。。用了几分钟啊

使用python启动一个最简单的web服务器

【1】从官网下载python的安装包  https://www.python.org/downloads/ (有两个版本 python2, python3)

【2】进入到文件(文件夹)所在的目录,打开一个命令行窗口

python2 :  python -m SimpleHTTPServer 8080

python3:  python -m http.server [8000]

【3】打开浏览器即可访问 localhost:8080/path/to/staticfile

html5 and file upload

前段时间比较了各种计算文件摘要的算法,发现还是MD5和SHA1两个算法在低碰撞、高速率两方面做的好一点,一般来说选择MD5就可以了,SHA1慢一点。通过计算文件Hash不仅可以减少服务器的文件数,还可以加快用户上传文件的速度,可谓一举两得。但是由于HTML的原因,通过浏览器上传文件是需要在服务器计算该Hash值(除非使用插件)。不仅占用服务器资源,也造成服务器长时间无响应的“假死”状态。

通过HTML5的File API可以读取文件内容,Worker对象可以建立JavaScript线程,完美实现浏览器端文件Hash计算。例:https://md5file.com/calculator。而且,支持HTML5 Worker的浏览器还支持文件拖拽,具有非常好的用户体验。

对于公有云存储而言,是一个不错的选择。

PS:连JavaScript都有线程了,PHP什么时候会有了。。。。

http host vs server name

今天调试 Yale 的 cas 客户端,发现客户端返回地址总是为localhost,开始还以为这个地址是由服务器端根据refer和client address来解析得到的。后来 阅读客户端源码才知道是由客户端传递给服务器端的。

在获取客户端的callback地址时,使用 Client.php 中的 _getServerUrl 方法来计算。
该方法中前半部分在处理proxy后的情况,这是个复杂问题,因为根据代理服务器的级别程度不同,隐藏或修改的信息不同。只能按照信息是可靠的且是参考HTTP规范的来识别。
后半部分是一般情况,接下来讨论的重点。主要是HTTP的三个参数的处理 HTTP_HOST, SERVER_NAME ,SERVER_PORT。
HTTP_HOST 是 HTTP 请求中 HOST 头字段
  根据 HTTP/1.1 规范 (RFC 2616 ), HOST 应该符合如下要求,原文如下:

In order to prevent denial of service attacks, an invalidation based on the URI in a Location or Content-Location header MUST only be performed if the host part is the same as in the Request-URI.

 也就是 HOST 字段 应该和 URL 中的 HOST part 部分相同。基于该规则下,HTTP_HOST 可能会含有端口号。

 

SERVER_NAME 是当前Web服务器名,可在Web Server程序(如 Apache,Nginx)的配置文件中配置(server_name , 一般默认为 localhost)。

 PHP中的解释:
    The name of the server host under which the current script is executing. If the script is running on a virtual host, this will be the value defined for that virtual host.
  CGI文档中对SERVER_NAME参数的定义(规范):
The SERVER_NAME variable MUST be set to name of the server host to
which the client request is directed. It is a case-insensitive
hostname or network address. It forms the host part of the Script-
URI. The syntax for an IPv6 address in a URI is defined in RFC 2373   [12].
  SERVER_NAME = server-name
  server-name = hostname | ipv4-address | ( “[” ipv6-address “]” )
A deployed server can have more than one possible value for this
variable, where several HTTP virtual hosts share the same IP address.
In that case, the server uses the contents of the Host header to
select the correct virtual host.
另外Web Server中还可以配置 server_name_alias 。
SERVER_PORT 是 TCP 链接时的端口号,也就是服务器端配置的监听端口号。因为很多协议如HTTP默认80端口, HTTPS默认443端口。所以在request url中可以缺省。
综上,可以看到 http_host 和 server_name 可能相同,可能不同。不同时,可能只是因为端口部分一个有,一个没有。也可能完全不同。
在 CAS 的 客户端中,优先选取 server_name,然后再选取 http_host 。因为 这个原因导致了 前面的那个问题。
我想优先server_name的原因是因为在某些语言中如PHP,提示http_host可能会不存在(不规范的HTTP请求导致?)。
至于为什么php中http_host可能不存在,需要阅读PHP源码。
如果是因为HTTP请求本身导致,依据RFC2616的定义,HTTP_HOST 是必须有的,对于建立TCP链接蓄意破坏HTTP协议的请求导致http_host不存在,应该作为非法请求处理。
不仅仅程序语言问题,还有另外一个重大问题是涉及到不同的人:
    对于一个请求是否作为非法请求进行处理,往往是产品经理或者需求管理、系统安全人员的工作范畴。
    对于应用程序,http_host或server_name的使用是Web开发人员的工作。
    对于server_name的配置,是系统运维人员的工作。
因为在不同的应用环境下问题是不同的。http_host应该优先server_name处理,而标准CGI规范下,server_name 和 http_host中的hostname部分是相同的。

swift object features

swift 对象特性

1 fake directory 伪目录

2 multi-version  多版本
3 support large file (over 5 GiB) 大对象/文件
伪目录:之所以叫伪目录,是因为swift中并没有目录这一概念。但又可以做到“目录”方面的特性,是因为object的命名可以包含斜杠,就像通常Web中的URI的path一样。通过对名字中的斜杠进行分割,我们可以得到一个目录树结构。从而通过客户端API的封装来实现对目录的一些操作,但这并不是一个真正的目录,所以没有目录修改时间等元数据等的记载。尤其是,不存在目录大小这个概念。
多版本:swift提供了对象对版本功能,具体 找 Google。
大文件: 大文件的管理 用到了 一个元数据 和 一个命名惯例,和分段管理方法。
    swift 为了 平衡分区,限制了最大文件大小(即不容许上传特大文件),为了上传更大的文件,通过分段来实现。
    一个元数据即object的X-Object-Manifest属性,具有该属性的文件,称为“清单文件”。该类文件/对象,没有内容。其X-Object-Manifest属性的值,为一个包含container名字的path前缀,具有该前缀的对象都是该大文件对象的分段文件。
   一个命名惯例,为了将大文件和大文件的分段文件分开管理。比如在list的时候看起来简洁,将分段文件放到单独的容器中。该类container的命名为对应大文件的容器名字加上”_segments”。当然,也是可以不创建,全部放到一个容器中的。
  分段管理方法,该分段管理是对客户端可见的,因此可以单独对分段文件进行修改,上传等。在上传时,主要是客户端来控制,不过下载的时候,swift内部可识别“清单文件”,并提供大文件的合并下载。当然 客户端也是可以自己下载各分段后合并。
  另外,分段文件的命名。比如swift客户端 命名方法:
     大文件 :  com/google/big.db
     分段文件: com_segments/google/big.db/12345678900/102410241024/00000000
                        com_segments/google/big.db/12345678900/102410241024/00000001
     即 前面容器加上 _segmnets ,文件名字后面加上总文件大小,分段大小,分段号。
  同容器命名一样,由于是客户端控制,也可以使用其他方法。
================
PS, 之所以 看这个 是因为,要做 Swift EC,这个在Swift blueprint 中已提出,但我不想和Swift组织那样从Swift内部开始写,感觉这样比较困难。从外围来做工作比较简单一些。
从Swift大文件的管理方法,可以为此提供一些思路。
另外,测试了 Jerasure 和 zfec 在 n=4,m=2 下的文件编解码。 在我的测试中,zfec表现更好,而且zfec有python sdk,虽然 自己写了个 Jerasure的python wrapper, 但没有仔细测试,对自己代码还缺点信心。
前期工作还是没有做好。。。引以为戒。。。

sqlite

SQlite 作为 著名的 嵌入式 数据库,被广泛采用。OpenStack/Swift 就使用 sqlite作为元数据存储方式。(不过 由于对大数据处理出现丢失情况,正在考虑其他方案)。支持的语言、平台(操作系统)众多。对于数据量小的结构化数据不适为一个好的方案。

一、 工具

截止 目前(2014-1-7) 最新 sqlite 版本为 3.8。在 windows操作系统, 除了官网提供的命令行工具,还有一些开源/免费的GUI工具。如 SQLite Expert(个人版)、Sqliteadmin、SQLite Database Browser、SQLiteSpy。

sqlite expert 需要安装。支持 unicode。

sqliteadmin 绿色版(免安装),不支持 unicode,数据库文件路径含有中文字符是会打开失败。

sqlite database brower 绿色版,支持 unicode。

sqlitespy 绿色版,支持 unicode。小而精巧,个人比较喜欢这个软件。要是支持文件拖拽就好了,毕竟写路径比较麻烦。

综上: 推荐 使用 SQLite Database Browser 。下载地址:http://sourceforge.net/projects/sqlitebrowser/

sqlitespy 下载地址:http://www.yunqa.de/delphi/doku.php/products/sqlitespy/index

二、元数据

python 中包含 sqlite的标准库。所以可以直接使用python来操作sqlite数据库。

除了操作表记录外,有时我们也需要查询表结构等元数据信息,这些信息保存在 sqlite_master 表中。 包含5个字段(参考2):type 类型名 如 table、index等;name 名字(表名、索引名等);tbl_name 表名; rootpage 结构的页号; sql 存储对应的sql语句。

Python 代码:

 

参考:

http://www.oschina.net/news/43608/5-popular-and-free-sqlite-management-tools

http://www.sqlite.org/fileformat2.html

Swift Object Storage

分布式存储系统 有几个重要功能 features:

1 集群管理
2 名字空间
3 分布与查找
在详细讨论之前,先引入几个基本概念。
node, drive , device, disk , backend 这几个出现在好多文档里面。它们都是一个意思。简称 dev. 即指一块磁盘,也就是 挂载在 /srv/node 下的目录。因为 这些 目录即挂载点分别对应着一个磁盘。当然也可以将一个物理磁盘划分为多个分区,挂载在/srv/node下的多个目录。或者是 /srv/[num]/node 目录下。注意与物理主机节点相区别,有时也会用Node。
partition ,指一致性哈希中vnode,也就是 虚结点,简称 part。
replica,副本,也就是 一份数据或对象的多份拷贝,但是各个拷贝的副本号不同。
1 集群管理
   先介绍一下,主机架构。swift集群中分为 proxy主机和 storage主机。当然一个主机也可以同时具有这两种角色。
   如下图:
          
注意 图中 的 Node 和上面 提到的不是一个意思,指的是 Host,即物理主机节点。
一个集群 可以 有 多个 proxy ,在 多个 proxy间 负载 ,可以使用 LVS 或 f5 等 各种 软硬件方式。
下面讨论 devices 的管理。
  swift 采用 分层 tier 管理所有的dev。
   在1.8.0 即 H版 中  加入region 概念。也就是 区域,来支持一个全球的swift集群。
   然后是 zone ,是 node 的一个集合,可以是 几台 物理主机,或 一个 机架 或 一个机房,甚至一个数据中心。
   然后是 物理主机 Host。
   最后是 物理设备 即 dev。
   如下所示: 来源 swift/swift/common/ring/utils.py
    Example:

region 1 + zone 1 + 192.168.101.1:6000 + device id 0
               |                     |                                        |
               |                     |                                        + device id 1
               |                     |                                        |
               |                     |                                        + device id 2
               |                     |
               |                     + 192.168.101.2:6000 + device id 3
               |                                                               |
               |                                                               + device id 4
               |                                                               |
               |                                                               + device id 5
               |
               + zone 2 + 192.168.102.1:6000 + device id 6
                                    |                                        |
                                    |                                        + device id 7
                                    |                                        |
                                    |                                        + device id 8
                                    |
                                    + 192.168.102.2:6000 + device id 9
                                                                              |
                                                                              + device id 10

region 2 + zone 1 + 192.168.201.1:6000 + device id 12
                                     |                                        |
                                     |                                        + device id 13
                                     |                                        |
                                     |                                        + device id 14
                                     |
                                     + 192.168.201.2:6000 + device id 15
                                                                               |
                                                                               + device id 16
                                                                               |
                                                                               + device id 17

从上面可以看到, 每一个 dev 都有 一个 全局(整个集群)唯一的ID。在 分配 part 的 replicas 时 会尽可能将 不同的region或zone上,官方文档 原文 (来源 github/CHANGELOG ):
The ring builder now supports as-unique-as-possible partition
      placement, unified balancing methods, and can work on more than one
      device at a time.
该变化 在 1.5.0 即 F版中,在 F版之前,同一个part的各个replica 不能在 一个 zone 中。因为 一个 dev只能在一个zone中,所以 不同的zone肯定意味着不同的device。
刚才 提到了 part 的 replica,part 也就是 vnode 是 虚结点。所有的数据 都存储在虚结点中,每个数据都有一个数据ID(通常是 HASH)。一个数据ID 被 映射到 一个 vnode 上,因为 每个 vnode 有多份,所以 每个数据也就有多份。此处 可以参见 源代码文件 swift/swift/common/ring/builder.py
 在 类 RingBuilder 有一个重要的属性 _replica2part2dev, 申明如下
                  # _replica2part2dev maps from replica number to partition number to

        # device id. So, for a three replica, 2**23 ring, it's an array of
# three 2**23 arrays of device ids (unsigned shorts). This can work a
# bit faster than the 2**23 array of triplet arrays of device ids in
# many circumstances. Making one big 2**23 * 3 array didn't seem to
# have any speed change; though you're welcome to try it again (it was
# a while ago, code-wise, when I last tried it).
self._replica2part2dev = None

  使用时,如下(文件同上):
                 dev_id = self._replica2part2dev[replica][part]
另外,replica 还可以是个大于1的小数。来源如下 类 RingBuilder:
  方法: __init__ 中
                 if replicas < 1:

            raise ValueError("replicas must be at least 1 (was %.6f)"
% (replicas,))

  方法: _adjust_replica2part2dev_size 中
                 “”

        Make sure that the lengths of the arrays in _replica2part2dev
are correct for the current value of self.replicas.

Example:
self.part_power = 8
self.replicas = 2.25

self._replica2part2dev will contain 3 arrays: the first 2 of
length 256 (2**8), and the last of length 64 (0.25 * 2**8).

Returns a 2-tuple: the first element is a list of (partition,
replicas) tuples indicating which replicas need to be
(re)assigned to devices, and the second element is a count of
how many replicas were removed.
"""

也就是 说 在 二维数组 _replica2part2dev 中 首先是 不同的 replica 的 part。然后是 part 与 dev 的映射。因为 replica 可能为小数,所以 每个 part的 replica 数是不一样的。准确的说,part号小的replica多。因为是用 列表保存的映射关系 也就是 数组下标是part号,值是dev的ID。下标从0开始的。
综上: 集群中共有 2^part_power 个 part,标号 为 0 ~ 2^part_power-1 。
            因为 replica的原因 part 与 dev 是一个 多对多的关系。也就是 一个 part 会保存到多个dev上。
            一个对象(数据)对应一个part,一对多的关系。
            part的数目是固定的,也就是 集群一旦建立是不能改变的。
            变化的是 dev,所以要改变part与dev的映射。详细 内容 参考 builder.py 文件。
附:根据 deploy_guide , 虚结点个数 最好为 未来最大物理磁盘设备数的100倍。如下所述:

The first step is to determine the number of partitions that will be in the ring. We recommend that there be a minimum of 100 partitions per drive to insure even distribution across the drives. A good starting point might be to figure out the maximum number of drives the cluster will contain, and then multiply by 100, and then round up to the nearest power of two.

For example, imagine we are building a cluster that will have no more than 5,000 drives. That would mean that we would have a total number of 500,000 partitions, which is pretty close to 2^19, rounded up.

It is also a good idea to keep the number of partitions small (relatively). The more partitions there are, the more work that has to be done by the replicators and other backend jobs and the more memory the rings consume in process. The goal is to find a good balance between small rings and maximum cluster size.

The next step is to determine the number of replicas to store of the data. Currently it is recommended to use 3 (as this is the only value that has been tested). The higher the number, the more storage that is used but the less likely you are to lose data.

It is also important to determine how many zones the cluster should have. It is recommended to start with a minimum of 5 zones. You can start with fewer, but our testing has shown that having at least five zones is optimal when failures occur. We also recommend trying to configure the zones at as high a level as possible to create as much isolation as possible. Some example things to take into consideration can include physical location, power availability, and network connectivity. For example, in a small cluster you might decide to split the zones up by cabinet, with each cabinet having its own power and network connectivity. The zone concept is very abstract, so feel free to use it in whatever way best isolates your data from failure. Zones are referenced by number, beginning with 1.

2 名字空间
    swift上有 account,container,object 三种类型的对象。
    有 记录 account , container,object 的 元数据文件,记录 object 内容的 数据文件。
    元数据文件 为 sqlite格式的db文件,数据文件 为 原始数据流保存的字节文件,.data结尾。
    data 文件 的 保存路径:
        dev_dirpath/dev/type_name/partition/suffix_path/name_hash/filename
       dev_dirpath 是 dev 挂载点的目录,一般 为 /srv/node ,写在配置文件 object-server.conf 中
       dev 挂载目录名,如 sdb1,sdb2 等, 在 用 swift-ring-builder 添加 device时 会用到
        type_name 即 类型名,上面提到的三种 data文件属于 object,故为 object
        partition 即 分区号,也就是 该 文件 被分到的  part。
                    因为 part的数目是2的次方,所以可以直接使用移位来计算。
                    代码 ( 文件 swift/swift/common/ring/ring.py):
                         def get_part(self, account, container=None, obj=None):

        """
Get the partition for an account/container/object.

:param account: account name
:param container: container name
:param obj: object name
:returns: the partition number
"
        Get the partition for an account/container/object.
        :param account: account name        :param container: container name        :param obj: object name        :returns: the partition number        """        key = hash_path(account, container, obj, raw_digest=True)        if time() > self._rtime:            self._reload()        part = struct.unpack_from('>I', key)[0>> self._part_shift        return part

       suffix_path 即 计算出来的 name_hash 的 后 3位。
       name_hash 即 hash_path 计算出来值
       filename 不同的 文件方法 不一样,对于 data文件 使用 时间戳加上后缀即扩展名。
                        对于元数据文件一般是name_hash加上 .db。
      例如:
/srv/node/sdb1/objects/109486/8fd/c7a68547b5aefe34c3e1371bcebbb8fd/1381454264.75078.data
/srv/node/sdb1/accounts/47305/82a/3323e66c399c5f6230030e493e6b082a/3323e66c399c5f6230030e493e6b082a.db
account,container,object的名字:
  account 长度 对于 UTF8 编码和URL Encode,不操过 256 字节,最好不要包含冒号。
  container 长度同上,不能包括 斜杠 即 /
  object 长度 对于 UTF8编码和 URL Encode 不操作 1024 字节,可以包含 斜杠 /
另外:系统在 大文件分片上传时,会自动创建一个 contianer,名字为 原container名加上  _segments 用来保存各分片对象。
因为 swift 是 对象 存储,所以是 不支持 目录的。不过 swift支持前缀查询,所以可以指定 一部分 前缀来 当作目录。如
   object        d/a/c.txt
   object        d/a/1/2.txt
   object        s/5
 上面三个 object 中 前两个 有同样的前缀 d/a/ ,故而 可以当作在同一个目录下。也就是 在 list object时 使用 prefix参数。
  格式: GET /<api version>/<account>/<container>[?parm=value]
所以:理论上限 为 8^256 个 account,8^512 个 container ,8^1536 个 object。
           每个 account 有 8^1270 个 object。
3  分布与查询
     节点分布 : 集群信息保存在 ring 文件中。ring 文件 维护了 part 到 节点的映射。
     文件分布 :  每个 account 都有一个自己独立的元数据文件。account中保存账户信息和 container 列表。
                           container 元数据文件 保存 object 列表。
                          元数据文件 保存了 与用户有关的,如名字等的元数据信息。
                          元数据文件内容 可以参考【牛皮糖】, 或 源文件 swift/swift/account/backend.py 旧版本在 swift/swift/common/db.py
    分布时,首先将 文件 根据 HASH 得到的 ID映射到 集群中的 part,然后根据part找到所有replica,再 以 replica号 和 part 号 从 ring 文件中 找到 dev_id 和 dev 信息。完成 数据文件 的 分布,并写入到 元数据文件中。
    查询时,直接 查找 元数据文件。而元数据文件的位置根据 分布算法 和 ring文件 即可找到。
   注意: swift 的 客户端 是没办法 计算 元数据文件的位置的。也就是 swift 客户端不能直接和 storage host 通信。
References:
swift 官方文档 github, deploy_guide , develop_guide , administrator_guide : http://docs.openstack.org
swiftstack :  http://swiftstack.com/
gholt(swift core developer): http://greg.brim.net/
launchpad/swift:     https://launchpad.net/swift
Google: google
others: www.chenshake.com ,http://www.openstack.cn  。。。
对以上组织或个人,以及其他没有写上的 表示感谢!
=======================================
swift 看了 好久, 前些时间 都是 零零碎碎的,其实现在也是,整理了些笔记,没有写 博客。由于每次查找资料 都有找到网上的博客。所以也整理一份。顺便自己也理一理。只要是 数据文件是如何 保存在 集群上的。一致性 和 容错方面没有提到。可以查看上面参考的资料。