课堂课题:
为什么要使用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)结束语:
今天的分享就到这里啦,欢迎大家点赞、转发、留言、拍砖~
大家好,我是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.结束语:
今天的分享就到这里啦,欢迎大家点赞、转发、留言、拍砖~