当前位置:   article > 正文

Redis_redis服务器存在1s的误差

redis服务器存在1s的误差

简介

redis底层用C语句编写,redis是将数据存放到内存

  • 优点:存取速度快,官方称读取速度会达到30万次每秒,写速度在10万次每秒最有,具体限制于硬件.
  • 缺点:对持久化支持不够良好,所以一般不作为数据的主数据库存储,而是配合传统的关系型数据库使用.
    redis主要就是使用命令来进行操作,java端在代码中可以使用Jedis来操作redis服务器

应用领域

  • String:缓存、限流、计数器、分布式锁、分布式Session
  • Hash:存储用户信息、用户主页访问量、组合查询;一般应用于将redis作为分布式缓存,存储数据库中的数据对象。结构:map<ket,<key,value>>
  • List:微博关注人时间轴列表、简单队列;redis中使用的是双向循环链表来实现
  • Set:赞、踩、标签、好友关系
  • Zset:排行榜;应用商店流行软件排名等需求中
    在这里插入图片描述

redis持久化方案

RDB

一种快照存储持久化方式,具体就是将Redis某一时刻的内存数据保存到硬盘的文件当中,默认保存的文件名为dump.rdb,可以设置间隔多长时间保存一次(Redis不用任何配置默认的持久化方案)

优点:
1:与AOF方式相比,通过rdb文件恢复数据比较快。
2:rdb文件非常紧凑,适合于数据备份。
3:通过RDB进行数据备,由于使用子进程生成,所以对Redis服务器性能影响较小。

缺点:
1:服务器断电时会丢失部分数据(数据的完整性得不到保证)
2:使用save命令会造成服务器阻塞,直接数据同步完成才能接收后续请求。
3:使用bgsave命令在forks子进程时,如果数据量太大,forks的过程也会发生阻塞,另外,forks子进程会耗费内存。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

开启rdb持久化方式:
客户端可以通过向Redis服务器发送save或bgsave命令让服务器生成rdb文件,或者通过服务器配置文件指定触发RDB条件。
1:当客户端向服务器发送save命令请求进行持久化时,服务器会阻塞save命令之后的其他客户端的请求,直到数据同步完成。如果数据量太大,同步数据会执行很久,而这期间Redis服务器也无法接收其他请求,所以,最好不要在生产环境使用save命令。
2:与save命令不同,bgsave命令是一个异步操作。当客户端发服务发出bgsave命令时,Redis服务器主进程会forks一个子进程来数据同步问题,在将数据保存到rdb文件之后,子进程会退出。所以,与save命令相比,Redis服务器在处理bgsave采用子线程进行IO写入,而主进程仍然可以接收其他请求,但forks子进程是同步的,所以forks子进程时,一样不能接收其他请求,这意味着,如果forks一个子进程花费的时间太久(一般是很快的),bgsave命令仍然有阻塞其他客户的请求的情况发生。
3:服务器配置触发条件(被动触发)
就是在Redis配置文件中的save指定到达触发RDB持久化的条件,比如【多少秒内至少达到多少写操作】就开启RDB数据同步。该方式与bgsave命令类似,达到触发条件时,会forks一个子进程进行数据同步,不过最好不要通过这方式来触发RDB持久化,因为设置触发的时间太短,则容易频繁写入rdb文件,影响服务器性能,时间设置太长则会造成数据丢失。

例如我们可以在配置文件redis.conf指定如下的选项:

# 300s内至少达至10条写命令
save 300 10
  • 1
  • 2

之后在启动服务器时加载配置文件。

# 启动服务器加载配置文件
redis-server redis.conf
  • 1
  • 2
AOF

AOF持久化方式会记录客户端对服务器的每一次写操作命令,并将这些写操作以Redis协议追加保存到以后缀为aof文件末尾,在Redis服务器重启时,会加载并运行aof文件的命令,以达到恢复数据的目的。

  • 开启AOF持久化方式
    Redis默认不开启AOF持久化方式,我们可以在配置文件中开启并进行更加详细的配置,如下面的redis.conf文件:
# 开启aof机制
appendonly yes

# aof文件名
appendfilename "appendonly.aof"

# 写入策略,always表示每个写操作都保存到aof文件中,也可以是everysec或no
appendfsync always //这种策略很安全,但是每个写请注都有IO操作,所以也很慢。
everysec:默认写入策略,每秒写入一次aof文件,因此,最多可能会丢失1s的数据。
NO:Redis服务器不负责写入aof,而是交由操作系统来处理什么时候写入aof文件。更快,但也是最不安全的选择,不推荐使用。

# 默认不重写aof文件;客户端向服务器发送bgrewriteaof命令,也可以让服务器进行AOF重写。
no-appendfsync-on-rewrite no

# 保存目录
dir ~/redis/
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
在写入aof日志文件时,如果Redis服务器宕机,则aof日志文件文件会出格式错误,在重启Redis服务器时,Redis服务器会拒绝载入这个aof文件,可以通过以下步骤修复aof并恢复数据。

1、备份现在aof文件,以防万一。
2、使用redis-check-aof命令修复aof文件,该命令格式如下:
     $ redis-check-aof -fix file.aof
3、重启Redis服务器,加载已经修复的aof文件,恢复数据。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

优点:AOF只是追加日志文件,因此对服务器性能影响较小,速度比RDB要快,消耗的内存较少。
缺点:AOF方式生成的日志文件太大,即使通过AFO重写,文件体积仍然很大;恢复数据的速度比RDB慢。
混合持久化是Redis 4.0才有的功能(默认关闭)。通过aof-use-rdb-preamble配置参数控制,yes则表示开启,no表示禁用,默认是no,可通过config set修改。
当开启混合持久化时,fork出的子进程先将共享的内存副本以RDB方式写入AOF文件,然后在将重写缓冲区的增量命令以AOF方式写入到文件,写入完成后通知主进程更新统计信息,并将新的AOF文件替换旧的的AOF文件。
简单的说:新的AOF文件前半段是RDB格式的全量数据,后半段是AOF格式的增量数据。

混合模式可以结合AOF和RDB的优点,能够快速加载的同时避免丢失过多的数据。

Redis为何这么快

  • 当我们执行set hello world 时,会有如下数据模型
    在这里插入图片描述

  • redisObject对象非常重要,Redis对象的类型、内部编码、内存回收、共享对象等功能,都需要redisObject支持。这样设计的好处是,可以针对不同的使用场景,对5中常用类型设置多种不同的数据结构实现,从而优化对象在不同场景下的使用效率。

  • 无论是dictEntry对象,还是redisObject、SDS对象,都需要内存分配器(如jemalloc)分配内存进行存储。jemalloc作为Redis的默认内存分配器,在减小内存碎片方面做的相对比较好。比如jemalloc在64位系统中,将内存空间划分为小、大、巨大三个范围;每个范围内又划分了许多小的内存块单位;当Redis存储数据时,会选择大小最合适的内存块进行存储。

  • 前面说过,Redis每个对象由一个redisObject结构表示,它的ptr指针指向底层实现的数据结构,而数据结构由encoding属性决定

安装Redis

#首先下载一些依赖
yum -y install cpp binutils glibc glibc-kernheaders glibc-common glibc-devel gcc make gcc-c++ libstdc++-devel tcl

mkdir -p /usr/local/src/redis
cd /usr/local/src/redis
#wget下载命令
wget http://download.redis.io/releases/redis-3.0.2.tar.gz  或者 rz 上传
tar -xvf redis-3.0.2.tar.gz
cd redis-3.0.2
make
make test #这个不用执行,会需要很长时间
make install
#复制conf配置文件 
cp /usr/local/redis-3.0.0/redis.conf  /usr/local/redis/bin
vi /etc/redis.conf
#修改下面的内容。默认为no(可以使redis在后台运行)
daemonize yes
#启动
redis-server/ect/redis.conf
#测试
redis-cli
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

主从复制

  • 主从复制的好处有2点:
    避免redis单点故障
    构建读写分离架构,满足读多写少的应用场景
  • 实例
    创建三个目录,将Redis复制三份,分别将安装目录下的redis.conf拷贝到这三个目录下。
    分别进入这三个目录,分别修改配置文件,将端口分别设置为:6379(Master)、6380(Slave)、6381(Slave)。同时要设置pidfile文件为不同的路径。
    启动三个实例
  • 设置主从(在redis中设置主从有2种方式)
    在redis.conf中设置slaveof
    slaveof <masterip> <masterport>
    使用redis-cli客户端连接到redis服务,执行slaveof命令
    slaveof <masterip> <masterport>
    第二种方式在重启后将失去主从复制关系。
    查看主从信息:INFO replication
  • 特点
    Master可以进行set,get,flush等操作,并且同步到所有的Slave中,但是slave只能进行读操作。

主丛丛复制

  • 优点
    可以减轻Master的压力,只需复制自己的rdb文件给下一级的slave
    可以将持久化动作交给slave,关闭自己的持久化,提高性能。
  • 默认情况下redis数据库充当slave角色时是只读的不能进行写操作。
    可以在配置文件中开启非只读:slave-read-only no

复制原理

  • 复制的过程原理
    当从库和主库建立MS关系后,会向主库发送SYNC命令;
    主库接收到SYNC命令后会开始在后台保存快照(RDB持久化过程),并将期间接收到的写命令缓存起来;
    当快照完成后,master会将快照文件和所有缓存的写命令发送给slave;
    slave接收到后,会载入快照文件并且执行收到的缓存的命令;
    之后,master每当接收到写命令时就会将写命令发送slave,从而保证数据的一致;
  • 无磁盘复制
    当master接收到SYNC的命令时会执行RDB过程,即使在配置文件中禁用RDB持久化也会生成,那么如果主库所在的服务器磁盘IO性能较差,那么这个复制过程就会出现瓶颈,Redis在2.8.18版本开始实现了无磁盘复制功能,Redis在与从数据库进行复制初始化时将不会将快照存储到磁盘,而是直接通过网络发送给从数据库,避免了IO性能差问题。
  • 开启无磁盘复制:repl-diskless-sync yes

主从复制架构中出现宕机

  • 从Redis宕机(slave)
    这个相对而言比较简单,在Redis中从库重新启动后会自动加入到主从架构中,自动完成同步数据;
    如果从库在断开期间,主库的变化不大,从库再次启动后,(从库有做持久化的前提下)因为在Redis2.8版本后就实现了,主从断线后恢复的情况下实现增量复制。
  • 主Redis宕机(master)
    第一步,在从数据库中执行SLAVEOF NO ONE命令,断开主从关系并且提升为主库继续服务;
    第二步,将主库重新启动后,执行SLAVEOF命令,将其设置为其他库的从库,这时数据就能更新回来;可使用Redis提高的哨兵(sentinel)的功能。实现自动切换。

哨兵(sentinel)

哨兵的作用就是对Redis的系统的运行情况的监控,它是一个独立进程。它的功能有2个:

  • 监控主数据库和从数据库是否运行正常;
  • 主数据出现故障后自动将从数据库转化为主数据库;
    多个哨兵,不仅同时监控主从数据库,而且哨兵之间互为监控。

配置哨兵

启动哨兵进程首先需要创建哨兵配置文件:

  • vim sentinel.conf
    输入内容:
    sentinel monitor xiaoMaster 127.0.0.1 6379 1
    …(配置多个,即表示多个哨兵)

  • 说明:
    xiaoMaster:监控主数据的名称,自定义即可,可以使用大小写字母和“.-_”符号
    127.0.0.1:监控的主数据库的IP
    6379:监控的主数据库的端口
    1:最低通过票数

  • 启动哨兵进程:
    redis-sentinel ./sentinel.conf

集群

即使有了主从复制,每个数据库都要保存整个集群中的所有数据,容易形成木桶效应。使用Jedis实现了分片集群,是由客户端控制哪些key数据保存到哪个数据库中,如果在水平扩容时就必须手动进行数据迁移,而且需要将整个集群停止服务,这样做非常不好的。Redis3.0版本的一大特性就是集群(Cluster)

架构

  • 所有的redis节点彼此互联(PING-PONG机制),内部使用二进制协议优化传输速度和带宽.
  • 节点的fail是通过集群中超过半数的节点检测失效时才生效.
  • 客户端与redis节点直连,不需要中间proxy层.客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可
  • redis-cluster把所有的物理节点映射到[0-16383]slot(插槽)上,cluster 负责维护node<->slot<->value

修改配置文件(redis.conf)

设置不同的端口,6379、6380、6381(只有在单机模拟时才需要)
开启集群,cluster-enabled yes
指定集群的配置文件,cluster-config-file “nodes-xxxx.conf”

创建集群

安装ruby环境

  • 因为redis-trib.rb是有ruby语言编写的所以需要安装ruby环境。
    yum -y install zlib ruby rubygems
    gem install redis
  • 手动安装:
    rz上传redis-3.2.1.gem
    gem install -l redis-3.2.1.gem

步骤

  • 首先,进入redis的安装包路径下:cd /usr/local/redis/redis-3.0.1/src/
  • 执行命令:
    ./redis-trib.rb create --replicas 0 192.168.56.102:6379 192.168.56.102:6380 192.168.56.102:6381
    –replicas 0:指定了从数据的数量为0
    注意:这里不能使用127.0.0.1,否则在Jedis客户端使用时无法连接到!
  • 测试
    当key的hash槽信息是在6380上,现在使用redis-cli连接的6379,无法完成set操作,需要客户端跟踪重定向。(redis-cli -c)
    这里写图片描述

使用Jedis连接到集群

添加依赖,要注意jedis的版本为2.7.2
这里写图片描述

说明:这里的jc不需要关闭,因为内部已经关闭连接了。

插槽的分配

  • 通过cluster nodes命令可以查看当前集群的信息:

    该信息反映出了集群中的每个节点的id、身份、连接数、插槽数等。
  • 当我们执行set abc 123命令时,redis是如何将数据保存到集群中的呢
  • 接收命令set abc 123
  • 通过key(abc)计算出插槽值,然后根据插槽值找到对应的节点。(abc的插槽值为:7638)
  • 重定向到该节点执行命令
    整个Redis提供了16384个插槽,也就是说集群中的每个节点分得的插槽数总和为16384。
    ./redis-trib.rb 脚本实现了是将16384个插槽平均分配给了N个节点。
    注意:如果插槽数有部分是没有指定到节点的,那么这部分插槽所对应的key将不能使用。

插槽和key的关系

  • 计算key的插槽值:
    key的有效部分使用CRC16算法计算出哈希值,再将哈希值对16384取余,得到插槽值。
  • 什么是有效部分?
  • 如果key中包含了{符号,且在{符号后存在}符号,并且{和}之间至少有一个字符,则有效部分是指{和}之间的部分;{hello}_tatao的有效部分是hello
  • 如果不满足上一条情况,整个key都是有效部分;

新增集群节点

  • 首先再开启一个实例的端口为6382
  • 执行脚本:./redis-trib.rb add-node 192.168.56.102:6382 192.168.56.102:6379
  • 接下来需要给6382这个服务分配插槽,将6379的一部分(1000个)插槽分配给6382:

删除集群节点(假设我们想要删除6380这个节点)

想要删除集群节点中的某一个节点,需要严格执行2步:

  • 将这个节点上的所有插槽转移到其他节点上;
    执行脚本:./redis-trib.rb reshard 192.168.56.102:6380
    按照要求输入值即可。
  • 使用redis-trib.rb删除节点(格式:del-node host:port node_id)
    ./redis-trib.rb del-node 192.168.56.102:6380 4a9b8886ba5261e82597f5590fcdb49ea47c4c6c

故障转移

  • 集群中的每个节点都会定期的向其它节点发送PING命令,并且通过有没有收到回复判断目标节点是否下线;
  • 集群中每一秒就会随机选择5个节点,然后选择其中最久没有响应的节点放PING命令;
  • 如果一定时间内目标节点都没有响应,那么该节点就认为目标节点疑似下线;
  • 当集群中的节点超过半数认为该目标节点疑似下线,那么该节点就会被标记为下线;
  • 当集群中的任何一个节点下线,就会导致插槽区有空档,不完整,那么该集群将不可用;
  • 解决
  • 在Redis集群中可以使用主从模式实现某一个节点的高可用
  • 当该节点(master)宕机后,集群会将该节点的从数据库(slave)转变为(master)继续完成集群服务;
创建主从集群
  • 需要启动6个redis实例,分别是:
    6379(主) 6479(从)
    6380(主) 6480(从)
    6381(主) 6481(从)
    命令:
    cd 6379/ && redis-server ./redis.conf && cd …
    cd 6380/ && redis-server ./redis.conf && cd …
    cd 6381/ && redis-server ./redis.conf && cd …
    cd 6479/ && redis-server ./redis.conf && cd …
    cd 6480/ && redis-server ./redis.conf && cd …
    cd 6481/ && redis-server ./redis.conf && cd …
  • 创建集群,指定了从库数量为1,创建顺序为主库(3个)、从库(3个):
    ./redis-trib.rb create --replicas 1 192.168.56.102:6379 192.168.56.102:6380 192.168.56.102:6381 192.168.56.102:6479 192.168.56.102:6480 192.168.56.102:6481
  • 测试(master宕机,回复后变成slave,slave宕机,不影响集群)
  • 保存、读取数据OK!说明集群运行没问题
  • kill掉一个节点

    发现6480节点不可用。
  • 当我们保存,读取数据时,发现集群可用,可见从数据库宕机不会影响集群正常服务。
  • 回复节点之后,看到节点中的信息为最新版的信息。

使用集群需要注意的事项
1、多键的命令操作(如MGET、MSET),如果每个键都位于同一个节点,则可以正常支持,否则会提示错误。
2、集群中的节点只能使用0号数据库,如果执行SELECT切换数据库会提示错误。

参考:my.oschina.net/liughDevelop/blog/2236771

Redis实用监控工具

redis-benchmark

  1. 简述
    Redis自带的性能检测工具, 该工具可以模拟 N 个客户端同时发出 Y 个请求。可以使用 redis-benchmark -h 来查看基准参数。
  2. 命令格式
    redis-benchmark [-h ] [-p ] [-c ] [-n <requests]> [-k ]
    在这里插入图片描述
  3. 实例
    redis-benchmark -n 1000 -q : 同时执行1000个请求来检测性能:
    redis-benchmark -h localhost -p 6379 -c 50 -n 10000:50个并发请求,10000个请求

redis-cli

  1. 简介
    查看redis的连接及读写操作
  2. 命令格式
    redis-cli -h xx -p yy monitor
  3. redis-cli info:
    Redis 监控最直接的方法就是使用系统提供的 info 命令,只需要执行该条命令,就能获得 Redis 系统的状态报告。
    结果会返回 Server、Clients、Memory、Persistence、Stats、Replication、CPU、Keyspace 8个部分。从info大返回结果中提取相关信息,就可以达到有效监控的目的

showlog

  1. 简介
    redis的slowlog是redis用于记录记录慢查询执行时间的日志系统。由于slowlog只保存在内存中,因此slowlog的效率很高,完全不用担心会影响到redis的性能。Slowlog是Redis从2.2.12版本引入的一条命令。
  2. 命令格式
    在redis-cli中有关于slowlog的设置:
   CONFIG SET slowlog-log-slower-than 6000
   CONFIG SET slowlog-max-len 25
  • 1
  • 2

设置完成后使用 SGOWLOG GET 20 查看数据

RedisLive(第三方监视工具)

  1. 简述
    RedisLive是由Python编写的开源的图形化监控工具。核心服务部分只包括一个web服务和基于Redis自带的Info命令以及monitor命令的监控服务。支持多实例监控,监控信息可以使用redis存储和sqlite持久化存储。(最好使用Python2.7来运行)
  2. 安装
//安装依赖
yum install epel-release
sudo yum install python-pip
pip install --upgrade pip
pip install tornado
pip install redis
pip install python-dateutil
//安装RedisLive
git clone https://github.com/nkrode/RedisLive.git
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

修改配置文件redis-live.conf(cd RedisLive/src)

//按照以下方式修改配置文件
{
    "RedisServers":        
    [ 
        #在此处添加需要监控的redis实例
        {
              "server": "127.0.0.1",                #redis监听地址,此处为本机
              "port" : 6379,                        #redis端口号,可以通过lsof -i | grep redis-ser查看 redis-server端口号
              "password" : "some-password"          #redis认证密码,如果没有可以删除该行,注意json格式
        }        
    ],

    "DataStoreType" : "redis",        #监控数据存储方案的配置,可选择redis或sqllite
    #用来存储监控数据的 Redis 实例
    "RedisStatsServer":    
    {
        "server" : "127.0.0.1",
        "port" : 6379,
        "password" : "some-password"
    },
    #监控数据持久化数据存储配置
    "SqliteStatsStore" :
    {
        "path":  "db/redislive.sqlite"    #redis数据文件
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  1. 启动
    启动监控服务,每60秒监控一次
./redis-monitor.py --duration=60
  • 1

再次开启一个终端,进入/root/RedisLive/src目录,启动web服务

./redis-live.py
  • 1

单线程的 Redis 为什么这么快

  • 纯内存操作
  • 单线程操作,避免了频繁的上下文切换
  • 采用了非阻塞 I/O 多路复用机制:只有单个线程,通过跟踪每个 I/O 流的状态,来管理多个 I/O 流

Redis 的过期策略和内存淘汰机制

Redis 采用的是定期删除+惰性删除策略。

  • 为什么不用定时删除
    定时删除,用一个定时器来负责监视 Key,过期则自动删除。虽然内存及时释放,但是十分消耗 CPU 资源。在大并发请求下,CPU 要将时间应用在处理请求,而不是删除 Key,因此没有采用这一策略。
  • 定期删除+惰性删除
    定期删除,Redis 默认每个 100ms 检查,有过期 Key 则删除。需要说明的是,Redis 不是每个 100ms 将所有的 Key 检查一次,而是随机抽取进行检查。如果只采用定期删除策略,会导致很多 Key 到时间没有删除。于是,惰性删除派上用场。

redis 提供 6种数据淘汰策略:

  • volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰
  • volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰
  • volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰
  • allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰
  • allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰
  • no-enviction(驱逐):禁止驱逐数据

redis的并发竞争问题如何解决

采用队列模式将并发访问变为串行访问。Redis本身没有锁的概念,Redis对于多个客户端连接并不存在竞争,但是在Jedis客户端对Redis进行并发访问时会发生连接超时、数据转换错误、阻塞、客户端关闭连接等问题,这些问题均是由于客户端连接混乱造成。对此有2种解决方法:
1:客户端角度,为保证每个客户端间正常有序与Redis进行通信,对连接进行池化,同时对客户端读写Redis操作采用内部锁synchronized。
2:服务器角度,利用setnx实现锁

redis事物的了解CAS(check-and-set 操作实现乐观锁 )

Redis作为NoSQL数据库也同样提供了事务机制。在Redis中,MULTI/EXEC/DISCARD/WATCH这四个命令是我们实现事务的基石。下面简要的列出Redis中事务的实现特征:
1). 在事务中的所有命令都将会被串行化的顺序执行,事务执行期间,Redis不会再为其它客户端的请求提供任何服务,从而保证了事物中的所有命令被原子的执行。
2). 和关系型数据库中的事务相比,在Redis事务中如果有某一条命令执行失败,其后的命令仍然会被继续执行。
3). 我们可以通过MULTI命令开启一个事务,有关系型数据库开发经验的人可以将其理解为"BEGIN TRANSACTION"语句。在该语句之后执行的命令都将被视为事务之内的操作,最后我们可以通过执行EXEC/DISCARD命令来提交/回滚该事务内的所有操作。这两个Redis命令可被视为等同于关系型数据库中的COMMIT/ROLLBACK语句。
4). 在事务开启之前,如果客户端与服务器之间出现通讯故障并导致网络断开,其后所有待执行的语句都将不会被服务器执行。然而如果网络中断事件是发生在客户端执行EXEC命令之后,那么该事务中的所有命令都会被服务器执行。
5). 当使用Append-Only模式时,Redis会通过调用系统函数write将该事务内的所有写操作在本次调用中全部写入磁盘。然而如果在写入的过程中出现系统崩溃,如电源故障导致的宕机,那么此时也许只有部分数据被写入到磁盘,而另外一部分数据却已经丢失。Redis服务器会在重新启动时执行一系列必要的一致性检测,一旦发现类似问题,就会立即退出并给出相应的错误提示。此时,我们就要充分利用Redis工具包中提供的redis-check-aof工具,该工具可以帮助我们定位到数据不一致的错误,并将已经写入的部分数据进行回滚。修复之后我们就可以再次重新启动Redis服务器了。

Redis异步队列

一般使用list结构作为队列,rpush生产消息,lpop消费消息。当lpop没有消息的时候,要适当sleep一会再重试。list还有个指令叫blpop,在没有消息的时候,它会阻塞住直到消息到来。使用pub/sub主题订阅者模式,可以实现1:N的消息队列(生产一次消费多次)。

  • pub/sub有什么缺点
    在消费者下线的情况下,生产的消息会丢失
  • redis实现延时队列
    使用sortedset,拿时间戳作为score,消息内容作为key调用zadd来生产消息,消费者用zrangebyscore指令获取N秒之前的数据轮询进行处理。

Redis的同步机制

Redis可以使用主从同步,从从同步。第一次同步时,主节点做一次bgsave,并同时将后续修改操作记录到内存buffer,待完成后将rdb文件全量同步到复制节点,复制节点接受完成后将rdb镜像加载到内存。加载完成后,再通知主节点将期间修改的操作记录同步到复制节点进行重放就完成了同步过程。

Redis集群的原理

Redis Sentinal着眼于高可用,在master宕机时会自动将slave提升为master,继续提供服务。
Redis Cluster着眼于扩展性,在单个redis内存不足时,使用Cluster进行分片存储。

基于Redis实现分布式锁

setnx来争抢锁,抢到之后,再用expire给锁加一个过期时间防止锁忘记了释放。
setnx和expire合成一条指令来用

  • 基于redis或zookeeper的实现可认为是悲观锁,乐观锁和悲观锁最根本的区别在于线程之间是否相互阻塞。
  • 从2.6.12版本开始,redis为SET命令增加了一系列选项(set [key] NX/XX EX/PX [expiration]):
EX seconds – 设置键key的过期时间,单位时秒
PX milliseconds – 设置键key的过期时间,单位时毫秒
NX – 只有键key不存在的时候才会设置key的值
XX – 只有键key存在的时候才会设置key的值
  • 1
  • 2
  • 3
  • 4

在旧版本的redis中(指2.6.12版本之前),使用redis实现分布式锁一般需要setNX、expire、getSet、del等命令。而且会发现这种实现有很多逻辑判断的原子操作以及本地时间等并没有控制好。而在旧版本的redis中,redis的超时时间很难控制,用户迫切需要把setNX和expiration结合为一体的命令,把他们作为一个原子操作

问题及解决方法

  1. 死锁问题
    为了防止死锁,redis至少需要设置一个超时时间;但是,当锁自动释放了,但是程序并没有执行完毕,这时候其他线程又获取到锁执行同样的程序,可能会造成并发问题。
  2. 锁释放问题
    每个获取redis锁的线程应该释放自己获取到的锁,而不是其他线程的,所以我们需要在每个线程获取锁的时候给锁做上不同的标记以示区分;线程在释放锁的时候需要判断当前锁是否属于自己,如果属于自己才释放,这里涉及到逻辑判断语句,至少是两个操作在进行,那么我们需要考虑这两个操作要在一个原子内执行,否则在两个行为之间可能会有其他线程插入执行,导致程序紊乱。
  3. 更可靠的锁
    单实例的redis(这里指只有一个master节点)往往是不可靠的,虽然实现起来相对简单一些,但是会面临着宕机等不可用的场景,即使在主从复制的时候也显得并不可靠(因为redis的主从复制往往是异步的)。

集群Redis 分布式锁

Java环境有 Redisson 可用于生产环境集群Redis 分布式锁。在Redis的分布式环境中,Redis 的作者提供了RedLock 的算法来实现一个分布式锁。RedLock算法加锁步骤如下

  • 加锁

获取当前Unix时间,以毫秒为单位。
依次尝试从N个实例,使用相同的key和随机值获取锁。在步骤2,当向Redis设置锁时,客户端应该设置一个网络连接和响应超时时间,这个超时时间应该小于锁的失效时间。例如你的锁自动失效时间为10秒,则超时时间应该在5-50毫秒之间。这样可以避免服务器端Redis已经挂掉的情况下,客户端还在死死地等待响应结果。如果服务器端没有在规定时间内响应,客户端应该尽快尝试另外一个Redis实例。
客户端使用当前时间减去开始获取锁时间(步骤1记录的时间)就得到获取锁使用的时间。当且仅当从大多数(这里是3个节点)的Redis节点都取到锁,并且使用的时间小于锁失效时间时,锁才算获取成功。
如果取到了锁,key的真正有效时间等于有效时间减去获取锁所使用的时间(步骤3计算的结果)。
如果因为某些原因,获取锁失败(没有在至少N/2+1个Redis实例取到锁或者取锁时间已经超过了有效时间),客户端应该在所有的Redis实例上进行解锁(即便某些Redis实例根本就没有加锁成功)。
解锁

向所有的Redis实例发送释放锁命令即可,不用关心之前有没有从Redis实例成功获取到锁.

Redis布隆过滤器

Redis的布隆过滤器不是原生自带的,而是要通过module加载进去。Redis在4.0的版本中加入了module功能

Redis的布隆过滤器主要有两个命令:

bf.add 添加元素到布隆过滤器中:bf.add strs xy
bf.exists 判断某个元素是否在过滤器中:bf.exists strs xy
Redis中有一个命令可以来设置布隆过滤器的准确率:

bf.reserve strs 0.01 100

三个参数的含义:
第一个值是过滤器的名字。
第二个值为error_rate的值:允许布隆过滤器的错误率。
第三个值为initial_size的值:初始化位数组的大小。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

布隆过滤器与BitMap类似,底层也是一个位数组。1表示有,0表示无。但布隆过滤器比BitMap需要更少的内存,因为使用多个hash算法。

  • 误差
    从原理可以看得出来,布隆过滤器是有可能存在一定的误差的。尤其是当hash函数比较少的时候。布隆过滤器是根据多次hash计算下标后,数组的这些下标是否都为1来判断这个元素是否存在的。所以是存在一定的几率,要检查的元素实际上没有插入,但被其它元素插入影响,导致所有下标都为1。
    所以布隆过滤器不能删除,因为一旦删除(即将相应的位置为0),就很大可能会影响其他元素。
    如果使用布隆过滤器判断一个函数是否存在于一个集合,如果它返回true,则代表可能存在。如果它返回false,则代表一定不存在。

如果空间越大,hash函数越多,结果就越精确,但空间效率和查询效率就会越低。

  • 布谷鸟过滤器
    RedisBloom模块还实现了布谷鸟过滤器,它算是对布隆过滤器的增强版。解决了布隆过滤器的一些比较明显的缺点,比如:不能删除元素,不能计数等。除此之外,布谷鸟过滤器不用使用多个hash函数,所以查询性能更高。除此之外,在相同的误判率下,布谷鸟过滤器的空间利用率要明显高于布隆,空间上大概能节省40%多。
本文内容由网友自发贡献,转载请注明出处:https://www.wpsshop.cn/w/Gausst松鼠会/article/detail/353114
推荐阅读
相关标签
  

闽ICP备14008679号