40 KiB
40 KiB
Tinode Chat Server 代码通读指南
本指南帮助你快速理解 Tinode 代码架构,掌握开发流程,能够独立完成新需求开发和项目部署。
目录
架构总览
整体架构图
┌─────────────────────────────────────────────────────────────────────────────┐
│ 客户端层 │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ WebSocket │ │ Long Poll │ │ gRPC │ │ HTTP │ │
│ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │
└──────────┼─────────────────┼─────────────────┼─────────────────┼───────────┘
│ │ │ │
▼ ▼ ▼ ▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ 协议处理层 │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ hdl_websock.go / hdl_longpoll.go │ │
│ │ hdl_grpc.go / http.go │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ 会话层 (Session) │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ Session 结构体 - 管理单个客户端连接 │ │
│ │ - 认证状态、订阅列表、消息队列 │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ Hub (核心路由) │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ - 消息路由分发 │ │
│ │ - Topic 生命周期管理 │ │
│ │ - 全局状态维护 │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ Topic 层 │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ me │ │ fnd │ │ p2p │ │ grp │ │ sys │ │
│ │ (用户) │ │ (发现) │ │ (单聊) │ │ (群聊) │ │ (系统) │ │
│ └─────────┘ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ 存储层 (Store) │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ store.go - 存储抽象层 │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ MySQL │ │PostgreSQL│ │ MongoDB │ │RethinkDB│ │
│ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘
核心组件职责
| 组件 | 文件 | 职责 |
|---|---|---|
| main.go | 入口点 | 配置加载、组件初始化、服务启动 |
| Hub | hub.go | 全局消息路由、Topic 生命周期管理 |
| Session | session.go | 客户端连接管理、消息收发 |
| Topic | topic.go | 聊天室/会话逻辑、消息处理 |
| Store | store/store.go | 数据库操作抽象层 |
| Plugins | plugins.go | 插件系统、事件钩子 |
启动流程详解
1. 入口函数 (main.go)
// main.go 启动流程
func main() {
// 1. 解析命令行参数
flag.Parse()
// 2. 加载配置文件
config := loadConfig(configFileName)
// 3. 初始化日志系统
logs.Init()
// 4. 初始化数据库适配器
store.Open(config.StoreConfig)
// 5. 初始化认证模块
auth.Init(config.AuthConfig)
// 6. 初始化推送通知
push.Init(config.PushConfig)
// 7. 初始化媒体处理
media.Init(config.MediaConfig)
// 8. 创建 Hub(核心路由)
globals.hub = newHub()
// 9. 创建 SessionStore
globals.sessionStore = newSessionStore()
// 10. 初始化插件
pluginsInit(config.PluginConfig)
// 11. 启动 HTTP/WebSocket 服务
listenAndServe(addr, mux, tlsConfig, stop)
}
2. 配置加载流程
配置文件 tinode.conf 是 JSON 格式,主要配置项:
{
"listen": ":6060", // HTTP 监听地址
"grpc_listen": ":16060", // gRPC 监听地址
"api_key_salt": "...", // API 密钥盐值
"max_message_size": 131072, // 最大消息大小
"store_config": { // 数据库配置
"use_adapter": "mysql",
"adapters": {
"mysql": { "dsn": "..." }
}
},
"auth_config": { // 认证配置
"basic": { "add_to_tags": true }
},
"push_config": { // 推送配置
"fcm": { "credentials_file": "..." }
}
}
3. 数据库初始化 (tinode-db)
# 初始化数据库并加载测试数据
./tinode-db -config=tinode.conf -data=data.json
# 主要步骤:
# 1. 连接数据库
# 2. 创建表/集合
# 3. 创建索引
# 4. 加载初始数据(用户、话题)
核心数据结构
1. Hub 结构体
Hub 是整个系统的核心路由器:
// server/hub.go
type Hub struct {
// Topic 缓存 (name -> *Topic)
topics *sync.Map
// 客户端消息路由通道
routeCli chan *ClientComMessage
// 服务端消息路由通道
routeSrv chan *ServerComMessage
// 订阅请求通道
join chan *ClientComMessage
// Topic 注销通道
unreg chan *topicUnreg
// 用户状态变更通道
userStatus chan *userStatusReq
// 关闭通道
shutdown chan chan<- bool
}
Hub 的核心方法:
// hub.go - 主事件循环
func (h *Hub) run() {
for {
select {
case msg := <-h.routeCli:
// 路由客户端消息到对应 Topic
h.routeClientMessage(msg)
case msg := <-h.routeSrv:
// 路由服务端消息
h.routeServerMessage(msg)
case req := <-h.join:
// 处理订阅请求,创建或加载 Topic
h.processJoin(req)
case req := <-h.unreg:
// 注销 Topic
h.processUnregister(req)
case done := <-h.shutdown:
// 关闭所有 Topic
h.gracefulShutdown()
done <- true
return
}
}
}
2. Topic 结构体
Topic 是聊天的核心单元:
// server/topic.go
type Topic struct {
// Topic 名称 (如 "usrABC", "grpXYZ")
name string
// Topic 类型
cat types.TopicCat // me, fnd, p2p, grp, sys, slf
// 订阅者数据
perUser map[types.Uid]perUserData
// 附加的会话
sessions map[*Session]perSessionData
// 消息通道
clientMsg chan *ClientComMessage // 客户端消息
serverMsg chan *ServerComMessage // 服务端消息
reg chan *ClientComMessage // 订阅请求
unreg chan *ClientComMessage // 取消订阅
meta chan *ClientComMessage // 元数据操作
exit chan *shutDown // 关闭信号
// 状态
status int32 // 0=none, 1=new, 2=ready, 3=paused
}
Topic 类型分类:
| 类型 | 前缀 | 说明 |
|---|---|---|
me |
me |
用户个人话题,管理联系人列表 |
fnd |
fnd |
搜索话题,用于发现用户 |
p2p |
p2p |
点对点聊天 |
grp |
grp |
群组聊天 |
chn |
chn |
频道(只读群组) |
sys |
sys |
系统话题 |
slf |
slf |
自我话题(草稿/收藏) |
3. Session 结构体
Session 代表一个客户端连接:
// server/session.go
type Session struct {
// 协议类型
proto SessionProto // WEBSOCK, LPOLL, GRPC, PROXY
// 会话 ID
sid string
// 连接句柄
ws *websocket.Conn // WebSocket
grpcnode pbx.Node_MessageLoopServer // gRPC
// 用户信息
uid types.Uid // 用户 ID
authLvl auth.Level // 认证级别
// 消息队列
send chan any // 发送队列
stop chan any // 停止信号
detach chan string // 分离话题
// 订阅的话题
subs map[string]*Subscription
// 状态标记
terminating int32 // 是否正在终止
background bool // 是否后台会话
}
4. 消息结构体
客户端到服务端的消息定义:
// server/datamodel.go
// 握手消息
type MsgClientHi struct {
Id string `json:"id,omitempty"`
UserAgent string `json:"ua,omitempty"`
Version string `json:"ver,omitempty"`
DeviceID string `json:"dev,omitempty"`
Platform string `json:"platf,omitempty"`
}
// 登录消息
type MsgClientLogin struct {
Id string `json:"id,omitempty"`
Scheme string `json:"scheme,omitempty"`
Secret []byte `json:"secret,omitempty"`
}
// 订阅消息
type MsgClientSub struct {
Id string `json:"id,omitempty"`
Topic string `json:"topic,omitempty"`
Get *MsgGetQuery `json:"get,omitempty"`
Set *MsgSetQuery `json:"set,omitempty"`
}
// 发布消息
type MsgClientPub struct {
Id string `json:"id,omitempty"`
Topic string `json:"topic,omitempty"`
Content any `json:"content,omitempty"`
}
消息处理流程
1. 完整消息流转图
┌──────────────────────────────────────────────────────────────────────────────┐
│ 客户端发送: {"hi", "ver":"0.25", "ua":"Chrome/120"} │
└──────────────────────────────────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────────────────────┐
│ WebSocket Handler (hdl_websock.go) │
│ serveWebSocket() -> sess.readLoop() -> sess.dispatchRaw(raw) │
└──────────────────────────────────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────────────────────┐
│ Session 消息分发 (session.go) │
│ dispatchRaw() -> 解析 JSON -> 识别消息类型 -> 调用对应 handler │
└──────────────────────────────────────────────────────────────────────────────┘
│
┌───────────────────────┼───────────────────────┐
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│ hi │ │ login │ │ sub │
│ handler │ │ handler │ │ handler │
└──────────┘ └──────────┘ └──────────┘
│ │ │
└───────────────────────┼───────────────────────┘
▼
┌──────────────────────────────────────────────────────────────────────────────┐
│ Hub 路由 (hub.go) │
│ routeCli <- ClientComMessage -> h.routeClientMessage() │
│ 根据 RcptTo 找到对应 Topic -> 发送到 Topic.clientMsg 通道 │
└──────────────────────────────────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────────────────────┐
│ Topic 处理 (topic.go) │
│ t.clientMsg <- msg -> t.processClientMessage() │
│ 根据 msg.Original 类型调用对应处理函数 │
│ - hi -> t.handleHi() │
│ - login -> t.handleLogin() │
│ - sub -> t.handleSubscribe() │
│ - pub -> t.handlePublish() │
└──────────────────────────────────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────────────────────┐
│ 数据库操作 (store/store.go) │
│ store.MessageSave() / store.TopicGet() / store.UserCreate() │
└──────────────────────────────────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────────────────────┐
│ 响应消息生成 │
│ ServerComMessage{Ctrl/Data/Pres/Meta/Info} │
│ -> 序列化为 JSON -> 发送到 Session.send 通道 │
└──────────────────────────────────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────────────────────┐
│ Session 发送 (session.go) │
│ sess.writeLoop() -> sess.send <- msg -> WebSocket.WriteMessage() │
└──────────────────────────────────────────────────────────────────────────────┘
2. 消息类型与处理函数映射
| 消息类型 | 结构体 | 处理函数 | 说明 |
|---|---|---|---|
hi |
MsgClientHi | handleHi() | 握手,建立连接 |
login |
MsgClientLogin | handleLogin() | 用户登录认证 |
acc |
MsgClientAcc | handleAccount() | 创建/更新账户 |
sub |
MsgClientSub | handleSubscribe() | 订阅话题 |
leave |
MsgClientLeave | handleLeave() | 取消订阅 |
pub |
MsgClientPub | handlePublish() | 发布消息 |
get |
MsgClientGet | handleGet() | 获取元数据/消息 |
set |
MsgClientSet | handleSet() | 更新元数据 |
del |
MsgClientDel | handleDelete() | 删除消息/话题 |
note |
MsgClientNote | handleNote() | 发送通知 |
3. WebSocket 消息处理示例
// server/hdl_websock.go
// 处理 WebSocket 连接
func serveWebSocket(wrt http.ResponseWriter, req *http.Request) {
// 1. 验证 API Key
if isValid, _ := checkAPIKey(getAPIKey(req)); !isValid {
wrt.WriteHeader(http.StatusForbidden)
return
}
// 2. 升级 HTTP 连接为 WebSocket
ws, err := upgrader.Upgrade(wrt, req, nil)
// 3. 创建 Session
sess, count := globals.sessionStore.NewSession(ws, "")
// 4. 启动读写协程
go sess.writeLoop() // 发送消息
go sess.readLoop() // 接收消息
}
// 读循环
func (sess *Session) readLoop() {
for {
_, raw, err := sess.ws.ReadMessage()
if err != nil {
return
}
sess.dispatchRaw(raw) // 分发消息
}
}
// 写循环
func (sess *Session) writeLoop() {
for {
select {
case msg := <-sess.send:
sess.sendMessage(msg)
case <-sess.stop:
return
}
}
}
关键模块详解
1. 认证模块 (server/auth/)
认证模块采用插件式架构:
server/auth/
├── auth.go # 认证接口定义
├── basic/ # 用户名密码认证
├── token/ # Token 认证
├── anon/ # 匿名认证
├── code/ # 验证码认证
└── rest/ # REST 外部认证
认证接口定义:
// server/auth/auth.go
type Authenticator interface {
// 初始化
Init(jsonconf json.RawMessage, opts ...any) error
// 认证检查
Authenticate(secret []byte) (types.Uid, Level, time.Time, error)
// 生成秘密值
GenSecret(uid types.Uid, authLvl Level, expires time.Time) ([]byte, error)
}
添加新认证方式步骤:
- 在
server/auth/下创建新目录,如oauth/ - 实现
Authenticator接口 - 在
main.go中导入:_ "github.com/tinode/chat/server/auth/oauth" - 在配置文件中添加相应配置
2. 数据库适配器 (server/db/)
数据库层采用适配器模式:
// server/db/adapter.go
type Adapter interface {
// 连接管理
Open(config json.RawMessage) error
Close() error
IsOpen() bool
// 用户管理
UserCreate(user *t.User) error
UserGet(uid t.Uid) (*t.User, error)
UserUpdate(uid t.Uid, update map[string]any) error
UserDelete(uid t.Uid, hard bool) error
// 话题管理
TopicCreate(topic *t.Topic) error
TopicGet(name string) (*t.Topic, error)
// 消息管理
MessageSave(msg *t.Message) error
MessageGet(topic string, opts *MsgGetOpts) ([]t.Message, error)
// 订阅管理
SubscriptionGet(topic string, uid t.Uid) (*t.Subscription, error)
}
添加新数据库支持:
- 在
server/db/下创建新目录,如cockroach/ - 实现
Adapter接口 - 在
main.go中导入:_ "github.com/tinode/chat/server/db/cockroach" - 构建时指定 tag:
go build -tags cockroach
3. 推送通知 (server/push/)
// server/push/push.go
type PushHandler interface {
// 初始化
Init(jsonconf json.RawMessage) error
// 发送推送
Push(userId types.Uid, data *PushData) error
}
FCM 推送配置:
// tinode.conf
"push_config": {
"fcm": {
"credentials_file": "/path/to/credentials.json"
}
}
4. 文件存储 (server/media/)
支持本地文件系统和 S3:
// server/media/media.go
type Handler interface {
// 上传
Upload(uri string, reader io.Reader) (string, error)
// 下载
Download(uri string) (io.ReadCloser, error)
// 删除
Delete(uri string) error
}
5. 插件系统 (server/plugins.go)
插件可以拦截和处理所有消息:
// 插件事件类型
const (
plgHi = 1 << iota // 握手
plgAcc // 账户操作
plgLogin // 登录
plgSub // 订阅
plgPub // 发布
plgGet // 获取
plgSet // 设置
plgDel // 删除
)
// 插件接口
type Plugin interface {
// 处理客户端消息
HandleClientMessage(msg *ClientComMessage) (*ClientComMessage, error)
// 处理服务端消息
HandleServerMessage(msg *ServerComMessage) (*ServerComMessage, error)
}
开发实战指南
场景 1: 添加新的消息类型
步骤 1: 定义消息结构体
// server/datamodel.go
type MsgClientTyping struct {
Id string `json:"id,omitempty"`
Topic string `json:"topic,omitempty"`
IsTyping bool `json:"typing"`
}
步骤 2: 添加解析逻辑
// server/session.go - dispatchRaw()
func (sess *Session) dispatchRaw(raw []byte) {
var msg struct {
Hi *MsgClientHi `json:"hi"`
// ... 其他类型
Typing *MsgClientTyping `json:"typing"` // 新增
}
json.Unmarshal(raw, &msg)
if msg.Typing != nil {
sess.handleTyping(msg.Typing)
}
}
步骤 3: 实现处理函数
// server/topic.go
func (t *Topic) handleTyping(msg *MsgClientTyping) {
// 广报给其他订阅者
for sess := range t.sessions {
if sess.uid != msg.sess.uid {
sess.queueOut(&ServerComMessage{
Info: &MsgServerInfo{
Topic: t.name,
From: msg.sess.uid.String(),
Content: map[string]any{"typing": msg.IsTyping},
},
})
}
}
}
场景 2: 添加新的 API 端点
步骤 1: 定义 HTTP Handler
// server/http.go
func serveCustomAPI(wrt http.ResponseWriter, req *http.Request) {
// 验证请求
if req.Method != http.MethodPost {
wrt.WriteHeader(http.StatusMethodNotAllowed)
return
}
// 处理请求
var data struct {
UserID string `json:"user_id"`
}
json.NewDecoder(req.Body).Decode(&data)
// 返回响应
wrt.Header().Set("Content-Type", "application/json")
json.NewEncoder(wrt).Encode(map[string]any{
"status": "ok",
})
}
步骤 2: 注册路由
// server/http.go - initHTTP()
func initHTTP() *http.ServeMux {
mux := http.NewServeMux()
// ... 其他路由
mux.HandleFunc("/v1/custom", serveCustomAPI) // 新增
return mux
}
场景 3: 添加新的数据库字段
步骤 1: 修改数据模型
// server/store/types/types.go
type User struct {
Id types.Uid
CreatedAt time.Time
// 新增字段
Nickname string `json:"nickname"`
}
步骤 2: 修改数据库适配器
// server/db/mysql/adapter.go
func (a *adapter) UserCreate(user *t.User) error {
_, err := a.db.Exec(`
INSERT INTO users (id, createdat, nickname)
VALUES (?, ?, ?)
`, user.Id.String(), user.CreatedAt, user.Nickname)
return err
}
步骤 3: 更新数据库 Schema
-- server/db/mysql/schema.sql
ALTER TABLE users ADD COLUMN nickname VARCHAR(64);
场景 4: 开发插件
创建一个消息过滤插件:
// myplugin/filter.go
package main
import (
"github.com/tinode/chat/pbx"
)
type FilterPlugin struct {
bannedWords []string
}
func (p *FilterPlugin) HandleClientMessage(msg *pb.ClientMsg) (*pb.ClientMsg, error) {
// 检查消息内容
if msg.Pub != nil {
content := string(msg.Pub.Content)
for _, word := range p.bannedWords {
if strings.Contains(content, word) {
// 过滤敏感词
content = strings.ReplaceAll(content, word, "***")
msg.Pub.Content = []byte(content)
}
}
}
return msg, nil
}
func (p *FilterPlugin) HandleServerMessage(msg *pb.ServerMsg) (*pb.ServerMsg, error) {
return msg, nil
}
注册插件:
// main.go
func main() {
plugin := &FilterPlugin{
bannedWords: []string{"badword1", "badword2"},
}
globals.plugins = append(globals.plugins, plugin)
}
测试与调试
1. 单元测试
# 运行所有测试
go test ./server/...
# 运行特定测试
go test -run TestTopicPublish ./server/
# 查看覆盖率
go test -cover ./server/...
2. 集成测试
使用 tn-cli 进行集成测试:
# 登录
python tn-cli.py --login-basic=alice:alice123
# 执行测试脚本
python tn-cli.py < test-script.txt
3. 调试技巧
开启详细日志:
// 在 tinode.conf 中
{
"logging": {
"level": "debug"
}
}
使用 pprof 性能分析:
# 启动服务器时开启 pprof
./server -expvar=/debug/vars
# 访问性能分析
go tool pprof http://localhost:6060/debug/pprof/profile
调试 WebSocket:
// 浏览器控制台
const ws = new WebSocket('ws://localhost:6060/v0/channels');
ws.onopen = () => {
ws.send(JSON.stringify({hi: {ver: "0.25", ua: "test"}}));
};
ws.onmessage = (e) => {
console.log('收到:', JSON.parse(e.data));
};
4. 数据库调试
# MySQL
mysql -u root -p tinode -e "SELECT * FROM users LIMIT 10;"
# MongoDB
mongosh tinode --eval "db.users.find().limit(10)"
# PostgreSQL
psql -U postgres -d tinode -c "SELECT * FROM users LIMIT 10;"
部署指南
1. 本地开发环境
# 1. 克隆代码
git clone https://github.com/tinode/chat.git
cd chat
# 2. 安装依赖
go mod download
# 3. 启动数据库(以 MySQL 为例)
docker run -d --name mysql \
-e MYSQL_ROOT_PASSWORD=root \
-e MYSQL_DATABASE=tinode \
-p 3306:3306 \
mysql:8.0
# 4. 初始化数据库
go run ./tinode-db -tags mysql -data=./tinode-db/data.json
# 5. 启动服务器
go run ./server -tags mysql
2. 生产环境部署
方式 1: 直接部署
# 构建
go build -tags mysql -ldflags "-X main.buildstamp=`git describe --tags`" ./server
# 创建配置目录
mkdir -p /etc/tinode
cp server/tinode.conf /etc/tinode/
# 创建 systemd 服务
cat > /etc/systemd/system/tinode.service << 'EOF'
[Unit]
Description=Tinode Chat Server
After=network.target mysql.service
[Service]
Type=simple
User=tinode
ExecStart=/usr/local/bin/server -config=/etc/tinode/tinode.conf
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
EOF
# 启动服务
systemctl daemon-reload
systemctl enable tinode
systemctl start tinode
方式 2: Docker 部署
# 使用官方镜像
docker run -d --name tinode \
-p 6060:18080 \
-e MYSQL_DSN="root@tcp(mysql:3306)/tinode?parseTime=true" \
tinode/tinode-mysql:latest
# Docker Compose
cat > docker-compose.yml << 'EOF'
version: '3'
services:
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: tinode
volumes:
- mysql_data:/var/lib/mysql
tinode:
image: tinode/tinode-mysql:latest
ports:
- "6060:18080"
depends_on:
- mysql
environment:
STORE_CONFIG_ADAPTERS_MYSQL_DSN: "root@tcp(mysql:3306)/tinode?parseTime=true"
volumes:
mysql_data:
EOF
docker-compose up -d
3. 集群部署
# tinode.conf 集群配置
{
"cluster": {
"enabled": true,
"self": "node1",
"nodes": [
{"id": "node1", "addr": "node1.example.com:12000"},
{"id": "node2", "addr": "node2.example.com:12000"},
{"id": "node3", "addr": "node3.example.com:12000"}
]
}
}
4. 监控配置
# Prometheus 监控
"expvar": "/debug/vars"
# Grafana 仪表盘
# 导入: https://grafana.com/grafana/dashboards/...
5. 生产环境配置建议
{
// 安全配置
"tls": {
"enabled": true,
"strict_max_age": 604800
},
// 性能配置
"max_message_size": 262144,
"max_subscriber_count": 256,
// 日志配置
"logging": {
"level": "info"
},
// 数据库连接池
"store_config": {
"adapters": {
"mysql": {
"dsn": "user:pass@tcp(db-host:3306)/tinode?parseTime=true&max_open_conns=100&max_idle_conns=10"
}
}
}
}
常见问题排查
1. 连接问题
# 检查端口是否监听
netstat -tlnp | grep 6060
# 检查防火墙
iptables -L -n | grep 6060
# 测试连接
curl http://localhost:6060/v0/hi
2. 数据库问题
# 检查数据库连接
mysql -u root -p -e "SHOW PROCESSLIST;"
# 检查表结构
mysql -u root -p tinode -e "SHOW TABLES;"
# 查看错误日志
tail -f /var/log/mysql/error.log
3. 性能问题
# CPU 分析
go tool pprof http://localhost:6060/debug/pprof/profile?seconds=30
# 内存分析
go tool pprof http://localhost:6060/debug/pprof/heap
# 查看统计
curl http://localhost:6060/debug/vars | jq .
扩展阅读
开发流程总结
┌─────────────────────────────────────────────────────────────────┐
│ 开发新功能流程 │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 1. 理解需求 │
│ - 阅读需求文档 │
│ - 确认技术方案 │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 2. 设计数据结构 │
│ - 修改 datamodel.go 添加消息类型 │
│ - 修改 store/types/types.go 添加数据模型 │
│ - 更新数据库 schema │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 3. 实现业务逻辑 │
│ - 在 topic.go 添加处理函数 │
│ - 在 store.go 添加数据库操作 │
│ - 实现必要的适配器方法 │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 4. 编写测试 │
│ - 单元测试 *_test.go │
│ - 集成测试(使用 tn-cli) │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 5. 测试验证 │
│ - go test ./... │
│ - 本地运行验证 │
│ - 性能测试 │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 6. 代码审查 & 提交 │
│ - 代码审查 │
│ - 更新文档 │
│ - Git commit │
└─────────────────────────────────────────────────────────────────┘
祝开发顺利!