對於一個網站來說,無論是商城網站還是門戶網站,搜索框都是有一個比較重要的地位,它的存在可以說是
為了讓用戶更快、更方便的去找到自己想要的東西。對於經常逛這個網站的用戶,當然也會想知道在這里比較“火”
的東西是什么,這個時候我們搜索框上的熱詞就起作用了。其實我覺得這一塊的完善會對這個網站帶來許多益處。
可能現在比較普遍的做法是把這些相應的信息存到我們的關系型數據庫中,如sql server 和 oracle。方便起見
的話,可能每搜索一次就往表里插一次數據,用的時候要先統計數據,統計完后再排序,最后才展示。這種情況下,
如果搜索量很大的話,表的膨脹速度就會非常快,如果sql沒寫好,查詢的時候估計會。。相比Redis,同等條件下,
Redis的速率肯定是會較優,畢竟是從內存中拿出來的。
案例用到的一些相關技術和說明:
技術 | 說明 |
.NET Core | 網站嘛,你懂的。有事沒事用Core寫寫Demo,免得跟不上發展的腳步。 |
Redis | 存儲搜索詞,用了主從的模式,主寫從讀 |
Jquery-ui | 主要是用了里面的autocomplete |
開始正題之前,我們要確定用Redis中的那種數據結構,五種之中比較合適的應該是SortedSet,我們可以用成員來
作為搜索詞,成員分數來作為搜索詞的搜索次數,這樣就可以很方便的來操作相關的數據了。
下面開始正題:
我們在開始的時候需要初始化一下數據。這里就直接在第一次運行的時候初始化。用上流水線的技術,速度還是
很可觀的。初始化了70個搜索關鍵詞(NBA球星),然后用隨機數作為關鍵字的下標,去隨機給這個關鍵字加1分。這
個分數就是這個關鍵字被搜索的次數。下面來看看初始化的相關代碼:
public IActionResult Index() { //keys IList<string> keys = new List<string>() { "kobe","johnson","jabbar","west","o'neal","baylor","mccann","worthy","gasol","chamberlain", "fisher","odom","bynum","horry","rambis","riley","clarkson","Williams","young","Russell", "ingram","randle","nance","brown","deng","yi","ariza","artest","walton","vujacic", "james","paul","curry","park","yao","kevin","wade","rose","popovich","leonard", "aldridge","ginobili","duncan","lavine","rubio","garnett","wiggins","westbrook","durant","ibaka", "nowitzki","pierce","crawford","love","smith","iguodala","barnes","green","thompson","harden", "lillard","mccollum","lin","jackson","nash","stoudemire","whiteside","dragic","Howard","batum" }; //init Random random = new Random(); var tran = _redis.GetTransaction(); for (int i = 0; i < 1000000; i++) { tran.SortedSetIncrementAsync(_searchKey, keys[random.Next(0, 70)], 1); } tran.ExecuteAsync(); return View(); }
這里是在加載這個頁面的時候就把這些熱搜詞存進Redis中,這樣我們才能有數據來演示啊。這里還用到了一個
非事務型的流水線。就是把要操作的指令存放到一個隊列中,最后把這個隊列扔到服務端去執行,這樣就有效的減少
了不必要的網絡傳輸,同時也提高了執行速度。
好了,初始數據有了,下面要做的就是用戶在搜索的時候,根據用戶的輸入去匹配搜索次數多的關鍵字,展示最
Hot的10個,當然這個展示的個數是隨我們定的,最后可以考慮把這個放到我們的配置文件中去,甚至是放到數據庫中,
為的是靈活和方便維護。下面是我們在后台的處理邏輯:
public IActionResult GetHotKey(string key="") { if (string.IsNullOrEmpty(key)) {//default var res = _redis.ZRevRange(_searchKey, 0, 9); var list = (from i in res select i.ToString()); return Json(list); } else {//by user input var res = _redis.ZRevRange(_searchKey, 0, -1); var list = (from i in res select i.ToString()).Where(x => x.Contains(key)).Take(10).ToList(); return Json(list); } }
對於查詢的處理是非常的簡單的,用戶不小心輸入空格的時候就展示最熱的10個關鍵詞,如果用戶有輸入的話,就把 關鍵詞中包含用戶輸入的展示出來。那么我們在頁面上要做些什么呢?下面就是我們演示用的搜索框。
<div class="row"> <div class="col-md-6 col-md-offset-4" style="padding-top:50px;"> <input id="key" name="key" placeholder="search" class="form-control col-md-4"> <button class="btn btn-primary" type="button" id="searchSubmit">Search</button> <div id="result"></div> </div> </div>
相應的js是寫到 scripts 這個section中的,js的話是比較簡單的就是用ajax去請求我們要展示的數據。更多的應該是
jquery-ui的api問題,大家也可以換用自己比較熟悉的組件,舉一反三即可。下面是autocomplete的api ,如果有需要可
以去看一下。
@section scripts{ <script type="text/javascript"> $(function () { //show hot keyword $("#key").autocomplete({ source: function (request, response) { $.ajax({ url: "@Url.Action("GetHotKey", "Auto")", dataType: "json", data: { key: request.term }, success: function (data) { response(data); } }); }, }); </script> }
到這里,用戶搜索前的操作,我們是做好了,下面先來看一下效果。
那么用戶點擊了搜索之后我們要做些什么處理呢?無論是新的關鍵字還是已有的關鍵字,我們都是要做處理的,當然redis
中zincrby命令來處理這個是十分合適的,存在的就把分數加1,不存在就創建一個分數為1的成員。下面是搜索時的后台邏輯處理:
[HttpPost] public IActionResult SetHotKey(string key) { if (!string.IsNullOrWhiteSpace(key)) { _redis.ZIncrby(_searchKey,key); //other //... return Json(new { code = "000", msg = "OK" }); } else { return Json(new { code = "999", msg = "keyword can not be empty!" }); } }
限制了用戶不能搜索空關鍵字,在把這個關鍵字存儲或者分數加一之后,就是展示我們的搜索的結果。這個搜索的結果一般
是從solr等全文檢索的地方查出來的,不是我們講的重點,所以就忽略了。然后我們還要加一段js去處理我們搜索的時候應該做的
操作。當然,都是些比較簡單的操作。
//search $("#searchSubmit").click(function () { $.ajax({ url: "@Url.Action("SetHotKey", "Auto")", dataType: "json", type: "POST", data: { key: $("#key").val() }, success: function (data) { if (data.code == "000") { $("<p>search successful!</p>").appendTo("#result"); } else { $("<p>"+data.msg+"</p>").appendTo("#result"); } } }); });
在演示的時候,我們搜索了“我愛你”和“我不信”,在Redis的客戶端我們找出搜索次數最少的6個,然后就可以看到我們那兩
個關鍵字最的分數都是1。確定是剛插入的數據。
到這里,我們做的這個熱搜詞可以說是大功告成了。當然這可以說是最最最簡單的一個雛形。我們還可以適當的添加一些
東西讓這個功能變得更加完善。比如我可以在搜索展示的時候顯示一下搜索的次數等。
最后是完整的控制器和頁面代碼:
using AutoCompleteDemo.Common; using Microsoft.AspNetCore.Mvc; using System; using System.Collections.Generic; using System.Linq; namespace AutoCompleteDemo.Controllers { public class AutoController : Controller { private readonly IRedis _redis; private readonly string _searchKey = "search"; public AutoController(IRedis redis) { _redis = redis; } public IActionResult Index() { //keys IList<string> keys = new List<string>() { "kobe","johnson","jabbar","west","o'neal","baylor","mccann","worthy","gasol","chamberlain", "fisher","odom","bynum","horry","rambis","riley","clarkson","Williams","young","Russell", "ingram","randle","nance","brown","deng","yi","ariza","artest","walton","vujacic", "james","paul","curry","park","yao","kevin","wade","rose","popovich","leonard", "aldridge","ginobili","duncan","lavine","rubio","garnett","wiggins","westbrook","durant","ibaka", "nowitzki","pierce","crawford","love","smith","iguodala","barnes","green","thompson","harden", "lillard","mccollum","lin","jackson","nash","stoudemire","whiteside","dragic","Howard","batum" }; //init Random random = new Random(); var tran = _redis.GetTransaction(); for (int i = 0; i < 2000000; i++) { tran.SortedSetIncrementAsync(_searchKey, keys[random.Next(0, 70)], 1); } tran.ExecuteAsync(); return View(); } public IActionResult GetHotKey(string key="") { if (string.IsNullOrEmpty(key)) {//default var res = _redis.ZRevRange(_searchKey, 0, 9); var list = (from i in res select i.ToString()); return Json(list); } else {//by user input var res = _redis.ZRevRange(_searchKey, 0, -1); var list = (from i in res select i.ToString()).Where(x => x.Contains(key)).Take(10).ToList(); return Json(list); } } [HttpPost] public IActionResult SetHotKey(string key) { if (!string.IsNullOrWhiteSpace(key)) { _redis.ZIncrby(_searchKey,key); //other //... return Json(new { code = "000", msg = "OK" }); } else { return Json(new { code = "999", msg = "keyword can not be empty!" }); } } } } AutoController
@{ ViewData["Title"] = "Auto Complete"; } <div class="row"> <div class="col-md-6 col-md-offset-4" style="padding-top:50px;"> <input id="key" name="key" placeholder="search" class="form-control col-md-4"> <button class="btn btn-primary" type="button" id="searchSubmit">Search</button> <div id="result"></div> </div> </div> @section scripts{ <script type="text/javascript"> $(function () { //show hot keyword $("#key").autocomplete({ source: function (request, response) { $.ajax({ url: "@Url.Action("GetHotKey", "Auto")", dataType: "json", data: { key: request.term }, success: function (data) { response(data); } }); }, }); //search $("#searchSubmit").click(function () { $.ajax({ url: "@Url.Action("SetHotKey", "Auto")", dataType: "json", type: "POST", data: { key: $("#key").val() }, success: function (data) { if (data.code == "000") { $("<p>search successful!</p>").appendTo("#result"); } else { $("<p>"+data.msg+"</p>").appendTo("#result"); } } }); }); }); </script> } Index.cshtml