<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>BruceDing’s blog</title><link>https://bruceding.me/</link><description>Recent content on BruceDing’s blog</description><generator>Hugo -- 0.128.0</generator><language>en-us</language><lastBuildDate>Sun, 15 Mar 2026 07:08:00 +0800</lastBuildDate><atom:link href="https://bruceding.me/index.xml" rel="self" type="application/rss+xml"/><item><title>OpenClaw 阿里云ECS部署指南</title><link>https://bruceding.me/posts/openclaw-aliyun-ecs-deploy/</link><pubDate>Sun, 15 Mar 2026 07:08:00 +0800</pubDate><guid>https://bruceding.me/posts/openclaw-aliyun-ecs-deploy/</guid><description>什么是 OpenClaw？ OpenClaw（原名Clawdbot、Moltbot）是一款开源的本地优先AI代理与自动化平台。它不仅能像聊天机器人一样对话，更能通过自然语言调用浏览器、文件系统、邮件等工具，完成整理文档、处理邮件、安排日程等实际任务，像一个&amp;quot;能替你干活的AI数字员工&amp;quot;。
OpenClaw的核心特点包括：
多渠道集成：支持企业微信、QQ、钉钉、飞书四大国内主流IM 持久记忆：拥有长期记忆能力，可以记住用户偏好和历史交互 主动执行：能够主动执行任务，如发送邮件、管理文件等 开源可定制：完全开源，可以根据业务需求进行深度定制 ECS 部署 OpenClaw 本文介绍如何在阿里云的ECS上部署原生的OpenClaw。使用的是Alibaba Cloud系统。如果直接用OpenClaw提供的shell进行部署的话，可能会因为不识别这个系统而失败。
首先需要安装npm包。
yum install npm 后面部署比较简单，直接使用OpenClaw的方案即可。
curl -fsSL https://openclaw.ai/install.sh | bash 成功后可以直接使用 openclaw -h 命令查看相关的功能。首先进行初始化配置的话，使用 openclaw onboard。这里可以比较简单，只需要配置模型就行，其余的过程可以直接跳过。模型配置好后，服务能启动之后，那么直接可以通过对话的方式，让openclaw自己配置了。
配置好了之后，通过 openclaw dashboard 来查看如何登录Web UI。我们相当于在本地去访问云上的ECS的openclaw，需要在本地终端打开 ssh -N -L 18789:127.0.0.1:18789 root@ecs公网IP。openclaw的默认端口是18789，这个时候需要在阿里云的安全组上开启这个端口，当然，为了安全，可以限制本机出口的IP。
如果全部配置好后，本地浏览器上输入 127.0.0.1:18789 就可以访问了。
与钉钉集成 如果成功登录了openclaw的Web UI，后续的配置，可以交由openclaw自动完成了。
openclaw的一个核心特性就是多渠道的集成，本文介绍如何与钉钉集成。
集成方式也比较简单，根据这里直接让openclaw配置就好了。openclaw本身能通过web_fetch获取网页的内容，跟着openclaw的提示一步步做就好了。
如果成功了，就可以通过钉钉的机器人或者应用使用openclaw了。但是一开始只支持文本的发送，当发送图片时，openclaw无法很好地识别。
解决方案是通过百炼的视觉模型来解决。
首先让OpenClaw自动配置下imageModel，使用的模型是 qwen-image-2.0-pro。 然后自建了skill，当遇到图片时，可以发送到这个模型进行内容的识别。skill名称是 image-vision，描述是 使用阿里云 qwen-image-2.0-pro 视觉模型识别图片内容。当用户发送图片或需要识别图片时自动触发，支持 PNG/JPG/GIF/WebP 格式，可识别文字、物体、场景、布局等。 这样钉钉上发送图片就可以识别了。
使用skill和记忆 openclaw强大的一个特性就是支持skill。当遇到一个通用的问题，或者可重复解决的问题时，可以直接使用skill-creator来创建自定义的skill。
openclaw提供的web_search是使用Brave API，但是开通Brave服务需要银行卡等信息，这个并不友好。我们可以使用tavily开通使用来替换Brave。
那么可以安装skill来使用tavily，比如framix-team-openclaw-tavily-tavily-search或者openclaw-tavily-search。配置好了之后，如果openclaw还是用Brave搜索的话，可以明确告诉openclaw让它记住。
参考资料 OpenClaw官网：https://openclaw.ai/ 阿里云百炼：https://www.aliyun.com/benefit/scene/codingplan 阿里云OpenClaw部署专题：https://www.aliyun.com/activity/ecs/clawdbot</description></item><item><title>Linux IO Stack 分析及调优实践</title><link>https://bruceding.me/posts/linux-io-stack-analysis-and-tuning/</link><pubDate>Sun, 11 Jan 2026 18:27:08 +0800</pubDate><guid>https://bruceding.me/posts/linux-io-stack-analysis-and-tuning/</guid><description>本文首先介绍Linux IO Stack 的整体链路，然后结合相关工具，跟踪IO的性能，并且根据实践中的场景，介绍如何进行IO的调优。
Linux IO Stack 文件读写的方式 根据上图，我们总结下文件读写的几种方式。
使用标准库的文件读写函数 直接根据文件描述符进行读写。先说明写数据的情况。主要两种情形：
在不使用Fsync 的情况下，实际写入Page Cache 就成功返回。 后续会由内核的线程异步将数据刷入磁盘。在系统内存充足的情况下，写入速度很快。但是内存紧张的情况下，可能涉及脏页的写入&amp;amp;内存的页替换，会有延迟的情况。如果写完数据，立即进行读取的话，速度也会很快，读取也是优先从Page Cache 读取。
如果涉及到WAL日志文件，防止意外断电情况下的数据丢失，会使用Fsync进行写入。这时也会写入到Page Cache ，但是会阻塞，直到提交到块IO，并且完成实际磁盘设备的写入才返回。这里有个优化的点，可以使用DSync 的模式，这样只会写入数据，而不进行元数据的更新，从而减少IO的调用。
在数据读取时，会先从Page Cache 读取。如果Page Cache 中没有，才会从磁盘读取。读取的性能一般会很高，尤其是顺序读取的情况下。 系统会进行预读，从而提前加载数据到Page Cache。
直接IO (O_DIRECT) 在打开文件时，设置 O_DIRECT 标志位，即可开启直接IO。Direct IO 的写入，不会经过Page Cache， 直接进行块IO的提交。这里会减少系统内核函数的调用。简化IO链路。这里不会涉及预读，需要上层应用控制。比如数据库软件都会使用Direct IO 来进行文件读写。
O_DIRECT 的写入，需要内存地址的对齐，以及IO的大小也需要对齐。否则会报错。
这里也可以单独设置Fsync的标志，如果没有设置，提交到块IO就返回了。否则需要等待磁盘的写入完成才返回。
使用 mmap 系统调用 mmap 系统调用可将文件映射到进程的虚拟地址空间，允许通过内存指针直接读写文件内容。 当使用 MAP_SHARED 模式写入时，数据会修改内核的 Page Cache 中对应的页面，并将其标记为“脏页”。 随后，内核的回写线程（如 flusher）会在后台异步地将这些脏页写入磁盘。 若需确保数据已持久化，应用程序应显式调用 msync()。
使用mmap 实现了零拷贝的（Zero Copy）机制，避免了数据在用户空间和内核空间之间的复制。 在读取文件时，使用 mmap 的方式居多。 当读取文件时，在内存中不存在的情况下，触发缺页中断，加载之后再返回。当内存紧张时，内存页可能被换出。 在mmap打开的内存地址，可以进行madvise调用，灵活的调整访问方式。
上面介绍的几种读写方式都是同步IO的方式，如果使用异步AIO，可以参考io_uring的使用，这里有介绍,即使使用AIO,也同样复用上图的IO Stack。
Block Layer 不管是通过Page Cache， 还是Direct IO， 最终都是提交到块IO。当数据写入到Page Cache 后，达到一定的阈值(内核脏页占比，内核脏页字节数量，过期时间)等等，会有内核线程把数据提交到块IO。在现代Linux内核中，块IO是由MQ(多队列)实现的。</description></item><item><title>使用Wireshark分析gRPC</title><link>https://bruceding.me/posts/wireshark-analyzing-grpc/</link><pubDate>Sun, 21 Dec 2025 16:07:05 +0800</pubDate><guid>https://bruceding.me/posts/wireshark-analyzing-grpc/</guid><description>本文介绍如何使用Wireshark来分析gRPC协议的网络通信，当遇到性能问题或者网络问题时，通过分析gRPC的网络包可以提供一种定位问题的方式。
当我们抓取grpc的网络数据之后，使用wireshark 打开后，默认显示的是TCP 协议。我们知道grpc 是基于http2 协议的，需要使用http2进行分析。可以任选一个包，右击进行编码选择。
在我们场景中，3380 是grpc 的server 端口，这里也把 Current 选择为HTTP2 。
确定后，协议变了，同时显示的内容也回把grpc的协议信息也显示出来了。
当我们定位任一个HTTP2的包，右击选择 HTTP2 Stream， 就可以看到具体某个stream 的信息了。 由于grpc 使用了 http2协议，多个grpc 请求时共用一个tcp 连接的，但是不同的请求会使用不同的stream 进行区分。 不能像http 那样，在同一个tcp连接上，请求时顺序的，一个请求结束再去处理下一个请求。
打开具体某个stream后，可以在窗口下方找到如下的信息。把Time since request 应用为新列。
这个时间实际是用http2.time 字段来计算的。它的逻辑是： 当前这一帧的时间 减去 该 Stream ID 起始帧（即客户端发送的 HEADERS 请求帧）的时间。简单来说：
对于服务器返回的第一个响应包： http2.time = 服务器处理耗时 + 网络往返延迟。 对于服务器返回的最后一个响应包（Trailers）： http2.time = 该 RPC 请求从发起到彻底结束的总耗时。 通过窗口的 Edit Column， 是可以看到Time since request 定义为 http2.time 的。
通过http2.time就可以分析 server 返回的响应耗时。我们对 Time since request 进行排序。倒序后，找到相应最大的包，进行Flow http2 strem 进行分析。而且可以看到， server 的响应的耗时分布。</description></item><item><title>GO性能提升的相关改动</title><link>https://bruceding.me/posts/go-performance-enhancements/</link><pubDate>Sun, 02 Nov 2025 07:59:41 +0800</pubDate><guid>https://bruceding.me/posts/go-performance-enhancements/</guid><description>随着GO版本的迭代，GO语言的性能也在不断提升。下文梳理下GO种提升性能的几处改动。
math/rand/v2 Go 1.22 版本引入，提供了一个新的随机数生成器，性能更好。 代码测试如下：
package main import ( &amp;#34;math/rand&amp;#34; randV2 &amp;#34;math/rand/v2&amp;#34; &amp;#34;testing&amp;#34; ) func BenchmarkRand(b *testing.B) { b.StartTimer() for range b.N { rand.Intn(100) } } func BenchmarkRandV2(b *testing.B) { b.StartTimer() for range b.N { randV2.IntN(100) } } 测试结果
goos: darwin goarch: amd64 pkg: test cpu: Intel(R) Core(TM) i7-8850H CPU @ 2.60GHz BenchmarkRand-12 84788529 14.24 ns/op BenchmarkRandV2-12 154128462 7.790 ns/op math/rand/v2 不需要设置随机种子，使用加密安全的随机数生成器。
Go maps with Swiss Tables Go 1.24版本引入，使用 Swiss Table 的理念，全新重写了map。直接使用Go1.</description></item><item><title>io_uring 学习</title><link>https://bruceding.me/posts/io_uring-learn/</link><pubDate>Wed, 22 Oct 2025 21:34:32 +0800</pubDate><guid>https://bruceding.me/posts/io_uring-learn/</guid><description>io_uring 是 Linux 内核提供的异步 IO 接口，它可以用于替代传统的同步 IO 接口，如 read、write 等。io_uring 提供了一种基于事件循环的异步 IO 模型，能够在单个线程中处理多个 IO 操作，从而提高 IO 操作的并发性和效率。io_uring 从内核5.1版本开始引入，在这之前，AIO 提供了异步IO 的功能。但是存在诸多的限制，包括
只能作用于文件IO, 不能作用于网络IO. 在文件IO 中，只能使用O_DIRECT标记打开文件，无法使用buffer cache 。这样使用的范围比较少，只有在数据库领域有应用。 不能完全提供异步功能，如果元数据不可用时，也得同步等待 不能完全做到zero-copy 在某些设备场景中，无法做到真正异步，也得同步等待 io_uring 使用统一的接口解决异步IO问题，包括文件IO 和 网络IO。非阻塞的接口，减少了线程上下文的切换。并且可以使用系统的buffer cache 。 io_uring 主要的数据结构包括两个ring buffer 。 Submission Queue (SQ)：用于提交IO请求的环形缓冲区。应用程序将IO请求放入SQ中，内核从SQ中获取请求并处理。 IO 请求放入tail 中， 内核通过head 读取进行处理。 Completion Queue (CQ)：用于存储IO操作完成的结果。内核将IO操作的完成结果放入tail中，应用程序从head中获取结果并处理。 io_uring 提供一系列系统调用的接口，目前可以通过liburing 库来更方便的使用。
在这个库的examples 目录下自带了很多的使用例子。具体的例子也可以通过这里有更多的解释。
liburing-cp 这是一个文件IO的例子，使用liburing 实现的 cp 命令。从这里 看到最新的代码。
初始化 io_uring 实例， entries 是 SQ 的大小， 一般设置为 2 的幂次方。 通常情况下CQ 是SQ 大小的2倍。</description></item><item><title>阿里云云盘性能测试</title><link>https://bruceding.me/posts/%E9%98%BF%E9%87%8C%E4%BA%91%E4%BA%91%E7%9B%98%E6%80%A7%E8%83%BD%E6%B5%8B%E8%AF%95/</link><pubDate>Sat, 19 Jul 2025 15:38:42 +0800</pubDate><guid>https://bruceding.me/posts/%E9%98%BF%E9%87%8C%E4%BA%91%E4%BA%91%E7%9B%98%E6%80%A7%E8%83%BD%E6%B5%8B%E8%AF%95/</guid><description>应用服务对磁盘的使用基本模式是顺序写，随机读。通过具体的ECS来测试下云盘的性能。 测试ECS机型ecs.r7.4xlarge（16核128G） 分别测试三种磁盘性能PL1, PL2, PL3 大小都是一样，1300G。选择 1300G 主要是 PL3 有磁盘的最小限制。 挂载之后 PL1 挂载到 /root/data1 PL2 挂载到 /root/data2 PL3 挂载到 /root/data3 挂载之后重启实例进行测试。测试方法参考阿里云官方文档。
顺序写测试 PL1 顺序写性能 PL2 顺序写性能 PL3 顺序写性能 随机读测试 PL1 随机读性能 PL2 随机读性能 PL3 随机读性能 结论 PL2/PL3 比 PL1 有比较好的性能。因为1300G 已经超过了 PL1 本身的性能限制。PL2和PL3 是相差不大的。但是PL3 会支持更大的磁盘，如果磁盘容量更大的话，选择PL3。 在时延上，PL2 也比 PL1 有比较大的提升。</description></item><item><title>Badger Scan 性能探究</title><link>https://bruceding.me/posts/badger-scan-performance/</link><pubDate>Sat, 19 Apr 2025 23:51:59 +0800</pubDate><guid>https://bruceding.me/posts/badger-scan-performance/</guid><description>Badger 是go实现的高性能KV库，与RocksDB类似，也是使用LSM实现的。但不同的是，对于大value, 为了减少了LSM的读写放大的问题，把value记录到单独的value log 中。在LSM记录的value 只是相对应的value log 的offset , 也就是log file ID 和所在文件的offset。本文描述是基于 1.6.2 版本。
KKV是一种数据结构，尤其在推荐领域广泛用到。比如说用户浏览过的文章，视频或者商品列表。对于同一个用户来说，浏览过的物料实际是一个list 。可以简单表示如下：
user_id Item_id Timestamp User1 Item1 1745070155 User1 Item2 1745070155 User1 Item3 1745070155 那么在底层，如果使用badger进行存储的时候，会把（user_id, item_id） 当成 key 存储。那么如果针对某个 user_id ，我们需要进行前缀匹配操作，匹配到user_id开头的数据都获取到，进而获取到item_id 列表。badger 的scan操作，官方的例子如下：
db.View(func(txn *badger.Txn) error { it := txn.NewIterator(badger.DefaultIteratorOptions) defer it.Close() prefix := []byte(&amp;#34;1234&amp;#34;) for it.Seek(prefix); it.Valid(); it.Next() { item := it.Item() k := item.Key() err := item.Value(func(v []byte) error { fmt.Printf(&amp;#34;key=%s, value=%s\n&amp;#34;, k, v) return nil }) if err !</description></item><item><title>Mcp Go Client开发示例</title><link>https://bruceding.me/posts/mcp-go-client/</link><pubDate>Fri, 04 Apr 2025 22:16:16 +0800</pubDate><guid>https://bruceding.me/posts/mcp-go-client/</guid><description>在mcp协议解析文章中，我们已经了解了mcp协议的基本概念，以及如何使用mcp协议来开发一个mcp server。本文将介绍如何使用mcp协议来开发一个mcp client。 我们使用 mcp-go来开发，并且模型调用基于阿里云百炼平台。 源码可以参考这里。
整体的步骤可以描述如下：
初始化 mcp client, 由于使用 stdio transport 方式，通过 command 方式，启动一个mcp server。 mcp client 初始化之后，通过 list tools 接口获取所有的工具信息。 处理用户请求的信息，把 prompt 和 tools 信息传递给大模型。我们使用了qwen-plus 模型，本身支持 function call 功能，我们直接使用 tools 把工具信息传递给大模型。 大模型返回结果后，解析结果，判断是否调用了工具。如果没有使用工具，直接返回大模型的结果。如果使用了工具，大模型返回的 ToolCalls 信息包含了工具名称以及调用工具需要的参数信息。 根据ToolCalls 返回的信息，mcp client 通过 call tool 接口，调用工具。 拿到相应的工具返回的结果后，把用户的 prompt 和工具的结果传递给大模型。 拿到大模型的结果后，返回给用户。 首先在 pairec-mcp-demo 目录下进行编译，生成 pairec-mcp-demo 二进制文件, 然后 copy 到 client 目录下。
初始化mcp client 这里 serverPath 就是 pairec-mcp-demo 二进制文件的路径。 首先通过 Initialize 接口初始化mcp client。
func connectToServer(serverPath string) (*client.</description></item><item><title>使用Mcp Inspector调试MCP Server</title><link>https://bruceding.me/posts/mcp-inspector%E8%B0%83%E8%AF%95server/</link><pubDate>Sun, 30 Mar 2025 08:19:50 +0800</pubDate><guid>https://bruceding.me/posts/mcp-inspector%E8%B0%83%E8%AF%95server/</guid><description>工具的介绍参考这里，使用方式也比较简单。
使用类似的命令，启动服务。
npx @modelcontextprotocol/inspector &amp;lt;command&amp;gt; 我们使用前文提到的 pairec-mcp-demo 作为参考。
启动命令
npx -y @modelcontextprotocol/inspector &amp;#34;/Users/bruceding 1/Projects/go/src/go-echo-mcp/pairec-mcp-demo/pairec-mcp-demo&amp;#34; --log_dir ./log /Users/bruceding 1/Projects/go/src/go-echo-mcp/pairec-mcp-demo/pairec-mcp-demo 是sever 的二进制路径。
--log_dir ./log 中两个是命令行的启动参数。
启动成功后，会出现如下的提示，浏览器打开相应的地址。
在打开的页面中，选择 transport type ，确认 command 和 arguments 没有问题后，点击 connect ，会返回连接成功的信息。
连接成功后，如果测试 Tools 功能，选择 Tools tab, 调用 List Tools 方法，会列出 tools 工具，进行测试即可。</description></item><item><title>Mcp协议解析</title><link>https://bruceding.me/posts/mcp%E5%8D%8F%E8%AE%AE%E8%A7%A3%E6%9E%90/</link><pubDate>Thu, 27 Mar 2025 22:47:55 +0800</pubDate><guid>https://bruceding.me/posts/mcp%E5%8D%8F%E8%AE%AE%E8%A7%A3%E6%9E%90/</guid><description>从上文 中，介绍了MCP 协议的基本内容，如何使用以及如何基于 stdio transport 开发mcp server 。 本文主要介绍下mcp协议的实现细节。通过从0开始开发一个mcp server, 并且通过日志的方式记录mcp client 和 mcp server 之间的数据是如何交互的。
开发思路是通过 stdio 进行数据读取，然后写入到日志中。注册到 cline 中这样就能接受到 mcp client 的请求了。 通过mcp 协议规范，处理请求，然后返回respone 数据。 处理完一种请求后，不断的更新服务，不断的和mcp client 进行交互，进入下一轮请求，循环往复，直至一个完整的链路完成。
MCP 协议使用 JSON-RPC格式，具体的request，response schema 可以参考这里。
全部的源码参考这里。
还是使用 Tools 走通整个链路。 下图中可以看出
Server 启动时，client 就是发起初始化请求，Server 要把相应的能力高速 Client （Resource， Promot, Tool)。 假设如果只支持 Tool 能力，client 会发起 tools/list 请求，把所有的工具信息获取到。包括 名称、参数列表、描述等等。 如果用户发起了请求， Client 会把用户的提示 + tools 列表信息给到 LLM LLM 会选择格式的工具，并组装请求高速 Client Client 发起tools/call 调用， mcp server 执行相应的工具功能 返回请求后，client 把结果告诉LLM， LLM 进行总结后，输出到客户。 我们看下核心的代码功能。 实际就是监听标准的输入，解析出 method 方法，然后进行相应的处理。</description></item><item><title>MCP协议介绍</title><link>https://bruceding.me/posts/mcp%E4%BB%8B%E7%BB%8D/</link><pubDate>Thu, 20 Mar 2025 22:06:00 +0800</pubDate><guid>https://bruceding.me/posts/mcp%E4%BB%8B%E7%BB%8D/</guid><description>MCP(Model Context Protocol,模型上下文协议) 是一个开源的协议，提供了一种通用、标准的方式把LLM AI 大模型和不同数据源以及工具连接起来。类似USB HUB 方式。
与传统调用 API 的方式不同，每个工具都有不同的调用方式，不同的AI 应用或者 LLM 调用时，都需要集成一遍。MCP 的出现，本质上来说出现一个中间层，把工具的提供方和工具的调用方进行了解耦，这样AI应用可以灵活的使用现有已经实现的工具，而不需要单独的集成，提供了开发效率。
协议架构 MCP HOSTS 面向终端用户的AI 应用，类似 Claude Desktop, IDEs 等通过MCP 协议来访问数据
MCP Client 协议的客户端，可以和mcp server 1:1 进行连接。他们之间通过 mcp 协议交互。Client 通常被集成到 host 中。
MCP Server 实现了mcp 协议的服务提供方，它会把数据源以及工具的使用通过服务暴露出去。
目前包括两种 transport layer:
Stdio transport 通过标准的输入输出进行通讯(stdin, stdout) mcp server 运行在本机的进程中，比如 IDE 中的 AI 助手操作本地的终端，或者管理目录和文件等等 HTTP with SSE transport 使用 SSE 协议进行远程服务通讯 使用http post 请求方式 MCP 的协议是通过 JSON-RPC 2.0的方式来通讯的，具体协议可以参考这里。需要理解消息类型
request/response， 一个请求，正确的相应后返回 response , 具体response 业务数据封装在 result 字段中。 当请求出错的情况下，返回的错误信息封装到 error 字段中。 还有一种特殊的请求叫 Notifications ， 这个请求不会带RequestId, 也不需要返回响应数据。 在MCP server 的实现中，需要定义好需要提供哪几类数据到client 使用。</description></item><item><title>Wasm component model介绍</title><link>https://bruceding.me/posts/wasmcomponentmodel%E4%BB%8B%E7%BB%8D/</link><pubDate>Tue, 11 Mar 2025 22:27:01 +0800</pubDate><guid>https://bruceding.me/posts/wasmcomponentmodel%E4%BB%8B%E7%BB%8D/</guid><description>Desc Text.</description></item><item><title>Wasm导入函数示例</title><link>https://bruceding.me/posts/wasm%E5%AF%BC%E5%85%A5%E5%87%BD%E6%95%B0%E7%A4%BA%E4%BE%8B/</link><pubDate>Sat, 08 Mar 2025 11:42:13 +0800</pubDate><guid>https://bruceding.me/posts/wasm%E5%AF%BC%E5%85%A5%E5%87%BD%E6%95%B0%E7%A4%BA%E4%BE%8B/</guid><description>从wasm技术介绍 里可以看到如何导出函数，并且在宿主机里进行调用。本文介绍如何进行导入函数，从宿主机导入函数并在wasm函数里使用。 还是基于tinygo 来进行演示。
首先定义导入函数
//go:build tinygo package main import ( &amp;#34;fmt&amp;#34; &amp;#34;reflect&amp;#34; &amp;#34;unsafe&amp;#34; ) //export CallFromHost func CallFromHost() int32 //export Add func Add(a, b int32) int32 { return a + b } type User struct { Name string Age int } //export CallHost func CallHost() int32 { return CallFromHost() } //export AddUser func AddUser(nameData *byte, nameLen int32, age int32) bool { fmt.Println(nameLen, age) name := RawBytePtrToString(nameData, int(nameLen)) user := User{Name: name, Age: int(age)} fmt.</description></item><item><title>Badger的GC初步探究</title><link>https://bruceding.me/posts/badger%E7%9A%84gc%E5%88%9D%E6%AD%A5%E6%8E%A2%E7%A9%B6/</link><pubDate>Tue, 04 Mar 2025 00:26:07 +0800</pubDate><guid>https://bruceding.me/posts/badger%E7%9A%84gc%E5%88%9D%E6%AD%A5%E6%8E%A2%E7%A9%B6/</guid><description>类似 RocksDB， badger 是 Go 基于 LSM 实现的 KV 数据库。本文介绍基于 badger 的 1.62 版本。 与传统的LSM 不同，对于 value 数据，badger 会写入 value log，来减少写放大和读放大。但是也会造成问题，就是磁盘会占用过多。 value log 的 gc 实现的比较简单。
我们先看下 badger 写入链路。
数据会优先写入 value log , 这里实际当成了 WAL 使用 value pointer 会记录 写入 value log 文件的 fid ,offset, len 然后把数据写入 memtable ,如何memtable 数据写满的话，会变成不可更改的 memtable , 然后写入到 SST Tables 中。 那么何时刷新 memtable ， 目前有两种情况：
一个是 memtable 大小控制，超过 memtable 规定的大小，就会刷新。默认是 64M , 通过WithMaxTableSize 可以指定 在一个通过 LogRotatesToFlush 控制，这个参数是说 写入 value log 的文件轮转了多少次，默认是 2 次。 超过这个数值也会刷新。 当 badger 进行重启的时候，会对部分数据进行回放操作。从哪个点的数据进行回放，是通过 head pointer 实现的。 head pointer 记录了 fid 和 offset ， 数据比这个 head pointer 晚的话，都会进行回放。</description></item><item><title>Raft协议学习</title><link>https://bruceding.me/posts/raft%E5%8D%8F%E8%AE%AE%E5%AD%A6%E4%B9%A0/</link><pubDate>Sun, 02 Mar 2025 17:29:08 +0800</pubDate><guid>https://bruceding.me/posts/raft%E5%8D%8F%E8%AE%AE%E5%AD%A6%E4%B9%A0/</guid><description>论文地址：https://raft.github.io/raft.pdf
原理演示： https://raft.github.io/raftscope/index.html
背景 在分布式系统中，数据一致性是必须考虑的点，而且是优先考虑。在不同的场景中，数据一致性包括强一致性和弱一致性（最终一致性）。Paxos及其变种提供的共识算法扮演了重要的角色。但是非常难以理解和实现，Raft旨在简化算法逻辑，提供易于理解的共识算法。常见的 etcd 就是使用的 Raft 协议。
论文中 Raft 协议主要包括以下部分：
leader 选举 日志复制 安全性（实际上是各种边界条件的考虑，额外增加了规则） 集群成员的变化 复制状态机 在集群中，机器都会处在相同的状态，及时部分机器失败的情况下。为了达到这个目的，一般使用日志副本的方式。保证副本日志的一致性，就是共识算法的作用。通过共识模块，不同的机器会产生相同的日志副本，副本里的命令是相同的，顺序是相同的。那么状态执行这些log，就会产生相同的状态。
Raft 协议 通常，一个Raft集群包含5个节点，这样可以容忍2个节点的失败。
Raft使用 term （任期）的概念来代替时间的观念。term 使用正整数表示，每次leader选举，都会增加term值。
集群中三个角色：
Follower : 是个被动角色，只会接受请求进行回应。 Candidate: 候选人，只有 Candidate 才能发起选举投票。选举成功，变为 Leader。 Leader: 集群中只有一个节点是这个角色。 当系统启动，或者没有收到 leader 的心跳请求， follower 会有一个随机时间，超时后term+1,并且会变成 Candidate 会发起投票。投票成功，会变成 leader , 会立即发送心跳请求。其余失败的 Candidate 会变成 follower 。
集群中主要有两种RPC, RequestVote RPC 和 AppendEntries RPC。前者用于投票，是 Candidate 发起的。后者用于同步log数据，或者心跳请求。
一个节点收到的 term 的比自己的大，会更新当前节点的 term 。
leader 选举 当选举开始时，follower 增加当前的任期term, 然后变成 Candidate。它给自己投票，然后并发的把RequestVote 请求发送给其它节点。那么后面会出现三种情况：</description></item><item><title>ollama 使用</title><link>https://bruceding.me/posts/ollama-%E4%BD%BF%E7%94%A8/</link><pubDate>Sat, 01 Mar 2025 17:29:08 +0800</pubDate><guid>https://bruceding.me/posts/ollama-%E4%BD%BF%E7%94%A8/</guid><description>安装 curl -fsSL https://ollama.com/install.sh | sh 运行模型示例
ollama run llama3.2 类似docker 命令，可以使用 ollama list 和 ollama ps 命令查看已安装的模型和正在运行的模型。
自定义模型 类似 dockerfile ， 可以创建Modelfile 来自定义模型的行为及输出。
比如定义一个翻译助手，从英文翻译到中文。 Modelfile 可以创建如下：
FROM llama3.2 # set the system message SYSTEM &amp;#34;&amp;#34;&amp;#34; 你的任务是把用户提供的{{.Prompt}} 直接翻译成中文。 &amp;#34;&amp;#34;&amp;#34; 然后可以创建模型
ollama create fanyi-llama3.2 -f Modelfile 之后，可以运行
ollama run fanyi-llama3.2 Modelfile 的使用说明参考 https://github.com/ollama/ollama/blob/main/docs/modelfile.md。
使用api 接口服务 启动 server 服务
ollama serve 在本地起一个 11434 端口。
然后另一个终端，运行模型
ollama run fanyi-llama3.2 接口请求
curl http://localhost:11434/api/generate -d &amp;#39;{ &amp;#34;model&amp;#34;: &amp;#34;fanyi-llama3.</description></item><item><title>wasm技术介绍</title><link>https://bruceding.me/posts/wasm%E6%8A%80%E6%9C%AF%E4%BB%8B%E7%BB%8D/</link><pubDate>Fri, 28 Feb 2025 17:29:08 +0800</pubDate><guid>https://bruceding.me/posts/wasm%E6%8A%80%E6%9C%AF%E4%BB%8B%E7%BB%8D/</guid><description>什么是 WebAssembly WebAssembly（缩写为 Wasm）是一种基于堆栈的虚拟机的二进制指令格式。 Wasm 被设计为编程语言的可移植编译目标，支持在网络上部署客户端和服务器应用程序。
背景 目标很宏大， compile once , run anywhere。
特性 性能： 接近本机执行性能，编译成 low level 的二进制格式
安全： 沙箱的执行环境
多语言支持
可移植性： 跨平台,跨体系结构
有多种运行时
wasmtime
wasmer
wasmEdge
wasmZero
两种编译技术
AOT
JIT
wasm 文本格式 参考：https://developer.mozilla.org/en-US/docs/WebAssembly/Guides/Understanding_the_text_format
定义个function并且导出 (module (func $add (param $lhs i32) (param $rhs i32) (result i32) local.get $lhs local.get $rhs i32.add) (export &amp;#34;add&amp;#34; (func $add)) ) 调用其它function (module (func $getAnswer (result i32) i32.const 42) (func (export &amp;#34;getAnswerPlus1&amp;#34;) (result i32) call $getAnswer i32.</description></item><item><title>使用hugo搭建blog</title><link>https://bruceding.me/posts/%E4%BD%BF%E7%94%A8hugo%E6%90%AD%E5%BB%BAblog/</link><pubDate>Wed, 12 Feb 2025 20:38:52 +0800</pubDate><guid>https://bruceding.me/posts/%E4%BD%BF%E7%94%A8hugo%E6%90%AD%E5%BB%BAblog/</guid><description>本文介绍如何使用 hugo 搭建静态网站，网站内容push 到 github 上，通过 github action 编译然后传到 linode 机器上，通过nginx 服务访问网站内容。
hugo 的使用参考这里.
首先进行 hugo 安装， 本地电脑是mac ，通过 brew 安装
brew install hugo 然后创建一个网站, 会新建这个目录。
hugo new site bruceding-blog-site 进入 bruceding-blog-site 目录中 ，进行 git 初始化。
cd bruceding-blog-site git init 添加主题
git submodule add https://github.com/nanxiaobei/hugo-paper themes/paper echo &amp;#34;theme = &amp;#39;paper&amp;#39;&amp;#34; &amp;gt;&amp;gt; hugo.toml 在github 上创建一个仓库，名字为 bruceding-blog-site。 然后把本地的仓库和github 上的仓库关联起来。
git remote add origin git@github.com:bruceding/bruceding-blog-site.git 新建一个blog 文章
hugo new content content/posts/使用hugo搭建blog.md 会生成类似的文件头, 把 draft 改成 false 。否则不会生成html 文件。</description></item></channel></rss>