一、 初识redis

1、 认识NoSQL

        SQL和NoSQL分别指的是关系型数据库和非关系型数据库。

特性

SQL

NoSQL

数据结构

结构化,表设计重要。修改大数据量的表可能导致长时间的锁表和业务影响。

非结构化,约束少。例如:键值型(Redis),列类型(HBase),文档型(MongoDB, JSON存储),图类型(neo4j)。修改数据结构的影响小。

数据关联性

数据存在关联关系并由数据库维护,如user、item、order等。

无关联关系,数据使用嵌套方式存储,可能导致信息重复。

查询方式

使用标准SQL查询语法。

非SQL查询,查询语法因数据库而异。

事务特性

支持ACID事务,安全性高。

只支持BASE事务,不能保证强事务。

        总结:SQL和NoSQL各有优势,适用于不同场景。SQL适合需要结构化、关联性强、高事务安全性的应用;NoSQL适用于处理大规模非结构化数据,提供灵活的数据存储和高效读写操作。

2、 认识Redis

        Redis(Remote Dictionary Server)是一种强大且灵活的非关系型(NoSQL)数据库。它基于内存,所以能提供高速读写性能。以下是RemoteDictionaryServer的主要特性:

  1. 键值数据模型:Redis是一款键值型(Key-Value)的NoSQL数据库,它支持多种不同的数据结构。这种灵活性使得Redis能够应对各种数据需求。
  2. 单线程:Redis采用单线程的设计,因此每个命令操作都具有原子性,从而保证了线程安全。即使在6.0版本中网络请求的读写开始支持多线程处理,命令的执行依然维持在单线程。
  3. 低延迟与高速度:基于内存存储和IO多路复用技术,Redis实现了极快的响应速度和低延迟。良好的编码策略更是进一步提升了其性能。
  4. 数据持久化:Redis提供了RDB和AOF两种数据持久化方案,用户可以根据自己的业务需求选择最适合的方式。
  5. 主从集群与分片集群:Redis支持主从集群和分片集群,帮助用户实现更优的数据管理。主从集群能实现读写分离和数据备份,而分片集群则通过将数据拆分来方便进行水平扩展。
  6. 支持多语言客户端:Redis兼容多种编程语言的客户端,极大地提高了其使用便利性。

3、 安装Redis

安装步骤

        按照官方文档的指示进行安装即可,Quick starts | Redis。在安装过程中,请确保备份配置文件redis.conf。

核心配置参数

  •  bind:默认值为127.0.0.1,表示只能由本地访问。如果设置为0.0.0.0,则可以被任意IP访问,生产环境不要设置为0.0.0.0
  • daemonize:默认值为NO,表示不允许后台运行。
  • requirepass:访问Redis必须输入的密码,例如requirepass 2000表示密码为2000。
  • port:指定Redis监听的端口。
  • dir:工作目录。
  • database:数据库数量,默认为16(范围0-15)。
  • logfile:默认为空,不记录日志。可以通过logfileredis.log指定日志文件名。
  • maxmemory:设置Redis能够使用的最大内存。

二、 Redis常见命令

1、 Redis数据结构

        Redis是一种键值(Key-Value)型数据库,其中键一般为字符串类型,而值则可以有多种数据结构。以下是Redis支持的主要数据结构:

  • String(字符串): 最基本的类型,一个key对应一个value。
  • Hash(哈希表): 用于存储对象,每个对象可以包含多个字段和值。
  • List(列表): 一个简单的字符串列表,按插入顺序排序。
  • Set(无序集合): 字符串的无序集合,通过Hash实现,可以快速地添加、删除、查找元素。
  • Sorted Set(有序集合): 字符串的集合,并且每个字符串都会关联一个浮点数类型的分数对集合中的元素进行排序。
  • GEO(地理坐标信息): 可以存储地理位置相关信息,如经纬度,方便做地理位置相关功能。
  • BitMap(位存储): 可以看作是一个以位为单位的数组,数组的每个单元只能存储0和1,即位的值只能为0或者1。
  • HyperLog(基数统计): 用来做基数统计的算法,特点是占用空间小,精度可控。

2、 通用命令

        以下是一些在Redis中常用的命令:

  • keys: 该命令用于查看所有符合模板的key。由于Redis是单线程的,使用keys命令会阻塞其他命令,因此不建议在生产环境下使用。
  • del: 该命令用于删除一个或多个指定的key。返回值表示成功删除的key数量。
  • exists: 该命令用于判断一个或多个key是否存在。返回值表示存在的key数量。
  • expire: 该命令用于为一个key设置过期时间。当到达过期时间后,这个key会被自动删除。
  • ttl: 该命令用于查看一个key的剩余存活时间。返回值为正数表示剩余存活时间(秒),-1表示永久有效,-2表示已经被删除。

3、 不同数据结构的操作命令

String类型

String类型是最基本的类型,一个键一般对应一个值,可以分为String、Int和Float类型。所有这些类型在底层都是以字节数组形式存储。

  • set key value:添set key value:加或修改一个已经存在的String类型的键值对。
  • get key:根get key:据key获取String类型的value。
  • mset key1 value1 key2 value2...:批量添加多个String类型的键值对。
  • mget key1 key2...:根据多个key获取多个String类型的value。
  • incr key:让incr key:一个整型的key自增1,返回值是增加后的值。
  • incr key increment:让一个整型的key增长指定值,可以使用负数实现自减。
  • incrbyfloat key increment:让一个浮点类型的数字自增并指定步长。
  • setnx key value:添加一个String类型的键值对,前提是key不存在,否则不执行。
  • setex key seconds value添加一个String类型的键值对并设置有效期。

Hash类型

        Hash类型也叫散列,其value是一个无序字典,类似于Java中的HashMap。

  • hset key field value:添加或修改hash类型key的field的值。
  • hget key field:获hget key field:取一个hash类型key的field值。
  • hmset key field1 value1 field2 value2...:批量添加多个hash类型key的field值。
  • hmget key field1 field2...:批量获取多个hash类型key的field值。
  • hgetall key:获取一个hash类型的key中的所有的field和value。
  • hkeys key:获取一个hash类型中key中的所有field。
  • hvals key:获取一个hash类型中key的所有的value。
  • hincrby key field increment:让一个hash类型key的字段自增并指定步长,返回时增长的结果。
  • hsetnx key field value:添加一个hash类型的key的field值,前提时这个field不存在,否则不执行。

List类型

        List类型是类似于Java中的LinkedList,是一个双向链表结构,支持正向检索和反向检索。

  • lpush key element...:向列表左侧插入一个或多个元素。
  • lpop key:移除并返回列表左侧的第一个元素,如果没有则返回nil。
  • rpush key element...:向列表右侧插入一个或多个元素。
  • rpop key:移除并返回列表右侧的第一个元素,没有则返回nil。
  • lrange key start end:返回一段角标范围内的所有元素,角标从0开始。
  • blpop key timeout和brpop key timeout:与lpop和rpop类似,只不过如果没有元素则等待指定时间,而不是直接返回nil。

Set类型

        Set类型结构与Java中的HashSet类似,可以看作是value为null的HashMap。查找速度快,支持交集并集差集等功能。

  • sadd key member...:向一个set中添加一个或多个元素。
  • srem key member...:移除一个set中的指定元素。
  • scard key:返回set中元素的个数。
  • sismember key member:判断一个元素是否存在于set中。
  • smembers key:返回set中的所有元素。
  • sinter key1 key2...:返回set1和set2的交集。
  • sdiff key1 key2...:返回set1与set2的差集。
  • sunion key1 key2...:返回set1和set2的并集。

Sorted Set类型

        Sorted Set类型是一个可排序的set集合,每一个元素都带有一个score属性,可以基于score属性对元素排序。

  • zadd key score member [score member...]:添加一个或多个元素到sorted set中,如果已经存在则更新其score值。
  • zrem key member...:删除sorted set中的一个指定元素。
  • zscore key member:获取sorted set中指定元素的score值。
  • zrank key member:获取sorted set中指定元素的排名。
  • zcard key:获取sorted set中的元素个数。
  • zcount key min max:统计score值在指定范围内的所有元素的个数。
  • zincrby key increment member:让sorted set中指定元素自增,步长为指定的increment值。
  • zrange key start stop [WITHSCORES]:按照score排序后,获取指定score范围内的元素。
  • zrangebyscore key min max [WITHSCORES]:按照score排序后,获取指定score范围内的元素。
  • zdiff key1 key2...,zinter key1 key2...,zunion key1 key2...:差集、交集和并集操作。

三、 Redis的Java客户端

1、 常见的Redis客户端

Jedis:Jedis提供了丰富的方法,基本上每种Redis命令都对应一个方法,因此学习成本相对较低。但是,Jedis实例不是线程安全的,因此在多线程环境下需要通过连接池来使用。

Lettuce:Lettuce基于Netty实现,支持同步、异步和响应式编程模型,它是线程安全的,并且支持Redis的哨兵模式、集群模式以及管道模式。

Redission:Redission是一个分布式的Java数据结构库,它基于Redis实现,可以看作是一种分布式版本的Java java.util.*接口。Redission包括了诸如Map,Queue,Lock,Semaphore,AtomicLong等强大功能。

2、 Spring Data Redis

        Spring Data Redis定义了一套API,底层可以使用Jedis或Lettuce客户端。它提供了RedisTemplate API用于操作Redis,支持Redis的发布/订阅模型,支持Redis哨兵和Redis集群,支持基于Lettuce的响应式编程,支持JDK、JSON、String、Spring对象的序列化与反序列化,还支持基于Redis的JDK Collection实现。

  • RedisTemplate.opsForValue():操作String类型。
  • RedisTemplate.opsForHash():操作hash类型。
  • RedisTemplate.opsFoList():操作list类型。
  • RedisTemplate.opsForSet():操作set类型。
  • RedisTemplate.opsForZSet():操作sorted set类型。
使用Spring Boot操作Redis
  1. 引入spring-boot-starter-data-redis依赖。
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
  1. 在application.yml配置文件中配置Redis信息。
spring:
  redis:
    host: localhost
    port: 6379
  1. 注入RedisTemplate或StringRedisTemplate对象。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

@Component
public class RedisExample {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    public void saveValueToRedis() {
        // Save a string value to Redis
        stringRedisTemplate.opsForValue().set("myKey", "myValue");
    }

    public void readValueFromRedis() {
        // Read a string value from Redis
        String value = stringRedisTemplate.opsForValue().get("myKey");
        System.out.println(value);
    }
}

       

3、 序列化存在的问题及解决方案

        直接使用Redis原生的序列化及反序列化会存在可读性差,占用内存还大的问题,本质是因为RedisTemplate.opsForXXX()的set方法传入的是Object类,在写入前会将Object序列化为字节形式,默认使用的方法是JDK序列化。

解决方案

        为了解决上述问题,有两种解决方案:

方案1
  1. 自定义RedisTemplate
  2. 修改RedisTemplate的序列化器为GenericJackson2JsonRedisSerializer
@Configuration
pubulic class Redis Config {
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory)
        // 创建RedisTemplate对象
        RedisTemplate<String, Object> template = new Redi:STemplate<<>();
        //设置连接工厂
template.setConnectionFactory(connectionFactory);
        // 创建JSON序列化工具
        GenericJackson2JsonRedisSerializer jsonRedisSSerializer = new GenericJackson2JsonRedisSerializer();
        // 设置Key的序列化
        template.setKeySerializer(RedisSerializer.string());
        template.setHashKeySerializer(RedisSerializer.string())
        //设置Value的序列化
        template.setValueSerializer(jsonRedisSerializer);
        template.setHashValueSerializer(jsonRedisSerializer);
        // 返回
        return template;
    }
}
方案2
  1. 使用StringRedisTemplate
  2. 写入Redis时,手动把对象序列化为JSON
  3. 读取Redis时,手动把读取到的JSON反序列化为对象
@lombok
public class User {
    private String name;
    private String age;
}
private static final ObjectMapper mapper = new ObjectMapper();

void testSaveUser throws JsonProcessingException {
    // 创建对象
    User user=new User(name:"Jack", age:21);
    // 手动序列化    
    String json = mapper.writeValueAsString(user);
    // 写入数据
    stringRedisTemplate.opsForValue().set("user:100", json);
    // 获取数据
    String jsonUser = stringRedisTemplate.opsForVaalue().get("user:100")
    // 手动反序列化
    User userl = mapper.readValue(jsonUser, User.class);
    System.out.println("user1 = "+ user1);
}

总结

        方案一中在进行反序列化的时候,会需要传类的全限定名。它虽然解决了可读性差的问题,但仍然存在占用内存较大的问题。因此大多时候,推荐自己手动序列化反序列化,使用方案2。