Redis-笔记

架构的演变

单体/库架构

90年代开始,当时主流的技术是JSP/HTML+Servlet+JDBC,那个时候网站上基本的网站访问量都不会太大, 所以单机数据库就已经完全胜任。那个时代互联网刚刚发展! select * select 列名 where 条件要精准

缓存+读写分离+垂直拆分

 单纯的实现了读写分离确实也解决了服务器压力,但是不足以胜任日益增长的访问量,然后我们将矛头指向 了架构优化,如何优化

瓶颈: 用户量和数据量越来越大,即使我们通过读写分离和分库分表也不足以支撑的时候,单表数据量已经爆炸 了。

分库分表+水平拆分(MySQL集群)

瓶颈: 近10年,是历史上科技发展最迅速的10年,我们的数据从简单的文字、图片也已经转变为各式各样的数据类 型,那么传统的关系型数据库MySQL已经不满足 我们现在发展的需求。图片、热点数据、大篇幅文章、定位信息等各式各样的数据扑面而来!,那么就需要 我们挨个解决, 如果有一个数据库能帮助我们分担这种特殊的数据,那么关系型数据库(MySQL、Oracle)的压力也就会变 小了。 同时数据的存储也从IO 1+1 变成了 IO 1+N

当下互联网公司架构

那么我们对架构的分析,大家也了解到,目前互联网项目对于各式各样的数据我们MySQL这种关系型数据库 存储数据就显得特别疲惫,那么这个时候 NoSQL(非关系型)数据库就随之而来

关于NoSQL

NoSQL = Not Only SQL (不仅仅是SQL)        关系型数据库:表格,行,列

很多的数据类型用户的个人信息,社交网络,地理位置。这些数据类型的存储不需要一个固定的格式!不需要多余的操作就可以横向扩展的!

Map<String,Object>,使用键值对来控制。这个就是典型的Redis的一种

NoSQL的特点

1.方便扩展(K,V之间没有关系,很好扩展)。像Java为什么做接口,就是为了解耦。

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

3、数据类型是多样型的!(不需要事先设计数据库!随取随用!如果是数据量十分大的表,很多人就无法设计了!)

4、传统RDBMS和NoSQL

NoSQL的四大类

redis介绍及安装

redis介绍 

Redis(Remote Dictionary Server ),即远程字典服务,是一个开源的使用ANSI C语言编写、支持网络、可 基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。从2010年3月15日起,Redis的 开发工作由VMware主持。从2013年5月开始,Redis的开发由Pivotal赞助。redis会周期性的把更新的数据写入磁盘或者修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主-从)同步。
免费和开源!是当下最热门的NoSQL技术之一!也被人们称为结构化数据库。

redis能做什么?

1. 内存存储,持久化,断电即丢失,所以持久化很重要,redis采用两种机制(RDB和AOF) 2. 效率高,可以用于高速缓存 3. 发布订阅系统 4. 地图信息分析 5. 计数器等,网站浏览量

redis特点/特性 1. 多样的数据类型 2. 持久化 3. 集群 4. 事务等...

Redis为什么是单线程的


1、官方答案
Redis是基于内存的操作,CPU不是Redis的瓶颈,Redis的瓶颈最有可能是机器内存的大小或者网络宽带。既然单线程容易实现,而且CPU不会成为瓶颈,那么顺理成章的采用单线程的方案。

2、我的理解
(1)不需要各种锁的性能消耗

Redis的数据结构并不全是key-value形式的,还有list,hash等复杂的结构,这些结构有可能会进行很细粒度的操作,比如在很长的列表后面添加一个元素,在hash中添加或删除一个对象,这些操作可能就需要加非常多的锁,导致的结果是同步开销大大增加。

总之,在单线程的情况下,就不用去考虑各种锁的问题,不存在加锁和释放锁的操作,没有因为可能出现的死锁而导致的性能消耗。

(2)单线程多进程集群方案

单线程的威力实际上非常强大,每核心效率也非常高,多线程自然是可以比单线程有更高的性能上限,但是在今天的计算环境中,即使是单机多线程的上限也往往不能满足需要了,需要进一步摸索的是多服务器集群化的方案,这些方案中多线程的技术照样是用不上的。

所以单线程、多进程的集群不失为一个时髦的解决方案。

(3)CPU消耗

采用单线程,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗CPU。

但是如果CPU称为Redis的瓶颈,或者不想让服务器其它CPU核闲置,那怎么办?

可以考虑多起几个Redis进程,Redis是key-value数据库,不是关系型数据库,数据之间没有约束。只要客户端分清哪些key放在哪个Redis进程中就可以了。
 

下载安装redis

因为redis依赖c++环境 所以我们需要安装c++

yum install gcc-c++

升级版本 因为redis7.* 需要gcc高版本的支持 4个分别执行

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

通过工具将安装文件放在opt文件

 解压文件夹 tar -zxvf redis-7.0.5.tar.gz 
  cd redis-7.0.5
  make #注意需要在解压后的redis文件夹中执行。这一步就是编译,大多数的源代码包都经过这一步进行编译(当然有些perl或python编写的软件需要 调用perl或python来进行编译)。如果 在 make 过程中出现 error ,就要记下错误代码(注意不仅仅 是最后一行),然后可以向开发者提交 bugreport(一般在 INSTALL 里有提交地址)。或者系统少了 一些依赖库等,这些需要自己仔细研究错误代码。make 的作用是开始进行源代码编译,以及一些功能 的提供,这些功能由他的 Makefile 设置文件提供相关的功能。比如 make install 一般表示进行安装, make uninstall 是卸载,不加参数就是默认的进行源代码编译。make 是 Linux 开发套件里面自动化 编译的一个控制程序,他通过借助 Makefile 里面编写的编译规范进行自动化的调用 gcc 、ld 以及运行 某些需要的程序进行编译的程序。
make install 这条命令来进行安装(当然有些软件需要先运行 make check 或 make test来进行一些测试),这一步 一般需要你有 root 权限(因为要向系统写入文件)。

检查redis环境服务所在的位置

启动测试

redis-server

redis-server redis-config #后面是配置文件路径

配置redis

备份文件

在redis配置文件的同级目录创建一个文件夹并且cp一个redis.conf文件到新的文件夹, 以后我们使用这个cp的文件进行操作,原生原件不变动。

修改文件

daemonize no || 这里的no改成yes || 

重新启动redis 并查看测试

redis-server redis0.conf

查看进程

ps -aux | grep redis

ps -ef | grep redis

客户端连接测试

 redis-cli -h 127.0.0.1 -p 6379

redis-cli -h 127.0.0.1 -p 6379 --raw #支持中文显示

redis测试

set name 张三

get name

keys *

# 退出客户端 exit

# 结束服务方式1 客户端内直接输入 shutdown shutdown

# 结束服务方式2 客户端外直接输入命令 redis-cli shutdown

redis可视化工具安装

配置防火墙开放端口

firewall-cmd --add-port=6379/tcp --permanent

firewall-cmd --remove-port=8888/tcp --permanent

firewall-cmd --list-ports

redis服务默认有保护机制,机制规则默认是只允许当前安装redis服务的电脑访问127.0.0.1

修改配置文件

protected-mode no

注释掉 pind 127.0.0.1

重新启动redis 服务

redis-cli shutdown

redis-server redis0.conf

redis数据类型

String类型

例举:分布式锁、Session相关、验证码相关、计数相关(点击量/阅读量/关注)等单一的值。

取值赋值

关键词:set get append strlen

加减操作

increment 自增1 decrement 自减 incrby 固定增量 自定义 decrby 固定减量 自定义

范围操作 range

getrange key index1 index2 获取当前指定范围 如果最大长度 index2 可替为-1

GETRANGE content 0 -1 # 获取全部字符串

替换操作

setrange key offset index 内容 ##offset 偏移量 指定位置开始替换

判断是否存在

EXISTS t6 判断值是否存在

#set with expire #如果存在设置消失时间及信息值 -- 订单 setex key time v 既能增加过期时间还能够重新指定新的值,如果当前key不存在创建一个新的key和值并指 定过期时间 #set if not expire #如果不存在 默认会创建一个新的,存在不操作 / 及判断存在不存在同时也能根据结 果进行进一步操作 setnx key v

批量值操作

more set mset k1 v1 k2 v2 mget k1 k2 #如果一个值不想改变只想重新设定时间 

关于对象的存储

将这个对象存储到redis中, key user 注意:字符串需要去掉引号 希望再存储一个李四,key 希望叫user 但是直接用会被覆盖,取值也不方便。

取值赋值操作

getset 先取值,再赋值

关于浮点类型的增减操作

incr decr 直接使用会报错 关键词: incrbyfloat 不支持直接+1-1操作 支持固定增减量操作

删除数据

del key ...

List列表类型

list列表类型,特点:所有的命令操作都是使用l开始。 和链表/队列比较相似,可以通过首尾进行操作。 工作中主要实现一些列表数据(导航、数据集List 队列消息,主要列表相关都可以存储)

 基本赋值和取值操作

保留案例:导航信息存储 默认左侧插入值: lpush l意思 list 右侧插入值:rpush r意思 right 查询数据:lrange key start end 获取长度 llen key

127.0.0.1:6379> lpush stu zhangsan lisi (integer) 2 #返回list长度

127.0.0.1:6379> lrange stu 0 -1 #取值 1) "lisi" 2) "zhangsan"

127.0.0.1:6379> lpush stu wangwu (integer) 3

127.0.0.1:6379> lrange stu 0 -1 1) "wangwu" 2) "lisi" 3) "zhangsan"

127.0.0.1:6379> rpush stu zhaoliu # 结尾插入值 (integer) 4

127.0.0.1:6379> lrange stu 0 -1 1) "wangwu" 2) "lisi" 3) "zhangsan" 4) "zhaoliu"

127.0.0.1:6379> llen stu # 获取当前列表长度

list结构的删除数据操作

删除整个list列表 删除list列表中的某一些数据 关键词:pop 弹出 抛出 语法: lpop count 左侧弹出 rpop count 右侧弹出 删除指定值 list remove 关键词:lrem key count value ---- 默认从左侧开始查询并删除

索引相关

index 通过索引获取数据 lindex key index 截取列表中一部分值 trim 在Java和脚本中是去掉前后空格,本身意思 修剪、整理。

在redis中作用域截取部分值,截取完成只保 留获取的内容。 del key #这种是直接将整个列表都删除 del删除删除任意类型 # 没给count值,默认左 弹出1 返回的信息是弹出的值

127.0.0.1:6379> lpop stu "lisi" # count 提供值,可以弹出多个,返回值就是弹出的值得列表 127.0.0.1:6379> lpop stu 10 1) "zhangsan"

127.0.0.1:6379> rpop stu "zhangsan"

127.0.0.1:6379> rpop stu 1 1) "lisi"

127.0.0.1:6379> 127.0.0.1:6379> lpush stu wangwu zhaoliu zhangsan lisi wangwu tianqi zhangsan zhangsan

127.0.0.1:6379> lrem stu 2 zhangsan # count正数 从左向右删除 count负数, 从右向左删除 127.0.0.1:6379> lrange stu 0 -1

127.0.0.1:6379> lindex stu 1 ltrim key start end 列表中替换值 lset

其他

在指定的一个位置前面插入值(不是索引)

在指定的一个位置后面插入值(不是索引)

linsert key before|after 原始值位置 新的值

两个列表值交换 需要两个列表, 第一个列表中的最后一个值插入到第二个列表中的第一位 rpoplpush r pop l push (右弹出左推进) 如果list1和list2为同一个,最后一个值置顶 rpoplpush list1[第一个列表] list2[第二个列表]

Set集合类型

特点: 无序、不能重复 所有命令 s 开头 用途:点赞,签到,like等功能、抽奖功能

基本操作

通过测试:重复的值无法添加成功,并且值得顺序也不是固定的。 添加值 sadd key v........ 查询值 127.0.0.1:6379> ltrim stu 1 4

127.0.0.1:6379> lset stu 1 zhaoliu

127.0.0.1:6379> linsert stu before zhaoliu lisi

127.0.0.1:6379> linsert stu after lisi wangwu

127.0.0.1:6379> rpoplpush stu teacher #置顶数据

127.0.0.1:6379> rpoplpush teacherteacher

127.0.0.1:6379> sadd book sanguoyanyi jinpingmei hongloumeng smembers members 成员 组成 通用删除 del key 将整个集合直接删除 删除指定指定的元素 srem key value

 其他操作

判断当前集合中是否存在指定的值 s is member 是否是成员 语法 sismember key v

查看当前集合中值个数 s card 语法:sacrd key 随机获取和随机删除

随机获取: srandmember [ s random member ] 语法:srandmember key count

随机删除: spop 语法: spop key count

127.0.0.1:6379> smembers book

127.0.0.1:6379> srem book jinpingmei

127.0.0.1:6379> sismember book jinpingmei (integer) 1

127.0.0.1:6379> sismember book jinpingmei2 (integer) 0

127.0.0.1:6379> scard book (integer) 4

127.0.0.1:6379> srandmember book "jinpingmei"

127.0.0.1:6379> srandmember book 2 1) "hongloumeng" 2) "jinpingmei"

127.0.0.1:6379> spop book "jinpingmei" 127.0.0.1:6379> spop book 10 1) "hongloumeng" 2) "sanguoyanyi"

特殊操作

解决中文问题 [root@sunwz ~]# redis-cli -h 127.0.0.1 -p 6379 --raw 差集

diff 差异 只返回第一个集合的差值 交集

127.0.0.1:6379> sadd zb1 xiaozhi dasima caixukun lijiaqi
4
127.0.0.1:6379> sadd zb2 liziqi xiaozhi dasima wangdaxian xuxubaobao
5
127.0.0.1:6379> sdiff zb1 zb2
caixukun
lijiaqi
127.0.0.1:6379> sdiff zb2 zb1
wangdaxian
liziqi
xuxubaobao
127.0.0.1:6379>

sinter 交集

sinter zb2 zb1

并集 s union 联盟 工会 ---- 去重

sunion zb1 zb2

Hash类型(map结构)

基础语法

存值(key (key - value))

语法: hset key k1 v1 k2 v2 k3 v3

hget key k -- 获取单个值

# book redis的key name 值得key pingandeshijie 值中的值 (key key value)
127.0.0.1:6379> hset book name pingandeshijie
127.0.0.1:6379> hget book name
pingandeshijie
127.0.0.1:6379> hset book name pingandeshijie price 30 author luyao
# 如果存储对象
127.0.0.1:6379> hset student name zhangsan age 19 hobbies chouyanhejiutangtou
# 如果存储对象集合
{id:1,name:zhangsan}
{id:2,name:lisi}
127.0.0.1:6379> hset student 1 {id:1,name:zhangsan} 2 {id:2,name:lisi}
127.0.0.1:6379> hget student 2

hmget 名称k k k #获取多个key   hmget student 1 2

获取全部的值 hgetall 名称k    hgetall student

通过key删除所有数据 -- 通用  del student

通过值的key删除指定的数据  hdel student 2

读取当前hash表(map集合)中数据的个数   hlen student

其他用法

判断是否存在 

hexists student 1

 读取所有的key ----- 1w条数据 取某一些特定的数据,取到所有key 循环 取值 判断 筛选。

hkeys student

取所有的value --- 案例:Java中想将redis中的hash结构转化为set结构

hvals student

Zset

有序不重复集合,在set的基础上多增加一个值 set k1 * v1 (zset k1 排序的值(score) v1) 有序不可重复的set集合 主要使用方向:工资、班级成绩等等之类的数据。 或者 权重处理 0 普通 1 重要

基本语法

增加和查询

# emps k
{id:1,name:zhangsan,sal:8000}
{id:2,name:lisi,sal:5000}
{id:3,name:wangwu,sal:12000}
#增加数据
127.0.0.1:6379> zadd emps 8000 {id:1,name:zhangsan,sal:8000}
1
127.0.0.1:6379> zadd emps 5000 {id:2,name:lisi,sal:5000}
1
127.0.0.1:6379> zadd emps 12000 {id:3,name:wangwu,sal:12000}
1

127.0.0.1:6379> zrange emps 0 -1 #查询 - 默认升序
{id:2,name:lisi,sal:5000}
{id:1,name:zhangsan,sal:8000}
{id:3,name:wangwu,sal:12000}
127.0.0.1:6379> zrandmember emps 1 #随机读取
{id:1,name:zhangsan,sal:8000}

删除数据 删除整个key del key

zrem emps {id:2,name:lisi,sal:5000}

zset 复杂查询

zset 复杂查询

127.0.0.1:6379> zrangebyscore emps 8000 10000
{id:1,name:zhangsan,sal:8000}
127.0.0.1:6379> zrangebyscore emps -inf +inf
{id:2,name:lisi,sal:5000}
{id:1,name:zhangsan,sal:8000}
{id:3,name:wangwu,sal:12000}

升序操作

# 默认取所有数据
127.0.0.1:6379> zrange emps 0 -1
{id:2,name:lisi,sal:5000}
{id:1,name:zhangsan,sal:8000}
{id:3,name:wangwu,sal:12000}
# 根据当前排序结果取所有数据
127.0.0.1:6379> zrangebyscore emps -inf +inf
{id:2,name:lisi,sal:5000}
{id:1,name:zhangsan,sal:8000}
{id:3,name:wangwu,sal:12000}
# 根据当前排序结果取所有数据 并显示排序字段
127.0.0.1:6379> zrangebyscore emps -inf +inf withscores
{id:2,name:lisi,sal:5000}
5000
{id:1,name:zhangsan,sal:8000}
8000
{id:3,name:wangwu,sal:12000}
12000
# 根据当前排序结果取所有数据 并显示排序字段 并截取某一部分值
127.0.0.1:6379> zrangebyscore emps -inf +inf withscores limit 0 1
{id:2,name:lisi,sal:5000}
5000
# 取排序字段值大于**的数据
127.0.0.1:6379> zrangebyscore emps 8000 +inf
{id:1,name:zhangsan,sal:8000}
{id:3,name:wangwu,sal:12000}
# 取排序字段值小于**的数据
127.0.0.1:6379> zrangebyscore emps -inf 8000
{id:2,name:lisi,sal:5000}
{id:1,name:zhangsan,sal:8000}

降序操作 z rev range k start stop start stop取索引的范围。

127.0.0.1:6379> zrange emps 0 -1
{id:2,name:lisi,sal:5000}
{id:1,name:zhangsan,sal:8000}
{id:3,name:wangwu,sal:12000}
127.0.0.1:6379> zrangebyscore emps -inf +inf
{id:2,name:lisi,sal:5000}
{id:1,name:zhangsan,sal:8000}
{id:3,name:wangwu,sal:12000}
127.0.0.1:6379> zrevrange emps 0 -1
{id:3,name:wangwu,sal:12000}
{id:1,name:zhangsan,sal:8000}
{id:2,name:lisi,sal:5000}
127.0.0.1:6379> zrevrangebyscore emps +inf -inf
{id:3,name:wangwu,sal:12000}
{id:1,name:zhangsan,sal:8000}
{id:2,name:lisi,sal:5000}
127.0.0.1:6379> zrevrangebyscore emps +inf -inf withscores
{id:3,name:wangwu,sal:12000}
12000
{id:1,name:zhangsan,sal:8000}
8000
{id:2,name:lisi,sal:5000}
5000
127.0.0.1:6379> zrevrangebyscore emps +inf -inf withscores limit 0 1
{id:3,name:wangwu,sal:12000}
12000
127.0.0.1:6379> zrevrangebyscore emps +inf 8000
{id:3,name:wangwu,sal:12000}
{id:1,name:zhangsan,sal:8000}
127.0.0.1:6379> zrevrangebyscore emps 8000 -inf
{id:1,name:zhangsan,sal:8000}

geospatial 地理位置

作用:朋友圈定位,附近的人,打车距离计算。

添加地理位置信息 GEOADD geoadd key 精度 纬度 名称

# maps 127.0.0.1:6379> geoadd maps 116.22518499110029 39.951383565611934 xuejiaao 127.0.0.1:6379> geoadd maps 116.22119922850416 39.95185236700199 hongyun 127.0.0.1:6379> geoadd maps 116.32169891808317 39.894741892979205 beijingxizhan 127.0.0.1:6379> geoadd maps 116.77067924950407 39.830968132325836 huanhuxiaozhan 127.0.0.1:6379> geoadd maps 116.69798065636442 39.52723389254955 langfang 127.0.0.1:6379> geoadd maps 117.20129181359098 39.083057286402244 tianjing 127.0.0.1:6379> geoadd maps 121.47772003624723 31.19857972418721 shanghai 127.0.0.1:6379> geoadd maps 121.52029205773161 25.050634672686915 taiwan

查询指定位置的经纬度 GEOPOS

127.0.0.1:6379> geopos maps taiwan
121.52029305696487427
25.05063527678047564
127.0.0.1:6379> geopos maps langfang
116.69798165559768677
39.52723385909389719
127.0.0.1:6379>

查询两地点之间的距离 GEODIST geodist key 地点1 地点2 单位(默认 米)

127.0.0.1:6379> geodist maps xuejiaao hongyun M
343.8236
127.0.0.1:6379> geodist maps xuejiaao hongyun km
0.3438
127.0.0.1:6379> geodist maps beijingxizhan shanghai
1073394.0301
127.0.0.1:6379> geodist maps beijingxizhan shanghai km
1073.3940

查询附近的城市(定位) GEORADIUS        

》georadius maps 116.22518499110029 39.951383565611934 1500 KM

查询附近的城市(定位)--显示到中间的距离 GEORADIUS 关键词: withdist

》eoradius maps 116.22518499110029 39.951383565611934 500 KM withdist

查询附近的城市(定位)--显示他人精准信息 GEORADIUS 关键词:withcoord

》georadius maps 116.22518499110029 39.951383565611934 500 KM withdist withcoord

查询附近的城市(定位)--显示他人精准信息 并指定返回个数 GEORADIUS 关键词: count

》georadius maps 116.22518499110029 39.951383565611934 500 KM withdist withcoord count 1

GEO RADIUS BY MEMBER 通过元素(非坐标)定位信息

》georadiusbymember maps hongyun 10 KM

Geohash 返回hash值 作用是将二维写的经纬度转化为一维的字符串,字符串越像表示距离越近

》geohash maps hongyun

HyperLogLog基数统计

Redis HyperLogLog 是用来做基数统计的算法,HyperLogLog 的优点是,在输入元素的数量或者体积 非常非常大时,计算基数所需的空间总是固定的、并且是很小的。在 Redis 里面,每个 HyperLogLog 键只需要花费 12 KB 内存,就可以计算接近 2^64(long类型的最大值) 个不同元素的基数。这和计算基 数时,元素越多耗费内存就越多的集合形成鲜明对比。但是,因为 HyperLogLog 只会根据输入元素来 计算基数,而不会储存输入元素本身,所以 HyperLogLog 不能像集合那样,返回输入的各个元素

什么是基数? 比如数据集 {1, 3, 5, 7, 5, 7, 8}, 那么这个数据集的基数集为 {1, 3, 5 ,7, 8}, 基数重复元素为5(个数)。 基数(不估计就是在误差可接受的范围内,快速计算基数。 案例:网页访问次数(一个人访问一个网页多次但是记录的时候也只算一个人) 这种方式其实完全可以使用set方式去实现,但是set的方式会存储大量的用户ID就会比较占用内存,比较麻 烦,而我们的目的是为了计数,所以set就显得有些不足。

添加基础数据

pfadd clazz zhangsan lisi wangwu zhaoliu tianqi 99 2111 99.9

pfadd clazz zhangsan lisi wangwu zhaoliu

获取总数

> pfcount clazz

合并 -- 规则 将后面的数据合并到第一个指定的key中--合并之后也是默认去重复

127.0.0.1:6379> pfadd stu xiongda xionger zhangsan
1
127.0.0.1:6379> pfmerge clazz stu
OK
127.0.0.1:6379> pfcount clazz
11
127.0.0.1:6379> pfcount stu
3

Bitmaps位图

1Byte=8bit(比特) 1KB=1024Byte(字节)=8*1024bit 1MB=1024KB 1GB=1024MB 1TB=1024GB 1PB=1024TB 位存储: 给大家举个例子,比如中国有14亿人口,那么我们用位图中使用0表示每一个人 

0 0 0 0 0 0 x 14亿 ,这种数据其实即使是14亿占用内存也是不多的,那么接下来我们使用位图表示当前疫情 下哪些人被感染了,那么我们就可以将位图更改为 0 1 0 0 1 0 x 14亿 . 我们就能通过 0 和 1 判断 没有感染的和传染人的数量。 在我们日常的开发过程中,位图经常用作于 统计用户活跃or不活跃、登录or未登陆 、上班族一年的打卡记 录,只要有两个状态的都可以使用位图计算。 案例:是否打卡(一周) 0 未打卡 1 打卡 一年为例 365 = 365bit  1字节=8bit  约46字节左右

增加基础数据 setbit

setbit kq 1 1

setbit kq 2  1

setbit kq 3 1

setbit kq 4 0

setbit kq 5 1

setbit kq 6 1

查看某一天的打卡记录 getbit

get kq

getbit kq 1

统计打卡的天数 bitcount

.bitcount kq

redis事务

redis 事务可以一次执行多个命令,一个事务中所有命令都会被序列化。在事务执行过程,会按照顺序串行 化执行队列中的命令,其他客户端提交的命令请求不会插入到事务执行命令序列中。redis事务没有隔离级别 的概念,所以不会出现脏读、幻读、不可重复读,因为队列就有序列化的概念,你可以认为默认就是MySQL 序列化级别。

redis采用了乐观锁方式进行事务控制,它使用watch命令监视给定的key,当exec(提交事务)时候如果监 视的key从调用watch后发生过变化,则整个事务会失败。也可以调用watch多次监视多个key。注意watch的 key是对整个连接有效的,如果连接断开,监视和事务都会被自动清除。当然exec,discard,unwatch命令 都会清除连接中的所有监视

redis保证一个事务中的所有命令要么都执行,要么都不执行。如果在发送EXEC命令前客户端断线了,则 Redis会清空事务队列,事务中的所有命令都不会执行。而一旦客户端发送了EXEC命令,所有的命令就都会 被执行,即使此后客户端断线也没关系,因为Redis中已经记录了所有要执行的命令。 -------前面话述中看似 redis是能够保证原子性,但是保证原子性是有特殊的前提的,首先单个 Redis 命令的执行是原子性的,但 Redis 没有在事务上增加任何维持原子性的机制,所以 Redis 事务的执行并不是原子性的。可能有人问redis 因为使用watch命令监视再加上乐观锁应该能保证原子性了,但实际事务执行过程中因为编译(命令)导致的错 误可以保证原子性(出现的概率极低)多线程出现干扰问题,但是运行的错误(命令正确,但是结果出错误) 就不能够保证原子性了

事务从开始到执行会经历以下三个阶段:

1. 开始事务(MULTI) 2. 命令入队(输出queued) 3. 执行事务(EXEC) / 取消事务(DISCARD)

标准事务执行 EXEC

一组执行的命令绑定成一个执行的事务单元

127.0.0.1:6379> MULTI #开启事务
OK
127.0.0.1:6379> set zhangsan 100 #增加信息
QUEUED #入列但不执行
127.0.0.1:6379> set lisi 200 #增加信息
QUEUED
127.0.0.1:6379> get zhangsan #读取信息
QUEUED
127.0.0.1:6379> set wangwu 50000 #增加信息
QUEUED
127.0.0.1:6379> EXEC #提交事务
1) OK
2) OK
3) "100"
4) OK

放弃事务执行DISCARD

在关系型数据库中,之所以有回滚的方法是因为在事务执行的过程中(增加)在commit之前已经操作了数 据库,那么一旦出现问题,我们必须提供一个回滚的方法来回到最初的状态。 --- 占ID

reids中,我们执行的命令都在队列中(没有执行),所以就没有回滚的概念,此时你只要放弃提交(取消执 行),那么数据就是最初状态了。

127.0.0.1:6379> MULTI #开启事务
OK
127.0.0.1:6379> set k1 v1 #执行增加
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> DISCARD #在出现一些特殊情况的时候 例如set k2的代码(java)通过if判断当前值不可
用,那么当前事务就没有意义了,那么可以直接取消事务
OK
127.0.0.1:6379> get k1
(nil)
127.0.0.1:6379>

redis事务保证原子性测试

redis事务中执行编译错误(命令错误)是保证原子性的, 但是redis运行错误(命令正确,但是结果出错误),事务是不保证原子性的。 相当于Java中两种异常状态 编译异常 运行异常1/0

127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> sss 2 #命令错误
ERR unknown command `sss`, with args beginning with: `2`,
127.0.0.1:6379> set k3 v3
QUEUED
127.0.0.1:6379> EXEC
EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> get k2

redis事务不保证原子性测试

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> set k3 "jiazong" #设置字符串
QUEUED
127.0.0.1:6379> INCRBY k3 100 #自增一个字符串
QUEUED
127.0.0.1:6379> set k4 v4 #设置值
QUEUED
127.0.0.1:6379> EXEC #提交事务
1) OK
2) OK
3) OK
4) (error) ERR value is not an integer or out of range #报错的位置,但是其他的命令都执行
5) OK
127.0.0.1:6379> get k2
"v2"
127.0.0.1:6379> get k4
"v4"
127.0.0.1:6379> get k3

测试redis乐观锁

redis默认使用的是乐观锁保证事务原子性。

悲观锁: 认为什么时候都会出问题,无论做什么都需要加锁。synchronized -- 特别消耗性能

乐观锁: 认为什么时候都不会出现问题,所以不会上锁,但是执行的时候会经过判断是否有修改, MySQL中使用version判断,正确提交,错误不提交。

redis 中通过 Watch监视器实现乐观锁功能!

正常执行

127.0.0.1:6379> set money 100 #设定100元
OK
127.0.0.1:6379> set out 0 #花销 0
OK
127.0.0.1:6379> WATCH money #设定监控 钱的变化
OK
127.0.0.1:6379> MULTI #开启事物
OK
127.0.0.1:6379> DECRBY money 20 #我自己花销20
QUEUED
127.0.0.1:6379> INCRBY out 20 #花销记录中增加20
QUEUED
127.0.0.1:6379> EXEC #提交事务
1) (integer) 80
2) (integer) 20
127.0.0.1:6379> get money
"80"
127.0.0.1:6379> get out

异常执行案例

模拟有其他人干扰 - 开两个客户端代表多线程。

客户端一

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
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> DECRBY money 20
QUEUED
127.0.0.1:6379> INCRBY out 20
QUEUED
127.0.0.1:6379>
#没有提交事务

客户端二

set money 20

客户端一 .

exit   提交 #提交失败!原因就是watch启动了监视功能,一旦发生变化,放弃事务

关于废弃锁处理

UNWATCH #放弃原来的锁

压测工具

压力测试一般都是属于软测的岗位人员主要使用的工具,但是现代编程都要讲究DevOps思想。所以对于技 术人员也需要掌握一些测试和运维的内容,当前我们可以使用这个压力测试工具测一下自己电脑的性能.

概述 redis-benchmark是官方自带的Redis性能测试工具,用来测试Redis在当前环境下的读写性能。在使用Redis 的时候,服务器的硬件配置、网络状况、测试环境都会对Redis的性能有所影响,我们需要对Redis实时测试 以确定Redis的实际性能。

语法格式: redis-benchmark [参数] [参数值]

 专业术语TPS、QPS、RT

响应时间RT:响应时间是指系统对请求作出响应的时间。一个系统通常会提供许多功能,而不同功能的业务 逻辑也千差万别,因而不同功能的响应时间也不尽相同。在讨论一个系统的响应时间时,通常是指该系统所 有功能的平均时间或者所有功能的最大响应时间。

吞吐量TPS:吞吐量是指系统在单位时间内处理请求的数量。不同系统的平均响应时间随用户数增加而增长 的速度也不大相同,这也是采用吞吐量来度量并发系统的性能的主要原因。一般而言,吞吐量是一个比较通 用的指标,两个具有不同用户数和用户使用模式的系统,如果其最大吞吐量基本一致,则可以判断两个系统 的处理能力基本一致。

查询率QPS:每秒查询率QPS是对一个特定的查询服务器在规定时间内所处理流量多少的衡量标准,在互联 网中,经常用每秒查询率来衡量服务器的性能。对应fetches/sec,即每秒的响应请求数,也即是最大吞吐能 力,查询率通常是针对单机进行压力测试。

测试

redis启动后进入到bin目录中,执行以下命令进行性能测试:

./redis-benchmark -t set,get -n 100000

SET部分和GET部分,只需要关注最后一句输出即可,以GET为例,上述输出:79365.08 requests per second,表示每秒的GET命令处理79365.08个请求,也就是QPS为7.9W个。 这里的数据都是理想数据,测试出来的QPS不能代表实际生产的处理能力,在实际生产中,服务器的硬件配 置、网络状况、测试环境都会对Redis的性能有所影响。

在实际生产中,我们需要关心在应用场景中,redis能够处理的QPS是多少。我们需要估计生产的报文大小, 使用benchmark工具指定-d数据块大小来模拟:

./ redis-benchmark -t get -n 100000 -c 100 -d 2048

指定并发数为100,数据大小为2048字节,在实际生产中,每个业务处理的数据大小不一致,取出一个最大 的数据为基数进行测试即可,在程序里将数据的字节大小打印出来,使用redis-benchmark的-d参数指定数 据大小。

redis持久化

理论

,Redis 相对于 Memcache 等其他的缓存产品,有一个比较明显的优势就是 Redis 不仅仅支持 简单的key-value类型的数据, 同时还提供list,set,zset,hash等数据结构的存储。这几种丰富的数据类型我们 接下来我们要介绍 Redis 的另外一大优势——持久化。

由于 Redis 是一个内存数据库,所谓内存数据库,就是将数据库中的内容保存在内存中, 这与传统的MySQL,Oracle等关系型数据库直接将内容保存到硬盘中相比,内存数据库的读写效率比传统数 据库要快的多(内存的读写效率远远大于硬盘的读写效率)。 但是保存在内存中也随之带来了一个缺点,一旦断电或者宕机,那么内存数据库中的数据将会全部丢失。

为了解决这个缺点,Redis提供了将内存数据持久化到硬盘,以及用持久化文件来恢复数据库数据的功能。 Redis 支持两种形式的持久化,一种是RDB快照(snapshotting),另外一种是AOF(append-only-file)

RDB(redis DateBase) 

概述

RDB是Redis用来进行持久化的一种方式,是把当前内存中的数据集快照写入磁盘,也就是 Snapshot 快照 (数据库中所有键值对数据)。恢复时是将快照文件直接读到内存里。

触发方式 RDB 有两种触发方式,分别是自动触发和手动触发。

自动触发

################################ 快照 #################################
#
# 保存数据到磁盘,格式如下:
#
# save <seconds> <changes>
#
# 指出在多长时间内,有多少次更新操作,就将数据同步到数据文件rdb。
# 相当于条件触发抓取快照,这个可以多个条件配合
#
# 比如默认配置文件中的设置,就设置了三个条件
#
# save 900 1 900秒内至少有1个key被改变
# save 300 10 300秒内至少有10个key被改变
# save 60 10000 60秒内至少有10000个key被改变
save 900 1
save 300 10
save 60 10000
# 存储至本地数据库时(持久化到rdb文件)是否压缩数据,默认为yes
rdbcompression yes
# 本地持久化数据库文件名,默认值为dump.rdb
dbfilename dump.rdb
# 工作目录
#
# 数据库镜像备份的文件放置的路径。
# 这里的路径跟文件名要分开配置是因为redis在进行备份时,先会将当前数据库的状态写入到一个临时文件中,
等备份完成时,
# 再把该该临时文件替换为上面所指定的文件,而这里的临时文件和上面所配置的备份文件都会放在这个指定的路
径当中。
#
# AOF文件也会存放在这个目录下面
#
# 注意这里必须制定一个目录而不是文件

save: 这里是用来配置触发 Redis的 RDB 持久化条件,也就是什么时候将内存中的数据保存到硬盘。比如 “save m n”。表示m秒内数据集存在n次修改时,自动触发bgsave(这个命令下面会介绍,手动触发RDB持 久化的命令)。

rdbcompression :默认值是yes。对于存储到磁盘中的快照,可以设置是否进行压缩存储。如果是的话, redis会采用LZF算法进行压缩。如果你不想消耗CPU来进行压缩的话,可以设置为关闭此功能,但是存储在 磁盘上的快照会比较大。

stop-writes-on-bgsave-error :默认值为yes。当启用了RDB且最后一次后台保存数据失败,Redis是否停 止接收数据。这会让用户意识到数据没有正确持久化到磁盘上,否则没有人会注意到灾难(disaster)发生 了。如果Redis重启了,那么又可以重新开始接收数据了。

rdbchecksum :默认值是yes。在存储快照后,我们还可以让redis使用CRC64算法来进行数据校验,但是 这样做会增加大约10%的性能消耗,如果希望获取到最大的性能提升,可以关闭此功能。

dbfilename :设置快照的文件名,默认是 dump.rdb

dir:设置快照文件的存放路径,这个配置项一定是个目录,而不能是文件名。默认是和当前配置文件保存在 同一目录

手动触发

手动触发Redis进行RDB持久化的命令有两种:

save:该命令会阻塞当前Redis服务器,执行save命令期间,redis不能处理其他命令,直到RDB过程完成为 止。显然该命令对于内存比较大的实例会造成长时间阻塞,这是致命的缺陷,为了解决此问题,redis提供了 第二种方式。(不建议使用)

bgsave:执行该命令时,Redis会在后台异步进行快照操作,快照同时还可以响应客户端请求。具体操作是 Redis进程执行fork操作创建子进程,RDB持久化过程由子进程负责,完成后自动结束。阻塞只发生在创建子 进程fork阶段,一般时间很短。

基本上 Redis 内部所有的RDB操作都是采用 bgsave 命令。

还有两种特殊的操作也能触发RDB的持久化,但是因为情况特殊,所以不作为手动触发条件 # 执行执行 flushall 命令,也会产生dump.rdb文件,但里面是空的,无意义 # 关闭redis 服务同样会生成 --- 规则使用 bgsave 保存数据

RDB数据恢复(企业管理者经常用的手段)

将备份文件 (dump.rdb) 移动到 redis 安装目录并启动服务即可,redis就会自动加载文件数据至内存了。 Redis 服务器在载入 RDB 文件期间,会一直处于阻塞状态,直到载入工作完成为止。 启动服务器的当前目录一定是redis-*** 否则快照生成的路径就会发生错误!

config get dir

测试文件恢复

停止 RDB 持久化

有些情况下,我们只想利用Redis的缓存功能,并不像使用 Redis 的持久化功能,那么这时候我们最好停掉 RDB 持久化。可以通过上面讲的在配置文件 redis.conf 中,可以注释掉所有的 save 行来停用保存功能或者 直接一个空字符串来实现停用:save ""

也可以执行命令redis-cli config set save ""

RDB 的优势和劣势(高危面试题)

优势

1. RDB是一个非常紧凑的文件(默认压缩),它保存redis 在某个时间点上的数据集。这种文件非常适合用于 备份和灾难恢复。

2. 生成RDB文件的时候,redis主进程会fork()一个子进程来处理所有保存工作,主进程不需要进行任何磁 盘IO操作。

3. RDB 在恢复大数据集时的速度比 AOF 的恢复速度要快。

劣势

1. RDB方式数据没办法做到实时持久化/秒级持久化。因为bgsave每次运行都要执行fork操作创建子进 程,属于重量级操作(内存中的数据被克隆了一份,大致2倍的膨胀性需要考虑),频繁执行成本过高(影 响性能) 127.0.0.1:6379> config get dir dir /opt/redis-7.0.4 # save 60 10000 save "" redis-cli config set save ""

2. RDB文件使用特定二进制格式保存,Redis版本演进过程中有多个格式的RDB版本,存在老版本Redis服 务无法兼容新版RDB格式的问题(版本不兼容)

3. 在一定间隔时间做一次备份,所以如果redis意外down掉的话,就会丢失最后一次快照后的所有修改(数 据有丢失)

RDB 自动保存的原理(高低薪差异题)

redis有个服务器状态结构:

struct redisService{
//1、记录保存save条件的数组
struct saveparam *saveparams;
//2、修改计数器
long long dirty;
//3、上一次执行保存的时间
time_t lastsave;
}

首先看记录保存save条件的数组 saveparam,里面每个元素都是一个 saveparams 结构:

struct saveparam{
//秒数
time_t seconds;
//修改数
int changes;
};

前面我们在 redis.conf 配置文件中进行了关于save 的配置

save 3600 1 :表示3600 秒内如果至少有 1 个 key 的值变化,则保存

save 300 100:表示300 秒内如果至少有 10 个 key 的值变化,则保存

save 60 10000:表示60 秒内如果至少有 10000 个 key 的值变化,则保存

dirty 计数器和lastsave 属性

dirty 计数器记录距离上一次成功执行 save 命令或者 bgsave 命令之后,Redis服务器进行了多少次修改(包 括写入、删除、更新等操作)。

astsave 属性是一个时间戳,记录上一次成功执行 save 命令或者 bgsave 命令的时间。

执行原理: 通过这两个命令,当服务器成功执行一次修改操作,那么dirty 计数器就会加 1,而lastsave 属性记录上一次 执行save或bgsave的时间,Redis 服务器还有一个周期性操作函数 severCron ,默认每隔 100 毫秒就会执行 一次,该函数会遍历并检查 saveparams 数组中的所有保存条件,只要有一个条件被满足,那么就会执行 bgsave 命令。

执行完成之后,dirty 计数器更新为 0 ,lastsave 也更新为执行命令的完成时间。

AOF

概述

Redis的持久化方式之一RDB是通过保存数据库中的键值对来记录数据库的状态。而另一种持久化方式 AOF 则是通过保存Redis服务器所执行的写命令来记录数据库状态。

AOF以协议文本的方式,将所有对数据库进行写入的命令(及其参数)记录到 AOF 文件,以此达到记录数据 库状态的目的。

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

RDB 持久化方式就是将 str1,str2,str3 这三个键值对保存到 RDB文件中,而 AOF 持久化则是将执行的 set,sadd,lpush 三个命令保存到 AOF 文件中。

 AOF配置

############################## AOF ###############################
# 默认情况下,redis会在后台异步的把数据库镜像备份到磁盘,但是该备份是非常耗时的,而且备份也不能很频
繁,
#如果发生诸如拉闸限电、拔插头等状况,那么将造成比较大范围的数据丢失。
# 所以redis提供了另外一种更加高效的数据库备份及灾难恢复方式。
# 开启append only模式之后,redis会把所接收到的每一次写操作请求都追加到appendonly.aof文件中,
#当redis重新启动时,会从该文件恢复出之前的状态。
# 但是这样会造成appendonly.aof文件过大,所以redis还支持了BGREWRITEAOF指令,对appendonly.aof
进行重新整理。
# 你可以同时开启asynchronous dumps 和 AOF
appendonly no
# AOF文件名称 (默认: "appendonly.aof")
# appendfilename appendonly.aof
# Redis支持三种同步AOF文件的策略:
# no: 不进行同步,系统去操作 . Faster.
# always: always表示每次有写操作都进行同步. Slow, Safest.
# everysec: 表示对写操作进行累积,每秒同步一次. Compromise.
#
# 默认是"everysec",按照速度和安全折中这是最好的。
# 如果想让Redis能更高效的运行,你也可以设置为"no",让操作系统决定什么时候去执行
# 或者相反想让数据更安全你也可以设置为"always"
#
# 如果不确定就用 "everysec".
# appendfsync always
appendfsync everysec
# appendfsync no
# AOF策略设置为always或者everysec时,后台处理进程(后台保存或者AOF日志重写)会执行大量的I/O操作
# 在某些Linux配置中会阻止过长的fsync()请求。注意现在没有任何修复,即使fsync在另外一个线程进行处理
#
# 为了减缓这个问题,可以设置下面这个参数no-appendfsync-on-rewrite
#
# This means that while another child is saving the durability of Redis is
# the same as "appendfsync none", that in pratical terms means that it is
# possible to lost up to 30 seconds of log in the worst scenario (with the
# default Linux settings).
#
# If you have latency problems turn this to "yes". Otherwise leave it as
# "no" that is the safest pick from the point of view of durability.
no-appendfsync-on-rewrite no
# Automatic rewrite of the append only file.
# AOF 自动重写
# 当AOF文件增长到一定大小的时候Redis能够调用 BGREWRITEAOF 对日志文件进行重写
#
# 它是这样工作的:Redis会记住上次进行些日志后文件的大小(如果从开机以来还没进行过重写,那日子大小在开
机的时候确定)
#
# 基础大小会同现在的大小进行比较。如果现在的大小比基础大小大制定的百分比,重写功能将启动
# 同时需要指定一个最小大小用于AOF重写,这个用于阻止即使文件很小但是增长幅度很大也去重写AOF文件的情况
# 设置 percentage 为0就关闭这个特性
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
#

appendonly:默认值为no,也就是说redis 默认使用的是rdb方式持久化,如果想要开启 AOF 持久化方 式,需要将appendonly 修改为 yes。

appendfilename :aof文件名,默认是"appendonly.aof"

appendfsync**:**aof持久化策略的配置; 1. no表示不执行fsync,由操作系统保证数据同步到磁盘,速度最快,但是不太安全; 2. always表示每次写入都执行fsync,以保证数据同步到磁盘,效率很低;3. everysec表示每秒执行一次fsync,可能会导致丢失这1s数据。通常选择 everysec ,兼顾安全性和效 率。(默认)

no-appendfsync-on-rewrite:在aof重写或者写入aof文件的时候,会执行大量IO,此时对于everysec和 always的aof模式来说,执行fsync会造成阻塞过长时间,no-appendfsync-on-rewrite字段设置为默认设置 为no。如果对延迟要求很高的应用,这个字段可以设置为yes,否则还是设置为no,这样对持久化特性来说 这是更安全的选择。 设置为yes表示rewrite期间对新写操作不fsync,暂时存在内存中,等rewrite完成后再写 入,默认为no,建议yes。Linux的默认fsync策略是30秒。可能丢失30秒数据。默认值为no。

auto-aof-rewrite-percentage:默认值为100。aof自动重写配置,当目前aof文件大小超过上一次重写的 aof文件大小的百分之多少进行重写,即当aof文件增长到一定大小的时候,Redis能够调用bgrewriteaof对日 志文件进行重写。当前AOF文件大小是上次日志重写得到AOF文件大小的二倍(设置为100)时,自动启动 新的日志重写过程。 64M - 40M - 80M(55M) - 110M(70M)

auto-aof-rewrite-min-size:64mb。设置允许重写的最小aof文件大小,避免了达到约定百分比但尺寸仍 然很小的情况还要重写。

aof-load-truncated:aof文件可能在尾部是不完整的,当redis启动的时候,aof文件的数据被载入内存。 重启可能发生在redis所在的主机操作系统宕机后,出现这种现象 redis宕机或者异常终止不会造成尾部不完 整现象,可以选择让redis退出,或者导入尽可能多的数据。如果选择的是yes,当截断的aof文件被导入的时 候,会自动发布一个log给客户端然后load。如果是no,用户必须手动redis-check-aof修复AOF文件才可 以。默认值为 yes。

开启AOF

appendonly yes

AOF文件故障修复

# 关闭redis # 删除dump.rdb # 随便修改点aof文件 # 重新启动redis

通过redis-check-aof --fix  工具 修复文件

修复后重新启动。

AOF文件重写机制

AOF 文件包含三类文件:基本文件、增量文件与清单文件。其中基本文件一般为 rdb 格式,就是 rdb 持久化 的数据文件。

由于AOF持久化是Redis不断将写命令记录到 AOF 文件中,随着Redis不断的进行,AOF 的文件会越来越 大,文件越大,占用服务器内存越大以及 AOF 恢复要求时间越长。为了解决这个问题,Redis新增了重写机 制,当AOF文件的大小超过所设定的阈值时,Redis就会启动AOF文件的内容压缩,只保留可以恢复数据的最 小指令集。可以使用命令 bgrewriteaof 来重写。

如果不进行 AOF 文件重写,那么 AOF 文件将保存四条 SADD 命令,如果使用AOF 重写,那么AOF 文件中将 只会保留下面一条命令: sadd animals "dog" "tiger" "panda" "lion" "cat"

也就是说 AOF 文件重写并不是对原文件进行重新整理,而是直接读取服务器现有的键值对,然后用一条命令 去代替之前记录这个键值对的多条命令,生成一个新的文件后去替换原来的 AOF 文件。

AOF 重写触发机制:

通过 redis.conf 配置文件中的 auto-aof-rewrite-percentage:默认值为100,以及auto-aof-rewrite-minsize:64mb 配置,也就是说默认Redis会记录上次重写时的AOF大小,默认配置是当AOF文件大小是上次 rewrite后大小的一倍且文件大于64M时触发。

这里再提一下,我们知道 Redis 是单线程工作,如果重写 AOF 需要比较长的时间,那么在重写 AOF 期间, Redis将长时间无法处理其他的命令,这显然是不能忍受的。Redis为了克服这个问题,解决办法是将 AOF 重 写程序放到子程序中进行,这样有两个好处:

1. 子进程进行 AOF 重写期间,服务器进程(父进程)可以继续处理其他命令。 2. 子进程带有父进程的数据副本,使用子进程而不是线程,可以在避免使用锁的情况下,保证数据的安全 性。

使用子进程解决了上面的问题,但是新问题也产生了:因为子进程在进行 AOF 重写期间,服务器进程依然在 处理其它命令,这新的命令有可能也对数据库进行了修改操作,使得当前数据库状态和重写后的 AOF 文件状 态不一致。

为了解决这个数据状态不一致的问题,Redis 服务器设置了一个 AOF 重写缓冲区,这个缓冲区是在创建子进 程后开始使用,当Redis服务器执行一个写命令之后,就会将这个写命令也发送到 AOF 重写缓冲区。当子进 程完成 AOF 重写之后,就会给父进程发送一个信号,父进程接收此信号后,就会调用函数将 AOF 重写缓冲 区的内容都写到新的 AOF 文件中。 这样将 AOF 重写对服务器造成的影响降到了最低。

AOF的优缺点

优点:

1. AOF 持久化的方法提供了多种的同步频率,即使使用默认的同步频率每秒同步一次,最多也就丢失1秒 的数据而已。

2. AOF 文件使用 Redis 命令追加的形式来构造,因此,即使 Redis 只能向 AOF 文件写入命令的片断,使 用 redis-check-aof 工具也很容易修正 AOF 文件。

3. AOF 文件的格式可读性较强,这也为使用者提供了更灵活的处理方式。例如,如果我们不小心错用了 FLUSHALL 命令,在重写还没进行时,我们可以手工将最后的 FLUSHALL 命令去掉,然后再使用 AOF 来恢复数据。

缺点:

1. 对于具有相同数据的的 Redis,AOF 文件通常会比 RDB 文件体积更大。

2. 虽然 AOF 提供了多种同步的频率,默认情况下,每秒同步一次的频率也具有较高的性能。但在 Redis 的负载较高时,RDB 比 AOF 具好更好的性能保证。

3. RDB 使用快照的形式来持久化整个 Redis 数据,而 AOF 只是将每次执行的命令追加到 AOF 文件中,因 此从理论上说,RDB 比 AOF 方式更健壮。官方文档也指出,AOF 的确也存在一些 BUG,这些 BUG 在 RDB 没有存在。

关于RDB和AOF的思考和选择

如果可以忍受一小段时间内数据的丢失,毫无疑问使用 RDB 是最好的,定时生成 RDB 快照(snapshot)非 常便于进行数据库备份, 并且 RDB 恢复数据集的速度也要比 AOF 恢复的速度要快,而且使用 RDB 还可以 避免 AOF 一些隐藏的 bug;否则就使用 AOF 重写。但是一般情况下建议不要单独使用某一种持久化机制, 而是应该两种一起用,在这种情况下,当redis重启的时候会优先载入AOF文件来恢复原始的数据,因为在通常 情况下AOF文件保存的数据集要比RDB文件保存的数据集要完整。Redis后期官方可能都有将两种持久化方式 整合为一种持久化模型。

 redis发布订阅

 Redis 发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息。 Redis 的 subscribe 命令可以让客户端订阅任意数量的频道, 每当有新信息发送到被订阅的频道时, 信息就 会被发送给所有订阅指定频道的客户端。

为什么要用发布订阅? 熟悉消息中间件的同学都知道,针对消息订阅发布功能,市面上很多大厂使用的是kafka、RabbitMQ、 ActiveMQ, RocketMQ等这几种,redis的订阅发布功能跟这三者相比,相对轻量,针对数据准确和安全性要 求没有那么高可以直接使用,适用于小公司。

Redis有两种发布/订阅模式:

基于频道(Channel)的发布/订阅 基于模式(pattern)的发布/订阅

"发布/订阅" 包含2种角色:发布者和订阅者。发布者可以向指定的频道(channel)发送消息;订阅者可以订阅 一个或者多个频道(channel),所有订阅此频道的订阅者都会收到此消息。

订阅者订阅频道 subscribe channel [channel ...]

subscribe mrtt csdn

发布者发布消息 publish channel message

set  1 2

客户端2 (发布者) 发布消息给频道后,客户端1 (订阅者) 的客户端窗口变化

redis集群

使用redis的复制功能创建主机和从机(一对多) 主从机支持多个数据库之间的数据同步。一类是主数据库 (master主机)一类是从数据库(slave从机)(主从复制) 读写分离

从机接收主数据库同步过来的数据,一个主数据库可以有多个从数据库,而一个从数据库只能有一个主数据 库(只有一个老大)

通过redis的复制功能可以很好的实现数据库的读写分离,提高服务器的负载能力。主数据库主要进行写操 作,而从数据库负责读操作。

这样大部分80%的操作都是读取数据,所以在之前给大家介绍的架构图中,读写分离的方式,从而减轻服务 器压力,这样也就是集群的环境了。

# 一般推荐搭建方式为1主2从为最低配。

主从复制(redis集群)的作用

# 1 数据冗余: 主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式,在大数据领域,冗余一 般是指一模一样的数据存储多于一份的情况.

# 2 数据灾备(故障恢复): 当主节点出现问题时,可以由从节点提供服务,实现快速的故障服务

# 3 负载均衡:主从复制的基础之上,可以实现读写分离,提高并发了量。

# 4 高可用(集群)基础:主从复制是哨兵和集群实施的基础,因此说redis的主从复制是高可用的基础(集群 环境的基础)

# 所以在真实的项目中,我们不可能是单机模式,基本都是搭建redis集群,实现高可用和高并发

集群基础搭建(单机多集群)

基础命令

# info 查看所有配置信息 -- 信息太多。

# info server 服务器信息

# info clients 表示已连接客户端信息

# info cpu CPU 计算量统计信息

# info replication 主从复制信息 

准备工作

# 将存储方式改为rdb

# 搭建1主2从集群 6379 6380 6381

# 多复制2份 redis-config 文件 并修改对应的端口号port和dump6379.rdb dump6380.rdb dump6381.rdb

# 修改pidfile记录文件

# 修改启动日志文件名

# 主机配置 - 6379
# 主机端口port --> 6379 不用修改
# pidfile --> 守护进程产生的文件 默认redis_6379 主机也不用改
# 日志logfile --> 改成"6379.log"
# 数据库文件dbfilename --> dump6379.rdb
#从机配置-1 6380
# 主机端口port --> 6380
# pidfile --> 守护进程产生的文件 默认redis_6380
# 日志logfile --> 改成"6380.log"
# 数据库文件dbfilename --> dump6380.rdb
#从机配置-2 6381
# 主机端口port --> 6381
# pidfile --> 守护进程产生的文件 默认redis_6381
# 日志logfile --> 改成"6381.log"
# 数据库文件dbfilename --> dump6381.rdb

启动集群

创建三个虚拟机,启动6379,6380,638.

一主二从配置

# 默认情况下,每一台redis服务器都是主节点 - 所以我们只要配置从机就可以了!!

# 分别连接客户端(对应端口登录) 通过 info replication查看情况 --- 默认都是主机

# 配置策略 --从认主  主(6379) 从(6380,6381)

# 配置命令

slaveof ip port

6380: slaveof 127.0.0.1 6379

6381: slaveof 127.0.0.1 6379

info replication查看6379虚拟机

slave0:ip=127.0.0.1,port=6380,state=online,offset=168,lag=1 slave1:ip=127.0.0.1,port=6381,state=online,offset=168,lag=1

注意:我们这里使用的是命令配置的,如果服务器重启就会消失配置,真是的企业环境都配置到配置文件 中,这样就是永久配置了。

哨兵模式总结

优点

1. 哨兵集群 基于主从复制模式,所有主从复制的优点它全有 2. 主从可以切换,故障可以转移,系统可用性高 3. 哨兵模式就是主从复制的升级,从手动到自动,更加完善。

缺点

1. 一旦配置了哨兵集群,redis的扩容就会比较复杂,一旦容量到达上限在线扩容比较麻烦。 2. 实现哨兵配置集群文件也比较多,类似与redis主从配置,需要很好的设计思路。

缓存穿透、雪崩、击穿

Redis 缓存穿透 + 缓存雪崩 + 缓存击穿的原因和解决方案_骑驴的小牧童的博客-CSDN博客

————————————————
版权声明:本文为CSDN博主「骑驴的小牧童」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/womenyiqilalala/article/details/105205532

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值