Redis学习与入门----深度历险(一)

2023-11-21 20:30

本文主要是介绍Redis学习与入门----深度历险(一),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

      • redis操作数据的基础数据结构
        • string
        • list
        • hash
        • set
        • zset(跳跃链表)
      • redis分布式锁
        • 分布式锁的使用
        • 分布式锁的超时问题
      • redis的简单延时队列
      • redis的位图数据结构
        • bitcount和bitpos
      • HyperLogLog(高级数据结构)
      • 布隆过滤器
        • 布隆过滤器是什么
        • 布隆过滤器的原理
        • 布隆过滤器一些的常用场景

redis操作数据的基础数据结构

通俗易懂的Redis数据结构基础教程

string

string是键值对的形式。Redis是将值存在内存中的,其为了避免内存的频繁分配,提供了预分配内存空间的方法。当字符串是少于1M的时候,是两倍扩容的方式。当内存大于1M时,采用+1M的方式扩容。字符串的最大可用内存为512M。

set 变量名 值
get 变量名
获取子串:getrange 变量名 start end
覆盖子串:setrange 变量名 start end
在最后追加子串:append 变量名 子串值
用作计数器:1.incrby 变量名 加数2.decrby 变量名 减数3.不写默认增加或减少14.计数器是有范围的,它不能超过Long.Max,不能低于Long.MIN
设置过期时间:expire 变量名 时间(默认秒为单位)
查看剩余生命:ttl 变量名 
删除变量:del 变量名
list

list结构,即链表结构,在redis中,提供的是双向链表。结合使用rpush/rpop/lpush/lpop四条指令,你可以将其作为队列形式,也可以只采用一端,形成栈的结构。

分为左和右:
rpush 链表名 值 值 值 ...
rpop 链表名
lpush 链表名 值 值 值 ...
lpop 链表名
计算链表长度: llen 链表名
通过下标访问链表:lindex 链表名 下标(从0开始)
通过lrange访问链表子串: lrange 链表名 start end
访问整个链表的方法:1.通过llen计算得到链表长度,lrange 链表名 0 链表长度-12.lrange 链表名 0 -1
更改指定链表下标的值:lset 链表名 下标 新值
在指定值前面或者后面插入值:linsert 链表名 before/after 指定值 插入值
删除指定值的元素:lrem 链表名 数量 删除值
定长链表的一个使用: 1.第一步:ltrim 链表名 起始下标 结束下标2.第二步:lrange 链表名 0 -1
hash

类似于JDK1.7的hashmap结构。以链表+数组的形式存储数据。

hset hash表名 key value
hmset hash表名 key value key value ....
hget hash表名 key
hmget hash表名 key key key ...
获取hash表中所有的键值对:hgetall hash表名
获取hash表中所有的key:hkeys hash表名
获取hash表中所有的value:hvals hash表名
删除key:hdel hash表名 key
判断key是否存在:hexits hash表名 key
hash也可以作为计数器来使用,只要每一个的值是整数:1.hincrby hash表名 key value(整数)2.如果value不是整数,使用hincrby会报错。

同hashmap一样,rehash是非常耗时的操作,所以redis的hash结构,采用的是渐进式的rehash。
rehash的时候,会保存新旧两个hash结构,查询的时候,同时查询两个结构,并在后面命令中,慢慢将旧hash中的值,传给新hash。当旧hash最后一个元素移除后,就删除该数据结构。
hash结构可以用来存储用户信息。不同于整个对象都要序列化的字符串,hash可以对用户的每个字段进行单独存储。减少了流量的浪费,但存储消耗高于字符串

set

Java程序员都知道HashSet的内部实现使用的是HashMap,只不过所有的value都指向同一个对象。Redis的set结构也是一样,它的内部也使用hash结构,所有的value都指向同一个内部值。

sadd 表名 元素 元素 元素 ...
列出集合元素: smembers 表名 
获取集合长度: scard 表名
获取随机元素: srandmember 表名
删除一个或多个指定值的元素:hrem 表名 元素 元素 ...
随机删除一个元素:spop 表名 
判断元素是否存在:sismember 表名 元素 
zset(跳跃链表)

SortedSet(zset)是Redis提供的一个非常特别的数据结构,一方面它等价于Java的数据结构Map<String, Double>,可以给每一个元素value赋予一个权重score,另一方面它又类似于TreeSet,内部的元素会按照权重score进行排序,可以得到每个元素的名次,还可以通过score的范围来获取元素的列表。

zset底层实现使用了两个数据结构,第一个是hash,第二个是跳跃列表,hash的作用就是关联元素value和权重score,保障元素value的唯一性,可以通过元素value找到相应的score值。跳跃列表的目的在于给元素value排序,根据score的范围获取元素列表

zadd 表名 score value score value ...
查看元素的权重:zscore 表名 value
正向查看元素,由小到大对元素进行排序(最小的返回0):zrank 表名 value
反向查看元素,由大到小对元素进行排序(最大的返回0):zrevrank 表名 value 
根据score范围获取其中的元素列表:zrangebyscore 表名 起始值 结尾值
带有score的返回:zrangebyscore 表名 起始值 结尾值 withscores
返回整个表的,由小到大:zrangebyscore 表名 -inf +inf withscores 
返回整个表的,由大到小:zrangebyscore 表名 +inf -inf withscores
移除范围内的元素:zremrangebyscore 表名 起始score 结束score

在这里插入图片描述

redis分布式锁

分布式应用在逻辑处理时经常会遇到并发问题。比如要修改内存中的一个对象的状态。那么步骤肯定大致分为三步,先读出该对象状态,再修改该对象状态,再将新状态写回内存。因为这样的操作不是原子操作,在并发情况下是很容易出现问题的。所以可以考虑使用分布式锁来限制程序的并发执行。

分布式锁的使用
  • 最开始分布式锁采用setnx(set if not exists)的方法去锁住一个对象操作,只允许一个客户端(即一个线程)来操作。
  • 后面发现如果在客户端获取到锁之后,却在中间发生了异常,导致锁没有释放,那么就会陷入死锁。所以我们可以在调用setnx后,再执行expire方法,对这个锁加上一个过期时间。但是如果异常发生在了setnx和expire之间,仍然会产生死锁,因为这两个操作并不是一个原子操作。
  • 在redis2.8之后,redis中加入了set的扩展参数nx和ex。使得nx和ex成为了一个原子操作。命令如下
    在这里插入图片描述
分布式锁的超时问题

分布式锁并不能解决超时问题,如果在加锁和释放锁中间的代码逻辑执行时间过长,导致还没有释放锁,这个锁就过期了。那么这个时候第二个线程就可以去向redis申请这个过期的锁作为自己的锁。这里就违背了redis锁的唯一性原则。
解决办法:
1.人工干预
2.使用lua脚本去给redis服务器发送请求,申请延时锁的释放时间。

redis的简单延时队列

因为list的数据结构,所以redis可以用来做一个简单的延时队列。
客户端通过redis的延时队列pop来获取消息,然后进行处理,如果该队列空了, 那么客户端就会陷入pop的死循环,也就是空轮询,这会拉高客户端的cpu,也会拉高redis的QPS。
解决的办法:
1.可以sleep当前线程一会
2.但即使sleep的再短,也会造成一定的延时,所以可以使用阻塞读,blpop。阻塞读会在队列没有数据的时候,进入休眠状态,当数据到来时,立马醒过来。
3.如果一直阻塞,那么就会造成空闲连接问题,闲置过久,redis可能会主动断开该连接,以减少闲置资源占用,这个时候blpop会抛出异常。所以客户端要注意捕获异常。

redis的位图数据结构

假设有这么一个场景,统计用户一年的签到记录,如果用缓存实现,那么就要记录365天,如果使用普通的key value,那么就是365个key value,那么当用户增多,这个存储空间是会很大的。
为了解决上述问题,redis提供了位图数据结构。这样每一个签到(以0或1)就只占一个位,一年就是365个位,即46个字节,那么就大大节约了存储空间。
注意:二进制码的顺序是从右往左,我们数组里的顺序是从左往右
下面根据hello的ASCII码,将其存入位图结构

#以h为例
#h的ASCII码转成二进制后,就是01101000
#我们只需要设置1的值
setbit key 位置 1
#上面的位置指的就是1的位置,对于h来说,那么就是1、2、4位(这里得注意,二进制码的顺序是从右往左,我们数组里的顺序是从左往右)。
#所以对于h的设置
setbit key 1 1
setbit key 2 1
setbit key 4 1
get key   打印会发现是h,如下图所示

在这里插入图片描述
再继续对key插入e的bit,那么就如下图一样
在这里插入图片描述

上面的操作可以理解为“零存整取”,即存的是字节,取的是字符。
那么当然也可以零存零取,整存零取

  • 对于整存零取,我们set a b
    在这里插入图片描述
    可以发现,获取8个位之后,组合之后是01100010,对应的值是不是97,不正是a吗?
  • 对于零存零取,只设置了b的第一位是1,所以取第二位是返回的是0 在这里插入图片描述
bitcount和bitpos
  • bitcount用来查找指定字符范围内出现的1的次数
    bitcount key start end
    在这里插入图片描述
  • bitpos用来查找指定字符范围内的第一个位的的位置
    在这里插入图片描述

HyperLogLog(高级数据结构)

Redis 提供了 HyperLogLog 数据结构就是用来解决这种统计问题的。HyperLogLog 提供不精确的去重计数方案,虽然不精确但是也不是非常不精确,标准误差是 0.81%,HyperLogLog 数据结构是 Redis 的高级数据结构。
其常用操作有pfadd,pfcount,pfmerge。pfmerge用于合并多个pfadd的值。
HyperLogLog可以用于大量用户相关的数据。对比set存储,可以说是非常高效的。其只需要占据12k的存储空间。这个可以解决许多精确不高的统计需求。
pfadd的用法类似于set集合的sadd,放入数据,计数+1
pfcount获取当前计数。

在这里插入图片描述
使用pfmerger,将指定的key当前的里面的元素数量追加到前面的指定的key的元素数量上。这个时候fruit的计数就变成了5,但其内部元素还是3
在这里插入图片描述

布隆过滤器

HyperLogLog这样的数据结构可以用来估计大概的值。但如果我们想知道某一个值是不是存在于其中,HyperLogLog并没办法做到这一点。

讲一个使用场景,我们在使用某app看推荐内容的时候,系统会自动推荐新的内容,它需要去重我们看过的内容,那么它是怎么实现的去重?
1、如果是遍历整个服务器的历史记录,将其存入数据库,那么对于数据库的exists查询请求就很频繁了,当并发量高的时候,数据库可能就会扛不住。
2、如果将整个缓存起来,那么浪费的存储空间太大了。时间一长,不仅存不下,性能也会下降。

这个时候,就可以采用布隆过滤器,可以专门解决这类问题,在达到去重的同时,在空间上还能节省90%以上。

布隆过滤器是什么

可以将其理解成一个不怎么精确的set结构。当我们使用它的contains方法判断某个对象是否存在时,它可能会误判。但可以人为将这个误判的几率调到很小。

布隆过滤器对于去重是这样的。它认为没见过的就一定是没见过的,它认为见过的(去重),那么可能是它没见过的(因为一些长得比较像的系数组合,导致它误以为它见过,这也是精度误差所在)。

Redis提供的布隆过滤器在Redis4.0提供插件功能之后才正式登场。

可以通过调整bf.reserve的参数来更改误判率。bf.reserve有三个参数,分别是key,error_rate(默认0.01),initial_size(默认100)。

布隆过滤器的error_rate(错误率)越小,需要的initial_size的值就越大。但initial_size的值越大,会浪费存储空间,估计的过小,影响准确率。所以在使用布隆过滤器前,要尽可能的精确估计好元素数量,还需要加上一定的冗余空间,避免实际元素数量大于估计元素数量。

布隆过滤器的原理

内部其实是一个位数组和多个散列性极好的hash函数。当要添加一个key时,就会经过多个hash散列函数的计算,得出具体的存放位置, 将具体的存放位置改为1。当需要查询该key时,也要通过多个hash函数去计算,对比每一个位置上是不是都是1,如果有一个不是,那么就判断该key不存在。

这个位数组越长,错误率越低,需要的hash函数的最佳数量也会增多,影响计算的效率。
可以通过网页上的布隆计算器,计算出具体的数值

布隆过滤器一些的常用场景
  • 可以用在爬虫去重网页URL上。
  • redis的缓存穿透,就是可以通过布隆过滤器来过滤掉大多数无用的请求。来降低数据库压力的。

这篇关于Redis学习与入门----深度历险(一)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/404920

相关文章

Redis在windows环境下如何启动

《Redis在windows环境下如何启动》:本文主要介绍Redis在windows环境下如何启动的实现方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录Redis在Windows环境下启动1.在redis的安装目录下2.输入·redis-server.exe

Redis实现延迟任务的三种方法详解

《Redis实现延迟任务的三种方法详解》延迟任务(DelayedTask)是指在未来的某个时间点,执行相应的任务,本文为大家整理了三种常见的实现方法,感兴趣的小伙伴可以参考一下... 目录1.前言2.Redis如何实现延迟任务3.代码实现3.1. 过期键通知事件实现3.2. 使用ZSet实现延迟任务3.3

Redis分片集群的实现

《Redis分片集群的实现》Redis分片集群是一种将Redis数据库分散到多个节点上的方式,以提供更高的性能和可伸缩性,本文主要介绍了Redis分片集群的实现,具有一定的参考价值,感兴趣的可以了解一... 目录1. Redis Cluster的核心概念哈希槽(Hash Slots)主从复制与故障转移2.

Spring Boot + MyBatis Plus 高效开发实战从入门到进阶优化(推荐)

《SpringBoot+MyBatisPlus高效开发实战从入门到进阶优化(推荐)》本文将详细介绍SpringBoot+MyBatisPlus的完整开发流程,并深入剖析分页查询、批量操作、动... 目录Spring Boot + MyBATis Plus 高效开发实战:从入门到进阶优化1. MyBatis

SpringCloud动态配置注解@RefreshScope与@Component的深度解析

《SpringCloud动态配置注解@RefreshScope与@Component的深度解析》在现代微服务架构中,动态配置管理是一个关键需求,本文将为大家介绍SpringCloud中相关的注解@Re... 目录引言1. @RefreshScope 的作用与原理1.1 什么是 @RefreshScope1.

Python 中的异步与同步深度解析(实践记录)

《Python中的异步与同步深度解析(实践记录)》在Python编程世界里,异步和同步的概念是理解程序执行流程和性能优化的关键,这篇文章将带你深入了解它们的差异,以及阻塞和非阻塞的特性,同时通过实际... 目录python中的异步与同步:深度解析与实践异步与同步的定义异步同步阻塞与非阻塞的概念阻塞非阻塞同步

Redis 中的热点键和数据倾斜示例详解

《Redis中的热点键和数据倾斜示例详解》热点键是指在Redis中被频繁访问的特定键,这些键由于其高访问频率,可能导致Redis服务器的性能问题,尤其是在高并发场景下,本文给大家介绍Redis中的热... 目录Redis 中的热点键和数据倾斜热点键(Hot Key)定义特点应对策略示例数据倾斜(Data S

redis+lua实现分布式限流的示例

《redis+lua实现分布式限流的示例》本文主要介绍了redis+lua实现分布式限流的示例,可以实现复杂的限流逻辑,如滑动窗口限流,并且避免了多步操作导致的并发问题,具有一定的参考价值,感兴趣的可... 目录为什么使用Redis+Lua实现分布式限流使用ZSET也可以实现限流,为什么选择lua的方式实现

Redis中管道操作pipeline的实现

《Redis中管道操作pipeline的实现》RedisPipeline是一种优化客户端与服务器通信的技术,通过批量发送和接收命令减少网络往返次数,提高命令执行效率,本文就来介绍一下Redis中管道操... 目录什么是pipeline场景一:我要向Redis新增大批量的数据分批处理事务( MULTI/EXE

Redis中高并发读写性能的深度解析与优化

《Redis中高并发读写性能的深度解析与优化》Redis作为一款高性能的内存数据库,广泛应用于缓存、消息队列、实时统计等场景,本文将深入探讨Redis的读写并发能力,感兴趣的小伙伴可以了解下... 目录引言一、Redis 并发能力概述1.1 Redis 的读写性能1.2 影响 Redis 并发能力的因素二、