Redis基础知识
Redis简介
Redis是由c语言开发的一款完全开源免费的key-value数据库,它支持多种语言的API。Redis是非关系型数据库,因为web2.0的兴起,非关系型数据库变得异常火爆,传统的关系型数据库暴露了太多的问题,关系型数据库很难实现高并发,高效率。
Redis的优点是效率高、丰富的数据类型、原子性(多个操作也支持事务)。
缺点是数据缺少结构化、耗内存(因为redis直接将数据保存在内存中,如果需要持久化有两种方案,一种是定时快照(RDB),即每隔一段时间就将内存中的数据存入磁盘,每次均是写全部数据,代价很高;另一种是只追踪语句的变化(AOF),但是这一种需要大量的log,同时所有操作重新执行一遍。)
Redis安装
- 到官网下载tar包并上传到虚拟机:redis的tar包下载地址
- 将压缩包解压到opt目录下:
tar -zxvf 安装包 -C /opt/
- 安装gcc环境:
apt-get install build-essential
- redis的目录下执行
make
命令安装 - 安装完毕后输入
make install
测试,发现都显示install - 进入
/usr/local/bin
目录下,redis已经安装成功 - 在该目录下创建myconf目录:
mkdir myconf
- 将redis中的配置文件拷贝到myconf中:
cp /opt/redis-6.0.6/redis.conf myconf
- 进入myconf目录,修改该配置文件中的
daemonize
为yes
,修改为后台运行 - 回到
/usr/local/bin
目录下使用命令redis-server myconf/redis.conf
启动redis服务 - 使用redis客户端连接:
redis-cli -p 6379
- 通过客户端关闭redis服务:
shutdown
Redis基本知识
默认有16个数据库,索引从0到15,默认使用的是第0个数据库。redis5.0版本是单线程的,但是6.0开始变为了多线程。redis基于内存,所以CPU并不是redis的瓶颈,其每秒QPS接近10万+,
命令 | 说明 |
---|---|
select index | 切换数据库 |
dbsize | 查看当前数据库大小 |
keys * | 查看所有键 |
flushdb | 清空当前数据库 |
flushall | 清空全部数据库的内容 |
Redis五大基本类型
基本命令
命令 | 类型 |
---|---|
expire key_name second | 为key设置过期时间 |
ttl key_name | 查看剩余过期时间 |
move key_name index | 将该键值移动到指定数据库 |
exists key_name | 查看该key是否存在 |
del key_name | 删除某个key |
rename key_name1 key_name2 | 修改key的名称 |
type key_name | 查看该类型 |
- String类型:使用场景比如计数器
命令 | 说明 |
---|---|
set key_name value | 设置key、value |
setex key second value | 设置键值并设置超时 |
setnx key_name value | 键不存在则设置,设置成功返回1,否则0 |
get key | 得到key的值 |
append key_name value | 向该key中追加值 |
strlen key_name | 查看该key的value长度 |
getrange key_name start end | 得到key中值的一部分 |
getset key value | 设置新值,返回旧值 |
mset key1 value key2 value2 | 批量设置键值 |
msetnx key1 value key2 value2 | 不存在则设置,有一个不成功都不成功 |
mget key1 key2 | 批量获取键的值 |
incr key_name | 对应的值+1 |
decr key_name | 对应的值-1 |
incrby key_name x | 对应的值+x |
decrby key_name x | 对应的值-x |
- Hash类型:经常用于保存对象
命令 | 说明 |
---|---|
hset key field value | 设置键值对 |
hsetnx key field value | field 不存在时,设置值。 |
hmset key field1 value1 field2 value2 | 批量设置键值对 |
hget key field | 获取指定键的值 |
hmget key field1 field2 | 批量获取若干个键的值 |
hgetall key | 获取全部的键值 |
hkeys key | 获取所有的键 |
hvals key | 获取所有的值 |
hlen key | 获得键值对的个数 |
hdel key field | 删除键名为field的键值对 |
hincrby key field x | 为指定键的值增加x |
hincrbyfloat key field x | 为指定键的值加浮点型的x |
hexists key field | 查询某个键是否存在 |
- List类型
命令 | 说明 |
---|---|
lpush key value1 value2 | 左侧头部插入 |
rpush key value1 value2 | 右侧尾部插入 |
llen key | 获取列表长度 |
lindex key index | 通过索引获得队列中的元素 |
lrange key start stop | 获取start到stop的元素 |
lpop key | 左边移除一个 |
rpop key | 右边移除一个 |
lset key index value | 修改特定位置的值 |
lrem key count value | 移除count个指定的值 |
ltrim key start stop | 仅保留index在start到stop之间的值 |
rpoplpush key1 key2 | 将key1移除一个并添加到key2中 |
linsert key before|after pivot value | 在列表的元素前或者后插入元素 |
blpop key1 [key2 ] timeout | 移出并获取列表的第一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。 |
brpop key1 [key2 ] timeout | 移出并获取列表的最后一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。 |
- Set类型
命令 | 说明 |
---|---|
sadd key member1 [member2] | 向集合添加一个或多个成员 |
scard key | 获取集合的成员数 |
smembers key | 返回集合中的所有成员 |
sismember key member | 判断 member 元素是否是集合 key 的成员 |
srandmember key [count] | 返回集合中一个或多个随机数 |
srem key member1 [member2] | 移除集合中一个或多个成员 |
spop key count | 随机删除count个元素 |
smove key1 key2 value | 将set1中的value移动到set2 |
sdiff key1 key2 | 返回set11和set2的差集 |
sdiffstore key3 key1 key2 | 返回set1和set2的差集并存入set3 |
sinter key1 key2 | 返回set1和set2的交集 |
sinterstore key3 key1 key2 | 返回set1和set2的交集并存入set3 |
sunion key1 key2 | 返回set1和set2并集 |
sunionstore key3 key1 key2 | 返回set1和set2的并集并存入set3 |
- ZSet类型(有序集合,实现工资表排序等)
命令 | 说明 |
---|---|
zadd key score value | 添加成员,用score排序 |
zcard key | 查看成员数 |
zcount key min max | 返回在指定区间的成员数 |
zrank key value | 返回指定值的索引 |
zrange key start stop | 返回索引start到stop之间的值 |
zrevrange key start stop | 返回索引start到stop之间的值,且是倒序 |
zrangebyscore key -inf inf | 返回根据score排序的从小到大序列 |
zrangebyscore key -inf inf withscores | 返回根据score排序的从小到大序列并附带分数 |
zrem key value | 删除某个成员 |
zremrangebyrank key start stop | 移除下标从start到stop之间的值 |
zremrangebyscore key score1 score2 | 移除分数介于二者之间的所有值 |
zincreby key x value | 给该value加x |
三种特殊的数据类型(了解)
- geospatial indexes:可以推算地理位置信息,两地之间的距离等,基于Zset编写,所以zset的一些指令可以使用。
指令 | 说明 |
---|---|
geoadd key 经度 纬度 城市 | 向key中添加一个城市的经纬度信息(经度从-180到180,纬度从-85.05到85.05) |
geopso key 城市1 城市2 | 获取该城市的经纬度 |
geodist key 城市1 城市2 km | 获取城市1和城市2的距离,单位是km |
georadius key 经度 纬度 距离 km 【withdist | withcoord】 | 获取key中距离该经纬度指定半径的所有城市 |
georadiusbymember key 城市 距离 km【withdist | withcoord】 | 获取key中距离该城市指定半径的所有城市 |
geohash key 城市 | 返回该城市对应位置的hash |
- hyperloglogs:网站统计访问次数等可以使用
指令 | 说明 |
---|---|
pfadd key value1 value2 | 定义key |
pfcount key | 查询不重复的元素个数 |
pfmerge key3 key1 key2 | 将key1和key2中的并集合并到key3 |
- bigmaps:位存储,每个键只有两个值,分别是0和1。
指令 | 说明 |
---|---|
setbit key offset 0|1 | 定义key |
getbit key offset | 获取信息 |
bitcount key | 获取为1的数目 |
Redis事务
redis的事务不保证原子性,redis事务的本质是一组命令的集合,按照顺序执行。redis的事务没有隔离级别的概念redis的事务可以分为三个步骤:
- 开启事务:multi
- 命令入队:各种redis的命令
- 执行事务或放弃事务:exec(提交)或discard(取消)
如果事务中有编译型异常,也就是语法有错误,那么该事务中所有的命令都不会执行。如果有运行时异常,那么只有产生异常的命令才会执行失败。
- redis实现乐观锁
通过watch命令来实现,语法为watch key
,表示对某个key进行监视,然后开启事务操作该事务,若此时其他线程对该key进行操作,修改了key中的值,那么会使得该事务执行失败,自动实施unwatch
命令
Jedis操作redis
引入Jedis依赖即可,通过Jedis jedis=new Jedis(IP地址,端口号)
获取jedis对象,该对象的方法和redis的命令完全相同。前提要关闭redis的只允许本机访问的限制,将redis.conf中的bind 127.0.0.1注释掉;关闭保护模式,将redis.conf中的protected-mode改为no
Springboot整合redis
SpringBoot2.0之后,底层采用lettuce替换了jedis,springboot操作redis主要通过reidsTemplate和stringRedisTemplate。
redisTemplate主要通过以下几个方法去操作不同的数据类型:
- opsForValue:操作字符串类型
- opsForHash:操作哈希类型
- opsForList:操作列表
- opsForSet:操作集合
- opsForZset:操作有序集合
- opsForGeo:操作地理信息
- opsForHyperLogLog:操作基数
也可以通过redisTemplate.getConnectionFactory().getConnection()
获取connection对象后直接操作数据库。但是redis默认的序列化方式为jdk序列器,需要自己手动编写配置类并修改配置信息。
redis配置文件
通用配置 | 说明 |
---|---|
bind 127.0.0.1 | 绑定的ip |
protected-mode yes | 保护模式 |
port 6379 | 绑定的端口号 |
daemonsize yes | 是否守护态运行 |
pidfile /var/run/redis_6379.pid | 以守护态运行需要指定的pid文件 |
requirepass 密码 | 设置redis密码 |
日志配置 | 说明 |
---|---|
loglevel notice | 日志级别 |
logfile / | 日志文件的地址 |
databases 16 | 默认的数据库数量 |
always-show-logs yes | 是否总是显示log |
持久化配置 | 说明 |
---|---|
save 次数 时间 | 表示在一定时间内超过指定的次数就持久化 |
stop-writes-on-bgsave-error yes | 持久化产生错误是否正常工作 |
rdbcompression yes | 是否压缩rdb文件 |
rdbchecksum yes | 保存rdb文件时,进行错误检查 |
appendonly no | 默认为rdb,不开启aof模式 |
appendfilename appendonly.aof | aof的持久化文件名称 |
appendfsync always|everysec | 每次修改同步或者每秒同步 |
redis持久化
-
RDB模式(Redis Database)
在指定的时间内将数据快照写入磁盘,redis会单独创建一个进程来持久化,先将数据快照保存为临时rdb文件,然后替换掉上次保存的快照,文件名为dump.rdb。期间主进程不进行任何操作,rdb比aof更加高效,适合大规模的数据恢复。缺点就是最后一次持久化的数据可能丢失。触发的规则:
- save规则满足时,自动触发rdb持久化
- 执行flushall命令也会触发
- 退出redis也会产生rdb文件
恢复rdb:放到redis的启动目录下(也就是/usr/local/bin目录下)
-
AOP模式
以日志的形式来记录每一次写操作,只许追加文件但不许修改文件,redis在启动之初会读取该文件重新构建数据,该文件的默认名称为appendonly.aof。该模式默认是不开启的,需要在配置文件中手动开启。如果aof文件发生了错误,redis是不能启动的,此时需要redis-check-aof工具修复aof文件,命令为
redis-check-aof --check aof文件名
redis发布订阅
Redis 发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息。
指令 | 说明 |
---|---|
subscribe 频道1 频道2… | 订阅某个频道 |
unsubscribe 频道1 频道2… | 退订某个频道 |
public 频道1 message | 向频道1发送消息 |
psubscribe pattern [pattern …] | 订阅一个或多个符合给定模式的频道 |
punsubscribe [pattern [pattern …]] | 退订所有给定模式的频道。 |
redis主从复制
redis的最大内存尽量不超过20G,否则最好切换成集群模式。redis可以实现一主多从,将主机的数据复制到从机上面,可以进行故障恢复。redis主机以写为主,从机以读为主,实现了负载均衡。用命令info replication
可以查看当前机器的主从信息。
- 主从配置
- 临时配置主从机:默认情况每一台redis都是主机,配置成从机需要用命令
slaveof 主机IP地址 主机端口号
去将另一台redis配置为主机,当前机器就变为了另一台的从机,该配置重启后又变为主机。 - 永久配置主从机:通过配置文件,找到
replicaof masterip masterport
将其配置为主机的IP和port即可。如果主机的redis有密码,就在masterauth 密码
处填写主机的密码即可
主机追加了key-value后,从机中也会有相应的key-value,但是从机默认是只读的,不能修改该key-value。
- 复制原理
从机连到主机上后,会执行一次sync命令,去同步主机的全部数据
- 全量复制
从机启动时会去主机复制全部的数据 - 增量复制
从机运行过程中,主机写入的任何数据都会写入从机中
当主节点断了之后,从节点可以手动用命令slaveof no one
让自己成为主节点
- 哨兵模式
如果主节点断了之后从节点需要手动配置的话,费时费力,也会造成一段时间内服务不可用
当主服务器宕机后,哨兵1检测到了该情况,此时还不会立即选举主节点,当其他哨兵也陆续发现该主机宕机并且数量达到一定的值后,其中一个哨兵就会发起一次投票,票数最多的从机会当选为新的主机。
配置哨兵模式:-
在myconf目录下创建配置文件
sentinel.conf
-
输入配置信息:
sentinel monitor 被监控主机点的名称 主机 端口号 1
sentinel中的配置信息还有很多:#配置端口 port 26379 #以守护进程模式启动 daemonize yes #日志文件名 logfile "sentinel_26379.log" #存放备份文件以及日志等文件的目录 dir "/opt/redis/data" #监控的名称 IP 端口号 sentinel通过投票后认为mater宕机的数量,此处为至少2个 sentinel monitor mymaster 192.168.192.129 6379 2 #30秒ping不通主节点的信息,主观认为master宕机 sentinel down-after-milliseconds mymaster 30000 #故障转移后重新主从复制,1表示串行,>1并行 sentinel parallel-syncs mymaster 1 #故障转移开始,三分钟内没有完成,则认为转移失败 sentinel failover-timeout mymaster 180000
-
启动sentinel的进程:
redis-sentinel myconf/sentinel.conf
-
redis的缓存穿透和雪崩
缓存穿透:就是当用户要查询某条数据时,在redis缓存数据库中没查找到,也就是缓存没有命中,那么就会去mysql数据库中查找,如果访问量特别大,那么就会给持久层的数据库(mysql)造成很大的压力。这就叫缓存穿透。这种情况一般都是恶意用户的请求,比如查询id为-1的用户,数据库中一直查不出来,因此就一直不会放入redis,所以会一直穿透redis进入数据库的查询。解决方案:
- 布隆过滤器。
- 参数的合法性检验,如果不合法直接返回错误
- 缓存空对象,然后设置过期时间,可能产生的问题就是数据库已经有了,但缓存中还没有,因为设置了延时。
缓存击穿:缓存击穿是指对某个热点key查询时,该key过了设置的过期时间,那么请求会大量的走到mysql中,造成持久层压力大。解决方案:
- 是设置热点key不过期(不推荐)
- 加互斥锁,一个时刻只能有一个线程去访问mysql,其他线程只许等待即可
- 分布式锁
缓存雪崩:一段时间内,key全部过期或者redis发生了宕机。解决方案:
- 实现redis高可用,将热点数据存放在不同节点上
- 限流降级,利用hystrix或者sentinel等技术配置服务兜底方案。
- 数据预热:手动的添加key,即把可能访问的数据先访问一边保存在缓存中,可以设置不同的过期时间,让缓存过期的时间尽量均衡
- 随机设置缓存的失效时间,让缓存不要再同一时间失效
- 设置定时任务,当缓存快要过期时再次将缓存放入redis
写在后面
该笔记是在B站狂神说java
的课程学习中记录的,感谢狂神带我入门。