作者归档:admin

转岗一年总结

去年的这个月,我转岗了,不再是一个把代码写好,写干净的纯粹程序员。有几分失落,有几分彷徨,虽然有些无所适从,但是还是做了下去,自己选的路,得自己走下去。

一年了,整整一年了。过程中有一些过错,也有一些收获,而最近团队也发生了一些事情,并因这些事情被菊爷严肃的批评了(批评的非常好)。于是就有了今天这篇文章,以此反思自己一年的工作历程。

当面临一个机会,选择向左走 OR 向右走?想想自己适合什么?最终要的是什么?得失之间,做了决定,如此,一路走下去。

从招聘开始

招聘是一个识人的过程,对于一个新手,还是一个曾经专注于技术的新手,这时自己对于技术的偏执就彻底的显示出来了,希望自己的团队成员会是专业的人员,希望自己的团队中能进来一些高手,一句话:技术好才是真的好。依着这样的标准,各种面试,发现自己进了一个死循环,如一些爱情故事: ”爱我的人我不爱,我爱的人不爱我“。被打击后,尝试着降低自己的标准……

但是这一年来招的人最后留下来超过一年的并不多,什么原因?与团队文化不符?没有给他带来所要的东西?或者根本与团队融合不了。新成员与团队的融合和飞机飞行一样,起飞的时候都很难,但还是努力的冲向天空,遇到气流,还会有一些颠簸 …… 最后,不到终点不能换乘别的航班。是什么让他们选择换了别一趟航班?

反思自己这一年招聘历程,离开的人都是我的识人水平有问题,根本就没有认清你想要的是什么样的人,或者说什么样的人适应你的团队。认识到在招聘面试过程中需要关注的第一条:一个将要进入团队的人必须需要符合你的公司文化和团队文化,如果这个都不符合,再好的技术也没有用,能力越强,对于整个团队的破坏性就越大。也体会到书上说的:面试并不是一个几个小时就可以做完的活动,这是一个持续的动作,当一个新人通过正常的面试流程到了你的团队,作为团队leader,你就要开始你的实战面试,在三个月的试用期,一定要强烈跟进新人的状态和与团队的整合进度,当发现新人不可用,或确实与团队不符合时,一定要立即做出处理,否则会对整个团队产生影响,至于换成什么人那是下一个问题。

新人来了后,确实没有花费太多的心思,任凭他们自己适应这个团队,这个需要自省的地方!老大说任何一个团队成员的成长都会付出许多的精力和心血,部门和公司都还小,还没有绝对优势的薪酬和福利、还没有绝对的精神领袖来吸引人,更多的是尽力给团队的成员创造好的成长环境,提供一个相当公平、朝气的工作氛围,提供犯错的机会。

在人和事中往返

转岗后,更多的时间是在做项目经理,各种流程和文档,当然还有编码,虽然这已经比较少了。一段时间后,项目经理也没有做了,将项目直接交给团队成员负责,而自己只做一些代码review和进度check操作。忽然发现自己离实际的项目越来越远,对一些业务已经不太了解了。直到有一天,老大在月度总结后问我,你上个月都干嘛了,才发现部门的项目自己一个也没参加,而自己也不知道自己在忙些什么!终于,让自己迷茫了。每天忙忙碌碌,却不知为何而忙!

一个leader,始终需要关注人和事。向上走,人更关注些,向下走,事更关注些。人不好,事也不会做得好。前面已经说了如果让一个人成为团队的一员,这是团队搭建的水平;而当一个人成为团队成员后,如何让其可以安心留下来,这是团队管理的水平。一个leader既要有团队搭建的能力也要有团队管理的能力。对于团队管理我只能说我还没有入门,还在找属于我自己的门。但是我会秉承一些原则:

  • 发现并留住可自我成长的人才
  • 对事不对人
  • 可以犯错,错不过三

余世维在《经理人五项修炼》中说,一个领导需要做三件事:第一,思考你的战略,第二,计划你的工作,第三,教育好你的员工。我们还没到这个层次,但是计划和成员管理还是需要做的。

“凡事豫则立,不豫则废。“。在工作的过程中,计划是必不可少的环节,每周都会有计划,团队的计划、团队成员的计划,个人的计划,都会去做,按既定的目标前行。一周复一周,周计划,周总结,以周为时间单位粒度的确认,多是按惯例去执行,却没有自己的章法和思路。基于此,以《一页纸项目管理》中的项目进度表为蓝本,进行简化,去掉与部门实际不符的内容,增加人员维度的考量,以项目为一个整体,以每周五为检查点,实现部门事务的整体跟进。这与实际的项目管理工作没有太多的关系,项目管理还是基于公司的项目管理系统。

对于团队成员,菊爷说:”因为你是一个领导,所以你必须对每一个手下有着灵敏的触觉!他们的一举一动,他们的思维反应,他们的耐心躁动,他们的激进疲软,你必须在第一时间做到心中有数,而不是等不相关的其他人发现后,你才后知后觉甚至还发出质问为何有这样的情况发生!你必须有这个触觉跟担当。”。于此,需要做到心如明镜,何其困难。团队管理的细化,具体到每个成员,他们的诉求是什么?他们的性格是什么?他们最近家里是否有发生什么事情?有小孩子了?家里老人过来了?……

leader应该具备的三气

才气
技术人以技术为本,技术是基础,不能落下。
文艺的气息,能写文章,能写PPT,以在众人面前良好的表述自己的想法和思路。
能够及时的解决问题,遇到难点能让人想到你可以解决。
这就是你的才气。才气的体现是得让人说你行,并且说你行的人得行。

霸气
这是一种气场,是在知晓如何去做、为何去做之后所带来的胸有成竹。
遇到问题和争论能够拍板,具有话语权,说一不二,一口吐沫能砸一个坑
团队成员能够信服你。

大气
眼界高,能够从更长的时间维度和更大的空间维度看问题。
大局观好,不能局限于一个部门一小块业务去看问题。
心胸宽广,大肚能容天下可容之事。

除此之外,还需要有较强的沟通能力、执行力、积极的反馈以及以身作则的态度等等。

最后的感恩

一年了。
感谢那些伤害了我的人,是他们让我成长;
感谢那些帮助了我的人,是他们让我感到了温暖;
感谢给我试错机会的人,是他们让我能够遇到这些人这些事;
感谢家里的人,回家的温暖让疲惫的心有一个可以休息的港湾。

在线修改MySQL大表的表结构

问题描述

由于某个临时需求,需要给在线MySQL的某个超过千万的表增加一个字段。此表在设计之时完全按照需求实现,并没有多余的保留字段。

我们知道在MySQL中如果要执行ALTER TABLE操作,MySQL会通过制作原来表的一个临时副本来工作。对于表结构的修改在副本上施行,然后将新表替换原始表,此时会产生锁表,用户可以从原始表读取数据,而用户的更新和写入操作都会被lock,待新表准备好后写入新表。
这对于在线的数据量较大的表来说是绝对无法容忍的,并且由于这种在线操作时间会很长,此时如果show processlist,会发现有若干的MySQL进程处于lock状态,当这种进程太多超过单台服务器允许的MySQL进程数,其它进程可能会被拒绝连接。

有哪些方案可以处理这个问题呢?

方案1、直接ALTER TABLE
这个方案只能说这仅仅是一种方案,在某些非实时在线或数据量较小时有较好的表现。

方案2、模拟数据库修改表结构的操作,在非数据库层实现整个过程。

  1. 实现业务中对于数据的读写分离
  2. 创建一个已经按需求修改好结构的新表
  3. 修改业务逻辑,将读操作指向旧表,将写操作指向新表。如果读旧表没有,再读新表,并将旧的数据写入到新表,当然这一步写入操作我们可以不用,我们可以在后台做一个定时任务将旧数据同步到新表。

这种方案有一个较大的缺点,需要业务逻辑层配合实现数据的迁移,对于业务逻辑有修改,并且如果有多台机器的话,需要一台一台的修改,较费时间,但是对于MySQL的两种主要存储引擎都适用。


方案3、facebook online schema change
facebook的OSC在整体流程上与方案2没有较大的区别,只是它在这里引入了触发器,从而不需要修改业务逻辑,在数据库层就实现了新数据的两个表的同步问题。其大概步骤如下:

  1. 按需求创建新表
  2. 针对原始表创建触发器
  3. 对于原始表的更新操作都会被触发器更新到新表中
  4. 把原始表中的数据复制到新表中
  5. 将新表替换旧表

fb的osc方案从数据库层解决了方案2的问题,但是它仅支持InnoDB存储引擎。


方案4、换一个思路,保留字段。
假设一切可以从头再来,我们也许可以加多一些冗余字段,各个类型都加一些,备用。只是,回不去了!

方案5、再换一个思路,增加扩展表。
我们不在原有的表的基础上修改了,以增加扩展表的方式,将新字段的数据写入到扩展表中,修改业务逻辑,这些字段从新表中读取。
志强同学说这是典型的维表结构设计。
暂时解决了问题,如果这些字段后续使用频率高的话,可能会有对后期维护或业务有一定的影响。

后记
基于现有的需求,只是需要记录新的字段,所以采用了扩展表的方案。

细读PHP的生命周期

在《Extending and Embedding PHP》中,有一张经典的描述PHP单进程生命周期的图,一直也是按这个图理解其生命周期的,可是当准备一次内核分享时,却表现自己没有什么可以说的,于是就有了今天的这篇文章:细读PHP的生命周期。这里,我们会详细说明在CLI模式下PHP一个生命周期中做了哪些事情。

启动

在调用每个模块的模块初始化前,会有一个初始化的过程,它包括:

  • 初始化若干全局变量

这里的初始化全局变量大多数情况下是将其设置为NULL,有一些除外,比如设置zuf(zend_utility_functions),以zuf.printf_function = php_printf为例,这里的php_printf在zend_startup函数中会被赋值给zend_printf作为全局函数指针使用,而zend_printf函数通常会作为常规字符串输出使用,比如显示程序调用栈的debug_print_backtrace就是使用它打印相关信息。

  • 初始化若干常量

这里的常量是PHP自己的一些常量,这些常量要么是硬编码在程序中,比如PHP_VERSION,要么是写在配置头文件中,比如PEAR_EXTENSION_DIR,这些是写在config.w32.h文件中。

  • 初始化ZEND引擎和核心组件

前面提到的zend_startup函数的作用就是初始化ZEND引擎,这里的初始化操作包括内存管理初始化、全局使用的函数指针初始化(如前面所说的zend_printf等),对PHP源文件进行词法分析、语法分析、中间代码执行的函数指针的赋值,初始化若干HashTable(比如函数表,常量表等等),为ini文件解析做准备,为PHP源文件解析做准备,注册内置函数(如strlen、define等),注册标准常量(如E_ALL、TRUE、NULL等)、注册GLOBALS全局变量等。

  • 解析php.ini

php_init_config函数的作用是读取php.ini文件,设置配置参数,加载zend扩展并注册PHP扩展函数。此函数分为如下几步:初始化参数配置表,调用当前模式下的ini初始化配置,比如CLI模式下,会做如下初始化:

INI_DEFAULT("report_zend_debug", "0");
INI_DEFAULT("display_errors", "1");

不过在其它模式下却没有这样的初始化操作。接下来会的各种操作都是查找ini文件:

  1. 判断是否有php_ini_path_override,在CLI模式下可以通过-c参数指定此路径(在php的命令参数中-c表示在指定的路径中查找ini文件)。
  2. 如果没有php_ini_path_override,判断php_ini_ignore是否为非空(忽略php.ini配置,这里也就CLI模式下有用,使用-n参数)。
  3. 如果不忽略ini配置,则开始处理php_ini_search_path(查找ini文件的路径),这些路径包括CWD(当前路径,不过这种不适用CLI模式)、执行脚本所在目录、环境变量PATH和PHPRC和配置文件中的PHP_CONFIG_FILE_PATH的值。
  4. 在准备完查找路径后,PHP会判断现在的ini路径(php_ini_file_name)是否为文件和是否可打开。如果这里ini路径是文件并且可打开,则会使用此文件, 也就是CLI模式下通过-c参数指定的ini文件的优先级是最高的,其次是PHPRC指定的文件,第三是在搜索路径中查找php-%sapi-module-name%.ini文件(如CLI模式下应该是查找php-cli.ini文件),最后才是搜索路径中查找php.ini文件。
  • 全局操作函数的初始化

php_startup_auto_globals函数会初始化在用户空间所使用频率很高的一些全局变量,如:$_GET、$_POST、$_FILES等。这里只是初始化,所调用的zend_register_auto_global函数也只是将这些变量名添加到CG(auto_globals)这个变量表。

php_startup_sapi_content_types函数用来初始化SAPI对于不同类型内容的处理函数,这里的处理函数包括POST数据默认处理函数、默认数据处理函数等。

  • 初始化静态构建的模块和共享模块(MINIT)

php_register_internal_extensions_func函数用来注册静态构建的模块,也就是默认加载的模块,我们可以将其认为为内置模块。在PHP5.3.0版本中内置的模块包括PHP标准扩展模块(/ext/standard/目录,这里是我们用的最频繁的函数,比如字符串函数,数学函数,数组操作函数等等),日历扩展模块、FTP扩展模块、 session扩展模块等。这些内置模块并不是一成不变的,在不同的PHP模板中,由于不同时间的需求或其它影响因素会导致这些默认加载的模块会变化,比如从代码中我们就可以看到mysql、xml等扩展模块曾经或将来会作为内置模块出现。

模块初始化会执行两个操作: 1. 将这些模块注册到已注册模块列表(module_registry),如果注册的模块已经注册过了,PHP会报Module XXX already loaded的错误。 1. 将每个模块中包含的函数注册到函数表( CG(function_table) ),如果函数无法添加,则会报 Unable to register functions, unable to load。

在注册了静态构建的模块后,PHP会注册附加的模块,不同的模式下可以加载不同的模块集,比如在CLI模式下是没有这些附加的模块的。

在内置模块和附加模块后,接下来是注册通过共享对象(比如DLL)和php.ini文件灵活配置的扩展。

在所有的模块都注册后,PHP会马上执行模块初始化操作(zend_startup_modules)。它的整个过程就是依次遍历每个模块,调用每个模块的模块初始化函数,也就是在本小节前面所说的用宏PHP_MINIT_FUNCTION包含的内容。

  • 禁用函数和类

php_disable_functions函数用来禁用PHP的一些函数。这些被禁用的函数来自PHP的配置文件的disable_functions变量。其禁用的过程是调用zend_disable_function函数将指定的函数名从CG(function_table)函数表中删除。

php_disable_classes函数用来禁用PHP的一些类。这些被禁用的类来自PHP的配置文件的disable_classes变量。其禁用的过程是调用zend_disable_class函数将指定的类名从CG(class_table)类表中删除。

ACTIVATION

在处理了文件相关的内容,PHP会调用php_request_startup做请求初始化操作。请求初始化操作,除了图中显示的调用每个模块的请求初始化函数外,还做了较多的其它工作,其主要内容如下:

  • 激活ZEND引擎

gc_reset函数用来重置垃圾收集机制,当然这是在PHP5.3之后才有的。

init_compiler函数用来初始化编译器,比如将编译过程中在放opcode的数组清空,准备编译时用来的数据结构等等。

init_executor函数用来初始化中间代码执行过程。在编译过程中,函数列表、类列表等都存放在编译时的全局变量中,在准备执行过程时,会将这些列表赋值给执行的全局变量中,如:EG(function_table) = CG(function_table); 中间代码执行是在PHP的执行虚拟栈中,初始化时这些栈等都会一起被初始化。除了栈,还有存放变量的符号表(EG(symbol_table))会被初始化为50个元素的hashtable,存放对象的EG(objects_store)被初始化了1024个元素。 PHP的执行环境除了上面的一些变量外,还有错误处理,异常处理等等,这些都是在这里被初始化的。通过php.ini配置的zend_extensions也是在这里被遍历调用activate函数。

  • 激活SAPI

sapi_activate函数用来初始化SG(sapi_headers)和SG(request_info),并且针对HTTP请求的方法设置一些内容,比如当请求方法为HEAD时,设置SG(request_info).headers_only=1;此函数最重要的一个操作是处理请求的数据,其最终都会调用sapi_module.default_post_reader。而sapi_module.default_post_reader在前面的模块初始化是通过php_startup_sapi_content_types函数注册了默认处理函数为main/php_content_types.c文件中php_default_post_reader函数。此函数会将POST的原始数据写入$HTTP_RAW_POST_DATA变量。

在处理了post数据后,PHP会通过sapi_module.read_cookies读取cookie的值,在CLI模式下,此函数的实现为sapi_cli_read_cookies,而在函数体中却只有一个return NULL;

如果当前模式下有设置activate函数,则运行此函数,激活SAPI,在CLI模式下此函数指针被设置为NULL。

  • 环境初始化

这里的环境初始化是指在用户空间中需要用到的一些环境变量初始化,这里的环境包括服务器环境、请求数据环境等。实际到我们用到的变量,就是$_POST、$_GET、$_COOKIE、$_SERVER、$_ENV、$_FILES。和sapi_module.default_post_reader一样,sapi_module.treat_data的值也是在模块初始化时,通过php_startup_sapi_content_types函数注册了默认数据处理函数为main/php_variables.c文件中php_default_treat_data函数。

以$_COOKIE为例,php_default_treat_data函数会对依据分隔符,将所有的cookie拆分并赋值给对应的变量。

  • 模块请求初始化

PHP通过zend_activate_modules函数实现模块的请求初始化,也就是我们在图中看到Call each extension’s RINIT。此函数通过遍历注册在module_registry变量中的所有模块,调用其RINIT方法实现模块的请求初始化操作。

运行

php_execute_script函数包含了运行PHP脚本的全部过程。

当一个PHP文件需要解析执行时,它可能会需要执行三个文件,其中包括一个前置执行文件、当前需要执行的主文件和一个后置执行文件。非当前的两个文件可以在php.ini文件通过auto_prepend_file参数和auto_append_file参数设置。如果将这两个参数设置为空,则禁用对应的执行文件。

对于需要解析执行的文件,通过zend_compile_file(compile_file函数)做词法分析、语法分析和中间代码生成操作,返回此文件的所有中间代码。如果解析的文件有生成有效的中间代码,则调用zend_execute(execute函数)执行中间代码。如果在执行过程中出现异常并且用户有定义对这些异常的处理,则调用这些异常处理函数。在所有的操作都处理完后,PHP通过EG(return_value_ptr_ptr)返回结果。

DEACTIVATION

PHP关闭请求的过程是一个若干个关闭操作的集合,这个集合存在于php_request_shutdown函数中。这个集合包括如下内容:

  1. 调用所有通过register_shutdown_function()注册的函数。这些在关闭时调用的函数是在用户空间添加进来的。一个简单的例子,我们可以在脚本出错时调用一个统一的函数,给用户一个友好一些的页面,这个有点类似于网页中的404页面。
  2. 执行所有可用的__destruct函数。这里的析构函数包括在对象池(EG(objects_store)中的所有对象的析构函数以及EG(symbol_table)中各个元素的析构方法。
  3. 将所有的输出刷出去。
  4. 发送HTTP应答头。这也是一个输出字符串的过程,只是这个字符串可能符合某些规范。
  5. 遍历每个模块的关闭请求方法,执行模块的请求关闭操作,这就是我们在图中看到的Call each extension’s RSHUTDOWN。
  6. 销毁全局变量表(PG(http_globals))的变量。
  7. 通过zend_deactivate函数,关闭词法分析器、语法分析器和中间代码执行器。
  8. 调用每个扩展的post-RSHUTDOWN函数。只是基本每个扩展的post_deactivate_func函数指针都是NULL。
  9. 关闭SAPI,通过sapi_deactivate销毁SG(sapi_headers)、SG(request_info)等的内容。
  10. 关闭流的包装器、关闭流的过滤器。
  11. 关闭内存管理。
  12. 重新设置最大执行时间

结束

最终到了要收尾的地方了。

  • flush

sapi_flush将最后的内容刷新出去。其调用的是sapi_module.flush,在CLI模式下等价于fflush函数。

  • 关闭ZEND引擎

zend_shutdown将关闭ZEND引擎。

此时对应图中的流程,我们应该是执行每个模块的关闭模块操作。在这里只有一个zend_hash_graceful_reverse_destroy函数将module_registry销毁了。当然,它最终也是调用了关闭模块的方法的,其根源在于在初始化module_registry时就设置了这个hash表析构时调用ZEND_MODULE_DTOR宏。而ZEND_MODULE_DTOR宏对应的是module_destructor函数。在此函数中会调用模块的module_shutdown_func方法,即PHP_RSHUTDOWN_FUNCTION宏产生的那个函数。

在关闭所有的模块后,PHP继续销毁全局函数表,销毁全局类表、销售全局变量表等。通过zend_shutdown_extensions遍历zend_extensions所有元素,调用每个扩展的shutdown函数。

PS: 最近有同学问到TIPI项目的进度问题,主编说:在七月份会有一次版本发布,更多的内容可以查看项目的github。