Leon Ding Blog
type: channel
link: https://blog.leonding.me/
description: 预测未来，不如创造未来！
items: 基于 LogStructuredFS 的 NoSQL 设计与实现
items: 密码学的应用之 TLS 协议
items: Java 原生化的未来 Graal AOT 编译器
items: 在 Ubuntu 上安装 MySQL8.x 版本
items: 使用 WSL + VS Code 现代 Java 开发
items: 函数式编程的思想和优势
items: 编码从来不是一件简单事情
items: 独立开发者如何起步
items: 一个合格的站长应该具备的能力
items: 开发基于网络程序该考虑什么？

基于 LogStructuredFS 的 NoSQL 设计与实现
type: item
date: 2024-12-22T23:45:00+08:00
description: NoSQL 的抽象数据库系统的核心作用在于高效地存储和管理数据，并支持从海量信息中快速检索所需记录。传统的 SQL 关系型数据库在数据规模持续增长的情况下，容易遭遇单机性能瓶颈。目前提升性能的主流做法多依赖垂直扩展增强单机硬件能力，而非水平扩展增加节点以线性扩容。由于其架构限制，关系型数据库在弹性扩展性方面存在障碍，难以充分适应多节点分布式环境。同时为满足 ACID 事务特性，这类系统在支持分布式事务处理时也面临一定的技术挑战。相比之下，NoSQL 数据库更适用于大规模数据存储和高吞吐、低延迟的应用场景。由于 NoSQL 不受固定数据结构和关系的限制，具有更灵活的扩展能力，并且支持节点的动态扩展，从而更好地适应分布式环境。无论是关系型 SQL 数据库还是 NoSQL 数据库，它们都有一个共同的核心组件存储引擎，这是数据库设计与实现的关键核心部分。目前数据库存储引擎可以分为两大类，一类是基于内存的 （In-Memory）实现，例如 Redis 和 Memcached 这类的将数据全部存储在内存中；另外一种则是类似于 MySQL 的 InnoDB 和 LevelDB 这种将数据存储在磁盘中。两者都有各自的优势和劣势，基于内存的存储引擎通常存储的数据量较少，但其访问速度往往比基于磁盘的存储引擎快好几个数量级，而基于磁盘的存储引擎访问速度较慢，但存储数据量要比内存引擎多几个数量级。通常内存的价格要比磁盘的贵很多，这也是在选择存储硬件时要考虑的成本问题。存储引擎的开发者需要权衡读写性能和硬件成本的同时，重点在于如何高效存储数据和检索数据。数据库的存储引擎相当于在操作系统文件系统之上，建立的一套用于数据组织、存储与查询的逻辑管理层，最终实现是依赖于操作系统的文件系统 API 接口的，最终数据是被持久化到磁盘文件中保存，这个就对应着 ACID 中的 Durability 数据持久性。持久性（Durability）是存储引擎最为至关重要的功能实现，目前很多基于内存版本的 NoSQL 数据库例如 Memcached 和 Redis 在这方面做的就比较差，在 NoSQL 服务器运行过程中突然崩溃断电就会导致数据没有被持久化存储的情况，从而导致数据丢失影响到上层的业务程序。[scode type="yellow"]所谓的持久性就是如何将计算机运算的结果持久化存储在一些物理介质中；持久性是指系统能够将状态变更的结果可靠保存，使其在系统重启或故障后仍然能够恢复。[/scode]存储是现代计算机最核心的功能之一，计算机中的程序和文件以数据块的形式保存在各种存储介质中，主要可分为持久化存储和非持久化存储两类。非持久化存储通常指内存（如 RAM），其特点是在断电或关机后，数据会被清除。尽管容量通常小于持久化存储，但其高速读写性能使其广泛应用于程序运行过程中的数据处理和临时缓存。相比之下，持久化存储如硬盘、固态硬盘、DVD、磁带等，能够在断电后保留数据，适用于长期保存程序和文件信息。鉴于数据可能存储在多种物理介质（如磁盘、SSD、光盘等）上，主流的操作系统需要提供一致的抽象接口，以屏蔽底层硬件差异，使用户能够高效、统一地进行文件的读写、创建与删除等操作。当前主流操作系统均实现了完整的文件系统，开发者可以通过如 POSIX 这样的标准 API 接口来访问和管理存储设备，而无需关心具体设备的物理实现方式。尽管操作系统的文件子系统通过标准如 POSIX 提供了对存储设备的访问能力但仍存在一些限制；例如存储性能受限于设备的物理读写速度，同时文件系统通常采用页缓存 Page Cache 或写缓冲机制，这意味着数据写入操作并不会立即持久化到磁盘，一旦系统发生故障或意外断电，可能会导致数据丢失，影响系统的可靠性。基础架构综上所述，针对这些场景的问题 UrnaDB 采用了基于 Log-Structured Megre Tree 日志结构化文件系统的存储引擎实现，存储引擎会以 Append-Only Log 的方式将所有的数据操作写入到数据文件中。同时 UrnaDB 为了高速查询检索数据记录，存储引擎会将数据记录索引信息全部保存在内存中，从而实现高效快速的查询目标数据记录。这样的设计的好处是能以磁盘最大写入性能进行写入数据，并且还能减少读取磁盘索引所需要的时间，通过一次索引定位来读取数据记录，写入和查询流程图：其核心持久化机制基于预写日志 Write-Ahead Logging 简称 WAL ，在对数据执行任何操作前，都会先将操作记录写入 WAL 日志文件。WAL 文件不仅承担持久化的角色，也作为主要的数据存储载体。在数据库进程崩溃后，只需从 WAL 日志中顺序读取各条 Segment 记录，即可高效恢复内存中的索引结构。在 UrnaDB 中对这些 WAL 数据文件有一个统称为…

密码学的应用之 TLS 协议
type: item
date: 2024-07-26T19:14:00+08:00
description: 在现代社会网络通信的隐私和安全性已成为人们日益关注的重要话题，无论是浏览购物网站、在线银行交易，还是日常的社交媒体互动，我们的个人信息都面临着潜在的风险和威胁。在这篇文章中，将探讨在浏览购物网站时，如何保护我们的隐私和确保信息安全，是谁在保护我们的数据隐私和通信的安全性。在谈论网络通信的安全性之前，让我们回顾一下人类在通信保密性的发展史。通信的保密性自古以来就是一个重要的问题，人类不断发展新的方法来保护信息的隐私和安全，最早的加密技术可以追溯到古代文明，古希腊和罗马使用了各种简单的加密方法，如凯撒密码（Caesar Cipher），其中每个字母被替换为其后固定位置的字母。凯撒密码也称为移位密码（Shift Cipher），是以古罗马将军 Julius Caesar 的名字命名的，据说凯撒在军事通信中使用这种密码来加密敏感信息。在战争期间使用这种加密方法来保护军事命令和机密信息，确保即使信息被截获，敌人也无法轻易破译，凯撒通常使用 N <= 25 位移位（即每个字母向后移 N <= 25 个位置）来加密消息。当消息被敌军获取了，敌军在不知道消息加密的方式情况下，只能得到一堆胡乱排列的字母。凯撒密码的数学公式为如下：凯撒密码的核心原理是通信的双方有一个固定字母顺序表，以这个固定顺序的字母表作为消息加密的基础，通过将原本的消息进行移动位置的方式进行加密。凯撒密码易于破译，尤其是使用频率分析法，通过统计文本中各个字母出现的频率，可以很容易地推测出移位量并解密消息。比如，在加密文本中可以通过寻找加密文本中最常出现的字母来推断移位量，当找到这个字母偏移量就很容易破解出来密文信息了。[scode type="blue"]下面是通过 Java 自定义码表实现凯撒密码加密和解密逻辑：[/scode]public class CaesarCipher { private static final char[] ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray(); public static void main(String[] args) { String text = "HELLO"; int shift = 3; String encrypted = encrypt(text, shift); String decrypted = decrypt(encrypted, shift); System.out.println("Original: " + text); System.out.println("Encrypted: " + encrypted); System.out.println("Decrypted: " + decrypted); } // 加密函数 public static String encrypt(String text, int shift) { StringBuilder result = new StringBuilder(); for (int i = 0; i < text.length(); i++) { char ch = text.charAt(i); if (Character.isUpperCase(ch)) { // 拿到对应字母的下标 int originalPosition = findPosition(ch); // 找到偏移量之后字母下标，A 至 Z 之间循环 int newPosition = (originalPosition + shift) % ALPHABET.length; result.append(ALPHABET[newPosition]); } else { result.append(ch); // 非字母字符不变 } } return result.toString(); } // 解密函数 public static String decrypt(String text, int shift) { StringBuilder result = new StringBuilder(); for (int i = 0; i < text.length(); i++) { char ch = text.charAt(i); if (Character.isUpperCase(ch)) { int originalPosition = findPosition(ch); int newPosition = (originalPosition - shift + ALPHABET.length) % ALPHABET.length; result.append(ALPHABET[newPosition]); } e…

Java 原生化的未来 Graal AOT 编译器
type: item
date: 2024-07-17T22:35:00+08:00
description: Java 这门语言与生俱来的显著特性就是“一次编译，到处运行”，这种功能得益于 JVM 平台的支持，Java 程序通常通过将其打包为 JAR 或 WAR 包，并依赖 JVM 和 Servlet 容器来运行。其底层运行时 JVM 采用 JIT（即时编译）模式来执行程序代码，JVM 会在运行时进行编译优化和动态执行代码，这通常会导致较高的内存占用。这样的好处是采用 JIT 可以热更新和热部署程序，并且 JVM 可以在运行期间对程序进行动态分析，来实时优化程序以达到最好的性能状态。Cloud Native 面临的问题随着云计算的发展，服务端程序的运行和部署方式发生了巨大变化，特别是在云原生（Cloud Native）场景下，服务器程序被打包成一个个容器运行。在这种环境下，传统方式容器化的 Java 程序面临着打包后的镜像文件过大的问题。镜像文件中需要包含 JRE 和各种第三方库，导致镜像体积庞大，占用更多的磁盘空间。目前云计算的计费方式也发生了改变，采用按需分配的实时计费方式，根据程序的内存占用和运行时间计费。这与传统 IDC 自托管式机房中长时间运行服务器程序形成鲜明对比，按需分配的计费方式显然更具性价比，因此优化 Java 程序在容器环境中的运行方式变得尤为重要。Serverless 面临的问题传统的 JVM 工作原理要求程序员先将编写的 Java 程序打包为 JAR 或 WAR 包，这是普通 Java 开发者所能接触到的程序编译和构建阶段。然而当程序运行起来之后，底层的 JVM 会进行许多复杂的操作，包括内存管理、垃圾回收、即时编译等。需要先启动 JRE 中的 JVM 来加载 Tomcat 服务器所需的字节码，再加载 WAR 包中的字节码文件到 Servlet 容器中运行服务，这个过程非常耗时。下图为整个传统 Java 程序从源代码到程序运行各个阶段的工作流程：下图为传统的 Java 运行时执行程序的生命周期，首先应用的开发者将 *.java 源代码编译为 bytecode 和 jar 依赖包通过 maven 构建打包为一个单独的 jar 包。当执行 java -jar xxx.jar 命令时，首先要启动 JRE 中的 JVM 程序，因为 JVM 本身也是一款单独程序对应着 $JAVA_HOME\bin 目录中的 java ，当 JVM 启动之后会在系统中创建一个进程，将要运行的 jar 程序包和 JRE 标准库中 class 文件加载入 JVM 内存中，并且找到 main 方法开始解释执行。当程序运行一段时间之后 JVM 充分获取了 Java 程序的一些执行数据信息，开始对程序进行 JIT 及时编译优化程序，当程序被 JIT 优化之后的程序才能达到和 C/C++ 这种直接编译为 Native Code 程序的性能。在云计算推崇的 Serverless 架构场景下，Java 的 JIT 模式并不适合，Serverless 应用程序需要能够快速启动以响应事件驱动的请求，并且在空闲时自动关机销毁，释放在服务请求期间所使用的内存。然而，这种需求与传统的 Java 程序运行模式背道而驰，使得 Java 在 Serverless 场景中显得不适用，这就是被业界称为的 冷启动 问题。为了解决这一痛点，Oracle Labs 推出一款新的 JDK 产品 GraalVM JDK ，相比传统的 JDK 该产品内置 Java 静态编译工具和一款新的虚拟机，和传统的 JVM 不一样 GraalVM 是支持多语言进行混合编程的虚拟机，这也是它一大亮点。GraalVM 中的静态编译工具不仅是支持将 Java 编译为二进制可执行文件，还支持 JavaScript 和 Python 等其他语言的编译优化功能，本文只关注于 GraalVM 对 Java 程序进行 AOT（Ahead-Of-Time）编译的解决方案。使用 GraalVM 对 Java 程序进行 AOT 编译的解决方案，可以显著缩短启动时间并降低内存占用，使 Java 程序更适合在 Serverless 环境中运行，通过 AOT 编译，编译器生成一个 Native Code 二进制文件，即对应平台架构的机器代码程序。这种方式无需像 JIT 那样依赖于 JVM 运行，在内存和磁盘占用上有显著改善。下图为 AOT 编译工作流程，在编译阶段会程序进行静态分析，通过它内置工具分析 Java 源代码中依赖关系，将其所有依赖和代码执行逻辑都进行提前编译为机器代码，缺点也很明显可能会失去在 JIT 模式下的动态反射功能。如上图，在 AOT 模式下将在运行时的过程放到在程序构建阶段，构建阶段会对 Java 程序执行静态代码分析和依赖可达性分析，将其所有的依赖软件包都编译生产所属平台对应的可执行二进制文件。AOT…

在 Ubuntu 上安装 MySQL8.x 版本
type: item
date: 2024-06-23T19:29:00+08:00
description: 在 MySQL 8.x 带来了许多新特性和改进包括 JSON 数据类型和 Java Script 作为 JavaScript Shell 支持，可以直接使用 JavaScript（JS）作为数据库查询语言或存储过程的语言，更好的性能和安全性，例如连接过程中出现的 Public Key Retrieval is not allowed 错误提示，这些都是新版本特性。本文将记录完整的基于 Ubuntu 最新版本中安装 MySQL8.x 版本的过程。安装和配置在安装 MySQL8.x 之前可以尝试先更新一下操作系统和软件包，执行下面命令可以更新：sudo apt update && apt upgrade -y更新系统和软件包版本需要一点时间，这个过程取决于你网络和所使用的 apt 软件包数据源地址，数据源配置文件路径 /etc/apt/sources.list。[scode type="yellow"]如果不确定系统中是否安装其他版本的 MySQL 版本，可以先执行清理命令。清空完成之后，提供 apt search mysql 包管理工具搜索最新的 MySQL8.x 软件安装包，最后通过 apt install 执行进行安装。[/scode]sudo apt remove mysql* sudo apt autoremove sudo apt autoclean清空完成之后提供 apt 所提供的 MySQL 软件包进行安装：sudo apt search mysql sudo apt install mysql-*安装完成之后，可以使用 sudo mysql_secure_installation 来初始化 root 用户的密码，这是 MySQL 新版本所提供的初始化数据库程序：sudo mysql_secure_installation[scode type="yellow"]如果需要让 MySQL 服务器能被远程的用户提供客户端访问，需要配置修改服务器所绑定监听的 ipv4 地址，默认是本地环路地址，在这个 /etc/mysql/mysql.conf.d/mysqld.cnf 中找到 bind-address 的值修改它为 0.0.0.0 即可。[/scode]# changed. Ref https://dev.mysql.com/doc/refman/8.0/en/server-system-variables.html#sysvar_tmpdir # tmpdir = /tmp # # Instead of skip-networking the default is now to listen only on # localhost which is more compatible and is not less secure. bind-address = 0.0.0.0 #这一行 ✅ mysqlx-bind-address = 127.0.0.1通过上面的配置目前 root 还是只能通过本机的客户端进行连接，有远程登录需求的可以配置一个远程用户并且允许远程登录，并且设置初始化密码，我这里开放的是 root 用户，执行下面命令：GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' IDENTIFIED WITH mysql_native_password BY '新密码' WITH GRANT OPTION;解决安全连接问题在 MySQL8.x 版本中如果要使用正常的密码连接到 Server 中连接过程中出现的 Public Key Retrieval is not allowed 错误提示，这个是新特别特意到安全功能增强，如果要使用这种方式连接必须要配置 SSL/TLS 密钥对进行连接。[scode type="yellow"]指定用户使用的密码验证方法 mysql_native_password ，这让 MySQL 使用的默认身份验证插件，它支持基于用户名和密码的常规身份验证机制，执行下面命令使用默认密码验证方式进行连接：[/scode]ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY 'new password';执行上面的 SQL 之后，刷新一下 MySQL 授权表：flush privileges;最后记得在服务器上允许开放 3306 端口数据包的连接。

使用 WSL + VS Code 现代 Java 开发
type: item
date: 2024-06-17T16:55:00+08:00
description: 在我的 Java 编程生涯中使用过很多 Java 开发环境，从编写第一个 Java 程序使用的 Notepad++ 到 Eclipse 再到 IntelliJ IDEA。能编写 Java 的 IDE 产品有很多，如何选择一个好的 Java 开发环境？这对于从事 Java 程序设计的程序员来说是一个重要的问题。选择一个合适的 IDE 不仅能提高开发效率，还能减少调试和排错的时间。本篇文章将分享一些选择 Java 开发环境的关键考虑因素，并介绍如何通过 WSL + VSCode 打造一个混合开发基础 IDE 环境来开发 Java 程序。从事 Java 程序开发的程序员主流都是做服务器端程序开发。在编写程序项目源代码阶段，通常使用 Windows 系统的 PC 进行开发工作，而当程序部署上线之后，往往运行在 Linux 系统环境中。这就要求我们不仅要熟悉 Windows 系统，还要熟悉 Linux 系统，才能更好地编写和调试 Java 程序。这也是选择开发环境时需要考虑的一个重要因素，因为一个好的 IDE 应该能够方便地在不同操作系统之间切换，并提供良好的跨平台兼容性。目前主流的 Eclipse 和 IntelliJ IDEA 都是重量级的 Java 集成开发环境，并且针对不同系统架构开发了对应的版本。程序员在 IntelliJ IDEA for Windows 版本下开发，就必须使用 Windows 下的一些开发和调试技巧，而在 IntelliJ IDEA for Linux 中又需要掌握一些 Linux 下特有的功能和设置。那么有没有一种折中的解决方案，可以在 Windows 开发 Java 程序并且能获得 Linux 系统中的一些开发体验和特有功能呢？答案是有的。[scode type="green"]使用 Microsoft 推出的轻量级 IDE 产品 VSCode 和最新版本 Windows 系统所提供的 WSL（Windows Subsystem for Linux）功能就可以实现这种开发模式。我个人认为此种开发方式是更符合现在 Java 程序开发的方式，虽然 JVM 为 Java 程序提供强大跨平台能力，能让 Java 程序一次编写到处运行，但在开发阶段会不可避免的使用所在的开发机上特有命令和功能。对于需要跨平台开发和调试的需求，使用 VSCode 与 WSL 的组合可以在 Windows 环境中提供类似 Linux 的开发体验，能在够让 Windows 和 Linux 系统之间文件和命令互相操作，这种方式不仅简化了环境切换还能提高开发效率，目前最新版本的 IntelliJ IDEA 的 UI/UX 设计越来越像 VSCode 了，所以个人还是更推荐 VSCode 作为基础的 IDE 产品。[/scode]基础 WSL 配置使用 WSL 和 VSCode 作为基础开发环境，最低要求操作系统为 Windows 10。如果使用 Windows 11 则更为理想。在 Windows 11 系统中，WSL 功能已默认开启，但要正常使用还是需要一点额外的操作。WSL 本身不提供完整的 Linux 系统功能，只是提供了一个与 Linux 兼容的内核接口层和虚拟化技术在轻量级虚拟机 VM 中运行 Linux 内核，再配合其兼容层的功能使用户能够无缝结合使用 Windows 和 Linux 的功能。[scode type="yellow"]默认情况下，WSL 并未内置 Linux 系统镜像，需要开发者自行选择和安装相应的 Linux 发行版。[/scode]可以通过在终端中输入以下命令来查看 WSL 官方提供的 Linux 发行版：# 查看能安装的 WSL Linux 版本 wsl --list --online通过列出的版本信息，建议使用默认的 Ubuntu 即可，按照指定的 Linux 发行版本到 Windows 11 系统中：# 指定对应的 Linux 版本进行安装 wsl --install -d Ubuntu通过以上命令就可以安装 Linux 发行版本到 Windows 系统中，命令执行流程如下图：当有多 Linux 版本环境需求时，可以进行多个版本的选择性安装，同样使用 wsl --install -d <发行版本名称> 来获取相应的版本安装，查看已经安装的 Linux 发行版本可以使用 wsl --list --verbose 命令查看，下面是一组常用的命令：功能命令安装指定的版本wsl --install -d <发行版本名称>查看已经安装的版本wsl --list --verbose选择启动指定的 Linux 发行版wsl -d Ubuntu更新 WSL 程序wsl --update查看 WSL 状态wsl --st…

函数式编程的思想和优势
type: item
date: 2024-04-15T01:37:00+08:00
description: 自从高中开始学编程已经使用过好几门（PL）编程语言了，目前主流最多使用的编程语言 C/C++ 、Java 、JavaScript 这些都属于结构式和命令式编程语言。但在 Java 在 8 版本发布之后引入了 Lambda 表达式方式对函数式编程的支持，从现代的 Java 设计角度上来说并不是一个纯粹的结构和命令式编程语言了，个人将其归属为一个综合性很强的多编程范式的语言。在过去一年的重新学习了基于 ES6 版本的 JavaScript 之后，让我对函数式编程语言更为感兴趣了。在此之前使用过 Go 语言的实现过一些类似于函数式编程的代码，但 Go 在语言设计的时候并没有添加三目运算符和 Lambda 表达式，使其在编程体验上逊色于 JavaScript 和 Java8 这类原生支持三目运算符和 Lambda 表达式的函数式语言，目前我已经从完全放弃了 Go 语言传统命令式进行编程了，除了几个极少历史遗留开源项目正在使用它进行维护之外。如果读者正在考虑深入学习一门 PL 编程语言的话，我建议学习并且使用现代 Java21 之后的版本，现代 Java 已经添加很多新特性，这篇文章我会介绍一下函数式编程的优势。面向过程程序员日常开发中使用的最多编程方式是面向过程和面向对象，面向对象 OOP 是从 C 语言的面向过程演变发展设计出来的；面向过程取决于程序员如何设计一个好的数据结构和用编写好的函数去操作数据完成逻辑计算，在面向过程编程代码逻辑过程中，函数是主要的组织单元，程序的执行是一系列的函数调用，使用结构体来组织数据，但是能操作这些结构体仍然是通过函数来进行的。[scode type="yellow"]编程范式最典型的例子，生活中一件事情抽象到计算机程序如何做？例如一个抽象的问题：如何把一头大象放到冰箱里面该如何做？换到人类的思维第一反应是怎么可能会有冰箱能装得下一头大象？在计算机里你只要想将具体逻辑告诉它，它能可以按照你逻辑去做某件事情，它很笨不会考虑这么多其他因素，不能去考虑大象是不是比冰箱大，除非在装填函数中编写了一些逻辑来检查是否合规。[/scode]下面就是使用 C 语言就编写一个 3 个函数把大象结构放到冰箱结构里，每个步骤对应着一个操作函数，面向过程的窜行代码：#include <stdio.h> #include <stdbool.h> // 定义大象的结构体 struct Elephant { char name[20]; int weight; }; // 定义冰箱的结构体 struct Refrigerator { bool isOpen; bool hasElephant; }; // 函数原型 void openRefrigerator(struct Refrigerator *fridge); void closeRefrigerator(struct Refrigerator *fridge); void placeElephantInRefrigerator(struct Elephant *elephant, struct Refrigerator *fridge); int main() { // 创建大象对象 struct Elephant elephant = {"大象", 5000}; // 创建冰箱对象 struct Refrigerator refrigerator = {false, false}; printf("将大象放到冰箱里面：\n"); // 打开冰箱 openRefrigerator(&refrigerator); // 将大象放到冰箱里 placeElephantInRefrigerator(&elephant, &refrigerator); // 关上冰箱 closeRefrigerator(&refrigerator); return 0; } // 打开冰箱的函数 void openRefrigerator(struct Refrigerator *fridge) { printf("打开冰箱门\n"); fridge->isOpen = true; } // 关闭冰箱的函数 void closeRefrigerator(struct Refrigerator *fridge) { printf("关闭冰箱门\n"); fridge->isOpen = false; } // 将大象放到冰箱里的函数 void placeElephantInRefrigerator(struct Elephant *elephant, struct Refrigerator *fridge) { if (fridge->isOpen) { printf("将 %s 放进冰箱\n", …

编码从来不是一件简单事情
type: item
date: 2024-03-22T18:32:00+08:00
description: 对于写代码和编写程序来说，放到今天稍微学习一下 Python 和 JavaScript 这类编程语言就可以实现满足自己需求的应用程序，因为已经有很多很好封装好的框架共我们开发者使用，乃至一个中学生都不需要懂一些数学知识，调用相关的 Math 包就可以进行高等数学相关计算。现在有许多高级的编程语言和优秀的框架，使得开发变得更加容易和高效，Python 和 JavaScript 等语言拥有丰富的生态系统和强大的社区支持，为开发者提供了各种各样的工具和框架，使得构建复杂的应用程序变得更加容易。上文提到从需求变成编程再到程序，需求是代码编写者对于软件系统所需功能和特性的描述，例如实现一个四则运算计算器程序，可以是在程序直接编写 console.log("20 + 4 = ",20 + 4) 这种形式来完成需求，也可以是实现一个漂亮的界面完成对应的需求。从 IDEA 到 Coding 到 Product 这个过程中 coding 环节是属于技术实现环节，实现某个功能可以有很多种技术方案，但是方案这么多作为程序员如何选？主流开发的软件都属于应用软件，目前跑在电脑上和跑在手机上的应用软件并没有一些本质区别，都是程序员将自己的大脑逻辑抽象通过代码的形式告诉计算机，当执行这段代码运行之后该程序就会按照你编写的逻辑去交给 CPU 执行，只不过 CPU 要比我们脑子里面计算快得多。这个过程就是 Coding 阶段不能确保程序员大脑里的逻辑一定是正确的，当不合规的逻辑被编写到程序代码中，就可能会产生很严重的后果，例如程序崩溃导致的事件。在电脑和手机只要你开发的不是一些基础的软件，应用软件遇到问题最多就是卡死重启一下，现在的服务器端软件更是搞出来主从或者高可用架构来解决程序员在编码阶段留下的 bug 的问题。在工业时代崇尚机器自动化，现在计算机程序已经渗透到各行各业中了，天上飞的地上跑的水下游的，飞机汽车潜艇这些设备上都靠着计算机程序在辅助人类完成一些操作。机械工程师在设计一些机械结构时犯下的错可能会导致飞机会坠毁，即使是设计合理的机械结构现在控制这些机械装置的已经不是人类了，是跑在计算机中的程序在控制这些机械结构。所以一架飞机或者自动化驾驶的汽车出了事故，责任不完全在机械工程师身上，更有可能的是编写软件的工程师导致的。即现在从一名普通人转行为一名程序员太容易了，普通人通过特定的 IT 培训经过几个月的训练就可以写代码，此种工程师可能不具备专业的 CS 知识就可以动手写程序了，带了问题就上面所说的不可靠软件，如果需要开发专业型的软件还是需要专业型的知识作为铺垫的，例如让一个学土木工程或者做建筑设计的去做软件工程师，那就对应上了中文的俗语 “隔行如隔山” 。一些知名计算机科学界的先驱们就提出了点观点 Dijkstra 曾说过：[scode type="yellow"]我们所使用的工具对我们的思维习惯有着深远的影响，因此也对我们的思维能力有着深远的影响。......使用 COBOL 会削弱人的思维；因此，教授 COBOL 应被视为刑事犯罪。[/scode]简单的工具容易被使用，但是会让使用者不去思考背后的原理，导致缺乏逻辑思维和不去思考事物的本质。这在日常生活中使用的智能手机可以拍照可以打电话可以玩游戏，但是发明和制造一台手机所需要的知识远远不是一个人就能做到的，是一个跨学科和跨领域合作的产品。现在用电脑写一篇文章的时候打错字可以回退修改，但是如果在纸上你写错了一个字可能就报废一张纸，解决办法则你可以使用铅笔进行写字配合着橡皮擦使用，古代人使用毛笔更需要思维和手法练习才发展出来了书法行业。现在已经很少有人去使用了 COBOL 语言进行程序设计了，虽然我本人也是没有写过，但是在研究一些 PL 过程中了解到 COBOL 这门 PL 语言属于结构化加命令式结合产物。但是新的流行语言例如脚本语言和弱语言 JavaScript 和 Python 这些只有在运行的时候才能确定某个变量的类型，还有 C/C++ 这类有空指针设计的 PL ，也会带来底层软件的 Bug ：使用这里语言开发的程序如果程序在开发期没有有效找出 bug 或者程序员的逻辑错误导致 bug 产生，当程序发布之后这些带有 bug 程序会被发布到线上环境，可能在未来的某一时刻会因为这些 bug 导致程序出现故障从而影响到整个系统。一些隐秘的 bug 不一定尽快被修复，例如一个计算闰年和数据类型存储精度丢失的 bug ，要导致触发这个 bug 条件是当前是时间是处于闰年或者闰秒才会发生错误。这类隐秘的 bug 是开发阶段程序员逻辑错误导致的没有处理好，在修复的这些 bug 过程中需要花大量的时间解决，如果在开发这些涉及相关领域知识软件系统时应该提前去查看一些相关领域的资料文件，使用一些业绩主流的解决方案，例…

独立开发者如何起步
type: item
date: 2024-02-18T22:12:00+08:00
description: 随着就业形势不佳，互联网科技公司的寒冬来临，越来越多的程序员被裁员。在这样的背景下，越来越多的程序员开始尝试成为独立开发者（indie Hacker），希望通过开发一款产品来实现财务和工作时间上的自由。他们致力于通过开发和推广自己的产品或服务来实现个人或团队的利润和成功。相比之下，独立开发者在产品开发和运营方面可能相对弱势。独立开发者往往只有一个人，大多数是程序员出身，没有在公司里面一个团队的支持，自己需要去发觉需求，对需求进行分析和产品 UI 设计直到通过代码实现该产品，最后部署上线运营，在这个过程中需要很多资源的支持，这篇博文我将介绍一些关于从小白如何转型为独立开发者经验。编码技能当从一名产品经理和 UI 设计师转型到独立开发者，可能已经熟悉产品开发的流程和方法论，但是缺乏专业的编码技能。产品经理通常需要与程序员合作才能完成产品，但当你想作为一名独立开发者时必须要掌握编程这门技能了，或者找程序员外包的项目，但是我不太推荐这样去做，都不是自己用代码实现出来的产品怎么能称之为独立开发者呢。我建议你最好先学习一门编程语言，因为编程可以帮助你将想法变为现实中的产品，在这方面我建议学习 JavaScript 语言，因为它可以用于开发 Web、Desktop App 和 Mobile App 应用。首先学习最新的 ES6 标准 JavaScript 基础，再学习前端 MVVM 类型的 UI 框架，这里推荐 Vue.js 和 React 框架，此外如果需要将产品开发为 Mobile Native App 产品，要考虑多个手机端的同步开发工作，在过去可能需要两个团队才能完成工作，当现如今已经有很多的第三方框架可以帮助实现此需求，此时推荐你学习 ionic Framework ，ionic 可以允许你使用 Vue 和 React 技术来实现 App 程序，并且已经提供了各种手机设备上的 UI 组件来使用，可以省去一些设计 UI 的时间。对于如果产品考虑多个桌面端推荐考虑使用 Electron ，可以使用 JS + HTML + CSS 来完成传统 QT 的桌面端开发工作，因此推荐要学习编码技能转行的朋友，去学习 JavaScript 技术栈是不二之选。从 IDEA 到产品做独立开发者从来都不是一件轻松的事情，除了要应对前期持续的时间投入和打造产品的费用都投入亏损，想法太多，过于追求完美，很多想要的功能根本无法在短期内实现，最后在慢慢实现的煎熬中放弃了整个产品；第二辛苦做的产品真正上线之后却不被市场认可，数据没有起色，从而失去了继续更新的力量。作为一名程序员出生的开发者，把自己的需求变为一款产品，要经历多个阶段，从 UI 和 UX 原型图再到开发阶段实现，最终发布产品上线这个周期很长，且需要的资源支持非常多，面临着很多问题。首先从产品选型出发是做 App 形式的产品还是基于 Web 的 Saas 形式的产？取决于多种因素，包括您的产品定位、目标用户群、技术栈、团队能力、市场竞争情况等等。如果第一款产品，只是作为一次独立开发者尝试，建议还是采用 App 的方式只需要将应用提交到应用商店（如 App Store 或 Google Play，当然需要支付一些对应平台的软件数字开发者证书费用），由平台管理应用的分发和更新；采用 App 的方式发布产品不需要购买服务器资源和考虑扩容问题，使用用户客户端本地计算和硬件资源，如果在产品发布并且运营一段时间之后没有盈利，乃至盈利金额都不能抵消掉开发阶段的资金投入，那么问题可能就出现在 IDEA 和产品选型上了，可以考虑暂时停止开发这款产品了，因此工具类的 App 产品就不会需要维护服务器后期更多费用。获得用户和收入有的独立开发者在做开源项目，经营开源社区，并构建自己的影响力；有的人通过分享、出书、写专栏获得收益；有的人则会开发工具类软件，笔记、闹钟、创意工具等等；有的独立开发者自己开发了很多款游戏。

一个合格的站长应该具备的能力
type: item
date: 2024-01-01T05:10:00+08:00
description: 现在注册一个域名和购买一台服务器部署一个 Web 网站非常容易，现在流行的 Serverless 架构更是连服务器都不需要购买，直接在各个云厂商的后台点点鼠标就可以部署一个在线的 Web 程序，虽然相比传统建站的方式简单很多，但是也有考虑一些很基础因素和配置，这篇博文我讲以博客系统为例子讲解如何维护一个 Web 服务程序。现在有很多流行的博客系统，例如静态的 Jekyll、Hugo、Hexo，动态以 PHP 为首的 WordPress 系统，WordPress 有着完善的生态系统是世界上目前二次开发使用最多的 Web 系统，静态的博客可以部署在 Github Pages 和 Vercel 等，直接将编写好的网站源代码上传到免费的空间即可。而对于动态博客系统还说，需要 LNMP 环境的支持才能正常运行，现在也流行直接购买已经部署好的数据库环境和运行环境来跑博客，对于动态博客系统还是数据才是最重要的，这篇博客将介绍一些方法一名合格的站长该如何运维自己博客。基础防御[scode type="yellow"]博客系统存放在主机之上通过互联网提供世界各地访问，这些访客里面有普通人也有一些黑客，黑客可以利用博客程序的漏洞例如 XSS 和 SQL 注入漏洞来破坏程序，这对于博客系统安全有了新的要求。普通的静态博客完全可以不用管这些，但是对于 PHP 这类的动态脚本语言来说更需要关注，因为 PHP 是一门动态语言如果被黑客注入了恶意代码会导致程序数据泄露，严重的会破坏程序的运行，目前场景的作为就是在博客核心代码里面对用户输入的数据进行过滤或者做合法性校验，或者在 PHP 使用 disable_functions 配置项来禁用一些敏感函数，来保证程序安全性。[/scode]如果考虑不是在应用程序嵌入代码来做防御，可以使用一些带有代码注入攻击检查防御的网关产品，博客作为一种 Web 程序提供的服务是要经过 HTTP 网络请求的，可以考虑采用带有防御型的网关产品。例如 Cloudflare 它可以提供基于 SASE 和 SSE 平台，将网站的域名的 DNS 接入到 Cloudflare 并且使用它来解析服务网站，可以改善安全和韧性减少攻击参数传入到主程序中，国内也有长亭科技的 SafeLine 的 WAF 产品。Cloudflare 并且提供 CDN 服务，可以隐藏真实的主机 IP 地址，也能确保域名和 DNS 设置的安全，防止域名劫持和 DNS 污染攻击。对于 HTTP 协议来说才有基于 TLS 安全层的 HTTP 协议，启用 SSL/TLS 证书，确保所有数据在传输过程中都是加密的，防止 Cookie 和 Session 信息被截取或篡改。对于基于使用 LNMP 作为基础设施运行的系统，要考虑是不是使用的长期支持版本的软件，如果使用的不是长期支持的版本的软件，如果出现了漏洞，对于这些已经不维护的基础软件会很麻烦，不能得到相应的补丁更新。操作系统环境还是建议使用基于 RHEL 系列的 Linux 发行版本，现在 CentOS 已经是最优的，我推荐使用 Rocky Linux 这个系统是 CentOS 创始人创建的一个克隆 RHEL 的开源操作系统，是在 RHEL 的基础上进行重新编译的，该系统在很大程度上兼容 RHEL 版本的，并且可以通过相同的方式安装 RHEL 软件包，还能得到长期的更新支持。当整个基础环境搭建完成之后，强烈不建议使用 root 账号去运行网站的基础设施，更应该创建一个专用的用户组和用户来运行对于的软件，这样即使黑客绕过前面的基层防护，也没有更高的权限去访问其他的数据和程序。服务器防火墙软件还是推荐使用目前主流的 firewalld ，这是 REHL 企业级的服务器管理软件，相比一些老的 iptables 来管理出入站流量复杂的操作更容易操作。不要开放不必要的端口，一个 Web 服务器最多开发基础的端口 80 、 443 、22 端口，其他端口可以一律禁止外部访问使用，最好的方式使用白名单 IP 的方式进行访问控制，通过跳板机进行管理。对于 root 用户来说应该不使用 root 密码进登录，而使用基于 SSH 的非对称密钥进行登录，可以将登录电脑和被管理的服务器进行绑定，将公钥上传到远程服务器来进行登录管理。此外此种方式还要考虑私钥的安全性，不要将私钥公开，避免在页面或日志中输出敏感信息，如数据库凭据、API 密钥等。数据备份在线系统的核心是数据，这些数据一般会被存储在 MySQL 这些关系型数据库中，目前主流的动态博客系统数据都是存储在关系数据库中，前面提到黑客通过一些特殊的手段获取到了服务器执行权限，从而可能会导致数据库被拖库导致数据泄漏。对于数据库系统来说管理也是不推荐开放 root 权限给远程用户登陆，对于 Web …

开发基于网络程序该考虑什么？
type: item
date: 2023-11-29T02:06:00+08:00
description: 开发基于网络的程序时，有许多因素需要考虑，以确保你的应用程序是高效、安全、可伸缩和用户友好的。但是今天这篇文章更加关注的是 OSI 模型中的应用层协议和运输层的协议，基于这些层的程序开发更加关注什么？如果作为一名应用开发者我们最多接触到的网络层面上的应用协议和运输协议；应用的概念更加面向用户，强调的是提供某种服务或功能，可以是桌面应用、移动应用、Web 应用等，这里围绕的是应用开发如何对网络协议选型，因为应用开发是在操作系统用户态上的开发，对于内核的可编程操作是不允许的，所以本篇文章更加关注的是基于 TCP/UDP 之上的协议选型时要考虑的问题。TCP 和 UDPTCP 和 UDP 都是运输层提供两套协议，提供系统内核的 API 可以使用它进行运输数据包达到另外一台主机上，市面上很多的远程调用框架例如 gRPC 和 Thrift 在底层运输数据包都是采用的内核提供的 API 进行发包的。这些 RPC 框架在应用层之上提供了一种结构化的、高效的远程调用机制，使开发者能够更轻松地构建分布式系统。虽然它们通常在应用层协议的基础上构建，但它们自身更类似于框架或库，提供了更高级别的抽象，用于处理远程服务调用的复杂性。gRPC 使用 Protocol Buffers（ProtoBuf）作为接口定义语言（IDL），这允许开发者定义服务接口、消息格式等，而不用关心底层通信细节。这里围绕的 gRPC 来讨论这个问题，了解 gRPC 协议都知道该协议是基于 HTTP/2 协议，利用了 HTTP/2 的多路复用、头部压缩、二进制帧提供了更高效的网络传输，相比 HTTP/1.1 使用多个 TCP 连接，每个连接只能传输一个请求和响应，这可能导致 TCP 连接的浪费和性能瓶颈，而 HTTP/2 引入了多路复用的特性，允许多个请求和响应在同一 TCP 连接上并行传输，这样也减少了 TCP 连接的数量提高了性能。[scode type="yellow"]通过这么一解释就很容易明白 gRPC 为什么要使用 HTTP/2 协议作为基础的应用数据传输协议了，HTTP/2 协议属于是应用层的协议，它依赖运输层协议是 TCP 协议，确实符合 gRPC 协议的应用场景，针对在分布式系统上对数据的可靠性和高效的数据传输要求。也从侧面角度反映出了为什么 Spring Cloud 没有直接使用 gRPC 这种协议进行数据通信，而是直接使用 HTTP 协议，随着 HTTP 协议版本改进采用更高效的二进制数据传输，性能和 gRPC 也不相上下。Spring Cloud 并没有直接使用 HTTP/2.0 进行通信，而是建立在 HTTP 协议之上。默认情况下 Spring Cloud 使用 HTTP/1.1 作为通信协议，但是可以通过一些配置和定制来启用 HTTP/2.0 ，相比 gRPC 特殊的数据传输格式，HTTP/2.0 反而没有那么多数据格式要求可以自定义是 JSON 还是 XML 这种，最终都是会被编码为二进制数据帧。[/scode]对于需要可靠型网络数据传输的场景，使用 TCP 是一个最佳的选择，但是也并不意味着盲目的去使用它传输数据。 TCP 传输数据之前每次都是需要进行 3 次握手，这 3 个握手数据包是主要的耗时来源，延迟就此产生了，如果在一次性很短暂数据运输需求下，这就显得不是那么适用了，快速打开一个连接使用完成之后快速释放掉，很多应用层的网络程序都是跑在 TCP 协议之上，必须经过这个慢启动的过程。HTTP/1.0 版本主要就是针对此种突发性连接的场景，获取到某个 HTML 内容立马断开 TCP 连接，完全没有利用好 TCP 协议，每次发送一个 HTTP 请求都需要经过一次 TCP 慢启动过程。