文章目录
Redis(Remote Dictionary Server)是一个开源的内存数据结构存储系统,由Salvatore Sanfilippo于2009年开发。最初的开发目的是为了一家意大利初创公司提供实时Web日志分析服务,但由于其卓越的性能和广泛的应用场景,迅速发展成为广受欢迎的数据库、缓存和消息代理。
前沿
Redis的历史可以追溯到2009年,当时Salvatore Sanfilippo在为自己的项目寻找一个高性能的内存缓存解决方案时,发现现有的解决方案无法满足需求。于是,他决定开发一个新的系统,这就是Redis的起源。Redis最初发布于2009年3月,并在2010年被VMware赞助,随后在2013年又转由Pivotal赞助。随着时间的推移,Redis不断发展,增加了许多新的数据结构和功能,如发布/订阅、持久化、Lua脚本、LRU驱动事件等,使其成为现代应用中不可或缺的组成部分。
为什么近年来要使用Redis
近年来,Redis的使用显著增加,主要原因包括以下几个方面:
-
性能:Redis以其极高的读写速度著称,能够在毫秒级别内处理数百万次操作,这使其成为实时应用和高吞吐量系统的理想选择。
-
多样的数据结构:Redis不仅支持简单的键值对,还支持丰富的数据结构如列表、集合、有序集合、哈希、位图和HyperLogLog等,这使得开发者可以用更少的代码实现复杂的数据操作。
-
持久化:尽管Redis是一个内存数据库,但它提供了多种持久化机制,如RDB快照和AOF日志,可以在重启时恢复数据,从而确保数据的可靠性。
-
扩展性:Redis支持分片和集群模式,可以轻松扩展以处理更大的数据集和更高的请求量。这对于需要处理大规模并发请求的应用来说非常重要。
-
丰富的生态系统:Redis拥有丰富的客户端库和工具支持,几乎覆盖所有主流编程语言,方便开发者在不同平台和环境中使用。
使用Redis的好处
使用Redis的好处显而易见:
-
高性能:Redis的内存存储和高效的数据操作使其能够提供极快的响应时间,适用于需要低延迟和高并发的应用,如实时分析、缓存和会话存储等。
-
灵活的数据操作:支持多种数据结构和丰富的操作,使得开发者可以方便地实现复杂的应用逻辑,而无需依赖外部系统。
-
简化架构:Redis的多功能性减少了系统中需要的组件数量,可以用作数据库、缓存、消息队列等,从而简化系统架构和维护成本。
-
高可用性和持久化:通过主从复制、哨兵和集群模式,Redis提供了高可用性和数据持久化支持,确保数据的安全性和系统的稳定性。
总之,Redis凭借其高性能、多样的数据结构、易用性和强大的功能,成为现代应用中广泛使用的数据存储解决方案。无论是在初创公司还是大型企业中,Redis都能有效提升系统性能和可靠性,是不可多得的优秀工具,接下来让我们一起探究redis的巧妙吧。
redis 安装
- 先去redis官网选择对应的版本,window下载.zip linux下载.tar
- 进入liunx安装目录执行make下载对应的依赖(linux是由c语言编写的需要gcc环境, brew/wget install gcc -c++)
- 进入redis的src/redis-server 启动redis服务,默认redis是占用式(启动服务之后就不能在原有的窗口输入命令了)
- 进入reds.conf 修改 redis的默认占用式启动,309行daemonize no 改为 yes. 1049行 requirepass 自己的密码
- 用配置文件命令的方式启动redis redis-server ./redis.conf
- 配置redis接受远程连接
- bind 127.0.0.1 -::1 注释掉
- protected-mode yes 改为no
- 之后将redis的进程清理掉再重启 1 ps -ef | grep redis , redis-server ./redis/conf
- 开放远程服务器的防火墙,远程服务器的安全组 => 防火墙打开6379保存即可
redis常用命令
- redis-cli 连接本地redis客户端
redis-cli [-options] -h -p -a
# -h:host -p:port -a:指定密码
- redis-server ./redis.conf 通过配置文件启动redis服务
redis mac m1客户端
通过命令安装
brew install --cask another-redis-desktop-manager
-
连接本地redis服务器出现 Client On Error: ReplyError: WRONGPASS invalid username-password pair or user is disabled. Config right?
解决办法就是name/username空着就行不需要填写
linux配置环境变量
- 编辑/etc/profile
redis/src # vim /etc/profile
export PATH = ${PATH}:/redis/redis-7.02/src
- 遇到/private/etc/profile E212 Can open file for writing无法写入的情况可以试试
1.点击esc 2.输入: w ! sudo tee % 即可保存
Geospatial地理位置
- geoadd city 添加城市经度 116.20 39.56 beijing 120.52 30.40 shanghai 添加两座城市的经度
- geopos city xxxx 查看该城市的经度纬度
- geodist city xxx xxx m/km/ft 两地城市的距离 默认是m
- geoserach frommember|经纬度 beijing byradius 500km 以某个城市为圆心搜索指定半径范围内的元素
HperLoglog 基数统计
基数:统计集合中去重之后的个数。
常用场景:
- 统计网站的访问uv(独立访问次数)
- 一首音乐的独立用户播放
- 一段视频的独立用户播放
常用命令
# 1.PFADD KEY NUM 添加
PFADD userlogin 1 2 3 4 5 6
# 2.PFCOUNT KEY 统计某个key的基数
PFCOUNT userlogin 6
# 3.PFMERGE key1 key2 合并两个key
PFMERGE hll1 hll2
BitMaps
redis 的bitmaps是一种紧凑的数据类型,用于存储二进制的逻辑和状态,bitmaps不是实际的数据类型,而是定义在string上的一组位操作。占用空间及其的少1bit。
常用场景
- 打卡 0表示未打 1表示打卡
- 登陆状态
常用命令
# SETBIT key index val
SETBIT user1:week 0 1 # 统计一周员工打卡情况
# GETBIT key
GETBIT user1:week
# BITOP and destkey [key...] 该命令可以用位操作 AND OR XOR NOT
# 统计连续登录的用户
BITOP AND distkey user1:week user2:week
# BITCOUNT key start end
# 统计员工打卡情况
BITCOUNT user1:week 0 0
# STRLEN key 查看某个key内存占用情况
STRLEN user1:week
Redis事务处理
redis事务不保证原子性,事务中有个命令错了(非语法错误,例如 incr str ),不会影响其他命令的执行,但是如果开启了事务,并且写的命令存在语法错误则提交事务时会失败。
事务:业务中整体执行,整体成功or整体失败,以整体的形式去执行,一次性执行多个命令
redis事务命令
- multi 开启事务 尾部会有个TX标识 ,输入的命令都会进入queued事务队列中等待exec来执行
- discard 取消事务
- exec 执行事务
redis乐观锁
redis如何完成乐观锁:就是说redis自带的watch监听的机制
秒杀业务就是通过redis的watch命令来实现了乐观锁的,**如果在事务执行之前watch监听的这些值发生了变化,那么事务会被打断,一旦执行了EXEC or DISCARD命令,所有的watch监控都会被取消。**并发安全问题。
乐观锁:为了保证数据的一致性。
例子:有同台客户端同时操作redis
set age 21
b1开启了事务
b1 (TX)> incr age // 自增年龄
queued
b2> incr age // 22
b1(TX)> exec
b1> get age
b1> 23
----------watch监听解决--------
开启事务前先用watch监控,最后用unwatch释放监控
watch age name
...
unwatch
watch key 在事务提交时,如果watch所监听到的key的值,被其他客户端所更改时,当执行exec命令时就会出现nil,该事务的整体都不会进行更改。
Redis持久化
redis单线程,redis提供两种持久化机制RDB和AOF ,默认是RDB
RDB快照模式
RDB即快照模式,它是redis默认的持久化方式,它将redis数据库的快照保存在dump.rdb这个二进制文件中。快照:就是将内存数据以二进制文件的形式保存起来,通过时间间隔以快照的方式保存。
redis使用操作系统的多进程COW(copy on writer)机制来实现快照持久化操作。
RDB实际上是redis内部的一个定时器事件
数据备份策略:
- 手动策略
- 通过save / bgsave来保存二进制指令到dump.rdb,推荐使用basave它是开启个子进程来调用fork()来将数据保存到dump.rdb文件,而save则是同步的,写入数据量庞大时,会面临阻塞或者是redis断开连接。
- 自动策略
- 进入redis.conf文件中 找到save 900 1 300 这个是 redis的自动保存策略。900秒 至少有一个key被更改,则创建副本备份数据。
RDB持久化的优劣势
RDB持久化的过程中会开个子进程将redis所有的数据都保存到dump.rdb文件中,即消耗资源也浪费时间。所以不能频繁的创建rdb文件,会严重影响服务器的性能。
RDB最大的不足就是最后一次持久化可能会丢失一部分的数据,在持久化的过程中服务器宕机了,这时存储的数据就并不完整,比如子进程创建好了rdb文件,但服务器还没来得及用它来覆盖旧的rdb文件,这样就会把最后一次持久化的数据丢失。
RDB数据持久化适用于大规模的数据恢复,并且还原速度快,如果对数据完整性不特别的敏感,推荐使用RDB。
AOF追加模式
AOF被称为追加模式,日志模式,它只储存redis服务器已经执行的命令,并且只记录对内存有过修改的命令,这种记录方式叫做增量复制。默认存储文件为appendonly.aof。
# 默认情况下appendonly no关闭的,需要在redis.conf中自行打开
重写机制
redis长期运行,aof的文件会越来越大,我们可以通过BGREWRITEAOF命令来为aof文件瘦身
自动触发AOF重写
# redis.conf 默认配置项
auto-aof-rewrite-percentage 100 # 100则表示1倍,200,则2倍
auto-aof-rewrite-min-size 64mb #触发自动重写aof最小的体积,大于or等于64mb则自动触发
redis主从模式
主从模式:一matser多slave,master负责写读,而slave只能读。
主节点一旦宕机,从节点一直等待主节点重新连接
# 单机部署多应用 用端口隔离
127.0.0.1 6379 master
127.0.0.1 6380 slave
127.0.0.1 6381 slave
# 多机部署 ip隔离
127.0.0.1 6379 master
127.0.0.2 6379 slave
127.0.0.3 6379 slave
搭建主从模式
# 通过命令开启一个端口为6300的从机
redis-server --prot 6300 --slaveof 127.0.0.1 6379 --masterauth sanyueshui --requirepass sanyueshui
- 取消主从关联
# 在从服务器中使用 即可取消主从模式
slaveof no one
- 查看主从情况信息
info replication
配置文件实现主从模式
# 拷贝三份redis的配置文件 redis6300.conf/redis6301.conf
cp ./redis7.2.4/redis.conf ./redis7.2.4/redis6300.conf
# 修改redis6300.conf的端口,主节点密码和6300本机的密码,并关联主节点
port 6300
masterauth 主节点的密码
requirepass 6300从节点的密码
slaveof 127.0.0.1 6379 or replicaof 127.0.0.1 6379 关联主节点
# 保存启动服务即可
# 用配置文件的方式,就写死了主从关系,不灵活。
哨兵模式
哨兵模式是基于主从模式上的改进,自动版主从模式,哨兵模式默认会每30秒轮询一次,查看主节点是否正常工作,如果出现宕机则会触发故障转移,投票选举新的主节点。旧的主节点恢复正常之后会变成从节点跟主节点关联。
主观下线
哨兵是基于心跳机制来检测服务器的状态,每隔一秒向服务器ping ,如果sentinel发现某个实例为在指定时间范围内响应,则认为该实例主观下线
客观下线
若超过一定数量的sentinel都认为该实例主观下线,则被称为客观下线,数量一般是一半以上。
故障转移
- sentinel会给备选的节点发送slaveof on one,让该节点成为主节点。
- sentinel给其他slave发送slaveof host port命令,开始从master上同步数据。
- 最后sentinel将故障节点标记为slave,故障节点恢复后会成为新的主节点的slave。
单机部署哨兵模式
# 复制一份sentinel.conf 文件
cp ./redis/sentinel.conf ./sentinel6300.conf
# 覆盖sentinel6300.conf文件的内容
sentinel monitor myredis 127.0.0.1 6379 1 # sentinel/2+1
sentinel auth-pass myredis sanyueshui
# 主节点宕机以后选举的间隔时间为多少s 单位毫秒
sentinel down-after-milliseconds myredis 10000
# 通过redis-sentinel命令启动 哨兵模式
redis-sentinel ./sentinel6300.conf
redis集群模式
redis集群模式有16384个槽,redis会将每个key进行hash来决定存储在哪个槽中。
- 集群命令
# 查看当前节点集群信息
cluster info
# 查看所有集群的信息
cluster nodes
单机搭建集群模式
# 创建6个目录 并拷贝redis.conf至目录中
mkdir ./cluster -p 8001 8002 8003 8004 8005 8006
cp ./redis.conf ../redis/800x
# 编辑reedis.conf配置文件
daemonize yes
port 8001
# 修改数据存储的目录,这里必须要指定不同的目录位置,否者会造成数据的丢失
dir /redis/cluster/8001
# 开启集群模式
cluster-enabled yes
# 集群节点信息文件,这里最好和端口保持一致
cluster-config-file nodes-8001.conf
# 集群节点的超时时限,单位是毫秒
cluster-node-timeout 10000
# 如果是多机设置,阿里云的内网IP或者注释掉或者bind:0.0.0.0、
bind 0.0.0.0
# 受保护模式
protected-mode no
# 开启aof数据持久化
appendonly yes
# aof持久化文件名
appendfilename "appendonly8001.aof"
# aof文件所存放的位置,它是和dir一起拼接形成的,比如:/redis/redis7.2.4/cluster/8001/aof/appendonly8001.aof
appenddirname "aof"
# 当前服务节点的密码
requirepass sanyueshui
# 主节点的密码
masterauth sanyueushui
# 用redis-server ./cluster/8001/redis8001.conf 分别启动6个服务
# 使用redis-cli创建redis集群 num:代表1主多少从 -a:每个节点的密码
# redis-cli --cluster create --cluster-replicas num host:port -a password
redis-cli --clutser create --cluster-replicas 1 127.0.0.1:8001 127.0.0.1:8002
127.0.0.1:8003 127.0.0.1:8004 127.0.0.1:8005 127.0.0.1:8006 -a sanyueshui
# 集群的关闭
redis-cli -c -h 127.0.0.1 -p 8001 -a sanyueshui shutdown
集群节点的增加
# 新增个8007的主节点
redis-server ./redis/cluster/8007/redis.conf
# 通过add-node添加节点 这里的127.0.0.1:8001是做陪衬
redis-cli --cluster add-node 127.0.0.1:8007 127.0.0.1:8001 -a sanyueshui
# 给新添加的主节点添加hash槽
redis-cli --cluster reshard 127.0.0.1:8007 -a sanyueshui
# How many slots do you want to move ? 分配多少空间 1000
# what is the receiving node ID ? 给谁分配空间 输入需要分配空间节点uuid即可
# Please enter all the source node IDs 是否为其他的节点重新分配hash槽 all
集群主从节点关系绑定
# 进入从节点的客户端
cluster replicate 绑定主节点的uuid
集群节点删除
# 删除从节点
redis-cli --cluster del-node 127.0.0.1:8008 uuid -a sanyeushui
# 删除主节点 需要将主节点占用的槽位先释放或者是迁移
redis-cli --cluster reshard 127.0.0.1:8087 -a sanyueshui --cluster-from 被删除主节点的uuid
# How many slots do you want to move ? 分配多少空间 1000
# what is the receiving node ID ? 将槽空间给哪个节点 主节点的uuid
缓存穿透 key不存在
当用户去查询某个数据时,会先进入redis缓存,redis缓存没有之后才会去查询数据库,数据库也查询不到的话,就会返回一个空对象,表示查询失败,列如这种请求特别的多,或者是用户利用这种请求来恶意攻击,就会给mysql数据库造成很大的压力,甚至是奔溃,这种现象就叫缓存穿透。
解决方案一:缓存空对象
当mysql返回空对象时,redis将这个对象存储起来,同时为其设置一个过期时间,当用户再次发起相同的请求时,就会从缓存中拿到这个空对象,用户的请求阻断在了redis的缓存层,从而保护了后端数据库,但这种策略会占用redis的缓存空间。
解决方案二:布隆过滤器
用布隆过滤器来判断数据是否存在,可以预防缓存穿透,首先将用户可能会访问的热点数据都存储在布隆过滤器中(也成为缓存预热)当用户请求时会先进入布隆过滤器,如果请求的数据不存在,布隆过滤器会拒绝该请求,用布隆过滤器更高效。
缓存击穿 key过期
用户查询的数据在redis缓存中不存在,但是在数据库中存在,这种原因是由于redis缓存中方的key过期导致的,比如一个热点数据key,在某个时间段失效了,但是它无时无刻都在接受大量的并发访问,则这些请求就会进入后端数据库,导致压力瞬间增大。
解决方案一:改变key的过期时间
设置热点数据永不过期。
把时间全部错开
解决方案二:分布式锁
重新设计缓存的使用方式:
- 上锁:当我们通过key去查询数据时,首先查询缓存,如果没有,就通过分布式锁进行加锁,第一个获取锁的进程进入后端数据进行查询,并将查询结果缓存到redis中。
- 解锁:当其他进程发现锁被某个进程占用时,就进入等待状态,直至解锁之后其余进程在以此访问被缓存的key。
缓存雪崩 大量key同时过期
缓存中大量的key同时过期,此时数据访问量又非常大,从而导致后端数据库压力突然暴增,甚至会挂掉,它和缓存击穿不同,击穿是在并发量特别大,某个key突然过期,而雪崩是大量的key同时过期,它们不是一个量级的。
解决方案
- 高可用
- 集群
布隆过滤器
布隆过滤器是redis4.0的新功能,它是以插件的形式加载到redis中,给redis提供强大的去重功能。
布隆过滤器在空间上能节省90%以上,但他的不足之处是去重率99%左右,也就是说有1%左右的误判率,在处理海量数据时,几乎可以忽略。
黑白名单,邮件判重,缓存穿透,过滤
布隆过滤器原理
当布隆过滤器添加key时,会使用不同的hash函数对key存储的元素值进行哈希计算,从而会得到多个哈希值,根据哈希值计算出一个整数索引值,将该索引值与位数组长度做取余运算,最终得到一个为数组位置,并将该位置的值变为1,每个hash函数都会计算出一个不同的位置,然后吧数组中与之对应订单位置变为1。通过上述过程就完成了元素的添加(add)操作。
下载安装bloom
- 通过wget 命令下载
wget https://github.com/RedisBloom/RedisBloom
- 解压并安装依赖
tar -zxvf ./redis/RedisBloom
make
- 在redis.conf文件末尾添加
loadmodule /redis/redis-7.2.4/RedisBloom-2.2.8/redisbloom.so
- 重启配置文件
redis-server ./redis/redis.conf
常用命令
bf.add # 添加元素到布隆过滤器
bf.exists # 判断某个元素是否存在于布隆过滤器
bf.madd # 同时添加多个元素到布隆过滤器
bf.mexists # 同时判断多个元素是否存在布隆过滤器
bf.reserve # 以自定义的方式设置布隆过滤器参数值,共3个参数 key error_rate(错误率) initial_size(初始大小)
bf.reserve recard 0.01 1000 # 设定recard 的出错率为 1000中的0.0.1
Go操作redis
- 下载go的redis模块
go get github.com/gomodule/redigo/redis
var RedisClientPool *redis.Pool
func InitJimDb() {
RedisClientPool = &redis.Pool{
MaxIdle: 100,
MaxActive: 12000,
IdleTimeout: time.Duration(180),
Dial: func() (redis.Conn, error) {
c, err := redis.Dial("tcp", "127.0.0.1:6379", redis.DialPassword("xxxxxx"),redis.DialReadTimeout(time.Second),redis.DialWriteTimeout(time.Second))
if err != nil {
logger.Errorf("redisClient dial host: %s, auth: %s err: %s", "127.0.0.1:6379", "xxxxxx", err.Error())
return nil, err
}
return c, nil
},
TestOnBorrow: func(c redis.Conn, t time.Time) error {
if time.Since(t) < time.Minute {
return nil
}
_, err := c.Do("PING")
if err != nil {
logger.Errorf("redisClient ping err: %s", err.Error())
}
return err
},
}
logger.Infof("init jimDb ok")
}
func CloseJimDb() {
if RedisClientPool != nil {
err := RedisClientPool.Close()
if err != nil {
logger.Errorf("do CloseJimDb error:%s", err.Error())
}
}
}