<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>舒の随想日记</title>
	<atom:link href="http://blog.hesey.net/feed" rel="self" type="application/rss+xml" />
	<link>http://blog.hesey.net</link>
	<description>思考生活，关注科技。To live is to CHANGE the world.</description>
	<lastBuildDate>Thu, 23 Feb 2012 01:11:32 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.3.1</generator>
		<item>
		<title>Linux IO调度器</title>
		<link>http://blog.hesey.net/2012/02/linux-io-scheduler.html?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=linux-io-scheduler</link>
		<comments>http://blog.hesey.net/2012/02/linux-io-scheduler.html#comments</comments>
		<pubDate>Fri, 17 Feb 2012 13:38:34 +0000</pubDate>
		<dc:creator>Hesey</dc:creator>
				<category><![CDATA[Linux]]></category>
		<category><![CDATA[技术]]></category>

		<guid isPermaLink="false">http://blog.hesey.net/?p=945</guid>
		<description><![CDATA[在现代计算机体系中，机械硬盘仍然作为大部分情况下的存储设备使用，而机械硬盘的访问相对内存差了多个数量级，主要原因在于机械臂转动的寻道时间太长，机械操作没法跟上电子信号的传递，所以OS不可能对每次I/O请求都直接作寻道处理，而是需要额外的工作。 在Linux中，这部分工作主要由I/O调度器完成。 既然时间消耗主要花费在寻道上，那么减少寻道时间就成为I/O调度算法的核心，这主要是通过对I/O请求实施合并和排序完成的。I/O调度器的主要工作是管理像磁盘这种块设备的请求队列，并分配I/O资源给请求。目标是提升全局吞吐量，注意，这意味着I/O请求并不是按照FIFO来处理，而是存在不公平的现象，原理类似于公平锁和非公平锁。 I/O调度器通过合并和排序完成调度任务。 合并是指将两个或多个请求结合成一个新的请求，例如当新来的请求和当前请求队列中的某个请求需要访问的是相同或相邻扇区时，那么就可以把两个请求合并为对同一或多个相邻扇区的请求，这样只需要一次寻道即可。通过合并的做法，多个I/O请求被压缩为一次I/O，最后发送给磁盘的只需要一条寻址命令，就能完成多次寻址同样的效果。显然，合并请求能减少系统开销和磁盘寻址次数。 解决了对相邻扇区的访问，那么对于非相邻扇区呢？我们知道机械臂的转动是朝着扇区增长的方向的，类似于电梯，那么如果我们把I/O请求按照扇区增长排列，一次旋转就可以访问更多的扇区，就可以缩短所有请求的实际寻道时间。这便是I/O调度器的另一项任务：排序。 1、Linus电梯 上面说到排序操作导致的结果和电梯类似，所以早期Linux的I/O调度算法被称为电梯算法，在2.4内核中，被称作Linus电梯。 Linus电梯实现了向前合并和向后合并，对应扇区的增长和减少，而排序操作则如同上面所说的，通过将新请求插入到已有请求队列中合适的位置去实现。那么这里就有一个问题了，如果一个请求比较倒霉，老是被插队怎么办？Linus电梯解决这个问题的办法是当一段时间后检测到队列中有长期没有被处理的请求，那么就暂时中止插入，将新请求直接放到队列尾部，寄希望于顺序处理当前队列中现有的请求来保证响应。但这种做法的缺点在于，I/O调度器并没有真正去处理饥饿的请求，而是采取了一种间接的方式，所以很有可能饥饿的请求仍然饥饿，并没有解决实质的问题。 2、DeadLine 为了解决Linus电梯的饥饿问题，DeadLine在全局吞吐量和延迟方面取了权衡。在DeadLine算法中，每个I/O请求都有一个超时时间，默认读请求是500ms，写请求是5s。 DeadLine除了和Linus电梯一样维护了一个拥有合并和排序功能的请求队列以外，额外维护了两个队列，分别是读请求队列和写请求队列，它们都是带有超时的FIFO队列。当新来一个I/O请求时，会被同时插入普通队列和读/写队列，然后处理普通队列中的请求。当调度器发现读/写请求队列中的请求超时的时候，会优先处理这些请求，保证尽可能不产生请求饥饿。当然，这里会牺牲一定的全局吞吐量。 3、Anticipatory DeadLine解决了饥饿问题，但是降低了全局吞吐量，当系统大量存在顺序请求时，可能导致请求无法被很好地排序，引发频繁寻道。 Anticipatory是基于预测的I/O算法，大体上它和DeadLine很类似，也维护了三个请求队列。区别在于，当它处理完一个I/O请求以后并不会直接返回处理下一个请求，而是会等待片刻，默认为6ms。如果这时候有新来的针对当前扇区相邻扇区的请求，那么会直接处理它，当等待时间结束后，调度器才返回处理下一个队列请求。 试想一下，如果系统有频繁的针对邻近扇区的I/O请求，那么这种预测算法必然大幅提高整体的吞吐量，毕竟节约了那么多寻道时间。Anticipatory也是当前Linux内核默认的I/O调度器。 更新：在Linux Kernel 2.6.28 的Anticipatory/CFS算法中，对于块设备增加了QUEUE_FLAG_NONROT的标志位，标记随机访问设备以消除等待时间的开销。感谢 @fbcon 的提醒：） 4、CFQ(Complete Fair Queuing) CFQ把I/O请求按照进程分别放入进程对应的队列中，所以A进程和B进程发出的I/O请求会在两个队列中。而各个队列内部仍然采用合并和排序的方法，区别仅在于，每一个提交I/O请求的进程都有自己的I/O队列。 CFQ的“公平”是针对进程而言的，它以时间片算法为前提，轮转调度队列，默认从当前队列中取4个请求处理，然后处理下一个队列的4个请求。这样就可以确保每个进程享有的I/O资源是均衡的。 5、Noop Noop做的事情非常简单，它不会对I/O请求排序也不会进行任何其它优化（除了合并）。Noop除了对请求合并以外，不再进行任何处理，直接以类似FIFO的顺序提交I/O请求。 那么为什么我们需要这种调度器呢？因为Noop面向的不是普通的块设备，而是随机访问设备（例如SSD），对于这种设备，不存在传统的寻道时间，那么就没有必要去做那些多余的为了减少寻道时间而采取的事情了。 从上面的算法中我们可以看到，对于不同的场景，选择不同的I/O调度器是十分有必要的。例如对于数据库这种随机访问特别明显的场景，如果使用默认的Anticipatory，就会牺牲大量不必要的等待时间，这时候使用DeadLine通常是更好的选择。对于SSD这种设备，采用Noop或者DeadLine也通常能获得比默认调度器更好的性能。]]></description>
			<content:encoded><![CDATA[<p>在现代计算机体系中，机械硬盘仍然作为大部分情况下的存储设备使用，而机械硬盘的访问相对内存差了多个数量级，主要原因在于机械臂转动的寻道时间太长，机械操作没法跟上电子信号的传递，所以OS不可能对每次I/O请求都直接作寻道处理，而是需要额外的工作。</p>
<p>在Linux中，这部分工作主要由I/O调度器完成。<span id="more-945"></span></p>
<p>既然时间消耗主要花费在寻道上，那么减少寻道时间就成为I/O调度算法的核心，这主要是通过对I/O请求实施<span style="color: #ff0000;"><strong>合并和排序</strong></span>完成的。I/O调度器的主要工作是管理像磁盘这种块设备的请求队列，并分配I/O资源给请求。目标是提升全局吞吐量，注意，这意味着<span style="color: #ff0000;"><strong>I/O请求并不是按照FIFO来处理</strong></span>，而是存在不公平的现象，原理类似于公平锁和非公平锁。</p>
<p>I/O调度器通过合并和排序完成调度任务。</p>
<p>合并是指将两个或多个请求结合成一个新的请求，例如当新来的请求和当前请求队列中的某个请求需要访问的是相同或相邻扇区时，那么就可以把两个请求合并为对同一或多个相邻扇区的请求，这样只需要一次寻道即可。通过合并的做法，多个I/O请求被压缩为一次I/O，最后发送给磁盘的只需要一条寻址命令，就能完成多次寻址同样的效果。显然，合并请求能减少系统开销和磁盘寻址次数。</p>
<p>解决了对相邻扇区的访问，那么对于非相邻扇区呢？我们知道机械臂的转动是朝着扇区增长的方向的，类似于电梯，那么如果我们把I/O请求按照扇区增长排列，一次旋转就可以访问更多的扇区，就可以缩短所有请求的实际寻道时间。这便是I/O调度器的另一项任务：排序。</p>
<h1>1、Linus电梯</h1>
<blockquote><p>上面说到排序操作导致的结果和电梯类似，所以早期Linux的I/O调度算法被称为电梯算法，在2.4内核中，被称作Linus电梯。</p>
<p>Linus电梯实现了向前合并和向后合并，对应扇区的增长和减少，而排序操作则如同上面所说的，通过将新请求插入到已有请求队列中合适的位置去实现。那么这里就有一个问题了，如果一个请求比较倒霉，老是被插队怎么办？Linus电梯解决这个问题的办法是当一段时间后检测到队列中有长期没有被处理的请求，那么就暂时中止插入，将新请求直接放到队列尾部，寄希望于顺序处理当前队列中现有的请求来保证响应。但这种做法的<span style="color: #ff0000;"><strong>缺点在于，I/O调度器并没有真正去处理饥饿的请求，而是采取了一种间接的方式，所以很有可能饥饿的请求仍然饥饿，并没有解决实质的问题。</strong></span></p></blockquote>
<h1>2、DeadLine</h1>
<blockquote><p>为了解决Linus电梯的饥饿问题，DeadLine在全局吞吐量和延迟方面取了权衡。在DeadLine算法中，每个I/O请求都有一个超时时间，默认读请求是500ms，写请求是5s。</p>
<p><span style="color: #ff0000;"><strong>DeadLine除了和Linus电梯一样维护了一个拥有合并和排序功能的请求队列以外，额外维护了两个队列，分别是读请求队列和写请求队列，它们都是带有超时的FIFO队列</strong></span>。当新来一个I/O请求时，会被同时插入普通队列和读/写队列，然后处理普通队列中的请求。当调度器发现读/写请求队列中的请求超时的时候，会优先处理这些请求，保证尽可能不产生请求饥饿。当然，这里会牺牲一定的全局吞吐量。</p></blockquote>
<h1>3、Anticipatory</h1>
<blockquote><p>DeadLine解决了饥饿问题，但是降低了全局吞吐量，当系统大量存在顺序请求时，可能导致请求无法被很好地排序，引发频繁寻道。</p>
<p>Anticipatory是基于预测的I/O算法，大体上它和DeadLine很类似，也维护了三个请求队列。<span style="color: #ff0000;"><strong>区别在于，当它处理完一个I/O请求以后并不会直接返回处理下一个请求，而是会等待片刻，默认为6ms。如果这时候有新来的针对当前扇区相邻扇区的请求，那么会直接处理它，当等待时间结束后，调度器才返回处理下一个队列请求。</strong></span></p>
<p>试想一下，如果系统有频繁的针对邻近扇区的I/O请求，那么这种预测算法必然大幅提高整体的吞吐量，毕竟节约了那么多寻道时间。Anticipatory也是当前Linux内核默认的I/O调度器。</p>
<p><span style="color: #ff0000;"><strong>更新：</strong></span><span style="color: #000000;">在Linux Kernel 2.6.28 的Anticipatory/CFS算法中，对于块设备增加了QUEUE_FLAG_NONROT的标志位，标记随机访问设备以消除等待时间的开销。感谢 @fbcon 的提醒：）</span></p></blockquote>
<h1>4、CFQ(Complete Fair Queuing)</h1>
<blockquote><p>CFQ把I/O请求按照进程分别放入进程对应的队列中，所以A进程和B进程发出的I/O请求会在两个队列中。而各个队列内部仍然采用合并和排序的方法，区别仅在于，<span style="color: #ff0000;"><strong>每一个提交I/O请求的进程都有自己的I/O队列。</strong></span></p>
<p>CFQ的“公平”是针对进程而言的，它以时间片算法为前提，轮转调度队列，默认从当前队列中取4个请求处理，然后处理下一个队列的4个请求。这样就可以确保每个进程享有的I/O资源是均衡的。</p></blockquote>
<h1>5、Noop</h1>
<blockquote><p>Noop做的事情非常简单，它不会对I/O请求排序也不会进行任何其它优化（除了合并）。Noop除了对请求合并以外，不再进行任何处理，直接以类似FIFO的顺序提交I/O请求。</p>
<p>那么为什么我们需要这种调度器呢？因为<span style="color: #ff0000;"><strong>Noop面向的不是普通的块设备，而是随机访问设备（例如SSD）</strong></span>，对于这种设备，不存在传统的寻道时间，那么就没有必要去做那些多余的为了减少寻道时间而采取的事情了。</p></blockquote>
<p>从上面的算法中我们可以看到，对于不同的场景，选择不同的I/O调度器是十分有必要的。例如对于数据库这种随机访问特别明显的场景，如果使用默认的Anticipatory，就会牺牲大量不必要的等待时间，这时候使用DeadLine通常是更好的选择。对于SSD这种设备，采用Noop或者DeadLine也通常能获得比默认调度器更好的性能。</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.hesey.net/2012/02/linux-io-scheduler.html/feed</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>支持多域名虚拟主机SSL/TLS认证的技术：SNI</title>
		<link>http://blog.hesey.net/2012/02/sni-for-multi-domain-ssl-tls.html?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=sni-for-multi-domain-ssl-tls</link>
		<comments>http://blog.hesey.net/2012/02/sni-for-multi-domain-ssl-tls.html#comments</comments>
		<pubDate>Fri, 10 Feb 2012 06:24:17 +0000</pubDate>
		<dc:creator>Hesey</dc:creator>
				<category><![CDATA[技术]]></category>

		<guid isPermaLink="false">http://blog.hesey.net/?p=936</guid>
		<description><![CDATA[早期的SSLv2根据经典的公钥基础设施PKI(Public Key Infrastructure)设计，它默认认为：一台服务器（或者说一个IP）只会提供一个服务，所以在SSL握手时，服务器端可以确信客户端申请的是哪张证书。 但是让人万万没有想到的是，虚拟主机大力发展起来了，这就造成了一个IP会对应多个域名的情况。解决办法有一些，例如申请泛域名证书，对所有*.yourdomain.com的域名都可以认证，但如果你还有一个yourdomain.net的域名，那就不行了。 在HTTP协议中，请求的域名作为主机头（Host）放在HTTP Header中，所以服务器端知道应该把请求引向哪个域名，但是早期的SSL做不到这一点，因为在SSL握手的过程中，根本不会有Host的信息，所以服务器端通常返回的是配置中的第一个可用证书。因而一些较老的环境，可能会产生多域名分别配好了证书，但返回的始终是同一个。 既然问题的原因是在SSL握手时缺少主机头信息，那么补上就是了。 SNI（Server Name Indication）定义在RFC 4366，是一项用于改善SSL/TLS的技术，在SSLv3/TLSv1中被启用。它允许客户端在发起SSL握手请求时（具体说来，是客户端发出SSL请求中的ClientHello阶段），就提交请求的Host信息，使得服务器能够切换到正确的域并返回相应的证书。 要使用SNI，需要客户端和服务器端同时满足条件，幸好对于现代浏览器来说，大部分都支持SSLv3/TLSv1，所以都可以享受SNI带来的便利。 附：支持SNI的浏览器、服务器、库 Browsers with support for TLS server name indication Internet Explorer 7 or later, on Windows Vista or higher. Does not work on Windows XP, even Internet Explorer 8. Mozilla Firefox 2.0 or later Opera 8.0 or later (the TLS 1.1 protocol must [...]]]></description>
			<content:encoded><![CDATA[<p>早期的SSLv2根据经典的公钥基础设施<span style="color: #ff0000;"><strong>PKI</strong></span>(Public Key Infrastructure)设计，它默认认为：<span style="color: #ff0000;"><strong>一台服务器（或者说一个IP）只会提供一个服务</strong></span>，所以在SSL握手时，服务器端可以确信客户端申请的是哪张证书。</p>
<p>但是让人万万没有想到的是，虚拟主机大力发展起来了，这就造成了一个IP会对应多个域名的情况。解决办法有一些，例如申请泛域名证书，对所有*.yourdomain.com的域名都可以认证，但如果你还有一个yourdomain.net的域名，那就不行了。</p>
<p>在HTTP协议中，请求的域名作为主机头（Host）放在HTTP Header中，所以服务器端知道应该把请求引向哪个域名，但是早期的SSL做不到这一点，因为在SSL握手的过程中，根本不会有Host的信息，所以服务器端通常返回的是配置中的第一个可用证书。因而一些较老的环境，可能会产生多域名分别配好了证书，但返回的始终是同一个。</p>
<p><span style="color: #ff0000;"><strong>既然问题的原因是在SSL握手时缺少主机头信息，那么补上就是了。</strong></span></p>
<p><span style="color: #ff0000;"><strong>SNI</strong></span>（Server Name Indication）定义在<span style="color: #008000;"><strong><a href="http://tools.ietf.org/html/rfc4366"><span style="color: #008000;">RFC 4366</span></a></strong></span>，是一项用于改善SSL/TLS的技术，在SSLv3/TLSv1中被启用。<span style="color: #ff0000;"><strong>它允许客户端在发起SSL握手请求时（具体说来，是客户端发出SSL请求中的ClientHello阶段），就提交请求的Host信息，使得服务器能够切换到正确的域并返回相应的证书。</strong></span></p>
<p><span style="color: #ff0000;"><strong>要使用SNI，需要客户端和服务器端同时满足条件</strong></span>，幸好对于现代浏览器来说，大部分都支持SSLv3/TLSv1，所以都可以享受SNI带来的便利。<span id="more-936"></span></p>
<p><span style="color: #ff6600;"><strong>附：支持SNI的浏览器、服务器、库</strong></span><br />
<strong></strong></p>
<p><strong>Browsers with support for TLS server name indication</strong></p>
<blockquote><p>Internet Explorer 7 or later, on Windows Vista or higher. Does not work on Windows XP, even Internet Explorer 8.<br />
Mozilla Firefox 2.0 or later<br />
Opera 8.0 or later (the TLS 1.1 protocol must be enabled)<br />
Opera Mobile at least version 10.1 beta on Android[citation needed]<br />
Google Chrome (Vista or higher. XP on Chrome 6 or newer. OS X 10.5.7 or higher on Chrome 5.0.342.1 or newer)<br />
Safari 2.1 or later (Mac OS X 10.5.6 or higher and Windows Vista or higher)<br />
Konqueror/KDE 4.7 or later<br />
MobileSafari in Apple iOS 4.0 or later<br />
Android default browser on Honeycomb or newer<br />
Windows Phone 7[citation needed]<br />
MicroB on Maemo</p></blockquote>
<p><strong>Servers</strong></p>
<blockquote><p>Apache 2.2.12 or later using mod_ssl(or alternatively with experimental mod_gnutls)<br />
Cherokee if compiled with TLS support<br />
Versions of lighttpd 1.4.x and 1.5.x with patch, or 1.4.24+ without patch<br />
Nginx with an accompanying OpenSSL built with SNI support<br />
LiteSpeed 4.1 or later<br />
Pound 2.6 or later<br />
Apache Tomcat on Java 7 or later<br />
Microsoft Internet Information Server IIS 8</p></blockquote>
<p><strong>Libraries</strong></p>
<blockquote><p>Mozilla NSS 3.11.1 client-side only<br />
OpenSSL<br />
0.9.8f (released 11 Oct 2007) &#8211; not compiled in by default, can be compiled in with config option &#8216;&#8211;enable-tlsext&#8217;<br />
0.9.8j (released 07 Jan 2009) through 1.0.0 (released 29 March 2010) &#8211; compiled in by default<br />
GNU TLS<br />
libcurl / cURL since 7.18.1 (released 30 Mar 2008) when compiled against an SSL/TLS toolkit with SNI support<br />
Python 3.2 (ssl, urllib and httplib modules)<br />
Qt 4.8<br />
Oracle Java 7 JSSE</p></blockquote>
]]></content:encoded>
			<wfw:commentRss>http://blog.hesey.net/2012/02/sni-for-multi-domain-ssl-tls.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>证书链及在nginx下的配置</title>
		<link>http://blog.hesey.net/2012/02/certificate-chain-and-configuration-of-nginx.html?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=certificate-chain-and-configuration-of-nginx</link>
		<comments>http://blog.hesey.net/2012/02/certificate-chain-and-configuration-of-nginx.html#comments</comments>
		<pubDate>Fri, 10 Feb 2012 05:34:33 +0000</pubDate>
		<dc:creator>Hesey</dc:creator>
				<category><![CDATA[技术]]></category>

		<guid isPermaLink="false">http://blog.hesey.net/?p=929</guid>
		<description><![CDATA[通常为了保护Web通信传输，我们会使用SSL/TLS协议去同时实现加密传输和身份认证，这里需要在服务器端配置好CA证书。 CA证书分为两类：根证书（Root CA）和中间证书（Intermediate CA）。这里的证书我们用OpenSSL就可以生成，但是只有经过认证的CA机构签发的根证书才会被浏览器或其它设备信任，做法就是预先在浏览器或系统里内置可信的根证书。 但是根证书的使用是收到严格限制的，不可能对于每一类用户都使用根证书去签发子数字证书，所以就有了中间证书的概念。中间证书由根证书或上一级中间证书签发，它可以再往下级签发数字证书。例如我们自己为某个域名申请了证书 My CA，那么对于三级证书链，签发过程是这样的： Root CA 签发 Intermediate CA， Intermediate CA 签发 My CA 这时我们就可以用My CA去给域名作数字认证了，那么验证的过程可想而知，就是签发的逆过程，这是通过证书链来完成的。 上面讲到的签发关系很像链式结构，所以被称作证书链。那么浏览器怎么知道My CA是由谁签发的呢？这是由证书里自带的一个字段：Issued By完成的，它会告诉浏览器，这张证书是由谁签发的，请核实签发者是否是可信的。 浏览器继续审核中间证书，看是否和可信证书库中的证书有匹配： 如果有则认为My CA也是可信的； 如果没有，继续往上找，直到根证书： 如果根证书是可信的，那么整条证书链就是可信的； 如果根证书不可信，那么My CA将被认作是不可信的，浏览器就会发出警告。 对于nginx，我们可以把完整的证书链配置在证书文件中，这样就可以一次性通过链式查找去找到证书链上的所有证书，并一一验证。注意不要只配置你自己的user-end证书，因为nginx对于证书链的长度变量ssl_verify_depth默认是1，所以当证书链长度比这个长时，会无法找到根证书，浏览器有可能错误地认为证书不可信。 例如我们生成的证书是my.crt，这时下载证书链文件（*.pem），并追加到my.crt后面： cat ca.pem &#62;&#62; my.crt 最后使用vi检查一下，保证每个证书的结尾标记（&#8212;&#8211;END CERTIFICATE&#8212;&#8211;）和开头标记（&#8212;&#8211;BEGIN CERTIFICATE&#8212;&#8211;）不在同一行，不然启动nginx会报错。 更多信息请参考： http://en.wikipedia.org/wiki/Intermediate_certificate_authorities http://www.whichssl.com/intermediate_certificates.html http://wiki.nginx.org/HttpSslModule]]></description>
			<content:encoded><![CDATA[<p>通常为了保护Web通信传输，我们会使用SSL/TLS协议去同时实现加密传输和身份认证，这里需要在服务器端配置好CA证书。</p>
<p>CA证书分为两类：<span style="color: #ff0000;"><strong>根证书（Root CA）</strong></span>和<span style="color: #ff0000;"><strong>中间证书（Intermediate CA）</strong></span>。这里的证书我们用OpenSSL就可以生成，但是只有经过认证的CA机构签发的根证书才会被浏览器或其它设备信任，做法就是预先在浏览器或系统里内置可信的根证书。</p>
<p>但是根证书的使用是收到严格限制的，不可能对于每一类用户都使用根证书去签发子数字证书，所以就有了中间证书的概念。<span style="color: #ff0000;"><strong>中间证书由根证书或上一级中间证书签发，它可以再往下级签发数字证书。</strong></span>例如我们自己为某个域名申请了证书 My CA，那么对于三级证书链，签发过程是这样的：</p>
<blockquote><p><span style="color: #ff6600;"><strong>Root CA 签发 Intermediate CA， Intermediate CA 签发 My CA</strong></span></p></blockquote>
<p>这时我们就可以用My CA去给域名作数字认证了，那么验证的过程可想而知，就是签发的逆过程，这是通过证书链来完成的。<span id="more-929"></span></p>
<p>上面讲到的签发关系很像链式结构，所以被称作证书链。那么浏览器怎么知道My CA是由谁签发的呢？这是由证书里自带的一个字段：Issued By完成的，它会告诉浏览器，这张证书是由谁签发的，请核实签发者是否是可信的。</p>
<p>浏览器继续审核中间证书，看是否和可信证书库中的证书有匹配：</p>
<blockquote><p><span style="color: #ff6600;"><strong>如果有则认为My CA也是可信的；</strong></span></p>
<p><span style="color: #ff6600;"><strong>如果没有，继续往上找，直到根证书：</strong></span></p>
<blockquote><p><span style="color: #ff6600;"><strong>如果根证书是可信的，那么整条证书链就是可信的；</strong></span></p>
<p><span style="color: #ff6600;"><strong>如果根证书不可信，那么My CA将被认作是不可信的，浏览器就会发出警告。</strong></span></p></blockquote>
</blockquote>
<p>对于nginx，我们可以把完整的证书链配置在证书文件中，这样就可以一次性通过链式查找去找到证书链上的所有证书，并一一验证。注意不要只配置你自己的user-end证书，因为nginx对于证书链的长度变量ssl_verify_depth默认是1，所以当证书链长度比这个长时，会无法找到根证书，浏览器有可能错误地认为证书不可信。</p>
<p>例如我们生成的证书是my.crt，这时下载证书链文件（*.pem），并追加到my.crt后面：</p>
<pre class="brush:[bash]">cat ca.pem &gt;&gt; my.crt</pre>
<p>最后使用vi检查一下，保证每个证书的结尾标记（&#8212;&#8211;END CERTIFICATE&#8212;&#8211;）和开头标记（&#8212;&#8211;BEGIN CERTIFICATE&#8212;&#8211;）不在同一行，不然启动nginx会报错。</p>
<p>更多信息请参考：</p>
<blockquote><p><a href="http://en.wikipedia.org/wiki/Intermediate_certificate_authorities">http://en.wikipedia.org/wiki/Intermediate_certificate_authorities</a></p>
<p><a href="http://www.whichssl.com/intermediate_certificates.html">http://www.whichssl.com/intermediate_certificates.html</a></p>
<p><a href="http://wiki.nginx.org/HttpSslModule">http://wiki.nginx.org/HttpSslModule</a></p></blockquote>
]]></content:encoded>
			<wfw:commentRss>http://blog.hesey.net/2012/02/certificate-chain-and-configuration-of-nginx.html/feed</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>生活的可能性</title>
		<link>http://blog.hesey.net/2012/02/posibilities-of-life.html?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=posibilities-of-life</link>
		<comments>http://blog.hesey.net/2012/02/posibilities-of-life.html#comments</comments>
		<pubDate>Mon, 06 Feb 2012 12:45:31 +0000</pubDate>
		<dc:creator>Hesey</dc:creator>
				<category><![CDATA[思考]]></category>

		<guid isPermaLink="false">http://blog.hesey.net/?p=922</guid>
		<description><![CDATA[我时常想起这么个问题：如果当初作了另一个选择，那么恐怕不会是现在的生活吧。 想想就觉得很奇妙，不同的选择，就把人引向不同的道路，有时候一不小心，又殊途同归。记得以前有个笔友在信里说，如果没有遇见彼此，也许生活是完全不同的另一番模样。 和大多数人一样，我也是个不喜欢太多变化的人。本质上人都害怕未知，害怕触碰甚至窥视自己不知道的领域。 但自己的内心又分明躁动不安，我经常反复问自己的一个问题是： 我究竟想要什么？ 在不同的时间里，这个问题总有不同的答案。在时间的长河里，我不断拷问自己。我知道，这个问题没有答案，也不会有终结。正因如此，才需要时不时地提醒一下自己： 现在做的，真的是自己想做的吗？ 现在争取的，真的是我想要的吗？ 我见过很多人不知道自己究竟要什么，空有一腔激情，却不知道该在哪里释放。有时候会和朋友说，想清楚这真的是你想要的吗，如果真的是，那就坚持去做，什么都别想。问题是，“真的是”这个决断，不是那么轻易可以做出的，它包含着思考、选择，最重要的，还有舍弃，谁都不喜欢舍弃些什么，是吧？ 我一直相信，人在年轻的时候，是需要去迎接更多变化的。 因为年轻，所以输得起； 因为年轻，所以存在着更多的可能性。 如果你不知道自己该做什么，那就去做能让自己兴奋的事情，如果没有，那就去做让自己感到害怕的事。 曾看到过一段话，深以为然： 每次在做选择的时候，如果其中一个是保险而且安逸的，另一个需要冒险并且让你感到不安，那么后者会让你学到最多东西，并且得到最大的成长。 未知让人恐惧，但可能性就在这未知之中。 人们常说学会抓住机遇，以前我一直以为，机遇就是机会，就是能让自己进步的各种机会。现在觉得，可能年轻的时候，机遇更宝贵的地方在于，它能让你去体验更多可能性，更多种生活方式。当你看过越多，你便会有比较，有所比较，便有思考，有些事情也就想明白了。 当然，年纪大了，就需要考虑很多现实因素，所以，趁着年轻，好好体验生活吧，有些事情你注定要去做，但现在就要做准备，找到你真正想要的。 这是人一生的思辨。]]></description>
			<content:encoded><![CDATA[<p>我时常想起这么个问题：如果当初作了另一个选择，那么恐怕不会是现在的生活吧。</p>
<p>想想就觉得很奇妙，不同的选择，就把人引向不同的道路，有时候一不小心，又殊途同归。记得以前有个笔友在信里说，如果没有遇见彼此，也许生活是完全不同的另一番模样。</p>
<p>和大多数人一样，我也是个不喜欢太多变化的人。本质上人都害怕未知，害怕触碰甚至窥视自己不知道的领域。</p>
<p>但自己的内心又分明躁动不安，我经常反复问自己的一个问题是：</p>
<blockquote><p><span style="color: #ff6600;"><strong>我究竟想要什么？<span id="more-922"></span></strong></span></p></blockquote>
<p>在不同的时间里，这个问题总有不同的答案。在时间的长河里，我不断拷问自己。我知道，这个问题没有答案，也不会有终结。正因如此，才需要时不时地提醒一下自己：</p>
<blockquote><p><span style="color: #ff6600;"><strong>现在做的，真的是自己想做的吗？</strong></span></p>
<p><span style="color: #ff6600;"><strong>现在争取的，真的是我想要的吗？</strong></span></p></blockquote>
<p>我见过很多人不知道自己究竟要什么，空有一腔激情，却不知道该在哪里释放。有时候会和朋友说，<span style="color: #ff0000;"><strong>想清楚这真的是你想要的吗，如果真的是，那就坚持去做，什么都别想。</strong></span>问题是，“真的是”这个决断，不是那么轻易可以做出的，它包含着思考、选择，最重要的，还有<span style="color: #ff0000;"><strong>舍弃</strong></span>，谁都不喜欢舍弃些什么，是吧？</p>
<p>我一直相信，人在年轻的时候，是需要去迎接更多变化的。</p>
<p>因为年轻，所以输得起；</p>
<p>因为年轻，所以存在着更多的可能性。</p>
<p>如果你不知道自己该做什么，那就去做能让自己兴奋的事情，如果没有，那就去做让自己感到害怕的事。</p>
<p>曾看到过一段话，深以为然：</p>
<blockquote><p><span style="color: #ff6600;"><strong>每次在做选择的时候，如果其中一个是保险而且安逸的，另一个需要冒险并且让你感到不安，那么后者会让你学到最多东西，并且得到最大的成长。</strong></span></p></blockquote>
<p>未知让人恐惧，但可能性就在这未知之中。</p>
<p>人们常说学会抓住机遇，以前我一直以为，机遇就是机会，就是能让自己进步的各种机会。现在觉得，可能年轻的时候，机遇更宝贵的地方在于，它能让你去体验更多可能性，更多种生活方式。当你看过越多，你便会有比较，有所比较，便有思考，有些事情也就想明白了。</p>
<p>当然，年纪大了，就需要考虑很多现实因素，所以，趁着年轻，<a href="http://blog.hesey.net/2009/10/experience-but-not-only-enjoy-life.html"><span style="color: #008000;"><strong>好好体验生活吧</strong></span></a>，有些事情你注定要去做，但现在就要做准备，找到你真正想要的。</p>
<p><span style="color: #ff0000;"><strong>这是人一生的思辨。</strong></span></p>
]]></content:encoded>
			<wfw:commentRss>http://blog.hesey.net/2012/02/posibilities-of-life.html/feed</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>回首2011</title>
		<link>http://blog.hesey.net/2011/12/review-my-2011.html?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=review-my-2011</link>
		<comments>http://blog.hesey.net/2011/12/review-my-2011.html#comments</comments>
		<pubDate>Sat, 31 Dec 2011 14:08:54 +0000</pubDate>
		<dc:creator>Hesey</dc:creator>
				<category><![CDATA[思考]]></category>

		<guid isPermaLink="false">http://blog.hesey.net/?p=884</guid>
		<description><![CDATA[如果要用一个词来形容2011的话，我想，就用“激动”好了。 这一年我从学校出来，由一个胆胆颤颤的学生，正式转变成一名工作者，经济上的收入虽然不高，但勉强可以做到自收自支，更多的是，开始把自己的能力发挥出来，能够做一些真正产生影响的东西，这是我一直以来都想做的事情。 这一年，很多在书上看到的技术产品，总算可以真的亲手去用，去体验，去借助我的代码直接触摸那些曾经让我觉得无比高耸的高峰。 这一年，我终于成为团队的一员，从小作坊的独立开发，慢慢适应协同开发、合作，不得不说，和一群相投的人一起做事，真的很开心！ Timeline 3月份到了学校以后，心态比较焦虑，关于实习的事情，一方面自己很想有机会能出去看看，能真的去做一些事情，另一方面，又唯恐自己还达不到要求，做得不好。那段时间一直上自习看书，和考研的兄弟们一起窝在考研自习室钻研。后来正巧朋友飘零发推说招实习生，周末匆匆忙忙准备了一份简历就投过去了，周一下午我还在自习室看分布式，大概四点二十意外接到了面试的电话，全是技术问题，自己觉得答得比较一般了，有两个问题没答得比较顺畅。 晚上七点半的时候，面试官，也就是我现在的老大，打来电话，恭喜我通过面试，正式“邀请”我去淘宝实习（当时觉得，这么一家大公司，对一个学生能用“邀请”这个词，真是很难得）。当时和女朋友坐在公交车上，她知道我当时有多开心。 然后就是完成在学校的任务了，师兄列了一些需要掌握的知识点出来，我就自己看书去学，这个过程中，眼界开阔了许多，也因为更有方向感，能够定下心来深入地去学习一番，这段时间，可以说自己的成长是飞速的。 7月15号终于完成了学校的实训项目，过了一周我就到了公司。 其实刚来的时候，真的很不习惯，首先就是自己会的东西，和现有的事情没法很好地接轨，简单地说，会的用不上，要做的都不会。那段时间真的很纠结，因为自己很想尽快上手去做一些有价值的事情，自己也主动找老大聊了几次，慢慢找到方向感。 后来就是做的第一个项目了，在这个项目中，真正接触到了很多以前只是在文字上见过的东西，例如JNI（为此我还复习了一点C/C++）、HSF、Tair、Hadoop等等，像分布式RPC、分布式缓存、并发一系列的东西，能够说自己用以前自己的理解去用起来，实现了一个高效的异步生产消费模型，充分发挥了机器的能力，把CPU、IO利用率都提升了很多，也成为后来处理海量数据的一个基础设施。 后面越来越熟悉现有的架构、框架，慢慢有些观念也改变了，比如业务和技术，比如对产品的理解，越来越希望能把自己的产品做得更好，思考什么才是更好，用什么样的技术去做。做的很多东西也上线了，真正的对用户产生了影响。 2011年一共写了13篇Blog，数量不多，但质量都还可以，明年希望能产出更多分享的东西。 2011年，成长良多，要感激很多人，身边的师兄们，网上的朋友们，也感谢2011年的自己，很快适应了新的环境，也总算做了一点事情，但还没到我对自己期望的程度。 2012年，又是一年，尽管看起来这一年对于很多公司来说将并不好过。我的愿望很简单：希望能做更多落地的事情，做一些实实在在的事情，2011年我给了自己一个希冀，希望2012年年底，能对自己有一个满意的答复。希望年底再总结的时候，能说今年干得还不错。 最后，新的一年，希望大家都有一个好的起点。 落后的，只要踏踏实实，很快会赶上； 领先的，想的也要更多一点。 总之，我们又有一整年的时间可以挥霍，只这一点便已让人感到无比幸福！]]></description>
			<content:encoded><![CDATA[<p>如果要用一个词来形容2011的话，我想，就用“<span style="color: #ff0000;"><strong>激动</strong></span>”好了。</p>
<p>这一年我从学校出来，由一个胆胆颤颤的学生，正式转变成一名工作者，经济上的收入虽然不高，但勉强可以做到自收自支，更多的是，开始把自己的能力发挥出来，能够做一些<span style="color: #ff0000;"><strong>真正产生影响的东西</strong></span>，<span style="color: #ff0000;"><strong>这是我一直以来都想做的事情</strong></span>。</p>
<p>这一年，很多在书上看到的技术产品，总算可以真的亲手去用，去体验，去借助我的代码直接触摸那些曾经让我觉得无比高耸的高峰。</p>
<p>这一年，我终于成为团队的一员，从小作坊的独立开发，慢慢适应协同开发、合作，不得不说，和一群相投的人一起做事，真的很开心！<span id="more-884"></span></p>
<p><span style="color: #ff6600;"><strong>Timeline</strong></span></p>
<p>3月份到了学校以后，心态比较焦虑，关于实习的事情，一方面自己很想有机会能出去看看，能真的去做一些事情，另一方面，又唯恐自己还达不到要求，做得不好。那段时间一直上自习看书，和考研的兄弟们一起窝在考研自习室钻研。后来正巧朋友飘零发推说招实习生，周末匆匆忙忙准备了一份简历就投过去了，周一下午我还在自习室看分布式，大概四点二十意外接到了面试的电话，全是技术问题，自己觉得答得比较一般了，有两个问题没答得比较顺畅。</p>
<p>晚上七点半的时候，面试官，也就是我现在的老大，打来电话，恭喜我通过面试，正式“邀请”我去淘宝实习（当时觉得，这么一家大公司，对一个学生能用“邀请”这个词，真是很难得）。当时和女朋友坐在公交车上，她知道我当时有多开心。</p>
<p>然后就是完成在学校的任务了，师兄列了一些需要掌握的知识点出来，我就自己看书去学，这个过程中，眼界开阔了许多，也因为更有方向感，能够定下心来深入地去学习一番，这段时间，可以说自己的成长是飞速的。</p>
<p>7月15号终于完成了学校的实训项目，过了一周我就到了公司。</p>
<p>其实刚来的时候，真的很不习惯，首先就是自己会的东西，和现有的事情没法很好地接轨，简单地说，会的用不上，要做的都不会。那段时间真的很纠结，因为自己很想尽快上手去做一些有价值的事情，自己也主动找老大聊了几次，慢慢找到方向感。</p>
<p>后来就是做的第一个项目了，在这个项目中，真正接触到了很多以前只是在文字上见过的东西，例如JNI（为此我还复习了一点C/C++）、HSF、Tair、Hadoop等等，像分布式RPC、分布式缓存、并发一系列的东西，能够说自己用以前自己的理解去用起来，实现了一个高效的异步生产消费模型，充分发挥了机器的能力，把CPU、IO利用率都提升了很多，也成为后来处理海量数据的一个基础设施。</p>
<p>后面越来越熟悉现有的架构、框架，慢慢有些观念也改变了，比如业务和技术，比如对产品的理解，越来越希望能把自己的产品做得更好，思考什么才是更好，用什么样的技术去做。<span style="color: #ff0000;"><strong>做的很多东西也上线了，真正的对用户产生了影响。</strong></span></p>
<p>2011年一共写了13篇Blog，<span style="color: #ff0000;"><strong>数量不多，但质量都还可以，明年希望能产出更多分享的东西。</strong></span></p>
<p>2011年，成长良多，要感激很多人，身边的师兄们，网上的朋友们，也感谢2011年的自己，很快适应了新的环境，也总算做了一点事情，但还没到我对自己期望的程度。</p>
<p>2012年，又是一年，尽管看起来这一年对于很多公司来说将并不好过。我的愿望很简单：<span style="color: #ff0000;"><strong>希望能做更多落地的事情，做一些实实在在的事情</strong></span>，2011年我给了自己一个希冀，希望2012年年底，能对自己有一个满意的答复。希望年底再总结的时候，能说今年干得还不错。</p>
<p>最后，<span style="color: #ff0000;"><strong>新的一年，希望大家都有一个好的起点。</strong></span></p>
<p>落后的，只要踏踏实实，很快会赶上；</p>
<p>领先的，想的也要更多一点。</p>
<p>总之，我们又有一整年的时间可以挥霍，只这一点便已让人感到无比幸福！</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.hesey.net/2011/12/review-my-2011.html/feed</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>本机搭建Hadoop伪分布式模式</title>
		<link>http://blog.hesey.net/2011/12/build-pseudo-distributed-mode-on-localhost.html?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=build-pseudo-distributed-mode-on-localhost</link>
		<comments>http://blog.hesey.net/2011/12/build-pseudo-distributed-mode-on-localhost.html#comments</comments>
		<pubDate>Thu, 29 Dec 2011 12:34:40 +0000</pubDate>
		<dc:creator>Hesey</dc:creator>
				<category><![CDATA[分布式]]></category>
		<category><![CDATA[技术]]></category>

		<guid isPermaLink="false">http://blog.hesey.net/?p=878</guid>
		<description><![CDATA[Hadoop运行时有三种模式： 单机模式 伪分布式模式 完全分布式模式 前两种可以在单机运行，最后一种用于真实的集群环境，通常用在生产环境上。我们可以搭建本地的伪分布式模式来模拟分布式环境的执行。 步骤如下： 1、去http://www.apache.org/dyn/closer.cgi/hadoop/core/下载Hadoop 2、编辑conf/hadoop-env.sh，将 # export JAVA_HOME=/usr/lib/j2sdk1.5-sun 这行改为 export JAVA_HOME=/usr/local/jdk1.6.0_30 路径是JDK安装的路径，可以在Shell用which java查看，注意是JDK不是JRE噢~ 3、解压后，编辑conf目录下的文件： 给core-site.xml添加配置： &#60;property&#62; &#60;name&#62;fs.default.name&#60;/name&#62; &#60;value&#62;hdfs://127.0.0.1:9000/&#60;/value&#62; &#60;/property&#62; &#60;property&#62; &#60;name&#62;hadoop.tmp.dir&#60;/name&#62; &#60;!-- 配置工作的临时目录 --&#62; &#60;value&#62;/home/hesey/tmp/hadoop-hesey&#60;/value&#62; &#60;/property&#62; 给mapred-site.xml添加配置： &#60;property&#62; &#60;name&#62;mapred.job.tracker&#60;/name&#62; &#60;value&#62;127.0.0.1:9001&#60;/value&#62; &#60;/property&#62; 给hdfs-site.xml添加配置： &#60;property&#62; &#60;name&#62;dfs.replication&#60;/name&#62; &#60;value&#62;1&#60;/value&#62; &#60;/property&#62; 4、在Hadoop目录下执行： bin/hadoop namenode -format 格式化NameNode 5、在Hadoop目录下执行： bin/start-all.sh 启动所有组件 6、Shell下执行jps命令，可以看到： 3919 DataNode 4119 SecondaryNameNode 3740 NameNode 4427 [...]]]></description>
			<content:encoded><![CDATA[<p>Hadoop运行时有三种模式：</p>
<blockquote><p>单机模式</p>
<p>伪分布式模式</p>
<p>完全分布式模式</p></blockquote>
<p>前两种可以在单机运行，最后一种用于真实的集群环境，通常用在生产环境上。我们可以搭建本地的伪分布式模式来模拟分布式环境的执行。<span id="more-878"></span></p>
<p>步骤如下：</p>
<p>1、去<a href="http://www.apache.org/dyn/closer.cgi/hadoop/core/">http://www.apache.org/dyn/closer.cgi/hadoop/core/</a>下载Hadoop</p>
<p>2、编辑conf/hadoop-env.sh，将</p>
<pre class="brush:[bash]"># export JAVA_HOME=/usr/lib/j2sdk1.5-sun</pre>
<p>这行改为</p>
<pre class="brush:[bash]">export JAVA_HOME=/usr/local/jdk1.6.0_30</pre>
<p>路径是JDK安装的路径，可以在Shell用which java查看，注意是JDK不是JRE噢~</p>
<p>3、解压后，编辑conf目录下的文件：</p>
<p>给core-site.xml添加配置：</p>
<pre class="brush:[xml]">&lt;property&gt;
&lt;name&gt;fs.default.name&lt;/name&gt;
&lt;value&gt;hdfs://127.0.0.1:9000/&lt;/value&gt;
&lt;/property&gt;

&lt;property&gt;
&lt;name&gt;hadoop.tmp.dir&lt;/name&gt;
&lt;!-- 配置工作的临时目录 --&gt;
&lt;value&gt;/home/hesey/tmp/hadoop-hesey&lt;/value&gt;
&lt;/property&gt;</pre>
<p>给mapred-site.xml添加配置：</p>
<pre class="brush:[xml]">&lt;property&gt;
&lt;name&gt;mapred.job.tracker&lt;/name&gt;
&lt;value&gt;127.0.0.1:9001&lt;/value&gt;
&lt;/property&gt;</pre>
<p>给hdfs-site.xml添加配置：</p>
<pre class="brush:[xml]">&lt;property&gt;
&lt;name&gt;dfs.replication&lt;/name&gt;
&lt;value&gt;1&lt;/value&gt;
&lt;/property&gt;</pre>
<p>4、在Hadoop目录下执行：</p>
<pre class="brush:[bash]">bin/hadoop namenode -format</pre>
<p>格式化NameNode</p>
<p>5、在Hadoop目录下执行：</p>
<pre class="brush:[bash]">bin/start-all.sh</pre>
<p>启动所有组件</p>
<p>6、Shell下执行jps命令，可以看到：</p>
<p>3919 DataNode<br />
4119 SecondaryNameNode<br />
3740 NameNode<br />
4427 Jps<br />
4365 TaskTracker<br />
4187 JobTracker</p>
<p>这个时候就可以跑Job啦，如果有错误可以去logs目录下面看日志</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.hesey.net/2011/12/build-pseudo-distributed-mode-on-localhost.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Redis容灾策略</title>
		<link>http://blog.hesey.net/2011/11/failover-of-redis.html?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=failover-of-redis</link>
		<comments>http://blog.hesey.net/2011/11/failover-of-redis.html#comments</comments>
		<pubDate>Wed, 09 Nov 2011 13:54:25 +0000</pubDate>
		<dc:creator>Hesey</dc:creator>
				<category><![CDATA[技术]]></category>
		<category><![CDATA[数据库]]></category>

		<guid isPermaLink="false">http://blog.hesey.net/?p=872</guid>
		<description><![CDATA[Redis利用内存发挥的高性能读写在很多场景下大有所为，但是Redis本身毕竟还是一个单机数据库，如果系统对其属于强依赖，那么还是必须做好必要的容灾，针对这个问题，有以下几种策略： 一、M/S切换 由于Redis是单机数据库，所以针对MySQL的一些容灾方案也能顺利适用，例如当Redis意外宕机，可以将请求马上切到备库，同时快速恢复数据。 二、AOF Redis有两种持久化的方式，分别是SnapShotting和Append-Only File，其原理和特性可以参考《对redis数据持久化的一些想法》一文，总得来说，快照对性能影响更小，也只会备份需要的数据，但两次快照中间的数据是没法保证一定会持久化的。 相比之下AOF的粒度更细，持久化效果更好，类似于MySQL的BinLog，缺点是会损失一部分性能，而且会记录不必要的日志，这一点当系统运行时间很长时会特别突出，也许恢复所有数据本来只要1个小时，却可能要花100甚至1000小时去搞。 三、读取数据源直接恢复 这个方案是和业务场景相关的，由于之前某个项目中Redis起到了存放索引的作用，所以利用MySQL的数据可以容易地重新建立Redis的所有内容，这里可以临时搞一个Trigger，不断读MySQL写Redis，利用MySQL的顺序读和Redis高TPS的特性，实践中，可以在几分钟内重建上千万的数据量。 目前Redis在功能上通常仍用于Cache，如果需要保证HA的持久化存储，请考虑具体场景，此外也可以考虑是否可以使用原生分布式的memcached或升级硬件（如SSD、Fusion-IO）增强原有DB的性能。]]></description>
			<content:encoded><![CDATA[<p>Redis利用内存发挥的高性能读写在很多场景下大有所为，但是Redis本身毕竟还是一个单机数据库，如果系统对其属于强依赖，那么还是必须做好必要的容灾，针对这个问题，有以下几种策略：</p>
<h1>一、M/S切换</h1>
<blockquote><p>由于Redis是单机数据库，所以针对MySQL的一些容灾方案也能顺利适用，例如当Redis意外宕机，可以将请求马上切到备库，同时快速恢复数据。</p></blockquote>
<h1>二、AOF</h1>
<blockquote><p>Redis有两种持久化的方式，分别是SnapShotting和Append-Only File，其原理和特性可以参考《<a href="http://www.yiihsia.com/2011/04/%E5%AF%B9redis%E6%95%B0%E6%8D%AE%E6%8C%81%E4%B9%85%E5%8C%96%E7%9A%84%E4%B8%80%E4%BA%9B%E6%83%B3%E6%B3%95/"><span style="color: #008000;"><strong>对redis数据持久化的一些想法</strong></span></a>》一文，总得来说，快照对性能影响更小，也只会备份需要的数据，但两次快照中间的数据是没法保证一定会持久化的。</p>
<p>相比之下AOF的粒度更细，持久化效果更好，类似于MySQL的BinLog，缺点是会损失一部分性能，而且会记录不必要的日志，这一点当系统运行时间很长时会特别突出，也许恢复所有数据本来只要1个小时，却可能要花100甚至1000小时去搞。</p></blockquote>
<h1>三、读取数据源直接恢复</h1>
<blockquote><p>这个方案是和业务场景相关的，由于之前某个项目中Redis起到了存放索引的作用，所以利用MySQL的数据可以容易地重新建立Redis的所有内容，这里可以临时搞一个Trigger，不断读MySQL写Redis，利用MySQL的顺序读和Redis高TPS的特性，实践中，可以在几分钟内重建上千万的数据量。</p></blockquote>
<p>目前Redis在功能上通常仍用于Cache，如果需要保证HA的持久化存储，请考虑具体场景，此外也可以考虑是否可以使用原生分布式的memcached或升级硬件（如SSD、Fusion-IO）增强原有DB的性能。</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.hesey.net/2011/11/failover-of-redis.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>用AtomicStampedReference解决ABA问题</title>
		<link>http://blog.hesey.net/2011/09/resolve-aba-by-atomicstampedreference.html?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=resolve-aba-by-atomicstampedreference</link>
		<comments>http://blog.hesey.net/2011/09/resolve-aba-by-atomicstampedreference.html#comments</comments>
		<pubDate>Wed, 28 Sep 2011 02:12:42 +0000</pubDate>
		<dc:creator>Hesey</dc:creator>
				<category><![CDATA[Java]]></category>
		<category><![CDATA[技术]]></category>

		<guid isPermaLink="false">http://blog.hesey.net/?p=859</guid>
		<description><![CDATA[在运用CAS做Lock-Free操作中有一个经典的ABA问题： 线程1准备用CAS将变量的值由A替换为B，在此之前，线程2将变量的值由A替换为C，又由C替换为A，然后线程1执行CAS时发现变量的值仍然为A，所以CAS成功。但实际上这时的现场已经和最初不同了，尽管CAS成功，但可能存在潜藏的问题，例如下面的例子： 现有一个用单向链表实现的堆栈，栈顶为A，这时线程T1已经知道A.next为B，然后希望用CAS将栈顶替换为B： head.compareAndSet(A,B); 在T1执行上面这条指令之前，线程T2介入，将A、B出栈，再pushD、C、A，此时堆栈结构如下图，而对象B此时处于游离状态： 此时轮到线程T1执行CAS操作，检测发现栈顶仍为A，所以CAS成功，栈顶变为B，但实际上B.next为null，所以此时的情况变为： 其中堆栈中只有B一个元素，C和D组成的链表不再存在于堆栈中，平白无故就把C、D丢掉了。 以上就是由于ABA问题带来的隐患，各种乐观锁的实现中通常都会用版本戳version来对记录或对象标记，避免并发操作带来的问题，在Java中，AtomicStampedReference&#60;E&#62;也实现了这个作用，它通过包装[E,Integer]的元组来对对象标记版本戳stamp，从而避免ABA问题，例如下面的代码分别用AtomicInteger和AtomicStampedReference来对初始值为100的原子整型变量进行更新，AtomicInteger会成功执行CAS操作，而加上版本戳的AtomicStampedReference对于ABA问题会执行CAS失败： public class Test { private static AtomicInteger atomicInt = new AtomicInteger(100); private static AtomicStampedReference atomicStampedRef = new AtomicStampedReference(100, 0); public static void main(String[] args) throws InterruptedException { Thread intT1 = new Thread(new Runnable() { @Override public void run() { atomicInt.compareAndSet(100, 101); atomicInt.compareAndSet(101, 100); } }); Thread intT2 [...]]]></description>
			<content:encoded><![CDATA[<p>在运用CAS做Lock-Free操作中有一个经典的ABA问题：</p>
<p>线程1准备用CAS将变量的值由A替换为B，在此之前，线程2将变量的值由A替换为C，又由C替换为A，然后线程1执行CAS时发现变量的值仍然为A，所以CAS成功。但实际上这时的现场已经和最初不同了，尽管CAS成功，但可能存在潜藏的问题，例如下面的例子：</p>
<p><a href="http://blog.hesey.net/wp-content/uploads/2011/09/ABA-1.png"><img class="aligncenter size-full wp-image-860" title="ABA-1" src="http://blog.hesey.net/wp-content/uploads/2011/09/ABA-1.png" alt="" width="149" height="173" /></a></p>
<p><span id="more-859"></span>现有一个用单向链表实现的堆栈，栈顶为A，这时线程T1已经知道A.next为B，然后希望用CAS将栈顶替换为B：</p>
<p>head.compareAndSet(A,B);</p>
<p>在T1执行上面这条指令之前，线程T2介入，将A、B出栈，再pushD、C、A，此时堆栈结构如下图，而对象B此时处于游离状态：</p>
<p><a href="http://blog.hesey.net/wp-content/uploads/2011/09/ABA-2.png"><img class="aligncenter size-full wp-image-861" title="ABA-2" src="http://blog.hesey.net/wp-content/uploads/2011/09/ABA-2.png" alt="" width="149" height="256" /></a></p>
<p>此时轮到线程T1执行CAS操作，检测发现栈顶仍为A，所以CAS成功，栈顶变为B，但实际上B.next为null，所以此时的情况变为：</p>
<p><a href="http://blog.hesey.net/wp-content/uploads/2011/09/ABA-3.png"><img class="aligncenter size-full wp-image-862" title="ABA-3" src="http://blog.hesey.net/wp-content/uploads/2011/09/ABA-3.png" alt="" width="342" height="222" /></a></p>
<p>其中堆栈中只有B一个元素，C和D组成的链表不再存在于堆栈中，平白无故就把C、D丢掉了。</p>
<p>以上就是由于ABA问题带来的隐患，各种乐观锁的实现中通常都会用版本戳version来对记录或对象标记，避免并发操作带来的问题，在Java中，AtomicStampedReference&lt;E&gt;也实现了这个作用，它通过包装[E,Integer]的元组来对对象标记版本戳stamp，从而避免ABA问题，例如下面的代码分别用AtomicInteger和AtomicStampedReference来对初始值为100的原子整型变量进行更新，AtomicInteger会成功执行CAS操作，而加上版本戳的AtomicStampedReference对于ABA问题会执行CAS失败：</p>
<pre class="brush:[java]">public class Test {
    private static AtomicInteger atomicInt = new AtomicInteger(100);
    private static AtomicStampedReference atomicStampedRef = new AtomicStampedReference(100, 0);

    public static void main(String[] args) throws InterruptedException {
       Thread intT1 = new Thread(new Runnable() {
           @Override
           public void run() {
              atomicInt.compareAndSet(100, 101);
              atomicInt.compareAndSet(101, 100);
           }
       });

       Thread intT2 = new Thread(new Runnable() {
           @Override
           public void run() {
              try {
                  TimeUnit.SECONDS.sleep(1);
              } catch (InterruptedException e) {
              }
              boolean c3 = atomicInt.compareAndSet(100, 101);
              System.out.println(c3); // true
           }
       });

       intT1.start();
       intT2.start();
       intT1.join();
       intT2.join();

       Thread refT1 = new Thread(new Runnable() {
           @Override
           public void run()
              try {
                  TimeUnit.SECONDS.sleep(1);
              } catch (InterruptedException e) {
              }
              atomicStampedRef.compareAndSet(100, 101, atomicStampedRef.getStamp(), atomicStampedRef.getStamp() + 1);
              atomicStampedRef.compareAndSet(101, 100, atomicStampedRef.getStamp(), atomicStampedRef.getStamp() + 1);
           }
       });

       Thread refT2 = new Thread(new Runnable() {
           @Override
           public void run() {
              int stamp = atomicStampedRef.getStamp();
              try {
                  TimeUnit.SECONDS.sleep(2);
              } catch (InterruptedException e) {
              }
              boolean c3 = atomicStampedRef.compareAndSet(100, 101, stamp, stamp + 1);
              System.out.println(c3); // false
           }
       });

       refT1.start();
       refT2.start();
    }
}</pre>
]]></content:encoded>
			<wfw:commentRss>http://blog.hesey.net/2011/09/resolve-aba-by-atomicstampedreference.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>对象都是在堆上分配的吗？</title>
		<link>http://blog.hesey.net/2011/07/object-allocation-on-non-heap.html?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=object-allocation-on-non-heap</link>
		<comments>http://blog.hesey.net/2011/07/object-allocation-on-non-heap.html#comments</comments>
		<pubDate>Fri, 22 Jul 2011 02:34:03 +0000</pubDate>
		<dc:creator>Hesey</dc:creator>
				<category><![CDATA[Java]]></category>
		<category><![CDATA[技术]]></category>

		<guid isPermaLink="false">http://blog.hesey.net/?p=852</guid>
		<description><![CDATA[学习面向对象的过程中通常的说法是new出来的对象都是分配在堆上的，那么这个结论是不是如此绝对，有没有反例呢？ 在Java中，典型的对象不再堆上分配的情况有两种：TLAB和栈上分配。 一、为什么不在堆上分配 我们知道堆是由所有线程共享的，既然如此那它就是竞争资源，对于竞争资源，必须采取必要的同步，所以当使用new关键字在堆上分配对象时，是需要锁的。既然有锁，就必定存在锁带来的开销，而且由于是对整个堆加锁，相对而言锁的粒度还是比较大的，当对象频繁分配时，不免影响效率。 所以对于某些特殊情况，可以采取避免在堆上分配对象的办法，以提高对象创建和销毁的效率。 二、TLAB分配 JVM在内存新生代Eden Space中开辟了一小块区域，由线程私有，称作TLAB（Thread-local allocation buffer），默认设定为占用Eden Space的1%。在Java程序中很多对象都是小对象且用过即丢，它们不存在线程共享也适合被快速GC，所以对于小对象通常JVM会优先分配在TLAB上，并且TLAB上的分配由于是线程私有所以没有锁开销。因此在实践中分配多个小对象的效率通常比分配一个大对象的效率要高。 三、栈上分配 JVM在Server模式下的逃逸分析可以分析出某个对象是否永远只在某个方法、线程的范围内，并没有“逃逸”出这个范围，逃逸分析的一个结果就是对于某些未逃逸对象可以直接在栈上分配，由于该对象一定是局部的，所以栈上分配不会有问题。 四、对象非堆上分配的思想和启发 对象不在堆上分配主要的原因还是堆是共享的，在堆上分配有锁的开销。无论是TLAB还是栈都是线程私有的，私有即避免了竞争（当然也可能产生额外的问题例如可见性问题），这是典型的用空间换效率的做法。 在实践中，类似的做法还有很多，例如Hadoop中对于Map过程在节点的本地内存中处理，直到最后Reduce过程再合并数据。对于任务之间可以分解到不同线程、进程的情况，就可以采用类似的做法用空间换效率，对吞吐率的提升有很大帮助。]]></description>
			<content:encoded><![CDATA[<p>学习面向对象的过程中通常的说法是new出来的对象都是分配在堆上的，那么这个结论是不是如此绝对，有没有反例呢？</p>
<p>在Java中，典型的对象不再堆上分配的情况有两种：<span style="color: #ff0000;"><strong>TLAB</strong></span>和<span style="color: #ff0000;"><strong>栈上分配</strong></span>。<span id="more-852"></span></p>
<h1>一、为什么不在堆上分配</h1>
<blockquote><p>我们知道堆是由所有线程共享的，既然如此那它就是竞争资源，对于竞争资源，必须采取必要的同步，所以当使用new关键字在堆上分配对象时，是需要锁的。既然有锁，就必定存在锁带来的开销，而且由于是对整个堆加锁，相对而言锁的粒度还是比较大的，当对象频繁分配时，不免影响效率。</p>
<p>所以对于某些特殊情况，可以采取避免在堆上分配对象的办法，以提高对象创建和销毁的效率。</p></blockquote>
<h1>二、TLAB分配</h1>
<blockquote><p>JVM在内存新生代Eden Space中开辟了一小块区域，由线程私有，称作<a href="http://wikis.sun.com/display/MaxineVM/Threads#Threads-Threadlocalallocationbuffer%28TLAB%29" target="_blank"><span style="color: #008000;"><strong>TLAB</strong></span></a>（Thread-local allocation buffer），默认设定为占用Eden Space的1%。在Java程序中很多对象都是小对象且用过即丢，它们不存在线程共享也适合被快速GC，所以对于小对象通常JVM会优先分配在TLAB上，并且TLAB上的分配由于是线程私有所以没有锁开销。因此在实践中分配多个小对象的效率通常比分配一个大对象的效率要高。</p></blockquote>
<h1>三、栈上分配</h1>
<blockquote><p>JVM在Server模式下的逃逸分析可以分析出某个对象是否永远只在某个方法、线程的范围内，并没有“逃逸”出这个范围，逃逸分析的一个结果就是对于某些未逃逸对象可以直接在栈上分配，由于该对象一定是局部的，所以栈上分配不会有问题。</p></blockquote>
<h1>四、对象非堆上分配的思想和启发</h1>
<blockquote><p>对象不在堆上分配主要的原因还是堆是共享的，在堆上分配有锁的开销。无论是TLAB还是栈都是线程私有的，私有即避免了竞争（当然也可能产生额外的问题例如可见性问题），这是典型的用空间换效率的做法。</p>
<p>在实践中，类似的做法还有很多，例如Hadoop中对于Map过程在节点的本地内存中处理，直到最后Reduce过程再合并数据。对于任务之间可以分解到不同线程、进程的情况，就可以采用类似的做法用空间换效率，对吞吐率的提升有很大帮助。</p></blockquote>
]]></content:encoded>
			<wfw:commentRss>http://blog.hesey.net/2011/07/object-allocation-on-non-heap.html/feed</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>理解重排序</title>
		<link>http://blog.hesey.net/2011/07/reordering.html?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=reordering</link>
		<comments>http://blog.hesey.net/2011/07/reordering.html#comments</comments>
		<pubDate>Wed, 20 Jul 2011 06:28:05 +0000</pubDate>
		<dc:creator>Hesey</dc:creator>
				<category><![CDATA[Java]]></category>
		<category><![CDATA[并发]]></category>
		<category><![CDATA[技术]]></category>

		<guid isPermaLink="false">http://blog.hesey.net/?p=842</guid>
		<description><![CDATA[重排序通常是编译器或运行时环境为了优化程序性能而采取的对指令进行重新排序执行的一种手段。重排序分为两类：编译期重排序和运行期重排序，分别对应编译时和运行时环境。 在并发程序中，程序员会特别关注不同进程或线程之间的数据同步，特别是多个线程同时修改同一变量时，必须采取可靠的同步或其它措施保障数据被正确地修改，这里的一条重要原则是：不要假设指令执行的顺序，你无法预知不同线程之间的指令会以何种顺序执行。 但是在单线程程序中，通常我们容易假设指令是顺序执行的，否则可以想象程序会发生什么可怕的变化。理想的模型是：各种指令执行的顺序是唯一且有序的，这个顺序就是它们被编写在代码中的顺序，与处理器或其它因素无关，这种模型被称作顺序一致性模型，也是基于冯·诺依曼体系的模型。当然，这种假设本身是合理的，在实践中也鲜有异常发生，但事实上，没有哪个现代多处理器架构会采用这种模型，因为它是在是太低效了。而在编译优化和CPU流水线中，几乎都涉及到指令重排序。 一、编译期重排序 编译期重排序的典型就是通过调整指令顺序，在不改变程序语义的前提下，尽可能减少寄存器的读取、存储次数，充分复用寄存器的存储值。 假设第一条指令计算一个值赋给变量A并存放在寄存器中，第二条指令与A无关但需要占用寄存器（假设它将占用A所在的那个寄存器），第三条指令使用A的值且与第二条指令无关。那么如果按照顺序一致性模型，A在第一条指令执行过后被放入寄存器，在第二条指令执行时A不再存在，第三条指令执行时A重新被读入寄存器，而这个过程中，A的值没有发生变化。通常编译器都会交换第二和第三条指令的位置，这样第一条指令结束时A存在于寄存器中，接下来可以直接从寄存器中读取A的值，降低了重复读取的开销。 二、重排序对于流水线的意义 现代CPU几乎都采用流水线机制加快指令的处理速度，一般来说，一条指令需要若干个CPU时钟周期处理，而通过流水线并行执行，可以在同等的时钟周期内执行若干条指令，具体做法简单地说就是把指令分为不同的执行周期，例如读取、寻址、解析、执行等步骤，并放在不同的元件中处理，同时在执行单元EU中，功能单元被分为不同的元件，例如加法元件、乘法元件、加载元件、存储元件等，可以进一步实现不同的计算并行执行。 流水线架构决定了指令应该被并行执行，而不是在顺序化模型中所认为的那样。重排序有利于充分使用流水线，进而达到超标量的效果。 三、确保顺序性 尽管指令在执行时并不一定按照我们所编写的顺序执行，但毋庸置疑的是，在单线程环境下，指令执行的最终效果应当与其在顺序执行下的效果一致，否则这种优化便会失去意义。 通常无论是在编译期还是运行期进行的指令重排序，都会满足上面的原则。 四、Java存储模型中的重排序 在Java存储模型（Java Memory Model, JMM）中，重排序是十分重要的一节，特别是在并发编程中。JMM通过happens-before法则保证顺序执行语义，如果想要让执行操作B的线程观察到执行操作A的线程的结果，那么A和B就必须满足happens-before原则，否则，JVM可以对它们进行任意排序以提高程序性能。 volatile关键字可以保证变量的可见性，因为对volatile的操作都在Main Memory中，而Main Memory是被所有线程所共享的，这里的代价就是牺牲了性能，无法利用寄存器或Cache，因为它们都不是全局的，无法保证可见性，可能产生脏读。 volatile还有一个作用就是局部阻止重排序的发生，对volatile变量的操作指令都不会被重排序，因为如果重排序，又可能产生可见性问题。 在保证可见性方面，锁（包括显式锁、对象锁）以及对原子变量的读写都可以确保变量的可见性。但是实现方式略有不同，例如同步锁保证得到锁时从内存里重新读入数据刷新缓存，释放锁时将数据写回内存以保数据可见，而volatile变量干脆都是读写内存。]]></description>
			<content:encoded><![CDATA[<p>重排序通常是编译器或运行时环境为了优化程序性能而采取的对指令进行重新排序执行的一种手段。重排序分为两类：<span style="color: #ff0000;"><strong>编译期重排序</strong></span>和<span style="color: #ff0000;"><strong>运行期重排序</strong></span>，分别对应编译时和运行时环境。</p>
<p>在并发程序中，程序员会特别关注不同进程或线程之间的数据同步，特别是多个线程同时修改同一变量时，必须采取可靠的同步或其它措施保障数据被正确地修改，这里的一条重要原则是：<span style="color: #ff0000;"><strong>不要假设指令执行的顺序，你无法预知不同线程之间的指令会以何种顺序执行。</strong></span></p>
<p>但是在<span style="color: #ff0000;"><strong>单线程</strong></span>程序中，通常我们容易假设指令是<span style="color: #ff0000;"><strong>顺序执行</strong></span>的，否则可以想象程序会发生什么可怕的变化。理想的模型是：各种指令执行的顺序是唯一且有序的，这个顺序就是它们被编写在代码中的顺序，与处理器或其它因素无关，这种模型被称作<a href="http://en.wikipedia.org/wiki/Sequential_consistency" target="_blank"><span style="color: #008000;"><strong>顺序一致性模型</strong></span></a>，也是基于冯·诺依曼体系的模型。当然，这种假设本身是合理的，在实践中也鲜有异常发生，但事实上，<span style="color: #ff0000;"><strong>没有哪个现代多处理器架构会采用这种模型</strong></span>，因为它是在是太低效了。而在编译优化和CPU流水线中，几乎都涉及到指令重排序。<span id="more-842"></span></p>
<h1>一、编译期重排序</h1>
<blockquote><p>编译期重排序的典型就是通过调整指令顺序，在不改变程序语义的前提下，尽可能减少寄存器的读取、存储次数，充分复用寄存器的存储值。</p>
<p>假设第一条指令计算一个值赋给变量A并存放在寄存器中，第二条指令与A无关但需要占用寄存器（假设它将占用A所在的那个寄存器），第三条指令使用A的值且与第二条指令无关。那么如果按照顺序一致性模型，A在第一条指令执行过后被放入寄存器，在第二条指令执行时A不再存在，第三条指令执行时A重新被读入寄存器，而这个过程中，A的值没有发生变化。通常编译器都会交换第二和第三条指令的位置，这样第一条指令结束时A存在于寄存器中，接下来可以直接从寄存器中读取A的值，降低了重复读取的开销。</p></blockquote>
<h1>二、重排序对于流水线的意义</h1>
<blockquote><p>现代CPU几乎都采用流水线机制加快指令的处理速度，一般来说，一条指令需要若干个CPU时钟周期处理，而通过流水线并行执行，可以在同等的时钟周期内执行若干条指令，具体做法简单地说就是把指令分为不同的执行周期，例如读取、寻址、解析、执行等步骤，并放在不同的元件中处理，同时在执行单元EU中，功能单元被分为不同的元件，例如加法元件、乘法元件、加载元件、存储元件等，可以进一步实现不同的计算并行执行。</p>
<p>流水线架构决定了指令应该被并行执行，而不是在顺序化模型中所认为的那样。重排序有利于充分使用流水线，进而达到<a href="http://zh.wikipedia.org/wiki/%E8%B6%85%E7%B4%94%E9%87%8F" target="_blank"><span style="color: #008000;"><strong>超标量</strong></span></a>的效果。</p></blockquote>
<h1>三、确保顺序性</h1>
<blockquote><p>尽管指令在执行时并不一定按照我们所编写的顺序执行，但毋庸置疑的是，<span style="color: #ff0000;"><strong>在单线程环境下，指令执行的最终效果应当与其在顺序执行下的效果一致</strong></span>，否则这种优化便会失去意义。</p>
<p>通常无论是在编译期还是运行期进行的指令重排序，都会满足上面的原则。</p></blockquote>
<h1>四、Java存储模型中的重排序</h1>
<blockquote><p>在Java存储模型（Java Memory Model, JMM）中，重排序是十分重要的一节，特别是在并发编程中。JMM通过happens-before法则保证顺序执行语义，如果想要让执行操作B的线程观察到执行操作A的线程的结果，那么A和B就必须满足happens-before原则，否则，<span style="color: #ff0000;"><strong>JVM可以对它们进行任意排序以提高程序性能。</strong></span></p>
<p>volatile关键字可以<span style="color: #ff0000;"><strong>保证变量的可见性</strong></span>，因为对volatile的操作都在Main Memory中，而Main Memory是被所有线程所共享的，这里的代价就是牺牲了性能，无法利用寄存器或Cache，因为它们都不是全局的，无法保证可见性，可能产生脏读。</p>
<p>volatile还有一个作用就是<span style="color: #ff0000;"><strong>局部阻止重排序的发生</strong></span>，对volatile变量的操作指令都不会被重排序，因为如果重排序，又可能产生可见性问题。</p>
<p>在保证可见性方面，锁（包括显式锁、对象锁）以及对原子变量的读写都可以确保变量的可见性。但是实现方式略有不同，例如同步锁保证得到锁时从内存里重新读入数据刷新缓存，释放锁时将数据写回内存以保数据可见，而volatile变量干脆都是读写内存。</p></blockquote>
]]></content:encoded>
			<wfw:commentRss>http://blog.hesey.net/2011/07/reordering.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>

