redis是一个开源的KV结构的一个高性能内存数据库,他支持多种数据数据类型存储,有字符串String 、散列Hash、列表List、集合Set、有序集合Zset,是一个NoSQL( 非关系型数据库 )数据库html
Redis的应用场景前端
内存数据库(登陆信息、购物车信息、用户浏览记录等)linux
缓存服务器(商品数据、广告数据等等)。(最多使用)git
解决分布式集群架构中的session分离问题(session共享)。程序员
任务队列。(秒杀、抢购、12306等等)github
支持发布订阅的消息模式redis
应用排行榜。数据库
网站访问统计。后端
数据过时处理(能够精确到毫秒)数组
Redis没有官方的Windows版本,咱们都是在Linux上安装运行
安装教程:http://www.javashuo.com/article/p-pxjsxnqr-hy.html 或者 http://www.javashuo.com/article/p-wxyhyukw-ek.html
最近要学习不少须要Linux环境的技术,我专门下了CentOS,用做练习用:
安装教程,用的CentOS7 : http://www.javashuo.com/article/p-eudzgqvn-hz.html
相关设置太多了,我提供几个经常使用的 :
开端口,和CentOS6不同了 : firewall-cmd --permanent --zone=public --add-port=6379/tcp
一个是Redis自带的 redis-cli 不作讲解,能够去翻上面的博客有介绍
运用的最多的一个类型 https://github.com/xetorthio/jedis
简单的随便来个列子吧:分别为获取单列链接 和 从链接池中获取链接
set key value get key
mset k v k v k v mget k k k :同时设置或获取多个值
getset key value : 取值再赋值
incr key / incyby key num :递增 / 递增指定整数(value为整数才可用),好比订单号,商品号主键
decr key / decrby key num :递减 / 递减指定整数(value为整数才可用)
setnx key value :当key没有被赋值时才可被赋值,能够防止覆盖
append key value : 为指定key的value末尾追加value
strlen key : 获取指定key对应值的的长度
hset key field value hmset key field value field2 value2 :一次设置单个/多个值
hget key field hmget key field1 field2... :一次获取单个/多个值
hgetall key : 获取全部字段值
hsetnx key field value : 当field不存在时,插入值,当其存在时,不作操做
hdel key field1 field2 :删除单个/多个字段
hincrby key field num : 增长数字num 好比购物车中的购买数量
hkeys key / hvals key : 只获取字段名 / 字段值
hgetall key : 获取全部字段
通常用于储存那些对象数据,特别是对象属性常常发生增删改操做的数据(购物车)
若是采用String就得序列化回对象,修改了值再序列化存到redis,不高效
Redis的列表类型能够储存一个有序的字符串列表,经常使用的操做是向列表两段添加元素,或者得到列表的某一个片断
列表类型内部使用的是双向链表实现的 增删快,查询慢,可是若是查询头部或者尾部的10条纪录也是极快的
lpush key v1 v2 v3 v4 rpush key v1 v2 v3 v4 :从列表左 / 右添加元素
lrange key start stop :查看列表某个区间的全部元素,索引从0开始
lpop key / rpop key :左 / 右 弹出一个元素(先移除再返回)
llen key :获取列表中元素的个数
由于储存是有序的,能够用来知足商品评论的需求,按照时间排序利用的就是插入有序这个特色
set类型即集合类型,其中的数据没有顺序,可是保证不重复
sadd key v1 v2 v3 :添加元素
srem key v1 v2 :删除指定元素
smembers key :获取集合中的全部的元素
sismember key v1 :判断元素是否在集合中
sdiff setA setB :求差集,属于setA 不属于setB的元素
sinter setA setB :求交集,属于setA 和setB的交集部分
sunion setA setB :求并集,setA 和 setB的并集
scard setA :获取集合中元素的个数
spop setA : 由于储存是无序的,因此是随机弹出一个元素
在集合类型的基础上,有序集合类型为集合中的每个元素都关联了一个分数,这使得咱们能够完成插入/删除和判断元素是否在集合中,还可以得到分数最高或者最低的前N个元素,或许指定分数范围内的元素等与分数有关的操做
Zest和List的区别:
列表类型是经过链表实现的,获取两端的数据很快,可是访问中间的数据会变慢不少
有序集合类型使用的事散列表实现,即便读取中间部分的数据也很快
列表中不能简单的调整某个元素的位置,可是有序集合能够经过更改分数实现调整
有序集合类型要比列表类型更耗内存
zadd key 分数 元素 好比:zadd stu 80 english :添加一个元素到有序集合,:英语元素 80分
zrange key start stop :按照元素分数从小到大,得到排名在某个范围的元素列表
zrevrange key start stop :按照元素分数从大到小,得到排名在某个范围的元素
withscores :跟在上面两种语法后,能够把元素的分数一并显示出来
zscore key 元素 :获取元素的分数
zrem key 元素 :删除元素
zincrby key num field :为field元素增长分数 num
zcard key :得到集合中元素的数量
zcount key min max :获取指定分数范围内的元素的个数
zremrangebyrank key start stop :按照排名范围删除元素
zremrangebyscoe key min max :按照分数范围删除元素
zrank / zrevrank key 元素 :从小->大 / 大->小获取元素的的排名
由于有一个分数,影响着咱们元素的存放,商品销售排行榜就能利用这一点,分数表示销售数量
keys pattern :获取全部有pattern样式的key
del / exists key :删除 / 判断是否存在 元素
expire key num :看成缓存数据库时,设置key的有效生存时间 单位秒
ttl / persist key : 查看key 的剩余有效时间 / 清楚生存时间
rename old new :重命名 key
type key :显示指定key的数据类型
rediss的事务是经过 multi 、 exec 、 discard 、warch 这四个命令来完成的
redis的单个命令都是原子性的,因此这里说的事务针对的是 : 命令集合
redis将命令集合序列化并确保处于同一事务的命令连续且不被打断的执行完毕
redis不支持事务回滚
multi
用于标记事务块的开始
redis会将后续的命令逐个放入到队列中,而后使用 EXEC 命令原子化的执行这个命令序列
exec
在一个事务中执行先前全部放入到队列中的命令,而后恢复正常的链接状态
discard
清除全部执勤在一个事务中放入队列的命令,然乎恢复正常的链接状态
watch
当某个事务须要按条件执行时,就要使用这个命令将给定的键设置为受监控状态
使用该语法可实现redis的乐观锁 watch key [key....]
unwatch
清除全部先前为一个事务监控的键
一种是语法错误,当咱们真正用代码衔接的时候基本不会出现这种错误
另外一种是类型错误,多种数据类型之间的转换形成的错误
redis不支持事务回滚,一方面是为了保持性能,另外一方面就是上面两种错误都是咱们程序员能够避免的
单引用单进程多线程 : synchronize、Lock
分布式引用使用锁 :多进程
基于数据库的乐观锁实现分布式锁
基于zookeeper临时节点的分布式锁 ( 不懂 )
基于redis的分布式锁 (学习目标)
互斥性 :在任意时刻,只有一个客服端能够持有锁
同一性 :加锁和解锁都必须是同一个客服端
可重入性:即便有一个客服端在持有锁时崩溃,也能保证后续的客服端可以加锁
Redis做为一个内存数据库,经过按期将内存中数据刷到硬盘中,保证数据的持久性
简单带过,大部分我都知道 RDB 、AOF
这种方式redis会在指定的状况下触发快照,将数据持久化到硬盘,好比:
复合自定义配置的快照规则
执行save或者bgsave命令
执行flushall命令
执行主从复制操做
能够在redis.conf中设置自定义快照规则,好比:相互之间是 或 的关系
save 900 1 : 表示15分钟(900秒钟)内至少1个键被更改则进行快照。
save 300 10 : 表示5分钟(300秒)内至少10个键被更改则进行快照。
save 60 10000 :表示1分钟内至少10000个键被更改则进行快照。
快照的实现原理
redis使用fork函数赋值一份当前进程的副本(也就是创造一个子进程干事)
父进程继续处理客户端发来的各类请求,子线程刷数据到硬盘中
当子进程将数据刷完后,就会用该临时文件替换以前的RDB文件,操做完成
说明:
redis启动后会读取PDB快照文件,将数据从硬盘载入到内存中
在快照过程当中,是创立一个临时文件装载,若是快照出现错误,也能够恢复之前的版本
通常咱们能够经过定时备份RDB文件来实现redis数据库的备份,RDB文件占用空间小,传输也较快
使用RDF持久化方案,若是redis异常宕机,会丢失一部分数据,若是不能接受这部分损失,建议再开启AOF
RDB能够最大化Redis的性能,父进程不进行任何IO操做,但若是待持久化数据较大时,可能比较耗时
介绍:
AOF持久化方案须要手动开启 :appendonly yes
AOF持久化方案,记录的都每一条更改了Redis中数据的命令,写入一个AOF文件中,另外还值得一说的是,这个记录还会被优化好比你设置了一个值,而后你又对该键,设置了其余的值,就会产生覆盖,把以前的那个命令覆盖掉,从而使得被记载的命令是少的,打开过AOF文件的朋友都知道,AOF文件中的数据可读性很高
同步磁盘数据:
redis每次更改数据的时候,aof就会将命令记录到aof文件中,但命令并无实时写入到硬盘,而是写到硬盘缓存中,硬盘缓存再将数据刷到硬盘
配置文件中的参数问题:
appendfsync always 每次执行写入都会进行同步: 最安全,可是效率过低
appendfsync everysec :每一秒刷一次 (推荐使用)
appendfsync no :不主动进行同步操做,不安全,不考虑
AOF文件 损害后之后如何修复:
在写入aof文件时,redis若是宕机形成AOF文件出错的话,redis在启动的时候会拒绝载入这个aof文件,确保数据不会被破坏
咱们能够先备份当前损坏的aof文件,对其一个副本进行修复工做
使用redis自带的 redis-chech-aof程序,对aof文件进行修复:redis-check-aof--fix readonly.aof
重启redis,观察redis是否载入载入修复后的aof文件,若是成功读取 把备份的aofa文件删除便可
首先放到最前面,若是对于数据比较重要,建议两个持久化方式都开启
若是你能承受几分宗之内的数据丢失,能够只是哟个rdb,redis受性能影响小
不建议单独使用AOF持久化方式,RDB回复数据集的速度比AOF的速度快不少
以前的博客彻底的记录过一次:http://www.javashuo.com/article/p-wxyhyukw-ek.html
LUA:是一种脚本语言,用C语言编写,功能为嵌放在应用程序中,为程序提供灵活的扩展和定制功能
Redis中使用LUA的好处
减小网络开销:能够把多个命令放在一个脚本中执行
原子操做 :Redis会将整个脚本做为一个总体执行,中间不会被其余命令打断
复用性 : 脚本保存在redis中,任何客户端均可以调度使用
能够本地下载上传到linux,也可使用curl命令在linux系统中进行在线下载
curl -R -O http://www.lua.org/ftp/lua-5.3.5.tar.gz
yum -y install readline-devel ncurses-devel
tar -zxvf lua-5.3.5.tar.gz
make linux
make install 最后,直接输入 lua命令便可进入lua的控制台
redis2.6版本后内置了Lua解释器,可使用Eval对Lua脚本进行求值 格式以下
EVAL script numkeys key [key ...] arg [arg ...]
命令说明:
script参数:是一段Lua脚本程序,它会被运行在Redis服务器上下文中,这段脚本没必要(也不该该)定义为一个Lua函数。
numkeys参数:用于指定键名参数的个数。
key [key ...]参数: 从EVAL的第三个参数开始算起,使用了numkeys个键(key),表示在脚本中所用到的那些Redis键(key),这些键名参数能够在Lua中经过全局变量KEYS数组,用1为基址的形式访问( KEYS[1] , KEYS[2] ,以此类推)。
arg [arg ...]参数:,能够在Lua中经过全局变量argv数组访问,访问的形式和KEYS变量相似( ARGV[1] 、 ARGV[2] ,诸如此类)。
好比:
eval "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}" 2 key1 key2 first second
"key1"
"key2"
"first"
"second"
redis.call()
redis.pcall()
这两个函数的惟一区别在于它们使用不一样的方式处理执行命令所产生的错误
示例: eval "return redis.call('set',KEYS[1],'bar')" 1 foo
OK
EVAL 命令要求你在每次执行脚本的时候都发送一次脚本主体(script body)。
Redis 有一个内部的缓存机制,所以它不会每次都从新编译脚本,不过在不少场合,付出无谓的带宽来传送脚本主体并非最佳选择。
为了减小带宽的消耗, Redis 实现了 EVALSHA 命令,它的做用和 EVAL 同样,都用于对脚本求值,但它接受的第一个参数不是脚本,而是脚本的 SHA1 校验和(sum)
若是服务器还记得给定的 SHA1 校验和所指定的脚本,那么执行这个脚本
若是服务器不记得给定的 SHA1 校验和所指定的脚本,那么它返回一个特殊的错误,提醒用户使用 EVAL 代替 EVALSHA
> evalsha 6b1bf486c81ceb7edf3c093f4c48582e38c0e791 0
"bar"
l SCRIPT FLUSH :清除全部脚本缓存
l SCRIPT EXISTS :根据给定的脚本校验和,检查指定的脚本是否存在于脚本缓存
l SCRIPT LOAD :将一个脚本装入脚本缓存,返回SHA1摘要,但并不当即运行它
l SCRIPT KILL :杀死当前正在运行的脚本
可使用redis-cli 命令直接执行脚本,格式以下:
$ redis-cli --eval script KEYS[1] KEYS[2] , ARGV[1] ARGV[2] ...
咱们可使用list类型的 lpush 和 rpop 实现消息队列
brpop 能够代替 rpop,若是消息队列中没有消息弹出,会一直阻塞,到时间了返回null,不会一直创建链接发送rpop命令
订阅消息( subscribe ) : subscribe test
发布消息( publish ) :publish test “我是Ninja”
对于redis做为缓存服务器,咱们在查询数据数据的时候,优先访问redis是否有数据,没有的话在查询数据库
查询完数据库,得到结果的时候,除了返回给前端外,还得将查询结果写入redis缓存
理解:
好比咱们根据主键去查询数据,主键做为key在redis中缓存,当咱们发起查询请求的时候,若是redis没有对应的值,那么查询就会创建数据库链接进行数据库查询,若是这个主键所关联的数据在数据库中也没有数据,而且对该主键查询的请求量还很大,就会对后端系统形成很大的压力,这就叫作缓存穿透
应对方案:
将对数据库查询为空的状况也进行缓存,只是缓存的时间设短一点,或者该key对应的数据有插入的时候清理缓存
作一个过滤,能够把全部的可能存在的key放到一个大的Bitmap中,查询时经过该bitmap过滤。(布隆表达式)
理解:
当缓存服务器重启,或者短期内大量缓存的有效时间所有到期,全部请求都走数据库进行查询,给后端系统形成巨大压力的状况
应对方案
不一样的key,设置不一样的过时时间,让缓存失效的时间点精良均匀
在缓存失效后,经过加锁或者队列的方式控制读取数据库写缓存的线程数量,好比查询某个key,咱们只容许一个线程去查询数据库,而后写缓存,其余线程等待,去缓存中获取数据
作二级缓存,一级缓存的有效时间短,二级缓存的有效期为长期,当一级缓存失效时访问二级缓存
理解:
缓存在某个过时的时候,刚好这个时候对与该key有大量的并发请求,这些请求全区后端数据库加载数据并写入到缓存,在成后端数据库瞬间崩塌,是否是缓存雪崩有点像?缓存击穿是针对某一个key ,缓存雪崩是针对多对key
应对方案:
使用redis的 setnx 互斥锁先进行判断,这样其余线程就处于等待状态,保证不会有大量并发请求去查询数据库就OJBK
或者使用双重检测锁解决这个问题:
若是为null ,咱们就要上锁了,咱们我都知道Spring容器默认是单列的,因此我以this为锁,
上锁以后,咱们再次从缓存中获取缓存数据,目的是咱们只须要一个请求去访问数据库,并将数据缓存到redis
其余请求处于等待队列中,稍后获取数据全是从redis缓存中获取,不会再去访问数据库,避免了热点key的问题
最大缓存 maxmemory = 0 ( 默认没设置大小 )
redis有最大缓存这一说法,也就是它的短板,默认没有最大值,当数据超过最大内存时,redis直接宕机
因此咱们须要给他设置最大值,当数据达到最大值时,就会触发数据淘汰策略,保证redis不会宕机
淘汰策略:
redis淘汰策略配置:maxmemory-policy voltile-lru,支持热配置
redis提供六种数据淘汰策略:
voltile-lru:从已设置过时时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰
volatile-ttl:从已设置过时时间的数据集(server.db[i].expires)中挑选将要过时的数据淘汰
volatile-random:从已设置过时时间的数据集(server.db[i].expires)中任意选择数据淘汰
allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰
allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰
no-enviction(驱逐):禁止驱逐数据
常见的实现就是使用一个联表保存缓存的数据,新数据插入到链表头部
当缓存数据被访问的时候,就把该缓存数据移到链表头部
当链表要满的时候,将链表尾部的数据丢弃
当存在热点数据时,LRU的效率很好,由于热点数据都在链表的头部部分,因此查询效率仍是高但偶发性、周期性的批量操做会致使热点数据不在头部部分,LRU的命中里急剧降低,缓存污染比较严重,这样的话,命中就须要遍历链表,得到数据,再次将热点数据移到头部