商品图片,平均200-500K,说大不大,说小不小,但量大且细碎,最早通过页面上传,全部保存在文件里,且不分目录,管理和索引都很慢,几乎无法备份,读取也很慢。

改进方案由大鱼设计,图片是保存在MySQL表里,每10万张图就换一张新表,操作语言是PHP,它解决了图片备份和缓存的问题。

经过一段运行时间后,我对效果并不满意,主要是速度还是有些慢,尤其是第一次加载的过程。这期间又负责主体商品数据迁移到MongoDB,大致研究了一下GridFS,并做了些测试,感觉这个比MySQL要靠谱,且MongoDB还有ShardingReplica Set支持,帮我解决了分布存储的问题,很是诱人,决定一试。

设计思路和需求整理:

  • 决不允许重复图片存在
  • 文件只有原始的需要保留,其他各尺寸和效果都可以由原图生成
  • 图片URL总是固定的,不管它出现在哪里
  • 缩略图生成规则也简单的体现在URL里,参考 Abusing Amazon images
  • 第一次请求,由图片处理程序生成静态文件,以后请求即直接定位到静态文件

多年前,郝培强同学送过我一本《Python语言入门》,但这么些年里只写过三两个小工具,按熟悉程度依然算新手。既然这个解决方案撞枪口上了,于是有改用Python实现的念头。我是这么想的,1.要简单;2.要快,不光运行快,还要写得快;3.最好是常驻型程序。貌似只有Python和Ruby符合,但Ruby我不熟悉,那就用Python好了。某个周六,花了一天时间,写了个雏形,支持图片读取并显示。又花了N天,添加管理和上传。

上传到github,命名为ImSto,将它作为我的第一个开源项目。

关于Python的Web框架,不得不絮叨几句,那真叫一个多,看了一些评论,也粗略研究了Pylons和CherryPy,很久以前学过一点点Django。但感觉似乎都有些复杂,这个需求也不复杂,决定干脆不用任何框架,自己动手丰衣足食算了。

所用到的组件:

  • MongoDB (GridFS): 这个不用说了,核心存储,初期环境用到了三台主机组成的 Replica Sets
  • Nginx: 解析URL并定位到静态文件,如果未生成则转向uWSGI程序去生成图片
  • Python + pymongo
  • ImageMagick: 用来生成图片缩略图,有两种调用方式:Api和命令行shell。Demo环境由于内存使用限制,用了shell方式。
  • uWSGI: 是用纯C写的WSGI服务器,支持多种语言(主要是Python)和Web Server(官方首推Cherokee和Nginx)。有点儿类似PHP-FPM。

项目地址: https://github.com/liut/imsto 只是完成了 TODO里的部分核心功能,代码结构也比较粗糙,待逐步重构。

演示地址: http://demo.imsto.org:81/ 稍后添加权限认证,所以,在这之前请随便添随便删,不要客气。

Update:
1. 上传的页面使用了 html5的一些特性,为了实现Ajax上传和进度显示,以及即时预览,暂时只支持Firefox3.5+。稍后再改进。
2. 请不要向我推荐用 Flash,谢谢。

Now that we have a handle on the concept of strings, let’s take a look at the power and flexibility of arrays. Arrays are known as compound data types; all that really means is that they are more complex in structure than simple strings and integers, which are also known as scalar data types. Imagine an array as an egg carton. It carries 12 individual compartments that can house one egg each, yet it travels around as one entity. The compartments can be broken off and made into single egg holders, or holders in any number combination. Additionally, these compartments are not limited to holding only eggs; they can hold rocks, or cookies, or match sticks. Of course, an analogy like this has its limitations—egg cartons cannot easily be expanded and they cannot hold other egg cartons, all of which, we will see, arrays are excellent at doing.

说完字符串的概念,现在再让我们来看看强大而又极具灵活性的数组。数组被称为复合数据类型,意思是说,它比简单的字符串或数字以及其他已知的数据类型要复杂一些。你可以把数组想像成一个存放鸡蛋的纸盒(市场里能看到有很多槽的那种,我们一般叫蛋托)。但是呢,盒里的每个鸡蛋可以分出很多更小的槽,而这些小小的槽可以拆除或者再放进更多的鸡蛋筐。此外,这些槽不仅能放鸡蛋,还能放面包、饼干、火腿肠什么的。当然,这个比喻其实并不恰当,因为真正的纸盒不能随意缩放,也不可能再在槽里装进去另一个纸盒。我之所以这么说,是想表达一个意思:数组真的很好很强大。

Let’s talk more precisely about arrays. Like egg cartons, arrays have compartments (elements) that hold data. The elements and their respective data always travel together (although you can have an empty element without data) and are known as key/value pairs. So, if we had an array of five elements, each containing a number in the range from 1 to 5, it would look like Table 5-1 (elements start their counting with 0).

我们来更详细的谈谈数组。前面说了,它像蛋盒,有很多可以放数据的槽(元素)。这些元素和其中的数据总是一一对应(虽然你也可以让元素空着不放数据),这就人们所说的键/值对。因此,如果我们有一个包含5个元素的数组,每个分别包含从数字1到5,就像表 5-1所示(元素是从0开始计数的)

Table 5-1. Visual representation of an array with numeric (indexed) keys

表 5-1 形象化的表示数字(作索引)的数组

键 0 1 2 3 4
值 1 2 3 4 5

Indexed Arrays
Arrays with numerical keys are known as indexed arrays. The keys can also be named with strings, if you prefer, creating what is known as an associative array, as you will see later in this chapter. Let’s consider an array called $myArray.

索引数组
用数字作为键名的数组一般叫索引数组(译者注:这时的键又叫下标)。当然只要你愿意,键也可以用字符串表示,那样的话,指的就是在本章稍后看到的关联数组。让我们考虑一个变量名为 $myArray 的数组。

Array variable names follow the same naming rules as regular PHP var- iables (see the section “Variables: Data Types, Loose Typing, and Scope” on page 9).

数组变量的命名同样遵循PHP变量命名约定(参见第9页 “变量:数据类型,弱类型,作用域” )

In PHP code, you can reference the elements of an array by their keys, surrounded by square brackets. If we want to take the value of the third element of the array—the contents being the number 3 in this case—and assign it to its own variable, the code would look like this:

在PHP语言中,你可以使用方括号括起来的键名来访问数组中的元素。如果我们要取得数组中第三个元素的值——在这个例子里它是数字3——并将它分配给一个要定义的变量,实现的代码应该是这样:


// 切记, 数组元素的键(即下标)从0开始算起
$singleValue = $myArray[2];
echo $singleValue ; // 会输出 3

Now, this assumes that we have already defined the array somewhere else in our code. There are two ways in which to create arrays; the first is a variation on the use of the square bracket syntax. Here is the code for creating an array with this method:

现在,假设我们已经在代码中提前定义了一个数组。有两个办法可以实现,第一个就是用方括号语法,下面是用这个方法的代码:


$myArray[0] = 1;
$myArray[1] = 2;
$myArray[2] = 3;
$myArray[3] = 4;
$myArray[] = 5;

This method assumes that we already know the key order and their values, as the key numbers here are hardcoded. Notice that the last line of code in the above example is not “forcing” the key number inside the square brackets; when this is done (not pro- viding the key number), the result is that the next available integer key will be assigned to that element for us.

此方法假定我们已经知道数组的键和对应的值,但这是种对键的硬编码。注意上面示例代码中最后一行并没有“强制”分配方括号内键的数值,这样做(就是不提供键的具体数值)的结果,使它为我们将分配下一个可用的键索引数值给此元素。

Creating an array in this fashion may or may not be what you want. The other method available is to use the array function with the key/value pairs passed together and separated by commas, like so:

用这种形式创建数组,或许有些啰嗦。另一种更常用方法是将一组用逗号分隔的键/值对传递给array函数。像这样:


$myArray = array(0 => 1, 1 => 2, 2 => 3, 3 => 4, 4 => 5);

This way of creating an array is much more condensed, yet it may be a little more difficult for humans to read.

这种形式更简单明了,但可能会稍稍不利于代码阅读。

If you want to create an empty array, just use the array function without any parameters:

如果你想创建一个空数组,只要写一个空参数的array函数即可:


$a = array();

The keys of any array have to be named uniquely, otherwise there would be confusion when referencing their contents. PHP won’t prevent you from using the same key multiple times in an assignment, but each identical key assigned will replace the one before it.
任何数组的键必须唯一,否则会在由键取内容时造成混乱。PHP不会禁止你为同一个键的内容反复赋值,但是每次赋值时都会覆盖同名键的之前内容。

Associative Arrays

关联数组

So far we have looked at indexed arrays, but as I mentioned before, the key portion of an array can also consist of character strings. The data values that we have looked at so far have also been numerical, but these too can be changed to string data, as shown in Table 5-2.

到目前为止,我们知道了索引数组,但就像我前面提到的,数组的键也可以用字符串来表示;同样的,每个键所对应的值不光可以是数字,当然也可以是字符串,如表 5-2 所示:

键	first	second	fname	initial	lname
值	1	2	Peter	B	MacIntyre

PostgreSQL 8.4及以上版,新增特性中包括支持 VARIADIC 函数, 是指函数支持不定数量的参数,只要靠后的参数的数据类型(除数组外)一致即可识别。在函数内部,这些一致类型的参数,会被当作一个数组来处理。但如果其中有一个类型和前后不一致,就无法支持。

这个特性可以让处理集合类的参数变得更灵活。

应用实例:商品表和标签(Tag)的多对多关系。存储过程如下:



CREATE OR REPLACE FUNCTION goods_tag_add(a_goods_id int, VARIADIC a_tags text[], OUT t_id INT8) 
RETURNS SETOF INT8 AS $$
DECLARE
	t_rec record;
	i int4;
BEGIN
	FOR i IN SELECT generate_subscripts(a_tags, 1) LOOP
		-- RAISE NOTICE 'tag: %', a_tags[i]; // debug
		
		SELECT id FROM goods_tag WHERE name = a_tags[i] INTO t_rec;
		IF FOUND THEN
			t_id := t_rec.id;
		ELSE
			INSERT INTO goods_tag(name)
			VALUES (a_tags[i]);
			t_id := CURRVAL('goods_tag_id_seq');
		END IF;
		
		IF NOT EXISTS(SELECT created FROM goods_tag_map 
			WHERE goods_id = a_goods_id AND tag_id = t_id) THEN
			INSERT INTO goods_tag_map(goods_id, tag_id)
			VALUES(a_goods_id, t_id);
		END IF;
		
		RETURN NEXT;

	END LOOP;

	RETURN;
	
END;
$$ LANGUAGE 'plpgsql';

调用此函数的PHP代码示例:


$goods_id = 1; // 目标(这里以商品为例)ID,整数类型
$tags = array('上衣', '夏季', '休闲'); // 不定数量的 tag 文字
$tag_ids = Da_Wrapper::getAll(DB_NS, 
		"SELECT goods_tag_add(?,".implode(',',str_split(str_repeat('?', count($tags)))).")", 
		array_merge(array($goods_id),$tags));
// Da_Wrapper 类是对PDO的封装,DB_NS 是连接串定义,SQL语句之所以这样写是不管 $tags元素有多少,都可以拼成参数传给存储过程

* PostgreSQL 约定了 函数参数最多为100个,如果确实超过这个数,可以分拆成多次调用。不过,多数应用里应该没这么多吧。

参考:
What is new in PostgreSQL 8.4
Waiting for 8.4 – variadic functions
在PostgreSQL中模拟MySQL的ORDER BY FIELD()

由于使用太久时间的 TortoiseSVN(因图标是个乌龟,常爱称为小乌龟), 习惯了在Windows资源管理器下的Merge(版本合并)操作。在换了Mac之后,只好使用终端(控制台)下的svn命令,有些简单操作没有太大问题,如:co(check out),up(update),ci(commit)等。但在 Merge操作还不太适应。每次merge都是要开动虚拟机,用win下的环境来操作。想想确实比较无奈。

今天在家,不想再开虚拟机里和在里面连公司的VPN,这样麻烦无比,于是研究了一下用svn命令操作merge。

人都是被逼的。

我的SVN项目环境:
开发主干:~/svn/proj/trunk
要发布(合并)的分支:~/svn/proj/branches/proj-1.2

首先,切换到工作路径:cd ~/svn/proj/branches/proj-1.2 也就是发布版本的目录位置
检查两个版本:发布分支版 svn info 找到 Last Changed Rev即为最后版本号(当前上次版本是4067,这个版本正是我要发布的,建议先svn up一次,万一有其他成员在此做过发布呢)
开发主干版本 svn info ~/svn/proj/trunk/ (得到当前最新版本是 4070)
先收集开发主干的日志:


svn log -r4067:4070 ~/svn/proj/trunk/ > ~/tmp/r4067.txt

清理一下日志格式,去掉不需要的内容(如空行),如果需要,还可以再编辑一下:


grep -v "^[r-]" ~/tmp/r4067.txt |grep -v "^$" >~/tmp/r4067-clean.txt

开始合并代码(当前目录仍位于发布分支/branches/proj-1.2):


svn merge -r 4069:4070 ~/svn/proj/trunk/ .

合并过程会有详细的文件版本修正清单
也可以在合并后用svn status查看修正内容清单
最后,用 svn ci -m ~/tmp/r4067-clean.txt 来提交发布分支。

如果合并后有冲突,要先解决掉再提交。

貌似以上操作可以整理成脚本自动完成。但是对于一个即将发布的项目版本,还是手工查看一下代码修正历史和日志较为妥当。

首先,请允许我感谢郭嘉,我的小黑T61光荣下岗了;还要感谢我的大肚老婆,赏赐重金购得MC373一台。以下操作假设您已经安装完macports,我的系统是10.6.3,10.5没测试过,不保证可用。

先安装nginx,建议用这个替换掉apache2


sudo port install nginx
cd /opt/local/etc/nginx
sudo cp nginx.conf.example nginx.conf
sudo cp mime.types.example mime.types
sudo cp fastcgi_params.example fastcgi_params

以上三个配置文件默认安装后没有,如果已经有就不用拷贝了。

加载到自动运行:


sudo launchctl load -w /Library/LaunchDaemons/org.macports.nginx.plist

下载我制作的包含php5-fpm Portfile的本地包


cd ~/ && wget http://liut.cc/DarwinPorts.tbz

解压到自己的位置 例如: /Users/liutao/


cd ~/ && tar jxvf DarwinPorts.tbz

修改自定义Port来源


sudo vim /opt/local/etc/marcports/sources.conf

在末尾添加一行(如果已经有就不要加了),实际路径根据需要修改:


file:///Users/liutao/DarwinPorts/local-sources/

然后就可以安装php5- fpm集成包。这个Port是我根据官方php-5.3.2修改而来,主要是添加了FPM和几个扩展选项,如PostgreSQL,MySQL,SOAP,GD,Tidy等,可以根据实际需要选择,FPM取自php.netsvn主干,默认有效,另外还默认有curl,iconv,mbstring,openssl 等。个人感觉,macports比FreeBSD的ports定制更容易,基于tcl的语法更简单易懂。

php5-fpm的Portfile部分内容:



# fpm
variant fpm conflicts apache apache2 fastcgi description {FPM support} {
    pre-fetch {
        if {"darwin" == ${os.platform} && ${os.major} < 9} {
            ui_error "The suhosin variant requires Mac OS X 10.5 or greater."
            return -code error "incompatible Mac OS X version"
        }
    }
	configure.args-delete 
        --disable-cgi
    configure.args-append 
		--enable-cgi --enable-fpm
	startupitem.create      yes
	startupitem.name        php-fpm
	startupitem.logfile     ${prefix}/var/log/php-fpm.log
	startupitem.start       "${prefix}/sbin/php-fpm.init start"
	startupitem.stop        "${prefix}/sbin/php-fpm.init stop"
	startupitem.restart     "${prefix}/sbin/php-fpm.init reload"
	startupitem.logevents   yes

	post-destroot {
		xinstall -m 755 -d ${destroot}${prefix}/var/log/php
		xinstall -m 755 -c ${worksrcpath}/sapi/fpm/init.d.php-fpm ${destroot}${prefix}/sbin/php-fpm.init
		xinstall -m 644 -c ${filespath}/php-fpm.conf ${destroot}${prefix}/etc
	}
	post-activate {
		if {[file exists ${prefix}/etc/php-fpm.conf.default] && ![file exists ${prefix}/etc/php-fpm.conf]} {
			copy ${prefix}/etc/php-fpm.conf.default ${prefix}/etc/php-fpm.conf
		}
	}

}

为什么叫php5-fpm这个名字呢?因为官方没有这个名称,所以会找到本地的source,要不然就得换个名字。

这时候就可以用macports来安装了,注意,如果您已经安装了官方的php5(就是默认会添加apache的那个,目前也是5.3.2,请先uninstall掉它)


sudo port install php5-fpm +postgresql +mysqlnd +soap +gd +tidy

sudo mkdir -p /opt/local/var/log/php 
## 一般安装会建立,但如果没有上级(log)会建立失败,所以建议先安装nginx

加载到自动运行:


sudo launchctl load -w /Library/LaunchDaemons/org.macports.php-fpm.plist

然后就是修改nginx.conf配置来支持PHP fastcgi,这样的内容网上有很多,请自行搜索。


(function($){

	/**
	 * example: <div id="land_box"></div>
	 * $('#land_box').radioButtons({data: {'CN':'中国','US':'美国'}, name: 'land'});
	 */
	$.fn.radioButtons = function(options) {
		options = $.extend({
			data: {'0':'Choice 1','1':'Choice 2'},
			name: 'radio',
			selected: '',
			skipEmpty: true
		}, options);
		var self = $(this).empty(), i = 0;
		for( var k in options.data) {
			if (options.skipEmpty && k === "") continue;
			var id = 'ws_'+options.name+'_'+i, text = options.data[k], radio =
			$("<input />").attr("id", id)
				.attr("type", "radio").attr("name", options.name)
				.attr("value", k);
			if (k === options.selected) radio.attr('checked', 'checked');
			radio.appendTo(self);
			$("<label />").attr("for", id).text(text).appendTo(self);
			i ++;
		}
		self.buttonset();
		return this;
	};
})(jQuery);

效果参考: jQuery UI Botton

写到一半,发现铺垫有些长,总扯和技术无关的东西。我以前没这么啰嗦和八卦的,这是为什么呢?

有一个人,这个人我已经不记得他的相貌和姓名,但是他曾经影响了我。99年的春天,联想开始做公共网站,我虽然主要工作是做设计,但其实我的兴趣在技术上,喜欢自己找些Perl(当年的CGI程序多数是Perl的,自己在学校学到的那不足千行的C还不知道怎么用在Web上)的留言版或聊天室程序改改代码和界面,用在一个叫“幸福之家”的土得掉渣儿的网站上。 大约在秋天或年底的时候,这个据说是北大(也可能是清华)的学生来帮忙做兼职开发新闻系统。我经常正事儿不干,看着他写代码。他的主要开发方式就是连接到服务器上打开Vi开始写。那时我虽然也在Solaris用过VI,但还只会移动光标和保存之类的简单操作,而且并不习惯。这样完全用VI开发还觉得比较新奇。那是我第一次接触PHP,那时的版本是3.0,所以他写的文件扩展名全是.php3。我负责前台的界面和样式,偶尔打打下手写点儿代码。这样边写边测试(完全正式线上服务器环境,现在想想那时真无畏),大概用了一周时间,一个基本的新闻管理系统就上线了。就这样,我也就开始了我的PHP生涯。

这个新闻管理系统运行了不到半年后,就被联想研究院的同事完全用Java重写。我也在合作期间偷偷学了点儿Java语言和EJB的皮毛。而这位年长于我一两岁的同学,就此失去了联系,不知道他现在在做什么。问题是,我真不记得他叫什么,也没有联系方式,好像我俩都很内向,不问彼此,真是一对奇怪的年轻人。

在离开联想之前,我用PHP写过几个小项目,我现在还记得有一个广告维护和更新系统,主要功能是广告管理、图片上传、和html文本块编辑等。由于之前有研究过Perl的代码,加上一些C的基础,让我对PHP这个脚本语言真真的喜爱有加。你想想看,那个年月,有哪个Web脚本有如此丰富的扩展又有这么简单易学的代码。什么数据库操作啊、图像生成啊——我甚至用它的GD库实现了饼图显示投票的结果(当然饼图的生成算法是抄来的)——、文本操作什么的,好多的扩展,包括数不清的和风格不一的命名,它实在太容易学了,也太好用了,语法自由,又没有像Java那样的强类型约束,我对它实在是爱不释手,这可如何是好哇。后来的几年也确实是PHP高速普及的时期。

联想当年做的那个网站,用的数据库几乎全是Oracle,而且跑在Sun的服务器上,据说还有光纤阵列啥的。多年后有人告诉我,由于无人打理,域名被人抢注了,我记得当时我的心情平静的。据说抢注 FM365.com 的人后来做了个叫 265 的网站。

02年和同样是联想出来的两个朋友去了北大附中网校。当时在线的是ASP环境。那之前还没用过ASP,写了一些模块,发现和PHP差距较大,如果不用Com,很多想法较难实现。这里再啰嗦两句,ASP支持两种语言:VBScript和JScript,我实在是不喜欢VB的语法,后期写的模块我都尽量用JScript实现。而且,JScript支持一种类似Hashtable的Dictionary对象,操作字典类数据很方便。不过,总得说来,ASP还是太弱了,如果PHP是把多功能军刀,ASP只能算是生锈的水果刀。

人的思想是会变的,人又总不满足于现状。约一年后就将整个系统逐步升级到.Net。第一次用C#发现和Java很像,也是第一次开始试着用分层的概念设计系统架构。在这期间的同事有霍炬戴飞。经常在去饭馆的路上还在和火炬讨论是分三层好还是四层好。

霍炬对我的帮助和影响又更多些,他还送了我两本书《设计模式》和《C++Primer》(前者到是时常翻阅,后者较厚,没怎么看,几次搬家都想扔了)。

坦率的讲,C#真是门集众家之所长的好的语言,相较于PHP这类脚本语言,又足够OO和足够完善。PHP和它相比,实在是太土妞了。加上刚接触到设计模式——这个东西就像火炬说的手里有把锤子就会发现到处都是钉子——这么个超级大锤子。所以我那段时间像同时沉迷两样东西,一个是WoW(还在公测貌似),一个就是C#。我能从写大段脚本和无数子过程学习并过渡到对象开发和多层架构的设计,这得感谢C#和.Net。

在网校做的最后一个项目是个多用户Blog系统,基于b2的开源改造,又重回到了PHP的怀抱。虽然从个人角度我非常喜欢C#语言,但我仍对Windows平台没有信心(那时Mono还是个实验项目)。

大约04年的时候,或许是受Java的影响,PHP社区也出现了一些开发框架,如CakePHPZend FrameworkCodeIgniterSymfony…。但是,在看了这些框架并做了些测试之后,我限入了纠结之中。Cake和其他几位还稍好,最变态的就是Zend,几乎完全模拟强类型语言(如Java),完全的用OO方法设计模块和类。方向和庞杂度直奔Java的屁股而去。

PHP真的要这么写吗?这和Java和.Net有什么区别?这还有PHP的优势么?看烦了各个论坛各种框架之间的口水战。为了防止可能继续误入歧途,我逃离了这些豪华巨轮。但是我限入了迷惘,我要再怎么写的PHP呢,我似乎失去了方向。我各类在各类开源项目和书里寻找答案。随着几次失败的项目和工作单位的变动,思想也有较大的变化。后来的两年多时间,较少关注社区的动向,按照自己对Web开发和结构分层的理解,写一些适合自己用的基础库,谈不上框架,用着倒也趁手。

我也有接触和学习其他的新的语言和框架,例如PythonDjangoRuby。其中Python的语言特性让我很着迷,这是我见过的第一个把清晰(或者说整洁)度作为语法规则一部分的编程语言。也许是我本人爱干净,也许是我的想法变了,我甚至觉得Python的语言哲学:“There should be one– and preferably only one –obvious way to do it.”(有且仅有一种明确的实现方法),是非常完美且正确的理念。我回过头再审视PHP满身的伤疤和陷阱,不禁要问:PHP是不是进步的太慢和太不思进取了。为什么会这样呢?

这里又要感谢一位同学,几年前某天在西南三环某处租房里,郝培强送了我一本Python入门(也可能是我拿在手里一直不放下的原因),那时他还单身,还很纯情,大家在聊着对未来的憧憬和各种的梦想。

就一种语言来说,PHP易学易用,几乎不需要什么基础和背景,适合各类人群(比如被大家臭骂的北大青岛的毕业生),只是代码的质量差距较大而已。然而,人不可能永远在初级状态,人是会成长的,用得越久,越是能感受到它的局限。

前段时间看到有人说PHP很烂,虽然这话难听且观点很武断,但他文中所说的PHP特性包括引用几位老外的观点又都是事实。比如这个,还有这个

我的朋友老王说,“语言不重要,重要的是思想”,这话不错。但思想的获得何其难,它需要一段时间积累加上一些天分甚至还需要有一点点机缘才能灌输到人的脑子里面。为了实现高质量和可控的代码,我需要写几十页纸的约定和规范,并且还要定期Review,要不然就等着数不清的地雷哪天突然被踩中吧。影响生产力因素有多种,有人的因素,也有语言和工具(包括平台)的因素。如果一种语言可以很容易的实现清晰、明确和严谨的高质量代码,而另一种语言则需要数年经验且小心小心再小心才能达到同样的质量要求,孰优孰劣,似乎不难判断。

其实俺又是个重感情的人(:D),这么多年一路陪俺走过一个沟又一个坎儿,今儿使劲说嫌弃的话,心里怪不是滋味的(:S)。

现实会给人一些安慰。有数量众多的人用PHP,也有大量的网站运行其上,PHP在实事上占有一席之地。一切在变化中,结局还未可知。

Update:
关于开发速度的问题,要看具体针对的业务需求。PHP由于有大量开源项目(且不论其质量)可以参考使用,加上人力成本相对较低,可以很容易在短期内部署一个适应多种类型的小型网站,确实会比Java要快些。然而,但是,根据前面的描述,你知道我要说什么,你需要有至少一个最好是多个有非常丰富经验的人来控制架构和设计,否则的话,随着用户和访问量的提升,一定会遇到技术上的瓶颈。这些都是有现实教训的例子的。

以下的文字和技术无关,和WoW(魔兽世界)有关,如果您没有玩过可能将无法理解并会因此产生错误判断,故请忽略之。

我是在内地公测时开始接触WoW。期间中断2年后又转战台服,断断续续,直到09年春终止。

有些记忆的碎片挥之不去。

当我第一次花了50个银币从达纳苏斯的宠物商人那里买了一只花白的小猫头鹰,我走到哪儿它就跟到哪儿,那时在我身上总共只有几十个银币尚不足以兑换成一个金币;

当我第一次踏上黑海岸,在好友列表里看到洗衣机涮羊肉同学在赤脊山。他告诉路线:先坐船到米奈希尔港,然后向东穿湿地再一路向南,最后到达艾尔文森林,那是我们联盟的地盘。我看了看地图,那是多么遥远的地方啊,我怎么可能长途跋涉去那么远呢;

当我站在奥泊丁的旅店门外,第一次看到有人骑着白色的夜刃豹从我身边从容而过,我流着口水,万分的羡慕。45级时,忍受不了旅行姿势的速度,向小铃铛借了几十个金币,凑够90金买了第一只坐骑,我骑上它,做各种跳和翻越动作;

当我第一次下矿井,挂了10多次后才无奈作罢,5个人坐在荒野草地上兴致勃勃的聊天;

当我在芦苇海岸想钓到一条大马哈鱼却意外的第一次钓到“19磅重的鲶鱼”,周围却没有人,很安静;

当我第一次看到炎魔拉格纳罗斯的火光消散,只留下一把锤子掉落在岩浆池边,大家欢呼并长舒一口气;

当我第一次坐上自己制造的小飞机,飞过泰罗卡森林的树梢,飞向天空卫队在山巅的营地的时候,我给朋友留言:飞行果然是个颠覆的概念;

当我在黑暗神殿的尽头,面对倒下的伊利丹怒风,和麦维影歌伤感的挥手告别;

当我和萨尔协助希尔瓦娜斯·风行者从瓦里玛萨斯手里夺回幽暗城的时候;

当太多经历亦无以详述,无数张镜头组合成影像滑过之时。

我骑着双足飞龙,在北裂境寒冷的上空,漫无目的的飞来飞去,有一个声音说:忘掉那些曾经激动的时刻吧。

在风暴群山,在冰冠城寨,在奈辛瓦里营地,在龙眠神殿的顶端,我飞得累了,我要休息。

最后,我回到了达拉然,站在织符者广场上,面对着许愿池的喷泉,长久的伫立着。

直到时间耗尽,下线。

—————- 无法忘却的串串名字一定不全和或还不准之分隔线 —————-

二区众星之子:小铃铛、别管她、洗衣涮羊肉、大地僧冰、莎当妮、冰红茶。。。

台服屠魔山谷:愛睡覺的熊、一九、紅連怒、蜜雪吳、泡泡、部落、排骨、叫不醒、日照、狂暴龍、不爽、帕格、司徒嘉特。。。

对了,我就是那个喜欢钓鱼、烹飪、喜欢和朋友一起解任務并打屁聊天的遙感衛星。

声明:以下观点只代表个人想法,不喜勿骂

晚上和一个朋友在MSN上聊天,聊起Flash。整理了一下,我不看好Flash,基于以下原因

  • Flash是一个在特殊时期,仓促诞生的网页多媒体呈现技术;
  • Flash是个封闭的技术,并且Adobe收购MacroMedia之后继续封闭和拒绝开放;
  • Flash对客户端的CPU和资源占用较多,这一点饱受用户抱怨,以始终未有较明显改善;
  • Flash网页组件不是一个遵循Web标准的组件,在网页交互上,它不具有开放性和标准性;
  • Flash试图通过自有平台扩展向整个Web交互平台发展,且自创一套封闭标准以维持其垄断性,这会威胁到众多终端厂商的利益;
  • Adobe不是一家受人景仰的公司,有野心,无实力,在开放的大趋势下,它一点儿让人尊敬的进步也没有。它或许想走微软的老路,但是条已经被证明并不好走的路。

Flash本身不具备不可替代性,随着iPad的发布和HTML5的普及,Flash将变得越发无用。且由于先天不足,调整余地不大,几近末路。

最近在尝试将一部分数据切换到(不是替换哦)MongoDB,由于MongoDB支持非常强大的查询操作,给相关的业务模块带来了极大的操作便利,再配合模块更新方法的同步或异步操作以实现数据一致性,这或许会改变未来业务模块的结构设计方向。

以下记录一些使用过程中总结的经验和感受:

=关于固定尺寸(指存储大小)的表(Capped Collections)

固定尺寸的表有更高的操作性能,但只适合特定的数据,不常修改的且定期淘汰的数据,如日志等

固定尺寸表的特性:

  • 新插入的数据如果达到容量限制,会把最旧的(也即最先插入的)替换掉
  • 可以修改数据(条目),但是,但是——为了这个我只能放弃使用它——更新的数据不可以增大(就是比原数据字节长),除非不修改
  • 在32系统下固定尺寸最大只能到10亿字节(不到1G);在64位系统上则取决于硬盘空间;当然了,推荐用64位系统
  • 如果要改变固定大小,只能整个表(Collection)删除(drop掉),然后重建

=关于索引

大多数在MySQL、PostgreSQL等关系数据库下的索引优化经验也适于MongoDB

  • 为了最大化性能,要根据实际查询设置索引
  • 如果你的表有个字段叫status,可能的值是0和1,这个字段单独健个索引对查询帮助不大,甚至是白占空间,建议将它和别的字段一起做联合索引
  • 多和explain分析查询,其他关系数据库也是这个原则
  • 争取做到每个查询都正好仅有一个索引满足它
  • 如果数据更新很频繁,最好少建索引

=其他建议和唠叨

  • 用实际的主键重命名为 _id 以替换 MongoDB 内部的主键
  • 字段的名称也占用每条记录的存储空间,所以,尽量用较短的字段名
  • PHP下的pecl-mongo扩展,里面的多数写方法,都有个safe选项,建议添加并设为true,这样在发生错误时会抛出异常,方便后端处理

更多资料参考: MongoDB 手册