Nosql概述
为什么要用Nosql
单机版MySQL的年代!
90年代,一个基本的网站访问量一般不会太大,单个数据库完全足够!那个时候,更多的去使用静态网页 html ~ 服务器根本没有太大的压力!
思考一下,这种情况下:整个网站的瓶颈是什么?
- 数据量如果太大、一个机器放不下了!
- 数据的索引 (B+ Tree),一个机器内存也放不下
- 访问量(读写混合),一个服务器承受不了
Memcached(缓存) + MySQL + 垂直拆分 (读写分离)
网站80%的情况都是在读,每次都要去查询数据库的话就十分的麻烦!所以说我们希望减轻数据的压力,可以使用缓存来保证效率!
发展过程: 优化数据结构和索引—> 文件缓存(IO)—-> Memcached(当时最热门的技术! )
分库分表 + 水平拆分 + MySQL集群
技术和业务在发展的同时,对人的要求也越来越高!
本质:数据库(读,写)
早些年MyISAM: 表锁,十分影响效率!高并发下就会出现严重的锁问题
转战Innodb:行锁
慢慢的就开始使用分库分表来解决写的压力! MySQL 在哪个年代推出 了表分区!这个并没有多少公司使用!
MySQL 的集群,很好满足哪个年代的所有需求!
什么是NoSQL
NoSQL,泛指非关系型的数据库,NoSQL即Not-Only SQL(不仅仅是SQL) ,它可以作为关系型数据库的良好补充。随着互联网web2.0网站的兴起,非关系型的数据库现在成了一个极其热门的新领域,非关系数据库产品的发展非常迅速
很多的数据类型用户的个人信息,社交网络,地理位置。这些数据类型的存储不需要一个固定不需要多月的操作就可以横向扩展的 ! Map
了解3V+3高
大数据时代的3V:主要是描述问题的
- 海量 Volume
- 多样 Variety
- 实时 Velocity
大数据时代的3高:主要是对程序的要求
- 高并发
- 高可扩
- 高性能
真正在公司中的实践:NoSQL + RDBMS 一起使用才是最强的,阿里巴巴的架构演进!
NoSQL的四大分类
- 键值(Key-Value)存储数据库
这一类数据库主要会使用到一个哈希表,这个表中有一个特定的键和一个指针指向特定的数据。
Key/value模型对于IT系统来说的优势在于简单、易部署。但是如果DBA只对部分值进行查询或更新的时候,Key/value就
显得效率低下了
相关产品: Tokyo Cabinet/Tyrant、Redis、Voldemort、Berkeley DB
典型应用: 内容缓存,主要用于处理大量数据的高访问负载。
数据模型: 一系列键值对
优势: 快速查询
劣势: 存储的数据缺少结构化
- 文档型数据库(bson格式 和json一样)
文档型数据库的灵感是来自于Lotus Notes办公软件的,而且它同第一种键值存储相类似。该类型的数据模型是版本
化的文档,半结构化的文档以特定的格式存储,比如JSON。文档型数据库可 以看作是键值数据库的升级版,允许之间嵌套
键值。而且文档型数据库比键值数据库的查询效率更高。
相关产品:CouchDB、MongoDB
典型应用:Web应用(与Key-Value类似,Value是结构化的)
数据模型: 一系列键值对
优势:数据结构要求不严格
劣势: 查询性能不高,而且缺乏统一的查询语法
- 列存储数据库
这部分数据库通常是用来应对分布式存储的海量数据。键仍然存在,但是它们的特点是指向了多个列。这些列是由列家
族来安排的。
相关产品:Cassandra, HBase, Riak
典型应用:分布式的文件系统
数据模型:以列簇式存储,将同一列数据存在一起
优势:查找速度快,可扩展性强,更容易进行分布式扩展
劣势:功能相对局限
- 图关系数据库
图形结构的数据库同其他行列以及刚性结构的SQL数据库不同,它是使用灵活的图形模型,并且能够扩展到多个服务器
上。NoSQL数据库没有标准的查询语言(SQL),因此进行数据库查询需要制定数据模型。许多NoSQL数据库都有REST式的数
据接口或者查询API。
相关数据库:Neo4J、InfoGrid、Infinite Graph
典型应用:社交网络
数据模型:图结构
优势:利用图结构相关算法。
劣势:需要对整个图做计算才能得出结果,不容易做分布式的集群方案。
总结
因此,我们总结NoSQL数据库在以下的这几种情况下比较适用:
- 数据模型比较简单;
- 需要灵活性更强的IT系统;
- 对数据库性能要求较高;
- 不需要高度的数据一致性;
- 对于给定key,比较容易映射复杂值的环境
NoSQL数据库的产生就是为了解决大规模数据集合多重数据种类带来的挑战,尤其是大数据应用难题(高并发)。
Redis初始与安装
Redis概述
- Redis概述?
在我们日常的Java Web开发中,无不都是使用数据库来进行数据的存储,由于一般的系统任务中通常不会存在高并发的情况,所以这样看起来并没有什么问题,可是一旦涉及大数据量的需求,比如一些商品抢购的情景,或者是主页访问量瞬间较大的时候,单一使用数据库来保存数据的系统会因为面向磁盘,磁盘读/写速度比较慢的问题而存在严重的性能弊端,一瞬间成千上万的请求到来,需要系统在极短的时间内完成成千上万次的读/写操作,这个时候往往不是数据库能够承受的,极其容易造成数据库系统瘫痪,最终导致服务宕机的严重生产问题。
- 什么是Redis ?
Redis :REmote DIctonary Server(远程字典服务器)是完全开源免费的, 用C语言编写的,遵守BSD协议,是一个高性能的 (key-value) 分布式内存数据库,基于内存运行并支持持久化的NoSQL数据库, 是当前最热门的NoSQL数据库之一,也被人们称为数据结构服务器。
- Redis 可以干什么
- 存储和持久化:redis支持异步将内存中的数据写到硬盘上,同时不影响继续服务
- 取最新N个数据的操作,如:可以将最新的10条评论的ID放在Redis的List集合里面
- 模拟类似于HttpSession这种需要设定过期时间的功能
- 发布、订阅消息系统
- 定时器、计数器
- Redis去哪下载和参考
- http://www.redis.cn (英文官网)
- http://www.redis.cn (中文官网)
- http://redisdoc.com/ (Redis 命令参考)
- https://redis.net.cn (很好的一个教程网站)
- Redis 优点
- 性能极高 :Redis能读的速度是110000次/s,写的速度是81000次/s 。
- 丰富的数据类型 : Redis支持二进制案例的 Strings, Lists, Hashes, Sets 及 Ordered Sets 数据类型操作。
- 原子 : Redis的所有操作都是原子性的,意思就是要么成功执行要么失败完全不执行。单个操作是原子性的。多个操作也支持事务,即原子性,通过MULTI和EXEC指令包起来。
- 丰富的特性 : Redis还支持 publish/subscribe, 通知, key 过期等等特性。
- Redis 开发经验
由于企业里面做Redis开发,99%都是Linux版的运用和安装,几乎不会涉及到Windows版,Windows版不作为重点,小伙伴们可以下去自己玩,企业实战就认一个版:Linux。所以说我们也直接学习Linux上用Redis,Windows上不做多解释。
Redis 安装和配置
环境介绍
- Linux (CentOs7)
- Redis 5.0.8 ( 下载地址:https://redis.io/download )
检查 Linux下安装 gcc 环境
可以通过rpm -qa | grep gcc
来查询是否已经安装了gcc。如果没有 gcc 环境进行安装
yum install gcc-c++
安装包下载地址https://redis.io/,下载获得 redis-5.0.8.tar.gz 后将它 放入我们的 Linux 目录 /opt(放入哪个目录纯属自己的爱好)
或者直接在终端下载
wget https://github.com/antirez/redis/archive/5.0.8.tar.gz
解压redis5.0.8压缩包进入解压后的文件,可以看到我们redis的配置文件。
进入/opt目录下,解压命令:
tar -zxvf redis-5.0.8.tar.gz
解压完成后出现文件夹:redis-5.0.8
。进入目录:cd redis-5.0.8
在redis-5.0.8
目录下执行 make
命令进行编译
Redis的默认安装路径 /usr/local/bin
将redis配置文件。复制到我们当前目录下
redis默认不是后台启动的,修改配置文件!
在我们启动之前,需要先做一个简单的配置:修改 redis.conf 文件,将里面的 daemonize no
改成 yes
,让服务在后台启动,如下:
启动Redis服务!
使用redis-cli 进行连接测试!
查看redis的进程是否开启!
如何关闭Redis服务呢?
shutdown
再次查看进程是否存在
OK ,至此,我们的 redis 就算安装成功了。
测试性能
redis-benchmark 是一个压力测试工具!官方自带的性能测试工具!
redis-benchmark 命令参数:
我们来简单测试下:
# 测试:100个并发连接 100000请求
redis-benchmark -h localhost -p 6379 -c 100 -n 100000
基础的知识
Redis默认有16个数据库
默认使用的是第0个
可以使用 select 进行切换数据库!
127.0.0.1:6379> select 3 # 切换数据库
OK
127.0.0.1:6379[3]> DBSIZE # 查看数据库大小
(integer) 0
查看所有键
127.0.0.1:6379[3]> keys * # 查看数据库所有的key
1) "name"
清除当前数据库 flushdb
127.0.0.1:6379[3]> flushdb
OK
127.0.0.1:6379[3]> keys *
(empty list or set)
清除全部数据库的内容 FLUSHALL
127.0.0.1:6379[3]> FLUSHALL
OK
127.0.0.1:6379[3]> keys *
(empty list or set)
Redis数据类型
Redis-Key
由于五大数据类型的数据结构本身有差异,因此对应的命令也会不同,但是有一些命令不管对于哪种数据类型都是存在的,我们今天就先来看看这样一些特殊的命令。
Redis 键命令用于管理 redis 的键。 下面说一点常用的命令,其它的点击标题进入官网查看:
而key是不忽略大小写的。
127.0.0.1:6379> keys * # 查看所有的key
(empty list or set)
127.0.0.1:6379> set name chen # set key
OK
127.0.0.1:6379> keys *
1) "name"
127.0.0.1:6379> set age 1
OK
127.0.0.1:6379> keys *
1) "age"
2) "name"
127.0.0.1:6379> EXISTS name # 判断当前的key是否存在
(integer) 1
127.0.0.1:6379> EXISTS name1
(integer) 0
127.0.0.1:6379> move name 1 # 移除当前的key
(integer) 1
127.0.0.1:6379> keys *
1) "age"
127.0.0.1:6379> set name chen
OK
127.0.0.1:6379> keys *
1) "age"
2) "name"
127.0.0.1:6379> clear # 清除
127.0.0.1:6379> keys *
1) "age"
2) "name"
127.0.0.1:6379> get name
"qinjiang"
127.0.0.1:6379> EXPIRE name 10 # 设置key的过期时间,单位是秒
(integer) 1
127.0.0.1:6379> ttl name # 查看当前key的剩余时间
(integer) 4
127.0.0.1:6379> ttl name
(integer) 3
127.0.0.1:6379> ttl name
(integer) 2
127.0.0.1:6379> ttl name
(integer) 1
127.0.0.1:6379> ttl name
(integer) -2
127.0.0.1:6379> get name
(nil)
127.0.0.1:6379> type name # 查看当前key的一个类型!
string
127.0.0.1:6379> type age
string
1、DEL 命令
- 通过 DEL 命令我们可以删除一个已经存在的 key; 如果键被删除,它将给出输出1,否则它将为0:
2、TYPE 命令
- TYPE key 返回 key 所储存的值的类型。
3、DUMP 命令
- DUMP 命令可以序列化给定的 key,并返回序列化之后的值:
4、EXISTS 命令
- EXISTS 命令用来检测一个给定的 key 是否存在:
5、TTL 命令
- TTL 命令可以查看一个给定 key 的有效时间:
- -2 表示 key 不存在或者已过期;-1 表示 key 存在并且没有设置过期时间(永久有效)。当然,我们可以通过下面的命令给 key 设置一个过期时间:
6、EXPIRE 命令
- EXPIRE 命令可以给 key 设置有效期,在有效期过后,key 会被销毁。
7、PERSIST 命令
- PERSIST 命令表示移除一个 key 的过期时间,这样该 key 就永远不会过期:
8、PEXPIRE 命令
- PEXPIRE 命令的功能和 EXPIRE 命令的功能基本一致,只不过这里设置的参数是毫秒:
9、PTTL 命令
- PTTL 命令和 TTL 命令基本一致,只不过 PTTL 返回的是毫秒数:
10、KEYS 命令
- KEYS 命令可以获取满足给定模式的所有 key,
- KEYS 表示获取所有的 KEY, 也可以是一个正则表达式。
后面如果遇到不会的命令,可以在官网查看帮助文档!
哪里去获得redis常见数据类型操作命令
- http://redisdoc.com/ (Redis 命令参考)
五大数据类型介绍
Redis 中的数据都是以 key/value 的形式存储的,五大数据类型主要是指 value 的数 据类型,包含如下五种:
- STRING (Strng)
STRING 是 redis 中最基本的数据类型,redis 中的 STRING 类型是二进制安全的, 即它可以包含任何数据,比如一个序列化的对象甚至一个 jpg 图片,要注意的是 redis 中的字符串大小上限是 512M 。
- LIST (list)
LIST 是一个简单的字符串列表,按照插入顺序进行排序,我们可以从 LIST 的头部 (LEFT) 或者尾部 (RIGHT) 插入一个元素,也可以从 LIST 的头部(LEFT)或者尾部 (RIGHT) 弹出一个元素。
- HASH (hash)
HASH 类似于 Java 中的 Map ,是一个键值对集合,在 redis 中可以用来存储对象。
- SET(set)
SET 是 STRING 类型的无序集合,不同于 LIST ,SET 中的元素不可以重复。
- ZSET (zset)
ZSET 和 SET 一样,也是 STRING 类型的元素的集合,不同的是 ZSET 中的每个元素 都会关联一个 double 类型的分数,ZSET 中的成员都是唯一的,但是所关联的分数 可以重复。
String(字符串)
赋值语法:
SET KEY_NAME VALUE: (说明:多次设置name会覆盖) (Redis SET 命令用于设置给定 key 的值。如果 key已经存储值, SET 就覆写旧值,且无视类型)
命令:
SETNX key1 value:(not exist) 如果key1不存在,则设值 并返回1。如果key1存在,则不设值并返回0;(解决分
布式锁 方案之一,只有在 key 不存在时设置 key 的值。Setnx(SET if Not eXists) 命令在指定的 key 不存在
时,为 key 设置指定的值)
SETEX key1 10 lx :(expired) 设置key1的值为lx,过期时间为10秒,10秒后key1清除(key也清除)
SETRANGE string range value: 替换字符串
取值语法:
GET KEY_NAME :Redis GET命令用于获取指定 key 的值。如果 key 不存在,返回 nil 。如果key 储存的值不是字
符串类型,返回一个错误。
GETRANGE key start end :用于获取存储在指定 key 中字符串的子字符串。字符串的截取范围由 start 和 end
两个偏移量决定(包括 start 和 end 在内)
GETBIT key offset :对 key 所储存的字符串值,获取指定偏移量上的位(bit)
GETSET语法: GETSET KEY_NAME VALUE :Getset 命令用于设置指定 key 的值,并返回 key 的旧值,当 key
不存在时,返回 nil
STRLEN key :返回 key 所储存的字符串值的长度
删值语法:
DEL KEY_Name :删除指定的KEY,如果存在,返回值数字类型。
批量写:MSET k1 v1 k2 v2 ... 一次性写入多个值
批量读:MGET k1 k2 k3
GETSET name value :一次性设值和读取(返回旧值,写上新值)
自增/自减:
INCR KEY_Name :Incr 命令将 key 中储存的数字值增1。如果 key 不存在,那么 key 的值会先被初始化为 0 ,然
后再执行 INCR 操作
自增:INCRBY KEY_Name :增量值 Incrby 命令将 key 中储存的数字加上指定的增量值
自减:DECR KEY_NAME 或 DECYBY KEY_NAME 减值 :DECR 命令将 key 中储存的数字减1
:(注意这些 key 对应的必须是数字类型字符串,否则会出错,)
字符串拼接:APPEND KEY_NAME VALUE
:Append 命令用于为指定的 key 追加至未尾,如果不存在,为其赋值
字符串长度 :STRLEN key
##########################################################################
127.0.0.1:6379> set key1 v1 # 设置值
OK
127.0.0.1:6379> get key1 # 获得值
"v1"
127.0.0.1:6379> keys * # 获得所有的key
1) "key1"
127.0.0.1:6379> EXISTS key1 # 判断某一个key是否存在
(integer) 1
127.0.0.1:6379> APPEND key1 "hello" # 追加字符串,如果当前key不存在,就相当于setkey
(integer) 7
127.0.0.1:6379> get key1
"v1hello"
127.0.0.1:6379> STRLEN key1 # 获取字符串的长度!
(integer) 7
127.0.0.1:6379> APPEND key1 ",kaungshen"
(integer) 17
127.0.0.1:6379> STRLEN key1
(integer) 17
127.0.0.1:6379> get key1
"v1hello,kaungshen"
##########################################################################
# i++
# 步长 i+=
127.0.0.1:6379> set views 0 # 初始浏览量为0
OK
127.0.0.1:6379> get views
"0"
127.0.0.1:6379> incr views # 自增1 浏览量变为1
(integer) 1
127.0.0.1:6379> incr views
(integer) 2
127.0.0.1:6379> get views
"2"
127.0.0.1:6379> decr views # 自减1 浏览量-1
(integer) 1
127.0.0.1:6379> decr views
(integer) 0
127.0.0.1:6379> decr views
(integer) -1
127.0.0.1:6379> get views
"-1"
127.0.0.1:6379> INCRBY views 10 # 可以设置步长,指定增量!
(integer) 9
127.0.0.1:6379> INCRBY views 10
(integer) 19
127.0.0.1:6379> DECRBY views 5
(integer) 14
##########################################################################
# 字符串范围 range
127.0.0.1:6379> set key1 "hello,kuangshen" # 设置 key1 的值
OK
127.0.0.1:6379> get key1
"hello,kuangshen"
127.0.0.1:6379> GETRANGE key1 0 3 # 截取字符串 [0,3]
"hell"
127.0.0.1:6379> GETRANGE key1 0 -1 # 获取全部的字符串 和 get key是一样的
"hello,kuangshen"
# 替换!
127.0.0.1:6379> set key2 abcdefg
OK
127.0.0.1:6379> get key2
"abcdefg"
127.0.0.1:6379> SETRANGE key2 1 xx # 替换指定位置开始的字符串!
(integer) 7
127.0.0.1:6379> get key2
"axxdefg"
##########################################################################
# setex (set with expire) # 设置过期时间
# setnx (set if not exist) # 不存在在设置 (在分布式锁中会常常使用!)
127.0.0.1:6379> setex key3 30 "hello" # 设置key3 的值为 hello,30秒后过期
OK
127.0.0.1:6379> ttl key3
(integer) 26
127.0.0.1:6379> get key3
"hello"
127.0.0.1:6379> setnx mykey "redis" # 如果mykey 不存在,创建mykey
(integer) 1
127.0.0.1:6379> keys *
1) "key2"
2) "mykey"
3) "key1"
127.0.0.1:6379> ttl key3
(integer) -2
127.0.0.1:6379> setnx mykey "MongoDB" # 如果mykey存在,创建失败!
(integer) 0
127.0.0.1:6379> get mykey
"redis"
##########################################################################
mset
mget
127.0.0.1:6379> mset k1 v1 k2 v2 k3 v3 # 同时设置多个值
OK
127.0.0.1:6379> keys *
1) "k1"
2) "k2"
3) "k3"
127.0.0.1:6379> mget k1 k2 k3 # 同时获取多个值
1) "v1"
2) "v2"
3) "v3"
127.0.0.1:6379> msetnx k1 v1 k4 v4 # msetnx 是一个原子性的操作,要么一起成功,要么一起
失败!
(integer) 0
127.0.0.1:6379> get k4
(nil)
# 对象
set user:1 {name:zhangsan,age:3} # 设置一个user:1 对象 值为 json字符来保存一个对象!
# 这里的key是一个巧妙的设计: user:{id}:{filed} , 如此设计在Redis中是完全OK了!
127.0.0.1:6379> mset user:1:name zhangsan user:1:age 2
OK
127.0.0.1:6379> mget user:1:name user:1:age
1) "zhangsan"
2) "2"
##########################################################################
getset # 先get然后在set
127.0.0.1:6379> getset db redis # 如果不存在值,则返回 nil
(nil)
127.0.0.1:6379> get db
"redis
127.0.0.1:6379> getset db mongodb # 如果存在值,获取原来的值,并设置新的值
"redis"
127.0.0.1:6379> get db
"mongodb"
Hash(哈希)
Hash类型是String类型的field和value的映射表,或者说是一个String集合。hash特别适合用于存储对象,相比较而言,将一个对象类型存储在Hash类型要存储在String类型里占用更少的内存空间,并对整个对象的存取。可以看成具有KEY和VALUE的MAP容器,该类型非常适合于存储值对象的信息,如:uname,upass,age等。该类型的数据仅占用很少的磁盘空间(相比于JSON)。Redis 中每个 hash 可以存储 2的32次方 - 1 键值对(40多亿)
常用命令
赋值语法:
HSET KEY FIELD VALUE :为指定的KEY,设定FILD/VALUE
HMSET KEY FIELD VALUE [FIELD1,VALUE1]…… :同时将多个 field-value (域-值)对设置到哈希表 key 中。
取值语法:
HGET KEY FIELD :获取存储在HASH中的值,根据FIELD得到VALUE
HMGET KEY field[field1] :获取key所有给定字段的值
HGETALL KEY :返回HASH表中所有的字段和值
HKEYS KEY :获取所有哈希表中的字段
HLEN KEY :获取哈希表中字段的数量
删除语法:
HDEL KEY field1[field2] :删除一个或多个HASH表字段
其它语法:
HSETNX key field value :只有在字段 field 不存在时,设置哈希表字段的值
HINCRBY key field increment :为哈希表 key 中的指定字段的整数值加上增量 increment 。
HINCRBYFLOAT key field increment :为哈希表 key 中的指定字段的浮点数值加上增量 increment 。
HEXISTS key field :查看哈希表 key 中,指定的字段是否存在
##########################################################################
127.0.0.1:6379> hset myhash field1 kuangshen # set一个具体 key-vlaue
(integer) 1
127.0.0.1:6379> hget myhash field1 # 获取一个字段值
"kuangshen"
127.0.0.1:6379> hmset myhash field1 hello field2 world # set多个 key-vlaue
OK
127.0.0.1:6379> hmget myhash field1 field2 # 获取多个字段值
1) "hello"
2) "world"
127.0.0.1:6379> hgetall myhash # 获取全部的数据,
1) "field1"
2) "hello"
3) "field2"
4) "world"
127.0.0.1:6379> hdel myhash field1 # 删除hash指定key字段!对应的value值也就消失了!
(integer) 1
127.0.0.1:6379> hgetall myhash
1) "field2"
2) "world"
##########################################################################
hlen
127.0.0.1:6379> hmset myhash field1 hello field2 world
OK
127.0.0.1:6379> HGETALL myhash
1) "field2"
2) "world"
3) "field1"
4) "hello"
127.0.0.1:6379> hlen myhash # 获取hash表的字段数量!
(integer) 2
##########################################################################
127.0.0.1:6379> HEXISTS myhash field1 # 判断hash中指定字段是否存在!
(integer) 1
127.0.0.1:6379> HEXISTS myhash field3
(integer) 0
##########################################################################
# 只获得所有field
# 只获得所有value
127.0.0.1:6379> hkeys myhash # 只获得所有field
1) "field2"
2) "field1"
127.0.0.1:6379> hvals myhash # 只获得所有value
1) "world"
2) "hello"
##########################################################################
incr decr
127.0.0.1:6379> hset myhash field3 5 #指定增量!
(integer) 1
127.0.0.1:6379> HINCRBY myhash field3 1
(integer) 6
127.0.0.1:6379> HINCRBY myhash field3 -1
(integer) 5
127.0.0.1:6379> hsetnx myhash field4 hello # 如果不存在则可以设置
(integer) 1
127.0.0.1:6379> hsetnx myhash field4 world # 如果存在则不能设置
(integer) 0
Hash的应用场景:(存储一个用户信息对象数据)
- 常用于存储一个对象
- 为什么不用string存储一个对象?
hash是最接近关系数据库结构的数据类型,可以将数据库一条记录或程序中一个对象转换成hashmap存放在redis
中。
用户ID为查找的key,存储的value用户对象包含姓名,年龄,生日等信息,如果用普通的key/value结构来存储,主要有
以下2种存储方式:
第一种方式将用户ID作为查找key,把其他信息封装成一个对象以序列化的方式存储,这种方式的缺点是,增加了序列
化/反序列化的开销,并且在需要修改其中一项信息时,需要把整个对象取回,并且修改操作需要对并发进行保护,引入CAS
等复杂问题。
第二种方法是这个用户信息对象有多少成员就存成多少个key-value对儿,用用户ID+对应属性的名称作为唯一标识来
取得对应属性的值,虽然省去了序列化开销和并发问题,但是用户ID为重复存储,如果存在大量这样的数据,内存浪费还是
非常可观的。
总结: Redis提供的Hash很好的解决了这个问题,Redis的Hash实际是内部存储的Value为一个HashMap,并提供了直接存取这个Map成员的接口
List(列表)
List类型是一个链表结构的集合,其主要功能有push、pop、获取元素等。更详细的说,List类型是一个双端链表的节后,我们可以通过相关的操作进行集合的头部或者尾部添加和删除元素,List的设计非常简单精巧,即可以作为栈,又可以作为队列,满足绝大多数的需求
按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)一个列表最多可以包含 232 - 1 个元素 (4294967295, 每个列表超过40亿个元素) 类似JAVA中的LinkedList
常用命令
存值
赋值语法:
LPUSH key value1 [value2] :将一个或多个值插入到列表头部(从左侧添加)
RPUSH key value1 [value2] :在列表中添加一个或多个值(从右侧添加)
LPUSHX key value :将一个值插入到已存在的列表头部。如果列表不在,操作无效
RPUSHX key value :一个值插入已存在的列表尾部(最右边)。如果列表不在,操作无效。
取值
取值语法:
LLEN key :获取列表长度
LINDEX key index :通过索引获取列表中的元素
LRANGE key start stop :获取列表指定范围内的元素
删除
删除语法:
LPOP key 移出并获取列表的第一个元素(从左侧删除)
RPOP key 移除列表的最后一个元素,返回值为移除的元素(从右侧删除)
BLPOP key1 [key2 ] timeout 移出并获取列表的第一个元素, 如果列表没有元素会阻塞列表直到等待超时或发
现可弹出元素为止。
实例:
redis 127.0.0.1:6379> BLPOP list1 100
在以上实例中,操作会被阻塞,如果指定的列表 key list1 存在数据则会返回第一个元素,否则在等待100秒后会返回
nil
BRPOP key1 [key2 ] timeout :移出并获取列表的最后一个元素, 如果列表没有元素会阻塞列表直到等待超时或
发现可弹出元素为止。
LTRIM key start stop :对一个列表进行修剪(trim),就是说,让列表只保留指定区间内的元素,不在指定区
间之内的元素都将被删除。
修改
修改语法:
LSET key index value :通过索引设置列表元素的值
LINSERT key BEFORE|AFTER world value :在列表的元素前或者后插入元素 描述:将值 value 插入到列表
key 当中,位于值 world 之前或之后。
高级命令
高级语法:
RPOPLPUSH source destination :移除列表的最后一个元素,并将该元素添加到另一个列表并返回
示例描述:
RPOPLPUSH a1 a2 :a1的最后元素移到a2的左侧
RPOPLPUSH a1 a1 :循环列表,将最后元素移到最左侧
BRPOPLPUSH source destination timeout :从列表中弹出一个值,将弹出的元素插入到另外一个列表中并返
回它; 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。
##########################################################################
# 存值 LPUSH
# 取值 Rpush
127.0.0.1:6379> LPUSH list one # 将一个值或者多个值,插入到列表头部 (左)
(integer) 1
127.0.0.1:6379> LPUSH list two
(integer) 2
127.0.0.1:6379> LPUSH list three
(integer) 3
127.0.0.1:6379> LRANGE list 0 -1 # 获取list中值!
1) "three"
2) "two"
3) "one"
127.0.0.1:6379> LRANGE list 0 1 # 通过区间获取具体的值!
1) "three"
2) "two"
127.0.0.1:6379> Rpush list righr # 将一个值或者多个值,插入到列表位部 (右)
(integer) 4
127.0.0.1:6379> LRANGE list 0 -1
1) "three"
2) "two"
3) "one"
4) "righr"
##########################################################################
# 左边移除LPOP
# 右边移除LPOP
RPOP
127.0.0.1:6379> LRANGE list 0 -1
1) "three"
2) "two"
3) "one"
4) "righr"
127.0.0.1:6379> Lpop list # 移除list的第一个元素
"three"
127.0.0.1:6379> Rpop list # 移除list的最后一个元素
"righr"
127.0.0.1:6379> LRANGE list 0 -1
1) "two"
2) "one"
##########################################################################
Lindex
127.0.0.1:6379> LRANGE list 0 -1
1) "two"
2) "one"
127.0.0.1:6379> lindex list 1 # 通过下标获得 list 中的某一个值!
"one"
127.0.0.1:6379> lindex list 0
"two"
##########################################################################
Llen
127.0.0.1:6379> Lpush list one
(integer) 1
127.0.0.1:6379> Lpush list two
(integer) 2
127.0.0.1:6379> Lpush list three
(integer) 3
127.0.0.1:6379> Llen list # 返回列表的长度
(integer) 3
##########################################################################
移除指定的值!
取关 uid
Lrem
127.0.0.1:6379> LRANGE list 0 -1
1) "three"
2) "three"
3) "two"
4) "one"
127.0.0.1:6379> lrem list 1 one # 移除list集合中指定个数的value,精确匹配
(integer) 1
127.0.0.1:6379> LRANGE list 0 -1
1) "three"
2) "three"
3) "two"
127.0.0.1:6379> lrem list 1 three
(integer) 1
127.0.0.1:6379> LRANGE list 0 -1
1) "three"
2) "two"
127.0.0.1:6379> Lpush list three
(integer) 3
127.0.0.1:6379> lrem list 2 three
(integer) 2
127.0.0.1:6379> LRANGE list 0 -1
1) "two"
##########################################################################
trim 修剪。; list 截断!
127.0.0.1:6379> keys *
(empty list or set)
127.0.0.1:6379> Rpush mylist "hello"
(integer) 1
127.0.0.1:6379> Rpush mylist "hello1"
(integer) 2
127.0.0.1:6379> Rpush mylist "hello2"
(integer) 3
127.0.0.1:6379> Rpush mylist "hello3"
(integer) 4
127.0.0.1:6379> ltrim mylist 1 2 # 通过下标截取指定的长度,这个list已经被改变了,
只剩下截取的元素!
OK
127.0.0.1:6379> LRANGE mylist 0 -1
1) "hello1"
2) "hello2"
##########################################################################
rpoplpush # 移除列表的最后一个元素,将他移动到新的列表中!
127.0.0.1:6379> rpush mylist "hello"
Set(集合)
Redis 的 Set 是 String 类型的无序集合。集合成员是唯一的,这就意味着集合中不能出现重复的数据。 Redis 中集合是通过哈希表实现的,set是通过hashtable实现的 集合中最大的成员数为 232 - 1 (4294967295, 每个集合可存储40多亿个成员)。 类似于JAVA中的 Hashtable集合
值语法:
SADD key member1 [member2] :向集合添加一个或多个成员
值语法:
SCARD key :获取集合的成员数
SMEMBERS key :返回集合中的所有成员
SISMEMBER key member :判断 member 元素是否是集合 key 的成员(开发中:验证是否存在判断)
SRANDMEMBER key [count] :返回集合中一个或多个随机数
除语法:
SREM key member1 [member2] :移除集合中一个或多个成员
SPOP key [count] :移除并返回集合中的一个随机元素
SMOVE source destination member :将 member 元素从 source 集合移动到 destination 集合
集语法:
SDIFF key1 [key2] :返回给定所有集合的差集(左侧)
SDIFFSTORE destination key1 [key2] :返回给定所有集合的差集并存储在 destination 中
集语法:
SINTER key1 [key2] :返回给定所有集合的交集(共有数据)
SINTERSTORE destination key1 [key2] :返回给定所有集合的交集并存储在 destination 中
集语法:
SUNION key1 [key2] :返回所有给定集合的并集
SUNIONSTORE destination key1 [key2] :所有给定集合的并集存储在 destination 集合中
##########################################################################
127.0.0.1:6379> sadd myset "hello" # set集合中添加匀速
(integer) 1
127.0.0.1:6379> sadd myset "chen"
(integer) 1
127.0.0.1:6379> sadd myset "lovechen"
(integer) 1
127.0.0.1:6379> SMEMBERS myset # 查看指定set的所有值
1) "hello"
2) "lovechen"
3) "chen"
127.0.0.1:6379> SISMEMBER myset hello # 判断某一个值是不是在set集合中!
(integer) 1
127.0.0.1:6379> SISMEMBER myset world
(integer) 0
##########################################################################
127.0.0.1:6379> scard myset # 获取set集合中的内容元素个数!
(integer) 4
##########################################################################
rem
127.0.0.1:6379> srem myset hello # 移除set集合中的指定元素
(integer) 1
127.0.0.1:6379> scard myset
(integer) 3
127.0.0.1:6379> SMEMBERS myset
1) "lovechen2"
2) "lovechen"
3) "chen"
##########################################################################
set 无序不重复集合。抽随机!
127.0.0.1:6379> SMEMBERS myset
1) "lovechen2"
2) "lovechen"
3) "chen"
127.0.0.1:6379> SRANDMEMBER myset # 随机抽选出一个元素
"chen"
127.0.0.1:6379> SRANDMEMBER myset
"chen"
127.0.0.1:6379> SRANDMEMBER myset
"chen"
127.0.0.1:6379> SRANDMEMBER myset
"chen"
127.0.0.1:6379> SRANDMEMBER myset 2 # 随机抽选出指定个数的元素
1) "lovechen"
2) "lovechen2"
127.0.0.1:6379> SRANDMEMBER myset 2
1) "lovechen"
2) "lovechen2"
127.0.0.1:6379> SRANDMEMBER myset # 随机抽选出一个元素
"lovechen2"
##########################################################################
删除定的key,随机删除key!
127.0.0.1:6379> SMEMBERS myset
1) "lovechen2"
2) "lovechen"
3) "chen"
127.0.0.1:6379> spop myset # 随机删除一些set集合中的元素!
"lovechen2"
127.0.0.1:6379> spop myset
"lovechen"
127.0.0.1:6379> SMEMBERS myset
1) "chen"
##########################################################################
将一个指定的值,移动到另外一个set集合!
127.0.0.1:6379> sadd myset "hello"
(integer) 1
127.0.0.1:6379> sadd myset "world"
(integer) 1
127.0.0.1:6379> sadd myset "chen"
(integer) 1
127.0.0.1:6379> sadd myset2 "set2"
(integer) 1
127.0.0.1:6379> smove myset myset2 "chen" # 将一个指定的值,移动到另外一个set集
合!
(integer) 1
127.0.0.1:6379> SMEMBERS myset
1) "world"
2) "hello"
127.0.0.1:6379> SMEMBERS myset2
1) "chen"
2) "set2"
##########################################################################
微博,B站,共同关注!(并集)
数字集合类:
- 差集 SDIFF
- 交集
- 并集
127.0.0.1:6379> SDIFF key1 key2 # 差集
1) "b"
2) "a"
127.0.0.1:6379> SINTER key1 key2 # 交集 共同好友就可以这样实现
1) "c"
127.0.0.1:6379> SUNION key1 key2 # 并集
1) "b"
2) "c"
3) "e"
4) "a"
5) "d"
Zset(有序集合)
Redis 有序集合和集合一样也是string类型元素的集合,且不允许重复的成员。 2、不同的是每个元素都会关联一个double类型的分数。redis正是通过分数来为集合中的成员进行从小到大的排序。 3、有序集合的成员是唯一的,但分数(score)却可以重复。 4、集合是通过哈希表实现的。 集合中最大的成员数为 2次方32 - 1 (4294967295, 每个集合可存储40多亿个成员)。Redis的ZSet是有序、且不重复 (很多时候,我们都将redis中的有序集合叫做zsets,这是因为在redis中,有序集合相关的操作指令都是以z开头的)
赋值语法:
ZADD key score1 member1 [score2 member2] :向有序集合添加一个或多个成员,或者更新已存在成员的分
数 取
值语法:
ZCARD key :获取有序集合的成员数
ZCOUNT key min max :计算在有序集合中指定区间分数的成员数
ZRANK key member :返回有序集合中指定成员的索引
ZRANGE key start stop [WITHSCORES] :通过索引区间返回有序集合成指定区间内的成员(低到高)
ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT] :通过分数返回有序集合指定区间内的成员
ZREVRANGE key start stop [WITHSCORES] :返回有序集中指定区间内的成员,通过索引,分数从高到底
ZREVRANGEBYSCORE key max min [WITHSCORES] :返回有序集中指定分数区间内的成员,分数从高到低排序
删除语法:
DEL key :移除集合
ZREM key member [member ...] :移除有序集合中的一个或多个成员
ZREMRANGEBYRANK key start stop :移除有序集合中给定的排名区间的所有成员(第一名是0)(低到高排序)
ZREMRANGEBYSCORE key min max :移除有序集合中给定的分数区间的所有成员
ZINCRBY key increment member :增加memeber元素的分数increment,返回值是更改后的分数
127.0.0.1:6379> zadd myset 1 one # 添加一个值
(integer) 1
127.0.0.1:6379> zadd myset 2 two 3 three # 添加多个值
(integer) 2
127.0.0.1:6379> ZRANGE myset 0 -1
1) "one"
2) "two"
3) "three"
##########################################################################
排序如何实现
127.0.0.1:6379> zadd salary 2500 xiaohong # 添加三个用户
(integer) 1
127.0.0.1:6379> zadd salary 5000 zhangsan
(integer) 1
127.0.0.1:6379> zadd salary 500 kaungshen
(integer) 1
# ZRANGEBYSCORE key min max
127.0.0.1:6379> ZRANGEBYSCORE salary -inf +inf # 显示全部的用户 从小到大!
1) "kaungshen"
2) "xiaohong"
3) "zhangsan"
127.0.0.1:6379> ZREVRANGE salary 0 -1 # 从大到进行排序!
1) "zhangsan"
2) "kaungshen"
127.0.0.1:6379> ZRANGEBYSCORE salary -inf +inf withscores # 显示全部的用户并且附带成
绩 1)
"kaungshen"
2) "500"
3) "xiaohong"
4) "2500"
5) "zhangsan"
6) "5000"
127.0.0.1:6379> ZRANGEBYSCORE salary -inf 2500 withscores # 显示工资小于2500员工的升
序排序!
1) "kaungshen"
2) "500"
3) "xiaohong"
4) "2500"
##########################################################################
# 移除rem中的元素
127.0.0.1:6379> zrange salary 0 -1
1) "kaungshen"
2) "xiaohong"
3) "zhangsan"
127.0.0.1:6379> zrem salary xiaohong # 移除有序集合中的指定元素
(integer) 1
127.0.0.1:6379> zrange salary 0 -1
1) "kaungshen"
2) "zhangsan"
127.0.0.1:6379> zcard salary # 获取有序集合中的个数
(integer) 2
##########################################################################
127.0.0.1:6379> zadd myset 1 hello
(integer) 1
127.0.0.1:6379> zadd myset 2 world 3 kuangshen
(integer) 2
127.0.0.1:6379> zcount myset 1 3 # 获取指定区间的成员数量!
(integer) 3
127.0.0.1:6379> zcount myset 1 2
(integer) 2
三种特殊数据类型
Reids 在 Web 应用的开发中使用非常广泛,几乎所有的后端技术都会有涉及到 Redis 的使用。Redis 种除了常见的字符串 String、字典 Hash、列表 List、集合 Set、有序集合 SortedSet 等等之外,还有一些不常用的数据类型,这里着重介绍三个。
Geospatial 地理位置
Redis 的 Geo 在Redis3.2 版本就推出了! 这个功能可以推算地理位置的信息,两地之间的距离,方圆几里的人!
GEO 的数据结构总共有六个命令
geoadd
geopos
geodist
georadius
georadiusbymember
geohash
Hyperloglog
Redis 在 2.8.9 版本添加了 HyperLogLog 结构。是用来做基数统计的算法,HyperLogLog 的优点是,在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定的、并且是很小的。
在 Redis 里面,每个 HyperLogLog 键只需要花费 12 KB 内存,就可以计算接近 2^64 个不同元素的基 数。这和计算基数时,元素越多耗费内存就越多的集合形成鲜明对比。但是,因为 HyperLogLog 只会根据输入元素来计算基数,而不会储存输入元素本身,所以 HyperLogLog 不能像集合那样,返回输入的各个元素。
什么是基数?比如数据集 {1, 3, 5, 7, 5, 7, 8}, 那么这个数据集的基数集为 {1, 3, 5 ,7, 8}, 基数(不重复元素)为5。基数估计就是在误差可接受的范围内,快速计算基数。
常用命令
PFADD key element [element ...] :添加指定元素到 HyperLogLog 中
PFCOUNT key [key ...] :返回给定 HyperLogLog 的基数估算值
PFMERGE destkey sourcekey [sourcekey ...] :将多个 HyperLogLog 合并为一个 HyperLogLog
应用场景
统计注册 IP 数
统计每日访问 IP 数
统计页面实时 UV 数
统计在线用户数
统计用户每天搜索不同词条的个数
统计真实文章阅读数
Bitmap
位存储,统计用户信息,活跃,不活跃! 登录 、 未登录! 打卡,365打卡! 两个状态的,都可以使用Bitmaps!
Bitmap 位图,数据结构! 都是操作二进制位来进行记录,就只有0 和 1 两个状态!
365 天 = 365 bit 1字节 = 8bit 46 个字节左右!
测试:使用bitmap 来记录 周一到周日的打卡!周一:1 周二:0 周三:0 周四:1 ……
查看某一天是否有打卡!
127.0.0.1:6379> getbit sign 3
(integer) 1
127.0.0.1:6379> getbit sign 6
(integer) 0
统计操作,统计 打卡的天数!
127.0.0.1:6379> bitcount sign # 统计这周的打卡记录,就可以看到是否有全勤!
Redis的事务
Redis事务是一组 Redis 命令的集合,可以作为一个单元来进行执行,要么全部执行成功,要么全部失败回滚。
在建立事务后,用户可以依次将多个命令添加到事务中,但这些命令并不会马上执行。只有在事务提交(即执行EXEC命令)的时候,才会按照添加指令的顺序进行执行,在执行期间,其他客户端发送的请求不会插入到事务执行的命令序列中。
Redis事务还支持乐观锁机制,即在事务提交前对事务所操作的数据进行监视,并且如果在提交事务之前其他客户端已经对这些数据进行了修改,则事务提交失败。
因此,Redis事务可以确保多个操作同时进行,就像一个整体,从而避免了多个命令在执行过程中出现异常导致数据不一致的风险。
- Redis会将一个事务中的所有命令序列化,然后按顺序执行
- 执行中不会被其它命令插入,不许出现加赛行为
一个事务从开始到执行会经历以下三个阶段:
- 开始事务;
- 命令入队;
- 执行事务。
事务使用
multi # 开启事务,之后的命令不再立刻执行、而是进入任务队列
//…… # 输入事务内的命令
exec # 执行事务,执行任务队列里的命令
discard # 取消事务,清空任务队列
Redis 中事务的用法非常简单,我们通过 MULTI
命令开启一个事务,在MULT
命令执行之后,我们可以继续发送命令去执行,此时的命令不会被立马执行,而是放在一个队列中,当所有的命令都输入完成后,我们可以通过 EXEC 命令发起执行,也可以通过 DISCARD 命令清空队列
127.0.0.1:6379> multi # 开启事务
OK
# 命令入队
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> get k2
QUEUED
127.0.0.1:6379> set k3 v3
QUEUED
127.0.0.1:6379> exec # 执行事务
1) OK
2) OK
3) "v2"
4) OK
放弃事务
127.0.0.1:6379> multi # 开启事务
OK
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> set k3 v3
QUEUED
127.0.0.1:6379> set k4 v4
QUEUED
127.0.0.1:6379> discard # 取消事务
OK
127.0.0.1:6379> get k1 # 事务队列中命令都不会被执行!
(nil)
编译型异常(代码有问题! 命令有错!) ,事务中所有的命令都不会被执行!
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> set k3 v3
QUEUED
127.0.0.1:6379> getset k3 # 错误的命令
(error) ERR wrong number of arguments for 'getset' command
127.0.0.1:6379> set k4 v4
QUEUED
127.0.0.1:6379> set k5 v5
QUEUED
127.0.0.1:6379> exec # 执行事务报错!
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> get k5 # 所有的命令都不会被执行!
(nil)
运行时异常(1/0), 如果事务队列中存在语法性,那么执行命令的时候,其他命令是可以正常执行的,错误命令抛出异常!
127.0.0.1:6379> set k1 "v1"
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> incr k1 # 会执行的时候失败!
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> set k3 v3
QUEUED
127.0.0.1:6379> get k3
QUEUED
127.0.0.1:6379> exec
1) (error) ERR value is not an integer or out of range # 虽然第一条命令报错了,但是
依旧正常执行成功了!
2) OK
3) OK
4) "v3"
127.0.0.1:6379> get k2
"v2"
127.0.0.1:6379> get k3
"v3"
监控! Watch 监视一个(或多个) key ,如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断。
悲观锁: 很悲观,认为什么时候都会出问题,无论做什么都会加锁!
乐观锁:很乐观,认为什么时候都不会出问题,所以不会上锁! 更新数据的时候去判断一下,在此期间是否有人修改过这个数据,获取version更新的时候比较 version
Redis监视测试
需求:某一帐户在一事务内进行操作,在提交事务前,另一个进程对该帐户进行操作
127.0.0.1:6379> set money 100
OK
127.0.0.1:6379> set out 0
OK
127.0.0.1:6379> watch money # 监视 money 对象
OK
127.0.0.1:6379> multi # 事务正常结束,数据期间没有发生变动,这个时候就正常执行成功!
OK
127.0.0.1:6379> DECRBY money 20
QUEUED
127.0.0.1:6379> INCRBY out 20
QUEUED
127.0.0.1:6379> exec
1) (integer) 80
2) (integer) 20
127.0.0.1:6379> watch money # 监视 money
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> DECRBY money 10
QUEUED
127.0.0.1:6379> INCRBY out 10
QUEUED
127.0.0.1:6379> exec # 执行之前,另外一个线程,修改了我们的值,这个时候,就会导致事务执行失
败!
(nil)
127.0.0.1:6379> set money 1000 # 修改数据
OK
127.0.0.1:6379>
如果修改失败,获取最新的值就好
常用事务命令
命令 | 描述 |
---|---|
DISCARD | 取消事务,放弃执行事务块内的所有命令。 |
EXEC | 执行所有事务块内的命令。 |
MULTI | 标记一个事务块的开始。 |
UNWATCH | 取消 WATCH 命令对所有 key 的监视。 |
WATCH key [key …] | 监视一个(或多个) key ,如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断。 |
事务常见使用中的情况
正常执行:执行很正常没一点报错
放弃事务:使用 discard
命令放弃当前事务
全体连坐:进入队列之前就能发现的错误,比如命令输错;此时一个错误,整个事务的不执行
冤头债主:执行 EXEC 之后才能发现的错误;对的执行,错的抛出异常
事务中的异常情况
Redis 中事务的异常情况总的来说分为两类:
- 进入队列之前就能发现的错误,比如命令输错;
执行 EXEC 之后才能发现的错误,比如给一个非数字字符加 1 ;
那么对于这两种不同的异常,redis 中有不同的处理策略。
- 第一种:对于第一种错误,服务器会对命令入队失败的情况进行记录,并在客户端调用 EXEC 命令时,拒绝执行并自动放弃这个事务:如下
- 第二种:而对于第二种情况,redis 并没有对它们进行特别处理, 即使事务中有某个/某些命令在执行时产生了错误, 事务中的其他命令仍然会继续执行:如下
WATCH命令
前几命令都特别简单就不在此详细叙述了,接下来我们看一下WATCH命令。
事务中的 WATCH 命令可以用来监控一个 key,通过这种监控,我们可以为 Redis 事务提供(CAS)行为。 如果有至少一个被 WATCH 监视的键在 EXEC 执行之前被修改了,那么整个事务都会被取消,EXEC 返回 nil来表示事务已经失败。如下:
通过 unwatch 命令,可以取消对一个 key 的监控,如下:【补充】
事务总结
Redis事务的3阶段
- 开启:以MULTI开始一个事务
- 入队:将多个命令入队到事务中,接到这些命令并不会立即执行,而是放到等待执行的事务队列里面
- 执行:由EXEC命令触发事务
Redis3特性
- 单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。
- 没有隔离级别的概念:队列中的命令没有提交之前都不会实际的被执行,因为事务提交前任何指令都不会被实际执行,也就不存在”事务内的查询要看到事务里的更新,在事务外查询不能看到”这个让人万分头痛的问题
- 不保证原子性:redis同一个事务中如果有一条命令执行失败,其后的命令仍然会被执行,没有回滚
Redis配置文件解析
Redis的配置文件位于Redis安装目录下的redis.conf文件中,它可以配置Redis服务器的各种参数和选项。可以使用文本编辑器打开并进行修改。
在Redis中,redis.conf对Redis服务器的整体性能和运行状态具有重要影响,并且随着Redis的应用场景和负载情况的变化,需要不断地对redis.conf进行修改和优化。
- Redis默认不是以守护进程的方式运行,可以通过该配置项修改,使用yes启用守护进程
daemonize no - 当Redis以守护进程方式运行时,Redis默认会把pid写入/var/run/redis.pid文件,可以通过pidfile指定
pidfile /var/run/redis.pid - 指定Redis监听端口,默认端口为6379,作者在自己的一篇博文中解释了为什么选用6379作为默认端口,因为6379在手机按键上MERZ对应的号码,而MERZ取自意大利歌女Alessia Merz的名字
port 6379 - 绑定的主机地址
bind 127.0.0.1
5.当 客户端闲置多长时间后关闭连接,如果指定为0,表示关闭该功能
timeout 300 - 指定日志记录级别,Redis总共支持四个级别:debug、verbose、notice、warning,默认为verbose
loglevel verbose - 日志记录方式,默认为标准输出,如果配置Redis为守护进程方式运行,而这里又配置为日志记录方式为标准输出,则日志将会发送给/dev/null
logfile stdout - 设置数据库的数量,默认数据库为0,可以使用SELECT 命令在连接上指定数据库id
databases 16 - 指定在多长时间内,有多少次更新操作,就将数据同步到数据文件,可以多个条件配合
save
Redis默认配置文件中提供了三个条件:
save 900 1
save 300 10
save 60 10000
分别表示900秒(15分钟)内有1个更改,300秒(5分钟)内有10个更改以及60秒内有10000个更改。 - 指定存储至本地数据库时是否压缩数据,默认为yes,Redis采用LZF压缩,如果为了节省CPU时间,可以关闭该选项,但会导致数据库文件变的巨大
rdbcompression yes - 指定本地数据库文件名,默认值为dump.rdb
dbfilename dump.rdb - 指定本地数据库存放目录
dir ./ - 设置当本机为slav服务时,设置master服务的IP地址及端口,在Redis启动时,它会自动从master进行数据同步
slaveof - 当master服务设置了密码保护时,slav服务连接master的密码
masterauth - 设置Redis连接密码,如果配置了连接密码,客户端在连接Redis时需要通过AUTH 命令提供密码,默认关闭
requirepass foobared - 设置同一时间最大客户端连接数,默认无限制,Redis可以同时打开的客户端连接数为Redis进程可以打开的最大文件描述符数,如果设置 maxclients 0,表示不作限制。当客户端连接数到达限制时,Redis会关闭新的连接并向客户端返回max number of clients reached错误信息
maxclients 128 - 指定Redis最大内存限制,Redis在启动时会把数据加载到内存中,达到最大内存后,Redis会先尝试清除已到期或即将到期的Key,当此方法处理 后,仍然到达最大内存设置,将无法再进行写入操作,但仍然可以进行读取操作。Redis新的vm机制,会把Key存放内存,Value会存放在swap区
maxmemory - 指定是否在每次更新操作后进行日志记录,Redis在默认情况下是异步的把数据写入磁盘,如果不开启,可能会在断电时导致一段时间内的数据丢失。因为 redis本身同步数据文件是按上面save条件来同步的,所以有的数据会在一段时间内只存在于内存中。默认为no
appendonly no - 指定更新日志文件名,默认为appendonly.aof
appendfilename appendonly.aof - 指定更新日志条件,共有3个可选值:
no:表示等操作系统进行数据缓存同步到磁盘(快)
always:表示每次更新操作后手动调用fsync()将数据写到磁盘(慢,安全)
everysec:表示每秒同步一次(折衷,默认值)
appendfsync everysec - 指定是否启用虚拟内存机制,默认值为no,简单的介绍一下,VM机制将数据分页存放,由Redis将访问量较少的页即冷数据swap到磁盘上,访问多的页面由磁盘自动换出到内存中(在后面的文章我会仔细分析Redis的VM机制)
vm-enabled no - 虚拟内存文件路径,默认值为/tmp/redis.swap,不可多个Redis实例共享
vm-swap-file /tmp/redis.swap - 将所有大于vm-max-memory的数据存入虚拟内存,无论vm-max-memory设置多小,所有索引数据都是内存存储的(Redis的索引数据 就是keys),也就是说,当vm-max-memory设置为0的时候,其实是所有value都存在于磁盘。默认值为0
vm-max-memory 0 - Redis swap文件分成了很多的page,一个对象可以保存在多个page上面,但一个page上不能被多个对象共享,vm-page-size是要根据存储的 数据大小来设定的,作者建议如果存储很多小对象,page大小最好设置为32或者64bytes;如果存储很大大对象,则可以使用更大的page,如果不 确定,就使用默认值
vm-page-size 32 - 设置swap文件中的page数量,由于页表(一种表示页面空闲或使用的bitmap)是在放在内存中的,,在磁盘上每8个pages将消耗1byte的内存。
vm-pages 134217728 - 设置访问swap文件的线程数,最好不要超过机器的核数,如果设置为0,那么所有对swap文件的操作都是串行的,可能会造成比较长时间的延迟。默认值为4
vm-max-threads 4 - 设置在向客户端应答时,是否把较小的包合并为一个包发送,默认为开启
glueoutputbuf yes - 指定在超过一定的数量或者最大的元素超过某一临界值时,采用一种特殊的哈希算法
hash-max-zipmap-entries 64
hash-max-zipmap-value 512 - 指定是否激活重置哈希,默认为开启(后面在介绍Redis的哈希算法时具体介绍)
activerehashing yes - 指定包含其它的配置文件,可以在同一主机上多个Redis实例之间使用同一份配置文件,而同时各个实例又拥有自己的特定配置文件
include /path/to/local.conf
如果你只想做一个码农的话,就只会用Redis就可以了,但是你要想提高自己对Redis的运用和了解,我们应该尽量的读redis.conf配置文件,很多情况下我们都要修改配置文件完成我们要写的功能。
Redis持久化
持久化的功能:Redis是内存数据库,数据都是存储在内存中,为了避免进程退出导致数据的永久丢失,需要定期将Redis中的数据以某种形式(数据或命令)从内存保存到硬盘;当下次Redis重启时,利用持久化文件实现数据恢复。除此之外,为了进行灾难备份,可以将持久化文件拷贝到一个远程位置。
Redis持久化分为RDB持久化和AOF持久化,前者将当前数据保存到硬盘,后者则是将每次执行的写命令保存到硬盘(类似于MySQL的binlog);由于AOF持久化的实时性更好,即当进程意外退出时丢失的数据更少,因此AOF是目前主流的持久化方式,不过RDB持久化仍然有其用武之地。
Redis为了保证效率,数据缓存在内存中,Redis 会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,以保证数据的持久化。
如果 Redis 负责为数据库高热度数据访问加速或者一些其他业务(数据库中有重复数据),那么没必要为 Redis 数据持久化。
RDB持久化
RDB持久化是将当前进程中的数据生成快照保存到硬盘(因此也称作快照持久化),保存的文件后缀是rdb
;
当Redis重新启动时,可以读取快照文件恢复数据。 Redis 默认的方式,redis 通过快照方式将数据持久化到磁盘中。
原理
Redis会单独创建(fork)一个子进程来进行持久化,会先将数据写入到一个临时文件中,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件。整个过程中,主进程是不进行任何IO操作的。这就确保了极高的性能。如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那RDB方式要比AOF方式更加的高效。RDB的缺点是最后一次持久化后的数据可能丢失。我们默认的就是RDB,一般情况下不需要修改这个配置!
数据以二进制形式默认存储在 安装目录/data/dump.rgb 文件。如果 Redis 数据库被关闭,下次重启时会从该文件读取数据。都是在我们的配置文件中快照中进行配置的!
触发机制
1、save的规则满足的情况下,会自动触发rdb规则
2、执行 flushall 命令,也会触发我们的rdb规则!
3、退出redis,也会产生 rdb 文件!
备份就自动生成一个 dump.rdb
如何恢复rdb文件
1、只需要将rdb文件放在我们redis启动目录就可以,redis启动的时候会自动检查dump.rdb 恢复其中的数据!
2、查看需要存在的位置
127.0.0.1:6379> config get dir
1) "dir"
2) "/usr/local/bin" # 如果在这个目录下存在 dump.rdb 文件,启动就会自动恢复其中的数据
操作RDB命令
Redis 提供了两个命令用来生成 RDB 文件,一个是 SAVE,一个是 BGSAVE。
- SAVE命令命令
SAVE 命令会阻塞 Redis 服务器进程,走到 RDB 文件创建完毕为止,在 Redis 服务器进程阻塞期间,Redis 不能处理任何命令请求。 - BGSAVE命令
BGSAVE 命令会派生出一个子进程,然后由子进程创建 RDB 文件,因此,BGSAVE 命令不会阻塞 Redis 服务器进程
可以使用 LASTSAVE 命令来检查保存 RDB 文件的操作是否执行成功
如何配置快照持久化
- redis中的快照持久化默认是开启的,redis.conf中相关配置主要有如下几项:
# 如果900s内,如果至少有一个1 key进行了修改,我们及进行持久化操作
save 900 1
# 如果300s内,如果至少10 key进行了修改,我们及进行持久化操作
save 300 10
# 如果60s内,如果至少10000 key进行了修改,我们及进行持久化操作
save 60 10000
# 我们之后学习持久化,会自己定义这个测试!
stop-writes-on-bgsave-error yes # 持久化如果出错,是否还需要继续工作!
rdbcompression yes # 是否压缩 rdb 文件,需要消耗一些cpu资源!
rdbchecksum yes # 保存rdb文件的时候,进行错误的检查校验!
dir ./ # rdb 文件保存的目录!
RDB快照持久化的优缺点
- 优点:
1、只有一个文件 dump.rdb,方便持久化。
2、容灾性好,一个文件可以保存到安全的磁盘。
3、性能最大化,fork 子进程来完成写操作,让主进程继续处理命令,所以是 IO 最大化。使用单独子进程来进行持久化,主进程不会进行任何 IO 操作,保证了 redis 的高性能
4.相对于数据集大时,比 AOF 的启动效率更高。 - 缺点:
1、数据安全性低。RDB 是间隔一段时间进行持久化,如果持久化之间 redis 发生故障,会发生数据丢失。所以这种方式更适合数据要求不严谨的时候)
2、AOF(Append-only file)持久化方式: 是指所有的命令行记录以 redis 命令请 求协议的格式完全持久化存储)保存为 aof 文件。
AOF持久化
AOF 持久化以日志的形式记录服务器所处理的每一个写操作、将Redis执行过的所有写指令记录下来(读操作不记录),只许追加文件但不可以改写文件,redis启动之初会读取该文件重新构建数据,简单说,Redis重启的话就根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作
启用 AOF 持久化方案后,当我们执行类似 SET 设置(或修改)命令时,Redis 会将命令以 Redis 通信协议 文本保存到 appendonly.aof 文件中。
AOF和RDB可以共同存
- Redis 将写命令保存到 AOF 文件中
- Redis 利用 AOF 文件还原内存数据
执行持久化策略
AOF 持久化方案提供 3 种不同时间策略将数据同步到磁盘中,同步策略通过 appendfsync 指令完成:
- everysec(默认):表示每秒执行一次 fsync 同步策略,效率上同 RDB 持久化差不多。由于每秒同步一次,所以服务器故障时会丢失 1 秒内的数据。
- always: 每个写命令都会调用 fsync 进行数据同步,最安全但影响性能。
- no: 表示 Redis 从不执行 fsync,数据将完全由内核控制写入磁盘。对于 Linux 系统来说,每 30 秒写入一次。
使用是推荐采用默认的 everysec 每秒同步策略,兼顾安全与效率。
开启 AOF
持久化(redis.conf)
AOF(append only file):只进行追加操作的文件。默认情况下,Redis 会禁用 AOF 重写,如需开启我们需要到配置文件中将 appendonly 指令配置为 yes(默认:no 不启用)。
appendonly yes
注意:重启 Redis 才生效
appendonly no # 默认是不开启aof模式的,默认是使用rdb方式持久化的,在大部分所有的情况下,rdb完全够用!
appendfilename "appendonly.aof" # 持久化的文件的名字
# appendfsync always # 每次修改都会 sync。消耗性能
appendfsync everysec # 每秒执行一次 sync,可能会丢失这1s的数据!
# appendfsync no # 不执行 sync,这个时候操作系统自己同步数据,速度最快!
# rewrite 重写
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
此时,当我们在 redis 中进行数据操作时,就会自动生成 AOF 的配置文件 appendonly.aof
AOF 文件的重写与压缩
AOF 备份有很多明显的优势,当然也有劣势,那就是文件大小。随着系统的运行,AOF 的文件会越来越大,甚至把整个电脑的硬盘填满,AOF 文件的重写与压缩机制可以在一定程度上缓解这个问题。 当 AOF 的备份文件过大时,我们可以向 redis 发送一条 bgrewriteaof
命令进行文件重写。
bgrewriteaof
的执行原理和我们上文说的 bgsave 的原理一致,这里我就不再赘述,因此 bgsave 执行过程中存在的问题在这里也一样存在。
bgrewriteaof
也可以自动执行,自动执行时间则依赖于 auto-aof-rewrite-percentage
和 auto-aof-rewrite-min-size
配置,auto-aof-rewrite-percentage 100 表示当目前 aof 文件大小超过上一次重写时的 aof 文件大小的百分之多少时会再次进行重写,如果之前没有重写,则以启动时的 aof 文件大小为依据,同时还要求 AOF 文件的大小至少要大于 64M(auto-aof-rewrite-min-size 64mb)。
Redis持久化总结
- 如果 Redis 只做缓存服务器,那么可以不使用任何持久化方式。
- 同时开启两种持久化方式的种情况下,当 redis 重启的时候会优先载入 AOF 文件来恢复原始的数据, 因为在通常情况下 AOF 文件保存的数据集要比 RDB 文件保存的数据集要完整;RDB 的数据不完整时,同时使用两者时服务器重启也只会找 AOF 文件。那要不要只使用 AOF 呢? 作者建议不要,因为 RDB 更适合用于备份数据库( AOF 在不断变化不好备份), 快速重启,而且不会有 AOF 可能潜在的 bug ,留着作为一个万一的手段。
- 因为 RDB 文件只用作后备用途,建议只在 slave 上持久化 RDB 文件,而且只要 15 分钟备份一次就够了,只保留 save 900 1 这条规则。
- 如果 Enalbe AOF,好处是在最恶劣情况下也只会丢失不超过两秒数据,启动脚本较简单只 load 自己的 AOF 文件就可以了。代价一是带来了持续的 IO,二是 AOF rewrite 的最后将 rewrite 过程中产生的新数据写到新文件造成的阻塞几乎是不可避免的。只要硬盘许可,应该尽量减少 AOF rewrite 的频率,AOF 重写的基础大小默认值 64M 太小了,可以设到 5G 以上。默认超过原大小 100% 大小时重写可以改到适当的数值。
- 如果不 Enable AOF ,仅靠 Master-Slave Replication 实现高可用性也可以。能省掉一大笔 IO 也减少了 rewrite 时带来的系统波动。代价是如果 Master/Slave 同时倒掉,会丢失十几分钟的数据,启动脚本也要比较两个 Master/Slave 中的 RDB 文件,载入较新的那个。
Redis发布/订阅
Redis 发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息。 Redis 客户端可以订阅任意数量的频道。
- pub即publish,推送。
- sub即subscribe,订阅。
Redis 的发布订阅系统有点类似于我们生活中的电台,电台可以在某一个频率上发送广播,而我们可以接收任何一个频率的广播。
发布订阅相关命令
命令 | 描述 |
---|---|
PSUBSCRIBE | 订阅一个或多个符合给定模式的频道。 |
PUBSUB | 查看订阅与发布系统状态。 |
PUBLISH | 将信息发送到指定的频道。 |
PUNSUBSCRIBE | 退订所有给定模式的频道。 |
SUBSCRIBE | 订阅给定的一个或多个频道的信息。 |
UNSUBSCRIBE | 指退订给定的频道。 |
订阅频道和信息发布
功能说明:Redis 的SUBSCRIBE
命令可以让客户端订阅任意数量的频道, 每当有新消息发送到被订阅的频道时, 信息就会被发送给所有订阅指定频道的客户端。
订阅者订阅某个channel1(频道),发布者将消息发布到Redis服务器,Redis服务器将消息推送给这个频道的订阅者。
订阅例子示意图:下图展示了频道 channel1 , 以及订阅这个频道的三个客户端 —— client2 、 client5 和 client1 之间的关系:
发布例子示意图:当有新消息通过 PUBLISH 命令发送给频道 channel1 时, 这个消息就会被发送给订阅它的三个客户端:
测试
订阅端:
127.0.0.1:6379> SUBSCRIBE chen # 订阅一个频道 kuangshenshuo
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "chen"
3) (integer) 1
# 等待读取推送的信息
1) "message" # 消息
2) "chen" # 那个频道的消息
3) "hello,chen" # 消息的具体内容
1) "message"
2) "chen"
3) "hello,redis"
发送端:
127.0.0.1:6379> PUBLISH chen "hello,chen" # 发布者发布消息到频道!
(integer) 1
127.0.0.1:6379> PUBLISH chen "hello,redis" # 发布者发布消息到频道!
(integer) 1
127.0.0.1:6379>
发布订阅使用场景
这一功能最明显的用法就是构建实时消息系统,比如普通的即时聊天,群聊等功能
- 在一个博客网站中,有100个粉丝订阅了你,当你发布新文章,就可以推送消息给粉丝们。
- 微信公众号模式
Redis主从复制(Master/Slave)
如果数据只交给一个 Redis 服务器处理,那么可能面临两大问题:
- 服务器同时处理过多读写操作,超过服务器负载。
- 一旦服务器宕机,就会导致服务异常中断。
为了避免这两个问题,我们必须引入多个 Redis 服务器来保存相同数据,并采用主从复制结构:一个主服务器 Master 对应多个从服务器 Slave 。
主从复制,是指将一台Redis服务器的数据,复制到其他的Redis服务器。前者称为主节点(master),后者称为从节点(slave);数据的复制是单向的,只能由主节点到从节点。
Redis配置成主从模式,主库(Master)只负责写数据,从库(Slave)只负责读数据。 主机数据更新后根据配置和策略,自动同步到备机的master/slaver机制
默认情况下,每台Redis服务器都是主节点;且一个主节点可以有多个从节点(或没有从节点),但一个从节点只能有一个主节点。
Redis的复制可以干什么
- 数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式
- 故障恢复:当主节点出现问题时,可以由从节点提供服务,实现快速的故障恢复;实际上是一种服务的冗余
- 负载均衡:在主从复制的基础上,配合读写分离,可以由主节点提供写服务,由从节点提供读服务(即写Redis数据时应用连接主节点,读Redis数据时应用连接从节点),分担服务器负载;尤其是在写少读多的场景下,通过多个从节点分担读负载
- 高可用基石:由于主从复制是哨兵和集群能够实施的基础,因此说主从复制是Redis高可用的基础
容错机制
- 主从之间使用心跳建立连接。 Slave 每秒 ping 一次,汇报自己的偏移量、获取最新的指令。Master 默认每 10s ping 一次 Slave, 检查 slave 是否在线:如果 Slave 多数掉线或者高延迟,Master 停止写和数据同步功能,保障数据稳定性。
- 在分布式系统里还会部署多个 Redis 服务器作为哨兵(除端口号外完全相同),不提供数据服务,只负责监控主从机制的运行:如果发现 Master 宕机,哨兵将通知所有机器,使 Master 下线并开启投票机制选用一个 Slave 担任 Master 。
配置连接
配置复制的方式有以下三种:
1)在配置文件中加入slaveof{masterHost}{masterPort}随Redis启动生
效。
2)在redis-server启动命令后加入—slaveof{masterHost}{masterPort}生
效。
3)直接使用命令:slaveof{masterHost}{masterPort}生效。
相比于输入指令,我们一般直接修改 conf 文件夹内的配置文件,由机器自动建立连接。
# Master
requirepass 123456 # 连接主服务器需要密码(可选)
# Slave
slaveof 127.0.0.1 6379 # 根据套接字自动连接主服务器
masterauth 123456 # 主服务器密码