-
-
Notifications
You must be signed in to change notification settings - Fork 2.5k
Add TTL for etcd sd #413
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add TTL for etcd sd #413
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,16 +1,25 @@ | ||
| package etcd | ||
|
|
||
| import ( | ||
| "sync" | ||
| "time" | ||
|
|
||
| etcd "github.com/coreos/etcd/client" | ||
|
|
||
| "github.com/go-kit/kit/log" | ||
| ) | ||
|
|
||
| const ( | ||
| minHeartBeatTime = time.Millisecond * 500 | ||
| ) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This constant should either be made private (I think that's preferable) or given a doc comment. |
||
|
|
||
| // Registrar registers service instance liveness information to etcd. | ||
| type Registrar struct { | ||
| client Client | ||
| service Service | ||
| logger log.Logger | ||
| quit chan struct{} | ||
| sync.Mutex | ||
| } | ||
|
|
||
| // Service holds the instance identifying data you want to publish to etcd. Key | ||
|
|
@@ -19,9 +28,35 @@ type Registrar struct { | |
| type Service struct { | ||
| Key string // unique key, e.g. "/service/foobar/1.2.3.4:8080" | ||
| Value string // returned to subscribers, e.g. "http://1.2.3.4:8080" | ||
| TTL *TTLOption | ||
| DeleteOptions *etcd.DeleteOptions | ||
| } | ||
|
|
||
| // TTLOption allow setting a key with a TTL. This option will be used by a loop | ||
| // goroutine which regularly refreshes the lease of the key. | ||
| type TTLOption struct { | ||
| heartbeat time.Duration // e.g. time.Second * 3 | ||
| ttl time.Duration // e.g. time.Second * 10 | ||
| } | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Given that a TTLOption is constructed with a constructor, I don't think either of these fields needs to be exported. Is that correct? |
||
|
|
||
| // NewTTLOption returns a TTLOption that contains proper ttl settings. param | ||
| // heartbeat is used to refresh lease of the key periodically by a loop goroutine, | ||
| // its value should be at least 500ms. param ttl definite the lease of the key, | ||
| // its value should be greater than heartbeat's. | ||
| // e.g. heartbeat: time.Second * 3, ttl: time.Second * 10. | ||
| func NewTTLOption(heartbeat, ttl time.Duration) *TTLOption { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The doc comment here is a little sparse. Could it please describe what each of the parameters are, and some good default values? It would also be good to describe the bounds-checking logic implemented in the function body. |
||
| if heartbeat <= minHeartBeatTime { | ||
| heartbeat = minHeartBeatTime | ||
| } | ||
| if ttl <= heartbeat { | ||
| ttl = heartbeat * 3 | ||
| } | ||
| return &TTLOption{ | ||
| heartbeat: heartbeat, | ||
| ttl: ttl, | ||
| } | ||
| } | ||
|
|
||
| // NewRegistrar returns a etcd Registrar acting on the provided catalog | ||
| // registration (service). | ||
| func NewRegistrar(client Client, service Service, logger log.Logger) *Registrar { | ||
|
|
@@ -43,6 +78,29 @@ func (r *Registrar) Register() { | |
| } else { | ||
| r.logger.Log("action", "register") | ||
| } | ||
| if r.service.TTL != nil { | ||
| go r.loop() | ||
| } | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This says "bail early if there's no configured TTL" — but I think it's much more clear if the logic is inverted, i.e. "if there is a configured TTL, create the quit chan and launch the maintenance loop". |
||
| } | ||
|
|
||
| func (r *Registrar) loop() { | ||
| r.Lock() | ||
| r.quit = make(chan struct{}) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This mutates registrar state, which makes it unsafe for concurrent access from multiple goroutines. I think the registrar needs its internal state protected with a mutex, which should be taken with every public method. |
||
| r.Unlock() | ||
|
|
||
| tick := time.NewTicker(r.service.TTL.heartbeat) | ||
| defer tick.Stop() | ||
|
|
||
| for { | ||
| select { | ||
| case <-r.quit: | ||
| return | ||
| case <-tick.C: | ||
| if err := r.client.Register(r.service); err != nil { | ||
| r.logger.Log("err", err) | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // Deregister implements the sd.Registrar interface. Call it when you want your | ||
|
|
@@ -53,4 +111,10 @@ func (r *Registrar) Deregister() { | |
| } else { | ||
| r.logger.Log("action", "deregister") | ||
| } | ||
| r.Lock() | ||
| defer r.Unlock() | ||
| if r.quit != nil { | ||
| close(r.quit) | ||
| r.quit = nil | ||
| } | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It looks like r.quit should also be reset to nil. |
||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm. What do you think about