1. 程式人生 > >Redis實現分散式儲存

Redis實現分散式儲存

Memcache是在服務端實現分片的分散式的快取系統,而Redis是基於Master-Slave(主從),如果想把Reids做成分散式快取,就要多做幾套Master-Slave,每套Master-Slave完成各自的容災處理,另外,Redis只能在客戶端完成分片。

Redis有中語言的客戶端,其中基於Java語言的客戶端叫做Jedis,Jedis客戶端已經為Redis實現了分散式儲存。下面分別介紹了Jedis分散式儲存的簡單使用以及Spring與Jedis的整合使用。

一、Jedis分散式儲存舉例

package com.ghs.test;

import java.util.ArrayList
; import java.util.List; import org.apache.commons.pool2.impl.GenericObjectPoolConfig; import org.junit.Before; import org.junit.Test; import redis.clients.jedis.JedisShardInfo; import redis.clients.jedis.ShardedJedis; import redis.clients.jedis.ShardedJedisPool; public class TestShardJedis { ShardedJedisPool pool = null;
ShardedJedis jedis = null; @Before public void setup(){ JedisShardInfo shardInfo1 = new JedisShardInfo("192.168.1.108"); JedisShardInfo shardInfo2 = new JedisShardInfo("..."); List<JedisShardInfo> shardInfos = new ArrayList<JedisShardInfo>(); shardInfos.add
(shardInfo1); shardInfos.add(shardInfo2); pool = new ShardedJedisPool(new GenericObjectPoolConfig(), shardInfos); } @Test public void testShard(){ jedis = pool.getResource(); //CRUD jedis.set("name", "zhangsan"); System.out.println(jedis.get("name")); jedis.del("name"); //釋放物件 pool.returnResource(jedis); } }

二、Spring+Jedis實現分散式儲存

spring中的配置:
這裡只是把示例一中JedisShardInfo和ShardJedisPool的示例交給Sping容器管理。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="
    http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-4.1.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context-4.1.xsd">

    <context:property-placeholder location="classpath:redis.properties" />
    <bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
        <property name="maxActive" value="${redis.pool.maxActive}" />
        <property name="maxIdle" value="${redis.pool.maxIdle}" />
        <property name="maxWait" value="${redis.pool.maxWait}" />
        <property name="testOnBorrow" value="${redis.pool.testOnBorrow}" />
    </bean>
    <bean id="shardedJedisPool" class="redis.clients.jedis.ShardedJedisPool">
        <constructor-arg index="0" ref="jedisPoolConfig" />
        <constructor-arg index="1">
            <list>
                <bean class="redis.clients.jedis.JedisShardInfo">
                    <constructor-arg index="0" value="${redis1.ip}" />
                    <constructor-arg index="1" value="${redis.port}"
                        type="int" />
                </bean>
                <bean class="redis.clients.jedis.JedisShardInfo">
                    <constructor-arg index="0" value="${redis2.ip}" />
                    <constructor-arg index="1" value="${redis.port}" type="int" />
                </bean>
            </list>
        </constructor-arg>
    </bean>
</beans>

對訪問操作進行封裝:

package com.ghs.test;

import java.util.List;

import javax.annotation.Resource;

import redis.clients.jedis.ShardedJedis;
import redis.clients.jedis.ShardedJedisPool;

public class SpringRedisClient implements IRedisClient{

    @Resource(name="shardedJedisPool")
    private ShardedJedisPool shardedJedisPool;

    private ShardedJedis getResource(){
        return shardedJedisPool.getResource();
    }

    @Override
    public String set(String key,String value){
        return getResource().set(key,value);
    }
    @Override
    public String get(String key){
        return getResource().get(key);
    }

    @Override
    public Long del(String key) {
        return getResource().del(key);
    }

    @Override
    public Long lpush(String key, String... strings) {
        return getResource().lpush(key, strings);
    }

    @Override
    public Long rpush(String key, String... strings) {
        return getResource().rpush(key, strings);
    }

    @Override
    public List<String> lrange(String key, int start, int end){
        return getResource().lrange(key, start,end);
    }

    //…………………………
}

三、Jedis分散式儲存實現原理

1、JedisShardInfo類
這個類封裝了Redis主機的一些基本資訊:

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

最重要的是它的父類中有一個weight欄位,作為本Redis伺服器的權值。
這個類還有一個繼承自父類的方法createResource(),用來生成這個Redis伺服器對應的Jedis物件,即往Redis伺服器存取資料的物件。

對一致性雜湊演算法熟悉以後,對Sharded類的理解就不難了,通過這個類來為每個分片建立虛擬節點,為每次操作獲取分片。
Sharded中的三個欄位,nodes是用來模擬一致性雜湊演算法用的;algo是用來對字串產生雜湊值的hash函式,這裡預設的是murmurhash,這個演算法的隨機分佈特徵表現比較好;resources這個map是用來儲存JedisShardInfo與其對應的Jedis類之間的對映關係。

  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 void initialize(List<S> shards) {
    nodes = new TreeMap<Long, S>();

    for (int i = 0; i != shards.size(); ++i) {
      final S shardInfo = shards.get(i);
      if (shardInfo.getName() == null) for (int n = 0; n < 160 * shardInfo.getWeight(); n++) {
        nodes.put(this.algo.hash("SHARD-" + i + "-NODE-" + n), shardInfo);
      }
      else for (int n = 0; n < 160 * shardInfo.getWeight(); n++) {
        nodes.put(this.algo.hash(shardInfo.getName() + "*" + shardInfo.getWeight() + n), shardInfo);
      }
      resources.put(shardInfo, shardInfo.createResource());
    }
  }

在for迴圈中,遍歷主機列表(shards.get(i)),之後對每個主機按照單權重160的比例計算shard值,將shard值和主機資訊(shardInfo)放到nodes中,將主機資訊(shardInfo)和其對應的連結資源(Jedis)對映放入到resources中。

Weight是權重,用於調節單個主機被對映值個數,如果weight為1,那麼當前主機將被對映為160個值,weight為2,當前主機將被對映為320個值,因此weight為2的節點被訪問到的概率就會高一些。

遍歷list中的每一個shardInfo,將其權重weight*160生成n,然後用名字或者編號來生成n個雜湊值(這個是為了保證雜湊演算法的平衡性而生成的虛擬節點),然後將其和本shardInfo的對應關係儲存到treemap裡面(這是在模擬一致性雜湊演算法中將虛擬節點對映到環上的操作),最後將shardInfo與對應的Jedis類的對映關係儲存到resources裡面。

   //獲取分片
    public S getShardInfo(byte[] key) {
    SortedMap<Long, S> tail = nodes.tailMap(algo.hash(key));
    if (tail.isEmpty()) {
      return nodes.get(nodes.firstKey());
    }
    return tail.get(tail.firstKey());
  }

  public S getShardInfo(String key) {
    return getShardInfo(SafeEncoder.encode(getKeyTag(key)));
  }

首先根據傳入的key按照hash演算法(預設為murmurhash)取得其value,然後用這個value到treemap中找key大於前面生成的value值的第一個鍵值對,這個鍵值對的value既是對應的shardedInfo。