ChatGPT可以替代谁?

最近ChatGPT热度不小,它聊起天来有板有眼,据说图灵测试都通过了。于是不少人就开始慌了,尤其是那些只说话不干事儿的,生怕被ChatGPT抢了饭碗。有人据此列举了一些高危工种,包括律师、作家、程序员甚至建筑设计师。

要我说这些猜测都是扯淡。上面几种是肯定不会被替代的。倒是一些CEO们最有可能被替代。我们来仔细分析一下。

ChatGPT最擅长的,是通过已有的大量信息和数据,全面地分析出有价值的信息。在“大量”和“全面”这两个维度上,人类已经是不可能战胜机器的。

ChatGPT最不擅长的,或者说甚至都不具有的,是创意和梦想。它可以从大量的历史数据中,找到别人的创意和梦想,学习出大概什么是创意和梦想,然后拼凑出一个有模有样的一段话,让人看上去像是一个创意和梦想。但是这样的创意和梦想,既不是它自己原创的,也不是真正的那种,能给人类带来实的变化的创意和梦想。

ChatGPT能写出小说,甚至质量不错,甚至可以感动一些读者没有怎么被触动过的心弦。它 甚至可能比起点中文、红袖添香里年水货作家写的都要好。但是它是写不出那种能流传百年的经典之作的。所以即使从长期来看,广义上的作家也是不可能被替代的,能被替代的,也就是那些水文写手罢了。

至于律师,律师虚假诉讼是要坐牢的,ChatGPT能坐吗?承担不了说话的结果的责任,它连律师席都上不去。更不要说替代。

程序员和建筑设计师就不多说了,这些是创意工种,同时需要承担责任。(搬砖码农除外)

我们反过来想,ChatGPT最擅长的那些,大量、全面的信息处理,是哪个常见岗位最需要的呢?不难发现,CEO是最符合这个条件的。不同CEO的工作内容、重点和工作方式都不尽相同,所以我们肯定也不能一杆子全打死。所以我们进一步分析一下,具体哪些特点的CEO是最容易被ChatGPT(或其技术手段衍生的其它产品)替代的呢?

大体会有如下两个特点:

  1. 短期价值驱动。只看最近的价值,没有对未来的展望、愿景和预判。
  2. 纯理性客观。没有个人的主观的输出,全面的数据驱动。一切判断和决策都用数字来驱动。

一般而言,商业上的成功,有三种类型。一种是通过纯粹的管理手段,以求严格质量控制,优秀的用户体验,提高运营效率,辅助以跑马圈地的速度(流量,病毒式营销都在这里),最后再用市场占有率和品牌知名度形成马太效应,巩固优势地位。还有一种,是通过技术、理念、模式的革新带来的生产效率的极大(10倍级别)提高,这种成功,是需要从原有的所有已知模式中跳出来,想前人所未想,为前人所不为,才能达到的。比如蒸汽机的发明、搜索引擎的发明、装配式建筑等等。最后一种,通过非经济手段垄断。这个就不展开了。

我们不难发现,在第一种类型的企业中,一个短期价值驱动、纯理性的CEO要做的,大体就是兼顾平衡多种因素,在数据化的支持下,力求做出当下能做出,全局或局部的最优解。而CEO由于个人精力有限,显然无法兼顾到所有问题,于是常常出现的状况就是,只有最重要最紧急的事情才会呈报给CEO来做最终决定,而CEO又常常很难在短时间内给出好的答案。而更多不太紧急的问题,又都处于自生自灭的状态,时刻准备着,成为需要CEO来关注的紧要问题。

这些鸡毛零碎的“在给定的,明确的数据和逻辑下,求解大数据量下全局最估解问题,而且用文字解析清楚”。明明就是ChatGPT的Perfect Market Fit啊。在一个已经做到管理、运营信息数据化的公司,只要把这些数据接入ChatGPT,让它与CEO在信息上站在一个水平线上,它回答和解决纯客观问题的速度和效率,绝对碾压一众还需要吃饭睡觉的CEO们。ChatGPT只需要把那种需要主观、风格上两可的判断权,留给CEO就好。比如年会是去喜马拉雅还是去香格里拉。

不过各位CEO们也不需要慌,你们只需要比ChatGPT做得稍稍好一点儿就好了。这应该并不难。另外,CEO们和律师一样,相比ChatGPT,也有天然的不可替代性在。再或者你有资源(现在哪个CEO没有?),也是ChatGPT不可替代的。

所以,以上言论是纯粹的非理性闲谈,仅供一笑,切莫当真。CEO们其实安全得很,至不济,联合起来,把做出ChatGPT的公司买下来就是了。解决不了问题,就把产生问题的人或是公司解决掉嘛。

最后,打个广告,如果你担心自己或是自己公司的CEO被ChatGPT替代的话,请直接发检讨给我,800字以上,我免费帮你看下你还有没有救。

最早的那些记忆

关于小时候的记忆有很多,已经分不太清楚哪个时间上更早些,索性都写下来。

大概可能是4-5岁的时候,记得有一个晚上,自己做在爷爷的自行车上,还有奶奶,还有一个可能是爷爷的同事吧,说到我可能要上幼儿园了,我也不知道幼儿园是什么。但是这是在讨论我,我也听懂了,而且又不知道会发生什么,所以记了下来。

大概可能更早些时候,大概也是4岁吧。奶奶带着我去上班,那是一间锅炉房,记得在那里和奶奶玩手结,就是把一个细绳,用几个手指头拉起来。两个人轮流拉绳,看最后谁能拉开。还记得那时的厂子里有不少废旧的角铁,奶奶捡来,用小锤子打成小簸箕给我玩沙子。只是细节已经不太记得了。

大概也是4岁的时候,有一天晚上妈妈去打水,我就藏在被子里,妈妈回来喊我,我也没有出声,妈妈没有见着我,就去外面找我。我看妈妈走了,就出来在门口坐着等妈妈回来。感觉很久之后才回来。回来妈妈也没有凶我。

我很小的时候应该就很喜欢小动物,但是家里人都不太喜欢。爷爷虽然应该也不喜欢小动物,但是大爱屋及乌,有天晚上下班回来,在公文包里放了一只小猫。那应该是一只奶猫。但是那里家里并没有奶给它喝。它大概也是因为冷,经常会躲在灶台下面。我自己也并不知道喂养猫。有的时候,晚上妈妈带上我和猫,去市场的肉摊去找扔下来的肉碎给猫吃。但是小奶猫哪里能吃得了这肉呢。记得好像那里还怪过这猫挑食。

后来没有多久,有一天早晨,妈妈发现没有猫叫了,后来在灶台下面,煤渣堆里,找到了小猫,已经死了。后来和妈妈一起把小猫埋在了一根电线杆的下面。

另一件事儿,是傍晚和妈妈一起捡柴火回来,我突然感觉脖子一疼,以为是柴火上的钉子扎到了自己,到家里妈妈一看,是一只蜜蜂(或是别的什么蜂)在叮我。然后我才哭了起来。然后妈妈拿毛巾沾上水敷在脖子上。

小时候还有一件事儿,自己喝水的时候,发现自己总是不呼吸的,以为自己的有什么病,于是有一次喝水的时候,自己同时试着呼吸,结果呛水了。

小的时候还记得自己每次洗完澡,都会把毛巾披在身上,当披风用。那个时候,大人擦脸的毛巾,差不多和自己的身体一样长。

莫干山和虎丘的剑池

湖州德清莫干山有一个著名景点,叫剑池。在苏州虎丘,也有一个剑池。好巧不巧,这两个剑池,都和一把剑有关。这把传奇的剑,生于莫干山的剑池,没于虎丘的剑池。

最早了解到干将莫邪的故事,是在几岁的时候看过的一个木偶剧动画片,只懵懵懂懂地记得,讲一男一女打铁铸剑。这个剑好像很不好铸,很久没铸好,然后不知道为什么就有人来把他们杀了,他们俩有个娃,这个娃长大了,要去杀一个大人物,没杀成,就自己把自己的头割了下来,后面大人物不知怎么就把这个头煮了,但是这个头很生猛,在锅里发了火,跳出来把这个大人物咬进了锅里给一起煮了。当年十岁左右看的时候,也并不觉得这个动画片有什么恐怖的地方,只是觉得这个人好厉害。后来才知道,这个大人物就是楚王,那个动画片叫《眉间尺》。这个故事,改编自鲁迅先生的小说《铸剑》(原名就是《眉间尺》),而鲁迅先生的这个小说,应该又是出自干宝的《搜神记》中的《三王墓》。和动画片里的剧情如出一辙,除了最后的结局,是个妥妥的悲剧。

楚干将、莫邪为楚王作剑,三年乃成,王怒,欲杀之。剑有雌雄,其妻重身当产,夫语妻曰:“吾为王作剑,三年乃成;王怒,往,必杀我。汝若生子,是男,大,告之曰:‘出户,望南山,松生石上,剑在其背。’”于是即将雌剑往见楚王。王大怒,使相之。剑有二,一雄,一雌,雌来,雄不来。王怒,即杀之。
莫邪子名赤,比后壮,乃问其母曰:“吾父所在?”母曰:“汝父为楚王作剑,三年乃成,王怒,杀之。去时嘱我:‘语汝子,出户,往南山,松生石上,剑在其背。’”于是子出户,南望,不见有山,但睹堂前松柱下石砥之上,即以斧破其背,得剑。日夜思欲报楚王。
王梦见一儿,眉间广尺,言欲报仇。王即购之千金。儿闻之,亡去,入山,行歌。客有逢者,谓:“子年少,何哭之甚悲耶?”曰:“吾干将、莫邪子也。楚王杀吾父,吾欲报之。”客曰:“闻王购子头千金,将子头与剑来,为子报之。”儿曰:“幸甚。”即自刎,两手捧头及剑奉之,立僵。客曰:“不负子也。”于是尸乃仆。客持头往见楚王,王大喜。客曰:“此乃勇士头也,当于汤镬煮之。”王如其言。煮头三日三夕,不烂。头踔出汤中,瞋目大怒。客曰:“此儿头不烂,愿王自往临视之,是必烂也。”王即临之。客以剑拟王,王头随堕汤中。客亦自拟己头,头复堕汤中。三首俱烂,不可识别。乃分其汤肉葬之。故通名三王墓。今在汝南北宜春县界。

《搜神记》是杜撰的志怪小说,不足为信。而且上面的故事实在是有些负面,于是试着查找真实的干将莫邪的故事,有据可查的,也就《吴地志 匠门》

匠门,又名干将门。东南水陆二路,今陆路废。出海道通大莱沿松江下沪渎阖闾使干将于此铸剑,材五山之精,合五金之英,使童女三百人祭炉神,鼓橐,金银不销,铁汁不下。其妻莫邪曰:“铁汁不下,宁有计?”干将曰:“先师欧冶铸剑之颖不销,亲铄耳,以然成物吾何难哉!可女人聘炉神,当得之。”莫耶闻语,投入炉中,铁汁出,遂成二剑。雄号干将,作龟文;雌号莫耶,鳗文。余铸得三千,并号作龟文剑。干将进雄剑于吴王,而藏雌剑。时时悲鸣,忆其雄也。

看完这个故事。突然感觉《搜神记》里讲得,好像也没有那么假。而且这个吴王阖闾,用人真是的不拘一格,一边儿和楚国打着仗,一边儿把一个楚国人叫来给他打造兵器。

这两个故事,虽然都没有提到莫干山的“剑池”,但是想必铸剑是需要水来淬火的,莫干山有竹有泉,想也是个铸剑的好地方。《吴地志》倒是有提到了虎丘的剑池:

《吴越春秋》云:“阖闾葬虎丘,十万人治。葬经三日,金精化为白虎,蹲其上,因号‘虎丘’。”秦始皇东巡至虎丘,求吴王宝剑。其虎当坟而踞,始皇以剑击之不及,误中于石(遗迹尚存)。其虎西走二十五里忽失,于今虎疁,唐讳虎,钱氏讳疁,改为浒墅。剑无复获,乃陷成池,古号“剑池”。池傍有石,可坐千人,号千人石。

这里可以大胆地猜想一下,这个故事里,令秦始皇垂涎三尺的吴王阖闾的那柄剑,应该就是干将莫邪铸造的那柄。毕竟阖闾的儿子夫差用的,是十来把夫差剑。

抽象与责任分离

之前有写过一篇文章,介绍类基多态与模式匹配的区别与适用场景的不同。但是其中并没有具体的例子,不免有些空泛。一转眼两年多了,这次找了一个具体的例子,一步步分析各种不同的方案,或许更好理解些。

问题

在银行交易系统中,有很多不同的交易类型,每种交易类型的数据结构都有所不同。同时,银行每天会把所有交易向用户报告,报告的格式也有很多种。需要一个模块,处理所有交易数据,并生成各种不同格式的报告。

交易类型比如有:

  • Deposit
  • Credit Transfer
  • Direct Debit

报告文件类型比如有:

  • Csv
  • Text
  • SWIFT

方案

在不做任何设计和类的划分的情况下,无非就是把所有的逻辑都放一起,于是我们就得出了这样的一个方案:

全都放在一个叫File Generator的类里来处理。于是,无论是有新的交易类型,还是有新的文件格式,这个类都是需要改的。这显然违反了单一责任原则。

这个问题很好解决的嘛,只要让每个Generator,只处理特定的某一种具体情况,然后每次有新的情况,只需要新加一个Generator就好了,高内聚,低耦合,可扩展,而且写好的Generator代码就再也不用改了。就像这样:

其实呢,本质上,这仅仅只是把原来的大的FileGenerator分成了一个个小的类(或函数)而已。换个思路想一下,当Deposit交易的数据结构有变化时,你需要动几个类?在这个设计下,是不是需要动的类的数量反而越来越多了?(有时,改的类的数量多并不是问题,但是这里,假定是。)

嫌类多啊?那我们分析一下哪种变更比较频繁然后有针对性地优化嘛。

如果时常加新的输出文件类型,那可以针对加文件优化:

于是每次有新文件类型,只要加个新Generator类就好。如果时常需要加新的交易类型,那又可以针对交易类型优化:

于是每次有新交易类型,也只要加个新的Processor类就好了。

最后还要再加一句:“就看业务上能不能判断出来,到底是交易类型加得多,还是报告文件类型加得多了。我们技术上都好办。”成功把一个纯技术问题成功甩给业务方。然后尽看业务方一脸蒙逼。

真的就好了吗?技术就是这么解决问题的吗?

引入抽象层隔离依赖

以上所有的方案,无非是同样的一坨坨代码,到底是分成几个函数,几个类的差别而已,本质上都是一样的。

上面所提的各个方案中,所存在的根本问题是:没有抽象,也就没有正确使用多态的能力。

Generator所面对的,一边儿是输入,是一个具体的数据类,另一边儿输出,也都是一个个具体的数据类。编程原则上常讲,要面向接口编程,而不要面向具体细节编程1。上面的Generator,可说是反面典型。

当一个模块(可以是一个类,也可以是一个函数,也可以是一组类),要实现一个功能的时候,应该基于一层抽象层来构建自己的逻辑,这样才能保证自己的逻辑的稳定性,而不会被具体实现的变化牵连产生衍生问题。

以上面的File Generator为例,用于生成文件所要依赖的东西(即数据),应该是一个抽象的概念,而不是任何具体的数据结构。

比如,如果是要生成交易流水,那么,我们可以为生成交易流水这个函数,先定义一个抽象的,不依赖任何具体业务的概念,说,我要生成流水,就需要这些数据,别的都不用了。然后我们可以用一个专门的类来表达这个概念,比如叫Statement,那么,整个设计可以变成这样:

同时,原来的File Generator,其实分成了两个部分,一个部分负责把具体的场景化的业务数据,转成统一的抽象的数据;另一个部分负责将这个Statement,再根据具体的情况,生成不同格式的文件。由于这个Statement类,就是File Generator负责定义的,File Generator的逻辑也是基于这个抽象的概念来写的,其逻辑也就是稳固的。同时,Generator的责任也更单纯。

上图中的Statement Extractor独立出来的好处是可以让具体的数据类,如Deposit,Transfer等,对其具体使用场景(用于生成Statement文件)无感而引入的。但是如果在特定上下文中,这些具体的数据类,可能会具体场景有感知,就没有必要总是把数据转换的部分独立出来。可以合并到一个基类中提供实现,如下所示:

另外,上图中的File Generator,也可以直接引用Transaction基类。但是不应该直接引用任何具体的子类。

应用

上面的示例,仅仅用来解释,在上述问题中,如何通过引入抽象的数据实体,分解责任并构建更加稳固的代码逻辑。但是并表示:

  • 同类问题只有这一种解决方法。
  • 只要有这类问题,就一定要引入抽象层才是更好的。

每一层都是额外的复杂度,只有当被解决的问题的复杂度大于解决方案复杂度增加带来的新的认识负荷时,才值得增加这额外的一层。

不要走极端

见过不少代码,所有的类,都实现一个与类名一致的接口,从上层的Controller到底层的数据库皆然,问其缘故,说是要“面向接口编程”,所以这样做的。我就在想,这也不知是被哪个没耐心的大佬坑了,同时脑子里不由自主地就浮现出这些对话的画面。

没耐心大佬:“你这个代码能不能加个接口?面向抽象编程懂不懂?”

小白:“好的,您看是不是这里?”

没耐心大佬:“对。”

小白:“好的,那这里呢?要不要加?”

没耐心大佬:“也要。”

小白:“好的,那还有这里呢?加不加呢?”

没耐心大佬:“……,这样吧,你全加吧。全加也比全不加好。”

反馈循环

做事儿,尤其是那种可重复操作的事儿,想要把事情做对做好,提高效率和质量,最重要的一环,就是建立“反馈——修正”的回路。

用学炒菜以为例,我们可以看菜谱,了解每个步骤的先后,物料的多少,时间的长短,火候的大小。然后按这个设计好的步骤来做,做上个十次八次,如果发现并没有越做越好,是什么原因呢?

是菜谱没讲到位?下次换一本,现在下厨房APP,随便搜索一个菜名,能有十来个菜谱。一个不行,那就换一个呗。

是原料不好?下次买更好的材料。这次西红柿太生,都不出汁,下次用更熟的。 这个鸡蛋不是草鸡蛋,所以没有风味。

是工具不好?这锅太薄,导致受热不均匀,所以有的地方生的,有的地方糊了。

其实都不是。

炒菜是一个连续的过程,不是零散的几个操作的组合。火一直在烧,菜也在持续的产生变化,如果无视这些连续的变化,只是按步就班地在预设的时间点,机械地执行预设好的操作。那做出来的菜也只能说是差强人意,勉强能吃而已。

炒菜的时候,要观察,看菜在烹饪过程中的逐渐变化,香味逐渐散发。比如西红柿炒蛋;西红柿下锅,多久开始出汁,出多少汁,要不要加水,加多少水,什么时间点加水,重点不是看好表、算好时间,而是要看锅里的菜,根据每次炒菜的时候的状态适时地动态的增减。有的西红柿汁多,都可以不用加水,有的西红柿水分少,想吃汤多些的,就要多加些水。

这观察就是一个获取反馈的过程,以便知道,之前的操作的结果,是否和自己设想的一样。 然后动态地调整下一次的操作。观察的本质,是从外界获取信息(而非自身的主观认识。)修正的本质,是根据观察的结果,分析出新的认识,然后改变自己原来的认识,是对自身认识的修正。

如果你炒一个菜,等完全炒完了,再来看结果,是不是和菜谱上的一样,不一样,有改进空间,分析一下哪里没做好,下次炒的时候改进。那么炒一个菜,就是一次观察与修正的循环。可以想一下,这样要炒非常多次菜,才能把修正出好的结果。

但是这样的“观察与修正”的循环,在一次炒菜中,其实可以有很多次。过程中的每个操作,无论是加水加盐,甚至什么时候盖锅盖,锅盖的角度是多少,都可以存在建立观察与修正的循环的机会。

这样,炒一个菜,建立这样观察与修正的循环的次数越多,从这次炒菜中所学到的新的知识也会越多。也就能越快地把一个菜炒好。

这也是为什么,有的人,炒个菜看上去很轻松很顺利地就炒好了,但是有的人无论炒多少次,都还是一样的结果。同样是炒一个菜,有的人在整个炒菜的过程中,可能会产生无数的心得。但是在有的人的世界中,炒菜只是严格按菜谱要求,使用不同的工具,在几个指定的时间,把精确重量的食材放进去翻炒而已,没有任何新的发现和领悟。这两种方式截然不同,但是两个人在可见的行为上,可能又没有任何明显的差别。因为心得、领悟、发现这样,都是在整个过程中,随时随地可能发生的内心的活动。

炒菜是这样,做事儿也是一样的道理。如果我们只是按计划去做,追求计划或方案本身的正确性与合理性,但是却没有对真正重要的执行过程,花足够多的心思,只是机械地执行,那结果,多半就和第一次炒菜一样,不会尽如人意。

艾森豪威尔有句话,我在看来表达的其实是同一个意思:

Plans are worthless, but planning is everything.

无论我们的计划是什么,其实都没什么用,重要的是根据实际情况决定要做什么的过程,而这个过程,势必会对原计划产生影响和改变(于是说,原来的计划就没什么用了)。

所以,不要在意别人讲的上海计划几号解封的传说,多在意一些,我们现在应该如何做,才能让上海早日解封。如果你觉得做什么都没有用,至少可以多做些准备。

博客迁移札记——第四日

第九日

上了五天班,期间没有什么进展。但是反思了一下,为什么迁移个博客这么简单的事儿花了三天还没有彻底迁移完。究其原因,还是自己有病,一种叫“Not Invented Here”的病。

当时的逻辑是:”因为服务器不是专门给博客用的,所以不能用特别定制化的WordPress镜像,所以需要自己搭。

这个逻辑看着通,其实只是借口罢了。当时自己,其实心里早就决定想自己做一遍,便借故各种冠冕堂皇的理由来佐证自己的判断罢了。等自己做完,积累了一定的经验之后,再回过头来看时,才能以更公正的心态看态迁移博客这个事儿。

客观地讲,一开始并没有预见到手工搭建WordPress站和数据迁移的过程的复杂度。所以对于不同方案的实施成本的预判失准。所以找了一条自己感觉自己能控制住的道路——手工建站,其它方案都没有多想。

士别三日,重头再来。这次我们用Podman。方便起见,我们先在本地试一下。

如果是服务器端,需要先来安装Podman:

dnf install docker       #CentOS上即是Podman
dnf install podman-compose

然后准备这样的服务声明文件比如叫(stack.yml):

version: '3.1'

services:
  wordpress:
    image: wordpress:5.9
    restart: always
    ports:
      - 8080:80
    environment:
      WORDPRESS_DB_HOST: db
      WORDPRESS_DB_USER: admin
      WORDPRESS_DB_PASSWORD: admin123
      WORDPRESS_DB_NAME: wordpress
    volumes:
      - wordpress:/var/www/html

  db:
    image: mysql:8.0
    restart: always
    ports:
      - 3306:3306
    environment:
      MYSQL_DATABASE: wordpress
      MYSQL_USER: admin
      MYSQL_PASSWORD: admin123
      MYSQL_ROOT_PASSWORD: 123456
    volumes:
      - wordpress_db:/var/lib/mysql

volumes:
  wordpress:
  wordpress_db:

这里需要注意的是,如果不想数据丢的话,一定要挂volume。(问题:这些Volume,如果像这样不明确配置,会放在什么地方呢?这个要搞清楚,否则要备份都不知道要备份哪里。)

然后运行:

docker-compose -f stack.yml up

然后就好了。 就可以在http://localhost:8088/上打开WordPress并进行初始安装了。

这里MySQL和WordPress的版本是明确的,但是WordPress镜像中内置的操作系统和Web Server的版本是不知道的。我们可以连上WordPress的Pod上进行确认。

user@host % docker exec -it ea46d247c5b8 bash

root@ea46d247c5b8:/var/www/html# php --version
PHP 7.4.27 (cli) (built: Jan 26 2022 18:11:58) ( NTS )
Copyright (c) The PHP Group
Zend Engine v3.4.0, Copyright (c) Zend Technologies
    with Zend OPcache v7.4.27, Copyright (c), by Zend Technologies

root@ea46d247c5b8:/var/www/html# apache2 -v
Server version: Apache/2.4.52 (Debian)
Server built:   2022-01-03T21:27:14

root@ea46d247c5b8:/var/www/html# cat /etc/os-release
PRETTY_NAME="Debian GNU/Linux 11 (bullseye)"
NAME="Debian GNU/Linux"
VERSION_ID="11"
VERSION="11 (bullseye)"
VERSION_CODENAME=bullseye
ID=debian
HOME_URL="https://www.debian.org/"
SUPPORT_URL="https://www.debian.org/support"
BUG_REPORT_URL="https://bugs.debian.org/"

root@ea46d247c5b8:/var/www/html# uname -r
5.10.76-linuxkit

这样,我们确认了WordPress镜像中,几个基础组件的版本。

  • PHP 7.4.27
  • Apache 2.4.52
  • Debian 11 (Linux Kernel 5.10.76)

而阿里ECS的服务器上,我手工安装的(最新)版本是这样的:

  • PHP 7.4.19
  • Apache 2.4.37
  • Anolis OS 8.5 (Linux Kernel 4.18.0)

可以发现,最新镜像中自带各个组件,比我自己安装的还要新。(当然,这主要是因为各个Linux发行版的Repo的控制和更新频率不同。)有鉴于Linux Kernel差了一个大版本。还得意了解一下,这个大版本到底更新了什么

而且,更重要的是,WordPress镜像中自带的PHP和Apache,都已经把插件和相关配置做好了,不需要自己再手工安装插件或是手工做任何的配置。比如Apache会配置成在/var/www上AllowOverride,以便固定链接功能能正常工作。(当然,是代码都会有Bug,Image的配置也是代码,也会坏,比如这里可以看到,官方的镜像也出过固定链接坏掉的情况。)

这里其实也有一个问题:原来的ECS,我可以独立升级OS,PHP和Apache,而不会需要重新部署与迁移。但是现在使用Docker镜像,如何更新镜像,又不需要重新做迁移,就会要求把应用数据与运行环境严格剥离。

这样,原第一日工作完成。而且WordPress的健康检查基本正常。(只是中文版ICP备案的选项也还是没有,看来真的是官方不再支持了。)

然后,我们可以开始做数据迁移了。先把老数据导出来,方便起见,直接在本机导。先在本机(MacOS)安装mysql-client(这里有mysqldump):

brew install mysql-client
echo 'export PATH="/usr/local/opt/mysql-client/bin:$PATH"' >> ~/.zshrc

然后执行数据导出:

mysqldump -h <IP> -u admin -p --opt wordpress > wordpress.sql

执行了大概20秒,导出了5MB左右的SQL文件。然后直接导入到新的数据库中:

mysql -h 127.0.0.1 -u root -p wordpress < wordpress.sql

然后,如果有必要,再手工把wp_options表里的siteurl和home的地址及wp_posts里的content更新一下。(前文有讲,这里不再赘述。)

看着这两行命令就可以完成的事儿,回想起第二日,手工在DMS各种跑工单,自己提自己批自己跑自己下载结果的情景。就感觉心里一阵刺痛。

这样,原第二日的工作也基本完成了。(除了图片上传的部分。)

现在快是快了,但是马上就有几个新问题需要解决:

  • stack.yml文件中,有声明数据库密码。是否能去掉?(当然,手工部署的,这个密码是在wp-config.php文件中。也是不安全的。)
  • 在Docker-Compose的启动过程中,如何自动迁移数据?
  • 如何升级wordpress和mysql?甚至OS。
  • 如何在迁移过程中自动传输文件?
  • Docker Image自带OS,用Docker启动了MySQL和WordPress,瞬间2GB空间就没有了。

Google之后其实都不难找到答案:

博客迁移札记——第三日

第三日

第三天白天主要是带娃在游乐场玩,她在那边儿玩沙子滑滑梯,我在边上,除了负责盯梢和傻笑以外,有空的时候,脑子里就在想Permalinks访问不通,问题可能在哪里?WordPress迁移怎么这么麻烦,还是我自己的做法走偏了?有一刻,突然想起来一个关键问题,在Apache的配置文件里,好像看到过一个叫AllowOverride的东西,想到.htaccess文件可以改变Apache服务器的行为,就在猜,这么NB的超能力,不会是默认关了吧?但是如果默认是关的,为什么所有的方案都没有提到呢?手上没有本子,就只能想想。

晚上回家吃完饭,打开/etc/httpd/conf/httpd.conf,发现了下面这样的配置及注释,才验证了自己的猜想:

# Further relax access to the default document root:
<Directory "/var/www/html">
    #
    # AllowOverride controls what directives may be placed in .htaccess files.
    # It can be "All", "None", or any combination of the keywords:
    #   Options FileInfo AuthConfig Limit
    #
    AllowOverride None
</Directory>

默认值是None。我改成All了,重启Http服务,刷新页面,permalinks正常工作了。

所以说,程序员的产出呀,和工作时间其实没啥关系,思路对了,解决起来非常简单,思路不对,折腾到半夜也没有用。(注:这里的产出,主要指创造型和解决问题型的产出,按PRD堆砌CRUD页面不在此范畴)

然后在WordPress的后台首页,看到了这样的错误:

这可能就是所谓的模块级服务降级吧。不得不说,WordPress的错误提示还是很友好的,至少一看就知道是有模块没有安装的问题。但是再想想,为啥PHP的模块要分这么细呢?不由得想到,这开源界的洁癖还真是无所不在啊。看看人家.NET,all-in-one,管你要不要用呢。

这个处理起来也很简单,装就是了。

dnf install php-xml php-intl

这个问题处理好了之后,在健康检查页面,又看到了这么个错误:

我以为这无非就是再跑一行dnf命令的事儿,然而事实证明我太天真了。

Google一下install imagick on CentOS 8,会发现有一长串结果,标题差别还很大,这一般不是个好兆头,说明这个事儿比较复杂,而且每个人的做法都不一样。

下面是要按其中一份作业整理出来的小抄摘要笔记:

dnf install epel-release
dnf config-manager --set-enabled PowerTools
dnf install ImageMagick ImageMagick-devel
dnf install php-pear make
pecl install imagick
echo "extension=imagick.so" > /etc/php.d/20-imagick.ini
systemctl restart httpd

然后跑到第三步就失败了,失败原因很简单,说是找不到ImageMagick。同时,我还注意到,在命令行的输出里,开始出现了一个新品种的提示信息:

Repository epel is listed more than once in the configuration

这个错误提示就比较让人恼火了,它平静地阐述了一个客观事实,但是又没有解释这个事实有什么不对的地方。就仿佛有人大喊,“下雨啦”,也不知道是在感慨久旱逢甘霖,还是在提醒大家赶快收衣服。

但是,作为职业程序员,这十几年间培养起来的职业素养,在这一刻终于发挥了作用。一眼就看出来,写这个提示的人的意思是,出现多次会有问题,而且背后的意思是,其中只有一个会真正生效。然后,真正的问题是,生效的这个里,没有ImageMagick这个包,然后可以乐观地猜想,没有生效的那个仓库(Repository)里有这个包。我们先假设网上安装步骤是对的,那么就是刚才安装的官方epel-release没有生效,也就是说,系统里自带了一个安装包不全的坑货Repository,名字也叫epel,故意把官方repository给调包了。

脑子里分析到这里,虽然有种推理破案的快感,但是我在心里还是骂了句“操!”,因为这意味着一个更加恐怖的事情:我这台ECS主机的操作系统,不是原生的CentOS,是定制版的OS,而且修改过Repository。意思是,我这几天安装的所有软件,都可能不是官方版本,技术上来讲,可以是私人定制版本。而如果这个Repository是个不正经的Repository,那么他基本上可以在我的这台主机上为所欲为。(比如阿里开发者社区上公开的PyPI镜像下架列表。)

然后我赶紧看了一下操作系统信息(BTW,这里可以了解下Redhat,Fedora和CentOS的关系):

[user@host ~]# cat /etc/os-release
NAME="Anolis OS"
VERSION="8.5"
ID="anolis"
ID_LIKE="rhel fedora centos"
VERSION_ID="8.5"
PLATFORM_ID="platform:an8"
PRETTY_NAME="Anolis OS 8.5"
ANSI_COLOR="0;31"
HOME_URL="https://openanolis.cn/"

Anolis OS是什么鬼?一看这名字就是阿里出的坑货啊。我明明记得下单的时候选的是CentOS啊。我再去看我当时下的订单的详细信息,里面写的居然是“Anolis OS 8.4 RHCK 64位Linux64位”。

那么有两种可能,要么我点错了,要么是阿里为了推广自己的OS搞了鬼,毕竟这个Anolis OS,我看了一下,自称是100%兼容CentOS 8的。但是这种事儿永远没办法求证了。除非有24小时录屏。【此处应有商机】

算了,还是先解决问题吧。反正后面可以换操作系统

先找出元凶:

[user@host ~]# grep -Rln "\[epel\]" /etc/yum.repos.d
/etc/yum.repos.d/AnolisOS-DDE.repo
/etc/yum.repos.d/epel.repo

果然有两个repo都定义了[epel]。然后果然有一个是AnolisOS带的。我看看里面的内容:

[user@host ~]# cat /etc/yum.repos.d/AnolisOS-DDE.repo
[DDE]
name=AnolisOS-$releasever - DDE
baseurl=http://mirrors.cloud.aliyuncs.com/anolis/$releasever/DDE/$basearch/os
enabled=0
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-ANOLIS
gpgcheck=1

[epel]
name=epel
baseurl=http://mirrors.aliyun.com/epel/8/Everything/$basearch/
enabled=0
gpgkey=http://mirrors.aliyun.com/epel/RPM-GPG-KEY-EPEL-8
gpgcheck=1

这就非常有意思了。有这么几个问题:

  • 名字叫DDE.repo,然后里面定义了两个repo。这不是不行,就是感觉不太规范的样子。
  • 一个是aliyun的mirror站,一个是aliyuncs的mirror站。而后者是打不开的。你说你都打到公开售卖的镜像里的URL,打都打不开这像话吗?尽管看上去默认是Disable的。

算了,阿里那边儿的事儿先放放(虽然我后来发现阿里已经有个页面介绍如何配置epel了),先解决这个ImageMagick安装不上的问题,这个对我来说更重要些。解决办法很简单,就是把上面的[epel]这部分注释掉就好了。

#[epel]
#name=epel
#baseurl=http://mirrors.aliyun.com/epel/8/Everything/$basearch/
#enabled=0
#gpgkey=http://mirrors.aliyun.com/epel/RPM-GPG-KEY-EPEL-8
#gpgcheck=1

做这个操作的时候,我又在想,要是整个文件系统都放git里,我就不用注释或是备份了,直接删除。

然后,再次运行上面的中断掉的第三步,就顺利完成了。

然而,都安装好了之后,用php命令验证的确貌似安装成功了:

[user@host ~]# php -m | grep magic
imagick

WordPress还是说imagick没加载。算了,一个小时都没救回来也算尽力了,反正我也用不到这个功能。等有空了再回来看看PHP或是WP的error log啥的吧。

现在还有更重要的问题要解决,就是WordPress上的ICP备案信息没有了。

当我们安装完中文版的WordPress之后,可以在配置中填写ICP备案号并显示在页面上。然而,新的服务器上,一开始是安装的是英文版,后来切换成了中文。但是这个ICP备案号的配置根本就没有,更不要说显示在页面上了。

据考证,这个功能是WordPress 3.7开始由官方引入。所以理论上应该在WordPress的源代码里可以找到。所以在老服务器上找一下:

[hugo.gu@local htdocs] % grep -R "仅对WordPress" .
./wp-content/languages/zh_CN.php:		'<p class="description">仅对WordPress自带主题有效。</p>';

然后下载最新的官方的中文版WordPress。会发现里面没有这个文件:

[hugo.gu@local wordpress] % find . -name "zh_CN.php" -print
[hugo.gu@local wordpress] % 

的确也有人猜测,WordPress大概是在某个版本开始放弃了在language包里添加配置的这种做法。有兴趣的同学可以去那个毫无人气的WordPress中文社区去展开进一步的调查。

最终,我也没能在任何官方来源上找到这个文件,只能通过FileZilla,自己上传了一个老版本的zh_CN.php文件上去。然后一刷新,果然就都有了。尽管这个只能在自带主题上使用的配置项其实非常鸡肋,但是WordPress官方的这种,为了不让人顺顺利利地,以开源方式使用他们家的产品,所做的诸多努力,着实让人倍感钦佩。

然而,ICP备案的事儿,还没有结束。现在ICP备案信息显示出来了,但是由于迁移了服务器,IP地址发生了变化,那么之前的ICP备案信息,是否需要提交复核修正或是重新申请呢?

博客迁移札记——第二日

第二日

新建好只是第一步,更重要的,是如何把原来的东西原原本本地搞过来。本着最小手工工作量的原则,我在想,是不是只要把数据库表全复制过来就好了呢?WordPress的安装目录下,有没有什么别的东西要复制过来呢?最少需要复制哪些呢?这些问题在网上是找不到完整的答案的。因为具体情况是不一样的。而且,还是那个问题,考虑到开源界10年的卓越贡献。

具体做哪些先放一下,先把前期工作准备好。比如老的主机上的图片,怎么搞到新的服务器上呢?像昨天那样用scp传是不行的。主要是因为云虚拟主机根本没有开ssh,其次呢,是因为这么手工一个个跑命令也不是个事儿。虽然也可以写脚本,但是这种脚本的可重用性就比较差,我下次再要传别的文件呢?再写个脚本?给脚本改参数?都不好。传文件这种事儿就应该是一键完成的。考虑到云虚拟主机只能通过FTP传,天然的直接方案就是,在新的ECS上也搭建一个FTP服务器。

说干就干。命令如下:

dnf install vsftpd
systemctl start vsftpd
systemctl enable vsftpd

安装简单,配置起来可就没那么简单了。首先,得有一个特别的ftp用户,比如就叫ftpuser,然后这个用户得能访问/var/www/html这个文件夹。然后还得和Apache不打架。然后,还不能有安装隐患。

创建用户并配置相关权限的命令如下(主要参考了这里):

adduser ftpuser                   #创建用户
passwd ftpuser                    #设置密码
usermod -d /var/www -m ftpuser    #设置用户主目录
usermod -a -G apache ftpuser      #为用户添加组

其中apache是Apache HTTPD服务用的一个用户组。而/var/www默认的Owner和Group是root(因为安装Apache的时候用的root)。所以还需要把/var/www的Owner和Group都改成apache,以便ftpuser可以访问并修改里面的文件。

chgrp -R apache /var/www         #更改文件夹所属组
chmod -R g+w /var/www            #更改组权限

这些都配置了之后,发现从ECS主机上,本地是可以连上去的。但是从Mac上就连不上去,最后Google半天,发现是Passive Mode的连接模式,

最终,对/etc/vsftpd/vsftpd.conf这个文件的改动是这个样子的。

[user@host vsftpd]$ diff vsftpd.conf_back vsftpd.conf
124c125,128
<
---
> pasv_addr_resolve=YES
> pasv_address=<PUBLIC_IP_V4>
> pasv_min_port=xxxxx
> pasv_max_port=yyyyy

然后,去ECS上开如下几个端口:21,xxxxx/yyyyy。

关于pasv_min/max_port的更多解释可以参考官方文档:

man vsftpd.conf

然后,下载个FileZilla,就可以连上去了。但是新发现的一个问题是,客户端可以访问到整个服务器上的所有文件。然后又是一轮Google:how to restrict ftp user to a directory?然后发现和chroot的配置相关。顺便了解了一下chroot是个啥。于是又要改/etc/vsftpd/vsftpd.conf作如下配置变更:

[user@host vsftpd]$ diff vsftpd.conf_back vsftpd.conf
100c100,101
< #chroot_local_user=YES
---
> chroot_local_user=YES
> allow_writeable_chroot=YES

这样,就可以把客户端的访问权限限制在/var/www里了。

下一个问题是,需要传哪些文件呢?图片文件在哪里呢?这里展示了WordPress的目录结构,可以看名字猜一下,哪些放的是WordPress自己的代码、样式,哪些放的是用户数据?哪些需要传到新的站点上呢?

[user@host blog]# tree -L 2 -d .
.
|-- wp-admin
|   |-- css
|   |-- images
|   |-- includes
|   |-- js
|   |-- maint
|   |-- network
|   `-- user
|-- wp-content
|   |-- languages
|   |-- plugins
|   |-- themes
|   |-- upgrade
|   `-- uploads
`-- wp-includes
    |-- assets
    |-- block-patterns
    |-- blocks
    |-- block-supports
    |-- certificates
    |-- css
    |-- customize
    |-- fonts
    |-- ID3
    |-- images
    |-- IXR
    |-- js
    |-- php-compat
    |-- PHPMailer
    |-- pomo
    |-- random_compat
    |-- Requests
    |-- rest-api
    |-- SimplePie
    |-- sitemaps
    |-- sodium_compat
    |-- Text
    |-- theme-compat
    `-- widgets

这里顺便问个问题,请观察这几个文件夹的名字,问,文件夹的命名风格是什么?

    |-- IXR
    |-- rest-api
    |-- SimplePie
    |-- sitemaps
    |-- sodium_compat

答:Free Style(自由风格)因为这是Freeware(自由软件)。

好,在不看代码的情况下,我们只能通过文件夹的名字盲猜其用途,然后决定要不要复制到新服务器上。当然,另一个简单的方式是,全部都复制。但是显然是不行的,因为至少wp-config.php这个文件里,就包括了针对特定服务器的配置。覆盖过去,直接数据库就连不上了。所以不能无脑地全复制。我们先按常识自己挑一些:

  • uploads:上传的图片和视频等。这
  • themes:主题文件。老的WordPress有些老的主题,新的默认只安装新的。当然,也可以在新网站上重新安装这些主题,而不用复制过去。
  • languages:本地化。本来这个东西应该是新的安装也自带的,但是非常神奇的是,新版WordPress里少一个zh_CN.php文件,这个文件还是很有用的。于是就只能从老版复制过来。

比如,如何做云端数据库全表迁移?更具体些,是从云虚拟主机的数据库,迁移到ECS自建数据库上。

在云虚拟主机的页面上,我找到了一个叫DMS的工具。可以管理阿里云的所有数据库的。而且还有桌面版。而且还支持MacOS。这样的话,也就不需要安装phpMyAdmin了,毕竟服务器资源有限,能少安装东西就少安装东西,CPU得用来跑业务,而不是用来跑工具,哪怕是辅助,能不长驻就不要长驻。

但是在尝试从Mac连接到数据库的时候出了问题:连不上。好说,无非又要开ECS的端口了。

再试,又失败了,还是一个新品种的错误:不允许用户从远程登录。还记得前一天是如何创建的数据库用户的吗?

CREATE USER `admin`@`localhost` IDENTIFIED BY 'pass';

这里的localhost的意思就是只允许用户从本地登录。于是我们得让这个用户可以远程访问。也不难找到现成的作业来抄。

CREATE USER 'admin'@'%' IDENTIFIED BY 'pass';
GRANT ALL ON *.* TO 'admin'@'%';
FLUSH PRIVILEGES;

然后就顺序连上ECS的数据库了。然而,在连云虚拟主机的MySQL实例的时候,又出状况了。DMS要求我先建立一个本地网关,然后把云虚拟主机的MySQL实例,作为第三方数据库实例注册到DMS上。

我就无语了。这里的故事背景是:云虚拟主机业务,其实是阿里收购的万网。敢情阿里的收购,收完就完了,根本不当自己人对待。哪怕会影响用户体验。

在DMS上试了好几回全库数据导出和SQL执行,最终把数据库里的数据全迁移过程了。遇到的坑包括:

原数据库(MySQL 5.7)中存在’0000-00-00 00:00:00.000’这样的日期值,新的数据库(MySQL 8.0)是插入不进去的,导致数据导入失败。

所以得先把现有数据,在老的服务上修好。SQL如下:

UPDATE `wp_posts`
   SET `post_date_gmt`= DATE_SUB(`post_date`, INTERVAL 8 HOUR)
 WHERE `post_date_gmt`= '0000-00-00 00:00:00';

UPDATE `wp_posts`
   SET `post_modified_gmt`= DATE_SUB(`post_modified`, INTERVAL 8 HOUR)
 WHERE `post_modified_gmt`= '0000-00-00 00:00:00';

注意,上面有个Hardcode的数字8。是东八区的八。如果你在别的时区,自己手工改下再执行哈。

把数据导入到了新的库上之后呢,要把数据里的原来的URL全部替换成新的,SQL如下:

UPDATE `wp_posts`
 SET guid = REPLACE(guid, 'www.hugogu.cn' ,'blog.hugogu.cn/blog');

UPDATE `wp_posts`
 SET post_content = 
    REPLACE(post_content, 'www.hugogu.cn' ,'blog.hugogu.cn/blog');

UPDATE wp_options 
 SET option_value = 
    REPLACE(option_value, 'www.hugogu.cn' ,'blog.hugogu.cn/blog') 
 WHERE option_name = 'home' OR option_name = 'siteurl';

更新过程中,还有一个小插曲,看到home和siteurl的值是一样的,我强迫症就犯了,开始脑补他们可能有什么不同以及怎么用。脑补了一套理论之后就顺手把siteurl改成了自己感觉更合理的’blog.hugogu.cn’。然后刷新页面一看,500了。所有页面全挂。

然后Google了一下这个问题,确认了这是WordPress苦手普遍都会犯的错误,而不是WordPress自己的问题。便在DMS上直接手工改数据库上的数据把值改回来。但是其实,通过改这个siteurl以求解决的那两个问题还是没有解决:

  1. 通过blog.hugogu.cn访问博客空间,而非blog.hugogu.cn/blog。如果WordPress的配置解决不了这个问题,那就意味着需要改Apache的配置了。这非常让人沮丧。这个后面会介绍。
  2. WordPress为什么要让siteurl可以配置?这个配置是用来解决什么问题的?不过这个问题不在WordPress迁移的主流程上,就不继续深入研究了。

现在数据迁移好了,可以通过blog.hugogu.cn/blog来访问了,但是这个URL本身就特别的不专业——blog这个词出现了两次。我们现在来想办法把这个词干掉。从Apache提供的能力上来看,感觉有很多方式。然而我想找的是最合理最简洁的方式去做。

还有一个问题是Permalinks也始终不工作。网上可以找到的方案基本都会提到三个方案,三个方案都试过了。一直折腾到半夜都没有解决这个问题。

不过想想至少内容都有了,只是样式和配置上和原来的还不太一样。这是第二日。

博客迁移札记——开篇

2022过年这几天在家没啥事儿干,发现阿里的ECS比自己用的云虚拟主机要便宜很多。于是就顺手买了几年的。结果没成想,这一顺手,就把三个节假日搭了进去,期间徒手码了近500条命令才算基本告一段落。

我一直对于写些Google能搜索到的东西没有任何兴趣。但是这三天实际操作下来,发现网上搜索到的资料,当时也许对,但是时至今日,没有一个到现在还是正确而且合理的。

在此,首先感谢开源社区过去10年的卓越贡献,让10年前的实操层面的知识基本都报废了。也让我有动力把这三天的琐碎点滴重新记录下来。感觉写得再啰嗦点,大概就能出书了,《7天精通WordPress迁移——基于CentOS 8、Apache 2.4和MySQL 8》。(尽管我也知道,10年后,本文很可能也没有什么参考价值了。)

I Hear and I Forget, I See and I Remember, I Do and I Understand.

—— 《荀子·儒效》

第一日

服务器到手就是一台祼机,除了自带的CentOS以外,什么都没有。

职业程序员干活第一步,先Google了一下“WordPress迁移”找别人的作业打小抄。找到的比较全的是一个知乎专栏文章《WordPress 搬家方法总结:迁移主机和更换域名》,里面介绍了一个叫All-in-one的插件。看上去自己什么都不用干,只要点点就可以搞定。这很好。

但是走到第二步就卡住了。因为步骤里说:在新主机空间上安装好 WordPress,进入后台安装 All-in-One WP Migration。

得,还是得先自己安装WordPress喽?作为一位不专业的客户,我的期望是,你都叫All-in-one了,居然连自动安装WordPress这么基本的工作都没做。如果我在新主机上,都已经安装好了WordPress,我要你干啥?我为啥不用……我找找……啊,这个WordPress自带的导出功能?

不过转念一想,不对,如果WordPress已经做得够好了,为啥还会有人写插件呢?而且还是成立了一家公司做WordPress迁移服务!这里一定是大坑啊。

官方功能介绍,都只介绍了它能做什么,做好了什么。

然而,它不能做什么,没做好什么,才更值得关注。

—— Hugo Gu

看上面的“导出”功能介绍是看不出什么的了。于是我只能自己试下,也就点下“下载导出的文件”的事儿嘛。

然后眼看着这个文件,一秒钟就下载下来了,我有就种不祥的预感,这特么肯定缺东西啊。但是鬼知道你缺什么啊?你让我肉眼看吗?我看了眼下载下来的XML文件大小,460KB,这肯定没有图片啊。打开简单看了一眼,果然只有文本,图片都是URL地址。好么,这意思是我还要自己再手工导图片?也行,但是更大的问题是:除了图片还有没有别的缺的?

不过无论用哪种方式,看上去我都得自己安装WordPress。我一开始一直没有想法自己安装WordPress,是因为安装WordPress这件事儿,并不是点个exe文件或是跑个yum install这好了。而是至少得安装这些东西:

  • Apache HTTPD:Web服务器。
  • PHP:运行WordPress的脚本语言。世界上最NB的编程语言。(BTW,我还不会。)
  • MySQLMariaDB:WordPress的数据都是存在这里的。
  • WordPress:撰写、发布博客的网站。

然后你为了做管理,可能还得安装下面这些东西。

  • vsftpd: 一个FTP服务器,可以用于管理Web Server里的内容。
  • phpMyAdmin: 一个MySQL的Web管理页面。(你看PHP的NB之处就在于此,人家都不叫MySQLAdmin,而要叫phpXXXX,仅仅是因为用PHP写的。

而且按Linux服务的风格,这些东西不大会是安装好了就可以直接用的。

大家都知道自己用WordPress搭个博客网站不是个十分简单的事儿,所以WordPress自己干脆商业化了,自己官方没有任何文档介绍如何自己用WordPress搭博客(原来是有的,现在全删除了。),只给了个建站手册详细介绍了把WordPress搭好需要注意的各种事项(大概是用来劝退要自建的小白之用),然后自己官方网站隆重推出一键搭博客,每个月30块到500块不等。有兴趣的同学可以去官网了解(需要科学上网)。这一个月30,一年至少360,而且还是功能受限版本,全功能一年6000块。一台虚拟主机也就200多,什么功能都有,作为一名程序员,不仅手痒痒,也肉疼。

另外,微软这里,居然有一篇写于2020年5月的,在IIS上安装WordPress的教程。但是其版的落后程度让人瞠目结舌。

这里又要补充一句,阿里云,在买ECS的时候,可以指定默认安装哪个镜像,其中就有很多WordPress镜像,为啥不直接用呢?主要原因还是一样的,因为我不知道这镜像缺什么,其次就是现有的镜像的OS和PHP和WP的版本都比较老。安装好了一样要升级一轮。而且买了服务器,如果只搭个博客,肯定是浪费了。

说干就干。

先从Web服务装起,WordPress 官方支持的有Apache HTTP和nginx。那我们到底用哪个呢?主要的考量点有两个:功能和性能。考虑个人博客一般对性能要求不高,而且一般Web服务器上的性能问题都可以通过水平扩展的方式解决,然功能问题一般都得靠人才能解决,所以一般而言:功能远比性能重要

网上不难找到性能测评结果,从其中一篇来看,单就WordPress来看,Apache 2.4的性能相比2.2已经有了大幅度的提高,已经接近nginx。而进一步的性能提升,大都会依赖合理地添加缓存,而非Web Server本身的性能优势。说来也是,都是走的FastCGI体系,性能能差多少呢?瓶颈就算有,主要也会是在PHP上。不然WordPress也不会强烈建议用户把PHP升级到7.4以便获得最多几倍的性能提升了。

目前找到的,会影响WordPress使用的,Apache HTTP和nginx的功能上的差别就是文件夹级配置。Apache HTTP支持通过每个文件夹中的.htaccess文件来控制Web Server的行为,但是nginx并没有类似的能力支持。于是WordPress的permalinks,在Apache HTTP上,可以通过自动生成.htaccess文件的方式得到支持(当然,需要配置开启AllowOverride)。但是在nginx上,就必须也要同步修改nginx的配置文件才能生效。从软件工程最佳实践的角度,这违反了功能正交性的基本原则。了解到这一个差别,基本就让我一票否决了nginx。(这里我要再补充一下,以防有人把这句话的覆盖面放大这里“一票否决了nginx”仅限于个人WordPress建站场景。至于各大公司,服务压力大,要把每个节点性能发挥到极致,同时也有实力搞一整套工具做配置管理。用啥都可以。只要规章制度流程健全,贯彻实施到位,用哪个其实都OK。这个问题其实非常有争议,如果你有兴趣,可以在这里加入圣战。)

安装HttpD还是很简单的。

dnf install httpd httpd-manual mod_ssl mod_perl mod_auth_mysql

至于后面那些mod是干啥的,不要问,问就是开源社区常识。然后启动:

systemctl start httpd.service

一个Web服务就好了。但是啥东西都没有,主页都没有。

然后在安装MySQL之前又卡住了,我们到底是安装MySQL呢?还是MariaDB呢?老的服务是MySQL的,虽然MariaDB其实就是从MySQL分出来的,而且从开源血统上讲,比MySQL的还纯正。好,我们来做个更细致的比较吧,拿别人的作业直接看结果吧。总结一下如下表:

MySQLMariaDB
优势高性能、高可用
事务支持
向后兼容
开源
基于 MySQL 社区版
新引擎 (PBXT, XtraDB, Maria, FederatedX)
劣势难以扩展
Oracle买了,使用上有限制
不适合超大数据
新引擎,谁也不知道后面会发生什么。
就像所有的开源引擎一样,没有技术支持。

想想还是MySQL,看上去MariaDB为了不让Oracle有解题发挥过河拆桥的空间,把引擎都给换了。那就不靠谱了。我喜欢折腾,但是不喜欢在正经事儿上折腾。所以当然是MySQL了。

分析完了,安装其实很简单。

dnf install @mysql                  #安装
systemctl start mysqld.service      #启动
systemctl enable mysqld             #开启自动启动
mysql_secure_installation           #配置
mysql -u root -p                    #联上去

之后需要手工创建给WordPress用的数据库。(这里假设你看得懂SQL)

mysql> CREATE DATABASE wordpress;
mysql> CREATE USER `admin`@`localhost` IDENTIFIED BY 'pass';
mysql> GRANT ALL ON wordpress.* TO `admin`@`localhost`;
mysql> FLUSH PRIVILEGES;
mysql> exit

MySQL安装及配置完毕。下面轮到PHP了。

dnf install php php-fpm php-opcache php-gd php-curl php-mysqlnd php-json php-intl php-xml php-pear php-devel
systemctl enable --now php-fpm

网上多数介绍安装的文章,不会一开始就安装这么多东西,但是这些东西WordPress的基本功能就是需要的。所以一次性安装好就好了。比如xml、intl、pear、devel这些一开始可以不安装,但是你会发现WordPress管理后台首页上就会有功能加载不了,同时健康检查也会说有很多框架缺失。(后文会有介绍,当然,如果你按这个命令做,就不会再遇到后文的那些问题了。)

然后下载wordpress的安装包。

curl https://wordpress.org/latest.tar.gz --output wordpress.tar.gz

下了半天下载不下来。速度只有1K/s的样子。然后还下5分钟就断了,然后还没有自动断点续传。我猜这是因为WordPress这个网站不在大局域网里,而且大概率也没做CDN。这个时候,我脑子里冒出三个选择:

  • 无限重试。
  • 在服务器上安装个类似迅雷的玩意。
  • 考虑到我本地和服务器之间的网速还行,我可以在本地下载下来,然后想办法上传到服务器上去。

我思考了几秒钟选了方案三。于是又多了一个事儿。就是如何从本地把文件发到ECS服务器上去。

先是在ECS的网站上找有没有官方支持的功能,找到了一个“发送文件”的功能,刚想夸夸阿里,结果一打开就发现这也太渣了,最大32KB。

算了,还是用最土的办法吧。直接SSH发。第一步先把本地的SSH Key注册到ECS服务器:

本地把pub key复制到剪切板:

pbcopy < .ssh/id_rsa.pub

然后再在服务器上把key写到~/.ssh/authorized_keys里。

为了方便登录,在本地的~/.ssh/config里加上如下配置。

host aliyun-host
    HostName <IP of the Host>
    Port 22
    IdentityFile ~/.ssh/id_rsa
    PasswordAuthentication no
    User <Username to login>

然后就可以这样从本地登录到ECS服务器了。

ssh aliyun-host

然后就可以用下面的命令把本地文件上传到服务器的用户目录上了。

scp wordpress.tar.gz aliyun-host:~

写了3000多字,终于来到了激动人心的,本来要做的正事儿,WordPress安装环节。步骤也很简单。(主要参考了这里。)

tar xf wordpress.tar.gz
cp -r wordpress /var/www/html

这里,网上有些文章会提示说,要登到主机上手工修改wp-config.php文件,把数据库的用户名密码配置上去。其实完全是多余的,WordPress早就可以在界面上配置了。在打开网站之前,需要在ECS的安全组规则中,打开80端口。

然后直接打开网站就好。http://SERVER-HOST-NAME/wordpress。按提示配置数据库链接就好。(这里其实就不太对了,如果只是建立个博客,多数人会希望用 http://SERVER-HOST-NAME/ 访问,这个后面会介绍做法。)更一般地讲:任何登录到服务器上做的手工配置文件变更,都应该避免

就这样,一个空的新的WordPress站建好了。这是第一日。

我像个公主吗?

“爸爸,你看我像个公主吗?”

悠悠穿着一身蓝色的公主裙跑过来,手上拿着小魔仙动画里同款的魔法手杖,摆出要施展魔法的姿势,一脸期待地问我。

“悠悠,你知道吗?是不是个公主,和你穿什么样的衣服没有关系。更重要的是你的气质。”

我说着就在想,现在和悠悠说这些她可能完全听不懂,比如她可能就不知道什么是气质。我一边儿想,一边儿就看到她的脸一点一点地耷拉下来,然后我还没来及再说什么,她就咧着嘴,带着哭腔跑开了,跑到了茶几和沙发之间趴在地上大哭起来,嘴里嘟囔着:“我讨厌爸爸, 我讨厌爸爸。”

刚才的回答,对于她现在五岁的年纪来讲,可能并没有办法真正理解,而只能理解为一种否定。

“悠悠,爸爸没有说你不像一个公主呀,而且你一哭可就不像公主了哦。”

“我不想听爸爸说话,你不要说啦……”

然后悠悠哭着跑回了房间,跟她妈妈哭诉:“爸爸说我不像公主”。

我就在想,我本应该如何回应呢?

一脸惊喜地说,“真是个漂亮的小公主呀。”

还是,“你无论穿什么都是爸爸的小公主。”

这里的问题在于,即使我说的是对的,但是她如果理解错了我的意思,那其实我说的,我想表达的,其实并没有起到任何效果。而且,如果她会错了意,甚至会起到相反的作用。

所以和小孩子说话,甚至和其他所有人说话也一样,一定要用对方能够理解,易于接受的方式去表达。否则自己说得再多也没有用。

当你认为有什么东西会在他成年后对他有帮助时,你也只能讲他现阶段能够理解的东西。 ——卢梭