为什么要使用MEMCACHE?MEMCACHE有什么作用?

大家好,我是IT修真院郑州分院第十期学员,一枚正直纯洁善良的JAVA程序员。

今天给大家分享一下,修真院官网JAVA任务六,扩展思考中的知识点——为什么要使用MEMCACHE?MEMCACHE有什么作用?

一、背景介绍

     memcache是什么?

     memcache是一个高性能的分布式的内存对象缓存系统,用于动态Web应用以减轻数据库负担。它通过在内存中缓存数据和对象,来减少读取数据库的次数。从而提高动态、数据库驱动网站速度。

     memcache通过在内存里维护一个统一的巨大的hash表,它能够用来存储各种格式的数据,包括图像、视频、文件以及数据库检索的结果等。memcache主要用于分担数据库负的压力,memcache将数据调用到内存中,然后从内存中读取,从而大大提高读取速度。

二、知识剖析

     1.memcache的工作原理:

     首先memcached是以守护程序方式运行于一个或多个服务器中,随时接受客户端的连接操作。

     客户端在与memcached服务建立连接之后,接下来的事情就是存取对象了,每个被存取的对象都有一个唯一的标识符key,存取操作均通过这个key进行,保存到memcached中的对象实际上是放置内存中的,并不是保存cache文件中的,这也是为什么memcached能够如此高效快速的原因。注意,这些对象并不是持久的,服务停止之后,里边的数据就会丢失。

     >memcache采用了C/S的模式,在server端启动服务进程,在启动时可以指定监听的ip、自己的端口号,所使用的内存大小等几个关键参数。一旦启动,服务就一直处于可用状态。

     2.memcache的简单使用(cmd命令行),操作详见代码实战;

     (1)set key flags exptime bytes [noreply]        value(第二行);

     (2)add key flags exptime bytes [noreply]       value(第二行);

     (3)replace key flags exptime bytes [noreply]      value(第二行);

     (4)append key flags exptime bytes [noreply]      value(第二行);

     (5)preppend key flags exptime bytes [noreply]      value(第二行);

     (6)get key(单个) get key1 key2 key3(多个,空格隔开);

     (7)delete key [noreply]。

      各参数含义:

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

     flags:可以包括键值对的整型参数,客户机使用它存储关于键值对的额外信息 。

     exptime:在缓存中保存键值对的时间长度(以秒为单位,0 表示永远).

     bytes:在缓存中存储的字节数.

     noreply(可选): 该参数告知服务器不需要返回数据。

     value:存储的值(始终位于第二行)(可直接理解为key-value结构中的value)。

     3.在springMVC项目中使用memcache(详见代码实战)

三、常见问题

      1.为什么会有memcache和memcached两种名称?

     memcache是这个项目的名称,而memcached是它服务器端的主程序文件名。

      2.memcache内置内存存储方式:

     为了提高性能,memcached中保存的数据都存储在memcache内置的内存存储空间中。由于数据仅存在于内存中,因此,重启memcached、重启操作系统就会导致全部数据消失。另外,内容容量达到指定值之后,就基于LRU(Least Recently Used)算法自动删除不使用的缓存。memcached本身是为缓存而设计的服务器,因此并没有过多考虑数据的永久性问题。

     3.Memcached的缓存策略:

     Memcached的缓存策略是LRU(最近最少使用)加上到期失效策略。当你在memcached内存储数据项时,你有可能会指定它在缓存的失效时间,默认为永久。当memcached服务器用完分配的内时,失效的数据被首先替换,然后也是最近未使用的数据。在LRU中,memcached使用的是一种Lazy Expiration策略,自己不会监控存入的key/vlue对是否过期,而是在获取key值时查看记录的时间戳,检查key/value对空间是否过期,这样可减轻服务器的负载。

四、编码实战

     1.memcache的简单使用(cmd命令行);

     (1)链接已经启动的memcache。注意一定要启动,连接时要与启动时的端口号一致。

            

     (2)操作“增删改查”;

     添加并查看:

     

     添加、查看、替代、查看是否成功;

     

      添加、查看、后面追加、前面追加以及查看;

     

     添加、查看、删除、查看是否删除成功;

     

     2.在springMVC项目中使用memcache缓存。

     (1)导入jar包;

<!-- https://mvnrepository.com/artifact/com.whalin/Memcached-Java-Client -->
<dependency>
 <groupId>com.whalin</groupId>
 <artifactId>Memcached-Java-Client</artifactId>
 <version>3.0.2</version>
</dependency>

     (2)memcache.properties;

#######################设置Memcached服务器参数#######################
#设置服务器地址
memcached.server=127.0.0.1
#该端口号
memcached.port=11210
#容错
memcached.failOver=true 
#设置初始连接数
memcached.initConn=20
#设置最小连接数
memcached.minConn=10
#设置最大连接数
memcached.maxConn=50
#设置连接池维护线程的睡眠时间
memcached.maintSleep=3000
#设置是否使用Nagle算法(Socket的参数),如果是true在写数据时不缓冲,立即发送出去
memcached.nagle=false
#设置socket的读取等待超时时间
memcached.socketTO=3000
#设置连接心跳监测开关
memcached.aliveCheck=true
#######################设置Memcached服务器参数#######################

     (3)在applicationContext.xml中引入;

 <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="order" value="1"/>
        <property name="ignoreUnresolvablePlaceholders" value="true"/>
        <property name="locations">
            <list>
                <value>classpath:memcache.properties</value>
            </list>
        </property>
    </bean>

    <!-- Memcached配置 -->
    <bean id="memcachedPool" class="com.whalin.MemCached.SockIOPool"
          factory-method="getInstance" init-method="initialize" destroy-method="shutDown">
        <constructor-arg>
            <value>memcache</value>
        </constructor-arg>
        <property name="servers">
            <list>
                <value>${memcached.server}:${memcached.port}</value>
            </list>
        </property>
        <property name="initConn">
            <value>${memcached.initConn}</value>
        </property>
        <property name="minConn">
            <value>${memcached.minConn}</value>
        </property>
        <property name="maxConn">
            <value>${memcached.maxConn}</value>
        </property>
        <property name="maintSleep">
            <value>${memcached.maintSleep}</value>
        </property>
        <property name="nagle">
            <value>${memcached.nagle}</value>
        </property>
        <property name="socketTO">
            <value>${memcached.socketTO}</value>
        </property>
    </bean>

     (4)MemcacheUtils工具类;

package com.zyq.util;
import com.whalin.MemCached.MemCachedClient;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Date;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

/**
 * @创建人 zyq
 * @创建时间${date}
 * @描述
 */

 public class MemcacheUtils {

    private static Logger logger = LogManager.getLogger(MemcacheUtils.class);
    /**
     * cachedClient.
     */
    private static MemCachedClient cachedClient;

    static {
        if (cachedClient == null) {
            cachedClient = new MemCachedClient("memcache");
        }
    }

    /**
     * 构造函数.
     */
    public MemcacheUtils() {
    }

    /**
     * 向缓存添加新的键值对。如果键已经存在,则之前的值将被替换.
     *
     * @param key   键
     * @param value 值
     * @return boolean
     */
    public static boolean set(String key, Object value) {
        return setExp(key, value, null);
    }

    /**
     * 向缓存添加新的键值对。如果键已经存在,则之前的值将被替换.
     *
     * @param key    键
     * @param value  值
     * @param expire 过期时间 NewDate(1000*10):十秒后过期
     * @return boolean
     */
    public static boolean set(String key, Object value, Date expire) {
        return setExp(key, value, expire);
    }

    /**
     * 向缓存添加新的键值对。如果键已经存在,则之前的值将被替换.
     *
     * @param key    键
     * @param value  值
     * @param expire 过期时间 New Date(1000*10):十秒后过期
     * @return boolean
     */
    private static boolean setExp(String key, Object value, Date expire) {
        boolean flag = false;
        try {
            flag = cachedClient.set(key, value, expire);
        } catch (Exception e) {
            logger.error("Memcached set方法报错,key值:" + key + "\r\n" + exceptionWrite(e));
        }
        return flag;
    }

    /**
     * 仅当缓存中不存在键时,add 命令才会向缓存中添加一个键值对.
     *
     * @param key   键
     * @param value 值
     * @return boolean
     */
    public static boolean add(String key, Object value) {
        return addExp(key, value, null);
    }

    /**
     * 仅当缓存中不存在键时,add 命令才会向缓存中添加一个键值对.
     *
     * @param key    键
     * @param value  值
     * @param expire 过期时间 NewDate(1000*10):十秒后过期
     * @return boolean
     */
    public static boolean add(String key, Object value, Date expire) {
        return addExp(key, value, expire);
    }

    /**
     * 仅当缓存中不存在键时,add 命令才会向缓存中添加一个键值对.
     *
     * @param key    键
     * @param value  值
     * @param expire 过期时间 NewDate(1000*10):十秒后过期
     * @return boolean
     */
    private static boolean addExp(String key, Object value, Date expire) {
        boolean flag = false;
        try {
            flag = cachedClient.add(key, value, expire);
        } catch (Exception e) {
            logger.error("Memcached add方法报错,key值:" + key + "\r\n" + exceptionWrite(e));
        }
        return flag;
    }

    /**
     * 仅当键已经存在时,replace命令才会替换缓存中的键.
     *
     * @param key   键
     * @param value 值
     * @return boolean
     */
    public static boolean replace(String key, Object value) {
        return replaceExp(key, value, null);
    }

    /**
     * 仅当键已经存在时,replace命令才会替换缓存中的键.
     *
     * @param key    键
     * @param value  值
     * @param expire 过期时间 NewDate(1000*10):十秒后过期
     * @return boolean
     */
    public static boolean replace(String key, Object value, Date expire) {
        return replaceExp(key, value, expire);
    }

    /**
     * 仅当键已经存在时,replace命令才会替换缓存中的键.
     *
     * @param key    键
     * @param value  值
     * @param expire 过期时间 NewDate(1000*10):十秒后过期
     * @return boolean
     */
    private static boolean replaceExp(String key, Object value, Date expire) {
        boolean flag = false;
        try {
            flag = cachedClient.replace(key, value, expire);
        } catch (Exception e) {
            logger.error("Memcachedreplace方法报错,key值:" + key + "\r\n" + exceptionWrite(e));
        }
        return flag;
    }

    /**
     * get 命令用于检索与之前添加的键值对相关的值.
     *
     * @param key 键
     * @return boolean
     */
    public static Object get(String key) {
        Object obj = null;
        try {
            obj = cachedClient.get(key);
        } catch (Exception e) {
            logger.error("Memcached get方法报错,key值:" + key + "\r\n" + exceptionWrite(e));
        }
        return obj;
    }

    /**
     * 删除 memcached 中的任何现有值.
     *
     * @param key 键
     * @return boolean
     */
    public static boolean delete(String key) {
        return cachedClient.delete(key);
    }

    /**
     * 清理缓存中的所有键/值对.
     *
     * @return boolean
     */
    public static boolean flashAll() {
        boolean flag = false;
        try {
            flag = cachedClient.flushAll();
        } catch (Exception e) {
            logger.error("MemcachedflashAll方法报错\r\n" + exceptionWrite(e));
        }
        return flag;
    }

    /**
     * 返回异常栈信息,String类型.
     *
     * @param e Exception
     * @return boolean
     */
    private static String exceptionWrite(Exception e) {
        StringWriter sw = new StringWriter();
        PrintWriter pw = new PrintWriter(sw);
        e.printStackTrace(pw);
        pw.flush();
        return sw.toString();
    }
}

     (5)学生列表页controller(调用工具类的方法判断);

//    查询所有学生信息,上下页跳转使用
   @RequestMapping(value = "/studentS",method = RequestMethod.GET)
      public ModelAndView selectAllByPage(Integer currPage){
              logger.info("输入参数:当前页为"+currPage+"。");
       ModelAndView modelAndView = new ModelAndView("myView");
       Page<Student> page;
       if (MemcacheUtils.get(currPage.toString())==null){
                      logger.info(currPage.toString()+"这一页没有缓存,已经添加缓存");
           page = studentService.selectAllByPage(currPage);
           MemcacheUtils.set(currPage.toString(),page);
       }else {
            page = (Page<Student>) MemcacheUtils.get(currPage.toString());
           logger.info(currPage.toString()+"这一页有缓存,不用添加缓存");
       }
       modelAndView.addObject("page",page);
       modelAndView.addObject("item","viewsBody");
       logger.info("***********************分割线***************************");
       return modelAndView;
   }

     (6)运行项目,查看效果(进入页面,然后刷新);

     (7)也可以利用命令行查看,可以看到get 1有对应的值,但是因为存入的是实体类,所以会乱码;

  

五、扩展思考

     1. 缓存穿透及解决?

     缓存穿透是指查询一个一定不存在的数据,由于缓存是不命中时需要从数据库查询,查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到数据库去查询,造成缓存穿透。

     解决1:采用布隆过滤器,使用一个足够大的bitmap,用于存储可能访问的key,不存在的key直接被过滤;

     解决2:访问key未在DB查询到值,也将空值写进缓存,但可以设置较短过期时间。

     2.缓存雪崩及解决

     大量的key设置了相同的过期时间,导致在缓存在同一时刻全部失效,造成瞬时DB请求量大、压力骤增,引起雪崩。

     解决:这个没有完美解决办法,但可以分析用户行为,尽量让失效时间点均匀分布。大多数系统设计者考虑用加锁或者队列的方式保证缓存的单线程(进程)写,从而避免失效时大量的并发请求落到底层存储系统上。

     3.缓存击穿及解决

     一个存在的key,在缓存过期的一刻,同时有大量的请求,这些请求都会击穿到DB,造成瞬时DB请求量大、压力骤增。

     解决:在访问key之前,采用SETNX(set if not exists)来设置另一个短期key来锁住当前key的访问,访问结束再删除该短期key。

 六、参考文献

https://www.cnblogs.com/mxmbk/articles/6586229.html

https://www.cnblogs.com/lixuwu/p/7446170.html

https://blog.csdn.net/fei33423/article/details/79027790

七、更多讨论

     1.memcache和redis区别

    Memcache:

    Memcache可以利用多核优势,单实例吞吐量极高,可以达到几十万QPS,适用于最大程度扛量

    只支持简单的key/value数据结构,不像Redis可以支持丰富的数据类型。

    无法进行持久化,数据不能备份,只能用于缓存使用,且重启后数据全部丢失

     Redis:

     支持多种数据结构,如string,list,dict,set,zset,hyperloglog

     单线程请求,所有命令串行执行,并发情况下不需要考虑数据一致性问题。

    支持持久化操作,可以进行aof及rdb数据持久化到磁盘,从而进行数据备份或数据恢复等操作,较好的防止数据丢失的手段。

    支持通过Replication进行数据复制,通过master-slave机制,可以实时进行数据的同步复制,支持多级复制和增量复制.

    支持pub/sub消息订阅机制,可以用来进行消息订阅与通知。

     支持简单的事务需求,但业界使用场景很少,并不成熟

     2.memcached命令add和set的区别

     set 命令用于向缓存添加新的键值对。如果键已经存在,则之前的值将被替换。

     使用add添加新的键值对,仅当缓存中不存在键时,add 命令才会向缓存中添加一个键值对。如果缓存中已经存在键,则之前的值将仍然保持不变,并且将获得响应 NOT_STORED。

     3.什么样的数据适合写入缓存

     访问频率高、修改频率低、数据量小的数据

八、视频教程

https://v.qq.com/x/page/q07698j5a3e.html

快点加入我们吧:http://www.jnshu.com/login/1/23284132

猜你喜欢

转载自blog.csdn.net/qq_41802128/article/details/82529725