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统计完全可以忽略

