• 如何开发一个自定义功能模块
    • 目录约定
    • 启动约定
    • SDK

    如何开发一个自定义功能模块

    在开发自定义模块前请阅读源码安装 Baetyl,了解 Baetyl 的编译环境。

    自定义模块不限定开发语言,可运行即可,基本没有限制,甚至可以直接使用 hub.docker.com 上已有的镜像,比如 eclipse-mosquitto。但是了解下面介绍的约定,有利于更好、更快地开发自定义模块。

    目录约定

    目前,进程模式和容器模式一样,会为每个服务开辟独立的工作空间,虽然达不到隔离的效果,但是可以保证用户使用体验的一致。进程模式会在 var/run/baetyl/services 目录下为每个服务创建一个以服务名称命名的目录,服务程序启动时会指定该目录为工作目录,服务绑定的存储卷(volume)会映射(软链)到工作目录下。这里我们沿用容器模式的叫法,把该目录下的空间也称作容器,那么容器中的目录有如下推荐的使用方式:

    • 容器中默认工作目录:/
    • 容器中默认配置文件:/etc/baetyl/service.yml
    • 容器中默认持久化路径:/var/db/baetyl
    • 容器中默认日志路径:/var/log/baetyl注意:如果数据需要持久化在设备(宿主机)上,比如数据库和日志,必须通过存储卷将容器中的目录映射到宿主机目录上,否者服务停止后数据会丢失。

    启动约定

    模块启动的方式没有过多要求,推荐从默认文件中加载YMAL格式的配置,然后运行模块的业务逻辑,最后监听 SIGTERM 信号来优雅退出。一个简单的 Golang 模块实现可参考 MQTT 远程通讯模块(baetyl-remote-mqtt)。

    SDK

    如果模块使用 Golang 开发,可使用 Baetyl 提供的 SDK,位于该项目的 sdk 目录中,由 Context 提供功能接口。目前,提供的 SDK 能力还比较有限,后续会逐渐加强。

    Context 接口列表如下:

    1. // 返回服务的系统配置,比如 hub 和 logger
    2. Config() *ServiceConfig
    3. // 加载服务的自定义配置
    4. LoadConfig(interface{}) error
    5. // 通过系统配置创建一个连接 Hub 的 Client,可以指定 Client ID 和订阅的主题信息
    6. NewHubClient(string, []mqtt.TopicInfo) (*mqtt.Dispatcher, error)
    7. // 返回日志接口
    8. Log() logger.Logger
    9. // 检查运行模式
    10. IsNative() bool
    11. // 等待退出,接收 SIGTERM 和 SIGINT 信号
    12. Wait()
    13. // 返回等待退出的 Channel
    14. WaitChan() <-chan os.Signal
    15.  
    16. // 主程序 RESTful API
    17.  
    18. // 更新系统服务
    19. UpdateSystem(string, bool) error
    20. // 查看系统状态
    21. InspectSystem() (*Inspect, error)
    22. // 获取一个宿主机的空闲端口
    23. GetAvailablePort() (string, error)
    24. // 报告本实例的状态信息
    25. ReportInstance(stats map[string]interface{}) error
    26. // 启动某个服务的某个实例
    27. StartInstance(serviceName, instanceName string, dynamicConfig map[string]string) error
    28. // 停止某个服务的某个实例
    29. StopInstance(serviceName, instanceName string) error

    下面以简单定时器模块实现为例,介绍SDK的用法。

    1. package main
    2.  
    3. import (
    4. "encoding/json"
    5. "time"
    6.  
    7. "github.com/baetyl/baetyl/protocol/mqtt"
    8. baetyl "github.com/baetyl/baetyl/sdk/baetyl-go"
    9. )
    10.  
    11. // 自定义模块的自定义配置,
    12. type config struct {
    13. Timer struct {
    14. Interval time.Duration `yaml:"interval" json:"interval" default:"1m"`
    15. } `yaml:"timer" json:"timer"`
    16. Publish mqtt.TopicInfo `yaml:"publish" json:"publish" default:"{\"topic\":\"timer\"}"`
    17. }
    18.  
    19. func main() {
    20. // 模块在 Baetyl 的 Context 中启动,SDK 的功能均由 Context 提供
    21. baetyl.Run(func(ctx baetyl.Context) error {
    22. var cfg config
    23. // 加载自定义配置
    24. err := ctx.LoadConfig(&cfg)
    25. if err != nil {
    26. return err
    27. }
    28. // 创建连接Hub的客户端
    29. cli, err := ctx.NewHubClient("", nil)
    30. if err != nil {
    31. return err
    32. }
    33. // 启动客户端,支持自动重连
    34. cli.Start(nil)
    35. // 创建定时器
    36. ticker := time.NewTicker(cfg.Timer.Interval)
    37. defer ticker.Stop()
    38. for {
    39. select {
    40. case t := <-ticker.C:
    41. msg := map[string]int64{"time": t.Unix()}
    42. pld, _ := json.Marshal(msg)
    43. // 定时发送消息到 Hub
    44. err := cli.Publish(cfg.Publish, pld)
    45. if err != nil {
    46. // 打印错误日志
    47. ctx.Log().Errorf(err.Error())
    48. }
    49. case <-ctx.WaitChan():
    50. // 等待退出信号,SIGTERM 或者 SIGINT
    51. return nil
    52. }
    53. }
    54. })
    55. }

    baetyl-timer 的配置中,hub 属于系统配置,timerpublish 是该模块的自定义配置。

    1. hub:
    2. address: tcp://localhub:1883
    3. username: test
    4. password: hahaha
    5. clientid: timer1
    6. timer:
    7. interval: 1s
    8. publish:
    9. topic: timer1