Redis学习笔记——基础篇p1-p22

前言:

今天主要学习了Redis基础知识,比如它的通用命令和Redis的java客户端的一些知识。写一个学习日记记录一下所学的知识。

学习收获:

首先Redis是一种NoSQL数据库,所以在介绍Redis之前先介绍一下NoSQL。

NoSQL:

NoSQL与SQL不同的是,它是非关系型数据库,旨在解决传统关系型数据库在处理海量数据、高并发场景、灵活数据模型的局限性,并且更注重高性能、高可用性和可扩展性。S:Structured意为结构化,按照约定去插入数据库,并且结构不能改变。而NoSQL就是一种非结构化的数据库,比如说Redis,Document,Graph等,这些数据结构都没有严格的要求,比较松散。

并且NoSQL的表与表之间也是无关联的。NoSQL也没有固定的语法格式,使用比较简单,没有复杂的语法。

 不过相比与SQL,NoSQL无法满足事务的ACID这种强一致特性,但是它采用BASE理论(基本可用、软状态、最终一致性)来牺牲强一致性换取性能。

NosQL vs SQL:

Redis: 

Redis(Remote Dictionary Server),远程字典服务器,是一个基于内存的键值型Nosql数据库。

特征:

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

通过RDB定期快照,将内存数据保存到磁盘,来进行备份和恢复。或者AOF来记录写操作日志,数据更安全,支持重写机制避免文件过大。

  • 支持主存集群、分片集群

主存集群可以就是说 从节点可以备份主节点的数据,如果主节点宕机,数据在从节点也能被找得到,并且还能提高IO的效率。而分片集群就是把数据进行了拆分,比如把这些数据拆为了n份,存在不同的节点上去,然后用很多机器一起存,增加了存储的上限。

  • 支持多语言客户端

比如说Jedis(Java)、Redis-py(Python)等。 

Redis是一个key-value的数据库,key一般是String类型,不过value的类型多种多样。

key的结构: 

Redis的key容许有多个单词形成层级结构,多个单词之间用‘ :’隔开,格式如下:

比如我们的项目名称叫dianping,有user和product两种不同的类型的数据,我们可以这样定义key:

dianping:user:1
dianping:product:1

如果Value是一个Java对象,比如一个User对象,则可以将对象序列化为JSON字符串后存储 。

String类型: 

String类型,也就是字符串类型,是Redis中最简单的存储类型。其value是字符串,不过根据字符串的格式不同,又能分为3类:

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

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

常用的命令:

SET key "value"           # 设置值
GET key                   # 获取值
INCR counter              # 计数器自增(原子性)
DECRBY counter 5          # 计数器减5
SETNX key "value"         # 仅当键不存在时设置(用于分布式锁)
SETEX key 3600 "value"    # 设置值并同时设置过期时间(秒)
MSET user:1:name "Alice" user:1:age 25 user:1:city "Beijing" #批量操作

Hash类型:

Hash类型,也叫散列,它的value是一个无序字典,类似于Java中的HashMap结构。因为如果用String结构是将对象序列化为JSON字符串后存储,当需要修改对象某个字段时很不方便。而Hash结构可以将对象中的每个字段独立存储,可以针对带个字段做CRUD:

 并且相比多个String存储,Hash可以减少内存随便和键空间的消耗。

常用命令:

HSET user:1 name "Alice"        # 设置哈希字段
HGET user:1 name                # 获取单个字段
HGETALL user:1                  # 获取所有字段和值
HINCRBY user:1 age 1            # 字段自增
HDEL user:1 email               # 删除字段

List类型:

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

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

List的底层实现取决于数据量和元素大小:

  • 压缩列表(ziplist):

适合于小数据量(元素少,长度短),通过连续内存块存储,节省空间。如果当列表长度或元素大小超过阈值时,自动转为双向列表。

  • 双向链表(linkedlist):

适用于大数据量,每个节点包含前驱和后继指针,支持快速的增删改查。并且Redis引入quicklist(双向链表+压缩列表)优化内存占有,每个节点是一个压缩列表,减少链表节点的数量。

常用命令:

LPUSH key value [value ...]

从列表左侧(头部)插入一个或多个元素。
RPUSH key value [value ...]从列表右侧(尾部)插入一个或多个元素。
LPOP key删除并返回列表左侧第一个元素。
RPOP key删除并返回列表右侧第一个元素。
LLEN key获取列表长度。
LRANGE key start stop获取列表中指定区间的元素(索引从 0 开始,-1 表示最后一个元素)。
LSET key index value修改指定索引位置的元素值。
LREM key count value删除列表中前 count 个值为 value 的元素(count>0 从左往右删,count<0 从右往左删,count=0 删所有)。
LPUSHX key value仅当列表存在时,从左侧插入元素(原子操作)。
RPUSHX key value仅当列表存在时,从右侧插入元素(原子操作)。
LINDEX key index获取指定索引位置的元素。

应用场景:

朋友圈点赞或者评论(谁先谁后)。 

Set类型:

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

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

并且Set的底层实现也是由元素类型和数量决定的:

  • 整数集合:

适用于所有元素都是整数且数量较少的场景。并且用有序数据存储整数,节省内存,但插入非整数时会自动转换为哈希表。

  • 哈希表:

适用于元素为非整数或者数量超过阈值的场景。基于哈希表实现,插入、删除、查询的平均复杂度为O(1).。并且Redis6.0引入渐进式rehash,避免大规模数据迁移时阻塞主线程。

常用命令:

SADD key member [member ...]向集合中添加一个或多个元素(自动去重)。
SREM key member [member ...]从集合中删除一个或多个元素。
SCARD key获取集合中元素的数量。
SISMEMBER key member判断元素是否存在于集合中(存在返回1,不存在返回0)。
SMEMBERS key返回集合中的所有元素(顺序随机)。
SRANDMEMBER key [count]随机返回集合中的count个元素(不指定count时返回 1 个,可用于抽样)。
SPOP key [count]随机删除并返回集合中的count个元素(不指定count时删除 1 个)。
SINTER key [key ...]返回多个集合的交集(共同元素)。
SUNION key [key ...]返回多个集合的并集(所有元素去重)。
SDIFF key [key ...]返回第一个集合与其他集合的差集(存在于第一个集合但不存在于其他集合的元素)。

应用场景:

共同好友列表,通过多个集合的交集来实现。

SortedSet类型:

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

SortedSet具备一下特性:

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

其底层的跳表(SkipList) + 哈希表:一般元素数量较多或者分数为浮点数使用。

  • 跳表:按分数排序,支持快速范围查询。
  • 哈希表:存储元素到分数的映射。

常用命令:

ZADD key score member [score member ...]向集合中添加元素及分数(重复元素会更新分数)。
ZREM key member [member ...]删除集合中的一个或多个元素。
ZCARD key获取集合中元素的数量。
ZSCORE key member获取元素的分数。
ZRANK key member获取元素的排名(按分数升序,从 0 开始)。
ZREVRANK key member获取元素的排名(按分数降序,从 0 开始)。
ZRANGE key start stop [WITHSCORES]按分数升序返回指定索引范围的元素(startstop为索引,0-based)。
ZREVRANGE key start stop [WITHSCORES]按分数降序返回指定索引范围的元素。
ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count]按分数范围升序查询元素(min≤score≤max(min表示不包含min)。
ZREVRANGEBYSCORE key max min [WITHSCORES] [LIMIT offset count]按分数范围降序查询元素。
ZINCRBY key increment member为元素的分数增加指定增量(支持负数)。
ZCOUNT key min max统计分数在指定范围内的元素数量。

应用场景: 

排行榜系统,比如游戏积分排名、商品销量排行等。

Redis的Java客户端:

Jedis:

以Redis命令作为方法的名称,学习成本低,简单实用。但是Jedis实列是线程不安全的,多线程环境下需要基于连接池来使用。

  • 再使用Jedis时,我们首先要引入依赖:

  • 然后建立连接:

  • 此时我们就可以进行测试,比如测试String类型:

  • 最后释放资源。

Jedis连接池:

Jedis本身是线程不安全的,如果在多线程且并发的情况下,并且频繁的创建和销毁连接会有性能损耗,所以要通过使用Jedis连接池来连接。

// 配置连接池参数
JedisPoolConfig poolConfig = new JedisPoolConfig();
poolConfig.setMinIdle(0);          //最小空闲连接
poolConfig.setMaxTotal(100);       // 最大连接数
poolConfig.setMaxIdle(20);         // 最大空闲连接数
poolConfig.setTestOnBorrow(true);  // 取连接时测试可用性



// 创建连接池(有密码版本)
JedisPool jedisPool = new JedisPool(poolConfig, 192.168.150.101, 6379, 3000, "123456");



//从连接池获取连接
try (Jedis jedis = jedisPool.getResource()) {
    // 使用 Jedis 执行 Redis 命令
    jedis.set("key", "value");
    String value = jedis.get("key");
} catch (Exception e) {
    // 处理异常
}

JedisPool(连接池对象)传入的是连接的一些参数,而JedisPoolConfig(连接池配置)传入的是池的一些参数。并且连接池一般就配置:最大连接数,空闲连接数和等待时间。

SpringDataRedis:

SpringData是Spring中数据操作的模块,包含对各种数据库的集成,其中对Redis的集成模块就叫做SpringDataRedis。它集成了Spring生态,可以无缝整合Spring的依赖注入、事务管理、缓存抽象等特性。所以SpringDataRedis有很多功能:

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

他的核心组件就是RedisTemplate,其中封装了各种对Redis的操作。

 SpringDataRedis使用:
  • 引入依赖:

不管是我们的Redis还是Jedis,底层都会通过commons-pool2实现连接池效果。

  • 配置文件:

Springboot的自动装配,可以让我们不用 用编码的方式来配置文件,直接在application.yml配置Redis信息。

  • 注入RedisTemplate:

  • 编写测试:

 直接注入RedisTemplate,因为SpringBoot实现了自动装配,并且Spring默认使用的lettuce连接池。

SpringDataRedis的序列化方式:

RedisTemplate可以接收任意的Object作为值写入Redis,只不过写入前会把Object序列化为字节形式,默认采用JDK序列化,得到的结果就是这样:

所以这种序列化方式的缺点就是:

  • 可读性差
  • 内存占用大 

具体来说就是因为RedisTemplate的set方法接收的是Object并不是String,所以就会都把他们作为Java对象帮我们转为我们所需要的字节,底层采用了JDK的序列化方式。

在没定义上面四个值时,就会走上图的第二个这个默认的jdk序列化器。而jdk的序列化器用Object输出流来写出对象,就会把Java对象转为字节。所以采会看到下面这种字节内容。

为了解决这种可读性差我们有两种方式:

  •  Key使用StringRedis序列化器,Value使用GenericJackson2Json序列化器。所以可以自定义RedisTemplate的序列化方式:

虽然JSON这种的序列化方式可以满足我们的需求,但还是会存在一些问题,如下:

这种方式为了在反序列化时知道对象的类型,JSON序列化器会将类的class类型写入json结果中,存入Redis,带来额外的内存开销。所以我们为了节省内存空间,我们并不会使用JSON序列化器处理value,而是统一使用String序列化器,要求只能存储String类型的key和valu。当需要存储Java对象是,去手动完成对象的序列化和反序列化。 所有就有了我们的第二种。

  • 手动序列化和反序列化对象:

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

public class StringRedisTemplate extends RedisTemplate<String, String> {

	/**
	 * Constructs a new <code>StringRedisTemplate</code> instance. {@link #setConnectionFactory(RedisConnectionFactory)}
	 * and {@link #afterPropertiesSet()} still need to be called.
	 */
	public StringRedisTemplate() {
		setKeySerializer(RedisSerializer.string());
		setValueSerializer(RedisSerializer.string());
		setHashKeySerializer(RedisSerializer.string());
		setHashValueSerializer(RedisSerializer.string());
	}

	/**
	 * Constructs a new <code>StringRedisTemplate</code> instance ready to be used.
	 *
	 * @param connectionFactory connection factory for creating new connections
	 */
	public StringRedisTemplate(RedisConnectionFactory connectionFactory) {
		this();
		setConnectionFactory(connectionFactory);
		afterPropertiesSet();
	}

	protected RedisConnection preProcessConnection(RedisConnection connection, boolean existingConnection) {
		return new DefaultStringRedisConnection(connection);
	}
}

 

通过这种方式读出来的反序列化后的User对象:

总结:

今天回顾了Redis的一些常见的数据结构已经他们的常用命令。并且了解了Jedis和SpringDateRedis的实现方式、如何使用已经他们一些底层的实现原理。总之收获颇丰,特此记录一下。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值