fox/service/natsService.go
2025-06-17 18:24:33 +08:00

186 lines
5.3 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package service
import (
"encoding/json"
"fmt"
"github.com/fox/fox/etcd"
"github.com/fox/fox/ipb"
"github.com/fox/fox/log"
"github.com/fox/fox/nat"
"github.com/fox/fox/processor"
"github.com/golang/protobuf/proto"
"github.com/nats-io/nats.go"
"os"
"time"
)
const (
updateTopic = "update.service.topic"
)
// 初始化服务所需要的参数
type InitNatsServiceParams struct {
EtcdAddress []string
EtcdUsername string
EtcdPassword string
NatsAddress []string
ServiceType string // 本服务的服务类型
ServiceName string // 本服务的服务名,唯一索引
OnFunc IOnFunc // 包含初始化,停止,服务的消息处理
TypeId int // 与ServiceType一一对应
Version string
}
type NatsService struct {
*BaseService
nats *nat.Nats
registry *etcd.Registry[etcd.ServiceNode]
RpcProcessor *processor.RpcProcessor
node etcd.ServiceNode // 本服务节点信息
}
func NewNatsService(param *InitNatsServiceParams) (*NatsService, error) {
var err error
s := new(NatsService)
s.BaseService = NewBaseService(param.ServiceType, param.ServiceName, param.OnFunc, s)
s.node = etcd.ServiceNode{
TypeId: param.TypeId,
Name: s.Name(),
Type: s.Type(),
Version: param.Version,
}
if s.registry, err = etcd.NewRegistry[etcd.ServiceNode](param.EtcdAddress, param.EtcdUsername, param.EtcdPassword, s.node); err != nil {
return nil, err
}
s.RpcProcessor = processor.NewRpcProcessor()
s.nats = nat.NewNats(param.ServiceName, param.NatsAddress...)
if err = s.nats.Connect(); err != nil {
s.registry.UnregisterService()
return nil, err
}
// 订阅本服务名的topic
if err = s.Subscribe(Topic(s)); err != nil {
log.Error(err.Error())
}
// 订阅广播服务上线
_ = s.subscribeUpdateService()
s.publishUpdateService()
// 订阅rpc回调
_ = s.subscribeRpc()
return s, nil
}
func (n *NatsService) publishUpdateService() {
jsonData, _ := json.Marshal(n.node)
_ = n.nats.Publish(updateTopic, jsonData)
//log.Debug(n.Log("publishUpdateService:%v", string(jsonData)))
}
func (n *NatsService) subscribeUpdateService() error {
return n.SubscribeCb(updateTopic, func(m *nats.Msg) {
var node = &etcd.ServiceNode{}
_ = json.Unmarshal(m.Data, node)
//log.Debug(n.Log("发现新节点:%v", string(m.Data)))
// 不是同类服务不处理,是自己发出来的更新,也不处理
if node.Type != n.Type() || node.Name == n.Name() {
//log.Debug(n.Log("与本节点不匹配.本节点:%v", n.node))
return
}
// 有新服务上线,本服务准备退出
if n.node.Version < node.Version {
log.InfoF(n.Log("有新服务:%v,版本:%v.本服务即将关闭", node.Name, node.Version))
n.NotifyStop()
//n.WaitStop()
os.Exit(0)
}
})
}
func (n *NatsService) subscribeRpc() error {
return n.SubscribeCb(RpcTopic(n), func(m *nats.Msg) {
var iMsg = &ipb.InternalMsg{}
_ = proto.Unmarshal(m.Data, iMsg)
//log.DebugF("recv rpc msg:%v", iMsg.String())
rsp, err := n.RpcProcessor.Dispatch(iMsg.RpcMsgId, iMsg)
if err != nil {
log.Error(err.Error())
return
}
rspData, _ := proto.Marshal(rsp)
_ = m.Respond(rspData)
})
}
// 订阅回调 cb会切回service协程执行避免在cb中有阻塞式请求
func (n *NatsService) SubscribeCb(topic string, cb func(m *nats.Msg)) error {
return n.nats.SubscribeCb(topic, func(m *nats.Msg) {
n.RunOnce(func() {
cb(m)
})
})
}
func (n *NatsService) Subscribe(topic string) error {
return n.nats.Subscribe(topic, n.msg)
}
// 队列订阅,队列中只会有一个消费者消费该消息
func (n *NatsService) QueueSubscribe(topic string, queue string) error {
return n.nats.QueueSubscribe(topic, queue, n.msg)
}
func (s *NatsService) OnStop() {
s.nats.Close()
}
func (s *NatsService) SendByTopic(topic string, msg *ipb.InternalMsg) error {
data, _ := proto.Marshal(msg)
return s.nats.Publish(topic, data)
}
// 依赖子类实现通过节点id获取topic名再调用SendByTopic
func (s *NatsService) SendByServiceId(serviceId int, msg *ipb.InternalMsg) error {
_ = serviceId
_ = msg
return nil
}
// call会阻塞主协程需要起新协程去等待返回值业务逻辑则转回主协程处理。
// 不要将处理返回值的业务逻辑放到新协程,避免业务逻辑中有数据并发问题
// 比如并发call拉用户数据然后操作本地的user map
func (s *NatsService) CallByTopic(rpcTopic string, timeout time.Duration, msg *ipb.InternalMsg) (*ipb.InternalMsg, error) {
data, _ := proto.Marshal(msg)
response, err := s.nats.Rpc(rpcTopic, timeout, data)
if err != nil {
return nil, err
}
rspMsg := &ipb.InternalMsg{}
_ = proto.Unmarshal(response, rspMsg)
return rspMsg, nil
}
// 依赖子类实现通过节点id获取topic名再调用CallByTopic
func (s *NatsService) CallByServiceId(serviceId int, timeout time.Duration, msg *ipb.InternalMsg) (*ipb.InternalMsg, error) {
_ = serviceId
_ = msg
_ = timeout
return nil, fmt.Errorf("需要子类实现")
}
func (s *NatsService) ServiceEtcd() *etcd.Registry[etcd.ServiceNode] {
return s.registry
}
//// 从etcd中获取所有服务节点
//func (s *NatsService) GetServiceNodes() *sync.Map {
// return s.registry.GetNodes()
//}
// 查找指定的服务节点信息
func (s *NatsService) FindServiceNode(serviceName string) (etcd.ServiceNode, error) {
return s.registry.FindNode(serviceName)
}