分布式缓存Memcached技术讲解以及实现

一、安装memcached
Memcached本身不支持分布式,需要客户端实现分布式:

Memcached需要把相应的jar包放到Tomcat的lib文件中,下载地址:

1.安装Memcached
如果你是用Ubuntu,直接
sudo apt-get install memcached
就可以了,我用的是Ubuntu的13.04版本。当然,这一般来说不是最新的版本,源更新没有官网下载的那么快,所以你也可以下载相应的tar.gz文件
安装Memcached需要下载两个文件,一个是Memcached,另外一个是libevent,因为Memcached的工作机制是基于libevent,所以需要安装。这是安装Memcached必须的文件
libevent官网: http://libevent.org/
memcached官网: http://www.memcached.org/
版本:v1.4.20


2.先安装libevent:
# tar -zxvf libevent-2.0.21-stable.tar.gz
# cd libevent-2.0.21-stable
# ./configure –prefix=/usr
# make
# make install

3.测试libevent是否安装成功:

# ls -al /usr/lib | grep libevent

看到libevent,成功

4.安装memcached,同时需要安装中指定libevent的安装位置:
# cd /tmp
# tar -zxvf memcached-1.4.20.tar.gz
# cd memcached-1.4.20
# ./configure –with-libevent=/usr
# make
# make install
如果中间出现报错,请仔细检查错误信息,按照错误信息来配置或者增加相应的库或者路径。
安装完成后会把memcached放到 /usr/local/bin/memcached 
可以使用whereis memcached来查看是否安装成功

安装成功。

5.开启memcached
# memcached -d -m 512 -p 12000 -u root -l localhost -c 256 -P /var/run/memcached.pid 
-d 以守护进程开启 -m 分配512MB内存 -p 端口号为12000 -l IP为localhost,可以指定任意IP(如192.168.1.100) -最大连接数256(默认1024)

6. 测试是否安装成功
# telnet localhost 12000
输入指定的ip和端口
因为Memcached就是一个巨大的hash表,所以当然是存储和显示键对值
set key1 0 60 8
richlu
上面就是设置了一个key1的key,flags值为0,60s过期,8个字节,value为richlu的值

 

存储命令的格式:

1
2
<command name> <key> <flags> <exptime> <bytes>
<data block>

参数说明如下:

<command name> set/add/replace
<key> 查找关键字
<flags> 客户机使用它存储关于键值对的额外信息
<exptime> 该数据的存活时间,0表示永远
<bytes> 存储字节数
<data block> 存储的数据块(可直接理解为key-value结构中的value)
设置完之后可以拿回出来
get key1功
总的测试就是:
telnet localhost 12000
set key1 0 60 8
richlu
get key1
quit
这是详细说明Memcached的常用命令参考:
http://www.cnblogs.com/jeffwongishandsome/archive/2011/11/06/2238265.html

二、在java中使用memcached
在这里我使用的是memcached client for java。另外两种方式是XMemcached和spymecached,这里不做详细介绍,有兴趣的自行查找。
首先需要一个jar包 使用memcached的jar包是:java_memcached-release_2.5.3.jar
会用git的人就用git吧,不会用的话就下载zip文件,解压后用ant来构造jar包吧
测试代码:
package com.rich.test;

import com.meetup.memcached.MemcachedClient;
import com.meetup.memcached.SockIOPool;

public class MemcacheTest {

protected static MemcachedClient mcc = new MemcachedClient();
 
     //设置缓存服务器列表,当使用分布式缓存时,可以指定多个缓存服务器
     static {
      String[] servers =
       {
        "localhost:12000",
        // "localhost:12111",
       };
 
      //创建一个SockIOPool实例
     SockIOPool pool = SockIOPool.getInstance();
     pool.setServers(servers);
     pool.setNagle(false);
     pool.setSocketTO(3000);
     pool.setSocketConnectTO(0);
 
     pool.initialize();
     }
 public static void main(String[] args) {

      mcc.set("foo", "This is a test String");
      String bar = mcc.get("foo").toString();
      System.out.println(">>>" + bar);
 }
}
另外两种方式可以参考:http://jiaxiaoyuan1204.blog.163.com/blog/static/65553152010520112614157/

至此,我们应该算是能够真正使用Memcached

三、一致性哈希算法

memcached本身是集中式的缓存系统,要搞多节点分布,只能通过客户端实现。

memcached的分布算法一般有两种选择:


1、hash模余算法:

     根据hash(key)的结果,模连接数的余数决定存储到哪个节点(键的整数哈希值,根据服务器个数取余来选定服务器节点),也就是hash(key)% sessions.size(),这个余数计算的方法简单,数据的分散性也相当优秀。

    但也有其缺点。那就是当添加或移除服务器时,缓存重组的代价相当巨大。添加/删除服务器后(特别是某台服务器down机之后),余数就会产生巨变,这样就无法保证获取时计算的服务器节点与保存时相同,从而影响缓存的命中率——造成原有的缓存数据将大规模失效。


2、一致性hash(Consistent Hashing)

一致性hash(Consistent Hashing)如下所示:首先求出memcached服务器(节点)的哈希值,并将其配置到0~2^32的圆(continuum)上。然后用同样的方法 求出存储数据的键的哈希值,并映射到圆上。然后从数据映射到的位置开始顺时针查找,将数据保存到找到的第一个服务器上。如果超过232仍然找不到服务器, 就会保存到第一台memcached服务器上。

//不太懂一致性hash中虚拟节点的用法

答:虚拟节点是为了能让服务器节点能够均匀分布在圆上,有利于负载均衡。其实现是复制服务器节点,增加服务器节点的个数。


获取hashCode和hash散列值
import java.io.UnsupportedEncodingException;  
import java.nio.ByteBuffer;  
import java.nio.ByteOrder;  
import java.security.MessageDigest;  
import java.security.NoSuchAlgorithmException;  
import java.util.*;  
  
public class ConsistencyHash {  
    private TreeMap<Long,Object> nodes = null;  
    //真实服务器节点信息  
    private List<Object> shards = new ArrayList();  
    //设置虚拟节点数目  
    private int VIRTUAL_NUM = 4;  
  
    /** 
     * 初始化一致环 
     */  
    public void init() {  
         shards.add("192.168.0.0-服务器0");  
         shards.add("192.168.0.1-服务器1");  
         shards.add("192.168.0.2-服务器2");  
         shards.add("192.168.0.3-服务器3");  
         shards.add("192.168.0.4-服务器4");  
  
        nodes = new TreeMap<Long,Object>();  
        for(int i=0; i<shards.size(); i++) {  
            Object shardInfo = shards.get(i);  
            for(int j=0; j<VIRTUAL_NUM; j++) {  
                nodes.put(hash(computeMd5("SHARD-" + i + "-NODE-" + j),j), shardInfo);  
            }  
        }  
    }  
  
    /** 
     * 根据key的hash值取得服务器节点信息 
     * @param hash 
     * @return 
     */  
    public Object getShardInfo(long hash) {  
        Long key = hash;  
        SortedMap<Long, Object> tailMap=nodes.tailMap(key);  
        if(tailMap.isEmpty()) {  
            key = nodes.firstKey();  
        } else {  
            key = tailMap.firstKey();  
        }  
        return nodes.get(key);  
    }  
  
    /** 
     * 打印圆环节点数据 
     */  
     public void printMap() {  
         System.out.println(nodes);  
     }  
  
    /** 
     * 根据2^32把节点分布到圆环上面。 
     * @param digest 
     * @param nTime 
     * @return 
     */  
      public long hash(byte[] digest, int nTime) {  
        long rv = ((long) (digest[3+nTime*4] & 0xFF) << 24)  
                | ((long) (digest[2+nTime*4] & 0xFF) << 16)  
                | ((long) (digest[1+nTime*4] & 0xFF) << 8)  
                | (digest[0+nTime*4] & 0xFF);  
  
        return rv & 0xffffffffL; /* Truncate to 32-bits */  
      }  
  
    /** 
     * Get the md5 of the given key. 
     * 计算MD5值 
     */  
     public byte[] computeMd5(String k) {  
        MessageDigest md5;  
        try {  
            md5 = MessageDigest.getInstance("MD5");  
        } catch (NoSuchAlgorithmException e) {  
            throw new RuntimeException("MD5 not supported", e);  
        }  
        md5.reset();  
        byte[] keyBytes = null;  
        try {  
            keyBytes = k.getBytes("UTF-8");  
        } catch (UnsupportedEncodingException e) {  
            throw new RuntimeException("Unknown string :" + k, e);  
        }  
  
        md5.update(keyBytes);  
        return md5.digest();  
     }  
  
     public static void main(String[] args) {  
         Random ran = new Random();  
         ConsistencyHash hash = new ConsistencyHash();  
         hash.init();  
         hash.printMap();  
         //循环50次,是为了取50个数来测试效果,当然也可以用其他任何的数据来测试  
         for(int i=0; i<50; i++) {  
             System.out.println(hash.getShardInfo(hash.hash(hash.computeMd5(String.valueOf(i)),ran.nextInt(hash.VIRTUAL_NUM))));  
         }  
   }  
  
}  

以上是memcached的散列实现,当然我们只要调用它的jar包,即java_memcached-release_2.5.3.jar包,按照上面的使用方法就已经实现了一致性hash了。
关于Memcached client for java的api可以参考: http://blog.csdn.net/qqiabc521/article/details/6438429







  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值