raftexample هو مثال مقدم من etcd يوضح استخدام مكتبة خوارزمية توافق الطوافة etcd. تنفذ raftexample في النهاية خدمة تخزين ذات قيمة مفتاحية موزعة توفر REST API.
ستقرأ هذه المقالة وتحلل كود raftexample، على أمل مساعدة القراء على فهم أفضل لكيفية استخدام مكتبة الطوافة etcd ومنطق التنفيذ لمكتبة الطوافة.
بنية raftexample بسيطة جدًا، مع الملفات الرئيسية كما يلي:
يصل طلب الكتابة إلى طريقة SubmitHTTP لوحدة httpapi عبر طلب HTTP PUT.
curl -L http://127.0.0.1:12380/key -XPUT -d value
بعد مطابقة طريقة طلب HTTP عبر المحول، يدخل في تدفق معالجة طريقة PUT:
يتم تقديم الاقتراح إلى مكتبة خوارزمية الطوافة من خلال طريقة الاقتراح التي توفرها مكتبة خوارزمية الطوافة.
يمكن أن يكون محتوى الاقتراح إضافة زوج جديد من القيمة الرئيسية، أو تحديث زوج من القيمة الرئيسية الحالية، وما إلى ذلك.
// httpapi.go v, err := io.ReadAll(r.Body) if err != nil { log.Printf("Failed to read on PUT (%v)\n", err) http.Error(w, "Failed on PUT", http.StatusBadRequest) return } h.store.Propose(key, string(v)) w.WriteHeader(http.StatusNoContent)
بعد ذلك، دعونا نلقي نظرة على طريقة الاقتراح لوحدة kvstore لمعرفة كيفية إنشاء الاقتراح ومعالجته.
في طريقة الاقتراح، نقوم أولاً بتشفير زوج المفتاح والقيمة المراد كتابته باستخدام gob، ثم نقوم بتمرير المحتوى المشفر إلى ProposC، وهي قناة مسؤولة عن نقل المقترحات التي أنشأتها وحدة kvstore إلى وحدة raft.
// kvstore.go func (s *kvstore) Propose(k string, v string) { var buf strings.Builder if err := gob.NewEncoder(&buf).Encode(kv{k, v}); err != nil { log.Fatal(err) } s.proposeCيتم استلام الاقتراح الذي تم إنشاؤه بواسطة kvstore وتمريره إلى SubmitC ومعالجته بواسطة طريقةservChannels في وحدة الطوافة.
بعد التأكد من عدم إغلاق ProposeC، تقوم وحدة الطوافة بإرسال الاقتراح إلى مكتبة خوارزمية الطوافة للمعالجة باستخدام طريقة الاقتراح التي توفرها مكتبة خوارزمية الطوافة.
// raft.go select { case prop, ok :=بعد تقديم الاقتراح، فإنه يتبع عملية خوارزمية الطوافة. ستتم إعادة توجيه الاقتراح في النهاية إلى العقدة الرائدة (إذا لم تكن العقدة الحالية هي العقدة الرائدة وتسمح للمتابعين بإعادة توجيه المقترحات، والتي يتم التحكم فيها بواسطة تكوين DisableProposalForwarding). سيضيف القائد الاقتراح كمدخل سجل إلى سجل الطوافة الخاص به وسيقوم بمزامنته مع العقد التابعة الأخرى. وبعد اعتباره ملتزمًا، سيتم تطبيقه على جهاز الحالة وسيتم إرجاع النتيجة إلى المستخدم.
ومع ذلك، نظرًا لأن مكتبة الطوافة etcd نفسها لا تتعامل مع الاتصال بين العقد، والإلحاق بسجل الطوافة، والتطبيق على جهاز الحالة، وما إلى ذلك، فإن مكتبة الطوافة تقوم فقط بإعداد البيانات المطلوبة لهذه العمليات. يجب أن نقوم بتنفيذ العمليات الفعلية.
لذلك، نحتاج إلى تلقي هذه البيانات من مكتبة الطوافة ومعالجتها وفقًا لنوعها. تقوم الطريقة Ready بإرجاع قناة للقراءة فقط يمكننا من خلالها تلقي البيانات التي تحتاج إلى معالجة.
تجدر الإشارة إلى أن البيانات المستلمة تتضمن حقولًا متعددة، مثل اللقطات التي سيتم تطبيقها، وإدخالات السجل التي سيتم إلحاقها بسجل الطوافة، والرسائل التي سيتم إرسالها عبر الشبكة، وما إلى ذلك.
بالاستمرار في مثال طلب الكتابة (العقدة الرئيسية)، بعد تلقي البيانات المقابلة، نحتاج إلى حفظ اللقطات والحالة الصلبة والإدخالات بشكل مستمر لمعالجة المشكلات الناجمة عن تعطل الخادم (على سبيل المثال، تصويت أحد المتابعين لعدة مرشحين). يشكل HardState وEntries معًا الحالة المستمرة على جميع الخوادم كما هو مذكور في الورقة. وبعد حفظها باستمرار، يمكننا تطبيق اللقطة وإلحاقها بسجل الطوافة.
نظرًا لأننا حاليًا العقدة الرائدة، فسوف تقوم مكتبة الطوافة بإرجاع رسائل من نوع MsgApp إلينا (المقابلة لـ AppendEntries RPC في الورقة). نحتاج إلى إرسال هذه الرسائل إلى العقد التابعة. هنا، نستخدم rafthttp المقدم من etcd للاتصال بالعقدة وإرسال الرسائل إلى العقد التابعة باستخدام طريقة الإرسال.
// raft.go case rd :=بعد ذلك، نستخدم طريقة PublishEntries لتطبيق إدخالات سجل الطوافة الملتزمة على جهاز الحالة. كما ذكرنا سابقًا، في raftexample، تعمل وحدة kvstore كجهاز الحالة. في طريقة PublishEntries، نقوم بتمرير إدخالات السجل التي يجب تطبيقها على جهاز الحالة للالتزام C. كما هو الحال مع ProposC السابق، فإن الالتزام هو المسؤول عن إرسال إدخالات السجل التي اعتبرتها وحدة الطوافة ملتزمة بوحدة kvstore لتطبيقها على جهاز الحالة.
// raft.go rc.commitCفي طريقة readCommits لوحدة kvstore، يتم فك تشفير الرسائل المقروءة من الالتزام C لاسترداد أزواج القيمة الرئيسية الأصلية، والتي يتم تخزينها بعد ذلك في بنية خريطة داخل وحدة kvstore.
// kvstore.go for commit := range commitC { ... for _, data := range commit.data { var dataKv kv dec := gob.NewDecoder(bytes.NewBufferString(data)) if err := dec.Decode(&dataKv); err != nil { log.Fatalf("raftexample: could not decode message (%v)", err) } s.mu.Lock() s.kvStore[dataKv.Key] = dataKv.Val s.mu.Unlock() } close(commit.applyDoneC) }بالعودة إلى وحدة الطوافة، نستخدم الطريقة المتقدمة لإعلام مكتبة الطوافة بأننا انتهينا من معالجة البيانات المقروءة من القناة الجاهزة وأننا جاهزون لمعالجة الدفعة التالية من البيانات.
في وقت سابق، على العقدة الرائدة، أرسلنا رسائل من نوع MsgApp إلى العقد التابعة باستخدام طريقة الإرسال. يستمع rafthttp الخاص بالعقدة التابعة إلى المنفذ المقابل لتلقي الطلبات وإرجاع الاستجابات. سواء كان ذلك طلبًا تم استلامه بواسطة عقدة تابعة أو استجابة تم تلقيها بواسطة عقدة رائدة، فسيتم إرساله إلى مكتبة الطوافة للمعالجة من خلال طريقة الخطوة.
تقوم raftNode بتنفيذ واجهة Raft في rafthttp، ويتم استدعاء طريقة المعالجة لواجهة Raft للتعامل مع محتوى الطلب المستلم (مثل رسائل MsgApp).
// raft.go func (rc *raftNode) Process(ctx context.Context, m raftpb.Message) error { return rc.node.Step(ctx, m) }ما ورد أعلاه يصف تدفق المعالجة الكامل لطلب الكتابة في raftexample.
ملخص
وبهذا ينتهي محتوى هذه المقالة. من خلال تحديد هيكل نموذج raftexample وتفصيل تدفق المعالجة لطلب الكتابة، آمل أن أساعدك على فهم أفضل لكيفية استخدام مكتبة الطوافة etcd لبناء خدمة تخزين KV الموزعة الخاصة بك.
إذا كان هناك أي أخطاء أو مشاكل، فلا تتردد في التعليق أو مراسلتي مباشرة. شكرًا لك.
مراجع
https://github.com/etcd-io/etcd/tree/main/contrib/raftexample
https://github.com/etcd-io/raft
https://raft.github.io/raft.pdf
تنصل: جميع الموارد المقدمة هي جزئيًا من الإنترنت. إذا كان هناك أي انتهاك لحقوق الطبع والنشر الخاصة بك أو الحقوق والمصالح الأخرى، فيرجى توضيح الأسباب التفصيلية وتقديم دليل على حقوق الطبع والنشر أو الحقوق والمصالح ثم إرسالها إلى البريد الإلكتروني: [email protected]. سوف نتعامل مع الأمر لك في أقرب وقت ممكن.
Copyright© 2022 湘ICP备2022001581号-3