返回文章列表
高并发项目
RedisLinux环境搭建入门

11redis搭建与入门

一、Redis简介

Redis诞生于2009年,全称是 Remote Dictionary Server(远程词典服务器),是一个基于内存的键值型 NoSQL 数据库。key 一般是 String 类型,value 支持多种不同数据结构,功能丰富。

Redis 的特征

  • 键值(key-value)型,value 支持多种不同数据结构,功能丰富
  • 单线程,每个命令具备原子性
  • 低延迟,速度快(基于内存、IO 多路复用、良好的编码)
  • 支持数据持久化
  • 支持主从集群、分片集群
  • 支持多语言客户端

Redis介绍

NoSQL 与 SQL 对比

SQL 是关系型数据库,NoSQL 是非关系型数据库,二者在多个维度存在差异:

维度 SQL NoSQL
数据结构 结构化(Structured) 非结构化
数据关联 关联的(Relational) 无关联的
查询方式 SQL 查询 非 SQL
事务特性 ACID BASE
存储方式 磁盘 内存
扩展性 垂直扩展 水平扩展

NoSQL与SQL对比

事务方面,SQL 满足 ACID(原子性、一致性、隔离性、持久性),NoSQL 满足 BASE(基本可用、软状态、最终一致)。

ACID与BASE事务对比

NoSQL 常见分类:

  1. 键值类型(Redis)
  2. 文档类型(MongoDB)
  3. 列类型(HBase)
  4. Graph 类型(Neo4j)

使用场景上,SQL 适合数据结构固定、对数据安全性和一致性要求较高的业务;NoSQL 适合数据结构不固定、对一致性和安全性要求不高、但对性能要求较高的场景。

二、安装Redis

可使用 Docker 快速部署 Redis,并开启 AOF 持久化:

docker run \
--restart=always \
--log-opt max-size=100m \
--log-opt max-file=2 \
-p 6379:6379 \
--name redis \
-v /root/redis/conf/redis.conf:/etc/redis/redis.conf \
-v /root/redis/data:/data \
-d redis redis-server /etc/redis/redis.conf \
--appendonly yes \
--requirepass 123456

三、Redis数据结构

Redis 是 key-value 数据库,key 一般是 String 类型,value 的类型多种多样,可分为基本类型和特殊类型:

  • 基本类型:String、Hash、List、Set、SortedSet
  • 特殊类型:GEO、BitMap、HyperLogLog

Redis数据结构

通用命令

通用指令是部分数据类型都可以使用的指令,常见的有:

  • KEYS:查看符合模板的所有 key
  • DEL:删除一个指定的 key
  • EXISTS:判断 key 是否存在
  • EXPIRE:给一个 key 设置有效期,到期时该 key 会被自动删除
  • TTL:查看一个 key 的剩余有效期

通过 help [command] 可以查看一个命令的具体用法。

Redis通用命令

String 类型

String 是 Redis 中最简单的存储类型,value 是字符串,根据格式可分为 3 类:

  • string:普通字符串
  • int:整数类型,可以做自增、自减操作
  • float:浮点类型,可以做自增、自减操作

不管是哪种格式,底层都是字节数组形式存储,只不过是编码方式不同。字符串类型的最大空间不能超过 512M。

常见命令:

  • SET:添加或修改已存在的 String 键值对
  • GET:根据 key 获取 String 类型的 value
  • MSET:批量添加多个 String 键值对
  • MGET:根据多个 key 获取多个 value
  • INCR:让整型 key 自增 1
  • INCRBY:让整型 key 自增并指定步长
  • INCRBYFLOAT:让浮点类型数字自增并指定步长
  • SETNX:添加 String 键值对,前提是 key 不存在
  • SETEX:添加 String 键值对并指定有效期

Key 的层级结构:Redis 的 key 允许有多个单词形成层级结构,多个单词之间用 : 隔开,格式为 项目名:业务名:类型:id。例如项目名为 heima 时:

  • 用户 key:heima:user:1
  • 商品 key:heima:product:1

如果 Value 是 Java 对象,可将对象序列化为 JSON 字符串后存储。

Hash 类型

Hash 也叫散列,value 是一个无序字典,类似于 Java 中的 HashMap 结构。String 结构将对象序列化为 JSON 字符串后存储,修改单个字段时很不方便;而 Hash 结构可以将对象中的每个字段独立存储,可以针对单个字段做 CRUD。

常见命令:

  • HSET key field value:添加或修改 hash 类型 key 的 field 值
  • HGET key field:获取一个 hash 类型 key 的 field 值
  • HMSET:批量添加多个 hash 类型 key 的 field 值
  • HMGET:批量获取多个 hash 类型 key 的 field 值
  • HGETALL:获取一个 hash 类型 key 中所有的 field 和 value
  • HKEYS:获取一个 hash 类型 key 中所有的 field
  • HVALS:获取一个 hash 类型 key 中所有的 value
  • HINCRBY:让一个 hash 类型 key 的字段值自增并指定步长
  • HSETNX:添加 hash 类型 key 的 field 值,前提是 field 不存在

List 类型

Redis 中的 List 类型与 Java 中的 LinkedList 类似,可以看做是双向链表结构,既支持正向检索也支持反向检索。特征:

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

常用来存储一个有序数据,例如朋友圈点赞列表、评论列表等。

常见命令:

  • LPUSH key element ...:向列表左侧插入一个或多个元素
  • LPOP key:移除并返回列表左侧的第一个元素,没有则返回 nil
  • RPUSH key element ...:向列表右侧插入一个或多个元素
  • RPOP key:移除并返回列表右侧的第一个元素
  • LRANGE key start end:返回一段角标范围内的所有元素
  • BLPOP 和 BRPOP:与 LPOP 和 RPOP 类似,没有元素时等待指定时间而不是直接返回 nil

利用 List 结构可以模拟:

  • 栈:入口和出口在同一边
  • 队列:入口和出口在不同边
  • 阻塞队列:入口和出口在不同边,出队时采用 BLPOP 或 BRPOP

Set 类型

Redis 的 Set 结构与 Java 中的 HashSet 类似,可以看做是一个 value 为 null 的 HashMap,特征:

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

常见命令:

  • SADD key member ...:向 set 中添加一个或多个元素
  • SREM key member ...:移除 set 中的指定元素
  • SCARD key:返回 set 中元素的个数
  • SISMEMBER key member:判断一个元素是否存在于 set 中
  • SMEMBERS:获取 set 中的所有元素
  • SINTER key1 key2 ...:求 key1 与 key2 的交集
  • SDIFF key1 key2 ...:求 key1 与 key2 的差集
  • SUNION key1 key2 ...:求 key1 和 key2 的并集

SortedSet 类型

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

SortedSet 特性:

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

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

常见命令:

  • ZADD key score member:添加一个或多个元素到 sorted set,已存在则更新 score
  • ZREM key member:删除 sorted set 中的指定元素
  • ZSCORE key member:获取指定元素的 score 值
  • ZRANK key member:获取指定元素的排名
  • ZCARD key:获取 sorted set 中的元素个数
  • ZCOUNT key min max:统计 score 值在给定范围内的所有元素的个数
  • ZINCRBY key increment member:让指定元素自增,步长为指定的 increment
  • ZRANGE key min max:按 score 排序后,获取指定排名范围内的元素
  • ZRANGEBYSCORE key min max:按 score 排序后,获取指定 score 范围内的元素
  • ZDIFF、ZINTER、ZUNION:求差集、交集、并集

注意:所有排名默认都是升序,如果要降序则在命令的 Z 后面添加 REV 即可。

四、持久化

Redis 虽然是基于内存的数据库,但支持数据持久化,主要包含两种方式:

  • RDB(Redis Database):将内存中的数据以快照形式保存到磁盘,默认生成 dump.rdb 文件。可以通过配置 save 规则触发自动快照,或在命令行执行 BGSAVE 手动触发。RDB 文件紧凑、恢复速度快,但可能丢失最后一次快照之后的数据。
  • AOF(Append Only File):将所有写命令追加到磁盘文件 appendonly.aof 中。可在配置中通过 appendonly yes 开启。相比 RDB,AOF 数据安全性更高,但文件体积较大、恢复速度相对较慢。

五、Java客户端

Redis 官网提供了各种语言的客户端。Java 端常见的客户端包括:

  • Jedis:以 Redis 命令作为方法名称,学习成本低,简单实用。但 Jedis 实例是线程不安全的,多线程环境下需要基于连接池来使用。
  • Lettuce:基于 Netty 实现,支持同步、异步和响应式编程方式,线程安全,支持 Redis 哨兵模式、集群模式和管道模式。
  • Redisson:基于 Redis 实现的分布式、可伸缩的 Java 数据结构集合,包含 Map、Queue、Lock、Semaphore、AtomicLong 等强大功能。

Redis的Java客户端

Jedis 客户端

引入依赖:

<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>3.7.0</version>
</dependency>

建立连接、测试与释放资源:

private Jedis jedis;
 
@BeforeEach
void setUp() {
    // 建立连接
    jedis = new Jedis("192.168.150.101", 6379);
    // 设置密码
    jedis.auth("123321");
    // 选择库
    jedis.select(0);
}
 
@Test
void testString() {
    // 插入数据,方法名称就是redis命令名称
    String result = jedis.set("name", "张三");
    System.out.println("result = " + result);
    // 获取数据
    String name = jedis.get("name");
    System.out.println("name = " + name);
}
 
@AfterEach
void tearDown() {
    // 释放资源
    if (jedis != null) {
        jedis.close();
    }
}

Jedis 连接池:Jedis 本身线程不安全,并且频繁创建和销毁连接会有性能损耗,推荐使用连接池代替直连方式。

public class JedisConnectionFactory {
    private static final JedisPool jedisPool;
 
    static {
        JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
        // 最大连接
        jedisPoolConfig.setMaxTotal(8);
        // 最大空闲连接
        jedisPoolConfig.setMaxIdle(8);
        // 最小空闲连接
        jedisPoolConfig.setMinIdle(0);
        // 设置最长等待时间,ms
        jedisPoolConfig.setMaxWaitMillis(200);
        jedisPool = new JedisPool(jedisPoolConfig, "192.168.150.101", 6379,
                1000, "123321");
    }
 
    // 获取Jedis对象
    public static Jedis getJedis(){
        return jedisPool.getResource();
    }
}

SpringDataRedis 客户端

SpringData 是 Spring 中数据操作的模块,其中对 Redis 的集成模块叫做 SpringDataRedis。它提供了 RedisTemplate 统一 API 来操作 Redis。

SpringDataRedis

特点:

  • 提供了对不同 Redis 客户端的整合(Lettuce 和 Jedis)
  • 提供了 RedisTemplate 统一 API 来操作 Redis
  • 支持 Redis 的发布订阅模型
  • 支持 Redis 哨兵和 Redis 集群
  • 支持基于 Lettuce 的响应式编程
  • 支持基于 JDK、JSON、字符串、Spring 对象的数据序列化及反序列化
  • 支持基于 Redis 的 JDK Collection 实现

RedisTemplate 将不同数据类型的操作 API 封装到了不同的类型中:

API 返回值类型 说明
redisTemplate.opsForValue() ValueOperations 操作 String 类型数据
redisTemplate.opsForHash() HashOperations 操作 Hash 类型数据
redisTemplate.opsForList() ListOperations 操作 List 类型数据
redisTemplate.opsForSet() SetOperations 操作 Set 类型数据
redisTemplate.opsForZSet() ZSetOperations 操作 SortedSet 类型数据
redisTemplate 通用的命令

引入依赖:

<!--Redis依赖-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--连接池依赖-->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
</dependency>

配置文件:

spring:
  redis:
    host: 192.168.150.101
    port: 6379
    password: 123321
    lettuce:
      pool:
        max-active: 8  # 最大连接
        max-idle: 8    # 最大空闲连接
        min-idle: 0    # 最小空闲连接
        max-wait: 100  # 连接等待时间

注入 RedisTemplate 并测试:

@SpringBootTest
public class RedisTest {
    @Autowired
    private RedisTemplate redisTemplate;
 
    @Test
    void testString() {
        // 插入一条 string 类型数据
        redisTemplate.opsForValue().set("name", "李四");
        // 读取一条 string 类型数据
        Object name = redisTemplate.opsForValue().get("name");
        System.out.println("name = " + name);
    }
}

序列化方式优化

RedisTemplate 可以接收任意 Object 作为值写入 Redis,写入前会把 Object 序化为字节形式,默认采用 JDK 序列化。其缺点是可读性差、内存占用较大。

JDK默认序列化

方案一:自定义 RedisTemplate

@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory)
        throws UnknownHostException {
    // 创建Template
    RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
    // 设置连接工厂
    redisTemplate.setConnectionFactory(redisConnectionFactory);
    // 设置序列化工具
    GenericJackson2JsonRedisSerializer jsonRedisSerializer =
            new GenericJackson2JsonRedisSerializer();
    // key和hashKey采用string序列化
    redisTemplate.setKeySerializer(RedisSerializer.string());
    redisTemplate.setHashKeySerializer(RedisSerializer.string());
    // value和hashValue采用JSON序列化
    redisTemplate.setValueSerializer(jsonRedisSerializer);
    redisTemplate.setHashValueSerializer(jsonRedisSerializer);
    return redisTemplate;
}

JSON 序列化方式虽然能满足需求,但为了在反序列化时知道对象的类型,JSON 序列化器会将类的 class 类型写入 json 结果中存入 Redis,会带来额外的内存开销。

JSON序列化的额外开销

方案二:使用 StringRedisTemplate

为了节省内存空间,统一使用 String 序列化器,要求只能存储 String 类型的 key 和 value。当需要存储 Java 对象时,手动完成对象的序列化和反序列化。

手动序列化与反序列化

Spring 默认提供了 StringRedisTemplate 类,它的 key 和 value 的序列化方式默认就是 String 方式,省去了自定义 RedisTemplate 的过程:

@Autowired
private StringRedisTemplate stringRedisTemplate;
 
// JSON工具
private static final ObjectMapper mapper = new ObjectMapper();
 
@Test
void testStringTemplate() throws JsonProcessingException {
    // 准备对象
    User user = new User("虎哥", 18);
    // 手动序列化
    String json = mapper.writeValueAsString(user);
    // 写入一条数据到redis
    stringRedisTemplate.opsForValue().set("user:200", json);
 
    // 读取数据
    String val = stringRedisTemplate.opsForValue().get("user:200");
    // 反序列化
    User user1 = mapper.readValue(val, User.class);
    System.out.println("user1 = " + user1);
}

两种方案对比:

  • 方案一:自定义 RedisTemplate,修改序列化器为 GenericJackson2JsonRedisSerializer,使用方便但会额外存储 class 信息。
  • 方案二:使用 StringRedisTemplate,写入时手动把对象序列化为 JSON,读取时手动反序列化为对象,更节省内存空间。