├── .gitignore ├── apiserver.go ├── apiserver_test.go ├── bucket.go ├── bucket_test.go ├── docs ├── images │ ├── img.png │ └── img_1.png ├── logging.md ├── pprof │ └── readme.md └── readme.md ├── example ├── demo.go └── http.go ├── option.go ├── pkg ├── bench │ ├── conf.go │ ├── main.go │ └── server.go ├── conn │ ├── connect.go │ ├── connect_gorilla.go │ ├── option.go │ └── option_test.go ├── label │ ├── group.go │ ├── interface.go │ ├── label.go │ ├── label_test.go │ └── manager.go ├── logging │ ├── logger_test.go │ └── zaplog.go ├── mock │ └── conn.go └── print │ └── color.go ├── script └── dockerfile │ └── dockerfile └── sim.go /.gitignore: -------------------------------------------------------------------------------- 1 | ./Log 2 | go.mod 3 | go.sum 4 | cmd 5 | /cmd/ 6 | .idea -------------------------------------------------------------------------------- /apiserver.go: -------------------------------------------------------------------------------- 1 | /* Copyright 2022 steven 2 | * Licensed under the Apache License, Version 2.0 (the "License"); 3 | * you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * http://www.apache.org/licenses/LICENSE-2.0 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, 8 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9 | * See the License for the specific language governing permissions and 10 | * limitations under the License. 11 | */ 12 | 13 | package sim 14 | 15 | import ( 16 | "context" 17 | "errors" 18 | "github.com/mongofs/sim/pkg/conn" 19 | "github.com/mongofs/sim/pkg/logging" 20 | "go.uber.org/atomic" 21 | "go.uber.org/zap" 22 | "net/http" 23 | _ "net/http/pprof" 24 | "sync" 25 | "time" 26 | ) 27 | 28 | const ( 29 | RunStatusRunning = 1 + iota 30 | RunStatusStopped 31 | ) 32 | 33 | type Hooker interface { 34 | // when the client request to make a connection , you need to check the request is legal 35 | // or not ,when the client is legal , we will call the ValidateSuccess , the other side will 36 | // call the ValidateFailed 37 | Validate(token string) error 38 | ValidateFailed(err error, cli conn.Connect) 39 | ValidateSuccess(cli conn.Connect) 40 | 41 | // when client send message by connection , the server need use this function to implement 42 | // logic of receive message 43 | HandleReceive(conn conn.Connect, data []byte) 44 | 45 | // when we create the connection ,we need know the identification , this is you must to implement 46 | IdentificationHook(w http.ResponseWriter, r *http.Request) (string, error) 47 | } 48 | 49 | type sim struct { 50 | // this is the slice of bucket , the bucket implement you can see ./bucket.go 51 | // or github/mongofs/sim/bucket.go . for avoid the big locker , the specific 52 | // implement use hash crc13 , so you don't worry about the matter of performance 53 | bs []bucketInterface 54 | 55 | // this is the counter of online User, there have a goroutine to provide the 56 | // precision of online people 57 | num atomic.Int64 58 | 59 | // this is function to notify all goroutine exit 60 | cancel context.CancelFunc 61 | ctx context.Context 62 | 63 | // this parameter is for judge sim status ( running or not ) 64 | running uint 65 | 66 | // this is the option about sim ,you can see ./option.go or github.com/mongofs/sim/option.go 67 | // you can use the function provided by option.go to set the parameters 68 | opt *Options 69 | 70 | // this is hook your must to implement 71 | hooker Hooker 72 | } 73 | 74 | var ( 75 | stalk *sim 76 | once sync.Once 77 | ) 78 | 79 | var ( 80 | // make sure the resource not exist 81 | errInstanceIsExist = errors.New("the instance is existed ") 82 | // make sure the resource exist 83 | errInstanceIsNotExist = errors.New("the instance is not existed ") 84 | // the server is not Running 85 | errServerIsNotRunning = errors.New("the server is not running ") 86 | errServerIsRunning = errors.New("the server is running ") 87 | 88 | // hook is nil 89 | errHookIsNil = errors.New("hook is nil ") 90 | ) 91 | 92 | func NewSIMServer(hooker Hooker, opts ...OptionFunc) error { 93 | if hooker == nil { 94 | panic("hook is nil ") 95 | } 96 | if stalk != nil { 97 | return errInstanceIsExist 98 | } 99 | ctx, cancel := context.WithCancel(context.Background()) 100 | options := LoadOptions(hooker, opts...) 101 | b := &sim{ 102 | hooker: hooker, 103 | num: atomic.Int64{}, 104 | opt: options, 105 | running: RunStatusStopped, 106 | ctx: ctx, 107 | cancel: cancel, 108 | } 109 | // logger 110 | { 111 | var loggingOps = []logging.OptionFunc{ 112 | logging.SetLevel(logging.InfoLevel), 113 | logging.SetLogName("sim"), 114 | logging.SetLogPath("./log"), 115 | } 116 | logging.InitZapLogger(b.opt.debug, loggingOps...) 117 | } 118 | 119 | b.num.Store(0) 120 | b.initBucket() // init bucket plugin 121 | 122 | stalk = b 123 | return nil 124 | } 125 | 126 | // This function is 127 | func Run() error { 128 | if stalk == nil { 129 | return errInstanceIsNotExist 130 | } 131 | if stalk.running != RunStatusStopped { 132 | // that is mean the sim not run 133 | return errServerIsRunning 134 | } 135 | return stalk.run() 136 | } 137 | 138 | func SendMessage(msg []byte, Users []string) error { 139 | if stalk == nil { 140 | return errInstanceIsNotExist 141 | } 142 | if stalk.running != RunStatusRunning { 143 | // that is mean the sim not run 144 | return errServerIsNotRunning 145 | } 146 | stalk.sendMessage(msg, Users) 147 | return nil 148 | } 149 | 150 | func Upgrade(w http.ResponseWriter, r *http.Request) error { 151 | if stalk == nil { 152 | return errInstanceIsNotExist 153 | } 154 | if stalk.running != RunStatusRunning { 155 | // that is mean the sim not run 156 | return errServerIsNotRunning 157 | } 158 | return stalk.upgrade(w, r) 159 | } 160 | 161 | func Stop() error { 162 | if stalk == nil { 163 | return errInstanceIsNotExist 164 | } 165 | if stalk.running != RunStatusRunning { 166 | // that is mean the sim not run 167 | return errServerIsNotRunning 168 | } 169 | stalk.close() 170 | time.Sleep(200 * time.Millisecond) 171 | return nil 172 | } 173 | 174 | type HandleUpgrade func(w http.ResponseWriter, r *http.Request) error 175 | 176 | func (s *sim) pprof() error { 177 | if s.opt.debug { 178 | if stalk == nil { 179 | return errInstanceIsNotExist 180 | } 181 | if stalk.running != RunStatusRunning { 182 | // that is mean the sim not run 183 | return errServerIsNotRunning 184 | } 185 | go func() { 186 | http.ListenAndServe(s.opt.PProfPort, nil) 187 | }() 188 | } 189 | return nil 190 | } 191 | 192 | func (s *sim) run() error { 193 | if s.opt.ServerDiscover != nil { 194 | s.opt.ServerDiscover.Register() 195 | defer func() { 196 | s.opt.ServerDiscover.Deregister() 197 | }() 198 | } 199 | parallelTask, finishChannel := s.Parallel() 200 | s.running = RunStatusRunning 201 | go func() { 202 | defer func() { 203 | if err := recover(); err != nil { 204 | 205 | } 206 | }() 207 | // monitor the channel and log the out information 208 | for { 209 | select { 210 | case <-finishChannel: 211 | logging.Log.Info("sim : exit -1 ") 212 | return 213 | case finishMark := <-parallelTask: 214 | logging.Log.Info("task finish", zap.String("FINISH_TASK", finishMark)) 215 | } 216 | } 217 | }() 218 | if err := s.pprof(); err != nil { 219 | // todo 220 | panic(err) 221 | } 222 | return nil 223 | } 224 | 225 | func (s *sim) online() int { 226 | return int(s.num.Load()) 227 | } 228 | 229 | // because there is no parallel problem in slice when you read the data 230 | // and there is no any operate action on bucket slice ,so not use locker 231 | func (s *sim) sendMessage(message []byte, users []string) { 232 | if len(users) != 0 { 233 | for _, user := range users { 234 | bs := s.bucket(user) 235 | bs.SendMessage(message, user) 236 | } 237 | return 238 | } 239 | // because there is no parallel problem in slice when you read the data 240 | // and there is no any operate action on bucket slice ,so not use locker 241 | for _, bt := range s.bs { 242 | bt.SendMessage(message) 243 | } 244 | return 245 | } 246 | func (s *sim) upgrade(w http.ResponseWriter, r *http.Request) error { 247 | // this is plugin need the coder to implement it 248 | identification, err := s.hooker.IdentificationHook(w, r) 249 | if err != nil { 250 | return err 251 | } 252 | bs := s.bucket(identification) 253 | 254 | // try to close the same identification device 255 | bs.Offline(identification) 256 | sig := bs.SignalChannel() 257 | cli, err := conn.NewConn(identification, sig, w, r, s.hooker.HandleReceive) 258 | if err != nil { 259 | return err 260 | } 261 | if err := s.hooker.Validate(identification); err != nil { 262 | s.hooker.ValidateFailed(err, cli) 263 | return nil 264 | } else { 265 | s.hooker.ValidateSuccess(cli) 266 | } 267 | if bucketId, userNum, err := bs.Register(cli); err != nil { 268 | cli.Close("register to bucket error ") 269 | return err 270 | } else { 271 | logging.Log.Info("upgrade", zap.String("ID", cli.Identification()), zap.String("BUCKET_ID", bucketId), zap.Int64("BUCKET_ONLINE", userNum)) 272 | return nil 273 | } 274 | } 275 | 276 | func (s *sim) close() error { 277 | s.cancel() 278 | s.running = RunStatusStopped 279 | return nil 280 | } 281 | 282 | func (s *sim) Parallel() (chan string, chan string) { 283 | var prepareParallelFunc = []func(ctx context.Context) (string, error){ 284 | s.monitorBucket, 285 | } 286 | monitor, closeCn := make(chan string), make(chan string) 287 | go func() { 288 | defer func() { 289 | if err := recover(); err != nil { 290 | logging.Log.Error("Parallel", zap.Any("PANIC", err)) 291 | } 292 | }() 293 | wg := sync.WaitGroup{} 294 | for _, v := range prepareParallelFunc { 295 | wg.Add(1) 296 | go func() { 297 | /*defer func() { 298 | if err := recover(); err != nil { 299 | logging.Log.Error("Parallel", zap.Any("PANIC", err)) 300 | } 301 | }()*/ 302 | mark, err := v(s.ctx) 303 | if err != nil { 304 | logging.Log.Error("Parallel task", zap.Error(err)) 305 | return 306 | } 307 | monitor <- mark 308 | wg.Add(-1) 309 | return 310 | }() 311 | } 312 | wg.Wait() 313 | close(closeCn) 314 | }() 315 | return monitor, closeCn 316 | } 317 | -------------------------------------------------------------------------------- /apiserver_test.go: -------------------------------------------------------------------------------- 1 | package sim 2 | 3 | import ( 4 | "github.com/mongofs/sim/pkg/conn" 5 | "net/http" 6 | "testing" 7 | ) 8 | 9 | type hook struct{} 10 | 11 | func (h hook) Offline(conn conn.Connect, ty int) { 12 | panic("implement me") 13 | } 14 | 15 | func (h hook) Validate(token string) error { 16 | panic("implement me") 17 | } 18 | 19 | func (h hook) ValidateFailed(err error, cli conn.Connect) { 20 | panic("implement me") 21 | } 22 | 23 | func (h hook) ValidateSuccess(cli conn.Connect) { 24 | panic("implement me") 25 | } 26 | 27 | func (h hook) HandleReceive(conn conn.Connect, data []byte) { 28 | panic("implement me") 29 | } 30 | 31 | func (h hook) IdentificationHook(w http.ResponseWriter, r *http.Request) (string, error) { 32 | panic("implement me") 33 | } 34 | 35 | func TestNewSIMServer(t *testing.T) { 36 | type args struct { 37 | hooker Hooker 38 | opts []OptionFunc 39 | } 40 | var ( 41 | // test the right way 42 | testHookNotNil = hook{} 43 | ) 44 | 45 | tests := []struct { 46 | name string 47 | args args 48 | wantErr error 49 | want bool 50 | }{ 51 | // test the right way 52 | { 53 | name: "test good situation ", 54 | args: args{ 55 | hooker: testHookNotNil, 56 | opts: []OptionFunc{}, 57 | }, 58 | wantErr: nil, 59 | want: true, 60 | }, 61 | // you can't run this test function along 62 | // create the object repeated 63 | { 64 | name: "create the object repeated ", 65 | args: args{ 66 | hooker: testHookNotNil, 67 | opts: []OptionFunc{}, 68 | }, 69 | wantErr: errInstanceIsExist, 70 | want: true, 71 | }, 72 | } 73 | for _, tt := range tests { 74 | t.Run(tt.name, func(t *testing.T) { 75 | err := NewSIMServer(tt.args.hooker, tt.args.opts...) 76 | if err != tt.wantErr { 77 | t.Errorf("NewSIMServer() error = '%v', wantErr '%v'", err, tt.wantErr) 78 | } 79 | }) 80 | } 81 | } 82 | 83 | // we can't mock the IO of net , that is too complicate ,if we want to test this case 84 | // we just need mock the interface of Connection witch in github.com/mongofs/sim/pkg/conn/connection.go 85 | // 86 | func TestSendMessage(t *testing.T) { 87 | NewSIMServer(&hook{}) 88 | go func() { 89 | if err := Run(); err != nil { 90 | panic(err) 91 | } 92 | }() 93 | type args struct { 94 | msg []byte 95 | Users []string 96 | } 97 | tests := []struct { 98 | name string 99 | args args 100 | wantErr error 101 | want bool 102 | }{ 103 | // instance is not nil , test the single person 104 | { 105 | name: " send message when instance is not nil , test the single person", 106 | args: args{ 107 | msg: []byte(" the instance is nil "), 108 | Users: []string{"steven", "mike", "mikal"}, 109 | }, 110 | wantErr: nil, 111 | }, 112 | 113 | // instance is not nil ,test the all person 114 | { 115 | name: " send message when instance is not nil ,test the all person", 116 | args: args{ 117 | msg: []byte(" the instance is nil "), 118 | Users: []string{"steven", "mike", "mikal"}, 119 | }, 120 | wantErr: nil, 121 | }, 122 | 123 | // instance is not nil ,test the specific person 124 | { 125 | name: " send message when instance is not nil ,test the specific person ", 126 | args: args{ 127 | msg: []byte(" the instance is nil "), 128 | Users: []string{}, 129 | }, 130 | wantErr: nil, 131 | }, 132 | } 133 | 134 | for _, tt := range tests { 135 | t.Run(tt.name, func(t *testing.T) { 136 | err := SendMessage(tt.args.msg, tt.args.Users) 137 | if err != nil && err != tt.wantErr { 138 | t.Errorf("SendMessage() error = %v, wantErr %v", err, tt.wantErr) 139 | } 140 | }) 141 | 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /bucket.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 steven 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * Unless required by applicable law or agreed to in writing, software 8 | * distributed under the License is distributed on an "AS IS" BASIS, 9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | * See the License for the specific language governing permissions and 11 | * limitations under the License. 12 | */ 13 | 14 | package sim 15 | 16 | import ( 17 | "context" 18 | "github.com/pkg/errors" 19 | "go.uber.org/zap" 20 | "strconv" 21 | "sync" 22 | "time" 23 | 24 | "github.com/mongofs/sim/pkg/conn" 25 | "github.com/mongofs/sim/pkg/logging" 26 | "go.uber.org/atomic" 27 | ) 28 | 29 | type bucketInterface interface { 30 | // you can register the user to the bucket set 31 | Register(client conn.Connect) (string, int64, error) 32 | 33 | // you can offline the user in anytime 34 | Offline(identification string) 35 | 36 | // send message to users , if empty of users set ,will send message to all users 37 | SendMessage(message []byte, users ...string /* if no param , it will use broadcast */) 38 | 39 | // return the signal channel , you can use the channel to notify the bucket 40 | // uses is offline , and delete the users' identification 41 | SignalChannel() chan<- string 42 | 43 | // get the number of online user 44 | Count() int 45 | } 46 | 47 | type bucketMessage struct { 48 | origin *[]byte 49 | users *[]string 50 | } 51 | 52 | type bucket struct { 53 | id string 54 | 55 | // Attention: 56 | // this is a switch to control bucket use channel buffer or not , 57 | // more higher performance you can turn it on ,if you want do message ack , 58 | // the better choice is turn it off 59 | bucketChannel chan *bucketMessage 60 | 61 | rw sync.RWMutex 62 | // Element Number 63 | np atomic.Int64 64 | // users set 65 | users map[string]conn.Connect 66 | // Here is different point you need pay attention 67 | // the close signal received by component that is connection 68 | // so we need use channel to inform bucket that user is out of line 69 | closeSig chan string 70 | ctx context.Context 71 | callback func() 72 | opts *Options 73 | } 74 | 75 | 76 | func NewBucket(option *Options, id int ,ctx context.Context) *bucket { 77 | res := &bucket{ 78 | id: "bucket_" + strconv.Itoa(id), 79 | rw: sync.RWMutex{}, 80 | np: atomic.Int64{}, 81 | closeSig: make(chan string), 82 | opts: option, 83 | ctx: ctx, 84 | } 85 | res.users = make(map[string]conn.Connect, res.opts.BucketSize) 86 | if option.BucketBuffer <= 0 { 87 | go res.monitorDelChannel() 88 | go res.keepAlive() 89 | return res 90 | } else { 91 | res.bucketChannel = make(chan *bucketMessage, option.BucketBuffer) 92 | res.consumer(1 << 2) 93 | go res.monitorDelChannel() 94 | go res.keepAlive() 95 | return res 96 | } 97 | return res 98 | } 99 | 100 | // consumer is a goroutine pool to send message parallelly 101 | func (h *bucket) consumer(counter int) { 102 | for i := 0; i < counter; i++ { 103 | go func() { 104 | defer func() { 105 | if err := recover(); err != nil { 106 | logging.Log.Error("consumer", zap.Any("PANIC", err)) 107 | } 108 | }() 109 | for { 110 | select { 111 | case message := <-h.bucketChannel: 112 | BoardCast := len(*message.users)-1 >= 0 113 | if !BoardCast { 114 | h.broadCast(*message.origin, false) 115 | } else { 116 | for _, user := range *message.users { 117 | h.send(*message.origin, user, false) 118 | } 119 | } 120 | case <-h.ctx.Done(): 121 | return 122 | } 123 | } 124 | }() 125 | } 126 | } 127 | 128 | func (h *bucket) Offline(identification string) { 129 | h.rw.Lock() 130 | cli, ok := h.users[identification] 131 | delete(h.users, identification) 132 | h.rw.Unlock() 133 | if ok { 134 | h.opts.Offline(cli, OfflineBySqueezeOut) 135 | time.Sleep(50 * time.Millisecond) 136 | cli.Close("Use the Bucket API : Offline ") 137 | } 138 | } 139 | 140 | func (h *bucket) Register(cli conn.Connect) (string, int64, error) { 141 | if cli == nil { 142 | return "", 0, errors.New("sim : the obj of cli is nil ") 143 | } 144 | h.rw.Lock() 145 | defer h.rw.Unlock() 146 | h.users[cli.Identification()] = cli 147 | h.np.Add(1) 148 | return h.id, h.np.Load(), nil 149 | } 150 | 151 | func (h *bucket) SendMessage(message []byte, users ...string /* if no param , it will use broadcast */) { 152 | if h.bucketChannel != nil { 153 | if len(users)-1 >= 0 { 154 | h.bucketChannel <- &bucketMessage{ 155 | origin: &message, 156 | users: &users, 157 | } 158 | } else { 159 | h.bucketChannel <- &bucketMessage{ 160 | origin: &message, 161 | users: &users, 162 | } 163 | } 164 | return 165 | } 166 | if len(users)-1 >= 0 { 167 | for _, user := range users { 168 | h.send(message, user, false) 169 | } 170 | return 171 | } 172 | h.broadCast(message, false) 173 | } 174 | 175 | func (h *bucket) SignalChannel() chan<- string { 176 | return h.closeSig 177 | } 178 | 179 | func (h *bucket) Count() int { 180 | h.rw.RLock() 181 | defer h.rw.RUnlock() 182 | return len(h.users) 183 | } 184 | 185 | // this function need a lot of logs 186 | func (h *bucket) send(data []byte, token string, Ack bool) { 187 | h.rw.RLock() 188 | cli, ok := h.users[token] 189 | h.rw.RUnlock() 190 | if !ok { // user is not online 191 | return 192 | } else { 193 | err := cli.Send(data) 194 | logging.Log.Error("bucket send", zap.String("ID",cli.Identification()),zap.Error(err)) 195 | } 196 | return 197 | } 198 | 199 | func (h *bucket) broadCast(data []byte, Ack bool) { 200 | h.rw.RLock() 201 | for _, cli := range h.users { 202 | err := cli.Send(data) 203 | if err != nil { 204 | if !errors.Is(err,conn.ErrConnectionIsClosed) { 205 | // if err == errConnectionIsClosed ,there is no need to record 206 | logging.Log.Error("bucket broadCast", zap.String("ID",cli.Identification()),zap.Error(err)) 207 | } 208 | continue 209 | } 210 | } 211 | h.rw.RUnlock() 212 | } 213 | 214 | func (h *bucket) delUser(identification string) { 215 | h.rw.Lock() 216 | defer h.rw.Unlock() 217 | _, ok := h.users[identification] 218 | if !ok { 219 | return 220 | } 221 | delete(h.users, identification) 222 | //更新在线用户数量 223 | h.np.Add(-1) 224 | if h.callback != nil { 225 | h.callback() 226 | } 227 | } 228 | 229 | // To monitor the whole bucket 230 | // run in a goroutine 231 | func (h *bucket) monitorDelChannel() { 232 | defer func() { 233 | if err := recover(); err != nil { 234 | logging.Log.Fatal("monitorDelChannel", zap.Any("PANIC", err)) 235 | } 236 | }() 237 | if h.ctx != nil { 238 | for { 239 | select { 240 | case token := <-h.closeSig: 241 | h.delUser(token) 242 | case <-h.ctx.Done(): 243 | return 244 | } 245 | } 246 | } 247 | for { 248 | select { 249 | case token := <-h.closeSig: 250 | h.delUser(token) 251 | } 252 | } 253 | } 254 | 255 | // To keepAlive the whole bucket 256 | // run in a goroutine 257 | func (h *bucket) keepAlive() { 258 | defer func() { 259 | if err := recover(); err != nil { 260 | logging.Log.Error("keepAlive", zap.Any("PANIC", err)) 261 | } 262 | }() 263 | if h.opts.ClientHeartBeatInterval == 0 { 264 | return 265 | } 266 | for { 267 | var cancelCli []conn.Connect 268 | now := time.Now().Unix() 269 | h.rw.Lock() 270 | for _, cli := range h.users { 271 | inter := now - cli.GetLastHeartBeatTime() 272 | if inter < 2*int64(h.opts.ClientHeartBeatInterval) { 273 | continue 274 | } 275 | cancelCli = append(cancelCli, cli) 276 | } 277 | h.rw.Unlock() 278 | for _, cancel := range cancelCli { 279 | cancel.Close("heartbeat is not arrive ") 280 | } 281 | time.Sleep(10 * time.Second) 282 | } 283 | } 284 | -------------------------------------------------------------------------------- /bucket_test.go: -------------------------------------------------------------------------------- 1 | package sim 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | "time" 7 | 8 | "github.com/mongofs/sim/pkg/conn" 9 | ) 10 | 11 | type MockConn struct { 12 | id string 13 | heartTime int64 14 | } 15 | 16 | func (m MockConn) Identification() string { 17 | return m.id 18 | } 19 | 20 | func (m MockConn) Send(data []byte) error { 21 | fmt.Printf("%v received message : %v\n",m.id ,string(data)) 22 | return nil 23 | } 24 | 25 | func (m MockConn) Close() { 26 | fmt.Printf("%v Close the connection \n",m.id ) 27 | return 28 | } 29 | 30 | func (m MockConn) SetMessageType(messageType conn.MessageType) { 31 | return 32 | } 33 | 34 | func (m MockConn) ReFlushHeartBeatTime() { 35 | m.heartTime = time.Now().Unix() 36 | } 37 | 38 | func (m MockConn) GetLastHeartBeatTime() int64 { 39 | return m.heartTime 40 | } 41 | 42 | 43 | // send message to a person 44 | func TestBucket_SendMessage(t *testing.T) { 45 | 46 | } 47 | -------------------------------------------------------------------------------- /docs/images/img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mongofs/sim/67e2e5a2194a61c5631ec6ffe5a6434820abbacd/docs/images/img.png -------------------------------------------------------------------------------- /docs/images/img_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mongofs/sim/67e2e5a2194a61c5631ec6ffe5a6434820abbacd/docs/images/img_1.png -------------------------------------------------------------------------------- /docs/logging.md: -------------------------------------------------------------------------------- 1 | ## logging SCOPE 2 | 3 | the form to show the code you need check logs up 4 | 5 | ### english 6 | 7 | | key word | meaning | 类型| 8 | | ---- | ---- | ---- | 9 | | BUCKET_NUMBER | bucket number | int| 10 | | BUCKET_SIZE | bucket size | int| 11 | | BUCKET_PARALLEL | bucket goroutine pool| int| 12 | | BUCKET_ONLINE | bucket online | int| 13 | | BUCKET_ID | bucket id |int| 14 | | MONITOR_ONLINE_INTERVAL | print online interval |int| 15 | | ERROR | error | string | 16 | | ONLINE | print online users number |int| 17 | | PPROF | pprof url |string| 18 | | ID | user identification |string| 19 | | FINISH_TASK | finish task | string| 20 | | PANIC | panic | string| 21 | | SPEND_TIME | the spend time of write message to connection |string| 22 | | WEAK_NET | users' connection is in weak status |string| 23 | | OFFLINE_CAUSE | connection closed by CAUSE | string| 24 | |COUNT_LOSE_CONTENT|The data packets lost during the period between the last printing and the current printing |int64| 25 | |COUNT_CONTENT|The total data packets during the period between the last printing and the current printing|int64| 26 | |COUNT_CONTENT_LEN|The total packet length during the period between the last printing and the current printing|int64| 27 | 28 | ### chinese 29 | 30 | | key word | meaning | 类型| 31 | | ---- | ---- | ---- | 32 | | BUCKET_NUMBER | 配置的Bucket数量 | int| 33 | | BUCKET_SIZE | 配置的Bucket的尺寸| int| 34 | | BUCKET_PARALLEL | bucket并发线程数量| int| 35 | | BUCKET_ONLINE | 当前bucket在线人数 | int| 36 | | BUCKET_ID | 桶ID |int| 37 | | MONITOR_ONLINE_INTERVAL | 监控打印的时间间隔 |int| 38 | | ERROR | 错误 | string | 39 | | ONLINE | 当前所有用户在线数量 |int| 40 | | PPROF | pprof访问地址 |string| 41 | | ID | 用户ID |string| 42 | | FINISH_TASK | 结束的异步任务 | string| 43 | | PANIC | 程序出现panic被捕获 | string| 44 | | SPEND_TIME | 用户下推失败,出现丢包 |string| 45 | | WEAK_NET | 用户链接写入时间过长:最终还是写入成功 |string| 46 | | OFFLINE_CAUSE | 用户连接断线的原因 | string| 47 | |COUNT_LOSE_CONTENT|上次打印丢包到这次打印丢包期间丢包总数量|int64| 48 | |COUNT_CONTENT|从上次打印下推消息到这次下推消息总数量(包数量)|int64| 49 | |COUNT_CONTENT_LEN|从上一次下推到现在总的下推消息的总长度|int64| 50 | 51 | ### monitor 52 | 53 | It is recommended to aggregate and monitor the following 54 | fields, usually there are index requirements for the following fields 55 | 56 | - Panic 57 | - Error 58 | - Online 59 | - Bucket 60 | - CountLoseContent 61 | - CountContent 62 | - CountContentLen 63 | 64 | 65 | 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /docs/pprof/readme.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mongofs/sim/67e2e5a2194a61c5631ec6ffe5a6434820abbacd/docs/pprof/readme.md -------------------------------------------------------------------------------- /docs/readme.md: -------------------------------------------------------------------------------- 1 | # SIM 2 | 对sim的思考,之前开源过一个项目是im,在我的github主页还能看到,其实长链接项目无非是两套重逻辑,一套是基于tcp的长链接 3 | 或者是基于websocket的长链接,本质都是连接,但是websocket可以直接利用http+tls的便车,所以如果自己实现一套tcp的长 4 | 连接其实不是特别有必要的话,我是不建议的。但是可以从网络库的角度来切入,比如原生包的net实现是BIO,也就是阻塞等待IO,那么 5 | 我们可以切换另外一套net库实现我们的底层接入逻辑,比如说基于NIO的非阻塞等待IO,目前开源的gnet、netpoll都是不错的选择, 6 | 后面为了练手也好,解耦也好,我还是有打算写一套自己的netpoll,名称叫snetpoll。 7 | 8 | ## 目录 9 | - [整体设计](##整体设计) 10 | - [用户连接存放](####用户连接存放) 11 | - [label设计](####label设计) 12 | - [快速开始](##快速开始) 13 | - [依赖安装](####依赖安装) 14 | - [构建](####构建) 15 | - [客户端连接](####客户端连接) 16 | - [rpc管理端](####rpc管理) 17 | - [API](##API) 18 | 19 | ### 整体架构 20 | sim组件是一个比较小巧的组件,一共4000多行代码,硬编码成分在2000多行左右, 21 | 22 | #### 用户连接存放 23 | 另外一套重逻辑就是查找了,当我们不论基于tcp还是websocket始终在查找逻辑看来就是一个接口,可以写,可以读,可以关闭。但是 24 | 这个话题又简单又复杂,如果单推,我们提倡的是O1时间复杂度下推,其实golang 原生的Map就已经满足需求,但是由于在业务场景中 25 | 不断有人加入,那么就不断有写,此时就是一个并发读写,那么就需要加锁,加锁可以保障数据安全,但是又会引入新的问题:锁竞争的 26 | 问题,一般出现锁问题就是减小锁的粒度,比如分段锁,在具体实现上可以参照buckt实现。 27 | ![img](./images/img.png) 28 | 29 | #### label设计 30 | 后续公司版本迭代,每个版本需要不同的内容,比如v1版本的app推送需要是json,到后面v2版本需要proto,到v3版本可能需要一个 31 | 新字段,所以需要支持多版本内容推送,在业务逻辑层将内容组织好,然后一次性推送给我去筛选,所以要支持基于tag来进行推送内容, 32 | 目前就基于这个需求开发wli这个部分,简称websocket label interface。当然这个也是非常常用的一个功能,比如说分组,或者 33 | 房间,都可以基于这个进行开发,但是每个组会单独的存放,比如一个用户有很多个tag: version_v1,room_2018,room_2019,那么 34 | 就会出现一个问题,比如某个分组有10000人,所以这个分组还需要进行人数的控制,如果超过某个阈值,就需要进行新的分组迁移,比如 35 | 我设置一个分组人数limit为1000,放在groupA中,此时又写入一个用户tag,此时触发分离用户B,肯定是需要重平衡一下的,比如: 36 | groupA就是501人,groupB就是500人,分组规则可以通过hash取模,但是如果分组规模较大,那么此时肯定是比较耗时的,是一个On操作, 37 | 所以我建议你分组尽量不要使用超级大组,相同label的group可以通过链表进行串联, 38 | ![img](./images/img_1.png) 39 | 40 | 41 | 42 | wli 出现后还会出现一个这样的问题:我需要给room_2018 且 version_v2的所有用户发送信息,这就是一个求交集的方案了,如果纯纯的 43 | 求交集那么就是O(n * m)的复杂度了,这里我取巧了一下,将用户的标签放到用户的结构体中,那么我只需要进行遍历较小的那个集合就可以获取 44 | 到所有的交集信息了。时间复杂度也变成了O(n)n是较小的集合。 45 | ### 快速开始 46 | 值得注意的是sim项目并不是安装即用的项目,需要部分硬编码成分在里面,这就意味着我们需要将sim以依赖的方式引入,同时sim也依赖 47 | 其他包,所以需要保证你的网络是没有问题的。 48 | #### 依赖安装 49 | 1. 需要访问到外网,存在部分依赖用国内网络很难拉下来 50 | 2. 操作系统: 推荐Ubuntu 51 | 52 | #### 构建 53 | 单独启动一个项目,将下面的内容拷贝到你的可执行程序中,引入涉及到的依赖,就可以直接run起来了。 54 | ```` 55 | type MockReceive struct{} 56 | 57 | func (m MockReceive) Handle(conn Connect, data []byte) { 58 | conn.ReFlushHeartBeatTime() 59 | } 60 | 61 | type MockValidate struct{} 62 | 63 | func (m MockValidate) Validate(token string) error { 64 | //fmt.Println("token successs ") 65 | return nil 66 | } 67 | 68 | func (m MockValidate) ValidateFailed(err error, cli Client) { 69 | panic("implement me") 70 | } 71 | 72 | func (m MockValidate) ValidateSuccess(cli Client) { 73 | return 74 | } 75 | 76 | type mockDiscovery struct { 77 | 78 | } 79 | 80 | func (m mockDiscovery) Register() { 81 | logging.Infof("sim : start Discover.Register success") 82 | } 83 | 84 | func (m mockDiscovery) Deregister() { 85 | logging.Infof("sim : start Discover.Deregister success") 86 | } 87 | 88 | 89 | func main (){ 90 | go http.ListenAndServe("0.0.0.0:6061", nil) // 开启端口监听 91 | optionfunc := []OptionFunc{ 92 | WithServerRpcPort(":8089"), 93 | WithServerHttpPort(":8081"), 94 | WithLoggerLevel(logging.DebugLevel), 95 | WithLabelManager(), 96 | WithDiscover(mockDiscovery{}), 97 | WithClientHeartBeatInterval(500), 98 | } 99 | Run(&MockValidate{}, &MockReceive{}, optionfunc...) 100 | } 101 | ```` 102 | 这段代码就可以让你快速得开启你的IM服务组件,但是建议你还是不要这么在生产环境中写这样的代码,至少你得完善一下validate、receive 103 | 你需要和客户端好好沟通并设计一套完整的通讯文档,如果说你想支持分布式部署的话,那么你就需要在option选项中选择WithDiscover,把 104 | 你自己的注册与发现实现一遍。 105 | 106 | #### 客户端连接 107 | 我们的最终目的是通过客户端进行链接,和客户端进行保持一个不断的连接,实现主动的下推消息,那么im的连接方式可以通过下面的方式进行连接 108 | 测试, 在这个脚本网站: http://coolaf.com/tool/chattest,测试下面这个连接 109 | ```` 110 | ws://127.0.0.1:$port/$validateRouter?$validatekey=123abc 111 | ```` 112 | 注意: $开头的都可以通过option进行设置,也需要根据自己的设置进行填写 113 | 114 | #### rpc管理端 115 | 在这个测试中可以使用如下的内容进行rpc测试联调,当然也是支持http方式测试联调,主要关注点在于api目录下的sim.pb.go 中clientServer 116 | ```` 117 | const ( 118 | Address = "ws://127.0.0.1:8080/conn" 119 | DefaultRpcAddress = "127.0.0.1:8081" 120 | ) 121 | 122 | var ( 123 | cli = Client() 124 | ctx = context.Background() 125 | ) 126 | 127 | func Client ()im.BasicClient{ 128 | return api.NewBasicClient(conn) 129 | } 130 | 131 | var conn,_ = grpc.Dial(DefaultRpcAddress,grpc.WithInsecure()) 132 | ```` 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | -------------------------------------------------------------------------------- /example/demo.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "go.uber.org/zap" 6 | "net/http" 7 | "os" 8 | "os/signal" 9 | "syscall" 10 | "time" 11 | 12 | "github.com/mongofs/sim" 13 | "github.com/mongofs/sim/pkg/conn" 14 | "github.com/mongofs/sim/pkg/logging" 15 | ) 16 | 17 | type talk struct { 18 | http *httpserver 19 | } 20 | 21 | type hooker struct { 22 | } 23 | 24 | func (h hooker) Validate(token string) error { 25 | return nil 26 | } 27 | 28 | func (h hooker) Offline(cli conn.Connect, ty int) { 29 | if ty == sim.OfflineBySqueezeOut { 30 | cli.Send([]byte("您已经被挤掉了")) 31 | } 32 | } 33 | 34 | func (h hooker) ValidateFailed(err error, cli conn.Connect) { 35 | panic("implement me") 36 | } 37 | 38 | func (h hooker) ValidateSuccess(cli conn.Connect) { 39 | return 40 | } 41 | 42 | func (h hooker) HandleReceive(conn conn.Connect, data []byte) { 43 | 44 | conn.Send([]byte("你好呀")) 45 | 46 | conn.ReFlushHeartBeatTime() 47 | //fmt.Println(string(data)) 48 | return 49 | } 50 | 51 | func (h hooker) IdentificationHook(w http.ResponseWriter, r *http.Request) (string, error) { 52 | return r.Form.Get("token"), nil 53 | 54 | } 55 | func test() { 56 | fmt.Println(len([]byte("1234567890"))) // 10 57 | } 58 | 59 | func main() { 60 | sim.NewSIMServer(hooker{}, sim.WithServerDebug()) 61 | tk := &talk{http: NewHTTP()} 62 | if err := sim.Run(); err != nil { 63 | panic(err) 64 | } 65 | go func() { 66 | if err := tk.http.Run(sim.Upgrade); err != nil { 67 | panic(err) 68 | } 69 | }() 70 | go func() { 71 | for { 72 | time.Sleep(5000 * time.Millisecond) 73 | err := sim.SendMessage([]byte("1234567890"), []string{}) 74 | if err != nil { 75 | fmt.Println(err) 76 | } 77 | //fmt.Println("一个循环") 78 | } 79 | }() 80 | sig := make(chan os.Signal, 1) 81 | 82 | // Free up resources by monitoring server interrupt to instead of killing the process id 83 | // graceful shutdown 84 | signal.Notify(sig, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT) 85 | select { 86 | case sig := <-sig: 87 | logging.Log.Info("main", zap.Any("SIG", sig)) 88 | if err := sim.Stop(); err != nil { 89 | panic(err) 90 | } 91 | break 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /example/http.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "github.com/mongofs/sim" 7 | "github.com/mongofs/sim/pkg/logging" 8 | "go.uber.org/zap" 9 | "net" 10 | "net/http" 11 | ) 12 | 13 | type httpserver struct { 14 | http *http.ServeMux 15 | port string 16 | } 17 | 18 | type Response struct { 19 | w http.ResponseWriter 20 | Desc string `json:"desc"` 21 | Status int `json:"status"` 22 | Data interface{} `json:"data"` 23 | } 24 | 25 | func NewHTTP() *httpserver { 26 | return &httpserver{ 27 | http: &http.ServeMux{}, 28 | port: ":3306", 29 | } 30 | } 31 | 32 | func (r *Response) SendJson() (int, error) { 33 | resp, _ := json.Marshal(r) 34 | r.w.Header().Set("Content-Type", "application/json") 35 | r.w.WriteHeader(r.Status) 36 | return r.w.Write(resp) 37 | } 38 | 39 | func (s *httpserver) Run(upgrade sim.HandleUpgrade) error { 40 | s.http.HandleFunc("/conn", func(writer http.ResponseWriter, r *http.Request) { 41 | //now := time.Now() 42 | defer func() { 43 | //escape := time.Since(now) 44 | //logging.Infof("sim : %s create %s cost %v url is %v ", r.RemoteAddr, r.Method, escape, r.URL) 45 | }() 46 | res := &Response{ 47 | w: writer, 48 | Data: nil, 49 | Status: 200, 50 | } 51 | if r.ParseForm() != nil { 52 | res.Status = 400 53 | res.Data = "connection is bad " 54 | res.SendJson() 55 | return 56 | } 57 | 58 | token := r.Form.Get("token") 59 | if token == "" { 60 | res.Status = 400 61 | res.Data = "token validate error" 62 | res.SendJson() 63 | } 64 | 65 | // upgrade connection 66 | 67 | if err := upgrade(writer,r);err != nil{ 68 | res.Status = 400 69 | res.Data = "upgrade failed" 70 | res.SendJson() 71 | } 72 | }) 73 | return s.run(context.Background()) 74 | } 75 | 76 | func (s *httpserver) run(ctx context.Context) error { 77 | listen, err := net.Listen("tcp", s.port) 78 | if err !=nil { 79 | return err 80 | } 81 | defer listen.Close() 82 | if err != nil { 83 | logging.Log.Info("run" ,zap.Error(err)) 84 | return err 85 | } 86 | logging.Log.Info("run", zap.String("PORT",s.port)) 87 | if err := http.Serve(listen, s.http); err != nil { 88 | return err 89 | } 90 | return nil 91 | } 92 | -------------------------------------------------------------------------------- /option.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 steven 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * Unless required by applicable law or agreed to in writing, software 8 | * distributed under the License is distributed on an "AS IS" BASIS, 9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | * See the License for the specific language governing permissions and 11 | * limitations under the License. 12 | */ 13 | 14 | package sim 15 | 16 | import ( 17 | "github.com/mongofs/sim/pkg/conn" 18 | "github.com/mongofs/sim/pkg/logging" 19 | ) 20 | 21 | const ( 22 | DefaultClientHeartBeatInterval = 120 23 | DefaultBucketSize = 1 << 9 // 512 24 | DefaultServerBucketNumber = 1 << 4 // 16 25 | DefaultBucketBuffer = 1 << 5 // 32 26 | DefaultBucketSendMessageGoroutine = 1 << 2 // 4 27 | 28 | // to show some pprof 29 | DefaultPProfPort = ":6060" 30 | ) 31 | 32 | const ( 33 | OfflineBySqueezeOut = iota + 1 34 | OfflineByLogic 35 | ) 36 | 37 | type Options struct { 38 | ClientHeartBeatInterval int // ClientHeartBeatInterval 39 | Connection *conn.Option 40 | BucketSize int // BucketSize bucket size 41 | BucketBuffer int // BucketBuffer the buffer of cache bucket data , it will lose data when service dead 42 | BucketSendMessageGoroutine int // BucketSendMessageGoroutine bucket goroutine witch use to send data 43 | ServerBucketNumber int // ServerBucketNumber 44 | LogPath string // LogPath 45 | LogLevel logging.Level // LogLevel 46 | PProfPort string // PProfPort 47 | 48 | // when user Offline by some reason , you must to know that , so there may have some operate 49 | // you need to do , so you can implement this function , but i suggest you don't 50 | Offline func(conn conn.Connect, ty int) 51 | 52 | // ====================================== Option for hard code =============================== 53 | ServerDiscover Discover // ServerDiscover 54 | debug bool 55 | } 56 | 57 | type Discover interface { 58 | Register() 59 | Deregister() 60 | } 61 | 62 | func DefaultOption() *Options { 63 | return &Options{ 64 | // client 65 | ClientHeartBeatInterval: DefaultClientHeartBeatInterval, 66 | Connection: conn.DefaultOption(), 67 | // server 68 | BucketSize: DefaultBucketSize, 69 | BucketBuffer: DefaultBucketBuffer, 70 | BucketSendMessageGoroutine: DefaultBucketSendMessageGoroutine, 71 | ServerBucketNumber: DefaultServerBucketNumber, 72 | PProfPort: DefaultPProfPort, 73 | 74 | debug: false, 75 | } 76 | } 77 | 78 | func LoadOptions(hooker Hooker, Opt ...OptionFunc) *Options { 79 | opt := DefaultOption() 80 | for _, o := range Opt { 81 | o(opt) 82 | } 83 | return opt 84 | } 85 | 86 | type OptionFunc func(b *Options) 87 | 88 | func WithServerDebug() OptionFunc { 89 | return func(b *Options) { 90 | b.debug = true 91 | } 92 | } 93 | 94 | func WithPprofPort(pprof string) OptionFunc { 95 | return func(b *Options) { 96 | b.PProfPort = pprof 97 | } 98 | } 99 | 100 | func WithServerBucketNumber(ServerBucketNumber int) OptionFunc { 101 | return func(b *Options) { 102 | b.ServerBucketNumber = ServerBucketNumber 103 | } 104 | } 105 | 106 | func WithClientHeartBeatInterval(ClientHeartBeatInterval int) OptionFunc { 107 | return func(b *Options) { 108 | b.ClientHeartBeatInterval = ClientHeartBeatInterval 109 | } 110 | } 111 | 112 | func WithConnectionOption(option *conn.Option) OptionFunc { 113 | return func(b *Options) { 114 | b.Connection = option 115 | } 116 | } 117 | 118 | func WithBucketSize(BucketSize int) OptionFunc { 119 | return func(b *Options) { 120 | b.BucketSize = BucketSize 121 | } 122 | } 123 | 124 | func WithBucketBuffer(BucketBuffer int) OptionFunc { 125 | return func(b *Options) { 126 | b.BucketBuffer = BucketBuffer 127 | } 128 | } 129 | 130 | func WithLoggerLevel(level logging.Level) OptionFunc { 131 | return func(b *Options) { 132 | b.LogLevel = level 133 | } 134 | } 135 | 136 | func WithDiscover(discover Discover) OptionFunc { 137 | return func(opts *Options) { 138 | opts.ServerDiscover = discover 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /pkg/bench/conf.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 steven 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * Unless required by applicable law or agreed to in writing, software 8 | * distributed under the License is distributed on an "AS IS" BASIS, 9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | * See the License for the specific language governing permissions and 11 | * limitations under the License. 12 | */ 13 | package main 14 | 15 | import "github.com/spf13/pflag" 16 | 17 | var ( 18 | concurrency int // 并发请求数量 ,比如 -c 100 ,代表每秒创建100个链接 19 | number int // 总的请求数量 ,比如 -n 10000,代表总共建立链接10000个 20 | keepTime int // 总的在线时长,比如 -k 100 ,代表在线时间为100秒,100秒后就会释放 21 | heartBeat int // 是否上报心跳,如果设置存在值,就会按照默认的结构体进行{test :1 } 进行心跳发送 22 | monitorPrint int // 设置当前状态打印间隔,默认10s 23 | host string // 设置对应的Url ,比如 -h 127.0.0.1:8080,目前展示不能支持配置链接token,后续会加上 24 | identification string // 设置对应的identification的key值,比如 identification =token,表示你的token 才是唯一标识 25 | ) 26 | 27 | // 整体流程: connection -c - 28 | func init() { 29 | pflag.IntVarP(&concurrency, "concurrency", "c", 100, "并发请求数量 ,比如 -c 100 ,代表每秒创建100个链接") 30 | pflag.IntVarP(&number, "number", "n", 200, "总的请求数量 ,比如 -n 10000,代表总共建立链接10000个") 31 | pflag.IntVarP(&heartBeat, "heartBeat", "b", 50, "是否上报心跳,如果设置存在值,就会按照默认的结构体{test :1}按设定时间进行心跳发送 ") 32 | pflag.IntVarP(&keepTime, "keepTime", "k", 0, "总的在线时长,比如 -k 100s ,代表在线时间为100秒,100秒后就会释放,为0不释放") 33 | pflag.IntVarP(&monitorPrint, "monitor", "m", 10, "设置当前状态打印间隔,默认10s") 34 | pflag.StringVarP(&host, "host", "h", "ws://127.0.0.1:3306/conn", "设置对应的Url") 35 | pflag.StringVarP(&identification, "identification", "i", "token", "标识,比如 identification =token,表示你的token 才是唯一标识") 36 | pflag.Parse() 37 | } 38 | 39 | type Config struct { 40 | Concurrency int 41 | Number int 42 | KeepTime int 43 | Host string 44 | HeartBeat int 45 | Monitor int 46 | Identification string 47 | } 48 | 49 | // InitConfig 实例化 50 | func InitConfig() *Config { 51 | return &Config{ 52 | Concurrency: concurrency, 53 | Number: number, 54 | KeepTime: keepTime, 55 | Host: host, 56 | HeartBeat: heartBeat, 57 | Monitor: monitorPrint, 58 | Identification: identification, 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /pkg/bench/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "time" 5 | "fmt" 6 | ) 7 | 8 | func main() { 9 | config := InitConfig() 10 | RunServer(config) 11 | if config.KeepTime == 0 { 12 | select {} 13 | }else { 14 | time.Sleep(time.Duration(config.KeepTime) * time.Second) 15 | } 16 | fmt.Println("exit process") 17 | } 18 | 19 | 20 | // RunServer 启动服务 21 | func RunServer(cof *Config) { 22 | sb := NewBench(cof) 23 | sb.Run() 24 | } -------------------------------------------------------------------------------- /pkg/bench/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | print2 "github.com/mongofs/sim/pkg/print" 6 | 7 | "github.com/gorilla/websocket" 8 | "go.uber.org/atomic" 9 | "math/rand" 10 | "time" 11 | ) 12 | 13 | var ( 14 | r = rand.New(rand.NewSource(time.Now().Unix())) 15 | ) 16 | 17 | const ( 18 | StageOFNewConnection = iota + 1 19 | StageOfFinishCreateConnection 20 | ) 21 | 22 | type Bench struct { 23 | 24 | // metric count 25 | success atomic.Int32 26 | fail atomic.Int32 27 | online atomic.Int32 28 | retry atomic.Int32 29 | 30 | // message count 31 | singleMessageCount atomic.Int64 32 | allUserMessageCount atomic.Int64 33 | heartBeat int 34 | stage int 35 | 36 | oToken string //output token 37 | config *Config 38 | 39 | closeMonitor chan string 40 | } 41 | 42 | var url string 43 | 44 | func NewBench(conf *Config) *Bench { 45 | return &Bench{ 46 | success: atomic.Int32{}, 47 | fail: atomic.Int32{}, 48 | online: atomic.Int32{}, 49 | retry: atomic.Int32{}, 50 | singleMessageCount: atomic.Int64{}, 51 | allUserMessageCount: atomic.Int64{}, 52 | heartBeat: 180, 53 | stage: StageOFNewConnection, 54 | oToken: "BaseToken", 55 | config: conf, 56 | closeMonitor: make(chan string, 10), 57 | } 58 | } 59 | 60 | func (s *Bench) Run() { 61 | tokens := s.getValidateKey() 62 | url = fmt.Sprintf("%s?%s=", s.config.Host, s.config.Identification) 63 | print2.PrintWithColor(fmt.Sprintf("CONFIG-PRINT-URL : url is '%s'", url), print2.FgGreen) 64 | print2.PrintWithColor(fmt.Sprintf("CONFIG-PRINT-HEARTBEAT : heartbeat interval is '%vs' ", s.heartBeat), print2.FgGreen) 65 | print2.PrintWithColor(fmt.Sprintf("CONFIG-PRINT-KEEPTIME : keep time is '%vs' ", s.heartBeat), print2.FgGreen) 66 | print2.PrintWithColor(fmt.Sprintf("CONFIG_PRINT_ONLINE_TEST : '%s' BaseToken is online", identification), print2.FgGreen) 67 | print2.PrintWithColorAndSpace(fmt.Sprintf("=====================================CONFIG_IS_UP ================================"), print2.FgYellow, 1, 1) 68 | 69 | // create the based connection 70 | go func() { 71 | if err := s.CreateClient(s.oToken); err != nil { 72 | panic(err) 73 | } 74 | }() 75 | s.Batch(tokens) 76 | s.monitor() 77 | } 78 | 79 | var limiter = time.NewTicker(50 * time.Microsecond) 80 | 81 | func (s *Bench) Batch(tokens []string) { 82 | for k, v := range tokens { 83 | if k%s.config.Concurrency == 0 && k != 0 { 84 | time.Sleep(1000 * time.Millisecond) 85 | if s.success.Load() != 0 { 86 | print2.PrintWithColor(fmt.Sprintf("Current_Status: Online %v ,Fail %v ", s.success.Load(), s.fail.Load()), print2.FgGreen) 87 | } 88 | } 89 | go s.CreateClient(v) 90 | } 91 | 92 | // because the interval of for loop exist the sleep 1 millisecond 93 | time.Sleep(1001 * time.Millisecond) 94 | print2.PrintWithColor(fmt.Sprintf("Current_Status: Online %v ,Fail %v ", s.success.Load(), s.fail.Load()), print2.FgGreen) 95 | 96 | s.stage = StageOfFinishCreateConnection 97 | print2.PrintWithColorAndSpace(fmt.Sprintf(" =====================================Created_Connection_Situation_IS_UP==========================================="), print2.FgYellow, 1, 1) 98 | } 99 | 100 | func (s *Bench) CreateClient(identification string) error { 101 | <-limiter.C 102 | dialer := websocket.Dialer{} 103 | conn, _, err := dialer.Dial(url+identification, nil) 104 | if err != nil { 105 | s.fail.Inc() 106 | fmt.Printf("error occurs during runtime id : %v, url : %s ,err :%s\r\n", "ddd", url, err.Error()) 107 | return err 108 | } else { 109 | s.success.Inc() 110 | s.online.Inc() 111 | } 112 | defer conn.Close() 113 | 114 | go func() { 115 | for { 116 | time.Sleep(time.Duration(s.heartBeat) * time.Second) 117 | conn.WriteJSON("{test: 1}") 118 | } 119 | }() 120 | 121 | defer func() { 122 | if err := recover(); err != nil { 123 | fmt.Printf("panic Occre : %v \n", err) 124 | } 125 | }() 126 | 127 | for { 128 | messageType, messageData, err := conn.ReadMessage() 129 | if err != nil { 130 | s.closeMonitor <- identification 131 | fmt.Printf("Connection_Read_Err : %v\r\n", err) 132 | } 133 | s.allUserMessageCount.Inc() 134 | if identification == s.oToken { 135 | switch messageType { 136 | case websocket.TextMessage: 137 | if s.stage == StageOfFinishCreateConnection { 138 | s.singleMessageCount.Inc() 139 | print2.PrintWithColor(fmt.Sprintf("BaseToken_Receive : %v", string(messageData)), print2.FgBlue) 140 | } 141 | case websocket.BinaryMessage: 142 | default: 143 | } 144 | } 145 | } 146 | return nil 147 | } 148 | 149 | func (s *Bench) monitor() { 150 | go func() { 151 | t := time.NewTicker(time.Duration(s.config.Monitor) * time.Second) 152 | for { 153 | select { 154 | case <-t.C: 155 | str := fmt.Sprintf("Current_Status: Online_%v ,Retry_%v, Msg_Count_%v ,All_Msg_Count %v", 156 | s.online.Load(), s.retry.Load(), s.singleMessageCount.Load(), s.allUserMessageCount.Load()) 157 | print2.PrintWithColor(str, print2.FgGreen) 158 | case token := <-s.closeMonitor: 159 | fmt.Printf("Client_Offline: %v is closed \r\n", token) 160 | s.retry.Inc() 161 | s.online.Dec() 162 | // go s.CreateClient(s.config.Identification) 163 | } 164 | } 165 | }() 166 | } 167 | 168 | func (s *Bench) getValidateKey() []string { 169 | var tokens []string 170 | for i := 0; i < s.config.Number; i++ { 171 | tokens = append(tokens, RandString(20)) 172 | } 173 | return tokens 174 | } 175 | 176 | func RandString(len int) string { 177 | bytes := make([]byte, len) 178 | for i := 0; i < len; i++ { 179 | b := r.Intn(26) + 65 180 | bytes[i] = byte(b) 181 | } 182 | return string(bytes) 183 | } 184 | -------------------------------------------------------------------------------- /pkg/conn/connect.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 steven 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * Unless required by applicable law or agreed to in writing, software 8 | * distributed under the License is distributed on an "AS IS" BASIS, 9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | * See the License for the specific language governing permissions and 11 | * limitations under the License. 12 | */ 13 | 14 | package conn 15 | 16 | // Connect 目前暂时支持 "github.com/gorilla/websocket" ,后续笔者打算编写一个基于epoll+EL的一个 17 | // 网络模型,目前初步名称称为snetpoll,将websocket支持该模型,不过当下来说gorilla的包还是一个非常不错 18 | // 的选择,所以目前所有调用都抽象出来,后续可能增加底层扩展支持,目前只用到gorilla的基础方法,后续增加或者 19 | // 切换底层支持的话会重新发版,也考虑通过参数控制让用户自行选择实现 20 | 21 | type Connect interface { 22 | 23 | // the connection 24 | Identification() string 25 | 26 | Send(data []byte) error 27 | 28 | Close(reason string) 29 | 30 | ReFlushHeartBeatTime() 31 | 32 | GetLastHeartBeatTime() int64 33 | 34 | } 35 | -------------------------------------------------------------------------------- /pkg/conn/connect_gorilla.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 steven 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * Unless required by applicable law or agreed to in writing, software 8 | * distributed under the License is distributed on an "AS IS" BASIS, 9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | * See the License for the specific language governing permissions and 11 | * limitations under the License. 12 | */ 13 | package conn 14 | 15 | import ( 16 | "go.uber.org/atomic" 17 | "go.uber.org/zap" 18 | "net/http" 19 | "sync" 20 | "time" 21 | 22 | "github.com/mongofs/sim/pkg/logging" 23 | 24 | "github.com/gorilla/websocket" 25 | "github.com/pkg/errors" 26 | ) 27 | 28 | const ( 29 | StatusConnectionClosed = iota + 1 30 | StatusConnectionRunning 31 | ) 32 | 33 | var ( 34 | ErrConnectionIsClosed = errors.New("connection is closed") 35 | ErrConnectionIsWeak = errors.New("connection is in weak status") 36 | ) 37 | 38 | // This connection is upgrade of github.com/gorilla/websocket 39 | type conn struct { 40 | once sync.Once 41 | con *websocket.Conn 42 | identification string 43 | // buffer 这里是用户进行设置缓冲区的,这里和websocket的缓冲区不同的是,这里的内容是单独 44 | // 按照消息个数来缓冲的,而websocket是基于tcp的缓冲区进行字节数组缓冲,本质是不同 45 | // 的概念,值得注意的是,slice是指针类型,意味着传输的内容是可以很大的,在chan层 46 | // 表示仅仅是8字节的指针,建议单个传输内容不要太大,否则在用户下发的过程中如果用户网络 47 | // 不是很好,TCP连接写入能力较差,内容都会堆积在内存中导致内存上涨,这个参数也建议不要 48 | // 设置太大,建议在8个 49 | buffer chan []byte 50 | 51 | // heartBeatTime 这里是唯一一个伴随业务性质的1结构,值得注意的是,在我们实际应用场景中 52 | // 这里会容易出错,如果我将连接本身close掉,然后将连接标示放入closeChan,此时 53 | // 如果通道阻塞,本次连接的用户拿着同样的token进行连接,那么就会出现新的 54 | // 连接在bucket不存在的情况,建议做法是:最后在客户端能保证,每次发起连接 55 | // 都是一个全新的token,这样就能完全隔离掉这种情况 56 | // 由于本身业务复杂性,客户端某些功能不能实现,那么就只能采取:建立连接在 57 | // 之前先查后写,目前默认采取这种方案,但是又会伴随另外一个问题: 如果旧链接 58 | // 依旧在线,那么就得发送信号释放old conn ,整体性能就会降低 59 | // 针对第二种,我们踩过坑: 前台调用接口进入具体聊天室,聊天室内用户一直停留 60 | // 用户连接死掉或者被客观下线,前台发起重连,然后旧的连接下线新的链接收不到消息 61 | heartBeatTime int64 62 | 63 | // closeChan 是一个上层传入的一个chan,当用户连接关闭,可以将本身token传入closeChan 64 | // 通知到bucket层以及其他层进行处理,但是bucket作为connect管理单元,在做上层channel监听 65 | // 的时候尽量不要读取closeChan 66 | 67 | notify chan<- string 68 | 69 | status int 70 | 71 | // closeChan 72 | closeChan chan struct{} 73 | messageType MessageType // text /binary 74 | } 75 | 76 | type Receive func(conn Connect, data []byte) 77 | 78 | func NewConn(Id string, sig chan<- string, w http.ResponseWriter, r *http.Request, Receive Receive) (Connect, error) { 79 | result := &conn{ 80 | once: sync.Once{}, 81 | identification: Id, 82 | buffer: make(chan []byte, userOption.Buffer), 83 | heartBeatTime: time.Now().Unix(), 84 | notify: sig, 85 | closeChan: make(chan struct{}), 86 | messageType: userOption.MessageType, 87 | } 88 | err := result.upgrade(w, r, userOption.ConnectionReadBuffer, userOption.ConnectionWriteBuffer) 89 | if err != nil { 90 | return nil, err 91 | } 92 | result.status = StatusConnectionRunning 93 | go result.monitorSend() 94 | go result.monitorReceive(Receive) 95 | return result, nil 96 | } 97 | 98 | func (c *conn) Identification() string { 99 | return c.identification 100 | } 101 | 102 | func (c *conn) Send(data []byte) error { 103 | if c.status != StatusConnectionRunning { 104 | // judge the status of connection 105 | return ErrConnectionIsClosed 106 | } 107 | if len(c.buffer)*10 > cap(c.buffer)*7 { 108 | sendLoseContent.Inc() 109 | // judge the Send channel first 110 | return ErrConnectionIsWeak 111 | } 112 | c.buffer <- data 113 | return nil 114 | } 115 | 116 | func (c *conn) Close(reason string) { 117 | c.close(reason) 118 | } 119 | 120 | func (c *conn) ReFlushHeartBeatTime() { 121 | c.heartBeatTime = time.Now().Unix() 122 | } 123 | 124 | func (c *conn) GetLastHeartBeatTime() int64 { 125 | return c.heartBeatTime 126 | } 127 | 128 | var ( 129 | sendContent *atomic.Int64 = &atomic.Int64{} 130 | sendContentLength *atomic.Int64 = &atomic.Int64{} 131 | sendLoseContent *atomic.Int64 = &atomic.Int64{} 132 | ) 133 | 134 | func SwapSendData() (content, loseContent, contentLength int64) { 135 | content = sendContent.Swap(0) 136 | loseContent = sendLoseContent.Swap(0) 137 | contentLength = sendContentLength.Swap(0) 138 | return 139 | } 140 | 141 | func (c *conn) monitorSend() { 142 | defer func() { 143 | if err := recover(); err != nil { 144 | logging.Log.Error("monitorSend", zap.Any("PANIC", err)) 145 | } 146 | }() 147 | for { 148 | select { 149 | case <-c.closeChan: 150 | goto loop 151 | case data := <-c.buffer: 152 | startTime := time.Now() 153 | err := c.con.WriteMessage(int(c.messageType), data) 154 | if err != nil { 155 | logging.Log.Warn("monitorSend", zap.Error(err)) 156 | goto loop 157 | } 158 | spendTime := time.Since(startTime) 159 | if spendTime > time.Duration(2)*time.Second { 160 | logging.Log.Warn("monitorSend weak net ", zap.String("ID", c.identification), zap.Any("WEAK_NET", spendTime)) 161 | } 162 | sendContent.Inc() 163 | sendContentLength.Add(int64(len(data))) 164 | } 165 | } 166 | loop: 167 | c.close("monitorSend") 168 | } 169 | 170 | func (c *conn) monitorReceive(handleReceive Receive) { 171 | defer func() { 172 | if err := recover(); err != nil { 173 | logging.Log.Error("monitorReceive ", zap.Any("panic", err)) 174 | } 175 | }() 176 | var temErr error 177 | for { 178 | _, data, err := c.con.ReadMessage() 179 | if err != nil { 180 | temErr = err 181 | goto loop 182 | } 183 | handleReceive(c, data) 184 | } 185 | loop: 186 | c.close("monitorReceive", temErr) 187 | } 188 | 189 | func (c *conn) close(cause string, err ...error) { 190 | c.once.Do(func() { 191 | c.status = StatusConnectionClosed 192 | c.notify <- c.Identification() 193 | if len(err) > 0 { 194 | if err[0] != nil { 195 | // todo 196 | //logging.Log.Error("close ", zap.String("ID",c.identification),zap.Error(err[0])) 197 | } 198 | } 199 | close(c.closeChan) 200 | if err := c.con.Close(); err != nil { 201 | logging.Log.Error("close ", zap.String("ID", c.identification), zap.Error(err)) 202 | } 203 | logging.Log.Info("close", zap.String("ID", c.identification), zap.String("OFFLINE_CAUSE", cause)) 204 | }) 205 | } 206 | 207 | func (c *conn) upgrade(w http.ResponseWriter, r *http.Request, readerSize, writeSize int) error { 208 | conn, err := (&websocket.Upgrader{ 209 | CheckOrigin: func(r *http.Request) bool { 210 | return true 211 | }, 212 | ReadBufferSize: readerSize, 213 | WriteBufferSize: writeSize, 214 | }).Upgrade(w, r, nil) 215 | if err != nil { 216 | return err 217 | } 218 | c.con = conn 219 | return nil 220 | } 221 | -------------------------------------------------------------------------------- /pkg/conn/option.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 steven 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * Unless required by applicable law or agreed to in writing, software 8 | * distributed under the License is distributed on an "AS IS" BASIS, 9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | * See the License for the specific language governing permissions and 11 | * limitations under the License. 12 | */ 13 | 14 | package conn 15 | 16 | import ( 17 | "errors" 18 | "go.uber.org/atomic" 19 | ) 20 | 21 | type MessageType uint 22 | 23 | const ( 24 | Buffer = 1 << 3 25 | ConnectionWriteBuffer = 1 << 10 26 | ConnectionReadBuffer = 1 << 10 27 | ) 28 | 29 | const ( 30 | MessageTypeText MessageType = iota + 1 31 | MessageTypeBinary 32 | ) 33 | 34 | // Various errors contained in OpError. 35 | var ( 36 | // For connection write buffer param 37 | ErrConnWriteBufferParam = errors.New("conn write buffer param is wrong err , the value must bigger the 1") 38 | 39 | // For connection read buffer param 40 | ErrConnReadBufferParam = errors.New("conn read buffer param is wrong err , the value must bigger the 1") 41 | 42 | // For connection buffer param 43 | ErrBufferParam = errors.New("conn buffer param is wrong err , the value must bigger the 1") 44 | // For connection Message Type param 45 | ErrMessageTypeParam = errors.New("conn MessageType param is wrong err , the value must be 1 or 2") 46 | ) 47 | 48 | type Option struct { 49 | Buffer int // Buffer the data that need to send 50 | MessageType MessageType // Message type 51 | ConnectionWriteBuffer int // connection write buffer 52 | ConnectionReadBuffer int // connection read buffer 53 | } 54 | 55 | func DefaultOption() *Option { 56 | return &Option{ 57 | Buffer: Buffer, 58 | MessageType: MessageTypeText, 59 | ConnectionWriteBuffer: ConnectionWriteBuffer, 60 | ConnectionReadBuffer: ConnectionReadBuffer, 61 | } 62 | } 63 | 64 | var userOption *Option = DefaultOption() 65 | 66 | func SetOption(option *Option) error { 67 | if err := validate(option); err != nil { 68 | return err 69 | } 70 | userOption = option 71 | return nil 72 | } 73 | 74 | func validate(option *Option) error { 75 | if option.Buffer < 1 { 76 | return ErrBufferParam 77 | } else if option.ConnectionReadBuffer < 1 { 78 | return ErrConnReadBufferParam 79 | } else if option.ConnectionWriteBuffer < 1 { 80 | return ErrConnWriteBufferParam 81 | } else if option.MessageType != MessageTypeText && option.MessageType != MessageTypeBinary { 82 | return ErrMessageTypeParam 83 | } else { 84 | return nil 85 | } 86 | } 87 | 88 | // counter message wrapper add a counter for message , the counter is for record 89 | // that times of message send by net card 90 | type CounterMessageWrapper struct { 91 | origin *[]byte 92 | counter *atomic.Int64 93 | } 94 | 95 | // wrap message 96 | func WrapSendMessage(message *[]byte) CounterMessageWrapper { 97 | return CounterMessageWrapper{ 98 | origin: message, 99 | counter: &atomic.Int64{}, 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /pkg/conn/option_test.go: -------------------------------------------------------------------------------- 1 | package conn 2 | 3 | import "testing" 4 | 5 | func TestSetOption(t *testing.T) { 6 | type args struct { 7 | option *Option 8 | } 9 | tests := []struct { 10 | name string 11 | args args 12 | want error 13 | }{ 14 | { 15 | name: "no Option", 16 | args: args{ 17 | option: &Option{ 18 | Buffer: 0, 19 | MessageType: 1, 20 | ConnectionWriteBuffer: 0, 21 | ConnectionReadBuffer: 0, 22 | }}, 23 | want: ErrBufferParam, 24 | }, 25 | { 26 | name: "bad Option", 27 | args: args{option: &Option{ 28 | Buffer: 1, 29 | MessageType: 1, 30 | ConnectionWriteBuffer: 0, 31 | ConnectionReadBuffer: -20, 32 | }}, 33 | want: ErrConnReadBufferParam, 34 | }, 35 | } 36 | for _, tt := range tests { 37 | t.Run(tt.name, func(t *testing.T) { 38 | err := SetOption(tt.args.option) 39 | if err !=tt.want { 40 | t.Errorf("SetOption() error = '%v', wantErr '%v' \n", err, tt.want) 41 | } 42 | }) 43 | } 44 | } 45 | 46 | -------------------------------------------------------------------------------- /pkg/label/group.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 steven 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * Unless required by applicable law or agreed to in writing, software 8 | * distributed under the License is distributed on an "AS IS" BASIS, 9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | * See the License for the specific language governing permissions and 11 | * limitations under the License. 12 | */ 13 | 14 | package label 15 | 16 | import ( 17 | "strconv" 18 | "sync" 19 | "time" 20 | ) 21 | 22 | 23 | 24 | 25 | 26 | // group 是一个组的概念,相同tag 的用户将放在一起,对外提供广播功能,减少寻址过程,增加分组广播功能 27 | // group 的调用者是是target,提供的所有功能也是面对target(标签)而设定的。group是target存储数据 28 | // 的基本单元,而最基本的实体就是Client对象,在group内部存储用户是使用哈希表,并发读写是依靠读写锁 29 | // 来进行保障 30 | type group struct { 31 | rw *sync.RWMutex 32 | cap, num, load int 33 | set map[string]Client 34 | createTime int64 35 | } 36 | 37 | var groupPool = sync.Pool{ 38 | New: func() interface{} { 39 | return &group{ 40 | rw: &sync.RWMutex{}, 41 | set: make(map[string]Client, DefaultCapacity), 42 | } 43 | }, 44 | } 45 | 46 | // @ForTesting 47 | func GetG(cap int) *group { 48 | if cap == 0 { 49 | cap = DefaultCapacity 50 | } 51 | grp := groupPool.Get().(*group) 52 | grp.createTime = time.Now().Unix() 53 | grp.cap = cap 54 | return grp 55 | } 56 | 57 | func (g *group) info() *map[string]string { 58 | g.rw.RLock() 59 | defer g.rw.RUnlock() 60 | res := &map[string]string{ 61 | "online": strconv.Itoa(g.num), 62 | "load": strconv.Itoa(g.load), 63 | "create_time": strconv.Itoa(int(g.createTime)), 64 | } 65 | return res 66 | } 67 | 68 | func (g *group) free() ([]Client, error) { 69 | return g.move(g.num), nil 70 | } 71 | 72 | func (g *group) add(cli Client) bool { 73 | g.rw.Lock() 74 | defer g.rw.Unlock() 75 | if _, ok := g.set[cli.Identification()]; ok { 76 | g.set[cli.Identification()] = cli 77 | return true 78 | } else { 79 | g.set[cli.Identification()] = cli 80 | g.num++ 81 | g.calculateLoad() 82 | } 83 | if g.num > g.cap { 84 | return false 85 | } 86 | return false 87 | } 88 | 89 | func (g *group) addMany(cliS []Client) { 90 | if len(cliS) == 0 { 91 | return 92 | } 93 | g.rw.Lock() 94 | defer g.rw.Unlock() 95 | for _, cli := range cliS { 96 | if _, ok := g.set[cli.Identification()]; ok { 97 | g.set[cli.Identification()] = cli 98 | } else { 99 | g.set[cli.Identification()] = cli 100 | g.num++ 101 | } 102 | } 103 | g.calculateLoad() 104 | } 105 | 106 | func (g *group) del(tokens []string) (clear bool, success []string, current int) { 107 | if len(tokens) == 0 { 108 | return 109 | } 110 | g.rw.Lock() 111 | defer g.rw.Unlock() 112 | for _, token := range tokens { 113 | if _, ok := g.set[token]; ok { 114 | delete(g.set, token) 115 | g.num-- 116 | g.calculateLoad() 117 | success = append(success, token) 118 | } else { 119 | clear = false 120 | } 121 | } 122 | return clear, success, g.num 123 | } 124 | 125 | func (g *group) move(num int) []Client { 126 | var ( 127 | counter = 0 128 | res []Client 129 | ) 130 | g.rw.Lock() 131 | defer g.rw.Unlock() 132 | if num > g.num { 133 | num = g.num 134 | } 135 | for k, v := range g.set { 136 | if counter == num { 137 | break 138 | } 139 | delete(g.set, k) 140 | res = append(res, v) 141 | g.num-- 142 | counter++ 143 | } 144 | g.calculateLoad() 145 | return res 146 | } 147 | 148 | func (g *group) broadcast(content []byte) []string { 149 | g.rw.RLock() 150 | defer g.rw.RUnlock() 151 | var res []string 152 | for _, v := range g.set { 153 | err := v.Send(content) 154 | if err != nil { 155 | res = append(res, v.Identification()) 156 | } 157 | } 158 | return res 159 | } 160 | 161 | func (g *group) broadcastWithTag(content []byte, tags []string) []string { 162 | var res []string 163 | g.rw.RLock() 164 | defer g.rw.RUnlock() 165 | for _, v := range g.set { 166 | if v.HaveTags(tags) { 167 | err := v.Send(content) 168 | if err != nil { 169 | res = append(res, v.Identification()) 170 | } 171 | } 172 | } 173 | return res 174 | } 175 | 176 | func (g *group) calculateLoad() { 177 | g.load = g.cap - g.num // cap - len 178 | } 179 | 180 | // @ForTesting 181 | func (g *group) Destroy() error { 182 | g.cap, g.num, g.cap, g.load, g.createTime = 0, 0, 0, 0, 0 183 | groupPool.Put(g) 184 | return nil 185 | } 186 | -------------------------------------------------------------------------------- /pkg/label/interface.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 steven 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * Unless required by applicable law or agreed to in writing, software 8 | * distributed under the License is distributed on an "AS IS" BASIS, 9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | * See the License for the specific language governing permissions and 11 | * limitations under the License. 12 | */ 13 | 14 | package label 15 | 16 | import "context" 17 | 18 | type TargetStatus int 19 | 20 | const ( 21 | TargetStatusNORMAL = iota // normal 22 | TargetStatusShouldEXTENSION // start extension 23 | TargetStatusShouldSHRINKS // start shrinks 24 | TargetStatusShouldReBalance // start reBalance 25 | TargetStatusShouldDestroy // should destroy 26 | ) 27 | 28 | const DefaultCapacity = 128 29 | 30 | type Parallel func() error 31 | 32 | // Manager Label管理器 33 | type Manager interface { 34 | Run ()[] func(ctx context.Context)error 35 | 36 | // AddClient 添加一个用户到Label服务中,需要指出具体某个tag ,如果tag 不存在需要创建出新的tag 37 | // forClient 是需要用户将label 做一下本地保存,一个用户可以存储多个label,所以需要在用户操作的时候 38 | // 去协助调配label的增删改查 39 | AddClient(tag string, client Client) (ForClient, error) 40 | 41 | // List 获取当前存在的label ,获取label 列表信息 42 | List(limit, page int) []*LabelInfo 43 | 44 | // LabelInfo 获取具体label相信信息 45 | LabelInfo(tag string) (*LabelInfo, error) 46 | 47 | // BroadCastByLabel 通过label来进行广播,可以利用这个接口做版本广播,比如v1的用户传输内容格式是基于json 48 | // v2版本的用户是基于protobuf 可以通过这个api 非常便捷就可以完成 49 | BroadCastByLabel(tc map[string][]byte) ([]string, error) 50 | 51 | // BroadCastWithInnerJoinLabel 通过label的交集发布,比如要找到 v1版本、room1 、man 三个标签都满足 52 | // 才发送广播,此时可以通过这个接口 53 | BroadCastWithInnerJoinLabel(cont []byte, tags []string) ([]string,error) 54 | } 55 | 56 | // Client 存储单元的标准,每一个用户应该支持这几个方法才能算作一个客户,send 主要用做数据下发,HaveTags 57 | // 做组内消息下推的筛选,Identification 是获取到用户的链接标示做存储,方法上有具体为什么使用哈希表做存储 58 | type Client interface { 59 | Send([]byte) error 60 | 61 | // HaveTags 判断用户是否存在这批tag,这个函数是用于支持BroadManager的交集推送,如何判断交集中实际 62 | // 上带来一定的定制化嫌疑,但是目前想到的最快的方法就是这样,可以支持On 的复杂度进行交集下推 63 | HaveTags([]string) bool 64 | // 在用户管理中,需要将用户的标识作为key值进行存储,原本打算使用链表进行存储的,但是发现还是存在部分需求 65 | // 需要进行支持快速查找,比如快速找到组内是否存在某个用户,用户存在就将用户标识对应的内容进行替换,如果 66 | // 不存在就进行新增。使用链表就不是很有必要,所以决定是用hash表进行存储 67 | Identification() string 68 | } 69 | 70 | // ForClient 用户使用工具,当用户被删除的时候就是,用户可以调用此方法,将用户标签在targetManager对象中删除 71 | type ForClient interface { 72 | Delete(token []string) ([]string, int) 73 | } 74 | 75 | // Actor 是Label单元的行为集合,可以操作Actor实现者的创建 76 | type Actor interface { 77 | 78 | // Destroy 判断status 状态为shouldDestroy的时候可以调用此方法 79 | Destroy() 80 | 81 | // Expansion label 本身支持扩张,如果用户在某个tag下增长到一定的人数,那么在这个target为了减少锁的粒度 82 | // 需要进行减小,那么相对应的操作就是增加新的容器进行存放用户,这就是扩容 83 | Expansion() 84 | 85 | // Shrinks label 本身支持缩容,如果用户在某个tag下缩减到一定的人数,那么在这个target为了减少锁的粒度 86 | // 需要进行减小,那么相对应的操作就是增加新的容器进行存放用户,这就是缩容 87 | Shrinks() 88 | 89 | // Balance 重新进行平衡,这个给外部进行调用,目的是为了在合适的时候进行重新平衡用户,如果说在扩张后就进行重 90 | // 平衡操作,会导致频繁的重平衡,那么需要外部在做一定的判断进行重平衡 91 | Balance() 92 | } 93 | 94 | // 广播器,用于进行广播,交集广播 95 | type BroadCast interface { 96 | // 常规广播,入参为字节数组,将直接写入用户的链接中,返回值为失败的用户的切片 97 | // 主要报错实际在依赖注入的层面就可以记录,不需要这里进行操作 98 | // 交集广播,和上面的不同的是,只有拥有多个标签的用户才能进行广播,比如说 man 、 18、178 99 | // 这三个标签都满足了才能进行广播,我们只能选择广播器所依附的实体对象进行再筛选,一般依附的 100 | // 实体对象我们选择最少数量原则 101 | BroadCast(data []byte, tags ...string) []string 102 | } 103 | 104 | // label 标签管理单元,相同的标签会放在同样的标签实现中,标签是整个wti的管理单元,具有相同的标签的用户将会 105 | // 放在这里 106 | type Label interface { 107 | 108 | Actor 109 | 110 | BroadCast 111 | 112 | ForClient 113 | 114 | // Add 往标签中添加一个用户 115 | Add(cli Client) ForClient 116 | 117 | // Count 获取到label中的所有用户 118 | Count() int 119 | 120 | // Info 获取到label相关消息 121 | Info() *LabelInfo 122 | 123 | // Status 每次调用获取到当前target的相关状态,调用manager本身的其他暴露的端口进行耗时的操作,比如进行 124 | // 重平衡、进行扩容、进行缩容等操作, 125 | Status() TargetStatus 126 | } 127 | 128 | type LabelInfo struct { 129 | Name string 130 | Online int 131 | Limit int 132 | CreateTime int64 133 | Status int 134 | NumG int 135 | Change int //状态变更次数 136 | GInfo []*map[string]string 137 | } 138 | -------------------------------------------------------------------------------- /pkg/label/label.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 steven 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * Unless required by applicable law or agreed to in writing, software 8 | * distributed under the License is distributed on an "AS IS" BASIS, 9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | * See the License for the specific language governing permissions and 11 | * limitations under the License. 12 | */ 13 | 14 | package label 15 | 16 | import ( 17 | "container/list" 18 | "errors" 19 | "sync" 20 | "time" 21 | 22 | "github.com/mongofs/sim/pkg/logging" 23 | ) 24 | 25 | // label 是相同的标签的管理单元,相同的target都会放置到相同的 26 | type label struct { 27 | rw sync.RWMutex 28 | name string 29 | num int // online user 30 | numG int // online Group 31 | targetG int // should be numG 32 | maxGOnlineDiff int // 可容忍的最大差值 33 | offset *list.Element // the next user group offset 34 | 35 | flag TargetStatus // 36 | capChangeTime time.Duration 37 | li *list.List 38 | change int // 进行扩容缩容操作次数 39 | limit int // max online user for group 40 | createTime int64 // create time 41 | } 42 | 43 | var targetPool = sync.Pool{New: func() interface{} { 44 | return &label{ 45 | rw: sync.RWMutex{}, 46 | li: list.New(), 47 | } 48 | }} 49 | 50 | func NewLabel(targetName string, limit int) (*label, error) { 51 | if targetName == "" || limit == 0 { 52 | return nil, errors.New("bad param of label") 53 | } 54 | tg := targetPool.Get().(*label) 55 | tg.name = targetName 56 | tg.limit = limit 57 | tg.createTime = time.Now().Unix() 58 | g := GetG(tg.limit) 59 | elm := tg.li.PushFront(g) 60 | tg.offset = elm 61 | tg.numG++ 62 | tg.getMaxGOnlineDiff() 63 | return tg, nil 64 | } 65 | 66 | // ============================================= API ================================= 67 | 68 | func (t *label) Info() *LabelInfo { 69 | return t.info() 70 | } 71 | 72 | func (t *label) Add(cli Client)ForClient { 73 | if cli == nil { 74 | return nil 75 | } 76 | t.add(cli) 77 | return t 78 | } 79 | 80 | func (t *label) Delete(token []string) ([]string, int) { 81 | if token == nil { 82 | return nil, 0 83 | } 84 | return t.del(token) 85 | } 86 | 87 | func (t *label) Count() int { 88 | t.rw.RLock() 89 | defer t.rw.RUnlock() 90 | return t.num 91 | } 92 | 93 | func (t *label) BroadCast(data []byte, tags ...string) []string { 94 | if len(data) == 0 { 95 | return nil 96 | } 97 | return t.broadcast(data, tags...) 98 | } 99 | 100 | func (t *label) Expansion() { 101 | since := time.Now() 102 | t.rw.Lock() 103 | defer t.rw.Unlock() 104 | t.expansion(t.targetG - t.numG) 105 | escape := time.Since(since) 106 | logging.Infof("sim : label Expansion , spend time %v ,", escape) 107 | } 108 | 109 | func (t *label) Shrinks() { 110 | t.rw.Lock() 111 | t.rw.Unlock() 112 | shrinksNum := t.numG -t.targetG 113 | if shrinksNum <= 0 {return} 114 | since := time.Now() 115 | t.shrinks(shrinksNum) 116 | escape := time.Since(since) 117 | logging.Infof("sim : label Shrinks ,count %v spend time %v ,",shrinksNum, escape) 118 | } 119 | 120 | func (t *label) Balance() { 121 | since := time.Now() 122 | t.balance() 123 | escape := time.Since(since) 124 | logging.Infof("sim : label %v Balance ,online user %v ,countG %v , spend time %v ,", t.name, t.num, t.numG, escape) 125 | } 126 | 127 | func (t *label) Status() TargetStatus { 128 | return t.fixStatus() 129 | } 130 | 131 | func (t *label) Destroy() { 132 | if t.num != 0 { 133 | return 134 | } 135 | t.destroy() 136 | } 137 | 138 | func (t *label) broadcast(data []byte, tags ...string) []string { 139 | t.rw.RLock() 140 | defer t.rw.RUnlock() 141 | node := t.li.Front() 142 | var res []string 143 | 144 | if len(tags) == 0 { 145 | for node != nil { 146 | g := node.Value.(*group) 147 | res = append(res, g.broadcast(data)...) 148 | node = node.Next() 149 | } 150 | } else { 151 | for node != nil { 152 | res = append(res, node.Value.(*group).broadcastWithTag(data, tags)...) 153 | node = node.Next() 154 | } 155 | } 156 | return res 157 | } 158 | 159 | func (t *label) info() *LabelInfo { 160 | var res = &LabelInfo{} 161 | t.rw.RLock() 162 | defer t.rw.RUnlock() 163 | res.Name = t.name 164 | res.Limit = t.limit 165 | res.Online = t.num 166 | res.NumG = t.numG 167 | res.Change = t.change 168 | res.CreateTime = t.createTime 169 | res.Status = int(t.flag) 170 | var numG []*map[string]string 171 | node := t.li.Front() 172 | for node != nil { 173 | g := node.Value.(*group) 174 | numG = append(numG, g.info()) 175 | node = node.Next() 176 | } 177 | res.GInfo = numG 178 | return res 179 | } 180 | 181 | func (t *label) add(cli Client) { 182 | t.rw.Lock() 183 | defer t.rw.Unlock() 184 | g := t.offset.Value.(*group) 185 | if same := g.add(cli); same { 186 | return 187 | } 188 | t.num++ 189 | t.moveOffset() 190 | return 191 | } 192 | 193 | func (t *label) del(token []string) (res []string, current int) { 194 | t.rw.Lock() 195 | defer t.rw.Unlock() 196 | node := t.li.Front() 197 | for node != nil { 198 | gp := node.Value.(*group) 199 | _, result, cur := gp.del(token) 200 | current += cur 201 | res = append(res, result...) 202 | node = node.Next() 203 | } 204 | t.num = current 205 | return 206 | } 207 | 208 | func (t *label) moveOffset() { 209 | if t.offset.Next() != nil { 210 | t.offset = t.offset.Next() 211 | } else { 212 | t.offset = t.li.Front() 213 | } 214 | } 215 | 216 | func (t *label) fixStatus() TargetStatus { 217 | t.rw.Lock() 218 | defer t.rw.Unlock() 219 | 220 | if t.num == 0 && time.Now().Unix()-t.createTime > 30 { 221 | t.flag = TargetStatusShouldDestroy 222 | return t.flag 223 | } 224 | 225 | // 修正targetG 226 | tg := t.num/t.limit + 1 // 2 /2 =1 , 3/2 = 1 227 | if tg == t.targetG && t.targetG == t.numG && t.num != 0{ 228 | // targetG == numG ,说明状态被校正,但是内部可能存在不平衡状态 229 | t.fixBalance() 230 | return t.flag 231 | } 232 | t.targetG = tg 233 | 234 | if t.numG == t.targetG { 235 | return t.flag 236 | } 237 | 238 | if t.targetG > t.numG { 239 | t.flag = TargetStatusShouldEXTENSION 240 | return t.flag 241 | } 242 | if t.targetG < t.numG { 243 | t.flag = TargetStatusShouldSHRINKS 244 | return t.flag 245 | } 246 | 247 | 248 | 249 | return t.flag 250 | } 251 | 252 | func (t *label) getMaxGOnlineDiff(){ 253 | t.maxGOnlineDiff = t.limit/3 254 | } 255 | 256 | func (t *label) fixBalance(){ 257 | node := t.li.Front() 258 | var n []int 259 | for node != nil { 260 | n = append(n, node.Value.(*group).num) 261 | node = node.Next() 262 | } 263 | var min, max = 10000, 0 264 | for _, v := range n { 265 | if v < min { 266 | min = v 267 | } 268 | if v > max { 269 | max = v 270 | } 271 | } 272 | if max-min >= t.maxGOnlineDiff { 273 | t.flag = TargetStatusShouldReBalance 274 | }else{ 275 | t.flag = TargetStatusNORMAL 276 | } 277 | } 278 | 279 | 280 | func (t *label) expansion(num int) { 281 | for i := 0; i < num; i++ { 282 | newG := GetG(t.limit) 283 | t.li.PushBack(newG) 284 | t.numG += 1 285 | } 286 | } 287 | 288 | func (t *label) shrinks(num int) { 289 | // 缩容的几个重要问题 290 | // 1. 什么时候判断是否应该缩容 : 291 | // 2. 缩容应该由谁来判定 : 每次删除用户就需要进行判断, 292 | // 3. 缩容的标准是什么 : 总在线人数和总的G 所分摊的人数来判断需不需要缩容,但是需要额外容量规划,如果使用量低于30% 可以开启缩容 293 | // 3.1 缩容的目标是: 将利用率保证在60% 左右 294 | // 4. 谁来执行shrink : 应该由target 聚合层进行统一状态管理 295 | 296 | 297 | var free []Client 298 | var freeNode []*list.Element 299 | node := t.li.Front() 300 | for i := 0; i < num; i++ { 301 | ng := node.Value.(*group) 302 | res, err := ng.free() 303 | if err != nil { 304 | logging.Error(err) 305 | break 306 | } 307 | free = append(free, res...) 308 | t.numG-- 309 | ng.Destroy() 310 | if node.Next() != nil { 311 | freeNode = append(freeNode, node) 312 | node = node.Next() 313 | continue 314 | } 315 | break 316 | } 317 | 318 | for _, v := range freeNode { 319 | t.li.Remove(v) 320 | } 321 | 322 | node1 := t.li.Front() 323 | ng := node1.Value.(*group) 324 | ng.addMany(free) 325 | } 326 | 327 | // @ forTesting 328 | func (t *label) distribute() (res []int) { 329 | t.rw.RLock() 330 | defer t.rw.RUnlock() 331 | node := t.li.Front() 332 | for node != nil { 333 | res = append(res, node.Value.(*group).num) 334 | node = node.Next() 335 | } 336 | return 337 | } 338 | 339 | func (t *label) balance() { 340 | // 根据当前节点进行平均每个节点的人数 341 | avg := t.num/t.numG + 1 342 | 343 | // 负载小于等于 10 的节点都属于 紧急节点 344 | // 负载小于 0 的节点属于立刻节点 345 | // 获取到所有节点的负载情况 : 负载小于 0 的优先移除 346 | var lowLoadG []*group 347 | var steals []Client 348 | t.rw.Lock() 349 | defer t.rw.Unlock() 350 | t.change++ 351 | node := t.li.Front() 352 | for node != nil { 353 | g := node.Value.(*group) 354 | gnum := g.num 355 | if gnum > avg { 356 | // num >avg ,说明超载 357 | steal := gnum - avg 358 | st := g.move(steal) 359 | steals = append(steals, st...) 360 | } else { 361 | // 进入这里说明当前节点load 偏低 362 | diff := avg - gnum 363 | if diff > 0 { 364 | if len(steals) > diff { 365 | g.addMany(steals[:diff]) 366 | steals = steals[diff:] 367 | node = node.Next() 368 | continue 369 | } else { 370 | g.addMany(steals) 371 | steals = []Client{} 372 | node = node.Next() 373 | continue 374 | } 375 | } 376 | // 到这里说明情况当前这个 377 | lowLoadG = append(lowLoadG, g) 378 | } 379 | node = node.Next() 380 | } 381 | 382 | for _, g := range lowLoadG { 383 | w := avg - g.num 384 | if w >= len(steals) { 385 | g.addMany(steals) 386 | break 387 | } 388 | g.addMany(steals[:w]) 389 | steals = steals[w:] 390 | } 391 | } 392 | 393 | func (t *label) destroy() { 394 | t.createTime, t.num, t.limit, t.numG = 0, 0, 0, 0 395 | t.flag = 0 396 | t.li = list.New() 397 | t.offset = nil 398 | targetPool.Put(t) 399 | } 400 | -------------------------------------------------------------------------------- /pkg/label/label_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 steven 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * Unless required by applicable law or agreed to in writing, software 8 | * distributed under the License is distributed on an "AS IS" BASIS, 9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | * See the License for the specific language governing permissions and 11 | * limitations under the License. 12 | */ 13 | 14 | package label 15 | 16 | import ( 17 | "fmt" 18 | . "github.com/smartystreets/goconvey/convey" 19 | "testing" 20 | ) 21 | 22 | type MockClient struct { 23 | token string 24 | } 25 | 26 | func (m MockClient) Send(bytes []byte) error { 27 | fmt.Printf("mockeClient : %v \n\r", string(bytes)) 28 | return nil 29 | } 30 | 31 | func (m MockClient) HaveTags(strings []string) bool { 32 | return true 33 | } 34 | 35 | func (m MockClient) Identification() string { 36 | return m.token 37 | } 38 | 39 | func TestLabel_Status(t *testing.T) { 40 | Convey("进行各种状态测试,查看状态是否判断正确", t, func() { 41 | Convey("测试扩容过程中状态判断是否正确", func() { 42 | // 容量为2 43 | tg, err := NewLabel("example", 2) 44 | if err != nil { 45 | t.Fatal(err) 46 | } 47 | tg.Add(&MockClient{token: "1111"}) 48 | tgStatus1 := tg.Status() 49 | So(tgStatus1 == TargetStatusNORMAL && tg.targetG == 1, ShouldBeTrue) 50 | tg.Add(&MockClient{token: "1222"}) 51 | tgStatus2 := tg.Status() 52 | So(tgStatus2 == TargetStatusShouldEXTENSION && tg.targetG == 2, ShouldBeTrue) 53 | tg.Add(&MockClient{token: "1333"}) 54 | tg.Add(&MockClient{token: "1334"}) 55 | tgStatus3 := tg.Status() 56 | So(tgStatus3 == TargetStatusShouldEXTENSION && tg.targetG == 3, ShouldBeTrue) 57 | }) 58 | 59 | Convey("测试需要缩容的情况是否正确", func() { 60 | tg, err := NewLabel("example", 2) 61 | if err != nil { 62 | t.Fatal(err) 63 | } 64 | tg.expansion(10) 65 | tgStatus1 := tg.Status() 66 | So(tgStatus1 == TargetStatusShouldSHRINKS && tg.targetG == 1 && tg.numG == 11, ShouldBeTrue) 67 | tg.expansion(2) 68 | tgStatus2 := tg.Status() 69 | So(tgStatus2 == TargetStatusShouldSHRINKS && tg.targetG == 1 && tg.numG == 13, ShouldBeTrue) 70 | tg.Add(&MockClient{token: "1333"}) 71 | tg.Add(&MockClient{token: "1334"}) 72 | tgStatus3 := tg.Status() 73 | So(tgStatus3 == TargetStatusShouldSHRINKS && tg.targetG == 2 && tg.numG == 13, ShouldBeTrue) 74 | tg.Add(&MockClient{token: "1335"}) 75 | tg.Add(&MockClient{token: "1336"}) 76 | tg.Add(&MockClient{token: "1337"}) 77 | tgStatus4 := tg.Status() 78 | So(tgStatus4 == TargetStatusShouldSHRINKS && tg.targetG == 3 && tg.numG == 13, ShouldBeTrue) 79 | tg.shrinks(5) // numG = 8 80 | So(tgStatus4 == TargetStatusShouldSHRINKS && tg.targetG == 3 && tg.numG == 8, ShouldBeTrue) 81 | }) 82 | 83 | 84 | Convey("测试在状态错误下能否自行校正", func() { 85 | tg, err := NewLabel("example", 2) 86 | if err != nil { 87 | t.Fatal(err) 88 | } 89 | tg.Add(&MockClient{token: "1111"}) 90 | tg.Add(&MockClient{token: "1112"}) 91 | tg.Add(&MockClient{token: "1113"}) 92 | tg.Add(&MockClient{token: "1114"}) 93 | tgStatus1 := tg.Status() 94 | So(tgStatus1 == TargetStatusShouldEXTENSION && tg.targetG == 3 && tg.numG == 1, ShouldBeTrue) 95 | tg.expansion(2) 96 | // 状态回归正常后 97 | tgStatus2 := tg.Status() 98 | So(tgStatus2 == TargetStatusShouldReBalance && tg.targetG == 3 && tg.numG == 3, ShouldBeTrue) 99 | }) 100 | }) 101 | } 102 | 103 | func TestTarget_Balance(t *testing.T) { 104 | Convey("测试重平衡", t, func() { 105 | tg, err := NewLabel("example", 20,) 106 | if err != nil { 107 | t.Fatal(err) 108 | } 109 | for i := 0; i < 200; i++ { 110 | tg.add(&MockClient{token: fmt.Sprintf("aaa_%d", i)}) 111 | } 112 | tg.expansion(7) 113 | fmt.Println("------------------expansion to 8 group",tg.distribute()) 114 | tg.Balance() 115 | fmt.Println("------------------after expansion to 8 group, Balance",tg.distribute()) 116 | tg.shrinks(5) 117 | fmt.Println("------------------shrinks to 3 group",tg.distribute()) 118 | tg.Balance() 119 | fmt.Println("------------------after shrinks to 3 group, Balance",tg.distribute()) 120 | tg.expansion(128) 121 | fmt.Println("------------------expansion to 131 group",tg.distribute()) 122 | tg.Balance() 123 | fmt.Println("------------------after expansion to 131 group",tg.distribute()) 124 | tg.shrinks(127) 125 | fmt.Println("------------------shrinks expansion to 4 group",tg.distribute()) 126 | tg.Balance() 127 | fmt.Println("------------------after shrinks to 4 group",tg.distribute()) 128 | }) 129 | } 130 | -------------------------------------------------------------------------------- /pkg/label/manager.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 steven 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * Unless required by applicable law or agreed to in writing, software 8 | * distributed under the License is distributed on an "AS IS" BASIS, 9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | * See the License for the specific language governing permissions and 11 | * limitations under the License. 12 | */ 13 | 14 | package label 15 | 16 | import ( 17 | "context" 18 | "math" 19 | "sync" 20 | "time" 21 | 22 | "github.com/mongofs/sim/pkg/errors" 23 | "github.com/mongofs/sim/pkg/logging" 24 | ) 25 | 26 | type manager struct { 27 | // mp tagName => 28 | mp map[string]Label // wti => []string 29 | sort []Label 30 | rw *sync.RWMutex 31 | 32 | flag bool 33 | limit, watchTime int 34 | expansion, shrinks, balance, destroy chan Label 35 | } 36 | 37 | 38 | var m Manager 39 | 40 | 41 | func Add (label string ,cli Client)(ForClient,error){ 42 | return m.AddClient(label,cli) 43 | } 44 | 45 | 46 | func NewManager() Manager { 47 | if m ==nil { 48 | m = &manager{ 49 | mp: map[string]Label{}, 50 | rw: &sync.RWMutex{}, 51 | limit: DefaultCapacity, 52 | watchTime: 20, 53 | expansion: make(chan Label, 5), 54 | shrinks: make(chan Label, 5), 55 | balance: make(chan Label, 5), 56 | } 57 | } 58 | return m 59 | } 60 | 61 | // Run 将target的需要长时间运行的内容返回出去执行 62 | func (s *manager) Run() []func(ctx context.Context) error{ 63 | return s.parallel() 64 | } 65 | 66 | func (s *manager) AddClient(tag string, client Client) (ForClient, error) { 67 | if tag == "" || client == nil { 68 | return nil, errors.ErrBadParam 69 | } 70 | return s.add(tag, client) 71 | } 72 | 73 | func (s *manager) List(limit, page int) []*LabelInfo { 74 | return s.list() 75 | } 76 | 77 | func (s *manager) LabelInfo(label string) (*LabelInfo, error) { 78 | if label == "" { 79 | return nil, errors.ErrBadParam 80 | } 81 | return s.LabelInfo(label) 82 | } 83 | 84 | func (s *manager) BroadCastByLabel(tc map[string][]byte) ([]string, error) { 85 | if len(tc) == 0 { 86 | return nil, errors.ErrBadParam 87 | } 88 | return s.broadcastByLabel(tc) 89 | } 90 | 91 | func (s *manager) BroadCastWithInnerJoinLabel(cont []byte, tags []string) ([]string, error) { 92 | if len(cont) == 0 || len(tags) == 0 { 93 | return nil, errors.ErrBadParam 94 | } 95 | return s.broadcast(cont, tags...), nil 96 | } 97 | 98 | // Add 添加用户到某个target 上去,此时用户需要在用户单元保存target内容 99 | func (s *manager) add(tag string, client Client) (ForClient, error) { 100 | s.rw.Lock() 101 | defer s.rw.Unlock() 102 | var res ForClient 103 | if tg, ok := s.mp[tag]; ok { 104 | res = tg 105 | tg.Add(client) 106 | } else { 107 | ctag, err := NewLabel(tag, s.limit) 108 | if err != nil { 109 | return nil, err 110 | } 111 | s.mp[tag] = ctag 112 | res = ctag 113 | } 114 | return res, nil 115 | } 116 | 117 | func (s *manager) list() []*LabelInfo { 118 | s.rw.RLock() 119 | defer s.rw.RUnlock() 120 | var res []*LabelInfo 121 | for _, v := range s.mp { 122 | res = append(res, v.Info()) 123 | } 124 | return res 125 | } 126 | 127 | func (s *manager) info(tag string) (*LabelInfo, error) { 128 | s.rw.RLock() 129 | defer s.rw.RUnlock() 130 | if v, ok := s.mp[tag]; ok { 131 | return v.Info(), nil 132 | } 133 | return nil, errors.ERRWTITargetNotExist 134 | } 135 | 136 | func (s *manager) broadcast(cont []byte, tags ...string) (res []string) { 137 | s.rw.RLock() 138 | defer s.rw.RUnlock() 139 | if len(tags) != 0 { 140 | var min int = math.MaxInt32 141 | var mintg Label 142 | for _, tag := range tags { 143 | if v, ok := s.mp[tag]; ok { 144 | temN := v.Count() 145 | if v.Count() < min { 146 | min = temN 147 | mintg = v 148 | } 149 | } 150 | } 151 | res = append(res, mintg.BroadCast(cont, tags...)...) 152 | return 153 | } 154 | for _, v := range s.mp { 155 | res = append(res, v.BroadCast(cont)...) 156 | } 157 | return 158 | } 159 | 160 | func (s *manager) broadcastByLabel(msg map[string][]byte) ([]string, error) { 161 | s.rw.RLock() 162 | defer s.rw.RUnlock() 163 | var res []string 164 | for tagN, cont := range msg { 165 | if tar, ok := s.mp[tagN]; ok { 166 | res = append(res, tar.BroadCast(cont)...) 167 | } 168 | } 169 | return res, nil 170 | } 171 | 172 | func (s *manager) parallel()(res []func(ctx context.Context) error) { 173 | res = append(res, s.monitor, s.handleMonitor) 174 | return 175 | } 176 | 177 | func (s *manager) monitor(ctx context.Context) error { 178 | logging.Infof("sim : monitor of label manager starting ") 179 | ticker := time.NewTicker(time.Duration(s.watchTime) * time.Second) 180 | for { 181 | select { 182 | case <- ticker.C: 183 | s.rw.RLock() 184 | for k, r := range s.mp { 185 | st := r.Status() 186 | switch st { 187 | default: 188 | continue 189 | case TargetStatusShouldEXTENSION: 190 | s.expansion <- r 191 | case TargetStatusShouldReBalance: 192 | s.balance <- r 193 | case TargetStatusShouldSHRINKS: 194 | s.shrinks <- r 195 | case TargetStatusShouldDestroy: 196 | delete(s.mp,k) 197 | r.Destroy() 198 | } 199 | } 200 | s.rw.RUnlock() 201 | case <- ctx.Done(): 202 | goto loop 203 | } 204 | } 205 | loop : 206 | logging.Infof("sim : monitor of label manager Closed ") 207 | return nil 208 | } 209 | 210 | func (s *manager) handleMonitor(ctx context.Context) error { 211 | logging.Infof("sim : handleMonitor of label manager starting ") 212 | for { 213 | select { 214 | case t := <-s.expansion: 215 | t.Expansion() 216 | case t := <-s.shrinks: 217 | t.Shrinks() 218 | case t := <-s.balance: 219 | t.Balance() 220 | case <- ctx.Done() : 221 | goto loop 222 | } 223 | } 224 | loop : 225 | logging.Infof("sim : handleMonitor of label manager Closed ") 226 | return nil 227 | } 228 | 229 | 230 | 231 | -------------------------------------------------------------------------------- /pkg/logging/logger_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 steven 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * Unless required by applicable law or agreed to in writing, software 8 | * distributed under the License is distributed on an "AS IS" BASIS, 9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | * See the License for the specific language governing permissions and 11 | * limitations under the License. 12 | */ 13 | 14 | package logging 15 | 16 | import "testing" 17 | 18 | func TestLog_Infof(t *testing.T) { 19 | Infof("%v,%v , %v",1,2,3) 20 | } 21 | -------------------------------------------------------------------------------- /pkg/logging/zaplog.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 steven 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * Unless required by applicable law or agreed to in writing, software 8 | * distributed under the License is distributed on an "AS IS" BASIS, 9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | * See the License for the specific language governing permissions and 11 | * limitations under the License. 12 | */ 13 | 14 | package logging 15 | 16 | import ( 17 | "fmt" 18 | "os" 19 | "time" 20 | 21 | "go.uber.org/zap" 22 | "go.uber.org/zap/zapcore" 23 | "gopkg.in/natefinch/lumberjack.v2" 24 | ) 25 | 26 | 27 | 28 | type OutPut int 29 | 30 | const ( 31 | OutPutFile OutPut = iota + 1 32 | OutPutStout 33 | ) 34 | 35 | type log struct { 36 | option *Option 37 | *zap.Logger 38 | } 39 | 40 | const ( 41 | DefaultLogName = "sim" 42 | DefaultLogPath = "./log" 43 | DefaultLogLevel = InfoLevel // debug、info、warn、error、panic、fatal 44 | ) 45 | 46 | var ( 47 | defaultOption = &Option{ 48 | logName: DefaultLogName, 49 | logPath: DefaultLogPath, 50 | level: DefaultLogLevel, 51 | } 52 | Log = &log{ 53 | option: defaultOption, 54 | Logger: nil, 55 | } 56 | ) 57 | 58 | type Level int 59 | 60 | const ( 61 | DebugLevel Level = iota - 1 62 | InfoLevel 63 | WarnLevel 64 | ErrorLevel 65 | DPanicLevel 66 | PanicLevel 67 | FatalLevel 68 | ) 69 | 70 | type Option struct { 71 | logName string 72 | logPath string 73 | level Level 74 | } 75 | 76 | type OptionFunc func(option *Option) 77 | 78 | func SetLogName(logName string) OptionFunc { 79 | return func(o *Option) { 80 | o.logName = logName 81 | } 82 | } 83 | func SetLogPath(logPath string) OptionFunc { 84 | return func(o *Option) { 85 | o.logName = logPath 86 | } 87 | } 88 | func SetLevel(level Level) OptionFunc { 89 | return func(o *Option) { 90 | o.level = level 91 | } 92 | } 93 | 94 | func init(){ 95 | core := getCores(OutPutStout, Log.option.logName) 96 | caller := zap.AddCaller() 97 | development := zap.Development() 98 | zlog := zap.New(core, caller, development) 99 | Log.Logger = zlog 100 | } 101 | 102 | func InitZapLogger(bug bool, ops ...OptionFunc) *log { 103 | var out OutPut 104 | if !bug { 105 | out = OutPutFile 106 | } else { 107 | out = OutPutStout 108 | } 109 | for _, v := range ops { 110 | v(Log.option) 111 | } 112 | core := getCores(out, Log.option.logName) 113 | caller := zap.AddCaller() 114 | development := zap.Development() 115 | zlog := zap.New(core, caller, development) 116 | Log.Logger = zlog 117 | return Log 118 | } 119 | func getZapEncoder() zapcore.EncoderConfig { 120 | return zapcore.EncoderConfig{ 121 | TimeKey: "time", 122 | LevelKey: "level", 123 | NameKey: "logger", 124 | CallerKey: "line", 125 | MessageKey: "msg", 126 | StacktraceKey: "stacktrace", 127 | LineEnding: zapcore.DefaultLineEnding, 128 | EncodeLevel: zapcore.LowercaseLevelEncoder, // 小写编码器 129 | EncodeTime: zapcore.ISO8601TimeEncoder, // ISO8601 UTC 时间格式 130 | EncodeDuration: zapcore.SecondsDurationEncoder, // 131 | EncodeCaller: zapcore.FullCallerEncoder, // 全路径编码器 132 | EncodeName: zapcore.FullNameEncoder, 133 | } 134 | } 135 | func getWriter(logPath, logfile string) lumberjack.Logger { 136 | if logPath == "" { 137 | logPath = "./log" 138 | } 139 | today := time.Now().Format("20060102") 140 | filename := fmt.Sprintf("%s/%s/%s", logPath, today, logfile) 141 | return lumberjack.Logger{ 142 | Filename: filename, // 日志文件路径 143 | MaxSize: 128, // 每个日志文件保存的最大尺寸 单位:M 128 144 | MaxBackups: 30, // 日志文件最多保存多少个备份 30 145 | MaxAge: 7, // 文件最多保存多少天 7 146 | Compress: true, // 是否压缩 147 | } 148 | } 149 | func getCores(output OutPut, serverName string) zapcore.Core { 150 | cores := []zapcore.Core{} 151 | encoderConfig := getZapEncoder() 152 | 153 | infoLevel := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool { return lvl == zapcore.InfoLevel }) 154 | waringLevel := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool { return lvl == zapcore.WarnLevel }) 155 | errorLevel := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool { return lvl == zapcore.ErrorLevel }) 156 | switch output { 157 | case OutPutStout: 158 | cores = append(cores, zapcore.NewCore(zapcore.NewJSONEncoder(encoderConfig), zapcore.NewMultiWriteSyncer(zapcore.AddSync(os.Stdout)), infoLevel)) 159 | cores = append(cores, zapcore.NewCore(zapcore.NewJSONEncoder(encoderConfig), zapcore.NewMultiWriteSyncer(zapcore.AddSync(os.Stdout)), waringLevel)) 160 | cores = append(cores, zapcore.NewCore(zapcore.NewJSONEncoder(encoderConfig), zapcore.NewMultiWriteSyncer(zapcore.AddSync(os.Stdout)), errorLevel)) 161 | case OutPutFile: 162 | // 获取 info、error日志文件的io.Writer 抽象 getWriter() 在下方实现 163 | infoWriter := getWriter("", serverName+"_info.log") 164 | waringWriter := getWriter("", serverName+"_waring.log") 165 | errorWriter := getWriter("", serverName+"_error.log") 166 | cores = append(cores, zapcore.NewCore(zapcore.NewJSONEncoder(encoderConfig), zapcore.NewMultiWriteSyncer(zapcore.AddSync(&infoWriter)), infoLevel)) 167 | cores = append(cores, zapcore.NewCore(zapcore.NewJSONEncoder(encoderConfig), zapcore.NewMultiWriteSyncer(zapcore.AddSync(&waringWriter)), waringLevel)) 168 | cores = append(cores, zapcore.NewCore(zapcore.NewJSONEncoder(encoderConfig), zapcore.NewMultiWriteSyncer(zapcore.AddSync(&errorWriter)), errorLevel)) 169 | } 170 | return zapcore.NewTee(cores...) 171 | } 172 | -------------------------------------------------------------------------------- /pkg/mock/conn.go: -------------------------------------------------------------------------------- 1 | package mock 2 | -------------------------------------------------------------------------------- /pkg/print/color.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 steven 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * Unless required by applicable law or agreed to in writing, software 8 | * distributed under the License is distributed on an "AS IS" BASIS, 9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | * See the License for the specific language governing permissions and 11 | * limitations under the License. 12 | */ 13 | 14 | package print 15 | 16 | import ( 17 | "fmt" 18 | "strconv" 19 | ) 20 | 21 | type Color int 22 | 23 | // Foreground text colors 24 | const ( 25 | FgBlack Color = iota + 30 26 | FgRed 27 | FgGreen 28 | FgYellow 29 | FgBlue 30 | FgMagenta 31 | FgCyan 32 | FgWhite 33 | ) 34 | 35 | // Foreground Hi-Intensity text colors 36 | const ( 37 | FgHiBlack Color = iota + 90 38 | FgHiRed 39 | FgHiGreen 40 | FgHiYellow 41 | FgHiBlue 42 | FgHiMagenta 43 | FgHiCyan 44 | FgHiWhite 45 | ) 46 | 47 | // Colorize a string based on given color. 48 | func PrintWithColor(s string, c Color) { 49 | fmt.Printf("\033[1;%s;40m%s\033[0m\n", strconv.Itoa(int(c)), s) 50 | } 51 | 52 | // Colorize a string based on given color. 53 | func PrintWithColorAndSpace(s string, c Color,front,back int) { 54 | base := fmt.Sprintf("\033[1;%s;40m%s\033[0m\n", strconv.Itoa(int(c)), s) 55 | for front > 0 { 56 | base = fmt.Sprintf("\n%s",base) 57 | front -- 58 | } 59 | for back > 0 { 60 | base = fmt.Sprintf("%s\n",base) 61 | back -- 62 | } 63 | fmt.Printf(base) 64 | } -------------------------------------------------------------------------------- /script/dockerfile/dockerfile: -------------------------------------------------------------------------------- 1 | # Version v1.01 2 | FROM frolvlad/alpine-glibc 3 | MAINTAINER steven 4 | RUN [./example] 5 | WORKDIR /steven 6 | VOLUME -------------------------------------------------------------------------------- /sim.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 steven 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * Unless required by applicable law or agreed to in writing, software 8 | * distributed under the License is distributed on an "AS IS" BASIS, 9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | * See the License for the specific language governing permissions and 11 | * limitations under the License. 12 | */ 13 | 14 | package sim 15 | 16 | import ( 17 | "context" 18 | "fmt" 19 | "github.com/mongofs/sim/pkg/conn" 20 | "github.com/mongofs/sim/pkg/logging" 21 | "github.com/zhenjl/cityhash" 22 | "go.uber.org/zap" 23 | "time" 24 | ) 25 | 26 | // init the bucket 27 | func (s *sim) initBucket() { 28 | // prepare buckets 29 | s.bs = make([]bucketInterface, s.opt.ServerBucketNumber) 30 | s.ctx, s.cancel = context.WithCancel(context.Background()) 31 | 32 | for i := 0; i < s.opt.ServerBucketNumber; i++ { 33 | s.bs[i] = NewBucket(s.opt, i, s.ctx) 34 | } 35 | 36 | logging.Log.Info("initBucket", zap.Int("BUCKET_NUMBER", s.opt.ServerBucketNumber)) 37 | logging.Log.Info("initBucket", zap.Int("BUCKET_SIZE", s.opt.BucketSize)) 38 | } 39 | 40 | func (s *sim) bucket(token string) bucketInterface { 41 | idx := s.routeBucket(token, uint32(s.opt.ServerBucketNumber)) 42 | return s.bs[idx] 43 | } 44 | 45 | func (s *sim) monitorBucket(ctx context.Context) (string, error) { 46 | var interval = 10 47 | var dataMonitorInterval = 60 48 | dataMonitorTimer := time.NewTimer(time.Duration(dataMonitorInterval) * time.Second) 49 | timer := time.NewTicker(time.Duration(interval) * time.Second) 50 | logging.Log.Info("monitorBucket ", zap.Int("MONITOR_ONLINE_INTERVAL", interval)) 51 | for { 52 | select { 53 | case <-ctx.Done(): 54 | return "monitorBucket", nil 55 | case <-timer.C: 56 | var sum int64 = 0 57 | for _, v := range s.bs { 58 | sum += int64(v.Count()) 59 | } 60 | s.num.Store(sum) 61 | if s.opt.debug == true { 62 | // you get get the pprof , 63 | pprof := fmt.Sprintf("http://127.0.0.1%v/debug/pprof", s.opt.PProfPort) 64 | 65 | logging.Log.Info("monitorBucket ", zap.Int64("ONLINE", s.num.Load()), zap.String("PPROF", pprof)) 66 | } else { 67 | logging.Log.Info("monitorBucket ", zap.Int64("ONLINE", s.num.Load())) 68 | } 69 | case <-dataMonitorTimer.C: 70 | content, loseContent, contentLength := conn.SwapSendData() 71 | logging.Log.Info("monitorBucket", 72 | zap.Int64("COUNT_LOSE_CONTENT", loseContent), 73 | zap.Int64("COUNT_CONTENT", content), 74 | zap.Int64("COUNT_CONTENT_LEN(Byte)", contentLength), 75 | zap.Int64("COUNT_CONTENT_LEN(KB)", contentLength/1024), 76 | zap.Int64("COUNT_CONTENT_LEN(MB)", contentLength/1024/1024)) 77 | } 78 | } 79 | } 80 | 81 | func (s *sim) routeBucket(token string, size uint32) uint32 { 82 | return cityhash.CityHash32([]byte(token), uint32(len(token))) % size 83 | } 84 | --------------------------------------------------------------------------------