文章目录
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 支持的键值对
- 字符串类型
- string 哈希类型
- hash 列表类型
- list 集合类型 set
- 有序集合类型 sortedset
redis 应用场景
- 缓存(数据查询、短连接、新闻内容、商品内容等等)
- 聊天室的在线好友列表
- 任务队列。(秒杀、抢购、12306等等)
- 应用排行榜
- 网站访问统计
- 数据过期处理(可以精确到毫秒
- 分布式集群架构中的session分离
安装
redis是C语言开发,安装redis需要先将官网下载的源码进行编译,编译依赖gcc环境。如果没有gcc环境,需要安装gcc
- 安装gcc
yum install gcc-c++
- 将Windows下下载的压缩文件redis-5.0.7.tar.gz上传到Linux下
- 解压文件
tar -zxvf /opt/softes/redis-5.0.5.tar.gz -C redis/
- 进入解压后的目录进行编译(.c 文件编译为 .o文件)
# 进入解压后的目录
cd /opt/softes/redis-5.0.5
#进行编译
make install
安装完成后,在 /usr/local/redis/bin 中的文件:
copy 文件
redis启动需要一个配置文件,可以修改端口号等信息。
将加压目录下的redis.conf 拷贝到/usr/local下启动(前端启动)
# 进入指定目录
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
- redis 的关闭
- 查询到PID,kill -9 pid 【断电,非正常关闭,一般不用,否则造成数据丢失】
ps -ef | grep redis
- 正常关闭,数据保存(shutdown)
# 停止 redis 服务
./redis-cli shutdown
- 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 追加消息时自动确认
优点:
- Stream 支持阻塞式拉取消息
读取消息时,只需要增加 BLOCK 参数即可支持阻塞式拉取消息。
BLOCK 0 表示阻塞等待,不设置超时时间,直到新的消息发布 - 支持发布\订阅模式
- Stream能保证信息不丢失
消费者处理完信息后,会执行XACK命令告诉Redis该信息已处理完,将该信息标记为处理完成。如果消费者宕机,也不会执行XACK,数据就仍然存在,待消费者重新下发后,再处理该数据 - Stream中数据会写入到RDB和AOF做持久化
如果Stream宕机,也可以从RDB和AOF中恢复数据 - 消息堆积时,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"
分布式锁
SET lockKey uniqueValue EX 3 NX
lockKey - 锁名
uniqueValue - 锁的值
EX - 过期时间设置(秒),EX 3 表示这个锁有 3 秒的过期时间。 PX 3 单位为毫秒。
NX - 只有当锁名对应的key值不存在时才能set成功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 的事务拥有以下特性
- 如果在执行 exec 之前事务中断了,那么所有的命令都不会执行
- 如果某个命令语法错误,不仅会导致该命令入队失败,整个事务都将无法执行
- 如果执行了 exec 命令之后,那么所有的命令都会按序执行
- 当 redis 在执行命令时,如果出现了错误,那么 redis 不会终止其它命令的执行,这是与关系型数据库事务最大的区别,redis 事务不会因为某个命令执行失败而回滚
redis 事务的缺陷
- 不满足原子性
- 与关系型数据库不同,redis 事务不满足原子性,一个事务执行过程中,其他事务或 cilent 可以对相应的 key 进行修改
- 要避免这样的并发问题需要使用 WATCH 命令,但是要慎重选择加锁的 key
- 额外的 WATCH 会增加事务失败的可能,而缺少必要的 WATCH 又会让我们的程序产生竞争条件
- 一个事务中后执行的命令无法依赖先执行命令的结果
- 事务中的所有命令都是互相独立的,在 EXEC 命令之前没有真正的执行,所有无法使用
- 唯一可以做的是:通过 watch 保证我们在进行修改时。如果其他事务刚好进行了修改,则当前的修改停止,然后应用层做相应的处理
- 事务中的每条命令都与 redis 服务器进行网络交互
- redis 事务开启之后,没执行一个操作返回的都是 queued ,涉及客户端与服务端的多次交互
持久化
redis除了支持多种多样的存储类型,还有一点也非常重要,那就是尽管它是基于内存的存储系统,但它也能进行数据的持久化操作。这一点,对于缓存不幸宕机想恢复缓存数据时相当有效。同样,我们实际使用redis时,为了更高的性能和更高的可用性会将redis配置为集群主从模式。本章节重点介绍持久化和主从复制的相关配置。
redis的持久化有两种方式:快照持久化(RDB)和AOF持久化。
快照持久化(RDB)
快照持久化是在某一时刻的所有数据写入到硬盘中持久化,因为持久化的时机不确定,所以可能存在数据丢失的风险。所以,快照持久化适用于即使丢失一部分数据也不会在成问题的场景。
配置快照持久化:
- 命令
- 配置文件
优点:
- RDB是一个非常紧凑的文件,它保存了某个时间点得数据集,非常适用于数据集的备份,比如你可以在每个小时报保存一下过去24小时内的数据,同时每天保存过去30天的数据,这样即使出了问题你也可以根据需求恢复到不同版本的数据集。
- RDB是一个紧凑的单一文件,很方便传送到另一个远端数据中心或者亚马逊的S3(可能加密),非常适用于灾难恢复。
- RDB在保存RDB文件时父进程唯一需要做的就是fork出一个子进程,接下来的工作全部由子进程来做,父进程不需要再做其他IO操作,所以RDB持久化方式可以最大化redis的性能。
- 与AOF相比,在恢复大的数据集的时候,RDB方式会更快一些。
缺点:
- 耗时、耗性能。RDB 需要经常fork子进程来保存数据集到硬盘上,当数据集比较大的时候,fork的过程是非常耗时的,可能会导致Redis在一些毫秒级内不能响应客户端的请求。如果数据集巨大并且CPU性能不是很好的情况下,这种情况会持续1秒,AOF也需要fork,但是你可以调节重写日志文件的频率来提高数据集的耐久度。
- 不可控、丢失数据。如果你希望在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);
}
}
}