当前位置 博文首页 > BWH_Steven:【1w字+干货】第一篇,基础:让你的 Redis 不再只是
Redis 基础以及进阶的两篇已经全部更新好了,为了字数限制以及阅读方便,分成两篇发布。
本篇主要内容为:NoSQL 引入 Redis ,以及在 Linux7 环境下的安装,配置,以及总结了非常详细的类型,用法以及例子,最后通过 Jedis 以及 Springboot 中的 RedisTemplate 在 IDEA 中远程操作
Redis。
第二篇会主要涉及到配置文件,发布订阅,主从复制,哨兵等一些进阶用法的一个基本使用。
Redis是一个使用ANSI C编写的开源、支持网络、基于内存、可选持久性的键值对存储数据库 ——维基百科
可以简单的说,Redis就是一款高性能的NoSQL数据库
我们前面所学习的MySQL数据库是典型的的SQL数据库也就是传统的关系型数据库,而我们今天学习的Redis数据库则是一款NoSQL数据库,也叫作非关系型数据库,它与我们熟悉的MySQL等的概念完全是不一样的,它是一项全新的数据库理念,我们帖一组百度百科的解释
NoSQL,泛指非关系型的数据库。随着互联网web2.0网站的兴起,传统的关系数据库在处理web2.0网站,特别是超大规模和高并发的SNS类型的web2.0纯动态网站已经显得力不从心,出现了很多难以克服的问题,而非关系型的数据库则由于其本身的特点得到了非常迅速的发展。NoSQL数据库的产生就是为了解决大规模数据集合多重数据种类带来的挑战,尤其是大数据应用难题 ——百度百科
说明:我们现在所看到的的博客,RSS,P2P,微博,抖音等均属于 Web2.0的产物,Web2.0相比较过去的Web1.0更加注重于用户的交互,用户不仅可以浏览,还可以上传一些资源到网站上,例如图片文字或者说短视频等,使得用户也参与到了网站内容的制造中去了
部署成本低:部署操作简单,以开源软件为主
存储格式丰富:支持 key-value形式、文档、图片等众多形式,包括对象或者集合等格式
速度快:数据存储在缓存中,而不是硬盘中,而且例如Redis基于键值对,同时不需要经过SQL层解析,性能非常高
无耦合性,易扩展
有人会说,NoSQL = Not SQL ,但是我更倾向这样理解 NoSQL = Not only SQL ,我们不能以一个绝对的结论来判定两项技术的好坏,每一项技术的产生都有其特定的原因,在我看来,NoSQL更适合作为SQL数据库的补充,由于海量数据的出现,性能的要求高了起来,而NoSQL这种产物,对于结构简单但是数据量大的数据处理起来要比传统的SQL快很多,但是同样的,其逻辑运算就必须很简单,否则它也是力不从心的
在我看来,可以简单的说,NoSQL就是以功能换取性能,但是需要处理复杂的业务逻辑还需要使用关系型数据库,所以说想要在模型中完全用NoSQL替代SQL是不现实的,两者更像是互补的关系
SQL的好处:
市面上的NoSQL产品非常多,我们今天所要介绍的就是其中一款基于键值存储的数据库——Redis
分类 | Examples举例 | 典型应用场景 | 数据模型 | 优点 | 缺点 |
---|---|---|---|---|---|
键值(key-value) | Tokyo Cabinet/Tyrant, Redis, Voldemort, Oracle BDB | 内容缓存,主要用于处理大量数据的高访问负载,也用于一些日志系统等等。 | Key 指向 Value 的键值对,通常用hash table来实现 | 查找速度快 | 数据无结构化,通常只被当作字符串或者二进制数据 |
列存储数据库 | Cassandra, HBase, Riak | 分布式的文件系统 | 以列簇式存储,将同一列数据存在一起 | 查找速度快,可扩展性强,更容易进行分布式扩展 | 功能相对局限 |
文档型数据库 | CouchDB, MongoDb | Web应用(与Key-Value类似,Value是结构化的,不同的是数据库能够了解Value的内容) | Key-Value对应的键值对,Value为结构化数据 | 数据结构要求不严格,表结构可变,不需要像关系型数据库一样需要预先定义表结构 | 查询性能不高,而且缺乏统一的查询语法。 |
图形(Graph)数据库 | Neo4J, InfoGrid, Infinite Graph | 社交网络,推荐系统等。专注于构建关系图谱 | 图结构 | 利用图结构相关算法。比如最短路径寻址,N度关系查找等 | 很多时候需要对整个图做计算才能得出需要的信息,而且这种结构不太好做分布式的集群方案。 |
我们在一开始提到了,Redis就是一款高性能的NoSQL数据库,那么它的应用场景是什么呢?
用于用户内容缓存,可以处理大量数据的高访问负载,例如:数据查询,新闻,商品内容
任务队列,例如:秒杀,12306
在线好友列表
应用、网站访问统计排行
由于其基于键值存储,那么可以支持的存储的类型有什么呢?
字符串类型 - String
列表 - list:linkedlist
集合 - set
有序集合 - sortedset
哈希 - hash:map
说明:
推荐使用 Linux 进行部署,所以我们后面也会详细介绍 Linux 中的安装配置方式,但是如果只是想快速学习语法,也可以勉强使用 Windows 版本,安装会简单很多。
Redis is written in ANSI C and works in most POSIX systems like Linux,
*BSD
, and OS X, without external dependencies. Linux and OS X are the two operating systems where Redis is developed and tested the most, and we recommend using Linux for deployment . Redis may work in Solaris-derived systems like SmartOS, but the support is best effort. There is no official support for Windows builds.官网说明地址:https://redis.io/topics/introduction
官网:https://redis.io(推荐)
中文网:http://www.redis.net.cn
# 下载 redis-6.0.9 压缩包
wget http://download.redis.io/releases/redis-6.0.9.tar.gz
补充:
/home
目录下一般来说,我们程序都会放在 /opt
目录下,所以我们先将这个压缩文件移动过去再解压
# 移动此文件到根目录下的 opt 目录中
mv redis-6.0.9.tar.gz /opt
# 解压此文件
tar -zxvf redis-6.0.9.tar.gz
解压后 opt 目录下就多出一个 redis-6.0.9 的文件夹,我们打开它,就可以看到一些文件在其中,其中 redis.conf 是我们一会要用的配置文件,暂时先不理会
解压后的文件貌似也不能运行啊,这是当然的,因为这些文件还没有经过编译和安装,在编译之前,首先要检查一下 GCC 的版本
如果你选择的是 Redis 6 以上的版本,例如这里选择的 6.0.9,你的 gcc 版本如果太低就会导致后面编译出错,最起码你的 gcc 要到 5.3 的版本以上
如果没有 gcc 先进行安装
yum -y install gcc
yum -y install gcc-c++
安装完成后,通过 gcc -v
查看到安装到的版本是 4.x.x 版本的,所以要升级,旧版本的 Redis 可以不去做升级这一步
依次执行下面每一条命令
# 升级到gcc 9.3
yum -y install centos-release-scl
yum -y install devtoolset-9-gcc devtoolset-9-gcc-c++ devtoolset-9-binutils
# scl命令启用只是临时的,退出shell或重启就会恢复原系统gcc版本
scl enable devtoolset-9 bash
# 长期使用 gcc 9.3 还需要进行如下操作
echo "source /opt/rh/devtoolset-9/enable" >>/etc/profile
source /etc/profile
查看一下更新后的版本
依次执行编译和安装,
# 编译
make
# 安装
make install
make 会慢一下,耐心等待一下,如果出了错误,一般都是 gcc 的问题
安装后的内容一般都在 /usr/local/bin
下
我们把原来的配置文件就放在那个解压文件中,我们自己用的,单独复制一份出来,方便我们操作和更改
我们先去 /usr/local/bin
中创建一个新的文件夹,然后把之前解压后的文件夹中的 redis.conf
拷贝过来
# 跳转到指定目录下
cd /usr/local/bin
# 新建一个文件夹
mkdir myconfig
# 复制 /opt/redis-6.0.9/redis.conf 到 当前目录的 myconfig 文件夹下
cp /opt/redis-6.0.9/redis.conf myconfig
看一下过程
为了保证我们的redis可以后台运行,我们去编辑拷贝过来的 redis.conf 配置文件
vim redis.conf
在其中找到 daemonize no
将 no 修改为 yes,保存退出
下面先运行一下其服务端(保证当前在 usr/local/bin
目录下)
# 运行服务端
redis-server myconfig/redis.conf
接着运行其客户端
# 运行客户端
redis-cli -p 6379
可以简单测试一下,例如 set get 一下,能拿到值就代表成功了
先看一下运行中时,进程的存在情况
# 查看redis 进程
ps -ef|grep redis
在客户端中,可以通过 shutdown 和 exit 执行关闭(这个是在Redis客户端中执行)
# 关闭
127.0.0.1:6379> shutdown
not connected> exit
# 再次查看一下进程状况
[root@centos7 bin]# ps -ef|grep redis
我们可以去github中寻找windows版本,不过版本会有所滞后,官方起码是没有支持更新的,可能微软还想着能拽他一把。最新的版本好像也都是好几年前的了
https://github.com/microsoftarchive/redis/releases
解压即可用:分别启动 redis-server.exe 和 redis-cli.exe 就能直接测试使用了吗,有问题修改redis.windows.conf 配置文件
redis-server [--port 6379]
有时候参数会过多,建议使用配置文件启动
redis-server [xx/redis.conf]
例如:redis-server myconfig/redis.conf
redis-cli [-h 127.0.0.1 -p 6379]
例如 :redis-cli -p 6379
在客户端中(标志有 127.0.0.1:6379>)直接输入 shutown 等即可
# 关闭
127.0.0.1:6379> shutdown
not connected> exit
若在目录中(前面为 $
等),可以执行
redis-cli shutdown
kill redis-pid
返回 PONG 即连通了
127.0.0.1:6379> ping
PONG
注:每一种类型的存储方式是不太一样的,所以这里的操作不会讲到添加存储,下面会在每种类型中详细讲解。
只是想简单先测试,可以先暂时用这几个命令(这是 String 类型的)
可以使用 set key value
添加
set 为命令,key 为键,value 为值
例如:set test ideal-20
get key
获取到值
127.0.0.1:6379> keys *
1) "test"
2) "test2"
*
作为通配符,表示任意字符,因为其会遍历所有键,然后显示所有键列表,时间复杂度O(n),数据量过大的环境,谨慎使用127.0.0.1:6379> dbsize
(integer) 2
127.0.0.1:6379> exists test
(integer) 1
127.0.0.1:6379> type test
string
127.0.0.1:6379> move test2 3
(string) 1
127.0.0.1:6379> select 3
OK
127.0.0.1:6379[3]> keys *
1) "ideal-20-2"
127.0.0.1:6379> del test2
(integer) 1
127.0.0.1:6379> expire test 120
(integer) 1
127.0.0.1:6379> ttl test
(integer) 116
127.0.0.1:6379> persist test
(integer) 1
127.0.0.1:6379> ttl test
(integer) -1
127.0.0.1:6379> rename test ideal
OK
127.0.0.1:6379> keys *
1) "ideal"
127.0.0.1:6379> flushdb
OK
127.0.0.1:6379> keys *
(empty array)
127.0.0.1:6379> flushall
OK
127.0.0.1:6379> keys *
(empty array)
127.0.0.1:6379>
127.0.0.1:6379> set address beijing 5000
OK
127.0.0.1:6379> get address
“beijing”
127.0.0.1:6379> del address
(string) 1
如果字符串中的值为数字类型,可以进行递增递减,其它类型会报错
127.0.0.1:6379> set age 21
OK
127.0.0.1:6379> incr age # 递增
(integer) 22
127.0.0.1:6379> incrby age 5 # 递增 5
(integer) 27
127.0.0.1:6379> decr age # 递减
(integer) 26
127.0.0.1:6379> decrby age 5 # 递减 5
(integer) 21
127.0.0.1:6379> set ideal hello
OK
127.0.0.1:6379> append ideal ,ideal-20 # 追加内容
(integer) 14
127.0.0.1:6379> get ideal
"hello,ideal-20"
127.0.0.1:6379> get ideal
"hello,ideal-20"
127.0.0.1:6379> getrange ideal 0 3
"hell"
127.0.0.1:6379> getrange ideal 0 -1
"hello,ideal-20"
127.0.0.1:6379> get ideal
"hello,ideal-20"
127.0.0.1:6379> setrange ideal 6 bwh # 从下标为6的位置开始替换
(integer) 14
127.0.0.1:6379> get ideal
"hello,bwhal-20"
127.0.0.1:6379> strlen addr1
(integer) 7
127.0.0.1:6379> setnx address guangdong # address键 不存在,则创建
(integer) 1
127.0.0.1:6379> get address
"guangdong"
127.0.0.1:6379> setnx address beijing # address键 存在,则失败
(integer) 0
127.0.0.1:6379> get address
"guangdong"
127.0.0.1:6379> mset addr1 beijing addr2 guangdong addr3 shanghai # 同时存储多个值
OK
127.0.0.1:6379> keys *
1) "addr3"
2) "addr2"
3) "addr1"
127.0.0.1:6379> mget addr1 addr2 addr3 # 同时获取多个值
1) "beijing"
2) "guangdong"
3) "shanghai"
127.0.0.1:6379> msetnx age1 20 age2 25 age3 30 # 第一次同时存储多个值(保证不存在)
(integer) 1
127.0.0.1:6379> msetnx age4 35 age5 40 age1 45 # 第二次同时存储多个值(保证不存在),失败了
(integer) 0
127.0.0.1:6379>
127.0.0.1:6379> set user:1 {name:zhangsan,age:20} # 存一个对象
OK
127.0.0.1:6379> keys *
1) "user:1"
127.0.0.1:6379> get user:1
"{name:zhangsan,age:20}"
127.0.0.1:6379> mset user:1:name lisi user:1:age 25
OK
127.0.0.1:6379> mget user:1:name user:1:age
1) "lisi"
2) "25"
127.0.0.1:6379> getset addr beijing # 原先没有值,返回 nil
(nil)
127.0.0.1:6379> get addr
"beijing"
127.0.0.1:6379> getset addr guangdong # 原先有值,返回原先的值,然后覆盖新值
"beijing"
127.0.0.1:6379> get addr
"guangdong"
下面演示添加到左边的,右边的是一样的就不演示了
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> lpush list1 C
(integer) 3
127.0.0.1:6379> lrange list1 0 -1
1) "C"
2) "B"
3) "A"
127.0.0.1:6379> lrange list1 0 -1
1) "A"
2) "B"
3) "C"
127.0.0.1:6379> linsert list1 before C XXX # 在 C 前插入 XXX
(integer) 4
127.0.0.1:6379> lrange list1 0 -1
1) "A"
2) "B"
3) "XXX"
4) "C"
127.0.0.1:6379> lrange list1 0 -1 # 获取所有值
1) "C"
2) "B"
3) "A"
127.0.0.1:6379> lrange list1 0 1 # 获取指定区间的值
1) "C"
2) "B"
127.0.0.1:6379> lrange list1 0 -1
1) "C"
2) "B
127.0.0.1:6379> lindex list1 0
"C"
127.0.0.1:6379> lindex list1 1
"B"
127.0.0.1:6379> llen list1
(integer) 1
127.0.0.1:6379> lrange list1 0 -1
1) "D"
2) "C"
3) "B"
4) "A"
127.0.0.1:6379> lpop list1 # 删除列表最左边的元素,且返回元素
"D"
127.0.0.1:6379> rpop list1 # 删除列表最右边的元素,且返回元素
"A"
127.0.0.1:6379> lrange list1 0 -1
1) "C"
2) "B"
127.0.0.1:6379> lrange list1 0 -1
1) "C"
2) "C"
3) "B"
4) "A"
127.0.0.1:6379> lrem list1 1 A # 删除1个A
(integer) 1
127.0.0.1:6379> lrange list1 0 -1
1) "C"
2) "C"
3) "B"
127.0.0.1:6379> lrem list1 2 C # 删除2个C
(integer) 2
127.0.0.1:6379> lrange list1 0 -1
1) "B"
127.0.0.1:6379>
127.0.0.1:6379> lrange list1 0 -1
1) "A"
2) "B"
3) "C"
127.0.0.1:6379> rpoplpush list1 list2 # 移除 list1 中最后一个元素,且添加到list2 中去
"C"
127.0.0.1:6379> lrange list1 0 -1
1) "A"
2) "B"
127.0.0.1:6379> lrange list2 0 -1
1) "C"
127.0.0.1:6379> rpush list1 A
(integer) 1
127.0.0.1:6379> rpush list1 B
(integer) 2
127.0.0.1:6379> rpush list1 C
(integer) 3
127.0.0.1:6379> rpush list1 D
(integer) 4
127.0.0.1:6379> ltrim list1 1 2 # 截取下标为1到2的值
OK
127.0.0.1:6379> lrange list1 0 -1
1) "B"
2) "C"
语法:lset list 下标 value
127.0.0.1:6379> exists list1 # 判断是否存在此list
(integer) 0
127.0.0.1:6379> lset list1 0 beijing # 不存在,替换报错
(error) ERR no such key
127.0.0.1:6379> lpush list1 guangdong # 创建一个list
(integer) 1
127.0.0.1:6379> lindex list1 0
"guangdong"
127.0.0.1:6379> lset list1 0 beijing # 存在,替换成功
OK
127.0.0.1:6379> lindex list1 0
"beijing"
set:一种无序(不保证有序)集合,且元素不能重复
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> sadd set1 C
(integer) 1
127.0.0.1:6379> sadd set1 C # set的值不能重复
(integer) 0
127.0.0.1:6379> smembers set1 # 查询指定set的所有值,乱序
1) "B"
2) "A"
3) "C"
127.0.0.1:6379> smesmbers set1 # 查询指定set的所有值,乱序
1) "B"
2) "A"
3) "C"
127.0.0.1:6379> scard set1
(integer) 3
127.0.0.1:6379> smembers set1
1) "D"
2) "B"
3) "A"
4) "C"
127.0.0.1:6379> srandmember set1 # 获取一个随机元素
"D"
127.0.0.1:6379> srandmember set1 # 获取一个随机元素
"B"
127.0.0.1:6379> srandmember set1 2 # 获取两个随机元素
1) "A"
2) "D"
127.0.0.1:6379> srem set1 C # 删除 C 这个元素
(integer) 1
127.0.0.1:6379> smembers set1
1) "B"
2) "A"
127.0.0.1:6379> smembers set1
1) "D"
2) "B"
3) "A"
4) "C"
127.0.0.1:6379> spop set1 # 随机删除一个元素
"A"
127.0.0.1:6379> spop set1 # 随机删除一个元素
"B"
127.0.0.1:6379> smembers set1
1) "D"
2) "C"
127.0.0.1:6379> smembers set1
1) "D"
2) "C"
127.0.0.1:6379> smove set1 set2 D # 从 set1 移动 D 到 set2
(integer) 1
127.0.0.1:6379> smembers set1
1) "C"
127.0.0.1:6379> smembers set2
1) "D"
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> sadd set1 C
(integer) 1
127.0.0.1:6379> sadd set2 B
(integer) 1
127.0.0.1:6379> sadd set2 C
(integer) 1
127.0.0.1:6379> sadd set2 D
(integer) 1
127.0.0.1:6379> sadd set2 E
(integer) 1
127.0.0.1:6379> sinter set1 set2 # 交集
1) "B"
2) "C"
127.0.0.1:6379> sunion set1 set2 # 并集
1) "D"
2) "E"
3) "C"
4) "B"
5) "A"
127.0.0.1:6379> sdiff set1 set2 # 差集
1) "A"
此类型和 set 一样也是 string 类型元素的集合,且不允许重复的元素
不同的是每个元素都会关联一个double类型的分数,redis正是通过分数来为集合中的成员进行从小到大的排序
有序集合的成员是唯一,但分数(score)却可以重复
127.0.0.1:6379> zadd sortedset1 20 zhangsan # 添加一个
(integer) 1
127.0.0.1:6379> zadd sortedset1 10 lisi 60 wangwu # 添加多个
(integer) 2
127.0.0.1:6379> zrange sortedset1 0 -1
1) "lisi"
2) "zhangsan"
3) "wangwu"
127.0.0.1:6379> zrangebyscore sortedset1 -inf +inf # 从小到大
1) "lisi"
2) "zhangsan"
3) "wangwu"
127.0.0.1:6379> zrevrange sortedset1 0 -1 # 从大到小
1) "wangwu"
2) "zhangsan"
3) "lisi"
127.0.0.1:6379> zrangebyscore sortedset1 -inf +inf withscores # 显示从小到大且附带值
1) "lisi"
2) "10"
3) "zhangsan"
4) "20"
5) "wangwu"
6) "60"
127.0.0.1:6379> zrangebyscore sortedset1 -inf 20 withscores # 显示从小到大,且数值小于20的
1) "lisi"
2) "10"
3) "zhangsan"
4) "20"
127.0.0.1:6379>
127.0.0.1:6379> zcard sortedset1
(integer) 2
127.0.0.1:6379> zcount sortedset1 10 60
(integer) 3
127.0.0.1:6379> zrange sortedset1 0 -1
1) "lisi"
2) "zhangsan"
3) "wangwu"
127.0.0.1:6379> zrem sortedset1 wangwu # 删除 wangwu 这个元素
(integer) 1
127.0.0.1:6379> zrange sortedset1 0 -1
1) "lisi"
2) "zhangsan"
127.0.0.1:6379> hset hash1 username admin
(integer) 1
127.0.0.1:6379> hset hash1 password admin
(integer) 1
127.0.0.1:6379> hsetnx hash1 username admin888 # 已存在,失败
(integer) 0
127.0.0.1:6379> hsetnx hash1 code 666 # 不存在,成功
(integer) 1
127.0.0.1:6379> hget hash1 password
"admin"
127.0.0.1:6379> hgetall hash1
1) "username"
2) "admin"
3) "password"
4) "admin"
127.0.0.1:6379> hlen hash1
(integer) 2
127.0.0.1:6379> hkeys hash1 # 获取所有 field 字段
1) "username"
2) "password"
127.0.0.1:6379> hvals hash1 # 获取所有 value 值
1) "admin"
2) "admin"
127.0.0.1:6379> hdel hash1 username
(integer) 1
127.0.0.1:6379> hsetnx hash1 code 666
(integer) 1
127.0.0.1:6379> hincrby hash1 code 2
(integer) 668
127.0.0.1:6379> hincrby hash1 code -68
(integer) 600
使用经纬度,作为地理坐标,然后存储到一个有序集合 zset/sortedset 中去保存,所以 zset 中的命令也是可以使用的
命令列表:
语法:geoadd key longitud latitude member [..]
longitud——经度、 latitude——纬度
有效的经度从-180度到180度。
有效的纬度从-85.05112878度到85.05112878度。
127.0.0.1:6379> geoadd china:city 116.413384 39.910925 beijing
(integer) 1
127.0.0.1:6379> geoadd china:city 113.271431 23.135336 guangzhou
(integer) 1
127.0.0.1:6379> geoadd china:city 113.582555 22.276565 zhuhai
(integer) 1
127.0.0.1:6379> geoadd china:city 112.556391 37.876989 taiyuan
(integer) 1
127.0.0.1:6379> geopos china:city beijing zhuhai
1) 1) "116.41338318586349487"
2) "39.9109247398676743"
2) 1) "116.41338318586349487"
2) "39.9109247398676743"
语法:geodist key member1 member2 [unit]
单位默认为米,可以修改,跟在 member 后即可,例如 km
指定单位的参数 unit 必须是以下单位的其中一个:
m 表示单位为米
km 表示单位为千米
mi 表示单位为英里