2022年一月份总结与2022年二月份计划

Posted by AlstonWilliams on January 25, 2022

技术学习

读《Expert One-on-One J2EE Development without EJB》这本书的时候,看到IOC的几种实现方式,有Dependency Lookup还有Dependency Injection。看到这里,突然冒出来一个问题,我们为什么要使用IOC?

比较常见的一个类似的问题是为什么要用Spring。标准答案一般是Spring提供了IOC,AOP,解耦啥的。

IOC的两种方式,一种是Dependency Lookup,一种是DI。

Dependency Lookup类似下面这种:

public class MyBusinessObject implements MyBusinessInterface {

    private DataSource ds;
    private MyCollaborator myCollaborator;
    private int myIntValue;

    public MyBusinessObject() throws NamingException {
        Context ctx = null;

        try {
            ctx = new InitialContext();
            ds = (DataSource) ctx.lookup(“java:comp/env/dataSourceName”);
            myCollaborator = (MyCollaborator) ctx.lookup(“java:comp/env/myCollaboratorName”);
            myIntValue = ((Integer)ctx.lookup(“java:comp/env/myIntValueName”)).intValue();
        }
        catch (NamingException ex) {
             logger.warn(“InitialContext threw exception on close”, ex);
        }
        finally {
             try {

             }
       }

      if (ctx != null) {
         ctx.close();
      }
      // Business methods

}

这段代码是EJB中实现Dependency Lookup的样例代码。可以看到,它是主动获取依赖的对象的。

而Dependency Injection的样例代码如下:

public class MyBusinessObject implements MyBusinessInterface {
     private DataSource ds;
     private MyCollaborator myCollaborator;
     private int myIntValue;
     public void setDataSource(DataSource ds) {
         this.ds = ds;
     }

     void setMyCollaborator(MyCollaborator myCollaborator) {
        this.myCollaborator = myCollaborator;
     }

     void setMyIntValue(int myIntValue) {
         this.myIntValue = myIntValue;
     }
}

这是基于Setter的DI模式。没有用到Spring。它是被动注入的,由Container统一注入。

我们可以看到,IOC其实就是为了解耦,而实现IOC的两种方法,DL和DI,从代码上来看,只是后者代码更少,更简单。当然后者还有一些其它优点,比如依赖更少,测试更简单。这些不详细讨论。

所以Spring相对于EJB,进步的并不是出现了IOC,它只是改进了实现方式。那IOC到底是什么?能给我们带来什么便捷性?

细细想来,这个问题从我刚开始接触Java Web开发的时候,大概七年前,就有这个疑问,这是当时没有去正视它。

七年前,接触Java Web开发的时候,当时主流的框架还是Struts2, Hibernate/MyBatis, Spring(基于xml那种最原始的Spring,而不是后来出现的Spring Boot)。这个时候,Struts2和Hibernate带给我的提升是最明显的。Struts2相对于直接基于Servlet和JSP的开发方式,简单了很多。Hibernate以及MyBatis这些ORM框架则极大的简化了数据库操作的复杂度。而Spring带给我了什么我并不知道。当时就是把配置,比如数据库用户名密码啥的写到一个XML里面,然后写个配置类,自动装配好。其它的好处呢?不知道。

其实从我的角度来看,用这种方式写个配置文件,有啥好处吗?完全没有。我为什么不把配置写在一个文本文件里面,然后写一个Java工具类直接去解析文件获取配置就好了呢?像Spring Boot出现之后写的application.properties配置等,不就是这种思想吗?这种方式不是更加清晰明了吗?

我能想到的一个比较好的理由是解耦。为啥呢?因为后面改了代码,比如数据库原先读的是MySQL,然后改成了MongoDB啥的,用Spring的方法就不用修改代码,直接改下配置就好了。但是这个有什么实际价值么?我认为没有什么卵用。

因为你适配MongoDB代码都写了,顺手改下引用的地方不就行了?反正代码都是需要重新编译重新部署的。当然,都放在Spring配置文件中,确实哪些地方引用了比较清晰。

说什么IOC解耦,这个点确实存在,但我认为不值一提。解耦应该降低代码复杂度,提升开发效率等。解耦比较有代表性的案例,有前后端分离,以及微服务。这些案例每个都极大地提升了开发效率。每个模块都是不同的人开发,然后通过面向接口的方式协作。但一个模块内部,完全由一个人开发,这个模块中间的解耦有什么意义呢?难道不是反而提升了复杂度吗?

有趣的是,作者在《Expert One-on-One J2EE Development without EJB》这本书中也提到,”Nevertheless, in my experience, most application objects won’t need to depend on the container. In our JPetStore sample application, for example, there are no Spring IoC container dependencies or IoC-related imports in Java code.”。

抛开IOC不说,Spring的成功离不开AOP。虽然Spring中也是集成的其它的AOP框架,但是抛开Spring,其它的我还真不会用。

Spring发展到现在,成为Java Web中最重要的框架,成为我们离不开的框架,更多的原因是因为它的生态。比如Spring MVC,使前后端分离成为可能,再也不是前后端在一起,在Servlet中写代码,数据在JSP/Struts中渲染了。比如Spring Boot,改革了Web应用的部署方式,给微服务打下了基础。最早需要先手动部署Tomcat/Glassfish啥的这些Web服务器,然后将应用代码打包成war包放上去跑。后来Spring Boot出来之后则是可以直接内嵌Web服务器,代码打包成jar包,一条命令就可以启动了。这两个是Spring生态系统中最重要的两个,还有很多大大小小的组件,成就了Spring。

虽然从业务代码来说IOC好像没什么用。但是对框架来说,IOC却是基石。比如框架A调用了通过JPA访问数据库,JPA是一个规范,是一个基础的模块,假设是模块B,然后分别有不同的模块实现这个规范,去访问数据库,假设它们是模块C,模块D。那框架A预先不能直接new一个JPA的对象去访问数据库,因为JPA的实现可能不止C,D,还有可能出现E,F,这种情况下,通过IOC把具体的JPA实现注入给框架A,就更加灵活。这样是合理的。

而对业务代码来说,一般都是一个Service对应一个Impl,一个Dao对应一个Impl,都是一对一的关系,除了Spring Boot通过IOC实现Controller的注入有价值,在Controller里面访问Service时,还用IOC的方式注入,没什么必要吧。如果只是为了应用单例模式,那我们把service实现成单例模式也不困难。现在都是用IOC的方式注入Service,还是因为口口相传所以深入人心了吧。

IOC给框架开发者提供了便利的同时,很多时候也让作为使用者的我们感到一些痛苦。

比如我想测试一个Service,按照非IOC的想法,直接new一下这个Service,然后调用对应的函数就可以了。当然前提是这个Service依赖的DAO内部做了够好的封装。但是采用IOC的方式的话,由于所有的依赖都是容器去管理,就只能采用特定的方式去进行测试。比如SpringBoot就得采用下面的代码:

import com.example.TestService;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@SpringBootTest(classes = TestApplication.class)
public class Test {

    @Autowired
    private TestService testService;

    @org.junit.Test
    public void test() {
        testService.execUserGroup(351);
    }

}

这种方式,在我看来,完全不够单元,更应该称之为集成测试。

然后我们项目里面往往还会依赖很多其它的东西,比如配置中心等。这些错综复杂的依赖关系都是通过IOC进行集成到一起,很多东西我们改起来很困难。

比如我们使用了服务发现,当一个请求过来时,先经过网关,然后再到我们自己的服务。然后在debug时,我们希望不影响测试环境,只有我们自己测试的请求才走本地的实例。但由于服务发现的机制,最后往往就是相互影响。服务发现的流程,简单来说就是把实例的ip等信息注册到ZK等注册中心去,然后网关通过注册中心拿到这些信息,当一个请求到达网关时,根据对应的服务名,拿到对应的实例信息,然后通过负载均衡算法转发到对应的实例。而我们本地启动实例时,由于用到了配置中心,用的都是同一份配置,都是测试环境的配置,所以就会把本地的实例注册进去,然后测试环境的请求就会转发到我们本地的实例中。

如果我们是通过application.properties等本地配置方式,而不是通过配置中心的方式,那么就可以直接本地启动时修改配置,让本地实例不进行注册就好了。但是走配置中心就没法这么改。所以最终我们是在配置中心copy了一份测试环境的配置,修改了一些配置,保存为开发环境的配置,解决了这个问题。

很多方案在带来便利的同时,也带来了新的问题,就像走配置中心帮我们解决了修改配置必须重启服务的问题,但又带来了这种debug的问题。

本月了解了一下Flink的Checkpoint以及背压机制。

Checkpoint是由JobMaster发起的,流程为它给每个算子发送Checkpoint请求,并给Source算子生成一个Checkpoint Barrier。然后下游收到Checkpoint Barrier的算子都会进行Checkpoint,将数据持久化到Backend中。最常用的是RocksDB Backend,它的大体实现方式和HBase差不多,都是基于SSTable以及Compaction的机制。很多存算一体的MPP架构的引擎大多也是采用的这种机制,像CK的MergeTree,Doris等。也正是这种根据Key进行排序的特性,让他们能够实现稀疏索引,才能进行高效查询。在各个算子都做完Checkpoint以后,会告诉JobMaster已经Checkpoint完了,然后JobMaster收到所有算子的完成消息之后,会结束这个Checkpoint。

Flink中JobMaster和TaskManager的通信是通过Akka实现的。Akka中内置了DeathWatch这种机制,它允许一个Actor监视其它Actor,当其它Actor死掉时,监视者会收到一条消息,和ZooKeeper中的Watch机制有点像。通过Akka,Flink中对于高可用性的处理就简单很多。如果不通过Akka,那么就需要通过心跳机制自己去检测。虽然也不繁琐,但也得开发不是?

Flink的背压机制说起来也简单,就是通过Netty的水位线机制实现的。但简单带来的副作用就是监控麻烦。目前Flink对背压的监控,是通过查看调用栈的方式来实现的,因为如果背压,那么调用栈在申请Buffer的那一个调用的几率就会升高,我们有时候为了找出消耗CPU大的线程,以及对应的代码,也会先通过Linux命令找到CPU占用率大的线程,然后通过JStack去采样几次,就可以找到有问题的代码。这样去采样其实对系统资源消耗还是蛮大的。所以Flink背压监控只会在打开对应的Web页面时,才会去进行采样,减少对系统的负载。

去年也看过Flink原理方面的文章,但是一直都不明觉厉,看不懂。直到自己研究了Flink的源码,看这些文章才如鱼得水,很清晰。像背压机制,之前一直看不懂,是因为不知道Netty的水位线机制。

了解的另外两个方面,一个是离线混部技术,即通过在线和离线的机器资源共享,达到优化资源利用率,增效降本的效果,另一个是机器学习平台的建设,即数据如何清洗,如何训练,以及最终如何提供服务。关于机器学习平台建设这方面,是因为知道机器学习依赖大数据,但一直不知道数仓建设完成之后,机器学习怎样使用,以及训练出来的模型最终如何提供服务,所以就先了解了一下这个流程。

有两篇不错的文章可以参考:

现在很多数据平台,都是只做到可以取到数仓的数据,但是之后的机器学习能力却做不到。一个完整的数据中台应该也是需要包括这些的吧。

技术分享

参加快手的数据中台分享,收获颇多。

第一个收获是真正了解了数据中台是什么,应该是什么样子。现在是接触到了很多数据产品,但如何把它们连接到一起,发挥作用一直不知道。从快手数据中台总体架构上,看到了它们协同发挥作用。

另一个收获是理清了实时这方面。现在在学习Flink,但其实一直没有实践场景。而我对离线很熟悉,所以就一直用离线的角度去看待实时。但实时和离线完全是两码事,从关注的重点,到技术栈,如数据如何同步,使用什么存储,以及使用场景,都完全不同。

看完这个分享,我把数据中台分成四方面。一方面是数据产品,一方面是OLAP,另外两个方面分别是离线和实时。数据产品是对外提供的服务,有BI,多维分析,AB Test,数据接口服务等。这一块是目前我在做的。数据产品底层涉及到OLAP,传统的有Hive,Spark,HBase等,比较新的有CK,Doris等。那数据从业务数据库或者埋点数据同步到Hive,Kafka等,根据时效性又分为离线和实时,这是两条技术栈。对于离线来说,业务数据一般是通过Datax等工具同步过来到Hive,埋点数据则可以通过Flume同步到HDFS,或者同步到Kafka后面做处理。数据同步完之后,数仓通过Spark进行计算,分层,最终数据存在Hive里面,然后进一步分发到CK/Doris等,为数据产品服务。而对于实时数据来说,有近实时和实时两种。近实时是Spark Streaming那种微批处理的模式,链路和离线差不多。对于实时来说,则是数据统一同步到Kafka,然后Flink处理,输出到Hive/Doris等。难点在于数据如何保证准确性。对于数据同步这一环,埋点数据还是一样的,对于业务数据来说,一般是通过解析binlog然后发送到Kafka,或者直接通过Flink CDC进行处理。如果涉及到流批一体,那么就是数据湖,像Hudi等的工作了。

可以看到,离线和实时都有数据同步的工作,它们用的工具是不一样的。那么可以统一么?快手是自己研发了这么一套工具去做。开源的和商业软件也有不少,如果用的是阿里云的话,可以通过阿里的DataWorks。

实时数据的应用场景还是蛮多的,除了实时数仓,实时GMV等,这些其实还是用近实时实现的,但像推荐系统,风控,千人千面这种,就必须要做到真正的实时了。后面如果有机会,一定要参与真正的实时项目中。

音乐

这个月吉他方面进展不错,屡次达到了预期的效果。

元旦有一晚弹了一次吉他,带去感情的去弹,虽然能听出来还有进步的空间,但很爽,很满意。弹到高潮身体随着音符跳动,脸贴着面板,感受着音符的抚摸。

过去学的全部应用到里面了。还得是放松心态去弹,力度也要刚好,才能弹出来比较好的效果。而不是像工作日晚上回来那样子,只能小心翼翼地去弹,就很不爽,该重的地方重不了。

但工作日晚上可以练习节奏等,虽然弹出来音色不满意,但周末就可以放开去弹,检验成果。

一个爱好,很多时候不是刚开始就喜欢它,而是学习了很久才真正变成爱好。前面练习的过程往往是痛苦的。这时候需要抱着好奇心去学习哪儿做的不好,然后克服它,而不是呵斥自己。本月读的一本《好奇心》就说明了这一点。如果抱着好奇心去看待,那么就会尝试更多可能。而如果是呵斥自己的话,那么人就很紧张,对这件事也就没什么动力了。更不要说做好了。

周中练习的时候,因为一般时间都比较晚,大概九点四十到十点半这个区间,不能声音很大,所以弹起来缺少感觉,不能代入感情。那这段时间就是纯练习,练习学到的技巧。而等到周末可以放开手脚去弹的时候,就会感觉进步巨大,感情饱满,奔放,弹出来的也是音乐,而不是噪音了。

偶然弹了一下钢琴。之前学的曲子都已经忘记了,但是一些记得的片段还是能凭借肌肉记忆弹出来。而且让我欣喜万分的是,不是刻意去弹,对自己没要求,就瞎玩玩,反而手会更放松,按键的时候更加灵活,不容易出错,而且就算不踩延音踏板也能让声音接起来。要弹哪个音也是能从键盘上一下就能找出来。相信半年后重新学习钢琴的时候,这半年的暂停会带来很不一样的体验。

音乐方面,本月进步明显。

首先是吉他方面,学习了一个视频中提到的五种扫弦节奏型。并且在按和弦方面也进步了。现在对扫出来的音色还算满意,但节奏方面,以及稳定性方面还需要进一步提升。同时在其他技术方面也要找视频进一步学习一下。

声乐方面,进步也还可以。订阅了一个线上课程,将之前线下课程中提到的各个点拆开来详细讲解,并练习。在呼吸方面,学习了新的被动呼吸方式,但是用在实战中目前还有一定的难度。在抬软腭方面,以及咬字方面,不太稳定,经常会让声音进到鼻子里。在喉舌分离方面,胸部支撑这方面,训练时能做到,但是实战时也有一定难度。

总结下来就是因为身体还不协调,不习惯,唱的时候关注点过多,没有达到肌肉记忆的程度,导致唱出来的有问题,可以通过多唱来慢慢适应。

但虽然有这些问题,唱出来的还是初步达到了自己喜欢的状态。只是很初步,看到了胜利的曙光,后面还需要刻苦练习。

读书

本月在读书方面收获蛮大的。

一本是《好奇心》。这本书介绍了好奇心对我们的重要性。好奇心的重要性不止体现在创新上面,在交友,学习等方面也发挥着重要作用。比如在交友上,充满好奇心可以让我们对对方的经历充满兴趣,会提更多的问题,更能促进友谊的进步。再比如在学习上,充满好奇心不仅能减少紧张带来的压力,也能让我们学习更多的知识。

另一本是《统一行动》。这本书介绍了穆拉利如何将福特汽车从破产边缘拯救回来。在穆拉利接手之前,福特汽车高管勾心斗角,产品线繁杂,效率低,产品也过时老气,不能满足消费者需求,而在他来了以后,将管理层团结了起来,并大幅缩减产品线,将世界不同国家不同的生产线都统一到了一起,大幅降低了成本,最终让福特汽车重回正轨,实现了伟大的二次复兴。

这本书给我的最大感悟,一方面是要数据驱动,另一方面就是要有愿景,识人,善用。比如比尔福特,他作为亨利福特,即福特汽车创始人的曾孙,跳出来拯救福特汽车,让福特汽车没有在另一位CEO手上走向破产。在他意识到他拯救不了福特汽车的时候,积极承认,从外面找合适的人才来进行拯救。这是很了不起的。把穆拉利找来以后,又全力支持他,不仅在公司里支持他,在家族中也积极替他沟通。他还和公司的CFO积极融资,为穆拉利的改革准备了充足的资金,所以比尔福特就很了不起。但他解决不了高管不团结的问题。引入穆拉利之后,穆拉利积极对管理层进行大刀阔斧的改革,将不适合的人替换掉,同时积极从公司挑选有能力的人担任职位。最重要的是,他通过BRP让高管都团结了起来,让高管们为了公司去努力,而不是为了个人晋升去努力。这些人团结起来很可怕。他们有的和政府关系深厚,有的对制造汽车有很深的见解,有的在宣传上出人拔萃。当他们团结起来之后,发挥了每个人的能力,福特汽车就走上了正轨。

穆拉利会在一些公司人才要离开的时候,让他们留下来。而这些人很多都确实留下来了。他们想离开的原因很简单,就是认为福特汽车不值得自己待下去。而穆拉利来了以后,他们看到高管终于为了福特汽车复兴努力,他们也就不想离开了。这个跟我们现实生活也很有借鉴价值。就像一家公司有人跳槽,有人摸鱼,其实每个人都是很有价值,没人想摸鱼,而管理者不能提供一个愿景,不能发挥人的价值,那再优秀的人才也待不久吧。每个员工都愿意为了自己认同的愿景去奉献自己的时间,自己的精力。而不是令人恶心的卷,没有意义的加班,熬时长。

穆拉利在公司内外,也充分透明。公司营收怎么样,有哪些风险,他们做了什么努力,都让员工知道。只有员工了解这些信息,才会认识到愿景,并奉献个人价值。而又有多少公司做到了这些呢?

这本书是值得反复去读,去学习的。

理财

想方设法节流。

基金方面大跌,跌幅有7.68%之巨。虽然一直喜欢基金下跌,然后买入,但这次第一次质疑自己是不是眼光有问题,选的赛道是否正确。最终还是选择相信自己的判断,继续买入。

在生活一些其他方面也选择节省资金,消费降级。比如用骑自行车代替打车出行,预估一个月省几百块。比如控制在饮食方面的开销,预计一个月也能几百块。比如用自己打扫卫生的方式替代请保洁阿姨,预估每月能节省两百块。

等过段时间换了新的居住地点,预计每月也能节省不少资金。

考虑到未来,资金方面压力很大。

二月份计划

  • 切记心平气和,享受过程,而不是只看结果
  • 阅读完《智造中国》(✔)
  • 阅读完《贝佐斯传》(✔)
  • 阅读完《硅谷钢铁侠》(✔)
  • 吉他节奏练好,并找视频学习一下其它的扫弦方面的知识
  • 声乐被动呼吸练好,喉舌分离练好,并充分应用到歌唱中。上完线下声乐课程