Redis
课程目标
-
能够掌握Redis不同数据类型操作
-
能够使用Java API操作Redis
-
能够理解Redis的两种持久化方式
-
能够理解Redis的主从复制架构,哨兵,集群。
NoSQL数据库发展历史简介
NoSQL介绍
-
Web的历史发展历程
-
Web 1.0——以门户网站为代表,网易、新浪,主要以用户阅读、浏览为主,没有太大性能问题
-
Web 2.0——有很多用户参与进来,交互性越来越多(微博、天涯、猫扑),就有很多的用户加入进来,网站的压力也越来越大,数据库的压力很大
-
-
数据库性能瓶颈
-
单机数据库的性能是有限的,能够支持的并发数百-1000,
-
所以需要使用Redis缓存来解决高并发问题
-
-
NoSQL - not only SQL
-
Not Relational(非关系型)——没有对SQL支持、不能去建立表与表的关系
-
特点:
-
扩展性比较好
-
No schema(没有模式),可以用来存储结构化、非结构化、半结构化的数据
-
速度快
-
支撑海量数据的存储
-
-
Redis介绍
Redis的基本介绍
-
网站的行为分析/流量分析
-
骨灰级指标
-
PV——Page View,页面浏览量,用户浏览一次页面就会累加一次
-
IP——IP地址,一个IP一天只计算一次
-
UV——Unique Visit,唯一的用户访问数,每个用户一天只计算一次
-
-
Redis是一个NoSQL、基于Key-value键值对的存储引擎
Redis的应用场景
-
计数器
-
TopN、排行榜(微博的热搜榜、热门话题、抖音直播间的热门直播间、淘宝电商的排行榜)
-
去重的计数
-
实时系统,用于存储一些规则
-
定时过期的一些应用(短信验证码)
-
缓存(保护数据库不被高并发压垮)
Redis的特点
-
速度非常快,单机能够支持的并发、读写的速度达10W以上(Kafka更快——80W-150W)
-
支持多种数据结构类型,操作非常灵活
-
string
-
list
-
set
-
hash (存对象)
-
zset
-
...
-
Redis的数据类型
对字符串string的操作
注意:
-
在执行一些累加器的操作时,千万不能使用 set/get来操作
-
要使用INCR/DESC/INCRBY
对hash/list/set/zset的操作
# 三、操作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 keyname # 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代表第一名
Redis Java API操作 30
连接以及关闭redis客户端 31
-
操作Redis一般要使用Jedis的连接池,这样可以有效的复用连接资源
@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(linux)将来在编写Flink程序/Spark Streaming程序操作Redis的时候,注意操作完Redis之后,执行close,将连接返回到连接池。
@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
@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)
@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类型的数据
-
计算UV主要是去重
-
将来所有的一些要求高效率去重的业务场景,都可以使用Set操作
@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的持久化
RDB持久化方案
-
RDB是一种全量备份,是将整个redis中的所有的数据,都保存到一个rdb文件中
AOF持久化方案
-
AOF相当于是将Redis的写操作以命令的形式保存在一个名为appendonly.aof的文件中
-
但redis集群重启的时候,就可以按照appendonly.aof文件回放(类似于:HDFS中的NameNode的元数据恢复——FSImage、Editslog)
比较两者的特点:
-
RDB效率要高,因为它是直接将Redis的最新数据完整的保存下来,但如果Redis中存储的数据量比较大的时候,也会有一定的性能消耗,因为它需要创建额外的进程来去进行数据的持久化。所以要指定合理的策略。Redis中也有对应的推荐策略
-
AOF效率相对RDB要低一些,因为它会将历史的写操作都执行一遍来进行恢复,同时在执行写操作的时候也会将每一个指令存储下来。但我们对数据的安全性要求高的时候,就可以考虑AOF。
Redis 高级使用
Redis 事务
-
Redis可以使用以下几个指令来开启事务
-
MULTI 开启
-
EXEC 执行(结束)
-
DISCARD 取消(介绍)
-
-
Redis的事务“回滚”是指的:如果在Redis开启事务之后,出现了语法错误,Redis可以检查出来,进行回滚
-
但如果Redis的一些问题是在运行时才出现的,例如:类型不匹配,无法进行事务回滚的
-
Redis的事务是不支持隔离性和原子性 ------
Redis 过期策略
-
针对设置了expire过期时间的key,怎么样移除掉这些key
-
每一个key,都有一个定时器扫描。对CPU消耗比较大,因为如果在有很多设置了过期时间的key时,每个key都要开启一个定时器。对内存比较友好,只要key一失效,就马上会释放内存空间。
-
惰性移除key。不会定时扫描,但客户端访问这个key时候,如果发现这个key到期了,就移除。如果没有客户端访问,就不移除。对CPU友好,对内存不友好(有可能会有很多key在一定时间内得不到快速的清除)
-
定时扫描,是一种折中的方法。检查expire key的字段,定期地扫描期,将过期的key移除掉
-
内存淘汰策略
-
当redis中的内存不足的时候,就会根据内存淘汰的策略来清除对应的内存空间
-
LRU——优先清除最近最少访问的Key
-
推荐的策略:allkeys-lru
Redis的主从复制架构 ----- 高可用
-
Redis可以配置主从复制结构,主节点就是Master节点,从节点就是Slave,Slave会不断地从Master节点同步数据
-
一个Master节点可以对应多个Slave节点
-
Slave节点会向Master节点发送SYNC的请求,Master接收到SYNC请求之后,做两件事情:(初始化)
-
第一、以RDB的形式保存快照
-
第二、因为保存快照是比较耗时的,所以会将保存快照期间的命令缓存下来
-
当RDB保存完整后,就开始发送给Slave节点开始同步数据
-
一旦初始化完成,后续Master节点每接收一个写命令,就会立刻同步到Slave节点
-
-
主从复制结构的应用场景
-
备份容错(如果只有一个节点,会存在单点故障问题)
-
读写分离(读多写少的场景很适用),如果写操作很多,就得使用集群
-
从数据库持久化(可以将持久化的性能消耗移动到从节点)
-
Redis中的Sentinel架构 ----哨兵(解决主节点出问题)---高可用
Sentinel介绍
-
哨兵是主要用来保障Redis主从复制架构是高可用的,是能够自动进行主节点切换的
-
它可以监控主从复制中的节点,当主节点崩溃的时候,会自动进行切换
-
一般哨兵的配置节点数不能是1个,最好是有几个主从节点,就配置几个哨兵。不能哨兵自己出现单点故障
-
哨兵在Linux系统上是一个独立的进程,它的默认端口号是26379
-
当我们去查看操作哨兵的时候,需要指定客户端的连接端口号为:26379
配置哨兵
Redis的sentinel模式代码开发连接
Redis 集群 --- 大数据 ,高并发
-
Redis集群解决的问题
-
高可用
-
解决单机Redis内存是有限的问题(技术组件不是内存越多越好,因为内存配置得越高,例如JVM的Heap内存配置得很高后,就会导致内存碎片整理很耗时,垃圾回收会发生卡顿,导致集群的效率下降)
-
解决单机Redis网络受限的问题
-
-
分布式存储的重点——分区
-
MySQL——根据顺序分区的方式,例如:根据主键来进行分区(分库分表),一般是在Java web开发中会遇到
-
按照哈希取余的方式来进行分区(类似于MapReduce的默认分区策略)
-
问题:当分区的数量发生变化的时候,会导致key产生较大影响,原先分布在第一个节点上的数据,分区数量调整后,指定到了其他的分区
-
-
按照一致性Hash的方式来进行分区
-
是一个环状的Hash空间,它的分区算法是和哈希取余算法不一样的
-
首先将每一个分区的标号(0、1、2)进行算法计算,然后将计算出来的值,放入到环状的Hash空间空
-
再将key同样进行算法计算,然后将计算出来的值,同样也放入到环状的Hash空间中
-
最后,找到key在hash空间中距离自己位置最近的分区,放入到该分区中
-
这样,当分区的数量发生变化的时候,影响不会太大
-
-
Redis集群是使用槽的方式来进行分区的
-
现有有一个槽的空间(0-16383),需要将这些空间分布到不同的节点中
-
node1: 0 -3xxx
-
node2: 3xxx- 6xxx
-
...
-
有一个key,首先进行CRC16算法&16383 = 值,Redis会判断这个值应该在哪个槽中
-
-
###