Computer >> कंप्यूटर ट्यूटोरियल >  >> प्रोग्रामिंग >> Redis

रेडिस हैश टेबल स्कैन की व्याख्या:तंत्र के अंदर

रेडिस हैश टेबल स्कैन की व्याख्या:तंत्र के अंदर <पी> एहुद तामीर

द्वारा <पी> एक सॉफ़्टवेयर डेवलपर के रूप में मेरे लिए बड़ी चुनौतियों में से एक अन्य लोगों के कोड को पढ़ना है। इस पोस्ट के लिए, मैंने सी कोड का एक दिलचस्प टुकड़ा पढ़ा जो मुझे पहले नहीं पता था, और मैं इसे आपके सामने प्रस्तुत करने जा रहा हूं। मैं जिस कोड के बारे में बात करने जा रहा हूं वह रेडिस का हिस्सा है डेटाबेस, और इसे यहां पाया जा सकता है।

<पी> रेडिस एक कुंजी-मूल्य डेटाबेस है। डेटाबेस में प्रत्येक प्रविष्टि एक कुंजी से एक मान तक मैपिंग है। मान कई प्रकार के हो सकते हैं. पूर्णांक, सूचियाँ, हैश तालिकाएँ और बहुत कुछ हैं। पर्दे के पीछे, डेटाबेस स्वयं भी एक हैश तालिका है। इस पोस्ट में, हम Redis में SCAN कमांड का पता लगाएंगे।

<पी> रेडिस हैश टेबल स्कैन की व्याख्या:तंत्र के अंदर _By © उपयोगकर्ता:कॉलिन / विकिमीडिया कॉमन्स, CC BY-SA 4.0, [https://commons.wikimedia.org/w/index.php?curid=30343877](https://commons.wikimedia.org/w/index.php?curid=30343877" rel='noopener' target=”blank” title=”)

रेडिस स्कैन

<पी> SCAN एक कर्सर-आधारित पुनरावृत्ति कमांड है, जो क्लाइंट को तालिका में सभी तत्वों पर जाने की अनुमति देता है। यह कर्सर-आधारित स्कैनर एक पूर्णांक कर्सर स्वीकार करता है प्रत्येक कॉल पर, और वस्तुओं का एक बैच लौटाता है और कर्सर मान स्कैन के लिए अगली कॉल में उपयोग किया जाएगा। प्रारंभिक कर्सर मान 0 है, और जब SCAN अगले कर्सर मान के रूप में 0 लौटाता है, तो इसका मतलब है कि स्कैन हो गया है और सभी तत्व क्लाइंट द्वारा देखे गए हैं।

<पी> SCAN कमांड में कुछ दिलचस्प गुण हैं:

  1. यह गारंटी देता है कि तालिका में मौजूद सभी आइटम कम से कम एक बार लौटाए जाएंगे .
  2. यह राज्यविहीन है . तालिका अपने सक्रिय स्कैनर के बारे में कोई डेटा सहेजती नहीं है। इसका मतलब यह भी है कि स्कैन डेटाबेस को लॉक नहीं करता है।
  3. यह तालिका का आकार बदलने के लिए लचीला है। O(1) एक्सेस समय को बनाए रखने के लिए, हैश टेबल एक निश्चित लोड फैक्टर बनाए रखते हैं। लोड फैक्टर मापता है कि किसी निश्चित समय पर तालिका कितनी "भरी" है। जब लोड फैक्टर बहुत बड़ा या बहुत छोटा हो जाता है, तो तालिका का आकार बदल दिया जाता है। तालिका का आकार बदलने के दौरान कॉल करने पर भी SCAN अपनी गारंटी बनाए रखेगा।

कार्यान्वयन

<पी> SCAN को dict.c में फ़ंक्शन dictScan() में लागू किया गया है . यह फ़ंक्शन का हस्ताक्षर और अतिरिक्त हाउसकीपिंग है:

unsigned long dictScan(dict *d,
 unsigned long v,
 dictScanFunction *fn,
 dictScanBucketFunction* bucketfn,
 void *privdata)
{
 dictht *t0, *t1;
 const dictEntry *de, *next;
 unsigned long m0, m1;
 if (dictSize(d) == 0) return 0;
 // ...
<पी> ध्यान देने योग्य बातें:

  • फ़ंक्शन 5 पैरामीटर स्वीकार करता है:dict *d , स्कैन किया जाने वाला शब्दकोश, unsigned long v , कर्सर, और 3 अन्य पैरामीटर जिन पर हम बाद में विचार करेंगे।
  • फ़ंक्शन इस फ़ंक्शन की अगली कॉल में उपयोग किए जाने वाले कर्सर मान को लौटाता है। यदि फ़ंक्शन 0 लौटाता है, तो इसका मतलब है कि स्कैन हो गया है।
  • if (dictSize(d) == 0) return 0; . जब शब्दकोश खाली होता है तो फ़ंक्शन 0 लौटाता है यह इंगित करने के लिए कि स्कैन हो गया है।

1. सामान्य स्कैनिंग

<पी> निम्नलिखित कोड तत्वों के एक समूह को स्कैन करता है:

if (!dictIsRehashing(d)) {
 t0 = &(d->ht[0]);
 m0 = t0->sizemask;
 /* Emit entries at cursor */
 if (bucketfn) bucketfn(privdata, &t0->table[v & m0]);
 de = t0->table[v & m0];
 while (de) {
 next = de->next;
 fn(privdata, de);
 de = next;
 }
 /* Set unmasked bits so incrementing the reversed cursor
 * operates on the masked bits */
 v |= ~m0;
 /* Increment the reverse cursor */
 v = rev(v);
 v++;
 v = rev(v);
} else {
 // ...
<पी> आइए इसे चरण-दर-चरण देखें। आइए नीचे पहली पंक्ति से शुरू करें:

if (!dictIsRehashing(d)) {
 t0 = &(d->ht[0]);
 m0 = t0->sizemask;
<पी> रीहैशिंग किसी तालिका के आकार बदलने के बाद तत्वों को समान रूप से फैलाने की प्रक्रिया है। dict.c हैश तालिका वृद्धिशील रूप से पुनः हैश होती है , जिसका अर्थ है कि यह पूरी तालिका को एक बार में नहीं, बल्कि थोड़ा-थोड़ा करके दोहराता है। टेबल पर किया गया प्रत्येक ऑपरेशन, जैसे जोड़ना, हटाना, ढूंढना, एक रीहैश चरण भी निष्पादित करता है। यह रीहैशिंग के दौरान संचालन के लिए तालिका को उपलब्ध रखने की अनुमति देता है। रीहैशिंग को लागू करने के तरीके के कारण, रीहैशिंग के दौरान फ़ंक्शन अलग तरह से काम करता है। हम यह देखकर शुरुआत करेंगे कि जब तालिका दोबारा नहीं बदल रही है तो क्या होता है।

<पी> हैश तालिका का एक सूचक स्थानीय चर t0 में सहेजा गया है , और इसके आकार का मुखौटा m0 में सहेजा गया है . आकार का मुखौटा: dict.c हैश टेबल हमेशा 2^n होते हैं आकार में. किसी दिए गए तालिका आकार के लिए, आकार मास्क 2^n-1 है , जो n के साथ एक बाइनरी संख्या है सबसे कम-महत्वपूर्ण बिट्स को 1 पर सेट किया गया है। उदाहरण के लिए, n=4; 2^n-1 = 00001111 के लिए . किसी दी गई कुंजी के लिए, हैश तालिका में उसका स्थान अंतिम n होगा हैश के टुकड़े कुंजी का. हम इसे थोड़ी देर में क्रियान्वित होते देखेंगे।

<पी> Dict.c हैश तालिका ओपन एड्रेसिंग का उपयोग करती है। तालिका में प्रत्येक प्रविष्टि परस्पर विरोधी हैश मान वाले तत्वों की एक लिंक्ड-सूची है। इसेबाल्टीकहा जाता है . इस अगले भाग में, एकबाल्टी तत्वों का स्कैन किया जाता है:

/* Emit entries at cursor */
if (bucketfn) bucketfn(privdata, &t0->table[v & m0]);
de = t0->table[v & m0];
while (de) {
 next = de->next;
 fn(privdata, de);
 de = next;
}
<पी> आकार के मास्क के उपयोग पर ध्यान दें :t0->table[v & m0] . v तालिकाe. v & की अनुक्रमणिका सीमा से बाहर हो सकता है m0 केवल las बनाए रखने के लिए साइज़ मास्क का उपयोग करता है टी एन अंक o f v, और तालिका में एक वैध सूचकांक उत्पन्न करता है।

<पी> अब तक आप अनुमान लगा चुके होंगे कि bucketfn क्या है के लिए है. bucketfn कॉलर द्वारा प्रदान किया जाता है और इसे तत्वों की प्रत्येक बकेट पर लागू किया जाता है। यह privdata भी पारित हो गया है , जो dictScan() को भेजा गया मनमाना डेटा है कॉल करने वाले द्वारा. इसी प्रकार, fn बकेट में सभी प्रविष्टियों पर एक-एक करके लागू किया जाता है। ध्यान दें कि एक बाल्टी खाली हो सकती है, ऐसी स्थिति में इसका मान NULL है .

<पी> ठीक है, तो हमने तत्वों की एक बाल्टी पर पुनरावृति की। आगे क्या होगा? हम अगली कॉल के लिए कर्सर मान को dictScan() पर वापस करने जा रहे हैं . यह वर्तमान कर्सर v को बढ़ाकर किया जाता है , लेकिन एक मोड़ के साथ! कर्सर को पहले उलटा किया जाता है, फिर बढ़ाया जाता है, और फिर दोबारा उलटा किया जाता है:

 /* Set unmasked bits so incrementing the reversed cursor
 * operates on the masked bits */
 v |= ~m0;
 /* Increment the reverse cursor */
 v = rev(v);
 v++;
 v = rev(v);
<पी> पहला, v |= ~m0 सभी गैर-नकाबपोश बिट्स को v में सेट करता है से 1. प्रभाव यह है कि v को उलटने पर और बढ़ते-बढ़ते, इन बिट्स को प्रभावी ढंग से अनदेखा कर दिया जाएगा। फिर, v उलटा किया जाता है, बढ़ाया जाता है और फिर उलटा किया जाता है। आइए एक उदाहरण देखें:

Table size = 16 (n = 4, m0 = 16-1 = 00001111)
v = 00001000 (Current cursor)
v |= ~m0; // v == 11111000 (~m0 = 11110000)
v = rev(v); // v == 00011111
v++; // v == 00100000
v = rev(v); // v == 00000100
<पी> इस बिट-मैजिक के बाद, v लौटा दिया जाता है.

<पी> कर्सर को बढ़ने से पहले उलटा क्यों किया जाता है? तालिका पुनरावृत्तियों के बीच बढ़ सकती है। यह गारंटी देता है कि कर्सर वैध रहता है। जब तालिका बड़ी हो जाती है, तो उसके आकार के मास्क में बाईं ओर से अतिरिक्त बिट्स जोड़ दिए जाते हैं . उलटी संख्या को बढ़ाकर, हम छोटी तालिका के सूचकांकों को बड़ी तालिका में विस्तारित कर सकते हैं।

<पी> उदाहरण के लिए:मान लें कि पुरानी तालिका का आकार 16 था (आकार मुखौटा 00001111 ) और कर्सर 00001000 था . जब तालिका 32 तत्वों तक बढ़ जाती है तो इसका आकार मास्क 00011111 होगा . 00001000 में पहले के सभी तत्व स्लॉट या तो 00001000 पर मैप होगा या 00011000 नई तालिका में. ये कर्सर छोटी और बड़ी दोनों तालिकाओं के साथ संगत हैं!

2. टेबल रीहैशिंग के दौरान स्कैनिंग

<पी> अंतिम भाग हमें यह समझने की आवश्यकता है कि तालिका रीहैशिंग के दौरान स्कैन कैसे काम करता है। वृद्धिशील पुनरावृत्ति एक ही समय में दो सक्रिय तालिकाओं द्वारा dict.c में कार्यान्वित किया जाता है। हैश तालिका का आकार बदलने पर एक दूसरी तालिका बनाई जाती है। नई तालिका में नए आइटम जोड़े जाते हैं. प्रत्येक रीहैश चरण पर पुरानी तालिका से तत्वों को नई तालिका में ले जाया जाता है। जब पुरानी टेबल खाली हो जाती है तो उसे हटा दिया जाता है।

<पी> स्कैन करते समय, पुरानी और नई दोनों तालिकाओं को छोटे से शुरू करके तत्वों के लिए स्कैन किया जाता है। टेबल . छोटी तालिका में आइटम स्कैन करने के बाद, पूरक आइटम बड़ी तालिका से स्कैन किया जाता है। इस प्रकार कर्सर द्वारा कवर किए गए सभी तत्व v स्कैन किये जाते हैं. आइए देखें कि यह कैसा दिखता है। यह संपूर्ण कोड स्निपेट है, हम इसे नीचे विघटित करेंगे:

 } else { // dictIsRehashing(d)
 t0 = &d->ht[0];
 t1 = &d->ht[1];
 /* Make sure t0 is the smaller and t1 is the bigger table */
 if (t0->size > t1->size) {
 t0 = &d->ht[1];
 t1 = &d->ht[0];
 }
 m0 = t0->sizemask;
 m1 = t1->sizemask;
 /* Emit entries at cursor */
 if (bucketfn) bucketfn(privdata, &t0->table[v & m0]);
 de = t0->table[v & m0];
 while (de) {
 next = de->next;
 fn(privdata, de);
 de = next;
 }
 /* Iterate over indices in larger table that are the expansion
 * of the index pointed to by the cursor in the smaller table */
 do {
 /* Emit entries at cursor */
 if (bucketfn) bucketfn(privdata, &t1->table[v & m1]);
 de = t1->table[v & m1];
 while (de) {
 next = de->next;
 fn(privdata, de);
 de = next;
 }
 /* Increment the reverse cursor not covered by the smaller mask.*/
 v |= ~m1;
 v = rev(v);
 v++;
 v = rev(v);
 /* Continue while bits covered by mask difference is non-zero */
 } while (v & (m0 ^ m1));
 }
<पी> सबसे पहले, t0 और t1 m0 के साथ क्रमशः छोटी और बड़ी तालिकाओं को संग्रहीत करने के लिए उपयोग किया जाता है और m1 प्रत्येक के लिए आकार के मुखौटे। फिर छोटी टेबल को स्कैन किया जाता है, जैसा हमने पहले देखा था।

<पी> इसके बाद, बड़े आकार के मास्क m1 का उपयोग करके कर्सर को बड़ी तालिका में अनुक्रमित करने के लिए उपयोग किया जाता है। :de = t1->table[v & m1] . आंतरिक लूप में, छोटी तालिका के सूचकांक के सभी विस्तारों को कवर करने के लिए कर्सर को बढ़ाया जाता है।

<पी> उदाहरण के लिए, यदि छोटी तालिका में बकेट का सूचकांक 0100 था , और बड़ी तालिका दोगुनी बड़ी है, इस लूप में शामिल सूचकांक 00100 होंगे और 10100 . 'जबकि करो' की स्थिति कर्सर को छोटी तालिका की बकेट द्वारा कवर की गई सीमा से आगे बढ़ने से रोकती है:while (v & (m0 ^ m1)); . इसे अंतिम रूप से समझने के लिए मैं इसे आप पर छोड़ता हूँ :)

<पी> बस इतना ही! हमने संपूर्ण हैश टेबल स्कैन फ़ंक्शन को कवर कर लिया है। एकमात्र गायब हिस्सा rev(v) का कार्यान्वयन है , जो किसी संख्या में बिट्स को उलटने का एक सामान्य कार्य है। dict.c में प्रयुक्त कार्यान्वयन विशेष रूप से दिलचस्प है क्योंकि यह O(log n) रनिंग टाइम प्राप्त करता है। मैं इसे भविष्य की पोस्ट में शामिल कर सकता हूं।

<पी> पढ़ने के लिए धन्यवाद! उनकी प्रेरणा और समर्थन के लिए डीविर वोल्क को बहुत धन्यवाद! जेसन ली को उनकी प्रतिक्रिया के लिए धन्यवाद जिससे मुझे पोस्ट में त्रुटि सुधारने में मदद मिली।

<पी> मुफ़्त में कोड करना सीखें. फ्रीकोडकैंप के ओपन सोर्स पाठ्यक्रम ने 40,000 से अधिक लोगों को डेवलपर्स के रूप में नौकरी पाने में मदद की है। आरंभ करें


  1. जावास्क्रिप्ट के साथ टाइपिंग इफेक्ट कैसे बनाएं? जावास्क्रिप्ट के साथ टाइपिंग इफेक्ट कैसे बनाएं?

    JavaScript के साथ टाइपिंग इफेक्ट बनाने के लिए, कोड इस प्रकार है - उदाहरण <!DOCTYPE html> <html> <head> <style>    body{       font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;    }    button{      

  1. एंड्रॉइड मोबाइल मैग्नेटिक फील्ड सेंसर का समर्थन कैसे करें? एंड्रॉइड मोबाइल मैग्नेटिक फील्ड सेंसर का समर्थन कैसे करें?

    यह उदाहरण इस बारे में प्रदर्शित करता है कि एंड्रॉइड मोबाइल कैसे जांचें मैग्नेटिक फील्ड सेंसर का समर्थन करता है चरण 1 - एंड्रॉइड स्टूडियो में एक नया प्रोजेक्ट बनाएं, फाइल ⇒ न्यू प्रोजेक्ट पर जाएं और एक नया प्रोजेक्ट बनाने के लिए सभी आवश्यक विवरण भरें। चरण 2 - निम्न कोड को res/layout/activity_main.x

  1. HTML DOM वीडियो प्रीलोड प्रॉपर्टी HTML DOM वीडियो प्रीलोड प्रॉपर्टी

    HTML DOM वीडियो प्रीलोड प्रॉपर्टी एक स्ट्रिंग लौटाती है, जब क्रिएटर को लगता है कि वीडियो डेटा लोड होना चाहिए। हालांकि कभी-कभी इसे अनदेखा किया जा सकता है। डिफ़ॉल्ट मान मेटाडेटा है। सिंटैक्स निम्नलिखित वाक्य रचना है - रिटर्निंग स्ट्रिंग मान mediaObject.preload प्रीलोड सेट करना एक स्ट्रिंग के लिए me