Sharded源码分析

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
简介: 1. 概述当业务的数据量非常庞大时,需要考虑将数据存储到多个缓存节点上,如何定位数据应该存储的节点,一般用的是一致性哈希算法。Jedis在客户端角度实现了一致性哈希算法,对数据进行分片,存储到对应的不同的redis实例中。

1. 概述

当业务的数据量非常庞大时,需要考虑将数据存储到多个缓存节点上,如何定位数据应该存储的节点,一般用的是一致性哈希算法。
Jedis在客户端角度实现了一致性哈希算法,对数据进行分片,存储到对应的不同的redis实例中。
Jedis对Sharded的实现主要是在ShardedJedis.javaShardedJedisPool.java中。
本文主要介绍ShardedJedis的实现,ShardedJedisPool是基于apache的common-pool2的对象池实现。

2. 继承关系

img_9e7cf2eb29dc0d3aa6e96c5669cd8758.png

3. 构造函数

img_151a88caef9e4f8ffd488c1b2ea189a1.png

参数

  • shards
    一个JedisShardInfo的列表,一个JedisShardedInfo类代表一个数据分片的主体
  • algo
    数据分片算法
  • keyTagPattern
    自定义分片算法所依据的key的形式。例如,可以不针对整个key的字符串做哈希计算,而是类似对thisisa{key}中包含在大括号内的字符串进行哈希计算

JedisShardInfo源码

public class JedisShardInfo extends ShardInfo<Jedis> {
    public String toString() {
    return host + ":" + port + "*" + getWeight();
    }

    private int timeout;
    private String host;
    private int port;
    private String password = null;
    private String name = null;

    public String getHost() {
    return host;
    }

    public int getPort() {
    return port;
    }

    public JedisShardInfo(String host) {
    super(Sharded.DEFAULT_WEIGHT);
    URI uri = URI.create(host);
    if (uri.getScheme() != null && uri.getScheme().equals("redis")) {
        this.host = uri.getHost();
        this.port = uri.getPort();
        this.password = uri.getUserInfo().split(":", 2)[1];
    } else {
        this.host = host;
        this.port = Protocol.DEFAULT_PORT;
    }
    }

    public JedisShardInfo(String host, String name) {
    this(host, Protocol.DEFAULT_PORT, name);
    }

    public JedisShardInfo(String host, int port) {
    this(host, port, 2000);
    }

    public JedisShardInfo(String host, int port, String name) {
    this(host, port, 2000, name);
    }

    public JedisShardInfo(String host, int port, int timeout) {
    this(host, port, timeout, Sharded.DEFAULT_WEIGHT);
    }

    public JedisShardInfo(String host, int port, int timeout, String name) {
    this(host, port, timeout, Sharded.DEFAULT_WEIGHT);
    this.name = name;
    }

    public JedisShardInfo(String host, int port, int timeout, int weight) {
    super(weight);
    this.host = host;
    this.port = port;
    this.timeout = timeout;
    }

    public JedisShardInfo(URI uri) {
    super(Sharded.DEFAULT_WEIGHT);
    this.host = uri.getHost();
    this.port = uri.getPort();
    this.password = uri.getUserInfo().split(":", 2)[1];
    }

    @Override
    public Jedis createResource() {
    return new Jedis(this);
    }
}

可见JedisShardInfo包含了一个redis节点主机号,端口号,名称,密码等相关信息
要构造一个ShardedJedis,需提供一个或多个JedisShardInfo

最终构造函数的实现在其父类Sharded

img_edda6d1e219f278fb71872bb64b71d34.png

4. 哈希环的初始化

Sharded类维护了

  • TreeMap
    基于红黑树实现,用来存放经过一致性哈希计算后的redis节点,
  • LinkedHashMap
    用来保存ShardInfo与Jedis实例的对应关系。

定位的流程如下
先在TreeMap中找到对应key所对应的ShardInfo,然后通过ShardInfo在LinkedHashMap中找到对应的Jedis实例

Sharded类对这些实例变量的定义如下所示:

    public static final int DEFAULT_WEIGHT = 1;
    private TreeMap<Long, S> nodes;
    private final Hashing algo;
    private final Map<ShardInfo<R>, R> resources = new LinkedHashMap<ShardInfo<R>, R>();

    // 用于提取密钥标签的默认模式。 该模式必须有一个组(在圆括号之间),它将标签分隔开来进行散列。 空模式可以避免为每个查找应用正则表达式,因此不会使用关键标记来改进性能。
    private Pattern tagPattern = null;

    public static final Pattern DEFAULT_KEY_TAG_PATTERN = Pattern
        .compile("\\{(.+?)\\}");

接下来看其构造函数中的initialize方法


img_692cfb59437636ca3f087f7c31473066.png

可以看到,它对每一个ShardInfo通过一定规则计算其哈希值,然后存到TreeMap中,这里它实现了一致性哈希算法中虚拟节点的概念,因为我们可以看到同一个ShardInfo不止一次被放到TreeMap中,数量是,权重*160。

增加了虚拟节点的一致性哈希有很多好处,比如能避免数据在redis节点间分布不均匀

然后,在LinkedHashMap中放入ShardInfo以及其对应的Jedis实例,通过调用其自身的createSource()来得到jedis实例

数据定位

从ShardedJedis的代码中可以看到,无论进行什么操作,都要先根据key来找到对应的Redis,然后返回一个可供操作的Jedis实例。
例如其set方法


img_9c792a941a804b1f890e790b37ec1830.png

而getShard方法则在Sharded.java中实现,其源代码如下所示:


img_86de392bdcd9f218b267d4de7818d82a.png

可以看到,先通过getShardInfo方法从TreeMap中获得对应的ShardInfo,然后根据这个ShardInfo就能够再LinkedHashMap中获得对应的Jedis实例了
相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
目录
相关文章
|
消息中间件 存储 缓存
聊聊 Kafka:协调者 GroupCoordinator 源码剖析之 FIND_COORDINATOR
聊聊 Kafka:协调者 GroupCoordinator 源码剖析之 FIND_COORDINATOR
168 0
|
监控 前端开发 JavaScript
shin-monitor源码分析
shin-monitor源码分析
|
存储 Java API
源码分析ElasticJob选主实现原理
源码分析ElasticJob选主实现原理
源码分析ElasticJob选主实现原理
|
存储 NoSQL 算法
Redis Cluster原理解析
Redis Cluster原理解析
207 0
Redis Cluster原理解析
|
存储 算法 API
【Zookeeper】源码分析之持久化(一)之FileTxnLog
前一篇已经分析了序列化,这篇接着分析Zookeeper的持久化过程源码,持久化对于数据的存储至关重要,下面进行详细分析。
190 0
【Zookeeper】源码分析之持久化(一)之FileTxnLog
TIKV 源码分析(二)raft-rs 示例程序
前言raft-rs 的 5 节点示例程序稍微比较复杂一些,但是看懂的话,就会对 raft 的使用得心应手。示例程序Node 结构体struct Node {     // None if the raft is not initialized.     raft_group: Option<RawNode<MemStorage>>,     my_mailbox: Rece
|
存储 Rust 算法
TIKV 源码分析(一)raft-rs 组件
Raft 是分布式领域中应用非常广泛的一种共识算法,相比于此类算法的鼻祖 Paxos,具有更简单、更容易理解和实现的特点。TiKV 依赖的周边库 raft-rs 是参照 ETCD 的 RAFT 库编写的 RUST 版本。本文不会详细介绍 RAFT 协议的原理或者实现,而是利用 raft-rs 的示例程序来讲解 raft-rs 如何使用。Public API 简述RawNode 结构体TIKV 的 
TIKV 源码分析(一)raft-rs 组件
|
存储 安全
【Zookeeper】源码分析之持久化(二)之FileSnap
 前篇博文已经分析了FileTxnLog的源码,现在接着分析持久化中的FileSnap,其主要提供了快照相应的接口。
133 0
|
数据库
【Zookeeper】源码分析之持久化(三)之FileTxnSnapLog
前面分析了FileSnap,接着继续分析FileTxnSnapLog源码,其封装了TxnLog和SnapShot,其在持久化过程中是一个帮助类。
128 0
|
存储 NoSQL 数据库
MongoDB Sharded cluster架构原理
为什么需要Sharded cluster? MongoDB目前3大核心优势:『灵活模式』+ 『高可用性』 + 『可扩展性』,通过json文档来实现灵活模式,通过复制集来保证高可用,通过Sharded cluster来保证可扩展性。 当MongoDB复制集遇到下面的业务场景时,你就需要考虑使用Sh