普通视图

发现新文章,点击刷新页面。
昨天以前Mobility

hexo多语言方案总结及最佳实践

作者 流沙
2024年1月22日 18:12

对于hexo的多语言方案,官方并没有提供很完备的支持,但是网络上有很多人尝试过不同的解决方案。今天,我们对这些方案简单做个总结,看看我们需要的多语言方案是什么样子的。

其实,hexo的多语言方案,说白了就是两个思路,一个是在文章维度切换语言,借助hexo原生的能力和hexo-generator-i18n等插件,用一套框架维护两个语言的内容;另一个则是独立维护不同语言的样式、内容,在站点维度切换语言,只是在域名层面合并到同一个域名。

具体要选择什么方案,取决于大家想要一个什么样的多语言网站。一开始我想象中的多语言网站的是第一种,即统一的首页,在首页和每篇文章上都支持切换语言,对每篇文章,可以通过切换语言,直达对应的其他语言译文上。

这个思路,借助于hexo-generator-i18n插件,可以实现,但要做比较多的定制开发。所以,我又重新思量了一下思路,我要的多语言网站应该是什么样。重新考虑之后,逐渐觉得第二种思路才是更合理的。我们要做一个多语言网站的目的是什么?对我来说,其实就是要获取一些英文流量。作为一个未备案网站,已经很久无法获取百度的搜索流量了,google在中国占的份额又太低,所以单凭中文内容,很难从搜索中获取较高流量。所以我希望通过提供一些英文内容,获取英文流量。这个目标下,完全不需要对所有的文章都维护多语言版本。何况,不同文章的语言受众也的确有很大区别,所以,语言的切换可以直接在站点层面进行。

具体操作方式上,一种是直接维护多个站点,两个站点内容、样式完全独立,只是部署在同一个域名下。比如中文站点根路径是lichuanyang.top,英文站点根路径是lichuanyang.top/en . 两个站点上各做一个跳转链接,向对方跳转。

我采用的是另一种方式,相比上述方式,成本小一些,不需要实际维护两个站点。原理大致如下:

在本地,同样维护两个站点。但是英文站点不会去做实际的部署,只是用作生成静态网页的工具。英文版的内容生成之后,直接复制到中文网站的对应目录下,只对中文网站做部署即可。

具体操作步骤如下:

  • 将博客目录整体复制一份,作为英文博客目录. 例如,我的博客根目录叫blog.source, 复制出一个blog.source.en. 这步完成后,如果博客源码也是以git维护的,可以直接在原目录的外层直接新建一个git项目,在外层做管理就行了。 示例如下:

    1
    2
    blog(git根目录)/blog.source
    /blog.source.en
  • 将en目录下的文章全部删除;站点描述等文本调整为英文内容;英文语言设置为en;英文站点根 (_config.yml中的root配置)设置成/en

  • 在两个站点下增加跳转链接。我是借助菜单功能,直接在菜单里加一个其他语言项。 next主题中配置如下:

    1
    2
    3
    中文站 :    English: /en || fa fa-language
    英文站: 中文: https://lichuanyang.top || fa fa-language

  • 之后配置基本就完成了,可以在en目录下写英文文章,流程与之前写中文文章时完全一致

  • 最后就是生成和发布环节了,这一步要注意,每次生成时,我们需要先生成中文站点,再生成英文站点并将英文内容复制到中文目录下,避免生成中文内容时将英文内容覆盖掉。具体操作示例如下:

    1
    hexo clean && hexo g &&cd ../blog.source.en && hexo clean && hexo g && cd ../blog.source &&cp -r ../blog.source.en/public/. public/en/ && hexo s

    命令最后一段,使用hexo s就是本地启动,使用hexo d就是发布出去,和正常使用时一样。

这样,我们就有了一个好用的多语言站点了。之后,写中文内容就在中文目录下进行,写英文内容就在英文目录下进行, 最后执行一下上边的命令就可以了。

原文地址: http://lichuanyang.top/posts/40400/

怎么理解数据库的四个隔离级别

作者 流沙
2023年12月8日 17:12

数据库的事务隔离级别实际上是个很好理解的东西。

但是在一些劣质博客和公众号的努力下,这块知识已经成功的成为了一片烂八股。

接下来,只需要顺着我的思路,回答几个简答的问题,就可以将事务隔离级别理解透彻。

问:怎么实现不同事务直接的隔离?

对这个问题,当你处于讨论数据库事务隔离级别语境下时,是不是觉得很难回答?然后先把数据库三个字从脑子里剃掉,不同事务,也就是不同线程,操作数据怎么做隔离?答案是不是很简单?就是加锁。

问:锁有哪些类型

答:有很多种分类方式。在这里,我们把锁分成读锁和写锁。

问:加不同级别锁时,代码的表现会有什么区别?

到这里,原问题的答案是不是就呼之欲出了。

事实上,数据库事务为什么会有这些个不同的隔离级别,就是因为不同场景下使用了不同的锁。比前边说的稍微复杂的点,就是数据库中存在范围锁,在其他领域不太常用,就是把某一段数据整体锁住。

如果我们把所有操作能加的锁都加上,实际上就是串行化的操作了。这种方式隔离性当然很好,但性能就没法说了,所以一般也不会有人使用。

可重复读则是对涉及到的数据加读锁和写锁,并持有到事务结束,但不会加范围锁。这样就会出现幻读的问题,即一个事务内执行两次范围查询,如果这两次查询之间有新的数据被插入,就会导致两次范围查询的结果不一致。

读已提交和可重复读的区别是他的读锁会在查询操作结束之后立刻释放掉,这样,在事务执行过程中,已经查询过的数据是可以被其他事务任意修改的,所以也就会有不可重复读的问题。

读未提交级别下,则完全不会加读锁。这样造成的问题是,由于读操作时不会去申请读锁,所以反而会导致能够读到其他事务上加了写锁的数据,也就会出现脏读的问题。

当然,为了更好的平衡性能与隔离性,还有一些诸如MVCC之类的方案,用额外的存储来实现事务隔离的效果,这些是取巧解决80%问题的方式。

原文地址: http://lichuanyang.top/posts/9775/

读书笔记《系统之美》,如何面对现实中的复杂问题

作者 流沙
2022年5月16日 18:34

这本书的作用主要是教大家面对现实中的复杂问题,如何更有效的去思考。书本身偏重系统的基本概念,这些概念其实都比较好理解。另外,作者举了非常多的例子。初读这本书时,会感觉理论和实际例子有那么些割裂。 但是后来逐渐感觉到,这个话题确实是没那么容易讲的通俗易懂的,需要读者自己付出更多的思考,才能有实质性的收获。而作者举的这些例子,就是非常好的引导读者去思考的引子。通过跟着作者一起思考这些例子,我们就可以帮助自己更好的理解书中的概念和逻辑。

所以,这篇笔记中,我主要其实就是比较机械化的把书中的一些核心概念提炼出来,同时会尝试举一些身边的例子。 而若想更好的理解书中每一部分,还是推荐去读一遍书,并且跟着作者的思路一起去好好思考那些例子的逻辑。

基本概念

系统是一组相互连接的事物,在一定时间内,以特定的行为模式相互影响。

任何一个系统都包括三种构成要件:要素、连接、功能或目标。

存量是所有系统的基础,比如浴缸中的水、人口数量、书店中的书、树木的体积、银行里的钱,等等。但是,存量不一定非得是物质的,你的自信、在朋友圈中的良好口碑,或者对世界的美好希冀等,都可以是存量。

存量会随着时间的变化而不断改变,使其发生变化的就是“流量”

绘制存量-流量图,是理解系统的基础方式。

当某一个存量的变化影响到与其相关的流入量或流出量时,反馈回路就形成了。

调节回路与增强回路是两种很常见的反馈回路,比较好理解,就是字面意思。调节回路趋向于将系统存量维持稳定,增强回路则是会不断放大、增强原有的发展态势。

常见的系统

接下来通过一些常见的系统模型,来帮助大家更好的理解系统是什么。

单存量系统

  • 系统1.1: 一个存量、两个相互制衡的调节回路的系统, 比如温度调节器,通过一个升温调节回路、一个降温调节回路,控制存量(温度)稳定。

  • 系统1.2:一个存量、一个增强回路以及一个调节回路的系统, 比如人口,新出生是增强回路,死亡是调节回路。

  • 系统1.3:含有时间延迟的系统,比如库存,相比简单的温度调节器,主要区别在于调节回路上会存在延时。

双存量系统

  • 系统2.1:一个可再生性存量受到另外一个不可再生性存量约束的系统, 比如开采石油等不可再生资源,就会存在资本和资源两个存量,其中存在一个增强回路,即开采资源过程中会获得利润,获得的利润能够增加资本,从而扩大产能,加大开采量。同时,由于石油不可再生,开采难度会越来越大,也就形成了一个调节回路。

  • 系统2.2:有两个可再生性存量的系统,比如渔业,和石油比较类似,只是鱼是可再生资源,会导致系统的发展形态会和上一类系统有所区别。

系统的三大特征

  • 适应力。 系统之所以会有适应力,是因为系统内部结构存在很多相互影响的反馈回路,正是这些回路相互支撑,即使在系统遭受巨大的扰动时,仍然能够以多种不同的方式使系统恢复至原有状态。比如人体就是一个适应力非常强的系统。

  • 自组织。 系统所具备的使其自身结构更为复杂化的能力,被称为“自组织”。自组织对于系统来说,意味着系统可以自己去“进化”,从而完成更大的目标,或实现更复杂的功能。像我之前的老板,经常跟我说我们的目标是去建立一个具有“自组织”能力的团队。只有这样,团队才能跟着业务发展一起进步。

  • 层次性。 在新结构不断产生、复杂性逐渐增加的过程中,自组织系统经常生成一定的层级或层次性。一个大的系统中包含很多子系统,一些子系统又可以分解成更多、更小的子系统。如果各个子系统基本上能够维系自身,发挥一定的功能,并服务于一个更大系统的需求,而更大的系统负责调节并强化各个子系统的运作,那么就可以产生并保持相对稳定的、有适应力和效率的结构。

系统的障碍

我们认为自己所知道的关于这个世界的任何东西都只是一个模型。我们的模型通常是与现实世界高度一致的。这就是我们为什么会成为这个星球上最为成功的一个物种的原因。 我们的模型仍远远达不到能完整地描绘世界的程度。这就是我们为什么经常会犯错误、会感到出乎意料的原因。

所以,我们要看看从系统的角度可以发现哪些问题,帮助我们尽量的少犯错。作者主要整理了以下6点:

  • 系统结构是行为的根源,而系统行为体现为随时间而发生的一系列事件;而人们容易被表象所迷惑
  • 在非线性的世界里,不要用线性的思维模式
  • 需要恰当地划定系统边界
  • 要能看清各种限制因素
  • 系统中无所不在的时间延迟
  • 人们只有有限理性, 导致决策往往并非整体最优

系统的陷阱与对策

这一章则是一些更具体的问题,以及针对这些问题有哪些可能的对策。

政策阻力;治标不治本

有一些长期持续的行为模式,可能并不符合人们的预期,往往被视为一个问题。尽管人们发明了各种技术、采取了多项政策措施,试图去“修复”它们,但系统好像很顽固,每年都产生相同的行为。这是一种常见的系统陷阱,人们习惯称之为“治标不治本”或“政策阻力”, 比如毒品泛滥、失业等。

“政策阻力”来自于系统中各个参与者的有限理性,每一个参与者都有自己的目标。当各个子系统的目标不同或不一致时,就会产生变革的阻力。应对“政策阻力”最有效的方式是,设法将各个子系统的目标协调一致,通常是设立一个更大的总体目标,让所有参与者突破各自的有限理性。

公地悲剧

对于人们共同分享的、有限的资源,很容易出现开发(或消耗)逐步升级或增长的态势。“公地悲剧”之所以产生,一个重要原因是资源的消耗与资源的使用者数量增长之间的反馈缺失了,或者时间延迟太长。

防止“公地悲剧”有以下三种方式:一是教育,帮助人们更清晰的看到后果;二是将资源私有化;三是采取一些配额制之类的管制措施。

目标侵蚀

绩效标准受过去绩效的影响,尤其是当人们对过去的绩效评价偏负面,也就是过于关注坏消息时,将启动一个恶性循环,使得目标和系统的绩效水平不断下滑。通俗的说法,就是温水煮青蛙,形势在不知不觉中慢慢变差。

对策是保持一个绝对的绩效标准。更好的状况是,将绩效标准设定为过去的最佳水平,从而不断提高自己的目标,并以此激励自己。

竞争升级

“以眼还眼,以牙还牙”。每一个参与者期望的系统状态都是相对于其他参与者而言的,并试图超越对方,领先一步,连并驾齐驱都不行;而且每个参与者都有高估对方的敌意、夸大对方实力的倾向。竞争升级的系统结构是一个增强回路,它是以指数级方式发展起来的,一旦超过某个限度,其使竞争激化的速度会超出绝大多数人的想象。

对策一种依托于一方主动让步;更优雅的方式则是期望双方达成协定。

富者愈富

利用积累起来的财富、权力、特殊渠道或内部信息,可以创造出更多的财富、权力、渠道以及信息。这些都是另外一个被称为“富者愈富”的基模的例子。

对策:多元化,即允许在竞争中落败的一方可以退出,开启另外一场新的博弈;反垄断法,即严格限制赢家所占有的最大份额比例;修正竞赛规则,限制最强的一些参与者的优势,或对处于劣势的参与者给予一些特别关照,增强他们的竞争力(例如施舍、馈赠、税赋调节、转移支付等);对获胜者给予多样化的奖励,避免他们在下一轮竞争中争夺同一有限的资源,或产生偏差。

转嫁负担

当面对一个系统性问题时,如果采用的解决方案根本无助于解决潜在的根本问题,只是缓解(或掩饰)了问题的症状时,就会产生转嫁负担、依赖性和上瘾的状况。

应对这一陷阱最好的办法是提前预防,防止跌入陷阱。一定要意识到只是缓解症状或掩饰信号的政策或做法,都不能真正地解决问题。

规避规则

任何规则都可能会有漏洞或例外情况,因而也存在规避规则的机会。也就是说,虽然一些行为在表面上遵守或未违背规则,但实质上却不符合规则的本意,甚至扭曲了系统。

对策是设计或重新设计规则,从规避规则的行为中获得创造性反馈,使其发挥积极的作用,实现规则的本来目的。

目标错位

系统行为对于反馈回路的目标特别敏感。如果目标定义不准确或不完整,即使系统忠实地执行了所有运作规则,其产出的结果却不一定是人们真正想要的。

对策就是恰当地设定目标及指标,以反映系统真正的福利。

系统的变革方式,能够干预系统的杠杆点

这一部分则是一些我们能够去干预系统的方式,按照有效性由低到高去排列。这一章里的很多理论,相信大家或多或少在其他的地方也见到过一些。很多优秀的人,即便没读过这本书,很多时候其实也是这么思考的。而系统之美这本书,能够帮助我们更加体系化的了解为什么要这么做。

12:数字,通过各种流量的数值来调节系统。像王者荣耀处理平衡性的策划,基本上很大一部分精力就在做这事,哪个英雄机制过强了,就狠狠的削一把数值,哪个英雄不大行,就加数值,让他站撸也有很高的强度。这种方式实际上是效力很低的一种方式,它无法改变系统基本的结构。但是大多数人90%的注意力都会集中在参数上。

11:缓冲器。通过提高缓冲器的容量,我们通常可以使系统稳定下来。但是,如果缓冲器过大,系统也将变得缺乏弹性,它对于变化的反应速度将过于缓慢。同时,要建立、扩大或维护某些缓冲器的容量,也需花费巨大的时间和资金,例如建设水库或仓库等。为此,一些企业发明了“零库存”的“及时生产”模式。在这些企业看来,与耗费巨资维持固定的库存相比,偶尔的波动或缺货造成的损失并不是很大。

10.存量—流量结构:实体系统及其交叉节点。这一点主要关乎于系统的整体设计。恰当的杠杆点,需要从一开始就被设计好。一旦实体的结构建立起来了,要想找到杠杆点,就需要理解系统的限制和瓶颈,在尽可能发挥它们的最大效率的同时,避免出现较大的波动或扩张,超出其承受能力。而如果系统已经运行起来,想再去调整其中的关键节点,就会变的很困难。软件工程中著名的“防腐层”概念,可以一定程度上优化这个问题,即通过一些预设的中间层,减少调整关键节点的成本。

9.时间延迟:系统对变化做出反应的速度。时间延迟是一个高杠杆点,但事实上,时间延迟通常不是很容易改变的。很多事物的发展有其内在规律,该花多长时间就得花多少时间。你不可能一夜之间积累起一大笔资本,孩子也不可能在一夜之间长大,拔苗助长也无法加快庄稼的生长。但如果有办法改变时间延迟,往往可以取得显著效果。比如近期疫情防控中,为什么要经常进行大规模的核酸检测,就是通过这种方式降低病毒从被传播到被发现这一段的时间延迟。

8.调节回路:试图修正外界影响的反馈力量。这一点很好理解,我们可以通过加调节回路的方式,加强对系统的控制。kubernetes里的controller可以认为就是调节回路的实现,通过不同的controller, 就可以将不同组件的值,也就是存量,控制在期望值上。

7.增强回路:驱动收益增长的反馈力量。和调节回路类似,只是目标不同。

6.信息流:谁能获得信息的结构。相当于一个新的回路,让人们在之前得不到信息的地方可以获得反馈。比如说资本主义罪恶的加班排名,就是将同事的加班信息告诉你,让你自然产生压力,从而促进加班。

5.系统规则:激励、惩罚和限制条件。这一部分,实际上就是将系统的范围、边界和自由度定义清楚。

4.自组织:系统结构增加、变化或进化的力量。接文章开头的话题,要建设一个自组织的团队,是要做很多事情的,比如提升团队中每个成员的能力,让大家都能有决策能力;加强团队内信息的透明度;等等。而这样的团队一旦能建成,不光产出会有质的提升,团队中成员的工作成就感和幸福度也会大幅度提升。

3.目标:系统的目的或功能。系统中的某个参与者可以清晰地设定、阐述、重复、支持并坚持新的目标,从而引导了系统的变革。这就是为什么这些年OKR这么流行的原因。通过明确合理的OKR, 指导大家更合理的决策和行动。

2.社会范式:决定系统之所以为系统的心智模式。一些社会公认的观念,一些潜在的基本假设以及关于社会现实本质的普遍看法,构成了社会的范式(paradigm),或者是一整套世界观,它们是人们普遍相信的、关于世界是如何运作的一系列基本假设、规则或信念。这些信念都是隐含的,因为在一个社会中,几乎每一个人都已经知道它们,因而无须特别申明。这些范式自然会对系统运行产生极其重大的影响,当然,要改变这些范式,也会比改变其他东西更困难。

1.超越范式。与改变范式相比,在更高的层次上,还有另外一个杠杆点,那就是使自己摆脱任何范式的控制。这一点其实有那么一点玄学的意思了,我们在这里就不多展开。核心点其实就是我们要有意识的去跳出当前系统。

系统的生存法则,与系统共舞

在工业社会长大的人,若热衷于系统思考,很可能会犯一个严重的错误。他们可能会假定,通过系统分析,可以认清系统中的相互联系以及复杂纠葛,借助计算机的威力,最后找到预测和控制系统的钥匙。不幸的是,这是错误的观念,其根源在于工业时代根深蒂固的心智模式,即相信存在一把预测和控制的钥匙。但实际上,要做到这一点是完全不现实的,我们需要认识到并愿意放弃控制的错觉,需要换一种截然不同的方式。这样,我们仍然可以有很大的作为空间。这种方式就是与系统共舞。我们无法控制系统,但是可以在系统中生存的更好。

接下来就是和系统共舞的一些法则,这一部分相当于一些小tips, 也都比较好理解,我们就简单列举一下:

  • 跟上系统的节拍:要观察系统是如何运作的,才能跟上它。我们需要多关注事实和数据,才不至于被别人的理论带着走。
  • 把你的心智模式展现在阳光下,画出系统结构图,强迫自己把自己内心隐藏的各种假设投射出来,并精准地表述它们。
  • 相信、尊重并分享信息
  • 谨慎地使用语言,并用系统的概念去丰富语言
  • 关注重要的,而不只是容易衡量的
  • 为反馈系统制定带有反馈功能的政策。对于动态的、自我调节的反馈系统,不能用静止的、刚性的政策来进行管制,好的政策必须能够根据系统状态的变化及时地灵活调整。
  • 追求整体利益
  • 聆听系统的智慧
  • 界定系统的职责
  • 保持谦逊,做一名学习者
  • 庆祝复杂性
  • 扩展时间的范围
  • 打破各种清规戒律
  • 扩大关切的范围
  • 不要降低“善”的标准

原文地址: http://lichuanyang.top/posts/53791/

分布式系统设计中的通用方法

作者 流沙
2022年4月13日 20:13

之前翻译过一篇关于分布式系统的文章 https://lichuanyang.top/posts/3914/ ,在各个平台都取得了不错的反响。因此,最近又重新整理了一下相关的知识,结合一些这一年多里新的理解,重新整理了下这篇文章。

首先我们需要明确本文要讨论的分布式系统是什么,简单的说,就是满足多节点和有状态这两个条件即可。多节点很好理解,有状态则是指这个系统要维护一些数据,不然的话,其实我们无脑的水平扩容就没有任何问题,也就不存在分布式系统的问题了。

常见的分布式系统, 无论是mysql, cassandra, hbase这些数据库,还是rocketmq, kafka, pulsar这样的消息队列,还是zookeeper之类的基础设施,其实都满足这两个条件。

这些分布式系统的实现通常来说主要需要关注两个方面:一是自己本身功能的实现,二是在分布式环境下保持良好的性能与稳定性;即便是两个功能完全不一样的系统,其对第二类问题的处理方式也会有很多相似之处。本文的关注重点也即在对第二类问题的处理上。

接下来,我们列举一下分布式系统都有哪些常见目标,包括而不限于:

  • 大量普通的服务器通过网络互联,对外作为整体提供服务;
  • 随着集群规模增长,系统整体性能表现为线性增长;
  • 能够自动容错,故障节点自动迁移,不同节点的数据要能保持一致性;

要达成这些目标,又有哪些挑战呢?大概有以下这些:

  1. 进程崩溃: 原因很多,包括硬件故障、软件故障、正常的例行维护等等,在云环境下会有一些更加复杂的原因;
    进程崩溃导致的最大问题就是会丢数。出于性能的考虑,很多情况下我们不会进行同步的写磁盘,而是会将数据暂时放在内存的缓冲区,再定期刷入磁盘。而在进程崩溃的时候,内存缓冲区中的数据显然会丢失。

  2. 网络延迟和中断: 节点的通信变到很慢时,一个节点如何确认另一个节点是否正常;

  3. 网络分区: 集群中节点分裂成两个子集,子集内通信正常,子集之间断开(脑裂),这时候集群要如何提供服务。

这里插一个彩蛋,在CAP理论的前提下,现实中的系统通常只有两种模式:放弃高可用的CP模式和放弃强一致性的AP模式。为什么没有一种放弃分区容忍性的CA模式?就是因为我们无法假设网络通信一定正常,而一旦接受了集群变成两个分区,再想合并回来就不现实了。

  1. 进程暂停:比如full gc之类的原因导致进程出现短暂的不可用后又迅速恢复,不可用期间集群有可能已经做出了相关的反应,当这个节点再恢复的时候如何维持状态的一致性。

  2. 时钟不同步和消息乱序:集群内不同节点的操作,我们希望它的顺序是明确的;不同节点之间的时钟不同步,会导致我们无法利用时间戳确保这件事。而消息的乱序就给分布式系统的处理带来了更大的难度。

下面,我们就依次介绍,针对这些问题,都有什么处理方式。

对于进程崩溃的问题,首先要明确的是,单纯实现进程崩溃下不丢数,没有任何难度,重要的是怎么在保证系统性能的前提下达到这个目标。

首先要介绍的就是write-ahead log这种模式,服务器将每个状态更改作为命令存储在硬盘上的仅附加(append-only)文件中。 append操作由于是顺序的磁盘写,通常是非常快的,因此可以在不影响性能的情况下完成。 在服务器故障恢复时,可以重播日志以再次建立内存状态。

其关键思路是先以一个小成本的方式写入一份持久化数据,不一定局限于顺序写磁盘,此时就可以向client端确认数据已经写入,不用阻塞client端的其他行为。server端再异步的去进行接下来高消耗的操作。

典型场景及变体:mysql redo log; redis aof; kafka本身 ;业务开发中的常见行为:对于耗时较高的行为,先写一条数据库记录,表示这个任务将被执行,之后再异步进行实际的任务执行;

write-ahead log会附带一个小问题,日志会越攒越多,要如何处理其自身的存储问题呢?有两个很自然的思路: 拆分和清理。

拆分即将大日志分割成多个小日志,由于WAL的逻辑一般都很简单,所以其拆分也不复杂,比一般的分库分表要容易很多。这种模式叫做 Segmented Log, 典型的实现场景就是kafka的分区。

关于清理,有一种模式叫做low-water mark(低水位模式), 低水位,即对于日志中已经可以被清理的部分的标记。标记的方式可以基于其数据情况(redolog), 也可以基于预设的保存时间(kafka),也可以做一些更精细的清理和压缩(aof)。

再来看网络环境下的问题,首先使用一个非常简单的心跳(HeartBeat)模式,就可以解决节点间状态同步的问题。一段时间内没有收到心跳,就将这个节点视为已宕机处理。

而关于脑裂的问题,通常会使用大多数(Quorum)这种模式,即要求集群内存活的节点数要能达到一个Quorum值,(通常集群内有2f+1个节点时,最多只能容忍f个节点下线,即quorum值为f+1),才可以对外提供服务。我们看很多分布式系统的实现时,比如rocketmq, zookeeper, 都会发现需要满足至少存活多少个节点才能正常工作,正是Quorum模式的要求。

Quorum解决了数据持久性的问题,也就是说,成功写入的数据,在节点失败的情况下,是不会丢失的。但是单靠这个,无法提供强一致性的保证,因为不同节点上的数据是会存在时间差的,client连接到不同节点上时,会产生不同的结果。可以通过主从模式(Leader and Followers) 解决一致性的问题。其中一个节点被选举为主节点,负责协调节点间数据的复制,以及决定哪些数据对client是可见的。

高水位(High-Water Mark)模式是用来决定哪些数据对client可见的模式。一般来说,在quorum个从节点上完成数据写入后,这条数据就可以标记为对client可见。完成复制的这条线,就是高水位。

主从模式的应用范围实在太广,这里就不做举例了。分布式选举算法很多,比如bully, ZAB, paxos, raft等。其中,paxos无论是理解还是实现难度都太大,bully在节点频繁上下线时会频繁的进行选举,而raft可以说是一种稳定性、实现难度等各方面相对均衡,使用也最广泛的一种分布式选举算法。像elastic search, 在7.0版本里,将选主算法由bully更换为raft;kafka 2.8里,也由利用zk的ZAB协议,修改为raft.

到这儿,我们先总结一下。实际上,一个对分布式系统的操作,基本上就可以概括为下边这么几步:

  1. 写主节点的Write-Ahead Log;
  2. 写1个从节点的 WAL
  3. 写主节点数据;
  4. 写1个从节点数据
  5. 写quorum个子节点WAL
  6. 写quorum个子节点数据

其中,2-5步之间的顺序不是固定的。分布式系统平衡性能和稳定性的最重要方式,实质上就是决定这几步操作的顺序,以及决定在哪个时间点向client端返回操作成功的确认信息。例如,mysql的同步复制、异步复制、半同步复制,就是典型的这种区别的场景。

关于进程暂停,造成的主要的问题场景是这样的:假如主节点暂停了,暂停期间如果选出了新的主节点,然后原来的主节点恢复了,这时候该怎么办。这时候,使用Generation Clock这种模式就可以,简单的说,就是给主节点设置一个单调递增的代编号,表示是第几代主节点。像raft里的term, ZAB里的epoch这些概念,都是generation clock这个思路的实现。

再看看时钟不同步问题,在分布式环境下,不同节点的时钟之间必然是会存在区别的。在主从模式下,这种问题其实已经被最大限度的减少了。很多系统会选择将所有操作都在主节点上进行,主从复制也是采取复制日志再重放日志的形式。这样,一般情况下,就不用考虑时钟的事情了。唯一可能出问题的时机就是主从切换的过程中,原主节点和新主节点给出的数就有可能存在乱序。

一种解决时钟不同步问题的方案就是搞一个专门的服务用来做同步,这种服务叫做NTP服务。但这种方案也不是完美的,毕竟涉及到网络操作,所以难免产生一些误差。所以想依靠NTP解决时钟不同步问题时,系统设计上需要能够容忍一些非常微弱的误差。

其实,除了强行去把时钟对齐之外,还有一些简单一些的思路可以考虑。首先思考一个问题,我们真的需要保证消息绝对的按照真实世界物理时间去排列吗?其实不是的,我们需要的只是 一个自洽、可重复的确定消息顺序的方式,让各个节点对于消息的顺序能够达成一致即可。也就是说,消息不一定按照物理上的先后排列,但是不同节点排出来的应该一样。

有一种叫Lamport Clock的技术就能达到这个目标。它的逻辑很简单,如图所示

lamport stamp

就是本机上的操作会导致本机上的stamp加1,发生网络通信时,比如C接收到B的数据时,会比较自己当前的stamp, 和B的stamp+1, 选出较大的值,变成自己当前的戳。 这样一个简单的操作,就可以保证任何有相关性的两个操作(包括出现在同一节点、有通信两种情况)的顺序在不同节点之间看来是一致的。

另外,还有一些相对简单些的事情,也是分布式系统设计中经常要考虑的,比如怎么让数据均匀的分布在各个节点上。对于这个问题,我们可能需要根据业务情况去找一个合适的分片key, 也可能需要找到一个合适的hash算法。另外,也有一致性哈希这种技术,让我们控制起来更自如。

分布式系统设计中还需要重点考虑的一块就是如何衡量系统性能,指标包括性能(延迟、吞吐量)、可用性、一致性、可扩展性等等,这些说起来都比较好理解,但要是想更完善的去衡量,尤其是想更方便的去观测这些指标的话,也是一个很大的话题。

原文地址: http://lichuanyang.top/posts/45718/

高并发解决方案很难吗?轻松聊清楚高并发设计

作者 流沙
2022年3月29日 18:40

高请求并发就一定会有高并发问题吗?其实不是的。可以设想一下,假如我们的应用全是内存逻辑,无论请求量再大,其实我们简单的增加节点就可以解决问题,那么自然不存在所谓高并发问题。高并发问题之所以存在,是因为系统中存在一些单点瓶颈,这个瓶颈是无法靠粗暴扩容解决的,所以我们才需要找别的方案解决这个问题。

事实上,这个瓶颈,绝大多数情况下都是数据库。

对于90%以上的场景来说,高并发问题本质的难点就在于数据库能够承载的并发是有限的。而各种高并发技术方案的作用归根结底其实也都是去降低单库的连接数,

比如:

系统拆分:首先将不同业务的库分开,每个业务可以各自独享一个数据库;

缓存:使用缓存降低需要访问数据库的比例;

MQ等方式削峰:避免瞬时的数据库连接数过多;

分库、分表、读写分离:对同一业务的数据库做进一步的拆分,降低单库的访问量;

引入elastic search, clickhouse等其他存储:与上一条类似,将一些不适合mysql的业务拆分出来,进一步降低mysql的并发;

上面的思路,是提升应用的处理能力;不需要所有请求都去连数据库,那么自然就可以承载更高的并发了。

另一个方向的思路,是限流,这样做的主要意义有两个:保证处理能力内的这一部分请求能够被正常处理,而不是拖垮所有请求; 即使有问题,也将问题限制在一个很小的范围。

限流的方式很多,比如我们熟悉的漏桶、令牌桶等算法,这里就不再细说。

除此之外,还有各种我们所熟悉的“池子”,比如tomcat连接池、线程池等,包括应用里的mysql连接池,其实也是在限制能够被发出的最大请求数。

为了确定这些池子的状态,要做好监控,比如tomcat的活跃连接数、mysql的活跃连接数等等,快要满的时候就要看情况准备扩容了。

如何给这些池子设置更合理的参数,保证:机器的数据库的资源被充分利用;池子未满时,确定不会超过数据库的负载

这样理解下来,我们也就知道为什么大部分讲高并发处理思路的文章,关注焦点都在缓存、分库分表、连接池优化这些事情上了。

原文地址: https://lichuanyang.top/posts/11970/

从redolog,undolog到隔离级别,刨根问底,讲清楚事务和ACID

作者 流沙
2022年2月7日 16:40

前些日子读了周志明老师的《凤凰架构》这本书,对于很多方面的技术有了更深的认知,因此打算做一些总结。今天先以讲事务的这一段做个印子,结合书中内容和个人理解,争取将本地事务的相关知识讲个命名白白。如果有讲的不对的地方,欢迎大家多多指正。

所谓事务,就是保证数据库中的数据都是符合期望的,在不断的增删改查中,数据库会不断的从一个正确的状态变化到另一个正确的状态,而不会被外界感知到不“正确”的中间状态。

举个常见的例子,就是在 A有100元,B有100元的状态下,A要给B转账10元,肯定会有A先转出10元,再转给B,这样的中间状态。事务就是,对于用户来说,只能感知到 A100 B100 和 A90 B110 这两个状态,而不会感知到过程中的A90 B100等等奇奇怪怪的状态。

这个介绍,其实也就是事务ACID特性中一致性的概念。ACID这个说法虽然很流行,但A、C、I、D之间其实并不是平等的概念,简单来说,AID是方法,C是目的。也就是说,实现了原子性、持久性、隔离性,也就实现了一致性,也就实现了事务。

接下来,我们就依次看看AID分别要如何实现。

原子性和持久性

实现原子性和持久性面临的相同问题其实挺多的,所以把它们放在一起介绍。

先复习一下基本概念,所谓原子性,就是事务内的操作要么都成功,要么都失败;所谓持久性,就是已经完成的操作不会丢失。

值得说明的是,单纯的“实现原子性和持久性”并不存在任何难度,要讨论的问题其实是“如何更高性能的实现原子性和持久性”,(后面要说的隔离性也是一个概念,只有高性能的隔离性才有意义)。

其中一个关键点在于,写磁盘是一个非常重的操作,所以通常会存在一个内存缓冲区,要写磁盘的数据会先写到缓冲区里,再择机落盘。那么假如在事务已提交而尚未落盘的这个时间点,系统出现故障,那么这部分未落盘的数据自然就会丢失,数据库也就失去了持久性。针对这个问题,一个很自然的想法就是事务提交的时候强制刷盘。这个方案可不可行?当然是可行的。但它的问题就是会影响性能。系统出故障毕竟是小概率事件,为了处理这个小概率事件,相当于所有操作都要额外付出一些代价。

实际上为了解决这个问题,一个常规的处理思路就是使用一个commit Log, 也就是在实际写数据之前,先将所有要修改的信息记录在一个log里,如果出现上面描述的问题,系统重启时,会先根据commit log进行数据恢复。而由于写这份log是一个顺序写磁盘的操作,性能会远远好于随机写磁盘,所以这个方式的性能是没有问题的。在数据真正写入之后,再加一个标记,表示这条log已经完成了持久化。

接下来,我们再看一下commit log是否还有优化空间?自然是有的。Commit log的一个重要缺陷就是所有真实的磁盘操作都必须发生在事务提交之后。假如说这个事务非常大,就会占用很大的内存缓冲区,这也会影响系统的性能。改进方案是 write-ahead log 这个机制,这个机制我在之前的文章(https://lichuanyang.top/posts/3914/)里也介绍过,和commit log其实非常像,也是先顺序写一个log文件,唯一的区别就是write-ahead log允许在事务提交之前写入。mysql里的redo log,其实就是一个典型的write-ahead log实现。

讲到这里,我们先暂停一下,回顾一下上面的内容,会发现上边其实基本都在说持久性,原因是对于上边的机制来说,原子性其实都是自然而然的事情,commit log写进去了,这条事务就相当于完成了;commit log没写入,这个事务就相当于不存在。但是使用了write-ahead log的话,情况就不一样了,一个事务会涉及多次磁盘写入,所以也就不满足原子性了。因此,需要引入别的机制来保证原子性,undo log就是实现这个目标的一个典型思路。当变动数据写入磁盘前,必须先记录undo log,注明修改了哪个位置的数据、从什么值改成什么值等,以便在事务回滚或者崩溃恢复时根据undo log对提前写入的数据变动进行擦除。

像在mysql中,实际上也就是像我们上边讲的那样,利用redo log和undo log实现高效可靠的持久性和原子性。

隔离性,隔离级别

如何实现不同事务之间的隔离,一个很自然的思路就是加锁,实际上常规的数据库实现也就是这么做的。一般来说,有这么几种类型的锁:读锁(也叫共享锁),写锁(也叫排他锁),范围锁。

对于一条数据,只有一个事务能持有写锁;不同的事务可以同时持有读锁,数据被添加读锁后不能再添加写锁, 添加写锁后也不能再添加读锁;范围锁则是对一个范围加写锁,在这个范围内都不能写入数据。

我们知道,数据库有四种常见的隔离级别:可串行化,可重复读,读已提交,读未提交。其区别其实就是加锁粒度的不同。

如果我们把所有操作能加的锁都加上,实际上就是串行化的操作了。这种方式隔离性当然很好,但性能就没法说了,所以一般也不会有人使用。

可重复读则是对涉及到的数据加读锁和写锁,并持有到事务结束,但不会加范围锁。这样就会出现幻读的问题,即一个事务内执行两次范围查询,如果这两次查询之间有新的数据被插入,就会导致两次范围查询的结果不一致。

读已提交和可重复读的区别是他的读锁会在查询操作结束之后立刻释放掉,这样,在事务执行过程中,已经查询过的数据是可以被其他事务任意修改的,所以也就会有不可重复读的问题。

读未提交级别下,则完全不会加读锁。这样造成的问题是,由于读操作时不会去申请读锁,所以反而会导致能够读到其他事务上加了写锁的数据,也就会出现脏读的问题。

其实说到底,隔离性和性能就是一对相互矛盾的需求。加锁加的越多,隔离性自然越好,性能也自然越差。我们需要根据实际的使用场景来决定锁究竟要加到什么程度。

而另一个思路,则是再看看有没有锁以外的方式,考虑28原则,看看有没有什么办法,牺牲20%的性能去解决80%的问题。具体来讲,涉及隔离性问题的场景,其实可以简化为一个读事务+一个写事务,和两个写事务,这两种场景。大部分场景下,当然是读+写的情况更多,所以我们找个方式去解决读+写场景下的幻读问题。相信很多人已经猜到了,这种方式就是MVCC。关于MVCC, 网上介绍资料实在太多,我们就不再赘述了。

经过上面对于事务几个特性的介绍,相信大家已经对本地事务有了非常深刻的认识。如有问题,欢迎留言讨论。下一篇文章,我会继续讲一下分布式事务的相关知识,如果感兴趣,可以关注我的个人博客、知乎或者公众号追更~

原文地址: http://lichuanyang.top/posts/7774/

java项目低学习成本使用kubernetes的实践经验

作者 流沙
2022年1月26日 15:19

很多创业公司相比于大厂,一个非常重要的劣势就是基础设施不完善,没有各种各样完善的工具。因此,我打算整理一下,基于开源社区提供的能力,如何用尽量小的运维和开发成本,去搭建出一套体验良好的开发流程。

首先,我们理一下整个开发流程中的必备步骤都有哪些,我大概罗列一下,包括项目的创建、开发、code review、部署测试环境、部署灰度和线上环境、查看线上监控、日志、以及排查问题等。

根据这整个流程链条,我们再来理一下为了尽可能的提升开发和运维效率,我们需要把哪些方面保障好,以及这些方面可以有什么低成本的解决方案。

有几个点吧:

  • 快速创建一个项目,并且将必备的web、监控、日志等组件添加好;
  • 提升一些常规的代码开发,比如数据库增删改查的开发效率;
  • 简化打包到部署的流程,测试环境能自动部署更好;
  • 能够给每个服务轻松的加上一整套监控和报警,包括通用的机器、jvm、接口层面的指标,和根据业务自定义的指标;
  • 能有地方集中看各个节点的日志;
  • 必要的时候,可以在线上节点上有强力的工具帮助排查问题。

接下来,我们就看一下这些问题分别怎么解决。

部署环境

首先,在本文的标题中,其实已经假定了部署环境就是kubernetes, 所以,我先说一下为什么只有kubernetes这一个选项。随便翻开一个kubernetes的介绍资料,我们都可以看到很多它的优势说明,这里我们不多赘述。而其中最关键的,也是每个人都能切身体会到的好处,其实就是它提供了完善的自动扩缩容机制。可以非常简单的设置一个cpu消耗的期望值,k8s集群就可以根据目前cpu的负载去调整pod数量。我对比了我们启用自动扩缩容前后的数据,每天的机器成本可以相差接近600美金,占总成本的1/3还要多。

由于k8s本身的运维成本是很高的,推荐直接购买云服务商的服务,无论aws还是阿里云,都提供了很完善的k8s集群功能。

另外,推荐使用 kuboard (https://kuboard.cn/) 这个工具对k8s进行管理。kuboard是一个图像化的k8s管理工具,包括部署、配置、扩容、登入pod等常见操作,都可以在图形化界面下操作,使用很方便。

项目开发

一般基于spring initializr创建一个项目就可以。对于常用的日志、监控等各类配置,建议整理一份最佳实践并做一个模板项目。新项目可以基于这个模板项目来创建,可以利用mvn archetype或者类似的工具让这个过程更加顺畅。

至于一些常用的数据库、缓存等基本代码,感觉spring全家桶已经足够好用了。

对于code review, 我们可以使用gitlab的webhook, 当代码提交时,自动给项目组的人发通知。

打包&部署

对于springboot项目,可以用buildpacks 打成docker镜像,而不用再考虑docker file的细节。具体的buildpacks产品,我们用的是paketo buildpacks, 其他的诸如cloudfoundry,heroku也都差不多,目前没有还研究这些包有什么具体需求。

接下来,我们仍然可以利用gitlab的webhook, 触发一次jenkins的构建任务。在jenkins的构建任务重,我们可以完成打包和部署到kubernetes测试集群这些操作。

监控&报警

推荐使用 prometheus + grafana 套餐即可,关于prometheus的使用,以及在springboot项目下的配置,可以参考我之前的文章 https://lichuanyang.top/posts/28288/

在k8s上,可以装一个weave cloud agent, 然后就可以配置对prometheus接口进行自动抓取了。

在grafana里,可以直接写promeql配置监控报表。 另外,grafana官网上,有大量的别人共享出来的图表,可以直接使用。

在grafana里,也可以配置各种各样自定义规则的报警。如果使用飞书的话,在飞书里配置grafana助手,可以很容易的将飞书作为grafana的报警通道。

日志

可以使用loki (https://grafana.com/oss/loki/)作为日志收集、查询的工具。loki可以认为是一个轻量级的ELK, 其维护成本会比ELK低很多。

排查问题

对于java的项目,使用神器arthas可以解决绝大多数问题排查的需求。对于arthas的接入,可以采用arthas springboot starter这种方式 。而对于k8s上的pod, 如果环境能够和办公环境打通,那可以利用k8s的port forward 功能,将arthas的端口转发到本地来。 当然,这样做的话,务必要控制好权限。

金丝雀部署

关于金丝雀部署的作用和实现方式,可以参考我的另一篇文章 https://lichuanyang.top/posts/30764/

总结一下, 对于运维来说,只需要维护一些诸如gitlab, kuboard, prometheus, grafana, loki之类的基础设施,而且基本都是一些维护较简单的工具。在此基础上,我们辅助以合理的流程和技巧,就能实现一个非常好的开发体验。

原文地址: http://lichuanyang.top/posts/40964/

kubernetes环境下做金丝雀发布的一种思路

作者 流沙
2021年12月23日 18:55

金丝雀发布其实是一种非常适合在云原生体系下进行的流程,因此相信很多人有在kubernetes下做金丝雀发布的需求。通过本文,我们就介绍一种非常简单的做金丝雀发布的思路。

首先介绍一下金丝雀发布是什么,金丝雀这个名字,起源是,矿井工人发现,金丝雀对瓦斯气体很敏感,矿工会在下井之前,先放一只金丝雀到井中,如果金丝雀不叫了,就代表瓦斯浓度高。

在系统层面的具体含义就是,发布开始后,先启动一个新版本应用,但是并不直接将流量切过来,而是测试人员对新版本进行线上测试,启动的这个新版本应用,就是我们的金丝雀。在金丝雀上测试没有问题后,再将线上的流量切换到新版本上。

再具体点讲,可以将一个域名映射到两组服务器上,一组是正式的线上环境,另一组就可以是金丝雀环境了。

假如说我们不是用k8s做部署,而是直接部署到多台服务器上的话,金丝雀部署就很简单,任意指定一台或多台机器做金丝雀即可。

而在k8s环境下,由于k8s接管了部署的流程,我们就需要做一些别的事情来实现这个效果。

有一种很简单的思路,就是创建两个deployment, 但是通过label关联到同一个service上,就可以将同一个service的流量分配到两组容器中了。两个deployment可以分别部署,也就可以部署不同版本的镜像了。

例如下边两个deployment

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
labels:
app: nginx
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.7.9
---
apiVersion: v1
kind: Service
metadata:
name: nginx-service
labels:
app: nginx
spec:
selector:
app: nginx
ports:
- name: nginx-port
protocol: TCP
port: 80
nodePort: 32600
targetPort: 80
type: NodePort

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment-canary
labels:
app: nginx
track: canary
spec:
replicas: 1
selector:
matchLabels:
app: nginx
track: canary
template:
metadata:
labels:
app: nginx
track: canary
spec:
containers:
- name: nginx
image: nginx:1.8.0

只在第一个deployment中配置service,其配置的selector规则是app:nginx, 然后两个deployment都打上app:nginx的label.

这样就实现了我们所说的效果。

当然,这种方式只是实现了一种非常简单的金丝雀发布流程,是没有办法做更精细,比如说按用户信息去灰度的规则的。对于同一个用户,也有可能一次请求到了金丝雀中,下一次请求又到了正式环境中。假如需要更加精细的灰度规则,可以考虑采用spring cloud, istio等工具。

原文地址: http://lichuanyang.top/posts/30764/

iterm2配置ssh书签, 实现记住密码和自动登录

作者 流沙
2021年10月8日 16:45

如果你像我一样,需要经常性的访问不同的远程服务器,记录服务器的ip和输入密码就是一件非常痛苦的事情。好在,通过在item2中做一些配置,可以很好的解决这个痛点。最终实现的效果,就是类似配置了一些ssh书签,能够在iterm2中记住ssh密码, 实现免密码登录和自动登录的效果。

iterm2 (https://iterm2.com/) 是mac下使用非常广泛的一款终端替代产品,提供了很多强大的功能。要实现ssh书签,实现免密码登录、自动登录的效果,关键点是其中的三个特性:profile, trigger 和 password manager.

profile顾名思义就是一套配置,像我们正常打开iterm2时,其实就是打开了default profile. 配置profile的入口就在工具栏 profiles 选项下,可以增加或编辑现有profile. 我们将需要的profile的general标签下的 commond 模块修改为 Command, 内容填入 ssh命令, 比如 ssh root@1.1.1.1, 就可以在打开profile时自动执行ssh命令。 profile中其他的文本、颜色等配置都不重要,可以按需填写。

trigger也是profile的一个特性,入口在profile配置页的advanced标签下,它的作用就是利用关键词触发一个动作,我们现在要做的就是用password这个关键词触发打开 password manager。 操作很简单,就是增加一个trigger, regular expression 填入 password, action选择 open password manager, 注意勾选instant和enabled两个选项。

最后一个要配置的是password manager, password manager 就是一个密码管理器,是item2中会默认安装的一款插件,入口在工具栏 window 标签下。打开password manager, 将需要保存的密码都录入进去就可以了。

这样,我们就实现了在iterm2中用“书签”保存远程服务器的地址和密码。使用时,直接访问对应的profile, 等待password manager 弹出,选择对应的密码记录,点击输入就可以了。

原文地址: http://lichuanyang.top/posts/20763/

怎样做一个好的技术分享

作者 流沙
2021年8月31日 14:27

技术分享基本在每个公司都会有。分享的效果因人,因团队而异。在有些团队中,分享会因为种种原因,逐渐沦为一个没什么用的时间杀手。

写这篇文章,是希望从选题等方面出发,通过一些比较基础的规则,提升技术分享的下限,争取让80%的人都能做出一次80分的技术分享。

选题

基本原则

首先要了解到的是,线下的技术分享不是分享知识的唯一形式。其他的,比如写博客写文章,拍视频,或者简单的分享一两句话,都是常见的分享形式。针对不同的知识,要考虑用什么样的方式进行分享更加合适。

比如,对于某项技术的入门介绍,推荐以文章的形式进行。读者可以进行快速的浏览或者对关键信息进行检索,要比坐下来听一遍效率高很多。

推荐选题

通常来说,比较好的选题需要有较强的实用性,能够吸引到听众,且难度适中。能够让听众在听得懂和有新收获之间达到一个平衡。

举一些比较好的选题例子,以及一些注意事项:

  • 对于新技术的介绍,需要介绍清楚新技术的产生背景,解决了什么问题,相对之前的类似解决方案有什么优缺点,引入了什么新问题,等;
  • 对晦涩难懂的技术的讲解,这类选题粒度可以小一些,需要在充分理解问题的基础上有较好的分享技巧,保证大家听的明白;
  • 比较细节的技术深入讨论,与上一类类似,要注意背景介绍;
  • 对于一类问题的高度总结,比如cache的一致性、分布式id、分布式事务等,这类话题可以从实际存在的问题切入,分析不同解决方案的优劣,以及技术方案的演进等;
  • 业务分享,这一类最好是选择常见的业务领域,同时要注意对背景的介绍;

内容准备

对于分享效果,虽然会受各方面的影响,但归根结底最重要的还是分享内容中是否有足够的干货。

为了能够有干货, 最好的方法还是平常的积累,建议平常多思考,对平常工作中用到的东西建立起更深的认知。推荐阅读《卡片笔记写作法》这本书,平时有持续输入,才能有输出产生。

此外,

准备过程中注意思考和总结,形成自己的观点。单纯对其他人的东西做搬运,实际上造成的是自己时间的浪费;

最好能将实际的应用场景融汇到分享内容中;

比较难的知识点,注意分享技巧,让听众能听明白,可以考虑先就分享的关键部分,先找一两个同事试讲;

通过看视频,除了可以了解分享内容本身,也可以了解到其他人是怎么讲的,对分享效果比较有帮助。

除了分享的真正内容外,建议增加以下内容的准备:

预习资料。可以提前准备一些有助于理解分享内容的资料,比如某本书的某几章,某篇文章等,要求大家提前阅读。分享者需要给出每个材料的预计阅读时间,单次分享整体需要的阅读时间不宜超过1个小时。

对背景的详细阐述,尤其是涉及到一些业务知识或者不常见的场景时。

分享流程

准备好分享内容后,需要找人进行review,reviewer可以是自己的mentor, leader, 或者其他在相关领域非常熟悉的人。

reviewer在充分理解分享内容的基础上,主要对选题和分享内容进行审核,确保满足本文列举的其他要求。同时尽量保证分享内容不要有技术性的错误。

分享材料一定要提前发出来,让听众有提前熟悉的机会。

分享时注意语速控制,不宜过快。

原文地址: http://lichuanyang.top/posts/54216/

户口?大厂?高薪?生活?聊聊应届程序员的职业选择

作者 流沙
2021年3月30日 18:36

今天结合我的个人经历来聊一聊应届生的职业选择问题,主要针对后端开发(对岗位的选择,话题也比较大,后边有机会的话会单独开一篇文章写),我这几年的时间里历经了国企、大公司、小公司这样不同的工作环境,所以对于不同公司的情况还是比较有发言权的。

职业选择,最重要的无非就是城市和公司选择。城市选择是一件非常主观的事情,比如我,作为一个北方人,既不喜欢南方城市的气候,又想离家近,还需要大城市的机会,北京就几乎成了唯一选择。当然,给大家的建议,功利点的话,建议选择一些快速发展中的强二线城市,比如重庆、合肥,这样随着城市平台的发展,你自己身处其中,身价也会提升。

接下来,我会限定在北京的范围内,结合我自己的经历,说一下公司的选择。

提到北京,一个不得不提的问题就是户口的问题。不过,我强烈不建议你依据一个户口就决定了整个人生怎么走,比如明明不想进体制内,却为了个户口就去了。务必在考虑户口问题之前,优先想好职业发展方向的问题。

很多人会纠结于体制内/体制外、大公司/小公司这些明面上的选择,但这些其实都只是表象而已,同样是体制内,也可能有天差地别的变化。一个正确的做法是想清楚自己真正想要的,然后再去寻找合适的工作机会。对于什么是自己想要的,确实不是每个人都能想的清楚。所以,我把这个问题拆解一下,看看工作的选择究竟意味着什么。大家可以思考以下几个问题:

  • 你最希望从工作中获得什么?钱?权力?还是内心的成就感?或是并不想从工作中获得任何东西?
  • 你希望工作在你的人生中占据多大的比例
  • 对于工作中获取的资源(人脉、信息等),和通过工作提升的能力,那一项你更有信心能带到其他的环境里
  • 你喜欢做常规的事务性工作,还是希望持续的去挑战困难问题

对于上边的任何一个问题,任何回答都没有高下之分,比如挑战困难问题并不比做常规工作更优越,这个只是大家根据个人价值观做出的选择而已。

这些选择的任意排列组合,相信都可以找到合适的机会。比如不期待从工作中有所得,不想工作占据太多人生,也愿意做常规的工作,那就可以考虑一些边缘的体制内单位;期望有权力,可以让工作占据很多生活时间,更想通过工作积累资源,就可以考虑核心的、有实权的体制内单位;而如果期望挣钱,更希望通过工作提升个人能力,程序员就是个不错的选择。

想好了上边的问题,就可以选择公司了。很多时候,我们可能无法找到并顺利的进入一个满足所有要求的地方。这也没关系,我们可以利用跳槽,每次解决其中一部分问题,并且持续的搜集信息和提升能力,寻找到合适的机会,并有能力获得这个机会。

我以自己的经历讲一下我的几个关键选择是怎么考虑的。

刚毕业的时候,我的期望是获得一个北京户口,并且能留在互联网行业内。那个时候我的选择有两个大方向,一是去互联网公司竞争ssp offer, 但是当时的能力确实不足,所以这条路很难走的通;另一条路就是在体制内寻找业务接近互联网场景的机会,最好是面向普通用户的C端产品,因为这样可以有充足的用户量和数据量,让我不至于离业内先进的技术太远。很幸运,我找到了这样的机会,也成功的抓住了。入职之后,也基本如我所料,虽然公司技术水平并不高,企业文化也有很多问题,但是业务场景是非常好的,我也有充足的自己发挥的机会,借助工作,也学到了很多东西。

接下来,我要解决的问题就是大厂经历,所以接下来跳槽的时候就只考虑了几个一线大厂。关于工作本身,倒没什么好说的,这段经历虽然过的很不开心,但是希望达到的目标也达到了,所以也没什么遗憾的。

再下一次跳槽,我期望解决的问题有三个:大幅提高收入,抹平因为在国企呆的时间比较长造成的和同龄人的收入差距;别加班太多;人际关系轻松一些。这三个问题看似矛盾是不是?但是用心去找总是能找到合适的机会的,我也就这样来到了现在的公司。当然不是所有问题都解决的非常好,但是已经足够让我的工作体验非常幸福了。

通过我自己的经历,其实大家也可以看到,我总是似乎在提互相矛盾的要求,总是想鱼与熊掌兼得,而且最后也能幸运的都得到。其实这世界上公司那么多,并不是说有哪两个条件是必然互相矛盾的。大家没有必要过早的就舍弃什么,比如做程序员就一定意味着996,意味着放弃生活吗?显然不是。

上边可以衍生出两个比较细的问题,我也一起说下吧。

一是要不要选择北京户口。我先说一下我通过这个户口获得了什么吧,首先是赶上了15,16年那波房价大涨,让家庭资产在我并没有什么理财观念的时候,也获得了大幅增值。这一点我觉得放在今天,已经不成立了,房住不炒的大前提下,就算北京的房子,也不可能跑的过股市。第二点,就是一些手续办的方便一点,这个我觉得基本可以忽略不计吧,一年估计都办不了一次事,而且越来越多的手续也支持异地办理了。第三点,就是可以放心的让孩子在北京接受教育,不用担忧未来的不确定性。为什么我说是不确定性,因为我觉得接近二十年之后,非京的孩子是否依旧不能在北京高考,是有很大的不确定性的。北京的高考难度会卷成什么样,也是有很大的不确定性的。北京户口,只能说让我个人未来的选择固定了下来,但这个选择未来会是什么效果,其实很难说的准。

所以说,当前来看,北京户口的价值其实非常有限。回到2015年的话,我仍然会毫不犹豫的选择要北京户口。但是如果让我在2021年的今天做这个选择,其实我大概率是不要的。

第二点,是关于大厂经历。这一点,我的看法是必须要有,而且尽早有,但是没必要一辈子都呆在大公司里。大厂经历的价值,其实很多人已经说的很清楚了,所以我也不想再赘述。感兴趣的话可以私下找我沟通。

上边的篇幅里,主要说的是方向的选择。选定了方向之后,还有个重要的问题是具体公司的选择。面对一个公司,其实考虑两个问题就可以:

  • 我通过这个工作能得到什么
  • 我因为这个工作会失去什么,将来有什么办法可以弥补

以我的第一份工作来说,获得的就是户口和互联网的业务场景。同时会失去大公司的学习资源、优良的公司氛围、高薪水等等,这些问题都需要自己想办法去解决。

好了,上文已经讲完了本文的大部分内容。有个点,我一直没有提,就是中年危机的问题,可能会有人问。我为什么不提呢,因为我不觉得这是一个能靠选择解决的问题。有些人会觉得体制内很稳定,即使天天混日子也没人能把你怎么着。但是我不觉得这种依赖外部条件的事情能算稳定,历史上体制内不是没出过问题。走这条路或许你有99.99%的机会一辈子过的安安稳稳,但是如果那万分之一的可能性发生了,你打算怎么办呢。而做程序员就不一样,或许只有50%的机会能平稳的呆在一家公司,但是随着工作提升的能力,让我可以很安心的应对另一半可能性。解决中年危机的唯一方式就是提升自己,不管是提升能力还是积累资源,你总要从工作中有所得,才能持续的保持自己的竞争力。

我是流沙,大家可以通过公众号( Mobility ), 个人网站 等和我交流。

原文地址: http://lichuanyang.top/posts/34931/

我提升开发效率的经验

作者 流沙
2021年2月1日 17:38

大家好,我是流沙,一名近六年工作经验的程序员。我的工作经历里,包含了整体文化、氛围差别非常大的公司,和很多不同类型的人合作过,观察到过很多没效率的现象。同时,我一直觉得我自己的开发效率是非常高的。整个职业经历里,我很少在八小时的工作时间外处理任务性的事项。即使有时迫于公司制度不得不进行加班,也是在执行自己的学习计划,或者进行一些深层的思考。因此,我想做一个系统性的关于开发效率的总结,分享给大家。

首先,列两个非常没效率的场景。

  • 小A开发过程中碰到一个没见过的报错,于是抱着电脑去找团队里的大牛小X, 小X努力的搜集相关的各种必要信息。与此同时,小A就在旁边看着,并且不时的和小X还有旁边的小Y聊着天。
  • 小C拿到一个大型需求,匆匆看了一遍,接着就开始开发。开发一半了,发现产品稿上有个点看不明白,又去和产品问。问的过程中产品又想起来别的事,又说了起来。最后聊了半天,小C发现自己之前的工作基本白做了。

虽然有夸张的成分,但是这些事也是确实每时每刻都在发生的。造成这些现象的原因很多,我们今天就好好剖析一下。在剖析效率问题之前,我们需要先看看程序员日常都要做哪些事。其中对工作效率影响比较大的,我把他们主要归为三类:

  1. 日常开发
  2. 沟通
  3. 排查和解决问题

接下来,我们就对这三项逐一分析,如何提升工作效率。

日常开发

日常开发中都有什么情况会造成效率下降呢,我列举几个:

  • 像上边小C一样的返工行为
  • 日常工作中大量重复劳动,比如写sql,写模板代码
  • 本来是一个很小的改动点,改了才发现这也要改,那也要改;更严重的是,根本不知道「那也要改」,结果一改就崩
  • 个人注意力不集中,老走神

可以看到,这里边大部分是和代码设计有关的。对于设计,其实就两句话,一是要设计,二是要设计好。

也就是说,首先,你得知道开发之前是得有设计这么个环节的。软件工程是一门科学,它有概要设计、详细设计这些步骤是有道理的。要设计到什么程度再进开发呢?我的观点是越细越好,理论上,除了实际把代码码出来,其他所有事都可以在设计阶段做完。

关于设计的好不好,一个重要的评价标准,就是后续改起来是否容易。如果一个变化的原因会导致多处代码修改,那就是一个不好的设计。如何提升这一点,需要去学习设计模式、架构方面的知识,没有速成之路。

另外,关于注意力的问题,当然大家可以借助番茄钟这样的工具。但是工具永远都只能是辅助,最重要的还是自己去提升主观上的自控力。

沟通

接着说沟通这块,沟通分主动沟通和被动沟通。主动沟通就是你自己这边发起,要去找其他人问问题、讨论;被动沟通则是别人发起,来问你问题或者讨论事。

主动沟通的主要问题就是越聊越远,聊着聊着就不知道聊哪去了。这里我给两个建议,一是发起沟通前一定要想清楚自己想通过这次沟通达到什么目的,不能有个模糊的想法就迷迷糊糊的去了,这种沟通就是最大的时间杀手;二是可以列一个沟通清单,沟通过程中话题扩散其实是个常见的现象,我觉得没必要刻意去杜绝这种事,关键是要保证自己本来想聊的聊出结果来,这就需要一个可以用来check的清单。

被动沟通其实是一种很容易打乱工作节奏的事,但是很多情况下无法避免,比如突然又线上问题要处理,领导有事找你,等等。我们能做的就只有减少不必要的被动沟通次数。比如自己提供的功能,写好文档和注释,其他人就不需要靠问你就能看明白。方法名、返回值等团队内要建立起一些固有约定,大家都遵守,也就不需要问才能了解了。

另外还有一点是做好任务的拆分,做若干个小任务,而不是做一个大任务。这样,当被迫打断工作时,「切换上下文」的成本会低一些。

排查和解决问题

这也是很重要的一个方面。对有些人来说,查Bug改Bug的时间,可能会远高于开发新需求。对于这个,我们的主要目标就是降低排查难度,减少排查时间。可以从下面几个方面做。

  1. 设法尽早暴露问题,比如通过写单元测试。同样一个问题,出现在一个小模块里,和出现在一个复杂系统中,排查难度是不言而喻的。

  2. 要有足够多的信息,比如日志,比如各类监控数据。就像我们体检一样,只有先看得到那些异常指标,才能再去针对性的详细排查。否则什么指标没有,就只能两眼抓瞎乱查了。

  3. 再一个就是排查方式,每个人都需要逐渐积累经验,建立起相对固定的排查问题的套路。比如我,习惯就是遇到问题先把各种能看到的信息都扫一遍,从中发现异常指标,指标看不懂的话就借助搜索引擎或者他人来理解,然后根据指标思考有什么可能原因,再逐步去验证。这里边很重要的一点就是什么样的问题要自己思考,什么样的问题应该借助于他人或者搜索引擎。像上边小A的做法,就是非常不可取的,问别人或者搜索引擎的问题,一定是非常具体的,你随手一个报错发过来,别人首先得花大量时间搜集信息。一个1小时搞得定的问题,强行去问别人,很可能要花掉两个人一下午。

    什么叫具体呢,比如遇到一个ClassNotFoundException, 如果你压根不知道它是什么意思,那去搜一下没有问题。而假如你已经知道它的意思就是找不到类,那再去搜索就没有任何意义,这是要做的是思考这个类为什么找不到,有哪些可能原因。

除了上边这些之外,其实还有一类原因,就是996大环境大家主动选择的摸鱼。对这一点,我还是建议大家快速干完活之后腾出时间提升自己,摸鱼无益。再一点就是用脚投票,多去找没有加班文化的公司、团队,用自己的努力让他们发展的更好。

大家有想法也可以在评论区或者公众号( Mobility )和我交流。

原文地址: http://lichuanyang.top/posts/3423/

❌
❌