JMX连接池

jmx连接断开后无法直接连接,而在每次使用都创建的话性能会比较差,我们可以使用apache的pool组件,它有检测连接的功能,当每次从连接池中请求连接后它会调用validateObject方法,检测这个连接是否正常,如果不正常的话会再调用makeObject方法生成新的连接,同时丢掉断开的连接。

我们先需要创建一个连接工厂,代码如下:

class JMXConnectionFactory
  extends KeyedPooledObjectFactory[String, JMXConnector] {

  @Override
  def makeObject(config: String): PooledObject[JMXConnector] = {
    val json: JsonObject = new JsonParser().parse(config).getAsJsonObject
    val host = json.getAsJsonPrimitive("host").getAsString
    val port = json.getAsJsonPrimitive("port").getAsString
    val url = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://%s:%s/jmxrmi".format(host, port))
    val environment = Maps.newHashMap[String, Array[String]]()
    if (json.has("username") && json.has("password")) {
      val credentials = Array(json.getAsJsonPrimitive("username").getAsString,
                              json.getAsJsonPrimitive("password").getAsString)
      environment.put(JMXConnector.CREDENTIALS, credentials)
    }
    val connect = JMXConnectorFactory.connect(url, environment)
    new DefaultPooledObject[JMXConnector](connect)
  }

  @Override
  def destroyObject(key: String, obj: PooledObject[JMXConnector]) = obj.getObject.close()

  @Override
  def validateObject(key: String, obj: PooledObject[JMXConnector]): Boolean = {
    var ret = false
    try {
      obj.getObject.getConnectionId
      ret = true
    } catch {
      case e: Throwable => {}
    }
    ret
  }

  @Override
  def activateObject(key: String, p: PooledObject[JMXConnector]) = {}

  @Override
  def passivateObject(key: String, p: PooledObject[JMXConnector]) = {}
}

然后需要创建一个pool

private val jmxPool = new GenericKeyedObjectPool[String, JMXConnector](new JMXConnectionFactory)
jmxPool.setTestOnBorrow(true)
jmxPool.setMaxIdlePerKey(-1)
jmxPool.setMinIdlePerKey(-1)
jmxPool.setMaxTotalPerKey(5)
jmxPool.setMaxTotal(100)
jmxPool.setTimeBetweenEvictionRunsMillis(1000 * 60 * 5)
jmxPool.setMinEvictableIdleTimeMillis(1000 * 60 * 5)

在gradle配置中需要添加相关的依赖

...
compile 'org.apache.commons:commons-pool2:2.2'
compile 'com.google.code.gson:gson:2.2.4'
...

Posted in scala | Leave a comment

Apache Kafka-0.8.1.1源码编译问题

使用Kafka里面自带的脚本进行编译
Kafka源码,里面带了gradlew的脚本,我们可以使用它编译Kafka源码:

$ git clone http://git-wip-us.apache.org/repos/asf/kafka.git
$ git checkout -b 0.8.1 origin/0.8.1
$ ./gradlew releaseTarGz

在运行这个命令之后,会报如下的错误:

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':core:signArchives'.
> Cannot perform signing task ':core:signArchives' because it has no configured signatory

* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output.

BUILD FAILED

网上都说使用下面命令执行可以正常打包,但试过了不行。

./gradlew releaseTarGzAll -x signArchives

其实可以在build.gradle文件signing块里面添加

required = false

修改后文件为

...
uploadArchives {
    repositories {
      signing {
        required false
        sign configurations.archives
...

关于这个配置的含义文档中有描述

Whether or not this task should fail if no signatory or signature type are configured at generation time. If required is a Callable, it will be stored and "called" on demand (i.e. when isRequired() is called) and the return value will be interpreting according to the Groovy Truth. For example:

 signing {
   required = { gradle.taskGraph.hasTask("uploadArchives") }
 }
 
Because the task graph is not known until Gradle starts executing, we must use defer the decision. We can do this via using a Closure (which is a Callable). For any other type, the value will be stored and evaluated on demand according to the Groovy Truth.
 signing {
   required = false
 }

配置上这个参数就可以正常打包了。下面是打包输出的信息。

/data/bin/jdk1.7.0_45/jre/bin/java -Dgradle.home=/home/jiaguotian/.gradle/wrapper/dists/gradle-1.6-bin/72srdo3a5eb3bic159kar72vok/gradle-1.6 -Dtools.jar=/data/bin/jdk1.7.0_45/jre/lib/tools.jar -Didea.launcher.port=7535 -Didea.launcher.bin.path=/data/app/idea-IC-129.1525/bin -Dfile.encoding=UTF-8 -classpath ..... 25/lib/idea_rt.jar com.intellij.rt.execution.application.AppMain org.gradle.launcher.GradleMain --build-file /home/jiaguotian/workspace/kafka-apache/build.gradle releaseTarGz_2_10_1
The TaskContainer.add() method has been deprecated and is scheduled to be removed in Gradle 2.0. Please use the create() method instead.
Building project 'core' with Scala version 2.10.1
Building project 'perf' with Scala version 2.10.1
Building project 'campaign:queue-kafka' with Scala version 2.10.1
Building project 'campaign:monitor-kafka' with Scala version 2.10.1
Building project 'campaign:campaign-examples' with Scala version 2.10.1
:releaseTarGz_2_10_1
Building project 'core' with Scala version 2.10.1
Building project 'perf' with Scala version 2.10.1
Building project 'campaign:queue-kafka' with Scala version 2.10.1
Building project 'campaign:monitor-kafka' with Scala version 2.10.1
Building project 'campaign:campaign-examples' with Scala version 2.10.1
:kafka-apache:core:compileJava UP-TO-DATE
:kafka-apache:core:compileScala UP-TO-DATE
:kafka-apache:core:processResources UP-TO-DATE
:kafka-apache:core:classes UP-TO-DATE
:kafka-apache:core:copyDependantLibs UP-TO-DATE
:kafka-apache:core:jar UP-TO-DATE
:kafka-apache:core:javadoc UP-TO-DATE
:kafka-apache:core:javadocJar UP-TO-DATE
:kafka-apache:core:scaladoc UP-TO-DATE
:kafka-apache:core:scaladocJar UP-TO-DATE
:kafka-apache:core:srcJar UP-TO-DATE
:kafka-apache:core:compileTestJava UP-TO-DATE
:kafka-apache:core:compileTestScala UP-TO-DATE
:kafka-apache:core:processTestResources UP-TO-DATE
:kafka-apache:core:testClasses UP-TO-DATE
:kafka-apache:core:testJar UP-TO-DATE
:kafka-apache:core:signArchives SKIPPED
:kafka-apache:core:releaseTarGz

BUILD SUCCESSFUL

Posted in kafka, 开发工具 | Leave a comment

递归

递归是算法是简洁、优美、强大的,递归简单地说就是把自身越来越小的形式表示和解决。

递归计算机中是如何实现的呢?

我们需要对函数调用做些了解。一个可执行文件被加载到虚拟存储器中, 其中的数据由以下几个区域组成,程序和代码(代码段),堆,共享库和静态数据区,栈。当进程调用一个函数时,就需要在栈中保存和这个调用相关的信息。栈上的这部分空间我们称为栈帧。栈帧中包括输入参数、返回值的空间、计算使用到的临时空间(比如c语言函数内部使用到的变量,在函数返回后,因为栈帧被弹出而自动清除),调用函数保存的状态信息及输出参数等。一个方法的输出参数会成为下一个调用函数的输入参数。栈帧在调用函数时压入栈中,当函数返回时才从栈中弹出。

有上面的描述我们可以推出递归函数是如何执行的,递归递归,递推加回归,在递推过程时函数通过不断调用自己来执行,然后当其中的调用满足结束条件时,递推阶段结束,开始回归操作,在回归操作中,其以递推逆序的方式执行,直到最初的函数返回。

可以看到每次调用自身的时候都需要一个栈帧来保存现场,这会占用大量的空间来存储他们,同时也因为大量信息的保存和恢复,生成和销毁他们也要花费大量时间。

有没有办法可以消除这个缺点呢?有的,如果一个函数所有递归调用都出现在函数末尾,那么我们称其为尾递归。

尾递归可以替换成等价的迭代实现,不会改变程序结果,但可以节省函数调用的开销。现代的编译器通常能够检测到尾递归,当检测到时,其会覆盖当前栈帧,而不重新创建一个,这样空间和时间上都节省不少。

我们再考虑一种情况,如果递归函数的终止条件永远得不到满足,最后程序栈会增加到超过系统可以接受的最大限度,产生栈溢出而终止执行。

另外在很多系统实现时都会把递归的算法改成迭代的算法,也是出于对函数执行效率的考虑,比如数据库最重要的算法二分查找算法,快速排序算法,都是采用的迭代算法。

但是递归还是非常有用处的,递归的算法比较容易理解算法本身,所以在许多数据结构和算法的书中都会有以递归算法来讲解算法实现。

Posted in 原理, 读书笔记 | Tagged | Leave a comment

Vertica简要

数据分析工作负载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,网络使用),

Posted in 数据库 | Leave a comment

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意思是“每秒查询率”,是一台服务器每秒能够相应的查询次数,是对一个特定的查询服务器在规定时间内所处理流量多少的衡量标准。

Posted in MySQL, 数据库 | Leave a comment

如何取得jvm实例的cpu占用

本文会贴很多代码,代码遵循Google的java代码格式。

获取数据篇

1、jmx连接的创建是一个比较重的操作,我们使用apache的common pool2创建连接工厂。

public class JmxConnectionFactory implements KeyedPooledObjectFactory<JmxServer, JMXConnector> {

  @Override
  public PooledObject<JMXConnector> makeObject(JmxServer server) throws Exception {
    JMXServiceURL
        serviceURL =
        new JMXServiceURL(String.format(
            "service:jmx:rmi:///jndi/rmi://%s:%s/jmxrmi", server.getHost(), server.getPort()));
    Map<String, Object> environment = Maps.newHashMap();
    String username = server.getUsername();
    String password = server.getPassword();
    if ((username != null) && (password != null)) {
      String[] credentials = new String[2];
      credentials[0] = username;
      credentials[1] = password;
      environment.put(JMXConnector.CREDENTIALS, credentials);
    }
    environment.put("sun.rmi.transport.proxy.connectTimeout", 1000);
    environment.put("sun.rmi.transport.tcp.responseTimeout", 3000);
    JMXConnector connect = JMXConnectorFactory.connect(serviceURL, environment);
    return new DefaultPooledObject<JMXConnector>(connect);


  }

  @Override
  public void destroyObject(JmxServer key, PooledObject<JMXConnector> object) throws Exception {
    object.getObject().close();
  }

  @Override
  public boolean validateObject(JmxServer key, PooledObject<JMXConnector> object) {
    JMXConnector connector = object.getObject();
    try {
      connector.getConnectionId();
      return true;
    } catch (IOException exception) {
      // ignore
    }
    return false;
  }

  @Override
  public void activateObject(JmxServer key, PooledObject<JMXConnector> p) throws Exception {
  }

  @Override
  public void passivateObject(JmxServer key, PooledObject<JMXConnector> p) throws Exception {
  }
}

2、从连接池中获取JMX连接

  private static GenericKeyedObjectPool<JmxServer, JMXConnector> POOL;
  private static AtomicInteger actives = new AtomicInteger(0);
  //....
try {
      JMXConnector connector = POOL.borrowObject(server);
      try {
        MBeanServerConnection mbsc = connector.getMBeanServerConnection();
        // 在这个地方使用连接获取JVM的监控数据
        // ......
      } finally {
        POOL.returnObject(server, connector);
      }

3、计算cpu占用的逻辑是:
获取:ProcessCpuTime,Uptime,AvailableProcessors,然后结合上一次获取到的数据得出,算式为:

Math.min(99F, (ProcessCpuTime-PreProcessCpuTime) / ((Uptime-PreUptime) * 10000F * AvailableProcessors));

方式一:通过获取相应的Bean,然后通过Bean去获取数据

private long prevUpTime, prevProcessCpuTime;
// ......
      RuntimeMXBean runtimeMBean =
          newPlatformMXBeanProxy(mbsc, RUNTIME_MXBEAN_NAME, RuntimeMXBean.class);
      OperatingSystemMXBean
          operatingSystemMBean =
          newPlatformMXBeanProxy(mbsc, OPERATING_SYSTEM_MXBEAN_NAME,
                                 com.sun.management.OperatingSystemMXBean.class);
      int nCPUs = operatingSystemMBean.getAvailableProcessors();
      if (runtimeMBean != null && operatingSystemMBean != null) {
        long uptime = runtimeMBean.getUptime();
        long processCpuTime = operatingSystemMBean.getProcessCpuTime();
        if (prevUpTime != 0 && prevProcessCpuTime != 0) {
          long elapsedCpu = processCpuTime - prevProcessCpuTime;
          long elaspedTime = uptime - prevUpTime;
          float cpuUsage = Math.min(99F, elapsedCpu / (elaspedTime * 10000F * nCPUs));
          prevUpTime = uptime;
          prevProcessCpuTime = processCpuTime;
          //
          JsonObject value = new JsonObject();
          String key = "CpuUsage";
          LOGGER.debug("received value '{}%' for item '{}'", cpuUsage, key);
          value.addProperty(MonitorConst.JSON_TAG_VALUE, cpuUsage);
          value.addProperty(MonitorConst.JSON_TAG_NAME, key);
          return value;
        } else {
          prevUpTime = uptime;
          prevProcessCpuTime = processCpuTime;
        }
      }
// ......

方式二、通过key来直接获取,代码通用些,比较长,代码参考zabbix gateway实现

// 通用获取方法
protected String getStringValue(MBeanServerConnection mbsc, String key) throws Exception {
    MonitorItem item = new MonitorItem(key);

    if (item.getKeyId().equals("jmx")) {
      if (2 != item.getArgumentCount()) {
        throw new MonitorException(
            "required key format: jmx[<object name>,<attribute name>]");
      }

      ObjectName objectName = new ObjectName(item.getArgument(1));
      String attributeName = item.getArgument(2);
      String realAttributeName;
      String fieldNames = "";
      int sep;

      //
      // Attribute name and composite data field names are separated by dots. On the other hand the
      // name may contain a dot too. In this case user needs to escape it with a backslash. Also the
      // backslash symbols in the name must be escaped. So a real separator is unescaped dot and
      // separatorIndex() is used to locate it.
      //

      sep = HelperFunctionChest.separatorIndex(attributeName);

      if (-1 != sep) {
        LOGGER.trace("'{}' contains composite data", attributeName);

        realAttributeName = attributeName.substring(0, sep);
        fieldNames = attributeName.substring(sep + 1);
      } else {
        realAttributeName = attributeName;
      }

      // unescape possible dots or backslashes that were escaped by user
      realAttributeName = HelperFunctionChest.unescapeUserInput(realAttributeName);

      LOGGER.trace("attributeName:'{}'", realAttributeName);
      LOGGER.trace("fieldNames:'{}'", fieldNames);

      return getPrimitiveAttributeValue(mbsc.getAttribute(objectName, realAttributeName),
                                        fieldNames);
    } else if (item.getKeyId().equals("jmx.discovery")) {
      if (0 != item.getArgumentCount()) {
        throw new MonitorException("required key format: jmx.discovery");
      }

      JsonArray counters = new JsonArray();

      for (ObjectName name : mbsc.queryNames(null, null)) {
        LOGGER.trace("discovered object '{}'", name);

        for (MBeanAttributeInfo attrInfo : mbsc.getMBeanInfo(name).getAttributes()) {
          LOGGER.trace("discovered attribute '{}'", attrInfo.getName());

          if (!attrInfo.isReadable()) {
            LOGGER.trace("attribute not readable, skipping");
            continue;
          }

          try {
            LOGGER.trace("looking for attributes of primitive types");
            String
                descr =
                (attrInfo.getName().equals(attrInfo.getDescription()) ? null
                                                                      : attrInfo
                     .getDescription());
            findPrimitiveAttributes(counters, name, descr, attrInfo.getName(),
                                    mbsc.getAttribute(name, attrInfo.getName()));
          } catch (Exception e) {
            Object[] logInfo = {name, attrInfo.getName(), e};
            LOGGER.trace("processing '{},{}' failed", logInfo);
          }
        }
      }

      JsonObject mapping = new JsonObject();
      mapping.add(MonitorConst.JSON_TAG_DATA, counters);
      return mapping.toString();
    } else {
      throw new MonitorException("key ID '%s' is not supported", item.getKeyId());
    }
  }

  private String getPrimitiveAttributeValue(Object dataObject, String fieldNames) throws
                                                                                  MonitorException {
    LOGGER
        .trace("drilling down with data object '{}' and field names '{}'", dataObject,
               fieldNames);

    if (null == dataObject) {
      throw new MonitorException("data object is null");
    }

    if (fieldNames.equals("")) {
      if (isPrimitiveAttributeType(dataObject.getClass())) {
        return dataObject.toString();
      } else {
        throw new MonitorException(
            "data object type is not primitive: %s" + dataObject.getClass());
      }
    }

    if (dataObject instanceof CompositeData) {
      LOGGER.trace("'{}' contains composite data", dataObject);

      CompositeData comp = (CompositeData) dataObject;

      String dataObjectName;
      String newFieldNames = "";

      int sep = HelperFunctionChest.separatorIndex(fieldNames);

      if (-1 != sep) {
        dataObjectName = fieldNames.substring(0, sep);
        newFieldNames = fieldNames.substring(sep + 1);
      } else {
        dataObjectName = fieldNames;
      }

      // unescape possible dots or backslashes that were escaped by user
      dataObjectName = HelperFunctionChest.unescapeUserInput(dataObjectName);

      return getPrimitiveAttributeValue(comp.get(dataObjectName), newFieldNames);
    } else {
      throw new MonitorException("unsupported data object type along the path: %s",
                                 dataObject.getClass());
    }
  }

  private void findPrimitiveAttributes(JsonArray counters, ObjectName name, String descr,
                                       String attrPath, Object attribute) {
    LOGGER.trace("drilling down with attribute path '{}'", attrPath);

    if (isPrimitiveAttributeType(attribute.getClass())) {
      LOGGER.trace("found attribute of a primitive type: {}", attribute.getClass());

      JsonObject counter = new JsonObject();

      counter.addProperty("{#JMXDESC}", null == descr ? name + "," + attrPath : descr);
      counter.addProperty("{#JMXOBJ}", name.toString());
      counter.addProperty("{#JMXATTR}", attrPath);
      counter.addProperty("{#JMXTYPE}", attribute.getClass().getName());
      counter.addProperty("{#JMXVALUE}", attribute.toString());

      counters.add(counter);
    } else if (attribute instanceof CompositeData) {
      LOGGER.trace("found attribute of a composite type: {}", attribute.getClass());

      CompositeData comp = (CompositeData) attribute;

      for (String key : comp.getCompositeType().keySet()) {
        findPrimitiveAttributes(counters, name, descr, attrPath + "." + key, comp.get(key));
      }
    } else if (attribute instanceof TabularDataSupport || attribute.getClass().isArray()) {
      LOGGER.trace("found attribute of a known, unsupported type: {}", attribute.getClass());
    } else {
      LOGGER
          .trace("found attribute of an unknown, unsupported type: {}", attribute.getClass());
    }
  }

  private boolean isPrimitiveAttributeType(Class<?> clazz) {
    Class<?>[]
        clazzez =
        {Boolean.class, Character.class, Byte.class, Short.class, Integer.class, Long.class,
         Float.class, Double.class, String.class, java.math.BigDecimal.class,
         java.math.BigInteger.class,
         java.util.Date.class, ObjectName.class};

    return HelperFunctionChest.arrayContains(clazzez, clazz);
  }
// 使用示例
获取:ProcessCpuTime,Uptime,AvailableProcessors,然后结合上一次获取到的数据得出,算式为:
String processCpuTime=getStringValue(mbsc, "jmx[\"java.lang:type=OperatingSystem\",ProcessCpuTime]")
String uptime=getStringValue(mbsc, "jmx[\"java.lang:type=Runtime\",Uptime]", #1)-last("jmx[\"java.lang:type=Runtime\",Uptime]")
String availableProcessors=getStringValue(mbsc, "jmx[\"java.lang:type=OperatingSystem\",AvailableProcessors]")

方式三、zabbix
1、clone一个Template JMX Generic,修改添加相应的item的配置,添加的Template JMX Consumer

      <template>
            <template>Template JMX Consumer</template>
            <name>Template JMX Consumer</name>
            <groups>
                <group>
                    <name>Templates</name>
                </group>
            </groups>
            <applications/>
            <items>
                <item>
                    <name>AvailableProcessors</name>
                    <type>16</type>
                    <snmp_community/>
                    <multiplier>0</multiplier>
                    <snmp_oid/>
                    <key>jmx["java.lang:type=OperatingSystem",AvailableProcessors]</key>
                    <delay>60</delay>
                    <history>7</history>
                    <trends>365</trends>
                    <status>0</status>
                    <value_type>3</value_type>
                    <allowed_hosts/>
                    <units/>
                    <delta>0</delta>
                    <snmpv3_contextname/>
                    <snmpv3_securityname/>
                    <snmpv3_securitylevel>0</snmpv3_securitylevel>
                    <snmpv3_authprotocol>0</snmpv3_authprotocol>
                    <snmpv3_authpassphrase/>
                    <snmpv3_privprotocol>0</snmpv3_privprotocol>
                    <snmpv3_privpassphrase/>
                    <formula>1</formula>
                    <delay_flex/>
                    <params/>
                    <ipmi_sensor/>
                    <data_type>0</data_type>
                    <authtype>0</authtype>
                    <username/>
                    <password/>
                    <publickey/>
                    <privatekey/>
                    <port/>
                    <description/>
                    <inventory_link>0</inventory_link>
                    <applications>
                        <application>
                            <name>Operating System</name>
                        </application>
                    </applications>
                    <valuemap/>
                </item>
                <item>
                    <name>Cpu Usage</name>
                    <type>15</type>
                    <snmp_community/>
                    <multiplier>0</multiplier>
                    <snmp_oid/>
                    <key>CpuUsage</key>
                    <delay>30</delay>
                    <history>7</history>
                    <trends>365</trends>
                    <status>0</status>
                    <value_type>0</value_type>
                    <allowed_hosts/>
                    <units>%</units>
                    <delta>0</delta>
                    <snmpv3_contextname/>
                    <snmpv3_securityname/>
                    <snmpv3_securitylevel>0</snmpv3_securitylevel>
                    <snmpv3_authprotocol>0</snmpv3_authprotocol>
                    <snmpv3_authpassphrase/>
                    <snmpv3_privprotocol>0</snmpv3_privprotocol>
                    <snmpv3_privpassphrase/>
                    <formula>1</formula>
                    <delay_flex/>
                    <params>(last("jmx[\"java.lang:type=OperatingSystem\",ProcessCpuTime]", #1)-last("jmx[\"java.lang:type=OperatingSystem\",ProcessCpuTime]", #2))/((last("jmx[\"java.lang:type=Runtime\",Uptime]", #1)-last("jmx[\"java.lang:type=Runtime\",Uptime]", #2))*10000*last("jmx[\"java.lang:type=OperatingSystem\",AvailableProcessors]", 0))</params>
                    <ipmi_sensor/>
                    <data_type>0</data_type>
                    <authtype>0</authtype>
                    <username/>
                    <password/>
                    <publickey/>
                    <privatekey/>
                    <port/>
                    <description/>
                    <inventory_link>0</inventory_link>
                    <applications>
                        <application>
                            <name>Runtime</name>
                        </application>
                    </applications>
                    <valuemap/>
                </item>
                <item>
                    <name>ProcessCpuTime</name>
                    <type>16</type>
                    <snmp_community/>
                    <multiplier>0</multiplier>
                    <snmp_oid/>
                    <key>jmx["java.lang:type=OperatingSystem",ProcessCpuTime]</key>
                    <delay>60</delay>
                    <history>7</history>
                    <trends>365</trends>
                    <status>0</status>
                    <value_type>3</value_type>
                    <allowed_hosts/>
                    <units/>
                    <delta>0</delta>
                    <snmpv3_contextname/>
                    <snmpv3_securityname/>
                    <snmpv3_securitylevel>0</snmpv3_securitylevel>
                    <snmpv3_authprotocol>0</snmpv3_authprotocol>
                    <snmpv3_authpassphrase/>
                    <snmpv3_privprotocol>0</snmpv3_privprotocol>
                    <snmpv3_privpassphrase/>
                    <formula>1</formula>
                    <delay_flex/>
                    <params/>
                    <ipmi_sensor/>
                    <data_type>0</data_type>
                    <authtype>0</authtype>
                    <username/>
                    <password/>
                    <publickey/>
                    <privatekey/>
                    <port/>
                    <description/>
                    <inventory_link>0</inventory_link>
                    <applications>
                        <application>
                            <name>Operating System</name>
                        </application>
                    </applications>
                    <valuemap/>
                </item>
            </items>
            <discovery_rules/>
            <macros/>
            <templates>
                <template>
                    <name>Template JMX Generic</name>
                </template>
            </templates>
            <screens/>
        </template>
// 修改原来的模板
<item>
                    <name>jvm Uptime</name>
                    <type>15</type>
                    <snmp_community/>
                    <multiplier>1</multiplier>
                    <snmp_oid/>
                    <key>jmxUptime</key>
                    <delay>60</delay>
                    <history>7</history>
                    <trends>365</trends>
                    <status>0</status>
                    <value_type>3</value_type>
                    <allowed_hosts/>
                    <units>uptime</units>
                    <delta>0</delta>
                    <snmpv3_contextname/>
                    <snmpv3_securityname/>
                    <snmpv3_securitylevel>0</snmpv3_securitylevel>
                    <snmpv3_authprotocol>0</snmpv3_authprotocol>
                    <snmpv3_authpassphrase/>
                    <snmpv3_privprotocol>0</snmpv3_privprotocol>
                    <snmpv3_privpassphrase/>
                    <formula>0.001</formula>
                    <delay_flex/>
                    <params>jmx["java.lang:type=Runtime",Uptime]</params>
                    <ipmi_sensor/>
                    <data_type>0</data_type>
                    <authtype>0</authtype>
                    <username/>
                    <password/>
                    <publickey/>
                    <privatekey/>
                    <port/>
                    <description/>
                    <inventory_link>0</inventory_link>
                    <applications>
                        <application>
                            <name>Runtime</name>
                        </application>
                    </applications>
                    <valuemap/>
                </item>
                <item>
                    <name>jvm Uptime Microsecond</name>
                    <type>16</type>
                    <snmp_community/>
                    <multiplier>0</multiplier>
                    <snmp_oid/>
                    <key>jmx["java.lang:type=Runtime",Uptime]</key>
                    <delay>60</delay>
                    <history>7</history>
                    <trends>365</trends>
                    <status>0</status>
                    <value_type>3</value_type>
                    <allowed_hosts/>
                    <units>uptime</units>
                    <delta>0</delta>
                    <snmpv3_contextname/>
                    <snmpv3_securityname/>
                    <snmpv3_securitylevel>0</snmpv3_securitylevel>
                    <snmpv3_authprotocol>0</snmpv3_authprotocol>
                    <snmpv3_authpassphrase/>
                    <snmpv3_privprotocol>0</snmpv3_privprotocol>
                    <snmpv3_privpassphrase/>
                    <formula>1</formula>
                    <delay_flex/>
                    <params/>
                    <ipmi_sensor/>
                    <data_type>0</data_type>
                    <authtype>0</authtype>
                    <username/>
                    <password/>
                    <publickey/>
                    <privatekey/>
                    <port/>
                    <description/>
                    <inventory_link>0</inventory_link>
                    <applications>
                        <application>
                            <name>Runtime</name>
                        </application>
                    </applications>
                    <valuemap/>
                </item>

Posted in Java语言, kafka | Leave a comment

数字证书原理

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

文中首先解释了加密解密的一些基础知识和概念,然后通过一个加密通信过程的例子说明了加密算法的作用,以及数字证书的出现所起的作用。接着对数字证书做一个详细的解释,并讨论一下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页,其中的内容比较长,我滚动内容后后抓了三个图,把完整的信息显示出来:

certificateDetails

里面的内容比较多——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),在右边的区域中就可以看到所有的受信任的证书发布机构的证书。

trustedcaAuth

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

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

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

installCertificate

这个证书是我自己生成的测试证书,在证书的导入向导里面,它会让你选择导入到什么位置,如果是一个我们自己信任的证书发布机构自己的证书,只要导入到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的数字证书文件。可以双击证书打开,看看证书的内容如下:

testCertificate1

证书的发布机构是”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

Posted in 系统开发 | Leave a comment

MySQL Memory Allocation

MySQL Memory Allocation

Table of Contents

Allocating RAM for MySQL – The Short Answer
What is the key_buffer?
What is the buffer_pool?
Another algorithm
Mutex bottleneck
HyperThreading and Multiple cores (CPUs)
32-bit OS and MySQL
64-bit OS with 32-bit MySQL
64-bit OS and MySQL
max_connections, thread_stack
table_cache (table_open_cache)
Query Cache
thread_cache_size
Binary Logs
swappiness
NUMA
huge pages
ENGINE=MEMORY
How to Set VARIABLEs
Web server
Tools
Postlog
Brought to you by Rick James

Allocating RAM for MySQL – The Short Answer

If using just MyISAM, set key_buffer_size to 20% of _available_ RAM. (Plus innodb_buffer_pool_size=0)

If using just InnoDB, set innodb_buffer_pool_size to 70% of _available_ RAM. (Plus key_buffer_size = 10M, small, but not zero.)

Rule of thumb for tuning mysql:
⚈ Start with released copy of my.cnf / my.ini.
⚈ Change key_buffer_size and innodb_buffer_pool_size according to engine usage and RAM.
⚈ Slow queries can usually be ‘fixed’ via indexes, schema changes, or SELECT changes, not by tuning.
⚈ Don’t get carried away with the Query cache until you understand what it can and cannot do.
⚈ Don’t change anything else unless you run into trouble (eg, max connections).
⚈ Be sure the changes are under the [mysqld] section, not some other section.

Now for the gory details. (NDB Cluster is not discussed here.)in

What is the key_buffer?

MyISAM does two different things for caching.
⚈ Index blocks (1KB each, BTree structured, from .MYI file) live in the “key buffer”.
⚈ Data block caching (from .MYD file) is left to the OS, so be sure to leave a bunch of free space for this.
Caveat: Some flavors of OS always claim to be using over 90%, even when there is really lots of free space.

SHOW GLOBAL STATUS LIKE ‘Key%’; then calculate Key_read_requests / Key_reads If it is high (say, over 10), then the key_buffer is big enough.

What is the buffer_pool?

InnoDB does all its caching in a the “buffer pool”, whose size is controlled by innodb_buffer_pool_size. It contains 16KB data and index blocks from the open tables, plus some maintenance overhead.

MySQL 5.5 (and 5.1 with the “Plugin”) lets you declare the block size to be 8KB or 4KB. MySQL 5.5 allows multiple buffer pools; this can help because there is one mutex per pool, thereby relieving some of the Mutex bottleneck.

More on InnoDB Tuning

Another algorithm

This will set the main cache settings to the minimum; it could be important to systems with lots of other processes and/or RAM is 2GB or smaller.

Do SHOW TABLE STATUS for all the tables in all the databases.

Add up Index_length for all the MyISAM tables. Set key_buffer_size no larger than that size.

Add up Data_length + Index_length for all the InnoDB tables. Set innodb_buffer_pool_size to no more than 110% of that total.

If that leads to swapping, cut both settings back. Suggest cutting them down proportionately.

Run this to see the values for you system. (If you have a lot of tables, it can take minute(s).)
SELECT ENGINE,
ROUND(SUM(data_length) /1024/1024, 1) AS “Data MB”,
ROUND(SUM(index_length)/1024/1024, 1) AS “Index MB”,
ROUND(SUM(data_length + index_length)/1024/1024, 1) AS “Total MB”,
COUNT(*) “Num Tables”
FROM INFORMATION_SCHEMA.TABLES
WHERE table_schema not in (“information_schema”, “performance_schema”)
GROUP BY ENGINE;

Mutex bottleneck

MySQL was designed in the days of single-CPU machines, and designed to be easily ported to many different architectures. Unfortunately, that lead to some sloppiness in how to interlock actions. There are small number (too small) of “mutexes” to gain access to several critical processes. Of note:
⚈ MyISAM’s key_buffer
⚈ The Query Cache
⚈ InnoDB’s buffer_pool
With multi-core boxes, the mutex problem is causing performance problems. In general, past 4-8 cores, MySQL gets slower, not faster. MySQL 5.5 and Percona’s XtraDB are making that somewhat better in InnoDB; the practical limit for cores is more like 32, and performance tends plateaus after that rather than declining. 5.6 claims to scale up to about 48 cores.

HyperThreading and Multiple cores (CPUs)

Short answers:
⚈ Turn off HyperThreading
⚈ Turn off any cores beyond 8
⚈ HyperThreading is mostly a thing of the past, so this section may not apply.

HyperThreading is great for marketing, lousy for performance. It involves having two processing units sharing a single hardware cache. If both units are doing the same thing, the cache will be reasonably useful. If the units are doing different things, they will be clobbering each other’s cache entries.

Furthermore MySQL is not great on using multiple cores. So, if you turn off HT, the remaining cores run a little faster.

32-bit OS and MySQL

First, the OS (and the hardware?) may conspire to not let you use all 4GB, if that is what you have. If you have more than 4GB of RAM, the excess beyond 4GB is _totally_ inaccessable and unusable on a 32-bit OS.

Secondly, the OS probably has a limit on how much RAM it will allow any process to use.

Example: FreeBSD’s maxdsiz, which defaults to 512MB.

Example:
$ ulimit -a

max memory size (kbytes, -m) 524288

So, once you have determined how much RAM is available to mysqld, then apply the 20%/70%, but round down some.

If you get an error like [ERROR] /usr/libexec/mysqld: Out of memory (Needed xxx bytes), it probably means that MySQL exceeded what the OS is willing to give it. Decrease the cache settings.

64-bit OS with 32-bit MySQL

The OS is not limited by 4GB, but MySQL is.

If you have at least 4GB of RAM, then maybe these would be good:
⚈ key_buffer_size = 20% of _all_ of RAM, but not more than 3G
⚈ buffer_pool = 3G

You should probably upgrade MySQL to 64-bit.

64-bit OS and MySQL

MyISAM only: key_buffer_size (before 5.0.52 / 5.1.23) had a hard limit of 4G. See also 5.1 restrictions
Otherwise, use about 20% of RAM. Set (in my.cnf / my.ini) innodb_buffer_pool_size = 0.

InnoDB only: innodb_buffer_pool_size = 70% of RAM. If you have lots of RAM and are using 5.5 (or later), then consider having multiple pools. Recommend 1-16 innodb_buffer_pool_instances, such that each one is no smaller than 1GB. (Sorry, no metric on how much this will help; probably not a lot.)

Meanwhile, set key_buffer_size = 20M (tiny, but non-zero)

If you have a mixture of engines, lower both numbers.

max_connections, thread_stack

Each “thread” takes some amount of RAM. This used to be about 200KB; 100 threads would be 20MB, not a signifcant size. If you have max_connections = 1000, then you are talking about 200MB, maybe more. Having that many connections probably implies other issues that should be addressed.

In 5.6 (or MariaDB 5.5), optional thread pooling interacts with max_connections. This is a more advanced topic.

Thread stack overrun rarely happens. If it does, do something like thread_stack=256K

More on max_connections, wait_timeout, connection pooling, etc

table_cache (table_open_cache)

(The name changed in some version.)

The OS has some limit on the number of open files it will let a process have. Each table needs 1 to 3 open files. Each PARTITION is effectively a table. Most operations on a partitioned table open _all_ partitions.

In *nix, ulimit tells you what the file limit is. The maximum value is in the tens of thousands, but sometimes it is set to only 1024. This limits you to about 300 tables. More discussion on ulimit

(This paragraph is in disputed.) On the other side, the table cache is (was) inefficiently implemented — lookups were done with a linear scan. Hence, setting table_cache in the thousands could actually slow down mysql. (Benchmarks have shown this.)

You can see how well your system is performing via SHOW GLOBAL STATUS; and computing the opens/second via Opened_files / Uptime If this is more than, say, 5, table_cache should be increased. If it is less than, say, 1, you might get improvement by decreasing table_cache.

Query Cache

Short answer: query_cache_type = OFF and query_cache_size = 0

The QC is effectively a hash mapping SELECT statements to resultsets.

Long answer… There are many aspects of the “Query cache”; many are negative.
⚈ Novice Alert! The QC is totally unrelated to the key_buffer and buffer_pool.
⚈ When it is useful, the QC is blazingly fast. It would not be hard to create a benchmark that runs 1000x faster.
⚈ There is a single mutex controlling the QC.
⚈ The QC, unless it is OFF & 0, is consulted for _every_ SELECT.
⚈ Yes, the mutex is hit even if query_cache_type = DEMAND (2).
⚈ Yes, the mutex is hit even for SQL_NO_CACHE.
⚈ Any change to a query (even adding a space) leads (potentially) to a different entry in the QC.

“Pruning” is costly and frequent:
⚈ When _any_ write happens on a table, _all_ entries in the QC for _that_ table are removed.
⚈ It happens even on a readonly Slave.
⚈ Purges are performed with a linear algorithm, so a large QC (even 200MB) can be noticeably slow.

To see how well your QC is performing, SHOW GLOBAL STATUS LIKE ‘Qc%’; then compute the read hit rate: Qcache_hits / Qcache_inserts If it is over, say, 5, the QC might be worth keeping.

If you decide the QC is right for you, then I recommend
⚈ query_cache_size = no more than 50M
⚈ query_cache_type = DEMAND
⚈ SQL_CACHE or SQL_NO_CACHE in all SELECTs, based on which queries are likely to benefit from caching.

QC in depth

thread_cache_size

This is a minor tunable. Zero will slow down thread (connection) creation. A small (say, 10), non-zero number is good. The setting has essentially no impact on RAM usage.

It is the number of extra processes to hang onto. It does not restrict the number of threads; max_connections does.

Binary Logs

If you have turned on binarly loging (via log_bin) for replication and/or point-in-time recovery, The system will create binary logs forever. That is, they can slowly fill up disk. Suggest setting expire_logs_days = 14 to keep only 14 days’ worth of logs.

swappiness

RHEL, in its infinite wisdom, decided to let you control how aggressively the OS will preemptively swap RAM. This is good in general, but lousy for MySQL.

MySQL would love for RAM allocations to be reasonably stable — the caches are (mostly) pre-allocated; the threads, etc, are (mostly) of limited scope. ANY swapping is likely to severly hurt performance of MySQL.

With a high value for swappiness, you lose some RAM because the OS is trying to keep a lot of space free for future allocations (that MySQL is not likely to need).

With swappiness = 0, the OS will probably crash rather than swap. I would rather have MySQL limping than die.

Somewhere in between (say, 5?) might be a good value for a MySQL-only server.

NUMA

OK, it’s time to complicate the architecture of how a CPU talks to RAM. NUMA (Non-Uniform Memory Access) enters the picture. Each CPU (or maybe socket with several cores) has a part of the RAM hanging off each. This leads to memory access being faster for local RAM, but slower (tens of cycles slower) for RAM hanging off other CPUs.

Then the OS enters the picture. In at least one case (RHEL?), two things seem to be done:
⚈ OS allocations are pinned to the ‘first’ CPU’s RAM.]
⚈ Other allocations go by default to the first CPU until it is full.

Now for the problem.
⚈ The OS and MySQL have allocated all the ‘first’ RAM.
⚈ MySQL has allocated some of the second RAM.
⚈ The OS needs to allocate something.
Ouch — it is out of room in the one CPU where it is willing to allocate its stuff, so it swaps out some of MySQL. Bad.

Possible solution: Configure the BIOS to “interleave” the RAM allocations. This should prevent the premature swapping, at the cost of off-CPU RAM accesses half the time. Well, you have the costly accesses anyway, since you really want to use all of RAM.

Overall performance loss/gain: A few percent.

huge pages

This is another hardware performance gimmick.

For a CPU to access RAM, especially mapping a 64-bit address to somewhere in, say, 128GB or ‘real’ RAM, the TLB is used. (TLB = Translation Lookup Buffer.) Think of the TLB as a hardware associative memory lookup table; given a 64-bit virtual address, what is the real address.

Because it is an associative memory of finite size, sometimes there will be “misses” that require reaching into real RAM to resolve the lookup. This is costly, so should be avoided.

Normally, RAM is ‘paged’ in 4KB pieces; the TLB actually maps the top (64-12) bits into a specific page. Then the bottom 12 bits of the virtual address are carried over intact.

For example, 128GB of RAM broken 4KB pages means 32M page-table entries. This is a lot, and probably far exceeds the capacity of the TLB. So, enter the “Huge page” trick.

With the help of both the hardware and the OS, it is possible to have some of RAM in huge pages, of say 4MB (instead of 4KB). This leads to far fewer TLB entries, but it means the unit of paging is 4MB for such parts of RAM. Hence, huge pages tend to be non-pagable.

Now RAM is broken into pagable and non pagable parts; what parts can reasonably be non pagable? In MySQL, the innodb_buffer_pool is a perfect candidate. So, by correctly configuring these, InnoDB can run a little faster:
Huge pages enabled
⚈ Tell the OS to allocate the right amount (namely to match the buffer_pool)
⚈ Tell MySQL to use huge pages

innodb memory usage vs swap
That thread has more details on what to look for and what to set.

Overall performance gain: A few percent. Yawn.

ENGINE=MEMORY

This is a little-used alternative to MyISAM and InnoDB. The data is not persistent, so it has limited uses. The size of a MEMORY table is limited to max_heap_table_size, which defaults to 16MB. I mention it in case you have changed the value to something huge; this would stealing from other possible uses of RAM.

How to Set VARIABLEs

In the text file my.cnf (my.ini on Windows), add more modify a line to say something like
innodb_buffer_pool_size = 5G
That is, VARIABLE name, “=”, and a value. Some abbreviations are allowed, such as M for million (1048576), G for billion.

For the server to see it, the settings must be in the “[mysqld]” section of the file.

The settings in my.cnf or my.ini will not take effect until you restart the server.

Most settings can be changed on the live system by connecting as user root (or other user with SUPER privilege) and doing something like
SET @@global.key_buffer_size = 77000000;
Note: No M or G suffix is allowed here.

To see the setting a global VARIABLE do something like
mysql> SHOW GLOBAL VARIABLES LIKE “key_buffer_size”;
+—————–+———-+
| Variable_name | Value |
+—————–+———-+
| key_buffer_size | 76996608 |
+—————–+———-+
Note that this particular setting was rounded down to some multiple that MySQL liked.

You may want to do both (SET, and modify my.cnf) in order to make the change immediately and have it so that the next restart (for whatever reason) will again get the value.

Web server

A web server like Apache runs multiple threads. If each threads opens a connection to MySQL, you could run out of connections. Make sure MaxClients (or equivalent) is set to some civilized number (under 50).

Tools

MySQLTuner
⚈ TUNING-PRIMER

There are several tools that advise on memory. One misleading entry they come up with
Maximum possible memory usage: 31.3G (266% of installed RAM)
Don’t let it scare you — the formulas used are excessively conservative. They assume all of max_connections are in use and active, and doing something memory-intensive.

Total fragmented tables: 23 This implies that OPTIMIZE TABLE _might_ help. I suggest it for tables with either a high percentage of “free space” (see SHOW TABLE STATUS) or where you know you do a lot of DELETEs and/or UPDATEs. Still, don’t bother to OPTIMIZE too often. Once a month might suffice.

Postlog

Created 2010; Refreshed Oct, 2012, Jan, 2014

More in-depth: Tocker’s tuning for 5.6
Irfan’s InnoDB performance optimization basics (redux)
10 MySQL settings to tune after installation

Contact me by posting a question at MySQL Forums :: Performance
– Rick James

MySQL Documents by Rick James

Tips, Debugging, HowTos, Optimizations, etc…

Rick’s RoTs (Rules of Thumb — lots of tips)
Memory Allocation (caching, etc)
Character Set and Collation problem solver
Converting from MyISAM to InnoDB — includes differences between them
Big DELETEs – how to optimize
Compound INDEXes plus other insights into the mysteries of INDEXing
Partition Maintenance (DROP+REORG) for time series
Entity-Attribute-Value — a common, poorly performing, design patter; plus an alternative
Find the nearest 10 pizza parlors (efficient searching on Latitude + Longitude)
Alter of a Huge table
Latest 10 news articles — how to optimize the schema and code for such
Pagination, not with OFFSET, LIMIT
Data Warehouse techniques (esp., Summary Tables)
Techniques on efficiently finding a random row (On beyond ORDER BY RAND())
GUID/UUID Performance (type 1 only)
IP Range Table Performance
MySQL Limits
Galera Limitations (with Percona XtraDB Cluster / MariaDB)
Rollup Unique User Counts
Best of MySQL Forum

原文地址:
mysql.rjweb.org/doc.php/memory

Posted in MySQL | Tagged | Leave a comment

最近的生活 2014-07-25

最近总觉得时间过的很快,一眨眼一个周就过去了,有很多事情来不急去做。每天起床、吃饭、上班、下班、睡觉,要看书只能够在上下班的地铁上了,不过就靠着这挤出来的时间,倒也看完了几本书了,我是标准的不求甚解者,看完的东西隔个几天就记忆不清了,真想拥有过目不忘的本事啊。
最近换了手机,老的G2手机太慢了,好多的软件都运行不起来,现在用手机订阅了一些博客,在手机多看上也买了几本书,现在在吃饭的间隙、等车的时候…都翻翻看看,人呀,总要找些事情做。
说起学习,刚看完了《深入理解jvm虚拟机》《java虚拟机原理》,《mysql技术内幕.innodb存储引擎》。看得时候觉得嗯嗯嗯,然后过几天就忘记了,回过头来看又觉得是啊,是要这么实现,然后过几天又记不清了。唉,纸上得来终是浅啊,得亲自搞搞才行,前些天自己编译了jdk,打算参考着代码看看,还有innodb的代码,不过c,c++不熟悉啊,希望在看的同时再学习下她。毕竟还是必要的。要学的东西太多了…年纪大了…
朋友也少联系了…

–EOF-

Posted in 猿の生活 | Leave a comment

工程师的生活

工程师的生活
http://www.raychase.net/1543

    我忽然很好奇,想知道其他软件工程师的生活是什么样的?人永远都没有活在别人心中的形象那么绚烂,生活中总有无数烂事烦事需要处理,但是每个人都有自己享受生活的方式。逛了逛了各式技术博客和论坛,我发现大家似乎都太严肃了,太谦逊了,太学术了。做软件本来是一件很有意思的事情,但是这些帖子和文章无非就包括这么几种:
    1、技术文章,不解释,这部分当然是大头,虽然技术文章普遍不受欢迎;
    2、牢骚,喵了个咪的薪水低啊,呜了个汪的加班苦啊;
    3、心灵鸡汤,要励志、要发奋、要改变世界;
长者语气教育后辈,“给刚入职的程序员们的警示”;
    4、无聊的纷争,Linux就是比Windows牛逼,Java就是一门屎尿屁的语言……
    做软件的人只是如此吗?就只有上面这几条单调的事情可以聊?工程师就不能记录更丰富的生活吗?在大多数人都在谈论生活品质的时候,工程师也应该跟上脚步。我相信Geek的生活有人羡慕也只能算少数,码农的生活虽司空见惯但他们才是最大的群体,才是软件行业未来的希望。既然没有任何人提及软件工程师的生活品质,那我愿意做第一个吃螃蟹的人:
成为工程师,而不是码农。如果你连这样的愿望都没有,我们的不同点太多,就算我白啰嗦了。
    寻找不同的享乐方式。为什么把享受放在那么靠前的位置?不是说要先奋斗后享乐吗?这样想的话,说不定你已经被洗过脑了。你的成功不会和享乐冲突的,每个人都可以选择自己的生活方式,谁都有自己的衡量标准,但是在我看来,只有在苦中坚持而不会作乐的生活才是百分百失败的。
     为你和你自己的梦想而工作。不要单纯为公司而工作,也不要只是为父母而工作。知道得少不可怕,可怕的是知道的都是被洗脑了的。容许其他人说那些大道理,容许那些心灵鸡汤天天试图灌你喝,自己千万要清醒,要对自己负责。那些为了公司而拼了命的人,并不是你的榜样。前两天看到一条评论根深蒂固加班文化的奴才机制,过度工作导致又有某某人猝死,却有大部分回复是在说“请注意锻炼一下”,这让我感到无比悲哀和寒心,这里的问题是“锻炼”不够的问题么?
    尊重、容忍和改变。我在《致那些自嘲码农的苦逼程序员》里面已经说过了,我们都理解那些迫不得已的事情,隐忍是在等待时机,蛰伏是有明确目的的,是为了冲破现状,追寻更接近理想和价值观的生活。最怕的事情是,在这样自己都不愿认可的生活中,磨平了棱角。
    积极争取想要的一切。我不想泡心灵鸡汤,因为我只是想说那些小事。就像你想要“一台大屏幕的显示器”这样的事情一样,如果它当然可以大大地提高你的工作品质,没有什么太明显不过的迹象阻挠你,为什么不争取一下?会失败还是成功?至少争取过了,不会有一点遗憾。我有一位欧洲的同事,它给公司的后勤部门提了不少意见建议,于是我们有了咖啡机、饮料有了更多的选品,灯管坏了能得到及时修理。还有一件小事,我的同事在飞机上被冷气吹得不舒服,提出来,没费多少口舌,得到了五千个里程的补偿。如果不屑去做、无所谓、有顾虑、懒得动弹,那就什么都不会有的。
    过酷一点的生活,还有自由的生活。你会有你自己的理解,比如西乔所说的“我在过着很奢侈的生活”,这绝不仅仅是只物质上(事实上她认为程序员还算是“收入能和付出成正比的群体”)。我可以以我自己为例,生活在北京但是我和我老婆远没有足够的钱,去买北京令我们方便和舒适的房子,那么我们就先不买,她每周都去练瑜伽,我每周都会打球,周末可以看电影、享受美食、学自己感兴趣的东西。我们还收养(主要是她在照顾)了两只无家可归的小狗(刚来的时候大概眼睛刚睁开,只能吃奶粉,现在已经会疯跑和到处乱啃了),等它们再大一点的时候可以把它们送到好一点的人家里去当宠物(如果你也在北京且感兴趣的话请联系我,邮件地址在右上角“关于四火”里有),我觉得这很酷。

Posted in 猿の生活 | Leave a comment