github.com/polarismesh/polaris@v1.17.8/apiserver/eurekaserver/replicate_worker.go (about)

     1  /**
     2   * Tencent is pleased to support the open source community by making Polaris available.
     3   *
     4   * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
     5   *
     6   * Licensed under the BSD 3-Clause License (the "License");
     7   * you may not use this file except in compliance with the License.
     8   * You may obtain a copy of the License at
     9   *
    10   * https://opensource.org/licenses/BSD-3-Clause
    11   *
    12   * Unless required by applicable law or agreed to in writing, software distributed
    13   * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
    14   * CONDITIONS OF ANY KIND, either express or implied. See the License for the
    15   * specific language governing permissions and limitations under the License.
    16   */
    17  
    18  package eurekaserver
    19  
    20  import (
    21  	"bytes"
    22  	"context"
    23  	"encoding/json"
    24  	"fmt"
    25  	"io"
    26  	"net/http"
    27  	"time"
    28  
    29  	"github.com/emicklei/go-restful/v3"
    30  
    31  	"github.com/polarismesh/polaris/common/version"
    32  )
    33  
    34  type ReplicateWorker struct {
    35  	namespace   string
    36  	peers       []string
    37  	taskChannel chan *ReplicationInstance
    38  	ctx         context.Context
    39  }
    40  
    41  const (
    42  	batchMaxInterval   = 5 * time.Second
    43  	batchReplicateSize = 10
    44  )
    45  
    46  func NewReplicateWorker(ctx context.Context, namespace string, peers []string) *ReplicateWorker {
    47  	worker := &ReplicateWorker{
    48  		namespace:   namespace,
    49  		peers:       peers,
    50  		taskChannel: make(chan *ReplicationInstance, 1000),
    51  		ctx:         ctx,
    52  	}
    53  	go worker.batchReplicate()
    54  	return worker
    55  }
    56  
    57  func (r *ReplicateWorker) AddReplicateTask(task *ReplicationInstance) {
    58  	r.taskChannel <- task
    59  }
    60  
    61  func (r *ReplicateWorker) batchReplicate() {
    62  	batchTasks := make([]*ReplicationInstance, 0)
    63  	batchTicker := time.NewTicker(batchMaxInterval)
    64  	for {
    65  		select {
    66  		case <-r.ctx.Done():
    67  			eurekalog.Infof("[EUREKA-SERVER] replicate worker done")
    68  			batchTicker.Stop()
    69  		case task := <-r.taskChannel:
    70  			batchTasks = append(batchTasks, task)
    71  			if len(batchTasks) == batchReplicateSize {
    72  				// replicate at once when batch size reach max threshold
    73  				r.doBatchReplicate(batchTasks)
    74  				batchTasks = make([]*ReplicationInstance, 0)
    75  			}
    76  		case <-batchTicker.C:
    77  			if len(batchTasks) > 0 {
    78  				// replicate when time interval reached
    79  				r.doBatchReplicate(batchTasks)
    80  				batchTasks = make([]*ReplicationInstance, 0)
    81  			}
    82  		}
    83  	}
    84  }
    85  
    86  func (r *ReplicateWorker) doBatchReplicate(tasks []*ReplicationInstance) {
    87  	request := &ReplicationList{ReplicationList: make([]*ReplicationInstance, 0, len(tasks))}
    88  	for _, task := range tasks {
    89  		if task.Action == actionHeartbeat {
    90  			request.ReplicationList = append(request.ReplicationList, &ReplicationInstance{
    91  				AppName:            task.AppName,
    92  				Id:                 task.Id,
    93  				LastDirtyTimestamp: task.LastDirtyTimestamp,
    94  				OverriddenStatus:   task.OverriddenStatus,
    95  				Status:             task.Status,
    96  				Action:             task.Action,
    97  			})
    98  		} else {
    99  			request.ReplicationList = append(request.ReplicationList, task)
   100  		}
   101  	}
   102  	jsonData, err := json.Marshal(request)
   103  	if nil != err {
   104  		eurekalog.Errorf("[EUREKA-SERVER] fail to marshal replicate tasks: %v", err)
   105  		return
   106  	}
   107  	replicateInfo := make([]string, 0, len(tasks))
   108  	for _, task := range tasks {
   109  		replicateInfo = append(replicateInfo, fmt.Sprintf("%s:%s", task.Action, task.Id))
   110  	}
   111  	eurekalog.Infof("start to send replicate text %s, peers %v", string(jsonData), r.peers)
   112  	for _, peer := range r.peers {
   113  		go r.doReplicateToPeer(peer, tasks, jsonData, replicateInfo)
   114  	}
   115  }
   116  
   117  func (r *ReplicateWorker) doReplicateToPeer(
   118  	peer string, tasks []*ReplicationInstance, jsonData []byte, replicateInfo []string) {
   119  	response, err := sendHttpRequest(r.namespace, peer, jsonData, replicateInfo)
   120  	if nil != err {
   121  		eurekalog.Errorf("[EUREKA-SERVER] fail to batch replicate to %s, err: %v", peer, err)
   122  		return
   123  	}
   124  	if len(response.ResponseList) == 0 {
   125  		return
   126  	}
   127  	for i, respInstance := range response.ResponseList {
   128  		if respInstance.StatusCode == http.StatusNotFound {
   129  			task := tasks[i]
   130  			if task.Action == actionHeartbeat {
   131  				eurekalog.Infof("[EUREKA-SERVER] instance %s of service %s not exists in %s, do register instance info %+v",
   132  					task.Id, task.AppName, peer, task.InstanceInfo)
   133  				// do the re-register
   134  				registerTask := &ReplicationInstance{
   135  					AppName:            task.AppName,
   136  					Id:                 task.Id,
   137  					LastDirtyTimestamp: task.LastDirtyTimestamp,
   138  					Status:             StatusUp,
   139  					InstanceInfo:       task.InstanceInfo,
   140  					Action:             actionRegister,
   141  				}
   142  				r.AddReplicateTask(registerTask)
   143  			}
   144  		}
   145  	}
   146  }
   147  
   148  type ReplicateWorkers struct {
   149  	works map[string]*ReplicateWorker
   150  }
   151  
   152  func NewReplicateWorkers(ctx context.Context, namespacePeers map[string][]string) *ReplicateWorkers {
   153  	works := make(map[string]*ReplicateWorker)
   154  	for namespace, peers := range namespacePeers {
   155  		works[namespace] = NewReplicateWorker(ctx, namespace, peers)
   156  	}
   157  	return &ReplicateWorkers{
   158  		works: works,
   159  	}
   160  }
   161  
   162  func (r *ReplicateWorkers) Get(namespace string) (*ReplicateWorker, bool) {
   163  	work, exist := r.works[namespace]
   164  	return work, exist
   165  }
   166  
   167  func sendHttpRequest(namespace string, peer string,
   168  	jsonData []byte, replicateInfo []string) (*ReplicationListResponse, error) {
   169  	client := &http.Client{}
   170  	req, err := http.NewRequest(http.MethodPost,
   171  		fmt.Sprintf("http://%s/eureka/peerreplication/batch/", peer), bytes.NewBuffer(jsonData))
   172  	if nil != err {
   173  		eurekalog.Errorf("[EUREKA-SERVER] fail to create replicate request: %v", err)
   174  		return nil, err
   175  	}
   176  	req.Header.Set(headerIdentityName, valueIdentityName)
   177  	req.Header.Set(headerIdentityVersion, version.Version)
   178  	req.Header.Set(restful.HEADER_ContentType, restful.MIME_JSON)
   179  	req.Header.Set(restful.HEADER_Accept, restful.MIME_JSON)
   180  	if len(namespace) != 0 {
   181  		req.Header.Set(HeaderNamespace, namespace)
   182  	}
   183  	response, err := client.Do(req)
   184  	if err != nil {
   185  		eurekalog.Errorf("[EUREKA-SERVER] fail to send replicate request: %v", err)
   186  		return nil, err
   187  	}
   188  	defer func() {
   189  		if nil != response && nil != response.Body {
   190  			_ = response.Body.Close()
   191  		}
   192  	}()
   193  	respStr, _ := io.ReadAll(response.Body)
   194  	respObj := &ReplicationListResponse{}
   195  	err = json.Unmarshal(respStr, respObj)
   196  	if nil != err {
   197  		eurekalog.Errorf("[EUREKA-SERVER] fail unmarshal text %s to ReplicationListResponse: %v", string(respStr), err)
   198  		return nil, err
   199  	}
   200  	eurekalog.Infof("[EUREKA-SERVER] success to replicate to %s, instances %v", peer, replicateInfo)
   201  	return respObj, nil
   202  }