Redis入门
Redis可以用于数据库、缓存和消息队列。
连接Redis
frank@frank-virtual-machine:/usr/local/bin$ redis-server kconfig/redis.conf
frank@frank-virtual-machine:/usr/local/bin$ redis-cli -p 6379
127.0.0.1:6379> ping
PONG
127.0.0.1:6379>
Redis默认有16个数据库
# Set the number of databases. The default database is DB 0, you can select
# a different one on a per-connection basis using SELECT <dbid> where
# dbid is a number between 0 and 'databases'-1
databases 16
基础命令
127.0.0.1:6379> set name zhaobin
OK
127.0.0.1:6379> keys *
1) "name"
127.0.0.1:6379> get name
"zhaobin"
127.0.0.1:6379> exists name
(integer) 1
127.0.0.1:6379> exists name11
(integer) 0
127.0.0.1:6379[1]> move name 0
(integer) 1
127.0.0.1:6379> keys *
1) "age"
2) "name"
127.0.0.1:6379> EXPIRE name 10
(integer) 1
127.0.0.1:6379> ttl name
(integer) -2
127.0.0.1:6379> type age
string
选择数据库查看大小
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 zhaobin
OK
127.0.0.1:6379[3]> dbsize
(integer) 1
127.0.0.1:6379[3]> get name
"zhaobin"
清空数据库
flushdb #清空当前数据库
flushall #清空所有数据库
Redis是单线程的
Redis是基于内存操作的,CPU不是Redis性能限制的瓶颈,限制的瓶颈是机器的内存和网络带宽。
Redis是将数据全部放在内存中的。
五大数据类型
Redis-Key
Srting
List
命令都是以l
开头的,list
可以用作栈、队列和阻塞队列
127.0.0.1:6379> LPUSH list one #将值插入到列表的头部
(integer) 1
127.0.0.1:6379> LPUSH list two
(integer) 2
127.0.0.1:6379> LPUSH list three
(integer) 3
127.0.0.1:6379> LRANGE list 0 2 #倒着取数据
1) "three"
2) "two"
3) "one"
127.0.0.1:6379> Rpush list right #将值插入到list的尾部
(integer) 4
127.0.0.1:6379> LRANGE list 0 -1
1) "three"
2) "two"
3) "one"
4) "right"
####################################################
LPOP #移除list的头部(左)
RPOP #移除list的尾部(右)
####################################################
Lindex #下标
####################################################
Llen #取长度
####################################################
Lrem #移除指定的值
消息队列(Lpush Rpop
)
栈(Lpush Lpop
)
Set
命令都是以s
开头的
无序不重复
sadd
smembers
sismember
srem
Hash
Map集合,key-<key-value>
hset key key value
hget
Zset
有序集合
在set的基础上,增加了一个值。
127.0.0.1:6379> zadd myset 1 one
(integer) 1
127.0.0.1:6379> zadd myset 2 two
(integer) 1
127.0.0.1:6379> zadd myset 3 three
(integer) 1
127.0.0.1:6379> ZRANGE myset 0 -1
1) "one"
2) "two"
3) "three"
三种特殊数据类型
BitMap
Hyperloglog
Geospatial
事务
Redis事务的本质:一组命令的集合!一个事务所有的命令都会被序列化,在事务的执行过程中,会按照顺序执行。
一次性、顺序性、排他性
Redis事务没有隔离的级别。
Redis单条命令保证原子性,但是Redis事务不保证原子性。
Redis事务
- 开启事务(multi)
- 命令入队()
- 执行事务(exec)
正常执行事务
127.0.0.1:6379> MULTI #开启事务
OK
#命令入队
127.0.0.1:6379(TX)> set k1 v1
QUEUED
127.0.0.1:6379(TX)> set k2 v2
QUEUED
127.0.0.1:6379(TX)> get k2
QUEUED
127.0.0.1:6379(TX)> set k3 v3
QUEUED
127.0.0.1:6379(TX)> exec #执行事务
1) OK
2) OK
3) "v2"
4) OK
放弃事务
127.0.0.1:6379> MULTI #开启事务
OK
#命令入队
127.0.0.1:6379(TX)> set k1 v1
QUEUED
127.0.0.1:6379(TX)> set k2 v2
QUEUED
127.0.0.1:6379(TX)> set k4 v4
QUEUED
127.0.0.1:6379(TX)> discard #放弃事务
OK
127.0.0.1:6379> get k4 #事务队列中的命令都不会被执行
(nil)
编译型异常
事务中所有的命令都不会被执行。
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379(TX)> set k1 v1
QUEUED
127.0.0.1:6379(TX)> set k2 v2
QUEUED
127.0.0.1:6379(TX)> set k3 v3
QUEUED
127.0.0.1:6379(TX)> GETSET k3 #错误的命令
(error) ERR wrong number of arguments for 'getset' command
127.0.0.1:6379(TX)> set k4 v4
QUEUED
127.0.0.1:6379(TX)> exec #执行事务报错 所有的命令都不会被执行
(error) EXECABORT Transaction discarded because of previous errors.
运行时异常
执行命令的时候其他命令是可以正常执行的。
127.0.0.1:6379> set k1 "v1"
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379(TX)> incr k1
QUEUED
127.0.0.1:6379(TX)> set k2 v2
QUEUED
127.0.0.1:6379(TX)> set k3 v3
QUEUED
127.0.0.1:6379(TX)> get k3
QUEUED
127.0.0.1:6379(TX)> exec
1) (error) ERR value is not an integer or out of range
2) OK
3) OK
4) "v3"
<font = red>
监控 Watch
悲观锁
无论做什么都会加锁。
乐观锁
- 获取version
- 更新的时候比较version
Redis监视测试
正常执行成功
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 #监视money对象
OK
127.0.0.1:6379> MULTI #事务正常结束,数据期间没有发生变动,正常执行成功
OK
127.0.0.1:6379(TX)> DECRBY money 20
QUEUED
127.0.0.1:6379(TX)> INCRBY out 20
QUEUED
127.0.0.1:6379(TX)> exec
1) (integer) 80
2) (integer) 20
测试多线程修改值,使用watch可以当作redis的乐观操作。
127.0.0.1:6379> watch money #监视money
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379(TX)> DECRBY money 10
QUEUED
127.0.0.1:6379(TX)> INCRBY out 10
QUEUED
127.0.0.1:6379(TX)> exec #执行前另外一个线程修改了money,修改失败
(nil)
127.0.0.1:6379> unwatch #1.发现事务执行失败,先解锁
OK
127.0.0.1:6379> WATCH money #2.获取最新的值,再次监视。select version
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379(TX)> DECRBY money 1
QUEUED
127.0.0.1:6379(TX)> INCRBY out 1
QUEUED
127.0.0.1:6379(TX)> exec #3.比较监视的对象是否发生变化,没有则执行成功。没有自旋
1) (integer) 99
2) (integer) 21
127.0.0.1:6379>
Jedis
Redis官方推荐的java连接开发工具。
测试
1.导入对应的依赖
<dependencies>
<!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.2.0</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.62</version>
</dependency>
</dependencies>
2.编码测试
- 连接数据库
- 操作命令
- 断开连接
import redis.clients.jedis.Jedis;
public class TestPing {
public static void main(String[] args) {
Jedis jedis = new Jedis("127.0.0.1",6379);
System.out.println(jedis.ping());
}
}
常用API
String
List
Set
Hash
Zset
事务
import com.alibaba.fastjson.JSONObject;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;
public class TestPing {
public static void main(String[] args) {
Jedis jedis = new Jedis("127.0.0.1",6379);
JSONObject jsonObject = new JSONObject();
jsonObject.put("hello", "world");
jsonObject.put("name", "zhaobin");
String result = jsonObject.toJSONString();
Transaction multi = jedis.multi();
try {
multi.set("user1", result);
multi.set("user2", result);
multi.exec(); //执行事务
} catch (Exception e) {
multi.discard(); //放弃事务
e.printStackTrace();
} finally {
System.out.println(jedis.get("user1"));
System.out.println(jedis.get("user2"));
jedis.close();
}
}
}
输出
{"name":"zhaobin","hello":"world"}
{"name":"zhaobin","hello":"world"}
Process finished with exit code 0
SpringBoot整合
Jedis:采用的直连,多个线程操作的话,是不安全的。
避免不安全,采用jedis pool连接池。 BIO
lettuce:采用netty,实例可以在多个线程*享,不存在线程不安全的情况。可以减少线程数量,更像NIO模式。
Redis.conf
网络
bind 127.0.0.1 #绑定的ip
protected-mode yes #保护模式
port 6379 #端口设置
通用
daemonize yes #以守护进程的方式运行,默认是no
pidfile /var/run/redis_6379.pid #如果一后台的方式运行,需要指定一个pid文件。
#日志
# Specify the server verbosity level.
# This can be one of:
# debug (a lot of information, useful for development/testing)
# verbose (many rarely useful info, but not a mess like the debug level)
# notice (moderately verbose, what you want in production probably) 生产环境
# warning (only very important / critical messages are logged)
loglevel notice
# Specify the log file name. Also the empty string can be used to force
# Redis to log on the standard output. Note that if you use standard
# output for logging but daemonize, logs will be sent to /dev/null
logfile "" #日志生成文件名
databases 16 #数据库的数量
快照
持久化:在规定的时间内,执行了多少次操作,则会持久化到文件.rdb
或者.aof
# Save the DB to disk.
#
# save <seconds> <changes>
#
# Redis will save the DB if both the given number of seconds and the given
# number of write operations against the DB occurred.
#
# Snapshotting can be completely disabled with a single empty string argument
# as in following example:
#
# save ""
#
# Unless specified otherwise, by default Redis will save the DB:
# * After 3600 seconds (an hour) if at least 1 key changed
# * After 300 seconds (5 minutes) if at least 100 keys changed
# * After 60 seconds if at least 10000 keys changed
#
# You can set these explicitly by uncommenting the three following lines.
#
save 3600 1 360s内至少有一个key进行了修改则会进行持久化操作
save 300 100
save 60 10000
stop-writes-on-bgsave-error yes #持久化出错是否需要继续工作
rdbcompression yes #是否压缩rdb文件,需要消耗一些CPU资源
rdbchecksum yes #保存rdb文件的时候,进行错误的检查校验
# The working directory.
#
# The DB will be written inside this directory, with the filename specified
# above using the 'dbfilename' configuration directive.
#
# The Append Only File will also be created inside this directory.
#
# Note that you must specify a directory here, not a file name.
dir ./ #rdb保存的位置
maxmemory-policy
maxmemory-policy noeviction #默认为noeviction
volatile-lru -> Evict using approximated LRU, only keys with an expire set. #只对设置了过期时间的key进行LRU
allkeys-lru -> Evict any key using approximated LRU. #删除LRU算法的key
volatile-lfu -> Evict using approximated LFU, only keys with an expire set.
allkeys-lfu -> Evict any key using approximated LFU.
volatile-random -> Remove a random key having an expire set. #随机删除即将过期的key
allkeys-random -> Remove a random key, any key. #随机删除
volatile-ttl -> Remove the key with the nearest expire time (minor TTL)#删除即将过期的
noeviction -> Dont evict anything, just return an error on write operations. #永不过期,返回错误
Append ONLY模式(AOF)
appendonly no #默认不开启AOF,默认使用RDB持久化
Redis持久化
Redis是内存数据库,如果不将内存中的数据库状态保存到磁盘,那么一旦服务器进程退出,服务器中的数据库状态也会消失。所以Redis提供了持久化功能。
RDB(Redis Database)
触发机制
- save规则满足的情况下
- 执行flushall
- 退出redis
恢复RDB文件
将rdb文件放在redis的启动目录
config get dir
优点:
- 适合大规模的数据恢复
- 对数据的完整性要求不高
缺点:
- 需要一定的时间间隔进行操作,如果redis意外宕机了,最后一次的修改数据就没了。
- fork进程的时候,会占用一定的内存空间。
AOF(Append Only File)
将所有命令都记录下来,恢复将所有命令都执行一遍。
redis-check-aof --fix #修复aof文件 数据可能会丢失
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
如果aof文件大于64M,则会fork一个新的进程将文件进行重写。(合并命令)
优点:
1.每一次修改都同步,文件完整性
2.默认是每秒同步一次,可能会丢失一秒的数据。
缺点:
1.相对于数据文件来说,aof远远大于rdb,修复的速度也比rdb慢。
2.aof运行效率比rdb慢。
Redis发布订阅
Redis发布订阅(pub/sub)是一种消息通信模式,发送者(pub)发送消息,订阅者(sub)接受消息。
Redis客户端可以订阅任意数量的频道。
订阅端:
frank@frank-virtual-machine:/usr/local/bin$ redis-cli -p 6379
127.0.0.1:6379> SUBSCRIBE frank
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "frank"
3) (integer) 1
1) "message"
2) "frank"
3) "hello world"
1) "message"
2) "frank"
3) "welcome"
发布端
frank@frank-virtual-machine:/usr/local/bin$ redis-cli -p 6379
127.0.0.1:6379> PUBLISH frank "hello world"
(integer) 1
127.0.0.1:6379> PUBLISH frank "welcome"
(integer) 1
原理:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jZLb0pcI-1624946464873)(/home/frank/.config/Typora/typora-user-images/image-20210510154408080.png)]
Redis主从复制
主从复制的概念
主从复制,是指将一台Redis服务器的数据,复制到其他的Redis服务器。前者称为主节点(master/leader),后者称为从节点(slave/follower) ; 数据的复制是单向的,只能由主节点到从节点。Master以写为主,Slave以读为主。
默认情况下,每台Redis服务器都是主节点 ;且一个主节点可以有多个从节点(或没有从节点),但一个从节点只能有一个主节点。
主从复制的作用
- 读写分离:主节点写,从节点读,提高服务器的读写负载能力
- 数据冗余︰主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式。
- 故障恢复︰当主节点出现问题时,可以由从节点提供服务,实现快速的故障恢复 ; 实际上是一种服务的冗余。
- 负载均衡︰在主从复制的基础上,配合读写分离,可以由主节点提供写服务,由从节点提供读服务(即写Redis数据时应用连接主节点,读Redis数据时应用连接从节点),分担服务器负载 ; 尤其是在写少读多的场景下,通过多个从节点分担读负载,可以大大提高Redis服务器的并发量。
- 高可用(集群)基石︰除了上述作用以外,主从复制还是哨兵和集群能够实施的基础,因此说主从复制是Redis高可用的基础。
环境配置
只配置从库,不配置主库。
127.0.0.1:6379> INFO replication #查看当前库的信息
# Replication
role:master #角色
connected_slaves:0 #从机的数量
master_failover_state:no-failover
master_replid:4e0c8aa500279505d09120520406c85c132612b5
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文件
- 端口
- pid名字
- log文件名字
- dump.rdb名字
启动
root 50099 1042 0 16:44 ? 00:00:00 redis-server 127.0.0.1:6379
root 50134 1042 0 16:45 ? 00:00:00 redis-server 127.0.0.1:6380
root 50142 1042 0 16:45 ? 00:00:00 redis-server 127.0.0.1:6381
frank 50151 49915 0 16:46 pts/0 00:00:00 grep --color=auto redis
一主二从
127.0.0.1:6380> SLAVEOF 127.0.0.1 6379 #配置从机
OK
127.0.0.1:6380> info replication
# Replication
role:slave
master_host:127.0.0.1
master_port:6379
master_link_status:up
master_last_io_seconds_ago:4
master_sync_in_progress:0
slave_repl_offset:28
slave_priority:100
slave_read_only:1
replica_announced:1
connected_slaves:0
master_failover_state:no-failover
master_replid:e16592878a71fd546a6e3bd91b3fd8e89314c2f6
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:28
127.0.0.1:6379> info replication $主机
# Replication
role:master
connected_slaves:2
slave0:ip=127.0.0.1,port=6380,state=online,offset=168,lag=0
slave1:ip=127.0.0.1,port=6381,state=online,offset=168,lag=0
master_failover_state:no-failover
master_replid:e16592878a71fd546a6e3bd91b3fd8e89314c2f6
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:168
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:168
从config文件中配置从机(永久)
# replicaof <masterip> <masterport> 主机信息
# masterauth <master-password> 主机有密码
主机可以写也可以读,但是从机只能读。
复制原理
- 全量复制
- 增量复制
哨兵模式
当主服务器宕机后,需要手动把一台从服务器切换为主服务器,这就需要人工干预,费事费力,还会造成一段时间内服务不可用。这不是一种推荐的方式,更多时候,我们优先考虑哨兵(Sentinel)模式。
哨兵模式是一种特殊的模式,首先Redis提供了哨兵的命令,哨兵是一个独立的进程,作为进程,它会独立运行。其原理是哨兵通过发送命令,等待Redis服务器响应,从而监控运行的多个Redis实例。
这里的哨兵有两个作用
- 通过发送命令,让Redis服务器返回监控其运行状态,包括主服务器和从服务器。
- 当哨兵监测到master宕机,会自动将slave切换成master,然后通过发布订阅模式通知其他的从服务器,修改配置文件,让它们切换主机。
然而一个哨兵进程对Redis服务器进行监控,可能会出现问题,为此,我们可以使用多个哨兵进行监控。各个哨兵之间还会进行监控,这样就形成了多哨兵模式。
故障切换(failover)过程:
假设主服务器宕机,哨兵1先检测到这个结果,系统并不会马上进行failover过程,仅仅是哨兵1主观的认为主服务器不可用,这个现象成为主观下线。当后面的哨兵也检测到主服务器不可用,并且数量达到一定值时,那么哨兵之间就会进行一次投票,投票的结果由一个哨兵发起,进行failover操作。切换成功后,就会通过发布订阅模式,让各个哨兵把自己监控的从服务器实现切换主机,这个过程称为客观下线。这样对于客户端而言,一切都是透明的。
1.配置哨兵配置文件
sentinel monitor myredis 127.0.0.1 6379 1 #sentinel monitor 被监控的名称 host port 1
2.启动哨兵
frank@frank-virtual-machine:/usr/local/bin$ sudo redis-sentinel kconfig/sentinel.conf
51265:X 10 May 2021 21:08:19.847 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
51265:X 10 May 2021 21:08:19.847 # Redis version=6.2.3, bits=64, commit=00000000, modified=0, pid=51265, just started
51265:X 10 May 2021 21:08:19.847 # Configuration loaded
51265:X 10 May 2021 21:08:19.848 * Increased maximum number of open files to 10032 (it was originally set to 1024).
51265:X 10 May 2021 21:08:19.848 * monotonic clock: POSIX clock_gettime
_._
_.-``__ ''-._
_.-`` `. `_. ''-._ Redis 6.2.3 (00000000/0) 64 bit
.-`` .-```. ```\/ _.,_ ''-._
( ' , .-` | `, ) Running in sentinel mode
|`-._`-...-` __...-.``-._|'` _.-'| Port: 26379
| `-._ `._ / _.-' | PID: 51265
`-._ `-._ `-./ _.-' _.-'
|`-._`-._ `-.__.-' _.-'_.-'|
| `-._`-._ _.-'_.-' | https://redis.io
`-._ `-._`-.__.-'_.-' _.-'
|`-._`-._ `-.__.-' _.-'_.-'|
| `-._`-._ _.-'_.-' |
`-._ `-._`-.__.-'_.-' _.-'
`-._ `-.__.-' _.-'
`-._ _.-'
`-.__.-'
51265:X 10 May 2021 21:08:19.852 # Sentinel ID is 4aa5668a1e0ea7e9b42228b7c65c37c8588813a4
51265:X 10 May 2021 21:08:19.852 # +monitor master myredis 127.0.0.1 6379 quorum 1
优点:
1.哨兵集群,基于主从复制模式
2.主从可以切换,故障可以转移
缺点:
配置麻烦
Redis缓存穿透和雪崩
缓存穿透
缓存穿透的概念很简单,用户想要查询一个数据,发现redis内存数据库没有,也就是缓存没有命中,于是向持久层数据库查询。发现也没有,于是本次查询失败。当用户很多的时候,缓存都没有命中,于是都去请求了持久层数据库。这会给持久层数据库造成很大的压力,这时候就相当于出现了缓存穿透。
解决方案:
布隆过滤器:布隆过滤器是一种数据结构,对所有可能查询的参数以hash形式存储,当用户想要查询的时候,使用布隆过滤器发现不在集合中,就直接丢弃,不再对持久层查询。
缓存空对象:当存储层不命中后,即使返回的空对象也将其缓存起来,同时会设置一个过期时间,之后再访问这个数据将会从缓存中获取,保护了后端数据源;
但是这种方法会存在两个问题:
- 如果空值能够被缓存起来,这就意味着缓存需要更多的空间存储更多的键,因为这当中可能会有很多的空值的键;
- 即使对空值设置了过期时间,还是会存在缓存层和存储层的数据会有一段时间窗口的不一致,这对于需要保持一致性的业务会有影响。
缓存击穿
key对应的数据存在,但在redis中过期,此时若有大量并发请求过来,这些请求发现缓存过期一般都会从后端DB加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端DB压垮。
微博热搜
解决方案:
设置热点永不过期
加互斥锁:
业界比较常用的做法,是使用mutex。简单地来说,就是在缓存失效的时候(判断拿出来的值为空),不是立即去load db,而是先使用缓存工具的某些带成功操作返回值的操作(比如Redis的SETNX或者Memcache的ADD)去set一个mutex key,当操作返回成功时,再进行load db的操作并回设缓存;否则,就重试整个get缓存的方法。
SETNX,是「SET if Not eXists」的缩写,也就是只有不存在的时候才设置,可以利用它来实现锁的效果。
雪崩击穿
在某一时间段,缓存集中过期失效。Redis宕机。
解决方案:
1)redis高可用
这个思想的含义是,既然redis有可能挂掉,那我多增设几台redis,这样一台挂掉之后其他的还可以继续工作,其实就是搭建的集群。
(2)限流降级
这个解决方案的思想是,在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。
(3)数据预热
数据加热的含义就是在正式部署之前,我先把可能的数据先预先访问一遍,这样部分可能大量访问的数据就会加载到缓存中。在即将发生大并发访问前手动触发加载缓存不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀。