**课程目标**
* 能够掌握Redis不同数据类型操作
* 能够使用Java API操作Redis
* 能够理解Redis的两种持久化方式
* 能够理解Redis的主从复制架构
* 能够理解Redis的Sentinel架构
* 能够理解Redis集群架构
## NoSQL+MQ阶段
1. 海量数据的存储(HDFS)不能覆盖所有的大数据存储应用场景,所以要学习更多的一些分布式存储来解决大数据存储的一些特定场景的问题
2. 通过NoSQL + Mq阶段去敲开一个阶段的大门
## NoSQL数据库发展历史简介 4
### NoSQL介绍
* Web的历史发展历程
* Web 1.0——以门户网站为代表,网易、新浪,主要以用户阅读、浏览为主,没有太大性能问题
* Web 2.0——有很多用户参与进来,交互性越来越多(微博、天涯、猫扑),就有很多的用户加入进来,网站的压力也越来越大,数据库的压力很大
* 数据库性能瓶颈
* 单机数据库的性能是有限的,能够支持的并发数百-1000,
* 所以需要使用Redis缓存来解决高并发问题
* NoSQL
* Not Relational(非关系型)——没有对SQL支持、不能去建立表与表的关系
* 特点:
* 扩展性比较好
* No schema(没有模式),可以用来存储结构化、非结构化、半结构化的数据
* 速度快
* 支撑海量数据的存储
* NewSQL:基于NoSQL之上又可以支持一些SQL相关的操作
* HBase -> Phoenix
* DBEngines网站中会统计目前数据库在全世界的排名。
## Redis介绍 7
### Redis的基本介绍 7
* 网站的行为分析/流量分析
* 骨灰级指标
* PV——Page View,页面浏览量,用户浏览一次页面就会累加一次
* IP——IP地址,一个IP一天只计算一次
* UV——Unique Visit,唯一的用户访问数,每个用户一天只计算一次
* Redis是一个NoSQL、基于Key-value键值对的存储引擎
### Redis的应用场景 7
* 计数器
* TopN、排行榜(微博的热搜榜、热门话题、抖音直播间的热门直播间、淘宝电商的排行榜)
* 去重的计数
* 实时系统,用于存储一些规则
* 定时过期的一些应用(短信验证码)
* 缓存(保护数据库不被高并发压垮)
### Redis的特点 8
* 速度非常快,单机能够支持的并发、读写的速度达10W以上(Kafka更快——80W-150W)
* 支持多种数据结构类型,操作非常灵活
* string
* list
* set
* hash
* zset
* ...
## Redis单机环境安装 9
### Linux版Redis安装 10
1. 下载Redis源码包
2. 因为需要编译Redis代码,需要下载Redis的编译器
3. 需要在线安装tcl的程序,这个程序可以控制Linux中命令的执行流程(例如:先执行脚本1、再执行脚本2)
4. 使用make命令来安装Redis(make命令类似于Java中的Maven,可以控制整个项目的编译、打包),一般的C/C++项目都会有一个文件MakeFile
5. C程序的编译过程
* 先将C程序编译成.O文件(目标码),可以在操作系统上识别的01组成的指令
* 再将目标码连接一些C语言程序库,形成最终的可执行文件
6. 在软件的编译后,一般会执行运行一些测试,确保程序能够在操作系统上正确的执行
注意:
* 在生产环境,关闭redis的时候,不用使用 kill -9,应该使用redis-cli -h 主机名 -p 端口 shutdown
* 因为如果直接kill -9,可能redis的一些数据会丢失
## Redis的数据类型 13
### 对字符串string的操作 14
注意:
* 在执行一些累加器的操作时,千万不能使用 set/get来操作
* 要使用INCR/DESC/INCRBY
### 对hash/list/set/zset的操作
```ruby
# 三、操作list类型
# push往列表的头部插入数据
node1.itcast.cn:6379> LPUSH list 1 2 3 4
(integer) 4
# range表示取指定范围的元素(0--1表示获取数据的元素)
node1.itcast.cn:6379> LRANGE list 0 -1
# 四、操作SET类型
# 4.1 添加元素
SADD set_test 1
SADD set_test 1 2 3 4
# SMEMBERS key
# 返回集合中的所有成员
# SCARD key
# 获取集合的成员数
# 4.2 获取所有的元素
SMEMBERS set_test
# 4.3 获取元素的个数
SCARD set_test
# 要使用SET结构来保存网站的UV
SADD uv:2020-01-01 001 002 003
SCARD uv:2020-01-01
# 五、针对key的操作
# 5.1 删除一个key,对应的数据结构
DEL list
# 5.2 判断set_test这个key是否存在
EXISTS set_test
# 返回1表示存在,返回0表示不存在
# node1.itcast.cn:6379> EXISTS set_test
# (integer) 1
# node1.itcast.cn:6379> EXISTS set_test1
# (integer) 0
# 六、针对ZSET(有序SET)的操作
# 6.1 向ZSet中添加页面的PV值
ZADD pv 100 page1.html 200 page2.html 300 page3.html
# 6.2 获取一共有几个页面
# ZCARD key
ZCARD pv
# 6.3 要给page1.html页面增加pv值
# ZINCRBY key increment member
ZINCRBY pv 10 page1.html
# 6.4 创建两个保存PV的ZSET:
ZADD pv_zset1 10 page1.html 20 page2.html
ZADD pv_zset2 5 page1.html 10 page2.html
ZINTERSTORE pv_zset_result 2 pv_zset1 pv_zset2
# 6.7 获取ZSET中的所有成员
# ZRANGE key start stop [WITHSCORES]
ZRANGE pv_zset_result 0 -1 WITHSCORES
# 6.8 求page1.html在页面PV中的排名(最小)
# 默认是按照升序统计 0, 1, 2,3 ...,从小到大排列
# ZRANK key member
ZRANK pv_zset_result page1.html
# 6.9 求page1.html在页面PV中的排名(最大)
# ZREVRANK key member
# 注意:这个操作效率很高,并不是重新排序,只是把ZSET反转(revert)即可
ZREVRANK pv_zset_result page1.html
# 注意:
# 1. 排名是ZRANK是基于从小到大排列的,ZREVRANK是基于从大到小排列
# 2. 排名是从0开始,0代表第一名
```
### 对位图BitMaps的操作 24
* Bitmap存储的值是0或者1
* 理解Bitmaps:就是一个存储0、1的一维数组
* 操作Bitmaps的时候,必须要带上一个offset(偏移量)
```ruby
# 七、Bitmap操作
# 6.10 uid=0,5,11,15,19
# 使用Bitmap来保存用户是否访问过网站
SETBIT unique:users:2020-01-01 0 1
SETBIT unique:users:2020-01-01 5 1
SETBIT unique:users:2020-01-01 11 1
SETBIT unique:users:2020-01-01 15 1
SETBIT unique:users:2020-01-01 19 1
#SETBIT unique:users:2020-01-01 1000000 1
# 6.11 获取指定用户是否访问过网站
# GETBIT key offset
GETBIT unique:users:2020-01-01 0
# 6.12 统计2020-01-01这一天一共有多少用户访问了网站
# BITCOUNT key [start end]
BITCOUNT unique:users:2020-01-01 0 -1
# 6.13 计算2020-01-01与2020-01-02的所有访问网站的用户
# BITOP operation destkey key [key, …]
SETBIT unique:users:2020-01-02 0 1
SETBIT unique:users:2020-01-02 6 1
SETBIT unique:users:2020-01-02 12 1
# 取or操作
BITOP or unique:users:or:2020-01-01_02 unique:users:2020-01-01 unique:users:2020-01-02
# 取数量
BITCOUNT unique:users:or:2020-01-01_02 0 -1
```
### 对HyperLogLog结构的操作 27
应用场景:
* 只用来做一些去重的、大数据量的统计(网站UV值)
限制:
* 这个HyperLogLog结构存在一定的误差,误差很小,0.81%。所以它不适合对精确度要求特别高的统计。而UV这种操作,对精确度要求每那么高,是可以适用该结构的
* HyperLogLog结构不会存储数据的明细,针对UV场景,它为了节省空间资源,只会存储数据经过算法计算后的基数值,基于基数值来进行统计的
```ruby
# pfadd key userid, userid...
# pfcount key
# 需求:
# 求某个网站的UV值
pfadd taobao:uv:2020-01-01 1
pfadd taobao:uv:2020-01-01 2
pfadd taobao:uv:2020-01-01 1
# 获取UV的值(去重后的)
pfcount taobao:uv:2020-01-01
```
## Redis Java API操作 30
### 连接以及关闭redis客户端 31
* 操作Redis一般要使用Jedis的连接池,这样可以有效的复用连接资源
```java
@BeforeTest
public void beforeTest() {
// JedisPoolConfig配置对象
JedisPoolConfig config = new JedisPoolConfig();
// 指定最大空闲连接为10个
config.setMaxIdle(10);
// 最小空闲连接5个
config.setMinIdle(5);
// 最大等待时间为3000毫秒
config.setMaxWaitMillis(3000);
// 最大连接数为50
config.setMaxTotal(50);
jedisPool = new JedisPool(config, "node1.itcast.cn", 6379);
}
```
注意:
* 在IDEA中,有时候提示可能不完整,其实Jedis连接池,可以指定端口号
### 操作string类型数据 32
* Redis操作string其实和SHELL命令是一样
* 将来在编写Flink程序/Spark Streaming程序操作Redis的时候,注意操作完Redis之后,执行close,将连接返回到连接池。
```java
@Test
public void stringTest() {
// 获取Jedis连接
Jedis jedis = jedisPool.getResource();
// 1.添加一个string类型数据,key为pv,用于保存pv的值,初始值为0
jedis.set("pv", "0");
// 2.查询该key对应的数据
System.out.println("pv:" + jedis.get("pv"));
// 3.修改pv为1000
jedis.set("pv", "1000");
// 4.实现整形数据原子自增操作 +1
jedis.incr("pv");
// 5.实现整形该数据原子自增操作 +1000
jedis.incrBy("pv", 1000);
System.out.println(jedis.get("pv"));
// 将jedis对象放回到连接池
jedis.close();
}
```
### 操作hash列表类型数据 33
注意:
* 当我们后续在编写Flink、Spark Streaming流处理程序使用Java操作Redis时候,涉及到一些数字的累加
* 一定要使用incr、hincrBy
```java
@Test
public void hashTest() {
// 获取Jedis连接
Jedis jedis = jedisPool.getResource();
// 1. 往Hash结构中添加以下商品库存
// a) iphone11 => 10000
// b) macbookpro => 9000
jedis.hset("goods", "iphone11", "10000");
jedis.hset("goods", "macbookpro", "9000");
// 2. 获取Hash中所有的商品
Set<String> goodSet = jedis.hkeys("goods");
System.out.println("所有商品:");
for (String good : goodSet) {
System.out.println(good);
}
// 3. 新增3000个macbookpro库存
// String storeMacBook = jedis.hget("goods", "macbookpro");
// long longStore = Long.parseLong(storeMacBook);
// long addStore = longStore + 3000;
// jedis.hset("goods", "macbookpro", addStore + "");
jedis.hincrBy("goods", "macbookpro", 3000);
// 4. 删除整个Hash的数据
// jedis.del("goods");
jedis.close();
}
```
### 操作list类型数据 34
* List可以用来存储重复的元素,而且是有序的
* 获取所有的元素,lrange(key, 0, -1)
```java
@Test
public void listTest() {
// 获取Jedis连接
Jedis jedis = jedisPool.getResource();
// 1. 向list的左边插入以下三个手机号码:18511310001、18912301231、18123123312
jedis.lpush("tel_list", "18511310001", "18912301231", "18123123312");
// 2. 从右边移除一个手机号码
jedis.rpop("tel_list");
// 3. 获取list所有的值
List<String> telList = jedis.lrange("tel_list", 0, -1);
for (String tel : telList) {
System.out.println(tel);
}
jedis.close();
}
```
### 操作set类型的数据 34
* 计算UV主要是去重
* 将来所有的一些要求高效率去重的业务场景,都可以使用Set操作
```java
@Test
public void setTest(){
// 获取Jedis连接
Jedis jedis = jedisPool.getResource();
// 求UV就是求独立有多少个(不重复)
// 1. 往一个set中添加页面 page1 的uv,用户user1访问一次该页面
jedis.sadd("uv", "user1");
// jedis.sadd("uv", "user3");
// jedis.sadd("uv", "user1");
// 2. user2访问一次该页面
jedis.sadd("uv", "user2");
// 3. user1再次访问一次该页面
jedis.sadd("uv", "user1");
// 4. 最后获取 page1的uv值
System.out.println("uv:" + jedis.scard("uv"));
jedis.close();
}
```
## Redis的持久化 35
### RDB持久化方案 35
* RDB是一种全量备份,是将整个redis中的所有的数据,都保存到一个rdb文件中
### AOF持久化方案 37
* AOF相当于是将Redis的写操作以命令的形式保存在一个名为appendonly.aof的文件中
* 但redis集群重启的时候,就可以按照appendonly.aof文件回放(类似于:HDFS中的NameNode的元数据恢复——FSImage、Editslog)
比较两者的特点:
* RDB效率要高,因为它是直接将Redis的最新数据完整的保存下来,但如果Redis中存储的数据量比较大的时候,也会有一定的性能消耗,因为它需要创建额外的进程来去进行数据的持久化。所以要指定合理的策略。Redis中也有对应的推荐策略
* AOF效率相对RDB要低一些,因为它会将历史的写操作都执行一遍来进行恢复,同时在执行写操作的时候也会将每一个指令存储下来。但我们对数据的安全性要求高的时候,就可以考虑AOF。
## Redis 高级使用 39
### Redis 事务 39
* Redis可以使用以下几个指令来开启事务
* MULTI
* EXEC
* DISCARD
* Redis的事务“回滚”是指的:如果在Redis开启事务之后,出现了语法错误,Redis可以检查出来,进行回滚
* 但如果Redis的一些问题是在运行时才出现的,例如:类型不匹配,无法进行事务回滚的
* Redis的事务是不支持隔离性和原子性
### Redis 过期策略 42
* 针对设置了expire过期时间的key,怎么样移除掉这些key
* 每一个key,都有一个定时器扫描。对CPU消耗比较大,因为如果在有很多设置了过期时间的key时,每个key都要开启一个定时器。对内存比较友好,只要key一失效,就马上会释放内存空间。
* 惰性移除key。不会定时扫描,但客户端访问这个key时候,如果发现这个key到期了,就移除。如果没有客户端访问,就不移除。对CPU友好,对内存不友好(有可能会有很多key在一定时间内得不到快速的清除)
* 定时扫描,是一种折中的方法。检查expire key的字段,定期地扫描期,将过期的key移除掉
### 内存淘汰策略 43
* 当redis中的内存不足的时候,就会根据内存淘汰的策略来清除对应的内存空间
* LRU——优先清除最近最少访问的Key
* 推荐的策略:allkeys-lru
## Redis的主从复制架构 44
### 另外两台服务器安装Redis 45
### 启动Redis服务 47
## Redis中的Sentinel架构 47
### Sentinel介绍 47
### 配置哨兵 49
### Redis的sentinel模式代码开发连接 52
## Redis 集群 54
### 引言 54
### Redis Cluster 设计 55
### Redis Cluster 搭建 57
### 启动Redis服务 62
### Redis Cluster 管理 68
### JavaAPI操作redis集群 69
## Redis高频面试题 71
### 缓存穿透 71
### 缓存击穿 72
### 缓存雪崩 72