remove signal in reader
等待ping时间改为30秒 整理关闭逻辑 增加文档
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"flag"
|
"flag"
|
||||||
"net/http"
|
"net/http"
|
||||||
@@ -8,7 +9,6 @@ import (
|
|||||||
"os/signal"
|
"os/signal"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gorilla/handlers"
|
"github.com/gorilla/handlers"
|
||||||
@@ -24,29 +24,62 @@ const (
|
|||||||
writeWait = 10 * time.Second
|
writeWait = 10 * time.Second
|
||||||
|
|
||||||
// Time allowed to read the next pong message from the peer.
|
// Time allowed to read the next pong message from the peer.
|
||||||
pongWait = 60 * time.Second
|
pongWait = 30 * time.Second
|
||||||
|
|
||||||
// Send pings to peer with this period. Must be less than pongWait.
|
// Send pings to peer with this period. Must be less than pongWait.
|
||||||
pingPeriod = (pongWait * 9) / 10
|
pingPeriod = (pongWait * 9) / 10
|
||||||
|
|
||||||
// Maximum message size allowed from peer.
|
// Maximum message size allowed from peer.
|
||||||
// maxMessageSize = 512
|
maxMessageSize = 512
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
newline = []byte{'\n'}
|
newline = []byte{'\n'}
|
||||||
|
space = []byte{' '}
|
||||||
addr = flag.String("addr", ":9000", "http service address")
|
addr = flag.String("addr", ":9000", "http service address")
|
||||||
bootstrapServers = flag.String("bootstrap-servers", "localhost:9092", "kafka bootstrap servers")
|
bootstrapServers = flag.String("bootstrap-servers", "localhost:9092", "kafka bootstrap servers")
|
||||||
// partition = flag.Int("partition", 0, "kafka tipic partion")
|
// partition = flag.Int("partition", 0, "kafka tipic partion")
|
||||||
wait = flag.Duration("graceful-timeout", time.Second*15, "the duration for which the server gracefully wait for existing connections to finish - e.g. 15s or 1m")
|
wait = flag.Duration("graceful-timeout", time.Second*15, "the duration for which the server gracefully wait for existing connections to finish - e.g. 15s or 1m")
|
||||||
upgrader = websocket.Upgrader{
|
upgrader = websocket.Upgrader{
|
||||||
ReadBufferSize: 1024,
|
ReadBufferSize: 2048,
|
||||||
WriteBufferSize: 1024,
|
WriteBufferSize: 2048,
|
||||||
}
|
}
|
||||||
hubMap = make(map[string]Hub)
|
hubMap = make(map[string]Hub)
|
||||||
router = mux.NewRouter()
|
router = mux.NewRouter()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Hub maintains the kafka reader and set of active clients
|
||||||
|
// broadcasts messages to the clients.
|
||||||
|
type Hub struct {
|
||||||
|
|
||||||
|
// hun running indicator
|
||||||
|
running bool
|
||||||
|
|
||||||
|
// Kafka topic
|
||||||
|
topic string
|
||||||
|
|
||||||
|
// Kakfa reader partition
|
||||||
|
partition int
|
||||||
|
|
||||||
|
// Kafka start offset, -1 = latest, -2 = ealist
|
||||||
|
offset int64
|
||||||
|
|
||||||
|
// Kafka Reader
|
||||||
|
reader *kafka.Reader
|
||||||
|
|
||||||
|
// Registered clients.
|
||||||
|
clients map[*Client]bool
|
||||||
|
|
||||||
|
// Inbound messages from the clients.
|
||||||
|
broadcast chan []byte
|
||||||
|
|
||||||
|
// Register requests from the clients.
|
||||||
|
register chan *Client
|
||||||
|
|
||||||
|
// Unregister requests from clients.
|
||||||
|
unregister chan *Client
|
||||||
|
}
|
||||||
|
|
||||||
// Client is a middleman between the websocket connection and the hub.
|
// Client is a middleman between the websocket connection and the hub.
|
||||||
type Client struct {
|
type Client struct {
|
||||||
hub *Hub
|
hub *Hub
|
||||||
@@ -58,7 +91,9 @@ type Client struct {
|
|||||||
send chan []byte
|
send chan []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
func create(topic string, offset int64, partition int) *kafka.Reader {
|
func createKafkaReader(topic string, offset int64, partition int) *kafka.Reader {
|
||||||
|
|
||||||
|
// Use uuid for kafka group id
|
||||||
group := uuid.NewV4()
|
group := uuid.NewV4()
|
||||||
|
|
||||||
log.WithFields(log.Fields{
|
log.WithFields(log.Fields{
|
||||||
@@ -69,48 +104,54 @@ func create(topic string, offset int64, partition int) *kafka.Reader {
|
|||||||
"offset": offset}).Info("creating kafka client ... ")
|
"offset": offset}).Info("creating kafka client ... ")
|
||||||
|
|
||||||
reader := kafka.NewReader(kafka.ReaderConfig{
|
reader := kafka.NewReader(kafka.ReaderConfig{
|
||||||
Brokers: strings.Split(*bootstrapServers, ","),
|
Brokers: strings.Split(*bootstrapServers, ","), // kafka brokers, split by comma
|
||||||
Topic: topic,
|
Topic: topic, // topic to subscribe
|
||||||
Partition: partition,
|
Partition: partition, // partition from url
|
||||||
MinBytes: 10e1,
|
MinBytes: 10e2, // 1k
|
||||||
MaxBytes: 10e6, // 10MB
|
MaxBytes: 10e6, // 10MB
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// offset from url
|
||||||
|
// -1 = latest
|
||||||
|
// -2 = ealiest
|
||||||
reader.SetOffset(offset)
|
reader.SetOffset(offset)
|
||||||
|
|
||||||
return reader
|
return reader
|
||||||
}
|
}
|
||||||
|
|
||||||
// readPump pumps messages from the websocket connection to the hub.
|
// startClient keep the client connection running
|
||||||
//
|
// response the pong message
|
||||||
// The application runs readPump in a per-connection goroutine. The application
|
|
||||||
// ensures that there is at most one reader on a connection by executing all
|
func (c *Client) startClient() {
|
||||||
// reads from this goroutine.
|
|
||||||
func (c *Client) readPump() {
|
log.WithField("remote", c.conn.RemoteAddr()).Info("client started")
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
c.hub.unregister <- c
|
c.hub.unregister <- c
|
||||||
c.conn.Close()
|
c.conn.Close()
|
||||||
log.WithField("client", c).Warn("exit")
|
log.WithField("client", c).Warn("exit")
|
||||||
}()
|
}()
|
||||||
// c.conn.SetReadLimit(maxMessageSize)
|
|
||||||
|
// response to pong message
|
||||||
|
c.conn.SetReadLimit(maxMessageSize)
|
||||||
c.conn.SetReadDeadline(time.Now().Add(pongWait))
|
c.conn.SetReadDeadline(time.Now().Add(pongWait))
|
||||||
c.conn.SetPongHandler(func(string) error { c.conn.SetReadDeadline(time.Now().Add(pongWait)); return nil })
|
c.conn.SetPongHandler(func(string) error { c.conn.SetReadDeadline(time.Now().Add(pongWait)); return nil })
|
||||||
|
|
||||||
sigchan := make(chan os.Signal, 1)
|
for {
|
||||||
signal.Notify(sigchan, syscall.SIGINT, syscall.SIGTERM)
|
_, message, err := c.conn.ReadMessage()
|
||||||
run := true
|
if err != nil {
|
||||||
for run == true {
|
if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
|
||||||
select {
|
log.WithError(err).Error("read message error")
|
||||||
case sig := <-sigchan:
|
|
||||||
log.WithFields(log.Fields{"signal": sig}).Info(" terminating")
|
|
||||||
run = false
|
|
||||||
for topic, hub := range hubMap {
|
|
||||||
log.WithField("topic", topic).Warn("closing hub")
|
|
||||||
hub.running = false
|
|
||||||
}
|
}
|
||||||
log.Fatal("break ")
|
break
|
||||||
|
}
|
||||||
|
message = bytes.TrimSpace(bytes.Replace(message, newline, space, -1))
|
||||||
|
|
||||||
|
// if client send ping, response pong
|
||||||
|
if string(message) == "ping" {
|
||||||
|
c.send <- []byte("pong")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// writePump pumps messages from the hub to the websocket connection.
|
// writePump pumps messages from the hub to the websocket connection.
|
||||||
@@ -119,6 +160,8 @@ func (c *Client) readPump() {
|
|||||||
// application ensures that there is at most one writer to a connection by
|
// application ensures that there is at most one writer to a connection by
|
||||||
// executing all writes from this goroutine.
|
// executing all writes from this goroutine.
|
||||||
func (c *Client) writePump() {
|
func (c *Client) writePump() {
|
||||||
|
|
||||||
|
// ping message
|
||||||
ticker := time.NewTicker(pingPeriod)
|
ticker := time.NewTicker(pingPeriod)
|
||||||
defer func() {
|
defer func() {
|
||||||
ticker.Stop()
|
ticker.Stop()
|
||||||
@@ -126,11 +169,13 @@ func (c *Client) writePump() {
|
|||||||
c.hub.unregister <- c
|
c.hub.unregister <- c
|
||||||
log.WithField("client", c).Warn("exit")
|
log.WithField("client", c).Warn("exit")
|
||||||
}()
|
}()
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case message, ok := <-c.send:
|
case message, ok := <-c.send:
|
||||||
c.conn.SetWriteDeadline(time.Now().Add(writeWait))
|
c.conn.SetWriteDeadline(time.Now().Add(writeWait))
|
||||||
if !ok {
|
if !ok {
|
||||||
|
log.WithField("client", c).Warn("sending close message")
|
||||||
// The hub closed the channel.
|
// The hub closed the channel.
|
||||||
c.conn.WriteMessage(websocket.CloseMessage, []byte{})
|
c.conn.WriteMessage(websocket.CloseMessage, []byte{})
|
||||||
return
|
return
|
||||||
@@ -138,6 +183,7 @@ func (c *Client) writePump() {
|
|||||||
|
|
||||||
w, err := c.conn.NextWriter(websocket.TextMessage)
|
w, err := c.conn.NextWriter(websocket.TextMessage)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
log.WithError(err).Error("can't get next wirter")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
w.Write(message)
|
w.Write(message)
|
||||||
@@ -150,11 +196,13 @@ func (c *Client) writePump() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := w.Close(); err != nil {
|
if err := w.Close(); err != nil {
|
||||||
|
log.WithError(err).Error("error in close writer")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
case <-ticker.C:
|
case <-ticker.C:
|
||||||
c.conn.SetWriteDeadline(time.Now().Add(writeWait))
|
c.conn.SetWriteDeadline(time.Now().Add(writeWait))
|
||||||
if err := c.conn.WriteMessage(websocket.PingMessage, nil); err != nil {
|
if err := c.conn.WriteMessage(websocket.PingMessage, nil); err != nil {
|
||||||
|
log.WithError(err).Error("can't write ping message")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -194,33 +242,7 @@ func websocketHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
// Allow collection of memory referenced by the caller by doing all work in
|
// Allow collection of memory referenced by the caller by doing all work in
|
||||||
// new goroutines.
|
// new goroutines.
|
||||||
go client.writePump()
|
go client.writePump()
|
||||||
go client.readPump()
|
go client.startClient()
|
||||||
}
|
|
||||||
|
|
||||||
// Hub maintains the set of active clients and broadcasts messages to the
|
|
||||||
// clients.
|
|
||||||
type Hub struct {
|
|
||||||
running bool
|
|
||||||
|
|
||||||
topic string
|
|
||||||
|
|
||||||
partition int
|
|
||||||
|
|
||||||
offset int64
|
|
||||||
|
|
||||||
reader *kafka.Reader
|
|
||||||
|
|
||||||
// Registered clients.
|
|
||||||
clients map[*Client]bool
|
|
||||||
|
|
||||||
// Inbound messages from the clients.
|
|
||||||
broadcast chan []byte
|
|
||||||
|
|
||||||
// Register requests from the clients.
|
|
||||||
register chan *Client
|
|
||||||
|
|
||||||
// Unregister requests from clients.
|
|
||||||
unregister chan *Client
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func newHub(topic string, offset int64, partition int) *Hub {
|
func newHub(topic string, offset int64, partition int) *Hub {
|
||||||
@@ -229,25 +251,29 @@ func newHub(topic string, offset int64, partition int) *Hub {
|
|||||||
topic: topic,
|
topic: topic,
|
||||||
partition: partition,
|
partition: partition,
|
||||||
offset: offset,
|
offset: offset,
|
||||||
reader: create(topic, offset, partition),
|
reader: createKafkaReader(topic, offset, partition),
|
||||||
broadcast: make(chan []byte),
|
broadcast: make(chan []byte),
|
||||||
register: make(chan *Client),
|
register: make(chan *Client),
|
||||||
unregister: make(chan *Client),
|
unregister: make(chan *Client),
|
||||||
clients: make(map[*Client]bool),
|
clients: make(map[*Client]bool),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
func (h *Hub) read() {
|
|
||||||
|
// Read message from kafka broker and broadcast to the clients
|
||||||
|
|
||||||
|
func (h *Hub) readKafka() {
|
||||||
|
|
||||||
log.WithField("hub", *h).Info("start to read")
|
log.WithField("hub", *h).Info("start to read")
|
||||||
for h.running == true {
|
for h.running == true {
|
||||||
|
|
||||||
if h.reader == nil {
|
if h.reader == nil {
|
||||||
log.Info("waiting ... ")
|
log.Info("waiting ... ")
|
||||||
time.Sleep(writeWait)
|
time.Sleep(writeWait)
|
||||||
h.reader = create(h.topic, h.offset, h.partition)
|
h.reader = createKafkaReader(h.topic, h.offset, h.partition)
|
||||||
}
|
}
|
||||||
m, err := h.reader.ReadMessage(context.Background())
|
m, err := h.reader.ReadMessage(context.Background())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.WithFields(log.Fields{"error": err}).Error("read error")
|
log.WithFields(log.Fields{"error": err}).Error("websocket read")
|
||||||
h.reader.Close()
|
h.reader.Close()
|
||||||
h.reader = nil
|
h.reader = nil
|
||||||
} else {
|
} else {
|
||||||
@@ -263,7 +289,7 @@ func (h *Hub) getKey() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h *Hub) run() {
|
func (h *Hub) run() {
|
||||||
go h.read()
|
go h.readKafka()
|
||||||
for h.running == true {
|
for h.running == true {
|
||||||
select {
|
select {
|
||||||
case client := <-h.register:
|
case client := <-h.register:
|
||||||
@@ -273,12 +299,13 @@ func (h *Hub) run() {
|
|||||||
delete(h.clients, client)
|
delete(h.clients, client)
|
||||||
close(client.send)
|
close(client.send)
|
||||||
log.WithField("client", client).Warn("delete client")
|
log.WithField("client", client).Warn("delete client")
|
||||||
|
// Check if
|
||||||
if len(h.clients) < 1 {
|
if len(h.clients) < 1 {
|
||||||
log.Warn("all clients left")
|
log.Warn("all clients left")
|
||||||
h.reader.Close()
|
|
||||||
h.running = false
|
h.running = false
|
||||||
|
// h.reader.Close()
|
||||||
delete(hubMap, h.getKey())
|
delete(hubMap, h.getKey())
|
||||||
log.WithField("hub", h).Info("clear hub")
|
log.WithField("hub", h).Info("remove hub")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case message := <-h.broadcast:
|
case message := <-h.broadcast:
|
||||||
@@ -292,6 +319,20 @@ func (h *Hub) run() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
log.WithField("reader", h.reader).Warn("closing kafka reader ")
|
||||||
|
h.reader.Close()
|
||||||
|
log.WithField("hub", h).Warn("closed")
|
||||||
|
}
|
||||||
|
|
||||||
|
func destroy() {
|
||||||
|
log.Info(" terminating ")
|
||||||
|
// close all hub
|
||||||
|
for key, hub := range hubMap {
|
||||||
|
log.WithField("key", key).Warn("closing hub")
|
||||||
|
hub.running = false
|
||||||
|
// hub.reader.Close()
|
||||||
|
}
|
||||||
|
log.Fatal(" terminated ")
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@@ -329,6 +370,7 @@ func main() {
|
|||||||
defer cancel()
|
defer cancel()
|
||||||
// Doesn't block if no connections, but will otherwise wait
|
// Doesn't block if no connections, but will otherwise wait
|
||||||
// until the timeout deadline.
|
// until the timeout deadline.
|
||||||
|
destroy()
|
||||||
srv.Shutdown(ctx)
|
srv.Shutdown(ctx)
|
||||||
// Optionally, you could run srv.Shutdown in a goroutine and block on
|
// Optionally, you could run srv.Shutdown in a goroutine and block on
|
||||||
// <-ctx.Done() if your application should wait for other services
|
// <-ctx.Done() if your application should wait for other services
|
||||||
|
|||||||
Reference in New Issue
Block a user