IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 大数据 -> Redis -> 正文阅读

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

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应用,其实并不需要强一致性。

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

列存储数据库

  • 分布式文件系统
  • 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

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

特性:

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

官网

中文网

window在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
    
    make install
    

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

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

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

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

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

  9. 查看redis进程

  10. 关闭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整合


SpringBoot操作数据:spring-data jpa jdbc mongodb redis

SpringData也是和SpringBoot齐名的项目

说明:在SpringBoot2.x之后,原来使用的jedis被替换成了lettuce

源码分析:

@Bean
@ConditionalOnMissingBean(name = {"redisTemplate"})	//可以自己定义RedisTemplate来替换这个默认的!
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
    // 默认的RedistTemplate 没有过多的设置,redis对象都是需要序列化的
    // 两个泛型都是Object, Object 的类型,使用需要强制转换<String,Object> 
    RedisTemplate<Object, Object> template = new RedisTemplate();
    template.setConnectionFactory(redisConnectionFactory);
    return template;
}

@Bean
@ConditionalOnMissingBean	//	 由于String是Redis最经常使用的类型,所以说单独提出来了一个Bean
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
    return new StringRedisTemplate(redisConnectionFactory);
}
  1. 导入依赖

    <!--操作redis-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    
  2. 配置连接

    # 配置redis
    spring:
      redis:
        host: 127.0.0.1
        port: 6379
    
  3. 测试

    @SpringBootTest
    class RedisApplicationTests {
        @Autowired
        private RedisTemplate redisTemplate;
    
        @Test
        void contextLoads() {
            // redisTemplate 操作不同的数据类型
            // opsForValue 操作字符串的
            // opsForList   操作List
            // ......
            
            // 获取redis的连接对象
            // RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
            //connection.flushDb();
            //connection.flushAll();
    
            redisTemplate.opsForValue().set("name", "晓琳");
            System.out.println(redisTemplate.opsForValue().get("name"));
        }
    }
    
  4. 默认采用JDK的序列化方式,可能会使用JSON来序列化

  5. 编写自己的RedisTemplate

    @Configuration
    public class RedisConfig {
        // 编写自己的配置类
        @Bean
        @SuppressWarnings("all")
        public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
            // 一般直接使用<String, Object>
            RedisTemplate<String, Object> template = new RedisTemplate<>();
            template.setConnectionFactory(factory);
    
            // Json序列化配置
            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);
    
            // String的序列化
            StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
            // key采用String的序列化方式
            template.setKeySerializer(stringRedisSerializer);
            // hash也采用String的序列化方式
            template.setHashKeySerializer(stringRedisSerializer);
            // value采用jackson序列化方式
            template.setValueSerializer(jackson2JsonRedisSerializer);
            // hash的value采用jackson序列化方式
            template.setHashValueSerializer(jackson2JsonRedisSerializer);
            template.afterPropertiesSet();
    
            return template;
        }
    }
    @Configuration
    public class RedisConfig {
        // 编写自己的配置类
        @Bean
        @SuppressWarnings("all")
        public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
            // 一般直接使用<String, Object>
            RedisTemplate<String, Object> template = new RedisTemplate<>();
            template.setConnectionFactory(factory);
    
            // Json序列化配置
            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);
    
            // String的序列化
            StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
            // key采用String的序列化方式
            template.setKeySerializer(stringRedisSerializer);
            // hash也采用String的序列化方式
            template.setHashKeySerializer(stringRedisSerializer);
            // value采用jackson序列化方式
            template.setValueSerializer(jackson2JsonRedisSerializer);
            // hash的value采用jackson序列化方式
            template.setHashValueSerializer(jackson2JsonRedisSerializer);
            template.afterPropertiesSet();
    
            return template;
        }
    }
    

7、Redis持久化


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

7.1、RDB(Redis DataBase)

什么是RDB?

原理是redis会单独创建(fork) 一个与当前进程一模一样的子进程进行持久化,这个子进程的所有数据(变量。环境变量,程序程序数器等)都和原进程一模一样,会先将数据写入到一个临时文件中待持久化结束了,再用这个临时文件替换上次持久化好的文件,整过程中,主进程不进行任何的io操作,这就确保了极高的性能

持久化文件在哪里

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

触发机制

  1. 在指定的时间间隔内,执行指定次数的写操作
  2. 执行save(阻塞, 只管保存快照,其他的等待) 或者是bgsave (异步)命令
  3. 执行flushall命令,清空数据库所有数据,意义并不大
  4. 执行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 always
appendfsync everysec
# appendfsync no

always:同步持久化,每次发生数据变化会立刻写入到磁盘中。性能较差当数据完整性比较好(慢,安全)

everysec:出厂默认推荐,每秒异步记录一次(默认值)

no:不同步

如果aof文件有错误,这时候redis是启动不起来的,需要修复这个文件

redis提供了一个工具:redis-check-aof --fix appendonly.aof 进行修复

如果文件正常,重启即可恢复

AOF 的优缺点

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

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

总结

  1. Redis 默认开启RDB持久化方式,在指定的时间间隔内,执行指定次数的写操作,则将内存中的数据写入到磁盘中。
  2. RDB 持久化适合大规模的数据恢复但它的数据一致性和完整性较差。
  3. Redis 需要手动开启AOF持久化方式,默认是每秒将写操作日志追加到AOF文件中。
  4. AOF 的数据完整性比RDB高,但记录内容多了,会影响数据恢复的效率。
  5. Redis 针对 AOF文件大的问题,提供重写的瘦身机制。
  6. 若只打算用Redis 做缓存,可以关闭持久化。
  7. 若打算使用Redis 的持久化。建议RDB和AOF都开启。其实RDB更适合做数据的备份,留一后手。AOF出问题了,还有RDB

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服务器都是主节点

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

主要作用:

  1. 数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式
  2. 故障恢复:当主节点出现问题时,可以由从节点提供服务,实现快速的故障恢复;实际上是一种服务的冗余
  3. 负载均衡:在主从复制的基础上,配合读写分离,可以由主节点提供写服务,由从节点提供读服务(即写Redis数据时应用连接主节点,读Redis数据时应用连接从节点),分担服务器负载;尤其是在写少读多的场景下,通过多个从节点分担读负载,可以大大提高Redis服务器的并发量
  4. 高可用(集群):除了上述作用以外,主从复制还是哨兵和集群能够实施的基础,因此说主从复制是Redis高可用的基础

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

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

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

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

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

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端口
  • 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
# 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

如果两个服务都配置好了,就有两个从机了

注:这种**通过命令的配置是一次性的,如果机器宕机、断电等,就需要重新认大哥!
在实际工作中,我们都是
通过配置文件中修改指定配置的!**这样的话是永久的

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

主机写:

从机只能读取内容!

证明,虽然主机断开了,但是从机还是可以正常读取原先就有的数据的!

如果是使用命令行,来配置的主从,这个时候如果重启了,就会变回主机!只要变为从机,立马就会从主机中获取到值!

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服务器进行监控,可能会出现问题,为此,我们可以使用多个哨兵进行监控。各个哨兵之间还会进行监控,这样就形成了多哨兵模式

目前状态是一主二从

  1. 配置哨兵配置文件

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

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

  2. 启动哨兵

    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,设置不同的过期时间,让缓存失效的时间点尽量均匀

  大数据 最新文章
实现Kafka至少消费一次
亚马逊云科技:还在苦于ETL?Zero ETL的时代
初探MapReduce
【SpringBoot框架篇】32.基于注解+redis实现
Elasticsearch:如何减少 Elasticsearch 集
Go redis操作
Redis面试题
专题五 Redis高并发场景
基于GBase8s和Calcite的多数据源查询
Redis——底层数据结构原理
上一篇文章      下一篇文章      查看所有文章
加:2021-12-11 15:47:32  更:2021-12-11 15:50:00 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 12:04:05-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码