Redis | 基础
Redis是一个火了有段时间的NoSQL数据库,准确一点说是kv数据库。除了常用的string/int hash list set以外,还有一些其他数据类型。
加上我最近太穷了,没有办法去买Azure storage queue,所以打上了redis的主意,服务器内存又小,不得已打上了redis的主意,打算榨干redis的各种功能。
结构
上面说了,redis是一个kv数据库。
也就是没有表啊什么的麻烦的事情,就是一个字符串的key存着一个value。这个value是什么呢?
基本的数据类型
redis是一个类型很少的数据库
常用的类型就几种
- string
- list
- hash
- set
- sorted set
string
redis不区分字符串和数字,凡是存进来的数据都是string。甭管你存了一个a还是1。
但是对于纯数字的string来说我们可以多做一些操作,比如说incr,会将值自增。
有的人鼻孔朝天:多此一举,我取回来加一再存回去不就好了。
那不是。村里捐款箱有100,你和张三都要给村里捐一块,都告诉村长说村里捐款箱有101块,这一块是你的还是张三的?打一架?
redis的自增就杜绝了这种问题的存在,redis里面incr 100次,就一定会给值增100。redis的操作是原子的
redis字符串是在标准库的字符串基础上做了一些新的工作,用一个结构体表示了原来的一个字符序列。
list/hash/set
列表和哈系都是大家熟悉的类型了。c++和go的map,py的dict,都和redis的hash差不多,c++的vector,go、py的数组,都和list差不多,c++和python的set和redis的set也差不多。不大一样的就是无论存了什么,在redis里面元素都是string。
redis用了链表来表示列表,用哈系表实现了hash和set。
sorted set
有一个略微不一样的类型叫sorted set,这个类型在Java中也有提供,叫SortedSet。和集合类似,元素不能重复,但是有一个score用来排序,让集合中的元素保持有序。可以按照score区间取出有序的一部分元素,也可以直接取出元素的score。
redis实现sorted set没有用到新的数据结构,只是用到了集合和列表,不过列表的实现叫Skip List。
除了基本的数据类型和kv
redis除了上述的kv+数据类型的用法以外,在非常早的版本中,还加入了pub/sub的功能。也就是队列的功能。
redis的pub/sub可以非常方便的用来给各个系统做广播,可以用来分发一些实时的消息。
用法
客户端
redis提供了命令行客户端,直接读写kv。各个语言也有sdk,比如说我一直在用aioredis和go-redis。Java有一个客户端叫redissen,做了很多额外的功能,也非常贴心。
常用的场景
会话session和缓存
有的信息,比如说用户信息,每个请求都从数据库读会很费时间,redis高速的读取刚刚好可以补足这类问题。
我们每次读取的时候可以
- 先查询redis缓存有没有对应的信息
- 如果没有
- 从数据库中读取
- 存入缓存中
- 如果有
- 从缓存中读取
- 返回给调用者
修改和删除时,一般会
- 修改/删除数据库中的记录
- 删除缓存对应的key
- 等读取时自动读入缓存中
广播
主要是用到pub/sub。比较简单。
排行榜
redis的sorted set非常适合做排行榜。
用score做score,用户id做element。设置score用add,取出score用zscore,按顺序从低往高取出倒数排名用zrange,从高往低则是zrevrange,取出倒数排名用zrank,取出正数的排名就用zrevrank。如果变更分数,根据需求可以选择用zincr去增加分数,也可以用zadd ch去直接变更分数。
异步队列
我们也可以用redis做异步的队列。redis里面有两种数据是有序且带原子操作的:list,sorted set。
最简单ver:list
redis的list可以从两侧任意一侧push/pop,那么我们只要从一侧push,另一侧pop就好了。
这种队列的问题在于分布式系统中的任意一环都是不可靠的。比如说消费者执行任务可能失败,要把任务推回去的话还有网络失败和数据库恰好挂掉的可能,所以只能用来分发一些不重要的东西。
复杂ver:zset
结合时间戳,我们就可以以时间戳为score,用redis的sorted set做一个任务队列。
增加任务用zadd key 任务生效时间 任务id,获得任务用zrangeby key 0 当前时间戳,保证别人不会拿到就用zincr把score加到下一次其他人可以拿到的时间,执行完了用zrem删掉元素就好。其他的信息通过另一个key来记录,比如说任务本身的信息,重试的次数,诸如此类的。
这样你可以
- 控制下一次任务出现的时间
- 控制重试
- 不担心任务超时
分布式锁
redis的set完整的形式是这样的:
SET key value [EX seconds|PX milliseconds] [NX|XX] [KEEPTTL]
也就是可以设置超时时间,可以只在值存在或者不存在的情况下修改或者增加值。
依赖于NX/XX,我们可以在redis中完成一个分布式锁:
SET lock:xxx EX timeout NX instance-id
- 成功?:持锁,开始干活
- 完成后删除锁
- 失败?:抛出异常
- 成功?:持锁,开始干活
如果我们这个worker可能会超时,超时前我们需要续命,那么可以
SET lock:xxx EX timeout NX instance-id
- 成功?:持锁
- 持续更新超时时间:
EXPIRE lock:xxx timeout
- 完成后删除锁
- 持续更新超时时间:
- 失败?:抛出异常
- 成功?:持锁