redis基础和使用篇(一)--简介

文章目录

  • 1 redis简介
    • 1.1 什么是redis
    • 1.2 redis特性
  • 2 redis数据结构
    • 2.1 String--字符串
    • 2.2 List--列表
    • 2.3 hash--哈希
    • 2.4 Set--集合
    • 2.5 zset--有序集合
    • 2.6 3种高级数据结构
  • 3 redis使用场景
    • 3.1 热点数据的缓存
    • 3.2 分布式锁
    • 3.3 限时业务的运用
    • 3.4 计算器相关处理
    • 3.5 排行榜相关问题
    • 3.6 点赞、好友等相互关系的存储
  • 4 redis java客户端
    • 4.1 Jedis
      • 4.1.1 Jedis特点
      • 4.1.2 Jedis使用
    • 4.2 高级客户端Redisson
      • 4.2.1 Redisson特点
      • 4.2.2 Redisson集成springboot
    • 4.2 springBoot自带客户端生菜(lettuce)
      • 4.2.1 lettuce特点
      • 4.2.2 lettuce集成springboot

1 redis简介

1.1 什么是redis

Redis是现在最受欢迎的NoSQL数据库之一,一个使用ANSI C编写的开源、包含多种数据结构、支持网络、基于内存、可选持久性的键值对存储数据库。

1.2 redis特性

  • 基于内存运行,性能高效,网上资料显示单节点redis每秒可以执行10w条命令;
  • Redis的所有操作都是原子性的,同时Redis还支持对几个操作全并后的原子性执行
  • key-value存储系统,同时支持丰富的数据结构,string,list,set,zset,hash等;
  • 有丰富的内存优化和淘汰策略;
  • 支持 RDB和aof两种策略的持久化操作;
  • 支持主从同步配置,支持集群搭建。

2 redis数据结构

2.1 String–字符串

redis是使用C语言开发,但C中并没有字符串类型,只能使用指针或符数组的形式表示一个字符串,所以redis设计了一种简单动态字符串(SDS[Simple Dynamic String])作为底实现,有点类似于java的string的底层实现,只是redis中采用的是空间预分配的策略,SDS对象中包含三个属性:

  • Int len 表示此字符串的实际长度,buf中已经占有的长度
  • Int free 根据len> 1MB ? 1mb : len,表示 buf中未使用的缓冲区长度
  • Char buf[] 实际保存字符串数据的地方

2.2 List–列表

一个列表结构可以有序地存储多个字符串,可以使用lpush lpop rpush rpop等操作命令进行列表的读取和插入相关操作。3.2版本中底层实现基于quicklist,quicklist可以简单理解是一个ziplist组成的双向链表。
ziplist是一个经过特殊编码的双向链表,它的设计目标就是为了提高存储效率。ziplist可以用于存储字符串或整数,其中整数是按真正的二进制表示进行编码的,而不是编码成字符串序列。它能以O(1)的时间复杂度在表的两端提供push和pop操作。
实际上,ziplist充分体现了Redis对于存储效率的追求。一个普通的双向链表,链表中每一项都占用独立的一块内存,各项之间用地址指针(或引用)连接起来。这种方式会带来大量的内存碎片,而且地址指针也会占用额外的内存。而ziplist却是将表中每一项存放在前后连续的地址空间内,一个ziplist整体占用一大块内存。它是一个表(list),但其实不是一个链表(linked list)。
quicklist可以参考这篇博客

2.3 hash–哈希

redis的散列可以存储多个键 值 对之间的映射,散列存储的值既可以是字符串又可以是数字值,并且用户同样可以对散列存储的数字值执行自增操作或者自减操作。散列可以看作是一个文档或关系数据库里的一行。hash底层的数据结构实现有两种:

  1. 一种是ziplist,上面已经提到过。当存储的数据超过配置的阀值时就是转用hashtable的结构。这种转换比较消耗性能,所以应该尽量避免这种转换操作。同时满足以下两个条件时才会使用这种结构:
    当键的个数小于hash-max-ziplist-entries(默认512)
    当所有值都小于hash-max-ziplist-value(默认64)

  2. 另一种就是hashtable。这种结构的时间复杂度为O(1),但是会消耗比较多的内存空间。

2.4 Set–集合

redis的集合和列表都可以存储多个字符串,它们之间的不同和java的list与set类似,列表可以存储多个相同的字符串,而集合则通过使用散列表(hashtable)来保证自已存储的每个字符串都是各不相同的(这些散列表只有键,但没有与键相关联的值),redis中的集合是无序的。

2.5 zset–有序集合

有序集合和散列一样,都用于存储键值对:有序集合的键被称为成员(member),每个成员都是各不相同的。有序集合的值则被称为分值(score),分值必须为浮点数。有序集合是redis里面唯一一个既可以根据成员访问元素(这一点和散列一样),又可以根据分值以及分值的排列顺序访问元素的结构。它的存储方式也有两种:

  • 一种是ziplist结构,与上面的hash中的ziplist类似,member和score顺序存放并按score的顺序排列
  • 另一种是skiplist与dict的结合。
    skiplist是一种跳跃表结构,用于有序集合中快速查找,大多数情况下它的效率与平衡树差不多,但比平衡树实现简单。redis的作者对普通的跳跃表进行了修改,包括添加span\tail\backward指针、score的值可重复这些设计,从而实现排序功能和反向遍历的功能。一般跳跃表的实现,主要包含以下几个部分:
  • 表头(head):指向头节点
  • 表尾(tail):指向尾节点
  • 节点(node):实际保存的元素节点,每个节点可以有多层,层数是在创建此节点的时候随机生成的一个数值,而且每一层都是一个指向后面某个节点的指针。
  • 层(level):目前表内节点的最大层数
  • 长度(length):节点的数量。

有序列表使用skiplist保障有序性和访问查找性能,dict就用来存储元素信息,并且dict的访问时间复杂度为O(1)。

2.6 3种高级数据结构

  • bitmap,可以理解为redis实现的BloomFilter(布隆过滤器),bitmap并不是一种真实的数据结构,它本质上是String数据结构,只不过操作的粒度变成了位,即bit。因为String类型最大长度为512MB,所以bitmap最多可以存储2^32个bit;
  • Geo本身不是一种数据结构,它本质上还是借助于Sorted Set(ZSET),并且使用GeoHash技术进行填充。Redis中将经纬度使用52位的整数进行编码,放进zset中,score就是GeoHash的52位整数值。在使用Redis进行Geo查询时,其内部对应的操作其实就是zset(skiplist)的操作。通过zset的score进行排序就可以得到坐标附近的其它元素,通过将score还原成坐标值就可以得到元素的原始坐标。总之,Redis中处理这些地理位置坐标点的思想是:二维平面坐标点 --> 一维整数编码值 --> zset(score为编码值) --> zrangebyrank(获取score相近的元素)、zrangebyscore --> 通过score(整数编码值)反解坐标点 --> 附近点的地理位置坐标;
  • Streams,这是Redis5.0引入的全新数据结构,用一句话概括Streams就是Redis实现的内存版kafka。而且,Streams也有Consumer Groups的概念。通过Redis源码中对stream的定义我们可知,streams底层的数据结构是Radix Tree(基数树),Radix Tree事实上就几乎相同是传统的二叉树。仅仅是在寻找方式上,以一个unsigned int类型数为例,利用这个数的每个比特位作为树节点的推断。能够这样说,比方一个数10001010101010110101010,那么依照Radix 树的插入就是在根节点,假设遇到0,就指向左节点,假设遇到1就指向右节点,在插入过程中构造树节点,在删除过程中删除树节点。

3 redis使用场景

3.1 热点数据的缓存

随着移动互联网的到来,我们面对的三大难题就是海量数据,高可用和低延时,在这种背景下,redis作为一个基于内存的缓存数据库,优势就很明显。而项目中使用redis最多的场景也是作为热点数据的缓存,比如秒杀场景中的商品信息和详情等,游戏中的用户信息等,以及各种预加载的资源等。

3.2 分布式锁

现在服务基本都是集群+微服务方式部署,进行水平和垂直的拆分,保证服务的高可用和解耦,这个时候操作共享资源的时候,比如购物车模块大家都进行某个商品的抢购,请求可能会分发到不同的节点上,但是总库存是有限的,而且需要保证一致性,防止超卖或者少卖,jdk的相关锁是满足不了需求的,这种场景下,本身单线程能够保证事务,并且执行命令响应较快的redis就是首先,尤其redisson本身实现了一套分布式锁的api,使用起来很方便。

3.3 限时业务的运用

比如登陆token,游戏场景中某些限时彩蛋,或者一定实效性的验证码,这些是有时效性的,可以借助redis的expire方法,设置一个过期时间,对应的就是资源的有效时间,过期后redis会自动删除。

3.4 计算器相关处理

大家应该都听过分布式唯一id的生成,比较主流的有雪花算法和使用 Redis +mysql来预生成和分配 id,这里就是利用Redis的原子操作 INCR 和 INCRBY 来实现。类似场景还有分布式限流布隆过滤器底层实现等。

3.5 排行榜相关问题

针对排行榜问题,比如游戏中的战力排行榜,微博热搜榜,微信步数榜等,关系型数据库在排行榜方面查询速度普遍偏慢,所以可以借助redis的SortedSet进行热点数据的排序。

//将玩家对应的数据放入zset中,第一个参数是key,第二个参数是值,第三个是存储的用户信息
jedis.zadd(Constants.USER_RANK, userScore.getScore(), value);
//给对应的value的数据增加number的值
jedis.zincrby(Constants.SALES_LIST, number, value);
// 按照用户分数多少排行,取出前五名
jedis.zrevrangeWithScores(Constants.USER_RANK, 0, 4);

3.6 点赞、好友等相互关系的存储

Redis set对外提供的功能与list类似是一个列表的功能,特殊之处在于set是可以自动排重的,当你需要存储一个列表数据,又不希望出现重复数据时,set是一个很好的选择,并且set提供了判断某个成员是否在一个set集合内的重要接口,这个也是list所不能提供的。 又或者在微博应用中,每个用户关注的人存在一个集合中,就很容易实现求两个人的共同好友功能。
这个在奶茶活动中有运用,就是利用set存储用户之间的点赞关联的,另外在点赞前判断是否点赞过就利用了sismember方法,当时这个接口的响应时间控制在10毫秒内,十分高效。

4 redis java客户端

Redis的Java客户端很多,官方推荐和日常使用较多的有三种:Jedis、Redisson和lettuce。

4.1 Jedis

4.1.1 Jedis特点

  • 轻量,简洁,便于集成和改造
  • 支持连接池
  • 支持pipelining、事务、LUA Scripting、Redis Sentinel、Redis Cluster
  • 不支持读写分离,需要自己实现

4.1.2 Jedis使用

引入依赖

<dependency>
      <groupId>redis.clients</groupId>
      <artifactId>jedis</artifactId>
      <version>${jedis.version}</version>
 </dependency>

创建jedis链接
在这里插入图片描述
在这里插入图片描述

4.2 高级客户端Redisson

4.2.1 Redisson特点

  • 基于Netty实现,采用非阻塞IO,性能高
  • 支持异步请求
  • 支持连接池
  • 支持读写分离,支持读负载均衡,在主从复制和Redis Cluster架构下都可以使用
  • 支持同步/异步/异步流/管道流方式连接
  • 分布式锁和同步器
  • 多样化数据序列化
  • 分布式集合(map,Multimap,set,list等)
  • 文档较丰富,有中文文档

4.2.2 Redisson集成springboot

引入pom依赖

 <!--redisson start -->
        <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson-spring-boot-starter</artifactId>
            <version>3.10.6</version>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-actuator</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <!--redisson end -->
       <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
            <version>2.3</version>
        </dependency>

编写配置文件

server:
  port: 1006 # 服务监听端口

spring:
  application:
    name: metadata-manage
  profiles:
    active: dev # 程序运行环境,决定了使用哪个配置文件
  redis:
    redisson:
      config: classpath:config/redis/redisson-${spring.profiles.active}.yml

redisson-dev.yml

singleServerConfig:
  # 连接空闲超时,单位:毫秒
  idleConnectionTimeout: 10000
  pingTimeout: 1000
  # 连接超时,单位:毫秒
  connectTimeout: 10000
  # 命令等待超时,单位:毫秒
  timeout: 10000
  # 命令失败重试次数,如果尝试达到 retryAttempts(命令失败重试次数) 仍然不能将命令发送至某个指定的节点时,将抛出错误。
  # 如果尝试在此限制之内发送成功,则开始启用 timeout(命令等待超时) 计时。
  retryAttempts: 3
  # 命令重试发送时间间隔,单位:毫秒
  retryInterval: 1500
  # 重新连接时间间隔,单位:毫秒
  reconnectionTimeout: 3000
  # 执行失败最大次数
  failedAttempts: 3
  # 单个连接最大订阅数量
  subscriptionsPerConnection: 5
  # 客户端名称
  clientName: null
  # 节点地址
  address: ${REDIS_URL}
  # 密码
  password: ${REDIS_PASSWORD}
  # 发布和订阅连接的最小空闲连接数
  subscriptionConnectionMinimumIdleSize: 1
  # 发布和订阅连接池大小
  subscriptionConnectionPoolSize: 50
  # 最小空闲连接数
  connectionMinimumIdleSize: 32
  # 连接池大小
  connectionPoolSize: 64
  # 数据库编号
  database: 1
  # DNS监测时间间隔,单位:毫秒
  dnsMonitoringInterval: 5000
# 线程池数量,默认值: 当前处理核数量 * 2
threads: 0
# Netty线程池数量,默认值: 当前处理核数量 * 2
nettyThreads: 0
# 编码
codec: !<org.redisson.codec.JsonJacksonCodec> { }
# 传输模式
transportMode: "NIO"

项目中使用redisson

@Resource
public RedissonClient redissonClient;

//操作分布式map
RMap<Object, Object> map = redissonClient.getMap(uuid);

4.2 springBoot自带客户端生菜(lettuce)

4.2.1 lettuce特点

Lettuce是一个高性能基于Java编写的Redis驱动框架,底层集成了Project Reactor提供天然的反应式编程,通信框架集成了Netty使用了非阻塞IO,5.x版本之后融合了JDK1.8的异步编程特性,在保证高性能的同时提供了十分丰富易用的API。

  • 支持Redis的新增命令ZPOPMIN, ZPOPMAX, BZPOPMIN, BZPOPMAX;
  • 支持通过Brave模块跟踪Redis命令执行;
  • 支持Redis Streams;
  • 支持异步的主从连接;
  • 支持异步连接池;
  • 新增命令最多执行一次模式(禁止自动重连);
  • 全局命令超时设置(对异步和反应式命令也有效)。

4.2.2 lettuce集成springboot

springboot2.x开始默认集成lettuce,所以不需要额外引入lettuce的依赖包
maven依赖引入

<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-redis</artifactId>
		</dependency>
        <!-- lettuce pool 缓存连接池 -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
            <version>2.3</version>
        </dependency>
        <!-- jackson json 优化缓存对象序列化 -->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.9.6</version>
        </dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
        <dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>

配置文件编写


# Redis数据库索引(默认为0)
spring.redis.database=0
# Redis服务器连接密码(默认为空)
spring.redis.password=
# 连接超时时间(毫秒)
spring.redis.timeout=10000
 
# Lettuce
# 连接池最大连接数(使用负值表示没有限制)
spring.redis.lettuce.pool.max-active=8
# 连接池最大阻塞等待时间(使用负值表示没有限制)
spring.redis.lettuce.pool.max-wait=10000
# 连接池中的最大空闲连接
spring.redis.lettuce.pool.max-idle=8
# 连接池中的最小空闲连接
spring.redis.lettuce.pool.min-idle=0
# 关闭超时时间
spring.redis.lettuce.shutdown-timeout=100

项目中使用lettuce

@Autowired
    private RedisTemplate redisTemplate;

你可能感兴趣的:(温习,redis,redisson,java客户端,lettuce,数据结构)