IT修真院·小课堂丨互联网职业小课堂在线学习平台

课堂课题:

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

关联任务:

任务五

直播时间:

2020-07-19 15:00:00


课堂内容:

视频链接:

PPT链接:

提交按钮:

小课堂内容格式


标题:

【修真院xx(职业)小课堂】课题名称

开场语:

大家好,我是IT修真院XX分院第X期的学员XX,一枚正直纯洁善良的XX程序员,今天给大家分享一下,修真院官网XX(职业)任务X,深度思考中的知识点——XXX

(1)背景介绍:

背景介绍的时候,尽可能的要宽广,讲清楚来龙去脉,讲清楚为什么会需要这个技术。

(2)知识剖析:

讲知识点的时候,尽可能的成体系,学会成体系的去给别人介绍知识。现在很多做的都是零散的,没有分类。

(3)常见问题:

最少列出1个常见问题。

(4)解决方案:

写清楚常见问题的解决方案。

(5)编码实战:

尽可能的去寻找在真实项目中在用的。如果你能找到某个网站在用你说的知识点,这是最好的。学以致用,否则当成练习题就没有意义了。多准备一些demo,讲解过程中将知识点和demo结合,便于大家理解所讲解的知识点。

(6)拓展思考:

知识点之外的拓展思考,由分享人进行讲解,这些东西就是所谓的深度,也是一个人技术水准高低比较的表现。

(7)参考文献:

引入参加文献的时候,在引用的句子后面加上序号【1】。参考文献中列出详细来源。不要去抄别人的东西,这是一个基本的态度。

(8)更多讨论:

Q1:提问人:问题?
A1:回答人(可以是分享人,也可以是其他学员):回答
Q2:提问人:问题?
A2:回答人(可以是分享人,也可以是其他学员):回答
Q3:提问人:问题?
A3:回答人(可以是分享人,也可以是其他学员):回答

(9)鸣谢:

感谢XX、XX师兄,此教程是在他们之前技术分享的基础上完善而成。

(10)结束语:

今天的分享就到这里啦,欢迎大家点赞、转发、留言、拍砖~

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

辅导师兄 [真传弟子]JAVA-李绍博


大家好,我是IT修真院成都分院第18期的学员黄迎旭,一枚正直纯洁善良的Java程序员
今天给大家分享一下,修真院官网java(职业)任务5,深度思考中的知识点——为什么要使用memcache?memcache有什么作用?

1.背景介绍

假设又这样一个场景,前台有一个操作,传过来一个请求到后台,这时候需要操作数据库,一顿操作下来查询数据大概耗时300ms,然后后面长时间内数据不会变化,然后这样的请亲又来了一次,还是耗时300ms,如果我们使用缓存会怎样,把刚才查询的结果按照key-value的形式扔进缓存,通过key查询出来耗时只要2ms,后面的所有一样的请求查询就只用2ms。同时也因为处理速度快,能够支持高并发。

缓存是高性能下提高热点数据访问性能的有效手段。用于处理系统的高并发,高性能。

综上,像Memcache,Redis这样的缓存组件就诞生了。

2.知识剖析

缓存

缓存分类:本地缓存,分布式缓存,多级缓存

本地缓存就是在进程的内存中进行缓存,比如我们的 JVM 堆中,可以LRUMap 来实现,也可以使用 Ehcache 这样的工具来实现。本地缓存是内存访问,没有远程交互开销,性能最好,但是受限于单机容量,一般缓存较小且无法扩展。


分布式缓存可以很好得解决这个问题。分布式缓存一般都具有良好的水平扩展能力,对较大数据量的场景也能应付自如。缺点就是需要进行远程请求,性能不如本地缓存。

为了平衡这种情况,实际业务中一般采用多级缓存,本地缓存只保存访问频率最高的部分热点数据,其他的热点数据放在分布式缓存中。


淘汰机制:不管是本地缓存还是分布式缓存,为了保证较高性能,都是使用内存来保存数据,由于成本和内存限制,当存储的数据超过缓存容量时,需要对缓存的数据进行剔除。一般的剔除策略有 FIFO 淘汰最早数据、LRU 剔除最近最少使用、和 LFU 剔除最近使用频率最低的数据几种策略。


Memcache

后面我会把 Memcache 简称为 MC。

MC是一个自由开源的,高性能,分布式内存对象缓存系统,是一种基于内存的key-value存储,用来存储小块的任意数据(字符串、对象)。一般的使用目的是,通过缓存数据库查询结果,减少数据库访问次数,以提高动态Web应用的速度、提高可扩展性。

然后下面是从MC的安装,特点,内存结构,钙化问题详细阐述

安装:

(1)首先需要安装Libevent,Libevent是一款跨平台的事件处理接口的封装,可以兼容多个操作系统的事件访问。 Memcached的安装依赖于Libevent,因此需要先完成Libevent的安装。

yum install gcc gcc-c++ make -y #yum安装gcc编译环境包

(2)上传/解压软件包

tar -zxvf libevent-2.1.8-stable.tar.gz

tar -zxvf memcached-1.5.6.tar.gz

cd libevent-2.1.8-stable/

./configure --prefix=/software/local/libevent

make && make install

到此libevent安装完毕

(3).安装Memcached

cd ../memcached-1.5.6/

./configure \

--prefix=/software/local/memcached \

--with-libevent=/software/local/libevent/ #指定libevent安装路径

make && make install

(4).创建软连接,方便memcached服务

ln -s /software/local/memcached/bin/* /usr/local/bin/

红色的是我自己安装的路径,自己上传文件在那个路径下,就在哪个路径下操作,一般都是在/usr/local 下

(5).启动服务`

启动 memcached(-d:守护进程、-m:指定缓存大小为32M 、-p:指定默认端口11211 、 -u:指定 登陆用户为 root)

memcached -d -m 32m -p 11211 -u root

netstat -antp | grep memcached #查看启动监听端口

(6).连接memcached数据库

telnet HOST PORT

命令中的 HOST 和 PORT 为运行 Memcached 服务的 IP 和 端口。没有telnet指令的需要自己安装。

特点:

      (1).MC 处理请求时使用多线程异步 IO 的方式,可以合理利用 CPU 多核的优势,性能非常优秀;

      (2).MC 功能简单,使用内存存储数据,只支持 K-V 结构,不提供持久化和主从同步功能;

      (3).MC 的内存结构以及钙化问题后面会详细介绍;

      (4).MC 对缓存的数据可以设置失效期,过期后的数据会被清除;

      (5).失效的策略采用延迟失效,就是当再次使用数据时检查是否失效;

      (6).当容量存满时,会对缓存中的数据进行剔除,剔除时除了会对过期 key 进行清理,还会按 LRU 策略对数据进行剔除。

另外,使用 MC 有一些限制:

       key 不能超过 250 个字节;

       value 不能超过 1M 字节;

       key 的最大失效时间是 30 天;

内存结构:

图片上传不了,所以加载不出来

首先来看 MC 的内存结构。MC 默认是通过 Slab Allocator 来管理内存。Slab 机制主要是用来解决频繁 malloc/free 会产生内存碎片的问题。

malloc()在运行期动态分配分配内存,free()释放由其分配的内存。malloc()在分配用户传入的大小的时候,还分配的一个相关的用于管理的额外内存,不过,用户是看不到的。所以,分配内存大小的实际空间如下

实际的大小 = 管理空间 + 用户空间

如图,MC 会把内存分为许多不同类型的 Slab,每种类型 Slab 用来保存不同大小的对象。每个 Slab 由若干的 Page 组成,如图中浅绿色的模块。不同 Slab 的 Page,默认大小是一样的,都是 1M,这也是默认 MC 存储对象不能超过 1M 的原因。每个 Page 内又划分为许多的 Chunk,Chunk 就是实际用来保存对象的空间,就是图中橘色的。不同类型的 Slab 中 Chunk 的大小是不同的,当保存一个对象时,MC 会根据对象的大小来选择最合适的 Chunk 来存储,减少空间浪费。

Slab Allocator 创建 Slab 时的参数有三个,分别是 Chunk 大小的增长因子,Chunk 大小的初始值以及 Page 的大小。在运行时会根据要保存的对象大小来逐渐创建 Slab。

钙化问题:

考虑这样一个场景,使用 MC 来保存用户信息,假设单个对象大约 300 字节。这时会产生大量的 384 字节大小的 Slab。运行一段时间后,用户信息增加了一个属性,单个对象的大小变成了 500 字节,这时再保存对象需要使用 768 字节的 Slab,而 MC  中的容量大部分创建了 384 字节的 Slab,所以 768 的 Slab 非常少。这时虽然 384 Slab 的内存大量空闲,但 768 Slab 还是会根据 LRU 算法频繁剔除缓存,导致 MC 的剔除率升高,命中率降低。这就是所谓的 MC 钙化问题。

解决钙化问题可以开启 MC 的 Automove 机制,每 10s 调整 Slab。也可以分批重启 MC 缓存,不过要注意重启时要进行一定时间的预热,防止雪崩问题。另外,在使用 Memcached 时,最好计算一下数据的预期平均长度,调整 growth factor, 以获得最恰当的设置,避免内存的大量浪费。

3.常见问题/解决方案

缓存更新方式

第一个问题是缓存更新方式,这是决定在使用缓存时就该考虑的问题。

缓存的数据在数据源发生变更时需要对缓存进行更新,数据源可能是 DB,也可能是远程服务。数据源是 DB 时,更新的方式可以是主动同步更新,可以在更新完 DB 后就直接更新缓存。

当数据源不是 DB 而是其他远程服务,可能无法及时主动感知数据变更,这种情况下一般会选择对缓存数据设置失效期,也就是数据不一致的最大容忍时间。这种场景下,可以选择失效更新,key 不存在或失效时先请求数据源获取最新数据,然后再次缓存,并更新失效期。

但这样做有个问题,如果依赖的远程服务在更新时出现异常,则会导致数据不可用。改进的办法是异步更新,就是当失效时先不清除数据,继续使用旧的数据,然后由异步线程去执行更新任务。这样就避免了失效瞬间的空窗期。

另外还有一种纯异步更新方式,定时更新对数据进行分批更新。实际使用时可以根据业务场景选择更新方式。


数据不一致

第二个问题是数据不一致的问题,可以说只要使用缓存,就要考虑如何面对这个问题。缓存不一致产生的原因一般是主动更新失败,例如更新 DB 后,更新 Redis 因为网络原因请求超时;或者是异步更新失败导致。

解决的办法是,如果服务对耗时不是特别敏感可以增加重试;如果服务对耗时敏感可以通过异步补偿任务来处理失败的更新,或者短期的数据不一致不会影响业务,那么只要下次更新时可以成功,能保证最终一致性就可以。


缓存穿透

第三个问题是缓存穿透。产生这个问题的原因可能是外部的恶意攻击,例如,对用户信息进行了缓存,但恶意攻击者使用不存在的用户id频繁请求接口,导致查询缓存不命中,然后穿透 DB 查询依然不命中。这时会有大量请求穿透缓存访问到 DB。

解决的办法如下。

对不存在的用户,在缓存中保存一个空对象进行标记,防止相同 ID 再次访问 DB。不过有时这个方法并不能很好解决问题,可能导致缓存中存储大量无用数据。

使用 BloomFilter 过滤器,BloomFilter 的特点是存在性检测,如果 BloomFilter 中不存在,那么数据一定不存在;如果 BloomFilter 中存在,实际数据也有可能会不存在。非常适合解决这类的问题。


缓存击穿

第四个问题是缓存击穿,就是某个热点数据失效时,大量针对这个数据的请求会穿透到数据源。

解决这个问题有如下办法。

可以使用互斥锁更新,保证同一个进程中针对同一个数据不会并发请求到 DB,减小 DB 压力。

使用随机退避方式,失效时随机 sleep 一个很短的时间,再次查询,如果失败再执行更新。

针对多个热点 key 同时失效的问题,可以在缓存时使用固定时间加上一个小的随机数,避免大量热点 key 同一时刻失效。


缓存雪崩

第五个问题是缓存雪崩。产生的原因是缓存挂掉,这时所有的请求都会穿透到 DB。

解决方法:

使用快速失败的熔断策略,减少 DB 瞬间压力;

使用主从模式和集群模式来尽量保证缓存服务的高可用。

实际场景中,这两种方法会结合使用。

4.编码实战

java中运用

(1).导入jar包。

java_memcached-release_2.jar
commons-pool-.jar
slf4j-api-.jar
slf4j-simple-.jar

java_memcached-release_2.6.6.jar这个包在maven依赖引入不管用,我是自己想办法解决的。就是上面遇到问题。

(2).创建一个MemcachedUtil工具类。

用来获取MemcachedClient以及自定义方法。设置连接池的属性,后面我改了是放在用Spring整合了配置。

public class MemcachedUtil {
   private static MemCachedClient mcc =new MemCachedClient();
   private MemcachedUtil(){

   }

  static{
      String[] servers = {"120.25.222.150:11211"};
      Integer[] weights = {1};

      SockIOPool pool = SockIOPool.getInstance();
      pool.setServers(servers);
      pool.setWeights(weights);
      pool.setInitConn(5);//设置开始时每个cache服务器的可用连接数
      pool.setMinConn(5);//设置每个服务器最少可用连接数
      pool.setMaxConn(250);//设置每个服务器最大可用连接数
      pool.setMaxIdle(1000*60*60*6);//设置可用连接池的最长等待时间
      //设置连接池维护线程的睡眠时间 ,设置为0,维护线程不启动。维护线程主要通过log输出
      pool.setMaintSleep(30);
      //设置是否使用Nagle算法,因为通讯数据量通常都比较大(相对TCP控制数据)而且要求响应及时,
      // 因此该值需要设置为false(默认是true)
      pool.setNagle(false);
      pool.setSocketConnectTO(0);//设置socket的读取等待超时值
      pool.setSocketTO(3000);
      pool.setHashingAlg(3);
      // 设置hash算法
       //  alg=0 使用String.hashCode()获得hash code,该方法依赖JDK,可能和其他客户端不兼容,建议不使用
       //  alg=1 使用original 兼容hash算法,兼容其他客户端
       //  alg=2 使用CRC32兼容hash算法,兼容其他客户端,性能优于original算法
       //  alg=3 使用MD5 hash算法
       //  采用前三种hash算法的时候,查找cache服务器使用余数方法。采用最后一种hash算法查找cache//       pool.initialize();// 设置完pool参数后最后调用该方法,启动pool。
  }
  public static MemCachedClient getMemCachedClient(){
       return mcc;
  }

   /**
    * set缓存数据
    * @param key 取值的键
    * @param value 缓存的字符串类型
    */
  public static void set(String key,String value){
   mcc.set(key,value);
  }
   /**
    * set缓存数据
    * @param key
    * @param obj  缓存对象类型
    */
  public static void set(String key,Object obj){
      mcc.set(key,obj);
  }

   /**
    * replace替换存在key的值,key不存在则替换失败
    * @param key
    * @param obj
    */
   public static void replace(String key,Object obj){
       mcc.replace(key, obj);
   }

   /**
    * append在已有的key后追加数据
    * @param key
    * @param obj
    */
   public static void append(String key,Object obj){
       mcc.append(key,obj);
   }

   /**
    * get 获取单个数据
    * @param key
    * @return
    */
   public static Object get(String key){
       return mcc.get(key);
   }

   /**
    * get获取多个值
    * @param keys
    * @return 返回map集合
    */
   public static Map<String,Object> get(Collection<String> keys){
       Map<String,Object> map = new HashMap<>();
       for(String s:keys){

           map.put(s,mcc.get(s));
       }
           return map;
   }
}

(3).修改自己Service的方法。

从数据库查询的学生数据放入缓存。

public class StudentServiceImpl implements StudentService {
   @Resource
   private StudentMapper studentMapper;
      //定义一个常量key
   private static String STUKEY = "studentList";
      private static Logger logger = LoggerFactory.getLogger(StudentServiceImpl.class);
     @Override
   public List<Student> selectAll() {
         MemCachedClient mcc = MemcachedUtil.getMemCachedClient();
         String key = STUKEY;
         List<Student> stuList = null;
       ObjectMapper mapper = new ObjectMapper();
              //如果缓存没有key,则查询数据库,把值放到缓存中,最后返回数据
   //否则key存在,直接从缓存中取数据
   if(!mcc.keyExists(key)){
              stuList = studentMapper.selectAll();
               MemcachedUtil.set(key,stuList);
        }else{
               stuList = castList(mcc.get(key),Student.class);
       }
       return stuList;
   }

   /**
    * 使用Class.cast做类型转换,将Object转换成List
    * @param obj
    * @param clazz
    * @param <T>
    * @return
    */
   public static <T> List<T> castList(Object obj, Class<T> clazz)
   {
       List<T> result = new ArrayList<T>();
       if(obj instanceof List<?>)
       {
           for (Object o : (List<?>) obj)
           {
               result.add(clazz.cast(o));
           }
           return result;
       }
       return null;
   }}

5.扩展思考

(1). memcached的客户端有哪些?

        Memcached Client for Java

        SpyMemcached

        XMemcached

Memcached Client for Java 比 SpyMemcached更稳定、更早、更广泛;

SpyMemcached 比 Memcached Client for Java更高效;

XMemcached 比 SpyMemcache并发效果更好

(2). memcached缺乏认证以及安全管制,应该怎么办?

        将memcached服务器放置在防火墙后。

(3). memcached使用场景?

    >.非持久化存储:对数据存储要求不高

    >.分布式存储:不适合单机使用

    >.Key/Value存储:格式简单,不支持List、Array数据格式

(4)不适用memcached的业务场景?

    >缓存对象的大小大于1MB。Memcached本身就不是为了处理庞大的多媒体(large media)和巨大的二进制块(streaming huge blobs)而设计的。

    >key的长度大于250字符,这也是我们尽力避免的

    >业务本身需要的是持久化数据或者说需要的应该是database

6.参考文献

百度百科。拉钩教育。

7.更多讨论

8.结束语:

今天的分享就到这里啦,欢迎大家点赞、转发、留言、拍砖~

9.鸣谢:

感谢XX、XX师兄,此教程是在他们之前技术分享的基础上完善而成。

10.结束语:

今天的分享就到这里啦,欢迎大家点赞、转发、留言、拍砖~


评论

请您登录 后进行评论