来源:Catcher8
www.cnblogs.com/catcher1994/p/5877262.html
对于一个网站来说,无论是商城网站还是门户网站,搜索框都是有一个比较重要的地位,它的存在可以说是为了让用户更快、更方便的去找到自己想要的东西。对于经常逛这个网站的用户,当然也会想知道在这里比较“火”的东西是什么,这个时候我们搜索框上的热词就起作用了。其实我觉得这一块的完善会对这个网站带来许多益处。
可能现在比较普遍的做法是把这些相应的信息存到我们的关系型数据库中,如sql server 和 oracle。方便起见的话,可能每搜索一次就往表里插一次数据,用的时候要先统计数据,统计完后再排序,最后才展示。这种情况下,如果搜索量很大的话,表的膨胀速度就会非常快,如果sql没写好,查询的时候估计会。。相比Redis,同等条件下,Redis的速率肯定是会较优,毕竟是从内存中拿出来的。
下面我们就用.NET Core和StackExchange.Redis来做一下这个简单的案例。
案例用到的一些相关技术和说明:
技术 | 说明 |
.NET Core | 网站嘛,你懂的。有事没事用Core写写Demo,免得跟不上发展的脚步。 |
Redis | 存储搜索词,用了主从的模式,主写从读 |
Jquery-ui | 主要是用了里面的autocomplete |
开始正题之前,我们要确定用Redis中的那种数据结构,五种之中比较合适的应该是SortedSet,我们可以用成员来作为搜索词,成员分数来作为搜索词的搜索次数,这样就可以很方便的来操作相关的数据了。
下面开始正题:
我们在开始的时候需要初始化一下数据。这里就直接在第一次运行的时候初始化。用上流水线的技术,速度还是很可观的。初始化了70个搜索关键词(NBA球星),然后用随机数作为关键字的下标,去随机给这个关键字加1分。这个分数就是这个关键字被搜索的次数。下面来看看初始化的相关代码:
public IActionResult Index(){//keysIList<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"};//initRandom 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)){//defaultvar res = _redis.ZRevRange(_searchKey, 0, 9);var list = (from i in res select i.ToString());return Json(list);}else{//by user inputvar 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 这个p中的,js的话是比较简单的就是用ajax去请求我们要展示的数据。更多的应该是jquery-ui的api问题,大家也可以换用自己比较熟悉的组件,举一反三即可。下面是autocomplete的api ,如果有需要可以去看一下。
@p 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(){//keysIList<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"};//initRandom 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)){//defaultvar res = _redis.ZRevRange(_searchKey, 0, 9);var list = (from i in res select i.ToString());return Json(list);}else{//by user inputvar 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>
@p 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
--完--
如果需要Redis视频,可以在公众号后台聊天框回复【Redis视频】,可以免费获取编程视频 。
Redis其它推荐阅读:
Redis 零基础入门视频教程
Linux 安装 Redis 图文教程
刚接触学Redis,看这一篇就够了!
再来聊聊Redis到底是什么?
说说 Redis 数据结构和常用命令
Redis最常见面试问题,附答案!
阿里巴巴官方 Redis 开发规范!
如何借助 Redis 实现秒杀系统?
史上最全 Redis 高可用解决方案总结
10 个正确使用 Redis 的技巧
Redis 分布式锁的正确实现方式
Redis 源码学习之 Redis 事务
详解Redis的内存淘汰策略
Redis 的各项功能,都解决了哪些问题?
为什么单线程的Redis却能支撑高并发?
Redis应用场景,实现功能 “附近的人”
超详细揭秘 Redis 持久化,建议收藏!
后端开发都应该掌握的Redis基础
Redis是如何实现点赞、取消点赞的?
如何用 Redis 做实时订阅推送的?
Redis 在百亿数据量中的使用技巧
Redis 主从复制以及主从复制原理
如何访问 Redis 中的海量数据?
一文深入了解Redis内存模型!
Redis常见的几种缓存模式
长按加入10W+朋友的IT圈
觉得内容还不错的话,给我点个“在看”呗