Elgg分布式扩展和性能优化(二)

来源:互联网 发布:wps是啥软件 编辑:程序博客网 时间:2024/06/11 17:02

继续来谈关于Elgg的种种,首先从LAMP的MySQL开始

4. MySQL InnoDB vs MyISAM

传统的LAMP应用默认都使用的是MySQL的MyISAM引擎,关于InnoDB引擎和MyISAM引擎的比较,网上可以搜到的资料很多,这里就不详细展开了,简单来说InnoDB的事务、行级锁定等特性在高并发的情况下对数据库性能有相当的提升。实际上,MySQL在5.5版本之后已经将默认引擎更换为InnoDB了。因此把Elgg的数据库表转换为InnoDB类型是个简单有效的优化手段。

对Elgg而言,大部分的表都可以直接转换为InnoDB类型,不过由于InnoDB不支持MyISAM的全文搜索功能,因为有几个加了全文搜索的表暂时不能转换,仍然保留MyISAM类型。如果想进一步完全转换到InnoDB,就要考虑去掉利用数据库的全文搜索功能,使用自己实现的第三方全文搜索来替代(如Lucene)。

总结一下,Elgg数据库里可以转换为InnoDB类型的表包括(表前缀elgg_)

elgg_access_collection_membership
elgg_access_collections
elgg_annotations
elgg_api_users
elgg_config
elgg_datalists
elgg_entities
elgg_entity_relationships
elgg_entity_subtypes
elgg_metadata
elgg_metastrings
elgg_private_settings
elgg_river
elgg_users_sessions

保留MyISAM类型的表包括

elgg_groups_entity
elgg_objects_entity
elgg_sites_entity
elgg_system_log
elgg_users_entity

还剩下两个是Memory类型的,不需要改变。

5.  Elgg Metadata non thread-safe bug

Elgg的Metadata在实现上有一个很严重的bug,这个bug会导致数据库数据异常,而且这个bug到目前为止官方都没有修复,和数据库也有一定的关系,所以在这里先写出来。

Elgg的metadata提供了一种灵活的给对象附加属性的方式,比如  $blog->author='Daniel' 这样一句话就可以对一个blog对象赋予一个名字为author、值为Daniel的metadata,而在数据库里不用事先申明这个author字段(是不是有点NoSQL的感觉)。并且,Elgg说明可以为一个metadata赋予string或者array类型的值,问题就出在这里。

在实现上,->实际上是PHP5的magic methods,最终会调用ElggEntity类里的setMetaData这个方法

public function setMetaData($name, $value, $value_type = null, $multiple = false) {// normalize value to an array that we will loop over// remove indexes if value already an array.if (is_array($value)) {$value = array_values($value);} else {$value = array($value);}// saved entity. persist md to db.if ($this->guid) {// if overwriting, delete first.if (!$multiple) {                $options = array('guid' => $this->getGUID(),'metadata_name' => $name,'limit' => 0);// @todo in 1.9 make this return false if can't add metadata// http://trac.elgg.org/ticket/4520// // need to remove access restrictions right now to delete// because this is the expected behavior$ia = elgg_set_ignore_access(true);if (false === elgg_delete_metadata($options)) {return false;}elgg_set_ignore_access($ia);}// add new md$result = true;foreach ($value as $value_tmp) {// at this point $value should be appended because it was cleared above if needed.$md_id = create_metadata($this->getGUID(), $name, $value_tmp, $value_type,$this->getOwnerGUID(), $this->getAccessId(), $multiple);if (!$md_id) {return false;}}return $result;}// unsaved entity. store in temp array// returning single entries instead of an array of 1 element is decided in// getMetaData(), just like pulling from the db.else {// if overwrite, delete firstif (!$multiple || !isset($this->temp_metadata[$name])) {$this->temp_metadata[$name] = array();}// add new md$this->temp_metadata[$name] = array_merge($this->temp_metadata[$name], $value);return true;}}

bug出现在:当有多个进程同时并发的写同一个metadata值时,比如有100个进程同时写$blog->author=’Daniel’ ,在数据库中就会重复出现相同的多条记录,如果并发持续的写的话,重复记录会迅速增长,严重的情况下,取该条metadata值会耗尽php可用内存,直接导致网站崩溃。

这是一个明显的race condition问题,其原因就是在上面的那段代码里,每当为一个metadata赋值时,会首先检查是否存在该metadata,如果存在,那么就先删除该条metadata的所有记录,然后再创建新值。在并发写的情况下,可能某个进程已经删除了该metadata,另一个进程在查询时会发现没有该记录,于是直接创建一条新记录,而此时前一个进程又会继续执行创建新记录,这样就导致了数据的重复。

实际上,“先删除再创建”这种机制最好的解决方法就是 – 使用事务(还记得之前我们为什么要把数据库换成InnoDB么),把上面的过程用事务-回滚的机制改写,就能彻底解决这个问题。

这个问题出现的根本原因,实际上是由于Elgg没有限制metadata为一个值或多个值,导致无法事先确定该metadata会有几条记录存在,因此只能用“先删除后创建”的笨办法保证逻辑的正确。所以这是一个设计上的问题,而Elgg的开发者现在也没有想出什么好的办法来解决,除非使用事务,这个bug也一直存在于Elgg的代码中:(

如果不想使用事务这种相对复杂的逻辑来解决这个问题,我也想出了一个临时的解决方案,不过首先要保证避免在开发中使用metadata来存储array,使得metadata的逻辑更简单。然后只需要在上述代码中注释掉

// if overwriting, delete first.if (!$multiple) {                $options = array('guid' => $this->getGUID(),'metadata_name' => $name,'limit' => 0);// @todo in 1.9 make this return false if can't add metadata// http://trac.elgg.org/ticket/4520// // need to remove access restrictions right now to delete// because this is the expected behavior$ia = elgg_set_ignore_access(true);if (false === elgg_delete_metadata($options)) {return false;}elgg_set_ignore_access($ia);}

这一部分就可以了,因为后面create_metadata方法实际上会检查如果已存在值,就更新,如果没有,就插入。这样做就能保证在并发写的情况下,只有一条记录被创建或更新,当然,最终的赋值是什么,要取决于最终是哪个进程最后调用并成功执行了sql语句。
0 0
原创粉丝点击