Redis学习笔记

NoSQL概述

为什么要用NoSQL

用户的个人消息,社交网络,地理位置,用户自己产生的数据,用户日志等等

什么NoSQL

NoSQL=not only sql

泛指非关系型数据库,随着web2.0互联网的诞生,传统的关系型数据库很难对付,尤其是超大规模的高并发的社区,暴露很多难以克服的问题,NoSQL发展迅速

很多数据类型用户的个人消息,社交网络,地理位置,用户自己产生的数据,用户日志等等不需要一个固定的格式,不需要多余的操作就可以横向扩展。

NoSQL特点

1、方便扩展(数据之间没有关系方便扩展)

2、大数据量 高性能(redis 写8万/s 读11万/s,NoSQL的缓存记录级,是一种颗粒度的缓存,性能会比较高)

3、数据类型多样性(不需要事先设计数据库!随去随用,如果数据量十分大,很多人无法设计)

4、传统的RDBMS与NoSQL

传统的RDBMS
-结构化组织
-SQL
-数据和关系都存在单独的表里
-操作,数据定义语言
-严格的一致性
-基础的事务
...
NoSQL
-不仅仅是数据
-没有固定的查询语言
-键值对存储,列存储,文档存储,图形数据库(社交关系)
-最终一致性
-CAP定理和Base(异地多活)
-高性能、高可用、高扩展性能
-...

3v+3高

大数据时代3V,主要描述问题

  1. 海量Volume
  2. 多样Variety
  3. 实时Velocity

大数据时代3高,主要对程序要求

  1. 高并发
  2. 高可扩
  3. 高性能

阿里巴巴演进分析

image-20201211115042818

image-20201211170147253

#1、商品的基本信息
	名称、价格、商家信息...
	关系型数据库就可以解决!Mysql/Oracle(淘宝早年就去IOE了-王坚)
	淘宝内部的MySQL 不是大家用的MySQL
#2、商品的描述评论(文字比较多)
	文档型数据库中,mongodb
#3、图片
	分布式文件系统FastDFS
	--淘宝自己的  TFS
	--Google    GFS
	--Hadoop    HDFS
	--阿里云	  OSS
#4、商品的关键字(搜索)
	--搜索引擎 solr ElasticSearch
	--ISearch:多隆
#5、人们的波段信息
	--内存数据库
	--Redis、tair、memache...
#6、商品的交易,外部支付接口
	--三方应用	

image-20201211111003583

image-20201214154539986

NoSQL四大分类

kv键值对

新浪 Redis

美团 Redis+Tair

阿里百度 Redis+Memecache

文档型数据库

(bson与json一样)

MongoDB

​ 基于分布式文件存储的数据库,c++编写,主要来处理大量的文档

​ 介于关系型和非关系型数据中间产品,MongoDB是非关系型数据库中功能最丰富,最像关系型数据库的

ConthDB

列存储数据库

HBase

分布式文件系统

图关系数据库

存储的不是图而是关系,比如朋友圈社交网络、广告推荐

Neo4J,InfoGrid

image-20201211171907749

Redis入门

概述

Redis是什么

​ Remote Dictionary Server 远程字典服务

​ 免费和开源 结构化数据库

​ Key-Value数据库

Redis能干什么

​ 内存存储、持久化,内存中是断电即失的所以持久化很重要(rdb aof)

​ 效率高,可用于高速缓存

​ 发布订阅系统

​ 地图信息分析

​ 计时器、计数器(浏览量!)

​ …

特性

​ 多样的数据类型

​ 持久化

​ 集群

​ 事务

学习中用到的

Redis安装(Linux)

1、官网下载

2、解压

3、进入解压后文件

4、基本的环境安装:

注意:网络无法下载时候将网络配置暂时改为下图

img

yum install gcc-c++
#来到redis的解压目录中
make
#如果make报错 执行
	make distclean
	#若依旧报错
    #而redis6.0+需要的gcc版本为5.3及以上,所以升级gcc即可
    #升级gcc到9以上
    yum -y install centos-release-scl
    yum -y install devtoolset-9-gcc devtoolset-9-gcc-c++ devtoolset-9-binutils
    #临时将此时的gcc版本改为9
    scl enable devtoolset-9 bash
    #或永久改变
    echo "source /opt/rh/devtoolset-9/enable" >>/etc/profile
    #查看gcc版本
    gcc -v
make install

5、redis默认安装目录

/usr/local/bin

6、将redis配置文件复制到我们当前的目录

创建目录config,拷贝安装文件目录下的redis.conf到实际安装文件的config目录下

image-20201214153527606

7.默认不是后台启动

更改配置文件:daemonize 改为 yes

8、启动Redis服务

执行:redis-server config/redis.conf

9、测试连接 redis-cli -p 6379

image-20201211170308524

10、查看redis的进程是否开启

image-20201214155017615

11、关闭redis服务 shutdown exit

image-20201214154959443

12、再次查看进程是否关闭

image-20201214161410500

13、后边使用单机多redis启动集群测试

测试性能

redis-benchmark 是一个压力测试工具

官方自带的性能测试工具

redis-benchmark 命令参数

# 测试100个并发连接 100000个请求
redis-benchmark -h localhost -p 6379 -c 100 -n 100000

如何查看这些分析

image-20201214154753606

基础知识

默认16个数据库

默认使用第0个

可以使用select切换数据库

DBSIZE 数据库大小

keys * 查看所有的key

flushall 清空全部 flushdb 清空当前数据库

Redis是单线程的

Redis基于内存操作的,CPU不是Redis性能瓶颈,Redis的瓶颈是根据机器的内存和网络带宽,既然可以使用单线程来实现,就使用但线程了 , 所有就使用单线程。

Redis是c语言写的,官方数据100000+QPS,完全不比key-value的memecache差

Redis为什么单线程还这么快?

1、误区1:高性能的服务器一定是多线程的

2、误区2:多线程(CPU上下文切换)一定比单线程效率高!

CPU>内存>硬盘的速度

核心:Redis将所有数据放到内存,所以说使用单线程去操作效率最高,多线程(CPU上下文切换:耗时的操作),对于内存系统来说,如果没有上下文切换效率就是最高的。多次读写都是再一个CPU上的, 在内存情况下,这个就是最佳方案。

五大数据类型

数据库、缓存、消息中间件MQ

Redis-Key

Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。 它支持多种类型的数据结构,如 字符串(strings)散列(hashes)列表(lists)集合(sets)有序集合(sorted sets) 与范围查询, bitmapshyperloglogs地理空间(geospatial) 索引半径查询。 Redis 内置了 复制(replication)LUA脚本(Lua scripting)LRU驱动事件(LRU eviction)事务(transactions) 和不同级别的 磁盘持久化(persistence), 并通过 Redis哨兵(Sentinel)和自动 分区(Cluster)提供高可用性(high availability)。

http://redis.cn的命令 查看所有的命令

String(字符串)

#####################################################
127.0.0.1:6379> set key1 v1  # 设置值
OK
127.0.0.1:6379> get key1# 获得值
"v1"
127.0.0.1:6379> exists key1 # 判断是否存在key
(integer) 1
127.0.0.1:6379> append key1 hello #追加字符串,如果当前key不存在则创建相当于set key,存在则追加
(integer) 7
127.0.0.1:6379> get key1
"v1hello"
127.0.0.1:6379> strlen key1 # 获取字符串的长度
(integer) 7
127.0.0.1:6379> 
127.0.0.1:6379> append key1 ,Hing
(integer) 12
127.0.0.1:6379> strlen key1
(integer) 12
127.0.0.1:6379> get key1
"v1hello,Hing"
#####################################################
i++
i+=n 步长
127.0.0.1:6379> set views 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> 
127.0.0.1:6379> decr views #自减1
(integer) 1
127.0.0.1:6379> decr views
(integer) 0
127.0.0.1:6379> get views
"0"
127.0.0.1:6379> incrby views 10 # 可以设置步长 指定增减
(integer) 10
127.0.0.1:6379> get views
"10"
127.0.0.1:6379> decrby views 5
(integer) 5
127.0.0.1:6379> get views
"5"
127.0.0.1:6379> 
#####################################################
# 字符串范围range 
127.0.0.1:6379> set key1 "hello,world" #设置key1的值
OK
127.0.0.1:6379> get key1
"hello,world"
127.0.0.1:6379> 
127.0.0.1:6379> getrange key1 0 4 #截取字符串【0,4】
"hello"
127.0.0.1:6379> getrange key1 0 -1#获取全部字符串 和get key是一样的
"hello,world"

#替换
127.0.0.1:6379> set key2 abcdef
OK
127.0.0.1:6379> get key2
"abcdef"
127.0.0.1:6379> setrange key2 1 xx #替换指定位置的字符串
(integer) 6
127.0.0.1:6379> get key2
"axxdef"
#####################################################
# setex (set with expire)  #设置过期时间
# setnx (set if not exist) #不存在则设置,在分布式锁中常常使用
127.0.0.1:6379> setex key3 30 hello #设置key3值hello 30秒过期
OK
127.0.0.1:6379> ttl key3
(integer) 23
127.0.0.1:6379> keys *
1) "key3"
127.0.0.1:6379> setnx mykey hing # 如果不存在则创建mykey
(integer) 1
127.0.0.1:6379> keys *
1) "mykey"
127.0.0.1:6379> setnx mykey redis #如果存在创建失败
(integer) 0
127.0.0.1:6379> 
127.0.0.1:6379> keys *
1) "mykey"
127.0.0.1:6379> get mykey
"hing"
#####################################################
mset
mget
127.0.0.1:6379> mset k1 v1 k2 v2 k3 v3 #同时设置多个值
OK
127.0.0.1:6379> keys *
1) "k1"
2) "k3"
3) "k2"
127.0.0.1:6379> 
127.0.0.1:6379> mget k1 k2 k3 # 同时获取多个值
1) "v1"
2) "v2"
3) "v3"
127.0.0.1:6379> msetnx k1 v0 k4 v4 #msetnx是一个原子性的操作,要么一起成功要么一起失败
(integer) 0
127.0.0.1:6379> keys *
1) "k1"
2) "k3"
3) "k2"
127.0.0.1:6379> get v4
(nil)

#对象
set user:1{name:zhangsan,age:3}#设置一个user1:对象值为json字符来保存一个对象

# 这里的key是一个巧妙的设计:user:{id}:{filed} ,如此设计在redis中完全ok

127.0.0.1:6379> mset user:1:name zhangsan user:1:age 3
OK
127.0.0.1:6379> keys *
1) "user:1:age"
2) "user:1:name"
127.0.0.1:6379> mget user:1:name user:1:age 
1) "zhangsan"
2) "3"
127.0.0.1:6379> 
#####################################################
getset # 先get再set
127.0.0.1:6379> getset db redis # 如果不存在则返回nil,设置值
(nil)
127.0.0.1:6379> get db
"redis"
127.0.0.1:6379> getset db mongodb# 如果存在,则返回原来的值,并设置新的值
"redis"
127.0.0.1:6379> get db
"mongodb"
#####################################################

数据结构是相同的,

String 类型的应用场景:value除了可以是字符串还可以是数字

​ 计数器

​ 统计多单位数量

​ 粉丝数

​ 对象缓存存储

List(列表)

基本的数据类型。

在Redis里边可以用做:栈、队列、阻塞队列,所有的list都是l开头的

#####################################################
127.0.0.1:6379> lpush list one  #将一个或者多个值插入到列表的头部
(integer) 1
127.0.0.1:6379> lpush list two
(integer) 2
127.0.0.1:6379> lpush list three
(integer) 3
127.0.0.1:6379> lrange list 0 -1 #获取list中的值
1) "three"
2) "two"
3) "one"
127.0.0.1:6379> lrange list 0 1 #获取区间范围值
1) "three"
2) "two"
127.0.0.1:6379> rpush list zero #将一个或者多个值插入到列表的尾部
(integer) 4
127.0.0.1:6379> lrange list 0 -1
1) "three"
2) "two"
3) "one"
4) "zero"
#####################################################
lpop #移除左边
rpop #移除右边
127.0.0.1:6379> lrange list 0 -1
1) "three"
2) "two"
3) "one"
4) "zero"
127.0.0.1:6379> lpop list #移除list的头部元素
"three"
127.0.0.1:6379> lrange list 0 -1
1) "two"
2) "one"
3) "zero"
127.0.0.1:6379> rpop list #移除list的尾部元素
"zero"
127.0.0.1:6379> 
127.0.0.1:6379> lrange list 0 -1
1) "two"
2) "one"
127.0.0.1:6379> 
#####################################################
lindex
127.0.0.1:6379> lrange list 0 -1
1) "two"
2) "one"
127.0.0.1:6379> lindex list 0 #通过下标获取list第0个元素
"two"
127.0.0.1:6379> lindex list 1 #获取第一个元素
"one"
#####################################################
llen
127.0.0.1:6379> lpush list one
(integer) 1
127.0.0.1:6379> lpush list two
(integer) 2
127.0.0.1:6379> lpush list three
(integer) 3
127.0.0.1:6379> lrange list 0 -1
1) "three"
2) "two"
3) "one"
127.0.0.1:6379> llen list #获取列表的长度
(integer) 3
#####################################################
移除指定的值(取关)
lrem

127.0.0.1:6379> lrange list 0 -1
1) "three"
2) "two"
3) "one"
127.0.0.1:6379> lpush list three
(integer) 4
127.0.0.1:6379> lrange list 0 -1
1) "three"
2) "three"
3) "two"
4) "one"
127.0.0.1:6379> lrem list 1 one # 移除list集合指定个数的value,精确匹配
(integer) 1
127.0.0.1:6379> lrange list 0 -1
1) "three"
2) "three"
3) "two"
127.0.0.1:6379> lrem list 1 three
(integer) 1
127.0.0.1:6379> lrange list 0 -1
1) "three"
2) "two"
127.0.0.1:6379> lpush list three
(integer) 3
127.0.0.1:6379> lrange list 0 -1
1) "three"
2) "three"
3) "two"
127.0.0.1:6379> lrem list 2 three
(integer) 2
127.0.0.1:6379> lrange list 0 -1
1) "two"
127.0.0.1:6379> 
#####################################################
trim 修剪 list 截断
127.0.0.1:6379> lpush mylist hello
(integer) 1
127.0.0.1:6379> lpush mylist hello1
(integer) 2
127.0.0.1:6379> lpush mylist hello2
(integer) 3
127.0.0.1:6379> lpush mylist hello3
(integer) 4
127.0.0.1:6379> lpush mylist hello4
(integer) 5
127.0.0.1:6379> keys *
1) "mylist"
127.0.0.1:6379> lrange mylist 0 -1
1) "hello4"
2) "hello3"
3) "hello2"
4) "hello1"
5) "hello"
127.0.0.1:6379> ltrim mylist 1 2 #通过下标截取指定的list元素,list已经改变,只剩下截取的元素
OK
127.0.0.1:6379> lrange mylist 0 -1
1) "hello3"
2) "hello2"
#####################################################
rpoplpush # 移除列表的最后一个元素,并移动到新的列表中
127.0.0.1:6379> lpush mylist hello
(integer) 1
127.0.0.1:6379> lpush mylist hello1
(integer) 2
127.0.0.1:6379> lpush mylist hello12
(integer) 3
127.0.0.1:6379> lrange mylist 0 -1
1) "hello12"
2) "hello1"
3) "hello"
127.0.0.1:6379> rpoplpush mylist myotherlist #移除列表的底部元素,将其移到新的列表中
"hello"
127.0.0.1:6379> lrange mylist 0 -1 #查看原来的列表
1) "hello12"
2) "hello1"
127.0.0.1:6379> lrange myotherlist 0 -1 # 查看目标列表
1) "hello"
127.0.0.1:6379> 
#####################################################
lset#将列表的指定下标的值替换为另外一个值,更新操作

127.0.0.1:6379> exists list #判断是否存在列表
(integer) 0
127.0.0.1:6379> lset list 0 vlaue # 不存在则设置更新就会报错
(error) ERR no such key
127.0.0.1:6379> lpush list item
(integer) 1
127.0.0.1:6379> lrange list 0 -1
1) "item"
127.0.0.1:6379> lset list 0 value # 存在则更新当前下标的值
OK
127.0.0.1:6379> lrange list 0 -1
1) "value"
127.0.0.1:6379> lset list 1 value # 不存在当前下标的值,则更新会报错
(error) ERR index out of range
127.0.0.1:6379> 
#####################################################
linsert #将某个具体的value插入到列表中某个元素的前面或者后边

127.0.0.1:6379> lpush list hello
(integer) 1
127.0.0.1:6379> lpush list world
(integer) 2
127.0.0.1:6379> lrange list 0 -1
1) "world"
2) "hello"
127.0.0.1:6379> linsert list before world hing
(integer) 3
127.0.0.1:6379> lrange list 0 -1
1) "hing"
2) "world"
3) "hello"
127.0.0.1:6379> linsert list after world ,
(integer) 4
127.0.0.1:6379> lrange list 0 -1
1) "hing"
2) "world"
3) ","
4) "hello"

小结

实际上是一个链表,before Node after,left,right都可以插入值

如果kye不存在则创建新的链表

如果key存在新增元素

如果移除所有值,空链表,也代表不存在

在两边插入或者改动值,效率最高,中间元素相对来说效率会低一点!

消息队列!(Lpush Rpop)栈(Lpush Lpop)

Set(集合)

set是不能重复的

#####################################################
127.0.0.1:6379> clear
127.0.0.1:6379> sadd myset hello # set集合添加元素
(integer) 1
127.0.0.1:6379> sadd myset hing
(integer) 1
127.0.0.1:6379> sadd myset zhang
(integer) 1
127.0.0.1:6379> smembers myset #查看指定set的所有值
1) "hello"
2) "hing"
3) "zhang"
127.0.0.1:6379> sismember myset hello #判断某个值是否在set集合中
(integer) 1
127.0.0.1:6379> sismember myset world
(integer) 0
#####################################################
127.0.0.1:6379> scard myset #获取set集合元素的个数
(integer) 3
127.0.0.1:6379> sadd myset good
(integer) 1
127.0.0.1:6379> scard myset
(integer) 4
#####################################################
127.0.0.1:6379> srem myset good 移除set中的指定元素
(integer) 1
127.0.0.1:6379> scard myset
(integer) 3
127.0.0.1:6379> smembers myset
1) "hello"
2) "hing"
3) "zhang"
127.0.0.1:6379> 
#####################################################
set 无序不重合集合。抽随机
127.0.0.1:6379> SMEMBERS myset
1) "hello"
2) "hing"
3) "zhang"
127.0.0.1:6379> SRANDMEMBER myset  #随机抽选一个元素
"hello"
127.0.0.1:6379> SRANDMEMBER myset 
"hello"
127.0.0.1:6379> SRANDMEMBER myset 
"hing"
127.0.0.1:6379> SRANDMEMBER myset 
"zhang"
127.0.0.1:6379> SRANDMEMBER myset 2 #随机抽选指定个数的元素
1) "hello"
2) "zhang"
127.0.0.1:6379> SRANDMEMBER myset 2
1) "hello"
2) "zhang"
127.0.0.1:6379> SRANDMEMBER myset 2
1) "hing"
2) "zhang"
#####################################################
删除指定的key,随机删除key
127.0.0.1:6379> SMEMBERS myset
1) "hello"
2) "hing"
3) "zhang"
127.0.0.1:6379> SPOP myset #随机删除myset中的集合
"hing"
127.0.0.1:6379> SPOP myset
"hello"
127.0.0.1:6379> SMEMBERS myset
1) "zhang"
127.0.0.1:6379>
#####################################################
将一个指定的值移动到另外的set集合中
127.0.0.1:6379> sadd myset hello
(integer) 1
127.0.0.1:6379> sadd myset worls
(integer) 1
127.0.0.1:6379> sadd myset hing
(integer) 1
127.0.0.1:6379> sadd myset2 set2
(integer) 1
127.0.0.1:6379> smove myset myset2 hing #将一个指定的值移动到另外的一个set集合中
(integer) 1
127.0.0.1:6379> SMEMBERS myset
1) "hello"
2) "worls"
127.0.0.1:6379> SMEMBERS myset2
1) "hing"
2) "set2"
#####################################################
微博 B站 共同关注(并集)
数字集合类:
	-差集 SDIFF
	-交集 SINTER
	-并集 SUNION
127.0.0.1:6379> sadd k1 a
(integer) 1
127.0.0.1:6379> sadd k1 b
(integer) 1
127.0.0.1:6379> sadd k1 c
(integer) 1
127.0.0.1:6379> sadd k2 c
(integer) 1
127.0.0.1:6379> sadd k2 d
(integer) 1
127.0.0.1:6379> sadd k2 e
(integer) 1
127.0.0.1:6379> SDIFF k1 k2 #差集
1) "a"
2) "b"
127.0.0.1:6379> SINTER k1 k2 #交集 共同好友
1) "c"
127.0.0.1:6379> SUNION k1 k2 #并集
1) "a"
2) "b"
3) "c"
4) "e"
5) "d"
127.0.0.1:6379> 

微博,A用户的所有关注的人放到一个set集合中,将他的粉丝也放到一个集合中!

共同关注,共同爱好,二度好友(六度分割理论)

Hash(哈希)

map集合,key-map,这个值是一个map集合,本质和string类型没有太大区别,还是简单的key-value

#####################################################
127.0.0.1:6379> hset myhash k1 v1 # set 一个具体的key-value
(integer) 1
127.0.0.1:6379> hset myhash k2 v2
(integer) 1
127.0.0.1:6379> HGET myhash k1 #获取一个字段的值
"v1"
127.0.0.1:6379> HMSET myhash k3 v3 k4 v4 #设置多个字段值
OK
127.0.0.1:6379> hmget myhash k1 k2 #获取多个字段值
1) "v1"
2) "v2"
127.0.0.1:6379> HGETALL myhash #获取全部数据
1) "k1"
2) "v1"
3) "k2"
4) "v2"
5) "k3"
6) "v3"
7) "k4"
8) "v4"
127.0.0.1:6379> hdel myhash k4 #删除hash指定的key字段,对应的value值也就消失
(integer) 1
127.0.0.1:6379> HGETALL myhash
1) "k1"
2) "v1"
3) "k2"
4) "v2"
5) "k3"
6) "v3"
127.0.0.1:6379> 
#####################################################
127.0.0.1:6379> HLEN myhash #获取hash表的字段数
(integer) 3
127.0.0.1:6379> HGETALL myhash
1) "k1"
2) "v1"
3) "k2"
4) "v2"
5) "k3"
6) "v3"
#####################################################
127.0.0.1:6379> HEXISTS myhash k1 #判断hash中指定字段是否存在
(integer) 1
127.0.0.1:6379> HEXISTS myhash k5
(integer) 0
#####################################################
127.0.0.1:6379> HKEYS myhash #只获取所有的field
1) "k1"
2) "k2"
3) "k3"
127.0.0.1:6379> HVALS myhash#只获取所有的value
1) "v1"
2) "v2"
3) "v3"
127.0.0.1:6379> 
#####################################################
incr
decr
127.0.0.1:6379> hset myhash k5 2 
(integer) 1
127.0.0.1:6379> HINCRBY myhash k5 2 #指定增量
(integer) 4
127.0.0.1:6379> HINCRBY myhash k5 -2
(integer) 2
127.0.0.1:6379> hsetnx myhash k6 hello #如果不存在则可以设置
(integer) 1
127.0.0.1:6379> hsetnx myhash k6 world #如果存在则不可以设置
(integer) 0
127.0.0.1:6379> 

hash可以存储变更的数据,尤其用户信息之类的保存,经常变动的信息,hash更适合对象的存储,String 更适合字符串的存储

Zset(有序集合)

在set的基础上,增加一个值用于排序,set k1 v1,zset k1 score1 v1

127.0.0.1:6379> zadd myset 1 one  #添加一个值
(integer) 1
127.0.0.1:6379> zadd myset 2 two 3 three #添加多个值
(integer) 2
127.0.0.1:6379> 
127.0.0.1:6379> zrange myset 0 -1 #获取所有
#####################################################
排序如何实现
127.0.0.1:6379> zadd salary 2500 xiaohong #添加三个元素
(integer) 1
127.0.0.1:6379> zadd salary 5000 zhangsan 500 hing
(integer) 2
127.0.0.1:6379> ZRANGEBYSCORE salary -inf +inf # 显示全部从小到大
1) "hing"
2) "xiaohong"
3) "zhangsan"
127.0.0.1:6379> ZREVRANGE salary 0 -1 #从大到小
1) "zhangsan"
2) "xiaohong"
3) "hing"
127.0.0.1:6379> 
127.0.0.1:6379> ZRANGEBYSCORE salary -inf +inf withscores #显示全部并且附带成绩
1) "hing"
2) "500"
3) "xiaohong"
4) "2500"
5) "zhangsan"
6) "5000"
127.0.0.1:6379> ZRANGEBYSCORE salary -inf 2500 withscores #显示小于2500升序
1) "hing"
2) "500"
3) "xiaohong"
4) "2500"
#####################################################
移除rem中的元素
127.0.0.1:6379> zrem salary xiaohong #移除指定元素
(integer) 1
127.0.0.1:6379> zrange salary 0 -1
1) "hing"
2) "zhangsan"
#####################################################
127.0.0.1:6379> ZCARD salary #获取集合元素个数
(integer) 2
127.0.0.1:6379> zadd myset 1 hello
(integer) 1
127.0.0.1:6379> zadd myset 2 world 3 hing
(integer) 2
127.0.0.1:6379> 
127.0.0.1:6379> ZCOUNT myset 1 3 #获取指定区间的成员数量
(integer) 3
127.0.0.1:6379> 

三种特殊数据类型

Geospatial地理位置

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

geoadd

#geoadd 添加地理位置
#规则 两极无法直接添加,我们一般会下载城市数据,直接通过java程序一次性导入
#有效的经度 -180~180
#有效的纬度 -85.05112878~85.05112878
#当坐标位置超出上述指定范围,该命令会返回一个错误

#参数key value(经度、纬度、名称)
127.0.0.1:6379> GEOADD china:city 116.40 39.90 beijing
(integer) 1
127.0.0.1:6379> GEOADD china:city 121.47 31.23 shanghai 106.50 29.53 chongqing 114.05 22.52 shenzhen 120.16 30.24 hangzhou 108.96 34.26 xian
(integer) 5

geopos

获得当前定位,一定是一个坐标值

127.0.0.1:6379> geopos china:city beijing chongqing #获取指定城市的经度和纬度
1) 1) "116.39999896287918091"
   2) "39.90000009167092543"
2) 1) "106.49999767541885376"
   2) "29.52999957900659211"

geodist

获取距离

127.0.0.1:6379> GEODIST china:city beijing shanghai km #查看北京到上海的直线距离 单位千米
"1067.3788"
127.0.0.1:6379> GEODIST china:city beijing chongqing km
"1464.0708"
127.0.0.1:6379> GEODIST china:city shanghai chongqing km
"1447.6737"

georadius给定的经纬度为中心,找出某一半径的元素

我附近的人?(获得所有附近人的地址、定位)通过半径来查询

127.0.0.1:6379> GEORADIUS china:city 110 30 1000 km #以100,30经纬度为中心寻找方圆1000km内城市
1) "chongqing"
2) "xian"
3) "shenzhen"
4) "hangzhou"
127.0.0.1:6379> GEORADIUS china:city 110 30 500 km
1) "chongqing"
2) "xian"
127.0.0.1:6379> GEORADIUS china:city 110 30 500 km withdist #显示到中心的距离
1) 1) "chongqing"
   2) "341.9374"
2) 1) "xian"
   2) "483.8340"
127.0.0.1:6379> 
127.0.0.1:6379> GEORADIUS china:city 110 30 500 km withcoord #显示他人的定位信息
1) 1) "chongqing"
   2) 1) "106.49999767541885376"
      2) "29.52999957900659211"
2) 1) "xian"
   2) 1) "108.96000176668167114"
      2) "34.25999964418929977"
127.0.0.1:6379> GEORADIUS china:city 110 30 500 km withcoord count 1 #筛选指定结果
1) 1) "chongqing"
   2) 1) "106.49999767541885376"
      2) "29.52999957900659211"
127.0.0.1:6379> GEORADIUS china:city 110 30 500 km withcoord count 2
1) 1) "chongqing"
   2) 1) "106.49999767541885376"
      2) "29.52999957900659211"
2) 1) "xian"
   2) 1) "108.96000176668167114"
      2) "34.25999964418929977"
127.0.0.1:6379> GEORADIUS china:city 110 30 500 km withcoord count 3
1) 1) "chongqing"
   2) 1) "106.49999767541885376"
      2) "29.52999957900659211"
2) 1) "xian"
   2) 1) "108.96000176668167114"
      2) "34.25999964418929977"
127.0.0.1:6379> 

GEORADIUSBYMEMBER

找出位于指定元素周围的其他元素

127.0.0.1:6379> GEORADIUSBYMEMBER china:city beijing 1000 km 
1) "beijing"
2) "xian"
127.0.0.1:6379> GEORADIUSBYMEMBER china:city shanghai  400 km
1) "hangzhou"
2) "shanghai"

GEOHASH

该命令将返回11个字符的Geohash字符串

#将二维的经纬度转换为一维的字符串,如果两个字符串越接近,那么则距离越近
127.0.0.1:6379> GEOHASH china:city beijing chongqing
1) "wx4fbxxfke0"
2) "wm5xzrybty0"

geo的底层实现原理其实就是Zset,我们可以使用Zset命令操作geo

127.0.0.1:6379> ZRANGE china:city 0 -1 # 查看地图中全部元素
1) "chongqing"
2) "xian"
3) "shenzhen"
4) "hangzhou"
5) "shanghai"
6) "beijing"
127.0.0.1:6379> ZREM china:city beijing # 移除指定元素
(integer) 1
127.0.0.1:6379> ZRANGE china:city 0 -1
1) "chongqing"
2) "xian"
3) "shenzhen"
4) "hangzhou"
5) "shanghai"

Hyperloglog

什么是基数

A{1,3,5,7,8,9} B{1,3,5,7,9}

基数(不重复的元素)=5,数据量大可以接受误差

简介

Redis2.8.9版本就更新了Hyperloglog数据结构

Redis Hyperloglog基数统计算法

优点:占用内存固定的,2^64不同的元素技术,只需要12KB内存,如果要从内存角度来比较的话Hyperloglog首选

网页的UV(一个人访问网站多次,但是还是算作一个人)

传统的方式,set保存用户的id,然后就可以统计set中的元素量作为标准判断

这个方式如果保存大量的用户,就会比较麻烦!我们的目的是为了计数,而不是保存用户的id;

%0.81错误率!统计UV任务,可以忽略不计!

测试使用

127.0.0.1:6379> PFADD mykey1 a b c d e f g h i # 创建第一组元素mykey1
(integer) 1
127.0.0.1:6379> PFCOUNT mykey1 # 统计mykey1的基数数量
(integer) 9
127.0.0.1:6379> PFADD mykey2 s d f h f r c a  #创建第二组元素mykey2
(integer) 1
127.0.0.1:6379> PFCOUNT mykey2
(integer) 7
127.0.0.1:6379> PFMERGE mykey3 mykey1 mykey2 #合并两组mykey1 mykey2 ==>mykey3 并集
OK
127.0.0.1:6379> PFCOUNT mykey3 # 并集的数量
(integer) 11

如果允许容错,那么一定可以使用,hyperloglog,如果不允许重复则使用set或者自己的数据类型!

Bitmaps

位存储

统计疫情感染人数:0 0 0 0 0 1 1 0 0

统计用户信息,活跃不活跃,登录未登录,打卡未打卡,两个状态的,都可以使用。

Bitmaps位图,数据结构,都是操作二进制来记录,就只有0和1两个状态。

365天=365bit 1字节=8bit

测试:

使用bitmap来记录周一到周日的打卡!

打卡1 未打卡0:周一 1 周二 0 周三 1 周四1…

127.0.0.1:6379> SETBIT sign 0 1
(integer) 0
127.0.0.1:6379> SETBIT sign 1 0
(integer) 0
127.0.0.1:6379> SETBIT sign 2 1
(integer) 0
127.0.0.1:6379> SETBIT sign 3 1
(integer) 0
127.0.0.1:6379> SETBIT sign 4 0
(integer) 0
127.0.0.1:6379> SETBIT sign 5 1
(integer) 0
127.0.0.1:6379> SETBIT sign 6 0
(integer) 0

查看某一天是否打卡

127.0.0.1:6379> GETBIT sign 1
(integer) 0
127.0.0.1:6379> GETBIT sign 6
(integer) 0

统计操作,统计打卡的天数

127.0.0.1:6379> BITCOUNT sign #统计这周打卡记录 看是否全勤
(integer) 4
127.0.0.1:6379> 

事务

Redis事务本质:一组命令的集合!一个事务中的所有命令都会被序列化,在事务执行过程中,会按照顺序执行。

一次性,顺序性,排他性,执行一系列的命令!

----------队列 set set set 执行-------------

Redis事务没有隔离级别的概念!

所有命令在事务中,并没有直接被执行,只有发起执行命令的时候才会执行exec

Redis单条命令保存原子性,但是事务不保证原子性!

redis事务

​ 开启事务(multi)

​ 命令入队

​ 执行事务(exec)

正常执行事务

127.0.0.1:6379> MULTI  # 开启事务
OK
127.0.0.1:6379> set k1 v1 #命令入队
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> get k2
QUEUED
127.0.0.1:6379> set k3 v3
QUEUED
127.0.0.1:6379> exec #执行事务
1) OK
2) OK
3) "v2"
4) OK

放弃事务

127.0.0.1:6379> MULTI  #开启事务
OK
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> set k4 v4
QUEUED
127.0.0.1:6379> DISCARD #取消事务
OK
127.0.0.1:6379> get k4 #事务队列中命令都不会被执行
(nil)
127.0.0.1:6379>

编译型异常(代码有问题,命令有错),事务中所有的命令都不会被执行

127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> set k1 v1 
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> set k3 v3
QUEUED
127.0.0.1:6379> setget k2 #错误命令
(error) ERR unknown command `setget`, with args beginning with: `k2`, 
127.0.0.1:6379> set k4 v4
QUEUED
127.0.0.1:6379> set k5 v5
QUEUED
127.0.0.1:6379> exec #执行事务报错
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> get k5 # 所有命令都不会被执行
(nil)

运行时异常(1/0)如果事务队列中存在语法性错误,那么执行命令的时候,其他命令可以被正常执行,错误命令抛出异常

127.0.0.1:6379> set k1 v1
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> INCR k1 #会执行的时候失败
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> set k3 v3
QUEUED
127.0.0.1:6379> get k3
QUEUED
127.0.0.1:6379> exec
1) (error) ERR value is not an integer or out of range #虽然第一条命令失败了,但是依旧正常执行成功了
2) OK
3) OK
4) "v3"
127.0.0.1:6379> get k2
"v2"
127.0.0.1:6379> get k3
"v3"

监控(Watch)

悲观锁

  • 很悲观,认为什么时候都会出问题,无论做什么都会加锁。

乐观锁

  • 很乐观,认为什么时候都不会出问题,所以不会上锁,更新数据的时候去判断一下,在此期间是否有人修改过这个数据
  • 获取version
  • 更新的时候比较version

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> DECRBY money 20
QUEUED
127.0.0.1:6379> INCRBY out 20
QUEUED
127.0.0.1:6379> exec
1) (integer) 80
2) (integer) 20
127.0.0.1:6379> 

测试多线程修改值,使用watch可以当作redis的乐观锁操作

127.0.0.1:6379> WATCH money #监视money
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> DECRBY money 10
QUEUED
127.0.0.1:6379> INCRBY out 10
QUEUED
127.0.0.1:6379> exec #执行之前另外的线程修改了我们的值,这个时候事务执行会失败
(nil)

如果执行失败,获取最新值即可

127.0.0.1:6379> UNWATCH   #发生事务执行失败先解锁
OK
127.0.0.1:6379> WATCH money #获取最新的值再次监视 select version
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> DECRBY money 10
QUEUED
127.0.0.1:6379> INCRBY out 10
QUEUED
127.0.0.1:6379> exec# 比较监视的内容是否发生变化,如果没有变化那么可以执行成功,如果变化就执行失败
1) (integer) 990
2) (integer) 30
127.0.0.1:6379> 

Jedis

使用Java来操作Redis

什么是Jedis

Redis官方推荐的Java连接开发工具,使用Java操作Redis中间件,如果想用Java操作Redis,那么一定要对Jedis很熟。

测试

1 导入对应的依赖

<!--导入jedis -->
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>3.4.1</version>
</dependency>
<!--fastjson -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
</dependency>

2编码测试

  • 连接数据库

  • 操作命令

  • 断开连接

    package com.test;
    
    import redis.clients.jedis.Jedis;
    
    public class Test {
        public static void main(String[] args) {
            //1 、new Jedis 对象
            Jedis jedis=new Jedis("172.22.24.139",6379);
            //所有的命令就是我们之前学习的指令
            System.out.println(jedis.ping());
        }
    }
    

    输出:

    image-20210102192901193

    常用的API

    参考之前的学习语句一样

    String

    List

    Set

    Hash

    Zset

    所有的API命令。就是对应的我们上边学习的指令,一个都没有变化

    事务

    package com.test;
    
    import com.alibaba.fastjson.JSONObject;
    import redis.clients.jedis.Jedis;
    import redis.clients.jedis.Transaction;
    
    public class TestTx {
        public static void main(String[] args) {
    
            Jedis jedis=new Jedis("172.22.24.139");
    
            jedis.flushDB();
            JSONObject jsonObject=new JSONObject();
            jsonObject.put("hello","world");
            jsonObject.put("name","Hing");
            //开启事务
            Transaction multi=jedis.multi();
            String jsonStr=jsonObject.toJSONString();
    
            try {
                multi.set("user1",jsonStr);
                multi.set("user2",jsonStr);
                int i=1/0;//代码执行出现异常,事务执行失败
    
                multi.exec(); //执行事务
            } catch (Exception e) {
                multi.discard(); //放弃事务
    //            e.printStackTrace();
            }finally {
                System.out.printf(jedis.get("user1"));
                System.out.printf(jedis.get("user2"));
                jedis.close();//关闭jedis
            }
    
        }
    }
    

SpringBoot整合

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

image-20210102184917571

SpringData是和Spring Boot齐名的项目

说明:在SpringBoot2.0之后,原来的jedis被替换为了lettuce

jedis:采用直连,多个线程操作不安全,想要避免不安全,使用jredis连接池,像BIO模式

lettuce:采用netty,实例可以在多个线程中共享,不存在线程安全问题,可以减少线程数量!像NIO模式

源码分析:

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

@Bean
@ConditionalOnMissingBean
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
//由于String是redis最常用的类型所以单独提出来一个bean
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
    StringRedisTemplate template = new StringRedisTemplate();
    template.setConnectionFactory(redisConnectionFactory);
    return template;
}

整合测试

1、导入依赖

 <dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-data-redis</artifactId>
 </dependency>

2、配置连接

#springboot的所有配置类都有一个自动配置类RedisAutoConfiguration
#自动配置类会绑定一个properties配置文件 RedisProperties

#配置Redis
spring.redis.host=172.22.24.139
spring.redis.port=6379

3、测试

package com.example.springbootredis;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisTemplate;

@SpringBootTest
class SpringbootRedisApplicationTests {

    @Autowired
    private RedisTemplate redisTemplate;
    @Test
    void contextLoads() {
        //redisTemplate操作不同的数据类型,API和我们的指令一样的
        //opsForValue 操作字符串 类似 String
        //opsForList 操作list 类似list
        //opsForSet
        //opsForHash
        //opsForZSet
        //opsForGeo
        //opsForHyperLogLog
        //opsForStream

        //除了基本的操作我们常用的方法都可以直接通过redisTemplate操作,比如事务,和基本的CRUD

        //获取redis的连接对象
        //        RedisConnection redisConnection=redisTemplate.getConnectionFactory().getConnection();
        //        redisConnection.flushDb();
        //        redisConnection.flushAll();


        redisTemplate.opsForValue().set("mykey","Hing's java");
        System.out.printf(redisTemplate.opsForValue().get("mykey").toString());
    }
}

image-20210102193101478

image-20201230153107199

关于对象的保存:所有的对象都需要序列化

image-20210102200736972

image-20210102200429284

序列化方式

image-20210104151904212

我们编写一个自己的RedisTemplate

package com.example.springbootredis.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration
public class RedisConfig {

    //自己定义一个RedisTemplate
    @Bean
    @ConditionalOnSingleCandidate(RedisConnectionFactory.class)
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {

        //我们为了自己开发方便,一般使用<String,Object>
        RedisTemplate<String, Object> template = new RedisTemplate<String,Object>();
        template.setConnectionFactory(redisConnectionFactory);

        //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.setObjectMapper(om);

        //String序列化
        StringRedisSerializer stringRedisSerializer=new StringRedisSerializer();

        //key采用String的序列化方式
        template.setKeySerializer(stringRedisSerializer);
        //hash的key也采用String的序列化方式
        template.setHashKeySerializer(stringRedisSerializer);
        //value的序列化采用jackson
        template.setValueSerializer(jackson2JsonRedisSerializer);
        //hash的value序列化也采用jackson
        template.setHashValueSerializer(jackson2JsonRedisSerializer);
        template.afterPropertiesSet();
        
        return template;
    }
}

在开发的过程中我们要定义我们自己的工具类方便我们进行Redis的具体的操作处理

此处省略,需要可以上网上去找

Redis.config详解

启动的时候通过配置文件来启动

单位

image-20210102200134043

配置文件单位对大小写不敏感

包含

image-20210104163228950

就好比学习spring 的import include

网络

bind 127.0.0.1 #绑定ip地址
protected-mode yes #保护模式
port 6379 #端口设置

通用 GENERAL

daemonize yes #以守护进程的方式运行,默认no

pidfile /var/run/redis_6379.pid #如果以后台方式运行我们就需要指定一个pid文件

# 日志
# Specify the server verbosity level.
# This can be one of:
# debug (a lot of information, useful for development/testing)
# verbose (many rarely useful info, but not a mess like the debug level)
# notice (moderately verbose, what you want in production probably) 生产环境
# warning (only very important / critical messages are logged)
loglevel notice
logfile "" #日志文件位置名

databases 16 #数据库数量,默认16个数据库

always-show-logo yes #是否总是显示logo

快照 SNAPSHOTTING

持久化,在规定的时间内,执行了多少次的操作,则会持久化到文件 .rdb .aof

redis是内存数据库,如果没有持久化,那么数据断电即失!

#如果900s内,至少有 1个key进行更改,我们就进行持久化操作
save 900 1
#如果300s内,至少有 10个key进行更改,我们就进行持久化操作
save 300 10
#如果60s内,至少有 10000个key进行更改,我们就进行持久化操作
save 60 10000
#我们后期学习持久化,会自己定义这个测试!

stop-writes-on-bgsave-error yes #持久化如果出错,是否还需要继续工作

rdbcompression yes #是否压缩rdb文件,需要消耗一定的cpu资源

rdbchecksum yes #保存rdb文件时,是否做错误的检查校验!

dir ./ #rdb文件保存的目录

复制 REPLICATION ,后面主从复制的时候再做分析

安全 SECURITY

可以在这里设置redis的密码,默认是没有密码的

示例:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pTd1EJ2D-1619616522904)(Redis.assets/20210311164938.png)]

实操:

127.0.0.1:6379> config get requirepass #获取redis的密码
1) "requirepass"
2) ""
127.0.0.1:6379> config set requirepass 123456 #设置redis的密码
OK
127.0.0.1:6379> config get requirepass
1) "requirepass"
2) "123456"
127.0.0.1:6379> ping
PONG
127.0.0.1:6379> 

限制 CLIENTS

maxclients 10000  #设置最大的客户端数量

maxmemory <bytes> #redis配置最大内存容量

maxmemory-policy noeviction #内存到达上限后的处理策略
    1、volatile-lru:只对设置了过期时间的key进行LRU(默认值) 
    2、allkeys-lru : 删除lru算法的key   
    3、volatile-random:随机删除即将过期key   
    4、allkeys-random:随机删除   
    5、volatile-ttl : 删除即将过期的   
    6、noeviction : 永不过期,返回错误

模式 APPEND ONLY MODE aof配置

appendonly no #默认不开启aof模式,默认使用rdb方式进行的持久化,在所有的大部分,rdb够用

appendfilename "appendonly.aof" #持久化文件的名字

# appendfsync always #每次修改都会 sync,消耗性能
appendfsync everysec #每秒执行一次 sync,可能会丢失这1s的数据
# appendfsync no  #不执行 sync ,这个时候操作系统自己同步数据,速度最快!

具体的配置持久化详解!

Redis持久化

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

RDB(Redis Database)

什么是RDB

在主从复制中,rdb备用,在从机上,不占主机内存!

image-20210104152051076

在指定的时间间隔内将内存中的数据集快照写入磁盘,也就是Snapshot快照,它恢复时将快照文件直接读到内存

Redis会单独创建(fork)一个子进程来进行持久化,会先将数据写到一个临时文件夹中,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件,整个过程中,主进程不进行任何的I/O操作。这就确保了极高的性能,如果需要进行大规模的数据恢复,且对于数据恢复的完整性不是非常敏感,那RDB要比AOF更加的高效RDB的缺点是最后一次持久化后的数据可能丢失。我们默认的就是RDB,一般情况下不需要修改这个配置。

有时候生产环境我们会将这个文件进行备份!

**rdb保存的文件是dump.rdb ** 都是在我们的配置文件快照中进行的配置!

image-20210104154958596

image-20210104162912534

触发机制

1 save规则满足的情况下,会自动触发rdb规则

2 执行flushall的命令,也会触发rdb规则

3 退出redis,也会产生rdb文件

备份就是自动生成一个dump.rdb文件

image-20210104163546642

如何恢复rdb文件

1、只要将文明的dump.rdb文件放在我们的redis启动目录就可以,redis启动的时候会自动检查dump.rdb恢复其中的数据

2、查看需要存的位置

127.0.0.1:6379> config get dir
1) "dir"
2) "/usr/local/bin" #如果这个目录下存在dump.rdb文件,启动就会自动恢复其中的数据

几乎它自己默认的配置就够用了

优点

适合大规模的数据恢复

对数据的完整性要求不高

缺点

需要一定的时间间隔进行操作,如果redis意外宕机了,最后一次修改数据就没有了

fork进程的时候会占用一定的内存空间

AOF(Append Only File)

将我们的所以的命令都记录下来,history,恢复的时候就把这个文件全部执行一遍

是什么

image-20210104165443211

以日志的形式来记录每个写操作,将Redis执行过的所有指令都记录下来(读操作不记录),只许追加文件但是不许改写文件,Redis启动之初会读取该文件重新构建数据,换言之,redis重启就根据日志文件内容将写指令从前到后执行一次以完成数据的恢复工作!

AOF保存的是appendonly.aof文件

append

image-20210104163834655

默认是不开启的,我们需要手动配置,我们只需要将 appendonly 设置为yes就开启了aof

重启redis就可以生效了。

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

redis给我们提供了这样一个工具:redis-check-aof --fix

redis-check-aof --fix appendonly.aof #修复aof文件

如果文件正常,重启就可以直接恢复了

重写

aof默认文件无限追加,文件就会越来越大

image-20210104164911601

如果aof文件大于64m,太大了,就会fork一个新的进程来将我们的文件进行重新

优点

appendonly no #默认不开启aof模式,默认使用rdb方式进行的持久化,在所有的大部分,rdb够用

appendfilename "appendonly.aof" #持久化文件的名字

# appendfsync always #每次修改都会 sync,消耗性能
appendfsync everysec #每秒执行一次 sync,可能会丢失这1s的数据
# appendfsync no  #不执行 sync ,这个时候操作系统自己同步数据,速度最快!

1 每一次修改都同步,文件的完整性更加的好

2 每秒同步一次,可能会丢失一秒的数据

3 从不同步效率最高

缺点

1、相对于数据文件来说,aof远远大于rdb,修复速度慢rdb

2、AOF运行效率也比rdb慢,所以我们的redis默认配置就是rdb持久化

扩展

  1. RDB持久化方式能够在指定的时间间隔内对你的数据进行快照存储
  2. AOF持久化方式记录每次对服务器的写操作,当服务器重启的时候重新执行这些命令来恢复原始的数据,AOF命令以Redis协议追加保存每次写的操作到文件末尾,Redis还能对AOF文件进行后台重写,使得文件的体积不至于过大
  3. 只做缓存,如果你只希望你的数据在服务器运行的时候存在,你也可以不使用任何的持久化
  4. 同时开启两种持久化方式
    1. 在这种情况下当redis重启的时候会优先载入AOF文件来恢复原始的数据,因为在通常情况下AOF文件保存的数据集要比RDB文件保存的数据集完整。
    2. RDB的数据不实时,同时使用两者时服务器重启也只会找AOF文件,那要不要只使用AOF呢,建议不要,因为RDB更适合用于备份数据库,(AOF在不断的变化不好备份),快速重启,而且不会有AOF可能潜在的BUG,留着作为一个万一的手段。
  5. 性能建议
    1. 因为RDB文件只作为后备用途,建议只在slave上持久化RDB文件,而且只要15分钟备份一次就够了,只保留 save 900 1 这条规则
    2. 如果Enable AOF,好处是在最恶劣的情况下只会丢失不超过两秒数据,启动脚本较简单只load自己的AOF文件就可以了,代价一个是带来持续的IO,二是AOF的rewrite的最后将rewrite过程中产生的新数据写到新文件造成的阻塞几乎是不可避免的,只要磁盘许可,应该尽量减少AOFrewrite的频率,AOF重写的基础大小默认64M太小了,可以设置到5G以上,默认超过原大小100%大小重写可以改到适当的数值。
    3. 如果不EnableAOF ,仅靠Master-Slave Replication 实现高可用性也可以,能省掉一大笔的IO,也减少rewrite时带来的系统波动,代价是如果Master /Slave同时宕掉,会丢失十几分钟的数据,启动脚本也要比较两个Master/Slave中的RDB文件,载入较新的那个,微博就是这种架构。

Redis发布订阅

Redis发布订阅是一种消息通信模式:发送者(Pub)发送消息,订阅者(Sub)接收消息。

Redis客户端可以订阅任意数量的频道

订阅/发布 消息图

第一 消息发送者 第二个频道 第三个消息订阅者

image-20210108110055300

Redis 发布订阅命令

下表列出了 redis 发布订阅常用命令:

序号命令及描述
1[PSUBSCRIBE pattern pattern …] 订阅一个或多个符合给定模式的频道。
2[PUBSUB subcommand argument [argument …]] 查看订阅与发布系统状态。
3PUBLISH channel message 将信息发送到指定的频道。
4[PUNSUBSCRIBE pattern [pattern …]] 退订所有给定模式的频道。
5[SUBSCRIBE channel channel …] 订阅给定的一个或多个频道的信息。
6[UNSUBSCRIBE channel [channel …]] 指退订给定的频道。

测试程序

订阅者

127.0.0.1:6379> SUBSCRIBE HingsChannel  #订阅一个频道
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "HingsChannel"
3) (integer) 1
#等待读取推送的消息
1) "message" #消息
2) "HingsChannel" #哪个频道的消息
3) "hello,hing" #消息的具体内容
1) "message"
2) "HingsChannel"
3) "hello,world"

发布者

127.0.0.1:6379> PUBLISH HingsChannel hello,hing #发布者发送消息到频道
(integer) 1
127.0.0.1:6379> PUBLISH HingsChannel hello,world #发布者发送消息到频道
(integer) 1

Redis主从复制

概念

主从复制,是指将一台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;

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

对于这种场景,我们可以使用以下结构:

image-20210108100537595

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

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

环境配置

只配置从库,不配主库

127.0.0.1:6379> info replication # 查看当前库信息
# Replication
role:master #  	角色master
connected_slaves:0 # 没有从库
master_replid:a88a0797c5fab268e885485fd9590d3fd4f0ed15
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:0
second_repl_offset:-1
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0
127.0.0.1:6379> 

复制三个配置文件,修改配置信息

1、端口

2、pid名称

3、log文件名称

4、dump.rdb文件名称

修改后启动三个Redis服务,可以通过进程信息查看

image-20210104170936172

一主二从

默认情况下每台Redis服务都是主节点,一般情况只需要配置从机

认老大,一主(6379)二从(6380,6381)

配置从机6380

127.0.0.1:6380> SLAVEOF 127.0.0.1 6379 #SLAVEOF host port找谁当自己老大
OK
127.0.0.1:6380> info replication
# Replication
role:slave #  当前角色是从机
master_host:127.0.0.1 # 主机信息
master_port:6379
master_link_status:up
master_last_io_seconds_ago:2
master_sync_in_progress:0
slave_repl_offset:0
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:caac6d6c8c795c4daa931d1b1b3862a3b5ba000b
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:0
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:0

#在主机查看信息
# Replication
role:master 
connected_slaves:1 
slave0:ip=127.0.0.1,port=6380,state=online,offset=210,lag=1 #多了从机的配置
master_replid:caac6d6c8c795c4daa931d1b1b3862a3b5ba000b
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:210
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:210
127.0.0.1:6379> 

配置从机6381

同上

如果两个都配置完,就有两个从机了

image-20210108113622804

真实的主从配置应该在配置文件中进行配置,这也是永久的,我们这里使用的命令,暂时的!

细节

主机可以写,从机不能写

image-20210108114315484

从机只能读取内容

image-20210108142821995

测试:主机断开连接,从机依旧连接到主机,但是没有写操作,如果主机回来,从机依旧可以获取到主机的写信息

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

复制原理

Slave启动成功连接到Master会发送一个Sync命令

Master接到命令,启动后台存储进程,同时收集所有接收到的用于修改数据集的命令,在后台进程执行完毕之后,master将传送整个数据文件到slave,并完成一次完全同步

全量复制:slave服务在接收到数据库文件数据后将其存盘并加载到内存中。

增量复制:Master继续将新的所有收集到的修改命令依次传给slave,完成同步

但是只要是重新连接master,一次完全同步(全量复制)将被自动执行。我们的数据一定可以在从机中看到

层层链路

上一个Master连接下一个Slave

image-20210108114244908

这个时候也可以完成主从复制

如果没有老大了,这个时候能不能选个老大出来?手动

如果主机断开连接,我们可以使用SLAVEOF no one 让自己变成主机,其他的节点就可以就可以手动的连接到这个节点(手动)!如果这个时候老大修复,需要重新配置连接。

哨兵模式

(自动选取老大的模式)

概述

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

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

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

image-20210108145026066

这里的哨兵有两个作用:

​ 通过发送命令,让redis服务器返回其监控 运行状态,包括主服务器和从服务器

​ 当哨兵监测到master宕机,会自动将slave切换成master,然后通过发布订阅模式通知其他的从服务器,修改配置文件,通知他们切换主机。

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

image-20210108153712382

假设主服务器宕机,哨兵1先监测到这个结果,系统不会马上进行failover过程,仅仅是哨兵1主观的认为主服务器不可用,这个现象称为主观下线,当后面的哨兵也监测到主服务器不可用,并且数量达到一定的值时那么哨兵之间就会进行一次投票,投票的结果由一个哨兵发起,进行failover(故障转移)操作,切换成功后,就会通过发布订阅模式,让各个哨兵把自己监控的从服务器实现切换主机,这个过程称为客观下线

测试

我们目前的状态是一主二从

1、配置哨兵配置文件sentinel.conf

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

后边的这个数字1,代表主机挂了,slave投票看让谁接替称为主机,票数最多的就会成为主机!

2、启动哨兵

[root@hadoop bin]# redis-sentinel config/sentinel.conf 
22807:X 08 Jan 2021 15:30:34.998 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
22807:X 08 Jan 2021 15:30:34.998 # Redis version=6.0.9, bits=64, commit=00000000, modified=0, pid=22807, just started
22807:X 08 Jan 2021 15:30:34.998 # Configuration loaded
22807:X 08 Jan 2021 15:30:34.998 * Increased maximum number of open files to 10032 (it was originally set to 1024).
                _._                                                  
           _.-``__ ''-._                                             
      _.-``    `.  `_.  ''-._           Redis 6.0.9 (00000000/0) 64 bit
  .-`` .-```.  ```\/    _.,_ ''-._                                   
 (    '      ,       .-`  | `,    )     Running in sentinel mode
 |`-._`-...-` __...-.``-._|'` _.-'|     Port: 26379
 |    `-._   `._    /     _.-'    |     PID: 22807
  `-._    `-._  `-./  _.-'    _.-'                                   
 |`-._`-._    `-.__.-'    _.-'_.-'|                                  
 |    `-._`-._        _.-'_.-'    |           http://redis.io        
  `-._    `-._`-.__.-'_.-'    _.-'                                   
 |`-._`-._    `-.__.-'    _.-'_.-'|                                  
 |    `-._`-._        _.-'_.-'    |                                  
  `-._    `-._`-.__.-'_.-'    _.-'                                   
      `-._    `-.__.-'    _.-'                                       
          `-._        _.-'                                           
              `-.__.-'                                               

22807:X 08 Jan 2021 15:30:34.999 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.
22807:X 08 Jan 2021 15:30:35.010 # Sentinel ID is 9912d53e7b484a7bf008893fa48bbc57bf3d4125
22807:X 08 Jan 2021 15:30:35.010 # +monitor master myredis 127.0.0.1 6379 quorum 1
22807:X 08 Jan 2021 15:30:35.010 * +slave slave 127.0.0.1:6380 127.0.0.1 6380 @ myredis 127.0.0.1 6379
22807:X 08 Jan 2021 15:30:35.011 * +slave slave 127.0.0.1:6381 127.0.0.1 6381 @ myredis 127.0.0.1 6379

如果master节点断开了,这个时候就会从从机中随机选择一个服务器作为主机(这里边有一个投票算法)

image-20210108153619636

哨兵日志

image-20210108144957686

如果主机回来了,只能归并到新的主机下,当作从机,这就是哨兵模式的规则

哨兵模式

优点:

  1. 哨兵集群,基于主从复制模式,所有的主从配置优点,它全有
  2. 主从可以切换,故障可以转移,系统的可用性就会很好
  3. 哨兵模式就是主从模式的升级,手动到自动更加健壮

缺点:

  1. Redis不好在线扩容,集群容量一旦到达上限,在线扩容十分麻烦
  2. 实现哨兵模式配置十分麻烦,里边由很多选择

Redis缓存穿透与雪崩

image-20210108155722332

缓存穿透(查不到)

概念

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

解决方案

布隆过滤器

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

image-20210108155131460

缓存空对象

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

image-20210108170727674

但是这个方法会存在两个问题:

1、如果空值能够被缓存起来,这就意味着缓存需要更多的空间存储更多的键,因为这当中会有很多空值的键

2、即使对空值设置了过期的时间,还是会存在缓存层和存储层的数据会有一段时间窗口的不一致,这对于需要保持一致性的业务会有影响

缓存击穿(量太大,缓存过期)

概述

这里需要注意和缓存击穿的区别,缓存击穿是指一个key非常热点,在不停的扛着大并发,大并发集中对这个点进行访问,当这个key在失效的瞬间,持续的大并发就会穿破缓存,直接请求数据库,就像在一个屏幕上凿开了一个洞。

当某个key在过期的瞬间,有大量的请求并发访问,这类数据一般是热点数据,由于缓存过期,会同时访问数据库来查询最新数据,并且回写缓存,会导致数据库瞬间压力过大。

解决方案

设置热点数据永久不过期

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

加互斥锁

分布式锁:使用分布式锁保证对于每个key同时只有一个线程去查询后端服务,其他线程没有获得分布式锁的权限,因此只需要等待即可,这种方式将高并发的压力转移到了分布式锁,因此对分布式锁的考验很大。

缓存雪崩

概念

缓存雪崩,是指在某个时间段,缓存集中失效,redis宕机!

产生雪崩的原因之一,比如在写文本的时候,马上就到双十二零点,很快就会迎来一大波抢购,这波商品时间集中的放入了缓存,假设缓存一个小时,那么到了凌晨一点钟的时候,这批商品的缓存就都过期了,而对这批商品的访问查询,都落到了数据库上,对数据库而言,就会产生周期性的压力波峰,于是所有的请求都会达到存储层,存储层的调用量会暴增,造成存储层也会挂掉的情况。

集中过期,倒不是非常致命,比较致命的缓存雪崩,是缓存服务器某个节点宕机或者断网,因为自然形成的缓存雪崩,一定是在某个时间集中创建缓存,这个时候,数据库也是可以顶住压力的,无非是对数据库产生周期性的压力而已,而缓存服务节点的宕机,对数据库服务器造成的压力是不可预知的很有可能瞬间就把数据库压垮。

解决方案

redis高可用

这个思想的含义是,既然redis有可能挂掉,那么我们多增设几台redis,这样一台挂掉之后其他的还可以继续工作,其实就是搭建的集群。

限流降级

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

数据预热

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

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值