Redis
1、NoSql
1.1、什么是Nosql?
NoSQL,泛指非关系型的数据库。意即不仅仅是SQL ,NoSQL有时也称作Not Only SQL的缩写,是对不同于传统的关系型数据库的数据库管理系统的统称
随着互联网web2.0网站的兴起,传统的关系数据库在处理web2.0网站,特别是超大规模和高并发的SNS类型的web2.0纯动态网站已经显得力不从心,出现了很多难以克服的问题,而非关系型的数据库则由于其本身的特点得到了非常迅速的发展
NoSQL数据库的产生就是为了解决大规模数据集合多重数据种类带来的挑战,尤其是大数据应用难题,包括超大规模数据的存储
1.2、为什么要使用Nosql?
传统的关系数据库具有不错的性能,高稳定型,久经历史考验,而且使用简单,功能强大,同时也积累了大量的成功案例。在互联网领域,MySQL成为了绝对靠前的王者,毫不夸张的说,MySQL为互联网的发展做出了卓越的贡献。
在90年代,一个网站的访问量一般都不大,用单个数据库完全可以轻松应付。在那个时候,更多的都是静态网页,动态交互类型的网站不多
关系数据库很强大,但是它并不能很好的应付所有的应用场景。MySQL的扩展性差(需要复杂的技术来实现),大数据量场景下IO压力大,表结构更改困难,正是当前使用MySQL的开发人员面临的问题
今天我们可以通过第三方平台(如:Google,Facebook等)可以很容易的访问和抓取数据。用户的个人信息,社交网络,地理位置,用户生成的数据和用户操作日志已经成倍的增加。我们如果要对这些用户数据进行挖掘,那SQL数据库已经不适合这些应用了,NoSQL数据库的发展也却能很好的处理这些大的数据
1.3、RDBMS vs NoSQL
RDBMS
- 高度组织化结构化数据
- 结构化查询语言(SQL) (SQL)
- 数据和关系都存储在单独的表中。
- 数据操纵语言,数据定义语言
- 严格的一致性
- 基础事务
NoSQL
- 代表着不仅仅是SQL
- 没有声明性查询语言
- 没有预定义的模式
- 键 - 值对存储,列存储,文档存储,图形数据库
- 最终一致性,而非ACID属性
- 非结构化和不可预知的数据
- CAP定理
- 高性能,高可用性和可伸缩性
1.4、CAP定理(CAP theorem)
在计算机科学中, CAP定理(CAP theorem), 又被称作 布鲁尔定理(Brewer’s theorem), 它指出对于一个分布式计算系统来说,不可能同时满足以下三点:
- 一致性(Consistency) (所有节点在同一时间具有相同的数据)
- 可用性(Availability) (保证每个请求不管成功或者失败都有响应)
- 分隔容忍(Partition tolerance) (系统中任意信息的丢失或失败不会影响系统的继续运作)
CAP理论的核心是:一个分布式系统不可能同时很好的满足一致性,可用性和分区容错性这三个需求,最多只能同时较好的满足两个。
因此,根据 CAP 原理将 NoSQL 数据库分成了满足 CA 原则、满足 CP 原则和满足 AP 原则三 大类:
- CA -单点集群,满足一致性,可用性的系统,通常在可扩展性上不太强大。
- CP - 满足一致性,分区容忍性的系统,通常性能不是特别高。
- AP - 满足可用性,分区容忍性的系统,通常可能对一致性要求低一些
CA:传统Oracle数据库
AP:大多数网站架构的选择
CP:Redis 、Mongodb
注意:分布式架构的时候必须要做出取舍
一致性和可用性之间取一个平衡。多余大多数web应用,其实并不需要强一致性。
因此牺牲C换取P,这是目前分布式数据库产品的方向
1.5、当下NoSQL的经典应用
最佳实践:当下的应用是 SQL 与 NoSQL 一起使用的 代表项目:阿里巴巴商品信息的存放
去IOE
去除:IBM是服务器提供商,Oracle是数据库软件提供商,EMC则是存储设备提供商
荐文
1.6、NoSql四大分类
KV键值对
- 新浪:BerKeleyDB+redis
- 美团:redis+tair
- 阿里百度:memcache+Redis
文档数据库(bson格式和json一致)
- CacheDB
- MongoDB
- MongoDB 是一个基于分布式文件存储的数据库。由 C++ 语言编写。旨在为 WEB 应用提供可扩展的高性能数据存储解决方案
- MongoDB 是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库当中功能最丰富,最像关系数据库的
列存储数据库
图关系数据库
不是来存图形的,而是来存储关系的,如朋友圈社交网络,广告推荐 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度关系查找等 | 很多时候需要对整个图做计算才能得出需要的信息,而且这种结构不太好做分布式的集群 |
2、Redis入门
2.1、Redis是什么?
Redis(Remote Dictionary Server ),即远程字典服务
是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value 数据库,并提供多种语言的API
开源且免费,是当前最热门的NoSQL技术之一,也被人们称之为结构化数据库
2.2、Redis可以做什么?
官方提供数据为:读的速度是11w次/s,写的速度是8.1w次/s
- 内存存储、持久化
- 效率高,可用于高速缓存
- 发布订阅系统
- 地图信息分析
- 计时器、计数器、浏览量等
特性:
官网
中文网
window在Github上下载
注:Redis推荐在Linux服务器上搭建
2.3、Window安装
-
下载安装包 -
下载完毕得到压缩包 -
解压到环境目录下即可,Redis十分小,只有5M -
开启Redis,双击redis-server.exe 即可 redis默认端口:6379 -
使用redis客户端连接redis 双击redis-cli.exe ,出现以下界面
2.4、Linux安装
-
下载安装包 redis-6.2.6.tar.gz -
解压redis安装包 程序一般放在/opt 目录下 tar -axvf redis-6.2.6.tar.gz
-
解压完毕后,可以看到redis的配置文件 -
基本环境安装 yum install gcc-c++
make
make install
redis默认安装路径:usr/local/bin -
新建一个目录,将redis配置文件复制到当前目录下 -
redis默认不是后台启动的,修改配置文件 vim redis.conf -
启动Redis服务 usr/local/bin -
使用redis-cli 进行连接测试 -
查看redis进程 -
关闭redis服务 shutdown
2.5、基础知识
redis默认有16个数据库
默认使用的是第0个,可以使用select 进行切换
127.0.0.1:6379> select 6
OK
127.0.0.1:6379[6]> dbsize
(integer) 0
127.0.0.1:6379[2]> keys *
1) "name"
清除当前数据库的内容 flushdb
清除全部数据库的内容 FLUSHALL
Redis是单线程的,官方FAQ表示,因为Redis是基于内存操作的,CPU不是Redis的性能瓶颈,它的瓶颈是根据机器的内存大小和网络带宽,既然可以使用单线程来实现,那就顺理成章地采用单线程的方案了
Redis为什么是单线程还这么快?
- redis是将所有的数据存放在内存中的,内存的读写速度非常快,所以说使用单线程去操作效率是最高的
- redis是单线程的,省去了很多上下文切换线程的时间;对于内存系统来说,如果没有上下文切换效率就是最高的,多次读写都是在一个CPU上的,在内存情况下,这是个最佳的方案
3、五大数据类型
3.1、Redis-Key
127.0.0.1:6379[2]> keys *
(empty array)
127.0.0.1:6379[2]> set username xiaozhang
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
(integer) 1
127.0.0.1:6379[2]> exists name
(integer) 0
127.0.0.1:6379[2]> move username 6
(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
(integer) 1
127.0.0.1:6379> ttl age
(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
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
(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
string
redis命令手册
3.2、String(字符串)
127.0.0.1:6379> set username xiaozhang
OK
127.0.0.1:6379> get username
"xiaozhang"
127.0.0.1:6379> keys *
1) "username"
127.0.0.1:6379> exists username
(integer) 1
127.0.0.1:6379> append username hello
(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"
127.0.0.1:6379> set views 0
OK
127.0.0.1:6379> get views
"0"
127.0.0.1:6379> incr views
(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
(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
127.0.0.1:6379> set str hello,redis
OK
127.0.0.1:6379> get str
"hello,redis"
127.0.0.1:6379> getrange str 0 4
"hello"
127.0.0.1:6379> getrange str 0 -1
"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"
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
OK
127.0.0.1:6379> ttl age
(integer) 12
127.0.0.1:6379> setnx mykey redis
(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
(integer) 0
127.0.0.1:6379> get mykey
"redis"
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
127.0.0.1:6379> get email
(nil)
127.0.0.1:6379> set user:1 {name:zhangsan,age:1}
OK
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"
127.0.0.1:6379> getset db redis
(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 除了是字符串还可以是数字
3.3、List(列表)
所有的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
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
"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
(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
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"
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"
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
(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
(integer) 4
127.0.0.1:6379> lrange list 0 -1
1) "first"
2) "third"
3) "fourth"
4) "second"
3.4、Set(集合)
set 中的值是不允许重复的
127.0.0.1:6379> sadd myset hello
(integer) 1
127.0.0.1:6379> sadd myset redis
(integer) 1
127.0.0.1:6379> smembers myset
1) "redis"
2) "hello"
127.0.0.1:6379> SISMEMBER myset hello
(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
(integer) 3
127.0.0.1:6379> srem myset hello
(integer) 1
127.0.0.1:6379> scard myset
(integer) 2
127.0.0.1:6379> smembers myset
1) "redis"
2) "hi,xiaozhao"
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
"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
(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"
3.5、Hash(散列)
Map集合,key-<key-value> 这时,这个值是一个map集合
127.0.0.1:6379> hset myhash username xiaolin
(integer) 1
127.0.0.1:6379> hget myhash username
"xiaolin"
127.0.0.1:6379> hmset myhash username xiaozhang age 20
OK
127.0.0.1:6379> hmget myhash username age
1) "xiaozhang"
2) "20"
127.0.0.1:6379> hgetall myhash
1) "username"
2) "xiaozhang"
3) "age"
4) "20"
127.0.0.1:6379> hdel myhash age
(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
(integer) 3
127.0.0.1:6379> hexists myhash email
(integer) 1
127.0.0.1:6379> hexists myhash gender
(integer) 0
127.0.0.1:6379> hkeys myhash
1) "username"
2) "age"
3) "email"
127.0.0.1:6379> hvals myhash
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存储
3.6、Zset(有序集合)
在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
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"
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
4、三种特殊数据类型
4.1、geospatial(地理位置)
朋友定位、附近的人,打车距离计算
Redis GEO 主要用于存储地理位置信息,并对存储的信息进行操作,该功能在 Redis 3.2 版本新增。
经度纬度查询
相关命名,只有六个命令
geoadd 用于存储指定的地理空间位置,可以将一个或多个经度(longitude)、纬度(latitude)、位置名称(member)添加到指定的 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 返回两个给定位置之间的距离
单位:
- m :米,默认单位。
- km :千米。
- mi :英里。
- ft :英尺。
127.0.0.1:6379> geodist china:city beijing shanghai
"1067673.5823"
127.0.0.1:6379> geodist china:city beijing shanghai km
"1067.6736"
127.0.0.1:6379> geodist china:city henan hangzhou km
"785.5734"
georadius 以给定的经纬度为中心,找出某一半径内的元素
127.0.0.1:6379> georadius china:city 110 30 1000 km
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"
4.2、Hyperloglog(基数统计)
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
(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
OK
127.0.0.1:6379> pfcount letters
(integer) 12
如果允许容错,建议使用 Hpyerloglog
如果不允许容错,就使用set 或者其他数据类型即可
4.3、Bitmap(位图)
位存储
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
使用场景:用户签到、统计活跃用户、用户在线状态
5、事务
Redis单条命令是保持原子性的,但是整个事务是不保证原子性的!
Redis事务没有隔离级别的概念
事务本质:一组命令的集合,一个事务中的所有命令都会被序列化,在事务执行的过程中,会按照顺序执行
一次性、顺序性、排他性
按顺序执行这个命令集,且不会被其他命令所插入执行。总的来说,事务的执行一共有三个阶段:
- 开启事务(Multi)
- 命令入队
- 执行事务(EXec)
正常执行事务
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
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
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
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的事务不保证原子性,一条命令执行失败了,其他的仍然会执行,且不会回滚
6、SpringBoot整合
SpringBoot操作数据:spring-data jpa jdbc mongodb redis
SpringData也是和SpringBoot齐名的项目
说明:在SpringBoot2.x之后,原来使用的jedis被替换成了lettuce
源码分析:
@Bean
@ConditionalOnMissingBean(name = {"redisTemplate"})
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> template = new RedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
return new StringRedisTemplate(redisConnectionFactory);
}
-
导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
-
配置连接
spring:
redis:
host: 127.0.0.1
port: 6379
-
测试 @SpringBootTest
class RedisApplicationTests {
@Autowired
private RedisTemplate redisTemplate;
@Test
void contextLoads() {
redisTemplate.opsForValue().set("name", "晓琳");
System.out.println(redisTemplate.opsForValue().get("name"));
}
}
-
默认采用JDK的序列化方式,可能会使用JSON来序列化 -
编写自己的RedisTemplate @Configuration
public class RedisConfig {
@Bean
@SuppressWarnings("all")
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.serialize(om);
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
template.setKeySerializer(stringRedisSerializer);
template.setHashKeySerializer(stringRedisSerializer);
template.setValueSerializer(jackson2JsonRedisSerializer);
template.setHashValueSerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
}
@Configuration
public class RedisConfig {
@Bean
@SuppressWarnings("all")
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.serialize(om);
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
template.setKeySerializer(stringRedisSerializer);
template.setHashKeySerializer(stringRedisSerializer);
template.setValueSerializer(jackson2JsonRedisSerializer);
template.setHashValueSerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
}
7、Redis持久化
Redis是内存数据库,如果不将内存中的数据库状态保存到磁盘,那么一旦服务器进程退出,服务器中的数据库状态也会消失。所以Redis提供了持久化功能
7.1、RDB(Redis DataBase)
什么是RDB?
原理是redis会单独创建(fork) 一个与当前进程一模一样的子进程进行持久化,这个子进程的所有数据(变量。环境变量,程序程序数器等)都和原进程一模一样,会先将数据写入到一个临时文件中待持久化结束了,再用这个临时文件替换上次持久化好的文件,整过程中,主进程不进行任何的io操作,这就确保了极高的性能
持久化文件在哪里
RDB持久化是指在指定的时间间隔内将内存中的数据集快照写入磁盘。也是默认的持久化方式,这种方式是就是将内存中数据以快照的方式写入到二进制文件中,默认的文件名为dump.rdb
触发机制
- 在指定的时间间隔内,执行指定次数的写操作
- 执行save(阻塞, 只管保存快照,其他的等待) 或者是bgsave (异步)命令
- 执行
flushall 命令,清空数据库所有数据,意义并不大 - 执行
shutdown 命令,保证服务器正常关闭且不丢失任何数据,意义并不大
通过RDB文件恢复数据
将dump.rdb文件拷贝到redis的安装目录的bin目录下,重启redis服务即可(redis启动的时候会自动检查dump.rdb并恢复其中的数据)。在实际开发中,一般会考虑到物理机硬盘损坏情况,选择备份dump.rdb
RDB的优缺点
优点:
- 适合大规模的数据恢复
- 如果业务对数据完整性和一致性要求不高,RDB是很好的选择
缺点:
- 数据的完整性和一致性不高,因为RDB可能在最后一次备份时宕机了
- 备份时占用内存,因为Redis 在备份时会独立创建一个子进程,将数据写入到一个临时文件(此时内存中的数据是原来的两倍哦),最后再将临时文件替换之前的备份文件
所以Redis 的持久化和数据的恢复要选择在夜深人静的时候执行是比较合理的
7.2、AOF(Append Only File)
AOF :Redis 默认不开启。它的出现是为了弥补RDB的不足(数据的不一致性),所以它采用日志的形式来记录每个写操作(读操作不记录),并追加到文件中。Redis 重启的会根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作
从配置文件了解AOF
打开redis.conf 文件,找到 APPEND ONLY MODE 对应内容 1 redis 默认关闭,开启需要手动把no改为yes
appendonly yes
2 指定本地数据库文件名,默认值为 appendonly.aof
appendfilename "appendonly.aof"
3 指定更新日志条件
appendfsync everysec
always:同步持久化,每次发生数据变化会立刻写入到磁盘中。性能较差当数据完整性比较好(慢,安全)
everysec:出厂默认推荐,每秒异步记录一次(默认值)
no:不同步
如果aof 文件有错误,这时候redis是启动不起来的,需要修复这个文件
redis提供了一个工具:redis-check-aof --fix appendonly.aof 进行修复
如果文件正常,重启即可恢复
AOF 的优缺点
优点:数据的完整性和一致性更高,每秒同步一次,可能会丢失一秒的数据
缺点:因为AOF记录的内容多,文件会越来越大,数据恢复也会越来越慢
总结
- Redis 默认开启RDB持久化方式,在指定的时间间隔内,执行指定次数的写操作,则将内存中的数据写入到磁盘中。
- RDB 持久化适合大规模的数据恢复但它的数据一致性和完整性较差。
- Redis 需要手动开启AOF持久化方式,默认是每秒将写操作日志追加到AOF文件中。
- AOF 的数据完整性比RDB高,但记录内容多了,会影响数据恢复的效率。
- Redis 针对 AOF文件大的问题,提供重写的瘦身机制。
- 若只打算用Redis 做缓存,可以关闭持久化。
- 若打算使用Redis 的持久化。建议RDB和AOF都开启。其实RDB更适合做数据的备份,留一后手。AOF出问题了,还有RDB
8、Redis发布订阅
Redis发布订阅(pub/sub)是一种消息通讯模式:发送者(pub)发送消息,订阅者(sub)接收消息
Redis客户端可以订阅任意数量的频道
实现方式:
这些命令被广泛用于构建即时通信应用,比如网络聊天室(chatroom)和实时广播、实时提醒等
订阅端:
127.0.0.1:6379> subscribe 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值上进行了消息发布后,所有订阅它的客户端都会收到相应的消息。这一功能最明显的用法就是用作实时消息系统,比如普通的即时聊天,群聊等功能
使用场景:
9、主从复制
9.1、什么是主从复制
主从复制,是指将一台Redis服务器的数据,复制到其他的Redis服务器。前者称为主节点(master/leader),后者称为从节点(slave/follower);数据的复制是单向的,只能由主节点到从节点。Master以写为主,Slave 以读为主
默认情况下,每台Redis服务器都是主节点
并且一个主节点可以有多个从节点(或没有从节点),但每一个从节点只能有一个主节点
主要作用:
- 数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式
- 故障恢复:当主节点出现问题时,可以由从节点提供服务,实现快速的故障恢复;实际上是一种服务的冗余
- 负载均衡:在主从复制的基础上,配合读写分离,可以由主节点提供写服务,由从节点提供读服务(即写Redis数据时应用连接主节点,读Redis数据时应用连接从节点),分担服务器负载;尤其是在写少读多的场景下,通过多个从节点分担读负载,可以大大提高Redis服务器的并发量
- 高可用(集群):除了上述作用以外,主从复制还是哨兵和集群能够实施的基础,因此说主从复制是Redis高可用的基础
一般来说,要将Redis运用于工程项目中,只使用一台Redis是万万不能的(会造成宕机),原因如下:
- 从结构上,单个Redis服务器会发生单点故障,并且一台服务器需要处理所有的请求负载,压力较大
- 从容量上,单个Redis服务器内存容量有限,就算一台Redis服务器内存容量为256G,也不能将所有内存用作Redis存储内存,一般来说,单台Redis最大使用不应超过20G
电商网站中的商品,一般都是一次上传,无数次浏览,说专业点也就是多读少写
**主从复制,读写分离!**80%的情况下都是在进行读操作,减缓服务器的压力,架构中常常使用,一主二从
只要在公司中,主从复制就是必须要使用的,因为在真实的项目中不可能单机使用Redis!
9.2、环境配置(单机集群)
127.0.0.1:6379> info replication
role:master
connected_slaves:0
master_failover_state:no-failover
master_replid:84eede35d4700858ff250c9a7b884511488f798f
开启三台服务:
复制三个配置文件,修改对应的信息
port 端口pid 名log 文件名称dump.rdb 名称
修改完毕之后,启动3个Redis服务,可通过进程信息查看
9.3、一主二从(单机测试)
**默认情况下,每台Redis服务器都是主节点,**一般情况下只需配置从机即可
认大哥leader
一主(79)二从(80、81)
127.0.0.1:6380> slaveof 127.0.0.1 6379
OK
127.0.0.1:6380> info replication
role:slave
master_host:127.0.0.1
master_port:6379
master_link_status:up
127.0.0.1:6379> info 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
如果两个服务都配置好了,就有两个从机了
注:这种**通过命令的配置是一次性的,如果机器宕机、断电等,就需要重新认大哥! 在实际工作中,我们都是通过配置文件中修改指定配置的!**这样的话是永久的
主机可以写,从机不能写只能读,**主机写,从机读,**主机中的所有信息和数据,都会自动从机保存
主机写:
从机只能读取内容!
证明,虽然主机断开了,但是从机还是可以正常读取原先就有的数据的!
如果是使用命令行,来配置的主从,这个时候如果重启了,就会变回主机!只要变为从机,立马就会从主机中获取到值!
9.4、复制原理
Slave 启动成功连接到 master 后会发送一个sync同步命令
Master 接到命令,启动后台的存盘进程,同时收集所有接收到的用于修改数据集命令,在后台进程执行完毕之后,master将传送整个数据文件到slave,并完成一次完全同步。
**全量复制:**而slave服务在接收到数据库文件数据后,将其存盘并加载到内存中
增量复制: Master 继续将新的所有收集到的修改命令依次传给slave,完成同步
但是只要是重新连接master,一次完全同步(全量复制)将被自动执行! 我们的数据一定可以在从机中看到
9.5、哨兵模式
自动选举大哥的模式
主从切换技术的方法是:当主服务器宕机后,需要手动把一台从服务器切换为主服务器,这就需要人工干预,费事费力,还会造成一段时间内服务不可用。这不是一种推荐的方式,更多时候,我们优先考虑 哨兵模式 。Redis从2.8开始正式提供了Sentinel(哨兵) 架构来解决这个问题
谋朝篡位 的自动版,能够后台监控主机是否故障,如果故障了根据投票数自动将从库转换为主库。
哨兵模式是一种特殊的模式,首先Redis提供了哨兵的命令,哨兵是一个独立的进程 ,作为进程,它会独立运行。其原理是哨兵通过发送命令,等待Redis服务响应,从而监控运行的多个Redis实例
这里的哨兵有两个作用
- 通过发送命令,让Redis服务器返回监控其运行状态,包括主服务器和从服务器
- 当哨兵监测到master宕机,会自动将slave切换成master,然后通过发布订阅模式通知其他的从服务器,修改配置文件,让它们切换主机
然而一个哨兵进程对Redis服务器进行监控,可能会出现问题,为此,我们可以使用多个哨兵进行监控。各个哨兵之间还会进行监控,这样就形成了多哨兵模式
目前状态是一主二从
-
配置哨兵配置文件
sentinel monitor myredis 127.0.0.1 6379 1
后面数字1,代表主机挂了,salve投票看让谁接替成为主机,票数多的就会成为主机 -
启动哨兵 redis-sentinel redisConfig/sentinel.conf
如果Master主节点断开了,这时候就会从从机中随机选择一个服务器,如果主机此时回来,它只能归并到新的主机下,当做从机(小弟),这就是哨兵模式的规则
9.6、总结
优点:
- 哨兵集群,基于主从复制模式 ,所有的主从配置优点,它全有
- 主从可以切换,故障可以转移 ,系统的可用性就会更好
- 哨兵模式就是主从模式的升级,手动到自动,更加健壮!
缺点:
- Redis不好在线扩容的,集群容量一旦到达上限,在线扩容就十分麻烦!
- 实现哨兵模式的配置其实是很麻烦的,里面有很多选择!
10、缓存穿透和雪崩
10.1、缓存穿透(查不到)
概念
缓存穿透就是用户想要查询一个数据,发现redis内存没有数据库也没有,也就是缓存没有命中,于是向持久层数据库查询。发现也没有,于是本次查询失败。当用户很多的时候,缓存都没有命中,于是都去请求了持久层数据库。这会给持久层数据库造成很大的压力,这时候就相当于出现了缓存穿透
解决方案
布隆过滤器
布隆过滤器是一种数据结构,对所有可能查询的参数以hash形式存储,在控制层先进行校验,不符合则丢弃,从而避免了对底层存储系统的查询压力
缓存空对象
当存储层不命中后,即使返回的空对象也将其缓存起来,同时会设置一个过期时间 ,之后再访问这个数据将会从缓存中获取,保护了后端的数据源
此种方法存在的问题:
- 如果空值能够被缓存起来,这就意味着缓存需要更多的空间存储更多的键,因为这当中可能会有很多
的空值的键 - 即使对空值设置了过期时间,还是会存在缓存层和存储层的数据会有一段时间窗口的不一致,这对于需要保持一致性的业务会有影响
10.2、缓存击穿(查询量太大)
概念
是指一个非常热点的key,在不停的扛着大并发,当这个key失效时,一瞬间大量的请求冲到持久层的数据库中,就像在一堵墙上某个点凿开了一个洞!
解决方案
设置热点key永不过期
从缓存层面来看,没有设置过期时间,所以不会出现热点*key过期后产生的问题
加互斥锁
在查询持久层数据库时,保证了只有一个线程能够进行持久层数据查询,其他的线程让它睡眠几百毫秒,等待第一个线程查询完会回写到Redis缓存当中,剩下的线程可以正常查询Redis缓存,就不存在大量请求去冲击持久层数据库了!
10.3、缓存雪崩
概念
在某一个时间段,缓存的key大量集中同时过期了,所有的请求全部冲到持久层数据库上,导致持久层数据库挂掉! 范例:双十一零点抢购,这波商品比较集中的放在缓存,设置了失效时间为1个小时,那么到了凌晨一点,这批缓存全部失效了,而大量的请求过来时,全部冲过了缓存,冲到了持久层数据库!
解决方案
Redis高可用
搭建Redis集群,既然redis有可能挂掉,那我多增设几台redis,这样一台挂掉之后其他的还可以继续工作,其实就是搭建的集群。(异地多活!)
限流降级
在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待
数据预热
数据加热的含义就是在正式部署之前,先把可能的数据先预先访问一遍,这样部分可能大量访问的数据就会加载到缓存中。在即将发生大并发访问前手动触发加载缓存不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀
|