Nosql概述
为什么使用Nosql
1、单机Mysql时代
90年代,一个网站的访问量一般不会太大,单个数据库完全够用。随着用户增多,网站出现以下问题
- 数据量增加到一定程度,单机数据库就放不下了
- 数据的索引(B+ Tree),一个机器内存也存放不下
- 访问量变大后(读写混合),一台服务器承受不住。
2、Memcached(缓存) + Mysql + 垂直拆分(读写分离)
网站80%的情况都是在读,每次都要去查询数据库的话就十分的麻烦!所以说我们希望减轻数据库的压力,我们可以使用缓存来保证效率!
优化过程经历了以下几个过程:
-
优化数据库的数据结构和索引(难度大) -
文件缓存,通过IO流获取比每次都访问数据库效率略高,但是流量爆炸式增长时候,IO流也承受不了 -
MemCache,当时最热门的技术,通过在数据库和数据库访问层之间加上一层缓存,第一次访问时查询数据库,将结果保存到缓存,后续的查询先检查缓存,若有直接拿去使用,效率显著提升。
3、分库分表 + 水平拆分 + MySQL集群
本质:数据库的读+写
4、如今最近的年代
如今信息量井喷式增长,各种各样的数据出现(用户定位数据,图片数据等),大数据的背景下关系型数据库(RDBMS)无法满足大量数据要求。Nosql数据库就能轻松解决这些问题。
目前一个基本的互联网项目
为什么要用NoSQL ?
用户的个人信息,社交网络,地理位置。用户自己产生的数据,用户日志等等爆发式增长! 这时候我们就需要使用NoSQL数据库的,Nosql可以很好的处理以上的情况!
什么是Nosql
**NoSQL = Not Only SQL(不仅仅是SQL)**Q
Not Only Structured Query Language
关系型数据库:列+行,同一个表下数据的结构是一样的。
非关系型数据库:数据存储没有固定的格式,并且可以进行横向扩展。
NoSQL泛指非关系型数据库,随着web2.0互联网的诞生,传统的关系型数据库很难对付web2.0时代!尤其是超大规模的高并发的社区,暴露出来很多难以克服的问题,NoSQL在当今大数据环境下发展的十分迅速,Redis是发展最快的。
Nosql特点
- 方便扩展(数据之间没有关系,很好扩展!)
- 大数据量高性能(Redis一秒可以写8万次,读11万次,NoSQL的缓存记录级,是一种细粒度的缓存,性能会比较高!)
- 数据类型是多样型的!(不需要事先设计数据库,随取随用)
- 传统的 RDBMS 和 NoSQL
传统的 RDBMS(关系型数据库)
- 结构化组织
- SQL
- 数据和关系都存在单独的表中 row col
- 操作,数据定义语言
- 严格的一致性
- 基础的事务
- …
Nosql
- 不仅仅是数据
- 没有固定的查询语言
- 键值对存储,列存储,文档存储,图形数据库(社交关系)
- 最终一致性
- CAP定理和BASE
- 高性能,高可用,高扩展
- …
了解3V与3高
大数据时代的3V :主要是描述问题的
海量Velume
多样Variety
实时Velocity
大数据时代的3高 : 主要是对程序的要求
高并发
高可扩
高性能
真正在公司中的实践:NoSQL + RDBMS 一起使用才是最强的
阿里巴巴演进分析
推荐阅读:阿里云的这群疯子https://yq.aliyun.com/articles/653511
- 一般存放在关系型数据库:Mysql,阿里巴巴使用的Mysql都是经过内部改动的。
- 文档型数据库:MongoDB
- 分布式文件系统 FastDFS
- 淘宝:TFS
- Google: GFS
- Hadoop: HDFS
- 阿里云: oss
- 搜索引擎:solr,elasticsearch
- 阿里:Isearch 多隆
- 内存数据库:Redis,Memcache
- 第三方应用
Nosql的四大分类
KV键值对
- 新浪:Redis
- 美团:Redis + Tair
- 阿里、百度:Redis + Memcache
文档型数据库(bson数据格式):
- MongoDB(掌握)
- 基于分布式文件存储的数据库。C++编写,用于处理大量文档。
- MongoDB是关系型数据库和非关系型数据库的中间产品。MongoDB是非关系型数据库中功能最丰富的,NoSQL中最像关系型数据库的数据库。
- ConthDB
列存储数据库
图关系数据库
存储的是关系,比如:朋友圈社交关系,广告推荐
分类 | 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入门
redis的安装
Windows安装Redis
https://github.com/dmajkic/redis
- 解压安装包
- 开启redis-server.exe
- 启动redis-cli.exe测试
Linux安装
-
下载安装包!redis-6.2.6tar.gz -
解压Redis的安装包!程序一般放在 /opt 目录下 -
基本环境安装 yum install gcc-c++
make
make install
-
redis默认安装路径 /usr/local/bin -
将redis的配置文件复制到 程序安装目录 /usr/local/bin/my_config 下 -
redis默认不是后台启动的,需要修改配置文件! -
通过指定的配置文件启动redis服务 -
使用redis-cli连接指定的端口号测试,Redis的默认端口6379,-p 选项能够指定连接的端口号
-
查看redis进程是否开启 -
关闭Redis服务 shutdown -
再次查看进程是否存在 -
后面我们会使用单机多Redis启动集群测试
测试性能
**redis-benchmark:**Redis官方提供的性能测试工具,参数选项如下:
简单测试:
redis-benchmark -h localhost -p 6379 -c 100 -n 100000
基础知识
redis默认有16个数据库
默认使用的第0个数据库
16个数据库为:DB 0~DB 15 可以使用select n 切换到DB n,dbsize 可以查看当前数据库的大小,与key数量相关,get database 查看当前数据库
127.0.0.1:6379> config get databases
1) "databases"
2) "16"
127.0.0.1:6379> select 8
OK
127.0.0.1:6379[8]> dbsize
(integer) 0
127.0.0.1:6379> set name sakura
OK
127.0.0.1:6379> SELECT 8
OK
127.0.0.1:6379[8]> get name
(nil)
127.0.0.1:6379[8]> DBSIZE
(integer) 0
127.0.0.1:6379[8]> SELECT 0
OK
127.0.0.1:6379> keys *
1) "counter:__rand_int__"
2) "mylist"
3) "name"
4) "key:__rand_int__"
5) "myset:__rand_int__"
127.0.0.1:6379> DBSIZE
(integer) 5
keys * :查看当前数据库中所有的key。
flushdb :清空当前数据库中的键值对。
flushall :清空所有数据库的键值对。
Redis是单线程的,Redis是基于内存操作的。
所以Redis的性能瓶颈不是CPU,而是机器内存和网络带宽。
那么为什么Redis的速度如此快呢,性能这么高呢?QPS达到10W+
Redis为什么单线程还这么快?
- 误区1:高性能的服务器一定是多线程的?
- 误区2:多线程(CPU上下文会切换!)一定比单线程效率高!
核心:Redis是将所有的数据放在内存中的,所以说使用单线程去操作效率就是最高的,多线程(CPU上下文会切换:耗时的操作!),对于内存系统来说,如果没有上下文切换效率就是最高的,多次读写都是在一个CPU上的,在内存存储数据情况下,单线程就是最佳的方案。
基础数据类型
Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件MQ。
它支持多种类型的数据结构,如 字符串(strings), 散列(hashes), 列表(lists), 集合 (sets), 有序集合(sorted sets) 与范围查询, bitmaps, hyperloglogs 和 地理空间(geospatial) 索引半径查询。
Redis 内置了 复制(replication),LUA脚本(Lua scripting), LRU驱动事件(LRU eviction),事务(transactions) 和不同级别的 磁盘持久化(persistence), 并通过Redis哨兵(Sentinel)和自动 分区(Cluster)提供高可用性(high availability)。
Redis-key的操作
查看redis中所有的键
keys *
设置键值
set key value
判断key是否存在
EXISTS key
移除一个key
move key database
设置一个key的过期时间,单位是秒
EXPIRE key time
查看一个key的过期时间
ttl key
查看key的具体类型
type key
举例说明
127 .0.0.1:6379> keys *
(empty list or set)
127 .0.0.1:6379> set name lisen
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
(integer) 1
127 .0.0.1:6379> EXISTS name
(integer) 0
127 .0.0.1:6379> move name 1
(integer) 1
127 .0.0.1:6379> keys *
1 ) "age"
127 .0.0.1:6379> set name lisen
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
(integer) 1
127 .0.0.1:6379> ttl name
(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) -
127 .0.0.1:6379> get name
(nil)
127 .0.0.1:6379> type name
127 .0.0.1:6379> type age
五大基本数据类型
String(字符串)
String类型的使用场景:value除了字符串还可以是数字,进行数字的操作与运算!
基本使用
追加字符串
APPEND key str
获取字符串长度
STRLEN key
数字操作
incr key
decr key
INCRBY key value
DECRBY key value
截取字符串
GETRANGE key start end
该命令能够截取子串,范围为:[start,end],如果end=-1代表截取至最后一个字符
字符串替换
SETRANGE key offset value
从offset索引位置开始的字符串替换为value
声明与赋值
setex key time value # 声明一个key其值为value,设置time秒后过期
setnx key value # 如果key这个键不存在,则创建key
setnx 命令通常在分布式中使用,作为乐观锁的一种实现方法!
mset key1 value1 key2 value2 ....
msetnx key1 value1 key2 value2 ...
mget key1 key2 key3 ...
msetnx 是一个原子操作,如果其中有一个key在数据库中存在,那么其余声明均不会执行!
getset key new_value
如果原来key不存在,getset 返回null
举例说明
127.0.0.1:6379> set key1 v1
OK
127.0.0.1:6379> get key1
"v1"
127.0.0.1:6379> keys *
1) "key1"
127.0.0.1:6379> EXISTS key1
(integer) 1
127.0.0.1:6379> APPEND key1 hello,world
(integer) 13
127.0.0.1:6379> get key1
"v1hello,world"
127.0.0.1:6379> STRLEN key1
(integer) 13
127.0.0.1:6379>
127.0.0.1:6379> set views 0
OK
127.0.0.1:6379> incr views
(integer) 1
127.0.0.1:6379> decr views
(integer) 0
127.0.0.1:6379> incrby views 10
(integer) 10
127.0.0.1:6379> decrby views 10
(integer) 0
127.0.0.1:6379> set key1 hello,world
OK
127.0.0.1:6379> get key1
"hello,world"
127.0.0.1:6379> getrange key1 0 1
"he"
127.0.0.1:6379> getrange key1 0 -1
"hello,world"
127.0.0.1:6379> setrange key1 0 HELLO
(integer) 11
127.0.0.1:6379> get key1
"HELLO,world"
127.0.0.1:6379>
127.0.0.1:6379> setex name 100 cjx
OK
127.0.0.1:6379> ttl name
(integer) 91
127.0.0.1:6379> keys *
1) "name"
2) "key1"
127.0.0.1:6379> setnx name cjx2
(integer) 0
127.0.0.1:6379> ttl name
(integer) -2
127.0.0.1:6379> keys *
1) "key1"
127.0.0.1:6379> setnx name cjx2
(integer) 1
127.0.0.1:6379> mset k1 v1 k2 v2 k3 v3
OK
127.0.0.1:6379> keys *
1) "name"
2) "key1"
3) "k3"
4) "k2"
5) "k1"
127.0.0.1:6379> mget k1 k2 k3
1) "v1"
2) "v2"
3) "v3"
127.0.0.1:6379> msetnx k3 v33 k4 v4
(integer) 0
127.0.0.1:6379> keys *
1) "name"
2) "key1"
3) "k3"
4) "k2"
5) "k1"
127.0.0.1:6379> getset db INNODB
(nil)
127.0.0.1:6379> get db
"INNODB"
List(列表)
redis中,List可以作为:堆栈、队列、阻塞队列!
所有的List命令都是以 ’ l '开头的
插入元素
LPUSH key value
RPUSH key value
Linsert key before|after preValue value
获取元素
LINDEX key index
LRANGE key start end
LRANGE 类似于getRange 命令,获取到[start,end]的元素,如果end==-1,则获取至队尾的元素。
移除元素
LPOP key
RPOP key
lrem key count value
rpoplpush list1 list2
使用LPOP 、RPOP 命令移除的元素会被返回!
lrem 指令会删除 count 个value元素,如果count为0则删除list中所有值为value元素。
返回长度
LLen key
截取元素
ltrim key start end
ltrim 命令能够让原列表截取[start,end]的部分并赋值给原list
替换元素
LSET key index value
需要注意:如果列表不存在或列表index索引位置为空,则会报错!
举例说明
127.0.0.1:6379> LPUSH list1 one two three
(integer) 3
127.0.0.1:6379> LRANGE list1 0 -1
1) "three"
2) "two"
3) "one"
127.0.0.1:6379> RPUSH list1 zero
(integer) 4
127.0.0.1:6379> LRANGE list1 0 -1
1) "three"
2) "two"
3) "one"
4) "zero"
127.0.0.1:6379> LINDEX list1 1
"two"
127.0.0.1:6379> lrange list 0 -1
1) "two"
2) "three"
3) "fout"
127.0.0.1:6379> linsert list before "two" one
(integer) 4
127.0.0.1:6379> lrange list 0 -1
1) "one"
2) "two"
3) "three"
4) "fout"
127.0.0.1:6379> LPOP list1
"three"
127.0.0.1:6379> LRANGE list1 0 -1
1) "two"
2) "one"
3) "zero"
127.0.0.1:6379> RPOP list1
"zero"
127.0.0.1:6379> LRANGE list1 0 -1
1) "two"
2) "one"
127.0.0.1:6379> lrange list 0 -1
1) "two"
2) "twoo"
3) "twoo"
4) "twoo"
127.0.0.1:6379> lrem list 0 twoo
(integer) 3
127.0.0.1:6379> lrange list 0 -1
1) "two"
127.0.0.1:6379> lrange list 0 -1
1) "two"
2) "three"
3) "fout"
4) "five"
127.0.0.1:6379> ltrim list 0 2
OK
127.0.0.1:6379> lrange list 0 -1
1) "two"
2) "three"
3) "fout"
Set(集合)
与Java一样,Redis中的set无序、不重复
添加元素
sadd key value...
查看元素
smembers key
sismember key value
查看元素个数
scard key
移除元素
srem key value
随机操作
srandmember key
spop key
移动元素
smove set1 set2 value
集合操作
SDIFF key1 key2
SINTER key1 key2
SUNION key1 key2
集合操作允许多集合运算,如果只有一个集合作为参数那么其比较对象就是自己。
举例说明
---------------SADD--SCARD--SMEMBERS--SISMEMBER--------------------
127.0.0.1:6379> SADD myset m1 m2 m3 m4
(integer) 4
127.0.0.1:6379> SCARD myset
(integer) 4
127.0.0.1:6379> smembers myset
1) "m4"
2) "m3"
3) "m2"
4) "m1"
127.0.0.1:6379> SISMEMBER myset m5
(integer) 0
127.0.0.1:6379> SISMEMBER myset m2
(integer) 1
127.0.0.1:6379> SISMEMBER myset m3
(integer) 1
---------------------SRANDMEMBER--SPOP----------------------------------
127.0.0.1:6379> SRANDMEMBER myset 3
1) "m2"
2) "m3"
3) "m4"
127.0.0.1:6379> SRANDMEMBER myset
"m3"
127.0.0.1:6379> SPOP myset 2
1) "m1"
2) "m4"
---------------------SMOVE--SREM----------------------------------------
127.0.0.1:6379> SMOVE myset newset m3
(integer) 1
127.0.0.1:6379> SMEMBERS myset
1) "m4"
2) "m2"
3) "m1"
127.0.0.1:6379> SMEMBERS newset
1) "m3"
127.0.0.1:6379> SREM newset m3
(integer) 1
127.0.0.1:6379> SMEMBERS newset
(empty list or set)
-----------------------------SDIFF------------------------------------
127.0.0.1:6379> SDIFF setx sety setz
1) "m4"
127.0.0.1:6379> SDIFF setx sety
1) "m4"
2) "m1"
127.0.0.1:6379> SDIFF sety setx
1) "m5"
-------------------------SINTER---------------------------------------
127.0.0.1:6379> SINTER setx sety setz
1) "m6"
127.0.0.1:6379> SINTER setx sety
1) "m2"
2) "m6"
-------------------------SUNION---------------------------------------
127.0.0.1:6379> SUNION setx sety setz
1) "m4"
2) "m6"
3) "m3"
4) "m2"
5) "m1"
6) "m5"
127.0.0.1:6379> SUNION setx sety
1) "m4"
2) "m6"
3) "m2"
4) "m1"
5) "m5"
Hash(哈希集合)
Map集合,key-Map集合,这时候值是一个Map集合。每一个key对应着一个HashMap,HashMap存放的键值对均是String类型
添加元素
hset key key_map value
hmset key key_map1 value1 key_map2 value2 ...
hsetnx key key_map value
获取元素
hget key key_map
hmget key key_map1 key_map2 ...
hgetall key
hkeys key
hvals key
删除元素
hdel key key_map
获取数量
hlen key
判断是否存在
HEXISTS key key_map
数字操作
HINCRBY key key_map value
HDECRBY key key_map value
------------------------HSET--HMSET--HSETNX----------------
127.0.0.1:6379> HSET studentx name sakura
(integer) 1
127.0.0.1:6379> HSET studentx name gyc
(integer) 0
127.0.0.1:6379> HSET studentx age 20
(integer) 1
127.0.0.1:6379> HMSET studentx sex 1 tel 15623667886
OK
127.0.0.1:6379> HSETNX studentx name gyc
(integer) 0
127.0.0.1:6379> HSETNX studentx email 12345@qq.com
(integer) 1
----------------------HEXISTS--------------------------------
127.0.0.1:6379> HEXISTS studentx name
(integer) 1
127.0.0.1:6379> HEXISTS studentx addr
(integer) 0
-------------------HGET--HMGET--HGETALL-----------
127.0.0.1:6379> HGET studentx name
"gyc"
127.0.0.1:6379> HMGET studentx name age tel
1) "gyc"
2) "20"
3) "15623667886"
127.0.0.1:6379> HGETALL studentx
1) "name"
2) "gyc"
3) "age"
4) "20"
5) "sex"
6) "1"
7) "tel"
8) "15623667886"
9) "email"
10) "12345@qq.com"
--------------------HKEYS--HLEN--HVALS--------------
127.0.0.1:6379> HKEYS studentx
1) "name"
2) "age"
3) "sex"
4) "tel"
5) "email"
127.0.0.1:6379> HLEN studentx
(integer) 5
127.0.0.1:6379> HVALS studentx
1) "gyc"
2) "20"
3) "1"
4) "15623667886"
5) "12345@qq.com"
-------------------------HDEL--------------------------
127.0.0.1:6379> HDEL studentx sex tel
(integer) 2
127.0.0.1:6379> HKEYS studentx
1) "name"
2) "age"
3) "email"
-------------HINCRBY--HINCRBYFLOAT------------------------
127.0.0.1:6379> HINCRBY studentx age 1
(integer) 21
127.0.0.1:6379> HINCRBY studentx name 1
(error) ERR hash value is not an integer
127.0.0.1:6379> HINCRBYFLOAT studentx weight 0.6
"90.8"
Zset(有序集合)
在Set集合的基础上,增加了一个值Score,用于排序。如果Score相同,就按照value进行字典排序。
添加元素
zadd key score value...
输出
zrange key min max [BYSCORE|BYLEX] [WITHSCORE] [limit offset count]
- BYSCORE:根据score值排序
- BYLEX:根据value值排序,如果是字符串就根据字典序排序
zRangeByScore key min max [WITHSCORES] [limit offset count]
- min、max:输出数据的范围,在该范围的项才能参与排序,一般使用
-inf +inf 表示所有项均参与排序 - WITHSCORES:同时显示value与score
- limit 、offset、count:类似于sql语句
zRevRange key start stop [WITHSCORES]
输出[start,stop]索引的元素,如果stop为-1,等价于输出至最后一个元素
移除元素
zrem key value
查询
zcard key
zcount key min max
zrank key member
zscore key member
集合操作
ZDIFF key1 key2
ZINTER key1 key2
ZUNION key1 key2
ZDIFFSTORE 目标集合 numbers key1 key2 key3...
ZUNIONSTORE 目标集合 numbers key1 key2 key3...
ZDIFF 命令能够根据key1元素比较key2、key3输入集合求出差集并存储与目标集合中,输入集合数量由numbers给定
-------------------ZADD--ZCARD--ZCOUNT--------------
127.0.0.1:6379> ZADD myzset 1 m1 2 m2 3 m3
(integer) 2
127.0.0.1:6379> ZCARD myzset
(integer) 2
127.0.0.1:6379> ZCOUNT myzset 0 1
(integer) 1
127.0.0.1:6379> ZCOUNT myzset 0 2
(integer) 2
----------------ZINCRBY--ZSCORE--------------------------
127.0.0.1:6379> ZINCRBY myzset 5 m2
"7"
127.0.0.1:6379> ZSCORE myzset m1
"1"
127.0.0.1:6379> ZSCORE myzset m2
"7"
--------------ZRANK--ZRANGE-----------------------------------
127.0.0.1:6379> ZRANK myzset m1
(integer) 0
127.0.0.1:6379> ZRANK myzset m2
(integer) 2
127.0.0.1:6379> ZRANGE myzset 0 1
1) "m1"
2) "m3"
127.0.0.1:6379> ZRANGE myzset 0 -1
1) "m1"
) "m3"
3) "m2"
------------------ZRANGEBYLEX---------------------------------
127.0.0.1:6379> zadd testset 0 abc 0 add 0 amaze 0 apple 0 back 0 java 0 redis
(integer) 7
127.0.0.1:6379> ZRANGEBYLEX testset - +
1) "abc"
2) "add"
3) "amaze"
4) "apple"
5) "back"
6) "java"
7) "redis"
127.0.0.1:6379> ZRANGEBYLEX testset - + LIMIT 0 3
1) "abc"
2) "add"
3) "amaze"
127.0.0.1:6379> ZRANGEBYLEX testset - + LIMIT 3 3
1) "apple"
2) "back"
3) "java"
127.0.0.1:6379> ZRANGEBYLEX testset (- [apple
1) "abc"
2) "add"
3) "amaze"
4) "apple"
127.0.0.1:6379> ZRANGEBYLEX testset [apple [java
1) "apple"
2) "back"
3) "java"
127.0.0.1:6379> ZRANGEBYLEX testset (- (apple
1) "abc"
2) "add"
3) "amaze"
------------------ZREM--ZREMRANGEBYLEX--ZREMRANGBYRANK--ZREMRANGEBYSCORE--------------------------------
127.0.0.1:6379> ZREM testset abc
(integer) 1
127.0.0.1:6379> ZREMRANGEBYLEX testset [apple [java
(integer) 3
127.0.0.1:6379> ZREMRANGEBYRANK testset 0 1
(integer) 2
127.0.0.1:6379> ZREMRANGEBYSCORE myzset 0 3
(integer) 2
----------------ZREVRANGE--ZREVRANGEBYSCORE--ZREVRANGEBYLEX-----------
127.0.0.1:6379> zadd myset 1 m1 2 m2 3 m3 4 m4 7 m7 9 m9
127.0.0.1:6379> ZRANGE myset 0 -1
1) "m1"
2) "m2"
3) "m3"
4) "m4"
5) "m7"
6) "m9"
127.0.0.1:6379> ZREVRANGE myset 0 3
1) "m9"
2) "m7"
3) "m4"
4) "m3"
127.0.0.1:6379> ZREVRANGE myset 2 4
1) "m4"
2) "m3"
3) "m2"
127.0.0.1:6379> ZREVRANGEBYSCORE myset 6 2
1) "m4"
2) "m3"
3) "m2"
127.0.0.1:6379> ZREVRANGEBYSCORE myset 2 6
(empty list or set)
127.0.0.1:6379> ZREVRANGEBYLEX testset [java (add
1) "java"
2) "back"
3) "apple"
4) "amaze"
-----------------------------补充---------------------------
127.0.0.1:6379> ZRANGE testset 0 -1
1) "java"
2) "redis"
127.0.0.1:6379> ZADD testset 0 hehe 0 xixi
(integer) 2
127.0.0.1:6379> ZRANGE testset 0 -1
1) "hehe"
2) "java"
3) "redis"
4) "xixi"
127.0.0.1:6379> ZADD testset 1 aaa
(integer) 1
127.0.0.1:6379> ZRANGE testset 0 -1
1) "hehe"
2) "java"
3) "redis"
4) "xixi"
5) "aaa"
-------------------------ZREVRANK------------------------------
127.0.0.1:6379> ZREVRANK myset m7
(integer) 1
127.0.0.1:6379> ZREVRANK myset m2
(integer) 4
-------------------ZINTERSTORE--ZUNIONSTORE---------------------------------
127.0.0.1:6379> ZRANGE mathscore 0 -1
1) "xg"
2) "xm"
3) "xh"
127.0.0.1:6379> ZRANGE enscore 0 -1
1) "xm"
2) "xg"
3) "xh"
127.0.0.1:6379> ZRANGE enscore 0 -1 withscores
1) "xm"
2) "70"
3) "xg"
4) "90"
5) "xh"
6) "93"
127.0.0.1:6379> ZINTERSTORE sumscore 2 mathscore enscore
(integer) 3
127.0.0.1:6379> ZRANGE sumscore 0 -1 withscores
1) "xm"
2) "160"
3) "xg"
4) "177"
5) "xh"
6) "188"
127.0.0.1:6379> ZUNIONSTORE lowestscore 2 mathscore enscore AGGREGATE MIN
(integer) 3
127.0.0.1:6379> ZRANGE lowestscore 0 -1 withscores
1) "xm"
2) "70"
3) "xg"
4) "87"
5) "xh"
6) "93"
三种特殊数据类型
geospatial 地理位置
添加地理位置 (geoadd)
geoadd key latitude(纬度) longitude(经度) name ...
规则:两级无法直接添加,我们一般会下载城市数据,直接通过java程序一次性导入!
当坐标位置超出上述指定范围时,该命令将会返回一个错误。
获取地理位置(geopos)
geopos key member [member...]
返回的一定是一个坐标!
返回两个位置直线距离(geodist)
geodist key member1 member2 [unit]
该指令默认是以米为单位,通过修改unit改变输出单位,可输入值为:
给定的经纬度为中心,找出某一半径内的元素(georadius )
georadius key longitude latitude radius [unit] [withcoord] [count] [withdist] [count n]
- unit : 单位,使用规则等同
geodist - withcoord :显示经度与纬度
- withdist:显示距离,单位与半径单位相同
- count n :只显示前n个数据,按照距离递增排序
给定坐标为中心,找出某一半径内的元素(georadiusbymember)
georadiusbyMember key member radius [unit] [withcoord] [count] [withdist] [count n]
GEO底层的实现原理是Zset,可以使用Zset的操作处理GEO数据
127.0.0.1:6379[13]> ZRANGE china:city 0 -1
1) "chongqin"
2) "shenzheng"
3) "hangzhou"
4) "jiangsu"
5) "shanghai"
6) "tianjing"
7) "beijing"
127.0.0.1:6379[13]> ZRANGEBYLEX china:city - +
1) "chongqin"
2) "shenzheng"
3) "hangzhou"
4) "jiangsu"
5) "shanghai"
6) "tianjing"
7) "beijing"
127.0.0.1:6379[13]> ZREM china:city jiangsu
(integer) 1
127.0.0.1:6379[13]> ZRANGE china:city 0 -1
1) "chongqin"
2) "shenzheng"
3) "hangzhou"
4) "shanghai"
5) "tianjing"
6) "beijing"
举例说明
geoadd
127.0.0.1:6379[13]> GEOADD china:city 116.41667 39.91667 beijing
(integer) 1
127.0.0.1:6379[13]> GEOADD china:city 121.43333 34.50000 shanghai
(integer) 1
127.0.0.1:6379[13]> GEOADD china:city 117.20000 39.13333 tianjing
(integer) 1
127.0.0.1:6379[13]> GEOADD china:city 118.78333 32.05000 jiangsu
127.0.0.1:6379[13]> ZRANGE china:city 0 -1
1) "jiangsu"
2) "shanghai"
3) "tianjing"
4) "beijing"
geopos
127.0.0.1:6379[13]> GEOPOS china:city jiangsu
1) 1) "118.78332942724227905"
2) "32.04999907785209956"
127.0.0.1:6379[13]> GEOPOS china:city jiangsu shanghai
1) 1) "118.78332942724227905"
2) "32.04999907785209956"
2) 1) "121.4333304762840271"
2) "34.49999971716130887"
geodist
127.0.0.1:6379> GEODIST china:city zhangjiajie xian
"589959.2719"
georadius
127.0.0.1:6379[13]> geoadd china:city 114.06667 22.61667 shenzheng
(integer) 1
127.0.0.1:6379[13]> geoadd china:city 120.20000 30.26667 hangzhou
(integer) 1
127.0.0.1:6379[13]> geoadd china:city 106.45000 29.56667 chongqin
(integer) 1
127.0.0.1:6379[13]> GEORADIUS china:city 110 30 1000 km
1) "chongqin"
2) "shenzheng"
3) "hangzhou"
4) "jiangsu"
127.0.0.1:6379[13]> GEORADIUS china:city 110 30 500 km
1) "chongqin"
127.0.0.1:6379[13]> GEORADIUS china:city 110 30 1000 km withcoord withdist
1) 1) "chongqin"
2) "346.0548"
3) 1) "106.4500012993812561"
2) "29.56666939001875249"
2) 1) "shenzheng"
2) "915.6424"
3) 1) "114.06667023897171021"
2) "22.61666928352524764"
3) 1) "hangzhou"
2) "981.3098"
3) 1) "120.20000249147415161"
2) "30.2666706589875858"
4) 1) "jiangsu"
2) "867.3741"
3) 1) "118.78332942724227905"
2) "32.04999907785209956"
127.0.0.1:6379[13]> GEORADIUS china:city 110 30 1000 km withcoord withdist count 1
1) 1) "chongqin"
2) "346.0548"
3) 1) "106.4500012993812561"
2) "29.56666939001875249"
hyperloglog
适用场景: 网站UV量。传统用set统计,但若存在大量用户id,则太消耗内容且麻烦,若只为计数且允许有错误率(0.81%),则可行,否则还是用set统计
基数:集合中不重复元素个数。如{1, 3, 5, 5 ,7}则为{1,3,5,7},基数为4
命令 | 描述 |
---|
PFADD key element1 [elememt2…] | 添加指定元素到 HyperLogLog 中 | PFCOUNT key [key] | 返回给定 HyperLogLog 的基数估算值。 | PFMERGE destkey sourcekey [sourcekey…] | 将多个 HyperLogLog 合并为一个 HyperLogLog |
127.0.0.1:6379> PFADD mykey a b c d e f g h i j
(integer) 1
127.0.0.1:6379> PFCOUNT mykey
(integer) 10
127.0.0.1:6379> PFADD mykey2 i j z x c v b n m
(integer) 1
127.0.0.1:6379> PFCOUNT mykey2
(integer) 9
127.0.0.1:6379> PFMERGE mykey3 mykey mykey2
OK
127.0.0.1:6379> PFCOUNT mykey3
(integer) 15
127.0.0.1:6379>
Bitmaps
Bitmaps通常存储两个状态的数据,例如:登录或未登录、打开或未打卡…
bitmaps的具体实现类是String类型
常见指令
命令 | 描述 |
---|
setbit key offset value | 为指定key的offset位设置值 | getbit key offset | 获取offset位的值 | bitcount key [start end] | 统计字符串被设置为1的bit数,也可以指定统计范围按字节 | bitop operration destkey key[key…] | 对一个或多个保存二进制位的字符串 key 进行位元操作,并将结果保存到 destkey 上。 | BITPOS key bit [start] [end] | 返回字符串里面第一个被设置为1或者0的bit位。start和end只能按字节,不能按位 |
举例说明
------------setbit--getbit--------------
127.0.0.1:6379> setbit sign 0 1
(integer) 0
127.0.0.1:6379> setbit sign 2 1
(integer) 0
127.0.0.1:6379> setbit sign 3 1
(integer) 0
127.0.0.1:6379> setbit sign 5 1
(integer) 0
127.0.0.1:6379> type sign
string
127.0.0.1:6379> getbit sign 2
(integer) 1
127.0.0.1:6379> getbit sign 3
(integer) 1
127.0.0.1:6379> getbit sign 4
(integer) 0
-----------bitcount----------------------------
127.0.0.1:6379> BITCOUNT sign
(integer) 4
127.0.0.1:6379[13]> get sign
"\xb4"
redis基础
事务
在SQL中事务通常有四大特性:原子性(A)、一致性?、隔离性(I)、持久性(D)
Redis单条命令保存原子性,但是Redis事务不保障原子性!
Redis事务没有隔离级别,也就是没有隔离性!
Redis事务本质:一组命令的集合。 ----------------- 队列 set set set 执行 ------------------- 事务中每条命令都会被序列化,执行过程中按顺序执行,不允许其他命令进行干扰。 一次性 顺序性 排他性 1 Redis事务没有隔离级别的概念 2 Redis单条命令是保证原子性的,但是事务不保证原子性!
Redis事务操作过程
- 开启事务(
multi ) - 命令入队
- 执行事务(
exec )
所以事务中的命令在加入队列时没有被执行,只有提交时才会开始执行并一次性完成
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 k1
QUEUED
127.0.0.1:6379> set k3 v3
QUEUED
127.0.0.1:6379> keys *
QUEUED
127.0.0.1:6379> exec
1) OK
2) OK
3) "v1"
4) OK
5) 1) "k3"
2) "k2"
3) "k1"
放弃事务(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> DISCARD
OK
127.0.0.1:6379> EXEC
(error) ERR EXEC without MULTI
127.0.0.1:6379> get k1
(nil)
Redis事务异常
Redis事务存在两种异常:运行时异常、编译时异常
编译时异常
编译时异常出错,所有的命令都不会执行!
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> error k1
(error) ERR unknown command `error`, with args beginning with: `k1`,
127.0.0.1:6379> get k2
QUEUED
127.0.0.1:6379> EXEC
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> get k1
(nil)
运行时异常
运行时异常出错,错误的命令不会执行,其余命令均可以正常执行!Redis事务不保证原子性!
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> INCR k1
QUEUED
127.0.0.1:6379> get k2
QUEUED
127.0.0.1:6379> exec
1) OK
2) OK
3) (error) ERR value is not an integer or out of range
4) "v2"
Redis事务虽然说是事务,但是其更像是一种批处理。
乐观锁
乐观锁的普通实现方式是定义一个version字段,当某一个线程修改某个参数时,让version的字段自动改变。请求到达时首先会查询当前version,当修改数据时再一次查询version,如果version前后不一致说明有其他线程参与了修改,修改失败。
Redis中乐观锁可以使用关键字watch 监听version字段,每一次修改都会比对前后的version实现乐观锁。
watch key
unwatch key
举例说明
正常执行成功
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
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
模拟多线程修改值,使用watch实现乐观锁操作!
127.0.0.1:6379> set money 100
OK
127.0.0.1:6379> set out 0
127.0.0.1:6379> watch 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 80
OK
无论是运行成功还是修改失败或者调用discard 退出事务,该线程会自动放弃锁。
判断为失败的情况
127.0.0.1:6379[13]> watch money
OK
127.0.0.1:6379[13]> MULTI
OK
127.0.0.1:6379[13]> DECRBY money 20
QUEUED
127.0.0.1:6379[13]> INCRby out 20
QUEUED
127.0.0.1:6379[13]> exec
1) (integer) 60
2) (integer) 20
Jedis
Jedis 是 Redis 官方推荐的 java连接开发工具! 使用Java 操作Redis 中间件!
导入对应的依赖
<dependencies>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.2.0</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.62</version>
</dependency>
</dependencies>
编码测试:
import redis.clients.jedis.Jedis;
public class TestPing {
public static void main(String[] args) {
Jedis jedis = new Jedis("127.0.0.1",6379);
System.out.println(jedis.ping());
}
}
常用的API
所有的api命令,就是我们对应的上面学习的指令,一个都没有变化! 这里没有展示
使用事务
public class TestTX {
public static void main(String[] args) {
Jedis jedis = new Jedis("39.99.xxx.xx", 6379);
JSONObject jsonObject = new JSONObject();
jsonObject.put("hello", "world");
jsonObject.put("name", "kuangshen");
Transaction multi = jedis.multi();
String result = jsonObject.toJSONString();
try {
multi.set("user1", result);
multi.set("user2", result);
multi.exec();
}catch (Exception e){
multi.discard();
} finally {
System.out.println(jedis.get("user1"));
System.out.println(jedis.get("user2"));
jedis.close();
}
}
}
SpringBoot整合Redis
备注:从SpringBoot2.x之后,原先使用的Jedis被lettuce替代
Jedis:采用直连,模拟多个线程操作会出现安全问题。为避免此问题,需要使用Jedis Pool连接池!类似于BIO模式
lettuce:采用netty网络框架,对象可以在多个线程中被共享,完美避免线程安全问题,减少线程数据,类似于NIO模式
首先先查看RedisAutoConfiguration中的源码
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {
@Bean
@ConditionalOnMissingBean(name = "redisTemplate")
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory)
throws UnknownHostException {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
@Bean
@ConditionalOnMissingBean
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory)
throws UnknownHostException {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
}
上面的@Import注解导入了两个配置类,有Lettuce和Jedis,可以点开这两个类查看
对比一下可以发现,Jedis配置类中有两个类是默认不存在的,不存在就无法使用
1、导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2、属性配置
# 配置Redis
spring.redis.host=127.0.0.1
spring.redis.port=6379
备注:这边的配置,需要注意的是,SpringBoot整合的是Lettuce,如果在配置文件中添加额外的配置,比如Redis的最大等待时间、超时时间等,在对应的RedisProperties类所映射的配置文件中,属性名称一定要加上带有lettuce,如果加上jedis,它默认不会生效
3、测试连接
@Test
void contextLoads() {
ValueOperations ops = redisTemplate.opsForValue();
redisTemplate.opsForGeo();
ops.set("k1", "xiaohuang");
Object o = ops.get("k1");
System.out.println(o);
}
测试了之后在控制台可以成功获取
但是在Linux中的Redis获取结果得到的却是乱码
127.0.0.1:6379> keys *
1) "\xac\xed\x00\x05t\x00\x02k1"
这与RedisTemplate默认序列化有关,Java的默认序列化规则Redis无法识别,因此输入的数据无法被Redis反序列化输出的就是乱码。
先展示RedisTemplate的部分源码
private @Nullable RedisSerializer keySerializer = null;
private @Nullable RedisSerializer valueSerializer = null;
private @Nullable RedisSerializer hashKeySerializer = null;
private @Nullable RedisSerializer hashValueSerializer = null;
@Override
public void afterPropertiesSet() {
super.afterPropertiesSet();
boolean defaultUsed = false;
if (defaultSerializer == null) {
defaultSerializer = new JdkSerializationRedisSerializer(
classLoader != null ? classLoader : this.getClass().getClassLoader());
}
}
}
而默认的RedisTemplate中的所有序列化器都是使用这个序列化器:
自定义RedisTemplate
@Bean
@SuppressWarnings("all")
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory)
throws UnknownHostException {
RedisTemplate<String, Object> template = new RedisTemplate<>();
Jackson2JsonRedisSerializer serializer =new Jackson2JsonRedisSerializer(Object.class);
template.setDefaultSerializer(serializer);
template.setConnectionFactory(redisConnectionFactory);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance);
serializer.setObjectMapper(om);
StringRedisSerializer srs = new StringRedisSerializer();
template.setKeySerializer(srs);
template.setHashKeySerializer(srs);
template.setValueSerializer(serializer);
template.setHashValueSerializer(serializer);
template.afterPropertiesSet();
return template;
}
redis进阶
redis.conf解析
Redis的启动必须要用到配置文件!
Units 单位
容量单位不区分大小写,同时注意:k!=kb
includes 包含
可以使用 include 包含多个配置文件
?
network 网络
网络,表示Redis启动时开放的端口默认与本机绑定
bind 127.0.0.1
Redis指定监听端口,默认为6379
port 6379
表示服务器闲置多长时间(秒)后被关闭,如果这个这个数值为0,表示这个功能不起作用
timeout 300
是否开启保护模式,Redis默认开启,如果没有设置bind的IP地址和Redis密码,那么服务就会默认只能在本机运行
protected-mode yes
General 通用
是否以守护进程的方式运行,即后台运行,一般默认为no,需要手动改为yes否则无法后台运行
daemonize yes
如果以守护进程的方式运行,就需要指定一个pid文件,在Redis启动时创建,退出时删除
pidfile /var/run/redis_6379.pid
配置日志等级,日志等级的可选项如下:
- debug:打印的信息较多,在工作中主要用于开发和测试
- verbose:打印的信息仅次于debug,但是格式较为工整
- notice:Redis默认配置,在生产环境中使用
- warning:只打印一些重要信息,比如警告和错误
loglevel notice
打印的日志文件名称,如果为空,表示标准输出,在配置守护进程的模式下会将输出信息保存到/dev/null
logfile ""
数据库支持数量,16个
databases 16
SNAPSHOTTING 快照(RDB配置)
中文翻译为快照,如果在规定的时间内,数据发生了几次更新,那么就会将数据同步备份到一个文件中
Redis的持久化有两种方式,一种是RDB,一种是AOF。SNAPSHOTTING主要针对的是Redis持久化中的RDB
Redis是一个内存数据库,如果不采用持久化对数据进行保存,那么就会出现断电即失的尴尬场面
save 900 1
save 300 10
save 60 10000
top-writes-on-bgsave-error yes
rdbcompression yes
rdbchecksum yes
dir ./
Security 安全
箭头处按照:requirepass 密码 的格式设置登录密码,使用指令也同样可以实现,不过需要保存指令保存配置,指令格式如下:
127.0.0.1:6379> CONFIG GET requirepass
1) "requirepass"
2) ""
127.0.0.1:6379> CONFIG SET requirepass "123456"
OK
127.0.0.1:6379> set name "cjx"
OK
127.0.0.1:6379> get name "cjx"
Invalid argument(s)
127.0.0.1:6379> AUTH "123456"
OK
127.0.0.1:6379> get name
"cjx22"
Client 客户端
maxclients 10000
maxmemory <bytes>
maxmemory-policy noeviction
redis 中的默认的过期策略是 volatile-lru 。
设置方式
config set maxmemory-policy volatile-lru
maxmemory-policy 策略
**1、volatile-lru:**只对设置了过期时间的key进行LRU(默认值)
2、allkeys-lru : 删除lru算法的key
**3、volatile-random:**随机删除即将过期key
**4、allkeys-random:**随机删除
5、volatile-ttl : 删除最近即将过期的
6、noeviction : 永不过期,返回错误
AOF配置
Redis持久化
面试和工作,持久化都是重点! Redis 是内存数据库,如果不将内存中的数据库状态保存到磁盘,那么一旦服务器进程退出,服务器中的数据库状态也会消失。所以 Redis 提供了持久化功能!
RDB(Redis DataBase)
什么是RDB
在指定的时间间隔内将内存中的数据集快照写入磁盘,也就是行话讲的Snapshot快照,它恢复时是将快照文件直接读到内存里。
Redis会单独创建(fork)一个子进程来进行持久化,会先将数据写入到一个临时文件中,待持久化过程都结束了,再用这个临时文件**替换**上次持久化好的文件。整个过程中,主进程是不进行任何IO操作的。这就确保了极高的性能。如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那RDB方式要比AOF方式更加的高效。RDB的缺点是最后一次持久化后的数据可能丢失。我们默认的就是RDB,一般情况下不需要修改这个配置!
有时候在生产环境我们会将这个文件进行备份!
rdb保存的文件是dump.rdb 都是在我们的配置文件中快照中进行配置的!
触发机制
- 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) "./" # 如果在这个目录下存在 dump.rdb文件,启动就会自动恢复其中的数据
几乎就他自己默认的配置就够用了,但是我们还是需要去学习!
优点:
- 1、适合大规模的数据恢复!
- 2、对数据的完整性要求不高!
缺点:
- 1、需要一定的时间间隔进程操作!如果redis意外宕机了,这个最后一次修改数据就没有的了!
- 2、fork进程的时候,会占用一定的内容空间!!
AOF(Append Only File)
将我们的所有命令都记录下来,history,恢复的时候就把这个文件全部在执行一遍!
什么是AOF?
以日志的形式来记录每个写操作,将Redis执行过的所有指令记录下来(读操作不记录),只许追加文件但不可以改写文件,redis启动之初会读取该文件重新构建数据,换言之,redis重启的话就根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作
Aof保存的是 appendonly.aof 文件
什么是AOF
快照功能(RDB)并不是非常耐久(durable): 如果 Redis因为某些原因而造成故障停机,那么服务器将丢失最近写入、以及未保存到快照中的那些数据。 从 1.1 版本开始, Redis 增加了一种完全耐久的持久化方式: AOF 持久化。
如果要使用AOF,需要修改配置文件
appendonly no yes则表示启用AOF
默认是不开启的,我们需要手动配置,然后重启redis,就可以生效了!
如果这个aof文件有错误,这时候redis是启动不起来的,我需要修改这个aof文件
redis给我们提供了一个工具redis-check-aof修复,修复代码为:redis-check-aof --fix appendonly.aof ,这样的修复可能会造成某些数据的丢失。
重写规则说明
aof 默认就是文件的无限追加,文件会越来越大
如果 aof 文件大于 64m,太大了! fork一个新的进程来将我们的文件进行重写!
AOF 文件重写的实现
- AOF重写并不需要对原有AOF文件进行任何的读取,写入,分析等操作,这个功能是通过读取服务器当前的数据库状态来实现的。
127.0.0.1:6379> RPUSH list "A" "B"
(integer) 2
127.0.0.1:6379> RPUSH list "C"
(integer) 3
127.0.0.1:6379> RPUSH list "D" "E"
(integer) 5
127.0.0.1:6379> LPOP list
"A"
127.0.0.1:6379> LPOP list
"B"
127.0.0.1:6379> RPUSH list "F" "G"
(integer) 5
127.0.0.1:6379> LRANGE list 0 -1
1) "C"
2) "D"
3) "E"
4) "F"
5) "G"
- 当前列表键list在数据库中的值就为
["C", "D", "E", "F", "G"] 。要使用尽量少的命令来记录list键的状态,最简单的方式不是去读取和分析现有AOF文件的内容,,而是直接读取list键在数据库中的当前值,然后用一条RPUSH list "C" "D" "E" "F" "G" 代替前面的6条命令。
优点:
- 1、每一次修改都同步,文件的完整会更加好!
- 2、每秒同步一次,可能会丢失一秒的数据
- 3、从不同步,效率最高的!
缺点:
- 1、相对于数据文件来说,aof远远大于 rdb,修复的速度也比 rdb慢!
- 2、Aof 运行效率也要比 rdb 慢,所以我们redis默认的配置就是rdb持久化
RDB和AOF选择
有点 | RDB | AOF |
---|
启动优先级 | 低 | 高 | 体积 | 小 | 大 | 恢复速度 | 快 | 慢 | 数据安全性 | 丢数据 | 根据策略决定 |
如何选择使用哪种持久化方式?
如果仅仅是使用Redis当做缓存,那么甚至可以不用持久化!但是一般来说, 如果想达到足以媲美 PostgreSQL 的数据安全性, 你应该同时使用两种持久化功能。
如果你非常关心你的数据, 但仍然可以承受数分钟以内的数据丢失, 那么你可以只使用 RDB 持久化。
有很多用户都只使用 AOF 持久化, 但并不推荐这种方式: 因为定时生成 RDB 快照(snapshot)非常便于进行数据库备份, 并且 RDB 恢复数据集的速度也要比 AOF 恢复的速度要快。
发布订阅
Redis 发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息。微信、微博、关注系统! Redis 客户端可以订阅任意数量的频道。 订阅/发布消息图: 第一个:消息发送者, 第二个:频道 第三个:消息订阅者!
下图展示了频道 channel1 , 以及订阅这个频道的三个客户端 —— client2 、 client5 和 client1 之间的关系:
当有新消息通过 PUBLISH 命令发送给频道 channel1 时,这个消息就会被发送给订阅它的三个客户端:
命令
命令 | 描述 |
---|
PSUBSCRIBE pattern [pattern…] | 订阅一个或多个符合给定模式的频道。 | PUNSUBSCRIBE pattern [pattern…] | 退订一个或多个符合给定模式的频道。 | PUBSUB subcommand [argument[argument]] | 查看订阅与发布系统状态。 | PUBLISH channel message | 向指定频道发布消息 | SUBSCRIBE channel [channel…] | 订阅给定的一个或多个频道。 | UNSUBSCRIBE channel [channel…] | 退订一个或多个频道 |
------------订阅端----------------------
127.0.0.1:6379> SUBSCRIBE sakura # 订阅sakura频道
Reading messages... (press Ctrl-C to quit) # 等待接收消息
1) "subscribe" # 订阅成功的消息
2) "sakura"
3) (integer) 1
1) "message" # 接收到来自sakura频道的消息 "hello world"
2) "sakura"
3) "hello world"
1) "message" # 接收到来自sakura频道的消息 "hello i am sakura"
2) "sakura"
3) "hello i am sakura"
--------------消息发布端-------------------
127.0.0.1:6379> PUBLISH sakura "hello world" # 发布消息到sakura频道
(integer) 1
127.0.0.1:6379> PUBLISH sakura "hello i am sakura" # 发布消息
(integer) 1
-----------------查看活跃的频道------------
127.0.0.1:6379> PUBSUB channels
1) "sakura"
原理
每个 Redis 服务器进程都维持着一个表示服务器状态的 redis.h/redisServer 结构, 结构的 pubsub_channels 属性是一个字典,这个字典就用于保存订阅频道的信息,其中,字典的键为正在被订阅的频道, 而字典的值则是一个链表, 链表中保存了所有订阅这个频道的客户端。
客户端订阅,就被链接到对应频道的链表的尾部,退订则就是将客户端节点从链表中移除。
缺点
- 如果一个客户端订阅了频道,但自己读取消息的速度却不够快的话,那么不断积压的消息会使redis输出缓冲区的体积变得越来越大,这可能使得redis本身的速度变慢,甚至直接崩溃。
- 这和数据传输可靠性有关,如果在订阅方断线,那么他将会丢失所有在短线期间发布者发布的消息。
应用
- 消息订阅:公众号订阅,微博关注等等(起始更多是使用消息队列来进行实现)
- 多人在线聊天室。
这边消息队列的功能相比MQ之类的就差很多了,所以稍微复杂的场景,我们就会使用消息中间件MQ处理。
主从复制与集群搭建
基础概念
主从复制,是指将一台Redis服务器的数据,复制到其他的Redis服务器。前者称为主节点(Master/Leader),后者称为从节点(Slave/Follower), 数据的复制是单向的!只能由主节点复制到从节点(主节点以写为主、从节点以读为主)。
默认情况下,每台Redis服务器都是主节点,一个主节点可以有0个或者多个从节点,但每个从节点只能由一个主节点。通常情况下主机用于写数据,从机用于读取数据,因为真实情况下读取的请求数量远远高于写入的请求数量
集群作用
- 数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余的方式。
- 故障恢复:当主节点故障时,从节点可以暂时替代主节点提供服务,是一种服务冗余的方式
- 负载均衡:在主从复制的基础上,配合读写分离,由主节点进行写操作,从节点进行读操作,分担服务器的负载;尤其是在读多写少的场景下,通过多个从节点分担负载,提高并发量。
- 高可用基石:主从复制还是哨兵和集群能够实施的基础。
为什么使用集群
- 单台服务器难以负载大量的请求
- 单台服务器故障率高,系统崩坏概率大
- 单台服务器内存容量有限。
环境配置
查看当前库的信息:info replication
127.0.0.1:6379> info replication
role:master
connected_slaves:0
master_replid:3b54deef5b7b7b7f7dd8acefa23be48879b4fcff
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:0
second_repl_offset:-1
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0
既然需要启动多个服务,就需要多个配置文件。每个配置文件对应修改以下信息:
- 端口号:
port - pid文件名:
pidfile - 日志文件名:
logfile - rdb文件名:
dbfilename
启动单机多服务集群:
ps -ef | grep redis 查看当前启动的服务进程,可以看到集群搭建完毕:
一主二从配置
默认情况下,每台Redis服务器都是主节点,因此我们一般情况下只用配置从机就好了!
认老大!一主(79)二从(80,81)
使用SLAVEOF host port 命令就可以为从机配置主机了。
然后主机上也能看到从机的状态:
我们这里是使用命令搭建,是暂时的,真实开发中应该在从机的配置文件中进行配置,这样的话是永久的
使用规则??
- 从机只能读,不能写,主机可读可写但是多用于写。主机写入的数据在所有从机中均能够读取!
##################################从机操作#####################################
127.0.0.1:6381> set name sakura # 从机6381写入失败
(error) READONLY You can't write against a read only replica.
127.0.0.1:6380> set name sakura # 从机6380写入失败
(error) READONLY You can't write against a read only replica.
##################################主机操作#####################################
127.0.0.1:6379> set name sakura
OK
127.0.0.1:6379> get name
"sakura"
-
当主机断电宕机后,默认情况下从机的角色不会发生变化 ,集群中只是失去了写操作,当主机恢复以后,又会连接上从机恢复原状。 -
当从机断电宕机后,若不是使用配置文件配置的从机,再次启动后作为主机是无法获取之前作为从机时的数据的,若此时重新配置为从机,又可以获取到主机的所有数据。这里就要提到一个复制原理。 -
第二条中提到,默认情况下,主机故障后,不会出现新的主机,有两种方式可以产生新的主机:
- 从机手动执行命令
slaveof no one ,这样执行以后从机会独立出来成为一个主机 - 使用哨兵模式(自动选举)
如果主机断开了连接,我们可以使用SLAVEOF no one 让自己变成主机!其他的节点就可以手动连接到最新的主节点(手动)!如果这个时候老大修复了,那么就能重新连接!
复制原理
Slave启动成功连接到 master后会发送一个sync同步命令
Master接收到该命令后,启动后台的存盘进程,同时手机所有接收到的用于修改数据集命令,在后台进程执行完毕后,master将传送整个数据文件到Slave中,并完一次完全同步
全量复制:Slave服务在接收到数据库文件后,将其存盘并加载到内存中,常出现在与主机成功连接时。
增量复制:Master继续将所有收集到的修改命令依次传送给Slave,完成同步,常出现在主机执行修改命令后。
只要是重写连接master,全量复制就会自动执行!
哨兵模式
概念
在主从模式中,主机出现宕机的情况会导致集群无法执行写操作,过去需要手动更改从机的配置使其成为新的主机,这样不仅麻烦且容易出错,通常为了自动监控主机运行情况与主机宕机时实现主从切换,会配置哨兵进程监视主机,成为哨兵模式。
这里的哨兵有两个作用
- 通过发送命令,让Redis服务器返回监控其运行状态,包括主服务器和从服务器。
- 当哨兵监测到master宕机,会自动将slave切换成master,然后通过发布订阅模式通知其他的从服务器,修改配置文件,让它们切换主机。
然而一个哨兵进程对Redis服务器进行监控,可能会出现问题,为此,我们可以使用多个哨兵进行监控。各个哨兵之间还会进行监控,这样就形成了**多哨兵模式**。
如果某一个哨兵监测到主机宕机,此时该哨兵不会立即执行主从切换动作,此时仅仅是哨兵1主观认为主机无法使用,这个现象成为主观下线。如果多个哨兵均监测到主机无法使用时,哨兵之间会进行一次投票,投票会根据一定的算法决定出让哪一个从机成为新的主机,并进行failover(故障转移)操作,并且每个哨兵会自动修改其监视服务器为新主机,此过程成为客观下线。
实现哨兵模式
每一个哨兵其实是一个redis服务进程,启动时也需要给定其配置文件,哨兵模式的配置文件是sentinel.conf
1、哨兵模式配置文件
sentinel monitor myredis 127.0.0.1 6379 1
后面的这个数字1,代表主机挂了,slave投票看让谁接替成为主机,票数最多的,就会成为主机!
2、启动哨兵
redis-sentinel xxx/sentinel.conf
reids-sentinel 同样是一个可执行服务
可以看到连接成功!
此时哨兵监视着我们的主机6379,当我们断开主机后:
哨兵模式优缺点
优点:
- 哨兵集群,基于主从复制模式,所有主从复制的优点,它都有
- 主从可以切换,故障可以转移,系统的可用性更好
- 哨兵模式是主从模式的升级,手动到自动,更加健壮
缺点:
- Redis不好在线扩容,集群容量一旦达到上限,在线扩容就十分麻烦
- 实现哨兵模式的配置其实是很麻烦的,里面有很多配置项
哨兵模式的全部配置
完整的哨兵模式配置文件 sentinel.conf
# Example sentinel.conf
# 哨兵sentinel实例运行的端口 默认26379
port 26379
# 哨兵sentinel的工作目录
dir /tmp
# 哨兵sentinel监控的redis主节点的 ip port
# master-name 可以自己命名的主节点名字 只能由字母A-z、数字0-9 、这三个字符".-_"组成。
# quorum 当这些quorum个数sentinel哨兵认为master主节点失联 那么这时 客观上认为主节点失联了
# sentinel monitor <master-name> <ip> <redis-port> <quorum>
sentinel monitor mymaster 127.0.0.1 6379 1
# 当在Redis实例中开启了requirepass foobared 授权密码 这样所有连接Redis实例的客户端都要提供密码
# 设置哨兵sentinel 连接主从的密码 注意必须为主从设置一样的验证密码
# sentinel auth-pass <master-name> <password>
sentinel auth-pass mymaster MySUPER--secret-0123passw0rd
# 指定多少毫秒之后 主节点没有应答哨兵sentinel 此时 哨兵主观上认为主节点下线 默认30秒
# sentinel down-after-milliseconds <master-name> <milliseconds>
sentinel down-after-milliseconds mymaster 30000
# 这个配置项指定了在发生failover主备切换时最多可以有多少个slave同时对新的master进行 同步,
# 这个数字越小,完成failover所需的时间就越长,
# 但是如果这个数字越大,就意味着越 多的slave因为replication而不可用。
# 可以通过将这个值设为 1 来保证每次只有一个slave 处于不能处理命令请求的状态。
# sentinel parallel-syncs <master-name> <numslaves>
sentinel parallel-syncs mymaster 1
# 故障转移的超时时间 failover-timeout 可以用在以下这些方面:
#1. 同一个sentinel对同一个master两次failover之间的间隔时间。
#2. 当一个slave从一个错误的master那里同步数据开始计算时间。直到slave被纠正为向正确的master那里同步数据时。
#3.当想要取消一个正在进行的failover所需要的时间。
#4.当进行failover时,配置所有slaves指向新的master所需的最大时间。不过,即使过了这个超时,slaves依然会被正确配置为指向master,但是就不按parallel-syncs所配置的规则来了
# 默认三分钟
# sentinel failover-timeout <master-name> <milliseconds>
sentinel failover-timeout mymaster 180000
# SCRIPTS EXECUTION
#配置当某一事件发生时所需要执行的脚本,可以通过脚本来通知管理员,例如当系统运行不正常时发邮件通知相关人员。
#对于脚本的运行结果有以下规则:
#若脚本执行后返回1,那么该脚本稍后将会被再次执行,重复次数目前默认为10
#若脚本执行后返回2,或者比2更高的一个返回值,脚本将不会重复执行。
#如果脚本在执行过程中由于收到系统中断信号被终止了,则同返回值为1时的行为相同。
#一个脚本的最大执行时间为60s,如果超过这个时间,脚本将会被一个SIGKILL信号终止,之后重新执行。
#通知型脚本:当sentinel有任何警告级别的事件发生时(比如说redis实例的主观失效和客观失效等等),将会去调用这个脚本,
#这时这个脚本应该通过邮件,SMS等方式去通知系统管理员关于系统不正常运行的信息。调用该脚本时,将传给脚本两个参数,
#一个是事件的类型,
#一个是事件的描述。
#如果sentinel.conf配置文件中配置了这个脚本路径,那么必须保证这个脚本存在于这个路径,并且是可执行的,否则sentinel无法正常启动成功。
#通知脚本
# sentinel notification-script <master-name> <script-path>
sentinel notification-script mymaster /var/redis/notify.sh
# 客户端重新配置主节点参数脚本
# 当一个master由于failover而发生改变时,这个脚本将会被调用,通知相关的客户端关于master地址已经发生改变的信息。
# 以下参数将会在调用脚本时传给脚本:
# <master-name> <role> <state> <from-ip> <from-port> <to-ip> <to-port>
# 目前<state>总是“failover”,
# <role>是“leader”或者“observer”中的一个。
# 参数 from-ip, from-port, to-ip, to-port是用来和旧的master和新的master(即旧的slave)通信的
# 这个脚本应该是通用的,能被多次调用,不是针对性的。
# sentinel client-reconfig-script <master-name> <script-path>
sentinel client-reconfig-script mymaster /var/redis/reconfig.sh
缓存穿透与雪崩
服务的高可用问题
Redis缓存的使用,极大的提升了应用程序的性能和效率,特别是数据查询方面。但同时,它也带来了一些问题。其中,最要害的问题,就是数据的一致性问题(事务在运行时不能保证原子性),从严格意义上讲,这个问题无解。如果对数据的一致性要求很高,那么就不能使用缓存。 另外的一些典型问题就是,缓存穿透、缓存雪崩和缓存击穿。目前,业界也都有比较流行的解决方案。
缓存穿透
概念
缓存穿透的概念很简单,用户想要查询一个数据,发现redis内存数据库没有,也就是缓存没有命中,于是向持久层数据库查询。发现也没有,于是本次查询失败。当用户很多的时候,缓存都没有命中(秒杀!),于是都去请求了持久层数据库。这会给持久层数据库造成很大的压力,这时候就相当于出现了缓存穿透。洪水攻击。数据库也查不到就没有缓存,就会一直与数据库访问。
解决方案
1.布隆过滤器
对所有可能查询的参数以Hash的形式存储,以便快速确定是否存在这个值,在控制层先进行拦截校验,校验不通过直接打回,减轻了存储系统的压力。
2.缓存空对象
一次请求若在缓存和数据库中都没找到,就在缓存中方一个空对象用于处理后续这个请求。
1、如果空值能够被缓存起来,这就意味着缓存需要更多的空间存储更多的键,因为这当中可能会有很多的空值的键; 2、即使对空值设置了过期时间,还是会存在缓存层和存储层的数据会有一段时间窗口的不一致,这对于需要保持一致性的业务会有影响
缓存击穿(量太大 缓存过期)
概述
相较于缓存穿透,缓存击穿的目的性更强,一个存在的key,在缓存过期的一刻,同时有大量的请求,这些请求都会击穿到DB,造成瞬时DB请求量大、压力骤增。这就是缓存被击穿,只是针对其中某个key的缓存不可用而导致击穿,但是其他的key依然可以使用缓存响应。
比如热搜排行上,一个热点新闻被同时大量访问就可能导致缓存击穿。
解决方案
1.设置热点数据永不过期
这样就不会出现热点数据过期的情况,但是当Redis内存空间满的时候也会清理部分数据,而且此种方案会占用空间,一旦热点数据多了起来,就会占用部分空间。
2.加互斥锁(分布式锁)
在访问key之前,采用SETNX(set if not exists)来设置另一个短期key来锁住当前key的访问,访问结束再删除该短期key。保证同时刻只有一个线程访问。这样对锁的要求就十分高。
缓存雪崩
缓存概念
大量的key设置了相同的过期时间,导致在缓存在同一时刻全部失效,造成瞬时DB请求量大、压力骤增,引起雪崩。
解决方案
redis高可用
这个思想的含义是,既然redis有可能挂掉,那我多增设几台redis,这样一台挂掉之后其他的还可以继续工作,其实就是搭建的集群
限流降级
这个解决方案的思想是,在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。
数据预热
数据加热的含义就是在正式部署之前,我先把可能的数据先预先访问一遍,这样部分可能大量访问的数据就会加载到缓存中。在即将发生大并发访问前手动触发加载缓存不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀。
|