Redis基础篇
一、安装Redis
首先,通过DockerHub搜索redis,找到相应的版本号。并通过以下命令进行安装启动。
[root@shang ~]# systemctl status docker # 查看docker是否启动
[root@shang ~]# systemctl start docker # 启动docker
[root@shang ~]# docker pull redis:6.2.4 # 安装redis 6.2.4版本的镜像
[root@shang ~]# docker images # 查看镜像
REPOSITORY TAG IMAGE ID CREATED SIZE
redis 6.2.4 fad0ee7e917a 9 days ago 105MB
[root@shang ~]# mkdir -p /opt/docker/redis/data/ # 创建数据目录
[root@shang ~]# chmod 777 /opt/docker/redis/data # 修改文件夹权限
# 复制一个redis.conf到 /opt/docker/redis/目录下,修改配置,使其它客户端可以访问
# 通过外部配置文件启动redis
[root@shang ~]# docker run -d --name redis6379 -p 6379:6379 -v /opt/docker/redis/data:/data -v /opt/docker/redis/redis.conf:/etc/redis/redis.conf redis:6.2.4 redis-server /etc/redis/redis.conf --appendonly yes
e765c1e08c8926ff8de647bf30f2fe5eed2f8a1f1d35a9f4067279857f3b0fb7
[root@shang ~]# docker ps # 查看容器是否启动成功
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
e765c1e08c89 redis:6.2.4 "docker-entrypoint.s…" 3 seconds ago Up 2 seconds 0.0.0.0:6379->6379/tcp, :::6379->6379/tcp redis6379
# 启动redis6379的客户端进行连接
[root@shang ~]# docker exec -it redis6379 redis-cli
127.0.0.1:6379>
/opt/docker/redis/data:/data 将redis容器数据目录挂载到/opt/docker/redis/data
/opt/docker/redis/redis.conf:/etc/redis/redis.conf 将redis容器配置文件挂载到/opt/docker/redis/redis.conf
redis-server /etc/redis/redis.conf 通过配置文件/etc/redis/redis.conf启动redis-server,因上面配置文件被挂载,所以最终使用的使我们主机目录的配置文件 /opt/docker/redis/redis.conf
--appendonly yes 开启数据持久化
二、基本数据类型
1. redis-key
127.0.0.1:6379> set name shang
OK
127.0.0.1:6379> set age 18
OK
127.0.0.1:6379> type name # 查看key的类型
string
127.0.0.1:6379> keys * # 查看所有的key
1) "age"
2) "name"
127.0.0.1:6379> exists age s # 返回存在key的数量
(integer) 1
127.0.0.1:6379> exists age name # 返回存在key的数量
(integer) 2
127.0.0.1:6379> exists a # 返回存在key的数量
(integer) 0
127.0.0.1:6379> move name 1 # 从当前库移除key
(integer) 1
127.0.0.1:6379> keys *
1) "age"
127.0.0.1:6379> expire age 10 # 给key设置过期时间
(integer) 1
127.0.0.1:6379> ttl age # 查看key的剩余时间
(integer) 6
127.0.0.1:6379> ttl age
(integer) 4
127.0.0.1:6379> ttl age # key失效,返回-2
(integer) -2
127.0.0.1:6379> get age
(nil)
2. String
127.0.0.1:6379> set name zhang # 设置 key
OK
127.0.0.1:6379> get name # 获取key值
"zhang"
127.0.0.1:6379> append name "san" # 拼接字符串,返回字符串长度
(integer) 8
127.0.0.1:6379> get name
"zhangsan"
127.0.0.1:6379> strlen name # 获取字符串长度
(integer) 8
127.0.0.1:6379> set view 0
OK
127.0.0.1:6379> get view
"0"
127.0.0.1:6379> incr view # 自增1
(integer) 1
127.0.0.1:6379> incr view # 自增1
(integer) 2
127.0.0.1:6379> get view
"2"
127.0.0.1:6379> decr view # 自减1
(integer) 1
127.0.0.1:6379> decr view # 自减1
(integer) 0
127.0.0.1:6379> get view
"0"
127.0.0.1:6379> incrby view 5 # 增量,步长5
(integer) 5
127.0.0.1:6379> incrby view 10 # 增量,步长10
(integer) 15
127.0.0.1:6379> get view
"15"
127.0.0.1:6379> decrby view 15 # 减量,步长15
(integer) 0
127.0.0.1:6379> get view
"0"
127.0.0.1:6379> getrange name 0 -1 # 截取字符串,获取全部字符串
"zhangsan"
127.0.0.1:6379> getrange name 0 4 # 截取字符串
"zhang"
127.0.0.1:6379> setrange name 6 ui # 字符串替换
(integer) 8
127.0.0.1:6379> get name
"zhangsui"
127.0.0.1:6379> setex food 5 fish # 设置key的同时设置过期时间
OK
127.0.0.1:6379> ttl food
(integer) -2
127.0.0.1:6379> setnx name lisi # 不存在时才设置key,存在时则不设置
(integer) 0
127.0.0.1:6379> get name
"zhangsui"
127.0.0.1:6379> setnx age 18 # 不存在时才设置key
(integer) 1
127.0.0.1:6379> get age
"18"
127.0.0.1:6379> mset k1 v1 k2 v2 # 批量插入key
OK
127.0.0.1:6379> keys *
1) "k1"
2) "k2"
127.0.0.1:6379> mget k1 k3 k2 # 批量获取key
1) "v1"
2) (nil)
3) "v2"
127.0.0.1:6379> getset k1 vv # 存在,则返回旧值,更新新值
"v1"
127.0.0.1:6379> get k1
"vv"
127.0.0.1:6379> getset k3 v3 # 不存在,则返回(nil),设置新值
(nil)
127.0.0.1:6379> get k3
"v3"
3. List
lpush rpush # 头部或尾部插入元素
lpop rpop # 头部或尾部弹出元素
lrange # 查看列表元素范围
llen # 查看元素列表个数
ltrim # 通过下标截取指定的长度,这个list被改变了,只剩下被截取的元素
rpoplpush # 移除列表的最后一个元素,将他移动到新的列表中
lset # 将列表中指定下标的值替换为另外一个值,更新操作
linsert # 将某个具体的value 插入到list指定value的前边或者后边
lindex # 通过下标获取列表中的值
127.0.0.1:6379> lpush k1 one two # 头部插入两个元素
(integer) 2
127.0.0.1:6379> lpush k1 three # 头部插入一个元素
(integer) 3
127.0.0.1:6379> lrange k1 0 -1 # 0 -1 查看全部元素
1) "three"
2) "two"
3) "one"
127.0.0.1:6379> lrange k1 1 1 # 查看从索引为1的位置开始的一个元素
1) "two"
127.0.0.1:6379> rpush k1 four # 尾部插入一个元素
(integer) 4
127.0.0.1:6379> lrange k1 0 -1
1) "three"
2) "two"
3) "one"
4) "four"
127.0.0.1:6379> lpop k1 2 # 头部弹出两个元素
1) "three"
2) "two"
127.0.0.1:6379> lrange k1 0 -1
1) "one"
2) "four"
127.0.0.1:6379> rpop k1 1
1) "four"
127.0.0.1:6379> lrange k1 0 -1
1) "one"
127.0.0.1:6379> llen k1
(integer) 1
127.0.0.1:6379> lpush k1 one one two two three
(integer) 6
127.0.0.1:6379> lrange k1 0 -1
1) "three"
2) "two"
3) "two"
4) "one"
5) "one"
6) "one"
127.0.0.1:6379> lrem k1 2 one
(integer) 2
127.0.0.1:6379> lrange k1 0 -1
1) "three"
2) "two"
3) "two"
4) "one"
127.0.0.1:6379> ltrim k1 0 2
OK
127.0.0.1:6379> lrange k1 0 -1
1) "three"
2) "two"
3) "two"
127.0.0.1:6379> lpush k2 one
(integer) 1
127.0.0.1:6379> rpoplpush k1 k2
"two"
127.0.0.1:6379> lrange k2 0 -1
1) "two"
2) "one"
127.0.0.1:6379> lrange k1 0 -1
1) "three"
2) "two"
127.0.0.1:6379> lset k3 1 hello
(error) ERR no such key
127.0.0.1:6379> lset k1 1 one
OK
127.0.0.1:6379> lrange k1 0 -1
1) "three"
2) "one"
127.0.0.1:6379> lset k1 3 three
(error) ERR index out of range
127.0.0.1:6379> linsert k1 before one two
(integer) 3
127.0.0.1:6379> lrange k1 0 -1
1) "three"
2) "two"
3) "one"
127.0.0.1:6379> linsert k1 after two two
(integer) 4
127.0.0.1:6379> lrange k1 0 -1
1) "three"
2) "two"
3) "two"
4) "one"
4. Set
sadd # 添加元素
smembers # 获取set集合中的所有元素
sismember # 判断一个值是否存在set中
scard # 获取set集合中元素的个数
srem # 移除set集合中指定元素
srandmember # 随机抽取元素
spop # 随机删除set集合中的元素
smove #将一个指定的set集合(存在)元素移动到另一个set集合(存在)中
sdiff # 求集合差集
sinter # 求集合交集
sunion # 求集合并集
127.0.0.1:6379> sadd k1 v1 v2
(integer) 2
127.0.0.1:6379> smembers k1
1) "v2"
2) "v1"
127.0.0.1:6379> sadd k1 v3
(integer) 1
127.0.0.1:6379> smembers k1
1) "v2"
2) "v1"
3) "v3"
127.0.0.1:6379> sismember k1 v4
(integer) 0
127.0.0.1:6379> sismember k1 v1
(integer) 1
127.0.0.1:6379> scard k1
(integer) 3
127.0.0.1:6379> srem k1 v2
(integer) 1
127.0.0.1:6379> smembers k1
1) "v1"
2) "v3"
127.0.0.1:6379> sadd k1 v1 v2 v4 v5 v6
(integer) 4
127.0.0.1:6379> smembers k1
1) "v1"
2) "v4"
3) "v3"
4) "v6"
5) "v2"
6) "v5"
127.0.0.1:6379> srandmember k1 2
1) "v1"
2) "v5"
127.0.0.1:6379> srandmember k1
"v3"
127.0.0.1:6379> spop k1
"v1"
127.0.0.1:6379> sadd k2 one two four
(integer) 3
127.0.0.1:6379> sadd k3 one three four five
(integer) 4
127.0.0.1:6379> sdiff k2 k3
1) "two"
127.0.0.1:6379> sinter k2 k3
1) "four"
2) "one"
127.0.0.1:6379> sunion k2 k3
1) "one"
2) "four"
3) "five"
4) "two"
5) "three"
5. Hash
6. Zset
三、主从复制
在Slave启动并连接到Master之后,它将主动发送一个SYNC命令。此后Master将启动后台存盘进程,同时收集所有接收到的用于修改数据集的命令,在后台进程执行完毕后,Master将传送整个数据库文件到Slave,以完成一次完全同步。而Slave服务器在接收到数据库文件数据之后将其存盘并加载到内存中。此后,Master继续将所有已经收集到的修改命令,和新的修改命令依次传送给Slaves,Slave将在本次执行这些数据修改命令,从而达到最终的数据同步。如果Master和Slave之间的链接出现断连现象,Slave可以自动重连Master,但是在连接成功之后,一次完全同步将被自动执行。
哨兵模式
四、持久化机制
五、三大问题
正常顺序:业务层 ==》缓存 ==》数据库
1. 缓存穿透
问题描述
核心:查询缓存和数据库,二者皆不存在的数据。
业务逻辑发松查询请求,首先从缓存中查询,由于缓存不存在,然后再前往数据库中查询。发现数据库中也没有该条记录,然后返回 null。这就是缓存穿透,根本原因是因为查询不存在的数据。
危害
如果存在海量请求访问不存在的数据,那么就会有海量请求访问数据库,最终会导致数据压力倍增或者系统崩溃。
为什么发生缓存穿透
- 恶意攻击,故意营造大量不存在的数据请求服务器,由于缓存中并不存在这些数据,导致大量的请求均落在数据库中,从而导致数据库崩溃。
- 代码逻辑错误,开发时需注意!!!
解决方案
- 缓存空数据
将数据库查询为空的key也存储在缓存中(集合返回为{},而非null),设置一个较短的过期时间,让其自动剔除。=》解决缓存中存在大量的空对象,内存占用问题
如果在缓存中空数据未过期时,插入一条记录,则会导致缓存层与存储层数据不一致的现象 ==》此时可以删除缓存中的空对象 - 布隆过滤器(bitmap)
在业务层与缓存层之间加一层,布隆过滤器
业务层 ==》 布隆过滤器 ==》缓存 ==》数据库
当业务发送请求时,首先在布隆过滤器中判断key是否存在。若不存在,则说明数据库中也不存在该数据,因此缓存都不用查询,直接返回null。若存在,则继续执行后面的流程。
使用场景
第一种:适应于空数据的key数量有限,key重复请求概率较高的场景。
第二种:适用于空数据的key各不相同、key重复请求概率低的场景。
2. 缓存雪崩
问题描述
缓存的存在可以对数据库进行保护,抵挡大量的查询请求,从而避免数据库压力剧增或崩溃。如果缓存因某种原因发生宕机,那么原来大量可以被缓存处理的请求就会全部被数据库处理,此时会导致数据库处理不了,导致系统崩溃。这就是缓存雪崩。
解决方案
- 使用缓存集群,保证缓存高可用
- 使用Hystrix
防雪崩工具,通过服务熔断、降级、限流三个手段来降低雪崩发生后的损失。
Hystrix就是一个Java类库,它采用命令模式,每一项服务处理请求都有各自的处理器。所有的请求都要经过各自的处理器。处理器会记录当前服务的请求失败率。一旦发现当前服务的请求失败率达到预设的值,Hystrix将会拒绝随后该服务的所有请求,直接返回一个预设的结果。这就是所谓的“熔断”。当经过一段时间后,Hystrix会放行该服务的一部分请求,再次统计它的请求失败率。如果此时请求失败率符合预设值,则完全打开限流开关;如果请求失败率仍然很高,那么继续拒绝该服务的所有请求。这就是所谓的“限流”。而Hystrix向那些被拒绝的请求直接返回一个预设结果,被称为“降级”。
3. 缓存击穿
问题描述
热点数据集中失效,导致海量请求进入数据库重建缓存,这个过程可能数据库处理不了那么多请求,导致系统崩坏。
解决方案
- 互斥锁
当第一个数据库查询请求发起后,就将缓存中该数据上锁;此时到达缓存的其他查询请求将无法查询该字段,从而被阻塞等待;当第一个请求完成数据库查询,并将数据更新值缓存后,释放锁;此时其他被阻塞的查询请求将可以直接从缓存中查到该数据。
互斥锁具有两个问题
第一个问题:降低系统的吞吐量
第二个问题:互斥锁可以避免某一个热点数据失效导致数据库崩溃的问题,而往往有一批热点数据同时失效的场景,此时如何防止数据库过载???===》将热点数据的过期时间错开(在基础时间上+随机数) - 永不过期
存在数据一致性问题,代码复杂度会增大