redis笔记1---基础

基础数据结构

redis的所有操作都是原子的,这得益于redis是单线程的结构。redis有5种基本的数据结构,分别是:

  • string:字符串
  • k-v:键值对
  • list:链表
  • hash:哈希
  • set:集合
  • zset:有序集合

string是动态字符串,通过预分配空间减少内存的频繁分配操作,string在长度小于1M时,是成倍的增加现有空间;超过1M时,每次增加1M的空间,最大长度512MB

键值对相当于一个字典,支持增删改查,从键值对的角度来看,redis本身就可以看成一个字典。k-v结构中,k是字符串,v可以是字符串或者是整数。整数的情况下,可以使用incrby命令实现自增操作。每个k-v可以设置超时时间,使用expire命令可以设置,比如expire myKey 5,则myKey在5秒后自动销毁。自增的大小,必须在signed long之间,超过范围会报错。

list是链式结构,双端插入删除的复杂度是 O ( 1 ) O(1) O(1),索引定位的复杂度是 O ( n ) O(n) O(n)。基本操作除了插入,索引下标之外,还有ltrim操作,例如ltrim myList 1 3是只保留myList范围1-3的数据。但是,这个复杂度也是接近 O ( n ) O(n) O(n)的,比如ltrim myList 1 -1就是 O ( n ) O(n) O(n)的复杂度。-1表示最后一个元素,其余同理。链表在元素个数较少的情况下,会是ziplist的结构,即元素存储在一个内存块中,这样提高空间利用率,减少内存碎片。

hash是字典的结构,它的内部本身存储了很多键值对。

hash
k
value
k
value
k
value

hash的键值通过桶哈希的方式存储。当哈希冲突时,redis采取渐进式rehash操作。这个方式同时保留新旧两个hash表,在后续的定时任务或者操作中,旧表的内容会渐进式地移动到新的表,移动完成后,旧表删除,保留新表。

set就相当于C++中的std::unordered_set<T>结构,内部键值是无序唯一的,可以一次添加多个,或者删除多个,可以统计个数,判定是否是对应的成员等。

zset结构是带有权重的k-v操作,每个k存储的v是一个score,该结构使用了跳表实现,元素根据score实现有序,我们可以根据k来索引元素,也可以根据score来划分区间等操作。基本结构如下:

key
key
score
key
score
key
score

zset可以用来存放粉丝列表,比如key表示idscore表示时间等。

所有的容器,如果不存在,则操作时立刻新建一个;如果操作后没有元素了,则立刻删除。

分布式锁

分布式锁,不是我们传统意义上说的锁,比如在内存中使用锁的结构来保证同时只能有一个线程访问临界区;而是使用一个信号量的机制,设置一个标记,来表示当前是否有分布式的进程操作这个数据。

最简单的操作:

setnx mylock true

这相当于建立了一个锁,使用完成后,需要取消,命令是:

del mylock

不过,这么做有缺陷,比如设置完成锁后,且进程获取到了锁,但是进程此时由于某些原因挂了,那么就形成了死锁。一般的解决方案是设置超时时间,代码示例:

set mylock true ex 5 nx

这条命令把设置锁和设置超时结合起来,方式操作被终端,引发死锁。这是这是5秒的超时时间。上面的命令如果分开写,则是:

setnx mylock
expire mylock 5

注意,如果一个锁超过了设置的超时时间,但是使用锁的任务获取锁后,在超时时间过了之后还在操作临界区,而此时又有新的分布式进程设置了锁,那么就会出现问题。旧的进程会删除掉新进程的锁,这样会造成不安全的因素。

可重入锁可以采用引用计数的方式实现,比较麻烦,而且需要精确考虑超时时间,一般不推荐使用。这里给出可重入锁的基本使用方式,没有时间过期的方式:


如果加锁失败,可以选择直接抛出异常,或者sleep定时时间不断重试,或者放入异步队列中等待重试。

延时队列

异步消息队列

该队列用于生产者和消费者模式,比如有多个分布式生产者和消费者,消费者获取生产者的数据,但是没有顺序要求。这更适合只有一组消费者的情况。

p1
cargo queue......
p2
c1
c2

在这种情况下,可以使用list作为异步队列,使用lpushrpop读取数据。

如果队列空了,而且消费者比较多,那么可能出现消费者不断轮询redis服务器的情况,此时造成消费者CPU消耗高,同时造成redis的QPS过高。

这种情况,最简单的方式是消费者定时检测,每次轮询sleep(1)。不过容易造成延迟。另一个方式,使用listblpop或者brpop进行操作,这是阻塞读,直到有数据放入队列中。注意,如果阻塞时间过长,redis可能会切断客户端的连接。这需要我们检测到异常时进行重试。一般redis或者有关的库可以配置空闲时间。

延时队列

使用zset可以设置延时队列,每次取出超时最近的消息。

def delay():
    msg = str(uuid.uuid4())
    value = json.dump()
    retry_ts = time.time() + 5
    redis.zadd("delay_queue", retry_ts, value)

# loop函数会有多个线程执行,为了保证可用性
def loop():
    while True:
    values = redis.zrangebyscore("delay_queue", 0, time.time(), start=0, num=1)
    if not values:
        time.sleep(1)
        continue
    value = values(0)
    success = redis.zrem("delay_queue", value)  # 只有一个线程能成功执行删除操作
    if success:
        msg = json.load(value)  # 执行删除成功的才可以真正获取数据
        handle_msg(msg)

redis队列不保证数据的可靠性

消息不保证可靠,应该是消息被发送出去,消费者是否接收到消息redis不做保证,不像一般的mq,会有ack机制,要求消费者收到消息进行ack确认,超时未确认mq会再次投递消息,而redis没有这个机制。

高级结构

位图

相当于位操作。位图本身是自动扩展的,如果偏移量超过当前的内容返回,会自动填充。注意位图的填充模式,数组顺序和位图顺序是相反的。比如:

h                  w
01101000   01100101    # 从右向左
0110100001100101       # 从左向右
bitcount # 统计指定范围1的个数
bitops   # 统计指定范围第一个1出现的位置
bitfield + get | set | incrby # 对区间进行操作
bitfield + overflow # 可以指定溢出的操作方式。默认是截断的

HyperLogLog

适用于不精确的统计情景。比如网页统计用户的访问量UV,需要对用户的ID进行去重操作。这个结构占用12KB的空间,一般来说,可能有0.x%的误差。只能添加,不能删除

pfadd hll user1  # 添加数据 O(1)
pfadd count hll # 统计数据 O(1)
pfmerge hll0 hll1 hll2 # 1 2合并到0上,O(N)

布隆过滤器

布隆过滤器,是一个不太精确的set结构,用于去重,但是节约90%的空间。基本原理是把数据哈希映射到比特位,之后根据比特位进行判断是否存在,如下图:


可以参考:https://www.cnblogs.com/zhanggguoqi/p/10571225.html

布隆过滤器,如果判断某个值存在,则此时该值可能不存在;但是如果判定一个值不存在,则一定不存在。

我们可以设置过滤器的错误率和容量。如果超过容量,那么误判率会快速上升。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值