精华内容
下载资源
问答
  • 首先我们来看一个公式,MySQL中内存分为全局内存和线程内存两大部分(其实并不全部,只是影响比较大的 部分):per_thread_buffers=(read_buffer_size+read_rnd_buffer_size+sort_buffer_size+thread_stack+join_...

    首先我们来看一个公式,MySQL中内存分为全局内存和线程内存两大部分(其实并不全部,只是影响比较大的 部分):

    per_thread_buffers=(read_buffer_size+read_rnd_buffer_size+sort_buffer_size+thread_stack+join_buffer_size+binlog_cache_size

    +tmp_table_size)*max_connections

    global_buffers=

    innodb_buffer_pool_size+innodb_additional_mem_pool_size+innodb_log_buffer_size+key_buffer_size+query_cache_size

    total_memory=global_buffers+per_thread_buffers

    全局缓存:key_buffer_size:决定索引处理的速度,尤其是索引读的速度。默认值是16M,通过检查状态值Key_read_requests和Key_reads,可以知道key_buffer_size设置是否合理。比例key_reads / key_read_requests应该尽可能的低,至少是1:100,1:1000更好(上述状态值可以使用'key_read%'获得用来显示状态数据)。key_buffer_size只对MyISAM表起作用。即使你不使用MyISAM表,但是内部的临时磁盘表是MyISAM表,也要使用该值。可以使用检查状态值'created_tmp_disk_tables'得知详情。

    innodb_buffer_pool_size:InnoDB使用该参数指定大小的内存来缓冲数据和索引,这个是Innodb引擎中影响性能最大的参数。

    innodb_additional_mem_pool_size:指定InnoDB用来存储数据字典和其他内部数据结构的内存池大小。缺省值是8M。通常不用太大,只要够用就行,应该与表结构的复杂度有关系。如果不够用,MySQL会在错误日志中写入一条警告信息。

    innodb_log_buffer_size:指定InnoDB用来存储日志数据的缓存大小,如果您的表操作中包含大量并发事务(或大规模事务),并且在事务提交前要求记录日志文件,请尽量调高此项值,以提高日志效率。

    query_cache_size:是MySQL的查询缓冲大小。(从4.0.1开始,MySQL提供了查询缓冲机制)使用查询缓冲,MySQL将SELECT语句和查询结果存放在缓冲区中,今后对于同样的SELECT语句(区分大小写),将直接从缓冲区中读取结果。根据MySQL用户手册,使用查询缓冲最多可以达到238%的效率。通过检查状态值'Qcache_%',可以知道query_cache_size设置是否合理:如果Qcache_lowmem_prunes的值非常大,则表明经常出现缓冲不够的情况,如果Qcache_hits的值也非常大,则表明查询缓冲使用非常频繁,此时需要增加缓冲大小;如果Qcache_hits的值不大,则表明你的查询重复率很低,这种情况下使用查询缓冲反而会影响效率,那么可以考虑不用查询缓冲。此外,在SELECT语句中加入SQL_NO_CACHE可以明确表示不使用查询缓冲。

    线程缓存每个连接到MySQL服务器的线程都需要有自己的缓冲。大概需要立刻分配256K,甚至在线程空闲时,它们使用默认的线程堆栈,网络缓存等。事务开始之后,则需要增加更多的空间。运行较小的查询可能仅给指定的线程增加少量的内存消耗,然而如果对数据表做复杂的操作例如扫描、排序或者需要临时表,则需分配大约read_buffer_size,sort_buffer_size,read_rnd_buffer_size,tmp_table_size大小的内存空间。不过它们只是在需要的时候才分配,并且在那些操作做完之后就释放了。有的是立刻分配成单独的组块。tmp_table_size 可能高达MySQL所能分配给这个操作的最大内存空间了。

    read_buffer_size:是MySQL读入缓冲区大小。对表进行顺序扫描的请求将分配一个读入缓冲区,MySQL会为它分配一段内存缓冲区。read_buffer_size变量控制这一缓冲区的大小。如果对表的顺序扫描请求非常频繁,并且你认为频繁扫描进行得太慢,可以通过增加该变量值以及内存缓冲区大小提高其性能。

    sort_buffer_size:是MySQL执行排序使用的缓冲大小。如果想要增加ORDER BY的速度,首先看是否可以让MySQL使用索引而不是额外的排序阶段。如果不能,可以尝试增加sort_buffer_size变量的大小。

    read_rnd_buffer_size:是MySQL的随机读缓冲区大小。当按任意顺序读取行时(例如,按照排序顺序),将分配一个随机读缓存区。进行排序查询时,MySQL会首先扫描一遍该缓冲,以避免磁盘搜索,提高查询速度,如果需要排序大量数据,可适当调高该值。但MySQL会为每个客户连接发放该缓冲空间,所以应尽量适当设置该值,以避免内存开销过大。

    tmp_table_size:是MySQL的临时表缓冲大小。所有联合在一个DML指令内完成,并且大多数联合甚至可以不用临时表即可以完成。大多数临时表是基于内存的(HEAP)表。具有大的记录长度的临时表 (所有列的长度的和)或包含BLOB列的表存储在硬盘上。如果某个内部heap(堆积)表大小超过tmp_table_size,MySQL可以根据需要自动将内存中的heap表改为基于硬盘的MyISAM表。还可以通过设置tmp_table_size选项来增加临时表的大小。也就是说,如果调高该值,MySQL同时将增加heap表的大小,可达到提高联接查询速度的效果。

    thread_stack :主要用来存放每一个线程自身的标识信息,如线程id,线程运行时基本信息等等,我们可以通过 thread_stack 参数来设置为每一个线程栈分配多大的内存。

    join_buffer_size:应用程序经常会出现一些两表(或多表)Join的操作需求,MySQL在完成某些 Join 需求的时候(all/index join),为了减少参与Join的“被驱动表”的读取次数以提高性能,需要使用到 Join Buffer 来协助完成 Join操作。当 Join Buffer 太小,MySQL 不会将该 Buffer 存入磁盘文件,而是先将Join Buffer中的结果集与需要 Join 的表进行 Join 操作,然后清空 Join Buffer 中的数据,继续将剩余的结果集写入此 Buffer 中,如此往复。这势必会造成被驱动表需要被多次读取,成倍增加 IO 访问,降低效率。

    binlog_cache_size:在事务过程中容纳二进制日志SQL 语句的缓存大小。二进制日志缓存是服务器支持事务存储引擎并且服务器启用了二进制日志(—log-bin 选项)的前提下为每个客户端分配的内存,注意,是每个Client 都可以分配设置大小的binlog cache 空间。如果系统中经常会出现多语句事务的话,可以尝试增加该值的大小,以获得更好的性能。当然,我们可以通过MySQL 的以下两个状态变量来判断当前的binlog_cache_size 的状况:Binlog_cache_use 和Binlog_cache_disk_use。“max_binlog_cache_size”:和"binlog_cache_size"相对应,但是所代表的是binlog 能够使用的最大cache 内存大小。当我们执行多语句事务的时候,max_binlog_cache_size 如果不够大的话,系统可能会报出“ Multi-statement transaction required more than 'max_binlog_cache_size' bytes ofstorage”的错误。

    其中需要注意的是:table_cache表示的是所有线程打开的表的数目,和内存无关。

    展开全文
  • 缓存体系为了提交服务器的性能和减小对数据库的压力,缓存在宝贝项目中应用较多,基本上很少有直接对数据库的访问,绝大多数的数据均来自于缓存系统。所以缓存系统的整体结构和实现效率相对来说比较重要。类别...

    缓存体系

    为了提交服务器的性能和减小对数据库的压力,缓存在宝贝项目中应用较多,基本上很少有直接对数据库的访问,绝大多数的数据均来自于缓存系统。所以缓存系统的整体结构和实现效率相对来说比较重要。

    类别

    memcache:对于由用户创建的信息,均采用memcache缓存。其特点是缓存键值中有_1234(userid)的字样(memcache服务器是一种高效的分布式内存缓存服务器,可以方便的进行扩展,“缺点”是数据是通过网络访问,有一定的网络开销,这种网络开销甚至有时候会成为系统的瓶颈!)

    apc:对于所有系统配置性的数据采用apc缓存。APC缓存就存放于web机自身,为了避免过多的占用WEB机内存,一般情况下都只会存放一些访问频率很高的数据。()

    分析

    采用apc之前有考虑过在web机架设memcache服务器,但最终放弃这个方案。原因如下

    如果在web机上架设memcache服务,那一台机器上提供了两个服务 web 和 memcaced,当然这样做也没关系,只是隐隐觉得不太好。毕竟我们向往的架构还是各司其职,以便于分析性能和瓶颈,提高整体的效率。

    memcache架设在web机上,php访问memcache 是否就相当于访问本机内存呢。答案是no,即使在web上架设memcache,PHP的访问依然是通过网络进行的,memcache有提供一个sock的连接方式用于本机连接,但网上查到的文章说性能很不理想。memcache是一个分布式的高性能的缓存方案,但他不是全能的方案,他被设计的目的就是为了提供分布式的内存缓存,现在我们要将他做为一个本地缓存方案本身就是强人所难。

    APC本身最大的功能并不是提供内存缓存,主要是提供对PHP编译过程中OPCODE缓存的支持,能极大的提高PHP在编译阶段的效率。他同时也提供了内存缓存的功能。并且内存缓存的效率很高,曾经在网上看过一篇文章说访问APC缓存的速度与访问PHP自身内存数据的速度差不多。因此这是一个很高效的缓存 机制,并且APC存放于WEB,对他的访问没有网络开销。这是他与MEMCACHE相比的最大特性。

    PHP结构

    新增了两个配置文件,用于存放apc缓存和memcache缓存的配置。(过期时间配置),如下。

    $config['usercache']['userOneInfo_']['time']='1000';

    $config['usercache']['userOneInfo_']['cache_type']='2';

    $config['usercache']['userOneInfo_']['descript']='单个用户缓存';

    可以方便的在配置中对缓存时间进行调整。避免后期再改对程序。

    实际代码中的缓存结构。

    相对很简单的代码

    class user{

    public function getInfo(){

    $this->load->library('cache');

    $cacheName = 'allWorkList';

    $list = array();

    $list = $this->cache->apc_get($cacheName);

    if (!$list) {

    $sql = 'SELECT * FROM b**;

    $query = $this->db['default']->query($sql);

    while ($row = $this->db['default']->fetch_array($query)) {

    $list[$row['work_id']] = $row;

    }

    $this->load->config('syscache');

    $this->cache->apc_set($cacheName, $list, $this->config->config['syscache']['allWorkList']['time']);

    }

    return emtpy($list) ? array() : $list;

    }

    private function delInfoCache(){

    $this->load->library('cache');

    $cacheName = 'allWorkList';

    return $this->cache->del_apc($cacheName);

    }

    public function updateSomething()

    {

    $sql = "update work set ....";

    if($this->db['default']->query($sql)) {

    $this->delInfoCache();

    }

    return true;

    }

    }

    ?>

    如果有缓存则读缓存,如果没有缓存则取数据并放入缓存。 如果有更新或者删除等操作时则需删除缓存(删除即更新缓存),其加需注意

    删除缓存的操作与创建缓存的操作在同一个类中,并且为private方法,即只能在本类中创建,更新。该类的对像被调用时外部是不知道其中有缓存的。(这类似于一个原则,用于防止在代码中随意更新缓存造成代码混乱)

    上面显示的是apc缓存的访用,memcache访问类似,其实可以构造cache类让他根据配置来使用不同的缓存方案(file,memcache,apc)。

    不足与优化

    memcache是分布式缓存,所有的数据均请求自网络。有下面一个应用。

    for($i=0; $I<100; $i++ ) {

    $this->user->getInfo();

    }

    这在实际的代码中是经常出现的。或者有时候一次PHP请求中会多次调用到getInfo方法。如果是采用的memcache缓存则会出现多次请求网络取回相同的数据的情况。这当然是很浪费的。尤其是在所取的数据非常大的情况下。我们需要对这种情况进行处理。

    改造getInfo方法

    public function getInfo(){

    static $list;

    if(!empty($list)) return $list;

    $this->load->library('cache');

    $cacheName = 'allWorkList';

    $list = array();

    $list = $this->cache->apc_get($cacheName);

    if (!$list) {

    $sql = 'SELECT * FROM b**;

    $query = $this->db['default']->query($sql);

    while ($row = $this->db['default']->fetch_array($query)) {

    $list[$row['work_id']] = $row;

    }

    $this->load->config('syscache');

    $this->cache->apc_set($cacheName, $list, $this->config->config['syscache']['allWorkList']['time']);

    }

    return emtpy($list) ? array() : $list;

    }

    改动在上面代码中黑色标出的部份,即申明一个全局的静态变量存放从缓存中取回的数据,静态变量在函数调用完成之后并不会马上销毁,而是等PHP请求结束才会回收。在PHP执行过程中如果多次调用到getInfo方法,他会从静态变量中取出数据并立即返回,不会发起网络请求了。

    这样的改动虽然达到了我们的要求但是在实际中增加了很大的代码开销。每次使用缓存都需要单独设置静态变量并进行管理。如果可以我们可以改造cache类,将上面的操作在cache中进行,外部调用即不用做这些改变。也可以从cache类中继承一个子类实现相应的功能。

    实施的具体缓存方案。

    单条记录和列表记录的关系

    有这样的情况,用户的服装列表,数据量相对较大,而且因为访问好友家也会要显示用户宠物的着装情况。(这个还是比较频繁)。因此我们需要在memcache中缓存一个数组如下$testArr。

    array(

    [123] =>array(

    dress_id=>123

    dressmod_id=>12

    name=>'test'

    ....

    )

    [124]=>array(

    ...

    )

    )

    方法大致如下

    public function getUserAllDressList(){

    $sql = 'select ...';

    return $list;//查数据库存入列表缓存,数据如上面的array;

    }

    上面的返回数组 键值即为id 。但在很多时候我们要对物品进行操作的时候需要先取出这个物品的数据然后进行相关的验证。(防止别人非法穿上了别人的衣服)因此我们需要一个getone的方法,有两种方案如下。

    function getOneDressDetail($dress_id) {

    $sql = 'select ...';

    ...

    return $sql;//即为单个服装再建立一个缓存。

    }

    这种方案相当于在缓存中人工进行了冗余,因为单条记录和数据和列表中的数据其实是一致的。这样子在更新缓存时需要对更新单条记录的缓存也需要更新列表的缓存。因些不是一个太好的方案,但是如果对列表的缓存更新机制做一定的处理其实也未偿不可以应用。比如列表的缓存不主动更新(列表读取的机会很少)

    function getOneDressDetail($userId, $dress_id){

    $userAllDressList = $this->getUserAllDressList($userId);//缓存用户所有服装的方法,结果如上面的那个数组

    return $userAllDressList[$dress_id];//这里一般需要进行非空的判断否则可能会报错。

    }

    这样就取到了用户的单个服装。并没有去查数据库也没有为单个衣服建立一个单独的缓存。

    这样的方式比较灵活,代码量较少,对于对单件衣服的操作相对较少的环境应该比较适合。但这种方式也有缺点,比如用户穿上了某件衣服,就需要更新用户的缓存,我们这里的单件缓存是取自于列表数据,因些我们需要更新整个列表的数据。国为某件衣服的一个小改变就更新整个列表听起来似乎是很不合适。并且这样的方式还有一个比较大的问题就是我只需要取一条记录,但是会把所有的列表都从缓存服务器中取出。$userAllDressList = $this->getUserAllDressList($userId);这句是取出了整个缓存记录然后由PHP选择其中的一条。对于网络流量的开销也还是比较大。

    于是又有另外一种方案。

    这种方案的应用发生了一点变化。

    用户用户多个宠物,初始化时需一齐获取所有的宠物数据。并进行相应的操作,比如完成打工,打工,培训等等。同上面的情况类似我们也需要对宠物的操作进行验证,也就是要取得单只宠物的数据。宠物数据相对服装数据不同,更新非常频繁,如果每次更新都重建宠物列表的缓存有点不划算。于是我想出了下面一个解决方式。

    将列表数据分成两部分

    public function getPetList($userId){

    $allPetIdArr = $this->getPetIds($userId);

    foreach($allPetIdArr as $v) {

    $list[$v] = $this->getOnePetDetail($v);

    }

    return $list;

    }

    private function getPetIds($userId)

    {

    ...

    return $array;//array(1,2,3)//从缓存中取出用户所有宠物的id

    }

    private function getOnePetDetail($petId){

    ...//从缓存中取出单只宠物的数据。

    }

    即建立一个IDS的缓存数据表存放用户所有宠物的id. 宠物id缓存的更新只有在购买宠物时会触发,相对非常少,建立后基本不需要更新。

    通过循环去单独取每个宠物的数据,并整合成列表数据。 宠物数据更新时只需要更新单只宠物的数据,不需要重建列表的缓存。

    以上的三种方案根据不同的数据更新频率来使用,应该会有一定的提升。不过第三种方案相对更为复杂一些。也许并不是一个很好的模式,而且在建立列表缓存的时候有一个循环,会多次连接数据库。我也并没有做过性能的测试,因些只是一个解决的思路,使用中还需要再根据情况再定,不过对于单条记录更新较频繁的记录这样的方式应该是比较可行的。 *

    缓存全局更新

    宝贝1时有发现这样的情况,数据库配置更新或者程序更新,需要更新缓存(系统的和用户的),当时清空了整个memcache服务器。然后用户访问时会重建缓存。在用户访问量较高时可能造成很多请求同时发送到数据库,造成数据库的瞬间压力。有同事(JJC)给了解决方案是记录在线的用户表(宝贝本身就有维护在线表),并且利用脚本清除在线用户的缓存数据,应该是可行的,可以用脚本分几分钟来清除缓存。比如有在线用户1000人,则1分钟清除一次缓存。一次100人,到清除完成为止,这样更新的请求会保持在一个合理的范围内,不会造成数据库峰值过大。

    展开全文
  • 内存缓存MemoryCache

    2021-02-04 08:00:00
    内存缓存MemoryCache实现了ICache接口,Redis同样实现了ICache接口,两者在缓存操作上达到了高度抽象统一。应用设计时一律使用ICache接口,开发环境装配为Memor...

    内存缓存MemoryCache实现了ICache接口,Redis同样实现了ICache接口,两者在缓存操作上达到了高度抽象统一。应用设计时一律使用ICache接口,开发环境装配为MemoryCache,生产环境根据分布式需要可以装配为Redis。如果应用系统没有分布式需求,继续使用MemoryCache更好。

    超高性能

    MemoryCache核心是并行字典ConcurrentDictionary,由于省去了序列化和网络通信,使得它具有千万级超高性能(普通台式机实测2.87亿tps)。

    MemoryCache支持过期时间,默认容量10万个,未过期key超过该值后,每60秒根据LRU清理溢出部分。

    常用于进程内千万级以下数据缓存场景。

    本机测试数据如下(I9-10900K):

    Test v1.0.0.1130 Build 2021-01-31 19:33:32 .NETCoreApp,Version=v5.0
    Test 
    Memory性能测试[顺序],批大小[0],逻辑处理器 20 个
    测试 10,000,000 项,  1 线程
    赋值 耗时     934ms 速度 10,706,638 ops
    读取 耗时     989ms 速度 10,111,223 ops
    删除 耗时     310ms 速度 32,258,064 ops
    累加 耗时     584ms 速度 17,123,287 ops
    测试 20,000,000 项,  2 线程
    赋值 耗时     927ms 速度 21,574,973 ops
    读取 耗时   1,024ms 速度 19,531,250 ops
    删除 耗时     319ms 速度 62,695,924 ops
    累加 耗时     594ms 速度 33,670,033 ops
    测试 40,000,000 项,  4 线程
    赋值 耗时   1,011ms 速度 39,564,787 ops
    读取 耗时   1,039ms 速度 38,498,556 ops
    删除 耗时   1,636ms 速度 24,449,877 ops
    累加 耗时     608ms 速度 65,789,473 ops
    测试 80,000,000 项,  8 线程
    赋值 耗时     989ms 速度 80,889,787 ops
    读取 耗时   1,227ms 速度 65,199,674 ops
    删除 耗时   1,858ms 速度 43,057,050 ops
    累加 耗时     675ms 速度 118,518,518 ops
    测试 200,000,000 项, 20 线程
    赋值 耗时   1,644ms 速度 121,654,501 ops
    读取 耗时   1,807ms 速度 110,680,686 ops
    删除 耗时   2,936ms 速度 68,119,891 ops
    累加 耗时   1,569ms 速度 127,469,725 ops
    测试 200,000,000 项, 64 线程
    赋值 耗时   1,686ms 速度 118,623,962 ops
    读取 耗时   1,877ms 速度 106,553,010 ops
    删除 耗时     695ms 速度 287,769,784 ops
    累加 耗时   1,585ms 速度 126,182,965 ops
    总测试数据:2,200,000,042

    ICache接口

    ICache是缓存抽象接口,主要实现是MemoryCache和Redis

    /// <summary>缓存接口</summary>
    public interface ICache
    {
        #region 属性
        /// <summary>名称</summary>
        String Name { get; }
        /// <summary>默认缓存时间。默认0秒表示不过期</summary>
        Int32 Expire { get; set; }
        /// <summary>获取和设置缓存,永不过期</summary>
        /// <param name="key"></param>
        /// <returns></returns>
        Object this[String key] { get; set; }
        /// <summary>缓存个数</summary>
        Int32 Count { get; }
        /// <summary>所有键</summary>
        ICollection<String> Keys { get; }
        #endregion
        #region 基础操作
        /// <summary>是否包含缓存项</summary>
        /// <param name="key"></param>
        /// <returns></returns>
        Boolean ContainsKey(String key);
        /// <summary>设置缓存项</summary>
        /// <param name="key">键</param>
        /// <param name="value">值</param>
        /// <param name="expire">过期时间,秒。小于0时采用默认缓存时间<seealso cref="Expire"/></param>
        /// <returns></returns>
        Boolean Set<T>(String key, T value, Int32 expire = -1);
        /// <summary>设置缓存项</summary>
        /// <param name="key">键</param>
        /// <param name="value">值</param>
        /// <param name="expire">过期时间</param>
        /// <returns></returns>
        Boolean Set<T>(String key, T value, TimeSpan expire);
        /// <summary>获取缓存项</summary>
        /// <param name="key">键</param>
        /// <returns></returns>
        T Get<T>(String key);
        /// <summary>批量移除缓存项</summary>
        /// <param name="keys">键集合</param>
        /// <returns></returns>
        Int32 Remove(params String[] keys);
        /// <summary>清空所有缓存项</summary>
        void Clear();
        /// <summary>设置缓存项有效期</summary>
        /// <param name="key">键</param>
        /// <param name="expire">过期时间</param>
        Boolean SetExpire(String key, TimeSpan expire);
        /// <summary>获取缓存项有效期</summary>
        /// <param name="key">键</param>
        /// <returns></returns>
        TimeSpan GetExpire(String key);
        #endregion
        #region 集合操作
        /// <summary>批量获取缓存项</summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="keys"></param>
        /// <returns></returns>
        IDictionary<String, T> GetAll<T>(IEnumerable<String> keys);
        /// <summary>批量设置缓存项</summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="values"></param>
        /// <param name="expire">过期时间,秒。小于0时采用默认缓存时间<seealso cref="Expire"/></param>
        void SetAll<T>(IDictionary<String, T> values, Int32 expire = -1);
        /// <summary>获取列表</summary>
        /// <typeparam name="T">元素类型</typeparam>
        /// <param name="key">键</param>
        /// <returns></returns>
        IList<T> GetList<T>(String key);
        /// <summary>获取哈希</summary>
        /// <typeparam name="T">元素类型</typeparam>
        /// <param name="key">键</param>
        /// <returns></returns>
        IDictionary<String, T> GetDictionary<T>(String key);
        /// <summary>获取队列</summary>
        /// <typeparam name="T">元素类型</typeparam>
        /// <param name="key">键</param>
        /// <returns></returns>
        IProducerConsumer<T> GetQueue<T>(String key);
        /// <summary>获取栈</summary>
        /// <typeparam name="T">元素类型</typeparam>
        /// <param name="key">键</param>
        /// <returns></returns>
        IProducerConsumer<T> GetStack<T>(String key);
        /// <summary>获取Set</summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="key"></param>
        /// <returns></returns>
        ICollection<T> GetSet<T>(String key);
        #endregion
        #region 高级操作
        /// <summary>添加,已存在时不更新</summary>
        /// <typeparam name="T">值类型</typeparam>
        /// <param name="key">键</param>
        /// <param name="value">值</param>
        /// <param name="expire">过期时间,秒。小于0时采用默认缓存时间<seealso cref="Cache.Expire"/></param>
        /// <returns></returns>
        Boolean Add<T>(String key, T value, Int32 expire = -1);
        /// <summary>设置新值并获取旧值,原子操作</summary>
        /// <remarks>
        /// 常常配合Increment使用,用于累加到一定数后重置归零,又避免多线程冲突。
        /// </remarks>
        /// <typeparam name="T">值类型</typeparam>
        /// <param name="key">键</param>
        /// <param name="value">值</param>
        /// <returns></returns>
        T Replace<T>(String key, T value);
        /// <summary>尝试获取指定键,返回是否包含值。有可能缓存项刚好是默认值,或者只是反序列化失败,解决缓存穿透问题</summary>
        /// <typeparam name="T">值类型</typeparam>
        /// <param name="key">键</param>
        /// <param name="value">值。即使有值也不一定能够返回,可能缓存项刚好是默认值,或者只是反序列化失败</param>
        /// <returns>返回是否包含值,即使反序列化失败</returns>
        Boolean TryGetValue<T>(String key, out T value);
        /// <summary>累加,原子操作</summary>
        /// <param name="key">键</param>
        /// <param name="value">变化量</param>
        /// <returns></returns>
        Int64 Increment(String key, Int64 value);
        /// <summary>累加,原子操作</summary>
        /// <param name="key">键</param>
        /// <param name="value">变化量</param>
        /// <returns></returns>
        Double Increment(String key, Double value);
        /// <summary>递减,原子操作</summary>
        /// <param name="key">键</param>
        /// <param name="value">变化量</param>
        /// <returns></returns>
        Int64 Decrement(String key, Int64 value);
        /// <summary>递减,原子操作</summary>
        /// <param name="key">键</param>
        /// <param name="value">变化量</param>
        /// <returns></returns>
        Double Decrement(String key, Double value);
        #endregion
        #region 事务
        /// <summary>提交变更。部分提供者需要刷盘</summary>
        /// <returns></returns>
        Int32 Commit();
        /// <summary>申请分布式锁</summary>
        /// <param name="key">要锁定的key</param>
        /// <param name="msTimeout"></param>
        /// <returns></returns>
        IDisposable AcquireLock(String key, Int32 msTimeout);
        #endregion
        #region 性能测试
        /// <summary>多线程性能测试</summary>
        /// <param name="rand">随机读写。顺序,每个线程多次操作一个key;随机,每个线程每次操作不同key</param>
        /// <param name="batch">批量操作。默认0不分批,分批仅针对随机读写,对顺序读写的单key操作没有意义</param>
        Int64 Bench(Boolean rand = false, Int32 batch = 0);
        #endregion
    }

    基本用法

    添删改查基本功能,Get/Set/Count/ContainsKey/Remove

    var ic = new MemoryCache();
    var key = "Name";
    var key2 = "Company";
    ic.Set(key, "大石头");
    ic.Set(key2, "新生命");
    Assert.Equal("大石头", ic.Get<String>(key));
    Assert.Equal("新生命", ic.Get<String>(key2));
    var count = ic.Count;
    Assert.True(count >= 2);
    // Keys
    var keys = ic.Keys;
    Assert.True(keys.Contains(key));
    // 过期时间
    ic.SetExpire(key, TimeSpan.FromSeconds(1));
    var ts = ic.GetExpire(key);
    Assert.True(ts.TotalSeconds > 0 && ts.TotalSeconds < 2, "过期时间");
    var rs = ic.Remove(key2);
    Assert.Equal(1, rs);
    Assert.False(ic.ContainsKey(key2));
    ic.Clear();
    Assert.True(ic.Count == 0);

    其中Set的第三个参数支持过期时间,单位秒。

    集合操作

    SetAll/GetAll 是高吞吐的关键,其中SetAll第二参数支持过期时间,单位秒

    var ic = new MemoryCache();
    var dic = new Dictionary<String, String>
    {
        ["111"] = "123",
        ["222"] = "abc",
        ["大石头"] = "学无先后达者为师"
    };
    ic.SetAll(dic);
    var dic2 = ic.GetAll<String>(dic.Keys);
    Assert.Equal(dic.Count, dic2.Count);
    foreach (var item in dic)
    {
        Assert.Equal(item.Value, dic2[item.Key]);
    }

    高级操作

    MemoryCache有几个非常好用的高级操作,全部都是线程安全:

    • Add。添加,已存在时不更新,常用于锁争夺。例如,可用于判断指定订单是否处理过,加上过期时间,就是我们经常说的多少小时去重。

    • Replace。设置新值并获取旧值,原子操作

    • TryGetValue。尝试获取指定键,返回是否包含值。有可能缓存项刚好是默认值

    • Increment。累加

    • Decrement。累减

    缓存过期策略

    MemoryCache内置LRU淘汰算法,当缓存项超过最大值Capacity(默认10万)时,剔除最久未使用的缓存项,以避免内存占用过大。

    缓存项未达到最大值Capacity时,MemoryCache定时检查并剔除过期项。

    展开全文
  • 从抽象的角度来看,JMM定义了线程和主内存之间的抽象关系:线程之间的共享变量存储在主内存(Main Memory)中,每个线程都有一个私有的本地内存(Local Memory),本地内存中存储了该线程以读/写共享变量的副本。...

    JMM定义了Java 虚拟机(JVM)在计算机内存(RAM)中的工作方式。JVM是整个计算机虚拟模型,所以JMM是隶属于JVM的。从抽象的角度来看,JMM定义了线程和主内存之间的抽象关系:线程之间的共享变量存储在主内存(Main Memory)中,每个线程都有一个私有的本地内存(Local Memory),本地内存中存储了该线程以读/写共享变量的副本。本地内存是JMM的一个抽象概念,并不真实存在。它涵盖了缓存、写缓冲区、寄存器以及其他的硬件和编译器优化。

    bf08e4546bfa

    java程序简单调用图

    三大性质

    JMM是围绕并发编程中原子性、可见性、有序性三个特征来建立的。

    原子性

    一个操作是不可中断的,要么全部执行成功要么全部执行失败,类似于事务。原子性变量操作包括read,load,assign,use,store,write。Java 基本类型的数据访问大都是原子操作,但是long 和 double 类型是 64 位,在 32 位 JVM 中会将 64 位数据的读写操作分成两次 32 位来处理,所以 long 和 double 在 32 位 JVM 中是非原子操作的。

    可见性:

    一个线程对主内存的修改可以及时被其他线程观察到。导致可见性问题的根本原因:高速缓存。

    有序性

    有序性指的是程序按照代码的先后顺序执行。导致有序性的根本原因:指令重排序

    线程存在本地工作内存,线程共享的主内存。规则:所有线程都不能直接操作主内存,需先访问工作内存,再访问主内存。

    bf08e4546bfa

    内存简单访问方式

    源代码>>>编译器的重排序>>>CPU层面的重排序(指令级,内存级)>>>最终执行的指令

    Java 内存模型中的指令重排不会影响单线程的执行顺序,但是会影响多线程并发执行的正确性,所以在并发中我们必须要想办法保证并发代码的有序性;在 Java 里可以通过 volatile 关键字保证一定的有序性,还可以通过 synchronized、Lock 来保证有序性,因为 synchronized、Lock 保证了每一时刻只有一个线程执行同步代码相当于单线程执行,所以自然不会有有序性的问题;除此之外 Java 内存模型通过 happens-before 原则如果能推导出来两个操作的执行顺序就能先天保证有序性,否则无法保证。从 Java 内存模型我们就能看出来多线程访问共享变量都要经过线程工作内存到主存的复制和主存到线程工作内存的复制操作,所以普通共享变量就无法保证可见性了;Java 提供了 volatile 修饰符来保证变量的可见性,每次使用 volatile 变量都会主动从主存中刷新,除此之外 synchronized、Lock、final 都可以保证变量的可见性。

    CPU高速缓存

    我们都知道CPU/内存/IO三者计算速度差别很大,CPU>内存>IO;在计算机中,cpu和内存的交互最为频繁,相比内存,磁盘读写太慢,内存相当于高速的缓冲区。随着多核cpu时代的到来,内存的读写速度又远不如cpu。因此cpu上出现了高速缓存的概念。cpu上加入了高速缓存,用来解决处理器和内存访问速度差异。在多核cpu中,每个处理器都有各自的高速缓存(L1,L2,L3),而主内存确只有一个 。大概结构如下:

    bf08e4546bfa

    CPU缓存模型

    L1是一级缓存,L1d是数据缓存,L1i是指令缓存

    L2是二级缓存,比L1稍大

    L3是三级缓存,L3缓存是cpu共享的高速缓存,主要目的是进一步降低内存操作的延迟问题。

    CPU-01读取数据A,数据A被读入 CPU-01 的高速缓存中。

    CPU-02读取数据A,同样存入CPU-02高速缓存中。这样 CPU1 , CPU2 的高速缓存拥有同样的数据。

    CPU-01修改了数据A,被修改后,数据A被放回CPU-01的高速缓存行,但是还没有写入到主内存 。

    CPU-02访问数据A,但由于CPU-01并没有将数据A写入主内存,导致了数据不一致。

    高速缓存带来了缓存不一致问题:CPU层面的解决方案:总线锁,缓存锁

    总线锁:处理器的锁,锁的是总线,锁住之后,会导致CPU串行化,效率很慢。(CPU与其它部件通信,是通过总线的方式来通信,当线程A与主内存通信时,先加个锁,此时其他线程不能与主内存通信)

    缓存锁:简单的说,如果某个内存区域数据A,已经同时被两个或以上处理器核缓存,缓存锁就会通过缓存一致性机制阻止对其修改,以此来保证操作的原子性,当其他处理器核回写已经被锁定的缓存行的数据时会导致该缓存行无效。

    缓存行(Cache line):CPU缓存中的最小缓存单位

    目前主流的CPU Cache的Cache Line大小都是64Bytes。假设我们有一个512字节的一级缓存,那么按照64B的缓存单位大小来算,这个一级缓存所能存放的缓存个数就是512/64 = 8个。

    在多核CPU的情况下,每个CPU都有独立的一级缓存,如何才能保证缓存内部数据的一致?一致性的协议MESI。

    缓存一致性协议MESI

    MESI实现方法是在CPU缓存中保存一个标记位,以此来标记四种状态。另外,每个CPU的缓存控制器不仅知道自己的读写操作,也监听其它CPU的读写操作,就是嗅探(snooping)协议。

    缓存状态

    MESI 是指4种状态的首字母。每个缓存行有4个状态,可用2个bit表示,它们分别是:

    状态

    描述

    监听任务

    M 修改 (Modified)

    该缓存行有效,但是数据A被修改了,和内存中的数据A不一致,数据只存在于本CPU中。

    缓存行必须时刻监听所有试图读取主存中旧数据A的操作,当数据A写回主存并将状态变成S(共享)状态之后,解除该监听。

    E 独享(Exclusive)

    该缓存行有效,数据A和内存中的数据A一致,数据A只存在于本CPU中。

    缓存行必须监听其它CPU读取主存中该数据A的操作,一旦有这种操作,该缓存行需要变成S(共享)状态。

    S 共享 (Shared)

    该缓存行有效,数据A和内存中的数据A一致,数据A存在于很多CPU中。

    缓存行必须监听其它CPU使该缓存行无效或者独享该缓存行的请求,并将该缓存行变成无效(Invalid)。

    I 无效 (Invalid)

    该缓存行无效。

    对于M和E状态而言总是精确的,他们在和该缓存行的真正状态是一致的,而S状态可能是非一致的。如果一个缓存将处于S状态的缓存行作废了,而另一个缓存实际上可能已经独享了该缓存行,但是该缓存却不会将该缓存行升迁为E状态,这是因为其它缓存不会广播他们作废掉该缓存行的通知,同样由于缓存并没有保存该缓存行的copy的数量,因此(即使有这种通知)也没有办法确定自己是否已经独享了该缓存行。

    从上面的意义看来E状态是一种投机性的优化:如果一个CPU想修改一个处于S状态的缓存行,总线事务需要将所有该缓存行的copy变成invalid状态,而修改E状态的缓存不需要使用总线事务。

    状态转换

    bf08e4546bfa

    MESI状态转换

    本地:本CPU操作

    远程:其他CPU操作

    本地读:无效状态的更新主存变成独享或共享;其他状态维持本状态。

    本地写:M状态保持不变;共享状态、独享状态变M状态;无效状态更新主存并修改变为M修改状态。

    远程读:独享变共享;共享不变;修改变共享;

    远程写:所有状态变无效。

    MESI带来的问题

    缓存的一致性消息传递是耗时的,CPU切换时会产生延迟。当一个CPU发出缓存数据A的修改消息(缓存行状态修改消息等)时,该CPU会等待其他缓存了该数据A的CPU响应完成。该过程导致阻塞,阻塞会存在各种各样的性能问题和稳定性问题。

    bf08e4546bfa

    阻塞原因

    存储缓存-Store Bufferes

    为了解决CPU状态切换的阻塞问题,避免CPU资源的浪费,引入Store Bufferes。CPU把它想要写入到主存的值写到Store Bufferes,然后继续去处理其他事情。当其他CPU都确认处理完成时,数据才会最终被提交。

    看一下该过程代码演示:

    value = 3;

    void cpu_01(){

    value = 10;//此时cpu_01发出消息,cpu_02变为I状态(store buffer 和 通知其他缓存行失效)(异步)

    isFinsh = true; //标记上一步操作发送消息完成,cpu_01修改->M状态,同步value和isFinish到主存;

    }

    void cpu_02(){

    if(isFinsh){

    //value一定等于10?!

    assert value == 10;

    }

    }

    Store Bufferes的风险:isFinsh的赋值可能在value赋值之前。

    这种在可识别的行为中发生的变化称为指令重排序(指令级别的优化)。它只是意味着其他的CPU会读到跟程序中写入的顺序不一样的结果。为了解决这个问题,引入了内存屏障。

    失效队列

    缓存行状态修改不是一个简单的操作,它需要CPU单独处理,另外,Store Buffers大小有限,所以CPU需要等待状态修改确认处理完成的响应。这两个操作都会使得性能大幅降低。为了解决这个问题,又引入了失效队列。

    由于CPU指令优化导致了问题,所以又提供了内存屏障的指令,明确让程序员告诉CPU什么地方的指令不能够优化

    指令重排

    前提:指令重排只针对单个处理器 和 编译器的单个线程 保证响应结果不变。

    分两个层面:编译器和处理器的指令重排。

    源代码-》编译器的重排序-〉CPU层面的重排序(指令级,内存级)-》最终执行的指令

    看几个概念

    1.数据依赖性:如果两个操作访问同一个变量,且这两个操作中有一个为写操作,此时这两个操作之间就存在数据依赖性。

    说明

    代码

    描述

    写后读

    a=1 ; b=a ;

    写一个变量之后,再读该变量

    写后写

    a=1 ; a=2 ;

    写一个变量之后,再写该变量

    读后写

    b=a ; a=1 ;

    读一个变量之后,再写该变量

    2.as-if-serial 语义:不管怎么重排序,(单处理器/单线程)执行结果不变。编译器和处理器都必须遵守。

    为了遵守 as-if-serial 语义,编译器和处理器不会对存在数据依赖性的操作做重排序,因为这种重排序会改变执行结果。

    程序顺序规则:先行发生happens- before

    重排序需要遵守happens-before规则,不能说你想怎么排就怎么排,如果那样岂不是乱了套。

    1.程序顺序规则

    程序顺序规则中所说的每个操作happens-before于该线程中的任意后续操作,并不是说前一个操作必须要在后一个操作之前执行,而是指前一个操作的执行结果必须对后一个操作可见,如果不满足这个要求那就不允许这两个操作进行重排序。对于这一点,可能会有疑问。顺序性是指,我们可以按照顺序推演程序的执行结果,但是编译器未必一定会按照这个顺序编译,但是编译器保证结果一定==顺序推演的结果。

    2.监视器锁规则

    对一个锁的解锁,happens-before于随后对这个锁的加锁。同一时刻只能有一个线程执行锁中的操作,所以锁中的操作被重排序外界是不关心的,只要最终结果能被外界感知到就好。除了重排序,剩下影响变量可见性的就是CPU缓存了。在锁被释放时,A线程会把释放锁之前所有的操作结果同步到主内存中,而在获取锁时,B线程会使自己CPU的缓存失效,重新从主内存中读取变量的值。这样,A线程中的操作结果就会被B线程感知到了。

    3.volatile变量规则

    对一个volatile域的写,happens-before于任意后续对这个volatile域的读。volatile变量的操作会禁止与其它普通变量的操作进行重排序。volatile变量的写操作就像是一条基准线,到达这条线之后,不管之前的代码有没有重排序,反正到达这条线之后,前面的操作都已完成并生成好结果。

    4.传递性规则

    A happens- before B;B happens- before C;==》A happens- before C;推导出

    5.线程启动规则

    如果线程A执行操作ThreadB.start()(启动线程B),那么A线程的ThreadB.start()操作happens-before于线程B中的任意操作。线程启动规则可以这样去理解:调用start方法时,会将start方法之前所有操作的结果同步到主内存中,新线程创建好后,需要从主内存获取数据。这样在start方法调用之前的所有操作结果对于新创建的线程都是可见的。

    6.线程结束规则

    线程中的任何操作都Happens-Before其它线程检测到该线程已经结束。假设两个线程s、t。在线程s中调用t.join()方法。则线程s会被挂起,等待t线程运行结束才能恢复执行。当t.join()成功返回时,s线程就知道t线程已经结束了。所以根据本条原则,在t线程中对共享变量的修改,对s线程都是可见的。类似的还有Thread.isAlive方法也可以检测到一个线程是否结束。可以猜测,当一个线程结束时,会把自己所有操作的结果都同步到主内存。而任何其它线程当发现这个线程已经执行结束了,就会从主内存中重新刷新最新的变量值。所以结束的线程A对共享变量的修改,对于其它检测了A线程是否结束的线程是可见的。

    7.中断规则

    一个线程在另一个线程上调用interrupt,Happens-Before被中断线程检测到interrupt被调用。

    假设两个线程A和B,A先做了一些操作operationA,然后调用B线程的interrupt方法。当B线程感知到自己的中断标识被设置时(通过抛出InterruptedException,或调用interrupted和isInterrupted),operationA中的操作结果对B都是可见的。

    8.终结器规则

    一个对象的构造函数执行结束Happens-Before它的finalize()方法的开始。

    “结束”和“开始”表明在时间上,一个对象的构造函数必须在它的finalize()方法调用时执行完。

    根据这条原则,可以确保在对象的finalize方法执行时,该对象的所有field字段值都是可见的。

    内存屏障(Memory Barriers)

    编译器级别的内存屏障/CPU层面的内存屏障

    CPU层面提供了三种屏障:写屏障,读屏障,全屏障

    写屏障Store Memory Barrier是一条告诉CPU在执行后续指令之前,需要将该缓存行对应的store buffer中的全部写指令执行完成。

    读屏障Load Memory Barrier是一条告诉CPU在执行后续指令之前,需要将该缓存行对应的失效队列中的全部失效指令执行完成。

    全屏障Full Memory Barrier 是读屏障和写屏障的合集

    void cpu_01() {

    value = 10;

    //在更新数据之前必须将所有存储缓存(store buffer)中的指令执行完毕。

    storeMemoryBarrier();

    finished = true;

    }

    void cpu_02() {

    while(!finished);

    //在读取之前将所有失效队列中关于该数据的指令执行完毕。

    loadMemoryBarrier();

    assert value == 10;

    }

    CPU缓存淘汰策略

    CPU Cache的淘汰策略。常见的淘汰策略主要有LRU和Random两种。通常意义下LRU对于Cache的命中率会比Random更好,所以CPU Cache的淘汰策略选择的是LRU。当然也有些实验显示在Cache Size较大的时候Random策略会有更高的命中率

    参考

    展开全文
  • spark 缓存内存管理

    2020-12-28 14:54:00
    spark内存管理 1 persist 和 unpersist cache() 调用 persist(),且默认存储级别是 MEMORY_ONLY。 persist() 用来设置RDD的存储级别 存储级别 意义 MEMORY_ONLY 使用未序列化的Java对象格式,将数据保存在内存中。...
  • 一.Join语法概述join 用于多表中字段之间的联系,语法如下...JOIN 按照功能大致分为如下三类:INNER JOIN(内连接,或等值连接)...bitsCN.com首先我们来看一个公式,MySQL中内存分为全局内存和线程内存两大部分(其实...
  • 服务器缓存内存溢出 内容精选换一换对于不同业务场景,通过在调整数据库的参数配置,可以有效提升服务器性能。本文中所有未作特别说明的操作皆需在Host1和Host2同步进行,仅需在某台服务器单独进行的操作将注明...
  • 全局共享内存主要是 MySQL Instance(mysqld进程)以及底层存储引擎用来暂存各种全局运算及可共享的暂存信息,如存储查询缓存的 Query Cache,缓存连接线程的 Thread Cache,缓存表文件句柄信息的 Table Cache,缓存二...
  • 本篇文章是对mysql中的缓存如何使用内存进行了详细的分析介绍,需要的朋友参考下先说明2点开启缓存也会带来开销,主要表现在一下方面读取在查询开始之前必须要检查缓存如果查询是缓存的,但是不在结果集中,那么产生...
  • 其发展的迭代历史如下图所示,从2007年至今发展超过10年。   图1:RocketMQ迭代历史 1 消息存储架构     图2:RocketMQ消息存储架构 RocketMQ的消息存储架构如上图所示,可以看到主要由...
  • InnoDB 分布式缓存插件实现的memcached作为一个MySQL插件守护进程访问该InnoDB存储引擎直接绕过MySQL的SQL层。下图说明了daemon_memcached与SQL相比,应用程序如何通过插件访问数据。图15.4带有集成memcached服务器...
  • 缓存是什么 缓存其实就是存储在内存中的临时数据,这里的数据量会比较小,一般来说,服务器的内存也是有限的,不可能将所有的数据都放到
  • mysql缓存

    2021-03-15 20:42:13
    仅做个人备份,浏览请看原文 一、前言 在当今的各种系统中,缓存是对系统性能...按道理,MySQL Server默认打开,是鼓励用户使用缓存,但是大拿们却建议关闭此功能,并且国内各个云厂商提供的MySQL云服务中默认都.
  • 在平时被问及最多的问题就是关于 ...这是MySQL数据库性能优化专题系列的第一篇文章:MySQL 数据库性能优化之缓存参数优化数据库属于 IO 密集型的应用程序,其主要职责就是数据的管理及存储工作。而我们知道,从内存...
  • 其实Linux与Win的内存管理不同,会尽量缓存内存以提高读写性能,通常叫做Cache Memory。为什么Linux系统没运行多少程序,显示的可用内存这么少?其实Linux与Win的内存管理不同,会尽量缓存内存以提高读...
  • 一级缓存:也称本地缓存,sqlSession级别的缓存。一级缓存是一直开启的;与数据库同一次回话期间查询到的数据会放在本地缓存中。如果需要获取相同的数据,直接从缓存中拿,不会再查数据库。一级缓存失效的四种情况:...
  • MySQL优化之缓存优化

    2021-02-05 10:26:52
    1. MySQL缓存为了提高查询速度,我们可以通过不同的方式去缓存我们的结果从而提高响应效率。当我们的数据库打开了Query Cache(简称QC)功能后,数据库在执行SELECT语句时,会将其结果放到QC中,当下一次处理同样的...
  • MySQL 查询缓存

    2021-01-28 03:40:05
    MySQL查询缓存可以跳过SQL解析优化查询等阶段,直接返回缓存结果给用户,查询缓存的工作流程如下:命中条件缓存存在一个hash表中,通过查询SQL,查询数据库,客户端协议等作为key.在判断是否命中前,MySQL不会解析SQL,而是...
  • 在Java程序中,有的时候需要根据不同的场景来使用不同的缓存类型。在Java中主要分别有堆缓存、堆外缓存、磁盘缓存、分布式缓存等。
  • MySQL缓存

    2021-01-18 22:33:54
    MySQL缓存一、前言在当今的各种系统中,缓存是对系统性能优化的重要手段。MySQL Query Cache(MySQL查询缓存)在MySQL Server中是默认打开的,但是网上各种资料以及有经验的DBA都建议生产环境中把MySQL Query Cache...
  • 一、CPU高速缓存(cache) 参考:https://blog.csdn.net/u014470361/article/details/80060701 cache,中译名高速缓冲存储器,其作用是为了更好的利用局部性原理,减少CPU访问主存的次数。简单地说,CPU正在访问的...
  • redis清空缓存数据库

    2021-02-05 16:32:23
    来自:今日头条,作者:聚IT 链接:https://www.toutiao.com/i6752317753866060299/导读 在实际项目中Redis常被应用于做缓存,分布式锁、消息队列等。但是在搭建配置好Redis服务器后很多朋友应该会发现和有这样的...
  • mybatis缓存机制

    2021-01-28 20:32:05
    MyBatis 缓存详解缓存是一般的ORM 框架都会提供的功能,目的就是提升查询的效率和减少数据库的压力。跟Hibernate 一样,MyBatis 也有一级缓存和二级缓存,并且预留了集成第三方缓存的接口。缓存体系结构:MyBatis 跟...
  • 最近经常提示内存不足,发现任务管理器中已提交内存变得很大,PageFile也很大。 但任务管理器的详细信息里面查看所有进程的提交内存都不是很大,加起来远远达不到任务管理器性能页签里面的已提交内存。 网上一搜,...
  • InnoDB有多个内存块组成的内存池,负责如下工作:维护进程、线程内部数据结构缓存磁盘上数据后台线程的作用是刷新内存池中的数据,保证缓冲池中缓存是最新的数据。将修改的数据文件刷新到磁盘文件。保证在数据库...
  • 一级缓存:也称本地缓存,sqlSession级别的缓存。一级缓存是一直开启的;与数据库同一次会话期间查询到的数据会放在本地缓存中。如果需要获取相同的数据,直接从缓存中拿,不会再查数据库。一级缓存失效的四种情况:...
  • 缓存和块缓存

    2021-05-17 16:35:54
    如果经常访问一个数据元素,则该元素很可能位于物理内存中(因而被缓存)。较不常用或很少使用的数据元素,将随时间的推移,逐渐自动退出缓存。实现基本原理:上一次使用的数据元素,将由内核自动放置到LRU列表的...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 199,463
精华内容 79,785
关键字:

内存已提交已缓存