Redis官网:https://www.redis.net.cn/tutorial/3505.html
Nosql概述
为什么要用Nosql:
我们现在处于大数据时代,大数据一般的数据库无法进行分析处理。
关系型数据库:表,列,行。
NoSQL= not only sql
泛指非关系型数据库。随着web2.0互联网诞生,传统的关系型数据库很难对付web2.0时代,尤其是超大规模的高并发的社区。NoSQL在当今大数据环境下发展的十分迅速,Redis是发展最快的,而且是当下必须掌握的一个技术。
很多数据类型用户的个人信息,社交网络,地理位置,这些数据类型的存储不需要一个固定的格式,不需要多余的操作就可以横向扩展的。
NoSQL特点:
1、方便扩展(数据之间没有关系,很好扩展)
2、大数据量高性能(Redis一秒可以写八万次,可以读取十一万次。Nosql的缓存记录级,是一种细粒度的缓存,性能比较高)
3、数据类型是多样型的。(不需要事先设计数据库,随取随用,如果是数据量十分大的表,很多人就无法设计了)
4、传统的RDBMS和Nosql:
传统的RDBMS:
- 结构化组织
- SQL
- 数据和关系都存在单独的表中
- 操作数据定义语言
- 严格的一致性
- 基础的事务操作
NoSQL: - 不仅仅是数据
- 没有固定的查询语言
- 键值对存储,列存储,文档存储,图形数据库(社交关系)
- 最终一致性。
- CAP定理,BASE理论(异地多活)
- 高性能,高可用,高可扩
大数据时代的3V:海量、多样、实时
大数据时代的3高:高并发,高可拓,高性能
在公司中的事件是NoSQL+关系型数据库,一起使用才是最强的。
NoSQL的四大分类:
1、KY键值对
- 新浪:Redis
- 美团:Redis+Tair
- 阿里:Redis+memecache
2、文档型数据库 - MongoDB(基于分布式文件存储的数据库,是介于关系型数据库和非关系型数据库中中间产品,非关系型数据库中功能最丰富,最像关系型数据库的)
- ConthDB
3、列存储数据库 - HBase
- 分布式文件系统
4、图关系数据库
(不是存图形,是存关系,比如:社交网络) - Neo4j
Redis概述:
Redis:即远程字典服务,是一共开源的使用ANSI C语言编写,可基于内存亦可持久化的日志型,KV数据库,并提供多种语言的API。
是当下最热门的NoSQL技术之一,也被人们称为结构化数据库。
Redis能干嘛?
- 内存存储,持久化。
- 效率高,可以用于高速缓存
- 发布订阅系统
- 地图信息分析
- 计时器、计数器
特点: - 多种的数据类型
- 持久化
- 集群
- 事务
基础知识:
redis默认有16个数据库。默认使用的是第0个,可以使用select进行切换数据库。
127.0.0.1:6379> select 3 #切换数据库
OK
127.0.0.1:6379[3]> dbsize#查看数据库大小
(integer) 0
127.0.0.1:6379[3]> set name shuanggem
OK
127.0.0.1:6379[3]> dbsize
(integer) 1
127.0.0.1:6379[3]> get name
"shuanggem"
127.0.0.1:6379[3]> flushdb #清除当前数据库
OK
Redis是单线程的,Redis是很快的,官方表示,Redis是基于内存操作,CPU不是Redis性能的瓶颈。Redis的瓶颈是根据机器的内存和网络带宽,既然可以使用单线程来实现,就使用单线程了。
Redis为什么单线程还能这么快?
- 误区一:高性能的服务器一定是多线程的。
- 误区二:多线程一定比单线程效率高。
核心:Redis是将所有的数据全部放在内存中的,所以说使用单线程去操作效率就是最高的。
对于内存系统来说,如果没有上下文切换效率就是最高的,多次读写在一个CPU上,在内存情况下,就是效率最高的。
什么是Redis五大基本类型
Redis支持五种数据类型:string(字符串),hash(哈希),list(列表),set(集合)及zset(sorted set:有序集合)。
String类型
127.0.0.1:6379> set key1 v1 #设置值
OK
127.0.0.1:6379> get key1 #获取值
"v1"
127.0.0.1:6379> keys * #查看值
1) "age"
2) "key1"
127.0.0.1:6379> move age #删除值
(error) ERR wrong number of arguments for 'move' command
127.0.0.1:6379> move age 1
(integer) 1
127.0.0.1:6379> append key1 "hello" #追加字符串,如果key1不存在,则新建
(integer) 7
127.0.0.1:6379> get key1
"v1hello"
127.0.0.1:6379> strlen key1 #获取字符串长度
(integer) 7
127.0.0.1:6379> append key1 "world" #追加字符串
(integer) 12
127.0.0.1:6379> set views 0
OK
127.0.0.1:6379> get views
"0"
127.0.0.1:6379> incr views #自增1
(integer) 1
127.0.0.1:6379> get views
"1"
127.0.0.1:6379> decr views #自减1
(integer) 0
127.0.0.1:6379> incrby views 10 #增10
(integer) 10
127.0.0.1:6379> get views
"10"
127.0.0.1:6379> decrby views 5 #减5
(integer) 5
127.0.0.1:6379> get views
"5"
127.0.0.1:6379> set key1 "hello,shuangge"
OK
127.0.0.1:6379> get key1
"hello,shuangge"
127.0.0.1:6379> getrange key1 0 3 #获取【0,3】范围的字符
"hell"
127.0.0.1:6379> getrange key1 0 -1 #获取全部字符串
"hello,shuangge"
127.0.0.1:6379> setrange key1 1 xx #替换指定位置的字符串
(integer) 14
127.0.0.1:6379> get key1
"hxxlo,shuangge"
setex(设置过期事件)
setnx(不存在设置)在分布式锁中常使用
127.0.0.1:6379> setex key3 30 "hello" #设置key3值三十秒后过期
OK
127.0.0.1:6379> setnx mykey "redis" #如果mykey不存在,创建mykey,如果存在,创建失败
(integer) 1
127.0.0.1:6379> ttl key3
(integer) 5
127.0.0.1:6379> ttl key3
(integer) -2
127.0.0.1:6379> setnx mykey "mysql"
(integer) 0
127.0.0.1:6379> get mykey
"redis"
127.0.0.1:6379> mset k1 v1 k2 v2 k3 v3 #同时设置多个值
OK
127.0.0.1:6379> keys *
1) "k3"
2) "k1"
3) "k2"
127.0.0.1:6379> mget k1 k2 k3
1) "v1"
2) "v2"
3) "v3"
127.0.0.1:6379> msetnx k1 v1 k4 v4 #是个原子性操作,,要么一起成功,要么一起失败
set user:1{name:zhangsan,age:3} #设置一个user:1对象 值为json字符来保存一个对象。
127.0.0.1:6379> mset user:1:name zhangsan user:1:age 2
OK
127.0.0.1:6379> mget user:1:name user:1:age
1) "zhangsan"
2) "2"
List(列表)
Redis 列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素导列表的头部(左边)或者尾部(右边)。
实例
127.0.0.1:6379> lpush k1 redis
(integer) 1
127.0.0.1:6379> lpush k1 mongodb
(integer) 2
127.0.0.1:6379> lpush k1 rabitmq
(integer) 3
127.0.0.1:6379> lrange k1 0 10
1) "rabitmq"
2) "mongodb"
3) "redis"
list实际上是一个链表。
Set(集合)
Redis的Set是string类型的无序集合。
集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是O(1)。
sadd 命令
添加一个string元素到,key对应的set集合中,成功返回1,如果元素以及在集合中返回0,key对应的set不存在返回错误。
sadd key member
实例
127.0.0.1:6379> sadd redis.net.cn redis
(integer) 1
127.0.0.1:6379> sadd redis.net.cn mongodb
(integer) 1
127.0.0.1:6379> sadd redis.net.cn rabitmq
(integer) 1
127.0.0.1:6379> sadd redis.net.cn rabitmq
(integer) 0
127.0.0.1:6379> smembers redis.net.cn
1) "rabitmq"
2) "mongodb"
3) "redis"
注意:以上实例中 rabitmq 添加了两次,但根据集合内元素的唯一性,第二次插入的元素将被忽略。
Hash(哈希)
Redis hash 是一个键值对集合。
Redis hash是一个string类型的field和value的映射表,hash特别适合用于存储对象。
实例
127.0.0.1:6379> HMSET user:1 username redis.net.cn password redis.net.cn points 200
OK
127.0.0.1:6379> HGETALL user:1
1) "username"
2) "redis.net.cn"
3) "password"
4) "redis.net.cn"
5) "points"
6) "200"
127.0.0.1:6379>
以上实例中 hash 数据类型存储了包含用户脚本信息的用户对象。 实例中我们使用了 Redis HMSET, HEGTALL 命令,user:1 为键值。
zset(sorted set:有序集合)
Redis zset 和 set 一样也是string类型元素的集合,且不允许重复的成员。
不同的是每个元素都会关联一个double类型的分数。redis正是通过分数来为集合中的成员进行从小到大的排序。
zset的成员是唯一的,但分数(score)却可以重复。
zadd 命令
添加元素到集合,元素在集合中存在则更新对应score
zadd key score member
实例
redis 127.0.0.1:6379> zadd redis.net.cn 0 redis
(integer) 1
redis 127.0.0.1:6379> zadd redis.net.cn 0 mongodb
(integer) 1
redis 127.0.0.1:6379> zadd redis.net.cn 0 rabitmq
(integer) 1
redis 127.0.0.1:6379> zadd redis.net.cn 0 rabitmq
(integer) 0
redis 127.0.0.1:6379> ZRANGEBYSCORE redis.net.cn 0 1000
1) "redis"
2) "mongodb"
3) "rabitmq"
Hyperloglog
基数,不重复的元素。
Hyperloglog是用来做基数统计的算法。
优点:占用的内存是固定的。
127.0.0.1:6379> PFadd mykey a b c d e f g h i j
(integer) 1
127.0.0.1:6379> pfcount mykey
(integer) 10
127.0.0.1:6379> pfadd mykey2 i j z x c v b n m
(integer) 1
127.0.0.1:6379> pfcount mykey2
(integer) 9
127.0.0.1:6379> pfmerge mykey3 mykey mykey2
OK
127.0.0.1:6379> pfcount mykey3
(integer) 15
Bitmaps
位存储
使用bitmap记录三天内打卡情况(模拟)
127.0.0.1:6379> setbit sign 0 1
(integer) 0
127.0.0.1:6379> setbit sign 1 0
(integer) 0
127.0.0.1:6379> setbit sign 2 0
事务
Redis事务没有原子性,没有隔离级别的概念。
Redis事务本质:一组命令的集合。一个事务中的所有命令都会被序列化,在事务执行过程中,会按顺序执行。一次性、顺序性、排他性。
Redis事务:
- 开启事务(multi)
- 命令入队
- 执行事务(exec)
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> discard #放弃事务
锁:Redis可以实现乐观锁
悲观锁:很悲观,什么时候都会出问题,无论做什么都会加锁
乐观锁:很乐观,认为什么时候都不会出问题,所以不会上锁。更新数据的时候去判断一下,在此期间是否有人修改过数据。
- 获取version
- 更新时比较version
正常执行成功
127.0.0.1:6379> set money 100
OK
127.0.0.1:6379> set out 0
OK
127.0.0.1:6379> watch money
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> decrby money 20
QUEUED
127.0.0.1:6379> incrby out 20
QUEUED
127.0.0.1:6379> exec
1) (integer) 80
2) (integer) 20
Redis持久化
RDB:redis database
在指定的时间间隔内将内存中的数据集快照写入磁盘,它恢复时是将快照文件直接读到内存里。
rdb保存的文件是dump.rdb,都是在我们的配置文件中配置的。
触发机制:
1、save的规则满足的情况下,会自动触发。
2、执行flushall命令,也会触发。
3、推出redis也会产生rdb文件。
如何恢复rdb文件:
只需要将rdb文件放在redis启动目录就可以。redis启动时会自动检查dump.rdb恢复其中的数据。
优点:
1、适合大规模数据恢复
2、对数据完整性要求不高
缺点:
1、需要一定的时间间隔进行操作。
2、fork进程的时候,会占用一定的内存空间。
AOF:append only file
将我们所有命令都记录下来aof保存的是appendonly.aof文件。默认不开启,需要我们手动配置。
优点:
1、每一次修改都同步,文件的完整会更好。
2、每秒同步一次。
3、从不同步,效率是最高的。
缺点:
1、相对于数据文件来说,aof远远大于rdb,修复速度也慢。
2、aof运行效率也慢。所以我们redis默认的配置是rdb持久化。
Redis主从复制
主从复制,读写分离,架构中经常使用,一主二从。
默认情况下,每一台服务器都是一个主节点。
一般只需要配置从机即可。
哨兵模式
主从切换技术的方法是:当主服务器宕机后,需要手动把一台从服务器切换为主服务器。这就需要人工干预,更多时候,我们会优先考虑哨兵模式来解决这个问题。
哨兵模式是一种特殊的模式,哨兵是一个独立的进程,作为进程,它会独立运行,其原理是哨兵通过发送命令,等待Redis服务器响应,从而监控运行的多个Redis实例。
这里的哨兵有两个作用:
1、通过发送命令,让Redis服务器返回监控其运行状态,包括主服务器和从服务器。
2、当哨兵检测到主服务器宕机,会自动将从服务器切换为主服务器,然后通过发布订阅模式通知其他服务器,修改配置文件,让他们切换主机。
优点:
1、哨兵集群,基于主从复制模式,所有的主从配置优点,它都有。
2、主从可以切换,故障可以转移,系统的可用性就会更好。
3、哨兵模式就是主从模式的升级,手动到自动,更加健壮。
缺点:
1、Redis不好在线扩容。
2、实现哨兵模式的配置其实很麻烦。
缓存穿透和雪崩
缓存穿透是指查询一个根本不存在的数据,缓存层和持久层都不会命中。在日常工作中出于容错的考虑,如果从持久层查不到数据则不写入缓存层,缓存穿透将导致不存在的数据每次请求都要到持久层去查询,失去了缓存保护后端持久的意义。 缓存穿透问题可能会使后端存储负载加大,由于很多后端持久层不具备高并发性,甚至可能造成后端存储宕机。通常可以在程序中统计总调用数、缓存层命中数、如果同一个Key的缓存命中率很低,可能就是出现了缓存穿透问题。
造成缓存穿透的基本原因有两个。第一,自身业务代码或者数据出现问题(例如:set 和 get 的key不一致),第二,一些恶意攻击、爬虫等造成大量空命中(爬取线上商城商品数据,超大循环递增商品的ID)
解决方案:
- 缓存空对象
缓存空对象:是指在持久层没有命中的情况下,对key进行set (key,null)
缓存空对象会有两个问题:第一,value为null 不代表不占用内存空间,空值做了缓存,意味着缓存层中存了更多的键,需要更多的内存空间,比较有效的方法是针对这类数据设置一个较短的过期时间,让其自动剔除。第二,缓存层和存储层的数据会有一段时间窗口的不一致,可能会对业务有一定影响。例如过期时间设置为5分钟,如果此时存储层添加了这个数据,那此段时间就会出现缓存层和存储层数据的不一致,此时可以利用消息系统或者其他方式清除掉缓存层中的空对象
- 布隆过滤器拦截
在访问缓存层和存储层之前,将存在的key用布隆过滤器提前保存起来,做第一层拦截,当收到一个对key请求时先用布隆过滤器验证是key否存在,如果存在在进入缓存层、存储层。可以使用bitmap做布隆过滤器。这种方法适用于数据命中不高、数据相对固定、实时性低的应用场景,代码维护较为复杂,但是缓存空间占用少。
布隆过滤器实际上是一个很长的二进制向量和一系列随机映射函数。布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率和删除困难。
二、缓存雪崩
由于缓存层承载着大量请求,有效地保护了存储层,但是如果缓存层由于某些原因不可用(宕机)或者大量缓存由于超时时间相同在同一时间段失效(大批key失效/热点数据失效),大量请求直接到达存储层,存储层压力过大导致系统雪崩。
解决方案:
可以把缓存层设计成高可用的,即使个别节点、个别机器、甚至是机房宕掉,依然可以提供服务。利用sentinel或cluster实现。
采用多级缓存,本地进程作为一级缓存,redis作为二级缓存,不同级别的缓存设置的超时时间不同,即使某级缓存过期了,也有其他级别缓存兜底
缓存的过期时间用随机值,尽量让不同的key的过期时间不同(例如:定时任务新建大批量key,设置的过期时间相同)
三、缓存击穿
系统中存在以下两个问题时需要引起注意:
当前key是一个热点key(例如一个秒杀活动),并发量非常大。
重建缓存不能在短时间完成,可能是一个复杂计算,例如复杂的SQL、多次IO、多个依赖等。
在缓存失效的瞬间,有大量线程来重建缓存,造成后端负载加大,甚至可能会让应用崩溃。
解决方案:
- 分布式互斥锁 只允许一个线程重建缓存,其他线程等待重建缓存的线程执行完,重新从缓存获取数据即可。set(key,value,timeout)
- 永不过期
从缓存层面来看,确实没有设置过期时间,所以不会出现热点key过期后产生的问题,也就是“物理”不过期。
从功能层面来看,为每个value设置一个逻辑过期时间,当发现超过逻辑过期时间后,会使用单独的线程去更新缓