Fork me on GitHub

Non-client-server mesh sidecar

网络服务一般分为client和server端, server端负责监听一个地址, 等待client的连接

在他们中间如果需要做七层的流量治理与转发, 通常会引入一个像下图的proxy(以envoy为例)

proxy进程内包含两部分: Listeners负责监听来自client的连接, Upstream Clusters负责主动dial server, 并将数据转发给server

这样的模式很容易拓展到多级的proxy

但我司的Service mesh产品却是不一样的做法: 无论是client应用还是server应用, 都统一去dial proxy

Listners:

  • App Listener: 负责监听来自app(client/server)的连接
  • Proxy Listner: 负责监听来自client端proxy的连接

Clusters:

  • Proxy Clusters: 负责将来自client app的数据转发到server proxy
  • Upstream Cluseters: 负责将来自client proxy的数据转发到server app, 其中server app是通过app listener注册到Upstream Clusters上来的

这样有几个显而易见的坏处

  • 代码结构冗余, 需要存在两套不一样的Listener和upstream逻辑, 给代码理解和日常维护带来了一定的障碍
  • 多级的拓展变得非常费劲

但其实也仔细挖掘也是有好处的

  • app视角里模糊了client/server的界限, 可以用同一个conn执行client和server的操作
  • server连接是app主动连接注册到proxy, 所以proxy就不需要再执行类似连接预热的操作了
  • mesh产品覆盖率直线上升, 传染法则 (LOL)

在工作中保持临在

我记得差不多是一年前和老板有一次对话,那时候公司在剧烈动荡,我说我要“不管公司整体舆论也好氛围也好,努力去专注于自己眼前的事”,但显然那个时候的我并没有怎么体会过临在,所以这句话也不过是某种虚伪的讨好。

对于我而言在工作中保持临在很难,这是多年学生生涯一次次的态度选择积累的惯性思维:本能地去嫉妒能力强的和知道的多的,对自己的无知和无能会感到极度的恐慌,会急功近利地厌恶细节和琐碎的事情。

但今天我意识到我好好地做了件“琐碎”的事:我花了一天的一大半时间去支持了一个稍微边缘的团队一个极度落后服务版本的需求,这需要我去翻读两年前的代码,梳理结构和权限,也要求助之前有过相关经验的同事。不知不觉就把这件事情支持好,并且还花时间去做了梳理和总结,把核心内容总结给同事们。

当然是会收到同事们的小鼓励,但我很快回到当下。这确实也不是很大的事情,前面的支持和后面的总结分享都不过是单纯觉得“就这样去做这件事本身,而且会对他人有帮助”,并没有想着做的过程会多费劲、做完以后能有什么具体的反馈和影响。

临在是需要可以练习的,这是我这一两个月来学习到的并在践行的事情,而且这股力量它不是间歇的,同时也不是很强大,但是是在持续地在每个当下发挥着作用。

践行幸福的哲学

近期其实一直都很想好好写一下,今晚又在亲近的人身上感受到了幸福的力量和勇气

摊开我大学四年甚至更之前的日记本,写的满满的都是“挣扎” —— 我想要获得幸福,想要通过这样那样的方法改变我眼前的问题来找到答案或者平衡,却不断地被失败和焦虑袭击而放弃。这样的状态反应到了生活的方方面面上,当然也反应到了博客的经营上。

于是我时常在挣扎中感到绝望:人是不是没有改变自己的能力。我当然不会允许自己沉浸在这样的思维里,于是会有许多自我安慰甚至是自我欺骗式的鼓励,内心的焦虑却在一次次的挫败中不断地放大。

那么人到底有没有能力改变自己,并最终获得幸福,我现在开始从心底里相信是有的。但这种改变和相信和以往的认知并不一样。

幸福不是一个状态,而是一个需要勇气和毅力去践行的哲学。《被讨厌的勇气》里说的是,“任何人都能随时获得幸福”,我开始慢慢理解这句话的含义:世界其实并没有美丑,世界只不过是世界罢了,那么认定自己幸福与否的,从来就只是自己。

大家肉体最终都会消散(意识会不会我不知道,但我还是有些希望不会哈哈),有的也只是当下。但要是沉浸在“我本应该”、“世界对我不公”、“要是这样就好了”之中,那就离开了当下,也就离开了幸福。所以无论旅途最终会去到哪里,最重要的永远都是当下,此时此刻。

随着一部分自己的觉醒,开始意识到身边被痛苦之身包围着的人们,同时也意识到自己被很大的痛苦之身包围着;甚至很有可能是因为自己的痛苦之身比一般人大得多,在倏然观察到它的时候才会如此惊讶。由它衍生出的受害者心理、焦虑、敌对感便得到了解释。

践行幸福的哲学其实最早可以追溯到大三时的生日朋友圈,那可能是最早记录下来的自我接纳。旅程中有许许多多的反复,但是正如《新世界 灵性的觉醒》里说的:“觉醒一旦发生,就不会停下”。于是便更有了前进下去的勇气。

Leetcode 312. Burst Balloons

第一次做动态规划的题目

题目

Given n balloons, indexed from 0 to n-1. Each balloon is painted with a number on it represented by array nums. You are asked to burst all the balloons. If the you burst balloon i you will get nums[left] * nums[i] * nums[right] coins. Here left and right are adjacent indices of i. After the burst, the left and right then becomes adjacent.

Find the maximum coins you can collect by bursting the balloons wisely.

Note:

  • You may imagine nums[-1] = nums[n] = 1. They are not real therefore you can not burst them.
  • 0 ≤ n ≤ 500, 0 ≤ nums[i] ≤ 100

Example:

1
2
3
4
Input: [3,1,5,8]
Output: 167
Explanation: nums = [3,1,5,8] --> [3,5,8] --> [3,8] --> [8] --> []
coins = 3*1*5 + 3*5*8 + 1*3*8 + 1*8*1 = 167

题目解析与分析

题意

大概就是:有一排气球,每个标了数字,然后戳一个气球得到的奖励是左气球×该气球×右气球,当然如果左或右没有气球就乘1。问该以什么顺序戳气球得到的奖励最大

最笨的思路

枚举法:将所有的情况列举一遍。当有n个气球的时候,第一步我们有n种选择,第二步我们又有n-1个选择……显然全枚举的算法复杂度为$O(N!)$,效率不敢恭维,因此这里就不实现——因为不可能过。

参阅

进一步想法

我们需要去考虑上面枚举法做了什么重复的计算。我们可以想到,给定一组气球,它所能获得的最大的奖励应该和前面已经被戳的气球无关——被戳过的气球只是在求和的时候累积上了而已。

对于给定k<n, 其可能的组合数有 $C_n^k$ 种,我们可以把k(从1开始)的所有情况都记录在内存上,k+1就可以基于k进行计算,那么我们总共需要进行的计算就是
$$C_n^1+C_n^2+…+C_n^n$$
这种算法优于$O(N!)$, 但仍然坏于$O(2^N)$; 我们需要更优的算法

分治的想法

我们考虑用分治去思考这一道题。先是正常地考虑分治,我戳爆某个气球,可不可以把剩下的气球分成两堆呢?这是否可行的前提是两堆会不会互相干扰:答案是肯定的,在戳爆某个气球以后,左堆的最右气球会需要右堆的最左气球来进行计算。

这时候我们需要反向的思维:我们正向地想戳气球的过程,当然会导致两堆相互影响。那如果我们考虑的是在这一堆里最后戳爆的那个气球呢?假如A,B,C,D,E中我最后戳C, 那左堆(A,B)在戳的过程中显然会以C为右边界,而右堆(D,E)以C为左边界——这就实现了分治,两个子问题是相互不干扰的。

想一下为什么分治会更好,它少算了哪些步骤

定义一个函数 burst(left, right),它的值表示以left right分别为左右边界的时候 [left,right]所能得到的最大的奖励值。这里需要说明的是这里定义的左右边界都需要晚于中间的任意气球被戳,那样在剩下的气球(i)里任意选取一个作为倒数第三(边界是倒数一、二,顺序不重要,只要知道它会比中间的数晚戳就行)去戳,那它又能成为左右两个新区间的新边界,则这样戳所能得到的最大值是

1
nums[left]*nums[i]*nums[right] + burst(left,i) + burst(i,right)

这样我们只要遍历每个 i in (left,right) 去求出上述等式并得到最大值,那就得到了 burst(left, right) 的最大值

还有一些细节:

  • 假设 nums[-1]=nums[n]=1,这样两个假边界就显然是整组气球里“最晚被戳的气球”了
  • 当两个边界相邻,说明中间无球可戳,所以应该有burst(i,i+1)=0

按照上面说的就可以实现如下具体的算法了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public int maxCoins(int[] iNums) {
int[] nums = new int[iNums.length + 2];
int n = 1;
for (int x : iNums) if (x > 0) nums[n++] = x;
nums[0] = nums[n++] = 1;


int[][] memo = new int[n][n];
return burst(memo, nums, 0, n - 1);
}

public int burst(int[][] memo, int[] nums, int left, int right) {
if (left + 1 == right) return 0;
if (memo[left][right] > 0) return memo[left][right];
int ans = 0;
for (int i = left + 1; i < right; ++i)
ans = Math.max(ans, nums[left] * nums[i] * nums[right]
+ burst(memo, nums, left, i) + burst(memo, nums, i, right));
memo[left][right] = ans;
return ans;
}
// 12 ms

ad钙奶和房子

现在对贩卖焦虑的行为已经越来越敏感了。

上班路上看到了一个这样的广告:一个小男孩一身汗地跑进家里向妈妈哭诉,“妈!我又被班里的高个子男生撞倒了”,妈妈一脸慈爱地拿出了一罐ad钙奶……
我在想这种情况的真实版会是怎么样的?小孩的哭诉一直在发生,妈妈真实的话可能会是:“平时让你多喝牛奶你不喝,现在被撞了才知道在这里哭!”看完广告的区别,可能是把牛奶换成两倍的ad钙奶。

其实我也在贩卖焦虑。最近一年来我们家发生了一件我不愿意发生的大事:在我妈的极力主张下,非常勉强地、左拼右凑地在南沙区买了一套小房子,比较鸡肋、没有什么上升空间,还让降低了爸妈的生活水平。

所以最近沟通免不了和我妈沟通(吵架)。昨天出门前又聊到了这件事,起因是她现在不舍得打车了,因为还欠着钱想要先紧巴紧巴先还上。我想告诉她的是,小钱省不了多少,我们是应该关注和节省的是那些冲动下花出去的大钱。

可想而知又是吵一顿,但随着房价真的在下跌,经济情况如当时预料的那样在变坏,我妈吵架的底气越来越弱。从结果来看我好像“赢了”,但我并不怎么开心,因为我妈开始说她后悔了,她也开始想这个决定是不是太冲动了,现在压力很大,然后想到我说的一些话又觉得很绝情,压力就更大了。

我突然有点惊醒了,我说的话也是在贩卖焦虑;资本家贩卖焦虑能赚钱,我贩卖焦虑赚的是一种延迟复仇的快感;我相信两者最终都没法指向内心的平和与愉悦。

对网络编程的思考

加入Spex接近两年,但是其实到今年七月份开始才真正做agent相关的工作,并且也是到最近才算是半只脚迈进对agent进行性能分析的部分;在做这一部分工作的时候,除了有“这一部分真有意思”的激动,同时也觉察出自己在网络编程方面知识和经验的欠缺,阻碍了对程序的优化 —— 而且这样的现象在组里其实比较普遍。

Spex agent的代码基本是一个proxy server(IO->逻辑处理->IO,既接收连接又对外创建连接), 服务端部分采用的是类似经典的reactor的事件处理模型。值得一提的是逻辑处理环节利用了Go的channel特性划分了几个阶段,这也给性能和代码逻辑分析带来了一定的挑战。

萌生对网络编程进行更系统化学习的起因是最近一次失败的性能优化尝试。在现有Spex agent的代码中,客户端部分(即作为client向外创建连接并交换数据的部分,这包括client agent向server agent建立TCP连接 & server agent向server instance建立UDS连接的部分)中是每个goroutine用一个for loop处理一个连接的序列化和I/O,我怀疑这部分可能会造成队头阻塞,就用goroutine pool来调用序列化和I/O。结果在和QA的简单验证下,发现延时P99并无法得到明显的改善,而与此同时系统的吞吐量下降了。

但并不是说这一个思路不正确就说明这个尝试失败了,而是在验证的过程中其实很大程度是不严谨的 —— 使用的是最基础的性能测试场景以及凭借自己猜测增加了请求包大小的测例,并没有尝试构建复现队头阻塞的场景,对于应该关注哪些指标,怎么看系统是否达到了瓶颈也有些无头苍蝇的感觉……当然这些问题其实都可以逐一地去研究并解决,但就是如果有更系统化的知识体系,这些问题本来也应该是很常见思路比较明确的问题,没有必要一个劲地瞎闯,还是需要更系统地学习网络编程。

之前也看了一些书,比如《UNP》、《Linux高性能服务器编程》,同时也有对Go HTTP的源码进行学习。但之前的学习没有领悟到一个比较重要的原则:抓大放小 —— 很容易就陷入到某一细分领域的某一个细节,但这往往在进行系统化学习的时候都是不重要且会分散注意力的。因此这些书的学习基本都虎头蛇尾,对于关键内容也没有清晰的认识和总结。

这次我选的核心阅读书目是陈硕的《Linux多线程服务器编程》,也是在许久之后把C++重新捡回来,为后面envoy相关的项目打一下基础。既然遵循抓大放小的原则,就对一些觉得是重点的内容深入代码和示例去看去运行,但不要去纠结小的点。同时CSAPP也是要继续学习,但这属于长时间的打地基工作,没必要说一定把CSAPP学完了自底向上都明白了再去接触和实际工程更接近的内容。

乡土中国-从欲望到需要

这一章主脉络是划分了欲望和需要的区别,进而描述了从乡土社会的基于欲望行事演变到现代社会的基于需要行事的过程。

单纯的欲望指导行为符合这样的流程:欲望–紧张–行动–满足–愉快。这一种满足欲望的能力是天生的,最重要的一点就是人都需要生存,因此会去吃甜食。除了原始的欲望以外,还有基于经验和教化的欲望,我这里称之为“文化欲望”。费先生把欲望全然归结于文化,我觉得是有偏颇的,更何况两者本身就是相辅相成的存在。

在乡土社会中,原始欲望和文化欲望大部分正好符合社会的良性发展,比如人饿了就吃东西,虽然只是考虑了自己的原始的欲望,但实际上整个族群也因为它没有饿死而得到更多的劳力;夫妻间的性行为可以说是爱和性冲动所结合的行为,但从社会的角度也是有益于族群的繁衍。当然夫妻之事经发展也成为了一种文化欲望:“传宗接代”仍是老一辈人根深蒂固的原则。

关于文化欲望对社会的正面作用作者还举了一个比较好的例子——巫术。从现代科学的角度,巫术对于驱散鬼怪(无论是否真实存在)都没有什么大的意义,但巫术也切实地消减了人们对于未知的恐惧,心安对于社会继续稳定发展是特别有益的,毕竟恐慌蔓延下没有人会有心思关心生产。

但为什么说这些欲望是“大部分”符合社会发展,而不是直接断言全部呢?因为实际上稳定的乡土社会也在以一个缓慢的速率改变着的,原来符合发展的欲望并不能直接切除,也是身处社会中的人不断试错而形成新的欲望来满足社会的需要。

但现代社会发展的速度远非乡土社会可比,单靠欲望行事是靠不住的,正如亚当·斯密所描述的“冥冥中的手”也是具有局限性的。现代社会的欲望逐渐进化为需要,需要是自觉的,是对行为和目的背后的理解。譬如现在的人吃东西不单单是关注食物好吃(当然对好吃食物的追随这一原始欲望仍然很重要),而是开始关注这食物含了多少卡路里,有什么维生素,够不够蛋白质等等。

因而在现代社会中,谁更能对行为和目的有更深刻的理解,谁就能有更多的时势权力来指导身边、社区乃至整个社会的人。这也就是我们通常所说的“知识就是力量”,这句话虽然俗,但蕴含了这么一部分的道理。

乡土中国-名实分离

晨读day two

这章有点标题党的感觉,虽然名字叫“名实分离”,但其实关于这一部分在最后才有所提及,也是前文论述的部分延伸结果。名实分离实际是变动社会中人们对长老权力的一种适应,在权力未惠及自己(还不是长老)时选择“阳奉阴违”的方式,而后在自己也成为长老以后以注释的方式为权力加入新的解释内容(注释),满足变动社会中维护长老社会的需求。

来进一步总结一下社会的几种权力的形式。首先就是上文提到的长老权力,这是稳定的乡村社会中具备的一种权力,父命子不可违。其次是横暴权力,是独裁和专制,其基础是剥削。随后是同意权力,发生在契约建立的基础上,辅以对权力的监督;最后是变动社会中特有的权力,可以说是长老权力的反面,时势权力。这里其实是化用了“时势造英雄”这一句,因为这种权力突出的就是英雄这个符号。由英雄带领大家走出社会变更时期的惶恐和迷茫。

这一章实际上针对时势权力影射了当时的政治内容,但并没有举出时下的例子,也没有拿乡村社会的变更作为示例。这一点其实让我有些不满意,有点“没有值回票价”的感觉。倒不是我想实际地看政治例子,这方面可以自己补充,我想了解的是乡村是怎么样受时势权力影响和变更的。当然这里我自己也有记忆储备,便是前段时间看的《平凡的世界》,里面的孙少安就可以算是这样的一个英雄。

横暴权力压制反对,同意权力实际上建立在反对(监督)的基础上,长老社会虽也不同意反对但有独特的改进方法。而对于时势权力,反对也当然是存在的,因为文化英雄的观点受其局限性总会有失偏颇或不公的地方,再者说,一个迷茫的时代肯定不止一个英雄,社会不会缺少自信的人。这时候反对的声音引起的结果就是拉阵营、搞宣传、打嘴仗,从而争取人民的跟随和信任。所以实际上时势权力也是无法容忍反对的,反对者不是同一个阵营。因此在我理解上,时势权力也是需要逐渐被同意权力过渡的,这也是因为人民无法容忍一直迷茫,总要向平稳发展过渡。

另外讲到社会结构的变动时候,作者提到了一个观点(他不同意),社会结构也类似于有机体,也有生长、发展和衰老的阶段。他认为社会结构就是人用于满足生活需要的工具,当社会结构不满足人的需要时,就会发生社会结构的变更,这一动作是人主动去做的。实际我认为两种说法都有合理性,社会结构确实是由人发明和更改的,但根据论文 More Is Different 的说法,更宏观的内容实际上已经不能够用微观的调研方法去研究,正如小球的自由落体不能用分子动力学来研究一样,社会结构本身应该以高于个体的宏观研究方法去研究。

乡土中国-血缘与地缘

很久没有好好看书做笔记了

血缘与地缘的联系

乡土社会稳定的社会结构是以血缘为基础的,以子继父业的形式形成稳定的社会分工,也就是所谓“农人之子恒为农,商人之子恒为商”。血缘以不由分说、无可争辩地形式分配传统社会中各人的职业、身份和财产。

在稳定的乡土社会中,地缘不过是血缘的投影,地域的远近一定程度反映着血缘的亲疏,因此地理区位也就成为了社会化了的空间。我们现在仍然可以见到这样对区位的划分:左尊于右,南尊于北;更明显的例子是,我们现在仍然用“地位”一词表示一个人的社会阶层,这便是来自血缘的地域投影。在原始社会中血缘和地域是合一的,而现代社会仍然延续了部分的传统。

但人,特别是现代人,终究是要流动的。传统社会的人口流动主要源自于繁殖引起的土地资源不足,首先会引起向内的精耕以提升土地的单位报酬,但随着人口的继续增加,社群便会分裂,形成新的村落,寻找新的耕地。而现代人流动的理由就更多了,就业、教育,亦或是单纯对某一地的喜爱,这在发达的现代社会已成为可能。

但人口在流动时,仍然会以另外的形式保有血缘的空间投影。比如移民社会中的“新英伦新约克New York”云云。再者还有我们的籍贯,即使我们一生都可能不会去往我们籍贯所在的地方,但我们还是会在介绍自己以及填写资料的时候诚实地写上。但事实上,现在随着社会的进一步变迁、城市化的发展,籍贯的概念可能会越来越模糊并最终消失。但在它消失之前,我们仍可以说,籍贯是血缘的一种表现形式。

血缘关系的局限和新地域概念

血缘关系对社会活动有许多限制,最大的体现莫过于商业活动。亲戚间的交易往往是基于人情和相互拖欠,也就是俗话说的“亲戚间不用计较那么细”。反而明算账意味着人情的减弱甚至有点绝交的意味。这样一来,一宗“人情交易”可以被长期甚至永久地搁置,因此对于不亲的关系人们反而会减少这样的往来,避免太多的人情债,无论是别人欠自己的还是自己欠别人的。

随着社会生活的逐渐发达,牵扯的社会活动越来越多,就越来越不能靠人情来维系权利和义务的平衡,这时候就需要一定程度的“当场清算”,也就是商业。但这种形式是有悖于传统的亲密血缘关系的,因此就衍生出集市的概念,乡村社会划分出一个特定的地点让大家都到这里来,暂时撇开社会联系,进行纯粹的商业交易与结算,不用讲人情。

因为有集市的存在,“外村人”也可以参与本村的商业活动并生活于此,这是由商业衍生出的新的地缘概念,有别于传统的附加在血缘上的地缘关系,是契约关系的基础,也是现代社会的基础。从学院结合转变到地缘结合是社会性质的转变,也是社会史上的大转变。

有不少概念也是在自己的理解上添油加醋,也不保证能准确传达费孝通先生原文的意思,比如关于籍贯、新地缘关系的产生部分。

LeetCode 42. Trapping Rain Water

42. Trapping Rain Water

Given n non-negative integers representing an elevation map where the width of each bar is 1, compute how much water it is able to trap after raining.

Alt text

The above elevation map is represented by array [0,1,0,2,1,0,1,3,2,1,2,1]. In this case, 6 units of rain water (blue section) are being trapped. Thanks Marcos for contributing this image!

Example:

1
2
Input: [0,1,0,2,1,0,1,3,2,1,2,1]
Output: 6

我的解法

一开始我有一个错误的解法,这个解法是通过有穷自动机分析出来的;虽然这这种解法最终无法实现,但我发现有穷自动机真的是个逻辑分析的好工具,也算增长了经验

  • 先遍历一遍找出最大值,并假设一开始每一列都有着max高度的水;这个是个 O(N)的操作;
  • 而后从上至下,逐行地减去头和尾溢出的部分,这是个kO(N)的操作,k取决于最大的高度能有多大
  • 总结来说这是一个O(kN)的操作
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
38
39
40
41
42
43
44
45
46
47
48
49
func trap(height []int) int {

if len(height) == 0{
return 0;
}

// fill with the max;
max := height[0]
for _, val := range height {
if max < val {
max = val
}
}
result := max * len(height)

// delete the first 0 cols
sp := 0
for ; sp < len(height) && height[sp] == 0; sp++ {
result -= max;
}

// delete the existing places
for e:=sp; e<len(height); e++{
result -= height[e];
}

// delete row by row
for curH := max; curH >= 1; curH-- {

// delete the head
for e := sp;; e++ {
if height[e] < curH {
result--;
} else{
break;
}
}
// delete the tail
for e := len(height)-1;;e--{
if height[e] < curH{
result--;
} else{
break;
}
}
}
return result;
}

更巧妙的

讨论区里利用双指针,可以有 O(N)(只遍历一遍)的算法。具体看这里,这里不说详细的解析,简单来说就是左右同步扫描,并将水从低处往高处逐列填。这里只贴一下代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Solution {
public:
int trap(int A[], int n) {
int left=0; int right=n-1;
int res=0;
int maxleft=0, maxright=0;
while(left<=right){
if(A[left]<=A[right]){
if(A[left]>=maxleft) maxleft=A[left];
else res+=maxleft-A[left];
left++;
}
else{
if(A[right]>=maxright) maxright= A[right];
else res+=maxright-A[right];
right--;
}
}
return res;
}
};

更多的解法

Solution有更多的解法,还有一种动态规划的解法很有意思,将左边加起来,右边加起来,重叠部分就是所求。

  • Copyrights © 2015-2024 Jeff
  • Visitors: | Views:

请我喝杯咖啡吧~

支付宝
微信