Arabic (العربية/عربي) translation by ansgaradh (you can also view the original English article)
في حالة الاستخدام الصحيحة ، تبدو مرشحات بلوم مثل السحر. هذا بيان جريء ، ولكن في هذا البرنامج التعليمي سوف نستكشف بنية البيانات الغريبة ، وأفضل طريقة لاستخدامها ، وبعض الأمثلة العملية باستخدام Redis و Node.js.
مرشحات بلوم هي بنية احتمالية للبيانات أحادية الاتجاه. يمكن أن تكون كلمة "فلتر" مربكة في هذا السياق ؛ يشير الفلتر إلى أنه شيء نشط ، فعل ، ولكن قد يكون من الأسهل التفكير فيه كخزن ، اسم. باستخدام فلتر بلوم بسيط يمكنك القيام بأمرين:
- أضف بندًا.
- تحقق من عدم إضافة عنصر مسبقًا.
هذه قيود مهمة يجب فهمها - لا يمكنك إزالة عنصر ولا يمكنك إدراج العناصر في مرشح بلوم. أيضا ، لا يمكنك أن تخبر ، على وجه اليقين ، أنه إذا تمت إضافة عنصر إلى الفلتر في الماضي. هذا هو المكان الذي تأتي فيه الطبيعة الاحتمالية لفلتر بلوم - من الممكن أن تكون الإيجابيات الخاطئة ممكنة ، ولكن السلبيات الخاطئة ليست كذلك. إذا تم إعداد الفلتر بشكل صحيح ، فقد تكون الإيجابيات الزائفة نادرة للغاية.
توجد متغيرات من مرشحات Bloom ، وتضيف قدرات أخرى ، مثل الإزالة أو التحجيم ، ولكنها تضيف أيضًا إلى التعقيد والقيود. من المهم أولاً فهم فلاتر بلوم البسيطة قبل الانتقال إلى المتغيرات. هذه المادة سوف تغطي فقط مرشحات بلوم بسيطة.
مع هذه القيود ، لديك عدد من المزايا: حجم ثابت ، تشفير يستند إلى التجزئة ، وعمليات بحث سريعة.
عندما تقوم بإعداد مرشح بلوم ، فأنت تعطي حجمًا. تم إصلاح هذا الحجم ، لذلك إذا كان لديك عنصر واحد أو مليار عنصر في الفلتر ، فلن يتعدى الحجم المحدد. عند إضافة المزيد من العناصر إلى الفلتر ، فإن فرصة حدوث زيادات إيجابية خاطئة. إذا حددت فلترًا أصغر ، فسيزيد هذا المعدل الموجب الخاطئ بسرعة أكبر مما لو كان حجمك أكبر.
بنيت مرشحات بلوم على مفهوم تجزئة في اتجاه واحد. مثل الكثير من تخزين كلمات المرور بشكل صحيح ، تستخدم مرشحات Bloom خوارزمية تجزئة لتحديد معرف فريد للعناصر التي تم تمريرها إليه. لا يمكن عكس الزوائد ، بطبيعتها ، ويتم تمثيلها من خلال سلسلة أحرف تبدو عشوائية. لذلك ، إذا تمكن شخص ما من الوصول إلى مرشح بلوم ، فلن يكشف عن أي من المحتويات مباشرة.
أخيرا ، مرشحات بلوم سريعة. تشتمل العملية على مقارنات أقل بكثير من الطرق الأخرى ، ويمكن تخزينها بسهولة في الذاكرة ، مما يحول دون الوصول إلى بيانات قاعدة بيانات الأداء.
الآن بعد أن عرفت حدود وفلاتر بلوم ، دعنا نلقي نظرة على بعض المواقف التي يمكنك استخدامها.
اقامة
سنستخدم Redis و Node.js لتوضيح مرشحات Bloom. Redis هو وسيلة تخزين لمرشح Bloom الخاص بك؛ انها سريعة ، في الذاكرة ، ولها بعض الأوامر المحددة (GETBIT
، SETBIT
) التي تجعل التنفيذ فعالة. سأفترض أن لديك Node.js و npm و Redis مثبت على نظامك. يجب تشغيل خادم Redis الخاص بك على localhost
في المنفذ الافتراضي حتى تعمل الأمثلة الخاصة بنا.
في هذا البرنامج التعليمي ، لن نطبق فلترًا من الألف إلى الياء ؛ بدلاً من ذلك ، سنركز على الاستخدامات العملية باستخدام وحدة مدمجة مسبقًا في npm: bloom-redis . ازهر رديس لديه مجموعة موجزة جدا من الطرق: إضافة
، يحتوي
و اضحة
.
كما ذكرنا سابقًا ، تحتاج عوامل تصفية Bloom إلى خوارزمية تجزئة لإنشاء معرفات فريدة لعنصر ما. يستخدم bloom-redis خوارزمية MD5 المعروفة ، والتي ، على الرغم من أنها ربما ليست مناسبة تمامًا لمرشح Bloom (بطيء قليلاً ، مبالغة في البتات) ، ستعمل بشكل جيد.
أسماء المستخدمين الفريدة
يجب أن تكون أسماء المستخدمين ، خاصةً تلك التي تحدد هوية مستخدم في عنوان URL ، فريدة. إذا كنت تنشئ تطبيقًا يتيح للمستخدمين تغيير اسم المستخدم ، فستحتاج على الأرجح إلى اسم مستخدم لم يتم استخدامه مطلقًا لتجنب حدوث ارتباك ولقاء أسماء مستخدمين.
بدون مرشح بلوم ، ستحتاج إلى الإشارة إلى جدول يحتوي على كل اسم مستخدم على الإطلاق ، وعلى نطاق واسع قد يكون هذا مكلفًا جدًا. تسمح لك عوامل تصفية Bloom بإضافة عنصر في كل مرة يستخدم فيها المستخدم اسمًا جديدًا. عندما يتحقق المستخدم لمعرفة ما إذا كان اسم المستخدم مأخوذًا أم لا ، فكل ما عليك فعله هو فحص فلتر بلوم. ستتمكن من إخبارك ، بكل تأكيد ، إذا كان اسم المستخدم المطلوب قد تمت إضافته مسبقًا. من المحتمل أن يعود المرشح بشكل خاطئ إلى أن اسم المستخدم قد تم استخدامه عندما لا يكون ، ولكن هذه الأخطاء تقع على جانب الحذر ولا يمكن أن تسبب أي ضرر حقيقي (بصرف النظر عن عدم قدرة المستخدم على المطالبة بـ "k3w1d00d47") .
لتوضيح ذلك ، لنقم بإنشاء ملقم REST سريع باستخدام Express. أولاً ، قم بإنشاء ملفpackage.json
ثم قم بتشغيل الأوامر الطرفية التالية.
npm install bloom-redis --save
npm install express --save
npm install redis --save
يكون حجم الخيارات الافتراضية لـ bloom-redis عند اثنين ميغا بايت. هذا يخطئ على جانب الحذر ، لكنه كبير جدا. يعد إعداد حجم مرشح بلوم أمرًا بالغ الأهمية: كبير جدًا وتهدر الذاكرة ، صغير جدًا ومعدل موجب كاذب سيكون مرتفعًا جدًا. يتم تضمين الرياضيات تشارك في تحديد حجم تماما وخارج نطاق هذا البرنامج التعليمي ، ولكن لحسن الحظ هناك آلة حاسبة حجم مرشح بلوم لإنجاز المهمة دون تكسير كتاب دراسي.
الآن ، قم بإنشاء app.js
الخاص بك كما يلي:
1 |
var
|
2 |
Bloom = require('bloom-redis'), |
3 |
express = require('express'), |
4 |
redis = require('redis'), |
5 |
|
6 |
app, |
7 |
client, |
8 |
filter; |
9 |
|
10 |
//setup our Express server
|
11 |
app = express(); |
12 |
|
13 |
//create the connection to Redis
|
14 |
client = redis.createClient(); |
15 |
|
16 |
|
17 |
filter = new Bloom.BloomFilter({ |
18 |
client : client, //make sure the Bloom module uses our newly created connection to Redis |
19 |
key : 'username-bloom-filter', //the Redis key |
20 |
|
21 |
//calculated size of the Bloom filter.
|
22 |
//This is where your size / probability trade-offs are made
|
23 |
//http://hur.st/bloomfilter?n=100000&p=1.0E-6
|
24 |
size : 2875518, // ~350kb |
25 |
numHashes : 20 |
26 |
});
|
27 |
|
28 |
app.get('/check', function(req,res,next) { |
29 |
//check to make sure the query string has 'username'
|
30 |
if (typeof req.query.username === 'undefined') { |
31 |
//skip this route, go to the next one - will result in a 404 / not found
|
32 |
next('route'); |
33 |
} else { |
34 |
filter.contains( |
35 |
req.query.username, // the username from the query string |
36 |
function(err, result) { |
37 |
if (err) { |
38 |
next(err); //if an error is encountered, send it to the client |
39 |
} else { |
40 |
res.send({ |
41 |
username : req.query.username, |
42 |
//if the result is false, then we know the item has *not* been used
|
43 |
//if the result is true, then we can assume that the item has been used
|
44 |
status : result ? 'used' : 'free' |
45 |
});
|
46 |
}
|
47 |
}
|
48 |
);
|
49 |
}
|
50 |
});
|
51 |
|
52 |
|
53 |
app.get('/save',function(req,res,next) { |
54 |
if (typeof req.query.username === 'undefined') { |
55 |
next('route'); |
56 |
} else { |
57 |
//first, we need to make sure that it's not yet in the filter
|
58 |
filter.contains(req.query.username, function(err, result) { |
59 |
if (err) { next(err); } else { |
60 |
if (result) { |
61 |
//true result means it already exists, so tell the user
|
62 |
res.send({ username : req.query.username, status : 'not-created' }); |
63 |
} else { |
64 |
//we'll add the username passed in the query string to the filter
|
65 |
filter.add( |
66 |
req.query.username, |
67 |
function(err) { |
68 |
//The callback arguments to `add` provides no useful information, so we'll just check to make sure that no error was passed
|
69 |
if (err) { next(err); } else { |
70 |
res.send({ |
71 |
username : req.query.username, status : 'created' |
72 |
});
|
73 |
}
|
74 |
}
|
75 |
);
|
76 |
}
|
77 |
}
|
78 |
});
|
79 |
}
|
80 |
});
|
81 |
|
82 |
app.listen(8010); |
لتشغيل هذا الخادم: عقدة app.js
. انتقل إلى متصفحك وأشره إلى: https: // localhost: 8010 / check؟ username = kyle
. يجب أن تكون الإجابة: {"اسم المستخدم": "kyle" ، "status": "free"}
.
الآن ، دعنا نحفظ اسم المستخدم هذا من خلال توجيه المتصفح على http: // localhost: 8010 / save؟ username = kyle
. ستكون الإجابة:{"اسم المستخدم": "kyle" ، "status": "created"}
. إذا رجعت إلى العنوان http: // localhost: 8010 / check؟ username = kyle
، فستكون الإجابة {"اسم المستخدم": "kyle" و "status": "used"}
. وبالمثل ، عند الرجوع إلىhttp: // localhost: 8010 / save؟ username = kyle
سيؤدي إلى {"اسم المستخدم": "kyle" و "status": "not-created"}
.
من الجهاز ، يمكنك رؤية حجم الفلتر: redis-cli strlen username-bloom-filter
.
الآن ، مع عنصر واحد ، يجب أن تظهر 338622
.
والآن ، حاول إضافة المزيد من أسماء المستخدمين باستخدام المسار / save
. جرب ما تشاء.
إذا قمت بفحص الحجم مرة أخرى ، قد تلاحظ أن حجمك قد ارتفع قليلاً ، ولكن ليس لكل إضافة.الغريب ، أليس كذلك؟ داخليًا ، يعمل مرشح بلوم على تعيين وحدات البت الفردية (1's / 0) في مواضع مختلفة في السلسلة المحفوظة عند اسم المستخدم -البلوم. ومع ذلك ، هذه ليست متجاورة ، لذلك إذا قمت بتعيين بت في فهرس 0 ثم واحد في الفهرس 10،000 ، سيكون كل شيء بين 0. بالنسبة للاستخدامات العملية ، ليس من المهم في البداية فهم الآليات الدقيقة لكل عملية - فقط اعلم أن هذا أمر طبيعي وأن تخزينك في Redis لن يتجاوز القيمة التي حددتها.
محتوى جديد
يحافظ المحتوى الجديد على موقع ويب على عودة المستخدم ، لذا كيف تظهر للمستخدم شيئًا جديدًا في كل مرة؟ باستخدام نهج قاعدة البيانات التقليدية ، يمكنك إضافة صف جديد إلى جدول بمعرف المستخدم ومعرف القصة ، ثم يمكنك الاستعلام عن هذا الجدول عند اتخاذ قرار لعرض جزء من المحتوى. كما قد تتخيل ، ستنمو قاعدة البيانات بسرعة كبيرة ، خاصة مع نمو كل من المستخدمين والمحتوى.
في هذه الحالة ، فإن النتيجة السلبية الكاذبة (على سبيل المثال عدم عرض جزء غير مرئي من المحتوى) لها عواقب قليلة جدًا ، مما يجعل من مرشحات Bloom خيارًا قابلاً للتطبيق. في البداية ، قد تظن أنك ستحتاج إلى مرشح بلوم لكل مستخدم ، لكننا سنستخدم تسلسلاً بسيطًا لمعرّف المستخدم ومعرّف المحتوى ، ثم ندخل تلك السلسلة في فلترنا. بهذه الطريقة يمكننا استخدام مرشح واحد لجميع المستخدمين.
في هذا المثال ، لنقم بإنشاء خادم Express أساسي آخر يعرض المحتوى. في كل مرة تزور فيها المسار / عرض المحتوى / أي اسم مستخدم
(مع وجود أي اسم مستخدم لأي قيمة آمنة لعنوان URL) ، سيتم عرض جزء جديد من المحتوى حتى يصبح الموقع خارج المحتوى. في المثال ، يكون المحتوى هو السطر الأول من الكتب العشرة الأولى فيProject Gutenberg .
سنحتاج إلى تثبيت وحدة npm أخرى. من المحطة ، شغل: npm install async - save
ملف app.js الجديد الخاص بك:
1 |
var
|
2 |
async = require('async'), |
3 |
Bloom = require('bloom-redis'), |
4 |
express = require('express'), |
5 |
redis = require('redis'), |
6 |
|
7 |
app, |
8 |
client, |
9 |
filter, |
10 |
|
11 |
// From Project Gutenberg - opening lines of the top 10 public domain ebooks
|
12 |
// https://www.gutenberg.org/browse/scores/top
|
13 |
openingLines = { |
14 |
'pride-and-prejudice' : |
15 |
'It is a truth universally acknowledged, that a single man in possession of a good fortune, must be in want of a wife.', |
16 |
'alices-adventures-in-wonderland' : |
17 |
'Alice was beginning to get very tired of sitting by her sister on the bank, and of having nothing to do: once or twice she had peeped into the book her sister was reading, but it had no pictures or conversations in it, \'and what is the use of a book,\' thought Alice \'without pictures or conversations?\'', |
18 |
'a-christmas-carol' : |
19 |
'Marley was dead: to begin with.', |
20 |
'metamorphosis' : |
21 |
'One morning, when Gregor Samsa woke from troubled dreams, he found himself transformed in his bed into a horrible vermin.', |
22 |
'frankenstein' : |
23 |
'You will rejoice to hear that no disaster has accompanied the commencement of an enterprise which you have regarded with such evil forebodings.', |
24 |
'adventures-of-huckleberry-finn' : |
25 |
'YOU don\'t know about me without you have read a book by the name of The Adventures of Tom Sawyer; but that ain\'t no matter.', |
26 |
'adventures-of-sherlock-holmes' : |
27 |
'To Sherlock Holmes she is always the woman.', |
28 |
'narrative-of-the-life-of-frederick-douglass' : |
29 |
'I was born in Tuckahoe, near Hillsborough, and about twelve miles from Easton, in Talbot county, Maryland.', |
30 |
'the-prince' : |
31 |
'All states, all powers, that have held and hold rule over men have been and are either republics or principalities.', |
32 |
'adventures-of-tom-sawyer' : |
33 |
'TOM!' |
34 |
};
|
35 |
|
36 |
|
37 |
app = express(); |
38 |
client = redis.createClient(); |
39 |
|
40 |
filter = new Bloom.BloomFilter({ |
41 |
client : client, |
42 |
key : '3content-bloom-filter', //the Redis key |
43 |
size : 2875518, // ~350kb |
44 |
//size : 1024,
|
45 |
numHashes : 20 |
46 |
});
|
47 |
|
48 |
app.get('/show-content/:user', function(req,res,next) { |
49 |
//we're going to be looping through the contentIds, checking to see if they are in the filter.
|
50 |
//Since this spends time on each contentId wouldn't be advisable to do over a high number of contentIds
|
51 |
//But, in this case the number of contentIds is small / fixed and our filter.contains function is fast, it is okay.
|
52 |
var
|
53 |
//creates an array of the keys defined in openingLines
|
54 |
contentIds = Object.keys(openingLines), |
55 |
//getting part of the path from the URI
|
56 |
user = req.params.user, |
57 |
checkingContentId, |
58 |
found = false, |
59 |
done = false; |
60 |
|
61 |
//since filter.contains is asynchronous, we're using the async library to do our looping
|
62 |
async.whilst( |
63 |
//check function, where our asynchronous loop will end
|
64 |
function () { return (!found && !done); }, |
65 |
function(cb) { |
66 |
//get the first item from the array of contentIds
|
67 |
checkingContentId = contentIds.shift(); |
68 |
|
69 |
//false means we're sure that it isn't in the filter
|
70 |
if (!checkingContentId) { |
71 |
done = true; // this will be caught by the check function above |
72 |
cb(); |
73 |
} else { |
74 |
//concatenate the user (from the URL) with the id of the content
|
75 |
filter.contains(user+checkingContentId, function(err, results) { |
76 |
if (err) { cb(err); } else { |
77 |
found = !results; |
78 |
cb(); |
79 |
}
|
80 |
});
|
81 |
}
|
82 |
},
|
83 |
function(err) { |
84 |
if (err) { next(err); } else { |
85 |
if (openingLines[checkingContentId]) { |
86 |
//before we send the fresh contentId, let's add it to the filter to prevent it from showing again
|
87 |
filter.add( |
88 |
user+checkingContentId, |
89 |
function(err) { |
90 |
if (err) { next(err); } else { |
91 |
//send the fresh quote
|
92 |
res.send(openingLines[checkingContentId]); |
93 |
}
|
94 |
}
|
95 |
);
|
96 |
} else { |
97 |
res.send('no new content!'); |
98 |
}
|
99 |
}
|
100 |
}
|
101 |
);
|
102 |
});
|
103 |
|
104 |
app.listen(8011); |
إذا انتبهت بعناية إلى وقت الذهاب والإياب في أدوات Dev ، فستلاحظ أنه كلما طلبت مسارًا واحدًا باسم مستخدم ، كلما طالت المدة. بينما يستغرق التحقق من عامل التصفية وقتًا ثابتًا ، في هذا المثال ، نتحقق من وجود المزيد من العناصر. تقتصر عوامل تصفية Bloom على ما يمكن أن يخبرك به ، لذلك فأنت تختبر وجود كل عنصر. بالطبع ، في مثالنا هذا بسيط إلى حد ما ، ولكن الاختبار لمئات العناصر سيكون غير فعال.
بيانات تالفة
في هذا المثال ، سنقوم ببناء خادم Express صغير يقوم بعمل أمرين: قبول بيانات جديدة عبر POST ، وعرض البيانات الحالية (مع طلب GET). عندما تكون البيانات الجديدة POST'ed إلى الخادم ، سيتحقق التطبيق من وجودها في الفلتر. إذا لم يكن موجودًا ، سنقوم بإضافته إلى مجموعة في Redis ، وإلا فسنعود إلى null. سيقوم طلب GET بجلبه من Redis وإرساله إلى العميل.
هذا يختلف عن الحالتين السابقتين ، في أن الإيجابيات الزائفة لن تكون مقبولة. سنستخدم فلتر Bloom كخط دفاع أول. بالنظر إلى خصائص مرشحات Bloom ، سنعرف فقط للتأكد من عدم وجود شيء في الفلتر ، لذلك يمكننا في هذه الحالة المضي قدمًا وإدخال البيانات. إذا قام مرشح Bloom بإرجاع ذلك على الأرجح في المرشح ، فسنقوم بفحص مقابل مصدر البيانات الفعلي.
إذن ، ماذا نكسب؟ نكتسب سرعة عدم التحقق من المصدر الفعلي في كل مرة. في الحالات التي يكون فيها مصدر البيانات بطيئًا (واجهات برمجة التطبيقات الخارجية ، قواعد بيانات pokey ، وسط ملف ثابت) ، تكون هناك حاجة إلى زيادة السرعة. لشرح السرعة ، دعنا نضيف تأخيرًا واقعيًا يبلغ 150 مللي ثانية في مثالنا. سنستخدم أيضًا console.time
/console.timeEnd
لتسجيل الاختلافات بين فحص مرشح Bloom وفحص الفلتر غير Bloom.
في هذا المثال ، سنستخدم أيضًا عددًا محدودًا جدًا من البتات: 1024 فقط. سوف تملأ بسرعة.عندما تملأ ، ستظهر المزيد والمزيد من الإيجابيات الخاطئة - سترى زيادة وقت الاستجابة مع تملأ المعدل الإيجابي الخاطئ.
يستخدم هذا الخادم نفس الوحدات النمطية كما كان من قبل ، لذا قم بتعيين ملف app.js
إلى:
1 |
var
|
2 |
async = require('async'), |
3 |
Bloom = require('bloom-redis'), |
4 |
bodyParser = require('body-parser'), |
5 |
express = require('express'), |
6 |
redis = require('redis'), |
7 |
|
8 |
app, |
9 |
client, |
10 |
filter, |
11 |
|
12 |
currentDataKey = 'current-data', |
13 |
usedDataKey = 'used-data'; |
14 |
|
15 |
app = express(); |
16 |
client = redis.createClient(); |
17 |
|
18 |
filter = new Bloom.BloomFilter({ |
19 |
client : client, |
20 |
key : 'stale-bloom-filter', |
21 |
//for illustration purposes, this is a super small filter. It should fill up at around 500 items, so for a production load, you'd need something much larger!
|
22 |
size : 1024, |
23 |
numHashes : 20 |
24 |
});
|
25 |
|
26 |
app.post( |
27 |
'/', |
28 |
bodyParser.text(), |
29 |
function(req,res,next) { |
30 |
var
|
31 |
used; |
32 |
|
33 |
console.log('POST -', req.body); //log the current data being posted |
34 |
console.time('post'); //start measuring the time it takes to complete our filter and conditional verification process |
35 |
|
36 |
//async.series is used to manage multiple asynchronous function calls.
|
37 |
async.series([ |
38 |
function(cb) { |
39 |
filter.contains(req.body, function(err,filterStatus) { |
40 |
if (err) { cb(err); } else { |
41 |
used = filterStatus; |
42 |
cb(err); |
43 |
}
|
44 |
});
|
45 |
},
|
46 |
function(cb) { |
47 |
if (used === false) { |
48 |
//Bloom filters do not have false negatives, so we need no further verification
|
49 |
cb(null); |
50 |
} else { |
51 |
//it *may* be in the filter, so we need to do a follow up check
|
52 |
//for the purposes of the tutorial, we'll add a 150ms delay in here since Redis can be fast enough to make it difficult to measure and the delay will simulate a slow database or API call
|
53 |
setTimeout(function() { |
54 |
console.log('possible false positive'); |
55 |
client.sismember(usedDataKey, req.body, function(err, membership) { |
56 |
if (err) { cb(err); } else { |
57 |
//sismember returns 0 if an member is not part of the set and 1 if it is.
|
58 |
//This transforms those results into booleans for consistent logic comparison
|
59 |
used = membership === 0 ? false : true; |
60 |
cb(err); |
61 |
}
|
62 |
});
|
63 |
}, 150); |
64 |
}
|
65 |
},
|
66 |
function(cb) { |
67 |
if (used === false) { |
68 |
console.log('Adding to filter'); |
69 |
filter.add(req.body,cb); |
70 |
} else { |
71 |
console.log('Skipped filter addition, [false] positive'); |
72 |
cb(null); |
73 |
}
|
74 |
},
|
75 |
function(cb) { |
76 |
if (used === false) { |
77 |
client.multi() |
78 |
.set(currentDataKey,req.body) //unused data is set for easy access to the 'current-data' key |
79 |
.sadd(usedDataKey,req.body) //and added to a set for easy verification later |
80 |
.exec(cb); |
81 |
} else { |
82 |
cb(null); |
83 |
}
|
84 |
}
|
85 |
],
|
86 |
function(err, cb) { |
87 |
if (err) { next(err); } else { |
88 |
console.timeEnd('post'); //logs the amount of time since the console.time call above |
89 |
res.send({ saved : !used }); //returns if the item was saved, true for fresh data, false for stale data. |
90 |
}
|
91 |
}
|
92 |
);
|
93 |
});
|
94 |
|
95 |
app.get('/',function(req,res,next) { |
96 |
//just return the fresh data
|
97 |
client.get(currentDataKey, function(err,data) { |
98 |
if (err) { next(err); } else { |
99 |
res.send(data); |
100 |
}
|
101 |
});
|
102 |
});
|
103 |
|
104 |
app.listen(8012); |
بما أن POSTing إلى خادم يمكن أن تكون خادعة مع متصفح ، دعونا نستخدم curlللاختبار.
curl --data “your data goes here" --header "Content-Type: text/plain" http://localhost:8012/
يمكن استخدام برنامج نصي باش سريع لإظهار مدى ملاءمة الفلتر بأكمله:
1 |
#!/bin/bash
|
2 |
for i in `seq 1 500`; |
3 |
do
|
4 |
curl --data “data $i" --header "Content-Type: text/plain" http://localhost:8012/ |
5 |
done
|
النظر في ملء أو مرشح كامل مثير للاهتمام. بما أن هذا الجهاز صغير ، يمكنك مشاهدته بسهولة مع redis-cli
. من خلال تشغيلredis-cli تحصل على فلتر قديم
من المحطة بين إضافة عناصر ، سترى زيادة البايت الفردي. سيكون عامل تصفية كامل \ xff
لكل بايت. في هذه المرحلة ، سيعود الفلتر دائمًا إلى وضع إيجابي.
استنتاج
لا تعتبر مرشحات Bloom حلًا لكل دواء ، ولكن في الحالة المناسبة ، يمكن لمرشح Bloom توفير تكملة سريعة وفعالة لهياكل البيانات الأخرى.