Memcache总结

Memcache介绍

Memcached是一个自由开源的,高性能,分布式内存对象缓存系统。
Memcached是以LiveJournal旗下Danga Interactive公司的Brad Fitzpatric为首开发的一款软件。
Memcached是一种基于内存的key-value存储,用来存储小块的任意数据(字符串、对象)。

安装

yum install memcached
rpm -ivh memcached-1.4.4-3.el6.i686.rpm
开启服务:service memcached start

Memcached连接

telnet HOST PORT

END     结束行
quit    退出

Memcached基本使用

Memcached存储

  1. set
    Memcached set 命令用于将 value(数据值) 存储在指定的 key(键) 中。
    如果set的key已经存在,该命令可以更新该key所对应的原来的数据,也就是实现更新的作用。
    语法:

    set key flags exptime bytes [noreply] 
    value 
    

    参数:
    key:键值 key-value 结构中的 key,用于查找缓存值。
    flags:可以包括键值对的整型参数,客户机使用它存储关于键值对的额外信息 。
    exptime:在缓存中保存键值对的时间长度(以秒为单位,0 表示永远)
    bytes:在缓存中存储的字节数
    noreply(可选): 该参数告知服务器不需要返回数据
    value:存储的值(始终位于第二行)(可直接理解为key-value结构中的value)

  2. add
    Memcached add 命令用于将 value(数据值) 存储在指定的 key(键) 中。
    如果 add 的 key 已经存在,则不会更新数据,之前的值将仍然保持相同,并且您将获得响应 NOT_STORED。
    语法:

    add key flags exptime bytes [noreply]
    value
    

    参数意义同上。

  3. replace
    Memcached replace 命令用于替换已存在的 key(键) 的 value(数据值)。
    如果 key 不存在,则替换失败,并且您将获得响应 NOT_STORED。
    语法:

    replace key flags exptime bytes [noreply]
    value
    
  4. append
    Memcached append 命令用于向已存在 key(键) 的 value(数据值) 后面追加数据 。
    语法:

    append key flags exptime bytes [noreply]
    value
    
  5. prepend
    Memcached prepend 命令用于向已存在 key(键) 的 value(数据值) 前面追加数据 。
    语法:

    prepend key flags exptime bytes [noreply]
    value
    
  6. CAS
    Memcached CAS(Check-And-Set 或 Compare-And-Swap) 命令用于执行一个”检查并设置”的操作
    它仅在当前客户端最后一次取值后,该key 对应的值没有被其他客户端修改的情况下, 才能够将值写入。
    检查是通过cas_token参数进行的, 这个参数是Memcach指定给已经存在的元素的一个唯一的64位值。
    语法:

    cas key flags exptime bytes unique_cas_token [noreply]
    value
    

    参数:
    key:键值 key-value 结构中的 key,用于查找缓存值。
    flags:可以包括键值对的整型参数,客户机使用它存储关于键值对的额外信息 。
    exptime:在缓存中保存键值对的时间长度(以秒为单位,0 表示永远)
    bytes:在缓存中存储的字节数
    unique_cas_token通过 gets 命令获取的一个唯一的64位值。
    noreply(可选): 该参数告知服务器不需要返回数据
    value:存储的值(始终位于第二行)(可直接理解为key-value结构中的value)

Memcached查找

  1. get
    Memcached get 命令获取存储在 key(键) 中的 value(数据值) ,如果 key 不存在,则返回空。
    语法:

    get key1 key2 key3
    

    参数:
    key:键值 key-value 结构中的 key,用于查找缓存值。

  2. gets
    Memcached gets 命令获取带有 CAS 令牌存 的 value(数据值) ,如果 key 不存在,则返回空。
    语法:

    gets key1 key2 key3
    
  3. delete
    Memcached delete 命令用于删除已存在的 key(键)。
    语法:

    delete key [noreply]
    

    参数:
    key:键值 key-value 结构中的 key,用于查找缓存值。
    noreply(可选): 该参数告知服务器不需要返回数据。

  4. incr/decr
    Memcached incr 与 decr 命令用于对已存在的 key(键) 的数字值进行自增或自减操作。
    incr 与 decr 命令操作的数据必须是十进制的32位无符号整数。
    如果 key 不存在返回 NOT_FOUND,如果键的值不为数字,则返回 CLIENT_ERROR,其他错误返回 ERROR。
    语法:

    incr key increment_value
    

    参数:
    key:键值 key-value 结构中的 key,用于查找缓存值。
    increment_value: 增加的数值。

Memcached统计

  1. stats
    Memcached stats 命令用于返回统计信息例如 PID(进程号)、版本号、连接数等。
    语法:

    stats
    
  2. stats items
    Memcached stats items 命令用于显示各个 slab 中 item 的数目和存储时长(最后一次访问距离现在的秒数)。
    语法:

    stats items
    
  3. stats slabs
    Memcached stats slabs 命令用于显示各个slab的信息,包括chunk的大小、数目、使用情况等。
    语法:

    stats slabs
    
  4. stats sizes
    Memcached stats sizes 命令用于显示所有item的大小和个数。
    该信息返回两列,第一列是 item 的大小,第二列是 item 的个数。
    语法:

    stats sizes
    
  5. flush_all
    Memcached flush_all 命令用于用于清理缓存中的所有 key=>value(键=>值) 对。
    该命令提供了一个可选参数 time,用于在制定的时间后执行清理缓存操作。
    语法:

    flush_all [time] [noreply]
    

Java 连接 Memcached 服务

        /**
         * Java 连接 Memcached操作
         */
        public class MemcachedJava {

            /**
             * set操作,存储,key存在则更新
             */
            @Test
            public void set(){
                try {
                    //连接MemcachedClient服务
                    MemcachedClient memcachedClient = new MemcachedClient(new InetSocketAddress("192.168.134.10", 11211));
                    System.out.println("连接成功!");
                    //存储数据
                    Future future = memcachedClient.set("testSet", 900, "test2");
                    //查看存储状态
                    System.out.println("存储状态: " + future.get());
                    //输出值,使用get获取数据
                    System.out.println("testSet的值是: " + memcachedClient.get("testSet"));
                    //关闭连接
                    memcachedClient.shutdown();
                } catch (IOException e) {
                    e.printStackTrace();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (ExecutionException e) {
                    e.printStackTrace();
                }

            }

            /**
             * add操作,添加,key存在,添加失败
             */
            @Test
            public void add(){
                try {
                    //连接Memcached服务
                    MemcachedClient memcachedClient = new MemcachedClient(new InetSocketAddress("192.168.134.10", 11211));
                    System.out.println("连接成功!");
                    //添加数据
                    Future future = memcachedClient.set("testAdd", 900, "test3");
                    //打印状态
                    System.out.println("存储状态: " + future.get());
                    //输出值
                    System.out.println("testAdd的值: " + memcachedClient.get("testAdd"));

                    //添加
                    Future future2 = memcachedClient.add("testAdd", 900, "memcached");
                    System.out.println("添加状态: " + future2.get());

                    //添加新key
                    future2 = memcachedClient.add("newAdd", 900, "newKey");
                    System.out.println("添加新key状态: " + future2.get());
                    //输出新value
                    System.out.println("newAdd的值: " + memcachedClient.get("newAdd"));

                    //关闭连接
                    memcachedClient.shutdown();
                } catch (IOException e) {
                    e.printStackTrace();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (ExecutionException e) {
                    e.printStackTrace();
                }
            }

            /**
             * replace操作,替换
             */
            @Test
            public void replace(){
                try {
                    MemcachedClient memcachedClient = new MemcachedClient(new InetSocketAddress("192.168.134.10", 11211));
                    System.out.println("连接成功!");
                    //添加键值对
                    Future future = memcachedClient.set("testReplace", 900, "test4");
                    System.out.println("存储状态: " + future.get());
                    //获取键值对应的值
                    System.out.println("testReplace的值: " + memcachedClient.get("testReplace"));

                    //替换value
                    future = memcachedClient.replace("testReplace", 900, "replaceSuccess");
                    //输出替换状态
                    System.out.println("替换状态: " + future.get());
                    //获取替换后的值
                    System.out.println("替换后的值: " + memcachedClient.get("testReplace"));

                    //关闭连接
                    memcachedClient.shutdown();
                } catch (IOException e) {
                    e.printStackTrace();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (ExecutionException e) {
                    e.printStackTrace();
                }
            }

            /**
             * append操作,value尾追加
             */
            @Test
            public void append(){
                try {
                    MemcachedClient memcachedClient = new MemcachedClient(new InetSocketAddress("192.168.134.10",11211));
                    System.out.println("连接成功!");
                    //添加数据
                    Future future = memcachedClient.set("testAppend", 900, "test5");
                    //输出存储状态及value
                    System.out.println("存储状态: " + future.get() + ", " + "testAppend的值: " + memcachedClient.get("testAppend"));

                    //对key进行追加操作
                    Future future2 = memcachedClient.append("testAppend", " append success");
                    System.out.println("追加状态: " + future2.get() + ", " + "追加后testAppend的值" + memcachedClient.get("testAppend"));

                    //关闭连接
                    memcachedClient.shutdown();
                } catch (IOException e) {
                    e.printStackTrace();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (ExecutionException e) {
                    e.printStackTrace();
                }
            }

            /**
             * prepend操作,value头追加
             */
            @Test
            public void prepend(){
                try {
                    MemcachedClient memcachedClient = new MemcachedClient(new InetSocketAddress("192.168.134.10", 11211));
                    System.out.println("连接成功!");
                    //添加数据
                    Future future = memcachedClient.set("testPrepend", 900, "test6");
                    System.out.println("存储状态: " + future.get() + ", " + "testPrepend的值: " + memcachedClient.get("testPrepend"));

                    //对key进行追加
                    Future future2 = memcachedClient.prepend("testPrepend", "begin ");
                    System.out.println("追加状态: " + future2.get() + ", " + "追加后的testPrepend的值: " + memcachedClient.get("testPrepend"));

                    //关闭连接
                    memcachedClient.shutdown();
                } catch (IOException e) {
                    e.printStackTrace();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (ExecutionException e) {
                    e.printStackTrace();
                }
            }

            /**
             * CAS操作
             */
            @Test
            public void cas(){
                try {
                    MemcachedClient memcachedClient = new MemcachedClient(new InetSocketAddress("192.168.134.10", 11211));
                    System.out.println("连接成功!");
                    //添加数据
                    Future<Boolean> future = memcachedClient.set("testCAS", 900, "test7");
                    System.out.println("存储状态: " + future.get() + ", " + "testCAS的值: " + memcachedClient.get("testCAS"));

                    //通过gets方法获取CAS token
                    CASValue<Object> casValue = memcachedClient.gets("testCAS");
                    System.out.println("CAS token: " + casValue);

                    //使用cas方法更新数据
                    CASResponse casResponse = memcachedClient.cas("testCAS", casValue.getCas(), " cas success ");
                    System.out.println("CAS 响应状态: " + casResponse);
                    //输出cas更新后的值
                    System.out.println("cas后testCAS的值: " + memcachedClient.get("testCAS"));

                    //关闭连接
                    memcachedClient.shutdown();
                } catch (IOException e) {
                    e.printStackTrace();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (ExecutionException e) {
                    e.printStackTrace();
                }
            }

            /**
             * delete操作
             */
            @Test
            public void delete(){
                try {
                    MemcachedClient memcachedClient = new MemcachedClient(new InetSocketAddress("192.168.134.10", 11211));
                    System.out.println("连接成功!");
                    //存储数据
                    Future<Boolean> future = memcachedClient.set("testDelete", 900, "test8");
                    System.out.println("存储状态: " + future.get() + ", " + "testDelete的值: " + memcachedClient.get("testDelete"));

                    //删除key
                    Future<Boolean> future2 = memcachedClient.delete("testDelete");
                    System.out.println("删除状态: " + future2.get() + ", " + "删除后testDelete的值: " + memcachedClient.get("testDelete"));

                    //关闭连接
                    memcachedClient.shutdown();
                } catch (IOException e) {
                    e.printStackTrace();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (ExecutionException e) {
                    e.printStackTrace();
                }
            }

            /**
             * Incr/Decr操作
             */
            @Test
            public void incrOrDecr(){
                try {
                    MemcachedClient memcachedClient = new MemcachedClient(new InetSocketAddress("192.168.134.10", 11211));
                    System.out.println("连接成功!");
                    //添加数字值
                    Future<Boolean> future = memcachedClient.set("number", 900, "66");
                    //输出状态及值
                    System.out.println("存储状态: " + future.get() + ", " + "number的值: " + memcachedClient.get("number"));

                    //自增并输出
                    System.out.println("自增后number的值: " + memcachedClient.incr("number", 8));

                    //自减并输出
                    System.out.println("自减后number的值: " + memcachedClient.decr("number", 6));

                    //关闭连接
                    memcachedClient.shutdown();
                } catch (IOException e) {
                    e.printStackTrace();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (ExecutionException e) {
                    e.printStackTrace();
                }
            }
        }

memcached 的内存管理与删除机制

内存碎片化

如果用 c 语言直接 malloc,free 来向操作系统申请和释放内存时, 在不断的申请和释放过程中,形成了一些很小的内存片断,无法再利用. 这种空闲,但无法利用内存的现象,称为内存的碎片化。

slab allocator

memcached 用 slab allocator 机制来管理内存。 slab allocator 原理: 预告把内存划分成数个 slab class 仓库。(每个 slab class 大小 1M)各仓库,切分成不同尺寸的小块(chunk)。需要存内容时,判断内容的大小,为其选取合理的仓库。
memcached 中保存着 slab class 内空闲 chunk 的列表, 根据该列表选择空的 chunk, 然后将数
据缓存于其中。
由于 slab allocator 机制中, 分配的 chunk 的大小是”固定”的, 因此, 对于特定的 item,可能造
成内存空间的浪费。
开发者可以对网站中缓存中的 item 的长度进行统计,并制定合理的 slab class 中的 chunk 的大
小. 目前还不能自定义 chunk 的大小,但可以通过参数来调整各 slab class 中 chunk大小的增长速度. 即增长因子, grow factor!

grow factor 调优

memcached 在启动时可以通过­f 选项指定 Growth Factor 因子, 并在某种程度上控制 slab 之
间的差异. 默认值为 1.25. 但是,在该选项出现之前,这个因子曾经固定为 2,称为”powers of 2” 策略。

memcached 的过期数据惰性删除

  1. 当某个值过期后,并没有从内存删除, 因此,stats 统计时, curr_item 有其信息。
  2. 当某个新值去占用他的位置时,当成空 chunk 来占用。
  3. 当 get 值时,判断是否过期,如果过期,返回空,并且清空, curr_item 就减少了。

这个过期,只是让用户看不到这个数据而已,并没有在过期的瞬间立即从内存删除. 这个称为 lazy expiration, 惰性失效。优点:节省了 cpu 时间和检测的成本。

lru 删除机制

lru: least recently used 最近最少使用
fifo: first in ,first out
当某个单元被请求时,维护一个计数器,通过计数器来判断最近谁最少被使用. 就把谁 t 出。

些参数限制

key 的长度: 250 字节, (二进制协议支持 65536 个字节)。
value 的限制: 1m, 一般都是存储一些文本,这个值足够了。
内存的限制: 32 位下最大设置到 2g。

Memcached使用场景

memcached 的最常用场景是利用其”读取快”来保护数据库,防止频率读取数据库。 也有的项目中,利用其”存储快”的特点来实现主从数据库的消息同步。

    public class CacheInterceptor {

            @Resource
            private MemcachedClient memCachedClient;

            // 时间 缓存时间
            public static final int TIMEOUT = 360000;// 秒

            private int expiry = TIMEOUT;

            // 配置环绕方法
            public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
                // 去Memcached中看看有没有我们的数据 包名+ 类名 + 方法名 + 参数(多个)
                String cacheKey = getCacheKey(pjp);
                System.out.println(cacheKey);
                // 如果Memcached 连接不上
                if (memCachedClient.getStats().isEmpty()) {
                    System.out.println("Memcached服务器连接不上");
                    return pjp.proceed();
                }

                // 返回值
                if (null == memCachedClient.get(cacheKey)) {
                    // 回Service
                    Object proceed = pjp.proceed();
                    // 先放到Memcached中一份
                    memCachedClient.set(cacheKey, expiry, proceed);
                }
                return memCachedClient.get(cacheKey);
            }

            // 后置由于数据库数据变更 清理get*
            public void doAfter(JoinPoint jp) {
                // 包名+ 类名 + 方法名 + 参数(多个) 生成Key
                // 包名+ 类名
                String packageName = jp.getTarget().getClass().getName();

                // 包名+ 类名 开始的 都清理
                Map<String, Object> keySet = MemCachedUtil.getKeySet(memCachedClient);
                //
                Set<Entry<String, Object>> entrySet = keySet.entrySet();
                // 遍历
                for (Entry<String, Object> entry : entrySet) {
                    if (entry.getKey().startsWith(packageName)) {
                        memCachedClient.delete(entry.getKey());
                    }
                }
            }

            // 包名+ 类名 + 方法名 + 参数(多个) 生成Key
            public String getCacheKey(ProceedingJoinPoint pjp) {
                // StringBuiter
                StringBuilder key = new StringBuilder();
                // 包名+ 类名
                String packageName = pjp.getTarget().getClass().getName();
                key.append(packageName);
                // 方法名
                String methodName = pjp.getSignature().getName();
                key.append(".").append(methodName);

                // 参数(多个)
                Object[] args = pjp.getArgs();

                ObjectMapper om = new ObjectMapper();

                om.setSerializationInclusion(Inclusion.NON_NULL);

                for (Object arg : args) {

                    // 流
                    StringWriter str = new StringWriter();

                    // 对象转Json 写的过程 Json是字符串流
                    try {
                        om.writeValue(str, arg);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    // 参数
                    key.append(".").append(str);
                }

                return key.toString();
            }

            public void setExpiry(int expiry) {
                this.expiry = expiry;
            }

        }

分布式集群算法

memcached 节点之间,是不互相通信的,memcached 的分布式,要靠用户去设计算法,把数据分布在多个 memcached 节点中。

取模算法

N 个节点要,从 0->N-1 编号. key 对 N 取模,余 i,则 key 落在第 i 台服务器上。

服务器越多, 则 down 机的后果越严重!

一致性哈希算法

把各服务器节点映射放在钟表的各个时刻上, 把 key 也映射到钟表的某个时刻上. 该 key 沿钟表顺时针走,碰到的第 1 个节点即为该 key 的存储节点。
分布[0,2^32-1]的数字,当某个节点 down 后,只影响该节点顺时针之后的 1 个节点,而其他节点不受影响。
虚拟节点即—-N 个真实节点,把每个真实节点映射成 M 个虚拟节点, 再把 M*N 个虚拟节点, 散列在圆环上. 各真实节点对应的虚拟节点相互交错分布。这样,某真实节点 down 后,则把其影响平均分担到其他所有节点上。

        /**
         *  一致性Hash Java算法
         * @param <T>
         */
        public class ConsistentHash<T> {

            private final HashFunction hashFunction;
            //节点复制因子
            private final int numberOfReplicas; 
            //存储虚拟节点的hash值到真实节点的映射
            private final SortedMap<Long,T> circle = new TreeMap<Long,T>();

            public ConsistentHash(HashFunction hashFunction, int numberOfReplicas,Collection<T> nodes) {
                this.hashFunction = hashFunction;
                this.numberOfReplicas = numberOfReplicas;
                for (T t : nodes) {
                    add(t);
                }
            }

            private void add(T t) {
                for (int i = 0; i < numberOfReplicas; i++) {
                    //虚拟node均衡分布在环上,数据存储在顺时针方向的虚拟node上
                    circle.put(hashFunction.hash(t.toString()), t);
                }
            }

            public void remove(T t){
                for (int i = 0; i < numberOfReplicas; i++) {
                    circle.remove(hashFunction.hash(t.toString()));
                }
            }

            /**
             * 获取最近的顺时针节点
             */
            public T get(Object key){
                if (circle.isEmpty()) {
                    return null;
                }
                //获取hash值
                long hashcode = hashFunction.hash((String)key);
                //数据映射在两台虚拟机器所在环之间
                if(!circle.containsKey(hashcode)){
                    SortedMap<Long, T> tailMap = circle.tailMap(hashcode);
                    hashcode = tailMap.isEmpty() ? circle.firstKey() : tailMap.firstKey();
                }
                return circle.get(hashcode);
            }

            public long getSize(){
                return circle.size();
            }

            /**
             * 查看MD5算法生成的hashCode
             */
            public void balance(){
                //获取treeMap中所有的key
                Set<Long> keySet = circle.keySet();
                //排序
                SortedSet<Long> sortedSet = new TreeSet<>(keySet);
                for (Long hashCode : sortedSet) {
                    System.out.println(hashCode);
                }

                //查看用MD5算法生成两个hashCode的差值
                Iterator<Long> iterator = sortedSet.iterator();
                Iterator<Long> iterator2 = sortedSet.iterator();
                if (iterator2.hasNext()) {
                    iterator2.next();
                    long keyPre, keyAfter;
                    while (iterator.hasNext() && iterator2.hasNext()) {
                        keyPre = iterator.next();
                        keyAfter = iterator2.next();
                        System.out.println(keyAfter - keyPre);
                    }
                }
            }

            public static void main(String[] args) {
                Set<String> sets = new HashSet<String>();
                sets.add("a");
                sets.add("b");
                sets.add("c");

                ConsistentHash<String> consistentHash = new ConsistentHash<String>(new HashFunction(), 2, sets);
                consistentHash.add("d");

                System.out.println("hash环大小: " + consistentHash.getSize());
                System.out.println("每个节点的位置: ");
                consistentHash.balance();
            }
        }

        /**
         *  实现一致性hash算法中的hash函数,使用MD5保证一致性hash平衡性
         */
        public class HashFunction {
            private MessageDigest md5 = null;

            public long hash(String key){
                if (null == md5) {
                    try {
                        md5 = MessageDigest.getInstance("MD5");
                    } catch (NoSuchAlgorithmException e) {
                        e.printStackTrace();
                    }
                }
                md5.reset();
                md5.update(key.getBytes());
                byte[] digest = md5.digest();
                //hash函数实现
                long result = ((long) (digest[3] & 0xff ) << 24) 
                        | (long) (digest[2] & 0xff << 16 
                        | (long) (digest[1] & 0xff << 8) | (long) (digest[0] & 0xff));

                return result & 0xffffffffL;
            }
        }

memcached 问题

  1. 缓存雪崩
    缓存雪崩一般是由某个缓存节点失效,导致其他节点的缓存命中率下降, 缓存中缺失的数据
    去数据库查询.短时间内,造成数据库服务器崩溃.
  2. 数据丢失
    memcached 数据丢失,设为永久有效,却莫名其妙的丢失了。
    1:如果 slab 里的很多 chunk,已经过期,但过期后没有被 get 过, 系统不知他们已经过期. 2:永久数据很久没 get 了,不活跃,如果新增 item,则永久数据被踢了. 3: 当然,如果那些非永久数据被 get,也会被标识为 expire,从而不会再踢掉永久数据
    方案:永久数据和非永久数据分开放。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值