NoSQL(NoSQL = Not Only SQL),意即 不仅仅是SQL
,是一项全新的数据库理念,泛指 非关系型的数据库。
随着互联网的高速崛起,网站的用户群的增加,访问量的上升,传统(关系型)数据库上都开始出现了性能瓶颈,Web 程序不再仅仅专注在功能上,同时也在追求性能。所以 NOSQL 数据库应运而上,具体表现为对如下三高问题的解决:
NoSQL数据库的四大分类如下:
键值(Key-Value)存储数据库:
相关产品:Tokyo Cabinet/Tyrant、Redis、Voldemeort、Berkeley DB
典型应用:内容缓存,主要用于处理大量数据的高访问负载
数据模型:一系列键值对
优势:快速查询
劣势:存储的数据缺少结构化
列存储数据库(分布式):
相关产品:Cassandra、HBase、Riak
典型应用:分布式的文件系统
数据模型:以列簇式存储,将同一列数据存在一起
优势:查找速度快,可扩展性强,更容易进行分布式扩展
劣势:功能相对局限
文档型数据库:
相关产品:CouchDB、MongoDB
典型应用:Web应用(与Key-Value类似,Value是结构化的)
数据模型:一系列键值对
优势:数据结构要求不严格
劣势:查询性能不高,而且缺乏统一的查询语法
图形(Graph)数据库:
相关产品:Neo4J、InfoGrid、Infinite Graph
典型应用:社交网络
数据模型:图结构
优势:利用图结构相关算法
劣势:需要对整个图做计算才能得出结果,不容易做分布式的集群方案
在大数据存取上具备关系型数据库无法比拟的性能优势,例如:
Redis 是用 C语言 开发的一个开源的高性能 键值对(key-value)数据库,数据是保存在内存里面的。官方提供测试数据,50个并发执行100000个请求,读的速度是 110000次/s,写的速度是 81000次/s,且 Redis 通过提供多种键值数据类型来适应不同场景下的存储需求,目前为止 Redis 支持的键值数据类型如下:
Redis 的应用场景:
Redis 的特点:高性能:Redis 读的速度是 11W次/s,写的速度是 8.1W次/S。 原子性:保证数据的准确性。持久存储:支持两种方式的持久化,RDB 和 AOF, 可以把内存中的数据持久化到磁盘中。支持主从: master-slave 架构,可以实现负载均衡、高可用。支持集群:从3.0版本开始支持。
官方提倡使用 Linux 版的 Redis,所以官网只提供了 Linux 版的 Redis 下载,我们可以从 GitHub上下载 Window 版的 Redis,具体链接地址如下:
但是笔者不选择解压安装,以如下方式进行安装:
下载好了之后,双击开始安装,直接下一步下一步安装即可,记着把设置环境变量前面的复选框勾上,其他采用默认设置就好了:
安装步骤可以参考这篇博文:https://www.cnblogs.com/liuqingzheng/p/9831331.html
也可以采用解压 Redis 压缩包的安装方式,见到如下目录结构:
安装好之后,黑窗口测试:
将下载好的安装包上传到 bigdata04 机器的 /data/soft 目录下:
解压:tar -zxvf redis-5.0.9.tar.gz
编译+安装:cd redis-5.0.9、make、make install。注意:由于 Redis 需要依赖于C语言环境,如果读者安装的 Centos 镜像是精简版,会缺失C语言的依赖,所以需要安装C语言环境才可以编译成功。笔者在这使用的 Centos 镜像是完整版,里面是包含C语言环境的,所以就不存在这个问题了。
修改 redis.conf 配置文件:vi redis.conf,修改内容如下:
daemonize yes
logfile /data/soft/redis-5.0.9/log
bind 127.0.0.1 192.168.61.103
daemonize 参数的值默认是 no,表示在前台启动 Redis,但是 Redis 是一个数据库,我们希望把它放到后台运行,所以将参数的值改为 yes。logfile 参数的值默认为空,表示 Redis 会将日志输出到 /dev/null(黑洞) 里面,也就是不保存了,建议在这设置一个日志路径记录 Redis 的日志,便于后期排查问题。bind 参数可以绑定指定 ip,这样就可以通过这里指定的 ip 来访问 Redis 服务了,可以在后面指定当前机器的本地回环地址(127.0.0.1)和内网地址(192.168.61.103),指定本地回环地址是为了能够在本机自己连自己比较方便。指定内网地址是为了能够让公司局域网内的其它服务器也能连到这个 Redis,如果你这台机器有外网地址的话不建议在这配置,因为使用外网地址的话就不安全了,容易受到网络攻击。
启动 redis:redis-server redis.conf
验证:
[root@bigdata04 redis-5.0.9]# ps -ef | grep redis
root 34734 1 0 18:40 ? 00:00:00 redis-server 127.0.0.1:6379
root 34742 1731 0 18:40 pts/0 00:00:00 grep --color=auto redis
连接 redis 数据库:redis-cli
[root@bigdata04 redis-5.0.9]# redis-cli
127.0.0.1:6379>
[root@bigdata04 redis-5.0.9]# redis-cli -h 192.168.61.103 -p 6379
192.168.61.103:6379>
[root@bigdata04 redis-5.0.9]# redis-cli -h 127.0.0.1 -p 6379
127.0.0.1:6379>
停止 redis 数据库:
[root@bigdata04 redis-5.0.9]# redis-cli -h 127.0.0.1 -p 6379
127.0.0.1:6379> shutdown
not connected>
[root@bigdata04 redis-5.0.9]# ps -ef | grep redis
root 34881 1731 0 18:51 pts/0 00:00:00 grep --color=auto redis
[root@bigdata04 redis-5.0.9]# redis-server redis.conf
[root@bigdata04 redis-5.0.9]# redis-cli shutdown
[root@bigdata04 redis-5.0.9]# ps -ef | grep redis
root 34897 1731 0 18:52 pts/0 00:00:00 grep --color=auto redis
在可以在 Windows 机器上使用图形化工具进行连接:
启动 redis 服务,使用 redis-cli 客户端连到 redis 数据库里面:redis-server redis.conf、redis-cli
退出客户端:quit、exit。不过我还是习惯使用 Ctrl+C 退出客户端
帮助命令:help
获得符合规则的键:keys 例如:keys *、keys a*、在生产环境下建议禁用 keys 命令,因为这个命令会查询过滤 redis 中的所有数据,可能会造成服务阻塞,影响 redis 执行效率。如果有类似的查询需求建议使用 scan。scan 命令是一个基于游标的迭代器。这意味着命令每次被调用都需要使用上一次调用返回的游标作为该次调用的游标参数,以此来延续之前的迭代过程。当 scan 命令的游标参数被设置为 0 时, 服务器将开始一次新的迭代, 而当服务器向用户返回值为 0 的游标时, 表示迭代已结束。scan 命令是一个基于游标的迭代器。这意味着命令每次被调用都需要使用上一次调用返回的游标作为该次调用的游标参数,以此来延续之前的迭代过程。当 scan 命令的游标参数被设置为 0 时, 服务器将开始一次新的迭代, 而当服务器向用户返回值为 0 的游标时, 表示迭代已结束。
过滤:scan 0 match a* count 10
判断键是否存在:exists 例如:
127.0.0.1:6379> exists a1
(integer) 1
127.0.0.1:6379> exists c1
(integer) 0
删除键:del 例如:del a1。也支持删除多个 key,例如:del b2 b1 a2
获得键值的类型:type,例如:type a2。这个命令可以帮我们快速识别某一个 key 中存储的数据是什么类型的,因为针对存储了不同类型值的 key,操作的命令是不一样的。
Redis 默认支持 16 个数据库,通过 databases 参数控制的,这个参数在 redis.conf 配置文件中,如下:
每个数据库对外都是以一个从0开始的递增数字命名,不支持自定义。Redis 默认选择的是0号数据库,可以通过 select 命令切换。例如:select 1
一般在工作中会使用 2~3
个数据库,可以根据业务类型来分库,不同业务的数据存到不同的库里面,还有一种用法是,一个库作为测试库,一个库作为正式库,如果没有特殊需求,一般使用0号数据库就可以了,这个库使用起来比较方便,默认就是0号库,不需要使用 select 切换。具体在工作中怎么用都行,只要理解它的特性就可以了。但是有一点需要注意:多个数据库之间并不是完全隔离的,如果使用 flushall 命令,则会清空 redis 中所有数据库内的数据。并且我们在 redis 中使用多个库,并不能提高 redis 的存储能力,因为默认这16个库共用 redis 的内存存储空间,如果想要提高 redis 的存储能力,需要给我们的服务器增加内存才可以。如果只想清空当前数据库中的数据,可以使用 flushdb。
redis 中存储的数据是以 key-value 的形式存在的,其中 value 支持5种数据类型,在日常开发中主要使用比较多的有字符串、哈希、字符串列表、字符串集合四种类型,其中最为常用的是字符串类型。
string 是 redis 最基本的类型,用的也是最多的,一个 key 对应一个 value。应用场景:
常见命令:
命令 | 描述 |
---|---|
SET key value(重点) | 设置指定 key 的值 |
GET key(重点) | 获取指定 key 的值 |
DEL key | 删除key |
GETSET key value | 将给定 key 的值设为 value ,并返回 key 的旧值(old value)。 |
SETEX key seconds value(重点) | 将值 value 关联到 key ,并将 key 的过期时间设为 seconds (以秒为单位)。 |
SETNX key value | 只有在 key 不存在时设置 key 的值。 |
INCR key(重点) | 将 key 中储存的数字值增一。 |
INCRBY key increment | 将 key 所储存的值加上给定的增量值(increment) 。 |
DECR key | 将 key 中储存的数字值减一。 |
DECRBY key decrement | key 所储存的值减去给定的减量值(decrement) 。 |
应用举例: |
[root@bigdata04 redis-5.0.9]# redis-cli
127.0.0.1:6379> set str a
OK
127.0.0.1:6379> get str
"a"
127.0.0.1:6379> mset str1 a1 str2 a2
OK
127.0.0.1:6379> mget str1 str2
1) "a1"
2) "a2"
127.0.0.1:6379> set num 1
OK
127.0.0.1:6379> incr num
(integer) 2
127.0.0.1:6379> get num
"2"
127.0.0.1:6379> decr num
(integer) 1
127.0.0.1:6379> get num
"1"
127.0.0.1:6379> incrby num 2
(integer) 3
127.0.0.1:6379> get num
"3"
127.0.0.1:6379> incrbyfloat num 2.1
"5.1"
127.0.0.1:6379> incrbyfloat num -2.1
"3"
127.0.0.1:6379> get str
"a"
127.0.0.1:6379> strlen str
(integer) 1
127.0.0.1:6379> set str abcd
OK
127.0.0.1:6379> strlen str
(integer) 4
127.0.0.1:6379> getset str edfg
"abcd"
商品编号、订单号采用string的递增数字特性生成。
定义商品编号key:product:id
192.168.101.3:7003> INCR product:id
(integer) 2
192.168.101.3:7003> INCR product:id
(integer) 3
使用 string 的问题:假设有 User 对象以 JSON 序列化的形式存储到 Redis 中,User 对象有 id、username、password、age、name 等属性,存储的过程如下: 保存、更新。User 对象 ==> json(string) ==>redis
,如果在业务上只是更新 age 属性,其他的属性并不做更新我应该怎么做呢? 如果仍然采用上边的方法在传输、处理时会造成资源浪费,hash 可以很好的解决这个问题。
Redis 中 hash 是一个键值对集合。 Redis hash 是一个 string 类型的 field 和 value 的映射表,hash 特别适合用于存储对象。Redis 存储 hash 可以看成是 String key 和 String value 的 map 容器,也就是说把值看成 map 集合。它特别适合存储对象,相比较而言,将一个对象类型存储在 Hash 类型里要比存储在 String 类型里占用更少的内存空间并方便存取整个对象。
key value
u1 name zsage 18
u2 name lsage 19Map
用一个对象来存储用户信息,商品信息,订单信息等等。 常见命令如下:
命令 | 命令描述 |
---|---|
hset key filed value | 将哈希表 key 中的字段 field 的值设为 value |
hmset key field1 value1 [field2 value2]…(重点) | 同时将多个 field-value (字段-值)对设置到哈希表 key 中 |
hget key filed | 获取存储在哈希表中指定字段的值 |
hmget key filed1 filed2 (重点) | 获取多个给定字段的值 |
hdel key filed1 [filed2] (重点) | 删除一个或多个哈希表字段 |
hlen key | 获取哈希表中字段的数量 |
del key | 删除整个hash(对象) |
HGETALL key (重点) | 获取在哈希表中指定 key 的所有字段和值 |
HKEYS key | 获取所有哈希表中的字段 |
HVALS key | 获取哈希表中所有值 |
应用举例:
[root@bigdata04 redis-5.0.9]# redis-cli
127.0.0.1:6379> hset user:1 name amo age 18
(integer) 2
127.0.0.1:6379> hget user:1 name
"amo"
127.0.0.1:6379> hget user:1 age
"18"
127.0.0.1:6379> hmset user:2 name jerry age 32
OK
127.0.0.1:6379> hmget user:2 name age
1) "jerry"
2) "32"
127.0.0.1:6379> hmget user:1 name age
1) "amo"
2) "18"
127.0.0.1:6379> hgetall user:2
1) "name"
2) "jerry"
3) "age"
4) "32"
127.0.0.1:6379> hexists user:2 name
(integer) 1
127.0.0.1:6379> hexists user:2 city
(integer) 0
127.0.0.1:6379> hincrby user:2 age 2
(integer) 34
127.0.0.1:6379> hdel user:2 age
(integer) 1
127.0.0.1:6379> hset user:2 city beijing
(integer) 1
127.0.0.1:6379> hkeys user:2
1) "name"
2) "city"
127.0.0.1:6379> hvals user:2
1) "jerry"
2) "beijing"
127.0.0.1:6379> hlen user:2
(integer) 2
hset 和 hmset:
ArrayList 使用数组方式存储数据,所以根据索引查询数据速度快,而新增或者删除元素时需要设计到位移操作,所以比较慢。 LinkedList 使用双向链表方式存储数据,每个元素都记录前后元素的指针,所以插入、删除数据时只是更改前后元素的指针指向即可,速度非常快。然后通过下标查询元素时需要从头开始索引,所以比较慢,但是如果查询前几个元素或后几个元素速度比较快。
列表类型(list)可以存储一个有序的字符串列表(链表)
,常用的操作是向列表两端添加元素,或者获得列表的某一个片段。列表类型内部是使用双向链表(double linked list)实现的,所以向列表两端添加元素的时间复杂度为O(1),获取越接近两端的元素速度就越快。这意味着即使是一个有几千万个元素的列表,获取头部或尾部的10条记录也是极快的。应用场景:
常见命令:
命令 | 命令描述 |
---|---|
lpush key value1 value2…(重点) | 将一个或多个值插入到列表头部(左边) |
rpush key value1 value2…(重点) | 在列表中添加一个或多个值(右边) |
lpop key(重点) | 左边弹出一个 相当于移除第一个 |
rpop key(重点) | 右边弹出一个 相当于移除最后一个 |
llen key | 返回指定key所对应的list中元素个数 |
LINDEX key index | 通过索引获取列表中的元素 |
LINSERT key BEFORE| AFTER pivot value | 在列表的元素前或者后插入元素 |
应用举例: |
[root@bigdata04 redis-5.0.9]# redis-cli
127.0.0.1:6379> lpush list1 a
(integer) 1
127.0.0.1:6379> lpush list1 b
(integer) 2
127.0.0.1:6379> lpop list1
"b"
127.0.0.1:6379> lpop list1
"a"
127.0.0.1:6379> lpop list1
(nil)
127.0.0.1:6379> rpush list2 x
(integer) 1
127.0.0.1:6379> rpush list2 y
(integer) 2
127.0.0.1:6379> rpop list2
"y"
127.0.0.1:6379> rpop list2
"x"
127.0.0.1:6379> lpush list3 a b c d
(integer) 4
127.0.0.1:6379> llen list3
(integer) 4
127.0.0.1:6379> lrange list3 0 -1
1) "d"
2) "c"
3) "b"
4) "a"
127.0.0.1:6379> lindex list3 1
"c"
127.0.0.1:6379> lset list3 1 m
OK
127.0.0.1:6379> lrange list3 0 -1
1) "d"
2) "m"
3) "b"
4) "a
127.0.0.1:6379> linsert list3 before "m" "g"
(integer) 5
127.0.0.1:6379> lrange list3 0 -1
1) "d"
2) "g"
3) "m"
4) "b"
5) "a"
Redis 的 Set 是 string 类型的无序集合。集合成员是唯一的,这就意味着集合中不能出现重复的数据。Redis 中集合是通过哈希表实现的,所以添加、删除,查找的时间复杂度都是 O(1)。集合中最大的成员数为2的32次方-1 (4294967295, 每个集合可存储40多亿个成员)。Redis 还提供了多个集合之间的交集、并集、差集的运算,特点:无序+唯一。 应用场景:
常见命令:
命令 | 命令描述 |
---|---|
sadd key member1 [member2] (重点) | 向集合添加一个或多个成员 |
srem key member1 [member2] | 移除一个成员或者多个成员 |
smembers key | 返回集合中的所有成员,查看所有 |
SCARD key | 获取集合的成员数 |
SPOP key | 移除并返回集合中的一个随机元素 |
SDIFF key1 [key2] (重点) | 返回给定所有集合的差集 |
SUNION key1 [key2] (重点) | 返回所有给定集合的并集 |
SINTER key1 [key2] (重点) | 返回给定所有集合的交集 |
应用举例: |
[root@bigdata04 redis-5.0.9]# redis-cli
127.0.0.1:6379> sadd set1 a
(integer) 1
127.0.0.1:6379> sadd set1 b
(integer) 1
127.0.0.1:6379> smembers set1
1) "a"
2) "b"
127.0.0.1:6379> srem set1 a
(integer) 1
127.0.0.1:6379> sismember set1 b
(integer) 1
127.0.0.1:6379> sismember set1 a
(integer) 0
127.0.0.1:6379> sadd set2 a b c
(integer) 3
127.0.0.1:6379> sadd set3 a b x
(integer) 3
127.0.0.1:6379> sdiff set2 set3
1) "c"
127.0.0.1:6379> sdiff set3 set2
1) "x"
127.0.0.1:6379> sinter set2 set3
1) "a"
2) "b"
127.0.0.1:6379> sunion set2 set3
1) "a"
2) "x"
3) "b"
4) "c"
127.0.0.1:6379> scard set3
(integer) 3
127.0.0.1:6379> spop set3
"b"
127.0.0.1:6379> smembers set3
1) "a"
2) "x"
Redis 有序集合和集合一样也是 string 类型元素的集合,且不允许重复的成员。不同的是每个元素都会关联一个 double 类型的分数。redis 正是通过分数来为集合中的成员进行从小到大的排序。有序集合的成员是唯一的,但分数(score)却可以重复。集合是通过哈希表实现的,所以添加、删除、查找的复杂度都是(1)。 集合中最大的成员数为2的32次方-1(4294967295,每个集合可存储40多亿个成员)。特点: 有序(根据分数排序)+唯一。应用场景:
常见命令:
命令 | 命令描述 |
---|---|
ZADD key score member [score member …](重点) | 增加元素 |
ZSCORE key member | 获取元素的分数 |
ZREM key member [member …] | 删除元素 |
ZCARD key | 获得集合中元素的数量 |
ZRANGE key start stop[WITHSCORES] (重点) | 获得排名在某个范围的元素列表 |
ZREVRANGE key start stop (重点) | 按照分数从高到低排序 |
应用举例: |
[root@bigdata04 redis-5.0.9]# redis-cli
127.0.0.1:6379> zadd zset1 5 a
(integer) 1
127.0.0.1:6379> zadd zset1 3 b
(integer) 1
127.0.0.1:6379> zadd zset1 4 c
(integer) 1
127.0.0.1:6379> zscore zset1 a
"5"
127.0.0.1:6379> zrange zset1 0 -1
1) "b"
2) "c"
3) "a"
127.0.0.1:6379> zrevrange zset1 0 -1
1) "a"
2) "c"
3) "b"
127.0.0.1:6379> zincrby zset1 3 a
"8"
127.0.0.1:6379> zscore zset1 a
"8"
127.0.0.1:6379> zcard zset1
(integer) 3
127.0.0.1:6379> zrem zset1 a
(integer) 1
127.0.0.1:6379> zrange zset1 0 -1
1) "b"
2) "c"
keys *: 查询所有的 key。
exists key:判断是否有指定的key 若有返回1,否则返回0。
expire key 秒数:设置这个key在缓存中的存活时间(重要)。 expireat abc 1626968100 也可以指定时间戳。
ttl key:展示指定 key 的剩余时间。若返回值为 -1:永不过期、若返回值为 -2:已过期或者不存在。
del key:删除指定key(重要)。
rename key 新key:重命名。
type key:判断一个key的类型。
ping:测试连接是否连接。
redis 默认是16个数据库,编号是从0~15。【默认是0号库】
select index:切换库。
move key index:把key移动到几号库(index是库的编号)。
flushdb:清空当前数据库。
flushall:清空当前实例下所有的数据库。
info:返回关于 Redis 服务器的各种信息和统计数值。
# 筛选其中几条重要信息查看一下
# Server
# redis 服务器版本信息
redis_version:5.0.9
# redis 服务的可执行文件路径
executable:/data/soft/redis-5.0.9/redis-server
# 启动redis时使用的配置文件路径
config_file:/data/soft/redis-5.0.9/redis.conf# Clients
# 已连接客户端数量
connected_clients:2# Memory
# redis 目前存储数据使用的内存
used_memory_human:857.25K
# redis 可以使用的内存总量,和服务器的内存有关
total_system_memory_human:1.78G# Keyspace
# db0表示0号数据库,keys:表示0号数据库的key总量,expires:表示0号数据库失效被删除的key总量
db0:keys=10,expires=0,avg_ttl=0
db3:keys=1,expires=0,avg_ttl=0
设置密码: vi redis.conf、
redis-cli shutdown、redis-server redis.conf、redis-cli、auth admin
禁用命令:
Redis 监控命令-monitor:monitor 可以监控我们对 redis 的所有操作,如果在线上的服务器上打开了这个功能,这里面就会频繁打印出来我们对 redis 数据库的所有操作,这样会影响 redis 的性能,所以说要慎用。但是在某些特殊的场景下面它是很有用的,排查问题:redis-cli monitor | grep key。
Redis 发布订阅(pub/sub)是进程间一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息。Redis 客户端可以订阅任意数量的频道。相关的命令如下:
序号 | 命令及描述 |
---|---|
1 | PUBLISH channel message 将信息发送到指定的频道。 |
2 | SUBSCRIBE channel [channel …] 订阅给定的一个或多个频道的信息。 |
3 | UNSUBSCRIBE [channel [channel …]] 指退订给定的频道 |
开启两个客户端,A客户端如下: | |
![]() | |
B客户端如下: | |
![]() | |
Redis 发布订阅(pub/sub)是进程间一种消息通信模式,工作里面一般使用 MQ。 |
Redis 的高性能是由于其将所有数据都存储在了内存中,为了使 Redis 在重启之后仍能保证数据不丢失,需要将数据 从内存中同步到硬盘(文件)中,这一过程就是持久化。Redis 支持两种方式的持久化,一种是 RDB 方式,一种是 AOF 方式。可以单独使用其中一种或将二者结合使用。
RDB 持久化机制:
RDB持久化是指在 指定的时间间隔内 将内存中的数据集快照写入磁盘。这种方式是就是将内存中数据以快照的方式写入到二进制文件中,默认的文件名为 dump.rdb。 这种方式是默认 已经开启了,不需要配置。
在 redis.conf 配置文件中有如下配置:
其中,上面配置的是 RDB 方式数据持久化时机:
关键字 | 时间(秒) | key修改数量 | 解释 |
---|---|---|---|
save | 900 | 1 | 每900秒(15分钟)至少有1个key发生变化,则dump内存快照 |
save | 300 | 10 | 每300秒(5分钟)至少有10个key发生变化,则dump内存快照 |
save | 60 | 10000 | 每60秒(1分钟)至少有10000个key发生变化,则dump内存快照 |
这里面的三个时机哪个先满足都会执行快照操作。 |
AOF 持久化机制:
AOF 持久化机制会将每一个收到的写命令都通过 write 函数追加到文件中,默认的文件名是 appendonly.aof。 这种方式默认是没有开启的,要使用时候需要配置。
然后重新启动 Redis 服务,在开启 AOF 持久化机制后,默认会在目录下产生一个 appendonly.aof 文件,如下:
配置详解如下:
关键字 | 持久化时机 | 解释 |
---|---|---|
appendfsync | always | 每执行一次更新命令,持久化一次 |
appendfsync | everysec | 每秒钟持久化一次 |
appendfsync | no | 不持久化 |
小结:
选择:如果你非常关心你的数据, 但仍然可以承受数分钟以内的数据丢失,选择 RDB 持久化。如果对数据的完整性要求比较高,选择 AOF。
Redis 不仅是使用命令来操作,现在基本上主流的语言都有客户端支持,比如 Java、Python、C、C#、C++、php、Node.js、Go等。 在官方网站里列一些 Java 的客户端,有 Jedis、Redisson、Jredis、JDBC-Redis等,其中官方推荐使用 Jedis 和 Redisson。 在企业中用的最多的就是 Jedis,Jedis 同样也是托管在 github 上,说白了Jedis就是使用Java操作Redis的客户端(工具包)。地址:https://github.com/xetorthio/jedis。
方法 | 解释 |
---|---|
new Jedis(host, port) | 创建jedis对象,参数host是redis服务器地址,参数port是redis服务端口 |
set(key,value) | 设置字符串类型的数据 |
get(key) | 获得字符串类型的数据 |
hset(key,field,value) | 设置哈希类型的数据 |
hget(key,field) | 获得哈希类型的数据 |
lpush(key,values) | 设置列表类型的数据 |
lpop(key) | 列表左面弹栈 |
rpop(key) | 列表右面弹栈 |
sadd(String key, String… members) | 设置set类型的数据 |
zrange(String key, long start, long end) | 获得在某个范围的元素列表 |
del(key) | 删除key |
我们需要借助于第三方 jar 包 jedis 来操作,首先在 idea 中创建 maven 项目
然后在项目 bd_jredis 中的 pom.xml 文件中添加 jedis 依赖,如下:
redis.clients jedis 3.6.3
代码如下:
package com.amo;import redis.clients.jedis.Jedis;public class RedisSingle {public static void main(String[] args) {/*** 记得redis机器的防火墙要关闭,IP地址要写机器的内网地址*///1.获取jedis连接Jedis jedis = new Jedis("192.168.61.103", 6379);//jedis.auth("admin");//2.向redis中添加数据,key=name value=amojedis.set("name", "amo");//3.从redis中查询key=imooc的value的值String value = jedis.get("name");System.out.println(value);//4.关闭jedis连接jedis.close();}
}
jedis 连接资源的创建与销毁是很消耗程序性能,所以 jedis 为我们提供了 jedis 的池化技术,jedisPool 在创建时初始化一些连接资源存储到连接池中,使用 jedis 连接资源时不需要创建,而是从连接池中获取一个资源进行 redis 的操作,使用完毕后,不需要销毁该 jedis 连接资源,而是将该资源归还给连接池,供其他请求使用。使用连接池的方式操作 redis,代码如下:
package com.amo;import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;public class RedisPool {public static void main(String[] args) {//创建连接池配置对象JedisPoolConfig poolConfig = new JedisPoolConfig();//连接池中最大空闲连接数poolConfig.setMaxIdle(10);//连接池中创建的最大连接数poolConfig.setMaxTotal(100);//创建连接的超时时间poolConfig.setMaxWaitMillis(2000);//表示从连接池中获取连接的时候会先测试一下连接是否可用,这样可以保证取出的连接都是可用的poolConfig.setTestOnBorrow(true);//获取jedis连接池JedisPool jedisPool = new JedisPool(poolConfig, "192.168.61.103", 6379);//从jedis连接池中取出一个连接Jedis jedis = jedisPool.getResource();String value = jedis.get("name");System.out.println(value);//注意:此处的close方法有两层含义//1:如果jedis是直接创建的单连接,此时表示直接关闭这个连接//2:如果jedis是从连接池中获取的连接,此时会把这个连接返回给连接池jedis.close();//关闭jedis连接池jedisPool.close();}
}
基于 redis 连接池的方式提取 RedisUtils 工具类,代码如下:
package com.amo;import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;public class RedisUtils {//私有化构造函数,禁止newprivate RedisUtils() {}private static JedisPool jedisPool = null;//获取连接 多线程的时候会出问题,所以加上synchronizedpublic static synchronized Jedis getJedis() {if (jedisPool == null) {JedisPoolConfig poolConfig = new JedisPoolConfig();poolConfig.setMaxIdle(10);poolConfig.setMaxTotal(100);poolConfig.setMaxWaitMillis(2000);poolConfig.setTestOnBorrow(true);jedisPool = new JedisPool(poolConfig, "192.168.61.103", 6379);}return jedisPool.getResource();}//向连接池返回连接public static void returnResource(Jedis jedis) {jedis.close();}}
使用工具类代码如下:
package com.amo;import redis.clients.jedis.Jedis;public class TestRedisUtils {public static void main(String[] args) {//获取连接Jedis jedis = RedisUtils.getJedis();String value = jedis.get("name");System.out.println(value);//向连接池返回连接RedisUtils.returnResource(jedis);}
}
pipeline 管道: 针对批量操作数据或者批量初始化数据的时候使用,效率高,Redis 的 pipeline 功能在命令行中没有实现,在Java客户端(jedis)中是可以使用的,它的原理是这样的:
不使用管道的时候,我们每执行一条命令都需要和 redis 服务器交互一次,使用管道之后,可以实现一次提交一批命令,这一批命令只需要和 redis 服务器交互一次,所以就提高了性能。这个功能就类似于 mysql 中的 batch 批处理。初始化10万条数据,使用普通方式一条一条添加和使用管道批量初始化进行对比分析,代码如下:
package com.amo;import redis.clients.jedis.Jedis;
import redis.clients.jedis.Pipeline;public class PipelineOp {public static void main(String[] args) {//1:不使用管道Jedis jedis = RedisUtils.getJedis();long start_time = System.currentTimeMillis();for (int i = 0; i < 100000; i++) {jedis.set("a" + i, "a" + i);}long end_time = System.currentTimeMillis();System.out.println("不使用管道,耗时:" + (end_time - start_time));//2:使用管道Pipeline pipelined = jedis.pipelined();start_time = System.currentTimeMillis();for (int i = 0; i < 100000; i++) {pipelined.set("b" + i, "b" + i);}pipelined.sync();end_time = System.currentTimeMillis();System.out.println("使用管道,耗时:" + (end_time - start_time));RedisUtils.returnResource(jedis);}
}
执行结果如下图所示:
从这可以看出来,针对海量数据的初始化,管道可以显著提高初始化性能。
在开始之前,请确保已经安装好了 Redis 并能正常运行,安装方式可以参考上文。除了安装好 Redis 数据库外,还需要安装好 redis-py 库,即用来操作 Redis 的 Python 包,可以使用 pip 进行安装,安装命令如下:
pip install redis
安装好 Redis 数据库和 redis-py 库之后,便可以开始下文的学习了。
补充:解析器 PythonParser (Python实现默认)、HiredisParser (C语言实现)
帮助文档:https://pypi.org/project/redis/
redis-py 库提供 Redis 和 StrictRedis 两个类,用来实现 Redis 命令对应的操作。StrictRedis 类实现了绝大部分官方的 Redis 命令,参数也一一对应,例如 set 方法就对应 Redis 命令的 set 方法。而 Redis 类是 StrictRedis 的子类,其主要功能是向后兼容旧版本库里的几个方法。为了实现兼容,Redis 类对方法做了改写,例如将 lrem 方法中 value 和 num 参数的位置互换,这和 Redis 命令行的命令参数是不一致的。官方推荐使用 StrictRedis 类,所以本文后续也使用 StrictRedis 类的相关方法作为演示。
连接 Redis 数据库示例代码如下:
# -*- coding: utf-8 -*-
# @Time : 2023-03-18 17:16
# @Author : AmoXiang
# @File : 1.建立连接.py
# @Software: PyCharm
# @Blog : https://blog.csdn.net/xw1680import redisdef get_connection():""" 直接连接到redis """# connection = redis.Redis(host='localhost', port=6379, db=1)connection = redis.StrictRedis(host='localhost', port=6379, db=1)return connectiondef get_connection_by_pool():""" 使用连接池 """pool = redis.ConnectionPool(host='localhost', port=6379, db=1, max_connections=20)# connection = redis.Redis(connection_pool=pool)connection = redis.StrictRedis(connection_pool=pool)return connectiondef get_connection_by_url():# redis://[:password]@host:port/dburl = "redis://localhost:6379/0"pool = redis.ConnectionPool.from_url(url)connection = redis.StrictRedis(connection_pool=pool)return connectiondef close_connection(conn):""" 关闭连接 """conn.close()if __name__ == '__main__':# connection = get_connection()# connection.set('key', 'value')# close_connection(connection)# connection2 = get_connection_by_pool()# result = connection2.set('key2', 'value2')# print(result)# connection2.close()# close_connection(connection2)connection3 = get_connection_by_url()result = connection3.set('key2', 'value2')print(result)# connection2.close()close_connection(connection3)
参考代码如下:
# -*- coding: utf-8 -*-
# @Time : 2023-03-18 17:36
# @Author : AmoXiang
# @File : 2.字符串操作.py
# @Software: PyCharm
# @Blog : https://blog.csdn.net/xw1680import json
import redisclass BaseRedisConnection(object):def __init__(self):pool = redis.ConnectionPool(host='localhost', port=6379, db=1, max_connections=20,# 自动进行结果的转换decode_responses=True)self.conn = redis.StrictRedis(connection_pool=pool)def __del__(self):""" 关闭连接 """try:self.conn.close()except Exception as e:print(e)class TestString(BaseRedisConnection):"""set : 设置值get : 获取值mset : 设置多个键值对mget : 获取多个键值对append : 添加字符串incr/decr: 增加/减少 1"""def test_set(self):"""set 设置值"""user1 = self.conn.set('user1', 'Amy')print(user1)user2 = self.conn.set("user2", '李四')print(user2)# 10秒后自动删除user3 = self.conn.set('user3', 'user3', ex=10)print(user3)def test_get(self):""" get 获取值 """user1 = self.conn.get('user1')print('user1: ', user1)# 如果没有设置参数: decode_responses=True 则需要手动的进行结果转换# user2 = self.conn.get('user2').decode('utf-8')user2 = self.conn.get('user2')print('user2: ', user2)user3 = self.conn.get('user3')print('user3: ', user3) # key在redis中不存在则返回Noneuser4 = self.conn.get('user4')print('user4: ', user4) # key在redis中不存在则返回Nonedef test_mset(self):""" mset 设置多个键值对 """d = {'user5': 'Bob','user6': 'Bobx'}result = self.conn.mset(d)print(result)def test_mget(self):""" mget 获取多个键值对 """result = self.conn.mget(['user1', 'user5', 'user7'])print(result)def test_incr(self):""" incr/decr 增加/减少1 """result = self.conn.incr('score')print(result)score = self.conn.get('score')print('score:', score)def test_del(self):result = self.conn.delete('user1')print(result)def register(self, username, password, nickname):"""模拟用户注册:param username: 用户名:param password: 密码:param nickname: 昵称:return:"""key = f'user:{username}'user_info = {'username': username,'password': password,'nickname': nickname,}value = json.dumps(user_info)result = self.conn.set(key, value)print('result', result)def login(self, username, password):"""模拟用户登录:param username: 用户名:param password: 密码:return:"""key = f'user:{username}'value = self.conn.get(key)print(value)if value is None:print('用户不存在')return Noneuser_info = json.loads(value)print(user_info['username'])print(user_info['password'])print(user_info['nickname'])if user_info['password'] != password:print('密码输入不正确')return Noneprint('登录成功')def main():str_obj = TestString()# str_obj.test_set()# str_obj.test_get()# str_obj.test_mset()# str_obj.test_mget()# str_obj.test_incr()str_obj.test_del()# str_obj.register('lili', '123456', '丽丽')# str_obj.login('lili', '12')# str_obj.login('lili', '123456')# str_obj.login('lili2', '123456')if __name__ == '__main__':main()
参考代码如下:
# -*- coding: utf-8 -*-
# @Time : 2023-03-18 18:24
# @Author : AmoXiang
# @File : 3.列表操作.py
# @Software: PyCharm
# @Blog : https://blog.csdn.net/xw1680import redisclass BaseRedisConnection(object):def __init__(self):pool = redis.ConnectionPool(host='localhost', port=6379, db=1, max_connections=20,# 自动进行结果的转换decode_responses=True)self.conn = redis.StrictRedis(connection_pool=pool)def __del__(self):""" 关闭连接 """try:self.conn.close()except Exception as e:print(e)class TestList(BaseRedisConnection):"""lpush/rpush : 从左/右插入数据lrange : 获取指定长度的数据ltrim : 截取一定长度的数据lpop/rpop :移除最左/右的成员并返回lpushx/rpushx : key存在的时候才插入数据,不存在时不做任何处理"""def test_push(self):""" lpush/rpush -- 从左/右插入数据 """# t = ['Amy3', 'Bob5']# result = self.conn.rpush('user_list', *t)result = self.conn.lpush('user_list', 'Amy', 'Bob')print(result)user_list = self.conn.lrange('user_list', 0, -1)print(user_list)def test_pop(self):""" lpop/rpop --移除最左/右的元素并返回 """result = self.conn.lpop('user_list')print(result)user_list = self.conn.lrange('user_list', 0, -1)print(user_list)def test_llen(self):""" 列表长度 """result = self.conn.llen('user_list')print(result)def main():list_obj = TestList()# list_obj.test_push()# list_obj.test_pop()list_obj.test_llen()if __name__ == '__main__':main()
参考代码如下:
# -*- coding: utf-8 -*-
# @Time : 2023-03-18 19:34
# @Author : AmoXiang
# @File : 4.散列操作.py
# @Software: PyCharm
# @Blog : https://blog.csdn.net/xw1680import redisclass BaseRedisConnection(object):def __init__(self):pool = redis.ConnectionPool(host='localhost', port=6379, db=1, max_connections=20,# 自动进行结果的转换decode_responses=True)self.conn = redis.StrictRedis(connection_pool=pool)def __del__(self):""" 关闭连接 """try:self.conn.close()except Exception as e:print(e)class TestHash(BaseRedisConnection):"""hset/hget :设置/获取散列值hmset/hmget : 设置/获取多对散列值hsetnx : 如果散列已经存在,则不设置hkeys/hvals : 返回所有Keys/Valueshlen : 返回散列包含域(field)的数量hdel : 删除散列指定的域(field)hexists : 判断是否存在"""def test_hset(self):""" hset/hget --设置/获取散列值 """result = self.conn.hset('stu:0001', 'name', '李丽')print(result)# 插入之前判断是否已经存在exists = self.conn.hexists('stu:0001', 'name')print('exists:', exists)# 如果已经存在,则不插入result = self.conn.hsetnx('stu:0002', 'name', '王丽')print('res:', result)def test_hmset(self):""" hmset/hmget -- 设置/获取多对散列值 """m = {'name': 'Bob','age': 21,'grade': 98}# Redis.hmset() is deprecated. Use Redis.hset() instead.# result = self.conn.hmset('stu:0003', m)result = self.conn.hset('stu:0003', mapping=m)print(result)keys = self.conn.hkeys('stu:0003')print('keys:', keys)def test_hdel(self):""" hdel : 删除散列指定的域(field) """length = self.conn.hlen('stu:0003')print('length:', length)result = self.conn.hdel('stu:0003', 'age')print('res:', result)length = self.conn.hlen('stu:0003')print('length2:', length)def register(self, username, password, nickname):"""模拟用户注册:param username: 用户名:param password: 密码:param nickname: 昵称:return:"""user_info = {'username': username,'password': password,'nickname': nickname}key = f'user:{username}'result = self.conn.hset(key, mapping=user_info)print(result)def get_user_info(self, username):""" 获取用户信息 """key = f'user:{username}'user = self.conn.hmget(key, 'username', 'password', 'nickname')# user = self.conn.hmget(key, 'username')print(user)print('username:', user[0])def main():hash_obj = TestHash()# hash_obj.test_hset()# hash_obj.test_hmset()# hash_obj.test_hdel()# hash_obj.register('wangwu', '123456', '王五')hash_obj.get_user_info('wangwu')if __name__ == '__main__':main()
参考代码如下:
# -*- coding: utf-8 -*-
# @Time : 2023-03-18 19:48
# @Author : AmoXiang
# @File : 5.集合操作.py
# @Software: PyCharm
# @Blog : https://blog.csdn.net/xw1680import redisclass BaseRedisConnection(object):def __init__(self):pool = redis.ConnectionPool(host='localhost', port=6379, db=1, max_connections=20,# 自动进行结果的转换decode_responses=True)self.conn = redis.StrictRedis(connection_pool=pool)def __del__(self):""" 关闭连接 """try:self.conn.close()except Exception as e:print(e)class TestSet(BaseRedisConnection):"""sadd/srem : 添加/删除成员sismember :判断是否为set的一个成员smembers :返回该集合的所有成员sdiff : 返回一个集合与其它集合的差异sinter : 返回几个集合的交集sunion : 返回几个集合的并集"""def test_sadd(self):""" sadd -- 添加成员 """result = self.conn.sadd('zoo1', 'Dog', 'Cat')print(result)animals = ['Monkey', 'Panda', 'Dog']result = self.conn.sadd('zoo1', *animals)print(result)members = self.conn.smembers('zoo1')print(members)def test_srem(self):""" srem -- 删除成员 """result = self.conn.srem('zoo1', 'Dog')print(result)members = self.conn.smembers('zoo1')print(members)def course_analysis(self):""" 课程分析 """# 1. 自然科学science_stu_list = ['Stu003', 'Stu022', 'Stu021', 'Stu012', 'Stu014', ]self.conn.sadd('science', *science_stu_list)# 2. 大学英语english_stu_list = ['Stu001', 'Stu021', 'Stu011', 'Stu012', 'Stu004', ]self.conn.sadd('english', *english_stu_list)result = self.conn.sinter('science', 'english')print(result)def main():set_obj = TestSet()set_obj.test_sadd()# set_obj.test_srem()# set_obj.course_analysis()if __name__ == '__main__':main()
参考代码如下:
# -*- coding: utf-8 -*-
# @Time : 2023-03-18 19:50
# @Author : AmoXiang
# @File : 6.有序集合操作.py
# @Software: PyCharm
# @Blog : https://blog.csdn.net/xw1680import redisclass BaseRedisConnection(object):def __init__(self):pool = redis.ConnectionPool(host='localhost', port=6379, db=1, max_connections=20,# 自动进行结果的转换decode_responses=True)self.conn = redis.StrictRedis(connection_pool=pool)def __del__(self):""" 关闭连接 """try:self.conn.close()except Exception as e:print(e)class TestzSet(BaseRedisConnection):"""zadd/zrem : 添加(修改)/删除成员zcard/zcount :统计成员的数量zscore :某个成员的分数(score)zrange/zrevrange :按升序/降序查看成员zrank/zrevrank :按升序/降序查看成员排名"""def test_zadd(self):""" zadd -- 添加成员 """rank = {'李明': 3.25,'王浩': 3.21,'张皓文': 4.02,'吴文杰': 4,'赵宇': 3.53,'林峰': 2.5}result = self.conn.zadd('swimming', rank)print(result)count = self.conn.zcount('swimming', 0, 100)print('count:', count)def test_zrem(self):""" zrem -- 删除成员 """result = self.conn.zrem('swimming', '林峰')print(result)count = self.conn.zcount('swimming', 0, 100)print('count:', count)def rank_analysis(self):""" 比赛结果分析 """result = self.conn.zrange('swimming', 0, -1)print(result)# 前3名result = self.conn.zrange('swimming', 0, 2)print(result)def main():set_obj = TestzSet()# set_obj.test_zadd()# set_obj.test_zrem()set_obj.rank_analysis()if __name__ == '__main__':main()
至此今天的学习就到此结束了,笔者在这里声明,笔者写文章只是为了学习交流,以及让更多学习数据库的读者少走一些弯路,节省时间,并不用做其他用途,如有侵权,联系博主删除即可。感谢您阅读本篇博文,希望本文能成为您编程路上的领航者。祝您阅读愉快!
好书不厌读百回,熟读课思子自知。而我想要成为全场最靓的仔,就必须坚持通过学习来获取更多知识,用知识改变命运,用博客见证成长,用行动证明我在努力。
如果我的博客对你有帮助、如果你喜欢我的博客内容,请点赞
、评论
、收藏
一键三连哦!听说点赞的人运气不会太差,每一天都会元气满满呦!如果实在要白嫖的话,那祝你开心每一天,欢迎常来我博客看看。
编码不易,大家的支持就是我坚持下去的动力。点赞后不要忘了关注
我哦!