zoukankan      html  css  js  c++  java
  • 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迁移

  • 相关阅读:
    数据库 第一、二、三范式
    JVM垃圾回收(GC)整理总结学习
    ConcurrentHashMap
    Java GC、新生代、老年代
    Android -- 查看手机中所有进程
    ThreadLocal
    Android -- DrawerLayout
    WeakReference与SoftReference
    ASP.NET Core Web服务器 Kestrel和Http.sys 特性详解
    微服务架构体系
  • 原文地址:https://www.cnblogs.com/guapiwangxi/p/11684622.html
Copyright © 2011-2022 走看看