什么时NoSql
not only sql,泛指非关系型数据库。
NoSql的特点
解耦
方便扩展(没有数据之间的关系)
大数据量,高性能(1秒可以写8万次,可以读11万次)
数据类型是多样性的,不需要设计表,随取随用
redis (remote dictionary server) 远程服务字典
启动redis服务器
redis-server redos.conf
客户端启动连接redis服务器
redis-cli -p 6379
压力测试工具
redis-benchmark -h localhost -p 6379 -c 100 -h 100000 默认测试主机的话,地址和端口可以省略
redis基础知识
redis有16个数据库默认使用的是第0个数据库,可以通过select num(数据库编号)切换数据库
查看数据库大小 dbsize
查看当前数据库下所有的key keys *
清空当前数据库 flushdb 清空全部数据库 flushall
为什么redis是单线程的
因为redis是基于内存操作的,cpu不是redis的性能瓶颈,而redis的速度取决于,内存速度和带宽速度,多线程跟cpu有关,所以就使用了单线程
误区:多线程一定比单线程快,比如说一个单核CPU 在进行线程切换时会存在上下文的切换,这是一个耗时操作。计算机中 速度大小为 CPU > 内存 > 硬盘,而redis的所有数据都是保存在内存中的,因此,对于redis来说单线程是最快的,多线程的上下文切换耗时
Redis-Key
- set name xuhaijun 添加值为xuhaijun 的键name
- exists name 判断name键是否存在
- move name 1 将name移动到1数据库
- get name 获取键name的值
- expire name 10 设置键name10秒之后过期
- ttl name 查看键name 还有多长时间过期
- type name 查看name的类型
String
- set key v1 设置值
- get key 获取值
- append key v2 追加字符串,如果当key不存在就相当于 set key
- strlen key 获取字符串的长度
- incr view 自增1
- decr view 自减1
- incrby view 10 增加10
- decrvy view 10 减去10
- getrange key 0 3 获取key 0,3范围的数据
- getrange key 0 -1 查看key 的全部字符串
- setrange key 1 xxx 从1号位置开始替换, 比如key = abcdef 结果就为 axxxef
- setex key 30 "hh" 设置key的值,并且30s过期
- setnx key "hh" 如果key 这个键不存在就创建,如果存在则创建失败
- mset k1 v1 k2 v2 k3 v3 同时设置多个值
- mget k1 k2 k3 同时获取多个值
- msetnx k1 v1 k2 v2 k3 v3 原子性操作,要么同时成功,要么同时失败
- getset key hh 先get 然后set 如果数据库中不存在键值则返回一个nul值
List
- lpush list one 将一个或多个值放到列表的头部
- rpush list two 将一个或多个值放到列表的尾部
- lrange list 0 1 获取列表0到1 元素
- lrange list 0 -1 获取列表所有元素
- lpop list 移除左边第一个元素
- rpop list 移除右边第一个元素
- lindex list 1 获取列表中国下标为1的值
- llen list 获取列表长度
- lrem list 1 one 移除列表中确定的值one, 如果有存在的只移除一个
- ltrim list 1 2 截取列表中1 到 2 的元素 此时list 已经改变只剩下截取的部分
- rpoplpush list newlist 移除list列表中的最后一个元素,放到newlist的头部
- lset list 0 item 将list0号位置的值替换成 item,如果不存在列表则报错
- linsert list before "hello" "item" 在list 列表中的hello元素前面插入item,before 改成after 则在后面
Set
- set 中值不能重复
- sadd set hello 像set中添加元素
- smembers set 查看set中的所有元素
- sismembers hello 查看set中是否包含指定元素
- scard set查看set中元素个数
- srem set hello 移除set中的hello
- srandmember set 随机获取set集合中的一个元素
- srandmember set 3 随机获取set集合中的3个元素
- spop set 随机移除set 中的一个元素
- smove set myset hello 将set中的hello移动到myset
- sdiff key1 key2 查看两个集合的差集以key1 作为参考集合
- sinter key1 key2 查看两个集合的交集
- sunion key1 key2 查看两个集合的并集
Hash
- hset myhash key value 像hash中添加元素
- hget myhash key 获取hash中指定键的值
- hmset myhash key1 value1 key2 value2 像hash中添加多个元素
- hmget myhash key1 key2 获取多个元素
- hgetall myhash 获取所有元素
- hdel myhash key 删除指定字段
- hlen myhash 获取hash表中字段个数
- hexists myhash key 判断指定字段是否存在
- hkeys myhash 获取所有字段
- hvals myhash 获取所有值
- hsetnx myhash key value 如果不存在设置成功如果存在设置失败
- hincrby myhash key 1 增加1
- hdecrvy myhash key -1 减去1
Zset(有序)
- zadd salary 200 zhangsan 300 lisi 500 wangwu 添加元素
- zrange salary 0 -1 获取所有元素
- zrangebyscore salary -inf +inf 从小到大显示所有用户
- zrangebyscore salary -inf 400 从小到大显示score小于400的用户
- zrevrange salary 0 -1 从大到小显示所有用户
- zrem salary lisi 移除元素
- zcard salary 获取集合中元素个数
- zcount salary 1 3 获取score在1和3 之间的元素数量
geospatial(地理位置)
- geoadd china:city 116.40 39.90 beijing 添加元素
- geopos china:city beijing shanghai 获取指定一个或多个城市的经纬度
- geodist china:ctity beijing shanghai km 查看北京到上海的直线距离,单位是千米
- georadius china:city 110 36 1000 km count 2 获取以指定坐标点为圆心的半径1000km 的所有元素count 用于限定数量,不加查询所有
- georadiusbymember china:city beijing 100km count 2 以城市名为圆心
- geo 的底层实现是zset因此对于zset的一些操作命令也适用于geo
Hyperloglog
基数—一个集合中不重复的元素
优点:在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定的、并且是很小的。
在 Redis 里面,每个 HyperLogLog 键只需要花费 12 KB 内存,就可以计算接近 2^64 个不同元素的基数
应用场景:
网页UV(网站独立访客,一个人访问多次也算作一次)
传统方式通过set集合进行保存,但是这种方式会保存大量的用户ID,非常麻烦,我们的目的是用于计数而不是统计id。
- pfadd mykey a b c d 添加元素
- pfcount mykey 计算不重复元素个数
- pfmerge mykey3 mykey1 mykey2 合并mykey1 和mykey2 到mykey3
Bitmap(位存储)
- setbit sign 0 1 添加元素,第一个为元素位置 第二个数为值,只能是0或1
- getbit sign 0 获取元素
- bitcount sign 获取值为1的个数 后面可以加范围
事务
redis 中单条命令是保证原子性的,但是事务不保证原子性
redis 事务的本质:一组命令的集合,一个事务中的所有命令都会被序列化 将命令放入队列中,按照顺序执行
redis 事务操作
- 开启事务 multi
- 命令入队
- 执行事务 exec
- 放弃事务 discard
编译时异常:所有的命令都不会被执行
执行时异常:不保证原子性,错误命令不被执行,其它命令正常执行
redis 实现乐观锁
悲观锁:认为什么都会出问题,什么操作都会加锁
乐观锁:认为什么都不会出问题,什么操作都不会加锁,更新数据的时候判断在此期间数据是否发生改变
实现:
- watch money 通过watch监视money 字段
- multi 开启事务
- ......命令入队
- exec 执行事务,如果发现这时money在此期间没有发生改变,提交成功,如果有人在此期间修改了money的数据则事务执行失败,失败后应执行unwatch放弃监视,然后从新监视
Redis 持久化
因为Redis 是内存数据库,如果不将内存中的数据保存到硬盘上的话,一旦关闭服务器进程,就会导致数据丢失
RDB
RDB实现原理:经过一段时间间隔Redis会单独fork一个子进程来进行持久化操作,首先会将数据写入一个临时文件中,等到持久化过程结束了,再用这个临时文件替换上次持久化好的文件。整个过程,朱金城不进行任何IO操作。这就确保了极高的性能。如果需要进行大规模的数据恢复,并且对于数据恢复的完成性不是非常的敏感,那RDB方式要比AOF方式更加高效。RDB的确定是最后一次持久化后的数据可能丢失。
触发机制:save规则满足的情况下(比如60秒写入了10000条数据),或者执行flushall命令时,关闭redis服务的时候 都会触发rdb规则生成dump.rdb文件
恢复机制:确保dump.rdb文件在redis的启动目录下,当redis启动时会自动扫描并且恢复
优点:
- 适合大规模的数据恢复
缺点: - 适合对数据完整性要求不高的服务
- 需要一定的时间间隔有,如果redis意外宕机,那么最后一次修改的数据就没有了
- fork进程的时候,会占用一定的内容空间
AOF
AOF实现原理,以日志的形式来记录每一个写的操作,将redis执行过的所有指定记录下来(读操作不记录)appendonly.aof,只许追加文件但不可以改写文件,redis启动之初会读取该文件重新构建数据,换言之,redis重启的话根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作
优点:
- 每一次修改都同步,文件的完整会更好
缺点: - 相对于数据文件来说,aof远远大于rdb,修复的速度也比rdb慢
- aof运行效率比rdb慢,所以我们redis默认的配置就是rdb的持久化
Redis发布订阅
redis发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送信息,订阅者(sub)接收信息。(微信,微博的关注系统)redis客户端可以订阅任意数量的频道
- subscribe mychannel 订阅一个频道
- publish muchannel 发布者发布消息到频道
实现原理:通过publish命令向订阅者发送信息,redis-server会使用给定的频道作为键,在它所维护的channel字典中查找记录了订阅这个频道的所有客户端的链表,遍历这个链表,将信息发布给所有订阅者。
Redis主从复制
主从复制,是指将一台redis服务器的数据,复制到其他redis服务器,前者称为主节点,后者称为从节点,数据复制是单向的,只能有主节点到从节点。master以写为主,slave以读为主。
默认情况下,每台redis服务器都是主节点,且一个主节点可以有多个从节点,但一个从节点只能有一个主节点。
作用:
- 数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式。
- 故障恢复:当主节点出现问题时,可以有从节点提供服务,实现快速的故障恢复;实际上是服务的冗余
- 负载均衡:在主从复制的基础上,配合读写分离,可以由主节点提供写服务,由从节点提供读服务(即写redis数据时应用连接主节点,读redis数据时应用连接从节点),分担服务器负载,尤其在写少读多的场景下,通过多个节点分担读负载,可以大大提高redis服务器的并发量。
- 高可用基石:除了上述作用以外,主从复制还是哨兵和集群能够实施的基础,因此说主从复制是redis高可用的基础
配置:
slave 127.0.0.1 6379 认定主机,或者在配置文件中设置
复制原理:Slave启动成功连接到master(主机)后会发送一个sync同步命令。Master接到命令,启动后台的存盘进程,同时收集所有接收的用于修改数据集命令,在后台进程执行完毕之后 ,master将传送整个数据文件到slave并完成一次同步。
全量复制:slave服务在接收到数据库文件数据后将其存盘并加载到内存中
增量复制:Master继续将新的所有收集到的修改命令依次传给slave,完成同步
只要重新连接master,一次完全同步(全量复制)将会被自动执行,我们的数据一定可以在从机中看到
如果主机断开了,从机可以使用slaveof no one 来使自己成为主机,其它节点再手动连接主节点
哨兵模式
配置哨兵模式的配置文件为sentinel.conf
修改配置文件 sentinel monitor myredis 127.0.0.1 6379 1
启动哨兵 redis-sentinel sentinel.conf
在主从复制的基础上配置一个哨兵,用来监视 主机和从机的状态,当主机宕机之后,会自动切换主机。如果主机回来了,只能归并到新主机下,当作从机。
优点:
- 哨兵集群,基于主从复制模式,拥有所有的主从配置优点
- 主从可以切换,故障可以转移,系统的可用性就会更好
- 哨兵模式就是主从复制的升级,手动到自动更加健壮
缺点:
- redis不好在线扩容,集群一旦达到上限,在线扩容就十分麻烦
- 配置文件繁琐
redis缓存穿透与雪崩
redis缓存的使用,极大的提升了应用程序的性能和效率,特别 是数据查询方面。但同时,他也 带来了一些问题,其中,最要害的问题就是数据的一致性问题,从严格意义上来讲,这个问题无解,如果对于数据的一致性要求很高,那么就不能使用缓存。另外的一些典型的问题就是,缓存穿透,缓存雪崩和缓存击穿。目前都有比较流行的解决方案
缓存穿透:
缓存穿透的概念很简单,用户想要查询一个数据,发现redis内存数据库没有,也就也是缓存没有命中,于是向持久层数据库查询,发现也没有,于是查询失败,当用户过多时,缓存都没有命中,于是都去请求了持久层数据库。这会给持久层数据库造成很大的压力,这时候就相当于出现了缓存穿透
解决方案:
-
布隆过滤器
布隆过滤器是一种数据结构,对所有可能查询的参数以hash形式存储,在控制层先进行校验,不符合则丢弃,从而避免了对底层存储系统的查询压力
-
缓存空对象
当数据层不命中后,即使返回空对象也将其缓存起来,同时设置一个过期时间,之后 在访问这个数据将会从缓存中获取,保护了后端的数据源
存在问题:如果空值可以被缓存起来,这就意味着缓存需要更多的空间存储更多的键,因为这当中可能会有很多的空值的键。即使对空值设置了过去时间,还是会存在缓存层和存储层的数据会有一段时间不一样,这对需要保持一致性的业务会有影响
缓存击穿
缓存击穿是指一个key 非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像是在屏障上凿开一个洞。当某个key在过期的瞬间,有大量的请求并发访问,这类的数据一般是热点数据,由于缓存过期,会同时访问数据库查询最新数据并且写回缓存,会导致数据库瞬间压力过大
解决方案:
- 设置热点数据永不过期
从缓存层面来看,没有设置过期时间,所以不会出现热点key过期后产生的问题 - 加互斥锁
分布式锁:使用分布式锁,保证每个key同时只能有一个线程取查看后端服务,其它线程没有获得分布式锁的权限,因此只需要等待即可,这种方式讲高并发的压力转移到了分布式锁上,因此对分布式所的考研很大
缓存雪崩
缓存雪崩是指在某一时间段,缓存集中过期失效或者服务器宕机
产生原因:双十一期间的商品抢购,商品信息会集中放入缓存并且设置过期时间,那么到了过期时间的时候。所有商品的缓存同时 过期,然后对于这些商品的查询全部都落到了数据库上,对于数据库而言,就会产生周期性的压力波峰,于是所有的请求都会到达存储层,存储层的调用量会暴增,造成存储层也会挂掉
解决方案:
- redis高可用
多增加redis服务器,搭建集群 - 限流降级
在缓存失效后,通过加锁或者队列来控制书数据库写缓存的线程数量。比如对某一个key只允许一个线程查询和写缓存,其它线程等待 - 数据预热
在正式部署之前,把可能的数据预先访问一遍,这样部分可能大量访问的数据就会加载到缓存中,在即将发生大并发访问前手动触发加载缓存不同的key,设置不同的过期时间,让缓存失效的时间尽量均匀