Redis(一)

Redis(一)

为什么要使用Redis?

首先, 关系型数据库中的数据是存放在磁盘上的, 而磁盘的IO是有限制的. 就算MySQL对索引做了优化, 每次读一个索引, 当海量的请求压过来的时候可能查询到各种各样不同的数据, 会发生把磁盘IO占满的情况, 这种情况就是数据库的瓶颈.

那么相比磁盘, 内存的速度要快很多, 磁盘的寻址速度是ms级, 而内存的寻址速度是ns级, 内存的瓶颈也远高于磁盘.

那么将数据库整个放到内存中如何?

但是内存的价格也远比磁盘贵, 而且内存大了以后,所配套的CPU, 网卡全都需要随之定制.

那么能否有一种方案既便宜又绕开磁盘瓶颈呢?

于是就有了缓存, 将一部分数据缓存到内存中, 用的时候快速读取, 当缓存中没有的时候才去磁盘中取.

数据库引擎排行

这个页面有数据库的排行信息.

缓存的选择

缓存可以选择memory cacheredis. 那他们有什么区别呢

memory cache和redis都是键值对结构, 但是他们最大的区别就是 memory cache没有类型的概念.

在memory cache中, 想要修改一个value必须将value拿到客户端, 修改之后再set回去. 而redis提供了对数据的直接操作.

Redis的安装

  1. 打开redis官网, 找到redis最新的稳定版

  2. 复制redis下载链接

  3. 下载redis

    wget http://download.redis.io/releases/redis-6.0.5.tar.gz

    // 这里的链接就是第二步中复制的链接

  4. 解压redis

    tar xf redis-6.0.5.tar.gz

  5. 进入解压后的redis目录

    vi README.md

    // 可以看到, README中写到,

    Building Redis

    It is as simple as:

    % make

  6. 退出README.md

  7. 在redis目录

    make

  8. 如果上一步提示安装gcc, 按照提示安装之后 执行make distclean删除之前生成的文件.

  9. 执行完会输出 “Hint: It’s a good idea to run ‘make test’ ; “ 可以测试一下

  10. 之后进入src目录

    cd src

  11. 可以看到, 已经有了redis-server

  12. 启动redis-server

    ./redis-server

  13. 但是在服务器上总不至于每次重启都手动开启redis, 因此要把redis做成服务.

  14. README.md中写了, 使用make install将redis命令装入/usr/local/bin中

    make install

  15. 并且在utils中, 有一个install_server.sh, 这个仅提供给linux使用, mac不好使

    cd utils

    ./install_server.sh

    // 可能会提示

    This systems seems to use systemd.

    Please take a look at the provided example service unit files in this directory, and adapt and install them. Sorry!

    在install_server.sh中将这部分注释掉即可

  16. 运行中出现

    Please select the redis port for this instance: [6379] 这是选择这个服务器启动的端口

    Please select the redis config file name [/etc/redis/6379.conf] 选择配置文件

    Please select the redis log file name [/var/log/redis_6379.log] 配置日志地址

    Please select the data directory for this instance [/var/lib/redis/6379] 选择redis的数据目录

    Please select the redis executable path [/usr/local/bin/redis-server] 选择redis可执行文件的配置

  17. 全部配置好之后, 再一次问你 Is this ok? 直接回车确定

  18. 提示Installation successful!

  19. cd /etc/init.d查看, 确实多个redis_6379这个启动脚本

  20. 使用 service redis_6379 status查看redis状态

  21. 这样就在6379端口成功的配置了一个redis. redis可以起多个, 只要端口选择另外一个就可以了.

Redis为什么快

  1. 因为Redis首先是基于内存的,

  2. redis是k-v结构的, 类似于HashMap, 查找和操作的时间复杂度都是O(1)

  3. 单线程, 减少了不必要的线程上下文切换和竞争, 不用考虑锁

    指的是处理网络请求的时候只有单线程, 实际整个redis肯定是有多个线程的.

  4. 采用了多路复用的IO模型

多路复用

为什么要多路复用?

阻塞IO

如果是阻塞IO, 一个线程去处理一个连接, 同时有1000个连接就要起1000个线程去接收数据, 这个消耗是不可接受的.

这个线程在等待数据的时候只能进行阻塞等待, 不能做其他的事, 而线程的成本又是很高的, 所以需要减少线程.

非阻塞IO

如果只起一个线程, 这个线程去轮询的查看每个连接是否发送数据, 当连接就绪的时候读取数据.

这样是减少了线程数, 但是线程每次去访问连接都是用户态到内核态的转变, 也是非常消耗资源的.

而且存在一个将数据从连接读入的过程.

多路复用

将轮询这个操作交给内核处理, 当连接就绪的时候, 由内核通知相应的进程进行处理, 并使用共享内存进行数据的共享, 节省了拷贝数据的消耗.

Redis的使用

在前面, 装好了redis, 并且装成了服务, 那么可以直接redis-cli启动redis客户端.

redis-cli默认连接的是6379客户端, 可以通过参数修改连接的host和port等各种参数,

连接到redis之后, 可以使用help查看帮助

string

help @string

  APPEND key value
  summary: Append a value to a key
  since: 2.0.0

  BITCOUNT key [start end]
  summary: Count set bits in a string
  since: 2.6.0

  BITFIELD key [GET type offset] [SET type offset value] [INCRBY type offset in
rement] [OVERFLOW WRAP|SAT|FAIL]
  summary: Perform arbitrary bitfield integer operations on strings
  since: 3.2.0

  BITOP operation destkey key [key ...]
  summary: Perform bitwise operations between strings
  since: 2.6.0

  BITPOS key bit [start] [end]
  summary: Find first bit set or clear in a string
  since: 2.8.7

  DECR key
  summary: Decrement the integer value of a key by one
  since: 1.0.0

  DECRBY key decrement
  summary: Decrement the integer value of a key by the given number
  since: 1.0.0

  GET key
  summary: Get the value of a key
  since: 1.0.0

  GETBIT key offset
  summary: Returns the bit value at offset in the string value stored at key
  since: 2.2.0

  GETRANGE key start end
  summary: Get a substring of the string stored at a key
  since: 2.4.0

  GETSET key value
  summary: Set the string value of a key and return its old value
  since: 1.0.0

  INCR key
  summary: Increment the integer value of a key by one
  since: 1.0.0

  INCRBY key increment
  summary: Increment the integer value of a key by the given amount
  since: 1.0.0

  INCRBYFLOAT key increment
  summary: Increment the float value of a key by the given amount
  since: 2.6.0

  MGET key [key ...]
  summary: Get the values of all the given keys
  since: 1.0.0

  MSET key value [key value ...]
  summary: Set multiple keys to multiple values
  since: 1.0.1

  MSETNX key value [key value ...]
  summary: Set multiple keys to multiple values, only if none of the keys exist
  since: 1.0.1

  PSETEX key milliseconds value
  summary: Set the value and expiration in milliseconds of a key
  since: 2.6.0

  SET key value [EX seconds] [PX milliseconds] [NX|XX]
  summary: Set the string value of a key
  since: 1.0.0

  SETBIT key offset value
  summary: Sets or clears the bit at offset in the string value stored at key
  since: 2.2.0

  SETEX key seconds value
  summary: Set the value and expiration of a key
  since: 2.0.0

  SETNX key value
  summary: Set the value of a key, only if the key does not exist
  since: 1.0.0

  SETRANGE key offset value
  summary: Overwrite part of a string at key starting at the specified offset
  since: 2.2.0

  STRLEN key
  summary: Get the length of the value stored in a key
  since: 2.2.0

string可以面向 字符串, 数值, 位图

字符串

  • set

    SET key value [EX seconds] [PX milliseconds] [NX|XX]
    summary: Set the string value of a key
    since: 1.0.0
    group: string

    set key value 后面可以加过期时间, 还可以加 NX,XX,

    NX是不存在的时候才set, 存在时无效. 可以用于分布式锁

    XX是存在的时候才set, 不存在时无效. 可以用于更新

  • mset

    MSET key value [key value …]
    summary: Set multiple keys to multiple values
    since: 1.0.1
    group: string

    可以设置多个key-value

  • get

  • mget

  • append

    可以追加字符串

    127.0.0.1:6379> get key1
    (nil)
    127.0.0.1:6379> set key1 hello
    OK
    127.0.0.1:6379> append key1 world
    (integer) 10
    127.0.0.1:6379> get key1
    “helloworld”

  • getrange

    127.0.0.1:6379> getrange key1 5 9
    “world”

    127.0.0.1:6379> getrange key1 5 -1
    “world”

    字符串支持正负向索引, -1代表字符串结束, -2代表倒数第二个位置.

  • setrange

    127.0.0.1:6379> setrange key1 5 3306
    (integer) 10
    127.0.0.1:6379> get key1
    “hello3306d”

数值型

key中会带有类型信息, 用来描述对应value的类型.

127.0.0.1:6379> type key1
string

因为redis的五种value都会有自己的方法, 方法和类型绑定的.

在操作value时, 如果方法的类型和实际类型不匹配的话, 一对比就可以直接抛错.

使用 object encoding key可以查看key的编码类型, 对于set key2 99来说, 获得的结果就是"int"

对于数值类型, 对他可做的操作就是增减, incr和decr用来加一或减一, incrby和decrby用来加一个数或减一个数.

还可以对数值类型加减一个小数使用incrbyfloat.

二进制安全

在redis进程与外界交互的时候, 使用的是字节流. 即, 只用于读取数据, 不会通过任何形式解析数据.

这样一来, 只要双方依然按照二进制的形式进行编码解码, 数据就不会被破坏.

127.0.0.1:6379> set k1 99999
OK
127.0.0.1:6379> strlen k1
(integer) 5
127.0.0.1:6379> append k1 99
(integer) 7
127.0.0.1:6379> strlen k1
(integer) 7
127.0.0.1:6379> object encoding k1
“raw”
127.0.0.1:6379> incr k1
(integer) 10000000
127.0.0.1:6379> object encoding k1
“int”

除了存储type以外, 在key上还会存储编码, 基于二进制安全的考虑, redis并没有用1个字节存储-128~127之间的内容, 而是每一位都单独存储.

当调用incr等方法需要做计算的时候, 会将这些字节取出来整理成可计算的数值.

如果没有报错, 说明计算完的结果也可以作为数值使用, 就将编码类型改成数值型, 下次就不用再做判断.

位图

redis中, 位图是一个字节一个字节组成的, SETBIT key offset value中的offset的含义是整个二进制串中的第几个位置.

127.0.0.1:6379> setbit k1 1 1
(integer) 0
127.0.0.1:6379> get k1
“@”
127.0.0.1:6379> strlen k1
(integer) 1

k1代表一个字节, 0000 0000, 将1位置改为1 , 0100 0000, 它对应的ascii码就是@符号.

127.0.0.1:6379> setbit k1 7 1
(integer) 0
127.0.0.1:6379> get k1
“A”
127.0.0.1:6379> strlen k1
(integer) 1

改到7位置的时候, 因为字节长度索引最长是7, 所以还没越界, k1的长度没有加1.

127.0.0.1:6379> setbit k1 8 1
(integer) 0
127.0.0.1:6379> get k1
“A\x80”
127.0.0.1:6379> strlen k1
(integer) 2

改到8位置的时候, 一个字节已经装不下了, 就添加了一个字节来放8位置的1.

而且前一个字节的ascii对应A, 后一个输出的是16机制的1000 0000, 因为8代表的是第二个字节的第一位.

list

list维护的是一个双向链表, 并且维护了头和尾节点.

操作命令有lpush,rpush,lpop,rpop, 从同侧进出就是栈, 从不同侧进出就是队列.

lpush的意思是从链表的左侧压入. lpop的意思是从链表的左侧弹出.

同时list还可以实现一个阻塞的单播队列.

blpop

BLPOP key [key …] timeout
summary: Remove and get the first element in a list, or block until one is ava
ilable
since: 2.0.0
group: list

当两个客户端同时对一个key进行blpop时, 因为是Remove and get, 所以一个线程获取到之后停止阻塞, 另一个线程获取不到, 继续阻塞.

blpop也可以设置超时时间, 当时间到了还没有获取到就返回空.

hash

hash约等于java中的HashMap.

相当于在key对应的value中再放一个hashMap.

127.0.0.1:6379> hset u1 name zhangsan
(integer) 1
127.0.0.1:6379> hmset u1 age 18 aaddress cn
OK
127.0.0.1:6379> hget u1 name
“zhangsan”
127.0.0.1:6379> hmget u1 age aaddress
1) “18”
2) “cn”
127.0.0.1:6379> hvals u1
1) “zhangsan”
2) “18”
3) “cn”
127.0.0.1:6379> hgetall u1
1) “name”
2) “zhangsan”
3) “age”
4) “18”
5) “aaddress”
6) “cn”

set

set就类似java中的HashSet.

存储不相同的值, 主要操作有sadd, srem等增删操作和sinter取交集, sunion取并集, sdiff取差集.

srandmember 取随机几个元素. 后面接的count可能有几种情况, 正负数, 绝对值是否超过set长度.

如果是正数, 会保证结果不重复, 但是返回条数不一定会达到count值.

如果是负数, 一定会返回条数达到count值, 但是不能保证不重复.

但是不管怎么取, 本身这个set里的值是不变的.

127.0.0.1:6379> sadd k1 xooo oxoo ooxo ooox
(integer) 4
127.0.0.1:6379> srandmember k1 5
1) “ooox”
2) “xooo”
3) “ooxo”
4) “oxoo”
127.0.0.1:6379> srandmember k1 -5
1) “ooox”
2) “ooox”
3) “ooox”
4) “xooo”
5) “oxoo”

spop可以从set中随机弹出一个或几个元素, 就像抽奖一样, 取出一个小球不放回.

127.0.0.1:6379> spop k1 2
1) “oxoo”
2) “xooo”
127.0.0.1:6379> smembers k1
1) “ooox”
2) “ooxo”

sorted set

sprted set类似于java中的TreeSet, 放入的元素是有序的.

但是这个有序和list的有序不同, list的有序是放入元素的有序, sorted set的有序是元素本身的有序, 跟放入顺序无关.

ZADD key [NX|XX] [CH] [INCR] score member [score member …]
summary: Add one or more members to a sorted set, or update its score if it al
ready exists
since: 1.2.0
group: sorted_set

sorted set添加的时候需要制定一个score,

放入sorted set的元素按照score从小到大的排序.

127.0.0.1:6379> zadd k1 8 apple 2 banana 3 peach 8 orange
(integer) 4
127.0.0.1:6379> zrange k1 0 -1
1) “banana”
2) “peach”
3) “apple”
4) “orange”

zrange是按照索引取元素, zrangebyscore是按照score取元素.

这是从小到大取, 如果从大到小怎么取. zrevrange.

对于分数也可以进行增减, 在增减后, set中的顺序也在改变.

127.0.0.1:6379> zincrby k1 1.5 banana
“3.5”
127.0.0.1:6379> zrange k1 0 -1 withscores
1) “peach”
2) “3”
3) “banana”
4) “3.5”
5) “apple”
6) “8”
7) “orange”
8) “8”

zunionstore将两个sorted set合并存储. 默认将两个set中相同的score相加, 可以通过调整两个set的权重来调调整score.

可以使用aggreate来指定如何取score.

sorted set的底层存储结构

使用的是skip list, 跳表.

跳表就是在链表基础上的优化, 普通的链表查询效率是 O(n), 而跳表通过层级的跳跃, 会减小查询的复杂度, 使之变为O(logn), 即使用链表达到了数组二分的复杂度.


   转载规则


《Redis(一)》 echi1995 采用 知识共享署名 4.0 国际许可协议 进行许可。
 上一篇
Redis(二) Redis(二)
Redis(二)管道可以使用nc连接redis, 只要连接到redis对应的端口, 比如6379, 就可以使用redis中的命令. 通过echo将多个命令组合起来, echo可以识别\n, 使用\n将多条命令拼起来, 在一次网络请求发送过去
下一篇 
深入理解JVM(三) 深入理解JVM(三)
深入理解JVM(三)GC算法CMSCMS在任何一个JAVA版本中都不是默认的垃圾回收器, 但是同时他又是一个非常重要的GC. Serial和Parallel都是无法和工作线程同时相应, 必须垃圾收集结束才可以进行工作线程的继续. 这就导致了
  目录