modified | Friday 27 June 2025 |
---|
收件箱
##########################################
##########################################
##########################################
##########################################
##########################################
##########################################
##########################################
##########################################
##########################################
##########################################
##########################################
##########################################
##########################################
##########################################
##########################################
##########################################
##########################################
##########################################
##########################################
##########################################
##########################################
##########################################
—————-BEGIN OF DOCUMENT —————–
go env -w GO111MODULE=‘on’
go env -w GOSUMDB=sum.golang.google.cn
go env -w GOPROXY=https://goproxy.cn,direct
当涉及到 Java 性能分析时,有一系列强大的命令和工具可以帮助开发人员分析应用程序的性能瓶颈、内存使用情况和线程问题。以下是一些常用的 Java 性能分析命令和工具,以及它们的详细说明和示例。
以下是一些常用的性能分析命令和工具汇总:
命令 功能简述
jps 用于查看正在运行的 Java 进程的状态信息。
jinfo 用于查看和修改正在运行的 Java 进程的 Java 虚拟机(JVM)参数和系统属性。
jstack 用于生成 Java 进程的线程转储信息。
jmap 用于生成 Java 进程的内存映射信息。
jconsole 是 Java 自带的监控和管理控制台,它提供了一个图形化界面,允许您监视和管理正在运行的 Java 应用程序的性能和资源使用情况。
jvisualvm 是一款功能强大的多合一故障诊断和性能监控的图形化工具。
1、jps
jps (JVM Process Status Tool)命令用于查看正在运行的 Java 进程的状态信息,包括进程 ID 和主类名。这对于迅速了解系统上正在运行的 Java 应用程序很有帮助。
1.1 语法
jps [ options ] [ hostid ]
其中,options 是可选的命令行选项,hostid 是可选的 RMI 注册表主机 ID。下面是一些常用的选项:
-q:只输出进程ID,不包括类名和JAR文件名。
-m:输出传递给 main 方法的参数。
-l:输出主类全名,或者对于JAR文件,输出JAR文件路径。
-v:输出传递给 JVM 的参数。
1.2 示例
假设您有一个正在运行的Java应用程序,其主类为com.example.MyApp,并且它的进程ID为12345。下面是一些使用jps命令的示例:
查看所有 Java 进程的进程 ID 和主类名:
$ jps
12345 com.example.MyApp
67890 sun.tools.jps.Jps
在上面的输出中,12345 是您的Java应用程序的进程 ID,com.example.MyApp 是应用程序的主类名。
只输出 Java 进程的进程 ID:
$ jps -q
12345
67890
输出 Java 进程的进程 ID 和传递给 main 方法的参数:
$ jps -m
12345 com.example.MyApp arg1 arg2
67890 sun.tools.jps.Jps -m
输出 Java 进程的进程 ID 和主类全名:
$ jps -l
12345 com.example.MyApp
67890 sun.tools.jps.Jps
通过使用不同的选项,您可以根据需要获取不同层次的信息。jps 命令对于快速查看正在运行的Java 进程非常有用,尤其在定位和监视Java应用程序时。
2、jinfo
jinfo 是一个 Java 命令行工具,用于查看和修改正在运行的 Java 进程的 Java 虚拟机(JVM)参数和系统属性。通过使用 jinfo,您可以动态地检查和更改 Java 应用程序的配置信息,而无需停止应用程序。
在很多情况下,Java 应用程序不会指定所有的 JVM 参数,开发人员可能不知道某一个具体 JVM 参数的默认值。在这种情况下,jinfo 就能很方便地查看 JVM 参数的当前值。
2.1 语法
jinfo [options] pid
其中,options 可以是以下选项之一:
-flags:打印指定 JVM 的参数值。
-sysprops:打印Java虚拟机的系统属性。
-flag name:打印指定名称的标志参数的值。
-flag [+|-]name:打印或设置指定名称的布尔标志参数的值。
pid 是正在运行的Java进程的进程ID。
2.2 示例
当使用jinfo命令来查看Java进程的信息时,可以得到类似下面的输出示例。以下是一些可能的输出示例,根据您运行的Java应用程序和虚拟机配置,实际输出可能会有所不同。
示例1:查看标志参数:
$ jinfo -flags 12345
Attaching to process ID 12345, please wait…
Debugger attached successfully.
Server compiler detected.
JVM version is 11.0.12+7
Non-default VM flags: -XX:CICompilerCount=3 -XX:InitialHeapSize=268435456 -XX:MaxHeapSize=4294967296 -XX:MaxNewSize=1431306240 -XX:MinHeapDeltaBytes=524288 -XX:NewSize=89128960 -XX:OldSize=179306496 -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseFastUnorderedTimeStamps -XX:+UseParallelGC
Command line: -XX:InitialHeapSize=268435456 -XX:MaxHeapSize=4294967296 -XX:MaxNewSize=1431306240 -XX:MinHeapDeltaBytes=524288 -XX:NewSize=89128960 -XX:OldSize=179306496 -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseFastUnorderedTimeStamps -XX:+UseParallelGC
VM Flags:
-XX:CICompilerCount=3
-XX:InitialHeapSize=268435456
-XX:MaxHeapSize=4294967296
-XX:MaxNewSize=1431306240
-XX:MinHeapDeltaBytes=524288
-XX:NewSize=89128960
-XX:OldSize=179306496
-XX:+UseCompressedClassPointers
-XX:+UseCompressedOops
-XX:+UseFastUnorderedTimeStamps
-XX:+UseParallelGC
示例 2:查看系统属性:
$ jinfo -sysprops 12345
Attaching to process ID 12345, please wait…
Debugger attached successfully.
Server compiler detected.
JVM version is 11.0.12+7
Non-default VM flags: -XX:CICompilerCount=3 -XX:InitialHeapSize=268435456 -XX:MaxHeapSize=4294967296 -XX:MaxNewSize=1431306240 -XX:MinHeapDeltaBytes=524288 -XX:NewSize=89128960 -XX:OldSize=179306496 -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseFastUnorderedTimeStamps -XX:+UseParallelGC
Command line: -XX:InitialHeapSize=268435456 -XX:MaxHeapSize=4294967296 -XX:MaxNewSize=1431306240 -XX:MinHeapDeltaBytes=524288 -XX:NewSize=89128960 -XX:OldSize=179306496 -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseFastUnorderedTimeStamps -XX:+UseParallelGC
Property settings:
awt.toolkit = sun.awt.windows.WToolkit
java.class.path = /path/to/your/application.jar
…
示例 3:查看特定标志参数的值:
$ jinfo -flag UseG1GC 12345
Attaching to process ID 12345, please wait…
Debugger attached successfully.
Server compiler detected.
JVM version is 11.0.12+7
Non-default VM flags: -XX:CICompilerCount=3 -XX:InitialHeapSize=268435456 -XX:MaxHeapSize=4294967296 -XX:MaxNewSize=1431306240 -XX:MinHeapDeltaBytes=524288 -XX:NewSize=89128960 -XX:OldSize=179306496 -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseFastUnorderedTimeStamps -XX:+UseParallelGC
Command line: -XX:InitialHeapSize=268435456 -XX:MaxHeapSize=4294967296 -XX:MaxNewSize=1431306240 -XX:MinHeapDeltaBytes=524288 -XX:NewSize=89128960 -XX:OldSize=179306496 -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseFastUnorderedTimeStamps -XX:+UseParallelGC
UseG1GC=true
示例 4:修改特定标志参数的值:
$ jinfo -flag +PrintGCDetails 12345
Attaching to process ID 12345, please wait…
Debugger attached successfully.
Server compiler detected.
JVM version is 11.0.12+7
Non-default VM flags: -XX:CICompilerCount=3 -XX:InitialHeapSize=268435456
3、jstack
jstack(Java Stack Trace)命令用于生成 Java 进程的线程转储信息。它可以显示每个线程的堆栈跟踪,帮助您找出可能的死锁、死循环和线程问题。
3.1 语法
jstack [ options ] pid
其中,options 是命令选项,pid 是目标 Java 进程的进程 ID。
以下是一些常用的命令选项:
-F:强制生成线程堆栈,即使Java进程没有响应。
-m:除了线程堆栈,还包括每个线程的本地(本地方法)信息。
-l:除了线程堆栈,还包括锁信息。
-h:显示帮助信息。
3.2 示例
使用 jstack 命令分析线程问题。
假设您有一个 Java 应用程序,出现了死锁的情况。您可以使用 jstack 命令来分析线程问题。以下是一个示例:
首先,找到目标 Java 进程的进程 ID(pid),可以使用 jps 命令来查看正在运行的Java进程:
jps
假设您找到了要分析的 Java 进程的进程 ID 为 12345,使用 jstack 命令来生成线程堆栈信息:
jstack 12345
命令会输出每个线程的调用栈信息,您可以在输出中查找线程状态、锁信息等。
假设您的 Java 应用程序中有两个线程,一个正在等待锁,而另一个持有该锁。这可能导致死锁。通过运行 jstack 命令,您可以获得类似以下的输出:
“Thread-1” 10 prio=5 os_prio=0 tid=0x00007f36e8001000 nid=0x5303 waiting for monitor entry [0x00007f36e3100000]
java.lang.Thread.State: BLOCKED (on object monitor)
at com.example.MyClass.method1(MyClass.java:50)
- waiting to lock <0x00000000e3033c88> (a java.lang.Object)
at com.example.MyClass.run(MyClass.java:100)
“Thread-2” 11 prio=5 os_prio=0 tid=0x00007f36e8001800 nid=0x5304 waiting on condition [0x00007f36e300f000]
java.lang.Thread.State: TIMED_WAITING (sleeping)
at java.lang.Thread.sleep(Native Method)
at com.example.MyClass.method2(MyClass.java:75)
at com.example.MyClass.run(MyClass.java:120)
在这个示例中,Thread-1 正在等待获取一个锁,而 Thread-2 在持有锁的线程中等待。这可能是一个死锁的迹象,您可以根据这些信息来分析并解决线程问题。
总之,jstack 命令是一个有用的工具,用于生成Java进程的线程堆栈跟踪信息,帮助您分析和解决应用程序中的线程问题。
4、jmap
jmap(Java Memory Map)命令用于生成 Java 进程的内存映射信息。它提供了堆的详细信息,包括 Java 堆内存、对象统计和内存使用情况的详细信息,可以帮助开发人员分析内存泄漏、内存使用情况等问题。
4.1 语法
jmap [options]
其中,options 是一些可选的命令选项,
常用的 jmap 命令选项包括:
-heap: 显示Java堆内存使用情况。
-histo: 显示Java堆内存中的对象统计信息。
-dump:
-finalizerinfo: 显示等待终结者队列中的对象。
-F: 在无法连接到进程时,强制执行转储操作。
4.2 示例
示例1:使用jmap命令分析 Java 堆内存
假设您有一个 Java 应用程序正在运行,您想要分析其 Java 堆内存使用情况,以便查找内存泄漏问题。您可以使用以下命令:
jmap -heap
其中,
示例2:使用jmap命令生成堆内存转储文件
假设您怀疑 Java 应用程序存在内存泄漏,您可以使用 jmap 命令生成堆内存转储文件,以便后续分析。以下是一个示例:
jmap -dump:format=b,file=heapdump.bin
在这个示例中,format=b 表示将转储以二进制格式保存,file=heapdump.bin 指定转储文件名。您可以将
注意事项:
使用jmap命令时,建议在测试或开发环境中进行,避免在生产环境中使用,因为生成堆内存转储文件可能会影响应用程序的性能。
jmap命令可能需要JVM的调试权限,因此确保您有足够的权限来运行该命令。
转储文件可能会相当大,特别是在内存使用量较大的情况下。确保您有足够的磁盘空间来存储转储文件。
总之,jmap 命令是一个有用的工具,可以帮助开发人员分析 Java 应用程序的内存使用情况,查找内存泄漏问题,并生成堆内存转储文件以进行后续分析。
5、jconsole
jconsole(Java Monitoring and Management Console)是 Java 自带的监控和管理控制台,它提供了一个图形化界面,允许您监视和管理正在运行的 Java 应用程序的性能和资源使用情况。jconsole 是一个非常有用的工具,可以帮助开发人员识别问题、进行性能调优和监控Java应用程序。
5.1 主要功能
实时监控: 可以实时监控 Java 应用程序的内存使用情况、线程状态、垃圾回收、类加载等。
堆内存分析: 提供了对堆内存的监控和分析,可以查看对象数量、内存占用等信息。
线程分析: 可以帮助您检查线程的状态、堆栈跟踪和 CPU 使用情况,帮助您发现死锁和性能问题。
垃圾回收分析: 提供了垃圾回收的详细信息,可以查看垃圾回收的频率和效果。
MBean管理: 可以连接到 MBean(管理bean),允许您管理和监控应用程序的 MBean。
5.2 示例
以下是使用 jconsole 的示例,以监控一个正在运行的 Java 应用程序:
启动您的 Java 应用程序。假设您的应用程序是一个简单的 Java 程序,例如:
public class MyApp {
public static void main(String[] args) {
while (true) {
System.out.println(“Running…”);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
打开终端并运行以下命令,启动 jconsole:
jconsole
在 jconsole 界面中,您可以看到一个列表,显示了正在运行的 Java 进程。选择您的应用程序进程并点击“连接”。
jconsole:新建连接
在 jconsole 的不同选项卡中,您可以查看内存使用情况、线程状态、垃圾回收信息等。例如,您可以在“内存”选项卡中查看堆内存使用情况。
jconsole:内存
在“线程”选项卡中,您可以查看每个线程的状态、堆栈跟踪等信息,以帮助您识别潜在的线程问题。
jconsole:线程
jconsole 是一个强大的 Java 性能监控和管理工具,提供了丰富的功能来监视和分析应用程序的性能和资源使用情况。通过 jconsole,开发人员可以轻松地识别问题、分析性能瓶颈,以及进行调优,从而提升Java应用程序的性能和效率。
6、VisualVM
Java VisualVM(Java Visual Monitoring and Troubleshooting Tool)是一款功能强大的多合一故障诊断和性能监控的图形化工具,它集成了多种性能统计工具的功能,使用它可以代替 jmap、jstack 甚至 jconsole。
它是 Java 开发工具包(JDK)的一部分,可以帮助您实时监控应用程序的各种指标,如内存使用、线程情况、垃圾回收等,以便发现和解决性能问题。
6.1 主要功能
实时监控: 允许您实时监控Java应用程序的运行状态。您可以查看内存使用情况、线程状态、CPU利用率等指标。
内存分析: 提供了内存分析工具,可以帮助您识别内存泄漏和对象分配情况。您可以查看堆内存的内容,分析对象引用关系。
线程分析: 可以监控和分析应用程序中的线程状态,帮助您发现死锁、线程争用等问题。
垃圾回收分析: 工具可以显示垃圾回收的详细信息,帮助您确定垃圾回收的类型、频率和影响。
CPU分析: 可以帮助您分析 CPU 利用率高的原因,找出耗费 CPU 资源的部分。
多种插件: 支持各种插件,扩展了其功能。您可以安装插件来支持不同的 Java 应用程序和特定的性能分析需求。
6.2 示例
监控内存和 CPU 使用。
启动 Java VisualVM: 您可以在JDK的 bin 目录中找到jvisualvm.exe(Windows)或 jvisualvm(Linux/macOS)并运行它。
jvisualvm
连接到应用程序: 在 Java VisualVM 中,点击的“远程”按钮,然后在“远程”面板中添加要连接的远程或本地Java进程。
监控性能: 连接到应用程序后,您可以查看实时性能监控信息,包括内存、线程、CPU使用等。您还可以选择不同的监视选项,如“监视”、“内存”、“线程”等标签。
jvisualvm:监控
进行内存分析: 在“抽样器”标签中,您可以生成并分析堆转储,查看对象引用关系、占用内存的对象等。
jvisualvm:抽样器
线程分析和垃圾回收分析: 在“线程”和“监视”标签中,您可以监控线程状态、识别死锁,以及查看垃圾回收行为。
jvisualvm:线程
总之,Java VisualVM 是一个强大的性能分析工具,可以帮助您监控、分析和优化Java应用程序的性能。通过实时监控、内存分析、线程分析等功能,您可以更深入地了解应用程序的运行情况,并解决性能问题。
7、总结
Java 性能分析是确保应用程序优化和效率的关键步骤。这些命令和工具提供了详细的信息,可以帮助您分析和解决性能问题。无论您需要查找内存泄漏、分析线程问题,还是优化垃圾回收,这些工具都能够提供有价值的信息。掌握这些命令和工具,将有助于您更好地管理和优化您的Java应用程序,提供更好的用户体验。
引言
Java应用程序的开发和部署过程中,合理利用各类环境变量是关键之一。不同类型的环境变量,如系统环境变量、进程级环境变量、Java启动参数设置的系统属性以及Spring Boot配置文件中的环境变量,它们之间存在优先级差异。
深入理解这些环境变量的优先级关系,将使得Java应用程序更加灵活和易于维护。本文将详细介绍这些环境变量的优先级,结合实际场景提供最佳实践,以帮助开发人员更好地配置和管理Java应用程序。
示例: 假设在操作系统中设置了一个名为 JAVA_HOME 的系统环境变量,指向Java的安装目录。尽管Java应用程序继承了这个变量,但它并不直接用于应用程序的特定配置。
2.2 进程级环境变量
进程级环境变量是在Java进程启动时设置的,可在启动脚本或命令行中指定。这一层级的环境变量会覆盖系统环境变量,但只在Java进程的生命周期内有效。进程级环境变量的优势在于可以根据应用程序的不同运行环境进行定制化配置。
示例:
通过启动脚本或命令行参数 -D 设置Java进程的环境变量,例如:
java -Dspring.profiles.active=dev -jar myapp.jar
这里的 spring.profiles.active 是一个进程级环境变量,用于指定Spring应用程序的激活配置文件。
2.3 Java启动参数 (-D参数)
通过 -D 参数可以在启动Java进程时设置Java系统属性。这些属性可以通过 System.getProperty() 方法在Java应用程序中获取。Java启动参数设置的系统属性优先级较高,通常用于指定应用程序的一些关键配置。
示例:
java -Dserver.port=8080 -jar myapp.jar
在这个例子中,server.port 是一个Java系统属性,它会覆盖进程级环境变量和系统环境变量中的同名属性。
2.4 Spring Boot配置文件中的环境变量
在Spring Boot应用程序的配置文件中,可以使用 ${} 语法引用环境变量。这样的环境变量可以是系统环境变量、进程级环境变量,甚至是Java启动参数设置的系统属性。Spring Boot配置文件中的环境变量通常用于配置应用程序的各种属性,如数据库连接、端口号等。
示例:
server:
port: ${SERVER_PORT:8080}
在这个例子中,如果环境变量 SERVER_PORT 存在,则使用该值,否则默认使用 8080。
Java启动参数设置的系统属性(-D参数):优先级最高。
进程级环境变量:次高优先级。
系统环境变量:优先级最低。
Spring Boot配置文件中的环境变量:介于进程级环境变量和系统环境变量之间,可通过动态设置或默认值实现更灵活的配置。
4. 最佳实践
动态切换配置文件:通过Java启动参数设置系统属性,可以实现在不同环境中动态切换配置文件,提高灵活性。
保护敏感信息:避免将敏感信息硬编码在代码中,通过环境变量传递,可以在不同环境中轻松更改这些信息。
日志级别控制:使用Java启动参数设置系统属性,可以在运行时动态调整日志级别,有助于排查问题。
多环境配置:利用Spring Boot配置文件中的环境变量,可以为不同环境提供特定的配置,如数据库连接信息、服务端口等。
. Golang 多架构编译的原理
Golang 的多架构编译功能依赖于两个关键的环境变量:GOOS 和 GOARCH。其中,GOOS 表示目标操作系统,而 GOARCH 表示目标架构。通过设置这两个环境变量,我们可以告诉 Go 编译器在编译过程中要生成的目标平台。
编写代码:准备好你的 Go 代码,例如一个简单的 main.go 文件,其中包含了你的应用程序逻辑。
进行编译:使用 go build 命令进行编译,同时指定目标操作系统和架构。例如,要在 CentOS amd64 上为 Linux 64 位编译,可以使用命令 GOOS=linux GOARCH=amd64 go build -o hello-linux-amd64 main.go。
验证编译结果:编译完成后,可以验证生成的可执行文件是否能够在目标平台上正常运行。
package main
import “fmt”
func main() {
fmt.Println(“Hello, World!”)
}
使用以下命令,我们可以将这个简单的 Go 程序编译为多个目标平台的可执行文件:
GOOS=linux GOARCH=amd64 go build -o hello-linux-amd64 main.go
GOOS=windows GOARCH=amd64 go build -o hello-windows-amd64.exe main.go
GOOS=linux GOARCH=arm64 go build -o hello-linux-arm64 main.go
GOOS=darwin GOARCH=amd64 go build -o hello-darwin-amd64 main.go
通过以上命令,我们可以在不同的操作系统和架构上编译出相应的可执行文件,使得我们的应用程序能够在各种环境中运行。
3.2 编写多架构编译脚本
为了方便编译多个架构,可以编写一个脚本来自动编译多个目标平台。
创建 build.sh 脚本:
#!/bin/bash
OUTPUT_DIR=“build”
PLATFORMS=(“linux/amd64” “linux/arm64” “windows/amd64” “darwin/amd64”)
mkdir -p $OUTPUT_DIR
for PLATFORM in “\({PLATFORMS[@]}"; do OS=\) (echo \(PLATFORM | cut -d'/' -f1) ARCH=\) (echo \(PLATFORM | cut -d'/' -f2) OUTPUT_NAME=\) OUTPUT_DIR/hello-\(OS-\) ARCH
if [ $OS = "windows" ]; then
OUTPUT_NAME+='.exe'
fi
echo "Building for $OS/$ARCH..."
GOOS=$OS GOARCH=$ARCH go build -o $OUTPUT_NAME main.go
done
使脚本可执行:
chmod +x build.sh
运行脚本:
./build.sh
这个脚本会在 build 目录中生成多个目标平台的可执行文件。
3.3 安装和使用交叉编译工具链
对于一些架构,例如 ARM,你可能需要安装特定的交叉编译工具链。例如:在 CentOS X86 架构的环境上,编译 ARM 架构的应用。
目前,CentOS 官方仓库不直接提供 gcc-arm-linux-gnu 和 gcc-aarch64-linux-gnu 包。因此,我们可以从开发者网站(如 ARM 官方或 Linaro)下载预编译的工具链。下面示例展示如何安装 Linaro 提供的 ARM 工具链。
下载 Linaro 64 位 ARM 工具链
wget https://releases.linaro.org/co…
解压并安装工具链
sudo tar -C /usr/local -xvf gcc-linaro-7.5.0-2019.12-x86_64_aarch64-linux-gnu.tar.xz
将工具链添加到系统环境变量
echo “export PATH=$PATH:/usr/local/gcc-linaro-7.5.0-2019.12-x86_64_aarch64-linux-gnu/bin” » ~/.bashrc
source ~/.bashrc
验证工具链安装
安装完成后,可以通过以下命令验证工具链是否安装成功。
aarch64-linux-gnu-gcc –version
使用 ARM 工具链进行交叉编译
安装完成并验证工具链后,你可以使用这些工具链为 ARM 平台进行交叉编译。例如:
GOARCH=arm64 GOOS=linux CC=aarch64-linux-gnu-gcc go build -o hello-arm64 main.go
通过这些步骤,你可以在 CentOS 7 amd64 上安装 ARM 工具链,并为 ARM 平台进行交叉编译。
3.4 使用 Docker 进行多架构编译
Docker 提供了一种方便的方法来进行多平台构建。你可以使用 Docker 的 Buildx 插件来构建不同平台的 Docker 镜像。
安装 Docker 和 Buildx
sudo yum install -y yum-utils
sudo yum-config-manager –add-repo https://download.docker.com/li…
sudo yum install docker-ce docker-ce-cli containerd.io
sudo systemctl start docker
sudo systemctl enable docker
docker run –rm –privileged multiarch/qemu-user-static –reset -p yes
docker buildx create –use
创建 Dockerfile
创建一个简单的 Dockerfile:
FROM golang:1.18 AS builder
WORKDIR /app
COPY . .
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o /hello
FROM alpine:latest
COPY –from=builder /hello /hello
CMD ["/hello”]
构建多平台 Docker 镜像
docker buildx build –platform linux/amd64,linux/arm64 -t hello:latest –push .
4. 结语
通过这篇文章,读者可以全面了解 Golang 的多架构编译功能,掌握如何使用这一功能来实现跨平台部署。同时,通过最佳实践示例,读者可以更加直观地理解 Golang 多架构编译的实际应用场景,为日后的开发工作提供参考和指导。
Orange Pi AI Pro 开发板是香橙派联合华为精心打造的高性能 AI 开发板,其搭载了昇腾 AI 处理器,可提供 8TOPS INT8 的计算能力,内存提供了 8GB 和 16GB 两种版本。可以实现图像、视频等多种数据分析与推理计算,可广泛用于教育、机 器人、无人机等场景。
开发板使用总结
易用性
官方文档很详细,容易上手。配置简单,安装方便。开发板接口很丰富,支持很多常见接口,如千兆网口、USB、Type-C、WiFi、蓝牙等,属于开箱即可用的开发板。
负载能力
OrangePi 的性能虽然不及高端服务器,但对于家庭 NAS 用途足够胜任。使用 Nextcloud 进行文件存储和小规模的文件共享是完全没问题的。在进行文件上传、下载以及媒体流播放时,OrangePi 的 CPU 利用率会有所上升,但仍能保持较为流畅的操作体验。
散热能力
OrangePi 开发板具有非常高的散热能力,可以满足大多数家庭 NAS 的需求。在长时间运行下,OrangePi 的温度会略有上升,尤其是在进行大文件传输或高负载操作时。建议使用更大的散热片或风扇进行辅助散热,以确保系统稳定运行。
噪音能力
OrangePi 本身没有活动部件,因此在正常运行时是完全静音的。如果使用外部风扇,噪音水平取决于风扇的质量和速度。一般来说,小型风扇的噪音是可以接受的,不会对日常生活造成明显影响。
6.2 个人云盘使用总结
在实际使用中,OrangePi 搭建的 NAS 系统表现令人满意。以下是一些具体的使用感受:
文件访问速度
在局域网内访问文件速度较快,上传下载速度基本能够满足日常需求。使用Nextcloud客户端可以方便地同步文件。
多设备支持
Nextcloud支持多种设备访问,包括Windows、Mac、Linux、Android和iOS设备。可以随时随地访问你的文件,非常方便。
数据安全性
可以通过设置用户权限和加密来保护你的数据安全。定期备份也很重要,可以使用外部硬盘或其他NAS设备进行备份。
拓展性
Nextcloud支持多种插件和应用,可以根据需要扩展功能,如日历、联系人管理、在线文档编辑等。
一、NFS简介
NFS(Network File System) 是一种分布式文件系统协议,允许用户通过网络在不同主机间共享文件和目录。它适用于局域网环境,常用于服务器集群、数据共享等场景。本文详细介绍NFS服务端与客户端的安装、配置及挂载流程。
二、NFS服务端配置
Ubuntu/Debian
sudo apt update && sudo apt install nfs-kernel-server
CentOS/RHEL
sudo yum install nfs-utils # CentOS 7
sudo dnf install nfs-utils # CentOS 8+/RHEL
2. 创建共享目录
sudo mkdir -p /mnt/nfs_share
sudo chown nobody:nogroup /mnt/nfs_share # 设置权限(根据需要调整)
3. 配置NFS导出规则
编辑配置文件 /etc/exports,定义共享目录及访问权限:
/mnt/nfs_share 192.168.1.0/24(rw,sync,no_subtree_check)
常用权限选项:
rw:读写权限。
ro:只读权限。
sync:同步写入磁盘(数据安全性高)。
async:异步写入(性能更好,但可能丢失数据)。
no_root_squash:允许客户端root用户保留权限(谨慎使用)。
示例:
/mnt/nfs_share *(rw,sync,no_root_squash)
4. 生效配置并启动服务
sudo exportfs -a # 重新加载 exports 配置
sudo systemctl start nfs-server # 启动服务
sudo systemctl enable nfs-server # 设置开机自启
5. 防火墙配置
开放 NFS 相关端口(NFSv4 默认使用 TCP 2049):
sudo ufw allow 2049/tcp # Ubuntu
sudo firewall-cmd –permanent –add-service=nfs && firewall-cmd –reload # CentOS
三、NFS客户端配置
sudo apt install nfs-common
CentOS/RHEL
sudo yum install nfs-utils
2. 创建本地挂载点
sudo mkdir -p /mnt/nfs_client
3. 手动挂载NFS共享
sudo mount -t nfs <服务端IP>:/mnt/nfs_share /mnt/nfs_client
示例:
sudo mount -t nfs 192.168.1.100:/mnt/nfs_share /mnt/nfs_client
4. 自动挂载(重启生效)
编辑 /etc/fstab 文件,添加以下行:
<服务端IP>:/mnt/nfs_share /mnt/nfs_client nfs defaults 0 0
示例:
192.168.1.100:/mnt/nfs_share /mnt/nfs_client nfs defaults 0 0
5. 验证挂载
df -h | grep nfs # 查看挂载状态
touch /mnt/nfs_client/test.txt # 测试读写权限
四、高级配置与调试
sudo mount -t nfs -o vers=4 192.168.1.100:/mnt/nfs_share /mnt/nfs_client
2. 用户身份映射
在服务端 /etc/exports 中配置 anonuid 和 anongid:
/mnt/nfs_share 192.168.1.0/24(rw,sync,all_squash,anonuid=1000,anongid=1000)
3. 查看NFS共享状态
服务端:
showmount -e localhost # 查看已导出的共享目录
客户端:
showmount -e <服务端IP>
4. 日志排查
服务端日志:/var/log/syslog(Ubuntu)或 /var/log/messages(CentOS)。
客户端日志:dmesg | grep nfs。
五、常见问题解决
权限被拒绝(Permission Denied)
检查服务端 /etc/exports 的权限配置。
确保客户端用户对挂载点有访问权限。
连接超时或无法访问
确认防火墙已放行NFS端口(尤其是NFSv3需要额外开放 rpcbind 端口)。
使用 rpcinfo -p <服务端IP> 检查RPC服务状态。
挂载后文件属主显示为nobody
在服务端配置 no_all_squash 或指定 anonuid/anongid。
六、总结
NFS 是 Linux 环境中高效的文件共享解决方案。通过配置服务端导出规则、客户端挂载目录,并合理设置权限与防火墙规则,即可实现稳定可靠的网络存储。对于生产环境,建议结合Kerberos增强安全性,并定期监控NFS性能。
注意:NFS默认不加密传输数据,公网环境建议使用VPN或SSH隧道保护通信。
JDK 17 是继 JDK 8 后的首个 LTS 版本,支持模块化、Records 等新特性,但移除了部分旧 API(如 javax.servlet)。
直接影响:基于 JDK 8 构建的 SpringFox(Swagger 2.x)因依赖旧规范无法兼容新版本。
Jakarta EE 的崛起:
Java EE 移交 Eclipse 基金会后更名为 Jakarta EE,包名从 javax.* 改为 jakarta.*。
核心冲突:Spring Boot 3.x 和 SpringDoc(Swagger 3.x)强制依赖 Jakarta EE 9+,导致旧项目升级时需全局替换包名。
安全风险:SpringFox 已停止维护,存在未修复漏洞(如 CVE-2021-28170)。
功能需求:SpringDoc 支持 OpenAPI 3.0 规范,提供更灵活的文档定义和响应示例。
生态兼容:微服务、云原生场景下,JDK 17 的容器化支持更优。
微服务的概念,最早由 Martin Fowler 与 James Lewis 于 2014 年共同提出
Martin Fowlern 在《Microservices》一文是如:
Martin Fowler
In short, the microservice architectural style is an approach todeveloping a single application as a suite of small services, each running inits own process and communicating with lightweight mechanisms, often an HTTPresource API. These services are built around business capabilities andindependently deployable by fully automated deployment machinery. There is abare minimum of centralized management of these services, which may be writtenin different programming languages and use different data storage technologies.
如 Martin 所言,将单体应用拆分为一组微小的服务,每个微小的服务单独运行,服务间可通过如 RESTful API 这样轻量级的接口交互,这些服务以业务能力为核心,用自动化部署机制独立部署。这些服务可以用不同语言开发,用不同技术来存储数据。
以我理解来看,微服务架构的特性如下:
将单体应用进行解耦,按照一定方式(如:业务分类等)拆分为多个微小的服务,微服务间相互交互以完成实际业务流。
微服务间通信方式更轻量化,如:RESTful。
各微服务支持单独部署、单独运行。
各微服务的开发语言不限,可交叉选择不同语言。
简单来说,微服务其实是从早期的 CORBA、COM+ 等技术,到后来的 SOA、RESTful 架构,是一种分布式计算思想的延续。
具体来说,把单体应用拆分为一个一个微小服务,而这些微小服务又不依赖任何服务器,使其可以通过自动化方式独立部署,每个服务可以运行在自己的进程或 Docker 容器中,通过轻量的通信机制,能够基于业务能力快速构建,动态扩容,实现集中化管理的系统架构。
为什么要使用微服务
伴随着互联网系统的爆发、系统的多样化以及系统分层切块的演变,系统变得越来越复杂,调用链也越来越复杂,传统单体系统已经无法再支撑这种变化,因此微服务的思想也就顺应而来,用来解决这种现状。
传统的单体系统,企业往往需要耗费几个月乃至几年,才能落地一个系统,达到上线的标准。这就给一些小公司的前进带来了瓶颈,没人敢轻易研发、重构一个新的产品,但在现在互联网日益变革的时代,不得不大胆向前尝试,力争在最短的时间内完成一个新的产品。在互联网时代常常要求一周内完成一个功能或小项目,这种不断伸缩的业务形态,不断要求缩短的开发周期,使得我们需要在系统的扩展、伸缩、减低相互影响上做出文章。
那么,怎么才能达成系统的扩张呢?在 Microservice Architecture 一文中提到,我们需要将服务进行拆分,拆分为前置服务和业务服务,并在前端新增 SLB(Server Load Balance),用一组相同的前置服务组成及其来提供服务。
而减低各模块、各业务的相互影响,就需要将单体系统按照模块或业务进行拆分,以此来减低其耦合性。
上面提及到问题,在微服务架构下,给出了一些完美的解决方案。
1.模块服务化
单体系统,团队在多人协作开发时,往往会存在因代码、设计思路等差异而造成相互影响,相互等待对方的现状,而且系统的庞大也给后期维护带来诸多不便。而微服务最突出的一个特性“解耦”,恰恰解决了这种问题,让系统更加轻量化,便于多人协同开发而互不依赖。
2.独立部署,灵活扩展
传统的单体架构是以整个系统为单位进行部署,而微服务则是以每一个独立服务(例如:用户服务,文件服务等)为单位进行部署。用下图能够更好的体现:
独立部署,灵活扩展
左边是单体架构的集群,右边是微服务集群 。
各个服务都是独立部署,可以根据各自服务的特点进行适当调整,即:根据服务的吞吐量、压力等不同的指标,分别给出不同的部署方案(部署策略),使得资源更加充分合理的使用。这种灵活部署只有微服务架构才能实现。
3.资源的有效隔离
这是微服务设计的原则之一,就是每一个微服务拥有自己独立的数据源,假如微服务 A 想要读写微服务B的数据库,只能调用微服务 B 对外暴露的接口来完成。这样有效避免了服务之间争用数据库和缓存资源所带来的问题。
资源的有效隔离
如果采用 Docker 部署,则每一个微服务实例在 Docker 容器上运行,更加完美的实现了服务器资源(内存、CPU 资源等)的有效隔离 。
4.多语言,多选择
在微服务架构下,因为有了模板服务化,各模块互不依赖的特点,对开发语言的选择就没有统一的要求,完全可以根据企业技术人员情况,不同模块的特点来选择不同的开发语言,让开发变得更加多样化。
5.团队组织架构的灵活
微服务架构设计的思想,改变了原有的企业研发团队的组织架构。传统的研发组织架构是水平架构,前端有前端的团队,后端有后端的团队,DBA 有 DBA 的团队,测试有测试的团队。
而微服务架构的设计思想对团队的划分有了一定的影响,使得团队组织架构的划分更倾向于垂直架构,比如用户业务是一个团队来负责,支付业务是一个团队来负责。这种团队组织架构,也更好的协同来完成一个系统。
IP 黑洞:目前无解,但仅对部分服务黑洞,如谷歌系(谷歌、推特、YouTube 等)
DNS 污染:为域名返回一个假的 IP。使用 hosts 文件强制指定域名对应 ip 或者使用加密的 DNS(DoH、DNS 签名等)
HTTP 劫持:因为流量不是加密的,GFW 作为天然的中间人可以直接进行篡改(如:重定向到 404 页面、劫持到反诈页面等)。可以使用 HTTPS 连接规避,但你可能会遇到 SNI 阻断
SNI 阻断:在客户端与服务器建立加密连接前,客户端会发送 Client Hello 报文,而这个报文是明文,并且一般都会携带 server_name ,GFW 可以知道你要访问哪个网站,对不在白名单(如:discord.com)的域名进行阻断。因为 server_name 实际上是一个扩展,并不强制,你可以不发送它来规避 SNI 阻断
当然,上述这种垂直划分只是一个理想的架构,实际在企业中并不会把团队组织架构拆分得这么绝对。
6.组件/框架多样化、成熟化
伴随着微服务出现,不断膨胀,各类技术组件、框架应用而生,为我们的开发降低了成本,加快了项目的开发周期。这些组件/框架纷纷落地投产,变得更加的稳定成熟。Spring Cloud 家族就是一类典型的代表,后续文章将在详细介绍在微服务中的技术选型。
正因为微服务上述这些特性,使得在微服务的影响下,各类项目顺势崛起,为各类中小型软件公司带来了希望。
杀手锏,tcpioneer#
影视飓风2024/10/9 补档:清晰度不如4年前!视频变糊是你的错觉吗?
磁力链接:magnet:?xt=urn:btih:ffb877b0e3e964cb307048542faa22571f515245
凯恩斯交叉
发布时间:2016年10月1日
对于西方经济学,学校开设了一个学期的课程,授课的孙老师上来就同我们讲:学时不够。
她认为这门课最好拆分成微观经济学和宏观经济学两个学期,才能讲的稍微那么完整入门一些。 接着还应当讲高级的内容,加入计量经济学和数学分析。无奈培养计划安排如此,她也没有办法。 在推荐材料上,老师给我们了推荐曼昆的《经济学原理》上下册,很厚的两本书。 我最怕大部头了,可是实际读来却发现,老师果然很懂我们的智商。 几堂课听下来,我只记住了那朗朗上口的 “比凯恩斯还凯恩斯的凯恩斯交叉”。 可读那本大部头,就好像有一个老头在旁边给我讲故事,娓娓道来,栩栩如生。
其实十年寒窗上来,读书也是有循序渐进的道理,一年级有一年级的教材,三年级有三年级的教材。 但是大学老师推荐的书就不一样了,往往是经典中的经典,或者是同自己相关的利益团体出版的内容。 这就容易导致什么问题呢?我们刚接触这个领域,不知道什么是好书,什么是烂书。 用心一点的老师,会在幻灯片推荐材料的那一页注明:哪些是适合初学者看的,哪些是适合到达一定阶段的人看的, 哪些是学者们还在研究的… 哪些是我们教学组编的,也是很不错的参考资源。嘿嘿,懂你意思。
一些科研型的老师,在写文章列参考文献的时候没有这样的需求,提到了就加上去。
最可怕的是有些推荐材料的第一书目就是一本高层次的书籍,初学者读起来晦涩难懂。 所以有些时候读书如爬山,学生境界不到,就会说一些书是烂书;境界起来了,又觉得写得有些道理;等自己独成一派了,又觉得写得很有局限性。 要看山是山,到看山不是山,最后看山还是山。不同的作者只是提供了不同的角度,哪能说高鸿业的书就一定是不如曼昆的呢?
阳春白雪,下里巴人,各有千秋罢了。
国内的教材喜欢写得精辟,国外的教材喜欢写得通俗。 前者作为教科书,需要老师带着解释;后者像一本故事书,学生自己读也能津津有味,故事书的篇幅当然会更多了! 这里也体现了老师和大师的差距,大师能够站在更高的视角,却用更质朴的语言去解释一件事情,可以提供不同的角度。 而普通学者尚未达到那样的境界,所以只能从自身专业的角度来谈问题,不考虑衔接的过程。 这可能就是常识水平差异,或者说知识的诅咒。
读者也要对自己有些了解,不然明明是自己读得太浅,反倒要说作者的不是,容易贻笑大方。 烂书绝对是有的,但是你不多读点书,怎么能在很快的时间内知道这是一本初学者书籍,还是高观点书籍,亦或者是彻彻底底的烂书呢? 一种广为流传的说法是:先多读书,再读好书,最后才是好读书。倘若不自己经历一番,怎么会明白其中的道理。
同学期授课其它课程的,还有《应用文写作》的林老师,《高等数学》的沈老师,以及《线性代数》的满老师等人。 满老师上第一堂课时很有趣,她也列出了一系列的推荐材料,并且点评了同济大学的教材和其它教材的区别。 她带着欢快的语气告诉我们:“同学们,线性代数很有意思的,线性代数的知识以后在很多领域都会被用到的。”
坐在下面的学生,包括我在内,都是刚从高中环境上来的,看上去一片呆气 —— 因为大家都尚不知道,推荐材料这一页,需要一个学者用多大的心血去整理。 可能只觉得正课的内容考试会考到,所以才是关键的吧。 满老师仿佛没有得到想要的回应,眼神变得有些失落,但又很快进入下一个话题,可能是已经习惯了。 我们默认用同济的教材上课。
他给我们推荐了很多书,其中一本叫作《乌合之众》。 学期末的时候,他给我们放了一部德国电影,叫作《浪潮》。 这两部作品之间其实存在着很大的关联,范老师告诉我们要保持思考和判断,不要轻易地相信某个人的话都是对的。 有一点逻辑悖论的感觉,范老师的话都是对的吗? 但范老师是上大学以来第一个直接对我(们)说这些话的人。
我很喜欢 Philip Guo 个人网站的风格,有 VLOG / PODCAST / ARTICLES 三种记录生活的方式,他的创作原则我很喜欢:
One of my main hobbies is writing articles on this website (and more recently, recording podcasts and vlogs). One heuristic I use when deciding what to write is to ask myself:
Will at least 100 people care about this topic three years from now?
The following don’t qualify, so I rarely write about them:
commentary on news events or fads
reactions to someone else’s article
personal notes that only my friends would care about
Finally, if I see many people writing about a topic, then I avoid it unless I have a drastically different perspective.
沈向洋博士的七个经验和教训
发布时间:2020年11月16日
内容距离首次发布已有 1683 天,请注意时效性。
全文摘录自,最近沈向洋博士在 X-Talk 上的演讲,
你不可能做所有的事情
在自己的专业一定要做得很深
一定要把故事讲好
一定要有一个远大的目标
Difficult, not Impossible
一个一个项目加起来
一定要明白自己前进的方向
你不可能做所有的事情
第一件事情,当你职业生涯刚刚起步的时候,一定要明白 你不可能做所有的事情,你可能有很多很多的想法,但是不见得什么事情都可以做到。就像我刚才讲的,我一直想做教授,但是我在最后一刻改变主意了,我一个朋友说服了我去参加他的 start-up. 他是这样说服我的,我跟他在车里谈了四个小时,他叫 Eric。他说:Harry,我最后终于明白了,你想当教授。他说:那简单,你先参加 start-up,先去赚很多很多钱,我给你的母校卡内基 · 梅隆捐一个 Harry Shum Professorship,指定你是第一个获奖人,这样的话你可以给学校再捐一个 Harry Shum Robotics Center. 当然这件事情没有发生。事实上我后来参加了这样一个 start-up 以后,很快又加入了微软。当时很重要的一件事情,就是第一个孩子出生了。当时我想了一下两件事情不可以同时成立的:做 start-up 和带一个小孩子。但是孩子生出来了以后,你就没有办法去 Get rid of(摆脱),所以你只能 Get rid of startup.
在自己的专业一定要做得很深
第二个 lesson,其实人一生的职业旅程非常长,首先我们职业生涯刚刚起步的时候,非常重要的一件事情就是 在自己的专业一定要做得很深,你一定要有一件事情、某一个方向,大家知道你做了点什么样的工作。我自己非常幸运参加微软,博士以后在微软研究院的时候,跟很多的同事,当时计算机视觉领域中非常优秀的工程师和科学家一起工作,包括待会儿上台的张正友博士,一起做了很多方面的工作。我自己做的一个方向叫做 Motion Estimation,特别当时做全景图,就是 Panoramic Image,拍了几张照片以后把它拼起来这样。我也很自豪地跟大家讲,今天大家用手机拍全景图的时候,说不定也用了我们的技术。这件事情很重要,特别是你刚刚起步的时候。很多人忘记了,如果你不在某一个方向做到足够深的话,大家就记不住你。
一定要把故事讲好
第三点我想重点讲一下,就算是对工程师和科学家来讲,除了你专业做得好以外,最重要的一件事情大家不要忘记,就是一定要把故事讲好。我一辈子听过很多了不起的演讲。很多很多年以前,在 SIGGRAPH 会议上我听过一个 keynote,是迪士尼的 VP of Imagination Engineering 做的演讲。为什么讲故事很重要?他说你在迪士尼看了那么多的电影、那么多的动画,不同的历史阶段,从二维动画到三维动画,到现在 VR 这些东西。这些实际上都不是最重要的事情,最重要的是大家喜欢迪士尼是因为迪士尼背后的这些故事。对我们很多科学家、工程师来讲,我们也经常要做一些报告。大家做报告时,可能里面写的字非常非常多。我自己很幸运在很年轻的时候,研究生阶段就有机会参加了 SIGGRAPH,1995 年。这是我好朋友 Eric Chan 的报告,这是我一辈子见过的技术 Presentation 里面得到掌声次数最多的。他当时在苹果公司,写了一篇文章叫 QuickTime VR,当时我在苹果公司做实习生。Eric 在台上,他有特别的 Style,整个十几分钟的演讲,一共有八次掌声,我记得我在台下看着 Eric 演讲的时候,觉得非常了不起。一个中国人讲一口台湾腔英语,能够让大家有八次掌声,这非常非常了不起。所以我就想在这里跟大家讲 story telling 非常重要,比如做 presentation,一张 slide 上面不应该超过七行字,多了以后大家看不清楚也搞不懂,这些东西都非常非常重要。
一定要有一个远大的目标
第四个 lesson,我想给大家讲的是一定要有目标,一定要清楚自己最后要追求什么。我自己非常荣幸,2004 年开始担任微软亚洲研究院院长,当时我们定下来的目标,就是说一定要成为世界上最了不起的研究院,后来我们也基本上达到这样一个目标。我记得 MIT Technology Review 曾经写过一篇封面报道叫《The World’s Hottest Computer Lab》。那篇文章出来时,我正好在美国出差。美国机场这些地方有专门卖杂志的,我当时不知道 MIT Technology Review 的文章会出来,封面上就是我两个同事的照片。然后我就看看那个卖杂志的女士,我就跟她讲「May I have all your copies?」那个女士很高兴,先收了我的钱,然后问我「May I ask why?」然后我就很自豪地跟她讲,我说「Look, those two people on the cover, they work for me.」所以你一定要有一个远大的目标,做了不起的事情。
Difficult, not Impossible
第五个 lesson,其实是我这么多年工作,特别是后来从研究部门到了产品部门以后,对处理所有问题的复杂性有了更深刻的认识以后,自己创造出来的一段话,叫做「Control the controllable, observe the observable, leave the rest alone」. 把握可控的,留心可见的,余下顺其自然。我本科的时候念的是自动控制专业,大家有一些控制理论背景的话,就会对这个句式很熟悉。因为当时我从研究院调出去以后,到微软的产品部门做搜索引擎,做 Bing。我们的工作当然就是跟谷歌竞争,跟谷歌竞争当然不是开玩笑的事情,大多数人都会觉得跟谷歌弄的话下场肯定是很悲惨的。但是我很自豪地跟大家讲,现在微软 Bing 这样一个业务线的话,一年也挣 100 亿美元,所以我们其实做得还是相当不错的,在美国超过三分之一的搜索量来自于 Bing。当时大家就在想:一个研究院来的人,他怎么可以去带产品线?所以我当时就在想,作为一个新兵到产品部门去工作,大家都问:你到这里来也没有做过产品,你可以给我们带来什么?我说我也不知道可以给大家带来什么,但是我想我至少可以跟大家保证,等我哪天离开这里的时候,大家会 remember me as the VP who knows how to party。所以你一定要想到,你自己有哪些地方可以去 motivate 大家,可以把大家团结起来。哪些东西你自己是可以 control 的,如果你不可以 control 的话,你召集了也没有用,你就应该去观察这样的一些事情。所以在这件事情上大家一定要想。这是我最喜欢的一张 slide,大家看过《教父》的话都会知道,Michael Corleone 经常讲,不管什么时候你遇到多大的困难,Difficult, not Impossible. Remember that.
一个一个项目加起来
第六个 lesson,我想跟大家分享的,实际上我自己非常有幸,当时和前同事 Jim Gray,这个你们可能还记得,1998 年图灵奖得主,以前是微软研究院的研究员,他非常非常了不起,很不幸后来他独自驾船出海失踪了。我对 Jim 非常敬仰,因为我当时正在从研究院去产品部门工作,我就请教他。我说:「Jim,你这一生的职业生涯非常了不起,得了图灵奖,又在研究院工作过,又在产品部门工作过,好像你从来就不介意你到底是在研究院工作,还是在产品部门工作。」他就给了我一个非常好的 lesson,我还记得当时我们是在台北的一个酒店顶楼上交流。他说:「我从来不担心这个问题,到底是在研究院,还是在产品部门。你要选择你哪一个项目,你一生到最后的话,实际上就是你做过哪几个 projects. 」其实我的好朋友高文院士也讲过,他说你的职业生涯到了一定地步以后,大家就看你背上到底写了哪几个字,就是你做过哪几个 projects。特别是我们 50 位得奖的青年才俊,事业起步开始的时候,未来还有很光辉的道路。一定要记住,一个一个项目加起来。
一定要明白自己前进的方向
最后,我想跟大家分享一个,我自己觉得特别是在美国的时候,跟美国的同事讲中庸之道。我有时候讲不过老美的时候,我就跟他们讲「子曾经曰过」,然后我把我想讲的东西跟他们讲一遍。中庸之道。当然以前的路跟现在不一样,现在都分成左右两道。但是中庸之道里面还有一件非常重要的事情,并不是你只是 walk in the middle of the road,这里更重要的一点是 keep the direction,一定要明白自己前进的方向。
重新开始写技术文章
发布时间:2020年11月23日
记得上大学的前两个学期,我很喜欢在第三方平台写技术博客,除了写一些题解外,凡是遇到了什么新奇的好玩的东西都需要记录下来。后来在 IBM Developer 和 MSDN 读了一些技术作家写得比较好的文章,才发现我之前写的哪里是什么“技术博客”啊,分明是些零零散散的“笔记”罢了,再过不久愈发觉得里面的东西实在太没有营养,于是羞愧地把它们给删除了,此后只在静态博客里发布随笔,还写了篇名为《技术博客黑洞》的文章作为吐槽。
随笔里记录思想,其实并不是在务虚,毕竟“正经人谁写日记啊?”只是如果一个人做事情完全只谈方法论而不提具体实践,他不是在扯淡,那就是扯到蛋了。如今要开始写这些过几年后我可能觉得“未必是那么回事儿的东西”,纯属无奈,只希望自己作为老拖延症患者,真的能把这件事坚持下去。
国内的很多程序员写博客,复制粘贴抄袭者居多,有些人把翻译当作原创,慢慢地风气就歪了。甚至如今还有集成多篇文章于一体的究极缝合怪,读起来云里雾里,成功实现了 1 + 1 + 1 = -100 的效果。
命运巧合,当时为了将 Coursera 的 Deep Learning 课程引进国内,中途配合 Andrew 做过一段时间和技术翻译有关的工作,在相关圈子里混迹过一段时间。感慨国内程序员翻译国外文档时的粗制滥造,占着茅坑不拉屎的情况时有出现(比如现在的 Pytorch 授权给 ApacheCN 的中文文档就是一个笑话),部分译者还爱自吹自擂,有甚者更觉得自己是个名家角色了。对于初学者来说,这类翻译读着痛苦,但聊胜于无,英文不好的只能接受,英文要是稍微好些的也不会愿意去看翻译后的文章。毕竟前沿期刊会议论文、官方网站和文档、YouTube 上的视频,难道它们不香吗?因此除了值得被引进,由权威人士翻译校对的经典教材,否则我不推荐任何人找翻译后的中文资源学习。写到这儿,我想起有人曾经在知乎问 Evan You:“为什么 Vue 的更新记录不能有中文,中文文档也一直滞后?” 尤的回答也很干脆:“为啥我要牺牲陪老婆孩子的时间来替你省学英语的时间?”
技术类文章用英文语法来写,有着得天独厚的优势,非常简洁易懂,概念可直接对应源码命名。有的地方硬要译成中文的话,感觉就像“我要用靴子踢你的屁股”一样奇怪。所以即使是国产开源项目,我依旧推荐优先写英文文档。写 API 文档算是比较轻松的,写教程的话难度高了那么一点儿,而要写好技术文章,如果经过翻译这一道工序,总不如原滋原味,所以要么让一个英语和技术功底都好的人来做翻译,要么简直写原创性的有营养的文章。我厌恶现在的 AI 自媒体,也正是因为他们从 Medium 等国外媒体网站或科技公司的官方博客里翻译几篇文章,就可以发在自己的公众号里了,是否经过授权我不清楚,问题在于有时译得不准确;若是看到些中文原创的内容,大部分时候只是在恰饭罢了,这就是流量变现吧!然后你就会看到一堆人前赴后继地模仿,各种抄袭洗稿,去运营粗制滥造的公众号。
没办法,这个时代的人们喜欢以流量为指标,这就导致很多人为了完成相关 OKR 而不择手段,只在乎结果,不在乎过程和意义是否正确,导致表面光鲜的背后隐藏着巨大的风险。今天早上听了汪海林老师在《不要再让选秀艺人影响影视行业了!》视频里说的话,我在想写作平台何尝不是如此,互联网媒体何尝不是如此,主流价值观都要被资本的手段给玩歪了。越傻 X 的东西越有人看,等到有人写干货出来了就一起诋毁它,劣币驱逐良币,大家越来越不知道什么是好作品,反而不断跟风弄出更多 Bull Shit.
如今天元 MegEngine 开源了,版本也到了 1.0 正式版,社区里却几乎看不到什么面向初学者的原创文章。一个如此极客的深度学习框架,缺少 Storyteller,或者说 Developer Advocate,这种情况实在让人着急。毕竟 Tensorflow 和 Pytorch 的生态已经趋于成熟,作为国产框架要进行追赶实在很难办。
然而干着急也不是办法,事情还是得有人干,不论将来是否有成效,总归要下定决心试它一试。目前打算在自己的博客里面瞎写点,等内容和质量上来后再分享出去,虽说客观真理需要我们在不断的认知刷新中求得,但也希望自己不要在当前阶段写出什么误人子弟的东西。
原文出处:https://jmmv.dev/2021/04/always-be-quitting.html
A good philosophy to live by at work is to “always be quitting”. No, don’t be constantly thinking of leaving your job 😱. But act as if you might leave on short notice 😎. Counterintuitively, this will make you a better engineer and open up growth opportunities. A thread 👇. / 在工作中,一个好的生活哲学是“总是准备离职”。不,不是让你一直想着离开你的工作😱。但是要表现得好像你可能会在短时间内离开😎。与直觉相反,这会使你成为一个更好的工程师,并开启成长的机会。以下是具体的做法👇。
So what does it mean to always be quitting? It means “making yourself replaceable”; “deprecating yourself”; “automating yourself out of your job”. You might have heard these more-popular names (which you’ll need to do your own research) and they hint at how to act. / “总是准备离职” 具有什么含义呢?它意味着“让自己可以被替代”;“让自己过时”;“把自己从工作中自动化出去”。你可能听过这些更时髦的说法(你得自己琢磨琢磨),它们暗示了如何行动。
The key lies in NOT being indispensable. If you are, you’ll be stuck at your specific job for as long as that job is relevant with little chance to disconnect (no vacations, no growth). And when (not if) the job becomes unnecessary, so will your position. / 关键在于不要让自己变得不可或缺。否则的话,你会在你的特定工作中停滞不前,只要那个工作还有意义,你就很难脱身(没有假期,没有成长)。而一旦(这不是假设)工作变得不必要时,你的位置也会变得不必要。
Paradoxically, by being disposable, you free yourself. You make it easier for yourself to grow into a higher-level role and you make it easier for yourself to change the projects you work on. Confused still? Here are 10 specific things you can do: / 矛盾的是,通过让自己变得可有可无,你解放了自己。你让自己更容易成长为一个更高级别的角色,你让自己更容易改变你工作的项目。还是感到困惑?以下是你可以做的 10 件具体事情:
📕 Document your knowledge. Every time someone asks you a question, they are highlighting a gap in the documentation. Take the chance to write the answer down (in a document, bug, code comment—wherever) so that the next person doesn’t need YOU. / 📕 记录你的知识。每次有人问你一个问题,他们都在突出文档中的一个缺口。抓住机会把答案写下来(在文档、bug、代码注释——无论哪里),这样下一个人就不需要你了。
🏁 Document your long-term plans. People should know what’s coming up in your projects and/or team by looking at those plans, not by relying on you to tell them “in real time”. Plan a few months ahead so, if you leave, your peers won’t be lost from day one. / 🏁 记录你的长期计划。人们应该通过查看这些计划,而不是依赖你“实时”告诉他们,来了解你的项目和/或团队即将发生什么。提前几个月计划,这样,如果你离开,你的同事们从第一天开始就不会迷失。
🤝 Document your meetings. Keep (public, within the team) notes for all meetings you attend, listing who was there, what was discussed, and any conclusions. Reference those notes from design documents. Your replacement will need these to catch up. / 🤝 记录你的会议。保留你参加的所有会议的(公开的,团队内的)笔记,列出谁在那里,讨论了什么,以及任何结论。从设计文档中引用这些笔记。你的替代者需要这些来赶上。
🚶♂️ Bring others to meetings. If not a 1-on-1 and you are the only person from your team attending a meeting, involve someone else. Different perspectives are useful, but more importantly, you are avoiding becoming the only point of contact. / 🚶♂️ 把其他人带到会议上。如果不是一对一的,而你是你的团队中唯一参加会议的人,那么就让别人参与进来。不同的观点是有用的,但更重要的是,你避免了成为唯一的联系点。
👩🔧 Train people around you. The goal is for them to be independent (what is usually considered “seniority” in a typical engineering ladder). Familiarize them with the plans and technologies and make sure they know how to use the documentation. / 👩🔧 培训你周围的人。目标是让他们独立(这通常被认为是典型的工程阶梯中的“资深”)。让他们熟悉计划和技术,并确保他们知道如何使用文档。
👩🎓 Identify and train your replacement. In the same vein as training others, to switch roles you’ll need to replace yourself. Identify who that replacement might be and actively and continuously coach them. / 👩🎓 识别并培训你的替代者。与培训他人一样,要换角色,你需要替换自己。确定可能的替代者是谁,并积极持续地指导他们。
🔑 Give power to the people. Trust them to do the right thing. If you are in a leadership position, don’t make it so people come to you asking for permission. Let them make their own choices. Guide them so that their choices are based on the right data. / 🔑 给人民权力。相信他们会做正确的事情。如果你处于领导地位,不要让人们来找你寻求许可。让他们做出自己的选择。指导他们,使他们的选择基于正确的数据。
📧 Do not make yourself the point of contact. Establish mailing lists or other forms of communication that can accommodate other people, and then grow those groups. (The exception is when management needs names for accountability.) / 📧 不要让自己成为联系点。建立可以容纳其他人的邮件列表或其他形式的通信,然后发展这些群体。(例外情况是当管理层需要负责人的名字时。)
👨💼 Delegate. Once you have given power to others, included them in groups and meetings, and documented your knowledge, they’ll be ready to take work from you. Delegate work that can make them grow and focus on the things only you can do. / 👨💼 委派。一旦你给了他人权力,让他们参加了小组和会议,并记录了你的知识,他们就准备好从你那里接手工作了。委派可以让他们成长的工作,并专注于只有你能做的事情。
🏫 Always be learning. Take the chance to grow your knowledge in any area you are interested in, and keep it fun. Bonus points if that area aligns with the future path you want to take. / 🏫 始终在学习。抓住机会在你感兴趣的任何领域增长你的知识,并保持乐趣。如果那个领域与你想走的未来道路一致,那就更好了。
Note that nothing here implies abdicating responsibility. You still have to be responsible for all the projects and teams you own, and you have to be for as long as you are in your role. This is important because this responsibility is what will open up new gates. / 注意,这里没有任何东西暗示放弃责任。你仍然必须对你拥有的所有项目和团队负责,只要你还在你的角色中,你就必须负责。这一点很重要,因为这个责任是打开新大门的关键。
Lastly note that, by doing all of the above, you are actively making your whole team better, not just yourself, even if you are an IC. In fact, you are practicing a subset of the skills sometimes associated with staff/principal+ engineers. / 最后要注意的是,通过做以上所有的事情,你正在积极地使你的整个团队变得更好,而不仅仅是你自己,即使你是一个 IC(Individual Contributor,指的是那些主要通过自己的个人工作成果,而不是通过管理他人来贡献的员工)。事实上,你正在实践一些与员工/主管+工程师有关的技能的子集。
文档中就应该提供为零基础用户服务的教程,这是用户基数最大的群体。毕竟高级用户不是凭空产生的,回想当年我们学习某个知识迷迷惑惑的阶段,是不是突然看到一篇好文章,里面的只言片语或许就让自己茅塞顿开了?早期我们寄希望于社区用户,但得到的几篇教程质量都不达标,我甚至一度怀疑是不是因为国内开发者写博客的门槛太低了导致平均质量已经降下来了。后来觉得还是要从自己身上找原因,Pytorch 最初的教程是核心开发者 Soumith Chintala 自己写的,如今由 Stanford 的 Justin Johnson 进行了补充,MegEngine 也应该有一套官方自己的入门教程。半年前 MegEngine 的官网架构中,教程、文档、API 是三个独立开的东西,大家都在摸着石头过河。而现在的文档,是一个特别大的概念(在上面的表格中你应该了解过了),有充分的发挥想象力的空间,处处可改进,最终要实现的目标是:大家香,才是真的香。
回到给农大学生做支持这件事上,我总不能和对方说:“你们先去把 Pytorch 教程看一遍,然后就会用 MegEngine 了。” 然后露出一个尴尬又不失礼貌的微笑吧?只好让她们先去看 Andrew 的 DL 视频,看不懂的地方就答疑,自己琢磨着开始写 MegEngine 版本的教程作为框架侧的练习材料。这时遇到了第二个难点: 人的思维一旦变复杂,很难还原到初学者视角,我们容易把自己的常识误以为是别人也已经掌握的知识,从而在写教程的时候写出很抽象的内容。 纯粹以个人理解视角写出来的东西也不能算得上是面向大众的教程,很多时候只能说是个人笔记。如果读过糟糕代码风格 + 只言片语的算法题解博客,又接触过良好代码风格 + 切中要点解释的算法题解博客,感触应该会比较深。好在我个人比较幸运,之前曾有过课程助教的经验,平日里也习惯了多角度观察,所以切换视角这个事情对我来说已经不是那么困难。基本上白天写完一篇教程,晚上就会找到些写得不友好的地方。
另外要注意的是,获取教程受众的反馈很重要。 国人比较谦虚,能力越强的人越是喜欢自己把问题研究清楚,等到视野开阔理解能力变强后,就算教程存在着瑕疵,也被过往经验自动完善掉,很难意识到是教程本身存在着不合理的地方。我一直和几个研究生说 “如果觉得没写清楚的地方就多反馈,不要怕问蠢问题。这不会麻烦到我,而是在互帮互助。” 实际上大家的理解能力都在线,真正焦虑的反而是我,怕没讲明白学生却不好意思说,宁可自己花额外的时间去消化,这种情况应该被视作潜在的教学事故。
不是所有人都是天生的教育工作者,Andrew 在 Linkedin 上有一篇很热门的文章,讲的是如何通过刻意练习,让自己成为更受学生欢迎的老师。即使是同样的材料,给不同的讲师去教授,起到的效果也可能截然不同。比如我见到过一些老师用 CS231N 的课件去给学生讲课,但完全体会不到 Andrej Karpathy 讲课时的感觉。再举个例子,直到现在,我还是会偶尔想起当年上算法分析课时的那名教授,他主要研究计算机网络方向,和我们说因为写文章要用到算法,最近开始研究算法导论了,就顺带申请教授这门课,希望和大家一起进步… Ohh 我亲爱的老师,时间就是性命。无端的空耗别人的时间,无异于谋财害命。
一定要使用自己熟悉的教学材料,而不是用大家都觉得好的材料去给学生做临时翻译,一是没有原汁原味的体验,二是容易让其他人对教学材料和原始作者形成抵触和误解。 在写零基础教程系列的时候,我一直在研究如何能够让它变得更加 Beginner friendly, 一个用户说要不要直接用李沐的《动手学深度学习》或者是 Coursera 上面 Deep Learning Specialization 的编程作业,我认为这些材料都很好,但不适合放在 MegEngine 文档里,决定还是自己搞。最初的几版往往都在加内容,担心举例不够;而最后往往都在做减法,去掉一切当前不用过度关注的数学细节。现在看似是完成大体内容了,而我却只能说差强人意,还需要经过长期的检验和更新,自己也不是什么领域大牛,特别怕写出来的东西误人子弟,早就做好了挨骂的准备。教程最后的问题都倾向于让人去主动思考而不是留客观题,因为我是反对培养做题家的, 在学习新知识的初期,建立基本的直觉和浓厚的兴趣是重中之重,至于如何启发思考,把握内容的节奏感和分寸感(甚至像艺术品一样去打磨它们),则是更进阶的话题。好消息是,我们如今能够从研究实习生那儿获得教程的支持,其实任何人都可以尝试提供写教程的思路。
从学习阶段进入使用阶段后后,教程的使命就结束了,用户指南和 API 参考成为了文档中经常被光顾的地方。这就好像一个神经网络模型已经得到了充分的训练,接着我们只需要用训练好的模型去执行推理任务即可,那些费时良久的反向传播和参数优化时光都变成了过去式。除了用户指南,MegEngine 文档中还提供了开发者指南,因为 MegEngine 是一个开源项目,我们希望社区中的开发者能够借助文档中的信息更有效地参与进来,为 MegEngine 的发展作出贡献。指南的写法,和教程还真有挺大的区别。可惜的是由于各种原因,到现在为止,MegEngine 的文档也只是完成了整体拼图中的极小的一块,依旧是“路漫漫其修远兮“,需要“上下求索”。但如今我们有思路啦,找到方向啦,不再是当初摸着石头过河的状态了,并且我坚信它能按照预期设想伴随着 MegEngine 持续发展下去。
前面用了很多篇幅来讲教程,是因为这是前段时间的工作重心,亦对后续的工作有很大启发。然而我们也说过了,教程只是文档世界的一小块拼图,想要更好地理解文档建设的意义和目的,可以有很多理解的视角,这次我选择引用前不久看到的一句话作为解释:
用户通常只会用到软件中 15 % 的功能,而不同类型的用户使用的往往是同一个软件中那不同的 15% 部分。
道理挺容易懂的,以 Microsoft PowerPoint 为例,初级用户往往只需要用到基本的功能,比如图片、文字和页面的编辑;随着需求的改变,有些人研究大纲视图、讲义备注和演讲者模式,有些人研究动画效果,有些人研究如何优雅地插入
公式(关于这个问题,Windows 用户可以试一试 IguanaTex;MacOS 用户可以试试 IguanaTexMac;另外可以使用 Mathpix Snip 进行 OCR 识别),这已经开始进入插件范畴了。如果用户需要的那 15% 无法在某一个软件中(或者以插件的形式)提供,自然地就会去寻找满足需求的同类替代品——比如在 3D 建模领域,建筑、机械类 CAD 平面设计经常用 3DMax,影视动画设计经常用 Maya,游戏建模经常用 Unity/Unreal,包装设计经常用 C4D,雕刻用 ZBrush,还有一个开源的 Blender,目前各项功能都是 60分… 生活中有非常多类似的例子。
从 15% (不用在意这个值是否客观)这个点出发,可以引申出非常多的思考:
一个用户在刚接触到新产品时,学习的是最简单的功能,界面的友好程度、功能上手的难易程度等会直接决定用户的留存。但是由于对不同的用户这 15% 是不同的部分,因此需要分别提供对应的入口。而且用户的属性不是一成不变的,作为软件供应商,还要思考如何帮助用户发展自我,从初级萌新转变为高级资深玩家。这个时候,提供不同入口的教程尤其重要。
从软件研发的角度来看,知道自己在干什么很重要。或许我们正专注于实现这 15% 的关键特性,脑海中知道其中所有的技术细节。但我们有义务形成这样一种大局观,即我的设计在整个系统中扮演着什么样的角色,它又是怎样深刻地影响到上下游各个环节的,可以理解成系统架构的意识。这个时候,信息流程类文档尤其重要,我开始尝试推广切实可行的 MEP 方案。
虽然整个软件系统由很多个所谓的 15% 模块所组成,一些基础设施(底层设施)的 15% 模块是不直接面向用户的,研发过程中不可避免地会面临人员流动的情况。我们如何保障在不破坏原有大体设计思路的情况下进行持续稳定的开发和迭代?如何保障公开接口稳定性?这个时候,设计文档尤其重要,理应存在于开发者指南中。
MegEngine 文档或许已经能够满足某类 15 % 用户的需求了,但这还远远不足够。
我们需要建立这样一个认知, 写文档本质上依旧是在为解放自己的生产力进行铺垫 —— 聪明的程序员都懂得如何偷懒,巴不得能用脚本尽可能地去自动化一切工作。我们可以研发出深度学习框架来简化训推流程,用户也可以基于此提供更加高级的接口或库。但我们往往会忽略这样一个事实:在一个团队中,研发人员的日常绝不仅限于纯粹地去实现代码逻辑功能,很多时候都需要和人交流,大致可以分为以下几种情景:
我们要教会使用者如何去用我们开发出来的东西,因此教程必不可少。
我们要实现一个东西,因此需要找人讨论实现方案,提供 Proposal / Prototype 实现。其中存在着信息的收集、同步、讨论、达成一致决策的过程,如果不采取任何形式的总结,将来很有可能需要向其他的想要了解技术细节的人解释 “当初为什么要这样做(不那样做)” ,更糟糕的情况是,你可能需要将同样的事情向不同的人解释了一遍又一遍;而人的记忆不是永远可靠的(人脑也不是用来机械记忆的工具),我们无法保证将来总是能回忆起曾经的各种决定。一旦产生元老级别的人员流动,很多未来得及记录的信息就像青春岁月一样飘散在风里了。
我们要读懂别人的实现,碰到了困难,然后跑过去问实现者。对方可能不一定有空,但强者往往又表现得十分友善,基本上有问必答,最终会占据掉原本用来安排进行其它工作的时间;当然你也可以询问其他了解这块的人,但口口相传的方式更加无法保障信息的准确性, 我们需要制定流程规范,目的是为了提升整体的工作效率,口头约定并不具有约束效果;
随着工作经验的丰富,我们开始指导实习生的日常工作。一些我们觉得属于常识的东西,可能对于新人来说就是全新的事物,尤其是限定于项目中的一些细节,毕竟一些坑可能永远不会在其它项目中遇见。虽然我认同 “该踩的坑终究还是要自己踩一遍”的说法,这样印象会比较深刻。但作为过来人,提供辅助视角作为参考,会更有帮助;甚至我们可以刻意地去设计一些优化过的坑来帮助实习生更好地成长,减少其它的干扰项。
即使是写教程,也会因为我们之前提到的 15% 理论而需要达到 “因材施教“ 的效果。例如有的用户当前只想学习如何训练模型、有的用户只想学习如何推理部署;再细化一些,有的用户只想知道多机怎么用,而有的用户只想搞明白量化的原理。这个时候,文档需要针对这不同的 15% 用户,分别提供最合适的入门教程。比较常见的情况是,用户在弄明白如何去训练一个模型后,有可能开始对模型部署感兴趣了;而对另外一部分的用户来说,可能完全没有阅读过训练教程,只希望能够用现成的模型进行部署。同样地,从学习阶段过渡到使用阶段后,用户更需要的是简明的指南。
文档的另外一个作用是服务于项目本身的开发者,可我们是那么地讨厌写文档,又那么地讨厌别人的项目没好好写文档。这时候不得不对比一下“直接交流”这种形式,很多人都会以口头交流更高效为由拒绝写文档。诚然,相较于打字聊天这种形式,面对面的交流可以和帮助我们保持专注和高效。但如果是频繁地解释已经实现的东西,其长期效益未必高过写文档。毕竟不是所有人都是语言大师,交谈过程中废话的比例有时候会比预想的多得多,想要强行地让别人兼容自己的思维模式,亦或者是做到对他人的兼容,都是极端困难的,需要大量的练习。如果不在正式的交流开始之前拉齐双方的认知,可能聊着聊着发现原来不在同一个频道。
很多时候在一场对话中,我们可能会觉得 “怎么讲了这么久,对方还是不明白?是不是理解能力有问题啊?” ,而对方可能早就在内心吐槽 “这个人讲的是什么东西啊?根本没把东西讲清楚,是不是表达能力有问题啊?他是不是根本不知道我问的是什么?”,总之锅肯定是对方的就没问题啦!与人相处是值得一生去探究的话题,而对话时进行的一些假设其实是我们与生俱来的弱点,对他人抱有严格的期待,而容易忽视掉自己身上不足的部分。不欢而散的情况也是有可能产生的,这其实还算好,更糟糕的情况是为了避免尴尬而不懂装懂,误认为这次交流是有效的,结果又要用更多的交流来填坑,我们真的做到科学高效了吗?
我在与人相处的时候有一个比较常用的小技巧,即借助于对方已经输出的事物来快速寻找共识。怎么理解这句话呢?要想了解一个人,直接问另外一个人 “你觉得这个人怎么样?” 得到的答案难免带点主观,要么被打哈哈糊弄过去;直接去和对方交流呢又会比较花时间,因此我倾向于去寻找并阅读他/她对外输出的内容,比如邮件、演讲、讲座、论文、博客、歌单、视频等等,尝试去构筑对方的认知体系,习惯他/她的表达方式,看能否找到融合点,这个探索过程通常是让人欣喜的,我们也在拓宽自己的可接受范围。应用于文档上面,则演变成我们在讨论技术问题的时候可以交流双方对同一篇文档的理解是否一致,不一致的话则求同存异,最好的情况是大家都得到了新的思路或者是观点,同时文档的内容也能够更新。如果觉得文档写得不好,吐槽文档的内容就是了(即对事不对人),然后一起分析怎样去改进它——只是交流而不做记录的话,很容易遗忘甚至是产生错误的记忆。通过总结文档的形式,让每一次的交流都能以某种形式沉淀下来,长期来看会节省非常多的时间成本,如若帮助到了别人,无形之中可能就放大了之前讨论的价值,推荐大家都试一试这种无情绪压力负担的输出方式。
我还想稍微提一下版式设计方面的细节,这是一种无意识地改善用户体验的途径。在这个创意无处不在的时代,越来越多的人成为设计师。简历、论文、PPT、个人主页、博客、活动海报、给客人的邮件、名片……,处处都在考验你的设计能力。几乎每次提到产品设计时,我都会和人安利 Robin Williams 的 《The Non-Designer’s Design Book》,时刻注意并尝试练习满足 “亲密性”、“对齐“、“重复“和“对比” 四大原则。在文档中体现的比较明显的地方是:我们会经常用到一些视觉样式元素,比如 note/warning 来避免过多的重复,形成局部的对比,否则用户读起来会很累。整个文档的配色风格也需要一致,字体图片的样式、段落的间距,处处都是可供改善的细节。
我还想要强调一下已知常见文档形式的局限性,以及现代化文档的发展趋势。由于历史原因,很多文档要求以纯文本的形式进行提供,这样对文本编辑器友好。但在一些概念解释的情景下,往往是一图胜千言的,因此我们需要主动地拥抱表达形式的变化,尝试用更加新颖的媒体手段进行创作和表达。一种比较好的实践模式是,首先想清楚你想要输出内容的范围,对应的受众有哪些。这个时候可以列举出一个模糊的大纲(可以参考一些写作范式),但不用要求它一开始就在内容上达到很完美的程度。经过一段时间的折腾后,我们应该能够得到最原始的文本,往往只能用于审视内容结构,无法达到直接发布的程度,我们可以根据版式设计的原则去添加合适的图片,正确的配图能帮助读者更好地理解内容。
如今不可忽视的一种内容表达载体的形式是视频,比较常见的做法是在一篇教程中,标题下方是一个嵌入的视频,紧接着是对应的文字脚本或者内容总结,将来的 MegEngine 文档应该也会进行这方面的尝试。在线教育是大势所趋,但可惜的是很多人入场探索,却只学习到了视频的术,认知停留在“我们需要录个像,把人脸放上去让用户感到被重视”的程度。一些人的目的本就是趁着这波红利卷了钱就跑,并不试图进一步明白怎样通过视频更加有效地传递信息,哪里有什么教育的初心?一些不专业的细节处理往往还会往视频中引入过多的噪声,降低信噪比,加大用户的认知成本。比如 PPT 风格的讲解视频内容中最容易被忽视的一个因素是音频的质量,试想一下我们在进行会议的时候,经常会出现一些人都发言不清晰的情况,这个时候恐怕就没有精力去捕捉里面的细节信息了,成本太高;所以糟糕的音质比糟糕的画面更容易让人失去耐心。音频后期技巧还包括去除口水音、去除齿音等技巧,其实如果一个人的普通话不标准,也有可能劝退一些听众。BGM 也很重要,用得好是加分项,用得敷衍就容易折磨观众的耳朵。在细节处不求做的完美,但不能差到拉低了整体的下限。
如果是实拍视频,还需要考虑到摄影(画面)方面的一些细节。比如一些代码演示的视频中,如果出现了敲错代码,或者是讲着讲着发现思路不清晰甚至讲错的情况,请务必重新录制,严谨一些!不要让观众因为你的失误而付出代价。至于视频画面的内容, 3Blue1Brown 的风格是我目前最推荐的用来解释抽象概念的途径,在 MegEngine 的入门教程中也有推荐。我们应该以工业级的标准去打磨我们的视频质量,想想这个镜头想要表达什么,需要用到什么样的视觉效果才是最自然的,节奏感对不对,是不是这个情节可以放在后面作为压轴?画面中的留白、实际内容比例也会直接影响到观众的体验… 只有我们将可能存在的干扰项优化到极致了,用户才能专注于感知核心内容的表达。直接鼓捣出一个视频是很困难的,得一步一个脚印慢慢走。而我们的写作,其实就是积累原始表达内容的方式,写作的过程中我们的头脑会一直在思考,刚开始可能会觉得这让人痛苦,但久而久之,你会发现找个地方安静地写作,是浮躁世界静下心来与自己对话的一种方式。
另外,将来的人们在发布内容时,一定会做更多在交互性上面的尝试,正如 https://distill.pub/ 现在所做的(编辑最近写了一篇《Distill Hiatus》,认为大多数类型的文章的未来可能是自我出版,值得一读)。从把一件事情做完,到把一件事情做好,基本上会慢慢地走上一条未曾设想的道路,我们在不断地拓展自己的认知边界,最终完成意识形态上的升级,将来在做其它事情的时候,或许能得到一定的启发。
我们可以用一张表来表示体系完整的文档应该覆盖到哪些情景:
学习阶段 使用阶段
实践步骤 教程 🤔 指南 📖
理论知识 解释 📝 参考 📚
教程(Tutorial):指导读者通过一系列步骤完成项目(或有意义的练习)的课程;
指南(Guide):引导读者完成解决常见问题所需步骤的指南(How-to 系列);
参考(Reference):用于 API 查询和浏览,方便网络引擎检索的百科全书;
解释(Explaination):对于特定主题、特性的概念性说明。
收集一些临时文字。
pen 钢笔
pencil 铅笔
ballpen 圆珠笔
eraser 橡皮,黑板擦
ruler 格尺
penci-box 铅笔盒
sharpener 卷笔刀
draft “草稿”,指方案、计划、报告、合同等文字草稿。
例句:
My secretary has typed out the first draft of the report, but I must revise it before I submit it to the conference.
我的秘书已经打印好报告的第一稿,但我还必须在提交给会议之前把它修改一下。
The chairman went over the draft of the report and make some corrections on it.
主席审阅了报告草案并作了几次修改。
A draft of the new rule is set to be approved by the Federal Reserve Board and put out for public comment within weeks.
一份新规草案将在几周内获得美联储董事会通过,并公开征求意见。
We will give you a definite reply after we have studied the details of the draft contract.
我们研究合同草本的细节后会给你们一个肯定答复。
script 指剧本、电影脚本、广播稿,讲话稿。
例句:
Two writers collaborated on the script for the movie.
两位作家合作编写了这部电影的剧本。
During certain scenes of the play there isn’t any script and the actors just improvise.
在戏剧的某些场景中,没有剧本,演员们只是即兴发挥。
It’s a great idea for a show but the script lacks a little sparkle.
这是一个很棒的想法,但讲稿缺乏一点亮点。
The producer disliked the script and demanded a rewrite.
制片人不喜欢这个剧本,要求重写。
Have you got the script for the listening in class?
你拿到课堂听力的原稿了吗?
manuscript 手稿、原稿;手写本,手抄本。
例句:
He sent the 400-page manuscript to his publisher.
他将400页的手稿寄给了他的出版商。
I hunted down the manuscript in the British Museum.
我在大英博物馆找到了这份手稿。
It is thought that the maunscript is the work of a monk and dates from the twelfth century.
那部手抄本被认为出自12世纪一位僧侣之手。
sketch “草图”、指绘略图、素描或写生。
例句:
The report contains several sketches of the new device and a verbal description.
这份报告包括几份新装置草图和一段文字描述。
The artists usually make several sketches before the final painting.
艺术家在最终作画前经常会先画几张草图。
He drew a rough sketch for us to show how we can get the destination.
他为我们画了一幅草图,标明我们如何才能到达目的地。
He did a sketch of the ballet dancers.
他为这些芭蕾舞者画了一张素描。
She drew a sketch map of the area to show us the way.
她画了一幅该地区的草图给我们指路。
人工智能生成时代,已经不知道写什么了
随着人工智能的发展,大多数的基础内容产出已经变得毫无意义了,首当其冲的是技术社区。 放在几年前,简中技术社区的绝大部分文章基本上都是对官方英文文档、教程等的翻译。 处于新手期的人们把它记录下来,以备自己查阅,也方便他人学习, 但现在你只需要把文档和需求、或者是报错信息丢给大语言模型,问题基本就能解决, 甚至不用你写任何一行代码(没准将来的文档也全是人工智能写的)。 而且随着人工智能模型的进一步训练,这种能力会越来越强。 此类技术文章只有在深度和广度上有所突破,才能有更多的价值。 [2]
比如我的上一篇关于 AMD 内存泄漏 的文章, 这种问题无法通过人工智能得到解决方案,因此就有了被单独记录下来的价值。 但将来它一定会成为人工智能的训练数据,以便更好地帮助人类解决类似问题。 ↩︎
除了技术文章和代码生成,更加值得警惕的是,我们需要经过一个各种形式的内容产出良莠不齐的时期, 因为人工智能极大地减低了内容的门槛,导致整个创作生态变得混乱。 这种混乱不仅体现在写作领域,还有听书等类型的应用,绝大部分有声书主播(声优)预计三年内就会被人工智能取代掉。 我的父亲很喜欢听单田芳的评书,可惜大师已经去世了,而 AI 单田芳已经初见雏形。 至于短视频领域,似乎遭受了更大的低成本劣质创作的 “迫害”… 这种大趋势固然是好的,只是在发展初期,内容和版权均缺少有效的监管和规范。 我想,人工智能是为了帮我们解放生产力,挑战更加困难的领域,而不是用有限的生产力去创造尽可能多的垃圾。
至少在 AIGC 领域,我坚信将来会出现先驱者,告诉我们 AIGC 的最佳实践方向。
如此看来,关乎个人的非技术创作似乎成为了不被侵扰的净土,可于我而言这也是矛盾的。 如果只是记录自己的生活,我可能不会选择公开自己的动态在个人博客,因为有私有化部署的 NAS 应用。 分享自己的想法吧,太保守显得平庸,太激进显得危险,也有可能成为日后被党同伐异、口诛笔伐的材料,实在是如履薄冰。 [3] 成年人的社会除了利益交换,剩下的表达好像只剩下 “关我屁事” 和 “关你屁事” 了。 慢慢地,一切的行为都要为了某种利益,而不是为了自己的兴趣和爱好。 如果一个人的观点受到了广泛的关注,可能并不一定是因为其中具备多少客观的真理, 而是因为它的内容能够引起人们的共鸣,缓解了一些焦虑,解决了部分实际的需求。 也有可能是因为这个人已经取得了世俗的成功,人们更倾向于相信他的观点。 另一方面,很多人在网络上浏览内容并不是为了解决核心问题,而是为了消磨时间,找到一些可以让自己感到愉悦的东西。 这也是为什么成功学、觉醒学、心理学、情感学大师总能够在网络上找到一席之地。 要知道中国人都是信奉闷声发大财(和窝里斗)的,永远不要相信在网上花钱就一定能学到真东西, 除非培训你会给对方带来更大的价值。
如果沉默的成为大多数,那么少数的发声只因有利可图。 ↩︎
回想起自己曾经在 技术博客黑洞 对创作提出的要求—— 写出 50 年后,甚至 100 年后的人们读起来依旧能有启发的作品。 内心不禁在询问自己,我真的做到了吗? 所以还是得慢慢沉淀,就像部分潜心搞科研的人一样,不断地积累,不断地思考,不断地总结,最后搞点新花样 [4] ,没准无心插柳妙手偶得,忽地发现就造福人类了。
前提是你的论文不能是 AI 帮你写的… ↩︎
如果你的创作内容十有八九是可以完全由人工智能生成的,那就值得考量考量该不该在这部分内容上投入时间和精力了。 在不遥远的将来,基于人工智能的问答将成为我们日常生活的一部分,这是不可逆的趋势 —— 人人都能成为拥有贾维斯的托尼·斯塔克(钢铁侠)!
综上所述,除却对时间的功利安排外, 我的创作欲望已经低无可低,加上对自己的要求也越来越高,也不自信短期内能产出有价值的内容。 所以 xLog 这个平台对我来说,可能只是一个注册了账号的空壳。 期待 xLog 平台能搞出更多有吸引力的功能,让我有动力去尝试一下。
骆驼玩法
什么是骆驼玩法?
使用 Git LFS(Large File Storage)来管理这些二进制文件, LFS 的原理是:在 Git 仓库中只存储指针文件,而真正的文件存储在 LFS 服务器上,按需获取。
version https://git-lfs.github.com/spec/v1
oid sha256:4cac19622fc3ada9c0fdeadb33f88f367b541f38b89102a3f1261ac81fd5bcb5
size 84977953
讲到这里,不妨提一下“博客”的由来。“博客”(又称“部落格”) 一词来自于英文“blog” , 最初 Jorn Barger 在1997年12月17日提出了 “weblog” 的概念,即“网页”(web) 与 “记录”(log) 的组合; 而后由 Peter Merholz 开玩笑地拆分为“我们”(we) 与 blog 的组合, 后续术语 blogger 的出现,使这一概念慢慢被普及和接受。 不难看出在那时的背景下,大众需要的是能够在互联网上编辑和发布内容的产品,最好能和其他人产生社交互动。 如今微博和微信朋友圈的出现,让网络社交变得更便捷,自媒体公众号成为了博客的替代品,这是技术的进步。
———— END OF DOCUMENT ———————–
##########################################
##########################################
##########################################
##########################################
##########################################
##########################################
##########################################
##########################################
##########################################
##########################################
##########################################
##########################################
##########################################
##########################################
##########################################
##########################################
##########################################
##########################################
##########################################
##########################################
##########################################
##########################################