JS和C#.NET获取客户端IP - 海角之上 - 博客园

mikel阅读(579)

来源: JS和C#.NET获取客户端IP – 海角之上 – 博客园

我们经常在项目中会遇到这种需要获取客户端真实IP的需求,其实在网上也能随便就能查到各种获取的方法,我也是在网上查了加上了自己的实践,说一下自己在实践后的感受,基本上网上大部分都是用JS的方法来获取客户端的IP,今天我也分享一种C# .NET可以获取到客户端IP的方法。

一、JS获取客户端IP

基本上在网上可以查到的以下几种方法:

(1)使用搜狐接口(适用所有平台及浏览器)

<script src="http://pv.sohu.com/cityjson?ie=utf-8"></script>  
<script type="text/javascript">  
document.write(returnCitySN["cip"]+','+returnCitySN["cname"])  
</script>

这种方式我有尝试过,第一天还好好的,第二天就不行了,后面就变成:在我本地电脑上直接运行是没有问题的,但是发布到服务器后就没有效果了。

(2)使用新浪接口(适用所有平台及浏览器)

复制代码
<script type="text/javascript" src="http://counter.sina.com.cn/ip/" charset="gb2312"></script>       <!--获取接口数据,注意charset -->
<script type="text/javascript"> 
document.writeln("IP地址:"+ILData[0]+"<br />");           //输出接口数据中的IP地址 
document.writeln("地址类型:"+ILData[1]+"<br />");         //输出接口数据中的IP地址的类型 
document.writeln("地址类型:"+ILData[2]+"<br />");         //输出接口数据中的IP地址的省市
document.writeln("地址类型:"+ILData[3]+"<br />");         //输出接口数据中的IP地址的
document.writeln("地址类型:"+ILData[4]+"<br />");         //输出接口数据中的IP地址的运营商
</script>
复制代码

这个地址说实话我都打不开,可能是慢。放弃了。

 

以上两种都是使用的第三方接口的方式,好像还有一些个人的接口,从我的实践来看,第三方接口不稳定,而且修改了接口可能会导致你的项目出错,其实是不可取的。

还有针对IE浏览器,使用AcitiveX插件方法,这种方法必需要让用户设置自己的IE浏览器允许运行AcitiveX插件才可行,而且Firefox和Chrome等浏览器不支持,显然也是不可取的。

综上,其实我是不推荐采用JS的方式来获取客户端IP的,下面介绍一种C# .NET的方式,也是我自己在使用的。

二、C# .NET方式获取客户端IP

直接贴代码

复制代码
string IP;
if (Request.ServerVariables["HTTP_VIA"] != null) // 服务器, using proxy
{
         //得到真实的客户端地址
         IP = Request.ServerVariables["HTTP_X_FORWARDED_FOR"].ToString();  // Return real client IP.
}
else //如果没有使用代理服务器或者得不到客户端的ip  not using proxy or can't get the Client IP
{
          //得到服务端的地址    
          IP = Request.ServerVariables["REMOTE_ADDR"].ToString(); //While it can't get the Client IP, it will return proxy IP.
}
复制代码

 

JS获取ip地址 - Goatherd - 博客园

mikel阅读(630)

来源: JS获取ip地址 – Goatherd – 博客园




微信公众号开发之语音消息识别 - 洛水3000 - 博客园

mikel阅读(765)

来源: 微信公众号开发之语音消息识别 – 洛水3000 – 博客园

1.开通语音识别(默认关闭)

2.语音识别

请注意,开通语音识别后,用户每次发送语音给公众号时,微信会在推送的语音消息XML数据包中,增加一个Recognition字段(注:由于客户端缓存,开发者开启或者关闭语音识别功能,对新关注者立刻生效,对已关注用户需要24小时生效。开发者可以重新关注此帐号进行测试)。开启语音识别后的语音XML数据包如下:

 

复制代码
  1 <?php
  2 /**
  3   * wechat php test
  4   */
  5 
  6 //define your token
  7 define("TOKEN", "weixin");
  8 $wechatObj = new wechatCallbackapiTest();
  9 //$wechatObj->valid();//接口验证
 10 $wechatObj->responseMsg();//调用回复消息方法
 11 class wechatCallbackapiTest
 12 {
 13     public function valid()
 14     {
 15         $echoStr = $_GET["echostr"];
 16 
 17         //valid signature , option
 18         if($this->checkSignature()){
 19             echo $echoStr;
 20             exit;
 21         }
 22     }
 23 
 24     public function responseMsg()
 25     {
 26         //get post data, May be due to the different environments
 27         $postStr = $GLOBALS["HTTP_RAW_POST_DATA"];
 28 
 29           //extract post data
 30         if (!empty($postStr)){
 31                 /* libxml_disable_entity_loader is to prevent XML eXternal Entity Injection,
 32                    the best way is to check the validity of xml by yourself */
 33                 libxml_disable_entity_loader(true);
 34                   $postObj = simplexml_load_string($postStr, 'SimpleXMLElement', LIBXML_NOCDATA);
 35                 $fromUsername = $postObj->FromUserName;
 36                 $toUsername = $postObj->ToUserName;
 37                 $keyword = trim($postObj->Content);
 38                 $time = time();
 39                 $msgType = $postObj->MsgType;//消息类型
 40                 $event = $postObj->Event;//时间类型,subscribe(订阅)、unsubscribe(取消订阅)
 41                 
 42                 $textTpl = "<xml>
 43                             <ToUserName><![CDATA[%s]]></ToUserName>
 44                             <FromUserName><![CDATA[%s]]></FromUserName>
 45                             <CreateTime>%s</CreateTime>
 46                             <MsgType><![CDATA[%s]]></MsgType>
 47                             <Content><![CDATA[%s]]></Content>
 48                             <FuncFlag>0</FuncFlag>
 49                             </xml>"; 
 50                            
 51                 switch($msgType){
 52                     case "event":
 53                     if($event=="subscribe"){
 54                         $contentStr = "Hi,欢迎关注海仙日用百货!"."\n"."回复数字'1',了解店铺地址."."\n"."回复数字'2',了解商品种类.";
 55                     } 
 56                     break;
 57                     case "text"://文本消息
 58                         switch($keyword){
 59                             case "1":
 60                             $contentStr = "店铺地址:"."\n"."杭州市江干区.";    
 61                             break;
 62                             case "2":
 63                             $contentStr = "商品种类:"."\n"."杯子、碗、棉签、水桶、垃圾桶、洗碗巾(刷)、拖把、扫把、"
 64                                          ."衣架、粘钩、牙签、垃圾袋、保鲜袋(膜)、剪刀、水果刀、饭盒等.";
 65                             break;
 66                             default:
 67                             $contentStr = "对不起,你的内容我会稍后回复";
 68                         }
 69                     break;
 70                     case "voice"://语音消息
 71                     //语音识别
 72                     $recognition = $postObj->Recognition;
 73                     $format = $postObj->Format;
 74                     $contentStr = "你发送的是语音消息"."\n"."语音格式为:"."\n".$format."\n"."语音内容为:"."\n".$recognition;
 75                     break;
 76                 }
 77                 $msgType = "text";
 78                 $resultStr = sprintf($textTpl, $fromUsername, $toUsername, $time, $msgType, $contentStr);
 79                 echo $resultStr;
 80         }else {
 81             echo "";
 82             exit;
 83         }
 84     }
 85         
 86     private function checkSignature()
 87     {
 88         // you must define TOKEN by yourself
 89         if (!defined("TOKEN")) {
 90             throw new Exception('TOKEN is not defined!');
 91         }
 92         
 93         $signature = $_GET["signature"];
 94         $timestamp = $_GET["timestamp"];
 95         $nonce = $_GET["nonce"];
 96                 
 97         $token = TOKEN;
 98         $tmpArr = array($token, $timestamp, $nonce);
 99         // use SORT_STRING rule
100         sort($tmpArr, SORT_STRING);
101         $tmpStr = implode( $tmpArr );
102         $tmpStr = sha1( $tmpStr );
103         
104         if( $tmpStr == $signature ){
105             return true;
106         }else{
107             return false;
108         }
109     }
110 }
111 
112 
113 ?>
复制代码

 

阿里官方Redis开发规范! - 知乎

mikel阅读(636)

来源: 阿里官方Redis开发规范! – 知乎

本文主要介绍在使用阿里云Redis的开发规范,从下面几个方面进行说明。

  • 键值设计
  • 命令使用
  • 客户端使用
  • 相关工具

通过本文的介绍可以减少使用Redis过程带来的问题。

一、键值设计

1、key名设计

可读性和可管理性

以业务名(或数据库名)为前缀(防止key冲突),用冒号分隔,比如业务名:表名:id

ugc:video:1

简洁性

保证语义的前提下,控制key的长度,当key较多时,内存占用也不容忽视,例如:

user:{uid}:friends:messages:{mid}简化为u:{uid}:fr:m:{mid}

不要包含特殊字符

反例:包含空格、换行、单双引号以及其他转义字符

2、value设计

拒绝bigkey

防止网卡流量、慢查询,string类型控制在10KB以内,hash、list、set、zset元素个数不要超过5000。反例:一个包含200万个元素的list。非字符串的bigkey,不要使用del删除,使用hscan、sscan、zscan方式渐进式删除,同时要注意防止bigkey过期时间自动删除问题(例如一个200万的zset设置1小时过期,会触发del操作,造成阻塞,而且该操作不会不出现在慢查询中(latency可查)),查找方法和删除方法

选择适合的数据类型

例如:实体类型(要合理控制和使用数据结构内存编码优化配置,例如ziplist,但也要注意节省内存和性能之间的平衡)

反例:

set user:1:name tom
set user:1:age 19
set user:1:favor football

正例:

hmset user:1 name tom age 19 favor football

控制key的生命周期

redis不是垃圾桶,建议使用expire设置过期时间(条件允许可以打散过期时间,防止集中过期),不过期的数据重点关注idletime。

二、命令使用

1、O(N)命令关注N的数量

例如hgetall、lrange、smembers、zrange、sinter等并非不能使用,但是需要明确N的值。有遍历的需求可以使用hscan、sscan、zscan代替。

2、禁用命令

禁止线上使用keys、flushall、flushdb等,通过redis的rename机制禁掉命令,或者使用scan的方式渐进式处理。

3、合理使用select

redis的多数据库较弱,使用数字进行区分,很多客户端支持较差,同时多业务用多数据库实际还是单线程处理,会有干扰。

4、使用批量操作提高效率

  • 原生命令:例如mget、mset。
  • 非原生命令:可以使用pipeline提高效率。

但要注意控制一次批量操作的元素个数(例如500以内,实际也和元素字节数有关)。

注意两者不同:

  • 原生是原子操作,pipeline是非原子操作。
  • pipeline可以打包不同的命令,原生做不到
  • pipeline需要客户端和服务端同时支持。

5、不建议过多使用Redis事务功能

Redis的事务功能较弱(不支持回滚),而且集群版本(自研和官方)要求一次事务操作的key必须在一个slot上(可以使用hashtag功能解决)

6、Redis集群版本在使用Lua上有特殊要求

1、所有key都应该由 KEYS 数组来传递,redis.call/pcall 里面调用的redis命令,key的位置,必须是KEYS array, 否则直接返回error,”-ERR bad lua script for redis cluster, all the keys that the script uses should be passed using the KEYS arrayrn”2、所有key,必须在1个slot上,否则直接返回error, “-ERR eval/evalsha command keys must in same slotrn”

7、monitor命令

必要情况下使用monitor命令时,要注意不要长时间使用。

三、客户端使用

1、避免多个应用使用一个Redis实例

不相干的业务拆分,公共数据做服务化。

2、使用连接池

可以有效控制连接,同时提高效率,标准使用方式:

执行命令如下
Jedis jedis = null;
try {
    jedis = jedisPool.getResource();
 //具体的命令
    jedis.executeCommand()
} catch (Exception e) {
    logger.error("op key {} error: " + e.getMessage(), key, e);
} finally {
 //注意这里不是关闭连接,在JedisPool模式下,Jedis会被归还给资源池。
 if (jedis != null)
        jedis.close();
}

3、熔断功能

高并发下建议客户端添加熔断功能(例如netflix hystrix)

4、合理的加密

设置合理的密码,如有必要可以使用SSL加密访问(阿里云Redis支持)

5、淘汰策略

根据自身业务类型,选好maxmemory-policy(最大内存淘汰策略),设置好过期时间。默认策略是volatile-lru,即超过最大内存后,在过期键中使用lru算法进行key的剔除,保证不过期数据不被删除,但是可能会出现OOM问题。

其他策略如下:

  • allkeys-lru:根据LRU算法删除键,不管数据有没有设置超时属性,直到腾出足够空间为止。
  • allkeys-random:随机删除所有键,直到腾出足够空间为止。
  • volatile-random:随机删除过期键,直到腾出足够空间为止。
  • volatile-ttl:根据键值对象的ttl属性,删除最近将要过期数据。如果没有,回退到noeviction策略。
  • noeviction:不会剔除任何数据,拒绝所有写入操作并返回客户端错误信息”(error) OOM command not allowed when used memory”,此时Redis只响应读操作。

四、相关工具

1、数据同步

redis间数据同步可以使用:redis-port

2、big key搜索

redis大key搜索工具

3、热点key寻找

内部实现使用monitor,所以建议短时间使用facebook的redis-faina 阿里云Redis已经在内核层面解决热点key问题

五、删除bigkey

  • 下面操作可以使用pipeline加速。
  • redis 4.0已经支持key的异步删除,欢迎使用。

1、Hash删除: hscan + hdel

public void delBigHash(String host, int port, String password, String bigHashKey) {
 Jedis jedis = new Jedis(host, port);
 if (password != null && !"".equals(password)) {
        jedis.auth(password);
    }
 ScanParams scanParams = new ScanParams().count(100);
 String cursor = "0";
 do {
 ScanResult<Entry<String, String>> scanResult = jedis.hscan(bigHashKey, cursor, scanParams);
 List<Entry<String, String>> entryList = scanResult.getResult();
 if (entryList != null && !entryList.isEmpty()) {
 for (Entry<String, String> entry : entryList) {
                jedis.hdel(bigHashKey, entry.getKey());
            }
        }
        cursor = scanResult.getStringCursor();
    } while (!"0".equals(cursor));

 //删除bigkey
    jedis.del(bigHashKey);
} 

2、List删除: ltrim

public void delBigList(String host, int port, String password, String bigListKey) {
 Jedis jedis = new Jedis(host, port);
 if (password != null && !"".equals(password)) {
        jedis.auth(password);
    }
 long llen = jedis.llen(bigListKey);
 int counter = 0;
 int left = 100;
 while (counter < llen) {
 //每次从左侧截掉100个
        jedis.ltrim(bigListKey, left, llen);
        counter += left;
    }
 //最终删除key
    jedis.del(bigListKey);
} 

3、Set删除: sscan + srem

public void delBigSet(String host, int port, String password, String bigSetKey) {
 Jedis jedis = new Jedis(host, port);
 if (password != null && !"".equals(password)) {
        jedis.auth(password);
    }
 ScanParams scanParams = new ScanParams().count(100);
 String cursor = "0";
 do {
 ScanResult<String> scanResult = jedis.sscan(bigSetKey, cursor, scanParams);
 List<String> memberList = scanResult.getResult();
 if (memberList != null && !memberList.isEmpty()) {
 for (String member : memberList) {
                jedis.srem(bigSetKey, member);
            }
        }
        cursor = scanResult.getStringCursor();
    } while (!"0".equals(cursor));

 //删除bigkey
    jedis.del(bigSetKey);
} 

4、SortedSet删除: zscan + zrem

public void delBigZset(String host, int port, String password, String bigZsetKey) {
 Jedis jedis = new Jedis(host, port);
 if (password != null && !"".equals(password)) {
        jedis.auth(password);
    }
 ScanParams scanParams = new ScanParams().count(100);
 String cursor = "0";
 do {
 ScanResult<Tuple> scanResult = jedis.zscan(bigZsetKey, cursor, scanParams);
 List<Tuple> tupleList = scanResult.getResult();
 if (tupleList != null && !tupleList.isEmpty()) {
 for (Tuple tuple : tupleList) {
                jedis.zrem(bigZsetKey, tuple.getElement());
            }
        }
        cursor = scanResult.getStringCursor();
    } while (!"0".equals(cursor));

 //删除bigkey
    jedis.del(bigZsetKey);
}

原作者:付磊-起扬
原文链接:阿里云Redis开发规范-云栖社区-阿里云
原出处:阿里云
侵删

深夜,我偷听到程序员要对session下手…… - 轩辕之风 - 博客园

mikel阅读(586)

来源: 深夜,我偷听到程序员要对session下手…… – 轩辕之风 – 博客园

我是一个web服务器

我是一个web服务器,我的工作是给人类提供上网服务,我每天要为数以万计的人提供网页浏览服务。

 

 

已经是深夜了,我还在和手下几个兄弟为了一件事紧张讨论着。

“老大,现在咱们每天处理的请求越来越多了,session同步的问题不能再拖了,必须想个办法”

“二哥说的是啊,老大,不能再拖了”

“老二,老三,咱们是一个集群,你们说的问题我不是不知道,我昨天听程序员们在讨论说要给我们接入一个叫Redis的家伙,相信这一问题很快就能得到解决啦,大家再忍忍。”

 

 

“Redis,他是谁,什么来头?怎么没听过这号人物”

“我也没见过,等等看吧”

session-cookie时代

到底是什么问题,让我们兄弟几个如此着急上火?事情还得从多年以前说起······

那时候,这俩兄弟还没来,就我一个web服务器,每天处理的不过是一些静态资源文件,像HTML、CSS、JS、图片等等,日子过的清闲自在。

 

 

日子一天天过着,互联网却悄然发生着变化。除了静态网页之外,可以动态交互的网络应用开始如雨后春笋般涌现,像各种各样的论坛啊,购物网站啊之类的。

这家公司的老板也不例外,招了一帮程序员要搞一套支持动态网页交互的网站出来。

以往的时候,我只需要按照HTTP协议的规范处理请求就完事儿了。不过动态交互应用出现后,我还得记住每一个请求背后的用户是谁,要不然就张冠李戴,全乱套了。

为了解决这个问题,程序员们想出了一个叫session的办法:

浏览器登陆以后,我就分配一个session id,表示一个会话,然后返回给浏览器,让它保存着。后续再来请求的时候,就把它给带上,我就能知道是谁啦!

 

 

还别说,这办法还是挺管用的,成功解决了用户身份识别的问题,这一用就是好几年。

不过,互联网的发展实在是太快了,用户量蹭蹭上涨,而我却发起了愁。

原先用户量少的时候,session id管理起来倒也简单,现在用户越来越多,对应的session id数量也与日俱增,我有点不堪重负了。

终于前不久,公司对web服务器进行了扩展,给我找来了两个小弟,还专门添置了一个nginx来进行负载均衡,这一下我们变成了3台web服务器组成的小集群了。

 

 

我的工作一下轻松了许多,两位小兄弟为了分担了不少。我原以为以后的日子要好过一些了,可没想到,两位小兄弟的加入却引入了新的问题。

原先的session id虽然很多,我一个人累是累点,但是方便管理啊!现在人手是增加了,可是这个session id的管理问题却变得复杂起来。

因为咱们现在是个集群,请求如果发到我这,我给登记了session id,但下一次请求说不定就发到老二那里,一会儿又发到老三那里,这个就说不准了,这样我们几个手头的信息不一致,就会出现一些异常情况,用户估计要破口大骂:这什么辣鸡网站?

后来我们跟nginx商量了一下,让他同一个用户来的请求都发给我们固定的一个人,这才稳住了局面。

不过好景不长,后面我们三兄弟都相继出现过宕机的情况,这时候nginx还是得把请求交给还在工作的兄弟,原来的问题就又出现了!

我们几个逼急了,商量了一下,干脆大家伙来同步session id的信息好了,有新增、失效的情况都给其他几个兄弟招呼一下,大家都管理一份,这样就不会出现不一致的问题了。

 

 

搞了半天,又变成以前一个人管理所有session id的情况了,不仅如此,还要抽出时间和几位兄弟同步,把session id搬来搬去,工作量不减反增了。

就这样艰难的过了一段日子,大家都怨声载道,所以有了开头的那一番讨论。

这一次,希望这位新来的叫Redis的伙伴能拯救我们。

独立缓存——Redis

过了几天,总算把这个叫Redis的小伙伴给盼来了!

这小子看起来特别精神,了解清楚情况后,告诉我们说:“三位老哥,以后这session id都统一存在我这里吧,你们别各自保存了,这不是各位的擅长”

“你行吗?”,老二看着不太相信他的话,一脸不屑。

“行不行,试试不就知道了吗?”

 

 

接下来,我们听从了Redis的建议,不再保存这烦人的session id,全部一股脑儿交给了他,我们需要的时候再找他获取。

你还别说,这小子个子不大,本事不小,读写速度都特快,让我们头疼的问题总算是解决了!

Token时代

几个月后的一天···

“听说了吗?程序员们又要更改session id的存储方案了”,这一天,老二神神秘秘的说到。

“不对不对,我听到的版本是以后不用session id了,要变天了!”,老三也凑了上来。

一旁的redis老哥一听不乐意了,“咋的?是嫌我干的不好吗?”

我也赶紧催促,“你俩就别卖关子了,听到了什么,快说说”

 

 

老三示意大家围拢一些,小声说到:“我上次听两个程序员在议论,不知道他们在哪里学来了一套叫JWT(JSON Web Token) 的技术,硬说让我们来管理保存session id负担太重了,以后不保存了!还说,还说···”

“还说啥,你倒是说啊!”

“还说,Redis也不是万能的,也有崩溃的风险,一崩溃就全完了,所以要革新技术”,老三继续说到。

Redis一听更着急上火了,“我工作这么久以来,从没有撂过挑子吧,怎么能这么说我呢?再不行我也可以像你们搞个集群嘛”

“Redis老弟你先别急。唉,老三,这不保存session id,以后怎么鉴别用户呢?你有没有听到他们怎么说的?”

“听他们说,没有session id,但是换了一个token,用它来识别用户”

老二一听不以为意:“换了个名字,换汤不换药嘛!咱们还不是要保存token,才能匹配谁是谁”

老三摇了摇头:“不是的,这可不只是改了个名字那么简单!这个token是由三部分构成,就像这样:”

 

 

“你们看,第一部分是JWT的基本信息,然后把用户的身份信息放在第二部分,接着和第一部分合在一起做一个计算,计算的时候加入了一个只有我们才知道的密钥secretkey,计算结果作为第三部分。最后三部分拼在一起作为最终的token发送给客户端保存着···”

还没等老三说完,老二点出了其中的关键:“我知道了,后面咱们再收到这个token的时候,就可以通过同样的算法验证前面两部分的结果和第三部分是不是相同,就知道这个token是不是伪造的啦!因为密钥只有我们知道,别人没办法伪造出一个token的!最后确认有效之后,再取第二部分的用户身份信息,就知道这是谁了!”

 

 

听完他们的分析,我和Redis老兄都默默的点了点头,“有点意思啊,这样一来,咱们确实不用存了!不过现在咱们几个工作配合的也挺好的,他们费这么大劲是为了什么啊?”

“我猜他们是想节约开支,把Redis老哥给裁掉!”,老二说到。

老三摇了摇头,“依我看,八成他们是想展示技术给领导看,这不又快到职级晋升答辩了,他们想搞事情!唉,老大,这事你怎么看?”

“我啊,我···”

朋友们,你怎么看?session-cookie和JWT,你更倾向谁?

突然挂了!Redis缓存都在内存中,这下完了! - 轩辕之风 - 博客园

mikel阅读(536)

来源: 突然挂了!Redis缓存都在内存中,这下完了! – 轩辕之风 – 博客园

我是Redis,一个叫Antirez的男人把我带到了这个世界上。

“快醒醒!快醒醒!”,隐隐约约,我听到有人在叫我。

慢慢睁开眼睛,原来旁边是MySQL大哥。

“我怎么睡着了?”

“嗨,你刚才是不是出现了错误,整个进程都崩溃了!害得一大堆查询请求都给我怼过来了!”,MySQL说到。

刚刚醒来,脑子还有点懵,MySQL大哥扶我起来继续工作。

“糟了!我之前缓存的数据全都不见了!”

“WTF?你没有做持久化吗?”,MySQL大哥一听脸色都变了。

我尴尬的摇了摇头,“我都是保存在内存中的,所以才那么快啊”

“那也可以在硬盘上保存一下啊,遇到这种情况全部从头再来建立缓存,这不浪费时间嘛!”

我点了点头,“让我琢磨一下,看看怎么做这个持久化”。

RDB持久化

没几天,我就拿出了一套方案:RDB

既然我的数据都在内存中存放着,最简单的就是遍历一遍把它们全都写入文件中。

为了节约空间,我定义了一个二进制的格式,把数据一条一条码在一起,生成了一个RDB文件。

不过我的数据量有点大,要是全部备份一次得花不少时间,所以不能太频繁的去做这事,要不然我不用干正事了,光花时间去备份了。

还有啊,要是一直没有写入操作,都是读取操作,那我也不用重复备份,浪费时间。

思来想去,我决定提供一个配置参数,既可以支持周期性备份,也可以避免做无用功。

就像这样:

  • save 900 1     # 900秒(15分钟)内有1个写入
  • save 300 10    # 300秒(5分钟)内有10个写入
  • save 60 10000  # 60秒(1分钟)内有10000个写入

多个条件可以组合使用,只要上面一个条件满足,我就会去进行备份。

后来我又想了一下,这样还是不行,我得fork出一个子进程去做这件事,不能浪费我的时间。

有了备份文件,下次我再遇到崩溃退出,甚至服务器断电罢工了,只要我的备份文件还在,我就能在启动的时候读取,快速恢复之前的状态啦!

MySQL:binlog

我带着这套方案,兴冲冲的拿给了MySQL大哥看了,期待他给我一些鼓励。

“老弟,你这个方案有点问题啊”,没想到,他竟给我浇了一盆冷水。

“问题?有什么问题?”

“你看啊,你这个周期性去备份,周期还是分钟级别的,你可知道咱们这服务每秒钟都要响应多少请求,像你这样不得丢失多少数据?”,MySQL语重心长的说到。

我一下有些气短了,“可是,这个备份一次要遍历全部数据,开销还是挺大的,不适合高频执行啊”

“谁叫你一次遍历全部数据了?来来来,我给你看个东西”,MySQL大哥把我带到了一个文件目录下:

  • mysql-bin.000001
  • mysql-bin.000002
  • mysql-bin.000003
  • ···

“看,这些是我的二进制日志binlog,你猜猜看里面都装了些什么?”,MySQL大哥指着这一堆文件说到。

我看了一眼,全是一堆二进制数据,这哪看得懂,我摇了摇头。

“这里面呀记录了我对数据执行更改的所有操作,像是INSERTUPDATEDELETE等等动作,等我要进行数据恢复的时候就可以派上大用场了”

听他这么一说,我一下来了灵感!告别了MySQL大哥,回去研究起新的方案来了。

AOF持久化

你们也知道,我也是基于命令式的,每天的工作就是响应业务程序发来的命令请求。

回来以后,我决定照葫芦画瓢,学着MySQL大哥的样子,把我执行的所有写入命令都记录下来,专门写入了一个文件,并给这种持久化方式也取了一个名字:AOF(Append Only File)

不过我遇到了RDB方案同样的问题,我该多久写一次文件呢?

我肯定不能每执行一条写入命令就记录到文件中,那会严重拖垮我的性能!我决定准备一个缓冲区,然后把要记录的命令先临时保存在这里,然后再择机写入文件,我把这个临时缓冲区叫做aof_buf

说干就干,我试了一下,竟然发现数据没有写入到文件中去。多方打听才知道,原来操作系统也有个缓存区,我写的数据被他缓存起来了,没有给我写入到文件中去,这不是坑爹呢嘛!

看来,我写完了还得要去刷新一下,把数据真正给写下去,思来想去,我还是提供一个参数,让业务程序去设置什么时候刷新吧。

appendfsync参数,三个取值:

  • always: 每个事件周期都同步刷新一次
  • everysec: 每一秒都同步刷新一次
  • no: 我只管写,让操作系统自己决定什么时候真正写入吧

AOF重写

这一次我不像之前那么冲动,我决定先试运行一段时间再去告诉MySQL大哥,免得又被他戳到软肋。

试用了一段时间,各方面都运行良好,不过我发现随着时间的推移,我写的这个AOF备份文件越来越大,越来越大!不仅非常占硬盘空间,复制移动,加载分析都非常的麻烦耗时。

我得想个办法把文件给压缩一下,我把这个过程叫做AOF重写

一开始,我打算去分析原来的AOF文件,然后将其中的冗余指令去掉,来给AOF文件瘦瘦身,不过我很快放弃了这个想法,这工作量实在太大了,分析起来也颇为麻烦,浪费很多精力跟时间。

原来的一条条记录这种方式实在是太笨了,数据改来改去,有很多中间状态都没用,我何不就把最终都数据状态记录下来就好了?

比如:

  • RPUSH name_list ‘编程技术宇宙’
  • RPUSH name_list ‘帅地玩编程’
  • RPUSH name_list ‘后端技术学堂’

可以合并成一条搞定:

  • RPUSH name_list ‘编程技术宇宙’ ‘帅地玩编程’ ‘后端技术学堂’

AOF文件重写的思路我是有了,不过这件事干起来还是很耗时间,我决定和RDB方式一样,fork出一个子进程来做这件事情。

谨慎如我,发现这样做之后,子进程在重写期间,我要是修改了数据,就会出现和重写的内容不一致的情况!MySQL大哥肯定会挑刺儿,我还得把这个漏洞给补上。

于是,我在之前的aof_buf之外,又准备了一个缓冲区:AOF重写缓冲区

从创建重写子进程开始的那一刻起,我把后面来的写入命令也copy一份写到这个重写缓冲区中,等到子进程重写AOF文件结束之后,我再把这个缓冲区中的命令写入到新的AOF文件中。

最后再重命名新的AOF文件,替换掉原来的那个臃肿不堪的大文件,终于大功告成!

再三确定我的思路没有问题之后,我带着新的方案再次找到了MySQL大哥,我都做到这份儿上了,这一次,想必他应该无话可说了吧?

MySQL大哥看了我的方案露出了满意的笑容,只是问了一个问题:

这AOF方案这么好了,RDB方案是不是可以不要了呢?

万万没想到,他居然问我这个问题,我竟陷入了沉思,你觉得我该怎么回答好呢?

突然挂了!Redis缓存都在内存中,这下完了! - 轩辕之风 - 博客园

mikel阅读(684)

来源: 突然挂了!Redis缓存都在内存中,这下完了! – 轩辕之风 – 博客园

我是Redis,一个叫Antirez的男人把我带到了这个世界上。

“快醒醒!快醒醒!”,隐隐约约,我听到有人在叫我。

慢慢睁开眼睛,原来旁边是MySQL大哥。

“我怎么睡着了?”

“嗨,你刚才是不是出现了错误,整个进程都崩溃了!害得一大堆查询请求都给我怼过来了!”,MySQL说到。

刚刚醒来,脑子还有点懵,MySQL大哥扶我起来继续工作。

“糟了!我之前缓存的数据全都不见了!”

“WTF?你没有做持久化吗?”,MySQL大哥一听脸色都变了。

我尴尬的摇了摇头,“我都是保存在内存中的,所以才那么快啊”

“那也可以在硬盘上保存一下啊,遇到这种情况全部从头再来建立缓存,这不浪费时间嘛!”

我点了点头,“让我琢磨一下,看看怎么做这个持久化”。

RDB持久化

没几天,我就拿出了一套方案:RDB

既然我的数据都在内存中存放着,最简单的就是遍历一遍把它们全都写入文件中。

为了节约空间,我定义了一个二进制的格式,把数据一条一条码在一起,生成了一个RDB文件。

不过我的数据量有点大,要是全部备份一次得花不少时间,所以不能太频繁的去做这事,要不然我不用干正事了,光花时间去备份了。

还有啊,要是一直没有写入操作,都是读取操作,那我也不用重复备份,浪费时间。

思来想去,我决定提供一个配置参数,既可以支持周期性备份,也可以避免做无用功。

就像这样:

  • save 900 1     # 900秒(15分钟)内有1个写入
  • save 300 10    # 300秒(5分钟)内有10个写入
  • save 60 10000  # 60秒(1分钟)内有10000个写入

多个条件可以组合使用,只要上面一个条件满足,我就会去进行备份。

后来我又想了一下,这样还是不行,我得fork出一个子进程去做这件事,不能浪费我的时间。

有了备份文件,下次我再遇到崩溃退出,甚至服务器断电罢工了,只要我的备份文件还在,我就能在启动的时候读取,快速恢复之前的状态啦!

MySQL:binlog

我带着这套方案,兴冲冲的拿给了MySQL大哥看了,期待他给我一些鼓励。

“老弟,你这个方案有点问题啊”,没想到,他竟给我浇了一盆冷水。

“问题?有什么问题?”

“你看啊,你这个周期性去备份,周期还是分钟级别的,你可知道咱们这服务每秒钟都要响应多少请求,像你这样不得丢失多少数据?”,MySQL语重心长的说到。

我一下有些气短了,“可是,这个备份一次要遍历全部数据,开销还是挺大的,不适合高频执行啊”

“谁叫你一次遍历全部数据了?来来来,我给你看个东西”,MySQL大哥把我带到了一个文件目录下:

  • mysql-bin.000001
  • mysql-bin.000002
  • mysql-bin.000003
  • ···

“看,这些是我的二进制日志binlog,你猜猜看里面都装了些什么?”,MySQL大哥指着这一堆文件说到。

我看了一眼,全是一堆二进制数据,这哪看得懂,我摇了摇头。

“这里面呀记录了我对数据执行更改的所有操作,像是INSERTUPDATEDELETE等等动作,等我要进行数据恢复的时候就可以派上大用场了”

听他这么一说,我一下来了灵感!告别了MySQL大哥,回去研究起新的方案来了。

AOF持久化

你们也知道,我也是基于命令式的,每天的工作就是响应业务程序发来的命令请求。

回来以后,我决定照葫芦画瓢,学着MySQL大哥的样子,把我执行的所有写入命令都记录下来,专门写入了一个文件,并给这种持久化方式也取了一个名字:AOF(Append Only File)

不过我遇到了RDB方案同样的问题,我该多久写一次文件呢?

我肯定不能每执行一条写入命令就记录到文件中,那会严重拖垮我的性能!我决定准备一个缓冲区,然后把要记录的命令先临时保存在这里,然后再择机写入文件,我把这个临时缓冲区叫做aof_buf

说干就干,我试了一下,竟然发现数据没有写入到文件中去。多方打听才知道,原来操作系统也有个缓存区,我写的数据被他缓存起来了,没有给我写入到文件中去,这不是坑爹呢嘛!

看来,我写完了还得要去刷新一下,把数据真正给写下去,思来想去,我还是提供一个参数,让业务程序去设置什么时候刷新吧。

appendfsync参数,三个取值:

  • always: 每个事件周期都同步刷新一次
  • everysec: 每一秒都同步刷新一次
  • no: 我只管写,让操作系统自己决定什么时候真正写入吧

AOF重写

这一次我不像之前那么冲动,我决定先试运行一段时间再去告诉MySQL大哥,免得又被他戳到软肋。

试用了一段时间,各方面都运行良好,不过我发现随着时间的推移,我写的这个AOF备份文件越来越大,越来越大!不仅非常占硬盘空间,复制移动,加载分析都非常的麻烦耗时。

我得想个办法把文件给压缩一下,我把这个过程叫做AOF重写

一开始,我打算去分析原来的AOF文件,然后将其中的冗余指令去掉,来给AOF文件瘦瘦身,不过我很快放弃了这个想法,这工作量实在太大了,分析起来也颇为麻烦,浪费很多精力跟时间。

原来的一条条记录这种方式实在是太笨了,数据改来改去,有很多中间状态都没用,我何不就把最终都数据状态记录下来就好了?

比如:

  • RPUSH name_list ‘编程技术宇宙’
  • RPUSH name_list ‘帅地玩编程’
  • RPUSH name_list ‘后端技术学堂’

可以合并成一条搞定:

  • RPUSH name_list ‘编程技术宇宙’ ‘帅地玩编程’ ‘后端技术学堂’

AOF文件重写的思路我是有了,不过这件事干起来还是很耗时间,我决定和RDB方式一样,fork出一个子进程来做这件事情。

谨慎如我,发现这样做之后,子进程在重写期间,我要是修改了数据,就会出现和重写的内容不一致的情况!MySQL大哥肯定会挑刺儿,我还得把这个漏洞给补上。

于是,我在之前的aof_buf之外,又准备了一个缓冲区:AOF重写缓冲区

从创建重写子进程开始的那一刻起,我把后面来的写入命令也copy一份写到这个重写缓冲区中,等到子进程重写AOF文件结束之后,我再把这个缓冲区中的命令写入到新的AOF文件中。

最后再重命名新的AOF文件,替换掉原来的那个臃肿不堪的大文件,终于大功告成!

再三确定我的思路没有问题之后,我带着新的方案再次找到了MySQL大哥,我都做到这份儿上了,这一次,想必他应该无话可说了吧?

MySQL大哥看了我的方案露出了满意的笑容,只是问了一个问题:

这AOF方案这么好了,RDB方案是不是可以不要了呢?

万万没想到,他居然问我这个问题,我竟陷入了沉思,你觉得我该怎么回答好呢?

还不懂Redis?看完这个故事就明白了! - 轩辕之风 - 博客园

mikel阅读(563)

来源: 还不懂Redis?看完这个故事就明白了! – 轩辕之风 – 博客园

我是Redis

你好,我是Redis,一个叫Antirez的男人把我带到了这个世界上。

说起我的诞生,跟关系数据库MySQL还挺有渊源的。

在我还没来到这个世界上的时候,MySQL过的很辛苦,互联网发展的越来越快,它容纳的数据也越来越多,用户请求也随之暴涨,而每一个用户请求都变成了对它的一个又一个读写操作,MySQL是苦不堪言。尤其是到“双11”、“618“这种全民购物狂欢的日子,都是MySQL受苦受难的日子。

据后来MySQL告诉我说,其实有一大半的用户请求都是读操作,而且经常都是重复查询一个东西,浪费它很多时间去进行磁盘I/O。

后来有人就琢磨,是不是可以学学CPU,给数据库也加一个缓存呢?于是我就诞生了!

出生不久,我就和MySQL成为了好朋友,我们俩常常携手出现在后端服务器中。

应用程序们从MySQL查询到的数据,在我这里登记一下,后面再需要用到的时候,就先找我要,我这里没有再找MySQL要。

为了方便使用,我支持好几种数据结构的存储:

  • String
  • Hash
  • List
  • Set
  • SortedSet
  • Bitmap
  • ······

因为我把登记的数据都记录在内存中,不用去执行慢如蜗牛的I/O操作,所以找我要比找MySQL要省去了不少的时间呢。

可别小瞧这简单的一个改变,我可为MySQL减轻了不小的负担!随着程序的运行,我缓存的数据越来越多,有相当部分时间我都给它挡住了用户请求,这一下它可乐得清闲自在了!

有了我的加入,网络服务的性能提升了不少,这都归功于我为数据库挨了不少枪子儿。

缓存过期 && 缓存淘汰

不过很快我发现事情不妙了,我缓存的数据都是在内存中,可是就算是在服务器上,内存的空间资源还是很有限的,不能无节制的这么存下去,我得想个办法,不然吃枣药丸。

不久,我想到了一个办法:给缓存内容设置一个超时时间,具体设置多长交给应用程序们去设置,我要做的就是把过期了的内容从我里面删除掉,及时腾出空间就行了。

超时时间有了,我该在什么时候去干这个清理的活呢?

最简单的就是定期删除,我决定100ms就做一次,一秒钟就是10次!

我清理的时候也不能一口气把所有过期的都给删除掉,我这里面存了大量的数据,要全面扫一遍的话那不知道要花多久时间,会严重影响我接待新的客户请求的!

时间紧任务重,我只好随机选择一部分来清理,能缓解内存压力就行了。

 

就这样过了一段日子,我发现有些个键值运气比较好,每次都没有被我的随机算法选中,每次都能幸免于难,这可不行,这些长时间过期的数据一直霸占着不少的内存空间!气抖冷!

我眼里可揉不得沙子!于是在原来定期删除的基础上,又加了一招:

那些原来逃脱我随机选择算法的键值,一旦遇到查询请求,被我发现已经超期了,那我就绝不客气,立即删除。

这种方式因为是被动式触发的,不查询就不会发生,所以也叫惰性删除!

可是,还是有部分键值,既逃脱了我的随机选择算法,又一直没有被查询,导致它们一直逍遥法外!而于此同时,可以使用的内存空间却越来越少。

而且就算退一步讲,我能够把过期的数据都删除掉,那万一过期时间设置的很长,还没等到我去清理,内存就吃满了,一样要吃枣药丸,所以我还得想个办法。

我苦思良久,终于憋出了个大招:内存淘汰策略,这一次我要彻底解决问题!

我提供了8种策略供应用程序选择,用于我遇到内存不足时该如何决策:

  • noeviction:返回错误,不会删除任何键值
  • allkeys-lru:使用LRU算法删除最近最少使用的键值
  • volatile-lru:使用LRU算法从设置了过期时间的键集合中删除最近最少使用的键值
  • allkeys-random:从所有key随机删除
  • volatile-random:从设置了过期时间的键的集合中随机删除
  • volatile-ttl:从设置了过期时间的键中删除剩余时间最短的键
  • volatile-lfu:从配置了过期时间的键中删除使用频率最少的键
  • allkeys-lfu:从所有键中删除使用频率最少的键

有了上面几套组合拳,我再也不用担心过期数据多了把空间撑满的问题了~

缓存穿透 && 布隆过滤器

我的日子过的还挺舒坦,不过MySQL大哥就没我这么舒坦了,有时候遇到些烦人的请求,查询的数据不存在,MySQL就要白忙活一场!不仅如此,因为不存在,我也没法缓存啊,导致同样的请求来了每次都要去让MySQL白忙活一场。我作为缓存的价值就没得到体现啦!这就是人们常说的缓存穿透。

 

这一来二去,MySQL大哥忍不住了:“唉,兄弟,能不能帮忙想个办法,把那些明知道不会有结果的查询请求给我挡一下”

这时我想到了我的另外一个好朋友:布隆过滤器

我这位朋友别的本事没有,就擅长从超大的数据集中快速告诉你查找的数据存不存在(悄悄告诉你,我的这位朋友有一点不靠谱,它告诉你存在的话不能全信,其实有可能是不存在的,不过它他要是告诉你不存在的话,那就一定不存在)。

 

如果你对我这位朋友感兴趣的话,可以看看这里《白话布隆过滤器BloomFilter》

我把这位朋友介绍给了应用程序,不存在的数据就不必去叨扰MySQL了,轻松帮忙解决了缓存穿透的问题。

缓存击穿 && 缓存雪崩

这之后过了一段时间太平日子,直到那一天···

有一次,MySQL那家伙正优哉游哉的摸鱼,突然一大堆请求给他怼了过去,给他打了一个措手不及。

一阵忙活之后,MySQL怒气冲冲的找到了我,“兄弟,咋回事啊,怎么一下子来的这么猛”

我查看了日志,赶紧解释到:“大哥,实在不好意思,刚刚有一个热点数据到了过期时间,被我删掉了,不巧的是随后就有对这个数据的大量查询请求来了,我这里已经删了,所以请求都发到你那里来了”

“你这干的叫啥事,下次注意点啊”,MySQL大哥一脸不高兴的离开了。

这一件小事我也没怎么放在心上,随后就抛之脑后了,却没曾想几天之后竟捅了更大的篓子。

那一天,又出现了大量的网络请求发到了MySQL那边,比上一次的规模大得多,MySQL大哥一会儿功夫就给干趴下了好几次!

等了好半天这一波流量才算过去,MySQL才缓过神来。

“老弟,这一次又是什么原因?”,MySQL大哥累的没了力气。

“这一次比上一次更不巧,这一次是一大批数据几乎同时过了有效期,然后又发生了很多对这些数据的请求,所以比起上一次这规模更大了”

MySQL大哥听了眉头一皱,“那你倒是想个办法啊,三天两头折磨我,这谁顶得住啊?”

“其实我也很无奈,这个时间也不是我设置的,要不我去找应用程序说说,让他把缓存过期时间设置的均匀一些?至少别让大量数据集体失效”

“走,咱俩一起去”

后来,我俩去找应用程序商量了,不仅把键值的过期时间随机了一下,还设置了热点数据永不过期,这个问题缓解了不少。哦对了,我们还把这两次发生的问题分别取了个名字:缓存击穿和缓存雪崩。

我们终于又过上了舒适的日子···

redis解决了什么问题? - 知乎

mikel阅读(1179)

来源: redis解决了什么问题? – 知乎

小主 | 兰希姑娘

最近参加一次项目的架构评审,一位小哥提到,当前的架构缺少redis,这是不可以的,原因是不符合高可用的原则,他的高可用指的是当mySQL宕机的时候,如果有redis,系统还可以继续提供服务,也就是说他认为一定要有redis,是为了提升系统应对mySQL宕机的风险。

不知道大家怎么看,虽说有一定道理,但是侧重点是不是不太对?之所以有了mysql,系统还要使用redis,主要的原因并非是灾备考虑,而是其他的原因。

今天就来聊聊有了mysql,为什么还要使用redis,redis解决了什么问题?

mysql的功能和存在的问题

首先我们先聊下mysql的功能和存在的问题。

mysql数据存储在磁盘里,对数据有强一致需求、持久存储需求的项目需要选择mysql(以及类似的数据库)。

但是mysql支持并发访问的能力有限,当有大量并发请求的时候,mysql会挂掉的。

另外,有时候,我们需要更快的响应速度,而mysql速度有限,不能满足需求。

上面的两个问题,恰好都是redis的强项。

为什么要使用redis

实际上,在项目中使用redis,其实主要是从两个角度去考虑: 性能和并发。(以下内容引用自作者:孤独烟的文章)

(一)性能 如下图所示,我们在碰到需要执行耗时特别久,且结果不频繁变动的SQL,就特别适合将运行结果放入缓存。这样,后面的请求就去缓存中读取,使得请求能够迅速响应。

(二)并发 如下图所示,在大并发的情况下,所有的请求直接访问数据库,数据库会出现连接异常。这个时候,就需要使用redis做一个缓冲操作,让请求先访问到redis,而不是直接访问数据库。

redis基础知识介绍

说到这里,再来补充下redis的基础知识。

Redis(Remote Dictionary Server ),即远程字典服务,是一个开源的使用ANSI C语言 编写,是一个高性能的key-value存储系统。

为了保证效率,数据都是缓存在内存中,同时redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步。

它支持存储的value类型相对更多,包括string(字符串)、list(链表)、set(集合)、sorted set 和hash(哈希类型)。

redis提供多种语言的API,它提供了Java,C/C++,C#,PHP,JavaScript,Perl,Object-C,Python,Ruby,Erlang等客户端,使用很方便。

4 常见的redis+mysql设计方案

目前大多数公司的存储都是mysql + redis,mysql作为主存储,redis作为辅助存储被用作缓存,加快访问读取的速度,提高性能。

具体来讲就是,mysql存储着所有的业务数据,根据业务规模会采用相应的分库分表、读写分离、主备容灾、数据库集群等手段。

但是由于mysql是基于磁盘的IO,基于服务响应性能考虑,将业务热数据利用redis缓存,使得高频业务数据可以直接从内存读取,提高系统整体响应速度。

常见的设计方案是:redis+mysql读写分离方案。

具体如下。

业务数据读操作流程:

业务数据更新操作流程:

redis+mysql结合导致的一致性问题

两者结合最大的问题就是缓存和数据库双写一致性问题。

为了解决这个问题,我们可以对存入缓存的数据设置过期时间,也就是说如果数据库写成功,缓存更新失败,那么只要到达过期时间,则后面的读请求自然会从数据库中读取新值然后回填缓存。

这个方法作为一致性的兜底方案。

更精准的做法是,在写操作上,首先,采取正确更新策略,先更新数据库,再删缓存。其次,因为可能存在删除缓存失败的问题,提供一个补偿措施即可,例如利用消息队列。

有的方案在这里采用的是更新完mysql成功后,然后进行相应的更新redis中数据的操作。

这里的问题是,要考虑到更新的操作可能是并发的,而写mysql和写redis是两个步骤,不是原子性的。

例如有线程1和线程2同时进行写操作,执行顺序可能是如下的情况:

线程1写mysql->线程2写mysql->线程2写redis->线程1写redis

这样的话,结果变成了mysql的内容是线程2写入的,而redis的内容是线程1写入的,mysql和redis中的数据就不一致了,后续的数据读取都是错的。

而采用每次写完mysql后就清除redis的方式,就保证了写完后的读取必然会重新从mysql读取数据,然后写入redis。这样就保证了redis里的数据最终和mysql中是一致的,保证了数据的最终一致性。

当然,这些措施并不能保证完全杜绝不一致的发生。

数据库和缓存双写,就必然会存在不一致的问题。

如果对数据有强一致性要求,就不能放缓存,因为我们所做的方案其实从根本上来说,只能降低不一致发生的概率,无法完全避免。

6 redis这么好,那么为什么不直接全部用redis存储呢

聊聊最后一个问题,不知道大家有没有这样的疑问:redis这么好,那么为什么不直接全部用redis存储呢?答案很简单,贵呀。

因为redis存储在内存中,如果存储在内存中,存储容量肯定要比磁盘少很多,那么要存储大量数据,只能花更多的钱去购买内存,造成在一些不需要高性能的地方是相对比较浪费的,所以目前基本都是mysql(主) + redis(辅),在需要性能的地方使用redis,在不需要高性能的地方使用mysql。

技术精进是一场修行,相信自己和时间力量。

MongoDB集群搭建 —— 主从模式 - 简书

mikel阅读(932)

来源: MongoDB集群搭建 —— 主从模式 – 简书

本文为转载,原文:MongoDB集群搭建 —— 主从模式

介绍

在大数据的时代,传统的关系型数据库要能更高的服务必须要解决高并发读写、海量数据高效存储、高可扩展性和高可用性这些难题。不过就是因为这些问题NoSQL诞生了。

NOSQL有这些优势:

  1. 大数据量,可以通过廉价服务器存储大量的数据,轻松摆脱传统mysql单表存储量级限制。
  2. 高扩展性,Nosql去掉了关系数据库的关系型特性,很容易横向扩展,摆脱了以往老是纵向扩展的诟病。
  3. 高性能,Nosql通过简单的key-value方式获取数据,非常快速。还有NoSQL的Cache是记录级的,是一种细粒度的Cache,所以NoSQL在这个层面上来说就要性能高很多。
  4. 灵活的数据模型,NoSQL无需事先为要存储的数据建立字段,随时可以存储自定义的数据格式。而在关系数据库里,增删字段是一件非常麻烦的事情。如果是非常大数据量的表,增加字段简直就是一个噩梦。
  5. 高可用,NoSQL在不太影响性能的情况,就可以方便的实现高可用的架构。比如mongodb通过mongos、mongo分片就可以快速配置出高可用配置。

mongo在应用中有几种常见的模式,单实例、主从模式、副本集模式、分片。

单实例

这种配置只适合简易开发时使用,生产使用不行,因为单节点挂掉整个数据业务全挂,如下图。

虽然不能生产使用,但这个模式可以快速搭建启动,并且能够用mongodb的命令操作数据库。之前的文章中提到的都是单实例的,这里就不赘述了。

主从模式

采用双机备份后主节点挂掉了后从节点可以接替主机继续服务。所以这种模式比单节点的高可用性要好很多。

主从配置

下面我们将一步一步来搭建主从模式的数据库集群
首先我这边已经准备好两台服务器10.29.240.13, 10.29.240.51,其中10.29.240.13作为主服务器10.29.240.51作为从服务器

主服务器

在主服务器上创建个存放数据的路径:

mkdir -p /data/mongodb/master

然后通过mongo命令启动服务,加上-master参数,这是作为主服务器启动的参数。

mongod –dbpath /data/mongodb/master –master

 

可以看到我们的数据库已经以主服务器模式启动了。我们可以使用mongo命令登进去看看。

然后依次执行以下命令,创建数据库,创建集合并插入数据: 

> use mydb
> db.createCollection("students")
> db.students.insert({
name:"tom",
age:23,
gender:"male"
})

到此,我们主服务器便完成了。

从服务器

在从服务器上创建个存放数据的路径:

mkdir -p /data/mongodb/slave

将数据库以从数据库的模式启动:

mongod -dbpath /data/mongodb/slave -slave -source 10.29.240.13:27017

 

从结果中我们可以看到我们的数据库是以从数据库方式启动的。
下面我们用mongo命令登录从数据库,看下数据有没有同步进来

报错了,说是not master and slaveOk=false
这是正常的,因为SECONDARY是不允许读写的,如果非要解决,执行以下命令 

rs.slaveOk()

 

从上图中,我们看到执行rs.slaveOk()后,我们就可以读取数据库内容了。我们查询到了主数据插入的数据。 

主从复制

通过以上的配置,我们已经实现了主从模式的搭建,下面我们试下再次从主服务器插入数据,从数据库是否能及时更新。
主服务器执行以下命令,插入数据:

db.students.insert({
name:"iris",
age:18,
gender:"female"
})

 

然后在从服务器继续查询 

db.students.find()

我们看到,从数据库已经同步到了主数据库的变化。

那我们在试试在从数据库中写入数据:

db.students.insert({
name:"chain",
age:20,
gender:"male"
})

报错了。原因是在主从模式下,从服务器是不允许写数据的,只能读数据。

故障转移测试

现在两台服务器如果主服务器挂掉了,从服务器可以正常运转吗?
先执行以下命令杀掉原来的mongodb主服务器

kill -3 `ps -ef|grep mongod|grep -v grep|awk '{print $2}'`

测试从服务器能否可写:

db.students.insert({
name:"chain",
age:20,
gender:"male"
})

 

这个时候还是报not master的错.
看起来从服务器没有自动接替主服务器的功能,只有手工处理了!
停止从服务器,在原数据文件启动并添加主服务器标示。
从服务器上依次执行以下命令: 

kill -3 `ps -ef|grep mongod|grep -v grep|awk '{print $2}'`
mongod  --dbpath /data/mongodb/slave --master

这个时候就变成主服务器了,我们再执行下新增和查询命令试下:

读写都正常了。

多个从节点

现在只是一个数据库服务器又提供写又提供读,机器承载会出现瓶颈。大家还记得mysql里的读写分离吗?把20%的写放到主节点,80%的读放到从节点分摊了减少了服务器的负载。但是大部分应用都是读操作带来的压力,一个从节点压力负载不了,可以把一个从节点变成多个节点。那mongodb的一主多从可以支持吗?答案是肯定的。

问题

搭建了这套主从复制系统是不是就很稳健了,其实不然。。。看看这几个问题?

  • 主节点挂了能否自动切换连接?目前需要手工切换。
  • 主节点的写压力过大如何解决?
  • 从节点每个上面的数据都是对数据库全量拷贝,从节点压力会不会过大?
  • 就算对从节点路由实施路由访问策略能否做到自动扩展?

还有这么多问题,有其他解决方案吗?后面的文章我们接着搞。

作者:ChainZhang
链接:https://www.jianshu.com/p/aec4899df434
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。