理解 Kubernetes 的 Scheduler

概述 kube-scheduler 是 Kubernetes 集群的核心组件之一,它负责为新创建的 Pods 分配节点。它根据多种因素进行决策,包括: 资源需求和限制:考虑每个 Pod 请求的资源量(如 CPU 和内存)以及节点上可用的资源。 亲和性和反亲和性规则:根据 Pod 的亲和性设置选择最适合的节点。 健康检查:确保选择的节点健康且能够运行 Pod。 负载均衡:尽量平衡集群中各个节点的负载。 使用 limits 和 reuqests 在部署对象中的 spec 中常常会见到关于 limits 和 requests 的声明 ,例如: apiVersion: apps/v1 kind: Deployment metadata: name: nginx-deployment spec: replicas: 1 selector: matchLabels: app: nginx template: metadata: labels: app: nginx spec: containers: - name: nginx image: nginx resources: limits: memory: 1Gi cpu: 1 requests: memory: 256Mi cpu: 100m 这里的 limits 和 requests 是与 Pod 容器资源管理相关的两个关键概念: Limits:指定容器运行时能够使用的最大资源量 Requests:指定容器启动时最低需要的资源量 limits 和 requests 跟 scheduler 有什么关系 ? ...

十一月 29, 2023 · 7 分钟

理解 Kubernetes 的 Etcd

概述 etcd 是一个基于 Raft 协议实现。开源的、分布式的键值存储系统。主要用于在分布式系统中提供强一致性和高可用性的数据存储。etcd 在 Kubernetes 中的作用如下: 集群状态数据存储:集群配置,集群状态信息等 保证集群一致性和高可用:多实例的数据同步 服务发现和配置共享 集群数据备份和恢复 作为 Kubernetes 的核心组件,etcd 为集群的稳定性、可靠性和一致性提供了支撑。 安装 命令行启动 安装参考官方文档 etcd install 指引即可,安装后验证: $ etcd --version 输出: etcd Version: 3.5.10 Git SHA: 0223ca52b Go Version: go1.21.3 Go OS/Arch: darwin/arm64 phoenix@xiaobindeMacBook-Pro ~ % etcd 在终端启动 etcd: $ etcd 输出: {"level":"warn","ts":"2023-11-23T06:59:49.265098+0800","caller":"embed/config.go:676","msg":"Running http and grpc server on single port. This is not recommended for production."} {"level":"info","ts":"2023-11-23T06:59:49.265318+0800","caller":"etcdmain/etcd.go:73","msg":"Running: ","args":["etcd"]} {"level":"info","ts":"2023-11-23T06:59:49.265352+0800","caller":"etcdmain/etcd.go:100","msg":"failed to detect default host","error":"default host not supported on darwin_arm64"} {"level":"warn","ts":"2023-11-23T06:59:49.265361+0800","caller":"etcdmain/etcd.go:105","msg":"'data-dir' was empty; using default","data-dir":"default.etcd"} #..... 容器启动 在容器中启动 etcd 实例: ...

十一月 25, 2023 · 3 分钟

理解 Kubernetes 的 Configmap

安装说明 通过 docker desktop 可以安装适用于单机和开发环境单机版的 K8S,如果 docker desktop 无法启动 Kubernates 通过以下方式解决: 一:添加国内镜像源 为 Docker 的 daemon.json 添加配置: { "registry-mirrors": [ "https://docker.mirrors.ustc.edu.cn", "https://registry.docker-cn.com" ] } 二:通过脚本下载 Kubernetrs 所需要的镜像 在 GitHub 中的 k8s-for-docker-desktop 项目中下载 Kubernetes 版本对应的分支,然后执行脚本即可,启动完成后,验证 K8S 集群状态: $ kubectl cluster-info $ kubectl get nodes $ kubectl describe node 理解 Pod 先通过一个简单的示例理解 Pod,Pod 是 Kubernetes 中的基本部署单元,这里看看如何用 Pod 创建一个 nginx 服务。使用 kubectl 命令部署一个 nginx 的服务: $ kubectl create deployment nginx-arm --image=nginx 创建部署后,您可以使用以下命令检查 Pod 的状态: ...

十一月 16, 2023 · 3 分钟

大模型的 Prompt 使用技巧

概述 前段时间在 DeepLearning 学了一门 Prompt 的课程,感觉受益匪浅,因此在这里总结分享一下学习笔记,希望可以帮到大家。 为什么要学习 Prompt ? 因为在未来的 AIGC 年代,学习有效的 Promot 提示词有效的利用 AI 来完成一些重复性的工作。这也我认为未来每个人都必备的技能之一。 以下是我个人学完这门课程的总结: 更好的完成任务:试想一下,如果你给 AI 一个模糊的问题,那么你得到的只会是一个模糊的回答 多元化的结果:可以让 AI 更多维的结果,但不限于:代码,JSON,XML,HTML 等格式文本,甚至是图片,视频等 避开 AI 的局限:喜欢编造事实,这是目前 AI 已知的缺陷,但有效的 Prompt 可以帮助你有效的避开这个已知,但目前还无法解决的缺陷 不再迷信完美的 Prompt:了解真相后,你将不再迷信类似于 awesome-chatgpt-prompts-zh 各种所谓的魔法,速成的调教指南,因为不存在完美的 Prompt 了解 AI 的能力:目前大模型的能力局限在:摘要,推理,转换,扩展等能力上,目前的 AI 并非无所不能,不要过分神话,也不要过分贬低它 总而言之,学习 Prompt 提示词可以帮助您更好地与 LLM 模型进行交互,指导其生成符合您需求的文本,并提高效率和准确性。也推荐大家有时间可以看完完整的视频课程。我就不过多展开了。以下是我对课程的学习笔记。 第一章: Introduction 引言 第一章节,引言主要介绍和 ChatGPT 或类似的 LLM 交流时,要遵循的几个基本原则,如下: 明确的指令:清晰的指令会得到更准确的回复。例如,而不是问 “我应该吃什么?",你可以问 “我应该在素食饮食中添加哪些蛋白质来源 ?” 合理的期待:模型的知识储备和它的训练参数和训练方向有关,例如对于 ChatGPT 这样一个作为通用领域的大模型,对于一些特别复杂、需要深度专业知识,它是无法提供准确的答案的,特定领域的问题必须由特定领域的专用模型来解决。 验证结果:如上,对于特别复杂和专业的问题,AI 有时候会虚构信息,你必须对 AI 的回复进行验证,如果发现了错误,可以尝试用不同的方式提问。 等待 AI 思考的时间:AI 需要理解你的问题,并生成一个有用的响应,这可能需要一些时间,特别是对于复杂的问题。要有一点耐心 以上就是向第一章课程中包含的向 AI 提问的基本原则,希望对你有所帮助。 ...

六月 5, 2023 · 2 分钟

Java 泛型的理解和应用

概述 泛型是一种将类型参数化的动态机制,使用得到的话,可以从以下的方面提升的你的程序: 安全性:使用泛型可以使代码更加安全可靠,因为泛型提供了编译时的类型检查,使得编译器能够在编译阶段捕捉到类型错误。通过在编译时检查类型一致性,可以避免在运行时出现类型转换错误和 ClassCastException 等异常。减少由于类型错误引发的bug。 复用和灵活性:泛型可以使用占位符 <T> 定义抽象和通用的对象,你可以在使用的时候再来决定具体的类型是什么,从而使得代码更具通用性和可重用性。 简化代码,增强可读性:可以减少类型转换的需求,简化代码,可以使代码更加清晰和易于理解。通过使用具有描述性的泛型类型参数,可以更准确地表达代码的意图,还可以避免使用原始类型或Object类型,从而提供更多的类型信息,使代码更加具有表达力 这就是泛型的概念,是 Java 后期的重大变化之一。泛型实现了参数化类型,可以适用于多种类型。泛型为 Java 的动态类型机制提供很好的补充,但是 Java 的泛型本质上是一种高级语法糖,也存在类型擦除导致的信息丢失等多种缺点,我们可以在本篇文章中深度探讨和分析。 简单的示例 泛型在 Java 的主要作用就是创建类型通用的集合类,我们创建一个容器类,然后通过三个示例来展示泛型的使用: 没有使用泛型的情况 使用 Object 类型作为容器对象 使用泛型作为容器对象 示例1:没有使用泛型的情况 public class IntList { private int[] arr; // 只能存储整数类型的数据 private int size; public IntList() { arr = new int[10]; size = 0; } public void add(int value) { arr[size++] = value; } public int get(int index) { return arr[index]; } public int size() { return size; } public static void main(String[] args) { IntList list = new IntList(); list.add(1); list.add(2); list.add(3); int value = list.get(1); // 需要显式进行类型转换 System.out.println(value); // 输出: 2 } } 在上述示例中,使用了一个明确的 int 类型存储整数的列表类 IntList,但是该类只能存储整数类型的数据。如果想要存储其他类型的数据,就需要编写类似的类,导致类的复用度较低。 ...

五月 24, 2023 · 6 分钟

Java 反射的理解和应用

概述 反射(Reflection)机制是指在运行时动态地获取类的信息以及操作类的成员(字段、方法、构造函数等)的能力。通过反射,我们可以在编译时期未知具体类型的情况下,通过运行时的动态查找和调用。 虽然 Java 是静态的编译型语言,但是反射特性的加入,提供一种直接操作对象外的另一种方式,让 Java 具备的一些灵活性和动态性,我们可以通过本篇文章来详细了解它 为什么需要反射 ? Java 需要用到反射的主要原因包括以下几点: 运行时动态加载,创建类:Java中的类是在编译时加载的,但有时希望在运行时根据某些条件来动态加载和创建所需要类。反射就提供这种能力,这样的能力让程序可以更加的灵活,动态 动态的方法调用:根据反射获取的类和对象,动态调用类中的方法,这对于一些类增强框架(例如 Spring 的 AOP),还有安全框架(方法调用前进行权限验证),还有在业务代码中注入一些通用的业务逻辑(例如一些日志,等,动态调用的能力都非常有用 获取类的信息:通过反射,可以获取类的各种信息,如类名、父类、接口、字段、方法等。这使得我们可以在运行时检查类的属性和方法,并根据需要进行操作 一段示例代码 以下是一个简单的代码示例,展示基本的反射操作: import java.lang.reflect.Method; public class ReflectionExample { public static void main(String[] args) { // 假设在运行时需要调用某个类的方法,但该类在编译时未知 String className = "com.example.MyClass"; try { // 使用反射动态加载类 Class<?> clazz = Class.forName(className); // 使用反射获取指定方法 Method method = clazz.getMethod("myMethod"); // 使用反射创建对象 Object obj = clazz.newInstance(); // 使用反射调用方法 method.invoke(obj); } catch (ClassNotFoundException e) { System.out.println("类未找到:" + className); } catch (NoSuchMethodException e) { System.out.println("方法未找到"); } catch (IllegalAccessException | InstantiationException e) { System.out.println("无法实例化对象"); } catch (Exception e) { System.out.println("其他异常:" + e.getMessage()); } } } 在这个示例中,我们假设在编译时并不知道具体的类名和方法名,但在运行时需要根据动态情况来加载类、创建对象并调用方法。使用反射机制,我们可以通过字符串形式传递类名,使用 Class.forName() 动态加载类。然后,通过 getMethod() 方法获取指定的方法对象,使用 newInstance() 创建类的实例,最后通过 invoke() 方法调用方法。 ...

五月 21, 2023 · 4 分钟

Java.nio.file 文件库的介绍和使用

概述 在早期的 Java 版本中,文件 IO 操作功能一直相对较弱,主要存在以下问题: 缺乏对现代文件系统的支持:只提供的基础的文件操作,不支持很多现代的文件系统 API 不够直观:文件操作的 API 设计相对较为复杂和冗长,使用体验感很差 对于大文件处理和并发性能不够:简单的 I/O 模型,没有充分利用现代硬件的性能优势,而且还有很多同步的问题 但 Java 在后期版本中引入了 java.nio.file 库来提高 Java 对文件操作的能力。还增加的流的功能,似乎使得文件变成更好用了。所以本章,我们就来主要介绍 java.nio.file 中常用的类和模块,大致如下: Path 路径:Paths 模块和 Path 工具类介绍 Files 文件:File 和 FileSystems 工具类介绍 文件管理服务:WatchService 、PathMatcher 等等文件服务 Path 路径 java.nio.file.Paths 和 java.nio.file.Path 类在 Java NIO 文件 I/O 框架中用于处理文件系统路径。以下是对它们的简单介绍: Paths 模块:Paths 模块提供了一些静态方法来创建 Path 对象,Path 对象表示文件系统中的路径。例如,可以使用 Paths.get() 方法创建一个 Path 对象,这个对象表示一个文件路径。 Path 类:Path 类代表一个文件系统中的路径,它提供了一系列的方法来操作文件路径。例如,可以使用 Path.toAbsolutePath() 方法获取一个绝对路径,或者使用 Path.getParent() 方法获取路径的父路径。 关于跨平台:Path 对象可以工作在不同操作系统的不同文件系统之上,它帮我们屏蔽了操作系统之间的差异 以下是一些简单使用场景示例: import java.nio.file.Path; import java.nio.file.Paths; public class PathExample { public static void main(String[] args) { // 创建一个绝对路径 Path absolutePath = Paths.get("C:\Users\phoenix\file.txt"); // 这里传入 "example\file.txt" 创建的相对路径 System.out.println("Absolute path: " + absolutePath); // 获取父路径 System.out.println("Parent path: " + absolutePath.getParent()); // 获取文件名 System.out.println("File name: " + absolutePath.getFileName()); // 获取根路径 System.out.println("Root path: " + absolutePath.getRoot()); // 合并路径 Path resolvePath = Paths.get("C:\Users\phoenix").resolve("file.txt"); System.out.println("Merged path:" + resolvePath); } } 输出结果: ...

五月 9, 2023 · 4 分钟

提高代码质量的几条参考建议

单元测试 什么是单元测试 ? 单元测试通常是指对一个函数或方法测试。单元测试的目的是验证每个单元的行为是否符合预期,并且在修改代码时能够快速检测到任何潜在的问题。通过编写测试用例,我们可以验证这些模块在特定输入下是否产生正确的输出。单元测试的目的是确保每个模块在各种情况下都能正常运行。 写单元测试的好处 可以带来以下几个好处: 提高代码质量:单元测试可以我们提前的发现代码中的潜在问题,例如边界条件、异常情况等,从而减少出错的概率。 提高代码可维护性:单元测试可以帮助开发人员理解代码的功能和实现细节,从而更容易维护和修改代码。 提高代码可靠性:修改代码后,可以通过单元测试可以帮助开发人员验证代码的正确性,从而提高代码的可靠性。 写单元测试是一种良好的软件开发实践,可以提高代码质量、可维护性和可靠性,同时也可以提高开发效率和支持持续集成和持续交付。 单元测试入门 上手单元测试,通常同时从静态测试(Static Test)开始,因为它简单,好理解,静态测试(Static Test)是指在编写测试用例时,我们提前定义好所有的测试方法和测试数据。这些测试方法和数据在编译时就已经确定,不会在运行时发生变化。Junit 中的静态测试通常的常规注解,如 @Test、@Before、@After 等。先来看看一组简单的静态测试示例。 首先,确保你的 pom.xml 文件包含 JUnit 的依赖: <dependencies> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-api</artifactId> <version>5.8.0</version> <scope>test</scope> </dependency> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-engine</artifactId> <version>5.8.0</version> <scope>test</scope> </dependency> </dependencies> 然后,创建一个简单的计算器类,通常这里替换为你实际要测试的业务类: public class SimpleCalculator { public int add(int a, int b) { return a + b; } public int subtract(int a, int b) { return a - b; } } 然后在 /test 的相同目录下创建对应的测试类 import org.junit.jupiter.api.*; import static org.junit.jupiter.api.Assertions.assertEquals; public class SimpleCalculatorTest { // 在所有测试方法执行前,仅执行一次。这个方法需要是静态的。 @BeforeAll static void setup() { System.out.println("BeforeAll - 初始化共享资源,例如数据库连接"); } // 在所有测试方法执行后,仅执行一次。这个方法需要是静态的。 @AfterAll static void tearDown() { System.out.println("AfterAll - 清理共享资源,例如关闭数据库连接"); } // 在每个测试方法执行前,都会执行一次。用于设置测试方法所需的初始状态。 @BeforeEach void init() { System.out.println("BeforeEach - 初始化测试实例所需的数据"); } // 在每个测试方法执行后,都会执行一次。用于清理测试方法使用的资源。 @AfterEach void cleanup() { System.out.println("AfterEach - 清理测试实例所用到的资源"); } // 标注一个测试方法,用于测试某个功能。 @Test void testAddition() { System.out.println("Test - 测试加法功能"); SimpleCalculator calculator = new SimpleCalculator(); assertEquals(5, calculator.add(2, 3), "2 + 3 应该等于 5"); } // 再添加一个测试方法 @Test void testSubtraction() { System.out.println("Test - 测试减法功能"); SimpleCalculator calculator = new SimpleCalculator(); assertEquals(1, calculator.subtract(3, 2), "3 - 2 应该等于 1"); } } 以上程序,可以看到 Junit 常用注解使用说明: ...

四月 25, 2023 · 4 分钟

Redis Stream:更完善的消息队列

该数据结构需要 Redis 5.0.0 + 版本才可用使用 概述 Redis stream 是 Redis 5 引入的一种新的数据结构,它是一个高性能、高可靠性的消息队列,主要用于异步消息处理和流式数据处理。在此之前,想要使用 Redis 实现消息队列,通常可以使用例如:列表,有序集合、发布与订阅 3 种数据结构。但是 stream 相比它们具有以下的优势: 支持范围查找:内置的索引功能,可以通过索引来对消息进行范围查找 支持阻塞操作:避免低效的反复轮询查找消息 支持 ACK:可以通过确认机制来告知已经成功处理了消息,保证可靠性 支持多个消费者:多个消费者可以同时消费同一个流,Redis 会确保每个消费者都可以独立地消费流中的消息 话不多说,接下来具体看看如何使用它。(PS:万字长文,行驶途中请系好安全带) XADD 添加元素 XADD 命令的语法格式如下: XADD stream-name id field value [field value] stream-name: 指定 redis stream 的名字 id: 是指 stream 中的消息 ID,通常使用 * 号表示自动生成 field value: 就是消息的内容,是 K-V 格式的键值对 关于使用 XADD 添加元素,还有以下特点: 自动创建流:当 my-stream 流不存在时,redis 会自动创建,然后将元素追加在流的末尾处 任意键值对:流中的每个元素可以包含一个或任意多个键值对 下面是一个使用 XADD 命令添加新消息的示例: XADD my-stream * name John age 30 email john@example.com 上述命令的说明: ...

四月 17, 2023 · 8 分钟

Java 异常处理的使用和思考

概念 异常处理的概念起源于早期的编程语言,如 LISP、PL/I 和 CLU。这些编程语言首次引入了异常处理机制,以便在程序执行过程中检测和处理错误情况。异常处理机制随后在 Ada、Modula-3、C++、Python、Java 等编程语言中得到了广泛采用和发展。在 Java 中,异常处理是提供一种在程序运行时处理错误和异常情况的方法。异常处理机制使得程序能够在遇到错误时继续执行,而不是立即崩溃。这种机制使程序更具有健壮性和容错性。异常分为两类:受检异常(Checked Exceptions)和非受检异常(Unchecked Exceptions) 受检异常(Checked Exceptions): 受检异常是指那些在编译时必须处理的异常。它们通常是由程序员的错误或外部资源问题引起的。例如,IOException、FileNotFoundException 等。受检异常必须在方法签名中使用 throws 关键字声明,或者在方法体内用 try-catch 块捕获和处理。 非受检异常(Unchecked Exceptions): 非受检异常是指那些在编译时不强制要求处理的异常。它们通常是由编程错误引起的,如空指针异常(NullPointerException)、数组越界(ArrayIndexOutOfBoundsException)等。非受检异常继承自 java.lang.RuntimeException 类,不需要在方法签名中声明,也不需要强制捕获和处理。 它们的关系如下: 异常处理 Java 使用 try/catch 关键字进行异常捕获,使用 throw 声明抛出异常,示例代码如下: public class NullPointerExceptionExample { public static void main(String[] args) { String nullString = null; try { int length = nullString.length(); } catch (NullPointerException e) { System.out.println("Caught NullPointerException!"); e.printStackTrace(); } } } 在这个示例中,我们尝试获取一个 null 字符串的长度。当调用 nullString.length() 时,会抛出 NullPointerException。我们使用 try-catch 语句捕获异常并处理它 自定义异常 Java 官方异常不可能预见所有可能发生的错误,有时候你需要结合自己的业务场景,例如以下场景: ...

四月 9, 2023 · 6 分钟