文章目录

  • 一 特性
  • 二 基本概念
  • redis的发布/订阅模式
  • redis的高性能部署模式
  • Master-Slave模式
  • Sentinel模式
  • cluster模式
  • redis的内存模型
  • redis的过期淘汰策略
  • redis的数据持久化策略
  • redis单线程模式
  • 三 分布式锁



一 特性

redis的特性:

  • Redis支持数据的持久化,可以将内存中的数据保存在磁盘中,重启的时候可以再次加载进行使用。
  • Redis不仅仅支持简单的key-value类型的数据,同时还提供list,set,zset,hash等数据结构的存储。
  • Redis支持数据的备份,即master-slave模式的数据备份。
二 基本概念

redis支持五种基本的数据类型:
string(字符串),hash(哈希),list(列表),set(集合),zset(有序集合)
同时还有以下不太常用的高级数据类型:HyperLogLogs(估计集合基数),Bitmaps(),GEO(地理位置存储)

redis的发布/订阅模式

redis 的tag redis的tag机制_分布式锁


发布者和订阅者都是Redis客户端,Channel则为Redis服务器端,发布者将消息发送到某个频道,订阅了这个频道的订阅者就能够接收到这个消息,Redis的这种发布订阅机制与基于topic的发布订阅类似,Channel相当于主题redis使用publish命令发送消息,其返回值为接收到的消息的订阅者的数量

redis使用subscribe命令订阅某个频道,其返回值包括客户端订阅的频道,目前已经订阅的频道的数量,以及接收到的消息,其中subscribe表示已经成功订阅了某个频道。

redis 的tag redis的tag机制_redis_02


redis的发布订阅在redis中的应用:

Redis的发布订阅功能与redis中的数据存储是无关的,它不会影响Redis的key space,即不会影响Redis中存储的数据,但通过发布订阅机制,Redis还提供了另一个功能,即Keyspace Notification,允许客户端通过订阅特定的频道,从而得知是否有改变Redis中的数据的事件。例如,有一个客户端删除了Redis中键为mykey的数据,该操作会触发两条消息,mykey del和del mykey,前者属于频道keysapce,表示keyspace发生的变化,后者属于频道keyevent,表示执行的操作。 【引注】

redis的发布订阅与activeMQ的比较

  • (1)ActiveMQ支持多种消息协议,包括AMQP,MQTT,Stomp等,并且支持JMS规范,但Redis没有提供对这些协议的支持;
  • (2)ActiveMQ提供持久化功能,但Redis无法对消息持久化存储,一旦消息被发送,如果没有订阅者接收,那么消息就会丢失;
  • (3)ActiveMQ提供了消息传输保障,当客户端连接超时或事务回滚等情况发生时,消息会被重新发送给客户端,Redis没有提供消息传输保障。

总之,ActiveMQ所提供的功能远比Redis发布订阅要复杂,毕竟Redis不是专门做发布订阅的,但是如果系统中已经有了Redis,并且需要基本的发布订阅功能,就没有必要再安装ActiveMQ了,因为可能ActiveMQ提供的功能大部分都用不到,而Redis的发布订阅机制就能满足需求。

redis的高性能部署模式
Master-Slave模式

主从模式的部署策略:

主从模式的特性:

  • 1,一个Master可以有多个Slave
  • 2,默认情况下,Master节点可以进行读或者写,slave节点只能进行读操作,写操作被禁止
  • 3,slave节点挂了不影响其他slave节点的读和master节点的读和写,重新启动后会将数据从master节点同步过来
  • 4,master节点挂了以后,不影响slave节点的读,Redis将不再提供写服务,master节点启动后Redis将重新对外提供写服务。
  • 5,master节点挂了以后,不会slave节点重新选一个master

主从模式的作用:

  • 1,数据备份(当一个节点损坏时,可以从备份数据进行恢复)
  • 2,负载均衡(读的操作可以负载到多个slave节点上)

redis的主从复制的原理

  • 1,从数据库向主数据库发送sync命令
  • 2,主数据库接收sync命令后,执行BGSAVE命令(保存快照),创建一个RDB文件,在创建RDB文件期间的命令将保存在缓冲区中。
  • 3,当主数据库执行完BGSAVE时,会向从数据库发送RDB文件,而从数据库会接收并载入该文件。
  • 4,主数据库将缓冲区的所有写命令发给从服务器执行。
  • 5,以上处理完之后,之后主数据库每执行一个写命令,都会将被执行的写命令发送给从数据库。
Sentinel模式

Redis-Sentinel是Redis官方推荐的高可用性(HA)解决方案
sentinel的中文含义是哨兵、守卫。也就是说既然主从模式中,当master节点挂了以后,slave节点不能主动选举一个master节点出来,那么我就安排一个或多个sentinel来做这件事,当sentinel发现master节点挂了以后,sentinel就会从slave中重新选举一个master。

  • 1,sentinel模式是建立在主从模式的基础上,如果只有一个redis节点,sentinel就没有意义
  • 2,当master节点挂了以后,sentinel会在slave中选择一个做为master,并修改它们的配置文件,其他slave的配置文件也会被修改,比如slaveof属性会指向新的master
  • 3,当master节点重新启动后,它将不再是master而是作为slave接收新的master节点的同步数据
  • 4,sentinel因为也是一个进程,有挂掉的可能,所以sentinel也会启动多个形成一个sentinel集群
  • 5,当主从模式配置密码时,sentinel也会同步将配置信息修改到配置文件中,不许要担心。
  • 6,一个sentinel或sentinel集群可以管理多个主从Redis(多个单机主从)

当使用Sentinel模式的时候,客户端就不要直接连接Redis,而是连接sentinel的ip和port,由sentinel来提供具体的可提供服务的Redis实现,这样当master节点挂掉以后,sentinel就会感知并将新的master节点提供给使用者。

cluster模式

cluster的出现是为了解决单机Redis容量有限的问题,将Redis的数据根据一定的规则分配到多台机器

  • 1,cluster可以说是sentinel和主从模式的结合体,通过cluster可以实现主从和master重选功能,所以如果配置两个副本三个分片的话,就需要六个Redis实例。
  • 2,因为Redis的数据是根据一定规则(slots)分配到cluster的不同机器的,当数据量过大时,可以新增机器进行扩容
redis的内存模型

使用redis-cli连接上redis-server之后,输入info memory命令打印对应的内存使用情况

redis 的tag redis的tag机制_redis_03


used_memory:Redis分配器分配的内存总量(单位是字节),包括使用的虚拟内存(swap), user_memory_human是针对这个显示的更友好

used_memory_rss :Redis进程占据操作系统的内存(单位是字节),与top及ps命令看到的值是一致的;除了分配器分配的内存之外,used_memory_rss还包括进程运行本身需要的内存、内存碎片等,但是不包括虚拟内存。

因此,used_memory和used_memory_rss,前者是从Redis角度得到的量,后者是从操作系统角度得到的量。二者之所以有所不同,一方面是因为内存碎片和Redis进程运行需要占用内存,使得前者可能比后者小,另一方面虚拟内存的存在,使得前者可能比后者大。

mem_fragmentation_ratio:内存碎片比率,该值是used_memory_rss / used_memory的比值。

mem_allocator:Redis使用的内存分配器,在编译时指定;可以是 libc 、jemalloc或者tcmalloc,默认是jemalloc;截图中使用的便是默认的libc。redis的内部的数据结构:

redis 的tag redis的tag机制_redis 的tag_04


详解,以set hello world

(1)dictEntry:Redis是Key-Value数据库,因此对每个键值对都会有一个dictEntry,里面存储了指向Key和Value的指针;next指向下一个dictEntry,与本Key-Value无关。

(2)Key:图中右上角可见,Key(”hello”)并不是直接以字符串存储,而是存储在SDS结构中。

(3)redisObject:Value(“world”)既不是直接以字符串存储,也不是像Key一样直接存储在SDS中,而是存储在redisObject中。实际上,不论Value是5种类型的哪一种,都是通过redisObject来存储的;而redisObject中的type字段指明了Value对象的类型,ptr字段则指向对象所在的地址。不过可以看出,字符串对象虽然经过了redisObject的包装,但仍然需要通过SDS存储。在redisObject还有其他未显示出来的字段

(4)jemalloc:无论是DictEntry对象,还是redisObject、SDS对象,都需要内存分配器(如jemalloc)分配内存进行存储。以DictEntry对象为例,有3个指针组成,在64位机器下占24个字节,jemalloc会为它分配32字节大小的内存单元。

redis的过期淘汰策略

redis的过期key的淘汰时机

  • 1,访问key的时候进行判断
  • 2,cpu空闲的时候

redis的过期key的清理算法:

  • 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的两种持久化的策略:

  • RDB:save操作会在redis的主线程中执行,因此会阻塞其他请求操作,避免使用bgsave:调用fork,产生子进程,父进程继续进行处理
  • AOF: 配置文件中的appendonly修改为yes,开启AOF持久化后,你所执行的每一条指令,都会被记录到appendonly.aof文件中,但事实上,并不会立即将命令写入到硬盘文件中,而是写入到硬盘缓存,在接下来的策略中,配置多久来从硬盘缓存写入到硬盘文件。所以在一定程度一定条件下,还是会有数据丢失,不过你可以大大减少数据损失。

两者之间的区别:

  • RDB每次进行快照方式会重新记录整个数据集的所有信息。RDB在恢复数据时更快,可以最大化redis性能,子进程对父进程无任何性能影响,redis可以通过创建快照来获取存储在内存里面的数据在某个时间点上的副本,redis创建快照之后,可以对快照进行备份,可以将快照复制到其他服务器从而创建具有相同数据的服务器副本
  • AOF有序的记录了redis的命令操作。意外情况下数据丢失甚少。他不断地对aof文件添加操作日志记录,你可能会说,这样的文件得多么庞大呀。是的,的确会变得庞大,但redis会有优化的策略,比如你对一个key1键的操作,set key1 001 , set key1 002, set key1 003。那优化的结果就是将前两条去掉咯,那具体优化的配置在配置文件中对应的是
redis单线程模式

redis单线程和单进程实现高性能的原因:

  • 1,纯内存,redis将所有的数据放在内存,内存的响应时间为100纳秒,这是Redis达到每秒万级别访问的重要基础;
  • 2,非阻塞的io,redis使用epoll作为io多路复用技术的实现,在加上Redis自身的事件处理模型将epoll中的链接、读写、关闭都转换为事件,不在网络I/O上浪费过多的时间;
  • 3,单线程避免了线程切换和竞态产生的消耗
三 分布式锁

redis的一个使用场景就是可以作为分布式锁,通过redis的set原子操作进行加锁和使用redis的eval的原子语句进行解锁操作
加锁:

private static final String LOCK_SUCCESS = "OK";
 private static final String SET_IF_NOT_EXIST = "NX";
 private static final String SET_WITH_EXPIRE_TIME = "PX";
 /**
     * 尝试获取分布式锁
     * @param jedis Redis客户端
     * @param lockKey 锁
     * @param requestId 请求标识
     * @param expireTime 超期时间
     * @return 是否获取成功
     */
    public static boolean tryGetDistributedLock(Jedis jedis, String lockKey, String requestId, int expireTime) {

        String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);

        if (LOCK_SUCCESS.equals(result)) {
            return true;
        }
        return false;

    }

加锁的代码主要是通过**jedis.set(String key, String value, String nxxx, String expx, int time)**这个set方法一共有五个形参

  • 第一个key,使用key在当锁,因为key是唯一的
  • 第二个为value,传入的是requestId,通过给value赋值为requestId,我们就可以确定这个锁是哪个请求加上的,在解锁的时候就有依据,
  • 第三个为nxxx,这个参数我们填的是NX,意思是SET IF NOT EXIST,即当key不存在时,我们进行set操作;若key已经存在,则不做任何操作;
  • 第四个为expx,这个参数我们传的是PX,意思是我们要给这个key加一个过期的设置,具体时间由第五个参数决定。
  • 第五个为time,与第四个参数相呼应,代表key的过期时间。

总的来说,执行上面的set()方法就只会导致两种结果:

  1. 当前没有锁(key不存在),那么就进行加锁操作,并对锁设置个有效期,同时value表示加锁的客户端。
  2. 已有锁存在,不做任何操作。

解锁:

private static final Long RELEASE_SUCCESS = 1L;

    /**
     * 释放分布式锁
     * @param jedis Redis客户端
     * @param lockKey 锁
     * @param requestId 请求标识
     * @return 是否释放成功
     */
    public static boolean releaseDistributedLock(Jedis jedis, String lockKey, String requestId) {

        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
        Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));

        if (RELEASE_SUCCESS.equals(result)) {
            return true;
        }
        return false;

    }

解锁的过程很简单,redis使用eval命令去执行一段lua代码,这段lua代码的主要的功能是:首先是获取锁对应的value值,检测是否与requestId相等,如果相等则删除锁(解锁)

对于分布式锁,同样还有一个对应的分布式锁需要进行关注,那就是使用zookeeper实现的分布式锁
zookeeper分布式锁的原理:
基于临时顺序节点(创建临时有序节点)

  • 1.客户端调用create()方法创建名为“locknode/guid-lock-”的节点,需要注意的是,这里节点的创建类型需要设置为EPHEMERAL_SEQUENTIAL。
  • 2.客户端调用getChildren(“locknode”)方法来获取所有已经创建的子节点。
  • 3.客户端获取到所有子节点path之后,如果发现自己在步骤1中创建的节点是所有节点中序号最小的,那么就认为这个客户端获得了锁。
  • 4.如果创建的节点不是所有节点中序号最小的,那么则监视比自己创建节点的序列号小的最大的节点,进入等待。直到下次监视的子节点变更的时候,再进行子节点的获取,判断是否获取锁。
    释放锁的过程相对比较简单,就是删除自己创建的那个子节点即可。

两个分布式锁的区别:
1,如何解决死锁问题

  • Zookeeper使用会话有效期方式解决死锁现象。
  • Redis 是对key设置有效期解决死锁现象

2,性能:因为redis是NOSql数据库,性能上是比zookeeper上好的
3,可靠性:因为redis的有效期不是很好的控制,可能会产生有效期延迟,zookeeper就是不一样,因为zookeeper临时节点先天性可控的有效期,所以相对来说Zookeeper比Redis更好
4,redis分布式锁需要不断的尝试获取锁,比较消耗性能,zookeeper分布式锁获取不到之后,只需要监听节点的删除事件,不需要不断的获取锁,性能开销少