首页 > 代码库 > 项目中使用Redis的一些总结和体会

项目中使用Redis的一些总结和体会

第一部分:为什么我的项目中要使用Redis

我知道有些地方没说到位,希望大神们提出来,我会吸取教训,大家共同进步!

  • 注册时邮件激活的部分使用Redis
  • 发送邮件时使用Redis的消息队列,减轻网站压力。
  • 使用Lucene.Net在进行分词时使用Redis消息队列和多线程来避免界面卡死等性能问题。

 

  • 请大家先思考一个问题:这个问题在大并发、高负载的网站中必须考虑!大家思考如何让速度更快。

技术分享

三种方法:(1)数据库(2)页面静态化(3)Redis、Memcached

第二部分:Redis是什么

概述:redis是一种nosql数据库,他的数据是保存在内存中,同时redis可以定时把内存数据同步到磁盘,即可以将数据持久化,并且他比memcached支持更多的数据结构(string,list列表[队列和栈],set[集合],sorted set[有序集合] hash(hash表))

2.1介绍:

  •  Redis是一个高性能的key-value存储系统。和Memcached类似,它支持存储的value类型相对更多,包括string(字符串)、list(链表)、set(集合)、zset(sorted set --有序集合)和hash(哈希类型)。
  • Redis 很大程度补偿了memcached这类key/value存储的不足,在部 分场合可以对关系数据库起到很好的补充作用。它提供了Python,Ruby,Erlang,PHP客户端,使用很方便。(注: 摘自百度全科),1.主要是支持持久化2.支持更多数据结构 3.支持主从同步
  • Redis支持主从同步。数据可以从主服务器向任意数量的从服务器上同步,从服务器可以是关联其他从服务器的主服务器。

2.2  memcached和redis的比较:

技术分享

2.3Redis的优势:

技术分享

 

2.4Redis在windows下的安装:

注:关于Redis的安装网上有很多文章,讲的要比我的好,建议大家去看那些大神的文章,这里我只简单介绍一下。

(1)解压Redisbin.zip
(2)注册成windows服务
安装RedisWatch,会把Redis注册为一个系统服务,然后到安装RedisWatch的文件夹下找到watcher.config,修改下面的两个地方,如下图:
技术分享
 
特别提醒:作为一个专业的程序员文件放置的路径不要有特殊字符、空格、文件名不要有中文,否则就加班吧。
当然了,正式的生产环境还是Linux的效率高,因为在Linux上是源码安装。
(3)启动redis服务
技术分享
(4)使用netstat -anb | more

如果你看到有6379 在监听,说明ok(默认的端口号时候:6379)

 2.5Redis的数据结构:

前言:Redis中存储的数据都为字符串格式的。下面来分别介绍Redis中常用的数据结构。

  • string数据结构

太简单了,略过。

  •  list数据结构

概述: 什么是list ,list是一种数据结构,可以当做队列和栈来使用。

技术分享

当你从左边添加数据,再从左边取数据,就模拟出栈;当你从右边添加数据,再从左边取数据,就模拟出队列。因此Redis真的很强大,看到栈和队列这样的数据结构,你难道就不激动吗?这样的数据结构太TM好了,能帮我们处理很多棘手的问题。这里我先卖个关子,下面会介绍我在项目中是如何使用Redis解决棘手的问题。

  • set集合

和list结构差不多,这里不再啰嗦。

下面就是操作set的一些命令。

技术分享

  • hash数据结构

技术分享

图中的"user:100"就相当于key,而它所指向的类似于表结构的数据就是value,这样的数据结构有利于存储对象数据。也是非常常用的方法。

 

 

 注意:

redis指令不区分大小写,但是出于规范考虑,应该使用大写
redis中存放的键是区分大小写的.

 Redis常用命令文档:http://redis.readthedocs.io/en/2.6/

第三部分:Redis如何使用

 3.1C#中如何使用Redis来解决邮箱激活的实效性。

首先思考个问题:为什么要进行邮件激活?激活码该存到哪里?(大家先思考,我不直接说,这样通过下面的例子你会体会的更深。)

原因:用户在注册的时候,虽然正则表达式能检查邮箱的格式是否正确,但是正则检查不了邮箱是否可用,于是让用户进行激活,就能避免用户填写一个不可用的邮箱。

 

传统方法的代码实现:

1)数据库表的设计:

技术分享

在用户注册的表中添加一个字段:IsActive用来判断激活的状态。

技术分享

该表用来存放激活码。

 代码实现:

BLL层代码:

技术分享
 1 /// <summary> 2 /// 2016-08-30 3 /// 注册的时候看看是否已经存在该用户 4 /// </summary> 5 /// <param name="username"></param> 6 /// <returns></returns> 7 public T_Users GetByUserName(string username) 8 { 9 T_UsersDAL userDal = new T_UsersDAL();10 return userDal.GetByUserName(username);11 }
View Code
技术分享
 1         /// <summary> 2         /// 注册的时候看看邮箱是否已经被注册 3         /// </summary> 4         /// <returns></returns> 5         public bool CheckEmailOnReg(string email) 6         { 7             T_UsersDAL userDal = new T_UsersDAL(); 8             T_Users user= userDal.CheckEmailOnReg(email); 9             return user == null;10         }
View Code

DAL层代码:

技术分享
 1  /// <summary> 2         /// 注册时候看看是否已经存在该用户名 3         /// </summary> 4         /// <param name="username"></param> 5         /// <returns></returns> 6         public T_Users GetByUserName(string username) 7         { 8             string sql = "select * from T_Users where UserName=@UserName"; 9             DataTable dt = SqlHelper.ExecuteQuery(sql, new SqlParameter("@UserName", username));10             T_Users userInfo = null;11             if (dt.Rows.Count > 0)12             {13                 foreach (DataRow dr in dt.Rows)14                 {15                     userInfo = RowToUserInfoByDataRow(dr);16                 }17             }18             return userInfo;19         }
View Code
技术分享
 1 /// <summary> 2         /// 注册的时候检查用户的邮箱是否被注册 3         /// </summary> 4         /// <param name="email"></param> 5         /// <returns></returns> 6         public T_Users CheckEmailOnReg(string email) 7         { 8             string sql = "select * from T_Users where Email=@Email "; 9             DataTable dt = SqlHelper.ExecuteQuery(sql, new SqlParameter("@Email",email));10 11             T_Users userInfo = null;12             if (dt.Rows.Count>0)13             {14                 foreach (DataRow dr in dt.Rows)15                 {16                     userInfo=RowToUserInfoByDataRow(dr);17                 }18             }19             return userInfo;20         }
View Code

UI层代码:

技术分享
  1 <!--#include file="/html/head.html"-->  2 <title>注册</title>  3 <!--#include file="/html/linkscript.html"-->  4 <script type="text/javascript">  5     function checkPasswordLevel(value) {  6         if (!value) {  7             return 1;  8         }  9         if (value.length < 6) { 10             return 1; 11         } 12         if (value.length == 6 && (/[0-9]/.test(value) || /[a-z]/.test(value))) { 13             return 1; 14         } 15  16         if (value.length >= 6 && /[0-9]/.test(value) && /[a-z]/.test(value) && /(?=[\x21-\x7e]+)[^A-Za-z0-9]/.test(value)) { 17             return 3; 18         } 19         return 2; 20     } 21     $(function () { 22         $("#btnReg").click(function () { 23             var username = $("#username").val(); 24             var password = $("#password").val(); 25             var password2 = $("#password2").val(); 26             var email = $("#email").val(); 27             var phone = $("#PhoneNum").val(); 28             var qq = $("#qq").val(); 29             var school = $("#school").val(); 30  31             var validCode = $("#validCode").val(); 32             //todo:非空验证。JQuery EasyUI 33             if (phone == "") { 34                 $("#phoneMsg").text("手机号不能为空!"); 35                 return; 36             } 37             else { 38                 var reg = "^1(3[0-9]|4[57]|5[0-35-9]|7[01678]|8[0-9])\\d{8}$"; 39                 if (!reg.test(phone)) { 40                     $("#phoneMsg").text("手机号不合法!"); 41                     return; 42                 } 43             } 44             if (qq=="") { 45                 $("#qqMsg").text("QQ号不能为空!"); 46                 return; 47             } 48             if (school=="") { 49                 $("#schoolMsg").text("学校不能为空!"); 50             } 51             if (validCode == "") { 52                 $("#validateCodeMsg").text("验证码不能为空!"); 53                 return; 54             } 55             if (password == "") { 56                 $("#userPasswordMsg").text("密码不能为空!"); 57                 return; 58             } 59             if (password != password2) { 60                 $("#pwdError").text("两次输入的密码不一致!"); 61                 return; 62             } 63  64             $.ajax({ 65                 url: "UserController.ashx", type: "post", 66                 dataType: "json", 67                 data: { action: "registerSubmit", username: username, password: password, email: email, validCode: validCode, phone: phone, qq: qq, school: school }, 68                 success: function (data) { 69                     if (data.status == "ok") { 70                         alert("注册成功"); 71                         window.location.href = http://www.mamicode.com/"index.shtml"; 72                     } 73                     else { 74                         alert("注册失败:" + data.msg); 75                         //只有这句话刷新验证码是不安全的,需要后台也刷新验证码 76                         $("#imgValidCode").attr("src", "UserController.ashx?action=createValideCode&id=" + new Date()); 77                         78                        79                     } 80                 }, 81                 error: function () { 82                     alert("注册请求失败"); 83                 } 84             }); 85  86         }); 87  88         $("#password").keyup(function () { 89  90             var level = checkPasswordLevel($("#password").val()); 91             switch (level) { 92                 case 1: { 93                     $("#td1").css("backgroundColor", "#FF8000"); 94                     $("#td2").css("backgroundColor", ""); 95                     $("#td3").css("backgroundColor", ""); 96                 } 97                     break; 98                 case 2: { 99                     $("#td1").css("backgroundColor", "");100                     $("#td2").css("backgroundColor", "#FF4000");101                     $("#td3").css("backgroundColor", "");102                 }103                     break;104                 case 3: {105                     $("#td1").css("backgroundColor", "");106                     $("#td2").css("backgroundColor", "");107                     $("#td3").css("backgroundColor", "#5CB85C");108                 }109                     break;110             }111         })112         $("#password2").blur(function () {113             var password = $("#password").val();114             var password2 = $("#password2").val();115             if (password != password2) {116                 $("#pwdError").text("两次输入的密码不一致!");117                 return;118             }119             else {120                 $("#pwdError").text("");121             }122         });123         //todo:焦点离开email的时候,编写正则表达式检查email地址是否正确124         //todo:前台用户的注册:邮件发送激活码,一个邮件只能注册一个账号。125         $("#email").blur(function () {126             var email = $(this).val();127             if (email == "") {128                 $("#userEmailMsg").text("邮箱不能为空!");129                 return;130             }131             else {132                 var re = /^\w+@[a-z0-9]+(\.[a-z]+){1,3}$/;133                 if (re.test(email)) {134                     $.ajax({135                         url: "UserController.ashx", type: "post", dataType: "json",136                         data: { action: "checkEmail", email: email },137                         success: function (data) {138                             if (data.status == "ok") {139                                 $("#userEmailMsg").text(data.msg);140                                 return;141                             }142                             else if (data.status == "error") {143                                 $("#userEmailMsg").text(data.msg);144                                 return;145                             }146                         },147                         error: function () {148                             $("#userEmailMsg").text("检查邮箱是否可用失败");149                             return;150                         }151                     })152                 }153                 else {154                     $("#userEmailMsg").text("邮箱的格式不正确!");155                     return;156                 }157             }158 159         });160         $("#username").keyup(function () {161             $("#userNameMsg").text("");162         });163         $("#email").keyup(function () {164             $("#userEmailMsg").text("");165         });166         //检查用户名是否可用。167         $("#username").blur(function () {168             var username = $("#username").val();169             if (username == "") {170                 $("#userNameMsg").text("用户名不能为空!");171                 return;172             }173             $.ajax({174                 url: "UserController.ashx", type: "post", dataType: "json",175                 data: { action: "checkUserName", username: username },176                 success: function (data) {177                     if (data.status == "ok") {178                         $("#userNameMsg").text("此用户名可用");179                     }180                     else {181                         $("#userNameMsg").text("此用户名不可用,请换用其他用户名");182                     }183                 },184                 error: function () {185                     $("#userNameMsg").text("检查用户名是否可用失败");186                 }187             });188         });189     });190 </script>191 <style type="text/css">192     #table td193     {194         width: 70px;195         height: 12px;196         background-color: lightgray;197         border: 1px solid #D0D0D0;198         color: #BBBBBB;199         line-height: 9px;200         color: white;201         font-size: 12px;202         font-family: 微软雅黑;203     }204 </style>205 <!--#include file="/html/headend.html"-->206 <!--#include file="/html/navbar.html"-->207 <main id="post-page" class="container mainContent" role="main">208     <table>209         <tr><td><label for="username">用户名:</label></td><td><input type="text" id="username" /><span id="userNameMsg"></span></td></tr>210         <tr>211             <td><label for="password">输入密码:</label></td>212             <td>213                 <input type="password" id="password" /><span id="userPasswordMsg"></span>214                 <table id="table" border="0" cellpadding="0" cellspacing="1" style="display: inline-table;">215                     <tr>216                         <td id="td1" style="height: 12px; text-align: center;">弱</td>217                         <td id="td2" style="height: 12px; text-align: center;">中</td>218                         <td id="td3" style="height: 12px; text-align: center;">强</td>219                     </tr>220                 </table>221             </td>222 223         </tr>224         <tr><td><label for="password2">再次输入密码:</label></td><td><input type="password" id="password2" /><label id="pwdError"></label></td></tr>225         <tr><td><label>邮箱:</label></td><td><input type="text" id="email" /><span id="userEmailMsg"></span></td></tr>226         <tr><td><label>手机号:</label></td><td><input type="text" id="PhoneNum" /><span id="phoneMsg"></span></td></tr>227         <tr><td><label>QQ:</label></td><td><input type="text" id="qq" /><span id="qqMsg"></span></td></tr>228         <tr><td><label>学校:</label></td><td><input type="text" id="school"/><span id="schoolMsg"></span></td></tr>229 230         <tr><td><label>验证码:</label></td><td><input type="text" id="validCode" /><img src=http://www.mamicode.com/"UserController.ashx?action=createValideCode" id="imgValidCode" /><span id="validateCodeMsg"></span></td></tr>231         <tr><td><input type="button" id="btnReg" value=http://www.mamicode.com/"注册" /></td><td></td></tr>232     </table>233 </main>234 <!--#include file="/html/foot.html"-->
View Code

一般处理程序:

技术分享
 /// <summary>        /// 用户注册        /// </summary>        /// <param name="context"></param>        public void registerSubmit(HttpContext context)        {            //获取请求报文中从浏览器传过来的数据            string username = context.Request["username"];            string password = context.Request["password"];            string email = context.Request["email"];            string phone = context.Request["phone"];            string qq = context.Request["qq"];            string school = context.Request["school"];            string validCode = context.Request["validCode"];            //注意:通过js进行数据合法性校验,只是为了用户用起来方便而已,在服务器中校验才能保证数据的安全。            if (string.IsNullOrWhiteSpace(phone))            {                AjaxHelper.WriteJson(context.Response, "error", "手机号不能为空!");                return;            }            if (string.IsNullOrWhiteSpace(qq))            {                AjaxHelper.WriteJson(context.Response,"error","QQ号不能为空!");                return;            }            if (string.IsNullOrWhiteSpace(school))            {                AjaxHelper.WriteJson(context.Response,"error","学校不能为空!");                return;            }            if (string.IsNullOrWhiteSpace(username))            {                AjaxHelper.WriteJson(context.Response,"error","用户名不能为空!");                return;            }            if (string.IsNullOrWhiteSpace(password))            {                AjaxHelper.WriteJson(context.Response,"error","密码不能为空!");                return;            }            if (string.IsNullOrWhiteSpace(email))            {                AjaxHelper.WriteJson(context.Response,"error","邮箱不能为空!");                return;            }            if (string.IsNullOrWhiteSpace(validCode))            {                AjaxHelper.WriteJson(context.Response,"error","验证码不能为空!");                return;            }            if (validCode!=CommonHelper.GetValidCode(context))            {                AjaxHelper.WriteJson(context.Response,"error","验证码错误");                CommonHelper.ResetValidCode(context);                return;            }             T_UsersBLL userBll = new T_UsersBLL();            if (!userBll.CheckUserNameOnReg(username))            {                AjaxHelper.WriteJson(context.Response,"error","当前用户不可用");                return;            }            if (!userBll.CheckEmailOnReg(email))            {                AjaxHelper.WriteJson(context.Response,"error","该邮箱已被注册!");                return;            }            //插入数据库(T_Users)            long userId = userBll.AddNewUser(username, password, email,phone,qq,school);            //激活码            Random rand = new Random();            string activeCode = rand.Next(10000,99999).ToString();            //方案一:把激活码存入到数据库(T_UserActiveCodes)            T_UserActiveCodes userActiveCode = new T_UserActiveCodes();            userActiveCode.UserName = username;            userActiveCode.RegDateTime = DateTime.Now;            userActiveCode.ActiveCode = activeCode;            //插入到激活码数据表中            new T_UserActiveCodesBLL().Add(userActiveCode);                         //邮件链接和正文            string activeUrl = "http://localhost:22585/UserController.ashx?action=active&username=" + context.Server.UrlEncode(username) + "&activeCode=" + activeCode;            string emailBody = "尊敬的" + username + "您好,请点击下面的链接激活您的账户"                + "<a href=http://www.mamicode.com/‘" + activeUrl + "‘>点击此链接激活您的账号</a>,如果链接打不开,则把下面的地址复制到浏览器中进行激活:" + activeUrl;            //发送邮件            FrontHelper.SendEmail(email,"请激活您的***账号",emailBody);        /*             * 测试了网易和qq邮箱,能发是能发但是,对所发的邮件标题和内容是有限制的,不能发很容就能识别出来是垃圾邮件的邮件,标题和正文要正式点,负责不会接收到。             * 在生产环境中:无法使用163、qq等这种免费邮箱发送大量的邮件。             * Edm专用服务器,掏钱就ok。             SendCloud、Comm100、yiye             */
View Code

邮件发送代码:

public static void SendEmail(string toEmail, string subject, string body)        {            string smtpServer = ConfigurationManager.AppSettings["SmtpServer"];            string smtpFrom = ConfigurationManager.AppSettings["SmtpFrom"];            string smtpUserName = ConfigurationManager.AppSettings["SmtpUserName"];            string smtpPassword = ConfigurationManager.AppSettings["SmtpPassword"];            MailMessage mailObj = new MailMessage();            mailObj.IsBodyHtml = true;            //from:abc@qq.com            mailObj.From = new MailAddress(smtpFrom); //发送人邮箱地址            mailObj.To.Add(toEmail);   //收件人邮箱地址            mailObj.Subject = subject;    //主题            mailObj.Body = body;    //正文            SmtpClient smtp = new SmtpClient();//通过.Net内置的SmtpClient类和邮件服务器进行通讯,发送邮件。            //是和发邮件方的smtp通讯,由发邮件方的邮件服务器和收邮件方的邮件服务器通讯进行邮件的转接。            smtp.Host = smtpServer;         //smtp服务器名称            smtp.UseDefaultCredentials = true;            smtp.Credentials = new NetworkCredential(smtpUserName, smtpPassword);  //发送人的登录名和密码            smtp.Send(mailObj);        }关于邮箱的账号和密码最好配置到配置文件中。为了安全。

好好思考一下这样写的缺陷在哪?不仅有缺陷而且还有安全问题,有哪些安全问题?如果用户量大的话这样设计是否合理?会对什么有压力?如果不合理该如何优化?

首先我们来分析一下:

上面的方法是在用户表的基础上再增加一个字段,用来存激活码。这样合理吗?
由于激活码只用一次,所以在用户表的基础上再增加一个字段会麻烦一下,之前的功能会有影响。那到底该怎么解决比较好?

这时候Redis的好处就非常明显了,key-value数据库,并且还能设置数据的有效时间,很好的解决了上面遇到的问题,只需要改动上面很少的一部分代码就可以实现想要的功能。

代码如下:

技术分享
1  //方案二:把激活码存入的Redis中(最佳)2             //Redis代替数据库保存UserName和激活码的字典结构3             using (var client = RedisManager.ClientManager.GetClient())4             {5                 client.Set<string>(ACTIVECODE_PREFIX + username, activeCode, DateTime.Now.AddMinutes(30));6             }
View Code

如果到这里真的就OK了吗?我在这里买个关子,大家可以想想为什么我要添加下面的这段代码:

1 //把注册用户信息,放入消息队列。便于另外一个程序来获取消息队列数据,发送邮件2             using (var client = RedisManager.ClientManager.GetClient())3             {4                 string info = username + "|" + email;5                 client.EnqueueItemOnList("NewRegUsers", info);6             }

请大家先好好思考一下,问题有哪些,在评论区可以提出来,后面我会一一分析,因为后面会提到很多东西,比如并发量、定时器、Redis的消息队列、多线程、Windows服务等等。尽情期待~~~~~~~~~~

第四部分:Redis使用过程中需要注意的地方

 尽情期待~~~~~~~~~~

第五部分:总结

 尽情期待~~~~~~~~~~

 

 

项目中使用Redis的一些总结和体会