Redis笔记_1
全局唯一ID
: 经常会需要全局唯一ID
,比如订单表的生成。因为
- 如果
ID
自增,则规律性太明显 - 并且表中可能存在大量的数据,会受到表单数据量的限制
全局唯一ID
生成器特性:
- 唯一性
- 高可用
- 高性能
- 递增性
- 安全性
超卖问题
- 原本库存为1,线程1查询后发现库存满足要求。要去扣除库存,但在此之前,线程2,线程3同时查询,发现库存充足。同样满足扣除库存的要求,此时产生了超卖问题
解决方法: 加锁
-
乐观锁: 认为线程安全问题不一定发生,因此不加锁,只是在更新数据时判断有没有其他线程对数据进行了修改
- 如果没有修改则认为自己安全,才能更新数据
- 如果已经被其他线程修改说明发生了安全问题,此时重试或返回异常
-
悲观锁: 认为线程安全问题一定会发生,因此操作数据之前先获取数据,确保线程串行执行。(性能差)
- 比如
Synchronized、Lock
都是悲观锁
- 比如
乐观锁
- 版本号法
- 用版本标识数据更新,如果版本
version
更新了,则表示数据已经被更新了。
- 用版本标识数据更新,如果版本
- CAS法
- 用之前查询到的数据判断是否存在相同的数据,如果不存在,则表示数据已经更新。实际上就是简化了版本字段。
这样存在问题,因为如果有一百个线程,初始情况下stock=100
,第一个线程首先执行查询,满足,然后修改数据库stock - 1
。在修改数据库之前,其他线程查询stock=100
,也满足查询条件,但是实际上查询数据库只有stock=99
了,发现被修改了,因此直接错误退出。
所以应该使用stock>0
而不是等于前一个值
分布式锁
- 满足分布式系统或集群模式下多进程可见并且互斥的锁
要求
- 多进程可见即所有
JVM
可见 - 互斥
- 高可用
- 高性能
- 安全性
MySQL | Redis | Zookeeper | |
---|---|---|---|
互斥 | 利用MySQL本身互斥锁机制 | 利用SETNX互斥命令 | 利用节点的唯一性和有序性实现互斥 |
高可用 | 好 | 好 | 好 |
高性能 | 一般 | 好 | 一般 |
安全性 | 断开连接,自动释放锁 | 利用锁超市时间,到期释放 | 临时节点,断开连接自动释放 |
基于Redis
的分布式锁
分布式锁需要实现两个方法:获取锁和释放锁
- 获取锁
- 互斥: 确保只有一个线程能够获取锁
setnx lock thread1
,expire lock 5
- 但是上面这种两条语句的方法不是原子的,有可能执行了一条后线程中断
- 可以使用
set lock thread1 nx ex 5
合并成一条语句
- 互斥: 确保只有一个线程能够获取锁
- 释放锁
- 手动释放:
del key
- 超时释放: 获取锁的时候设置超时时间
- 手动释放:
Redisson
分布式锁原理
- 可重入: 利用
hash
结构记录线程ID
和重入次数 - 可重试: 利用信号量和
pubsub
功能实现等待、唤醒,获取锁失败的重试机制 - 超时续约: 利用
watchDog
,每隔一段时间重置超时时间
Redis
消息队列
基于List
的消息队列的优缺点
- 优点:
- 利用
Redis
存储,不受限与JVM
内存上限 - 基于
Redis
持久化机制,保证数据安全性 - 满足消息有序性
- 利用
- 缺点:
- 无法避免消息丢失
- 只支持单消费者
基于PubSub
的消息队列
- 发布订阅,一个消费者可以订阅一个或多个
channel
,生产者向对应的channel
发送消息后,所有订阅者可以收到相关信息 subscribe channel [channel]
: 订阅一个或多个频道publish channel msg
: 向一个频道发送消息psubscribe pattern [pattern]
: 订阅与pattern格式匹配的所有频道- 优点:
- 采用发布订阅模型,支持多生产,多消费
- 缺点:
- 不支持持久化
- 无法避免消息丢失,不安全
- 消息堆积有上限,超出时数据丢失
基于Stream
的消息队列
- 发送消息:
xadd key [NOMKSTREAM] [MAXLEN|MINID [=|~] threadhold [LIMIT count]] *|ID field value [field value ...]
[NOMKSTREAM]
: 如果队列不存在,是否自动创建队列,默认是动创建[MAXLEN|MINID [=|~] threadhold [LIMIT count]]
: 设置消息队列的最大消息数量*|ID
: 消息的唯一ID
,*代表由Redis
自动生成,格式是时间戳-递增数字
field value [field value ...]
: 发送到队列中的消息,称为Entry
,格式就是多个键值对
- 读取消息:
XREAD [COUNT count] [BLOCK milliseconds] STREAMS key[key ...] ID [ID ...]
[COUNT count]
: 每次读取消息的最大数量[BLOCK milliseconds]
: 没有消息时,是否阻塞,阻塞时长STREAM key [key ...]
: 要从哪个队列读取消息,key
就是队列名ID [ID ...]
: 起始ID
,只返回大于该ID
的消息。0表示从第一条消息开始,$表示从最新的消息开始
指定$
表示读取最新的消息,但是如果一下到来几条消息,依然只会读取最新的消息,导致漏读
- 优点:
- 消息可回溯
- 一个消息可以被多个消费者读取
- 可以阻塞读取
- 缺点:
- 有消息漏镀的风险
消费者组
-
将多个消费者划分到一个组中,监听同一个队列
-
特点:
- 消息分流: 队列中的消息会分流给组内不同的消费者,不是重复的消费,从而加快消息处理的速度
- 消息标识: 消费者组维护一个标识,记录最后一个被处理的消息,哪怕消费者宕机,还会从标识之后读取消息,确保每一个消息都会被消费
- 消息确认: 消费者获取消息后,消息处于
pending
状态,存入pending-list
。当处理完成后需要通过XACK
来确认消息,标记消息为已处理,才会从pending-list
中移除(可以确保所有的消息都能被消费到)
-
创建消费者组:
xgroup create key groupName ID [MKSTREAM]
key
: 队列名称groupName
: 消费者组名称ID
: 起始ID
标识,$
标识队列中最后一个消息,0
标识队列中第一个消息MKSTREAM
: 队列不存在时自动创建队列
-
删除消费者组:
xgroup destroy key groupName
-
删除消费者组中指定的消费者:
xgroup delconsumer key groupname consumername
-
给指定的消费者组添加消费者:
xgroup createconsumer key groupname consumername
-
从消费者组读取消息:
xreadgroup GROUP group consumer [COUNT count] [BLOCK milliseconds] [NOACK] STREAMS key [key ...] ID [ID ...]
group
: 消费者组名称consumer
: 消费者名称,如果消费者不存在,则会自动创建一个count
: 本次查询的最大数量BLOCK milliseconds
: 当没有消息时最长等待时间NOACK
: 无需手动ACK,获取到消息后自动确认STREAMS key
: 指定队列名称ID
: 获取消息的起始ID- ‘>’: 从下一个未消费的消息开始
- 其他: 根据指定ID从
pending-list
中获取已经消费但是没有确认的消息,比如0,是从pending-list
中的第一个消息开始
-
特点:
- 消息可回溯
- 可以多消费者争抢消息,加快消费速度
- 可以阻塞读取
- 没有消息漏读风险
- 有消息确认机制,保证消息至少被消费一次
List | PubSub | Stream | |
---|---|---|---|
消息持久化 | 支持 | - | 支持 |
阻塞读取 | 支持 | 支持 | 支持 |
消息堆积处理 | 受限于内存空间,可以利用多消费者加快处理 | 受限于消费者缓冲区 | 受限于队列长度,可以利用消费者组提高消费速度,减少堆积 |
消息确认机制 | - | - | 支持 |
消息回溯 | - | - | 支持 |
GEO
GEOADD
: 添加一个地理空间信息,包括经度、纬度、值GEODIST
: 计算指定两个点之间的距离并返回GEOHASH
: 将值的坐标转为哈希字符串形式并返回GEOPOS
: 返回指定值的坐标GEORADIUS
: 指定圆心、半径,找到院内包含的所有值,按照圆心之间的距离排序后返回,6.2版本以后废弃GEOSEARCH
: 指定范围内搜索值,按照与指定点之间的距离排序后返回,范围可以是圆形或矩形GEOSEARCHSTORE
: 与GEOSEARCH
功能一致,不过可以把结果存储到一个指定的key中
BitMap
Redis
中使用String
类型实现BitMap
,最大上限512M,即SETBIT
: 向指定位置offset
存入一个0 or 1BITCOUNT
: 统计BitMap
中值为1的bit
位的数量BITFIELD
: 操作BitMap
中bit数组中的指定位置offset
的值,实现了查询修改以及自增,一般用来查询BITFIELD_RO
: 获取BitMap
中bit数组,并以十进制形式返回BITOP
: 将多个BitMap
的结果位运算BITPOS
: 查找bit数组中指定范围内第一个0 or 1出现的位置
HyperLogLog(HLL)
-
UV
:Unique Visitor
独立访客量,通过互联网访问,浏览这个网页的自然人。一天内同一个用户多次访问,只记录一次 -
PV
:Page View
页面访问量或点击量,用户没访问网站的一个页面,记录次PV
,用来衡量网站的流量 -
UV
在服务端做会麻烦,因为要判断该用户是否统计过了,所以需要保存用户信息。但是如果每个访问的用户都保存到Redis
中,则数据量很大 -
HLL
是一种概率算法,用于确定非常大的集合的基数,不需要存储其所有的值。基于String
实现,单个HLL
内存永远小于16K,测量结果是概率性的,误差小于81%。对于UV
统计完全可以忽略