博客

  • 数据中台

    最近要开展新的项目了。预留出这个文档。

    首先来讲一下

    数据中台是由于微服务逐渐兴起而出现的一种新的数据

    qData 项目

    qData 项目

    API 鉴权

    在 qData 中,外部应用要访问发布的 API,要使用应用管理模块。也就是说,把注册和上线了的 API 进行发布管理。通过官方手册可知,首先我们需要通过应用编号(client_id)和应用密钥(client_sercert)获得有时效的 client_token,并在下次请求中携带这个 token,才能够正常访问内容数据。

    查询数据差异问题

    但在实际过程中,我遇到了这样一个问题。前端界面中,数据服务中的接口调用功能可以返回正常的数据,但是使用项目管理进行发布的 API 在 ApiFox 中却返回的不是同一批数据。因此,要开始排查具体的情况问题。

    我打算通过阅读源码分析这个问题。

    qData 是一个前后端分离项目。既然两种路径得到的结果不一样,那么我肯定要先找到对应的路径具体是位于哪个文件中,调用了那些函数,具体的配置有什么差异而导致了这种结果。

    前端的接口调用的位置是非常容易找到的,根据 URL 地址(http://{HOST}:{PORT}/#/ds/api/edit?id=2{id})可以很快定位到对应的文件

    export function queryServiceForwarding(data) {
        return request({
            url: '/ds/api/queryServiceForwarding',
            method: 'post',
            data: data
        });
    }

  • 谈论云原生

    实际上,地方国企和互联网企业的使用的技术是大不相同的。在这里,我们强调的是一个项目的闭环,面对用户时细节上的准确性和专业性,这当然是非常重要的。但是在互联网企业里面有一个我在当前环境完全接触不到的一整块核心技术——⌈云原生⌋、⌈高并发⌋、⌈架构设计⌋。

    也许在我没有来到这里之前,这个词语对我来说还是术语层面的,但是在实际的开发学习过程中,我发现了为什么会需要这些,以及为什么会不需要这些。

    现代的开发工具已经使提供一个简单的服务变得非常容易了。所以这从来不是什么问题,技术、设计和实现细节上会各有高下。但是,面对真正的复杂的难题,例如说大规模集群访问的情况下,这些就是非常需要去讨论和研究的情况了。

    未来,我打算踏入互联网行业作为一名运维开发工程师,那么什么技术才是我的⌈瑞士军刀⌋,我对软件架构和集群的理解是什么,如何保障可信赖的服务,这些都是我当前所没有怎么接触和学习到的,而必须要重点花时间去学习和理解的。

    最近我在开发组做事。写了一些 Java 的后端的简单代码和 Vue3 的前端界面。逻辑上它们都是很直观的。开发的工作更加细致,重点在于完善一个服务内部的自洽。虽然说我的核心方向并不打算往那方面发展,但是这些最基础的软件工程经验——技能的多样性、多元化和一个整体的视角来说,这都是会对我的技术框架理解上非常有益处的。

    这些工具都有非常强的抽象属性在里面。比如说 kafka。我也许会出个专题专门讲一讲其中的内容。就是抽象的事物有助于我们理解其「关系」。

  • 杂谈-原型设计

    我突然有了一种⌈啊哈⌋的感觉。

    我突然知道为什么非关系型数据库现在为什么如此受欢迎了。首先是不断变更的业务需求,对快速产出原型的急迫,也因此学习掌握一门非关系型数据库的应用方法是非常重要的。

    程序设计也许应该跑在数据库后面,这是我的今天的一点感叹。当然同样重要。只是我发现单纯产出产品原型时,也就是主要只写前端的一些页面的时候,实际上大部分内容就是围绕用例数据展开编写的。这条办件我需要放在哪里,展示什么样的内容,最终无论是体系框架如何设计的、后端鉴权评估如何处理,最终都是要回归这个直观的页面的。

    但是此时的用例数据往往是非常零散的,也就是说,是不成体系的。对于一个数据项,大到从开始到结束的生命周期,小到具体某一个流程、某一个节点的状态机情况,都是定义模糊甚至于没有定义的。

    没有一个非常优秀和有经验的数据库设计师、产品经理来给出清晰完整的文档时,开发人员面临的就是这样的情况。我需要自己梳理产品面为我提供的这些零零散散的数据,找到它们对应的业务模块以及映射到计算机中的具体位置。在真正撰写之前,这些必备的工作是我自己心中要有数的,因为我已经意识到他们并没有为我提供这样一个条件,但是为了减少我自己的工作量,我来做好这些处理工作,翻译、修缮为一个骨架是非常重要的。

    我想写代码本身就是在做一个翻译工作。现在代码本身确实变得廉价了,但是好的代码仍然很少,好的、能够明确传达需求的文档更少。我想起一个好笑的名言,⌈Talk is cheap. Show me the code⌋,现在甚至可以变成了 ⌈Code is cheap. Show me the prompt⌋。不过核心并不是代码不代码的问题,而是在设计之初这些定义是否足够柔韧、逻辑贯通、可操作、可理解、可协作。

    没有人能理解的语言就不是好语言,除非有天才能够翻译出它的精妙之处,否则它只能湮没在历史的沙漠里,这是人类社会中无法避免的事。

  • 办件生命周期论

    水35公升、碳20公斤、氨4公升、石灰1.5公斤、磷800克、盐250克、硝石100克、氟7.5克、铁5克、硅3克,还有其余15种微量元素……这就是一个成年人身体的组成物质。

    即使是小孩,只要有这些材料,花不了几个钱就能在市场上买齐。人这种生物,原来是可以用这么便宜的价格买到的吗?

    ——《钢之炼金术师》

    了解一个软件,作为认识者而不是创造者,先入为主地看向一个产品原型的业务时,我认为一个非常好的视角是⌈生命周期⌋的视角。

    首先从⌈状态⌋描述与定义它:

    一个办件是如何诞生的?它会经过怎样的状态层?在这些状态层中,它又会有哪些对应的状态枚举,最终,它会有什么样的生命周期结束?

    再是从⌈流转⌋看到它的变化:

    • 是什么事件(Event)或动作(Action)导致状态发生改变?
    • 描述处理过程

    是什么事件(Event)或动作(Action)导致状态发生改变?

    最后是

  • Java 反射

    阿莱夫的直径可能只有一英寸,但所有的空间都在那里。我从宇宙的每一个角度清楚地看到了它。我看到了地球上所有的镜子,但没有一个能映照出我;我看到了一只手精致的骨骼结构;我看到了自己黑色血液的循环;我看到了爱的结合和死亡的改变。我从各个点和角度看到了阿莱夫,在阿莱夫中我看到了大地,在大地中又看到了阿莱夫。我感到头晕目眩,流泪了,因为我的眼睛看到了那个秘密的、难以想象的宇宙……

    ——《阿莱夫》

    路易斯·博尔赫斯(阿根廷作家,1899-1986)

    在 UNIX/Linux 系统的系统设计哲学中有一句非常著名的原则,那就是⌈一切皆文件⌋。BIOS 引导系统是文件,输入的 bash 命令是硬盘中存储的文件,用户的权限配置也是文件,我们使用文件操作文件。

    在 Java 中,我们也可以有这样的信念,那就是⌈一切皆对象⌋。编写的类是一个对象,操作的变量是一个对象,一个数据类型是一个对象。统一的抽象类型定义使我们可以更好地理解

    反射基础

    RTTI(Run-Time Type Identification)运行时类型识别。

  • 术语和逻辑

    Die Grenzen meiner Sprache bedeuten die Grenzen meiner Welt.

    语言的边界就是思想的边界。

    ——《逻辑哲学论》

    维特根斯坦(德国哲学家,1889-1951)

  • 在 Java 建构、映射一个实体

    这一篇会主要围绕一些技术细节展开,核心任务是构建一个完整的、自洽的且具有完整生命周期的实体类。

    本篇内容为⌈设计人员的增删改查功能⌋。

    在本 Spring Boot 框架中,主要使用的包为:

    • Mybatis-plus
    • Lombok

    Web 开发核心注解

    按照主要负责的功能,分为多个层次来认识这些:

    • 启动与配置
      • @SpringBootApplication
    • 流量控制层
      • @RestController
      • @RestMapping
        • @GetMapping/@PostMapping/@PutMapping……
    • 业务处理层
      • @Service
    • 数据交互层
      • @Mapper,对具体查询语句的定义
      • @TableName/@TableId/@TableField,Mybatis 包中对数据库表单的实际映射模式设计
      • @Data,对 private 的变量实现存取更新操作,不需要繁复代码
    • 额外工具引入
      • @Resource,类的自动注入,不需要再额外写代码 new 实例化

  • 为什么我们需要序列化?

    汇编语言中有一个非常有趣的内容,如果使用 gdb 去调试 /bin/ls 这个程序,可以看到直接进入首行地址是不行的,因为在内存中,这个实际上是一个相对地址。

    这也是为什么我们需要序列化。

    因为按照正常的逻辑思路来说,为什么 Java 需要序列化多此一举呢?对象在内存中也是一个正常地一串二进制数据,为什么不能直接复制这些内容传输出去呢?

    首先,这有点类似于我前面提到的像汇编中同样的问题。在 JVM 中,进程访问的是虚拟地址。如果我的电脑内存中存储的这个虚拟空间地址中的绝对地址,直接复制传输到另外一个人的电脑中,它的 JVM 启动并访问了这个绝对地址,也许会出大问题!

    所以一个包装好的,内部处理好的完整的、精致整洁的处理类——序列化,它在安全意义上是非常重要的!

  • 面向开发者的 RPC 指南

    If I have seen further than others, it is by standing upon the shoulders of giants.

    如果我看得比其他人更远,那是因为我站在巨人的肩膀上。

    ——艾萨克·牛顿(英国物理学家,1643-1727)

    术语解释

    RPC(Remote Procedure Call,远程过程调用)框架是一种技术思想,旨在让客户端能够像调用本地方法一样,无需关注底层网络细节即可调用远程服务器上的服务。

    当我们在谈论 RPC 时,我们在谈论什么

    如果说层层叠叠的网络协议技术让孤立的计算机能够相互通信,那么 RPC 技术则是在原本通信的基础上更加往前走了一步,我们使通信变得更加简单可用。

    当我们回顾互联网的发展历史,不难发现,总有天才般的珠玉在前,它们前沿而充满了令人惊叹的早慧,但往往令人只能望而却步。但是随后又会有更多的天才参与其中,使之推广——或者说更加成熟可理解,让更多的开发者能够参与其中,从而让更多人能够参与到对互联网这一人类历史上最浩大的工程协作中来。

    RPC 的核心思想

    RPC(Remote Procedure Call,远程过程调用)框架是一种技术思想,旨在让客户端能够像调用本地方法一样,无需关注底层网络细节即可调用远程服务器上的服务。

    以 Java 程序为例。

    我们知道,一个程序要在计算机中执行,它首先要以一个进程的形式存在,也就是说,要从磁盘加载到内存中,由内核进行管理。

    调用本地方法

    // 本地函数
    int calculateSum(int a, int b) {
        return a + b;
    }
    
    // 在同一个程序中调用
    void main() {
        int result = calculateSum(5, 10); // 1. 直接调用
        printf("结果是: %d\n", result);     // 2. 获得返回值并使用
    }

    这里,在内核中实际上发生了如下结果。

    • 程序从原本的磁盘中加载进入内存,成为了一个由内核管理的进程,具有完整的生命周期。
    • 内核调用 Java 编译器进行了编译处理,生成了对应的 Java.class 字节码。
    • JVM 运行该程序。JVM 与内核进行协议和沟通。
    • 寄存器和 CPU 进行汇编语言层面的计算处理。
    • 所有行为都在本地的内存空间(也就是内存条)中执行完毕,得到返回的结果。

    可以看到,上述所有的操作都是在本机中运行的,虽然涉及到不少底层的原理,但是 JVM 都为我们隐藏起来了,实际上用户看到的直接是一个返回的结果。

    一般远程通信

    具体代码

    一般来说,在不使用高级语言提供的框架时,我们需要自己来编写建构通信协议的具体对象,并交付给内核进行处理。一个进程一般监听一个端口,进程间的通信有多种方法,但是远程通信一般使用 socket(套接字)。

    也就是说,我们具体要考虑和编写的内容如下:

    • 使用 Java 的 Socket 类建立 socket 对象。否则我们需要通过 JNI 调用原生 socket API。
    • 使用 Java 的序列化类将对象转为字节流。因为内核以及网络驱动中只处理字节数据(这仍然是一种抽象。在更底层,数据的本质实际上是一些电路信号)。
    • 使用 Java 的 Buffer 类建立和配置缓冲区。socket 实际上也是一个位于内存中的进程,它的接收缓冲区和发送缓冲区同样需要内核为其分配内存大小,在等待到对应的中断信号切换至内核态时,会通过驱动与网络硬件进行数据交互。很直观的,缓冲区会影响单次数据传输的大小,从而间接影响到传输效率。

    RPC 调用过程

    // 在客户端代码中
    // remoteCalculator 是 RPC 框架生成的一个代理对象
    int result = remoteCalculator.calculateSum(5, 10); // 看上去和本地调用一模一样
    printf("远程计算结果是: %d\n", result);

  • 芋道代码解析-后端(1)-安全与鉴权

    主要使用的框架:Spring Boot Security

    前置基本术语和知识

    • 网页服务器(Web Server)一词通常包含两个层面的意义:
      • 硬件层面:指一台负责存放网站文件,并透过网络提供服务的电脑。它借由超文本传输协议(HTTP)与客户端(一般是指网页浏览器)进行资料交换。
      • 软件层面:指一个运行于电脑上的服务器程序(如 Apache, Nginx 或 IIS)。其核心功能是接受用户的请求,并回传相对应的网页内容(如 HTML 文件,图片或资料)。
      • 每一台网页服务器(硬件)至少会执行一个网页服务器程序(软件)。在这篇博客里面,我们主要使用它的软件层面的定义。
    • Servlet(Server Applet)
      • 全称 Java Servlet。是用 Java 编写的服务器端程序。其主要功能在于交互式地浏览和修改数据,生成动态的 Web 内容。
      • 当 servlet 被部署在应用服务器(主要是 Web 服务器)中(服务器中用于管理 Java 组件的部分被抽象成为容器),由容器控制 servlet 的生命周期。默认情况下,在第一次请求的时候,servlet 会被加载实例化(注意这个⌈被动⌋的定义)。初始化并跟着正式执行方法后,它会被常驻,知道服务器关闭或被清理时执行一次销毁方法后实体销毁。
      • 它的工作模式主要如下:
        • 客户端发送请求至服务器(由 Web 服务器管理)
        • 服务器启动并调用 Servlet,Servelt 根据客户端请求生成相应内容并将其传给服务器。
        • 服务器将响应返回客户端(由 Web 服务器管理)
      • HttpServlet 实际上是 Java 服务器页面(Jakarta Server Pages,JSP)的前辈。也就是说,更加擅长于动态生成 HTML 的 JSP 脚本实际上取代了 servlet 中的交互模块。
    • 传统的 JavaWeb 三板斧
      • Servlet
      • 过滤器(Filter)
        • Servlet Filter 是在 Serlet 2.3 规范中加入的功能。过滤器可以动态地拦截请求和响应,以变换或使用包含在请求或响应中的信息。
        • 某些情况下,我们需要在业务代码执行前获取请求中的某些信息,就可以使用过滤器。
        • 简而言之:
          • 在客户端的请求访问服务器中的资源之前,拦截这些请求。
          • 在服务器的响应发送回客户端之前,处理这些响应。
        • 如果我们使用一个过滤器不能解决业务需求,那么就用多个,多个过滤器可以对请求和响应进行多次处理。多个过滤器组合而成的就是过滤器链(Filter Chains),请求会依次按照过滤器的顺序一一进入,直到最后一个过滤器为止。当返回响应的时候,也是一样,从最后一个过滤器依次传递到第一个过滤器,最后到达客户端。
      • 监听器(Listener)

    这个博主的解析会更加详尽生动,具体可以看以下回答。

    为什么要有 Servlet ,什么是 Servlet 容器,什么是 Web 容器? – bravo1988的回答 – 知乎
    https://www.zhihu.com/question/585070524/answer/2939949226