Linux云计算 |【第四阶段】NOSQL-DAY1
主要内容:
NoSQL概述(RDBMS、NoSQL)、部署Redis服务、Redis数据类型(字符串、散列类型、列表类型、集合类型、有序集合类型)、Redis其它操作命令、修改Redis服务运行参数、部署支持PHP和Redis的Nginx服务器
一、NoSQL概述
NoSQL(Not Only SQL)是一类非关系型数据库管理系统(DBMS),它们不使用传统的基于表格的关系模型,而是采用各种不同的数据模型来存储和检索数据。NoSQL 数据库的设计目的是为了解决传统关系型数据库在某些场景下的局限性,特别是在处理大规模数据、高并发访问和分布式系统时。
1、数据类型
① RDBMS
- 关系数据库管理系统:Relational Database Management System
- 按照预先设置的组织结构,将数据存储在物理介质上,数据之间可以做关联操作
- 主流的RDBMS软件:MySQL、MariaDB、Oracle、DB2、SQL Server、PostgreSQL
② NoSQL
- 泛指非关系型数据库:Not Only SQL(意为"不仅仅是SQL")
- 不需要预先定义数据存储结构,每条记录可以有不同的数据类型和字段个数
- 在一些数据库结构经常变化,数据结构不定的系统中,就非常适合使用NoSQL来存储。比如监控系统中的监控信息的存储,可能每种类型的监控信息都不太一样。这样可以避免经常对MySQL进行表结构调整,增加字段带来的性能问题。
- 这种架构的缺点就是数据直接存储在NoSQL中,不能做关系数据库的复杂查询,如果由于需求变更,需要进行某些查询,可能无法满足,所以采用这种架构的时候需要确认未来是否会进行复杂关系查询以及如何应对。
- 主流NoSQL软件:Redis、Memcached、MongoDB、CouchDB、Neo4j、FlockDB
2、NoSQL的主要特点:
- 非关系型:
NoSQL 数据库不使用 SQL 作为查询语言,而是使用其他查询语言或 API。
数据模型通常是非关系型的,如键值对、文档、列族、图等。
- 分布式:
NoSQL 数据库通常设计为分布式系统,能够水平扩展,支持大规模数据存储和高并发访问。
支持数据分片(Sharding)和复制(Replication),提高系统的可用性和性能。
- 灵活性:
NoSQL 数据库通常具有动态模式(Schema),允许在运行时添加或修改数据结构,而不需要停机或重新设计数据库。
适合处理半结构化或非结构化数据。
- 高性能:
由于去除了复杂的关系模型和事务处理,NoSQL 数据库通常具有更高的读写性能。
适合处理实时数据分析和高吞吐量的应用场景。
- 高可用性:
NoSQL 数据库通常设计为高可用性,支持自动故障转移和数据冗余。
适合需要持续在线服务的应用场景。
3、常见 NoSQL 数据库类型:
键值存储(Key-Value Stores):
- 数据模型:键值对。
- 示例:Redis、Riak。
文档存储(Document Stores):
- 数据模型:文档(通常是 JSON 或 BSON 格式)。
- 示例:MongoDB、CouchDB。
列族存储(Column Family Stores):
- 数据模型:列族(Column Families)。
- 示例:Apache Cassandra、HBase。
图数据库(Graph Databases):
- 数据模型:图结构(节点、边、属性)。
- 示例:Neo4j、OrientDB。
二、Redis介绍
Redis(Remote Dictionary Server)是一个开源的、高性能的键值存储系统,通常被称为数据结构服务器。它支持多种数据结构,如字符串(String)、哈希(Hash)、列表(List)、集合(Set)、有序集合(Sorted Set)等。Redis 的设计目标是提供极高的性能和灵活性,适用于多种应用场景,如缓存、消息队列、实时分析等。
官方网站:www.redis.cn/
主要特点:
高性能:
- Redis 是内存数据库,数据存储在内存中,因此读写速度非常快。
- 支持持久化(Persistence),可以将数据定期保存到磁盘,防止数据丢失。
数据结构丰富:
- 支持多种数据结构,如字符串、哈希、列表、集合、有序集合等。
- 每种数据结构都有丰富的操作命令,如增删改查、排序、聚合等。
原子操作:
- Redis 的所有操作都是原子的,适合处理并发访问。
- 支持事务(Transaction),可以将多个命令打包成一个事务执行。
高可用性:
- 支持主从复制(Master-Slave Replication),提高系统的可用性和读写性能。
- 支持哨兵(Sentinel)机制,自动监控和管理 Redis 集群,实现故障转移。
分布式:
- 支持集群(Cluster)模式,可以将数据分布到多个节点,实现水平扩展。
- 支持分片(Sharding),可以将数据分散到不同的节点,提高系统的吞吐量。
灵活性:
- 支持多种编程语言的客户端库,如 Python、Java、Node.js 等。
- 支持多种持久化方式,如 RDB(快照)和 AOF(日志)。
应用场景:
- 作为缓存层,存储频繁访问的数据,减少数据库的负载。适合处理高并发的读写请求,如 Web 应用的会话管理、页面缓存等。
- 作为轻量级的消息队列,支持发布/订阅(Pub/Sub)模式。适合处理实时消息传递,如聊天系统、实时通知等。
- 作为实时数据存储和分析平台,支持高效的聚合操作。适合处理实时统计、排行榜、计数器等。
- 作为会话存储,存储用户会话数据,支持分布式会话管理。适合处理分布式应用的会话管理,如 Web 应用、移动应用等。
- 作为分布式锁服务,支持高效的锁机制,防止并发冲突。适合处理分布式系统的并发控制,如分布式任务调度、资源管理等。
1、部署Redis服务
搭建Redis服务的三个主要步骤:安装gcc编译器、编译安装、添加PATH环境变量、初始化启动服务
- 源码包目录:redis-4.0.8/
- 执行程序路径:/usr/local/redis/bin
- 初始化配置脚本:redis-4.0.8/utils/install_server.sh
- 服务启动脚本:/etc/init.d/redis_6379
- 数据库数据目录:/var/lib/redis/6379/
步骤1:部署redis服务(参考:/linux-soft/4/redis-4.0.8.tar.gz)
wget http://download.redis.io/releases/redis-4.0.8.tar.gz //没有安装包可联网下载
1)安装gcc编译器
[root@redis ~]# yum -y install gcc
2)解压redis-4.0.8.tar.gz压缩包
[root@redis ~]# tar -xf redis-4.0.8.tar.gz
[root@redis ~]# cd redis-4.0.8/
[root@redis redis-4.0.8]# ls
3)修改安装目录为/usr/local/redis/
[root@redis redis-4.0.8]# vim +27 src/Makefile
PREFIX?=/usr/local/redis
…
4)编译安装(解压后,已配置好Makefile,无需configure,直接make编译调用gcc)
[root@redis redis-4.0.8]# make && make install
5)将redis命令目录添加至PATH环境变量
[root@redis1 redis-4.0.8]# vim /etc/bashrc //尾部追加
export PATH=$PATH:/usr/local/redis/bin
[root@redis1 redis-4.0.8]# source /etc/bashrc //或者退出重新打开终端
6)初始化redis服务(直接回车采用默认值)
切换到源码目录下的utils,运行初始化配置脚本install_server.sh
[root@redis redis-4.0.8]# ./utils/install_server.sh
Welcome to the redis service installer
This script will help you easily set up a running redis server
Please select the redis port for this instance: [6379] //指定端口,Redis服务默认端口为6379
Selecting default: 6379
Please select the redis config file name [/etc/redis/6379.conf] //指定主配置文件
Selected default - /etc/redis/6379.conf
Please select the redis log file name [/var/log/redis_6379.log] //指定日志文件
Selected default - /var/log/redis_6379.log
Please select the data directory for this instance [/var/lib/redis/6379] //指定数据库目录
Selected default - /var/lib/redis/6379
Please select the redis executable path [/usr/local/redis/bin/redis-server] //指定程序调用
Selected config:
Port : 6379 //端口6379
Config file : /etc/redis/6379.conf //配置文件
Log file : /var/log/redis_6379.log //日志文件
Data dir : /var/lib/redis/6379 //数据目录
Executable : /usr/local/redis/bin/redis-server //启动程序目录
Cli Executable : /usr/local/redis/bin/redis-cli //命令行接口
Is this ok? Then press ENTER to go on or Ctrl-C to abort.
Copied /tmp/6379.conf => /etc/init.d/redis_6379 //服务启动脚本
Installing service...
Successfully added to chkconfig!
Successfully added to runlevels 345!
Starting Redis server...
Installation successful!
补充:初始化配置完成后,默认运行Redis服务;
常见报错: 未初始化redis或未启动服务,使用redis-cli提示服务拒绝访问
[root@redis1 ~]# redis-cli
Could not connect to Redis at 127.0.0.1:6379: Connection refused
Could not connect to Redis at 127.0.0.1:6379: Connection refused
not connected> exit
7)验证Redis服务
① 查看服务状态:
# 使用/etc/init.d/redis_6379路径方式查看服务状态
[root@redis ~]# /etc/init.d/redis_6379 status
Redis is running (5030)
# 使用service方式查看服务状态
[root@redis ~]# service redis_6379 status //以前版本没有systemctl,用的就是service
Redis is running (5030)
# 查看服务端口状态信息
[root@redis ~]# ss -nlptu | grep :6379
tcp LISTEN 0 128 127.0.0.1:6379 *:* users:(("redis-server",pid=5030,fd=6))
② 关闭服务:
# 使用/etc/init.d/redis_6379路径方式关闭服务
[root@redis ~]# service redis_6379 stop
Stopping ...
Redis stopped
# 使用service方式关闭服务
[root@redis ~]# /etc/init.d/redis_6379 stop
Stopping ...
Redis stopped
③ 开启服务:
# 使用/etc/init.d/redis_6379路径方式开启服务
[root@redis ~]# /etc/init.d/redis_6379 start
Starting Redis server...
# 使用service方式开启服务
[root@redis ~]# service redis_6379 start
Starting Redis server...
④ 重启服务:
# 使用/etc/init.d/redis_6379路径方式重启服务
[root@redis ~]# /etc/init.d/redis_6379 restart
Stopping ...
Redis stopped
Starting Redis server...
# 使用service方式重启服务
[root@redis ~]# service redis_6379 restart
Stopping ...
Redis stopped
Starting Redis server...
⑤ 连接Redis服务:
[root@redis ~]# redis-cli
127.0.0.1:6379> ping //测试服务,正常返回PONG
PONG
127.0.0.1:6379> exit
2、Redis数据类型
Redis数据类型包括:字符型、列表型、哈希型、集合型、有序集合型;
1. 字符型(String)
特点:最基本的数据类型,存储字符串、整数或浮点数。支持原子操作,如增减操作(INCR、DECR)。
SET key value:设置键值对。
GET key:获取键对应的值。
INCR key:将键对应的值加 1。
DECR key:将键对应的值减 1。
APPEND key value:将值追加到键对应的字符串末尾。
2. 列表型(List)
特点:存储有序的字符串列表,支持从两端插入和删除元素。适合实现队列、栈等数据结构。
LPUSH key value:将值插入到列表的头部。
RPUSH key value:将值插入到列表的尾部。
LPOP key:移除并返回列表头部的元素。
RPOP key:移除并返回列表尾部的元素。
LRANGE key start stop:获取列表中指定范围的元素。
3. 哈希型(Hash)
特点:存储字段和值的映射关系,适合存储对象。每个哈希可以存储多个字段-值对。
HSET key field value:设置哈希表中字段的值。
HGET key field:获取哈希表中字段的值。
HGETALL key:获取哈希表中所有字段和值。
HDEL key field:删除哈希表中的字段。
HINCRBY key field increment:将哈希表中字段的值增加指定的整数。
4. 集合型(Set)
特点:存储无序的字符串集合,不允许重复元素。支持集合运算,如并集、交集、差集。
SADD key member:向集合中添加元素。
SREM key member:从集合中移除元素。
SMEMBERS key:获取集合中的所有元素。
SISMEMBER key member:判断元素是否在集合中。
SUNION key1 key2:返回两个集合的并集。
5. 有序集合型(Sorted Set)
特点:存储有序的字符串集合,每个元素关联一个分数(score)。元素按分数从小到大排序,支持范围查询。
ZADD key score member:向有序集合中添加元素。
ZREM key member:从有序集合中移除元素。
ZRANGE key start stop:获取有序集合中指定范围的元素。
ZRANGEBYSCORE key min max:获取有序集合中分数在指定范围内的元素。
ZINCRBY key increment member:将有序集合中元素的分数增加指定的值。
1)字符串类型
字符串类型是 Redis 中最基本的数据类型,也是最常用的数据类型,它能存储任何形式的字符串,包括二进制数据;可以用其存储用户的邮箱、JSON 化的对象甚至是一张图片;一个字符串类型键允许存储的数据的最大容量是512 MB;字符串类型是其他4种数据类型的基础,其他数据类型和字符串类型的差别从某种角度来说只是组织字符串的形式不同;
— 字符串操作命令:
① 赋值与取值(SET / GET)
- 格式:SET key value [EX seconds] [PX milliseconds] [NX\XX]
- 格式:GET key
127.0.0.1:6379> SET username tom //为username赋值tom
OK
127.0.0.1:6379> KEYS * //查看当前Redis服务器下有哪些key键
1) "username"
127.0.0.1:6379> GET username //对username进行取值
"tom"
127.0.0.1:6379> GET password //取一个不存在的key值,提示(nil)
(nil) //类似python的none,MySQL的null
② 递增 / 递减数字(INCR / DECR)
字符串类型可以存储任何形式的字符串,当存储的字符串是整数形式时,Redis 提供了一个实用的命令INCR,其作用是让当前键值递增,并返回递增后的值;递减用DECR
- 格式:INCR key //递增,默认为0
- 格式:INCRBY key increment //指定数字递增
- 格式:DECR key //递减
- 格式:DECRBY key increment //指定数字递减
注意:当要操作的键不存在时会默认键值为0,所以第一次递增后的结果是1
例如:递增整数(INCR)
127.0.0.1:6379> INCR num //num不存在,默认值为0,第一次递增结果为1
(integer) 1
127.0.0.1:6379> INCR num //继续递增1
(integer) 2
127.0.0.1:6379> GET num
"2"
127.0.0.1:6379> KEYS *
1) "username"
2) "num"
例如:当键值的值不是整数时Redis会提示错误
127.0.0.1:6379> SET name bar
OK
127.0.0.1:6379> INCR name
(error) ERR value is not an integer or out of range //提示错误ERROR
127.0.0.1:6379> SET name 1
OK
127.0.0.1:6379> INCR name
(integer) 2
例如:增加指定的整数(INCRBY)
127.0.0.1:6379> GET num
"2"
127.0.0.1:6379> INCRBY num 2 //指定数字2 递增
(integer) 4
127.0.0.1:6379> INCRBY num 2
(integer) 6
127.0.0.1:6379> GET num
"6"
例如:递减整数(DECR)
127.0.0.1:6379> DECR num //不指定数字,默认为1
(integer) 5
例如:递减指定的整数(DECRBY)
127.0.0.1:6379> DECRBY num 2 //指定数字2 递减
(integer) 3
127.0.0.1:6379> GET num
"3"
③ 向尾部追加值(APPEND)
- 格式:APPEND key “追加value”
127.0.0.1:6379> SET hi hello
OK
127.0.0.1:6379> APPEND hi " world" //因为字符串包含空格,需要使用引号
(integer) 11 //返回值为hi的总长度
127.0.0.1:6379> GET hi
"hello world"
④ 获取字符串长度(STRLEN)
- 格式:STRLEN key
127.0.0.1:6379> STRLEN hi
(integer) 11
例如:中文字符返回字节数(1个汉字3个字节)
127.0.0.1:6379> SET name 张三
OK
127.0.0.1:6379> STRLEN name
(integer) 6
127.0.0.1:6379> GET name
"\xe5\xbc\xa0\xe4\xb8\x89"
UTF-8编码的中文,由于“张”和“三”两个字的UTF-8编码的长度都是3,所以此例中会返回6
⑤ 同时获得/设置多个键值(MSET/MGET)
- 格式:MSET key1 value1 [key2 value2…]
- 格式:MGET key1 [key2…]
127.0.0.1:6379> MSET username tom password 123456
OK
127.0.0.1:6379> MGET username password
1) "tom"
2) "123456"
⑥ 判断字段是否存在(EXISTS)
- 格式:EXISTS key [key…] //存在为1,不存在为0
127.0.0.1:6379> EXISTS num
(integer) 1 //true
127.0.0.1:6379> EXISTS number
(integer) 0 //false
⑦ 字段不存在时赋值(SETNX)
- 格式:SETNX key value
127.0.0.1:6379> GET num
"3"
127.0.0.1:6379> SETNX num 100 //赋值已存在的字段,反馈为0即false
(integer) 0
127.0.0.1:6379> GET num //赋值字段未改变
"3"
127.0.0.1:6379> SETNX number 100 //赋值不存在的字段,反馈为1即ture
(integer) 1
127.0.0.1:6379> GET number
"100"
SETNX赋值,可避免覆盖已有字段的值,直接使用SET会覆盖字段的值;
⑧ 查看所有的key(KEYS *)
- 格式:KEYS *
127.0.0.1:6379> KEYS *
1) "hi"
2) "username"
3) "name"
4) "number"
5) "password"
6) "num"
⑨ 删除字段(DEL)
- 格式:DEL key [key…]
127.0.0.1:6379> DEL number
(integer) 1
⑩ 查看键的类型(TYPE)
- 格式:TYPE key
127.0.0.1:6379> TYPE name
string
案例:字符串实践
Redis 对于键的命名并没有强制的要求,但比较好的实践是用“对象类型:对象ID:对象属性”来命名一个键;
例如:使用键【`user:1:friends`】来存储ID为1的用户的好友列表
127.0.0.1:6379> SET user:1:friends "tom,jerry,jack,rose"
OK
127.0.0.1:6379> GET user:1:friends
"tom,jerry,jack,rose"
例如:如果正在编写一个博客网站,博客的一个常见的功能是统计文章的访问量,我们可以为每篇文章使用一个名为【`post:文章ID:page.view`】的键来记录文章的访问量,每次访问文章的时候使用INCR命令使相应的键值递增。
假设:有用户访问文章ID号为42的博文,则将其访问计数加1
127.0.0.1:6379> INCR post:33:page.view
(integer) 1
127.0.0.1:6379> INCR post:33:page.view
(integer) 2
127.0.0.1:6379> GET post:33:page.view
"2"
2)散列类型
散列类型(hash)的键值也是一种字典结构,其存储了字段(field)和字段值的映射,即字典嵌套字典;字段值只能是字符串;散列类型适合存储对象。使用对象类别和 ID 构成键名,使用字段表示对象的属性,而字段值则存储属性值。
— 散列类型操作命令:
① 赋值与取值(HSET / HGET)(HMSET / HMGET)
- 格式:HSET key field value //单个字段赋值(子键、子值)
- 格式:HGET key field //单个字段取值
- 格式:HMSET key field1 value [field2 value…] //多个字段赋值(子键、子值)
- 格式:HMGET key field1 [field2…] //多个字段取值
例如:设置单个字段
127.0.0.1:6379> HSET user1 name ANJ //给user1键赋值(name为子键,ANJ为子值)
(integer) 1
127.0.0.1:6379> HSET user1 gender boy //给user1键赋值(gender为子键,boy为子值)
(integer) 1
127.0.0.1:6379> HGET user1 name //取user1键的name子键的值
"ANJ"
127.0.0.1:6379> HGET user1 gender //取user1键的gender子键的值
"boy"
例如:设置多个字段
127.0.0.1:6379> HMSET user1 email ANJ@tedu.cn phone 13545678987
OK
127.0.0.1:6379> HMGET user1 email phone
1) "ANJ@tedu.cn"
2) "13545678987"
② 获取所有字段(HGETALL)
- 格式:HGETALL key //奇数为key,偶数为value
127.0.0.1:6379> HGETALL user1
1) "name"
2) "ANJ"
3) "gender"
4) "boy"
5) "email"
6) "ANJ@tedu.cn"
7) "phone"
8) "13545678987"
③ 判断字段是否存在并赋值(HEXISTS)
- 格式:HEXISTS key field //字段存在为1,不存在为0
127.0.0.1:6379> HEXISTS user1 name
(integer) 1 //true
127.0.0.1:6379> HEXISTS user1 address
(integer) 0 //false
④ 判断当字段不存在时赋值(HSETNX)
- 格式:HSETNX key field value
127.0.0.1:6379> HSETNX user1 address beijing
(integer) 1
127.0.0.1:6379> HGET user1 address
"beijing"
127.0.0.1:6379> HSETNX user1 address shanghai
(integer) 0
127.0.0.1:6379> HGET user1 address
"beijing"
⑤ 数字递增(HINCRBY)
- 格式:HINCRBY key field increment
127.0.0.1:6379> HINCRBY user1 age 20
(integer) 20
127.0.0.1:6379> HINCRBY user1 age 1
(integer) 21
⑥ 删除字段(HDEL)
- 格式:HDEL key field [field…]
127.0.0.1:6379> HDEL user1 age
(integer) 1
127.0.0.1:6379> hget user1 age
(nil)
⑦ 只获取所有字段名(HKEYS)
- 格式:HKEYS key
127.0.0.1:6379> HKEYS user1
1) "name"
2) "gender"
3) "email"
4) "phone"
5) "address"
⑧ 只获取所有字段值(HVALS)
- 格式:HVALS key
127.0.0.1:6379> HVALS user1
1) "ANJ"
2) "boy"
3) "ANJ@tedu.cn"
4) "13545678987"
5) "beijing"
⑨ 获得字段数量(HLEN)
- 格式:HLEN key
127.0.0.1:6379> HLEN user1
(integer) 5
案例:散列类型实践:
例如:将文章ID号为10的文章以散列类型存储在Redis中
127.0.0.1:6379> HSET post:10 title Linux该怎么学
(integer) 1
127.0.0.1:6379> HGETALL post:10
1) "title"
2) "Linux\xe8\xaf\xa5\xe6\x80\x8e\xe4\xb9\x88\xe5\xad\xa6"
127.0.0.1:6379> HSET post:10 author ANJ
(integer) 1
127.0.0.1:6379> HMSET post:10 date 2021-06-17 summary 'Linux example'
OK
127.0.0.1:6379> HGETALL post:10
1) "title"
2) "Linux\xe8\xaf\xa5\xe6\x80\x8e\xe4\xb9\x88\xe5\xad\xa6"
3) "author"
4) "ANJ"
5) "date"
6) "2021-06-17"
7) "summary"
8) "Linux example"
127.0.0.1:6379>
3)列表类型
列表类型(list)可以存储一个有序的字符串列表;常用的操作是向列表两端添加元素,或者获得列表的某一个片段;列表类型内部是使用双向链表(double linked list)实现的,获取越接近两端的元素速度就越快;使用链表的代价是通过索引(index)访问元素比较慢;这种特性使列表类型能非常快速地完成关系数据库难以应付的场景:如社交网站的新鲜事,我们关心的只是最新的内容,使用列表类型存储,即使新鲜事的总数达到几千万个,获取其中最新的100条数据也是极快的。
— 列表类型操作命令:
① LPUSH命令用来向列表左边增加元素,返回值表示增加元素后列表的长度(LPUSH)
- 格式:LPUSH key value1 [value2…]
- 格式:LRANGE key 起始下标 结束下标
127.0.0.1:6379> LPUSH numbers 1
(integer) 1
127.0.0.1:6379> LPUSH numbers 2 3 //追加插入,不会覆盖之前的元素
(integer) 3
127.0.0.1:6379> LRANGE numbers 0 -1 //起始下标为0,结束下标为-1
1) "3"
2) "2"
3) "1"
② RPUSH命令用来向列表右边增加元素,返回值表示增加元素后列表的长度(RPUSH)
- 格式:RPUSH key value1 [value2…]
127.0.0.1:6379> RPUSH numbers 5 6
(integer) 5
127.0.0.1:6379> LRANGE numbers -2 -1 //或者起始下标为3,结束下标为-1
1) "5"
2) "6"
③ 取出列表所有元素(LANGE)
- 格式:LRANGE key 起始下标 结束下标 //起始下标为0,结束下标为-1
127.0.0.1:6379> LRANGE numbers 0 -1
1) "3"
2) "2"
3) "1"
4) "5"
5) "6"
④ 从列表两端移除元素(LPOP / RPOP)
# 从列表左边移除元素
- 格式:LPOP key
127.0.0.1:6379> LPOP numbers
"3"
127.0.0.1:6379> LRANGE numbers 0 -1
1) "2"
2) "1"
3) "5"
4) "6"
# 从列表右边移除元素
- 格式:RPOP key
127.0.0.1:6379> RPOP numbers
"6"
127.0.0.1:6379> LRANGE numbers 0 -1
1) "2"
2) "1"
3) "5"
⑤ 获取列表中元素的个数(LLEN)
- 格式:LLEN key
127.0.0.1:6379> LLEN numbers
(integer) 3
⑥ 删除列表中指定的值(LREM)
- 格式:LREM key count value
# LREM命令会删除列表中count值为value的元素,返回值是实际删除的元素个数。根据count值的不同,LREM命令的执行方式会略有差异。
- 当count > 0时,LREM命令会从列表左边开始删除count值为value的元素。
- 当count < 0时,LREM命令会从列表右边开始删除count值为value的元素。
- 当count = 0时,LREM命令会删除所有值为value的元素。
127.0.0.1:6379> LPUSH numbers 0 1 2 0 1 2 0 1 2 //numbers左边增加
(integer) 12
127.0.0.1:6379> RPUSH numbers 2 1 0 2 1 0 2 1 0 //numbers右边增加
(integer) 21
127.0.0.1:6379> LRANGE numbers 0 -1
1) "2"
2) "1"
3) "0"
4) "2"
5) "1"
6) "0"
7) "2"
8) "1"
9) "0"
10) "2"
11) "1"
12) "5"
13) "2"
14) "1"
15) "0"
16) "2"
17) "1"
18) "0"
19) "2"
20) "1"
21) "0"
例如:从左侧开始删除2个1
127.0.0.1:6379> LREM numbers 2 1 //count为2(>0 列表左边) value为1
(integer) 2
127.0.0.1:6379> LRANGE numbers 0 -1
1) "2"
2) "0"
3) "2"
4) "0"
5) "2"
6) "1"
7) "0"
8) "2"
9) "1"
10) "5"
11) "2"
12) "1"
13) "0"
14) "2"
15) "1"
16) "0"
17) "2"
18) "1"
19) "0"
例如:从右侧开始删除2个0
127.0.0.1:6379> LREM numbers -2 0 //count为-2(<0 列表右边) value为0
(integer) 2
127.0.0.1:6379> LRANGE numbers 0 -1
1) "2"
2) "0"
3) "2"
4) "0"
5) "2"
6) "1"
7) "0"
8) "2"
9) "1"
10) "5"
11) "2"
12) "1"
13) "0"
14) "2"
15) "1"
16) "2"
17) "1"
例如:删除所有的0
127.0.0.1:6379> LREM numbers 0 0 //count为0(=0 列表所有) value为0
(integer) 4
127.0.0.1:6379> LRANGE numbers 0 -1
1) "2"
2) "2"
3) "2"
4) "1"
5) "2"
6) "1"
7) "5"
8) "2"
9) "1"
10) "2"
11) "1"
12) "2"
13) "1"
⑦ 获得 / 设置指定索引的元素值(LINDEX、LSET)
- 格式:LINDEX key index
例如:获取numbers列表中下标为0的值
127.0.0.1:6379> LINDEX numbers 0
"2"
- 格式:LSET key index value
例如:设置下标为1的值为10
127.0.0.1:6379> LSET numbers 1 10
OK
127.0.0.1:6379> LRANGE numbers 0 1
1) "2"
2) "10"
⑧ 删除指定范围之外的所有元素(LTRIM)
- 格式:LTRIM key 起始下标 结束下标
127.0.0.1:6379> LTRIM numbers 0 2 //删除指定索引0到索引2之外的所有元素
OK
127.0.0.1:6379> LRANGE numbers 0 -1
1) "2"
2) "10"
3) "2"
⑨ 插入元素(LINSERT)
- 格式:LINSERT key before pivot value
- 格式:LINSERT key after pivot value
例如:在已存在的元素10的前面插入20
127.0.0.1:6379> LINSERT numbers before 10 20
(integer) 4
127.0.0.1:6379> LRANGE numbers 0 -1
1) "2"
2) "20"
3) "10"
4) "2"
例如:在已存在的元素2的后面挺入30
127.0.0.1:6379> LINSERT numbers after 2 30
(integer) 5
127.0.0.1:6379> LRANGE numbers 0 -1
1) "2"
2) "30"
3) "20"
4) "10"
5) "2"
案例:列表类型实践:
例如:记录最新的10篇博文
127.0.0.1:6379> LPUSH post:list 11 12 13
(integer) 3
127.0.0.1:6379> LRANGE post:list 0 -1
1) "13"
2) "12"
3) "11"
4)集合类型
集合中的每个元素都是不同的(去重),且没有顺序
① 增加元素(SADD)
- - 格式:SADD key member1 [member2…]
- - 格式:SMEMBERS key //查看集合元素
127.0.0.1:6379> SADD letters a b c
(integer) 3
127.0.0.1:6379> SADD letters b c d
(integer) 1
127.0.0.1:6379> SMEMBERS letters //相同元素不会重复
1) "d"
2) "b"
3) "a"
4) "c"
② 删除元素(SREM)
- 格式:SREM key member1 [member2…]
127.0.0.1:6379> SREM letters a c
(integer) 2
127.0.0.1:6379> SMEMBERS letters
1) "d"
2) "b"
③ 判断元素是否在集合中(SISMEMBER)
- 格式:SISMEMBER key member //0表示不存在,1表示存在
127.0.0.1:6379> SISMEMBER letters a
(integer) 0
127.0.0.1:6379> SISMEMBER letters b
(integer) 1
④ 集合运算(SINTER、SUNION、SDIFF)
- 格式:SINTER key1 key2 //字段之间的交集
- 格式:SUNION key1 key2 //字段之间的并集
- 格式:SDIFF key1 key2 //字段之间的差集
127.0.0.1:6379> SADD s1 a b c
(integer) 3
127.0.0.1:6379> SADD s2 b c d
(integer) 3
127.0.0.1:6379> SINTER s1 s2 //交集
1) "c"
2) "b"
127.0.0.1:6379> SUNION s1 s2 //并集
1) "c"
2) "a"
3) "d"
4) "b"
127.0.0.1:6379> SDIFF s1 s2 //差集,s1有,s2没有
1) "a"
127.0.0.1:6379> SDIFF s2 s1 //差集,s2有,s1没有
1) "d"
⑤ 获得集合中元素个数(SCARD)
- 格式:SCARD key
127.0.0.1:6379> SCARD letters
(integer) 2
⑥ 随机获得集合中的元素(SRANDMEMBER)
- 格式:SRANDMEMBER key count //在集合key中随机取出n个不同元素
- 格式:SRANDMEMBER key -count //在集合key中随机取出n个可能相同元素
例如:在集合s1中随机取出两个不同元素
127.0.0.1:6379> SMEMBERS s1
1) "c"
2) "a"
3) "b"
127.0.0.1:6379> SRANDMEMBER s1 2
1) "a"
2) "b"
例如:在集合s1中随机取出两个有可能相同元素
127.0.0.1:6379> SRANDMEMBER s1 -2
1) "c"
2) "c"
127.0.0.1:6379> SRANDMEMBER s1 -2
1) "a"
2) "b"
⑦ 集合中随机移除一个元素(SPOP)
- 格式:SPOP key [count]
127.0.0.1:6379> SPOP s1
"a"
127.0.0.1:6379> SMEMBERS s1
1) "c"
2) "b"
案例:集合实践:
例如:为文章号为10的博客文章添加标签
127.0.0.1:6379> SADD post:10:tags python redis nginx
(integer) 3
127.0.0.1:6379> SMEMBERS post:10:tags
1) "redis"
2) "python"
3) "nginx"
5)有序集合类型
在集合类型的基础上有序集合类型为集合中的每个元素都关联了一个分数,这使得我们不仅可以完成插入、删除和判断元素是否存在等集合类型支持的操作,还能够获得分数最高(或最低)的前N个元素、获得指定分数范围内的元素等与分数有关的操作;虽然集合中每个元素都是不同的,但是它们的分数却可以相同。有序集合类型在某些方面和列表类型有些相似:
- ① 二者都是有序的;
- ② 二者都可以获得某一范围的元素;
- 有序集合类型和列表也有着很大的区别,这使得它们的应用场景也是不同的:
- ① 列表类型是通过链表实现的,获取靠近两端的数据速度极快,而当元素增多后,访问中间数据的速度会较慢,所以它更加适合实现如“新鲜事”或“日志”这样很少访问中间元素的应用
- ② 有序集合类型是使用散列表和跳跃表(Skip list)实现的,所以即使读取位于中间部分的数据速度也很快
- ③ 列表中不能简单地调整某个元素的位置,但是有序集合可以(通过更改这个元素的分数)
- ④ 有序集合要比列表类型更耗费内存
— 有序集合类型操作
① 增加、替换元素(ZADD、ZRANGE)
# ZADD 命令用来向有序集合中加入一个元素和该元素的分数,如果该元素已存在则会用新的分数替换原有的分数。ZADD命令的返回值是新加入到集合中的元素个数;
- 格式:ZADD key score1 member1 [score2 member2…]
- 格式:ZRANGE key 起始下标 结束下标 [withscores 显示score分数]
127.0.0.1:6379> ZADD scores 88 tom 90 jerry 75 bob 92 alice
(integer) 4
127.0.0.1:6379> ZRANGE scores 0 -1 //按照分数排序
1) "bob"
2) "tom"
3) "jerry"
4) "alice"
127.0.0.1:6379> ZRANGE scores 0 -1 withscores //奇数为元素,偶数为分数
1) "bob"
2) "75"
3) "tom"
4) "88"
5) "jerry"
6) "90"
7) "alice"
8) "92"
127.0.0.1:6379> ZADD scores 85 jerry //对已存在的元素替换分数
(integer) 0
127.0.0.1:6379> ZRANGE scores 0 -1 withscores
1) “bob”
2) “75”
3) “jerry”
4) “85”
5) “tom”
6) “88”
7) “alice”
8) “92”
② 获得元素的分数(ZSCORE)
- 格式:ZSCORE key member
127.0.0.1:6379> ZSCORE scores tom
"88"
③ 获得指定分数范围的元素(ZRANGEBYSCORE)
- 格式:ZRANGEBYSCORE key min max [withscores]
127.0.0.1:6379> ZRANGEBYSCORE scores 80 90 withscores
1) "jerry"
2) "85"
3) "tom"
4) "88"
④ 增加某个元素的分数(ZINCRBY)
- 格式:ZINCRBY key increment member
127.0.0.1:6379> ZINCRBY scores 3 bob
"75"
127.0.0.1:6379> ZSCORE scores bob
"78"
⑤ 获得集合中元素的数量(ZCARD)
- 格式:ZCARD key
127.0.0.1:6379> ZCARD scores
(integer) 4
⑥ 获得指定分数范围内的元素个数(ZCOUNT)
- 格式:ZCOUNT key min max
127.0.0.1:6379> ZCOUNT scores 80 90
(integer) 2
⑦ 删除元素(ZREM)
- 格式:ZREM key member [member…]
127.0.0.1:6379> ZREM scores bob
(integer) 1
127.0.0.1:6379> ZRANGE scores 0 -1 withscores
1) "jerry"
2) "85"
3) "tom"
4) "88"
5) "alice"
6) "92"
⑧ 获得元素的排名(ZRANK / ZREVRANK)
- 格式:ZRANK key member //升序排列
- 格式:ZREVRANK key member //降序排列
127.0.0.1:6379> ZRANK scores tom //获取tom的排名
(integer) 1 //升序排列,从0开始计数
127.0.0.1:6379> ZREVRANK scores alice //获取alice的排名
(integer) 0 //降序排列,从0开始计数
案例:有序集合类型实践:
例如:将博客文章按照点击量排序
127.0.0.1:6379> ZADD posts:page.view 0 post:10:page.view
(integer) 1
127.0.0.1:6379> ZINCRBY posts:page.view 1 post:10:page.view
"1"
127.0.0.1:6379> ZRANGE posts:page.view 0 -1 withscores
1) "post:10:page.view"
2) "1"
三、Redis其他操作命令
1)数据操作命令
① 查看key过期时间
- 格式:TTL key
127.0.0.1:6379> TTL name
(integer) -1 //-1表示永不过期
② 设置key的过期时间(秒)
- 格式:EXPIRE key seconds
127.0.0.1:6379> EXPIRE password 10 //过期时间为10秒
(integer) 1
127.0.0.1:6379> TTL password //查看实时过期时间效果
(integer) 2
127.0.0.1:6379> TTL password
(integer) -2 //-2表示已过期
127.0.0.1:6379> EXISTS password //查看key是否存在
(integer) 0
127.0.0.1:6379> SAVE //存盘
OK
例如:创建key的同时,设置它的过期时间。key是user1:login,value是11111,超时时间15秒
127.0.0.1:6379> set user1:login 11111 ex 15 //ex为选项
OK
127.0.0.1:6379> TTL user1:login
(integer) 14
127.0.0.1:6379> TTL user1:login
(integer) 9
127.0.0.1:6379> TTL user1:login
(integer) 4
127.0.0.1:6379> TTL user1:login
(integer) -2
2)数据库操作命令
① 默认Redis有16个库,将name移动到1号库
- 格式:MOVE key db
127.0.0.1:6379> MOVE name 1
(integer) 1
② 选择库(类似mysql的use)
- 格式:SELECT index
127.0.0.1:6379> SELECT 1
OK
127.0.0.1:6379[1]> KEYS *
1) "name"
③ 清除库
# 清除当前库
- 命令:FLUSHDB
127.0.0.1:6379[1]> FLUSHDB
OK
127.0.0.1:6379[1]> KEYS *
(empty list or set)
# 清除所有库
- 命令:FLUSHALL
127.0.0.1:6379[1]> SELECT 0
OK
127.0.0.1:6379> KEYS *
1) "s1"
2) "numbe"
3) "scores"
4) "letters"
5) "user1"
6) "user:1:friends"
7) "post:10"
8) "password"
9) "numbers"
10) "num"
11) "username"
12) "number"
13) "hi"
14) "s2"
15) "post:33:page.view"
16) "port:10"
127.0.0.1:6379> FLUSHALL
OK
127.0.0.1:6379> KEYS *
(empty list or set)
④ 数据库内关闭Redis
- 命令:SHUTDOWN
127.0.0.1:6379> SHUTDOWN
not connected> exit
[root@redis redis-4.0.8]# service redis_6379 status
cat: /var/run/redis_6379.pid: No such file or directory
Redis is running ()
四、修改Redis服务运行参数
1)常用参数
- ① 监听地址:默认运行在127.0.0.1上
- ② 监听端口:默认监听6379端口(不用修改)
- ③ 密码
例如:
① 修改配置文件(/etc/redis/6379.conf)
[root@redis ~]# vim /etc/redis/6379.conf
# bind 127.0.0.1 //注释此行,运行在0.0.0.0上(即所有主机)
port 6379 //端口号
requirepass tedu.cn //密码
② 修改启动脚本,关机添加密码(不添加密码,关闭服务时提示认证失败)
[root@redis ~]# vim +43 /etc/init.d/redis_6379
...
$CLIEXEC -p $REDISPORT -a tedu.cn shutdown
...
③ 重新启动服务并测试
[root@redis redis-4.0.8]# service redis_6379 restart
Stopping ...
Redis stopped
Starting Redis server...
# 修改前:查看服务端口状态信息
[root@redis ~]# ss -nlptu | grep :6379
tcp LISTEN 0 128 127.0.0.1:6379 *:* users:(("redis-server",pid=5030,fd=6))
# 修改后:查看服务端口状态信息
[root@redis ~]# ss -nlptu | grep :6379
tcp LISTEN 0 128 *:6379 *:* users:(("redis-server",pid=14897,fd=7))
tcp LISTEN 0 128 :::6379 :::* users:(("redis-server",pid=14897,fd=6))
# 运行redis-cli并进行PING测试
[root@redis ~]# redis-cli
127.0.0.1:6379> ping
(error) NOAUTH Authentication required. //无认证
127.0.0.1:6379> exit
[root@redis ~]# redis-cli -a tedu.cn
127.0.0.1:6379> ping
PONG
127.0.0.1:6379> exit
五、部署支持php和Redis的Nginx服务器
网络实验拓扑:
服务器角色:nginx、redis
- Nginx:IP为192.168.2.10,编译安装nginx,配置php支持redis
- Redis:IP为192.168.2.11,安装Redis
步骤1:安装并配置Nginx服务器
① 安装编译器
[root@nginx ~]# yum -y install gcc pcre-devel zlib-devel
补充:zlib是实现压缩功能的,zlib-devel是开发版的zlib
② 编译安装nginx(参考:lnmp_soft.tar.gz)
[root@nginx lnmp_soft]# tar -xf nginx-1.12.2.tar.gz
[root@nginx lnmp_soft]# cd nginx-1.12.2/
[root@nginx nginx-1.12.2]# ./configure
[root@nginx nginx-1.12.2]# make && make install
③ 安装php-fpm
[root@nginx ~]# yum -y install php-fpm
[root@nginx ~]# systemctl start php-fpm.service
[root@nginx ~]# ss -nlptu | grep :9000
tcp LISTEN 0 128 127.0.0.1:9000 *:* users:(("php-fpm",pid=4049,fd=0),("php-fpm",pid=4048,fd=0),("php-fpm",pid=4047,fd=0)"php-fpm",pid=4046,fd=0),("php-fpm",pid=4045,fd=0),("php-fpm",pid=4044,fd=6))
④ 修改配置文件,开启动态页面解析功能
[root@nginx ~]# vim +65 /usr/local/nginx/conf/nginx.conf
…
location ~ \.php$ {
root html;
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
# fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name;
include fastcgi.conf;
}
…
⑤ 启动nginx服务
[root@nginx ~]# /usr/local/nginx/sbin/nginx -t //-t语法检查
nginx: the configuration file /usr/local/nginx/conf/nginx.conf syntax is ok
nginx: configuration file /usr/local/nginx/conf/nginx.conf test is successful
[root@nginx ~]# /usr/local/nginx/sbin/nginx //启动服务
[root@nginx ~]# ss -nlptu |grep :80
tcp LISTEN 0 128 *:80 *:* users:(("nginx",pid=4119,fd=6),("nginx",pid=4118,fd=6))
测试:
[root@nginx ~]# vim /usr/local/nginx/html/test.php
<?php
echo "Hello World!\n";
?>
[root@nginx ~]# curl http://127.0.0.1/test.php
Hello World!
步骤2:配置PHP支持Redis(需要对PHP进行扩展)
① 安装php扩展
[root@nginx ~]# yum install -y php-devel automake autoconf //简化源代码编译过程工具
② 安装php-redis(参考:/linux-soft/4/ php-redis-2.2.4.tar.gz)
[root@nginx ~]# tar -xf php-redis-2.2.4.tar.gz
[root@nginx ~]# cd phpredis-2.2.4/
[root@nginx phpredis-2.2.4]# ls
[root@nginx phpredis-2.2.4]# phpize //检测php环境,生成相应的configure命令及配置信息文件/usr/bin/php-config
Configuring for:
PHP Api Version: 20100412
Zend Module Api No: 20100525
Zend Extension Api No: 220100525
[root@nginx phpredis-2.2.4]# ls
[root@nginx phpredis-2.2.4]# ./configure --with-php-config=/usr/bin/php-config
[root@nginx phpredis-2.2.4]# make && make install
[root@nginx phpredis-2.2.4]# ls /usr/lib64/php/modules/ //模块安装目录
curl.so fileinfo.so json.so phar.so redis.so zip.so
补充:在使用php的过程中,常常需要去添加一些PHP扩展库。但重新对php进行编译是比较蛮烦,所以这时候可使用phpize对php进行添加扩展。并且phpize编译的扩展库可以随时启用或停用,比较灵活。其实phpize是一个运行脚本,主要作用是检测php的环境还有就是在特定的目录生成相应的configure文件,这样makeinstall之后,生成的.so文件才会自动加载到php扩展目录下面;
补充:--with-php-config,告诉phpize要建立基于哪个版本的扩展;
③ 修改php配置文件并重启服务
[root@nginx ~]# vim /etc/php.ini //在730行下添加
extension_dir = "/usr/lib64/php/modules" //指定php扩展模块目录
extension = "redis.so" //扩展模块文件.so
[root@nginx ~]# systemctl restart php-fpm
[root@nginx ~]# php -m | grep -i redis //查看模块
redis
测试:
① 创建脚本,写入redis数据库
[root@nginx ~]# vim /usr/local/nginx/html/set_redis.php
<?php
$redis = new redis();
$redis->connect("192.168.2.11", "6379");
$redis->auth("tedu.cn");
$redis->set("username", "tom");
?>
② 创建脚本,读取redis数据
[root@nginx ~]# vim /usr/local/nginx/html/get_redis.php
<?php
$redis = new redis();
$redis->connect("192.168.2.11", "6379");
$redis->auth("tedu.cn");
echo $redis->get("username");
?>
③ 访问测试页面
[root@nginx ~]# curl http://127.0.0.1/set_redis.php
[root@nginx ~]# curl http://127.0.0.1/get_redis.php
Tom
④ 在redis数据库上查看
[root@redis ~]# redis-cli -a tedu.cn
127.0.0.1:6379> KEYS *
1) "username"
127.0.0.1:6379> GET username
"tom"
扩展知识:redis 单线程的理解
- 单线程模型
Redis客户端对服务端的每次调用都经历了发送命令,执行命令,返回结果三个过程。其中执行命令阶段,由于Redis是单线程来处理命令的,所有每一条到达服务端的命令不会立刻执行,所有的命令都会进入一个队列中,然后逐个被执行。并且多个客户端发送的命令的执行顺序是不确定的。但是可以确定的是不会有两条命令被同时执行,不会产生并发问题,这就是Redis的单线程基本模型。
- redis单线程问题
单线程指的是网络请求模块使用了一个线程(所以不需考虑并发安全性),即一个线程处理所有网络请求,其他模块仍用了多个线程。
- 为什么说redis能够快速执行
- (1) 绝大部分请求是纯粹的内存操作(非常快速)
- (2) 采用单线程,避免了不必要的上下文切换和竞争条件
- (3) 非阻塞IO - IO多路复用,Redis采用epoll做为I/O多路复用技术的实现,再加上Redis自身的事件处理模型将epoll中的连接,读写,关闭都转换为了时间,不在I/O上浪费过多的时间。
Redis采用单线程模型,每条命令执行如果占用大量时间,会造成其他线程阻塞,对于Redis这种高性能服务是致命的,所以Redis是面向高速执行的数据库。
- redis的内部实现
内部实现采用epoll,采用了epoll+自己实现的简单的事件框架。epoll中的读、写、关闭、连接都转化成了事件,然后利用epoll的多路复用特性,绝不在io上浪费一点时间 这3个条件不是相互独立的,特别是第一条,如果请求都是耗时的,采用单线程吞吐量及性能可想而知了。应该说redis为特殊的场景选择了合适的技术方案。
- Redis关于线程安全问题
redis实际上是采用了线程封闭的观念,把任务封闭在一个线程,自然避免了线程安全问题,不过对于需要依赖多个redis操作的复合操作来说,依然需要锁,而且有可能是分布式锁。
思维导图:
小结:
本篇章节为【第四阶段】NOSQL-DAY1 的学习笔记,这篇笔记可以初步了解到 NoSQL概述(RDBMS、NoSQL)、部署Redis服务、Redis数据类型(字符串、散列类型、列表类型、集合类型、有序集合类型)、Redis其它操作命令、修改Redis服务运行参数、部署支持PHP和Redis的Nginx服务器。除此之外推荐参考相关学习网址:
- redis 单线程的理解 - myseries - 博客园
- www.redis.cn/
- 什么是phpize及其用法 - 侯蜀黍 - 博客园
Tip:毕竟两个人的智慧大于一个人的智慧,如果你不理解本章节的内容或需要相关笔记、视频,可私信小安,请不要害羞和回避,可以向他人请教,花点时间直到你真正的理解。