赞
踩
NoSQL,泛指非关系型的数据库。意即不仅仅是SQL
,NoSQL有时也称作Not Only SQL的缩写,是
对不同于传统的关系型数据库的数据库管理系统的统称
随着互联网web2.0网站的兴起,传统的关系数据库在处理web2.0网站,特别是超大规模和高并发的SNS类型的web2.0纯动态网站已经显得力不从心,出现了很多难以克服的问题,而非关系型的数据库则由于其本身的特点得到了非常迅速的发展
NoSQL数据库的产生就是为了解决大规模数据集合多重数据种类带来的挑战,尤其是大数据应用难题,包括超大规模数据的存储
传统的关系数据库具有不错的性能,高稳定型,久经历史考验,而且使用简单,功能强大,同时也积累了大量的成功案例。在互联网领域,MySQL成为了绝对靠前的王者,毫不夸张的说,MySQL为互联网的发展做出了卓越的贡献
在90年代,一个网站的访问量一般都不大,用单个数据库完全可以轻松应付。在那个时候,更多的都是静态网页,动态交互类型的网站不多
关系数据库很强大,但是它并不能很好的应付所有的应用场景。MySQL的扩展性差(需要复杂的技术来实现),大数据量场景下IO压力大,表结构更改困难,正是当前使用MySQL的开发人员面临的问题
今天我们可以通过第三方平台(如:Google,Facebook等)可以很容易的访问和抓取数据。用户的个人信息,社交网络,地理位置,用户生成的数据和用户操作日志已经成倍的增加。我们如果要对这些用户数据进行挖掘,那SQL数据库已经不适合这些应用了,NoSQL数据库的发展也却能很好的处理这些大的数据
RDBMS
NoSQL
总结:关系型数据库与NOSQL数据库并非对立而是互补的关系,即通常情况下使用关系型数据库,在适合使用NoSQL的时候使用NOSQL数据库,让NoSQL数据库对关系型数据库的不足进行弥补。
一般会将数据存储在关系型数据库中,在nosql数据库中备份存储关系型数据库的数据
在计算机科学中, CAP定理(CAP theorem), 又被称作 布鲁尔定理(Brewer’s theorem), 它指出对于一个分布式计算系统来说,不可能同时满足以下三点:
CAP理论的核心是:一个分布式系统不可能同时很好的满足一致性,可用性和分区容错性这三个需求,最多只能同时较好的满足两个。
因此,根据 CAP 原理将 NoSQL 数据库分成了满足 CA 原则、满足 CP 原则和满足 AP 原则三 大类:
CA:传统Oracle数据库
AP:大多数网站架构的选择
CP:Redis
、Mongodb
注:分布式架构的时候必须要做出取舍
一致性和可用性之间取一个平衡。对于大多数web应用,其实并不需要强一致性
最佳实践:当下的应用是 SQL 与 NoSQL 一起使用的,形成优势互补
代表项目:阿里巴巴商品信息的存放
去除:IBM是服务器提供商,Oracle是数据库软件提供商,EMC则是存储设备提供商
KV键值对
文档数据库(bson格式和json一致)
列存储数据库
图关系数据库
不是来存图形的,而是来存储关系的,如朋友圈社交网络,广告推荐
Neo4J,InfoGrid
比较:
分类 | 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(Remote Dictionary Server ),即远程字典服务
是一个开源的使用ANSI C语言编写的一个开源的高性能键值对数据库、支持网络、可基于内存亦可持久化的日志型、Key-Value
数据库,并提供多种语言的API
开源且免费,是当前最热门的NoSQL技术之一,也被人们称之为结构化数据库
官方提供数据为:读的速度是11w次/s,写的速度是8.1w次/s
特性:
应用场景
Windows可以在Github上下载
注:Redis推荐在Linux服务器上搭建
下载完毕得到压缩包
解压到环境目录下即可,Redis十分小,只有5M
开启Redis,双击redis-server.exe
即可
redis默认端口:6379
使用redis客户端连接redis
双击redis-cli.exe
,出现以下界面
下载安装包 redis-6.2.6.tar.gz
解压redis安装包 程序一般放在/opt
目录下
tar -axvf redis-6.2.6.tar.gz
解压完毕后,可以看到redis的配置文件
基本环境安装
yum install gcc-c++
make install
redis默认安装路径:usr/local/bin
新建一个目录,将redis配置文件复制到当前目录下
redis默认不是后台启动的,修改配置文件 vim redis.conf
启动Redis服务 usr/local/bin
使用redis-cli
进行连接测试
查看redis进程
关闭redis服务 shutdown
redis默认有16个数据库
默认使用的是第0个,可以使用select
进行切换
127.0.0.1:6379> select 6 # 切换数据库
OK
127.0.0.1:6379[6]> dbsize # 查看db大小
(integer) 0
127.0.0.1:6379[2]> keys * # 查看当前数据的所有的key
1) "name"
清除当前数据库的内容 flushdb
清除全部数据库的内容 flushAll
Redis是单线程的,官方FAQ表示,因为Redis是基于内存操作的,CPU不是Redis的性能瓶颈,它的瓶颈是根据机器的内存大小和网络带宽,既然可以使用单线程来实现,那就顺理成章地采用单线程的方案了
Redis为什么是单线程还这么快?
127.0.0.1:6379[2]> keys * # 查看当前数据库所有的key (empty array) 127.0.0.1:6379[2]> set username xiaozhang # set key OK 127.0.0.1:6379[2]> set age 20 OK 127.0.0.1:6379[2]> keys * 1) "age" 2) "username" 127.0.0.1:6379[2]> exists username # 判断当前key是否存在 (integer) 1 127.0.0.1:6379[2]> exists name (integer) 0 127.0.0.1:6379[2]> move username 6 # 移动当前的key到指定数据库 (integer) 1 127.0.0.1:6379[2]> keys * 1) "age" 127.0.0.1:6379[2]> select 6 OK 127.0.0.1:6379[1]> keys * 1) "username" 127.0.0.1:6379[1]> select 0 OK 127.0.0.1:6379> keys * (empty array) 127.0.0.1:6379> set username xiaolin OK 127.0.0.1:6379> keys * 1) "username" 127.0.0.1:6379> get username "xiaolin" 127.0.0.1:6379> set age 20 OK 127.0.0.1:6379> get age "20" 127.0.0.1:6379> expire age 10 # 设置key过期时间 单位为秒 (integer) 1 127.0.0.1:6379> ttl age # 查看当前key过期的剩余时间 (integer) 7 127.0.0.1:6379> ttl age (integer) 4 127.0.0.1:6379> ttl age (integer) 0 127.0.0.1:6379> ttl age (integer) -2 # -2 表示已经过期 127.0.0.1:6379> get age (nil) 127.0.0.1:6379> set name xiaoming OK 127.0.0.1:6379> get name "xiaoming" 127.0.0.1:6379> del name # 移除指定key (integer) 1 127.0.0.1:6379> set username xiaozhao OK 127.0.0.1:6379> get username "xiaozhao" 127.0.0.1:6379> type username # 查看当前key的类型 string
127.0.0.1:6379> set username xiaozhang # 设置值 OK 127.0.0.1:6379> get username # 获得值 "xiaozhang" 127.0.0.1:6379> keys * # 获得当前数据库所有的key 1) "username" 127.0.0.1:6379> exists username # 判断某个key是否存在 (integer) 1 127.0.0.1:6379> append username hello # 追加字符串,如果当前key不存在,相当于set key (integer) 14 127.0.0.1:6379> get username "xiaozhanghello" 127.0.0.1:6379> strlen username # 获取字符串的长度 (integer) 14 127.0.0.1:6379> append username redis (integer) 19 127.0.0.1:6379> strlen username (integer) 19 127.0.0.1:6379> get username "xiaozhanghelloredis"
# i++ i-- # 步长 i+=2 i-=2 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 (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 (integer) 1 127.0.0.1:6379> incrby views 10 # 设置步长,指定增量 (integer) 11 127.0.0.1:6379> incrby views 10 (integer) 21 127.0.0.1:6379> decrby views 11 # 设置步长,指定减量 (integer) 10
# 字符串范围 range 127.0.0.1:6379> set str hello,redis # 设置str的值 OK 127.0.0.1:6379> get str "hello,redis" 127.0.0.1:6379> getrange str 0 4 # 截取字符串 [0,4] "hello" 127.0.0.1:6379> getrange str 0 -1 # 获取全部字符串,和get key一致 "hello,redis" # 替换 127.0.0.1:6379> set letter abcd OK 127.0.0.1:6379> get letter "abcd" 127.0.0.1:6379> setrange letter 2 xyz # 替换从指定位置开始的字符串 (integer) 5 127.0.0.1:6379> get letter "abxyz"
# setex (set with expire) # 设置过期时间 # setnx (set if not exists) # 不存在设置key 分布式锁经常使用 127.0.0.1:6379> set age 20 OK 127.0.0.1:6379> get age "20" 127.0.0.1:6379> setex age 15 hello # 设置age的值为hello,15秒后过期 OK 127.0.0.1:6379> ttl age # 查看过期时间 (integer) 12 127.0.0.1:6379> setnx mykey redis # 如果mykey不存在,创建mykey (integer) 1 127.0.0.1:6379> keys * 1) "letter" 2) "mykey" 3) "str" 127.0.0.1:6379> ttl age (integer) -2 127.0.0.1:6379> setnx mykey MongoDB # 如果mykey存在,创建失败 (integer) 0 127.0.0.1:6379> get mykey "redis"
# 批量set值/get值 # mset/mget 127.0.0.1:6379> mset username xiaolin gender 1 birth 2001-06-06 # 同时设置多个值 OK 127.0.0.1:6379> keys * 1) "letter" 2) "str" 3) "mykey" 4) "gender" 5) "age" 6) "username" 7) "birth" 127.0.0.1:6379> mget username gender birth # 同时获取多个值 1) "xiaolin" 2) "1" 3) "2001-06-06" 127.0.0.1:6379> msetnx username kk email 8696521@qq.com (integer) 0 # msetnx 是一个原子性的操作,要么一起成功,要么一起失败 127.0.0.1:6379> get email (nil)
# 对象 127.0.0.1:6379> set user:1 {name:zhangsan,age:1} # 值为json字段来保存对象 OK # 这里的key是一个巧妙的设置:user:{id}:{filed},如此设计在Redis是完全可以的 127.0.0.1:6379> mset user:1:username xiaolin user:1:age 22 OK 127.0.0.1:6379> mget user:1:username user:1:age 1) "xiaolin" 2) "22" # get set # 先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"
数据结构
是相通的
String使用场景,value
除了是字符串还可以是数字
所有的list命令都是以l
开头的
127.0.0.1:6379[2]> lpush list one # 将一个值或多个值,插入到列表的头部(左边) (integer) 1 127.0.0.1:6379[2]> lpush list two (integer) 2 127.0.0.1:6379[2]> lpush list three (integer) 3 127.0.0.1:6379[2]> lrange list 0 -1 # 获取list中的值 1) "three" 2) "two" 3) "one" 127.0.0.1:6379[2]> lrange list 0 1 # 通过区间获取具体的值 1) "three" 2) "two" 127.0.0.1:6379[2]> rpush list four # 将一个值或多个值,插入到列表的尾部(右边) (integer) 4 127.0.0.1:6379[2]> lrange list 0 -1 1) "three" 2) "two" 3) "one" 4) "four"
127.0.0.1:6379[2]> lrange list 0 -1 1) "three" 2) "two" 3) "one" 4) "four" 127.0.0.1:6379[2]> lpop list # 移除列表第一个元素 1) "three" 127.0.0.1:6379[2]> lrange list 0 -1 1) "two" 2) "one" 3) "four" 127.0.0.1:6379[2]> rpop list # 移除列表最后一个元素 "four" 127.0.0.1:6379[2]> lrange list 0 -1 1) "two" 2) "one" 127.0.0.1:6379[2]> lindex list 1 # 通过下标获得list中的某一个值 "one"
127.0.0.1:6379[2]> lpush list one (integer) 1 127.0.0.1:6379[2]> lpush list two (integer) 2 127.0.0.1:6379[2]> lpush list three (integer) 3 127.0.0.1:6379[2]> llen list # 查看列表长度 (integer) 3 # # # # # # # # # 127.0.0.1:6379[2]> rpush list four (integer) 4 127.0.0.1:6379[2]> lrange list 0 -1 1) "three" 2) "two" 3) "one" 4) "four" 127.0.0.1:6379[2]> lrem list 1 four # 移除list集合中指定个数的value,精确匹配 (integer) 1 127.0.0.1:6379[2]> lrange list 0 -1 1) "three" 2) "two" 3) "one" # # # # # # # # # 127.0.0.1:6379[2]> lpush list one (integer) 1 127.0.0.1:6379[2]> lpush list two (integer) 2 127.0.0.1:6379[2]> lpush list three (integer) 3 127.0.0.1:6379[2]> lrange list 0 -1 1) "three" 2) "two" 3) "one" 127.0.0.1:6379[2]> ltrim list 1 2 # 通过下标截取指定的长度,这个list已经被改变了,只剩下截取的元素了 OK 127.0.0.1:6379[2]> lrange list 0 -1 1) "two" 2) "one" # # # # # # # # # 127.0.0.1:6379[2]> rpush list one (integer) 1 127.0.0.1:6379[2]> rpush list two (integer) 2 127.0.0.1:6379[2]> rpush list three (integer) 3 127.0.0.1:6379[2]> rpush list four (integer) 4 127.0.0.1:6379[2]> lrange list 0 -1 1) "one" 3) "two" 4) "three" 5) "four" 127.0.0.1:6379[2]> rpoplpush list otherlist # 移除列表中最后一个元素,并将最后一个元素移动到新的列表中 "four" 127.0.0.1:6379[2]> lrange list 0 -1 # 查看原来的列表 1) "one" 2) "two" 3) "three" 127.0.0.1:6379[2]> lrange otherlist 0 -1 # 查看目标列表中,确认存在该值 1) "four"
# lset 将列表中指定下标的值替换为另外一个值,更新操作
127.0.0.1:6379[2]> exists list # 判断这个列表是否存在
(integer) 0
127.0.0.1:6379[2]> lset list 0 item # 如果不存在列表去更新的话会报错
(error) ERR no such key
127.0.0.1:6379[2]> lpush list one
(integer) 1
127.0.0.1:6379[2]> lrange list 0 0
1) "one"
127.0.0.1:6379[2]> lset list 0 item # 如果存在,更新当前下标的值
OK
127.0.0.1:6379[2]> lrange list 0 0
1) "item"
# # # # # # # # # # # linsert 将某个具体的value插入到列表中某个元素的前面或者后面 127.0.0.1:6379> rpush list first (integer) 1 127.0.0.1:6379> rpush list second (integer) 2 127.0.0.1:6379> linsert list before second third # 在second前面插入third元素 (integer) 3 127.0.0.1:6379> lrange list 0 -1 1) "first" 2) "third" 3) "second" 127.0.0.1:6379> linsert list after third fourth # 在third后面插入fourth元素 (integer) 4 127.0.0.1:6379> lrange list 0 -1 1) "first" 2) "third" 3) "fourth" 4) "second"
set
中的值是不允许重复的
127.0.0.1:6379> sadd myset hello # set集合中添加元素 (integer) 1 127.0.0.1:6379> sadd myset redis (integer) 1 127.0.0.1:6379> smembers myset # 查看指定set中的所有值 1) "redis" 2) "hello" 127.0.0.1:6379> SISMEMBER myset hello # 判断某一个值是否在set集合中 (integer) 1 127.0.0.1:6379> sadd myset hi,xiaozhao (integer) 1 127.0.0.1:6379> sadd myset redis # 添加重复元素,添加失败 (integer) 0 127.0.0.1:6379> scard myset # 获取set集合中的内容元素个数 (integer) 3 127.0.0.1:6379> srem myset hello # 移除set集合中的指定元素 (integer) 1 127.0.0.1:6379> scard myset (integer) 2 127.0.0.1:6379> smembers myset 1) "redis" 2) "hi,xiaozhao"
# set 无序不重复集合 127.0.0.1:6379> smembers myset 1) "redis" 2) "hi,xiaozhao" 127.0.0.1:6379> srandmember myset # 随机抽选出一个元素 "hi,xiaozhao" 127.0.0.1:6379> srandmember myset "redis" 127.0.0.1:6379> srandmember myset 2 # 随机抽选出指定个数的元素 1) "redis" 2) "hi,xiaozhao" 127.0.0.1:6379> smembers myset 1) "redis" 2) "hi,xiaozhao" 127.0.0.1:6379> spop myset # 随机删除set集合中的某个元素 "hi,xiaozhao" 127.0.0.1:6379> smembers myset 1) "redis" 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 redis (integer) 1 127.0.0.1:6379> smembers myset 1) "redis" 2) "world" 3) "hello" 127.0.0.1:6379> sadd settwo hello (integer) 1 127.0.0.1:6379> smove myset settwo redis # 将指定的值,移动到另外一个set集合中 (integer) 1 127.0.0.1:6379> SMEMBERS myset 1) "world" 2) "hello" 127.0.0.1:6379> SMEMBERS settwo 1) "redis" 2) "hello"
# 数字集合类
# 差集 交集 并集
127.0.0.1:6379> sdiff key1 key2 # 差集
1) "a"
127.0.0.1:6379> sinter key1 key2 # 交集 共同好友
1) "c"
2) "b"
127.0.0.1:6379> sunion key1 key2 # 并集
1) "b"
2) "c"
3) "e"
4) "a"
5) "d"
Map集合,key-<key-value>
这时,这个值是一个map集合
127.0.0.1:6379> hset myhash username xiaolin # set key-value (integer) 1 127.0.0.1:6379> hget myhash username # get key-value "xiaolin" 127.0.0.1:6379> hmset myhash username xiaozhang age 20 # 同时设置多个 key-value OK 127.0.0.1:6379> hmget myhash username age # 同时获取多个 key-value 1) "xiaozhang" 2) "20" 127.0.0.1:6379> hgetall myhash # 获取hash中全部数据 1) "username" 2) "xiaozhang" 3) "age" 4) "20" 127.0.0.1:6379> hdel myhash age # 删除hash指定的key字段,对应的value值也就消失了 (integer) 1 127.0.0.1:6379> hgetall myhash 1) "username" 2) "xiaozhang" 127.0.0.1:6379> hmset myhash age 22 email 965216399@qq.com OK 127.0.0.1:6379> hgetall myhash 1) "username" 2) "xiaozhang" 3) "age" 4) "22" 5) "email" 6) "965216399@qq.com" 127.0.0.1:6379> hlen myhash # 获取hash表的字段数量 (integer) 3 127.0.0.1:6379> hexists myhash email # 判断hash中指定字段是否存在 (integer) 1 127.0.0.1:6379> hexists myhash gender (integer) 0 127.0.0.1:6379> hkeys myhash # 只获得所有的key 1) "username" 2) "age" 3) "email" 127.0.0.1:6379> hvals myhash # 只获得所有的value 1) "xiaozhang" 2) "22" 3) "965216399@qq.com" 127.0.0.1:6379> hset myhash count 6 (integer) 1 127.0.0.1:6379> hincrby myhash count 6 # 指定增量 (integer) 12 127.0.0.1:6379> hincrby myhash count 6 (integer) 18 127.0.0.1:6379> hincrby myhash count -6 # 指定减量 (integer) 12 127.0.0.1:6379> hsetnx myhash gender 1 # 如果不存在则可以设置 (integer) 1 127.0.0.1:6379> hsetnx myhash gender 0 # 如果存在则不可以设置 (integer) 0
hash
多用于变更的数据,更加适合对象的存储,String
更加适合字符串存储,对象中某些频繁变化的属性抽出来用hash存储
在set
的基础上,增加了一个值,set k1 v1 zset k1 score1 v1
127.0.0.1:6379> zadd myzset 1 one # 添加一个值 (integer) 1 127.0.0.1:6379> zadd myzset 2 two 3 three 4 four # 添加多个值 (integer) 3 127.0.0.1:6379> zrange myzset 0 -1 1) "one" 2) "two" 3) "three" 4) "four" 127.0.0.1:6379> zadd salary 8000 xiaozhang # 添加三个用户 (integer) 1 127.0.0.1:6379> clear 127.0.0.1:6379> zadd salary 6000 xiaolin 7000 xiaozhao (integer) 2 127.0.0.1:6379> zrangebyscore salary -inf +inf # 显示全部的用户 从小到大排序 1) "xiaolin" 2) "xiaozhao" 3) "xiaozhang" 127.0.0.1:6379> zrangebyscore salary -inf +inf withscores # 显示全部的用户 从小到大排序,并且附带成绩 1) "xiaolin" 2) "6000" 3) "xiaozhao" 4) "7000" 5) "xiaozhang" 6) "8000" 127.0.0.1:6379> zrangebyscore salary -inf 7000 withscores # 显示工资小于7000员工的升序排列 1) "xiaolin" 2) "6000" 3) "xiaozhao" 4) "7000" 127.0.0.1:6379> ZREVRA NGE salary 0 -1 withscores # 根据工资降序排列 1) "xiaozhang" 2) "8000" 3) "xiaolin" 4) "6000" # 移除rem中的元素 127.0.0.1:6379> zrange salary 0 -1 1) "xiaolin" 2) "xiaozhao" 3) "xiaozhang" 127.0.0.1:6379> zrem salary xiaozhao # 移除有序集合中的指定元素 (integer) 1 127.0.0.1:6379> zrange salary 0 -1 1) "xiaolin" 2) "xiaozhang" 127.0.0.1:6379> zcard salary # 获取有序集合中的个数 (integer) 2 127.0.0.1:6379> zadd myzset 1 one 2 two 3 three (integer) 3 127.0.0.1:6379> zcount myzset 1 2 # 获取指定区间的成员数量 (integer) 2
朋友定位、附近的人,打车距离计算
Redis GEO 主要用于存储地理位置信息,并对存储的信息进行操作,该功能在 Redis 3.2 版本新增
相关命名,只有六个命令
geoadd 用于存储指定的地理空间位置,可以将一个或多个经度(longitude)、纬度(latitude)、位置名称(member)添加到指定的 key 中
# geoadd 添加地理位置
# 规则:两级(南/北极)无法直接添加,一般会下载城市数据,直接通过ava程序一次性导入
# 参数 key 值(经度、纬度、位置名称)
127.0.0.1:6379> geoadd china:city 113.66 34.757 henan
(integer) 1
127.0.0.1:6379> geoadd china:city 116.40 39.904 beijing
(integer) 1
127.0.0.1:6379> geoadd china:city 121.47 31.231 shanghai
(integer) 1
127.0.0.1:6379> geoadd china:city 114.08 22.547 shenzhen
(integer) 1
127.0.0.1:6379> geoadd china:city 114.29 30.584 wuhan
(integer) 1
127.0.0.1:6379> geoadd china:city 120.15 30.287 hangzhou
(integer) 1
geopos 给定的 key 里返回所有指定名称(member)的位置(经度和纬度),不存在的返回 nil
127.0.0.1:6379> geopos china:city wuhan # 获取指定城市的经度和纬度
1) 1) "114.29000169038772583"
2) "30.5840000050887042"
127.0.0.1:6379> geopos china:city beijing shanghai
1) 1) "116.39999896287918091"
2) "39.90399988166036138"
2) 1) "121.47000163793563843"
2) "31.23100025461577189"
获得当前定位:
geodist 返回两个给定位置之间的距离
单位:
127.0.0.1:6379> geodist china:city beijing shanghai # 查看北京到上海的直线距离 默认单位为:m
"1067673.5823"
127.0.0.1:6379> geodist china:city beijing shanghai km # 查看北京到上海的直线距离 单位:km
"1067.6736"
127.0.0.1:6379> geodist china:city henan hangzhou km # 查看河南到杭州的直线距离 单位:km
"785.5734"
georadius 以给定的经纬度为中心,找出某一半径内的元素
# 前提:所有的数据都应该录入到:china:city中,才会使结果更加清晰 127.0.0.1:6379> georadius china:city 110 30 1000 km # 以110 30 这个经纬度为中心,寻找附近1000km以内的城市 1) "shenzhen" 2) "wuhan" 3) "hangzhou" 4) "henan" 127.0.0.1:6379> georadius china:city 110 30 500 km withdist # 显示到中心距离的位置 1) 1) "wuhan" 2) "417.0735" 127.0.0.1:6379> georadius china:city 110 30 1000 km withcoord # 显示他人的定位信息 1) 1) "shenzhen" 2) 1) "114.08000081777572632" 2) "22.54699993773966327" 127.0.0.1:6379> georadius china:city 110 30 1000 km withdist withcoord count 1 # 筛选出指定的结果 1) 1) "wuhan" 2) "417.0735" 3) 1) "114.29000169038772583" 2) "30.5840000050887042"
georadiusbymember 可以找出位于指定范围内的元素, 但是 georadiusbymember 的中心点是由给定的位置元素决定的, 而不是使用经度和纬度来决定中心点。
127.0.0.1:6379> GEORADIUSBYMEMBER china:city beijing 1000 km # 找出位于指定元素周围的其他元素
1) "henan"
2) "beijing"
geohash 使用 geohash 来保存地理位置的坐标
# 将二维的经纬度转换为一维的字符串,如果两个字符串越接近,则距离越近
127.0.0.1:6379> geohash china:city beijing henan shanghai
1) "wx4g08rcss0"
2) "ww0vdrkdjy0"
3) "wtw3sj7vb00"
GEO底层实现原理其实就是Zset!可以使用Zset命令来操作geo
127.0.0.1:6379> zrange china:city 0 -1 # 查看地图中全部元素
1) "shenzhen"
2) "wuhan"
3) "hangzhou"
4) "shanghai"
5) "henan"
6) "beijing"
127.0.0.1:6379> zrem china:city shenzhen # 移除地图中指定元素
(integer) 1
127.0.0.1:6379> zrange china:city 0 -1
1) "wuhan"
2) "hangzhou"
3) "shanghai"
4) "henan"
5) "beijing"
Redis2.8.9版本就更新了Hyperloglog数据结构,Redis HyperLogLog 是用来做基数统计的算法,HyperLogLog 的优点是,在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定的、并且是很小的 12kb内存,2^64不同元素的基数
什么是基数?
A {1, 3, 5, 7, 5, 7, 8}
B {1, 3, 5, 7, 8}
基数是数据集去重后元素个数,基数(不重复的元素) = 5, 基数估计就是在误差可接受的范围内,快速计算基数
网页的UV unique visitor
(一个人访问一个网站多次,但是还是算作一个人)
127.0.0.1:6379> pfadd letter1 a b c d e f j # 创建第一组元素
(integer) 1
127.0.0.1:6379> pfcount letter1 # 统计 letter1 元素的基数
(integer) 7
127.0.0.1:6379> pfadd letter2 h i j k m n # 创建第二组元素
(integer) 1
127.0.0.1:6379> pfcount letter2
(integer) 6
127.0.0.1:6379> pfmerge letters letter1 letter2 # 合并两组元素 letter1 letter2 ===> letters 并集
OK
127.0.0.1:6379> pfcount letters # 查看并集的数量
(integer) 12
如果允许容错,建议使用 Hpyerloglog
如果不允许容错,就使用set
或者其他数据类型即可
位存储
Bitmap位图,数据结构,通过操作二进制来进行记录,只有0和1两个状态
# 记录周一到周日的打卡 127.0.0.1:6379> setbit state 0 1 (integer) 0 127.0.0.1:6379> setbit state 1 1 (integer) 0 127.0.0.1:6379> setbit state 2 0 (integer) 0 127.0.0.1:6379> setbit state 3 1 (integer) 0 127.0.0.1:6379> setbit state 4 1 (integer) 0 127.0.0.1:6379> setbit state 5 1 (integer) 0 127.0.0.1:6379> setbit state 6 1 (integer) 0 # 查看某一天是否打卡 127.0.0.1:6379> getbit state 3 (integer) 1 127.0.0.1:6379> getbit state 2 (integer) 0 # 统计打卡的天数 127.0.0.1:6379> bitcount state (integer) 6
使用场景:用户签到、统计活跃用户、用户在线状态
Redis单条命令是保持原子性的,但是整个事务是不保证原子性的!
Redis事务没有隔离级别的概念
事务本质:一组命令的集合,一个事务中的所有命令都会被序列化,在事务执行的过程中,会按照顺序执行
一次性、顺序性、排他性
事务执行流程
按顺序执行这个命令集,且不会被其他命令所插入执行。总的来说,事务的执行一共有三个阶段:
正常执行事务
127.0.0.1:6379> multi # 开启事务 OK # 命名入队 127.0.0.1:6379(TX)> set name xiaoli QUEUED 127.0.0.1:6379(TX)> set age 20 QUEUED 127.0.0.1:6379(TX)> get age QUEUED 127.0.0.1:6379(TX)> set gender 1 QUEUED 127.0.0.1:6379(TX)> exec # 执行事务 1) OK 2) OK 3) "20" 4) OK
手动放弃事务
127.0.0.1:6379> multi # 开启事务
OK
127.0.0.1:6379(TX)> set name xiaolin
QUEUED
127.0.0.1:6379(TX)> get name
QUEUED
127.0.0.1:6379(TX)> set birth 2003/05/16
QUEUED
127.0.0.1:6379(TX)> get birth
QUEUED
127.0.0.1:6379(TX)> discard # 取消事务
OK
127.0.0.1:6379> get birth # 事务队列中的命令都不会被执行
(nil)
编译性异常 (代码有问题,命令有错),事务中所有命令都不会被执行
全体连坐:意味着这一串命令只要有一个有错, 直接全体失败
127.0.0.1:6379> multi OK 127.0.0.1:6379(TX)> set name xiaolin QUEUED 127.0.0.1:6379(TX)> set age 18 QUEUED 127.0.0.1:6379(TX)> set gender 1 QUEUED 127.0.0.1:6379(TX)> getset gender # 错误的命令 (error) ERR wrong number of arguments for 'getset' command 127.0.0.1:6379(TX)> set email 965216379@qq.com QUEUED 127.0.0.1:6379(TX)> exec # 执行事务报错! (error) EXECABORT Transaction discarded because of previous errors. 127.0.0.1:6379> get name # 所有命令都不会执行 (nil) 127.0.0.1:6379> get age (nil)
运行时异常(1/0),命令没有报错但是逻辑出错了,那么其他正确的命令会执行,但是有错的不会执行,错误命令抛出异常
127.0.0.1:6379> multi OK 127.0.0.1:6379(TX)> set name xiaoming QUEUED 127.0.0.1:6379(TX)> incr name # 执行的时候失败,不能对字符串自增 QUEUED 127.0.0.1:6379(TX)> set age 20 QUEUED 127.0.0.1:6379(TX)> set gender 0 QUEUED 127.0.0.1:6379(TX)> get age QUEUED 127.0.0.1:6379(TX)> get gender QUEUED 127.0.0.1:6379(TX)> exec 1) OK 2) (error) ERR value is not an integer or out of range # 虽然第二条命令报错了,但是其他命令依然可以正常执行 3) OK 4) OK 5) "20" 6) "0"
Watch监控
悲观锁(Pessimistic Lock):顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁
乐观锁(Optimistic Lock):顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量
乐观锁策略:提交版本必须大于当前记录版本才能执行更新
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(TX)> decrby money 30
QUEUED
127.0.0.1:6379(TX)> incrby out 30
QUEUED
127.0.0.1:6379(TX)> exec
1) (integer) 70
2) (integer) 30
测试多线程修改值,使用watch
可以当做redis
的乐观锁操作
127.0.0.1:6379> watch money # 监视money
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> decrby money 20
QUEUED
127.0.0.1:6379(TX)> incrby out 20
QUEUED
127.0.0.1:6379(TX)> exec # 执行之前,另外一个线程修改我们值的,这个时候,就会导致事务执行失败
(nil)
如果修改失败,获取最新值即可
127.0.0.1:6379> unwatch # 如果发现事务执行失败,先解锁
OK
127.0.0.1:6379> watch money # 获取最新的值,再次监视,select version
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> decrby money 500
QUEUED
127.0.0.1:6379(TX)> incrby out 500
QUEUED
127.0.0.1:6379(TX)> exec # 对比监视的值是否发生变化,如果没有变化,那么可以执行成功,如果变了,就执行失败
1) (integer) 500
2) (integer) 530
注:只要执行了EXEC,之前加的监控锁都会被取消!Redis的事务不保证原子性,一条命令执行失败了,其他的仍然会执行,且不会回滚
SpringData:Spring的一个子项目。用于简化数据库访问,支持NoSQL和关系数据存储。其主要目标是让数据库的访问变得方便快捷
SpringDataRedis是Spring的一部分, 对Redis 底层开发包进行了高度封装
说明:在SpringBoot2.x之后,原来使用的jedis被替换成了lettuce
导入依赖
<!--操作redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
配置连接信息
# 配置redis
spring:
redis:
host: 127.0.0.1
port: 6379
注入模板进行测试
@SpringBootTest class RedisApplicationTests { @Autowired private RedisTemplate<Object, Object> redisTemplate; /** * RedisTemplate对大量api进行归类封装:opsForXXX * Redis五大数据类型:String、List、Hash、Set、ZSet * String:普通的字符串 * List:有序,可重复 * Hash:key-value键值对形式 * Set:无序,不可重复 * ZSet:有序,不可重复 */ // String:set、setex、setnx @Test public void testString() { redisTemplate.opsForValue().set("name", "admin"); String name = (String) redisTemplate.opsForValue().get("name"); System.out.println("name = " + name); // 存:并设置过期时间 int verifyCode = (int) Math.floor((Math.random() * 899999) + 100000); redisTemplate.opsForValue().set("code", String.valueOf(verifyCode), 1, TimeUnit.MINUTES); String code = (String) redisTemplate.opsForValue().get("code"); System.out.println("code = " + code); // 如果不存在该键才设置 Boolean flag = redisTemplate.opsForValue().setIfAbsent("email", "xiaobai@qq.com"); System.out.println("flag = " + flag); } }
默认采用JDK的序列化方式,可以修改默认的序列化方式
@Configuration // 配置类
public class RedisConfig {
@Bean
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
// 默认的Key序列化器为:JdkSerializationRedisSerializer
template.setKeySerializer(new StringRedisSerializer());
template.setHashKeySerializer(new StringRedisSerializer());
return template;
}
}
什么是持久化?
利用永久性存储介质(磁盘)将数据进行保存,在特定的时间将保存的数据进行恢复的工作机制称为持久化
为什么要进行持久化?
防止数据的意外丢失,确保数据安全性
Redis是内存数据库,如果不将内存中的数据库状态保存到磁盘,那么一旦服务器进程退出,服务器中的数据库状态也会消失。所以Redis提供了持久化功能
RDB全称Redis Database Backup file ( Redis数据备份文件),也被叫做Redis数据快照。简单来说就是把内存中的所有数据都记录到磁盘中。当Redis实例出现故障并重启后,从磁盘读取快照文件,恢复数据
将当前数据状态进行保存,快照形式,存储数据结果,存储格式简单,关注点在数据
RDB持久化是指在指定的时间间隔内将内存中的数据集快照写入磁盘。也是默认的持久化方式,这种方式是就是将内存中数据以快照的方式写入到二进制文件中,默认的文件名为dump.rdb
save指令相关配置
dbfilename dump.rdb
说明:设置本地数据库文件名,默认值为 dump.rdb
经验:通常设置为dump-端口号.rdb
dir
说明:设置存储.rdb
文件的路径
经验:通常设置成存储空间较大的目录中,目录名称data
rdbcompression yes
rdbchecksum yes
stop-writes-on-bgsave-error yes
bgsave指令工作原理
注: bgsave命令是针对save阻塞问题做的优化。Redis内部所有涉及到RDB操作都采用bgsave的方式
2、在指定的时间间隔内,执行指定次数的写操作(自动)
save 900 1
save 300 10
save 60 10000
save配置原理
注:
优点:
缺点:
RDB存储的弊端
解决方案
将数据的操作过程进行保存,日志形式,存储操作过程,存储格式复杂,关注点在数据的操作过程
AOF全称为Append Only File (追加文件)。Redis处理的每一个写命令都会记录在AOF文件,可以看做是命令日志文件
AOF:Redis 默认不开启。它的出现是为了弥补RDB的不足(数据的不一致性),所以它采用日志的形式来记录每个写操作(读操作不记录),并追加到文件中。Redis 重启的会根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作
AOF的主要作用是解决了数据持久化的实时性,目前已经是Redis持久化的主流方式
打开redis.conf
文件,找到 APPEND ONLY MODE 对应内容
redis 默认关闭,开启需要手动把no改为yes
appendonly yes
AOF持久化文件名,默认文件名为appendonly.aof
,建议配置为appendonly-端口号.aof
appendfilename "appendonly.aof"
指定更新日志条件
# 表示每执行一次写命令,立即记录到AOF文件
# appendfsync always
# 写命令执行完先放入AOF缓冲区,然后表示每隔1秒将缓冲区数据写到AOF文件,是默认方案
appendfsync everysec
# 写命令执行完先放入AOF缓冲区,由操作系统决定何时将缓冲区内容写回磁盘
# appendfsync no
AOF 的优缺点
优点:数据的完整性和一致性更高,每秒同步一次,可能会丢失一秒的数据
缺点:因为AOF记录的内容多,文件会越来越大,数据恢复也会越来越慢
随着命令不断写入AOF,文件会越来越大,为了解决这个问题,Redis引入了AOF重写机制压缩文件体积。AOF文件重写是将Redis进程内的数据转化为写命令同步到新AOF文件的过程。简单说就是将对同一个数据的若干个条命令执行结果转化成最终结果数据对应的指令进行记录
AOF重写作用
AOF重写方式
手动重写:bgrewriteaof
自动重写触发条件设置
# AOF文件比上次文件增长超过多少百分比则触发重写
auto-aof-rewrite-percentage percent
# AOF文件体积最小多大以上才触发重写
auto-aof-rewrite-min-size size
自动重写触发比对参数( 运行指令info Persistence获取具体信息 )
aof_current_size
aof_base_size
自动重写触发条件
RDB和AOF各有自己的优缺点,如果对数据安全性要求较高,在实际开发中往往会结合两者来使用
Redis发布订阅(pub/sub)是一种消息通讯模式:发送者(pub)发送消息,订阅者(sub)接收消息
Redis客户端可以订阅任意数量的频道
实现方式:
这些命令被广泛用于构建即时通信应用,比如网络聊天室(chatroom)和实时广播、实时提醒等
订阅端:
127.0.0.1:6379> subscribe cctv # 订阅名为cctv的频道
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "cctv"
3) (integer) 1
# 等待读取推送的信息
1) "message" # 消息
2) "cctv" # 来自那个频道的消息
3) "news" # 消息的具体内容
1) "message"
2) "cctv"
3) "hello,redis"
发送端:
127.0.0.1:6379> ping
PONG
127.0.0.1:6379> publish cctv news # 发布者发送消息到指定频道
(integer) 1
127.0.0.1:6379> publish cctv hello,redis # 发布者发布消息到指定频道
(integer) 1
Pub/Sub 从字面上理解就是发布(Publish)与订阅(Subscribe),在Redis中,你可以设定对某一个key值进行消息发布及消息订阅,当一个key值上进行了消息发布后,所有订阅它的客户端都会收到相应的消息。这一功能最明显的用法就是用作实时消息系统,比如普通的即时聊天,群聊等功能
使用场景:
主从复制,是指将一台Redis服务器的数据,复制到其他的Redis服务器。前者称为主节点(master/leader),后者称为从节点(slave/follower);数据的复制是单向的,只能由主节点到从节点。Master以写为主,Slave 以读为主
默认情况下,每台Redis服务器都是主节点
并且一个主节点可以有多个从节点(或没有从节点),但每一个从节点只能有一个主节点
一般来说,要将Redis运用于工程项目中,只使用一台Redis是万万不能的(会造成宕机),原因如下:
电商网站中的商品,一般都是一次上传,无数次浏览,说专业点也就是多读少写
主从复制,读写分离!80%的情况下都是在进行读操作,减缓服务器的压力,架构中常常使用,一主二从
只要在公司中,主从复制就是必须要使用的,因为在真实的项目中不可能单机使用Redis!
主从复制过程大体可以分为3个阶段:
127.0.0.1:6379> info replication # 查看当前库的信息
# Replication
role:master # 角色 master
connected_slaves:0 # 没有从机
master_failover_state:no-failover
master_replid:84eede35d4700858ff250c9a7b884511488f798f # 唯一标识的id
修改对应配置文件
port
端口log
文件名称修改完毕之后,启动3个Redis服务,可通过进程信息查看
默认情况下,每台Redis服务器都是主节点,一般情况下只需配置从机即可
主从连接(slave连接master)
一主(79)二从(80、81)
127.0.0.1:6380> slaveof 127.0.0.1 6379 # 找谁当自己的leader OK 127.0.0.1:6380> info replication # Replication role:slave # 当前角色是从机 master_host:127.0.0.1 master_port:6379 master_link_status:up # 查看主机的信息 127.0.0.1:6379> info replication # Replication role:master connected_slaves:1 # 多个从机的配置 slave0:ip=127.0.0.1,port=6380,state=online,offset=406,lag=1 # 从机的信息 master_failover_state:no-failover master_replid:885cc33c6da91d2b9792f697d9bd4c784b7bb4f7
如果两个服务都配置好了,就有两个从机了
注:这种命令的配置是一次性的,如果机器宕机、断电等,就需要重新认Leader!
在实际工作中,我们都是通过配置文件中修改指定配置的!这样的话是永久的
主机可以写,从机不能写只能读,主机写,从机读,主机中的所有信息和数据,都会自动从机保存
主机写:
从机只能读取内容!
主从连接三种方式
注:slave断开连接后,不会删除已有数据,只是不再接受master发送的数据
授权访问
自动选举Leader的模式
主从切换技术的方法是:当主服务器宕机后,需要手动把一台从服务器切换为主服务器,这就需要人工干预,费事费力,还会造成一段时间内服务不可用。这不是一种推荐的方式,更多时候,我们优先考虑 哨兵模式 。Redis从2.8开始正式提供了Sentinel(哨兵) 架构来解决这个问题
谋朝篡位
的自动版,能够后台监控主机是否故障,如果故障了根据投票数自动将从库转换为主库。
哨兵模式是一种特殊的模式,首先Redis提供了哨兵的命令,哨兵是一个独立的进程 ,作为进程,它会独立运行。其原理是哨兵通过发送命令,等待Redis服务响应,从而监控运行的多个Redis实例
这里的哨兵有两个作用
然而一个哨兵进程对Redis服务器进行监控,可能会出现问题,为此,我们可以使用多个哨兵进行监控。各个哨兵之间还会进行监控,这样就形成了多哨兵模式
目前状态是一主二从
配置哨兵配置文件
# sentinel monitor 被监控的的名称 host port 1
sentinel monitor myredis 127.0.0.1 6379 1
后面数字1,代表主机挂了,salve投票看让谁接替成为主机,票数多的就会成为主机
哨兵配置最佳实践
启动哨兵
redis-sentinel redisConfig/sentinel.conf
如果Master主节点断开了,这时候就会从从机中随机选择一个服务器,如果主机此时回来,它只能归并到新的主机下,当做从机(slave),这就是哨兵模式的规则
哨兵工作原理
问题:业务发展过程中遇到的峰值瓶颈
使用集群的方式可以快速解决上述问题
集群就是使用网络将若干台计算机联通起来,并提供统一的管理方式,使其对外呈现单机的服务效果
集群作用
数据存储设计
集群内部通讯设计
# 开启集群配置 添加节点 (yes)
cluster-enabled yes | no
# cluster配置文件名,该文件属于自动生成,仅用于快速查找文件并查询文件内容 (node-6379.conf)
cluster-config-file <filename>
# 节点服务响应超时时间,用于判定该节点是否下线或切换为从节点 (10000)
cluster-node-timeout <milliseconds>
# master连接的slave最小数量 (1)
cluster-migration-barrier <count>
# 查看集群节点信息
cluster nodes
# 防止路由失效加参数 -c
redis-cli -p 6381 -c
搭建主从配置
分别配置6个不同的Redis端口服务,然后启动 sed "s/6380/6381/g" redis-6380.conf > redis-6381.conf
构建主从集群
redis-cli --cluster create 127.0.0.1:6380 127.0.0.1:6381 127.0.0.1:6382 127.0.0.1:6383 127.0.0.1:6384 127.0.0.1:6385 --cluster-replicas 1
注:Redis5.0开始,建议使用redis-cli
作为创建集群的命令,不推荐再使用redis-trib.rb
来创建集群了,毕竟使用redis-trib.rb还要安装Ruby程序,比redis-cli麻烦的多!
概念
缓存穿透就是用户想要查询一个数据,发现redis内存没有数据库也没有,也就是缓存没有命中,于是向持久层数据库查询。发现也没有,于是本次查询失败。当用户很多的时候,缓存都没有命中,于是都去请求了持久层数据库。这会给持久层数据库造成很大的压力,这时候就相当于出现了缓存穿透
解决方案
布隆过滤器
布隆过滤器是一种数据结构,对所有可能查询的参数以hash形式存储,在控制层先进行校验,不符合则丢弃,从而避免了对底层存储系统的查询压力
缓存空对象
当存储层不命中后,即使返回的空对象也将其缓存起来,同时会设置一个过期时间 ,之后再访问这个数据将会从缓存中获取,保护了后端的数据源
此种方法存在的问题:
概念
是指一个非常热点的key,在不停的扛着大并发,当这个key失效时,一瞬间大量的请求冲到持久层的数据库中,就像在一堵墙上某个点凿开了一个洞!
解决方案
设置热点key永不过期
从缓存层面来看,没有设置过期时间,所以不会出现热点key过期后产生的问题
加互斥锁
在查询持久层数据库时,保证了只有一个线程能够进行持久层数据查询,其他的线程让它睡眠几百毫秒,等待第一个线程查询完会回写到Redis缓存当中,剩下的线程可以正常查询Redis缓存,就不存在大量请求去冲击持久层数据库了!
概念
在某一个时间段,缓存的key大量集中同时过期了,所有的请求全部冲到持久层数据库上,导致持久层数据库挂掉!
范例:双十一零点抢购,这波商品比较集中的放在缓存,设置了失效时间为1个小时,那么到了凌晨一点,这批缓存全部失效了,而大量的请求过来时,全部冲过了缓存,冲到了持久层数据库!
解决方案
Redis高可用
搭建Redis集群,既然redis有可能挂掉,那我多增设几台redis,这样一台挂掉之后其他的还可以继续工作,其实就是搭建的集群。(异地多活!)
限流降级
在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待
数据预热
数据加热的含义就是在正式部署之前,先把可能的数据先预先访问一遍,这样部分可能大量访问的数据就会加载到缓存中。在即将发生大并发访问前手动触发加载缓存不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。