Redis
Redis

一、NoSQL 数据库

NoSQL (Not Only SQL),泛指非关系型数据库NoSQL不依赖业务逻辑方式存储,而以简单的key-value模式存储。因此大大的增加了数据库的扩展能力。

  • 不遵循SQL标准
  • 不支持ACID
  • 远超于SQL性能

1.1 适用场景

  • 对数据高并发的读写
  • 海量数据的读写
  • 对数据高可扩展性的要求

1.2 不适用场景

  • 需要事务支持
  • 基于SQL的结构化查询存储,处理复杂的关系,需要即席查询

二、Redis基本介绍

默认端口:6379

默认有16个数据库,类似数组下标从0开始,初始默认使用 0 号库

统一密码管理,所有库的密码相同

2.1 单线程 + 多路 IO 复用

多路复用是指使用一个线程来检查多个文件描述符Socket的就绪状态,比如调用select()pool(),传入多个文件描述符,如果有一个文件描述符就绪,则返回,否则阻塞直到超市。得到就绪状态后进行真正的操作可以在同一个线程里执行,也可以启动线程执行(比如线程池)

2.2 安装(CentOS7 + Redis6)

  • 前置准备
1
2
3
4
yum -y install gcc tcl
yum -y install centos-release-scl
yum -y install devtoolset-9-gcc devtoolset-9-gcc-c++ devtoolset-9-binutils
echo -e "\nsource /opt/rh/devtoolset-9/enable" >>/etc/profile
  • 安装
1
2
3
4
5
6
7
cd /usr/local/
wget http://download.redis.io/releases/redis-6.0.9.tar.gz
tar -zxvf redis-6.0.9.tar.gz
cd redis-6.0.9
make && make install
# 等待安装完成
cp redis.conf /etc/redis.conf
  • 修改配置文件
1
2
3
4
5
6
vim /etc/redis.conf

#bind 127.0.0.1 # 将这行代码注释,监听所有的ip地址,外网可以访问
protected-mode no # 把yes改成no,允许外网访问
daemonize yes # 把no改成yes,后台运行
appendonly yes # 开启aof备份
  • 设置开机启动
1
2
3
4
# 启动服务器
/usr/local/bin/redis-server /etc/redis.conf
# 启动客户端
/usr/local/bin/redis-cli

2.3 Key 的相关操作

  • *keys * * 查看当前库所有 key

    image-20210706233040743

  • exists key 判断某个 key 是否存在

    image-20210706233115844

  • type key 查看 key 的类型

    image-20210706233129929

  • del key 删除指定的 key

    image-20210706233148261

  • unlink key 根据 value 选择非阻塞删除(表面上已经删除,实际上并没有,会等后期慢慢删除)

  • expire key N 给 key 设置过期时间(N秒)

  • ttl key 查看 key 还有多少秒过期

  • select N 使用第 N 号数据库

    image-20210706233222137

  • dbsize 查看当前数据库 key 的数量

    image-20210706233248093

  • flushdb 清空当前库

  • flushall 清空所有库

2.4 配置文件

2.4.1 文件头

主要是单位的设置方式

2.4.2 INCLUDE

可以导入其他文件

2.4.3 NETWORK

仅绑定的 ip 可以连接到 redis

yes 状态下仅本机可连接

端口号配置

设置 Tcp 的 backlog,backlog 是一个连接队列,队列总和 = 未完成三次握手队列 + 已完成三次握手队列。在高并发下需要一个高 backlog 来避免慢客户端连接问题

空闲等待时间,单位为秒,0 为永不超时

心跳检测时间,单位秒

3.4.4 GENERAL

允许后台启动

进程号文件

日志级别

日志文件输出路径

数据库个数

3.4.5 SECURITY

密码设置

3.4.6 CLIENTS

客户端最大连接数

三、基本五大类型

3.1 String

3.1.1 命令行操作

  • get [key] 查询 key 的值

    image-20210706233303971

  • append [key] [value] 将 value 添加到指定 key 尾

    image-20210707000713987

  • strlen [key] 计算 value 的长度

    image-20210707000731597

  • setnx [key] [value] 当 db 中不存在 key 时才加入

    image-20210707000815743

  • incr/decr [key] 将数字类型的字符串加/减1

    image-20210707000904031

  • incrby/decrby [key] [step] 将数字类型的字符串加/减step

    image-20210707000943185

  • mset [key1] [value1] [key2] [value2] … 同时添加多个 k-v 键值对

    image-20210707001031801

  • mget [key1] [key2] … 同时获取多个 value

    image-20210707001104879

  • msetnx [key1] [value1] [key2] [value2] … 同时添加多个 k-v 键值对,但如果 db 中存在一个 key,那么所有数据都将添加失败

    image-20210707001204001

  • getrange [key] [start] [end] 截取字串

    image-20210707001304380

  • setrange [key] [start] [value] 从 start 开始将原字符替换为 value

    image-20210707001342793

  • setex [key] [timeout] [value] 设置 k-v 键值对的有效时间

  • getset [key] [value] 获取旧值,设置新值

    image-20210707001409848

3.1.2 数据结构

String 的数据结构为简单动态字符串(Simple Dynamic String, SDS)。是可以修改的字符串,结构上类似于 Java 的 ArrayList,采用预分配冗余空间的方式来减少内存的频繁分配。

如图,内部为当前字符串实际分配的空间 capacity 一般要高于实际字符串长度 len, 当字符串长度小于 1M 时,扩容都是加倍现有的空间,如果超过 1M,扩容时一次指挥多扩 1M 的空间,最大 512M

3.2 List

3.2.1 命令行操作

  • lpush/rpush [key] [value1] [value2] … 从左边/右边插入一个或多个值

  • lpop/rpop [key] 从左边/右边吐出一个值。值在键在,值毁键亡

  • rpoplpush [key1] [key2] 从 key1 右边吐出一个值,加入到 key2 列表左边

  • lrange [key] [start] [end] 按照索引获取值

  • lindex [key] [index] 按照索引下标获取值

  • llen [key] 获取列表长度

  • linsert [key] before/after [element] [newElement] 在 element 前/后插入一个新 element

  • lrem [key] [n] [value] 从左边删除 n 个 value

  • lset [key] [index] [value] 设置指定下标处的值

3.2.2 数据结构

单键多值。Redis 列表是简单的字符串列表,按照插入顺序排序。可以添加一个元素的头部或尾部。

底层是一个双向链表,对两端的操作性能很高,通过索引下标的操作中间的节点性能可能会较差

数据结构为快速链表 quickList。在列表元素较少的情况下会使用一块连续的内存存储,这个结构是压缩链表(zipList)。它将所有的元素紧挨在一起存储,分配的是一块连续的内存。

当数据量比较多的时候才会改成 quickList。因为普通的链表需要的附加指针空间太大,会比较浪费空间。比如这个列表里存的只是 int 类型的数据,结构上还需要额外的指针 prev 和 next。

Redis 将链表和 zipList 结合起来组成 quickList。也就是将多个 zipList 使用双向指针串起来。这样既满足了快速的插入删除性能,又不会出现太大的空间冗余。

3.3 Set

3.3.1 命令行操作

  • sadd [key] [value1] [value2] … 将一个或多个元素加入到集合 key 中,已经存在的元素将被忽略。

  • smembers [key] 取出该集合的所有值

  • sismember [key] [value] 判断集合 key 中是否存在 value

    image-20210707090217198

  • scard [key] 返回集合元素个数

  • **srem [key] [value1] [value2] …**删除集合中的某个元素

  • **spop [key]**随机从集合中弹出一个值

  • srandmember [key] [N] 随即从集合中取出 N 个值,不会删除

  • **smove [source] [destination] [value]**把 value 从 source 集合移动到 destination 集合

  • **sinter [key1] [key2]**交集

  • **sunion [key1] [key2]**并集

  • **sdiff [key1] [key2]**差集

3.3.2 数据结构

Redis set 对外提供的功能与 list 类似是一个列表的功能,特殊之处在于 set 是可以自动排重的,当你需要存储一个列表数据,又不希望出现重复数据时,set 是一个很好的选择。

是 string 类型的无序集合。它的底层其实是一个 value 为 null 的 hash 表,所以添加,删除,查找的**复杂度都是O(1)**。

一个算法,随着数据的增加,执行时间的长短,如果是 O(1),数据增加,查找数据的时间不变。

set 数据结构是 dict 字典,用 hash 表实现

3.4 Hash

3.4.1 命令行操作

  • **hset [key] [field] [value]**给 key 集合中的 field 键赋值

  • **hget [key1] [field]**从 key1 集合 field 取出 value

  • **hmset [key] [field1] [value1] [field2] [value2] …**批量设置 hash 的值

  • hexists [key1] [field] 查看 key 中是否有 field

  • hkeys [key] 列出 key 中的所有 field

  • **hvals [key]**列出 key 中的所有 value

  • hincrby [key] [field] [step] 给 key 中的字段加上 step

  • **hsetnx [key] [field] [value]**将哈希表 key 中的域 field 的值设置为 value,当且仅当 field 不存在

3.4.2 数据结构

Redis hash 是一个键值对集合,是一个 string 类型的 filed 和 value 的映射表,hash 特别适合用于存储对象,类似 Java 里面的 Map<String, Object>

hash 类型对应的数据结构有 zipList,hashTable 两种。当 field-value 长度较短且个数比较少时,使用 zipList,反之使用 hashTable

3.5 Zset

3.5.1 命令行操作

  • **zadd [key] [score1] [value1] [score2] [value2] …**将一个或多个元素及评分加入有序集合中

  • **zrange [key] [start] [end] [withscores]**返回有序集合 key 中下标在 [start, stop] 的元素


3.5.2 数据结构

有序集合 zset 与普通集合 set 非常相似,是一个没有重复元素的字符串集合。不同之处是有序集合的每个成员都关联了一个评分(score)**,被用来按照从最低分到最高分的方式排序集合中的成员。集合的成员是唯一的,但评分可以重复**

因为元素是有序的,所以也可以很快的根据评分或次序来获取一个范围的元素

数据结构采用跳表

四、发布与订阅

4.1 subscribe

订阅

4.2 publish

发布

五、Jedis

5.1 普通使用

Jedis 为命令行中的每一个命令都创建了与其名字相对应的方法,可以直接调用

  • 导入依赖

    1
    2
    3
    4
    5
    <dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>3.6.1</version>
    </dependency>
  • 建立连接

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    import redis.clients.jedis.Jedis;

    public class Connection {

    private final static Jedis jedis = new Jedis("127.0.0.1", 6379);

    public static Jedis getJedis() {
    return jedis;
    }

    }
  • 测试连接

    1
    2
    3
    4
    5
    6
    7
    8
    public class Ping {

    public static void main(String[] args) {
    Jedis jedis = Connection.getJedis();
    System.out.println(jedis.ping());
    }

    }
  • 开始使用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public class JedisDemo {

    public static void main(String[] args) {
    Jedis jedis = Connection.getJedis();
    jedis.sadd("jedis", "v1", "v2", "v3");
    Set<String> smembers = jedis.smembers("jedis");
    System.out.println(smembers);
    }

    }

5.2 整合 SpringBoot

  • 导入依赖

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <dependency>
    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-redis</artifactId>
    <version>2.5.2</version>
    </dependency>

    <dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
    <version>2.10.0</version>
    </dependency>
  • 配置启动文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    spring:
    redis:
    host: 127.0.0.1
    port: 6379
    database: 0
    timeout: 1800000
    lettuce:
    pool:
    max-active: 20
    max-wait: -1
    max-idle: 5
    min-idle: 0
  • 书写配置类

    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
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    import com.fasterxml.jackson.annotation.JsonAutoDetect;
    import com.fasterxml.jackson.annotation.JsonTypeInfo;
    import com.fasterxml.jackson.annotation.PropertyAccessor;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
    import org.springframework.cache.CacheManager;
    import org.springframework.cache.annotation.EnableCaching;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.data.redis.cache.RedisCacheConfiguration;
    import org.springframework.data.redis.cache.RedisCacheManager;
    import org.springframework.data.redis.connection.RedisConnectionFactory;
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
    import org.springframework.data.redis.serializer.RedisSerializationContext;
    import org.springframework.data.redis.serializer.RedisSerializer;
    import org.springframework.data.redis.serializer.StringRedisSerializer;

    import java.time.Duration;
    @EnableCaching
    @Configuration
    public class RedisConfig {

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {

    RedisTemplate<String, Object> template = new RedisTemplate<>();
    RedisSerializer<String> redisSerializer = new StringRedisSerializer();
    Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
    ObjectMapper om = new ObjectMapper();
    om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
    // om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
    om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.WRAPPER_ARRAY);
    jackson2JsonRedisSerializer.setObjectMapper(om);
    template.setConnectionFactory(factory);
    // key采用String的方式进行序列化
    template.setKeySerializer(redisSerializer);
    // hash的key也采用String的方式进行序列化
    template.setHashKeySerializer(redisSerializer);
    // value序列化方式采用jackson
    template.setValueSerializer(jackson2JsonRedisSerializer);
    // hash序列化方式采用jackson
    template.setHashValueSerializer(jackson2JsonRedisSerializer);
    template.afterPropertiesSet();
    return template;
    }

    @Bean
    public CacheManager cacheManager(RedisConnectionFactory factory) {

    RedisSerializer<String> redisSerializer = new StringRedisSerializer();
    Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
    // 解决查询缓存转换异常的问题
    ObjectMapper om = new ObjectMapper();
    om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
    om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.WRAPPER_ARRAY);
    jackson2JsonRedisSerializer.setObjectMapper(om);
    RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
    .entryTtl(Duration.ofSeconds(600))
    .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
    .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer))
    .disableCachingNullValues();

    RedisCacheManager cacheManager = RedisCacheManager.builder(factory)
    .cacheDefaults(config)
    .build();
    return cacheManager;

    }
    }


六、事务和锁机制

Redis 事务是一个单独的隔离操作:食物中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。Redis 事务的主要作用就是串联多个命令防止别的命令插队

6.1 Multi、Exec、Discard

从输入 Multi 命令开始,输入的命令都会依次进入命令队列中,但不会执行,直到输入 Exec 之后,Redis 才会将之前的命令队列中的命令依次执行。组队过程中可以通过 Discard 来取消

6.2 异常处理

  • 情况一:在 multi - exec 组队过程中命令就已经出错 => 所有命令都不会执行

  • 情况二:在执行的过程中出现异常 => 只有出现异常的命令不会被执行

6.3 事务冲突

6.3.1 事务三特性

  • 单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送过来的命令请求所打断
  • 没有隔离级别的概念:队列中的命令没有提交之前都不会实际被执行,因为事务提交前任何指令都不会被实际执行
  • 不保证原子性:事务中如果有一条命令失败,其后的命令仍会被执行,没有回滚

6.3.2 命令行操作

  • watch [key1] [key2]… 可以监视一个或多个 key,如果在事务执行之前这个 key 被其他命令所改动,那么事务将被打断
  • unwatch [key1] [key2]… 取消监视