Redis基础篇

初始Redis(从安装到使用)_Java


初始Redis

认识NoSQL

初始Redis(从安装到使用)_redis_02

SQL

  • 结构化(Structured)
  • 关联的(Relational)
  • SQL查询
  • SELECT id,name,age FROM tb_user WHERE id = 1
  • 事务
  • 满足ACID(原子性、一致性、隔离性、持久性)

NoSQL

  • 非结构化
  • 无关联的
  • 非SQL
  • Redis get user:1
  • 事务
  • BASE
  • 要么没有事务,要么无法全部满足ACID

初始Redis(从安装到使用)_Redis_03

Redis基本特性

  • 键值(key-value)型,value支持多种不同数据结构,功能丰富
  • 单线程,每个命令具备原子性,中途不会执行其他命令(指命令处理始终是单线程的,自6.x起改为多线程接收网络请求)
  • 低延迟,速度快(基于内存、IO多路复用、良好的编码)
  • 支持数据持久化
  • 支持主从、分片集群
  • 支持多语言客户端

安装Redis

  1. 安装依赖
yum install -y gcc tcl
  1. 找到 redis 官网下载压缩包

进入/user/local/src/,并上传压缩包

cd /usr/local/src/

初始Redis(从安装到使用)_Redis_04

  1. 进行解压
tar zxvf redis-7.0.11.tar.gz
  1. 进入 redis 目录
cd redis-7.0.11
  1. 编译 和 运行
make && make install

初始Redis(从安装到使用)_Redis_05

  1. 进入 /usr/local/src/查看文件
  2. 启动 redis
# 前台启动
redis-server

初始Redis(从安装到使用)_Java_06

  1. 设置后台启动
  • 备份 redis 的配置文件
cp redis.conf redis.conf.bck
  • 修改配置文件
# 监听的地址,默认是127.0.0.1,会导致只能在本地访问,修改为0.0.0.0则可以在任意Ip访问,生产环境不要设置0.0.0.0
bind 0.0.0.0
# 守护进程,修改为yes则可以在后台运行
demonize yes
# 密码,设置后访问Redis必须输入密码
requirepass 123321
  • 启动 redis
# 进入 redis 安装目录
cd /usr/local/src /redis-7.0.11
# 启动
redis-server redis.conf
  • 停止服务
# 利用redis-cli来执行 shutdown 命令,即可停止 redis 服务
# 因为之前配置了密码需要-u来指定密码
redis-cli -u 123321 shutdown

#或者使用kill
kill -9 【进程id】
  1. 设置开机自启
  1. 新建 redis.service
vim lib/systemd/system/redis.service
  1. 修改文件
[Unit]
Documentation=https://redis.io/
After=network.target

[Service]
Type=forking
ExecStart=/usr/local/bin/redis-server /usr/local/src/redis-7.0.11/redis.conf
PrivateTmp=true

[Install]
WantedBy=multi-user.target
  1. 重载系统服务
systemctl daemon-reload
  1. 服务指令
设置开机启动,启动redis

设置开机自启动:systemctl enable redis

关闭开机自动启动:systemctl disable redis

启动redis服务:systemctl start redis

停止服务:systemctl stop redis

重新加载redis配置文件:systemctl reload redis

查看所有已启动的服务:systemctl list-units --type=service

查看服务当前状态:systemctl status redis

初始Redis(从安装到使用)_redis_07

Redis常见命令

Redis的数据结构

Redis 提供了丰富的数据类型,常见的有五种数据类型:String(字符串),Hash(哈希),List(列表),Set(集合)、Zset(有序集合)

初始Redis(从安装到使用)_redis_08

初始Redis(从安装到使用)_redis_09

查看帮助

  1. 查看官方文档
    https://redis.io/commands/?group=string
  2. 在命令行使用 help 命令

如:help @string

初始Redis(从安装到使用)_redis_10

通用命令

初始Redis(从安装到使用)_redis_11

不同数据结构和操作命令

String类型

初始Redis(从安装到使用)_redis_12

  • set: set name zhangsan
  • get: get name
  • mset: mset name zhangsan age 18
    批量增加
  • mget: mget name age
  • 批量获取
  • incr: incr age
  • 执行一次自增1
  • incrby: incrby age 2
  • 自己设置自增整型
  • incrbyfloat: incrbyfloat age 0.8
  • 自己设置浮点类型数字自增
  • setnx: setnx name lisi
  • key 不存在执行,否则不执行
  • setex: setex name 20 lisi
key的结构

初始Redis(从安装到使用)_Java_13

解决使用 Redis 时 key冲突:添加前缀

初始Redis(从安装到使用)_Java_14

127.0.0.1:6379> set allen:sites:1 '{ "name":"菜鸟教程" , "url":"www.runoob.com" }' 

127.0.0.1:6379> set allen:sites:2 '{ "name":"google" , "url":"www.google.com" }' 
  • 效果(在图形化界面)

初始Redis(从安装到使用)_Java_15

  • 弊端:无法直接修改字符串中的字段值

Hash类型

初始Redis(从安装到使用)_redis_16

Hash类型常见的命令

初始Redis(从安装到使用)_Java_17

  • HSET
# 添加单个
HSET heima:user:1 name allen
(integer) 1
HSET heima:user:1 age 19
(integer) 1
  • HGET
# 获取单个
127.0.0.1:6379> HGET heima:user:1 name
"allen"
127.0.0.1:6379> HGET heima:user:1 age
"19"
  • HMSET
# 批量添加数据
127.0.0.1:6379> HMSET heima:user2 name Mikasa age 19 gender 0
OK
  • HMGET
# 批量获取数据
127.0.0.1:6379> HMGET heima:user:2 name age gender
1) "Mikasa"
2) "19"
3) "0"
  • HGETALL
# 获取所有key - value 相当于 Java的 entry
127.0.0.1:6379> HGETALL heima:user:2
1) "name"
2) "Mikasa"
3) "age"
4) "19"
5) "gender"
6) "0"
  • HKEYS
# 获取所有key值 相当于Java的 getKeys
127.0.0.1:6379> HKEYS heima:user:1
1) "name"
2) "age"
  • HVALS
# 获取所有value 相当于 Java的 getValues
127.0.0.1:6379> HVALS heima:user:2
1) "Mikasa"
2) "19"
3) "0"
  • HINCRBY
# 让一个key 的字段指定整型自增
127.0.0.1:6379> HINCRBY heima:user:1 age 1
(integer) 20
127.0.0.1:6379> HINCRBY heima:user:1 age 1
(integer) 21
127.0.0.1:6379> HINCRBY heima:user:1 age 1
(integer) 22
  • HSETNX
# 有gender字段,不执行,未修改
127.0.0.1:6379> HSETNX heima:user:2 gender 1
(integer) 0
127.0.0.1:6379> HGET heima:user:2 gender
"0"

# 无gender字段,执行
127.0.0.1:6379> HSETNX heima:user:1 gender 1
(integer) 1
127.0.0.1:6379> HGET heima:user:1 gender
"1"

List类型

Redis中的LIst类型和Java中的LinkedList类似,可以看做双向链表,实际比双向链表更加复杂。支持正向检索和反向检索

特征:

  • 有序
  • 元素可重复
  • 插入删除快
  • 查询速度一般

List类型常见命令

初始Redis(从安装到使用)_Redis_18

LRANGE key 1,2 取1-2范围的元素

初始Redis(从安装到使用)_Java_19

  • LPUSH
# 在左侧插入
127.0.0.1:6379> LPUSH users 1 2 3
(integer) 3
  • RPUSH
# 在右侧插入
127.0.0.1:6379> RPUSH users 4 5 6
(integer) 6
  • LPOP
# 从左侧出
127.0.0.1:6379> LPOP users
"3"
127.0.0.1:6379> LPOP users
"2"
  • RPOP
# 从右侧出
127.0.0.1:6379> RPOP users
"6"
127.0.0.1:6379> RPOP users
"5"
  • LRANGE
# 获取范围内的元素 下标从 0 开始
127.0.0.1:6379> LRANGE users 0 1 
1) "1"
2) "4"
  • BLPOP和BRPOP
# user2 不存在 进入等待,阻塞时间无限
127.0.0.1:6379> BLPOP user2 0

#user2 中下标为1的元素不存在 进入阻塞,阻塞时间为100s
127.0.0.1:6379> BRPOP user2 1 100

思考:

如何利用List结构模拟一个栈?

  • 入口和出口在同一边(喝酒喝吐)

如何利用List结构模拟一个队列?

  • 入口和出口在不同边(喝酒喝饱上厕所)

如何利用List结构模拟一个阻塞队列?

  • 入口和出口在不同边
  • 出队时采用BLPOP或BRPOP

Set类型

Redis和Set结构与Java中的HashSet类似,可以看作是一个value为null的HashMap。因为也是一个hash表,因此具备HashSet类似的特性:

  • 无序
  • 元素不可重复
  • 查找快
  • 支持交集、并集、差集等功能

适用于交友型、社交型的应用(好友列表、共同列表、关注)

Set类型常见命令

  • 单个集合操作

初始Redis(从安装到使用)_Redis_20

  • 多个集合直接操作

初始Redis(从安装到使用)_Redis_21

  • SINTER:交集
    上图中的B C
  • SDIFF:差集
  • s1 DIFF s2 为 A
  • s2 DIFF s1 为 D
  • SUNION :并集
    上图中的A B C D,如果并集过程中有相同的会合并为一个

练习:

初始Redis(从安装到使用)_Java_22

# 创建张三的好友
127.0.0.1:6379> SADD zs lisi wangwu zhaoliu
(integer) 3

# 创建李四的好友
127.0.0.1:6379> SADD ls wangwu mazi ergou
(integer) 3

# 计算张三的好友有几人
127.0.0.1:6379> SCARD zs
(integer) 3

# 计算张三和李四有哪些共同好友
127.0.0.1:6379> SINTER zs ls
1) "wangwu"

#查询哪些是张三的好友却不是李四的好友
127.0.0.1:6379> SDIFF zs ls
1) "zhaoliu"
2) "lisi"

# 查询张三和李四的好友共有哪些人
127.0.0.1:6379> SUNION zs ls
1) "zhaoliu"
2) "wangwu"
3) "lisi"
4) "ergou"
5) "mazi"

# 判断李四是否是张三的好友
127.0.0.1:6379> SISMEMBER zs lisi
(integer) 1

# 判断张三是否是李四的好友
127.0.0.1:6379> SISMEMBER ls zhangsan
(integer) 0

# 将李四从张三的好友列表中移除
127.0.0.1:6379> SREM zs lisi
(integer) 1

SortedSet类型

Redis的SortedSet是一个可排序的set集合,与Java中的TreeSet有些类似,但底层数据结构却差别很大。SortedSet中的每一个元素都带有一个score属性,可以基于score属性对元素排序,底层的实现是一个跳表(SkipList)加hash表。

SortedSet具备下列特征:

  • 可排序
  • 元素不重复
  • 查询速度快

因为SortedSet的可排序特征,经常被用来实现排行榜这些功能。

案例练习:

将班级的下列学生得分存入Redis的SortedSet中:

Jack 85,Lucy 89,Rose 82,Tom 95,Jerry 78,Amy 92,Miles 76,实现下列功能

初始Redis(从安装到使用)_Redis_23

  • 添加学生得分
127.0.0.1:6379> ZADD stus 85 Jack 89 Lucy 82 Rose 95 Tom 78 Jerry 92 Amy 76 Miles
(integer) 7
  • 删除Tom同学
127.0.0.1:6379> ZREM stus Tom
(integer) 1
  • 获取Amy同学的分数
127.0.0.1:6379> ZSCORE stus Amy
"92"
  • 获取Rose同学的排名
127.0.0.1:6379> ZRANK stus Rose
(integer) 2
  • 查询80分以下有几个学生
# 此处范围时包含79的
127.0.0.1:6379> ZCOUNT stus 0 79
(integer) 2
  • 给Amy同学加2分
127.0.0.1:6379> ZINCRBY stus 2 Amy
"94"
  • 查出成绩前3名的同学
127.0.0.1:6379> ZREVRANGE stus 0 2
1) "Amy"
2) "Lucy"
3) "Jack"
  • 查出成绩80分以下的所有同学
127.0.0.1:6379> ZRANGEBYSCORE stus 0 79
1) "Miles"
2) "Jerry"

Redis的Java客户端

Redis的Java客户端官网:https://redis.io/resources/clients/#java

初始Redis(从安装到使用)_redis_24

Jedis客户端

Jedis官网:https://github.com/redis/jedis

使用:

  1. 导入依赖
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>4.3.0</version>
</dependency>
  1. 建立连接
@BeforeEach
void setUp() {
     // 1. 建立连接 指定ip地址和端口号
     jedis = new Jedis("192.168.6.100",6379);
     // 2. 输入密码
     jedis.auth("password");
     // 3. 选择库
     jedis.select(0);
}
  1. 进行测试
@Test
void testString() {
     String result = jedis.set("name", "Allen");
     System.out.println("result = " + result); // result = OK
     String name = jedis.get("name");
     System.out.println("name = " + name); // name = Allen
}

@Test
void testHash() {
     jedis.hset("user:allen:1","name","TomCat");
     jedis.hset("user:allen:1","color","Write");

     Map<String, String> map = jedis.hgetAll("user:allen:1");
     Set<Map.Entry<String, String>> entries = map.entrySet();
     for (Map.Entry<String, String> entry : entries) {
         System.out.println(entry.getKey() + ": " + entry.getValue() );
     }
}
  1. 关闭连接
@AfterEach
void close() {
     if(jedis != null){
            jedis.close();
     }
}

Jedis连接池

public class JedisConnectionFactory {
    private static final JedisPool jedisPoll;
    static {
        JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
        // 最大连接
        jedisPoolConfig.setMaxTotal(8);
        // 最大空闲连接
        jedisPoolConfig.setMaxIdle(8);
        // 最小空闲连接
        jedisPoolConfig.setMinIdle(0);
        // 最长等待时间
   jedisPoolConfig.setMaxWait(Duration.ofSeconds(1));
        jedisPoll = new JedisPool(jedisPoolConfig,
                "192.168.6.100", 6379, 1000, "password");
    }

    public static Jedis getJedis() {
        return jedisPoll.getResource();
    }
}

SpringDataRedis客户端

SpringData时Spring中数据操作的模块,包含对各种数据库的继承,其中Redis的继承模块就叫做SpringDataRedis。

官网地址:https://spring.io/projects/spring-data-redis

初始Redis(从安装到使用)_Redis_25

SpringDataRedis快速入门

SpringDataRedis中提供了RedisTemplate工具类,其中封装了各种对Redis的操作。并且将不同数据类型的操作API封装到了不同类型中。

初始Redis(从安装到使用)_Java_26

SpringDataRedis基于SpringBoot,默认引入lettuce依赖,如果有jedis需求需要引入jedis依赖

  1. 引入相关依赖
<dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

<dependency>
       <groupId>org.apache.commons</groupId>
       <artifactId>commons-pool2</artifactId>
       <version>2.11.1</version>
</dependency>
  1. 在application.yaml中写redis相关配置信息
spring:
  redis:
    host: 192.168.6.100
    port: 6379
    password: password
    lettuce:
      pool:
        max-active: 8
        max-idle: 8
        min-idle: 0
        max-wait: 1000ms
  1. 测试
@SpringBootTest
class RedisDemoApplicationTests {
    @Resource
    private RedisTemplate redisTemplate;

    @Test
    void contextLoads() {
        redisTemplate.opsForValue().set("name","ShowMaker");
        Object name = redisTemplate.opsForValue().get("name");
        System.out.println("name = " + name); // name = ShowMaker
    }

}

SrpingDataRedis的序列化方式

初始Redis(从安装到使用)_Java_27

自定义RedisTemplate

我们可以自定义RedisTemplate的序列号方式,代码如下:

@Configuration
public class RedisConfig {

    @Bean
    public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        // 创建RedisTemplate
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        // 设置连接工厂
        template.setConnectionFactory(redisConnectionFactory);
        // 创建JSON序列化工具
        GenericJackson2JsonRedisSerializer jackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
        template.setConnectionFactory(redisConnectionFactory);
        // 设置key的序列化
        template.setKeySerializer(RedisSerializer.string());
        template.setHashKeySerializer(RedisSerializer.string());
        // 设置value 的序列化
        template.setValueSerializer(jackson2JsonRedisSerializer);
        template.setHashValueSerializer(jackson2JsonRedisSerializer);
        // 返回
        return template;
    }
}

测试:

@Test
void objTest() {
     User user = new User("张三",19);
     redisTemplate.opsForValue().set("USER",user);
     User res = (User) redisTemplate.opsForValue().get("USER");
     System.out.println("res = " + res);
}
  • 写入对象序列化转为JSON,读出反序列化。
  • 存在问题:为了反序列化时指定对象类型,JSON序列化器会将类的class类型写入json结果中,存入Redis,对带来额外的开销。
手动序列化和反序列化(推荐使用)

为了节省内存空间,我们并不会使用JSON序列化器来处理value,而是统一使用String序列化器,要求只能存储String类型的key和value。当需要存储Java对象时,手动完成对象的序列化和反序列化。

初始Redis(从安装到使用)_Redis_28

@SpringBootTest
public class RedisStringTest {
    @Resource
    private StringRedisTemplate stringRedisTemplate;
    // SpringMVC默认使用的字符串序列化工具
    private static final ObjectMapper mapper = new ObjectMapper();
    @Test
    void redisStringTest() throws JsonProcessingException {
        User user = new User("王五", 18);
        // 进行序列化
        String json = mapper.writeValueAsString(user);
        // 写入
        stringRedisTemplate.opsForValue().set("UserObj",json);
        // 读取
        String userObj = stringRedisTemplate.opsForValue().get("UserObj");
        //反序列化
        User res = mapper.readValue(userObj, User.class);
        System.out.println("res = " + res);
    }
    
@Test
void hashTest() {
     stringRedisTemplate.opsForHash().put("user:200","name","SouthWind");
     stringRedisTemplate.opsForHash().get("user:200","name");
     stringRedisTemplate.opsForHash().put("user:200","age","18");
 Map<Object, Object> entries = stringRedisTemplate.opsForHash().entries("user:200");
     System.out.println("entries = " + entries);
    }
}