Redis知识点2

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的主从复制架构 ----- 高可用

  1. Redis可以配置主从复制结构,主节点就是Master节点,从节点就是Slave,Slave会不断地从Master节点同步数据

  2. 一个Master节点可以对应多个Slave节点

  3. Slave节点会向Master节点发送SYNC的请求,Master接收到SYNC请求之后,做两件事情:(初始化)

    1. 第一、以RDB的形式保存快照

    2. 第二、因为保存快照是比较耗时的,所以会将保存快照期间的命令缓存下来

    3. 当RDB保存完整后,就开始发送给Slave节点开始同步数据

    4. 一旦初始化完成,后续Master节点每接收一个写命令,就会立刻同步到Slave节点

  4. 主从复制结构的应用场景

    1. 备份容错(如果只有一个节点,会存在单点故障问题)

    2. 读写分离(读多写少的场景很适用),如果写操作很多,就得使用集群

    3. 从数据库持久化(可以将持久化的性能消耗移动到从节点)

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会判断这个值应该在哪个槽中

###

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值