redis的快速机制与数据类型
想一下 redis 的高并发和快速
单线程模型 - 避免了不必要的上下文切换和竞争条件(锁)
Redis客户端对服务端的每次调用都经历了发送命令,执行命令,返回结果三个过程。其中执行命令阶段,由于Redis是单线程来处理命令的,所有每一条到达服务端的每一条到达服务端的命令都不会立刻执行,所有的命令都会进入一个队列中,然后逐个执行。并且多个客户端发送的命令的执行顺序是不确定的。但是可以确定的是不会有两条命令被同时执行,不会产生并发问题,这就是Redis的单线程基本模型。
redis 核心就是如果我的数据全都在内存里,我单线程的去操作 就是效率最高的,为什么呢,因为多线程的本质就是 CPU 模拟出来多个线程的情况,这种模拟出来的情况就有一个代价,就是上下文的切换,对于一个内存的系统来说,它没有上下文的切换就是效率最高的。redis 用 单个CPU 绑定一块内存的数据,然后针对这块内存的数据进行多次读写的时候,都是在一个CPU上完成的,所以它是单线程处理这个事。在内存的情况下,这个方案就是最佳方案。
为何单核cpu绑定一块内存效率最高?
“我们不能任由操作系统负载均衡,因为我们自己更了解自己的程序,所以我们可以手动地为其分配CPU核,而不会过多地占用CPU”,默认情况下单线程在进行系统调用的时候会随机使用CPU内核,为了优化Redis,我们可以使用工具为单线程绑定固定的CPU内核,减少不必要的性能损耗!
redis作为单进程模型的程序,为了充分利用多核CPU,常常在一台server上会启动多个实例。而为了减少切换的开销,有必要为每个实例指定其所运行的CPU。
Linux 上 taskset 可以将某个进程绑定到一个特定的CPU。你比操作系统更了解自己的程序,为了避免调度器愚蠢的调度你的程序,或是为了在多线程程序中避免缓存失效造成的开销。
非阻塞IO - IO多路复用 - 减少网络IO的时间消耗
绝大部分请求是纯粹的内存操作(非常快速)
redis 的瓶颈一般主要在网络上
redis常见的数据类型
String字符串 - http://redisdoc.com/
Redis能存储二进制安全的字符串,最大长度为512M redis 127.0.0.1:6379> SET name "John Doe" OK redis 127.0.0.1:6379> GET name "John Doe" String类型支持批量的读写操作 redis 127.0.0.1:6379> MSET age 30 sex "male" OK redis 127.0.0.1:6379> MGET age sex 1) "30" 2) "male" String类型支持对其部分的修改和获取操作 redis 127.0.0.1:6379> APPEND name " Mr." (integer) 12 redis 127.0.0.1:6379> GET name "John Doe Mr." redis 127.0.0.1:6379> STRLEN name (integer) 12 redis 127.0.0.1:6379> GETRANGE name 0 3 "John" String类型也可以用来存储数字,并支持对数字的加减操作 redis 127.0.0.1:6379> INCR age (integer) 31 redis 127.0.0.1:6379> INCRBY age 4 (integer) 35 redis 127.0.0.1:6379> GET age "35" redis 127.0.0.1:6379> DECR age (integer) 34 redis 127.0.0.1:6379> DECRBY age 4 (integer) 30 redis 127.0.0.1:6379> GET age "30"
Hash哈希
Redis能够存储key对应多个属性的数据 redis 127.0.0.1:6379> HKEYS student 1) "name" 2) "age" 3) "sex" redis 127.0.0.1:6379> HVALS student 1) “tom" 2) "30" 3) "Male" redis 127.0.0.1:6379> HGETALL student 1) "name" 2) “tom" 3) "age" 4) "30" 5) "sex" 6) "Male" redis 127.0.0.1:6379> HDEL student sex (integer) 1 redis 127.0.0.1:6379> HGETALL student 1) "name" 2) “tom" 3) "age" 4) "30" Hash数据结构能够批量修改和获取 redis 127.0.0.1:6379> HMSET kid name Anna age 20 sex Female OK redis 127.0.0.1:6379> HMGET kid name age sex 1) “Anna " 2) "20" 3) "Female"
原生字符串类型:简单直观,每个属性都支持更新操作,但是用户ID为重复存储,如果存在大量这样的数据,内存浪费还是非常可观的。
序列化字符串类型:Key为用户ID,值为所有属性值的组合,使用合理可以提高内存使用效率,但增加了序列化/反序列化的开销,并且在需要修改其中一项信息时,需要把整个对象取回,并且修改操作需要对并发进行保护,引入cas等复杂问题 。
哈希类型:每个用户属性使用一对field-value,但是只用一个键保存。更新数据属性时通过 key(用户ID) + field(属性标签) 就可以操作对应属性数据了,既不需要重复存储数据,也不会带来序列化和并发修改控制的问题。但要控制哈希在ziplist和hashtable两种内部编码的转换,hashtable会消耗更多内存。
hscan --- 渐进式遍历,解决hgetall阻塞问题,hscan过程中键有变化,会导致新增键不能遍历到,或者重复遍历。
List列表 - 有序,1个list最多存储2^32 -1 个元素
Redis能够将数据存储成一个链表,并能对这个链表进行丰富的操作 redis 127.0.0.1:6379> LPUSH students "John Doe" (integer) 1 redis 127.0.0.1:6379> LPUSH students "Captain Kirk" (integer) 2 redis 127.0.0.1:6379> LPUSH students "Sheldon Cooper" (integer) 3 redis 127.0.0.1:6379> LLEN students (integer) 3 redis 127.0.0.1:6379> LRANGE students 0 2 1) "Sheldon Cooper" 2) "Captain Kirk" 3) "John Doe" redis 127.0.0.1:6379> LPOP students "Sheldon Cooper" redis 127.0.0.1:6379> LLEN students (integer) 2 Redis也支持很多修改操作 redis 127.0.0.1:6379> LINSERT students BEFORE "Captain Kirk" "Dexter Morgan" (integer) 3 redis 127.0.0.1:6379> LRANGE students 0 2 1) "Dexter Morgan" 2) "Captain Kirk" 3) "John Doe" redis 127.0.0.1:6379> LPUSH students "Peter Parker" (integer) 4 redis 127.0.0.1:6379> LRANGE students 0 3 1) "Peter Parker" 2) "Dexter Morgan" 3) "Captain Kirk" 4) "John Doe" redis 127.0.0.1:6379> LTRIM students 1 3 OK redis 127.0.0.1:6379> LLEN students (integer) 3
业务中有先后顺序的所有列表都可以用List很好的表示(单向队列,双向队列,循环队列,各种队列)
lpush+rpop -- quenue(先进先出)
lpush+lpop -- stack
lpush+ltrim -- Capped Collection(有限集合)
lpush+brpop -- Message Queue (消息队列)
先进先出的队列+阻塞读操作,可以很方便实现 “生产者,消费者”这类问题,通常用于解耦应用程序的不同模块
Set集合
Redis能够将一系列不重复的值存储成一个集合
redis 127.0.0.1:6379> SADD sql oracle (integer) 1 redis 127.0.0.1:6379> SADD sql mysql (integer) 1 redis 127.0.0.1:6379> SADD nosql redis (integer) 1 redis 127.0.0.1:6379> SADD nosql hbase (integer) 1 redis 127.0.0.1:6379> SADD nosql neo4j (integer) 1 redis 127.0.0.1:6379> SADD nosql MongoDB (integer) 1 redis 127.0.0.1:6379> SMEMBERS sql 1) “oracle" 2) “mysql“ redis 127.0.0.1:6379> SMEMBERS nosql 1) "redis" 2) "hbase" 3) "neo4j" 4) “MongoDB” Sets结构也支持相应的修改操作 redis 127.0.0.1:6379> SREM nosql MongoDB (integer) 1 redis 127.0.0.1:6379> SMEMBERS nosql 1) "redis" 2) "hbase“ 3) "neo4j”
Sets还支持对集合的并等操作 redis 127.0.0.1:6379> SUNION sql nosql 1) "oracle" 2) "mysql" 3) "redis" 4) "hbase" 5) "neo4j“
Sets的随机操作 redis 127.0.0.1:6379> srandmember nosql 1) "hbase" redis 127.0.0.1:6379> srandmember nosql 1) "redis"
srem --- 删除指定元素, spop ---- 随机删除1个元素 (抽奖)
srandmember --- 随机返回count个元素,不会删除
Sorted Set排序集 - 为每个成员分配1个分值,可根据分值排序
redis 127.0.0.1:6379> ZADD salary 3000 tom (integer) 1 redis 127.0.0.1:6379> ZADD salary 2500 bob (integer) 1 redis 127.0.0.1:6379> ZADD salary 4000 jack (integer) 1 redis 127.0.0.1:6379> ZRANGE salary 0 -1 withscores 1) “bob" 2) “2500" 3) “tom" 4) “3000" 5) “jack" 6) “4000“ redis 127.0.0.1:6379> ZCOUNT salary 2000 5000 (integer) 3 redis 127.0.0.1:6379> ZCOUNT salary 3000 5000 (integer) 2 redis 127.0.0.1:6379> ZRANK salary tom (integer) 1 redis 127.0.0.1:6379> ZREVRANGE salary 0 -1 1) “jack" 2) “tom" 3) “bob"
ZRANGE salary 0 -1 withscores ----- 从低排到高, withscores 同时返回分数
ZREVRANGE ----- 从高排到低 , 下标从0开始
ZCOUNT ---- 返回指定分数区间内的成员个数,闭区间
GEO地理位置
1)增加a和b的坐标 127.0.0.1:4517> GEOADD test 13.361389 38.115556 a 15.087269 37.502669 b (integer) 2 2)坐标200km内的元素 127.0.0.1:4517> GEORADIUS test 15 37 200 km WITHDIST 1) 1) "a" 2) "190.4424" 2) 1) "b" 2) "56.4413" 127.0.0.1:4517> GEORADIUS test 15 37 200 km WITHCOORD COUNT 1 1) 1) "b" 2) 1) "15.08726745843887329" 2) "37.50266842333162032" 3)a和b之间的距离 127.0.0.1:4517> GEODIST test a b " 166274.1516 " 5)增加坐标c 127.0.0.1:4517> GEOADD test 13.583333 37.316667 "c" (integer) 1 6)获取c 100kM内的元素 127.0.0.1:4517> GEORADIUSBYMEMBER test c 100 km 1) "c" 2) "a“ 7)获取a,b,c的坐标 127.0.0.1:4517> GEOPOS test a b c 1) 1) "13.36138933897018433" 2) "38.11555639549629859" 2) 1) "15.08726745843887329" 2) "37.50266842333162032" 3) 1) "13.5833314061164856" 2) "37.31666804993816555"
geoadd key名 经度 纬度 成员,geoadd key可以用于更新已存在成员的位置,但返回结果为0
georadius key longitude latitude radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count]
withcoord:返回结果中包含经纬度
withdist : 返回结果中包含离中心节点位置的距离
withhash: 返回结果中包含geohash
Publish/Subscribe及使用场景
订阅信息管道 1.用一个客户端订阅管道 redis 127.0.0.1:6379> SUBSCRIBE channelone Reading messages... (press Ctrl-C to quit) 1) "subscribe" 2) "channelone" 3) (integer) 1 2.另一个客户端往这个管道推送信息 redis 127.0.0.1:6379> PUBLISH channelone hello (integer) 1 redis 127.0.0.1:6379> PUBLISH channelone world (integer) 1 3.然后第一个客户端就能获取到推送的信息 1) "message" 2) "channelone" 3) "hello" 1) "message" 2) "channelone" 3) "world" 按一定模式批量订阅 1.用下面的命令订阅所有channel开头的信息通道 redis 127.0.0.1:6379> PSUBSCRIBE channel* Reading messages... (press Ctrl-C to quit) 1) "psubscribe" 2) "channel*" 3) (integer) 1 2.在另一个客户端对两个推送信息 redis 127.0.0.1:6379> PUBLISH channelone hello (integer) 1 redis 127.0.0.1:6379> PUBLISH channeltwo world (integer) 1 3.然后在第一个客户端就能收到推送的信息 1) "pmessage" 2) "channel*" 3) "channelone" 4) "hello" 1) "pmessage" 2) "channel*" 3) "channeltwo" 4) "world"
添加stream ,对比pub/sub和stream的区别
pub/sub 消息无法持久化,客户端断开期间消息无法获取
实例重启后,需要重新订阅
Redis事务及使用场景
1.通过MULTI和EXEC,将几个命令组合起来执行 redis 127.0.0.1:6379> SET num 0 OK redis 127.0.0.1:6379> MULTI OK redis 127.0.0.1:6379> INCR num QUEUED redis 127.0.0.1:6379> INCR num QUEUED redis 127.0.0.1:6379> INCR num QUEUED redis 127.0.0.1:6379> EXEC 1) (integer) 1 2) (integer) 2 3) (integer) 3 redis 127.0.0.1:6379> GET num "3" 2.将EXEC替换为DICARD命令来中断执行中的命令序列 redis 127.0.0.1:6379> SET num 0 OK redis 127.0.0.1:6379> MULTI OK redis 127.0.0.1:6379> INCR num QUEUED redis 127.0.0.1:6379> INCR num QUEUED redis 127.0.0.1:6379> INCR num QUEUED redis 127.0.0.1:6379> DICARD 1) (integer) 1 2) (integer) 2 3) (integer) 3 redis 127.0.0.1:6379> GET cnt "0"
事务不支持事务回滚,无法实现命令之间的逻辑关系;
错误命令处理机制:(1)语法错误,整个事务不执行
(2)命令敲错(语法正确),执行成功,回滚需要应用修复
Redis模块化
Redis4.0提供了模块化的功能,只需要在配置文件中增加了loadmodule /path/to/my_module.so参数,指定Redis在启动时加载某个模块化功能,就可以为用户提供更多的可能性。参考:https://redis.io/modules
Redis复制原理
Redis提供的高可用方案
Redis提供的分片算法
Redis迁移