Redis
Nosql,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。key-value不仅仅是key-value。
安装(Mac)
- 使用Homebrew下载redis
brew search redis
- 找到最新版,现在稳定版为redis@4.0
brew install redis@4.0
,等待下载完成即可 - 下载完后会有提示
If you need to have redis@4.0 first in your PATH run: echo 'export PATH="/usr/local/opt/redis@4.0/bin:$PATH"' >> ~/.zshrc
就是很简单配置路径,复制粘贴回车即可。
启动
redis-server
启动服务端
redis-cli -p 6379
开启客户端
数据类型
基本数据类型
- String:
1 | > set k1 v1 # 存取 |
- List: 双向添加特性,lpush从list左边添加一个数据,rpush从右边添加一个数据,lpop左边删除,rpop右边删除,lrange获取指定范围元素。消息队列,栈。
1 | > rpush mylist A |
- **Set:**不能重复
1 | > sadd myset 1 2 3 # 增加set不排序 |
- Hash:
1 | > hmset user:1000 username antirez birthyear 1977 verified 1 # 一次存入多个键值对 |
- **Zset:**排序的set
1 | > zadd hackers 1940 "Alan Kay" # 添加 1940相当于排序的权值 |
特殊数据类型
-
Geospatial 地理位置
附近的人,计算距离。
geoadd 添加地理位置,经纬。
两级无法添加,城市文件导入
1
2
3
4
5> > geoadd china:city 116.40 39.90 beijing
> (integer) 1
> > geoadd china:city 121.47 31.23 shanghai 114.05 22.52 shenzhen
> (integer) 1
>geopos 获取指定城市位置
1
2
3
4
5
6> > geopos china:city beijing shanghai
> !) 1)"116.39999896287918091"
> 2)"39.90000009167092543"
> 2) 1)"121.46899000983782923"
> 2)"31.23228378291833803"
>geodist 返回两个给定位置之间的距离
1
2
3> > geodist china:city beijing shanghai km
> "1067.3788"
>georadius 以给定经纬度为中心,查找一定范围内的人/城市
1
2
3> > georadius china:city 110.00 30.00 1000km # georadius 查找的列表 中心经纬度 查找半径 withdist withcoord count 1 指定查询最大数量
> 1)"shenzhen"
>georadiusbymember 找出位于指定范围内的元素,中心点是由给定的位置元素决定=找出位于指定元素周围的其他元素
1
2
3> georadiusbymember china:city beijing 1000 km
> 1) "beijing"
> -
Hyperloglog 基数统计
什么是基数
A {1,3,5,7,8,7}
B {1,3,5,7,8}
基数 (不重复的元素) = 5 ,可以接受误差!
用作基数统计的算法,网页的UV(一个人访问一个网站多次,但还是算作一个人)
传统方式,set保存用户id,然后就可以统计set中的元素数量作为标准判断。
这个方式如果保存大量用户的ID就会比较麻烦,目的是为了计数,而不是保存用户id
优点:占用内存是固定的,2^64不同的元素的基数,只需要12KB的内存,如果从内存角度来比较的话十分首选。
0.81%的错误率
1
2
3
4
5
6
7
8
9
10> pfadd mykey a b c d e f g h i j # 添加数据
(integer) 1
> pfcount mykey # 统计不同的有多少
(integer) 10
> pfadd mykey2 i j z x c v b n m
(integer) 1
> pfmerge mykey3 mykey mekey2 # 把mykey和mykey2合并为一个集合
OK
> pfcount mykey3 # 统计两个的不同值
(integer) 15 -
Bitmap 位图场景
位存储
统计用户信息,只有两个状态的,都可以使用Bitmaps
只有0和1两个状态
1
2
3
4
5
6
7
8
9
10
11> setbit sign 0 1 # 存数据,表示0这个位置当天状态是1
(integer) 0
> setbit sign 1 0 # 表示1这个位置当天状态是0
(integer) 0
> getbit sign 0 # 获取某位置的状态
(integer) 1
> getbit sign 1
(integer) 0
> bitcount sign # 获取列表全部状态为1的总数
(integer) 1
事务
multi(创建一个事务)、exec(执行这个事务)、discard(清空事务队列)和 watch(监控)是 Redis 事务相关的命令。事务可以一次执行多个命令, 并且带有以下两个重要的保证:
- 事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。
- 事务是一个原子操作:事务中的命令要么全部被执行,要么全部都不执行。
multi命令用于开启一个事务,它总是返回 OK
。 multi执行之后, 客户端可以继续向服务器发送任意多条命令, 这些命令不会立即被执行, 而是被放到一个队列中, 当exec命令被调用时, 所有队列中的命令才会被执行。
通过调用 discard, 客户端可以清空事务队列, 并放弃执行事务。
watch 使得 exec 命令需要有条件地执行: 事务只能在所有被监视键都没有被修改的前提下执行, 如果这个前提不能满足的话,事务就不会被执行。被 watch的键会被监视,并会发觉这些键是否被改动过了。 如果有至少一个被监视的键在 exec 执行之前被修改了, 那么整个事务都会被取消, exec返回nil-reply来表示事务已经失败。简单来说,就是给该数据加了乐观锁,并且支持多个同时添加。
1 | > multi # 开启事务 |
Jedis
就是一个java整合了redis 的各种api工具,直接使用,由于和redis命令几乎相同,不做记录,放上别人的博客和官方API文档。
Springboot整合
springData 操作数据的整合,jdbc,mongodb,redis,Jpa…
springboot2.x后不使用jedis变成lettuce
jedis:采用直连,多个线程操作是不安全的,如果想要避免不安全,使用jedis pool连接池,BIO。
lettuce: 采用netty,实例可以在多个线程中进行共享,不存在线程不安全的情况!可以减少线程数据来,更像NIO模式
配置类源码分析
spingboot所有配置类,都有一个自动配置类 RedisAutoConfiguration
自动配置类都会绑定一个 properties 配置文件 RedisProperties
找到这个配置类
1 | // |
配置文件源码
1 | ( |
整合测试
-
导入依赖
1
2
3
4<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency> -
配置连接
1 | # application.yml |
-
测试
ops里几乎包含了所有redis操作。
1
2//连接操作
RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
配置连接池时使用lettuce实现类,因为源码中jedis的类没有注入成功。
自定义Redis配置类
1 | package com.redis.spring.config; |
工具类封装
1 | package com.redis.spring.util; |
redis.conf配置文件分析
启动的时候可以使用不同的配置文件启动redis
配置文件 unit单位 对大小写不敏感。
导入其他配置文件:
1 | include /path/to/local.conf |
网络
1 | bind 127.0.0.1 # 绑定ip |
通用
1 | ################################# GENERAL ##################################### |
快照
持久化,在规定时间内,执行了多少次操作,则会持久化到文件以 .rdb. .aof 后缀
redis内存数据库,持久化尤为重要
1 | ################################ SNAPSHOTTING ################################ |
安全
可以设置redis密码,默认无
1 | > config get requirepass # 获取redis的密码 |
限制
1 | ################################### CLIENTS #################################### |
redis的key缓存淘汰策略LRU和LFU
官网说:当某个key被设置了过期时间之后,客户端每次对该key的访问(读写)都会事先检测该key是否过期,如果过期就直接删除;但有一些键只访问一次,因此需要主动删除,默认情况下redis每秒检测10次,检测的对象是所有设置了过期时间的键集合,每次从这个集合中随机检测20个键查看他们是否过期,如果过期就直接删除,如果删除后还有超过25%的集合中的键已经过期,那么继续检测过期集合中的20个随机键进行删除。这样可以保证过期键最大只占所有设置了过期时间键的25%。
LRU实现
Redis维护了一个24位时钟,可以简单理解为当前系统的时间戳,每隔一定时间会更新这个时钟。每个key对象内部同样维护了一个24位的时钟,当新增key对象的时候会把系统的时钟赋值到这个内部对象时钟。比如我现在要进行LRU,那么首先拿到当前的全局时钟,然后再找到内部时钟与全局时钟距离时间最久的(差最大)进行淘汰,这里值得注意的是全局时钟只有24位,按秒为单位来表示才能存储194天,所以可能会出现key的时钟大于全局时钟的情况,如果这种情况出现那么就两个相加而不是相减来求最久的key。
Redis中的LRU与常规的LRU实现并不相同,常规LRU会准确的淘汰掉队头的元素,但是Redis的LRU并不维护队列,只是根据配置的策略要么从所有的key中随机选择N个(N可以配置)要么从所有的设置了过期时间的key中选出N个键,然后再从这N个键中选出最久没有使用的一个key进行淘汰。
LFU实现
如果出现key1的时间比key2时间要久,但key1比key2使用更频繁,所以合理的淘汰策略应该删除key2而不是key1,LFU就是解决这种问题而生。
LFU把原来的key对象的内部时钟的24位分成两部分,前16位还代表时钟,后8位代表一个计数器。16位的情况下如果还按照秒为单位就会导致不够用,所以一般这里以时钟为单位。而后8位表示当前key对象的访问频率,8位只能代表255,但是redis并没有采用线性上升的方式,而是通过一个复杂的公式,通过配置两个参数来调整数据的递增速度。
下图从左到右表示key的命中次数,从上到下表示影响因子,在影响因子为100的条件下,经过10M次命中才能把后8位值加满到255.
1 | # +--------+------------+------------+------------+------------+------------+ |
上面说的情况是key一直被命中的情况,如果一个key经过几分钟没有被命中,那么后8位的值是需要递减几分钟,具体递减几分钟根据衰减因子lfu-decay-time来控制。
上面递增和衰减都有对应参数配置,那么对于新分配的key呢?如果新分配的key计数器开始为0,那么很有可能在内存不足的时候直接就给淘汰掉了,所以默认情况下新分配的key的后8位计数器的值为5(应该可配资),防止因为访问频率过低而直接被删除。
低8位我们描述完了,那么高16位的时钟是用来干嘛的呢?目前我的理解是用来衰减低8位的计数器的,就是根据这个时钟与全局时钟进行比较,如果过了一定时间(做差)就会对计数器进行衰减。
aof配置
1 | ############################## APPEND ONLY MODE ############################### |
Redis持久化
RDB
RDB持久化是把当前进程数据生成快照保存到硬盘的过程,触发RDB持久化过程分为手动触发和自动触发
RDB的优点:
·RDB是一个紧凑压缩的二进制文件,代表Redis在某个时间点上的数据 快照。非常适用于备份,全量复制等场景。比如每6小时执行bgsave备份, 并把RDB文件拷贝到远程机器或者文件系统中(如hdfs),用于灾难恢复。
·Redis加载RDB恢复数据远远快于AOF的方式。
RDB的缺点:
·RDB方式数据没办法做到实时持久化/秒级持久化。因为bgsave每次运 行都要执行fork操作创建子进程,属于重量级操作,频繁执行成本过高。
·RDB文件使用特定二进制格式保存,Redis版本演进过程中有多个格式 的RDB版本,存在老版本Redis服务无法兼容新版RDB格式的问题。
针对RDB不适合实时持久化的问题,Redis提供了AOF持久化方式来解决。
AOF
AOF(append only file)持久化:以独立日志的方式记录每次写命令, 重启时再重新执行AOF文件中的命令达到恢复数据的目的。AOF的主要作用 是解决了数据持久化的实时性,目前已经是Redis持久化的主流方式
开启AOF功能需要设置配置:appendonly yes,默认不开启。AOF文件名 通过appendfilename配置设置,默认文件名是appendonly.aof。保存路径同 RDB持久化方式一致,通过dir配置指定。AOF的工作流程操作:命令写入 (append)、文件同步(sync)、文件重写(rewrite)、重启加载 (load)
-
所有的写入命令会追加到aof_buf(缓冲区)中。
-
AOF缓冲区根据对应的策略向硬盘做同步操作。
AOF为什么把命令追加到aof_buf中?Redis使用单线程响应命令,如 果每次写AOF文件命令都直接追加到硬盘,那么性能完全取决于当前硬盘负 载。先写入缓冲区aof_buf中,还有另一个好处,Redis可以提供多种缓冲区同步硬盘的策略,在性能和安全性方面做出平衡
- 随着AOF文件越来越大,需要定期对AOF文件进行重写,达到压缩的目的。
Redis发布订阅
pub/sub时一种消息通信模式
三部分:消息发送者,频道,消息订阅者
订阅端
1 | > subscribe weather # 订阅天气频道 |
发送端
1 | > publish weather "sunny" # 发布消息到频道 |
使用场景
- 实时消息(比如金十数据,微博…)
- 实时聊天室
- 订阅关注(微信,直播平台订阅功能…)
配合MQ使用
Redis主从复制
概念
主从复制,是将一台redis的服务器的数据复制到其他redis服务器,前者主节点master,后者从节点slave,数据复制是单向的。读写分离,主机以写为主,从机以读为主,可以减缓服务器压力,至少三台。
主要功能
- 数据冗余
- 故障恢复
- 负载均衡
- 高可用(集群)基石
一台redis是不能的(宕机)
- 结构上,单个会发生单点故障
- 容量上,单台redis最大使用内存不超过20G
环境配置
只配置从库,不配置主库,默认为主库。
配置多个不同端口的配置文件,分别启动。
一主二从:一主79,二从80,81
1 | > info replication # 查看主从配置情况 |
在.conf配置文件中可以永久配置从机跟从的主机ip/port
主机可以写,从机不能写只能读!主机中所有信息和数据都会被从机保存。主机断开,从机依旧连接到主机,但是没有写操作,这个时候主机恢复,还是可以读到数据,从机断开也一样。
复制原理
slave启动成功连接到master后会发送一个sync同步命令
- 全量复制:每个slave只要连接到master就会进行一次全部数据复制。
- 增量复制:新增的数据依次传给slave,完成同步
层层链路
中间的从机当连接的主机断开了,自己变成主机。
哨兵模式
自动监控主机是否故障,如果故障根据投票数自动将从库转换为主库。
哨兵是一个独立的进程,哨兵通过发送命令,等待redis服务器响应,从而监控运行多个redis实例。
多哨兵模式:哨兵之间互相监控
1 | > vim sentinel.conf |
后面的数字表示主机挂了的时候通过从机选出新的主机来投票。failover故障转移,随机在从机中选一个为主机,如果上一个主机恢复了,会变成新主机的从机。
优点:
- 哨兵集群,基于主从复制。
- 主从可以切换,故障可以转移,系统可用性会更好
- 哨兵就是主从模式的升级,更加健壮
缺点:
-
redis 不好在线扩容,集群一旦到达上限,在线扩容十分麻烦
-
实现哨兵模式的配置是很麻烦,有很多选择:
多哨兵集群配置哨兵端口
定义工作目录
监控主机ip port
主机密码
默认延时操作
并行同步时间
故障转移时间
.sh通知脚本
主节点
Redis缓存穿透和雪崩
如果对数据一致性问题要求比较高就无法使用缓存。
缓存穿透:查不到
如果查询一个数据缓存中没有命中,于是向持久层数据库查询,发现没有,于是查询失败,当用户多的时候请求多次持久层数据库,会造成持久层数据库压力,造成缓存穿透。
解决方案
-
布隆过滤器:
首先是对所有可能查询的参数以hash形式存储,当用户想要查询的时候,使用布隆过滤器发现不在集合中,就直接丢弃,不再对持久层查询。
-
缓存空对象:当存储层不命中后,即使返回的空对象也将其缓存起来,同时会设置一个过期时间,之后再访问这个数据将会从缓存中获取,保护了后端数据源;
造成问题:1. 造成空值较多 2.空值时间过期不一致
缓存击穿:量太大缓存过期
指一个key非常热点,在不停扛高并发,集中对这个点访问,在这个key失效的瞬间(过期),就有大量的请求并发访问持久层数据库,造成数据库压力,形成缓存击穿。
解决方案
-
设置热点不过期(影响效率不考虑)
-
使用互斥锁(mutex key):
业界比较常用的做法。简单地来说,就是在缓存失效的时候(判断拿出来的值为空),不是立即去访问持久层数据库,而是先使用缓存工具的某些带成功操作返回值的操作(比如Redis的setnx或者Memcache的add)去set一个mutex key,当操作返回成功时,再进行访问数据库的操作并回设缓存;否则,就重试整个get缓存的方法。
分布式锁:一个key只有一个线程允许访问数据库,其他线程等待。
缓存雪崩
缓存集中过期失效,redis缓存服务器宕机,访问量瞬间都会到数据库中,造成数据库压垮。
解决方案
-
**redis高可用:**这个思想的含义是,既然redis有可能挂掉,那我多增设几台redis,这样一台挂掉之后其他的还可以继续工作,其实就是搭建的集群。
-
**限流降级(HyStrix: 服务熔断,服务降级 – Dashboard流监控):**这个解决方案的思想是,在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。
-
**数据预热:**数据加热的含义就是在正式部署之前,我先把可能的数据先预先访问一遍,这样部分可能大量访问的数据就会加载到缓存中。在即将发生大并发访问前手动触发加载缓存不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀。