<?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>℃冻番茄&#039;s Blog</title>
	<atom:link href="http://phpd.cn/feed" rel="self" type="application/rss+xml" />
	<link>http://phpd.cn</link>
	<description>记录平时工作、学习的过程！开始学习新东西，下一个目标Android开发......</description>
	<lastBuildDate>Thu, 10 May 2012 05:55:27 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.3.2</generator>
		<item>
		<title>基于sql实现redis主动缓存</title>
		<link>http://phpd.cn/archives/582</link>
		<comments>http://phpd.cn/archives/582#comments</comments>
		<pubDate>Thu, 10 May 2012 05:55:27 +0000</pubDate>
		<dc:creator>℃冻番茄</dc:creator>
				<category><![CDATA[图片/文字]]></category>

		<guid isPermaLink="false">http://phpd.cn/?p=582</guid>
		<description><![CDATA[主要功能点 主动缓存，无异常情况下只从redis中取数据，不走mysql 列表的分类、排序 缓存数据中区分正在进行、未开始、已结束 分析sql语句触发redis缓存系统中数据的更新 对关联数据进行同步更新（例 广告中的数据里面有商品信息，当商品发生改变时，触发把广告中的相关商品数据内容也进行同步更新） 针对单个表的缓存数据重建功能 原理 列表分页及排序主要依赖于redis 的sort 命令，通过配置来触发是否启用，是否同步关联数据等 数据存储结构 用redis 的 set (集合) 来保存ID号索引 用redis 的 &#8230; <a href="http://phpd.cn/archives/582" class="more-link">了解更多</a>]]></description>
			<content:encoded><![CDATA[<p><a href="http://phpd.cn/archives/582/rediscache" rel="attachment wp-att-583"><img class="alignnone size-medium wp-image-583" title="rediscache" src="http://phpd.cn/wp-content/uploads/2012/05/rediscache-700x494.jpg" alt="" width="700" /></a></p>
<p>主要功能点</p>
<p>主动缓存，无异常情况下只从redis中取数据，不走mysql<br />
列表的分类、排序<br />
缓存数据中区分正在进行、未开始、已结束<br />
分析sql语句触发redis缓存系统中数据的更新<br />
对关联数据进行同步更新（例 广告中的数据里面有商品信息，当商品发生改变时，触发把广告中的相关商品数据内容也进行同步更新）<br />
针对单个表的缓存数据重建功能</p>
<p><span id="more-582"></span></p>
<p>原理</p>
<p>列表分页及排序主要依赖于redis 的sort 命令，通过配置来触发是否启用，是否同步关联数据等</p>
<p>数据存储结构</p>
<p>用redis 的 set (集合) 来保存ID号索引<br />
用redis 的 hash(哈希) 来保存内容数据。Data为json结构化内容数据，同时hash内存入其它元素保存排序用的排序字段和内容<br />
例： hset(data, json_encode($data)) hset(price, 100) hset(sort, 1)</p>
<p>Key的组成</p>
<p>一个表中正在进行的主索引的key set结构 (taobao_item 表示数据表表名， cate 表示存储的是主索引)<br />
redisCache:taobao_item:cate</p>
<p>未开始ID号索引Key set结构<br />
redisCache:taobao_item: notstartids<br />
已结束ID号索引 key set结构<br />
redisCache:taobao_item:endids<br />
正在进行的分类索引Key set结构 cate_id表示分类的字段，最后面的1表示分类号，一个分类一个索引<br />
redisCache:taobao_item:cate:cate_id:1<br />
正在进行的内容Key (hash结构) 222表示是内容的ID号<br />
redisCache:taobao_item:content:222<br />
未开始的内容Key (hash结构)<br />
redisCache:taobao_item:notstart:22<br />
已结束的内容Key (hash结构)<br />
redisCache:taobao_item:end:22</p>
<p>排序命令</p>
<p>Sort redisCache:appmarket_app_commend:cate:cate_id:2</p>
<p>BY redisCache:appmarket_app_commend:content:*-&gt;sort:sort</p>
<p>GET redisCache:appmarket_app_commend:content:*-&gt;data</p>
<p>DESC</p>
<p>LIMIT 0 10</p>
<p>BY用于排序的Key，因为是hash结构，所以采用元素sort作为排序依据</p>
<p>GET 获取主内容，hash元素中的data 存储的是主内容的json结构化数据</p>
<p>LIMIT 分页，与mysql 的 limit作用方法一至</p>
<p>DESC 存在的话，为倒序，不存在的话为顺序</p>
<p>同步关联表数据</p>
<p>通过配置里的callback_sync_content 方法例如通过sql分析。监控到taobao_item发生update操作，因为设置了关联同步更新taobao_ad中的商品信息，所以触发，先从tabao_ad中通过回调配置中的get_id方法传入taoboa_item的主键ID号，得到tabao_ad中的主键ID，然后执行taobao_ad缓存的updateContent方法，从而实现关联更新taboa_ad中的相关数据</p>
<p>当数据过期时转存到已过期区域</p>
<p>基于用户触发<br />
hash key也就是内容key会有一个过期时间，当内容key过期，但索引key中是没法设定过期的<br />
当用户获取列表时，rediscache要对data进行处理(json_decode),同时，因为已过期的内容key不存在，通过sort命令获取的列表中存在false值，正常情况下是一个json结构化数据，<br />
如果存在false值，触发转存动作，对索引key进行同样的sort，但不传get参数，得到ID号索引数据，从而对比得到需要删除的ID号，先删除正在进行中区域的索引数据<br />
同时触发add动作，由rediscache再次存储数据，会自动根据源数据来进行入缓存作用</p>
<p>未开始数据转存正在进行中原理与正在进行的数据转存到已结束中</p>
<p>未开始的数据，添加时存入未开始内容key和未开始排序Key中，同时给未开始内容Key设定过期时间，也就是什么时候开始！<br />
使用sort获取未开始列表的方法，因为未开始索引里的ID号是不会过期的，但内容有过期时间，所以一对比就知道哪个ID的数据由未开始转入已开始<br />
在未开始索引key中删除，同时再次触发添加数据，由add方法自动判断数据存入的区域</p>
<p>支持多条件(AND)</p>
<p>因为每个条件都有一个索引key<br />
分析多条件，得到多个索引key<br />
使用sinterstore命令把多个索引key做交集并存入一个临时key中<br />
使用这个临时索引key取列表数据命令原型 sinterstore key key1 key2 key3</p>
<p>缓存数据的重建</p>
<p>因为在rediscache中触发更新比较频繁，所以有可能会出现缓存数据与源数据不一致的情况<br />
所以在管理后台做了缓存的重建功能<br />
有待优化注意事项</p>
<p>数据重建时会自动加锁，防止多个操作，同时rediscache中这个表的缓存失效，自动从回调中的源数据中取数据<br />
转存功能目前是基于用户触发，以后可以做异步，减少中间过程，优化用户获取列表的速度<br />
因为rediscache做主动缓存内存代价太高，同时为了更高的性能，所以希望在缓存系统中存在的数据都是有效数据，尽量把该下架的下架，该删除的删除 （下架和删除，数据都会在rediscache中清除）<br />
Redis做持久化，避免redis挂掉的情况下，需要对全部的数据做重建</p>
]]></content:encoded>
			<wfw:commentRss>http://phpd.cn/archives/582/feed</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>nginx配置PATH_INFO并rewrite掉中间的index.php</title>
		<link>http://phpd.cn/archives/576</link>
		<comments>http://phpd.cn/archives/576#comments</comments>
		<pubDate>Fri, 20 Apr 2012 06:28:06 +0000</pubDate>
		<dc:creator>℃冻番茄</dc:creator>
				<category><![CDATA[图片/文字]]></category>

		<guid isPermaLink="false">http://phpd.cn/?p=576</guid>
		<description><![CDATA[最近看博客统计数据，还是有很多搜索nginx 配置 PATH_INFO 和去掉中间的index.php的关键词，所以把目前自己在用的配置传一份到网上 server { listen 80; server_name ye55.dev; index index.html index.htm index.php default.html default.htm default.php; root /home/www/ye55/trunk/www/; if &#8230; <a href="http://phpd.cn/archives/576" class="more-link">了解更多</a>]]></description>
			<content:encoded><![CDATA[<p>最近看博客统计数据，还是有很多搜索nginx 配置 PATH_INFO 和去掉中间的index.php的关键词，所以把目前自己在用的配置传一份到网上</p>
<p>server<br />
{<br />
        listen       80;<br />
        server_name ye55.dev;<br />
        index index.html index.htm index.php default.html default.htm default.php;<br />
        root  /home/www/ye55/trunk/www/;</p>
<p>        if (!-f $request_filename) {<br />
                rewrite ^/(.*)$ /index.php/$1 last;<br />
        }<br />
        location ~ .*\.php(.*)$<br />
        {<br />
                fastcgi_pass  unix:/tmp/php-cgi.sock;<br />
                fastcgi_index index.php;<br />
                include fcgi.conf;<br />
                fastcgi_split_path_info ^(.+\.php)(.*)$;<br />
                include fastcgi_params;<br />
                fastcgi_param PATH_INFO $fastcgi_path_info;<br />
        }<br />
}</p>
]]></content:encoded>
			<wfw:commentRss>http://phpd.cn/archives/576/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>基于分析sql语句实现主动缓存</title>
		<link>http://phpd.cn/archives/568</link>
		<comments>http://phpd.cn/archives/568#comments</comments>
		<pubDate>Wed, 18 Apr 2012 12:41:52 +0000</pubDate>
		<dc:creator>℃冻番茄</dc:creator>
				<category><![CDATA[图片/文字]]></category>

		<guid isPermaLink="false">http://phpd.cn/?p=568</guid>
		<description><![CDATA[以前在上海的时候，做过一个用redis实现主动列表缓存的方案，但那时生产环境用的是1.0，所以那个列表缓存非常粗糙，只是满足需求而以，但运行得比较稳定，因此redis在实现上是可行的。 加上最近在看redis方面的资料，从头系统过了一遍redis,所以有了改进以前做的主动列表缓存的冲动，计划首先用在现在公司的一个新项目上，如果可行，就可以继续部署到老项目，改进性能。 要实现主动缓存，主要的问题在于以下几点 1. 怎么触发更新、删除、插入数据库时，同步更新redis里的数据 2. redis中数据的存储采用怎么的方式 3. 主动缓存中怎么排序和分类 4. redis意外停止服务的情况下，如果正常提供列表服务 5. 列表缓存应该工作在哪一层，dao ? service ? 6. redis中单个数据失效的情况下怎么剔除 7. 如果减少网络请求，尽量少的命令获取一个分布的数据 &#8230; <a href="http://phpd.cn/archives/568" class="more-link">了解更多</a>]]></description>
			<content:encoded><![CDATA[<p>以前在上海的时候，做过一个用redis实现主动列表缓存的方案，但那时生产环境用的是1.0，所以那个列表缓存非常粗糙，只是满足需求而以，但运行得比较稳定，因此redis在实现上是可行的。</p>
<p>加上最近在看redis方面的资料，从头系统过了一遍redis,所以有了改进以前做的主动列表缓存的冲动，计划首先用在现在公司的一个新项目上，如果可行，就可以继续部署到老项目，改进性能。</p>
<p>要实现主动缓存，主要的问题在于以下几点</p>
<p>1. 怎么触发更新、删除、插入数据库时，同步更新redis里的数据</p>
<p>2. redis中数据的存储采用怎么的方式</p>
<p>3. 主动缓存中怎么排序和分类</p>
<p>4. redis意外停止服务的情况下，如果正常提供列表服务</p>
<p>5. 列表缓存应该工作在哪一层，dao ? service ?</p>
<p>6. redis中单个数据失效的情况下怎么剔除</p>
<p>7. 如果减少网络请求，尽量少的命令获取一个分布的数据</p>
<p>目前方案正在设想中，先写下些东西，做下记录，后面再逐步完善！</p>
<p>一，解决mysql数据改变时触发实时更新redis数据，并最少改动现有代码</p>
<p>所以想在加一层 cache 层，使用cache层可以在Controller和service二个层中相互调用。cache中的数据可以来源于dao也可以来源于service层</p>
<p>解决第一个问题，我的想法是，直接在sql执行时，获得sql语句分析sql , 决定是否更新redis中的数据。</p>
<p>二， 做要实现redis主动缓存的相关配置，配置如下</p>
<pre class="brush: php; title: ; notranslate">
&lt;?php

return array(
			// db_user 表做数据缓存
			'db_user' =&gt; array(
							// 定义分类的字段，用于生成多个id索引set
							'cate' =&gt; array('group_id', 'vip'),
							// 设定排序所要用到的字段，数字
							'sort' =&gt; array('sort', 'create_time', 'last_time', 'login_num'),
							'callback' =&gt; array(
												'get' =&gt; array('common::getUserService()', 'getOne'), //回调类与方法，用于更新单个数据
												//用于当redis数据丢失的情况从mysql中还原数据，需要用于反射来注入数据，有待完善
												'getlist' =&gt; array('common::getUserService()', 'getList'),
											), 

						),
		);
</pre>
<p>主要的意义在配置中告诉程序怎么输出，如果redis失效的情况下，绕过redis缓存系统，直接按回调中的方法从mysql中输出数据。</p>
<p>redis怎么存储缓存数据：</p>
<p>1. 用一个或多个sets 存 id号索引数据。 比如配置中cate字段没有设置，就类型 listcache:db_user:ids</p>
<p>如果配置了cate字段 则出现一组 listcache:db_user:ids:cate:group_id:1 sets来分别存放对应的ID号</p>
<p>2. 另使用一个hash来存储内容，结构因sort配置而变<br />
hset list:cache:db_user:content:id:1 sort 1<br />
hset list:cache:db_user:content:id:1 create_time 122323123<br />
hset list:cache:db_user:content:id:1 last_time 1223231223<br />
hset list:cache:db_user:content:id:1 login_num 20<br />
hset list:cache:db_user:content:id:1 data 用户数据的序列化数据<br />
上面非data用于排序使用</p>
<p>最后获取列表使用 sort 命令<br />
例 sort list:cache:ids BY list:cache:db_user:content:id:*->sort DESC LIMIT 0 10<br />
上面命令用于获取排过序id号数据<br />
也可以直接获取最终的data数据 sort list:cache:ids BY list:cache:db_user:content:id:*->sort GET list:cache:db_user:content:id:*->data DESC LIMIT 0 10</p>
<p>就写到这吧，写得比较混乱。以后再整理些图出来，比较直观！</p>
]]></content:encoded>
			<wfw:commentRss>http://phpd.cn/archives/568/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>php路由实现rewrite改写url</title>
		<link>http://phpd.cn/archives/558</link>
		<comments>http://phpd.cn/archives/558#comments</comments>
		<pubDate>Wed, 11 Apr 2012 06:45:46 +0000</pubDate>
		<dc:creator>℃冻番茄</dc:creator>
				<category><![CDATA[我的作品]]></category>

		<guid isPermaLink="false">http://phpd.cn/?p=558</guid>
		<description><![CDATA[最近着手写一个网站，从框架到应用全部重新开发，很多代码属于重造轮子，但主要的目的就是练手，因为发现最近思维有些固化了，是得好好从头到底写个项目了！ 以前写的框架中路由功能非常有限，只是实现controller与action的选择，代码很简陋，没做过滤，安全性也有问题，所以就重写了一个路由。 框架出错提示 主要功能： controller与action的选择 php正则rewrite美化url 参数过滤 PATH_INFO与REQUEST_URI自动选择 代码如下]]></description>
			<content:encoded><![CDATA[<p>最近着手写一个网站，从框架到应用全部重新开发，很多代码属于重造轮子，但主要的目的就是练手，因为发现最近思维有些固化了，是得好好从头到底写个项目了！</p>
<p>以前写的框架中路由功能非常有限，只是实现controller与action的选择，代码很简陋，没做过滤，安全性也有问题，所以就重写了一个路由。</p>
<p><strong>框架出错提示</strong></p>
<p><a href="http://phpd.cn/archives/558/ye55" rel="attachment wp-att-562"><img class="alignnone size-medium wp-image-562" title="ye55" src="http://phpd.cn/wp-content/uploads/2012/04/ye55-700x334.png" alt="" width="700" height="334" /></a></p>
<p>主要功能：</p>
<ol>
<li>controller与action的选择</li>
<li>php正则rewrite美化url</li>
<li>参数过滤</li>
<li>PATH_INFO与REQUEST_URI自动选择</li>
</ol>
<p><span id="more-558"></span>代码如下</p>
<pre class="brush: php; title: ; notranslate">
class router {

	static public $controller = '';
	static public $action     = '';
	static public $params     = array();

	static public function get() {
		$config = array(
						'controller_default'	=&gt; 'index',
						'action_defalut'		=&gt; 'index',
						'controller_delimiter'	=&gt; '/',
						'action_delimiter'		=&gt; '/',
						'params_delimiter'		=&gt; '/',
						'params_kv_delimiter'	=&gt; '/',
						'extension_default'		=&gt; '.html',
					);
		if (false != $routerConfig = nash::config('main', 'router')) $config = array_merge($config, $routerConfig);

		//判断是否是GET形式
		if (isset($_GET['c']) || isset($_GET['a'])) {
			self::$controller = isset($_GET['c']) ? self::_filter($_GET['c']) : $config['controller_default'];
			self::$action = isset($_GET['a']) ? self::_filter($_GET['a']) : $config['action_defalut'];
			foreach ($_GET as $key =&gt; $value) {
				if (!in_array($key, array('c', 'a'))) self::$params[$key] = self::_filter($value);
			}
			return self::_output();
		}

		$queryString = isset($_SERVER['PATH_INFO']) ? self::_pathInfo() : self::_scriptName();
		$queryString = str_replace($config['extension_default'], '', $queryString);

		/********配置文件有rewrte规则时，对url进行改写**********/
		if (isset($config['rewrite']) &amp;&amp; is_array($config['rewrite'])) $queryString = self::_rewrite($queryString, $config['rewrite']);

		/*******获取取controller******/
		if (false === $pos = stripos($queryString, $config['controller_delimiter'])) {
			self::$controller = empty($queryString) ? $config['controller_default'] : self::_filter($queryString);
			self::$action = $config['action_defalut'];
			return self::_output();
		}
		self::$controller = self::_filter(substr($queryString, 0, $pos));
		$queryString = substr($queryString, $pos+1);

		/*******获取取action******/
		if (false === $pos = stripos($queryString, $config['action_delimiter'])) {
			self::$action = empty($queryString) ? $config['action_defalut'] : self::_filter($queryString);
			return self::_output();
		}
		self::$action = self::_filter(substr($queryString, 0, $pos));
		$queryString = substr($queryString, $pos+1);
		if (empty($queryString)) return self::_output();

		/*******获取取params参数******/
		if (false === $pos = stripos($queryString, $config['params_kv_delimiter'])) {
			return self::_output();
		}

		//当params_kv_delimiter与params_delimiter两个分界符相同时
		if ($config['params_kv_delimiter'] == $config['params_delimiter']) {
			$array = explode($config['params_delimiter'], $queryString);
			$count = count($array);
			for ($i = 0; $i &lt; $count; $i++) {
				if ($i%2 == 0) {
					self::$params[$array[$i]] = isset($array[$i+1]) &amp;&amp; !empty($array[$i+1]) ? self::_filter($array[$i+1]) : '';
				}
			}
			return self::_output();
		}

		//当params_kv_delimiter与params_delimiter两个分界符不同
		if (false === $pos = stripos($queryString, $config['params_delimiter'])) {
			$array = explode($config['params_kv_delimiter'], $queryString);
			!empty($array[1]) &amp;&amp; self::$params[$array[0]] = self::_filter($array[1]);
			return self::_output();
		}
		$array = explode($config['params_delimiter'], $queryString);
		foreach ($array as $value) {
			if (!empty($value)) {
				$arrayTmp = explode($config['params_kv_delimiter'], $value);
				!empty($arrayTmp[1]) &amp;&amp; self::$params[$arrayTmp[0]] = self::_filter($arrayTmp[1]);
			}
		}
		return self::_output();
	}

	/**
	 * php实现的正则rewrite url
	 */
	static private function _rewrite($string, $regular) {
		return preg_replace (array_keys($regular), array_values($regular), $string);
	}

	/**
	 * 对参数进行过滤，防止注入攻击
	 */
	static private function _filter($value) {
		return filter::vars($value, '#s_z');
	}

	static private function _pathInfo() {
		$queryString = $_SERVER['PATH_INFO'] == '/' ? $_SERVER['PATH_INFO'] = '' : $_SERVER['PATH_INFO'];
		substr($queryString, 0, 1) == '/' &amp;&amp; $queryString = substr($queryString, 1);
		return $queryString;
	}

	static private function _scriptName() {
		return substr(str_replace($_SERVER['SCRIPT_NAME'], '',$_SERVER['REQUEST_URI']),1);
	} 

	static private function _output() {
		return array(self::$controller, self::$action, self::$params);
	}
}
//$config = array('rewrite' =&gt; array('/user\/login\/(.*)/' =&gt; &quot;user/login/url_$1&quot;));
//var_dump(router::get());
</pre>
]]></content:encoded>
			<wfw:commentRss>http://phpd.cn/archives/558/feed</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>分享一个目前在使用的循环创建目录的函数</title>
		<link>http://phpd.cn/archives/555</link>
		<comments>http://phpd.cn/archives/555#comments</comments>
		<pubDate>Thu, 05 Apr 2012 06:36:42 +0000</pubDate>
		<dc:creator>℃冻番茄</dc:creator>
				<category><![CDATA[图片/文字]]></category>

		<guid isPermaLink="false">http://phpd.cn/?p=555</guid>
		<description><![CDATA[看起来很简单，递归 + &#038;&#038;运算符，习惯 !$a &#038;&#038; $a = true; 这样的语法的比较容易看明白]]></description>
			<content:encoded><![CDATA[<p>看起来很简单，递归 + &#038;&#038;运算符，习惯 !$a &#038;&#038; $a = true; 这样的语法的比较容易看明白</p>
<pre class="brush: php; title: ; notranslate">
static public function mkdir($path) {
	if (!is_dir($path) &amp;&amp; self::mkdir(dirname($path))) return mkdir($path);
	return true;
}
</pre>
]]></content:encoded>
			<wfw:commentRss>http://phpd.cn/archives/555/feed</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
		<item>
		<title>memcache一致性hash的php实现</title>
		<link>http://phpd.cn/archives/540</link>
		<comments>http://phpd.cn/archives/540#comments</comments>
		<pubDate>Tue, 27 Mar 2012 07:04:00 +0000</pubDate>
		<dc:creator>℃冻番茄</dc:creator>
				<category><![CDATA[我的作品]]></category>

		<guid isPermaLink="false">http://phpd.cn/?p=540</guid>
		<description><![CDATA[有段时间没认真写博客了，最近在看一些分布式方面的文章，所以就用php实现一致性hash来练练手，以前一般用的是最原始的hash取模做分布式，当生产过程中添加或删除一台memcache都会造成数据的全部失效，一致性hash就是为了解决这个问题，把失效数据降到最低，相关资料可以google一下！ php实现效率有一定的缺失，如果要高效率，还是写扩展比较好 经测试，5个memcache，每个memcache生成200个虚拟节点，set加get1000次，采用一致性哈希分布效率比原生单台速度相差5倍，效率有待优化 实现过程： memcache的配置 ip+端口+虚拟节点序列号 做hash,使用的是crc32,形成一个闭环。 对要操作的key进行crc32 二分法在虚拟节点环中查找最近的一个虚拟节点 从虚拟节点中提取真实的memcache ip和端口，做单例连接 代码如下：]]></description>
			<content:encoded><![CDATA[<p>有段时间没认真写博客了，最近在看一些分布式方面的文章，所以就用php实现一致性hash来练练手，以前一般用的是最原始的hash取模做分布式，当生产过程中添加或删除一台memcache都会造成数据的全部失效，一致性hash就是为了解决这个问题，把失效数据降到最低，相关资料可以google一下！</p>
<p>php实现效率有一定的缺失，如果要高效率，还是写扩展比较好<br />
经测试，5个memcache，每个memcache生成200个虚拟节点，set加get1000次，采用一致性哈希分布效率比原生单台速度相差5倍，效率有待优化<br />
实现过程：</p>
<ol>
<li>memcache的配置 ip+端口+虚拟节点序列号 做hash,使用的是crc32,形成一个闭环。</li>
<li>对要操作的key进行crc32</li>
<li>二分法在虚拟节点环中查找最近的一个虚拟节点</li>
<li>从虚拟节点中提取真实的memcache ip和端口，做单例连接</li>
</ol>
<p>代码如下：<span id="more-540"></span></p>
<pre class="brush: php; title: ; notranslate">
&lt;?php
/**
 * 一致性哈希memcache分布式，采用的是虚拟节点的方式解决分布均匀性问题,查找节点采用二分法快速查找
 * the last known user to change this file in the repository  &lt;$LastChangedBy: nash.xiong $&gt;
 * @author nash.xiong &lt;nash.xiong@gmail.com&gt;
 * @copyright Copyright &amp;copy; 2003-2012 phpd.cn
 * @license
 */
class memcacheHashMap {

	private $_node = array();
	private $_nodeData = array();
	private $_keyNode = 0;
	private $_memcache = null;

	//每个物理服务器生成虚拟节点个数 [注：节点数越多，cache分布的均匀性越好，同时set get操作时，也更耗资源，10台物理服务器，采用200较为合理]
	private $_virtualNodeNum = 200; 

	private function __construct() {
		/* 放入配置文件 */
		$config = array(
						'127.0.0.1:11211',
						'127.0.0.1:11212',
						'127.0.0.1:11213',
						'127.0.0.1:11214',
						'127.0.0.1:11215'
					);

		if (!$config) throw new Exception('Cache config NULL');
		foreach ($config as $key =&gt; $value) {
			for ($i = 0; $i &lt; $this-&gt;_virtualNodeNum; $i++) {
				$this-&gt;_node[sprintf(&quot;%u&quot;, crc32($value . '_' . $i))] = $value . '_' . $i;
			}
		}
		ksort($this-&gt;_node);
	}

	private function __clone(){}

	/**
	 * 单例，保证只有一个实例
	 */
	static public function getInstance() {
		static $memcacheObj = null;
		if (!is_object($memcacheObj)) {
			$memcacheObj = new self();
		}
		return $memcacheObj;
	}

	/**
	 * 根据key做一致性hash后连接到一台物理memcache服务器
	 * @param string $key
	 */
	private function _connectMemcache($key) {
		$this-&gt;_nodeData = array_keys($this-&gt;_node);
		$this-&gt;_keyNode = sprintf(&quot;%u&quot;, crc32($key));
		$nodeKey = $this-&gt;_findServerNode();
		//如果超出环，从头再用二分法查找一个最近的，然后环的头尾做判断，取最接近的节点
		if ($this-&gt;_keyNode &gt; end($this-&gt;_nodeData)) {
			$this-&gt;_keyNode -= end($this-&gt;_nodeData);
			$nodeKey2 = $this-&gt;_findServerNode();
			if (abs($nodeKey2 - $this-&gt;_keyNode) &lt; abs($nodeKey - $this-&gt;_keyNode))  $nodeKey = $nodeKey2;
		}
		var_dump($this-&gt;_node[$nodeKey]);
		list($config, $num) = explode('_', $this-&gt;_node[$nodeKey]);
		if (!$config) throw new Exception('Cache config Error');
		if (!isset($this-&gt;_memcache[$config])) {
			$this-&gt;_memcache[$config] = new Memcache;
			list($host, $port) = explode(':', $config);
			$this-&gt;_memcache[$config]-&gt;connect($host, $port);
		}
		return $this-&gt;_memcache[$config];
	}

	/**
	 * 采用二分法从虚拟memcache节点中查找最近的节点
	 * @param unknown_type $m
	 * @param unknown_type $b
	 */
	private function _findServerNode($m = 0, $b = 0) {
	    $total = count($this-&gt;_nodeData);
	    if ($total != 0 &amp;&amp; $b == 0) $b = $total - 1;
	    if ($m &lt; $b){
	        $avg = intval(($m+$b) / 2);
	        if ($this-&gt;_nodeData[$avg] == $this-&gt;_keyNode) return $this-&gt;_nodeData[$avg];
	        elseif ($this-&gt;_keyNode &lt; $this-&gt;_nodeData[$avg] &amp;&amp; ($avg-1 &gt;= 0)) return $this-&gt;_findServerNode($m, $avg-1);
	        else return $this-&gt;_findServerNode($avg+1, $b);
	    }
		if (abs($this-&gt;_nodeData[$b] - $this-&gt;_keyNode) &lt; abs($this-&gt;_nodeData[$m] - $this-&gt;_keyNode))  return $this-&gt;_nodeData[$b];
		else return $this-&gt;_nodeData[$m];
	}

	public function set($key, $value, $expire = 0) {
		return $this-&gt;_connectMemcache($key)-&gt;set($key, json_encode($value), 0, $expire);
	}

	public function add($key, $value, $expire = 0) {
		return $this-&gt;_connectMemcache($key)-&gt;add($key, json_encode($value), 0, $expire);
	}

	public function get($key) {
		return json_decode($this-&gt;_connectMemcache($key)-&gt;get($key), true);
	}

	public function delete($key) {
		return $this-&gt;_connectMemcache($key)-&gt;delete($key);
	}

}

$runData['BEGIN_TIME'] = microtime(true);
//测试一万次set加get
for($i=0;$i&lt;10000;$i++) {
	$key = md5(mt_rand());
	$b = memcacheHashMap::getInstance()-&gt;set($key, time(), 10);
}

var_dump(number_format(microtime(true) - $runData['BEGIN_TIME'],6));
$runData['BEGIN_TIME'] = microtime(true);
$m= new Memcache;
$m-&gt;connect('127.0.0.1', 11211);
for($i=0;$i&lt;10000;$i++) {
	$key = md5(mt_rand());
	$b = $m-&gt;set($key, time(), 0, 10);
}
var_dump(number_format(microtime(true) - $runData['BEGIN_TIME'],6));
//测试结果，采用一致性哈希分布效率比原生单台速度相差5倍
</pre>
]]></content:encoded>
			<wfw:commentRss>http://phpd.cn/archives/540/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>redis做共享锁机制</title>
		<link>http://phpd.cn/archives/536</link>
		<comments>http://phpd.cn/archives/536#comments</comments>
		<pubDate>Tue, 27 Mar 2012 02:05:40 +0000</pubDate>
		<dc:creator>℃冻番茄</dc:creator>
				<category><![CDATA[图片/文字]]></category>

		<guid isPermaLink="false">http://phpd.cn/?p=536</guid>
		<description><![CDATA[给出部分主代码 感谢HaKeem的提醒，程序发现一个bug,以前的代码会发生无法解锁的问题，放出修正后的代码 如果是redis2.2以上版本可以在lock逻辑中加上watch命令，锁定单个key]]></description>
			<content:encoded><![CDATA[<p>给出部分主代码</p>
<p>感谢HaKeem的提醒，程序发现一个bug,以前的代码会发生无法解锁的问题，放出修正后的代码<br />
如果是redis2.2以上版本可以在lock逻辑中加上watch命令，锁定单个key</p>
<pre class="brush: php; title: ; notranslate">
	/**
	 * 加锁  【新的加锁算法，采用redis做锁，redis失效情况下，返回 变量redisErrReturn 设定的值】
	 * @param string $key 唯一标识
	 * @param int $expire
	 * @return
	 */
	public function lock($key, $expire = 5) {
		try {
			list($key, $lockIdKey) = $this-&gt;_getLockKey($key);
			//抢锁，第一个线程抢到，并把过期时间写入锁
			if (Common::getQueue()-&gt;setnx($key, Common::getTime() + $expire)) return true;
			//没有抢到锁的线程，判断锁是否异常死锁，如果锁没有过期，返回false
			if (Common::getQueue()-&gt;get($key)  &gt; Common::getTime()) return false;
			//锁因异常死锁并过期的情况下，多个并发线程再次抢锁，getset命令到过期时间，如果未过期，表示锁已被其它线程抢得，返回false
			if (Common::getQueue()-&gt;getset($key, Common::getTime() + $expire) &gt; Common::getTime()) return false;
			Common::getQueue()-&gt;set($lockIdKey, $this-&gt;_lockId);
			return true;
		} catch (RedisException $e) {
			// 当捕捉到redis异常时，锁中断，返回变量redisErrReturn
			$this-&gt;_lockStatus = false;
			return $this-&gt;redisErrReturn;
		}
	}

	/**
	 * 给锁进行续期，延长锁的生效周期
	 * @param string $key
	 * @param int $expire       延时锁失效的秒数
	 * @param int $triggerTime  触发续期倒计时间
	 * @return
	 */
	public function updateExpire($key, $expire = 3, $triggerTime = 2) {
		if (!$this-&gt;_lockStatus) return true; //当redis失效，中断锁
		list($key, $lockIdKey) = $this-&gt;_getLockKey($key);
		$lockId = Common::getQueue()-&gt;get($lockIdKey);
		if ($this-&gt;_lockId === null || $lockId != $this-&gt;_lockId) return false;
		$time = Common::getQueue()-&gt;get($key);
		if ($time - $triggerTime &lt;= Common::getTime()) return Common::getQueue()-&gt;set($key, $time + $expire);
	}

	/**
	 * 解锁
	 * @param string $key
	 * @return
	 */
	public function unlock($key) {
		if (!$this-&gt;_lockStatus) return true; //当redis失效，中断解锁
		list($key, $lockIdKey) = $this-&gt;_getLockKey($key);
		$lockId = Common::getQueue()-&gt;get($lockIdKey);
		if ($this-&gt;_lockId === null || $lockId != $this-&gt;_lockId) return false;
		Common::getQueue()-&gt;del($key);
		Common::getQueue()-&gt;del($lockIdKey);
		return true;
	}

	/**
	 * 清除死锁产生的无用key 默认清除24小时以前的无用key
	 * @param int $cleanTime  默认24小时
	 */
	public function cleanLockKey($cleanTime = 86400) {
		if ($cleanTime &lt; 3600 || !$keys = Common::getQueue()-&gt;keys('PwLock:*')) return false;
		$i = 0;
		foreach ($keys as $value) {
			list($time, $lockId) = explode('|', Common::getQueue()-&gt;get($value));
			if (Common::getTime() - $time &gt; $cleanTime) {
				if (Common::getQueue()-&gt;del($value)) $i++;
			}
		}
		return $i;
	}

	private function _getLockKey($key) {
		return array('PwLock:' . $key, 'PwLock:' . $key . ':lockId');
	}
</pre>
]]></content:encoded>
			<wfw:commentRss>http://phpd.cn/archives/536/feed</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>linux tar分卷压缩</title>
		<link>http://phpd.cn/archives/525</link>
		<comments>http://phpd.cn/archives/525#comments</comments>
		<pubDate>Thu, 22 Mar 2012 11:00:33 +0000</pubDate>
		<dc:creator>℃冻番茄</dc:creator>
				<category><![CDATA[图片/文字]]></category>

		<guid isPermaLink="false">http://phpd.cn/?p=525</guid>
		<description><![CDATA[分卷压缩一个目录：如linux 在linux目录的上层目录： #tar cvf linux&#124;split -b 2m (已2M大小分卷压缩) #cat x* > linux.tar (合成分卷压缩包) 或者 #tar czvf linux.tar.gz linux/ #tar czvfp &#8230; <a href="http://phpd.cn/archives/525" class="more-link">了解更多</a>]]></description>
			<content:encoded><![CDATA[<p>分卷压缩一个目录：如linux<br />
在linux目录的上层目录：<br />
#tar cvf linux|split -b 2m (已2M大小分卷压缩)<br />
#cat x* > linux.tar (合成分卷压缩包)<br />
或者<br />
#tar czvf linux.tar.gz linux/<br />
#tar czvfp &#8211; linux.tar.gz | split -b 2m<br />
#cat x* > linux.tar.gz</p>
]]></content:encoded>
			<wfw:commentRss>http://phpd.cn/archives/525/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Mac OS Lion 下编译安装Nginx 1.0.12 + PHP 5.3.10 + Mysql 5.5.18 + Xdebug + PHPUnit ［转］</title>
		<link>http://phpd.cn/archives/531</link>
		<comments>http://phpd.cn/archives/531#comments</comments>
		<pubDate>Thu, 08 Mar 2012 11:10:43 +0000</pubDate>
		<dc:creator>℃冻番茄</dc:creator>
				<category><![CDATA[图片/文字]]></category>

		<guid isPermaLink="false">http://phpd.cn/?p=531</guid>
		<description><![CDATA[在mac下编译安装了最新版本namp的环境， 记录一下安装过程， 其实总体和linux下没有太多区别，另外port是个好东西。 mysql编译安装: 1. 建立mysql数据存储目录, 权限设置为mac os默认存在的_mysql权限： mkdir -p /var/mysql/data/ chown -R _mysql:_mysql /var/mysql/ 复制代码 2. 下载mysql， 我下的版本为mysql-5.5.18， 执行以下跨平台编译命令： &#8230; <a href="http://phpd.cn/archives/531" class="more-link">了解更多</a>]]></description>
			<content:encoded><![CDATA[<p>在mac下编译安装了最新版本namp的环境， 记录一下安装过程， 其实总体和linux下没有太多区别，另外port是个好东西。</p>
<p><strong>mysql编译<strong>安装</strong>:</strong></p>
<p>1. 建立mysql数据存储目录, 权限设置为mac os默认存在的_mysql权限：</p>
<div>
<pre>mkdir -p /var/mysql/data/
chown -R _mysql:_mysql /var/mysql/</pre>
<div><a>复制代码</a></div>
</div>
<p>2. 下载mysql， 我下的版本为mysql-5.5.18， 执行以下跨平台编译命令：</p>
<div>
<pre>cmake -DCMAKE_INSTALL_PREFIX=/usr/local/server/mysql -DMYSQL_DATADIR=/var/mysql/data -DWITH_INNOBASE_STORAGE_ENGINE=1 -DWITH_MEMORY_STORAGE_ENGINE=1 -DWITH_MYISAM_STORAGE_ENGINE=1 -DSYSCONFDIR=/etc/ -DWITH_SSL=yes -DDEFAULT_CHARSET=utf8 -DDEFAULT_COLLATION=utf8_general_ci -DWITH_READLINE=on</pre>
<div><a>复制代码</a></div>
</div>
<p>3. 编译， 安装：</p>
<div>
<pre>make
sudo make install</pre>
<div><a>复制代码</a></div>
</div>
<p>4. 改变mysql目录的权限：</p>
<div>
<pre>sudo chmod +w /usr/local/server/mysql
sudo chown -R _mysql:_mysql /usr/local/server/mysql</pre>
<div><a>复制代码</a></div>
</div>
<p>5. 创建库软链接：</p>
<div>
<pre>sudo ln -s /usr/local/server/mysql/lib/lib* /usr/lib/</pre>
</div>
<p>6.  copy配置文件到etc目录：</p>
<div>
<pre>cp /usr/local/server/mysql/support-files/my-large.cnf /etc/my.cnf
[client]
default-character-set = utf8

[mysqld]
character-set-server = utf8
default-storage-engine = MyISAM
basedir = /usr/local/server/mysql
datadir = /var/mysql/data
log-error = /var/mysql/mysql_error.log
pid-file = /var/mysql/mysql.pid</pre>
<div><a>复制代码</a></div>
</div>
<p>7. 建立初始数据表：</p>
<div>
<pre>sudo /usr/local/server/mysql/scripts/mysql_install_db --basedir=/usr/local/server/mysql --datadir=/var/mysql/data --user=_mysql</pre>
</div>
<p>8. 设置root密码:</p>
<div>
<pre>sudo /usr/local/server/mysql/bin/mysqladmin -u root password 'mysql'</pre>
</div>
<p>9.启动mysql:</p>
<div>
<pre>sudo /usr/local/server/mysql/bin/mysqld_safe --user=_mysql &amp;</pre>
</div>
<p>10. 测试安装是否成功：</p>
<div>
<pre>/usr/local/server/mysql/bin/mysql -u root -p -S /tmp/mysql.sock</pre>
</div>
<p>&nbsp;</p>
<p><strong>php编译安装:</strong></p>
<p>1. 编译安装php:</p>
<div>
<p>./configure &#8211;prefix=/usr/local/server/php &#8211;with-config-file-path=/usr/local/server/php/etc &#8211;enable-fpm &#8211;with-openssl &#8211;with-zlib &#8211;enable-mbstring &#8211;with-mcrypt &#8211;with-mysql=/usr/local/server/mysql &#8211;with-mysql-sock=/tmp/mysqld.sock &#8211;with-mysqli=/usr/local/server/mysql/bin/mysql_config &#8211;enable-sockets &#8211;without-iconv &#8211;with-curl=/opt/local/bin/curl</p>
<div><a>复制代码</a></div>
</div>
<p>2. 复制php.ini-development到编译时指定的php配制目录:</p>
<div>
<pre>sudo cp php.ini-development /usr/local/server/php/etc/php.ini</pre>
</div>
<p>3. 复制phpfpm的配置文件到其配制目录:</p>
<div>
<pre>sudo cp php-fpm.conf.default php-fpm.conf</pre>
</div>
<p>4. 重命名phpfpm执行文件为正常名称:</p>
<div>
<pre>sudo mv php-fpm.dSYM php-fpm</pre>
</div>
<p>5. 启动:</p>
<div>
<pre>sudo /usr/local/server/php/sbin/php-fpm</pre>
</div>
<p>6. 安装xdebug</p>
<p>1) 进入http://xdebug.org/find-binary.php网址， 输入phpinfo返回的html源码后其会自动生成安装步骤， 按照其方法编译安装， 最后变更php.ini配制文件指定xdebug.so扩展路径。</p>
<p>7. 安装phpunit</p>
<p>1) 升级pear.</p>
<div>
<pre>sudo /usr/local/server/php/bin/pear upgrade</pre>
</div>
<p>2) 安装phpunit.</p>
<div>
<pre>sudo ./pear config-set auto_discover 1
sudo ./pear install pear.phpunit.de/PHPUnit</pre>
<div><a>复制代码</a></div>
</div>
<p>3) 测试phpunit是否安装成功.</p>
<div>
<pre>/usr/local/server/php/bin/phpunit

#有输出帮助信息则为正常。</pre>
<div><a>复制代码</a></div>
</div>
<p><strong><br />
</strong></p>
<p><strong>nginx编译<strong>安装</strong>:</strong></p>
<p>1. 编译安装:</p>
<div>
<pre>./configure --user=_www --group=_www --prefix=/usr/local/server/nginx --with-http_stub_status_module --with-http_ssl_module --with-http_sub_module
make
sudo make install</pre>
<div><a>复制代码</a></div>
</div>
<p>2. 修改nginx配置：</p>
<div>
<pre>sudo vim /usr/local/server/nginx/conf/nginx.conf

#指定程序运行权限：
user _www _www;
#在http内添加一条server信息：
server {
    listen 80;
    server_name localhost;
    root /var/www;

    location ~ \.php$ {
        fastcgi_pass 127.0.0.1:9000;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $document_root/$fastcgi_script_name;
        include fastcgi_params;
    }
}</pre>
<div><a>复制代码</a></div>
</div>
<p>3.启动：</p>
<div>
<pre>sudo /usr/local/server/nginx/sbin/nginx

#没有任何返回则说明执行成功。</pre>
<div></div>
<div><a>复制代码</a></div>
</div>
<p>&nbsp;</p>
<p>参考文章:</p>
<p>lnap最新方案   http://www.yunwei8.com/nginx/</p>
]]></content:encoded>
			<wfw:commentRss>http://phpd.cn/archives/531/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>xcode4.3开启gcc/g++</title>
		<link>http://phpd.cn/archives/529</link>
		<comments>http://phpd.cn/archives/529#comments</comments>
		<pubDate>Mon, 05 Mar 2012 13:22:48 +0000</pubDate>
		<dc:creator>℃冻番茄</dc:creator>
				<category><![CDATA[图片/文字]]></category>

		<guid isPermaLink="false">http://phpd.cn/?p=529</guid>
		<description><![CDATA[真的坑爹，今天才开始玩MAC OX，装了个最新版本的10.7.3，只能装XCODE 4.3 这个月刚发行的版本。 安装时发现没有install过程，直接双击就进入开发环境了。而且装完后没有gcc 等各种编译工具，在TERMINAL下各种命令不识别，想装ruby的各种开发工具，都不行了。 查了半天才发现： Apple announced Xcode 4.3 for OSX Lion and 4.4 for OSX Mountain Lion &#8230; <a href="http://phpd.cn/archives/529" class="more-link">了解更多</a>]]></description>
			<content:encoded><![CDATA[<p>真的坑爹，今天才开始玩MAC OX，装了个最新版本的10.7.3，只能装XCODE 4.3 这个月刚发行的版本。<br /> 安装时发现没有install过程，直接双击就进入开发环境了。而且装完后没有gcc 等各种编译工具，在TERMINAL下各种命令不识别，想装ruby的各种开发工具，都不行了。</p>
<p>查了半天才发现：<br /> Apple announced Xcode 4.3 for OSX Lion and 4.4 for OSX Mountain Lion last week. The major difference is that Xcode no longer provide an installer which is good thing because you now could update Xcode with AppStore in the future, plus it is much easier to carry the development environment with you. However, there is a little problem with this new version of Xcode, is that all command line toolsets and compilers are not visible in terminal.</p>
<p>解决方案一：<br /> A simple fix is to update your PATH env:<br /> export PATH=/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin:/Applications/Xcode.app/Contents/Developer/usr/bin:$PATH<br /> Please be noted that clang does not reside in /Developer/usr/bin, it is now in /Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin<br /> Now you could access to gcc, g++, git or any toolsets bundled with Xcode. For your convenience, it is recommended to include it in your .bash_profile.<br /> 解决方案二：<br /> You can install these additional tools directly in Xcode :<br /> Preferences &gt; Downloads &gt; Command Line Tools &gt; Install</p>
]]></content:encoded>
			<wfw:commentRss>http://phpd.cn/archives/529/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>

