微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!
搭建高可用的redis集群,避免standalone模式带给你的苦难
现在项目上用redis的话,很少说不用集群的情况,毕竟如果生产上只有一台redis会有极大的风险,比如机器挂掉,或者内存爆掉,就比如我们生产环境曾今也遭遇到这种情况,导致redis内存不够挂掉的情况,当然这些都是我们及其不能容忍的,第一个必须要做到高可靠,其次才是高性能,好了,下面我来逐一搭建一下。 一:Redis集群搭建1. 下载   首先去官网下载较新的3.2.0版本,下载方式还是非常简单的,比如官网介绍的这样。$ wget http://download.redis.io/releases/redis-3.2.0.tar.gz$ tar xzf redis-3.2.0.tar.gz$ cd redis-3.2.0$ make 2. redis配置由于我们要做集群,而且还要redis自带的redis-trib.rb 能正常运行,我们需要在集群中开启三台master,三台slave,所以这里我需要建立6个文件夹,而且文件夹的名称就使用端口地址的名字,比如:6389. 6380....6384。3. config配置。   现在directory的分布情况大致如上图,接下来要做的事情就是配置redis.conf了,在这里需要配置四个选项。。。 <1> port  端口地址,比如6380文件夹下面的port就是6380,# Accept connections on the specified port, default is 6379 (IANA #815344).# If port 0 is specified Redis will not listen on a TCP socket.port 6379 <2> cluster-enabled 和 cluster-config-file       这个顾名思义,首先需要开启redis的cluster模式,然后配置一个cluster-config-file文件,这个文件用于存放redis的实时信息,redis会动态追加和修改这个conf下面的内容信息,不过要记住,这个nodes-6379.conf 可以根据 端口文件夹依次配置,比如6380文件夹可以改成nodes-6380.conf这样。。。# Normal Redis instances can't be part of a Redis Cluster; only nodes that are# started as cluster nodes can. In order to start a Redis instance as a# cluster node enable the cluster support uncommenting the following:#cluster-enabled yes# Every cluster node has a cluster configuration file. This file is not# intended to be edited by hand. It is created and updated by Redis nodes.# Every Redis Cluster node requires a different cluster configuration file.# Make sure that instances running in the same system do not have# overlapping cluster configuration file names.#cluster-config-file nodes-6379.conf <3> directory      为了方便管理,我这里配置的root目录取决于在哪个文件夹,比如6380下面我的dir就是: dir ./6380/# Note that you must specify a directory here, not a file name.dir ./6379/ <4> protected-mode      这个是redis 3.2 才追加的一个功能,从功能注释中,我们就可以发现,这个默认就是不让外界可以访问redis,所以这里我们就改为no,可以远程访问。# By default protected mode is enabled. You should disable it only if# you are sure you want clients from other hosts to connect to Redis# even if no authentication is configured, nor a specific set of interfaces# are explicitly listed using the "bind" directive.protected-mode no ok,到现在为止,我们的config就修改完毕了,其他端口的文件夹也可以依次配置之~ 二:开启redis    到现在为止,各个端口文件夹都配置成功了,接下来准备开启了,真的好么么哒~~~,窗口太多,有点萌萌的。  接下来我们可以看一下,在6379下面是不是有生成node-6379.conf文件,比如下面: 三:配置redis-trib.rb   因为redis-trib.rb是ruby写的,而我们的电脑肯定是没有ruby和一些配置依赖项,不过没关系,有强大的yum安装,一切都不是问题。 1. 执行replicas命令[jack@localhost ~]$ cluster/redis-trib.rb create --replicas 1 192.168.161.133:6379 192.168.161.133:6380 192.168.161.133:6381 192.168.161.133:6382 192.168.161.133:6383 192.168.161.133:6384/usr/bin/env: ruby: No such file or directory[jack@localhost ~]$   可以看到ruby是没有安装的,所以下一步我们要安装ruby了。。。 2. 安装ruby 【一定要是管理员权限哦】[jack@localhost ~]$ sudousage: sudo [-D level] -h | -K | -k | -Vusage: sudo -v [-AknS] [-D level] [-g groupname|#gid] [-p prompt] [-u username|#uid]usage: sudo -l[l] [-AknS] [-D level] [-g groupname|#gid] [-p prompt] [-U username] [-u user name|#uid] [-g groupname|#gid] [command]usage: sudo [-AbEHknPS] [-r role] [-t type] [-C fd] [-D level] [-ggroupname|#gid] [-p prompt] [-u user name|#uid] [-g groupname|#gid][VAR=value] [-i|-s] [<command>]usage: sudo -e [-AknS] [-r role] [-t type] [-C fd] [-D level] [-ggroupname|#gid] [-p prompt] [-u user name|#uid] file ...[jack@localhost ~]$ suPassword:jacsu: incorrect password[jack@localhost ~]$ yum install rubyLoaded plugins: fastestmirror, refresh-packagekit, securityYou need to be root to perform this command.[jack@localhost ~]$ jackbash: jack: command not found[jack@localhost ~]$ suPassword:[root@localhost jack]# yum install rubyLoaded plugins: fastestmirror, refresh-packagekit, securityLoading mirror speeds from cached hostfile* base: mirror.bit.edu.cn* extras: mirror.bit.edu.cn* updates: mirror.bit.edu.cnSetting up Install ProcessResolving Dependencies--> Running transaction check---> Package ruby.x86_64 0:1.8.7.374-4.el6_6 will be installed--> Processing Dependency: ruby-libs = 1.8.7.374-4.el6_6 for package: ruby-1.8.7.374-4.el6_6.x86_64--> Processing Dependency: libruby.so.1.8()(64bit) for package: ruby-1.8.7.374-4.el6_6.x86_64--> Running transaction check---> Package ruby-libs.x86_64 0:1.8.7.374-4.el6_6 will be installed--> Processing Dependency: libreadline.so.5()(64bit) for package: ruby-libs-1.8.7.374-4.el6_6.x86_64--> Running transaction check---> Package compat-readline5.x86_64 0:5.2-17.1.el6 will be installed--> Finished Dependency ResolutionDependencies Resolved================================================================================Package Arch Version Repository Size================================================================================Installing:ruby x86_64 1.8.7.374-4.el6_6 base 538 kInstalling for dependencies:compat-readline5 x86_64 5.2-17.1.el6 base 130 kruby-libs x86_64 1.8.7.374-4.el6_6 base 1.7 MTransaction Summary================================================================================Install 3 Package(s)Total download si
redis-cli中那些或许我们还不知道的一些实用小功能
玩过redis的朋友都知道,redis中有一个叫做redis-cli的小工具,我们可以利用它在test和develop环境下进行高效的模拟测试,然而在现实环境中,我们只知道直接键入redis-cli启动命令的交互式,而这个对redis-cli来说绝对是九牛一毛,下面我逐一给大家演示下。 一:非REPL (Read Eval Print Loop) 模式     通常我们都是使用REPL模式,就是连接上端口之后,发一条request再等待response这样一个loop的形式,如下所示:[root@localhost Desktop]# redis-cli -h 192.168.1.216192.168.1.216:6379> set username jackOK192.168.1.216:6379> set password 12345OK192.168.1.216:6379> 其实我还可以直接在命令行中使用redis-cli再配合各种附加参数,效果和上面图示是一模一样的,比如下面这样:[root@localhost Desktop]# redis-cli -h 192.168.1.216 set username jackOK[root@localhost Desktop]# redis-cli -h 192.168.1.216 set password 12345OK[root@localhost Desktop]#看到没有,这样也是可以的,是不是有点意思哈~~~ 二:从本地文件中执行命令导入     乍一看还是挺酷的,就是可以把本地文件中的一组redis命令直接导入到redis-cli中执行,这样也就免去了一行一行的去键入了,对不对,工作量可以大大的减轻,比如下面这样: 1. 首先找一个目录,这里就选择/usr/, 下面新建一个txt文件,命令还是非常的简单,执行两个set操作。 然后我们用 < 命令导入就可以了,这里126的ip是本地局域网内的一台虚拟机,是不是有点像pipeline管道操作,牛逼吧~~~ 如下所示:[root@localhost Desktop]# redis-cli -h 192.168.1.216 < /usr/1.txtOKOK[root@localhost Desktop]# 三:对指定redis命令重复调用对指定的redis命令进行重复调用,乍一看也没什么用处,但是如果你调用info命令会是咋样的呢??? 你肯定会想到,我操,监控对不对???好吧,猜对了,命令格式如下:redis-cli -r <count> and -i <delay> command 其中-r 是repeat的次数,-i是delay的sencond的秒数,那接下来我演示一下啊,调用info命令10次,每次延迟1s,如下所示:[root@localhost Desktop]# redis-cli -h 192.168.1.216 -r 10 -i 1 INFO# Serverredis_version:3.2.4redis_git_sha1:00000000redis_git_dirty:0redis_build_id:fc9ad9a14d3a0fb5redis_mode:standaloneos:Linux 3.10.0-327.el7.x86_64 x86_64arch_bits:64multiplexing_api:epollgcc_version:4.8.5process_id:6171run_id:8d1d5cffbf81e31c6c6e0bd144186e9df9fea482tcp_port:6379uptime_in_seconds:3536932uptime_in_days:40hz:10lru_clock:5049094executable:/etc/redis/redis-serverconfig_file:/etc/redis/6379.conf# Clientsconnected_clients:7client_longest_output_list:0client_biggest_input_buf:0blocked_clients:0# Memoryused_memory:1295512used_memory_human:1.24Mused_memory_rss:10395648used_memory_rss_human:9.91Mused_memory_peak:35199336used_memory_peak_human:33.57Mtotal_system_memory:2099109888total_system_memory_human:1.95Gused_memory_lua:37888used_memory_lua_human:37.00Kmaxmemory:0maxmemory_human:0Bmaxmemory_policy:noevictionmem_fragmentation_ratio:8.02mem_allocator:jemalloc-4.0.3# Persistenceloading:0rdb_changes_since_last_save:0rdb_bgsave_in_progress:0rdb_last_save_time:1481443658rdb_last_bgsave_status:okrdb_last_bgsave_time_sec:0rdb_current_bgsave_time_sec:-1aof_enabled:0aof_rewrite_in_progress:0aof_rewrite_scheduled:0aof_last_rewrite_time_sec:-1aof_current_rewrite_time_sec:-1aof_last_bgrewrite_status:okaof_last_write_status:ok...可以看到,命令一下子就刷出来了很多,有点眼花缭乱,一般来说我只关注的是used_memory_human字段,也就仅仅需要知道当然redis占用了多少内存就完事了,所以这里我需要grep一下:[root@localhost Desktop]# redis-cli -h 192.168.1.216 -r 10 -i 1 INFO | grep used_memory_humanused_memory_human:1.24Mused_memory_human:1.24Mused_memory_human:1.24Mused_memory_human:1.24Mused_memory_human:1.24Mused_memory_human:1.24Mused_memory_human:1.24Mused_memory_human:1.24Mused_memory_human:1.24Mused_memory_human:1.24M[root@localhost Desktop]#可以清楚的看到,当前memory_human占用1.24M对吧。。。是不是有一种监控的效果呢? 四:--stat完整版监控   其实上面的这个监控还仅仅算是一个极简的版本,可能不能满足有些朋友的需求,比如你就看不到当前的redis中有多少的keys,有多少的clients,有多少被blocked,有多少requests等等信息,如果这些都有了,是不是有点像mongodb中的mongostats呢?哈哈,下面我就迫不及待的给大家来演示一下吧,非常的简单。。。[root@localhost Desktop]# redis-cli -h 192.168.1.216 --stat------- data ------ --------------------- load -------------------- - child -keys mem clients blocked requests connections27 1.24M 7 0 1198768 (+0) 220627 1.24M 7 0 1198769 (+1) 220627 1.24M 7 0 1198770 (+1) 220627 1.24M 7 0 1198771 (+1) 220627 1.24M 7 0 1198772 (+1) 220627 1.24M 7 0 1198773 (+1) 220627 1.24M 7 0 1198774 (+1) 220627 1.24M 7 0 1198775 (+1) 220627 1.24M 7 0 1198776 (+1) 220627 1.24M 7 0 1198777 (+1) 220627 1.24M 7 0 1198778 (+1) 220627 1.24M 7 0 1198779 (+1) 220627 1.24M 7 0 1198780 (+1) 220627 1.27M 7 0 1198782 (+2) 220627 1.24M 7 0 1198783 (+1) 220627 1.24M 7 0 1198784 (+1) 220627 1.24M 7 0 1198785 (+1) 2206看到没有,是不是非常的牛逼,一目了然。 好了,更多的好功能,等待大家去挖掘吧,希望本篇对大家有帮助~~~
redis大幅性能提升之使用管道PipeLine和批量Batch操作
       前段时间在做用户画像的时候,遇到了这样的一个问题,记录某一个商品的用户购买群,刚好这种需求就可以用到Redis中的Set,key作为productID,value就是具体的customerid集合,后续的话,我就可以通过productid来查看该customerid是否买了此商品,如果购买了,就可以有相关的关联推荐,当然这只是系统中的一个小业务条件,这时候我就可以用到SADD操作方法,代码如下:static void Main(string[] args){ConnectionMultiplexer redis = ConnectionMultiplexer.Connect("192.168.23.151:6379");var db = redis.GetDatabase();var productID = string.Format("productID_{0}", 1);for (int i = 0; i < 10; i++){var customerID = i;db.SetAdd(productID, customerID);}} 一:问题    但是上面的这段代码很明显存在一个大问题,Redis本身就是基于tcp的一个Request/Response protocol模式,不信的话,可以用wireshark监视一下:从图中可以看到,有很多次的192.168.23.1 => 192.168.23.151 之间的数据往返,从传输内容中大概也可以看到有一个叫做productid_xxx的前缀,那如果有百万次局域网这样的round trip,那这个延迟性可想而知,肯定达不到我们预想的高性能。 二:解决方案【Batch】     刚好基于我们现有的业务,我可以定时的将批量的productid和customerid进行分组整合,然后用batch的形式插入到某一个具体的product的set中去,接下来我可以把上面的代码改成类似下面这样:1 static void Main(string[] args)2 {3 ConnectionMultiplexer redis = ConnectionMultiplexer.Connect("192.168.23.151:6379");45 var db = redis.GetDatabase();67 var productID = string.Format("productID_{0}", 1);89 var list = new List<int>();101112 for (int i = 0; i < 10; i++)13 {14 list.Add(i);15 }1617 db.SetAdd(productID, list.Select(i => (RedisValue)i).ToArray());18 } 从截图中传输的request,response可以看到,这次我们一次性提交过去,极大的较少了在网络传输方面带来的尴尬性。。 三:再次提出问题product维度的画像我们可以解决了,但是我们还有一个customerid的维度,也就是说我需要维护一个customerid为key的set集合,其中value的值为该customerid的各种平均值,比如说“总交易次数”,“总交易金额”。。。等等这样的聚合信息,然后推送过来的是批量的customerid,也就是说你需要定时维护一小嘬set集合,在这种情况下某一个set的批量操作就搞不定了。。。原始代码如下:1 static void Main(string[] args)2 {3 ConnectionMultiplexer redis = ConnectionMultiplexer.Connect("192.168.23.151:6379");45 var db = redis.GetDatabase();678 //批量过来的数据: customeridlist, ordertotalprice,具体业务逻辑省略9 var orderTotalPrice = 100;1011 var customerIDList = new List<int>();1213 for (int i = 0; i < 10; i++)14 {15 customerIDList.Add(i);16 }1718 //foreach更新每个redis 的set集合19 foreach (var item in customerIDList)20 {21 var customerID = string.Format("customerid_{0}", item);2223 db.SetAdd(customerID, orderTotalPrice);24 }25 } 四:解决方案【PipeLine】    上面这种代码在生产上当然是行不通的,不过针对这种问题,redis早已经提出了相关的解决方案,那就是pipeline机制,原理还是一样,将命令集整合起来通过一条request请求一起送过去,由redis内部fake出一个client做批量执行操作,代码如下:1 static void Main(string[] args)2 {3 ConnectionMultiplexer redis = ConnectionMultiplexer.Connect("192.168.23.151:6379");45 var db = redis.GetDatabase();678 //批量过来的数据: customeridlist, ordertotalprice,具体业务逻辑省略9 var orderTotalPrice = 100;1011 var customerIDList = new List<int>();1213 for (int i = 0; i < 10; i++)14 {15 customerIDList.Add(i);16 }1718 var batch = db.CreateBatch();1920 foreach (var item in customerIDList)21 {22 var customerID = string.Format("customerid_{0}", item);2324 batch.SetAddAsync(customerID, orderTotalPrice);25 }2627 batch.Execute();28 } 然后,我们再看下面的wireshark截图,可以看到有很多的SADD这样的小命令,这就说明有很多命令是一起过去的,大大的提升了性能。 最后可以再看一下redis,数据也是有的,是不是很爽~~~192.168.23.151:6379> keys *1) "customerid_0"2) "customerid_9"3) "customerid_1"4) "customerid_3"5) "customerid_8"6) "customerid_2"7) "customerid_7"8) "customerid_5"9) "customerid_6"10) "customerid_4" 好了,先就说到这里了,希望本篇对你有帮助。 
在redis中使用lua脚本让你的灵活性提高5个逼格
在redis的官网上洋洋洒洒的大概提供了200多个命令,貌似看起来很多,但是这些都是别人预先给你定义好的,但你却不能按照自己的意图进行定制,所以是不是感觉自己还是有一种被束缚的感觉,有这个感觉就对了。。。一:Lua脚本      说来也巧,redis的大老板给了你解决这种问题的方法,那就是Lua脚本,而且redis的最新版本也支持Lua Script debug,这应该也是未来Redis的一个发展趋势,要想学好Redis,必会Lua Script。。。有趣的是,官网上还提供了一个视频教程教你如何进行Debug操作。。。 【https://redis.io/topics/ldb】 youtube上面的视频,要是被墙了,记得上VPN哦。。。淘宝上不知道有没有售卖这种同款的吸顶灯~~~ 二:使用Redis-Cli Lua Script 解决几个灵活性问题 1. Lua语法的问题    lua是一门编程语言,所以这个就已经超出了redis本身的范畴,如果大家想好好学习一下,可以看下http://www.lua.org/ 的官网,然后下载一下玩一玩。 比如这里我下载了一个windows版本的lua 编译器,具体语法上就不细说了。。有了这个主题,我们再进行下一个环节。 2. Eval的使用EVAL script numkeys key [key ...] arg [arg ...]   首先大家一定要知道eval的语法格式,其中:   <1> script:     你的lua脚本   <2> numkeys:  key的个数   <3> key:         redis中各种数据结构的替代符号   <4> arg:         你的自定义参数 ok,可能乍一看模板不是特别清楚,下面我可以用官网的小案例演示一下:eval "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}" 2 username age jack 20 上面这一串代码大概是什么意思呢? 第一个参数的字符串就是script,也就是lua脚本。2表示keys的个数,KEYS[1] 就是 username的占位符, KEYS[2]就是age的占位符,ARGV[1]就是jack的占位符,ARGV[2]就是20的占位符,,以此类推,,,所以最后的结果应该就是:{return username age jack 20} 是不是有点像C#中的占位符:{0}呢??? 下面我在Redis中给大家演示一下:[root@localhost Desktop]# redis-cli127.0.0.1:6379> eval "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}" 2 username age jack 201) "username"2) "age"3) "jack"4) "20"127.0.0.1:6379> 通常境况下,我们不要在redis-cli中直接写lua脚本,这样非常不方便编辑,通常情况下我们都是把lua script放到一个lua文件中,然后执行这个lua脚本,比如下面这样: 然后我们通过下面命令执行,这种方式和前面介绍的不一样,参数 --eval script  key1 key2 , arg1 age2 这种模式,key和value用一个逗号隔开就好了,最后我们也看到了,数据都出来了,对吧。[root@localhost Desktop]# redis-cli --eval /usr/redis/sbin/1.lua username age , jack 201) "username"2) "age"3) "jack"4) "20"[root@localhost Desktop]# 三:实战下面我可以构思几个小案例通过lua解决。 1. 通过lua脚本获取指定的key的List中的所有数据local key=KEYS[1]local list=redis.call("lrange",key,0,-1);return list; 这里面的redis.call就是用来执行redis中list的lrange命令,接下来我通过lpush给person塞入三条数据,如下:[root@localhost Desktop]# redis-cli127.0.0.1:6379> lpush person mary jack peter(integer) 3127.0.0.1:6379> 然后我们来执行这个lua脚本,效果如下图,是不是很牛逼的感觉??? 有了这个1+1的效果,就可以玩些更复杂的操作。比如: 2.根据外面传过来的IDList 做“集合去重”的lua脚本逻辑:local key=KEYS[1];local args=ARGVlocal i=0;local result={};for m,n in ipairs(args) dolocal ishit=redis.call("sismember",key,n);if(ishit) thentable.insert(result,1,n);endendreturn result; 2. 找到hash中age小于指定值的所有数据,lua脚本如下:local result={};local myperson=KEYS[1];local nums=ARGV[1];local myresult =redis.call("hkeys",myperson);for i,v in ipairs(myresult) dolocal hval= redis.call("hget",myperson,v);redis.log(redis.LOG_WARNING,hval);if(tonumber(hval)<tonumber(nums)) thentable.insert(result,1,v);endendreturn result; 大家可以试着看下这段脚本,然后按照这个逻辑自己玩一玩,很有意思的,还是那句话,学好redis,必会Lua。。。。 
看看redis中那些好玩的module (sql on redis, bf/cf on redis)
自从redis加入了module功能之后,redis的生态就很有意思了,每个领域的大佬都会以插件的形式给redis扩展一些新的功能,比如本篇说到的rediSQL,rebloom。 一:rediSQL  1. 背景        redis虽然是牛逼,但还是有很多人吐槽redis操作性太弱,比如你想要在redis上实现一个比较复杂的业务逻辑,可能对你来说是一个灾难,有些同学会说用redis的存储过程lua撒,但是lua不是每个程序员都会的,更何况那些数据分析师,但要是问sql会不会,基本上合格的程序员和分析师在这个上面都是没毛病的,真的要是让sql落在redis上,那真是如虎添翼,可能最早让sql落到redis上的,应该是spark sql 吧,让redis作为spark的rdd,但这里说到的是另外一个通过module实现的sql on redis。   2. 下载     源代码可以到 github:https://github.com/RedBeardLab/rediSQL  ,下载地址是:https://github.com/RedBeardLab/rediSQL/releases    直接下载这个编译好的文件,拿来就用就好了。 3. 加载    这个简单,先把rediSQL_0.7.1.so 导入到centos中,然后只需使用module load  rediSQL_0.7.1.so 返回ok即可。1 [root@localhost redis]# ls2 00-RELEASENOTES COPYING Makefile README.md redis.conf runtest src3 appendonly.aof deps MANIFESTO redis-check-aof rediSQL_0.7.1.so runtest-cluster tests4 BUGS dump.rdb module redis-check-rdb redis-server runtest-sentinel utils5 CONTRIBUTING INSTALL mydata redis-cli redis-trib.rb sentinel.conf [root@localhost redis]# ./redis-cli127.0.0.1:6379> module load /data/redis/rediSQL_0.7.1.soOK 4. 简单使用    既然要让sql落到redis中,那就先得建库建表啦,这里database:Datamip, table:customer,然后做了一个简单的查询,如下:127.0.0.1:6379> REDISQL.CREATE_DB DatamipOK127.0.0.1:6379> REDISQL.EXEC Datamip "CREATE TABLE customer(id int, username varchar(10));"1) DONE2) (integer) 0127.0.0.1:6379> REDISQL.EXEC Datamip "INSERT INTO customer VALUES(1, 'jack');"1) DONE2) (integer) 1127.0.0.1:6379> REDISQL.EXEC Datamip "INSERT INTO customer VALUES(2, 'mary');"1) DONE2) (integer) 1127.0.0.1:6379> REDISQL.EXEC Datamip "SELECT * FROM customer WHERE id=2"1) 1) (integer) 22) "mary"127.0.0.1:6379>    是不是很爽的感觉,不过作者也是要吃饭的,所以企业版还是要收点压箱底的钱。 二: rebloom1. 背景这个module也很有意思,它给redis新增了两种过滤器,一个叫做bloom filter,一个叫做 cuckoo filter, bloomfilter 估计大家都知道,用极小的错误率换取原有的HashSet的1/8 -1/4的空间利用率,具体场景大家看着用吧,cuckoofilter 翻译过来就是布谷鸟过滤性,可能作者家就是养鸟的,不然怎么那么多鸟呢,大家只要理解cuckoofilter比bloomfilter更省空间,更低的错误率,而且还是支持删除。具体的大家可以看论文:http://www.cs.cmu.edu/~binfan/papers/conext14_cuckoofilter.pdf 。 2. 下载    github地址:https://github.com/RedisLabsModules/rebloom   然后找到release模式,下载完之后需要自己make一下。[root@localhost module]# lsv1.1.0.tar.gz[root@localhost module]# tar -xzvf v1.1.0.tar.gzrebloom-1.1.0/rebloom-1.1.0/.circleci/rebloom-1.1.0/.circleci/config.ymlrebloom-1.1.0/.clang-formatrebloom-1.1.0/.gitignorerebloom-1.1.0/Dockerfilerebloom-1.1.0/LICENSErebloom-1.1.0/Makefilerebloom-1.1.0/README.mdrebloom-1.1.0/contrib/rebloom-1.1.0/contrib/MurmurHash2.crebloom-1.1.0/contrib/bloom.crebloom-1.1.0/contrib/bloom.hrebloom-1.1.0/contrib/murmurhash2.hrebloom-1.1.0/docs/rebloom-1.1.0/docs/Bloom_Commands.mdrebloom-1.1.0/docs/CNAMErebloom-1.1.0/docs/Cuckoo_Commands.mdrebloom-1.1.0/docs/Java_Client.mdrebloom-1.1.0/docs/Quick_Start.mdrebloom-1.1.0/docs/_config.ymlrebloom-1.1.0/docs/index.mdrebloom-1.1.0/mkdocs.ymlrebloom-1.1.0/ramp.ymlrebloom-1.1.0/src/rebloom-1.1.0/src/cf.crebloom-1.1.0/src/cf.hrebloom-1.1.0/src/cuckoo.crebloom-1.1.0/src/cuckoo.hrebloom-1.1.0/src/print_version.crebloom-1.1.0/src/rebloom.crebloom-1.1.0/src/redismodule.hrebloom-1.1.0/src/sb.crebloom-1.1.0/src/sb.hrebloom-1.1.0/src/version.hrebloom-1.1.0/tests/rebloom-1.1.0/tests/Makefilerebloom-1.1.0/tests/cuckoo.pyrebloom-1.1.0/tests/pytests.pyrebloom-1.1.0/tests/test-basic.crebloom-1.1.0/tests/test-cuckoo.crebloom-1.1.0/tests/test-perf.crebloom-1.1.0/tests/test.h[root@localhost module]# lsrebloom-1.1.0 v1.1.0.tar.gz[root@localhost module]# cd rebloom-1.1.0[root@localhost rebloom-1.1.0]# lscontrib Dockerfile docs LICENSE Makefile mkdocs.yml ramp.yml README.md src tests[root@localhost rebloom-1.1.0]# makecc -Wall -Wno-unused-function -g -ggdb -O2 -fPIC -std=gnu99 -D_GNU_SOURCE -I/data/redis/module/rebloom-1.1.0 -I/data/redis/module/rebloom-1.1.0/contrib -c -o /data/redis/module/rebloom-1.1.0/src/rebloom.o /data/redis/module/rebloom-1.1.0/src/rebloom.ccc -Wall -Wno-unused-function -g -ggdb -O2 -fPIC -std=gnu99 -D_GNU_SOURCE -I/data/redis/module/rebloom-1.1.0 -I/data/redis/module/rebloom-1.1.0/contrib -c -o /data/redis/module/rebloom-1.1.0/contrib/MurmurHash2.o /data/redis/module/rebloom-1.1.0/contrib/MurmurHash2.ccc -Wall -Wno-unused-function -g -ggdb -O2 -fPIC -std=gnu99 -D_GNU_SOURCE -I/data/redis/module/rebloom-1.1.0 -I/data/redis/module/rebloom-1.1.0/contrib -c -o /data/redis/module/rebloom-1.1.0/src/sb.o /data/redis/module/rebloom-1.1.0/src/sb.ccc -Wall -Wno-unused-function -g -ggdb -O2 -fPIC -std=gnu99 -D_GNU_SOURCE -I/data/redis/module/rebloom-1.1.0 -I/data/redis/module/rebloom-1.1.0/contrib -c -o /data/redis/module/rebloom-1.1.0/src/cf.o /data/redis/module/rebloom-1.1.0/src/cf.cIn file included from /data/redis/module/rebloom-1.1.0/src/cf.c:6:0:/data/redis/module/rebloom-1.1.0/src/cuckoo.c: In function ‘CuckooFilter_Count’:/data/redis/module/rebloom-1.1.0/src/cuckoo.c:157:9: warning: passing argument 1 of ‘filterCount’ from incompatible pointer type [enabled by default]ret += filterCount(filter->filters[ii], &params);^/data/redis/module/rebloom-1.1.0/src/cuckoo.c:139:15: note:
redis学习--的持久化数据备份RDB和AOF
接上一篇:安装window下的redis,redis可视化管理工具(Redis Desktop Manager)安装,基础使用,实例化项目 一、dump.rdb文件是怎么生成的二、什么是redis持久化三、redis的RDB是什么?四、redis配置文件redis.config相关配置五、redis优点六、redis缺点 redis比memcache作为缓存数据库强大的地方:(1)支持数据类型比较多,(2)redis持久化功能。 一、dump.rdb文件是怎么生成的在redis服务挂掉的时候,根据redis的配置文件,会自动备份数据到本地。dump.rdb是由redis服务器自动生成的。默认情况下,每隔一段时间redis服务器程序会自动对数据库做一次遍历,把内存快照写在一个叫做“dump.rdb”文件里,这种持久化机制叫做SNAPSHOT。有了SNAPSHOT后,如果服务器宕机,重新启动redis服务器程序时redis会自动加载dump.rdb,将数据库恢复到上一次SNAPSHOT的状态。 至于多久一次做一次SNAPSHOT,SNAPSHOT文件的路径和文件名,你可以在redis的config文件中指定。 二、什么是redis的持久化Redis提供了不同级别的持久化方式:(1)RDB持久化方式:能够在指定的时间间隔能对你的数据进行快照存储。(2)AOF持久化方式:每次对服务器写的操作,当服务器重启的时候回重新执行这些命令来恢复原始的数据,AOF命令以redis协议追加保存每次写的操作到文件末尾。redis还能对AOF文件进行后台重写,使得AOF文件的体积不至于过大。(3)如果你只希望你的数据在服务器运行的时候存在,你可以不使用任何持久化方式。(4)你也可以同时开启这两种持久化方式。当redis服务重启的时候回优先载入AOF文件来恢复原始的数据,因为在通常情况下AOF文件保存的数据集要比RDB文件保存的数据集要完整 三、Redis的RDB是什么RDB在指定的时间间隔内将内存中的数据集快照写入磁盘,也就是SNAPSHOT快照。它恢复时是将快照文件直接写入到内存里,redis会单独创建(fork)一个子进程进行持久化吗,会先将数据写入到一个临时文件中,持久化过程都结束了,在用这个临时文件替换上从持久化好的文件。整个过程中,主进程是不进行任何IO操作的,这就确保了极高的性能。如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是敏感,那RDB方式要比AOF方式更加高效。、redis的缺点是最后一次持久化后的数据可能丢失。 四、redis配置文件redis.config相关配置(一)RDB快照方式持久化磁盘先看redis.window.config文件################################ SNAPSHOTTING ################################## Save the DB on disk:## save <seconds> <changes>## Will save the DB if both the given number of seconds and the given# number of write operations against the DB occurred.## In the example below the behaviour will be to save:# after 900 sec (15 min) if at least 1 key changed# after 300 sec (5 min) if at least 10 keys changed# after 60 sec if at least 10000 keys changed## Note: you can disable saving completely by commenting out all "save" lines.## It is also possible to remove all the previously configured save# points by adding a save directive with a single empty string argument# like in the following example:## save ""save 900 1save 300 10save 60 10000# By default Redis will stop accepting writes if RDB snapshots are enabled# (at least one save point) and the latest background save failed.# This will make the user aware (in a hard way) that data is not persisting# on disk properly, otherwise chances are that no one will notice and some# disaster will happen.## If the background saving process will start working again Redis will# automatically allow writes again.## However if you have setup your proper monitoring of the Redis server# and persistence, you may want to disable this feature so that Redis will# continue to work as usual even if there are problems with disk,# permissions, and so forth.stop-writes-on-bgsave-error yes# Compress string objects using LZF when dump .rdb databases?# For default that's set to 'yes' as it's almost always a win.# If you want to save some CPU in the saving child set it to 'no' but# the dataset will likely be bigger if you have compressible values or keys.rdbcompression yes# Since version 5 of RDB a CRC64 checksum is placed at the end of the file.# This makes the format more resistant to corruption but there is a performance# hit to pay (around 10%) when saving and loading RDB files, so you can disable it# for maximum performances.## RDB files created with checksum disabled have a checksum of zero that will# tell the loading code to skip the check.rdbchecksum yes# The filename where to dump the DBdbfilename dump.rdb# The working directory.## The DB will be written inside this directory, with the filename specified# above using the 'dbfilename' configuration directive.## The Append Only File will also be created inside this directory.## Note that you must specify a directory here, not a file name.dir ./ 4.1如何触发RDB快照配置文件中默认的快照配置save 900 1save 300 10save 60 10000上面的意思就是说:(1)如果至少一个键改变,会在900秒(15分钟)之后执行save操作(2)如果至少改变10个键,则在300秒(5分钟)之后执行save操作(3)如果至少改变10000个键,则在60秒(1分钟)之后执行save操作命令save:只管保存,其他不管命令bgsave:redis会在后台异步进行快照操作,快照的同时还可以响应客户端请求。 4.2默认的RDB方式保存的是dump.rdb文件,恢复识别也是dump.rdb 4.3stop-writes-on-bgsave-error yes如果后台保存到磁盘发生错误,将停止写操作,使用LZF压缩rdb文件,这会耗CPU, 但是可以减少磁盘占用。 4.4rdbcompression yes保存rdb和加载rdb文件的时候校验,可以防止错误,但是要付出约10%的性能,可以关闭,提高性能。 4.5rdbchecksum yes导出的rdb文件名 4.6dbfilename dump.rdb设置工作目录,rdb文件会写到该目录,append only file也会存储在该目录下 4.7dir ./redis会自动快照保存到磁盘或者调用bgsave,是后台进程完成的,其他客户端任然可以读写redis服务,后台保存快照到磁盘会占用大量的内存。 (二)AOF(append-only file)方式持久化另外一种方式为递增的方式,将会引起数据变化的操作,持久化到文件中,重启redis的时候,通过操作命令,恢复数据。每次执行写操作命令之后,都会将数据写到server.aofbuf中。## More details please check the following article:# http://antirez.com/post/redis-persistence-demystified.html## If unsure, use "everysec".# appendfsync alwaysappendfsync everysec# appendfsync no# When the AOF fsync policy is set to always or everysec, and a background# saving process (a background save or AOF log background rewriting) is# performing a lot of I/O against the disk, in some Linux configurations# Redis may block too long on the fsync() call. Note that there is no fix for# this currently, as even performing fsync in a different thread will block# our synchronous write(2) call.当配置为always的时候,每次server.aofbuf中的数据写入到文件之后,才会返回到客户端,这样可以保证数据不丢失,但是频繁的IO操作,会降低性能。everysec每秒写一次,这可能会丢失一
[Redis]Redis 概述及基本使用规范.
1 nosql的简介1.1 nosql简介随着互联网Web2.0网站的兴起,传统的关系数据库在应付Web2.0网站,特别是超大规模和高并发的SNS类型的Web2.0纯动态网站已经显得力不从心,暴露了很多难以克服的问题,如:1.1.1 对数据库高并发读写的需求网站要根据用户个性化信息来实时生成动态页面和提供动态信息,所以基本上无法使用动态页面静态化技术,因此数据库并发负载非常高,往往要达到每秒上万次读写请求。关系数据库应付上万次SQL查询还勉强顶得住,但是应付上千万次SQL写数据请求,硬盘IO就已经无法承受了。1.1.2 对海量数据的高效率存储和访问的需求对于大型的SNS网站,每天用户产生海量的用户动态,以国外的Friendfeed为例,一个月就达到了2.5亿条用户动态,对于关系数据库来说,在一张2.5亿条记录的表里面进行SQL查询,效率是极其低下乃至不可忍受的。1.1.3 对数据库的高可扩展性和高可用性的需求在基于Web的架构当中,数据库是最难进行横向扩展的,当一个应用系统的用户量和访问量与日俱增的时候,你的数据库却没有办法像Web服务器和应用服务器那样简单的通过添加更多的硬件和服务节点来扩展性能和负载能力。对于很多需要提供7*24小时不间断服务的网站来说,对数据库系统进行升级和扩展是非常痛苦的事情,往往需要停机维护和数据迁移,为什么数据库不能通过不断的添加服务器节点来实现扩展呢?在上面提到的“三高”的需求面前,关系数据库遇到了难以克服的障碍,而对于Web2.0网站来说,关系数据库的很多主要特性却往往无用武之地,例如:(1)数据库事务一致性需求很多Web实时系统并不要求严格的数据库事务,对读一致性的要求很低,有些场合对写一致性要求也不高。因此数据库事务管理成了数据库高负载下一个沉重的负担。(2)数据库的写实时性和读实时性需求对关系数据库来说,插入一条数据之后立刻查询,是肯定可以读出来这条数据的。并不要求这么高的实时性。(3)对复杂的SQL查询,特别是多表关联查询的需求任何大数据量的Web系统,都非常忌讳多个大表的关联查询,以及复杂的数据分析类型的复杂SQL报表查询,特别是SNS类型的网站,从需求以及产品设计角度,就避免了这种情况的产生。往往更多的只是单表的主键查询,以及单表的简单条件分页查询,SQL的功能被极大的弱化了。 因此,关系数据库在这些越来越多的应用场景下显得不那么合适了,为了解决这类问题的非关系数据库应运而生。NoSQL 是非关系型数据存储的广义定义。它打破了长久以来关系型数据库与ACID理论大一统的局面。NoSQL 数据存储不需要固定的表结构(例如以键值对存储,它的结构不固定,每一个元组可以有不一样的字段,每个元组可以根据需要增加一些自己的键值对,这样就不会局限于固定的结构,可以减少一些时间和空间的开销),通常也不存在连接操作。1.2 NoSQL无与伦比的特点在大数据存取上具备关系型数据库无法比拟的性能优势,例如:1.2.1 易扩展NoSQL数据库种类繁多,但是一个共同的特点都是去掉关系数据库的关系型特性。数据之间无关系,这样就非常容易扩展。也无形之间,在架构的层面上带来了可扩展的能力。1.2.2 大数据量,高性能NoSQL数据库都具有非常高的读写性能,尤其在大数据量下,同样表现优秀。这得益于它的无关系性,数据库的结构简单。1.2.3 灵活的数据模型NoSQL无需事先为要存储的数据建立字段,随时可以存储自定义的数据格式。而在关系数据库里,增删字段是一件非常麻烦的事情。如果是非常大数据量的表,增加字段简直就是一个噩梦。这点在大数据量的Web2.0时代尤其明显。1.2.4 高可用NoSQL在不太影响性能的情况,就可以方便的实现高可用的架构。比如Cassandra,HBase模型,通过复制模型也能实现高可用。 综上所述,NoSQL的非关系特性使其成为了后Web2.0时代的宠儿,助力大型Web2.0网站的再次起飞,是一项全新的数据库革命性运动。1.3 redis简介随着应用对高性能需求的增加,NoSQL逐渐在各大名企的系统架构中生根发芽。时至今日,涌现出的NoSQL产品已经有很多种了,例如Membase、MongoDB、Apache Cassandra、CouchDB等。不过,在国内外互联网巨头例如社交巨头新浪微博、传媒巨头Viacom及图片分享领域佼佼者Pinterest等名企都不约而同地采用了Redis作为其NoSQL数据库的选择,到底Redis是何方神圣呢?能让如此多的名企为它而痴狂。按照官方的说法,Redis是一个开源的,使用C语言编写,面向“键/值”(Key/Value)对类型数据的分布式NoSQL数据库系统,特点是高性能,持久存储,适应高并发的应用场景。因此,可以说Redis纯粹为应用而产生,它是一个高性能的key-value数据库,并且还提供了多种语言的API(包括我们的大C#)。那么,也许我们会问:到底性能如何呢?以下是官方的bench-mark数据:测试完成了50个并发执行100000个请求。设置和获取的值是一个256字节字符串。Linux box是运行Linux 2.6,这是X3320 Xeon 2.5 ghz。文本执行使用loopback接口(127.0.0.1)。结果:读的速度是110000次/s,写的速度是81000次/s 。(当然不同的服务器配置性能也有所不同)。和Memcached类似,Redis支持存储的value类型相对更多,包括string(字符串)、list(链表)、set(集合)、zset(sorted set --有序集合)和hash(哈希类型)。这些数据类型都支持push/pop、add/remove及取交集并集和差集及更丰富的操作,而且这些操作都是原子性的。在此基础上,Redis支持各种不同方式的排序。与Memcached一样,为了保证效率,数据都是缓存在内存中。区别的是Redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步(数据可以从主服务器向任意数量的从服务器上同步,从服务器可以是关联其他从服务器的主服务器。)。因此,Redis的出现,很大程度补偿了Memcached这类key/value存储的不足,在部分场合可以对关系数据库起到很好的补充作用。1.4 redis是什么redis是一个开源的、使用C语言编写的、支持网络交互的、可基于内存也可持久化的Key-Value数据库。  redis的作者,Salvatore Sanfilippo,来自意大利的西西里岛,现在居住在卡塔尼亚。目前供职于Pivotal公司。他使用的网名是antirez,如果你有兴趣,可以去他的博客逛逛,地址是antirez.com,他的github地址是http://github.com/antirez。2 CentOS下安装redis步骤:1、 将Windows下下载的压缩文件上传到Linux下。我们可以通过filezilla等FTP软件上传,这里通过secureCRT进行上传。步骤如下:l alt + pl put c:/redis-2.6.16.tar.gzl 完成上传2、 解压文件l tar –zxvf redis-2.6.16.tar.gz3、 编译redisl 进入解压文件夹,cd redis-2.6.16l 执行make[编译,将.c文件编译为.o文件]4、 安装l make PREFIX=/usr/local/redis installl 安装完后,在/usr/local/redis/bin下有几个可执行文件redis-benchmark    ----性能测试工具redis-check-aof     ----检查修复aof文件 appendonly fileredis-check-dump  ----检查快照持久化文件redis-cli           ----命令行客户端redis-server        ----redis服务器启动命令5、 copy文件redis启动需要一个配置文件:cp redis.conf /usr/local/redis6、 启动redis  /usr/local/redis/bin…   查看该进程: ps -ef | grep redis  结束该进程:kill -9 pid(进程号)命令:bin/redis-server redis.conf,这种方式又称为前台启动。后台启动可修redis.conf文件,daemonize no-àyes即可。 3 redis的数据结构redis是一种高级的key-value的存储系统,其中value支持五种数据类型。1、 字符串(String)2、 字符串列表(lists)3、 字符串集合(sets)4、 有序字符串集合(sorted sets)5、 哈希(hashs)而关于key的定义呢,需要大家注意的几点:1、 key不要太长,最好不要操作1024个字节,这不仅会消耗内存还会降低查找效率2、 key不要太短,如果太短会降低key的可读性3、 在项目中,key最好有一个统一的命名规范3.1 存储string3.1.1 概述字符串类型是Redis中最为基础的数据存储类型,它在Redis中是二进制安全的,这便意味着该类型可以接受任何格式的数据,如JPEG图像数据或Json对象描述信息等。在Redis中字符串类型的Value最多可以容纳的数据长度是512M。3.1.2 常用命令l set key value:设定key持有指定的字符串value,如果该key存在则进行覆盖操作。总是返回”OK”l get key:获取key的value。如果与该key关联的value不是String类型,redis将返回错误信息,因为get命令只能用于获取String value;如果该key不存在,返回null。l getset key value:先获取该key的值,然后在设置该key的值。l incr key:将指定的key的value原子性的递增1.如果该key不存在,其初始值为0,在incr之后其值为1。如果value的值不能转成整型,如hello,该操作将执行失败并返回相应的错误信息。l decr key:将指定的key的value原子性的递减1.如果该key不存在,其初始值为0,在incr之后其值为-1。如果value的值不能转成整型,如hello,该操作将执行失败并返回相应的错误信息。l incrby key increment:将指定的key的value原子性增加increment,如果该key不存在,器初始值为0,在incrby之后,该值为increment。如果该值不能转成整型,如hello则失败并返回错误信息l decrby key decrement:将指定的key的value原子性减少decrement,如果该key不存在,器初始值为0,在decrby之后,该值为decrement。如果该值不能转成整型,如hello则失败并返回错误信息l append key value:如果该key存在,则在原有的value后追加该值;如果该key不存在,则重新创建一个key/value3.2 存储list3.2.1 概述在Redis中,List类型是按照插入顺序排序的字符串链表。和数据结构中的普通链表一样,我们可以在其头部(left)和尾部(right)添加新的元素。在插入时,如果该键并不存在,Redis将为该键创建一个新的链表。与此相反,如果链表中所有的元素均被移除,那么该键也将会被从数据库中删除。List中可以包含的最大元素数量是4294967295。    从元素插入和删除的效率视角来看,如果我们是在链表的两头插入或删除元素,这将会是非常高效的操作,即使链表中已经存储了百万条记录,该操作也可以在常量时间内完成。然而需要说明的是,如果元素插入或删除操作是作用于链表中间,那将会是非常低效的。相信对于有良好数据结构基础的开发者而言,这一点并不难理解。1、 ArrayList使用数组方式存储数据,所以根据索引查询数据速度快,而新增或者删除元素时需要设计到位移操作,所以比较慢。2、 LinkedList使用双向链接方式存储数据,每个元素都记录前后元素的指针,所以
Redis的介绍及使用实例.
本文就来讲一下Redis安装的方法和Redis生成主键的优点以及和其他几种方式生成主键的对比. 1,Redis安装首先将Redis的tar包拷贝到Linux下的根目录然后解压到redis文件夹下:(先使用mkdir创建redis文件夹)接下来就是解压tar包到redis目录下:解压后的目录结构:编译: 使用Make命令安装:安装好之后的目录: 6379 下的目录结构:(这个rdb文件时: redis database, 暂时不用管它, 重启后自动生成的)bin下的目录结构:配置后台运行:(将redis-3.0.0目录下的redis.conf文件拷贝到6379目录下, 使用cp命令)编辑redis.conf文件(使用vim 命令编辑,修改daemonize为yes, 意思就是支持后台运行)启动redis服务:(启动及停止命令) 客服端连接服务器:(如果是远程连接: ./bin/redis-cli -h 192.168.200.128 -p 6379)命令行演示:这样一个redis就启动完成了. 2, 使用Redis生成主键的优点及与其他生成主键方式的对比Redis生成ID当使用数据库来生成ID性能不够要求的时候,我们可以尝试使用Redis来生成ID。这主要依赖于Redis是单线程的,所以也可以用生成全局唯一的ID。可以用Redis的原子操作 INCR和INCRBY来实现。可以使用Redis集群来获取更高的吞吐量。假如一个集群中有5台Redis。可以初始化每台Redis的值分别是1,2,3,4,5,然后步长都是5。各个Redis生成的ID为:A:1,6,11,16,21B:2,7,12,17,22C:3,8,13,18,23D:4,9,14,19,24E:5,10,15,20,25这个,随便负载到哪个机确定好,未来很难做修改。但是3-5台服务器基本能够满足器上,都可以获得不同的ID。但是步长和初始值一定需要事先需要了。使用Redis集群也可以方式单点故障的问题。另外,比较适合使用Redis来生成每天从0开始的流水号。比如订单号=日期+当日自增长号。可以每天在Redis中生成一个Key,使用INCR进行累加。优点:1)不依赖于数据库,灵活方便,且性能优于数据库。2)数字ID天然排序,对分页或者需要排序的结果很有帮助。缺点:1)如果系统中没有Redis,还需要引入新的组件,增加系统复杂度。2)需要编码和配置的工作量比较大。用INT做主键的优点:    1、需要很小的数据存储空间,仅仅需要4 byte 。    2、insert和update操作时使用INT的性能比GUID好,所以使用int将会提高应用程序的性能。    3、index和Join 操作,int的性能最好。    4、容易记忆。    5、支持通过函数获取最新的值,如:Scope_Indentity() 。使用INT做主键的缺点    1、如果经常有合并表的操作,就可能会出现主键重复的情况。    2、使用INT数据范围有限制。如果存在大量的数据,可能会超出INT的取值范围。    3、很难处理分布式存储的数据表。使用GUID做主键的优点:    1、它是独一无二的。    2、出现重复的机会少。    3、适合大量数据中的插入和更新操作。    4、跨服务器数据合并非常方便。使用GUID做主键的缺点:    1、存储空间大(16 byte),因此它将会占用更多的磁盘大小。    2、很难记忆。join操作性能比int要低。    3、没有内置的函数获取最新产生的guid主键。    4、GUID做主键将会添加到表上的所以其他索引中,因此会降低性能。3. 代码中演示使用RedisJava接口测试Redis:Spring和Redis整合:Service层:(ProductServiceIml.java)  @Autowired private Jedis jedis;2 public void insertProduct(Product product){3 //商品设置4 //ID自增长的方式不好, 在这里使用redis生成id5 Long id = jedis.incr("pno");6 product.setId(id);78 //设置默认下架9 product.setIsShow(false);10 //默认不删除11 product.setIsDel(false);12 //时间13 product.setCreateTime(new Date());1415 //保存商品, 在mapper.xml中写的是返回自增长的主键id, 然后进行级联保存sku表16 //但是product如果已经设置了id 就不会再返回主键id了.17 productDao.insertSelective(product);1819 //库存 多个20 for (String color : product.getColors().split(",")) {21 //尺码22 for(String size : product.getSizes().split(",")){23 Sku sku = new Sku();24 //商品ID 这个在mapper.xml中设置返回主键id25 sku.setProductId(product.getId());26 //颜色27 sku.setColorId(Long.parseLong(color));28 //市场价29 sku.setMarketPrice(0f);30 //售价31 sku.setPrice(0f);32 //运费33 sku.setDeliveFee(10f);34 //购买限制35 sku.setUpperLimit(188);36 //尺码37 sku.setSize(size);38 //时间39 sku.setCreateTime(new Date());40 //库存41 sku.setStock(0);42 skuDao.insertSelective(sku);43 }44 }45 }看到这个最上面使用了Jdis去调用Redis服务, 然后使用incr对pno(在redis中可以对pno设置值)加1操作. 之前使用的都是自增长ID, 在mapper.xml中insert完成之后自动返回主键id到product中, 在级联保存的时候可以直接使用product.getId(). 下面就来看一下redis与spring整合的配置.redis.xml配置文件:1 <beans xmlns="http://www.springframework.org/schema/beans"2 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc"3 xmlns:context="http://www.springframework.org/schema/context"4 xmlns:aop="http://www.springframework.org/schema/aop"5 xmlns:tx="http://www.springframework.org/schema/tx"6 xmlns:task="http://www.springframework.org/schema/task"7 xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"8 xsi:schemaLocation="http://www.springframework.org/schema/beans9 http://www.springframework.org/schema/beans/spring-beans-4.0.xsd10 http://www.springframework.org/schema/mvc11 http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd12 http://www.springframework.org/schema/context13 http://www.springframework.org/schema/context/spring-context-4.0.xsd14 http://www.springframework.org/schema/aop15 http://www.springframework.org/schema/aop/spring-aop-4.0.xsd16 http://www.springframework.org/schema/tx17 http://www.springframework.org/schema/tx/spring-tx-4.0.xsd18 http://www.springframework.org/schema/task19 http://www.springframework.org/schema/task/spring-task-4.0.xsd20 http://code.alibabatech.com/schema/dubbo21 http://code.alibabatech.com/schema/dubbo/dubbo.xsd">2223 <!-- 整合Redis-->24 <bean id="jdis" class="redis.clients.jedis.Jedis">25 <constructor-arg value="192.168.200.128" index="0" type="java.lang.String"/>26 <constructor-arg value="6379" index="1"/>27 </bean>2829 </beans>View Code关于Redis的使用暂时就这么多, 下次还会继续分享更多的内容. 
Solr的原理及在项目中的使用实例.
前面已经讲过 如果安装及配置Solr服务器了, 那么现在我们就来正式在代码中使用Solr.1,这里Solr主要是怎么使用的呢? 当我们在前台页面搜索商品名称关键词时, 我们这时是在Solr库中去查找相应的商品信息, 然后将搜索关键词高亮.2,那么Solr库中的商品信息又是如何添加的呢? 当我们在给商品上架的时候, 将商品信息update 到mysql数据库中的bbs_product表中, 然后同样的将相应的信息 添加到Solr库中.接下来就看代码的具体实现吧:  一, 商品上架我们在这里点击上架按钮: list.jsp:1 <div style="margin-top:15px;"><input class="del-button" type="button" value="删除" onclick="optDelete();"/><input class="add" type="button" value="上架" onclick="isShow('${name}', '${brandId }', '${isShow }' ,'${pagination.pageNo }')"/><input class="del-button" type="button" value="下架" onclick="isHide();"/></div>点击上架触发isShow事件:1 <script type="text/javascript">2 //上架3 function isShow(name,brandId,isShow,pageNo){4 //请至少选择一个5 var size = $("input[name='ids']:checked").size();6 if(size == 0){7 alert("请至少选择一个");8 return;9 }10 //你确定上架吗11 if(!confirm("你确定上架吗")){12 return;13 }14 //提交 Form表单15 $("#jvForm").attr("action","/product/isShow.do?name="+ name +"&brandId="+brandId+"&isShow="+isShow+"&pageNo="+pageNo);16 $("#jvForm").attr("method","post");17 $("#jvForm").submit();1819 }20 </script>接着到Controller层:ProductController.java:1 //添加页面2 @RequestMapping("/isShow.do")3 public String isShow(Long[] ids, Model model){4 productService.isShow(ids);5 return "forward:/product/list.do";6 }接着看Service层:ProdcutServiceImpl.java:1 //上架@Autowiredprivate SolrServer solrServer;2 public void isShow(Long[] ids){3 Product product = new Product();4 product.setIsShow(true);5 for (Long id : ids) {6 //上下架状态7 product.setId(id);8 productDao.updateByPrimaryKeySelective(product);910 //TODO 保存商品信息到Solr服务器11 SolrInputDocument doc = new SolrInputDocument();12 //ID13 doc.setField("id", id);14 //名称15 Product p = productDao.selectByPrimaryKey(id);16 doc.setField("name_ik", p.getName());17 //图片URL18 doc.setField("url", p.getImgUrls()[0]);19 //品牌 ID20 doc.setField("brandId", p.getBrandId());21 //价格 sql查询语句: select price from bbs_sku where product_id = ? order by price asc limit 122 SkuQuery skuQuery = new SkuQuery();23 skuQuery.createCriteria().andProductIdEqualTo(id);24 skuQuery.setOrderByClause("price asc");25 skuQuery.setPageNo(1);26 skuQuery.setPageSize(1);27 List<Sku> skus = skuDao.selectByExample(skuQuery);28 doc.setField("price", skus.get(0).getPrice());29 //...时间等 剩下的省略3031 try {32 solrServer.add(doc);33 solrServer.commit();34 } catch (Exception e) {35 // TODO Auto-generated catch block36 e.printStackTrace();37 }38 //TODO 静态化39 }40 }这里使用SolrInputDocument 来保存商品信息, 其中doc.setField("name_ik", p.getName());的name_ik 是我们在solr 配置文件配置的IK 分词器的字段, doc.setField("url", p.getImgUrls()[0]); 这里我们也只是取第一张图片的url用来展示.这里我们还用到了skuQuery, 因为一个商品中不同的颜色不同的尺码都可能有不同的价格, 我们在这里 是取到同一个productId下价格最小的来给显示~然后再就是将我们已经设置好的SolrInputDocument 通过SolrServer 来提交到Solr服务器. SolrServer是已经在spring中注册好了的, 在这里直接注入即可使用.spring来管理Solr:到了这里上架的功能就做好了, 这也是给后面Solr查询做好铺垫.二, 前台使用Solr查询到了这里就开始查看前台页面了, 前台页面是扒的网上的, 具体业务逻辑是自己修改的, 页面如下:这里需要特殊说明一下, 我们配置的全局拦截器变成了: / , 而且过滤掉静态资源, 配置如下:首先是babasport-portal project下的web.xml文件:1 <?xml version="1.0" encoding="UTF-8"?>2 <web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"4 xsi:schemaLocation="http://java.sun.com/xml/ns/javaee5 http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">67 <!-- Post过滤器 -->8 <filter>9 <filter-name>encoding</filter-name>10 <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>11 <init-param>12 <param-name>encoding</param-name>13 <param-value>UTF-8</param-value>14 </init-param>15 </filter>1617 <filter-mapping>18 <filter-name>encoding</filter-name>19 <url-pattern>/</url-pattern>20 </filter-mapping>2122 <!-- 前端控制器 -->23 <servlet>24 <servlet-name>portal</servlet-name>25 <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>26 <init-param>27 <param-name>contextConfigLocation</param-name>28 <!-- 默认读取的是 WEB-I
Redis系列二:reids介绍
一、什么是redis、redis有哪些特性、redis有哪些应用场景、redis的版本1. 什么是redisredis是一种基于键值对(key-value)数据库,其中value可以为string、hash、list、set、zset等多种数据结构,可以满足很多应用场景。还提供了键过期,发布订阅,事务,流水线,等附加功能,流水线: Redis 的流水线功能允许客户端一次将多个命令请求发送给服务器, 并将被执行的多个命令请求的结果在一个命令回复中全部返回给客户端, 使用这个功能可以有效地减少客户端在执行多个命令时需要与服务器进行通信的次数。2. redis有哪些特性1〉速度快,数据放在内存中,官方给出的读写性能10万/S,与机器性能也有关a. 数据放内存中是速度快的主要原因b. C语言实现,与操作系统距离近c. 使用了单线程架构,预防多线程可能产生的竞争问题2〉键值对的数据结构服务器3〉丰富的功能:键过期,发布订阅,事务,流水线.....4〉简单稳定:单线程5〉持久化:发生断电或机器故障,数据可能会丢失,持久化到硬盘6〉主从复制:实现多个相同数据的redis副本7〉高可用和分布式:哨兵机制实现高可用,保证redis节点故障发现和自动转移8〉客户端语言多:java php python c c++ nodejs等3. redis有哪些应用场景1. 缓存:合理使用缓存加快数据访问速度,降低后端数据源压力2. 排行榜:按照热度排名,按照发布时间排行,主要用到列表和有序集合3. 计数器应用:视频网站播放数,网站浏览数,使用redis计数4. 社交网络:赞、踩、粉丝、下拉刷新5. 消息队列:发布和订阅4.redis的版本版本号第二位为奇数,为非稳定版本(2.7、2.9、3.1)第二为偶数,为稳定版本(2.6、2.8、3.0)当前奇数版本是下一个稳定版本的开发版本,如2.9是3.0的开发版本
Redis系列四:redis支持的数据类型
一、字符串<String> 1. 字符串类型:实际上可以是字符串(包括XML JSON),还有数字(整形 浮点数),二进制(图片 音频 视频),最大不能超过512MB 2. 设值命令:set name lgs ex 10  //10秒后过期  px 10000 毫秒过期setnx name lgs  //不存在键name时才能设置,返回1设置成功;存在的话失败0set age 29    //存在键age时直接覆盖之前的键值,返回1成功场景:如果有多客户同时执行setnx,只有一个能设置成功,可做分布式锁获值命令:get age //存在则返回value, 不存在返回nil批量设值:mset country china city beijing批量获取:mget country city address //返回china  beigjin, address为nil   若没有mget命令,则要执行n次get命令,从而占用网络资源影响性能使用mget=1次网络请求+redis内部n次查询,一次性返回所有查询结果3. 计数:incr age //必须为整数自加1,非整数返回错误,无age键从0自增返回1decr age //整数age减1,非整数返回错误,无age键从0自减返回-1incrby age 2 //整数age+2decrby age 2//整数age -2 incrbyfloat age 1.1 //整数age+1.14. append追加指令:set name hello; append name world //追加后成helloworld5. 字符串长度:set hello “世界”;strlen hello//结果6,每个中文占3个字节6. 截取字符串:set name helloworld ; getrange name 2 4//返回 llo7. 内部编码:int:8字节长整型set age 100; object encoding age //返回intembstr:小于等于39字节串set name bejin;      object encodeing name //返回embstrraw:大于39字节的字符串set a fsdfwerwerweffffffffffdfsaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaobject encoding a //返回raw8. 切换数据库:select 29. 应用场景:键值设计:业务名:对象名:id:[属性]数据库为order, 用户表user,对应的键可为 order:user:1 或order:user:1:name注意:redis目前处于受保护模式,不允许非本地客户端链接,可以通过给redis设置密码,然后客户端链接的时候,写上密码就可以了   127.0.0.1:6379> config set requirepass 123456  临时生效或者修改redis.conf   requirepass 123456,启动时./redis-server redis.conf指定conf            ./redis-cli -p 6379 -a 12345678  //需要加入密码才能访问二、哈希hash哈希hash是一个string类型的field和value的映射表,hash适合用于存储对象。1. 命令  hset key field value   设值:hset user:1 name lgs         //成功返回1,失败返回0   取值:hget user:1 name              //返回lgs   删值:hdel user:1 age               //返回删除的个数   计算键对应的字段个数:hset user:1 name lgs; hset user:1 age 27;       hlen user:1               //返回2,user:1有两个属性值   批量设值:hmset user:2 name ll age 28 sex boy //返回OK   批量取值:hmget user:2 name age sex   //返回三行:ll 28 boy   判断field是否存在:hexists user:2 name //若存在返回1,不存在返回0   获取所有field: hkeys user:2            // 返回name age sex三个field   获取user:2所有value:hvals user:2     // 返回ll 28 boy   获取user:2所有field与value:hgetall user:2 //name age sex ll 28 boy值   增加1:hincrby user:2 age 1      //age+1       hincrbyfloat user:2 age 2   //浮点型加22. 内部编码:ziplist<压缩列表>和hashtable<哈希表>当field个数少且没有大的value时,内部编码为ziplist 如:hmset user:3 name lgs age 27; object encoding user:3 //返回ziplist当value大于64字节,内部编码由ziplist变成hashtable      如:hset user:4 address “a64字节”; object encoding user:3 //返回hashtableHASH类型是稀疏,每个键可以有不同的filed, 若用redis模拟做关系复杂查询开发因难,维护成本高3. 三种方案实现用户信息存储优缺点:   1. 原生:set user:1:name james;           set user:1:age  23;           set user:1:sex boy;      优点:简单直观,每个键对应一个值      缺点:键数过多,占用内存多,用户信息过于分散,不用于生产环境2. 将对象序列化存入redisset user:1 serialize(userInfo);      优点:编程简单,若使用序列化合理内存使用率高      缺点:序列化与反序列化有一定开销,更新属性时需要把userInfo全取出来进行反序列化,更新后再序列化到redis3. 使用hash类型:        hmset user:1 name james age 23 sex boy   优点:简单直观,使用合理可减少内存空间消耗   缺点:要控制ziplist与hashtable两种编码转换,且hashtable会消耗更多内存总结:对于更新不多的情况下,可以使用序列化,对于VALUE值不大于64字节可以使用hash类型4. 应用场景数据库有张用户表结构如下:使用hash转存三、列表<list>1. 用来存储多个有序的字符串,一个列表最多可存2的32次方减1个元素因为有序,可以通过索引下标获取元素或某个范围内元素列表, 列表元素可以重复   2. 列表相关命令 添加命令:rpush lpush linsetrpush name a b c d //从右向左插入a b c d, 返回值4lrange name  0 -1 //从左到右获取列表所有元素 返回 a b c dlpush fav a b c d//从左向右插入a b c dlinsert fav before b r //在b之前插入r, after为之后,使 用lrange fav 0 -1 查看:d c r b a查找命令:lrange lindex llenlrange key start end //索引下标特点:从左到右为0到N-1lindex fav -1 //返回最右末尾a,-2返回bllen fav //返回当前列表长度 5删除命令:lpop rpop lrem ltrimlpop fav //把最左边的第一个元素d删除rpop fav //把最右边的元素a删除lrem key count value//删除指定元素如:lpush test b b b b b j x z //键test放入z x j b b b b bLrange test 0 -1 //查询结果为 z x j b b b b blrem test 4 b  //从左右开始删除b的元素,删除4个,若lrem test 8 b, 删除8个b, 但只有5个全部删除 lrange test 0 -1 //删除后的结果为 b j x zlrem test 0 b  //检索所有b全部删除 j x z lpush user b b b b b j x z //键user从左到右放入 z x j b b b b bltrim user 1 3  //只保留从第2到第4的元素x j b,其它全删 lrange user 0 -1 //查询结果为 x j b, 其它已全被删掉修改命令:lsetlpush user01 z y x //键user01从左到右放入x y zlset user01 2 java // 把第3个元素z替换成javalrange user01 0 -1 //查询结果为 x y java 阻塞命令:blpop brpop3.列表内部编码1,当元素个数少且没大元素,编码为ziplist,减少内存的使用   rpush list a b c   object encoding list //返回ziplist2,当元素超过512个,或元素超过64字节,内部编码变成linkedlist链表;   rpush list a1 a2 ....a513  或rpush list xxxxxxxxxxxxxx   object encoding list  //linkedlist在3.2版本以后,redis提供了quicklist内部编码,它结合了ziplist和linkedlist两者的优势,之前的ziplist是存在BUG的,使用quicklist内部编码效率更高,所以我们现在3.2以后看不到这两个编码,只看到quicklist,  4. 列表应用场景以订单为例子(不推荐使用redis做消息队列)1.每个用户有多个订单key为 order:1   order:2  order:3, 结合hmset  hmset order:1 orderId 1 money 36.6 time 2018-01-01  hmset order:2 orderId 2 money 38.6 time 2018-01-01  hmset order:3 orderId 3 money 39.6 time 2018-01-01   把订单信息的key放到队列  lpush user:1:order order:1 order:2 order:3   每新产生一个订单, hmset order:4 orderId 4 money 40.6 time 2018-01-01追加一个order:4放入队列第一个位置 lpush user:1:order order:4 当需要查询用户订单记录时: List orderKeys = lrange user:1 0 -1 //查询user:1 的所有订单key值  for(Order order: orderKeys){           hmget order:1}四、无序集合set保存多元素,与列表不一样的是不允许有重复元素,且集合是无序,一个集合最多可存2的32次方减1个元素,除了支持增删改查,还支持集合交集、并集、差集; 1. 无序集合set相关命令元素操作:exists sadd smembers srem scard spopexists user //检查user键值是否存在sadd user a b c//向user插入
Redis系列五:redis键管理和redis数据库管理
一、redis键管理1 键重命名rename oldKey newkey //格式rename oldKey newKey //若oldKey之前存在则被覆盖set name james ;set name1 mike //数据初始化renamenx name name1 //重命名失败,只有当name1不存在才能改名2 返回随机键 randomkey //返回随机键3 键过期expire name:03 20 //键name:03 在10秒后过期ttl name:03 //查看过期按秒到计时,当返回-2说明已删除pttl name:03 //查看过期按毫秒到时计set name:05 james //初始化数据pexpire name:05 20000 //20000毫秒(20S)后过期expire name:06 -2 //直接过期,和del一样设置键在某个时间点过期 使用的是时间戳expireat name:04 1516971599 //设置在2018/01/27 20:59:59过期时间转时间戳:网址http://tool.chinaz.com/Tools/unixtime.aspxhset user:01 name james //初始化数据expire user:01 60 //设置60S后过期ttl user:01 //查看过期剩余时间persist user:01 //去掉过期ttl user:1 //返回-1 可以永久查询不失效注意:对于字符串重设值后,expire无效,set name jamesexpire name 50ttl nameset name james1 //此时expire取消ttl name //返回-1, 长期有效4. 键的迁移把部分数据迁移到另一台redis服务器1, move key db  //reids有16个库, 编号为0-15 set name james1;  move name 5 //迁移到第6个库 select 5 ;//数据库切换到第6个库, get name  可以取到james1 这种模式不建议在生产环境使用,在同一个reids里可以玩2, dump key;  restore key ttl value//实现不同redis实例的键迁移,ttl=0代表没有过期时间例子:在A服务器上 192.168.1.111set name james;dump name; //  得到"x00x05jamesbx001x82;f"DhJ"在B服务器上:192.168.1.118restore name 0 "x00x05jamesbx001x82;f"DhJ" get name  //返回james3,migrate指令迁移到其它实例redis,在1.111服务器上将test移到118migrate192.168.1.1186379  test1000copyreplacekeys指令要迁移的目标IP端口迁移键值目标库超时时间迁移后不删除原键不管目标库是不存在test键都迁移成功迁移多个键5. 键的遍历redis提供了两个命令来遍历所有的键1,键全量遍历:mset country china city bj name james  //设置3个字符串键值对 keys  * //返回所有的键, *匹配任意字符多个字符keys *y //以结尾的键, keys n*e //以n开头以e结尾,返回namekeys n?me  //  ?问号代表只匹配一个字符  返回name,全局匹配 keys n?m*   //返回namekeys [j,l]*  //返回以j l开头的所有键  keys [j]ames 全量匹配james考虑到是单线程, 在生产环境不建议使用,如果键多可能会阻塞,如果键少,可以2,渐进式遍历mset  a a b b c c d d e e f f g g h h i i j j k k l l m m n n o o p p q q r r s s t t u u v v w w x x y y z z    //初始化26个字母键值对字符串类型: scan 0 match n* count 20 //匹配以n开头的键,取20条,第一次scan 0开始第二次从游标10开始取20个以n开头的键,相当于一页一页的取,当最后返回0时,键被取完 注:渐进式遍历可有效地解决keys命令可能产生的阻塞问题除scan字符串外:还有以下SCAN 命令用于迭代当前数据库中的数据库键。SSCAN 命令用于迭代集合键中的元素。HSCAN 命令用于迭代哈希键中的键值对。ZSCAN 命令用于迭代有序集合中的元素(包括元素成员和元素分值)。用法和scan一样二、redis数据库管理select 0 //共16个库, 0 --15, select切换数据库set name jamesselect 1get name //隔离了,取不到,和mysql不同库一样其中redis3.0以后的版本慢慢弱化了这个功能,如在redis cluster中只允许0数据库原因:1,redis单线程,如果用多个库,这些库使用同一个CPU,彼此会有影响2,多数据库,调试与运维麻烦,若有一个慢查询,会影响其它库查询速度3,来回切换,容易混乱flushdb: 只清空当前数据库的键值对 dbsiz 0flushall: 清空所有库的键值对 (这两个指令慎用!!!!)
Redis系列六:redis相关功能
一、 慢查询原因分析与mysql一样:当执行时间超过阀值,会将发生时间耗时的命令记录redis命令生命周期:发送 排队 执行 返回慢查询只统计第3个执行步骤的时间预设阀值:两种方式,默认为10毫秒1,动态设置6379:> config set slowlog-log-slower-than 10000 //10毫秒10000微秒使用config set完后,若想将配置持久化保存到redis.conf,要执行config rewrite2,redis.conf修改:找到slowlog-log-slower-than 10000 ,修改保存即可注意:slowlog-log-slower-than =0记录所有命令 -1命令都不记录原理:慢查询记录也是存在队列里的,slow-max-len 存放的记录最大条数,比如设置的slow-max-len=10,当有第11条慢查询命令插入时,队列的第一条命令就会出列,第11条入列到慢查询队列中, 可以config set动态设置,也可以修改redis.conf完成配置。2 命令:获取队列里慢查询的命令:slowlog get 查询返回的结构如下获取慢查询列表当前的长度:slowlog len //返回7对慢查询列表清理(重置):slowlog reset //再查slowlog len 此时返回0 清空对于线上slow-max-len配置的建议:线上可加大slow-max-len的值,记录慢查询存长命令时redis会做截断,不会占用大量内存,线上可设置1000以上对于线上slowlog-log-slower-than配置的建议:默认为10毫秒,根据redis并发量来调整,对于高并发比建议为1毫秒注意:1,慢查询只记录命令在redis的执行时间,不包括排队、网络传输时间2,慢查询是先进先出的队列,访问日志记录出列丢失,需定期执行slow get,将结果存储到其它设备中(如mysql)二、redis-cli详解./redis-cli -r 3 -h 192.168.1.111 -a 12345678 ping //返回pong表示127.0.0.1:6379能通,r代表次数./redis-cli -r 100 -i 1 info |grep used_memory_human //每秒输出内存使用量,输100次,i代表执行的时间间隔./redis-cli -p 6379 -h 192.168.1.111 -a 12345678对于我们来说,这些常用指令以上可满足,但如果要了解更多执行./redis-cli --help, 可百度三、 redis-server详解./redis-server ./redis.conf & //指定配置文件启动./redis-server --test-memory 1024 //检测操作系统能否提供1G内存给redis, 常用于测试,想快速占满机器内存做极端条件的测试,可使用这个指令redis上线前,做一次测试四、redis-benchmark:基准性测试,测试redis的性能100个客户端同时请求redis,共执行10000次,会对各类数据结构的命令进行测试:./redis-benchmark -h 127.0.0.1 -c 100 -n 10000  //100个并发连接,100000个请求,检测host为localhost 端口为6379的redis服务器性能测试存取大小为100字节的数据包的性能:./redis-benchmark -h 127.0.0.1 -p 6379 -q -d 100 只测试 set,lpush操作的性能,-q只显示每秒钟能处理多少请求数结果:./redis-benchmark -h 127.0.0.1 -t set,lpush -n 100000 -q只测试某些数值存取的性能, 比如说我在慢查询中发现,大部分为set语句比较慢,我们自己可以测一下Set是不是真的慢:./redis-benchmark -h 192.168.1.111 -n 100000 -q script load "redis.call('set','foo','bar')"五、Pipeline(管道)1.背景:没有pipeline之前,一般的redis命令的执行过程都是:发送命令-〉命令排队-〉命令执行-〉返回结果。多条命令的时候就会产生更多的网络开销 这个时候需要pipeline来解决这个问题:使用pipeline来打包执行N条命令,这样的话就只需简历一次网络连接,网络开销就少了2. 使用pipeline和未使用pipeline的性能对比:使用Pipeline执行速度与逐条执行要快,特别是客户端与服务端的网络延迟越大,性能体能越明显3.原生批命令(mset, mget)与Pipeline对比1) 原生批命令是原子性,pipeline是非原子性, (原子性概念:一个事务是一个不可分割的最小工作单位,要么都成功要么都失败。原子操作是指你的一个业务逻辑必须是不可拆分的. 处理一件事情要么都成功要么都失败,其实也引用了生物里概念,分子-〉原子,原子不可拆分)2) 原生批命令一命令多个key, 但pipeline支持多命令(存在事务),非原子性3) 原生批命令是服务端实现,而pipeline需要服务端与客户端共同完成4. pipeline正确使用方式:使用pipeline组装的命令个数不能太多,不然数据量过大,增加客户端的等待时间,还可能造成网络阻塞,可以将大量命令的拆分多个小的pipeline命令完成如:有300个命令需要执行,可以拆分成每30个一个pipeline执行六、redis事务pipeline是多条命令的组合,为了保证它的原子性,redis提供了简单的事务;redis的事物与mysql事物的最大区别是redis事物不支持事物回滚事务:事务是指一组动作的执行,这一组动作要么都成功,要么都失败。1. redis的简单事务,将一组需要一起执行的命令放到multi和exec两个命令之间,其中multi代表事务开始,exec代表事务结束2.停止事务discard3. 命令错误,语法不正确,导致事务不能正常执行,即事物的原子性 4. watch命令:使用watch后, multi失效,事务失效,其他的线程任然可以对值进行修改  在客户端1设置值使用watch监听key并使用multi开启事物,在客户端2追加完c之后再来客户端1追加redis,然后执行事物,可以看到在客户端1追加的redis没有起效果:客户端1:客户端2: 七、LUA语言与Redis配合使用1. LUA脚本语言是C开发的,类似存储过程1).减少网络开销:本来5次网络请求的操作,可以用一个请求完成,原先5次请求的逻辑放在redis服务器上完成。使用脚本,减少了网络往返时延。2).原子操作:Redis会将整个脚本作为一个整体执行,中间不会被其他命令插入。3).复用:客户端发送的脚本会永久存储在Redis中,意味着其他客户端可以复用这一脚本而不需要使用代码完成同样的逻辑语法:eval+脚本+KEYS[1]+键个数+键——》eval script numkeys key [key ...]eval "return redis.call('get',KEYS[1])" 1 name  语法示例1:1 local int sum = 02 local int i =03 while i <= 1004 do sum = sum+i5 i = i+16 end7 print(sum)语法示例2:1 local tables myArray={“james”,”java”,false,34} //定义2 local int sum = 03 print(myArray[3]) //返回false4 for i = 1,1005 do6 sum = sum+17 end8 print(sum)9 for j = 1,#myArray //遍历数组10 do11 print(myArray[j])12 if myArray[j] == “james”13 then14 print(“true”)15 break16 else17 print(“false”)18 end19 end2. 案例:实现访问频率限制: 实现访问者 $ip 127.0.0.1在一定的时间 $time 20S内只能访问 $limit 10次.a)使用JAVA语言实现:1 private boolean accessLimit(String ip, int limit,2 int time, Jedis jedis) {3 boolean result = true;45 String key = "rate.limit:" + ip;6 if (jedis.exists(key)) {7 long afterValue = jedis.incr(key);8 if (afterValue > limit) {9 result = false;10 }11 } else {12 Transaction transaction = jedis.multi();13 transaction.incr(key);14 transaction.expire(key, time);15 transaction.exec();16 }17 return result;18 }以上代码有两点缺陷 :可能会出现竞态条件: 解决方法是用 WATCH 监控 rate.limit:$IP 的变动, 但较为麻烦;以上代码在不使用 pipeline 的情况下最多需要向Redis请求5条指令, 传输过多.b)使用lua脚本来处理,包括了原子性lua脚本:ipAccessCount.lua1 local key = KEYS[1]2 local limit = tonumber(ARGV[1])3 local expire_time = ARGV[2]45 local is_exists = redis.call("EXISTS", key)6 if is_exists == 1 then7 if redis.call("INCR", key) > limit then8 return 09 else10 return 111 end12 else13 redis.call("SET", key, 1)14 redis.call("EXPIRE", key, expire_time)15 return 116 end使用redis命令获取lua脚本来执行:./redis-cli -p 6379 -a 12345678 --eval ipAccessCount.lua rate.limit:127.0.0.1, 10 20和lua脚本的对应关系为: keys[1] = rate.limit:127.0.0.1   argv[1]=10次,  argv[2]=20S失效执行逻辑:使用redis-cli --eavl时,客户端把lua脚本字符串发给redis服务端,将结果返回客户端,如下图3. redis对Lua脚本的管理1.将Lua脚本加载到redis中,得到 返回的sha1值(类似于我们说的数字签名):afe90689cdeec602e374ebad421e3911022f47c0redis-cli -h 192.168.1.111 -a 12345678 script load "$(cat random.lua)"2.检查脚本加载是否成功,返回1 已加载成功sc
Redis系列七:redis持久化
redis支持RDB和AOF两种持久化机制,持久化可以避免因进程退出而造成数据丢失一、RDB持久化RDB持久化把当前进程数据生成快照(.rdb)文件保存到硬盘的过程,有手动触发和自动触发手动触发有save和bgsave两命令save命令:阻塞当前Redis,直到RDB持久化过程完成为止,若内存实例比较大会造成长时间阻塞,线上环境不建议用它bgsave命令:redis进程执行fork操作创建子线程,由子线程完成持久化,阻塞时间很短(微秒级),是save的优化,在执行redis-cli shutdown关闭redis服务时,如果没有开启AOF持久化,自动执行bgsave;显然bgsave是对save的优化。bgsave运行流程RDB文件的操作   命令:config set dir /usr/local  //设置rdb文件保存路径   备份:bgsave  //将dump.rdb保存到usr/local下   恢复:将dump.rdb放到redis安装目录与redis.conf同级目录,重启redis即可   优点:1,压缩后的二进制文文件适用于备份、全量复制,用于灾难恢复              2,加载RDB恢复数据远快于AOF方式   缺点:1,无法做到实时持久化,每次都要创建子进程,频繁操作成本过高              2,保存后的二进制文件,存在老版本不兼容新版本rdb文件的问题  二、AOF持久化针对RDB不适合实时持久化,redis提供了AOF持久化方式来解决开启:redis.conf设置:appendonly yes  (默认不开启,为no)默认文件名:appendfilename "appendonly.aof"         流程说明: 1,所有的写入命令(set hset)会append追加到aof_buf缓冲区中         2,AOF缓冲区向硬盘做sync同步         3,随着AOF文件越来越大,需定期对AOF文件rewrite重写,达到压缩         4,当redis服务重启,可load加载AOF文件进行恢复AOF持久化流程:命令写入(append),文件同步(sync),文件重写(rewrite),重启加载(load)AOF配置详解:appendonly yes     //启用aof持久化方式# appendfsync always //每收到写命令就立即强制写入磁盘,最慢的,但是保证完全的持久化,不推荐使用appendfsync everysec //每秒强制写入磁盘一次,性能和持久化方面做了折中,推荐# appendfsync no    //完全依赖os,性能最好,持久化没保证(操作系统自身的同步)no-appendfsync-on-rewrite  yes  //正在导出rdb快照的过程中,要不要停止同步aofauto-aof-rewrite-percentage 100  //aof文件大小比起上次重写时的大小,增长率100%时,重写auto-aof-rewrite-min-size 64mb   //aof文件,至少超过64M时,重写如何从AOF恢复?1. 设置appendonly yes;2. 将appendonly.aof放到dir参数指定的目录;3. 启动Redis,Redis会自动加载appendonly.aof文件。redis重启时恢复加载AOF与RDB顺序及流程:1,当AOF和RDB文件同时存在时,优先加载AOF2,若关闭了AOF,加载RDB文件3,加载AOF/RDB成功,redis重启成功4,AOF/RDB存在错误,redis启动失败并打印错误信息 
Redis系列八:redis主从复制和哨兵
一、Redis主从复制主从复制:主节点负责写数据,从节点负责读数据,主节点定期把数据同步到从节点保证数据的一致性1. 主从复制的相关操作a,配置主从复制方式一、新增redis6380.conf, 加入 slaveof 192.168.152.128 6379, 在6379启动完后再启6380,完成配置;b,配置主从复制方式二、redis-server --slaveof 192.168.152.128 6379 临时生效c,查看状态:info replicationd,断开主从复制:在slave节点,执行6380:>slaveof no onee,断开后再变成主从复制:6380:> slaveof 192.168.152.128 6379f,数据较重要的节点,主从复制时使用密码验证: requirepasse,从节点建议用只读模式slave-read-only=yes, 若从节点修改数据,主从数据不一致h,传输延迟:主从一般部署在不同机器上,复制时存在网络延时问题,redis提供repl-disable-tcp-nodelay参数决定是否关闭TCP_NODELAY,默认为关闭参数关闭时:无论大小都会及时发布到从节点,占带宽,适用于主从网络好的场景,参数启用时:主节点合并所有数据成TCP包节省带宽,默认为40毫秒发一次,取决于内核,主从的同步延迟40毫秒,适用于网络环境复杂或带宽紧张,如跨机房2. Redis主从拓扑a)一主一从:用于主节点故障转移从节点,当主节点的“写”命令并发高且需要持久化,可以只在从节点开启AOF(主节点不需要),这样即保证了数据的安全性,也避免持久化对主节点的影响  b)一主多从:针对“读”较多的场景,“读”由多个从节点来分担,但节点越多,主节点同步到多节点的次数也越多,影响带宽,也加重主节点的稳定 c)树状主从:一主多从的缺点(主节点推送次数多压力大)可用些方案解决,主节点只推送一次数据到从节点B,再由从节点B推送到C,减轻主节点推送的压力。 3. 主从复制原理 4. 数据同步redis 2.8版本以上使用psync命令完成同步,过程分“全量”与“部分”复制全量复制:一般用于初次复制场景(第一次建立SLAVE后全量)部分复制:网络出现问题,从节点再次连接主节点时,主节点补发缺少的数据,每次数据增量同步心跳:主从有长连接心跳,主节点默认每10S向从节点发ping命令,repl-ping-slave-period控制发送频率5. 主从的缺点a)主从复制,若主节点出现问题,则不能提供服务,需要人工修改配置将从变主b)主从复制主节点的写能力单机,能力有限c)单机节点的存储能力也有限6.主从故障如何故障转移a)主节点(master)故障,从节点slave-1端执行 slaveof no one后变成新主节点;b)其它的节点成为新主节点的从节点,并从新节点复制数据;c)需要人工干预,无法实现高可用。二、Redis哨兵机制(Sentinel)1. 为什么要有哨兵机制?       哨兵机制的出现是为了解决主从复制的缺点的2. 哨兵机制(sentinel)的高可用原理:当主节点出现故障时,由Redis Sentinel自动完成故障发现和转移,并通知应用方,实现高可用性。其实整个过程只需要一个哨兵节点来完成,首先使用Raft算法(选举算法)实现选举机制,选出一个哨兵节点来完成转移和通知3. 哨兵的定时监控任务任务1:每个哨兵节点每10秒会向主节点和从节点发送info命令获取最拓扑结构图,哨兵配置时只要配置对主节点的监控即可,通过向主节点发送info,获取从节点的信息,并当有新的从节点加入时可以马上感知到任务2:每个哨兵节点每隔2秒会向redis数据节点的指定频道上发送该哨兵节点对于主节点的判断以及当前哨兵节点的信息,同时每个哨兵节点也会订阅该频道,来了解其它哨兵节点的信息及对主节点的判断,其实就是通过消息publish和subscribe来完成的 任务3:每隔1秒每个哨兵会向主节点、从节点及其余哨兵节点发送一次ping命令做一次心跳检测,这个也是哨兵用来判断节点是否正常的重要依据客观下线:当主观下线的节点是主节点时,此时该哨兵3节点会通过指令sentinel is-masterdown-by-addr寻求其它哨兵节点对主节点的判断,当超过quorum(选举)个数,此时哨兵节点则认为该主节点确实有问题,这样就客观下线了,大部分哨兵节点都同意下线操作,也就说是客观下线 4. 领导者哨兵选举流程a)每个在线的哨兵节点都可以成为领导者,当它确认(比如哨兵3)主节点下线时,会向其它哨兵发is-master-down-by-addr命令,征求判断并要求将自己设置为领导者,由领导者处理故障转移;b)当其它哨兵收到此命令时,可以同意或者拒绝它成为领导者;c)如果哨兵3发现自己在选举的票数大于等于num(sentinels)/2+1时,将成为领导者,如果没有超过,继续选举………… 5. 故障转移机制a)由Sentinel节点定期监控发现主节点是否出现了故障sentinel会向master发送心跳PING来确认master是否存活,如果master在“一定时间范围”内不回应PONG 或者是回复了一个错误消息,那么这个sentinel会主观地(单方面地)认为这个master已经不可用了   b) 当主节点出现故障,此时3个Sentinel节点共同选举了Sentinel3节点为领导,负载处理主节点的故障转移  c) 由Sentinel3领导者节点执行故障转移,过程和主从复制一样,但是自动执行  流程: 1. 将slave-1脱离原从节点,升级主节点,         2. 将从节点slave-2指向新的主节点         3. 通知客户端主节点已更换         4. 将原主节点(oldMaster)变成从节点,指向新的主节点 d) 故障转移后的redis sentinel的拓扑结构图6. 哨兵机制-故障转移详细流程-确认主节点a) 过滤掉不健康的(下线或断线),没有回复过哨兵ping响应的从节点b) 选择salve-priority从节点优先级最高(redis.conf)的c) 选择复制偏移量最大,指复制最完整的从节点7. 实战:如何安装和部署哨兵以3个Sentinel节点、2个从节点、1个主节点为例进行安装部署1. 前提:先搭好一主两从redis的主从复制,和之前的主从复制搭建一样,搭建方式如下:A)主节点6379节点(/usr/local/bin/conf/redis6379.conf):修改 requirepass 12345678,注释掉#bind 127.0.0.1B) 从节点redis6380.conf和redis6381.conf: 配置都一样 修改 requirepass 12345678 ,注释掉#bind 127.0.0.1,加上访问主节点的密码masterauth 12345678 ,加上slaveof 192.168.152.128 6379    注意:当主从起来后,主节点可读写,从节点只可读不可写2. redis sentinel哨兵机制核心配置(也是3个节点):       /usr/local/bin/conf/sentinel_26379.conf         /usr/local/bin/conf/sentinel_26380.conf       /usr/local/bin/conf/sentinel_26381.conf将三个文件的端口改成: 26379   26380   26381然后:sentinel monitor mymaster 192.168.152.128 6379 2  //监听主节点6379      sentinel auth-pass mymaster 12345678     //连接主节点时的密码三个配置除端口外,其它一样。3. 哨兵其它的配置:只要修改每个sentinel.conf的这段配置即可:sentinel monitor mymaster 192.168.152.128 6379 2  //监控主节点的IP地址端口,sentinel监控的master的名字叫做mymaster,2代表,当集群中有2个sentinel认为master死了时,才能真正认为该master已经不可用了sentinel auth-pass mymaster 12345678  //sentinel连主节点的密码sentinel config-epoch mymaster 2  //故障转移时最多可以有2从节点同时对新主节点进行数据同步sentinel leader-epoch mymaster 2sentinel failover-timeout mymasterA 180000 //故障转移超时时间180s,                            a,如果转移超时失败,下次转移时时间为之前的2倍;b,从节点变主节点时,从节点执行slaveof no one命令一直失败的话,当时间超过180S时,则故障转移失败c,从节点复制新主节点时间超过180S转移失败sentinel down-after-milliseconds mymasterA 300000//sentinel节点定期向主节点ping命令,当超过了300S时间后没有回复,可能就认定为此主节点出现故障了……sentinel parallel-syncs mymasterA 1 //故障转移后,1代表每个从节点按顺序排队一个一个复制主节点数据,如果为3,指3个从节点同时并发复制主节点数据,不会影响阻塞,但存在网络和IO开销4. 启动redis服务和sentinel服务:a)先把之前安装的redis里面的标绿色的文件都拷贝到 usr/local/bin目录下,然后再再bin目录下新建一个conf文件夹存放配置好的redis主从配置文件和哨兵配置文件b)启动主从复制服务,先启动主再启动从主:./redis-server conf/redis6379.conf &从:./redis-server conf/redis6380.conf &./redis-server conf/redis6381.conf &       c)启动sentinel服务:       ./redis-sentinel conf/sentinel_26379.conf &       ./redis-sentinel conf/sentinel_26380.conf &      ./redis-sentinel conf/sentinel_26381.conf &到此服务全部启动完毕 连接到6379的redis的服务,可看到6379就是主节点,他有6380和6381两个从节点5. 测试:kill -9 6379  杀掉6379的redis服务可以看到杀掉6379以后6380变为了主节点,6381变为了6380的从节点重新启动6379以后变为6380的从节点看日志是分配6380 是6381的主节点,当6379服务再启动时,已变成从节点假设6380升级为主节点:进入6380>info replication     可以看到role:master打开sentinel_26379.conf等三个配置,sentinel monitor mymaster 192.168.152.128 6380 2打开redis6379.conf等三个配置, slaveof 192.168.152.128 6380,也变成了6380注意:生产环境建议让redis Sentinel部署到不同的物理机上。8.部署建议a,sentinel节点应部署在多台物理机(线上环境)b,至少三个且奇数个sentinel节点c,通过以上我们知道,3个sentinel可同时监控一个主节点或多个主节点    监听N个主节点较多时,如果sentinel出现异常,会对多个主节点有影响,同时还会造成sentinel节点产生过多的网络连接,    一般线上建议还是, 3个sentinel监听一个主节点 
Redis系列九:redis集群高可用
Redis集群的概念:RedisCluster是redis的分布式解决方案,在3.0版本后推出的方案,有效地解决了Redis分布式的需求,当一个服务挂了可以快速的切换到另外一个服务,当遇到单机内存、并发等瓶颈时,可使用此方案来解决这些问题一、分布式数据库概念1. 分布式数据库把整个数据按分区规则映射到多个节点,即把数据划分到多个节点上,每个节点负责整体数据的一个子集。比如我们库有900条用户数据,有3个redis节点,将900条分成3份,分别存入到3个redis节点2. 分区规则:   常见的分区规则哈希分区和顺序分区,redis集群使用了哈希分区,顺序分区暂用不到,不做具体说明;   rediscluster采用了哈希分区的“虚拟槽分区”方式(哈希分区分节点取余、一致性哈希分区和虚拟槽分区),其它两种也不做介绍,有兴趣可以百度了解一下3. 虚拟槽分区(槽:slot)   RedisCluster采用此分区,所有的键根据哈希函数(CRC16[key]&16383)映射到0-16383槽内,共16384个槽位,每个节点维护部分槽及槽所映射的键值数据   哈希函数: Hash()=CRC16[key]&16383 按位与   槽与节点的关系如下redis用虚拟槽分区原因:解耦数据与节点关系,节点自身维护槽映射关系,分布式存储4. redisCluster的缺陷:a,键的批量操作支持有限,比如mset, mget,如果多个键映射在不同的槽,就不支持了b,键事务支持有限,当多个key分布在不同节点时无法使用事务,同一节点是支持事务c,键是数据分区的最小粒度,不能将一个很大的键值对映射到不同的节点d,不支持多数据库,只有0,select 0e,复制结构只支持单层结构,不支持树型结构。  二、集群环境搭建-手动篇部署结构图:6389为6379的从节点,6390为6380的从节点,6391为6381的从节点  1.在/usr/local/bin/目录下新建一个文件夹clusterconf,用来存放集群的配置文件2. 分别修改6379、 6380、 7381、 6389、 6390、 6391配置文件以6379的配置为例:   port 6379                      //节点端口   cluster-enabled yes              //开启集群模式   cluster-node-timeout 15000       //节点超时时间(接收pong消息回复的时间)   cluster-config-file  /usr/localbin/cluster/data/nodes-6379.conf 集群内部配置文件  其它节点的配置和这个一致,改端口即可3. 配置完后,启动6个redis服务./redis-server clusterconf/redis6379.conf &./redis-server clusterconf/redis6380.conf &./redis-server clusterconf/redis6381.conf &./redis-server clusterconf/redis6389.conf &./redis-server clusterconf/redis6390.conf &./redis-server clusterconf/redis6391.conf &4. 各节点启动后,使用cluster meet ip port与各节点握手,是集群通信的第一步(关键步骤1:集群搭建-与各节点握手) 5. 握手成功后,使用cluster nodes可以看到各节点都可以互相查询到 6. 节点握手成功后,此时集群处理下线状态,所有读写都被禁止7. 使用cluster info命令获取集群当前状态 8. redis集群有16384个哈希槽,要把所有数据映射到16384槽,需要批量设置槽(关键步骤2:集群搭建-分配槽)redis-cli -h 192.168.152.128 -p 6379 cluster addslots {0...5461}redis-cli -h 192.168.152.128 -p 6380 cluster addslots {5641...10922}redis-cli -h 192.168.152.128 -p 6381 cluster addslots {10923...16383}9. 分配完槽后,可查看集群状态cluster info11. 然后再查看cluster nodes,查看每个节点的ID12. 将6389,6390,6391与 6379,6380,6381做主从映射(关键步骤3:集群搭建-集群映射),到此redis集群手动搭建完成 192.168.152.128:6389> cluster replicate 9b7b0c22f95eb01fb9935ad4b04d396c7f99e881192.168.152.128:6390> cluster replicate 5351c088472467ae485ed519cea271efda646bfa 92.168.152.128:6391> cluster replicate e718f126278072e1e180c3e518d73e0bc877b3dc三、集群环境搭建-自动篇使用ruby来自动分配槽与主从分配,见redis安装文档,建议用此方式完成1.首先下载ruby-2.3.1.tar.gz   和  redis-3.3.0.gem,然后把这两个工具通过winscp传入linux虚拟机2. 在/usr/local新建目录:ruby开始安装第一步中下载的两个软件tar -zxvf ruby-2.3.1.tar.gz a) cd /software/rubysoft/ruby-2.3.1b) ./configure -prefix=/usr/local/rubyc)  make && make install   //过程会有点慢,大概5-10分钟d) 然后gem install -l redis-3.3.0.gem  //没有gem需要安装yum install gem或者yum install rubygemse) 准备好6个节点,(注意不要设置requirepass),将/usr/local/bin/clusterconf/data之前手动配置的config-file文件删除;依次启动6个节点:./redis-server clusterconf/redis6379.conf....如果之前redis有数据存在,flushall清空;(坑:不需要cluster meet ..)f) 进入cd /usr/local/bin,  执行以下:1代表从节点的个数(最关键的一步:通过这个命令可以自动完成之前手动配置集群的握手、分配槽位、主从映射)./redis-trib.rb create --replicas 1 192.168.152.128:6379 192.168.152.128:6380 192.168.152.128:6381 192.168.152.128:6389 192.168.152.128:6390 192.168.152.128:6391主从分配,6379是6389的主节点,6380是6390的主节点,6381是6391的主节点,貌似只有主节点可读写,从节点只能读主节点死后,从节点变成主节点3.集群搭建好以后开始测试a) 连入redis集群并设置值./redis-cli -h 192.168.152.128 -p 6379 -c可以看到设置name以后,重定向到了6380的这个redis,这时因为键name经过CRC16[k]&16284虚拟槽分区算法以后落到了6380这个节点,所以数据就存到了6380这个redis节点切回6379获取name的值也会通过同样的算法重定向到63804.集群健康检查./redis-trib.rb check 192.168.152.128:6379 (注:redis先去注释掉requirepass,不然连不上)正常的 不正常的(来源于网络资源)如此出现了这个问题,6379的5798槽位号被打开了解决如下:6379,6380,6381的有部分槽位被打开了,分别进入这几个节点,执行6380:>cluster setslot 1180 stablecluster setslot 2998 stablecluster setslot 11212 stable其它也一样,分别执行修复完后: 当停掉6379后,过会6389变成主节点集群正常启动后,在每个redis.conf里加上   masterauth “12345678”   requiredpass “12345678”  当主节点下线时,从节点会变成主节点,用户和密码是很有必要的,设置成一致5.集群的多主多从配置如下:./redis-trib.rb create --replicas 2192.168.1.111:6379 192.168.1.111:6380 192.168.1.111:6381192.168.1.111:6479 192.168.1.111:6480 192.168.1.111:6481192.168.1.111:6579 192.168.1.111:6580 192.168.1.111:6581 6.集群节点之间的通信 1. 节点之间采用Gossip协议进行通信,Gossip协议就是指节点彼此之间不断通信交换信息  当主从角色变化或新增节点,彼此通过ping/pong进行通信知道全部节点的最新状态并达到集群同步 2. Gossip协议Gossip协议的主要职责就是信息交换,信息交换的载体就是节点之间彼此发送的Gossip消息,常用的Gossip消息有ping消息、pong消息、meet消息、fail消息 meet消息:用于通知新节点加入,消息发送者通知接收者加入到当前集群,meet消息通信完后,接收节点会加入到集群中,并进行周期性ping pong交换ping消息:集群内交换最频繁的消息,集群内每个节点每秒向其它节点发ping消息,用于检测节点是在在线和状态信息,ping消息发送封装自身节点和其他节点的状态数据;pong消息,当接收到ping meet消息时,作为响应消息返回给发送方,用来确认正常通信,pong消息也封闭了自身状态数据;fail消息:当节点判定集群内的另一节点下线时,会向集群内广播一个fail消息,3. 消息解析流程所有消息格式为:消息头、消息体,消息头包含发送节点自身状态数据(比如节点ID、槽映射、节点角色、是否下线等),接收节点根据消息头可以获取到发送节点的相关数据。消息解析流程:   4. 选择节点并发送ping消息:  Gossip协议信息的交换机制具有天然的分布式特性,但ping pong发送的频率很高,可以实时得到其它节点的状态数据,但频率高会加重带宽和计算能力,因此每次都会有目的性地选择一些节点; 但是节点选择过少又会影响故障判断的速度,redis集群的Gossip协议兼顾了这两者的优缺点,看下图: 不难看出:节点选择的流程可以看出消息交换成本主要体现在发送消息的节点数量和每个消息携带的数据量流程说明:A,选择发送消息的节点数量:集群内每个节点维护定时任务默认为每秒执行10次,每秒会随机选取5个节点,找出最久没有通信的节点发送ping消息,用来保证信息交换的随机性,每100毫秒都会扫描本地节点列表,如果发现节点最近一次接受pong消息的时间大于cluster-node-timeout/2 则立刻发送ping消息,这样做目的是防止该节点信息太长时间没更新,当我们宽带资源紧张时,在可redis.conf将cluster-node-timeout 15000  改成30秒,但不能过度加大B,消息数据:节点自身信息和其他节点信息四、redis集群扩容这也是分布式存储最常见的需求,当我们存储不够用时,要考虑扩容扩容步骤如下:A. 准备好新节点B. 加入集群,迁移
Redis系列十:缓存雪崩、缓存穿透、缓存预热、缓存更新、缓存降级
一、缓存雪崩缓存雪崩我们可以简单的理解为:由于原有缓存失效,新缓存未到期间(例如:我们设置缓存时采用了相同的过期时间,在同一时刻出现大面积的缓存过期),所有原本应该访问缓存的请求都去查询数据库了,而对数据库CPU和内存造成巨大压力,严重的会造成数据库宕机。从而形成一系列连锁反应,造成整个系统崩溃。缓存正常从Redis中获取,示意图如下:缓存失效瞬间示意图如下:缓存雪崩的解决方案:(1)碰到这种情况,一般并发量不是特别多的时候,使用最多的解决方案是加锁排队,伪代码如下:加锁排队只是为了减轻数据库的压力,并没有提高系统吞吐量。假设在高并发下,缓存重建期间key是锁着的,这是过来1000个请求999个都在阻塞的。同样会导致用户等待超时,这是个治标不治本的方法!注意:加锁排队的解决方式分布式环境的并发问题,有可能还要解决分布式锁的问题;线程还会被阻塞,用户体验很差!因此,在真正的高并发场景下很少使用!(2)给每一个缓存数据增加相应的缓存标记,记录缓存的是否失效,如果缓存标记失效,则更新数据缓存,实例伪代码如下:解释说明:1、缓存标记:记录缓存数据是否过期,如果过期会触发通知另外的线程在后台去更新实际key的缓存;2、缓存数据:它的过期时间比缓存标记的时间延长1倍,例:标记缓存时间30分钟,数据缓存设置为60分钟。 这样,当缓存标记key过期后,实际缓存还能把旧数据返回给调用端,直到另外的线程在后台更新完成后,才会返回新缓存。关于缓存崩溃的解决方法,这里提出了三种方案:使用锁或队列、设置过期标志更新缓存、为key设置不同的缓存失效时间,还有一各被称为“二级缓存”的解决方法,有兴趣的读者可以自行研究。二、缓存穿透缓存穿透是指用户查询数据,在数据库没有,自然在缓存中也不会有。这样就导致用户查询的时候,在缓存中找不到,每次都要去数据库再查询一遍,然后返回空(相当于进行了两次无用的查询)。这样请求就绕过缓存直接查数据库,这也是经常提的缓存命中率问题。 缓存穿透解决方案:(1)采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被这个bitmap拦截掉,从而避免了对底层存储系统的查询压力。(2)如果一个查询返回的数据为空(不管是数据不存在,还是系统故障),我们仍然把这个空结果进行缓存,但它的过期时间会很短,最长不超过五分钟。通过这个直接设置的默认值存放到缓存,这样第二次到缓存中获取就有值了,而不会继续访问数据库,这种办法最简单粗暴!  把空结果也给缓存起来,这样下次同样的请求就可以直接返回空了,即可以避免当查询的值为空时引起的缓存穿透。同时也可以单独设置个缓存区域存储空值,对要查询的key进行预先校验,然后再放行给后面的正常缓存处理逻辑。 三、缓存预热 缓存预热就是系统上线后,提前将相关的缓存数据直接加载到缓存系统。避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题!用户直接查询事先被预热的缓存数据! 缓存预热解决方案:(1)直接写个缓存刷新页面,上线时手工操作下;(2)数据量不大,可以在项目启动的时候自动进行加载;(3)定时刷新缓存;四、缓存更新除了缓存服务器自带的缓存失效策略之外(Redis默认的有6中策略可供选择),我们还可以根据具体的业务需求进行自定义的缓存淘汰,常见的策略有两种:(1)定时去清理过期的缓存;(2)当有用户请求过来时,再判断这个请求所用到的缓存是否过期,过期的话就去底层系统得到新数据并更新缓存。两者各有优劣,第一种的缺点是维护大量缓存的key是比较麻烦的,第二种的缺点就是每次用户请求过来都要判断缓存失效,逻辑相对比较复杂!具体用哪种方案,大家可以根据自己的应用场景来权衡。五、缓存降级当访问量剧增、服务出现问题(如响应时间慢或不响应)或非核心服务影响到核心流程的性能时,仍然需要保证服务还是可用的,即使是有损服务。系统可以根据一些关键数据进行自动降级,也可以配置开关实现人工降级。降级的最终目的是保证核心服务可用,即使是有损的。而且有些服务是无法降级的(如加入购物车、结算)。在进行降级之前要对系统进行梳理,看看系统是不是可以丢卒保帅;从而梳理出哪些必须誓死保护,哪些可降级;比如可以参考日志级别设置预案:(1)一般:比如有些服务偶尔因为网络抖动或者服务正在上线而超时,可以自动降级;(2)警告:有些服务在一段时间内成功率有波动(如在95~100%之间),可以自动降级或人工降级,并发送告警;(3)错误:比如可用率低于90%,或者数据库连接池被打爆了,或者访问量突然猛增到系统能承受的最大阀值,此时可以根据情况自动降级或者人工降级;(4)严重错误:比如因为特殊原因数据错误了,此时需要紧急人工降级。 参考资料:http://m.toutiao11.com/group/6533812974807679495/?iid=7163591049&app=news_article&timestamp=1521327601&tt_from=mobile_qq&utm_source=mobile_qq&utm_medium=toutiao_ios&utm_campaign=client_share
Basic Tutorials of Redis(1) - Install And Configure Redis
Nowaday, Redis became more and more popular , many projects use it in the cache module and the store module.There are already many people wrote posts about Redis.And I am vary pleasure to join them to share my learing of Redis.But I am new in this technology,if you find some mistakes on my post,please point out them and excuse my fault.The Environment of my computer is CentOS 7 (Vmware).What we do first is to install the Redis.The installation of Redisis vary easy.Let's start:a) use the cd command to enter the tmp folderb) use the wget command to download the redis wget http://download.redis.io/releases/redis-3.2.3.tar.gz  c) use the tar to decompress the file  tar xzf redis-3.2.3.tar.gz -C /usr/redis-3.2.3  d) enter the redis-3.2.3 folder        e) use the make command to build it         f) copy used file to /usr/redis/usr/redis-3.2.3/redis.conf/usr/redis-3.2.3/src/redis-server/usr/redis-3.2.3/src/redis-benchmark/usr/redis-3.2.3/src/redis-benchmark/usr/redis-3.2.3/src/redis-check-rdb/usr/redis-3.2.3/src/redis-cliNow,we can use the command  ./redis-server  to start the Redis.What a easy thing that everybody can do.We can find that while we use this command to start the redis , we can't do anything utill we open another window.There is a Solution to deal with it.We should stop the redis by  ctrl+c  first.Then use the  vim redis.conf  to modify the configuration.what we should do is to find out the daemonize and set its value to yes and save it.After this step,we start the service again.At this time you can enter commands in current window not another one.But you will ask this question:Is it running now?Sure!We can use the follow command to prove this question. ps aux|grep redis As the above picture shows,the service of redis is running.At last,I will tell you how to stop the service.You can use the client to stop it,and you can use the below command../redis-cli shutdown and use the command ps to prove it.The next post of this series is the basic opreation of the Redis , including the redis's native commandsand how to use StackExchange.Redis in C#. 
Basic Tutorials of Redis(2) - String
This post is mainly about how to use the commands to handle the Strings of Redis.And I will show you both the nativecommands and the usage of the StackExchange.Redis.The version of Redis is 3.2.3 and the vesion of StackExchange.Redisis 1.1.604-alpha.Yeah,You are right,I will show you by using a dotnet core console app.Maybe you will ask me that why don'tyou use the ServiceStack.Redis? The reasons why I choose StackExchange.Redis are as follow:1.I am a poor man so that I can not pay for the License of ServiceStack.Redis2.The opreations of this two drive are vary easy,but I like to use the StackExchange.Redis.3.Open source code OK,let's begin.First of all,we should start the service of redis.We can not do anything without the running service.And use the  ./redis-cli  toenter the client.Then choose the second database for this tutorial.How many commands belong to Strings?You can find the answer in http://www.redis.io/commands#stringand I use Xmind to make a picture as follow: Up to today,there are 24 commands that we can use to handle Strings.But I will choose some of them to show you in thistutorials .Many commands are very useful of Strings,and I will Introduce them at the future tutorials.The first thing we should take care is that how to store the data? If you learned Memcached before,you can compare with them.They have some common features.But there are no relationship between Redis and Memcached.In Redis,you can use the commandset to store the data you want to save,and the command get can help you to find out the data you stored.For example, I set a keynamed name with a value catcher,after successffully stored,the client will return you a OK message.And using the get command withthe key's name you can get the value of this key.set name catcherget nameHowever those two commands can only handle a single k/v.How about handle two k/v or more k/v?Don't worry,there are also some commands(mset,mget) can handle multi k/v which can help you to finish some difficult jobs.The usage of them look like this:mset age 18 gender malemget name age genderIt's very good for the flexible commands.The next command is append,which you may often use in the program languages tohandle the strings,such as C#'s StringBuilder.After creating an instance of StringBuilder(sb),we can use sb.Append to append manystrings to sb.Naturally,command append can do this too.As you can see , I appended wong to catcher so I got the result catcherwong.append name wongFor Relational Database,when designing a table,we often set the primary key increasing automatically.How can we do in Redis?Thecreator of Redis already considerred this situation.All of us can use command incr to increase the value by 1 automatically,and use command incrby to increase the value by a integet.I made the key named age increase automatically and the client returned the resultafter increasing.The command incrby can assign the value to increase.incr ageincr age 2The same as Memcached,Redis can also set the expired time when some of you want to use Redis for your Cache Module.Setex canset the value and expiration of a key.For an instance,I set a key named tmp for saving 5s.setex tmp 5 tmpStop introducing more,the usage of other commands you can find out in the site of Redis.Now, I will show you the same commands above by using C#.1 //connect the redis server by ip address2 ConnectionMultiplexer redis = ConnectionMultiplexer.Connect("192.168.198.128");3 //choose the second database of redis4 IDatabase db = redis.GetDatabase(2);56 //set get7 db.StringSet("name","catcher");8 Console.WriteLine(string.Format("the value of name is {0}",db.StringGet("name")));910 //mset mget11 var dic = new Dictionary<RedisKey, RedisValue>();12 dic.Add("age",18);13 dic.Add("gender","male");14 db.StringSet(dic.ToArray());1516 var keys = new RedisKey[2] { "age","gender"};17 Parallel.ForEach(db.StringGet(keys), (item) =>18 {19 Console.WriteLine(string.Format("mget value ----{0}",item));20 });2122 //append23 db.StringAppend("name", " wong");24 Console.WriteLine(string.Format("after append the value of name is {0}",db.StringGet("name")));2526 //incr incrby27 db.StringIncrement("age");28 Console.WriteLine(string.Format("after incr the value of age is {0}", db.StringGet("age")));29 db.StringIncrement("age",2);30 Console.WriteLine(string.Format("after incrby the value of age is {0}", db.StringGet("age")));3132 //setex33 db.StringSet("tmp", "tmp", TimeSpan.FromSeconds(5));34 var task = Task.Factory.StartNew(()=>35 {36 Task.Delay(TimeSpan.FromSeconds(5)).Wait()
Basic Tutorials of Redis(3) -Hash
When you first saw the name of Hash,what do you think?HashSet,HashTable or other data structs of C#?As for me,the first time I saw the Hash,I considered is as the HashTable.Actually,Hash can identify with HashTable,the same asDataRow.A row data of table can Regular as a Hash's data.The below picture may help you to card the point. There are 15 commands you can use in Redis,less than the Strings. Before we use the Hash of Redis, we must hava some exists Hashes in the Redis's database.So how can we store theHash to the database?And how can we get the Hash from the database.Command hset,hmset,hget,hmget can help us tosolve those two question.Now I use hset to add a key named user-1 with a filed named name , and the value of the filed iscatcher.And I use hget to get the value of name.hset user-1 name catcherhget user-1 nameThe hmset and hmget can handle multi k/v.hmset user-1 age 18 gender malehmget user-1 name age genderWhen you want to learn how many fileds in this Hash,you can use the command hlen to get the number of fileds in the Hash.And it will return a integer,meaning there are 3 fileds in the user-1.hlen user-1 hget and hmget is a little complex when a hash has 100 fileds or much more.To solve this problem,we can use thehgetall command,this command will return all of the fileds and the values of this key.hgetall user-1If there some fileds you don't need anymore,you can delete them by hdel command.For an instance,I delete thegender filed from the user-1.hdel user-1 genderSometimes,we have to judge wheather a filed existses in the key.At this time we can use the hexists to finish thejob.As you can see,I judge wheather gender and name exists in the user-1.hexists user-1 genderhexists user-1 nameWith the Requirement change,some places many only need the fileds of the hash ,the other place only need thevalues of the hash.At this situation,some people may use hgetall to finish the requirements,but I don't suggest to domore than the request.So I will use the hkeys to get all the fileds of the hash,and use the hvals to get all the values ofthe hash.hkeys user-1hvals user-1 How about increase a filed's value like the string do.As for me ,both of the increased command and decreased commandare the same.Because of their regular usage.For example,I increase the age of the user-1 by 2, and you will get the result likethe below image.hincr user-1 age 2After showing the native commands,we should turn to the usage of StackExchange.Redis.1 //hset hget hmset hmget2 db.HashSet("user-1", "name", "catcher");3 var user_1 = new HashEntry[2] { new HashEntry("age",18),new HashEntry("gender","male") };4 db.HashSet("user-1", user_1);56 Console.WriteLine("the name of user-1 is {0}",db.HashGet("user-1","name"));7 var user_1_fileds = new RedisValue[] { "name","age","gender" };8 var user_1_values = db.HashGet("user-1", user_1_fileds);9 foreach (var item in user_1_values)10 {11 Console.WriteLine(item);12 }1314 //hlen15 Console.WriteLine(string.Format("the number of filed in user-1 is {0}",db.HashLength("user-1")));1617 //hgetall18 var all = db.HashGetAll("user-1");19 foreach (var item in all)20 {21 Console.WriteLine(string.Format("the {0} of user-1 is {1}",item.Name,item.Value));22 }2324 //hdel25 db.HashDelete("user-1", "gender");26 var all_after_del = db.HashGetAll("user-1");27 foreach (var item in all_after_del)28 {29 Console.WriteLine(string.Format("the {0} of user-1 is {1}", item.Name, item.Value));30 }3132 //hexists33 Console.WriteLine(string.Format("gender {0} in the user-1", db.HashExists("user-1", "gender")?"is":"isn't"));34 Console.WriteLine(string.Format("gender {0} in the user-1", db.HashExists("user-1", "name") ? "is" : "isn't"));3536 //hkeys37 var keys = db.HashKeys("user-1");38 Console.WriteLine("the keys of user-1 are as follow:");39 foreach (var item in keys)40 {41 Console.WriteLine(item);42 }4344 //hvals45 var values = db.HashValues("user-1");46 Console.WriteLine("the values of user-1 are as follow:");47 foreach (var item in values)48 {49 Console.WriteLine(item);50 }5152 //hincrby53 Console.WriteLine(string.Format("after Increase user-1's age by 2,the age of user-1 is {0}",db.HashIncrement("user-1","age",2))); When you debug the codes,the results are as
Basic Tutorials of Redis(4) -Set
This post will introduce you to some usages of Set in Redis.The Set is a unordered set,it means that thedata was stored in the database randomly.And there are 15 commands you can use in Redis,the same as Hash.For storing the data to database,we can use the command sadd to finish the job.The sadd is very powerful,wecan not only use it to add a single value to the key,but also multi values.For example,I add 11 to the key namedset-1 at first.Laterly I add 12 ~15 too.So easy the command is.When you execute the sadd command , the clientwill return the amount of the set.sadd set-1 11sadd set-1 12 13 14 15 After executing the command sadd,it will return a integer to show us the amount of this set.But what can weknow the members of this set?We can use smembers to get all of the members in the set.smembers set-1There are two commands to help us to remove the members of the set.The spop command will remove a or multimember of the set randomly.As the following picture,I remove a member from the set-1 firstly,and then I remove twomembers from the set-1.At last,we will find that the set-1 only has 11 and 13.spop set-1spop set-1 2 srem,the second command of removing members from the set,can remove the members from the set by your ownideas,not randomly.I removed the last two members from the set-1 by this command.At this time,I want to get all of themembers of the set-1 ,you will get the information that the set is empty.srem set-1 11 13 When we are coding , the most things we do is to judge a member whether exists in the set.In Redis,you can do thisthing as well.The set-1 is empty now,I judge the member 11 whether exists in the set-11,it returns 0 meaning 11 is notthe member of the set.After adding members to this set,the second time to judge returns 1 meaning 11 is the member of set-1.sismember set-1 11 As all you know,we use the Property length or the method count to get how many members in the array by using C#.In Redis,we get the numbers of members in a set by using scard.scard set-1 The commands I will show you next needs at lease two sets,so I have to add another one.And you will be familiar withthe set opreation of Mathematical.Command sinter will return the command members both set-1 and set-2 contain.Commandsunion will return all of the members both set-1 and set-2 contian.Command sdiff will return the difference members from the sets.sinter set-1 set-2sunion set-1 set-2sdiff set-1 set-2sdiff set-2 set-1 We can store the result of the set opreation too.sinterstore inter-set set-1 set-2sunionstore union-set set-1 set-2sdiffstore diff-set-1 set-1 set-2sdiffstore diff-set-2 set-2 set-1 After showing the native commands,we should turn to the usage of StackExchange.Redis.//sadd smembersdb.SetAdd("set-1", 11);var set_1 = new RedisValue[4] {12,13,14,15 };db.SetAdd("set-1", set_1);Console.WriteLine("the members of the set-1 :");foreach (var item in db.SetMembers("set-1")){Console.WriteLine(item);}//spop sremConsole.WriteLine(string.Format("the value was poped is {0}", db.SetPop("set-1")));Console.WriteLine(string.Format("the value was poped is {0}", db.SetPop("set-1")));Console.WriteLine(string.Format("the value was poped is {0}", db.SetPop("set-1")));db.SetRemove("set-1", db.SetMembers("set-1"));Console.WriteLine(string.Format("amount of set-1 is {0}", db.SetMembers("set-1").Length));//sismemberConsole.WriteLine(string.Format("11 {0} the member of set-1",db.SetContains("set-1",11)?"is":"isn't"));var set_1_again = new RedisValue[5] {11, 12, 13, 14, 15 };db.SetAdd("set-1", set_1_again);Console.WriteLine(string.Format("11 {0} the member of set-1", db.SetContains("set-1", 11) ? "is" : "isn't"));//scardConsole.WriteLine(string.Format("amount of set-1 is {0}", db.SetLength("set-1")));var set_2 = new RedisValue[4] { 13, 14, 15,16 };db.SetAdd("set-2", set_2);//sinterConsole.WriteLine("the result of intersect:");foreach (var item in db.SetCombine(SetOperation.Intersect, new RedisKey[2] { "set-1", "set-2" })){Console.WriteLine(item);}// sunoinConsole.WriteLine("the result of union:");foreach (var item in db.SetCombine(SetOperation.Union, new RedisKey[2] { "set-1", "set-2" })){Console.WriteLine(item);}//sdiffConsole.WriteLine("the result of difference1:");foreach (var item in db.SetCombine(SetOperation.Difference, new RedisKey[2] { "set-1", "set-2" })){Console.WriteLine(item);}Console.WriteLine("the result of difference2:");foreach (var item in db.SetCombine(SetOperation.Difference, new RedisKey[2] { "set-2", "set-1" })){Console.WriteLine(item);}When you debug the codes,the results are as follow.  The next post of this series is the basic opreation of Sorted Set in Redis.
Basic Tutorials of Redis(5) - Sorted Set
The last post is mainly about the unsorted set,in this post I will show you the sorted set playing an importantrole in Redis.There are many command added after the version 2.8.9.OK,let's see the below picture firstly.Thereare 24 commands to handle the sorted set,the same as the string.Many commands are similar with the Set.Both of them are the set,the sorted set's member has a scoreplaying a important role on sorting.We can use the zadd to store the sorted set.The following example demonstratesthe usage of zadd.zadd set-1 4 azadd set-1 5 b 6 c 7 d 8 e 8 f 5 g 6 h 7 i 8 j 8 kFor learning how many elements in the set,and who are them ? we can use the command zrange.zrange set-1 0 -1zrange can also make us know the scores of the elements,we should open seletion the withscores to find out their scores.zrange set-1 0 -1 withscoresThere are another intresting commands to get the members.zrangebyscore can find out the members bytheir scores.For an instance,I want to find out the members' scores between 0 and 6,so I will use  zrangebyscore set-1 0 6  to finish this job.zrangebylex can find out the members by the lexicographical order when some of them are inthe same scores.Now I want to find out the members that order by lexicography when the score are the samewhile in the range (a,k],so using  zrangebylex set-1 (a [k  can easily do this job.We also can know the rank of a member.both Ascending and Descending.For ascending,we use zrank.For descendingwe use zrevrank .For example ,we want to know the member d's rank.zrank set-1 dzrevrank set-1 d There are also many command that we can use to remove the member from the set.Using zrem to remove one ormore members,Using zremrangebyrank to remove the members by their ranks.Using the zremrangebyscore to removethe members by their scores.Using the zremrangebylex to remove the members by their rank and lexicography.zrem set-1 azrem set-1 b czremrangebyrank set-1 0 1zremrangebyscore set-1 0 7zremrangebylex set-1 (e (jIf we want to know a member's score,we can use zscore to get its score.To get the score of member e,we use  zscore set-1 e .For learning how many members in the set by the range of score,we can use zcountto get the amount.To get the amount of the set by the score's range [0,10],we can use  zcount set-1 0 10 . Can we modify the scores of the members?Of course we can.Not only the exists member but also themember not in the set.If the member not exists in the set,Redis will store a new member to the set.Forexample,I want to modify the score of d which is not exists in the set.I will use  zincrby set-1 1 d  to finishthis easy job.And the result is that the set will has a new member with score 1.OK,thoes commands are what I want to show you for sorted set.Let's go on to see how StackExchange.RedisHandle the sorted set.1 //zadd2 db.SortedSetAdd("set-1", "a", 4);3 var set_1 = new SortedSetEntry[10]4 {5 new SortedSetEntry("b",5),6 new SortedSetEntry("c",6),7 new SortedSetEntry("d",7),8 new SortedSetEntry("e",8),9 new SortedSetEntry("f",8),10 new SortedSetEntry("g",5),11 new SortedSetEntry("h",6),12 new SortedSetEntry("i",7),13 new SortedSetEntry("j",8),14 new SortedSetEntry("k",8)15 };16 db.SortedSetAdd("set-1", set_1);1718 //zrange19 Console.WriteLine("rank by score ascending");20 foreach (var item in db.SortedSetRangeByRank("set-1", 0, -1, Order.Ascending))21 {22 Console.Write(item + " ");23 }24 Console.WriteLine("");25 foreach (var item in db.SortedSetRangeByRankWithScores("set-1"))26 {27 Console.WriteLine(string.Format("the {0} with score {1}", item.Element, item.Score));28 }29 //zrangebyscore30 Console.WriteLine("sorted by score");31 foreach (var item in db.SortedSetRangeByScore("set-1",0,6))32 {33 Console.Write(item + " ");34 }35 Console.WriteLine("");36 //zrangebylex37 Console.WriteLine("sorted by value");38 foreach (var item in db.SortedSetRangeByValue("set-1","a","z"))39 {40 Console.Write(item + " ");41 }42 Console.WriteLine("");43 //zrank44 Console.WriteLine(string.Format("d rank in {0} by ascending", db.SortedSetRank("set-1", "d", Order.Ascending)));45 Console.WriteLine(string.Format("d rank in {0} by descending", db.SortedSetRank("set-1", "d", Order.Descending)));4647 //zrem48 db.S
Basic Tutorials of Redis(6) - List
Redis's List is different from C#'s List,but similar with C#'s LinkedList.Sometimes I confuse with them.I expectthat you won't mix them and have a clear mind of them.There are 17 commands we can use in List. Push and pop are the base opreation of the linkediist,either as Redis's List.When we want to store thelist, lpush and rpush can help us to save the data.Both of them can save one or more values to the key.NowI add element 11 to the key named list-1,then add element 12 and 13 to this key.Here're the commands and result.lpush list-1 11lpush list-1 12 13 The code demonstrated above push the element from left to the right.As all we know,linkedlist has anontherway to push the elements.rpush is the another way.For example,I push the same elements to the key named list-2.Taking the following code and result.rpush list-2 11rpush list-2 12 13 By using those two commands to store the data,we don't know the Difference between them apparently.But whenyou select all of the elements,you will find out something.Using lrange can make us know the elements in the list.lrange list-1 0 -1lrange list-2 0 -1  The next picture explain the result clearly.You can combine the linkedlist's feature to think about the result.We also can insert an element before or after another element.Redis provides a command for us.For an instance,I insert 90 before 12 on list-1 ,and insert 80 after 12.You will get the following result.linsert list-1 before 12 90linsert list-1 after 12 80 Sometimes we may want to know how many elements in the list?Just as the length of a string.We use llen toget the length of the list.llen list-1We also can get the elements by their own index in the list.lindex list-1 2We also can modify the elements by their index.lset list-1 2 66The next time I will show you how to remove the elements from the list.There are three commands can helpus to remove elements.lpop will remove the leftmost element from the list.And the client will return the removed element.lpop list-1 rpop will remove the rightmost element from the list.And the client will return the removed element as well.rpop list-1 lrem will remove the count occurrences of elements equal to value.If count > 0,it will remove elements movingfrom head to tail.If count < 0,it will remove elements moving from tail to head.If count = 0,it will remove all elementsequal to value.lrem list-1 2 66 The following code demonastrates the basic usage of List in StackExchange.Redis.1 //lpush2 db.ListLeftPush("list-1", 11);3 var list_1 = new RedisValue[2] { 12,13};4 db.ListLeftPush("list-1", list_1);5 Console.WriteLine("after lpush:");6 foreach (var item in db.ListRange("list-1"))7 {8 Console.Write(item+" ");9 }10 Console.WriteLine("");11 //rpush12 db.ListRightPush("list-2", 11);13 var list_2 = new RedisValue[2] { 12, 13 };14 db.ListRightPush("list-2", list_1);15 Console.WriteLine("after rpush:");16 foreach (var item in db.ListRange("list-2"))17 {18 Console.Write(item + " ");19 }20 Console.WriteLine("");21 //linsert22 db.ListInsertBefore("list-1",12,90);23 Console.WriteLine("after linsert 90 before 12:");24 foreach (var item in db.ListRange("list-1"))25 {26 Console.Write(item + " ");27 }28 db.ListInsertAfter("list-1", 12, 80);29 Console.WriteLine("nafter linsert 80 after 12:");30 foreach (var item in db.ListRange("list-1"))31 {32 Console.Write(item + " ");33 }34 Console.WriteLine("");35 //llen36 Console.WriteLine(string.Format("the length of list-1 is {0}",db.ListLength("list-1")));37 //lindex38 Console.WriteLine(string.Format("the element in the second index is {0}", db.ListGetByIndex("list-1",2)));39 //lset40 db.ListSetByIndex("list-1", 2, 66);41 Console.WriteLine("after lset 66 from index 2:");42 foreach (var item in db.ListRange("list-1"))43 {44 Console.Write(item + " ");45 }46 Console.WriteLine("");47 //lpop48 Console.WriteLine(string.Format("lpop element {0}", db.ListLeftPop("list-1")) );49 //rpop50 Console.WriteLine(string.Format("rpop element {0}", db.ListRightPop("list-1")));51 //lrem52 db.ListRemove("list-1", 66,2);53 Console.WriteLine("after remove:");54 foreach (var item in db.ListRange("list-1"))55
Basic Tutorials of Redis(7) -Publish and Subscribe
This post is mainly about the publishment and subscription in Redis.I think you may subscribe some offiialaccounts on wechat,and when the authors published something new to their accounts,you will get them inyour wechat.The instance can make you understand this pattern easily.You will find there only 6 commandsfor publishment and subscription. This post introduces the basic usages as well.I will show you how to publish a message to a channel.publishis the command to publish sometings.I publish a channel named news-nba with the message nba.And the clientreturn me a integer 0.What is the meaning of the return value?It means that there is nobody subscribe this channel.publish news-nba nba I open a new client for subscriber.In this client,I subscribe the above channel news-nba.It will return somethingabout this channel.subscribe news-nba OK,let's publish a new message to the news-nba to see the changes in the both clients.After publishing lakers tothe news-nba channel,this client returns 1.It means that the channel has a subscriber.publish news-nba lakers Let's turn to the subscriber's client.You will find the message lakers was already in the client.So amazing it is.Opening the second client to subscribe this channel,and you will find something similar with the above example. publish a new message to the news-nba,it returns 2 meaning ...(you understand it)the both subscribers' client are as follows: All right,let's see the other commands of this feature in Redis.Seeing the sql first: select * from channels where channelname like 'news%' To execute this sql,you will get all of the channel whoes name start with news.Redis can also do that to help us subscibe many channels by matching their name vaguely.For example I want to subscribe all the channels of news.I will use psubscribe to do this job.psubscribe news-*Let's publish some message to the others channel started with news- to find out the changes.publish news-tech tech The subscriber's client will receive the message from the channel news-tech. Again!!Publishing a message to a new channel. As you can see,the client receives this message as well.The client of Redis can not show you the unsubscription.So turning to the StackExchange.Redis.Thefollowing code demonstrates the usage in C#.1 //publisher2 var sub = redis.GetSubscriber();3 //subscriber4 var sub2 = redis.GetSubscriber();56 sub.Publish("news-nba", "nba");7 sub.Publish("news-tech", "tech");89 //sub10 sub2.Subscribe("news-nba", (channel, value) =>11 {12 Console.WriteLine(string.Format("{0} has been published to {1}!",value,channel));13 });14 sub2.Subscribe("news-tech", (channel, value) =>15 {16 Console.WriteLine(string.Format("{0} has been published to {1}!", value, channel));17 });1819 sub.Publish("news-nba", "Lakers");20 sub.Publish("news-nba", "Kobe");2122 sub.Publish("news-tech", "lumia");2324 System.Threading.Thread.Sleep(2000);25 Console.WriteLine("unscribe the news-nba");26 sub2.Unsubscribe("news-nba");//unsub2728 sub.Publish("news-tech", "surface pro3");2930 System.Threading.Thread.Sleep(2000);31 sub.Publish("news-nba", "james");3233 System.Threading.Thread.Sleep(2000);34 Console.WriteLine("unscribe all channels");35 sub2.UnsubscribeAll();3637 sub.Publish("news-nba", "paul");38 sub.Publish("news-tech", "surface pro4");Debuging the code ,you will see the result as follow. The next post of this series is the basic opreation of transaction in Redis.Thanks for your reading.
Basic Tutorials of Redis(8) -Transaction
Data play an important part in our project,how can we ensure correctness of the data and prevent the datafrom error.In relational database, we are famillar with the usage of transaction.beginopreationscommit/rollbackBut there are some differences between the Redis and relational database.Let's introduce the commandsfirst.There are only 5 commands to use while you use the transaction in redis.I want to take an example to show you how to use the transaction.So here is the backgroud.There are threepersons and they want to buy the apple from the shop.The key for buying apple is the amount.For this example,I use the hash to store the infomation of the users and the products.I use the string to storethe totalmoney for the trade.The following picture shows this example's initial data. Let's start to finish this example.The first step is to initialize the data.hmset user-1 name tom money 200hmset user-2 name jack money 300hmset user-3 name paul money 500hmset product-1 name apple price 150 amount 2set totalmoney 0 Tom wants to buy the apple,so the product-1's amount should decrease by one,tom's money should decreaseby 150 and the totalmoney should increase by 150.The action buy includes three steps.We should check up theproduct-1 with buy action.watch product-1multihincrby product-1 amount -1hincrby user-1 money -150incrby totalmoney 150exec You will find that after the command  multi ,the commands before  exec  all return queued.It means that thosecommands store in a queue.After exec command,the client send this queue to the server and execute the commands.But this is a normal situation that the amount is not changed before the transaction execute.Now jack want to buy the apple , so does paul.And paul takes first,so jack can't buy the apple successfully!To showthis,we need another client to imitate paul takes first.Before execute the transaction of jack's action,we decrease theamount of product-1 to imitate paul buy the apple first.After executing the transaction of jack's action,the client returns(nil) meaning the transaction of jack's action executed fail and the three commands didn't execute too. This example shows the basic usage of transaction in redis.It is suitable for many questions.command  discard can help you to abort the current transaction.After executing the command discard.At this timeexecute the command exec will return an error.Because the multi didn't exists in this session. Now I will show you how to use the transaction in StackExchange.Redis.1 //Initiate the data2 db.HashSet("user-1", new HashEntry[2] { new HashEntry("name", "tom"), new HashEntry("money", 200) });3 db.HashSet("user-2", new HashEntry[2] { new HashEntry("name", "jack"), new HashEntry("money", 300) });4 db.HashSet("user-3", new HashEntry[2] { new HashEntry("name", "paul"), new HashEntry("money", 500) });5 db.HashSet("product-1", new HashEntry[3] { new HashEntry("name", "apple"), new HashEntry("price", 150),new HashEntry("amount", 2) });6 db.StringSet("totalmoney", 0);78 //multi9 var tran = db.CreateTransaction();10 //watch11 tran.AddCondition(Condition.HashEqual("product-1", "amount", "2"));1213 tran.HashDecrementAsync("product-1", "amount");14 tran.HashDecrementAsync("user-1", "money", 150);15 tran.StringIncrementAsync("totalmoney", 150);1617 Console.WriteLine(string.Format("execute the transaction {0}!", tran.Execute() ? "Y" : "N"));1819 //multi20 var tran2 = db.CreateTransaction();21 //watch22 tran.AddCondition(Condition.HashEqual("product-1", "amount", "1"));2324 tran.HashDecrementAsync("product-1", "amount");25 tran.HashDecrementAsync("user-2", "money", 150);26 tran.StringIncrementAsync("totalmoney", 150);2728 //paul take first29 db.HashDecrement("product-1", "amount");3031 Console.WriteLine(string.Format("execute the transaction {0}!", tran.Execute() ? "Y" : "N")); Debuging the code ,you will see the result as follow.The next post of this series is the RedisHelper.Thanks for your reading 
Basic Tutorials of Redis(9) -First Edition RedisHelper
After learning the basic opreation of Redis,we should take some time to summarize the usage.And I wrote my first edition RedisHelper.Here is the code:The Interface IRedis:1 public interface IRedis2 {3 ITransaction GetTransaction(int db = 0, bool isRead = false);45 #region String6 #region get7 /// <summary>8 /// get the string value9 /// </summary>10 /// <param name="key">the key of value</param>11 /// <param name="flag">behaviour</param>12 /// <param name="db">index of database</param>13 /// <returns></returns>14 RedisValue Get(string key, CommandFlags flag = CommandFlags.None, int db = 0);15 /// <summary>16 /// get the string value(Asynchronous)17 /// </summary>18 /// <param name="key">the key of value</param>19 /// <param name="flag">behaviour</param>20 /// <param name="db">index of database</param>21 /// <returns></returns>22 Task<RedisValue> GetAsync(string key, CommandFlags flag = CommandFlags.None, int db = 0);23 /// <summary>24 /// get the entity by deserialization25 /// </summary>26 /// <param name="key">the key of value</param>27 /// <param name="flag">behaviour</param>28 /// <param name="db">index of database</param>29 /// <returns></returns>30 T Get<T>(string key, CommandFlags flags = CommandFlags.None, int db = 0);31 /// <summary>32 /// get the entity by deserialization(Asynchronous)33 /// </summary>34 /// <param name="key">the key of value</param>35 /// <param name="flag">behaviour</param>36 /// <param name="db">index of database</param>37 /// <returns></returns>38 Task<T> GetAsync<T>(string key, CommandFlags flags = CommandFlags.None, int db = 0);39 #endregion4041 #region set42 /// <summary>43 /// set value to key44 /// </summary>45 /// <param name="key">the key</param>46 /// <param name="value">the value of the key</param>47 /// <param name="expiry">time to expiry</param>48 /// <param name="when">when this operation should be performed</param>49 /// <param name="flags">behaviour</param>50 /// <param name="db">index of database</param>51 /// <returns></returns>52 RedisValue Set(string key, string value, TimeSpan? expiry = default(TimeSpan?), When when = When.Always, CommandFlags flags = CommandFlags.None, int db = 0);53 /// <summary>54 /// set value to key(Asynchronous)55 /// </summary>56 /// <param name="key">the key</param>57 /// <param name="value">the value of the key</param>58 /// <param name="expiry">time to expiry</param>59 /// <param name="when">when this operation should be performed</param>60 /// <param name="flags">behaviour</param>61 /// <param name="db">index of database</param>62 /// <returns></returns>63 Task<bool> SetAsync(string key, string value, TimeSpan? expiry = default(TimeSpan?), When when = When.Always, CommandFlags flags = CommandFlags.None, int db = 0);64 #endregion6566 #region mget67 /// <summary>68 /// get multi values69 /// </summary>70 /// <param name="keys">the keys of the values</param>71 /// <param name="flags">behaviour</param>72 /// <param name="db">index of database</param>73 /// <returns></returns>74 IList<RedisValue> MGet(List<RedisKey> keys, CommandFlags flags = CommandFlags.None, int db = 0);75 /// <summary>76 /// get multi values(Asynchronous)77 /// </summary>78 /// <param name="keys">the keys of the values</param>79 /// <param name="flags">behaviour</param>80 /// <param name="db">index of database</param>81 /// <returns></returns>82 Task<RedisValue[]> MGetAsync(List<RedisKey> keys, CommandFlags flags = CommandFlags.None, int db = 0);83 #endregion8485 #region mset86 /// <summary>87 /// set multi values88 /// </summary>89 /// <param name="kvs">key-values<
Redis简单案例(一) 网站搜索的热搜词
对于一个网站来说,无论是商城网站还是门户网站,搜索框都是有一个比较重要的地位,它的存在可以说是为了让用户更快、更方便的去找到自己想要的东西。对于经常逛这个网站的用户,当然也会想知道在这里比较“火”的东西是什么,这个时候我们搜索框上的热词就起作用了。其实我觉得这一块的完善会对这个网站带来许多益处。可能现在比较普遍的做法是把这些相应的信息存到我们的关系型数据库中,如sql server 和 oracle。方便起见的话,可能每搜索一次就往表里插一次数据,用的时候要先统计数据,统计完后再排序,最后才展示。这种情况下,如果搜索量很大的话,表的膨胀速度就会非常快,如果sql没写好,查询的时候估计会。。相比Redis,同等条件下,Redis的速率肯定是会较优,毕竟是从内存中拿出来的。 下面我们就用.NET Core和StackExchange.Redis来做一下这个简单的案例。案例用到的一些相关技术和说明:技术说明 .NET Core网站嘛,你懂的。有事没事用Core写写Demo,免得跟不上发展的脚步。Redis存储搜索词,用了主从的模式,主写从读Jquery-ui主要是用了里面的autocomplete开始正题之前,我们要确定用Redis中的那种数据结构,五种之中比较合适的应该是SortedSet,我们可以用成员来作为搜索词,成员分数来作为搜索词的搜索次数,这样就可以很方便的来操作相关的数据了。下面开始正题:我们在开始的时候需要初始化一下数据。这里就直接在第一次运行的时候初始化。用上流水线的技术,速度还是很可观的。初始化了70个搜索关键词(NBA球星),然后用随机数作为关键字的下标,去随机给这个关键字加1分。这个分数就是这个关键字被搜索的次数。下面来看看初始化的相关代码:1 public IActionResult Index()2 {3 //keys4 IList<string> keys = new List<string>()5 {6 "kobe","johnson","jabbar","west","o'neal","baylor","mccann","worthy","gasol","chamberlain",7 "fisher","odom","bynum","horry","rambis","riley","clarkson","Williams","young","Russell",8 "ingram","randle","nance","brown","deng","yi","ariza","artest","walton","vujacic",9 "james","paul","curry","park","yao","kevin","wade","rose","popovich","leonard",10 "aldridge","ginobili","duncan","lavine","rubio","garnett","wiggins","westbrook","durant","ibaka",11 "nowitzki","pierce","crawford","love","smith","iguodala","barnes","green","thompson","harden",12 "lillard","mccollum","lin","jackson","nash","stoudemire","whiteside","dragic","Howard","batum"13 };1415 //init16 Random random = new Random();17 var tran = _redis.GetTransaction();18 for (int i = 0; i < 1000000; i++)19 {20 tran.SortedSetIncrementAsync(_searchKey, keys[random.Next(0, 70)], 1);21 }22 tran.ExecuteAsync();2324 return View();25 }这里是在加载这个页面的时候就把这些热搜词存进Redis中,这样我们才能有数据来演示啊。这里还用到了一个非事务型的流水线。就是把要操作的指令存放到一个队列中,最后把这个队列扔到服务端去执行,这样就有效的减少了不必要的网络传输,同时也提高了执行速度。好了,初始数据有了,下面要做的就是用户在搜索的时候,根据用户的输入去匹配搜索次数多的关键字,展示最Hot的10个,当然这个展示的个数是随我们定的,最后可以考虑把这个放到我们的配置文件中去,甚至是放到数据库中,为的是灵活和方便维护。下面是我们在后台的处理逻辑:1 public IActionResult GetHotKey(string key="")2 {3 if (string.IsNullOrEmpty(key))4 {//default5 var res = _redis.ZRevRange(_searchKey, 0, 9);6 var list = (from i in res select i.ToString());7 return Json(list);8 }9 else10 {//by user input11 var res = _redis.ZRevRange(_searchKey, 0, -1);12 var list = (from i in res select i.ToString()).Where(x => x.Contains(key)).Take(10).ToList();13 return Json(list);14 }15 }对于查询的处理是非常的简单的,用户不小心输入空格的时候就展示最热的10个关键词,如果用户有输入的话,就把关键词中包含用户输入的展示出来。那么我们在页面上要做些什么呢?下面就是我们演示用的搜索框。1 <div class="row">2 <div class="col-md-6 col-md-offset-4" style="padding-top:50px;">3 <input id="key" name="key" placeholder="search" class="form-control col-md-4">4 <button class="btn btn-primary" type="button" id="searchSubmit">Search</button>5 <div id="result"></div>6 </div>7 </div>相应的js是写到 scripts 这个section中的,js的话是比较简单的就是用ajax去请求我们要展示的数据。更多的应该是jquery-ui的api问题,大家也可以换用自己比较熟悉的组件,举一反三即可。下面是autocomplete的api  ,如果有需要可以去看一下。1 @section scripts{2 <script type="text/javascript">3 $(function () {4 //show hot keyword5 $("#key").autocomplete({6 source: function (request, response) {7 $.ajax({8 url: "@Url.Action("GetHotKey", "Auto")",9 dataType: "json",10 data: {11 key: request.term12 },13 success: function (data) {14 response(data);15 }16 });17 },18 });19 </script>20 }到这里,用户搜索前的操作,我们是做好了,下面先来看一下效果。那么用户点击了搜索之后我们要做些什么处理呢?无论是新的关键字还是已有的关键字,我们都是要做处理的,当然redis中zincrby命令来处理这个是十分合适的,存在的就把分数加1,不存在就创建一个分数为1的成员。下面是搜索时的后台逻辑处理:1 [HttpPost]2 public IActionResult SetHotKey(string key)3 {4