数据处理中提升性能的方法-引入并发但是避免同步

背景

只要存在数据库,就会有后台批量处理数据的需求,比如数据表备份、定期清理、数据替换、数据迁移,对于批量处理来说,往往会涉及大量的查询、过滤、归类、聚合计算,在批量脚本中直接查询数据库往往性能太低,甚至会因为一个大型的SQL导致数据库锁表出现线上事故,因此一般采用先导出到文件,在文件上计算然后再导入,比如:

1、使用mysql -e “select * from table” > output.txt的方式,执行SQL,将结果导出到文件中;

2、针对文件,使用各种方式进行聚合、过滤、替换等计算,最后产出成需要使用的格式;

3、发布产出的文件,或者使用load data命令导入到数据库;

由于只是一次性的批量查询数据库导出到文件,然后针对文件进行计算,而不是每次都查询数据库,大量节省了网络的IO耗费,从而提升处理的速度。

然而得到了导出的文件之后,如果文件过大,或者计算逻辑复杂比如大量的调用了耗费CPU的正则匹配、聚合计算,那么单线程的处理会耗费大量的时间,这时候就可以引入并发处理,使得机器的CPU、内存、IO、网络等资源全部充分利用起来,大幅度降低处理时间。

引入多线程,拆分输入文件为多个,每个小文件启动一个处理线程

HADOOP的MAP-REDUCE的做法,是先将文件split成小分片文件,然后针对每个分片做计算,最后将每个分片的结果聚合在一起,然而由于HADOOP的调度、集群稳定性等各种原因,对于MB大小级别的文件处理,会发现速度非常慢,有时候甚至比单机单线程处理速度还慢,将单机单线程改成多线程,往往会发现令人惊讶的效果提升。

直观的做法,是使用主线程读取输入的单个大文件,然后将读取的结果分配给子线程处理,然后主线程做整合,这种方式因为多线程共用了单个文件的IO,需要加入对文件的同步机制,最后会发现性能瓶颈在这单个文件的读取同步之上。

可以将大文件分片成小文件,然后每个文件分配给单个线程单独处理,避免线程间的资源同步,每个线程会享用单独的CPU核、内存单元、文件句柄,处理速度能达到最快。

使用这种方式,可以用以下的步骤进行:

1、使用SHELL,将输入文件拆分成预定线程数目的份数,存放到一个目录中;

2、以输入文件的目录路径作为参数,编程语言JAVA/PYTHON读取该目录的所有文件,对于每个文件启动一个处理线程,进行处理;

3、SHELL将输出目录的所有文件,使用cat file* > output_file的方式,得到最终的计算结果

#
# 将输入文件拆分成多个小文件,启动多线程进行处理,输出结果文件
#
function run_multi_task(){
	# 开启多个异步线程
	SPLITS_COUNT=20
	# 输入文件总数
	source_file_lines_count=`cat ${input_file} | wc -l`
	# 计算出拆分的文件个数
	split_file_lines_count=$(( $source_file_lines_count / $SPLITS_COUNT ))
	# 进行文件拆分
	split -l $split_file_lines_count -a 3 -d ${input_file} ${input_dir}/inputFile_
	
	# 执行JAVA程序
	$JAVA_CMD -classpath $jar_path "net.crazyant.BackTaskMain" "${input_dir}" "${output_dir}" "${output_err_dir}"
	
	# 合并文件
	cat ${output_dir}/* > ${output_file}
}

run_multi_task

这里注意,拆分文件的时候,不能使用split按照大小进行拆分,因为会把输入文件中的行截断;

对应的JAVA程序,则是通过读取文件夹中文件列表的方法,每个文件单独启动一个线程:

public class BackTaskMain {
    public static void main(String[] args) {
        String inputDataDir = args[1];
        String outputDataDir = args[2];
        String errDataDir = args[3];
        
        File inputDir = new File(inputDataDir);
        File[] inputFiles = inputDir.listFiles();
        
        // 记录开启的线程
        List<Thread> threads = new ArrayList<Thread>();
        for (File inputFile : inputFiles) {
            if (inputFile.getName().equals(".") || inputFile.getName().equals("..")) {
                continue;
            }
            
            // 针对每个inputFile,生成对应的outputFile和errFile
            String outputSrcLiceFpath = outputDataDir + "/" + inputFile.getName() + ".out";
            String errorOutputFpath = errDataDir + "/" + inputFile.getName() + ".err";
            
            // 创建Runnable
            BackRzInterface backRzInterface = new BackRzInterface();
            backRzInterface.setInputFilePath(inputFile.getAbsolutePath());
            backRzInterface.setOutputFilePath(outputSrcLiceFpath);
            backRzInterface.setErrorOutputFpath(errorOutputFpath);
            
            // 创建Thread,启动线程
            Thread singleRunThread = new Thread(backRzInterface);
            threads.add(singleRunThread);
            singleRunThread.start();
        }
        
        for (Thread thread : threads) {
            try {
                // 使用thread.join(),等待所有的线程执行完毕
                thread.join();
                System.out.println(thread.getName() + " has over");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("proccess all over");
    }
}

通过这种方式,将大文件拆分成小文件,启动多个线程,每个线程处理一个小文件,最终将每个小文件的结果聚合,就得到了最终产出,性能上却大幅提升。

若有依赖的资源,可以按线程先复制、拆分、克隆,防止依赖的资源成为性能瓶颈

在上面的代码中,BackRzInterface是每个线程启动时要使用的Runnable对象,可以看到用的是每次new的方式创建:

// 创建Runnable
BackRzInterface backRzInterface = new BackRzInterface();

这样每个处理线程依赖的BackRzInterface都是独立的,对这个Runnable代码的使用不会存在同步问题。

如果多线程处理中需要使用外部资源,最好想办法使得每个线程单独使用自己独占的资源,相互之间若不会存在冲突,可以实现最大化并发处理。

其他一些例子,比如:

  • 多线程用到了字典文件,那么方法是首先将字典文件复制多份,每个线程使用自己独占的字典,避免并发同步访问字典;
  • 多线程若需要统一ID发号,可以提前计算出每个输入文件的行数,然后依次生成第一个线程需要的ID范围、第二个线程需要的ID范围,这些不同的ID范围也可以分别生成不同的文件,这样每个线程会使用各自独立的ID资源,避免了多个线程单时刻访问单个ID发号服务,使得发号成为性能瓶颈的可能;
  • 多线程如果依赖相同的Service,如果可以每次new对象就每次new,如果Bean都是在Spring中管理,则将Service加上@Scope(“prototype”),或者将对象每次clone一下得到一个新对象,保证最终每个线程使用自己独占的对象。
  • 尽量使用函数式编程的思想,每个函数都不要产生副作用,不要修改入参,结果只能通过return返回,避免增加代码同步冲突的可能;

通过以上这些类似的方法,每次将可能需要同步访问的共享资源,通过复制、分片等手段得到不同份,每个线程单独访问自己那一份,避免同步访问,最终实现性能最优。

避免同步的终极方法:使用多进程进行实现资源隔离

如果将文件拆分成了多份,依赖的ID、词典等资源也相应提供了多份,但是发现代码中存在无法解决的代码级别同步,该怎么办呢?

相对于想尽办法解决代码中的同步问题来说,多线程和多进程之间的性能差别微乎其微,我们都知道线程会使用进程的资源,所以导致了线程之间存在竞争进程资源,但是对于进程来说,CPU、内存等硬件资源是完全隔离的,这时候将程序运行在多进程而不是多线程,反而能更好的提升性能。

对于一些支持多线程不好的语言,比如PHP,直接用这种多进程计算的方法,速度并不比支持多线程的JAVA、PYTHON语言差:

# 要拆分的文件数,也就是要启动的多进程数
SPLITS_COUNT=20

input_splits_dir="${input_dir}_splits"
output_splits_dir="${output_dir}_splits"
# 输入文件行数
source_file_lines_count=`cat ${input_file} | wc -l`
# 每个文件拆分的行数=总行数除以要拆分的文件个数(也就是对应进程的个数)
split_file_lines_count=$(( $source_file_lines_count / ${SPLITS_COUNT} ))
# 执行拆分,注意这里使用-l进行行级别拆分更好
split -l $split_file_lines_count -a 3 -d ${input_file} ${input_splits_dir}/inputfile_

process_idx=1
for fname in $(ls ${input_splits_dir}); do
	input_fpath=${input_splits_dir}/$fname
	ouput_fpath=${output_splits_dir}/$fname
	# 后台执行所有进程
	php "/php/main.php" "${input_fpath}" "${ouput_fpath}" &
	(( process_idx++ ))	
done

# 等待所有后台进程执行结束
wait

# 合并文件
cat $output_splits_dir/* > ${output_file}

上述代码中,使用shell的&符号,可以在后台同时启动多个进程,使用wait语法,可以实现多线程的Thread.join特性,等待所有的进程执行结束。

总结

对于输入文件的大小、计算的复杂度处于单机和集群计算之间的数据处理,使用并发处理最为合适,但是并发的同步处理却会降低多线程的性能,这时可以借助于输入文件复制拆分、依赖资源复制拆分切片等方法,实现每个线程处理自己的独占资源,从而最大化提升计算速度。而对于一些无法避免的代码同步冲突逻辑,可以退化为多进程处理数据,借助于SHELL的后台进程支持,实现进程级别的资源独占,最终大幅提升处理性能。

 

使用PHPUnit编写PHP单元测试的方法

局限于Java的testng在eclipse中非常强大的手动单测方法这个框框,我试图一直给eclipse安装PHP的测试框架,却发现一直出问题,最后才发觉,PHPUnit的单测思想是直接的脚本测试PHP文件,避免手工的操作。

测试本来就应该自动化,不是吗?所以我接受了PHPUnit的命令行测试方法,而不是在eclipse中手动的右键运行测试。

PHPUnit官网:https://phpunit.de/

使用方法在文档中很明确,加以补充:

1、将PHP的安装目录,加入到Windows的PATH环境变量中

2、任意的建立一个PHP的二进制文件可执行目录,比如c:\bin

3、将c:\bin加入到Windows的PATH环境变量中

4、下载 https://phar.phpunit.de/phpunit.phar 并将文件保存到 C:\bin\phpunit.phar

5、打开命令行(例如,按 Windows+R » 输入 cmd » ENTER)

6、建立外包覆批处理脚本(最后得到 C:\bin\phpunit.cmd):

C:\Users\username> cd C:\bin
C:\bin> echo @php "%~dp0phpunit.phar" %* > phpunit.cmd
C:\bin> exit

7、新开一个命令行窗口,确认一下可以在任意路径下执行 PHPUnit:

C:\Users\username> phpunit --version
PHPUnit x.y.z by Sebastian Bergmann and contributors.

如果在任意命令行下,执行phpunit不会报错,就说明安装成功了。

做一下测试,编写一个文件,注意下面的namespace和引入PHPUnit的方法:

<?php

namespace mytest;

/**
 * test PHPUnit
 * 
 * @author www.crazyant.net
 *        
 */
class MyTestClass extends \PHPUnit_Framework_TestCase {
    /**
     * Testing the answer to “do you love unit tests?”
     */
    public function testDoYouLoveUnitTests() {
        $love = true;
        $this->assertTrue($love);
    }
}  

这个文件放置在C:\tmp\test_phpunit.php

然后打开命令行,执行以下命令,即可完成测试:

C:\tmp>cd c:\tmp

C:\tmp>phpunit test_phpunit.php
PHPUnit 5.1.3 by Sebastian Bergmann and contributors.

.
/ 1 (100%)

Time: 106 ms, Memory: 9.50Mb

OK (1 test, 1 assertion)

C:\tmp>

既然命令行可以执行测试,那么当然很方便的将测试命令都写到一个自动化的.bat或者.bash文件中,每次开发完成,执行全量的自动测试即可。

PHPUnit官网地址:https://phpunit.de/documentation.html

 

本文地址:http://crazyant.net/1898.html,转载请注明来源。

Magento获取指定分类下的所有子分类信息

二次开发中经常遇到一种情况,就是判断一个分类是否属于某一个大的分类,而大的分类包括很多子分类成树状结构,基本的方法都是先获取大分类下的所有子分类的信息,然后再挨个比较看是否有ID和待定ID相等。一种用递归方法获取大分类所有的ID;第二种用队列等非递归的方法获取所有子分类ID,第二种明显性能要好一些。

本文在magento的模板文件里测试成功一个函数:输入一个分类的ID,将返回该分类下所有子分类(递归获取)的ID,组成一个数组返回。所用的方法为用队列实现的非递归方法:

/**
* 输入:某一个分类的ID数字
* 返回: 该分类下所有子分类的ID组成的数组
* 可用于: 模板文件中可以直接使用,也可以用于action等文件类内部
* 实现思路:使用队列的方法实现非递归,对树从上往下遍历
**/
function getAllChildrenOfCategory($cateid){
    $resArr = array();//结果数组
    $queueArr = array();//队列数组
    array_push($queueArr,$cateid);
    
    while($currentcid = array_pop($queueArr)){
        array_push($resArr,$currentcid);                                            
        //处理当前节点的孩子节点
        $_category = Mage::getModel('catalog/category')->load($currentcid);
        $subcats = $_category->getChildren();
        
        $idarrs = explode(',',$subcats);
        foreach($idarrs as $subCatid){
            if(!$subCatid) continue;
            $_subcategory = Mage::getModel('catalog/category')->load($subCatid);
            if($_subcategory->getIsActive()) {
                array_push($queueArr,$subCatid);
            }
        }
        reset($queueArr);
    }
    return $resArr;
}
//测试一下
$allProducerIds = getAllChildrenOfCategory(19);
$allDesignedIds = getAllChildrenOfCategory(18);

 

PHP没有栈和队列的数据结构,可以用数组来模拟实现,数组的array_push和array_pop刚好就是这么两个方法,其中因为array_pop每次会改变数组的指针,所以可以在循环的末尾reset一下重置数组。

最后的测试,输入的是两个分类的ID,函数执行结束以后,返回的数组里面就是所有子分类的ID。

PHP对数组的高级遍历和操作处理方法

PHP对数组的处理可以称为该语言最有吸引力的特性之一,它支持70多种数组相关的函数。不论你想翻转一个数组、判断某个值在数组中是否存在、将数组转换成一个字符串还是计算数组的大小,仅仅执行一个现有的函数就可以完成。然而也有一些数组相关的任务对开发者的要求就较高,仅仅知道手册有某个功能是不能解决的,这些任务就需要对PHP的原始特性有一些深入的理解,还需要一些解决问题的想象力。

多维关联数组排序

PHP提供了一些数组排序的函数,比如sort(), ksort(),和asort(),但是却没有提供对多维关联数组的排序。

继续阅读PHP对数组的高级遍历和操作处理方法

使用PHP连接、操纵Memcached的原理和教程

Memcahced开源分布式内存对象缓存系统通过减少数据库的负担,从而能够加速你的web应用。在本文中我将解释怎样实现一个基于Memcahced的缓存系统。

数据库

实例中使用的数据库表包含一个自增的id,一个标题和一个链接字段:

CREATE TABLE demos
(
id INT PRIMARY KEY AUTO_INCREMENT,
title VARCHAR(300), 
link VARCHAR(300), 
);

继续阅读使用PHP连接、操纵Memcached的原理和教程

PHP开发者最好的学习资源收集

以下内容翻译自:phpbuilder.comPHP Developer Resources文章。资源全为英文资料,不过英文很多文章确实写的非常好!
当今PHP是开发WEB应用程序最流行的脚本语言之一。由此可以发现internet.com上有非常多的好PHP资源。以下是我们的一些最好的PHP资源,包括一些有特色的教程和一些你可能不知道的离线资源。

有特色的PHP教程

  • 学习PHP
  • 使用PHP处理文件
  • 对PHP的介绍
  • 怎样使用PHP和MYSQL实现搜索功能
  • PHP的类继承
  • 怎样在SQL数据库中直接存储图片
  • PHP的类
  • 使PHP作为一种Shell脚本语言
  • 使用PHP发邮件的时候,怎样防止垃圾邮件

离线资源

  • PHP官方文档
  • Zend引擎
  • PHPDeveloper
  • PHP Weblog

确实有一些好资源,这些关键词可以作为搜索引擎搜索,另外internet.com的PHP模块的文章真的很不错。

其他的一些好资源

http://www.9lessons.info

http://phpacademy.org

PHP和MySQL处理树状、分级、无限分类、分层数据的方法

文章标题中的多个词语表达的其实是一个意思,就是递归分类数据,分级数据非常类似数据结构中的树状结构,即每个节点有自己的孩子节点,孩子结点本身也是父亲节点。这是一个递归、分层形式。可以称之为树形层级数据。

层级数据结构是编程语言中非常普通的一种数据结构,它代表一系列的数据每一项都有一个父亲节点(除了根节点)和其他多个孩子结点。WEB开发人员使用层级数据结构用于非常多的场景,包括内容管理系统CMS、论坛主题、邮件列表,还有电子商务网站的产品分类等。

本文章主要介绍了使用PHP和MYSQL来管理分级数据的方法,在其中将给出两种最流行的分级数据模型:

  • 邻接表模型
  • 嵌套集合模型

PHP创建和解析JSON数据的方法

JSON可以解释为“JavaScript的对象表示方法”,也就是说JSON的概念是来源于JavaScript的,对于WEB开发模式来说,下面这个图大家肯定很熟悉:

2012-8-18 225813

由此可以看到,客户端浏览器和服务器端脚本需要交换数据。对于小数据或无格式数据,直接用简单的POST和GET用字符串方法提交过去就可以,但是一些大型的数据,最好使用有格式的方法交换,比如JSON和XML。

JSON是什么?

JSON一种用于在互联网络中传输超量数据的数据交换格式。虽然在Internet中交换数据XML占主要地位,然而JSON却更加的简单并且适用于轻型的数据。

尽管JSON当初是被JavaScript发明并且用于访问远程数据的,它现在却被广泛适用于各种语言,因为JSON是一种于平台无关的数据格式。

JSON数据的数据类型和实例

JSON支持各种各样的数据类型,包括数字、字符串、布尔值、数组数据甚至对象数据(是一个集合,每个元素都是键:值对形式,使用逗号分隔,并且使用大括号包裹)。

让我们看一个JSON数据的简单例子,该例子表示一个雇员的详细信息:

{“id”:”1″,”name”:”mike”,”country”:”usa”,”office”:[“microsoft”,”oracle”]}

使用PHP创建和解析JSON数据的方法

PHP从5.2.0版本以后提供了JSON扩展来处理JSON数据,通过PHP有两个函数json_encode()json_decode非常方便的对JSON数据进行转换和解析。

首先,让我们看一段PHP用数组创建如上各式的JSON的代码:

$json_data = array ('id'=>1,'name'=>"mike",'country'=>'usa',"office"=>array("microsoft","oracle"));
echo json_encode($json_data);

 

该代码直接产生了JSON数据。现在让我们用PHP将上面的JSON解码:

$json_string='{"id":1,"name":"mike","country":"usa","office":["microsoft","oracle"]} ';
$obj=json_decode($json_string);

 

现在变量$obj包含了使用PHP解析后的JSON数据,你可以使用以下的方法来输出和访问:

echo $obj->name; //displays mike
echo $obj->office[0]; //displays microsoft

 

你猜的没错$obj->office是一个数组,你能够使用PHP的foreach方法遍历它:

foreach($obj->office as $val)
    echo $val;

 

PHP的验证码实现(w3schools推荐)

本文使用PHP一些可用的特性实现了验证码功能。该教程非常的简单,使用可以改变的字体生成了验证码图片,正如我们所了解的,验证码是用于避免垃圾评论或者自动提交的。

本验证码程序的资源:

下载地址

在线演示

captcha_code_file.php

代码:

<?php 
session_start();
//设置: 你可以在这里修改验证码图片的参数
$image_width = 120;
$image_height = 40;
$characters_on_image = 6;
$font = './monofont.ttf'; 

//以下字符将用于验证码中的字符 
//为了避免混淆去掉了数字1和字母i
$possible_letters = '23456789bcdfghjkmnpqrstvwxyz';
$random_dots = 10;
$random_lines = 30;
$captcha_text_color="0x142864";
$captcha_noice_color = "0x142864"; 

$code = ''; 

$i = 0;
while ($i < $characters_on_image) { 
    $code .= substr($possible_letters, mt_rand(0, strlen($possible_letters)-1), 1);
    $i++;
}

$font_size = $image_height * 0.75; 
$image = @imagecreate($image_width, $image_height);

/* 设置背景、文本和干扰的噪点 */ 
$background_color = imagecolorallocate($image, 255, 255, 255);

$arr_text_color = hexrgb($captcha_text_color); 
$text_color = imagecolorallocate($image, $arr_text_color['red'], 
$arr_text_color['green'], $arr_text_color['blue']);

$arr_noice_color = hexrgb($captcha_noice_color); 
$image_noise_color = imagecolorallocate($image, $arr_noice_color['red'], 
$arr_noice_color['green'], $arr_noice_color['blue']);

/* 在背景上随机的生成干扰噪点 */ 
for( $i=0; $i<$random_dots; $i++ ) {
    imagefilledellipse($image, mt_rand(0,$image_width),
    mt_rand(0,$image_height), 2, 3, $image_noise_color);
}

/* 在背景图片上,随机生成线条 */ 
for( $i=0; $i<$random_lines; $i++ ) {
    imageline($image, mt_rand(0,$image_width), mt_rand(0,$image_height),
    mt_rand(0,$image_width), mt_rand(0,$image_height), $image_noise_color);
}

/* 生成一个文本框,然后在里面写生6个字符 */ 
$textbox = imagettfbbox($font_size, 0, $font, $code); 
$x = ($image_width - $textbox[4])/2;
$y = ($image_height - $textbox[5])/2;
imagettftext($image, $font_size, 0, $x, $y, $text_color, $font , $code);

/* 将验证码图片在HTML页面上显示出来 */ 
header('Content-Type: image/jpeg');// 设定图片输出的类型
imagejpeg($image);//显示图片
imagedestroy($image);//销毁图片实例
$_SESSION['6_letters_code'] = $code;

function hexrgb ($hexstr) {
    $int = hexdec($hexstr);

    return array( "red" => 0xFF & ($int >> 0x10),
                "green" => 0xFF & ($int >> 0x8),
                "blue" => 0xFF & $int
    );
}
?>

 

验证验证码正确或错误的方法

验证码图片上的文字被存放到了SESSION 变量里面,验证的时候,我们需要将SESSION 里面的值和用户输入的值进行比较即可。

$_SESSION[6_letters_code] – 存放着验证码的文字值
$_POST[6_letters_code] – 这是用户输入的验证码的内容

index.php代码

包含了HTML, CSS, Javascript and PHP代码:

<?php session_start(); 

if(isset($_REQUEST['Submit'])){ 
    // 服务器端验证的代码
    if(empty($_SESSION['6_letters_code'] ) ||
        strcasecmp($_SESSION['6_letters_code'], $_POST['6_letters_code']) != 0)
    { 
        $msg="验证失败!";
    }else{
        //验证码验证正确,这里放验证成功后的代码
    }
} 
?>
<style type="text/css">
.table {
    font-family:Arial, Helvetica, sans-serif;
    font-size:12px;
    color:#333;
    background-color:#E4E4E4;
}
.table td {
    background-color:#F8F8F8;
}
</style>

<form action="" method="post" name="form1" id="form1" >
  <table width="400" border="0" align="center" cellpadding="5" cellspacing="1" class="table">
    <?php if(isset($msg)){?>
    <tr>
      <td colspan="2" align="center" valign="top"><?php echo $msg;?></td>
    </tr>
    <?php } ?>
    <tr>
      <td align="right" valign="top"> 验证码:</td>
      <td><img src="captcha_code_file.php?rand=<?php echo rand();?>" id='captchaimg'><br>
        <label for='message'>请输入上面的验证码 :</label>
        <br>
        <input id="6_letters_code" name="6_letters_code" type="text">
        <br>
        无法读图片吗?点击 <a href='javascript: refreshCaptcha();'>here</a> 刷新
        </p></td>
    </tr>
    <tr>
      <td> </td>
      <td><input name="Submit" type="submit" onclick="return validate();" value="提交"></td>
    </tr>
  </table>
</form>
<script type='text/javascript'>
function refreshCaptcha()
{
    var img = document.images['captchaimg'];
    img.src = img.src.substring(0,img.src.lastIndexOf("?"))+"?rand="+Math.random()*1000;
}
</script>

 

国外10个非常有趣的PHP博客

David Walsh blog

David Walsh是一个非常有经验的PHP开发者,他参与了很多大型的项目。在他的博客中你能发现几乎每天的对PHP开发的建议。

Roshan’s blog

在该Roshan Bhattarai博客中你能够发现非常多的关于PHP开发的很有趣的文章。

Woork – Antonio Lupetti的博客.

Antonio Lupetti是一个很专业的博客写作人,他写的文章大部分都是关于PHP编程的。

Tobias Schlitt – 充满了对PHP的热爱 

是一个Tobias Schlitt写的专注于PHP的博客。分类包括PHP, PEAR, Geek等等。

Paul M. Johns Blog

Paul是一个国际上承认的PHP专家,现在是OmniTI公司的一个高级程序员。在博客中,他写了很多关于他的项目的文章,但也涉及到了管理、商业、政治和文化的一些内容。

Antti H. – 一个通向软件技术的旅程

Antti Holvikari所作的一个非常有趣的关于PHP开发的博客。

There and Back Again

Joshua Eichorn所作的专注于AJAX、PHP和开源的博客。

Chris Shiflett – 关注PHP和WEB应用的安全 

Chris Shiflett所作的专注于PHP的博客。他也是《Essential PHP Security》一书的作者,该书被O’Reilly出版社出版。

Guru PHP – PHP权威观点

Maartin Brampton所作的一个有趣的PHP博客,他本人是一个PHP开发的专家。

Stuart Herbert On PHP

Stuart Hermert通过博客分享他关于部署PHP应用和维护PHP服务器的经验。