redis

本文介绍了Redis的基本概念,包括NoSQL数据库的种类和Redis的特点。详细讲解了Redis的命令行操作,如设置、获取、删除键值对,以及集合、哈希、列表和有序集合的操作。此外,还涵盖了Redis的持久化机制,如RDB和AOF,以及主从复制实现读写分离。最后提到了Redis的事务处理和在互联网平台中的缓存架构及其可能遇到的问题,如缓存穿透、缓存击穿和缓存雪崩,并给出了相应的解决方案。

一 Nosql

1 什么是Nosql数据库

我们之前学习过数据库:MySQL。MySQL他是一种典型的RDBMS。

1.1 互联网行业的发展的架构演变

1.1.1 单一数据库架构

​ 90年代,国内互联都还没有完全普及,采取的就是这种架构。当时一个网站的访问流量并不大,单独的数据库完全就可以满足日常的访问需求。当年的网站几乎都是静态结构

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nU0XmuDz-1654680729649)(001.png)]

1.1.2 Memcached + 数据库 + 垂直拆分

​ 随着行业的发展,系统版本逐渐更替,这个app的用户数也在激增,当数据量达到一定程度的时候,单一数据已经不足与支撑日常的访问存储。随后架构发生了演变,添加了缓存用于帮助更实时的查询。

​ 垂直拆分:说白了就是将一个app中的各个表按照业务情况分别存放在不同的数据库中。最终达到减少扫描效果,从而提升查询等速度。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wLI6JRTI-1654680729651)(002.png)]

1.1.3 主从复制 + 读写分离

再往后发展,读写压力日益增大,特别是读压力剧增。衍生出来新的数据库架构

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lsiApIeC-1654680729651)(003.png)]

1.1.4 分表分库 + 水平拆分 + 数据库集群

分表分库 : 将一张表拆分为多张表。

水平拆分: 将一张表达的数据库分别存储到不同的数据库中。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-s2QF9h6U-1654680729652)(004.png)]

1.1.5 发展的今天

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TAIWYK6m-1654680729652)(005.png)]

1.2 Nosql数据库

​ Not Only Sql(NoSql), 它也是一种数据库,但是从它的名称我们可以知道,他和RDBMS不一样,它不靠写sql来进行数据库的读写。它靠什么来读写呢?每种不同的Nosql数据库都不一样。

​ Redis : KV键值对存储数据库的Nosql数据库

​ MongoDB : 文档的数据库

​ HBase : 列式存储的Nosql的数据库

​ ClickHouse : 列式存储的支持位图的数据库

​ Neo4J : Nosql图数据库

二 Redis命令行操作

1 Redis C源码安装

##1. 下载C环境
[root@hadoop software]# yum -y install gcc-c++

##2. 解压安装redis
[root@hadoop software]# tar -zxvf redis-4.0.14.tar.gz -C /opt/apps/ & cd /opt/apps/
[root@hadoop redis-4.0.14]# make
[root@hadoop redis-4.0.14]# make PREFIX=/opt/apps/redis-4.0.14 install
[root@hadoop bin]# vi /etc/profile


##3. 测试
##3.1 启动redis服务——前台启动
[root@hadoop bin]# redis-server

##3.2 redis的客户端连接redis的服务
[root@hadoop ~]# redis-cli

127.0.0.1:6379> ping
PONG

127.0.0.1:6379> SHUTDOWN ## 关闭服务
not connected> exit ## 断开与服务的连接

##3.3 后台启动redis的服务
[root@hadoop redis-4.0.14]# mkdir conf
[root@hadoop redis-4.0.14]# cp redis.conf conf/
[root@hadoop redis-4.0.14]# vi conf/redis.conf

################################# GENERAL #####################################
# By default Redis does not run as a daemon. Use 'yes' if you need it.
# Note that Redis will write a pid file in /var/run/redis.pid when daemonized.
daemonize yes

[root@hadoop redis-4.0.14]# redis-server conf/redis.conf
20885:C 05 Jun 09:26:12.098 # Configuration loaded
[root@hadoop redis-4.0.14]# ps -ef | grep redis
root     20886     1  0 09:26 ?        00:00:00 redis-server 127.0.0.1:6379
root     21080  5854  0 09:26 pts/0    00:00:00 grep --color=auto redis
[root@hadoop ~]# redis-cli
127.0.0.1:6379> ping
PONG

2 Redis的五种常见数据类型

2.1 参考资料

https://redis.io/ ## 官网
https://www.redis.net.cn/ ## redis中文网
http://www.redis.cn/ ## redis中文教程

2.2 五种常见类型

2.2.1 字符串
  • 常用命令表格
命令描述
SET设置指定 key 的值
GET获取指定 key 的值
GETRANGE返回 key 中字符串值的子字符
GETSET将给定 key 的值设为 value ,并返回 key 的旧值 ( old value )
GETBIT对 key 所储存的字符串值,获取指定偏移量上的位 ( bit )
MGET获取所有(一个或多个)给定 key 的值
SETBIT对 key 所储存的字符串值,设置或清除指定偏移量上的位(bit)
SETEX设置 key 的值为 value 同时将过期时间设为 seconds
SETNX只有在 key 不存在时设置 key 的值
SETRANGE从偏移量 offset 开始用 value 覆写给定 key 所储存的字符串值
STRLEN返回 key 所储存的字符串值的长度
MSET同时设置一个或多个 key-value 对
MSETNX同时设置一个或多个 key-value 对
PSETEX以毫秒为单位设置 key 的生存时间
INCR将 key 中储存的数字值增一
INCRBY将 key 所储存的值加上给定的增量值 ( increment )
INCRBYFLOAT将 key 所储存的值加上给定的浮点增量值 ( increment )
DECR将 key 中储存的数字值减一
DECRBY将 key 所储存的值减去给定的减量值 ( decrement )
APPEND将 value 追加到 key 原来的值的末尾
  • 测试
##1. set命令 : 设置kv值
127.0.0.1:6379> set k1 v1
OK

##2. get命令 : 根据k获取v
127.0.0.1:6379> get k1
"v1"

##3. getrange : 根据k值获取v的子字符串
127.0.0.1:6379> getrange name 0 1
"li"

##4. getset : 根据k值设置v值,然后返回v的旧值
127.0.0.1:6379> getset name rocklee
"lixi"

##5. getbit : 获取指定k值指定偏移量上的位(0,1)
2byte --> 0000 0010
127.0.0.1:6379> set k2 a
OK
127.0.0.1:6379> getbit k2 0
(integer) 0

## 首先过去到k2对应的value的值,是字符串:a
## 然后a = 97, --》 二进制 0000 0000 0000 0000 0000 0000 0000 0000

##6. mget : 获取多个key的value
127.0.0.1:6379> mget name k1 k2
1) "rocklee"
2) "v1"
3) "a"

##7. setex : 设置一个值,并指定它的过期时间
127.0.0.1:6379> setex k3 5 v3
OK
127.0.0.1:6379> get k3
"v3"
127.0.0.1:6379> get k3
(nil)

##8. setnx : 设置一个值,如果k值不存在。时间使用秒为单位
127.0.0.1:6379> setnx k3 v33
(integer) 1
127.0.0.1:6379> get k3
"v33"
127.0.0.1:6379> setnx k3 v3
(integer) 0
127.0.0.1:6379> get k3
"v33"

##9. setrange : 替换一个指定k的字符串的一部分
127.0.0.1:6379> setrange name 4 LEE
(integer) 7
127.0.0.1:6379> get name
"rockLEE"

##10. strlen : 查看一个k的v的字符串长度
127.0.0.1:6379> strlen name
(integer) 7

##11. mset : 一次设置多个k的v
127.0.0.1:6379> mset k5 v5 k6 v6
OK
127.0.0.1:6379> mget k5 k6
1) "v5"
2) "v6"

##12. msetnx : 一次设置多个值,当k不存在就设置成功。如果一个不成功,那么都失败
127.0.0.1:6379> msetnx k5 v5 k7 v7
(integer) 0
127.0.0.1:6379> mget k5 k7
1) "v5"
2) (nil)

##13. psetex : 和setex相似,只不过这个命令指定的时间是毫秒、
127.0.0.1:6379> psetex k10 3000 v10
OK
127.0.0.1:6379> get k10
"v10"
127.0.0.1:6379> get k10
(nil)

##14. incr : 自增,使k对应的v自增。
127.0.0.1:6379> incr k11
(integer) 1
127.0.0.1:6379> incr k11
(integer) 2
127.0.0.1:6379> incr k11
(integer) 3
127.0.0.1:6379> incr k11
(integer) 4
127.0.0.1:6379> get k11
"4"
127.0.0.1:6379> set k12 v12
OK
127.0.0.1:6379> incr k12
(error) ERR value is not an integer or out of range

##15. 按照指定的增量自增
127.0.0.1:6379> incrby k11 2
(integer) 6
127.0.0.1:6379> incrby k11 2
(integer) 8

##16. append : 追加值
127.0.0.1:6379> append name 1986
(integer) 11
127.0.0.1:6379> get name
"rockLEE1986"

2.2.2 Set :集合
  • 常见命令表格
命令描述
SADD向集合添加一个或多个成员
SCARD获取集合的成员数
SDIFF返回给定所有集合的差集
SDIFFSTORE返回给定所有集合的差集并存储在 destination 中
SINTER返回给定所有集合的交集
SINTERSTORE返回给定所有集合的交集并存储在 destination 中
SISMEMBER判断 member 元素是否是集合 key 的成员
SMEMBERS返回集合中的所有成员
SMOVE将 member 元素从 source 集合移动到 destination 集合
SPOP移除并返回集合中的一个随机元素
SRANDMEMBER返回集合中一个或多个随机数
SREM移除集合中一个或多个成员
SUNION返回所有给定集合的并集
SUNIONSTORE所有给定集合的并集存储在 destination 集合中
SSCAN迭代集合中的元素
  • 测试
##1. sadd : 添加元素到set集合
127.0.0.1:6379> sadd s1 1 2 3 4 5 6
(integer) 6

##2. scard : 查看集合中的元素个数
127.0.0.1:6379> scard s1
(integer) 6

##3. smembers : 查看集合中的元素
127.0.0.1:6379> smembers s1
1) "1"
2) "2"
3) "3"
4) "4"
5) "5"
6) "6"

##4. sdiff : 求两个集合的差集
127.0.0.1:6379> sdiff s1 s2
1) "1"
2) "3"
3) "5"

##5. sinter : 交集
127.0.0.1:6379> sinter s1 s2
1) "2"
2) "4"
3) "6"

##6. sunion : 并集
127.0.0.1:6379> sunion s1 s2
1) "1"
2) "2"
3) "3"
4) "4"
5) "5"
6) "6"
7) "8"
8) "10"

##7. sismember : 判断一个元素是否在一个指定的集合中
127.0.0.1:6379> sismember s1 2
(integer) 1
127.0.0.1:6379> sismember s1 100
(integer) 0

##8. smove : 将一个集合的元素移动到另一个集合中
127.0.0.1:6379> smove s1 s3 4
(integer) 1
127.0.0.1:6379> smembers s3
1) "4"
127.0.0.1:6379> smembers s1
1) "1"
2) "2"
3) "3"
4) "5"
5) "6"

##9. 随机移除一个集合中的元素并返回这个元素
127.0.0.1:6379> spop s1
"1"
127.0.0.1:6379> spop s1
"3"
127.0.0.1:6379> smembers s1
1) "2"
2) "5"
3) "6"

##10. sdiffstore : 求两个key的差集,并将结果存储到destination中
127.0.0.1:6379> sdiffstore s10 s3 s4
(integer) 1
127.0.0.1:6379> smembers s10
1) "4"

##11. srem : 删除集合中的指定元素
127.0.0.1:6379> srem s1 2
(integer) 1

##12. sscan : 迭代集合中的元素
127.0.0.1:6379> sscan s1 1
1) "0"
2) 1) "5"
   2) "6"
2.2.3 Hash : 哈希
  • 命令行操作
命令描述
HDEL删除一个或多个哈希表字段
HEXISTS查看哈希表 key 中,指定的字段是否存在
HGET获取存储在哈希表中指定字段的值
HGETALL获取在哈希表中指定 key 的所有字段和值
HINCRBY为哈希表 key 中的指定字段的整数值加上增量 increment
HINCRBYFLOAT为哈希表 key 中的指定字段的浮点数值加上增量 increment
HKEYS获取所有哈希表中的字段
HLEN获取哈希表中字段的数量
HMGET获取所有给定字段的值
HMSET同时将多个 field-value (域-值)对设置到哈希表 key 中
HSET将哈希表 key 中的字段 field 的值设为 value
HSETNX只有在字段 field 不存在时,设置哈希表字段的值
HVALS获取哈希表中所有值
HSCAN迭代哈希表中的键值对
HSTRLEN返回哈希表 key 中, 与给定域 field 相关联的值的字符串长度
  • 测试
##1. hash中存值
127.0.0.1:6379> hset h1 name lixi
(integer) 1
127.0.0.1:6379> hset h1 gender male
(integer) 1

##2. 读取值
127.0.0.1:6379> hget h1 name
"lixi"
127.0.0.1:6379> hget h1 gender
"male"

##3. 获取到hash的所有的kv值
127.0.0.1:6379> hgetall h1
1) "name"
2) "lixi"
3) "gender"
4) "male"

##4. 删除hash中的field
127.0.0.1:6379> hdel h1 gender
(integer) 1
127.0.0.1:6379> hgetall h1
1) "name"
2) "lixi"

##5. 设置hash中的自增值
127.0.0.1:6379> hincrby h1 age 1
(integer) 1
127.0.0.1:6379> hincrby h1 age 1
(integer) 2

##6. 获取到hash中的所有value
127.0.0.1:6379> hvals h1
1) "lixi"
2) "2"

##7. 判断hash的field的是否存在
127.0.0.1:6379> hexists h1 name
(integer) 1
127.0.0.1:6379> hexists h1 age
(integer) 1
127.0.0.1:6379> hexists h1 gender
(integer) 0

##8. 获取到hash中所有的key
127.0.0.1:6379> hkeys h1
1) "name"
2) "age"

##9. 查看hash中的元素的个数
127.0.0.1:6379> hlen h1
(integer) 2

2.2.4 List : 列表
  • 命令表格
命令描述
BLPOP移出并获取列表的第一个元素
BRPOP移出并获取列表的最后一个元素
BRPOPLPUSH从列表中弹出一个值,并将该值插入到另外一个列表中并返回它
LINDEX通过索引获取列表中的元素
LINSERT在列表的元素前或者后插入元素
LLEN获取列表长度
LPOP移出并获取列表的第一个元素
LPUSH将一个或多个值插入到列表头部
LPUSHX将一个值插入到已存在的列表头部
LRANGE获取列表指定范围内的元素
LREM移除列表元素
LSET通过索引设置列表元素的值
LTRIM对一个列表进行修剪(trim)
RPOP移除并获取列表最后一个元素
RPOPLPUSH移除列表的最后一个元素,并将该元素添加到另一个列表并返回
RPUSH在列表中添加一个或多个值
RPUSHX为已存在的列表添加值
  • 测试
##1. 弹出元素 : lpop/rpop
127.0.0.1:6379> lpop l1
"5"
127.0.0.1:6379> lpop r1
"1"
127.0.0.1:6379> rpop l1
"1"
127.0.0.1:6379> rpop r1
"5"


##2. 添加元素 : lpush/rpush
127.0.0.1:6379> lpush l1 1 2 3 4 5
(integer) 5
127.0.0.1:6379> rpush r1 1 2 3 4 5
(integer) 5

##3. 查看集合中的元素 : lrange
127.0.0.1:6379> lrange l1 0 100
1) "5"
2) "4"
3) "3"
4) "2"
5) "1"
127.0.0.1:6379> lrange r1 0 100
1) "1"
2) "2"
3) "3"
4) "4"
5) "5"

##4. 获取指定索引的值
127.0.0.1:6379> lindex l1 0
"4"

##5. blpop/brpop : 先从l1的列表左边弹出元素,如果l1没有元素就从l2的列表的左边弹出元素,如果l2也没有就会阻塞直到超时
127.0.0.1:6379> blpop l1 l2 1 [key ...] timeout
127.0.0.1:6379> blpop l1 l2 1
1) "l1"
2) "2"
127.0.0.1:6379> blpop l1 l2 1
(nil)
(1.09s)

##6. brpoplpush : 从l1的最右边弹出元素,然后从l2的最左边插入一个元素,1表示超时时间
127.0.0.1:6379> brpoplpush l1 l2 1
(nil)
(1.10s)

##7. lrem : 删除元素
127.0.0.1:6379> lrem l1 3 1
(integer) 1
127.0.0.1:6379> lrange l1 0 100
1) "5"
2) "4"
3) "3"
4) "2"

##8. ltrim : 截取子列表
127.0.0.1:6379> ltrim l1 1 2
OK
127.0.0.1:6379> lrange l1 0 100
1) "4"
2) "3"

##9. linsert : 在指定的元素的前/后插入一个新元素
127.0.0.1:6379> linsert l1 before 3 1
(integer) 3
127.0.0.1:6379> lrange l1 0 100
1) "4"
2) "1"
3) "3"

##10. lpushx : 向一个已经存在的列表中添加元素
127.0.0.1:6379> lpush l2 1 2
(integer) 2
127.0.0.1:6379> lpushx l3 1 2
(integer) 0
127.0.0.1:6379> lpushx l2 1 2
(integer) 4
2.2.5 ZSet : 有序集合

这个有序不是指数据存放顺序,而是指它有一个特定的排序规则:score(分数)

  • 命令表格
序号命令及描述
1[ZADD key score1 member1 score2 member2]向有序集合添加一个或多个成员,或者更新已存在成员的分数
2ZCARD key获取有序集合的成员数
3ZCOUNT key min max计算在有序集合中指定区间分数的成员数
4ZINCRBY key increment member有序集合中对指定成员的分数加上增量 increment
5[ZINTERSTORE destination numkeys key key …]计算给定的一个或多个有序集的交集并将结果集存储在新的有序集合 key 中
6ZLEXCOUNT key min max在有序集合中计算指定字典区间内成员数量
7[ZRANGE key start stop WITHSCORES]通过索引区间返回有序集合成指定区间内的成员
8[ZRANGEBYLEX key min max LIMIT offset count]通过字典区间返回有序集合的成员
9[ZRANGEBYSCORE key min max WITHSCORES] [LIMIT]通过分数返回有序集合指定区间内的成员
10ZRANK key member返回有序集合中指定成员的索引
11[ZREM key member member …]移除有序集合中的一个或多个成员
12ZREMRANGEBYLEX key min max移除有序集合中给定的字典区间的所有成员
13ZREMRANGEBYRANK key start stop移除有序集合中给定的排名区间的所有成员
14ZREMRANGEBYSCORE key min max移除有序集合中给定的分数区间的所有成员
15[ZREVRANGE key start stop WITHSCORES]返回有序集中指定区间内的成员,通过索引,分数从高到底
16[ZREVRANGEBYSCORE key max min WITHSCORES]返回有序集中指定分数区间内的成员,分数从高到低排序
17ZREVRANK key member返回有序集合中指定成员的排名,有序集成员按分数值递减(从大到小)排序
18ZSCORE key member返回有序集中,成员的分数值
19[ZUNIONSTORE destination numkeys key key …]计算给定的一个或多个有序集的并集,并存储在新的 key 中
20[ZSCAN key cursor MATCH pattern] [COUNT count]迭代有序集合中的元素(包括元素成员和元素分值)
  • 测试
##1. 添加元素
127.0.0.1:6379> zadd zset1 100 v1 99 v2 101 v3
(integer) 3

##2. 查看集合中的所有的元素
127.0.0.1:6379> zrange zset1 0 199
1) "v2"
2) "v1"
3) "v3"
127.0.0.1:6379> zrange zset1 0 100 withscores
1) "v2"
2) "99"
3) "v1"
4) "100"
5) "v3"
6) "101"

##3. 返回集合中的指定的value的索引
127.0.0.1:6379> zrank zset1 v2
(integer) 0
127.0.0.1:6379> zrank zset1 v3
(integer) 2

##4. 删除指定的元素
127.0.0.1:6379> zrem zset1 v1
(integer) 1
127.0.0.1:6379> zrange zset1 0 100
1) "v2"
2) "v3"

##5. 删除指定分数区间的数据
127.0.0.1:6379> zremrangebyscore zset2 97 99
(integer) 2
127.0.0.1:6379> zrange zset2 0 100
1) "v4"
2) "v5"
3) "v1"

##6. 返回集合中指定数据的排名,集合按照分数数据递减
127.0.0.1:6379> zrevrange zset2 0 100 withscores
1) "v1"
2) "100"
3) "v5"
4) "96"
5) "v4"
6) "95"

##7. 计算指定的分数范围有多少个元素
127.0.0.1:6379> zcount zset2 90 99
(integer) 2

##8. 计算指定的集合的字典区间的元素的个数
127.0.0.1:6379> zlexcount zset2 [v1 [v9
(integer) 3

2.3 key命令

2.3.1 命令表格
序号命令及描述
1DEL key该命令用于在 key 存在是删除 key。
2DUMP key序列化给定 key ,并返回被序列化的值。
3EXISTS key检查给定 key 是否存在。
4EXPIRE key seconds为给定 key 设置过期时间。
5EXPIREAT key timestampEXPIREAT 的作用和 EXPIRE 类似,都用于为 key 设置过期时间。 不同在于 EXPIREAT 命令接受的时间参数是 UNIX 时间戳(unix timestamp)。
6PEXPIRE key milliseconds设置 key 的过期时间亿以毫秒计。
7PEXPIREAT key milliseconds-timestamp设置 key 过期时间的时间戳(unix timestamp) 以毫秒计
8KEYS pattern查找所有符合给定模式( pattern)的 key 。
9MOVE key db将当前数据库的 key 移动到给定的数据库 db 当中。
10PERSIST key移除 key 的过期时间,key 将持久保持。
11PTTL key以毫秒为单位返回 key 的剩余的过期时间。
12TTL key以秒为单位,返回给定 key 的剩余生存时间(TTL, time to live)。
13RANDOMKEY从当前数据库中随机返回一个 key 。
14RENAME key newkey修改 key 的名称
15RENAMENX key newkey仅当 newkey 不存在时,将 key 改名为 newkey 。
16TYPE key返回 key 所储存的值的类型。
2.3.2 测试
##1. 查询当前库中的key有哪些
127.0.0.1:6379> keys *
1) "zset2"
2) "zset1"
3) "r1"
4) "l1"
5) "set1"
6) "l2"

##2. 设置已有key的过期时间
127.0.0.1:6379> expire zset1 3
(integer) 1
127.0.0.1:6379> zrange zset1 0 100
(empty list or set)

##3. 查看key的过期时间
127.0.0.1:6379> ttl zset2
(integer) -2

##4. 查看key的过期时间(毫秒)
127.0.0.1:6379> pttl zset2
(integer) -2

##5. 移除过期时间
127.0.0.1:6379> expire r1 100
(integer) 1
127.0.0.1:6379> persist r1 ## 移除r1的过期时间
(integer) 1
127.0.0.1:6379> ttl r1

##6. 判断一个key是否存在
127.0.0.1:6379> exists r1
(integer) 1

##7. 设置已有key的过期时间(毫秒)
127.0.0.1:6379> pexpire r1 1000
(integer) 1

##8. 删除一个key
127.0.0.1:6379> del l1
(integer) 1

##9. 将当前库的数据移动到其他数据库
127.0.0.1:6379> move set1 1
(integer) 1

##10. 切换数据库
127.0.0.1:6379> select 1
OK
127.0.0.1:6379[1]> keys *
1) "set1"

##11. 随机现有库中的key
127.0.0.1:6379> randomkey
"k3"

##12. 修改key的名称
127.0.0.1:6379> rename k1 k11
OK
127.0.0.1:6379> keys *
1) "k3"
2) "k2"
3) "k11"
4) "l2"

##13. 查看key的类型
127.0.0.1:6379> type k3
string
127.0.0.1:6379> type l2
list

3 redis.conf

################################## INCLUDES ###################################

# 让你的核心配置文件redis.conf去引用其他的配置文件
#
# include /path/to/local.conf
# include /path/to/other.conf

################################## NETWORK #####################################

# 配置redis的服务绑定的ip地址以及端口号
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
bind hadoop
port 6379

################################# GENERAL #####################################

# 开启后台启动,默认是前台启动
daemonize yes
# 后台启动的时候redis的进程文件的生成路径
pidfile /opt/apps/redis-4.0.14/logs/redis_6379.pid

# 指定redis的日志级别
# 一下四种选择其中一种:
# debug (产生最多的日志信息,一般在开发和测试阶段使用此日志格式)
# verbose (比debug模式的日志量少,但是都是有用的日志信息)
# notice (产生的日志一般都是运行过程日志,一般在生产阶段使用的)
# warning (警告日志)
loglevel debug

# 指定redis的日志的文件名称,如果是一个空字符串,那么日志就会输出到/dev/null
logfile "/opt/apps/redis-4.0.14/logs/redis.log"

################################## SECURITY ###################################

# 此模式的意义是使其他redis的客户端,在连接到服务之后,使用命令之前必须使用密码校验:
## AUTH <PASSWORD>
#
# requirepass foobared
requirepass 123

e.g.
[root@hadoop conf]# redis-cli -h hadoop -p 6379
hadoop:6379> KEYS *
(error) NOAUTH Authentication required.
hadoop:6379> AUTH 123
OK
hadoop:6379> KEYS *
(empty list or set)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pt5RcsYg-1654680729653)(006.png)]

三 Redis API

1 导入依赖

<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>4.1.0</version>
</dependency>

2 连接到Redis服务

package com.qf.bigdata.jedis.day1;

import redis.clients.jedis.Jedis;

public class Demo1_QuickStart {
    public static void main(String[] args) {
        //1. 创建jedis的配置对象
        Jedis jedis = new Jedis("146.56.208.76", 6379);
        //2. 授权
        jedis.auth("123");
        //3. ping
        String pong = jedis.ping();
        //4. 输出
        System.out.println(pong);
        //5. 释放资源
        jedis.close();
    }
}

3 测试五种类型的简单应用

3.1 JedisUtils

package com.qf.bigdata.jedis.day1;

import redis.clients.jedis.Jedis;

/**
 * JedisUtils
 */
public class JedisUtils {
    private static String DEFAULT_HOST = "146.56.208.76";
    private static int DEFAULT_PORT = 6379;

    public static Jedis getDefaultJedis() {
        return getJedis(DEFAULT_HOST, DEFAULT_PORT);
    }

    public static Jedis getJedis(String host, int port) {
        return new Jedis(host, port);
    }

    public static void close(Jedis jedis) {
        if (jedis != null) jedis.close();
    }
}

3.2 Demo2

package com.qf.bigdata.jedis.day1;

import redis.clients.jedis.Jedis;

public class Demo2 {

    public static void main(String[] args) {
        //1. 获取到核心对象
        Jedis jedis = JedisUtils.getDefaultJedis();

        jedis.auth("123");

        //2. 测试
        jedis.set("k1", "v1");
        jedis.sadd("set1", "1", "2");
        jedis.lpush("list1", "1", "2");

        System.out.println(jedis.get("k1"));
        System.out.println(jedis.smembers("set1"));
        System.out.println(jedis.lrange("list1", 0, 100));

        //3. 释放资源
        JedisUtils.close(jedis);
    }
}

4 Jedis的连接池对象

package com.qf.bigdata.jedis.day1;

import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;

public class Demo3 {
    public static void main(String[] args) {
        //1. 创建连接池对象的配置对象
        GenericObjectPoolConfig config = new GenericObjectPoolConfig();
        config.setMaxTotal(100);
        config.setMaxIdle(50);
        config.setMinIdle(10);

        //2. 创建获取连接池对象
        JedisPool pool = new JedisPool(config, "146.56.208.76", 6379);

        //3. 从连接池对象中获取到jedis对象
        Jedis jedis = pool.getResource();
        jedis.auth("123");
        System.out.println(jedis.ping());
        //4. 释放资源
        pool.returnResource(jedis);
    }
}

5 进一步修改JedisUtils

package com.qf.bigdata.jedis.day1;

import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;

/**
 * JedisUtils
 */
public class JedisUtils {
    private static String DEFAULT_HOST = "146.56.208.76";
    private static int DEFAULT_PORT = 6379;

    private static GenericObjectPoolConfig config = new GenericObjectPoolConfig();

    static {
        config.setMaxTotal(100);
        config.setMaxIdle(50);
        config.setMinIdle(10);
    }

    private static JedisPool defaultPool = new JedisPool(config, DEFAULT_HOST, DEFAULT_PORT);
    private static JedisPool pool;

    public static Jedis getDefaultJedis() {
        return defaultPool.getResource();
    }

    public static void initPool(String host, int port) {
        pool = new JedisPool(config, host, port);
    }

    /**
     * 这个方法调用之前,一定要保证initPool方法被调用过了
     */
    public static Jedis getJedis() {
        if (pool != null) return pool.getResource();
        else return null;
    }

    public static void close(Jedis jedis, boolean isDefault) {
        if (jedis != null && isDefault) defaultPool.returnResource(jedis);
        else if(jedis != null && !isDefault) pool.returnResource(jedis);
    }
}
  • Demo5
package com.qf.bigdata.jedis.day1;

import redis.clients.jedis.Jedis;

public class Demo4 {
    public static void main(String[] args) {
        //1. 使用默认
//        Jedis jedis = JedisUtils.getDefaultJedis();
//        jedis.auth("123");
//        System.out.println(jedis.ping());
//        JedisUtils.close(jedis, true);

        //2. 使用指定的url
        JedisUtils.initPool("146.56.208.76", 6379);
        Jedis jedis = JedisUtils.getJedis();
        System.out.println(jedis);
        jedis.auth("123");
        System.out.println(jedis.ping());
        JedisUtils.close(jedis, false);
    }
}

6 一些连接池配置的简单介绍

GenericObjectPoolConfig config = new GenericObjectPoolConfig();
config.setMaxTotal(100); // 设置连接池对象最大大小
config.setMaxIdle(50); // 设置连接池对象允许的最大的闲置的连接数
config.setMinIdle(10); // 设置连接池对象允许的最小的闲置的连接数
config.setTestOnBorrow(true); //当你获取一个jedis的连接对象的时候是否自动测试连接可用:ping
config.setTestOnReturn(true); // 让然会一个实例给连接的时候,测试这个连接对象是否可用
config.setTestWhileIdle(true); // 开辟一条异步的线程监测对象的状态
config.setNumTestsPerEvictionRun(10); //默认10,一次最多测试池中jedis的个数

四 Redis课堂练习

1 需求

写一个字符界面。登陆的字符界面。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-R9PmygSg-1654680729654)(007.png)]

五 Redis的持久化

​ Redis的持久化的意义其实就在于将内存的数据保存在磁盘中。目的是保证数据安全。

Redis的持久化机制分为两种:

  1. RDB : 快照的方式

  2. AOF : 日志的方式

1 RDB

​ 保存redis数据库中的数据到磁盘中,以周期性的快照的方式保存数据到磁盘,这种方式适合做周期性的容灾备份,但是我们在使用RDB的时候,至少可能会丢失一段时间数据,所以一般情况下,redis只有RDB做数据持久化是不够的!!!

1.1 redis.conf

################################ SNAPSHOTTING  ################################
#
# 保存redis数据库中的数据到磁盘中
#
#  命令格式: save <seconds> <changes>
#
#  如果seconds和changes都配置了,他们会同时发生效果,并记录数据到磁盘
#
#   以下配置的案例的含义:
#   如果在900秒(15 min)之后至少发生一次写操作
#   如果在300秒(5 min)之后至少发生十次写操作
#   如果在60秒(1 min)之后至少发生一万次写操作
#
#   Note: 你也可以使save命令失效
#
#   save ""

save 900 1
save 300 10
save 60 10000

# 默认你开启了rdb模式的时候,当redis后台rdb保存数据到磁盘失败的时候,停止写操作。
stop-writes-on-bgsave-error yes

# 开启或者关闭对dump.rdb文件进行压缩
# 注意:开启压缩之后对于CPU的资源消耗会增加很多
rdbcompression yes

# 检查dump.rdb文件的安全性,但是开启它之后会损失10%左右的性能
rdbchecksum yes

# 保存的RDB文件的名称
dbfilename dump.rdb

# 保存dump.rdb文件的路径
dir /opt/apps/redis-4.0.14

1.2 测试

[root@hadoop ~]# redis-server /opt/apps/redis-4.0.14/conf/redis.conf
## 使用客户端连接服务,然后保存几个kv值,然后将redis的服务关闭
## 检查安装路径
[root@hadoop redis-4.0.14]# ll
total 328
-rw-r--r--  1 root root    179 Jun  6 09:55 dump.rdb

## 再次连接服务,发现肯定有之前保存过的数据
## 然后再关闭服务,删除dump.rdb,再次连接查看是否包含数据

2 AOF

​ AOF的持久化它是采取的一种记录日志的方式保存数据,说白了就是将客户端对服务器的所有的写操作都记录到日志中,当出现数据丢失的时候我们只需要加载这个日志文件,将其中的写操作命令重新执行一遍。

2.0 同步交互和异步交互

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FDeCE22Q-1654680729654)(008.png)]

2.1 redis.conf

############################## APPEND ONLY MODE ###############################

# 默认情况下(RDB模式),redis保存数据到磁盘是采取异步操作. 这种情况下大多数都是比较好的
# 但是它可能会丢失几分钟的数据。

# AOF的持久化它是采取的一种记录日志的方式保存数据,说白了就是将客户端对服务器的所有的写操作都记录到日志# 中,当出现数据丢失的时候我们只需要加载这个日志文件,将其中的写操作命令重新执行一遍。默认情况下AOF是没有开启的
#
# AOF and RDB 持久化官网建议我们同时使用
#
appendonly yes

# 配置aof的持久化文件的名称
appendfilename "appendonly.aof"

# 配置aof的写入命令到文件的策略:
#
# Redis 提供了一下3种策略:
#
# no: 不主动同步,只是让操作系统想要刷新的同步。当操作系统资源利用率低的时候,这是最快的。
# always: 每次写操作都会进行同步,是最慢的,但是确实最安全的。
# everysec: 每秒将写操作进行一次同步。

# appendfsync always
appendfsync everysec
# appendfsync no

# 配置AOF日志重写的策略
no-appendfsync-on-rewrite no

# 日志重写的策略
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DdyjQmxi-1654680729655)(009.png)]

2.2 测试

##1. 启动服务,使用客户端连接,设置几个写操作命令
##2. 关闭服务,删除dump.rdb排除是rdb还原的影响
##3. 观察到appendonly.aof产生,重启服务连接发现数据依然还在

六 主从复制 + 读写分离

​ 所有的写操作都是由主机来完成的,从机只负责读。当主机完成了写操作之后,从机负责向主机做数据同步。

1 修改配置文件:redis6380.conf/redis6381.conf/redis6382.conf

port 6380
pidfile /opt/apps/redis-4.0.14/logs/redis_6380.pid
logfile "/opt/apps/redis-4.0.14/logs/redis6380.log"
dbfilename dump6380.rdb
appendfilename "appendonly6380.aof"

# tip: 其他两个配置文件也参照以上进行修改。

2 测试

##1. 分别启动3台redis的服务
[root@hadoop ~]# redis-server /opt/apps/redis-4.0.14/conf/redis6380.conf
[root@hadoop ~]# redis-server /opt/apps/redis-4.0.14/conf/redis6381.conf
[root@hadoop ~]# redis-server /opt/apps/redis-4.0.14/conf/redis6382.conf

##2. 分别使用3台客户端连接3台服务器
[root@hadoop ~]# redis-cli -h hadoop -p 6380
[root@hadoop ~]# redis-cli -h hadoop -p 6381
[root@hadoop ~]# redis-cli -h hadoop -p 6382

##3. 查看当前节点的主从状态

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-46lbLmU0-1654680729655)(010.png)]

##4. 让6381和6382认6380为主机
hadoop:6381> slaveof hadoop 6380
OK
hadoop:6382> slaveof hadoop 6380
OK

##5. 再次查看主从关系

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8rDyDjYi-1654680729656)(011.png)]

##6. 在从机设置一个值
hadoop:6381> set k3 v3
(error) READONLY You can't write against a read only slave.
hadoop:6380> set k3 v3
OK
hadoop:6381> get k
"v3"

##7. 经过刚才的检测,我们已经测试过主从复制读写分离的功能
##8. 测试如果主机宕机了,从机依然认原来主机,但是在实际情况下我们不可能一直等待主机归来,因为从机只读,所以如果主机宕机时间过长我们就需要选拔其中一台从机作为我们的新的主机,否则我们从机只读,又没有主机,那么整个redis的集群就不能够保存新的数据了。
hadoop:6381> slaveof no one
OK
hadoop:6382> SLAVEOF hadoop 6381
OK

hadoop:6381> info replication
# Replication
role:master
connected_slaves:1
slave0:ip=10.206.0.4,port=6382,state=online,offset=28,lag=0

hadoop:6382> info replication
# Replication
role:slave
master_host:hadoop
master_port:6381
master_link_status:down

##9. 想要配置主从关系永久生效——修改redisxxx.conf
6381/6382 : slaveof hadoop 6380
6381/6382 : masterauth 123

3 测试哨兵模式——高可用

​ redis集群采取主从结构,但是主机只有一台,如果主机宕机,整个集群无法进行写操作。这个时候需要自动的在整个集群中选举一名新的主机作为主。哨兵主要三个功能:

  • 监控(Monitoring): Sentinel 会不断地检查你的主服务器和从服务器是否运作正常。
  • 提醒(Notification): 当被监控的某个 Redis 服务器出现问题时, Sentinel 可以通过 API 向管理员或者其他应用程序发送通知。
  • 自动故障迁移(Automatic failover): 当一个主服务器不能正常工作时, Sentinel 会开始一次自动故障迁移操作, 它会将失效主服务器的其中一个从服务器升级为新的主服务器, 并让失效主服务器的其他从服务器改为复制新的主服务器; 当客户端试图连接失效的主服务器时, 集群也会向客户端返回新主服务器的地址, 使得集群可以使用新主服务器代替失效服务器。

sentinel其实就是一条进程,这条进程就是负责监控主从。

3.1 sentinel.conf

sentinel monitor mymaster hadoop 6380 1

3.2 启动哨兵进程

##1. 启动哨兵进程的两种方式
##1.1
[root@hadoop ~]# redis-server /opt/apps/redis-4.0.14/conf/sentinel.conf --sentinel

##1.2 
[root@hadoop ~]# redis-sentinel /opt/apps/redis-4.0.14/conf/sentinel.conf

3.3 注意事项

##1. 以上的配置如果说你在redis.conf中设置了密码,那么以上配置是不会成功的!!!
## requirepass 123
## 因为哨兵进程在进行状态切换的时候需要调用你的redis的进程,但是我们哨兵不知道你的密码

##2. 解决以上问题有两种办法:
##2.1 第一种办法:修改redis.conf,将## requirepass 123注释
##2.2 第二种办法:让哨兵知道你的密码,去查看你redis的根路径下sentinel.conf
## 我们需要修改我们自定义的哨兵的配置:sentienl.conf
sentinel monitor mymaster hadoop 6380 1
sentinel auth-pass mymaster 123

七 事务——分布式事务

分布式事务不支持ACID。分布式支持CAP原则/定理/理论。

C : Consistency : 一致性,在分布式中的所有的数据备份,无论在任何时间节点,在同意时刻是否同样的值

A : Applicability : 可用性,保证每个请求,无论成功还是失败一定要有响应。

P : Partition Tolerance : 分区容错性,系统中的任意的信息的丢失或者失败都不能影响到系统的正常运行。

在分布式事务中,以上3个特性只能保证其二。所以我们的选择:CA、CP、AP。

在互联网产品中往往只需要保证最终一致性即可,所以C往往不那么重视。为了保证产品的稳定性,P是必须要有的。所以我们几乎就在AP和CP选择,AP是分布式事务最常见的特性。

BASE原则:

1 事务命令

序号命令及描述
1DISCARD取消事务,放弃执行事务块内的所有命令。相当于MYSQL中的ROLLBACK
2EXEC执行所有事务块内的命令。相当于MYSQL中的COMMIT
3MULTI标记一个事务块的开始。相当于MYSQL中的BEGINTRANSCATION
4UNWATCH取消 WATCH 命令对所有 key 的监视。
5[WATCH key key …]监视一个(或多个) key ,如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断。

2 测试redis事务操作

##1. 提交事务
hadoop:6379> MULTI
OK
hadoop:6379> set k1 v1
QUEUED
hadoop:6379> set k2 v2
QUEUED
hadoop:6379> set k3 v3
QUEUED
hadoop:6379> get k1
QUEUED
hadoop:6379> get k2
QUEUED
hadoop:6379> EXEC
1) OK
2) OK
3) OK
4) "v1"
5) "v2"

##2. 事务回滚
hadoop:6379> MULTI
OK
hadoop:6379> set k4 v4
QUEUED
hadoop:6379> DISCARD
OK
hadoop:6379> get k4
(nil)

##3. 事务的原子性
##3.1 株连九族——编译性错误
hadoop:6379> MULTI
OK
hadoop:6379> set k10 v10
QUEUED
hadoop:6379> set k20 v20
QUEUED
hadoop:6379> getset k30
(error) ERR wrong number of arguments for 'getset' command
hadoop:6379> EXEC
(error) EXECABORT Transaction discarded because of previous errors.
hadoop:6379> get k10
(nil)

##3.2 冤有头债有主——运行时异常
hadoop:6379> MULTI
OK
hadoop:6379> INCR k10
QUEUED
hadoop:6379> SET k20 v20
QUEUED
hadoop:6379> INCR k20
QUEUED
hadoop:6379> set k30 v30
QUEUED
hadoop:6379> exec
1) (integer) 1
2) OK
3) (error) ERR value is not an integer or out of range
4) OK
hadoop:6379> get k30
"v30"

3 watch——监视

redis中采取的乐观锁机制来保证数据安全的机制

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SSmp4ByC-1654680729656)(012.png)]

八 Redis消息中间件

了解

https://www.redis.net.cn/tutorial/3514.html

九 Redis常见的3个面试题

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qbrVk0uR-1654680729657)(013.png)]

这是我们互联网平台的最常见的缓存架构,但是这套架构是否有漏洞?

1 缓存穿透

1.1 什么是缓存穿透

​ 所谓的缓存穿透,就是利用我们比如登陆的时候的功能,我们首先会利用用户名这个数据到缓存库中去搜索是否包含了这个用户名的用户信息。那么有一种情况是,我们输入的用户,在缓存库中没有,并且在Mysql数据库中也没有,因为我的这个用户名根本就没在我这个数据库中注册过。黑客就会利用这个漏洞,我利用这个漏斗自己写一个随机生成字符串的功能作为用户名,由于redis没有这个数据,那么我们一定会将请求全部转移给数据库服务器。

1.2 解决方案

  • 设置白名单

    我们可以设置一个白名单,这个白名单一般使用bitmap来做。bitmap存储的名单id。注册的时候就可以将用户id存放到bitmap。

  • 布隆过滤器

    不能够保证这个东西100%存在,但是能够保证这个东西100%不存在

  • 实时监控

    实时的监控redis的命中率,当出现redis命中率极具下降的时候,我们控制redis进行报警。运维人员就可以立马排查原因。发现某个ip出现的问题,就将这个ip拉近黑名单。

2 缓存击穿

2.1 什么是缓存击穿

​ 通过某个key查找数据,但是在redis过期了。这个时候恰好大量的请求过来了,由于redis的key过期了,所以这堆请求最终全部去数据库服务器进行查询去了,查完了之后才会再次将数据存放到redis。这一下就可能导致后台全部崩溃。

2.2 解决方案

  • 分析key这种热度词在哪些时间点会产生高并发,在产生高并发时间点之后就可以设置它的过期时间了。
  • 预热处理:我们预料到在什么时间点会产生高并发之前就将热度词缓存到redis中。
  • 实时调整:实时的调整这个热度词过期时间。一般都会选择在午夜之后过期它比较保险。
  • 实时锁:当缓存失效的时候(通过key获取value的时候是否等于nil),不让它去访问数据据库。

3 缓存雪崩

3.1 什么叫雪崩

​ 所谓雪崩很形象,指的是你设置在redis中的key的过期时间设置得标准过于得统一,比如在今天18点redis所有的key恰好全部过期。这个时候产生的访问请求在redis中全部都找不到,那么所有的请求就会全部找数据库服务器。那么这个时候数据库服务器面对的压力山大。

3.2 解决方案

  • 将key的过期时间进行调整,尽量不要让一大片的key在同一个时间节点过期——尽量key的过期时间分散
  • 多层缓存:redis缓存 + memorycached缓存
  • 设置过期标志更新缓存: 写一段代码记录缓存是否过期,如果过期了开启一条现此更新这个key

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值