Redis

目录

  • NoSQL讲解
  • 阿里巴巴架构演进
  • NoSQL数据模型
  • NoSQL四大类
  • CAP
  • BASE
  • Redis入门
  • Redis安装
  • 五大基本数据类型
    • String
    • List
    • Set
    • Hash
    • Zset
  • 三种特殊数据类型
    • geo
    • hyperloglog
    • bitmap
  • Redis配置详解
  • Redis持久化
    • RDB
    • AOF
  • Redis事务操作
  • Redis实现订阅发布
  • Redis主从复制
  • Redis哨兵模式
  • 缓存穿透及解决方案
  • 缓存击穿及解决方案
  • 缓存雪崩及解决方案
  • 基础API之Jedis详解
  • SpringBoot集成Redis操作
  • Redis的实践分析

NoSQL概述

为什么要用NoSQL

1.单机Mysql的年代

image-20231210014851078

90年代,一个基本的网站访问量一般不会太大,单个数据库完全够用!

那个时候,更多的使用静态网页(HTML)——–服务器根本没有太大的压力!

思考一下:这种情况下:整个网站的瓶颈是什么?

  1. 数据量如果太大,一个机器放不下了!
  2. 数据的索引(B+Tree),一个机器内存也放不下!
  3. 访问量(读写混合),一个服务器承受不了!

只要你出现以上的三种情况之一,那么你就必须要晋级!

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

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

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

image-20231210020532784

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

技术和业务在发展的同时对人的要求也越来越高

本质:数据库(读,写)

早些年MyISAM:表锁,十分影响效率!高并发下就会出现严重的锁问题

早些年Innodb:行锁

慢慢的就开始使用分库分表来解决写的压力!MySQL在那个年代推出了表分区!这个并没有多少公司使用!

MySQL的集群,很好的满足了那个年代的所有需求!

image-20231210022518687

4.如今最近的年代

2010–2020 十年之间,世界已经发生了翻天覆地的变化;(定位,也是一种数据,音乐,热榜!)

MySQL等关系型数据库就不够用了!数据量很多,变化很快!

MySQL有的使用它来存储一些比较大的文件,博客,图片!数据库表很大,效率就很低了!如果有一种数据库来专门处理这种数据,MySQL的压力就变得十分小(研究如何处理这些问题!)大数据的IO压力下,表几乎没法更大!

5.目前一个基本的互联网项目!

image-20231210025655342

6.为什么要用NoSQL!

用户的个人信息,社交网络,地理位置,用户自己产生的数据,用户的日志等等爆发式的增长!

这时候我们就需要使用NoSQL数据库,NoSQL可以很好的处理以上的情况!

什么是NoSQL

1.NoSQL

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

关系型数据库:表格,行,列

泛指非关系型数据库,随着Web2.0互联网的诞生!传统的关系型数据库很难对付!尤其是超大规模的高并发的社区!暴露出来很多的难以克服的问题,NoSQL在当今大数据环境下发展的十分迅速,Redis是发展最快的,而且是我们当下必须要掌握的一个技术!

很多的数据类型用户的个人信息,社交网络,地理位置。这些数据类型的存储不需要一个固定的格式!不需要多月的操作就可以横向扩展的! Map<String,Object>使用键值对来控制!

2.NoSQL特点

解耦!

  1. 方便扩展(数据之间没有关系,很好扩展!)
  2. 大数据量高性能(Redis一秒写8万次,读取11万次,NoSQL的缓存记录级,是一种细粒度的缓存,性能会比较高!)
  3. 数据类型是多样的!(不需要事先设计数据库!随取随用!如果是数据量十分大的表,很多人就无法设计了!)
  4. 传统的RDBMS和NoSQL
java
1
2
3
4
5
6
7
8
传统的RDBMS
- 结构化组织
- SQL
- 数据和关系都存在单独的表中
- 操作语言,数据定义语言
- 严格的一致性
- 基础的事务操作
- ...
plaintext
1
2
3
4
5
6
7
8
NoSQL
- 不仅仅是数据
- 没有固定的查询语言
- 键值对存储,列存储,文档存储,图形数据库(社交关系)
- 最终一致性
- CAP定理和BASE(异地多活)
- 高性能,高可用,高可扩
- ...

3.了解:3V+3高

大数据时代的3V:主要是描述问题的

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

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

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

真正在公司中的实践:NoSQL+RDBMS一起使用才是最强的,阿里巴巴架构的演进!

阿里巴巴架构的演进

img

img

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 商品信息
- 一般存放在关系型数据库:Mysql,阿里巴巴使用的Mysql都是经过内部改动的。

# 商品描述、评论(文字居多)
- 文档型数据库:MongoDB

# 图片
- 分布式文件系统 FastDFS
- 淘宝:TFS
- Google: GFS
- Hadoop: HDFS
- 阿里云: oss

# 商品关键字 用于搜索
- 搜索引擎:solr,elasticsearch
- 阿里:Isearch 多隆

# 商品热门的波段信息
- 内存数据库:Redis,Memcache

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

NoSQL的四大分类

1.kv键值对:

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

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

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

3.列存储数据库

  • Hbase
  • 分布式文件系统

4.图关系数据库

image

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

四者对比!

分类Examples举例典型应用场景数据模型优点缺点
键值对(key-value)Tokyo Cabinet/Tyrant, Redis, Voldemort, Oracle BDB内容缓存,主要用于处理大量数据的高访问负载,也用于一些日志系统等等。Key 指向 Value 的键值对,通常用hash table来实现查找速度快数据无结构化,通常只被当作字符串或者二进制数据
列存储数据库Cassandra, HBase, Riak分布式的文件系统以列簇式存储,将同一列数据存在一起查找速度快,可扩展性强,更容易进行分布式扩展功能相对局限
文档型数据库CouchDB, MongoDbWeb应用(与Key-Value类似,Value是结构化的,不同的是数据库能够了解Value的内容)Key-Value对应的键值对,Value为结构化数据数据结构要求不严格,表结构可变,不需要像关系型数据库一样需要预先定义表结构查询性能不高,而且缺乏统一的查询语法。
图形(Graph)数据库Neo4J, InfoGrid, Infinite Graph社交网络,推荐系统等。专注于构建关系图谱图结构利用图结构相关算法。比如最短路径寻址,N度关系查找等很多时候需要对整个图做计算才能得出需要的信息,而且这种结构不太好做分布式的集群

Redis入门

概述

1.Redis是什么?

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

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

与memcached一样,为了保证效率,数据都是缓存在内存中。区别的是redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步。

2.Redis能该干什么?

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

3.特性

  1. 多样的数据类型

  2. 持久化

  3. 集群

  4. 事务

4.环境搭建

官网:https://redis.io/

推荐使用Linux服务器学习。

windows版本的Redis已经停更很久了…

Windows安装

1.下载安装包

https://github.com/dmajkic/redis

2.下载后解压的目录

image-20231210114022026

3.开启Redis,双击redis-server.exe

image-20231210115914324

4.启动redis-cli.exe测试

image-20231210120027939

Linux安装

本文中Linux系统是ubuntu18,redis是5.0.7版本

1.下载redis安装包

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

plaintext
1
tar -zxvf redis-5.0.7.tar.gz  //解压redis后可见,redis-5.0.7文件

在这里插入图片描述

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

在这里插入图片描述

4.基本环境安装

plaintext
1
2
3
4
5
sudo apt-get install gcc
sudo apt-get install g++
gcc --version //查看是否安装成功及安装版本
g++ --version
sudo make //加载所需环境,会多出src文件

在这里插入图片描述在这里插入图片描述
如果是第一次安装,make命令会执行比较久,耐心等待,成功后会多出一个src文件
在这里插入图片描述

5.redis默认安装路径”/usr/local/bin”

在这里插入图片描述

6.将redis配置文件复制到当前目录下,之后用这个配置文件进行启动

plaintext
1
cp /opt/redis-5.0.7/redis.conf 

在这里插入图片描述

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

在这里插入图片描述

8.通过指定的配置文件启动redis服务

plaintext
1
redis-server my-config/redis.conf 

在这里插入图片描述

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

在这里插入图片描述

10.到此redis在Linux下安装结束并测试已连通

测试性能

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

官方自带的性能测试工具!

redis-benchmark命令参数:

image-20231210144740604

简单测试:

java
1
2
测试:100个并发请求 100000请求
redis-benchmark -h localhost -p 6379 -c 100 -n 100000

image-20231210150010710

java
1
2
3
4
10万个请求进行写入测试
100个并发客户端
每次写入三个字节
只有一台服务器来处理这些请求,单机性能

基础的知识

Redis默认有16个数据库

image-20231210151032221

默认使用第0个

可以使用select进行切换数据库!

java
1
2
3
4
5
6
[root@hadoop redis-2.8.19]# redis-cli
127.0.0.1:6379> select 3 //切换数据库
OK
127.0.0.1:6379[3]> dbsize //查看数据库大小
(integer) 0
127.0.0.1:6379[3]>

image-20231210151810965

java
1
2
127.0.0.1:6379[3]> keys *    //查看数据库所有的key
1) "name"

清空当前数据库 flushdb

清空全部数据库内容 flushall

java
1
2
3
4
5
127.0.0.1:6379[3]> flushdb
OK
127.0.0.1:6379[3]> keys *
(empty list or set)
127.0.0.1:6379[3]>

思考:为什么redis是6379!(了解)

Redis是单线程的!

明白Redis是很快的,官方表示,Redis是基于内存操作,CPU不是Redis性能瓶颈,Redis的瓶颈是根据机器的内存和网络带宽,既然可以使用单线程来实现,就使用单线程了!所以就使用单线程了!

Redis是C语言写的,官方提供的数据为100000+的QPS,完全不比同样是使用key-value的Memecache差!

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

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

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

先去CPU->内存->硬盘的速度要有所了解!

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

五大数据类型

官网文档

Redis 是一种开源(BSD 许可)的内存数据结构存储,用作数据库、缓存、消息代理和流式处理引擎。Redis 提供数据结构,例如字符串哈希列表、集、带有范围查询的排序集位图超日志地理空间索引。 Redis 具有内置复制、Lua 脚本LRU 逐出事务和不同级别的磁盘持久性,并通过 Redis Sentinel 和 Redis 集群的自动分区提供高可用性。

您可以对这些类型运行原子操作,例如附加到字符串;递增哈希值;将元素推送到 列表;计算集合交集、集和差分集; 或获取排序集中排名最高的成员

为了实现最佳性能,Redis 使用内存中数据集。根据您的使用案例,Redis 可以保留您的数据 通过定期将数据集转储到磁盘或将每个命令追加到基于磁盘的日志中。如果您只需要一个功能丰富的网络内存中缓存,也可以禁用持久性。

Redis 支持异步复制,具有快速无阻塞同步和自动重连,并在网络拆分时进行部分重新同步。

Redis 还包括:

您可以使用大多数编程语言的 Redis。

Redis 是用 ANSI C 编写的,适用于大多数 POSIX 系统,如 Linux, BSD 和 Mac OS X,没有外部依赖。Linux 和 OS X 是开发和测试 Redis 最多的两个操作系统,我们建议使用 Linux 进行部署。Redis 可以在 Solaris 派生的系统(如 SmartOS)中工作,但支持是最大的努力*。 没有对 Windows 版本的官方支持。

所有命令用于SpringBoot,Jedis的学习

Redis-Key

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
127.0.0.1:6379> keys *						//查看所有的Key
(empty array)
127.0.0.1:6379> set name liuyunxuan //set key
OK
127.0.0.1:6379> keys *
1) "name"
127.0.0.1:6379> set age 1
OK
127.0.0.1:6379> keys *
1) "age"
2) "name"
127.0.0.1:6379> EXISTS name //判断当前的key是否存在
(integer) 1
127.0.0.1:6379> EXISTS name1
(integer) 0
127.0.0.1:6379> move name 1 //移除当前的key到一号数据库
(integer) 1
127.0.0.1:6379> keys *
1) "age"
127.0.0.1:6379> set name liuyunxuan
OK
127.0.0.1:6379> keys *
1) "name"
2) "age"
127.0.0.1:6379> get name
"liuyunxuan"
127.0.0.1:6379> EXPIRE name 10 //设置key的过期时间,单位是秒
(integer) 1
127.0.0.1:6379> ttl name //查看当前key的剩余时间
(integer) 2
127.0.0.1:6379> ttl name
(integer) -2
127.0.0.1:6379> get name
(nil)
127.0.0.1:6379>
127.0.0.1:6379> type name //查看当前key的类型
string
127.0.0.1:6379> type age
string

不会的命令可以去官网查

String(字符串)

90%的程序员使用Redis只会使用一个String类型

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
127.0.0.1:6379> set key1 v1					//设置值
OK
127.0.0.1:6379> get key1 //获得值
"v1"
127.0.0.1:6379> keys * //获取所有的值
1) "key1"
127.0.0.1:6379> EXISTS key1 //判断某一个key是否存在
(integer) 1
127.0.0.1:6379> APPEND key1 "hello" //追加字符串,如果当前key不存在,就相当于setkey
(integer) 7
127.0.0.1:6379> get key1
"v1hello"
127.0.0.1:6379> STRLEN key1 //获取字符串的长度!
(integer) 7
127.0.0.1:6379> APPEND key1 ",liuyunxuan"
(integer) 18
127.0.0.1:6379> STRLEN key1
(integer) 18
127.0.0.1:6379> get key1
"v1hello,liuyunxuan"
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
127.0.0.1:6379> set views 0					//初始浏览量为0
OK
127.0.0.1:6379> get views
"0"
127.0.0.1:6379> incr views //自增1 浏览量+1
(integer) 1
127.0.0.1:6379> incr views
(integer) 2
127.0.0.1:6379> get views
"2"
127.0.0.1:6379> decr views //自减1 浏览量-1
(integer) 1
127.0.0.1:6379> decr views
(integer) 0
127.0.0.1:6379> decr views
(integer) -1
127.0.0.1:6379> get views
"-1"
127.0.0.1:6379> INCRBY views 10 //可以设置步长,指定增量!
(integer) 9
127.0.0.1:6379> DECRBY views 10
(integer) -1
127.0.0.1:6379>
java
1
2
3
4
5
6
7
8
127.0.0.1:6379> set key1 "hello,liuyunxuan"	//设置key1的值
OK
127.0.0.1:6379> get key1
"hello,liuyunxuan"
127.0.0.1:6379> GETRANGE key1 0 3 //截取字符串[0,3]
"hell"
127.0.0.1:6379> GETRANGE key1 0 -1 //获取全部字符串,和get key是一样的
"hello,liuyunxuan"
java
1
2
3
4
5
6
7
8
9
//替换
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"
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//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) 22
127.0.0.1:6379> ttl key3
(integer) 21
127.0.0.1:6379> ttl key3
(integer) 19
127.0.0.1:6379> setnx mykey "redis" //如果mykey不存在,创建mykey
(integer) 1
127.0.0.1:6379> keys *
1) "mykey"
2) "key1"
3) "key2"
127.0.0.1:6379> ttl key3
(integer) -2
127.0.0.1:6379> setnx mykey "MongoDB" //如果mykey存在!创建失败!
(integer) 0
127.0.0.1:6379> get mykey
"redis"
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//mset
//mget
127.0.0.1:6379> mset k1 v1 k2 v2 k3 v3 //同时设置多个值
OK
127.0.0.1:6379> keys *
1) "k3"
2) "k2"
3) "k1"
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)
java
1
2
3
4
5
6
7
8
//对象
set user:1 {name:zhangsan,age:3} //设置一个user:1 对象 值为 json字符来保存一个对象
//这里的key是一个巧妙的设计; 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"
java
1
2
3
4
5
6
7
8
9
10
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"
127.0.0.1:6379>

数据结构是相同的!

String类型的使用场景:value除了是我们的字符串还可以是我们的数字!

  • 计数器
  • 统计数量
  • 粉丝数
  • 对象缓存存储!

List(列表)

基本的数据类型,列表

在Redis中我们可以把list玩成栈,队列,阻塞队列!

所有list命令都是以l开头的

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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 righr //将一个值或者多个值,插入到列表头部(右)
(integer) 4
127.0.0.1:6379> LRANGE list 0 1
1) "three"
2) "two"
127.0.0.1:6379> LRANGE list 0 -1
1) "three"
2) "two"
3) "one"
4) "righr"
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
LPOP
RPOP
127.0.0.1:6379> LRANGE list 0 -1
1) "three"
2) "two"
3) "one"
4) "righr"
127.0.0.1:6379> LPOP list //移除list的第一个元素
"three"
127.0.0.1:6379> RPOP list //移除list的最后一个元素
"righr"
127.0.0.1:6379> LRANGE list 0 -1
1) "two"
2) "one"
127.0.0.1:6379>
java
1
2
3
4
5
6
7
8
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"
java
1
2
3
4
5
6
7
8
9
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> LLEN list //返回列表长度
(integer) 3
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
移除指定的值!
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> lrem list 2 three
(integer) 2
127.0.0.1:6379> LRANGE list 0 -1
1) "two"
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
trim 修剪 ; list 截断
127.0.0.1:6379> keys *
(empty array)
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"
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
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"
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
linsert //将某个具体的value插入到列表中某个元素的前面或者后面!
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中的值不能重复的!

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
127.0.0.1:6379> sadd myset "hello"				//set集合中添加元素
(integer) 1
127.0.0.1:6379> sadd myset "world"
(integer) 1
127.0.0.1:6379> sadd myset "china"
(integer) 1
127.0.0.1:6379> SMEMBERS myset //查看指定set的所有值
1) "hello"
2) "world"
3) "china"
127.0.0.1:6379> SISMEMBER myset hello //判断某一个值是不是在set集合中!
(integer) 1
127.0.0.1:6379> SISMEMBER myset nihao
(integer) 0
java
1
2
127.0.0.1:6379> SCARD myset						//获取set集合中的内容元素的个数
(integer) 3
java
1
2
3
4
5
6
7
8
9
127.0.0.1:6379> srem myset hello				//移除set集合中的指定元素
(integer) 1
127.0.0.1:6379> SCARD myset
(integer) 2
127.0.0.1:6379> SISMEMBER myset
(error) ERR wrong number of arguments for 'sismember' command
127.0.0.1:6379> SMEMBERS myset
1) "world"
2) "china"
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
set 无序不重复集合,随机抽取!
127.0.0.1:6379> SRANDMEMBER myset
"china"
127.0.0.1:6379> SRANDMEMBER myset
"china"
127.0.0.1:6379> SRANDMEMBER myset
"china"
127.0.0.1:6379> SRANDMEMBER myset
"world"
127.0.0.1:6379> SRANDMEMBER myset //随机抽出一个元素
"china"
127.0.0.1:6379> SRANDMEMBER myset 2 //随机抽选出指定个数的元素
1) "world"
2) "china"
java
1
2
3
4
5
6
7
8
删除指定的key,随机删除key!
127.0.0.1:6379> SMEMBERS myset
1) "world"
2) "china"
127.0.0.1:6379> spop myset //随机删除一些set集合中的元素
"world"
127.0.0.1:6379> SMEMBERS myset
1) "china"
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
将一个指定的值,移动到另外一个set集合!
127.0.0.1:6379> sadd myset hello
(integer) 1
127.0.0.1:6379> sadd myset world
(integer) 1
127.0.0.1:6379> sadd myset china
(integer) 1
127.0.0.1:6379> sadd myset2 set2
(integer) 1
127.0.0.1:6379> smove myset myset2 china //将一个指定的值,移动到另外一个set集合
(integer) 1
127.0.0.1:6379> SMEMBERS myset
1) "hello"
2) "world"
127.0.0.1:6379> SMEMBERS myset2
1) "set2"
2) "china"
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
数字集合类:
-差集
-交集
-并集
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 key1 key2 //并集
1) "a"
2) "b"
3) "c"
4) "d"
5) "e"

微博。A用户将所有关注的人放在一个set集合中!将它的粉丝也放在一个集合中!

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

Hash(哈希)

Map集合,key-<key,map>! 的时候 这个值是一个map集合! 本质和String类型没有太大区别,还是一个简单的key-value!

set myhash field liuyunxuan

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
127.0.0.1:6379> hset myhash field1 liuyunxuan			//set一个具体的key-value
(integer) 1
127.0.0.1:6379> hget myhash field1 //获取一个字段值
"liuyunxuan"
127.0.0.1:6379> hmset myhash field1 hello field2 world //set多个具体的key-value
OK
127.0.0.1:6379> hmget myhash field1 field2 //获取多个字段值
1) "hello"
2) "world"
127.0.0.1:6379> hgetall myhash //获取全部的数据
1) "field1"
2) "hello"
3) "field2"
4) "world"
127.0.0.1:6379>
127.0.0.1:6379> clear
127.0.0.1:6379> hgetall myhash
1) "field1"
2) "hello"
3) "field2"
4) "world"
127.0.0.1:6379> hdel myhash field1 //删除hash指定key的字段!对应的value值也就消失了
(integer) 1
127.0.0.1:6379> hgetall myhash
1) "field2"
2) "world"
java
1
2
3
4
5
6
7
8
9
10
11
12
hlen
127.0.0.1:6379> hmset myhash filed1 hello filed2 world
OK
127.0.0.1:6379> HGETALL myhash
1) "field2"
2) "world"
3) "filed1"
4) "hello"
5) "filed2"
6) "world"
127.0.0.1:6379> hlen myhash //获取hash表的字段数量
(integer) 3
java
1
2
3
4
5
6
127.0.0.1:6379> HEXISTS myhash field1			//判断hash中指定字段是否存在
(integer) 0
127.0.0.1:6379> HEXISTS myhash field3
(integer) 0
127.0.0.1:6379> HEXISTS myhash filed1
(integer) 1
java
1
2
3
4
5
6
7
8
9
10
//只获得所有的field
//只获得所有的value
127.0.0.1:6379> hkeys myhash //只获得所有的field
1) "field2"
2) "filed1"
3) "filed2"
127.0.0.1:6379> hvals myhash //只获得所有的value
1) "world"
2) "hello"
3) "world"
java
1
2
3
4
5
6
7
8
9
10
11
incr decr
127.0.0.1:6379> hset myhash filed3 5 //指定增量!
(integer) 1
127.0.0.1:6379> HINCRBY myhash filed3 1
(integer) 6
127.0.0.1:6379> HINCRBY myhash filed3 -1
(integer) 5
127.0.0.1:6379> hsetnx myhash filed4 hello //如果不存在可以设置
(integer) 1
127.0.0.1:6379> hsetnx myhash filed4 world //如果存在不可以设置
(integer) 0

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

Zset(有序集合)

在set的基础上,增加了一个值,set k1 v1 zset k1 score v1

java
1
2
3
4
5
6
7
8
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"
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
排序如何实现
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 lisi
(integer) 1
127.0.0.1:6379> zrangebyscore salary -inf +inf //显示全部用户 从小到大!
1) "lisi"
2) "xiaohong"
3) "zhangsan"
127.0.0.1:6379> zrangebyscore salary -inf +inf withscores //显示全部用户并且附带成绩
1) "lisi"
2) "500"
3) "xiaohong"
4) "2500"
5) "zhangsan"
6) "5000"
127.0.0.1:6379> zrangebyscore salary -inf 2500 withscores //显示工资小于2500的员工的升序排序!
1) "lisi"
2) "500"
3) "xiaohong"
4) "2500"
127.0.0.1:6379> ZREVRANGE salary 0 -1 //从大到小进行排序!
1) "zhangsan"
2) "lisi"
java
1
2
3
4
5
6
7
8
9
10
11
12
//移除rem中的元素
127.0.0.1:6379> zrange salary 0 -1
1) "lisi"
2) "xiaohong"
3) "zhangsan"
127.0.0.1:6379> zrem salary xiaohong //移除有序集合中的指定元素
(integer) 1
127.0.0.1:6379> zrange salary 0 -1
1) "lisi"
2) "zhangsan"
127.0.0.1:6379> zcard salary //获取有序集合中的个数
(integer) 2
java
1
2
3
4
5
6
7
8
127.0.0.1:6379> zadd myset 1 hello
(integer) 1
127.0.0.1:6379> zadd myset 2 world 3 liuyunxuan
(integer) 2
127.0.0.1:6379> zcount myset 1 3 //获取指定区间的成员数量
(integer) 3
127.0.0.1:6379> zcount myset 1 2
(integer) 2

其余的一些API,可以去官网查看。

案例思路:set 排序 存储班级成绩表,工资表排序!

普通消息,1.重要消息 2.带权重进行判断!

排行榜应用实现

三种特殊数据类型

geospatial(地理位置)

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

Redis的GEO在3.2版本就推出了!

geoadd

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//geoadd 添加地理位置
//规则:两级无法直接添加,我们一般会下载城市数据,直接通过java程序一次性导入
127.0.0.1:6379> geoadd china:city 34.26 108.96 xian
(error) ERR invalid longitude,latitude pair 34.260000,108.960000
//经度,维度不能超过限制
//参数 key 值 (经度,维度,名称)
127.0.0.1:6379> geoadd china:city 116.41 39.91 beijing
(integer) 1
127.0.0.1:6379> geoadd china:city 121.48 31.40 shanghai
(integer) 1
127.0.0.1:6379> geoadd china:city 106.54 29.40 chongqing
(integer) 1
127.0.0.1:6379> geoadd china:city 113.88 22.55 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

geopos

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

java
1
2
3
4
5
6
7
8
127.0.0.1:6379> geopos china:city beijing			//获取指定的城市的经度和纬度
1) 1) "116.40999823808670044"
2) "39.90999956664450821"
127.0.0.1:6379> geopos china:city beijing chongqing
1) 1) "116.40999823808670044"
2) "39.90999956664450821"
2) 1) "106.54000014066696167"
2) "29.39999880018641676"

geodist

两人之间的距离

  • m for 米.
  • km for 千米.
  • mi for 英里.
  • ft for 英尺.
java
1
2
3
4
127.0.0.1:6379> geodist china:city beijing shanghai km		//查看上海到北京的直线距离
"1051.1201"
127.0.0.1:6379> geodist china:city beijing chongqing km //查看重庆到北京的直线距离
"1475.0562"

georadius

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

获取指定数量的人,200.

所有的数据都应该录入:china:city。才会让结果更加精确

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
127.0.0.1:6379> GEORADIUS china:city 110 30 1000 km			//获取110 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) "340.8679"
2) 1) "xian"
2) "483.8340"
127.0.0.1:6379> GEORADIUS china:city 110 30 500 km withcoord //显示他人的定位信息
1) 1) "chongqing"
2) 1) "106.54000014066696167"
2) "29.39999880018641676"
2) 1) "xian"
2) 1) "108.96000176668167114"
2) "34.25999964418929977"
127.0.0.1:6379> GEORADIUS china:city 110 30 500 km withdist withcoord count 1 //筛选出指定的结果!
1) 1) "chongqing"
2) "340.8679"
3) 1) "106.54000014066696167"
2) "29.39999880018641676"
127.0.0.1:6379> GEORADIUS china:city 110 30 500 km withdist withcoord count 2
1) 1) "chongqing"
2) "340.8679"
3) 1) "106.54000014066696167"
2) "29.39999880018641676"
2) 1) "xian"
2) "483.8340"
3) 1) "108.96000176668167114"
2) "34.25999964418929977"
127.0.0.1:6379> GEORADIUS china:city 110 30 500 km withdist withcoord count 3
1) 1) "chongqing"
2) "340.8679"
3) 1) "106.54000014066696167"
2) "29.39999880018641676"
2) 1) "xian"
2) "483.8340"
3) 1) "108.96000176668167114"
2) "34.25999964418929977"

georadiusbymember

java
1
2
3
4
5
6
7
//找出位于指定元素周围的其他元素!
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字符串!

java
1
2
3
4
//将二维的经纬度转换为一维的字符串,如果两个字符串越接近,那么则距离越近!
127.0.0.1:6379> geohash china:city beijing chongqing
1) "wx4g0crhte0"
2) "wm5z22h53v0"

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

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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.7}

B {1.3.5.7.8}

基数(不重复的元素),可以接受误差!

简介

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

Redis Hyperloglog 基数统计的算法!

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

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

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

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

测试使用

java
1
2
3
4
5
6
7
8
9
10
11
12
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 b n m //创建第二组元素 mykey2
(integer) 1
127.0.0.1:6379> PFCOUNT mykey2
(integer) 9
127.0.0.1:6379> PFMERGE mykey3 mykey mykey2 //合并两组mykey mykey2 -> mykey3 并集
OK
127.0.0.1:6379> PFCOUNT mykey3 //查看并集的数量!
(integer) 15

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

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

bitmap

位存储

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

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

365天=365bit 1字节=8bit 46个字节左右!

测试

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//使用bitmap来记录 周一到周日的打卡!
//周一:1 周二:0 周三:0 周四:1 周五:1 周六:0 周日:0
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 0
(integer) 0
127.0.0.1:6379> SETBIT sign 3 1
(integer) 0
127.0.0.1:6379> SETBIT sign 4 1
(integer) 0
127.0.0.1:6379> SETBIT sign 5 0
(integer) 0
127.0.0.1:6379> SETBIT sign 6 0
(integer) 0
//查看某一天是否打卡
127.0.0.1:6379> GETBIT sign 3
(integer) 1
127.0.0.1:6379> GETBIT sign 6
(integer) 0
//统计打卡的天数
127.0.0.1:6379> BITCOUNT sign //统计这周打卡的记录,就可以看到是否全勤
(integer) 3

事务

redis事务本质:一组命令的集合!一个事务中的所有命令都会被序列化,执行过程中按照顺序执行
一次性,顺序性,排他性,执行一些列的命令

redis单条命令是保持原子性,但是事务不保证原子性
redis事务没有隔离级别的概念
所有的命令在事务中,并没有被直接执行,只有发起执行命令才会被执行!exec
redis事务
a.开启事务(multi)
b.命令入队
c.执行事务(exec)

Jeids

SpringBoot整合

Redis.conf详解

Redis持久化

Redis发布订阅

Redis主从复制

Redis缓存穿透和雪崩