Redis详解

互联网架构的演变历程 :
前面我们讲了对于应用架构的演变,但是我们虽然使得更好的操作代码了
但是对于的数据我们要如何处理呢,当请求越来越多,那么对应的数据也就越来越多,所有我们也就需要优化数据库的操作
第1阶段:
数据访问量不大,简单的架构即可搞定

第2阶段
数据访问量大,使用缓存技术来缓解数据库的压力(就如mybatis的缓存类似,这里是cache的作用,下面图片中的cache)
不同的业务访问不同的数据库,双管齐下

第3阶段
主从读写分离
之前的缓存确实能够缓解数据库的压力,但是写和读都集中在一个数据库上,压力又来了(下面图片,只显示了一个主数据库)
且实际上也有指向从库的,只是没有显示(这里指向从库的是主从关系)
一个数据库负责写,一个数据库负责读,分工合作,愉快
让master(主数据库)来响应事务性(增删改)操作,让slave(从数据库)来响应非事务性(查询)操作
然后再采用主从复制来把master上的事务性操作(对应的数据)同步到slave数据库中
mysql的master/slave就是网站的标配

第4阶段
mysql的主从复制,读写分离的基础上,mysql的主库开始出现瓶颈(被多次操作的数据库)
由于MyISAM(或者说mysql)使用表锁,所以并发性能特别差
分库分表开始流行,mysql也提出了表分区,虽然不稳定,但我们看到了希望
开始吧,mysql集群

Redis入门介绍:
下面的介绍,了解即可
互联网需求的3高
高并发,高可扩,高性能
Redis 是一种运行速度很快,并发性能很强,并且运行在内存上的NoSql(not only sql)数据库
NoSQL数据库 和 传统数据库 相比的优势:
NoSQL数据库无需事先为要存储的数据建立字段,随时可以存储自定义的数据格式
而在关系数据库里,增删字段是一件非常麻烦的事情,如果是非常大数据量的表
增加字段简直就是一个噩梦(因为每个数据都要操作的)
Redis的常用使用场景:
缓存:毫无疑问这是Redis当今最为人熟知的使用场景,在提升服务器性能方面非常有效
一些频繁被访问的数据,经常被访问的数据如果放在关系型数据库,每次查询的开销都会很大
而放在redis中,因为redis 是放在内存中的可以很高效的访问
排行榜:在使用传统的关系型数据库(mysql oracle 等)来做这个事儿,非常的麻烦
而利用Redis的SortSet(有序集合)数据结构能够简单的搞定
计算器/限速器:利用Redis中原子性的自增操作,我们可以统计类似用户点赞数、用户访问数等
这类操作如果用MySQL,频繁的读写会带来相当大的压力
限速器比较典型的使用场景是限制某个用户访问某个API的频率,常用的有抢购时,防止用户疯狂点击带来不必要的压力
好友关系:利用集合的一些命令,比如求交集、并集、差集等,可以方便搞定一些共同好友、共同爱好之类的功能
简单消息队列:除了Redis自身的发布/订阅模式,我们也可以利用List来实现一个队列机制
比如:到货通知、邮件发送之类的需求,不需要高可靠,但是会带来非常大的DB(数据库)压力,完全可以用List来完成异步解耦
Session共享:以jsp为例,默认Session是保存在服务器的文件中,如果是集群服务
同一个用户过来可能落在不同机器上,这就会导致用户频繁登陆
采用Redis保存Session后,无论用户落在那台机器上都能够获取到对应的Session信息
Redis/Memcache/MongoDB对比(了解即可):
都是nosql数据库的著名代表
Redis和Memcache:
Redis和Memcache都是内存数据库。不过memcache还可用于缓存其他东西,例如图片、视频等等
memcache 数据结构单一kv,redis 更丰富一些,还提供 list,set, hash 等数据结构的存储,有效的减少网络 IO 的次数
虚拟内存–Redis当物理内存用完时,可以将一些很久没用到的value交换到磁盘
存储数据安全–memcache挂掉后,数据没了(没有持久化机制),redis可以定期保存到磁盘(持久化)
灾难恢复–memcache挂掉后,数据不可恢复,redis数据丢失后可以通过RBD或AOF恢复
Redis和MongoDB:
redis和mongodb并不是竞争关系,更多的是一种协作共存的关系。
mongodb本质上还是硬盘数据库,在复杂查询时仍然会有大量的资源消耗
而且在处理复杂逻辑时仍然要不可避免地进行多次查询
这时就需要redis或Memcache这样的内存数据库来作为中间层进行缓存和加速
比如在某些复杂页面的场景中,整个页面的内容如果都从mongodb中查询,可能要几十个查询语句,耗时很长
如果需求允许,则可以把整个页面的对象缓存至redis中,定期更新,这样mongodb和redis就能很好地协作起来
分布式数据库CAP原理:
CAP简介:
传统的关系型数据库事务具备ACID:
A:原子性,每个事务都是一个整体,不可以再拆分,事务中的所有SQL
要么都执行成功,要么都执行失败(因为回滚或者失败报错)
C:一致性,事务在执行之前,数据库的状态,与事务执行之后的状态要保持一致
I:独立性,事务与事务之间不应该相互影响,执行时要保证隔离状态
D:持久性,一旦事务执行成功,对数据的修改是持久的
分布式数据库的CAP:
C(Consistency):强一致性
“all nodes see the same data at the same time”(中文意思:所有节点在同一时间看到相同的数据),这个节点可以看成服务器
只是与zookeeper的节点(不是服务器),一样的操作,这里是服务器
即更新操作成功并返回客户端后,所有节点在同一时间的数据完全一致(而不会得到旧值,得到旧值的是弱一致,但是最终也会一致,一般zookeeper不是这样,他是C,而正是因为最终会一致,所以可以将强一致和弱一致统称为一致性,有时候可以认为强一致代表锁住的意思,当然,随着时间的推移,我们只是将是否锁称为强或者弱而已,所以这个C一般都是这两个的总称,然后在细化,如果以后出现redis不锁了,那么就是弱了,那么也就不是C了,而是A了,dang’r当然,一般不是C的都是A,这是基本的,除非你不操作同步,但是大多数都会,所以不考虑),这就是分布式的一致性
一致性的问题在并发系统中不可避免,对于客户端来说,一致性指的是并发访问时更新过的数据如何获取的问题
从服务端来看,则是更新如何复制分布到整个系统,以保证数据最终一致
A(Availability):高可用性
可用性指"Reads and writes always succeed"(中文意思:读写始终成功),即服务一直可用,而且要是正常的响应时间
好的可用性主要是指系统能够很好的为用户服务,不出现用户操作失败或者访问超时等用户体验不好的情况
P(Partition tolerance):分区容错性
即分布式系统在遇到某节点或网络分区故障时,仍然能够对外提供满足一致性或可用性的服务
分区容错性要求能够使应用虽然是一个分布式系统,而看上去却好像是在一个可以运转正常的整体
比如现在的分布式系统中有某一个或者几个机器宕掉了,其他剩下的机器还能够正常运转满足系统需求
对于用户而言并没有什么体验上的影响
CAP理论:
CAP理论提出就是针对分布式数据库环境的,所以,P这个属性必须容忍它的存在,而且是必须具备的
因为P是必须的,那么我们需要选择的就是A和C
大家知道,在分布式环境下,为了保证系统可用性,通常都采取了复制的方式,避免一个节点损坏,导致系统不可用
那么就出现了每个节点上的数据出现了很多个副本的情况,而数据从一个节点复制到另外的节点时需要时间和要求网络畅通的
所以,当P发生时,也就是无法向某个节点复制数据时(需要时间复制或者无法复制),这时候你有两个选择:
选择可用性 A,此时,那个失去联系的节点依然可以向系统提供服务(可读可写)
不过它的数据就不能保证是同步的了(失去了C属性)
选择一致性C,为了保证数据库的一致性,我们必须等待失去联系的节点恢复过来,在这个过程中
那个节点是不允许对外提供服务的(不可读和不可写),这时候系统处于不可用状态(失去了A属性)
最常见的例子是读写分离,某个节点负责写入数据,然后将数据同步到其它节点,其它节点提供读取的服务
当两个节点出现通信问题时:
你就面临着选择A(继续提供服务,但是数据不保证准确,因为读取的不是更新的)
选择C(用户处于等待状态,一直等到数据同步完成),上面选择一个,其他的一个属性基本不可选择
那么你可能会有疑问:为什么偏偏只能选择一个呢,看如下具体的说明:
在上面的例子中,假设A服务器操作写入,B服务器操作读取,他们需要同步,那么由于我们必须支持P,且由于是分布式系统
那么其中某个服务器,挂掉,基本也就会影响该服务器的功能
而由于这个只是操作数据库的服务器,对应操作该数据库的页面可能会受到影响,对应的数据可能没有
而对其他页面基本不会出现太大的影响,即基本可以使得用户有好的体验,只是对应的操作可能会有所改变
而不是数据库的,基本直接操作页面,而不是对应数据,如应用的架构演变
那么在这个基础上,首先分析问题
以程序的方向来看(挂掉和不可连接需要解释这个方向,延时不需要,因为延时的服务器是可以访问的
所以无论分开还是一起,都是一样的结果,而挂掉和不可连接却不是,所以延时不需要,而挂掉和不可连接需要,挂掉也可以说是宕机)
那么写入和复制同步是一起的,不以程序的方向看,则是分开(即看下面的等待恢复):
假设B服务器挂掉或者由于延时或者不可连接
以挂掉为例子(不可连接差不多一样,所以就不解释了):
那么首先我们选择C(保持一致性)
那么由于必须一致,所以我们在写入时,由于同步不了,或者设置不了B服务器数据,那么就会写入失败或者等待恢复,整体失败
而正是写入失败或者等待恢复,那么就违背了A(读写始终成功)
相反的,如果选择A,即写入成功
那么也就违背了C(保持一致性)
虽然相当于没有写入成功或者等待恢复,但为了高可用,一般会显示写入成功
对于一致性来说,那么就是不一致的
以延时为例子(由于不是挂掉和不可连接,那么就没有程序的方向了):
选择C(保持一致性),那么必然要等待同步完成,才可访问B,即外部不能访问(等待同步),导致违背了A(读写始终成功)
选择A(读写始终成功,即高可用),那么必然会在同步时,读取的数据与写入的数据不一致,导致违背了C(保持一致性)
所以C和A一般是不能共存的,只能选择其中一个
当然并不是必须选择一个,可以分开操作的,如可以一致性后,再高可用,虽然那一瞬间不能高可用
单点集群除外,单点集群,一般只会操作一个数据库,那么也就不分一致性和可用性的,因为再怎么一致,反正只有一个数据库
CAP总结:
分区是常态,不可避免,三者不可共存
可用性和一致性是一对冤家
一致性高,可用性低
一致性低,可用性高
因此,根据 CAP 原理将 NoSQL 数据库分成了满足 CA 原则
不分离,所以基本没有对应服务器挂掉或者由于延时,那么操作的数据库是一个,也就当然的是一致性和可用性的整体了
满足 CP 原则和满足 AP 原则三 大类:
CA - 单点集群,满足一致性,可用性的系统,通常在可扩展性上不太强大
CP - 满足一致性,分区容忍性的系统,通常性能不是特别高
AP - 满足可用性,分区容忍性的系统,通常可能对一致性要求低一些
介绍已经说明完毕,接下来开始真正的学习
下载与安装:
下载:
redis官网:http://www.redis.net.cn/

图形工具:https://redisdesktop.com/download

如果需要免费的,那么下载地址如下:
链接:https://pan.baidu.com/s/1JXLHZ17a8KYvOUX8uCfxCw
提取码:alsk
该工具,若是对应的连接没有的话,等待一会就会有提示的(一直加载中),具体的流程,在你下载好之后,很容易知道的
安装:
虽然可以在安装在windows操作系统,但是官方不推荐,所以我们一如既往的安装在linux上
上传下载好的redis的tar.gz包,并解压:
tar -zxvf redis-5.0.4.tar.gz #这里下载的就是5.0.4版本
安装gcc(必须有网络),因为Redis 是使用 C 语言编写的:
yum -y install gcc
忘记是否安装过,可以使用 gcc -v 命令查看gcc版本,如果没有安装过,会提示命令不存在
进入redis目录,进行编译(redis自带Makefile文件,所以可以直接进行编译,而不用我们生成Makefile文件):
make
编译之后,开始安装:
make install
#默认在/usr/local/bin/里面,起来会多出了对应redis命令,这些命令基本是全局的,即任意目录下都可直接使用
安装后的操作
后台运行方式:
redis默认不会使用后台运行,即当你启动redis时(在bin目录下,使用redis-server命令,启动)
他会占用当前界面,所以也就不能在对应的命令行那里操作了,当然你也可以在开一个窗口使得可以操作命令行
但对于单个窗口来说是不可以操作的
而你使用ctrl+c退出时,也顺便退出redis了,即如果你需要,修改配置文件redis.conf的对于配置变成daemonize yes
原来是no,现在改成yes,即使得他后台运行,即可后台运行(后台运行可以说是不占用当前界面的运行)
当你后台服务启动的时候,会写成一个进程文件运行
vim /opt/redis-5.0.4/redis.conf #找到自己对应redis目录下的redis.conf,我这里是解压在/opt/目录下
#使用底行模式的搜索找到对应配置,即/daemonize就可以找到
daemonize yes #改成这样
以配置文件的方式启动:
cd /usr/local/bin
redis-server /opt/redis-5.0.4/redis.conf
#平常的,直接redis-server,是默认操作daemonize no,即在没有修改配置文件之前,无论是直接的还是根据配置文件的
#就是会占用当前界面,而修改后,就需要配置文件的启动了,因为直接的默认daemonize no
#可以发现,虽然我们进行了安装,但是原来的目录还是有存放对应有用的信息的
查看是否启动成功,即端口占用
netstat -lntp | grep 6379
关闭数据库:
单实例关闭:
redis-cli shutdown
多实例关闭(用来关闭redis集群的):
redis-cli -p 6379 shutdown
常用操作:
检测6379端口是否在监听:
netstat -lntp | grep 6379
端口为什么是6379:
6379在是手机按键(9键)上MERZ对应的号码
而MERZ取自意大利歌女Alessia Merz的名字
MERZ长期以来被antirez(redis作者)及其朋友当作愚蠢的代名词
检测后台进程是否存在:
ps -ef|grep redis
连接redis并测试:
redis-cli #执行后,就进行连接,对应的redis客户端连接
#而redis-cli shutdown虽然字面意思是关闭redis客户端,但实际上是关闭了redis,即顺便关闭了客户端
ping #在出现的命令行里,执行ping(请求一下),若出现了PONG,则说明了确定是连接成功的(测试)
设置数据和获取数据:
# 保存数据
set k1 china #保存k1字段值为china,这个数据必须设置,否则报错,第一次相当于添加
#当再次进行操作时,就是重新设置,相当于修改
#且无论是否加引号,都默认引号里面的数据,但不能少引号以及不是同一个引号,否则报错
# 获取数据
get kl #获取china数据,一般由""包括,如"china"(而git则默认消除""),而没有字段的则返回(nil),相当于空值
#若是有两个引号的,则都默认加上"",使得不是一个引号里面的数据(包括引号了)
测试性能:
先 ctrl+c(快捷键)或者输入exit,退出redis客户端(自带的redis客户端):
redis-benchmark
执行命令后,命令不会自动停止,需要我们手动ctrl+c停止测试
会出现下面的结果(我这里是如下):
[root@localhost bin]# redis-benchmark
====== PING_INLINE ======100000 requests completed in 1.80 seconds   # 1.8秒(这个基本是会变的,除非运行特别好)处理了10万个请求,性能一般要看笔记本当前的配置高低# 可能也会受程序影响笔记本性能50 parallel clients3 bytes payloadkeep alive: 1
87.69% <= 1 milliseconds
99.15% <= 2 milliseconds
99.65% <= 3 milliseconds
99.86% <= 4 milliseconds
99.92% <= 5 milliseconds
99.94% <= 6 milliseconds
99.97% <= 7 milliseconds
100.00% <= 7 milliseconds
55524.71 requests per second   # 每秒处理的请求数量,该数量每次的测试基本会不同(除非运气特别好)
#上面两个注释的结果基本对应推断不同,因为可能每时每刻的请求可能多,或者少
默认16个数据库:
vim /opt/redis-5.0.4/redis.conf
#使用底行模式,输入/database,找到对应配置,即
databases 16 #默认16个数据库,其中编号从0开始,到15(数据库数量-1),即
#我们在客户端里进行操作数据时,默认一开始是0号数据库,可以通过如下命令,进行数据库的切换
select 16 #切换到16号,数据库,出现(error) ERR DB index is out of range 数据库的下标超出了范围
测试:
127.0.0.1:6379> get k1             # 查询k1
"china" #前面操作过的
127.0.0.1:6379> select 16         # 切换16号数据库
(error) ERR DB index is out of range   # 数据库的下标超出了范围
127.0.0.1:6379> select 15         # 切换15号数据库
OK
127.0.0.1:6379[15]> get k1       # 查询k1
(nil)
127.0.0.1:6379[15]> select 0     # 切换0号数据库
OK
127.0.0.1:6379> get k1           # 查询k1
"china"
数据库键的数量(在客户端下操作):
dbsize
#查询的是键的数量,一般我们操作set o h,时,会加一个键,即o键,则对应数量增加1,redis一开始一般默认0个键
keys * #查询有那些键
redis在linux支持命令补全(tab),对应命令补全后,基本都是大写,是redis的操作
清空数据库:
清空当前库(当前的编号数据库):
flushdb
清空所有(默认16个,若有更多的库,则自然清空更多的库)库,慎用:
flushall
模糊查询(key) :
模糊查询keys命令,有三个通配符:
*:通配任意多个字符,当然包括0个字符,与mysql的%的模糊查询类似
查询所有的键:
keys *
模糊查询k开头,后面随便多少个字符:
keys k*
模糊查询e为最后一位,前面随便多少个字符:
keys *e
双 * 模式,匹配任意多个字符:查询包含k的键:
keys *k*
?:通配单个字符:
模糊查询k字头,并且匹配一个字符:
keys k? #如kj,但是kjj不可以,只能是一个字符
你只记得第一个字母是k,他的长度是3:
keys k??
[]:通配括号内的某一个字符:
记得其他字母,就第二个字母可能是a或e:
keys r[ae]dis #根据[]里进行依次匹配,基本可以写很多个
当然redis的一般也有对应的正则表达式来操作,具体可以百度
键(key):
exists key:判断某个key是否存在
测试:
127.0.0.1:6379> exists k1
(integer) 1   # 返回1,就存在
127.0.0.1:6379> exists y1
(integer) 0   # 返回0,就不存在
move key db:移动(剪切,粘贴)键到几号库:
测试:
127.0.0.1:6379> move x1 8 # 将x1移动到8号库
(integer) 1   # 返回1,则移动成功
127.0.0.1:6379> exists x1 # 查看当前库中是否存在x1
(integer) 0   # 不存在(因为已经移走了)
127.0.0.1:6379> select 8   # 切换8号库
OK #操作成功
127.0.0.1:6379[8]> keys * # 查看当前库中的所有键
#[8]代表对应编号数据库,而没有则默认为0编号,一开始一般就默认是0编号
1) "x1"
ttl key:查看键还有多久过期(-1永不过期,新创建的键,默认是永不过期的,即返回的结果是-1,-2已过期)
即time to live(中文意思:生命周期), 还能活多久:
测试:
127.0.0.1:6379[1]> keys *
1) "asd"
127.0.0.1:6379[1]> ttl asd #查询是否过期
(integer) -1 #永不过期
expire key 秒:为键设置过期时间(生命倒计时):
测试:
127.0.0.1:6379[3]> set k1 v1 # 保存k1,这里只有这一个
OK #操作成功
127.0.0.1:6379[3]> ttl k1 # 查看k1的过期时间,默认为-1,即永不过期,除非设置了
(integer) -1 #永不过期
127.0.0.1:6379[3]> expire k1 10 # 设置k1的过期时间为10秒(10秒后自动销毁)
(integer) 1 #设置成功
127.0.0.1:6379[3]> ttl k1 # 等待一下,然后查看k1的过期时间,发现还有7秒过期
#这个等待和下面的等待看你决定等待多久
(integer) 7 #还有7秒过期
127.0.0.1:6379[3]> ttl k1 # 再等待一下,然后查看k1的过期时间,发现还有5秒过期
(integer) 5 #还有5秒过期
127.0.0.1:6379[3]> ttl k1 # 再等待一下,然后查看k1的过期时间,发现已经过期了
(integer) -2 #已过期
127.0.0.1:6379[3]> get k1 # 这时看一看k1是否存在
(nil) #发现不存在了
127.0.0.1:6379[3]> keys * #再看一看对应的键
(empty list or set) #若有其他键,自然会显示,但是在这之前只有k1这一个键,所以显示这个提示
#发现没有键了(因为在这之前只有一个键),即不存在对应键了,相当于删除或者说从内存中销毁了对应键
type key:查看键的数据类型:
测试:
127.0.0.1:6379[9]> set kk jj #设置或保存键和对应值
OK #操作成功
127.0.0.1:6379[9]> keys * #查看所有的键
1) "kk"
127.0.0.1:6379[9]> get kk #获取对应键的值
"jj"
127.0.0.1:6379[9]> type kk #上面的命令和返回结果前面有说明过,就不再说明了
string # kk的数据类型是会string字符串,若没有对应的kk,一般会返回none
使用Redis:
五大数据类型:
操作文档:http://redisdoc.com/
字符串String:
set,get,del,append,strlen(这一组命令):
测试:
127.0.0.1:6379> set k1 v1 # 保存数据
OK
127.0.0.1:6379> set k2 v2 # 保存数据
OK
127.0.0.1:6379> keys *
1) "k1"
2) "k2"
127.0.0.1:6379> del k2 # 删除数据k2,也可以指定多个,如del k2 k1,删除两个,那么返回就是2
(integer) 1 #返回1(删除1个),删除成功
127.0.0.1:6379> keys *
1) "k1" #没有k2了
127.0.0.1:6379> get k1 # 获取数据k1
"v1"
127.0.0.1:6379> append k1 abc # 往k1的值追加数据abc
(integer) 5 # 返回值的长度(字符数量)
127.0.0.1:6379> get k1
"v1abc" #正好是5个字符
127.0.0.1:6379> strlen k1 # 返回k1值的长度(字符数量)
(integer) 5 #返回5个字符
incr,decr,incrby,decrby(这一组命令):加减操作,操作的必须是数字类型
incr:意思是increment,增加
decr:意思是decrement,减少
测试:
127.0.0.1:6379> set k1 1 # 初始化k1的值为1
OK
127.0.0.1:6379> incr k1 # k1自增1(相当于java的k1++)
(integer) 2 #返回操作后的数,注意k1需要是数字类型(也就是数字字符串),否则报错
127.0.0.1:6379> incr k1
(integer) 3
127.0.0.1:6379> get k1
"3"
127.0.0.1:6379> decr k1 # k1自减1(相当于java的k1--),#注意:一般这样的redis内部的加减操作是原子的(这里的),也就是自带的操作锁的功能,这是redis的原因,至于其他的的操作,通常也是如此(并不一定),具体可以百度查看
#一般incr、incrby、decr、decrby都是原子的,一般来说,redis在设置数据这一块,必然是只能允许一人进入(任何一人进入的都是锁的操作,也就是单个线程操作,简称单线程),由于多路复用,那么其他像读取这一块,一般可以多人进入
(integer) 2 #返回操作后的数据
127.0.0.1:6379> decr k1
(integer) 1
127.0.0.1:6379> get k1
"1"
127.0.0.1:6379> incrby k1 3
# k1自增3(相当于java的k1+=3)
#只能是整数,小数或者其他数(如字母)以及不完整数或者不合理数(如06,5.)不可,否则报错
(integer) 4 #返回操作后的数据
127.0.0.1:6379> get k1
"4"
127.0.0.1:6379> decrby k1 2
# k1自减2(相当于java的k1-=3)
#只能是整数,小数或者其他数(如字母)以及不完整数或者不合理数(如06,5.)不可,否则报错
(integer) 2 #返回操作后的数据
127.0.0.1:6379> get k1
"2"
#注意:对应的值必须只能是整数,小数或者其他数(如字母)以及不完整数或者不合理数(如06,5.)不可
#否则incr,decr,incrby,decrby,操作不了,即报错
getrange,setrange(这一组命令):类似数据库的between…and…,也就是介于某某之间,与mysql一样的包括两边
range:意思是范围
测试:
127.0.0.1:6379> set k1 abcdef # 初始化k1的值为abcdef
OK
127.0.0.1:6379> get k1
"abcdef"
127.0.0.1:6379> getrange k1 0 -1
# 查询k1全部的值,负数代表从左边数数,正数代表从右边叔叔,其中a字符的下标代表0,那么f的下标就代表-1或者5
# 但是这个5是需要知道对应长度的,所以一般我们操作-1来查询对应的全部值操作,都包括两边
"abcdef"
127.0.0.1:6379> getrange k1 0 3 # 查询k1的值,范围是下标0~下标3(包含0和3,共返回4个字符)
"abcd"
127.0.0.1:6379> setrange k1 1 xxx # 替换k1的值,从下标1开始提供为xxx
#即从下标1开始,根据替换的字符长度,将原来的从下标1开始的后面相应长度进行替换(包括下标1)
#如果后面没有字符了,则添加上,如下
(integer) 6 #返回操作后的对应的字符串长度
127.0.0.1:6379> get k1
"axxxef"
setex,setnx(这一组命令)
set with expir(英文意思:使用Expire设置),即setex
理解的意思:添加数据的同时设置生命周期
测试:
127.0.0.1:6379> setex k1 5 v1 # 添加k1 v1数据的同时,设置5秒的声明周期
OK
127.0.0.1:6379> get k1
"v1"
127.0.0.1:6379> get k1
(nil) # 等待一下,再次获取,发现已过期,k1的值v1自动销毁,相当于使用了del k1
set if not exist(英文意思:如果不存在则设置),即setnx
理解的意思:添加数据的时候判断是否已经存在,防止已存在的数据被覆盖掉
测试:
127.0.0.1:6379> setnx k1 sun
(integer) 0 # 返回0,添加失败,因为k1已经存在,这是为了解决set命令覆盖数据的
127.0.0.1:6379> get k1
"laosun"
127.0.0.1:6379> setnx k2 sun
(integer) 1 # 返回1,k2不存在,所以添加成功
127.0.0.1:6379> get k2
"sun"
mset,mget,msetnx(这一组命令):
m:more更多
测试:
127.0.0.1:6379> set k1 v1 k2 v2 # set不支持一次添加多条数据
(error) ERR syntax error
127.0.0.1:6379> mset k1 v1 k2 v2 k3 v3 # mset可以一次添加多条数据
#若是一样的,如k1 v1 k1 v2,那么k1的值就是v2,即这个多条的添加,实际上就是多次的set命令操作
OK
127.0.0.1:6379> keys *
1) "k1"
2) "k2"
3) "k3"
127.0.0.1:6379> mget k2 k3 # 一次获取多条数据
#当然get k2 k3,会报错,因为get只能操作一个键,即与set一样,不操作多条数据
1) "v2"
2) "v3"
#操作的多个是一次性显示出来
127.0.0.1:6379> msetnx k3 v3 k4 v4 # 一次添加多条数据时,如果添加的数据中有已经存在的键,则失败
(integer) 0 #返回1,添加失败
127.0.0.1:6379> msetnx k4 v4 k5 v5 # 一次添加多条数据时,如果添加的数据中都不存在的,则成功
(integer) 1 #返回1,添加成功
getset:先get后set
测试:
127.0.0.1:6379> keys *
(empty list or set)
127.0.0.1:6379> set k1 1
OK
127.0.0.1:6379> getset k1 2 # 先获取k1的值,然后修改k1的值为2
"1" # 先进行get命令的操作的,即返回的结果是get命令的结果
127.0.0.1:6379> get k1
"2" #修改了
127.0.0.1:6379> keys *
1) "k1"
127.0.0.1:6379> getset k2 2 # 因为没有k2,所以get命令的结果为null,然后将k2键及其2的值添加到数据库
(nil) #get命令返回的值为null,这里显示的(nul)代表为null
127.0.0.1:6379> get k2
"2" #已经设置为2了
127.0.0.1:6379>
#所有可以得出getset命令,就是先使用get命令,然后使用set命令,返回的结果是get命令的结果
列表List:
push和pop,类似机枪AK47:push,压子弹,pop,射击出子弹
lpush,rpush,lrange(这一组命令):
l:left 自左向右→添加 (从上往下添加)
r:right 自右向左←添加(从下往上添加)
这里为了帮助理解,我们将列表看成如下:
//假设下面的五个[]代表一个整体的列表
[]
[]
[]
[]
[]
//取数据,先从最上面开始取
//若变成[][][][][],那么也就可以说是从左往右(即从上到下)了,当然,你也可以这样的理解
//这里我们操作从上到下的解释
//其他的集合也可以这样说明,我们统称为表(这里我们就称为列表,其他集合统称为表)
测试:
127.0.0.1:6379> lpush list01 1 2 3 4 5 #从上往下添加(后面的慢慢占用位置,即挤压或者推表位置)
#再次进行添加,都是进行挤压
(integer) 5 #返回该集合的数据个数
127.0.0.1:6379> keys *
1) "list01"
127.0.0.1:6379> lrange list01 0 -1 # 查询list01中的全部数据0表示开始,-1表示结尾
#我们将对应的数据,即1 2 3 4 5,从上往下的在列表添加数据,1肯定会被挤压到最下面,即5在最上面
#而由于取出数据时先从最上面开始取,所以是取出5 4 3 2 1
1) "5"
2) "4"
3) "3"
4) "2"
5) "1"
127.0.0.1:6379> rpush list02 1 2 3 4 5 #从下往上添加(后面的慢慢占用位置,即挤压或者推表位置)
#再次进行添加,都是进行挤压
#lpush和rpush都可以操作同一个键,只是方向是相反的
(integer) 5 #返回该集合的数据个数
127.0.0.1:6379> lrange list02 0 -1
#而由于是从下往上添加的,1肯定会被挤压到最上面,即5在最下面
#但是取出数据时,却是从上面开始读取,所以可以知道是1 2 3 4 5
1) "1"
2) "2"
3) "3"
4) "4"
5) "5"
lpop,rpop(这一组命令):移除第一个元素(也就是最上面或者最下面的元素,即我们默认取的位置):
测试:
127.0.0.1:6379> lpop list02 # 从上边(左边)移除第一个元素
"1" #返回移除的元素
127.0.0.1:6379> rpop list02 # 从下边(右边)移除第一个元素
"5" #返回移除的元素
lindex:根据下标查询元素(自上而下,当然也可以说是从左向右,前面帮助理解里有说明):
测试:
127.0.0.1:6379> lrange list01 0 -1
1) "5"
2) "4"
3) "3"
4) "2"
5) "1"
127.0.0.1:6379> lindex list01 2 # 从上到下数,下标为2的值
"3" #返回对应下标的值
127.0.0.1:6379> lindex list01 1 # 从上到下数,下标为1的值
"4" #返回对应下标的值
#下标当然从0开始
llen:返回集合(或者列表,这里统称为集合)长度:
测试:
127.0.0.1:6379> llen list01
(integer) 5 #返回集合的长度
lrem:删除n个value:
测试:
127.0.0.1:6379> lpush q 1 3 4 5 4 6 4 7
(integer) 8 #返回集合长度
127.0.0.1:6379> lrange q 0 -1
1) "7"
2) "4"
3) "6"
4) "4"
5) "5"
6) "4"
7) "3"
8) "1"
127.0.0.1:6379> lrem q 2 4 #从q中移除2个4,从上到下开始一路移除
(integer) 2 #返回移除的个数
127.0.0.1:6379> lrange q 0 -1
1) "7"
2) "6"
3) "5"
4) "4" #这个4没有移除,因为只移除两个
5) "3"
6) "1"
127.0.0.1:6379> lrem q 6 4 #就算你指定多个个数,如6,但是对应的只有一个4,那么只会移除一个4
(integer) 1 #返回移除的个数
127.0.0.1:6379> lrange q 0 -1
1) "7"
2) "6"
3) "5"
4) "3"
5) "1"
127.0.0.1:6379> lrem q 2 4
(integer) 0 #没有任何移除
127.0.0.1:6379> lrem q 2 7
(integer) 1 #返回移除的个数
127.0.0.1:6379> lrange q 0 -1
1) "6"
2) "5"
3) "3"
4) "1"
先看一看对应的lpush q 1 3 4 5 4 6 4 7命令的结果是否是返回集合长度:
测试:
127.0.0.1:6379> lpush q 1 3 4 5 4 6 4 7
(integer) 8
127.0.0.1:6379> lpush q 1 3 4 5 4 6 4 7
(integer) 16 #发现,的确是集合的长度8+8=16
127.0.0.1:6379>
ltrim:截取指定范围的值,别的全扔掉:
ltrim key begindex endindex(英文意思:Itrim键开始索引结束索引),这是你写这个命令时,对应提示的语法:
测试:
127.0.0.1:6379> lpush list01 1 2 3 4 5 6 7 8 9
(integer) 9
127.0.0.1:6379> lrange list01 0 -1
1) "9" # 下标0
2) "8" # 下标1
3) "7" # 下标2
4) "6" # 下标3
5) "5" # 下标4
6) "4" # 下标5
7) "3" # 下标6
8) "2" # 下标7
9) "1" # 下标8
127.0.0.1:6379> ltrim list01 3 6 # 截取下标3~6的值(包括下标3和下标6),别的全扔掉
OK #操作成功
#若参数一样,即ltrim list01 1 1,则只有一个,即8,若是大的在前小的在后,由于连接不到,那么返回空的
#即截取空的,而又由于其他的全扔掉(除了截取的全部扔掉,那么就是空了),那么对应的集合就是空的了
#当然了只有截取到的才算,所以一但出现对应范围没有截取的,自然也是空的,如ltrim list01 30 60,当然是空的
127.0.0.1:6379> lrange list01 0 -1
1) "6"
2) "5"
3) "4"
4) "3"
rpoplpush:从一个集合搞一个元素到另一个集合中(右出一个,左进一个)
即第一个集合从下面取出,然后放入第二个集合的上面:
测试:
127.0.0.1:6379> rpush list01 1 2 3 4 5
(integer) 5
127.0.0.1:6379> lrange list01 0 -1
1) "1"
2) "2"
3) "3"
4) "4"
5) "5"
127.0.0.1:6379> rpush list02 1 2 3 4 5
(integer) 5
127.0.0.1:6379> lrange list02 0 -1
1) "1"
2) "2"
3) "3"
4) "4"
5) "5"
127.0.0.1:6379> rpoplpush list01 list02
# list01右边(下边)出一个,从左边(上边)进入到list02的第一个位置
"5" #进行操作的那个数据
127.0.0.1:6379> lrange list01 0 -1
1) "1"
2) "2"
3) "3"
4) "4"
127.0.0.1:6379> lrange list02 0 -1
1) "5"
2) "1"
3) "2"
4) "3"
5) "4"
6) "5"
lset:改变某个下标的某个值:
lset key index value(英文意思:lset键索引值),这是你写这个命令时,对应提示的语法:
测试:
127.0.0.1:6379> lrange list02 0 -1
1) "5"
2) "1"
3) "2"
4) "3"
5) "4"
6) "5"
127.0.0.1:6379> lset list02 0 x # 将list02中下标为0的元素修改成x
OK #操作成功
127.0.0.1:6379> lrange list02 0 -1
1) "x"
2) "1"
3) "2"
4) "3"
5) "4"
6) "5"
linsert:插入元素(指定某个元素之前/之后):
linsert key before/after oldvalue newvalue(英文意思:oldvalue之前/之后的linsert键newvalue)
这是你写这个命令时,对应提示的语法:
测试:
127.0.0.1:6379> lrange list02 0 -1
1) "x"
2) "1"
3) "2"
4) "3"
5) "4"
6) "5"
127.0.0.1:6379> linsert list02 before 2 java
# 从左边(上边)进入,在list02中找到第一个的2这个元素之前插入java
(integer) 7 #返回插入后的集合长度,若是-1,则插入失败,一般是没有这个元素造成的
127.0.0.1:6379> lrange list02 0 -1
1) "x"
2) "1"
3) "java"
4) "2" #第一个2元素
5) "3"
6) "4"
7) "5"
127.0.0.1:6379> linsert list02 after 2 redis
# 从左边(上边)进入,在list02中找到第一个的2这个元素之后插入redis
(integer) 8
127.0.0.1:6379> lrange list02 0 -1
1) "x"
2) "1"
3) "java"
4) "2" #第一个2元素
5) "redis"
6) "3"
7) "4"
8) "5"
#若有多个2元素,只会操作从左边(上边)找到的第一个对应元素,其他的不操作
性能总结:类似添加火车皮一样,头尾操作效率高
中间操作效率惨(发现插入若有多个相同的元素,却只能选择其中一个,即第一个)
而若要改变,基本只能手动操作下标来进行修改,即改变,所以说,头尾操作效率高,而中间操作效率低
集合Set :
和java中的set特点类似,不允许重复
sadd,smembers,sismember(这一组命令):添加,查看,判断是否存在
测试:
127.0.0.1:6379> sadd set01 1 2 2 3 3 3
# 添加元素(自动排除重复元素),不管后面几个元素,在进行添加时,若一样,则直接忽略或者说覆盖(不会改变位置)
#若再次进行添加一样的键,若是有多余的数据,即不同的数据
#则进行添加,其他的相同的数据则不会添加(即位置不会改变),因为相同,所以这样可以进行添加数据
#实际上就算改变位置,也会因为排列进行不变,当然字母除外(随机)
(integer) 3 #添加的数据个数,若我们再次添加,自然会根据不同的来确定这个添加了多少个数据
127.0.0.1:6379> smembers set01 # 查询set01集合
1) "1"
2) "2"
3) "3"
#若添加的是数字字符串,则直接按照数据的谁大来进行排列,即谁大谁在后面进行排列
#然后从头到尾插入表(前面的帮助理解里说明了表的意思)
#若有字母,首先先随机排列(即无序,没有排序,然后从头到尾插入表)
#一般的无序也可以说是没有排序或者说没有排列,但也有些是随机的排序,这里就是随机的排序
#只有没有进行排列的,都可以叫做无序
#java大多数的集合都是无序的,也就是没有进行排列,一般都是根据顺序或者随机来操作
#然后当你再次进行添加时(可以不变或者变化),他可能会改变顺序
#但是,当改变到几次时(或者可能直接固定了,这个也是随机),即最后都会有一个固定的顺序
#之后对应的数基本是不会变化的了,即根据固定给出编号的来进行排列
#所以也可以说,当有字母时,就是随机的排列,而之所以有固定,是防止你进行多次操作
#使得每次进行随机排列,所以需要固定
127.0.0.1:6379> sismember set01 2
(integer) 1 # 返回1,则存在
127.0.0.1:6379> sismember set01 5
(integer) 0 # 返回0,则不存在
注意:上面的返回的1和0可不是下标,而是布尔,且不是ok
ok这个一般用来确定一个正确结果,因为除了ok
即没有对应的结果或者参数就是报错了,如指定下标越界,如对应的lset修改对应下标的值,若没有对应下标就会报错
而一般这样的没有判断操作,即使用ok和报错,或者说,没有判断操作的就是ok和报错
有判断操作的除外,如ltrim截取指定范围的值
而1:true存在,2:false不存在,基本对于两个结果来说,除了对应的返回数据,基本都是这样的
在参数基本不会造成错误的情况下,用来确定是否操作成功,因为有对应判断,所以就是这两种情况
这里的介绍了解即可
scard:获得集合中的元素个数:
测试:
127.0.0.1:6379> scard set01
(integer) 3 # 集合中有3个元素
srem:删除集合中的元素:
srem key value(英文意思:srem键值),这是你写这个命令时,对应提示的语法:
测试:
127.0.0.1:6379> srem set01 2 # 移除set01中的元素2
(integer) 1 # 1表示移除成功,自然0就表示失败
srandmember:从集合中随机获取几个元素:
srandmember (英文意思:srandmember公司),理解的意思:整数(个数)
测试:
127.0.0.1:6379> sadd set01 1 2 3 4 5 6 7 8 9
(integer) 9
127.0.0.1:6379> smembers set01
1) "1"
2) "2"
3) "3"
4) "4"
5) "5"
6) "6"
7) "7"
8) "8"
9) "9"
127.0.0.1:6379> srandmember set01 3 # 从set01中随机获取3个元素
1) "8"
2) "2"
3) "3"
#随机获取的3个元素
127.0.0.1:6379> srandmember set01 5 # 从set01中随机获取5个元素
1) "5"
2) "8"
3) "7"
4) "4"
5) "6"
#随机获取的5个元素
#若取得的数据,大于等于对应的集合的长度,则默认不会随机取数据,而是相当于第一次随机的排列,即smembers set01
#注意:是第一次,当你没有修改时,那么无论你排列多少次,都默认第一次的结果,若修改了,自然会重置
#若不写对应的参数,则默认随机一个数据
#若是负数,则随机数取出负数对应的数,如-8,则随机取8个
#当然,我们在指定下标时,负数一般是从右边开始的,当然,负数也是不能超过下标的,就如正数或者0不能超过一样
#0当然是肯定存在的,也就基本不会出现超过下标
spop:随机移除:
测试:
127.0.0.1:6379> smembers set01
1) "1"
2) "2"
3) "3"
4) "4"
5) "5"
6) "6"
7) "7"
8) "8"
9) "9"
127.0.0.1:6379> spop set01 # 随机移除一个元素
"8" #返回移除的元素
127.0.0.1:6379> spop set01 # 随机移除一个元素
"7" #返回移除的元素
#注意:可以指定移除个数,如spop set01 3,随机移除3个元素,即默认移除一个
smove:移动元素:将key1某个值赋值给key2:
测试:
127.0.0.1:6379> sadd set01 1 2 3 4 5
(integer) 5
127.0.0.1:6379> sadd set02 x y z
(integer) 3
127.0.0.1:6379> smove set01 set02 3 # 将set01中的元素3移动到set02中
(integer) 1 # 移动成功,若这个3不存在,一般就会返回0,即移动失败
#注意:若移动的是数字字符串,那么基本就是根据大写,若是字母,可能对方集合会进行随机排列
#当然,这是根据自身与原来做比较,而进行的操作(每次操作后)
数学集合类:
集:sinter
并集:sunion
差集:sdiff
测试:
127.0.0.1:6379> sadd a 1 2 3 4
(integer) 4
127.0.0.1:6379> sadd b 3 4 5 6
(integer) 4
127.0.0.1:6379> smembers a
1) "1"
2) "2"
3) "3"
4) "4"
127.0.0.1:6379> smembers b
1) "3"
2) "4"
3) "5"
4) "6"
127.0.0.1:6379> sinter a b # a和b共同存在的元素
1) "3"
2) "4"
127.0.0.1:6379> sunion a b # 将a和b中所有元素合并起来(排除重复的)
1) "1"
2) "2"
3) "3"
4) "4"
5) "5"
6) "6"
#上面两个无论怎么交换位置,结果基本都一样
127.0.0.1:6379> sdiff a b # 在a中存在,在b中不存在
1) "1"
2) "2"
127.0.0.1:6379> sdiff b a # 在b中存在,在a中不存在
1) "5"
2) "6"
127.0.0.1:6379> sdiff a
1) "1"
2) "2"
3) "3"
4) "4"
127.0.0.1:6379> sunion a
1) "1"
2) "2"
3) "3"
4) "4"
127.0.0.1:6379> sinter a
1) "1"
2) "2"
3) "3"
4) "4"
#上面单独的,基本就是相当于直接使用smembers a
127.0.0.1:6379> sinter aa
(empty list or set)
127.0.0.1:6379> sunion aa
(empty list or set)
127.0.0.1:6379> sdiff aa
(empty list or set)
#smembers aa自然是空的,因为没有
127.0.0.1:6379> sdiff aa b
(empty list or set)
127.0.0.1:6379> sdiff aa a
(empty list or set)
127.0.0.1:6379> sdiff a aa
1) "1"
2) "2"
3) "3"
4) "4"
#与null做比较,自然sdiff a aa可以,a中存在,aa中不存在,那么就是a
127.0.0.1:6379> sinter a aa
(empty list or set)
127.0.0.1:6379> sinter aa a
(empty list or set)
#与null做比较,都不可以,因为他们没有公有的数据
127.0.0.1:6379> sunion a aa
1) "1"
2) "2"
3) "3"
4) "4"
127.0.0.1:6379> sunion aa a
1) "1"
2) "2"
3) "3"
4) "4"
#与null做比较,都可以,因为他们共同的数据就是a
#总结:
#sinter:两者之间共同存在的数据
#sunion:两者合并的数据
#sdiff:左边参数(集合)存在的数据,而右边参数(集合)不存在的数据
哈希Hash:
类似java里面的Map<String,Object>
KV模式不变,但V是一个键值对
hset,hget,hmset,hmget,hgetall,hdel(这一组命令):添加,得到,多添加,多得到,得到全部,删除属性
测试:
127.0.0.1:6379> hset a i 1000 # 添加a,值为i=1000,不是这样的格式,一般会报错
(integer) 1 #返回1,添加成功
127.0.0.1:6379> hget a #必须指定键中的key
(error) ERR wrong number of arguments for 'hget' command
127.0.0.1:6379> hget i #必须指定键中的key
(error) ERR wrong number of arguments for 'hget' command
127.0.0.1:6379> hget a i #必须指定键中的key
"1000" #返回key的value值
127.0.0.1:6379> hget a 1000 #不能指定value,必须指定键,即1000当成键了,因为这个参数就会去找键
(nil)
127.0.0.1:6379> hmset i a 1000 b 1002 #进行多次添加key-value
OK #添加成功
127.0.0.1:6379> hget i a #得到对应键的key的value值
"1000" #返回value值
127.0.0.1:6379> hmget i a b #进行多次读取,需要指定对应键,因为这个参数就会去找键
1) "1000"
2) "1002"
#即键的值,存放key-value格式的数据,且是集合,即可以放多个
127.0.0.1:6379> hgetall i #得到键的所有信息,即key-value依次的信息
1) "a" #key
2) "1000" #value,与上面一起,下面以此类推
3) "b"
4) "1002"
127.0.0.1:6379> hdel i a #删除对应key为a的数据
(integer) 1 #删除成功
127.0.0.1:6379> hgetall i
1) "b"
2) "1002"
127.0.0.1:6379> hset k 90 #当再次进行添加时,自然的是进行多次添加,但是需要是key-value的格式,否则报错
(error) ERR wrong number of arguments for 'hset' command
127.0.0.1:6379> hset i k 90 #当再次进行添加时,多出了对应的数据
(integer) 1 #添加成功
127.0.0.1:6379> hgetall i
1) "b"
2) "1002"
3) "k"
4) "90"
127.0.0.1:6379> hset i b 999 #修改存在的key
(integer) 0 #这里返回0,代表没有添加,但实际上却是修改成功
127.0.0.1:6379> hgetall i
1) "b"
2) "999"
3) "k"
4) "90"
#从这里发现,无论什么集合或者对应的数据类型,都需要有一个键存在,这个键可以看成当前数据库的字段
#不是表,这个redis数据库可以看成一个表,该键的值,可以用集合代表多条数据
#根据前面的学习,发现这个字段基本是可以随便加的
hlen:返回元素的属性个数:
测试:
127.0.0.1:6379> hgetall i
1) "b"
2) "999"
3) "k"
4) "90"
127.0.0.1:6379> hlen i #查询集合的key个数,这里也就是元素的个数
(integer) 2 #key的数量,即b和k这两个属性
hexists:判断元素是否存在某个属性:
测试:
127.0.0.1:6379> hgetall i
1) "b"
2) "999"
3) "k"
4) "90"
127.0.0.1:6379> hexists i b # i中是否存在b属性
(integer) 1 #返回1,代表存在
127.0.0.1:6379> hexists i bb # i中是否存在bb属性
(integer) 0 #返回0,代表不存在
hkeys,hvals(这一组命令):获得属性的所有key/获得属性的所有value:
测试:
127.0.0.1:6379> hgetall i
1) "b"
2) "999"
3) "k"
4) "90"
127.0.0.1:6379> hkeys i # 获取i所有的属性名
1) "b"
2) "k"
127.0.0.1:6379> hvals i # 获取i所有属性的值(内容)
1) "999"
2) "90"
现在我们看看对应添加key-value是否有顺序:
测试:
127.0.0.1:6379> hset o d 1000
(integer) 1
127.0.0.1:6379> hset o a 1000
(integer) 1
127.0.0.1:6379> hset o b 1000
(integer) 1
127.0.0.1:6379> hset o 1 1000
(integer) 1
127.0.0.1:6379> hset o 2 1000
(integer) 1
127.0.0.1:6379> hset o 6 1000
(integer) 1
127.0.0.1:6379> hset o 4 1000
(integer) 1
127.0.0.1:6379> hgetall o1) "d"2) "1000"3) "a"4) "1000"5) "b"6) "1000"7) "1"8) "1000"9) "2"
10) "1000"
11) "6"
12) "1000"
13) "4"
14) "1000"
#我们发现他就是直接从头到尾的插入表,并没有进行排列
hincrby,hincrbyfloat(这一组命令):自增(整数)/自增(小数):
测试:
127.0.0.1:6379> hset i a 10 b 20
(integer) 2
127.0.0.1:6379> hgetall i
1) "a"
2) "10"
3) "b"
4) "20"
127.0.0.1:6379> hincrby i a #必须指定一个参数进行操作,没有默认的value++,否则报错
(error) ERR wrong number of arguments for 'hincrby' command
127.0.0.1:6379> hincrby i a 1 # 自增整数2,相当于即a这个key的value+=1
(integer) 11
127.0.0.1:6379> hgetall i
1) "a"
2) "11" #加1了
3) "b"
4) "20"
127.0.0.1:6379> hincrby i a 5.5
#只能是整数,小数或者其他数(如字母)以及不完整数或者不合理数(如06,5.)不可,否则报错
(error) ERR value is not an integer or out of range
127.0.0.1:6379> hincrbyfloat i a 5.5
#可以是整数,小数以及不完整数(如06,5.),但不可以是对应其他数(如字母),否则报错
"16.5" #返回操作后的数据
127.0.0.1:6379> hgetall i
1) "a"
2) "16.5"
3) "b"
4) "20"
127.0.0.1:6379> hincrbyfloat i a 5
"21.5"
127.0.0.1:6379> hincrbyfloat i a 5. #当成5来看
"26.5"
127.0.0.1:6379> hincrbyfloat i a 07 #当成7来看
"33.5"
127.0.0.1:6379> hincrbyfloat i a a #不可以是字母,即这里报错了
(error) ERR value is not a valid float
127.0.0.1:6379> hgetall i
1) "a"
2) "33.5"
3) "b"
4) "20"
hsetnx:添加的时候,先判断是否存在:
测试:
127.0.0.1:6379> flushdb #先情况当前数据库键,即信息,操作时一般都会先这样进行操作
OK
127.0.0.1:6379> hsetnx i a 10 # 添加时,判断a是否存在
(integer) 1 #添加成功,即不存在
127.0.0.1:6379> hgetall i
1) "a"
2) "10"
127.0.0.1:6379> hsetnx i a 10
(integer) 0 #添加失败,即存在
127.0.0.1:6379> hsetnx i a 11
(integer) 0 #添加失败,即存在
127.0.0.1:6379> hgetall i
1) "a"
2) "10"
#数据没有变化
127.0.0.1:6379> hset i a 11 #直接设置数据
(integer) 0 #没有添加,但修改了
127.0.0.1:6379> hgetall i
1) "a"
2) "11"
#数据改变
在这里提醒一下,若加入中文会怎么样(String和List和Set和Hash):
测试:
127.0.0.1:6379> set a 和
OK
127.0.0.1:6379> get a
"\xe5\x92\x8c"
127.0.0.1:6379> lpush b 和
(integer) 1
127.0.0.1:6379> lrange b 0 -1
1) "\xe5\x92\x8c"
127.0.0.1:6379> sadd c 和
(integer) 1
127.0.0.1:6379> smembers c
1) "\xe5\x92\x8c"
127.0.0.1:6379> hset d key 和
(integer) 1
127.0.0.1:6379> hgetall d
1) "key"
2) "\xe5\x92\x8c"
#我们可以发现,上面四种数据类型,都可以添加中文
#但都不能解决乱码,即对应的乱码应该与redis自身有关,造成显示时的乱码
上面的乱码以后进行解决,现在先不用管(88章博客里会详细的说明并解决)
有序集合Zset :
真实需求:
充10元可享vip1
充20元可享vip2
充30元可享vip3
以此类推…
zadd,zrange (这一组命令):添加,查询
withscores:中文意思:带分数或带颜色
测试:
127.0.0.1:6379> zadd zset01 10 vip1 20 vip2 30 vip3 40 vip4 50 vip5
(integer) 5 #返回添加的数据个数,注意,添加时必须是数字在前面
#可以是整数,小数以及不完整数(如06,5.),但不可以是对应其他数(如字母),否则报错
#对于这个,一般是操作数字时出现的限制,而可以增加字母的,那么对应的一定的字符串,所有也就是没有限制
#所有发现,在只能操作数字的时候,会有这个注释
#当然,就算没有,也无非是小数以及不完整数的是否可写,一般的,可以操作小数的都可以写,否则只能是整数
127.0.0.1:6379> zrange zset01 0 -1
# 查询数据,这里与Hash类似,可以看出key-value形式,只是添加时,是反过来的
1) "vip1"
2) "vip2"
3) "vip3"
4) "vip4"
5) "vip5"
127.0.0.1:6379> zrange zset01 0 -1 withscores # 带着分数查询数据1) "vip1"2) "10"3) "vip2"4) "20"5) "vip3"6) "30"7) "vip4"8) "40"9) "vip5"
10) "50"
127.0.0.1:6379> zadd zset01 10 j #当虽然数字在前面,但实际上却是操作后面的j的
(integer) 1 #添加的数据个数
127.0.0.1:6379> zrange zset01 0 -1 withscores1) "j"2) "10"3) "vip1"4) "10"5) "vip2"6) "20"7) "vip3"8) "30"9) "vip4"
10) "40"
11) "vip5"
12) "50"
127.0.0.1:6379> zadd zset01 11 j #若再次进行操作,即进行修改
(integer) 0 #没有添加,但修改了
127.0.0.1:6379> zrange zset01 0 -1 withscores1) "vip1"2) "10"3) "j"4) "11"  #而由于是有序的,所以根据数字排名,即谁大谁在后面5) "vip2"6) "20"7) "vip3"8) "30"9) "vip4"
10) "40"
11) "vip5"
12) "50"
zrangebyscore:模糊查询
( : 不包含
limit:跳过几个截取几个
测试:
127.0.0.1:6379> zadd a 10 v1 20 v2 30 v3 40 v4 50 v5 #进行添加数据
(integer) 5 #返回添加的数量
127.0.0.1:6379> zrange a 0 -1 #查询对应的属性,或者说key
1) "v1"
2) "v2"
3) "v3"
4) "v4"
5) "v5"
127.0.0.1:6379> zrange a 0 -1 withscores #顺便查询出对应的值,或者说value1) "v1"2) "10"3) "v2"4) "20"5) "v3"6) "30"7) "v4"8) "40"9) "v5"
10) "50"
127.0.0.1:6379> zrangebyscore a 20 40 # 相当于20 <= value <= 40
1) "v2"
2) "v3"
3) "v4"
127.0.0.1:6379> zrangebyscore a 20 (40 # 相当于20 <= value < 40
1) "v2"
2) "v3"
127.0.0.1:6379> zrangebyscore a (20 (40 # 相当于20 < value < 40
1) "v3"
127.0.0.1:6379> zrangebyscore a 10 40 limit 2 2
# 10 <= value <= 40,共返回四个,跳过前2个,取2个
1) "v3"
2) "v4"
127.0.0.1:6379> zrangebyscore a 10 40 limit 2 2 withscores
# 10 <= score <= 40,共返回四个,跳过前2个,取2个,并显示对应value
1) "v3"
2) "30"
3) "v4"
4) "40"
127.0.0.1:6379> zrangebyscore a 10 40 withscores limit 2 2  #换个位置也可以,与Linux的类似
1) "v3"
2) "30"
3) "v4"
4) "40"
127.0.0.1:6379> zrangebyscore a 10 40 withscores limit 2 1
# 10 <= score <= 40,共返回四个,跳过前2个,取1个,并显示对应value
1) "v3"
2) "30"
127.0.0.1:6379> zrangebyscore a 10 40 withscores limit
#需要指定对应范围,且要完整,即不指定或者指定一个都报错
(error) ERR syntax error
127.0.0.1:6379> zrangebyscore a 10 40 withscores
#这里我们就直接查询范围里面的了,发现前面的limit 2 1的确是将前面两个跳过,且只取得一个,即v3
1) "v1"
2) "10"
3) "v2"
4) "20"
5) "v3"
6) "30"
7) "v4"
8) "40"
127.0.0.1:6379> zrangebyscore a 10 40 withscores limit 2 6 #当取的数据取完后,就不会再取了
#即若大于等于后续的属性,则就是后面的所有属性
1) "v3"
2) "30"
3) "v4"
4) "40"
127.0.0.1:6379> zrangebyscore a 10 40 withscores limit 4 6 #忽略前面所有的自然就是没得取了
(empty list or set)
127.0.0.1:6379> zrangebyscore a 10 40 withscores limit 3 6 #正好取得一个
1) "v4"
2) "40"
zrem:删除元素:
测试:
127.0.0.1:6379> zrange a 0 -1
1) "v1"
2) "v2"
3) "v3"
4) "v4"
5) "v5"
127.0.0.1:6379> zrange a -1 0 #实际上进行取数据都是从左到右的,其他数据类型基本如此
(empty list or set)
127.0.0.1:6379> zrem a v2 # 移除v2
(integer) 1 #删除成功
127.0.0.1:6379> zrange a 0 -1
1) "v1"
2) "v3"
3) "v4"
4) "v5"
zcard,zcount,zrank,zscore:集合长度,范围内元素个数,得元素下标,通过值得分数:
测试:
127.0.0.1:6379> zrange a 0 -1
1) "v1"
2) "v3"
3) "v4"
4) "v5"
127.0.0.1:6379> zcard a # 集合中元素的个数
(integer) 4 #返回对应的个数
127.0.0.1:6379> zrange a 0 -1 withscores
1) "v1"
2) "10"
3) "v3"
4) "30"
5) "v4"
6) "40"
7) "v5"
8) "50"
127.0.0.1:6379> zcount a 30 50 # 分数在30~50之间(包括30和50),共有几个元素
(integer) 3 #返回对应的个数,正好是v3,v4,v5
127.0.0.1:6379> zcount a 50 60 #只有50这一个
(integer) 1 #返回一个
127.0.0.1:6379> zcount a 50 #必须完整,否则报错
(error) ERR wrong number of arguments for 'zcount' command
127.0.0.1:6379> zcount a #必须完整,否则报错
#实际上由于没有必须完整造成报错的,是因为命令是各式是固定的,而有些参数是可加的,如withscores
#而不可以加的,则必须写上
(error) ERR wrong number of arguments for 'zcount' command
127.0.0.1:6379> zrank a v4 # v4在集合中的下标(从上向下,从左到右)
(integer) 2 #正好是2
127.0.0.1:6379> zscore a v4 # 通过元素获得对应的分数
"40" #返回对应分数
zrevrank:逆序找下标(从下向上):
测试:
127.0.0.1:6379> zrange a 0 -1 withscores
1) "v1"
2) "10"
3) "v3"
4) "30"
5) "v4"
6) "40"
7) "v5"
8) "50"
127.0.0.1:6379> zrevrank a v4 #反过来找下标,若是正过来找就是2,而反过来找,就是1
(integer) 1 #返回逆序下标,这里就是1
zrevrange:逆序查询:
测试:
127.0.0.1:6379> zrange a 0 -1
1) "v1"
2) "v3"
3) "v4"
4) "v5"
127.0.0.1:6379> zrevrange a 0 -1 #逆序查询,相当于从最后开始算下标,且从最后找,所有任然是从小到大
1) "v5"
2) "v4"
3) "v3"
4) "v1"
zrevrangebyscore:逆序范围查找:
测试:
127.0.0.1:6379> zrevrange a 0 -1 withscores
1) "v5"
2) "50"
3) "v4"
4) "40"
5) "v3"
6) "30"
7) "v1"
8) "10"
127.0.0.1:6379> zrevrangebyscore a 30 10
# 逆序查询分数在30~20(包括30和20)之间的 (注意,先写大值,再写小值)
1) "v3"
2) "v1"
127.0.0.1:6379> zrevrangebyscore a 10 30 #而正是逆序查询,所以是会从大的到小的
#因为对应的数字由于下标从最后开始,使得从大到下形成的,否则就不是逆序查询了
#所以需要大的在前,否则报错,即查询不到(因为连接不到),即返回null,集合的null
(empty list or set)
127.0.0.1:6379> zrangebyscore a 10 30
1) "v1"
2) "v3"
127.0.0.1:6379> zrangebyscore a 30 10
#正如逆序查询一样,由于是正序查询,那么对应的是小的到大的,否则就是逆序查询了
#所以需要小的在前,否则报错,像这种查询的,所有集合基本都遵循这样的解释
#就如zrange a 0 -1 withscores中0 和-1的正查询(不可反过来,基本没有逆序查询操作)一样
#其他集合也基本是一样的(其他集合基本没有逆序查询的操作,当然,若有则遵循逆序查询解释)
(empty list or set)
持久化:
RDB:
Redis DataBase
在指定的时间间隔内,将内存中的数据集的快照写入磁盘
一般我们在/usr/local/bin目录中操作,对应的文件名是dump.rdb,而由于对应的操作是全局的
且配置文件创建的文件即dump.rdb是当前目录,所以在不同的目录下,启动的操作不同,我们最好统一在/usr/local/bin目录中操作
下面就是在/usr/local/bin目录中操作
自动备份:
redis是内存数据库,当我们每次用完redis,关闭linux时,按道理来说,内存释放,redis中的数
据也会随之消失
为什么我们再次启动redis的时候,昨天的数据还在,并没有消失呢?
正是因为,每次关机时,redis会自动将数据备份到一个文件中 :/usr/local/bin/dump.rdb,当开机的时候就从这个文件里读取出来
当然,每次关闭自然会默认覆盖该文件
接下来我们就来全方位的认识 自动备份机制
默认的自动备份策略不利于我们测试,所以修改redis.conf文件中的自动备份策略:
vim redis.conf
/SNAP   # 到底行模式进行输入搜索
#对应的配置
save 900 1
#满足从开启(客户端开启)开始计时,每过900秒
#且其中至少发生了1次操作(如增删改等等操作,使得数据库数据变化),则存一次
#然后继续从头计时,并记录上次计时的次数,除非判断成功,则消除次数,并重新计时,其中若记录了上次次数
#则只会判断次数,而不会判断时间了(虽然也在计时)
save 120 10    #原来一般是save 300 10,这个配置这里进行了修改
#满足从开启(客户端开启)开始计时,每过了120秒
#且至少发生了10次操作(如增删改等等操作,使得数据库数据变化),则存一次
#然后继续从头计时,并记录上次计时的次数,除非判断成功,则消除次数,并重新计时,其中若记录了上次次数
#则只会判断次数,而不会判断时间了(虽然也在计时)
#这里你可以将120改成20秒,并打开两个Linux窗口(记为1号窗口,2号窗口)
#其中1号窗口使用ls --full-time命令进行查看dump.rdb文件日期
#2号窗口开启redis客户端,操作两次数据
#然后在1号窗口多次使用ls --full-time命令进行查看,大概过了20秒,这个日期就更新了
#照着这样一路测试,若只操作1次,会发现,过了20秒并没有更新,接下来需要继续计时了
#但是这时只要你在操作一次,就会立马更新
save 60 10000
#满足从开启(客户端开启)开始计时,每过了60秒
#且至少发生了10000次操作(如增删改等等操作,使得数据库数据变化),则存一次
#然后继续从头计时,并记录上次计时的次数,除非判断成功,则消除次数,并重新计时,其中若记录了上次次数
#则只会判断次数,而不会判断时间了(虽然也在计时)
#当他们满足时,那么会自动进行保存,即就算你没有关闭redis,也会进行保存
#可以使用ls --full-time命令看(使用两个Linux窗口查看)
#上面的满足从开启开始计时的意思:如我出去走走(开启)
#假如在路上每走900秒,且900秒中间至少摇头了一次,则进行记录一下位置,这个900秒就是这个意思,然后继续这样
#除非进行关闭redis,即shutdown命令,模拟关机
#对应的解释:判断顺序是同时计时的,当有一个判断正确后,全部都重新计时
#即我们的配置也未必是有顺序的,如第二个配置中120,可能对应的时间大点,如改成1200,反正都是同时进行
#二者要必须同时满足,即操作次数和发生中间的总时间同时满足才可进行自动备份
#可以使用ls --full-time命令,精确到秒级(即可以看到秒)
当然如果你只是用Redis的缓存功能,不需要持久化
那么你可以注释掉所有的 save 行来停用保存功能,也可以直接一个空字符串来实现停用:save “”
但是上面的这样使用该配置文件启动后,无论怎么操作或者关闭都不会创建或者更新对应文件
当然,若不使用配置文件启动,则默认原来的配置,即没有修改之前的保存配置
使用shutdown模拟关机(即关闭redis,关机后,虽然还在对应客户端,但并没有进行连接,记得ctrl+c退出)
关机之前和关机之后,对比dump.rdb文件的更新时间
发现关机后对应创建时间变化,需要对应已经判断保存的(若原来没有则自然创建文件)
每次关机后都会创建(没有就会创建,一开始就是没有的)或更新dump.rdb文件信息
是在有可以操作的配置下,上面的两个停用保存功能就不可以
注意:当我们使用shutdown命令,redis会自动将当前数据库的数据进行再次保存,然后关闭redis
所以可能会发生两次日期时间变化(自动保存和这个保存)
即数据库备份(即保存当前的),所以,dump.rdb文件创建时间更新了
当然,若当你出现特殊情况,如明明使用shutdown但是不更新,且对应的配置文件不起作用,可以将dump.rdb文件删除
大概是你退出该服务器(如挂起),使得恢复时,造成固定数据,也就不会变化了(通常不会这样)
开机启动redis,我们要在120秒(你可以自己设置低一点)内保存10条数据
当120秒过去了,再查看dump.rdb文件的更新时间(开两个终端窗口,方便查看)
120秒保存10条数据这一动作触发了备份指令,当你等待120秒后,可以发现,我们并没有关闭redis
但是对应dump.rdb文件信息文件信息时间更新了,即dump.rdb文件中保存了10条数据
将dump.rdb拷贝一份dump10.rdb,此时两个文件中都保存10条数据
既然有数据已经备份了,那么为了测试是否只操作dump.rdb文件,那我们就肆无忌惮的将数据全部删除flushall,再次shutdown关机
再次启动redis,发现数据真的消失了,并没有按照我们所想的 将dump.rdb文件中的内容恢复到redis中,为什么:
因为,当我们保存10条以上的数据时,数据备份起来了
然后删除数据库,我们也备份文件中的数据,也没问题
但是,问题出在shutdown上,这个命令一旦执行,就会立刻备份,将删除之后的空数据库生成备份文件
将之前装10条数据的备份文件覆盖掉了,所以,就出现了上面的结果,自动恢复失败,即原因是shutdown命令的操作
怎么解决这个问题呢:要将备份文件再备份,即将dump.rdb文件删除
将dump10.rdb重命名为dump.rdb或者再次进行备份成dump.rdb
启动redis服务,登录redis,数据10条,全部恢复
当然,若没有dump.rdb文件,对应数据库数据自然都是空的
由此可知,数据的确是保存在dump.rdb文件中(可以进行设置的)
即也只操作这个名称的文件,修改名称不可以(如dump10.rdb文件,因为规定的,可以通过配置文件进行修改)
手动备份:
之前自动备份,必须更改好多数据,例如上边,我们改变了十多条数据,才会自动备份
现在,我只保存一条数据,就想立刻备份,而不用等待时间,应该怎么做:
虽然我们可以设置在少的时间里,进行操作一次的保存,但是这是自动的,且过多的计时,会造成性能的影响,所有我们需要手动
即每次操作完成,执行命令 save 就会立刻备份,这时可以使用ls --full-time命令看看日期了,发现改变了,返回ok,保存成功
实际上自动备份,就是到达时间且满足条件后,自动的执行这个save命令,且shutdown关闭之前也会执行save命令
与RDB相关的配置(可以通过底行模式的"/对应名称"来进行查找):
stop-writes-on-bgsave-error:进水口和出水口,出水口发生故障与否
yes:当后台备份时候发生错误,前台停止接收写入,默认这个
no:不管死活,就是往里怼
rdbcompression:对于存储到磁盘中的快照,是否启动LZF压缩算法,一般都会启动
虽然会降低性能,但是这点性能,多买一台电脑(如集群),完全搞定N个来回了
yes:启动,默认这个
no:不启动(不想消耗CPU资源,可关闭)
这个类似于压缩
rdbchecksum:在存储快照后,是否启动CRC64算法进行数据校验,开启后,大约增加10%左右的CPU消耗;
如果希望获得最大的性能提升,可以选择关闭,默认是yes,设置no关闭
这个类似于解压
dbfilename:快照备份文件名字,前面说过默认操作的文件名称,这里就可以改变了
如改变成dump10.rdb文件,那么就操作dump10.rdb文件了
dir:快照备份文件保存的目录(RDB和AOF都操作这个),即是保存的目录,也是读取的目录
默认为当前目录(这里就说明了,的确是操作当前目录的,当然你也可以改变)
优势and劣势:
优:适合大规模数据恢复,对数据完整性和一致行要求不高
劣:一定间隔备份一次,意外down掉,就失去最后一次快照的所有修改
AOF:
Append Only File
以日志的形式记录每个写操作
将redis执行过的写指令全部记录下来(读操作不记录)
即写的指令,一般包括,增删改,而查询不会记录
redis在启动之初会读取该文件从头到尾执行一遍,这样来重新构建数据
开启AOF:
为了避免失误,最好将redis.conf总配置文件备份一下,然后再修改内容如下:
appendonly yes #原来这个是no,改成yes
appendfilename appendonly.aof  #当我们redis启动时(不是客户端的开启或者启动)
#就会创建这个文件,前提是设置了yes
#原来这个也是这样,即不变,创建的文件名称,默认在当前目录,因为dir配置的值就是./
#注意,若你没有关闭redis,然后启动redis的话,则使用覆盖的这个文件
#即上一个redis启动的文件,因为有覆盖作用,即这里称为覆盖的文件
#因为会继承覆盖的redis的启动的这个文件,自然,若上个redis启动后,删除了这个文件,那么再次启动时,自然就没有了
#最后注意:这个文件的设置既是对应名称,也是对应的读取文件
当设置了yes时,客户端的启动只会读取这个appendonly.aof文件了(这里是设置成appendonly.aof)
而更新对应的appendonly.aof文件,大概每秒更新一次,后面会有介绍
而不会读取对应的dump.rdb文件了(设置成这个名称的)
但也只是不会读取dump.rdb文件而已,对应的保存(备份),还是会保存(备份)的
而设置成no时,自然的只会读取dump.rdb文件,当然,设置了no,对应的appendonly.aof文件都不会操作了
即创建都不会创建,读取和保存,自然也不会进行操作了
重新启动redis,以新配置文件启动:
redis-server /usr/local/redis5.0.4/redis.conf #当然找到对应的配置文件
#这里就是/usr/local/redis5.0.4/redis.conf了
连接redis,加数据,删库,退出
查看当前文件夹多一个appendonly.aof文件,看看文件中的内容,保存的都是 写 操作
对应文件类容对应写的操作(增删改),如下:客户端的操作是
127.0.0.1:6379> keys *
(empty list or set)
127.0.0.1:6379> set 1 1
OK
127.0.0.1:6379> flushall
OK
127.0.0.1:6379> shutdown
not connected> exit
127.0.0.1:6379> set 2 2
OK
127.0.0.1:6379> shutdown
not connected> exit
*2
$6
SELECT
$1
0
#上面5个数据代表启动,前提需要先进行增删改才会保存
*3
$3
#上面两个代表增加和修改
set #第一个set命令
$1 #代表数据,即分隔符
1
$1
1
*1
$8
flushall
#上面三个代表整体flushall命令,即*1,$8,flushall
#这里就是第二次开启客户端
*2
$6
SELECT
$1
0
#上面5个数据代表启动
*3
$3
#上面两个代表增加和修改,若是*2,$3则代表删除
set
$1
2
$1
2
#注意:除了主要命令外,其他的解释数据不要动,如flushall上面的$8,因为需要解释,否则启动时,会连接不了
#当然他只会解释对应的数据,你随便写的话,若是解释不了的,也是连接不了的
我们发现,的确只有增删改进行保存了,且是追加在文件后面,修改的操作自己去实验(我通过实验,的确也在里面)
文件中其中的flushall一句要删除,否则前面的数据恢复不了,因为是一路读取过去的,即相当于对应的命令再次一路执行一遍
这里可以进行回滚的操作,手动进行(操作删除flushall)
编辑这个文件,最后要 :wq! 强制执行(保存),实际上只需要:wq即可,可能有些时候需要强制或者文件的位置需要强制
但大多数情况下,只需要:wq即可
只需要重新连接,对应的flushall命令前面的数据就恢复成功了
共存?谁优先?,虽然前面说过是AOP优先,但这里再次说明一下:
我们查看redis.conf文件,AOF和RDB两种备份策略可以同时开启,那系统会怎样选择?
动手试试,编辑appendonly.aof,胡搞乱码,保存退出
启动redis 失败,所以是AOF优先载入来恢复原始数据,因为AOF比RDB数据保存的完整性更高
你也可以试着删除该文件,实际上删除后,启动客户端,发现都为空了
修复AOF文件,杀光不符合redis语法规范的代码
reids-check-aof --fix appendonly.aof #删除从操作开始,即删除该对应的错误位置的操作整体,以及后面的所有数据
#若是启动的解释出错,且是第一个(注意是第一个)
#自然,该文件就是空文件了,即所有解释都删除了,以此类推,会删除对应内部的其他解释
#若是单独的,不在操作里面,自然就是删除单独的,并删除后面的所有解释
#可以自己测试
与AOF相关的配置:
appendonly:开启aof模式
appendfilename:aof的文件名字,最好别改
appendfsync:追写策略
对应的三个值:
第一个:always,每次数据变更,就会立即记录到磁盘,性能较差,但数据完整性好
第二个:everysec,默认设置,异步操作,每秒记录,如果一秒内宕机,会有数据丢失,默认这个,即这里每秒更新一次
第三个:no,不追写
no-appendfsync-on-rewrite:重写时是否运用Appendfsync追写策略,用默认no即可,保证数据安全性
因为AOF采用文件追加的方式(如对应的appendonly.aof文件会越来越大),为了解决这个问题,增加了重写机制
redis会自动记录上一次AOF文件的大小,当AOF文件大小达到预先设定的大小时,redis就会启动
AOF文件进行内容压缩,只保留可以恢复数据的最小指令集合
auto-aof-rewrite-percentage:如果AOF文件大小已经超过原来的100%,也就是一倍,才重写压缩,默认100,即100%
auto-aof-rewrite-min-size:如果AOF文件已经超过了64mb,才重写压缩,默认64mb
总结(如何选择?):
RDB:只用作后备用途,建议15分钟备份一次就好
AOF:
在最恶劣的情况下,也只丢失不超过2秒的数据,数据完整性比较高,但代价太大,会带来持续的IO(每秒更新)
对硬盘的大小要求也高,默认64mb太小了,企业级最少都是5G以上
后面要学习的master/slave才是新浪微博的选择(主要使用这个,前面两个都有大的缺点,虽然AOF要小一点)
事务:
可以一次执行多个命令,是一个命令组,一个事务中,所有命令都会序列化(排队),不会被插队
一个队列中,一次性,顺序性,排他性的执行一系列命令
三特性:
隔离性:所有命令都会按照顺序执行,事务在执行的过程中,不会被其他客户端送来的命令打断
没有隔离级别:队列中的命令没有提交之前都不会被实际的执行
且不存在事务中查询会看到事务里的更新,因为都在一个命令组,并没有执行
事务外查询不能看到,因为已经执行了,这两个头疼的问题
实际上事务基本都是这样,只是redis是根据结果返回的,而不是mysql的操作表返回
不保证原子性:冤有头债有主,如果一个命令失败,但是别的命令可能会执行成功,没有回滚
也就是相当于一路执行过去
三步走:
开启multi
入队queued
执行exec
与关系型数据库事务相比:
multi:可以理解成关系型事务中的 begin
exec :可以理解成关系型事务中的 commit
discard :可以理解成关系型事务中的 rollback
一起执行:
开启事务,加入队列,一起执行,并成功:
127.0.0.1:6379> multi # 开启事务
OK
127.0.0.1:6379> set k1 v1
QUEUED # 加入队列,这时我们的命令执行就是加入队列了,无论是否对错
127.0.0.1:6379> set k2 v2
QUEUED # 加入队列
127.0.0.1:6379> get k2
QUEUED # 加入队列
127.0.0.1:6379> set k3 v3
QUEUED # 加入队列
127.0.0.1:6379> exec # 执行,一起成功!
1) OK
2) OK
3) "v2"
4) OK
#的确都是一路执行
一起不执行:
放弃之前的操作,恢复到原来的值
127.0.0.1:6379> multi # 开启事务
OK
127.0.0.1:6379> set k1 v1111
QUEUED
127.0.0.1:6379> set k2 v2222
QUEUED
127.0.0.1:6379> discard # 放弃操作,或者说不执行了,并关闭事务
OK
127.0.0.1:6379> get k1
"v1" # 还是原来的值
一粒老鼠屎坏一锅汤 :
一句报错,全部取消,恢复到原来的值:
127.0.0.1:6379> flushall
OK
127.0.0.1:6379> keys *
(empty list or set)
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set 1 1
QUEUED
127.0.0.1:6379> set 2 2
QUEUED
127.0.0.1:6379> set 1 1 1
#出现不合理的数据,但是却并不会造成事务的结束,因为他还是操作了(可以识别),只是执行格式不对而已
#可以识别的,基本可以执行,只是执行这个语句时,会报错
QUEUED
127.0.0.1:6379> exec
1) OK
2) OK
3) (error) ERR syntax error
#由于并不会造成事务的结束,所有还会执行
127.0.0.1:6379> keys *
1) "2"
2) "1"
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set 3 3
QUEUED
127.0.0.1:6379> setlalala
#出现不合理的数据,会造成事务的结束(执行时),因为识别不了
#这里与mysql不同,mysql基本都会可以提交
#因为对应识别不了基本不会使得事务出现问题,或者自动回滚(mysql的自动回滚),因为他是操作表的,而不是结果的
(error) ERR unknown command `setlalala`, with args beginning with:
#出现了错误,即不是QUEUED,相当于使得提交执行时,执行不了
127.0.0.1:6379> exec #由于有识别不了的,那么提交执行失败
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> keys *
1) "2"
2) "1"
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set 99 99
QUEUED
127.0.0.1:6379> setlalala
(error) ERR unknown command `setlalala`, with args beginning with:
127.0.0.1:6379> exec #由于有识别不了的,那么提交执行失败,且其他的也不会执行
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> keys *
1) "2"
2) "1"
#发现的确没有执行
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set 99 99
QUEUED
127.0.0.1:6379> setlalala
(error) ERR unknown command `setlalala`, with args beginning with:
127.0.0.1:6379> discard #虽然识别不了,但是可以回滚,因为我们不执行
OK
127.0.0.1:6379> keys *
1) "2"
2) "1"
冤有头债有主 :
追究责任,谁的错,找谁去:
127.0.0.1:6379> keys *
1) "a"
127.0.0.1:6379> get a
"a"
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set 6 6
QUEUED
127.0.0.1:6379> incr a
#可以识别,只是操作结果报错而已,类似java中的通过编译(识别不了就可以说是编译不通过),只是执行时报错
#而mysql可以说没有编译这个环节,所有识别不了,还是可以执行
QUEUED
127.0.0.1:6379> exec
1) OK #执行成功
2) (error) ERR value is not an integer or out of range #报错了
127.0.0.1:6379> keys *
1) "a"
2) "6"
watch监控 :
测试:模拟收入与支出:
正常情况下:
127.0.0.1:6379> set in 100 # 收入100元
OK
127.0.0.1:6379> set out 0 # 支出0元
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> decrby in 20 # 收入-20
QUEUED
127.0.0.1:6379> incrby out 20 # 支出+20
QUEUED
127.0.0.1:6379> exec
1) (integer) 80 #收入-20
2) (integer) 20 #支出+20
# 结果,没问题!
127.0.0.1:6379> get in
"80"
127.0.0.1:6379> get out
"20"
特殊情况下:
假设我们新建一个窗口,在没有执行命令时,也就是没有操作exec时,原来的键是可以被访问的,这时是可以进行修改
使得我们在修改对应数据时,对应操作事务的客户端突然出现数据的变化(与原来的结果不符)
而由于客户端可能是多个,那么我们需要这个数值,在操作事务的客户端里不会进行改变
也就是说事务的操作不能受外界影响,因为如果出现外界的影响,那么可能会出现数据的不合理性
比如余额不能少于0,一般我们会在事务之前判断,如果这时原来余额是30,那么判断后,是会进行事务操作(假如是减少20)
这时若我们修改成了10(可能并发会造成),那么就出现了数据不合理性
虽然可以在事务之后判断,但是在判断之前,该数据还是可能会处于不合理的
所以当事务进行提交时,若对应操作的键别人已经使用过了,则这个事务不进行执行,即exec返回(nil)即返回空
127.0.0.1:6379> watch in
# 监控收入in,只操作当前窗口的exec,即只有当前的窗口会进行监听
# 其他客户端进行exec时,不会监听,也就是不进行对应的监听判断
# 因为客户端之间只共享数据库而已,就如mysql一样
# 只是共享数据而已
# 基本只会操作第一个exec命令,或者说第一个事务(因为exec命令会将所有监控进行失效或者取消)
# 类似于后面的unwatch命令
# 当其他客户端进行操作了in时(增删改)
# 那么exec命令不操作任何命令
#注意:在开启事务的情况下,第一个exec执行完后,会清空所有的监听
#当然,事务和监听都只是操作当前客户端,其他客户端并不会进行操作,所有就需要监听
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> decrby in 20
QUEUED
127.0.0.1:6379> incrby out 20
QUEUED
127.0.0.1:6379> exec
(nil)
# 在exec命令之前,我开启了另一个窗口(线程),对监控的in(要先监控,否则不会记录是否被操作)做了修改
#所以本次的事务将被打断或者不执行(失效)
#类似于"乐观锁",即在执行时,会判断一下(监控的作用,当前监听的窗口会,其他窗口没有对应的监听,则不会)
#是否被操作过(增删改),没有则执行命令,否则返回(nil)即返回空
#所以其他客户端操作了这个键时
#对应的exec命令就会判断监听的键是否被操作,然后进行相应的是否执行
unwatch:取消watch命令对所有key的操作
只对当前窗口有效,即只清空当前窗口的watch命令的监听
与watch命令是一样的,都是操作当前窗口,其他窗口在使用exec命令时,不会进行判断监听
一旦执行了exec命令,那么之前加的所有监控自动失效(相当于自动执行了unwatch命令)
Redis的发布订阅:
进程间的一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息
例如:微信订阅号订阅一个或多个频道
其中一个客户端(称为1号窗口):
127.0.0.1:6379> subscribe cctv1 cctv5 cctv6 # 订阅三个频道
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "cctv1"
3) (integer) 1 #代表第一个
1) "subscribe"
2) "cctv5"
3) (integer) 2 #代表第二个
1) "subscribe"
2) "cctv6"
3) (integer) 3 #代表第一个
其中一个客户端(称为2号窗口):
127.0.0.1:6379> publish cctv5 NBA # 2.发送消息给cctv5
(integer) 1 #操作成功,返回对应操作的客户端个数
#若对应的客户端全部关闭,或者没有对应的频道(这里就是cctv5),则返回0
当2号窗口发送消息时,对应的所有的频道(不同客户端的也算),都会接收到信息,或者说,我们对这个频道的操作
给所有的客户端进行判断,若有这个频道,则将发送的信息给所有客户端显示(每个客户端基本都有不同的编号),从而都可显示
再次回到1号窗口:
127.0.0.1:6379> subscribe cctv1 cctv5 cctv6 # 订阅三个频道
Reading messages... (press Ctrl-C to quit)
1) "subscribe" #频道
2) "cctv1" #频道名称
3) (integer) 1
1) "subscribe" #频道
2) "cctv5" #频道名称
3) (integer) 2
1) "subscribe" #频道
2) "cctv6" #频道名称
3) (integer) 3
#发现的确多个信息
# cctv5接收到推送过来的信息
1) "message"  #信息
2) "cctv5" #操作的频道名称
3) "NBA" #发送的内容
主从复制:
就是 redis集群的策略
配从(库)不配主(库):小弟可以选择谁是大哥,但大哥没有权利去选择小弟
读写分离:主机写,从机读
准备三台服务器(分别为1号机器,2号机器,3号机器),并关闭对应的防火墙:
systemctl stop firewalld.service #关闭防火墙,使得端口可以被访问
然后修改对应的redis.conf:
#通过底行模式,输入/bind 找到bind
#我们可以找到bind 127.0.0.1,即允许对应的配置地址可以访问redis的服务
#即连接服务,即客户端的连接,否则一般默认只能是本地客户端进行连接
#当然,若删除或不写,则所有服务器都可以访问
bind 0.0.0.0 #则基本允许其他所有服务器访问了
#这里再下面的介绍中并不会体现,主要是客户端与服务器连接时,会有体现,就如连接mysql类似
启动三台redis客户端,并查看每台机器的角色,都是master(主)
info replication
测试开始
首先,将三个客户端的数据全都清空(flushall命令),第一台添加值
mset k1 v1 k2 v2 #添加多条数据
其余两台机器,复制(找大哥,即对应的服务器地址):
slaveof 192.168.164.128 6379 #若没有对应的端口或者地址,虽然可以执行,但是对应会显示没有上线
#即虽然会进行等待同步(第一次认大哥和后面大哥的操作,进行同步),但也只是等待而已,主要看主机,而不是从机
#当小弟,不是连接服务,只是给一个通道,可以进行同步,如主操作了对应数据,就会同步到小弟身上
#如发送命令的数据,小弟进行操作
#与上面的bind无关
这时,这个两台机器对应的角色就是slave(从)了
这时可以发现,对应的从机器的键同步了主机器,且不能进行操作(增删改),而主可以进行(增删改查)
虽然主要用来进行写的(增删改),但其实查询也是可以进行操作的,只是主要用来写
因为有小弟了,为什么需要自己亲自动手呢
而正是因为同步,所有从,基本只能进行查,因为必须要向着大哥看齐
第一台再添加值:
set k3 v3
再次进行看看从机器,发现的确进行了同步
总结:
思考1:slave之前的k1和k2是否能拿到:
可以获得,只要跟了大哥(主),之前的数据也会立刻同步
思考2:slave之后的k3是否能拿到:
可以获得,只要跟了大哥,数据会立刻同步
思考3:同时添加k4,结果如何:
主机(master)可以添加成功,从机(添加失败)失败,从机只负责读取
数据,无权写入数据,这就是"读写分离"
思考4:主机shutdown,从机如何:
从机仍然是slave,并显示他们的master已离线(是否上线对其是否启动,启动则上线,关闭则下线,不是与客户端的开启有关)
客户端只是用来操作对应的数据库的(相当于一个窗口),就如mysql在Linux显示的窗口类似,都是对文件的操作
思考5:主机重启,从机又如何:
从机仍然是slave,并显示他们的master已上线
思考6:从机死了,主机如何,从机归来身份是否变化:
主机没有变化,只是显示少了几个slave(从机死几个少几个),即存在的小弟
主机和从机没有变化,而重启归来的从机自立门户成为了master,不和原来的集群在一起了
血脉相传:
一个主机理论上可以多个从机,但是这样的话,这个主机会很累
我们可以使用java面向对象继承中的传递性来解决这个问题,减轻主机的负担
形成祖孙三代(爷,爸,儿三代):
#2号机器
127.0.0.1:6379> slaveof 192.168.164.128 6379 # 129跟随128(1号机器)
OK
#--------------------分割线-------------------
#3号机器
127.0.0.1:6379> slaveof 192.168.164.129 6379 # 130跟随129(2号机器)
#虽然129的角色显示仍然是从,实际上对应的显示不重要,主要是操作同步
OK
#但是,主只能是一个,即最顶端的一个,所以这里就是128
注意:若出现连接不上的,即上面明明执行了,但对应的info replication命令的状态却没有显示出被连接
一般是不允许访问端口,即大概是防火墙忘记关了
谋权篡位:
1个主机,2个从机,当1个主机挂掉了,只能从2个从机中再次选1个主机
国不可一日无君,军不可一日无帅
手动选老大:
模拟测试:1为master,2和3为slave,当1挂掉后,2篡权为master,若原来3是跟2的,那么这时仍然是跟2,否则需要自己指定跟随
slaveof no one # 在2号机器上执行,没有人能让我臣服,那我就是老大
slaveof 192.168.204.142 6379 # 3跟随2号
#在原来没有跟随的情况下执行,必须爷,爸,儿这里,儿就是跟随爸的,上面的血脉相传
思考:当1再次回归,会怎样:
2和3已经形成新的集群,和1没有任何的关系了。所以1成为了光杆司令
由此可以知道,slave的指向地址就是大哥(相对于自己来说,虽然有层级)
他们的联系基本就是这个地址的联系,没有这个自然就是没有关系了
复制原理:
图解:

完成上面几个步骤后就完成了从服务器数据初始化的所有操作,从服务器此时可以接收来自用户的读请求
全量复制:Slave初始化阶段,这时Slave需要将Master上的所有数据都复制一份
然后slave接收到数据文件后存盘,并加载到内存中(上面的步骤1234),相当于前面说过的第一次认大哥
增量复制:Slave初始化后,开始正常工作时主服务器发生的写操作同步到从服务器的过程(上面的步骤56)
相当于前面说过的后面大哥的操作
但只要是重新连接master,一次性(全量复制)同步将自动执行,因为是第一次认大哥
Redis主从同步策略:主从刚刚连接的时候,进行全量同步,全同步结束后,进行增量同步
当然,如果有需要,slave 在任何时候都可以发起全量同步(因为可以手动的进行连接)
redis 策略是,无论如何,首先会尝试进行增量同步,如不成功,要求从机进行全量同步(自己手动选择进行连接)
最后注意:当我们使用启动redis覆盖时
即已经启动redis,但不使用shutdown关闭redis,即使用exit退出客户端或者ctrl+c退出客户端,然后再次启动redis
那么会继承覆盖的数据(即这个时候并不会使用配置文件),而是共享同一个地方的内存,这时与配置文件无关
或者说,并没有进行执行
而当shutdown关闭后,这个内存也就关闭了,所以需要配置文件的读取
哨兵模式:
自动版的谋权篡位
有个哨兵一直在巡逻,突然发现,老大挂了,小弟们会自动投票,从众小弟中选出新的老大
Sentinel是Redis的高可用性解决方案:
由一个或多个Sentinel实例组成的Sentinel系统可以监视任意多个主服务器,以及所有从服务器
并在被监视的主服务器进入下线状态时,自动将下线主服务器属下的某个从服务器升级为新的主服务器
然后由新的主服务器代替已下线的主服务器继续处理命令请求
模拟测试:
1主,2和3从(2和3不是互相连接的)
每一台服务器中创建一个配置文件sentinel.conf,名字绝不能错
实际上可以随便改变,因为我们是指定名称的,但是最好不要改变,防止出现特殊问题,并编辑sentinel.conf
# sentinel monitor 被监控主机名 ip port 票数
# 主机名可以自定义,但最好不要是一样的,一样的中PID大的进行覆盖,其他一样的不进行监听,相当于没有哨兵模式
# 那么这时若出现单机,即不选举了(单机不选举)
sentinel monitor redis128 192.168.164.128 6379 1 #ip都是基本自己的主机ip
#正是因为这个票数,使得选择一个主机作为老大,一般都指定自己
启动服务的顺序(实际上顺序并没有必须要主到从,即可以随便的顺序):主Redis --> 从Redis --> Sentinel1/2/3:
redis-sentinel sentinel.conf
#注意:不要用这个命令启动客户端(使用配置文件),若对应的客户端是没有启动的,那么这个配置文件就被占用了
#即客户端就不能使用这个配置文件了
服务肯定是需要启动的,需要进行监听投票的操作,否则哨兵模式相当于没操作
将1号老大挂掉,后台自动发起激烈的投票,选出新的老大,注意要等待一会(对应机器上面会出现"!"号,表示在操作)
因为投票需要时间,所以接下来就可以查看从机器是否有对应的关系了
查看最后权利的分配:
半数机制:对应于半数以上的服务器相同id来说的选举是否,当然这里是针对投票,而不是机器
3成为了新的老大,2还是小弟(半数机制,一般ip后面的后投票,因为对应前面的,如129大于130,那么129一般对应投票id大)
在启动服务时可以看到对应的PID,大的优先级一般要高,当然,后投票的一般也会得到前面的票数
所以也可以说,优先级小的容易变成主(老大),配置文件可以设置优先级
实际上有时也可以说成优先级大的容易变成老大
这是针对于优先级这个名称来说的,这时可以说优先级大的后投票,或者设置的优先级数量越小(对于设置优先级来说的变化)优先级越大等等
不同的优先级就看优先级,而不用这样的看PID
相同的基本就要看PID了,但也要注意:基本只有在操作时,才会进行半数的投票
实际上哨兵机制有一个自动的投票(其中基本完全是看投票的数量,虽然有后投票得到前面投票的)
若机器的投票数相同(一般都设置为1,且是本机),那么就不会选择
若不相同,那么多数的自动变成主,其余变成从(没有哨兵机制的除外,即自定义名称相同的)
当然也有可能是因为票数多,且后上的半数机制,造成的主
所以一般我们操作(编辑)sentinel.conf时,即操作对应的配置时,对应的自定义名称要不同,且ip最好是本机
所以130就是老大了,这里的3就是这样,虽然以前说过,最好要单数的机器
实际上3和4都是不能挂两台(因为选举总数的一半不是剩余的机器),安全性一样
所以我们选择少的服务器(即单数),节省成本,实际上双数也可以
当然了,对应的半数选举是不分单数和双数的,只要超过半数即可
如果之前的老大再次归来呢:
1号再次归来,自己成为了master,和3平起平坐
过了几秒之后,被哨兵检测到了1号机的归来(检测需要时间),1号你别自己玩了,进入集体吧
但是新的老大已经产生了,你只能作为小弟再次进入集体
最后要注意:当开启哨兵模式时,基本上每个机器都会保留哨兵的状态(有的前提下,否则仍然是默认主状态)
也就是说,下次关闭在启动时
也就不是默认的主状态了,而是上次哨兵进行操作的状态(保存的),而由于这个状态是保留的
我们可以删除对应的文件,一般在原来的解压的redis目录里面,可以重新解压安装进行删除(对应的文件删除百度找答案吧)
缺点:
由于所有的写操作都是在master上完成的
然后再同步到slave上,所以两台机器之间通信会有延迟
当系统很繁忙的时候,延迟问题会加重
slave机器数量增加,问题也会加重
到这里可以发现,使用哨兵模式,真正的实现的高可用,即基本一定可以写(会选举出来,主)和读(从)
这里并没有挂掉一半(向上取整)而使得不执行的
因为这里只是选举主从(内部)时进行半数操作,而不会选举机器(外部,如zookeeper)
所以基本只有当全部的机器都挂掉,才会使得不能写和读
总配置redis.conf 详解 :
# Redis 配置文件示例
# 注意单位: 当需要配置内存大小时, 可能需要指定像1k,5GB,4M等常见格式
# 1k => 1000 bytes
# 1kb => 1024 bytes
# 1m => 1000000 bytes
# 1mb => 1024*1024 bytes
# 1g => 1000000000 bytes
# 1gb => 1024*1024*1024 bytes
# 单位是对大小写不敏感的 1GB 1Gb 1gB 是相同的
####################################### INCLUDES 包含文件相关 #######################################
# 可以在这里包含一个或多个其他的配置文件,如果你有一个适用于所有Redis服务器的标准配置模板
# 但也需要一些每个服务器自定义的设置,这个功能将很有用,被包含的配置文件也可以包含其他配置文件
# 所以需要谨慎的使用这个功能
# 注意"inclue"选项不能被admin或Redis哨兵的"CONFIG REWRITE"命令重写
# 因为Redis总是使用最后解析的配置行最为配置指令的值
# 你最好在这个文件的开头配置includes来避免它在运行时重写配置
# 如果相反你想用includes的配置覆盖原来的配置,你最好在该文件的最后使用include
#
# include /path/to/local.conf
# include /path/to/other.conf
####################################### GENERAL 综合配置 #######################################
# 默认Rdis不会作为守护进程运行,如果需要的话配置成'yes'
# 注意配置成守护进程后Redis会将进程号写入文件/var/run/redis.pid
daemonize no
# 当以守护进程方式运行时,默认Redis会把进程ID写到 /var/run/redis.pid,你可以在这里修改路径
pidfile /var/run/redis.pid
# 接受连接的特定端口,默认是6379
# 如果端口设置为0,Redis就不会监听TCP套接字
port 6379
# TCP listen() backlog.
# server在与客户端建立tcp连接的过程中,SYN队列的大小
# 在高并发环境下你需要一个高backlog值来避免慢客户端连接问题
# 注意Linux内核默默地将这个值减小到/proc/sys/net/core/somaxconn的值
# 所以需要确认增大somaxconn和tcp_max_syn_backlog两个值来达到想要的效果
tcp-backlog 511
# 默认Redis监听服务器上所有可用网络接口的连接,可以用"bind"配置指令跟一个或多个ip地址来实现
# 监听一个或多个网络接口
# 示例:
# bind 192.168.1.100 10.0.0.1
# bind 127.0.0.1
# 指定用来监听Unix套套接字的路径,没有默认值,所以在没有指定的情况下Redis不会监听Unix套接字
# unixsocket /tmp/redis.sock
# unixsocketperm 755
# 一个客户端空闲多少秒后关闭连接(0代表禁用,永不关闭)
timeout 0
# TCP keepalive.
# 如果非零,则设置SO_KEEPALIVE选项来向空闲连接的客户端发送ACK,由于以下两个原因这是很有用的:
# 1)能够检测无响应的对端
# 2)让该连接中间的网络设备知道这个连接还存活
# 在Linux上,这个指定的值(单位:秒)就是发送ACK的时间间隔
# 注意:要关闭这个连接需要两倍的这个时间值
# 在其他内核上这个时间间隔由内核配置决定
# 这个选项的一个合理值是60秒
tcp-keepalive 0
# 指定服务器调试等级
# 可能值:
# debug (大量信息,对开发/测试有用)
# verbose (很多精简的有用信息,但是不像debug等级那么多)
# notice (适量的信息,基本上是你生产环境中需要的)
# warning (只有很重要/严重的信息会记录下来)
loglevel notice
# 指明日志文件名。也可以使用"stdout"来强制让Redis把日志信息写到标准输出上
# 注意:如果Redis以守护进程方式运行,而设置日志显示到标准输出的话,日志会发送到/dev/null
logfile ""
# 要使用系统日志记录器,只要设置 "syslog-enabled" 为 "yes" 就可以了
# 然后根据需要设置其他一些syslog参数就可以了
# syslog-enabled no
# 指明syslog身份
# syslog-ident redis
# 指明syslog的设备,必须是user或LOCAL0 ~ LOCAL7之一
# syslog-facility local0
# 设置数据库个数,默认数据库是 DB 0
# 可以通过select <dbid> (0 <= dbid <= 'databases' - 1 )来为每个连接使用不同的数据库
databases 16
################################ SNAPSHOTTING 快照,持久化操作配置 ##################################
# 把数据库存到磁盘上:
# save <seconds> <changes>
# 会在指定秒数和数据变化次数之后把数据库写到磁盘上。
# 下面的例子将会进行把数据写入磁盘的操作:
# 900秒(15分钟)之后,且至少1次变更
# 300秒(5分钟)之后,且至少10次变更
# 60秒之后,且至少10000次变更
# 注意:你要想不写磁盘的话就把所有 "save" 设置注释掉就行了
# 通过添加一条带空字符串参数的save指令也能移除之前所有配置的save指令
# 像下面的例子:
# save ""
save 900 1
save 300 10
save 60 10000
# 默认如果开启RDB快照(至少一条save指令)并且最新的后台保存失败,Redis将会停止接受写操作
# 这将使用户知道数据没有正确的持久化到硬盘,否则可能没人注意到并且造成一些灾难
# 如果后台保存进程能重新开始工作,Redis将自动允许写操作
# 然而如果你已经部署了适当的Redis服务器和持久化的监控,你可能想关掉这个功能以便于即使是
# 硬盘,权限等出问题了Redis也能够像平时一样正常工作
stop-writes-on-bgsave-error yes
# 当导出到 .rdb 数据库时是否用LZF压缩字符串对象
# 默认设置为 "yes",因为几乎在任何情况下它都是不错的
# 如果你想节省CPU的话你可以把这个设置为 "no",但是如果你有可压缩的key和value的话
# 那数据文件就会更大了
rdbcompression yes
# 因为版本5的RDB有一个CRC64算法的校验和放在了文件的最后,这将使文件格式更加可靠
# 但在生产和加载RDB文件时
# 这有一个性能消耗(大约10%),所以你可以关掉它来获取最好的性能
# 生成的关闭校验的RDB文件有一个0的校验和,它将告诉加载代码跳过检查
rdbchecksum yes
# 持久化数据库的文件名
dbfilename dump.rdb
# 工作目录
# 数据库会写到这个目录下,文件名就是上面的 "dbfilename" 的值
# 累加文件也放这里
# 注意你这里指定的必须是目录,不是文件名
dir ./
################################## REPLICATION 主从复制的配置 ##################################
# 主从同步,通过 slaveof 指令来实现Redis实例的备份
# 注意,这里是本地从远端复制数据,也就是说,本地可以有不同的数据库文件、绑定不同的IP、监听不同的端口
# slaveof <masterip> <masterport>
# 如果master设置了密码保护(通过 "requirepass" 选项来配置)
# 那么slave在开始同步之前必须进行身份验证,否则它的同步请求会被拒绝。
# masterauth <master-password>
# 当一个slave失去和master的连接,或者同步正在进行中,slave的行为有两种可能:
# 1) 如果 slave-serve-stale-data 设置为 "yes" (默认值),slave会继续响应客户端请求
# 可能是正常数据,也可能是还没获得值的空数据
# 2) 如果 slave-serve-stale-data 设置为 "no"
# slave会回复"正在从master同步(SYNC with master in progress)"来处理各种请求
# 除了 INFO 和 SLAVEOF 命令
# 也就是yes可能是得到没有更新的(因为不是必须要先同步),而no基本得到的是最新的(因为必须要先同步)
slave-serve-stale-data yes
# 你可以配置salve实例是否接受写操作
# 可写的slave实例可能对存储临时数据比较有用(因为写入salve的数据在同master同步之后将很容被删除)
# 但是如果客户端由于配置错误在写入时也可能产生一些问题
# 从Redis2.6默认所有的slave为只读
# 注意:只读的slave不是为了暴露给互联网上不可信的客户端而设计的,它只是一个防止实例误用的保护层
# 一个只读的slave支持所有的管理命令比如config,debug等
# 为了限制你可以用'renamecommand'来隐藏所有的管理和危险命令来增强只读slave的安全性
slave-read-only yes
# slave根据指定的时间间隔向master发送ping请求
# 时间间隔可以通过 repl_ping_slave_period 来设置
# 默认10秒
# repl-ping-slave-period 10
# 以下选项设置同步的超时时间
# 1)slave在与master SYNC期间有大量数据传输,造成超时
# 2)在slave角度,master超时,包括数据、ping等
# 3)在master角度,slave超时,当master发送REPLCONF ACK pings
# 确保这个值大于指定的repl-ping-slave-period,否则在主从间流量不高时每次都会检测到超时
# repl-timeout 60
# 是否在slave套接字发送SYNC之后禁用 TCP_NODELAY
# 如果你选择"yes"Redis将使用更少的TCP包和带宽来向slaves发送数据,但是这将使数据传输到slave
# 上有延迟,Linux内核的默认配置会达到40毫秒
# 如果你选择了 "no" 数据传输到salve的延迟将会减少但要使用更多的带宽
# 默认我们会为低延迟做优化,但高流量情况或主从之间的跳数过多时,把这个选项设置为"yes",是个不错的选择
repl-disable-tcp-nodelay no
# 设置数据备份的backlog大小,backlog是一个slave在一段时间内断开连接时记录salve数据的缓冲
# 所以一个slave在重新连接时,不必要全量的同步,而是一个增量同步就足够了
# 将在断开连接的这段时间内slave丢失的部分数据传送给它
# 同步的backlog越大,slave能够进行增量同步并且允许断开连接的时间就越长
# backlog只分配一次并且至少需要一个slave连接
# repl-backlog-size 1mb
# 当master在一段时间内不再与任何slave连接,backlog将会释放,以下选项配置了从最后一个
# slave断开开始计时多少秒后,backlog缓冲将会释放
# 0表示永不释放backlog
# repl-backlog-ttl 3600
# slave的优先级是一个整数展示在Redis的Info输出中,如果master不再正常工作了
# 哨兵将用它来选择一个slave提升=升为master
# 优先级数字小的salve会优先考虑提升为master,所以例如有三个slave优先级分别为10,100,25
# 哨兵将挑选优先级最小数字为10的slave
# 0作为一个特殊的优先级,标识这个slave不能作为master
# 所以一个优先级为0的slave永远不会被哨兵挑选提升为master
# 默认优先级为100
slave-priority 100
# 如果master少于N个延时小于等于M秒的已连接slave,就可以停止接收写操作
# N个slave需要是"oneline"状态
# 延时是以秒为单位,并且必须小于等于指定值,是从最后一个从slave接收到的ping(通常每秒发送)开始计数
# This option does not GUARANTEES that N replicas will accept the write, but
# will limit the window of exposure for lost writes in case not enough slaves
# are available, to the specified number of seconds.
# 例如至少需要3个延时小于等于10秒的slave用下面的指令:
# min-slaves-to-write 3
# min-slaves-max-lag 10
# 两者之一设置为0将禁用这个功能
# 默认 min-slaves-to-write 值是0(该功能禁用)并且 min-slaves-max-lag 值是10
################################## SECURITY 安全相关配置 ##################################
# 要求客户端在处理任何命令时都要验证身份和密码
# 这个功能在有你不信任的其它客户端能够访问redis服务器的环境里非常有用
# 为了向后兼容的话这段应该注释掉。而且大多数人不需要身份验证(例如:它们运行在自己的服务器上)
# 警告:因为Redis太快了,所以外面的人可以尝试每秒150k的密码来试图破解密码
# 这意味着你需要一个高强度的密码,否则破解太容易了,下面的foobared一般就是密码
# requirepass foobared
# 命令重命名
# 在共享环境下,可以为危险命令改变名字,比如,你可以为 CONFIG 改个其他不太容易猜到的名字
# 这样内部的工具仍然可以使用,而普通的客户端将不行
# 例如:
# rename-command CONFIG b840fc02d524045429941cc15f59e41cb7be6c52
# 也可以通过改名为空字符串来完全禁用一个命令
# rename-command CONFIG ""
# 请注意:改变命令名字被记录到AOF文件或被传送到从服务器可能产生问题
################################## LIMITS 范围配置 ##################################
# 设置最多同时连接的客户端数量,默认这个限制是10000个客户端
# 然而如果Redis服务器不能配置处理文件的限制数来满足指定的值
# 那么最大的客户端连接数就被设置成当前文件限制数减32(因为Redis服务器保留了一些文件描述符作为内部使用)
# 一旦达到这个限制,Redis会关闭所有新连接并发送错误'max number of clients reached'
# maxclients 10000
# 不要用比设置的上限更多的内存,一旦内存使用达到上限
# Redis会根据选定的回收策略(参见:maxmemmory-policy)删除key
# 如果因为删除策略Redis无法删除key,或者策略设置为 "noeviction"
# Redis会回复需要更多内存的错误信息给命令,
# 例如,SET,LPUSH等等,但是会继续响应像Get这样的只读命令
# 在使用Redis作为LRU缓存,或者为实例设置了硬性内存限制的时候(使用 "noeviction" 策略)的时候
# 这个选项通常事很有用的
# 警告:当有多个slave连上达到内存上限的实例时
# master为同步slave的输出缓冲区所需内存不计算在使用内存中
# 这样当驱逐key时,就不会因网络问题 / 重新同步事件触发驱逐key的循环
# 反过来slaves的输出缓冲区充满了key被驱逐的DEL命令,这将触发删除更多的key,直到这个数据库完全被清空为止
# 总之...如果你需要附加多个slave,建议你设置一个稍小maxmemory限制
# 这样系统就会有空闲的内存作为slave的输出缓存区(但是如果最大内存策略设置为"noeviction"的话就没必要了)
# maxmemory <bytes>
# 最大内存策略:如果达到内存限制了,Redis如何选择删除key,你可以在下面五个行为里选:
# volatile-lru -> 根据LRU算法生成的过期时间来删除
# allkeys-lru -> 根据LRU算法删除任何key
# volatile-random -> 根据过期设置来随机删除key
# allkeys->random -> 无差别随机删
# volatile-ttl -> 根据最近过期时间来删除(辅以TTL)
# noeviction -> 谁也不删,直接在写操作时返回错误
# 注意:对所有策略来说,如果Redis找不到合适的可以删除的key都会在写操作时返回一个错误
#     目前为止涉及的命令:set setnx setex append
#     incr decr rpush lpush rpushx lpushx linsert lset rpoplpush sadd
#     sinter sinterstore sunion sunionstore sdiff sdiffstore zadd zincrby
#     zunionstore zinterstore hset hsetnx hmset hincrby incrby decrby
#     getset mset msetnx exec sort
# 默认值如下:
# maxmemory-policy volatile-lru
# LRU和最小TTL算法的实现都不是很精确,但是很接近(为了省内存),所以你可以用样本量做检测
# 例如:默认Redis会检查3个key然后取最旧的那个,你可以通过下面的配置指令来设置样本的个。
# maxmemory-samples 3
################################## APPEND ONLY MODE AOF模式配置 ##################################
# 默认情况下,Redis是异步的把数据导出到磁盘上,这种模式在很多应用里已经足够好
# 但Redis进程出问题或断电时可能造成一段时间的写操作丢失(这取决于配置的save指令)
# AOF是一种提供了更可靠的替代持久化模式,例如使用默认的数据写入文件策略(参见后面的配置)
# 在遇到像服务器断电或单写情况下Redis自身进程出问题但操作系统仍正常运行等突发事件时
# Redis能只丢失1秒的写操作
# AOF和RDB持久化能同时启动并且不会有问题
# 如果AOF开启,那么在启动时Redis将加载AOF文件,它更能保证数据的可靠性
# 请查看 http://redis.io/topics/persistence 来获取更多信息
appendonly no
# 纯累加文件名字(默认:"appendonly.aof")
appendfilename "appendonly.aof"
# fsync() 系统调用告诉操作系统把数据写到磁盘上,而不是等更多的数据进入输出缓冲区
# 有些操作系统会真的把数据马上刷到磁盘上,有些则会尽快去尝试这么做
# Redis支持三种不同的模式:
# no:不要立刻刷,只有在操作系统需要刷的时候再刷,比较快
# always:每次写操作都立刻写入到aof文件,慢,但是最安全
# everysec:每秒写一次,折中方案
# 默认的 "everysec" 通常来说能在速度和数据安全性之间取得比较好的平衡,根据你的理解来决定
# 如果你能放宽该配置为"no" 来获取更好的性能(但如果你能忍受一些数据丢失,可以考虑使用默认的快照持久化模式)
# 或者相反,用"always"会比较慢但比everysec要更安全
# 请查看下面的文章来获取更多的细节
# http://antirez.com/post/redis-persistence-demystified.html
# 如果不能确定,就用 "everysec"
# appendfsync always
appendfsync everysec
# appendfsync no
# 如果AOF的同步策略设置成 "always" 或者 "everysec"
# 并且后台的存储进程(后台存储或写入AOF日志)会产生很多磁盘I/O开销
# 某些Linux的配置下会使Redis因为 fsync()系统调用而阻塞很久
# 注意,目前对这个情况还没有完美修正,甚至不同线程的 fsync() 会阻塞我们同步的write(2)调用
# 为了缓解这个问题,可以用下面这个选项,它可以在 BGSAVE 或 BGREWRITEAOF 处理时阻止fsync()
# 这就意味着如果有子进程在进行保存操作,那么Redis就处于"不可同步"的状态
# 这实际上是说,在最差的情况下可能会丢掉30秒钟的日志数据(默认Linux设定)
# 如果把这个设置成"yes"带来了延迟问题,就保持"no",这是保存持久数据的最安全的方式
no-appendfsync-on-rewrite no
# 自动重写AOF文件
# 如果AOF日志文件增大到指定百分比,Redis能够通过 BGREWRITEAOF 自动重写AOF日志文件
# 工作原理:Redis记住上次重写时AOF文件的大小(如果重启后还没有写操作,就直接用启动时的AOF大小)
# 这个基准大小和当前大小做比较,如果当前大小超过指定比例,就会触发重写操作
# 你还需要指定被重写日志的最小尺寸,这样避免了达到指定百分比但尺寸仍然很小的情况还要重写
# 指定百分比为0会禁用AOF自动重写特性
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
################################## LUA SCRIPTING ##################################
# 设置lua脚本的最大运行时间,单位为毫秒,redis会记个log,然后返回error
# 当一个脚本超过了最大时限,只有SCRIPT KILL和SHUTDOWN NOSAVE可以用,
# 第一个可以杀没有调write命令的东西,要是已经调用了write,只能用第二个命令杀
lua-time-limit 5000
################################## SLOW LOG ##################################
# 是redis用于记录慢查询执行时间的日志系统,由于slowlog只保存在内存中
# 因此slowlog的效率很高,完全不用担心会影响到redis的性能
# 只有query执行时间大于(注意是大于)slowlog-log-slower-than的才会定义成慢查询,才会被slowlog进行记录
# 单位是微秒(因为是内存数据库,速度很快)
slowlog-log-slower-than 10000
# 10000 微妙=10 毫秒=0.01 秒
# slowlog-max-len表示慢查询最大的条数
slowlog-max-len 128
################################## EVENT NOTIFICATION ##################################
# 这个功能可以让客户端通过订阅给定的频道或者模式,来获知数据库中键的变化
# 以及数据库中命令的执行情况,所以在默认配置下,该功能处于关闭状态
# notify-keyspace-events 的参数可以是以下字符的任意组合,它指定了服务器该发送哪些类型的通知:
# K 键空间通知,所有通知以 __keyspace@__ 为前缀(创建和删除)
# E 键事件通知,所有通知以 __keyevent@__ 为前缀(修改)
# g DEL 、 EXPIRE 、 RENAME 等类型无关的通用命令的通知
# $ 字符串命令的通知
# l 列表命令的通知
# s 集合命令的通知
# h 哈希命令的通知
# z 有序集合命令的通知
# x 过期事件:每当有过期键被删除时发送
# e 驱逐(evict)事件:每当有键因为 maxmemory 政策而被删除时发送
# A 参数 g$lshzxe 的别名
# 输入的参数中至少要有一个 K 或者 E,否则的话,不管其余的参数是什么,都不会有任何 通知被分发
# 详细使用可以参考http://redis.io/topics/notifications
notify-keyspace-events ""
################################## ADVANCED CONFIG ##################################
# 单位字节:数据量小于等于hash-max-ziplist-entries的用ziplist,大于hash-max-ziplistentries用hash
hash-max-ziplist-entries 512
# value大小小于等于hash-max-ziplist-value的用ziplist,大于hash-max-ziplist-value用hash
hash-max-ziplist-value 64
# 数据量小于等于list-max-ziplist-entries用ziplist(压缩列表),大于list-max-ziplistentries用list
list-max-ziplist-entries 512
# value大小小于等于list-max-ziplist-value的用ziplist,大于list-max-ziplist-value用list
list-max-ziplist-value 64
# 数据量小于等于set-max-intset-entries用iniset,大于set-max-intset-entries用set
set-max-intset-entries 512
# 数据量小于等于zset-max-ziplist-entries用ziplist,大于zset-max-ziplist-entries用zset
zset-max-ziplist-entries 128
# value大小小于等于zset-max-ziplist-value用ziplist,大于zset-max-ziplist-value用zset
zset-max-ziplist-value 64
# 基数统计的算法 HyperLogLog 键只需要花费 12 KB 内存,就可以计算接近 2^64 个不同元素的基数
# 设置HyeperLogLog的字节数限制,这个值通常在0~15000之间,默认为3000,基本不超过16000
# value大小小于等于hll-sparse-max-bytes使用稀疏数据结构(sparse)
# 大于hll-sparse-maxbytes使用稠密的数据结构(dense)
# 一个比16000大的value是几乎没用的,建议的value大概为3000
# 如果对CPU要求不高,对空间要求较高的,建议设置到10000左右
hll-sparse-max-bytes 3000
# 重置hash,Redis将在每100毫秒时使用1毫秒的CPU时间来对redis的hash表进行重新hash
# 可以降低内存的使用,当你的使用场景中,有非常严格的实时性需要
# 不能够接受Redis时不时的对请求有2毫秒的延迟的话,把这项配置为no
# 如果没有这么严格的实时性要求,可以设置为yes,以便能够尽可能快的释放内存
activerehashing yes
# 对于Redis服务器的输出(也就是命令的返回值)来说,其大小通常是不可控制的
# 有可能一个简单的命令,能够产生体积庞大的返回数据,另外也有可能因为执行了太多命令
# 导致产生返回数据的速率超过了往客户端发送的速率,这是也会导致服务器堆积大量消息
# 从而导致输出缓冲区越来越大,占用过多内存,甚至导致系统崩溃
# 用于强制断开出于某些原因而无法以足够快的速度从服务器读取数据的客户端的连接
# 对于normal client,包括monitor。第一个0表示取消hard limit
# 第二个0和第三个0表示取消soft limit,normal client默认取消限制
# 因为如果没有寻问,他们是不会接收数据的
client-output-buffer-limit normal 0 0 0
# 对于slave client和MONITER client,如果client-output-buffer一旦超过256mb
# 又或者超过64mb持续60秒,那么服务器就会立即断开客户端连接
client-output-buffer-limit slave 256mb 64mb 60
# 对于pubsub client,如果client-output-buffer一旦超过32mb,又或者超过8mb持续60秒
# 那么服务器就会立即断开客户端连接
client-output-buffer-limit pubsub 32mb 8mb 60
# redis执行任务的频率
hz 10
# aof rewrite过程中,是否采取增量"文件同步"策略,默认为"yes",而且必须为yes
# rewrite过程中,每32M数据进行一次文件同步,这样可以减少"aof大文件"写入对磁盘的操作次数
aof-rewrite-incremental-fsync yes
通常情况下,默认的配置足够你解决问题
没有特殊的要求,记得不要乱改配置
Jedis:
java和redis打交道的API客户端
对应的依赖:
<dependencies><dependency><!--有Jedis类,即用来创建Jedis对象,即客户端的连接需要--><groupId>redis.clients</groupId><artifactId>jedis</artifactId><version>3.1.0</version></dependency><dependency><!--可以操作@Test--><groupId>org.testng</groupId><artifactId>testng</artifactId><version>RELEASE</version><scope>compile</scope></dependency></dependencies>
连接redis :
package com.lagou;import redis.clients.jedis.Jedis;/****/
public class Test1 {public static void main(String[] args) {Jedis j = new Jedis("192.168.164.128",6379); //相当于客户端,只是这不是自带的//而像这样的客户端,一般就如Mysql一样,可以看成代码平台进行输入语句,zookeeper和redis都可以这样说//而正是因为去连接的或者说访问的,我们也一般称为客户端,被连接和被访问,我们一般称为服务器//相对于连接,访问和被连接,被访问来说的String ping = j.ping(); //这就相当于在客户端输入ping命令,并得到返回值//这是自然的,因为返回的数据都是到客户端里的//我们只是得到服务器的数据而已,比如zookeeper,也与redis一样System.out.println(ping); //PONG}
}// 运行前:
// 1.关闭防火墙 systemctl stop firewalld.service
// 2.修改redis.conf [ bind 0.0.0.0 ] 允许任何ip访问,以这个redis.conf启动redis服务(重启redis)
//可以自己设置成127.0.0.1,然后测试,发现出现错误,即连接不了(注意:先关闭,然后启动,否则不会使用配置文件)
// redis-server /opt/redis5.0.4/redis.conf(记得写自己的位置)//这里就体现了bind的作用了,用来允许其他的服务器来连接redis
//若是空或者没有,自然就没有允许的了(因为没有这些ip)
常用API(实际上键的存储一般也是无序的,随机排列,无论是字母还是数字):
package com.lagou;import org.testng.annotations.Test;
import redis.clients.jedis.Jedis;import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Set;/****/
public class Test2 {//需要对应的依赖Jedis j = new Jedis("192.168.164.128", 6379);//string@Testpublic void textString() {j.set("k1", "v1");//相当于执行 set k1 v1j.set("k2", "v2"); //相当于执行 set k2 v2j.set("k3", "v3"); //相当于执行 set k3 v3//都是操作(添加和修改)数据Set<String> keys = j.keys("*");//相当于执行 keys *,查询所有的键,发现返回的是set集合,但是由于redis返回随机排列//那么这里虽然是得到set,但这个set是默认顺序的,所以就是按照redis返回的随机排列Iterator<String> iterator = keys.iterator();while (iterator.hasNext()) {String next = iterator.next();if("string".equals(j.type(next))) { //相当于type next(对应的键),查询他的类型是否是stringSystem.out.println(next + "->" + j.get(next));//这里执行了get next(对应的key),得到对应的数据,相当于这个命令//当然由于只能操作string,其他数据类型操作不了,所以这里可能会发生错误,类型获取方法不对//为了防止这样的操作错误,所以进行判断,只要是string类型,才会操作get next(对应key)命令//k3->v3//k1->v1//k2->v2//随机的一次,然后会有固定(根据客户端来的)//前面说过,所以可以发现redis自带的客户端的顺序可能与这里返回的不同}}Boolean k2 = j.exists("k2"); //相当于exists k2,是否存在该键System.out.println(k2);System.out.println(j.ttl("k2")); //相当于ttl k2,查询过期时间j.mset("k4", "v4", "k5", "v5"); //相当于mset k4 v4 k5 v5,操作多个List<String> mget = j.mget("k4", "k5", "k6"); //相当于mget k4 k5 k6,查询多个//返回[v4, v5, null],其中(nil)在自带的客户端显示这样,但是这里显示null//返回值进行了对应的显示操作,即显示操作不同System.out.println(mget); //[v4, v5, null]}//list@Testpublic void testList() {j.lpush("list01", "l1", "l2", "l3", "l4", "l5"); //相当于lpush list01 l1 l2 l3 l4 l5//对该键的list集合的值添加(不是操作)数据List<String> list01 = j.lrange("list01", 0, -1); //相当于lrange list01 0 -1//即查询该键list集合的数据System.out.println(list01); //[l5, l4, l3, l2, l1],从上往下压,所以取出时,是后进入先取}//set@Testpublic void testSet() {j.sadd("order", "jd001"); //相当于sadd order jd001j.sadd("order", "jd002"); //相当于sadd order jd002j.sadd("order", "jd003"); //相当于sadd order jd003//实际上上面可以直接的sadd order jd001 jd002 jd003//都是对该键添加(不是操作)数据(键是set集合的数据)Set<String> order = j.smembers("order"); //相当于smembers order,查询set集合System.out.println(order); //[jd002, jd003, jd001]j.srem("order","jd002"); //相当于srem order jd002,删除集合中指定的元素(值)System.out.println(j.smembers("order")); //[jd003, jd001]}//hash@Testpublic void testHash(){j.hset("user1","username","james");//相当于hset user1 username james,操作(添加和修改)对应数据System.out.println(j.hget("user1","username")); //james//相当于hget user1 username,得到对应的数据HashMap<String, String> h = new HashMap<>();h.put("username","tom");h.put("gender","boy");h.put("address","beijing");h.put("phone","13567809567");j.hmset("user2",h);//相当于hmset user2 这里是对应的多个key-value,操作多个//只是这里Jedis封装了可以使得传递Map进行逐一放入List<String> user2 = j.hmget("user2","username","phone");//相当于hmget user2 username phone,得到对应的key的value值,指定多个System.out.println(user2); //[tom, 13567809567]}//Zset@Testpublic void testZset(){//d代表double的的类型,这里是在java里面,所有可以这样操作// 但在自带的客户端里面,自然需要被识别才可,即60.0,60d会报错(识别不了),会以为是带有字母的数据System.out.println(60d); //60.0j.zadd("zset01",60d,"zs1"); //相当于zadd zset01 60.0 zs1j.zadd("zset01",70d,"zs2"); //相当于zadd zset01 70.0 zs2j.zadd("zset01",80d,"zs3"); //相当于zadd zset01 80.0 zs3j.zadd("zset01",90d,"zs4"); //相当于zadd zset01 90.0 zs4//是value-key类型,操作(添加和修改)数据Set<String> zset01 = j.zrange("zset01", 0, -1);//相当于zrenge zset01 0 -1,查询对应的集合数据(只有key)System.out.println(zset01); //[zs1, zs2, zs3, zs4]}
}
事务 :
初始化余额和支出:
package com.lagou;import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;/****/
public class Test3 {public static void main(String[] args) {Jedis j = new Jedis("192.168.164.128", 6379);int yue = Integer.parseInt(j.get("yue"));int zhichu = 10;//根据下面的模拟网络延迟介绍,我们要解决对应的问题,即也就是需要监控来解决//所有现在我们加上监控,来解决下面的数据影响//在事务前面加上监控,这里需要在模拟网络延迟前面//否则可能你去客户端修改时,可能到监听执行后,还没有去进行对应的键改变,就已经exec执行了j.watch("yue"); //相当于watch yue,对该键进行监控,监控和事务一样,都只操作本机//而这里也必须要在事务之前进行,在事务里面会报错,不会加入队列//这时你可以试着,在启动时,去其他客户端进行修改,发现对应的返回都是修改的数据,即事务并没有执行try {Thread.sleep(5000); //模拟网络延迟,这时你可以这样操作//一般的启动这个程序,也就相当于启动事务,我们进行启动//然后在去客户端(不是这个客户端),进行修改yue的数据//这时我们发现,事务任然是执行的,这样可能会出现数据不合理性//所有这就是其他客户端可以影响这个事务} catch (Exception e) {e.printStackTrace();}if(yue < zhichu){//由于监听在上面,所有要善后,若在下面的else里面,自然可以不用这样,看你的操作了//主要是看看是否是开启监听后判断,还是先判断在监听//如果是先监听,那么就会出现,在等待时,其他客户端在操作到小于10的余额时,会直接的不操作事务//而先判断在监听,那么就会出现,在等待时,其他客户端无论怎样的操作,都会执行事务//虽然可以有感到了判断(运气的碰到),使得监听在后,也判断了,但这样的几率太小//所以监听放在判断前面,是为了提高执行的效率,这就是好的代码顺序的好处j.unwatch(); //相当于unwatch,取消watch命令对所有key的操作System.out.println("余额不足");}else{//开启事务j.set("ooo","2");System.out.println(j.get("ooo")); //2Transaction mu = j.multi(); //相当于multi,开启事务mu.decrBy("yue",10); //余额减少//后面的在事务里的操作,必须要是multi执行//j.get("ooo");//j.set("ooo","2");,当你加上这两个命令时,会报错,因为开启事务后//Jedis就不允许再出现不是事务的操作命令了//在自带的里面是默认事务的命令的,因为操作不到客户端(相当于j)的命令//若出现了报错后,若是客户端命令造成的报错,就进行回滚,若是其他错误//如识别不了,即也就是不执行了//其余的识别的了的报错,那么虽然这里会报错,但不会使得不执行(类似于try catch的操作)//但java在事务里没有报错的返回(会记着,在exec执行完后,若有记着的,那么就返回报错)//只是不会操作罢了(因为不会出现识别不了,程序写死的,所有只有不会加入队列的,如watch监听)//即执行报错的自然显示报错,而执行的了的自然执行,但是在java程序里,这个报错却是exec造成的//那么exec全部操作完后,若有报错的,那么就会报错,使得后面的代码就不会执行//而自带的客户端虽然报错,但通常都只有识别和识别不了,其中识别不了也就是不执行//识别的了,自然执行时,会显示错误信息而已(有些可能不会加入队列,如在事务里操作watch监听)//可以执行的还会执行//即这时multi就需要重新开启了,可以直接使用exec,发现的确需要重新开启mu.incrBy("zhichu",10); //消费怎讲mu.exec(); //执行,相当于exec//再没有执行exec之前,不能使用客户端的执行,这里是j的执行,必须使用multi的执行,这里是mu的执行//当执行exec后,就可以使用客户端的执行,即j的执行j.set("ooo","2");System.out.println(j.get("ooo")); //2System.out.println("余额:" + j.get("yue"));System.out.println("消费:" + j.get("zhichu"));}}
}
模拟网络延迟:5秒内,进入linux修改余额为5,这样,余额<支出,就会进入if,而不会执行事务了
否则判断在前基本都会执行事务,虽然可以有感到了判断(运气的碰到),使得监听在后,也判断了,但这样的几率太小
JedisPool :
从上面可以看出,实际上每个程序都会用到连接,就如我们连接mysql一样
为了实现连接的可用性,那么也给出了类似于mysql连接池的操作,即redis的连接池
而不用我们频繁的创建连接(当然也是可以使用对应的全局或者静态来操作,虽然扩展或者安全性比连接池差)
多线程访问同一个变量时,基本都是拿出其中数据,然后赋值,当然方法的话,也是拿出一个方法进行执行
或者说无论使用上面操作,都可以说是操作副本(用副本进行操作)
并不是主本(除非赋值给主本),变量是,方法则是直接使用
当多个线程使用静态连接时,对应的连接可能是副本
虽然在少的用户下,比连接池好,因为这时连接池初始化资源多且性能也差不多
但是在多个用户下,由于静态的副本越来越多,导致后面的副本创建(需要唯一)
即唯一越来越难创建,所以这时连接池要好,因为这时性能比连接池差很多了,
redis的连接池技术:
详情:https://help.aliyun.com/document_detail/98726.html
对应依赖:
<dependency><!--
管理对象池,虽然这里可以删除,但对应的作用还是有的,只是这里没有体现而已,可以对对象进行管理
如跟踪对象的状态属性,比如创建时间
--><groupId>commons-pool</groupId><artifactId>commons-pool</artifactId><version>1.6</version>
</dependency>
对应的连接池的操作:
使用单例模式进行优化(包含普通模式):
package com.lagou;import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;/****/
public class JedisPoolUtil {//普通的连接池public static void main(String[] args) {//创建设置资源池对象,可以对资源池(也就是对连接池进行设置)JedisPoolConfig config = new JedisPoolConfig();//资源池中的最大连接数config.setMaxTotal(20);//资源池中允许的最大空闲连接数config.setMaxIdle(10);//当资源池用尽后,调用者的最大等待时间(单位为毫秒)config.setMaxWaitMillis(60*1000);//向资源池借用连接时是否做连接有效性检测(业务量很大时,建议设置为false,减少一次ping的开销)config.setTestOnBorrow(true);//给出对应的连接,然后根据配置创建连接池JedisPool jedisPool = new JedisPool(config,"192.168.164.128",6379);//得到连接池的连接Jedis jedis = jedisPool.getResource();jedis.set("haha","23");System.out.println("添加成功");}//使用单例模式进行优化private JedisPoolUtil(){}private volatile static JedisPool jedisPool= null;//volatile的作用,一般我们操作变量时(注意是变量,所以在方法上好像不可)//都是操作副本(相对来说),所有会出现数据的不合理性,而加上这个,那么就不会操作副本了//而是直接操作这个变量,换言之,就是基本不会出现不合理性//可是这样的得到是会比较慢的(保证都可以得到,且合理)//即可以这样说:指多个线程访问同一个变量时,其中一个线程修改了该变量的值//其它线程能够立即看到修改的值(等待操作,这就是慢的原因)//所以我们一般也不会使用volatile,但是若为了使得数据合理,可以使用这个private volatile static Jedis jedis = null;//返回一个连接池public static JedisPool getInstance(){//双层检测锁(企业中用的非常频繁)//以疫情为例if(jedisPool == null){ //第一层:测量体温synchronized (JedisPoolUtil.class){ //排队进站if(jedisPool == null){ //第二层:查看健康码//为什么要两次判断呢://假如有6个线程,他们都进行了第一次的判断//若没有第二次的判断,那么可以知道后续的线程都会进行创建对象//所以这就导致的对象的创建次数过多//你可能会有疑问,那么为什么要有第一个判断呢//那么这时就有一个问题,我们加锁进行等待好,还是不等待好//假设我们没有第一个判断,那么就永远是加锁的,也就是说,需要同步,从而降低了效率//若我们加了判断,那么就没有锁,也就是异步,提高效率,在通常情况下,我们一般都需要异步//所以从上面可知,我们一般需要两层来操作,所以这就是为什么企业中用的频繁的原因//先设置配置JedisPoolConfig config = new JedisPoolConfig();config.setMaxTotal(1000); //资源池中的最大连接数config.setMaxIdle(30); //资源池中允许的最大空闲连接数config.setMaxWaitMillis(60*1000); //当资源池用尽后,调用者的最大等待时间(单位为毫秒)config.setTestOnBorrow(true);//向资源池借用连接时是否做连接有效性检测(业务量很大时,建议设置为false,减少一次ping的开销)//给出对应的连接,然后根据配置创建连接池jedisPool = new JedisPool(config,"192.168.164.128",6379);}}}return jedisPool; //保证了对应的数据是有唯一返回的(无论是否多线程,且基本优化完善)//单例模式完成}//返回Jedis对象public  static Jedis getJedis(){if(jedis == null){Jedis resource = getInstance().getResource(); //对应的连接池一定是先操作的}return jedis;}
}
测试类:
package com.lagou;import redis.clients.jedis.Jedis;/****/
public class TestJedisPool {public static void main(String[] args) {Jedis jedis1 = JedisPoolUtil.getJedis(); //得到连接池Jedis jedis2 = JedisPoolUtil.getJedis(); //得到连接池System.out.println(jedis1==jedis2); //true,单例模式的完成}
}
高并发下的分布式锁:
在这里提一下为什么使用锁(锁的实现方式有很多,如java自带,或者操作单线程的redis等等),一般我们的操作同一个数据是需要锁的,但访问时,Servlet一般是不同的,所以基本是操作静态而使用锁
或者操作其他数据库的信息而使用锁,比如这里的redis的数据
经典案例:秒杀,抢购优惠券等(测试redis是否可以抗住高并发的场景)
搭建工程并测试单线程 :

对应的依赖:
 <packaging>war</packaging><dependencies><dependency><!--有对应的类操作页面,如前端控制器--><groupId>org.springframework</groupId><artifactId>spring-webmvc</artifactId><version>5.2.7.RELEASE</version></dependency><!--实现分布式锁的工具类,如有Config类,Redisson类,用来操作锁的--><dependency><groupId>org.redisson</groupId><artifactId>redisson</artifactId><version>3.6.1</version></dependency><!--spring操作redis的工具类,后面的spring.xml里面的两个配置都是需要这个,对应的类就是这里面的--><dependency><groupId>org.springframework.data</groupId><artifactId>spring-data-redis</artifactId><version>2.3.2.RELEASE</version></dependency><!--redis客户端,有Jedis类,即用来创建Jedis对象,即客户端的连接需要--><dependency><groupId>redis.clients</groupId><artifactId>jedis</artifactId><version>3.1.0</version></dependency><!--
json解析工具,这里不可以删除,虽然我们并没有操作
但是springmvc操作@RequestBody注解使用的json时,通常需要这个,否则报错
其他的json解析工具基本不行,具体可以看68章博客(一开始的介绍)
--><dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId><version>2.9.8</version></dependency></dependencies><build><plugins><plugin><groupId>org.apache.tomcat.maven</groupId><artifactId>tomcat7-maven-plugin</artifactId><configuration><port>8001</port><path>/</path></configuration><executions><execution><!-- 打包完成后,运行服务 --><phase>package</phase><goals><goal>run</goal></goals></execution></executions></plugin></plugins></build>
对应的web.xml:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns="http://xmlns.jcp.org/xml/ns/javaee"xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaeehttp://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"id="WebApp_ID" version="3.1"><servlet><servlet-name>springmvc</servlet-name><servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class><init-param><param-name>contextConfigLocation</param-name><param-value>classpath:spring/spring.xml</param-value></init-param></servlet><servlet-mapping><servlet-name>springmvc</servlet-name><url-pattern>/</url-pattern></servlet-mapping></web-app>
对应的spring.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsd"><context:component-scan base-package="controller"/><!--spring为连接redis提供的一个模板工具类,通过下面的连接信息,使得可以使用这个类来操作redis数据--><bean id="stringRedisTemplate"class="org.springframework.data.redis.core.StringRedisTemplate"><property name="connectionFactory" ref="connectionFactory"></property><!--注入了连接信息--></bean><!--连接信息(大概是连接池)--><bean id="connectionFactory"class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"><property name="hostName" value="192.168.164.128"></property><property name="port" value="6379"/></bean></beans>
对应的controller包里面的类:
package controller;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.DataType;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;/****/
@Controller
public class TestKill {@Autowiredprivate StringRedisTemplate stringRedisTemplate;@RequestMapping("/kill")//@ResponseBody可以写在对应返回值前面,因为就是操作返回值的public @ResponseBody String kill(){//首先先清空redis数据库,然后加上set phone 10命令来进行测试//从redis中获取 手机(phone)的库存数量int phoneCount = Integer.parseInt(stringRedisTemplate.opsForValue().get("phone"));//opsForValue()相当于只操作string类型的,因为只有对应的方法//若不是该类型,则自然报错(可以进行判断来解决,即type),相当于将原来的redis的方法分开了一样//DataType phone = stringRedisTemplate.type("phone");//System.out.println(phone); //STRING,即string类型//可以将stringRedisTemplate与原来的redis的变量看成一样的//只是他里面将对应类型的方法合在一起了(redis自带的不会,如type查看类型),需要对应的方法才可进行//如opsForValue(),操作了对应string方法//然后后面就是相当于get phone命令,来查询数据//判断手机数量是否可以秒杀if(phoneCount > 0){phoneCount--;//库存减少之后,再将库存的值保存回redisstringRedisTemplate.opsForValue().set("phone",phoneCount+"");System.out.println("库存-1,剩余:" + phoneCount);}else{System.out.println("库存不足");}return "over!";}}
现在可以进行测试了(可以使用jmeter来测试,78章博客里有),发现当多线程请求时(即高并发)
对应的值可能会是同时得到的(返回的剩余结果一样),这就是一个隐患
我们现在在方法那里加上同步锁,即如下:
 public @ResponseBody synchronized String kill(){...}
//加上synchronized再来测试,发现没有相同的数据了(不会得到相同的数据了)
//但正如以前说过,该锁,只能操作当前项目的并发问题,并不能解决其他项目的并发问题
//即只能解决一个tomcat的并发问题:synchronized锁的是一个进程下的线程并发
//如果分布式环境,多个进程并发,这种方案就失效了
高并发测试:
启动两次工程,端口号分别8001和8002
使用nginx做负载均衡
upstream sga{server 192.168.164.1:8001; #指向自己本机server 192.168.164.1:8002; #指向自己本机
}
server {listen       80;server_name localhost; #设置了localhost那么相当于就是不看域名了,因为localhost始终默认本机的(无论设置与否)#charset koi8-r;#access_log logs/host.access.log main;location / {proxy_pass http://sga;root   html;index index.html index.htm;}}
/usr/local/nginx/sbin/nginx -c /usr/local/nginx/conf/nginx.conf #以对应的配置文件启动
使用 JMeter 模拟1秒内发出100个http请求,会发现同一个商品会被两台服务器同时抢购
虽然在单个服务器里,顺序是从大到下,但是对应的数据可能不是逐步变小(因为中间被抢)
这样实际上是没有问题的,但是可能会出现,相同的数据
也就是说在可能会出现其中一个服务器里面发现了剩余:2(试验),另外一台服务器里面也出现了剩余:2(试验)
说明他们正好在同一时间段进行了得到同样的数据,可以多试几次进行试验
也就是说,他们得到了同一个数据了,所以单纯的synchronized的确解决不了分布式的执行,但却解决了单个服务器的操作
当然分布式也是需要synchronized的,你可以试着将synchronized删除,发现,单个服务器出现了很多相同的数据
正是因为没有同步,使得两边服务器相同的数据也就变多,但是事实上,可能大多数结果与加了synchronized一样的出现
第一次除外(没有对应的启动操作时,如web.xml没有配置load-on-startup,即启动时加载,而不是第一次访问时加载)
这时后端由于要初始化,那么请求大多异步(因为等待,这时就一起冲过去了)
所有说要看看真正的结果,可以看第一次的请求
解释如下:
第一:这是因为由于负载均衡对应的ip是分开的,这就使得服务器相同的数据几率变小
第二,负载均衡选择需要时间,使得多个请求很难异步在一起
但并不是所有,所以也是需要加上synchronized来保证不异步的,即真正的同步
实现分布式锁的思路:
首先我们一般是需要将对应锁的标记放在服务器都可以访问的地方,且基本只能被一人访问
因为redis是单线程的(对应进入redis来说的单个线程进入或者说访问,即核心操作是单线程的,其他还是多线程的)
所以命令也就具备原子性,相当于多个服务器操作redis命令时
只能是一个命令一个命令的执行,其他服务器等待执行,相当于执行命令部分加了锁等待
实际上前面的zookeeper的锁也是,因为他的锁对应的是单线程数据库)
而mysql则不是,他是多线程的,但在数据上却也是原子性的
即都不会同时进行操作数据,所以实际上mysql也是可以实现分布式锁的(具体百度)
这是底层的原因,就如一个1,2,3只能有一个1,没有副本的操作
上面锁的操作是操作phoneCount的,结果与phoneCount有关,所有无论有没有原子性,结果都是那样
因为是设置,而不是命令的减少值,命令无论操作多少次,都是不会出现小于0的,也就与原子性无关了
这里你可能会有疑问:
为什么redis是单线程速度也这么快呢:
实际上如果是多线程,速度还是要更快的,但是多线程非常的麻烦,且不好维护,更是因为单线程也够快了,所以为了更好的维护
所以redis是单线程(虽然他主要利用更快的多路复用操作,一般指NIO),如果要有对应的多线程的作用,你可以多加redis实例来操作(不同端口)
使用setnx命令实现锁,保存k-v(setnx k v命令会判断k是否存在,用这个来实现锁),k-v,对应键和值,并不是在v里面的k-v
如果k不存在,保存(当前线程加锁),执行完成后,删除k(del k)表示释放锁
如果k已存在,阻塞线程执行,表示有锁
如果加锁成功,在执行业务代码的过程中出现异常,导致没有删除k(释放锁失败)
那么就会造成死锁(后面的所有线程都无法执行,因为都阻塞了)
所以需要设置过期时间(expire k 10命令设置k的生命周期是10秒,或者setex k 10 v命令,在创建k时,顺便设置生命周期)
例如10秒后,redis自动删除对应的键,或者程序里面使用tru-finally来使得异常继续执行
但是finally有可能是不会执行的(有可能),所以最好设置过期时间也写上,两者一起操作
没有执行时,操作过期,执行时,操作finally,增加了安全
在有设置过期时间的情况下,且高并发下,由于时间段等因素导致服务器压力过大或过小,每个线程执行的时间不同
假如,有两个线程,称为线程1,线程2,在并发过程中,线程1先加锁执行,线程2后加锁执行
其中线程1在压力大的服务器,即程序执行完需要13秒,这时我们先加锁
当执行到第10秒(假如是10秒过期)时,redis自动过期了(即释放锁),这时线程2开启执行
但要注意,我们还过三秒会有再次释放锁的操作(即删除锁)
线程2在压力小的服务器,执行需要7秒,执行3秒后,锁突然被释放了,其他的线程也开始执行
为什么:因为是被线程1主动删除锁释放掉了
若出现连锁反应,当前线程刚加的锁,就被其他线程释放掉了,周而复始,即可能出现,我们刚刚加的锁,就被突然的释放
使得两个程序一起执行,即会有可能刚好处理同一个数据(中间过期),比如另一个太慢,刚好处理同一个数据
那么就可能出现相当于没有加锁的样子,即导致锁相当于失效了,即会导致不同的线程处理同样的数据了,为了解决这个问题
我们可以给每个线程加上唯一的标识UUID随机生成,释放的时候判断是否是当前的标识即可
就类似于以前说过的ThreadLocal类,用当前线程来决定唯一标识,虽然他操作的是连接
问题又来了,过期时间如果设定:
如果10秒太短不够用怎么办
设置60秒,太长又浪费时间
可以开启一个定时器线程,当过期时间小于总过期时间的1/3时(从大到小的),增长过期时间(吃仙丹续命)
知道线程执行完成,那么就不增长的,即防止线程的出错,即中途的过期,这样也就保证了可以不加唯一的标识
但为了防止出错,也是可以加上,作为一个保险的
我们发现,若自己实现分布式锁,太复杂
虽然不那么复杂,大概是定时器线程或者是过期时间以及UUID的操作需要考虑很多情况
所以我们需要可以更加简便的操作,即出现了Redisson
Redisson:
Redis 是最流行的 NoSQL 数据库解决方案之一,而 Java 是世界上最流行(注意,我没有说"最好")的编程语言之一
因为语言是各有优点的,主要操作对应的领域,如C搞底层,java搞web应用等等
虽然两者看起来很自然地在一起"工作",但是要知道,Redis 其实并没有对 Java 提供原生支持
相反,作为 Java 开发人员,我们若想在程序中集成 Redis,必须使用 Redis 的第三方库
而 Redisson 就是用于在 Java 程序中操作 Redis 的库,它使得我们可以在程序中轻松地使用 Redis
Redisson 在 java.util 中常用接口的基础上,为我们提供了一系列具有分布式特性的工具类
对应类的改变
package controller;import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.data.redis.connection.DataType;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;import java.util.concurrent.TimeUnit;/****/
@Controller
public class TestKill {@Autowiredprivate StringRedisTemplate stringRedisTemplate;@Autowiredprivate Redisson redisson;@RequestMapping("/kill")//@ResponseBody可以写在对应返回值前面,因为就是操作返回值的public @ResponseBody synchronized String kill(){//定义商品String productKey = "HUAWEI-P40"; //相当于临时的键,你可以将对应的请求次数调大,那么使用keys *是可以看到这个键的,多试几次//因为只有访问时,才会创建锁,否则次数太少,会发现都访问完了,也就没有创建锁的操作了//通过redisson获得锁RLock lock = redisson.getLock(productKey);//底层源码就是集成了前面说的思路中setnx,过期时间等操作,极大的简便的我们锁的操作//上锁(过期时间为30秒)lock.lock(30, TimeUnit.SECONDS);//设置30,具体单位看第二个参数,这里就是秒的单位,这里即30秒try {int phoneCount = Integer.parseInt(stringRedisTemplate.opsForValue().get("phone"));//判断手机数量是否可以秒杀if (phoneCount > 0) {phoneCount--;//库存减少之后,再将库存的值保存回redisstringRedisTemplate.opsForValue().set("phone", phoneCount + "");System.out.println("库存-1,剩余:" + phoneCount);} else {System.out.println("库存不足");}}catch (Exception e){e.printStackTrace();} finally {//释放锁lock.unlock();}//发现,操作锁是多么的简单(集成了,你也可以根据前面说的思路来自己操作,俗称造轮子)return "over!";}@Bean //将返回值放入IOC容器中,自然的被扫描的那个容器(xml),后低于变量赋值,优先于注入public Redisson redisson(){//redis的配置对象Config config = new Config();//使用单个redis服务器config.useSingleServer().setAddress("redis://192.168.164.128:6379").setDatabase(0);//使用集群redis,即没有联系的redis(如没有主从的)/*config.useClusterServers().setScanInterval(2000).addNodeAddress("redis://192.168.164.128:6379","redis://192.168.164.129:6379","redis://192.168.164.130:6379");*/Redisson redisson = (Redisson)Redisson.create(config);return redisson;}
}
通过上面的操作,运行后,我们可以看到
运行返回的结果中,服务器之间没有一样的数据了(多尝试几次也是如此),即完成了分布式锁的操作了
实现分布式锁的方案其实有很多,我们之前用过的zookeeper的特点就是高可靠性(基本不会出现错误)
现在我们用的redis特点就是高性能
可能会出现错误,非常小,就如在非常高的并发下,可能对应的程序会出现问题,但几率几乎为0
目前分布式锁,应用最多的仍然是"Redis"

80-Redis详解相关推荐

  1. 一文搞定 Spring Data Redis 详解及实战

    转载自  一文搞定 Spring Data Redis 详解及实战 SDR - Spring Data Redis的简称. Spring Data Redis提供了从Spring应用程序轻松配置和访问 ...

  2. 33.Redis详解

    一 缓存概念 缓存是为了调节速度不一致的两个或多个不同的物质的速度,在中间对速度较快的一方起到一个加速访问速度较慢的一方的作用,比如CPU的一级.二级缓存是保存了CPU最近经常访问的数据,内存是保存C ...

  3. Redis详解及使用

    Redis详解及使用 前言:对于缓存这个概念大家应该都清除,在项目开发中,为了避免用户频繁请求数据库,比如类似于淘宝这样的大量请求下,导致服务器的数据库访问压力过大导致崩溃,因此我们通过设置缓存来解决 ...

  4. 数据库应用——Redis详解

                                                                                         Redis详解 一.非关系型数 ...

  5. 网页访问服务器默认什么端口号,URL中没有端口号默认为80?详解IT产品必须熟知的访问地址概念!...

    原标题:URL中没有端口号默认为80?详解IT产品必须熟知的访问地址概念! 作为一个互联网产品经理,你可能每天打开几十个网页,但你有没有观察过他们的网址? 作者带你庖丁解牛并把网址拆开.标准URL地址 ...

  6. 基础知识redis详解--【Foam番茄】

    Redis 学习方式: 上手就用 基本的理论先学习,然后将知识融汇贯通 nosql讲解 为什么要用Nosql 现在都是大数据时代 大数据一般的数据库无法进行分析处理了 至少要会Springboot+S ...

  7. 分布式核心技术-Redis详解

    前面有写过一篇Redis集群实战详解,主要是针对部署redis集群实战操作.可参考:https://blog.csdn.net/qq_45441466/article/details/11642431 ...

  8. Redis详解(十五)------ 集群模式详解

    1.为什么需要集群? ①.并发量 通常来说,单台Redis能够执行10万/秒的命令,这个并发基本上能够满足我们所有需求了,但有时候比如做离线计算,为了更快的得出结果,有时候我们希望超过这个并发,那这个 ...

  9. redis详解(四)-- 高可用分布式集群

    一,高可用 高可用(High Availability),是当一台服务器停止服务后,对于业务及用户毫无影响. 停止服务的原因可能由于网卡.路由器.机房.CPU负载过高.内存溢出.自然灾害等不可预期的原 ...

  10. Redis详解(三)

    一.Redis集群介绍 Clustering: redis 3.0之后进入生产环境 分布式数据库,通过分片机制来进行数据分布,clustering 内的每个节点,仅有数据库的一部分数据; 去中心化的集 ...

最新文章

  1. javascript 两个数组组成一个对象
  2. 【树型DP】BZOJ1564 二叉查找树(noi2009)
  3. 当 position:sticky 遇到 bootstrap 浮动布局时候的踩坑记录
  4. 盘点上班族和大学生都适用的8个赚外快的小方法 总有一个适合你
  5. MyBatisPlus3.x代码生成器生成实体类自定义需要填充的字段
  6. OceanBase架构浅析(一)
  7. django ORM 操作
  8. 如何避免Java线程中的死锁?
  9. 软件开发的“三重门”
  10. 【转载】:单例设计模式
  11. ajax js图片上传到php,Ajax上传并预览图片(附代码)
  12. android用来显示界面的组件,Android 自学之基本界面组件(上)
  13. linux 脚本中的push,在Linux系统下实现ServerPush(转)
  14. Linux二进制保护(文末福利)
  15. (转)Resin常见问题及解决方法
  16. android6刷机教程,安卓手机刷机步骤
  17. 中国移动开放平台(dev.cmccopen.cn)请求头Header:Authorization验证失败的原因(我遇到的)
  18. 如何关闭伽卡他卡的开机自启
  19. uni-app——如何获取页面容器的高度
  20. FPGA在深度学习应用中或将取代GPU

热门文章

  1. [转帖]CR3,PDE,PTE,TLB 内存管理的简单说明
  2. 学生党开学换机什么蓝牙耳机比较好?新款上市高性价比蓝牙耳机推荐
  3. lua+nginx用户鉴权脚本--get方法
  4. OpenCV这么简单为啥不学——1.11、蓝背景证件照替换白色或红色
  5. 手持式DSView逻辑分析仪
  6. 正则表达式的介绍和使用
  7. 微信支付获取code 和获取权限时的区别
  8. 刚刚热乎的Win11该如何配置
  9. Javascript + Dom知识点总结
  10. 操作系统设计与实现 第一章 引言(四)