Redis

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定理
  • 高性能,高可用性和可伸缩性

总结:关系型数据库与NOSQL数据库并非对立而是互补的关系,即通常情况下使用关系型数据库,在适合使用NoSQL的时候使用NOSQL数据库,让NoSQL数据库对关系型数据库的不足进行弥补。
一般会将数据存储在关系型数据库中,在nosql数据库中备份存储关系型数据库的数据

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:RedisMongodb

注:分布式架构的时候必须要做出取舍

一致性和可用性之间取一个平衡。对于大多数web应用,其实并不需要强一致性

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 是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库当中功能最丰富,最像关系数据库的

列存储数据库

  • 分布式文件系统
  • Cassandra,HBase

图关系数据库

不是来存图形的,而是来存储关系的,如朋友圈社交网络,广告推荐
Neo4J,InfoGrid

比较:

分类Examples举例典型应用场景数据模型优点缺点
键值(key-value)Tokyo Cabinet/Tyrant,Redis Voldemort,Oracle BDB内容缓存,主要用于处理大量数据的高访问负载,也用于一些日志系统等等。Key 指向 Value 的键值对,通常用hash table来实现查找速度快数据无结构化,通常只被当作字符串或者二进制数据
列存储数据库Cassandra, HBase,Riak分布式的文件系统以列簇式存储,将同一列数据存在一起查找速度快,可扩展性强,更容易进行分布式扩展功能相对局限
文档型数据库CouchDB,MongoDbWeb应用(与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

  • 内存存储、持久化
  • 效率高,可用于高速缓存
  • 发布订阅系统
  • 地图信息分析
  • 计时器、计数器、浏览量等

特性:

  • 多样的数据类型
  • 持久化
  • 集群
  • 事务
  • ……

应用场景

  • 缓存(数据查询、短连接、新闻内容、商品内容等等)
  • 聊天室的在线好友列表
  • 任务队列。(秒杀、 抢购、12306等等)
  • 应用排行榜
  • 应用排行榜
  • 数据过期处理(可以精确到毫秒)
  • 分布式集群架构中的session分离

官网

中文网

Windows可以在Github上下载

注:Redis推荐在Linux服务器上搭建

2.3、Window安装

  1. 下载安装包

  2. 下载完毕得到压缩包

  3. 解压到环境目录下即可,Redis十分小,只有5M

  4. 开启Redis,双击redis-server.exe即可

    redis默认端口:6379

  5. 使用redis客户端连接redis

    双击redis-cli.exe,出现以下界面

2.4、Linux安装

  1. 下载安装包 redis-6.2.6.tar.gz

  2. 解压redis安装包 程序一般放在/opt目录下

    tar -axvf redis-6.2.6.tar.gz
    

  3. 解压完毕后,可以看到redis的配置文件

  4. 基本环境安装

    yum install gcc-c++
    make install
    


    redis默认安装路径:usr/local/bin

  5. 新建一个目录,将redis配置文件复制到当前目录下

  6. redis默认不是后台启动的,修改配置文件 vim redis.conf

  7. 启动Redis服务 usr/local/bin

  8. 使用redis-cli进行连接测试

  1. 查看redis进程

  2. 关闭redis服务 shutdown

2.5、基础知识

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为什么是单线程还这么快?

  • redis是将所有的数据存放在内存中的,内存的读写速度非常快,所以说使用单线程去操作效率是最高的
  • redis是单线程的,省去了很多上下文切换线程的时间;对于内存系统来说,如果没有上下文切换效率就是最高的,多次读写都是在一个CPU上的,在内存情况下,这是个最佳的方案

3、五大数据类型


3.1、Redis-Key

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

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 *	# 获得当前数据库所有的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除了是字符串还可以是数字

  • 计数器
  • 分布式锁
  • 对象缓存存储

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	# 获取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"

3.4、Set(集合)

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"

3.5、Hash(散列)

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存储

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	# 显示工资小于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

4、高级数据类型


4.1、geospatial(地理位置)

朋友定位、附近的人,打车距离计算

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 返回两个给定位置之间的距离

单位:

  • m :米,默认单位
  • km :千米
  • mi :英里
  • ft :英尺
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"

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		# 统计 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或者其他数据类型即可

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	# 监视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的事务不保证原子性,一条命令执行失败了,其他的仍然会执行,且不会回滚

6、SpringBoot整合


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;
    }
}

7、Redis持久化


什么是持久化?

利用永久性存储介质(磁盘)将数据进行保存,在特定的时间将保存的数据进行恢复的工作机制称为持久化

为什么要进行持久化?

防止数据的意外丢失,确保数据安全性

Redis是内存数据库,如果不将内存中的数据库状态保存到磁盘,那么一旦服务器进程退出,服务器中的数据库状态也会消失。所以Redis提供了持久化功能

7.1、RDB(Redis DataBase)

RDB全称Redis Database Backup file ( Redis数据备份文件),也被叫做Redis数据快照。简单来说就是把内存中的所有数据都记录到磁盘中。当Redis实例出现故障并重启后,从磁盘读取快照文件,恢复数据

将当前数据状态进行保存,快照形式,存储数据结果,存储格式简单,关注点在数据

RDB持久化是指在指定的时间间隔内将内存中的数据集快照写入磁盘。也是默认的持久化方式,这种方式是就是将内存中数据以快照的方式写入到二进制文件中,默认的文件名为dump.rdb

7.1.1、触发机制
  • 手动执行save(阻塞,只管保存快照,其他的等待) 或者是bgsave (启动后台保存操作,但不是立即执行,异步)命令
  • Redis手动关闭时会自动执行一次RDB

save指令相关配置

  • dbfilename dump.rdb

    • 说明:设置本地数据库文件名,默认值为 dump.rdb

    • 经验:通常设置为dump-端口号.rdb

  • dir

    • 说明:设置存储.rdb文件的路径

    • 经验:通常设置成存储空间较大的目录中,目录名称data

  • rdbcompression yes

    • 说明:设置存储至本地数据库时是否压缩数据,默认为 yes,采用 LZF 压缩
    • 经验:建议不开启,压缩也会消耗CPU,磁盘的话不值钱
  • rdbchecksum yes

    • 说明:设置是否进行RDB文件格式校验,该校验过程在写文件和读文件过程均进行
    • 经验:通常默认为开启状态,如果设置为no,可以节约读写性过程约10%时间消耗,但是存储一定的数据损坏风险
  • stop-writes-on-bgsave-error yes

    • 说明:后台存储过程中如果出现错误现象,是否停止保存操作
    • 经验:通常默认为开启状态

bgsave指令工作原理

注: bgsave命令是针对save阻塞问题做的优化。Redis内部所有涉及到RDB操作都采用bgsave的方式

2、在指定的时间间隔内,执行指定次数的写操作(自动)

save 900 1
save 300 10
save 60 10000

save配置原理

在这里插入图片描述

注:

  • save配置要根据实际业务情况进行设置,频度过高或过低都会出现性能问题,结果可能是灾难性的
  • save配置中对于second与changes设置通常具有互补对应关系,尽量不要设置成包含性关系
  • save配置启动后执行的是bgsave操作
7.1.2、三种启动方式对比

在这里插入图片描述

7.1.3、RDB优缺点

优点:

  • RDB是一个紧凑压缩的二进制文件,存储效率较高
  • RDB内部存储的是redis在某个时间点的数据快照,非常适合用于数据备份,全量复制等场景
  • RDB恢复数据的速度要比AOF快很多
  • 应用:服务器中每X小时执行bgsave备份,并将RDB文件拷贝到远程机器中,用于灾难恢复

缺点:

  • RDB方式无论是执行指令还是利用配置,无法做到实时持久化,具有较大的可能性丢失数据
  • bgsave指令每次运行要执行fork操作创建子进程,要牺牲掉一些性能
  • Redis的众多版本中未进行RDB文件格式的版本统一,有可能出现各版本服务之间数据格式无法兼容现象

7.2、AOF(Append Only File)

RDB存储的弊端

  • 存储数据量较大,效率较低
    • 基于快照思想,每次读写都是全部数据,当数据量巨大时,效率非常低
  • 大数据量下的IO性能较低,写RDB文件比较耗时,耗费性能
  • 基于fork创建子进程,内存产生额外消耗
  • 宕机带来的数据丢失风险

解决方案

  • 不写全数据,仅记录部分数据
  • 降低区分数据是否改变的难度,改记录数据为记录操作过程
  • 对所有操作均进行记录,排除丢失数据的风险

将数据的操作过程进行保存,日志形式,存储操作过程,存储格式复杂,关注点在数据的操作过程

7.2.1、AOF概念

AOF全称为Append Only File (追加文件)。Redis处理的每一个写命令都会记录在AOF文件,可以看做是命令日志文件

AOF:Redis 默认不开启。它的出现是为了弥补RDB的不足(数据的不一致性),所以它采用日志的形式来记录每个写操作(读操作不记录),并追加到文件中。Redis 重启的会根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作

AOF的主要作用是解决了数据持久化的实时性,目前已经是Redis持久化的主流方式

7.2.2、AOF写数据三种策略(appendfsync)

打开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
  • always(每次)
    • 每次写入操作均同步到AOF文件中,数据零误差,性能较低,不建议使用
  • everysec(每秒)
    • 每秒将缓冲区中的指令同步到AOF文件中,数据准确性较高,性能较高,建议使用,也是默认配置,在系统突然宕机的情况下会丢失1秒内的数据
  • no(系统控制)
    • 由操作系统控制每次同步到AOF文件的周期,整体过程不可控

AOF 的优缺点

优点:数据的完整性和一致性更高,每秒同步一次,可能会丢失一秒的数据

缺点:因为AOF记录的内容多,文件会越来越大,数据恢复也会越来越慢

7.2.3、AOF重写

随着命令不断写入AOF,文件会越来越大,为了解决这个问题,Redis引入了AOF重写机制压缩文件体积。AOF文件重写是将Redis进程内的数据转化为写命令同步到新AOF文件的过程。简单说就是将对同一个数据的若干个条命令执行结果转化成最终结果数据对应的指令进行记录

AOF重写作用

  • 降低磁盘占用量,提高磁盘利用率
  • 提高持久化效率,降低持久化写时间,提高IO性能
  • 降低数据恢复用时,提高数据恢复效率

AOF重写方式

手动重写:bgrewriteaof

自动重写触发条件设置

# AOF文件比上次文件增长超过多少百分比则触发重写
auto-aof-rewrite-percentage percent
# AOF文件体积最小多大以上才触发重写
auto-aof-rewrite-min-size size

自动重写触发比对参数( 运行指令info Persistence获取具体信息 )

aof_current_size
aof_base_size

自动重写触发条件

在这里插入图片描述

7.2.4、RDB VS AOF

RDB和AOF各有自己的优缺点,如果对数据安全性要求较高,在实际开发中往往会结合两者来使用

在这里插入图片描述

7.2.5、最佳实践
  • 对数据非常敏感,建议使用默认的AOF持久化方案
    • AOF持久化策略使用everysecond,每秒钟fsync一次。该策略redis仍可以保持很好的处理性能,当出现问题时,最多丢失0-1秒内的数据
    • 注:由于AOF文件存储体积较大,且恢复速度较慢
  • 数据呈现阶段有效性,建议使用RDB持久化方案
    • 数据可以良好的做到阶段内无丢失(该阶段是开发者或运维人员手工维护的),且恢复速度较快,阶段点数据恢复通常采用RDB方案
    • 注:利用RDB实现紧凑的数据
  • 综合对比
    • RDB与AOF的选择实际上是在做一种权衡,每种都有利有弊
    • 如不能承受数分钟以内的数据丢失,对业务数据非常敏感,选用AOF
    • 如能承受数分钟以内的数据丢失,且追求大数据集的恢复速度,选用RDB
    • 灾难恢复选用RDB
    • 双保险策略,同时开启 RDB 和 AOF,重启后,Redis优先使用 AOF 来恢复数据,降低丢失数据的量

8、Redis发布订阅


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值上进行了消息发布后,所有订阅它的客户端都会收到相应的消息。这一功能最明显的用法就是用作实时消息系统,比如普通的即时聊天,群聊等功能

使用场景:

  • 实时消息系统
  • 实时聊天
  • 订阅、关注系统

9、主从复制


9.1、什么是主从复制

主从复制,是指将一台Redis服务器的数据,复制到其他的Redis服务器。前者称为主节点(master/leader),后者称为从节点(slave/follower);数据的复制是单向的,只能由主节点到从节点。Master以写为主,Slave 以读为主

默认情况下,每台Redis服务器都是主节点

并且一个主节点可以有多个从节点(或没有从节点),但每一个从节点只能有一个主节点

9.1.1、主要作用
  1. 读写分离:master写、slave读,提高服务器的读写负载能力
  2. 负载均衡:基于主从结构,配合读写分离,由slave分担master负载,并根据需求的变化,改变slav的数量,通过多个从节点分担数据读取负载,大大提高Redis服务器并发量与数据吞吐量
  3. 高可用基石:基于主从复制,构建哨兵模式与集群,实现Redis的高可用方案
  4. 数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式
  5. 故障恢复:当主节点出现问题时,可以由从节点提供服务,实现快速的故障恢复;实际上是一种服务的冗余

一般来说,要将Redis运用于工程项目中,只使用一台Redis是万万不能的(会造成宕机),原因如下:

  1. 从结构上,单个Redis服务器会发生单点故障,并且一台服务器需要处理所有的请求负载,压力较大
  2. 从容量上,单个Redis服务器内存容量有限,就算一台Redis服务器内存容量为256G,也不能将所有内存用作Redis存储内存,一般来说,单台Redis最大使用不应超过20G

电商网站中的商品,一般都是一次上传,无数次浏览,说专业点也就是多读少写

在这里插入图片描述

主从复制,读写分离!80%的情况下都是在进行读操作,减缓服务器的压力,架构中常常使用,一主二从

只要在公司中,主从复制就是必须要使用的,因为在真实的项目中不可能单机使用Redis!

主从复制过程大体可以分为3个阶段:

  • 建立连接阶段(即准备阶段)
  • 数据同步阶段
  • 命令传播阶段
    在这里插入图片描述

9.2、环境配置(单机集群)

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服务,可通过进程信息查看

9.3、一主二从(单机测试)

默认情况下,每台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!

在实际工作中,我们都是通过配置文件中修改指定配置的!这样的话是永久的

主机可以写,从机不能写只能读,主机写,从机读,主机中的所有信息和数据,都会自动从机保存

主机写:

从机只能读取内容!

主从连接三种方式

  • 方式一:客户端发送命令
    • slaveof ip port
  • 方式二:启动服务器参数
    • redis-server -slaveof ip port
  • 方式三:服务器配置
    • slaveof ip port
  • 主从断开连接(从节点执行)
    • slaveof no one

注:slave断开连接后,不会删除已有数据,只是不再接受master发送的数据

授权访问

  • master客户端发送命令设置密码:requirepass password
  • master配置文件设置密码:config set requirepass password、config get requirepass
  • slave客户端发送命令设置密码:auth password
  • slave配置文件设置密码:masterauth password
  • slave启动服务器设置密码:redis-server –a password

9.4、主从复制工作原理

在这里插入图片描述

9.5、哨兵模式

自动选举Leader的模式

主从切换技术的方法是:当主服务器宕机后,需要手动把一台从服务器切换为主服务器,这就需要人工干预,费事费力,还会造成一段时间内服务不可用。这不是一种推荐的方式,更多时候,我们优先考虑 哨兵模式 。Redis从2.8开始正式提供了Sentinel(哨兵) 架构来解决这个问题

谋朝篡位的自动版,能够后台监控主机是否故障,如果故障了根据投票数自动将从库转换为主库

哨兵模式是一种特殊的模式,首先Redis提供了哨兵的命令,哨兵是一个独立的进程 ,作为进程,它会独立运行。其原理是哨兵通过发送命令,等待Redis服务响应,从而监控运行的多个Redis实例

这里的哨兵有两个作用

  • 通过发送命令,让Redis服务器返回监控其运行状态,包括主服务器和从服务器
  • 当哨兵监测到master宕机,会自动将slave切换成master,然后通过发布订阅模式通知其他的从服务器,修改配置文件,让它们切换主机

然而一个哨兵进程对Redis服务器进行监控,可能会出现问题,为此,我们可以使用多个哨兵进行监控。各个哨兵之间还会进行监控,这样就形成了多哨兵模式

目前状态是一主二从

  1. 配置哨兵配置文件

    # sentinel monitor 被监控的的名称 host port 1
    sentinel monitor myredis 127.0.0.1 6379 1
    

    后面数字1,代表主机挂了,salve投票看让谁接替成为主机,票数多的就会成为主机

哨兵配置最佳实践

  • 3个sentinel配置为2
  • 5个sentinel配置为3
  • 7个sentinel配置为4
  1. 启动哨兵

    redis-sentinel redisConfig/sentinel.conf 
    

    如果Master主节点断开了,这时候就会从从机中随机选择一个服务器,如果主机此时回来,它只能归并到新的主机下,当做从机(slave),这就是哨兵模式的规则

哨兵工作原理

  • 监控
    • 同步信息
  • 通知
    • 双方保持畅通
  • 故障转移
    • 发现问题
    • 竞选负责人
      • 挂的ip
      • 挂的端口
      • 竞选次数
      • 自己的runid
    • 选举新master
      • 在线的
      • 响应慢的
      • 与原master断开时间久的
      • 优先原则
    • 新master上任,其他slave去连接新的master,原master会作为slave故障恢复后重连

10、Redis集群


问题:业务发展过程中遇到的峰值瓶颈

  • redis提供的服务OPS可以达到10万/秒,当前业务OPS已经达到10万/秒
  • 内存单机容量达到256G,当前业务需求内存容量1T

使用集群的方式可以快速解决上述问题

10.1、什么是集群?

集群就是使用网络将若干台计算机联通起来,并提供统一的管理方式,使其对外呈现单机的服务效果
在这里插入图片描述

集群作用

  • 分散单台服务器的访问压力,实现负载均衡
  • 分散单台服务器的存储压力,实现可扩展性
  • 降低单台服务器宕机带来的业务灾难

10.2、集群结构设计

数据存储设计

  • 通过算法设计,计算出key应该保存的位置
  • 将所有的存储空间计划切割成16384份,每台主机保存一部分
    • 每份代表的是一个存储空间,不是一个key的保存空间
  • 将key按照计算出的结果放到对应的存储空间

在这里插入图片描述

集群内部通讯设计

  • 各个数据库相互通信,保存各个库中槽的编号数据
  • 一次命中,直接返回
  • 一次未命中,告知具体位置

10.3、cluster集群搭建

10.3.1、搭建方式
  • 原生安装(单条命令)
    • 配置服务器(3主3从)
    • 建立通信(Meet 自动)
    • 分槽(Slot 自动)
    • 搭建主从集群(master-slave)
  • 工具安装(批处理)
10.3.2、Cluster相关配置
# 开启集群配置 添加节点 (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麻烦的多!

11、缓存穿透和雪崩


11.1、缓存穿透(查不到)

概念

缓存穿透就是用户想要查询一个数据,发现redis内存没有数据库也没有,也就是缓存没有命中,于是向持久层数据库查询。发现也没有,于是本次查询失败。当用户很多的时候,缓存都没有命中,于是都去请求了持久层数据库。这会给持久层数据库造成很大的压力,这时候就相当于出现了缓存穿透

解决方案

布隆过滤器

布隆过滤器是一种数据结构,对所有可能查询的参数以hash形式存储,在控制层先进行校验,不符合则丢弃,从而避免了对底层存储系统的查询压力

缓存空对象

当存储层不命中后,即使返回的空对象也将其缓存起来,同时会设置一个过期时间 ,之后再访问这个数据将会从缓存中获取,保护了后端的数据源

此种方法存在的问题:

  • 如果空值能够被缓存起来,这就意味着缓存需要更多的空间存储更多的键,因为这当中可能会有很多
    的空值的键
  • 即使对空值设置了过期时间,还是会存在缓存层和存储层的数据会有一段时间窗口的不一致,这对于需要保持一致性的业务会有影响

11.2、缓存击穿(查询量太大)

概念

是指一个非常热点的key,在不停的扛着大并发,当这个key失效时,一瞬间大量的请求冲到持久层的数据库中,就像在一堵墙上某个点凿开了一个洞!

解决方案

设置热点key永不过期

从缓存层面来看,没有设置过期时间,所以不会出现热点key过期后产生的问题

加互斥锁

在查询持久层数据库时,保证了只有一个线程能够进行持久层数据查询,其他的线程让它睡眠几百毫秒,等待第一个线程查询完会回写到Redis缓存当中,剩下的线程可以正常查询Redis缓存,就不存在大量请求去冲击持久层数据库了!

11.3、缓存雪崩

概念

在某一个时间段,缓存的key大量集中同时过期了,所有的请求全部冲到持久层数据库上,导致持久层数据库挂掉!
范例:双十一零点抢购,这波商品比较集中的放在缓存,设置了失效时间为1个小时,那么到了凌晨一点,这批缓存全部失效了,而大量的请求过来时,全部冲过了缓存,冲到了持久层数据库!

解决方案

Redis高可用

搭建Redis集群,既然redis有可能挂掉,那我多增设几台redis,这样一台挂掉之后其他的还可以继续工作,其实就是搭建的集群。(异地多活!)

限流降级

在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待

数据预热

数据加热的含义就是在正式部署之前,先把可能的数据先预先访问一遍,这样部分可能大量访问的数据就会加载到缓存中。在即将发生大并发访问前手动触发加载缓存不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值