Redis 学习笔记(一)——基本概念及使用


一、基本概念

  REDIS——REmote DIctionary Server
  Redis 的一些特点和相对于其他存储系统的优势
 1. Redis 支持数据的持久化,可以将内存中的数据保存在磁盘中,重启的时候可以再次加载进行使用;
 2. Redis 支持五种数据类型:string、hash(哈希)、list(列表)、set(集合)及 zset(sorted set:有序集合);
 3. Redis 支持数据的备份,即 master-slave 模式的数据备份;
 4. 极高的性能,Redis 读速度是110000次/s,写速度是81000次/s ;
 5. 原子性,Redis 的所有单个操作都是原子性的。
  Redis是一个内存数据库,相对于其他非内存数据库来说,他会在启动时利用 RDB 文件或者 AOF 文件一次性的将全部数据加载到内存中,一次预热。
  Redis 数据库的概念
  Redis 支持多个数据库,并且每个数据库的数据是隔离的不能共享,并且基于单机才有,如果是集群就没有数据库的概念。
  Redis 是一个字典结构的存储服务器,而实际上一个 Redis 实例提供了多个用来存储数据的字典,客户端可以指定将数据存储在哪个字典中。这与我们熟知的在一个关系数据库实例中可以创建多个数据库类似,所以可以将其中的每个字典都理解成一个独立的数据库。每个数据库对外都是一个从0开始的递增数字命名,Redis 默认支持16个数据库(可以通过修改配置文件支持更多,无上限)。客户端与 Redis 建立连接后会自动选择0号数据库,可以使用 SELECT 命令更换数据库。
  然而这些以数字命名的数据库又与我们理解的数据库有所区别。首先 Redis 不支持自定义数据库的名字,每个数据库都以编号命名,开发者必须自己记录哪些数据库存储了哪些数据。另外 Redis 也不支持为每个数据库设置不同的访问密码,所以一个客户端要么可以访问全部数据库,要么都无法访问。最重要的一点是多个数据库之间并不是完全隔离的,比如 FLUSHALL 命令可以清空一个 Redis 实例中所有数据库中的数据。综上所述,这些数据库更像是一种命名空间,而不适宜存储不同应用程序的数据,不同的应用应该使用不同的 Redis 实例存储数据。由于 Redis 非常轻量级,一个空 Redis 实例占用的内在只有1M左右,所以不用担心多个 Redis 实例会额外占用很多内存。

二、使用

1、启动及配置

  redis 官网不提供支持 windows 操作系统的版本,需要到 https://github.com/MicrosoftArchive/redis/releases 下载对应版本(较 linux 的版本有落后),点击安装即可,以下应用全部基于 windows 操作系统。
  安装完成后,环境变量配置 redis 路径或者 cmd 切换到 redis 目录下,通过如下命令启动 redis 服务:

redis-server.exe redis.windows.conf

  启动完成后,另开一个 cmd 窗口,切换到 redis 目录下,测试 redis 是否可用。

redis-cli.exe -h 127.0.0.1 -p 6379
set myKey abc //设置键值对
get myKey //去除键值

  redis 的配置文件为 redis.windows.conf,可直接进行查看和修改,也可使用 config 命令

CONFIG GET 要查看的配置名称
CONFIG GET *
CONFIG SET 要设置的配置名称 新的值

  redis 主要配置说明:

配置含义配置含义
daemonize no是否开启守护线程pidfile /var/run/redis.pid当 Redis 以守护进程方式运行时pid的写入位置
port 6379监听端口bind 127.0.0.1主机地址
timeout 300客户端闲置多长时间时关闭连接(值为0表示关闭该功能)loglevel verbose日志记录级别(debug、verbose、notice、warning,默认 verbose)
logfile stdout日志记录方式databases 16数据库数量
save seconds changes指定在多长时间内有多少次更新操作就将数据同步到数据文件(执行 BGSAVE 命令),如:save 300 10,可以设置多个条件配合rdbcompression yesrdb 文件存储至本地数据库时是否压缩数据,Redis 采用 LZF 压缩,若要节省 CPU 时间,可关闭该功能,但会导致数据库文件变的巨大
dbfilename dump.rdb指定本地数据库文件名dir ./指定本地数据库存放目录
slaveof masterip masterport当讲本机设置为slav服务时,启动时会自动从该处配置的 master 服务进行数据同步masterauth master-password连接指定的 master 服务的密码
requirepass foobared设置本服务的密码(设置密码后,客户端连接时需要使用 AUTH password 命令提供密码)maxclients 128同一时间最大客户端连接数(当超过限制时会向新连接客户端返回 max number of clients reached 错误)
maxmemory bytesRedis 最大内存限制,Redis 启动时会把数据加载到内存中,达到最大内存后会先尝试清除已到期或即将到期的 Key,当此方法处理后,仍然到达最大内存设置,将无法再进行写入操作,但仍然可以进行读取操作。Redis 新的 vm 机制,会把 Key 存放内存,Value 存放在 swap 区appendonly no指定是否在每次更新操作后进行日志记录,默认不开启。 Redis 默认情况下是异步的把数据写入磁盘中,其同步数据文件规则依照上面 save 条件,所以有的数据会在一段时间内只存在于内存中,可能会在断电时导致一段时间内的数据丢失
appendfilename appendonly.aof更新日志文件名appendfsync everysec更新日志条件,no:表示等操作系统进行数据缓存同步到磁盘(快),always:表示每次更新操作后手动调用 fsync() 将数据写到磁盘(慢,安全),everysec:表示每秒同步一次(折中,默认值)
auto-aof-rewrite-percentage 100触发AOF文件自动重写的条件auto-aof-rewrite-min-size 64mb触发AOF文件自动重写的条件
vm-enabled no是否启用虚拟内存机制,VM 机制将数据分页存放,由 Redis 将访问量较少的页即冷数据 swap 到磁盘上,访问多的页面由磁盘自动换出到内存中vm-swap-file /tmp/redis.swap虚拟内存文件路径
vm-max-memory 0将所有大于 vm-max-memory 的数据存入虚拟内存,当设置为0时,所有 value 都存在于磁盘。注意,无论 vm-max-memory 设置多小,所有索引数据都是内存存储的(这里的索引指 redis 的 key 值)vm-page-size 32Redis swap 文件分成了很多的 page,一个对象可以保存在多个 page 上面,但一个 page 上不能被多个对象共享,它是要根据存储的数据大小来设定的,若存储很多小对象,page 大小最好设置为32或者64bytes;若存储大对象,则可以使用更大的page
vm-pages 134217728swap 文件中的 page 数量,注意由于页表(一种表示页面空闲或使用的状态值集合bitmap)是在放在内存中的,所以在磁盘上每8个 pages 将消耗1byte的内存。vm-max-threads 4访问 swap 文件的线程数,最好不要超过机器的核数,若设置为0,那么所有对 swap 文件的操作都是串行的,可能会造成比较长时间的延迟
glueoutputbuf yes在向客户端应答时,是否把较小的包合并为一个包发送hash-max-zipmap-entries 64或者hash-max-zipmap-value 512指定在超过一定的数量或者最大的元素超过某一临界值时,采用一种特殊的哈希算法
activerehashing yes是否激活重置哈希include /path/to/local.conf指定包含其它的配置文件,可以在同一主机上多个 Redis 实例之间使用同一份配置文件,而同时各个实例又拥有自己的特定配置文件

2、数据类型

  String: 最大存储512MB
  hash: 多个 string 类型的 field 和 value 构成的映射表,适合用于存储对象,每个 hash 可以存储 232 -1 键值对
  list: 字符串列表,按照插入顺序排序,一个列表最多存储232 -1个元素
  set: 字符串无序集合,元素不可重复
  zset: 字符串有序,元素不可重复,每个元素都会关联一个 double 类型的分数,redis 通过分数来为集合中的成员进行从小到大的排序(不同的元素所关联的分数可相同)
  相关操作:

SET name “lxw”  //string
GET name
DEL name  //删除数据


HMSET myhash field1 “hello” field2 “world”  //hash
HGET myhash field2


LPUSH mylist haha  //list
LPUSH mylist hehe
LRANGE mylist 0 10


SADD myset haha  //set
SADD myset hehe
SMEMBERS myset


ZADD myzset 0.1 aaa  //zset
ZADD myzset 0.1 bbb
ZADD myzset 10 ccc
ZRANGEBYSCORE myzset 0 100

3、常用操作

  • 启动客户端:redis-cli -h host -p port -a password(可加 --raw 使中文正常显示)
  • 检查 redis 服务是否启动:PING
  • 验证密码:AUTH password
  • 切换数据库:SELECT index
  • 键相关:命令+KEY_name

    • DEL key:删除
    • DUMP key:序列化 key
    • EXISTS key:检查是否存在
    • EXPIRE key seconds:设定过期时间(秒)
    • PEXPIRE key milliseconds:设定过期时间(毫秒)
    • KEYS PATTERN:查找符合格式的 key(如 keys te* 查找所有 te 开头的 key)
    • MOVE key db:将 key 移动到给的的数据库
    • PERSIST key:移除 key 的过期时间,永久保存
    • PTTL key:返回 key 的剩余过期时间(毫秒)
    • TTL key:返回 key 的剩余过期时间(秒)
    • RANDOMKEY:从当前所在数据库随机返回一个 key
    • RENAME key newkey:修改 key 的名称(若 newkey 存在会覆盖)
    • RENAMENX key newkey:仅当 newkey 不存在时修改 key 的名称
    • TYPE key:返回 key 存储的值类型
  • String 相关

    • SET key value
    • GET key
    • GETRANGE key start end:返回值的所选范围子串(保留收尾)
    • GETSET key value:设定新值同时返回旧值
    • GETBIT key offset:获取对应值指定偏移量上的位(bit)
    • MGET key1 [key2…]:返回所有指定的 key 的值
    • SETBIT key offset value:设置或清除指定偏移量上的位(bit)
    • SETEX key seconds value:同时设置 key 的值和过期时间(秒)
    • PSETEX key milliseconds value:同时设置 key 的值和过期时间(毫秒)
    • SETNX key value:当 key 不存在时才设置 key 的值
    • SETRANGE key offset value:用 value 参数覆写给定 key 所储存的字符串值,从偏移量 offset 开始
    • STRLEN key:值长度
    • MSET key value [key value …]:同时设置多个 key 的值
    • MSETNX key value [key value …]:同时设置多个 key-value 对,当且仅当所有给定 key 都不存在。
    • INCR key:将 key 中储存的数字值增一
    • INCRBY key increment:将 key 所储存的值加上给定的增量值
    • INCRBYFLOAT key increment:将 key 所储存的值加上给定的浮点增量值
    • DECR key:将 key 中储存的数字值减一
    • DECRBY key decrement:key 所储存的值减去给定的减量值
    • APPEND key value:如果 key 存在且是字符串, 将指定的 value 追加到该 key 原值末尾
  • hash 相关

    • HDEL key field1 [field2]:删除一个或多个哈希表字段
    • HEXISTS key field:查看指定的字段是否存在
    • HGET key field:获取对应字段
    • HMGET key field1 [field2]:获取所有指定的字段值
    • HGETALL key:获取所有字段名及值
    • HKEYS key:获取所有字段名
    • HVALS key:获取所有值
    • HLEN key:字段数量
    • HMSET key field1 value1 [field2 value2 ]:同时设置多个字段-值对
    • HSET key field value:设置字段-值对
    • HSETNX key field value:只有字段不存在时才设置对应的值
  • list 相关

    • BLPOP key1 [key2 ] timeout:移出并获取列表头部元素, 若列表没有元素,会阻塞直到等待超时或发现可弹出元素为止
    • BRPOP key1 [key2 ] timeout:移出并获取列表尾部元素, 若列表没有元素,会阻塞直到等待超时或发现可弹出元素为止
    • BRPOPLPUSH source destination timeout:从列表尾部弹出值,插入到另一个列表头部中并返回它; 如果列表没有元素会阻塞直到等待超时或发现可弹出元素为止
    • LINDEX key index:通过索引获取列表中的元素
    • LSET key index value:通过索引设置列表元素的值
    • LINSERT key BEFORE|AFTER pivot value:在列表的元素前/后插入元素
    • LLEN key:获取列表长度
    • LPOP key:移出并获取列表头部元素
    • LPUSH key value1 [value2]:将一个或多个值插入到列表头部
    • LPUSHX key value:将一个值插入到已存在的列表头部
    • LRANGE key start stop:获取列表指定范围内的元素
    • LREM key count value:移除列表元素
    • LTRIM key start stop:修剪列表,删除不在指定区域内的元素
    • RPOP key:移除列表的尾部元素,返回值为移除的元素
    • RPOPLPUSH source destination:移除列表尾部元素,并将该元素添加到另一个列表并返回
    • RPUSH key value1 [value2]:将一个或多个值插入到列表尾部
    • RPUSHX key value:为已存在的列表添加值
  • set 相关

    • 见参考链接,用到时补充
  • zset 相关

    • 见参考链接,用到时补充
  • 发布订阅

    • 开启一个客户端作为发布者,开启另一个作为订阅者
    • SUBSCRIBE [通道名…]:订阅一个或多个通道
    • UNSUBSCRIBE [通道名…]:退订指定通道
    • PUNSUBSCRIBE [pattern [pattern …]]:退订一个或多个通道名符合格式的通道
    • PSUBSCRIBE pattern [pattern …]:订阅一个或多个通道名符合格式的通道
    • PUBLISH 通道名 message:向通道发布消息
    • PUBSUB CHANNELS:查看通道
  • 事务

    • redis 的事务不保证原子性,可以理解为命令的批量提交,某处命令的执行失败不会让前面执行过的命令回滚,也不会影响后面命令的执行。
    • 在事务中的所有命令都将会被串行化的顺序执行,事务执行期间,redis 不会再为其它客户端的请求提供任何服务,从而保证了事物中的所有命令被原子的执行。
    • 通过 MULTI 命令开启事务,输入要执行的一系列命令语句后,通过 EXEC 命令执行事务。
    • DISCARD:放弃执行事务块中的所有语句
    • WATCH key [key …]:监视一个或多个 key,若在事务执行前(EXEC)该 key 被其他命令所改动,则事务将被打断
    • UNWATCH:取消 WATCH 命令对所有 key 的监视
  • 管理

    • CLIENT LIST:获取连接到服务的客户端列表
    • CLIENT KILL [ip:port] [ID client-id]:关闭对应客户端连接
    • CLIENT SETNAME name:设置当前客户端名字
    • CLIENT GETNAME:获取当前客户端名字
    • INFO:获取服务器各种信息和统计数值
    • CONFIG RESETSTAT:重置 INFO 命令中的某些统计数据
    • DBSIZE:查询当前数据库 key 数量
    • FLUSHALL:删除所有数据库的所有 key
    • FLUSHDB:删除当前数据库的所有 key
    • MONITOR:该客户端将实时打印 Redis 服务器接收到的所有命令(调试用)
    • ROLE:返回主从示例所属角色
    • SHUTDOWN [NOSAVE] [SAVE]:关闭服务(可同时异步保存数据到硬盘)
    • SLAVEOF host port:将当前服务器转变为指定服务器的从属服务器
  • 性能测试

    cmd 窗口切换到 redis 目录下,运行 redis-benchmark [option] [option value] 命令进行性能测试,如:redis-benchmark -n 10000 同时执行10000个请求。可选参数如下:
参数含义值示例参数含义值示例
-h指定服务器主机名127.0.0.1-p指定服务器端口6379
-s指定服务器socket-c指定并发连接数50
-n指定请求数10000-d以字节的形式指定 SET/GET 值的数据大小2
-k1=keep alive 0=reconnect1-rSET/GET/INCR 使用随机 key, SADD 使用随机值
–csv以 CSV 格式输出-l生成循环,永久执行测试

4、持久化

  数据持久化、故障及恢复的一些基本概念见附录

  • RDB (Redis DataBase)持久化

  因为 Redis 是内存数据库,它将自己的数据库状态储存在内存里,所以如果不想办法将储存在内存中的数据库状态保存到磁盘里,那么一旦服务器进程退出,服务器中的数据库状态也会消失不见。为了解决这个问题,Redis提供了 RDB 持久化功能,可以将 Redis 在内存中的数据库状态保存到磁盘中,RDB 持久化可以通过 SAVE 命令手动执行,也可以根据服务器配置选项定期执行(见前文中主要配置说明)。RDB 持久化生成的 RDB 文件是一个经过压缩的二进制文件,通过该文件可以还原生成 RDB 文件时的数据库状态。
  通过 SAVE 命令创建当前数据库的备份(会在目录中生成一个 dump.rdb 文件);恢复数据时,只需要将备份文件 (dump.rdb) 移动到相应目录并启动服务即可(Redis 没有专门用于载入RDB 文件的命令,载入工作只在服务启动时自动执行);也可以使用命令 BGSAVE ,该命令会在后台创建子进程执行备份,可通过 LASTSAVE 命令查看最近一次成功将数据保存到磁盘上的时间判断是否执行成功;SAVE 是阻塞式的 RDB 持久化,当命令正在执行时,客户端发送的所有命令请求都会被拒绝。BGSAVE 是非阻塞式的,不妨碍主进程,但是会大幅度增大内存使用率。
  自动定期执行 RDB 持久化:根据配置在多长时间内执行过多少次更新操作就自动执行 BGSAVE 命令,Redis 内部有一个 dirty 计数器,它负责记录距离上一次成功执行 SAVE 命令或者 BGSAVE 命令后,服务器对数据库状态进行了多少次修改,达到配置要求后自动触发 BGSAVE。
  Redis 每次进行 RDB 时,RDB 文件不会坏掉,因为每次 RDB 持久化都是先将数据写到一个临时文件中,这个过程完成后再通过原子性 rename 系统调用将临时文件重命名为 RDB 文件,这样在任何时候出现故障,Redis 的旧的 RDB 文件都总是可用的。
  BGSAVE 命令执行期间,客户端发送的 SAVE 及 BGSAVE 命令会被服务器拒绝,客户端发送的 BGREWRITEAOF 命令会被延迟到 BGSAVE 命令执行完毕之后执行(放置调用方法产生竞争);如果 BGREWRITEAOF 命令正在执行,那么客户端发送的 BGSAVE 命令会被服务器拒绝(CPU性能方面考虑)。

  • AOF(Append Only File)持久化

  与 RDB 保存整个数据库状态不同,AOF 是通过保存所有 Redis 执行的写命令来达到数据持久化的目的。可通过 appendonly 配置控制是否开启 AOF 功能,通过 appendfsync 配置控制文件同步频率。
  AOF 持久化包括三个步骤:

  1. 命令追加:开启 AOF 功能后,Redis 会在执行每一次写命令后,将命令追加到AOF对应缓冲区末尾;
  2. 文件写入:服务将缓冲区的数据写入 AOF 文件;
  3. 文件同步:步骤二貌似完成了全部流程,但其实操作系统为了提高效率,都会有一个同步策略控制文件何时从内存真正同步到磁盘中,appendfsync 配置正是用来控制同步频率的。

  AOF文件重写:

  • 随着 Redis 的不断运行,AOF 文件也会变得越来越大,占用太大磁盘的同时,过大的 AOF 文件也会导致还原操作时间变长。Redis 提供了文件重写的功能,通过该功能可得到一个更为精简的文件,重写可通过配置自动触发或者手动 BGREWRITEAOF 命令进行重写。
  • 自动触发条件:当前 AOF 文件大小与上次重写后 AOF 文件大小的百分比超过 auto-aof-rewrite-percentage 设置的值,同时当前 AOF 文件大小超过 auto-aof-rewrite-min-size 设置的最小值,就会触发重写。
  • 整个重写操作是绝对安全的,Redis 在创建新 AOF 文件的过程中,会继续将命令追加到原 AOF 文件中,即使重写过程中发生停机,原 AOF 文件也不会丢失。 而一旦新 AOF 文件创建完毕,Redis 就会从旧 AOF 文件切换到新 AOF 文件,并开始对新 AOF 文件进行追加操作;
  • 需要注意到是:重写操作并没有去读取旧的 AOF 文件,而是将整个内存中的数据库内容用命令的方式重写了一个新的 AOF 文件,这点和 RDB 有点类似。

  AOF 与 RDB 优缺点比较:

  • 每次进行 RDB 持久化时会占用较大内存,而 AOF 只是在第一次创建文件时内存使用率较大,之后每次写命令同步占用内存都很小;
  • AOF 文件的格式可读性较强,在误操作后可对 AOF 文件进行一定的手动修改;
  • 若AOF文件有损坏(比如由于某些原因包含了不完整的命令),可以使用 redis-check-aof --fix 文件名 命令工具进行修复;
  • RDB 文件通常比 AOF 文件小;
  • RDB 使用快照的形式来持久化整个 Redis 数据,而 AOF 只是将每次执行的命令追加到 AOF 文件中,因此从理论上说,RDB 比 AOF 方式更健壮;
  • RDB 在恢复大数据集时的速度比 AOF 的恢复速度要快,原因有两个:一是 RDB 文件中每一条数据只有一条记录,不会像 AOF 日志那样可能有一条数据的多次操作记录。另一个原因是 RDB 文件的存储格式和 Redis 数据在内存中的编码格式是一致的,不需要再进行数据编码工作,在CPU消耗上要远小于 AOF 日志的加载;
  • 由于 RDB 的频率比 AOF 要慢,所以一旦数据库出现问题,那么我们的 RDB 文件中保存的数据并不是最新的,从上次 RDB 文件生成到 Redis 停机这段时间的数据全部丢掉,对于某些业务场景这是不能容忍的。

  另外,若服务器开启了 AOF 功能,那么服务器会优先使用 AOF 文件而不是 RDB 来还原数据库状态。

5、redis 的 java 客户端

  java 可使用的 redis 客户端有三个:Jedis、Redisson、Lettuce,各自特点:

  • Jedis:比较全面的 redis 命令支持,使用阻塞的 I/O,方法调用都是同步的,程序流需要等到 sockets 处理完 I/O 才能执行,不支持异步。Jedis 客户端实例不是线程安全的,所以一般需要通过连接池来使用 Jedis。
  • Redisson:提供很多分布式相关操作服务,基于 Netty 框架的事件驱动的通信层,其方法调用是异步的。线程安全,所以可以操作单个 Redisson 连接来完成各种操作。不支持字符串操作、排序、事务、管道、分区等 Redis 特性。Redisson 的宗旨是促进使用者对 Redis 的关注分离,从而让使用者能够将精力更集中地放在处理业务逻辑上。
  • Lettuce:高级 Redis 客户端,用于线程安全同步、异步和响应使用,支持集群、Sentinel、管道和编码器,主要在一些分布式缓存框架上使用比较多。

a、jedis 客户端的使用示例

  需要 jedis.jar 包
  简单使用:

//连接本地的 Redis 服务
Jedis jedis = new Jedis("localhost");
System.out.println("服务正在运行: "+jedis.ping());

  完整应用示例:

import java.util.List;
import java.util.Set;
import org.apache.commons.lang.StringUtils;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

public final class RedisUtil {
    
    //Redis服务器IP
    private static String ADDR = "127.0.0.1";
    
    //Redis的端口号
    private static int PORT = 6379;
    
    //访问密码
    private static String AUTH = "foobared";
    
    //可用连接实例的最大数目
    //若值为-1,则表示不限制;若pool已经分配了maxActive个jedis实例,则此时pool的状态为exhausted(耗尽)
    private static int MAX_ACTIVE = 80;
    
    //控制一个pool最多有多少个状态为idle(空闲的)的jedis实例。
    private static int MAX_IDLE = 10;
    
    //等待可用连接的最大时间,单位毫秒,默认-1,表示永不超时。若超过等待时间,则直接抛出JedisConnectionException
    private static int MAX_WAIT = 10000;
    
    private static int TIMEOUT = 10000;
    
    //在borrow一个jedis实例时,是否提前进行validate操作;如果为true,则得到的jedis实例均是可用的
    private static boolean TEST_ON_BORROW = true;
    
    private static JedisPool jedisPool = null;
    
    /**
     * 初始化Redis连接池
     */
    static {
        try {
            JedisPoolConfig config = new JedisPoolConfig();
            config.setMaxActive(MAX_ACTIVE);
            config.setMaxIdle(MAX_IDLE);
            config.setMaxWait(MAX_WAIT);
            config.setTestOnBorrow(TEST_ON_BORROW);
            Jedis resource = null ;
            while(resource == null){
            	try{
            		jedisPool = new JedisPool(config, ADDR, PORT, TIMEOUT, AUTH);
            		resource = jedisPool.getResource();
            	}catch(Exception e){
            		//错误处理
            	}
            }
        } catch (Exception e) {
        	//错误处理
        }
    }
    
    /**
     * 获取Jedis实例
     */
    public synchronized static Jedis getJedis() {
        try {
            if (jedisPool != null) {
                Jedis resource = null;
                while(resource == null){
                	try{
                		resource = jedisPool.getResource();
                	}catch(Exception e){
                		//错误处理
                	}
                }
                return resource;
            } else {
                return null;
            }
        } catch (Exception e) {
        	//错误处理
            return null;
        }
    }
    
    /**
     * 释放jedis资源
     */
    public static void returnResource(final Jedis jedis) {
        if (jedis != null) {
            jedisPool.returnResource(jedis);
        }
    }
    
    /**
     * 判断key是否存在
     */
    public static boolean existKey(String key){
    	Jedis jedis = null;
    	boolean flag = false;
    	try {
    		jedis = getJedis();
    		flag = jedis.exists(key);
		} catch (Exception e) {
			//错误处理
		} finally{
			if(jedis != null){
		    	returnResource(jedis);
			}
		}
    	return flag;
    }
    
    /**
     * 获取值-String(带默认值)
     */
    public static String getValue(String key, String defaultValue){
    	Jedis jedis = null;
    	String value = "";
    	try {
    		jedis = getJedis();
			value = StringUtils.defaultIfEmpty(jedis.get(key), defaultValue);
    	} catch (Exception e) {
    		//错误处理
		} finally{
			if(jedis != null){
		    	returnResource(jedis);
			}
		}
    	return value;
    }
    
    /**
     * 获取值-String
     */
    public static String getValue(String key){
    	Jedis jedis = null;
    	String value = "";
    	try {
    		jedis = getJedis();
			value = jedis.get(key);
		} catch (Exception e) {
			//错误处理
		} finally{
			if(jedis != null){
		    	returnResource(jedis);
			}
		}
    	return value;
    }
    
    /**
     * 设置值-String
     */
    public static void setValue(String key, String value){
    	Jedis jedis = null;
    	try {
    		jedis = getJedis();
			jedis.set(key, value);
		} catch (Exception e) {
			//错误处理
		} finally{
			if(jedis != null){
		    	returnResource(jedis);
			}
		}
    }
    
    /**
     * 设置值-String(带过期时间,对应SETEX命令)
     */
    public static void setValue(String key , String value , int sec){
    	Jedis jedis = null;
    	try {
    		jedis = getJedis();
			jedis.setex(key, sec, value);
		} catch (Exception e) {
			//错误处理
		} finally{
			if(jedis != null){
		    	returnResource(jedis);
			}
		}
    }
    
    /**
     * 设置值-list
     */
    public static void setValue(String key, List<String> list){
    	Jedis jedis = null;
    	try {
    		jedis = getJedis();
    		if(list!=null){
    			for(String s : list){
    				jedis.lpush(key, s);
    			}
    		}
		} catch (Exception e) {
			//错误处理
		} finally{
			if(jedis != null){
		    	returnResource(jedis);
			}
		}
    }
    
    /**
     * 获取值-list
     */
    public static List<String> getValue(String key,long start, long end){
    	Jedis jedis = null;
    	try {
    		jedis = getJedis();
    		List<String> list = jedis.lrange(key, start, end);
    		return list;
		} catch (Exception e) {
			//错误处理
			return null;
		} finally{
			if(jedis != null){
		    	returnResource(jedis);
			}
		}
    }
    
    /**
     * 获取所有key
     */
    public static Set<String> getKeys(){
    	Jedis jedis = null;
    	try {
    		jedis = getJedis();
    		Set<String> set = jedis.keys("*");
    		return set;
		} catch (Exception e) {
			//错误处理
			return null;
		} finally{
			if(jedis != null){
		    	returnResource(jedis);
			}
		}
    }
}

b、springboot2.X 整合 redis

  maven依赖需要引入:

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

  application.properties中配置如下信息:

# REDIS (RedisProperties)
# Redis数据库索引(默认为0)
spring.redis.database=0  
# Redis服务器地址
spring.redis.host=127.0.0.1
# Redis服务器连接端口
spring.redis.port=6379  
# Redis服务器连接密码(默认为空)
spring.redis.password=
# 连接池最大连接数(使用负值表示没有限制)
spring.redis.jedis.pool.max-active=8  
# 连接池最大阻塞等待时间(使用负值表示没有限制)
spring.redis.jedis.pool.max-wait=-1
# 连接池中的最大空闲连接
spring.redis.jedis.pool.max-idle=8  
# 连接池中的最小空闲连接
spring.redis.jedis.pool.min-idle=0  
# 连接超时时间(毫秒)
spring.redis.timeout=10000

  编写 config 文件:

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.time.Duration;

@Configuration
@EnableCaching
// 继承CachingConfigurerSupport,为了自定义生成KEY的策略
public class RedisConfig extends CachingConfigurerSupport {

    @Bean(name="redisTemplate")
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(redisConnectionFactory);

        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(objectMapper);

        // value值的序列化采用fastJsonRedisSerializer
        template.setValueSerializer(jackson2JsonRedisSerializer);
        template.setHashValueSerializer(jackson2JsonRedisSerializer);
        // key的序列化采用StringRedisSerializer
        template.setKeySerializer(new StringRedisSerializer());
        template.setHashKeySerializer(new StringRedisSerializer());
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }

    @Bean
    public CacheManager cacheManager(RedisConnectionFactory factory) {
        RedisCacheConfiguration config =  RedisCacheConfiguration.defaultCacheConfig();
        //设置超时时间30秒
        config.entryTtl(Duration.ofSeconds(30));
        RedisCacheManager cacheManager =  RedisCacheManager.builder(factory).cacheDefaults(config).build();
        return cacheManager;
    }
}

  编写 service 文件:

import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;

@Service
public class RedisService {
    @Resource
    private RedisTemplate<String, Object> redisTemplate;

    public void set(String key, Object value) {
        ValueOperations<String, Object> vo =  redisTemplate.opsForValue();
        vo.set(key, value);
    }

    public Object get(String key) {
        ValueOperations<String, Object> vo =  redisTemplate.opsForValue();
        return vo.get(key);
    }
}

7、主从复制和哨兵机制

  一台 master 主机可以拥有多台 slave 从机,而一台 slave 从机也可以拥有多个自己的 slave 从机。如此下去,形成强大的多级服务器集群架构。主从复制的作用:读写分离,容灾恢复。主从复制的原则:在刚建立主从关系后,进行一次全量复制,之后主机数据修改时,从机进行对应的增量复制。主从复制的缺点:复制过程有延迟。
在这里插入图片描述

  • 开启三个 Redis 服务 A(6379端口)、B(6479端口)、C(6579端口),在各自对应的客户端中通过 info replication 命令查看状态信息,可以看到,三个新开启的服务全部为主机,连接从机数量为零;

role:master
connected_slaves:0
……

  • 在 A 中添加一下数据,在 B、C 中执行命令 SLAVEOF [A的IP] [A的端口],将他们设置为 A 的从机,此时,A 的从机连接数量及 B、C 的角色发生改变,并且 B、C 可以获取 A 中的数据(包括设置为从机之前的历史数据及设置为从机后的新数据);
  • 在 B、C 中尝试写入数据,发现失败,是因为读写分离的机制;
  • 主机或者从机宕机后,再启动恢复后,仍然可以保持原先的主从状态;
  • 将 C 设置为 B 的从机,查看 B 的信息,发现 B 的角色仍然为 slave,因为 B 还是 A 的从机;
  • 可通过 SLAVEOF no one 命令断开主从关系。

  哨兵监控:在一个集群中,若主机宕机,如何让其中一台从机自动转为主机?这里就要用到哨兵机制。哨兵有三个任务:1、监控:定期检查各个主从机是否正常;2、提醒:发现问题时,通过API向管理员或其他应用程序发送通知;3、故障迁移:若一台主机出现问题时,哨兵会自动将该主机下的某一个从机设置为新的主机,并让其他从机和新主机建立主从关系。设置哨兵的大体步骤如下:

  1. 在 redis 目录下创建 sentinel.conf 文件,文件配置如下:

#当前 Sentinel 服务运行的端口
port 26379
#保护模式 no
protected-mode no
  
#Sentinel 去监视一个名为 mymaster 的主 redis 实例,这个主实例的 IP 地址为本机地址127.0.0.1,端口号为6379
#而将这个主实例判断为失效至少需要1个 Sentinel 进程的同意,只要同意的数量不达标,自动 failover 就不会执行
#即配置格式为:sentinel monitor [为监控的主机指定一个命名] [主机 IP] [主机端口] [票数]
sentinel monitor mymaster 127.0.0.1 6379 1
  
#指定 Sentinel 认为 Redis 实例已经失效所需的毫秒数
#当实例超过该时间没有返回 PING ,或者直接返回错误,那么 Sentinel 将这个实例标记为主观下线
#只有一个 Sentinel 进程将实例标记为主观下线并不一定会引起实例的自动故障迁移,只有在足够数量的 Sentinel 都将一个实例标记为主观下线之后,实例才会被标记为客观下线(前面配置的得票数)
sentinel down-after-milliseconds mymaster 5000
  
#指定在执行故障转移时,可以同时最多有多少个 slave 进行示例同步
#在 slave 较多的情况下这个数字越小,同步的时间越长,完成故障转移所需的时间就越长
#但是如果这个数字越大,就意味着越多的 slave 因为 replication 而处于不可用状态
sentinel parallel-syncs mymaster 1
#指定多长时间内未能完成 failover 操作,则认为该 failover 失败
sentinel failover-timeout mymaster 15000

  sentinel monitor 配置中票数行尾代表什么意思呢?我们知道,网络是不可靠的,有时候一个 sentinel 会因为网络堵塞而误以为一个 master redis 已经死掉了,所以实际应用中都采用 sentinel 集群式,需要多个 sentinel 互相沟通投票来确认某个 master 是否真的死了。

  1. 通过 redis-server sentinel.conf --sentinel 命令启动哨兵监听服务,并通过 redis-cli -h 127.0.0.1 -p 26379 启动一个对应的客户端,通过 info sentinel 查看监听状态信息;
  2. 在6379 redis 服务对应的客户端中用 shutdown 命令关闭该服务,发现6479从服务自动切换为主服务,6579的关联
    主服务也切换为6479,同时查看监听服务的监听信息发生改变。

8、redis 集群和分区

  redis 在3.0版本后支持集群,redis 集群可以让多个 redis 节点之间进行数据共享,总数据会被按照一定方式分割到各个 redis 实例中,即每个 redis 示例仅保存全部 key 的一个子集。
  集群分区的优点:利用多台计算机可以构造出更大的数据库;扩展了计算能力。
  集群分区的缺点:涉及多个key的操作通常不支持,比如若两个 set 保存到不同的 redis 实例上时,无法对这两个 set 执行交集操作;涉及多个 key 的事务不能使用;数据处理较为复杂,比如你需要处理多个 rdb/aof 文件,并且从多个实例和主机备份持久化文件。
  数据分区方法:范围分区、哈希分区。

  • 范围分区:映射一定范围的对象到特定的 redis 实例中。如要保存一批 user 数据到 redis 集群中,可以让 id 从0-10000的保存到 A redis 实例中,id 为10000-20000的数据保存到 B redis 实例中。不足之处是需要有一个相关的映射表。
  • 哈希分区:通过一个hash函数将 key 转换为一个数字,假设有四台服务,再讲这个数字转化为0-3之间的数字,这样数据就会被保存到对应实例中。

  相关知识:redis 集群没有使用一致性 hash,而是引入了哈希槽的概念,redis 集群共有16384个哈希槽,每个 key 通过 CRC16 校验后对 16384 取模来决定放置哪个槽,集群的每个节点负责一部分hash槽。
  关于集群分区的更多概念和搭建方法在以后实际使用到时再做补充。


附:数据持久化、故障、恢复

  数据持久化:简单说就是将数据放到断电后不会丢失的设备中(即硬盘)。
  首先关注一下一般数据库进行写操作过程中的具体步骤:

  1. 客户端向服务端发送写操作请求——数据在客户端内存中;
  2. 服务端接收到写请求的数据——数据在服务端内存中;
  3. 服务端调用 write 将数据写出——数据在系统内存的缓存区中;
  4. 操作系统将缓冲区的数据转移到磁盘控制器上——数据在磁盘缓存区中;
  5. 磁盘控制器将数据写到磁盘的物理介质中——数据在实际的物理磁盘中。

  当数据库系统故障时——这是系统内核还是完好的,只要执行到第3步,数据就是安全的。
  当系统断电是——上面几步中所有缓存区中数据失效。
  我们的关注点:1、数据库多久进行一次 write 将数据写到系统化内存缓冲区中?2、系统内存多久将缓冲区中的数据写到磁盘控制器中?3、磁盘控制器多久将数据实际写到物理介质中?
  对于问题1:数据库自己进行控制,不同数据库内部实现不同;
  对于问题2:操作系统都有默认策略,同时数据库可以调用操作系统的相关接口强制控制写入的频率;
  对于问题3:多数情况下磁盘缓存是被设置关闭的,或者只开启读缓存,这个问题一般不用关注。
  
  数据恢复:通过关注以上问题,确保将数据写到磁盘上,但并不意味着磁盘上的数据不会损坏,一般有几种策略放置数据损坏后无法恢复:

  • 配置数据库进行数据同步备份,数据损坏后利用备份还原数据;
  • 增加操作日志,记录改变数据库的操作,由于操作日志是顺序追加的,所以数据损坏后可通过日志恢复数据;
  • 在数据库实现原理上,不进行旧数据的修改,只是以追加的方式完成写操作,这样,数据库中数据本身就是一份日志。
      

参考链接

http://www.runoob.com/redis/redis-tutorial.html
https://www.cnblogs.com/xingzc/p/5988080.html
https://www.cnblogs.com/itdragon/p/7932178.html
https://www.cnblogs.com/xingzc/category/865010.html
https://blog.csdn.net/weixin_39723544/article/details/80743074

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值