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的时间消耗

五种IO模型

绝大部分请求是纯粹的内存操作(非常快速)

CPU到底比内存跟硬盘快多少

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迁移

 

posted @ 2019-10-16 11:49  瓜皮望翕  阅读(313)  评论(0编辑  收藏  举报