序文
高可用性は、主に次の問題を解決します。
- 単一のノードではありません。どのサービスノードも、ダウンしたときに他のサービスノードを自動的に使用できます。
- 新しいサービスノードがサービスグループに入ることを許可しますが、クライアントはそれを認識しません。
サービス契約が異なれば、ソリューションも異なります。
grpc
- 推奨インデックス:5つ星
- 推奨される理由:現在の主流のetcd + grpcアーキテクチャには、成熟したパッケージ、多数の実際のケース、および質問するのに十分な人がいます。
grpcプロトコル、主流は高可用性のためにetcdベースのサービスディスカバリを使用します。彼のビジネスシナリオは、サービスグループのイントラネットが相互に電話をかけることです。各サービスノードが解放されると、サービスのホスト+ポートがetcdに登録され、キー値は/ user / node / 1、/ user / node / 2などの特定のサービスグループ名のプレフィックスです。登録のこのステップでは、5秒ごとにリースを維持する必要があります。
特定のサービスを呼び出す必要がある場合、プレフィックスマッチングの形式でetcdから利用可能なリストを取得し、バランスの取れた方法で利用可能なサービスを選択します。
etcdは、grpcプロトコルの高可用性のベストプラクティスを公式に提供しています。キーコードは次のとおりです。
発信者
import (
"go.etcd.io/etcd/clientv3"
etcdnaming "go.etcd.io/etcd/clientv3/naming"
"google.golang.org/grpc"
)
...
cli, cerr := clientv3.NewFromURL("http://localhost:2379")
r := &etcdnaming.GRPCResolver{
Client: cli}
b := grpc.RoundRobin(r)
conn, gerr := grpc.Dial("my-service", grpc.WithBalancer(b), grpc.WithBlock(), ...)
更新のためのサービスパーティー
go etcd.Register("x.x.x.x:port", "app_key", "y.y.y.y:port", 5)
package etcd
import (
"context"
"encoding/json"
"go.uber.org/zap"
"log"
"strings"
"time"
"fmt"
"go.etcd.io/etcd/client/v3"
)
var cli *clientv3.Client
// Register register service with name as prefix to etcd, multi etcd addr should use ; to split
func Register(etcdAddr, name string, addr string, ttl int64) error {
var err error
if cli == nil {
cli, err = clientv3.New(clientv3.Config{
Endpoints: strings.Split(etcdAddr, ";"),
DialTimeout: 15 * time.Second,
LogConfig: &zap.Config{
Level: zap.NewAtomicLevelAt(zap.ErrorLevel),
Development: false,
Sampling: &zap.SamplingConfig{
Initial: 100,
Thereafter: 100,
},
Encoding: "json",
EncoderConfig: zap.NewProductionEncoderConfig(),
// Use "/dev/null" to discard all
OutputPaths: []string{
"stderr"},
ErrorOutputPaths: []string{
"stderr"},
},
})
if err != nil {
return err
}
}
service := Service{
Addr: addr,
}
bts, err := json.Marshal(service)
if err != nil {
return err
}
serviceValue := string(bts)
serviceKey := fmt.Sprintf("%s/%s", name, serviceValue)
ticker := time.NewTicker(time.Second * time.Duration(ttl))
go func() {
for {
getResp, err := cli.Get(context.Background(), serviceKey)
if err != nil {
log.Println(err)
} else if getResp.Count == 0 {
err = withAlive(serviceKey, serviceValue, ttl)
if err != nil {
log.Println(err)
}
} else {
// do nothing
}
<-ticker.C
}
}()
return nil
}
type Service struct {
Addr string `json:"Addr"`
}
func withAlive(serviceKey string, serviceValue string, ttl int64) error {
leaseResp, err := cli.Grant(context.Background(), ttl)
if err != nil {
return err
}
fmt.Printf("key:%v\n", serviceKey)
_, err = cli.Put(context.Background(), serviceKey, serviceValue, clientv3.WithLease(leaseResp.ID))
if err != nil {
return err
}
ch, err := cli.KeepAlive(context.Background(), leaseResp.ID)
if err != nil {
log.Println(err)
return err
}
// ch管道的值需要持续取出释放,否则会占用通道导致切片饱和
go func() {
for {
_ = <-ch
}
}()
return nil
}
// UnRegister remove service from etcd
func UnRegister(serviceKey string) {
if cli != nil {
cli.Delete(context.Background(), serviceKey)
}
}
http
httpには多くの高可用性ソリューションがあり、おおよそ次の3つの主流があります。
- nginxの事前ルーティング、アップストリームを介して利用可能なサービスノードを構成します
- Tencent Cloudバックエンドは、ドメイン名<load balance>を複数のIPにサポートし、<healthcheck>を介してnginxと同じ効果を実現します。
- httpベースのロビンイコライザーを手動で実装し、etcdにアクセスします
最初
- 推奨インデックス:3つ星
- 理由:ノードの増減を維持するために手動でサーバーに移動する必要がありますが、これは非常に単純ではありません。高可用性は、アップストリームのロスレス移行と再起動に依存しており、手動での参加が必要ですが、これは比較的扱いにくいものです。
- サービスの前にnginxクラスターがあり、各nginxには次の構成例があります。
upstream srv_name_http {
server y.y.y.y:8112 weight=7;
server x.x.x.x:8112 weight=3;
server z.z.z.z:8112 weight=10;
}
server {
listen 80;
server_name your.addr.com;
error_log /data/log/nginx/your.addr.com.log;
# request header
proxy_read_timeout 3200;
proxy_send_timeout 3200;
proxy_set_header Host $http_host;
proxy_set_header Cookie $http_cookie;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
location / {
proxy_pass http://srv_name_http;
}
error_page 404 /404.html;
location = /40x.html {
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
}
}
nginxのメンテナンスでは、サーバーを分離するのが最善です。つまり、nginxを構成するためにサーバーに手動でログインする必要はありませんが、構成ファイル全体(通常はコードによって自動的に生成されます。エラー-proof)、プロジェクトディレクトリに埋め込まれています。展開中に、対応する場所にアップロードして実行しnginx -t
nginx -s reload
ます。メンテナンスコストを削減します。
二番目
- 推奨インデックス:5つ星
- 理由:より主流のソリューション、ユーザーのグループが多く、ロスレスである可能性があり、nginxを手動で保守する必要がない
- サーバーベンダーは通常、ドメイン名の負荷分散とヘルスチェックを提供します。httpドメイン名にはサーバーへの固定ポート80があるため、各サービスノードはnginxをproxy_passとして使用する必要があります。ただし、最初のものとの違いは、1。nginxとサービスノードが同じサーバーにバインドされており、nginxクラスターを事前に作成する必要がないことです。2. nginxにはサービスノードサーバーのみがあり、アップストリームを持つ必要はありません。
3番目のタイプ、
- etcdに基づいてhttpサービスディスカバリを実装します。
- 推奨インデックス:4つ星
- 理由:実装は単純に見えるかもしれませんが、優れたサービス検出コンポーネントを作成するには、etcdの原則とロスレス移行の原則を理解する必要があります。実行されている場合、その効果はetcd + grpcの効果と同等です。使用されている子供靴は使いやすいと言われていますが、実装はオープンソースを意図したものではありません、ハハ。
一般的な実装の原則は次のとおりです。
- プレフィックス付きで取得するたびに、すべての値をプルしてサービスのメモリに保存してください。
- プレフィックスキーの変更を監視し、キーが更新されると、メモリ内の値が削除されます。
- 呼び出し元は、etcdから直接取得するのではなく、メモリ内のキューから使用可能なURLのみを検索します。
しかし、真にロスレスであるためには、実装できる2つの方向があります。1つ
は、実装されたコンポーネントがラウンドロビントレーニングメカニズムを実装する必要があることです。リクエストが失敗すると、成功するかしきい値になるまで次のURLをリクエストし続けます。このようにして、ハートビートリースのウィンドウ期間を恐れることはありません。
次に、ルートをノードに追加できます。ノードが手動でオフラインになったら、消費バックログを待ってノードを閉じます。この実装の難しさはこのルートであり、curlはコマンドで記述する必要がありますlocalhost:xxxx/offline-from-etcd/
。このxxxxポートを見つけることは最も難しい部分です。
ここでは非表示ではありません。xxxxを見つける方法は、(ホスト名:ノード名)を介して環境変数にコピーを保存することです。その後、curlのxxxxがこの環境変数に置き換えられます。
はは、簡単じゃないですか。ホスト名を取得する方法は次のとおりです。os.GetHostname()
tcp、websocket
nginxをクライアントとハッシュのバランス戦略として使用すると、websokectを実現できます。TCPはテストされていません。
次の機能に焦点を当てて、サービスノードを追加します。
- クライアントがアクセスできる最速の利用可能なtcpIPを取得します
- 特定のサービスモジュールのIPとポートを取得し、戻ってクライアントに通知します
クライアントはtcpに直接接続されています。アーキテクチャ図は次のとおりです。
tcpの高可用性は、接続が確立されたときに確立された接続にのみ反映され、tcpサービスがハングしたときに確実にバウンスします。クライアントが自動的に再接続することをお勧めします。したがって、設計では、tcpはhttpほど複雑ではありません。