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的节点(不是服务器),一样的操作,这里是服务器
即更新操作成功并返回客户端后,所有节点在同一时间的数据完全一致,这就是分布式的一致性
一致性的问题在并发系统中不可避免,对于客户端来说,一致性指的是并发访问时更新过的数据如何获取的问题
从服务端来看,则是更新如何复制分布到整个系统,以保证数据最终一致
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和A一般是不能共存的,只能选择其中一个
单点集群除外,单点集群,一般只会操作一个数据库,那么也就不分一致性和可用性的,因为再怎么一致,反正只有一个数据库
CAP总结:
分区是常态,不可避免,三者不可共存
可用性和一致性是一对冤家
一致性高,可用性低
一致性低,可用性高
因此,根据 CAP 原理将 NoSQL 数据库分成了满足 CA 原则
不分离,所以基本没有对应服务器挂掉或者由于延时,那么操作的数据库是一个,也就当然的是一致性和可用性的整体了
满足 CP 原则和满足 AP 原则三 大类:
CA - 单点集群,满足一致性,可用性的系统,通常在可扩展性上不太强大
CP - 满足一致性,分区容忍性的系统,通常性能不是特别高
AP - 满足可用性,分区容忍性的系统,通常可能对一致性要求低一些
介绍已经说明完毕,接下来开始真正的学习
下载与安装:
下载:
redis官网:http://www.redis.net.cn/
图形工具:https://redisdesktop.com/download
安装:
虽然可以在安装在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 clients
3 bytes payload
keep 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 #根据[]里进行依次匹配,基本可以写很多个
键(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字符串
使用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--)
(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