github 使用 personal access token 认证

最近又因为这件事情折腾半小时, 索性记录下来.

  1. 因为公司电脑有防火墙或者公司电脑访问策略的问题, 不能使用 git 协议访问 github.com 的 push/pull, 所以只能使用 https 协议, 比如: https://github.com/int0x03/samples.git
  2. github.com 已经禁止使用账户加密码来操作 git push/pull, 看这里: https://docs.github.com/en/get-started/git-basics/about-remote-repositories#cloning-with-https-urls
  3. 如果使用用户名 + 密码会出现下面的错误:

    git push origin master
    Username for 'https://github.com': int0x03
    Password for 'https://int0x03@github.com':
    remote: Support for password authentication was removed on August 13, 2021.
    remote: Please see https://docs.github.com/get-started/getting-started-with-git/about-remote-repositories#cloning-with-https-urls for information on currently recommended modes of authentication.
    fatal: Authentication failed for 'https://github.com/int0x03/samples.git/'
  4. 所以要先添加 personal access token 然后把 token 做密码来访问. 具体路径是: github.com 右上角点头像, 进入 Settints -> Developer setttings -> Personal access tokens -> token classic 添加.
  5. 添加完成, 选择生产 token, 这个token 可以设置终止日期的.
  6. 可选把 token 放到 iTerm 的password 里面.

python Async Sync 函数

这篇是比较好的一篇文章: https://docs.chainlit.io/guides/sync-async

  1. sync -> sync -> ok
  2. sync -> async -> asyncio.run() | asyncio.get_event_loop().run_until_complete(async_func())
  3. async -> sync -> ok | await asyncio.to_thread(sync_task)
  4. async -> async -> await
  5. Python 的 asyncio 库是标准库中用于编写异步 I/O 代码的核心框架,自 Python 3.4 引入后成为构建高性能网络应用和并发程序的重要工具. asyncio 是 Python 异步编程的基石,适合需要高效处理 I/O 并发的场景。通过事件循环和协程机制,开发者能以同步风格编写异步代码,显著提升程序性能.
  6. Asyncer 是一个用于简化 Python 异步编程的第三方库,它通过更优雅的语法和高级功能让异步代码更易编写和维护. 相比标准库 asyncio,Asyncer 通过更高层次的抽象降低了异步编程门槛,适合需要快速开发高并发应用的场景.
  7. Python 的 anyio 库是一个​​跨异步框架的统一接口库​​,旨在简化异步编程的复杂性,同时兼容主流异步模型(如 asyncio 和 trio)。

操作系统获取随机数的进化

最近重新阅读 一书. 之前都是从前往后看, 这次选择从后往前看. 最后一章有一节专门讲 Java 获取随机数的相关类和这些类的对比和使用. 其实内容不仅仅局限于 Java, 而是从整个操作系统层面来看这个问题.

一边阅读, 一边查阅相关资料. 以我的理解它的进化大概有下面几个过程:

  1. 最早想, 无非就是一个随机数吗, 于是就有了一个随机函数, 随机函数怎么增加随机性呢? 那就加一个种子, 种子的值不确定.
  2. 接着发现代码别人是知道的, 所以随机函数就是公开的, 那么唯一的就剩下种子. 但是连续获得多个随机数后, 种子也是可猜测的. 需要找真正的随机数.
  3. 于是发现机器本身能获得一些随机值. 比如: 移动鼠标的性质, 磁盘转动的性质, 网络的性质等. 但是获得这些需要时间, 有时候很慢, 就是要block 的.
  4. 然后发现可以结合真随机值和伪随机数, 两者结合可以就可以做到别人猜测不到. 唯一的问题是有时候要等系统的真随机数还是需要点时间.
  5. 于是就有了获得真随机数的后台持续运行的 deamon 线程, 它可以做到持续的获得, 等到用的时候, 随时给.
  6. 到后来又进化到某些硬件支持.

具体到 Linux 操作系统上面, 就是 /dev/urandom/dev/random 设备和 rngd 守护进程.

为了让理解这个进化更细致, 下面是 Deepseek 的联网回答. 结构很好, 也很有逻辑关系. 唯一的不足感觉就是有点浅.

Linux随机数生成机制的演化与优化历程

在Linux系统中,随机数生成机制经历了从简单伪随机算法到复杂熵源管理、硬件加速的漫长优化过程。这一过程的核心矛盾在于随机性质量性能效率之间的平衡,同时需满足密码学安全的高标准要求。以下是其关键发展阶段及技术动因分析:


一、早期阶段:伪随机数生成器(PRNG)的局限性

技术背景

早期Linux系统依赖伪随机数生成算法(如线性同余法),其核心问题在于种子可预测性。若攻击者获取初始种子,即可推导出完整随机序列。例如,系统时间常被用作默认种子,但时间戳的有限精度(如秒级)导致种子空间狭小,易被暴力破解。

缺陷暴露

  • 安全性不足:SSH、TLS等安全协议依赖随机数生成密钥,伪随机数的可预测性直接威胁加密强度。
  • 确定性缺陷:相同种子生成相同序列,导致测试环境复现问题,但生产环境无法接受此类漏洞。

二、熵源驱动的随机数生成:从理论到实践

熵源的引入

Linux内核通过收集系统事件(如键盘敲击、磁盘I/O延迟、硬件中断)的物理随机性构建熵池(Entropy Pool)。例如:

  • 鼠标移动:用户操作的时间戳和坐标变化具有不可预测性。
  • 磁盘寻道时间:物理磁盘的机械延迟受环境因素影响,产生微小随机波动。
  • 中断计时:硬件中断的到达时间间隔因电路噪声而呈现随机性。

熵池管理机制

  • 熵值计量:内核通过熵估算算法(如Linux内核的rng-core模块)量化熵池的随机性强度,熵值越高,随机数质量越优。
  • 阻塞与非阻塞模式

    • /dev/random:严格依赖熵池,熵不足时阻塞,确保高安全性(如密钥生成)。
    • /dev/urandom:熵不足时切换为伪随机算法(如SHA-256),牺牲部分安全性以提升性能,适用于非安全敏感场景。

技术瓶颈

  • 熵源竞争:多进程并发读取熵池时,可能导致熵值快速耗尽,引发阻塞问题。
  • 启动阶段熵饥饿:系统冷启动时熵源未激活,早期加密操作可能面临低熵风险。

三、算法与架构的优化:从被动收集到主动预测

算法升级

  • Yarrow算法:Linux内核早期采用Yarrow架构,结合熵池与密码学算法(如HMAC-SHA1)生成随机数,平衡安全性与性能。
  • ChaCha20替代:为应对SHA-1碰撞攻击,Linux内核逐步迁移到ChaCha20算法,提升抗侧信道攻击能力。

熵池结构优化

内核将熵池细分为多个子池(如input_poolblocking_pool),分别处理不同类型熵源,并通过动态权重分配优化熵混合效率。

硬件随机数生成器(TRNG)支持

现代CPU和主板集成硬件熵源(如Intel RDRAND指令、TPM芯片),Linux通过/dev/hwrng直接接入硬件随机数,显著提升生成速度与质量。


四、系统级抽象与守护进程的引入

统一接口:getrandom()系统调用

2014年Linux 3.17引入getrandom(),统一用户态接口:

  • 自动选择最优熵源(优先硬件TRNG,其次熵池)。
  • 阻塞行为可控:通过标志位选择是否等待熵池初始化完成。

守护进程优化

  • rngd后台服务:持续监控熵池状态,通过混合熵源(如网络抖动、环境传感器数据)维持熵池活跃度,避免低熵场景。
  • 自适应速率控制:根据熵池负载动态调整伪随机算法的调用频率,平衡性能与随机性质量。

五、现代挑战与前沿方向

量子计算威胁

传统随机数生成算法可能被量子计算机高效破解,Linux社区正探索抗量子算法(如基于格的随机数生成方案)。

容器化环境适配

容器共享宿主机熵池导致随机性竞争,解决方案包括:

  • 容器内嵌轻量级熵源(如基于CPU时间戳抖动)。
  • 增强宿主机熵源分发机制,通过cgroup隔离熵资源。

标准化与审计

Linux内核引入随机数生成器的形式化验证(如FIPS 140-2认证),并定期公开熵源统计报告,增强透明度和可信度。


总结:从“伪随机”到“真随机”的演进逻辑

Linux随机数生成机制的迭代本质上是安全性需求驱动下的工程妥协

  1. 早期:以性能优先的伪随机算法为主,牺牲部分安全性。
  2. 中期:引入熵池和硬件熵源,构建混合生成模式。
  3. 现代:通过算法优化、守护进程和硬件加速,实现安全与性能的动态平衡。

未来,随着量子计算和容器技术的普及,Linux随机数生成机制将更注重抗量子攻击、轻量化与跨平台兼容性。

Java 微服务网络连接优化

在如今的大型互联网络中, 微服务基本是最流行的架构. 采用微服务架构, 自然少不了内部各个服务之间的相互调用, 那么其中的网络连接处理, 自然成了要重点考虑的对象. 下面就一步步仔细看看其中每一步要考虑的细节.

基本的网络连接

下面的代码使用了 JDK 最基础的代码, 告诉我们如何使用 URLHttpURLConnection 来进行网络连接.

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;

public class HttpClientExample {

    public static void main(String[] args) {
        String urlString = "https://api.example.com/v1/search";
        HttpURLConnection connection = null;

        try {
            // 创建 URL 对象
            URL url = new URL(urlString);
            
            // 打开连接
            connection = (HttpURLConnection) url.openConnection();
            
            // 设置请求方法
            connection.setRequestMethod("GET");
            
            // 设置请求属性(如有需要)
            connection.setRequestProperty("Content-Type", "application/json");
            
            // 设置连接超时和读取超时
            connection.setConnectTimeout(5000);
            connection.setReadTimeout(5000);
            
            // 发起请求
            int responseCode = connection.getResponseCode();
            
            // 检查响应码
            if (responseCode == HttpURLConnection.HTTP_OK) {
                // 正常响应,读取数据
                BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
                StringBuilder response = new StringBuilder();
                String line;
                while ((line = reader.readLine()) != null) {
                    response.append(line);
                }
                reader.close();
                
                // 打印响应
                System.out.println("Response: " + response.toString());
            } else {
                // 错误处理
                System.err.println("Error: Received HTTP code " + responseCode);
                BufferedReader errorReader = new BufferedReader(new InputStreamReader(connection.getErrorStream()));
                StringBuilder errorResponse = new StringBuilder();
                String errorLine;
                while ((errorLine = errorReader.readLine()) != null) {
                    errorResponse.append(errorLine);
                }
                errorReader.close();
                
                // 打印错误响应
                System.err.println("Error Response: " + errorResponse.toString());
            }
        } catch (Exception e) {
            // 异常处理
            System.err.println("Exception occurred: " + e.getMessage());
            e.printStackTrace();
        } finally {
            if (connection != null) {
                // 断开连接
                connection.disconnect();
            }
        }
    }
}

设置 timeout

上面的代码中, 我们可以看到, 连接 timeout 和 read timeout 都是通过 setConnectTimeoutsetReadTimeout 来设置的. 下面我们就来看看这两个 timeout 的区别.

连接 timeout

连接 timeout 是指在建立连接时, 客户端等待服务器响应的时间. 如果在这个时间内没有建立连接, 就会抛出 SocketTimeoutException. 这个 timeout 主要是为了防止网络延迟过长导致的连接阻塞. 具体来说, 就是 TCP 三次握手的时间. 如果在这个时间内没有完成三次握手, 就会抛出异常. 这个数值通常根据网络环境来设置, 比如在局域网中, 可以根据测试来设置为500或1000毫秒, 而在广域网中, 可以设置为为更长时间.

read timeout

read timeout 是指在连接建立后, 客户端等待服务器响应数据的时间. 如果在这个时间内没有收到数据, 就会抛出 SocketTimeoutException. 这个 timeout 主要是为了防止服务器长时间不响应导致的连接阻塞. 对于客户端这方具体来说, 当它发出去请求, 它就开始等带从 Socket 读取数据, 每次开始读, 就开始计时, 当到达 read timeout 的时间还没有任何数据收到, 就会因读取超时而抛出异常. 有时候从服务端返回的数据很多, 要分好几次读取, 每当当前一次 Socket.read() 读完, 就开始下一次read(), 直到所有的数据都读完. 每次重新读取(开始read()), 都会重新计时. 这个 timeout 通常设置为几秒到几十秒不等, 具体数值可以根据实际情况来设置.

对于 read timeout 的误解

很多人会误解 read timeout 是指在连接建立后, 客户端等待服务器响应数据的时间. 其实不是这样的, 它是指在连接建立后, 客户端等待服务器响应数据的时间. 如果在这个时间内没有收到数据, 就会抛出 SocketTimeoutException. 意思是如果从服务端读取的数据分多次, 每次都会重新计时, 每次都可能timeout . 整体的读数据时间, 可能是设置的 timeout 的好几倍的时间. 比如我们下载一个几G的镜像文件, 设置timeout 是5秒, 但是整体下载镜像的时间可能是半小时, 只要每次读取数据的时间不超过5秒, 就不会抛出异常. 最后顺利下载完成.

连接池

连接池是为了提高性能, 减少连接的创建和销毁的开销. 连接池会预先创建一定数量的连接, 当需要使用连接时, 从连接池中获取一个可用的连接, 使用完毕后将其放回连接池. 这样可以避免频繁地创建和销毁连接, 提高性能.

在 Java 11 中,JDK 引入了新的 HttpClient API,它提供了更现代化和更灵活的 HTTP 客户端功能。HttpClient 默认支持连接池,这意味着它可以重用连接以提高性能和效率。

HttpClient 默认连接池的特点:

  1. 连接复用:HttpClient 会自动管理和复用连接,这样可以减少每次请求都重新建立连接的开销。连接复用对于 HTTP/1.1 和 HTTP/2 都有效。
  2. 线程安全:HttpClient 是线程安全的,可以在多个线程之间共享一个 HttpClient 实例。这意味着多个请求可以并发地使用同一个 HttpClient 实例进行发送。
  3. 自动管理:连接池的管理是自动的,用户无需手动配置连接池的大小或其他参数。HttpClient 会根据需要动态调整连接池的大小。
  4. 异步支持:HttpClient 支持异步请求,这可以更高效地利用连接池,因为异步请求不会阻塞线程。
  5. HTTP/2 支持:HttpClient 默认支持 HTTP/2 协议,这使得连接复用更加高效,因为 HTTP/2 允许在单个连接上并发多个请求和响应。

下面是一个使用 HttpClient 的示例代码:

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;

public class HttpClientExample {

    public static void main(String[] args) {
        try {
            // 创建 HttpClient 实例
            HttpClient client = HttpClient.newBuilder()
                    .version(HttpClient.Version.HTTP_2)
                    .connectTimeout(Duration.ofSeconds(10))
                    .build();

            // 创建 HttpRequest 实例
            HttpRequest request = HttpRequest.newBuilder()
                    .uri(new URI("https://api.example.com/v1/search"))
                    .GET()
                    .build();

            // 发送请求并接收响应
            HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());

            // 打印响应状态码和响应体
            System.out.println("Response Code: " + response.statusCode());
            System.out.println("Response Body: " + response.body());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

连接池连接 timeout

连接池的连接 timeout 是指在连接池中获取连接时, 客户端等待连接可用的时间. 如果在这个时间内没有获取到连接, 就会抛出 TimeoutException. 这个 timeout 主要是为了防止连接池中的连接都被占用导致的阻塞. 这个 timeout 通常设置为几秒到几十秒不等, 具体数值可以根据实际情况来设置.

Apache HttpClient

Apache HttpClient 是 Apache 提供的一个功能强大的 HTTP 客户端库,广泛用于 Java 应用程序中进行 HTTP 请求和响应处理。它提供了丰富的功能,包括连接池、异步请求、自动重试、代理支持等。
Apache HttpClient 的连接池管理是通过 PoolingHttpClientConnectionManager 实现的。这个连接池可以管理多个连接,并且可以配置最大连接数、每个路由的最大连接数等参数。下面是一个使用 Apache HttpClient 的示例代码:

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;

public class ApacheHttpClientExample {

    public static void main(String[] args) {
        // 创建一个 CloseableHttpClient 实例
        try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
            // 创建一个 HttpGet 请求
            HttpGet request = new HttpGet("https://api.example.com/v1/search");
            
            // 发送请求并获取响应
            try (CloseableHttpResponse response = httpClient.execute(request)) {
                // 检查响应状态码
                int statusCode = response.getStatusLine().getStatusCode();
                System.out.println("Response Code: " + statusCode);

                // 获取响应实体
                HttpEntity entity = response.getEntity();
                if (entity != null) {
                    // 将响应实体转换为字符串
                    String responseBody = EntityUtils.toString(entity);
                    System.out.println("Response Body: " + responseBody);
                }
            }
        } catch (Exception e) {
            // 处理异常
            System.err.println("Exception occurred: " + e.getMessage());
            e.printStackTrace();
        }
    }
}

连接池配置

Apache HttpClient 的连接池配置可以通过 PoolingHttpClientConnectionManager 来实现。但是配置的时候一定要注意, 除了配置连接池的最大连接数外, 还要注意每个路由的最大连接设置, 否则每个路由只有2个最大连接. 下面是一个使用 Apache HttpClient 的连接池配置示例代码:

import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;

public class ApacheHttpClientConfigExample {

    public static void main(String[] args) {
        // 创建一个连接池管理器
        PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager();
        
        // 设置整个连接池的最大连接数
        connectionManager.setMaxTotal(100);  // 设置最大连接数为100

        // 设置每个路由的最大连接数
        connectionManager.setDefaultMaxPerRoute(20);  // 设置每个路由的最大连接数为20

        // 创建一个 HttpClient 实例,并使用连接池管理器
        try (CloseableHttpClient httpClient = HttpClients.custom()
                .setConnectionManager(connectionManager)
                .build()) {
            
            // 使用 httpClient 发送请求...
            // 这里可以添加发送请求的代码逻辑
            
            // 示例结束,不发送实际请求
            System.out.println("HttpClient configured with connection pool.");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

Java 远程 debug

本地 Debug 很容易, 那么远程 debug 是怎么做的, 又有哪些坑呢?

一般应用程序 debug

这里的一般应用程序就是一个 main 函数, 不是 web 应用程序. 首先, 这个应用在远程某主机上能运行, 只要启动的时候, 添加远程 debug 参数就可以了.

比如有如下代码, 为了让它慢一点可以 debug, 加入了一个循环和睡眠:

public class Debugger {
    
    public int loop(int count) {
        int sum = 0;
        for (int i = 0; i < count; i++) {
            sum += i;
            try {
                System.out.println(i + "/" + count + " current sum: " + sum);
                Thread.sleep(5L);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return sum;
    }
    
    public static void main(String[] args) {
        Debugger debugger = new Debugger();
        int sum = debugger.loop(10000);
        System.out.println("sum is " + sum);
    }

}

在远程编译, 启动, 并且加入监听端口为了方便远程debug:

$ javac Debugger.java
$ java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=0.0.0.0:8000 Debugger 

这里注意的是, 我们对于地址使用的是: 0.0.0.0:8000, 有些地方你看到的直接是 8000 端口, 没有主机ip部分. 这会导致这个端口只是开在 localhost, 你如果使用 netstat 查看, 能看到 8000 端口是开着的, 但是远程无法连接.
在远程可以使用 telnet 或者 curl 来测试这个端口是不是可以远程连接. 但是注意这个端口只能有一个远端能连接成功, 假如你已经使用 curl 连接上, 那么你远端的其它 IDE 就不能连接了.

我本地的 Eclipse 连接的过程是: 在菜单 Run 里面选择 Debug Configurations, 然后新建 Remote Java Application, 然后输入远程的 IP 和 端口. 如下:
remote_debug.png

连接后, Eclipse 会显示连接成功, 然后点击 暂停, 就会暂停远程的应用了.
debug_pause.png

debug 远程 tomcat 里面的应用

在 Tomcat 的 catalina.sh 里面, 添加如下环境变量设置:

export JPDA_ADDRESS=0.0.0.0:8000
export JPDA_TRANSPORT=dt_socket

然后启动时候, 添加 jdpa 参数, 就可以了

./catalina.sh jpda start

debug 远程基于 Spring boot 的应用

其实 Spring boot 是做的 fat jar, 它的远程debug 方式跟一般的应用是一样的, 就是在启动参数里面加上. 例子如下:

java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=0.0.0.0:8000 -jar myApp.jar 

隧道技术

有时候远程机器隔着防火墙, 你不能直接连, 但是你可以通过 ssh 登录, 那么就可以用过隧道技术, 使用 ssh 做隧道, 然后远程 debug.
首先, 我们假设远程机器的 debug 开在 8000 端口上:

java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=0.0.0.0:8000 -jar myApp.jar 

然后在本地机器上建立隧道:

ssh -L 5000:localhost:8000 user@remote_ip

这里的意思是: 在本地启动5000端口, 它所有的数据原封不动的转到远程 remote_ip 的8000 端口. 我们登录远程 remote_ip 的用户名是 user, 这里它会让你输入 ssh user 的密码. 连接成功后, 隧道建立.

这时候, 在本地和之前的一样, 只是把远程机器的ip 改成本地机器localhost, 端口改成 5000. 就能借助隧道进行远程 debug 了.