Redis超全精讲!(二)

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 Tair(兼容Redis),内存型 2GB
日志服务 SLS,月写入数据量 50GB 1个月
简介: 课程目标能够掌握Redis不同数据类型操作能够使用Java API操作Redis能够理解Redis的两种持久化方式能够理解Redis的主从复制架构能够理解Redis的Sentinel架构能够理解Redis集群架构

5. Redis Java API操作


Redis不仅可以通过命令行进行操作,也可以通过JavaAPI操作,通过使用Java API来对Redis数据库中的各种数据类型操作。

离线架构和实时架构流程:

21.png

5.1 创建maven工程并导入依赖

5.1.1 创建Maven工程

22.png

5.1.2 导入POM依赖

<dependencies>
    <dependency>
        <groupId>redis.clients</groupId>
        <artifactId>jedis</artifactId>
        <version>2.9.0</version>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.testng</groupId>
        <artifactId>testng</artifactId>
        <version>6.14.3</version>
        <scope>test</scope>
    </dependency>
</dependencies>
<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.0</version>
            <configuration>
                <source>1.8</source>
                <target>1.8</target>
                <encoding>UTF-8</encoding>
                <!--    <verbal>true</verbal>-->
            </configuration>
        </plugin>
    </plugins>
</build>


5.2 创建包结构和类

在test目录创建 cn.itcast.redis.api_test 包结构

创建RedisTest类


5.3 连接以及关闭redis客户端

因为后续测试都需要用到Redis连接,所以,我们先创建一个JedisPool用于获取Redis连接。此处,我们基于TestNG来测试各类的API。使用@BeforeTest在执行测试用例前,创建Redis连接池。使用@AfterTest在执行测试用例后,关闭连接池。


实现步骤:


创建JedisPoolConfig配置对象,指定最大空闲连接为10个、最大等待时间为3000毫秒、最大连接数为50、最小空闲连接5个

创建JedisPool

使用@Test注解,编写测试用例,查看Redis中所有的key

a) 从Redis连接池获取Redis连接

b) 调用keys方法获取所有的key

c) 遍历打印所有key

package cn.itcast.redis.api_test;
import org.testng.annotations.AfterTest;
import org.testng.annotations.BeforeTest;
import org.testng.annotations.Test;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
import java.util.List;
import java.util.Set;
/**
 * @author :caizhengjie
 * @description:
 * 1. 创建JedisPoolConfig配置对象,指定最大空闲连接为10个、最大等待时间为3000毫秒、最大连接数为50、最小空闲连接5个
 * 2. 创建JedisPool
 * 3. 使用@Test注解,编写测试用例,查看Redis中所有的key
 * a) 从Redis连接池获取Redis连接
 * b) 调用keys方法获取所有的key
 * c) 遍历打印所有key
 * @date :2021/1/26 11:28 上午
 */
public class RedisTest {
    private JedisPool jedisPool;
    @BeforeTest
    public void redisConnectionPool(){
        // 创建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,"bigdata-pro-m07");
    }
    @Test
    public void keysTest(){
        // 从redis连接池获取redis连接
        Jedis jedis = jedisPool.getResource();
        // 调用keys方法获取所有的key
        Set<String> keySet = jedis.keys("*");
        for (String key : keySet){
            System.out.println(key);
        }
    }
    @AfterTest
    public void afterTest(){
        // 关闭连接池
        jedisPool.close();
    }
}

注意:


操作Redis一般要使用Jedis的连接池,这样可以有效的复用连接资源

在IDEA中,有时候提示可能不完整,其实Jedis连接池,可以指定端口号


5.4 操作string类型数据

添加一个string类型数据,key为pv,用于保存pv的值,初始值为0

查询该key对应的数据

修改pv为1000

实现整形数据原子自增操作 +1

实现整形该数据原子自增操作 +1000

/**
 * 操作string类型数据
 */
@Test
public void stringTest(){
    // 从redis连接池获取redis连接
    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"));
}

注意:


Redis操作string其实和SHELL命令是一样

将来在编写Flink程序/Spark Streaming程序操作Redis的时候,注意操作完Redis之后,执行close,将连接返回到连接池。


5.5 操作hash列表类型数据

往Hash结构中添加以下商品库存

a) iphone11 => 10000

b) macbookpro => 9000

获取Hash中所有的商品

新增3000个macbookpro库存

删除整个Hash的数据

/**
 * 操作hash列表类型数据
 */
@Test
public void hashTest(){
    // 从redis连接池获取redis连接
    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();
}

注意:


当我们后续在编写Flink、Spark Streaming流处理程序使用Java操作Redis时候,涉及到一些数字的累加

一定要使用incr、hincrBy


5.6 操作list类型数据

向list的左边插入以下三个手机号码:18511310001、18912301231、18123123312

从右边移除一个手机号码

获取list所有的值

    /**
     * 操作list类型数据
     */
    @Test
    public void listTest(){
        // 从redis连接池获取redis连接
        Jedis jedis = jedisPool.getResource();
        // 1.向list的左边插入以下三个手机号:18511310001、18511310002、18511310003
        jedis.lpush("tel_list","18511310001","18511310002","18511310003");
        // 2.从右面移除一个手机号码
        jedis.rpop("tel_list");
        // 3.获取list所有的值
        List<String> telList = jedis.lrange("tel_list",0,-1);
        for (String tel : telList) {
            System.out.println(tel);
        }
    }

注意:


List可以用来存储重复的元素,而且是有序的

获取所有的元素,lrange(key, 0, -1)


5.7 操作set类型的数据


使用set来保存uv值,为了方便计算,将用户名保存到uv中。


往一个set中添加页面 page1 的uv,用户user1访问一次该页面

user2访问一次该页面

user1再次访问一次该页面

最后获取 page1的uv值

    /**
     * 操作set类型的数据
     */
    @Test
    public void setTest(){
        // 从redis连接池获取redis连接
        Jedis jedis = jedisPool.getResource();
        // 求UV就是求独立有多少个不重复
        // 1.往一个set中添加页面page1的uv,用户user1访问一次该页面
        jedis.sadd("uv","user1");
        // 2.user2访问一次该页面
        jedis.sadd("uv","user2");
        // 3.user1访问一次该页面
        jedis.sadd("uv","user1");
        // 最后获取page1的uv值
        System.out.println("uv:" + jedis.scard("uv"));
        jedis.close();
    }

注意:


计算UV主要是去重

将来所有的一些要求高效率去重的业务场景,都可以使用Set操作


6. Redis的持久化


由于redis是一个内存数据库,所有的数据都是保存在内存当中的,内存当中的数据极易丢失,所以redis的数据持久化就显得尤为重要,在redis当中,提供了两种数据持久化的方式,分别为RDB以及AOF,且Redis默认开启的数据持久化方式为RDB方式。


6.1 RDB持久化方案

6.1.1 介绍

Redis会定期保存数据快照至一个rdb文件中,并在启动时自动加载rdb文件,恢复之前保存的数据。可以在配置文件中配置Redis进行快照保存的时机:

save [seconds] [changes]

意为在seconds秒内如果发生了changes次数据修改,则进行一次RDB快照保存,例如

save 60 100

会让Redis每60秒检查一次数据变更情况,如果发生了100次或以上的数据变更,则进行RDB快照保存。可以配置多条save指令,让Redis执行多级的快照保存策略。Redis默认开启RDB快照。也可以通过SAVE或者BGSAVE命令手动触发RDB快照保存。 SAVE 和 BGSAVE 两个命令都会调用 rdbSave 函数,但它们调用的方式各有不同:


SAVE 直接调用 rdbSave ,阻塞 Redis 主进程,直到保存完成为止。在主进程阻塞期间,服务器不能处理客户端的任何请求。

BGSAVE 则 fork 出一个子进程,子进程负责调用 rdbSave ,并在保存完成之后向主进程发送信号,通知保存已完成。 Redis 服务器在BGSAVE 执行期间仍然可以继续处理客户端的请求。

6.1.2 RDB方案优点

对性能影响最小。如前文所述,Redis在保存RDB快照时会fork出子进程进行,几乎不影响Redis处理客户端请求的效率。

每次快照会生成一个完整的数据快照文件,所以可以辅以其他手段保存多个时间点的快照(例如把每天0点的快照备份至其他存储媒介中),作为非常可靠的灾难恢复手段。

使用RDB文件进行数据恢复比使用AOF要快很多

6.1.3 RDB方案缺点

快照是定期生成的,所以在Redis crash时或多或少会丢失一部分数据

如果数据集非常大且CPU不够强(比如单核CPU),Redis在fork子进程时可能会消耗相对较长的时间,影响Redis对外提供服务的能力

6.1.4 RDB配置

修改redis的配置文件

cd /opt/modules/redis-3.2.8
vim redis.conf
# 第202行
save 900 1
save 300 10
save 60 10000
save 5 1

这三个选项是redis的配置文件默认自带的存储机制。表示每隔多少秒,有多少个key发生变化就生成一份dump.rdb文件,作为redis的快照文件

例如:save 60 10000 表示在60秒内,有10000个key发生变化,就会生成一份redis的快照


重新启动redis服务

每次生成新的dump.rdb都会覆盖掉之前的老的快照

ps -ef | grep redis
bin/redis-cli -h bigdata-pro-m07 shutdown
bin/redis-server redis.conf

6.2 AOF持久化方案

6.2.1 介绍

采用AOF持久方式时,Redis会把每一个写请求都记录在一个日志文件里。在Redis重启时,会把AOF文件中记录的所有写操作顺序执行一遍,确保数据恢复到最新。

6.2.2 开启AOF

AOF默认是关闭的,如要开启,进行如下配置:

# 第594行
appendonly yes

6.2.3 配置AOF

AOF提供了三种fsync配置:always/everysec/no,通过配置项[appendfsync]指定:


appendfsync no:不进行fsync,将flush文件的时机交给OS决定,速度最快

appendfsync always:每写入一条日志就进行一次fsync操作,数据安全性最高,但速度最慢

appendfsync everysec:折中的做法,交由后台线程每秒fsync一次


6.2.4 AOF rewrite

随着AOF不断地记录写操作日志,因为所有的写操作都会记录,所以必定会出现一些无用的日志。大量无用的日志会让AOF文件过大,也会让数据恢复的时间过长。不过Redis提供了AOF rewrite功能,可以重写AOF文件,只保留能够把数据恢复到最新状态的最小写操作集。


AOF rewrite可以通过BGREWRITEAOF命令触发,也可以配置Redis定期自动进行:

auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb

Redis在每次AOF rewrite时,会记录完成rewrite后的AOF日志大小,当AOF日志大小在该基础上增长了100%后,自动进行AOF rewrite

auto-aof-rewrite-min-size最开始的AOF文件必须要触发这个文件才触发,后面的每次重写就不会根据这个变量了。该变量仅初始化启动Redis有效。

6.2.5 AOF优点

最安全,在启用appendfsync为always时,任何已写入的数据都不会丢失,使用在启用appendfsync everysec也至多只会丢失1秒的数据

AOF文件在发生断电等问题时也不会损坏,即使出现了某条日志只写入了一半的情况,也可以使用redis-check-aof工具轻松修复

AOF文件易读,可修改,在进行某些错误的数据清除操作后,只要AOF文件没有rewrite,就可以把AOF文件备份出来,把错误的命令删除,然后恢复数据。

6.2.6 AOF的缺点

AOF文件通常比RDB文件更大

性能消耗比RDB高

数据恢复速度比RDB慢

Redis的数据持久化工作本身就会带来延迟,需要根据数据的安全级别和性能要求制定合理的持久化策略:


AOF + fsync always的设置虽然能够绝对确保数据安全,但每个操作都会触发一次fsync,会对Redis的性能有比较明显的影响

AOF + fsync every second是比较好的折中方案,每秒fsync一次

AOF + fsync never会提供AOF持久化方案下的最优性能

使用RDB持久化通常会提供比使用AOF更高的性能,但需要注意RDB的策略配置


6.3 RDB or AOF

每一次RDB快照和AOF Rewrite都需要Redis主进程进行fork操作。fork操作本身可能会产生较高的耗时,与CPU和Redis占用的内存大小有关。根据具体的情况合理配置RDB快照和AOF Rewrite时机,避免过于频繁的fork带来的延迟


Redis在fork子进程时需要将内存分页表拷贝至子进程,以占用了24GB内存的Redis实例为例,共需要拷贝48MB的数据。在使用单Xeon 2.27Ghz的物理机上,这一fork操作耗时216ms。

1.jpeg


7. Redis 高级使用


7.1 Redis 事务

7.1.1 Redis事务简介

Redis 事务的本质是一组命令的集合。事务支持一次执行多个命令,一个事务中所有命令都会被序列化。在事务执行过程,会按照顺序串行化执行队列中的命令,其他客户端提交的命令请求不会插入到事务执行命令序列中。


总结说:Redis事务就是一次性、顺序性、排他性的执行一个队列中的一系列命令


Redis事务没有隔离级别的概念:


批量操作在发送 EXEC 命令前被放入队列缓存,并不会被实际执行,也就不存在事务内的查询要看到事务里的更新,事务外查询不能看到。

Redis不保证原子性:


Redis中,单条命令是原子性执行的,但事务不保证原子性,且没有回滚。事务中任意命令执行失败,其余的命令仍会被执行。

一个事务从开始到执行会经历以下三个阶段:

第一阶段:开始事务

第二阶段:命令入队

第三阶段、执行事务

Redis事务相关命令:


MULTI:开启事务,redis会将后续的命令逐个放入队列中,然后使用EXEC命令来原子化执行这个命令队列

EXEC:执行事务中的所有操作命令

DISCARD:取消事务,放弃执行事务块中的所有命令

WATCH:监视一个或多个key,如果事务在执行前,这个key(或多个key)被其他命令修改,则事务被中断,不会执行事务中的任何命令

UNWATCH:取消WATCH对所有key的监视

7.1.2 Redis事务演示

MULTI开始一个事务:给k1、k2分别赋值,在事务中修改k1、k2,执行事务后,查看k1、k2值都被修改。

bigdata-pro-m07:6379> set k1 v1
OK
bigdata-pro-m07:6379> set k2 v2
OK
bigdata-pro-m07:6379> multi
OK
bigdata-pro-m07:6379> set k1 11
QUEUED
bigdata-pro-m07:6379> set k2 22
QUEUED
bigdata-pro-m07:6379> exec
1) OK
2) OK
bigdata-pro-m07:6379> get k1
"11"
bigdata-pro-m07:6379> get k2
"22"
  1. 事务失败处理:语法错误(编译器错误),在开启事务后,修改k1值为11,k2值为22,但k2语法错误,最终导致事务提交失败,k1、k2保留原值。
bigdata-pro-m07:6379> flushdb
OK
bigdata-pro-m07:6379> keys *
(empty list or set)
bigdata-pro-m07:6379> set k1 v1
OK
bigdata-pro-m07:6379> set k2 v2
OK
bigdata-pro-m07:6379> multi
OK
bigdata-pro-m07:6379> set k1 11
QUEUED
bigdata-pro-m07:6379> sets k2 22
(error) ERR unknown command 'sets'
bigdata-pro-m07:6379> exec
(error) EXECABORT Transaction discarded because of previous errors.
bigdata-pro-m07:6379> get k1
"v1"
bigdata-pro-m07:6379> get k2
"v2"

Redis类型错误(运行时错误),在开启事务后,修改k1值为11,k2值为22,但将k2的类型作为List,在运行时检测类型错误,最终导致事务提交失败,此时事务并没有回滚,而是跳过错误命令继续执行, 结果k1值改变、k2保留原值。

bigdata-pro-m07:6379> flushdb
OK
bigdata-pro-m07:6379> keys *
(empty list or set)
bigdata-pro-m07:6379> set k1 v1
OK
bigdata-pro-m07:6379> set k2 v2
OK
bigdata-pro-m07:6379> multi
OK
bigdata-pro-m07:6379> set k1 11
QUEUED
bigdata-pro-m07:6379> lpush k2 22
QUEUED
bigdata-pro-m07:6379> exec
1) OK
2) (error) WRONGTYPE Operation against a key holding the wrong kind of value
bigdata-pro-m07:6379> get k1
"11"
bigdata-pro-m07:6379> get k2
"v2"

DISCARD取消事务

bigdata-pro-m07:6379> multi
OK
bigdata-pro-m07:6379> set k6 v6
QUEUED
bigdata-pro-m07:6379> set k7 v7
QUEUED
bigdata-pro-m07:6379> discard
OK
bigdata-pro-m07:6379> get k6
(nil)
bigdata-pro-m07:6379> get k7
(nil)

7.1.3 为什么Redis不支持事务回滚?

多数事务失败是由语法错误或者数据结构类型错误导致的,语法错误说明在命令入队前就进行检测的,而类型错误是在执行时检测的,Redis为提升性能而采用这种简单的事务,这是不同于关系型数据库的,特别要注意区分。Redis之所以保持这样简易的事务,完全是为了保证高并发下的核心问题——性能。


7.2 Redis 过期策略

Redis是key-value数据库,可以设置Redis中缓存的key的过期时间。Redis的过期策略就是指当Redis中缓存的key过期了,Redis如何处理。

过期策略通常有以下三种:


定时过期

每个设置过期时间的key都需要创建一个定时器,到过期时间就会立即清除。该策略可以立即清除过期的数据,对内存很友好;但是会占用大量的CPU资源去处理过期的数据,从而影响缓存的响应时间和吞吐量。

惰性过期

只有当访问一个key时,才会判断该key是否已过期,过期则清除。该策略可以最大化地节省CPU资源,却对内存非常不友好。 极端情况可能出现大量的过期key没有再次被访问,从而不会被清除,占用大量内存。

定期过期

每隔一定的时间,会扫描一定数量的数据库的expires字典中一定数量的key,并清除其中已过期的key。该策略是前两者的一个折中方案。通过调整定时扫描的时间间隔和每次扫描的限定耗时,可以在不同情况下使得CPU和内存资源达到最优的平衡效果。


7.3 内存淘汰策略

Redis的内存淘汰策略是指在Redis的用于缓存的内存不足时,怎么处理需要新写入且需要申请额外空间的数据,在Redis的配置文件中描述如下:

# MAXMEMORY POLICY: how Redis will select what to remove when maxmemory
# is reached. You can select among five behaviors:
#最大内存策略:当到达最大使用内存时,你可以在下面5种行为中选择,Redis如何选择淘汰数据库键
#当内存不足以容纳新写入数据时
# volatile-lru -> remove the key with an expire set using an LRU algorithm
# volatile-lru :在设置了过期时间的键空间中,移除最近最少使用的key。这种情况一般是把 redis 既当缓存,又做持久化存储的时候才用。
# allkeys-lru -> remove any key according to the LRU algorithm
# allkeys-lru : 移除最近最少使用的key (推荐)
# volatile-random -> remove a random key with an expire set
# volatile-random : 在设置了过期时间的键空间中,随机移除一个键,不推荐
# allkeys-random -> remove a random key, any key
# allkeys-random : 直接在键空间中随机移除一个键,弄啥叻
# volatile-ttl -> remove the key with the nearest expire time (minor TTL)
# volatile-ttl : 在设置了过期时间的键空间中,有更早过期时间的key优先移除 不推荐
# noeviction -> don't expire at all, just return an error on write operations
# noeviction : 不做过键处理,只返回一个写操作错误。 不推荐
# Note: with any of the above policies, Redis will return an error on write
#       operations, when there are no suitable keys for eviction.
# 上面所有的策略下,在没有合适的淘汰删除的键时,执行写操作时,Redis 会返回一个错误。下面是写入命令:
#       At the date of writing these commands are: set setnx setex append
#       incr decr rpush lpush rpushx lpushx linsert lset rpoplpush sadd
#       sinter sinterstore sunion sunionstore sdiff sdiffstore zadd zincrby
#       zunionstore zinterstore hset hsetnx hmset hincrby incrby decrby
#       getset mset msetnx exec sort
# 过期策略默认是:
# The default is:
# maxmemory-policy noeviction

实际项目中设置内存淘汰策略:maxmemory-policy allkeys-lru,移除最近最少使用的key。






相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
缓存 NoSQL 应用服务中间件
|
6月前
|
NoSQL 关系型数据库 MySQL
redis 入门01
redis 入门01
38 0
|
缓存 NoSQL 关系型数据库
redis系列1-入门
redis系列1-入门
42 0
|
存储 监控 负载均衡
Redis 从入门到弃坑
Redis 从入门到弃坑
118 0
|
存储 缓存 NoSQL
(三)、Redis入门
(三)、Redis入门
|
消息中间件 SQL NoSQL
Redis初入门(一)
本文站在入门者的角度简单介绍了一下Redis,并列举了部分常用操作
|
消息中间件 存储 缓存
Redis入门(一)
Redis入门
247 1
|
消息中间件 存储 缓存
redis 入门-redis 简介| 学习笔记
快速学习 redis 入门-redis 简介
|
存储 SQL 缓存
Redis超全精讲!(一)
课程目标 能够掌握Redis不同数据类型操作 能够使用Java API操作Redis 能够理解Redis的两种持久化方式 能够理解Redis的主从复制架构 能够理解Redis的Sentinel架构 能够理解Redis集群架构
Redis超全精讲!(一)