《Redis深度历险》记录

《Redis深度历险》记录 基础与应用 分布式锁 分布式锁用于高并发或分布式事务中保护临界资源。 最常见的实现方式是使用 SET 命令的 NX (不存在时设置)和 PX (设置过期时间)选项: 1 SET lock_name my_random_value NX PX 30000 lock_name : 锁的名称。 my_random_value : 唯一标识(通常使用 UUID)。 NX : 只有键不存在时才设置。 PX 30000 : 设置 30 秒的过期时间。 释放分布式锁时需要验证唯一标识是否匹配,防止误删其他客户端的锁。 超时问题 超时问题有三类: 锁过期,可以通过锁续约机制,即获取锁后启动一个守护线程,定期检查锁是否仍由自己持有,如果是则延长锁的过期时间。 客户端获取锁后崩溃,没有主动释放锁,锁就只能等待超时后才能被其他客户端获取。所以要设置合理的锁超时时间。 Redis 服务器和客户端之间的时钟不同步,可能导致锁提前或延后过期。尽量确保服务器和客户端时钟同步(NTP),同时适当增加锁的超时时间作为缓冲。 分布式锁的实现有 Redlock 算法、go-redis 中的简易 mutex 分布式锁。 延时队列 通过 List 来作为简易的消息队列使用。 通过 rpush , lpush 控制入队,rpop , lpop 控制出队。 使用 List 实现延时队列会出现一个问题,假设 List 为空,那么之后的 pop 操作就是死循环。此时可以使用阻塞读来避免这种情况,即使用 blpop, brpop 两个命令代替之前的出队命令。阻塞读在队列没有数据的时候,会立即进入休眠状态。 同时需要注意,List 长时间为空,Redis 客户端连接可能会因为空闲而被回收。所以需要加入重试机制。...

April 29, 2025 · 914 words · Kurong

《Kubernetes编程》记录

《Kubernetes编程》记录 概论 控制器和 Operator 控制器实现了一个控制循环,通过 API 服务器监测集群中的共享状态,进行必要的变更。Operator 也是一种控制器,但是包含了一些运维逻辑,如应用程序的生命周期管理。 控制循环的基本流程如下: 读取资源的状态,通常采用事件驱动模式,使用 Watch 来实现。 改变集群中的对象状态或集群外部系统的状态。 通过 API 服务器更新第 1 步中所提到的资源的状态,状态放在 etcd 中。 循环执行这些逻辑。 控制器通常会使用以下的数据结构: Informer :Informer 负责观察资源的目标状态,它还负责实现重新同步机制从而保证定时地解决冲突。 工作队列:事件处理器把状态变化情况放入工作队列。 多个相互独立的控制循环通过 API 服务器上的对象状态变化相互通信,对象状态的变化由 Informer 以事件的方式通知到控制器。这些通过 Watch 机制发送的事件对用户来说几乎不可见,在 API 服务器的审计机制中也看不到这些事件,只能看到对象更新的动作。控制器在处理事件时,以输出日志的方式记录它做的动作。 Watch 事件:在 API 服务器与控制器之间通过 HTTP 流的方式发送,用于驱动 Informer 。 Event 对象:是一种用户可见的日志机制,如 kubelet 通过 Event 来汇报 Pod 的生存周期事件。 乐观并发 Kubernetes 采用乐观并发机制,假设多用户操作同一资源的冲突概率较低,允许并发修改,仅在提交时检测冲突。 每个 Kubernetes 资源对象(如 Pod、Deployment)的元数据中都有一个 resourceVersion 字段,用于标识资源的当前版本号(由 etcd 生成)。这是乐观并发的核心: 读取资源时:客户端获取对象的 resourceVersion(例如 123)。 更新资源时:客户端必须在请求中携带该版本号(通过 metadata.resourceVersion 字段)。 服务器端校验:API Server 比较请求中的 resourceVersion 与当前存储中的版本: 匹配:允许更新,resourceVersion 递增。 不匹配:返回 409 Conflict 错误,拒绝更新(说明资源已被其他客户端修改过)。 当发生冲突时,客户端需重新获取资源的最新状态,然后基于最新数据重新提交修改。...

April 9, 2025 · 212 words · Kurong

《Kafka权威指南》记录

《Kafka权威指南》记录 初识 Kafka 消息和批次 消息有一个可选的元数据,也称为键。键是一个字节数组,可以用来实现如分布式键等应用。 为了提高效率,消息会被分批次写入 Kafka,批次包含了一组同属于一个主题和分区的消息。 模式 模式就是消息的格式,如Json、XML。 主题和分区 消息通过主题进行分类,主题又被分为若干个分区。 生产者和消费者 默认情况下,生产者会把消息均衡地分布到主题的所有分区中,也可以通过自定义设置决定消息发布到哪里。 消费者通过检查消息的偏移量来区分已经读取过的消息。 broker 和集群 一台单独的 Kafka 服务器称为 broker,单个 broker 可以处理数千个分区和每秒百万级的消息量。 集群由 broker 组成,同时也有集群中都有的分布式共识算法。Kafka 使用 ZAB 作为共识算法,2.8 版本后支持 KRaft 管理元数据。 Kafka 与其他消息队列的区别 Kafka 与其他消息队列相比,有以下几个显著的区别: 高吞吐量与低延迟 Kafka 设计上支持高吞吐量和低延迟,能够处理海量数据和高并发写入、读取场景。 其他消息队列(如 RabbitMQ、ActiveMQ)通常适用于较小规模或实时性要求较高的场景,但在海量数据和高并发场景下可能会遇到性能瓶颈。 持久化与分布式存储 Kafka 使用磁盘持久化和日志分段技术,并通过分区(Partition)和副本(Replica)机制实现分布式存储和容错。 其他消息队列有的采用内存队列(例如 RabbitMQ 默认使用内存),虽然也支持持久化,但在大规模数据存储和高可靠性需求下不如 Kafka 灵活。 消息模型与消费模式 Kafka 基于发布-订阅模型,采用拉取(Pull)的消费模式。消费者主动拉取消息,可以灵活控制消费速率和消息重放。 其他消息队列(例如 RabbitMQ)多采用推送(Push)模式,消息直接推送给消费者,实时性较好,但可能在处理慢消费者时需要额外机制(如 ACK、流控等)。 数据持久性和消息重放 Kafka 中的消息存储在日志中,并且消息不会在被消费后自动删除(可以根据保留策略删除),这使得消费者可以根据需求重放消息。 其他消息队列通常在消息被消费确认后就删除,无法轻易重放历史消息。 扩展性与容错性 Kafka 天生支持分布式集群部署,通过增加 Broker 节点来横向扩展,且内置副本机制保证容错。 其他消息队列虽然也有集群部署方案,但在大规模扩展和高容错要求下往往不如 Kafka 那样简单高效。 应用场景不同 Kafka 更适用于日志收集、流处理、大数据实时分析等场景。 其他消息队列则更适合需要复杂路由、即时响应、任务队列等场景,比如电商下单、实时通知等。 Kafka 生产者 消息发布方式:...

March 26, 2025 · 132 words · Kurong

《MongoDB 权威指南》记录

《MongoDB 权威指南》记录 MongoDB 简介 MongoDB 是面向文档的非关系型数据库。 MongoDB 具有以下特点: 易于使用:没有“行记录”的概念,而使用“文档”模型取代之。同时也没有预定义模式,文档键值的类型和大小是动态的。 易于扩展:MongoDB 采用了横向扩展方式,面向文档的数据模型使得跨多台服务器拆分数据更加容易。 性能强大:使用了机会锁,提高并发和吞吐量。同时会尽可能多的使用内存作为缓存,并尝试为查询自动选择合适的索引。 入门指南 文档 文档是一组有序键值的集合,如以下形式: 1 {"q": "ans"} 集合 集合就是一组文档,相当于关系型数据库的表结构。 集合具有动态模式的特性,每个文档的长度大小、键数量都可以不一样。不过开发中并不推荐这样做,还是将“形状”相同的文档存在同一个集合中好,因为这样查询起来性能不会受到影响。 在 MongoDB 中通常会用 . 字符分隔不同命名空间的子集合,如 blog.posts 。 数据库 MongoDB 使用数据库对集合进行分组。 数据类型 基本数据类型:除了 int 等,还有 Regex、Array、ObjectID(12字节的文档的唯一ID 标识)、Bytes、JSCode。 Array: {"things": ["pie", 3.14]},数组内可以混有不同类型的数据。 内嵌文档:文档作为值。 ObjectID:每个文档都有一个 _id ,默认是 ObjectID ,这个 ID 在集合内是唯一的。可以看到这个 ID 不是自增的,类似于 UUID,这说明 MongoDB 在设计之初就是作为分布式数据库存在的。以下是它的生成规则: 前 4 字节为时间戳,中间 5 个字节是随机值,后 3 个字节是计数器(起始值随机)。 索引 MongoDB 中可以设置单一索引和复合索引,方式如下: 1 db.users.createIndex({"age": 1, "username": 1}) # 复合索引 MongoDB 的默认存储引擎是 WiredTiger ,索引是 B+ 树实现的。所以在索引的优化上,可以参照相关关系型数据库的索引优化经验与原则。下面是部分经验:...

March 22, 2025 · 143 words · Kurong

《HTTP权威指南》记录

《HTTP权威指南》记录 本书中的 HTTP 协议版本为 HTTP/1.1 。 URL 与资源 URL 语法 在 URL 中以分号 ; 作为参数的分隔符,如下: 1 http://xxx.xxxx.com/hammers;sale=false/index.html;graphics=true 有些资源需要通过查询字符串来缩小查找范围,查询字符串通过 ? 分割,如常见的 GET 请求中的查询参数。 URL 支持使用片段组建来表示一个资源内部的片段,以 # 进行分割: 1 http://xxx.xxx.com/index.html#head Web 服务器 Web 服务器应该做的: 接受客户端连接:客户端收到一条连接之后,那么它将会把新连接添加到现存web服务器连接列表中,用于监视当前连接上的数据传输情况。期间服务器还应该做到通过一定的设备机制阻止未认证或已知恶意黑名客户端的连接。 接收请求报文: 这一步需要解析请求报文: 解析请求行,查找请求方式、URI 、版本号以及 CRLF 分隔符。 读取以 CRLF 结尾的报文首部。 检测到以 CRLF 结尾的,标识首部结尾的空行。 解析得到请求体。 处理请求:其他章节会详解。 对资源的映射及访问:找到客户端请求资源在服务器的上的目录路径。 Web 服务器存放内容的文件夹称为文档的根目录(doc root),Web 服务器会从请求报文中获取 URI ,并将其附加在 doc root 的后面。 在一个服务器上挂载多个 Web 站点,那么这样当请求的资源路径相同时,服务器应该从请求报文首部的 HOST 和 URI 字段找出真正的资源目录,这些目录都可以更改配置。 构建响应: 正确设置响应主体的长度(content-length)。 设置报文的 MIME 类型(content-type)。 有时候资源不在原地,需要进行重定向。 发送响应。 记录事务日志。 代理 Web 的中间实体 Web 代理服务器是代表客户端对事务请求处理的中间人,代理分为私有代理(只代理一个客户端)和公共代理(代理多个客户端)。 代理和网关的对比:代理的两端使用相同的协议,而网关的两端使用不同的协议,网关负责协议转换。 代理应用 内容过滤。 文档访问控制。 安全防火墙。 Web 缓存:缓存资源的副本。 反向代理:反向代理伪装成原始服务器,不过与服务器不同的是反向代理还可以向其他服务器发送请求,以便实现按需定位所请求的内容。 ……....

March 14, 2025 · 315 words · Kurong

《深入理解分布式共识算法》记录

《深入理解分布式共识算法》记录 从 ACID 和 BASE 到 CAP BASE 理论 BASE 包含; BA(Basically Available):基本可用,当分布式系统出现故障的时候,允许损失部分可用性。这里损失指响应时间的损失和功能上降级,前者是在增加响应时间上来提供服务,后者是在高峰期不请求后端而直接返回降级数据、以保证系统的稳定性。 S(Soft State):软状态,允许系统中的数据存在中间状态,并认为该状态不会影响系统的整体可用性,即允许节点之间的数据同步存在延迟。 E(Eventually Consistent):最终一致性。 为应用 BASE 理论,可以采用异步补偿机制。如果用这个机制,需要明确哪些操作属于非关键操作,如果非关键操作失败,应允许业务流程继续执行,然后在异步补偿非关键操作。 补偿指事务补偿:在分布式事务中,由于各个服务可能分布在不同的节点上,无法像单机事务那样简单地通过“回滚”来撤销操作。因此,事务补偿通过执行与原始操作相反的操作来达到类似的效果。 补偿操作需要注意两点: 幂等性:补偿操作可能需要多次执行,因此需要保证其幂等性,即多次执行的效果与一次执行相同。 最终一致性:补偿机制通常用于实现最终一致性,而不是强一致性,因此需要权衡一致性和性能。 CAP 理论 CAP 包含: C(Consistency)一致性:分布式系统的全序线性一致性。 A(Availability)可用性:任何情况都能够处理客户端的每个请求。 P(Partition Tolerance)分区容错性:发生网络分区时,系统仍能提供服务。 任何分布式系统都可分为三类架构: CP(锁定事务资源):所有节点的数据需要实时保持一致性,这需要锁定各分支事务的资源。当发生分区时,此期间为确保返回客户端数据的准确性且不破坏一致性,可能会因为无法响应最新数据而拒绝响应,即放弃 A 。 AP(尽力提供服务):要求每个节点拥有集群的所有能力或数据,能独立处理客户端的每个请求。发生分区时,可以通过缓存后本地副本来处理请求,以达到可用性,但会出现各节点不一致性,即放弃 C 。 CA(本地一致性):在不发生分区时,C、A 正常满足。一旦发生分区,会保证各子分区满足 CA 。 但是网络分区不可避免,CAP 理论就变成了在 C 和 A 中的权衡问题。 2PC、3PC,分布式事务的解决方案 2PC 2PC 中的字母含义是 P(Participant,参与者),C(Coordinator,协调者)。 2PC 即两阶段提交协议,用于解决分布式事务问题。第一阶段用于各个分支事务的资源锁定,第二阶段用于全局事务的提交或回滚: 第一阶段,准备: 开启全局事务。当协调者收到客户端的请求后,它将各个分支事务需要处理的内容通过 Prepare 请求发送给所有参与者,并询问能否正常处理自己的分支事务,然后等待各个参与者的响应。 处理分支事务。当参与者收到 Prepare 请求后便锁定事务资源,然后尝试执行,记录 Undo 和 Redo 信息,但不提交分支事务。 汇报分支事务状态。参与者根据第 2 步执行的结果,向协调者汇报各自的分支事务状态,Yes 表示可以提交,No 表示不能提交。 第二阶段,提交/回滚:...

March 7, 2025 · 298 words · Kurong

《数据密集型系统设计》记录

《数据密集型系统设计》记录 本书的电子版本链接:Vonng/ddia: 《Designing Data-Intensive Application》DDIA中文翻译 3. 存储与检索 本章围绕两大类存储引擎:日志结构(log-structured)的存储引擎,以及面向页面(page-oriented)的存储引擎(例如 B 树)。 数据库核心:数据结构 这里所使用的“日志”是一个更一般性的概念:一个支持 append-only 仅追加操作的记录序列。 为了高效查找数据库中特定键的值,需要用到索引。索引是从主要数据中衍生的额外结构,维护它会产生额外开销,特别是写入时。任何类型的索引通常都会减慢写入速度,因为每次写入数据时都需要更新索引。这是存储系统中一个重要的权衡:精心选择的索引加快了读查询的速度,但是每个索引都会拖慢写入速度。因为这个原因,数据库默认并不会索引所有的内容,而需要程序员或数据库管理员(DBA),基于对应用的典型查询模式的了解来手动选择索引。 SSTable 把键值对的序列按照键进行排序,这个格式称为排序字符串表(Sorted String Table, SSTable)。此时再要求每个键值在每个合并的段文件中出现一次。与使用散列索引的日志段相比,SSTable 有几个大的优势: 即使文件大于可用内存,合并段的操作仍然是简单高效: 一开始并排读取多个输入文件,查看每个文件中的第一个键,复制最低的键(根据排序顺序)到输出文件,不断重复此步骤,将产生一个新的合并段文件,而且它也是也按键排序的。 如果在几个输入段中出现相同的键,该怎么办?请记住,每个段都包含在一段时间内写入数据库的所有值。这意味着一个输入段中的所有值一定比另一个段中的所有值都更近(假设我们总是合并相邻的段)。当多个段包含相同的键时,我们可以保留最近段的值,并丢弃旧段中的值。 为了在文件中找到一个特定的键,你不再需要在内存中保存所有键的索引: 假设你正在内存中寻找键 handiwork,但是你不知道这个键在段文件中的确切偏移量。然而,你知道 handbag 和 handsome 的偏移,而且由于排序特性,你知道 handiwork 必须出现在这两者之间。这意味着你可以跳到 handbag 的偏移位置并从那里扫描,直到你找到 handiwork(或没找到,如果该文件中没有该键)。 由于读取请求无论如何都需要扫描所请求范围内的多个键值对,因此可以将这些记录分组为块(block),并在将其写入硬盘之前对其进行压缩。稀疏内存索引中的每个条目都指向压缩块的开始处。除了节省硬盘空间之外,压缩还可以减少对 I/O 带宽的使用。 那么,可以构建 SSTable 了,可以让存储引擎这样工作: 有新写入时,将其添加到内存中的平衡树数据结构(例如红黑树),这个内存树有时被称为内存表(memtable)。 当内存表大于某个阈值(通常为几兆字节)时,将其作为 SSTable 文件写入硬盘。这可以高效地完成,因为树已经维护了按键排序的键值对。新的 SSTable 文件将成为数据库中最新的段。当该 SSTable 被写入硬盘时,新的写入可以在一个新的内存表实例上继续进行。 收到读取请求时,首先尝试在内存表中找到对应的键,如果没有就在最近的硬盘段中寻找,如果还没有就在下一个较旧的段中继续寻找,以此类推。 后台进程周期性地执行合并和压缩过程,以合并段文件,并将已覆盖或已删除的值丢弃。 这样的索引结构被称为 LSM-Tree(Log-Structured Merge-Tree)。 当查找数据库中不存在的键时,LSM 树算法可能会很慢。为了优化这种访问,存储引擎通常会使用布隆过滤器。还可以使用大小分级和分层压缩。对于大小分级,较新和较小的 SSTables 相继被合并到较旧的和较大的 SSTable 中。对于分层压缩,键的范围被拆分到多个较小的 SSTables,而较旧的数据被移动到单独的层级,这使得压缩能够逐步进行并且使用较少的硬盘空间。 B-Tree 前面看到的日志结构索引将数据库分解为可变大小的段,通常是几兆字节或更大的大小,并且总是按顺序写入段。相比之下,B 树将数据库分解成固定大小的块或分页,传统上大小为 4KB(有时会更大),并且一次只能读取或写入一个页面。这种设计更接近于底层硬件,因为硬盘空间也是按固定大小的块来组织的。 在 B 树的底层中,写操作是用新数据覆写硬盘上的页面,并假定覆写不改变页面的位置。即当页面被覆写时,对该页面的所有引用保持完整。这与日志结构索引(如 LSM 树)形成鲜明对比,后者只追加到文件(并最终删除过时的文件),但从不修改文件中已有的内容。...

February 7, 2025 · 585 words · Kurong

《Kubernetes微服务实战》记录

《Kubernetes微服务实战》记录 面向开发人员的 Kubernetes 简介 部分核心概念 Node:Node 是 Kubernetes 集群中的一个工作单元,表示集群中的一台物理机或虚拟机。Node 的作用是为运行容器化应用提供计算资源,一个 Node 可以运行多个 Pod。Node 分为: Master Node:负责集群的控制平面,管理所有工作节点。 Worker Node:运行实际的应用程序工作负载,实际上就是 Pod。 Pod:Pod 是 Kubernetes 中最小的可部署单位,表示一个或多个容器的集合。有以下特性: Pod 内的容器共享一个 IP 地址,可以通过 localhost 相互通信。 Pod 内的容器可以共享挂载的存储卷(Volume)。 Pod 通常包含运行同一应用程序组件的容器,如主应用程序容器、日志采集容器等。 Namespace:用于逻辑上隔离资源。可以在同一集群中运行多个项目或团队的工作负载。 Service:用于将一组 Pod 的访问暴露给外部或集群内部的其他 Pod。 Deployment:用于声明和管理 Pod 的期望状态,例如副本数、滚动更新等。 Volume:提供持久化存储,允许 Pod 重启后数据仍然保留。 Kubernetes 架构 每个集群都有一个控制平面和数据平面,数据平面由多个节点组成,控制平面将在这些节点上部署并运行 Pod(容器组),监控变更并做出响应。 控制平面包含以下组件: API 服务器。 etcd 存储。 调度器:kube 调度器负责将 Pod 调度到工作节点。 控制器管理器:kube 控制器管理器是包含多个控制器的单个进程,这些控制器监控着集群事件和对集群的更改做出响应。 节点控制器:负责在节点出现故障时进行通知和响应。 副本控制器:确保每个副本集(replica set)或副本控制器对象中有正确数量的 Pod 。 端点控制器:为每个服务分配一个列出该服务的 Pod 的端点对象。 服务账户和令牌控制器:使用默认服务账户和响应的 API 访问令牌对新的命名空间进行初始化。 数据平面是集群中将容器化工作负载转为 Pod 运行的节点的集合。...

January 25, 2025 · 1376 words · Kurong

minikube:Ubuntu部署本地集群踩坑

minikube:Ubuntu 部署本地集群踩坑 官方安装教程 minikube start | minikube 再次感叹 homebrew 的伟大,一行命令就安装、后续也没有问题。 仪表盘 使用 minikube dashboard 启动仪表盘。如果在服务器上启动、本地访问,就运行类似于的下面命令: 1 kubectl proxy --address='0.0.0.0' --disable-filter=true 然后把 http://127.0.0.1:46749/api/v1/namespaces/kubernetes-dashboard/services/http:kubernetes-dashboard:/proxy/ 这样的 URL 改为 http://ServerIP:8001/api/v1/namespaces/kubernetes-dashboard/services/http:kubernetes-dashboard:/proxy/ ,就可以外网直接访问了(注意服务器开端口)。 Helm 安装 直接使用 snap 安装,方便快捷: 1 sudo snap install helm --classic kubectl 安装 snap 上大分: 1 sudo snap install kubectl --classic 安装可能遇到的问题的解决方案 The “docker” driver should not be used with root privileges 使用命令 minikube start 启动,参数 driver 默认为 docker 。如果以 root 用户运行该命令会出现如标题所示的错误,下面是解决方法: minikube - Why The “docker” driver should not be used with root privileges - Stack Overflow...

January 25, 2025 · 219 words · Kurong

《云原生开发实践》记录

《云原生开发实践》记录 本书会包含大量的实践内容,现阶段应着重看理论内容、实践内容暂且忽略,实践内容在理论补充完毕后用到哪块补哪块。 容器化 DOCKERFILE 的多阶段构建在部署阶段很有用,可以大幅减少镜像的体积。 Docker 可以自建网络,这样就可以把很多容器纳入到同一个网络下,实现容器间按容器名访问对方。 容器编排 对于一个中大型的应用,会有很多的容器组件。而有些容器如Redis、RabbitMQ、Kafka等需要紧密的配合,这时候就需要用到容器编排。 Docker Compose 组件用于实现本地单个节点上的容器编排,它会从 docker-compose.yaml 文件中读取所需的全部容器的定义,然后运行 docker-compose up 命令启动容器编排。Docker Swarm 可以管理多个节点上的容器,即管理 Docker 集群。 云原生软件生产流程 云计算的能力: 弱化了传统 IT 硬件概念,革命性地降低了企业在基础设施上的建设成本。 实现了弹性获取计算资源,用户可以按需使用。 云计算的成熟时云原生发展的基石,使云原生的许多概念得以落地。云原生应用一般有以下特点: 在应用的设计和开发阶段就为部署到云上做适配。 应用由多个松耦合的小模块构成而不是一个庞大的单体项目,即微服务架构。 通过容器来交付和发布应用,应用代码中会加入容器化需要的文件。 和传统的软件生产方式相比,云原生的优势主要在塔提高了应用发布和运维时的效率,显著降低了运维的复杂性。 云原生基础设施 Kubernetes Kubernetes 是开源的容器编排平台,支持集群环境下部署和管理容器化应用。目前已经是容器编排领域的事实标准,成为云原生的操作系统。Kubernetes 也叫 K8s ,8指中间的8个字母。K8s 2013年由 Google 开源,相比于 Docker Swarm K8s 提供更加复杂强大的功能。 K8s 集群中包含两种节点,一种是 Control Plane 节点(master 节点),另一种是 worker 节点。K8s 中的容器运行在 Pod 中,Pod 运行在 Node 中。每个集群默认至少有一个 Pod ,否则 Pod 无法调度和运行。Control Plane 是 K8s 的容器编排层,通过它提供的 API ,可以定义和部署容器及管理容器的整个生命周期。Control Plane 有以下组件:...

January 22, 2025 · 107 words · Kurong

《Go微服务实战》记录

《Go微服务实战》记录 本书包含很多的 Go 基础和部分进阶内容,这里只选取对现阶段有帮助的内容,毕竟 Go 已经入门过一遍了。 Go 基础 关于指针,如果涉及到修改变量本身就使用指针作为函数输入变量等,典型的如单例模式下的变量都是指针。反之如果不修改变量本身,就直接使用复制变量作为函数输入变量等,函数返回值自然也是某个新的变量。 关于 Go 中的循环,请看 code: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 package main func main() { for i := 0; i < 10; i++ { ... } // 最标准的循环 for { ... } // 就是没有显式跳出条件的 while arr := []int{1, 2, 3, 4, 5} for i, v := range arr { ... } // 这里的 range 是关键字,用法有些类似于 Python 中的 enumerate 函数 } Go 的垃圾回收策略使用的三色标记法,具体内容会单独进行学习。...

January 18, 2025 · 2955 words · Kurong

《PostgreSQL指南》记录

《PostgreSQL指南》记录 Why choose PostgreSQL, or not MySQL ? SQL 标准支持更好:PostgreSQL 对 SQL 标准的支持更加全面,而 MySQL 在某些情况下依赖非标准实现(如分组函数的行为)。 高级功能更强: JOSNB 的性能更优:PostgreSQL 的 JSONB 数据类型支持更高效的索引和操作,而 MySQL 的 JSON 功能较为有限。 复杂查询:支持递归查询、窗口函数等复杂功能,而 MySQL 在这些方面功能较弱或支持有限。 并行查询:PostgreSQL 提供原生的并行查询能力,能够充分利用多核 CPU,而 MySQL 直到 8.0 版本才有一定程度的改进。 扩展性更好: PostgreSQL 支持自定义数据类型、函数和插件,更适合需要扩展或复杂业务逻辑的项目。 PostGIS 插件使其在地理信息领域表现出色,而 MySQL 的 GIS 功能相对简单。 更高的性能和灵活性: 支持多种索引类型(如 GIN、GiST、BRIN 等),适合高性能全文搜索、地理查询等场景。 支持更复杂的查询优化器策略,适合复杂查询和数据分析。 更可靠的数据一致性: PostgreSQL 的 MVCC 机制更成熟,避免了常见的锁争用问题。 MySQL 的 MVCC 在某些情况下(如高并发)性能欠佳,容易导致死锁。 PostgreSQL 适用于有复杂数据分析需求、地理信息系统、高并发和大数据量等应用场景,相比于 MySQL 的应用场景更复杂。 数据库集簇、数据库和数据表 数据库集簇的逻辑结构 数据库集簇是一组数据库的集合,由一个 PostgreSQL 服务器管理。而数据库是数据对象的集合,数据库对象用于存储或引用数据的数据结构。 在 PosgreSQL 中,所有的数据库对象都通过相应的对象标识符(oid)进行管理,这些标识都是无符号 4 字节整型。...

December 22, 2024 · 768 words · Kurong

《高效使用Redis》记录

《高效使用Redis》记录 基础数据结构解析 对象 Redis中的 RedisObject 的 c 定义: 1 2 3 4 5 6 7 8 #define LRU_BITS 24 typedef struct redisObject { unsigned type:4; // 数据类型 unsigned encoding:4; // 底层数据结构 unsigned lru:LRU_BITS; // 缓存淘汰时使用 int refcount; // 引用计数 void *ptr; // 指向实际存储位置 } robj; RedisObject 是 Redis 中数据结构的一个抽象,用它来存储所有的 key-value 数据。 下面是结构体中各个属性的。说明: type:用来表示对象类型; encoding:表示当前对象底层存储所采用的数据结构,如int、字典、压缩列表、跳跃表等等; lru:用于在配置文件中通过 maxmemory-policy 配置已用内存到最大内存限制时的缓存淘汰策略。 以 GET 命令为例,简单看看 Redis 中的 LRU 实现。使用 GET 后,会执行这段代码更新对象的 lru 属性: 1 2 3 4 5 if (server....

December 5, 2024 · 1410 words · Kurong

《gRPC与云原生应用开发》记录

《gRPC与云原生应用开发》记录 gRPC 入门 gRPC 的定义 gRPC 是一项进程间通信技术,可以用来连接、调用、操作和调试分布式异构应用程序。 开发 gRPC 应用程序时,要先定义服务接口:消费者消费信息的方式、消费者能够远程调用的方法以及调用方法所使用的参数和消息格式等。在服务定义中使用的语言叫作接口定义语言(IDL)。 下面是使用 protocol buffers 作为 IDL 来定义服务接口: 服务定义 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 // ProductInfo.proto syntax = "proto3"; package ecommerce; // 防止协议消息类型之间发生命名冲突 // 定义服务接口 service ProductInfo { rpc addProduct(Product) returns (ProductID); rpc getProduct(ProductID) returns (Product); } // 定义消息格式 message Product { string id = 1; string name = 2; string description = 3; } message ProductID { string value = 1; } 上面定义完成之后,就使用 protocol buffers 编译器 protoc 生成服务器端和客户端代码。...

November 30, 2024 · 963 words · Kurong

《深入RabbitMQ》记录

《深入RabbitMQ》记录 RabbitMQ 基础 AMQP 协议 AMQP,即高级消息队列协议(Advanced Meesage Queuing Protocal)。它定义了一个 AMQ 应该有以下三部分: 交换器:一个接收器接收发送到 MQ 中的消息并决定把它们投递到何处; 队列:存储接收到的消息; 绑定:定义队列和交换器之间的关系。在 RabbitMQ 中,绑定或者叫绑定键即告知一个叫交换器应该将消息投递到哪些队列中。 使用 AMQ 协议与 Rabbit 进行交互 AMQP 帧类型 AMQP 规定了五种类型的帧: 协议头帧:用于连接到 RabbitMQ,仅使用一次; 方法帧:携带发送到 RabbitMQ 或从 RabbitMQ 接收到的 RPC 请求或响应; 内容头帧:包含一条消息的大小和属性; 消息体帧:包含消息的内容; 心跳帧:确保连接,一种校验机制。 将消息编组成帧 首先是方法帧、内容头帧,这两种帧比较好理解。 而消息体帧会根据消息体的大小切分成一到多个消息体帧。 要发送消息,首先发送的是方法帧,之后是内容帧,最后是若干个消息体帧。 帧结构 方法帧携带构建 RPC 请求所需的类、方法和参数。 内容头帧包含了消息的大小和其他对消息起描述作用的属性。 消息体帧可以传输很多类型的数据,可以是二进制、文本,也可以是二进制化的图片和序列化后的json、xml。 使用协议 包含下面一个基本流程: 声明交换器 声明队列 绑定队列到交换器 发布消息到 RabbitMQ 从 RabbitMQ 消费消息 消息属性详解 使用 content-type 创建显式的消息契约 在不同的语言的消费者框架中,框架可以自动的根据 content-type 类型将其反序列化为该语言中的数据结构。 使用 gzip 和 content-encoding 压缩消息大小 通过指定 content-encoding 属性可以在消息体上应用特殊的编码。...

November 28, 2024 · 192 words · Kurong

华为云部署踩坑

华为云部署踩坑 Docker 国内镜像源(截止到2024.11.1) DockerHub 镜像仓库 是否正常 hub.xdark.top 正常 hub.littlediary.cn 正常 dockerpull.org 新增 hub.crdz.gq 正常 docker.1panel.live 正常 docker.unsee.tech 新增 docker.m.daocloud.io 正常 docker.kejilion.pro 正常 registry.dockermirror.com 正常 hub.rat.dev 正常 dhub.kubesre.xyz 正常 docker.nastool.de 正常 docker.hpcloud.cloud 失效 docker.hlyun.org 失效 doublezonline.cloud 失效 docker.chenby.cn 失效 ginger20240704.asia 失效 lynn520.xyz 失效 hub.docker-ttc.xyz 失效 noohub.ru 失效 docker.nat.tf 失效 dockerproxy.cn 失效 freeno.xyz 失效 docker.registry.cyou 失效 hub.yuzuha.cc 失效 docker-cf.registry.cyou 失效 docker.mrxn.net 失效 dockerproxy.github.io 失效 docker.wget.at 失效 atomhub.openatom.cn 失效 ccr.ccs.tencentyun.com 失效 dockerproxy.com 失效 dislabaiot....

November 5, 2024 · 327 words · Kurong

《计组KG》课题开发过程(三)

牢骚 时隔一个多月才完成了基本的可视化系统的搭建,中间有着各种各样的原因:Vue3第一次用、Django-Ninja不熟悉等等再加上一些闲杂原因就一直拖到了现在,效率多少有点堪忧。 总之不算怎么说,也算是曲折的完成了原定计划,下面将从后端、前端的技术选择、功能介绍、成品展示等几个章节大致的讲述下。 后端 项目地址 KurongTohsaka/PCCKGVisualization 技术栈 Web 框架自然是选相对擅长且好用的 Django ,但是 Django 本身使用起来又过于繁重,不太适合开发Restful类型的接口,所以一般会搭配着 Django REST Framework (DRF) 使用。但是这一次我打算尝试一个新的 Django 扩展,它是 FastAPI 的 Django 版:Django-Ninja 。 Web 框架订好了以后,就该选数据库了。既然是存储图数据,那肯定是 Neo4j 。而网站数据就使用简单的 Sqlite 吧,主要就是省事。 功能与接口 下面的所有接口的最上级请求路径:pcc_kg_vs 功能主要分为两部分:认证和可视化。 首先是认证,包含以下基本功能: 用户认证:用户登陆、注册、登出 API 鉴权:token 验证 CSRF 验证:CSRF token 验证 下面是简易的接口标准: 登陆 先检查该用户是否已注册,若注册则从数据库中根据信息查询到用户,然后登陆该用户。若未注册则登陆失败 1 2 3 4 5 6 7 8 9 10 11 12 13 14 { "method": "POST", "path": "/login", "params": { "username": "", "password": "" }, "return": { "successful": bool, "code": int, "token": str, //登陆令牌 'info': str } } 登出 HTTPBearer...

November 4, 2024 · 247 words · Kurong

MAC上用docker配置neo4j以及apoc扩展的过程

拉取 neo4j Docker image 在 MAC 上用的 Docker Desktop,确实很方便操作。 直接在 Image Tab 页搜 Neo4j 就有一个官方 image,我选的 5.24.1版本,pull 下来。 这里之所以要选择一个特定的版本,原因是之后要启用的 apoc 扩展需要用对应 neo4j 版本的 jar 包。 配置容器 因为用的 Docker Desktop,很多没必要的步骤都可以直接忽略。配置好转发端口、挂载位置后,就启动容器。 Neo4j 容器启动后默认直接运行,登陆 Web 管理界面配置后用户名密码后就完成了基本配置。 Docker 真 tm 好用( 配置 apoc 这里需要注意一点,neo4j 5.x 之后的版本中,apoc 本身的相关配置需要写在 neo4j/conf/apoc.conf 里,而不是 neo4j/conf/neo4j.conf 中,不然在启动服务时会报错!!! 首先是 apoc 的下载地址:Index of /doc/neo4j-apoc/ 。 下载好后,把它放进 neo4j/plugins,并改名为 apoc.jar (啥名都行,这里是方面后面改配置)。 接下来修改 neo4j/conf/neo4j.conf ,修改以下两行并取消该行注释: 1 2 dbms.security.procedures.unrestricted=apoc.* dbms.security.procedures.allowlist=apoc.coll.*,apoc.load.* 最后修改 neo4j/conf/apoc.conf : 1 2 apoc.import.file.enabled=true apoc....

October 22, 2024 · 72 words · Kurong

《计组KG》课题开发过程(二)

前言 自从上次记录已经过去了一个月,整个课题进展不大。原因一个是暑期有点摆,另一个是关系抽取确实比较繁琐。不管怎么说,来记录下吧。 NER 数据集下的模型训练 首先需要声明的是该阶段的模型不参与于最后 KG 的构建,目的仅仅是跑通模型训练、验证的过程,为后续阶段提供便利。 NER 数据集 该部分信息在完成 RE 部分后可能会发生些微变动,仅作参考,后续会做调整。 共标记5147条中文语句,实体共标注1472个。 下面是各个标签下的数量统计: Label Count TECH 388 COMP 382 STOR 170 DATA 133 INST 105 ARCH 71 IO 61 PERF 54 PROG 52 CORP 17 ALG 16 PROT 15 PER 4 GRP 4 模型选择 模型有两大类: 传统深度学习方法 CNN-CRF BiLSTM-CRF BERT 系预训练模型,输出层为 CRF 或 MLP+Softmax BERT:BERT 是一个双向 Transformer 模型,通过掩码语言模型(Masked Language Model, MLM)和下一句预测(Next Sentence Prediction, NSP)任务进行预训练 RoBERTa:RoBERTa 是对 BERT 的优化版本,移除了 NSP 任务,并采用了动态掩码策略 ALBERT:ALBERT 是 BERT 的轻量级版本,通过参数共享和嵌入参数因子化来减少模型大小 XLM-RoBERTa:XLM-RoBERTa 是针对多语言的预训练模型,基于 RoBERTa 和 XLM 的结合 这里选择的是 XLM-RoBERTa,预训练模型选择的是 FacebookAI/xlm-roberta-large-finetuned-conll03-english · Hugging Face...

September 14, 2024 · 423 words · Kurong

《计组KG》课题开发番外:关系抽取的思考过程

现状 目前课题进行到了关系抽取这一步。在看完之前的RE综述后,我决定整一个 Joint 的 NER+RE 模型。目前已经完成了 NER 的标注工作,下一步工作自然就是 RE 的标注。但是 RE 的标注要比 NER 要困难的多,一个是对于关系的定义依赖于实体类型、句子词性等多种复杂要素,二是手工标注势必工作量巨大。所以 RE 的标注有什么解决方法呢? 我决定把整个思考过程记录下来,方便复盘。 相关开源库 thunlp/OpenNRE:用于神经关系提取 (NRE) 的开源包 (github.com) SapienzaNLP/relik:检索、读取和 LinK:学术预算上的快速准确的实体链接和关系提取 (ACL 2024) (github.com) huggingface/setfit:使用句子转换器进行高效的少样本学习 (github.com) EleutherAI/lm-evaluation-harness:一种用于语言模型小样本评估的框架。 (github.com) 关系抽取的可能方案 首先能想到的两种常见方案: 使用 RE 预训练模型预标注:和 NER 的标注工作流程一致,但是中文 RE 预训练模型很难找 远程监督:使用已有的知识库或实体关系词典,对大规模文本进行远程监督标注。但是不适合当前课题的小规模数据集,而且没有现成的大量 RE 标注数据 以上两种方案中只有预训练模型的预标注还算可行,那有没有什么方法可以加速这一过程?可以看看下面的两种方法: 半监督学习:结合少量人工标注数据和大量未标注数据,通过半监督学习的方法训练模型 few-shot 小样本学习:使用少量人工标注的数据对few-shot模型进行训练,以提高模型在少样本情况下的泛化能力 这两种方案值得单独进行介绍。 半监督学习 半监督学习(Semi-Supervised Learning)是一种结合了监督学习和无监督学习的机器学习方法。它利用少量的标记数据和大量的未标记数据来训练模型,从而提高模型的泛化能力和性能。 半监督学习的样本标注依赖假设,以下是部分常见假设: 平滑性假设:如果两个数据点在高密度区域中且距离很近,那么它们的输出也应该相似 聚类假设:如果数据点形成簇,那么同一簇中的数据点应该属于同一类 流形假设:高维数据通常位于低维流形上,同一流形上的数据点具有相同的标签 常用的方法有: 一致性正则化:假设对未标记数据加入小扰动后,其分类结果不应改变 伪标签:使用已标记数据训练初始模型,然后用该模型对未标记数据进行预测,生成伪标签,再将这些伪标签数据加入训练集中进行再训练 生成式模型:利用生成模型(如GANs)从数据分布中生成样本,并将这些样本用于训练分类器 优势: 利用未标注数据,提高模型的泛化能力和性能 较低标注成本 适应性强,可以应用于多种任务 缺点: 依赖数据假设:半监督学习通常假设未标记数据和标记数据在特征空间中具有相似性,这在实际应用中并不总是成立。如果这些假设不成立,可能会导致模型性能下降 标签传播误差 数据不平衡问题 我曾经在 kaggle 比赛中使用过半监督学习中的伪标签方法,只从工程实现的角度看比较容易,但是模型性能不一定有提升。...

August 24, 2024 · 431 words · Kurong

《计组KG》课题开发过程(一)

前言 开发该课题也有一个月了,整个过程并不是很顺利,很多细节部分如果没有得到及时梳理,对以后的研究和论文写作也有坏处。基于以上和其他原因,遂决定分阶段进行记录。 数据集 深度学习项目的良好开端就是有一个优良标注的数据集。而由于本课题起源于一个极小领域下,导致数据集必须完全自建。所有工作由我一人进行,工作量不可避免的大。所以必须尽可能的减少工作量,尽量实现在课题的中后期的所有标注工作都由程序自动化解决。 计组数据集的构建分为了以下几个过程: 计组数据集来源 数据预处理 数据集的预标注 基于词典的多次迭代标注 数据集格式的转换 接下来对每一个部分进行详述。 计组数据集来源 目前数据的来源如下: 计算机组成原理第6版 (白中英),pdf 转 txt 计算机组成原理第6版 (白中英) 课件,ppt 转 txt 数据预处理 以下是大概的预处理过程: 将所有的文本合并到一个文件,方便后续操作; 手工去掉一些与课题无关的文本和小部分错误内容; 去掉所有的空白字符(空格、换行符、制表符等); 去掉所有的特殊字符(数字、半角符号、特殊字符); 以中文句号进行分割,分别以整句、分词的形式输出到 json 文件中。 处理结果: 1 2 3 4 // 整句 { "0": "\u8ba1\u7b97\u673a\u7cfb\u7edf\u4e0d\u540c\u4e8e\u4e00\u822c\u7684\u7535\u5b50\u8bbe\u5907\uff0c\u5b83\u662f\u4e00\u4e2a\u7531\u786c\u4ef6\u3001\u8f6f\u4ef6\u7ec4\u6210\u7684\u590d\u6742\u7684\u81ea\u52a8\u5316\u8bbe\u5907" } 1 2 3 4 // 分词 { "0": ["\u8ba1", "\u7b97", "\u673a", "\u7cfb", "\u7edf", "\u4e0d", "\u540c", "\u4e8e", "\u4e00", "\u822c", "\u7684", "\u7535", "\u5b50", "\u8bbe", "\u5907", "\uff0c", "\u5b83", "\u662f", "\u4e00", "\u4e2a", "\u7531", "\u786c", "\u4ef6", "\u3001", "\u8f6f", "\u4ef6", "\u7ec4", "\u6210", "\u7684", "\u590d", "\u6742", "\u7684", "\u81ea", "\u52a8", "\u5316", "\u8bbe", "\u5907"] } 数据集的预标注 以上所有数据处理完后,共得到5632条文本。如果要自己一条条的标注,真就是整一个月啥也别干,所以还是要用比较省力的方式进行标注。我选择用一个在中文语料集上训练过的预训练模型进行第一轮标注,也就是预标注。...

August 1, 2024 · 283 words · Kurong

neo4j常用命令

neo4j启动与访问 启动neo4j 1 2 docker start test_neo4j docker exec -it test_neo4j /bin/bash 访问browser 1 http://localhost:7474/browser/ 访问database 1 2 3 neo4j://localhost:7687 auth: neo4j pw: 5225400599 CQL语法 create 创建节点 1 2 3 4 5 6 7 8 CREATE ( <node-name>:<label-name> { <Property1-name>:<Property1-Value> ........ <Propertyn-name>:<Propertyn-Value> } ) match 查询节点或属性 1 2 3 4 5 6 7 8 9 10 11 12 13 14 # 查询Dept下的内容 MATCH (dept:Dept) return dept # 查询Employee标签下 id=123,name="Lokesh"的节点 MATCH (p:Employee {id:123,name:"Lokesh"}) RETURN p ## 查询Employee标签下name="Lokesh"的节点,使用(where命令) MATCH (p:Employee) WHERE p....

June 26, 2024 · 572 words · Kurong

SQL

第一章:数据库基础 数据库:保存有组织的数据的容器(通常是一个文件或一组文件) 数据库软件(DBMS):MySql,Oracle,MongoDB之类。人们通常用数据库来代替数据库软件的名称 表(table):某种特定类型数据的结构化清单 模式:关于数据库和表的布局及特性的信息 列(column):表中的一个字段(该列由字段来唯一标识),所有表都是由一个或多个列组成的,每一列都有自己的数据类型 行(row):表中的一条数据是由行来存储的 主键(primary key):唯一标识表中每行的这个列就是主键,应该总是定义主键 关于主键: 任意两行都不具有相同的主键值 每个行都必须有一个主键值 SQL:结构化查询语言 第二章:MySQL简介 略 第三章:使用MySQL 登录数据库 默认主机名:localhost 默认端口:3306 1 2 mysql -u root -p # 然后输入密码 选择数据库 1 USE database_name; 了解数据库和表 查看所有数据库 1 SHOW DATABASES; 查看一个数据库中的所有表 1 SHOW TABLES; 查看一个表的所有字段 1 SHOW COLUMNS FROM table; 还有一种快捷写法 1 DESCRIBE table; 在返回的列表中可以看到一些建表信息,如字段名,数据类型,键类型,是否为NULL,默认值,其他类型。 其他的show语句 1 2 3 4 5 SHOW GRANTS; # 显示授予用户的安全权限 SHOW ERRORS; # 显示服务器错误 SHOW WARNINGS; # 显示服务器警告信息 SHOW STATUS; # 显示服务器的状态信息 HELP SHOW; # 显示mysql允许的show语句 有一个书写规则:select这样的关键词要大写,表名、列名、数据库名要小写...

June 26, 2024 · 2531 words · Kurong

正则表达式

第二章. 匹配单个字符 ’ . ’ 用来匹配任意单一字符, 元字符的一种 ’ \ ‘为转义字符, 属于元字符的一种, 元字符: 有特殊含义的字符 正则表达式被称为模式(pattern) 第三章. 匹配一组字符 ’ [ ] ‘为元字符, 表示一个字符集合, 必须匹配其中的一个或多个字符, 也可以全部匹配. ’ [ ] ‘可以用来匹配大小写, 如[Aa].*就匹配任意以A或a或Aa开头的字符串. 还有几种常见的用法, 如[a-z] [A-Z] [0-9], 这几种很常用, 还有一个用法[A-Za-z0-9] 这个字符集可以匹配以上三种用法的合集 ’ - ‘表示连字符, 是一种较为特殊的元字符, 只有在’ [ ] ’ 里才是元字符, 在其他地方就是一个普通的字符’ - ‘, 也因此在这种情况下它不需要转义 ’ ^ ’ 表示排除, 也是元字符. 在上面的几种用法中, 在集合的最前面加上’ ^ ‘, 就表示匹配除了该集合以外的字符, 而且需要注意的是, ’ ^ ‘的作用域是整个字符集合, 而不是紧跟在其身后的单个字符什么的 第四章. 使用元字符 如果要匹配元字符本身, 可以用 ’ \ . 如: 匹配[ ], 用\ [ \ ]...

June 26, 2024 · 376 words · Kurong

记一次博客搭建

My Blog Construction 前言 这是第二次用 Hugo 搭建静态博客了,之前的那个博客不论是主题、工作流、文件结构都很不合理,用起来效率低下。遂决定在研一之前重新搞一次。 完整过程 hugo 安装 在官方的 Releases · gohugoio/hugo (github.com) 下载对应版本即可,然后设置环境变量。 环境配置 网站选用 Github Pages 搭建,因为静态网站可以满足我的所有写作需求。域名就是仓库名705248010.github.io. 将仓库拉取到本地后就要开始配置了。先用 1 hugo new site <your site name> 生成一个对应文件夹,然后我将选择的主题 adityatelange/hugo-PaperMod: A fast, clean, responsive Hugo theme. (github.com) 放到 themes 文件夹内。 可以通过 1 hugo server 查看效果。 写Markdown 根据官方文档配置完成后就可以本地写文章了,用以下命令生成 1 hugo new --kind post ./xxxx/xxxx.md KaTex PaperMod 本身没有对数学公式的支持,但在 Hugo 官网能找到相关文档:Mathematics in Markdown | Hugo (gohugo.io) 。 这里选用 KaTex ,不用Mathjax的原因在于其对于内联公式符号 $ 的不支持。...

June 24, 2024 · 97 words · Kurong