Redis 个人学习笔记

Redis (Remote Dictionary Server)

【狂神说Java】Redis最新超详细版教程通俗易懂 | bilibili

外链图片正常显示的浏览地址

概述

Redis 能干嘛

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

Redis 特性

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

学习中需要用到的东西

官网:https://redis.io/

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

Linux 安装

  1. 下载安装包

  2. 程序建议放在 /opt 目录下

  3. 解压 tar -zvxf
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HqqVXaKR-1609299330682)(https://raw.githubusercontent.com/Satone7/Typora_PicGo/master/image/20201201130844.png)]

  4. 基本的环境安装

    yum install gcc-c++
    # 默认安装gcc版本为4.8.5,而安装Redis 6.0.9版本时会因为gcc版本过低(至少要gcc 8),make失败,报错:[server.o] Error 1
    # 解决办法。升级gcc,升级过程如下:
    yum -y install centos-release-scl
    yum -y install devtoolset-9-gcc devtoolset-9-gcc-c++ devtoolset-9-binutils
    scl enable devtoolset-9 bash
    echo "source /opt/rh/devtoolset-9/enable" >> /etc/profile
    

    解决办法引用自:https://blog.csdn.net/weixin_40836179/article/details/108245436

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-H2eFLIhG-1609299330686)(https://raw.githubusercontent.com/Satone7/Typora_PicGo/master/image/20201201130845.png)]

  5. Redis的默认安装路径:/usr/local/bin

  6. 将Redis配置文件,复制到当前目录下 /usr/local/bin/redis-config
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YnWSZ29y-1609299330687)(https://raw.githubusercontent.com/Satone7/Typora_PicGo/master/image/20201201130846.png)]

  7. Redis默认不是后台启动,修改配置文件daemonize设置,将其设置为后台启动
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8IM88hiZ-1609299330690)(https://raw.githubusercontent.com/Satone7/Typora_PicGo/master/image/20201201130847.png)]
    将no修改为yes

  8. 通过指定的配置文件启动Redis服务
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SfI0bBJ8-1609299330691)(https://raw.githubusercontent.com/Satone7/Typora_PicGo/master/image/20201201130848.png)]

  9. 通过客户端连接本机Redis
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ph2ylck5-1609299330693)(https://raw.githubusercontent.com/Satone7/Typora_PicGo/master/image/20201201130849.png)]

  10. 查看Redis的进程是否开启
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-R5l9zgm2-1609299330695)(https://raw.githubusercontent.com/Satone7/Typora_PicGo/master/image/20201201130850.png)]

  11. 关闭Redis服务 shutdown
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-P2S6BzyV-1609299330696)(https://raw.githubusercontent.com/Satone7/Typora_PicGo/master/image/20201201130851.png)]

性能测试

redis-benchmark是官方自带的压力测试工具。

图片来自Redis 性能测试 | 菜鸟教程 (runoob.com):

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JoG28axf-1609299330697)(https://raw.githubusercontent.com/Satone7/Typora_PicGo/master/image/20201201130852.png)]

# 测试:100个并发连接,每个并发10w请求
redis-benchmark -h localhost -p 6379 -c 100 -n 10000

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VPnygYOM-1609299330698)(https://raw.githubusercontent.com/Satone7/Typora_PicGo/master/image/20201201130853.png)]

基础知识

Redis默认有16个数据

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CRtZbZVo-1609299330698)(https://raw.githubusercontent.com/Satone7/Typora_PicGo/master/image/20201201130854.png)]

默认使用的是第0个,可以使用select进行切换

127.0.0.1:6379> ping  # 测试连通状况
PONG
127.0.0.1:6379> select 3 # 切换到3号数据库
OK
127.0.0.1:6379[3]> dbsize # 当前数据库大小
(integer) 0

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1hQrb4Wa-1609299330699)(https://raw.githubusercontent.com/Satone7/Typora_PicGo/master/image/20201201130855.png)]

127.0.0.1:6379[7]> keys * # 查看当前数据库所有key值
1) "name"
127.0.0.1:6379[7]> flushdb # 清空当前数据库
OK
127.0.0.1:6379[7]> keys *
(empty array)
127.0.0.1:6379[7]> FLUSHALL # 清空全部数据库
OK

Redis是单线程的(6.0以下)

Redis是很快的,官方表示,Redis是基于内存操作,CPU不是Redis性能瓶颈,而是机器的内存和网络带宽。

Redis是基于C语言,官方提供的数据位10w+的QPS,完全不比同样的kv键值数据库Memecache差。

Redis为什么单线程还这么快?
  1. 误区1:高性能的服务器一定是多线程的?
  2. 误区2:多线程一定比单线程效率高?

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

基本命令

> set <key> <value>
> get <key>
> keys *
> flushdb
> flushall
> move <key> <db>	# 移动
> del <key>		# 移除
> exists <key>	# 判断是否存在
> expire <key> <seconds>	# 设置过期时间,单位秒
> tll <key> # (time to live)查看还有多久过期(-1 表示不会过期,-2 表示当前库中没有/已经过期)
> type <key>	# 查看的类型

官网命令帮助文档:Redis命令中心(Redis commands) – Redis中国用户组(CRUG)

五大数据类型

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RJFSvN7w-1609299330700)(https://raw.githubusercontent.com/Satone7/Typora_PicGo/master/image/20201201130856.png)]

String(字符串)

127.0.0.1:6379> set key1 "v1"
OK
127.0.0.1:6379> append key1 "v2"	# 向已有的中追加,返回追加后的字符串长度;若不存在将等价于set
(integer) 4
127.0.0.1:6379> strlen key1		# 获得字符串长度
(integer) 4
127.0.0.1:6379> set views 0
OK
127.0.0.1:6379> incr views	# 整数原子+1
(integer) 1
127.0.0.1:6379> decr views	# 整数原子-1
(integer) 0
127.0.0.1:6379> get views
"0"
incrby(decrby) <key> <increment(decrement)> # 设置步长,增加(减少)
# 字符串范围 range
127.0.0.1:6379> set key1 "hello, world"
OK
127.0.0.1:6379> getrange key1  5	# 截取字符串[0:5],可以使用负数定位,与Python类似
"hello,"
127.0.0.1:6379> setrange key1 2 "resetv3"	# 从指定位置开始用新的覆盖
(integer) 12
127.0.0.1:6379> get key1
"heresetv3rld"
> setex <key> <seconds> <value>	# (set with expire) 设置过期时间,如果已存在则用覆盖原来的内容
> setnx <key> <value> 			# (set if not exist) 如果不存在就set,返回1;如果已经存在就无作用,返回0
> mset <key> <value> [<key> <value> ...]	# 可以批量设置键值对
> mget <key> [<key> ...]					# 批量get
> msetnx <key> <value> [<key> <value> ...]	# 批量使用setnx,原子性操作,要么全都成功,要么全都失败。

# 对象
> set user:1 {name:zhangsan,age:13}	# set一个json字符串
> mset user:1:name zhangsan user:1:age 13	# 通过mset批量设置对象单个值
# user:{id}:{filed} 如此设计在Redis中是完全可以的
127.0.0.1:6379> mset user:1:name zhangsan user:1:age 13
OK
127.0.0.1:6379> mget user:1:name user:1:age
1) "zhangsan"
2) "13"

> getset <key> <value>	# 先get再set
127.0.0.1:6379> getset key2 3232
(nil)								# 如果原来不存在值则返回nil
127.0.0.1:6379> getset key2 323
"3232"								# 如果原来存在则返回旧值并设置新值
127.0.0.1:6379> get key2
"323"
String类型的使用场景

value除了是字符串还可以是数字

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

List(列表)

可以做栈、队列、阻塞队列等。

几乎所有的list命令都是用l开头

> lpush <key> <value>	# 将一个或多个值插入列表头部(左)
> rpush <key> <value>	# 尾部(右)
> lrange <key> <start> <end>	# 获取列表指定区间的值
> lpop <key>					# 左移除
> rpop <key>					# 右移除
> lindex <key> <index(int)>		# 获取列表的第个值
> llen <key>					# 列表长度
> lrem <key> <count> <element>	# 移除列表中
> ltrim <key> <start> <end>		# 截取列表从区间的元素
> rpoplpush <source> <destination>	# 移除列表中队尾元素添加至列表队头
> lset <key> <index> <value>		# 将已存在的列表的第值替换,不能超过列表的范围(更新操作)
> linsert <key> <BEFORE|AFTER> <pivot> <element>	# 将某个具体的插入列表中某个元素的前面或者后面。
127.0.0.1:6379> linsert list1 BEFORE two insertItem
(integer) 3
127.0.0.1:6379> lrange list1 0 -1
1) "one"
2) "insertItem"
3) "two"
List 小结
  • list实际上是一个链表,before Node after,left,right都可以插入值
  • 如果key不存在,创建新的链表
  • 如果key存在,新增内容
  • 如果移除了列表中的所有值,那么key值所表示的列表将不存在
  • 在列表两边插入或改动值,效率最高;中间元素效率相对较低

Set(集合)

set中的值不能重复

> sadd <key> <value>	# 添加元素,不可重复(return 0/1)
> smembers <key>			# 展示集合中的所有元素
> sismember <key> <member>	# 判断某个元素是否在集合中(return 0/1)
> scard <key>					# 集合中的元素个数
> srem <key> <member> [<member> ...]	# 移除
> srandmember <key> [<count>]	# 在集合中随机抽选个元素,默认1
> spop <key> [<count>]		# 随机移除个元素,默认1
> smove <source> <distination> <member>		# 将集合中移动到集合中
> sdiff <key> [<key> ...]		# 差集
> sinter <key> [<key> ...]		# 交集
> sunion <key> [<key> ...]		# 并集

Hash(哈希)

Map集合,key-Map集合。(Java中的Map约等于Python中的字典)本质和String类型没有太大区别,还是一个简单的key-value

> hset <key> <field> <value> [<field> <value> ...]		# 存值,是map的键值对,不会覆盖
> hget <key> <field>		# 取值
> hmset		# 同时设置多个,会覆盖
> hmget
> hgetall <key>		# 获取哈希中的所有键值对
> hdel <key> <field> [<field> ...]		# 删除
> hlen <key>		# 长度
> hexists <key> <field>		# 判断哈希中的指定字段是否存在
> hkeys / hvals	<key>		# 哈希的全部键/值
> hincrby <key> <field> <increment(int)>	# 哈希中字段的值按照步长自增
> hsetnx <key> <field> <value>		# 不存在则set,存在则不成功

127.0.0.1:6379> hset hash1 field1 satone
(integer) 1
127.0.0.1:6379> hset hash1 field1 satone
(integer) 0
127.0.0.1:6379> hmset hash1 field1 hello field2 world
OK
127.0.0.1:6379> hmget hash1 field2 field1
1) "world"
2) "hello"
127.0.0.1:6379> hgetall hash1
1) "field1"
2) "hello"
3) "field2"
4) "world"
127.0.0.1:6379> hdel hash1 field1
(integer) 1
127.0.0.1:6379> hlen hash1
(integer) 1
127.0.0.1:6379> hkeys hash1
1) "field2"
Hash小结
  • 存放一些经常变动的数据,如用户信息保存
  • 是String类型中key-json对的更优的解决方案

Zset(有序集合)

在set的基础上增加了一个值。

> zadd <key> [NX|XX] [CH] [INCR] <score> <member> [<score> <member> ...]
> zrange <key> <start> <stop>
> zrangebyscore <key> <min> <max>	[WITHSCORES]	# 从最小值到最大值排序.(-inf,+inf 负无穷,正无穷) 默认为闭区间,可用"(int"取开区间
> zrem <key> <member> [<member> ...]	# 移除
> zcard <key>		# 元素个数
> zrevrange <start> <stop>		# 可以倒序排列
127.0.0.1:6379> ZREVRANGE s 0 -1
1) "zhangsan"
2) "xiaohong"
3) "lisi"

> zcount <key> <min> <max>		# 指定区间内的元素个数
案例思路
  • 班级成绩表、工资表排序
  • 普通消息、重要消息,带权值进行排序
  • 排行榜排序

三种特殊数据类型

geospatial 地理位置

key - (经度、纬度、成员名称)

经度的取值范围:[-180, 180]

纬度的取值范围:[-85.05112878, 85.05112878]

Redis的Geo在Redis3.2版本推出,这个功能可以推算地理位置信息,比如两地之间的距离等。

底层

Geo的底层实现是Zset数据类型,可以使用所有Zset命令

127.0.0.1:6379> type chain:city
zset

Google地图

Geo专用的只有6个命令

> geoadd <key> <longitude> <latitude> <member> [<longitude> <latitude> <member> ...]		# 经度、维度、成员(南北极无法直接添加,一般会下载城市数据,通过程序一次性导入)
> geodist <key> <member1> <member2> [m|km|ft|mi]	# 获取两点直线距离
> geohash <key> <member> [<member> ...]		# 获取返回11个字符的GeoHash,缩短字符、损失精度
> geopos <key> <member> [<member> ...]	# 获取成员的经纬度(坐标值)
> georadius <key> <longitude> <latitude> <radius> <m|km|ft|mi> [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT <count>] [ASC|DESC] [STORE <key>]	# 某个位置半径内的成员(WITHCOORD:输出经纬度,WITHDIST:输出距离,WITHHASH:输出HASH,COUNT :最多输出个结果,ASC|DESC:升序|降序)
> georadiusbymember <key> <member> <radius> <m|km|ft|mi> [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT <count>] [ASC|DESC] [STORE <key>]		# 某个成员半径内的成员

127.0.0.1:6379> geoadd chain:city 116.40 39.90 Beijing
(integer) 1
127.0.0.1:6379> geoadd chain:city 121.47 31.23 Shanghai
(integer) 1
127.0.0.1:6379> geoadd chain:city 106.50 29.53 Chongqing
(integer) 1
127.0.0.1:6379> geoadd chain:city 114.05 22.52 Shenzhen 120.16 30.24 Hangzhou 108.96 34.26 xian
(integer) 3
127.0.0.1:6379> geodist chain:city Beijing Shanghai
"1067378.7564"
127.0.0.1:6379> geodist chain:city Beijing Shanghai km
"1067.3788"
127.0.0.1:6379> geopos chain:city Shanghai
1) 1) "121.47000163793563843"
   2) "31.22999903975783553"
127.0.0.1:6379> geohash chain:city Beijing
1) "wx4fbxxfke0"
127.0.0.1:6379> georadius chain:city 110 30 1000 km WITHDIST COUNT 2 ASC
1) 1) "Chongqing"
   2) "341.9374"
2) 1) "xian"
   2) "483.8340"
127.0.0.1:6379> georadius chain:city 110 30 1000 km WITHDIST COUNT 2 DESC
1) 1) "Hangzhou"
   2) "977.5143"
2) 1) "Shenzhen"
   2) "924.6408"

Hyperloglog 基数

基数:不重复的元素的数量,可以接受误差

简介

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

Redis Hyperloglog 基数统计算法

优点:占用的内存是固定的,2^64不同的元素的基数,只需要使用12KB内存。如果从内存角度来比较,Hyperloglog为首选。

官方表示,Hyperloglog基数统计有0.81%错误率,但是在大部分应用场景可以忽略。如果不允许容错,则不推荐使用Hyperloglog。

底层

127.0.0.1:6379> pfadd hyper 1 2 3
(integer) 1
127.0.0.1:6379> type hyper
string
127.0.0.1:6379> get hyper
"HYLL\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80]f\x80Mt\x80Q,\x8cC\xf3"

应用场景

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

传统方式:用set保存用户的ID,然后统计set中的元素数量作为标准判断

这个方式如果保存大量的用户ID就会比较麻烦。但是最终的目的是为了计数,并不是为了保存用户ID。

专属命令

> pfadd <key> <element> [<elememt> ...]		# 添加
> pfcount <key> [<key> ...]		# 统计
> pfmerge <destkey> <sourcekey> [<sourcekey> ...]		# 合并(会去重)

Bitmaps 位图

应用场景

统计用户信息,区分活跃用户和不活跃用户,登录用户和未登录用户

设计365天打卡

只有两个状态的都可以使用Bitmaps

底层

127.0.0.1:6379> setbit bitmap1 0 0
(integer) 0
127.0.0.1:6379> type bitmap1
string
127.0.0.1:6379> get bitmap1
"\x00"

命令

> setbit <key> <offset> <value>		# 使用漂移量按位设置
> getbit <key> <offset>	
> bitcount <key> [<start> <end>]	# 统计

事务

MySQL中的事务

  • 原子性:要么同时成功,要么同时失败

Redis中的事务

Redis事务的本质:一组命令的集合(队列),在事务执行的过程中,会按照顺序执行。

一次性、顺序性、排他性(执行过程中不允许被干扰)

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

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

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

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

Redis事务:

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

执行事务示例

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

放弃事务

127.0.0.1:6379> multi
OK
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> set k3 v3
QUEUED
127.0.0.1:6379> discard			  # 放弃事务
OK
127.0.0.1:6379> get k3				# 命令队列里的所有命令都不会被执行
(nil)

异常

  • 编译型异常(代码/命令有错):事务中所有命令都不会被执行

    127.0.0.1:6379> multi
    OK
    127.0.0.1:6379> set k1 v1
    QUEUED
    127.0.0.1:6379> set k3 v3
    QUEUED
    127.0.0.1:6379> getset k3
    (error) ERR wrong number of arguments for 'getset' command
    127.0.0.1:6379> set k4 v4
    QUEUED
    127.0.0.1:6379> exec
    (error) EXECABORT Transaction discarded because of previous errors.
    127.0.0.1:6379> get k4
    (nil)
    127.0.0.1:6379> get k3
    (nil)
    127.0.0.1:6379> get k1
    (nil)
    
  • 运行时异常,如果事务队列中存在语法性异常,那么命令执行时,其他命令是可以正常执行的,错误命令单独抛出异常(Redis事务不存在原子性)

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

    监控:Watch

    悲观锁:

    • 很悲观,认为任意时刻都可能出问题,无论做什么都会加锁(影响性能)

    乐观锁:

    • 很乐观,认为任意时刻都不会出现问题,无论做什么都不上锁。更新数据的时候,判断在此期间是否有人修改过数据。1. 获取;2. 更新

    • 使用watch可以给加乐观锁,当事务执行过程中被其他客户端修改,则会导致事务执行失败。

      127.0.0.1:6379> watch money
      OK
      127.0.0.1:6379> decrby money 20		# 在watch加监视后修改了money
      (integer) 80
      127.0.0.1:6379> multi
      OK
      127.0.0.1:6379> decrby money 40
      QUEUED
      127.0.0.1:6379> incrby out 40
      QUEUED
      127.0.0.1:6379> exec		# money在事务外被修改,导致事务执行失败
      (nil)
      127.0.0.1:6379> unwatch		# 解除监视
      OK
      

Jedis

使用Java操作Redis

Jedis是Redis官方推荐的Java连接开发工具,是使用Java操作Redis的中间件。

Redis.conf

配置文件

单位

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OpmUHyQ1-1609299330700)(https://raw.githubusercontent.com/Satone7/Typora_PicGo/master/image/20201202183431.png)]

对大小写不敏感

包含(Include)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IsyFuJx7-1609299330701)(https://raw.githubusercontent.com/Satone7/Typora_PicGo/master/image/20201202183437.png)]

网络

bind 127.0.0.1 						# 绑定的IP
protected-mode yes					# 保护模式,默认开启
port 6379									# 端口
tcp-backlog 511
timeout 0
tcp-keepalive 300

通用(General)

daemonize yes			# 以守护进程的方式运行,默认为no
supervised no			# 管理守护进程,默认no,一般不用动
pidfile /var/run/redis_6379.pid			# 如果以后台方式运行,需要指定pid进程文件

# 日志
# Specify the server verbosity level.
# This can be one of:
# debug (a lot of information, useful for development/testing)
# verbose (many rarely useful info, but not a mess like the debug level)
# notice (moderately verbose, what you want in production probably) 适用生产环境
# warning (only very important / critical messages are logged) 只打印非常关键的信息
loglevel notice

logfile ""			# 日志的文件位置名
databases 16		# 数据库数量,默认16
always-show-logo yes			# 是否总是显示logo

快照(Snapshotting)

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

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

save 900 1			# 900秒内如果至少有1个key进行了修改,就进行持久化操作
save 300 10			# 300秒内如果有至少10个key进行了修改,就进行持久化操作
save 60 10000		# 60秒内如果有至少1w个key进行了修改,就进行持久化操作

stop-writes-on-bgsave-error yes 	# 持久化如果出错,知否还继续工作,默认yes
rdbcompression yes		# 是否压缩rdb文件,默认yes
rdbchecksum yes				# 是否校验rdb文件
dbfilename dump.rdb			# rdb保存时的文件名
rdb-del-sync-files no		# 
dir ./									# rdb保存的目录

复制(Replication)

安全 (Security)

requirepass foobared 		# 设置密码,默认被注释掉,无密码

127.0.0.1:6379> config get requirepass		# 获取Redis密码
1) "requirepass"
2) ""
127.0.0.1:6379> config set requirepass "123123"		# 设置Redis密码
OK
127.0.0.1:6379> exit
[root@VM-8-2-centos ~]# redis-cli
127.0.0.1:6379> ping		# 有密码,无权限
(error) NOAUTH Authentication required.
127.0.0.1:6379> auth 123123		# 登录
OK
127.0.0.1:6379> ping		# 正常使用
PONG

限制(Clients)

maxclients 10000		# 最大连接客户端数
maxmemory <bytes>		# 配置最大内存容量
maxmemory-policy noeviction			# 配置内存到达上限的处理策略

Redis中的六种垃圾回收策略:redis中maxmemory和淘汰策略 - 简书 (jianshu.com)

volatile-lru:从已设置过期时间的内存数据集中挑选最近最少使用的数据 淘汰;

volatile-ttl: 从已设置过期时间的内存数据集中挑选即将过期的数据 淘汰;

volatile-random:从已设置过期时间的内存数据集中任意挑选数据 淘汰;

allkeys-lru:从内存数据集中挑选最近最少使用的数据 淘汰;

allkeys-random:从数据集中任意挑选数据 淘汰;

no-enviction(驱逐):禁止驱逐数据。(默认淘汰策略。当redis内存数据达到maxmemory,在该策略下,直接返回OOM错误);

aof配置(Append only mode)

详见AOF章节

Redis持久化

重点

RDB (Redis DataBase)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jie54y9W-1609299330702)(https://raw.githubusercontent.com/Satone7/Typora_PicGo/master/image/20201203095604.png)]

在指定时间间隔内,将内存中的数据即快照写入磁盘,恢复时是将快照文件直接读到内存里。

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

RDB保存的文件默认是dump.rdb,可以在Redis.conf文件中进行修改。

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

备份会自动生成dump.rdb文件

如何恢复rdb文件
  1. 只需要将rdb文件放在Redis的启动目录下,Redis启动的时候会自动检查dump.rdb,恢复里面的数据

  2. 查看Redis的目录

    127.0.0.1:6379> config get dir
    1) "dir"
    2) "/usr/local/bin"
    
优缺点

优点:

  1. 适合大规模的数据恢复,子线程备份,主线程继续处理请求
  2. 对数据的完整性要求不高

缺点:

  1. 需要一定的时间间隔去操作,如果Redis意外宕机,最后一次rdb之后的数据就丢失了。
  2. fork进程的时候,会占用一定的内存空间

AOF (Append Only File)

将所有修改数据的命令都记录下来恢复的时候把这个文件都执行一遍

AOF保存的文件是appendonly.aof文件

在同时存在rdb和aof文件的情况下,redis会优先选择aof进行数据恢复

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RoahqInf-1609299330702)(https://raw.githubusercontent.com/Satone7/Typora_PicGo/master/image/20201203103927.png)]

AOF文件修复方法

如果执行错误的指令,也会被记录到aof文件中,并且重启Redis会因为aof文件中的错误指令而启动失败。

那么如果在实际生产中环境中,网络丢包、延迟、病毒、大文件运行失败等等因素导致aof文件破损。aof文件损坏了,该怎么修复?

  1. 备份待修复的aof文件
  2. 使用redis-check-aof --fix 进行修复
  3. 重启Redis,重新加载aof

Redis还提供了 redis-check-rdb 工具,修复rdb文件方法与aof类似。

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

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

no-appendfsync-on-rewrite no			# 重写时是否可以运用Appendfsync,用默认no即可,保证数据安全性。
auto-aof-rewrite-percentage 100			# 设置触发重写的基准值
auto-aof-rewrite-min-size 64mb			# 设置触发重写的基准值
AOF的重写策略

AOF采用文件追加方式,文件会越来越大。为避免出现此种情况,新增了重写机制, 当AOF文件的大小超过所设定的阈值时,Redis就会启动AOF文件的内容压缩,只保留可以恢复数据的最小指令集,可以使用命令bgrewriteaof

重写原理

AOF文件持续增长而过大时,会fork出一条新进程来将文件重写(也是先写临时文件最后再rename),遍历新进程的内存中数据,每条记录有一条的Set语句。重写aof文件的操作,并没有读取旧的aof文件,而是将整个内存中的数据库内容用命令的方式重写了一个新的aof文件,这点和快照有点类似

触发机制

Redis会记录上次重写时的AOF大小,默认配置是当AOF文件大小是上次rewrite后大小的一倍且文件大于64M时触发。请见配置文件默认是,auto-aof-rewrite-percentage 100的意思是超过100%,也就是一倍;auto-aof-rewrite-min-size 64mb是超过64mb.

优缺点

优点:

  1. 可以每次修改都同步,文件的完整性更好

缺点:

  1. 从数据文件来说,aof远远大于rdb,修复的速度也比rdb慢
  2. aof的运行效率要慢于rdb(aof写文件需要IO操作)

小结

  1. RDB持久化方式能在指定的时间间隔内对数据进行快照存储
  2. AOF持久化方式记录每次对服务器写的操作,当服务器重启的时候会执行这些命令来恢复数据,AOF命令以Redis协议追加保存每次写的操作到文件末尾,Redis还能对aof文件进行后台重写,压缩aof文件的体积
  3. 如果Redis只做缓存,只希望数据在服务器运行时存在,也可以不是用任何持久化
  4. 同时开启两种持久化方式
    • Redis优先读取aof文件恢复数据,因为aof持久化方式的文件完整性更好
    • RDB更适合用于备份数据库,快速重启,且不会有AOF可能潜在的bug,留作一个万一的手段
  5. 性能建议
    • 因为RDB文件只用作备份,建议只在Slave上持久化RDB文件,而且只需要保留save 900 1这条规则即可
    • 如果Enable AOF,好处是在最恶劣的情况下,也只会丢失不超过2秒数据,启动脚本较简单,只需要重载自己的aof文件即可,代价一是需要持续IO,二是重写过程的最后将新的aof写入磁盘时不可避免的造成阻塞。在硬盘许可的情况下,应尽量减少重写的频率,可以将AOF的重写基准设置到5GB以上。
    • 如果不Enable AOF,仅靠Master-Slave Repllcation实现高可用性也行,能省掉一大笔IO,也减少了重写时带来的系统波动。代价是如果主机从机同时宕机,会丢失十几分钟的数据,启动脚本也要比较主机从机中的RDB文件,载入较新的那个。(微博就是这种架构)

Redis发布订阅

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-voMezg79-1609299330703)(https://raw.githubusercontent.com/Satone7/Typora_PicGo/master/image/20201203110402.png)]

命令

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gQns3NiC-1609299330704)(https://raw.githubusercontent.com/Satone7/Typora_PicGo/master/image/20201203110755.png)]

命令测试

订阅者客户端

127.0.0.1:6379> subscribe channel1
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "channel1"
3) (integer) 1		# 开始监听,等待信息
1) "message"
2) "channel1"
3) "helloworld"

127.0.0.1:6379> psubscribe ch*		# 按照ch*模式进行订阅,*为通配符,意为订阅所有ch开头的频道
Reading messages... (press Ctrl-C to quit)
1) "psubscribe"		# 返回值类型,显示订阅成功
2) "ch*"				# 订阅模式
3) (integer) 1		# 订阅的频道数量
1) "pmessage"		# 接收值的类型,消息
2) "ch*"				# 频道匹配模式
3) "channl1"		# 消息本身的频道
4) "??"					# 消息内容

发送者客户端:

127.0.0.1:6379> PUBLISH channel1 helloworld
(integer) 1			# 接收到消息的客户端数量
127.0.0.1:6379>

原理

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

Redis通过publish、subscribe和psubscribe等命令实现发布和订阅功能

通过subscribe命令订阅某频道后,redis-server里维护了一个字典,字典的键就是一个个channel,而字典的值是一个链表,链表中保存了所有订阅这个channel的客户端。subscribe命令的关键就是将客户端添加到给定的channel订阅链表中。

使用场景

  1. 实时消息系统(推送)
  2. 实时聊天(群聊)·

Redis主从复制(Master-Slave Replication)

概念

将一台Redis服务器的服务,复制到其他Redis服务器。数据的复制是单向的,只能由主节点复制到从节点。

一个主节点可以有多个从节点,但一个从节点只能有一个主节点。

主从复制的作用主要包括:

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

一般来说,要将Redis运用于工程项目中,只使用一台Redis是万万不能的,原因如下:

  1. 从结构上,单个Redis服务器会发生单点故障,并且一台服务器需要处理所有的负载请求,压力较大
  2. 从容量上,单个Redis服务器内存容量有限,一般来说,单台Redis最大使用内存不应该超过20G

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tnITBhSM-1609299330705)(https://raw.githubusercontent.com/Satone7/Typora_PicGo/master/image/20201203112850.png)]

主从复制,读写分离。80%的情况下都是在进行读操作,减轻服务器的压力,最低一主二从。

环境配置

只需要配置从库,不用配置主库

127.0.0.1:6379> info replication		# 查看当前库的信息
# Replication
role:master				# 角色 master
connected_slaves:0				# 没有Slave
master_replid:8abb42c6c2219b7ca6a7ef83315757d94595750c
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

单机模拟主从机,修改配置的对应信息:

  1. 端口号
  2. pid名字
  3. log文件名
  4. dump.rdb文件名

启动三个Redis服务

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qL62W8ki-1609299330706)(https://raw.githubusercontent.com/Satone7/Typora_PicGo/master/image/20201203114946.png)]

> slaveof <host> <port>

127.0.0.1:6380> slaveof 127.0.0.1 6379
OK

127.0.0.1:6381> slaveof 127.0.0.1 6379
OK

127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:2		# 有两个Slave
slave0:ip=127.0.0.1,port=6380,state=online,offset=70,lag=1		# slave0的信息
slave1:ip=127.0.0.1,port=6381,state=online,offset=70,lag=1		# slave1的信息
master_replid:a02c004682dba83eb5d22d42c81dd89514387683
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:70
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:70

真实生产环境中,主从配置在conf文件中进行设置,在Redis启动时就会设置好主从角色

127.0.0.1:6379> set k1 v1 			# 在主机中写入
OK

127.0.0.1:6380> keys *				# 从机中能读到主机写入的信息
1) "k1"
127.0.0.1:6380> set k2 v2			# 但是从机不能写入
(error) READONLY You can't write against a read only replica.

主机断线重连,不影响主从复制关系

复制原理

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

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

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

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

但是只要是重新连接Master,就会自动执行一次完全同步(全量复制)。

链式连接

127.0.0.1:6380> slaveof 127.0.0.1 6379
OK

127.0.0.1:6381> slaveof 127.0.0.1 6380
OK

127.0.0.1:6380> info replication		# 查询中间节点6380的信息
# Replication
role:slave				# 角色依旧是 Slave , 不能写入
master_host:127.0.0.1
master_port:6379
master_link_status:up
master_last_io_seconds_ago:4
master_sync_in_progress:0
slave_repl_offset:2175
slave_priority:100
slave_read_only:1
connected_slaves:1
slave0:ip=127.0.0.1,port=6381,state=online,offset=2175,lag=1
master_replid:a02c004682dba83eb5d22d42c81dd89514387683
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:2175
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:977
repl_backlog_histlen:1199

也可以完成主从复制,但是79主机节点断线后,80节点使用slaveof no one命令将自己设置为主节点,拥有写入权限。

但是如果79节点重新上线,需要重新配置主从关系

哨兵模式

自动选举主节点的模式

概述

哨兵(Sentinel)可以后台监控主机是否故障,如果故障了根据投票数自动将从库转换为主库。

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kLnW1L0J-1609299330706)(https://raw.githubusercontent.com/Satone7/Typora_PicGo/master/image/20201203135830.png)]

集群多哨兵模式

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jHqot0sb-1609299330707)(https://raw.githubusercontent.com/Satone7/Typora_PicGo/master/image/20201203140028.png)]

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

测试

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

    # sentinel monitor 被监控的名字 host port 1
    sentinel monitor mRedis 127.0.0.1 6379 1
    

    后面的数字1,代表如果主机宕机,Slave投票让谁接替成为主机

  2. 使用配置文件启动哨兵,并且shutdown主机

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3I3R4GPc-1609299330708)(https://raw.githubusercontent.com/Satone7/Typora_PicGo/master/image/20201203141648.png)]

    哨兵重新选择了80节点作为新的主节点。

  3. 宕机的79节点重新上线,会被哨兵自动变为当前主节点的从节点

    # 哨兵输出
    6743:X 03 Dec 2020 14:18:13.146 * +convert-to-slave slave 127.0.0.1:6379 127.0.0.1 6379 @ mRedis 127.0.0.1 6380
    
    # 重新上线的79节点Info
    127.0.0.1:6379> info replication
    # Replication
    role:slave			# 变为了Slave
    master_host:127.0.0.1
    master_port:6380		# 主节点为80节点
    master_link_status:up
    master_last_io_seconds_ago:0
    master_sync_in_progress:0
    slave_repl_offset:8943
    slave_priority:100
    slave_read_only:1
    connected_slaves:0
    master_replid:8141663dbb7b0831c2cacb51ecff01d885976bbf
    master_replid2:0000000000000000000000000000000000000000
    master_repl_offset:8943
    second_repl_offset:-1
    repl_backlog_active:1
    repl_backlog_size:1048576
    repl_backlog_first_byte_offset:8659
    repl_backlog_histlen:285
    
优缺点

优点:

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

缺点:

  1. Redis不方便在线扩容,集群容量一旦到达上限,在线扩容非常麻烦
  2. 哨兵的配置麻烦
哨兵模式的配置

其他Redis集群

cluster

目前大型项目最流行的集群模式

Redis缓存穿透、击穿、雪崩

服务的高可用问题

缓存穿透

概念

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

解决方案
布隆过滤器

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4xmiNQAh-1609299330709)(https://raw.githubusercontent.com/Satone7/Typora_PicGo/master/image/20201203143903.png)]

缓存空对象

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-q0d62C4U-1609299330710)(https://raw.githubusercontent.com/Satone7/Typora_PicGo/master/image/20201203143911.png)]

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

  1. 如果空值能够被缓存起来,意味着将用更多的空间去设置无意义的键
  2. 即使对空值设置了过期时间,还是会存在缓存层与持久层的数据会有一段时间窗口的不一致,这对于需要保持一致性的业务会有影响

缓存击穿

概述

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

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

解决方案
  1. 设置热点数据永不过期
    从缓存层面看,没有设置过期时间,所以不会出现热点key过期后产生的问题
  2. 加互斥锁
    分布式锁:使用分布式锁,保证对于每个key同时只有一个线程去查询后端服务,其他线程没有获得分布式锁的权限,因此只需要等待即可。这种方式将高并发的压力转移到了分布式锁,因此对分布式锁的考验很大。

缓存雪崩

概念

是指在某一个时间段,缓存集中过期失效,Redis宕机

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-c0HrVImr-1609299330711)(https://raw.githubusercontent.com/Satone7/Typora_PicGo/master/image/20201203145628.png)]

解决方案
  1. Redis高可用。有可能因为Redis服务器宕机造成雪崩,多增设几台Redis服务器,搭建集群。
  2. 限流降级。在缓存失效后,通过加锁或者队列来控制数据写缓存的线程数量。
  3. 数据预热。在正式部署前,先把可能的数据预先访问一遍,设置不同的过期时间,让缓存失效的时间点尽量均匀。

Python中的Redis

安装

pip install redis

使用

import redis

# 连接服务器的Redis
r = redis.StrictRedis(host="tx.lesely.tk",port=6379,db=0)

# Redis命令 = Python方法,命令返回值 = 方法返回值

你可能感兴趣的:(Redis 个人学习笔记)