<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="zh-CN">
    <title>since2077 - 博客</title>
    <subtitle>系统编程 · 技术心得 · 生活感悟</subtitle>
    <link rel="self" type="application/atom+xml" href="https://since2077.top/blog/atom.xml"/>
    <link rel="alternate" type="text/html" href="https://since2077.top/blog/"/>
    <generator uri="https://www.getzola.org/">Zola</generator>
    <updated>2026-09-20T00:00:00+00:00</updated>
    <id>https://since2077.top/blog/atom.xml</id>
    <entry xml:lang="zh-CN">
        <title>go-pubsub：一个为实时流场景设计的进程内 Pub/Sub</title>
        <published>2026-09-20T00:00:00+00:00</published>
        <updated>2026-09-20T00:00:00+00:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://since2077.top/blog/go-pubsub-intro/"/>
        <id>https://since2077.top/blog/go-pubsub-intro/</id>
        
        <content type="html" xml:base="https://since2077.top/blog/go-pubsub-intro/">&lt;p&gt;这篇文章介绍我写的第一个开源库 &lt;a rel=&quot;noopener nofollow noreferrer external&quot; target=&quot;_blank&quot; href=&quot;https://github.com/F2077/go-pubsub&quot;&gt;go-pubsub&lt;/a&gt;：一个轻量的进程内 Pub/Sub，专为实时流媒体数据包这类&lt;strong&gt;瞬态数据流&lt;/strong&gt;设计。它的哲学是纯粹的 fire-and-forget——零持久化、不保证送达，只追求极致的单向消息传递速度。&lt;/p&gt;
&lt;h2 id=&quot;wei-shen-me-xie-ta&quot;&gt;为什么写它&lt;a class=&quot;zola-anchor&quot; href=&quot;#wei-shen-me-xie-ta&quot; aria-label=&quot;Anchor link for: wei-shen-me-xie-ta&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;做实时流媒体相关开发时，经常需要在进程内分发数据包：一个采集协程产出帧，N 个消费协程各自订阅。这类场景的共同点是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;数据&lt;strong&gt;转瞬即逝&lt;/strong&gt;：晚到一帧不如直接丢掉，等下一帧&lt;/li&gt;
&lt;li&gt;消费者&lt;strong&gt;可能随时消失&lt;/strong&gt;：订阅方断连后不应该拖累生产者&lt;/li&gt;
&lt;li&gt;延迟比可靠性重要得多&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;标准库里 &lt;code&gt;map[string]chan T&lt;/code&gt; 手搓一个并不难，但每次都要重新处理并发安全、订阅清理、背压策略这些边角问题。用 Kafka / NATS 又太重——数据根本不需要出进程。于是我把这些需求固化成了一个库。&lt;/p&gt;
&lt;h2 id=&quot;kuai-su-shang-shou&quot;&gt;快速上手&lt;a class=&quot;zola-anchor&quot; href=&quot;#kuai-su-shang-shou&quot; aria-label=&quot;Anchor link for: kuai-su-shang-shou&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;go&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; get&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; github.com/F2077/go-pubsub&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;基于泛型，消息类型在编译期确定：&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;go&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;broker&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; _&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; :=&lt;/span&gt;&lt;span&gt; pubsub&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;NewBroker&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;string&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;publisher&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; :=&lt;/span&gt;&lt;span&gt; pubsub&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;NewPublisher&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;string&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;broker&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;subscriber&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; :=&lt;/span&gt;&lt;span&gt; pubsub&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;NewSubscriber&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;string&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;broker&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;//&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; Subscribe with a buffered channel and a sliding 200ms timeout:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;//&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; if no publish lands within the window, ErrSubscriptionTimeout&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;//&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; is fired to ErrCh.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;sub&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; _&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; :=&lt;/span&gt;&lt;span&gt; subscriber&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;Subscribe&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;alerts&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    pubsub&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;WithChannelSize&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;string&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;pubsub&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Medium&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    pubsub&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;WithTimeout&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;string&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;200&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span&gt;time&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Millisecond&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;defer&lt;/span&gt;&lt;span&gt; sub&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;Close&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;publisher&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;Publish&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;alerts&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;CPU over 90%!&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;select&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;case&lt;/span&gt;&lt;span&gt; msg&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; :=&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; &amp;lt;-&lt;/span&gt;&lt;span&gt;sub&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Ch&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    fmt&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;Println&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;Received:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; msg&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;case&lt;/span&gt;&lt;span&gt; err&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; :=&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; &amp;lt;-&lt;/span&gt;&lt;span&gt;sub&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;ErrCh&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    log&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;Println&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;Timeout:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; err&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&quot;ji-ge-guan-jian-de-she-ji-qu-she&quot;&gt;几个关键的设计取舍&lt;a class=&quot;zola-anchor&quot; href=&quot;#ji-ge-guan-jian-de-she-ji-qu-she&quot; aria-label=&quot;Anchor link for: ji-ge-guan-jian-de-she-ji-qu-she&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;满则丢弃，而不是阻塞。&lt;/strong&gt; channel 满了消息就静默丢弃，生产者永远不被慢消费者拖住。这正是实时流想要的行为，但也意味着它&lt;strong&gt;绝对不能&lt;/strong&gt;用于需要可靠投递的场景。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;滑动超时自动回收订阅。&lt;/strong&gt; &lt;code&gt;WithTimeout&lt;/code&gt; 启动一个滑动计时器，每次成功投递都会重置；窗口内没有消息就往 &lt;code&gt;ErrCh&lt;/code&gt; 发一次 &lt;code&gt;ErrSubscriptionTimeout&lt;/code&gt;。订阅方异常退出但没 Close 时，资源也能自动释放。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;预置的 buffer 档位。&lt;/strong&gt; &lt;code&gt;Block / Single / Small / Medium / Large / Huge&lt;/code&gt; 六档（Medium = 100 是默认值），也接受任意 &lt;code&gt;uint16&lt;/code&gt;。基准测试显示 buffer 档位对发布性能几乎无影响（104~107 ns/op），按消费者的消费能力选就行。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;热路径零分配。&lt;/strong&gt; fan-out 时订阅者快照切片用 &lt;code&gt;sync.Pool&lt;/code&gt; 复用，单订阅者发布是 &lt;strong&gt;107.7 ns/op，0 allocs/op&lt;/strong&gt;。&lt;code&gt;allocs/op&lt;/code&gt; 是确定可复现的指标，&lt;code&gt;ns/op&lt;/code&gt; 会随机器负载波动，所以仓库里把基准测试方法和注意事项都写明了。&lt;/p&gt;
&lt;h2 id=&quot;shen-me-shi-hou-yong-shen-me-shi-hou-bie-yong&quot;&gt;什么时候用，什么时候别用&lt;a class=&quot;zola-anchor&quot; href=&quot;#shen-me-shi-hou-yong-shen-me-shi-hou-bie-yong&quot; aria-label=&quot;Anchor link for: shen-me-shi-hou-yong-shen-me-shi-hou-bie-yong&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;✅ 实时流数据分发、游戏/直播事件、进程内低延迟消息&lt;/li&gt;
&lt;li&gt;❌ 持久化队列、需要送达保证的场景——这类需求请用专业的消息队列&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;项目地址：&lt;a rel=&quot;noopener nofollow noreferrer external&quot; target=&quot;_blank&quot; href=&quot;https://github.com/F2077/go-pubsub&quot;&gt;https://github.com/F2077/go-pubsub&lt;/a&gt;，MIT 协议，欢迎 issue 和 PR。&lt;/p&gt;
</content>
        
    </entry>
    <entry xml:lang="zh-CN">
        <title>你好，世界</title>
        <published>2026-09-19T00:00:00+00:00</published>
        <updated>2026-09-19T00:00:00+00:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://since2077.top/blog/hello-world/"/>
        <id>https://since2077.top/blog/hello-world/</id>
        
        <content type="html" xml:base="https://since2077.top/blog/hello-world/">&lt;p&gt;这是博客的第一篇文章。&lt;/p&gt;
&lt;p&gt;这个站点由 Zola 生成，使用 serene 主题，由 Caddy 提供 HTTPS 服务。&lt;/p&gt;
</content>
        
    </entry>
</feed>
