Redis初探录

in 编程
关注公众号【好便宜】( ID:haopianyi222 ),领红包啦~
阿里云,国内最大的云服务商,注册就送数千元优惠券:https://t.cn/AiQe5A0g
腾讯云,良心云,价格优惠: https://t.cn/AieHwwKl
搬瓦工,CN2 GIA 优质线路,搭梯子、海外建站推荐: https://t.cn/AieHwfX9

天地有呼吸,是为息也……(づ ̄3 ̄)づ╭❤~

一:数据类型

1.String(字符串)

字符串的使用场景非常的广泛,一个常见的用途就是将信息结构体JSON序列化成字符串,进行存储。

需要注意的是Redis的字符串是动态的字符串,是可以修改的字符串,内部结构上类似于ArrayList,采用分配冗余的空间来减少内存的频繁分配。

如果value值是一个整数,还可以通过incr命令对它进行自增操作。范围是不超过 signed long的最大最小值。

2.list(列表)

Redlis的列表相当于java的LinkedList

常用来做异步队列使用。一个往里面放,一个从另一端轮训进行处理。

rpush 从右边放 多个字符串以 空格  隔开

llen 从左边弹出

llen 获取长度

同理模拟栈的 右边进右边出

rpush

rpop

同理左边...

左边是头右边是尾巴

但是list作为队列使用有几个问题:

3.hash(字典)

Redis的hash相当于java的hashmap(1.7)

4.set(集合)

Redis的set相当于java的HashSet

5.zset(有序列表)

zset类似于SortedSet和HashMap的结合体, 一方面它保证了内部Value的唯一性,另一方面它可以给每一个value赋值一个score,代表这个value的排序权重。

zset可以用来存储粉丝列表,value是粉丝的ID,score是关注时间。我们可以按照关注时间进行排序。

zset还可以用来存储学生的成绩,value是学生的ID,score是他们的成绩。

延时队列 消息序列化为一个zset的value(唯一),然后延时的市场:当前时间+延时秒数 做score排序,然后多线程轮询获取到期的任务进行处理

6.Geo

Redis的Geo模块才用了GeoHash算法,地图元素的数据都是经纬度,GeoHash算法将地球看成一个平面,然后划分成一系列足够小的方格,然后对这些方格进行整数编码,最终地图变为一个整数,通过这个整数可以还原出地图的坐标,位置越接近,整数编码就越接近。

在使用Redis的Geo时,我们只需意识到,他只是一个 zset(value为key,score为geohash的52位整数),通过zset的score排序就可以得到元素附近的坐标。

1. geoadd {name} {score} {key}
添加 多个见 空格隔开

2. geodist {name} {location1} {location2} {unit}
计算两个元素间的距离,集合名称,两个 名称和距离单位(m/km/ml/ft)

3. geopos {name} {key}
获取元素的经纬度坐标 多个间空格相隔

4. geohash {name} {key}
获取元素的经纬度编码字符

5. georadiusbymember 查询附近的元素
这个指令是最关键的,它的参数如下

georadiusbymember company ireader 20 km count 3 asc

范围 20 公里以内最多 3 个元素按距离正排,它不会排除自身

georadiusbymember company ireader 20 km withcoord withdist withhash count 3 asc

# 三个可选参数 withcoord withdist withhash 用来携带附加参数
# withdist 很有用,它可以用来显示距离

6. georadius 查询坐标附近的元素,他和上面很相似,唯一区别就是通过坐标来的,一般根据用户的定位来计算附近的车,附近的餐馆

georadius company 116.514202 39.905409 20 km withdist count 3 asc

一般建议 Geo 的数据使用单独的 Redis 实例部署,不使用集群环境。(数据迁移key太多,会卡顿)
如果数据量过亿甚至更大,就需要对 Geo 数据进行拆分,按国家拆分、按省拆分,按市拆分,在人口特大城市甚至可以按区拆分。这样就可以显著降低单个 zset 集合的大小。

7.bitmap(位图)

我们平时开发中,会有一些bool的类型数据需要存储,比如一年的签到记录。

因为在使用key/value来存储的时候,每个用户要存储365个,比较耗费空间。

为了解决这个问题,redis提供了bitmap,一个记录只占一个位,一年46字节。

位图不是特殊的数据结构,他就是一个byte数组

getbit key index 

setbit key index 0/1

bitcount key

8.HyperLogLog

用于不精确的统计,有一定的误差,但是可以替代set去重的方式用于统计,大大的节省了空间

但是不能验证是否存在,只可以做统计

误差标准是0.81%

pfadd name key

pfcount name

pfmerge 用于将多个pf的值加在一起 形成一个新的pf

顺便说一句:pf是发明这个数据结构的人爆炸头的名字首字母缩写

9.Bloom Filter(布隆过滤器)

比如在一个推荐系统里面,我们需要去掉已经看过的内容。
这个时候布隆过滤器就闪亮登场了,它就是解决这类去重问题的,同时还能节省90%的空间,只是没那么精确。

bf.reserve {key} {error_rate} {size}

创建一个空的布隆过滤器,并设置一个期望的错误率和初始大小。{error_rate}
过滤器的错误率在0-1之间,如果要设置0.1%,则应该是0.001。该数值越接近0,
内存消耗越大,对cpu利用率越高。

10.Redis-Cell -- 漏斗限流

常用来拒绝用户的多次访问,比如拦截恶意回帖


cl.throttle test 100 400 60 3


test: redis key

100: 官方叫max_burst,漏斗容量

400: 与下一个参数一起,表示在指定时间窗口内允许访问的次数
60: 指定的时间窗口,单位:秒
单位时间60秒可以访问400次


3: 表示本次要申请的令牌数,不写则默认为 1

以上命令表示从一个初始值为100的令牌桶中取3个令牌,该令牌桶的速率限制为
400次/60秒。

11.Stream --消息队列

Redis Stream本质上是在Redis内核上(非Redis Module)实现的一个消息发布订阅功能组件。相比于现有的PUB/SUB、BLOCKED LIST,其虽然也可以在简单的场景下作为消息队列来使用,但是Redis Stream无疑要完善很多。Redis Stream提供了消息的持久化和主备复制功能、新的RadixTree数据结构来支持更高效的内存使用和消息读取、甚至是类似于Kafka的Consumer Group功能。

二. Redis发布订阅

1.Pub/Sub

subscribe {name}
订阅主题

publish {name} {message} 向主题推送消息

三.Redis实现分布式锁

1.分布式锁的本质就是,在Redis里设置一个标志,当别的线程也要来时,发现别的线程已经占用了,只好放弃或者稍后再试。
锁的占有操作一般是通过 setnx指令,程序执行完成再释放标志。

但是有一个问题:如果这个程序在执行过程中发生了异常,可能会导致释放这个标志的指令没有调用,这样锁永远得不到释放,也就是死锁了。

也有人给标志设置了过期时间,这样就算忘了释放,过期后还是被释放的掉了。

但是setnx和expire必须是原子指令,这个指令就是set指令

SET {resource-name} {anystring} NX max-lock-time

1. NX-SETNX
只在键不存在时,才对键进行设置操作

2. EX-SETEX 
设置键的过期时间为 second 秒

3. PX-PSETEX
PSETEX key millisecond value 。设置键的过期时间为 millisecond 毫秒

4.  XX :只在键已经存在时,才对键进行设置操作。

上述锁 set nx不能解决超市 问题,如果在加锁和释放锁之间执行的逻辑太长,以至于超出了锁的限制,就会出现问题,第一个程序执行到一半,标识过期被第二个程序获得锁。

为了避免这个问题Redis的分部锁,不要用于时间较长的任务,如果真的偶尔出现了,小拨乱数据需要人工介入处理

还有一个更安全的方案是为set指令的 value参数设置一个随机数,释放锁时先匹配随机数是否一致,一致再释放锁,不一致则说明自己的锁 已经过期了现在这个锁不是自己的不要释放。需要借助Lua脚本
(这个只能解决第一次程序 结束不会删掉【第二次程序的锁】,不让第三个程序进入,只是1-2锁出问题了)

Redis 线程IO

1.redis单线程死如何处理那么多并发的

线程在读写IO时可以不必阻塞等待,读写可以瞬间完成然后干别的事情

NIO技术类似select轮询,但是现在已经不是了

Redis Pipeline(管道)

如果连续执行多条指令就会花费多个网络数据包来回链接,浪费时间

Redis的读写过程

1、客户端进程调用write将消息写到操作系统内核为套接字分配的发送缓冲send buffer。

2、客户端操作系统内核将发送缓冲的内容发送到网卡,网卡硬件将数据通过「网际路由」送到服务器的网卡。

3、服务器操作系统内核将网卡的数据放到内核为套接字分配的接收缓冲recv buffer。

4、服务器进程调用read从接收缓冲中取出消息进行处理。

5、结束。

什么是管道

管道的本质是改变了客户端的请求发送操作,将多条请求的 请求写操作一次性发出,服务器那边还是正常的处理,返回。管道中指令越多,越节省时间。

对于redis的客户端来讲
write操作是基本不耗时的
red操作是阻塞的,它需要等待缓冲数据到来然后返回,缓冲为空的时候,就在等待

所以多条指令顺序执行对于客户端视角:

1.写第一条指令(不耗时)
2.读第一条结果(IO耗时)

3.写第二条指令(不耗时)
4.读第二条结果(IO耗时)

管道的其实就是把这多条的写请求先发出去

1.写第一条指令(不耗时)
1.写第二条指令(不耗时)

读第一条结果(IO耗时) 读第二条结果(IO耗时)

所以Redis管道的本质是客户端通过改变了读写的顺序带来的性能的巨大提升。

Redis管道的特点

打包的命令越多,缓存消耗内存也越多。所以并是不是打包的命令越多越好。此外,pipeline期间将“独占”当前redis连接,此期间将不能进行非“管道”类型的其他操作,直到该pipeline关闭

pipeline不是原子性的,中间可能会存在部分失败的情况,也就是说不能保证每条命令都能执行成功,如果中间有命令出现错误,redis不会中断执行,而是直接执行下一条命令,然后将所有命令的执行结果(执行成果或者执行失败)放到列表中统一返回,如果需要每条命令都执行成功,我们在批量执行过程中需要监控执行数量和返回的成功数量是否一致。

pipeline管道使用场景

Pipeline在某些场景下非常有用,比如有多个command需要被及时提交,而且他们对相应结果没有互相依赖,对结果响应也无需立即获得,那么pipeline就可以充当这种批处理的工具;

Redis事务

Redis的事务没有原子性
Redis的事务具有隔离性--串行化执行

multi  开启器
exec 退出

discard 丢弃(类似回滚)

事务开始后 要么提交要么 丢弃

一般事务都是和pipeline一起使用

Watch Redis的乐观锁

当我们在一组事务中要分布式的操作,需要读取redis中的数据,然后再写进去新的值,在这个过程中,为了避免其他线程获取到这个还没有更新进去的值而赋值覆盖。

上述问题我们可以使用redis设计分布式锁来解决,这是一种悲观锁。redis针对自己的这种情况有一个乐观锁--watch

watch {name}

multi

exec

watch 必须在multi 和exec之前,否则会出错

在食物开始前盯住1个或者多个变量,当事务执行时,也就是exec要顺序的执行缓存的事务队列时,Redis会检查自watch后,变量是否被修改了,如果变量被修改了则告知客户端执行失败。这个时候一般客户端选择重试。

关注公众号【好便宜】( ID:haopianyi222 ),领红包啦~
阿里云,国内最大的云服务商,注册就送数千元优惠券:https://t.cn/AiQe5A0g
腾讯云,良心云,价格优惠: https://t.cn/AieHwwKl
搬瓦工,CN2 GIA 优质线路,搭梯子、海外建站推荐: https://t.cn/AieHwfX9
扫一扫关注公众号添加购物返利助手,领红包
Comments are closed.

推荐使用阿里云服务器

超多优惠券

服务器最低一折,一年不到100!

朕已阅去看看