Ubuntu 22.04 安装 AnyCable-Go 1.5 指南

在 Ubuntu 22.04 裸机从零安装 AnyCable-Go 1.5.6:预编译二进制与 Go 源码编译双方案,配齐 Redis 6 与 Ruby RPC 后端,systemd 托管,完成 gRPC 订阅、HTTP 广播、Redis 广播三链路端到端实测。

AnyCableAnyCableWebSocketGoRedis2026/10/25

Ubuntu 22.04 安装 AnyCable-Go 1.5 指南

目标:在 Ubuntu 22.04 服务器上从零安装 AnyCable-Go 1.5.6(WebSocket 实时推送服务器),配齐 Redis 6 广播通道与 Ruby RPC 后端应用,用 systemd 托管开机自启,并完成 gRPC 鉴权订阅、HTTP 广播、Redis 广播三条链路的端到端联通实测。本文所有命令与回显均已在 Ubuntu 22.04.5 LTS(x86_64)实测通过。

一、环境信息

  • 操作系统:Ubuntu 22.04.5 LTS(x86_64),内核 5.x,systemd 249
  • AnyCable-Go:v1.5.6(构建号 1.5.6-e0dd6fa,2024-10-21 发布,为独立仓 anycable/anycable-go 归档前最后一个稳定版;开发已并入单仓 anycable/anycable,2026-10-25 实查最新稳定版为 1.6.20,升级路径见 A1 篇)
  • 广播中间件:Redis 6.0.16(Ubuntu 22.04 官方仓库)
  • RPC 后端:Ruby 3.0.2 + anycable gem 1.5.2 + litecable 0.8.2(模拟业务应用)
  • 编译环境(仅方案 B):Go 1.27.3 linux/amd64
  • 安装路径与端口约定:
项目路径 / 端口
anycable-go 二进制/usr/local/bin/anycable-go
环境配置文件/etc/anycable/anycable-go.env
RPC 应用目录/opt/anycable-app/chat_app.rb
WebSocket / 健康检查8080(/cable、/health)
HTTP 广播端点8090(/_broadcast)
Prometheus 指标9091(/metrics)
RPC(gRPC)50051
Redis6379

💡 技术拓展:实时推送服务器的「广播通道」有多种实现可选——

通道特点适用场景
Redis Pub/Sub依赖最常见的 Redis,即推即弃绝大多数多节点部署
Redis Streams(redisx)带持久化与消费组,可补发需要消息回溯/可靠性
NATS独立消息系统,可内嵌已有 NATS 基础设施
HTTP应用直接 POST 到 anycable-go单节点、无中间件

本文主线采用「Redis Pub/Sub + HTTP 广播端点」组合(anycable-go 默认同时启用 http 与 redis 两个广播适配器),兼顾多节点扩展与单节点直推。

二、前置条件

AnyCable-Go 本身是一个无依赖的 Go 单二进制,但要构成完整可用的实时推送链路,还需要:Redis(节点间广播通道)、Ruby 运行环境(RPC 后端应用,负责连接鉴权与频道逻辑)、Python3(本文用于编写 WebSocket 验证客户端)。

2.1 安装 Redis 6

apt-get update -qq
apt-get install -y redis-server

安装完成后确认包版本:

dpkg -l redis-server | tail -1

实测回显:

ii  redis-server   5:6.0.16-1ubuntu1.2   amd64   Persistent key-value database with network interface

启动并设为开机自启(逐行一个单元):

systemctl enable redis-server
systemctl start redis-server
systemctl is-active redis-server

实测回显:

Synchronizing state of redis-server.service with SysV service script with /lib/systemd/systemd-sysv-install.
Executing: /lib/systemd/systemd-sysv-install enable redis-server
active

验证 Redis 可用并确认版本:

redis-cli ping
redis-server --version

实测回显:

PONG
Redis server v=6.0.16 sha=00000000:0 malloc=jemalloc-5.2.1 bits=64 build=d6ed09173b889769

2.2 安装 Ruby 与验证用 Python 客户端

RPC 后端应用用 Ruby 编写(anycable gem 是官方 RPC 实现);Python 的 websockets 库用于在后文模拟浏览器客户端验证推送链路。

apt-get install -y ruby-full python3 python3-pip curl ca-certificates
pip3 install websockets
ruby --version
python3 -c "import websockets; print(websockets.__version__)"

实测回显:

ruby 3.0.2p107 (2021-07-07 revision 0db68f0233) [x86_64-linux-gnu]
16.1.1

三、版本选型说明

AnyCable 由两部分组成:anycable-go(Go 编写的 WebSocket 服务器进程,本文主角)与 anycable / anycable-rails(Ruby 侧 RPC 与广播适配)。选型前先实查官方发布情况(查询日期 2026-10-25,来源:GitHub Releases 页与 releases.atom):

版本性质发布日期结论
v1.5.6独立仓 anycable/anycable-go 归档前最后稳定版2024-10-21✅ 本文采用(实测基线)
v1.6.20单仓 anycable/anycable 最新稳定版2026-10-21⏳ 新版本,选型对比见 A1 篇
v1.6.0-rc.1预发布(Release Candidate)2024-12-26❌ 生产不选 RC
v1.5.5已被官方标记 [DEPRECATED]—❌ 弃用

官方为 v1.5.6 提供的预编译资产(实测逐个探测 HTTP 状态):

资产架构可用性
anycable-go-linux-amd64Linux x86_64✅ 302 → 可下载(约 31.8 MB)
anycable-go-linux-arm64Linux ARM64✅ 可下载
anycable-go-darwin-amd64 / -darwin-arm64macOS✅ 可下载
anycable-go-windows-amd64.exeWindows❌ 404(官方不提供 Windows 二进制)

⚠️ 网络说明:本文实验服务器直连 GitHub Releases 下载成功(HTTP 200,31.8 MB)。若你的服务器访问 GitHub 缓慢或超时,可降级走代理下载,例如 curl -x http://<代理地址>:7897 -LO <URL>;哪个渠道实测可用就用哪个。

四、方案 A:官方预编译二进制安装(推荐主线)

适合绝大多数生产与测试环境:无需编译工具链,单二进制落地即可运行。

4.1 下载与安装

curl -L -o /tmp/anycable-go https://github.com/anycable/anycable-go/releases/download/v1.5.6/anycable-go-linux-amd64
chmod +x /tmp/anycable-go
mv /tmp/anycable-go /usr/local/bin/anycable-go
anycable-go --version

实测回显:

1.5.6-e0dd6fa

4.2 准备 RPC 后端应用(Ruby)

AnyCable 的经典架构是「anycable-go 扛 WebSocket 连接 + 业务应用通过 gRPC 做鉴权/频道逻辑」。这里用一个最小 Ruby 应用模拟业务后端(生产上对应你的 Rails/Ruby 应用)。

安装 anycable(与 anycable-go 1.5.6 同代的 1.5.2 版本)及配套 gem:

gem install anycable -v 1.5.2 --no-document
gem install litecable redis rack --no-document
anycable --version

实测回显:

Successfully installed anycable-core-1.5.2
Successfully installed anycable-1.5.2
2 gems installed
AnyCable v1.5.2

⚠️ 注意:Ruby 侧 anycable gem 必须显式 -v 1.5.2。直接 gem install anycable 会装到 1.6.x,与 anycable-go 1.5.6 不是同代协议版本,本文按同代配套原则固定 1.5.2。

创建应用目录与最小聊天频道应用:

mkdir -p /opt/anycable-app

写入 /opt/anycable-app/chat_app.rb:

require "anycable"
require "rack"
require "lite_cable"

AnyCable.connection_factory = LiteCable::Connection::Base

class ChatChannel < LiteCable::Channel::Base
  def subscribed
    stream_from "chat"
    transmit({ type: "notice", text: "joined chat" })
  end
end

LiteCable.channel_registry.add("ChatChannel", ChatChannel)

逐项说明:

  • require "rack":litecable 的 AnyCable 适配层内部引用 Rack::Request,Ruby 3.0 环境不显式加载会报 uninitialized constant Rack(本文排障实录,见八、常见问题 Q2)。
  • AnyCable.connection_factory:告诉 AnyCable RPC 服务器用哪个类处理连接生命周期。
  • ChatChannel#subscribed:订阅成功后把连接挂到 chat 流,并立即向客户端回推一条 joined chat 通知。
  • LiteCable.channel_registry.add:litecable 的频道靠注册表按名字解析,不注册会报 Unknown channel: ChatChannel。

4.3 编写环境配置

anycable-go 支持「命令行 flag / 环境变量 / TOML 配置文件」三种配置方式,优先级为 flag > 环境变量 > TOML。本方案用环境变量文件,便于 systemd 托管。

mkdir -p /etc/anycable

写入 /etc/anycable/anycable-go.env:

ANYCABLE_HOST=0.0.0.0
ANYCABLE_PORT=8080
ANYCABLE_RPC_HOST=127.0.0.1:50051
ANYCABLE_REDIS_URL=redis://127.0.0.1:6379/0
ANYCABLE_HTTP_BROADCAST_PORT=8090
ANYCABLE_BROADCAST_KEY=q1w2e3r4
ANYCABLE_LOG_LEVEL=info
ANYCABLE_METRICS_HTTP=/metrics
ANYCABLE_METRICS_PORT=9091

逐项说明:

环境变量含义本文取值理由
ANYCABLE_HOST / ANYCABLE_PORTWebSocket 服务监听地址0.0.0.0:8080 对外服务
ANYCABLE_RPC_HOSTRPC 后端 gRPC 地址与本机 Ruby RPC(50051)对接
ANYCABLE_REDIS_URLRedis 广播通道地址本机 Redis
ANYCABLE_HTTP_BROADCAST_PORTHTTP 广播端点端口默认 0=关闭,必须显式开启
ANYCABLE_BROADCAST_KEYHTTP 广播鉴权密钥不设则广播端点裸奔,生产必配
ANYCABLE_METRICS_HTTP / ANYCABLE_METRICS_PORTPrometheus 指标端点独立 9091 端口便于内网抓取

4.4 systemd 托管

为 RPC 应用与 anycable-go 各建一个单元,保证开机自启与崩溃自愈。

写入 /etc/systemd/system/anycable-rpc.service:

[Unit]
Description=AnyCable RPC server (Ruby app)
After=network.target redis-server.service
Requires=redis-server.service

[Service]
Type=simple
WorkingDirectory=/opt/anycable-app
ExecStart=/usr/local/bin/anycable -r ./chat_app.rb
Restart=on-failure
RestartSec=3

[Install]
WantedBy=multi-user.target

写入 /etc/systemd/system/anycable-go.service:

[Unit]
Description=AnyCable-Go WebSocket server
After=network.target redis-server.service anycable-rpc.service
Requires=redis-server.service

[Service]
Type=simple
EnvironmentFile=/etc/anycable/anycable-go.env
ExecStart=/usr/local/bin/anycable-go
Restart=on-failure
RestartSec=3

[Install]
WantedBy=multi-user.target

加载并启动(每行仅一个单元):

systemctl daemon-reload
systemctl enable anycable-rpc
systemctl enable anycable-go
systemctl start anycable-rpc
systemctl start anycable-go
systemctl is-active anycable-rpc
systemctl is-active anycable-go

实测回显:

Created symlink /etc/systemd/system/multi-user.target.wants/anycable-rpc.service → /etc/systemd/system/anycable-rpc.service.
Created symlink /etc/systemd/system/multi-user.target.wants/anycable-go.service → /etc/systemd/system/anycable-go.service.
active
active

4.5 端到端联通实测

先看 anycable-go 启动日志,确认各组件就位:

journalctl -u anycable-go --no-pager | tail -8

实测回显(节选关键行):

INF RPC controller initialized: 127.0.0.1:50051 (concurrency: 28, impl: grpc, enable_tls: false, proto_versions: v1, proxy_headers: cookie, proxy_cookies: <all>) context=rpc
INF Handle WebSocket connections at http://0.0.0.0:8080/cable
INF Handle health requests at http://0.0.0.0:8080/health
INF Serve metrics at http://0.0.0.0:9091/metrics context=metrics
INF Accept broadcast requests at http://0.0.0.0:8090/_broadcast (authorization required) context=broadcast provider=http
INF subscribed to Redis channel context=broadcast provider=redis channel=__anycable__

再确认 RPC 应用日志:

journalctl -u anycable-rpc --no-pager | tail -3

实测回显:

I, [2026-10-25T02:27:09.347113 #5244]  INFO -- : AnyCable version: 1.5.2 (proto_version: v1)
I, [2026-10-25T02:27:09.347113 #5244]  INFO -- : Broadcasting Redis channel: __anycable__
I, [2026-10-25T02:27:09.376308 #5244]  INFO -- : RPC server is listening on 127.0.0.1:50051 (workers_num: 30)

验证 1:健康检查

curl -s http://127.0.0.1:8080/health

实测回显:

Ah, ha, ha, ha, stayin' alive, stayin' alive.

验证 2:WebSocket 连接 → gRPC 鉴权订阅 → Ruby 应用广播 → 客户端收到

编写验证客户端 /opt/anycable-app/ws_client.py:

import asyncio, json, subprocess
import websockets

async def recv_until(ws, pred, timeout=8):
    async def _loop():
        while True:
            raw = await ws.recv()
            print("FRAME:", raw)
            if pred(json.loads(raw)):
                return json.loads(raw)
    return await asyncio.wait_for(_loop(), timeout=timeout)

async def main():
    async with websockets.connect("ws://127.0.0.1:8080/cable") as ws:
        await recv_until(ws, lambda o: o.get("type") == "welcome")
        print("==> WELCOME OK")

        sub = {"command": "subscribe", "identifier": json.dumps({"channel": "ChatChannel"})}
        await ws.send(json.dumps(sub))
        await recv_until(ws, lambda o: o.get("type") == "confirm_subscription")
        print("==> CONFIRMED ChatChannel")

        r = subprocess.run(
            ["ruby", "-ranycable", "-rjson", "-e",
             'AnyCable.broadcast("chat", JSON.generate({text: "hello from ruby app"})); puts "broadcast sent"'],
            capture_output=True, text=True)
        print("==> RUBY_BROADCAST:", r.stdout.strip())

        msg = await recv_until(ws, lambda o: o.get("message") and o["message"].get("text") == "hello from ruby app")
        print("==> MSG_FROM_RUBY:", json.dumps(msg))

asyncio.run(main())
print("==> ALL_OK")

执行:

python3 /opt/anycable-app/ws_client.py

实测回显(ping 心跳帧已略去):

==> WELCOME OK
FRAME: {"identifier":"{\"channel\": \"ChatChannel\"}","message":{"type":"notice","text":"joined chat"}}
FRAME: {"identifier":"{\"channel\": \"ChatChannel\"}","type":"confirm_subscription"}
==> CONFIRMED ChatChannel
==> RUBY_BROADCAST: broadcast sent
FRAME: {"identifier":"{\"channel\": \"ChatChannel\"}","message":{"text":"hello from ruby app"}}
==> MSG_FROM_RUBY: {"identifier": "{\"channel\": \"ChatChannel\"}", "message": {"text": "hello from ruby app"}}
==> ALL_OK

这条链路完整走通:WS 连接经 gRPC 到 Ruby 应用鉴权(welcome)→ 订阅 ChatChannel 经 RPC 确认(confirm_subscription + 频道主动 transmit 的 joined chat)→ Ruby 应用侧 AnyCable.broadcast 经 Redis Pub/Sub → anycable-go → 客户端收到消息。

⚠️ 注意:AnyCable.broadcast(stream, data) 的 data 必须是 JSON 字符串(用 JSON.generate 包好)。直接传 Ruby Hash 会把对象原样塞进 Redis 载荷,anycable-go 端解析后无法投递(实测:服务端日志只到 received pubsub message,客户端收不到)。在 Rails 里这一步由 Action Cable 的 coder 自动完成,裸用 anycable gem 时要自己编码。

验证 3:HTTP 广播端点(带密钥 / 不带密钥)

curl -s -w "\nNOKEY=%{http_code}\n" -X POST http://127.0.0.1:8090/_broadcast \
  -H "Content-Type: application/json" \
  -d '{"stream":"chat","data":"{\"text\":\"x\"}"}'
curl -s -w "\nWITHKEY=%{http_code}\n" -X POST http://127.0.0.1:8090/_broadcast \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer q1w2e3r4" \
  -d '{"stream":"chat","data":"{\"text\":\"x\"}"}'

实测回显:

NOKEY=401
WITHKEY=201

不带 Authorization: Bearer <broadcast_key> 一律 401 拒绝;带正确密钥返回 201,消息随即推给所有订阅 chat 流的客户端(与验证 2 同款脚本实测收到)。

验证 4:Prometheus 指标

保持一个 WebSocket 连接不断开,抓取指标:

curl -s http://127.0.0.1:9091/metrics | grep -E "^anycable_go_(clients_num|goroutines_num|rpc_call_total)"

实测回显:

anycable_go_clients_num 1
anycable_go_goroutines_num 87
anycable_go_rpc_call_total 7

clients_num 1 与在线连接数一致,指标端点可用。

五、方案 B:Go 源码编译安装

需要定制构建(打补丁、裁剪、交叉编译)或网络策略只允许源码入库时选用。先装 Go 工具链,再编译。

5.1 安装 Go 1.27.3

Ubuntu 22.04 仓库的 golang-go 仅 1.18,编译 anycable-go 过旧,直接使用官方工具链(版本实查 go.dev,2026-10-25 当前稳定 go1.27.3):

curl -L -o /tmp/go.tgz https://go.dev/dl/go1.27.3.linux-amd64.tar.gz
tar -C /usr/local -xzf /tmp/go.tgz
export PATH=$PATH:/usr/local/go/bin
go version

实测回显:

go version go1.27.3 linux/amd64

5.2 go install 的坑与源码编译

直接 go install 会被官方仓库 go.mod 里的 replace 指令拒绝(实测回显):

go: github.com/anycable/anycable-go/cmd/anycable-go@v1.5.6: github.com/anycable/anycable-go/cmd/anycable-go@v1.5.6:
	The go.mod file for the module providing named packages contains one or
	more replace directives. It must not contain directives that would cause
	it to be interpreted differently than if it were the main module.

因此编译必须克隆源码后构建:

apt-get install -y git
git clone --depth 1 --branch v1.5.6 https://github.com/anycable/anycable-go.git /root/anycable-go
cd /root/anycable-go
go build -o /usr/local/bin/anycable-go ./cmd/anycable-go
anycable-go --version

实测回显(构建耗时约 30 秒,6 核机器;输出节选(实测首尾)):

go: downloading github.com/russross/blackfriday/v2 v2.1.0
(中间为数十行 go: downloading 依赖下载行,略)
real	0m30.221s
1.5.6

⚠️ 网络说明:go build 需访问 proxy.golang.org 拉依赖。本文实验环境直连不通(connection refused),已改用代理(export https_proxy=http://<代理地址>:7897)后编译成功。内网环境也可预置 GOPROXY=https://goproxy.cn,direct 国内镜像。

编译产物与方案 A 的二进制完全等价,后续配置(4.3)、systemd 托管(4.4)、验证(4.5)步骤完全相同,不再重复。

六、方案对比与选择建议

维度方案 A:预编译二进制方案 B:源码编译
版本v1.5.6(官方构建 e0dd6fa)v1.5.6(自编译)
依赖无Go ≥1.21 工具链 + git + 依赖下载
安装耗时<1 分钟约 5 分钟(含工具链)
可定制性不可改代码可打补丁/裁剪
适用场景生产与测试首选定制构建、审计入库

选择建议:一律优先方案 A;只有需要改源码或合规要求源码构建时才走方案 B。两种方案产物的配置与运维方式完全一致。

七、配置说明

anycable-go 配置的三种方式与优先级:--flag > 环境变量(ANYCABLE_*)> TOML 配置文件(--config-path)。

查看完整生效配置与默认值:

anycable-go --print-config | head -20

实测回显(节选):

# AnyCable server configuration.
# Read more at https://docs.anycable.io/anycable-go/configuration

# General settings

# Public mode disables connection authentication, pub/sub streams and broadcasts verification
# public = false

# Disable connection authentication only
# noauth = false

# Application instance ID
# node_id = "<auto-generated at each server start>"

# The application secret key
secret = "none"

# Broadcasting adapters for app-to-clients messages
broadcast_adapters = ["http", "redis"]

TOML 等价写法示例(/etc/anycable/anycable-go.toml):

secret = "none"
broadcast_adapters = ["http", "redis"]

[server]
host = "0.0.0.0"
port = 8085

[server.ssl]
cert_path = "/etc/anycable/tls/server.crt"
key_path = "/etc/anycable/tls/server.key"

用配置文件启动(实测可通过 TLS 提供 wss 服务;自签证书生成方式见《AnyCable 1.5 配置参数详解》TLS 一节):

anycable-go --config-path /etc/anycable/anycable-go.toml &
curl -sk https://127.0.0.1:8085/health

实测回显:

INF Using configuration from file: /etc/anycable/anycable-go.toml
...
Ah, ha, ha, ha, stayin' alive, stayin' alive.

常用配置项与全量参数逐项说明见《AnyCable 1.5 配置参数详解》篇;日常启停与排障命令见《AnyCable-Go 1.5 常用 CLI 命令详解》篇。

八、常见问题

Q1:客户端订阅频道时无任何响应,日志报 grpc connection is not ready?

anycable-go 默认把「连接鉴权、订阅授权」都委托给 RPC 后端。RPC 应用没启动或 ANYCABLE_RPC_HOST 指错时,subscribe 会静默失败(debug 日志可见 subscribe failed ... cause: grpc connection is not ready)。排查:systemctl is-active anycable-rpc、journalctl -u anycable-rpc --no-pager | tail 确认 RPC 在 50051 监听。无后端的纯推送场景可用 --norpc --public_streams --jwt_secret <密钥> 独立模式(见《AnyCable 1.5 配置参数详解》)。

Q2:RPC 应用启动即报 uninitialized constant Rack?

litecable 的 AnyCable 适配层内部使用 Rack::Request,但 anycable gem 不自动加载 rack。在应用文件顶部加 require "rack"(并确保已 gem install rack)即可。这是本文实测踩到并解决的坑。

Q3:Ruby 侧广播了,客户端却收不到?

检查广播载荷:AnyCable.broadcast("chat", data) 的 data 必须是 JSON 字符串。可先 redis-cli MONITOR 观察 __anycable__ 频道,正确形态是 "data":"{\"text\":\"hi\"}"(字符串),错误形态是 "data":{"text":"hi"}(对象)。

Q4:HTTP 广播返回 401?

配置了 ANYCABLE_BROADCAST_KEY 后必须带请求头 Authorization: Bearer <key>。未配置时广播端点无鉴权(启动日志会写 no authorization),生产环境务必配置。

Q5:systemctl stop anycable-go 要等很久吗?

anycable-go 收到 SIGTERM 会进入优雅停机(日志:Shutting down... (hit Ctrl-C to stop immediately or wait for up to 30s for graceful shutdown)),默认最长 30 秒等待连接排空,可用 ANYCABLE_SHUTDOWN_TIMEOUT 调整。

九、卸载清理

按安装逆序卸载。先停服务并禁用自启(每行一个单元):

systemctl stop anycable-go
systemctl stop anycable-rpc
systemctl disable anycable-go
systemctl disable anycable-rpc

删除单元文件、二进制、配置与应用:

rm -f /etc/systemd/system/anycable-go.service
rm -f /etc/systemd/system/anycable-rpc.service
rm -f /usr/local/bin/anycable-go
rm -rf /etc/anycable /opt/anycable-app
systemctl daemon-reload

卸载 Ruby 侧组件与(可选的)Redis:

gem uninstall anycable anycable-core litecable redis rack -x
apt-get remove -y redis-server

验证无残留:

which anycable-go; systemctl list-unit-files | grep anycable; ls /opt/anycable-app

实测回显(三条均无输出即为清净):

(无输出)

十、参考链接