Redis

203 阅读17分钟

NoSQL

NoSQL,泛指非关系型的数据库;
传统的关系型数据库面对大数据量的高并发出现瓶颈,NoSQL 具有高扩展,高可用等一系列优势;
NoSQL 数据库有: 文档数据库 MongDB 等
图数据库
键值数据库 Redis/Memecache 等
列数据库

Redis

Redis 是一个开源的基于内存的,可持久化的键值数据库;
Redis 是单线程的
Redis 默认端口 6379
Redis 的特性:
多样的数据类型
支持集群/事务/持久化/主从复制
高性能,读写非常快,写 8 w/s, 读 11 w/次
Redis 能做什么? 内存数据库,支持持久化
地图信息分析
统计浏览量,粉丝数
缓存数据
利用 redis 的 sset 做排行榜
计数器,点赞数、用户访问量,利用 string 的 increby ;
限速器 排行榜,利用 sorted set 的 zrangebyscore 共同好友、共同爱好 ,利用set 的 sunionstore 取并集 附近的人、两地之间的距离 ,利用geospatical 的 geodist 获取距离,根据georadius 获取附近的人
简单消息队列,利用 list 的 lpush,rpop,blpop,brpop阻塞弹出;
发布订阅系统、聊天系统,利用 subscribe 、publish 订阅、发送消息

Redis Linux 安装和使用

1.下载 redis-5.0.9.tar.gz
2.下载程序一般放在 opt 目录下 mv /home/zs/redis-5.0.9.tar.gz /opt
3.解压:tar -zxvf redis-5.0.9.tar.gz
4.安装 gcc

安装 gcc 及 启动 redis

1. yum install gcc-c++
gcc -v // 版本查看
2. make // 配置
3.make install
4.redis 默认的安装路径在 /usr/local/bin 
  local 一般是本地程序的位置
5.复制redis 配置文件到 bin 下,之后就使用该文件启动
mkdir redis-config
cp /opt/redis-5.0.9/redis.conf redis-config
6.启动
redis 默认不是后台启动,需要修改配置文件守护进程为yes
vim redis-config
cd /usr/local/bin 
redis-server redis-config/redis.conf
7: 连接 redis
redis-cli -p 6379
8.查看 redis 进程是否开启
ps -ef | grep redis
9.关闭 redis 服务
shutdown
exit 退出

Redis 基础知识

redis 默认有 16 个数据库,默认使用第 0 号库;
可以使用 select 命令切换数据库;
image.png

Redis 命令

1:启动 redis-server redis-config/redis.conf
2:连接 redis, redis-cli -p 6379  
3:输入密码: auth 密码
4:切换数据库 select 1
5.查看当前数据库大小 dbsize 
6.keys * // 查看当前数据库所有 key
7.flushdb // 清空当前数据库
8.flushall // 清空所有数据库
9.  
127.0.0.1:6379> type key // 查看 key 类型
string
10.    
127.0.0.1:6379> PEXPIREAT key1 10 // 设置key 10毫秒过期
(integer) 1 // 如果 key 存在,返回1
127.0.0.1:6379> PEXPIREAT key2 10
(integer) 0 // 不存在的key 返回0
11.
rename key1 key2 // 修改 key1 为 key2
OK
127.0.0.1:6379> keys *
1) "key2"
12.  
PERSIST key1 // 设置 key1 永不过期
(integer) 1 // 设置成功返回1 ,失败返回0
13.
127.0.0.1:6379> MOVE key1 1 // 将 key1 从0号库移动到1号库
(integer) 1 
14.
RANDOMKEY // 从当前库随机返回一个key
15.
DUMP key1 // 序列化 key1 ,返回 序列化的值
"\x00\xc0\x01\t\x00\xf6\x8a\xb6z\x85\x87rM"
16.
127.0.0.1:6379> ttl key1 // 获取 key1 的剩余时间
(integer) -1 // 没有设置时间,返回-1,设置了时间返回剩余毫秒,已过期,返回-2
17.
127.0.0.1:6379> EXPIRE key1 1 // 设置过期时间, 以秒为的单位
(integer) 1
18.
127.0.0.1:6379> EXPIREAT key2 19232000 // 设置过期时间,单位是时间戳格式
(integer) 1
19.
127.0.0.1:6379> del key1 // 删除 key1 ,不存在的key 会返回 0
(integer) 1
20.
redis 127.0.0.1:6379> PTTL KEY_NAME // 返回剩余时间,毫秒为单位
21.
127.0.0.1:6379> renamenx key1 key2 // 修改 key1 为 不存在的 key2
(integer) 1
127.0.0.1:6379> keys *
1) "key2"
22.
127.0.0.1:6379> EXISTS key2 // 是否存在 key2 ,返回数字 0 /1
(integer) 1
23.
127.0.0.1:6379> keys key* // 模糊查询
1) "key2"
2) "key1"
模拟 100 个并发, 10 w 次请求
redis-benchmark -h localhost -p 6379 -c 100 -n 100000
------------------------------------
String 命令
24.
setnx key1 1  // 如果key不存在时设置
25.
getrange key 0 1 // 获取下标[0 -1]
26.
Mset key1 1 key2 2 key3 3 // 设置多个键值
27.
Setex key1 过期毫秒数 value // 设置多少毫秒后失效,如果key 存在会替换旧值
28.
getbit key1 1 // 获取偏移后的字节数,1 是偏移量
29.
decr key1 // 将值减1 
30.
decrby key1 2 // 减去2
31.
127.0.0.1:6379> STRLEN key1 // 长度
(integer) 1
32.
redis 127.0.0.1:6379> MSETNX key1 value1 key2 value2 // 不存在时设置多个
该操作时原子性的
33.
incrby key 10// 加10
incr // 加1 ,可用作计数器
------------------------------
list 命令:
lpush list 1 2 3 4 // 从左侧入队,顺序是:1,2,3,4
lrang list 0 -1 // 从左侧出队, 顺序是 4,3,2,1 
rpop list // 从右侧一个一个出队,先进先出 。出队后该key 就变成空
rpoplpush list list2 // 从list 中右侧弹出第一个元素,加入list2
blpop list 10 // 10s, blpop 中的 b 代表blockig,阻塞,从左侧弹出
brpop list 10 // 从右侧
lrem // 从左侧移除
llen // 长度
ltrim // 修剪
lpushx // 批量从左侧插入
linsert // 插入
------------------------------
hash 命令
127.0.0.1:6379> hset m1 k1 m1 k2 m2 // 批量插入 
(integer) 2
127.0.0.1:6379> hget m1 k1 // 单个获取
"m1"
127.0.0.1:6379> hmget m1 k1 k2 // 批量获取
1) "m1"
2) "m2"
redis 127.0.0.1:6379> HGETALL myhash // 获取所有
------------------------------
set 命令
sadd myset 1 // 添加k v
redis 127.0.0.1:6379> SMEMBERS myset// 获取元素
redis 127.0.0.1:6379> SCARD KEY_NAME // 获取元素个数
spop set // 移除一个元素,并返回剩余元素列表
SUNIONSTORE myset myset1 myset2 // 将myset1 myset2的并集添加到myset
sinterstore // 取交集
sscan // 迭代元素
sdiff // 获取差集
sdiffstore // 将差集存储
sismember // 判断元素是否是集合元素
smove // 将指定元素移除到另一个集合
srem // 移除元素
sinter // 返回交集
------------------------------
sorted set
zadd // 添加
zount // 数量
zrange // 返回有序集合,索引
zrangescore zset -1 7 withscores // 排序
ZSCORE // 获取分数值
------------------------------
geospatical 命令
geoadd key 维度 经度 名称 ...
georadius // 获取范围内的元素有哪些
geopos // 获取经纬度
geodist // 获取两地的距离
------------------------------
hyperloglog 命令
pfadd // 添加
pgmerge //合并
------------------------------
发布订阅命令
subscribe 频道名称 // 订阅一个频道
psubscribe 频道A 频道B // 订阅多个频道
punscribe 频道A 频道B // 退订多个频道

pubsub 频道A // 查看频道A 订阅情况

pubish 频道A 111 // 向频道A 发送消息

redis 是单线程的,为什么还这么快?

多线程并不一定比单线程快,多线程需要 CPU 线程上下文切换,这是一个耗时操作;
对于内存数据库来说,没有上下文切换。

Redis 数据类型

字符串 String

String的应用场景
计数器获取分数数量,存储对象

Sorted set

有序集合

hash

list

set

geospatial 地理位置

geoadd

# 有效的经度从 -180 - 180 ,有效的维度从 -85.05112878 - 85.05112878,当坐标位置超出上述指定范围时,该命令会返回一个错误
# 规则:添加地理位置,地球两级是没办法添加的,我们一般会通过 Java 程序一次性导入
# 参数:key 值(维度,经度,名称)
localhost:6379> geoadd china:city 1.1 2.2 beijing // 添加经纬度
(integer) 1
localhost:6379> geoadd china:city 3.3 4.4 shanghai
(integer) 1
localhost:6379> geoadd china:city 5.5 6.6 shenzhen 7.7. 8.8 sichuan
(error) ERR value is not a valid float
localhost:6379> geoadd china:city 5.5 6.6 shenzhen 7.7 8.8 sichuan
(integer) 2

geopos

# 获取指定城市的经纬度
# 参数:key name 
localhost:6379> geopos china:city beijing shanghai
1) 1) "1.09999805688858032"
   2) "2.1999998240030223"
2) 1) "3.29999953508377075"
   2) "4.40000091536662552"
localhost:6379> geopos china:city sichuan
1) 1) "7.70000249147415161"
   2) "8.80000056337268433"

geodist

# 两地之间的距离
# 参数:key name1 name1
localhost:6379> geodist china:city beijing shanghai km
"345.7577"

georadius

# 以给定的经度纬度为中心,查找某一半径的元素
# 参数:key 
localhost:6379> GEORADIUS china:city 1.1 2.2 1000 km
1) "beijing"
2) "shanghai"
3) "shenzhen"
-------------------------------------------------
# withcoord 显示他人的定位信息
# withdist 显示距离
# count 展示符合条件的个数
# asc desc 排序
localhost:6379> GEORADIUS china:city 1.1 2.2 1000 km withcoord withdist count 2 asc
1) 1) "beijing"
   2) "0.0002"
   3) 1) "1.09999805688858032"
      2) "2.1999998240030223"
2) 1) "shanghai"
   2) "345.7575"
   3) 1) "3.29999953508377075"
      2) "4.40000091536662552"

GEORADIUSBYMEMBER

# 查找某一个位置某一个范围内的其他成员
localhost:6379> GEORADIUSBYMEMBER china:city beijing 1000 km
1) "beijing"
2) "shanghai"
3) "shenzhen"

hyperloglog

**基数:不重复的元素,可以接受误差 可以使用 hyperloglog 来统计访问量 hyperloglog 是用来做基数统计的算法,是 Redis 2.8.9 推出的新功能 如果容错,就使用 set 来解决 **

pfadd 添加元素, pfcount 获取元素个数, pfmerge 合并元素并去掉重复的元素

localhost:6379> PFADD myset 1 2 3 4 5
(integer) 1
localhost:6379> PFADD myset2 1 2 3 4 5 6
(integer) 1
localhost:6379> PFCOUNT myset 
(integer) 5
localhost:6379> PFMERGE myset3 myset myset2
OK
localhost:6379> PFCOUNT myset3
(integer) 6

bitmaps

位图。统计用户活跃信息!登录,未登录;是否打卡。只有两个状态的都可以使用 bitmaps, 都是操作二进制位存储,只有 0 和 1 两个状态!

localhost:6379> setbit sign 0 1 // 存储
(integer) 0
localhost:6379> getbit sign 0 // 获取
(integer) 1

localhost:6379> bitcount sign  // 统计全部为 1 的
(integer) 2
localhost:6379> bitcount sign 0 1 // 统计区间内的
(integer) 2

事务

** redis 单条是原子性的,但是事务不保证原子性, reds 的事务是一组命令的执行,具有一次性,排他性,顺序性! **

#开启事务 multi
#执行命令,入队
#exec 执行事务
#放弃事务 discard
#有错误命令时,执行事务也是报错的,所有的命令都不会执行;如果时运行时异常,其他命令可以正常执行!
localhost:6379> MULTI 
OK
localhost:6379> set key1 1
QUEUED
localhost:6379> set key2 1
QUEUED
localhost:6379> get key1
QUEUED
localhost:6379> EXEC
1) OK
2) OK
3) "1"

localhost:6379> MULTI
OK
localhost:6379> set key 1
QUEUED
localhost:6379> DISCARD
OK
localhost:6379> get key
(nil)

乐观锁

命令 watch key
解锁 unwatch 

整合Springboot

** springboot 2.x 之后,原来的 jedis 替换为 lettuce; Jedis 采用的是直连,多线程操作不安全,如果想要避免,使用 Jedis pool !更像 BIO 模式 Leture 采用 netty, 是可以在多个线程中共享,不存在线程不安全的情况!更像 NIO 模式 **

注入 RedisTemplate 
通过 redisTemplate 来操作
opsForValue 操作String
opsForList 操作List
opsForSet 操作Set
opsForHash 操作Hash
opsForGeo 操作Geospatial
opsForZset 操作Zset
opsForHyperloglog 操作Hyperloglog

redis.config 详解

redis 对大小写不敏感

网络

protected-mode yes //保护模式
port 6379 // 端口设置

通用

daemonize no // 守护进程,默认是 yes 
pidfile /var/run/redis_6379.pid // 进程文件
logfile "" // 日志文件名
databases 16 // 默认的数据库数量
always-show-logo yes // 是否显示 logo

快照 SNAPSHOTTING

#持久化配置,在规定的时间内,执行了多少次操作,会持久化到 .rdb , .aof 文件中
#也可以设置自己的持久化配置
save 900 1 // 如果 900 秒内,至少有一个 key 进行了一次操作,就进行持久化操作 
save 300 10 // 如果 300 秒内,至少有10个 key 进行了一次操作,就进行持久化操作 
save 60 10000 // 如果 60 秒内,至少有10000个 key 进行了一次操作,就进行持久化操作 
******************************************************************************
#持久化出错,是否继续工作
stop-writes-on-bgsave-error yes
******************************************************************************
#是否压缩 rdb 文件,需要消耗一些 cpu 的资源
rdbcompression yes
******************************************************************************
#保存rdb 文件时,是否检查错误
rdbchecksum yes
******************************************************************************
# rdb 文件保存的目录,默认在当前目录下
dir ./

主从复制 REPLICATION

安全 SECURITY

#获取 redis 密码命令
config get requirepass 
#设置密码,这种方式在 redis 服务重启后失效
127.0.0.1:6379> config get requirepass
1) "requirepass"
2) ""
127.0.0.1:6379> config set requirepass 123456
OK
127.0.0.1:6379> config get requirepass
(error) NOAUTH Authentication required.
127.0.0.1:6379> auth 123456
OK
127.0.0.1:6379> config get requirepass
1) "requirepass"
2) "123456"

客户端限制 CLIENTS

maxclients 10000 # 客户端最大连接数

内存管理 MEMORY MANAGEMENT

#内存达到上限的处理策略
maxmemory-policy noeviction
1volatile-lru:只对设置了过期时间的key进行LRU(默认值) 
2、allkeys-lru : 删除lru算法的key   
3volatile-random:随机删除即将过期key   
4、allkeys-random:随机删除   
5volatile-ttl : 删除即将过期的   
6、noeviction : 永不过期,返回错误

AOF 的设置 APPEND ONLY MODE

appendonly no // 默认不开启,默认使用 rdb 模式,因为在所有情况下,rdb 完全够用
appendfilename "appendonly.aof" // 持久化文件的名字
*******************************************************************
# appendfsync always // 每次修改都会同步,速度比较慢
appendfsync everysec // 默认每秒执行一次
# appendfsync no // 不执行同步,操作系统自己同步数据,速度最快

Redis 持久化

redis 是一个内存数据库,如果不做持久化保存到硬盘中,数据就会断电即失

rdb

redis 会单独创建一个子进程来持久化,会先将数据写入一个临时文件,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件,整个过程,主进程是不进行任何 I/O 操作的,这就确保了极高的性能,如果需要进行大规模数据的恢复,且对数据恢复的完整性不是非常敏感,rdb 方式要比 aof 方式更加高效,rdb 的缺点是最后一次持久化时,如果宕机,那么数据可能会丢失,redis 默认使用 rdb 方式

#在指定的时间间隔内,将数据集体快照写入磁盘,恢复时是将快照文件直接读到内存;
#rdb 保存的文件是 dump.rdb

rdb 触发机制

  • shutdown
  • flush操作
  • save 的规则满足的情况下

aof

**将所有的命令都记录下来追加到 appendonly.aof 文件中,等恢复的时候再重新执行一下命令; 如果开启 rdb 和 aof 两种方式持久化,redis 重启时,会优先载入 aof 文件来恢复原始数据,因为通常情况下,aof 保存的数据要比 rdb 文件完整; 如果 appendonly.aof 文件被损坏,启动 redis 会失败,可以使用 redis-check-aof --fix appendonly.aof 操作来修复 **

性能建议

  • 建议只在从机上持久化 rdb 文件, 而且只要 15 分钟备份一次就好,只保留 save 900 1 这条配置就好
  • 如果只开启 aof, 在最恶劣情况下,最多不会丢失 2 秒数据, aof 重写的基础大小默认是 64 M, 可以设置为 5 G以上
  • 如果不开启 aof ,仅靠主从复制实现高性能也可以, 能省掉一大笔 I/O 操作,代价是如果主从同时宕机,会丢失十几分钟的数据,启动时比较两个服务中的 rdb 文件,哪个是最新的,微博就是这种架构!

Redis 的主从复制

主从复制,是将一台 redis 服务器的数据,复制到其他服务器,前者称为主节点,后者称为从节点; 数据的复制是单向的,只能从主节点到从节点,主节点以写为主,从节点以读为主; 主节点能写能读数据,从节点只能读取不能写数据;在没有配置哨兵的情况下,主节点宕机,从节点还是从节点的模式,并且还可以读取到数据; 使用命令行设置主从,当其中一个从机宕机后再重启,会变成主机,同时所有数据都会读取到;如果一个节点即是其他从节点的主节点,自身又是一个从节点,那么该节点其实是一个从节点,不能写入,主能读取

主从复制的作用

  • 数据冗余:主从复制实现了数据的热备份,是持久化以外的另一种数据冗余方式;
  • 故障恢复:当主节点故障时,可以由从节点提供服务,实现故障回复;
  • 负载均衡:在主从复制的基础上,配合读写分离,可以由主节点提供写服务,从节点提供读服务,即:写redis 时,应用连接主节点,读 redis 时,应用连接从节点,分担服务器负载,尤其在写少读多的情况下,可以大大提高 redis 的并发量。
  • 高可用:主从复制是哨兵模式实现的基础。

环境配置

#查看主从复制的信息
127.0.0.1:6379> info replication
# Replication
role:master // 角色 master 
connected_slaves:0 // 从机个数 0 
master_replid:cbae631fce275d263ad040382bd1118fd4c6e06d
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:0
second_repl_offset:-1
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0

修改配置文件

  • 在同一目录下复制多个redis.config 文件
  • 修改配置文件内容: 守护线程开启;i 端口号; PID;i logfile "xxx.log"; dbfilename dumpXXX.rdb

配置主从复制

默认情况下都是主节点
使用命令的方式是在重启后就失效了,需要在配置文件中修改
SLAVEOF 127.0.0.1 6379 

配置文件配置主从复制

手动设置当前节点为主节点 slaveof no one

哨兵模式

当主节点宕机,从节点自动选举主节点的模式;
redis 从 2.8 开始支持哨兵模式;
需要配置一个哨兵的进程,它会独立运行,其原理是哨兵通过发送命令,等待 redis 服务器响应,从而监控多个 redis 实例;
这里的哨兵有两个作用:发送命令监控各个节点的状态,当监控到主节点宕机,会自动将从节点切换为主节点,并通过发布订阅模式通知其他节点,修改配置文件,切换主节点;
为了保证单个哨兵的可用,可以使用多哨兵模式,各个哨兵互相监控;

配置哨兵模式

命令:
vim sentinel.conf
语法 sentinel monitor 被监控节点的名称 ip 端口 1 // 后面的 1 代表 主机宕机,投票
	sentinel monitor myredis 127.0.0.1 6379 1 
启动哨兵模式
redis-sentinel redis-config/sentinel.conf 
哨兵默认端口是26379

配置哨兵集群

  • 如果有多个集群需要配置多个端口
  • 配置工作目录
  • 配置主机的密码
  • 指定多少毫秒以后,主节点没有响应,此时哨兵主观认为主节点下线, 默认为 30 秒
  • 故障转移时间,默认 3 分钟
  • 通知脚本

缓存穿透和雪崩

** 缓存穿透(秒杀现象):用户想要查询一个数据,发现 redis 和数据库都没有, 于是本次查询失败,当大量用户同时这样查询,就会给数据库造成很大压力,这时候就相当于缓存穿透,继而带来雪崩现象 解决方案: 布隆过滤器;缓存空对象**

布隆过滤器

布隆过滤器是一种数据结构,对所有可能查询的数据以 hash 形式存储,在控制层先进行校验,不符合则丢弃,从而避免对底层数据库的查询压力。

缓存空对象

名词 - 缓存击穿(热搜现象,缓存过期)

是指一个 key 非常热点,被大并发集中访问,当这个 key 在失效的瞬间,持续的大并发会穿破缓存,直接请求数据库
解决方案:
设置热点数据不过期,但是会浪费空间;
使用分布式锁:当缓存被击穿,会有多个线程同时访问后台,此时给每一个 key 同一时间只有一个线程去后台查询,其余线程等待;

缓存雪崩

是指在某一时间段,缓存集体失效,redis 宕机。
产生雪崩的原因:比如在写操作,比如部分 key 的缓存集体在同一段时间过期,这时候请求会查询数据库,比如双十一时,一段时间的写操作很频繁。
解决方案:增加 redis 数量,异地多活;限流降级;数据预热

原理

1. redis 的分布式锁如何实现?