Redis学习笔记,保姆级别!!学不会你打我!

其中资料参考:Redis官网文档百度百科狂神说陈哈哈的博客

Redis概述

什么是Redis

首先看一下百度百科的解释

Redis(Remote Dictionary Server ),即远程字典服务,是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。从2010年3月15日起,Redis的开发工作由VMware主持。从2013年5月开始,Redis的开发由Pivotal赞助。

同时Redis也是一种NoSQL(Not Only SQL 不仅仅是SQL)类型的数据库

小知识关于Pivotal:中国研发中心,成立于2013年4月 [1] ,目前其主要团队成员分布在北京和上海。并且与全球Pivotal团队一起,推动了Pivotal主导的Greenplum数据库,HDB (SQL on Hadoop)以及Cloud Foundry等知名产品的开源。Pivotal正在努力构建一个面向新时代的新平台,从而为企业级“平台即服务(PaaS)“和”大数据基础架构“设定标准,帮助行业企业向数字经济转型

为什么要用Redis?

Redis是眼下最为人熟知的缓解高并发、提升高可用能力的手段之一,再提升服务器性能方面效果显著。

这里不得不提到高并发场景,我们知道,并发场景下核心点在数据库,引入缓存(以及引入任何负载均衡、集群等策略)的目的都是在减轻数据库压力,让更多原本打到DB上的请求,在中间被拦截处理掉。

通俗易懂点儿说,高并发对服务器来说,就好比你被人锤一拳,这拳头可是硬的很,光着膀子的话一拳就给我干吐血。。那么我为了承受住这一拳?穿棉袄、穿护垫、穿…是吧,只要够厚,我都以为你在给我挠痒痒~同理,Redis就是一件又厚又弹的棉袄。

话说回来,它有多厚多弹呢?操作缓存就是直接操作内存,速度相当快,直接操作缓存能够承受的请求数是远远大于直接访问数据库的。

作者

redis 的作者,叫Salvatore Sanfilippo,来自意大利的西西里岛,居住在卡塔尼亚。目前供职于Pivotal公司。他使用的网名是antirez。

膜拜一下大神(不懂就问,他头发怎么这么多!?)

突发!Redis之父退出:不再维护Redis项目

性能

Redis:读的速度是110000次/s,写的速度是81000次/s !

Window安装

1 、下载安装包:https://github.com/dmajkic/redis/releases

2 、下载完毕得到压缩包

3 、解压到自己电脑上的环境目录下的就可以的!Redis 十分的小,只有5M

我的Redis安装目录,简单的说了一下配置文件,后面会详细说

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5H9HK6Ev-1625062670318)(C:\Users\QiaoZheng\AppData\Roaming\Typora\typora-user-images\image-20210629230716838.png)]

4 、开启Redis,双击运行服务即可!

运行成功就是这样的。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HOE193RY-1625062670320)(C:\Users\QiaoZheng\AppData\Roaming\Typora\typora-user-images\image-20210629230812508.png)]

5、双击redis-cli.exe就可以了,Ping一下显示成功

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0VDgQvmd-1625062670322)(C:\Users\QiaoZheng\AppData\Roaming\Typora\typora-user-images\image-20210629231144731.png)]

至此Window安装完成,非常简单!

不过Redis不推荐在Window下运行,一般都是安装Linux下的,Window版Redis已经停止更新好久了!

Linux安装

1 、去官网下载安装包!我的是 redis-6.2.4.tar.gz

2 、解压Redis的安装包! 程序一般放到 /opt 目录下

mv redis-6.2.3.tar.gz /opt

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

tar -zxvf redis-6.2.3.tar.gz

image-20210515231004876

image-20210515231111463

4 、基本的环境安装

yum install gcc-c++

5、进入redis解压目录下执行make命令

make

make install

6 、redis的默认安装路径/usr/local/bin

image-20210515231925392

7、将redis配置文件。复制到我们当前目录下

image-20210515232123274

8、redis默认不是后台启动的,修改配置文件!

image-20210515232321291

9、启动Redis服务

redis-server maoconfig/redis.conf

image-20210515232453274

10、使用redis-cli 进行连接测试!

redis-cli -p 6379

image-20210515232759601

测试性能

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

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

redis-benchmark 命令参数!

图片来自菜鸟教程:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-f6CrkNTZ-1625062670331)(C:\Users\QiaoZheng\AppData\Roaming\Typora\typora-user-images\image-20210629232815352.png)]

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

image-20210515234105056

image-20210515234452037

image-20210515234613748

基础的知识

Redis默认有16个库,默认是使用的0号数据库,可以使用select命令切换。

127 .0.0.1:6379> select 3 # 切换数据库
OK
127 .0.0.1:6379[3]> DBSIZE  # 查看DB大小!
(integer) 0

image-20210516200010388

127 .0.0.1:6379[3]> keys *  # 查看数据库所有的key
1 ) "name"

清除当前的数据库flushdb

清空全部数据库 flushall

127.0.0.1:6379[3]> flushdb
OK
127.0.0.1:6379[3]> keys *
(empty array)

Redis是单线程的

官网回答:

Redis is single threaded. How can I exploit multiple CPU / cores?

It’s not very frequent that CPU becomes your bottleneck with Redis, as usually Redis is either memory or network bound. For instance, using pipelining Redis running on an average Linux system can deliver even 1 million requests per second, so if your application mainly uses O(N) or O(log(N)) commands, it is hardly going to use too much CPU.

However, to maximize CPU usage you can start multiple instances of Redis in the same box and treat them as different servers. At some point a single box may not be enough anyway, so if you want to use multiple CPUs you can start thinking of some way to shard earlier.

You can find more information about using multiple Redis instances in the Partitioning page.

However with Redis 4.0 we started to make Redis more threaded. For now this is limited to deleting objects in the background, and to blocking commands implemented via Redis modules. For future releases, the plan is to make Redis more and more threaded.

上面是Redis官网给的解释(官方文档链接),翻译后简单说,因为Redis的瓶颈不是CPU的运行速度,而往往是网络带宽和机器的内存大小。再说了,单线程切换开销小,容易实现。既然单线程容易实现,而且CPU不会成为瓶颈,那就顺理成章地采用单线程的方案,当然了,也是为了避免多线程存在的很多坑。对了,一个节点是一个单线程

五大数据类型

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

Key的基本命令

127.0.0.1:6379> move name 1 # 将key为name的key移动到1号数据库
(integer) 1
127.0.0.1:6379> keys * #查看所有的key
1) "age"
127.0.0.1:6379> select 1
OK
127.0.0.1:6379[1]> keys *
1) "name"
127 .0.0.1:6379> EXISTS name  # 判断当前的key是否存在
(integer) 1
127 .0.0.1:6379> EXPIRE name 10 # 设置key的过期时间,单位是秒
(integer) 1
127 .0.0.1:6379> ttl name  # 查看当前key的剩余时间
(integer) 4
127 .0.0.1:6379> EXPIRE name 10 # 设置key的过期时间,单位是秒
(integer) 1
127 .0.0.1:6379> ttl name  # 查看当前key的剩余时间
(integer) 4
127 .0.0.1:6379> type name  # 查看当前key的一个类型!
string

有不会的命令可以去官方文档查!

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

String(字符串)

127.0.0.1:6379> set key1 v1		# 设置值
OK
127.0.0.1:6379> get key1		# 获得值
"v1"
127.0.0.1:6379> keys *			# 获得所有的key
1) "key1"
127.0.0.1:6379> exists key1		# 判断某一个key是否存在
(integer) 1
127.0.0.1:6379> append key1 hello  # 追加字符串,如果当前key不存在,就相当于set key
(integer) 7
127.0.0.1:6379> strlen key1		# 获取字符串的长度
(integer) 7
127.0.0.1:6379> set views 0
OK
127.0.0.1:6379> get views
"0"
127.0.0.1:6379> incr views		# 自增1  浏览量为1
(integer) 1
127.0.0.1:6379> decr views		# 自减1   浏览量-1
(integer) 0
127.0.0.1:6379> incrby views 10		#可以设置步长,指定增量
(integer) 10
# 字符串范围 range
127.0.0.1:6379> set key1 hello,mao		# 设置 key1 的值
OK
127.0.0.1:6379> get key1
"hello,mao"
127.0.0.1:6379> getrange key1 0 3		# 截取字符串 [0,3]
"hell"
127.0.0.1:6379> getrange key1 0 -1		# 获取全部的字符串 和 get key 是一样的
"hello,mao"
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) 26
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) "mykey"
3) "key1"
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"
127.0.0.1:6379> mset k1 v1 k2 v2 k3 v3		#同时设置多个值
OK
127.0.0.1:6379> mget k1 k2 k3		# 同时获取多个值
127.0.0.1:6379> msetnx k1 v1 k4 v4		# msetnx 是一个原子性的操作,要么一起成功,要么一起失败
(integer) 0
# 对象
127.0.0.1:6379> 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"
127.0.0.1:6379> getset db redis		# 先get在set 如果不存在值,则返回 nil
(nil)
127.0.0.1:6379> get db
"redis"
127.0.0.1:6379> getset db MongoDB		# 如果存在值,获取原来的值,并设置新的值
"redis"
127.0.0.1:6379> get db
"MongoDB"

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

  • 计数器
  • 统计多单位的数量
  • 粉丝数
  • 对象缓存存储!

List(列表)

image-20210516211032480

在redis里面,我们可以把list玩成 ,栈、队列、阻塞队列!

所有的list命令都是用l开头的,Redis不区分大小命令

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"
127.0.0.1:6379> lpop list 		# 移除list的第一个元素
"three"
127.0.0.1:6379> rpop list		# 移除list的最后一个元素
"right"
127.0.0.1:6379> lindex list 1		# 通过下标获得 list 中的某一个值!
"one"
127.0.0.1:6379> lindex list 0
"two"
127.0.0.1:6379> llen list		# 返回列表的长度
(integer) 2
127.0.0.1:6379> lrem list 1 one		# 移除list集合中指定个数的value,精确匹配
(integer) 1
#向list中添加两个three后测试
127.0.0.1:6379> lrem list 2 three
(integer) 2
#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> 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	# 在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	# 在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(集合)

跟Java一样Set中的值是不能重复的。

127.0.0.1:6379> sadd myset hello		# set集合中添加元素
(integer) 1
127.0.0.1:6379> sadd myset mao
(integer) 1
127.0.0.1:6379> sadd myset lovemao
(integer) 1
127.0.0.1:6379> smembers myset		# 查看指定set的所有值
1) "lovemao"
2) "hello"
3) "mao"
127.0.0.1:6379> SISMEMBER myset hello	#判断某一个值是不是在set集合中!,存在返回1,不存在返回0
(integer) 1
127.0.0.1:6379> SISMEMBER myset world
(integer) 0
127.0.0.1:6379> SCARD myset		# 获取set集合中的内容元素个数
(integer) 3
127.0.0.1:6379> sadd myset lovemao2
(integer) 1
127.0.0.1:6379> scard myset
(integer) 4
#srem 移除set集合中的指定元素
127.0.0.1:6379> srem myset hello		
(integer) 1
127.0.0.1:6379> scard myset
(integer) 3
127.0.0.1:6379> SMEMBERS myset
1) "mao"
2) "lovemao2"
3) "lovemao"
#set  无序不重复集合。抽随机!
127.0.0.1:6379> SMEMBERS myset
1) "mao"
2) "lovemao2"
3) "lovemao"
127.0.0.1:6379> SRANDMEMBER myset		# 随机抽选出一个元素
"lovemao2"
127.0.0.1:6379> SRANDMEMBER myset
"lovemao2"
127.0.0.1:6379> SRANDMEMBER myset
"lovemao2"
127.0.0.1:6379> SRANDMEMBER myset
"lovemao"
127.0.0.1:6379> SRANDMEMBER myset 2		#随机抽选出指定个数的元素
1) "lovemao2"
2) "mao"
127.0.0.1:6379> SRANDMEMBER myset 2
1) "lovemao2"
2) "mao"
127.0.0.1:6379> SRANDMEMBER myset 2
1) "lovemao2"
2) "lovemao"
#删除指定的key:srem,随机删除key:spop!
127.0.0.1:6379> SMEMBERS myset
1) "mao"
2) "lovemao2"
3) "lovemao"
127.0.0.1:6379> spop myset		# 随机删除一些set集合中的元素!
"lovemao"
127.0.0.1:6379> spop myset
"mao"
127.0.0.1:6379> SMEMBERS myset
1) "lovemao2"
# 将一个指定的值移动到另外一个set集合中
127.0.0.1:6379> sadd myset hello
(integer) 1
127.0.0.1:6379> sadd myset
(error) ERR wrong number of arguments for 'sadd' command
127.0.0.1:6379> sadd myset world
(integer) 1
127.0.0.1:6379> sadd myset mao
(integer) 1
127.0.0.1:6379> sadd myset2 set2
(integer) 1
127.0.0.1:6379> smove myset myset2 mao	# 将一个指定的值,移动到另外一个set集合中
(integer) 1
127.0.0.1:6379> SMEMBERS myset
1) "hello"
2) "world"
127.0.0.1:6379> SMEMBERS myset2
1) "mao"
2) "set2"
#数字集合类:
# - 差集 SDIFF
# - 交集 SINTER
# - 并集 SUNION
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) "c"
2) "b"
3) "e"
4) "a"
5) "d"

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

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

Hash(哈希)

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

127.0.0.1:6379> hset myhash field1 mao	# set一个具体的 key-value
(integer) 1
127.0.0.1:6379> hget myhash field1	# 获取一个字段值
"mao"
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> hdel myhash field1	# 删除hash指定的key字段!对应的value值也就消失了!
(integer) 1
127.0.0.1:6379> hgetall myhash
1) "field2"
2) "world"
#hlen  获取hash表的字段数量!
127.0.0.1:6379> hlen myhash	# 获取hash表的字段数量!
(integer) 2
127.0.0.1:6379> HEXISTS myhash field1	# 判断hash中指定字段是否存在!
(integer) 1
127.0.0.1:6379> HEXISTS myhash field3
(integer) 0
# 只获得所有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 hello	# 如果存在则不能设置
(integer) 0

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

Zset(有序集合)

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

127.0.0.1:6379> zadd myset 1 one		#添加一个值
(integer) 1
127.0.0.1:6379> zadd myset 2 two 3 three	#添加多个值
(integer) 2
127.0.0.1:6379> ZRANGE myset 0 -1
1) "one"
2) "two"
3) "three"
#排序如何实现
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 mao
(integer) 1
127.0.0.1:6379> ZRANGEBYSCORE salary -inf +inf	#显示全部的用户 从小到大
1) "mao"
2) "xiaohong"
3) "zhangsan"
127.0.0.1:6379> ZREVRANGE salary 0 -1	# 从大到小进行排序
1) "zhangsan"
2) "mao"
127.0.0.1:6379> ZRANGEBYSCORE salary -inf +inf withscores	# 显示全部的用户并且附带成绩
1) "mao"
2) "500"
3) "xiaohong"
4) "2500"
5) "zhangsan"
127.0.0.1:6379> ZRANGEBYSCORE salary -inf 2500 withscores	# 显示工资小于2500员工的升序排列
1) "mao"
2) "500"
3) "xiaohong"
4) "2500"
# 移除rem中的元素
127.0.0.1:6379> zrange salary 0 -1
1) "mao"
2) "xiaohong"
3) "zhangsan"
127.0.0.1:6379> zrem salary xiaohong	# 移除有序集合中的指定的元素
(integer) 1
127.0.0.1:6379> zrange salary 0 -1
1) "mao"
2) "zhangsan"
127.0.0.1:6379> zcard salary		# 获取有序集合中的个数
(integer) 2
127.0.0.1:6379> zadd myset 1 hello
(integer) 1
127.0.0.1:6379> zadd myset 2 world 3 mao
(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,带权重进行判断!

排行榜应用实现,取Top 值

三种特殊数据类型

Geospatial 地理位置

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

可以查询一些测试数据经纬度查询:https://jingweidu.bmcx.com/

只有 六个命令:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JLMuOK8e-1625062670334)(C:\Users\QiaoZheng\AppData\Roaming\Typora\typora-user-images\image-20210630123507724.png)]

GEOADD

官网的解释:

该命令以采用标准格式的参数x,y,所以经度必须在纬度之前。这些坐标的限制是可以被编入索引的,区域面积可以很接近极点但是不能索引。具体的限制,由EPSG:900913 / EPSG:3785 / OSGEO:41001 规定如下:

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

当坐标位置超出上述指定范围时,该命令将会返回一个错误。

127.0.0.1:6379> geoadd china:city 34.23 108.93 nanchang
(error) ERR invalid longitude,latitude pair 34.230000,108.930000
127.0.0.1:6379> geoadd china:city 116.23 40.22 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.21 30.20 hangzhou
(integer) 1
127.0.0.1:6379> geoadd china:city 108.93 34.23 xian
(integer) 1

GEOPOS

key里返回所有给定位置元素的位置(经度和纬度)。

GEOPOS 命令返回一个数组, 数组中的每个项都由两个元素组成: 第一个元素为给定位置元素的经度, 而第二个元素则为给定位置元素的纬度

当给定的位置元素不存在时, 对应的数组项为空值。

127.0.0.1:6379> GEOPOS china:city beijing	# 获取指定的城市的经度和纬度!
1) 1) "116.23000055551528931"
   2) "40.2200010338739844"
127.0.0.1:6379> GEOPOS china:city chongqing
1) 1) "106.54000014066696167"
   2) "29.39999880018641676"
127.0.0.1:6379> GEOPOS china:city beijing chongqing
1) 1) "116.23000055551528931"
   2) "40.2200010338739844"
2) 1) "106.54000014066696167"
   2) "29.39999880018641676"

GEODIST

返回两个给定位置之间的距离。

如果两个位置之间的其中一个不存在, 那么命令返回空值。

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

  • m 表示单位为米。
  • km 表示单位为千米。
  • mi 表示单位为英里。
  • ft 表示单位为英尺。

如果用户没有显式地指定单位参数, 那么 GEODIST 默认使用米作为单位。

GEODIST 命令在计算距离时会假设地球为完美的球形, 在极限情况下, 这一假设最大会造成 0.5% 的误差。

127.0.0.1:6379> GEODIST china:city beijing shanghai km	# 查看上海到北京的直线距离
"1088.7854"
127.0.0.1:6379> GEODIST china:city beijing chongqing km	# 查看重庆到北京的直线距离
"1491.6716"

GEORADIUS

以给定的经纬度为中心, 返回键包含的位置元素当中, 与中心的距离不超过给定最大距离的所有位置元素

范围可以使用以下其中一个单位:

  • m 表示单位为米。
  • km 表示单位为千米。
  • mi 表示单位为英里。
  • ft 表示单位为英尺。

在给定以下可选项时, 命令会返回额外的信息:

  • WITHDIST: 在返回位置元素的同时, 将位置元素与中心之间的距离也一并返回。 距离的单位和用户给定的范围单位保持一致。
  • WITHCOORD: 将位置元素的经度和维度也一并返回。
  • WITHHASH: 以 52 位有符号整数的形式, 返回位置元素经过原始 geohash 编码的有序集合分值。 这个选项主要用于底层应用或者调试, 实际中的作用并不大。

命令默认返回未排序的位置元素。 通过以下两个参数, 用户可以指定被返回位置元素的排序方式:

  • ASC: 根据中心的位置, 按照从近到远的方式返回位置元素。
  • DESC: 根据中心的位置, 按照从远到近的方式返回位置元素。

在默认情况下, GEORADIUS 命令会返回所有匹配的位置元素。 虽然用户可以使用 COUNT <count> 选项去获取前 N 个匹配元素, 但是因为命令在内部可能会需要对所有被匹配的元素进行处理, 所以在对一个非常大的区域进行搜索时, 即使只使用 COUNT 选项去获取少量元素, 命令的执行速度也可能会非常慢。 但是从另一方面来说, 使用 COUNT 选项去减少需要返回的元素数量, 对于减少带宽来说仍然是非常有用的。

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) "481.1540"
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.92999857664108276"
      2) "34.23000121926852302"
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) "481.1540"
   3) 1) "108.92999857664108276"
      2) "34.23000121926852302"

GEORADIUSBYMEMBER

这个命令和 GEORADIUS命令一样, 都可以找出位于指定范围内的元素, 但是 GEORADIUSBYMEMBER的中心点是由给定的位置元素决定的, 而不是像GEORADIUS 那样, 使用输入的经度和纬度来决定中心点

指定成员的位置被用作查询的中心。

# 找出位于指定元素周围的其他元素!
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

返回一个或多个位置元素的 Geohash表示。

通常使用表示位置的元素使用不同的技术,使用Geohash位置52点整数编码。由于编码和解码过程中所使用的初始最小和最大坐标不同,编码的编码也不同于标准。此命令返回一个标准的Geohash,在维基百科和[geohash.org]网站都有相关描述

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

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

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

Hyperloglog

官网:

A HyperLogLog is a probabilistic data structure used in order to count unique things (technically this is referred to estimating the cardinality of a set). Usually counting unique items requires using an amount of memory proportional to the number of items you want to count, because you need to remember the elements you have already seen in the past in order to avoid counting them multiple times. However there is a set of algorithms that trade memory for precision: you end with an estimated measure with a standard error, in the case of the Redis implementation, which is less than 1%. The magic of this algorithm is that you no longer need to use an amount of memory proportional to the number of items counted, and instead can use a constant amount of memory! 12k bytes in the worst case, or a lot less if your HyperLogLog (We’ll just call them HLL from now) has seen very few elements.

HLLs in Redis, while technically a different data structure, is encoded as a Redis string, so you can call GET to serialize a HLL, and SET to deserialize it back to the server.

Conceptually the HLL API is like using Sets to do the same task. You would SADD every observed element into a set, and would use SCARD to check the number of elements inside the set, which are unique since SADD will not re-add an existing element.

While you don’t really add items into an HLL, because the data structure only contains a state that does not include actual elements, the API is the same:

  • Every time you see a new element, you add it to the count with PFADD.
  • Every time you want to retrieve the current approximation of the unique elements added with PFADD so far, you use the PFCOUNT.

我的英文水平是在有限,百度翻译的结果:

*Hyperloglog是一种概率数据结构,用于计数唯一的事物(从技术上来说,这是指估计集合的基数)。通常,计算唯一项需要使用与要计数的项目数量成比例的内存,因为您需要记住过去已经看到的元素,以避免多次计数。然而,有一组算法将内存转换为精度:在Redis实现的情况下,您以一个标准错误的估计度量结束,这个值小于1%。这个算法的神奇之处在于,您不再需要使用与计数的项目数量成比例的内存量,而是可以使用恒定的内存量!最坏情况下是12k字节,如果Hyperloglog(我们从现在开始将它们称为HLL)很少看到元素,那么就少了很多。*

Redis中的HLL虽然技术上是不同的数据结构,但它被编码为Redis字符串,因此您可以调用GET来序列化HLL,并将其设置为反序列化回服务器。

从概念上讲,HLL API就像使用集合来完成相同的任务。您将将每个观察到的元素添加到一个集合中,并将使用SCARD检查集合中的元素数量,因为SADD不会重新添加现有元素,因此该元素是唯一的。

虽然您没有真正将项目添加到HLL中,因为数据结构只包含不包含实际元素的状态,因此API是相同的:

每次看到新元素时,都会使用PFADD将其添加到计数中。

每次您想要检索到目前为止添加到PFADD中的唯一元素的当前近似值时,都使用PFCOUNT。

简单来说

HyperLogLog主要解决大数据应用中的非精确计数(大概有0.81% 错误率!可能多也可能少,但是会在一个合理的范围)操作,它可以接受多个元素作为输入,并给出输入元素的基数估算值,基数指的是集合中不同元素的数量。比如 {'apple', 'banana', 'cherry', 'banana', 'apple'} 的基数就是 3 。 HyperLogLog 的优点是,即使输入元素的数量或者体积非常非常大,计算基数所需的空间总是固定的、并且是很小的。在 Redis 里面,每个 HyperLogLog 键只需要花费 12 KB 内存,就可以计算接近 2^64 个不同元素的基数。这和计算基数时,元素越多耗费内存就越多的集合形成鲜明对比。

127.0.0.1:6379> pfadd myset a b c d e f g h i j	# 创建第一组元素
(integer) 1
127.0.0.1:6379> PFCOUNT myset	# 统计 mykey 中元素的基数数量
(integer) 10
127.0.0.1:6379> pfadd myset2 i jj z x c v b n m	#创建第二组元素 mykey2
(integer) 1
127.0.0.1:6379> PFCOUNT myset2
(integer) 9
127.0.0.1:6379> pfmerge mykey3 myset myset2	# 合并两组 mykey mykey2 =》mykey3并集
OK
127.0.0.1:6379> pfcount mykey3	# 查看并集的数量
(integer) 16

Bitmap

官网:

Bitmaps are not an actual data type, but a set of bit-oriented operations defined on the String type. Since strings are binary safe blobs and their maximum length is 512 MB, they are suitable to set up to 2^32 different bits.

Bit operations are divided into two groups: constant-time single bit operations, like setting a bit to 1 or 0, or getting its value, and operations on groups of bits, for example counting the number of set bits in a given range of bits (e.g., population counting).

One of the biggest advantages of bitmaps is that they often provide extreme space savings when storing information. For example in a system where different users are represented by incremental user IDs, it is possible to remember a single bit information (for example, knowing whether a user wants to receive a newsletter) of 4 billion of users using just 512 MB of memory.

翻译:

bitmap不是实际的数据类型,而是在字符串类型上定义的一组面向位的操作。由于字符串是二进制安全blob,其最大长度为512MB,因此适合设置为2^32个不同的位。

位操作分为两组:恒定时间的单位操作,如将位设置为1或0,或获取其值;以及对位组的操作,如对给定位范围内的设置位数进行计数(如总体计数)。

位图最大的优点之一是,在存储信息时,它们通常可以极大地节省空间。例如,在用增量用户id表示不同用户的系统中,仅使用512MB内存就可以记住40亿用户的一位信息(例如,知道用户是否想要接收时事通讯)。

使用SETBIT和GETBIT命令设置和检索位:

位存储

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

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

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

示例

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

周一打卡:1 周二打卡:1…

image-20210518202441951

查看某一天是否打卡!

127.0.0.1:6379> getbit sign 3
(integer) 1
127.0.0.1:6379> getbit sign 5
(integer) 0

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

127.0.0.1:6379> bitcount sign   # 统计这周的打卡天数(integer) 4
(integer) 4

事物

MULTI、 EXEC 、 DISCARD`和 WATCH是 Redis 事务相关的命令。事务可以一次执行多个命令, 并且带有以下两个重要的保证:

  • 事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。

  • 事务是一个原子操作:事务中的命令要么全部被执行,要么全部都不执行。

EXEC 命令负责触发并执行事务中的所有命令:

  • 如果客户端在使用 MULTI开启了一个事务之后,却因为断线而没有成功执行 EXEC,那么事务中的所有命令都不会被执行。
  • 另一方面,如果客户端成功在开启事务之后执行 EXEC ,那么事务中的所有命令都会被执行。

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

当使用 AOF 方式做持久化的时候, Redis 会使用单个 write(2) 命令将事务写入到磁盘中。

然而,如果 Redis 服务器因为某些原因被管理员杀死,或者遇上某种硬件故障,那么可能只有部分事务命令会被成功写入到磁盘中。

如果 Redis 在重新启动时发现 AOF 文件出了这样的问题,那么它会退出,并汇报一个错误。

使用redis-check-aof程序可以修复这一问题:它会移除 AOF 文件中不完整事务的信息,确保服务器可以顺利启动。

从 2.2 版本开始,Redis 还可以通过乐观锁(optimistic lock)实现 CAS (check-and-set)操作

用法

  • 开启事务(multi)

  • 命令入队(…)

  • 执行事务(exec)

    正常执行

127.0.0.1:6379> multi		# 开启事务
OK
127.0.0.1:6379(TX)> set k1 v1
QUEUED
127.0.0.1:6379(TX)> set k2 v2
QUEUED
127.0.0.1:6379(TX)> get k2
QUEUED
127.0.0.1:6379(TX)> set k3 v3
QUEUED
127.0.0.1:6379(TX)> exec		# 执行事务
1) OK
2) OK
3) "v2"
4) OK

放弃事物:

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

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

127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> set k1 v1
QUEUED
127.0.0.1:6379(TX)> set k2 v2
QUEUED
127.0.0.1:6379(TX)> set k3 v3
QUEUED
127.0.0.1:6379(TX)> getset k3		# 错误的命令
(error) ERR wrong number of arguments for 'getset' command
127.0.0.1:6379(TX)> set k4 v4
QUEUED
127.0.0.1:6379(TX)> set k5 v5
QUEUED
127.0.0.1:6379(TX)> exec		# 执行事务报错!
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> get k5		# 所有的命令都不会被执行!
(nil)

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

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

监控watch

悲观锁:

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

乐观锁:

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

  • 获取version

  • 更新的时候比较 version

watch:标记所有指定的key 被监视起来,在事务中有条件的执行(乐观锁)。

Redis测监视测试

正常执行:

127.0.0.1:6379> set money 100
OK
127.0.0.1:6379> set out 0
OK
127.0.0.1:6379> watch money		# 监视 money 对象
OK
127.0.0.1:6379> multi		# 事务正常结束,数据期间没有发生变动,这个时候就正常执行成功!
OK
127.0.0.1:6379(TX)> DECRBY money 20
QUEUED
127.0.0.1:6379(TX)> INCRBY out 20
QUEUED
127.0.0.1:6379(TX)> exec
1) (integer) 80
2) (integer) 20

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

127.0.0.1:6379> watch money		# 监视 money
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> DECRBY money 10
QUEUED
127.0.0.1:6379(TX)> INCRBY out 10
QUEUED
127.0.0.1:6379(TX)> exec		# 执行之前另外一个线程修改了这个money,就会导致事务执行失败!
(nil)

如果修改失败,获取最新的值就好

image-20210518205812951

策略

LRU是Redis唯一支持的回收方法。Redis的maxmemory指令用于将可用内存限制成一个固定大小,还包括了Redis使用的LRU算法,这个实际上只是近似的LRU。

Maxmemory配置指令

maxmemory配置指令用于配置Redis存储数据时指定限制的内存大小。通过redis.conf可以设置该指令,或者之后使用CONFIG SET命令来进行运行时配置。

例如为了配置内存限制为100mb,以下的指令可以放在redis.conf文件中。

maxmemory 100mb

设置maxmemory为0代表没有内存限制。对于64位的系统这是个默认值,对于32位的系统默认内存限制为3GB。

当指定的内存限制大小达到时,需要选择不同的行为,也就是策略。 Redis可以仅仅对命令返回错误,这将使得内存被使用得更多,或者回收一些旧的数据来使得添加数据时可以避免内存限制。

内存淘汰策略

当maxmemory限制达到的时候Redis会使用的行为由 Redis的maxmemory-policy配置指令来进行配置。

以下的策略是可用的:

  • noeviction:返回错误当内存限制达到并且客户端尝试执行会让更多内存被使用的命令(大部分的写入指令,但DEL和几个例外)
  • allkeys-lru: 尝试回收最少使用的键(LRU),使得新添加的数据有空间存放。
  • volatile-lru: 尝试回收最少使用的键(LRU),但仅限于在过期集合的键,使得新添加的数据有空间存放。
  • allkeys-random: 回收随机的键使得新添加的数据有空间存放。
  • volatile-random: 回收随机的键使得新添加的数据有空间存放,但仅限于在过期集合的键。
  • volatile-ttl: 回收在过期集合的键,并且优先回收存活时间(TTL)较短的键,使得新添加的数据有空间存放。

简单介绍一下LRU淘汰机制

在这里插入图片描述

LRU(Least recently used),淘汰最近最少使用的;算法根据数据的历史访问记录来进行淘汰数据,其核心思想是“如果数据最近被访问过,那么将来被访问的几率也更高”。

算法思路:

  • 新数据插入到链表头部;
  • 每当缓存命中(即缓存数据被访问),则将数据移到链表头部;
  • 当链表满的时候,将链表尾部的数据丢弃。

在服务器配置中保存了 lru 计数器 server.lrulock,会定时(redis 定时程序 serverCorn())更新,server.lrulock 的值是根据 server.unixtime 计算出来进行排序的,然后选择最近使用时间最久的数据进行删除。另外,从 struct redisObject 中可以发现,每一个 redis 对象都会设置相应的 lru。每一次访问数据,会更新对应redisObject.lru

在Redis中,LRU算法是一个近似算法,默认情况下,Redis会随机挑选5个键,并从中选择一个最久未使用的key进行淘汰。在配置文件中,按maxmemory-samples选项进行配置,选项配置越大,消耗时间就越长,但结构也就越精准。

过期策略

过期策略和内存淘汰策略乍一看相似,但却不是一个事儿。我们知道,往 redis 写入的数据会消失很正常的,在生产环境的 redis 经常会丢掉一些数据,写进去了,过一会儿可能就没了?这不是BUG。因为redis 是缓存,不是数据库。

缓存主要存在内存中,内存空间是有限的,比如 redis 就只能用 10G,你要是往里面写了 20G 的数据,会咋办?当然会干掉 10G 的数据,保留另外 10G 数据。那么干掉哪些数据,保留哪些数据呢?当然要根据热度干掉不常用的数据。

数据已经过期了,怎么还占用着内存?这是由 redis 的过期策略来决定。

在Redis中过期的key不会立刻从内存中删除,而是会同时以下面两种策略进行删除:

  • 定期删除:每隔一段时间,随机检查设置了过期的key并删除已过期的key;维护定时器消耗CPU资源;
  • 惰性删除:当key被访问时检查该key的过期时间,若已过期则删除;已过期未被访问的数据仍保持在内存中,消耗内存资源;

定期删除:

Redis每 100ms 进行一次过期扫描:

  1. 随机取20个设置了过期策略的key;
  2. 检查20个key中过期时间中已过期的key并删除;
  3. 如果有超过25%的key已过期则重复第一步;

这种循环随机操作会持续到过期key可能仅占全部key的25%以下时,并且为了保证不会出现循环过多的情况,默认扫描时间不会超过25ms;

惰性删除:

定期删除可能会导致很多过期 key 到了时间并没有被删除掉,那咋整呢?所以就是惰性删除了。这就是说,在你获取某个 key 的时候,redis 会检查一下 ,这个 key 如果设置了过期时间那么是否过期了?如果过期了此时就会删除,不会给你返回任何东西。

获取 key 的时候,如果此时 key 已经过期,就删除,不会返回任何东西。

但是实际上这还是有问题的,如果定期删除漏掉了很多过期 key,然后你也没及时去查,也就没走惰性删除,此时会怎么样?如果大量过期 key 堆积在内存里,导致 redis 内存块耗尽了,这就要用到上面说的内存淘汰策略了。

Jedis

Jedis 是 Redis 官方推荐的 java连接开发工具! 使用Java 操作Redis 中间件!如果你要使用java操作redis,那么一定要对Jedis 十分的熟悉!

测试

1 、导入对应的依赖

    <!--导入jedis的包-->
    <dependencies>
        <!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>3.6.0</version>
        </dependency>

        <!--fastjson-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.60</version>
        </dependency>
    </dependencies>

2 、编码测试:

  • 连接数据库
public class TestPing {
    public static void main(String[] args) {
        // 1、new Jedis 对象即可
        Jedis jedis = new Jedis("127.0.0.1", 6379);
        // jedis 所有的命令就是我们之前学习的所有指令
        System.out.println(jedis.ping());
    }
}
  • 操作命令

  • 断开连接!

  • 输出:

image-20210518211611577

Reids的命令在Jedis中变成了一个个的方法!

SpringBoot整合

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

SpringData 也是和 SpringBoot 齐名的项目!

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

jedis : 采用的直连,多个线程操作的话,是不安全的,如果想要避免不安全的,使用 jedis pool 连接池! 更像 BIO 模式

lettuce : 采用netty,实例可以再多个线程中进行共享,不存在线程不安全的情况!可以减少线程数据了,更像 NIO 模式

源码分析:

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

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

整合测试

1 、导入依赖

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

2 、配置连接

# Springboot 所有的配置类,都有一个自动配置类 RedisAutoConfiguration
# 自动配置类都会绑定一个 properties 配置文件 RedisProperties
# 配置redis
spring:
  redis:
    host: 127.0.0.1
    port: 6379

3 、测试!

@SpringBootTest
class Redis02SpringbootApplicationTests {


	@Autowired
	private RedisTemplate redisTemplate;

	@Test
	void contextLoads() {

		// redisTemplate 操作不同的数据类型,api和我们的指令是一样的
		// opsForValue 操作字符串 类似String
		// opsForList 操作List 类型List
		// opsForSet
		// opsForHash
		// opsForZSet
		// opsForGeo
		// opsForHyperLogLog
		// 除了基本的操作,我们常用的方法都可以直接通过 redisTemplate 操作,比如事务,和基本的crud
		/* 获取redis的连接对象
		RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
		connection.flushDb();
		connection.flushAll();*/
		redisTemplate.opsForValue().set("mykey","关注狂神说公众号");
		System.out.println(redisTemplate.opsForValue().get("mykey"));
	}

}

image-20210519205527214

image-20210519205732105

image-20210519210859444

我们来编写一个自己的 RedisTemplate

固定模板

@Configuration
public class RedisConfig {
    @Bean("myRedisTemplate")
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        //默认配置为了方便写成<String, Object>
        RedisTemplate<String, Object> template = new RedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);
        //Json序列化
        Jackson2JsonRedisSerializer<Object> objectJackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<Object>(Object.class);
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        /*该漏洞是由于Jackson框架enableDefaultTyping方法存在Java反序列化代码执行漏洞,
        攻击者利用漏洞可在服务器主机上执行任意代码或系统指令,取得网站服务器的控制权。
        objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);*/
        //替代方法activateDefaultTyping
        objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance ,
                ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);

        //序列化String
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        //key采用String的序列化方式
        template.setKeySerializer(stringRedisSerializer);
        //hash的key也采用String的的序列化方式
        template.setHashKeySerializer(stringRedisSerializer);
        //value采用json的序列化方式
        template.setValueSerializer(objectJackson2JsonRedisSerializer);
        //hash的value采用json的的序列化方式
        template.setHashValueSerializer(objectJackson2JsonRedisSerializer);
        //非spring注入使用RedisTemplate,需先调用afterPropertiesSet()方法
        template.afterPropertiesSet();
        return template;
    }
}

所有的redis操作,其实对于java开发人员来说,十分的简单,更重要的是要去理解redis的思想和每一种数据结构的用处和作用场景!

redis.conf详解

单位

image-20210519214335523

包含

image-20210519214516731

就是好比我们学习Spring、Import、include可以将多个配置文件包含

网络

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

通用 GENERAL

daemonize yes	# 以守护进程的方式运行,默认是no,我们需要自己开启为yes!

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

# 日志
# Specify the server verbosity level.
# This can be one of:
# debug (a lot of information, useful for development/testing)
# verbose (many rarely useful info, but not a mess like the debug level)
# notice (moderately verbose, what you want in production probably) 生产环境适用
# warning (only very important / critical messages are logged)
loglevel notice
logfile ""	# 日志的文件位置名
databases 16	# 数据库的数量,默认是16个数据库
always-show-logo no	# 是否总是显示 logo

快照 SNAPSHOTTING

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

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

# 如果3600秒内,如果至少有一个 key 进行了修改,我们就进行持久化操作
save 3600 1
# 如果300秒内,如果至少有10个 key 进行了修改,我们就进行持久化操作
save 300 10
# 如果60秒内,如果至少有10000个 key 进行了修改,我们就进行持久化操作
save 60 10000
# 我们之后学习持久化,会自己定义这个设置!

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

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

rdbchecksum yes	# 保存 rdb 的时候,进行错误的检查校验!

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

SECURITY 安全

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

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

CLIENTS限制

maxclients 10000	# 设置能连接上redis的最大客户端的数量!
maxmemory <bytes>	# redis 配置最大的内存容量
maxmemory-policy noeviction	# 内存达到上限之后的处理策略
	# 移除一些过期的 key
	# 报错
	# ...
    1 、volatile-lru:只对设置了过期时间的key进行LRU(默认值)
    2 、allkeys-lru : 删除lru算法的key
    3 、volatile-random:随机删除即将过期key
    4 、allkeys-random:随机删除
    5 、volatile-ttl : 删除即将过期的
    6 、noeviction : 永不过期,返回错误

APPEND ONLY MODE 模式 (aof配置)

appendonly no	# 默认是不开启aof模式的,默认是使用rdb方式持久化的,在大部分所有的情况下,rdb完全够用!
appendfilename "appendonly.aof"	# 持久化的文件的名字

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

Redis持久化

Redis 提供了不同级别的持久化方式:

  • RDB持久化方式能够在指定的时间间隔能对你的数据进行快照存储.
  • AOF持久化方式记录每次对服务器写的操作,当服务器重启的时候会重新执行这些命令来恢复原始的数据,AOF命令以redis协议追加保存每次写的操作到文件末尾.Redis还能对AOF文件进行后台重写,使得AOF文件的体积不至于过大.
  • 如果你只希望你的数据在服务器运行的时候存在,你也可以不使用任何持久化方式.
  • 你也可以同时开启两种持久化方式, 在这种情况下, 当redis重启的时候会优先载入AOF文件来恢复原始的数据,因为在通常情况下AOF文件保存的数据集要比RDB文件保存的数据集要完整.
  • 最重要的事情是了解RDB和AOF持久化方式的不同,让我们以RDB持久化方式开始:

RDB(快照)

什么是RDB

在主从复制中,rdb就是备用了!从机上面!

在指定的时间间隔内将内存中的数据集快照写入磁盘,也就是行话讲的Snapshot快照,它恢复时是将快照文件直接读到内存里。Redis会单独创建(fork)一个子进程来进行持久化,会先将数据写入到一个临时文件中,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件。整个过程中,主进程是不进行任何IO操作的。这就确保了极高的性能。如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那RDB方式要比AOF方式更加的高效。RDB的缺点是最后一次持久化后的数据可能丢失。我们默认的就是RDB,一般情况下不需要修改这个配置!

rdb保存的文件是dump.rdb

工作方式

当 Redis 需要保存 dump.rdb 文件时, 服务器执行以下操作:

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

这种工作方式使得 Redis 可以从写时复制(copy-on-write)机制中获益。

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

可以在配置文件中进行相关配置:

image-20210520195804095

image-20210520200107454

触发机制

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

2 、执行 flushall 命令,也会触发我们的rdb规则!

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

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

image-20210520200658389

如何恢复RBD文件

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

2 、查看需要存在的位置

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

RDB的优点

  • RDB是一个非常紧凑的文件,它保存了某个时间点的数据集,非常适用于数据集的备份,比如你可以在每个小时保存一下过去24小时内的数据,同时每天保存过去30天的数据,这样即使出了问题你也可以根据需求恢复到不同版本的数据集.
  • RDB是一个紧凑的单一文件,很方便传送到另一个远端数据中心或者亚马逊的S3(可能加密),非常适用于灾难恢复.
  • RDB在保存RDB文件时父进程唯一需要做的就是fork出一个子进程,接下来的工作全部由子进程来做,父进程不需要再做其他IO操作,所以RDB持久化方式可以最大化redis的性能.
  • 与AOF相比,在恢复大的数据集的时候,RDB方式会更快一些.

RDB的缺点

  • 如果你希望在redis意外停止工作(例如电源中断)的情况下丢失的数据最少的话,那么RDB不适合你.虽然你可以配置不同的save时间点(例如每隔5分钟并且对数据集有100个写的操作),是Redis要完整的保存整个数据集是一个比较繁重的工作,你通常会每隔5分钟或者更久做一次完整的保存,万一在Redis意外宕机,你可能会丢失几分钟的数据.
  • RDB 需要经常fork子进程来保存数据集到硬盘上,当数据集比较大的时候,fork的过程是非常耗时的,可能会导致Redis在一些毫秒级内不能响应客户端的请求.如果数据集巨大并且CPU性能不是很好的情况下,这种情况会持续1秒,AOF也需要fork,但是你可以调节重写日志文件的频率来提高数据集的耐久度.

AOF(Append Only File)

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

什么是AOF

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

Aof保存的是 appendonly.aof 文件

append

image-20210520201705996

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

appendonly no	# 默认是不开启aof模式的,默认是使用rdb方式持久化的,在大部分所有的情况下,rdb完全够用!
appendfilename "appendonly.aof"	# 持久化的文件的名字

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

# rewrite

重启,redis 就可以生效了!

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

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

image-20210520202444654

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

重写规则说明

image-20210520203003627

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

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

工作原理

AOF 重写和 RDB 创建快照一样,都巧妙地利用了写时复制机制:

  • Redis 执行 fork() ,现在同时拥有父进程和子进程。
  • 子进程开始将新 AOF 文件的内容写入到临时文件。
  • 对于所有新执行的写入命令,父进程一边将它们累积到一个内存缓存中,一边将这些改动追加到现有 AOF 文件的末尾,这样样即使在重写的中途发生停机,现有的 AOF 文件也还是安全的。
  • 当子进程完成重写工作时,它给父进程发送一个信号,父进程在接收到信号之后,将内存缓存中的所有数据追加到新 AOF 文件的末尾。
  • 搞定!现在 Redis 原子地用新文件替换旧文件,之后所有命令都会直接追加到新 AOF 文件的末尾。

AOF 优点

  • 使用AOF 会让你的Redis更加耐久: 你可以使用不同的fsync策略:无fsync,每秒fsync,每次写的时候fsync.使用默认的每秒fsync策略,Redis的性能依然很好(fsync是由后台线程进行处理的,主线程会尽力处理客户端请求),一旦出现故障,你最多丢失1秒的数据.

  • AOF文件是一个只进行追加的日志文件,所以不需要写入seek,即使由于某些原因(磁盘空间已满,写的过程中宕机等等)未执行完整的写入命令,你也也可使用redis-check-aof工具修复这些问题.

  • Redis 可以在 AOF 文件体积变得过大时,自动地在后台对 AOF 进行重写: 重写后的新 AOF 文件包含了恢复当前数据集所需的最小命令集合。 整个重写操作是绝对安全的,因为 Redis 在创建新 AOF 文件的过程中,会继续将命令追加到现有的 AOF 文件里面,即使重写过程中发生停机,现有的 AOF 文件也不会丢失。 而一旦新 AOF 文件创建完毕,Redis 就会从旧 AOF 文件切换到新 AOF 文件,并开始对新 AOF 文件进行追加操作。

  • AOF 文件有序地保存了对数据库执行的所有写入操作, 这些写入操作以 Redis 协议的格式保存, 因此 AOF 文件的内容非常容易被人读懂, 对文件进行分析(parse)也很轻松。 导出(export) AOF 文件也非常简单: 举个例子, 如果你不小心执行了 FLUSHALL 命令, 但只要 AOF 文件未被重写, 那么只要停止服务器, 移除 AOF 文件末尾的 FLUSHALL 命令, 并重启 Redis , 就可以将数据集恢复到 FLUSHALL 执行之前的状态。

AOF 缺点

  • 对于相同的数据集来说,AOF 文件的体积通常要大于 RDB 文件的体积。
  • 根据所使用的 fsync 策略,AOF 的速度可能会慢于 RDB 。 在一般情况下, 每秒 fsync 的性能依然非常高, 而关闭 fsync 可以让 AOF 的速度和 RDB 一样快, 即使在高负荷之下也是如此。 不过在处理巨大的写入载入时,RDB 可以提供更有保证的最大延迟时间(latency)。

、性能建议

  • 因为RDB文件只用作后备用途,建议只在Slave上持久化RDB文件,而且只要 15 分钟备份一次就够了,只保留 save 900 1 这条规则。
  • 如果Enable AOF ,好处是在最恶劣情况下也只会丢失不超过两秒数据,启动脚本较简单只load自己的AOF文件就可以了,代价一是带来了持续的IO,二是AOF rewrite 的最后将 rewrite 过程中产生的新数据写到新文件造成的阻塞几乎是不可避免的。只要硬盘许可,应该尽量减少AOF rewrite的频率,AOF重写的基础大小默认值64M太小了,可以设到5G以上,默认超过原大小100%大小重写可以改到适当的数值。
  • 如果不Enable AOF ,仅靠 Master-Slave Repllcation 实现高可用性也可以,能省掉一大笔IO,也减少了rewrite时带来的系统波动。代价是如果Master/Slave 同时挂掉,会丢失十几分钟的数据,启动脚本也要比较两个 Master/Slave 中的 RDB文件,载入较新的那个,微博就是这种架构。

Redis发布订阅

Redis 发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息。微信、微博、关注系统!

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

订阅/发布消息图:

image-20210520205008926

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

下图展示了频道 channel1 , 以及订阅这个频道的三个客户端 —— client2 、 client5 和 client1 之间的关系:

image-20210520205141004

当有新消息通过 PUBLISH 命令发送给频道 channel1 时, 这个消息就会被发送给订阅它的三个客户端:

image-20210520205209640

命令

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

image-20210520205303771

测试

订阅端:

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

发送端:

127.0.0.1:6379> PUBLISH test "hello,test"	# 发布者发送消息到频道!
(integer) 1
127.0.0.1:6379> PUBLISH test "hello,redis"	# 发布者发送消息到频道!
(integer) 1

原理

Redis是使用C实现的,通过分析 Redis 源码里的 pubsub.c 文件,了解发布和订阅机制的底层实现,籍此加深对 Redis 的理解。

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 、高可用(集群)基石:除了上述作用以外,主从复制还是哨兵和集群能够实施的基础,因此说主从复制是Redis高可用的基础。

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

1 、从结构上,单个Redis服务器会发生单点故障,并且一台服务器需要处理所有的请求负载,压力较大;

2 、从容量上,单个Redis服务器内存容量有限,就算一台Redis服务器内存容量为256G,也不能将所有内存用作Redis存储内存,一般来说,单台Redis最大使用内存不应该超过20G。

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

对于这种场景,我们可以使如下这种架构:

image-20210520211238731

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

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

环境配置

只配置从库,不用配置主库!

127.0.0.1:6379> info replication		#查看当前库的信息
# Replication
role:master		# 角色  master
connected_slaves:0		# 没有从机
master_failover_state:no-failover
master_replid:30a335d3ec84363d738343b2e57607f0ef6cca90
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

复制 3 个配置文件,然后修改对应的信息

1 、端口

2 、pid 名字

3 、log文件名字

4 、dump.rdb 名字

修改完毕之后,启动我们的 3 个redis服务器,可以通过进程信息查看!

image-20210520213559496

一主二从

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

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

127.0.0.1:6380> SLAVEOF 127.0.0.1 6379		# SLAVEOF host 6379 找谁当自己的老大!临时的,宕机重启又会变成主节点
OK
127.0.0.1:6380> info replication
# Replication
role:slave		# 当前角色是从机
master_host:127.0.0.1		# 可以看到主机的信息
master_port:6379
master_link_status:up
master_last_io_seconds_ago:6
master_sync_in_progress:0
slave_repl_offset:0
slave_priority:100
slave_read_only:1
replica_announced:1
connected_slaves:0
master_failover_state:no-failover
master_replid:6c48ecaf27edad5631781b5432548f10acde2cec
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:0
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:0
###############################################################
# 在主机中查看!
127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:1		# 多了从机的配置
slave0:ip=127.0.0.1,port=6380,state=online,offset=28,lag=1		# 多了从机的配置
master_failover_state:no-failover
master_replid:6c48ecaf27edad5631781b5432548f10acde2cec
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:28
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:28

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

image-20210520214352916

真实的从主配置应该在配置文件中配置,这样的话是永久的,我们这里使用的是命令,暂时的!关闭redis服务后再重启就没有了。

细节

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

主机写:

image-20210520214917078

从机只能读取内容!

image-20210520214957623

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

复制原理

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

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

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

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

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

层层链路

上一个M链接下一个 S!

image-20210520220346763

这时候也可以完成我们的主从复制!

如果没有老大了,这个时候能不能选择一个老大出来呢? 没有哨兵模式情况下需要手动配置!

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

哨兵模式

(自动选举老大的模式)

概述

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

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

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

image-20210520221658955

这里的哨兵有两个作用

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

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

image-20210520221919792

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

测试

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

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

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

后面的这个数字 1 ,表示至少要多少个sentinel哨兵一致认为主机挂掉了,那么才会触发failover【故障转移】操作,我们这里因为只有一个sentinel哨兵,假设有三个哨兵,那么这个数字就可以设置为2。哨兵投票看让谁接替成为主机,票数最多的,就会成为主机!

2 、启动哨兵!

[root@lloam bin]# redis-sentinel maoconfig/sentinel.conf
19968:X 20 May 2021 22:35:51.879 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
19968:X 20 May 2021 22:35:51.879 # Redis version=6.2.3, bits=64, commit=00000000, modified=0, pid=19968, just started
19968:X 20 May 2021 22:35:51.879 # Configuration loaded
19968:X 20 May 2021 22:35:51.879 * monotonic clock: POSIX clock_gettime
                _._                                                  
           _.-``__ ''-._                                             
      _.-``    `.  `_.  ''-._           Redis 6.2.3 (00000000/0) 64 bit
  .-`` .-```.  ```\/    _.,_ ''-._                                  
 (    '      ,       .-`  | `,    )     Running in sentinel mode
 |`-._`-...-` __...-.``-._|'` _.-'|     Port: 26379
 |    `-._   `._    /     _.-'    |     PID: 19968
  `-._    `-._  `-./  _.-'    _.-'                                   
 |`-._`-._    `-.__.-'    _.-'_.-'|                                  
 |    `-._`-._        _.-'_.-'    |           https://redis.io       
  `-._    `-._`-.__.-'_.-'    _.-'                                   
 |`-._`-._    `-.__.-'    _.-'_.-'|                                  
 |    `-._`-._        _.-'_.-'    |                                  
  `-._    `-._`-.__.-'_.-'    _.-'                                   
      `-._    `-.__.-'    _.-'                                       
          `-._        _.-'                                           
              `-.__.-'                                               

19968:X 20 May 2021 22:35:51.880 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.
19968:X 20 May 2021 22:35:51.882 # Sentinel ID is 3555ee07f208305027d0e90ca9a47de93b3c99f3
19968:X 20 May 2021 22:35:51.882 # +monitor master myredis 127.0.0.1 6379 quorum 1
19968:X 20 May 2021 22:35:51.883 * +slave slave 127.0.0.1:6380 127.0.0.1 6380 @ myredis 127.0.0.1 6379
19968:X 20 May 2021 22:35:51.885 * +slave slave 127.0.0.1:6381 127.0.0.1 6381 @ myredis 127.0.0.1 6379

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

image-20210520224201764

哨兵日志

image-20210520224334799

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

优点:

1 、哨兵集群,基于主从复制模式,所有的主从配置优点,它全有

2 、 主从可以切换,故障可以转移,系统的可用性就会更好

3 、哨兵模式就是主从模式的升级,手动到自动,更加健壮!

缺点:

1 、Redis 不好在线扩容的,集群容量一旦到达上限,在线扩容就十分麻烦!

2 、实现哨兵模式的配置其实是很麻烦的,里面有很多选择!

# Example sentinel.conf

# 哨兵sentinel实例运行的端口 默认 26379
port 26379

# 哨兵sentinel的工作目录
dir /tmp

# 哨兵sentinel监控的redis主节点的 ip port
# master-name 可以自己命名的主节点名字 只能由字母A-z、数字0-9 、这三个字符".-_"组成。
# quorum 配置多少个sentinel哨兵统一认为master主节点失联 那么这时客观上认为主节点失联了
# sentinel monitor <master-name> <ip> <redis-port> <quorum>
sentinel monitor mymaster 127 .0.0.1 6379 2

# 当在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无
#法正常启动成功。
#通知脚本
# shell编程
# 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缓存穿透和雪崩

Redis缓存的使用,极大的提升了应用程序的性能和效率,特别是数据查询方面。但同时,它也带来了一些问题。其中,最要害的问题,就是数据的一致性问题,从严格意义上讲,这个问题无解。如果对数据的一致性要求很高,那么就不能使用缓存。

另外的一些典型问题就是,缓存穿透、缓存雪崩和缓存击穿。目前,业界也都有比较流行的解决方案。

缓存穿透

image-20210521204551606

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

解决方案

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

image-20210521205047971

缓存空对象

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

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

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

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

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

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

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

解决方案

设置热点数据永不过期

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

加互斥锁

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

image-20210521210320816

缓存雪崩

缓存雪崩,是指在某一个时间段,缓存集中过期失效。Redis 宕机!缓存穿透的升级版

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

image-20210521210743763

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

解决方案

redis高可用

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

限流降级

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

数据预热

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

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值