码农戏码

新生代农民工的自我修养

0%

重读经典–《程序员修炼之道》

是什么造就了务实的程序员?

早期的采纳者/快速的适配者

你对技术和技巧有一种直觉,喜欢尝试。当接触到新东西时,你可以很快地掌握它们,并把它们与其他的知识结合起来。你的信心来自经验。

好奇

你倾向于问问题。这真不错–你怎么做到的?你对那个库有意见吗?总在听人说起的量子计算到底是什么?符号链接是怎么实现的?

你热衷于收集各种细微的事实,坚信它们会影响自己多年后的决策。

批判性的思考者

你在没有得到证实前很少接受既定的现实。当同事们说“因为就该怎么做”,或者供应商承诺会解决所有的问题时,你会闻到挑战的味道。

现实主义

你试图理解所面临的每个问题的本质。这种现实主义让你对事情有多困难、需要彡多长时间有一个很好的感知。一个过程应该很难,或是需要点时间才能完成,对这些深刻理解,给了你坚持下去的毅力。

多面手

你努力熟悉各种技术和环境,并努力跟上新的进展。虽然目前的工作可能要求你在某个专门领域成为行家,但你总是能够进入新的领域,迎接新的挑战。

重读经典–《程序员修炼之道》

大多数开发人员对设计正交系统的必要性都很熟悉。只不过他们可能会使用其他一些词来描述这个过程,例如模块化、基于组件和分层,等等。

正交性是什么

“正交性”是从几何学中借用来的术语。

若两条直线相交后构成直角,它们就是正交的。

例如,图表中的坐标轴就是正交的。对于向量而言,这两条线相互独立。图示中的向量1指向北,完全不影响东西朝向。向量2指向东,完全不影响南北朝向。

在计算科学中,这个术语象征着独立性或解耦性

对于两个或多个事务,其中一个的改变不影响其他任何一个,则这些事物是正交的。

在良好设计的系统中,数据库相关代码应该和用户界面保持正交:你可以变更界面但不应影响数据库,切换数据库而不必要更换界面。

正交的好处

编写正交的系统,就能获得两个主要的收益:提高生产力及降低风险

提高生产力

1、将变更限制在局部后,开发时间和测试时间都会减少。

2、正交的方法同时促进了重用。

3、组合正交组件能获得相当微妙的生产力提升。

假设一个组件能做M件独特的事情,另一个能做N件。如果它们是正交的,组合起来就能做M*N件事。如果两个组件不正交,有重叠之处,结果就少一些。

在正交组件的组合过程中,单个单元的性价比提高了。

降低风险

1、代码中病变的部分被隔离开。

如果一个模块生病了,不太可能将症状传播到系统的其他部分。把它切下来并移植一个新的健康器官进去,也更加容易

2、这样获得的系统不那么脆弱。

对特定区域进行小的变更和修复后,因此而产生的任何问题都将局限于该区域。

3、你不会被特定的供应商、产品或平台紧紧束缚。

因为这些第三方组件的接口所关联的,仅仅是整个开发中相对很小的一部分。

4、正交系统可能更利于测试,因为为其组件设计和运行测试更加容易

正交的方法

设计

系统应该由一组相互协作的模块构成,每个模块实现的功能应独立于其他模块。

有时这些模块组件被组织到不同的层次上,每一层都做了一级抽象。

这种分层的实现是设计正交系统的有力途径。

因为每一层只使用它下面一层提供的抽象,所以可以在不影响代码的情况下极其灵活地更改底层实现。

工具包和程序库

在引入第三方工具包和程序库时,请注意保持系统的正交性。技术选择要明智。

当你拿出一个工具包时,问问自己,它是否会将一些不应该有的变化强加给你代码。

编码

每当你写下代码时,就有降低软件的正交性的风险。

不仅需要盯着正做的事情,还要监控软件的大环境。

养成不断质疑代码的习惯。只要有机会就重新组织、改善其结构和正交性。这个过程被称为重构。

保持代码解耦

编写害羞的代码–模块不会向其他模块透露任何不必要的信息,也不依赖于其他模块的实现。

试试最少知识原则。如果你需要改变一个对象的状态,让该对象替你来完成。这样做能让你的代码和其他代码实现隔离,更有可能保持正交性。

避免全局数据

只要代码引用全局数据,就会将自己绑定到共享该数据的其他组件上。

即使只打算对全局数据进行读操作,也可能引发问题。

避免相似的函数

重复代码是结构问题的症状。

测试

基于正交性设计和实现的系统更容易测试。

由于系统组件之间的交互是形式化的,且交互有限,因此可以在单个模块级别上执行更多的系统测试。

文档

正交性也适用于文档。其涉及的两个独立维度是内容和呈现。

真正符合正交性的文档,应该能够在不改变内容的情况下显著地改变外观。

读完刘媛媛的《精准努力》,想起了冯唐说的“结构化思维,结构化表达”。当你再怎么焦虑、恐慌时,不只是情绪表现,而应该提出为什么焦虑,焦虑怎么消除?怎么体现结构化思维,最佳表现形式,就是绘制一张思维导图。

在《精准努力》中,作者针对我们常见的问题:目标、策略、执行、心态、学习、社交六个方面进行了结构化表达。

目标篇

制定目标,我们一般会想到SMART原则,但作者提出了独特的视角:

最主要的思想就是二八原则,找到那20%的因素,作为核心目标,进行攻克。

产品质量管理中有一个法则:重要少数法则

这个法则是由质量管理专家约瑟夫朱兰提出的。朱兰认为,大多数品质不良的问题能够形成,可以归因为重要的少数,其余的小瑕疵,才是属于不重要的多数造成的

在发生质量问题的时候,20%的问题是由基层操作人员的失误造成的,但是80%的问题是由领导者造成的;而80%的领导问题,又是在20%的重要环节上造成的,这就是质量管理当中的“二八原则”。

有用不等于值得

值不值得,要把机会成本考虑进去

为何你的努力和别的人的努力总是有差别?

如果能够抓住关键的环节,就可以更少的投入,收获更大的回报。

这不是投机取巧,我们只是追求有效。人的专注力、时间、精力都是有限的。我们必须选择一种有节制的、更专注的努力方式,去追求更高的效率,追求有用功。

当我们不知道什么问题是关键问题的时候,要把通往目标遇到的所有问题全部列举出来。问自己:

是哪些问题,导致你对现在的生活不满意?

其中的哪些问题是你认为的主要问题?

这些问题的解决,是否可以促进其他问题的解决?

通过自问自答,圈定其中关键的部分,然后在这20%的领域,投入专注的卓越努力,去得到80%的收获。

改掉只要有用就去做的思维,也可以反过来思考这个问题:如果我只做三件事,我应该做哪三件?如果只做一件呢?

策略篇

做事的正确顺序:策略,勤奋地执行策略,然后成功。

要做成一件事:

1、要有一个目标

2、思考通往目标的途径

3、最后才是执行层面的事情

每个人都应该去追求成功。在这个过程中,无论是靠天分、靠勤奋,还是靠机遇,都靠不住。最可靠的是属于自己的方法论。方法论带来的成功是必然的,只不过来得早或晚而已。

六步循环

自我定位-瞄准目标-制定策略-执行反馈-调整行动-最终完成

自我定位

什么是目标?“我想考北大”这不是目标,说得好听点,叫想法、愿望。目标要有可执行性和衡量标准。也就是要符合SMART原则。比如“我要考到240分”,这算是一个目标。

什么叫作学会解释目标?

比如要考北大,意味着两门专业课都必须要考120分;意味着这张卷子当中80%的考点我都要知道;意味着考试范围内的80%我必须得知道。如果我只知道10%的话,那么还要搞定70%,那整个复习的过程就会变成这样:找到不会的地方,变成会的,直到这个百分比达到80%。

这就是目标被解释的过程,从“我想考北大”到“我要考到240分”,到“我要搞定那此不会做的题目直到百分比达到80%”,到“我今天已经做了50%,还有30%”。

策略

策略意味着:

第一,找到通往目标的真正问题和障碍

第二,为这些问题和障碍去设计方案

第三,方案应该是一个互相促进和统一的系统

第四,集中精力去执行自己的方案,放弃其他的事情

执行

一件事情最后的成果如何,取决于以下几个要素:

效果=时间x精力x目标x策略x专注度x熟练度

执行篇

1、专注力

2、拖延症

3、精力管理

4、专注

5、坚持

最近在工作中使用一个消息队列组件。发现当系统没有完全启动成功时,就开始消费消息了,出现所依赖资源初始化工作还没有完成,造成消费失败的情况。

通过源码跟踪,发现组件开始消费是在InitializingBean#afterPropertiesSet中触发的。

怎么处理呢?

首先,InitializingBean#afterPropertiesSet是在Bean初始化完成后调用的。

而现状是想在所有资源都被初始化完成后,再开始执行。

第一种方案:SmartInitializingSingleton

spring中提供了所有Bean初始化完成后再调用的机制。实现SmartInitializingSingleton

SmartInitializingSingleton 与 InitializingBean两者有很大的区别:

1,SmartInitializingSingleton只作用于单例bean,InitializingBean无此要求。但他们都不能用于懒加载的bean。

2,SmartInitializingSingleton是在所有单例Bean都初始化完成后调用的,InitializingBean是每个bean初始化完成后就会调用。

但因为是第三方组件,也不想修改源码,所以此路不通。

第二种方案:想到了@Lazy

把触发的Bean设置成@Lazy,再所有资源初始化完成后,进行主动加载。激活具体的消息消费行为。

行动分两步:

1、在具体消费消息的Bean上增加注解@Lazy

2、spring启动完成后,再主动加载此Bean

此时,又涉及到了spring的初始化过程

spring提供了很多启动各个阶段的触发点

包括ApplicationRunner、CommandLineRunner以及各种事件:

ApplicationContextInitializedEvent : triggered after ApplicationContext is prepared and ApplicationContextInitializers are called but before bean definitions are loaded

ApplicationPreparedEvent : triggered after bean definitions are loaded

ApplicationStartedEvent : triggered after context has been refreshed but before command-line and application runners are called

ApplicationReadyEvent : triggered after any application and command-line runners are called

ApplicationFailedEvent : triggered if there is an exception on startup

结合spring初始化过程,写一个小示例,验证一下bean初始化与这些事件的先后顺序

创建一个Bean

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
public class InitFirst implements InitializingBean, SmartInitializingSingleton {

public InitFirst() {
System.out.println("InitFirst construct method");
}

public void init() {
System.out.println("InitFirst init method");
}

@PostConstruct
public void postConstruct() {
System.out.println("InitFirst @PostConstruct");
}

@Override
public void afterPropertiesSet() throws Exception {
System.out.println("InitFirst afterPropertiesSet()");
}

@Override
public void afterSingletonsInstantiated() {
System.out.println("InitFirst afterSingletonsInstantiated()");
}

}

实现一下BeanPostProcessor

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Component
public class InitBeanPostProcessor implements BeanPostProcessor {

@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof InitFirst || bean instanceof InitSecond) {
System.out.println("postProcessBeforeInitialization:" + beanName);
}
return bean;
}

@Override
@Nullable
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof InitFirst || bean instanceof InitSecond) {
System.out.println("postProcessAfterInitialization:" + beanName);
}
return bean;
}

}

监听一下各种Event

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Component
public class InitListener {


@EventListener(ApplicationPreparedEvent.class)
public void preparedEvent() {
System.out.println("Yaaah, ApplicationPreparedEvent");
}

@EventListener(ApplicationStartedEvent.class)
public void startedEvent() {
System.out.println("Yaaah, ApplicationStartedEvent");
}

@EventListener(ApplicationReadyEvent.class)
public void runAfterStartup() {
System.out.println("Yaaah, ApplicationReadyEvent");
}

}

最后执行的结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
InitFirst construct method
postProcessBeforeInitialization:initFirst
InitFirst @PostConstruct
InitFirst afterPropertiesSet()
InitFirst init method
postProcessAfterInitialization:initFirst
InitSecond construct method
postProcessBeforeInitialization:initSecond
InitSecond @PostConstruct
InitSecond afterPropertiesSet()
InitSecond init method
postProcessAfterInitialization:initSecond
InitFirst afterSingletonsInstantiated()
InitSecond afterSingletonsInstantiated()
Yaaah, ApplicationPreparedEvent
Yaaah, ApplicationStartedEvent
ApplicationRunner
CommandLineRunner
Yaaah, ApplicationReadyEvent

整个初始化的顺序:

construct -> postProcessBeforeInitialization -> @PostConstruct -> InitializingBean#afterPropertiesSet -> init-method -> postProcessAfterInitialization -> ApplicationPreparedEvent -> ApplicationStartedEvent -> ApplicationRunner/CommandLineRunner -> ApplicationReadyEvent

ApplicationRunner与CommandLineRunner谁先执行?

在都没有标注order的情况下,ApplicationRunner 的优先级要高于CommandLineRunner

在方案二执行时,发现了Spring延迟加载Lazy没有生效的情况,后来追查到是xxl-job的bug。在遍历任务时,加载了所有的bean,包含了Lazy的Bean。在最近的xxl-job 2.4版本中进行了修复:

https://github.com/xuxueli/xxl-job/pull/3155/commits/b4835d40f18084e9facb9ec0d41993fdc885aca8

参考资料

Run method on Spring Boot startup

对于新晋升管理者常见的困惑中,“不自信”也是普遍存在的一种现象。

如何克服这种“不自信”,大家服不服我?套用江湖人的话,“谁不服,那就打到他服”。

当然,如果在普升管理者之前,你就是团队中最强的技术骨干,其他成员的战斗指数以及综合实力都在你之下,那对这个困惑估计体会不深。

但在普升管理者之前,团队内有跟你级别一样的,能力差不多甚至专业能力在你之上的成员,那将经历一个痛苦的适应过程。

不自信

其实归结起来,新普升管理者不自信的来源,主要有三点:

第一,管理经验不足和能力欠缺

很多对管理事务不知道该怎么着手,在摸索前行中磕磕绊绊,于是怀疑自己没有能力做好管理。

这是每位管理者的必经阶段,其实也是学习所有新事物的必经阶段。就像刚接触技术时一样,也经常会碰到一些不知所措的问题。

只不过,技术问题往往有比较标准的答案,通过查资料就能解决大部分问题;而管理问题刚很少有标准答案,很多经验和技巧是在不断实践的过程中积累起来的,掌握起来不像技术问题那样可以查查资料就能快速解决。这也是管理有挑战的地方。

第二,和团队成员对立比较

由于资历或能力不是团队里最突出的,担心团队里资历老或能力强的团队成员会不服自己,尤其当这些人提出不同意见的时候,常常会引起新晋管理者的挫败和颓丧。

要解决这个问题,关键是转变认知。

你现在是团队的负责人,需要把自己从和任何团队成员的比较和竞争中抽离,把目光投向远方,去看看你将带一个什么样的团队,以及在这个过程中,你能为公司、团队和团队成员带来什么样的成绩和成长。

当你不把团队成员放在你的对立面的时候,你和他们就没有任何竞争关系,因为所有的比较和竞争都是在同一个层次上才会发生,就好像你可能会和你的同学、同事比财富,但不会和马云去比较一样。

你要做的,不是和团队成员竞争、比较,也不是比团队每个人都强,而是要考虑如何让大家把自己的才智发挥出来,去达成一个共同的团队目标。总之,你要做的不是管束和控制大家,而是引导和支持大家

你要做的,就是用大家的力量,去做出更好的成果,而不是单单因为你的职位让大家服气。

因此,当团队中有老资格和高能力员工的时候,转换一下思维,得到两个结论:

1、你真的是非常优秀,以至于你能被公司赏识来负责这样一个团队

2、因为这些老资格和高能力员工的存在,你有机会做出更好的业绩

第三,背负着沉重的包袱

因为担心管理工作做不好会辜负上级的期望,所以带着很大的压力去工作。

前两类自信来源于自我能力和角色认知的提升,那么第三类自信的增强来源于外部反馈,尤其是上级。

事实上,自信心的建立的确需要外部的正向反馈,这些正向反馈可以极大地提升你的自我认可度。

如何才能得到外部持续的正向反馈?首先要把反馈通道建立起来,尤其是和上级的沟通通道。可以和上级约好一个例行的沟通机制,定期汇报团队工作,并就已经完成的一些重要工作征求上级的看法和评价。这其中,改进建议固然很宝贵,但是你还需要寻求一些肯定性的反馈。


怎么服人

虽然文章开头戏称:谁不服,就打到他服。这显然是受武侠剧的深度影响。看看武侠剧里面,有哪一帮派的帮主或掌门人不是武术最高的?

可管理者终究不是武侠剧里面的掌门人。看看武林第一的少林寺方丈,功夫深还得看扫地僧。再说还有梅长苏呢。

所以首先需要确立一个认知:管理者在技术实现能力上并不一定比团队的技术骨干强。管理者比下属技术能力强又如何呢?技术更强就能服人吗?如果你沉浸在“让自己最强”的表象中,就会导致一个问题–你的水平会成为整个团队的瓶颈,这可不是公司希望看到的。

级别越高的管理者,越应该明确一点,管理者的目标是让团队的成员更强,凝聚力是多高,从而可以一起去攻克更艰难的目标,而不是证明你比你的下属或者平级更强

有了认知,如何赢得骨干的尊重呢?三步走:“给干将搭台子”,“克服心理障碍”,“提升自身能力”

给干将搭台子

第一、给充分的资源支持

给资源支持,分对内、对外两个方面

对外,平时你要多找一些需求点,然后把你的员工推出去,让他们去负责解决这个强需求点,并且要持续地推出去。

对内,为了团队的核心骨干能有更好的发展,你需要给他们提供充足的支持,努力去找需求、要资源、要认可,让他们获得真正的成长。

第二、让员工有被需要的感觉,让他感觉自己在这个团队里“独一无二”

不耍语言技巧,陈述事实。说出每一句话都是真诚的。

“这件事很有意义,但是也很难,就我们组织目前的人员配置,只有你最有可能搞定这件事,我没有别的选择,只能靠你”

克服心理屏障

你有没有遇到过这样的情况,你明明觉得你的平级或者团队的高级别员工有些事情做得让你不满意,但是因为他们级别高,或者你觉得他是个硬茬,而你没有足够的感情强度去把他们的问题指出来?

当你没有了解具体的细节,或者对事情的认识不够深刻时,就去给员工提意见,那效果一定会很差。所以,你给员工提意见和建议的时候不要空谈,要不绕弯子地谈具体的细节。了解得越详细,你提意见的时候也就越有底气。

提升自己的能力

如何提升自己的能力呢?没有捷径,最有效的方法就是去经历挫折,主动去走出舒适区,把每一次办砸的事情都看成“天将降大任于是人也,必先苦其心志,饿其体肤”。犯错比不犯错好,人从失败中汲取营养的效果远好于从成功中汲取营养的效果。


总结

当你知道了这些认知,自信心也不是一两天就能建立,但得相信你的上级,你被提拔为管理者,绝不是他的一时冲动。就好比你现在需要从团队中提拔一个新管理者一样,当他需要像你现在的情况,你会怎么鼓励他?“
你也许不是那个最强的人,但是你得相信,你是此时此刻做这事儿最合适的人”

所以相信自己,既然你被认命为这个团队的负责人,你就是此时此刻带领这个团队最合适的人。

管理是一门实践科学,从“知道”到“做到”,还需要长期地刻意练习。

管理有两个对象:一个是“事”,一个是“人”。

“事”指的是目标,“人”指的是团队。也就是说管理有两大基本职责:一是达到目标,二是建设团队

在担任管理职务之后,过去的业务思维和行为模式没有及时转变过来,则会在实际操练过程中,会碰到各种各样的问题,这是常态。

如果提前知道前面有哪些“坑”是最容易踩到的,就可以提前规避,选择跨过去或绕过去。

最近在读了几篇管理材料后,对于有哪些“坑”,做了简单的收集。

《技术管理案例课》

1、管理太细

陷入微观管理;

要么给部下制定出特别详细的解决问题细则,而不是给问下提出问题让他自己拿方案

要么就是不停地去盯问下的进度和要求问下汇报工作,不等部下自己去思考和做事就先给一堆意见

人的本性是要自由,要我的地盘我做主。

要注意下属对自己话语权威性的追求和对自己是一个靠得住的人的自我认可

2、大包大揽

“与其等他做,我自己早就做完了”

要么你不要把这件事交给下属做

要么你就跟他说清楚他的责任,然后让他全权负责

下属不吃点亏是长不大的。但这个“亏”必须是我们这个部门能承受的

3、迷信流程

误以为流程能解决一切问题

要提高组织效率,不脱两层皮付出点代价是不可能的。

我们真正的难点在于怎么用人,怎么拿捏流程不能处理的意外情况。

想要用好人,首先就要了解这个人。知道每个下属擅长做什么?不擅长做什么?他在乎什么,反感什么?谁和谁搭配能够形成互补?这需要进行长期积累和挖掘。

与其花大力气把这个人拗成你希望的样子,还不如专注在怎么用好他的长处。

4、刷存在感

邮件和会议里频繁出现,但很少看到他做关键决策

必须问自己,为什么团队需要你来做经理,你给团队提供什么额外价值,你对于所管理的团队有推动感吗?

对日常工作中的事情有了独立见解,也能做关键决策促进一些事情的解决,但没有长期目标,没有制定为了达到长期目标的阶段性举措。

5、不能聚集

对于多个项目,平均分配注意力

要做成一件事,必须要聚集。如何判断有没有做到聚集?简单地说,就是你得有一个且只有一个目标

作为主管经理,在同一时间有好几个项目,你的主要精力只能放在其中一个项目上,绝对不要平均分配时间。

6、不会说不

对兄弟部门、上级领导无原则“认怂”

有理有据,采用“不夸大,不缩小”的策略。

对于老板的要求,可以问老板问题以获取更多信息。比如,你可以问老板:我们为什么要做这件事?你的具体期待是什么样的?我现在碰到的具体困难是这样的,如果你一定要做A,在不增加人手的情况下,我现在手头的B项目会受影响。


《技术管理实战36讲》

1、过程导向,被动执行

管理思维:管理看结果,员工看动作。

没有从“管理者”的视角出发,所以至少带来如下后果

1、团队方向感缺失

2、团队做不出有效的业绩

3、无法带领一个团队

2、大包大揽,唯我最强

包工作、包责任、包功劳

会带来如下后果:

1、梯队问题

2、激励问题

3、个人发展问题

3、带头大哥,当家保姆

1、不职业的管理风格和文化

2、团队没有方向

4、单一视角,固化思维

1、习惯性卡住

2、认知层次低

3、难堪重任

5、自扫门前雪,固守边界

角色和责任的边界划分,是为了分工和合作,但由于很多大型项目有赖于多个团队一起协作完成,所以双需要有人主动站出来,去承担边界模糊的那部分职责。

作为员工边界分明无可厚非,但作为一个管理者,需要以全局的目标为己任,才能拿到公司要的业绩结果。

否则会带来如下问题:

1、项目推进不畅,从而影响全局的结果

2、自我设限,因此个人成长受限

3、个人影响力无法扩展

6、患得患失

对此详细的解释在《做技术还是做管理》文章中已经描述。


《不确定的危机下,做确定的业绩》

管理认知不到位,会出现充当二传手、过度亲力亲为、充当救火队长等管理错位现象

第一种:充当二传手

上级有任务给到管理者,管理者就直接转给下属。

下属有问题,管理者就让他直接找上级。

上级和下属都觉得组织当中最没有价值的就是这种管理者。

第二种:过度亲力亲为

这种管理者责任心非常强,他认为自己既然是团队管理者,所有的工作都应该是自己的工作,就应该事事冲在前面,做出榜样给下属看。

因此经常看到团队管理者玩命干,可他的员工却没事干。管理者的能力再强工作再忙,也不可能靠一己之力完成整个部门的业绩。

第三种:充当救火队长

由于没有事先分工,没有明确职责,没有设立流程,没有奖惩制度,没有充足训练,导致工作到处出问题。

对于技术管理者来说,团队执行力这件事,不是什么难事,因为工程师有一种确定性思维,“靠谱”是好的工程师天生的品质,凡是明确答应过的事情,往往都会如数兑现。

虽然如此,但也不能确保每件事都是落地有声。也有很多时候,技术管理者也有如下的心声:

1、这事我来干,一天就搞定了,怎么搞了一周?

2、如果A也像B那么积极主动,这个项目就不会出问题了,所以A,你能不能更主动一些呢?

3、我们各种各样的流程都有,很完整也很系统,但是大家就是不按照流程办事


如何有效地推进事项的进展,按计划成功落地。需要做好三件事:事前规划,事中跟进,事后总结

事前规划

此阶段,重点就是做好计划。把事安排得明明白白。计划就是达到目标的路径图。

优先级

首先得有“资源是永远不足”的认知。公司本身也是在有限资源下利益最大化。

到每个团队的资源更是如此。常用判断策略就是:重要紧急四象限。

换个角度,我们都是要趋利避害的,从收益大小来看事情的重要性,从损失大小看事情的紧急性。

那么重要紧急四象限就换种画法:

计划

各个任务的优先级确定完成之后,就得给优先处理的任务制定落地计划。

而达成计划共识和计划落地是计划中两个重要的部分。

计划共识

共识的第一层境界:听懂

讲得人要讲得清楚,听得人要听得明白

共识的第二层境界:认同

认同就是认可和赞同,实现上下同欲

共识的第三层境界:行动

共识的终极目标就是要促发下属行动

计划落地

计划制定完成,也与团队达到计划共识,剩下的就是计划落地,开始执行了。

这个阶段有两点比较关键:

1、职责清晰,责任到人

2、团队的排兵布阵

OGSM模型

目的、目标、策略、衡量标准

事中跟进

下属不会执行你要求的,但会执行你检查的。检查到哪儿,执行到哪儿。

事中阶段有三个重点:行动、规范和进程

行动:检查计划有没有在推进,能力和意愿是否到位,及时辅导和鼓励

规范:过程规范,结果才正确。检查基本流程和机制是否缺失,工作流程是否规范

进程:按照其实制订的工作计划,定时检查工作进度

事后总结

总结最好方式就是复盘。

不管工作的结果好与坏,看看工作过程中有哪些经验,有哪些问题,接下来目标如何调整,计划如何调整,执行如何优化,经验如何固化。

其目的是着眼未来,确保下个阶段工作做得更好。

寻找任务成功的必要条件,也就是关键节点。

思维导图

《技术管理实战》

《不确定的危机下做确定的业绩》

继承的本质是提高代码的复用。

然而自从继承出世后,被人们过多滥用,以至于像耦合恐惧一样恐惧继承,得了“继承创伤应激障碍”。

是不是有像耦合必然性一样,解决继承的创伤呢?

里氏替换原则

这个原则最早是在1986年由 Barbara Liskov 提出:

若对每个类型T1的对象o1,都存在一个类型T2的对象o2,使得在所有针对T2编写的程序P中,用o1替换o2后,程序P的行为功能不变,则T1是T2的子类型

在1996年,Robert Martin 重新描述了这个原则:

Functions that use pointers of references to base classes must be able tu use objects of derived classes without knowing it

通俗地讲,就是子类型必须能够替换掉它们的基类型,并且保证原来的逻辑行为不变及正确性不被破坏。

比如那个著名的长方形的例子。Rectangle 表示长方形,Square 继承 Rectangle,表示正方形。现在问题就来了,这个关系在数学中是正确的,但表示为代码却不太正确。长方形可以用成员函数单独变更长宽,但正方形却不行,长和宽必须同时变更。

1
2
3
4
Rectangle rect = new Square();
rect.setHeight(4); // 设置长度
rect.setWidth(5); // 设置宽度
assertThat(rect.area(), is(20)); // 对结果进行断言

当正方形 Square 替换长方形 Rectangle 时,发现程序不能正确运行,这样就不满足LSP,也就不适合使用继承。

还有那个同样著名的鸟类的例子。基类 Bird 有个 Fly 方法,所有的鸟类都应该继承它。但企鹅、鸵鸟这样的鸟类却不会飞,实现它们就必须改写 Fly 方法

1
2
3
4
5
6
7
8
9
10
11
12

public class AbstractBird {
//...省略其他属性和方法...
public void fly() { //... }
}

public class Ostrich extends AbstractBird { //鸵鸟
//...省略其他属性和方法...
public void fly() {
throw new UnSupportedMethodException("I can't fly.'");
}
}

这样的设计

一方面,徒增了编码的工作量

另一方面,也违背了我们之后要讲的最小知识原则(Least Knowledge Principle,也叫最少知识原则或者迪米特法则),暴露不该暴露的接口给外部,增加了类使用过程中被误用的概率。

想验证有没有违背里氏替换原则,主要是两方面:

1、IS-A的判定是基于行为,只有行为相同,才能说是满足IS-A关系。

2、通常说来,子类比父类的契约更严格,都是违反里氏替换原则的。

在类的继承中,如果父类方法的访问控制是 protected,那么子类 override 这个方法的时候,可以改成是 public,但是不能改成 private。因为 private 的访问控制比 protected 更严格,能使用父类 protected 方法的地方,不能用子类的 private 方法替换,否则就是违反里氏替换原则的。相反,如果子类方法的访问控制改成 public 就没问题,即子类可以有比父类更宽松的契约。同样,子类 override 父类方法的时候,不能将父类的 public 方法改成 protected,否则会出现编译错误。

而像长方形例子中,正方形继承了长方形,但是正方形有比长方形更严格的契约,即正方形要求长和宽是一样的。因为正方形有比长方形更严格的契约,那么在使用长方形的地方,正方形因为更严格的契约而无法替换长方形。

多态和里氏替换有点类似,但它们的关注的角度是不一样的。多态是面向对象编程的一大特性,也是面向对象编程语言的一种语法。它是一种代码实现的思路。而里氏替换是一种设计原则,是用来指导继承关系中子类该如何设计的,子类的设计要保证在替换父类的时候,不改变原有程序的逻辑以及不破坏原程序的正确性。

其实还是回归继承本质,就是为了代码复用,子类最好不要覆盖父类的任何方法,只做额外增强

组合优于继承

组合优于继承:如果一个方案既能用组合实现,也能用继承实现,那就选择组合实现。

上面提到的鸟类例子,通过 AbstractBird 类派生出两个更加细分的抽象类:会飞的鸟类 AbstractFlyableBird 和不会飞的鸟类 AbstractUnFlyableBird。当还要关注“鸟会不会叫”的时候,
那就需要再定义四个抽象类(AbstractFlyableTweetableBird、AbstractFlyableUnTweetableBird、AbstractUnFlyableTweetableBird、AbstractUnFlyableUnTweetableBird)

类的继承层次会越来越深、继承关系会越来越复杂。而这种层次很深、很复杂的继承关系

一方面,会导致代码的可读性变差。因为我们要搞清楚某个类具有哪些方法、属性,必须阅读父类代码、父类的父类的代码,一直追溯到最顶层父类的代码。

另一方面,这也破坏了类的封装特性,将父类的实现细节暴露给了子类。子类的实现依赖父类的实现,两者高度耦合,一旦父类代码修改,就会影响所有子类的逻辑。

简而言之,继承最大的问题就在于:继承层次过深、继承关系过于复杂会影响到代码的可读性和可维护性。

而利用组合、接口、委托三个技术手段,重构一下上面的继承问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

public interface Flyable {
void fly();
}
public class FlyAbility implements Flyable {
@Override
public void fly() { //... }
}
//省略Tweetable/TweetAbility/EggLayable/EggLayAbility

public class Ostrich implements Tweetable, EggLayable {//鸵鸟
private TweetAbility tweetAbility = new TweetAbility(); //组合
private EggLayAbility eggLayAbility = new EggLayAbility(); //组合
//... 省略其他属性和方法...
@Override
public void tweet() {
tweetAbility.tweet(); // 委托
}
@Override
public void layEgg() {
eggLayAbility.layEgg(); // 委托
}
}

总结

尽管我们鼓励多用组合少用继承,但组合也并不是完美的,继承也并非一无是处。继承改写成组合意味着要做更细粒度的类的拆分。这意味着,我们要定义更多的类和接口。类和接口的增多也就或多或少地增加代码的复杂程度和维护成本。

如果类之间的继承结构稳定,继承层次比较浅(比如,最多有两层继承关系),继承关系不复杂,我们就可以大胆使用继承。反之,系统越不稳定,继承层次很深,继承关系复杂,就尽量使用组合替代继承。

在Filter中获取请求体,就得解析一次request的inputStream。

而在tomcat的设计中,request的inputStream只能读取一次,读取完一次后,虽然inputStream还在那,也不会变成空,但里面的内容已经被没有了。

解决方案很简单,就是继承HttpServletRequestWrapper,缓存request中流的内容。

比如实现一个类:ServletRequestReadWrapper

整个类的结构是这样的:

然而世事总不那么一帆风顺。

当请求头Content-Type值为 multipart/form-data 时,情况就出现了问题。在https://www.zhihu.com/question/434950674 这个回答中有说明。

在使用multipart/form-data上传文件时,controller类似这样的

1
2
3
@PostMapping
public void upload(MultipartFile file) throws IOException {
}

想要得到一个MultipartFile类型的内容,原理与其他类型一样,spring从request中获得流内容,并反射创建MultipartFile。

但相对于普通类型,Multipart并没有从request.getInputstream中获取内容,而是从另一个方法getParts里面获取,但getParts返回的内容也是解析的request.getInputStream中的内容。

是不是有点奇怪,为什么在wrapper里面已经缓存了inputstream内容,为什么到了右下解再去getInputStream时,却没有了呢?

tomcat的request类结构是一个装饰器模式

requestWrapper中的getInputStream其实还得来源于真实的Request对象。而在整个处理过程的末端,获取inputStream,并不是从requestWrapper中获取的,而是从真实的Request对象中获取。此时流内容已经被读取过,自然就读取不到了。

再回味下主要的源码,HttpServletRequestWrapper中getParts()

1
2
3
4
5
6
7
8
@Override
public Collection<Part> getParts() throws IOException, ServletException {
return this._getHttpServletRequest().getParts();
}

private HttpServletRequest _getHttpServletRequest() {
return (HttpServletRequest) super.getRequest();
}

到了RquestFacade的getParts()

1
2
3
4
5
@Override
public Collection<Part> getParts() throws IllegalStateException,
IOException, ServletException {
return request.getParts();
}

再到Request的getParts()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public Collection<Part> getParts() throws IOException, IllegalStateException,
ServletException {

parseParts(true);
}

private void parseParts(boolean explicit) {

...
//解析流中文件内容,upload.parseRequest中会调用request.getInputStream
//而传入的request,并不requestWrapper,而是真实的Request本身
//而此时Request中的流已经被读取过了,所以解析出的文件内容为空
//controller中的file变成了null
List<FileItem> items = upload.parseRequest(new ServletRequestContext(this));

...

}

到此,原理已经讲清楚了。怎么解决呢?

1、在Wrapper中,非multipart缓存inputstream,是multipart时,则缓存parts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public ServletRequestReadWrapper(final HttpServletRequest request) throws IOException, ServletException {
super(request);
if (HttpUtils.isMultipartContent(request)) {
parts = request.getParts();
} else {

final ServletInputStream is = request.getInputStream();

if (null != is) {
final ByteArrayOutputStream os = new ByteArrayOutputStream();
this.bodyLength = ByteStreams.copy(is, os);
this.body = os.toByteArray();
}
}
}

2、放弃multipart的消息体。

得到消息体的内容,有时只是为了记录日志,像文件上传,其实得到的消息体也没啥好记录的,所以放弃记录mulipart类型消息体。

微服务架构是云时代的首选架构风格。

Martin Fowler 在 2014 年写的文章《微服务的前置条件》中提到如果使用微服务架构,则需要先拥有一些先决条件:

1、快速的环境提供能力

2、基本的监控能力

3、快速的应用部署能力

而这三个能力,正是云原生提供的基本能力。

新技术带来的价值

既然现在是云时代,是不是我们就不用考虑其它架构风格,直接使用微服务架构呢?答案显示并不是。我们不要为了新技术而新技术。当然,对于找工作肯定是有利的。但我们还是从实际出发,从业务价值出发。

《架构师的自我拯救》中提到,架构师还得多考虑商业价值。使用新技术就是我们的借力。目标还是为了提升商业价值。

所有的技术演进,都是围绕着用户对这个产品的核心诉求展开的,通过技术层面的架构改造,来解决用户当下的痛点。

在阿里大牛毕玄的访谈中,提到了服务化的核心是解决了两个问题:

一、为了解决系统的水平伸缩能力的问题。每个应用,每个系统,承担的责任变少了,伸缩性就变强了。对应到商业价值,以更细致的粒度,控制系统运营的成本。

二、研发协作问题。以前100人开发一个系统,大家都没有分工,接一个需求,要改就从头到尾全改,这个时候有可能会出现冲突,因为可能每个人都在改同一个地方,大家合并代码的时候就非常痛苦,效率很低。

服务化分工了,你就改这块儿,他就改那块儿,虽然增加了协作成本,但它毕竟能让100人甚至上千人的研发团队可以并行做下去,现代软件变得更复杂,做任何软件上来就是一帮人,必须考虑到一帮人协作的问题。

正是《拆完中台再拆微服务》中阐述的微服务是为了提升程序效能和团队效能。

云时代的特性

假如一个电商网站,平时只有几千用户同时使用,只需要100台机器就足以支撑这个系统了;而到了双十一,用户量可能猛增几百倍,那需要比如说10000台机器支持这个系统。

而云平台,可以动态调整系统需要的机器数量,让我们按需使用。这样我们就不需要在闲时投入过高的机器成本,也就是非双十一期间,维持10000台机器的开销。但同时也不会错过在业务高峰获取收益的机会,因为云平台会自动帮我们从100台扩容到10000台机器。

这种动态调节的能力被称为云的弹性,它是云平台一切美好特质的基础。

为了实现这种弹性,运行在云平台之上的系统,需要满足一个条件:这个系统可以通过水平扩展进行扩容。

水平扩展,指通过增加机器数量、构造集群的方法,来满足容量的需求。

垂直扩展,指当遇到扩容需求时,通过更换更强有力的机器,来获得更好的性能。

各种基础设施服务云平台,它们其实只有复制和剪切两个能力:

1、根据给定镜像,产生符合镜像内容的机器的能力。也就是将镜像复制为机器的能力。

2、撤销不需要的机器,将其放回资源池的能力,也就是剪切机器的能力。

通过复制和剪切这两个能力,云平台就能对某个镜像提供水平扩展。这种扩展方案通常被称作为弹性负载均衡。

那么怎样利用弹性负载均衡提供的水平扩展,才能更有效地架构系统呢?关键在于将弹性需求不同的组件分离。

假如你在运营一个在线电商平台,我们可以粗略地将这个电商 平台的业务分成产品目录和支付两大块。在双十一期间,肯定会遇到比平时更大的流量,因而需要更高的系统容量去支持业务。

但问题来了,产品浏览和完成支付两个部分增加的流量是一致的吗?从个人实际参与抢购的经验看,双十一之前,用户对产品浏览的需要比平时多;而在双十一当天,可能会对支付的需求更多。

因此我们对支付和产品目录两块功能,制定不同的水平扩展策略,然后由不同弹性负载均衡控制。这样就可以有针对性地在不同阶段为两块功能提供不同的容量。

按这个角度,我们把弹性作为主要指标,对系统进行划分,将不同弹性变化的组件放入到不同的弹性边界中。

通过弹性边界,可以更细致的粒度,控制系统运营成本,也才能真正发挥云平台的能力。所以当要想要利用云平台架构软件时,寻找合理的弹性边界是很重要的事。

微服务划分原则

微服务怎么划分?总体来讲,从功能性需求和非功能性需求两方面考虑。

从功能性方面考虑:微服务的划分应该有利于保证系统概念的一致性,更容易灵活扩展功能,而这些又要求开发团队顺畅的沟通协作。

DDD限界上下文正好在这方面提供了理论指导,奠定了划分基础。

根据限界上下文划分,既考虑到了传统模块化思维中对业务概念的松耦合、高内聚的要求,又考虑到团队的认知负载和认知边界。

这样一方面解决了团队协作和概念一致性问题。另一方面,每个限界上下文又是一个业务概念内聚的边界。在这个边界内部,就更容易建立可维护、易扩展的模型。

合理的微服务划分,应该是对于多数需求变更,只需要改动一个或少量的微服务。而划分不合理的话,对多数业务需求,都要修改多个微服务。

从非功能考虑:在性能、安全、可用性,甚至发布周期,看是不是需要进一步划分。或者考虑把几个限界上下文合并到一个微服务。极端情况下,把有上下文合并到一个服务,又变成一个单体。

到此,微服务的划分,我们当前都是以限界上下文优先来划分的。这也符合面向对象建模中的聚合概念,是一种“一致性优化”的模型结构。是“概念一致性边界”,“事务边界”。

但结合上一章节“弹性边界”,在云时代,弹性是个很重要指标。如果两个上下文明显具有不同的弹性诉求,那就应该拆分。而如果具有一致的弹性诉求,就可以不拆。

弹性边界跟软件模块之间存在依赖关系一样,弹性边界间也会存在依赖。而弹性边界间的依赖(也就是服务间调用关系,或是数据交换关系),会造成流量的传递。如果上游弹性边界突然需要处理大量的流量,那么这些流量自然也会传递到下游弹性边界中。

这在实现中常发生,当平台的入口系统流量上升后,后面依赖的系统流量也上升了,整个一条调用链路上的系统都得扩容。这种不同弹性边界间流量的传递就是弹性依赖。

只要组件之间存在交互,弹性依赖就不可避免。在云平台更擅长处理依赖于吞吐量的弹性依赖,但对依赖于响应时间的弹性依赖,就没有好办法了。这背后的原因在于水平扩展并不能保证改进响应时间,而只能提高吞量。也就是云平台的弹性并不总能改进响应时间,但一定可以提高吞吐量。

正因为云平台不擅长处理依赖于响应时间的弹性依赖,这类弹性依赖被称为弹性耦合,以表示与依赖于吞吐量的弹性依赖的区别。

怎么避免弹性耦合,才能充分利用云平台的能力。最简单的方式,是将组件的同步调用模式改为异步。因为服务与组件间的同步调用,会带来响应时间的依赖;而异步调用,则能将其改变为吞量的依赖。也就是将弹性耦合变成了弹性依赖,使得整个系统可以更好地利用云平台能力。

不过,当由同步变成异步时,意味着,原先产生的数据可能存在中间状态。比如支付,由原来的开始支付 -> 支付成功/失败;变成开始支付 -> 支付中 -> 支付成功/失败。

带来的中间件状态,在业务上是否有特殊含义,这在业务建模时,是需要考虑的第一个问题;再者,异步带来的一致性改变,对业务会产生什么影响,是需要考虑的第二个问题。

归根到底,为了解决弹性耦合的问题,我们需要将原味面向对象风格中默认的同步模型改为异步。

高内聚

微服务划分原则,不管是以限界上下文为主,还是以弹性边界为主。更多的还是要考虑业务形态。为业务赋能才是目标。多维度去权衡划分原则。

不能只考虑限界上下文,不管业务功能性还是团队认知负载是合理的,但弹性耦合了,一个扩容,链路上的所有系统都得扩容。也不能只考虑弹性边界,把简单同步的业务上下文都使用异步处理,忽略业务上的耦合必然性。为了异步而异步。

总结起来,还是要权衡,不要单维度走极端。这些永远正确的废话,真的很有道理。最近看饿了么CTO张雪峰的访谈,正好聊到了微服务问题,可以结合理论体会一下:

饿了么原来就是个单体,所有的业务逻辑就是一个东西、一个源代码库,C 端、B 端、D 端(Delivery,物流)全在一起,牵一发动全身,也就是说你在部署的时候,每个服务器都要布一坨这个东西,一是影响性能,二是发布很麻烦。只要有同学发布,即使跟你无关,你也要发布一遍,所有的机器都要扫一遍。我们做技术的就要拆解,肯定要至少再分一级。

拆分与否,我们当时就遵循一个原则:只要一个人有变化,一堆人要随着你动,或者叫“牵一发动大部分”的时候,这一定是有问题的。其实这也是逻辑原则或数学原则。所以我跟他们说,不要扯什么领域驱动、微服务了,就用这个原则。这个原则确实最容易讲清楚,但实践的时候要多次试、反复试。
单体是一个极端,微服务或单一原则是另一个极端。

饿了么从来没有真正提过微服务,从来没有过,我不去用这个概念。我们就是从业务的合理性去拆分。对领域驱动呢,我当时也是持观望态度,不能说保留态度,我觉得领域驱动是一个模棱两可的东西(顶尖 DDD 牛人或在大规模超复杂体系下成功实践过的同仁勿喷,毕竟让绝大部分技术同学吃透 DDD,无论 ROI 还是效率都很低),就跟架构一样,所以我希望回归朴素,就是从逻辑的角度,或者数学角度,给大家解释。所以当时我们也不做领域,我把以前的经验带过来,开始有一些中台的萌芽,比如说把交易系统、营销系统拆出来,把用户系统拆出来等等。

从逻辑上讲,当你十次里面有八次“牵一发要动大部分”的时候,你就没必要去拆,你就让它耦合(内聚)在那,哪怕最后合出来一个巨大的东西,那证明这个业务就是这样的,没办法。你要么抱怨很倒霉进入这个业务领域,要么你就自己想办法克服。当然还有一个办法就是你通过技术去改革这个业务,那意味着这个业务甚至整个行业的游戏规则都要变,在短时间内几乎不可能。之前也讲过,对绝大部分公司的技术团队来说,妄图通过技术驱动业务,还是省省吧。


后来我发现物流系统还有个很大的问题,搞物流系统这批同学,就是另一类极客。饿了么刚开始拆分服务,物流拆分得很夸张,直接同步变异步了。我说你们犯了一个错误,叫“为了异步而异步”。
大家以前的代码(交互)尽量都是一路撸到底嘛,直接写完,这个叫单体。后来搞微服务就要拆开了,结果他们不光拆开,拆开之后,还要用消息通知。我举个不太恰当但大家明白意思就行的例子,比如说算工资,本来可以直接算出来,他们非要先送一个你的职级,再送一个你的社保基数,然后送过来之后还不是马上给你,你要自己去取,我只是通知你有这个数据了。你取过来之后慢慢算,算完之后再推给另一个涉及工资计算的模块,诸如此类。物流同学就是用类似方式,他们真的把异步做到了“极致”(饿了么价值观:极致、激情、创新)。
但是他们做异步的初衷是什么?是因为物流的量很大。以前宕机是因为量很大,用同步的话服务器撑不住,所以就改异步。他们说至少可以缓和五秒钟,但后来我发现这五秒钟没意义。
我自己也体验过,比如我点个外卖,提交订单之后习惯性去刷一下,看看商户有没有接单,然后过一分钟看看骑手有没有接单。还要看地图,有时候看到小哥明明经过我这了,怎么先去送另一个人了?可能很多人都有这样的疑问,这个不能怪骑手,也不能怪系统,有各种原因,此处暂时不表。
大家都会去刷,后来我们发现用户在饿肚子时的心理承受能力就是三到五秒(淘宝、携程没这问题,大家对订单或物流状态变化的容忍度高很多),你是通过异步让这个订单不至于当场爆掉,但你延后五秒之后,堆积起来也很厉害,东西多了之后,最后还是爆掉,你只是让用户前五秒感觉系统没有宕机,但最终结果还是宕机。最后我们异地多活搞出来,几乎就没有大的事情了。

我们原来设想异地多活只能一次性切换,因为我们的业务是强耦合的,不像携程,携程机票、酒店关联度不大的,你要订机票 + 酒店,做个简单聚合就行了,但我们不一样,饿了么是用户下了单,商户接了单,物流就要送单,上下游其实是强耦合(高内聚)。
程序员可能会说,现实业务没你说的那么理想,该强耦合就强耦合,其实不是强耦合,另一个词叫高内聚,该内聚的时候你不要去追求什么微服务那些乱七八糟的东西,就应该高内聚,因为就是一个业务形态,业务才是最重要的判断耦合或内聚的依据。谁(调用方 / 消费方)也离不了谁(被调用方 / 服务方),你每次调用都涉及到它,干嘛非强扯开来?没太大好处,当然,可以分开发布算一个好处,但也仅是技术上的好处,不是业务或领域上的好处。

总结

我们将微服务架构看作是一种以业务上下文为弹性边界的云原生架构模式。弹性优先原则不仅仅适用于微服务架构,而是适用于所有云原生架构。

把功能需求和流量传导分为静态和动态的角度

静态的功能需求:

如果少数大需求需要跨微服务,是正常的,但是也要注意任务拆分,把大需求拆分到多个微服务,约定好接口,各自开发,各自部署,集中联调。

如果大多数需求需要跨微服务,那多半你的微服务拆分的是有问题的,你需要重新考虑你的微服务的拆分是否合理,必要的话合并一些微服务。

动态的流量传导:则是弹性边界,一旦流量上升,链路上所有系统都会被传导并进行扩容,那是不是拆分也不太合理,或者能否从弹性耦合变为弹性依赖。

微服务架构只是一种技术手段,使用微服务架构的目的,不是为了让你的架构更流行更酷,也不是为了让你的服务尽可能小,而是借助微服务的架构,让团队规模变小,大开发部门变成各个小的开发小组,并且各个小组应该尽可能独立,减少相互依赖,减少沟通成本。

而一个常见的问题就是错把手段当目的,为了微服务而微服务,服务拆的太细,反而维护和沟通成本大增。

理想的微服务架构,一个需求在一个微服务内就解决了,独立测试独立上线,基本不需要太多跨团队的协作。同时一个小团队维护的微服务也是有限的。