Understanding Indexing

Understanding Indexing

Without Needing to Understand Data Structures

MySQL UC 2011 – April 12, 2011

Zardosht Kasheff

什么是表?

(key,value)对集合的词典

1、确保你可以  修改 这个词典(插入、删除、修改)和  查询 (点查询、范围查询)词典

2、B-Tree和Fractal Tree是两个词典的例子

3、哈希则不是(不支持范围查询)

例子:

CREATE TABLE foo (a INT, b INT, c INT, PRIMARY KEY(a));

然后我们插入一批数据

a b c
100 5 45
101 92 2
156 56 45
165 6 2
198 202 56
206 23 252
256 56 2
412 43 45

一个key定义了词典的排序规则

1、对于数据结构和存储引擎我们会认为,在排序上进行范围查询是  快速 的

2、在其它顺序上的范围查询会进行表的扫描,这个操作是  很慢 的

3、点查询需要检索一个特定的值也是   的

一个点查询是快速的,但是读取一批行用这种方法会比使用按顺序的范围查询慢2个数量级

词典T上的索引I也是一个词典

1、同样我们需要定义一个(key,value)对

2、索引上的key是主词典上的列的子集

3、索引I的值是T的主KEY

还有其它的方法去定义这个值,但我们还是坚持使用T的主KEY来定义这个值

例子:

ALTER TABLE foo ADD KEY(b);

然后我们得到

Primary

a b c
100 5 45
101 92 2
156 56 45
165 6 2
198 202 56
206 23 252
256 56 2
412 43 45
Key(b)

b a
5 100
6 165
23 206
43 412
56 156
56 256
92 101
202 198

问题:COUNT(*) WHERE a<120;

100 5
101 92 2

=>

2

问题:COUNT(*) WHERE b>50;

56 156
56 256
92 101
202 198
=>
4

问题:SUM(c) WHERE b >50;

56 156
56 256
92 101
202 198
=>

156 56 45
256 56 2
101 92 2
198 202 56
=>
105

索引的好处?

1、索引使得查询变得快速

索引会提升部分查询请求的速度

2、需要在心里根据查询设计索引

选出最重要的查询给它们设计索引

考虑索引本身的代价

选出最重要的查询给它们设计索引

设计一个好的索引有3个规则可以参考

避免任何数据结构的细节

B树和分形树对于计算机科学家来说是有趣和好玩的,但这三个规则同样适用于其它数据结构

所有我们需要考虑的是范围查询是快速的(对每行)而点查询则慢的多(对行)

世界上没有绝对的规则

索引像是一个数据问题

规则有帮助,但每个方案都有自己的问题,需要分析解决问题的方法

也就是说规则有很大的帮助

三个规则

1、查询较少的数据

少的带宽,少的处理…

2、减少点查询

数据访问成本是不一样的

顺序访问数据比无序访问  快的多

3、避免排序

GROUP BY和ORDER BY查询需要后期的检索工作

索引在这种查询中可以帮助获得RowID

我们来分别看下这三种规则

规则1:查询较少的数据

规则1:慢查询的例子

表:(100万行数据,没有索引)

CREATE TABLE foo (a INT, b INT, c INT);

查询(匹配1000行数据)

SELECT SUM(c) FROM foo WHERE b=10 and a<150;

查询计划:

行b=10 AND a<150可以在表的任何地方

没有索引的帮助,整个表都会被扫描

执行速度慢:

检索100万行数据仅仅查询1000行数据

规则1:如何去添加一个索引

我们应该怎么去做?

减少检索到的数据

分析远少于100万的数据行

怎么做(对于一个简单的查询)?

设计索引时着眼于WHERE子句

由查询关注那些行来决定

其它行的数据对于这个查询来说并不重要

对于查询 SELECT SUM(c) FROM foo WHERE b=10 and a<150;

规则1:那个索引?

选择1:Key(a)

选择2:Key(b)

那个更好?由select来决定:

如果WHERE a<150的数据行更少,key(a)更好

如果WHERE b=10的数据更少,key(b)更好

选择3:key(a) AND key(b),然后MERGE我们稍后会进行讨论

规则1:选择最好的索引

key(a)与key(b)都不是最佳的

考虑:

WHERE a<150有20万行数据

WHERE b=10有10万行数据

WHERE b=10 AND a<150有1000行数据

然后key(a)和key(b)都会检查很多的数据

为了获得更好的性能,索引必须尝试尽量优化WHERE条件

我们需要复合索引

复合索引可以减少数据检索

WHERE条件:b=5 AND a<150

选择1:key(a, b)

选择2:key(b, a)

问题又来了那种选择更好?

Key(b, a)!

索引规则:

当创建一个复合索引,需要对每个列进行检查,条件b是相等判断,但在a上不是。

问题:WHERE b=5 and a>150;

a b c
100 5 45
101 6 2
156 5 45
165 6 2
198 6 56
206 5 252
256 5 2
412 6 45
b,a a
5,100 100
5,156 156
5,206 206
5,256 256
6,101 101
6,165 165
6,198 198
6,412 412

复合索引:没有平等的条件

如果WHERE条件是这样的:

WHERE a>100 AND a<200 AND b>100;

那个更好?

key(a),key(b),key(a,b),key(b,a)?

索引规则:

只要复合索引不被用于相等查询,复合索引的其它部分不会减少数据检索量

key(a,b)不再比key(a)好

key(b,a)不再比key(b)好

问题:WHERE b>=5 AND a>150;

a b c
100 5 45
101 6 2
156 5 45
165 6 2
198 6 56
206 5 252
256 5 2
412 6 45
b,a a
5,100 100
5,156 156
5,206 206
5,256 256
6,101 101
6,165 165
6,198 198
6,412 412
=>
5,156 156
5,206 206
5,256 256
6,101 101
6,165 165
6,198 198
6,412 412

复合索引:另一个例子

WHERE条件:b=5 AND c=100

key(b,a,c)和key(b)一样好,因为a没有在条件中使用,所以在索引中包含c并不会有帮助,key(b,c,a)会更好

a b c
100 5 100
101 6 200
156 5 200
165 6 100
198 6 100
206 5 200
256 5 100
412 6 100
5,100,100 100
5,156,200 156
5,206,200 206
5,256,100 256
6,101,200 101
6,165,100 165
6,198,100 198
6,412,100 412
=>
5,100,100 100
5,156,200 156
5,206,200 206
5,256,100 256

规则1:总结

根据查询条件设计复合索引

把相等条件的查询列放在复合索引的开始位置

保证第一个非相等条件的列在索引中越有选择性越好

如果复合索引的第一个列没有被相等条件来使用,或者没有在条件中使用,复合索引的其它列对于减少数据的检索没有帮助

是否就表明它们是无用的呢?

它们在规则2中可能有用

规则2:避免点查询

表:

CREATE TABLE foo (a INT,b INT,c INT,PRIMARY KEY(a), KEY(b);

查询:

SELECT SUM(c) FROM foo WHERE b>50;

查询计划:使用key(b)

因为随机的点查询的原因,对每个行都进行检索的代价很大

问题:SUM(c) WHERE b>50;

a b c
100 5 45
101 92 2
156 56 45
165 6 2
198 202 56
206 23 252
256 56 2
412 43 45
b a
5 100
6 165
23 206
43 412
56 156
56 256
92 101
202 198
56 156
56 256
92 101
202 198
=>
156 56 45
256 56 2
101 92 2
198 202 56
=>
105

还是这张表,但查询计划不同了

查询计划:扫描主表

每行的检索成本低

但是需要检索很多行

问题:SUM(c) WEHRE b>50;

a b c
100 5 45
101 92 2
156 56 45
165 6 2
198 202 56
206 23 252
256 56 2
412 43 45
=>
105

如果我们添加了另一个索引会怎么样?

如果添加key(b, c)呢?

由于我们在b上有索引,我们只检索我们需要的行

由于索引包含C的信息,我们不再需要再去检索主表了。  没有点查询了

覆盖索引:索引覆盖一个查询,如果这个索引包含足够的信息来回答这个查询。

例子:

问:SELECT SUM(c) FROM foo WHERE b<100;

问:SELECT SUM(b) FROM foo WHERE b<100;

索引:

key(b, c): 对第一个查询是覆盖索引

key(b, d):对第二个查询是覆盖索引

key(b, c, d):对每个索引都是覆盖索引

如何去构建一个覆盖索引

把检索的每个列都包含进去,并不仅仅是查询条件

问:SELECT c,d FROM foo WHERE a=10 AND b=100;

错误:ADD INDEX(a, b)

并不是覆盖索引。仍然需要点查询去检索c和d的值

正确:ADD INDEX(a, b, c, d)

包含所有相关的列

按照规则1规定把a和b放在索引开始位置

如果主键匹配WHERE条件呢?

问题:SELECT sum(c) FROM foo WHERE b>100 AND b<200;

SCHEMA:CREATE table foo (a INT, b INT, c INT, ai INT AUTO_INCREMENT, PRIMARY key(b, ai));

查询在主词典上做了范围查询

只有一个词典会按顺序访问到

这个查询很快

主键覆盖所有查询

如果排序规则匹配查询条件,问题得到解决

什么是聚簇索引

如果主键不匹配查询条件呢?

理想的情况下,必须确保辅助索引包含所有列

存储引擎不会让你去这样做

有一个例外。。。。TokuDB可以

TokuDB允许你定义任何聚簇索引

一个聚簇索引包含所有的查询,就像主键做的一样

聚簇索引的实践

问:SELECT SUM(c) FROM foo WHERE b<100;

问:SELECT SUM(b) FROM foo WEHRE b>200;

问:SELECT c,e FROM foo WEHRE b=1000;

索引:

key(b, c):第一个查询

key(b, d):第二个查询

key(b, c, e):第一个和第三个查询

key(b, c, d, e):所有的三个查询

索引需要大量的查询分析

考虑那个会涵盖所有的查询:聚簇索引 b

聚簇索引可以让你更加关注于查询条件

它们减少了点查询并且使查询更加快

聚簇索引更多的信息:索引合并

例子:

CREATE TABLE foo (a INT, b INT, c INT);

SELECT SUM(c) FROM foo WHERE b=10 AND a<150;

假设:

a<150有20万行数据

b=10有10万行数据

b=10 AND a<150有1000行数据

如果我们使用key(a)和key(b)然后把结果集合合并会怎样?

合并计划:

查询20万行数据从key(a)中,where a<150

查询10万行数据从key(b)中,where b=10

合并结果集合,然后找到查询标识的1000行数据

执行1000行数据的点查询去得到c

这比没有索引好一点

和没有索引相比,减少了扫描行的数量

和没有合并相比,减少了点查询的数量

那么聚簇索引会对合并有帮助么?

考虑key(a)是个聚簇索引

查询计划:

扫描key(a)20万行数据,where a<150

扫描结果集合得到b=10的结果

使用得到的1000行的数据去检索C的值

一次得到,没有点查询

更好的选择还有没有?

聚簇索引(b, a)!

规则2总结:
避免点查询
确保索引覆盖查询
包含查询涉及到的所有列,并不仅仅是查询条件中的
使用聚簇索引
使用聚簇索引包含所有查询
允许用户把关注点集中在查询条件上
给多个查询提速,包括还没有预见到的查询--简化数据库设计

规则3:避免排序

简单的查询不需要后续的处理

select * from foo where b=100;

仅仅取得数据然后返回给用户

复杂的查询需要对数据进行后续的处理

GROUP BY 和 ORDER BY 会排序数据

选择正确的索引可以避免这些排序的步骤

考虑:

问:SELECT COUNT(c) FROM foo;

问:SELECT COUNT(c) FROM foo GROUP BY b, ORDER BY b;

查询计划1:

当进行表扫描时,给C计数

查询计划2:

扫描表把数据写到临时表中

对临时表按B进行排序

重新扫描排序后的数据,对每个b,使用c进行计数

如果我们使用key(b, c)会怎么样呢?

通过添加国所有需要的字段,我们覆盖了查询。 快速

通过提前对B进行排序,我们避免了排序 快速

总结:

通过给GROUP BY或者ORDER BY使用预先排序了的索引

把它们都放到一起:

简单查询

SELECT COUNT(*) FROM foo WHERE c=5, ORDER BY b;

key(c, b):

把c放在索引第一个位置去减少行检索 R1

然后通过剩余的行排序去避免排序 R3

因为相等的检查在c上,所以剩余的行数据会被按b排好序

SELECT SUM(d) FROM foo WHERE c=100, GROUP BY b;

key(c, b, d):

c在索引的第一个位置可以减少行数据的检索 R1

然后其它的数据在b上排好序去避免查询 R3

确保了查询覆盖所有的查询,避免了点查询 R2

在一些情况下,差没有明确的答案

最优的索引是和数据相关的

问:SELECT COUNT(*) FROM foo WHERE c<100, GROUP BY b;

索引:

key(c, b)

key(b, c)

key(c, b)的查询计划:

使用c<100对数据进行过滤

仍然需要对数据进行排序

*检索的行不会被b进行排序

*查询条件不需要对c进行相等的检查,因此b的值分布在不同的c值块中

key(b, c)的查询计划

按b进行排序,R3

列WHERE c>=100同样需要处理,因此没有R1的优势

那个更好一些呢?

答案依赖于数据是什么样的

如果c>=100有更多的数据,节省时间不去检索无用的行。使用key(c, b)

如果c>=100没有多少行,执行查询的时间是以排序为主,那么使用key(b, c)

问题的关键是,通常情况下,规则会有帮助,但它通常只是帮助我们去思考查询和索引,而不是给我们一个配方。

另一个重要的问题:为什么不把所有的加载都添加上索引呢?

需要跟上插入的负荷更多的索引 = 更小的负荷

索引的代价:

空间:

问题,每个索引都会增加存储的需求

选项,使用压缩

性能:

问题,B-trees在某些索引任务中执行的很快(内存中,顺序的key),但是在其它类型的索引(辅助索引)会慢20倍

选项,分形树索引对于所有的索引类型都很快速,可以让消费者很容易索引,经常索引

范围查询性能:

问题,规则2依赖于范围查询足够快

问题,B-tree会比较容易碎片化(删除、随机插入…),碎片化的B-tee在范围查询上会变慢

选项,对于B-tee可以优化表,导出然后导入(时间和离线)

选项,对于Fractal Tree索引,不是问题,它不会碎片化

PS. 文档翻译自:

Understanding Indexing

Without Needing to Understand Data Structures

MySQL UC 2011 – April 12, 2011

Zardosht Kasheff

For more information…

• Please contact me at zardosht@tokutek.com for any

thoughts or feedback

• Please visit  Tokutek.com for a copy of this presentation

to learn more about the power of indexing, read about

Fractal Tree indexes, or to download a free eval copy of

TokuDB

PS. 正确设计数据库索引并不只是DBA的事情,每个合格开发者都必须具备这样的能力,但最近老是发现乱使用索引和不使用索引的情况,很明显是不理解索引,希望这篇译文能够帮助到这部分开发者吧。另外还有一篇不错的文章,有时间也会翻译出来,敬请期待。

Python操作

操作json

dump和dumps的区别是dump会生成一个类文件对象,dumps会生成字符串load和loads区别是load解析类文件对象,loads解析字符串格式的JSON

把json object的value由字符串改为[],load与loads,dump与dumps的区别

import json
def changejson(jstr):
  sdict = json.loads(jstr)
  # print sdict
  rdict = {}
  for k in sdict:
    item = []
    item.append(sdict[k])
    rdict[k] = item
  rjson = json.dumps(rdict)
  return rjson
print changejson('{}')

操作文件

# example 1:
t_file=open("/xxxx/xxx.txt", "r")
while 1:
  sline = t_file.readline().strip('\n')
  if not sline:
    break
  print "The Line is: %s" % (sline)
t_file.close()
# example 2:
# split file and hash with one filed
def splitfile(sfile, index, pre):
  f=open(sfile)
  files=[]
  for i in range(0, 64):
    files.append(open("/tmp/tmp/%s_%s" % (pre, i), "w"))
  while 1:
    line = f.readline().strip('\n')
    if not line:
      break
    row = line.split('\t')
    tid = row[index]
    htid = hash(tid)
    if (htid <= 0):
      htid = 0 - htid
    files[htid % 64].write("%s\n" %  (line))
  f.close()
  for i in range(0, 64):
    files[i].close()
#f_split("/tmp/tmp/jiaguo20150108_cpalog.dat", 1, "detail")

操作数据库

安装数据连接池模块DBUtils

# tar zxvf DBUtils-0.9.4.tar.gz

# cd DBUtils-0.9.4/

# python setup.py install

编写python脚本 Test.py

#!/usr/bin/python
import MySQLdb
from DBUtils.PooledDB import PooledDB
# 创建连接池
dbpool = PooledDB(creator = MySQLdb, maxusage=100, host="localhost",user="user",passwd="passwd",db="db",port=3306, charset="utf8")
# 使用
def findFromDb(sql):
  try:
    conn = dbpool.connection()
    cur = conn.cursor()
    count = cur.execute(sql)
    if (count > 0):
      # results = cur.fetchall()
      # for result in results:
        # ......
    cor.close()
    conn.close()
  except MySQLdb.Error,e:
    print "Mysql Error %d: %s" % (e.args[0], e.args[1])
    result = {}
    return result
print findFromDb("......")

Sqoop源码分析

Sqoop的Mysql数据导出实现分两种,一种是使用JDBC方式从Mysql中获取数据,一种是使用MysqlDump命令从MySql中获取数据,默认是 JDBC方式获取数据,如果要使用dump方式获取数据,需要添加 -direct 参数。

先说第一种:

配置语句时,需要添加 $CONDITIONS 点位符,比如:SELECT id FROM user WHERE $CONDITIONS,Sqoop在内部实现时会把它替换成需要的查询条件。

Sqoop起动后会先查询元数据,它会把 $CONDITIONS 替换为 (1=0) ,然后用得到的SQL语句查询数据库。这块Sqoop的实现不太好,对于导出一个表的情况,它会使用这个SQL查询三次数据库,分别是:获取 colInfo(最终得到columnTypes信息)、查询ColumnNames信息、生成QueryResult类时 generateFields操作获取columnTypeNames时。

Sqoop会对获取的Fields做校验,列不能重复,它还会处理数据库的字段到Java属性名的转换

QueryResult类是通过构建类文件,然后获取JavaCompiler,然后编译加载,为了提高处理性能,这块不是使用反射 实现,这个生成类内部处理mysql到hdfs属性值为空和分隔符的处理。

接着它会进行下面一个Sql查询操作,查询结果集为MIN(split列),MAX(split列),查询条件的处理逻辑为 $CONDITIONS 替换为(1=1),然后组合 (举例: SELECT MIN(id), MAX(id) FROM (SELECT ID,NAME,PASSPORT WHERE (1=1) ) AS t1 ),这样就查询出来此次导出数据最大的split列值和最小的split列值。

对于为整数、布尔值、时间格式、Float等 的分区列,进行split构建比较容易,这里就不多说,对于Text文本的处理方式需要解释一下,其先会对之前获取到的Min和Max的字串寻找它们最大 的相同字串,然后对于后面的字段转化为BigDecimal,结合char占两个字节(65536),进行处理,算法在 TextSplitter类中,比较简单,就是一个进制转换的问题。拆分好后,需要把Split的值再转换为String,然后加上公共 前缀,就构成了查询区间了。

其对数据的获取是在DataDrivenDBRecordReader中,在查询时会把 $CONDITIONS 替换成 split 的范围比如 ( id >= 1) && (id<10),使用JDBC获取到游标,然 后移动游标处理数据。

第二种方法与第一种方式有下面的差别:

初始化元数据,它是在构建的查询语句后面添加 limit 1 ,比如:SELECT t.* FROM `user` AS t LIMIT 1,因为dump方式查询指定获取列是 t.*,当使用limit 0时,数据库不会给它返回必须的元数据信息。

dump方式在map进行数据的获取,其会构建mysqldump命令,然后使用java去调用,输入输出流和错误流,其实现了 org.apache.sqoop.util.AsyncSink抽象类,用来处理输入输出流和错误流。

优化策略:

Sqoop查询无数据会进行三次相同的Sql查询,可以合并查询,不过由于查询很快,这块不需要修改实现。

分区列选择对于查询元数据和导出的查询都有影响,应该对索引做调优,避免对分区列的排序操作,加快元数据查询速度和导出数据的速度,尽量选择自增加的主键ID做Split列,区分度好并且可以顺序读取数据。

导出操作的查询语句中,$CONDITIONS 会被替换为范围区间,创建索引时,要考虑这个查询的优化。

索引建议,考虑三个规则(使查询数据集较少、减少点的查询、避免排序操作),Sqoop场景下,如果分区列不是主键(自增加)时,把分 区列做为联合索引的第一个字段,其它被选择的查询条件做为索引的其它字段,可优化此查询。

分区列的选择,要避免Split后数据不均衡。

从实现上来看-m参数是可以增加任务的并行度的,但数据库的读线程是一定的,所以-m过大对于数据库会是一个压力,在Sqoop的场 景下,数据库才是一个影响并发的瓶颈。增加job数意义不大。

下面列出Sqoop目前1.4.6版本存在的两个问题。

查看Sqoop源码,发现其存在两个比较严重的问题。

问题 1、数据分片与Mapper设定不合理

Sqoop在抽取时可以指定-m的参数,但这个-m的参数是控制mapper的数量的,但它也决定了最后能够生成的文件的数目,调节这个值可以实现对结果文件大小的控制,但问题是,如果产生的文件的格式不能够被分割,那么对这个数据的下游性能有很大影响,同时Sqoop在启动时会启动-m个MapperTask,会对数据库产生m的并发读取。个人认为Sqoop做为一个数据导出框架,对数据的控制应该再细至一些,-m只是控制MR的并行度,数据分片数目应该可控。

个人建议可以加个 -split-per-map 参数,比如设置-m=4 -split-per-map=2,则对结果集分 8 片,每个Mapper处理两片数据,最后共产生 8 个文件。

问题 2、分片效率低

Sqoop在做分片处理时有问题,其实现会使用Select Max(splitKey),Min(splitKey) From ( –select参数 ) as t1查询分片信息,在Mysql下,这样的查询会产生一个以split-id为主键的临时表,如果数据量不大,临时表数据可以在内存中,处理速度还可以保证。但如果数据量很大,内存中已经存放不下时,这些数据会被保存为MyISAM表存放到磁盘文件中,如果数据量再大一些,磁盘文件已经存放不下临时表时,拆分数据会失败。如果数据量大,即使没有查询也会很慢,大约会占用整个导出时间的45%,优化空间很大,如果不修改实现的话,不适合做大数据量表的全量数据导出操作。

解决方案:

修改:org.apache.sqoop.mapreduce.DataDrivenImportJob的

@Contract(“null, _ -> !null”)private String buildBoundaryQuery(String col, String query)

修改代码如下

/**
   * Build the boundary query for the column of the result set created by
   * the given query.
   * @param col column name whose boundaries we're interested in.
   * @param query sub-query used to create the result set.
   * @return input boundary query as a string
   */
  private String buildBoundaryQuery(String col, String query) {
    if (col == null || options.getNumMappers() == 1) {
      return "";
    }

    // Replace table name with alias 't1' if column name is a fully
    // qualified name.  This is needed because "tableName"."columnName"
    // in the input boundary query causes a SQL syntax error in most dbs
    // including Oracle and MySQL.
    String alias = "t1";
    int dot = col.lastIndexOf('.');
    String qualifiedName = (dot == -1) ? col : alias + col.substring(dot);

    ConnManager mgr = getContext().getConnManager();
    String ret = mgr.getInputBoundsQuery(qualifiedName, query);
    if (ret != null) {
      return ret;
    }

//    return "SELECT MIN(" + qualifiedName + "), MAX(" + qualifiedName + ") "
//        + "FROM (" + query + ") AS " + alias;
    return initBoundaryQuery(qualifiedName, query, alias);
  }

  private String initBoundaryQuery(String qualifiedName, String query, String alias) {
    StringBuilder regex = new StringBuilder();
    regex.append("(\\s[A|a][S|s][\\s][`]?");
    for (char c : qualifiedName.toCharArray()) {
      regex.append('[').append(c).append(']');
    }
    regex.append("[`|\\s|,])");
    final Matcher matcher1 = Pattern.compile(regex.toString()).matcher(query);
    final boolean asCheckOk = !matcher1.find();
    if(asCheckOk) {
      final Matcher matcher2 = Pattern.compile("(\\s[F|f][R|r][O|o][M|m]\\s)").matcher(query);
      int count = 0;
      while (matcher2.find()) {
        count++;
      }
      boolean fromCheckOk = count == 1;
      if(fromCheckOk) {
        final Matcher matcher = Pattern.compile("(\\s[F|f][R|r][O|o][M|m]\\s[\\s\\S]*)").matcher(query);
        while (matcher.find()) {
          return "SELECT MIN(" + qualifiedName + "), MAX(" + qualifiedName + ") "
                  + matcher.group();
        }
      }
    }
    return "SELECT MIN(" + qualifiedName + "), MAX(" + qualifiedName + ") "
            + "FROM (" + query + ") AS " + alias;
  }

twemproxytwo em proxy

twemproxy (nutcracker) Build Status

twemproxy (pronounced “two-em-proxy”), aka nutcracker is a fast and lightweight proxy for memcached and redis protocol. It was primarily built to reduce the connection count on the backend caching servers.
memcached和redis协议快速和轻量级的代理.它的创建主要是为了减少到后台缓存服务器的连接数量.

Features

Fast.
Lightweight.
Maintains persistent server connections.
Keeps connection count on the backend caching servers low.
Enables pipelining of requests and responses.
Supports proxying to multiple servers.
Supports multiple server pools simultaneously.
Implements the complete memcached ascii and redis protocol.
Easy configuration of server pools through a YAML file.
Supports multiple hashing modes including consistent hashing and distribution.
Observability through stats exposed on stats monitoring port.
特点:
快速
轻量级
保持持久的服务连接
保存少量的连接到后端缓存服务器
请求和响应启用流水线
支持代理多个服务器
支持多台服务器池
实现了完整的memcached ascii和redis协议
通过yaml文件容易的配置服务器池
支持多种hash模式,包括一致性hash
通过监控端口暴露可以观测的统计数据

Zero Copy

In nutcracker, all the memory for incoming requests and outgoing responses is allocated in mbuf. Mbuf enables zero-copy because the same buffer on which a request was received from the client is used for forwarding it to the server. Similarly the same mbuf on which a response was received from the server is used for forwarding it to the client.

请求流入和响应流出所使用的内存都在mbuf中申请,mbuf支持零拷贝因为同样的buffer从请求的client处得到的会被使用来连接server.同样的,后端服务器响应的buf也会被使用来传给客户端.

Furthermore, memory for mbufs is managed using a reuse pool. This means that once mbuf is allocated, it is not deallocated, but just put back into the reuse pool. By default each mbuf chunk is set to 16K bytes in size. There is a trade-off between the mbuf size and number of concurrent connections nutcracker can support. A large mbuf size reduces the number of read syscalls made by nutcracker when reading requests or responses. However, with large mbuf size, every active connection would use up 16K bytes of buffer which might be an issue when nutcracker is handling large number of concurrent connections from clients. When nutcracker is meant to handle a large number of concurrent client connections, you should set chunk size to a small value like 512 bytes using the -m or –mbuf-size=N argument.

此外,mbufs中的内存使用了复用池.一个mbuf被申请,它就不会被销毁,只是会把它扔回复用池.默认的每个mbuf块被设置为16K.在mbuf大小和所能支持的连接数上需要做权衡.一个大的mbuf值可减少nutcracker从请求和响应处读数据的系统调用数,但是,一个大的mbuf值,每个活动的连接都会使用16k的buffer,这就会引起nutcracker处理大量的客户端连接成为一个问题.当它需要处理大量的client连接时,应该使用 -m 或 –mbuf-size=N 的命令把块大小设置的小一些,比如512字节.

Configuration

nutcracker can be configured through a YAML file specified by the -c or –conf-file command-line argument on process start. The configuration file is used to specify the server pools and the servers within each pool that nutcracker manages. The configuration files parses and understands the following keys:

在启动时使用-c或–conf-file命令行参数把yaml文件传入.这个配置文件用来指定服务池和每个池中nutcracker管理的服务器,它解析和理解下面的键:

listen: The listening address and port (name:port or ip:port) for this server pool.
监听:服务池的监听地址和端口
hash: The name of the hash function. Possible values are:
哈希:哈希函数的名字,可能的值为:
one_at_a_time
md5
crc32
fnv1_64
fnv1a_64
fnv1_32
fnv1a_32
hsieh
murmur
jenkins
hash_tag: A two character string that specifies the part of the key used for hashing. Eg “{}” or “$$”. Hash tag enable mapping different keys to the same server as long as the part of the key within the tag is the same.
哈希标签:两个字符的字串分隔,用来指定哈希使用的key值.比如”{}””$$”.不同的键可以映射到同一台服务器,只要它们在标签中的key是一样的。
distribution: The key distribution mode. Possible values are:
分布:key分布模式.
ketama
modula
random
timeout: The timeout value in msec that we wait for to establish a connection to the server or receive a response from a server. By default, we wait indefinitely.
超时时间:超时值以毫秒为单位,我们等待建立一个连接到服务器或从服务器接收响应的值。默认情况,我们无限期地等待。
backlog: The TCP backlog argument. Defaults to 512.
preconnect: A boolean value that controls if nutcracker should preconnect to all the servers in this pool on process start. Defaults to false.
是否启动池中所有的服务器的连接在启动时,默认不启动
redis: A boolean value that controls if a server pool speaks redis or memcached protocol. Defaults to false.
指定使用redis协议还是使用memcached协议,默认false,使用memcached协议.
server_connections: The maximum number of connections that can be opened to each server. By default, we open at most 1 server connection.
对于每个服务器使用的最大连接数,默认情况下我们至少启动1个服务连接
auto_eject_hosts: A boolean value that controls if server should be ejected temporarily when it fails consecutively server_failure_limit times. Defaults to false.
是否暂时弹出服务器
server_retry_timeout: The timeout value in msec to wait for before retrying on a temporarily ejected server, when auto_eject_host is set to true. Defaults to 30000 msec.
server_failure_limit: The number of conseutive failures on a server that would leads to it being temporarily ejected when auto_eject_host is set to true. Defaults to 2.
servers: A list of server address, port and weight (name:port:weight or ip:port:weight) for this server pool.

Finally, to make writing syntactically correct configuration file easier, nutcracker provides a command-line argument -t or –test-conf that can be used to test the YAML configuration file for any syntax error.

保证写正确的配置文件简单,nutcracker提供一个命令行参数-t或者–test-conf可以用来测试yaml配置文件是否存在语法错误.

Observability

Observability in nutcracker is through logs and stats.

Nutcracker exposes stats at the granularity of server pool and servers per pool through the stats monitoring port. The stats are essentially JSON formatted key-value pairs, with the keys corresponding to counter names. By default stats are exposed on port 22222 and aggregated every 30 seconds. Both these values can be configured on program start using the -c or –conf-file and -i or –stats-interval command-line arguments respectively. You can print the description of all stats exported by nutcracker using the -D or –describe-stats command-line argument.

Logging in nutcracker is only available when nutcracker is built with logging enabled. By default logs are written to stderr. Nutcracker can also be configured to write logs to a specific file through the -o or –output command-line argument. On a running nutcracker, we can turn log levels up and down by sending it SIGTTIN and SIGTTOU signals respectively and reopen log files by sending it SIGHUP signal.

Pipelining

Nutcracker enables proxying multiple client connections onto one or few server connections. This architectural setup makes it ideal for pipelining requests and responses and hence saving on the round trip time.

For example, if nutcracker is proxing three client connections onto a single server and we get requests – ‘get key\r\n’, ‘set key 0 0 3\r\nval\r\n’ and ‘delete key\r\n’ on these three connections respectively, nutcracker would try to batch these requests and send them as a single message onto the server connection as ‘get key\r\nset key 0 0 3\r\nval\r\ndelete key\r\n’.

Pipelining is the reason why nutcracker ends up doing better in terms of throughput even though it introduces an extra hop between the client and server.
Deployment

If you are deploying nutcracker in production, you might consider reading through the recommendation document to understand the parameters you could tune in nutcracker to run it efficiently in the production environment.
Users

Pinterest
Tumblr
Twitter

Issues and Support

Have a bug or a question? Please create an issue here on GitHub!

https://github.com/twitter/twemproxy/issues

twemproxy recommendation document

If you are deploying nutcracker in your production environment, here are a few recommendations that might be worth considering.

建议,如果你想部署它到生产环境

Log Level

By default debug logging is disabled in nutcracker. However, it is worthwhile running nutcracker with debug logging enabled and verbosity level set to LOG_INFO (-v 6 or –verbosity=6). This in reality does not add much overhead as you only pay the cost of checking an if condition for every log line encountered during the run time.

默认情况下debug日志是没有打开的.然而打开debug日志并把日志级别设置到LOG_INFO(-v 6或者–verbosity=6)是值得的,它不会增加过多的开销.你只需要支付检查是否每个日志行的条件都在运行时发生了的时间成本.

At LOG_INFO level, nutcracker logs the life cycle of every client and server connection and important events like the server being ejected from the hash ring and so on. Eg.

在LOG_INFO的日志级别上,它记录了每个客户端的生命周期,每个服务器连接和重要的事件比如服务器被从hash环中退出等等.

[Thu Aug  2 00:03:09 2012] nc_proxy.c:336 accepted c 7 on p 6 from '127.0.0.1:54009'
[Thu Aug  2 00:03:09 2012] nc_server.c:528 connected on s 8 to server '127.0.0.1:11211:1'
[Thu Aug  2 00:03:09 2012] nc_core.c:270 req 1 on s 8 timedout
[Thu Aug  2 00:03:09 2012] nc_core.c:207 close s 8 '127.0.0.1:11211' on event 0004 eof 0 done 0 rb 0 sb 20: Connection timed out
[Thu Aug  2 00:03:09 2012] nc_server.c:406 close s 8 schedule error for req 1 len 20 type 5 from c 7: Connection timed out
[Thu Aug  2 00:03:09 2012] nc_server.c:281 update pool 0 'alpha' to delete server '127.0.0.1:11211:1' for next 2 secs
[Thu Aug  2 00:03:10 2012] nc_connection.c:314 recv on sd 7 eof rb 20 sb 35
[Thu Aug  2 00:03:10 2012] nc_request.c:334 c 7 is done
[Thu Aug  2 00:03:10 2012] nc_core.c:207 close c 7 '127.0.0.1:54009' on event 0001 eof 1 done 1 rb 20 sb 35
[Thu Aug  2 00:03:11 2012] nc_proxy.c:336 accepted c 7 on p 6 from '127.0.0.1:54011'
[Thu Aug  2 00:03:11 2012] nc_server.c:528 connected on s 8 to server '127.0.0.1:11212:1'
[Thu Aug  2 00:03:12 2012] nc_connection.c:314 recv on sd 7 eof rb 20 sb 8
[Thu Aug  2 00:03:12 2012] nc_request.c:334 c 7 is done
[Thu Aug  2 00:03:12 2012] nc_core.c:207 close c 7 '127.0.0.1:54011' on event 0001 eof 1 done 1 rb 20 sb 8

To enable debug logging, you have to compile nutcracker with logging enabled using –enable-debug=log configure option.

要打开日志,需要在编译的时候使用–enable-debug=log的配置选项

Liveness

活跃度

Failures are a fact of life, especially when things are distributed. To be resilient against failures, it is recommended that you configure the following keys for every server pool. Eg:

在分布式的环境中,失败是常见的事情.要灵活的处理失败,建议在每个server pool中都添加如下的配置

resilient_pool:
  auto_eject_hosts: true
  server_retry_timeout: 30000
  server_failure_limit: 3

Enabling auto_eject_hosts: ensures that a dead server can be ejected out of the hash ring after server_failure_limit: consecutive failures have been encountered on that said server. A non-zero server_retry_timeout: ensures that we don’t incorrectly mark a server as dead forever especially when the failures were really transient. The combination of server_retry_timeout: and server_failure_limit: controls the tradeoff between resiliency to permanent and transient failures.

auto_eject_hosts,保证死掉的服务器可以在server_failure_limit后被从hash环中退出来.
server_failure_limit:在上面的服务器上记录的连续失败次数
server_retry_timeout:确保我们不会错误的标记一个服务器为死掉,尤其当失败是相当短的时候.
这三个的组合:在永久故障和临时故障之间做权衡.

To ensure that requests always succeed in the face of server ejections (auto_eject_hosts: is enabled), some form of retry must be implemented at the client layer since nutcracker itself does not retry a request. This client-side retry count must be greater than server_failure_limit: value, which ensures that the original request has a chance to make it to a live server.

为了确保请求每次都成功,在开启了auto_eject_hosts的情况下,因为它并不提供重发请求,所以实现某种形式的重发必须在客户端.客户端级别的重发次数必须大于server_failure_limit的值,以确保这个原始请求有机会发到一个活着的服务器.

Timeout

It is always a good idea to configure nutcracker timeout: for every server pool, rather than purely relying on client-side timeouts. Eg:

配置nutcracker过期时间总是一个好的主意:对于每个服务器池配置,而不是单纯依靠客户端超时.

resilient_pool_with_timeout:
  auto_eject_hosts: true
  server_retry_timeout: 30000
  server_failure_limit: 3
  timeout: 400
# 带超时的弹性池

Relying only on client-side timeouts has the adverse effect of the original request having timedout on the client to proxy connection, but still pending and outstanding on the proxy to server connection. This further gets exacerbated when client retries the original request.

仅仅在客户端请求超时的回复对客户端到代理端的原始请求超时有相反的效应,但代理端到客户端的链接仍然会处于等待或未解决状态。当客户端有重试原始请求时它会被进一步加剧.

By default, nutcracker waits indefinitely for any request sent to the server. However, when timeout: key is configured, a requests for which no response is received from the server in timeout: msec is timedout and an error response SERVER_ERROR Connection timed out\r\n is sent back to the client.

默认情况下,任何请求发送给服务器后,它会无限期的等待.当timeout被设置后,如果一个请求在timeout的时间过后还没有从服务器上得到响应,这时一个错误信息的响应SERVER_ERROR会被发送给客户端.

Error Response

Whenever a request encounters failure on a server we usually send to the client a response with the general form – SERVER_ERROR \r\n (memcached) or -ERR (redis).

每当一个请求在服务器端遇到了失败,我们通常会给客户端发送一个错误的响应.
SERVER_ERROR \r\n 或者 ERR

For example, when a memcache server is down, this error response is usually:

SERVER_ERROR Connection refused\r\n or,
SERVER_ERROR Connection reset by peer\r\n

When the request timedout, the response is usually:

SERVER_ERROR Connection timed out\r\n

Seeing a SERVER_ERROR or -ERR response should be considered as a transient failure by a client which makes the original request an ideal candidate for a retry.

SERVER_ERROR或者-ERR响应应该被认为是一个客户端传输失败,这时客户端是一个理想的重发的候选人.

read, writev and mbuf

All memory for incoming requests and outgoing responses is allocated in mbuf. Mbuf enables zero copy for requests and responses flowing through the proxy. By default an mbuf is 16K bytes in size and this value can be tuned between 512 and 65K bytes using -m or –mbuf-size=N argument. Every connection has at least one mbuf allocated to it. This means that the number of concurrent connections nutcracker can support is dependent on the mbuf size. A small mbuf allows us to handle more connections, while a large mbuf allows us to read and write more data to and from kernel socket buffers.

….每一个连接至少拥有一个mbuf申请给它.这就表示它能支撑多少连接依赖于mbuf的大小.小的mbuf值允许我们处理更多的连接,大的mbuf允许我们读写更多的数据从或者去内核套间字缓冲区中.

If nutcracker is meant to handle a large number of concurrent client connections, you should set the mbuf size to 512 or 1K bytes.

如果它要处理大量的客户端连接,你应该把mbuf值设置为512或者1k bytes.

Maximum Key Length

The memcache ascii protocol specification limits the maximum length of the key to 250 characters. The key should not include whitespace, or ‘\r’ or ‘\n’ character. For redis, we have no such limitation. However, nutcracker requires the key to be stored in a contiguous memory region. Since all requests and responses in nutcracker are stored in mbuf, the maximum length of the redis key is limited by the size of the maximum available space for data in mbuf (mbuf_data_size()). This means that if you want your redis instances to handle large keys, you might want to choose large mbuf size set using -m or –mbuf-size=N command-line argument.

memcache ascii协议:250字节,不能有空白,\r \n字,而在redis中我们没有这些限制.然而,nutcraker需要把这个key存储在一个连续的内存空间.因为所有的请求和响应被存储在mbuf中,所以最大的redis的key的长度被mbuf中最大的可用数据空间的大小所限制(mbuf_data_size()).,这个mbuf大小可以用 -m 或者 –mbuf-size=N 的命令行参数来设置.

Node Names for Consistent Hashing

The server cluster in twemproxy can either be specified as list strings in format ‘host:port:weight’ or ‘host:port:weight name’.

servers:
– 127.0.0.1:6379:1
– 127.0.0.1:6380:1
– 127.0.0.1:6381:1
– 127.0.0.1:6382:1

Or,

servers:
– 127.0.0.1:6379:1 server1
– 127.0.0.1:6380:1 server2
– 127.0.0.1:6381:1 server3
– 127.0.0.1:6382:1 server4

In the former configuration, keys are mapped directly to ‘host:port:weight’ triplet and in the latter they are mapped to node names which are then mapped to nodes i.e. host:port pair. The latter configuration gives us the freedom to relocate nodes to a different server without disturbing the hash ring and hence makes this configuration ideal when auto_eject_hosts is set to false. See issue 25 for details.

…后者的配置给了我们自由迁移到不同的服务器节点,而不会干扰的哈希环,从而使得这种配置的理想时的auto_eject_hosts设置为false。有关详细信息,请参见问题25。

Note that when using node names for consistent hashing, twemproxy ignores the weight value in the ‘host:port:weight name’ format string.

当配置节点名字给hash环时,它会忽略配置中的weight值

Hash Tags

Hash Tags enables you to use part of the key for calculating the hash. When the hash tag is present, we use part of the key within the tag as the key to be used for consistent hashing. Otherwise, we use the full key as is. Hash tags enable you to map different keys to the same server as long as the part of the key within the tag is the same.

hash标签允许你使用部分key去计算hash值.当hash tag存在的时候,我们使用tag标签中的key去计算hash值.其它情况,我们使用全部的key去计算.它允许你去把不同的keys哈希到相同的服务器,只要它们在tag内的值是相同的.

For example, the configuration of server pool beta, aslo shown below, specifies a two character hash_tag string – “{}”. This means that keys “user:{user1}:ids” and “user:{user1}:tweets” map to the same server because we compute the hash on “user1″. For a key like “user:user1:ids”, we use the entire string “user:user1:ids” to compute the hash and it may map to a different server.

beta:
listen: 127.0.0.1:22122
hash: fnv1a_64
hash_tag: “{}”
distribution: ketama
auto_eject_hosts: false
timeout: 400
redis: true
servers:
– 127.0.0.1:6380:1 server1
– 127.0.0.1:6381:1 server2
– 127.0.0.1:6382:1 server3
– 127.0.0.1:6383:1 server4

数字证书原理

转载:        数字证书原理 – 无恙

文中首先解释了加密解密的一些基础知识和概念,然后通过一个加密通信过程的例子说明了加密算法的作用,以及数字证书的出现所起的作用。接着对数字证书做一个详细的解释,并讨论一下windows中数字证书的管理,最后演示使用makecert生成数字证书。如果发现文中有错误的地方,或者有什么地方说得不够清楚,欢迎指出!

1、基础知识

这部分内容主要解释一些概念和术语,最好是先理解这部分内容。

1.1、公钥密码体制(public-key cryptography)

公钥密码体制分为三个部分,公钥、私钥、加密解密算法,它的加密解密过程如下:

加密:通过加密算法和公钥对内容(或者说明文)进行加密,得到密文。加密过程需要用到公钥。 解密:通过解密算法和私钥对密文进行解密,得到明文。解密过程需要用到解密算法和私钥。注意,由公钥加密的内容,只能由私钥进行解密,也就是说,由公钥加密的内容,如果不知道私钥,是无法解密的。

公钥密码体制的公钥和算法都是公开的(这是为什么叫公钥密码体制的原因),私钥是保密的。大家都以使用公钥进行加密,但是只有私钥的持有者才能解密。在实际的使用中,有需要的人会生成一对公钥和私钥,把公钥发布出去给别人使用,自己保留私钥。

1.2、对称加密算法(symmetric key algorithms)

在对称加密算法中,加密使用的密钥和解密使用的密钥是相同的。也就是说,加密和解密都是使用的同一个密钥。因此对称加密算法要保证安全性的话,密钥要做好保密,只能让使用的人知道,不能对外公开。这个和上面的公钥密码体制有所不同,公钥密码体制中加密是用公钥,解密使用私钥,而对称加密算法中,加密和解密都是使用同一个密钥,不区分公钥和私钥。

// 密钥,一般就是一个字符串或数字,在加密或者解密时传递给加密/解密算法。前面在公钥密码体制中说到的公钥、私钥就是密钥,公钥是加密使用的密钥,私钥是解密使用的密钥。

1.3、非对称加密算法(asymmetric key algorithms)

在非对称加密算法中,加密使用的密钥和解密使用的密钥是不相同的。前面所说的公钥密码体制就是一种非对称加密算法,他的公钥和是私钥是不能相同的,也就是说加密使用的密钥和解密使用的密钥不同,因此它是一个非对称加密算法。

1.4、RSA简介

RSA是一种公钥密码体制,现在使用得很广泛。如果对RSA本身有兴趣的,后面看我有没有时间写个RSA的具体介绍。

RSA密码体制是一种公钥密码体制,公钥公开,私钥保密,它的加密解密算法是公开的。 由公钥加密的内容可以并且只能由私钥进行解密,并且由私钥加密的内容可以并且只能由公钥进行解密。也就是说,  RSA的这一对公钥、私钥都可以用来加密和解密,并且一方加密的内容可以由并且只能由对方进行解密 。

1.5、签名和加密

我们说加密,是指对某个内容加密,加密后的内容还可以通过解密进行还原。 比如我们把一封邮件进行加密,加密后的内容在网络上进行传输,接收者在收到后,通过解密可以还原邮件的真实内容。

这里主要解释一下签名,签名就是在  信息 的后面再加上一段内容,可以证明  信息没有被修改过,怎么样可以达到这个效果呢?一般是对  信息 做一个hash计算得到一个hash值,注意,这个过程是不可逆的,也就是说无法通过hash值得出原来的  信息 内容。在把  信息 发送出去时,把这个hash值加密后做为一个签名和  信息 一起发出去。 接收方在收到  信息 后,会重新计算  信息 的hash值,并和  信息 所附带的hash值(解密后)进行对比,如果一致,就说明信息的内容没有被修改过,因为这里hash计算可以保证不同的内容一定会得到不同的hash值,所以只要内容一被修改,根据  信息 内容计算的hash值就会变化。当然,不怀好意的人也可以修改  信息 内容的同时也修改hash值,从而让它们可以相匹配,为了防止这种情况,hash值一般都会加密后(也就是签名)再和 信息 一起发送,以保证这个hash值不被修改。至于如何让别人可以解密这个签名,这个过程涉及到数字证书等概念,我们后面在说到数字证书时再详细说明,这里您先只需先理解签名的这个概念。

2、一个加密通信过程的演化

我们来看一个例子,现在假设“服务器”和“客户”要在网络上通信,并且他们打算使用RSA(参看前面的RSA简介)来对通信进行加密以保证谈话内容的安全。由于是使用RSA这种公钥密码体制,“服务器”需要对外发布公钥(算法不需要公布,RSA的算法大家都知道),自己留着私钥。“客户”通过某些途径拿到了“服务器”发布的公钥,客户并不知道私钥。“客户”具体是通过什么途径获取公钥的,我们后面再来说明,下面看一下双方如何进行保密的通信:

2.1 第一回合:

“客户”->“服务器”:你好

“服务器”->“客户”:你好,我是服务器

“客户”->“服务器”:????

因为消息是在网络上传输的,有人可以冒充自己是“服务器”来向客户发送信息。例如上面的消息可以被黑客截获如下:

“客户”->“服务器”:你好

“服务器”->“客户”:你好,我是服务器

“客户”->“黑客”:你好         // 黑客在“客户”和“服务器”之间的某个路由器上截获“客户”发给服务器的信息,然后自己冒充“服务器”

“黑客”->“客户”:你好,我是服务器

因此“客户”在接到消息后,并不能肯定这个消息就是由“服务器”发出的,某些“黑客”也可以冒充“服务器”发出这个消息。如何确定信息是由“服务器”发过来的呢?有一个解决方法,因为只有服务器有私钥,所以如果只要能够确认对方有私钥,那么对方就是“服务器”。因此通信过程可以改进为如下:

2.2 第二回合:

“客户”->“服务器”:你好

“服务器”->“客户”:你好,我是服务器

“客户”->“服务器”:向我证明你就是服务器

“服务器”->“客户”:你好,我是服务器 {你好,我是服务器}[私钥|RSA]

//   意这里约定一下,{} 表示RSA加密后的内容,[ | ]表示用什么密钥和算法进行加密,后面的示例中都用这种表示方式,例如上面的  {你好,我是服务器}[私钥|RSA]    就表示用私钥对 “你好,我是服务器”  进行加密后的结果。

为了向“客户”证明自己是“服务器”, “服务器”把一个字符串用自己的私钥加密,把明文和加密后的密文一起发给“客户”。对于这里的例子来说,就是把字符串 “你好,我是服务器”和这个字符串用私钥加密后的内容 {你好,我是服务器}[私钥|RSA] 发给客户。

“客户”收到信息后,她用自己持有的公钥解密密文,和明文进行对比,如果一致,说明信息的确是由服务器发过来的。也就是说“客户”把 {你好,我是服务器}[私钥|RSA] 这个内容用公钥进行解密,然后和“你好,我是服务器”对比。因为由“服务器”用私钥加密后的内容,  由并且只能由 公钥进行解密,私钥只有“服务器”持有,所以如果解密出来的内容是能够对得上的,那说明信息一定是从“服务器”发过来的。

假设“黑客”想冒充“服务器”:

“黑客”->“客户”:你好,我是服务器

“客户”->“黑客”:向我证明你就是服务器

“黑客”->“客户”:你好,我是服务器 {你好,我是服务器}[???|RSA]     //这里黑客无法冒充,因为他不知道  私钥 ,无法用  私钥 加密某个字符串后发送给客户去验证。

“客户”->“黑客”:????

由于“黑客”没有“服务器”的私钥,因此它发送过去的内容,“客户”是无法通过服务器的公钥解密的,因此可以认定对方是个冒牌货!

到这里为止,“客户”就可以确认“服务器”的身份了,可以放心和“服务器”进行通信,但是这里有一个问题,通信的内容在网络上还是无法保密。为什么无法保密呢?通信过程不是可以用公钥、私钥加密吗?其实用RSA的私钥和公钥是不行的,我们来具体分析下过程,看下面的演示:

2.3 第三回合:

“客户”->“服务器”:你好

“服务器”->“客户”:你好,我是服务器

“客户”->“服务器”:向我证明你就是服务器

“服务器”->“客户”:你好,我是服务器 {你好,我是服务器}[私钥|RSA]

“客户”->“服务器”:{我的帐号是aaa,密码是123,把我的余额的信息发给我看看}[公钥|RSA]

“服务器”->“客户”:{你的余额是100元}[私钥|RSA]

注意上面的的信息 {你的余额是100元}[私钥],这个是“服务器”用私钥加密后的内容,但是我们之前说了,公钥是发布出去的,因此所有的人都知道公钥,所以除了“客户”,其它的人也可以用公钥对{你的余额是100元}[私钥]进行解密。所以如果“服务器”用私钥加密发给“客户”,这个信息是无法保密的,因为只要有公钥就可以解密这内容。然而“服务器”也不能用公钥对发送的内容进行加密,因为“客户”没有私钥,发送个“客户”也解密不了。

这样问题就又来了,那又如何解决呢?在实际的应用过程,一般是通过引入对称加密来解决这个问题,看下面的演示:

2.4 第四回合:

“客户”->“服务器”:你好

“服务器”->“客户”:你好,我是服务器

“客户”->“服务器”:向我证明你就是服务器

“服务器”->“客户”:你好,我是服务器 {你好,我是服务器}[私钥|RSA]

“客户”->“服务器”:{我们后面的通信过程,用对称加密来进行,这里是对称加密算法和密钥}[公钥|RSA]    //蓝色字体的部分是对称加密的算法和密钥的具体内容,客户把它们发送给服务器。

“服务器”->“客户”:{OK,收到!}[密钥|对称加密算法]

“客户”->“服务器”:{我的帐号是aaa,密码是123,把我的余额的信息发给我看看}[密钥|对称加密算法]

“服务器”->“客户”:{你的余额是100元}[密钥|对称加密算法]

在上面的通信过程中,“客户”在确认了“服务器”的身份后,“客户”自己选择一个对称加密算法和一个密钥,把这个对称加密算法和密钥一起用公钥加密后发送给“服务器”。注意,由于对称加密算法和密钥是用公钥加密的,就算这个加密后的内容被“黑客”截获了,由于没有私钥,“黑客”也无从知道对称加密算法和密钥的内容。

由于是用公钥加密的,只有私钥能够解密,这样就可以保证只有服务器可以知道对称加密算法和密钥,而其它人不可能知道(这个对称加密算法和密钥是“客户”自己选择的,所以“客户”自己当然知道如何解密加密)。这样“服务器”和“客户”就可以用对称加密算法和密钥来加密通信的内容了。

总结一下,RSA加密算法在这个通信过程中所起到的作用主要有两个:

因为私钥只有“服务器”拥有,因此“客户”可以通过判断对方是否有私钥来判断对方是否是“服务器”。客户端通过RSA的掩护,安全的和服务器商量好一个对称加密算法和密钥来保证后面通信过程内容的安全。

如果这里您理解了为什么不用RSA去加密通信过程,而是要再确定一个对称加密算法来保证通信过程的安全,那么就说明前面的内容您已经理解了。(如果不清楚,再看下2.3和2.4,如果还是不清楚,那应该是我们说清楚,您可以留言提问。)

到这里,“客户”就可以确认“服务器”的身份,并且双方的通信内容可以进行加密,其他人就算截获了通信内容,也无法解密。的确,好像通信的过程是比较安全了。

但是这里还留有一个问题,在最开始我们就说过,“服务器”要对外发布公钥,那“服务器”如何把公钥发送给“客户”呢?我们第一反应可能会想到以下的两个方法:

a)把公钥放到互联网的某个地方的一个下载地址,事先给“客户”去下载。

b)每次和“客户”开始通信时,“服务器”把公钥发给“客户”。

但是这个两个方法都有一定的问题,

对于a)方法,“客户”无法确定这个下载地址是不是“服务器”发布的,你凭什么就相信这个地址下载的东西就是“服务器”发布的而不是别人伪造的呢,万一下载到一个假的怎么办?另外要所有的“客户”都在通信前事先去下载公钥也很不现实。

对于b)方法,也有问题,因为任何人都可以自己生成一对公钥和私钥,他只要向“客户”发送他自己的私钥就可以冒充“服务器”了。示意如下:

“客户”->“黑客”:你好           //黑客截获“客户”发给“服务器”的消息

“黑客”->“客户”:你好,我是服务器,这个是我的公钥    //黑客自己生成一对公钥和私钥,把公钥发给“客户”,自己保留私钥

“客户”->“黑客”:向我证明你就是服务器

“黑客”->“客户”:你好,我是服务器 {你好,我是服务器}[黑客自己的私钥|RSA]      //客户收到“黑客”用私钥加密的信息后,是可以用“黑客”发给自己的公钥解密的,从而会误认为“黑客”是“服务器”

因此“黑客”只需要自己生成一对公钥和私钥,然后把公钥发送给“客户”,自己保留私钥,这样由于“客户”可以用黑客的公钥解密黑客的私钥加密的内容,“客户”就会相信“黑客”是“服务器”,从而导致了安全问题。  这里问题的根源就在于,大家都可以生成公钥、私钥对,无法确认公钥对到底是谁的。 如果能够确定公钥到底是谁的,就不会有这个问题了。例如,如果收到“黑客”冒充“服务器”发过来的公钥,经过某种检查,如果能够发现这个公钥不是“服务器”的就好了。

为了解决这个问题,数字证书出现了,它可以解决我们上面的问题。先大概看下什么是数字证书,一个证书包含下面的具体内容:

证书的发布机构 证书的有效期 公钥 证书所有者(Subject) 签名所使用的算法 指纹以及指纹算法

证书的内容的详细解释会在后面详细解释,这里先只需要搞清楚一点,  数字证书可以保证  数字 证书里的公钥确实是这个证书的所有者(Subject)的,或者证书可以用来确认对方的身份  。也就是说,我们拿到一个数字证书,我们可以判断出这个数字证书到底是谁的。至于是如何判断的,后面会在详细讨论数字证书时详细解释。现在把前面的通信过程使用数字证书修改为如下:

2.5 第五回合:

“客户”->“服务器”:你好

“服务器”->“客户”:你好,我是服务器,这里是我的数字证书        //这里用证书代替了公钥

“客户”->“服务器”:向我证明你就是服务器

“服务器”->“客户”:你好,我是服务器 {你好,我是服务器}[私钥|RSA]

注意,上面第二次通信,“服务器”把自己的证书发给了“客户”,而不是发送公钥。“客户”可以根据证书校验这个证书到底是不是“服务器”的,也就是能校验这个证书的所有者是不是“服务器”,从而确认这个证书中的公钥的确是“服务器”的。后面的过程和以前是一样,“客户”让“服务器”证明自己的身份,“服务器”用私钥加密一段内容连同明文一起发给“客户”,“客户”把加密内容用数字证书中的公钥解密后和明文对比,如果一致,那么对方就确实是“服务器”,然后双方协商一个对称加密来保证通信过程的安全。到这里,整个过程就完整了,我们回顾一下:

2.6 完整过程:

step1 : “客户”向服务端发送一个通信请求

“客户”->“服务器”:你好

step2 : “服务器”向客户发送自己的数字证书。证书中有一个公钥用来加密信息,私钥由“服务器”持有

“服务器”->“客户”:你好,我是服务器,这里是我的数字证书

step3 : “客户”收到“服务器”的证书后,它会去验证这个数字证书到底是不是“服务器”的,数字证书有没有什么问题,数字证书如果检查没有问题,就说明数字证书中的公钥确实是“服务器”的。检查数字证书后,“客户”会发送一个随机的字符串给“服务器”用私钥去加密,服务器把加密的结果返回给“客户”,“客户”用公钥解密这个返回结果,如果解密结果与之前生成的随机字符串一致,那说明对方确实是私钥的持有者,或者说对方确实是“服务器”。

“客户”->“服务器”:向我证明你就是服务器,这是一个随机字符串     //前面的例子中为了方便解释,用的是“你好”等内容,实际情况下一般是随机生成的一个字符串。

“服务器”->“客户”:{一个随机字符串}[私钥|RSA]

step4 : 验证“服务器”的身份后,“客户”生成一个对称加密算法和密钥,用于后面的通信的加密和解密。这个对称加密算法和密钥,“客户”会用公钥加密后发送给“服务器”,别人截获了也没用,因为只有“服务器”手中有可以解密的私钥。这样,后面“服务器”和“客户”就都可以用对称加密算法来加密和解密通信内容了。

“服务器”->“客户”:{OK,已经收到你发来的对称加密算法和密钥!有什么可以帮到你的?}[密钥|对称加密算法]

“客户”->“服务器”:{我的帐号是aaa,密码是123,把我的余额的信息发给我看看}[密钥|对称加密算法]

“服务器”->“客户”:{你好,你的余额是100元}[密钥|对称加密算法]

…… //继续其它的通信

2.7 其它问题:

上面的过程已经十分接近HTTPS的真实通信过程了,完全可以按照这个过程去理解HTTPS的工作原理。但是我为了方便解释,上面有些细节没有说到,有兴趣的人可以看下这部分的内容。可以跳过不看,无关紧要。

【问题1】

上面的通信过程中说到,在检查完证书后,“客户”发送一个随机的字符串给“服务器”去用私钥加密,以便判断对方是否真的持有私钥。但是有一个问题,“黑客”也可以发送一个字符串给“服务器”去加密并且得到加密后的内容,这样对于“服务器”来说是不安全的,因为黑客可以发送一些简单的有规律的字符串给“服务器”加密,从而寻找加密的规律,有可能威胁到私钥的安全。所以说,“服务器”随随便便用私钥去加密一个来路不明的字符串并把结果发送给对方是不安全的。

〖解决方法〗

每次收到“客户”发来的要加密的的字符串时,“服务器”并不是真正的加密这个字符串本身,而是把这个字符串进行一个hash计算,加密这个字符串的hash值(不加密原来的字符串)后发送给“客户”,“客户”收到后解密这个hash值并自己计算字符串的hash值然后进行对比是否一致。也就是说,“服务器”不直接加密收到的字符串,而是加密这个字符串的一个hash值,这样就避免了加密那些有规律的字符串,从而降低被破解的机率。“客户”自己发送的字符串,因此它自己可以计算字符串的hash值,然后再把“服务器”发送过来的加密的hash值和自己计算的进行对比,同样也能确定对方是否是“服务器”。

【问题2】

在双方的通信过程中,“黑客”可以截获发送的加密了的内容,虽然他无法解密这个内容,但是他可以捣乱,例如把信息原封不动的发送多次,扰乱通信过程。

〖解决方法〗

可以给通信的内容加上一个序号或者一个随机的值,如果“客户”或者“服务器”接收到的信息中有之前出现过的序号或者随机值,那么说明有人在通信过程中重发信息内容进行捣乱,双方会立刻停止通信。有人可能会问,如果有人一直这么捣乱怎么办?那不是无法通信了? 答案是的确是这样的,例如有人控制了你连接互联网的路由器,他的确可以针对你。但是一些重要的应用,例如军队或者政府的内部网络,它们都不使用我们平时使用的公网,因此一般人不会破坏到他们的通信。

【问题3】

在双方的通信过程中,“黑客”除了简单的重复发送截获的消息之外,还可以修改截获后的密文修改后再发送,因为修改的是密文,虽然不能完全控制消息解密后的内容,但是仍然会破坏解密后的密文。因此发送过程如果黑客对密文进行了修改,“客户”和“服务器”是无法判断密文是否被修改的。虽然不一定能达到目的,但是“黑客”可以一直这样碰碰运气。

〖解决方法〗

在每次发送信息时,先对信息的内容进行一个hash计算得出一个hash值,将信息的内容和这个hash值一起加密后发送。接收方在收到后进行解密得到明文的内容和hash值,然后接收方再自己对收到信息内容做一次hash计算,与收到的hash值进行对比看是否匹配,如果匹配就说明信息在传输过程中没有被修改过。如果不匹配说明中途有人故意对加密数据进行了修改,立刻中断通话过程后做其它处理。

3. 证书的构成和原理

3.1 证书的构成和原理

之前已经大概说了一个证书由什么构成,但是没有仔细进行介绍,这里对证书的内容做一个详细的介绍。先看下一个证书到底是个什么东西,在windows下查看一个证书时,界面是这样的,我们主要关注一下Details Tab页,其中的内容比较长,我滚动内容后后抓了三个图,把完整的信息显示出来:

里面的内容比较多——Version、Serial number、Signature algorithm 等等,挑几个重要的解释一下。

◆Issuer (证书的发布机构)

指出是什么机构发布的这个证书,也就是指明这个证书是哪个公司创建的(只是创建证书,不是指证书的使用者)。对于上面的这个证书来说,就是指”SecureTrust CA”这个机构。

◆Valid from , Valid to (证书的有效期)

也就是证书的有效时间,或者说证书的使用期限。 过了有效期限,证书就会作废,不能使用了。

◆Public key (公钥)

这个我们在前面介绍公钥密码体制时介绍过,公钥是用来对消息进行加密的,第2章的例子中经常用到的。这个数字证书的公钥是2048位的,它的值可以在图的中间的那个对话框中看得到,是很长的一串数字。

◆Subject (主题)

这个证书是发布给谁的,或者说证书的所有者,一般是某个人或者某个公司名称、机构的名称、公司网站的网址等。 对于这里的证书来说,证书的所有者是Trustwave这个公司。

◆Signature algorithm (签名所使用的算法)

就是指的这个数字证书的数字签名所使用的加密算法,这样就可以使用证书发布机构的证书里面的公钥,根据这个算法对指纹进行解密。指纹的加密结果就是数字签名(第1.5节中解释过数字签名)。

◆Thumbprint, Thumbprint algorithm (指纹以及指纹算法)

这个是用来保证证书的完整性的,也就是说确保证书没有被修改过,这东西的作用和2.7中说到的第3个问题类似。 其原理就是在发布证书时,发布者根据指纹算法(一个hash算法)计算整个证书的hash值(指纹)并和证书放在一起,使用者在打开证书时,自己也根据指纹算法计算一下证书的hash值(指纹),如果和刚开始的值对得上,就说明证书没有被修改过,因为证书的内容被修改后,根据证书的内容计算的出的hash值(指纹)是会变化的。 注意,这个指纹会使用”SecureTrust CA”这个证书机构的私钥用签名算法(Signature algorithm)加密后和证书放在一起。

注意,为了保证安全,在证书的发布机构发布证书时,证书的指纹和指纹算法,都会加密后再和证书放到一起发布,以防有人修改指纹后伪造相应的数字证书。这里问题又来了,证书的指纹和指纹算法用什么加密呢?他们是用证书发布机构的私钥进行加密的。可以用证书发布机构的公钥对指纹和指纹算法解密,  也就是说证书发布机构除了给别人发布证书外,他自己本身也有自己的证书 。证书发布机构的证书是哪里来的呢???这个证书发布机构的数字证书(一般由他自己生成)在我们的操作系统刚安装好时(例如windows xp等操作系统),这些证书发布机构的数字证书就已经被微软(或者其它操作系统的开发机构)安装在操作系统中了,微软等公司会根据一些权威安全机构的评估选取一些信誉很好并且通过一定的安全认证的证书发布机构,把这些证书发布机构的证书默认就安装在操作系统里面了,并且设置为操作系统信任的数字证书。这些证书发布机构自己持有与他自己的数字证书对应的私钥,他会用这个私钥加密所有他发布的证书的指纹作为数字签名。

3.2 如何向证书的发布机构去申请证书

举个例子方便大家理解,假设我们公司”ABC Company”花了1000块钱,向一个证书发布机构”SecureTrust CA”为我们自己的公司”ABC Company”申请了一张证书,注意,这个证书发布机构”SecureTrust CA”是一个大家公认并被一些权威机构接受的证书发布机构,我们的操作系统里面已经安装了”SecureTrust CA”的证书。”SecureTrust CA”在给我们发布证书时,把Issuer,Public key,Subject,Valid from,Valid to等信息以明文的形式写到证书里面,然后用一个指纹算法计算出这些数字证书内容的一个指纹,并把指纹和指纹算法用自己的私钥进行加密,然后和证书的内容一起发布,同时”SecureTrust CA”还会给一个我们公司”ABC Company”的私钥给到我们。我们花了1000块钱买的这个证书的内容如下:

×××××××××××××××证书内容开始×××××××××××××××××

Issuer : SecureTrust CA

Subject : ABC Company

Valid from : 某个日期

Valid to: 某个日期

Public Key : 一串很长的数字

…… 其它的一些证书内容……

{证书的指纹和计算指纹所使用的指纹算法}[SecureTrust CA的私钥|RSA]      //这个就是”SecureTrust CA”对这个证书的一个数字签名,表示这个证书确实是他发布的,有什么问题他会负责(收了我们1000块,出了问题肯定要负责任的)

×××××××××××××××证书内容结束×××××××××××××××××

               // 记不记得前面的约定?{} 表示RSA加密后的内容,[ | ]表示用什么密钥和算法进行加密

我们”ABC Company”申请到这个证书后,我们把证书投入使用,我们在通信过程开始时会把证书发给对方,对方如何检查这个证书的确是合法的并且是我们”ABC Company”公司的证书呢?首先应用程序(对方通信用的程序,例如IE、OUTLook等)读取证书中的Issuer(发布机构)为”SecureTrust CA” ,然后会在操作系统中受信任的发布机构的证书中去找”SecureTrust CA”的证书,如果找不到,那说明证书的发布机构是个水货发布机构,证书可能有问题,程序会给出一个错误信息。 如果在系统中找到了”SecureTrust CA”的证书,那么应用程序就会从证书中取出”SecureTrust CA”的公钥,然后对我们”ABC Company”公司的证书里面的指纹和指纹算法用这个公钥进行解密,然后使用这个指纹算法计算”ABC Company”证书的指纹,将这个计算的指纹与放在证书中的指纹对比,如果一致,说明”ABC Company”的证书肯定没有被修改过并且证书是”SecureTrust CA” 发布的,证书中的公钥肯定是”ABC Company”的。对方然后就可以放心的使用这个公钥和我们”ABC Company”进行通信了。

★这个部分非常重要,一定要理解,您可以重新回顾一下之前的两章“1、基础知识”和“ 2、一个加密通信过程的演化”,然后再来理解这部分的内容。如果您把这节的内容看了几遍还没有搞懂证书的工作原理,您可以留言指出我没有说清楚的内容,我好方便进行修正。

3.3 证书的发布机构

前面已经初步介绍了一下证书发布机构,这里再深入讨论一下。

其实所有的公司都可以发布证书,我们自己也可以去注册一家公司来专门给别人发布证书。但是很明显,我们自己的专门发布证书的公司是不会被那些国际上的权威机构认可的,人家怎么知道你是不是个狗屁皮包公司?因此微软在它的操作系统中,并不会信任我们这个证书发布机构,当应用程序在检查证书的合法信的时候,一看证书的发布机构并不是操作系统所信任的发布机构,就会抛出错误信息。也就是说windows操作系统中不会预先安装好我们这个证书发布机构的证书,不信任我们这个发布机构。

不受信任的证书发布机构的危害

为什么一个证书发布机构受不受信任这么重要?我们举个例子。假设我们开了一个狗屁公司来为别人发布证书,并且我和微软有一腿,微软在他们的操作系统中把我设置为了受信任的证书发布机构。现在如果有个小公司叫Wicrosoft 花了10块钱让我为他们公司申请了一个证书,并且公司慢慢壮大,证书的应用范围也越来越广。然后有个奸商的公司JS Company想冒充Wicrosoft,于是给了我¥10000,让我为他们颁布一个证书,但是证书的名字(Subject)要写Wicrosoft,假如我为了这¥10000,真的把证书给了他们,那么他们以后就可以使用这个证书来冒充Wicrosoft了。

如果是一个优秀的证书发布机构,比如你要向他申请一个名字叫Wicrosoft的证书,它会让你提供很多资料证明你确实可以代表Wicrosoft这个公司,也就是说他回去核实你的身份。证书发布机构是要为他发布出的证书负法律责任的。

到这里,你可能会想,TMD,那我们自己就不能发布证书吗?就一定要花钱去申请?当然不是,我们自己也可以成立证书发布机构,但是需要通过一些安全认证等等,只是有点麻烦。另外,如果数字证书只是要在公司内部使用,公司可以自己给自己生成一个证书,在公司的所有机器上把这个证书设置为操作系统信任的证书发布机构的证书(这句话仔细看清楚,有点绕口),这样以后公司发布的证书在公司内部的所有机器上就可以通过验证了(在发布证书时,把这些证书的Issuer(发布机构)设置为我们自己的证书发布机构的证书的Subject(主题)就可以了)。但是这只限于内部应用,因为只有我们公司自己的机器上设置了信任我们自己这个所谓的证书发布机构,而其它机器上并没有事先信任我们这个证书发布机构,所以在其它机器上,我们发布的证书就无法通过安全验证。

4. 在windows中对数字证书进行管理

4.1 查看、删除、安装 数字证书

我们在上一章中说到了,我们的操作系统中会预先安装好一些证书发布机构的证书,我们看下在windows中如何找到这些证书,步骤如下:

1)开始菜单->运行,输入mmc,回车

2)在打开的窗口中选择 File-> Add/Remove Snap-in…

3)然后在弹出的对话框的 Standalone Tab页里面点击 Add… 按钮

4)在弹出的对对话框中选择 certificates 后点击 Add 按钮

具体的步骤如下图所示:

上面的步骤结束后,会又弹出一个对话框,里面有三个单选按钮如下:

My user account Service account Computer account

可以选择第一或者第三个选项,用来查看当前用户的证书或整个计算里面安装的证书。我们这里就默认选择第一个,平时一般安装证书的时候都会给所有用户安装,所以选择第一个和第三个选项看到的证书会差不多。我们在左边的导航树中选中受信任的证书发布机构(Trusted Root Certificate Authorities),然后点击下面的证书(Certificates),在右边的区域中就可以看到所有的受信任的证书发布机构的证书。

注意上面的图片中,右边我们选中的这个证书发布机构”SecureTrust CA”,我们前面在第3章3.2节中举例子的时候,就是去向这个证书发布机构申请的证书,由于我们申请的证书是这个机构发布的,所以应用程序在检查我们的证书的发布机构时(会检查我们证书的签名,确认是该机构发布的证书),就会发现是可以信任的证书发布机构,从而就会相信我们证书的真实性。

删除数字证书很简单,直接在右边的列表中右键然后删除就可以了。

数字证书的安装也比较简单,直接双击数字证书文件,会打开数字证书,对话框下面会有一个Install Certificate按钮,点击后就可以根据向导进行安装,如下图所示:

这个证书是我自己生成的测试证书,在证书的导入向导里面,它会让你选择导入到什么位置,如果是一个我们自己信任的证书发布机构自己的证书,只要导入到Certificate Authorities就可以了。Trusted Root Certificate Authorities, Intermediate Certification Authorities, Third-Party Root Certification Authorities 都是可以的,他们只是对证书的发布机构做了一个分类,还有一些其它的证书类型,例如Personal(个人证书)等等,具体就不介绍了。安装的时候一般来说可以用默认的选择项一直”下一步”到底。

4.2 如何自己创建证书

每个证书发布机构都有自己的用来创建证书的工具,当然,具体他们怎么去创建一个证书的我也不太清楚,不同类型的证书都有一定的格式和规范,我没有仔细去研究过这部分内容。 微软为我们提供了一个用来创建证书的工具makecert.exe,在安装Visual Studio的时候会安装上。如果没有安装也无所谓,可以上网去下一个,搜索 makecert 就可以了。可以直接从我的博客下载,这是  链接 。

向一些正规的证书发布机构申请证书一般是要收费的(因为别人要花时间检查你的身份,确认有没有同名的证书等等),这里我们看下如何自己创建一个证书,为后面在IIS中配置Https做准备。

我们用到的是  makecert 这个工具,微软有很详细的使用帮助,我这里只做一个简单的解释,详细的各种参数和使用方法请查看  MSDN的makecert的帮助 。但是里面有些参数说得不够清楚,而且还有遗漏的,可以参看我后面的解释作为一个补充。

先看下  makecert 最简单的使用方式:

makecert.exe test.cer

上面的命令会在makecert.exe所在的目录生成一个证书文件test.cer的数字证书文件。可以双击证书打开,看看证书的内容如下:

证书的发布机构是”Root Agency”,证书的主题(证书发布给谁)是”Joe’s-Software-Emporium”,因为我们没有指定把证书发布给谁,makecert自己给我们随便生成了一个公司的名字。另外还指定了公钥、签名算法(用来解密签名)、指纹和指纹算法等。

注意 ,因为这个证书是由微软的工具生成的,严格来说它没什么发布机构,所以微软虚拟了一个叫做”Root Agency”的发布机构,默认情况下,windows里面安装了这个所谓的证书发布机构的证书,但是这证书默认情况下不是受信任的,原因很简单,这样做大家都可以用makecert来制作合法的数字证书了。如果我们自己硬是要,也可以把它设置为受信任的。

下面我们看下其它的参数,比如我们要给网站 www.jefferysun.com 生成一个证书MyCA.cer,假设我们把makecert.exe放在C:盘下,命令行如下:

makecert -r -pe -n “CN=10.30.146.206″ -b 01/01/2000 -e 01/01/2036 -eku 1.3.6.1.5.5.7.3.1 -ss my -sr localMachine -sky exchange -sp “Microsoft RSA SChannel Cryptographic Provider” -sy 12

C:\> makecert.exe –pe -r  –n  “CN=www.jefferysun.com” -ss my -sr LocalMachine -a sha1 -len 2048  MyCA.cer

解释一下makecert的常用参数的意思:

-n 指定主题的名字,这个是有固定的格式的, CN=主题名字 ,CN应该是Certificate Name的缩写。我这里的主题的名字就是我们的IIS所在机器的IP。这里可以指定一些主题的其它附加信息,例如 O= *** 表示组织信息等等。  -r 创建自签署证书,意思就是说在生成证书时,将证书的发布机构设置为自己。  -pe 将所生成的私钥标记为可导出。注意,服务器发送证书给客户端的时候,客户端只能从证书里面获取公钥,私钥是无法获取的。如果我们指定了这个参数,证书在安装在机器上后,我们还可以从证书中导出私钥,默认情况下是不能导出私钥的。正规的途径发布的证书,是不可能让你导出私钥的。  -b –e 证书的有效期  -ss 证书的存储名称,就是windows证书存储区的目录名,如果不存在在的话就创建一个。  -sr 证书的存储位置,只有currentuser(默认值)或 localmachine两个值。  -sv 指定保存私钥的文件,文件里面除了包含私钥外,其实也包含了证书。这个文件是需要保密的,这个文件在服务端配置时是需要用到的。 这个CN=10.30.146.206要与自己的服务器相对应,要不然在配置HTTPS的时候会出现错误  -a 指定签名算法,必须是md5或rsa1。(还记得签名算法的作用不?可以看一下3章的第1节中关于签名算法的介绍)  -in 指定证书发布机构的名称  -len 这个参数在中文的帮助文档中好像没有提到,但是这个其实很重要,用于指定公钥的位数,越大越安全,默认值是1024,推荐2048。我试了下,这个不为1024的倍数也是可以的。

生成证书后可以进行安装,安装过程可以参看4.1节。

本文链接: http://www.cnblogs.com/JeffreySun/archive/2010/06/24/1627247.html

ToKuDB简要

介绍TokuDB存储引擎是Tokutek2009年发布的一个数据库存储引擎,于2013年4月开源。支持MySQL/MariaDB。它和InnoDB一样支持事务、MVCC。

特色:

Franctal Tree而不是B-Tree

内部结点不仅有指向父子的指针还有Buffer区,数据写入先写buffer区,FIFO结构,写入只需要顺序添加到Buffer区就可返回,后续满时一次性刷新到下面的子树中,插入数据基本上是一个顺序添加的过程。可轻松应对随机IO,减少空间碎片。

出色的压缩性能

块大小默认是4MB

在线DDL

数据结构:

一、Buffered Tree:类B-Tree,写时直接写到Root结点,如果Root结点满了,就把数据刷新到它子结点上,如果子结点满了就继续刷新到子子结点,一直这样下去。因此,只有当结点满时才有Disk Seek产生。Node大小可设置很大,比如4MB,为提高读,需要对Node做更细划分,分成小块,随机读IO复杂度也为O(logN)。

写:

写时不产生disk seek,因为总是先写Root,由于经常被操作,可认为树顶部结点一直在Page Cache中。

节点数据紧凑,大Node压缩有优势

天然适合做事务,没有undo log,叶子结点上做mvcc

二、Fractal-Tree(Buffer-Tree的变种)buffered(4,16)-tree,OMT结构维护结点数据,大小4MB,nonleaf节点OMT结构,leaf节点多个OMT(4MB/64KB)

OMT:Order Maintenance Tree元素用数组表示,具有父子关系的元素尽量相邻存储,cpu cache line(如果一个节点的周边节点能在文件中紧邻的被存储,当读取其中一个的时候,其他节点被prefetching出来,io数则可以减少。这就是vEB layout)

结点刷新:Node完成写时检查是否满足flush条件,满足加到flush队列,后台线程并行处理从队列中读取的任务。

CheckPoint:60秒一次,sharp checkpoint无Fuzz Checkpoint,会对所有索引加只读锁,其它线程写node时,会clone一个Node。做完后检查LSN,以清理无用的log。基本上这个操作不影响前台读写操作,但是因为会进行数据压缩和clone node、写磁盘的操作,会造成一定的性能波动。

Cache:LRU,写cache时添加node到cache链表,然后检查cache状态,设置了四个水平位(低水平位,低警示点,高警示点,高水平位),高于高水平位,客户端线程进入等待状态,超过低警示点,开始收集逐出数据。

三、读:

KEY读:每次读数据都要从ROOT到LEAF,做数据合并,才能得到完整的ROW数据。

范围读:对树做深度优先遍历,在LEAF结点返回,痛苦。

四、Schema Changes列修改:Broadcast类型的Message,从Root广播到每一节点,最后到达LEAF结点。(Mysql对列修改会先关闭表,再打开表,关闭时Tokudb会把脏Node写盘,有些性能消耗),腾讯的游戏运维部门在InnoDB上实现了类似的功能。

索引:两种索引,cst_(clustering index)和cvr_(covering index),每个都是一个单独的F-tree文件

offline方式:启动多个线程,遍历记录生成索引,速度快,但创建过程中写操作不可用。

hot方式:速度慢,创建过程读写不受影响

log:log manager来管理log文件,无重做日志组的概念,当日志写满后重新生成一个文件继续写,checkpoint后检查数据都被刷新到磁盘后会删除,达到InnoDB类似的效果;分in buffer和out buffer(都是16MB),在两块内存上来回倒,多线程下锁控制方便。

优点

高压缩比,写性能高,DDL速度超快

缺点

cpu usr态消耗,响应时间变长

场景

插入效率高、压缩效率好、可在线DDL,适合写性能要求高,数据量过亿,记录不是太大的场合。

大压缩比所以CPU的USR态有消耗

相同QPS时读IOPS差别不大,写IOPS InnoDB平均多消耗些

TokuDB数据经过压缩,但会有解压消耗,所以结果不好确定,测试上看响应时间高于InnoDB

IOPS (Input/Output Operations Per Second),即每秒进行读写(I/O)操作的次数。QPS:Queries Per Second意思是“每秒查询率”,是一台服务器每秒能够相应的查询次数,是对一个特定的查询服务器在规定时间内所处理流量多少的衡量标准。

Camus实现

输出文件压缩:Camus默认只支持两种压缩格式(snappy和deflate),默认是defalte,使用 StringRecordWriterProvider写入文本格式文档时,还可以指定gzip的压缩格式,扩展其它压缩格式很容易。

文件目录规则:(配置的目录)+ topic名 + daily|hour + (年/月/日)|(年/月/日/小时) + 数据文件,例如:/rocketmq/data/vip_ods_heartbeat/daily/2015/06/10 /vip_ods_heartbeat.broker-a.0.999.48388735.1433865600000.deflate

文件名规则:topic名+ kafka的对应分区的learder的BrokerId+ kafka分区号+ 写入消息行数 + 最后一条消息的Offset + 编码的分区(时间 + 压缩格式后缀),例如:vip_ods_heartbeat.broker- a.0.999.48388735.1433865600000.deflate

确定数据导入是否完成:Camus中会在History的目录中存放历次消费的状态,包括开始执行的分区和它们的Offset、 执行结束位置的分区和它们的Offset,这两个文件以SequenceFile的形式存放在HDFS文件中,另外Camus在执行结束后可以把 执行信息汇总发送到Kafka的Topic中,Topic的名字为:TrackingMonitoringEvent,如果监控程序监控这个 Topic,是可以得到当前执行的情况的信息的。

Vertical简要

数据分析工作负载VS事务性工作负载证明商业和技术可靠的大规模分布式数据库,支持ACID、有效存储PB级别数据的系统

分析数据库的架构,关注与C-Store不同的点

实施和部署的经验,为什么引入那些不同

现实世界的经验可以引导未来大规模数据分析系统研究的方向

2.背景

2.1.1设计目标:分析工作负载不是事务工作负载

事务工作负载:每秒事务数多,每个事务会影响少数几个元组,大部分的事务是增加一个新行或者修改部分已存在的列

分析工作负载:每秒事务数少,每个事务检查很多表中的元组,比如分析用户的行为,行处理每秒很多

数据量级的增加,即使是小公司

最初就是设计一个分布式数据库

1、新节点加入系统性能有线性的扩展。使用共享磁盘的架构也能获得这样的扩展,但这很快会成为系统的瓶颈

2、优化和执行的引擎避免大量的网络数据传输,以避免内部互联成为系统瓶颈

3、查询和加载数据每秒都会有很多,必须关注支持高插入,否则只能有限的应用,批量的加载应该很快而又不能影响并行的查询

4、操作可在线,管理和维护任务不应该停止或暂停查询

5、易于使用是一个明确的目标。用CPU换时间,形式上减少复杂的网络和磁盘设置,减少性能的调优,自动化的物理设计和管理

3数据模型

3.1 (列)计划

属性排序子集(经过编码和压缩并按某种次序排序和分割的纵列集合)

加载过程中自动维护

每个Projection都有自己的排序,数据是完全排序的

任意数量、带有不同排序的推算或表列的子集是被允许的,可针对不同的查询进行优化。

不同排序规则,可被看成物化视图,但它们是物理数据结构,不是辅助索引。它不包含聚集、连接、额外的查询,认为它们在现实的分布式数据库中是不切实际的。

2.2连接索引,没有被实现,代价比带来的优势要少,实现复杂,执行代价在重现全元组在分布式查询时很高。

明确存储行ID,会点用很多空间,我们高效的压缩实现有助于减少这种开销,但没有计划实现它

3.3预连接推算

没有预期使用的多和重要:在小表连接操作上已经够好(高度优化的hash和合并算法),用户一般不愿意减慢

批量数据加载数据去优化查询速度。在加载数据时比连接查询时能够进行的优化机会要少,因为数据库在加载流中没有什么先验。

3.4 编码和压缩

不同列有不同的编码,同样的列在他们的每个推测中也可有不同的编码

自动:根据类型和配置,当使用场景不同确时

替换:低基数列

3.5 分区

c-store节点内水平分区,在单个节点使用并行提升性能

获得节点性并行并不需要硬盘物理分隔,在运行时逻辑分区,然后并行处理。尽管能够自动并行化,也提供根据value保持在物理上隔离

分区原因:快速批量删除(其它系统分区也是如此),否则需要查询所有物理文件要删除的行,添加删除标记,比起直接删文件慢的多

增加了存储的需求,在元组没有执行合并操作前影响查询性能。如果分区在所有推算上都一致,批量删除才是快速的,所以它是表层次的,而不是推算层次的

分区原因:增加查询性能,它保存最小值和最大值在每个ROS中,在做计划时就能够修剪容器。分区使这种技术更高效,分区使列数据不混合

3.6 分割:集群分布

可以指定列做hash到段

c-store根据projection的排序字段的第一个列分割物理存储到段

完全的分布式存储系统,分配存储元组到不同计算结点

节点内水平分区和节点外水平分区(分割)

分割对每个推算在排序方式上可能不同,推算分割提供决定把元组映射到节点,可以做许多重要的优化。(

完全本地分式连接,高效的分布式聚合,计算高基数不同的集合特别高效

复制推算在每个node上分布,分割推算一个元组在单个推算节点上存储

保持元组物理隔离为本地段,以方便集群的在线扩展与收缩

3.7读写优化存储

写在内存中,读在磁盘中

读:完整的依据推算排序排序的元组,列存储为一系列文件,两个文件1真实列数据2列数据索引

(大约1/1000,包含MetaData,如,开始位置,最大,最小值,固定不变故没有采用B-Tree)

写:内存,行列格式无关紧要,无编码和压缩,为缓冲数据操作(增、删、改)保证操作有足够多的行数以减少写的代价,

会随时间在行列模式下切换(和性能无关,只是软件工程考虑)

但它会以推算的分段表达式进行分段

3.7.1数据修改删除象量

不直接修改,当U和D操作时,创建D象量,它是要删除行的位置的列表,可能会有多个

DVWOS-》DVROS 高效的压缩

修改=删除+插入

4、无组移动:提高数据存储和查询效率

异步把数据从写优化到读优化,当WOS饱和mover操作没有完成之前,加载的数据直接入ROS,直到WOS重新获得足够的能力。mover平衡工作,避免产生小的ROS

读优化文件合并,减少ROS文件数量

写填满-》自动开始移动操作(随后的数据直接写到读优化,直到写有足够的空间)-》移动操作不能过多也不能过少

小文件影响压缩、减慢查询,需要更多文件句柄、seek和更多的全并排序文件的操作。

合并操作:回收已标记为删除的元组

没有限制ROS Containers大小,但它不会创建超过一定大小的(当前2T),足够大(每文件开销分摊,管理不笨重)

ROS层在Node间是私有的,不会在集群内协调

5、修改和事务

每个元组都和提交时间关联(纪元)(隐式64bit的列或删除象量)

所有节点在事务提交都同意其包含的纪元,就相当于有了全局的一致性快照,读无需要加锁

有一个分析工作量的表锁定模式

不使用传统的两阶段提交

共享锁:限制并发修改表,用来实现序列化隔离

插入锁:插入数据时到表使用,和自己兼容,支持匹量插入和加载同时发生,以保持高插入加载速度。但仍然提供事务语义

共享插入锁:读写

独占锁:删改

元组移动锁:和所有锁兼容除了X锁,在元组移动同时操作删除象量时

使用锁:

拥有锁:

分布式和组成员协议来协调集群内结点之间操作,使用广播和点对点来保证所有控制消息成功送达所有node提交在集群中成功如果它在选定数据的结点上成功,ROS与WOS创建的事务完成后其它事务才能看到

5.1纪元管理

纪元模型:纪元包含给定时间窗口所有提交了的事务

Last Good Epoch:所有数据都成功从WOS移动到ROS

Ancient History Mark:

5.2宽容失败

Vertica的数据复制通过使用Projection分段机制,以提供容错

每个Projection,必须至少有一个伙伴projection含有相同的列和分割以确保任何行被存储在同一样节点的Projection,当节点故障,伙伴projection会做为故障节点的源。

不需要传统的事务日志,数据+纪元自己就做为过去系统活动日志。vertica使用这些历史数据,去重放节点丢失的DML。节点会从正常的伙伴projection数据段恢复数据,

恢复,更新,重新平衡和备份都是在线操作;在执行这些操作同时可进行数据的加载和查询。影响的只是计算和带宽资源。

5.3

以元数据目录在节点间管理状态,它记录着表、用户、节点、纪元等的信息。

没有保存在数据库表中,因为它的表设计无法恰当的通过catalog访问和修改。它采用自定义的内存结构中,然后通过它自己的事务机制保存到磁盘中。

k-safety:当有小于或等于K个节点故障,集群仍然可用。

数据库projection必须确保有K+1个每个段的拷贝在不同的节点上。当有一半节点故障,集群会保护性关闭。集群必须保证有n/2+1个节点确保脑裂后的两个集群继续提供服务。

6 查询的执行

私有扩展标准的SQL声明的查询语言。

Vertica的公司扩展设计使可轻松地查询在SQL时过于繁琐或不可能的时间序列和日志型数据。

6.1查询操作符和计划格式

执行引擎为完全天量化,并且在同时查询一块行数据而不是同时查询单一行

标准操作树,每个操作执行一个特定的算法。一个操作者的输出做为下面操作者的输入。

执行引擎是多线程和流水线的

在一个时间请求一块行数据而不是请求一条数据。

使用上拉处理模型,直到一个操作从磁盘或网络读到数据。

扫描、分组(有多个算法依据性能最大化、内存需求、操作是否必须产生单独的组决定使用那个,并且实现了管道聚合方式可选择是否保持数据编码)、连接(实现了hash join和merge join算法,能够在必要时外部化)、表达式执行、排序、分析、发送/接收

特别处理和复杂的实现,确保可直接操作未解码的数据,这对于扫描、joins、低级聚集操作很重要。

SIP:Sideways Infomation Passing,侧面消息传递,采用在查询计划中尽可能早的过滤数据来优化join性能。(可在优化查询计算时就构建SIP filter,然后在执行时使用。在执行时,扫描操作会扫描join的hash table,SIP会用来判断outer key值在hash 表中是否存在。)

运行期间分析数据调整算法(内存不够hashtable->sort-merge join;生成预处理操作,并发执行,最后根据它们的结果生成最终数据)

晚物化、压缩的成本和估算、流聚合、消除排序、合并连接、不可见索引(每列按选择性排序,不需要索引来减少IO和更快查找值),数据die dai(L2缓存,更好的利用二级缓存和多核并发的特性)

使用pipeline执行引擎带来挑战是共享公共资源,可能造成不必要的yi chu到磁盘。vartica会把计划分成多个不会在同一时间执行的区,下游操作会回收上游操作的资源。

不同表的projection的数据被复制到所有节点上或者在join key上分割相同的范围,使得计划可以在每个节点的内部执行,然后结果发送到客户端连接的结点。

6.2 优化

选择和连接projections,保证scan和join更快(保证之后join的数据被减少)

收集性能数据(压缩I/O,CPU,网络使用)