在了解过世界最大的PHP站点,Facebook的后台技术后,今天我们来了解一个百万级PHP站点的网站架构:Poppen.de。Poppen.de是德国的一个社交网站,相对Facebook、Flickr来说是一个很小的网站,但它有一个很好的架构,融合了很多技术,如 Nigix、MySql、CouchDB、Erlang、Memcached、RabbitMQ、PHP、Graphite、Red5以及Tsung。

  Poppen.de目前有200万注册用户数、2万并发用户数、每天20万条私有消息、每天25万登录次数。而项目团队有11个开发人员,两个设计,两个系统管理员。该站点的商业模式采用免费增值模式,用户可以使用搜索用户、给好友发送消息、上载图片和视频等功能。

  如果用户想享受不受限制发送消息和上载图片,那么就得根据需要支付不同类型的会员服务,视频聊天及网站其他服务也采用同样的策略。

  Nginx

  Poppen.de 所有的服务都是基于Nginx服务上的。前端有两台Nginx服务器在高峰期提供每分钟15万次请求的负载,每个机器已经有四年寿命,并且只有一个CPU 和3GB RAM。Poppen.de拥有三台独立的图像服务器,由三台Nginx服务器为*.bilder.poppen.de提供每分钟8万次请求服务。

  Nginx 架构中一个很酷的设计就是有很多请求是由Memcached处理的,因此请求从缓存中获取内容而不需要直接访问PHP机器。比如,用户信息页(user profile)是网站需要密集处理的内容,如果把用户信息页全部缓存到Memcached上,那么请求直接从Memcached上获取内容。 Poppen.de的Memcached每分钟可以处理8000次请求。

  架构中有三个Nginx图像服务器提供本地图像缓存,用户上载图 像到一个中央文件服务器。当向这三个Nginx之一中请求图像时,如果服务器本地中没有存在该图像,则从中央文件服务器下载到该服务器上作缓存并提供服 务。这种负载均衡的分布式图像服务器架构设计可以减轻主要存储设备的负载。

  PHP-FPM

  该网站运行在PHP- FPM上。共有28台双CPU、6GB内存的PHP机器,每个机器上运行100个PHP-FPM的工作线程。使用启用了APC的PHP5.3.x。 PHP5.3可以降低CPU和内存使用率的30%以上。

  程序代码是基于Symfony1.2框架之上开发的。一是可以使用外部资源,二是 能够提高项目开发进度,同时在一个著名的框架上可以让新开发人员更容易加入到团队中来。虽然没有任何事情都是十全十美的,但可以从Symfony框架中得 到很多好处,让团队可以更多的精力放在Poppen.de的业务开发上去。

  网站性能优化使用XHProf,这是Facebook开源出来的一个类库。这个框架非常容易个性化和配置,能够可以缓存大部分高代价的服务器计算。

  MySQL

  MySQL是网站主要的RDBMS。网站又几个MySql服务器:一台4CPU、32GB的服务器存储用户相关信息,如基本信息、照片描述信息等。这台机器已经使用了4 年,下一步计划会使用共享集群来替换它。目前仍基于这个系统上进行设计,以简化数据访问代码。根据用户ID进行数据分区,因为网站中大部分信息都是以用户 为中心的,如照片、视频、消息等。

  有三台服务器按主-从-从配置架构提供用户论坛服务。一台从服务器负责网站自定义消息存储,到现在有 2.5亿条消息。另外四台机器为主-从配置关系。另外由4台机器配置成NDB族群专门服务于密集型写操作数据,如用户访问统计信息。

  数据表设计尽量避免关联操作,尽可能缓存最多的数据。当然,数据库的结构化规范已经完全被破坏掉了。因此,为了更容易搜索,数据库设计创建了数据挖掘表。大部分表是MyISAM型表,可以提供快速查找。现在的问题是越来越多的表已经全表锁住了。Poppen.de正考虑往XtraDB存储引擎上迁移。

  Memcached

  网站架构中Memcached应用相当多,超过45GB的高速缓存和51个节点。缓存了Session会话、视图缓存以及函数执行缓存等。架构中有一个系统 当记录被修改时可以自动地把数据更新到缓存中去。未来改善缓存更新的可能方案是使用新的Redis Hash API或者MongoDB。

  RabbitMQ

  在 2009年中开始在架构中使用RabbitMQ。这是一个很好的消息解决方案,便于部署和集中到这个架构中去,在LVS后运行了两台RabbitMQ服务 器。在上个月,已经把更多的东西集成到该队列中,意味着同一时刻有28台PHP服务器每天要处理50万次请求。发送日志、邮件通知、系统消息、图像上载等 更多的东西到这个队列中。

  应用PHP-FPM中的fastcgi_finish_request()函数集成队列消息,可以把消息异步发 送到队列中。当系统需要给用户发送HTML或JSON格式响应时,就调用这个函数,这样用户就没有必要等到PHP脚本清理。

  这个系统可以改善架构资源管理。例如,在高峰期服务每分钟可以处理1000次登录请求。这表示有1000并发更新用户表保存用户的登录时间。由于使用了队列机制,可以 按相反的顺序来运行这些查询。如果需要提高处理速度,只需要增加更多的队列处理者即可,甚至可以增加更多的服务器到这集群中去,而不需要修改任何配置和部 署新节点。

  CouchDB

  日志存储CouchDB运行在一台机器上。在这台机器上可以根据模块/行为进行日志查询 /分组,或者根据错误类型等等。这对定位问题非常有用。在使用日志聚合服务CouchDB之前,不得不逐台登录到PHP服务器上设法日志分析定位问题,这 是非常麻烦的。而现在把所有的日志集中到队列中保存到CouchDB中,可以集中进行问题检查和分析。

  Graphite

  网站使用Graphite采集网站实时信息并统计。从请求每个模块/行为到Memcached的命中和未命中、RabbitMQ状态监控以及Unix负载等等。Graphite服务平均每分钟有4800次更新操作。实践已经证实要监测网站发发生什么是非常有用的,它的简单文本协议和绘图功能可以方便地即插即 用的方式用于任何需要监控的系统上。

  一件很酷的事情是使用Graphite同时监控了网站的两个版本。一月份部署了Symfony框架新 版本,以前代码作为一个备份部署。这就意味着网站可能会面临性能问题。因此可以使用Graphite来对两个版本在线进行对比。

  发现新版本上的Unix负载表较高,于是使用XHProf对两个版本进行性能分析,找出问题所在。

  Red5

  网站为用户也提供了两种类型的视频服务,一种是用户自己上载的视频,另外一种是视频聊天,用户视频互动和分享。到2009年年中,每月为用户提供17TB的流量服务。

  Tsung

  Tsung 是一个Erlang编写的分布式基准分析工具。在Poppen.de网站中主要用于HTTP基准分析、MySQL与其他存储系统(XtraDB)的对比分 析。用一个系统记录了主要的MySQL服务器的流量,再转换成Tsung的基准会话。然后对该流量进行回放,由Tsung产生数以千计的并发用户访问实验 室的服务器。这样就可以在实验环境中与真实场景非常接近。

posted @ 2012-03-22 12:55 小马歌 阅读(178) | 评论 (0)编辑 收藏
 
对于大多数web应用来说,数据库都是一个十分基础性的部分。如果你在使用PHP,那么你很可能也在使用MySQL—LAMP系列中举足轻重的一份子。

对于很多新手们来说,使用PHP可以在短短几个小时之内轻松地写出具有特定功能的代码。但是,构建一个稳定可靠的数据库却需要花上一些时日和相关技能。下面列举了我曾经犯过的最严重的11个MySQL相关的错误(有些同样也反映在其他语言/数据库的使用上)。。。

1、使用MyISAM而不是InnoDB

MySQL有很多数据库引擎,但是你最可能碰到的就是MyISAM和InnoDB。

MySQL 默认使用的是MyISAM。但是,很多情况下这都是一个很糟糕的选择,除非你在创建一个非常简单抑或实验性的数据库。外键约束或者事务处理对于数据完整性 是非常重要的,但MyISAM都不支持这些。另外,当有一条记录在插入或者更新时,整个数据表都被锁定了,当使用量增加的时候这会产生非常差的运行效率。

结论很简单:使用InnoDB。

2、使用PHP的mysql函数

PHP自产生之日就提供了MySQL库函数(or near as makes no difference)。很多应用仍然在使用类似mysql_connect、mysql_query、mysql_fetch_assoc等的函数,尽管PHP手册上说:

如果你在使用MySQL v4.1.3或者更新版本,强烈推荐使用您使用mysqli扩展。

mysqli(MySQL的加强版扩展)有以下几个优点:

可选的面向对象接口 
prepared表达式,这有利于阻止SQL注入攻击,还能提高性能 
支持更多的表达式和事务处理 
另外,如果你想支持多种数据库系统,你还可以考虑PDO。

3、没有处理用户输入

这或者可以这样说#1:永远不要相信用户的输入。用服务器端的PHP验证每个字符串,不要寄希望与JavaScript。最简单的SQL注入攻击会利用如下的代码:

$username = $_POST["name"];   $password = $_POST["password"];   $sql = "SELECT userid FROM usertable WHERE username='$username' AND password='$password';";   // run query...  

只要在username字段输入“admin';--”,这样就会被黑到,相应的SQL语句如下:

SELECT userid FROM usertable WHERE username='admin';

狡猾的黑客可以以admin登录,他们不需要知道密码,因为密码段被注释掉了。

4、没有使用UTF-8

美国、英国和澳大利亚的我们很少考虑除英语之外的其他语言。我们很得意地完成了自己的“杰作”却发现它们并不能在其他地方正常运行。

UTF-8解决了很多国际化问题。虽然在PHP v6.0之前它还不能很好地被支持,但这并不影响你把MySQL字符集设为UTF-8。

5、相对于SQL,偏爱PHP

如果你接触MySQL不久,那么你会偏向于使用你已经掌握的语言来解决问题,这样会导致写出一些冗余、低效率的代码。比如,你不会使用MySQL自带的AVG()函数,却会先对记录集中的值求和然后用PHP循环来计算平均值。

此外,请注意PHP循环中的SQL查询。通常来说,执行一个查询比在结果中迭代更有效率。

所以,在分析数据的时候请利用数据库系统的优势,懂一些SQL的知识将大有裨益。

6、没有优化数据库查询

99%的PHP性能问题都是由数据库引起的,仅仅一个糟糕的SQL查询就能让你的web应用彻底瘫痪。MySQL的EXPLAIN statement、Query Profiler,还有很多其他的工具将会帮助你找出这些万恶的SELECT。

7、不能正确使用数据类型

MySQL提供了诸如numeric、string和date等的数据类型。如果你想存储一个时间,那么使用DATE或者DATETIME类型。如果这个时候用INTEGER或者STRING类型的话,那么将会使得SQL查询非常复杂,前提是你能使用INTEGER或者STRING来定义那个类型。

很多人倾向于擅自自定义一些数据的格式,比如,使用string来存储序列化的PHP对象。这样的话数据库管理起来可能会变得简单些,但会使得MySQL成为一个糟糕的数据存储而且之后很可能会引起故障。

8、在查询中使用*

永远不要使用*来返回一个数据表所有列的数据。这是懒惰:你应该提取你需要的数据。就算你需要所有字段,你的数据表也不可避免的会产生变化。

9、不使用索引或者过度使用索引

一般性原则是这样的:select语句中的任何一个where子句表示的字段都应该使用索引。

举 个例子,假设我们有一个user表,包括numeric ID(主键)和email address。登录的时候,MySQL必须以一个email为依据查找正确的ID。如果使用了索引的话(这里指email),那么MySQL就能够使用 更快的搜索算法来定位email,甚至可以说是即时实现。否则,MySQL就只能顺序地检查每一条记录直到找到正确的email address。

有的人会在每个字段上都添加索引,遗憾的是,执行了INSERT或者UPDATE之后这些索引都需要重新生成,这样就会影响性能。所以,只在需要的时候添加索引。

10、忘记备份!

虽然比较罕见,但是数据库还是有崩溃的危险。硬盘有可能损坏,服务器有可能崩溃,web主机提供商有可能会破产!丢失MySQL数据将会是灾难性的,所以请确保你已经使用了自动备份或者已经复制到位。

11、Bonus mistake-不考虑使用其他数据库

对于PHP开发人员来说,MySQL可能是使用最广泛的数据库系统,但并不是唯一的选择。PostgreSQL和Firebird是最强有力的竞争者:这个两者都是开源的,而且都没有被公司收购。微软提供了sql server Express,甲骨文提供了10g Express,这两者都是企业级数据库的免费版本。有时候,对于一个较小的web应用或者嵌入式应用,SQLite也不失为一个可行的替代方案。 
posted @ 2012-03-22 10:03 小马歌 阅读(296) | 评论 (0)编辑 收藏
 
     摘要: 先看下图中的场景,客户端A和B,以及服务器server都保存了同一个文件,最初,A、B和server上的文件内容都是相同的(记为File.1)。某一时刻,B修改了文件内容,上传到SERVER上(记为File.2)。客户端A这时试图向服务器SERVER更新文件到最新内容,也就是File.1更新为File.2。上面这个场景很常见,例如现在流行的网盘。假设我有一个文件a.txt在网盘上,上班时在公司的单...  阅读全文
posted @ 2012-03-19 16:45 小马歌 阅读(1097) | 评论 (0)编辑 收藏
 

人人网首页拖拽上传详解

  早在公元2011年6月3日傍晚,人人网推出了一个很装B且完全无视IE浏览器的功能——拖拽上床。哦,Sorry, 是拖拽上传。本文将重点介绍实现拖拽上传的几个HTML5技术:Drag&DropFileReader APIFormData

  关于这个拖拽上传,其实国外有很多网站已经有这样的应用,最早推出拖拽上传应用的是Gmail,它支持标准浏览器下拖拽本地文件到浏览器中作为邮件的附件发送。人人网的这个拖拽上传也是同理,可以让使用标准浏览器的用户通过简单的拖拽行为,将本地文件夹中的照片直接上传到人人网,用户体验能得到提升的同时,也希望借此机会推广一下标准浏览器,淘汰IE。人人网当时也向广大用户推出升级浏览器活动,并喊出口号:”工欲善其计算机,必先利其浏览器”。本次拖拽上传的宣传口号:你敢”脱”,我就敢上传…

人人网 - 拖拽上传

  言归正题,首先看看效果,大家如果有人人网帐号的话可以在首页试一试拖拽上传功能,下面是演示视频:

拖拽上传应用主要使用了以下HTML5技术:

  • Drag&Drop : HTML5基于拖拽的事件机制.
  • File API :  可以很方便的让Web应用访问文件对象,File API包括FileListBlobFileFileReaderURI scheme,本文主要讲解拖拽上传中用到的FileList和FileReader接口。
  • FormData : FormData是基于XMLHttpRequest Level 2的新接口,可以方便web应用模拟Form表单数据,最重要的是它支持文件的二进制流数据,这样我们就能够通过它来实现AJAX向后端发送文件数据了。

HTML5 Drag&Drop 拖拽事件

  关于Drag&Drop拖拽事件,之前我写过一篇专门介绍的文章《给力的 Google HTML5 训练营(HTML5 Drag&Drop 拖拽、FileReader实例教程)》,那篇文章详细讲解了Drag & Drap事件的原理和代码实例,这里的拖拽上传实现原理基本上是一样的,大家有兴趣或不太了解的话可以先看看那篇文章,我在这里就不再过多啰嗦了~下面直接出拖拽上传简要代码实例:

var oDragWrap = document.body;

 

//拖进
oDragWrap.addEventListener(’dragenter’, function(e) {
 e.preventDefault();
}, false);

//拖离
oDragWrap.addEventListener(’dragleave’, function(e) {
 dragleaveHandler(e);
}, false);

//拖来拖去 , 一定要注意dragover事件一定要清除默认事件
//不然会无法触发后面的drop事件
oDragWrap.addEventListener(’dragover’, function(e) {
 e.preventDefault();
}, false);

//扔
oDragWrap.addEventListener(’drop’, function(e) {
 dropHandler(e);
}, false);

var dropHandler = function(e) {
//将本地图片拖拽到页面中后要进行的处理都在这
}

获取文件数据 HTML5 File API

  在之前那篇文章中我也有介绍过关于File API中的FileReader接口,作为 File API 的一部分,FileReader 专门用于读取文件,根据 W3C 的定义,FileReader 接口 “提供一些读取文件的方法与一个包含读取结果的事件模型”。关于FileReader的详细介绍和代码实例大家可以先去看看那篇文章

  今天我着重介绍一下File API中的FileList接口,它主要通过两个途径获取本地文件列表,一是<input type=”file”>的表单形式,另一种则是e.dataTransfer.files拖拽事件传递的文件信息。很显然,我们这里会用到后者。

var fileList = e.dataTransfer.files;

  使用files方法将会获取到拖拽文件的数组形势的数据,每个文件占用一个数组的索引,如果该索引不存在文件数据,将返回null值。可以通过length属性获取文件数量.

var fileNum = fileList.length;

  拖拽上传需要注意的是需要判断两个条件,1:拖拽的是文件不是页面中的元素; 2:拖拽的是图片而不是其它文件,可以通过file.type属性获取文件的类型

//检测是否是拖拽文件到页面的操作
if (fileList.length === 0) {return;};
//检测文件是不是图片
if (fileList[0].type.indexOf(’image’) === -1) {return;}

  下面让我们来看看如何结合之前的拖拽事件来实现拖拽图片并在页面中进行预览:

var dropHandler = function(e) {
 e.preventDefault();

 

 //获取文件列表
 var fileList = e.dataTransfer.files;

 //检测是否是拖拽文件到页面的操作
 if (fileList.length == 0) {return;};

 //检测文件是不是图片
 if (fileList[0].type.indexOf(’image’) === -1) {return;}

 //实例化file reader对象
 var reader = new FileReader();
 var img = document.createElement(’img’);

 reader.onload = function(e) {
  img.src = this.result;
  oDragWrap.appendChild(img);
 }
 reader.readAsDataURL(fileList[0]);

}

  这里有一个简单的拖拽图片预览的Demo

  这时你如果用FireBug等类似调试工具查看DOM的话,会看到<img>标签的src属性是一个超长的文件二进制数据,所以如果DOM有很多这类图片,那就要当心浏览器性能了,因为这些数据极大地扩充的页面的代码量,而每次页面的reflow都会对浏览器形成很大的负担,So,如果这些图片还在DOM中,那就尽量不要做动画或任何重绘操作,如果真的要做就尽量让图片脱离文档流,让其绝对定位比较靠谱。

补充:可以使用window.URL.createObjectURL(file)来获取文件的URL(Chrome下用window.webkitURL.createObjectURL(file)),这种方式获取的URL要比上面说的readAsDataURL简短很多。而且可以省去使用FileReader。这里感谢BinBinLiao的留言建议:) 下面是使用readAsDataURL与createObjectURL生成的代码对比:

readasdataurl-vs-createobjecturl

优化后的代码:(红色为优化的代码)

var dropHandler = function(e) {
 e.preventDefault();

 

 var fileList = e.dataTransfer.files;  //获取文件列表
 var img = document.createElement(’img’);

 //检测是否是拖拽文件到页面的操作
 if (fileList.length == 0) {return;};

 //检测文件是不是图片
 if (fileList[0].type.indexOf(’image’) === -1) {return;}
 

 if (window.URL.createObjectURL) {
  //FF4+
  img.src = window.URL.createObjectURL(fileList[0]);
 } else if (window.webkitURL.createObjectURL) {
  //Chrome8+
  img.src = window.webkitURL.createObjectURL(fileList[0]);
 } else {
  //实例化file reader对象
  var reader = new FileReader();

 

  reader.onload = function(e) {
   img.src = this.result;
   oDragWrap.appendChild(img);
  }
  reader.readAsDataURL(fileList[0]);
 }

}

  需要注意的是,window.URL.createObjectURL是有生命周期的,也就意味着你每用此方法获取URL,其生命周期都会和DOM一样,它会单独占用内存,所以当删除图片或不再需要它是,记得用window.URL.revokeObjectURL(file)来释放其内存。当然,如果你没有释放,刷新页面也是可以释放的。

AJAX上传图片(file.getAsBinary & FormData)

  既然已经获取到了拖拽到web页面中图片的数据,下一步就是将其发送到服务器端了。

  话说HTML5时代之前,AJAX传输文件二进制流数据是不可能完成的事情,而现在我们完全可以通过file.getAsBinary获取文件的二进制数据流,进而将其当做XHR的data数据传送到后端,8过由于Chrome不支持file的getAsBinary方法,FF3.6+支持此方法。所以Chrome就要另寻它法了,这时我们发现XMLHttpRequest Level 2中的FormData接口完美解决了这个问题,它可以很快捷的模拟Form表单数据并通过AJAX发送至后端,FormData的支持情况是FF5及以上支持,Chrome12及以上支持。

   file.getAsBinary获取文件流很简单,但是要想上传数据,就要模拟一下表单的数据格式了,首先看看模拟表单的js代码, FormData模拟表单数据时更是简洁,不用麻烦的去拼字符串,而是直接将数据append到formdata对象中即可:

var xhr = new XMLHttpRequest();
var url = ‘http://upload.renren.com/……’;
var boundary = ‘———————–’ + new Date().getTime();
var fileName = file.name;

 

xhr.open(”post”, url, true);
xhr.setRequestHeader(’Content-Type’, ‘multipart/form-data; boundary=’ + boundary);

if (window.FormData) {
 //Chrome12+
 var formData = new FormData();
 formData.append(’file’, file);
 formData.append(’hostid’, userId);
 formData.append(’requestToken’, t);

 data = formData;
} else if (file.getAsBinary) {
 //FireFox 3.6+
 data = “–” +
 boundary +
 crlf +
 ”Content-Disposition: form-data; ” +
 ”name=\”" +
 ’file’ +
 ”\”; ” +
 ”filename=\”" +
 unescape(encodeURIComponent(file.name)) +
 ”\”" +
 crlf +
 ”Content-Type: image/jpeg” +
 crlf +
 crlf +
 file.getAsBinary() +
 crlf +
 ”–” +
 boundary +
 crlf +
 ”Content-Disposition: form-data; ” +
 ”name=\”hostid\”" +
 crlf +
 crlf +
 userId +
 crlf +
 ”–” +
 boundary +
 crlf +
 ”Content-Disposition: form-data; ” +
 ”name=\”requestToken\”" +
 crlf +
 crlf +
 t +
 crlf +
 ”–” +
 boundary +
 ’–’;
}

xhr.send(data);

首先表单数据headers头信息需要以下两项:

  • Content-Type : 设置其为multipart/form-data来模拟表单数据
  • boundary : 表单数据中的分隔符,用于分隔不同的文件或表单项,这是服务器端设置的格式。

发送时的post数据类似这样:

————————-1323611763556
Content-Disposition: form-data; name=”file”; filename=”4.jpg”
Content-Type: image/jpeg

 

ÿØÿà�JFIF�…这里是文件二进制流…~iúoî­5P%-vãîHü 4QHgÿÙ
————————-1323611763556
Content-Disposition: form-data; name=”hostid”

229421603
—————————–1323612996486

Content-Disposition: form-data; name=”requestToken”

369009193
————————-1323611763556–

好了,现在文件上传成功后你就可以按照平常AJAX的操作来进行后续处理了。

最后,再来总结一下拖拽上传的技术要点:

  1. 监听拖拽:监听页面元素的拖拽事件,包括:dragenter、dragover、dragleave和drop,一定要将dragover的默认事件取消掉,不然无法触发drop事件。如需拖拽页面里的元素,需要给其添加属性draggable=”true”;
  2. 获取拖拽文件:在drop事件触发后通过e.dataTransfer.files获取拖拽文件列表,.length属性获取文件数量,.type属性获取文件类型。
  3. 读取图片数据并添加预览图:实例化FileReader对象,通过其readAsDataURL(file)方法获取文件二进制流,并监听其onload事件,将e.result赋值给img的src属性,最后将图片append到DOM中。
  4. 发送图片数据:使用file.getAsBinary 和 FormData分别模拟表单数据AJAX提交文件流。

OK,拖拽上传就讲到这里,欢迎大家一起探讨。

posted @ 2012-03-19 16:35 小马歌 阅读(2220) | 评论 (0)编辑 收藏
 
     摘要: BOS(Bonita Open Solution) 是一个开源的 BPM 解决方案,有 3 个主要部分构成:Bonita Studio : 用户可以根据 BPMN 标准以图标的形式来设计和修改业务流程。同时也可以连接其已有的 信息系统 ( 例如 , ERP&n...  阅读全文
posted @ 2012-03-17 12:10 小马歌 阅读(758) | 评论 (0)编辑 收藏
 
     摘要: 如果你想知道你的服务器正在做干什么,你就需要了解一些基本的命令,一旦你精通了这些命令,那你就是一个 专业的 Linux 系统管理员。有些 Linux 发行版会提供 GUI 程序来进行系统的监控,例如 SUSE Linux 就有一个非常棒而且专业的工具 YaST,KDE 的 KDE System Guard 同样很出色。当然,要使用这些工具,你必须在服务器跟前进行操作,而且这些 GUI 的程序占用了...  阅读全文
posted @ 2012-03-16 20:24 小马歌 阅读(223) | 评论 (0)编辑 收藏
 

HTML5提供了一组API用来获取用户的地理位置,如果浏览器支持且设备具有定位功能,就能够直接使用这组API来获取当前位置信息。

该API是navigator对象的一个属性 – Geolocation。目前除了ie内核浏览器外,其他浏览器的最新版本基本都支持Geolocation。同时,移动设备IOS 3.0+ 和 Android 2.0+ 系统也支持它,现在很多移动设备的应用加入了地理定位的元素。

那么我们接下来看如何使用Geolocation API:

一、检查浏览器是否支持Geolocation

var hasGeolocation = !!(navigator.geolocation);
if(hasGeolocation){
alert(“浏览器支持hasGeolocation”);
}

二、navigator.geolocation 的方法:

* navigator.geolocation有三个方法,分别是getCurrentPosition()、watchPosition()和clearWatch()

getCurrentPosition()方法

* getCurrentPosition()方法检索用户的当前位置,但只检索一次。当该方法被脚本调用时,方法以异步的方式来尝试获取宿主设备的当前位置。

* 该方法最多可以有三个参数:

geolocationSuccess:带回当前位置的回调(callback)(必需的)
geolocationError:有错误发生时使用的回调(可选的)
geolocationOptions:地理位置选项(可选的)

调用如下所示:


navigator.geolocation.getCurrentPosition(geolocationSuccess, geolocationError, geolocationOptions);

watchPosition()方法

* watchPosition()方法定期轮询用户的位置,查看用户的位置是否发生改变。其最多可带三个参数。

调用如下所示:


var watchPositionHandler = navigator.geolocation.watchPosition(geolocationSuccess, geolocationError, geolocationOptions);

clearWatch()方法

* clearWatch()方法终止正在进行的watchPosition(),该方法只能带一个参数。在调用时,其找到之前已经开始了的watchID参数并立即停止它。

调用如下所示:


navigator.geolocation.clearWatch(watchID);

三、navigator.geolocation返回一个Position对象:

* Position对象有两个属性:timestamp和coords。timestamp属性表示地理位置数据的创建时间,coords属性又包含七个属性:

coords.latitude:估计纬度
coords.longitude:估计经度
coords.altitude:估计高度
coords.accuracy:所提供的以米为单位的经度和纬度估计的精确度
coords.altitudeAccuracy:所提供的以米为单位的高度估计的精确度
coords.heading: 宿主设备当前移动的角度方向,相对于正北方向顺时针计算
coords.speed:以米每秒为单位的设备的当前对地速度

* 注意altitude, altitudeAccuracy, heading, speed不一定被浏览器支持,所以大家最好看一下官方规范的描述,多一些了解。

四、注意事项

* Geolocation App是不能直接访问设备的,只能通过请求浏览器来访问设备;
* Geolocation涉及到用户隐私,在获取 Geolocation 的时候,需要先征求用户的意思。
* Geolocation目前没有比较好的前端兼容解决方案,但是在移动设备 iOS 和 Android上,我们可以大胆尝试使用。

posted @ 2012-03-16 15:25 小马歌 阅读(341) | 评论 (0)编辑 收藏
 

不得不佩服下谷歌Chrome团队,利用HTML5和CSS3实现了一本相当漂亮的在线电子书:《关于浏览器和互联网20件事》。

访问地址:http://www.20thingsilearned.com

话说这本电子书已经出来很久了,不过今天来看依然觉得相当的赞。我们无需刷新页面,就可以来回切换电子书页面,这正是OPOA(One Page One Application)的完美体现。

现在正在学习关于history API这方面的东西,所以特别感兴趣的是他们如何使用window.history.pushState()和window.history.replaceState()来做页面之间的不刷新切换。

今天查阅了一些资料,基本上对history API有了一个基本了解。

首先要说的就是history是个全局,即window.history。看到这个变量名你一定很熟悉,因为经常可以看到用window.history.back()或者window.history.go(-1)来返回上一页的JavaScript代码。

所以history并不是什么新东西,在HTML4的时代,我们可以使用它的这几个属性和方法:

length:历史堆栈中的记录数。

back():返回上一页。

forward():前进到下一页。

go([delta]):delta是个数字,如果不写或为0,则刷新本页;如果为正数,则前进到相应数目的页面;若为负数,则后退到相应数目的页面。

现在,HTML5为其又添加了以下2个方法:

pushState(data, title [, url]):往历史堆栈的顶部添加一条记录。data为一个对象或null,它会在触发window的popstate事件(window.onpopstate)时,作为参数的state属性传递过去;title为页面的标题,但当前所有浏览器都忽略这个参数;url为页面的URL,不写则为当前页。

replaceState(data, title [, url]):更改当前页面的历史记录。参数同上。这种更改并不会去访问该URL。不过目前只有Safari 5.0+、Chrome 8.0+、Firefox 4.0+和iOS 4.2.1+支持。如果想兼容老浏览器的话,可以试试History.js,而且它还修正了一些bug。

当然,在移动平台上,我们可以大胆尝试html5的history API。ios3.0+ 和Android2.0+ 平台的内置浏览器对history都比较完美了,利用它我们可以web app更趋向与native app。

下面,推荐几篇文章:

.Manipulating the browser history

.Session history and navigation

.Manipulating History for Fun & Profit

posted @ 2012-03-16 15:24 小马歌 阅读(2008) | 评论 (0)编辑 收藏
 

嘛,起因是黑子大叔在微博上的一条@信息,找起了这个的实现,看了一圈google的中文信息内似乎还没有怎么提到这个的内容,就发表上来。
详细效果就是类似于用Firefox4+/Chrome 5+/Safari 5+/Opera 11.5+登录新浪微博后看到的个人时间轴,在翻页时可以观察到这个效果,地址栏URL变动,但是页面没有刷新,用firebug观察也观察不到刷新整个页面,只有ajax请求的翻页数据。从先前的理解来说,URL的修改必然引起(请注意我不是在说通过锚点修改)浏览器重载页面,但利用HTML5新加入的history.pushState();和history.replaceState();可以完全自己维护一个历史记录列表绕开历史记录完全由浏览器控制的机制,从而实现比锚点更加完美的一种页内更新的体验。
代码方面很简单,只要在需要修改url的地方插入一行:
window.history.pushState({"html":response.html,"pageTitle":response.pageTitle}, 'title', urlPath);
//三个参数,分别为:状态对象,标题(目前被浏览器忽略),地址
即可在历史记录里面产生一个新的历史记录(另一个replaceState方法参数完全相同,只是替代掉当前的状态)。
在体验上,非常接近于使用锚点(window.location = “#foo”;),但是mozilla的文档提出了以下三点好处:
新的URL可以是和原始页面URL同域下的任何URL,如果用锚点,只能更新#后面的部分
仅在需要时更新URL,锚点的历史记录在相同时不会创建(即当前已经是”#foo”的情况下,如果再将当前页面锚点设置为”#foo”,不会产生新的历史记录)
可以记录下任意类型的数据,使用锚点的话将要自己维护一份历史数据列表或者把数据转码到一个字符串里
(翻译&描述的有点别扭,见笑了,不过其实应该自己也能体会到这些好处才是)
我自己实现的一个例子:
http://vifix.cn/atelier/demos/html5-update-browser-url-without-reloading-page
代码:
<?php
if(!isset($_REQUEST['page'])){
    $page = 1;
}
else{
    $page = intval($_REQUEST['page']);
}
 
if(isset($_REQUEST['ajaxload'])){
    echo "第{$page}页的内容";
    die;
}
?>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>HTML5 修改浏览器url而不刷新页面</title>
<script type="text/javascript">
var domLoaded = function(){
    if(ua != null && ua[1] < 10){
        alert('您的浏览器不支持');
        return ;
    }
 
    if(location.href.indexOf("?") > -1){
        var urlparts = location.href.match(/(.+?)\?.+/i);
        var urlbase = urlparts[1];
    }
    else{
        var urlbase = location.href;
    }
    var page = <?php echo $page;?>;
    var ua = window.navigator.userAgent.match(/msie (\d\.\d)/i);
    var content = document.getElementById("content");
    var loading = document.getElementById("loading");
 
    window.history.replaceState(
        {
            content:content.innerHTML,
            page:page
        },
        page,
        urlbase + (page > 1 ? '?page=' + page : '' )
    );
 
    var ajax = new XMLHttpRequest();
    var ajaxCallback = function(){
        if(ajax.readyState == 4){
            loading.style.display = 'none';
            content.innerHTML = ajax.responseText;
            window.history.pushState(
                {
                    content:content.innerHTML,
                    page:page
                },
                page,
                urlbase + "?page=" + page
            );
            next.href = urlbase + "?page=" + (page + 1);
        }
    };
 
    var next = document.getElementById('next');
    var nextClickEvent = function(event){
        if(loading.style.display != 'block'){
            loading.style.display = 'block';
            page++;
            ajax.open('GET', urlbase + '?page=' + page + '&ajaxload=on', true);
            ajax.onreadystatechange = ajaxCallback;
            ajax.send('');
            event.preventDefault();
        }
    };
    next.addEventListener('click', nextClickEvent, false);
 
    var popstate = function(){
        content.innerHTML = history.state.content;
        page = history.state.page;
    };
    window.addEventListener('popstate', popstate, false);
};
 
try{
    window.addEventListener('DOMContentLoaded', domLoaded, false);
}
catch(e){
    alert('您的浏览器不支持');    
}
</script>
</head>
<body>
    <p id="content">
        <?php echo "第{$page}页的内容";?>
    </p>
    <p>
        <a id="next" href="?page=<?php echo $page+1;?>">下一页</a>
    </p>
    <div id="loading" style="display:none;">
        加载中
    </div>
</body>
</html>
mozilla的文档
https://developer.mozilla.org/en/DOM/Manipulating_the_browser_history
stackoverflow上的相关问题:
http://stackoverflow.com/questions/3338642/updating-address-bar-with-new-url-without-hash-or-reloading-the-page
http://stackoverflow.com/questions/824349/modify-the-url-without-reloading-the-page
posted @ 2012-03-16 15:18 小马歌 阅读(2586) | 评论 (0)编辑 收藏
 

coreseek 是在Sphinx 基础上开发的全文检索软件, 具 体介绍详见文尾 附 录

=========================================================

一、       安装环境及介绍

1.      前提环境

系 统环境:centos5

操 作用户:root

文 中粗体字为需用户输入的命令内容

 

2.       源码包准备

下 载MMSEG 分词源码包 

wget  http://www.coreseek.cn/uploads/sources/mmseg3_0b3.tar.gz

下 载coreseek 源码包 

wget  http://www.coreseek.cn/uploads/sources/csft3_0b4.tar.gz

解 压缩源码包

tar -xzvf mmseg3_0b3.tar.gz

tar -xzvf csft3_0b4.tar.gz

 

 

3.      安装环境准备

安 装g++ 编译环境

yum install g++

yum install gcc

yum install make

 

安 装python 开发组件

yum install python

yum install python-dev

 

 

 

安 装make

yum install make

 

4.      编译mmseg

cd mmseg.3.0b3/

./configure --prefix=/ var / eyou /mmseg

make

make install

 

5.      编译coreseek

cd csft3_0b4/

./configure --prefix=/ var / eyou /coreseek --with-python --with-mysql --with-mmseg-includes=/ var / eyou /mmseg/include/mmseg --with-mmseg-libs=/ var / eyou /mmseg/lib/

make & make install

 

 

此步安装完成后, 将在/ var / eyou / 下生成 coreseek 目录

coreseek 目录中有三个目录分别为bin 、 etc 、var

bin 中 存有sphinx 用到的一些执行文件 包括 indexer 索引建立 search 查询工具 searchd 查询服务器 等

etc 中是配置文 件,该目录中 有一个sphinx.conf. disk ,这个相当于sphinx 的 配置例子文件,我们以这个文件为蓝本,重新创建一个空白内容的sphinx.conf ,存放在 etc

 

 

6.      创建dict 目录

创建字典目录:

mkdir /var/eyou/coreseek/dict/

产生字典步骤:

cd /root/soft/ mmseg.3.0b3/data

/var/eyou/mmseg/bin/mmseg -u unigram.txt

产生了unigram.txt.uni , 移到相应目录。

cp unigram.txt.uni /var/eyou/coreseek/dict/uni.lib

创建 / var / eyou /coreseek/dict/mmseg.ini

内容:

[mmseg]

merge_number_and_ascii=1;

number_and_ascii_joint=-;

compress_space=0;

seperate_number_ascii=1;

#merge_number_and_ascii: 字母和数字连续出现是非切分

#number_and_ascii_joint: 连接数字和字母可用的符号,如'-' '.' 等

#compress_space :暂时无效

#seperate_number_ascii :是否拆分数字,如 1988 -> 1/x 9/x 8/x 8/x

 

7.       php 调用 sphinx api

 

  通过官方API 调用Sphinx ,具体为:

coreseek 安装目录有一个API 目录,里面有三个PHP 文 件:test.php ,test2.php 和sphinxapi.php 。 sphinxapi.php 是sphinx 调用接口封装文件(这个文件就是官方提供的php 调 用API ),test.php 是一个在命令行下执行的查询例子文件,test2.php 是 一个生成摘要的 例子文件。

 

8.       配置 sphinx.conf

     具体参见sphinx.conf 的 注释说明

9.       启动服务

 

建 立索引

/var/eyou/coreseek/bin/indexer  --config  /var/eyou/coreseek/etc/sphinx.conf

启 动 searchd 服务

/var/eyou/coreseek/bin/searchd --config /var/eyou/coreseek/etc/sphinx.conf

 

 

 

二、       附录 :全文检索----coreseek

 

 

1.    全文搜索与数据库搜索的区别

o          专为全文搜索优化,效率更高
由于典型的数据库系统要考虑用户的“增删改查”等多种复杂操作,因此其存取数据的方式需要考察综合考虑各种应用;而全文搜索的数据存取方式 只考虑快速读取,相比数据库的查询,要快10 倍或更多。(即使启用了数据库内置的全文搜索功能,这个结论仍成立)。

o          支持复杂的查询表达式
数据库系统的查询,往往只支持“AND ” 或 "OR" 等有限的模式,而全文检 索不但支持"AND" 、“OR ” 查询,还支持“NOT ”、“近似”、 “整句”等多种查询方式;同时相比数据库系统,进行在一定范围内查询时也更高效

o          支持按相关度排序
数据库查询出的结果,往往按照数据库内置的排序规则进行排序,往往只能按时间、按点击等有效的排序规则进行;全文搜索除了能够支持数据库的 排序规则外,还支持按照结果的相关度排序,这往往会给访问者带来更大的便利。

o          支持中文分词
数据库提供的全文搜索功能往往不支持中文分词(或仅提供二元切分),导致某些短语检索不到或出现大量不相干的数据;中文全文检索系统支持中 文分词,进一步过滤了不相干的数据。

2.    自建全文搜索与使用Google 等第三方网站提供的站内全文搜索的区别

o          对网站设备有要求
自建全文搜索往往需要站长有至少一台独立主机,而使用第三方提供的全文搜索对站点的要求低,虚拟主机即可;不过,出现全文搜索需求的站点通 常已经有自己的独立主机了。

o          索引更新更及时
由于搜索服务在第三方托管,其往往只能按照一定的规则定期更新索引库(往往是几小时、甚至几天才更新一次索引),您网站上的新出现的内容往 往不能及时被搜索到;
使用自建全文搜索,可以保证您网站上新出现的内容可以”立即“被检索到。

o          更适应您的网站
由于中文需要进行分词的特性,导致没有一套通用的词库可以适用于全部网站,要得到优秀的检索结果需要定制一套适用于您网站的词库;
采用第三方的搜索服务,您是无法修改第三方厂商的词库的,而使用自建全文搜索则无此问题。

o          更有利于您网站的数据整合
有些网站不止是论坛,往往还包括内容管理(CMS) 、商城等多种应用,而使用数据库搜索往往需要用户在各个系统 中都进行搜索才能找到内容;
第三方的检索无法区别各个系统的不同。而自建的全文搜索可以有效的区分各个数据来源的不同数据,真正做到一次搜索应有尽有,从而改善您网站 的访问体验,增加您网站的点击率。

o          避免您的访问者遇到某些尴尬
第三方的搜索结果页面不是您可以控制修改的,有推荐一些可能会使您网站访问者尴尬的搜索短语的可能。而使用自建全文搜索则完全无此问题。
posted @ 2012-03-16 10:29 小马歌 阅读(799) | 评论 (1)编辑 收藏
仅列出标题
共95页: First 上一页 38 39 40 41 42 43 44 45 46 下一页 Last