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是将数据全部放在内存中的。
命令都是以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
)
命令都是以s
开头的
无序不重复
sadd
smembers
sismember
srem
Map集合,key-<key-value>
hset key key value
hget
有序集合
在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"
Redis事务的本质:一组命令的集合!一个事务所有的命令都会被序列化,在事务的执行过程中,会按照顺序执行。
一次性、顺序性、排他性
Redis事务没有隔离的级别。
Redis单条命令保证原子性,但是Redis事务不保证原子性。
Redis事务
正常执行事务
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
悲观锁
无论做什么都会加锁。
乐观锁
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>
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
Jedis:采用的直连,多个线程操作的话,是不安全的。
避免不安全,采用jedis pool连接池。 BIO
lettuce:采用netty,实例可以在多个线程中共享,不存在线程不安全的情况。可以减少线程数量,更像NIO模式。
网络
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提供了持久化功能。
触发机制
恢复RDB文件
将rdb文件放在redis的启动目录
config get dir
优点:
缺点:
将所有命令都记录下来,恢复将所有命令都执行一遍。
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发布订阅(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服务器。前者称为主节点(master/leader),后者称为从节点(slave/follower) ; 数据的复制是单向的,只能由主节点到从节点。Master以写为主,Slave以读为主。
默认情况下,每台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文件
启动
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服务器进行监控,可能会出现问题,为此,我们可以使用多个哨兵进行监控。各个哨兵之间还会进行监控,这样就形成了多哨兵模式。
故障切换(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内存数据库没有,也就是缓存没有命中,于是向持久层数据库查询。发现也没有,于是本次查询失败。当用户很多的时候,缓存都没有命中,于是都去请求了持久层数据库。这会给持久层数据库造成很大的压力,这时候就相当于出现了缓存穿透。
解决方案:
布隆过滤器:布隆过滤器是一种数据结构,对所有可能查询的参数以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,设置不同的过期时间,让缓存失效的时间点尽量均匀。