github.com/kaydxh/golang@v0.0.131/pkg/discovery/consul/discovery.go (about) 1 /* 2 *Copyright (c) 2022, kaydxh 3 * 4 *Permission is hereby granted, free of charge, to any person obtaining a copy 5 *of this software and associated documentation files (the "Software"), to deal 6 *in the Software without restriction, including without limitation the rights 7 *to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 *copies of the Software, and to permit persons to whom the Software is 9 *furnished to do so, subject to the following conditions: 10 * 11 *The above copyright notice and this permission notice shall be included in all 12 *copies or substantial portions of the Software. 13 * 14 *THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 *IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 *FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 *AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 *LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 *OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 *SOFTWARE. 21 */ 22 package consul 23 24 import ( 25 "context" 26 "fmt" 27 "net" 28 "strconv" 29 "sync" 30 "time" 31 32 "github.com/hashicorp/consul/api" 33 "github.com/kaydxh/golang/go/errors" 34 "github.com/sirupsen/logrus" 35 "go.uber.org/atomic" 36 ) 37 38 type ServiceRegistryServer struct { 39 ConsulAddress string 40 ServiceId string 41 ServiceName string 42 Tag []string 43 Ip string 44 Port int 45 TTL time.Duration 46 CheckInterval time.Duration 47 48 inShutdown atomic.Bool // true when when server is in shutdown 49 50 mu sync.Mutex 51 cancel func() 52 } 53 54 func NewServiceRegistry( 55 consulAddr string, 56 serviceName string, 57 serviceAddress string, 58 ) (*ServiceRegistryServer, error) { 59 logger := logrus.WithField("module", "service_registry"). 60 WithField("service_name", serviceName). 61 WithField("addr", serviceAddress) 62 63 host, port, err := net.SplitHostPort(serviceAddress) 64 if err != nil { 65 logger.WithError(err).Errorln("malformed service serviceAddress") 66 return nil, fmt.Errorf("malformed service serviceAddress: %w", err) 67 } 68 69 nport, err := strconv.Atoi(port) 70 if err != nil { 71 logger.WithField("port", port). 72 WithError(err). 73 Errorln("malformed service port, must be a number") 74 return nil, fmt.Errorf("malformed service port: %w", err) 75 } 76 serviceId := fmt.Sprintf("%v-%v-%v", serviceName, host, port) 77 s := &ServiceRegistryServer{ 78 ConsulAddress: consulAddr, 79 ServiceId: serviceId, 80 ServiceName: serviceName, 81 Tag: []string{}, 82 Port: nport, 83 Ip: host, 84 TTL: 300 * time.Second, 85 CheckInterval: 10 * time.Second, 86 } 87 return s, nil 88 } 89 90 // Run will initialize the backend. It must not block, but may run go routines in the background. 91 func (srv *ServiceRegistryServer) Run(ctx context.Context) error { 92 logger := srv.logger(). 93 WithField("service_name", srv.ServiceName). 94 WithField("service_id", srv.ServiceId) 95 logger.Infoln("ConsulRegistry Run") 96 if srv.inShutdown.Load() { 97 logger.Infoln("ConsulRegistry Shutdown") 98 return fmt.Errorf("server closed") 99 } 100 go func() { 101 errors.HandleError(srv.Serve(ctx)) 102 }() 103 return nil 104 } 105 106 func (srv *ServiceRegistryServer) Serve(ctx context.Context) error { 107 logger := srv.logger(). 108 WithField("service_name", srv.ServiceName). 109 WithField("service_id", srv.ServiceId) 110 logger.Infoln("ConsulRegistry Serve") 111 112 if srv.inShutdown.Load() { 113 logger.Infoln("ConsulRegistry Shutdown") 114 return fmt.Errorf("server closed") 115 } 116 117 defer srv.inShutdown.Store(true) 118 ctx, cancel := context.WithCancel(ctx) 119 srv.mu.Lock() 120 srv.cancel = cancel 121 srv.mu.Unlock() 122 123 t := time.NewTicker(time.Second * 30) 124 defer t.Stop() 125 for { 126 select { 127 case <-t.C: 128 logger.Infoln("registering service to consul") 129 err := srv.Register() 130 if err != nil { 131 logger.WithError(err).Errorln("register service failed") 132 continue 133 } 134 srv.logger().Info("register service by consul") 135 136 case <-ctx.Done(): 137 logger.Infoln("unregistering service to consul") 138 err := srv.UnRegister() 139 if err != nil { 140 logger.WithError(err).Errorln("unregister service failed") 141 return err 142 } 143 srv.logger().Info("unregister service by consul") 144 return nil 145 } 146 } 147 } 148 149 func (srv *ServiceRegistryServer) Shutdown() { 150 srv.inShutdown.Store(true) 151 srv.mu.Lock() 152 defer srv.mu.Unlock() 153 if srv.cancel != nil { 154 srv.cancel() 155 } 156 } 157 158 func (srv *ServiceRegistryServer) Register() error { 159 config := api.DefaultConfig() 160 config.Address = srv.ConsulAddress 161 client, err := api.NewClient(config) 162 if err != nil { 163 return err 164 } 165 agent := client.Agent() 166 167 checkUrl := fmt.Sprintf("http://%v:%v/api/%v/v1/health", srv.Ip, srv.Port, srv.ServiceName) 168 169 reg := &api.AgentServiceRegistration{ 170 ID: srv.ServiceId, 171 Name: srv.ServiceName, 172 Tags: srv.Tag, 173 Port: srv.Port, 174 Address: srv.Ip, 175 Check: &api.AgentServiceCheck{ 176 Interval: srv.CheckInterval.String(), 177 HTTP: checkUrl, 178 DeregisterCriticalServiceAfter: srv.TTL.String(), 179 }, 180 } 181 182 return agent.ServiceRegister(reg) 183 } 184 185 func (srv *ServiceRegistryServer) UnRegister() error { 186 config := api.DefaultConfig() 187 config.Address = srv.ConsulAddress 188 client, err := api.NewClient(config) 189 if err != nil { 190 return err 191 } 192 193 return client.Agent().ServiceDeregister(srv.ServiceId) 194 } 195 196 func (srv *ServiceRegistryServer) logger() logrus.FieldLogger { 197 return logrus. 198 WithField("module", "service_registry"). 199 WithField("consul", srv.ConsulAddress). 200 WithField("service_name", srv.ServiceName). 201 WithField("service_id", srv.ServiceId). 202 WithField("ip", srv.Ip).WithField("port", srv.Port) 203 }