精华内容
下载资源
问答
  • Redis架构

    2018-08-12 18:51:16
    Redis架构 Redis是一个单线程的架构 单线程 和 多线程: 单线程效率低,安全 多线程效率高,有线程安全问题  简化了数据结构和算法的实现:  Redis采用了事件模型的机制 I/O 多路复用机制 (Linux处理文件...

    Redis架构

    Redis是一个单线程的架构

    单线程 和 多线程:

    单线程效率低,安全

    多线程效率高,有线程安全问题

      简化了数据结构和算法的实现:

      Redis采用了事件模型的机制  I/O 多路复用机制 (Linux处理文件读取的机制)

     

      单线程异步回调 :node.js

      

      Redis是一个单线程,为什么效率还这么高?

    1. redis是基于内存的,他的读取速度本身就很快
    2. 使用单线程,避免了cpu对线程的切换,在一点程度上提高了效率
    3. redis处理问题的事件模型机制
    展开全文
  • redis架构

    千次阅读 2017-03-22 11:55:19
    最近参加阿里实习被问redis架构,瞬间懵逼了!!所特意去学习了下Redis免得下次又被问懵逼!!!!!!!!!!!  Redis是一个开源的先进的键值存储系统(Redis是一种高级key-value数据库。它跟memcached类似,...

             最近参加阿里实习被问redis架构,瞬间懵逼了!!所特意去学习了下Redis免得下次又被问懵逼!!!!!!!!!!!

             Redis是一个开源的先进的键值存储系统(Redis是一种高级key-value数据库。它跟memcached类似,不过数据可以持久化,而且支持的数据类型很丰富。。它通常可以用作数据结构服务器,因为它的键可以包含字符串(String)、哈希值(hash)、列表(list)、集合(set)和有序集合(sorted set)。而且Redis使用内存数据集合,你可以根据自身需求定时的将数据持久化在磁盘中,或将日志持久化在日志中。下面从redis的网络模型、数据结构和内存管理、持久化、多机和集群四个方面来理解redis架构:

    一,网络模型:

    Redis是典型的单进程单线程的事件驱动模型。(异步非阻塞网络I/O?)

    Redis流程上整体分为接受请求处理器、响应处理器和应答处理器三个同步模块,每个请求都有经历这三个模块

    Redis集成了libevent/epoll/Kqueue/select等多种事件处理机制。不了解网络编程I/O模型的可以参考:http://www.cnblogs.com/wujing-hubei/p/6111347.html

    二、数据结构与内存管理

    1.字符串

    1.1 结构

    Redis的字符串是对C语言原始字符串的二次封装,结构如下:

    struct sdshdr {
        long len;
        long free;
        char buf[];
    };
    

    可以看出,每当定义一个字符串时,除了保存字符的空间,Redis还分配了额外的空间用于管理属性字段。

    1.2 内存管理方式

    动态内存管理方式,动态方式最大的好处就是能够较为充分的利用内存空间,减少内存碎片化,与此同时带来的劣势就是容易引起频繁的内存抖动,通常采用“空间预分配”和“惰性空间释放两种优化策略来减少内存抖动,redis也不例外。

    每次修改字符串内容时,首先检查内存空间是否符合要求,否则就扩大2倍或者按M增长;减少字符串内容时,内存并不会立刻回收,而是按需回收。

    关于内存管理的优化,最基本的出发点就是浪费一点空间还是牺牲一些时间的权衡,像STL、tcmalloc、protobuf3的arena机制等采用的核心思路都是“预分配迟回收”,Redis也是一样的。

    1.3 二进制安全

    判断字符串结束与否的标识是len字段,而不是C语言的'\0',因此是二进制安全的。
    放心的将pb序列化后的二进制字符串存入redis。
    简而言之,通过redis的简单封装,redis的字符串的操作更加方便,性能更友好,并且屏蔽了C语言字符串的一些需要用户关心的问题。

    2.字典(哈希)

    字典的底层一定是hash,涉及到hash一定会涉及到hash算法、冲突的解决方法和hash表扩容和缩容。

    2.1 hash算法

    Redis使用的就是常用的Murmurhash2,Murmurhash算法能够给出在任意输入序列下的散列分布性,并且计算速度很快。之前做共享内存的Local-Cache的需求时也正是利用了Murmurhash的优势,解决了原有结构的hash函数散列分布性差的问题。

    2.2 hash冲突解决方法

    链地址法解决hash冲突,通用解决方案没什么特殊的。多说一句,如果选用链地址解决冲突,那么势必要有一个散列性非常好的hash函数,否则hash的性能将会大大折扣。Redis选用了Murmurhash,所以可以放心大胆的采用链地址方案。

    2.3 hash扩容和缩容

    维持hash表在一个合理的负载范围之内,简称为rehash过程。
    rehash的过程也是一个权衡的过程,在做评估之前首先明确一点,不管中间采用什么样的rehash策略,rehash在宏观上看一定是:分配一个新的内存块,老数据搬到新的内存块上,释放旧内存块。
    老数据何时搬?怎么搬?就变成了一个需要权衡的问题。
    第一部分的网络模型上明确的指出Redis的事件驱动模型特点,不适合玩长耗时操作。如果一个hashtable非常大,需要进行扩容就一次性把老数据copy过去,那就会非常耗时,违背事件驱动的特点。所以Redis依旧采用了一种惰性的方案
    新空间分配完毕后,启动rehashidx标识符表明rehash过程的开始;之后所有增删改查涉及的操作时都会将数据迁移到新空间,直到老空间数据大小为0表明数据已经全部在新空间,将rehashidx禁用,表明rehash结束。
    将一次性的集中问题分而治之,在Redis的设计哲学中体现的淋漓尽致,主要是为了避免大耗时操作,影响Redis响应客户请求。

    3.整数集合

    变长整数存储,整数分为16/32/64三个变长尺度,根据存入的数据所属的类型,进行规划。
    每次插入新元素都有可能导致尺度升级(例如由16位涨到32位),因此插入整数的时间复杂度为O(n)。这里也是一个权衡,内存空间和时间的一个折中,尽可能节省内存。

    4.跳跃表

    Redis的skilplist和普通的skiplist没什么不同,都是冗余数据实现的从粗到细的多层次链表,Redis中应用跳表的地方不多,常见的就是有序集合。
    Redis的跳表和普通skiplist没有什么特殊之处。

    5.链表

    Redis的链表是双向非循环链表,拥有表头和表尾指针,对于首尾的操作时间复杂度是O(1),查找时间复杂度O(n),插入时间复杂度O(1)。
    Redis的链表和普通链表没有什么特殊之处。

    三、持久化机制:AOF和RDB持久化

    AOF持久化日志,RDB持久化实体数据,AOF优先级大于RDB。但是默认的 是RDB.

    1.AOF持久化

    机制:通过定时事件将aof缓冲区内的数据定时写到磁盘上。

    基于语句追加方式(aof):

    aof方式实际类似mysql的基于语句的binlog方式,即每条会使Redis内存数据发生改变的命令都会追加到一个log文件中,也就是说这个log文件就是Redis的持久化数据。

    Append-only:filesnapshotting方法在redis异常死掉时, 最近的数据会丢失(丢失数据的多少视你save策略的配置),所以这是它最大的缺点,当业务量很大时,丢失的数据是很多的。Append-only方法可 以做到全部数据不丢失,但redis的性能就要差些。AOF就可以做到全程持久化,只需要在配置文件中开启(默认是no),appendonly yes开启AOF之后,redis每执行一个修改数据的命令,都会把它添加到aof文件中,当redis重启时,将会读取AOF文件进行“重放”以恢复到 redis关闭前的最后时刻。

    2.RDB持久化

    默认redis是会以快照的形式将数据持久化到磁盘的(一个二进 制文件,dump.rdb,这个文件名字可以指定),在配置文件中的格式是:save N M表示在N秒之内,redis至少发生M次修改则redis抓快照到磁盘。当然我们也可以手动执行save或者bgsave(异步)做快照。

    工作原理简单介绍一下:当redis需要做持久化时,redis会fork一个子进程;子进程将数据写到磁盘上一个临时RDB文件中;当子进程完成写临时文件后,将原来的RDB替换掉,这样的好处就是可以copy-on-write

    四.多机和集群

    1.主从服务器

    避免单点是所有服务的通用问题,Redis也不例外。解决单点就要有备机,有备机就要解决固有的数据同步问题。

    1.1 sync——原始版主从同步

    Redis最初的同步做法是sync指令,通过sync每次都会全量数据,显然每次都全量复制的设计比较消耗资源。改进思路也是常规逻辑,第一次全量,剩下的增量,这就是现在的psync指令的活。

    1.2 psync

    部分重同步实现的技术手段是“偏移序号+积压缓冲区”,具体做法如下:
    (1)主从分别维护一个seq,主每次完成一个请求便seq+1,从每同步完后更新自己seq;
    (2)从每次打算同步时都是携带着自己的seq到主,主将自身的seq与从做差结果与积压缓冲区大小比较,如果小于积压缓冲区大小,直接从积压缓冲区取相应的操作进行部分重同步;
    (3)否则说明积压缓冲区不能够cover掉主从不一致的数据,进行全量同步。
    本质做法用空间换时间,显然在这里牺牲部分空间换回高效的部分重同步,收益比很大。



    展开全文
  • Redis-cluster是近年来Redis架构不断改进中的相对较好的 Redis高可用方案。本文涉及到近年来Redis多实例架构的演变过程,包括普通主从架构(Master、slave 可进行写读分离)、哨兵模式下的主从架构、Redis-cluster高...
  • Redis架构和最佳实践,系统的说明了redis架构、开发最佳实践、生产问题解答和文档及自助工具
  • Redis架构和最佳实践shareRedis架构和最佳实践shareRedis架构和最佳实践share
  • Redis架构与Redis-cluster

    2018-09-13 16:18:00
    本文涉及到近年来 Redis 多实例架构的演变过程,包括普通主从架构(Master、slave 可进行写读分离)、哨兵模式下的主从架构Redis-cluster 高可用架构Redis 官方默认 cluster 下不进行读写分离)的简介。...
        

    导言

    Redis-cluster 是近年来 Redis 架构不断改进中的相对较好的 Redis 高可用方案。本文涉及到近年来 Redis 多实例架构的演变过程,包括普通主从架构(Master、slave 可进行写读分离)、哨兵模式下的主从架构、Redis-cluster 高可用架构(Redis 官方默认 cluster 下不进行读写分离)的简介。同时还介绍使用Java的两大redis客户端:Jedis与Lettuce用于读写redis-cluster的数据的一般方法。再通过官方文档以及互联网的相关技术文档,给出redis-cluster架构下的读写能力的优化方案,包括官方的推荐的扩展redis-cluster下的Master数量以及非官方默认的redis-cluster的读写分离方案,案例中使用Lettuce的特定方法进行redis-cluster架构下的数据读写分离。

    近年来redis多实例用架构的演变过程

    redis是基于内存的高性能key-value数据库,若要让redis的数据更稳定安全,需要引入多实例以及相关的高可用架构。而近年来redis的高可用架构亦不断改进,先后出现了本地持久化、主从备份、哨兵模式、redis-cluster群集高可用架构等等方案。

    1、redis普通主从模式

    通过持久化功能,Redis保证了即使在服务器重启的情况下也不会损失(或少量损失)数据,因为持久化会把内存中数据保存到硬盘上,重启会从硬盘上加载数据。 。但是由于数据是存储在一台服务器上的,如果这台服务器出现硬盘故障等问题,也会导致数据丢失。为了避免单点故障,通常的做法是将数据库复制多个副本以部署在不同的服务器上,这样即使有一台服务器出现故障,其他服务器依然可以继续提供服务。为此, Redis 提供了复制(replication)功能,可以实现当一台数据库中的数据更新后,自动将更新的数据同步到其他数据库上。

    在复制的概念中,数据库分为两类,一类是主数据库(master),另一类是从数据库(slave)。主数据库可以进行读写操作,当写操作导致数据变化时会自动将数据同步给从数据库。而从数据库一般是只读的,并接受主数据库同步过来的数据。一个主数据库可以拥有多个从数据库,而一个从数据库只能拥有一个主数据库。

    13465705-330a925d121b66fa.png!web

    主从模式的配置,一般只需要再作为slave的redis节点的conf文件上加入“slaveof masterip masterport”, 或者作为slave的redis节点启动时使用如下参考命令:

    redis-server --port 6380 --slaveof masterIp masterPort

    redis的普通主从模式,能较好地避免单独故障问题,以及提出了读写分离,降低了Master节点的压力。互联网上大多数的对redis读写分离的教程,都是基于这一模式或架构下进行的。但实际上这一架构并非是目前最好的redis高可用架构。

    2、redis哨兵模式高可用架构

    当主数据库遇到异常中断服务后,开发者可以通过手动的方式选择一个从数据库来升格为主数据库,以使得系统能够继续提供服务。然而整个过程相对麻烦且需要人工介入,难以实现自动化。 为此,Redis 2.8开始提供了哨兵工具来实现自动化的系统监控和故障恢复功能。 哨兵的作用就是监控redis主、从数据库是否正常运行,主出现故障自动将从数据库转换为主数据库。

    顾名思义,哨兵的作用就是监控Redis系统的运行状况。它的功能包括以下两个。

    (1)监控主数据库和从数据库是否正常运行。

    (2)主数据库出现故障时自动将从数据库转换为主数据库。

    13465705-f5efae89d9fe3d0a.png!web

    可以用info replication查看主从情况 例子: 1主2从 1哨兵,可以用命令起也可以用配置文件里 可以使用双哨兵,更安全,参考命令如下:

    redis-server --port 6379

    redis-server --port 6380 --slaveof 192.168.0.167 6379

    redis-server --port 6381 --slaveof 192.168.0.167 6379

    redis-sentinel sentinel.conf

    其中,哨兵配置文件sentinel.conf参考如下:

    sentinel monitor mymaster 192.168.0.167 6379 1

    其中mymaster表示要监控的主数据库的名字。配置哨兵监控一个系统时,只需要配置其监控主数据库即可,哨兵会自动发现所有复制该主数据库的从数据库。

    Master与slave的切换过程:

    (1)slave leader升级为master

    (2)其他slave修改为新master的slave

    (3)客户端修改连接

    (4)老的master如果重启成功,变为新master的slave

    3、redis-cluster群集高可用架构

    即使使用哨兵,redis每个实例也是全量存储,每个redis存储的内容都是完整的数据,浪费内存且有木桶效应。为了最大化利用内存,可以采用cluster群集,就是分布式存储。即每台redis存储不同的内容。

    采用redis-cluster架构正是满足这种分布式存储要求的集群的一种体现。redis-cluster架构中,被设计成共有16384个hash slot。每个master分得一部分slot,其算法为:hash_slot = crc16(key) mod 16384 ,这就找到对应slot。采用hash slot的算法,实际上是解决了redis-cluster架构下,有多个master节点的时候,数据如何分布到这些节点上去。key是可用key,如果有{}则取{}内的作为可用key,否则整个可以是可用key。群集至少需要3主3从,且每个实例使用不同的配置文件。

    13465705-8a2f841648ceb64d.png!web

    在redis-cluster架构中,redis-master节点一般用于接收读写,而redis-slave节点则一般只用于备份,其与对应的master拥有相同的slot集合,若某个redis-master意外失效,则再将其对应的slave进行升级为临时redis-master。

    在redis的官方文档中,对redis-cluster架构上,有这样的说明:在cluster架构下,默认的,一般redis-master用于接收读写,而redis-slave则用于备份,当有请求是在向slave发起时,会直接重定向到对应key所在的master来处理。但如果不介意读取的是redis-cluster中有可能过期的数据并且对写请求不感兴趣时,则亦可通过readonly命令,将slave设置成可读,然后通过slave获取相关的key,达到读写分离。具体可以参阅redis官方文档(https://redis.io/commands/readonly)等相关内容:

    Enables read queries for a connection to a Redis

    Cluster slave node.

    Normally slave nodes will redirect clients to the

    authoritative master for the hash slot involved

    in a given command, however clients can use slaves

    in order to scale reads using the READONLY command.

    READONLY tells a Redis Cluster slave node that

    the client is willing to read possibly stale

    data and is not interested in running write queries.

    When the connection is in readonly mode, the

    cluster will send a redirection to the client

    only if the operation involves keys not served

    by the slave's master node. This may happen because:

    The client sent a command about hash slots never served

    by the master of this slave.

    The cluster was reconfigured (for example resharded)

    and the slave is no longer able to serve commands for

    a given hash slot.

    例如,我们假设已经建立了一个三主三从的redis-cluster架构,其中A、B、C节点都是redis-master节点,A1、B1、C1节点都是对应的redis-slave节点。在我们只有master节点A,B,C的情况下,对应redis-cluster如果节点B失败,则群集无法继续,因为我们没有办法再在节点B的所具有的约三分之一的hash slot集合范围内提供相对应的slot。然而,如果我们为每个主服务器节点添加一个从服务器节点,以便最终集群由作为主服务器节点的A,B,C以及作为从服务器节点的A1,B1,C1组成,那么如果节点B发生故障,系统能够继续运行。节点B1复制B,并且B失效时,则redis-cluster将促使B的从节点B1作为新的主服务器节点并且将继续正确地操作。但请注意,如果节点B和B1在同一时间发生故障,则Redis群集无法继续运行。

    Redis群集配置参数:在继续之前,让我们介绍一下Redis Cluster在redis.conf文件中引入的配置参数。有些命令的意思是显而易见的,有些命令在你阅读下面的解释后才会更加清晰。

    (1)cluster-enabled :如果想在特定的Redis实例中启用Redis群集支持就设置为yes。 否则,实例通常作为独立实例启动。

    (2)cluster-config-file :请注意,尽管有此选项的名称,但这不是用户可编辑的配置文件,而是Redis群集节点每次发生更改时自动保留群集配置(基本上为状态)的文件。

    (3)cluster-node-timeout :Redis群集节点可以不可用的最长时间,而不会将其视为失败。 如果主节点超过指定的时间不可达,它将由其从属设备进行故障切换。

    (4)cluster-slave-validity-factor :如果设置为0,无论主设备和从设备之间的链路保持断开连接的时间长短,从设备都将尝试故障切换主设备。 如果该值为正值,则计算最大断开时间作为节点超时值乘以此选项提供的系数,如果该节点是从节点,则在主链路断开连接的时间超过指定的超时值时,它不会尝试启动故障切换。

    (5)cluster-migration-barrier :主设备将保持连接的最小从设备数量,以便另一个从设备迁移到不受任何从设备覆盖的主设备。有关更多信息,请参阅本教程中有关副本迁移的相应部分。

    (6)cluster-require-full-coverage :如果将其设置为yes,则默认情况下,如果key的空间的某个百分比未被任何节点覆盖,则集群停止接受写入。 如果该选项设置为no,则即使只处理关于keys子集的请求,群集仍将提供查询。

    以下是最小的Redis集群配置文件:

    port 7000

    cluster-enabled yes

    cluster-config-file nodes.conf

    cluster-node-timeout 5000

    appendonly yes

    注意:

    (1)redis-cluster最小配置为三主三从,当1个主故障,大家会给对应的从投票,把从立为主,若没有从数据库可以恢复则redis群集就down了。

    (2)在这个redis cluster中,如果你要在slave读取数据,那么需要带上readonly指令。redis cluster的核心的理念,主要是用slave做高可用的,每个master挂一两个slave,主要是做数据的热备,当master故障时的作为主备切换,实现高可用的。redis cluster默认是不支持slave节点读或者写的,跟我们手动基于replication搭建的主从架构不一样的。slave node要设置readonly,然后再get,这个时候才能在slave node进行读取。对于redis -cluster主从架构,若要进行读写分离,官方其实是不建议的,但也能做,只是会复杂一些。具体见下面的章节。

    (3)redis-cluster的架构下,实际上本身master就是可以任意扩展的,你如果要支撑更大的读吞吐量,或者写吞吐量,或者数据量,都可以直接对master进行横向扩展就可以了。也扩容master,跟之前扩容slave进行读写分离,效果是一样的或者说更好。

    (4)可以使用自带客户端连接:使用redis-cli -c -p cluster中任意一个端口,进行数据获取测试。

    Java中对redis-cluster数据的一般读取方法简介

    使用Jedis读写redis-cluster的数据

    由于Jedis类一般只能对一台redis-master进行数据操作,所以面对redis-cluster多台master与slave的群集,Jedis类就不能满足了。这个时候我们需要引用另外一个操作类:JedisCluster类。

    例如我们有6台机器组成的redis-cluster:

    172.20.52.85:7000、 172.20.52.85:7001、

    172.20.52.85:7002、172.20.52.85:7003、172.20.52

    .85:7004、172.20.52.85:7005

    其中master机器对应端口:7000、7004、7005

    slave对应端口:7001、7002、7003

    使用JedisCluster对redis-cluster进行数据操作的参考代码如下:

    // 添加nodes服务节点到Set集合

    Set<HostAndPort> hostAndPortsSet = new HashSet<HostAndPort>();

    // 添加节点

    hostAndPortsSet.add(new HostAndPort("172.20.52.85", 7000));

    hostAndPortsSet.add(new HostAndPort("172.20.52.85", 7001));

    hostAndPortsSet.add(new HostAndPort("172.20.52.85", 7002));

    hostAndPortsSet.add(new HostAndPort("172.20.52.85", 7003));

    hostAndPortsSet.add(new HostAndPort("172.20.52.85", 7004));

    hostAndPortsSet.add(new HostAndPort("172.20.52.85", 7005));

    // Jedis连接池配置

    JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();

    jedisPoolConfig.setMaxIdle(100);

    jedisPoolConfig.setMaxTotal(500);

    jedisPoolConfig.setMinIdle(0);

    jedisPoolConfig.setMaxWaitMillis(2000); // 设置2秒

    jedisPoolConfig.setTestOnBorrow(true);

    JedisCluster jedisCluster = new JedisCluster

    (hostAndPortsSet ,jedisPoolConfig);

    String result = jedisCluster.get("event:10");

    System.out.println(result);

    运行结果截图如下图所示:

    13465705-22f77613d868a6e8.png!web

    第一节中我们已经介绍了redis-cluster架构下master提供读写功能,而slave一般只作为对应master机器的数据备份不提供读写。如果我们只在hostAndPortsSet中只配置slave,而不配置master,实际上还是可以读到数据,但其内部操作实际是通过slave重定向到相关的master主机上,然后再将结果获取和输出。

    上面是普通项目使用JedisCluster的简单过程,若在spring boot项目中,可以定义JedisConfig类,使用@Configuration、@Value、@Bean等一些列注解完成JedisCluster的配置,然后再注入该JedisCluster到相关service逻辑中引用,这里介绍略。

    使用Lettuce读写redis-cluster数据

    Lettuce 和 Jedis 的定位都是Redis的client。Jedis在实现上是直接连接的redis server,如果在多线程环境下是非线程安全的,这个时候只有使用连接池,为每个Jedis实例增加物理连接,每个线程都去拿自己的 Jedis 实例,当连接数量增多时,物理连接成本就较高了。

    Lettuce的连接是基于Netty的,连接实例(StatefulRedisConnection)可以在多个线程间并发访问,应为StatefulRedisConnection是线程安全的,所以一个连接实例(StatefulRedisConnection)就可以满足多线程环境下的并发访问,当然这个也是可伸缩的设计,一个连接实例不够的情况也可以按需增加连接实例。

    其中spring boot 2.X版本中,依赖的spring-session-data-redis已经默认替换成Lettuce了。

    同样,例如我们有6台机器组成的redis-cluster:

    172.20.52.85:7000、 172.20.52.85:7001、172.20.52.

    85:7002、172.20.52.85:7003、172.20.52.85:7004、172.20.52.85:7005

    其中master机器对应端口:7000、7004、7005

    slave对应端口:7001、7002、7003

    在spring boot 2.X版本中使用Lettuce操作redis-cluster数据的方法参考如下:

    (1)pom文件参考如下:

    parent中指出spring boot的版本,要求2.X以上:

    <parent>

    <groupId>org.springframework.boot</groupId>

    <artifactId>spring-boot-starter-parent</artifactId>

    <version>2.0.4.RELEASE</version>

    <relativePath/> <!-- lookup parent from repository -->

    </parent>

    <!-- lookup parent from repository -->

    依赖中需要加入spring-boot-starter-data-redis,参考如下:

    <dependency>

    <groupId>org.springframework.boot</groupId>

    <artifactId>spring-boot-starter-data-redis</artifactId>

    </dependency>

    (2)springboot的配置文件要包含如下内容:

    spring.redis.database=0

    spring.redis.lettuce.pool.max-idle=10

    spring.redis.lettuce.pool.max-wait=500

    spring.redis.cluster.timeout=1000

    spring.redis.cluster.max-redirects=3

    spring.redis.cluster.nodes=172.20.52.85:7000,

    172.20.52.85:7001,172.20.52.85:7002,172.20.

    52.85:7003,172.20.52.85:7004,172.20.52.85:7005

    (3)新建RedisConfiguration类,参考代码如下:

    @Configuration

    public class RedisConfiguration {

    [@Resource](https://my.oschina.net/u/929718)

    private LettuceConnectionFactory myLettuceConnectionFactory;

    <a href='http://www.jobbole.com/members/q890462235'

    >@Bean</a>

    public RedisTemplate<String, Serializable>

    redisTemplate() {

    RedisTemplate<String, Serializable> template = new RedisTemplate<>();

    template.setKeySerializer(new StringRedisSerializer

    ());

    //template.setValueSerializer(new GenericJackson2JsonRedisSerializer());

    template.setValueSerializer(new StringRedisSerializer

    ());

    template.setConnectionFactory(myLettuceConnec

    tionFactory);

    return template;

    }

    }

    (4)新建RedisFactoryConfig类,参考代码如下:

    @Configuration

    public class RedisFactoryConfig {

    @Autowired

    private Environment environment;

    <a href='http://www.jobbole.com/members/q890462235'>

    @Bean</a>

    public RedisConnectionFactory myLettuceConnection

    Factory() {

    Map<String, Object> source = new HashMap<String, Object>();

    source.put("spring.redis.cluster.nodes", environment.getProperty("spring.redis.cluster.

    nodes"));

    source.put("spring.redis.cluster.timeout", environment.getProperty("spring.redis.cluster.timeout

    "));

    source.put("spring.redis.cluster.max-redirects", environment.getProperty("spring.redis.cluster.max-redirects"));

    RedisClusterConfiguration redisClusterConfiguration;

    redisClusterConfiguration = new RedisClusterConfiguration(new MapPropertySource("RedisClusterConfiguration",

    source));

    return new LettuceConnectionFactory(redisClusterConfiguration);

    }

    }

    (5)在业务类service中注入Lettuce相关的RedisTemplate,进行相关操作。以下是我化简到了springbootstarter中进行,参考代码如下:

    @SpringBootApplication

    public class NewRedisClientApplication {

    public static void main(String[] args) {

    ApplicationContext context = SpringApplication.run(NewRedisClientApplication.

    class, args);

    RedisTemplate redisTemplate = (RedisTemplate)context.getBean("redisTemplate");

    String rtnValue = (String)redisTemplate.opsForValue().get("event:10");

    System.out.println(rtnValue);

    }

    }

    运行结果的截图如下:

    13465705-d63a349c50435c65.png!web

    以上的介绍,是采用Jedis以及Lettuce对redis-cluster数据的简单读取。Jedis也好,Lettuce也好,其对于redis-cluster架构下的数据的读取,都是默认是按照redis官方对redis-cluster的设计,自动进行重定向到master节点中进行的,哪怕是我们在配置中列出了所有的master节点和slave节点。查阅了Jedis以及Lettuce的github上的源码,默认不支持redis-cluster下的读写分离,可以看出Jedis若要支持redis-cluster架构下的读写分离,需要自己改写和构建多一些包装类,定义好Master和slave节点的逻辑;而Lettuce的源码中,实际上预留了方法(setReadForm(ReadFrom.SLAVE))进行redis-cluster架构下的读写分离,相对来说修改会简单一些,具体可以参考后面的章节。

    redis-cluster架构下的读写能力的优化方案

    在上面的一些章节中,已经有讲到redis近年来的高可用架构的演变,以及在redis-cluster架构下,官方对redis-master、redis-slave的其实有使用上的建议,即redis-master节点一般用于接收读写,而redis-slave节点则一般只用于备份,其与对应的master拥有相同的slot集合,若某个redis-master意外失效,则再将其对应的slave进行升级为临时redis-master。但如果不介意读取的是redis-cluster中有可能过期的数据并且对写请求不感兴趣时,则亦可通过readonly命令,将slave设置成可读,然后通过slave获取相关的key,达到读写分离。

    具体可以参阅redis官方文档(https://redis.io/commands/readonly),以下是reids在线文档中,对slave的readonly说明内容:

    13465705-b8f29e6d4c20b4e9.png!web

    实际上本身master就是可以任意扩展的,所以如果要支撑更大的读吞吐量,或者写吞吐量,或者数据量,都可以直接对master进行横向水平扩展就可以了。也就是说,扩容master,跟之前扩容slave并进行读写分离,效果是一样的或者说更好。

    所以下面我们将按照redis-cluster架构下分别进行水平扩展Master,以及在redis-cluster架构下对master、slave进行读写分离两套方案进行讲解。

    (一)水平扩展Master实例来进行redis-cluster性能的提升

    redis官方在线文档以及一些互联网的参考资料都表明,在redis-cluster架构下,实际上不建议做物理的读写分离。那么如果我们真的不做读写分离的话,能否通过简单的方法进行redis-cluster下的性能的提升?我们可以通过master的水平扩展,来横向扩展读写吞吐量,并且能支撑更多的海量数据。

    对master进行水平扩展有两种方法,一种是单机上面进行master实例的增加(建议每新增一个master,也新增一个对应的slave),另一种是新增机器部署新的master实例(同样建议每新增一个master,也新增一个对应的slave)。当然,我们也可以进行这两种方法的有效结合。

    (1)单机上通过多线程建立新redis-master实例,即逻辑上的水平扩展:

    一般的,对于redis单机,单线程的读吞吐是4w/s~5W/s,写吞吐为2w/s。

    单机合理开启redis多线程情况下(一般线程数为CPU核数的倍数),总吞吐量会有所上升,但每个线程的平均处理能力会有所下降。例如一个2核CPU,开启2线程的时候,总读吞吐能上升是6W/s~7W/s,即每个线程平均约3W/s再多一些。但过多的redis线程反而会限制了总吞吐量。

    (2)扩展更多的机器,部署新redis-master实例,即物理上的水平扩展:

    例如,我们可以再原来只有3台master的基础上,连入新机器继续新实例的部署,最终水平扩展为6台master(建议每新增一个master,也新增一个对应的slave)。例如之前每台master的处理能力假设是读吞吐5W/s,写吞吐2W/s,扩展前一共的处理能力是:15W/s读,6W/s写。如果我们水平扩展到6台master,读吞吐可以达到总量30W/s,写可以达到12w/s,性能能够成倍增加。

    (3)若原本每台部署redis-master实例的机器都性能良好,则可以通过上述两者的结合,进行一个更优的组合。

    使用该方案进行redis-cluster性能的提升的优点有:

    (1)符合redis官方要求和数据的准确性。

    (2)真正达到更大吞吐量的性能扩展。

    (3)无需代码的大量更改,只需在配置文件中重新配置新的节点信息。

    当然缺点也是有的:

    (1)需要新增机器,提升性能,即成本会增加。

    (2)若不新增机器,则需要原来的实例所运行的机器性能较好,能进行以多线程的方式部署新实例。但随着线程的增多,而机器的能力不足以支撑的时候,实际上总体能力会提升不太明显。

    (3)redis-cluster进行新的水平扩容后,需要对master进行新的hash slot重新分配,这相当于需要重新加载所有的key,并按算法平均分配到各个Master的slot当中。

    (二)引入Lettuce以及修改相关方法,达到对redis-cluster的读写分离

    通过上面的一些章节,我们已经可以了解到Lettuce客户端读取redis的一些操作,使用Lettuce能体现出了简单,安全,高效。实际上,查阅了Lettuce对redis的读写,许多地方都进行了redis的读写分离。但这些都是基于上述redis架构中最普通的主从分离架构下的读写分离,而对于redis-cluster架构下,Lettuce可能是遵循了redis官方的意见,在该架构下,Lettuce在源码中直接设置了只由master上进行读写(具体参见gitHub的Lettuce项目):

    13465705-b244fc9ff0296230.png!web

    那么如果真的需要让Lettuce改为能够读取redis-cluster的slave,进行读写分离,是否可行?实际上还是可以的。这就需要我们自己在项目中进行二次加工,即不使用spring-boot中的默认Lettuce初始化方法,而是自己去写一个属于自己的Lettuce的新RedisClusterClient的连接,并且对该RedisClusterClient的连接进行一个比较重要的设置,那就是由connection.setReadFrom(ReadFrom.MASTER)改为connection.setReadFrom(ReadFrom.SLAVE)。

    下面我们开始对之前章节中的Lettuce读取redis-cluster数据的例子,进行改写,让Lettuce能够支持该架构下的读写分离:

    spring boot 2.X版本中,依赖的spring-session-data-redis已经默认替换成Lettuce了。

    同样,例如我们有6台机器组成的redis-cluster:

    172.20.52.85:7000、 172.20.52.85:7001、172.20.52.85:7002、172.20.52.85:7003、172.20.52.85:7004、172.20.52.85:7005

    其中master机器对应端口:7000、7004、7005

    slave对应端口:7001、7002、7003

    在spring boot 2.X版本中使用Lettuce操作redis-cluster数据的方法参考如下:

    (1)pom文件参考如下:

    parent中指出spring boot的版本,要求2.X以上:

    <parent>

    <groupId>org.springframework.boot</groupId>

    <artifactId>spring-boot-starter-parent</artifactId>

    <version>2.0.4.RELEASE</version>

    <relativePath/> <!-- lookup parent from repository -->

    </parent>

    <!-- lookup parent from repository -->

    依赖中需要加入spring-boot-starter-data-redis,参考如下:

    spring.redis.database=0

    spring.redis.lettuce.pool.max-idle=10

    spring.redis.lettuce.pool.max-wait=500

    spring.redis.cluster.timeout=1000

    spring.redis.cluster.max-redirects=3

    spring.redis.cluster.nodes=172.20.52.85:7000,172.

    20.52.85:7001,172.20.52.85:7002,172.20.52.85:

    7003,172.20.52.85:7004,172.20.52.85:7005

    (3)我们回到RedisConfiguration类中,删除或屏蔽之前的RedisTemplate方法,新增自定义的redisClusterConnection方法,并且设置好读写分离,参考代码如下:

    @Configuration

    public class RedisConfiguration {

    @Autowired

    private Environment environment;

    <a href='http://www.jobbole.com/members/q890462235

    '>@Bean</a>

    public StatefulRedisClusterConnection redisClus

    terConnection(){

    String strRedisClusterNodes = environment.getProperty("spring.redis.cluster.nodes");

    String[] listNodesInfos = strRedisClusterNodes.split(",");

    List<RedisURI> listRedisURIs = new ArrayList<RedisURI>();

    for(String tmpNodeInfo : listNodesInfos){

    String[] tmpInfo = tmpNodeInfo.split(":");

    listRedisURIs.add(new RedisURI(tmpInfo[0],Integer.

    parseInt(tmpInfo[1]),

    Duration.ofDays(10)));

    }

    RedisClusterClient clusterClient = RedisClusterClient.create(listRedisURIs);

    StatefulRedisClusterConnection<String, String>

    connection = clusterClient.connect();

    connection.setReadFrom(ReadFrom.SLAVE);

    return connection;

    }

    }

    其中,这三行代码是能进行redis-cluster架构下读写分离的核心:

    RedisClusterClient clusterClient = RedisClusterClient.create(listRedisURIs);

    StatefulRedisClusterConnection<String, String> connection = clusterClient.connect();

    connection.setReadFrom(ReadFrom.SLAVE);

    在业务类service中注入Lettuce相关的redisClusterConnection,进行相关读写操作。以下是我直接化简到了springbootstarter中进行,参考代码如下:

    @SpringBootApplication

    public class NewRedisClientApplication {

    public static void main(String[] args) {

    ApplicationContext context = SpringApplication.run(NewRedisClientApplication.

    class, args);

    StatefulRedisClusterConnection<String, String>

    redisClusterConnection = (StatefulRedisClusterConnection)context.getBean

    ("redisClusterConnection");

    System.out.println(redisClusterConnection.sync()

    .get("event:10"));

    }

    }

    运行的结果如下图所示:

    13465705-5c5a51f7bdddf324.png!web

    可以看到,经过改写的redisClusterConnection的确能读取到redis-cluster的数据。但这一个数据我们还需要验证一下到底是不是通过slave读取到的,又或者还是通过slave重定向给master才获取到的?

    带着疑问,我们可以开通debug模式,在redisClusterConnection.sync().get(“event:10”)等类似的获取数据的代码行上面打上断点。通过代码的走查,我们可以看到,在ReadFromImpl类中,最终会select到key所在的slave节点,进行返回,并在该slave中进行数据的读取:

    ReadFromImpl显示:

    13465705-07c7930729879aca.png!web

    另外我们通过connectFuture中的显示也验证了对于slave的readonly生效了:

    13465705-b00a514979c512dc.png!web

    这样,就达到了通过Lettuce客户端对redis-cluster的读写分离了。

    使用该方案进行redis-cluster性能的提升的优点有:

    (1)直接通过代码级更改,而不需要配置新的redis-cluster环境。

    (2)无需增加机器或升级硬件设备。

    但同时,该方案也有缺点:

    (1)非官方对redis-cluster的推荐方案,因为在redis-cluster架构下,进行读写分离,有可能会读到过期的数据。

    (2)需对项目进行全面的替换,将Jedis客户端变为Lettuce客户端,对代码的改动较大,而且使用Lettuce时,使用的并非spring boot的自带集成Lettuce的redisTemplate配置方法,而是自己配置读写分离的 redisClusterConnetcion,日后遇到问题的时候,可能官方文档的支持率或支撑能力会比较低。

    (3)需修改redis-cluster的master、slave配置,在各个节点中都需要加入slave-read-only yes。

    (4)性能的提升没有水平扩展master主机和实例来得直接干脆。

    总结

    总体上来说,redis-cluster高可用架构方案是目前最好的redis架构方案,redis的官方对redis-cluster架构是建议redis-master用于接收读写,而redis-slave则用于备份(备用),默认不建议读写分离。但如果不介意读取的是redis-cluster中有可能过期的数据并且对写请求不感兴趣时,则亦可通过readonly命令,将slave设置成可读,然后通过slave获取相关的key,达到读写分离。Jedis、Lettuce都可以进行redis-cluster的读写操作,而且默认只针对Master进行读写,若要对redis-cluster架构下进行读写分离,则Jedis需要进行源码的较大改动,而Lettuce开放了setReadFrom()方法,可以进行二次封装成读写分离的客户端,相对简单,而且Lettuce比Jedis更安全。redis-cluster架构下可以直接通过水平扩展master来达到性能的提升。

    Java高架构师、分布式架构、高可扩展、高性能、高并发、性能优化、Spring boot、Redis、ActiveMQ、Nginx、Mycat、Netty、Jvm大型分布式项目实战学习架构师视频免费获取

    进阶高级架构群;855355016

    展开全文
  • redis架构演变与redis-cluster群集读写方案导言redis-cluster是近年来redis架构不断改进中的相对较好的redis高可用方案。本文涉及到近年来redis多实例架构的演变过程,包括普通主从架构(Master、slave可进行写读分离...

    redis架构演变与redis-cluster群集读写方案

    导言

    redis-cluster是近年来redis架构不断改进中的相对较好的redis高可用方案。本文涉及到近年来redis多实例架构的演变过程,包括普通主从架构(Master、slave可进行写读分离)、哨兵模式下的主从架构、redis-cluster高可用架构(redis官方默认cluster下不进行读写分离)的简介。同时还介绍使用Java的两大redis客户端:Jedis与Lettuce用于读写redis-cluster的数据的一般方法。再通过官方文档以及互联网的相关技术文档,给出redis-cluster架构下的读写能力的优化方案,包括官方的推荐的扩展redis-cluster下的Master数量以及非官方默认的redis-cluster的读写分离方案,案例中使用Lettuce的特定方法进行redis-cluster架构下的数据读写分离。

    近年来redis多实例用架构的演变过程

    redis是基于内存的高性能key-value数据库,若要让redis的数据更稳定安全,需要引入多实例以及相关的高可用架构。而近年来redis的高可用架构亦不断改进,先后出现了本地持久化、主从备份、哨兵模式、redis-cluster群集高可用架构等等方案。

    1、redis普通主从模式

    通过持久化功能,Redis保证了即使在服务器重启的情况下也不会损失(或少量损失)数据,因为持久化会把内存中数据保存到硬盘上,重启会从硬盘上加载数据。 。但是由于数据是存储在一台服务器上的,如果这台服务器出现硬盘故障等问题,也会导致数据丢失。为了避免单点故障,通常的做法是将数据库复制多个副本以部署在不同的服务器上,这样即使有一台服务器出现故障,其他服务器依然可以继续提供服务。为此, Redis 提供了复制(replication)功能,可以实现当一台数据库中的数据更新后,自动将更新的数据同步到其他数据库上。

    在复制的概念中,数据库分为两类,一类是主数据库(master),另一类是从数据库(slave)。主数据库可以进行读写操作,当写操作导致数据变化时会自动将数据同步给从数据库。而从数据库一般是只读的,并接受主数据库同步过来的数据。一个主数据库可以拥有多个从数据库,而一个从数据库只能拥有一个主数据库。

    主从模式的配置,一般只需要再作为slave的redis节点的conf文件上加入“slaveof masterip masterport”, 或者作为slave的redis节点启动时使用如下参考命令:

    redis-server --port 6380 --slaveof masterIp masterPort

    redis的普通主从模式,能较好地避免单独故障问题,以及提出了读写分离,降低了Master节点的压力。互联网上大多数的对redis读写分离的教程,都是基于这一模式或架构下进行的。但实际上这一架构并非是目前最好的redis高可用架构。

    2、redis哨兵模式高可用架构

    当主数据库遇到异常中断服务后,开发者可以通过手动的方式选择一个从数据库来升格为主数据库,以使得系统能够继续提供服务。然而整个过程相对麻烦且需要人工介入,难以实现自动化。 为此,Redis 2.8开始提供了哨兵工具来实现自动化的系统监控和故障恢复功能。 哨兵的作用就是监控redis主、从数据库是否正常运行,主出现故障自动将从数据库转换为主数据库。

    顾名思义,哨兵的作用就是监控Redis系统的运行状况。它的功能包括以下两个。

    (1)监控主数据库和从数据库是否正常运行。

    (2)主数据库出现故障时自动将从数据库转换为主数据库。

    可以用info replication查看主从情况 例子: 1主2从 1哨兵,可以用命令起也可以用配置文件里 可以使用双哨兵,更安全,参考命令如下:

    redis-server --port 6379

    redis-server --port 6380 --slaveof 192.168.0.167 6379

    redis-server --port 6381 --slaveof 192.168.0.167 6379

    redis-sentinel sentinel.conf

    其中,哨兵配置文件sentinel.conf参考如下:

    sentinel monitor mymaster 192.168.0.167 6379 1

    其中mymaster表示要监控的主数据库的名字。配置哨兵监控一个系统时,只需要配置其监控主数据库即可,哨兵会自动发现所有复制该主数据库的从数据库。

    Master与slave的切换过程:

    (1)slave leader升级为master

    (2)其他slave修改为新master的slave

    (3)客户端修改连接

    (4)老的master如果重启成功,变为新master的slave

    3、redis-cluster群集高可用架构

    即使使用哨兵,redis每个实例也是全量存储,每个redis存储的内容都是完整的数据,浪费内存且有木桶效应。为了最大化利用内存,可以采用cluster群集,就是分布式存储。即每台redis存储不同的内容。

    采用redis-cluster架构正是满足这种分布式存储要求的集群的一种体现。redis-cluster架构中,被设计成共有16384个hash slot。每个master分得一部分slot,其算法为:hash_slot = crc16(key) mod 16384 ,这就找到对应slot。采用hash slot的算法,实际上是解决了redis-cluster架构下,有多个master节点的时候,数据如何分布到这些节点上去。key是可用key,如果有{}则取{}内的作为可用key,否则整个可以是可用key。群集至少需要3主3从,且每个实例使用不同的配置文件。

    在redis-cluster架构中,redis-master节点一般用于接收读写,而redis-slave节点则一般只用于备份,其与对应的master拥有相同的slot集合,若某个redis-master意外失效,则再将其对应的slave进行升级为临时redis-master。

    在redis的官方文档中,对redis-cluster架构上,有这样的说明:在cluster架构下,默认的,一般redis-master用于接收读写,而redis-slave则用于备份,当有请求是在向slave发起时,会直接重定向到对应key所在的master来处理。但如果不介意读取的是redis-cluster中有可能过期的数据并且对写请求不感兴趣时,则亦可通过readonly命令,将slave设置成可读,然后通过slave获取相关的key,达到读写分离。具体可以参阅redis官方文档(https://redis.io/commands/readonly)等相关内容:

    Enables read queries for a connection to a Redis Cluster slave node.

    Normally slave nodes will redirect clients to the authoritative master for the hash slot involved in a given command, however clients can use slaves in order to scale reads using the READONLY command.

    READONLY tells a Redis Cluster slave node that the client is willing to read possibly stale data and is not interested in running write queries.

    When the connection is in readonly mode, the cluster will send a redirection to the client only if the operation involves keys not served by the slave's master node. This may happen because:

    The client sent a command about hash slots never served by the master of this slave.

    The cluster was reconfigured (for example resharded) and the slave is no longer able to serve commands for a given hash slot.

    例如,我们假设已经建立了一个三主三从的redis-cluster架构,其中A、B、C节点都是redis-master节点,A1、B1、C1节点都是对应的redis-slave节点。在我们只有master节点A,B,C的情况下,对应redis-cluster如果节点B失败,则群集无法继续,因为我们没有办法再在节点B的所具有的约三分之一的hash slot集合范围内提供相对应的slot。然而,如果我们为每个主服务器节点添加一个从服务器节点,以便最终集群由作为主服务器节点的A,B,C以及作为从服务器节点的A1,B1,C1组成,那么如果节点B发生故障,系统能够继续运行。节点B1复制B,并且B失效时,则redis-cluster将促使B的从节点B1作为新的主服务器节点并且将继续正确地操作。但请注意,如果节点B和B1在同一时间发生故障,则Redis群集无法继续运行。

    Redis群集配置参数:在继续之前,让我们介绍一下Redis Cluster在redis.conf文件中引入的配置参数。有些命令的意思是显而易见的,有些命令在你阅读下面的解释后才会更加清晰。

    (1)cluster-enabled :如果想在特定的Redis实例中启用Redis群集支持就设置为yes。 否则,实例通常作为独立实例启动。

    (2)cluster-config-file :请注意,尽管有此选项的名称,但这不是用户可编辑的配置文件,而是Redis群集节点每次发生更改时自动保留群集配置(基本上为状态)的文件。

    (3)cluster-node-timeout :Redis群集节点可以不可用的最长时间,而不会将其视为失败。 如果主节点超过指定的时间不可达,它将由其从属设备进行故障切换。

    (4)cluster-slave-validity-factor :如果设置为0,无论主设备和从设备之间的链路保持断开连接的时间长短,从设备都将尝试故障切换主设备。 如果该值为正值,则计算最大断开时间作为节点超时值乘以此选项提供的系数,如果该节点是从节点,则在主链路断开连接的时间超过指定的超时值时,它不会尝试启动故障切换。

    (5)cluster-migration-barrier :主设备将保持连接的最小从设备数量,以便另一个从设备迁移到不受任何从设备覆盖的主设备。有关更多信息,请参阅本教程中有关副本迁移的相应部分。

    (6)cluster-require-full-coverage :如果将其设置为yes,则默认情况下,如果key的空间的某个百分比未被任何节点覆盖,则集群停止接受写入。 如果该选项设置为no,则即使只处理关于keys子集的请求,群集仍将提供查询。

    以下是最小的Redis集群配置文件:

    port 7000

    cluster-enabled yes

    cluster-config-file nodes.conf

    cluster-node-timeout 5000

    appendonly yes

    注意:

    (1)redis-cluster最小配置为三主三从,当1个主故障,大家会给对应的从投票,把从立为主,若没有从数据库可以恢复则redis群集就down了。

    (2)在这个redis cluster中,如果你要在slave读取数据,那么需要带上readonly指令。redis cluster的核心的理念,主要是用slave做高可用的,每个master挂一两个slave,主要是做数据的热备,当master故障时的作为主备切换,实现高可用的。redis cluster默认是不支持slave节点读或者写的,跟我们手动基于replication搭建的主从架构不一样的。slave node要设置readonly,然后再get,这个时候才能在slave node进行读取。对于redis -cluster主从架构,若要进行读写分离,官方其实是不建议的,但也能做,只是会复杂一些。具体见下面的章节。

    (3)redis-cluster的架构下,实际上本身master就是可以任意扩展的,你如果要支撑更大的读吞吐量,或者写吞吐量,或者数据量,都可以直接对master进行横向扩展就可以了。也扩容master,跟之前扩容slave进行读写分离,效果是一样的或者说更好。

    (4)可以使用自带客户端连接:使用redis-cli -c -p cluster中任意一个端口,进行数据获取测试。

    Java中对redis-cluster数据的一般读取方法简介

    使用Jedis读写redis-cluster的数据

    由于Jedis类一般只能对一台redis-master进行数据操作,所以面对redis-cluster多台master与slave的群集,Jedis类就不能满足了。这个时候我们需要引用另外一个操作类:JedisCluster类。

    例如我们有6台机器组成的redis-cluster:

    172.20.52.85:7000、 172.20.52.85:7001、172.20.52.85:7002、172.20.52.85:7003、172.20.52.85:7004、172.20.52.85:7005

    其中master机器对应端口:7000、7004、7005

    slave对应端口:7001、7002、7003

    使用JedisCluster对redis-cluster进行数据操作的参考代码如下:

    // 添加nodes服务节点到Set集合

    Set hostAndPortsSet = new HashSet();

    // 添加节点

    hostAndPortsSet.add(new HostAndPort("172.20.52.85", 7000));

    hostAndPortsSet.add(new HostAndPort("172.20.52.85", 7001));

    hostAndPortsSet.add(new HostAndPort("172.20.52.85", 7002));

    hostAndPortsSet.add(new HostAndPort("172.20.52.85", 7003));

    hostAndPortsSet.add(new HostAndPort("172.20.52.85", 7004));

    hostAndPortsSet.add(new HostAndPort("172.20.52.85", 7005));

    // Jedis连接池配置

    JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();

    jedisPoolConfig.setMaxIdle(100);

    jedisPoolConfig.setMaxTotal(500);

    jedisPoolConfig.setMinIdle(0);

    jedisPoolConfig.setMaxWaitMillis(2000); // 设置2秒

    jedisPoolConfig.setTestOnBorrow(true);

    JedisCluster jedisCluster = new JedisCluster(hostAndPortsSet ,jedisPoolConfig);

    String result = jedisCluster.get("event:10");

    System.out.println(result);

    运行结果截图如下图所示:

    第一节中我们已经介绍了redis-cluster架构下master提供读写功能,而slave一般只作为对应master机器的数据备份不提供读写。如果我们只在hostAndPortsSet中只配置slave,而不配置master,实际上还是可以读到数据,但其内部操作实际是通过slave重定向到相关的master主机上,然后再将结果获取和输出。

    上面是普通项目使用JedisCluster的简单过程,若在spring boot项目中,可以定义JedisConfig类,使用@Configuration、@Value、@Bean等一些列注解完成JedisCluster的配置,然后再注入该JedisCluster到相关service逻辑中引用,这里介绍略。

    使用Lettuce读写redis-cluster数据

    Lettuce 和 Jedis 的定位都是Redis的client。Jedis在实现上是直接连接的redis server,如果在多线程环境下是非线程安全的,这个时候只有使用连接池,为每个Jedis实例增加物理连接,每个线程都去拿自己的 Jedis 实例,当连接数量增多时,物理连接成本就较高了。

    Lettuce的连接是基于Netty的,连接实例(StatefulRedisConnection)可以在多个线程间并发访问,应为StatefulRedisConnection是线程安全的,所以一个连接实例(StatefulRedisConnection)就可以满足多线程环境下的并发访问,当然这个也是可伸缩的设计,一个连接实例不够的情况也可以按需增加连接实例。

    其中spring boot 2.X版本中,依赖的spring-session-data-redis已经默认替换成Lettuce了。

    同样,例如我们有6台机器组成的redis-cluster:

    172.20.52.85:7000、 172.20.52.85:7001、172.20.52.85:7002、172.20.52.85:7003、172.20.52.85:7004、172.20.52.85:7005

    其中master机器对应端口:7000、7004、7005

    slave对应端口:7001、7002、7003

    在spring boot 2.X版本中使用Lettuce操作redis-cluster数据的方法参考如下:

    (1)pom文件参考如下:

    parent中指出spring boot的版本,要求2.X以上:

    org.springframework.boot

    spring-boot-starter-parent

    2.0.4.RELEASE

    依赖中需要加入spring-boot-starter-data-redis,参考如下:

    org.springframework.boot

    spring-boot-starter-data-redis

    (2)springboot的配置文件要包含如下内容:

    spring.redis.database=0

    spring.redis.lettuce.pool.max-idle=10

    spring.redis.lettuce.pool.max-wait=500

    spring.redis.cluster.timeout=1000

    spring.redis.cluster.max-redirects=3

    spring.redis.cluster.nodes=172.20.52.85:7000,172.20.52.85:7001,172.20.52.85:7002,172.20.52.85:7003,172.20.52.85:7004,172.20.52.85:7005

    (3)新建RedisConfiguration类,参考代码如下:

    @Configuration

    public class RedisConfiguration {

    [@Resource](https://my.oschina.net/u/929718)

    private LettuceConnectionFactory myLettuceConnectionFactory;

    @Bean

    public RedisTemplate redisTemplate() {

    RedisTemplate template = new RedisTemplate<>();

    template.setKeySerializer(new StringRedisSerializer());

    //template.setValueSerializer(new GenericJackson2JsonRedisSerializer());

    template.setValueSerializer(new StringRedisSerializer());

    template.setConnectionFactory(myLettuceConnectionFactory);

    return template;

    }

    }

    (4)新建RedisFactoryConfig类,参考代码如下:

    @Configuration

    public class RedisFactoryConfig {

    @Autowired

    private Environment environment;

    @Bean

    public RedisConnectionFactory myLettuceConnectionFactory() {

    Map source = new HashMap();

    source.put("spring.redis.cluster.nodes", environment.getProperty("spring.redis.cluster.nodes"));

    source.put("spring.redis.cluster.timeout", environment.getProperty("spring.redis.cluster.timeout"));

    source.put("spring.redis.cluster.max-redirects", environment.getProperty("spring.redis.cluster.max-redirects"));

    RedisClusterConfiguration redisClusterConfiguration;

    redisClusterConfiguration = new RedisClusterConfiguration(new MapPropertySource("RedisClusterConfiguration", source));

    return new LettuceConnectionFactory(redisClusterConfiguration);

    }

    }

    (5)在业务类service中注入Lettuce相关的RedisTemplate,进行相关操作。以下是我化简到了springbootstarter中进行,参考代码如下:

    @SpringBootApplication

    public class NewRedisClientApplication {

    public static void main(String[] args) {

    ApplicationContext context = SpringApplication.run(NewRedisClientApplication.class, args);

    RedisTemplate redisTemplate = (RedisTemplate)context.getBean("redisTemplate");

    String rtnValue = (String)redisTemplate.opsForValue().get("event:10");

    System.out.println(rtnValue);

    }

    }

    运行结果的截图如下:

    以上的介绍,是采用Jedis以及Lettuce对redis-cluster数据的简单读取。Jedis也好,Lettuce也好,其对于redis-cluster架构下的数据的读取,都是默认是按照redis官方对redis-cluster的设计,自动进行重定向到master节点中进行的,哪怕是我们在配置中列出了所有的master节点和slave节点。查阅了Jedis以及Lettuce的github上的源码,默认不支持redis-cluster下的读写分离,可以看出Jedis若要支持redis-cluster架构下的读写分离,需要自己改写和构建多一些包装类,定义好Master和slave节点的逻辑;而Lettuce的源码中,实际上预留了方法(setReadForm(ReadFrom.SLAVE))进行redis-cluster架构下的读写分离,相对来说修改会简单一些,具体可以参考后面的章节。

    redis-cluster架构下的读写能力的优化方案

    在上面的一些章节中,已经有讲到redis近年来的高可用架构的演变,以及在redis-cluster架构下,官方对redis-master、redis-slave的其实有使用上的建议,即redis-master节点一般用于接收读写,而redis-slave节点则一般只用于备份,其与对应的master拥有相同的slot集合,若某个redis-master意外失效,则再将其对应的slave进行升级为临时redis-master。但如果不介意读取的是redis-cluster中有可能过期的数据并且对写请求不感兴趣时,则亦可通过readonly命令,将slave设置成可读,然后通过slave获取相关的key,达到读写分离。

    具体可以参阅redis官方文档(https://redis.io/commands/readonly),以下是reids在线文档中,对slave的readonly说明内容:

    实际上本身master就是可以任意扩展的,所以如果要支撑更大的读吞吐量,或者写吞吐量,或者数据量,都可以直接对master进行横向水平扩展就可以了。也就是说,扩容master,跟之前扩容slave并进行读写分离,效果是一样的或者说更好。

    所以下面我们将按照redis-cluster架构下分别进行水平扩展Master,以及在redis-cluster架构下对master、slave进行读写分离两套方案进行讲解。

    (一)水平扩展Master实例来进行redis-cluster性能的提升

    我们可以通过master的水平扩展,来直接提升读写吞吐量,并且能支撑更多的海量数据。对master进行水平扩展有两种方法,一种是在原有的机器基础上面进行master实例的增加,另一种是新增机器部署新的master实例。同时,我们建议每新增一个master,也至少新增一个对应的slave。

    (1) 在原有机器上通过启动新进程建立新redis-master实例:

    我们可以在原redis-cluster架构中的每台物理机上增加redis进程实例:

    一般的,对于redis单个实例的读吞吐是4w/s~5W/s,写吞吐为2w/s。

    在同一台机器上合理开启redis多个实例情况下(一般实例或线程数最好为CPU核数的倍数),总吞吐量会有所上升,但每个实例线程的平均处理能力会有所下降。例如一个2核CPU,开启2个实例的时候,总读吞吐能上升是6W/s~7W/s,即每个实例线程平均约3W/s再多一些。但同一台物理机器中过多的redis实例反而会限制了总吞吐量,而且一旦一台物理机器失效,会导致多个实例同时失效,从而导致redis-cluster全体失效的风险增加。

    在机器上新增redis实例需要分开路径在不同的文件夹内部署新的redis运行环境,并且配置好redis.conf文件,分配好不同的port,然后再分别使用命令启动。

    (2)新增机器,部署新redis-master实例,即物理上的水平扩展:

    例如,我们可以再原来只有3台master的基础上,连入新机器继续新实例的部署,最终水平扩展为6台master(建议每新增一个master,也新增一个对应的slave)。例如之前每台master的处理能力假设是读吞吐5W/s,写吞吐2W/s,扩展前一共的处理能力是:15W/s读,6W/s写。如果我们水平扩展到6台master,读吞吐可以达到总量30W/s,写可以达到12w/s,性能能够成倍增加。

    当然,如果我们的机器性能良好,我们可以将上面两种方式结合。例如如果需要水平扩展到6个master和6个slave甚至12个slave的redis-cluster,其实不必要引入真正的十几台机器来运行,实际上可以将他们合理部署到6台机器即可,如下图所示:

    其中,每一台机器下面都有master和slave,但同一台机器下的slave逻辑上并非是属于该物理机器下的master,而是交叉地服务与另外一台物理机器上的master,作为其备份。这样能避免当一台物理机器失效了,就整个master和其对应的slave同时失效,进而导致整个redis-cluster则无法继续提供服务。错开交叉方式的master-slave配置,具备更高的可用性。这样的redis-cluster中的机器,即使损坏或失效两台,只要不是最坏的情况下,仍能继续运行提供redis服务。如果一个master对应不止一个slave,而是2个,则可用性又会再次提高。

    上面通过了两种方法介绍了水平扩展redis-cluster以达到能力和高可用性的提升,使用该方桉的优点有:

    (1)符合redis官方要求和数据的准确性。

    (2)真实的水平扩展能达到更大吞吐量的性能扩展。

    (3)新增机器部署多套实例与备份实例能极大增加稳定性与健壮性。

    (4)无需代码的大量更改,只需在配置文件中重新配置新的节点信息。

    当然缺点也是有的:

    (1)水平扩展需要新增机器来提升性能,这意味着会增加一定的成本。但不妨租用成本相对较低的云服务器来部署新实例,同样达到扩容的效果。

    (2)若不新增机器,则需要原来的实例所运行的机器性能较好,能进行以多进程实例的方式部署新实例。但随着进程/线程的增多,而机器的能力不足以支撑的时候,实际上总体能力会提升不太明显,反而会增加单点失效导致的redis-cluster整体失效的风险。

    (3)redis-cluster进行新的水平扩容后,需要对master进行新的hash slot重新分配。但我们可以通过通过rebuild数据,重新初始化redis数据到水平扩展后的redis-cluster中来忽略这一问题,这个操作对应用本身不影响。

    (二)引入Lettuce以及修改相关方法,达到对redis-cluster的读写分离

    通过上面的一些章节,我们已经可以了解到Lettuce客户端读取redis的一些操作,使用Lettuce能体现出了简单,安全,高效。实际上,查阅了Lettuce对redis的读写,许多地方都进行了redis的读写分离。但这些都是基于上述redis架构中最普通的主从分离架构下的读写分离,而对于redis-cluster架构下,Lettuce可能是遵循了redis官方的意见,在该架构下,Lettuce在源码中直接设置了只由master上进行读写(具体参见gitHub的Lettuce项目):

    那么如果真的需要让Lettuce改为能够读取redis-cluster的slave,进行读写分离,是否可行?实际上还是可以的。这就需要我们自己在项目中进行二次加工,即不使用spring-boot中的默认Lettuce初始化方法,而是自己去写一个属于自己的Lettuce的新RedisClusterClient的连接,并且对该RedisClusterClient的连接进行一个比较重要的设置,那就是由connection.setReadFrom(ReadFrom.MASTER)改为connection.setReadFrom(ReadFrom.SLAVE)。

    下面我们开始对之前章节中的Lettuce读取redis-cluster数据的例子,进行改写,让Lettuce能够支持该架构下的读写分离:

    spring boot 2.X版本中,依赖的spring-session-data-redis已经默认替换成Lettuce了。

    同样,例如我们有6台机器组成的redis-cluster:

    172.20.52.85:7000、 172.20.52.85:7001、172.20.52.85:7002、172.20.52.85:7003、172.20.52.85:7004、172.20.52.85:7005

    其中master机器对应端口:7000、7004、7005

    slave对应端口:7001、7002、7003

    在spring boot 2.X版本中使用Lettuce操作redis-cluster数据的方法参考如下:

    (1)pom文件参考如下:

    parent中指出spring boot的版本,要求2.X以上:

    org.springframework.boot

    spring-boot-starter-parent

    2.0.4.RELEASE

    依赖中需要加入spring-boot-starter-data-redis,参考如下:

    org.springframework.boot

    spring-boot-starter-data-redis

    (2)springboot的配置文件要包含如下内容:

    spring.redis.database=0

    spring.redis.lettuce.pool.max-idle=10

    spring.redis.lettuce.pool.max-wait=500

    spring.redis.cluster.timeout=1000

    spring.redis.cluster.max-redirects=3

    spring.redis.cluster.nodes=172.20.52.85:7000,172.20.52.85:7001,172.20.52.85:7002,172.20.52.85:7003,172.20.52.85:7004,172.20.52.85:7005

    (3)我们回到RedisConfiguration类中,删除或屏蔽之前的RedisTemplate方法,新增自定义的redisClusterConnection方法,并且设置好读写分离,参考代码如下:

    @Configuration

    public class RedisConfiguration {

    @Autowired

    private Environment environment;

    @Bean

    public StatefulRedisClusterConnection redisClusterConnection(){

    String strRedisClusterNodes = environment.getProperty("spring.redis.cluster.nodes");

    String[] listNodesInfos = strRedisClusterNodes.split(",");

    List listRedisURIs = new ArrayList();

    for(String tmpNodeInfo : listNodesInfos){

    String[] tmpInfo = tmpNodeInfo.split(":");

    listRedisURIs.add(new RedisURI(tmpInfo[0],Integer.parseInt(tmpInfo[1]),Duration.ofDays(10)));

    }

    RedisClusterClient clusterClient = RedisClusterClient.create(listRedisURIs);

    StatefulRedisClusterConnection connection = clusterClient.connect();

    connection.setReadFrom(ReadFrom.SLAVE);

    return connection;

    }

    }

    其中,这三行代码是能进行redis-cluster架构下读写分离的核心:

    RedisClusterClient clusterClient = RedisClusterClient.create(listRedisURIs);

    StatefulRedisClusterConnection connection = clusterClient.connect();

    connection.setReadFrom(ReadFrom.SLAVE);

    在业务类service中注入Lettuce相关的redisClusterConnection,进行相关读写操作。以下是我直接化简到了springbootstarter中进行,参考代码如下:

    @SpringBootApplication

    public class NewRedisClientApplication {

    public static void main(String[] args) {

    ApplicationContext context = SpringApplication.run(NewRedisClientApplication.class, args);

    StatefulRedisClusterConnection redisClusterConnection = (StatefulRedisClusterConnection)context.getBean("redisClusterConnection");

    System.out.println(redisClusterConnection.sync().get("event:10"));

    }

    }

    运行的结果如下图所示:

    可以看到,经过改写的redisClusterConnection的确能读取到redis-cluster的数据。但这一个数据我们还需要验证一下到底是不是通过slave读取到的,又或者还是通过slave重定向给master才获取到的?

    带着疑问,我们可以开通debug模式,在redisClusterConnection.sync().get("event:10")等类似的获取数据的代码行上面打上断点。通过代码的走查,我们可以看到,在ReadFromImpl类中,最终会select到key所在的slave节点,进行返回,并在该slave中进行数据的读取:

    ReadFromImpl显示:

    另外我们通过connectFuture中的显示也验证了对于slave的readonly生效了:

    这样,就达到了通过Lettuce客户端对redis-cluster的读写分离了。

    使用该方案进行redis-cluster性能的提升的优点有:

    (1)直接通过代码级更改,而不需要配置新的redis-cluster环境。

    (2)无需增加机器或升级硬件设备。

    但同时,该方案也有缺点:

    (1)非官方对redis-cluster的推荐方案,因为在redis-cluster架构下,进行读写分离,有可能会读到过期的数据。

    (2)需对项目进行全面的替换,将Jedis客户端变为Lettuce客户端,对代码的改动较大,而且使用Lettuce时,使用的并非spring boot的自带集成Lettuce的redisTemplate配置方法,而是自己配置读写分离的 redisClusterConnetcion,日后遇到问题的时候,可能官方文档的支持率或支撑能力会比较低。

    (3)需修改redis-cluster的master、slave配置,在各个节点中都需要加入slave-read-only yes。

    (4)性能的提升没有水平扩展master主机和实例来得直接干脆。

    总结

    总体上来说,redis-cluster高可用架构方案是目前最好的redis架构方案,redis的官方对redis-cluster架构是建议redis-master用于接收读写,而redis-slave则用于备份(备用),默认不建议读写分离。但如果不介意读取的是redis-cluster中有可能过期的数据并且对写请求不感兴趣时,则亦可通过readonly命令,将slave设置成可读,然后通过slave获取相关的key,达到读写分离。Jedis、Lettuce都可以进行redis-cluster的读写操作,而且默认只针对Master进行读写,若要对redis-cluster架构下进行读写分离,则Jedis需要进行源码的较大改动,而Lettuce开放了setReadFrom()方法,可以进行二次封装成读写分离的客户端,相对简单,而且Lettuce比Jedis更安全。redis-cluster架构下可以直接通过水平扩展master来达到性能的提升。

    参考文档

    1,网文《关于redis主从、哨兵、集群的介绍》:https://blog.csdn.net/c295477887/article/details/52487621

    2,知乎《lettuce与jedis对比介绍》:https://www.zhihu.com/question/53124685

    3,网文《Redis 高可用架构最佳实践问答集锦》:http://www.talkwithtrend.com/Article/178165

    4,网文《Redis进阶实践之十一 Redis的Cluster集群搭建》:https://www.cnblogs.com/PatrickLiu/p/8458788.html

    5,redis官方在线文档:https://redis.io/

    6,网文《redis cluster的介绍及搭建(6)》:https://blog.csdn.net/qq1137623160/article/details/79184686

    7,网文《Springboot2.X集成redis集群(Lettuce)连接》:http://www.cnblogs.com/xymBlog/p/9303032.html

    8,Jedis的gitHub地址:https://github.com/xetorthio/jedis

    9,Lettuce的gitHub地址:https://github.com/lettuce-io/lettuce-core/

    展开全文
  • 常用的Redis架构设计

    千次阅读 2020-07-15 17:07:10
    Redis架构设计目前流行的四种模式一、一致性Hash二、Redis哨兵模式三、Codis四、Redis_cluster五、Codis集群和Redis_cluster的优劣对比 目前流行的四种模式 读者们,你们好!目前流行的Redis架构主要有四种,分别为...
  • redis架构演变与redis-cluster群集读写方案 导言 redis-cluster是近年来redis架构不断改进中的相对较好的redis高可用方案。本文涉及到近年来redis多实例架构的演变过程,包括普通主从架构(Master、slave可进行写...
  • redis-cluster是近年来redis架构不断改进中的相对较好的redis高可用方案。本文涉及到近年来redis多实例架构的演变过程,包括普通主从架构(Master、slave可进行写读分离)、哨兵模式下的主从架构、redis-cluster高...
  • redis架构

    2018-12-25 15:06:25
    redis是一个key-value存储系统。和Memcached类似,它支持存储的value类型相对更多,包括string(字符串)、list(链表)、set(集合)、zset(sorted set --有序集合)和hash(哈希类型)。这些数据类型都支持push/pop、add/...
  • 细说分布式Redis架构设计和踩过的那些坑 细说分布式Redis架构设计和踩过的那些坑_redis 分布式_ redis 分布式锁_分布式缓存redis细说分布式Redis架构设计和踩过的那些坑 ...
  • redis为什么需要实现读写分离master持久化对于主从架构的安全保障的意义`redis replication`最基本的原理redis主从架构的核心原理主从复制的断点续传无磁盘化复制过期key处理复制的完整流程数据同步相关的核心机制...
  • Redis架构原理及应用实践一:数据基本类型stringhashlistsetZset二:Redis使用场景1.会话缓存(Session Cache)2.队列3.全页缓存4.排行榜/计数器三:Redis高可用架构1.持久化RDBAOF四: Redis高并发及热key解决之道1....
  • Redis架构概述

    2020-08-10 17:09:36
    Redis单实例架构 Redis持久性 Redis数据存储的备份和恢复 Redis复制 Redis复制中的持久性 Redis中的聚类 集群中的持久性 群集和复制在一起 Redis客户端 Redis是一个内存中的键值数据存储。Redis是最流行的...
  • 文章目录Scrapy框架Scrapy框架整体架构Scrapy 框架运行流程Scrapy 框架各个模块分析Scrapy基本工作流程Scrapy常用命令scrapy - redis 架构scrapy - redis 简介scrapy - redis 架构基本运行流程优缺点scrapy - redis ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 18,566
精华内容 7,426
关键字:

redis架构

redis 订阅