新redis

根据哔哩哔哩 狂神说讲解视频记录笔记,并添加自己的一些理解

1. 关系数据库简介

https://aws.amazon.com/cn/relational-database/

关系数据库是数据项之间具有预定义关系的数据项的集合。这些项被组织为一组具有列和行的表。表用于保存有关要在数据库中表示的对象的信息。表中的每列都保存着特定类型的数据,字段存储着属性的实际值。表中的行表示一个对象或实体的相关值的集合。表中的每一行可标有一个称为主键的唯一标识符,并且可使用外键在多个表中的行之间建立关联。可以通过许多不同的方式访问此数据,而无需重新组织数据库表本身。

SQL

SQL(结构化查询语言)是用于与关系数据库通信的主要接口。SQL 于 1986 年成为美国国家标准协会 (ANSI) 的标准。流行的所有关系数据库引擎都支持标准的 ANSI SQL,其中一些引擎还对 ANSI SQL 进行了扩展,可支持特定于该引擎的功能。SQL 可用于添加、更新或删除数据行,检索事务处理和分析应用程序的数据子集,以及管理数据库的所有方面。

数据完整性

数据完整性指的是数据的整体完整性、准确性和一致性。关系数据库使用一组约束来强制确保数据库中数据的完整性。它们包括主键、外键、“Not NULL”约束、“Unique”约束、“Default”约束和“Check”约束。这些完整性约束有助于在表中的数据上强制实施业务规则,以确保数据的准确性和可靠性。除此之外,大多数关系数据库还允许将自定义代码嵌入到基于数据库上的操作执行的触发器中。

事务处理

数据库事务是作为构成单个逻辑工作单元的操作序列执行的一个或多个 SQL 语句。事务提供了一个“全是或全否”命题,这意味着整个事务必须作为一个单元完成并写入数据库,否则事务的各个组成部分都不应执行。在关系数据库术语中,事务会导致 COMMIT 或 ROLLBACK。每个事务以连贯和可靠的方式独立于其他事务进行处理

ACID合规性

所有数据库事务都必须遵守 ACID,即必须是原子的、一致的、隔离的和持久的,以确保数据的完整性。

  • 原子性要求事务作为一个整体成功执行,如果事务的任一部分执行失败,则整个事务都将无效。
  • 一致性规定作为事务一部分写入数据库的数据必须遵守所有定义的规则及限制,后者包括约束、级联和触发器。

  • 隔离对于实现并发控制至关重要,它确保每个事务对其自身是独立的。

  • 持久性要求在成功完成事务后,对数据库所做的所有更改都是永久性的。

2. 关系型数据库架构演进概述

关系型数据库架构演进一篇不错的位文章:https://www.cnblogs.com/xrq730/p/11039384.html

1.单机Mysql时代

90年代,一个网站的访问量一般不会太大,单个数据库完全够用。随着用户增多,网站出现以下问题:

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

img

2、Memcached(缓存) + Mysql + 垂直拆分(读写分离)

网站80%的情况都是在读,每次都要去查询数据库的话就十分的麻烦!所以说我们希望减轻数据库的压力,我们可以使用缓存来保证效率!

发展过程:优化数据结构和索引–》文件缓存(IO)–》Memcached(当时最热门的技术)

img

优化过程经历了以下几个过程:

  1. 优化数据库的数据结构和索引(难度大)
  2. 文件缓存,通过IO流获取比每次都访问数据库效率略高,但是流量爆炸式增长时候,IO流也承受不了
  3. MemCache,当时最热门的技术,通过在数据库和数据库访问层之间加上一层缓存,第一次访问时查询数据库,将结果保存到缓存,后续的查询先检查缓存,若有直接拿去使用,效率显著提升。

####3、分库分表 + 水平拆分 + Mysql集群

img

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wQ1TdcPZ-1620017737474)(./images/搜狗截图20201031205653.png)]

4、如今最近的年代

如今信息量井喷式增长,各种各样的数据出现(用户定位数据,图片数据等),大数据的背景下关系型数据库(RDBMS)无法满足大量数据要求。Nosql数据库就能轻松解决这些问题。目前一个基本的互联网项目:

img

##3.为什么要用NoSQL ?

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

什么是Nosql

NoSQL = Not Only SQL(不仅仅是SQL)

Not Only Structured Query Language

关系型数据库:列+行,同一个表下数据的结构是一样的。

非关系型数据库:数据存储没有固定的格式,并且可以进行横向扩展。

NoSQL泛指非关系型数据库,随着web2.0互联网的诞生,传统的关系型数据库很难对付web2.0时代!尤其是超大规模的高并发的社区,暴露出来很多难以克服的问题,NoSQL在当今大数据环境下发展的十分迅速,Redis是发展最快的。

很多的数据类型,例如用户的个人信息、社交网络、地理位置等,数据类型的存储不需要一个固定的格式,

Nosql特点

1.方便扩展(数据之间没有关系,很好扩展!)

2.大数据量高性能(Redis一秒可以写8万次,读11万次,NoSQL的缓存记录级,是一种细粒度的缓存,性能会比较高!)

3.数据类型是多样型的!(不需要事先设计数据库,随取随用)

4.传统的 RDBMS(关系数据库管理系统) 和 NoSQL

传统的 RDBMS(关系型数据库)

结构化组织
SQL
数据和关系都存在单独的表中 row col
操作,数据定义语言
严格的一致性
基础的事务

Nosql

不仅仅是数据
没有固定的查询语言
键值对存储,列存储,文档存储,图形数据库(社交关系)
最终一致性
CAP定理和BASE
高性能,高可用,高扩展

CAP:CAP原则又称CAP定理,指的是在一个分布式系统中,一致性(Consistency)(各分区数据一致)、可用性(Availability)(某些分区发生故障,其他分区正常工作)、分区容错性(Partition tolerance)。CAP 原则指的是,这三个要素最多只能同时实现两点,不可能三者兼顾

3V、3高

5.大数据时代的3V :

主要是描述问题的

海量Velume

多样Variety – 数据多样

实时Velocity – 延时

6.大数据时代的3高 : 主要是对程序的要求

高并发

高可扩(水平扩伸)

高性能

真正在公司中的实践:NoSQL + RDBMS 一起使用才是最强的。

阿里技术演进

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bcvviUIr-1620017737498)(./images/搜狗截图20210417165148.png)]

越来越复杂,如何解决呢?

看下面,发现不同类型的数据,被存储在不同的数据库中。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OIeTDpSH-1620017737506)(./images/搜狗截图20201102085628.png)]

4. NoSQL的四大分类

KY键值对

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

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

  • MonbDb(一般必须要掌握)
    • MongoDB是一个局域分布式文件存储的数据库,C++编写,主要用来处理大量的文档!
    • Mongo是一个介于关系型数据库和非关系型数据的中间的产品,MongoDB是非关系型数据库中功能最丰富、最像关系型数据库的!
  • ConthDb

列存储数据库

  • HBase
  • 分布式文件系统

图关系数据库

不是用来存图型,而是放的关系,比如朋友圈社交网络,广告推荐

分类图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5QHmiSfj-1620017737511)(./images/搜狗截图20201102154604.png)]

Redis入门

Redis是什么?

Redis(Remote Dictionary Server ),即远程字典服务。

是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。

与memcached一样,为了保证效率,数据都是缓存在内存中。

区别的是redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步。

Redis能做什么?

  1. 内存存储、持久化,内存是断电即失的,所以需要持久化(RDB、AOF)
  2. 高效率、用于高速缓冲
  3. 发布订阅系统
  4. 地图信息分析
  5. 计时器、计数器(eg:浏览量)
  6. 。。。

特性

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

官网:https://redis.io/

中文网:http://www.redis.cn/

介绍:http://www.redis.cn/topics/introduction

5. Windows安装

压缩包直接解压,大小只有几MB

然后

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WgUcEJOA-1620017737517)(./images/搜狗截图20201102195555.png)]

6. Linux安装

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

2.将安装包拉进服务器的home/kang文件下(在Xftp中进行擦操作)

3.移送到/opt目录中:mv redis-6.0.6.tar.gz /opt;

​ 解压:tar -zxvf redis-6.0.6.tar.gz

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gsZIf0xR-1620017737521)(./images/搜狗截图20201102202602.png)]

环境搭建

4.进入解压后的文件,可以看到配置文件:redis.conf

5.安装gcc

yum install gcc-c++

6.执行命令:make来自动配置环境。make后,可以make install确认

7.redis默认安装路径/usr/local/bin,cd进入此目录。然后建立kconfig空目录,将opt/redis-6.0,.6中的redis.config复制到kconfig中:cp /opt/redis-6.0.6/redis.conf kconfig

8.vim redis.config打开此配置文件,将daemonize no改为yes,允许服务后台运行,这样我们关闭之后数据不会丢失。然后:w保存并:q退出

9.返回bin目录,以配置文件启动redis:

redis-server kconfig/redis.conf
redis-cli -p 6379 #然后使用redis客户端连接,默认端口号6379:

``

查看当前进程;ps -ef|grep redis

关闭连接:shotdown

退出redis:exit

修改配置文件redis.conf

protected-mode设置为no

同时也将bind注释  -- 这个必须注解掉,因为我原来使用的是
bind 127.0.0.0 101.200.181.33
这种情况下,打开服务,只有本地127的服务,远程连接不能成功

daemonize改为yes     这个是允许后台运行的

然后通过配置文件运行
cd src
./redis-server ../redis.conf
查看是否运行
#采取端口监听查看方式
netstat -lanp | grep 6379
# 查看进程方式
ps -aux | grep redis

打开本地应用控制台
./redis-cli

# 停止服务
/usr/local/redis/src/redis-cli -p 6379 shutdown

需要永久配置密码的话就去redis.conf的配置文件中找到requirepass这个参数,如下配置:

修改redis.conf配置文件  
# requirepass foobared
requirepass 123   指定密码123
保存后重启redis就可以了


连接redis

1.redis-cli连接redis

[root@iZ2ze3zda3caeyx6pn7c5zZ bin]# redis-cli
127.0.0.1:6379> keys *
(error) NOAUTH Authentication required.
127.0.0.1:6379> auth 123        //指定密码

性能测试

**redis-benchmark:**Redis官方提供的性能测试工具,参数选项如下:

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

简单测试:

# 测试:在bin目录下测试100个并发连接 100000请求
redis-benchmark -h localhost -p 6379 -c 100 -n 100000

结果:

img

7. 基础知识

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 *

"counter:rand_int"
"mylist"
"name"
"key:rand_int"
"myset:rand_int"
127.0.0.1:6379> DBSIZE # size和key个数相关
(integer) 5

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

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

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

Redis是单线程的,Redis是基于内存操作的。

所以Redis的性能瓶颈不是CPU,而是机器内存和网络带宽。

那么为什么Redis的速度如此快呢,性能这么高呢?QPS达到10W+,完全不比同样使用key-value的Memecache差。

既然使用单线程,

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

  • 误区1:高性能的服务器一定是多线程的?
  • 误区2:多线程(CPU上下文会切换!)一定比单线程效率高!

CPU>内存>硬盘的速度

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

五大数据类型

Redis是一个开源(BSD许可),内存存储的数据结构服务器,可用作 数据库高速缓存消息队列代理(消息中间件MQ)。它支持字符串、哈希表、列表、集合、有序集合,位图,hyperloglogs等数据类型。内置复制、Lua脚本、LRU收回、事务以及不同级别磁盘持久化功能,同时通过Redis Sentinel提供高可用,通过Redis Cluster提供自动分区。

Redis-key

命令查询:http://www.redis.cn/commands.html

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

下面学习的命令:

  • exists key:判断键是否存在
  • del key:删除键值对
  • move key db:将键值对移动到指定数据库
  • expire key second:设置键值对的过期时间
  • ttl key:查看过期剩余时间
  • type key:查看value的数据类型

127.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 *

"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

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

String(字符串)

普通的set、get直接略过。

常用命令及其示例:

  • append 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 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
  • decrby/incrbyINCRBY/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”
  • strlenSTRLEN key: 获取key保存值的字符串长度
127.0.0.1:6379> get msg 
“hello world” 
127.0.0.1:6379> STRLEN msg 
(integer) 11

getrange截取字符串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> set msg hello
OK
127.0.0.1:6379> setrange msg 2 hello
(integer) 7
127.0.0.1:6379> get msg
"hehello"
127.0.0.1:6379> set msg2 world
OK
127.0.0.1:6379> setrange msg2 2 ww
(integer) 5
127.0.0.1:6379> get msg2
"wowwd"

getset GETSET key value: 将给定 key 的值设为 value ,并返回 key 的旧值(old value)。

先get再set,如果get不到就set一个新键值

127.0.0.1:6379> GETSET msg test 
“hello world”

setnxSETNX key value: 仅当key不存在时进行set

127.0.0.1:6379> SETNX msg test 
(integer) 0 
127.0.0.1:6379> SETNX name sakura 
(integer) 1

setexSETEX key seconds value: set 键值对并设置过期时间

127.0.0.1:6379> setex name 10 root 
OK 
127.0.0.1:6379> get name 
(nil)

mset MSET key1 value1 [key2 value2..]: 批量set键值对

127.0.0.1:6379> MSET k1 v1 k2 v2 k3 v3 
OK

msetnxMSETNX key1 value1 [key2 value2..]: 批量设置键值对,仅当参数中所有的key都不存在时执行

127.0.0.1:6379> MSETNX k1 v1 k4 v4 
(integer) 0

mget MGET key1 [key2..]: 批量获取多个key保存的值

127.0.0.1:6379> MGET k1 k2 k3 
1) “v1” 
2) “v2” 
3) “v3”

psetexPSETEX key milliseconds value: 和 SETEX 命令相似,但它以毫秒为单位设置 key 的生存时间

对象形式

set user:{name:zhangsan,age:2}

127.0.0.1:6379[1]> set user:1:name zhangsan
OK
127.0.0.1:6379[1]> set user:1:age 2
OK
127.0.0.1:6379[1]> get user:1:name
"zhangsan"
127.0.0.1:6379[1]> mget user:1:name user:1:age
1) "zhangsan"
2) "2

String类似的使用场景:value除了是字符串还可以是数字,用途举例:

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

List(列表)

Redis列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)

一个列表最多可以包含 232 - 1 个元素 (4294967295, 每个列表超过40亿个元素)。

首先我们列表,可以经过规则定义将其变为队列、栈、双端队列等。

img

正如图Redis中List是可以进行双端操作的,所以命令也就分为了LXXX和RLLL两类,有时候L也表示List例如LLEN

  • LPUSH/RPUSH key value1[value2..]从左边/右边向列表中PUSH值(一个或者多个)。
  • LRANGE key start end 获取list 起止元素==(索引从左往右 递增)==
  • LPUSHX/RPUSHX key value 向已存在的列名中push值(一个或者多个)
  • LINSERT key BEFORE|AFTER pivot value 在指定列表元素的前/后 插入value
  • LLEN key 查看列表长度
  • LINDEX key index 通过索引获取列表元素
  • LSET key index value 通过索引为元素设值,相当于更新操作
  • LPOP/RPOP key 从最左边/最右边移除值 并返回
  • RPOPLPUSH source destination 将列表的尾部(右)最后一个值弹出,并返回,然后加到另一个列表的头部
  • LTRIM key start end 通过下标截取指定范围内的列表
  • LREM key count value List中是允许value重复的 count > 0:从头部开始搜索 然后删除指定的value 至多删除count个 count < 0:从尾部开始搜索… count = 0:删除列表中所有的指定value。
  • BLPOP/BRPOP key1[key2] timout 移出并获取列表的第一个/最后一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。
  • BRPOPLPUSH source destination timeout 和RPOPLPUSH功能相同,如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。

代码示例:

---------------------------LPUSH---RPUSH---LRANGE--------------------------------
127.0.0.1:6379> LPUSH mylist k1 # LPUSH mylist=>{1}
(integer) 1
127.0.0.1:6379> LPUSH mylist k2 # LPUSH mylist=>{2,1}
(integer) 2
127.0.0.1:6379> RPUSH mylist k3 # RPUSH mylist=>{2,1,3}
(integer) 3
127.0.0.1:6379> get mylist # 普通的get是无法获取list值的
(error) WRONGTYPE Operation against a key holding the wrong kind of value
127.0.0.1:6379> LRANGE mylist 0 4 # LRANGE 获取起止位置范围内的元素

"k2"
"k1"
"k3"
127.0.0.1:6379> LRANGE mylist 0 2
"k2"
"k1"
"k3"
127.0.0.1:6379> LRANGE mylist 0 1
"k2"
"k1"
127.0.0.1:6379> LRANGE mylist 0 -1 # 获取全部元素
"k2"
"k1"
"k3"

---------------------------LPUSHX---RPUSHX-----------------------------------
127.0.0.1:6379> LPUSHX list v1 # list不存在 LPUSHX失败
(integer) 0
127.0.0.1:6379> LPUSHX list v1 v2
(integer) 0
127.0.0.1:6379> LPUSHX mylist k4 k5 # 向mylist中 左边 PUSH k4 k5
(integer) 5
127.0.0.1:6379> LRANGE mylist 0 -1

"k5"
"k4"
"k2"
"k1"
"k3"

---------------------------LINSERT--LLEN--LINDEX--LSET----------------------------
127.0.0.1:6379> LINSERT mylist after k2 ins_key1 # 在k2元素后 插入ins_key1
(integer) 6
127.0.0.1:6379> LRANGE mylist 0 -1

"k5"
"k4"
"k2"
"ins_key1"
"k1"
"k3"
127.0.0.1:6379> LLEN mylist # 查看mylist的长度
(integer) 6
127.0.0.1:6379> LINDEX mylist 3 # 获取下标为3的元素
"ins_key1"
127.0.0.1:6379> LINDEX mylist 0
"k5"
127.0.0.1:6379> LSET mylist 3 k6 # 将下标3的元素 set值为k6
OK
127.0.0.1:6379> LRANGE mylist 0 -1
"k5"
"k4"
"k2"
"k6"
"k1"
"k3"

---------------------------LPOP--RPOP--------------------------
127.0.0.1:6379> LPOP mylist # 左侧(头部)弹出
"k5"
127.0.0.1:6379> RPOP mylist # 右侧(尾部)弹出
"k3"
---------------------------RPOPLPUSH--------------------------
127.0.0.1:6379> LRANGE mylist 0 -1

"k4"
"k2"
"k6"
"k1"
127.0.0.1:6379> RPOPLPUSH mylist newlist # 将mylist的最后一个值(k1)弹出,加入到newlist的头部
"k1"
127.0.0.1:6379> LRANGE newlist 0 -1
"k1"
127.0.0.1:6379> LRANGE mylist 0 -1
"k4"
"k2"
"k6"

---------------------------LTRIM--------------------------
127.0.0.1:6379> LTRIM mylist 0 1 # 截取mylist中的 0~1部分
OK
127.0.0.1:6379> LRANGE mylist 0 -1

"k4"
"k2"

初始 mylist: k2,k2,k2,k2,k2,k2,k4,k2,k2,k2,k2
---------------------------LREM--------------------------
127.0.0.1:6379> LREM mylist 3 k2 # 从头部开始搜索 至多删除3个 k2
(integer) 3
删除后:mylist: k2,k2,k2,k4,k2,k2,k2,k2
127.0.0.1:6379> LREM mylist -2 k2 #从尾部开始搜索 至多删除2个 k2
(integer) 2
删除后:mylist: k2,k2,k2,k4,k2,k2
---------------------------BLPOP--BRPOP--------------------------
mylist: k2,k2,k2,k4,k2,k2
newlist: k1
127.0.0.1:6379> BLPOP newlist mylist 30 # 从newlist中弹出第一个值,mylist作为候选

"newlist" # 弹出
"k1"
127.0.0.1:6379> BLPOP newlist mylist 30
"mylist" # 由于newlist空了 从mylist中弹出
"k2"
127.0.0.1:6379> BLPOP newlist 30
(30.10s) # 超时了

127.0.0.1:6379> BLPOP newlist 30 # 我们连接另一个客户端向newlist中push了test, 阻塞被解决。

"newlist"
"test"
(12.54s)
小结
  • list实际上是一个双向链表,before Node after , left, right 都可以插入值
  • 如果key不存在,则创建新的链表
  • 如果key存在,新增内容
  • 如果移除了所有值,空链表,也代表不存在
  • 在两边插入或者改动值,效率最高!修改中间元素,效率相对较低

应用:

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

Set(集合)

Redis的Set是string类型的无序集合。集合成员是唯一的,这就意味着集合中不能出现重复的数据。

Redis中集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是O(1)。

集合中最大的成员数为 232 - 1 (4294967295, 每个集合可存储40多亿个成员)。

  • SADD key member1[member2..] 向集合中无序增加一个/多个成员
  • SCARD key 获取集合的成员数
  • SMEMBERS key 返回集合中所有的成员
  • SISMEMBER key member 查询member元素是否是集合的成员,结果是无序的
  • SRANDMEMBER key [count] 随机返回集合中count个成员,count缺省值为1
  • SPOP key [count] 随机移除并返回集合中count个成员,count缺省值为1
  • SMOVE source destination member 将source集合的成员member移动到destination集合
  • SREM key member1[member2..] 移除集合中一个/多个成员
  • SDIFF key1[key2..] 返回所有集合的差集 key1- key2 - …
  • SDIFFSTORE destination key1[key2..] 在SDIFF的基础上,将结果保存到集合中==(覆盖)==。不能保存到其他类型key噢!
  • SINTER key1 [key2..] 返回所有集合的交集
  • SINTERSTORE destination key1[key2..] 在SINTER的基础上,存储结果到集合中。覆盖
  • SUNION key1 [key2..] 返回所有集合的并集
  • SUNIONSTORE destination key1 [key2..] 在SUNION的基础上,存储结果到及和张。覆盖
  • SSCAN KEY [MATCH pattern] [COUNT count] 在大量数据环境下,使用此命令遍历集合中元素,每次遍历部分

代码示例

---------------SADD--SCARD--SMEMBERS--SISMEMBER--------------------
127.0.0.1:6379> SADD myset m1 m2 m3 m4 # 向myset中增加成员 m1~m4
(integer) 4
127.0.0.1:6379> SCARD myset # 获取集合的成员数目
(integer) 4
127.0.0.1:6379> smembers myset # 获取集合中所有成员

"m4"
"m3"
"m2"
"m1"
127.0.0.1:6379> SISMEMBER myset m5 # 查询m5是否是myset的成员
(integer) 0 # 不是,返回0
127.0.0.1:6379> SISMEMBER myset m2
(integer) 1 # 是,返回1
127.0.0.1:6379> SISMEMBER myset m3
(integer) 1

---------------------SRANDMEMBER--SPOP----------------------------------
127.0.0.1:6379> SRANDMEMBER myset 3 # 随机返回3个成员

"m2"
"m3"
"m4"
127.0.0.1:6379> SRANDMEMBER myset # 随机返回1个成员
"m3"
127.0.0.1:6379> SPOP myset 2 # 随机移除并返回2个成员
"m1"
"m4"

将set还原到{m1,m2,m3,m4}
---------------------SMOVE--SREM----------------------------------------
127.0.0.1:6379> SMOVE myset newset m3 # 将myset中m3成员移动到newset集合
(integer) 1
127.0.0.1:6379> SMEMBERS myset

"m4"
"m2"
"m1"
127.0.0.1:6379> SMEMBERS newset
"m3"
127.0.0.1:6379> SREM newset m3 # 从newset中移除m3元素
(integer) 1
127.0.0.1:6379> SMEMBERS newset
(empty list or set)

下面开始是多集合操作,多集合操作中若只有一个参数默认和自身进行运算
setx=>{m1,m2,m4,m6}, sety=>{m2,m5,m6}, setz=>{m1,m3,m6}
-----------------------------SDIFF------------------------------------
127.0.0.1:6379> SDIFF setx sety setz # 等价于setx-sety-setz

"m4"
127.0.0.1:6379> SDIFF setx sety # setx - sety
"m4"
"m1"
127.0.0.1:6379> SDIFF sety setx # sety - setx
"m5"

-------------------------SINTER---------------------------------------
共同关注(交集)
127.0.0.1:6379> SINTER setx sety setz # 求 setx、sety、setx的交集

"m6"
127.0.0.1:6379> SINTER setx sety # 求setx sety的交集
"m2"
"m6"

-------------------------SUNION---------------------------------------
127.0.0.1:6379> SUNION setx sety setz # setx sety setz的并集

"m4"
"m6"
"m3"
"m2"
"m1"
"m5"
127.0.0.1:6379> SUNION setx sety # setx sety 并集
"m4"
"m6"
"m2"
"m1"
"m5"

Hash(哈希)

Redis hash 是一个string类型的field和value的映射表,hash特别适合用于存储对象。

Set就是一种简化的Hash,只变动key,而value使用默认值填充。可以将一个Hash表作为一个对象进行存储,表中存放对象的信息。

  • HSET key field value 将哈希表 key 中的字段 field 的值设为 value 。重复设置同一个field会覆盖,返回0
  • HMSET key field1 value1 [field2 value2..] 同时将多个 field-value (域-值)对设置到哈希表 key 中。
  • HSETNX key field value 只有在字段 field 不存在时,设置哈希表字段的值。
  • HEXISTS key field 查看哈希表 key 中,指定的字段是否存在。
  • HGET key field value 获取存储在哈希表中指定字段的值
  • HMGET key field1 [field2..] 获取所有给定字段的值
  • HGETALL key 获取在哈希表key 的所有字段和值
  • HKEYS key 获取哈希表key中所有的字段
  • HLEN key 获取哈希表中字段的数量
  • HVALS key 获取哈希表中所有值
  • HDEL key field1 [field2..] 删除哈希表key中一个/多个field字段
  • HINCRBY key field n 为哈希表 key 中的指定字段的整数值加上增量n,并返回增量后结果 一样只适用于整数型字段
  • HINCRBYFLOAT key field n 为哈希表 key 中的指定字段的浮点数值加上增量 n。
  • HSCAN key cursor [MATCH pattern] [COUNT count] 迭代哈希表中的键值对。

代码示例

------------------------HSET--HMSET--HSETNX----------------
127.0.0.1:6379> HSET studentx name sakura # 将studentx哈希表作为一个对象,设置name为sakura
(integer) 1
127.0.0.1:6379> HSET studentx name gyc # 重复设置field进行覆盖,并返回0
(integer) 0
127.0.0.1:6379> HSET studentx age 20 # 设置studentx的age为20
(integer) 1
127.0.0.1:6379> HMSET studentx sex 1 tel 15623667886 # 设置sex为1,tel为15623667886
OK
127.0.0.1:6379> HSETNX studentx name gyc # HSETNX 设置已存在的field
(integer) 0 # 失败
127.0.0.1:6379> HSETNX studentx email [email protected]
(integer) 1 # 成功
----------------------HEXISTS--------------------------------
127.0.0.1:6379> HEXISTS studentx name # name字段在studentx中是否存在
(integer) 1 # 存在
127.0.0.1:6379> HEXISTS studentx addr
(integer) 0 # 不存在
-------------------HGET--HMGET--HGETALL-----------
127.0.0.1:6379> HGET studentx name # 获取studentx中name字段的value
"gyc"
127.0.0.1:6379> HMGET studentx name age tel # 获取studentx中name、age、tel字段的value

"gyc"
"20"
"15623667886"
127.0.0.1:6379> HGETALL studentx # 获取studentx中所有的field及其value
"name"
"gyc"
"age"
"20"
"sex"
"1"
"tel"
"15623667886"
"email"
"[email protected]"

--------------------HKEYS--HLEN--HVALS--------------
127.0.0.1:6379> HKEYS studentx # 查看studentx中所有的field

"name"
"age"
"sex"
"tel"
"email"
127.0.0.1:6379> HLEN studentx # 查看studentx中的字段数量
(integer) 5
127.0.0.1:6379> HVALS studentx # 查看studentx中所有的value
"gyc"
"20"
"1"
"15623667886"
"[email protected]"

-------------------------HDEL--------------------------
127.0.0.1:6379> HDEL studentx sex tel # 删除studentx 中的sex、tel字段
(integer) 2
127.0.0.1:6379> HKEYS studentx

"name"
"age"
"email"

-------------HINCRBY--HINCRBYFLOAT------------------------
127.0.0.1:6379> HINCRBY studentx age 1 # studentx的age字段数值+1
(integer) 21
127.0.0.1:6379> HINCRBY studentx name 1 # 非整数字型字段不可用
(error) ERR hash value is not an integer
127.0.0.1:6379> HINCRBYFLOAT studentx weight 0.6 # weight字段增加0.6
"90.8"

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

Zset(有序集合)

不同的是每个元素都会关联一个double类型的分数(score)。redis正是通过分数来为集合中的成员进行从小到大的排序。

score相同:按字典顺序排序

有序集合的成员是唯一的,但分数(score)却可以重复。

  • ZADD key score member1 [score2 member2] 向有序集合添加一个或多个成员,或者更新已存在成员的分数
  • ZCARD key 获取有序集合的成员数
  • ZCOUNT key min max 计算在有序集合中指定区间score的成员数
  • ZINCRBY key n member 有序集合中对指定成员的分数加上增量 n
  • ZSCORE key member 返回有序集中,成员的分数值
  • ZRANK key member 返回有序集合中指定成员的索引
  • ZRANGE key start end 通过索引区间返回有序集合成指定区间内的成员
  • ZRANGEBYLEX key min max 通过字典区间返回有序集合的成员
  • ZRANGEBYSCORE key min max 通过分数返回有序集合指定区间内的成员==-inf 和 +inf分别表示最小最大值,只支持开区间()==
  • ZLEXCOUNT key min max 在有序集合中计算指定字典区间内成员数量
  • ZREM key member1 [member2..] 移除有序集合中一个/多个成员
  • ZREMRANGEBYLEX key min max 移除有序集合中给定的字典区间的所有成员
  • ZREMRANGEBYRANK key start stop 移除有序集合中给定的排名区间的所有成员
  • ZREMRANGEBYSCORE key min max 移除有序集合中给定的分数区间的所有成员
  • ZREVRANGE key start end 返回有序集中指定区间内的成员,通过索引,分数从高到底
  • ZREVRANGEBYSCORRE key max min 返回有序集中指定分数区间内的成员,分数从高到低排序
  • ZREVRANGEBYLEX key max min 返回有序集中指定字典区间内的成员,按字典顺序倒序
  • ZREVRANK key member 返回有序集合中指定成员的排名,有序集成员按分数值递减(从大到小)排序
  • ZINTERSTORE destination numkeys key1 [key2 ..] 计算给定的一个或多个有序集的交集并将结果集存储在新的有序集合 key 中,numkeys:表示参与运算的集合数,将score相加作为结果的score
  • ZUNIONSTORE destination numkeys key1 [key2..] 计算给定的一个或多个有序集的交集并将结果集存储在新的有序集合 key 中
  • ZSCAN key cursor [MATCH pattern\] [COUNT count] 迭代有序集合中的元素(包括元素成员和元素分值)

代码示例

-------------------ZADD--ZCARD--ZCOUNT--------------
127.0.0.1:6379> ZADD myzset 1 m1 2 m2 3 m3 # 向有序集合myzset中添加成员m1 score=1 以及成员m2 score=2..
(integer) 2
127.0.0.1:6379> ZCARD myzset # 获取有序集合的成员数
(integer) 2
127.0.0.1:6379> ZCOUNT myzset 0 1 # 获取score在 [0,1]区间的成员数量
(integer) 1
127.0.0.1:6379> ZCOUNT myzset 0 2
(integer) 2
----------------ZINCRBY--ZSCORE--------------------------
127.0.0.1:6379> ZINCRBY myzset 5 m2 # 将成员m2的score +5
"7"
127.0.0.1:6379> ZSCORE myzset m1 # 获取成员m1的score
"1"
127.0.0.1:6379> ZSCORE myzset m2
"7"
--------------ZRANK--ZRANGE-----------------------------------
127.0.0.1:6379> ZRANK myzset m1 # 获取成员m1的索引,索引按照score排序,score相同索引值按字典顺序顺序增加
(integer) 0
127.0.0.1:6379> ZRANK myzset m2
(integer) 2
127.0.0.1:6379> ZRANGE myzset 0 1 # 获取索引在 0~1的成员

"m1"
"m3"
127.0.0.1:6379> ZRANGE myzset 0 -1 # 获取全部成员
"m1"
"m3"
"m2"

testset=>{abc,add,amaze,apple,back,java,redis} score均为0
------------------ZRANGEBYLEX---------------------------------
127.0.0.1:6379> ZRANGEBYLEX testset - + # 返回所有成员

"abc"
"add"
"amaze"
"apple"
"back"
"java"
"redis"
127.0.0.1:6379> ZRANGEBYLEX testset - + LIMIT 0 3 # 分页 按索引显示查询结果的 0,1,2条记录
"abc"
"add"
"amaze"
127.0.0.1:6379> ZRANGEBYLEX testset - + LIMIT 3 3 # 显示 3,4,5条记录
"apple"
"back"
"java"
127.0.0.1:6379> ZRANGEBYLEX testset (- [apple # 显示 (-,apple] 区间内的成员
"abc"
"add"
"amaze"
"apple"
127.0.0.1:6379> ZRANGEBYLEX testset [apple [java # 显示 [apple,java]字典区间的成员
"apple"
"back"
"java"

-----------------------ZRANGEBYSCORE---------------------
127.0.0.1:6379> ZRANGEBYSCORE myzset 1 10 # 返回score在 [1,10]之间的的成员

"m1"
"m3"
"m2"
127.0.0.1:6379> ZRANGEBYSCORE myzset 1 5
"m1"
"m3"

--------------------ZLEXCOUNT-----------------------------
127.0.0.1:6379> ZLEXCOUNT testset - +
(integer) 7
127.0.0.1:6379> ZLEXCOUNT testset [apple [java
(integer) 3
------------------ZREM--ZREMRANGEBYLEX--ZREMRANGBYRANK--ZREMRANGEBYSCORE--------------------------------
127.0.0.1:6379> ZREM testset abc # 移除成员abc
(integer) 1
127.0.0.1:6379> ZREMRANGEBYLEX testset [apple [java # 移除字典区间[apple,java]中的所有成员
(integer) 3
127.0.0.1:6379> ZREMRANGEBYRANK testset 0 1 # 移除排名0~1的所有成员
(integer) 2
127.0.0.1:6379> ZREMRANGEBYSCORE myzset 0 3 # 移除score在 [0,3]的成员
(integer) 2
testset=> {abc,add,apple,amaze,back,java,redis} score均为0
myzset=> {(m1,1),(m2,2),(m3,3),(m4,4),(m7,7),(m9,9)}
----------------ZREVRANGE--ZREVRANGEBYSCORE--ZREVRANGEBYLEX-----------
127.0.0.1:6379> ZREVRANGE myzset 0 3 # 按score递减排序,然后按索引,返回结果的 0~3

"m9"
"m7"
"m4"
"m3"
127.0.0.1:6379> ZREVRANGE myzset 2 4 # 返回排序结果的 索引的2~4
"m4"
"m3"
"m2"
127.0.0.1:6379> ZREVRANGEBYSCORE myzset 6 2 # 按score递减顺序 返回集合中分数在[2,6]之间的成员
"m4"
"m3"
"m2"
127.0.0.1:6379> ZREVRANGEBYLEX testset [java (add # 按字典倒序 返回集合中(add,java]字典区间的成员
"java"
"back"
"apple"
"amaze"

-------------------------ZREVRANK------------------------------
127.0.0.1:6379> ZREVRANK myzset m7 # 按score递减顺序,返回成员m7索引
(integer) 1
127.0.0.1:6379> ZREVRANK myzset m2
(integer) 4
mathscore=>{(xm,90),(xh,95),(xg,87)} 小明、小红、小刚的数学成绩
enscore=>{(xm,70),(xh,93),(xg,90)} 小明、小红、小刚的英语成绩
-------------------ZINTERSTORE--ZUNIONSTORE-----------------------------------
127.0.0.1:6379> ZINTERSTORE sumscore 2 mathscore enscore # 将mathscore enscore进行合并 结果存放到sumscore
(integer) 3
127.0.0.1:6379> ZRANGE sumscore 0 -1 withscores # 合并后的score是之前集合中所有score的和

"xm"
"160"
"xg"
"177"
"xh"
"188"

127.0.0.1:6379> ZUNIONSTORE lowestscore 2 mathscore enscore AGGREGATE MIN # 取两个集合的成员score最小值作为结果的
(integer) 3
127.0.0.1:6379> ZRANGE lowestscore 0 -1 withscores

"xm"
"70"
"xg"
"87"
"xh"
"93"

应用案例:

  1. set排序 存储班级成绩表 工资表排序!
  2. 普通消息,1.重要消息 2.带权重进行判断
  3. 排行榜应用实现,取Top N测试

1.1.什么是NOSQL

三种特殊数据类型

geospatial地理位置

地理位置查询

http://www.redis.cn/commands/geoadd.html

Hyperloglog(交集并集)

基数,

例如:pradd hkey a b c d e f g h i j

可以用于求共同好友(交集)

Bitmaps(范围查询)

位存储 存储键值 0或1,例如是否签到

http://www.redis.cn/topics/data-types-intro.html#bitmaps

8. 事务

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

一次性、顺序性、排他性!

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

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

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

redis事务:

  • 开启事务(multi)
  • 命令入队(……)
  • 取消事务(discard)
  • 执行事务(exec)

监控

悲观锁

查看修改等都会添加排它锁

效率低

乐观锁

乐观,不会加锁。更新数据的时候会判断一下,在此期间是否有人修改过这个数据

获取version

更新的时候比较version

watch(监视)
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> get money
"85"
127.0.0.1:6379> get out
"20"

127.0.0.1:6379> get money
"80"
127.0.0.1:6379> incrby money 5
(integer) 85

该watch是乐观锁,当第一个线程中的事务执行时,判断是否有其他线程对money进行操作,如果有,那么当前事务执行失败

unwatch解锁

Jedis

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

     
        <dependency>
            <groupId>redis.clientsgroupId>
            <artifactId>jedisartifactId>
            <version>3.3.0version>
        dependency>

使用:

public class TestTX  {
    static String s= null;
    public static void main(String[] args) {
        Jedis jedis = new Jedis("127.0.0.1",6379);

        jedis.flushDB();
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("name","linlikang");
        jsonObject.put("gender","男");
        s = jsonObject.toJSONString();
//        开启事务
        Transaction multi = jedis.multi();
        try{
            System.out.println("当前线程名"+Thread.currentThread().getName());
            Thread.sleep(100);

            multi.set("user1",s);
            multi.set("user2",s);
            int i = 1/0;
            multi.exec();
        }
        catch (Exception e) {
            multi.discard();
            e.printStackTrace();
        }
        finally {
            System.out.println(jedis.get("user1"));
            System.out.println(jedis.get("user2"));
            jedis.close();
        }
    }
}

SpringBoot整合

在Springboot2.X后,原来是用的jedis被替换为lettuce

jedis采用的直连,多个线程操作是不安全的,如果想要避免不安全,需要使用jedis pool 连接池

lettuce:采用netty,实例可以在多个线程中进行共享,不存在线程不安全的情况下

public class RedisAutoConfiguration
 @Bean
    @ConditionalOnMissingBean(
        name = {"redisTemplate"}
    )// 如果不存在redisTemplate,那么使用这里的方法,所以我们可以自己定义一个redisTemplate
    @ConditionalOnSingleCandidate(RedisConnectionFactory.class)
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        //默认RedisTemplate没有过多的设置,redis对象都是需要实例化的
        //
        RedisTemplate<Object, Object> template = new RedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }

    @Bean
    @ConditionalOnMissingBean
    @ConditionalOnSingleCandidate(RedisConnectionFactory.class)
//String是Redis中最常用的类型
    public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
        StringRedisTemplate template = new StringRedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }

步骤

导入依赖

  
            org.springframework.boot
            spring-boot-starter-data-redis
        

配置连接

 private int database = 0;
    private String url;
    private String host = "localhost";
    private String username;
    private String password;
    private int port = 6379;
    private boolean ssl;
    private Duration timeout;
    private Duration connectTimeout;
    private String clientName;
    private RedisProperties.ClientType clientType;
    private RedisProperties.Sentinel sentinel;
    private RedisProperties.Cluster cluster;
    private final RedisProperties.Jedis jedis = new RedisProperties.Jedis();
    private final RedisProperties.Lettuce lettuce = new RedisProperties.Lettuce();

使用

@Autowired
    private RedisTemplate redisTemplate;


    @Test
    void contextLoads() {
        //redisTemplate 操作不同的数据类型,api和指令是一样的。
        //opsForValue String
//        获取redis的连接对象
//        RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
//        connection.flushAll();
        redisTemplate.opsForValue().set("name","linlikang");
        System.out.println(redisTemplate.opsForValue().get("name"));

保存的实体类,需要序列化,否则需要转换为Json

 @Test
    void test01() throws JsonProcessingException {
        User user = new User("林立康",12);
        String s = new ObjectMapper().writeValueAsString(user);
        redisTemplate.opsForValue().set("user",user);
        System.out.println(redisTemplate.opsForValue().get("user"));
    }

使用自己定义的RedisTemplate(定义时配置序列化)

Bean
    @SuppressWarnings("all")
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
        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.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;
    }

Jedis操作各种redis中的数据结构

1) 字符串类型 string


set
    get

//1. 获取连接
Jedis jedis = new Jedis();//如果使用空参构造,默认值 "localhost",6379端口
//2. 操作
//存储
jedis.set("username","zhangsan");
//获取
String username = jedis.get("username");
System.out.println(username);

//可以使用setex()方法存储可以指定过期时间的 key value
jedis.setex("activecode",20,"hehe");//将activecode:hehe键值对存入redis,并且20秒后自动删除该键值对
 //3. 关闭连接
jedis.close();

哈希类型

hash:map格式

hset

hget

hgetAll

//1. 获取连接
	        Jedis jedis = new Jedis();//如果使用空参构造,默认值 "localhost",6379端口
	        //2. 操作
	        // 存储hash
	        jedis.hset("user","name","lisi");
	        jedis.hset("user","age","23");
	        jedis.hset("user","gender","female");
	
	        // 获取hash
	        String name = jedis.hget("user", "name");
	        System.out.println(name);
  // 获取hash的所有map中的数据
		        Map<String, String> user = jedis.hgetAll("user");
		
		        // keyset
		        Set<String> keySet = user.keySet();
		        for (String key : keySet) {
		            //获取value
		            String value = user.get(key);
		            System.out.println(key + ":" + value);
		        }
		
		        //3. 关闭连接
		        jedis.close();

3) 列表类型 list : linkedlist格式。支持重复元素

			lpush / rpush
			lpop / rpop
			lrange start end : 范围获取
			
			 //1. 获取连接
	        Jedis jedis = new Jedis();//如果使用空参构造,默认值 "localhost",6379端口
	        //2. 操作
	        // list 存储
	        jedis.lpush("mylist","a","b","c");//从左边存
	        jedis.rpush("mylist","a","b","c");//从右边存
	
	        // list 范围获取
	        List<String> mylist = jedis.lrange("mylist", 0, -1);
	        System.out.println(mylist);
	        
	        // list 弹出
	        String element1 = jedis.lpop("mylist");//c
	        System.out.println(element1);
	
	        String element2 = jedis.rpop("mylist");//c
	        System.out.println(element2);
	
	        // list 范围获取
	        List<String> mylist2 = jedis.lrange("mylist", 0, -1);
	        System.out.println(mylist2);
	
	        //3. 关闭连接
	        jedis.close();

集合类型set

不允许重复元素

sadd
smembers:获取所有元素

//1. 获取连接
	        Jedis jedis = new Jedis();//如果使用空参构造,默认值 "localhost",6379端口
	        //2. 操作
 // set 存储
		        jedis.sadd("myset","java","php","c++");
		
		        // set 获取
		        Set<String> myset = jedis.smembers("myset");
		        System.out.println(myset);
		
		        //3. 关闭连接
		        jedis.close();

有序集合类型sortedset:

不允许重复元素,且元素有顺序

zadd

zrange

//1. 获取连接
	        Jedis jedis = new Jedis();//如果使用空参构造,默认值 "localhost",6379端口
	        //2. 操作
	        // sortedset 存储
	        jedis.zadd("mysortedset",3,"亚瑟");
	        jedis.zadd("mysortedset",30,"后裔");
	        jedis.zadd("mysortedset",55,"孙悟空");
	
	        // sortedset 获取
	        Set<String> mysortedset = jedis.zrange("mysortedset", 0, -1);
	
	        System.out.println(mysortedset);
//3. 关闭连接
		        jedis.close();

9. 配置文件解读

查看redis.conf

# units are case insensitive so 1GB 1Gb 1gB are all the same.

大小写不敏感

#
# include .\path\to\local.conf
# include c:\path\to\other.conf

配置文件可以包含其他的配置文件

# bind 192.168.1.100 10.0.0.1  # 绑定ip
# bind 127.0.0.1 ::1

protected-mode yes # 是否受保护模式

daemonize no #  是否以后台运行 

pidfile /var/run/redis.pid  # 如果以后台方式运行,需要制定一个pid文件
# Specify the log file name. Also 'stdout' can be used to force
# Redis to log on the standard output.
logfile ""    #日志文件名称

快照

#   save ""
# 持久化,在规定时间内,执行了多少次操作,会持久化到文件.rdb.aof
save 900 1 # 如果900s内,至少有一个1 key进行了修改,那么进行持久化
save 300 10 # 300s内,如果至少进行10key修改,进行持久化
save 60 10000 #  60s,至少10000key进行修改


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

rdbcompression yes # 是否压缩rdb文件,需要消耗cpu


# tell the loading code to skip the check.
rdbchecksum yes # 保存rdb文件时,进行错误的检查效验

# The filename where to dump the DB
dbfilename dump.rdb


dir ./  # rdb文件保存目录
# requirepass foobared # 设置密码 默认是没有密码
# 使用auth password进行登录
或者通过命令方式修改密码:config set requirepass "123456"

# The default is:
#
# 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 : 永不过期,返回错误
appendonly no # 默认不开启aof模式,使用rdb持久化。
appendfilename "appendonly.aof" #持久化文件名


# appendfsync always # 每次修改都会sync 
appendfsync everysec # 每秒钟执行一次sync
# appendfsync no # 不执行

10. 持久化操作

  1. redis是一个内存数据库,当redis服务器重启,或者电脑重启,数据会丢失,我们可以将redis内存中的数据持久化保存到硬盘的文件中。
    1. redis持久化机制:
      1. RDB:默认方式,不需要进行配置,默认就使用这种机制

        • 在一定的间隔时间中,检测key的变化情况,然后持久化数据
        1. 编辑redis.windwos.conf文件

          #after 900 sec (15 min) if at least 1 key changed
          
          save 900 1
          
          #after 300 sec (5 min) if at least 10 keys changed
          
          save 300 10
          
          #after 60 sec if at least 10000 keys changed
          
          save 60 10000
          
          dbfilename dump.rdb  # 保存在该rdb文件中
          
        2. 重新启动redis服务器,并指定配置文件名称
          D:\JavaWeb2018\day23_redis\资料\redis\windows-64\redis-2.8.9>redis-server.exe redis.windows.conf

        3. 触发机制:

        • save规则符合
        • 执行flushall命令
        • 退出redis

        恢复rdb文件

        • 将rdb文件放在redis启动目录,redis启动的时候会自动检查dump.rdb恢复数据
        • 位置:/usr/local/bin

        rdb优缺点

        适合大规模的数据恢复

        如果对数据完整性要求不高,

        缺点:需要一定时间间隔进行操作!如果redis意外宕机,最后一次修改无法保存

        [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lNe7jOMt-1620017737530)(./images/rdb.jpg)]

      2. AOF:日志记录的方式,可以记录每一条命令的操作。可以每一次命令操作后,持久化数据

        1. 编辑redis.windwos.conf文件

          appendonly no(关闭aof) --> appendonly yes (开启aof)
          
          #appendfsync always : 每一次操作都进行持久化
          
          appendfsync everysec : 每隔一秒进行一次持久化
          
          #appendfsync no	 : 不进行持久化 
          
        2. 在目录文件redis-2.8.9中打开cmd,执行:redis-server.exe redis.windows.cong
          执行成功后,会在目标文件redis-2.8.9夹中生成一个appendonly.aof的文件夹。
          然后就可以打开客户端使用了,

        3. 每次重启时,需要读配置文件重新执行一遍,比较消耗资源。

        4. 如果这个aof文件有错误,那么redis是启动不起来的,需要修复这个文件

        5. 修复工具:redis-check-aof --fix appendonly.aof

        6. 优点:每一次修改都同步:文件完整性更好

          每秒钟同步:L可能会丢失一秒数据

          从不同步:效率最高

        7. 缺点:相对于数据文件来说,aof远远大于rdb,修复速度也比rdb慢

          [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WAAdaPug-1620017737541)(./images/aof.jpg)]

如果同时开启两种持久化方式

  • 当redis重启时,会优先载入aof文件来恢复原始数据,因为在通常情况下,aof文件保存的数据集要比rdb文件保存的数据集完整
  • rdb更适合用于备份数据库,(aof日志文件不断变化不好备份),

11. Redis发布订阅

菜鸟教程;https://www.runoob.com/redis/redis-pub-sub.html

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

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eczUEkSi-1620017737543)(./images/搜狗截图20210426223257.png)]

  • 开启本地 Redis 服务,开启两个 redis-cli 客户端。
  • 第一个 redis-cli 客户端输入 SUBSCRIBE runoobChat,意思是订阅 runoobChat 频道。
  • 第二个 redis-cli 客户端输入 PUBLISH runoobChat “Redis PUBLISH test” 往 runoobChat 频道发送消息,这个时候在第一个 redis-cli 客户端就会看到由第二个 redis-cli 客户端发送的测试消息。
序号 命令及描述
1 [PSUBSCRIBE pattern pattern …] 订阅一个或多个符合给定模式的频道。
2 [PUBSUB subcommand argument [argument …]] 查看订阅与发布系统状态。
3 PUBLISH channel message 将信息发送到指定的频道。
4 [PUNSUBSCRIBE pattern [pattern …]] 退订所有给定模式的频道。
5 [SUBSCRIBE channel channel …] 订阅给定的一个或多个频道的信息。
6 [UNSUBSCRIBE channel [channel …]] 指退订给定的频道。

应用场景

  • 实时消息系统
  • 实时聊天

12. 主从复制

80%情况下都是在进行读操作。

将一台redis服务器(主机)的数据复制到其他redis服务器(从机)上,也就是读写分离,

配置一个redis集群至少需要有三台服务器

面临问题

\1. 机器故障。我们部署到一台 Redis 服务器,当发生机器故障时,需要迁移到另外一台服务器并且要保证数据是同步的。而数据是最重要的,如果你不在乎,基本上也就不会使用 Redis 了。

\2. 容量瓶颈。当我们有需求需要扩容 Redis 内存时,从 16G 的内存升到 64G,单机肯定是满足不了。当然,你可以重新买个 128G 的新机器。

解决办法

要实现分布式数据库的更大的存储容量和承受高并发访问量,我们会将原来集中式数据库的数据分别存储到其他多个网络节点上。

主从复制的作用

\1. 数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式。

\2. 故障恢复:当主节点出现问题时,可以由从节点提供服务,实现快速的故障恢复;实际上是一种服务的冗余。

\3. 负载均衡:在主从复制的基础上,配合读写分离,可以由主节点提供写服务,由从节点提供读服务(即写Redis数据时应用连接主节点,读Redis数据时应用连接从节点),分担服务器负载;尤其是在写少读多的场景下,通过多个从节点分担读负载,可以大大提高Redis服务器的并发量。

\4. 读写分离:可以用于实现读写分离,主库写、从库读,读写分离不仅可以提高服务器的负载能力,同时可根据需求的变化,改变从库的数量;

\5. 高可用基石:除了上述作用以外,主从复制还是哨兵和集群能够实施的基础,因此说主从复制是Redis高可用的基础。

配置

127.0.0.1:6379> info replication
# Replication
role:master # 角色
connected_slaves:0 # 连接的从机数量
master_replid:27369f8e13738be7ad0affaf9ab68094f5d9202f
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名字
  • log文件名字
  • dump.rdb文件名字
127.0.0.1:6380> slaveof 127.0.0.1 6379 #认定6379端口为主机
OK
127.0.0.1:6380> info replication
# Replication
role:slave
master_host:127.0.0.1
master_port:6379

如果master主机设置了auth密码,那么需要在slave从机中设置masterauth masterPassword

这样配置成功后,只能主机写入数据,并且自动复制到从机上

如果是使用命令行来配制的主从关系,那么重启后,就会变成主机。不过,只要变成从机,立刻就会从主机中复制数据。

复制原理

Slave启动成功连接到master会发送一个sync命令

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

增量复制:master继续将新的所有收集到的修改命令一次传给slave

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

层层链路

从机依旧可以拥有另外的从机

master宕机后,slave变成主机

使用命令SLAVEOF no one 让自己变成主机,其他节点手动连接最新的主机。如果这个时候原来的主机修复了,不会改变现有模式。

哨兵模式

学习官网:http://www.redis.cn/topics/sentinel.html

概述

  • 监控(Monitoring): Sentinel 会不断地检查你的主服务器和从服务器是否运作正常。哨兵通过发送命令,等待redis服务器响应
  • 提醒(Notification): 当被监控的某个 Redis 服务器出现问题时, Sentinel 可以通过 API 向管理员或者其他应用程序发送通知。
  • 自动故障迁移(Automatic failover): 当一个主服务器不能正常工作时, Sentinel 会开始一次自动故障迁移操作, 它会将失效主服务器的其中一个从服务器升级为新的主服务器, 并让失效主服务器的其他从服务器改为复制新的主服务器; 当客户端试图连接失效的主服务器时, 集群也会向客户端返回新主服务器的地址, 使得集群可以使用新主服务器代替失效服务器。

哨兵是一个独立的进程,独立运行。

单机哨兵

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ta1ifNWh-1620017737550)(./images/搜狗截图20210428211223.png)]

作用:

当检测到master宕机后,会自动将slave转换为master,然后通过发布订阅模式通知其他从服务器,修改配置文件,让他们切换依赖的主机

多机哨兵

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DtG3825l-1620017737561)(./images/搜狗截图20210428211342.png)]

多个哨兵存在时,如果一个哨兵发现了master出现问题(主观下线),并不会立刻切换主机,而是等其他哨兵也发现master出问题(客观下线)后,才会投票选出新的主机

配置哨兵配置文件sentinel.conf

# sentinel monitor 被监视的名称(自己任意起)host port 1
sentinel monitor myredis 127.0.0.1 6379 1
# 1 表示主机宕机后,slave投票选出新的主机

启动哨兵

redis-sentinel kconfig/sentinel.conf

当主机修复后,只能当新的主机下的从机。

优点

  • 哨兵集群,基于主从复制模式,所有的主从配置优点,他全有
  • 主从可以切换,故障可以转移,系统的可用性更好

缺点

  • Redis不容易在线扩容,集群容量一旦到达上限,在线扩容就十分麻烦
  • 实现哨兵模式的配置很麻烦,配置文件需要很多配置,具体官网查看。

13. Redis缓存穿透和雪崩

缓存处理流程

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

穿透概念

数据查不到

用户查询一个数据,发现redis内存数据库没有,也就是没有命中,就会向持久层数据库查询,发现也没有,返回空数据。

当用户量非常大,并且缓存都没有命中,于是都去请求持久层数据库。

这会给持久层数据库造成很大压力,就相当于出现了缓存穿透。

解决方案

布隆过滤器

概念

本质上布隆过滤器是一种数据结构,比较巧妙的概率型数据结构(probabilistic data structure),特点是高效地插入和查询,可以用来告诉你 “某样东西一定不存在或者可能存在”

相比于传统的 List、Set、Map 等数据结构,它更高效、占用空间更少,但是缺点是其返回的结果是概率性的,而不是确切的。

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

参考知乎:https://zhuanlan.zhihu.com/p/43263751

缓存空对象

当查询底层数据库后,即使查询为空也会被保存在缓存中,同时设置过期时间。

这种方法有两个问题:

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

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

概念

指一个key是一个热点,在不停的扛着大并发,大并发集中对着一个点进行访问,当这个key在实效的瞬间,持续的大并发就穿破缓存,直接请求数据库,如同凿开一个洞。

解决方法

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

2. 加互斥锁

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

缓存雪崩

概念

某一个时间段,缓存集中失效,redis宕机。

其实集中过期,倒不是非常致命,比较致命的是缓存服务器某个结点宕机或断网。

自然形成的缓存雪崩一定是在某个时间段集中创建缓存,这个时候,数据库是可以顶住压力的。

解决方式

  1. 缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。
  2. 如果缓存数据库是分布式部署,将热点数据均匀分布在不同搞得缓存数据库中。
  3. 设置热点数据永远不过期。

Redis淘汰策略

以通过 API 向管理员或者其他应用程序发送通知。

  • 自动故障迁移(Automatic failover): 当一个主服务器不能正常工作时, Sentinel 会开始一次自动故障迁移操作, 它会将失效主服务器的其中一个从服务器升级为新的主服务器, 并让失效主服务器的其他从服务器改为复制新的主服务器; 当客户端试图连接失效的主服务器时, 集群也会向客户端返回新主服务器的地址, 使得集群可以使用新主服务器代替失效服务器。

哨兵是一个独立的进程,独立运行。

单机哨兵

[外链图片转存中…(img-ta1ifNWh-1620017737550)]

作用:

当检测到master宕机后,会自动将slave转换为master,然后通过发布订阅模式通知其他从服务器,修改配置文件,让他们切换依赖的主机

多机哨兵

[外链图片转存中…(img-DtG3825l-1620017737561)]

多个哨兵存在时,如果一个哨兵发现了master出现问题(主观下线),并不会立刻切换主机,而是等其他哨兵也发现master出问题(客观下线)后,才会投票选出新的主机

配置哨兵配置文件sentinel.conf

# sentinel monitor 被监视的名称(自己任意起)host port 1
sentinel monitor myredis 127.0.0.1 6379 1
# 1 表示主机宕机后,slave投票选出新的主机

启动哨兵

redis-sentinel kconfig/sentinel.conf

当主机修复后,只能当新的主机下的从机。

优点

  • 哨兵集群,基于主从复制模式,所有的主从配置优点,他全有
  • 主从可以切换,故障可以转移,系统的可用性更好

缺点

  • Redis不容易在线扩容,集群容量一旦到达上限,在线扩容就十分麻烦
  • 实现哨兵模式的配置很麻烦,配置文件需要很多配置,具体官网查看。

13. Redis缓存穿透和雪崩

缓存处理流程

[外链图片转存中…(img-bVNiC5v1-1620017737572)]

穿透概念

数据查不到

用户查询一个数据,发现redis内存数据库没有,也就是没有命中,就会向持久层数据库查询,发现也没有,返回空数据。

当用户量非常大,并且缓存都没有命中,于是都去请求持久层数据库。

这会给持久层数据库造成很大压力,就相当于出现了缓存穿透。

解决方案

布隆过滤器

概念

本质上布隆过滤器是一种数据结构,比较巧妙的概率型数据结构(probabilistic data structure),特点是高效地插入和查询,可以用来告诉你 “某样东西一定不存在或者可能存在”

相比于传统的 List、Set、Map 等数据结构,它更高效、占用空间更少,但是缺点是其返回的结果是概率性的,而不是确切的。

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

参考知乎:https://zhuanlan.zhihu.com/p/43263751

缓存空对象

当查询底层数据库后,即使查询为空也会被保存在缓存中,同时设置过期时间。

这种方法有两个问题:

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

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

概念

指一个key是一个热点,在不停的扛着大并发,大并发集中对着一个点进行访问,当这个key在实效的瞬间,持续的大并发就穿破缓存,直接请求数据库,如同凿开一个洞。

解决方法

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

2. 加互斥锁

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

缓存雪崩

概念

某一个时间段,缓存集中失效,redis宕机。

其实集中过期,倒不是非常致命,比较致命的是缓存服务器某个结点宕机或断网。

自然形成的缓存雪崩一定是在某个时间段集中创建缓存,这个时候,数据库是可以顶住压力的。

解决方式

  1. 缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。
  2. 如果缓存数据库是分布式部署,将热点数据均匀分布在不同搞得缓存数据库中。
  3. 设置热点数据永远不过期。

Redis淘汰策略

你可能感兴趣的:(数据库系统,redis,nosql,redis,nosql,数据库,面试题)