四十四、NoSQL、Redis

发布于:2025-07-09 ⋅ 阅读:(10) ⋅ 点赞:(0)

NoSQL

随着大数据时代的到来,越来越多的网站、应用系统需要支撑海量数据存储、高并发请求、高可用、高可扩展性等特性要求,传统的关系型数据库在应付这些调整已经显得力不从心,暴露了许多难以克服的问题。由此,各种各样的NoSQL(Not Only SQL)数据库作为传统关系型数据的一个有力补充得到迅猛发展。在这里插入图片描述

NoSQL(Not only SQL)数据库,可以理解为区别于关系型数据库如mysql、oracle等的非关系型数据库。

应用场景

NoSQL 作为分布式系统的实现,海量数据永久性存储、非结构化数据存储、超大规模数据高效读写、超强水平扩展能力等这些特征让 NoSQL 得到了广泛应用。

不足:事务支持、关联特性,甚至于 SQL 查询,这些却是 NoSQL 的短板,也决定了 NoSQL 尚且取代不了关系型数据库。

NoSQL 分类

通常情况下,按功能特征分为键值型、面向列族存储、文档型以及图数据库

类型 特点 应用 案例
键值型 简单数据存储形式,通过键来访问值 图像存储 Redis、MemcacheDB、Berkeley DB
可以通过key快速查询到其value 基于键的文件系统
一般来说,存储不管value的格式,照单全收 设计为可扩展系统
列族 稀疏矩阵存储形式,通过行列作为键 网络爬虫结果存储 Hbase、Cassandra、Accumulo
方便存储结构化和半结构化数据 大数据交互式查询
方便数据压缩提供数据查询IO优势 软一致性
文档型 讲层次化的数据结构存储形式 文档搜索 MongoDB、CouchDB、Couchbase
文档存储一般用类似json的格式存储 互联网内容管理
对某些字段建立索引以实现关系型数据库的某些功能 高度变化的数据
图存储 适用于关联性要求高的问题 社交网络 Neo4j、FlockDB、InfiniteGraph
图形关系的最佳存储 欺诈侦测
使用传统关系数据库来解决的话性能低下,而且设计使用不方便 强关联的数据

常见NoSQL

  • Redis:基于内存、支持持久化的键值型数据库
  • HBase:面向列、高效随机读写的 NoSQL
  • MongoDB:查询高效、支持多索引的文档型数据库。

特点:

Redis:

  • 高性能
  • 纯内存访问(非数据同步无需读取磁盘)
  • 单线程
  • 非阻塞多路IO复用

HBase:

  • HBase是一个开源的非关系型分布式数据库,它参考了谷歌的BigTable建模,实现的编程语言为Java。
  • 它是Apache软件基金会的Hadoop项目的一部分,运行于HDFS文件系统之上,为 Hadoop 提供类似于BigTable 规模的服务。因此,它可以容错地存储海量稀疏的数据。
  • HBase是一个高可靠、高性能、面向列、可伸缩的分布式数据库,是谷歌BigTable的开源实现,主要用来存储非结构化和半结构化的松散数据。
  • HBase的目标是处理非常庞大的表,可以通过水平扩展的方式,利用廉价计算机集群处理由超过10亿行数据和数百万列元素组成的数据表。
  • 主要应用在海量数据存储、超大规模随机读写访问的场景。
  • 特点:
    • 随机读写访问
    • 分布式、面向列
    • 强一致性
    • 底层数据存储在 HDFS 之上

MongoDB:

  • MongoDB 是一个分布式、面向文档的 NoSQL 数据库,用于大容量数据存储,提供统一的数据格式(bson),支持不同类型的索引。适用于存放对象或Json格式数据、追求高性能的业务场景。
  • 主要特点:
    • 面向文档,非常灵活
    • 支持各种类型的索引
    • 复制和故障切换,实现高可用性
    • 自动分片,易于扩展

RDBMS

  • 高度组织化结构化数据
  • 结构化查询语言(SQL)
  • 数据和关系都存储在单独的表中。
  • 数据操纵语言,数据定义语言
  • 严格的一致性
  • 基础事务
  • ACID

NoSQL

  • 代表着不仅仅是SQL
  • 没有声明性查询语言
  • 没有预定义的模式
  • 键 - 值对存储,列存储,文档存储,图形数据库
  • 最终一致性,而非ACID属性
  • 非结构化和不可预知的数据
  • CAP定理
  • 高性能,高可用性和可伸缩性

分布式数据库中的 CAP 原理

即 Consistency Available and Partition tolerance,即一致性(C)、可用性(A)、分区容错性(P)

任何分布式系统只能同时满足其中两点,无法做到三者兼顾

CAP定理:

  • Consistency(强一致性), 数据一致更新,所有数据变动都是同步的
  • Availability(高可用性), 好的响应性能
  • Partition tolerance(分区容错性) 可靠性

注: 系统中任意信息的丢失或失败不会影响系统的继续运作。

CAP理论的核心是:一个分布式系统不可能同时很好的满足一致性,可用性和分区容错性这三个需求

因此,根据 CAP 原理将 NoSQL 数据库分成了满足 CA 原则、满足 CP 原则和满足 AP 原则三 大类:

  • CA - 单点集群,满足一致性,可用性的系统,通常在可扩展性上不太强大。
  • CP - 满足一致性,分区容忍性的系统,通常性能不是特别高。
  • AP - 满足可用性,分区容忍性的系统,通常可能对一致性要求低一些。

CAP理论就是说在分布式存储系统中,最多只能实现上面的两点。

而由于当前的网络硬件肯定会出现延迟丢包等问题,所以分区容忍性是我们必须需要实现的。

所以我们只能在一致性和可用性之间进行权衡,没有NoSQL系统能同时保证这三点。

举例:

  • CA:传统Oracle数据库
  • AP:大多数网站架构的选择
  • CP:Redis、Mongodb

注意:分布式架构的时候必须做出取舍。

一致性和可用性之间取一个平衡。多余大多数web应用,其实并不需要强一致性。因此牺牲C换取P,这是目前分布式数据库产品的方向。

Redis

Redis:REmote DIctionary Server(远程字典服务器)是完全开源免费的,用C语言编写的,遵守BSD协议,是一个高性能的(key/value)分布式内存数据库,基于内存运行并支持持久化的NoSQL数据库,是当前最热门的NoSql数据库之一,也被人们称为数据结构服务器

Redis是用C语言开发的一个开源的高性能键值对(key-value)数据库,官方提供测试数据,50个并发执行100000个请求,读的速度是110000次/s,写的速度是81000次/s ,且Redis通过提供多种键值数据类型来适应不同场景下的存储需求。

下载:https://redis.io/
中文:http://www.redis.cn/

redis 支持的键值对

  1. 字符串类型
  2. string 哈希类型
  3. hash 列表类型
  4. list 集合类型 set
  5. 有序集合类型 sortedset

redis 应用场景

  1. 缓存(数据查询、短连接、新闻内容、商品内容等等)
  2. 聊天室的在线好友列表
  3. 任务队列。(秒杀、抢购、12306等等)
  4. 应用排行榜
  5. 网站访问统计
  6. 数据过期处理(可以精确到毫秒
  7. 分布式集群架构中的session分离

安装

redis是C语言开发,安装redis需要先将官网下载的源码进行编译,编译依赖gcc环境。如果没有gcc环境,需要安装gcc

  1. 安装gcc
yum install gcc-c++
  1. 将Windows下下载的压缩文件redis-5.0.7.tar.gz上传到Linux下
    在这里插入图片描述
  2. 解压文件
tar -zxvf /opt/softes/redis-5.0.5.tar.gz  -C redis/
  1. 进入解压后的目录进行编译(.c 文件编译为 .o文件)
# 进入解压后的目录
cd /opt/softes/redis-5.0.5

#进行编译
make install

在这里插入图片描述

  1. 安装完成后,在 /usr/local/redis/bin 中的文件:
    在这里插入图片描述

  2. copy 文件
    redis启动需要一个配置文件,可以修改端口号等信息。
    将加压目录下的redis.conf 拷贝到/usr/local下

  3. 启动(前端启动)

# 进入指定目录
cd /usr/local/bin
# 启动客户端
./redis.cli

在这里插入图片描述

启动redis,客户端连接: 连接6379端口

redis-cli -h ip地址 -p 端口

./redis.cli -h 127.0.0.1 -p 6379
# 因为使用本地ip 故也可省略
./redis.cli -p 6379

在这里插入图片描述
退出客户端:

quit
  1. redis 的关闭
  • 查询到PID,kill -9 pid 【断电,非正常关闭,一般不用,否则造成数据丢失】
ps -ef | grep redis
  • 正常关闭,数据保存(shutdown)
# 停止 redis 服务
./redis-cli shutdown
  1. redis 启动(后端模式)
  • 修改 redis.conf 配置文件,将 daemonize 设置为 yes 则以后端模式启动
vim /usr/local/redis.conf
# 编辑,将daemonize 后的值改为 yes

在这里插入图片描述

[root@localhost local]# ./bin/redis-server redis.conf 

在这里插入图片描述

在这里插入图片描述

Redis 命令

字符串

命令 说明
set 写:set [key] [value]
get 读:get [key]
mset 同时写入一个或多个字符串值:mset [key1] [value1] [key2] [value2]
dbsize 字符串大小
incr 自增:incr [key] 自增1 (只能对数值型数据执行)
decr 自减:decr [key] 自减1
incrby 增加指定值:incrby [key] number 对 key 增加 number 值
decrby 增加指定值:decrby [key] number 对 key 减少 number 值
incrbyfloat 自增任意浮点数:incrbyfloat [key] [float]
apppend 字符串追加(追加在后面,追加append_string到key):apppend [key] [append_string] 返回 key 的字符数
getrange 获取子字符串(获取key 中从start到end):getrange [key] [start] [end]
setrange 替换字符串(将key中从 start 开始替换 value):setrange [key] [start] [value]

在这里插入图片描述

二进制位命令

任何数据在操作系统中都是以二进制位形式存储的,字符串类型中redis提供了对其进行二进制位操作。通常情况下运用可能不多,但可以通过它实现一些“巧妙”的设计。

例如,在钉钉消息中,我们发送一条消息会显示“已读”和“未读”的人,我们需要将这两个信息存储在redis中,应该怎么设计?

我们设计一条消息的key值结构为“[user_id]:[msg_id]”,所以key=“1:100”就表示“用户ID为1发送的消息ID为100”。注意,此时如果用户ID=2的人读了这条消息,就通过命令setbit 1:100 2 1写入,如果用户ID=100的人读了这条消息,就通过setbit 1:100 10 1。这条命令的含义表示对key=1:100的二进制第2位写入1,对key=1:100的二进制第10位写入1,1表示已读,0则表示未读。

在这里插入图片描述

最后还有一个关于二进制位的命令bittop [operation] [result] [key1] [key2],可以对多个key值的二进制位进行二进制运算,包括并AND、或OR、异或XOR、非NOT,计算结果保存在[result]中。

列表

列表就是一个键,对应多个值

LPUSH 键名 值1 值2 值3 值4 ···

推入/弹出常见命令
命令 说明
LPUSH 新建列表 LPUSH 键名 值1 值2 值3···
RPUSH 将value值添加到列表的右端 RPUSH key 值1 值2 值3···
LPUSH 将value值添加到列表的左端 LPUSHkey 值1 值2 值3···
RPOP 移除列表最右端的元素,并返回该元素 RPOP key
LPOP 移除列表最左边的元素,并返回该元素 LPOP key
LTANGE 返回范围内的所有元素,闭合区间 LTANGE key start end
LINDEX 返回指定位置(index)的元素 LINDEX key index
LTRIM 保留范围内的所有元素,闭合区间。 注意上述两个命令不会修改列表,但LTRIN会修改列表 LTRIM key start end

在这里插入图片描述

哈希(hash)

hash 可以理解为 键是 String,值是 map

命令 说明
hmset 写入hash类型的值 hmset key 键1 值1 键2 值2 ···
hlen 返回 hash 包含的键值对数量 hlen key
hgetall 返回 hash 包含的所有键值对 hgetall key
hkeys 获取 hash 包含的所有键 hkeys key
hvals 获取 hash 包含的所有键对应的值 hvals key
hincrby 给 hash 中指定的键自增任意整数(与字符串类型的 incrby 类似) hincrby ket 键 number
hdel 删除 hash 中指定的键 hdel key 键
hexists 返回给定的键是否在 hash 中,0表示不存在,1表示存在 hexists key 键
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

集合(set)

集合(set)是以无序方式存储各不相同元素的数据类型,与Java中的 set 类型相似。具有新增、删除、读取等基本操作,还有两个集合之间运算的操作

读/写等常用命令
命令 说明
sadd 将一个或多个元素添加到集合中,返回添加的元素个数 sadd s1 hello java
scard 返回 set 中元素的个数 scard s1
smembers 返回 set 中的所有元素 smembers s1
sismember 判断集合中是否包含某一个成员 sismember s1 hello
smove 将集合s1中 hello 移动到s2,若s2不存在,会新建 smove s1 s2 hello
sdiff s1 - s1与s2的交集 sdiff s1 s2
sunion s1 与 s2 的并集 sunion s1 s2

在这里插入图片描述

有序(sorted set)

有序集合会给每一个元素指定一个分数,有序也是根据元素后边的分数来进行排序的

命令 说明
zadd 新增有序集合 zadd z1 90 java 80 html 70 css
zrange 返回集合中的所有元素
zrank 返回成员的排名,从小到大 zrank z1 java
zrevrank 返回成员的排名,从大到小 zrevrank z1 java
zscore 返回成员的 number 分数 zscore z1 css
zcount 返回分数范围内的成员数量 zcount [key] [min_score] [max_score]
zincrby 给member成员的分数加上incrment zincrby [key] [incrment] [member]
zrem 删除有序集合中指定的成员 zrem [key] [member1] [member2]…
在这里插入图片描述

Stream

主要用于消息队列
请添加图片描述
在这里插入图片描述
Redis Stream 有一个消息链表,将所有的消息都放在链中,每个消息都有唯一的id和对应的内容,每个Stream都有唯一的名称,在使用 xadd 追加消息时自动确认

优点:

  1. Stream 支持阻塞式拉取消息
    读取消息时,只需要增加 BLOCK 参数即可支持阻塞式拉取消息。
    BLOCK 0 表示阻塞等待,不设置超时时间,直到新的消息发布
  2. 支持发布\订阅模式
  3. Stream能保证信息不丢失
    消费者处理完信息后,会执行XACK命令告诉Redis该信息已处理完,将该信息标记为处理完成。如果消费者宕机,也不会执行XACK,数据就仍然存在,待消费者重新下发后,再处理该数据
  4. Stream中数据会写入到RDB和AOF做持久化
    如果Stream宕机,也可以从RDB和AOF中恢复数据
  5. 消息堆积时,Stream的处理
    会丢弃旧消息,只保留固定长度的新消息

发布消息时,指定队列长度,防止队列积压导致内存爆炸

Lottery:0>XADD streamtest MAXLEN 1000 * name Li age 20
"1657176543288-0"

查看所有消费组状态:
pending表示没有ack的数据

Lottery:0>xinfo groups streamtest
1) 1) "name"
   2) "s1"
   3) "consumers"
   4) "1"
   5) "pending"
   6) "6"
   7) "last-delivered-id"
   8) "1657164924048-0"

2) 1) "name"
   2) "s2"
   3) "consumers"
   4) "1"
   5) "pending"
   6) "4"
   7) "last-delivered-id"
   8) "1657164924048-0"

查看指定消费组状态:

Lottery:0>xinfo consumers streamtest s1
1) 1) "name"
   2) "c1"
   3) "pending"
   4) "6"
   5) "idle"
   6) "192178"

处理待ack信息:

Lottery:0>xack streamtest s1 1657164924048-0
"1"

Lottery:0>xinfo groups streamtest
1) 1) "name"
   2) "s1"
   3) "consumers"
   4) "1"
   5) "pending"
   6) "5"
   7) "last-delivered-id"
   8) "1657164924048-0"

2) 1) "name"
   2) "s2"
   3) "consumers"
   4) "1"
   5) "pending"
   6) "4"
   7) "last-delivered-id"
   8) "1657164924048-0"

获取消费组未处理完毕的消息:

Lottery:0>XPENDING streamtest s1 - + 10
1) 1) "1657076882890-0"  //消费id
   2) "c1"               //消费者
   3) "108726876"        //从此读取距离现在时间
   4) "1"                //消息被读取次数

2) 1) "1657077550951-0"
   2) "c1"
   3) "108701211"
   4) "1"

3) 1) "1657078713910-0"
   2) "c1"
   3) "108590106"
   4) "1"

4) 1) "1657155254465-0"
   2) "c1"
   3) "22757318"
   4) "1"

5) 1) "1657164829007-0"
   2) "c1"
   3) "22475009"
   4) "1"
Lottery:0>xpending streamtest s1
1) "5"
2) "1657076882890-0"
3) "1657164829007-0"
4) 1) 1) "c1"
      2) "5"

Lottery:0>xack streamtest s1 1657164829007-0
"1"
Lottery:0>xpending streamtest s1
1) "4"
2) "1657076882890-0"
3) "1657155254465-0"
4) 1) 1) "c1"
      2) "4"

分布式锁

  1. SET lockKey uniqueValue EX 3 NX
    lockKey - 锁名
    uniqueValue - 锁的值
    EX - 过期时间设置(秒),EX 3 表示这个锁有 3 秒的过期时间。 PX 3 单位为毫秒。
    NX - 只有当锁名对应的key值不存在时才能set成功

  2. Redisson
    https://github.com/redisson/redisson/wiki/%E7%9B%AE%E5%BD%95

事务(Transactions)

可以一次执行多个命令,本质是一组命令的集合,一个事务中所有命令都会序列化,按顺序的串行执行而不会被其他命令插入,不许加塞

事务的作用

一个队列中,一次性、顺序性、排他性的执行一系列的命令

事务中在执行时若出现错误,则事务队列中的所有命令都不执行,即不保证原子性

命令 说明
MULTI 开启事务
EXEC 执行所有 MULTI 之后的命令
DISCARD 丢弃所有 MULTI 之后发出的命令
WATCH 锁定 key 直到执行了 MULTI 或 EXEC 操作
UNWATCH 取消事务执行

事务的开启、执行、取消:
在这里插入图片描述

使用 watch 监视

在这里插入图片描述

当使用 watch 监视 key ,若在事务执行前 key 被改动,事务会取消所有命令的执行(可以看到输出 nil)
在这里插入图片描述
在这里插入图片描述

事务的三阶段:

  • 开启:以MULTI开始一个事务
  • 入队:将多个命令入队到事务中,接到这些命令并不会立即执行,而是放到等待执行的事务队列里面
  • 执行:由EXEC命令触发事务

在这里插入图片描述

redis 的事务拥有以下特性

  1. 如果在执行 exec 之前事务中断了,那么所有的命令都不会执行
  2. 如果某个命令语法错误,不仅会导致该命令入队失败,整个事务都将无法执行
  3. 如果执行了 exec 命令之后,那么所有的命令都会按序执行
  4. 当 redis 在执行命令时,如果出现了错误,那么 redis 不会终止其它命令的执行,这是与关系型数据库事务最大的区别,redis 事务不会因为某个命令执行失败而回滚

redis 事务的缺陷

  • 不满足原子性
    • 与关系型数据库不同,redis 事务不满足原子性,一个事务执行过程中,其他事务或 cilent 可以对相应的 key 进行修改
    • 要避免这样的并发问题需要使用 WATCH 命令,但是要慎重选择加锁的 key
    • 额外的 WATCH 会增加事务失败的可能,而缺少必要的 WATCH 又会让我们的程序产生竞争条件
  • 一个事务中后执行的命令无法依赖先执行命令的结果
    • 事务中的所有命令都是互相独立的,在 EXEC 命令之前没有真正的执行,所有无法使用
    • 唯一可以做的是:通过 watch 保证我们在进行修改时。如果其他事务刚好进行了修改,则当前的修改停止,然后应用层做相应的处理
  • 事务中的每条命令都与 redis 服务器进行网络交互
    • redis 事务开启之后,没执行一个操作返回的都是 queued ,涉及客户端与服务端的多次交互

持久化

redis除了支持多种多样的存储类型,还有一点也非常重要,那就是尽管它是基于内存的存储系统,但它也能进行数据的持久化操作。这一点,对于缓存不幸宕机想恢复缓存数据时相当有效。同样,我们实际使用redis时,为了更高的性能和更高的可用性会将redis配置为集群主从模式。本章节重点介绍持久化和主从复制的相关配置。

redis的持久化有两种方式:快照持久化(RDB)AOF持久化

快照持久化(RDB)

  快照持久化是在某一时刻的所有数据写入到硬盘中持久化,因为持久化的时机不确定,所以可能存在数据丢失的风险。所以,快照持久化适用于即使丢失一部分数据也不会在成问题的场景。

在这里插入图片描述

配置快照持久化:

  • 命令
  • 配置文件

优点:

  1. RDB是一个非常紧凑的文件,它保存了某个时间点得数据集,非常适用于数据集的备份,比如你可以在每个小时报保存一下过去24小时内的数据,同时每天保存过去30天的数据,这样即使出了问题你也可以根据需求恢复到不同版本的数据集。
  2. RDB是一个紧凑的单一文件,很方便传送到另一个远端数据中心或者亚马逊的S3(可能加密),非常适用于灾难恢复。
  3. RDB在保存RDB文件时父进程唯一需要做的就是fork出一个子进程,接下来的工作全部由子进程来做,父进程不需要再做其他IO操作,所以RDB持久化方式可以最大化redis的性能。
  4. 与AOF相比,在恢复大的数据集的时候,RDB方式会更快一些。

缺点:

  1. 耗时、耗性能。RDB 需要经常fork子进程来保存数据集到硬盘上,当数据集比较大的时候,fork的过程是非常耗时的,可能会导致Redis在一些毫秒级内不能响应客户端的请求。如果数据集巨大并且CPU性能不是很好的情况下,这种情况会持续1秒,AOF也需要fork,但是你可以调节重写日志文件的频率来提高数据集的耐久度。
  2. 不可控、丢失数据。如果你希望在redis意外停止工作(例如电源中断)的情况下丢失的数据最少的话,那么RDB不适合你。虽然你可以配置不同的save时间点(例如每隔5分钟并且对数据集有100个写的操作),是Redis要完整的保存整个数据集是一个比较繁重的工作,你通常会每隔5分钟或者更久做一次完整的保存,万一在Redis意外宕机,你可能会丢失几分钟的数据。

自动生成 RDB(配置文件)

在这里插入图片描述
stop-writes-on-bgsave-error yes #当持久化出现错误时,是否停止数据写入,默认停止数据写入。可配置为no,当持久化出现错误时,仍然能继续写入缓存数据。

rdbcompression yes #是否压缩数据,默认压缩。可配置为no,不压缩。

rdbchecksum yes #对持久化rdb文件是否进行校验,默认校验。可配置为no,不校验。

在这里插入图片描述
在这里插入图片描述

配置 redis 日志,通过日志查看 redis 的运行情况
在这里插入图片描述

在这里插入图片描述

通过查看日志,可以看到持久化保存的时间

或者直接通过命令动态修改配置(只在本次服务有效,重启后失效)

CONFIG SET save "5 1"

手动同步 RDB (命令)

通过命令 bgsave 和 save 随时进行快照持久化

bgsave 和 save 会创建一个子进程,通过子进程将快照写入硬盘,父进程则继续处理命令请求

save 是 redis 在快照创建完成前不会响应其他命令,即为阻塞式的,并不常用

在这里插入图片描述
在这里插入图片描述

save 与 bgsave 对比:

命令 save bgsave
IO类型 同步 异步
阻塞 是(阻塞发生在fock(),通常非常快)
复杂度 O(n) O(n)
优点 不会消耗额外的内存 需要fock子进程,消耗内存
缺点 阻塞客户端命令 不阻塞客户端命令

通过redis服务端的日志我们就能发现,配置文件中的save配置底层调用的是bgsave命令

在调用shutdown命令时,将会调用save命令阻塞其他命令,当执行完成后关闭服务器

AOF 持久化

工作原理:通过记录每个可能修改数据集的命令,并将其追加到AOF文件中。

主要特性:

  • 命令追加:所有对 Redis 数据库进行修改的操作都会被记录在 AOF 文件中。
  • 同步策略:
    • always: 每个写操作都同步到磁盘,性能最低但最安全。
    • everysec: 每秒同步一次,平衡了性能与安全性,默认选项。
    • no: 由操作系统决定何时将数据同步到磁盘,最快但也最不安全。
  • 重写机制:随着时间推移,AOF 文件会变得非常大。Redis 支持 AOF 重写,即创建一个新的 AOF 文件,只包含重建当前数据集所需的最小命令集合,从而减少文件大小而不丢失任何数据。

优点和缺点

  • 优点
    • 数据安全性高,尤其是在使用 always 或 everysec 策略时。
    • 即使发生崩溃,丢失的数据量也较小或几乎没有。
  • 缺点
    • 相比 RDB,AOF 文件通常更大且恢复速度较慢。
    • 写入性能可能会受到影响,特别是当采用 always 策略时。

Redis 在 Java 中的使用

package com.test;

import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.junit.Before;
import org.junit.Test;

import redis.clients.jedis.Jedis;

public class TestRedis {
    private Jedis jedis;

    @Before
    public void setup() {
        //连接redis服务器,192.168.0.100:6379
        jedis = new Jedis("192.168.0.100", 6379);
        //权限认证
        jedis.auth("admin");
    }

    /**
     * redis存储字符串
     */
    @Test
    public void testString() {
        //-----添加数据----------
        jedis.set("name","xinxin");//向key-->name中放入了value-->xinxin
        System.out.println(jedis.get("name"));//执行结果:xinxin

        jedis.append("name", " is my lover"); //拼接
        System.out.println(jedis.get("name"));

        jedis.del("name");  //删除某个键
        System.out.println(jedis.get("name"));
        //设置多个键值对
        jedis.mset("name","liuling","age","23","qq","476777XXX");
        jedis.incr("age"); //进行加1操作
        System.out.println(jedis.get("name") + "-" + jedis.get("age") + "-" + jedis.get("qq"));

    }

    /**
     * redis操作Map
     */
    @Test
    public void testMap() {
        //-----添加数据----------
        Map<String, String> map = new HashMap<String, String>();
        map.put("name", "xinxin");
        map.put("age", "22");
        map.put("qq", "123456");
        jedis.hmset("user",map);
        //取出user中的name,执行结果:[minxr]-->注意结果是一个泛型的List
        //第一个参数是存入redis中map对象的key,后面跟的是放入map中的对象的key,后面的key可以跟多个,是可变参数
        List<String> rsmap = jedis.hmget("user", "name", "age", "qq");
        System.out.println(rsmap);

        //删除map中的某个键值
        jedis.hdel("user","age");
        System.out.println(jedis.hmget("user", "age")); //因为删除了,所以返回的是null
        System.out.println(jedis.hlen("user")); //返回key为user的键中存放的值的个数2
        System.out.println(jedis.exists("user"));//是否存在key为user的记录 返回true
        System.out.println(jedis.hkeys("user"));//返回map对象中的所有key
        System.out.println(jedis.hvals("user"));//返回map对象中的所有value

        Iterator<String> iter=jedis.hkeys("user").iterator();
        while (iter.hasNext()){
            String key = iter.next();
            System.out.println(key+":"+jedis.hmget("user",key));
        }
    }

    /**
     * jedis操作List
     */
    @Test
    public void testList(){
        //开始前,先移除所有的内容
        jedis.del("java framework");
        System.out.println(jedis.lrange("java framework",0,-1));
        //先向key java framework中存放三条数据
        jedis.lpush("java framework","spring");
        jedis.lpush("java framework","struts");
        jedis.lpush("java framework","hibernate");
        //再取出所有数据jedis.lrange是按范围取出,
        // 第一个是key,第二个是起始位置,第三个是结束位置,jedis.llen获取长度 -1表示取得所有
        System.out.println(jedis.lrange("java framework",0,-1));

        jedis.del("java framework");
        jedis.rpush("java framework","spring");
        jedis.rpush("java framework","struts");
        jedis.rpush("java framework","hibernate");
        System.out.println(jedis.lrange("java framework",0,-1));
    }

    /**
     * jedis操作Set
     */
    @Test
    public void testSet(){
        //添加
        jedis.sadd("user","liuling");
        jedis.sadd("user","xinxin");
        jedis.sadd("user","ling");
        jedis.sadd("user","zhangxinxin");
        jedis.sadd("user","who");
        //移除noname
        jedis.srem("user","who");
        System.out.println(jedis.smembers("user"));//获取所有加入的value
        System.out.println(jedis.sismember("user", "who"));//判断 who 是否是user集合的元素
        System.out.println(jedis.srandmember("user"));
        System.out.println(jedis.scard("user"));//返回集合的元素个数
    }

    @Test
    public void test() throws InterruptedException {
        //jedis 排序
        //注意,此处的rpush和lpush是List的操作。是一个双向链表(但从表现来看的)
        jedis.del("a");//先清除数据,再加入数据进行测试
        jedis.rpush("a", "1");
        jedis.lpush("a","6");
        jedis.lpush("a","3");
        jedis.lpush("a","9");
        System.out.println(jedis.lrange("a",0,-1));// [9, 3, 6, 1]
        System.out.println(jedis.sort("a")); //[1, 3, 6, 9]  //输入排序后结果
        System.out.println(jedis.lrange("a",0,-1));
    }

    @Test
    public void testRedisPool() {
        RedisUtil.getJedis().set("newname", "中文测试");
        System.out.println(RedisUtil.getJedis().get("newname"));
    }
}

Redis 连接池

package com.test;

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 = "192.168.0.100";

    //Redis的端口号
    private static int PORT = 6379;

    //访问密码
    private static String AUTH = "admin";

    //可用连接实例的最大数目,默认值为8;
    //如果赋值为-1,则表示不限制;如果pool已经分配了maxActive个jedis实例,则此时pool的状态为exhausted(耗尽)。
    private static int MAX_ACTIVE = 1024;

    //控制一个pool最多有多少个状态为idle(空闲的)的jedis实例,默认值也是8。
    private static int MAX_IDLE = 200;

    //等待可用连接的最大时间,单位毫秒,默认值为-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);
            jedisPool = new JedisPool(config, ADDR, PORT, TIMEOUT, AUTH);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取Jedis实例
     * @return
     */
    public synchronized static Jedis getJedis() {
        try {
            if (jedisPool != null) {
                Jedis resource = jedisPool.getResource();
                return resource;
            } else {
                return null;
            }
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 释放jedis资源
     * @param jedis
     */
    public static void returnResource(final Jedis jedis) {
        if (jedis != null) {
            jedisPool.returnResource(jedis);
        }
    }
}

网站公告

今日签到

点亮在社区的每一天
去签到