视频学习:黑马程序员Java项目实战瑞吉外卖

部分内容参考:Kyle’s Blog

Redis入门

Redis简介

Redis是一个开源的内容中的数据结构存储系统,它可以用作:数据库、缓存和消息中间件。官网:https://redis.io

  • Redis是用C语言开发的一个开源的、高性能的键值对(key-value)数据库,官方提供的数据是可以达到100000+的QPS(每秒内查询次数)。它存储的value类型比较丰富,也被称为结构化NoSql数据库

  • NoSql (Not Only Sql),不仅仅是SQL,泛指非关系型数据库,NoSql数据库并不是要取代关系型数据库,而是关系型数据库的补充

    关系型数据库(RDBMS):MySQL、Oracl、DB2、SQLServer
    非关系型数据库(NoSql):Redis、Mongo DB、MemCached

  • Redis应用场景:缓存、消息队列、任务队列、分布式锁

下载与安装

Windows安装

下载对应版本的.zip压缩包,直接解压

Linux安装

  1. 将Redis安装包上传到Linux (FinalShell等工具)
  2. 解压安装包:tar -zxvf redis-xxx.tar.gz -C /usr/local,路径通常解压到 /usr/local
  3. 安装Redis的依赖环境gcc,命令:yum install gcc-c++
  4. 进入/usr/local/redis根目录,进行编译:make
  5. 进入redis的src目录,进行安装:make install

服务启动与停止

  • Linux启动与停止

    • 进入到/src目录下,执行redis-server即可启动服务,默认端口号为6379

      1
      ./redis-server
  • Linux设置后台运行

    • 进入到redis根目录下,修改配置redis.conf文件

      1
      vim redis.config

      在文件中进行关键字搜索:直接输入/关键字然后enter即可。类似ctrl+f

    • 找到daemonize on字段,将其修改为daemonize yes

    • 在redis根目录以redis.conf作为配置文件在后台运行

      1
      2
      3
      4
      [root@localhost redis-4.0.0]# src/redis-server ./redis.conf 
      40753:C 06 Jul 20:59:49.460 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
      40753:C 06 Jul 20:59:49.461 # Redis version=4.0.0, bits=64, commit=00000000, modified=0, pid=40753, just started
      40753:C 06 Jul 20:59:49.461 # Configuration loaded
  • Linux开启密码校验

    Redis默认没有开启密码,所以需要自行配置,不配置无法进行远程连接

    • 修改redis.conf配置文件,找到requirepass这行,将其注释去掉,在后面写上自己的密码

      image-20230706210825434

    • 然后杀掉原进程再重新启动:src/redis-server ./redis.conf

    • 此时直接进行客户端登录会出现以下状况:这是因为没有输入密码所致

      image-20230706211059149

    • 然后使用auth password命令输入密码,就能访问到数据库(此时为空)

      image-20230706211212850

    • 也可以在客户端登录时就输入密码

      1
      src/redis-cli -h localhost -p 6379 -a 密码
  • Linux开启远程连接

    Redis启动时默认是不允许远程连接的

    • 修改redis.conf配置文件

      • 找到bind 127.0.0.1这行,把这行注释掉(bind配置的是允许连接的ip,默认只允许本机连接若远程连接需注释掉,或改为0.0.0.0)

        image-20230706212159586

      • 修改protected-mode yesprotected-mode no(3.2之后加入的新特性,目的是禁止公网访问redis cache,增强redis的安全性)

    • 之后设置防火墙,开启6379端口

      1
      2
      3
      4
      5
      6
      7
      8
      # 开启6379端口
      firewall-cmd --zone=public --add-port=6379/tcp --permanent

      # 设置立即生效
      firewall-cmd --reload

      # 查看开放的端口
      firewall-cmd --zone=public --list-ports
    • 设置完成后kill掉redis进程,然后再重新启动

    • 远程连接Linux的Redis:

      在本地Redis的安装目录下,使用shift+右键,打开PowerShell

      • 在此之前:

        image-20230706212506574

      • 修改之后:

        image-20230706214614682


Redis数据类型

介绍

Redis存储的是key-value结构的数据,其中key是字符串类型,value有5中常用的数据类型

  • 字符串:String
  • 哈希:Hash
  • 列表:List
  • 集合:Set
  • 有序集合:Sorted Set

image-20230706215434901

常用命令

更多命令可以查看官方文档:https://www.redis.net.cn

字符串(String)

命令 描述
SET key value 设置指定key的值
GET key 获取指定key的值
SETEX key seconds value 设置指定key的值,并将key的过期时间设为seconds秒
SETNX key value 只有在key不存在时设置key的值
  • 简单演示

    image-20230706220328610

哈希(Hash)

Redis Hash是一个String类型的FieldValue的映射表,Hash特别适合用于存储对象

image-20230706220500941

命令 描述
HSET key field value 将哈希表key 中的字段field的值设为value
HGET key field 获取存储在哈希表中指定字段的值
HDEL key field 删除存储在哈希表中的指定字段
HKEYS key 获取哈希表中所有字段
HVALS key 获取哈希表中所有值
HGETALL key 获取在哈希表中指定key的所有字段和值
  • 简单演示

    image-20230706220653379

列表(List)

Redis List是简单的字符串列表,按照插入顺序排序

image-20230706220717215

命令 描述
LPUSH key value1 [value2] 将一个或多个值插入到列表头部
LRANGE key start stop 获取列表指定范围内的元素
RPOP key 移除并获取列表最后一个元素
LLEN key 获取列表长度
BRPOP key1 [key2] timeout 移出并获取列表的最后一个元素 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止
  • 简单演示

    image-20230706221418047

集合(Set)

Redis setString类型的无序集合。集合成员是唯一的,这就意味着集合中不能出现重复的数据

image-20230706221451906

命令 描述
SADD key member1 [member2] 向集合添加一个或多个成员
SMEMBERS key 返回集合中的所有成员
SCARD key 获取集合的成员数
SINTER key1 [key2] 返回给定所有集合的交集
SUNION key1 [key2] 返回所有给定集合的并集
SDIFF key1 [key2] 返回给定所有集合的差集
SREM key member1 [member2] 移除集合中一个或多个成员

有序集合(Sorted Set)

Redis Sorted Set有序集合是String类型元素的集合,且不允许重复的成员。每个元素都会关联一个double类型的分数(score) 。Redis正是通过分数来为集合中的成员进行从小到大排序。有序集合的成员是唯一的,但分数却可以重复。

image-20230706221654411

命令 描述
ZADD key score1 member1 [score2 member2] 向有序集合添加一个或多个成员,或者更新已存在成员的分数
ZRANGE key start stop [WITHSCORES] 通过索引区间返回有序集合中指定区间内的成员
ZINCRBY key increment member 有序集合中对指定成员的分数加上增量increment
ZREM key member [member …] 移除有序集合中的一个或多个成员
  • 简单演示

    image-20230706221918229

通用命令

命令 描述
KEYs pattern 查找所有符合给定模式(pattern)的key
EXISTs key 检查给定key是否存在
TYPE key 返回key所储存的值的类型
TTL key 返回给定key的剩余生存时间(TTL, time to live),以秒为单位
DEL key 该命令用于在key存在是删除key

在Java中操作Redis

介绍

  • Redis的Java客户端有很多,官方推荐的有三种
    • Jedis
    • Lettuce
    • Redisson
  • Spring对Redis客户端进行了整合,提供了SpringDataRedis,在Spring Boot项目中还提供了对应的Starter,即spring-boot-starter-data-redis

Jedis

Jedis了解一下即可,大多数情况下使用SpringDataRedis

  • 使用Jedis的步骤

    1. 获取连接
    2. 执行操作
    3. 关闭连接
  • 在此之前我们需要导入一下Jedis的maven坐标

    1
    2
    3
    4
    5
    <dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>2.8.0</version>
    </dependency>
  • 编写测试类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    @SpringBootTest
    class RedisTestApplicationTests {

    @Test
    void contextLoads() {
    //1. 获取连接
    Jedis jedis = new Jedis("localhost", 6379);
    //2. 执行具体操作
    jedis.set("name", "wzy");

    jedis.hset("stu", "name", "Jerry");
    jedis.hset("stu", "age", "18");
    jedis.hset("stu", "num", "4204000400");

    Map<String, String> map = jedis.hgetAll("stu");
    Set<String> keySet = map.keySet();
    for (String key : keySet) {
    String value = map.get(key);
    System.out.println(key + ":" + value);
    }
    String name = jedis.get("name");
    System.out.println(name);
    //3. 关闭连接
    jedis.close();
    }

    }
  • 输出结果

    num:4204000400
    name:Jerry
    age:18
    wzy

Spring Data Redis

SpringBoot项目中,可以使用SpringDataRedis来简化Redis(常用)。Spring Data Redis中提供了一个高度封装的类:RedisTemplate,针对jedis客户端中大量api进行了归类封装,将同一类型操作封装为operation接口,具体分类如下:

  • ValueOperations:简单K-V操作
  • SetOperations:set类型数据操作
  • ZSetOperations:zset类型数据操作
  • HashOperations:针对map类型的数据操作
  • ListOperations:针对list类型的数据操作

配置

  1. 创建sprongboot项目,导入Maven坐标(可以在创建时勾选redis)

    1
    2
    3
    4
    5
    <!--Spring Boot-redis的依赖包-->
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
  2. 完成配置类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    spring:
    data:
    redis:
    host: 127.0.0.1 # localhost
    port: 6379
    #password: 123456
    jedis:
    pool:
    max-active: 8 #最大连接数
    max-wait: 1ms #连接池最大阻塞等待时间
    max-idle: 4 #连接池中的最大空闲连接
    min-idle: 0 #连接池中的最小空闲连接
    database: 0 # 操作的0号数据库

    注意:这部分不同于教程多了一个data父标签,因为版本更新原本的已经过时

  3. 编写测试方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    @SpringBootTest
    class SpringbootRedisApplicationTests {
    @Autowired
    private RedisTemplate redisTemplate;

    @Test
    void contextLoads() {
    redisTemplate.opsForValue().set("name", "wzy");
    }

    }
  4. 然后打开redis-cli.exe客户端查看数据

    • 首先需要打开redis-server.exe服务端,但是我这里再Windows下直接打开会一闪而过,所以启动需要再redis根目录下执行命令:redis-server.exe redis.windows.conf

    • 然后再打开redis-cli.exe,查询数据库中的key:

      1
      2
      127.0.0.1:6379> keys *
      1) "\xac\xed\x00\x05t\x00\x04name"

      这里发现,多出了一串字符,我们存入的key是name。这是因为序列化所致

  5. 添加配置类RedisConfig.java,重新设置一下序列化器,防止出现乱码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    @Configuration
    public class RedisConfig extends CachingConfigurerSupport {
    @Bean
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory) {

    RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();

    //默认的Key序列化器为:JdkSerializationRedisSerializer
    redisTemplate.setKeySerializer(new StringRedisSerializer());
    redisTemplate.setHashKeySerializer(new StringRedisSerializer());
    redisTemplate.setConnectionFactory(connectionFactory);
    return redisTemplate;
    }
    }
  6. 然后再进行测试添加数据,然后进行数据的查询。此时结果就会显示正常。

    • 添加数据

      1
      2
      3
      4
      @Test
      void contextLoads() {
      redisTemplate.opsForValue().set("name2", "wzy");
      }
    • 查询数据

      1
      2
      3
      127.0.0.1:6379> keys *
      1) "\xac\xed\x00\x05t\x00\x04name"
      2) "name2"

    可以看出,此时key显示正常。

  7. 根据key获取value发现value也存在序列化,但是这部分不用处理,因为通常的结果还会反序列化处理

    1
    2
    127.0.0.1:6379> get name2
    "\xac\xed\x00\x05t\x00\x03wzy"

数据操作

  • 字符串

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    @Test
    void testRedisString(){
    ValueOperations valueOperations = redisTemplate.opsForValue();
    valueOperations.set("name", "wzy");
    System.out.println(valueOperations.get("name"));
    //设置有效时间10s
    valueOperations.set("age",23,10, TimeUnit.SECONDS);
    System.out.println(valueOperations.get("age"));

    //如果不存在则新增
    Boolean ifAbsent = valueOperations.setIfAbsent("name", "zyw");
    System.out.println(ifAbsent);
    }

    wzy
    23
    false

  • 哈希

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    @Test
    void testRedisHash(){
    HashOperations hashOperations = redisTemplate.opsForHash();
    hashOperations.put("001", "name", "wzy");
    hashOperations.put("001", "age", "23");
    hashOperations.put("001", "addr", "beijing");

    //只获取values
    List<String> values = hashOperations.values("001");
    for (String value : values) {
    System.out.println(value);
    }

    //获取map集合
    Map<String, String> map = hashOperations.entries("001");
    Set<String> keySet = map.keySet();
    for (String hashKey : keySet) {
    System.out.println(hashKey + ":" + map.get(hashKey));
    }
    }

    wzy
    23
    beijing
    name:wzy
    age:23
    addr:beijing

  • List

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    @Test
    void testList() {
    ListOperations listOperations = redisTemplate.opsForList();
    //存数据
    listOperations.leftPush("listData", "A");
    listOperations.leftPushAll("listData", "B", "C", "D");
    List<String> testDatas = listOperations.range("listData", 0, -1);
    //遍历
    for (String tableData : testDatas) {
    System.out.print(tableData + " ");
    }

    //获取当前list长度,用于遍历
    Long size = listOperations.size("listData");
    int value = size.intValue();
    //遍历输出并删除
    for (int i = 0; i < value; i++) {
    System.out.print(listOperations.leftPop("listData") + " ");
    }
    //输出当前list长度
    System.out.println(listOperations.size("listData"));
    }
  • Set

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    @Test
    void testSet() {
    SetOperations setOperations = redisTemplate.opsForSet();

    setOperations.add("rank", "a", "s", "ssr", "a");
    //遍历输出
    Set<String> tmpData = setOperations.members("rank");
    for (String value : tmpData) {
    System.out.print(value + " ");
    }
    System.out.println();

    //删除a
    setOperations.remove("rank", "a");
    //遍历输出
    tmpData = setOperations.members("rank");
    for (String value : tmpData) {
    System.out.print(value + " ");
    }
    }
  • Zset

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    @Test
    void testZset() {
    ZSetOperations zSetOperations = redisTemplate.opsForZSet();
    //存值
    zSetOperations.add("myZset", "a", 0.0);
    zSetOperations.add("myZset", "b", 1.0);
    zSetOperations.add("myZset", "c", 2.0);
    zSetOperations.add("myZset", "a", 3.0);
    //取值
    Set<String> myZset = zSetOperations.range("myZset", 0, -1);
    for (String s : myZset) {
    System.out.println(s);
    }
    //修改分数
    zSetOperations.incrementScore("myZset", "b", 4.0);
    //取值
    System.out.println();
    myZset = zSetOperations.range("myZset", 0, -1);
    for (String s : myZset) {
    System.out.println(s);
    }
    //删除成员
    zSetOperations.remove("myZset", "a", "b");
    //取值
    System.out.println();
    myZset = zSetOperations.range("myZset", 0, -1);
    for (String s : myZset) {
    System.out.println(s);
    }
    }
  • 通用操作

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    @Test
    void testCommon() {
    //查看Radia中所有key
    Set<String> keys = redisTemplate.keys("*");
    for (String key : keys) {
    System.out.println(key);
    }
    //判断某个key是否存在
    System.out.println(redisTemplate.hasKey("wzy"));

    //删除指定key
    redisTemplate.delete("name");

    //获取指定key的数据类型
    System.out.println(redisTemplate.type("001"));
    }

    name
    \xac\xed\x00\x05t\x00\x04name
    001

    false

    HASH