Redis快速入门1
Redis简介
- 键值型数据库 , 类似于 Hashmap,数据以键值对为数据结构。 一般来说值为json的结构方便存储更多的数据
- 与关系型数据库的区别 SQL V.S. NoSQL
对比向 | SQL | NoSQL |
结构化 | 表的结构,数据类型(str,int)等存在约束,一般定义好表的结构后不会更改 | 非结构化,在Redis中Key值得设置比较自由。 |
数据关联性 | 数据各表之间存在关系,各表相互维护,占用的数据空间较小 | 数据没有关联,数据库不会维护关系,存在重复空间占用 |
语法固定 | 存在固定的语法: eg. SELECT , From等固定关键字 | 命令没有统一标准,查询方式不复杂 |
事务 | 所有事务可以满足ACID(atom原子性,操作不可分割,必须全部完成或全部回滚)(Consistency 一致性)(Isolation隔离性)(Durability持久性) | 无法全部满足ACID, 能满足BASE 最终一致性(详见分布式原理入门) |
存储方式 | 一般存储在硬盘上 | 一般存储在内存中(性能高) |
扩展性 | 垂直 | 水平(更适合分布式)(通过Hash运算决定存储的节点的位置) |
- Redis 基于内存的键值型NoSQL数据库,特征如下:
- 键值型: value支持不同数据结构,功能丰富
- 对数据的处理是单线程的(对网络请求是多线程的)命令具备原子性
- 低延迟,速度快(a.存储在内存中 b.IO可以多路复用 c.有良好的编码)
- 支持数据的持久化 (定期的将数据持久化到磁盘,但是数据的使用和访问依旧是使用内存中的数据)
- 支持主从集群,分片集群
- 支持多语言客户端
拓展:
- IO多路复用:(参考 https://zhuanlan.zhihu.com/p/358208161)在访问者与服务器通过网络进行交互的时候,采用Socket的方式对相应的端口进行监听并执行请求。在Linux系统中,每一个Socket只能针对一个用户。针对多个用户请求的时候,老版本:
- A.Linux会采用新建进程的方式新建Socket(用父进程监听socket连接,连接成功后fork()子进程进行相应操作),但是断连之后子进程回收不到位就变成了僵尸进程,而且在进程中切换(上下文)消耗很高
B. Linux避免进程切换的高额负担改成创建线程 。因为现身本身不保存资源,所有的资源都是进程本身的,当交互(TCP连接断开)结束后就会销毁线程。即使采用线程池的方式避免线程的频繁创建与销毁对高额的请求依然会力不从心
C.I/O 多路复用(mutiplexing), 多路复用本来使不算陌生的一个词(见计网PPT 3-2) ,贴上多路复用的定义: Multiplexing(复用): gathering data from different app processes at source host, enveloping data with header information (later used for demultiplexing) to create segments to the network layer。但是在涉及Socket的IO操作中,就变得有点迷了。先类比一些常见的多路复用,如时间多路复用(设置时间片),分频复用(FDM等)
在Java中IO多路复用 指复用了同一个处理线程(在java中被抽象为选择器selector),由操作系统进行托管,当与选择器绑定的socket满足就绪的条件后,可以直接以事件驱动的形式(即释放select调用处的阻塞)获取到该socket。
在Linux系统中实现多路服用的方法: select/poll/epoll. 先说有缺陷的select 和 Poll. 两者将已连接的socket放到文件描述符集合(详见Linux笔记-2.文件篇-2.文件描述符),之后调用select将文件描述符合计copy到内核中,由内核通过线性遍历文件描述符集合检查是否由网络事件的发生。如检测到有事件发生,会再将文件描述符合集copy会用户态,由用户态在进行一次线性遍历找到变化的Socket,对其进行处理。所以时间复杂度是O(2n)两次线性遍历,再加上两次文件拷贝的时间,还可以进一步优化
epoll的优化:
A. epoll用红黑树来跟踪进程所有待检测的文件描述字,每次只需传入待检测的socket而不是集合,同时增删查的时间复杂度由原来的线性遍历O(N) 变成O(Logn)
B. eopll内核中维护了一个链表记录就绪事件
简单来说当收到socket请求时,poll和select余姚自己进行线性遍历自己找,而epoll 现在不仅告诉你sock组里面数据,还会告诉你具体哪个sock有数据,你不用自己去找了。同时poll比select纲纪那么一丢丢,select有集合上限(fd_max = 1024)而poll使用的连边,上限要高一些
- 主从集群与分片集群:
集群是一组协同工作的服务集合,一般由两个或者两个以上的服务器组成
主备集群: Raft
主从集群: Zab
分片集群: 整个集群将整个数据分片,每台机器存取的是数据的切片
Redis初学上手
安装部分:
官网冲了Docker image, 那就直接跳过冗杂安装过程,直接开一个docker。由于端口映射一般选择6379, 因为redis可视化部署在6379端口上,也可以看到很简单就进入了redis镜像的root
Redis的启动: 在Docker中已经将Redis的环境变量部署完善,所以运行使用命令, 其他几乎不需要所有的设置,docker的好处可见一斑
# redis-cli
但是,由于docker的适配性,导致docker redis的个性化稍微有点麻烦,在建立docker的时候就要配置好,这里是具体配置连接()
远端连接使用Redis Desktop,已成功连接上docker的redis,(记住打开6379端口)
从图上可以看出Redis(未改配置情况下) 默认有16个库。简单的命令整合
* set 键 值 存放键值对
123:0>set name jack "OK" 存放键:name, 名字:jack
* Select 库名 选择接下来操作的库
123:0> Select 1 "OK" 123:1> 从库0变为库1了
* get 键 得到键对应的值
123:0>get name "jack" 获得name键对应的值"jack"
Redis的数据结构:
Redis是一个key - value的数据库, key一般是String类型, 但是value的取值可以多种多样:
数据结构 | 实例 |
String | ‘hello world’ |
HashMap | {age:21, name:‘Jack’} |
List(链表) | [ A -> B -> C -> b] |
Set(无序集合,无重复) | {A,B,C} |
GEO(地理坐标,经纬度) | {A:(120.3, 30.5)} |
Bitmap(标记重复value,减小空间) | 01010110 -> 1246 详见 |
HyperLog (基数统计)快速计算不重复元素个数 | HyperLogLog,只需要12K内存,在标准误差0.81%的前提下,能够统计2^64个数据 |
- 拓展: HyperLoglog算法:
HLL是一种近似去重算法,它通过遍历所有元素一次的过程来用极少(真的超级少)的存储空间给出一个大致的数据流重复元素的个数,但HLL会存在偏差,偏差大小与占有的存储空间大小有关,也没法统计每个元素出现的个数。
HLL为什么会存在误差? HLL是一个基于概率的算法,并在改了计算后通过遍历进行分桶实验对结果进行调整的算法
步骤:
- 通过hash计算输入值对应的bit串
- 比特串的低t位(t = log2 m)对应的数字用来找到数组S中随影的位置i
- 从t+1位开始找第一个出现1的位置k, 将k计入数组Si的位置
- 基于数组S记录的所有数据的统计值,计算所有整体的基数值
非数学逻辑原理参见https://mp.weixin.qq.com/s/h2xEbWIQJ8YuZuT6-tfmBQ
这里尝试学下数学原理还是有用的:
首先先列出来这种方法的数学依据:极大似然估法,伯努利实验()
Redis 命令篇:
注,按照语法的规范性应该用大写的写法。但是实际上小写的也可以进行编译。
- Help 获取帮助与用法 但是更详细的教程推荐官方 redis.io/commmand/hdel
- KEYS: 查看符合模板的所有key。参数是键,支持模糊查询
> keys *a* //注:数据量特别大的时候用*需谨慎
1) "status_code"
2) "name"
> keys ?am?
1) "name"
> keys *
1) "test"
2) "status_code"
3) "name"
> keys status_[a-h]ode
1) "status_code"
> keys status_[k-i]ode
//空
- DEL 删掉键值,参数是键,键值一起删, 不支持模糊查询
> del test
"1"
> keys test
//空
> del k1 k2 k3
"3"
- MSET 批量进行数据的插入 参数是键值
- EXISTS 判断键是否存在 。 参数为键。 不支持模糊查询
> exists *am*
"0"
> exist name status_code
"2"
- EXPIRE 给key设置有效其,有效期到期时会自动删除 传参:Key Seconds。适用场景:入验证码等很快失效的场合
- TTL 查看KEY的剩余期限 传参:Keys。
> expire name 15
"1"
//wait 8 s
> TTL name
"7"
//wait 20s
> TTL name
"-2"
> TTL qIHGDUYFW //不存在的键值
”-2“
//说明Redis的TTL对并不存在的KEY会设置-2的default值
> TTL status_code
"-1"
//对没有设置expire的键值会设置为-1
Redis深入数据类型:
String 类型
String类型, 就是字符串类型,是Redis中最简单的存储类型,其value是字符串,不过根据字符串的格式不同,又可以分为三类:(注,一个String类型的上限为 512M)
- string : char型 普通字符串
- int: 证书类型, 可以做自增,自减操作
- float: 浮点类型,可以做自增,自减操作
常见命令:
- SET 添加或修改一个已经存在的键值对
- GET 根据kry获取String的 value
- MSET / MGET 多量set/get
- INCR 使一个整型的key自增加一
- INCRBY 使一个整型的key增加指定的大小(可以为负数,即decrease)
- INCRBYFLOAT 使float型key增长指定大小
redis_192.168.75.140:1>mset age 9 height 160 grade 59.4
"OK"
redis_192.168.75.140:1>incr age
"10"
redis_192.168.75.140:1>incrby height 10
"170"
redis_192.168.75.140:1>incrbyfloat grade 15.3
"74.7
- SETNX 添加一个String类型的键值对,如已存在此key则不执行
- SETEX 添加已String并指定TTL
redis_192.168.75.140:1>setnx name '小白'
"0"
redis_192.168.75.140:1>get name
"老王"
redis_192.168.75.140:1>setex name 10 '小可爱'
"OK"
redis_192.168.75.140:1>TTL name
"5"
redis_192.168.75.140:1>TTL name
"-2"
Key的层级格式
Redis 没有像Mysql一样的TABLE结构,而且键值唯一。所以为了方便数据的管理与索引, Redis的Key允许多个单词形成层级结构,多个单词间用 ‘:’ 隔开。
- eg. 公司名: laowang 部门:kaifa 组: bailan,juan 用户:iD
我们可以用: laowang:kaifa:bailan:iD:1这样的键值进行查找一个用户,不同组用户 laowang:kaifa:juan:iD:3查找
如果存储的是一个对象,可以将对象序列化json之后进行存储。在Python中如果是我们自己定义的类型(类)可以借用pickle模块进行序列化后存储数据
class student():
def __init__(self,age,ID,name,grade,college,classes):
self.name = name
self.age = age
self.grade = grade
self.college = college
self.classes = classes
self.ID = ID
laowang = student(20,'20198888888','老王',2019,'ChengduUniversity',3)
import pickle
k = pickle.dumps(laowang)
k
b'\x80\x04\x95\x80\x00\x00\x00\x00\x00\x00\x00\x8c\x08__main__\x94\x8c\x07student\x94\x93\x94)\x81\x94}\x94(\x8c\x04name\x94\x8c\x06\xe8\x80\x81\xe7\x8e\x8b\x94\x8c\x03age\x94K\x14\x8c\x05grade\x94M\xe3\x07\x8c\x07college\x94\x8c\x11ChengduUniversity\x94\x8c\x07classes\x94K\x03\x8c\x02ID\x94\x8c\x0b20198888888\x94ub.'
redis_192.168.75.140:1>set China:JiLin:Student "b'\x80\x04\x95\x80\x00\x00\x00\x00\x00\x00\x00\x8c\x08__main__\x94\x8c\x07student\x94\x93\x94)\x81\x94}\x94(\x8c\x04name\x94\x8c\x06\xe8\x80\x81\xe7\x8e\x8b\x94\x8c\x03age\x94K\x14\x8c\x05grade\x94M\xe3\x07\x8c\x07college\x94\x8c\x11ChengduUniversity\x94\x8c\x07classes\x94K\x03\x8c\x02ID\x94\x8c\x0b20198888888\x94ub.'redis_192.168.75.140:1>"
"OK"
数据库中就会出现层级结构:
HashMap类型
Hash类型,也叫散列,其value是一个无需字典,类似于Java的HashMap or Python 的Dict 。
这种结构可以将对象的每个字段独立存储,可以针对单个字段做CRUD
对比String存Json,我们设定了如下表格
Key | Value |
user1 | {name:‘Jack’,age:‘21’} |
Key | Value | |
Filed | Value | |
user1 | name | Jack |
age | 121 |
Hash 命令:
- HSET key filed value 添加或更改key的filed
- HGET key filed 获取key,filed的字段值
- HMSET/HMGET 批量。。。。。。
- HGETALL 获取 一个key所有的filed和value
- HKEYS 获取一个key所有的filed
- HVALS 获取一个KEY所有的Value
- HINCRBY / SETNX 类似INCRBY, SETEX
List类型
可以看作是一个双向链表结构,支持正向检索与反向检索。其重要特性如下:
- 有序
- 元素可以重复
- 插入与删除速度快
- 查询速度一般
LIst类型的命令集中在Push与Pop上
- LPUSH/ RPUSH key element 向队首、尾添加一个OR多个元素
- LPOP/RPOP 移除并获得队首/尾的第一个元素,没有返回NULL
- LRANGE key start end 返回一段角标范围内的所有元素(从0开始)
- BLPOP/ BRPOP 和LPOP, RPOP类似,但是没有元素是不会直接返回null, 而是等待一定时间自设 ,单位s
redis_192.168.75.140:2>LPUSH test_list 1 2 3 4 5 6 7 8 9
"9"
redis_192.168.75.140:2>RPOP test_list
"1"
redis_192.168.75.140:2>LPOP test_list
"9"
SET类型
可能接触python较多(没错不会java的跪物就是我)的同学对HashSET没有什么概念。其实Hashset就相当于value为null的hashmap, 一个可变的,无序的,元素不重复的list.特点如下:
- 无序
- 元素不可重复
- 查找快
- 支持交,并,差集(新增功能)
SET的命令如下:
- SADD key member1 member2 向set添加多个元素
- SREM key member 移除set中的指定元素
- SCARD key 返回keyset的元素个数
- SISMEMBER key member 判断一个元素是否在set中
- SMEMBERS 获取所有members
下面是多个key进行交并差集的操作:
- SINTER key1 key2 求key1, key2两个set的交集
- SDIFF key1 key2 求key1 - key2(差集)
- SUNION key1 key2 求key1,key2并集
SortedSet 类型
一个可排序的set集合。 SortSet中每一个㢝带一个score属性,根据此进行排序。底层是根据调跳表 + map完成的·。跳表可以参考。特性:
- 可排序
- 元素不重复
- 查询速度快
- 经常被用来实现排行榜这样的功能
常见命令:有点多:
- ZADD key score member 添加多个元素到sorted set,如果已存在就更新其score
- ZREM key member 删除某一member
- ZSCORE key member 获取sorted set中的指定元素的score值
- ZRANK key member 获取指定元素的排名
- ZCARD key 获取个数
- ZCOUNT key min max 统计 score 在min 与max之间的个数
- ZRANGE key min max 获取min, max排名内的member
- ZRANGEBYSCORE key min max 获取score 在min,max之间的个数
- ZINCRBY key increase member 使member的score增长increase的大小
- ZDIFF, ZINTER, ZUNION 差,交,并集
- 以上命令默认增序,如需降序请在命令头换成zrev~这样的
Redis 事务概念
回顾mysql事务: 事务会把数据库从一种一致状态转换为另一种状态。在数据库提交工作时,可以确保要么所有修改都已经保存了,要么所有修改都不保存。事务具有ACID这四个特性,分别为原子性(atomicity)、一致性(consistency)、隔离性(isolation)、持久性(durability)。对于InnoDB来说,事务的隔离性由锁来完成,而其他三种属性由redo log 和 undo log来完成。
Redis本身的设计决定了Redis很难保证Mysql的atom。 因为Redis本身设计的时候没有redo和undo日志,本身目的也不是为了像mysql一样。而且Redis本身是单线程执行,不存在多线程需要锁的概念。
Redis是异步单线程执行,也就是一个线程对应所有的客户端。只要客户端调用,Redis就会被执行,因此不能保证一个客户端在执行多个操作时不被其他用户插队
所以说,Redis事务的概念更像是批处理执行,保证客户端在调用结束前都不会被其他客户端插队。
为了保证事务的一致性,在开启事务之前必须要用watch命令监视要操作的记录。启动watch后一旦检测到其他用户端修改了监视的值,就将本次未执行的事务的批处理队列清空不再执行。Redis没有回滚的概念
redis > watch keys (keys可以是多个key哦) 启动监视
redis > MUNTI 开启一个事务。开启一个食物后所有的操作都不会立刻执行,只有执行EXEC才会开始批处理执行
redis > EXEC 执行批处理
redis > DISCARD 将批处理队列中没有执行的命令清空
Redis python开冲
预处理: pip install redis
Redis的连接:
# 创建一个连接对象
import redis
redis_connect = redis.Redis(host = '192.168.75.140',
port = 6379,
# password = XXX
db = 5)
# 创建一个连接池
redis_connect_pool = redis.ConnectionPool(host = '192.168.75.140',
port = 6379,
# password = XXX
db = 5,
max_connections = 20) # 连接池的大小,最大连接数
# 从连接池中提取连接。连接不需要手动关闭,在垃圾回收的时候连接会自动归还连接池
redis_connect2 = redis.Redis(connection_pool = redis_connect_pool)
# 删除引用,关闭连接
del redis_connect2
在Redis中的操作函数通过redis.py的接口还是可以蛮原汁原味的调用的
red = redis_connect
red.set('Name','老王')
red.set('age',99)
# 数据二进制传输
red.get('age')
b'99'
有些命令在redis.py不太一样,比如说DEL 是delete , MSET必须传参字典/Json格式
red.delete('age')
red.mset({'age':99, 'handsome':'Yes'})
red.get('handsome')
b'Yes'
基本其他的命令都别无二至(注,zadd第二个参数是dict {member:socre})
Redis的事务-》python的实现
PYthon模块用pipeline(管道) 的方式向redis服务器传递批处理命令和执行事务
# 实例化一个管道
pipline = red.pipeline()
# 开启监视
pipline.watch('student')
# 开始一个事务/操作向队列中堆积
pipline.multi()
pipline.hset('student','hobby','exercise')
# 执行队列中所有操作
pipline.execute()
# 一定要关闭管道,这样才可以进程回收此管道
pipline.reset()
red.hkeys('student')
[b'name', b'grade', b'hobby']