Memcached简介

  在Web服务开发中,服务端缓存是服务实现中所常常采用的一种提高服务性能的方法。其通过记录某部分计算结果来尝试避免再次执行得到该结果所需要的复杂计算,从而提高了服务的运行效率。

  除了能够提高服务的运行效率之外,服务端缓存还常常用来提高服务的扩展性。因此一些大规模的Web应用,如Facebook,常常构建一个庞大的服务端缓存。而它们所最常使用的就是Memcached。

  在本文中,我们就将对Memcached进行简单地介绍。

 

Memcached简介

  在介绍Memcached之前,让我们首先通过一个示例了解什么是服务端缓存。

  相信大家都玩过一些网络联机游戏吧。在我那个年代(03年左右),这些游戏常常添加了对战功能,并提供了天梯来显示具有最优秀战绩的玩家以及当前玩家在天梯系统中的排名。这是游戏开发商所常常采用的一种聚拢玩家人气的手段。而希望在游戏中证明自己的玩家则会由此激发斗志,进而花费更多时间来在天梯中取得更好的成绩。

  就天梯系统来说,其最主要的功能就是为玩家提供天梯排名的信息,而并不允许玩家对该系统中所记录的数据作任何修改。这样设定的结果就是,整个天梯系统的读操作居多,而写操作很少。反过来,由于一个游戏中的玩家可能有上千万甚至上亿人,而且在线人数常常达到上万人,因此对天梯的访问也会是非常频繁的。这样的话,即使每秒钟只有10个人访问天梯中的排名,对这上亿个玩家的天梯排名进行读取及排序也是一件非常消耗性能的事情。

  一个自然而然的想法就是:在对天梯排名进行一次计算后,我们在服务端将该天梯排名缓存起来,并在其它玩家访问的时候直接返回该缓存中所记录的结果。而在一定时间段之后,如一个小时,我们再对缓存中的数据进行更新。这样我们就不再需要每个小时执行成千上万次的天梯排名计算了。

  而这就是服务端缓存所提供的最重要功能。其既可以提高单个请求的响应速度,又可以降低服务层及数据库层的压力。除此之外,多个服务实例都可以读取该服务端缓存所缓存的信息,因此我们也不再需要担心这些数据在各个服务实例中都保存了一份进而需要彼此同步的问题,也即是提高了扩展性。

  而Memcached就是一个使用了BSD许可的服务端缓存实现。但是与其它服务端缓存实现不同的是,其主要由两部分组成:独立运行的Memcached服务实例,以及用于访问这些服务实例的客户端。因此相较于普通服务端缓存实现中各个缓存都运行在服务实例之上的情况,Memcached服务实例则是在服务实例之外独立运行的:

  从上图中可以看出,由于Memcached缓存实例是独立于各个应用服务器实例运行的,因此应用服务实例可以访问任意的缓存实例。而传统的缓存则与特定的应用实例绑定,因此每个应用实例将只能访问特定的缓存。这种绑定一方面会导致整个应用所能够访问的缓存容量变得很小,另一方面也可能导致不同的缓存实例中存在着冗余的数据,从而降低了缓存系统的整体效率。

  在运行时,Memcached服务实例只需要消耗非常少的CPU资源,却需要使用大量的内存。因此在决定如何组织您的服务端缓存结构之前,您首先需要搞清当前服务中各个服务实例的负载情况。如果一个服务器的CPU使用率非常高,却存在着非常多的空余内存,那么我们就完全可以在其上运行一个Memcached实例。而如果当前服务中的所有服务实例都没有过多的空余内存,那么我们就需要使用一系列独立的服务实例来搭建服务端缓存。一个大型服务常常拥有上百个Memcached实例。而在这上百个Memcached实例中所存储的数据则不尽相同。由于这种数据的异构性,我们需要在访问由Memcached所记录的信息之前决定在该服务端缓存系统中到底由哪个Memcached实例记录了我们所想要访问的数据:

  如上图所示,用户需要通过一个Memcached客户端来完成对缓存服务所记录信息的访问。该客户端知道服务端缓存系统中所包含的所有Memcached服务实例。在需要访问具有特定键值的数据时,该客户端内部会根据所需要读取的数据的键值,如“foo”,以及当前Memcached缓存服务的配置来计算相应的哈希值,以决定到底是哪个Memcached实例记录了用户所需要访问的信息。在决定记录了所需要信息的Memcached实例之后,Memcached客户端将从配置中读取该Memcached服务实例所在地址,并向该Memcached实例发送数据访问请求,以从该Memcached实例中读取具有键值“foo”的信息。在各个论坛的讨论中,这被称为是Memcached的两阶段哈希(Two-stage hash)。

  而对数据的记录也使用了类似的流程:假设用户希望通过服务端缓存记录数据“bar”,并为其指定键值“foo”。那么Memcached客户端将首先对用户所赋予的键值“foo”及当前服务端缓存所记录的可用服务实例个数执行哈希计算,并根据哈希计算结果来决定存储该数据的Memcached服务实例。接下来,客户端就会向该实例发送请求,以在其中记录具有键值“foo”的数据“bar”。

  这样做的好处则在于,每个Memcached服务实例都是独立的,而彼此之间并没有任何交互。在这种情况下,我们可以省略很多复杂的功能逻辑,如各个节点之间的数据同步以及结点之间消息的广播等等。这种轻量级的架构可以简化很多操作。如在一个节点失效的时候,我们仅仅需要使用一个新的Memcached节点替代老节点即可。而在对缓存进行扩容的时候,我们也只需要添加额外的服务并修改客户端配置。

  这些记录在服务端缓存中的数据是全局可见的。也就是说,一旦在Memcached服务端缓存中成功添加了一条新的记录,那么其它使用该缓存服务的应用实例将同样可以访问该记录:

  在Memcached中,每条记录都由四部分组成:记录的键,有效期,一系列可选的标记以及表示记录内容的数据。由于记录内容的数据中并不包含任何数据结构,因此我们在Memcached中所记录的数据需要是经过序列化之后的表示。

 

内存管理

  在使用缓存时,我们不得不考虑的一个问题就是如何对这些缓存数据的生存期进行管理。这其中包括如何使一个记录在缓存中的数据过期,如何在缓存空间不够时执行数据的替换等。因此在本节中,我们将对Memcached的内存管理机制进行介绍。

  首先我们来看一看Memcached的内存管理模型。通常情况下,一个内存管理算法所最需要考虑的问题就是内存的碎片化(Fragmentation):在长时间地分配及回收之后,被系统所使用的内存将趋向于散落在不连续的空间中。这使得系统很难找到连续内存空间,一方面增大了内存分配失败的概率,另一方面也使得内存分配工作变得更为复杂,降低了运行效率。

  为了解决这个问题,Memcached使用了一种叫Slab的结构。在该分配算法中,内存将按照1MB的大小划分为页,而该页内存则会继续被分割为一系列具有相同大小的内存块:

  因此Memcached并不是直接根据需要记录的数据的大小来直接分配相应大小的内存。在一条新的记录到来时,Memcached会首先检查该记录的大小,并根据记录的大小选择记录所需要存储到的Slab类型。接下来,Memcached就会检查其内部所包含的该类型Slab。如果这些Slab中有空余的块,那么Memcached就会使用该块记录该条信息。如果已经没有Slab拥有空闲的具有合适大小的块,那么Memcached就会创建一个新的页,并将该页按照目标Slab的类型进行划分。

  一个需要考虑的特殊情况就是对记录的更新。在对一个记录进行更新的时候,记录的大小可能会发生变化。在这种情况下,其所对应的Slab类型也可能会发生变化。因此在更新时,记录在内存中的位置可能会发生变化。只不过从用户的角度来说,这并不可见。

  Memcached使用这种方式来分配内存的好处则在于,其可以降低由于记录的多次读写而导致的碎片化。反过来,由于Memcached是根据记录的大小选择需要插入到的块类型,因此为每个记录所分配的块的大小常常大于该记录所实际需要的内存大小,进而造成了内存的浪费。当然,您可以通过Memcached的配置文件来指定各个块的大小,从而尽可能地减少内存的浪费。

  但是需要注意的是,由于默认情况下Memcached中每页的大小为1MB,因此其单个块最大为1MB。除此之外,Memcached还限制每个数据所对应的键的长度不能超过250个字节。

  一般来说,Slab中各个块的大小以及块大小的递增倍数可能会对记录所载位置的选择及内存利用率有很大的影响。例如在当前的实现下,各个Slab中块的大小默认情况下是按照1.25倍的方式来递增的。也就是说,在一个Memcached实例中,某种类型Slab所提供的块的大小是80K,而提供稍大一点空间的Slab类型所提供的块的大小就将是100K。如果现在我们需要插入一条81K的记录,那么Memcached就会选择具有100K块大小的Slab,并尝试找到一个具有空闲块的Slab以存入该记录。

  同时您也需要注意到,我们使用的是100K块大小的Slab来记录具有81K大小的数据,因此记录该数据所导致的内存浪费是19K,即19%的浪费。而在需要存储的各条记录的大小平均分布的情况下,这种内存浪费的幅度也在9%左右。该幅度实际上取决于我们刚刚提到的各个Slab中块大小的递增倍数。在Memcached的初始实现中,各个Slab块的递增倍数在默认情况下是2,而不是现在的1.25,从而导致了平均25%左右的内存浪费。而在今后的各个版本中,该递增倍数可能还会发生变化,以优化Memcached的实际性能。

  如果您一旦知道了您所需要缓存的数据的特征,如通常情况下数据的大小以及各个数据的差异幅度,那么您就可以根据这些数据的特征来设置上面所提到的各个参数。如果数据在通常情况下都比较小,那么我们就需要将最小块的大小调整得小一些。如果数据的大小变动不是很大,那么我们可以将块大小的递增倍数设置得小一些,从而使得各个块的大小尽量地贴近需要存储的数据,以提高内存的利用率。

  还有一个值得注意的事情就是,由于Memcached在计算到底哪个服务实例记录了具有特定键的数据时并不会考虑用来组成缓存系统中各个服务器的差异性。如果每个服务器上只安装了一个Memcached实例,那么各个Memcached实例所拥有的可用内存将存在着数倍的差异。但是由于各个实例被选中的概率基本相同,因此具有较大内存的Memcached实例将无法被充分利用。我们可以通过在具有较大内存的服务器上部署多个Memcached实例来解决这个问题:

  例如上图所展示的缓存系统是由两个服务器组成。这两个服务器中的内存大小并不相同。第一个服务器的内存大小为32G,而第二个服务器的内存大小仅仅有8G。为了能够充分利用这两个服务器的内存,我们在具有32G内存的服务器上部署了4个Memcached实例,而在只有8G内存的服务器上部署了1个Memcached实例。在这种情况下,32G内存服务器上的4个Memcached实例将总共得到4倍于8G服务器所得到的负载,从而充分地利用了32G内存服务器上的内存。

  当然,由于缓存系统拥有有限的资源,因此其会在某一时刻被服务所产生的数据填满。如果此时缓存系统再次接收到一个缓存数据的请求,那么它就会根据LRU(Least recently used)算法以及数据的过期时间来决定需要从缓存系统中移除的数据。而Memcached所使用的过期算法比较特殊,又被称为延迟过期(Lazy expiration):当用户从Memcached实例中读取数据的时候,其将首先通过配置中所设置的过期时间来决定该数据是否过期。如果是,那么在下一次写入数据却没有足够空间的时候,Memcached会选择该过期数据所在的内存块作为新数据的目标地址。如果在写入时没有相应的记录被标记为过期,那么LRU算法才被执行,从而找到最久没有被使用的需要被替换的数据。

  这里的LRU是在Slab范围内的,而不是全局的。假设Memcached缓存系统中的最常用的数据都存储在100K的块中,而该系统中还存在着另外一种类型的Slab,其块大小是300K,但是存在于其中的数据并不常用。当需要插入一条99K的数据而Memcached已经没有足够的内存再次分配一个Slab实例的时候,其并不会释放具有300K块大小的Slab,而是在100K块大小的各个Slab中找到需要释放的块,并将新数据添加到该块中。

 

高可用性

  在企业级应用中,我们常常强调一个系统需要拥有高可用性和高可靠性。而对于一个组成而言,其需要能够稳定地运行,并在出现异常的时候尽量使得异常的影响限制在某个特定的范围内,而不会导致整个系统不能正常工作。而且在出现异常之后,该组成需要能较为容易地恢复到正常的工作状态。

  那么Memcached需要什么样的高可用性呢?在讲解这个问题之前,我们先来看看在一个大型服务中Memcached所组成的服务端缓存是什么样的:

  从上图中可以看到,在一个大型服务中,由Memcached所组成的服务端缓存实际上是由非常多的Memcached实例组成的。在前面我们已经介绍过,Memcached实例实际上是完全独立的,不存在Memcached实例之间的相互交互。因此在其中一个发生了故障的时候,其它的各个Memcached服务实例并不会受到影响。如果一个拥有了16个Memcached实例的服务端缓存系统中的一个Memcached实例发生了故障,那么整个系统将还有93.75%的缓存容量可以继续工作。虽然缓存容量的减少会略微增加其后的各个服务实例的压力,但是一个应用所经历的负载波动常常比这个大得多,因此该服务应该还是能够正常工作的。

  而这也恰恰表明了Memcached所具有的独立性的正确性。由于Memcached本身致力于创建一个高效而且简单,却具有较强扩展性的缓存组件,因此其并没有强调数据的安全性。一旦其中的一个Memcached实例发生了故障,那么我们还可以从数据库及服务端再次计算得到该数据,并将其记录在其它可用的Memcached实例上。

  我想您读到这里一定会想:“不,还有一个问题,那就是由于Memcached实例的个数变化会导致哈希计算的结果发生变化,从而导致所有对数据的请求会导向到不正确的Memcached实例,使得由Memcached实例集群所提供的缓存服务全部失效,从而导致数据库的压力骤增。”

  是的,这也是我曾经有所顾虑的地方。而且这不仅仅在服务端缓存失效的时候存在。只要服务端缓存中Memcached实例的数量发生了变化,那么该问题就会发生。

  Memcached所使用的解决方法就是Consistent Hashing。在该算法的帮助下,Memcached实例数量的变化将只可能导致其中的一小部分键的哈希值发生改变。那该算法到底是怎么运行的呢?

  首先请考虑一个圆,在该圆上分布了多个点,以表示整数0到1023。这些整数平均分布在整个圆上:

  而在上图中,我们则突出地显示了6个蓝点。这六个蓝点基本上将该圆进行了六等分。而它们所对应的就是在当前Memcached缓存系统中所包含的三个Memcached实例m1,m2以及m3。好,接下来我们则对当前需要存储的数据执行哈希计算,并找到该哈希结果900在该圆上所对应的点:

  可以看到,该点在顺时针距离上离表示0的那个蓝点最近,因此这个具有哈希值900的数据将记录在Memcached实例m1中。

  如果其中的一个Memcached实例失效了,那么需要由该实例所记录的数据将暂时失效,而其它实例所记录的数据仍然还在:

  从上图中可以看到,在Memcached实例m1失效的情况下,值为900的数据将失效,而其它的值为112和750的数据将仍然记录在Memcached实例m2及m3上。也就是说,一个节点的失效现在将只会导致一部分数据不再在缓存系统中存在,而并没有导致其它实例上所记录的数据的目标实例发生变化。

  但是我们还不得不考虑另一个问题,那就是在一个服务的服务端缓存仅仅由一个或几个Memcached实例组成的情况。在这种情况下,其中一个Memcached实例失效是较为致命的,因为数据库以及服务器实例将接收到大量的需要进行复杂计算的请求,并将最终导致服务器实例和数据库过载。因此在设计服务端缓存时,我们常常采取超出需求容量的方法来定义这些缓存。例如在服务实际需要5个Memcached结点时我们设计一个包含6个节点的服务端缓存系统,以增加整个系统的容错能力。

 

使用Memcached搭建缓存系统

  OK,在对Memcached内部运行原理介绍完毕之后,我们就来看看如何使用Memcached为您的服务搭建缓存系统。

  首先,您需要从Memcached官方网站上下载Memcached的安装文件,并在您作为缓存服务器的系统上进行安装。在安装时,您需要对Memcached进行适当地配置,如其所需要侦听的端口,为其所分配的内存大小等。在Memcached正确配置并启动之后,我们就可以通过一系列客户端软件访问这些Memcached实例并对其进行操作了。由于我并不是一个运维人员,因此在这里我们将不再对这些配置进行详细地介绍。

  而我们要介绍的,则是如何用Memcached的Java客户端去读写数据。 而一个较为常见的Memcached客户端则是SpyMemcached。就让我们来看一看Spy Memcached的Main函数中是如何对其所提供的功能进行使用的:

 1 public static void main(String[] args) throws Exception{
 2     if(args.length < 2){
 3         System.out.println("Please specify command line options");
 4         return;
 5     }
 6 
 7     MemcachedClient memcachedClient = new MemcachedClient(AddrUtil.getAddresses("127.0.0.1:11211"));
 8     if (commandName.equals("get")){
 9         String keyName= args[1];
10         System.out.println("Key Name " + keyName);
11         System.out.println("Value of key " + memcachedClient.get(keyName));
12     } else if(commandName.equals("set")){
13         String keyName =args[1];
14         String value=args[2];
15         System.out.println("Key Name " + keyName + " value=" + value);
16         Future<Boolean> result= memcachedClient.set(keyName, 0, value);
17         System.out.println("Result of set " + result.get());
18     } else if(commandName.equals("add")){
19         String keyName =args[1];
20         String value=args[2];
21         System.out.println("Key Name " + keyName + " value=" + value);
22         Future<Boolean> result= memcachedClient.add(keyName, 0, value);
23         System.out.println("Result of add " + result.get());
24     } else if(commandName.equals("replace")){
25         String keyName =args[1];
26         String value=args[2];
27         System.out.println("Key Name " + keyName + " value=" + value);
28         Future<Boolean> result= memcachedClient.replace(keyName, 0, value);
29         System.out.println("Result of replace " + result.get());
30     } else if(commandName.equals("delete")){
31         String keyName =args[1];
32         System.out.println("Key Name " + keyName );
33         Future<Boolean> result= memcachedClient.delete(keyName);
34         System.out.println("Result of delete " + result.get());
35     } else{
36         System.out.println("Command not found");
37     }
38     memcachedClient.shutdown();
39 }

  可以看到,在该客户端的帮助下,对存储在Memcached实例中的数据的读取已经变得非常简单了。我们可以简单地通过调用该客户端的get(),set(),add(),replace()以及delete()函数来完成对数据的操作。

 

转载请注明原文地址并标明转载:http://www.cnblogs.com/loveis715/p/4681643.html

商业转载请事先与我联系:silverfox715@sina.com

转载于:https://www.cnblogs.com/loveis715/p/4681643.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/290752.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

JAVA设计模式之门面模式(外观模式)

医院的例子 现代的软件系统都是比较复杂的&#xff0c;设计师处理复杂系统的一个常见方法便是将其“分而治之”&#xff0c;把一个系统划分为几个较小的子系统。如果把医院作为一个子系统&#xff0c;按照部门职能&#xff0c;这个系统可以划分为挂号、门诊、划价、化验、收费、…

里程碑 .Net7再更新,从此彻底碾压Java!

.NET 7 Preview1发布了&#xff0c;没时间实操&#xff1f;先快来看看.NET7的七项重大改进&#xff01;1、不再支持.NET 7应用程序、运行时和SDK的多级查找&#xff08;MLL&#xff09;2、PATH停止向.NET 7运行时和SDK添加32位.NET3、默认情况下&#xff0c; dotnet build/publ…

软件架构知识体系

2019独角兽企业重金招聘Python工程师标准>>> 由于[GOF95]是论述软件模式的著作的第一本&#xff0c;也是OO设计理论著作中最流行的一本&#xff0c;因此有些人常常使用设计模式&#xff08;Design Pattern&#xff09;一词来指所有直接处理软件的架构、设计、程序实…

C#不要再使用Npoi啦,使用MiniExcel操作Excel文件更快更高效!

1.简介MiniExcel简单、高效避免OOM的.NET处理Excel查、写、填充数据工具。目前主流框架如Npoi 需要将数据全载入到内存方便操作&#xff0c;但这会导致内存消耗问题。MiniExcel 尝试以 Stream 角度写底层算法逻辑&#xff0c;能让原本1000多MB占用降低到几MB&#xff0c;避免内…

go和python切片的不同

2019独角兽企业重金招聘Python工程师标准>>> go有切片slice类型&#xff0c;python有列表和元组&#xff0c;这两种语言都有切片操作。 但是它们的切片操作是完全不同的。 首先说第一个&#xff0c;go的切片&#xff0c;其成员是相同类型的&#xff0c;python的列…

编程算法 - 切割排序 代码(C)

切割排序 代码(C)本文地址: http://blog.csdn.net/caroline_wendy排序切割, 把一个数组分为, 大于k\小于k\等于k的三个部分.能够使用高速排序的Partition函数, 进行处理, 把大于k的放在左边, 小于k的放在右边.使用一个变量记录中间的位置, 则时间复杂度为O(3n/2).代码:/** main…

【C#/.NET】.NET6中全局异常处理

微信公众号&#xff1a;趣编程ACE关注可了解每日一更的.NET日常实战开发技巧&#xff0c;欢迎公众号留言开发 获取源码;.NET6中全局异常处理异常处理是我们在程序开发中不可或缺的一环&#xff0c;下文我将会结合程序Sample讲解如何在.NET6中有效处理异常。Try-Ctach 块包裹自定…

JAVA学习笔记--初识容器类库

一、前言 JAVA中一切皆为对象&#xff0c;因而&#xff0c;持有对象显得尤为重要。 在JAVA中&#xff0c;我们可以通过创建一个对象的引用的方式来持有对象&#xff1a; HoldingObject holding; 也可以创建一个对象数组来持有一组特定类型的对象&#xff1a; HoldingObject hol…

如何保证执行异步方法时不会遗漏 await 关键字

前言在.NET Core 中已经广泛使用异步编程&#xff0c;代码中充斥着大量的 async 和 await 关键字。但有时候&#xff0c;调用一个异步方法时可能会忘了写 await。这会造成什么问题呢&#xff1f;问题重现示例代码如下&#xff1a;[HttpGet] public async Task<IEnumerable&l…

Avalonia跨平台入门第十二篇之动画效果

在前面分享的几篇中咱已经玩耍了Popup、ListBox多选、Grid动态分、RadioButton模板、控件的拖放效果、控件的置顶和置底、控件的锁定、自定义Window样式;今天趁着空闲时间接着去摸索简单的动画效果,毕竟有点动画的东西还是挺有意思的;最终实现的效果如下图:使用了Margin实现左右…

python之解析最简单的xml

1、person.xm文件如下 2、用xml.etree.ElementTree解析person.xml的实现 3、效果如下 4、总结 python里面的list []相当于java里面的list&#xff0c;然后可以改变其中的值。

mysql忘记密码,怎么办?

mysql忘记密码&#xff0c;怎么办&#xff1f;我们经常需要修改mySQL的密码&#xff0c;比如时间久了忘记了MySQL的密码&#xff0c;也或者是使用了一台别人使用过的电脑&#xff0c;不知道之前密码的情况下&#xff0c;又想使用MySQL&#xff0c;怎么办呢&#xff1f;准备工作…

三分钟学会缓存工具DiskLruCache

DiskLruCache是一个十分好用的android缓存工具&#xff0c;我们可以从GitHub上下载其源码&#xff1a;https://github.com/JakeWharton/DiskLruCache DiskLruCache所有的数据都存储在/storage/emulated/0/Android/data/应用包名/cache/XXX文件夹中(你也可以修改&#xff0c;但不…

【数据挖掘】知识总结——背景、定义、一般流程及应用(一)

数据挖掘知识总结&#xff08;一&#xff09; 1.数据挖掘产生的背景&&驱动力 DRIP&#xff08;Data Rich Information Poor&#xff09; 四种主要技术激发了人们对数据挖掘技术的开发、应用和研究的兴趣&#xff1a; 超大规模数据库的出现&#xff0c;如商业数据仓…

LinkedIn联合创始人:硅谷也就700万人,为什么能创建这么多瞩目的公司 ?

很多人不解&#xff1a;现在创业公司这么多&#xff0c;在世界任何地方都有很多人懂技术&#xff0c;营销&#xff0c;也有VC&#xff0c;可以组建团队&#xff0c;那为啥非要在硅谷做呢&#xff1f;作者董飞&#xff0c;整理了 Linkedin 创始人 Reid Hoffman 在CS183C 课程的分…

Docker容器安全的8大风险和33个最佳实践丨IDCF

作者&#xff1a;StackRox译者&#xff1a;冬哥原文&#xff1a;https://www.stackrox.io/blog/docker-security-101/容器以及例如Kubernetes等编排器开启了应用程序开发方法的新时代&#xff0c;支持微服务架构以及持续开发和交付。根据我们最新的容器状态和 Kubernetes 安全报…

iOS应用开发的五个Java开源工具

随着第三方工具的不断壮大&#xff0c;开发人员逐渐摆脱政策束缚&#xff0c;对于iOS系统的封闭性为其他语言&#xff08;如Java&#xff09;开发者诟病得到解脱&#xff0c;开始使用自己熟悉的语言来编写iOS本地应用&#xff0c;或将其他平台上的应用移植到iOS上。 本文为你介…

【Tensorflow】解决No module named ‘matplotlib‘/‘pandas‘

用Tensorflow出现No module named ‘matplotlib’/‘pandas’ 尝试网上方法打开终端pip install pandas 不成功&#xff0c;报一大堆红字错误。自己尝试了一种方法。 打开Anaconda Navigator&#xff1b;找到Environments&#xff0c;点击对应环境&#xff0c;我的是tensorfl…

人之将死其言也善?30年来死囚遗言分析

今天是感恩节&#xff0c;不知道这个话题合适不合适。我们经常会提到『死而无憾』这个词。这个词似乎是一种理想状态&#xff0c;几乎100%的人还是做不到的。那么我们的『憾』在哪里&#xff1f;这个问题看似不是难题&#xff0c;不过仔细想想是永远没答案的&#xff0c;人只有…

如何通过 C# 比较两幅图片的相似度?

咨询区 Byyo我在用 C# 实现一个可以查找重复图片的小工具&#xff0c;我目前是给每一个图片做一个 md5 码&#xff0c;然后通过 md5 值来判断图片是否相同。但现实情况要复杂的多&#xff0c;比如&#xff1a;图片被旋转了&#xff0c;比如&#xff1a;90图片大小不一致不同的压…