ZBlog

1566998746319

Redis详细介绍

Redis简介

  • Redis是一个面向键值对数据类型的内存数据库,可以满足我们对海量数据的快速读写的需求
  • 是一个nosql 非关系型数据库
  • Redis的键只能是字符串类型的 值可以是很多种数据类型
    • String 字符串
    • hash 哈希
    • list 字符串列表
    • set 字符串集合
    • sorted set 有序字符串集合
  • Redis的特点
    • 高性能:读11w/s 写8.1w/s 是一个单线程的服务
    • 原子性:保证数据的准确性
    • 持久存储:(RDB快照、AOF日志}
    • 主从结构
    • 支持集群 3.0以后
  • 应用场景
    • 以新浪微博为例
    • hash 关注列表 粉丝列表等
    • String 微博数量粉丝数量
    • sorted set 热门微博、TopN
  • 注意:Redis使用c语言写的 我们安装Redis之前需要先配置好c语言编译环境
  • Redis的基础指令
    • keys ? * [] ? 等查看符合规则的key 生产环境下禁用
    • exists key 判断一个键是否存在
    • del key1 key2 … 删除键值对
    • help 查看帮助
    • quit / exit 退出客户端
    • 注意:Redis的命令不区分大小写
  • Redis数据库的特性
    • 多数据库
      • 每个数据库对外就是以一个从0开始的递增的数字命名,不支持自定义
      • Redis默认支持16个数据库,可以通过修改database这个参数来修改默认值
      • Redis默认选择的是0号数据库
      • select 数字 可以选择数据库
      • 多个数据库之间并不是完全隔离的
      • flushall:清空Redis实例下的所有数据
        • flushdb 清空当前数据库中的所有数据

Redis常用数据类型

String
  • string类型是Redis最基本的数据类型,他可以存储任何形式的数据,包含二进制数据等。一个字符串类型的值存储的最大容量是1GB

  • 添加数据

    • set k v

    • 1
      2
      127.0.0.1:6379> set k1 v1
      OK
    • 一次添加多条数据

    • mset k2 v2 k3 v3

    • 1
      2
      127.0.0.1:6379> mset k2 v2 k3 v3
      OK
  • 获取数据

    • get k1

    • 1
      2
      127.0.0.1:6379> get k1
      "v1"
    • mget k2 k3

    • 1
      2
      3
      127.0.0.1:6379> mget k2 k3
      1) "v2"
      2) "v3"
  • 递增递减

    • incr 递增 decr 递减

    • 1
      2
      3
      4
      5
      6
      127.0.0.1:6379> set num 1
      OK
      127.0.0.1:6379> incr num
      (integer) 2
      127.0.0.1:6379> decr num
      (integer) 1
  • 根据指定类型递增递减

    • incrby 递增 decrby 递减

    • 1
      2
      3
      4
      127.0.0.1:6379> incrby num 2
      (integer) 3
      127.0.0.1:6379> decrby num 2
      (integer) 1
    • 以浮点数自增 不能以浮点数自减

    • 1
      2
      3
      4
      127.0.0.1:6379> incrbyfloat num 1.1
      "2.1"
      127.0.0.1:6379> decrbyfloat num 1.1
      (error) ERR unknown command 'decrbyfloat'
  • 获取长度

    • 1
      2
      3
      4
      5
      6
      127.0.0.1:6379> set k asdsad
      OK
      127.0.0.1:6379> get k
      "asdsad"
      127.0.0.1:6379> strlen k
      (integer) 6
hash
  • hash类型的值存储了字段以及字段值的映射,字段和字段值只能是字符串,不支持其他的数据类型。hash的键之多能存储2^32-1个字段

  • 添加数据

    • 1
      2
      3
      4
      127.0.0.1:6379> hset user name zs
      (integer) 1
      127.0.0.1:6379> hmset users name ls age 18
      OK
  • 获取数据

    • 1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      127.0.0.1:6379> hget user name       获取指定k的值
      "zs"
      127.0.0.1:6379> hmget users name age 获取指定k的值多个
      1) "ls"
      2) "18"
      127.0.0.1:6379> hlen users 获取k的数量
      (integer) 2
      127.0.0.1:6379> hkeys users 获取所有k
      1) "name"
      2) "age"
      127.0.0.1:6379> hvals users 获取所有v
      1) "ls"
      2) "18"
      127.0.0.1:6379> hgetall users 获取所有kv对
      1) "name"
      2) "ls"
      3) "age"
      4) "18"
  • 判断一个hash中是否存在某一个key

    • 1
      2
      3
      4
      127.0.0.1:6379> hexists user name
      (integer) 1
      127.0.0.1:6379> hexists user age
      (integer) 0
  • 递增

    • hincrby hash类型没有hincr命令
  • 删除数据

    • hdel
List
  • 是一个有序的字符串类型的列表,列表内部实现是使用双向链表实现的
    • list还可以作为队列使用 例如先进先出队列
    • 一个列表类型的键最多可以容纳2的32次方-1个元素
  • 常用指令
    • lpush/rpush/lpop/rpop 从左/从右添加数据 从左/从右取出数据
    • llen/lrange 获取列表的长度 获取列表指定范围的值查看所有 0 -1
    • lindex 查询指定角标位置的数据
    • lset 修改指定角标的值
set
  • set 集合中的所有元素都是不重复的 无序的
    • 一个集合类型的键最多存储2的32次方-1个元素
  • 常用命令
    • sadd/smembers/srem/sismember
      • 向集合中添加元素 获取集合汇总元素数量 删除集合中指定值的元素不存在即删除失败返回值为0 成功返回值为1 查看某个指定值是否存在
    • sdiff 差集
    • sinter 交集
    • sunion 并集 直接返回结果
    • sdiffstore/sinterstore/sunionstore 结果存在另一个指定的集合中
    • scard 获取集合的长度
sorted set
  • 有序集合在集合的基础上为每一个元素都关联一个分数,根据分数可以进行排序获取分数最高的几个元素等
  • 常用指令
    • zadd 向指定的key添加指定分数的值
    • zscore 查看指定值的分数
    • zrange 获取分值前几名 小到大
    • zrevrange 获取分值前几名 倒序 达到小
    • zrangebyscore 根据分值获取指定元素 默认是闭区间 可使用“(”来使用开区间
    • zincrby 指定元素分值自增
    • zcard 获取集合中的元素个数
    • zcount 获取指定分值范围内的元素
    • zrem 删除集合中的指定元素

Java操作Redis

  • 简单单机连接

    • 1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      package jredistest.jredisa;

      import redis.clients.jedis.Jedis;
      /**
      * 简单单机连接
      * @author zhao
      *
      */
      public class RedisDemo01 {
      public static void main(String[] args) {
      Jedis jedis = new Jedis("192.168.32.110",6379);

      jedis.set("aaa", "111");

      String value =jedis.get("str1");

      System.out.println(value);

      jedis.close();
      }

      }
  • 连接池连接

    • 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
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      69
      70
      71
      72
      73
      74
      75
      76
      77
      78
      79
      80
      81
      82
      83
      84
      85
      86
      87
      88
      89
      90
      package jredistest.jredisa;

      import redis.clients.jedis.Jedis;
      import redis.clients.jedis.JedisPool;
      import redis.clients.jedis.JedisPoolConfig;


      /**
      * 单机连接池
      *
      * @author zhao
      *
      */
      public class jredisDeml {
      public static void main(String[] args) {
      //连接池的配置信息
      JedisPoolConfig poolConfig = new JedisPoolConfig();
      //连接池中最大空闲连接数
      poolConfig.setMaxIdle(10);
      //连接池中最多允许创建的连接数
      poolConfig.setMaxTotal(100);
      //创建链接的最大等待时间
      poolConfig.setMaxWaitMillis(2000);
      //表示从连接池中取出链接的时候都会测试一下,这样可以保证取出的链接都是可用的
      poolConfig.setTestOnBorrow(true);

      //获取连接池
      JedisPool jedisPool = new JedisPool(poolConfig, "192.168.32.110", 6379);
      //从连接池中获取一个jedis链接
      Jedis jedis = jedisPool.getResource();
      String value = jedis.get("str");
      System.out.println(value);
      //是把链接还给连接池
      jedis.close();

      //关闭连接池
      jedisPool.close();

      }

      }

      package jredistest.jredisa;

      import redis.clients.jedis.Jedis;
      import redis.clients.jedis.JedisPool;
      import redis.clients.jedis.JedisPoolConfig;

      public class jidisTest1 {
      private jidisTest1(){}
      private static JedisPool jedisPool=null;

      /**
      * 从连接池中获取连接
      * @return
      */
      public static synchronized Jedis getJedis() {
      if(jedisPool==null) {
      JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
      //指定连接池中最大空连接数
      jedisPoolConfig.setMaxIdle(10);
      //指定连接池中最大连接数
      jedisPoolConfig.setMaxTotal(100);
      //设置最大连接超时时间
      jedisPoolConfig.setMaxWaitMillis(2000);

      jedisPoolConfig.setTestOnBorrow(true);

      jedisPool = new JedisPool(jedisPoolConfig,"192.168.32.110",6379);

      }
      return jedisPool.getResource();
      }
      /**
      * 返还连接池连接
      * @param jedis
      */
      public static void returnResource(Jedis jedis) {
      jedis.close();
      }
      /**
      * 关闭连接池
      */
      public static void closePool() {
      jedisPool.close();
      }



      }

Redis高级特性以及用法

生存时间

  • 在Redis中,可以使用expire命令设置一个键的生存时间,生存时间到了以后Redis会自动删除它
    • expire 设置生存时间
    • ttl 查看剩余生存时间 生存时间没了会显示-2
    • persist 取消生存时间 就是永久存在的 会显示-1
    • expireat 设置生存时间 时间戳

事务

  • Redis中的事务就是一组命令的集合,事务和命令一样都是Redis的最小的执行单元,一个事务中的命令要么都执行、要么都不执行(原子性)
    • 先将属于一个事务的命令发给Redis进行缓存,最后再让这些Redis执行这些命令
  • 一个事务的实例
    • multi
    • 命令1
    • 命令2
    • ….
    • exec 事务截止
    • discard 在事务运行之前放弃事务
  • 如果事务运行出现错误的处理方式
    • 语法错误:其中一条指令的语法错误就会导致整个事务不可以执行
    • 运行错误:运行过程中的错误 不会影响其他命令的执行(例如使用list的指令操作set类型的key)
  • Redis不支持回滚
    • 因为Redis不支持回滚才有了Redis事务的简洁快速
  • watch命令
    • 作用:监控一个键或者多个键,一但这个键的值被修改了立马终止之后的事务的执行
    • 但是不能保证其他客户端不修改这一键值,所以我们需要在事务失败的时候重新启动这个事务(就是说事务执行的过程中必须保证这个键值不变,在其他时候键值改变也可以)
    • 注意:当事务执行结束以后即执行完exec命令以后,watch就会取消对所有键值的监控
    • 使用 UNwatch取消监控

发布订阅模式

  • 我们可以使用Redis来实现一个发布订阅的功能
  • subscribe aaa 订阅aaa频道
  • publish aaa message 发布信息 向aaa频道发送message 信息
  • unsubscribe 取消订阅

管道

  • 针对批量数据操作使用效率高 例如批量初始化数据
  • 1568122142538
  • Redis的pipeline管道在命令行上是没有的,但是Redis支持管道,在Java的客户端可以使用
  • 管道可以减少客户端以及Redis服务器之间的交互从而大大提升命令执行的速度,在执行管道中的命令的时候中间可以添加其他命令执行

事务与管道的区别

  • 相同点:批量执行
  • 不同点:
    • 一个client发起的事务中的命令可以连续执行,而中间不会插入其他client命令,管道命令执行的过程中可以插入其他命令
    • 管道(pipline)的侧重点是效率高
    • 事务(transaction)的侧重点是一致性
    • pipeline不是原子命令,在Redis之中会把一个携带很多命令的pipeline拆分为几个子命令
    • transaction是原子命令

Redis的两种持久化

  • Redis的持久化就是就是把内存中的数据持久化存储到磁盘当中,可以保证Redis重启之后还能恢复之前的数据

RDB(Redis默认)

  • rdb方式的持久化是通过快照完成的,当符合一定条件时redis会自动将内存中的所有数据执行快照操作并存储到硬盘上。默认存储在dump.rdb文件中。(文件名在配置文件中dbfilename)
  • Redis进行快照的时机
    • save 900 1:表示900秒内至少一个键被更改则进行快照。
    • save 300 10
    • save 60 10000
  • 手动执行save或者bgsave命令让redis执行快照
    • 两个命令的区别在于,save是由主进程进行快照操作,会阻塞其它请求。bgsave是由redis执行fork函数复制出一个子进程来进行快照操作。
  • 文件修复:redis-check-rdb
  • rdb的优缺点
    • 优点:由于存储的有数据快照文件,恢复数据很方便。
    • 缺点:会丢失最后一次快照以后更改的所有数据。
  • Redis实现快照的过程
    • Redis使用fork函数复制一份当前进程的副本(子进程)
    • 父进程继续接收并处理客户端发来的请求,而子进程开始将内存中的数据写入到磁盘当中的临时文件
    • 当子进程写入万所有数据后,会使用该临时文件替换原来的旧文件,此次快照完毕
    • 注意:Redis在执行快照的过程中不会删除之前的rdb文件,只有快照结束后才会将所有的旧文件替换成为新的,也就是说任何时候rdb文件都是完整的,值就使得我们可以通过定时备份rdb文件来实现Redis数据库的备份
    • rdb文件是经过压缩的二进制文件,占用的空间会小于内存中的数据

AOF

  • aof方式的持久化是通过日志文件的方式。默认情况下redis没有开启aof,可以通过参数appendonly参数开启。 appendonly yes
  • aof文件的保存位置和rdb文件的位置相同,都是dir参数设置的,默认的文件名是appendonly.aof,可以通过appendfilename参数修改 appendfilename appendonly.aof
  • Redis同步命令的时机
    • appendfsync always 每次都会执行
    • appendfsync everysec 默认 每秒执行一次同步操作(推荐)
    • appendfsync no不主动进行同步,由操作系统来做,30秒一次
  • AOF日志文件重写
    • auto-aof-rewrite-percentage 100(当目前aof文件大小超过上一次重写时的aof文件大小的百分之多少时会再次进行重写,如果之前没有重写,则以启动时的aof文件大小为依据)
    • auto-aof-rewrite-min-size 64mb
    • 手动执行bgrewriteaof进行重写
    • 重写的过程只和内存中的数据有关,和之前的aof文件无关。
    • 所谓的“重写”其实是一个有歧义的词语,实际上, AOF 重写并不需要对原有的 AOF 文件进行任何写和读取, 它针对的是数据库中键的当前值。
  • 动态切换redis持久方式,从RDB 切换到 AOF(支持Redis 2.2及以上)
    • CONFIG SET appendonly yes
    • CONFIG SET save “”(可选)
  • 注意:当Redis启动之后,如果持久化机制RDB、AOF都已经打开了,系统默认会选择AOF来恢复数据,因为一般AOF保存的数据相对完整,如果AOF的数据丢失了也不会从RDB获取,该数据库内容就为空
  • 如果想吧正在运行的Redis数据库从RDB转换为AOF需要先动态切换,在修改配置文件,重启Redis就会从AOF中恢复数据

Redis扩展

Redis的安全策略

  • 设置数据库密码
    • 修改配置requirepass password
    • 验证密码auth password
  • bind参数 可以让数据库在指定IP下才能访问
    • bind 192.168.32.111
    • 注意:bind后面只能指定本机的IP,可以是内网IP也可以是外网IP(本机IP的意思就是指定一个本机的网卡IP 如果说本机有两个网卡,就可以指定一个网卡的IP,我们只能通过指定的网卡IP来对Redis进行访问)
    • bind:127.0.0.1 指定只能在本机访问本机Redis
  • 命令重命名
    • 修改命令的名称rename-command flushall cleanall
    • 禁用命令 rename-command flushall “”
  • 一个Redis实例最多可以存放多少key
    • Redis的存储极限就是系统中的内存值

Redis的优化

  • 精简键名和键值

    • 键名:尽量精简,但是也不能单纯为了节约空间而使用不易理解的键名。
    • 键值:对于键值的数量固定的话可以使用0和1这样的数字来表示,(例如:male/female、right/wrong)
  • 当业务不需要进行持久化的时候,关闭所有持久化机制可以获得最佳的性能

  • slowlog 记录执行慢的命令

    • slowlog-log-slower-than 它决定要对执行时间大于多少微秒(microsecond,1秒 = 1,000,000 微秒)的命令进行记录
    • slowlog-max-len 它决定 slowlog 最多能保存多少条日志
    • 当大仙Redis的性能下降的时候,可以查看日志看是哪些命令执行速度过慢
  • monitor 监控Redis的所有操作

    • 生产环境下不建议使用,除非是为了紧急排查问题 主要目的是监控所有机器对Redis的操作
  • 限制Redis的内存大小

    • 通过redis的info命令查看内存使用情况

    • 如果不设置maxmemory或者设置为0,64位系统不限制内存,32位系统最多使用3GB内存。

    • 修改配置文件中的maxmemory和maxmemory-policy

    • maxmemory:最大内存

    • maxmemory-policy:内存不足时,数据清除策略

    • 如果确定数据量不大,并且内存足够的话就不需要进行Redis的内存限制,如果数量不可预估,并且内存有限的话,尽量限制Redis的使用内存大小,这样可以避免Redis使用swap分区

    • 注意:如果不限制内存,当内存使用完以后,Redis就会占用swap分区,这样性能就比较低了,如果限制了内存,当内存用完的时候就不能再增加数据了,否则会报错OOM,可以设置maxmemory-policy,内存不足时删除数据

      如果一个Redis实例的内存使用率超过可用最大内存(used_memory>可用最大内存),那么操作系统开始进行内存与swap空间交换,把内存中旧的或不再使用的内容写入硬盘上(硬盘上的这块空间叫Swap分区),以便腾出新的物理内存给新页或活动页(page)使用。写入硬盘以后速度就会慢很多了。

  • Redis是单线程模型,客户端传过来的命令式按照顺序执行的,所以想要一次添加多条数据的时候可以使用管道或者是使用可以添加多条数据的命令

Redis的内存碎片

  • 问题:一个4G内存的Redis数据库,里面存储的key只有一万多个,结果服务器却提示内存使用率超过80%了,也没有存储很大的key。
  • 执行info命令查看内存相关信息发现
    • mem_fragmentation_ratio:20.76 (内存碎片率=used_memory_rss/used_memory)
  • 可以看到内存碎片率已经达到了20.76,内存碎片率在1~1.5之间属于正常,但超出1.5的时候就说明redis的内存管理变差了
  • 1568635349834
  • 产生原因:
    • redis自身的内存分配器,当删除数据的时候,不会马上返还内存,这样就不用每次都向OS申请内存了,从而实现高性能。
    • 修改数据的值,且修改后的value与原来value的大小差异较大。redis的每个k-v对初始化的内存大小是最适合的,当这个value改变了并且原来内存大小不适用的时候,就需要重新分配内存了。
  • 解决方案
    • 重启Redis服务器
    • Redis4.0以后,可以使用新增指令来手动回收内存碎片

Redis的应用场景

  • 排行榜相关
    • 使用sorted set
    • 获取前十名zrevrange zset 0 9
    • 获取某用户排名 zrank zset key
  • 计数
    • 使用incrby来进行计数
  • 队列
    • 使用list中的pop和push可以实现多种队列
  • 手机验证码
    • 可以使用expire来设置验证码的生存时间
  • Redis既可以作为数据库使用,也可以作为缓存系统使用

Redis的主从结构

  • 1568635628786
  • Redis的复制功能支持多个数据库之间的同步
    • 一类是主数据库(master)
    • 一类是从数据库(slave),主数据库可以进行读写操作,当发生写操作的时候自动将数据同步到从数据库,而从数据库一般是只读的,只接收主数据库同步过来的数据
    • 一个主数据库可以有多个从数据库,而一个从数据库只能有一个主数据库。
  • 修改配置文件redis.conf,修改从数据库
    • slaveof ip port
  • 或者在redis-cli命令行中动态执行slaveof命令设置主从关系
  • 通过redis的复制功能可以很好的实现数据库的读写分离,提高服务器的负载能力。主数据库主要进行写操作,而从数据库负责读操作。
  • 1568635761338
  • 复制的过程:
    1. 当一个从数据库启动时,会向主数据库发送sync命令
    2. 主数据库接收到sync命令后会开始在后台保存快照(执行rdb操作),并将保存快照期间接收到的命令缓存起来
    3. 当快照完成后,redis会将快照文件和所有缓存的命令发送给从数据库
    4. 从数据库收到后,会载入快照文件并执行收到的缓存的命令。
  • 注意:
    • 如果你使用主从复制,那么要确保你的master激活了持久化,或者确保它不会在当掉后自动重启。slave是master的完整备份,因此如果master通过一个空数据集重启,slave也会被清掉。
    • 在配置redis复制功能的时候如果主数据库设置了密码,需要在从数据的配置文件中通过masterauth参数设置主数据库的密码,这样从数据库在连接主数据库时就会自动使用auth命令认证了。相当于做了一个免密码登录。

Redis的哨兵结构

  • 1568635941132
  • redis的sentinel系统用于管理多个redis服务器,该系统主要执行三个任务
    1. 监控:Redis Sentinel实时监控主服务器和从服务器运行状态
    2. 提醒:当被监控的某个Redis 服务器出现问题时,Redis Sentinel 可以向系统管理员发送通知,也可以通过 API 向其他程序发送通知。
    3. 自动故障转移:当一个主服务器不能正常工作时,Redis Sentinel 可以将一个从服务器升级为主服务器,并对其他从服务器进行配置,让它们使用新的主服务器。当应用程序连接Redis
      服务器时, Redis Sentinel会告之新的主服务器地址和端口

Redis的集群结构

  • 1568636122066
  • redis集群是一个无中心的分布式Redis存储架构,可以在多个节点之间进行数据共享,解决了Redis高可用、可扩展等问题。redis集群提供了以下两个好处
    • 将数据自动切分(split)到多个节点
    • 当集群中的某一个节点故障时,redis还可以继续处理客户端的请求
  • 一个 Redis 集群包含 16384 个哈希槽(hash slot),数据库中的每个数据都属于这16384个哈希槽中的一个。集群使用公式 CRC16(key) % 16384 来计算键 key属于哪个槽。集群中的每一个节点负责处理一部分哈希槽。
  • 集群中的主从复制
    • 集群中的每个节点都有1个至N个复制品,其中一个为主节点,其余的为从节点,如果主节点下线了,集群就会把这个主节点的一个从节点设置为新的主节点,继续工作。这样集群就不会因为一个主节点的下线而无法正常工作。
    • 注意:如果某一个主节点和他所有的从节点都下线的话,redis集群就会停止工作了。
  • redis集群不保证数据的强一致性,在特定的情况下,redis集群会丢失已经被执行过的写命令
    • 使用异步复制(asynchronous replication)是 Redis集群可能会丢失写命令的其中一个原因,有时候由于网络原因,如果网络断开时间太长,redis集群就会启用新的主节点,之前发给主节点的数据就会丢失。
  • 添加节点
    • 主节点:如果添加的是主节点,那么我们需要创建一个空节点,然后将某些哈希槽移动到这个空节点里面
    • 从节点:如果添加的是从节点,我们也需要创建一个空节点,然后把这个新节点设置成集群中某个主节点的复制品。

博客内容遵循 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 协议

本站使用 Blog Zhao 作为主题 , 总访问量为 次 。
载入天数...载入时分秒...