【狂神说】Redis - 入门

本文章仅个人学习笔记,内容来源b站up主:遇见狂神说

一、Nosql概述

为什么使用Nosql

1、单机Mysql时代

在这里插入图片描述
90年代,一个网站的访问量一般不会太大,单个数据库完全够用。随着用户增多,网站出现以下问题

1、数据量增加到一定程度,单机数据库就放不下了
2、数据的索引(B+ Tree),一个机器内存也存放不下
3、访问量变大后(读写混合),一台服务器承受不住。

2.Memcached(缓存)+MySQL+垂直拆分(读写分离)

网站80%的情况都是在读,每次都要去查询数据库的话就十分的麻烦!所有说我们希望减轻数据库的压力,我们可以使用缓存在保证效率!
发展过程:优化数据结构和索引 → 文件缓存(IO) → Memcached(当时最热门的技术)
在这里插入图片描述

3.分库分表+水平拆分+MySQL集群

技术和业务在发展的同时,对人的要求也越来越高
本质:数据库(读、写)
早些年MyISAM:表锁,十分影响效率!高并发下就会出现严重的锁问题
转战innodb:行锁
慢慢的就开始使用分库分表来解决写的压力!
在这里插入图片描述

4.如今最近的年代

MySQL等关系型数据库就不够用了!数据量很多,变化很快
MySQL有的使用它来储存一些比较大的文件,博客,图片!数据库表很大,效率就低了!如果有一种专门来处理这种数据,MySQL压力就会变得十分小(研究如何处理这些问题!)

目前一个基本的互联网项目

在这里插入图片描述

为什么要用NoSQL

用户的个人信息,社交网络,地理位置。用户自己产生的数据,用户日志等等爆发式增长!
这时候我们就需要使用NoSQL数据库,NoSQL可以很好的处理以上的情况!

什么是NoSQL

NoSQL

NoSQL = Not Only SQL (不仅仅是SQL)
泛指非关系型数据库。
随着web2.0互联网的诞生,传统的关系型数据库很难对付web2.0时代!尤其是超大规模的高并发的社区!暴露出来很多难以克服的问题
NoSQL在当今大数据环境下发展的十分迅速,Redis是发展最快的,而且是我们当下必须要掌握的一个技术!
很多的数据类型用户的个人信息,社交网络,地理位置,这些数据类型的储存不需要一个固定的格式!不需要多余的操作就可以横向扩展的
Map<String,Object>使用键值对来控制!

NoSQL特点

解耦!
1、方便扩展(数据之间没有关系,很好扩展!)
2、大数据最高性能(Redis 一秒写8万次,读取11万,NoSQL的缓存记录级,是一种细粒度的缓存,性能会比较高!)
3、数据类型是多样性的(不需要事先设计数据库!随取随用!如果是数据量十分大的表,很多人就无法设计了!)
4、传统RDBMS和NoSQL

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

了解:3V+3高

大数据时代的3V:主要是描述问题的
1.海量Velume
2.多样Variety
3.实时Velocity
大数据时代的3高:主要是对程序的要求
1.高并发
2.高可扩
3,高性能

阿里巴巴演进分析

推荐阅读:阿里云的这群疯子https://yq.aliyun.com/articles/653511
在这里插入图片描述
在这里插入图片描述

# 1、商品的基本信息
名称、价格、商家信息:
关系型数据库就可以解决了  MySQL/Oracle
淘宝内部的MySQL不是大家用的MySQL

#2、商品的新建,评论(文字比较多)
文档型数据库中,MongoDB

#3、图片
分布式文件系统 FastDFS
	- 淘宝自己 TFS
	- Gooale GFS
	- Hadoop HDFS
	- 阿里云的 oss

#4、商品的关键字(搜索)
	- 搜索引擎 solr elasticsearch
	- 淘宝 ISerach

#5、商品热门的波段信息
	- 内存数据库
	- Redis Tair Memache

#6、商品的交易,外部的支付接口
	- 三方应用

一个简单的网页,背后的技术一定不是大家所想的那么简单!
大型互联网应用的问题:

  • 数据类型太多了!
  • 数据源繁多,经常重构!
  • 数据要改造,大面积改造

NoSQL的四大分类

KV键值对:

  • 新浪:Redis
  • 美团:Redis+Tair
  • 阿里、百度:Redis+memecache

文档型数据库(bson格式和json一样):

MongoDB(一般必须要掌握)

  • MongoDB是一个基于分布式文件存储的数据库,C++编写,主要用来处理大量的文档
  • MongoDB是一个介于关系型数据库和非关系型数据库中间的产品,是菲关系型数据库中功能最丰富的,最像关系型数据库的
  • ConthDB

列存储数据库

HBase
分布式文件系统

图关系数据库

他不是存图形,放的是关系,比如:朋友圈社交网络,广告推荐!
Neo4j,InfoGrid

在这里插入图片描述

二、Redis入门

概述

Redis 是什么?

Redis(Remote Dictionary Server),即远程字典服务!
是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型,key-Value数据库
免费和开源!是当下最热门的NoSQL技术之一!
与memcached一样,为了保证效率,数据都是缓存在内存中。区别的是redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步。

Redis 能干嘛?

1、内存存储、持久化,内存中是断电即失,所以说持久化很重要(rdb、aof)
2、效率高,可以用于高速缓存
3、发布订阅系统
4、地图信息分析
5、计时器、计数器(浏览量!)

特性

1、多样的数据类型
2、持久化
3、集群
4、事务

学习中需要用到的东西

1、官网:https://redis.io/
2、中文网:http://www.redis.cn/
3、通过官网下载
Redis推荐都是在Linux服务器上搭建的,我们是基于Linux学习!

Linux安装

1、下载安装包!官网
2、解压Redis的安装包! 一般是程序的文件,我们放到 /opt

[root@localhost shiqi]# mv redis-6.0.8.tar.gz /opt
[root@localhost shiqi]# cd opt
[root@localhost opt]# ls
containerd  redis-6.0.8.tar.gz
[root@localhost opt]# tar -zxvf redis-6.0.8.tar.gz 

在这里插入图片描述

3、进入解压后的文件,可以看到我们redis的配置文件
redis.conf
在这里插入图片描述

4、基本的环境安装

#yum install gcc-c++
# 最新的redis6.0 需要gcc 5以上,先升级gcc 
sudo yum install centos-release-scl
sudo yum install devtoolset-7-gcc*
scl enable devtoolset-7 bash

make 

make install 

5、redis的默认安装路径 /usr/local/bin
在这里插入图片描述

6、将redis配置文件,复制到我们当前目录下
在这里插入图片描述

7、redis默认不是后台启动的,修改配置文件
在这里插入图片描述

8、启动Redis服务
在这里插入图片描述

9、使用redis-cli进行连接测试!
在这里插入图片描述

10、查看redis的进程是否开启!
在这里插入图片描述

11、如何关闭redis服务?shutdown
在这里插入图片描述

12、再次查看进程是否存在
在这里插入图片描述

测试性能

redis-benchmark 是一个压力测试工具
在这里插入图片描述
我们来测试一下:

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

如何查看这些分析呢?
在这里插入图片描述

基础的知识

redis默认有16个数据库
在这里插入图片描述
默认使用的事第0个
16个数据库为:DB 0~DB 15
默认使用DB 0 ,可以使用select n切换到DB n,dbsize可以查看当前数据库的大小,与key数量相关。

127.0.0.1:6379> config get databases # 命令行查看数据库数量databases
1) "databases"
2) "16"

127.0.0.1:6379> select 8 # 切换数据库 DB 8
OK
127.0.0.1:6379[8]> dbsize # 查看数据库大小
(integer) 0

# 不同数据库之间 数据是不能互通的,并且dbsize 是根据库中key的个数。
127.0.0.1:6379> set name sakura 
OK
127.0.0.1:6379> SELECT 8
OK
127.0.0.1:6379[8]> get name # db8中并不能获取db0中的键值对。
(nil)
127.0.0.1:6379[8]> DBSIZE
(integer) 0
127.0.0.1:6379[8]> SELECT 0
OK
127.0.0.1:6379> keys *
1) "counter:__rand_int__"
2) "mylist"
3) "name"
4) "key:__rand_int__"
5) "myset:__rand_int__"
127.0.0.1:6379> DBSIZE # size和key个数相关
(integer) 5

127.0.0.1:6379[3]> flushdb
OK
127.0.0.1:6379[3]> flushall
OK

**keys *** :查看当前数据库中所有的key。

flushdb:清空当前数据库中的键值对。

flushall:清空所有数据库的键值对。

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

Redis为什么单线程还这么快?
1、误区1:高性能的服务器一定是多线程的?
2、误区2:多线程(CPU上下文会切换!)一定比单线程效率高!
核心:redis是将全部的数据放在内存中的,所以说使用单线程去操作,效率就是最高的。CPU切换:耗时的操作。对于内存系统来说,没有上下文切换,效率就是最高的。多次读写都是在一个CPU上的。所以内存情况下,这个就是最佳方案

三、五大数据类型

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

Redis-Key

在redis中无论什么数据类型,在数据库中都是以key-value形式保存,通过进行对Redis-key的操作,来完成对数据库中数据的操作。
下面学习的命令:

  • exists key:判断键是否存在
  • del key:删除键值对
  • move key db:将键值对移动到指定数据库
  • expire key second:设置键值对的过期时间
  • type key:查看value的数据类型
27.0.0.1:6379> keys * # 查看当前数据库所有key
(empty list or set)
127.0.0.1:6379> set name qinjiang # set key
OK
127.0.0.1:6379> set age 20
OK
127.0.0.1:6379> keys *
1) "age"
2) "name"
127.0.0.1:6379> move age 1 # 将键值对移动到指定数据库
(integer) 1
127.0.0.1:6379> EXISTS age # 判断键是否存在
(integer) 0 # 不存在
127.0.0.1:6379> EXISTS name
(integer) 1 # 存在
127.0.0.1:6379> SELECT 1
OK
127.0.0.1:6379[1]> keys *
1) "age"
127.0.0.1:6379[1]> del age # 删除键值对
(integer) 1 # 删除个数


127.0.0.1:6379> set age 20
OK
127.0.0.1:6379> EXPIRE age 15 # 设置键值对的过期时间

(integer) 1 # 设置成功 开始计数
127.0.0.1:6379> ttl age # 查看key的过期剩余时间
(integer) 13
127.0.0.1:6379> ttl age
(integer) 11
127.0.0.1:6379> ttl age
(integer) 9
127.0.0.1:6379> ttl age
(integer) -2 # -2 表示key过期,-1表示key未设置过期时间

127.0.0.1:6379> get age # 过期的key 会被自动delete
(nil)
127.0.0.1:6379> keys *
1) "name"

127.0.0.1:6379> type name # 查看value的数据类型
string

关于TTL命令

Redis的key,通过TTL命令返回key的过期时间,一般来说有3种:

  1. 当前key没有设置过期时间,所以会返回-1.
  2. 当前key有设置过期时间,而且key已经过期,所以会返回-2.
  3. 当前key有设置过期时间,且key还没有过期,故会返回key的正常剩余时间.

关于重命名RENAMERENAMENX

  • RENAME key newkey 修改 key 的名称
  • RENAMENX key newkey仅当 newkey 不存在时,将 key 改名为 newkey 。

后期如果有遇到不会的命令,可以再官网查看帮助文档!
在这里插入图片描述

String(字符串)

命令描述示例
APPEND key value向指定的key的value后追加字符串127.0.0.1:6379> set msg hello OK 127.0.0.1:6379> append msg " world" (integer) 11 127.0.0.1:6379> get msg “hello world”
DECR/INCR key将指定key的value数值进行+1/-1(仅对于数字)127.0.0.1:6379> set age 20 OK 127.0.0.1:6379> incr age (integer) 21 127.0.0.1:6379> decr age (integer) 20
INCRBY/DECRBY key n按指定的步长对数值进行加减127.0.0.1:6379> INCRBY age 5 (integer) 25 127.0.0.1:6379> DECRBY age 10 (integer) 15
INCRBYFLOAT key n 为数值加上浮点型数值127.0.0.1:6379> INCRBYFLOAT age 5.2 “20.2”
STRLEN key 获取key保存值的字符串长度127.0.0.1:6379> get msg “hello world” 127.0.0.1:6379> STRLEN msg (integer) 11
GETRANGE key start end按起止位置获取字符串(闭区间,起止位置都取)127.0.0.1:6379> get msg “hello world” 127.0.0.1:6379> GETRANGE msg 3 9 “lo worl”
SETRANGE key offset value用指定的value 替换key中 offset开始的值127.0.0.1:6379> SETRANGE msg 2 hello (integer) 7 127.0.0.1:6379> get msg “tehello”
GETSET key value将给定 key 的值设为 value ,并返回 key 的旧值(old value)。127.0.0.1:6379> GETSET msg test “hello world”
SETNX key value仅当key不存在时进行set127.0.0.1:6379> SETNX msg test (integer) 0 127.0.0.1:6379> SETNX name sakura (integer) 1
SETEX key seconds valueset 键值对并设置过期时间127.0.0.1:6379> setex name 10 root OK 127.0.0.1:6379> get name (nil)
MSET key1 value1 [key2 value2..] 批量set键值对127.0.0.1:6379> MSET k1 v1 k2 v2 k3 v3 OK
MSETNX key1 value1 [key2 value2..] 批量设置键值对,仅当参数中所有的key都不存在时执行127.0.0.1:6379> MSETNX k1 v1 k4 v4 (integer) 0
MGET key1 [key2..] 批量获取多个key保存的值127.0.0.1:6379> MGET k1 k2 k3 1) “v1” 2) “v2” 3) “v3”
PSETEX key milliseconds value 和 SETEX 命令相似,但它以毫秒为单位设置 key 的生存时间,
getset key value 如果不存在值,则返回nil,如果存在值,获取原来的值,并设置新的值
###########################################################################
# i++
# 步长 i+=
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
(integer) 1
127.0.0.1:6379> incr views  # 自增1  浏览量变为1
(integer) 2
127.0.0.1:6379> get views 
"2"
127.0.0.1:6379> decr views  # 自减1  浏览量-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> decrby views 5    # 可以设置步长,指定减量!
(integer) 5
127.0.0.1:6379> get views 
"5"
###########################################################################

# 字符串范围 range
127.0.0.1:6379> set key1 hello   # 设置 key1 的值
OK
127.0.0.1:6379> get key1
"hello"
127.0.0.1:6379> getrange key1 0 3  # 截取字符串 [0,3]
"hell"
127.0.0.1:6379> getrange key1 0 -1  # 获取全部的字符串 和 get key 是一样的
"hello"

# 替换
127.0.0.1:6379> set key2 abcdefg
OK
127.0.0.1:6379> get key2
"abcdefg"
127.0.0.1:6379> setrange key2 1 xx   # 替换指定位置开始的字符串
(integer) 7
127.0.0.1:6379> get key2
"axxdefg"

###########################################################################

# 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) 27
127.0.0.1:6379> ttl key3
(integer) 18
127.0.0.1:6379> get key3
"hello"
127.0.0.1:6379> setnx mykey "redis"  # 如果mykey不存在,创建mykey
(integer) 1
127.0.0.1:6379> keys *
1) "key2"
2) "key1"
3) "mykey"
127.0.0.1:6379> setnx mykey "redis"  # 如果mykey存在,创建失败!
(integer) 0
127.0.0.1:6379> get mykey
"redis"

###########################################################################
mset
mget

127.0.0.1:6379> mset k1 v1 k2 v2 k3 v3  # 同时设置多个值
OK
127.0.0.1:6379> keys *
1) "k2"
2) "k1"
3) "k3"
127.0.0.1:6379> mget k1 k2 k3      # 同时获取多个值
1) "v1"
2) "v2"
3) "v3"
127.0.0.1:6379> msetnx k1 v1 k4 v4   # msetnx 是一个原子性的操作,要么一起成功,要么一起失败!
(integer) 0
127.0.0.1:6379> get k4
(nil)

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

# 这里的key是一个x巧妙的设计:user:{id}:{filed},如此设计在redis中是完全ok的
127.0.0.1:6379> mset user:1:name zhangsan user:1:age 2
OK
127.0.0.1:6379> mget user:1:name user:1:age
1) "zhangsan"
2) "2"

###########################################################################
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除了是字符串还可以是数字,用途举例:

  • 计数器
  • 统计多单位的数量:uid:123666:follow 0
  • 粉丝数
  • 对象存储缓存

List

在redis里面,可以把list玩成:栈、队列、阻塞队列
所有的list命令都是用l开头的

在这里插入图片描述

###########################################################################
Lpush 
Rpush 
Lrange 

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 right  # 将一个值或者多个值,插入到列表的头部(右)
(integer) 4
127.0.0.1:6379> lrange list 0 -1
1) "three"
2) "two"
3) "one"
4) "right"

###########################################################################
Lpop
Rpop

127.0.0.1:6379> LRANGE list 0 -1
1) "three"
2) "two"
3) "one"
4) "right"
127.0.0.1:6379> lpop list   # 移除list的第一个元素
"three"
127.0.0.1:6379> rpop list   # 移除list的最后一个元素
"right"


###########################################################################
Lindex

127.0.0.1:6379> lrange list 0 -1
1) "two"
2) "one"
127.0.0.1:6379> lindex list 1   # 通过下标获得list中的某一个值
"one"
127.0.0.1:6379> lindex list 0
"two"
########################################################################### 
Llen

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> lpush list three
(integer) 4
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> lrem list 2 three
(integer) 2
127.0.0.1:6379> lrange list 0 -1
1) "two"

########################################################################### 
# trim 修剪   list 截断!
127.0.0.1:6379> rpush mylist "hello"
(integer) 1
127.0.0.1:6379> rpush mylist "hello1"
(integer) 2
127.0.0.1:6379> rpush mylist "hello2"
(integer) 3
127.0.0.1:6379> rpush mylist "hello3"
(integer) 4
127.0.0.1:6379> ltrim mylist 1 2   # 通过下标截取指定的长度,这个list已经被改变了,截断了只剩下截取的元素
OK
127.0.0.1:6379> lrange mylist 0 -1
1) "hello1"
2) "hello2"

###########################################################################
rpoplpush # 移除列表的最后一个元素,将他移动到新的列表中

127.0.0.1:6379> rpush mylist "hello"
(integer) 1
127.0.0.1:6379> rpush mylist "hello1"
(integer) 2
127.0.0.1:6379> rpush mylist "hello2"
(integer) 3
127.0.0.1:6379> rpoplpush mylist myotherlist   # 移除列表的最后一个元素,将他移动到新的列表中
"hello2"
127.0.0.1:6379> lrange mylist 0 -1  # 查看原来的列表
1) "hello"
2) "hello1"
127.0.0.1:6379> lrange myotherlist 0 -1  # 查看目标列表中,确实存在改值
1) "hello2"


###########################################################################
lset # 将列表中的指定下标的值替换为另外一个值,更新操作

127.0.0.1:6379> exists list  # 判断这个列表是否存在
(integer) 0
127.0.0.1:6379> lset list 0 item  # 如果不存在列表我们去更新就会报错
(error) ERR no such key
127.0.0.1:6379> lpush list value1
(integer) 1
127.0.0.1:6379> lrange list 0 0
1) "value1"
127.0.0.1:6379> lset list 0 item  # 如果存在,更新当前下标的值
OK
127.0.0.1:6379> lrange list 0 0
1) "item"
127.0.0.1:6379> lset list 1 other  # 如果不存在,就会报错
(error) ERR index out of range

###########################################################################
Linsert # 将某个具体的value插入列表中你指定的某个元素的前面或者后面

127.0.0.1:6379> clear
127.0.0.1:6379> rpush mylist "hello"
(integer) 1
127.0.0.1:6379> rpush mylist "world"
(integer) 2
127.0.0.1:6379> linsert mylist before "world" "other"
(integer) 3
127.0.0.1:6379> lrange mylist 0 -1
1) "hello"
2) "other"
3) "world"
127.0.0.1:6379> linsert mylist after world new
(integer) 4
127.0.0.1:6379> lrange mylist 0 -1
1) "hello"
2) "other"
3) "world"
4) "new"

###########################################################################

小结

  • 他实际上是一个链表,before Node after,left,right都可以插入值
  • 如果key不存在,创建新的链表
  • 如果key存在,新增内容
  • 如果移除了所有的值,空链表,也代表不存在
  • 在两遍插入或者改动值,效率最高!修改中间元素,效率相对较低

应用:

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

Set(集合)

set中的值是不能重复的
在这里插入图片描述

###########################################################################
127.0.0.1:6379> sadd myset hello    # set集合中添加元素
(integer) 1
127.0.0.1:6379> sadd myset hello1
(integer) 1
127.0.0.1:6379> sadd myset hello2
(integer) 1
127.0.0.1:6379> smembers myset   # 查看指定set的所有值
1) "hello1"
2) "hello"
3) "hello2"
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) 4

###########################################################################
rem   

127.0.0.1:6379> srem myset hello    # 移除set集合中的指定元素
(integer) 1
127.0.0.1:6379> scard myset
(integer) 3
127.0.0.1:6379> smembers myset
1) "hello1"
2) "hello3"
3) "hello2"
127.0.0.1:6379> 



###########################################################################
127.0.0.1:6379> srandmember myset  # 随机抽选出一个元素
"hello2"
127.0.0.1:6379> srandmember myset
"hello1"
127.0.0.1:6379> srandmember myset
"hello2"
127.0.0.1:6379> srandmember myset 2  # 随机选出指定的元素
1) "hello3"
2) "hello2"
127.0.0.1:6379> srandmember myset 2
1) "hello1"
2) "hello3"


###########################################################################
随机删除key

127.0.0.1:6379> smembers myset
1) "hello1"
2) "hello3"
3) "hello2"
127.0.0.1:6379> spop myset  #随机删除一些set集合中的元素
"hello2"
127.0.0.1:6379> spop myset
"hello3"


###########################################################################
smove # 将一个指定的值,移动到另外一个set集合中

127.0.0.1:6379> sadd myset hello
(integer) 1
127.0.0.1:6379> sadd myset hello2
(integer) 1
127.0.0.1:6379> sadd myset hello3
(integer) 1
127.0.0.1:6379> sadd myset2 set2
(integer) 1
127.0.0.1:6379> smove myset myset2 hello3   # 将一个指定的值,移动到另外一个set集合中
(integer) 1
127.0.0.1:6379> smembers myset
1) "hello"
2) "hello2"
127.0.0.1:6379> smembers myset2
1) "set2"
2) "hello3"


###########################################################################
微博,B站,共同关注(交集)
数字集合类:
- 差集 SDIFF
- 交集 SINTER
- 并集 SUNION

127.0.0.1:6379> sadd key1 a
(integer) 1
127.0.0.1:6379> sadd key1 b
(integer) 1
127.0.0.1:6379> sadd key1 c
(integer) 1
127.0.0.1:6379> sadd key2 c
(integer) 1
127.0.0.1:6379> sadd key2 d
(integer) 1
127.0.0.1:6379> sadd key2 e
(integer) 1
127.0.0.1:6379> sdiff key1 key2 # 差集
1) "a"
2) "b"
127.0.0.1:6379> sinter key1 key2  # 交集
1) "c"
127.0.0.1:6379> sunion key 1 key2 # 并集
1) "d"
2) "c"
3) "e"
127.0.0.1:6379> sdiff key2 key1
1) "d"
2) "e"


###########################################################################

小结

微博,A用户将所有关注的人放在一个set集合中!将它的粉丝也放在一个集合中!
共同关注,共同爱好,二度好友,推荐好友(六度分割理论)

Hash(哈希)

Map集合,key-value 本质和String类型没有太大区别,还是一个简单的key-value
在这里插入图片描述

###########################################################################
127.0.0.1:6379> hset myhash field1 hello   # set一个具体key-value
(integer) 1
127.0.0.1:6379> hget myhash field1
"hello"
127.0.0.1:6379> hmset myhash field1 hello2 field2 world   # set多个具体的key-value
OK
127.0.0.1:6379> hmget myhash field1 field2
1) "hello2"
2) "world"
127.0.0.1:6379> hgetall myhash  # 获取全部的数据
1) "field1"
2) "hello2"
3) "field2"
4) "world"

127.0.0.1:6379> hdel myhash field1   # 删除hash指定key字段
(integer) 1
127.0.0.1:6379> hgetall myhash
1) "field2"
2) "world"

###########################################################################
hlen  #长度

127.0.0.1:6379> hmset myhash field1 hello field2 world
OK
127.0.0.1:6379> hgetall myhash
1) "field2"
2) "world"
3) "field1"
4) "hello"
127.0.0.1:6379> hlen myhash   # 获取hash表的字段数量
(integer) 2
 


###########################################################################
hexists

127.0.0.1:6379> hexists myhash field3  # 判断hash中指定字段是否存在
(integer) 0
127.0.0.1:6379> hexists myhash field1
(integer) 1


###########################################################################
# 只获得所有字段 field
# 只获取所有值 value
127.0.0.1:6379> hkeys myhash # 只获得所有字段 field
1) "field2"
2) "field1"
127.0.0.1:6379> hvals myhash # 只获取所有值 value
1) "world"
2) "hello"

###########################################################################
incr decr

127.0.0.1:6379> hset myhash field3 5  
(integer) 1
127.0.0.1:6379> hincrby myhash field3 1 # 指定增量
(integer) 6
127.0.0.1:6379> hincrby myhash field3 -1
(integer) 5
127.0.0.1:6379> hsetnx myhash field4 hello
(integer) 1
127.0.0.1:6379> hsetnx myhash field4 world
(integer) 0

###########################################################################

小结

hash变更的数据 user name age
尤其是用户信息之类的,经常变动的信息!hash更适合于对象的存储,String更加适合字符串存储

Zset(有序集合)

在set的基础上,增加了一个值
在这里插入图片描述

###########################################################################
zadd zrange

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> zrange myset 0 -1
1) "one"
2) "two"
3) "three"

###########################################################################
Zrangebyscore

127.0.0.1:6379> zadd salary 2500 xiaohong
(integer) 1
127.0.0.1:6379> zadd salary 5000 zhangsan
(integer) 1
127.0.0.1:6379> zadd salary 500 shiqi
(integer) 1
127.0.0.1:6379> zrangebyscore salary -inf +inf   # 显示全部用户,从小到大
1) "shiqi"
2) "xiaohong"
3) "zhangsan"
127.0.0.1:6379> zrevrange salary 0 -1  # 从大到小排序
1) "zhangsan"
2) "shiqi"

127.0.0.1:6379> zrangebyscore salary -inf +inf withscores  # 显示全部的用户并附带成绩
1) "shiqi"
2) "500"
3) "xiaohong"
4) "2500"
5) "zhangsan"
6) "5000"
127.0.0.1:6379> zrangebyscore salary -inf 2500 withscores  # 显示工资小于2500员工的升序排列
1) "shiqi"
2) "500"
3) "xiaohong"
4) "2500"


###########################################################################
# 移除rem中的元素

127.0.0.1:6379> zrange salary 0 -1
1) "shiqi"
2) "xiaohong"
3) "zhangsan"
127.0.0.1:6379> zrem salary xiaohong # 移除有序集合中指定元素
(integer) 1
127.0.0.1:6379> zrange salary 0 -1
1) "shiqi"
2) "zhangsan"
127.0.0.1:6379>  zcard salary  # 获取有序集合中的个数
(integer) 2


###########################################################################
zcount

127.0.0.1:6379> zadd myset 1 hello
(integer) 1
127.0.0.1:6379> zadd myset 2 world 3 shiqi
(integer) 2
127.0.0.1:6379> zcount myset 1 3  # 获取指定区间的成员数量
(integer) 3
127.0.0.1:6379> zcount myset 1 2
(integer) 2

###########################################################################
###########################################################################
###########################################################################
###########################################################################

小结

不同的是每个元素都会关联一个double类型的分数(score)。redis正是通过分数来为集合中的成员进行从小到大的排序。
score相同:按字典顺序排序
有序集合的成员是唯一的,但分数(score)却可以重复。

案例

set 排序 储存班级成绩单,工资表排序!
普通消息 ,1.重要消息 2.带权重进行判断!
排行版应用实现

四、三种特殊数据类型

geospatital 地理位置

朋友的定位,附近的人,打车距离计算?
Redis的Geo 在Redis3.2版本就推出了!这个功能可以推算地理位置的信息,两地之间的距离,方圆几里的人!

有效经纬度

  • 有效的经度从-180度到180度。
  • 有效的纬度从-85.05112878度到85.05112878度。

在这里插入图片描述

getadd

# getadd 添加地理位置
# 规则:两级无法直接添加,我们一般会下载城市数据,直接通过java导入

# 参数 key 值(经度、纬度、姓名)

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
(integer) 1
127.0.0.1:6379> geoadd china:city 106.50 29.53 chongqing
(integer) 1
127.0.0.1:6379> geoadd china:city 114.05 22.52 shenzhen
(integer) 1
127.0.0.1:6379> geoadd china:city 120.16 30.24 hangzhou
(integer) 1
127.0.0.1:6379> geoadd china:city 108.96 34.26 xian
(integer) 1

getpos

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

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

GEODIST

指定单位的参数 unit 必须是以下单位的其中一个:

  • m 表示单位为米。
  • km 表示单位为千米。
  • mi 表示单位为英里。
  • ft 表示单位为英尺。
127.0.0.1:6379> geodist china:city beijing shanghai
"1067378.7564"
127.0.0.1:6379> geodist china:city beijing shanghai km
"1067.3788"

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

我附近的人?(获取所有附近的人的地址,定位)
获得指定数量

127.0.0.1:6379> GEORADIUS china:city 110 30 1000 km
1) "chongqing"
2) "xian"
3) "shenzhen"
4) "hangzhou"
127.0.0.1:6379> GEORADIUS china:city 110 30 500 km
1) "chongqing"
2) "xian"

georadiusbymember

# 找出位于指定元素周围的其他元素
127.0.0.1:6379> GEORADIUSBYMEMBER china:city beijing 1000 km
1) "beijing"
2) "xian"

geohash 命令 - 返回一个或多个位置元素的 Geohash表示

在这里插入图片描述

GEO 底层的实现原理其实就是Zset!我们可以用zset命令来操作geo

Hyperloglog

简介

Redis 2.8.9版本就更新了Hyperloglog数据结构
Redis Hyperloglog 基数统计的算法!

什么是基数?

A{1,3,5,7,8,7}
B{1,3,5,7,8}
基数(不重复的元素) = 5 可以接受误差

优点:

  • 在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定的、并且是很小的。
  • 花费 12 KB 内存,就可以计算接近 2^64 个不同元素的基数。
  • 因为 HyperLogLog 只会根据输入元素来计算基数,而不会储存输入元素本身,所以 HyperLogLog 不能像集合那样,返回输入的各个元素。
  • 其底层使用string数据类型

应用场景:

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

传统实现,存储用户的id,然后每次进行比较。当用户变多之后这种方式及其浪费空间,而我们的目的只是计数,Hyperloglog就能帮助我们利用最小的空间完成。

测试使用

127.0.0.1:6379> pfadd mykey a b c d e f g h i j # 创建第一组元素 mykey
(integer) 1
127.0.0.1:6379> pfcount mykey  # 统计 mykey 元素的基数数量
(integer) 10
127.0.0.1:6379> pfadd mykey2 i j z x c v 
(integer) 1
127.0.0.1:6379> pfcount mykey2
(integer) 6
127.0.0.1:6379> pfmerge mykey3 mykey2 mykey  # 合并两组 求并集
OK
127.0.0.1:6379> pfcount mykey3  # 看并集数量
(integer) 13

如果允许容错,那么一定可以使用Hyperloglog !

如果不允许容错,就使用set或者自己的数据类型即可 !

Bitmap

概念

位存储
Bitmaps位图,数据结构,都是操作二进制位来进行记录,只有0和1两个状态!
365天 = 365bit 1字节=8bit 46个字节左右

应用场景

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

命令

在这里插入图片描述

------------setbit--getbit--------------
127.0.0.1:6379> setbit sign 0 1 # 设置sign的第0位为 1 
(integer) 0
127.0.0.1:6379> setbit sign 2 1 # 设置sign的第2位为 1  不设置默认 是0
(integer) 0
127.0.0.1:6379> setbit sign 3 1
(integer) 0
127.0.0.1:6379> setbit sign 5 1
(integer) 0
127.0.0.1:6379> type sign
string

127.0.0.1:6379> getbit sign 2 # 获取第2位的数值
(integer) 1
127.0.0.1:6379> getbit sign 3
(integer) 1
127.0.0.1:6379> getbit sign 4 # 未设置默认是0
(integer) 0

-----------bitcount----------------------------
127.0.0.1:6379> BITCOUNT sign # 统计sign中为1的位数
(integer) 4

五、事务

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

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

  • 一次性
  • 顺序性
  • 排他性

1、Redis事务没有隔离级别
2、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 k1
QUEUED
127.0.0.1:6379> set k3 v3
QUEUED
127.0.0.1:6379> keys *
QUEUED
127.0.0.1:6379> exec # 事务执行
1) OK
2) OK
3) "v1"
4) OK
5) 1) "k3"
   2) "k2"
   3) "k1"

取消事务(discurd)

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> DISCARD # 放弃事务
OK
127.0.0.1:6379> EXEC 
(error) ERR EXEC without MULTI # 当前未开启事务
127.0.0.1:6379> get k1 # 被放弃事务中命令并未执行
(nil)

事务错误

代码语法错误(编译时异常)所有的命令都不执行

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> error k1 # 这是一条语法错误命令
(error) ERR unknown command `error`, with args beginning with: `k1`, # 会报错但是不影响后续命令入队 
127.0.0.1:6379> get k2
QUEUED
127.0.0.1:6379> EXEC
(error) EXECABORT Transaction discarded because of previous errors. # 执行报错
127.0.0.1:6379> get k1 
(nil) # 其他命令并没有被执行

代码逻辑错误 (运行时异常) **其他命令可以正常执行 ** >>> 所以不保证事务原子性

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> INCR k1 # 这条命令逻辑错误(对字符串进行增量)
QUEUED
127.0.0.1:6379> get k2
QUEUED
127.0.0.1:6379> exec
1) OK
2) OK
3) (error) ERR value is not an integer or out of range # 运行时报错
4) "v2" # 其他命令正常执行

# 虽然中间有一条命令报错了,但是后面的指令依旧正常执行成功了。
# 所以说Redis单条指令保证原子性,但是Redis事务不能保证原子性。

监控

悲观锁:

  • 很悲观,认为什么时候都会出现问题,无论做什么都会加锁
    乐观锁:
  • 很乐观,认为什么时候都不会出现问题,所以不会上锁!更新数据的时候去判断一下,在此期间是否有人修改过这个数据
  • 获取version
  • 更新的时候比较version
    Redis使用watch key监控指定数据,相当于乐观锁加锁。

正常执行

127.0.0.1:6379> set money 100 # 设置余额:100
OK
127.0.0.1:6379> set use 0 # 支出使用: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 use 20
QUEUED
127.0.0.1:6379> exec # 监视值没有被中途修改,事务正常执行
1) (integer) 80
2) (integer) 20

测试多线程修改值,使用watch可以当做redis的乐观锁操作(相当于getversion)

我们启动另外一个客户端模拟插队线程。

线程1:

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 use 20
QUEUED
127.0.0.1:6379> 	# 此时事务并没有执行

模拟线程插队,线程2:

127.0.0.1:6379> INCRBY money 500 # 修改了线程一中监视的money
(integer) 600

回到线程1,执行事务:

127.0.0.1:6379> EXEC # 执行之前,另一个线程修改了我们的值,这个时候就会导致事务执行失败
(nil) # 没有结果,说明事务执行失败

127.0.0.1:6379> get money # 线程2 修改生效
"600"
127.0.0.1:6379> get use # 线程1事务执行失败,数值没有被修改
"0"

解锁获取最新值,然后再加锁进行事务。
unwatch进行解锁。

注意:每次提交执行exec后都会自动释放锁,不管是否成功

六、Jedis

是什么

Jedis是官方推荐的java连接开发工具,是用java操作redis的中间件

编码测试:

  • 连接数据库
  • 操作命令
  • 断开连接
package com.shiqi;

import redis.clients.jedis.Jedis;

/**
 * @program: shiqi-redis
 * @description:
 * @author: 翁仕权
 * @create: 2020-09-21 21:34
 */
public class TestPing {
    public static void main(String[] args) {
        // 1. new Jedis对象即可
        Jedis jedis = new Jedis("127.0.0.1",6379);
        System.out.println(jedis.ping());

    }
}

常用的API

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

事务

public class TestTX {
    public static void main(String[] args) {
        Jedis jedis = new Jedis("39.99.xxx.xx", 6379);

        JSONObject jsonObject = new JSONObject();
        jsonObject.put("hello", "world");
        jsonObject.put("name", "kuangshen");
        // 开启事务
        Transaction multi = jedis.multi();
        String result = jsonObject.toJSONString();
        // jedis.watch(result)
        try {
            multi.set("user1", result);
            multi.set("user2", result);
            // 执行事务
            multi.exec();
        }catch (Exception e){
            // 放弃事务
            multi.discard();
        } finally {
            // 关闭连接
            System.out.println(jedis.get("user1"));
            System.out.println(jedis.get("user2"));
            jedis.close();
        }
    }
}

SpringBoot整合

1、导入依赖

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

springboot 2.x后 ,原来使用的 Jedis 被 lettuce 替换。

jedis:采用的直连,多个线程操作的话,是不安全的。如果要避免不安全,使用jedis pool连接池!更像BIO模式
lettuce:采用netty,实例可以在多个线程中共享,不存在线程不安全的情况!可以减少线程数据了,更像NIO模式

我们在学习SpringBoot自动配置的原理时,整合一个组件并进行配置一定会有一个自动配置类xxxAutoConfiguration,并且在spring.factories中也一定能找到这个类的完全限定名。Redis也不例外。

在这里插入图片描述
那么就一定还存在一个RedisProperties类
在这里插入图片描述
之前我们说SpringBoot2.x后默认使用Lettuce来替换Jedis,现在我们就能来验证了。

先看Jedis:
在这里插入图片描述
@ConditionalOnClass注解中有两个类是默认不存在的,所以Jedis是无法生效的

然后再看Lettuce:
在这里插入图片描述
完美生效。

现在我们回到RedisAutoConfiguratio

在这里插入图片描述
只有两个简单的Bean

  • RedisTemplate
  • StringRedisTemplate
    当看到xxTemplate时可以对比RestTemplat、SqlSessionTemplate,通过使用这些Template来间接操作组件。那么这俩也不会例外。分别用于操作Redis和Redis中的String数据类型。

在RedisTemplate上也有一个条件注解,说明我们是可以对其进行定制化的

说完这些,我们需要知道如何编写配置文件然后连接Redis,就需要阅读RedisProperties
在这里插入图片描述
这是一些基本的配置属性。
在这里插入图片描述
还有一些连接池相关的配置。注意使用时一定使用Lettuce的连接池

在这里插入图片描述
2、编写配置文件

# 配置redis
spring.redis.host=39.99.xxx.xx
spring.redis.port=6379

3、使用RedisTemplate

@SpringBootTest
class Redis02SpringbootApplicationTests {

    @Autowired
    private RedisTemplate redisTemplate;

    @Test
    void contextLoads() {

        // redisTemplate 操作不同的数据类型,api和我们的指令是一样的
        // opsForValue 操作字符串 类似String
        // opsForList 操作List 类似List
        // opsForHah

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

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

        redisTemplate.opsForValue().set("mykey","kuangshen");
        System.out.println(redisTemplate.opsForValue().get("mykey"));
    }
}

4、测试结果
此时我们回到Redis查看数据时候,惊奇发现全是乱码,可是程序中可以正常输出:
在这里插入图片描述
这时候就关系到存储对象的序列化问题,在网络中传输的对象也是一样需要序列化,否者就全是乱码。

我们转到看那个默认的RedisTemplate内部什么样子:
在这里插入图片描述
在最开始就能看到几个关于序列化的参数。

默认的序列化器是采用JDK序列化器
在这里插入图片描述
而默认的RedisTemplate中的所有序列化器都是使用这个序列化器:
在这里插入图片描述
后续我们定制RedisTemplate就可以对其进行修改。

RedisSerializer提供了多种序列化方案:
1、直接调用RedisSerializer的静态方法来返回序列化器,然后set
在这里插入图片描述
2、自己new 相应的实现类,然后set
在这里插入图片描述
5、定制RedisTemplate的模板:
我们创建一个Bean加入容器,就会触发RedisTemplate上的条件注解使默认的RedisTemplate失效。

@Configuration
public class RedisConfig {

   @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
        // 将template 泛型设置为 <String, Object>
        RedisTemplate<String, Object> template = new RedisTemplate();
        // 连接工厂,不必修改
        template.setConnectionFactory(redisConnectionFactory);
        /*
         * 序列化设置
         */
        // key、hash的key 采用 String序列化方式
        template.setKeySerializer(RedisSerializer.string());
        template.setHashKeySerializer(RedisSerializer.string());
        // value、hash的value 采用 Jackson 序列化方式
        template.setValueSerializer(RedisSerializer.json());
        template.setHashValueSerializer(RedisSerializer.json());
        template.afterPropertiesSet();
        
        return template;
    }
}

这样一来,只要实体类进行了序列化,我们存什么都不会有乱码的担忧了。

八、自定义Redis工具类

使用RedisTemplate需要频繁调用.opForxxx然后才能进行对应的操作,这样使用起来代码效率低下,工作中一般不会这样使用,而是将这些常用的公共API抽取出来封装成为一个工具类,然后直接使用工具类来间接操作Redis,不但效率高并且易用。

工具类参考博客:

https://www.cnblogs.com/zeng1994/p/03303c805731afc9aa9c60dbbd32a323.html

https://www.cnblogs.com/zhzhlong/p/11434284.html

九、Redis.conf

容量单位不区分大小写,G和GB有区别

在这里插入图片描述

可以使用 include 组合多个配置问题

在这里插入图片描述

网络配置

在这里插入图片描述

日志输出级别
在这里插入图片描述

日志输出文件

在这里插入图片描述

持久化规则

由于Redis是基于内存的数据库,需要将数据由内存持久化到文件中

持久化方式:

  • RDB
  • AOF

在这里插入图片描述

RDB文件相关

在这里插入图片描述
在这里插入图片描述

主从复制

在这里插入图片描述

Security模块中进行密码设置

在这里插入图片描述

客户端连接相关

maxclients 10000  最大客户端数量
maxmemory <bytes> 最大内存限制
maxmemory-policy noeviction # 内存达到限制值的处理策略

redis 中的默认的过期策略是 volatile-lru 。
设置方式

config set maxmemory-policy volatile-lru 

maxmemory-policy 六种方式
**1、volatile-lru:**只对设置了过期时间的key进行LRU(默认值)

2、allkeys-lru : 删除lru算法的key

**3、volatile-random:**随机删除即将过期key

**4、allkeys-random:**随机删除

5、volatile-ttl : 删除即将过期的

6、noeviction : 永不过期,返回错误

AOF相关部分

在这里插入图片描述

在这里插入图片描述

十、持久化—RDB

RDB:Redis Databases

什么是RDB

在指定时间间隔后,将内存中的数据集快照写入数据库 ;在恢复时候,直接读取快照文件,进行数据的恢复 ;
在这里插入图片描述
默认情况下, Redis 将数据库快照保存在名字为 dump.rdb的二进制文件中。文件名可以在配置文件中进行自定义。

工作原理

在进行 RDB 的时候,redis 的主线程是不会做 io 操作的,主线程会 fork 一个子线程来完成该操作;

  1. Redis 调用forks。同时拥有父进程和子进程。
  2. 子进程将数据集写入到一个临时 RDB 文件中。
  3. 当子进程完成对新 RDB 文件的写入时,Redis 用新 RDB 文件替换原来的 RDB 文件,并删除旧的 RDB 文件。

这种工作方式使得 Redis 可以从写时复制(copy-on-write)机制中获益(因为是使用子进程进行写操作,而父进程依然可以接收来自客户端的请求。)

在这里插入图片描述

触发机制

  1. save的规则满足的情况下,会自动触发rdb原则
  2. 执行flushall命令,也会触发我们的rdb原则
  3. 退出redis,也会自动产生rdb文件

save

使用 save 命令,会立刻对当前内存中的数据进行持久化 ,但是会阻塞,也就是不接受其他操作了;

由于 save 命令是同步命令,会占用Redis的主进程。若Redis数据非常多时,save命令执行速度会非常慢,阻塞所有客户端的请求。

在这里插入图片描述
flushall命令
flushall命令也会触发持久化 ;
触发持久化规则
满足配置条件中的触发条件 ;

可以通过配置文件对 Redis 进行设置, 让它在“ N 秒内数据集至少有 M 个改动”这一条件被满足时, 自动进行数据集保存操作。
在这里插入图片描述

在这里插入图片描述

优缺点

优点:

  1. 适合大规模的数据恢复
  2. 对数据的完整性要求不高

缺点:

  1. 需要一定的时间间隔进行操作,如果redis意外宕机了,这个最后一次修改的数据就没有了。
  2. fork进程的时候,会占用一定的内容空间。

十一、持久化AOF

Append Only File

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

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

什么是AOF

​ 快照功能(RDB)并不是非常耐久(durable): 如果 Redis 因为某些原因而造成故障停机, 那么服务器将丢失最近写入、以及未保存到快照中的那些数据。 从 1.1 版本开始, Redis 增加了一种完全耐久的持久化方式: AOF 持久化。

如果要使用AOF,需要修改配置文件:
在这里插入图片描述
appendonly no yes则表示启用AOF

默认是不开启的,我们需要手动配置,然后重启redis,就可以生效了!

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

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

优点和缺点

appendonly yes  # 默认是不开启aof模式的,默认是使用rdb方式持久化的,在大部分的情况下,rdb完全够用
appendfilename "appendonly.aof"

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

优点

  1. 每一次修改都会同步,文件的完整性会更加好
  2. 没秒同步一次,可能会丢失一秒的数据
  3. 从不同步,效率最高

缺点

  1. 相对于数据文件来说,aof远远大于rdb,修复速度比rdb慢!
  2. Aof运行效率也要比rdb慢,所以我们redis默认的配置就是rdb持久化

十二、RDB和AOP选择

RDB 和 AOF 对比

在这里插入图片描述

如何选择使用哪种持久化方式?

一般来说, 如果想达到足以媲美 PostgreSQL 的数据安全性, 你应该同时使用两种持久化功能。

如果你非常关心你的数据, 但仍然可以承受数分钟以内的数据丢失, 那么你可以只使用 RDB 持久化。

有很多用户都只使用 AOF 持久化, 但并不推荐这种方式: 因为定时生成 RDB 快照(snapshot)非常便于进行数据库备份, 并且 RDB 恢复数据集的速度也要比 AOF 恢复的速度要快。

十三、Redis发布与订阅

什么是发布与订阅

Redis 发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息。微信、微博、关注系统!
Redis 客户端可以订阅任意数量的频道。

订阅/发布消息图:

第一个:消息发送者, 第二个:频道 第三个:消息订阅者!
在这里插入图片描述
下图展示了频道 channel1 , 以及订阅这个频道的三个客户端 —— client2 、 client5 和 client1 之间的
关系:
在这里插入图片描述
当有新消息通过 PUBLISH 命令发送给频道 channel1 时, 这个消息就会被发送给订阅它的三个客户
端:

在这里插入图片描述

命令

这些命令被广泛用于构建即时通信应用,比如网络聊天室(chatroom)和实时广播、实时提醒等。
在这里插入图片描述

测试

------------订阅端----------------------
127.0.0.1:6379> SUBSCRIBE sakura # 订阅sakura频道
Reading messages... (press Ctrl-C to quit) # 等待接收消息
1) "subscribe" # 订阅成功的消息
2) "sakura"
3) (integer) 1
1) "message" # 接收到来自sakura频道的消息 "hello world"
2) "sakura"
3) "hello world"
1) "message" # 接收到来自sakura频道的消息 "hello i am sakura"
2) "sakura"
3) "hello i am sakura"

--------------消息发布端-------------------
127.0.0.1:6379> PUBLISH sakura "hello world" # 发布消息到sakura频道
(integer) 1
127.0.0.1:6379> PUBLISH sakura "hello i am sakura" # 发布消息
(integer) 1

-----------------查看活跃的频道------------
127.0.0.1:6379> PUBSUB channels
1) "sakura"

原理

Redis 通过 PUBLISH 、SUBSCRIBE 和 PSUBSCRIBE 等命令实现发布和订阅功能。
微信:
通过 SUBSCRIBE 命令订阅某频道后,redis-server 里维护了一个字典,字典的键就是一个个 频道!,
而字典的值则是一个链表,链表中保存了所有订阅这个 channel 的客户端。SUBSCRIBE 命令的关键,
就是将客户端添加到给定 channel 的订阅链表中。
在这里插入图片描述
通过 PUBLISH 命令向订阅者发送消息,redis-server 会使用给定的频道作为键,在它所维护的 channel
字典中查找记录了订阅这个频道的所有客户端的链表,遍历这个链表,将消息发布给所有订阅者

Pub/Sub 从字面上理解就是发布(Publish)与订阅(Subscribe),在Redis中,你可以设定对某一个
key值进行消息发布及消息订阅,当一个key值上进行了消息发布后,所有订阅它的客户端都会收到相应
的消息。这一功能最明显的用法就是用作实时消息系统,比如普通的即时聊天,群聊等功能。

使用场景:

1、实时消息系统!
2、事实聊天!(频道当做聊天室,将信息回显给所有人即可!)
3、订阅,关注系统都是可以的!
稍微复杂的场景我们就会使用 消息中间件 MQ ()

Redis主从复制

概念


主从复制,是指将一台Redis服务器的数据,复制到其他的Redis服务器。前者称为主节点
(master/leader),后者称为从节点(slave/follower);数据的复制是单向的,只能由主节点到从节点
Master以写为主,Slave 以读为主。
默认情况下,每台Redis服务器都是主节点
且一个主节点可以有多个从节点(或没有从节点),但一个从节点只能有一个主节点。

作用

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

为什么使用集群

  1. 单台服务器难以负载大量的请求
  2. 单台服务器故障率高,系统崩坏概率大
  3. 单台服务器内存容量有限。

在这里插入图片描述

主从复制,读写分离! 80% 的情况下都是在进行读操作!减缓服务器的压力!架构中经常使用! 一主
二从!
只要在公司中,主从复制就是必须要使用的,因为在真实的项目中不可能单机使用Redis

环境配置

我们在讲解配置文件的时候,注意到有一个replication模块 (见Redis.conf中第8条)

查看当前库的信息:info replication

127.0.0.1:6379> info replication
# Replication
role:master # 角色
connected_slaves:0 # 从机数量
master_replid:3b54deef5b7b7b7f7dd8acefa23be48879b4fcff
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

既然需要启动多个服务,就需要多个配置文件。每个配置文件对应修改以下信息:

  • 端口号
  • pid文件名
  • 日志文件名
  • rdb文件名

启动单机多服务集群:
在这里插入图片描述

一主二从配置

默认情况下,每台Redis服务器都是主节点我们一般情况下只用配置从机就好了!

认老大!一主(79)二从(80,81)

使用SLAVEOF host port就可以为从机配置主机了。
在这里插入图片描述
然后主机上也能看到从机的状态:在这里插入图片描述
我们这里是使用命令搭建,是暂时的,真实开发中应该在从机的配置文件中进行配置这样的话是永久的。
在这里插入图片描述

使用规则

  1. 从机只能读,不能写,主机可读可写但是多用于写。
 127.0.0.1:6381> set name sakura # 从机6381写入失败
(error) READONLY You can't write against a read only replica.

127.0.0.1:6380> set name sakura # 从机6380写入失败
(error) READONLY You can't write against a read only replica.

127.0.0.1:6379> set name sakura
OK
127.0.0.1:6379> get name
"sakura"
  1. 当主机断电宕机后,默认情况下从机的角色不会发生变化 ,集群中只是失去了写操作,当主机恢复以后,又会连接上从机恢复原状。
  2. 当从机断电宕机后,若不是使用配置文件配置的从机,再次启动后作为主机是无法获取之前主机的数据的,若此时重新配置称为从机,又可以获取到主机的所有数据。这里就要提到一个同步原理。
  3. 第二条中提到,默认情况下,主机故障后,不会出现新的主机,有两种方式可以产生新的主机:
    1. 从机手动执行命令slaveof no one,这样执行以后从机会独立出来成为一个主机
    2. 使用哨兵模式(自动选举)

复制原理

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

  1. 全量复制:而slave服务在接收到数据库文件数据后,将其存盘并加载到内存中。
  2. 增量复制:Master 继续将新的所有收集到的修改命令依次传给slave,完成同步
    但是只要是重新连接master,一次完全同步(全量复制)将被自动执行! 我们的数据一定可以在从机中看到!

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

谋朝篡位

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

十五、哨兵模式

概念

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

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

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

单哨兵模式

在这里插入图片描述
这里的哨兵有两个作用

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

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

多哨兵模式

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

测试

redis-sentinel xxx/sentinel.conf
成功启动哨兵模式
在这里插入图片描述
此时哨兵监视着我们的主机6379,当我们断开主机后:
在这里插入图片描述

优缺点

优点:

  • 哨兵集群,基于主从复制模式,所有主从复制的优点,它都有
  • 主从可以切换,故障可以转移,系统的可用性更好
  • 哨兵模式是主从模式的升级,手动到自动,更加健壮

缺点:

  • Redis不好在线扩容,集群容量一旦达到上限,在线扩容就十分麻烦
  • 实现哨兵模式的配置其实是很麻烦的,里面有很多配置项

哨兵模式的全部配置

完整的哨兵模式配置文件 sentinel.conf

# Example sentinel.conf
 
# 哨兵sentinel实例运行的端口 默认26379
port 26379
 
# 哨兵sentinel的工作目录
dir /tmp
 
# 哨兵sentinel监控的redis主节点的 ip port 
# master-name  可以自己命名的主节点名字 只能由字母A-z、数字0-9 、这三个字符".-_"组成。
# quorum 当这些quorum个数sentinel哨兵认为master主节点失联 那么这时 客观上认为主节点失联了
# sentinel monitor <master-name> <ip> <redis-port> <quorum>
sentinel monitor mymaster 127.0.0.1 6379 1
 
# 当在Redis实例中开启了requirepass foobared 授权密码 这样所有连接Redis实例的客户端都要提供密码
# 设置哨兵sentinel 连接主从的密码 注意必须为主从设置一样的验证密码
# sentinel auth-pass <master-name> <password>
sentinel auth-pass mymaster MySUPER--secret-0123passw0rd
 
 
# 指定多少毫秒之后 主节点没有应答哨兵sentinel 此时 哨兵主观上认为主节点下线 默认30秒
# sentinel down-after-milliseconds <master-name> <milliseconds>
sentinel down-after-milliseconds mymaster 30000
 
# 这个配置项指定了在发生failover主备切换时最多可以有多少个slave同时对新的master进行 同步,
这个数字越小,完成failover所需的时间就越长,
但是如果这个数字越大,就意味着越 多的slave因为replication而不可用。
可以通过将这个值设为 1 来保证每次只有一个slave 处于不能处理命令请求的状态。
# sentinel parallel-syncs <master-name> <numslaves>
sentinel parallel-syncs mymaster 1
 
 
 
# 故障转移的超时时间 failover-timeout 可以用在以下这些方面: 
#1. 同一个sentinel对同一个master两次failover之间的间隔时间。
#2. 当一个slave从一个错误的master那里同步数据开始计算时间。直到slave被纠正为向正确的master那里同步数据时。
#3.当想要取消一个正在进行的failover所需要的时间。  
#4.当进行failover时,配置所有slaves指向新的master所需的最大时间。不过,即使过了这个超时,slaves依然会被正确配置为指向master,但是就不按parallel-syncs所配置的规则来了
# 默认三分钟
# sentinel failover-timeout <master-name> <milliseconds>
sentinel failover-timeout mymaster 180000
 
# SCRIPTS EXECUTION
 
#配置当某一事件发生时所需要执行的脚本,可以通过脚本来通知管理员,例如当系统运行不正常时发邮件通知相关人员。
#对于脚本的运行结果有以下规则:
#若脚本执行后返回1,那么该脚本稍后将会被再次执行,重复次数目前默认为10
#若脚本执行后返回2,或者比2更高的一个返回值,脚本将不会重复执行。
#如果脚本在执行过程中由于收到系统中断信号被终止了,则同返回值为1时的行为相同。
#一个脚本的最大执行时间为60s,如果超过这个时间,脚本将会被一个SIGKILL信号终止,之后重新执行。
 
#通知型脚本:当sentinel有任何警告级别的事件发生时(比如说redis实例的主观失效和客观失效等等),将会去调用这个脚本,
#这时这个脚本应该通过邮件,SMS等方式去通知系统管理员关于系统不正常运行的信息。调用该脚本时,将传给脚本两个参数,
#一个是事件的类型,
#一个是事件的描述。
#如果sentinel.conf配置文件中配置了这个脚本路径,那么必须保证这个脚本存在于这个路径,并且是可执行的,否则sentinel无法正常启动成功。
#通知脚本
# sentinel notification-script <master-name> <script-path>
  sentinel notification-script mymaster /var/redis/notify.sh
 
# 客户端重新配置主节点参数脚本
# 当一个master由于failover而发生改变时,这个脚本将会被调用,通知相关的客户端关于master地址已经发生改变的信息。
# 以下参数将会在调用脚本时传给脚本:
# <master-name> <role> <state> <from-ip> <from-port> <to-ip> <to-port>
# 目前<state>总是“failover”,
# <role>是“leader”或者“observer”中的一个。 
# 参数 from-ip, from-port, to-ip, to-port是用来和旧的master和新的master(即旧的slave)通信的
# 这个脚本应该是通用的,能被多次调用,不是针对性的。
# sentinel client-reconfig-script <master-name> <script-path>
sentinel client-reconfig-script mymaster /var/redis/reconfig.sh

十六、缓存穿透与雪崩

缓存穿透(查不到)

概念

在默认情况下,用户请求数据时,会先在缓存(Redis)中查找,若没找到即缓存未命中,再在数据库中进行查找,数量少可能问题不大,可是一旦大量的请求数据(例如秒杀场景)缓存都没有命中的话,就会全部转移到数据库上,造成数据库极大的压力,就有可能导致数据库崩溃。网络安全中也有人恶意使用这种手段进行攻击被称为洪水攻击。

解决方案

布隆过滤器
对所有可能查询的参数以Hash的形式存储,以便快速确定是否存在这个值,在控制层先进行拦截校验,校验不通过直接打回,减轻了存储系统的压力。
在这里插入图片描述
缓存空对象
一次请求若在缓存和数据库中都没找到,就在缓存中方一个空对象用于处理后续这个请求。
在这里插入图片描述
这样做有一个缺陷:存储空对象也需要空间,大量的空对象会耗费一定的空间,存储效率并不高。解决这个缺陷的方式就是设置较短过期时间

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

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

概念

相较于缓存穿透,缓存击穿的目的性更强,一个存在的key,在缓存过期的一刻,同时有大量的请求,这些请求都会击穿到DB,造成瞬时DB请求量大、压力骤增。这就是缓存被击穿,只是针对其中某个key的缓存不可用而导致击穿,但是其他的key依然可以使用缓存响应。

​ 比如热搜排行上,一个热点新闻被同时大量访问就可能导致缓存击穿。

解决方案

  1. 设置热点数据永不过期

    这样就不会出现热点数据过期的情况,但是当Redis内存空间满的时候也会清理部分数据,而且此种方案会占用空间,一旦热点数据多了起来,就会占用部分空间。

  2. 加互斥锁(分布式锁)

    在访问key之前,采用SETNX(set if not exists)来设置另一个短期key来锁住当前key的访问,访问结束再删除该短期key。保证同时刻只有一个线程访问。这样对锁的要求就十分高。

缓存雪崩

概念

大量的key设置了相同的过期时间,导致在缓存在同一时刻全部失效,造成瞬时DB请求量大、压力骤增,引起雪崩。
在这里插入图片描述

解决方案

  • redis高可用

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

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

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

整理的常用命令

#启动redis
redis-server [配置文件路径]
#进入redis
redis-cli -p [端口号]    #端口号默认6379
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值