目录

redis使用场景

复习

redis的特点

  • 基于内存,快
  • 数据模型,KV,用来做缓存
  • 单线程,单线程是指worker是单线程的,io是多线程的,收到的任务放到一个队列里去做
  • 支持高并发,连接数很多,linux中使用epoll
  • 本地方法:计算向数据移动,IO优化
  • 整体模型是串行化/原子
  • 持久化
  • 集群
  • 主从复制

本地方法:计算向数据移动,IO优化

本地方法:index、incr,decr

redis的数据存储格式是bson,不像memcache是json

https://gitee.com/lienhui68/picStore/raw/master/null/image-20200928140825344.png

线程模型,worker 单线程

https://gitee.com/lienhui68/picStore/raw/master/null/image-20200928140907552.png

第一步:通过epoll知道哪些事件可以读

第二步:IO读取,因为是单线程所以是串行化的读取

第三步:计算,串行化,实际上io读取和写是多线程的。

秒杀

redis的串行化比数据库的串行化(事务)要快。

一个tomcat 几百或者千的连接数,当连接数达到几万,无法用一个tomcat解决

解决不了这么高并发怎么办

4层 lvs 7层 ngix 负载均衡

https://gitee.com/lienhui68/picStore/raw/master/null/image-20200928141208362.png

redis worker是单线程的,如何充分利用cpu资源

先计算1还是2 没法保证,因为read是并行的

工作线程是一个就满足串行/原子

https://gitee.com/lienhui68/picStore/raw/master/null/image-20200928142138334.png

2个客户端连接,上面是6个时间片,下面只需要4个时间片,连接越多,cpu核数越多,这两者的差距也就越大。

c1和c2是乱序的,但是同一个IO里读到的是有序的,在一个tcp连接是有序的。如果要保证有序的,客户端还需要保证串行化,上锁。

https://gitee.com/lienhui68/picStore/raw/master/null/image-20200928142959434.png

redis 6.x 以前,单线程

redis 6.x 以后,worker单线程,io多线程,默认不开启,需要手动开启

使用场景

数据类型

string

  • 字符串

    redis比较认字节,strlen返回字节数

  • 数值

    incr,decr

    上面这两个应用场景有:session共享,kv缓存,数值计算计数器,基于内存的fs文件系统(碎片文件,不能太大,如果不使用redis,并发很大,要访问的小文件很多,pagecache 又cache不住就会产生频繁磁盘IO,整体性能就拉胯很多

  • 二进制位

    可以使用help @string查看相关bit操作命令

    位图,索引优化,内存压榨,数据传输或者类型判定都经常被用到。

    1
    2
    3
    4
    5
    6
    7
    8
    
    127.0.0.1:6379> setbit k1 1 1
    (integer) 0
    127.0.0.1:6379> get k1
    "@"
    127.0.0.1:6379> setbit k1 7 1
    (integer) 0
    127.0.0.1:6379> get k1
    "A"
    

    ascii 64就表示 “@”,65表示A

    utf8是ascii码的扩展集。

    接着上面的k1继续执行

    1
    2
    3
    4
    5
    6
    
    127.0.0.1:6379> setbit k1 9 1
    (integer) 0
    127.0.0.1:6379> get k1
    "A@"
    127.0.0.1:6379> strlen k1
    (integer) 2  # 此时显示2个字节数
    

    让下标继续延长,往右继续延长,会自动扩展字节的宽度。

    如果此时再做如下操作

    1
    2
    3
    4
    
    127.0.0.1:6379> setbit k1 8 1
    (integer) 0
    127.0.0.1:6379> get k1
    "A\xc0"
    

    因为是utf-8编码,前面1个字节是0打头正常显示ascii码,后面一个字节是110打头,表示有俩字节表示一个字符,但实际只有一个字符,所以只显示一个字节的16进制表示形式\xc0

    https://gitee.com/lienhui68/picStore/raw/master/null/image-20200928214904207.png

    BITCOUNT

    1
    2
    3
    4
    
    BITCOUNT key 开始字节下标 结束字节下标
    eg:
    $ BITCOUNT k1 0 0
    (integer) 2 # 第0个字节bit是1的bit个数有2个
    

    BITCOUNT 还支持负向索引,-1表示倒数第一个字节,例如下面统计key对应的value所有字节的bit为1的个数

    1
    2
    
    127.0.0.1:6379> bitcount k1 0 -1
    (integer) 4
    

    BITOP

    按位操作

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    
    $ BITOP and 与结果 k1 k2
    eg:
    127.0.0.1:6379> setbit k1 7 1
    (integer) 0
    127.0.0.1:6379> SETBIT k1 1 1
    (integer) 0
    127.0.0.1:6379> get k1
    "A"
    127.0.0.1:6379> SETBIT k2 6 1
    (integer) 0
    127.0.0.1:6379> SETBIT k2 1 1
    (integer) 0
    127.0.0.1:6379> get k2
    "B"
      
    127.0.0.1:6379> BITOP and andkey k1 k2
    (integer) 1
    127.0.0.1:6379> get andkey
    "@"
      
    $ BITOP or 或结果 k1 k2
    127.0.0.1:6379> BITOP or orkey k1 k2
    (integer) 1
    127.0.0.1:6379> get orkey
    "C"
    

    使用场景

    1. 统计任意时间窗口一个用户的登录情况

       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      
      127.0.0.1:6379> SETBIT david 3 1 # 第4天登录
      (integer) 0
      127.0.0.1:6379> SETBIT david 6 1 # 第7天登录
      (integer) 0
      127.0.0.1:6379> SETBIT david 364 1 # 第365天登录
      (integer) 0
      127.0.0.1:6379> BITCOUNT david # 统计有多少天登录
      (integer) 3 # 3天
      127.0.0.1:6379> STRLEN david # 查看占用了多少字节
      (integer) 46
      
    2. 统计活跃用户数,日活,环比等 -> 业务决策

       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      12
      13
      
      $SETBIT 日期 userId 1/0 # 超过512M可以做分桶,512M可以表示5亿多的用户
      eg:
      127.0.0.1:6379> SETBIT 20200928 123 1 # 用户123于20200928登录
      (integer) 0
      127.0.0.1:6379> SETBIT 20200928 124 1 # 用户124于20200928登录
      (integer) 0
      127.0.0.1:6379> SETBIT 20200929 123 1 # 用户123于20200929登录
      (integer) 0
      # 查看这两天的活跃用户数,使用按位或即可
      127.0.0.1:6379> BITOP or res 20200928 20200929
      (integer) 16
      127.0.0.1:6379> bitcount res
      (integer) 2 # 这两天共有2个用户登录
      

list

数据结构:

https://gitee.com/lienhui68/picStore/raw/master/null/image-20200928221358384.png

  • 同向 栈 lpush lpop

    1
    2
    3
    4
    5
    6
    7
    8
    
    127.0.0.1:6379>
    127.0.0.1:6379> LPUSH stack 1 2 3 4
    (integer) 4
    127.0.0.1:6379> LRANGE stack 0 -1
    1) "4"
    2) "3"
    3) "2"
    4) "1"
    
  • 异向 队列 lpush rpop

    1
    2
    3
    4
    5
    6
    7
    
    127.0.0.1:6379> RPUSH list 1 2 3 4
    (integer) 4
    127.0.0.1:6379> LRANGE list 0 -1
    1) "1"
    2) "2"
    3) "3"
    4) "4"
    
  • lindex 数组

    1
    2
    3
    4
    5
    6
    7
    
    127.0.0.1:6379> LRANGE list 0 -1
    1) "1"
    2) "2"
    3) "3"
    4) "4"
    127.0.0.1:6379> LINDEX list 2
    "3"
    
  • ltrime 字符串,清除两端的数据,优化redis内存量

    1
    2
    3
    4
    5
    6
    
    127.0.0.1:6379> LTRIM list 0 -2
    OK
    127.0.0.1:6379> LRANGE list 0 -1
    1) "1"
    2) "2"
    3) "3"
    

    比如评论,无需显示所有评论,只需要显示前面几条评论即可,这样就达到了热数据的目的。

  • 顺序概念,放入的顺序

使用场景

  • 消息队列

    生产者消费者

  • 评论列表

  • 替代java的容器

    好处是可以让jvm的服务无状态,当jvm重启时可以继续使用容器里的数据。

hash

https://gitee.com/lienhui68/picStore/raw/master/null/image-20200928223544753.png

  • 如果没有hash

    1
    2
    3
    4
    
    127.0.0.1:6379> set david::name eh
    OK
    127.0.0.1:6379> set david::age 18
    OK
    
  • 有了hash

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    
    127.0.0.1:6379> FLUSHALL
    OK
    127.0.0.1:6379> hset david name eh
    (integer) 1
    127.0.0.1:6379> hset david age 18
    (integer) 1
    127.0.0.1:6379> HGETALL david
    1) "name"
    2) "eh"
    3) "age"
    4) "18"
    # scheme
    127.0.0.1:6379> hkeys david
    1) "name"
    2) "age"
    # 列值
    127.0.0.1:6379> hvals david
    1) "eh"
    2) "18"
    # 还支持计算
    127.0.0.1:6379> HINCRBY david age -1
    (integer) 17
    

使用场景

  • 详情页

    除了静态的字符串文本title、描述之外,还可以保存 收藏数、购买数、评论数,喜欢数,单线程原子的,支持在内存中计算速度极快

    ps:这些数据差一点也不是太重要

  • 聚集数据

    一个用户的数据来自好几个模块多个数据库的内容,当这些内容不怎么变化且被频繁访问的时候可以做一个聚集来缓存数据,可以先走个批处理将这些数据挨个查一遍,然后根据订单号或者某个商品或者userId聚集起来,之后用户来查的时候就很快,也减少了后端数据库访问的压力。

  • 用户好友数

set

集合

无序(底层是hashmap,rehash之后顺序就不一样了),不重复

  • 集合 交并差,单线程,后面的操作干不了。

    不推荐,但是也可以使用多个实例,让某些实例专门做集合交并差。

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    
    127.0.0.1:6379> FLUSHALL
    OK
    127.0.0.1:6379> sadd k1 a b c d e
    (integer) 5
    127.0.0.1:6379> sadd k2 c d e g f
    (integer) 5
    127.0.0.1:6379> SUNION k1 k2 # 并集
    1) "f"
    2) "g"
    3) "d"
    4) "a"
    5) "c"
    6) "b"
    7) "e"
    127.0.0.1:6379> SINTER k1 k2 # 交集
    1) "c"
    2) "d"
    3) "e"
    127.0.0.1:6379> SDIFF k1 k2 # k1 - k2
    1) "a"
    2) "b"
    127.0.0.1:6379> SDIFF k2 k1 # k2 - k1
    1) "f"
    2) "g"
    

    使用场景:

    • 共同好友 并集
    • 好友推荐 差集
  • 随机事件

    抽奖

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    
    # 随机返回n个元素的集合
    $ SRANDMEMBER key 正数/负数 # 正数表示不重复,负数表示可以重复
    eg:
    127.0.0.1:6379> SADD set a b c d e a b
    (integer) 5
    127.0.0.1:6379> SMEMBERS set
    1) "d"
    2) "a"
    3) "b"
    4) "c"
    5) "e"
    127.0.0.1:6379> SRANDMEMBER set -8
    1) "a"
    2) "d"
    3) "c"
    4) "e"
    5) "a"
    6) "b"
    7) "d"
    8) "d"
    127.0.0.1:6379> SRANDMEMBER set 3
    1) "b"
    2) "c"
    3) "e"
    # 随机返回一个元素
    $ SPOP key
    
    • 验证码
    • 扑克牌游戏
    • …其他随机的事件

zset

有序集合

去重,排序

help @sorted_set

根据分值在内存动态排序

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
127.0.0.1:6379> FLUSHALL
OK
127.0.0.1:6379> zadd k1 22.5 apple 11 orange 33 banana
(integer) 3
127.0.0.1:6379> ZRANGE k1 0 -1 withscores
1) "orange"
2) "11"
3) "apple"
4) "22.5"
5) "banana"
6) "33"
# 取出从小到大的前两名,如果分值相同会按字典序
127.0.0.1:6379> ZRANGE k1 0 1
1) "orange"
2) "apple"
# 取出从大到小的前两名
127.0.0.1:6379> ZREVRANGE k1 0 1
1) "banana"
2) "apple"
# 取出某个范围之内的,比如某段时间内的评论列表
127.0.0.1:6379> ZRANGEBYSCORE k1 20 40
1) "apple"
2) "banana"

使用场景

  • 排行榜
  • 评论: 排序/分页,动态排序,像内存

动态排序

  • 是否有重排序的过程?

    没有,只是从左向右取改成从右向左取。

  • 排序方式,数据结构

    每种数据类型在数据量小的时候都维持一些基本的数据结构,例如压缩表这种不怎么占内存的数据结构

    value的数量更大的时候,会牺牲空间换时间,切换到skiplist 跳表

    object help

    1
    2
    3
    4
    5
    6
    
    127.0.0.1:6379> object help
    1) OBJECT <subcommand> arg arg ... arg. Subcommands are:
    2) ENCODING <key> -- Return the kind of internal representation used in order to store the value associated with a key.
    3) FREQ <key> -- Return the access frequency index of the key. The returned integer is proportional to the logarithm of the recent access frequency of the key.
    4) IDLETIME <key> -- Return the idle time of the key, that is the approximated number of seconds elapsed since the last access to the key.
    5) REFCOUNT <key> -- Return the number of references of the value associated with the specified key.
    
    1
    2
    3
    4
    5
    6
    7
    8
    
    127.0.0.1:6379> object encoding k1
    "ziplist" # 当前数据量小,使用压缩表的方式
    127.0.0.1:6379> FLUSHALL
    OK
    127.0.0.1:6379> zadd k1 80 representationrepresentationrepresentationrepresentationrepresentationrepresentationrepresentationrepresentationrepresentationrepresentationrepresentationrepresentationrepresentationrepresentationrepresentationrepresentationrepresentationrepresentationrepresentationrepresentationrepresentationrepresentationrepresentationrepresentationrepresentationrepresentationrepresentationrepresentationrepresentationrepresentation
    (integer) 1
    127.0.0.1:6379> OBJECT encoding k1
    "skiplist" # 切换成跳表了
    
  • 跳表

    • 什么是跳表

      站内引用: {%post_link 工作/100_计科基础/数据结构与算法/理论/跳表 跳表 %}

      只要往右看为空就降层,可以跳过一些元素访问

    • 为什么选择跳表

      跟红黑树的速度性能差不多,但是实现简单,当然占用的空间也多

    • 造层是层数是随机层数

      可以查看源码

      https://gitee.com/lienhui68/picStore/raw/master/null/image-20200928234225746.png

      https://gitee.com/lienhui68/picStore/raw/master/null/image-20200928234300328.png

      ZSKIPLIST_MAXLEVEL 的值 是64。