码农戏码

新生代农民工的自我修养


  • 首页

  • 归档

  • 标签

  • 关于

  • 在线工具

  • 搜索

如何精准达到目标

发表于 2023-08-16
字数统计: 1.2k 字数 | 阅读时长 ≈ 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、坚持

Spring的Lazy与SmartInitializingSingleton

发表于 2023-06-10
字数统计: 897 字数 | 阅读时长 ≈ 4 分钟

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

通过源码跟踪,发现组件开始消费是在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

谁不服打到他服

发表于 2023-05-21
字数统计: 2.1k 字数 | 阅读时长 ≈ 7 分钟

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

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

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

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

不自信

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

第三,背负着沉重的包袱

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

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

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

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


怎么服人

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

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

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

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

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

给干将搭台子

第一、给充分的资源支持

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

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

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

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

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

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

克服心理屏障

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

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

提升自己的能力

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


总结

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

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

新晋升管理者的几个坑

发表于 2023-05-03
字数统计: 1.6k 字数 | 阅读时长 ≈ 5 分钟

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

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

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

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

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

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

《技术管理案例课》

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、患得患失

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


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

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

第一种:充当二传手

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

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

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

第二种:过度亲力亲为

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

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

第三种:充当救火队长

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

团队执行力不够都是下属问题

发表于 2023-05-01
字数统计: 902 字数 | 阅读时长 ≈ 3 分钟

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

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

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

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

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


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

事前规划

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

优先级

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

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

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

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

计划

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

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

计划共识

共识的第一层境界:听懂

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

共识的第二层境界:认同

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

共识的第三层境界:行动

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

计划落地

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

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

1、职责清晰,责任到人

2、团队的排兵布阵

OGSM模型

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

事中跟进

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

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

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

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

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

事后总结

总结最好方式就是复盘。

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

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

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

思维导图

《技术管理实战》

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

什么样的继承才是好的继承

发表于 2023-03-25
字数统计: 1.7k 字数 | 阅读时长 ≈ 6 分钟

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

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

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

里氏替换原则

这个原则最早是在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(); // 委托
}
}

总结

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

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

httpRequest中重复读取inputstream注意事项

发表于 2023-03-04
字数统计: 722 字数 | 阅读时长 ≈ 3 分钟

在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类型消息体。

云时代微服务划分原则是什么

发表于 2023-02-27
字数统计: 4.9k 字数 | 阅读时长 ≈ 16 分钟

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

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

1、快速的环境提供能力

2、基本的监控能力

3、快速的应用部署能力

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

新技术带来的价值

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

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

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

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

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

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

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

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

云时代的特性

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

微服务划分原则

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

高内聚

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

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

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

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

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

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

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


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

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

总结

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

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

静态的功能需求:

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

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

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

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

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

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

微服务不是银弹也不是哑弹

发表于 2023-02-19
字数统计: 2.1k 字数 | 阅读时长 ≈ 7 分钟

微服务的核心思路就在于将业务能力封装成独立的松耦合的服务。通过这样一组服务,构建企业内的能力生态系统。除了能满足当前应用的需要之外,也为未来可能的新应用提供了紧实的基础。

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

当提到微服务时,总会想到各种各样的好处:

1、使大型的复杂应用程序可以持续交付和持续部署

2、每个服务都相对较小并容易维护

3、服务可以独立部署

4、服务可以独立扩展

5、微服务架构可以实现团队的自治

6、更容易实验和采纳新的技术

7、更好的容错性

当然,没有一项技术是“银弹”。

微服务架构也存在一些显著的弊端和问题:

1、服务的拆分和定义是一项挑战

2、分布式系统带来的各种复杂性,使开发、测试和部署变得更困难

3、当部署跨越多个服务的功能时需要谨慎地协调更多开发团队

4、开发者需要思考到底应该在应用的什么阶段使用微服务架构

近些年,反对微服务的声音越来越多。

在twitter上有个很热门的贴子:
twitter.com/xuwenhao/status/1593469165892820992 作者显明的数落了微服务的种种不是。为了更方便你了解作者的看法,我简单罗列下:

1、微服务的适应场景非常有限。这么多巨头互联网公司的核心业务逐渐微服务化,的确是在特定的历史时期和场景下的解决方案。

2、大型系统的开发,核心的挑战其实只有一个,就是“控制复杂性”。微服务不会减小复杂性,只会转移复杂性,它天然是为了解决极度复杂的计算、存储问题,中小规模系统其实是根本不需要的,至少在成为大型系统之前。

3、在系统开发上,控制复杂性的方式,可以用三个关键词来描述,那就是“抽象”、“封装”和“复用”。

4、服务化的第一个挑战:是“服务”并不是无状态的,“服务”也绑定了数据。以电商业务为例。商场生成订单,必在服务内持久化下来。然后,这个订单会发给到订单履约系统,也会持久化下来。然后两边都有可能触发订单状态的变更,商场用户可能取消订单,履约系统可能因为商品缺货也取消订单。两边都需要有对应的接口和实现,去完成这样的状态同步。这个过程中,就容易引入数据不一致的问题。

5、第二个挑战:因为是不同的服务,就会面临“向前兼容”的问题,不同的系统并不是完全同步迭代的。而已经发布的服务,意味着对外有了明确的协议承诺。在服务发布新版本的时候,必须要确保向前兼容。

6、第三个挑战:因为服务化划分了明确的边界,系统更容易变成异构的,更容易引入更多的技术栈。并且有些功能,会在两个不同的语言、框架下各实现一遍,也容易进一步放大之前所说的业务数据不一致的第一个挑战

三种典型的伪微服务

在James Lewis 和 Martin Fowler的名作《微服务》中,将微服务定义为一种架构风格,并总结了它的九种特质:

1、通过服务实现组件化

2、服务按照业务能力划分组织

3、服务以产品而不是项目研发

4、逻辑集中在服务中,编排简单

5、每个服务自主决策(技术栈、语言等等)

6、每个服务自主管理数据(不强制使用统一数据源)

7、基础设施自动化

8、将服务失败当作常态纳入设计考量

9、演进式设计(不求一步到位)

在实现中,如果对上面的特质理解出现偏差,就会出现三种典型的伪微服务风格

1、分布式服务

服务按照业务能力划分组织

微服务中的服务应该以业务能力为粒度。这间接回答了“微服务到底多微合适”,既不是单纯的技术能力(比如查询、获取系统时间),也不是完整的应用,而是用以支撑构建应用的业务能力。

通常所说的“恰当粒度”是在业务与实现两个维度上平衡的结果。并不会存在“只从单一维度入手,越怎么样就越好”这么简单粗暴的结论。所以微服务并不是越小越好,当小到不能表示业务能力,就不再是微服务了。

如果不顾及服务是否按照业务能力划分组织,就是一种典型的伪微服务模式。被称之为分布式服务。当然分布式服务并不是反模式,它有其特有的用处,只不过它并不是微服务而已。

2、微工作组

服务以产品而不是项目研发

这主要是从生命周期角度看,产品和项目的差异体现在团体结构和生命周期上。

产品的生命周期分为初始、稳定、支持和结束生命几个阶段。那么产品的不同版本,可能处在不同的生命周期中。所以产品团队需要在同一时间内,支持多个处在不同生命周期的产品版本。而项目通常假设只有唯一产物,随着项目生命周期的进项,项目化服务一直在改变。

因此产品化服务的生命周期,实际上相当于承诺在产品生命周期内,服务是不变的。也就是说只要1.0不结束生命,那么我们就可以一直使用它。哪怕发布了1.5、2.0、3.0,只要1.0满足我的需要,并且还在生命周期内,作为消费者,可以无视你的后续版本。

微服务需要服务间不仅仅在接口上松耦合,还在要生命周期上松耦合。也就是微服务可以自主发布,其他服务不应该受到影响。产品化是实现这一点的根本途径。

如果服务缺乏产品化生命周期,那就会产生一组在生命周期上紧密耦合的服务,从而完全丧失微服务的意义。随着服务数量变多,这种生命周期的耦合还会带来难以承受的沟通成本。

3、傻服务

逻辑集中在服务中,编排简单

逻辑越在服务中集中,所需要的编排就越简单,通常通过RESTful API或者轻量的消息机制即可完成。

如果服务中的逻辑简单,那就会有大量的逻辑泄露到编排逻辑中,此时就需要使用复杂的编排工具辅助我们工作。

选择编排复杂的逻辑,听越来很有道理:既然我们希望在不同场景下复用服务,那么总有一些需要改变的订制代码,我们需要将它们与服务本身分离。分离之后,就能通过编排引擎,帮助我们在不同的场景下重用这些服务

但按照这个逻辑下去,服务往往会变成对于数据的CRUD,然后大量的逻辑存在于编排引擎中,这也是典型的伪微服务。像傻服务一样。

总结

其实任何技术都可以说它既不是银弹也不是哑弹。就是优势与缺点并存。我们需要权衡的是在什么样的阶段引入什么样的技术来帮我们更好更快地解决问题。

架构师眼中的正交设计

发表于 2023-02-11
字数统计: 1.4k 字数 | 阅读时长 ≈ 4 分钟

正交设计,什么是正交设计,在之前的几篇文章中,《架构的本质是业务的正交分解》、《应用对变化》都有学习记录。

这两篇文章越看越感觉有道理:

1、系统应该被分解为“最小化核心系统 + 多个彼此正交的周边系统”

2、消除重复

3、分离关注点

4、管理依赖:最小化依赖、向稳定方向依赖

对于这四个策略的理解,感觉自己的认知层次还是太低了。更多的时候还是在作为编码原则。而非架构原则。

在《架构师的自我拯救》提出了技术人员的商业价值,包括两部分:快速试错和快速规模化。

那么我们能在企业的竞争中做些什么,同时也自己创造增量价值呢?其实也被包含在这两方面。

比如快速试错。为什么需要快速试错?为了验证商业想法是否能得到市场认同,从而获得商业价值。必然需要高速响应业务和技术的需求。

这面临的挑战就是交付时间压力。市场不等人,竞争对手也不等人。

除了战略层面的尝试方向对不对,如果有人承诺方向一定对,那自然不用试错,直接火力全开,饱和式攻击就行。

绝大多数情况是没人担保的,只能尝试。对应到技术人员,挑战在于只是很小的一次尝试,不要把系统改得面目全非。

对应到架构,就是我们系统的得符合“开闭原则”。能稳住核心系统的不变性,又能保持系统有序的增量。把大多数的尝试尽量封装到一个小的领域内。不会因为多次的业务尝试,导致系统随着时间变得混乱。

想要做到这些,需要如下架构原则:

1、单一职责,把每个业务尝试封装到一个单一模块中。一旦尝试失败,就可以迅速把业务逻辑下线,避免影响整体的复杂性。

2、最小依赖,整体架构设计要保障大多数业务尝试可以在业务层完成。如果每个业务方的需求都会侵入到底层的逻辑,那么每次尝试都会变成跨模块,甚至跨团队合作,这种架构会大幅降低业务尝试的速度

3、最小暴露,相当于最小被依赖,在业务尝试期接口不对部门或企业外部暴露,包括API、数据共享、事件、消息等一切对外界造成影响的通信机制。尤其是输出,这样才能最小化它的爆炸半径。否则该业务尝试的数据模型会污染到其他业务,在尝试失败之后对其他业务的影响也会很难剥离。

原则很简单,但怎么落地这些原则,最大的挑战来于短视疸。如互联网火热时,人员更替很频繁,导致技术人员的稳定性差,想要快速拿到结果就会设计短视。还有企业组织结构,像康威定律一样,组织间的利益争夺,造成组织内最优设计,但企业整体设计熵增。

作为架构师,可以:

1、提升对封装能力重要性的认知。这是一个技术人员的基本功,从写代码的第一天就需要。只不过随着职业发展,从封装代码架构,到封装业务逻辑,最后到封装业务尝试。

2、建设复杂度控制机制。这里设计评审很关键。业务尝试也要有设计评审,而评审的一个固定环节就是逻辑、数据和接口的最小爆炸半径的设计。

3、推行最小必要架构原则。任何增加功能、引入复杂性的设计,都要做一个正式的评审,而简化的行为则不需要。


当然,这些内容,是在郭东白架构课程里面看到的,他提出这是一个架构师提升企业架构设计对外部的适应能力。我理解这就是应对变化的能力。怎么能更好的应对变化,最佳原则就是“开闭原则”。而这些内容我更感觉是正交设计的另一种表述。这些原则与正交设计是一脉相承的。

如果整个系统的各个功能模块是正交设计的,那自然能灵活应对变化。一个庞大系统无法适应变化时,主要是架构僵化以及各个模块之间的关系错综复杂,导致牵一发而动全身。不能改不敢改。

应对变化时,不要饱和式攻击取胜,需要对阶段性精确目标最大投入取得成果。怎么才能做到不饱和式攻击,在架构层面就是要做到正交分解。


总结一下:架构师主要职责就是为了抵制熵增。而抵制熵增不管从战术还是战略,正交设计都是一种很好的实践方式和指导原则。

12…16
朱兴生

朱兴生

154 日志
3 分类
53 标签
© 2016 — 2023 朱兴生 | Site words total count: 349.2k
由 Hexo 强力驱动
|
主题 — NexT.Mist v5.1.4
沪ICP备18040647号-1