<?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/categories/微博</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/categories/微博</link>
    </image>
    <item>
      <title>微服务的数据聚合Join_cn_hhaip的专栏-CSDN博客</title>
      <link>https://itindex.net/detail/61702-%E5%BE%AE%E6%9C%8D%E5%8A%A1-%E6%95%B0%E6%8D%AE-%E8%81%9A%E5%90%88</link>
      <description>&lt;div&gt;    &lt;p&gt;&lt;/p&gt;    &lt;div&gt;      &lt;h3&gt;文章目录&lt;/h3&gt;      &lt;ul&gt;        &lt;li&gt;          &lt;a href="https://blog.csdn.net/cn_hhaip/article/details/113335574#join_1"&gt;单库join&lt;/a&gt;&lt;/li&gt;        &lt;li&gt;          &lt;a href="https://blog.csdn.net/cn_hhaip/article/details/113335574#join_16"&gt;分布式微服务聚合join&lt;/a&gt;&lt;/li&gt;        &lt;li&gt;          &lt;a href="https://blog.csdn.net/cn_hhaip/article/details/113335574#CQRS_43"&gt;CQRS&lt;/a&gt;&lt;/li&gt;        &lt;li&gt;          &lt;ul&gt;            &lt;li&gt;              &lt;a href="https://blog.csdn.net/cn_hhaip/article/details/113335574#Denormalize__Materialize_the_View_45"&gt;Denormalize + Materialize the View&lt;/a&gt;&lt;/li&gt;            &lt;li&gt;              &lt;a href="https://blog.csdn.net/cn_hhaip/article/details/113335574#CQRS_66"&gt;CQRS模式&lt;/a&gt;&lt;/li&gt;            &lt;li&gt;              &lt;ul&gt;                &lt;li&gt;                  &lt;a href="https://blog.csdn.net/cn_hhaip/article/details/113335574#CQRS_81"&gt;CQRS和最终一致性&lt;/a&gt;&lt;/li&gt;                &lt;li&gt;                  &lt;a href="https://blog.csdn.net/cn_hhaip/article/details/113335574#CQRSUI_91"&gt;CQRS和UI（前端）更新策略&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;        &lt;li&gt;          &lt;a href="https://blog.csdn.net/cn_hhaip/article/details/113335574#2005_VS_2016_105"&gt;架构2005 VS 2016&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;    &lt;p&gt;&lt;/p&gt;    &lt;h1&gt;      &lt;a&gt;&lt;/a&gt;单库join&lt;/h1&gt;    &lt;p&gt;      &lt;img alt="" src="https://img-blog.csdnimg.cn/img_convert/e68adb9795a035a771ed8042097cbf6b.png"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;p&gt;传统SQL数据库，通常正规化(normalization)的方式来建模数据。正规化的好处是      &lt;code&gt;数据冗余少&lt;/code&gt;，不足之处是数据聚合Join会比较麻烦，可能实际Join的时候，需要将几张相关表，通过主键和外键关系才能Join起来。我们知道，Join是一种开销比较大的SQL运算，当数据量少的时候，这种开销通常OK。但是随着企业规模逐渐变大，数据库中的数据量也会越变越大，相应地，Join的开销也会越来越大。于是，Join变慢的问题就会越来越突出，通常表现为用户的查询慢，严重时，复杂的Join可能会导致数据库繁忙不响应甚至宕机。之前我在上家公司工作的时候，就曾经经历过几次复杂Join造成DB宕机的事故。可以说，单库Join性能慢的问题，是目前很多网站的普遍痛点问题。所以，去数据库Join，是很多企业当前正在做的数据库优化工作之一。&lt;/p&gt;    &lt;p&gt;      &lt;strong&gt;优点：&lt;/strong&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;strong&gt;缺点：&lt;/strong&gt;&lt;/p&gt;    &lt;ul&gt;      &lt;li&gt;join多张表的时候一样比较麻烦（SQL运算大）&lt;/li&gt;      &lt;li&gt;join非常影响性能，随着数据量越来越大，传将越慢，对CPU和内存的占用越来越大，甚至影响其他业务正常进行或DB宕机&lt;/li&gt;      &lt;li&gt;join往往是数据库瓶颈之一&lt;/li&gt;&lt;/ul&gt;    &lt;h1&gt;      &lt;a&gt;&lt;/a&gt;分布式微服务聚合join&lt;/h1&gt;    &lt;blockquote&gt;      &lt;p&gt;分布式聚合join服务层的另两种名字 1、Aggregator聚合服务 2、BFF服务，BFF是Backend for Frontend的简称&lt;/p&gt;&lt;/blockquote&gt;    &lt;p&gt;在分布式的微服务时代，数据聚合Join的问题并没有消失，它变成了另外一种形式（      &lt;code&gt;微服务之间的聚合join&lt;/code&gt;）。      &lt;br /&gt;例子:&lt;/p&gt;    &lt;p&gt;      &lt;img alt="" src="https://img-blog.csdnimg.cn/img_convert/1fa2997ea6bf5998b3c0bf13c17f30ae.png"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;p&gt;上图，有两个基础领域服务customer-service和order-service。根据微服务有界上下文和职责单一的原则，customer-service只负责客户数据，order-service只负责订单数据。但是前端业务需要一个order-history-API，这个API支持查询用户的历史订单，它既要提供用户详细信息，也要提供用户的历史订单信息。为此，我们需要引入这样一个order-history-API服务，它同时去调用order-service和customer-service，获得数据后再在本地进行聚合Join，然后再对外提供聚合好的客户+订单历史数据。      &lt;br /&gt;总体上，上图的order-history-API做的事情，就是所谓的分布式聚合Join。这个API服务还有两个专门的称谓，一个叫      &lt;code&gt;Aggregator聚合服务&lt;/code&gt;，另外一个叫      &lt;code&gt;BFF服务(BFF是Backend for Frontend的简称)&lt;/code&gt;，它的      &lt;code&gt;主要工作也就是聚合Join（外观模式）&lt;/code&gt;。&lt;/p&gt;    &lt;p&gt;大规模互联网系统中，分布式聚合Join非常常见。基本上你上任何一个大厂的网站，比方说天猫，京东，或者美团，携程等，它们的网站页面上的数据，大部分都是通过后台的分布式聚合服务聚合出来的。所以，      &lt;code&gt;聚合服务层(或者称BFF层)&lt;/code&gt;，是现代互联网和微服务架构中普遍存在的一个架构层次。&lt;/p&gt;    &lt;p&gt;      &lt;strong&gt;分布式聚合的优点：&lt;/strong&gt;      &lt;br /&gt;1、具有实时性和强一致性的好处      &lt;br /&gt;2、聚合服务层使相同业务更加内聚，不同业务更低耦合&lt;/p&gt;    &lt;p&gt;      &lt;strong&gt;分布式聚合的缺点：&lt;/strong&gt;&lt;/p&gt;    &lt;p&gt;1、一个是      &lt;code&gt;N+1问题&lt;/code&gt;。有的时候，为了获得A和B服务的聚合数据，可能A只需要调用一次，但是B却需要调用N次才能获取完整数据。这个就是软件开发领域臭名昭著的N + 1问题，它通常是性能杀手。&lt;/p&gt;    &lt;p&gt;2、第二个问题是数据量的问题。聚合服务需要把A和B的部分数据都加载到本地内存，然后才能进行聚合运算。当访问量大的时候，聚合服务会占用大量内存开销，严重时可能会造成内存被撑爆。&lt;/p&gt;    &lt;p&gt;3、第三个问题就是随着后台基础领域服务的数据量越来越大，总体聚合服务的性能也会随着越变越慢。需要特别说明的是，如果不做缓存的话，这种分布式聚合，对于每个请求都是会重复执行和运算的，也就是会有大量的频繁和重复的聚合运算，会白白消耗大量CPU/内存等资源。&lt;/p&gt;    &lt;h1&gt;      &lt;a&gt;&lt;/a&gt;CQRS&lt;/h1&gt;    &lt;h2&gt;      &lt;a&gt;&lt;/a&gt;Denormalize + Materialize the View&lt;/h2&gt;    &lt;blockquote&gt;      &lt;p&gt;反正规化的物化视图&lt;/p&gt;&lt;/blockquote&gt;    &lt;p&gt;企业实践表明当互联网公司的体量规模发展到一定的阶段，为了解决分布式聚合Join慢的问题(或者是为了解决传统SQL数据库Join慢的问题)，它们通常会采用另外一种称为      &lt;code&gt;数据分发 + 预聚合&lt;/code&gt;的新方式。      &lt;br /&gt;(坤同以前通过定时任务将领料汇总提前生成数据)&lt;/p&gt;    &lt;p&gt;      &lt;img alt="" src="https://img-blog.csdnimg.cn/img_convert/d82b9f5b078a104fcbf105fbd4c301df.png"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;p&gt;      &lt;strong&gt;例子：&lt;/strong&gt;      &lt;br /&gt;上图，我们这里也有两个基础领域服务，一个是商品服务      &lt;code&gt;item-service&lt;/code&gt;，另外一个是订单反馈服务      &lt;code&gt;order-feedback-service&lt;/code&gt;。      &lt;br /&gt;前端业务需要一个商品反馈服务item-feedback-service，它的数据是由item-service和order-feedback-service聚合的结果。为了实现这个order-feedbak-service，我们可以用前面的聚合(或者说BFF服务)来实现，但是那种做法可能每次查询的开销较大，性能无法满足要求。为了解决性能问题，我们可以改用之前讲解的数据分发技术，比方说      &lt;code&gt;事务性发件箱技术&lt;/code&gt;，或者      &lt;code&gt;CDC变更数据捕获技术&lt;/code&gt;，也就是基于      &lt;code&gt;数据分发+预聚合(提前组装数据)&lt;/code&gt;的思路来实现这个服务。当item-service或者order-feedback-service有数据变更的时候，我们把它们的变更，通过数据分发技术，分发到item-feedback-service这个聚合+查询服务。item-feedback-service可以根据本地已有的数据，加上发送过来的变更数据，实时/或者近实时的聚合计算出商品反馈数据，并存入本地数据库缓存起来，这个就是数据分发+预聚合的思路。&lt;/p&gt;    &lt;p&gt;      &lt;strong&gt;坤同例子：&lt;/strong&gt;      &lt;br /&gt;KA-VMC接收kafka的机器消息，然后通过http调用（可改成缓存）机器和商品信息提前组装好数据，最后保存到本服务业务库。然后通过数据分发技术，将数据分发不到不同的业务服务。&lt;/p&gt;    &lt;p&gt;**注意：**这个方式和前面的聚合层BFF方式是有本质区别的。前面的方式是每次请求都要触发重复计算的，而这里的方式是一次性预先聚合好，并且缓存起来，后面的查询都是查询的缓存数据，所以这是一个提前预聚合的思路，      &lt;code&gt;有一定的延迟&lt;/code&gt;。&lt;/p&gt;    &lt;p&gt;这个方式其实就是      &lt;code&gt;反正规化(denormalize)&lt;/code&gt;的方式，它把原来正规化的需要聚合Join的数据，通过反正规化方式预先聚合并缓存，这样可以大大加快后续的查询。另外，学过数据库的同学应该知道，数据库当中有      &lt;code&gt;物化视图(Materialized View)&lt;/code&gt;这样一个概念，它本质上也是一种预聚合的思路。物化视图把底层的若干张表，以反正规化的方式，实时地聚合起来，提供方便查询的视图View。并且，当底层数据表发生变更的时候，物化视图也可以实时同步这些变更(相当于实时聚合Join)。现在你应该明白，我们这里所讲的数据分发+预聚合方式，其实它的思想和物化视图是相同的，只不过我们这里讲的是      &lt;code&gt;分布式的物化视图&lt;/code&gt;。&lt;/p&gt;    &lt;p&gt;      &lt;strong&gt;注意&lt;/strong&gt;实时预聚合能够大大提升查询的性能，但是技术门槛也比较高。当数据变更发生的时候，或者说当变更数据流过来的时候，你就需要对数据流进行实时运算。这个计算越实时，查询的实时性就越好，当然，所需要的技术门槛也越高。之前我们提到过的Kafka Stream，它就是支持实时流式聚合的一个开源产品。&lt;/p&gt;    &lt;h2&gt;      &lt;a&gt;&lt;/a&gt;CQRS模式&lt;/h2&gt;    &lt;blockquote&gt;      &lt;p&gt;基于数据分发与聚合还有一个名称是CQRS(Command/Query Responsibility Segregation) ，它是将数据的写入和查询职责分离的一种模式&lt;/p&gt;&lt;/blockquote&gt;    &lt;p&gt;上面讲的数据分发+预聚合的方式，在互联网领域还有一个更时髦的名称，叫      &lt;code&gt;CQRS&lt;/code&gt;，英文全称是Command/Query Responsibility Segregation，翻译成中文是      &lt;code&gt;命令/查询职责分离模式&lt;/code&gt;。&lt;/p&gt;    &lt;p&gt;      &lt;img alt="" src="https://img-blog.csdnimg.cn/img_convert/f93e7fab7c95814fdf3f4663523eb3e7.png"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;p&gt;这个模式的总体形态，如上图所示。CQRS的左边是Command命令端，这一端通常只负责写入。CQRS的右边是Query查询端，这一端通常只负责读取。底层一般是数据分发技术，比如事务性发件箱、CDC还有MQ，它们将命令端的变更数据，实时或者近实时地同步到查询端。&lt;/p&gt;    &lt;p&gt;      &lt;code&gt;写入端的数据存储，通常采用传统SQL数据库(MySQL Oracle DB2)&lt;/code&gt;。而      &lt;code&gt;查询端则可以根据需要选择最适合的存储机制&lt;/code&gt;，比如说如果通过KV键查询的话，可以采用Redis或者Cassandra；通过关键字查询的话，可以采用ElasticSearch。当然，还可以引入离线批处理Hadoop，甚至是实时计算平台Spark/Flink/KafkaStream等。不管查询端采用何种存储技术，它们的目标都是提升查询的规模化和性能。&lt;/p&gt;    &lt;p&gt;总体上，从命令端到查询端，数据的流动变化过程，就是一个反正规化，适合各种快速查询需求的过程。在三层应用时代，为了提高查询性能，我们通常采用      &lt;code&gt;数据库的读写分离技术&lt;/code&gt;。到了微服务时代，这个技术的思路仍然适用，只不过它向上提升到服务层，演变成CQRS模式了。所以也可以说，      &lt;code&gt;CQRS是服务层的读写分离技术&lt;/code&gt;(读服务和写服务)。&lt;/p&gt;    &lt;p&gt;**注意：**合理应用CQRS技术，可以大大提升查询的性能，同时提升企业数据规模化的能力。但是对于CQRS/CDC这类技术，它们的技术门槛高，一般小公司可能玩不起，只有到一定体量的公司才会考虑，例：Netflix的落地案例。&lt;/p&gt;    &lt;h3&gt;      &lt;a&gt;&lt;/a&gt;CQRS和最终一致性&lt;/h3&gt;    &lt;p&gt;      &lt;img alt="" src="https://img-blog.csdnimg.cn/img_convert/df384c894864479c681f8411057775fd.png"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;p&gt;采用CQRS模式以后，客户从命令端写入数据，然后变更数据分发到查询端，查询端再聚合生成查询视图，这中间难免会有网络和聚合计算延迟，所以这个模式并不保证写入和查询数据的强一致性，而是演变成最终一致性。&lt;/p&gt;    &lt;p&gt;最终一致性会带来UI更新的问题。举个例子，如PPT所示，用户通过UI到Order订单服务创建一个新订单，这个订单落到订单服务的数据库中，然后订单服务在返回用户响应的同时，后台再异步发消息到Order Query订单查询服务，然后订单查询服务收到消息，就去做聚合更新订单视图的工作，这个工作可能需要耗费一定的时间。如果新视图在被更新之前，用户又通过UI来查询新订单数据，那么他可能会查不到数据。也就是说，CQRS的最终一致特性，会引入一定的时间差，而且这个时间差还是不确定的。&lt;/p&gt;    &lt;p&gt;**总结：**由于聚合的延迟性，CQRS没有强一致，可有最终一致性&lt;/p&gt;    &lt;p&gt;**注意：**考虑到网络的不稳定和不可靠，数据分发组件可能会因为网络等因素而重发数据(      &lt;code&gt;At least Once&lt;/code&gt;至少一次)，所以，查询端一般需要对数据进行去重或者做      &lt;code&gt;幂等&lt;/code&gt;处理（如：kafka消息就可能重复，幂等不可避免）。&lt;/p&gt;    &lt;h3&gt;      &lt;a&gt;&lt;/a&gt;CQRS和UI（前端）更新策略&lt;/h3&gt;    &lt;p&gt;      &lt;img alt="" src="https://img-blog.csdnimg.cn/img_convert/d6abf5cb3324f77937462581ad6d69ec.png"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;p&gt;为了解决最终一致性带来的时间差问题，业界通常有三种实践的UI更新策略，请看上图：&lt;/p&gt;    &lt;p&gt;第一种策略是      &lt;code&gt;乐观更新&lt;/code&gt;。UI在发出请求后，马上更新UI，页面反应已经更新的数据状态。&lt;/p&gt;    &lt;p&gt;例：比方说你点赞了某社交网站上的视频或图片，页面马上会显示一颗红心。然后页面后台再通过ajax等方式查询更新结果，如果确认更新成功，那就不需要做什么；如果确认更新失败，只需将页面状态回滚即可。这种方式仅适用于一些简单的场景。&lt;/p&gt;    &lt;p&gt;第二种策略是采用      &lt;code&gt;拉模式&lt;/code&gt;。UI向命令端发出请求时，请求中带上版本号，然后通过ajax等方式不断轮询查询端，并检查更新后的视图的版本号是否和请求的版本号一致，直到版本号匹配为止，也就是等到视图明确更新成功或失败为止。&lt;/p&gt;    &lt;p&gt;第三种策略是采用      &lt;code&gt;发布订阅模式&lt;/code&gt;。UI向命令端发出请求，同时通过websocket等方式订阅在查询端，查询端更新好视图，通过发消息通知UI更新页面展示。&lt;/p&gt;    &lt;h1&gt;      &lt;a&gt;&lt;/a&gt;架构2005 VS 2016&lt;/h1&gt;    &lt;p&gt;      &lt;img alt="" src="https://img-blog.csdnimg.cn/img_convert/98b1bbc393eda6f18f5d3018d185297a.png"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;p&gt;图是从2005到2016，互联网网站架构发生的变化&lt;/p&gt;    &lt;p&gt;重点提一下2016年的网站架构的几个显著特点：&lt;/p&gt;    &lt;p&gt;第一个当然是中间引入了微服务。微服务可以用不同语言栈开发，而且可以拥有独立的数据存储。&lt;/p&gt;    &lt;p&gt;第二个是      &lt;code&gt;微服务前端引入了BFF聚合服务层，实现实时和强一致性的聚合Join&lt;/code&gt;。&lt;/p&gt;    &lt;p&gt;第三个特点是后台引入了      &lt;code&gt;CQRS/CDC/大数据/AI&lt;/code&gt;等技术。这些技术引入的主要目标，说白了，无非就是对数据库中的数据(包括变更数据)，进行聚合或者再加工计算，生成能够进一步产生业务价值的读视图，再通过微服务或者BFF服务等方式暴露给用户。如果把下半部分逆时针旋转90度，就是一个典型的CQRS模式图。&lt;/p&gt;    &lt;p&gt;从总体架构上看，2016年的网站架构和2005年相比，最大的区别是2016年的网站架构是一个更大规模的读写分离架构。&lt;/p&gt;    &lt;p&gt;本文所讲的内容，包括      &lt;code&gt;微服务架构&lt;/code&gt;，      &lt;code&gt;数据分发技术&lt;/code&gt;，      &lt;code&gt;CDC&lt;/code&gt;，还有      &lt;code&gt;BFF聚合服务&lt;/code&gt;等等是现代网站架构的一个基础。&lt;/p&gt;    &lt;p&gt;参考：https://www.bilibili.com/read/cv7547597&lt;/p&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 />
      <guid isPermaLink="true">https://itindex.net/detail/61702-%E5%BE%AE%E6%9C%8D%E5%8A%A1-%E6%95%B0%E6%8D%AE-%E8%81%9A%E5%90%88</guid>
      <pubDate>Fri, 20 Aug 2021 16:50:46 CST</pubDate>
    </item>
    <item>
      <title>微服务架构设计模式 - XuMinzhe - 博客园</title>
      <link>https://itindex.net/detail/61695-%E5%BE%AE%E6%9C%8D%E5%8A%A1-%E6%9E%B6%E6%9E%84-%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F</link>
      <description>&lt;div&gt;    &lt;p&gt;&lt;/p&gt;    &lt;div&gt;      &lt;div&gt;目录&lt;/div&gt;      &lt;ul&gt;        &lt;li&gt;          &lt;a href="https://www.cnblogs.com/xmzJava/p/12637238.html#&amp;#20160;&amp;#20040;&amp;#26159;&amp;#24494;&amp;#26381;&amp;#21153;&amp;#27169;&amp;#24335;"&gt;什么是微服务模式&lt;/a&gt;&lt;/li&gt;        &lt;li&gt;          &lt;a href="https://www.cnblogs.com/xmzJava/p/12637238.html#&amp;#21333;&amp;#20307;&amp;#32467;&amp;#26500;&amp;#30340;&amp;#21382;&amp;#31243;"&gt;单体结构的历程&lt;/a&gt;&lt;/li&gt;        &lt;li&gt;          &lt;a href="https://www.cnblogs.com/xmzJava/p/12637238.html#&amp;#21333;&amp;#20307;&amp;#22320;&amp;#29425;&amp;#30340;&amp;#38134;&amp;#24377;-&amp;#24494;&amp;#26381;&amp;#21153;&amp;#26550;&amp;#26500;"&gt;单体地狱的银弹-微服务架构&lt;/a&gt;          &lt;ul&gt;            &lt;li&gt;              &lt;a href="https://www.cnblogs.com/xmzJava/p/12637238.html#&amp;#25193;&amp;#23637;&amp;#31435;&amp;#26041;&amp;#20307;&amp;#21644;&amp;#26381;&amp;#21153;"&gt;扩展立方体和服务&lt;/a&gt;&lt;/li&gt;            &lt;li&gt;              &lt;a href="https://www.cnblogs.com/xmzJava/p/12637238.html#&amp;#24494;&amp;#26381;&amp;#21153;&amp;#26550;&amp;#26500;&amp;#30340;&amp;#22909;&amp;#22788;&amp;#21644;&amp;#24330;&amp;#31471;"&gt;微服务架构的好处和弊端&lt;/a&gt;              &lt;ul&gt;                &lt;li&gt;                  &lt;a href="https://www.cnblogs.com/xmzJava/p/12637238.html#&amp;#20248;&amp;#28857;"&gt;优点&lt;/a&gt;                  &lt;ul&gt;                    &lt;li&gt;                      &lt;a href="https://www.cnblogs.com/xmzJava/p/12637238.html#&amp;#22823;&amp;#22411;&amp;#30340;&amp;#22797;&amp;#26434;&amp;#24212;&amp;#29992;&amp;#31243;&amp;#24207;&amp;#21487;&amp;#20197;&amp;#25345;&amp;#32493;&amp;#20132;&amp;#20184;&amp;#21644;&amp;#25345;&amp;#32493;&amp;#37096;&amp;#32626;"&gt;大型的复杂应用程序可以持续交付和持续部署&lt;/a&gt;&lt;/li&gt;                    &lt;li&gt;                      &lt;a href="https://www.cnblogs.com/xmzJava/p/12637238.html#&amp;#27599;&amp;#20010;&amp;#26381;&amp;#21153;&amp;#37117;&amp;#30456;&amp;#23545;&amp;#36739;&amp;#23567;&amp;#24182;&amp;#23481;&amp;#26131;&amp;#32500;&amp;#25252;"&gt;每个服务都相对较小并容易维护&lt;/a&gt;&lt;/li&gt;                    &lt;li&gt;                      &lt;a href="https://www.cnblogs.com/xmzJava/p/12637238.html#&amp;#26356;&amp;#22909;&amp;#30340;&amp;#23481;&amp;#38169;&amp;#24615;"&gt;更好的容错性&lt;/a&gt;&lt;/li&gt;                    &lt;li&gt;                      &lt;a href="https://www.cnblogs.com/xmzJava/p/12637238.html#&amp;#26356;&amp;#23481;&amp;#26131;&amp;#23454;&amp;#39564;&amp;#21644;&amp;#37319;&amp;#32435;&amp;#26032;&amp;#30340;&amp;#25216;&amp;#26415;"&gt;更容易实验和采纳新的技术&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;                &lt;li&gt;                  &lt;a href="https://www.cnblogs.com/xmzJava/p/12637238.html#&amp;#24330;&amp;#31471;"&gt;弊端&lt;/a&gt;                  &lt;ul&gt;                    &lt;li&gt;                      &lt;a href="https://www.cnblogs.com/xmzJava/p/12637238.html#&amp;#26381;&amp;#21153;&amp;#30340;&amp;#25286;&amp;#20998;&amp;#21644;&amp;#23450;&amp;#20041;&amp;#26159;&amp;#19968;&amp;#39033;&amp;#25361;&amp;#25112;"&gt;服务的拆分和定义是一项挑战&lt;/a&gt;&lt;/li&gt;                    &lt;li&gt;                      &lt;a href="https://www.cnblogs.com/xmzJava/p/12637238.html#&amp;#20998;&amp;#24067;&amp;#24335;&amp;#31995;&amp;#32479;&amp;#24102;&amp;#26469;&amp;#30340;&amp;#21508;&amp;#31181;&amp;#22797;&amp;#26434;&amp;#24615;"&gt;分布式系统带来的各种复杂性&lt;/a&gt;&lt;/li&gt;                    &lt;li&gt;                      &lt;a href="https://www.cnblogs.com/xmzJava/p/12637238.html#&amp;#24320;&amp;#21457;&amp;#32773;&amp;#38656;&amp;#35201;&amp;#24605;&amp;#32771;&amp;#21040;&amp;#24213;&amp;#24212;&amp;#35813;&amp;#22312;&amp;#24212;&amp;#29992;&amp;#30340;&amp;#20160;&amp;#20040;&amp;#38454;&amp;#27573;&amp;#20351;&amp;#29992;&amp;#24494;&amp;#26381;&amp;#21153;&amp;#26550;&amp;#26500;"&gt;开发者需要思考到底应该在应用的什么阶段使用微服务架构&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;        &lt;li&gt;          &lt;a href="https://www.cnblogs.com/xmzJava/p/12637238.html#&amp;#26381;&amp;#21153;&amp;#30340;&amp;#25286;&amp;#20998;&amp;#31574;&amp;#30053;"&gt;服务的拆分策略&lt;/a&gt;          &lt;ul&gt;            &lt;li&gt;              &lt;a href="https://www.cnblogs.com/xmzJava/p/12637238.html#&amp;#35782;&amp;#21035;&amp;#31995;&amp;#32479;&amp;#25805;&amp;#20316;"&gt;识别系统操作&lt;/a&gt;              &lt;ul&gt;                &lt;li&gt;                  &lt;a href="https://www.cnblogs.com/xmzJava/p/12637238.html#&amp;#21019;&amp;#24314;&amp;#25277;&amp;#35937;&amp;#39046;&amp;#22495;&amp;#27169;&amp;#22411;"&gt;创建抽象领域模型&lt;/a&gt;&lt;/li&gt;                &lt;li&gt;                  &lt;a href="https://www.cnblogs.com/xmzJava/p/12637238.html#&amp;#23450;&amp;#20041;&amp;#31995;&amp;#32479;&amp;#25805;&amp;#20316;"&gt;定义系统操作&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;            &lt;li&gt;              &lt;a href="https://www.cnblogs.com/xmzJava/p/12637238.html#&amp;#26681;&amp;#25454;&amp;#19994;&amp;#21153;&amp;#33021;&amp;#21147;&amp;#36827;&amp;#34892;&amp;#26381;&amp;#21153;&amp;#25286;&amp;#20998;"&gt;根据业务能力进行服务拆分&lt;/a&gt;              &lt;ul&gt;                &lt;li&gt;                  &lt;a href="https://www.cnblogs.com/xmzJava/p/12637238.html#&amp;#20174;&amp;#19994;&amp;#21153;&amp;#33021;&amp;#21147;&amp;#21040;&amp;#26381;&amp;#21153;"&gt;从业务能力到服务&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;            &lt;li&gt;              &lt;a href="https://www.cnblogs.com/xmzJava/p/12637238.html#&amp;#26681;&amp;#25454;&amp;#23376;&amp;#22495;&amp;#36827;&amp;#34892;&amp;#26381;&amp;#21153;&amp;#25286;&amp;#20998;"&gt;根据子域进行服务拆分&lt;/a&gt;&lt;/li&gt;            &lt;li&gt;              &lt;a href="https://www.cnblogs.com/xmzJava/p/12637238.html#&amp;#19978;&amp;#24093;&amp;#31867;&amp;#30340;&amp;#22788;&amp;#29702;"&gt;上帝类的处理&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;    &lt;p&gt;&lt;/p&gt;    &lt;h1&gt;什么是微服务模式&lt;/h1&gt;    &lt;p&gt;随着网络基础设施的高速发展，以及越来越多的个体接入互联网，在考虑构建支持海量请求以及多变业务的软件平台时，微服务架构成为多数人的首选。微服务架构的出现时服务事物发展规律的：当问题足够大，有足够多的的不确定因素时，人们习惯于把大的问题拆分成小的问题。通过分割，抽象和重用小而可靠的功能模块来构建整体方案。但是当这些小的，可重用的部分多来越多的时候，又会出现新的问题。再相似的阶段，人们遇到的问题也是相似的，这个时候人们需要一些共识，需要用一些通用的词汇来描述问题以及解决方案，这也是人们知识的总结，微服务模式就是这样的总结和概括，是一种可以通用的共识，用于描述微服务领域的中的问题及解决方案。&lt;/p&gt;    &lt;h1&gt;单体结构的历程&lt;/h1&gt;    &lt;p&gt;在企业发展的初期，应用程序相对较小，所有的代码运行在一个应用程序中有以下好处&lt;/p&gt;    &lt;ul&gt;      &lt;li&gt;应用的开发很简单：IDE 和其他开发工具只需要构建这一个单独的应用程序。&lt;/li&gt;      &lt;li&gt;易于对应用程序进行大规模的更改：可以更改代码和数据库模式，然后构建和部署。测试相对简单直观：开发者只需要写几个端到端的测试，启动应用程序，调用 REST API，然后使用 Selenium 这样的工具测试用户界面。&lt;/li&gt;      &lt;li&gt;部署简单明了：开发者唯一需要做的，就是把 WAR 文件复制到安装了 Tomcat 的服务器上。&lt;/li&gt;      &lt;li&gt;横向扩展不费吹灰之力：FTGO 可以运行多个实例，由一个负载均衡器进行调度。&lt;/li&gt;&lt;/ul&gt;    &lt;p&gt;不幸的是，开发人员已经意识到的，单体架构存在着巨大的局限性。每一次开发冲刺（Sprint）， 开发团队就会实现更多的功能，显然这会导致代码库膨胀。而且，随着公司的成功，研发团队的规模不断壮大。代码库规模变大的同时，团队的管理成本也不断提高。&lt;/p&gt;    &lt;p&gt;      &lt;img alt="" src="https://img2020.cnblogs.com/blog/1138295/202004/1138295-20200405145605762-666807514.png"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;ol&gt;      &lt;li&gt;过度的复杂性吓退开发者        &lt;br /&gt;系统本身过于庞大和复杂，以至于任何一个开发者都很难理解它的全部。因此，修复软件中的问题和正确地实现新功能就变得困难且耗时。各种交付截止时间都可能被错过。更糟糕的是，这种极度的复杂性正在形成一个恶性循环：由于代码库太难于理解，因此        &lt;br /&gt;开发人员在更改时更容易出错，每一次更改都会让代码库变得更复杂、更难懂&lt;/li&gt;      &lt;li&gt;开发速度缓慢        &lt;br /&gt;巨大的项目把开发人员的 IDE 工具搞得很慢，构建一次应用需要很长时间，更要命的是，因为应用太大，每启动一次都需要很长的时间。因此，从编辑到构建、运行再到测试这个周期花费的时间越来越长，这严重地影响了团队的工作效率。&lt;/li&gt;      &lt;li&gt;从代码提交到实际部署的周期很长，而且容易出问题        &lt;br /&gt;从代码完成到运行在生产环境是一个漫长且费力的过程。一个问题是，众多开发人员都向同一个代码库提交代码更改，这常常使得这个代码库的构建结果处于无法交付的状态。当我们尝试采用功能分支来解决这个问题时，带来的是漫长且痛苦的合并过程。紧接着，一旦团队完成一个冲刺任务，随后迎接他们的将是一个漫长的测试和代码稳定周期。&lt;/li&gt;      &lt;li&gt;把更改推向生产环境的另一个挑战是运行测试需要很长时间。因为代码库如此复杂，以至于一个更改可能引起的影响是未知的，为了避免牵一发而动全身的后果，即使是一个微小的更改，开发人员也必须在持续集成服务器上运行所有的测试套件。系统的某些部分甚至还需要手工测试。如果测试失败，诊断和修复也需要更多的时间。因此，完成这样的测试往往需要数天甚至更长时间。&lt;/li&gt;      &lt;li&gt;需要长期依赖某个可能已经过时的技术栈        &lt;br /&gt;单体地狱的最终表现，也体现在团队必须长期使用一套相同的技术栈方面。单体架构使得采用新的框架和编程语言变得极其困难。在单体应用上采用新技术或者尝试新技术都是极其昂贵和高风险的，因为这个应用必须被彻底重写。结果就是，开发者被困在了他们一开始选择的这个技术之内。有时候这也就意味着团队必须维护一个正在被废弃或过时的技术所开发的应用程序。&lt;/li&gt;&lt;/ol&gt;    &lt;h1&gt;单体地狱的银弹-微服务架构&lt;/h1&gt;    &lt;p&gt;软件架构其实对功能性需求影响并不大。事实上，在任何架构甚至是一团糟的架构之上，你都可以实现一组用例（应用的功能性需求）      &lt;br /&gt;架构的重要性在于它影响了应用的非功能性需求，也称为质量属性或者其他的能力 。随着 FTGO 应用的增长，各种质量属性和问题都浮出水面，最显著的就是影响软件交付速度的可维护性、可扩展性和可测试性。 训练有素的团队可以减缓项目陷入单体地狱的速度。团队成员可以努力维护他们的模块化应用。他们也可以编写全面的自动化测试。但是另一方面，他们无法避免大型团队在单体应用程序上协同工作的问题，也不能解决日益过时的技术栈问题。团队所能做的就是延缓项目陷入单体地狱的速度，但这是不可避免的。为了逃避单体地狱，他们必须迁移到新架构：微服务架构。&lt;/p&gt;    &lt;p&gt;今天，针对大型复杂应用的开发，越来越多的共识趋向于考虑使用微服务架构。但微服务到底是什么？不幸的是，微服务这个叫法本身暗示和强调了尺寸 。针对微服务架构有多种定义。有些仅仅是在字面意义上做了定义：服务应该是微小的不超过 100 行代码，等等。另外有些定义要求服务的开发周期必须被限制在两周之内。曾在 Netflix 工作的著名架构师Adrian Cockcroft 把微服务架构定义为面向服务的架构，它们由松耦合和具有边界上下文的元素组成。这个定义不错，但仍旧有些复杂难懂。立方体模型会是更好的定义。&lt;/p&gt;    &lt;h2&gt;扩展立方体和服务&lt;/h2&gt;    &lt;p&gt;      &lt;img alt="" src="https://img2020.cnblogs.com/blog/1138295/202004/1138295-20200405151213929-1640350808.png"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;ul&gt;      &lt;li&gt;X 轴扩展：在多个实例之间实现请求的负载均衡&lt;/li&gt;&lt;/ul&gt;    &lt;p&gt;X 轴扩展是扩展单体应用程序的常用方法。在负载均衡器之后运行应用程序的多个实例。负载均衡器在 N 个相同的实例之间分配请求。这是提高应用程序吞吐量和可用性的好方法      &lt;br /&gt;      &lt;img alt="" src="https://img2020.cnblogs.com/blog/1138295/202004/1138295-20200405151319532-447661189.png"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;ul&gt;      &lt;li&gt;        &lt;p&gt;Z 轴扩展：根据请求的属性路由请求          &lt;br /&gt;Z 轴扩展也需要运行单体应用程序的多个实例，但不同于 X 轴扩展，每个实例仅负责数据的一个子集。图 1-5 展示了 Z 轴扩展的工作原理。置于前端的路由器使用请求中的特定属性将请求路由到适当的实例。例如，应用程序可能会使用请求中包含的 userId 来路由请求。在这个例子中，每个应用程序实例负责一部分用户。该路由器使用请求 Authorization头部指定的 userId 来从 N 个相同的应用程序实例中选择一个。对于应用程序需要处理增加的事务和数据量时，Z 轴扩展是一种很好的扩展方式          &lt;br /&gt;          &lt;img alt="" src="https://img2020.cnblogs.com/blog/1138295/202004/1138295-20200405151429948-1131434272.png"&gt;&lt;/img&gt;&lt;/p&gt;&lt;/li&gt;      &lt;li&gt;        &lt;p&gt;Y轴扩展：根据功能把应用拆分为服务          &lt;br /&gt;X 轴和 Z 轴扩展有效地提升了应用的吞吐量和可用性，然而这两种方式都没有解决日益增长的开发问题和应用复杂性。为了解决这些问题，我们需要采用 Y 轴扩展，也就是功能性分解。Y 轴扩展把一个单体应用分成了一组服务          &lt;br /&gt;          &lt;img alt="" src="https://img2020.cnblogs.com/blog/1138295/202004/1138295-20200405151518606-1435624233.png"&gt;&lt;/img&gt;&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;    &lt;p&gt;服务本质上是一个麻雀虽小但五脏俱全的应用程序，它实现了一组相关的功能，例如订单管理、客户管理等。服务可以在需要的时候借助 X 轴或 Z 轴方式进行扩展。例如，订单服务可以被部署为一组负载均衡的服务实例。      &lt;br /&gt;微服务架构的概括性定义是：把应用程序功能性分解为一组服务的架构风格。请注意这个定义中并没有包含任何与规模有关的内容。重要的是，每一个服务都是由一组专注的、内聚的功能职责组成。&lt;/p&gt;    &lt;h2&gt;微服务架构的好处和弊端&lt;/h2&gt;    &lt;h3&gt;优点&lt;/h3&gt;    &lt;h4&gt;大型的复杂应用程序可以持续交付和持续部署&lt;/h4&gt;    &lt;ul&gt;      &lt;li&gt;拥有持续交付和持续部署所需要的可测试性。自动化测试是持续交付和持续部署的一个重要环节。因为每一个服务都相对较小，编写和执行自动化测试变得很容易。因此，应用程序的 bug 也就更少。&lt;/li&gt;      &lt;li&gt;拥有持续交付和持续部署所需要的可部署性。每个服务都可以独立于其他服务进行部署。如果负责服务的开发人员需要部署对该服务的更改，他们不需要与其他开发人员协调就可以进行。因此，将更改频繁部署到生产中要容易得多&lt;/li&gt;      &lt;li&gt;使开发团队能够自主且松散耦合。你可以将工程组织构建为一个小型（例如，两个比萨 ）团队的集合。每个团队全权负责一个或多个相关服务的开发和部署。每个团队可以独立于所有其他团队开发、部署和扩展他们的服务。结果，开发        &lt;br /&gt;的速度变得更快        &lt;br /&gt;        &lt;img alt="" src="https://img2020.cnblogs.com/blog/1138295/202004/1138295-20200405152249628-531796733.png"&gt;&lt;/img&gt;&lt;/li&gt;&lt;/ul&gt;    &lt;h4&gt;每个服务都相对较小并容易维护&lt;/h4&gt;    &lt;p&gt;微服务架构的另一个好处在于：相比之下每个服务都比较小。开发者更容易理解服务中的代码。较小规模的代码库不会把 IDE 等开发工具拖慢，这样可以提升开发者的工作效率。服务的启动速度也比大型的单体应用快得多，千万别小看这一点，快速启动的服务会提高效率，加速研发（提高调试、部署等环节的效率）。&lt;/p&gt;    &lt;h4&gt;更好的容错性&lt;/h4&gt;    &lt;p&gt;微服务架构也可以实现更好的故障隔离。例如，某个服务中的内存泄漏不会影响其他服务。其他服务仍旧可以正常地响应请求。相比之下，单体架构中的一个故障组件往往会拖垮整个系统&lt;/p&gt;    &lt;h4&gt;更容易实验和采纳新的技术&lt;/h4&gt;    &lt;p&gt;原则上，当开发一个新的服务时，开发者可以自由选择适用于这个服务的任何语言和框架。当然，很多公司对此往往有各种限制和规范，但重要的是团队有了选择的权利，而不是被之前选定的技术绑架。更进一步，因为服务都相对比较小，使用更好的编程语言和技术来重写一项服务变得有可能。这也意味着，如果对一项新技术的尝试以失败而告终，我们可以直接丢弃这部分工作而不至于给整个应用带来失败的风险。这跟单体架构是完全不同的，单体架构之下的技术选型会严重限制后期新技术的尝试&lt;/p&gt;    &lt;h3&gt;弊端&lt;/h3&gt;    &lt;p&gt;当然，没有一项技术可以被称为“银弹” 。微服务架构也存在一些显著的弊端和问题      &lt;br /&gt;微服务架构的主要弊端和问题如下：&lt;/p&gt;    &lt;h4&gt;服务的拆分和定义是一项挑战&lt;/h4&gt;    &lt;p&gt;采用微服务架构首当其冲的问题，就是根本没有一个具体的、良好定义的算法可以完成服务的拆分工作。与软件开发一样，服务的拆分和定义更像是一门艺术。更糟糕的是，如果对系统的服务拆分出现了偏差，你很有可能会构建出一个分布式的单体应用：一个包含了一大堆互相之间紧耦合的服务，却又必须部署在一起的所谓分布式系统。这将会把单体架构和微服务架构两者的弊端集于一身。&lt;/p&gt;    &lt;h4&gt;分布式系统带来的各种复杂性&lt;/h4&gt;    &lt;p&gt;使用微服务架构的另一个问题是开发人员必须处理创建分布式系统的额外复杂性。服务必须使用进程间通信机制。这比简单的方法调用更复杂。此外，必须设计服务来处理局部故障，并处理远程服务不可用或出现高延迟的各种情况。&lt;/p&gt;    &lt;h4&gt;开发者需要思考到底应该在应用的什么阶段使用微服务架构&lt;/h4&gt;    &lt;p&gt;使用微服务架构的另一个问题是决定在应用程序生命周期的哪个阶段开始使用这种架构。在开发应用程序的第一个版本时，你通常不会遇到需要微服务架构才能解决的问题。此外，使用精心设计的分布式架构将减缓开发速度。这对初创公司来说可能是得不偿失的，其中最大的问题通常是在快速发展业务模型和维护一个优雅的应用架构之间的取舍。微服务架构使得项目开始阶段的快速迭代变得非常困难。初创公司几乎肯定应该从单体的应用程序开始 。但是稍后，当问题变为如何处理复杂性时，那就是将应用程序功能性地分解为一组服务的时候了。由于盘根错节的依赖关系，你会发现重构很困难&lt;/p&gt;    &lt;h1&gt;服务的拆分策略&lt;/h1&gt;    &lt;p&gt;如何定义一个微服务架构呢？跟所有的软件开发过程一样，一开始我们需要拿到领域专家或者现有应用的需求文档。跟所有的软件开发一样，定义架构也是一项艺术而非技术。本节我们将介绍一种定义应用程序架构的三步式流程&lt;/p&gt;    &lt;p&gt;定义其架构的第一步是将应用程序的需求提炼为各种关键请求。但是，不是根据特定的进程间通信技术（如 REST 或消息）来描述这些请求，而是使用更抽象的系统操作这个概念。系统操作（system operation）是应用程序必须处理的请求的一种抽象描述。它既可以是更新数据的命令，也可以是检索数据的查询。每个命令的行为都是根据抽象领域模型定义的，抽象领域模型也是从需求中派生出来的。系统操作是描述服务之间协作方式的架构场景      &lt;br /&gt;      &lt;img alt="" src="https://img2020.cnblogs.com/blog/1138295/202004/1138295-20200405155632084-219230946.png"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;p&gt;该流程的第二步是确定如何分解服务。有几种策略可供选择。一种源于业务架构学派的策略是定义与业务能力相对应的服务。另一种策略是围绕领域驱动设计的子域来分解和设计服务。但这些策略的最终结果都是围绕业务概念而非技术概念分解和设计的服务。      &lt;br /&gt;      &lt;img alt="" src="https://img2020.cnblogs.com/blog/1138295/202004/1138295-20200405155944064-1626602147.png"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;p&gt;定义应用程序架构的第三步是确定每个服务的 API。为此，你将第一步中标识的每个系统操作分配给服务。服务可以完全独立地实现操作。或者，它可能需要与其他服务协作。在这种情况下，你可以确定服务的协作方式，这通常需要服务来支持其他操作&lt;/p&gt;    &lt;p&gt;      &lt;img alt="" src="https://img2020.cnblogs.com/blog/1138295/202004/1138295-20200405160050772-1942915995.png"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;h2&gt;识别系统操作&lt;/h2&gt;    &lt;p&gt;定义应用程序架构的第一步是定义系统操作。起点是应用程序的需求，包括用户故事及其相关的用户场景（请注意，这些与架构场景不同）。使用图 2-6 中所示的两步式流程识别和定义系统操作。第一步创建由关键类组成的抽象领域模型，这些关键类提供用于描述系统操作的词汇表。第二步确定系统操作，并根据领域模型描述每个系统操作的行为。&lt;/p&gt;    &lt;p&gt;      &lt;img alt="" src="https://img2020.cnblogs.com/blog/1138295/202004/1138295-20200405160440625-686337166.png"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;h3&gt;创建抽象领域模型&lt;/h3&gt;    &lt;p&gt;定义系统操作的第一步是为这个应用程序描绘一个抽象的领域模型。注意这个模型比我们最终要实现的简单很多。应用程序本身并不需要一个领域模型，因为我们在稍后会学到，每一个服务都有它自己的领域模型。尽管非常简单，抽象的领域模型仍旧有助于在开始阶段提供帮助，因为它定义了描述系统操作行为的一些词语      &lt;br /&gt;创建领域模型会采用一些标准的技术，例如通过与领域专家沟通后，分析用户故事和场景中频繁出现的名词。例如 Place Order 用户故事，我们可以把它分解为多个用户场景，&lt;/p&gt;    &lt;pre&gt;      &lt;code&gt;Given a consumer
And a restaurant
And a delivery address/time that can be served by that restaurant
And an order total that meets the restaurant&amp;apos;s order minimum
When the consumer places an order for the restaurant
Then consumer&amp;apos;s credit card is authorized
And an order is created in the PENDING_ACCEPTANCE state
And the order is associated with the consumer
And the order is associated with the restaurant&lt;/code&gt;&lt;/pre&gt;    &lt;p&gt;在这个用户场景中的名词，如 Consumer、Order、Restaurant 和 CreditCard，暗示了这些类都是需要的&lt;/p&gt;    &lt;p&gt;同样，Accept Order 用户故事也可以分解为多个场景，如下&lt;/p&gt;    &lt;pre&gt;      &lt;code&gt;Given an order that is in the PENDING_ACCEPTANCE state
and a courier that is available to deliver the order
When a restaurant accepts an order with a promise to prepare by a particular
time
Then the state of the order is changed to ACCEPTED
And the order&amp;apos;s promiseByTime is updated to the promised time
And the courier is assigned to deliver the order&lt;/code&gt;&lt;/pre&gt;    &lt;p&gt;经过分析最终我们可以得出如下的类图结构      &lt;br /&gt;      &lt;img alt="" src="https://img2020.cnblogs.com/blog/1138295/202004/1138295-20200405161408892-962344679.png"&gt;&lt;/img&gt;      &lt;br /&gt;每一个类的作用如下：&lt;/p&gt;    &lt;pre&gt;      &lt;code&gt;■ Consumer：下订单的用户。
■ Order：用户下的订单，它用来描述订单并跟踪状态。
■ OrderLineItem：Order 中的一个条目。
■ DeliveryInfo：送餐的时间和地址。
■ Restaurant：为用户准备生产订单的餐馆，同时也要发起送货。
■ MenuItem：餐馆菜单上的一个条目。
■ Courier：送餐员负责把订单送到用户手里。可跟踪送餐员的可用性和他们的位置。
■ Address：Consumer 或 Restaurant 的地址。
■ Location：Courier 当前的位置，用经纬度表示&lt;/code&gt;&lt;/pre&gt;    &lt;h3&gt;定义系统操作&lt;/h3&gt;    &lt;p&gt;当定义了抽象的领域模型之后，接下来就要识别系统必须处理的各种请求。我们并不讨论具体的用户界面，但是你能够想象在每一个用户场景下，前端的用户界面向后端的业务逻辑发出请求，后端的业务逻辑进行数据的获取和处理&lt;/p&gt;    &lt;p&gt;识别系统指令的切入点是分析用户故事和场景中的动词。例如 Place Order 用户故事，它非常明确地告诉我们，这个系统必须提供一个 Create Order 操作。很多用户故事都会直接对应或映射为系统命令。表 2-1 列出了一些关键的系统命令      &lt;br /&gt;      &lt;img alt="" src="https://img2020.cnblogs.com/blog/1138295/202004/1138295-20200405161732278-989438080.png"&gt;&lt;/img&gt;      &lt;br /&gt;命令规范定义了命令对应的参数、返回值和领域模型类的行为。行为规范中包括前置条件（即当这个操作被调用时必须满足的条件）和后置条件（即这个操作被调用后必须满足的条件）。例如，以下就是 createOrder() 系统操作的规范。      &lt;br /&gt;      &lt;img alt="" src="https://img2020.cnblogs.com/blog/1138295/202004/1138295-20200405161802239-499136594.png"&gt;&lt;/img&gt;      &lt;br /&gt;前置条件对应着 Place Order 用户场景中的 givens，后置条件对应着场景中的Then。当系统操作被调用时，它会检查前置条件，执行操作来完成和满足后置条件。&lt;/p&gt;    &lt;p&gt;抽象的领域模型和系统操作能够回答这个应用“做什么”这一问题。这有助于推动应用程序的架构设计。每一个系统操作的行为都通过领域模型的方式来描述。每一个重要的系统操作都对应着架构层面的一个重大场景，是架构中需要详细描述和特别考虑的地方。现在我们来看看如何定义应用程序的微服务架构&lt;/p&gt;    &lt;p&gt;系统操作被定义后，下一步就是完成应用服务的识别。如之前提到的，这并不是一个机械化的流程，相反，有多种拆分策略可供选择。每一种都是从一个侧面来解决问题，并且使用它们独有的一些术语。但是殊途同归，这些策略的结果都是一样的：一个包含若干服务的架构，这样的架构是以业务而不是技术概念为中心&lt;/p&gt;    &lt;h2&gt;根据业务能力进行服务拆分&lt;/h2&gt;    &lt;p&gt;创建微服务架构的策略之一就是采用业务能力进行服务拆分。业务能力是一个来自于业务架构建模的术语。业务能力是指一些能够为公司（或组织）产生价值的商业活动。特定业务的业务能力取决于这个业务的类型。例如，保险公司业务能力通常包括承保、理赔管理、账务和合规等。在线商店的业务能力包括：订单管理、库存管理和发货      &lt;br /&gt;组织的业务能力通常是指这个组织的业务是做什么，它们通常都是稳定的。与之相反，组织采用何种方式来实现它的业务能力，是随着时间不断变化的。这个准则在今天尤其明显，很多新技术在被快速采用，商业流程的自动化程度越来越高。例如，不久之前你还通过把支票交给银行柜员的方式来兑现支票，现在很多 ATM 机都支持直接兑现支票，而今，人们甚至可以使用智能手机拍照的方式来兑现支票。正如你所见，“兑现支票”这个业务能力是稳定不变的，但是这个能力的实现方式正在发生戏剧性的变化&lt;/p&gt;    &lt;h3&gt;从业务能力到服务&lt;/h3&gt;    &lt;p&gt;一旦确定了业务能力，就可以为每个能力或相关能力组定义服务。下图显示了 FTGO应用程序从能力到服务的映射。某些顶级能力（如会计记账能力）将映射到服务。在其他情况下，子能力映射到服务      &lt;br /&gt;      &lt;img alt="" src="https://img2020.cnblogs.com/blog/1138295/202004/1138295-20200405162830362-1290917529.png"&gt;&lt;/img&gt;      &lt;br /&gt;上图显示的服务仅仅是定义架构的第一次尝试。随着我们对应用程序领域的了解越来越多，它们可能会随着时间的推移而变化，特别是架构定义流程中的一个重要步骤是调查服务如何在每个关键架构服务中协作。例如，你可能会发现由于过多的进程间通信而导致特定的分解效率低下，导致你必须把一些服务组合在一起。相反，服务可能会在复杂性方面增长到值得将其拆分为多个服务的程度&lt;/p&gt;    &lt;h2&gt;根据子域进行服务拆分&lt;/h2&gt;    &lt;p&gt;领域驱动设计是构建复杂软件的方法论，这些软件通常都以面向对象和领域模型为核心。领域模型以解决具体问题的方式包含了一个领域内的知识。它定义了当前领域相关团队的词汇表，DDD 也称之为通用语言（Ubiquitous language）。领域模型会被紧密地映射到应用的设计和实现环节。在微服务架构的设计层面，DDD 有两个特别重要的概念，子域和限界上下文      &lt;br /&gt;领域驱动为每一个子域定义单独的领域模型。子域是领域的一部分，领域是 DDD 中用来描述应用程序问题域的一个术语。识别子域的方式跟识别业务能力一样：分析业务并识别业务的不同专业领域，分析产出的子域定义结果也会跟业务能力非常接近。FTGO 的子域包括：订单获取、订单管理、餐馆管理、送餐和会计。正如你所见：这些子域跟我们之前定义的业务能力非常接近。      &lt;br /&gt;DDD 把领域模型的边界称为限界上下文（bounded context）。限界上下文包括实现这个模型的代码集合。当使用微服务架构时，每一个限界上下文对应一个或者一组服务。换一种说法，我们可以通过 DDD 的方式定义子域，并把子域对应为每一个服务，这样就完成了微服务架构的设计工作。图 2-9 展示了子域和服务之间的映射，每一个子域都有属于它们自己的领域模型。      &lt;br /&gt;      &lt;img alt="" src="https://img2020.cnblogs.com/blog/1138295/202004/1138295-20200405163156133-1512419394.png"&gt;&lt;/img&gt;      &lt;br /&gt;DDD 和微服务架构简直就是天生一对。DDD 的子域和限界上下文的概念，可以很好地跟微服务架构中的服务进行匹配。而且，微服务架构中的自治化团队负责服务开发的概念，也跟 DDD 中每个领域模型都由一个独立团队负责开发的概念吻合。更有趣的是，子域用于它自己的领域模型这个概念，为消除上帝类和优化服务拆分提供了好办法&lt;/p&gt;    &lt;h2&gt;上帝类的处理&lt;/h2&gt;    &lt;p&gt;上帝类是在整个应用程序中使用的全局类。上帝类通常为应用程序的许多不同方面实现业务逻辑。它有大量字段映射到具有许多列的数据库表。大多数应用程序至少有一个这样的上帝类。Order 类是 FTGO 应用程序中上帝类的一个很好的例子。这并不奇怪：毕竟 FTGO 的      &lt;br /&gt;目的是向客户提供食品订单。系统的大多数部分都涉及订单。如果 FTGO 应用程序具有单个领域模型，则 Order 类将是一个非常大的类。它将具有与应用程序的许多不同部分相对应的状态和行为。下图显示了使用传统建模技术创建的 Order 类的结构      &lt;br /&gt;      &lt;img alt="" src="https://img2020.cnblogs.com/blog/1138295/202004/1138295-20200405163834623-1610602185.png"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;p&gt;Order 类具有与订单处理、餐馆订单管理、送餐和付款相对应的字段及方法。由于一个模型必须描述来自应用程序的不同部分的状态转换，因此该类还具有复杂的状态模型。在目前情况下，这个类的存在使得将代码分割成服务变得极其困难&lt;/p&gt;    &lt;p&gt;一种解决方案是将 Order 类打包到库中并创建一个中央 Order 数据库。处理订单的所有服务都使用此库并访问访问数据库。这种方法的问题在于它违反了微服务架构的一个关键原则，并导致我们特别不愿意看到的紧耦合。例如，对 Order 模式的任何更改都要求其他开发团队同步更新和重新编译他们的代码。&lt;/p&gt;    &lt;p&gt;另一种解决方案是将 Order 数据库封装在 Order Service 中，该服务由其他服务调用以检索和更新订单。该设计的问题在于这样的一个 Order Service 将成为一个纯数据服务，成为包含很少或没有业务逻辑的贫血领域模型（anemic domain model）。这两种解决方案都没有吸引力，但幸运的是，DDD 提供了一个好的解决方案。&lt;/p&gt;    &lt;p&gt;更好的方法是应用 DDD 并将每个服务视为具有自己的领域模型的单独子域。这意味着FTGO 应用程序中与订单有关的每个服务都有自己的领域模型及其对应的 Order 类的版本。Delivery Service 是多领域模型的一个很好的例子。如图 2-11 所示为 Order，它非常简单：取餐地址、取餐时间、送餐地址和送餐时间。此外，Delivery Service 使用更合适的 Delivery 名称，而不是称之为 Order。Delivery Service 对订单的任何其他属性不感兴趣&lt;/p&gt;    &lt;p&gt;      &lt;img alt="" src="https://img2020.cnblogs.com/blog/1138295/202004/1138295-20200405163956982-1072417617.png"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;p&gt;Kitchen Service 有一个更简单的订单视图。它的 Order 版本就是一个 Ticket（后 厨 工 单）。如图所 示，Ticket 只包含 status、requestedDeliveryTime、prepareByTime 以及告诉餐馆准备的订单项列表。它不关心消费者、付款、交付等这些与它无关的事情&lt;/p&gt;    &lt;p&gt;      &lt;img alt="" src="https://img2020.cnblogs.com/blog/1138295/202004/1138295-20200405164043268-2004480238.png"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;p&gt;Order Service 具有最复杂的订单视图，如图所示。即使它有相当多的字段和方法，它仍然比原始版本的那个 Order 上帝类简单得多。&lt;/p&gt;    &lt;p&gt;      &lt;img alt="" src="https://img2020.cnblogs.com/blog/1138295/202004/1138295-20200405164122618-1279150038.png"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;p&gt;每个领域模型中的 Order 类表示同一 Order 业务实体的不同方面。FTGO 应用程序必须维持不同服务中这些不同对象之间的一致性。例如，一旦 Order Service 授权消费者的信用卡，它必须触发在 Kitchen Service 中创建 Ticket。同样，如果 Kitchen Service 拒绝订单，则必须在 Order Service 中取消订单，并且为客户退款。我们通常会用用分布式事务去处理这些问题，这又是微服务架构的另一个问题了。&lt;/p&gt;    &lt;hr&gt;&lt;/hr&gt;    &lt;p&gt;参考:克里斯·理查森（Chris Richardson) 微服务架构设计模式&lt;/p&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 />
      <guid isPermaLink="true">https://itindex.net/detail/61695-%E5%BE%AE%E6%9C%8D%E5%8A%A1-%E6%9E%B6%E6%9E%84-%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F</guid>
      <pubDate>Wed, 18 Aug 2021 08:48:44 CST</pubDate>
    </item>
    <item>
      <title>使用Docker+springboot+springcloud+axon 构建CQRS/ES的微服务架构_quguang65265的博客-CSDN博客</title>
      <link>https://itindex.net/detail/61655-docker-springboot-springcloud</link>
      <description>&lt;div&gt;    &lt;blockquote&gt;      &lt;p&gt;原文地址：        &lt;a href="https://benwilcock.wordpress.com/2016/06/20/microservices-with-spring-boot-axon-cqrses-and-docker/"&gt;https://benwilcock.wordpress.com/2016/06/20/microservices-with-spring-boot-axon-cqrses-and-docker/&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&gt;    &lt;p&gt;在过去几年中，软件架构的变化步伐迅速发展。 DevOps，Microservices和Containerisation等新方法已成为热门话题，采用率迅速提高。 在这篇文章中，我想向您介绍一个我一直在研究的微服务项目，它结合了过去几年中两个突出的架构进步：命令和查询责任分离（CQRS）和容器化。&lt;/p&gt;    &lt;p&gt;在第一部分中，我将向您展示使用容器分发和运行多服务器微服务应用程序是多么容易。&lt;/p&gt;    &lt;p&gt;为了做到这一点，我使用Docker创建了一个包含运行演示所需的所有微服务的容器套件。 在撰写本文时，该套件中有七个微服务; 他们是：-      &lt;br /&gt;      &lt;img alt="&amp;#36825;&amp;#37324;&amp;#20889;&amp;#22270;&amp;#29255;&amp;#25551;&amp;#36848;" src="https://img-blog.csdn.net/20180809115508641?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3F1Z3Vhbmc2NTI2NQ==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70" title=""&gt;&lt;/img&gt;&lt;/p&gt;    &lt;p&gt;该演示的源代码可在      &lt;a href="https://github.com/benwilcock/cqrs-microservice-sampler"&gt;Github&lt;/a&gt;上获得，并演示如何实现和集成“云原生”Java所需的一些功能，包括： -      &lt;br /&gt;- 使用Java和Spring Boot的微服务;      &lt;br /&gt;- 使用Docker容器在任何地方构建，发送和运行;      &lt;br /&gt;- 使用Axon Framework v2，MongoDB和RabbitMQ进行命令和查询责任分离（CQRS）和事件源（ES）;      &lt;br /&gt;- 使用Spring Cloud进行集中配置，服务注册和API网关;&lt;/p&gt;    &lt;h3&gt;How it works&lt;/h3&gt;    &lt;p&gt;这里介绍的微服务示例项目围绕着一个虚构的“product”主数据应用程序，类似于您在大多数零售或制造公司中找到的应用程序。可以使用简单的RESTful服务API从此主数据中添加，存储，搜索和检索产品。随着更改的发生，通过使用消息发送通知给感兴趣的各方。&lt;/p&gt;    &lt;p&gt;Product Data应用程序使用CQRS架构风格构建。在诸如      &lt;code&gt;ADD&lt;/code&gt;之类的CQRS命令中，物理上与诸如’VIEW（其中id = 1）`之类的查询分开。实际上，在这个特定的例子中，Product域的代码库已经完全分为两个独立的组件 - 命令端微服务和查询端微服务。&lt;/p&gt;    &lt;p&gt;像大多数      &lt;a href="http://12factor.net/"&gt;12 factor apps&lt;/a&gt;一样，每个微服务都有一个单一的责任;拥有自己的数据存储区;并且可以独立于另一个进行部署和扩展。这是CQRS和微服务中最直译的解释。 CQRS或微服务都不能以这种方式实现，但为了本演示的目的，我选择创建一个非常明确的读写问题分离。      &lt;br /&gt;逻辑架构如下所示： -      &lt;br /&gt;      &lt;img alt="&amp;#36825;&amp;#37324;&amp;#20889;&amp;#22270;&amp;#29255;&amp;#25551;&amp;#36848;" src="https://img-blog.csdn.net/20180809115935374?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3F1Z3Vhbmc2NTI2NQ==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70" title=""&gt;&lt;/img&gt;&lt;/p&gt;    &lt;p&gt;命令端和查询端微服务都是使用Spring Boot框架开发的。 命令和查询微服务之间的所有通信纯粹是“事件驱动的”。 使用RabbitMQ消息传递在微服务组件之间传递事件。 消息传递提供了一种可扩展的方法，以松散耦合的方式在进程，微服务，遗留系统和其他方之间传递事件。&lt;/p&gt;    &lt;blockquote&gt;      &lt;p&gt;请注意，这两个服务的数据库与另一个服务器的数据库不同。        &lt;br /&gt;这很重要，因为它为每项服务提供了高度自治，这反过来又有助于各项服务独立于系统中的其他服务进行扩展。        &lt;br /&gt;有关CQRS架构的更多信息，请查看我的幻灯片共享CQRS微服务上面的幻灯片。&lt;/p&gt;&lt;/blockquote&gt;    &lt;p&gt;CQRS架构模式中存在的高度自治和隔离为我们提供了一个有趣的问题 - 我们应该如何分配和运行如此松散耦合的组件？ 在我看来，容器化提供了最好的机制，并且Docker被广泛使用，它的格式已成为容器映像的事实标准，大多数流行的云平台提供直接支持。 它也很容易使用，这肯定有帮助。&lt;/p&gt;    &lt;h3&gt;命令端微服务&lt;/h3&gt;    &lt;p&gt;命令是“actions which change state”。 命令端微服务包含所有域逻辑和业务规则。 命令用于添加新产品或更改其状态。 在特定产品上执行这些命令会导致生成“事件event”，这些事件由Axon框架持久保存到MongoDB中，并通过RabbitMQ消息传递到其他进程（尽可能多的进程）。&lt;/p&gt;    &lt;p&gt;在event-soursing中，事件是系统的唯一状态记录。 它们被系统用于描述和重建任何实体的当前状态（通过一次一个地重放它的过去事件，直到重新应用所有先前的事件）。 这听起来很慢，但实际上因为事件很简单，它非常快，可以使用名为“快照”的汇总进一步调整。&lt;/p&gt;    &lt;blockquote&gt;      &lt;p&gt;在领域驱动设计（DDD）中，实体通常被称为“Aggregate”或“AggregateRoot”。&lt;/p&gt;&lt;/blockquote&gt;    &lt;h3&gt;查询端微服务&lt;/h3&gt;    &lt;p&gt;查询端微服务充当事件监听器和视图。 它监听命令端发出的“事件”，并将它们处理成最有意义的形状（例如表格视图）。&lt;/p&gt;    &lt;p&gt;在这个特定的例子中，查询端只是构建和维护一个“物化视图”或“投影”，它保存了各个产品的最新状态（就其ID和描述以及它们是否可销售而言）。 可以多次复制查询端以实现可伸缩性，并且可以使RabbitMQ队列保存的消息持久，因此如果消息发生故障，它们甚至可以代表查询端临时存储消息。&lt;/p&gt;    &lt;p&gt;命令端和查询端都有REST API，可用于访问其功能。&lt;/p&gt;    &lt;p&gt;有关更多信息，请参阅      &lt;a href="http://www.axonframework.org/"&gt;Axon文档&lt;/a&gt;，该文档描述了Axon如何为您的Java应用程序带来CQRS和事件源，以及有关如何配置和使用它的大量详细信息。&lt;/p&gt;    &lt;h3&gt;运行例子&lt;/h3&gt;    &lt;p&gt;运行演示代码很简单，但您需要先在计算机上安装以下软件。 作为参考，我使用      &lt;a href="https://ubuntu.com/"&gt;Ubuntu 16.04&lt;/a&gt;作为我的操作系统，但我也成功测试了新的      &lt;a href="https://blog.csdn.net/quguang65265/article/details/Docker%20for%20Windows%20Beta"&gt;Docker for Windows Beta&lt;/a&gt;上的应用程序。&lt;/p&gt;    &lt;ul&gt;      &lt;li&gt;Docker (I’m using v1.8.2)&lt;/li&gt;      &lt;li&gt;Docker-compose (I’m using v1.7.1)&lt;/li&gt;&lt;/ul&gt;    &lt;p&gt;如果您已经拥有MongoDB或RabbitMQ，请在继续之前关闭这些服务以避免端口冲突。&lt;/p&gt;    &lt;h4&gt;第1步：获取Docker-compose配置文件&lt;/h4&gt;    &lt;p&gt;在新的空文件夹中，在终端执行以下命令以下载此演示的最新docker-compose配置文件。&lt;/p&gt;    &lt;pre&gt;      &lt;code&gt;$wgethttps://raw.githubusercontent.com/benwilcock/microservice-sampler/master/docker-compose.yml&lt;/code&gt;&lt;/pre&gt;    &lt;p&gt;尽量不要更改文件名 - Docker默认寻找名为“docker-compose.yml”的文件。 如果更改了名称，请在以下步骤中使用参数 -f 切换。&lt;/p&gt;    &lt;h4&gt;第2步：启动微服务&lt;/h4&gt;    &lt;p&gt;因为我们正在使用docker-compose，所以启动微服务现在只是执行以下命令的情况。&lt;/p&gt;    &lt;pre&gt;      &lt;code&gt;docker-composeup&lt;/code&gt;&lt;/pre&gt;    &lt;p&gt;在下载并运行docker镜像时，您会在终端窗口中看到大量下载和记录输出。&lt;/p&gt;    &lt;blockquote&gt;      &lt;p&gt;共有七个docker镜像，分别是mongodb，rabbitmq，config-service，discovery-service，gateway-service，product-cmd-side和product-qry-side。        &lt;br /&gt;如果要查看正在运行的docker实例（并获取其本地IP地址），请打开一个单独的终端窗口并执行以下命令： -&lt;/p&gt;&lt;/blockquote&gt;    &lt;pre&gt;      &lt;code&gt;docker ps&lt;/code&gt;&lt;/pre&gt;    &lt;p&gt;一旦实例启动并运行（这可能需要一些时间），您可以立即使用浏览器环顾四周。您应该能够访问： -&lt;/p&gt;    &lt;ul&gt;      &lt;li&gt;端口“15672”上的Rabbit Management Console&lt;/li&gt;      &lt;li&gt;端口“8761”上的Eureka Discovery Server控制台&lt;/li&gt;      &lt;li&gt;端口“8888”上的配置服务器映射&lt;/li&gt;      &lt;li&gt;端口’8080’上的API网关路由&lt;/li&gt;&lt;/ul&gt;    &lt;h3&gt;第3步：使用产品&lt;/h3&gt;    &lt;p&gt;到现在为止还挺好。现在我们要测试产品的添加。&lt;/p&gt;    &lt;p&gt;在这个手动系统测试中，我们将向命令端REST API发出      &lt;code&gt;add&lt;/code&gt;命令。&lt;/p&gt;    &lt;p&gt;当命令端处理完命令时，引发’ProductAddedEvent’，存储在MongoDB中，并通过RabbitMQ转发到查询端。然后查询端处理此事件并将产品的记录添加到其物化视图（实际上是这个简单演示的H2内存数据库）。事件处理完毕后，我们可以使用查询端微服务查找有关已添加新产品的信息。在执行这些任务时，您应该在docker-compose终端窗口中观察一些日志记录输出。&lt;/p&gt;    &lt;h4&gt;步骤3.1：添加新产品&lt;/h4&gt;    &lt;p&gt;为了执行测试，我们首先需要打开第二个终端窗口，我们可以在这里发出一些CURL命令而不停止我们在第一个窗口中运行的docker组合实例。&lt;/p&gt;    &lt;p&gt;出于本次测试的目的，我们将在产品目录中添加一个名为“Everything is Awesome”的MP3产品。为此，我们可以使用命令端REST API并使用POST请求发出它，如下所示……&lt;/p&gt;    &lt;pre&gt;      &lt;code&gt;$ curl-XPOST-v--header&amp;quot;Content-Type: application/json&amp;quot;--header&amp;quot;Accept: */*&amp;quot;&amp;quot;http://localhost:8080/commands/products/add/01?name=Everything%20Is%20Awesome&amp;quot;&lt;/code&gt;&lt;/pre&gt;    &lt;blockquote&gt;      &lt;p&gt;如果您没有“CURL”，则可以使用自己喜欢的REST API测试工具（例如Postman，SoapUI，RESTeasy等）。&lt;/p&gt;      &lt;p&gt;如果您正在使用Docker for Mac或Windows的公共测试版（强烈推荐），则需要将“localhost”替换为在终端窗口运行docker ps时显示的IP地址。&lt;/p&gt;&lt;/blockquote&gt;    &lt;p&gt;您应该看到类似于以下响应的内容。&lt;/p&gt;    &lt;pre&gt;      &lt;code&gt;* Trying127.0.0.1...* Connected to localhost (127.0.0.1) port8080(#0)&amp;gt; POST /commands/products/add/01?name=Everything%20Is%20Awesome HTTP/1.1&amp;gt; Host: localhost:9000&amp;gt; User-Agent: curl/7.47.0&amp;gt; Content-Type: application/json
&amp;gt; Accept: */*$ http://localhost:8080/commands/products/01&amp;lt; HTTP/1.1201Created
&amp;lt; Date: Thu,02Jun201613:37:07GMTThis
&amp;lt; X-Application-Context: product-command-side:9000&amp;lt; Content-Length:0&amp;lt; Server: Jetty(9.2.16.v20160414)&lt;/code&gt;&lt;/pre&gt;    &lt;p&gt;响应代码应为“HTTP / 1.1 201 Created”。这意味着MP3产品“Everything is Awesome”已成功添加到命令端事件源存储库中。&lt;/p&gt;    &lt;h4&gt;步骤3.2：查询新产品&lt;/h4&gt;    &lt;p&gt;现在让我们检查一下，我们可以查看刚刚添加的产品。为此，我们发出一个简单的’GET’请求。&lt;/p&gt;    &lt;pre&gt;      &lt;code&gt;$curlhttp://localhost:8080/queries/products/1&lt;/code&gt;&lt;/pre&gt;    &lt;p&gt;您应该看到以下输出。这表明查询端微服务有我们新添加的MP3产品的记录。该产品列为不可销售（salable = false）。&lt;/p&gt;    &lt;pre&gt;      &lt;code&gt;{name:&amp;quot;Everything Is Awesome&amp;quot;,
  saleable: false,
  _links: {
    self: {
    href:&amp;quot;http://localhost:8080/queries/products/1&amp;quot;},product:{href:&amp;quot;http://localhost:8080/queries/products/1&amp;quot;}
  }
}&lt;/code&gt;&lt;/pre&gt;    &lt;p&gt;ok！如果您愿意，可以继续重复测试以添加更多产品，请注意不要在POST时尝试重复使用相同的产品ID，否则您将看到错误。&lt;/p&gt;    &lt;p&gt;如果您熟悉MongoDB，则可以检查数据库以查看您创建的所有事件。同样，如果您了解RabbitMQ管理控制台的方法，您可以在命令端和查询端微服务之间看到消息。&lt;/p&gt;    &lt;p&gt;关于作者      &lt;br /&gt;Ben Wilcock是一名自由软件架构师兼技术主管，对微服务，云和移动应用程序充满热情。 Ben帮助几家富时100指数公司提高了响应能力，创新能力和敏捷性。 Ben也是一位受人尊敬的技术博主，他的文章在Java Code Geeks，InfoQ，Android Weekly等中都有所体现。您可以通过LinkedIn，Twitter和Github与他联系。&lt;/p&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 />
      <guid isPermaLink="true">https://itindex.net/detail/61655-docker-springboot-springcloud</guid>
      <pubDate>Mon, 02 Aug 2021 11:46:59 CST</pubDate>
    </item>
    <item>
      <title>基于qiankun框架的微前端实战使用_DJYanggggg的博客-CSDN博客_qiankun框架使用教程</title>
      <link>https://itindex.net/detail/61514-qiankun-%E6%A1%86%E6%9E%B6-%E5%89%8D%E7%AB%AF</link>
      <description>&lt;div&gt;    &lt;p&gt;   最近公司要整合目前所有的前端项目，希望放到同一个项目里面进行管理，但是项目使用的技术栈大体不相同，有原生js的，有用jq的也有用Vue的，整合的话要么重构，统一技术栈，不过这是不现实的，成本高，时间长，要么使用iframe，但是iframe也有很多缺点，通信不方便，刷新页面会导致路由丢失，这都是很不好的体验，于是我想起了最近很火的微前端概念，打算用微前端的技术来整合已有项目。&lt;/p&gt;    &lt;ol&gt;      &lt;li&gt;        &lt;strong&gt;什么是微前端？&lt;/strong&gt;        &lt;br /&gt;        &lt;br /&gt;微前端就是将不同的功能按照不同的维度拆分成多个子应用。通过主应用来加载这些子应用。微前端的核心在于        &lt;strong&gt;拆&lt;/strong&gt;, 拆完后在        &lt;strong&gt;合&lt;/strong&gt;!        &lt;br /&gt; &lt;/li&gt;      &lt;li&gt;        &lt;strong&gt;为什么使用微前端？&lt;/strong&gt;        &lt;br /&gt;        &lt;br /&gt;① 不同团队间开发同一个应用技术栈不同        &lt;br /&gt;② 希望每个团队都可以独立开发，独立部署        &lt;br /&gt;③ 项目中还需要老的应用代码        &lt;div&gt;          &lt;br /&gt;我们可以将一个应用划分成若干个子应用，将子应用打包成一个个的lib。当路径切换 时加载不同的子应用。这样每个子应用都是独立的，技术栈也不用做限制了！从而解决了前端协同开发问题。          &lt;br /&gt; &lt;/div&gt;&lt;/li&gt;      &lt;li&gt;        &lt;strong&gt;如何落地微前端？&lt;/strong&gt;        &lt;br /&gt;         &lt;div&gt;2018年Single-SPA诞生了，single-spa是一个用于前端微服务化的JavaScript前端解决方案 (本身没有处理样式隔离，js执行隔离)实现了路由劫持和应用加载。          &lt;br /&gt; &lt;/div&gt;        &lt;div&gt;2019年qiankun基于Single-SPA,提供了更加开箱即用的API（single-spa+sandbox+import-html-entry） 做到了，技术栈无关、并且接入简单（像iframe一样简单）。&lt;/div&gt;        &lt;br /&gt; &lt;/li&gt;      &lt;li&gt;qiankun框架实操        &lt;br /&gt;        &lt;br /&gt;这里我们打算建立三个项目进行实操，一个Vue项目充当主应用，另一个Vue和React应用充当子应用，话不多说，直接开干。        &lt;br /&gt;首先我们安装qiankun        &lt;pre&gt;          &lt;code&gt;yarn add qiankun 或者 npm i qiankun -S&lt;/code&gt;&lt;/pre&gt;        &lt;p&gt;安装完qiankun后我们在创建主应用，也是我们的基座&lt;/p&gt;        &lt;pre&gt;          &lt;code&gt;vue create qiankun-base&lt;/code&gt;&lt;/pre&gt;        &lt;p&gt;接着创建vue子应用&lt;/p&gt;        &lt;pre&gt;          &lt;code&gt;vue create qiankun-vue&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;&lt;/ol&gt;    &lt;p&gt;接着创建react子应用&lt;/p&gt;    &lt;pre&gt;      &lt;code&gt;cnpm install -g create-react-app
create-react-app my-app&lt;/code&gt;&lt;/pre&gt;    &lt;p&gt;创建好后的目录如下，qiankun-js忽略                           &lt;/p&gt;    &lt;p&gt;      &lt;img alt="" height="83" src="https://img-blog.csdnimg.cn/20200729163547532.png" width="244"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;p&gt; &lt;/p&gt;    &lt;p&gt; &lt;/p&gt;    &lt;p&gt;项目创建好后我们首先进行主应用qiankun-base的配置，进入man.js文件进行配置， 在main.js中加入以下代码,要注意的是，entry这项配置是我们两个子项目的域名和端口，我们必须确保两字子项目运行在这两个端口上面，container就是我们的容器名，就是我们子应用挂载的节点，相当于Vue项目里面的app节点，activeRule就是我们的激活路径，根据路径来显示不同的子应用。&lt;/p&gt;    &lt;pre&gt;      &lt;code&gt;import { registerMicroApps, start } from &amp;apos;qiankun&amp;apos;;
registerMicroApps([
  {
    name: &amp;apos;vueApp&amp;apos;, // 应用的名字
    entry: &amp;apos;//localhost:8021&amp;apos;,// 默认会加载这个html 解析里面的js 动态的执行 （子应用必须支持跨域）fetch
    container: &amp;apos;#vue&amp;apos;, // 容器名
    activeRule: &amp;apos;/vue&amp;apos;,// 激活的路径
  },
  {
    name: &amp;apos;reactApp&amp;apos;,
    entry: &amp;apos;//localhost:8020&amp;apos;,
    container: &amp;apos;#react&amp;apos;,
    activeRule: &amp;apos;/react&amp;apos;,
  },
]);
start();&lt;/code&gt;&lt;/pre&gt;    &lt;p&gt;配置完之后我们去到qiankun-base的app.vue文件进行主应用的页面编写，这里我安装了element-ui来进行页面美化，大家可以安装一下，&lt;/p&gt;    &lt;pre&gt;      &lt;code&gt;npm i element-ui -S&lt;/code&gt;&lt;/pre&gt;    &lt;p&gt;修改app.vue的组件代码如下      &lt;br /&gt; &lt;/p&gt;    &lt;pre&gt;      &lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div&amp;gt;
  &amp;lt;el-menu :router=&amp;quot;true&amp;quot; mode=&amp;quot;horizontal&amp;quot;&amp;gt;
      &amp;lt;!--基座中可以放自己的路由--&amp;gt;
      &amp;lt;el-menu-item index=&amp;quot;/&amp;quot;&amp;gt;Home&amp;lt;/el-menu-item&amp;gt; 
       &amp;lt;!--引用其他子应用--&amp;gt;
      &amp;lt;el-menu-item index=&amp;quot;/vue&amp;quot;&amp;gt;vue应用&amp;lt;/el-menu-item&amp;gt;
      &amp;lt;el-menu-item index=&amp;quot;/react&amp;quot;&amp;gt;react应用&amp;lt;/el-menu-item&amp;gt;
  &amp;lt;/el-menu&amp;gt;
    &amp;lt;router-view &amp;gt;&amp;lt;/router-view&amp;gt;
    &amp;lt;div id=&amp;quot;vue&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;
    &amp;lt;div id=&amp;quot;react&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/template&amp;gt;&lt;/code&gt;&lt;/pre&gt;    &lt;p&gt;大家可以看到，在elementui的路由导航模式下，菜单子元素的index就是要跳转的路径，这个路径和我们刚刚在main.js编写activeRule是一致，子应用的切换就是根据这里的index进行监听。&lt;/p&gt;    &lt;p&gt;接下来我们进行router的配置&lt;/p&gt;    &lt;pre&gt;      &lt;code&gt;import Vue from &amp;apos;vue&amp;apos;
import VueRouter from &amp;apos;vue-router&amp;apos;
import Home from &amp;apos;../views/Home.vue&amp;apos;

Vue.use(VueRouter)

  const routes = [
  {
    path: &amp;apos;/&amp;apos;,
    name: &amp;apos;Home&amp;apos;,
    component: Home
  },
  {
    path: &amp;apos;/about&amp;apos;,
    name: &amp;apos;About&amp;apos;,
    // route level code-splitting
    // this generates a separate chunk (about.[hash].js) for this route
    // which is lazy-loaded when the route is visited.
    component: () =&amp;gt; import(/* webpackChunkName: &amp;quot;about&amp;quot; */ &amp;apos;../views/About.vue&amp;apos;)
  }
]

const router = new VueRouter({
	mode: &amp;apos;history&amp;apos;,
  base: process.env.BASE_URL,
  routes
})

export default router&lt;/code&gt;&lt;/pre&gt;    &lt;p&gt;我们运行一下qiankun-base&lt;/p&gt;    &lt;pre&gt;      &lt;code&gt;npm run serve&lt;/code&gt;&lt;/pre&gt;    &lt;p&gt;界面应该是这样子的      &lt;br /&gt;      &lt;img alt="" height="529" src="https://img-blog.csdnimg.cn/20200729165213251.png" width="956"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;p&gt;目前页面除了菜单是没有其他东西的，点击也是没有效果的，因为我们还没配置子应用，现在我们来配置子应用      &lt;br /&gt;打开qiankun-vue目录， 在子Vue应用的main.js中加入以下代码&lt;/p&gt;    &lt;pre&gt;      &lt;code&gt;import Vue from &amp;apos;vue&amp;apos;
import App from &amp;apos;./App.vue&amp;apos;
import router from &amp;apos;./router&amp;apos;

let instance = null; 

//挂载实例
function render(){ 
    instance = new Vue({ router, render: h =&amp;gt; h(App) }).$mount(&amp;apos;#app&amp;apos;) 
}
//判断当前运行环境是独立运行的还是在父应用里面进行运行，配置全局的公共资源路径
if(window.__POWERED_BY_QIANKUN__){ 
    __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__; 
}
//如果是独立运行window.__POWERED_BY_QIANKUN__=undefined
if(!window.__POWERED_BY_QIANKUN__){
    render()
} 
//最后暴露的三个方法是固定的，加载渲染以及销毁
export async function bootstrap(){} 
export async function mount(props){
    render();
} 
export async function unmount(){
    instance.$destroy();
}&lt;/code&gt;&lt;/pre&gt;    &lt;p&gt;配置完main.js后我们继续配置基础配置模块，我们在子Vue应用的根目录下面新建一个Vue.config.js文件&lt;/p&gt;    &lt;pre&gt;      &lt;code&gt;module.exports = {
    devServer:{
        port:10000,//这里的端口是必须和父应用配置的子应用端口一致
        headers:{
            //因为qiankun内部请求都是fetch来请求资源，所以子应用必须允许跨域
            &amp;apos;Access-Control-Allow-Origin&amp;apos;:&amp;apos;*&amp;apos; 
        }
    },
    configureWebpack:{
        output:{
            //资源打包路径
            library:&amp;apos;vueApp&amp;apos;,
            libraryTarget:&amp;apos;umd&amp;apos;
        }
    }
}&lt;/code&gt;&lt;/pre&gt;    &lt;p&gt;接下里再进行router的配置&lt;/p&gt;    &lt;pre&gt;      &lt;code&gt;import Vue from &amp;apos;vue&amp;apos;
import VueRouter from &amp;apos;vue-router&amp;apos;
import Home from &amp;apos;../views/Home.vue&amp;apos;

Vue.use(VueRouter)

  const routes = [
  {
    path: &amp;apos;/&amp;apos;,
    name: &amp;apos;Home&amp;apos;,
    component: Home
  },
  {
    path: &amp;apos;/about&amp;apos;,
    name: &amp;apos;About&amp;apos;,
    component: () =&amp;gt; import(/* webpackChunkName: &amp;quot;about&amp;quot; */ &amp;apos;../views/About.vue&amp;apos;)
  }
]

const router = new VueRouter({
  mode: &amp;apos;history&amp;apos;,
  base: &amp;apos;/vue&amp;apos;,
  routes
})

export default router&lt;/code&gt;&lt;/pre&gt;    &lt;p&gt;这三步就已经把子应用配置好了，接下来我们再进行子React应用的配置。&lt;/p&gt;    &lt;p&gt;重写子React的src目录下的index.js文件，如下&lt;/p&gt;    &lt;pre&gt;      &lt;code&gt;import React from &amp;apos;react&amp;apos;;
import ReactDOM from &amp;apos;react-dom&amp;apos;;
import &amp;apos;./index.css&amp;apos;;
import App from &amp;apos;./App&amp;apos;;

function render(){
  ReactDOM.render(
    &amp;lt;React.StrictMode&amp;gt;
      &amp;lt;App /&amp;gt;
    &amp;lt;/React.StrictMode&amp;gt;,
    document.getElementById(&amp;apos;root&amp;apos;)
  );
}
if(!window.__POWERED_BY_QIANKUN__){
  render();
}
export async function bootstrap(){

}
export async function mount() {
  render()
}
export async function unmount(){
  ReactDOM.unmountComponentAtNode( document.getElementById(&amp;apos;root&amp;apos;));
}&lt;/code&gt;&lt;/pre&gt;    &lt;p&gt;再进行dev以及打包的配置，根目录下面的config-overrides.js&lt;/p&gt;    &lt;pre&gt;      &lt;code&gt;module.exports = {
    webpack:(config)=&amp;gt;{
        config.output.library = &amp;apos;reactApp&amp;apos;;
        config.output.libraryTarget = &amp;apos;umd&amp;apos;;
        config.output.publicPath = &amp;apos;http://localhost:20000/&amp;apos;;
        return config;
    },
    devServer:(configFunction)=&amp;gt;{
        return function (proxy,allowedHost){
            const config = configFunction(proxy,allowedHost);
            config.headers = {
                &amp;quot;Access-Control-Allow-Origin&amp;quot;:&amp;apos;*&amp;apos;
            }
            return config
        }
    }
}&lt;/code&gt;&lt;/pre&gt;    &lt;p&gt;子React的基础配置和子Vue基本相同，在这里我们就算是配置完了，接下来我们看看结果如何，启动主应用以及两个子应用&lt;/p&gt;    &lt;p&gt;      &lt;img alt="" height="881" src="https://img-blog.csdnimg.cn/20200824144606145.gif" width="1200"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;p&gt;上面是我录制的一个gif，可以看到我们的微前端实践已经成功了，点击相应的菜单可以跳转到对应的子应用，并且子应用内的路由是不会受影响的。还有一个需要注意的是，我们的应用路由模式要用history模式，没有的要去router文件里面定义&lt;/p&gt;    &lt;pre&gt;      &lt;code&gt;const router = new VueRouter({ 
    mode: &amp;apos;history&amp;apos;, 
    base: &amp;apos;/vue&amp;apos;, 
    routes 
})&lt;/code&gt;&lt;/pre&gt;    &lt;p&gt;那么今天的微前端的分享就到这里了，我可能讲得不太好，希望对大家有用，更加详细的教程和相关问题解答可以上官网&lt;/p&gt;    &lt;p&gt;      &lt;a href="https://qiankun.umijs.org/zh"&gt;https://qiankun.umijs.org/zh&lt;/a&gt;&lt;/p&gt;    &lt;p&gt;最后附上例子的源码地址，      &lt;a href="https://github.com/DJYang666/qiankun.git"&gt;https://github.com/DJYang666/qiankun.git&lt;/a&gt;，喜欢的多多关注哦&lt;/p&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 />
      <guid isPermaLink="true">https://itindex.net/detail/61514-qiankun-%E6%A1%86%E6%9E%B6-%E5%89%8D%E7%AB%AF</guid>
      <pubDate>Mon, 07 Jun 2021 14:46:50 CST</pubDate>
    </item>
    <item>
      <title>微服务设计模式：防腐层（Anti-corruption layer）_琦彦-CSDN博客</title>
      <link>https://itindex.net/detail/60987-%E5%BE%AE%E6%9C%8D%E5%8A%A1-%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-anti</link>
      <description>&lt;div&gt;    &lt;h1&gt;微软：微服务设计模式&lt;/h1&gt;    &lt;p&gt;2017年，微软      &lt;a href="https://www.azure.cn/community/cloudworld/2016/01/13/TittleTattle_CATTeam"&gt; AzureCAT &lt;/a&gt;模式和实践团队在      &lt;a href="https://docs.microsoft.com/en-us/azure/architecture/"&gt; Azure 架构中心&lt;/a&gt;发布了      &lt;a href="https://azure.microsoft.com/en-us/blog/design-patterns-for-microservices/"&gt; 9 个新的微服务设计模式&lt;/a&gt;，并给出了这些模式解决的问题、方案、使用场景、实现考量等。微软团队称这 9 个模式有助于更好的设计和实现微服务，同时看到业界对微服务的兴趣日渐增长，所以也特意将这些模式记录并发布。&lt;/p&gt;    &lt;p&gt;下图是微软团队建议如何在微服务架构中使用这些模式：&lt;/p&gt;    &lt;div&gt;      &lt;img alt="" height="422" src="https://img-blog.csdnimg.cn/20200202153525701.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2ZseTkxMDkwNQ==,size_16,color_FFFFFF,t_70" width="600"&gt;&lt;/img&gt;微软：微服务设计模式&lt;/div&gt;    &lt;p&gt; &lt;/p&gt;    &lt;p&gt;文中提到的9 个模式包括：外交官模式（Ambassador），防腐层（Anti-corruption layer），后端服务前端（Backends for Frontends），舱壁模式（Bulkhead），网关聚合（Gateway Aggregation），网关卸载（Gateway Offloading），网关路由（Gateway Routing），挎斗模式（Sidecar）和绞杀者模式（Strangler）。这些模式绝大多数也是目前业界比较常用的模式，如：&lt;/p&gt;    &lt;blockquote&gt;      &lt;ul&gt;        &lt;li&gt;          &lt;strong&gt;外交官模式（Ambassador）&lt;/strong&gt;可以用与语言无关的方式处理常见的客户端连接任务，如监视，日志记录，路由和安全性（如 TLS）。&lt;/li&gt;        &lt;li&gt;          &lt;strong&gt;防腐层\防损层（Anti-corruption layer）&lt;/strong&gt;介于新应用和遗留应用之间，用于确保新应用的设计不受遗留应用的限制。&lt;/li&gt;        &lt;li&gt;          &lt;strong&gt;后端服务前端（Backends for Frontends）&lt;/strong&gt;为不同类型的客户端（如桌面和移动设备）创建单独的后端服务。这样，单个后端服务就不需要处理各种客户端类型的冲突请求。这种模式可以通过分离客户端特定的关注来帮助保持每个微服务的简单性。&lt;/li&gt;        &lt;li&gt;          &lt;strong&gt;舱壁模式（Bulkhead）&lt;/strong&gt;隔离了每个工作负载或服务的关键资源，如连接池、内存和 CPU。使用舱壁避免了单个工作负载（或服务）消耗掉所有资源，从而导致其他服务出现故障的场景。这种模式主要是通过防止由一个服务引起的级联故障来增加系统的弹性。&lt;/li&gt;        &lt;li&gt;          &lt;strong&gt;网关聚合（Gateway Aggregation）&lt;/strong&gt;将对多个单独微服务的请求聚合成单个请求，从而减少消费者和服务之间过多的请求。&lt;/li&gt;        &lt;li&gt;          &lt;strong&gt;挎斗模式（Sidecar）&lt;/strong&gt;将应用程序的辅助组件部署为单独的容器或进程以提供隔离和封装。&lt;/li&gt;&lt;/ul&gt;&lt;/blockquote&gt;    &lt;p&gt;设计模式是对针对某一问题域的解决方案，它的出现也代表了工程化的可能。随着微服务在业界的广泛实践，相信这个领域将会走向成熟和稳定，笔者期望会有更多的模式和实践出现，帮助促进这一技术的进一步发展。&lt;/p&gt;    &lt;p&gt;本文，主要介绍      &lt;strong&gt;防腐层（Anti-corruption layer）模式&lt;/strong&gt;&lt;/p&gt;    &lt;h1&gt;      &lt;strong&gt;防腐层（Anti-corruption layer）&lt;/strong&gt;&lt;/h1&gt;    &lt;p&gt;在微服务（Microservices）架构实践中，人们大量地借用了DDD中的概念和技术，比如一个微服务应该对应DDD中的一个限界上下文（Bounded Context）；在微服务设计中应该首先识别出DDD中的聚合根（Aggregate Root）；还      &lt;strong&gt;有在微服务之间集成时采用DDD中的防腐层（Anti-Corruption Layer, ACL）。&lt;/strong&gt;&lt;/p&gt;    &lt;blockquote&gt;      &lt;p&gt;        &lt;strong&gt;防腐层（Anti-corruption layer，&lt;/strong&gt;简称 ACL        &lt;strong&gt;）&lt;/strong&gt;介于新应用和遗留应用之间，用于确保新应用的设计不受老应用的限制。        &lt;strong&gt;是一种在不同应用间转换的机制。&lt;/strong&gt;&lt;/p&gt;      &lt;p&gt;创建一个        &lt;strong&gt;防腐层&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;/blockquote&gt;    &lt;p&gt;在      &lt;strong&gt;不共享相同语义的不同子系统之间实施外观或适配器层&lt;/strong&gt;。 此层转换一个子系统向另一个子系统发出的请求。 使用      &lt;strong&gt;防腐层（Anti-corruption layer）&lt;/strong&gt;模式可确保应用程序的设计不受限于对外部子系统的依赖。       &lt;strong&gt;防腐层（Anti-corruption layer）&lt;/strong&gt;模式最先由      &lt;strong&gt;        &lt;a href="https://download.csdn.net/download/fly910905/12131300"&gt;Eric Evans 在 Domain-Driven Design（域驱动的设计）&lt;/a&gt;&lt;/strong&gt;中描述。&lt;/p&gt;    &lt;h2&gt;      &lt;strong&gt;防腐层（Anti-corruption layer）&lt;/strong&gt;提出背景&lt;/h2&gt;    &lt;p&gt;      &lt;strong&gt;大多数应用程序依赖于其他系统的某些数据或功能。&lt;/strong&gt; 例如，旧版应用程序迁移到新式系统时，可能仍需要现有的旧的资源。 新功能必须能够调用旧系统。 逐步迁移尤其如此，随着时间推移，较大型应用程序的不同功能迁移到新式系统中。&lt;/p&gt;    &lt;p&gt;这些旧系统通常会出现质量问题，如复杂的数据架构或过时的 API。 旧系统使用的功能和技术可能与新式系统中的功能和技术有很大差异。 若要与旧系统进行互操作，新应用程序可能需要支持过时的基础结构、协议、数据模型、API、或其他不会引入新式应用程序的功能。&lt;/p&gt;    &lt;p&gt;保持新旧系统之间的访问可以强制新系统至少支持某些旧系统的 API 或其他语义。 这些旧的功能出现质量问题时，支持它们“损坏”可能会是完全设计的新式应用程序。&lt;/p&gt;    &lt;p&gt;      &lt;strong&gt;不仅仅是旧系统，不受开发团队控制的任何外部系统(第三方系统)都可能出现类似的问题。&lt;/strong&gt;&lt;/p&gt;    &lt;h2&gt;解决方案&lt;/h2&gt;    &lt;p&gt;      &lt;strong&gt;在不同的子系统之间放置防损层以将其隔离&lt;/strong&gt;。 此层转换两个系统之间的通信，在一个系统保持不变的情况下，使另一个系统可以避免破坏其设计和技术方法。&lt;/p&gt;    &lt;div&gt;      &lt;img alt="" height="289" src="https://img-blog.csdnimg.cn/20200202174528939.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2ZseTkxMDkwNQ==,size_16,color_FFFFFF,t_70" width="600"&gt;&lt;/img&gt;      &lt;strong&gt;在不同的子系统之间放置防损层以将其隔离&lt;/strong&gt;&lt;/div&gt;    &lt;p&gt; &lt;/p&gt;    &lt;p&gt;上图显示了采用两个子系统的应用程序。 &lt;/p&gt;    &lt;p&gt;子系统 A 通过防损层调用子系统 B。 子系统 A 与防损层之间的通信始终使用子系统 A 的数据模型和体系结构。防损层向子系统 B 发出的调用符合该子系统的数据模型或方法。 防损层包含在两个系统之间转换所必需的所有逻辑。&lt;/p&gt;    &lt;p&gt;      &lt;strong&gt;该层可作为应用程序内的组件或作为独立服务实现。&lt;/strong&gt;&lt;/p&gt;    &lt;h1&gt;      &lt;strong&gt;Anti-corruption layer&lt;/strong&gt;注意事项&lt;/h1&gt;    &lt;ul&gt;      &lt;li&gt;防损层可能将延迟添加到两个系统之间的调用。&lt;/li&gt;      &lt;li&gt;防损层将添加一项必须管理和维护的其他服务。&lt;/li&gt;      &lt;li&gt;        &lt;strong&gt;请考虑防损层的缩放方式。&lt;/strong&gt;&lt;/li&gt;      &lt;li&gt;请考虑是否需要多个防损层。 可能需要使用不同的技术或语言将功能分解为多个服务，或者可能因其他原因对防损层进行分区。&lt;/li&gt;      &lt;li&gt;请考虑如何管理与其他应用程序或服务相关的防损层。 如何将其集成到监视、发布和配置进程中？&lt;/li&gt;      &lt;li&gt;        &lt;strong&gt;确保维护并可以监视事务和数据一致性&lt;/strong&gt;。&lt;/li&gt;      &lt;li&gt;请考虑防损层是要处理不同子系统之间的所有通信，还是只需处理部分功能。&lt;/li&gt;      &lt;li&gt;如果防损层是应用程序迁移策略的一部分，请考虑该层是永久性的，还是在迁移所有旧功能后即会停用。&lt;/li&gt;      &lt;li&gt;经常和绞杀者模式（strangler）一起使用&lt;/li&gt;&lt;/ul&gt;    &lt;h1&gt;      &lt;strong&gt;Anti-corruption layer使用场景&lt;/strong&gt;&lt;/h1&gt;    &lt;p&gt;在以下情况下使用此模式：&lt;/p&gt;    &lt;blockquote&gt;      &lt;ul&gt;        &lt;li&gt;          &lt;strong&gt;迁移计划为发生在多个阶段，但是新旧系统之间的集成需要维护&lt;/strong&gt;。          &lt;ul&gt;            &lt;li&gt;很多人一看到旧系统就想要赶快替换掉他，但是请不要急著想著去替换旧系统，因为这条路充满困难与失败，而且 旧系统通常反而是系统目前最赚钱的部分。更好的做法是在使用旧系统时包上一层 ACL，让你的开发不受影响，甚至可以一点一滴的替换旧系统的功能，达到即使不影响目前功能下也能开发新功能，达到重构的效果！&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;        &lt;li&gt;          &lt;strong&gt;两个或更多个子系统具有不同的语义，需要对外部上下文的访问进行一次转义&lt;/strong&gt;。          &lt;ul&gt;            &lt;li&gt;例如：对接第三方系統。缴费软件中的收银台系统，需要对接不同的支付方式（支付宝、各个银行、信用卡等），这是就需要              &lt;strong&gt;收银台系统充当一个Anti-corruption layer，将用户的缴费支付信息，转换成各个三方支付系统需要的数据格式。&lt;/strong&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;        &lt;li&gt;          &lt;strong&gt;如果内部多个组件对外部系统需要访问，那么可以考虑将其放到通用上下文中。&lt;/strong&gt;          &lt;ul&gt;            &lt;li&gt;例如：我们有一个抽奖平台，包含有现金券、折扣券、外卖券、出行券等组件，但他们都需要对接用户信息服务，这时就需要在抽奖平台中，搭建一个              &lt;strong&gt;Anti-corruption layer，作为&lt;/strong&gt;抽奖平台对接用户信息的通用适配层。&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/blockquote&gt;    &lt;p&gt;      &lt;strong&gt;如果新旧系统之间没有重要的语义差异，则此模式可能不适合。&lt;/strong&gt;&lt;/p&gt;    &lt;h1&gt;      &lt;strong&gt;Anti-corruption layer 示例1：JDK集合&lt;/strong&gt;&lt;/h1&gt;    &lt;blockquote&gt;      &lt;p&gt;在 JDK1.0 时我们用的集合还是 Vector（后来推荐使用 ArrayList），我们用的迭代器还是 Enumeration（后来推荐使用 Iterator）。现在我们需要一个适配器，搭建        &lt;strong&gt;Anti-corruption layer （EnumerationAdapter ），&lt;/strong&gt;让 Vector 也能使用 Iterator 迭代器，即在 Enumeration 和 Iterator 之间做适配。&lt;/p&gt;&lt;/blockquote&gt;    &lt;pre&gt;      &lt;code&gt;/**
 * 1、Iterator 是新版本的迭代器。
 * 2、Enumeration 是旧版本的迭代器。
 * 3、EnumerationAdapter 是适配者（Adapter）角色，相当于Anti-corruption layer 在 Enumeration 和 Iterator 之间做适配
 */
public class EnumerationAdapter implements Iterator {

    private Enumeration enumeration;

    public EnumerationAdapter(Enumeration enumeration) {
        this.enumeration = enumeration;
    }

    @Override
    public boolean hasNext() {
        return enumeration.hasMoreElements();
    }

    @Override
    public Object next() {
        return enumeration.nextElement();
    }

    @Override
    public void remove() {
        throw new UnsupportedOperationException(&amp;quot;remove&amp;quot;);
    }
}

// main方法
public static void main(String[] args) {
	Vector vector = new Vector();
	vector.add(&amp;quot;java&amp;quot;);
	vector.add(&amp;quot;python&amp;quot;);
	vector.add(&amp;quot;javaScript&amp;quot;);
	Enumeration enumeration = vector.elements();
	Iterator iterator = new EnumerationAdapter(enumeration);
	while (iterator.hasNext()) {
		System.out.println(iterator.next());
	}
}&lt;/code&gt;&lt;/pre&gt;    &lt;p&gt; &lt;/p&gt;    &lt;h1&gt;      &lt;strong&gt;Anti-corruption layer 示例2--企业员工管理系统新旧版本对接&lt;/strong&gt;&lt;/h1&gt;    &lt;blockquote&gt;      &lt;ul&gt;        &lt;li&gt;在企业员工管理系统中，我们经常需要通过ID查询雇员信息（EmployeeAccessService.findEmployee）&lt;/li&gt;        &lt;li&gt;但，我们在企业员工管理系统版本迭代中，可能存在老员工的信息在旧版本企业员工管理系统，新员工的信息在新版本企业员工管理系统。&lt;/li&gt;        &lt;li&gt;当我们查询老员工的信息，就需要委托给EmployeeAccessAdapter适配器，从旧版本企业员工管理系统（EmployeeAccessFacade）中获取一个员工信息。&lt;/li&gt;        &lt;li&gt;并通过EmployeeAccessTranslator，以将旧版本员工信息转换为应用程序模型中的域对象。&lt;/li&gt;&lt;/ul&gt;&lt;/blockquote&gt;    &lt;div&gt;      &lt;div&gt;        &lt;p&gt; &lt;/p&gt;&lt;/div&gt;&lt;/div&gt;    &lt;div&gt;      &lt;div&gt;        &lt;p&gt;          &lt;strong&gt;EmployeeAccessService（&lt;/strong&gt;查询雇员信息          &lt;strong&gt;）&lt;/strong&gt;&lt;/p&gt;        &lt;pre&gt;          &lt;code&gt;public Employee findEmployee(String empID){
    return adapter.findEmployee(empID);
}&lt;/code&gt;&lt;/pre&gt;        &lt;p&gt;          &lt;strong&gt;EmployeeAccessAdapter （&lt;/strong&gt;          &lt;strong&gt;Anti-corruption layer，对接旧版本企业员工管理系统&lt;/strong&gt;          &lt;strong&gt;）&lt;/strong&gt;&lt;/p&gt;        &lt;pre&gt;          &lt;code&gt;// 旧版本 员工信息
private EmployeeAccessFacade facade;

public Employee findEmployee(String empID){
    EmployeeAccessContainer container = facade.findEmployeeAccess(empID);
    return translator.translate(container);
}&lt;/code&gt;&lt;/pre&gt;        &lt;p&gt;          &lt;strong&gt;EmployeeAccessTranslator（&lt;/strong&gt;将旧版本员工信息转换为应用程序模型中的域对象          &lt;strong&gt;）&lt;/strong&gt;&lt;/p&gt;        &lt;pre&gt;          &lt;code&gt;public Employee translate(EmployeeAccessContainer container){
    Employee emp = null;
    if (container != null) {
        employee = new Employee();
        employee.setEmpID(idPrefix + container.getEmployeeDTO().getEmpID());
        ...(more complex mappings)&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;    &lt;p&gt; &lt;/p&gt;    &lt;p&gt;参考链接：&lt;/p&gt;    &lt;p&gt;      &lt;a href="https://docs.microsoft.com/en-us/azure/architecture/"&gt;https://docs.microsoft.com/en-us/azure/architecture/&lt;/a&gt;&lt;/p&gt;    &lt;p&gt;      &lt;a href="https://stackoverflow.com/questions/909264/ddd-anti-corruption-layer-how-to"&gt;https://stackoverflow.com/questions/909264/ddd-anti-corruption-layer-how-to&lt;/a&gt;&lt;/p&gt;    &lt;p&gt;      &lt;a href="https://docs.microsoft.com/zh-cn/azure/architecture/patterns/strangler"&gt;https://docs.microsoft.com/zh-cn/azure/architecture/patterns/strangler&lt;/a&gt;&lt;/p&gt;    &lt;p&gt;      &lt;a href="https://ithelp.ithome.com.tw/articles/10218591"&gt;https://ithelp.ithome.com.tw/articles/10218591&lt;/a&gt;&lt;/p&gt;    &lt;p&gt;      &lt;a href="https://tech.meituan.com/2017/12/22/ddd-in-practice.html"&gt;https://tech.meituan.com/2017/12/22/ddd-in-practice.html&lt;/a&gt;&lt;/p&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 />
      <guid isPermaLink="true">https://itindex.net/detail/60987-%E5%BE%AE%E6%9C%8D%E5%8A%A1-%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-anti</guid>
      <pubDate>Wed, 04 Nov 2020 19:14:58 CST</pubDate>
    </item>
    <item>
      <title>微博数仓数据延时优化方案</title>
      <link>https://itindex.net/detail/60721-%E5%BE%AE%E5%8D%9A-%E6%95%B0%E6%8D%AE-%E4%BC%98%E5%8C%96</link>
      <description>&lt;h1&gt;前言&lt;/h1&gt;  &lt;p&gt;本文以离线数据仓库为背景，重点介绍因传输链路数据延时的不确定性，可能导致部分延迟文件无法参与正常的调度计算或同步，导致数据缺失的问题成因、业务影响及相应的解决方案。&lt;/p&gt;  &lt;blockquote&gt;    &lt;p&gt;关于这类问题的处理，有这么一种论调：我们认为正常情况下，      &lt;strong&gt;脏&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;/blockquote&gt;  &lt;h1&gt;数仓架构&lt;/h1&gt;  &lt;p&gt;数据仓库使用    &lt;strong&gt;Hive&lt;/strong&gt;构建，日志或数据以文件形式(Text/ORCFile)存储于HDFS。数仓整体划分以下3层：&lt;/p&gt;  &lt;ul&gt;    &lt;li&gt;      &lt;p&gt;ODS（面向原始日志的数据表）&lt;/p&gt;&lt;/li&gt;    &lt;li&gt;      &lt;p&gt;DW（面向业务主题的数据表）&lt;/p&gt;&lt;/li&gt;    &lt;li&gt;      &lt;p&gt;DM（面向业务应用的数据表）&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;  &lt;h2&gt;日志来源&lt;/h2&gt;  &lt;p&gt;日志（原始日志）来源可以是多样的：&lt;/p&gt;  &lt;ul&gt;    &lt;li&gt;      &lt;p&gt;实时数据流（Kafka/Flume/Scribe）&lt;/p&gt;&lt;/li&gt;    &lt;li&gt;      &lt;p&gt;离线数据推送（Rsync）&lt;/p&gt;&lt;/li&gt;    &lt;li&gt;      &lt;p&gt;日志接口（Http/Wget）&lt;/p&gt;&lt;/li&gt;    &lt;li&gt;      &lt;p&gt;其它&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;  &lt;p&gt;无论使用哪一种方式，都会使用统一的目录规范存储于HDFS，如下：&lt;/p&gt;  &lt;pre&gt;    &lt;code&gt;${BASE_DIR}/业务日志名称/日期（yyyy_MM_dd）/小时（HH）/日志文件名称（带有时间戳）      &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;假设业务日志名称为 www_spoollxrsaansnq8tjw0_aliyunXweibo，存储目录结构示例：&lt;/p&gt;  &lt;pre&gt;    &lt;code&gt;业务日志目录：      &lt;br /&gt;${BASE_DIR}/www_spoollxrsaansnq8tjw0_aliyunXweibo      &lt;br /&gt;      &lt;br /&gt;日期目录：      &lt;br /&gt;${BASE_DIR}/www_spoollxrsaansnq8tjw0_aliyunXweibo/2020_06_22      &lt;br /&gt;${BASE_DIR}/www_spoollxrsaansnq8tjw0_aliyunXweibo/2020_06_23      &lt;br /&gt;${BASE_DIR}/www_spoollxrsaansnq8tjw0_aliyunXweibo/2020_06_24      &lt;br /&gt;      &lt;br /&gt;小时目录：      &lt;br /&gt;${BASE_DIR}/www_spoollxrsaansnq8tjw0_aliyunXweibo/2020_06_24/09      &lt;br /&gt;${BASE_DIR}/www_spoollxrsaansnq8tjw0_aliyunXweibo/2020_06_24/10      &lt;br /&gt;${BASE_DIR}/www_spoollxrsaansnq8tjw0_aliyunXweibo/2020_06_24/11      &lt;br /&gt;      &lt;br /&gt;日志文件：      &lt;br /&gt;${BASE_DIR}/www_spoollxrsaansnq8tjw0_aliyunXweibo/2020_06_24/11/202006241100-node-aliyun-al01.xiaoka.tv-www_spoollxrsaansnq8tjw0_aliyunXweibo.log      &lt;br /&gt;${BASE_DIR}/www_spoollxrsaansnq8tjw0_aliyunXweibo/2020_06_24/11/202006241100-node-aliyun-static.yizhibo.com-www_spoollxrsaansnq8tjw0_aliyunXweibo.log      &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;可见，目录规范设计的存储最小时间粒度为    &lt;strong&gt;小时&lt;/strong&gt;。实时数据流使用定时（如：5分钟）迭代新文件的方式存储数据，存储的具体日期/小时目录由创建新文件时的    &lt;strong&gt;当前时间&lt;/strong&gt;决定；离线数据推送或日志接口会从日志文件名称中提取时间戳，存储到该时间戳对应的日期/小时目录。&lt;/p&gt;  &lt;p&gt;    &lt;strong&gt;注&lt;/strong&gt;：需要留心不同来源的日志存储于HDFS的方式，后续会再次涉及到这部分内容。&lt;/p&gt;  &lt;h2&gt;数据表创建及分区动态挂载&lt;/h2&gt;  &lt;p&gt;为业务日志 www_spoollxrsaansnq8tjw0_aliyunXweibo 创建相应的原始日志数据表（ODS），假设数据表名称 aliyunXweibo ：&lt;/p&gt;  &lt;pre&gt;    &lt;code&gt;CREATE EXTERNAL TABLE IF NOT EXISTS aliyunXweibo (      &lt;br /&gt;    col1 STRING,      &lt;br /&gt;    col2 STRING,      &lt;br /&gt;    col3 STRING      &lt;br /&gt;    ...      &lt;br /&gt;)      &lt;br /&gt;PARTITIONED BY (dt STRING)      &lt;br /&gt;ROW FORMAT DELIMITED FIELDS TERMINATED BY &amp;apos;\t&amp;apos;      &lt;br /&gt;;      &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;数据表 aliyunXweibo 类型为外表（EXTERNAL），使用    &lt;strong&gt;dt&lt;/strong&gt;为分区（Partition）字段，格式：yyyyMMddHH，与HDFS存储目录中的日期/小时相互对应，如：&lt;/p&gt;  &lt;pre&gt;    &lt;code&gt;dt = &amp;apos;2020062400&amp;apos;      &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;对应着&lt;/p&gt;  &lt;pre&gt;    &lt;code&gt;${BASE_DIR}/www_spoollxrsaansnq8tjw0_aliyunXweibo/2020_06_24/00      &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;分区动态挂载就是建立数据表分区与HDFS日期/小时目录的    &lt;strong&gt;关系&lt;/strong&gt;。我们部署有一套专用的服务，定时执行以下2个操作：&lt;/p&gt;  &lt;ol&gt;    &lt;li&gt;      &lt;p&gt;如果某个业务日志的日期/小时目录存在，但业务数据表中相应的分区不存在，则执行        &lt;strong&gt;挂载&lt;/strong&gt;；&lt;/p&gt;      &lt;pre&gt;        &lt;code&gt;ALTER TABLE aliyunXweibo ADD PARTITION(dt = &amp;apos;2020062400&amp;apos;) LOCATION &amp;apos;${BASE_DIR}/www_spoollxrsaansnq8tjw0_aliyunXweibo/2020_06_24/00&amp;apos;;          &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;    &lt;li&gt;      &lt;p&gt;如果某个业务数据表中的分区存在，但业务日志中相应的日期/小时目录不存在（过期被删除），则执行        &lt;strong&gt;移除挂载&lt;/strong&gt;；&lt;/p&gt;      &lt;pre&gt;        &lt;code&gt;ALTER TABLE aliyunXweibo DROP IF EXISTS PARTITION(dt = &amp;apos;2020062400&amp;apos;);          &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;&lt;/ol&gt;  &lt;h2&gt;数据计算&lt;/h2&gt;  &lt;p&gt;原始日志数据表创建及分区挂载完成之后，便可以根据业务分析需求，开发部署相应的业务计算应用（Hive SQL/Spark SQL/Spark Application，DW/DM），计算结果以数据表（DW/DM）的形式存储于数据仓库中，这些数据表使用    &lt;strong&gt;dt&lt;/strong&gt;为分区字段，时间粒度为    &lt;strong&gt;小时&lt;/strong&gt;。&lt;/p&gt;  &lt;p&gt;数据表分区挂载、DW数据计算应用及DM数据计算应用可以简单理解为使用    &lt;strong&gt;小时&lt;/strong&gt;为时间周期定时（如：每小时第5分钟）运行，调度及相互之间的依赖关系均由调度系统负责。&lt;/p&gt;  &lt;p&gt;假设调度时间为 2020-06-24 01:05:00，调度系统中的应用会挂载和计算    &lt;strong&gt;上一小时&lt;/strong&gt;的数据，数据表分区挂载及DW/DM数据计算应用会运行完成之后，各个数据表新生成的分区如下：&lt;/p&gt;  &lt;pre&gt;    &lt;code&gt;aliyunXweibo(ODS)      &lt;br /&gt;dt = &amp;apos;2020062400&amp;apos;      &lt;br /&gt;      &lt;br /&gt;aliyunXweibo_DW(DW)      &lt;br /&gt;dt = &amp;apos;2020062400&amp;apos;      &lt;br /&gt;      &lt;br /&gt;aliyunXweibo_DM(DM)      &lt;br /&gt;dt = &amp;apos;2020062400&amp;apos;      &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;    &lt;strong&gt;注&lt;/strong&gt;：实际场景中，这一步会比较复杂（也请留心），这里仅用于理解流程，详情见后文。&lt;/p&gt;  &lt;p&gt;     &lt;br /&gt;综上所述，数据仓库经过日志存储、数据表创建及分区挂载、数据计算等一系列相关的流程之后，最后会形成典型的    &lt;strong&gt;ODS/DW/DM&lt;/strong&gt;3层业务数据表，用以支撑业务不同场景下的应用。&lt;/p&gt;  &lt;h1&gt;数据同步&lt;/h1&gt;  &lt;p&gt;业务场景中，有一个很重要的应用部分就是数据可视化或数据接口，其显著特点是要求    &lt;strong&gt;查询响应耗时低&lt;/strong&gt;（如：1-3 s），数据来源通常是数据仓库中的DW/DM数据表。如前文所述，数据仓库使用    &lt;strong&gt;Hive&lt;/strong&gt;构建，鉴于    &lt;strong&gt;Hive&lt;/strong&gt;自身的设计及特点，无法满足    &lt;strong&gt;查询响应耗时低&lt;/strong&gt;这个要求。通常的做法是将需要查询的数据表数据    &lt;strong&gt;同步&lt;/strong&gt;到合适的查询引擎（如：ElasticSearch、ClickHouse、MySQL等），然后使用这些查询引擎服务于数据可视化或数据接口。&lt;/p&gt;  &lt;p&gt;    &lt;strong&gt;数据同步&lt;/strong&gt;就是指将数据仓库中数据表（DW/DM）的数据同步至适用于    &lt;strong&gt;即席查询（OLAP）&lt;/strong&gt;场景的查询引擎中的过程；每次追加同步一个分区的数据。&lt;/p&gt;  &lt;p&gt;也就是说，DW/DM数据计算应用运行完成之后，还有会相应的数据同步应用，负责同步DW/DM数据表中相应时间区分中数据至外部的查询引擎。&lt;/p&gt;  &lt;p&gt;继续使用 数据计算 中的示例，假设需要同步的DW/DM数据表分区：&lt;/p&gt;  &lt;pre&gt;    &lt;code&gt;aliyunXweibo_DW(DW)      &lt;br /&gt;dt = &amp;apos;2020062400&amp;apos;      &lt;br /&gt;      &lt;br /&gt;aliyunXweibo_DM(DM)      &lt;br /&gt;dt = &amp;apos;2020062400&amp;apos;      &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;实际同步时会使用这样的SQL：&lt;/p&gt;  &lt;pre&gt;    &lt;code&gt;SELECT      &lt;br /&gt;    *      &lt;br /&gt;FROM      &lt;br /&gt;    aliyunXweibo_DW      &lt;br /&gt;WHERE      &lt;br /&gt;    dt = &amp;apos;2020062400&amp;apos;      &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;  &lt;h1&gt;数据延时&lt;/h1&gt;  &lt;p&gt;前文中描述的数据仓库/数据计算/数据同步，应该是目前业界普通采用的一种数据方案，实际工作场景中以此为指导也可以运行的不错。那么，问题在哪里？会影响什么？&lt;/p&gt;  &lt;p&gt;    &lt;strong&gt;细节，细节，还是细节 ！！！&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;以微博客户端为例，用户使用过程中会产生大量的性能或行为日志，这些日志因为业务场景的差异，日志上报的时机也是不同的。考虑到用户体验的问题，有一种常用的策略是    &lt;strong&gt;客户端切换至后台，且网络连接Wifi&lt;/strong&gt;，这时会将客户端中的日志上传至服务端。服务端接收到日志之后会中转至    &lt;strong&gt;Kafka&lt;/strong&gt;，供有需要的业务方消费使用。&lt;/p&gt;  &lt;p&gt;    &lt;strong&gt;注&lt;/strong&gt;：日志中通常会包含时间戳，表示日志记录时间或用户行为时间。后文统一称之为    &lt;strong&gt;日志时间&lt;/strong&gt;。&lt;/p&gt;  &lt;p&gt;简单思考就可以看出，    &lt;strong&gt;客户端切换后台&lt;/strong&gt;与    &lt;strong&gt;网络连接Wifi&lt;/strong&gt;都是很不可控的因素，受用户使用行为或使用场景影响较大。比如：用户于Wifi环境下连续刷微博1小时，我们可以认为这1小时之内是没有任何日志上传，只有等用户使用完微博，将客户端切换至后台且连接Wifi的条件下日志才会上传。也说是说，用户这部分日志被服务端接收到至少会延迟1小时。&lt;/p&gt;  &lt;p&gt;客户端记录的    &lt;strong&gt;日志时间&lt;/strong&gt;与服务端接收到日志的    &lt;strong&gt;当前时间&lt;/strong&gt;之间的    &lt;strong&gt;时间差&lt;/strong&gt;，就是    &lt;strong&gt;数据延时&lt;/strong&gt;。&lt;/p&gt;  &lt;p&gt;    &lt;strong&gt;注&lt;/strong&gt;：    &lt;strong&gt;数据延时&lt;/strong&gt;是绝对存在且不可避免的。这里的    &lt;strong&gt;延时&lt;/strong&gt;特指超出正常传输本身所需时间之外的部分。&lt;/p&gt;  &lt;h3&gt;对于存储的影响？&lt;/h3&gt;  &lt;p&gt;对于实时数据流，即使我们可以实时消费    &lt;strong&gt;流&lt;/strong&gt;（Kafka）中的数据，但数据本身可能是延时的，即日志时间与消费的当前时间相比滞后。消费    &lt;strong&gt;流&lt;/strong&gt;数据存储至HDFS时，可以有2种时间策略：&lt;/p&gt;  &lt;ol&gt;    &lt;li&gt;      &lt;p&gt;        &lt;strong&gt;当前时间&lt;/strong&gt;；&lt;/p&gt;      &lt;p&gt;使用        &lt;strong&gt;当前时间&lt;/strong&gt;创建相应日期/小时目录的文件，将之后 5分钟 （假设文件迭代周期为5分钟）之内消费的数据写入到该文件中；下一个 5分钟 到来时，继续使用        &lt;strong&gt;当前时间&lt;/strong&gt;创建新文件及写入数据。&lt;/p&gt;      &lt;p&gt;        &lt;strong&gt;影响&lt;/strong&gt;：可能会导致当前的日期/小时目录的文件被写入        &lt;strong&gt;脏&lt;/strong&gt;日志。这里的        &lt;strong&gt;脏&lt;/strong&gt;，是说从这些日志的        &lt;strong&gt;日志时间&lt;/strong&gt;来看，并不属于当前的日期/小时目录，可能属于过去某个日期/小时目录（数据延时），也可能属于下一个日期/小时目录（文件迭代不及时）。&lt;/p&gt;&lt;/li&gt;    &lt;li&gt;      &lt;p&gt;        &lt;strong&gt;日志时间&lt;/strong&gt;；&lt;/p&gt;      &lt;p&gt;使用        &lt;strong&gt;日志时间&lt;/strong&gt;创建相应日期/小时目录的文件，将消费的数据按        &lt;strong&gt;日志时间&lt;/strong&gt;写入到特定文件中；已创建的文件如果 5分钟 （假设文件迭代周期为5分钟）之内没有新的数据写入则关闭。&lt;/p&gt;      &lt;p&gt;        &lt;strong&gt;影响&lt;/strong&gt;：可能会导致过去的某个日期/小时目录的文件被不定时的写入日志；也就是说，使用某个日期/小时目录中的文件时，可能还有部分数据没有完全传输完成。&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;  &lt;p&gt;     &lt;br /&gt;对于离线数据推送，因为使用日志文件名称中的时间戳决定写入哪个日期/小时目录，仅仅可能会导致类似于实时数据流使用      &lt;strong&gt;日志时间&lt;/strong&gt;的影响，后续不再赘述。    &lt;br /&gt;     &lt;br /&gt;存储层的影响会    &lt;strong&gt;扩散&lt;/strong&gt;到计算层。&lt;/p&gt;  &lt;h3&gt;对于计算或同步的影响？&lt;/h3&gt;  &lt;p&gt;当我们想计算某个数据表    &lt;strong&gt;前1小时&lt;/strong&gt;的数据时，因为数据时延及存储层选用时间策略的关系，可能会遇到以下问题：&lt;/p&gt;  &lt;ol&gt;    &lt;li&gt;      &lt;p&gt;前1小时目录中，可能包含不确定比例的        &lt;strong&gt;脏&lt;/strong&gt;数据（        &lt;strong&gt;当前时间&lt;/strong&gt;）；&lt;/p&gt;&lt;/li&gt;    &lt;li&gt;      &lt;p&gt;前1小时目录中，可能缺失不确定比例的数据（        &lt;strong&gt;日志时间&lt;/strong&gt;）；&lt;/p&gt;&lt;/li&gt;    &lt;li&gt;      &lt;p&gt;存储层使用        &lt;strong&gt;当前时间&lt;/strong&gt;;&lt;/p&gt;      &lt;p&gt;计算ODS数据表        &lt;strong&gt;前1小时&lt;/strong&gt;分区数据时，因为这个小时分区目录中可能包含不属于该小时的数据，保存（Insert）计算结果到DW数据表时实际会写出多个小时分区，如：&lt;/p&gt;      &lt;pre&gt;        &lt;code&gt;aliyunXweibo(ODS)          &lt;br /&gt;dt = &amp;apos;2020062400&amp;apos;          &lt;br /&gt;          &lt;br /&gt;aliyunXweibo_DW(DW)          &lt;br /&gt;dt = &amp;apos;2020062322&amp;apos;          &lt;br /&gt;dt = &amp;apos;2020062323&amp;apos;          &lt;br /&gt;dt = &amp;apos;2020062400&amp;apos;          &lt;br /&gt;dt = &amp;apos;2020062401&amp;apos;          &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;      &lt;p&gt;其中，aliyunXweibo_DW(2020062322) 与 aliyunXweibo_DW(2020062323) 是因为 aliyunXweibo(2020062400) 中包含有因数据延时导致的        &lt;strong&gt;旧&lt;/strong&gt;日志导致的；aliyunXweibo_DW(2020062401) 是因为 aliyunXweibo(2020062400) 中包含有因文件迭代不及时的        &lt;strong&gt;超前&lt;/strong&gt;日志导致的。注意，aliyunXweibo_DW(2020062322) 与 aliyunXweibo_DW(2020062323) 这2个分区在前2个调度的周期中已计算且同步完成，这次相当于是在已有分区中        &lt;strong&gt;追加&lt;/strong&gt;(Append) 部分数据。&lt;/p&gt;      &lt;p&gt;如前所述，这次同步 aliyunXweibo_DW(2020062400) 会使用SQL：&lt;/p&gt;      &lt;pre&gt;        &lt;code&gt;SELECT          &lt;br /&gt;    *          &lt;br /&gt;FROM          &lt;br /&gt;    aliyunXweibo_DW          &lt;br /&gt;WHERE          &lt;br /&gt;    dt = &amp;apos;2020062400&amp;apos;          &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;      &lt;p&gt;很明显，aliyunXweibo_DW(2020062311) 与 aliyunXweibo_DW(2020062312) 中新        &lt;strong&gt;追加&lt;/strong&gt;的数据是无法参与同步的，对于查询引擎而言，这部分        &lt;strong&gt;追加&lt;/strong&gt;的数据是永远不可见的，相当于        &lt;strong&gt;丢失&lt;/strong&gt;。aliyunXweibo_DW(2020062401) 中的数据会被即将发生的        &lt;strong&gt;下一次&lt;/strong&gt;调度所同步。&lt;/p&gt;&lt;/li&gt;    &lt;li&gt;      &lt;p&gt;存储层使用        &lt;strong&gt;日志时间&lt;/strong&gt;；&lt;/p&gt;      &lt;p&gt;计算ODS数据表        &lt;strong&gt;前1小时&lt;/strong&gt;分区数据时，保存（Insert）计算结果到DW数据表时仅会写出1个小时分区，如：&lt;/p&gt;      &lt;pre&gt;        &lt;code&gt;aliyunXweibo(ODS)          &lt;br /&gt;dt = &amp;apos;2020062400&amp;apos;          &lt;br /&gt;          &lt;br /&gt;aliyunXweibo_DW(DW)          &lt;br /&gt;dt = &amp;apos;2020062400&amp;apos;          &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;      &lt;p&gt;同步 aliyunXweibo_DW(2020062400) 会使用SQL：&lt;/p&gt;      &lt;pre&gt;        &lt;code&gt;SELECT          &lt;br /&gt;    *          &lt;br /&gt;FROM          &lt;br /&gt;    aliyunXweibo_DW          &lt;br /&gt;WHERE          &lt;br /&gt;    dt = &amp;apos;2020062400&amp;apos;          &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;      &lt;p&gt;因为计算时属于这个小时分区目录中的文件可能还没有被全部传输完成，这部分文件永远也不会参与后续的调度计算或同步，相当于        &lt;strong&gt;丢失&lt;/strong&gt;。&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;  &lt;h1&gt;解决方案&lt;/h1&gt;  &lt;p&gt;最简单的解决方案就是定时重新计算且更新（覆盖）一定时间范围内的历史数据，考数据量级、资源成本及数据服务影响情况，相信大多数公司不会选择这种方案。&lt;/p&gt;  &lt;h2&gt;思路&lt;/h2&gt;  &lt;p&gt;我们解决方案的核心是    &lt;strong&gt;增量&lt;/strong&gt;，    &lt;strong&gt;增量计算&lt;/strong&gt;或    &lt;strong&gt;增量同步&lt;/strong&gt;。&lt;/p&gt;  &lt;ol&gt;    &lt;li&gt;      &lt;p&gt;增量计算&lt;/p&gt;      &lt;p&gt;每一次调度计算        &lt;strong&gt;上一小时&lt;/strong&gt;的数据时，不是计算        &lt;strong&gt;上一小时分区目录的文件&lt;/strong&gt;，而是以增量的方式计算        &lt;strong&gt;上一小时新写入的文件&lt;/strong&gt;。&lt;/p&gt;&lt;/li&gt;    &lt;li&gt;      &lt;p&gt;增量同步&lt;/p&gt;      &lt;p&gt;每一次调度同步        &lt;strong&gt;上一小时&lt;/strong&gt;的数据时，不是同步        &lt;strong&gt;上一小时分区目录的文件&lt;/strong&gt;，而是以增量的方式同步        &lt;strong&gt;上一小时新写入的文件&lt;/strong&gt;。&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;  &lt;p&gt;    &lt;strong&gt;Hive&lt;/strong&gt;原生支持以全表或分区的方式读取数据，我们需要进行相应地扩展使其支持：&lt;/p&gt;  &lt;blockquote&gt;    &lt;p&gt;读取数据表指定时间围内写入的文件数据&lt;/p&gt;&lt;/blockquote&gt;  &lt;p&gt;    &lt;strong&gt;注&lt;/strong&gt;：文件写入时间使用文件最后修改时间（mtime）表示，可通过Hadoop FileSystem API获取。&lt;/p&gt;  &lt;p&gt;Hive SQL 不支持表述这样的逻辑，    &lt;strong&gt;假设&lt;/strong&gt;存在如下属性：&lt;/p&gt;  &lt;ul&gt;    &lt;li&gt;      &lt;p&gt;        &lt;strong&gt;dip.mapreduce.input.fileinputformat.sync&lt;/strong&gt;：用于表示开启自定义特性，默认不开启。&lt;/p&gt;&lt;/li&gt;    &lt;li&gt;      &lt;p&gt;        &lt;strong&gt;dip.mapreduce.input.fileinputformat.starttime&lt;/strong&gt;：用于表示文件最后修改时间的起始时间点，毫秒，闭区间。&lt;/p&gt;&lt;/li&gt;    &lt;li&gt;      &lt;p&gt;        &lt;strong&gt;dip.mapreduce.input.fileinputformat.endtime&lt;/strong&gt;：用于表示文件最后修改时间的截止时间点，毫秒，闭区间。&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;  &lt;p&gt;如果需要读取数据表 aliyunXweibo 于 [2020-06-24 00:00:00, 2020-06-24 00:59:59] 内写入的文件数据，SQL如下：&lt;/p&gt;  &lt;pre&gt;    &lt;code&gt;set dip.mapreduce.input.fileinputformat.sync=true;      &lt;br /&gt;      &lt;br /&gt;set dip.mapreduce.input.fileinputformat.starttime=1592928000000;      &lt;br /&gt;set dip.mapreduce.input.fileinputformat.endtime=1592931599000;      &lt;br /&gt;      &lt;br /&gt;SELECT      &lt;br /&gt;    *      &lt;br /&gt;FROM      &lt;br /&gt;    aliyunXweibo      &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;    &lt;strong&gt;假设&lt;/strong&gt;成立的场景下，Hive    &lt;strong&gt;应&lt;/strong&gt;执行逻辑：&lt;/p&gt;  &lt;ol&gt;    &lt;li&gt;      &lt;p&gt;加载数据表 aliyunXweibo 目录下的所有文件列表；&lt;/p&gt;&lt;/li&gt;    &lt;li&gt;      &lt;p&gt;遍历文件列表中的每一个文件，根据文件最后修改时间（mtime）过滤掉不在指定时间范围内（[1592928000000, 1592931599000]）的文件；&lt;/p&gt;&lt;/li&gt;    &lt;li&gt;      &lt;p&gt;使用过滤之后的文件列表，完成后续的流程；&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;  &lt;p&gt;上述方式会扫描全表的文件，如果考虑文件数目较多，可以结合业务情况约束时间分区范围。&lt;/p&gt;  &lt;p&gt;如果需要读取数据表 aliyunXweibo，时间分区 [2020061701, 2020062401]，时间范围 [2020-06-24 00:00:00, 2020-06-24 00:59:59]内写入的文件数据：&lt;/p&gt;  &lt;pre&gt;    &lt;code&gt;set dip.mapreduce.input.fileinputformat.sync=true;      &lt;br /&gt;      &lt;br /&gt;set dip.mapreduce.input.fileinputformat.starttime=1592928000000;      &lt;br /&gt;set dip.mapreduce.input.fileinputformat.endtime=1592931599000;      &lt;br /&gt;      &lt;br /&gt;SELECT      &lt;br /&gt;    *      &lt;br /&gt;FROM      &lt;br /&gt;    aliyunXweibo      &lt;br /&gt;WHERE      &lt;br /&gt;    dt &amp;gt;= &amp;apos;2020061701&amp;apos;      &lt;br /&gt;    AND dt &amp;lt;= &amp;apos;2020062401&amp;apos;      &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;此时，Hive    &lt;strong&gt;应&lt;/strong&gt;执行逻辑：&lt;/p&gt;  &lt;ol&gt;    &lt;li&gt;      &lt;p&gt;加载数据表 aliyunXweibo 指定分区目录下的文件列表；&lt;/p&gt;&lt;/li&gt;    &lt;li&gt;      &lt;p&gt;遍历文件列表中的每一个文件，根据文件最后修改时间（mtime）过滤掉不在指定时间范围（[1592928000000, 1592931599000]）内的文件；&lt;/p&gt;&lt;/li&gt;    &lt;li&gt;      &lt;p&gt;使用过滤之后的文件列表，完成后续的流程；&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;  &lt;p&gt;     &lt;br /&gt;调度计算或同步需要读取    &lt;strong&gt;上一小时&lt;/strong&gt;的数据时，只需要通过调度时间转换为 [starttime, endtime] 即可。&lt;/p&gt;  &lt;p&gt;     &lt;br /&gt;如上所述，我们是通过自定义Hive配置属性的方式，使其支持按指定时间范围以增量的方式读取文件的。那么，    &lt;strong&gt;具体如何实现呢&lt;/strong&gt;？&lt;/p&gt;  &lt;p&gt;Hive读取文件是通过    &lt;strong&gt;Hadoop MapReduce InputFormat&lt;/strong&gt;实现的，以    &lt;strong&gt;TEXTFILE&lt;/strong&gt;（默认） 为例，实际使用的是    &lt;strong&gt;TextInputFormat&lt;/strong&gt;（org.apache.hadoop.mapred.TextInputFormat），继承自    &lt;strong&gt;FileInputFormat&lt;/strong&gt;（org.apache.hadoop.mapred.FileInputFormat），重点介绍以下 3 个方法：&lt;/p&gt;  &lt;ul&gt;    &lt;li&gt;      &lt;p&gt;getInputPaths&lt;/p&gt;      &lt;p&gt;通过属性        &lt;strong&gt;mapreduce.input.fileinputformat.inputdir&lt;/strong&gt;获取输入目录，谁来设置这个属性的值呢？        &lt;br /&gt;Hive会解析SQL语句，从中提取出数据表名称及分区过滤条件，动态计算需要读取的HDFS目录，然后将目录值（多个）设置到属性        &lt;strong&gt;mapreduce.input.fileinputformat.inputdir&lt;/strong&gt;中。        &lt;br /&gt; &lt;/p&gt;&lt;/li&gt;    &lt;li&gt;      &lt;p&gt;listStatus&lt;/p&gt;      &lt;p&gt;通过 getInputPaths() 获取输入目录，获取这些目录中所有文件对应的文件状态信息（FileStatus）。其中，文件状态信息中包括文件最后修改时间（modification_time）。&lt;/p&gt;      &lt;p&gt;listStatus() 也支持自定义过滤器（PathFilter），用于根据文件路径（Path）过滤文件，可以通过属性        &lt;strong&gt;mapreduce.input.pathFilter.class&lt;/strong&gt;指定。        &lt;br /&gt; &lt;/p&gt;&lt;/li&gt;    &lt;li&gt;      &lt;p&gt;getSplits&lt;/p&gt;      &lt;p&gt;通过 listStatus() 获取输入文件，形成切片（Split）用于后续处理逻辑使用。&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;  &lt;p&gt;     &lt;br /&gt;受 listStatus() 启发，既然可以通过自定义过滤器过滤文件，我们也可以扩展代码，获取自定义属性，然后根据文件最后修改时间过滤文件。&lt;/p&gt;  &lt;h2&gt;源码扩展&lt;/h2&gt;  &lt;p&gt;以 Hadoop 2.8.2 为例，在源文件    &lt;strong&gt;hadoop-2.8.2-src/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/src/main/java/org/apache/hadoop/mapred/FileInputFormat.java&lt;/strong&gt;    &lt;strong&gt;listStatus()&lt;/strong&gt;方法末尾处加入代码（260 ~ 279）：&lt;/p&gt;  &lt;pre&gt;    &lt;code&gt;254     sw.stop();      &lt;br /&gt;255     if (LOG.isDebugEnabled()) {      &lt;br /&gt;256       LOG.debug(&amp;quot;Time taken to get FileStatuses: &amp;quot;      &lt;br /&gt;257           + sw.now(TimeUnit.MILLISECONDS));      &lt;br /&gt;258     }      &lt;br /&gt;259      &lt;br /&gt;260     boolean sync = job.getBoolean(&amp;quot;dip.mapreduce.input.fileinputformat.sync&amp;quot;, false);      &lt;br /&gt;261     if (sync &amp;amp;&amp;amp; ArrayUtils.isNotEmpty(result)) {      &lt;br /&gt;262       long startTime = job.getLong(&amp;quot;dip.mapreduce.input.fileinputformat.starttime&amp;quot;, Long.MIN_VALUE);      &lt;br /&gt;263       long endTime = job.getLong(&amp;quot;dip.mapreduce.input.fileinputformat.endtime&amp;quot;, Long.MAX_VALUE);      &lt;br /&gt;264      &lt;br /&gt;265       result =      &lt;br /&gt;266           Arrays.stream(result)      &lt;br /&gt;267               .filter(      &lt;br /&gt;268                   file -&amp;gt; {      &lt;br /&gt;269                     boolean meet =      &lt;br /&gt;270                         startTime &amp;lt;= file.getModificationTime()      &lt;br /&gt;271                             &amp;amp;&amp;amp; file.getModificationTime() &amp;lt;= endTime;      &lt;br /&gt;272                     if (meet) {      &lt;br /&gt;273                       LOG.info(&amp;quot;Input meet path: &amp;quot; + file.getPath().toString());      &lt;br /&gt;274                     }      &lt;br /&gt;275      &lt;br /&gt;276                     return meet;      &lt;br /&gt;277                   })      &lt;br /&gt;278               .toArray(FileStatus[]::new);      &lt;br /&gt;279     }      &lt;br /&gt;280      &lt;br /&gt;281     LOG.info(&amp;quot;Total input files to process : &amp;quot; + result.length);      &lt;br /&gt;282     return result;      &lt;br /&gt;283   }      &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;  &lt;h2&gt;代码说明&lt;/h2&gt;  &lt;ol&gt;    &lt;li&gt;      &lt;p&gt;获取是否开启自定义特性；&lt;/p&gt;      &lt;pre&gt;        &lt;code&gt;boolean sync = job.getBoolean(&amp;quot;dip.mapreduce.input.fileinputformat.sync&amp;quot;, false);          &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;    &lt;li&gt;      &lt;p&gt;如果开启自定义特性（sync 为 true）且输入文件不为空，则获取文件写入时间范围，并执行过滤；&lt;/p&gt;      &lt;p&gt;获取文件写入起始时间（startTime）、截止时间（endTime）；&lt;/p&gt;      &lt;pre&gt;        &lt;code&gt;long startTime = job.getLong(&amp;quot;dip.mapreduce.input.fileinputformat.starttime&amp;quot;, Long.MIN_VALUE);          &lt;br /&gt;long endTime = job.getLong(&amp;quot;dip.mapreduce.input.fileinputformat.endtime&amp;quot;, Long.MAX_VALUE);          &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;      &lt;p&gt;遍历文件列表(result)中的每一个文件（file），判断文件最后修改时间（file.getModificationTime()）是否位于指定的文件写入时间范围（[startTime, endTime]）；&lt;/p&gt;      &lt;pre&gt;        &lt;code&gt;startTime &amp;lt;= file.getModificationTime()          &lt;br /&gt;    &amp;amp;&amp;amp; file.getModificationTime() &amp;lt;= endTime;          &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;&lt;/ol&gt;  &lt;h2&gt;编译部署&lt;/h2&gt;  &lt;p&gt;编译&lt;/p&gt;  &lt;pre&gt;    &lt;code&gt;mvn clean package -Dmaven.test.skip=true -Djavac.version=1.8      &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;使用编译生成的Jar文件&lt;/p&gt;  &lt;pre&gt;    &lt;code&gt;hadoop-2.8.2-src/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/target/hadoop-mapreduce-client-core-2.8.2.jar      &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;替换Hadoop部署目录的Jar文件&lt;/p&gt;  &lt;pre&gt;    &lt;code&gt;hadoop-2.8.2/share/hadoop/mapreduce/hadoop-mapreduce-client-core-2.8.2.jar      &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;    &lt;strong&gt;注&lt;/strong&gt;：如果使用的是HiveServer2，需要重启实例。&lt;/p&gt;  &lt;h2&gt;使用示例&lt;/h2&gt;  &lt;p&gt;假设我们需要统计数据表 mytable 指定文件写入时间范围 [1592915400000, 1592922600000] 内的行数。&lt;/p&gt;  &lt;h3&gt;Hive&lt;/h3&gt;  &lt;pre&gt;    &lt;code&gt;set hive.input.format=org.apache.hadoop.hive.ql.io.HiveInputFormat;      &lt;br /&gt;      &lt;br /&gt;set dip.mapreduce.input.fileinputformat.sync=true;      &lt;br /&gt;      &lt;br /&gt;set dip.mapreduce.input.fileinputformat.starttime=1592915400000;      &lt;br /&gt;set dip.mapreduce.input.fileinputformat.endtime=1592922600000;      &lt;br /&gt;      &lt;br /&gt;SELECT      &lt;br /&gt;    COUNT(1)      &lt;br /&gt;FROM      &lt;br /&gt;    mytable      &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;    &lt;strong&gt;注&lt;/strong&gt;：Hive 对于 InputFormat 有一些自定义的封装，默认使用    &lt;strong&gt;org.apache.hadoop.hive.ql.io.CombineHiveInputFormat&lt;/strong&gt;，详情参考相关文档。&lt;/p&gt;  &lt;h3&gt;Spark&lt;/h3&gt;  &lt;pre&gt;    &lt;code&gt;from pyspark.sql import SparkSession      &lt;br /&gt;      &lt;br /&gt;if __name__ == &amp;apos;__main__&amp;apos;:      &lt;br /&gt;    sql = &amp;apos;&amp;apos;&amp;apos;      &lt;br /&gt;    SELECT      &lt;br /&gt;        COUNT(1)      &lt;br /&gt;    FROM      &lt;br /&gt;        mytable      &lt;br /&gt;    &amp;apos;&amp;apos;&amp;apos;      &lt;br /&gt;    sql = sql.strip()      &lt;br /&gt;      &lt;br /&gt;    session = SparkSession.builder \      &lt;br /&gt;        .enableHiveSupport() \      &lt;br /&gt;        .getOrCreate()      &lt;br /&gt;      &lt;br /&gt;    session.sql(&amp;apos;set dip.mapreduce.input.fileinputformat.sync=true&amp;apos;)      &lt;br /&gt;      &lt;br /&gt;    session.sql(&amp;apos;set dip.mapreduce.input.fileinputformat.starttime=1592917200000&amp;apos;)      &lt;br /&gt;    session.sql(&amp;apos;set dip.mapreduce.input.fileinputformat.endtime=1592928000000&amp;apos;)      &lt;br /&gt;      &lt;br /&gt;    session.sql(sql).show()      &lt;br /&gt;      &lt;br /&gt;    session.stop()      &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;    &lt;strong&gt;注&lt;/strong&gt;：测试时可能在数据表 mytable 位于HDFS的存储目录中放入不同时间（文件最后修改时间）的文件进行模拟。&lt;/p&gt;  &lt;h2&gt;特别说明&lt;/h2&gt;  &lt;p&gt;Hive 使用的 InputFormat 实现类有很多，且不是所有的实现类都继承自 FileInputFormat，本文提供的只是一种通用的解决思案，实际使用时需要根据具体使用的 InputFormat 进行扩展。&lt;/p&gt;  &lt;h1&gt;结语&lt;/h1&gt;  &lt;p&gt;本文中讨论的    &lt;strong&gt;数据缺失&lt;/strong&gt;问题，之前与同事的讨论中曾一度被称之为    &lt;strong&gt;业界难题&lt;/strong&gt;进而被一度搁置。同时也可以看出，解决方案的实现过程虽然会涉及少量Hadoop源代码的扩展，但本质是十分简单的。问题的解决还是取决于工程师对技术及业务的掌控程度，仅供大家参考。&lt;/p&gt;  &lt;p&gt;     &lt;br /&gt;     &lt;br /&gt;     &lt;br /&gt;&lt;/p&gt;  &lt;img title="avatar"&gt;&lt;/img&gt;avatar
    &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 />
      <guid isPermaLink="true">https://itindex.net/detail/60721-%E5%BE%AE%E5%8D%9A-%E6%95%B0%E6%8D%AE-%E4%BC%98%E5%8C%96</guid>
      <pubDate>Tue, 30 Jun 2020 12:48:18 CST</pubDate>
    </item>
    <item>
      <title>转@鮑風雪 后浪中最令我迷惑又恶心的台词是“你们拥有了，我们曾经梦寐以求的权利，选择的权利”，然而现实是我微博给人点个赞都会被炸号。</title>
      <link>https://itindex.net/detail/60571-%E8%BF%B7%E6%83%91-%E6%81%B6%E5%BF%83-%E5%8F%B0%E8%AF%8D</link>
      <description>转@鮑風雪 后浪中最令我迷惑又恶心的台词是“你们拥有了，我们曾经梦寐以求的权利，选择的权利”，然而现实是我微博给人点个赞都会被炸号。&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 />
      <guid isPermaLink="true">https://itindex.net/detail/60571-%E8%BF%B7%E6%83%91-%E6%81%B6%E5%BF%83-%E5%8F%B0%E8%AF%8D</guid>
      <pubDate>Tue, 05 May 2020 12:20:04 CST</pubDate>
    </item>
    <item>
      <title>Octoman – 微博备份工具，可导出 HTML 文件[Chrome]</title>
      <link>https://itindex.net/detail/60391-octoman-%E5%BE%AE%E5%8D%9A-%E5%A4%87%E4%BB%BD</link>
      <description>&lt;p&gt;  &lt;a href="https://www.appinn.com/octoman-backup-weibo-for-chrome/"&gt;Octoman&lt;/a&gt; 是一款简单易用的 Chrome 扩展，用来备份微博，可导出为 HTML 文件，在浏览器进行浏览，支持备份任意用户微博，需要登录微博。@Appinn&lt;/p&gt;



 &lt;div&gt;  &lt;img alt="" src="https://img3.appinn.net/images/202002/octoman1.jpg!o"&gt;&lt;/img&gt;&lt;/div&gt;



 &lt;p&gt;来自  &lt;a href="https://meta.appinn.net/t/octoman/13754"&gt;发现频道&lt;/a&gt;，@  &lt;a href="https://meta.appinn.net/u/misswell"&gt;misswell&lt;/a&gt; 同学的推荐：&lt;/p&gt;



 &lt;p&gt;这是一个新浪微博备份工具，Chrome 浏览器插件/扩展。 使用方法：在 PC 版新浪微博页面点击扩展图片，选择需要保存的用户，点击保存按钮即可。&lt;/p&gt;



 &lt;p&gt;首先需要登录微博，然后进入你想备份的微博用户页面，点击扩展栏按钮，再点击保存，就开始下载了：&lt;/p&gt;



 &lt;div&gt;  &lt;img alt="" src="https://img3.appinn.net/images/202002/screenshot_2020-02-23_at_17_11_14.jpg!o"&gt;&lt;/img&gt;&lt;/div&gt;



 &lt;p&gt;每 500 条微博会保存为一个 html 文件，保存的信息有微博文字、评论数、转发数、点赞数：&lt;/p&gt;



 &lt;div&gt;  &lt;img alt="" height="504" src="https://img3.appinn.net/images/202002/screenshot-2020-02-23-at-18-04-16.jpg!o" width="405"&gt;&lt;/img&gt;&lt;/div&gt;



 &lt;p&gt;注意，图片可点击，会跳转到微博，而每一条微博下面那个评论和转发，是不能点的。&lt;/p&gt;



 &lt;p&gt;每备份 500 条，会休息两分钟，以防被微博踢掉封了 IP 就不好玩了。&lt;/p&gt;



 &lt;div&gt;  &lt;img alt="" height="225" src="https://img3.appinn.net/images/202002/screenshot_2020-02-23_at_17_27_17.jpg!o" width="444"&gt;&lt;/img&gt;&lt;/div&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;每500条微博存为一个 HTML 文件&lt;/li&gt;  &lt;li&gt;如想存图到本地，可开打 HTML 文件后右键另存&lt;/li&gt;  &lt;li&gt;微博太多会保存不全，因为新浪接口返回暂无微博&lt;/li&gt;  &lt;li&gt;保存的文件如果出现 finish 代表已经全部完成&lt;/li&gt;&lt;/ul&gt;



 &lt;p&gt;Octoman 官网  &lt;a href="https://blog.liuguofeng.com/p/5670"&gt;在这里&lt;/a&gt;，Chrome   &lt;a href="https://chrome.google.com/webstore/detail/octoman%E5%BE%AE%E5%8D%9A%E5%A4%87%E4%BB%BD/pojodomdlpobompicdllljgiomnfpmho"&gt;商店链接&lt;/a&gt;，  &lt;a href="http://blog.liuguofeng.com/file/OctoWeiboBackup.zip"&gt;离线包&lt;/a&gt;（可用开发者模式安装）。&lt;/p&gt;
 &lt;hr&gt;&lt;/hr&gt; &lt;h2&gt;相关阅读&lt;/h2&gt; &lt;ul&gt;  &lt;li&gt;   &lt;a href="https://www.appinn.com/synology-active-backup-for-business/" rel="bookmark" title="Permanent Link: &amp;#22914;&amp;#20309;&amp;#29992;&amp;#32676;&amp;#26198;&amp;#23436;&amp;#25972;&amp;#22791;&amp;#20221; Windows &amp;#31995;&amp;#32479;&amp;#12289;VM &amp;#34394;&amp;#25311;&amp;#26426;&amp;#12289;&amp;#26381;&amp;#21153;&amp;#22120;&amp;#65311;"&gt;如何用群晖完整备份 Windows 系统、VM 虚拟机、服务器？&lt;/a&gt;&lt;/li&gt;  &lt;li&gt;   &lt;a href="https://www.appinn.com/google-chrome-backup/" rel="bookmark" title="Permanent Link: Google Chrome Backup &amp;#8211; Chrome &amp;#22791;&amp;#20221;"&gt;Google Chrome Backup – Chrome 备份&lt;/a&gt;&lt;/li&gt;  &lt;li&gt;   &lt;a href="https://www.appinn.com/chrome-4-extensions-setup/" rel="bookmark" title="Permanent Link: Chrome 4.0&amp;#65292;&amp;#29992;&amp;#25193;&amp;#23637;&amp;#27494;&amp;#35013;&amp;#23427;"&gt;Chrome 4.0，用扩展武装它&lt;/a&gt;&lt;/li&gt;  &lt;li&gt;   &lt;a href="https://www.appinn.com/easeus-todo-backup-home/" rel="bookmark" title="Permanent Link: [&amp;#29305;&amp;#32422;&amp;#38480;&amp;#20813; 2 &amp;#22825;] EaseUS Todo Backup Home &amp;#8211; &amp;#25968;&amp;#25454;/&amp;#31995;&amp;#32479; &amp;#22791;&amp;#20221;&amp;#24674;&amp;#22797;&amp;#23567;&amp;#33021;&amp;#25163; [Win]"&gt;[特约限免 2 天] EaseUS Todo Backup Home – 数据/系统 备份恢复小能手 [Win]&lt;/a&gt;&lt;/li&gt;  &lt;li&gt;   &lt;a href="https://www.appinn.com/gmail-backup/" rel="bookmark" title="Permanent Link: GMail Backup &amp;#8211; &amp;#22791;&amp;#20221;&amp;#20320;&amp;#30340; GMail &amp;#37038;&amp;#20214;&amp;#21450;&amp;#20854;&amp;#23427;&amp;#26381;&amp;#21153;&amp;#24314;&amp;#35758;"&gt;GMail Backup – 备份你的 GMail 邮件及其它服务建议&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt; &lt;hr&gt;&lt;/hr&gt;
 &lt;a href="http://www.appinn.com/copyright/?utm_source=feeds&amp;utm_medium=copyright&amp;utm_campaign=feeds" title="&amp;#29256;&amp;#26435;&amp;#22768;&amp;#26126;"&gt;©&lt;/a&gt;2019 青小蛙 for  &lt;a href="http://www.appinn.com/?utm_source=feeds&amp;utm_medium=appinn&amp;utm_campaign=feeds" title="&amp;#26412;&amp;#25991;&amp;#26469;&amp;#33258;&amp;#23567;&amp;#20247;&amp;#36719;&amp;#20214;"&gt;小众软件&lt;/a&gt; |  &lt;a href="http://www.appinn.com/join-us/?utm_source=feeds&amp;utm_medium=joinus&amp;utm_campaign=feeds" title="&amp;#21152;&amp;#20837;&amp;#23567;&amp;#20247;&amp;#36719;&amp;#20214;"&gt;加入我们&lt;/a&gt; |  &lt;a href="https://meta.appinn.com/c/faxian/?utm_source=feeds&amp;utm_medium=contribute&amp;utm_campaign=feeds" target="_blank" title="&amp;#32473;&amp;#23567;&amp;#20247;&amp;#36719;&amp;#20214;&amp;#25237;&amp;#31295;"&gt;投稿&lt;/a&gt; |  &lt;a href="http://www.appinn.com/feeds-subscribe/?utm_source=feeds&amp;utm_medium=feedsubscribe&amp;utm_campaign=feeds" target="_blank" title="&amp;#21487;&amp;#20197;&amp;#20998;&amp;#31867;&amp;#35746;&amp;#38405;&amp;#23567;&amp;#20247;&amp;#65292;Windows/MAC/&amp;#28216;&amp;#25103;"&gt;订阅指南&lt;/a&gt; &lt;br /&gt; 3659b075e72a5b7b1b87ea74aa7932ff  &lt;br /&gt;
 &lt;a href="https://www.appinn.com/octoman-backup-weibo-for-chrome/#comments" title="to the comments"&gt;点击这里留言、和原作者一起评论&lt;/a&gt; &lt;em&gt;&lt;/em&gt;收藏0&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>Chrome 备份 导出 微博</category>
      <guid isPermaLink="true">https://itindex.net/detail/60391-octoman-%E5%BE%AE%E5%8D%9A-%E5%A4%87%E4%BB%BD</guid>
      <pubDate>Sun, 23 Feb 2020 18:12:29 CST</pubDate>
    </item>
    <item>
      <title>JHipster生成微服务架构的应用栈（一）- 准备工作 - 羽客 - 博客园</title>
      <link>https://itindex.net/detail/60351-jhipster-%E5%BE%AE%E6%9C%8D%E5%8A%A1-%E6%9E%B6%E6%9E%84</link>
      <description>&lt;div&gt;    &lt;blockquote&gt;      &lt;p&gt;本系列文章演示如何用JHipster生成一个微服务架构风格的应用栈。        &lt;br /&gt;环境需求：安装好JHipster开发环境的CentOS 7.4（        &lt;em&gt;          &lt;a href="https://www.cnblogs.com/yorkwu/p/9274474.html" target="_blank"&gt;参考这里&lt;/a&gt;&lt;/em&gt;）        &lt;br /&gt;应用栈名称：appstack        &lt;br /&gt;认证微服务： uaa        &lt;br /&gt;业务微服务：microservice1        &lt;br /&gt;网关微服务：gateway        &lt;br /&gt;实体名：role        &lt;br /&gt;主机IP：192.168.220.120&lt;/p&gt;&lt;/blockquote&gt;    &lt;h1&gt;微服务体系规划&lt;/h1&gt;    &lt;p&gt;      &lt;img src="https://images2018.cnblogs.com/blog/1428428/201807/1428428-20180719112613271-1251197751.png"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;p&gt;本系列文章会说明如何生成uaa（即图中的JHipster UAA），microservice1，gateway这3个微服务。      &lt;br /&gt;JHipster Console是现有的轮子，比较复杂，会有单独文章来介绍。      &lt;br /&gt;JHipster Registry也是现有的轮子，这里直接下载一个镜像来使用。&lt;/p&gt;    &lt;h1&gt;安装Docker&lt;/h1&gt;    &lt;p&gt;推荐版本：17.06      &lt;br /&gt;完整安装说明，请      &lt;em&gt;        &lt;a href="https://www.cnblogs.com/yorkwu/p/9239796.html" target="_blank"&gt;参考这里&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;    &lt;h1&gt;启动一个JHipster Registry&lt;/h1&gt;    &lt;p&gt;在命令行，任意目录下，启动一个JHipster Registry容器；如果本地没有jhipster/jhipster-registry:v4.0.0的镜像，容器启动时会自动去docker store下载镜像。&lt;/p&gt;    &lt;pre&gt;      &lt;code&gt;docker container run --name registry-app -e JHIPSTER.SECURITY.AUTHENTICATION.JWT.SECRET=dkk20dldkf0209342334 -d -p 8761:8761 jhipster/jhipster-registry:v4.0.0&lt;/code&gt;&lt;/pre&gt;    &lt;p&gt;启动完成后，可以通过浏览器访问      &lt;code&gt;http://192.168.220.120:8761&lt;/code&gt;，登录名和密码默认都是      &lt;code&gt;admin&lt;/code&gt;：&lt;/p&gt;    &lt;p&gt;      &lt;img src="https://images2018.cnblogs.com/blog/1428428/201807/1428428-20180719133610437-1882556995.png"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;p&gt;可以看到在      &lt;strong&gt;Instances Registered&lt;/strong&gt;区域，还没有注册的微服务。&lt;/p&gt;    &lt;h1&gt;创建整个应用栈的目录结构&lt;/h1&gt;    &lt;p&gt;在命令行，根据      &lt;strong&gt;微服务体系规划&lt;/strong&gt;，创建一个目录结构：&lt;/p&gt;    &lt;pre&gt;      &lt;code&gt;-- appstack
  |-- uaa
  |-- microservice1
  |-- gateway&lt;/code&gt;&lt;/pre&gt;    &lt;h1&gt;系列文章&lt;/h1&gt;    &lt;p&gt;      &lt;em&gt;JHipster生成微服务架构的应用栈（一）- 准备工作&lt;/em&gt;      &lt;br /&gt;      &lt;em&gt;        &lt;a href="https://www.cnblogs.com/yorkwu/p/9330665.html" target="_blank"&gt;JHipster生成微服务架构的应用栈（二）- 认证微服务示例&lt;/a&gt;&lt;/em&gt;      &lt;br /&gt;      &lt;em&gt;        &lt;a href="https://www.cnblogs.com/yorkwu/p/9335937.html" target="_blank"&gt;JHipster生成微服务架构的应用栈（三）- 业务微服务示例&lt;/a&gt;&lt;/em&gt;      &lt;br /&gt;      &lt;em&gt;        &lt;a href="https://www.cnblogs.com/yorkwu/p/9336241.html" target="_blank"&gt;JHipster生成微服务架构的应用栈（四）- 网关微服务示例&lt;/a&gt;&lt;/em&gt;      &lt;br /&gt;      &lt;em&gt;        &lt;a href="https://www.cnblogs.com/yorkwu/p/9336251.html" target="_blank"&gt;JHipster生成微服务架构的应用栈（五）- 容器编排示例&lt;/a&gt;&lt;/em&gt;&lt;/p&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 />
      <guid isPermaLink="true">https://itindex.net/detail/60351-jhipster-%E5%BE%AE%E6%9C%8D%E5%8A%A1-%E6%9E%B6%E6%9E%84</guid>
      <pubDate>Tue, 11 Feb 2020 15:10:30 CST</pubDate>
    </item>
    <item>
      <title>[分享创造] 开源一个类微博 twitter 的网站</title>
      <link>https://itindex.net/detail/60311-%E5%88%86%E4%BA%AB-%E5%88%9B%E9%80%A0-%E5%BC%80%E6%BA%90</link>
      <description>&lt;p&gt;网址：   &lt;a href="https://sserr.net" rel="nofollow"&gt;https://sserr.net&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;代码：   &lt;a href="https://github.com/coyove/iis" rel="nofollow"&gt;https://github.com/coyove/iis&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;之前的发布：   &lt;a href="https://v2ex.com/t/628871" rel="nofollow"&gt;https://v2ex.com/t/628871&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;时隔一个多月，当初的 todo 基本上都清掉了，代码也重构到了开源出去也不会太丢人的程度 XD。&lt;/p&gt;
 &lt;p&gt;总的来说这是一个类微博的系统，提供了大部分你所熟知的微博功能。时间线合并（ timeline merging ）采用读扩散，所以可以轻松的支持热点用户推文分发至海量 follower 的情况。回复 /@/收藏等则是写扩散。&lt;/p&gt;
 &lt;p&gt;后端采用纯 KV 形式存储，目前有两种实现（ dynamodb 和基于本地文件系统）。KV 不要求事务支持，所以理论上任何支持 set(k, v)和 get(k)的 IO 都可以用作存储驱动。（ dynamodb 会有 stale read，要解决这个问题需要本地缓存，如 redis ）&lt;/p&gt;
 &lt;p&gt;由于该架构设计特点，（以 dynamodb 为例）当一个账户 follow 的其他账户数目超过 10000 时合并延迟和性能会受比较大影响。但在另一方面，时间线合并的负担绝大部分在客户端，所以对于后端来说没有任何压力。&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 />
      <guid isPermaLink="true">https://itindex.net/detail/60311-%E5%88%86%E4%BA%AB-%E5%88%9B%E9%80%A0-%E5%BC%80%E6%BA%90</guid>
      <pubDate>Thu, 23 Jan 2020 21:13:30 CST</pubDate>
    </item>
    <item>
      <title>NLP 技术在微博 feed 流中的应用</title>
      <link>https://itindex.net/detail/60255-nlp-%E6%8A%80%E6%9C%AF-%E5%BE%AE%E5%8D%9A</link>
      <description>&lt;div&gt;  &lt;p&gt;   &lt;img src="https://img0.tuicool.com/6vyaIbQ.jpg!web"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;分享嘉宾：董兴华 新浪微博&lt;/p&gt;  &lt;p&gt;文章整理：凌铭&lt;/p&gt;  &lt;p&gt;内容来源：DataFunTalk&lt;/p&gt;  &lt;p&gt;出品平台：DataFun&lt;/p&gt;  &lt;p&gt;注：欢迎转载，转载请留言。&lt;/p&gt;  &lt;p&gt;导读：新浪微博截止2019.9统计的数据，月活跃用户数为4.97亿，日活跃用户数为2.16亿，其中约94%为移动端用户，今天会和大家分享新浪微博在 feed 流中遇到的 NLP 问题和解决思路。主要包括：&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;strong&gt;——难点与现存问题——&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;   &lt;img src="https://img1.tuicool.com/YBbyyqi.jpg!web"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;第一个问题，微博的内容都是比较短的 ( 一般都是100字符以内 )，比如右图中提到文本 &amp;quot;下午茶&amp;quot;，但是图片内容并不是美食 &amp;quot;下午茶&amp;quot;，考虑整个微博文本和图片内容应该分类到美女频道而不是美食频道更合适。另外对于短文本使用 LDA/PLSA 等 topic model 效果都不太好。&lt;/p&gt;  &lt;p&gt;❷   &lt;strong&gt;语言表达随意化&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;   &lt;img src="https://img1.tuicool.com/ZremyyR.jpg!web"&gt;&lt;/img&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;   &lt;img src="https://img1.tuicool.com/eIRVRvq.jpg!web"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;第三个问题，比如：在微博搜索结果页中，由于文本简短，大部分的结果在页面中能看到全文，没必要点击进入看内容 ( 除了第二条，需要点击展开全文 )，而从停留行为来看，由于一页展示多条博文，也不能准确定位用户感兴趣的是哪条微博。&lt;/p&gt;  &lt;p&gt;❹   &lt;strong&gt; 用户 feed 行为序列不能准确获取&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;   &lt;img src="https://img2.tuicool.com/QBJrAr2.jpg!web"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;这个问题与搜索结果页相似，用户停留在 feed 流的页面中，不能准确区分哪条微博为用户感兴趣的。我们对点击较高的博文分析发现，点击较高的博文很多包含多张图片。由于一条微博可以包含多张图片，部分高点击的博文是由于用户想查看图片的内容才点击进入微博的正文页，但是这样的点击不能代表用户对该博文感兴趣。&lt;/p&gt;  &lt;p&gt;综上所述，目前的微博场景很难获取十分准确的用户行为序列 ( 用户行为序列包括展示页，点击，停留，转发，评论，赞，收藏等 )，导致使用 LDA/PLSA 主题的方式和用户行为序列方式建模效果都不太好。接下来，将为大家分享下我们的解决办法和思路。&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;p&gt;博文标签，主要分为：一二级标签、实体标签、关键词标签。&lt;/p&gt;  &lt;h4&gt;① 一二级标签&lt;/h4&gt;  &lt;p&gt;   &lt;img src="https://img0.tuicool.com/biyYBfi.png!web"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;一级标签：对应频道，例如财经，法律，IT 产业，军事，历史，美食等标签。&lt;/p&gt;  &lt;p&gt;二级标签：一级标签 &amp;quot;财经&amp;quot;，包含的二级标签：投资，众筹，货币，股票，保险，债券，基金，贷款，美股等。&lt;/p&gt;  &lt;p&gt;一二级标签运用：一级标签与少量的二级标签和垂直频道对应，打上这部分标签的博文会分发到对应的频道下，对应的博文会在该频道进行展示。其次一级和二级标签也可用于画像的构建以及推荐中的召回和排序，但是作为标签，粒度太粗，不能很好的刻画用户兴趣。比如，有的用户只对英语感兴趣，如果把大量的教育相关的博文推荐给他，用户体验会比较差。&lt;/p&gt;  &lt;h4&gt;一二级标签分类系统：&lt;/h4&gt;  &lt;p&gt;   &lt;img src="https://img2.tuicool.com/qqmyUfa.jpg!web"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;目前分类系统使用 fasttext+bert 结合的方案，是由于 bert 效果好，而 fasttext 性能好。我们有如下的方案：针对关注度高 ( 明星 ) 和高质量的博文使用 bert，其他的博文先过一遍 fasttext，若标签得分高于阀值 ( 95分 )，则不再使用 bert 进行处理；若最高得分只有70分，那么使用 bert 再预测一次。Bert 是一种多层的编码器，最近的研究表明，bert 不同层的 embedding 捕获到了不同的句子知识。比如底层 embedding 捕获到词法方面特征，中间层 embedding 捕获到句法特征，高层 embedding 捕获到语义特征。因此，我们对 bert 的结构做了优化，使用对各层embedding 加权后获得的 embedding 作为整个博文的表征，后续的博文质量模型也是这种做法。&lt;/p&gt;  &lt;h4&gt;② 实体标签&lt;/h4&gt;  &lt;p&gt;实体标签在我们的场景中也叫三级标签，实体标签的来源：人工收集、微博热搜 query 和模型识别。如下图可看到一二级标签和实体标签之间的关系。&lt;/p&gt;  &lt;p&gt;   &lt;img src="https://img0.tuicool.com/777vaaJ.jpg!web"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;h4&gt;实体识别模型：&lt;/h4&gt;  &lt;p&gt;   &lt;img src="https://img2.tuicool.com/AjmEJbu.png!web"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;模型如图结构所示，首先经过 bert 层输出每个词对应每个序列标注的概率分布，再经过 crf 层输出最终的标注结果。而这些序列标注的训练数据是由人工标注的。&lt;/p&gt;  &lt;h4&gt;③ 关键词标签&lt;/h4&gt;  &lt;p&gt;关键词来源为两块：名词短语和用户 query。这是由于博文的文本较短所决定的 ( 90%以上都是100个字符以内 )，使用传统的主题模型方式并不奏效。&lt;/p&gt;  &lt;h4&gt;a. 名词短语抽取&lt;/h4&gt;  &lt;p&gt;首先我们先从名词短语抽取关键词说起，我们可从某个句子中获取依存句法相关信息。&lt;/p&gt;  &lt;p&gt;   &lt;img src="https://img0.tuicool.com/mqAzQzn.jpg!web"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;如上图所示，有 Stanford NLP、哈工大 LTP 和 HanLP 等工具包可以获取句子的依存句法，可以根据多个 parser 解析出来的依存句法对名词短语进行提取，例如，击剑、比赛两个词。首先在 stanford NLP 中击剑、比赛为一个名词短语且在一个语块 ( chunker ) 下，同样其他依存句法工具也是击剑、比赛都为名词短语并且都为同一个语块 ( chunker )。这样多个句法分析工具输出结果都是一致的，我们就认为击剑比赛可以作为标签使用。&lt;/p&gt;  &lt;p&gt;   &lt;img src="https://img2.tuicool.com/bAzuy2N.png!web"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;其次也可以从不同的分词结果对名词短语进行提取和校验。通过下列方式可以获取多个不同的分词结果：&lt;/p&gt;  &lt;p&gt;❶ 同一个分词器不同的分词粒度。&lt;/p&gt;  &lt;p&gt;❷ 同一个分词器 nbest ( 其它可能 ) 的结果&lt;/p&gt;  &lt;p&gt;❸ 不同的分词器&lt;/p&gt;  &lt;p&gt;使用不同的分词结果可以用于做名词短语提取正确性的校验。下文我们会详细阐述提取规则。&lt;/p&gt;  &lt;p&gt;   &lt;img src="https://img2.tuicool.com/jEBFNjJ.jpg!web"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;h4&gt;我们提取关键词短语有如下规则：&lt;/h4&gt;  &lt;p&gt;❶ 名词短语在多个句法 parser 中，在同一个 chunker 下，例如 &amp;quot;击剑比赛&amp;quot; 三个工具输出都是同一个 chunker。&lt;/p&gt;  &lt;p&gt;❷ 长度大于等于三个字以上的名词，长度小于 L 的名词短语，不宜太长 ( 实际操作中长度不超过6 )。&lt;/p&gt;  &lt;p&gt;❸ 名词短语边界与分词结果兼容，否则名词短语及名词短语所在同一 chunker 的短语不能作为候选 。例如我们使用 Stanford NLP 依存句法结果与一个分词结果做兼容，依存句法结果：香农/在/信息论/中/把/信息/熵定义/为/自/信息/的/期望 ，分词器的结果：香农/在/信息论/中/把/信息熵/定义/为/自信息/的/期望。&lt;/p&gt;  &lt;h4&gt;可得到如下结论：&lt;/h4&gt;  &lt;ul&gt;   &lt;li&gt;    &lt;p&gt;&amp;quot;信息论&amp;quot; 依存句法和分词都是同一个词说明兼容；&lt;/p&gt;&lt;/li&gt;   &lt;li&gt;    &lt;p&gt;&amp;quot;信息熵定义&amp;quot; 依存句法分割为 &amp;quot;信息&amp;quot; 和 &amp;quot;熵定义&amp;quot;，而分词结果为 &amp;quot;信息熵&amp;quot; 和 &amp;quot;定义&amp;quot;，这两个切分的边界不一致。即依存句法边界在信息、熵的中间，而分词边界在熵、定义中间说明与分词不兼容。若依存句法为信息/熵/定义，那么熵、定义是拆开的，边界算是与分词保持一致，那么 &amp;quot;信息熵&amp;quot; 则与分词结果兼容；&lt;/p&gt;&lt;/li&gt;   &lt;li&gt;    &lt;p&gt;&amp;quot;自信息&amp;quot; 依存句法为/自/信息/，分词结果为/自信息/，这里虽然依存句法自、信息被拆开了，但是与分词结果 &amp;quot;自信息&amp;quot; 是兼容的。&lt;/p&gt;&lt;/li&gt;   &lt;li&gt;    &lt;p&gt;&amp;quot;信息熵定义&amp;quot;，依存句法结果为/信息/熵定义/，分词结果为/信息熵/定义/，因为分词结果/信息熵/定义/是拆开的，并且依存句法拆分不一致所以与分词结果不兼容，除非依存句法与分词结果拆分一致都为/信息熵/定义/，那么才说明与分词兼容。&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;  &lt;p&gt;❹ 使用多个分词结果校验提升关键词精度&lt;/p&gt;  &lt;p&gt;   &lt;img src="https://img0.tuicool.com/iIRRvur.png!web"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;最后，我们可根据以上所说的实体识别和名词短语规则将博文中词语提取出来做为关键词的候选。&lt;/p&gt;  &lt;p&gt;b.    &lt;strong&gt;用户 query 提取&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;用户 query 作为关键词的条件：&lt;/p&gt;  &lt;p&gt;❶ Query必须为高频&lt;/p&gt;  &lt;p&gt;❷ 需要限制长度，不易太长&lt;/p&gt;  &lt;p&gt;❸ 另外，query 会存在边界错误的情况。可使用新词发现的方式，通过左右信息熵和紧密度进行噪音过滤。&lt;/p&gt;  &lt;h4&gt;c. 匹配算法：&lt;/h4&gt;  &lt;p&gt;   &lt;img src="https://img0.tuicool.com/3MnYjmr.jpg!web"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;首先，我们可以使用高效匹配算法，Trie 树/Hash table、Double array trie (dat)、AC 自动机 ( 三者中 AC 匹配效率最高 )， 500w+ 的词典通过这些匹配算法，在博文上进行匹配。接着我们使用分词工具对文章进行分词，来校验分词的结果边界是否与匹配算法的结果兼容，输出最终兼容的结果。&lt;/p&gt;  &lt;p&gt;   &lt;img src="https://img0.tuicool.com/QvA7Bvr.jpg!web"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;其实该方法与上文说的提取名词短语思路相似，例如：关键词为 &amp;quot;史记&amp;quot;，而文中为 &amp;quot;历史记录&amp;quot;。在使用匹配算法时会将 &amp;quot;历史记录&amp;quot; 中的 &amp;quot;史记&amp;quot; 匹配上，但是经过分词工具时，分出来历史/记录，而史记被分开，&amp;quot;史&amp;quot; 与 &amp;quot;历&amp;quot; 链接，没有单独成块，同时 &amp;quot;记&amp;quot; 也与 &amp;quot;录&amp;quot; 链接，那么可以判定与其不能兼容。&lt;/p&gt;  &lt;p&gt;有的同学会问，如果把关键词/实体词典加入分词用户词典，是不是同时解决了匹配问题和消歧问题？其实这是很难做到的，因为一般的分词词典词汇量约30-50万，分词的过程本身就是对句子消歧的过程，在分词的过程中往往借助词频、词性等其它信息，关键词/实体词 500W+，远超分词词典中词数量，如果放入分词用户词典，这些词会在匹配之后，在分词结果中保证不会被切开 ( 不同策略有所区别 )，将对分词的效果产生很大的负面影响。&lt;/p&gt;  &lt;h4&gt;d. 相关性&lt;/h4&gt;  &lt;p&gt;   &lt;img src="https://img0.tuicool.com/7vEzauz.png!web"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;使用匹配算法和分词验证兼容性得到以上关键词，其中天龙八部、新笑傲江湖等是和电影相关的词，但是与博文中的主题相关性不一致 ( 主题是旅游 )，由于关键词需要映射到用户画像上来统计用户兴趣，这些词不能代表用户的偏好。&lt;/p&gt;  &lt;p&gt;   &lt;img src="https://img2.tuicool.com/QnqqQri.jpg!web"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;标签与博文的相关性计算流程为：获取文本向量 -&amp;gt; 向量相似度计算 -&amp;gt; 设置阈值过滤标签。其中，我们使用 bert 获取文本向量，博文和标签 ( 关键词 ) 分别以字的形式输入至 bert 模型中，对模型最后一层的每个字相加求均值，效果最好 ( 不同文本相似度计算任务向量取法有所区别，有的任务取最后一层 cls 的向量效果较好 )。我们用于计算相似度的 bert 模型是基于博文的分类 fine-tuning 结果的模型。&lt;/p&gt;  &lt;p&gt;除了标签还进行了同义词、近义词和上下文词的挖掘，这里不展开做过多阐述。&lt;/p&gt;  &lt;h4&gt;④ 博文 embedding 标签&lt;/h4&gt;  &lt;p&gt;   &lt;img src="https://img2.tuicool.com/NRrQB3y.png!web"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;Embedding 标签通过用户的点击序列建模获取。模型如图所示，特征为 f0:用户id，f1: 用户的自身属性，f2: 用户点击列表，f3: 博文的id，f4：博文自身属性，label 为是否点击博文。如前文所述，用户的行为序列不能十分精准的获取，所以 embedding 的效果并不好。&lt;/p&gt;  &lt;p&gt;以上就是基于博文的一些打标签的方式，下面我们再来讨论一下基于用户方面的标签。&lt;/p&gt;  &lt;p&gt;❷    &lt;strong&gt;用户兴趣标签&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;   &lt;img src="https://img2.tuicool.com/VbaqaqQ.png!web"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;①   &lt;strong&gt;首先是基于模型 embedding 的用户兴趣标签：&lt;/strong&gt;上文中有介绍，此方法效果不太好。&lt;/p&gt;  &lt;h4&gt;② 基于统计的标签：&lt;/h4&gt;  &lt;p&gt;❶ 标签可以分成三种类型，分别为长期、短期和即时。分别进行统计，就有了不同类型的标签，可用于做召回或者排序模型的特征。&lt;/p&gt;  &lt;p&gt;❷ 关键词用户兴趣标签的特征有以下两个方面：一是，关键词在特定的用户行为中的曝光率、互动率、转评赞率和平均停留时长；二是，关键词在全量用户行为中的曝光率、互动率、转评赞率和平均停留时长，用来表示在大众整体的偏好，可用于排序的输入特征。&lt;/p&gt;  &lt;p&gt;❸ 将用户生成博文 embedding 映射到用户兴趣标签中。&lt;/p&gt;  &lt;p&gt;❹ 在以上计算用户兴趣的标签时，会用到一些平滑的策略。&lt;/p&gt;  &lt;h4&gt;——物料库——&lt;/h4&gt;  &lt;p&gt;   &lt;img src="https://img1.tuicool.com/VVneeuq.png!web"&gt;&lt;/img&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;   &lt;img src="https://img0.tuicool.com/Yr26zez.jpg!web"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;各层的权重在训练过程中优化获得。在权重embedding的基础上进行reduce_mean操作获取到最终的embedding表示，进行sigmoid操作获取到相应的概率分数。我们基于人工标注的数据进行 bert 的 fine-turning。&lt;/p&gt;  &lt;p&gt;❷    &lt;strong&gt;物料评级模型&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;   &lt;img src="https://img0.tuicool.com/aeUnQvj.jpg!web"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;物料评级模型分成两块：一块是博文的内容，使用 bert 进行文本向量的抽取；另一块为博主的特征和上下文特征 ( 博主发博的上下文 )，比如博主等级，粉丝数，健康等级，博主昵称等，把相关特征拼接在一起，进行多分类。&lt;/p&gt;  &lt;p&gt;——多模态——&lt;/p&gt;  &lt;p&gt;上文也介绍过，对于文本较短的博文，我们没法获取太多的信息，这样需要借助多模态信息。&lt;/p&gt;  &lt;p&gt;   &lt;img src="https://img1.tuicool.com/2MnqEnu.jpg!web"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;比如通过图像信息对文本内容的信息进行补充。那么通过该图像模态，我们可以打上美女这样的标签。&lt;/p&gt;  &lt;p&gt;   &lt;img src="https://img0.tuicool.com/UZBveyb.jpg!web"&gt;&lt;/img&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;   &lt;img src="https://img0.tuicool.com/z6fyeqy.png!web"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;上图是在 BERT 发布之前的做法：多模态对偶模型，其中文本编码使用 lstm 模块，图像编码则使用 Inception-Resnet-V2 模块。两者编码对齐到同一个长度进行 attention-both 计算，之后两个模块分别与文本编码和图像编码进行加权计算，然后向量 add 输出多模态向量，最后解码进行标签分类。Attention-both 可以这样理解：由于 image、文本都编码为语义向量，单独的使用文本进行解码时，可以利用图片的 attention 信息，而单独使用图片解码时，可以利用到文本的 attention 信息，Attention-both 就是这两者的结合。模型效果相对于文本模型有一定的提升，但是性能会比较慢。&lt;/p&gt;  &lt;p&gt;❷    &lt;strong&gt;BERT 多模态模型&lt;/strong&gt;&lt;/p&gt;  &lt;h4&gt;① 预训练模型&lt;/h4&gt;  &lt;p&gt;BERT 出来之后，微软发表了图像结合 BERT 的多模态预训练文章。&lt;/p&gt;  &lt;p&gt;   &lt;img src="https://img1.tuicool.com/Qf2eUnj.jpg!web"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;其预训练除了文本还加入了图像的部分。首先把完整的图像做切分 ( Fast RCNN )，每个为一个个小块图片，并且做 mask 输入到模型中进行预测还原。&lt;/p&gt;  &lt;p&gt;   &lt;img src="https://img1.tuicool.com/MjANrmM.jpg!web"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;文本和图片的向量融合再进行预训练，输出文本和图片的联合向量 。联合向量可用于相似博文的召回和根据博文内容来检索相似图片 ( 自动配图 )。&lt;/p&gt;  &lt;h4&gt;② 多模态打标签&lt;/h4&gt;  &lt;p&gt;   &lt;img src="https://img1.tuicool.com/RrQVjan.jpg!web"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;我们也尝试在多模态预训练之后做打标签的任务，预训练的向量之后进行多分类的 finetuning。&lt;/p&gt;  &lt;p&gt;——多任务——&lt;/p&gt;  &lt;p&gt;   &lt;img src="https://img0.tuicool.com/UviiaeI.jpg!web"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;对比之前版本的质量模型，多任务新增了 ctr 和停留时长的任务。整体效果会有些提升。&lt;/p&gt;  &lt;p&gt;——大规模预训练模型技术——&lt;/p&gt;  &lt;p&gt;   &lt;img src="https://img1.tuicool.com/EJby2mZ.jpg!web"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;有以下两种模型：BERT 和 GPT2，而这两种模型是有区别的。BERT 主要做编码，对文本进行理解。GPT2 则为做生成任务，可辅助写作。杜则尧同学开源了基于中文的 GPT2 模型：&lt;/p&gt;  &lt;p&gt;https://github.com/Morizeyao/GPT2-Chinese&lt;/p&gt;  &lt;p&gt;感兴趣可以具体了解。&lt;/p&gt;  &lt;p&gt;   &lt;img src="https://img2.tuicool.com/v26fQjQ.jpg!web"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;除了 BERT 和 GPT2，还有 T5。T5 整合了 NLP 的四大任务：序列标注 ，分类任务 ，句子关系判断 ，生成式任务。&lt;/p&gt;  &lt;p&gt;我们目前在大规模预训练模型技术，做了以下几块工作：&lt;/p&gt;  &lt;p&gt;❶ 实现了 GPT2 模型的训练和推理&lt;/p&gt;  &lt;p&gt;❷ 同时也实现了 T5 模型训练和推理&lt;/p&gt;  &lt;p&gt;❸ 由于模型参数太多，性能太慢，我们尝试了模型的蒸馏、量化和 Tensor RT 以便提升性能&lt;/p&gt;  &lt;p&gt;❹ 由于 T5 训练时是大杂烩，把所有任务和内容放在一块训练，我们认为把更相似的、可以相互影响的任务一起训练效果很更好&lt;/p&gt;  &lt;h4&gt;除此之外还孵化了以下几个项目：&lt;/h4&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;h4&gt;——总结——&lt;/h4&gt;  &lt;p&gt;今天主要讲了新浪微博在 feed 流中遇到的 NLP 问题和解决思路。我们内部正与产品、架构团队积极沟通，以获取高精度的用户行为序列用于用户行为的分析和建模。由于博文较短，我们没有采用主流 LDA/PLSA 技术解决相关问题，而采用了更为传统的 NLP 技术和一些 trick 完成了实体/关键词的挖掘和相关性计算。&lt;/p&gt;  &lt;p&gt;从技术来讲，随着预训练模型的出现，NLP 最近这几年取得了实质性的进展，但不可忽略的是：传统的 NLP 技术在某些场景下依然发挥着重要的作用。另外，一个技术趋势是在解决相关问题时逐渐从单一的技术走向多任务、多模态、多语言的融合。&lt;/p&gt;  &lt;p&gt;   &lt;strong&gt;分享嘉宾&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;   &lt;strong&gt;    &lt;strong&gt;▬&lt;/strong&gt;&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;   &lt;img src="https://img1.tuicool.com/Yn2ayiI.jpg!web"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;董兴华&lt;/p&gt;  &lt;p&gt;新浪微博 | 资深算法专家&lt;/p&gt;  &lt;p&gt;——END——&lt;/p&gt;  &lt;p&gt;文章推荐：&lt;/p&gt;  &lt;p&gt;   &lt;a href="http://mp.weixin.qq.com/s?__biz=MzU1NTMyOTI4Mw==&amp;mid=2247496157&amp;idx=1&amp;sn=d7a00dad2fb9c206ab7ba75fc5c4358d&amp;chksm=fbd743b1cca0caa79c4c0675b5dd9420b33fcb68a560890ad2168412bdf61ce0f1a80fe0ceea&amp;scene=21#wechat_redirect" rel="nofollow,noindex" target="_blank"&gt;推荐场景中召回模型的演化过程&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;   &lt;a href="http://mp.weixin.qq.com/s?__biz=MzU1NTMyOTI4Mw==&amp;mid=2247495786&amp;idx=1&amp;sn=da7f3edb91280bc37dbf276e74cfecba&amp;chksm=fbd74206cca0cb10d871e9af5e07a7f0427b7483dad6bd770e926b7622f31b0d4b1800e9bd15&amp;scene=21#wechat_redirect" rel="nofollow,noindex" target="_blank"&gt;知识结构化在阿里小蜜中的应用&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;   &lt;strong&gt;    &lt;strong&gt;     &lt;strong&gt;      &lt;img src="https://img0.tuicool.com/YRvymqN.jpg!web"&gt;&lt;/img&gt;&lt;/strong&gt;&lt;/strong&gt;&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;DataFunTalk：&lt;/p&gt;  &lt;p&gt;专注于大数据、人工智能技术应用的分享与交流。&lt;/p&gt;  &lt;p&gt;   &lt;img src="https://img1.tuicool.com/nQry6nn.jpg!web"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;一个「在看」，一段时光！   &lt;strong&gt;:point_down:&lt;/strong&gt;&lt;/p&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>tuicool</category>
      <guid isPermaLink="true">https://itindex.net/detail/60255-nlp-%E6%8A%80%E6%9C%AF-%E5%BE%AE%E5%8D%9A</guid>
      <pubDate>Sat, 28 Dec 2019 00:00:00 CST</pubDate>
    </item>
    <item>
      <title>微服务架构~BFF和网关是如何演化出来的 - 大大的橙子 - 博客园</title>
      <link>https://itindex.net/detail/60208-%E5%BE%AE%E6%9C%8D%E5%8A%A1-%E6%9E%B6%E6%9E%84-bff</link>
      <description>&lt;div&gt;    &lt;h3&gt; 介绍&lt;/h3&gt;    &lt;p&gt;BFF(Backend for Frontend)和网关Gateway是微服务架构中的两个重要概念，这两个概念相对比较新，有些开发人员甚至是架构师都不甚理解。&lt;/p&gt;    &lt;p&gt;本文用假想的公司案例+图示的方式，解释BFF和网关是什么，它们是怎么演化出来的。希望对架构师设计和落地微服务架构有所启发。&lt;/p&gt;    &lt;h3&gt;服务化架构V1&lt;/h3&gt;    &lt;p&gt;我们先把时间推回到大致2011年左右。假设有一家有一定业务体量的电商公司CoolShop，在这个时间点它已经完成单块应用的解构拆分，内部SOA服务化已经初步完成。这个时候它的无线应用还没有起步，前端用户体验层主要是传统的服务端Web应用，总体服务化架构V1如下图所示。&lt;/p&gt;    &lt;p&gt;      &lt;img alt="" src="https://images2018.cnblogs.com/blog/1050892/201807/1050892-20180726172012816-320411474.png"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;p&gt; &lt;/p&gt;    &lt;h3&gt;服务化架构V2&lt;/h3&gt;    &lt;p&gt;时间转眼来到2012年初，国内的无线应用开始起风，CoolShop公司也紧跟市场趋势，研发自己的无线原生App。为了能尽快上线，公司的架构师提出如下V2架构，让App直接调用内部的服务：&lt;/p&gt;    &lt;p&gt; &lt;/p&gt;    &lt;p&gt;      &lt;img alt="" src="https://images2018.cnblogs.com/blog/1050892/201807/1050892-20180726172058886-580750127.png"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;p&gt; &lt;/p&gt;    &lt;p&gt;这个架构有如下问题：&lt;/p&gt;    &lt;ol&gt;      &lt;li&gt;        &lt;p&gt;无线App和内部微服务强耦合，任何一边的变化都可能对另外一边造成影响。&lt;/p&gt;&lt;/li&gt;      &lt;li&gt;        &lt;p&gt;无线App需要知道内部服务的地址等细节。&lt;/p&gt;&lt;/li&gt;      &lt;li&gt;        &lt;p&gt;无线App端需要开发大量的聚合裁剪和适配逻辑：&lt;/p&gt;&lt;/li&gt;      &lt;ul&gt;        &lt;li&gt;          &lt;p&gt;            &lt;strong&gt;聚合&lt;/strong&gt;：某一个功能需要同时调用几个后端API进行组合，比如首页需要显示分类和产品细节，就要同时调用分类API和产品API，不能一次调用完成。&lt;/p&gt;&lt;/li&gt;        &lt;li&gt;          &lt;p&gt;            &lt;strong&gt;裁剪&lt;/strong&gt;：后端服务返回的Payload一般比较通用，App需要根据设备类型进行裁剪，比如手机屏幕小，需要多裁掉一些不必要的内容，Pad的屏幕比较大，可以少裁掉一些内容。&lt;/p&gt;&lt;/li&gt;        &lt;li&gt;          &lt;p&gt;            &lt;strong&gt;适配&lt;/strong&gt;：一种常见的适配场景是格式转换，比如有些后台服务比较老，只支持老的SOAP/XML格式，不支持新的JSON格式，则无线App需要适配处理不同数据格式。&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;      &lt;li&gt;        &lt;p&gt;随着设备类型的增多(iPhone/Android/iPad/WindowsPhone)，聚合裁剪和适配逻辑的开发会造成设备端的大量重复劳动。&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;    &lt;h3&gt;服务化架构V2.1&lt;/h3&gt;    &lt;p&gt;V2架构问题太多，没有开发实施。为解决上述问题，架构师经过思考决定在外部设备和内部微服务之间引入一个新的角色~Mobile BFF。&lt;/p&gt;    &lt;p&gt;所谓BFF其实是Backend for Frontend的简称，中文翻译是为前端而开发的后端，它主要由前端团队开发（后端微服务一般由后端团队开发）。BFF可以认为是一种适配服务，将后端的微服务进行适配（主要包括聚合裁剪和格式适配等逻辑），向无线端设备暴露友好和统一的API，方便无线设备接入访问后端服务。&lt;/p&gt;    &lt;p&gt;新的V2.1架构如下图所以：&lt;/p&gt;    &lt;p&gt; &lt;/p&gt;    &lt;p&gt;      &lt;img alt="" src="https://images2018.cnblogs.com/blog/1050892/201807/1050892-20180726172123192-2124844424.png"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;p&gt; &lt;/p&gt;    &lt;p&gt; &lt;/p&gt;    &lt;p&gt;这个架构的优势是：&lt;/p&gt;    &lt;ol&gt;      &lt;li&gt;        &lt;p&gt;无线App和内部微服务不耦合，通过引入BFF这层间接，使得两边可以独立变化：&lt;/p&gt;&lt;/li&gt;      &lt;ul&gt;        &lt;li&gt;          &lt;p&gt;后端如果发生变化，通过BFF屏蔽，前端设备可以做到不受影响。&lt;/p&gt;&lt;/li&gt;        &lt;li&gt;          &lt;p&gt;前端如果发生变化，通过BFF屏蔽，后端微服务可以暂不变化。&lt;/p&gt;&lt;/li&gt;        &lt;li&gt;          &lt;p&gt;当无线App有新的需求时，通过BFF的屏蔽，可以减少前后端团队的沟通协调开销，很多需求由前端团队在BFF上就可以自己搞定。&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;      &lt;li&gt;        &lt;p&gt;无线App只需要知道Mobile BFF的地址，并且服务接口是统一的，不需要知道内部复杂微服务的地址和细节。&lt;/p&gt;&lt;/li&gt;      &lt;li&gt;        &lt;p&gt;聚合裁剪和适配逻辑在Mobile BFF上实现，无线App端可以大大简化瘦身。&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;    &lt;h3&gt;服务化架构V3&lt;/h3&gt;    &lt;p&gt;V2.1架构比较成功，实施落地以后支持了CoolShop公司早期无线业务的成长。随着业务量进一步增长，投入无线研发的团队也不断增加，V2.1架构也逐渐暴露出如下问题：&lt;/p&gt;    &lt;ol&gt;      &lt;li&gt;        &lt;p&gt;刚开始只有一个Mobile BFF，是个单块，但是无线研发团队在不断增加，分别对应多条业务线。根据康威法则，单块的无线BFF和多团队之间就出现不匹配问题，团队之间沟通协调成本高，交付效率低下。&lt;/p&gt;&lt;/li&gt;      &lt;li&gt;        &lt;p&gt;Mobile BFF里头不仅有各个业务线的聚合/裁剪/适配和业务逻辑，还引入了很多跨横切面逻辑，比如安全认证，日志监控，限流熔断等。随着时间的推移，代码变得越来越复杂，技术债越堆越多，开发效率不断下降，缺陷数量不断增加。&lt;/p&gt;&lt;/li&gt;      &lt;li&gt;        &lt;p&gt;Mobile BFF集群是个失败单点(Single Point of Failure)，严重代码缺陷或者流量洪峰可能引发集群宕机，所有无线应用都不可用。&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;    &lt;p&gt;为了解决上述问题，架构师经过思考决定在外部设备和内部BFF之间再引入一个新的角色~API Gateway，新的架构V3如下图所示：&lt;/p&gt;    &lt;p&gt;      &lt;img alt="" src="https://images2018.cnblogs.com/blog/1050892/201807/1050892-20180726172243286-134363940.png"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;p&gt; &lt;/p&gt;    &lt;p&gt; &lt;/p&gt;    &lt;p&gt;新的架构V3有如下调整：&lt;/p&gt;    &lt;ol&gt;      &lt;li&gt;        &lt;p&gt;BFF按团队或业务线进行解耦拆分，拆分成若干个BFF微服务，每个业务线可以并行开发和交付各自负责的BFF微服务。&lt;/p&gt;&lt;/li&gt;      &lt;li&gt;        &lt;p&gt;网关（一般由独立框架团队负责运维）专注跨横切面(Cross-Cutting Concerns)的功能，包括：&lt;/p&gt;&lt;/li&gt;      &lt;ul&gt;        &lt;li&gt;          &lt;p&gt;            &lt;strong&gt;路由&lt;/strong&gt;，将来自无线设备的请求路由到后端的某个微服务BFF集群。&lt;/p&gt;&lt;/li&gt;        &lt;li&gt;          &lt;p&gt;            &lt;strong&gt;认证&lt;/strong&gt;，对涉及敏感数据的API访问进行集中认证鉴权。&lt;/p&gt;&lt;/li&gt;        &lt;li&gt;          &lt;p&gt;            &lt;strong&gt;监控&lt;/strong&gt;，对API调用进行性能监控。&lt;/p&gt;&lt;/li&gt;        &lt;li&gt;          &lt;p&gt;            &lt;strong&gt;限流熔断&lt;/strong&gt;，当出现流量洪峰，或者后端BFF/微服务出现延迟或故障，网关能够主动进行限流熔断，保护后端服务，并保持前端用户体验可以接受。&lt;/p&gt;&lt;/li&gt;        &lt;li&gt;          &lt;p&gt;            &lt;strong&gt;安全防爬&lt;/strong&gt;，收集访问日志，通过后台分析出恶意行为，并阻断恶意请求。&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;      &lt;li&gt;        &lt;p&gt;网关在无线设备和BFF之间又引入了一层间接，让两边可以独立变化，特别是当后台BFF在升级或迁移时，可以做到用户端应用不受影响。&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;    &lt;p&gt;在新的V3架构中，网关承担了重要的角色，它是解耦拆分和后续升级迁移的利器。在网关的配合下，单块BFF实现了解耦拆分，各业务线团队可以独立开发和交付各自的微服务，研发效率大大提升。另外，把跨横切面逻辑从BFF剥离到网关上去以后，BFF的开发人员可以更加专注业务逻辑交付，实现了架构上的关注分离(Separation of Concerns)。&lt;/p&gt;    &lt;h3&gt;服务化架构V4&lt;/h3&gt;    &lt;p&gt;业务在不断发展，技术架构也需要不断的调整来应对需求的变化。近年，CoolShop公司技术团队又迎来了新的业务和技术需求，主要是：&lt;/p&gt;    &lt;ol&gt;      &lt;li&gt;        &lt;p&gt;开放内部的业务能力，建设CoolShop Open API平台。借助第三方社区开发者的力量，在CoolShop平台上进行创新，进一步拓宽CoolShop的应用和业务形态。&lt;/p&gt;&lt;/li&gt;      &lt;li&gt;        &lt;p&gt;废弃传统的服务端Web应用模式，引入前后分离架构，前端采用H5单页等技术给用户提供更好的体验。&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;    &lt;p&gt;为满足业务需求，架构师对服务化架构又进行了拓展升级，新的V4新架构如下图所示：&lt;/p&gt;    &lt;p&gt; &lt;/p&gt;    &lt;p&gt;      &lt;img alt="" src="https://images2018.cnblogs.com/blog/1050892/201807/1050892-20180726172334264-1109465874.png"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;p&gt; &lt;/p&gt;    &lt;p&gt; &lt;/p&gt;    &lt;p&gt;V4整体思路和V3类似，只是拓展了新的接入渠道:&lt;/p&gt;    &lt;ol&gt;      &lt;li&gt;        &lt;p&gt;引入面向第三方开放API的BFF层和配套的网关，支持第三方开发者在CoolShop Open API平台上开发应用。&lt;/p&gt;&lt;/li&gt;      &lt;li&gt;        &lt;p&gt;引入面向H5应用的BFF层和配套的网关，支持前后分离和H5单页应用模式。&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;    &lt;p&gt;V4是一个比较完整的现代微服务架构，从外到内依次分为：端用户体验层-&amp;gt;网关层-&amp;gt;BFF层-&amp;gt;微服务层。整个架构层次清晰，职责分明，是一种灵活的能够支持业务不断创新的演化式架构。&lt;/p&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 />
      <guid isPermaLink="true">https://itindex.net/detail/60208-%E5%BE%AE%E6%9C%8D%E5%8A%A1-%E6%9E%B6%E6%9E%84-bff</guid>
      <pubDate>Sun, 08 Dec 2019 21:17:45 CST</pubDate>
    </item>
    <item>
      <title>微服务的脚手架Jhipster使用(一) - 陆陆起飞啦 - 博客园</title>
      <link>https://itindex.net/detail/60201-%E5%BE%AE%E6%9C%8D%E5%8A%A1-%E8%84%9A%E6%89%8B%E6%9E%B6-jhipster</link>
      <description>&lt;h4&gt;  &lt;strong&gt;随着微服务的普及以及docker容器的广泛应用，有传统的soa服务衍生出微服务的概念，微服务强调的是服务的独立性，屏蔽底层物理平台的差异，此时你会发现微服务跟容器技术完美契合。&lt;/strong&gt;  &lt;strong&gt;在此基础上衍生出的云原生以及DevOps的概念，废话不多说介绍一个非常牛叉的springCloud脚手架- -jhipster。&lt;/strong&gt;&lt;/h4&gt; &lt;p&gt;  &lt;strong&gt;　　   &lt;img alt="" height="182" src="https://img2018.cnblogs.com/blog/1465464/201811/1465464-20181121150604709-1128393896.png" width="183"&gt;&lt;/img&gt;&lt;/strong&gt;&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;   &lt;strong&gt;  　　安装&lt;/strong&gt;   &lt;strong&gt;　&lt;/strong&gt;&lt;/li&gt;&lt;/ul&gt; &lt;ol&gt;  &lt;li&gt;安装Java 8 from    &lt;a href="http://www.oracle.com/technetwork/java/javase/downloads/index.html"&gt;the Oracle website&lt;/a&gt;.&lt;/li&gt;  &lt;li&gt;安装Node.js from    &lt;a href="https://nodejs.org/"&gt;the Node.js website&lt;/a&gt; (请安装 64-bit version)&lt;/li&gt;  &lt;li&gt;安装npm包:    &lt;code&gt;npm install -g npm&lt;/code&gt;&lt;/li&gt;  &lt;li&gt;如果你想使用jhipster应用市场, 请安装 Yeoman:    &lt;code&gt;npm install -g yo&lt;/code&gt;&lt;/li&gt;  &lt;li&gt;最后安装JHipster:    &lt;code&gt;npm install -g generator-jhipster&lt;/code&gt;&lt;/li&gt;&lt;/ol&gt; &lt;ul&gt;  &lt;li&gt;   &lt;strong&gt;         生成项目&lt;/strong&gt;&lt;/li&gt;&lt;/ul&gt; &lt;ol&gt;  &lt;li&gt;选择一个空的文件夹打开cmd：jhipster   &lt;strong&gt;　&lt;/strong&gt;&lt;/li&gt;  &lt;li&gt;根据一步步step提示选择构建自己的服务项目   &lt;div&gt;    &lt;div&gt;     &lt;table border="0" cellpadding="0" cellspacing="0"&gt;      &lt;tr&gt;       &lt;td&gt;        &lt;div&gt;1&lt;/div&gt;        &lt;div&gt;2&lt;/div&gt;        &lt;div&gt;3&lt;/div&gt;        &lt;div&gt;4&lt;/div&gt;        &lt;div&gt;5&lt;/div&gt;        &lt;div&gt;6&lt;/div&gt;        &lt;div&gt;7&lt;/div&gt;        &lt;div&gt;8&lt;/div&gt;        &lt;div&gt;9&lt;/div&gt;        &lt;div&gt;10&lt;/div&gt;        &lt;div&gt;11&lt;/div&gt;        &lt;div&gt;12&lt;/div&gt;        &lt;div&gt;13&lt;/div&gt;        &lt;div&gt;14&lt;/div&gt;        &lt;div&gt;15&lt;/div&gt;        &lt;div&gt;16&lt;/div&gt;        &lt;div&gt;17&lt;/div&gt;        &lt;div&gt;18&lt;/div&gt;        &lt;div&gt;19&lt;/div&gt;        &lt;div&gt;20&lt;/div&gt;        &lt;div&gt;21&lt;/div&gt;        &lt;div&gt;22&lt;/div&gt;        &lt;div&gt;23&lt;/div&gt;        &lt;div&gt;24&lt;/div&gt;        &lt;div&gt;25&lt;/div&gt;        &lt;div&gt;26&lt;/div&gt;        &lt;div&gt;27&lt;/div&gt;        &lt;div&gt;28&lt;/div&gt;        &lt;div&gt;29&lt;/div&gt;        &lt;div&gt;30&lt;/div&gt;        &lt;div&gt;31&lt;/div&gt;        &lt;div&gt;32&lt;/div&gt;        &lt;div&gt;33&lt;/div&gt;        &lt;div&gt;34&lt;/div&gt;        &lt;div&gt;35&lt;/div&gt;        &lt;div&gt;36&lt;/div&gt;        &lt;div&gt;37&lt;/div&gt;        &lt;div&gt;38&lt;/div&gt;        &lt;div&gt;39&lt;/div&gt;        &lt;div&gt;40&lt;/div&gt;        &lt;div&gt;41&lt;/div&gt;        &lt;div&gt;42&lt;/div&gt;        &lt;div&gt;43&lt;/div&gt;        &lt;div&gt;44&lt;/div&gt;        &lt;div&gt;45&lt;/div&gt;        &lt;div&gt;46&lt;/div&gt;        &lt;div&gt;47&lt;/div&gt;        &lt;div&gt;48&lt;/div&gt;        &lt;div&gt;49&lt;/div&gt;        &lt;div&gt;50&lt;/div&gt;        &lt;div&gt;51&lt;/div&gt;        &lt;div&gt;52&lt;/div&gt;        &lt;div&gt;53&lt;/div&gt;        &lt;div&gt;54&lt;/div&gt;        &lt;div&gt;55&lt;/div&gt;        &lt;div&gt;56&lt;/div&gt;        &lt;div&gt;57&lt;/div&gt;        &lt;div&gt;58&lt;/div&gt;        &lt;div&gt;59&lt;/div&gt;        &lt;div&gt;60&lt;/div&gt;        &lt;div&gt;61&lt;/div&gt;        &lt;div&gt;62&lt;/div&gt;        &lt;div&gt;63&lt;/div&gt;        &lt;div&gt;64&lt;/div&gt;        &lt;div&gt;65&lt;/div&gt;        &lt;div&gt;66&lt;/div&gt;        &lt;div&gt;67&lt;/div&gt;        &lt;div&gt;68&lt;/div&gt;&lt;/td&gt;       &lt;td&gt;        &lt;div&gt;         &lt;div&gt;          &lt;code&gt;&amp;lt;strong&amp;gt;windows下：&amp;lt;/strong&amp;gt;&amp;lt;br&amp;gt;以下demo选择微服务应用。实际中根据自己需求生产项目。&lt;/code&gt;&lt;/div&gt;         &lt;div&gt;          &lt;code&gt;1&lt;/code&gt;          &lt;code&gt;: Which *type* of application would you like to create? (Use arrow keys)&lt;/code&gt;&lt;/div&gt;         &lt;div&gt;          &lt;code&gt;   &lt;/code&gt;          &lt;code&gt;Monolithic application (recommended &lt;/code&gt;          &lt;code&gt;for&lt;/code&gt;           &lt;code&gt;simple projects)  &lt;/code&gt;          &lt;code&gt;//简单项目&lt;/code&gt;&lt;/div&gt;         &lt;div&gt;          &lt;code&gt;   &lt;/code&gt;          &lt;code&gt;Microservice application &lt;/code&gt;          &lt;code&gt;// 微服务应用&lt;/code&gt;&lt;/div&gt;         &lt;div&gt;          &lt;code&gt;   &lt;/code&gt;          &lt;code&gt;Microservice gateway &lt;/code&gt;          &lt;code&gt;// 微服务网关&lt;/code&gt;&lt;/div&gt;         &lt;div&gt;          &lt;code&gt;   &lt;/code&gt;          &lt;code&gt;JHipster UAA server (&lt;/code&gt;          &lt;code&gt;for&lt;/code&gt;           &lt;code&gt;microservice OAuth2 authentication) &lt;/code&gt;          &lt;code&gt;// 微服务认证&lt;/code&gt;&lt;/div&gt;         &lt;div&gt;          &lt;code&gt;2&lt;/code&gt;           &lt;code&gt;:What is the base name of your application? (huhuawei)&lt;/code&gt;&lt;/div&gt;         &lt;div&gt;          &lt;code&gt;    &lt;/code&gt;          &lt;code&gt;输入服务名称&lt;/code&gt;&lt;/div&gt;         &lt;div&gt;          &lt;code&gt;3&lt;/code&gt;          &lt;code&gt;: As you are running in a microservice architecture, on which port would like        &lt;/code&gt;&lt;/div&gt;         &lt;div&gt;          &lt;code&gt;    &lt;/code&gt;          &lt;code&gt;your server to run? It should be unique to avoid port conflicts. (&lt;/code&gt;          &lt;code&gt;8081&lt;/code&gt;          &lt;code&gt;)&lt;/code&gt;&lt;/div&gt;         &lt;div&gt;          &lt;code&gt;    &lt;/code&gt;          &lt;code&gt;设置服务的端口号&lt;/code&gt;&lt;/div&gt;         &lt;div&gt;          &lt;code&gt;4&lt;/code&gt;          &lt;code&gt;：What is your &lt;/code&gt;          &lt;code&gt;default&lt;/code&gt;           &lt;code&gt;Java &lt;/code&gt;          &lt;code&gt;package&lt;/code&gt;           &lt;code&gt;name? (com.mycompany.myapp)&lt;/code&gt;&lt;/div&gt;         &lt;div&gt;          &lt;code&gt;    &lt;/code&gt;          &lt;code&gt;设置包名&lt;/code&gt;&lt;/div&gt;         &lt;div&gt;          &lt;code&gt;5&lt;/code&gt;          &lt;code&gt;：Which service discovery server &lt;/code&gt;          &lt;code&gt;do&lt;/code&gt;           &lt;code&gt;you want to use? (Use arrow keys)&lt;/code&gt;&lt;/div&gt;         &lt;div&gt;          &lt;code&gt;     &lt;/code&gt;          &lt;code&gt;JHipster Registry (uses Eureka)&lt;/code&gt;&lt;/div&gt;         &lt;div&gt;          &lt;code&gt;     &lt;/code&gt;          &lt;code&gt;Consul&lt;/code&gt;&lt;/div&gt;         &lt;div&gt;          &lt;code&gt;     &lt;/code&gt;          &lt;code&gt;No service discovery&lt;/code&gt;&lt;/div&gt;         &lt;div&gt;          &lt;code&gt;    &lt;/code&gt;          &lt;code&gt;选择注册中心。一般选择Registry比较多&lt;/code&gt;&lt;/div&gt;         &lt;div&gt;          &lt;code&gt;6&lt;/code&gt;          &lt;code&gt;：Which *type* of authentication would you like to use? (Use arrow keys)&lt;/code&gt;&lt;/div&gt;         &lt;div&gt;          &lt;code&gt;     &lt;/code&gt;          &lt;code&gt;JWT authentication (stateless, with a token)  &lt;/code&gt;          &lt;code&gt;// jwt&lt;/code&gt;&lt;/div&gt;         &lt;div&gt;          &lt;code&gt;     &lt;/code&gt;          &lt;code&gt;OAuth &lt;/code&gt;          &lt;code&gt;2.0&lt;/code&gt;           &lt;code&gt;/ OIDC Authentication (stateful, works with Keycloak and    &lt;/code&gt;&lt;/div&gt;         &lt;div&gt;          &lt;code&gt;     &lt;/code&gt;          &lt;code&gt;Okta)&lt;/code&gt;          &lt;code&gt;//Oauth2 OIDC 认证服务&lt;/code&gt;&lt;/div&gt;         &lt;div&gt;          &lt;code&gt;     &lt;/code&gt;          &lt;code&gt;Authentication with JHipster UAA server (the server must be generated &lt;/code&gt;&lt;/div&gt;         &lt;div&gt;          &lt;code&gt;     &lt;/code&gt;          &lt;code&gt;separately) &lt;/code&gt;          &lt;code&gt;// Oauth2+jwt Uaa认证服务&lt;/code&gt;&lt;/div&gt;         &lt;div&gt;          &lt;code&gt;     &lt;/code&gt;          &lt;code&gt;选择授权中心&lt;/code&gt;&lt;/div&gt;         &lt;div&gt;          &lt;code&gt;7&lt;/code&gt;          &lt;code&gt;： Which *type* of database would you like to use?&lt;/code&gt;&lt;/div&gt;         &lt;div&gt;          &lt;code&gt;      &lt;/code&gt;          &lt;code&gt;SQL (H2, MySQL, MariaDB, PostgreSQL, Oracle, MSSQL)&lt;/code&gt;&lt;/div&gt;         &lt;div&gt;          &lt;code&gt;      &lt;/code&gt;          &lt;code&gt;MongoDB&lt;/code&gt;&lt;/div&gt;         &lt;div&gt;          &lt;code&gt;      &lt;/code&gt;          &lt;code&gt;Couchbase&lt;/code&gt;&lt;/div&gt;         &lt;div&gt;          &lt;code&gt;      &lt;/code&gt;          &lt;code&gt;No database&lt;/code&gt;&lt;/div&gt;         &lt;div&gt;          &lt;code&gt;      &lt;/code&gt;          &lt;code&gt;Cassandra&lt;/code&gt;&lt;/div&gt;         &lt;div&gt;          &lt;code&gt;      &lt;/code&gt;          &lt;code&gt;选择数据库支持Nosql跟常见RDMB数据库&lt;/code&gt;&lt;/div&gt;         &lt;div&gt;          &lt;code&gt;8&lt;/code&gt;          &lt;code&gt;：? Which *production* database would you like to use? (Use arrow keys)&lt;/code&gt;&lt;/div&gt;         &lt;div&gt;          &lt;code&gt;      &lt;/code&gt;          &lt;code&gt;MySQL&lt;/code&gt;&lt;/div&gt;         &lt;div&gt;          &lt;code&gt;      &lt;/code&gt;          &lt;code&gt;MariaDB&lt;/code&gt;&lt;/div&gt;         &lt;div&gt;          &lt;code&gt;      &lt;/code&gt;          &lt;code&gt;PostgreSQL&lt;/code&gt;&lt;/div&gt;         &lt;div&gt;          &lt;code&gt;      &lt;/code&gt;          &lt;code&gt;Oracle&lt;/code&gt;&lt;/div&gt;         &lt;div&gt;          &lt;code&gt;      &lt;/code&gt;          &lt;code&gt;Microsoft SQL Server&lt;/code&gt;&lt;/div&gt;         &lt;div&gt;          &lt;code&gt;      &lt;/code&gt;          &lt;code&gt;选择数据库,这边会出现两次第一次是production 第二次是devlopment&lt;/code&gt;&lt;/div&gt;         &lt;div&gt;          &lt;code&gt;9&lt;/code&gt;          &lt;code&gt;：Do you want to use the Spring cache abstraction?&lt;/code&gt;&lt;/div&gt;         &lt;div&gt;          &lt;code&gt;     &lt;/code&gt;          &lt;code&gt;根据需求选择缓存&lt;/code&gt;&lt;/div&gt;         &lt;div&gt;          &lt;code&gt;10&lt;/code&gt;          &lt;code&gt;：Do you want to use Hibernate 2nd level cache? (Y/n)&lt;/code&gt;&lt;/div&gt;         &lt;div&gt;          &lt;code&gt;     &lt;/code&gt;          &lt;code&gt;是否支持二级缓存&lt;/code&gt;&lt;/div&gt;         &lt;div&gt;          &lt;code&gt;11&lt;/code&gt;          &lt;code&gt;： Would you like to use Maven or Gradle &lt;/code&gt;          &lt;code&gt;for&lt;/code&gt;           &lt;code&gt;building the backend? (Use&lt;/code&gt;&lt;/div&gt;         &lt;div&gt;          &lt;code&gt;      &lt;/code&gt;          &lt;code&gt;arrow keys)&lt;/code&gt;&lt;/div&gt;         &lt;div&gt;          &lt;code&gt;      &lt;/code&gt;          &lt;code&gt;Maven&lt;/code&gt;&lt;/div&gt;         &lt;div&gt;          &lt;code&gt;      &lt;/code&gt;          &lt;code&gt;Gradle&lt;/code&gt;&lt;/div&gt;         &lt;div&gt;          &lt;code&gt;12&lt;/code&gt;          &lt;code&gt;：Which other technologies would you like to use?&lt;/code&gt;&lt;/div&gt;         &lt;div&gt;          &lt;code&gt;       &lt;/code&gt;          &lt;code&gt;安装一些其他的组件。如ES,KAFKA之类的&lt;/code&gt;&lt;/div&gt;         &lt;div&gt;          &lt;code&gt;13&lt;/code&gt;          &lt;code&gt;：Would you like to enable internationalization support? (Y/n)&lt;/code&gt;&lt;/div&gt;         &lt;div&gt;          &lt;code&gt;       &lt;/code&gt;          &lt;code&gt;支持国际化？&lt;/code&gt;&lt;/div&gt;         &lt;div&gt;          &lt;code&gt;14&lt;/code&gt;          &lt;code&gt;： Please choose the &lt;/code&gt;          &lt;code&gt;native&lt;/code&gt;           &lt;code&gt;language of the application (Use arrow keys)&lt;/code&gt;&lt;/div&gt;         &lt;div&gt;          &lt;code&gt;        &lt;/code&gt;          &lt;code&gt;English&lt;/code&gt;&lt;/div&gt;         &lt;div&gt;          &lt;code&gt;        &lt;/code&gt;          &lt;code&gt;Estonian&lt;/code&gt;&lt;/div&gt;         &lt;div&gt;          &lt;code&gt;        &lt;/code&gt;          &lt;code&gt;Farsi&lt;/code&gt;&lt;/div&gt;         &lt;div&gt;          &lt;code&gt;        &lt;/code&gt;          &lt;code&gt;French&lt;/code&gt;&lt;/div&gt;         &lt;div&gt;          &lt;code&gt;        &lt;/code&gt;          &lt;code&gt;Galician&lt;/code&gt;&lt;/div&gt;         &lt;div&gt;          &lt;code&gt;        &lt;/code&gt;          &lt;code&gt;........&lt;/code&gt;&lt;/div&gt;         &lt;div&gt;          &lt;code&gt;        &lt;/code&gt;          &lt;code&gt;选择本地支持的语言包含中文&lt;/code&gt;&lt;/div&gt;         &lt;div&gt;          &lt;code&gt;15&lt;/code&gt;          &lt;code&gt;：Please choose additional languages to install&lt;/code&gt;&lt;/div&gt;         &lt;div&gt;          &lt;code&gt;       &lt;/code&gt;          &lt;code&gt;可以额外安装其他语言&lt;/code&gt;&lt;/div&gt;         &lt;div&gt;          &lt;code&gt;16&lt;/code&gt;          &lt;code&gt;：Besides JUnit and Jest, which testing frameworks would you like to use?&lt;/code&gt;&lt;/div&gt;         &lt;div&gt;          &lt;code&gt;         &lt;/code&gt;          &lt;code&gt;Gatling&lt;/code&gt;&lt;/div&gt;         &lt;div&gt;          &lt;code&gt;         &lt;/code&gt;          &lt;code&gt;Cucumber   &lt;/code&gt;&lt;/div&gt;         &lt;div&gt;          &lt;code&gt;       &lt;/code&gt;          &lt;code&gt;选择测试框架，针对微服务http接口测试，生成测试报告&lt;/code&gt;&lt;/div&gt;         &lt;div&gt;          &lt;code&gt;17&lt;/code&gt;          &lt;code&gt;：Would you like to install other generators from the JHipster Marketplace?&lt;/code&gt;&lt;/div&gt;         &lt;div&gt;          &lt;code&gt;       &lt;/code&gt;          &lt;code&gt;从jhipster市场中选择组件安装&lt;/code&gt;&lt;/div&gt;         &lt;div&gt;          &lt;code&gt;      &lt;/code&gt;          &lt;code&gt;　&lt;/code&gt;&lt;/div&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;&lt;/div&gt;&lt;/li&gt;  &lt;li&gt;如果你觉得安装这些环境太麻烦，你又熟悉docker的基本命令，那建议使用docker去生成项目；   &lt;div&gt;    &lt;div&gt;     &lt;a title="&amp;#22797;&amp;#21046;&amp;#20195;&amp;#30721;"&gt;      &lt;img alt="&amp;#22797;&amp;#21046;&amp;#20195;&amp;#30721;" src="https://common.cnblogs.com/images/copycode.gif"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/div&gt;    &lt;pre&gt;选择linux服务器,安装docker；
yum install -y yum-utils device-mapper-persistent-data lvm2
yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
yum list docker-ce --showduplicates | sort -r
sudo yum install -y docker-ce
sudo systemctl start docker
sudo systemctl enable docker

拉取jhipster官方镜像
docker pull jhipster/jhipster:master

启动jhipster镜像,选择一个空文件/jhipster夹挂载到容器中
docker container run --name jhipster
-v  /jhipster:/home/jhipster/app 
-v  ~/.m2:/home/jhipster/.m2 
-p 8080:8080 
-p 9000:9000 
-p 3001:3001 
-d  -t jhipster/jhipster

进入容器中
docker container exec -it --user root jhipster bash
然后就可以生成项目了。与windows上操作无差别&lt;/pre&gt;    &lt;div&gt;     &lt;a title="&amp;#22797;&amp;#21046;&amp;#20195;&amp;#30721;"&gt;      &lt;img alt="&amp;#22797;&amp;#21046;&amp;#20195;&amp;#30721;" src="https://common.cnblogs.com/images/copycode.gif"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/div&gt;&lt;/div&gt;   &lt;p&gt; &lt;/p&gt;&lt;/li&gt;&lt;/ol&gt; &lt;ul&gt;  &lt;li&gt;   &lt;strong&gt;         项目的组成简单介绍&lt;/strong&gt;&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;  &lt;strong&gt;　　Gateway:&lt;/strong&gt; springcloud Zuul Proxy  进行动态路由微服务  &lt;strong&gt;。&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;　　Registry：&lt;/strong&gt;主要封装了Eureka以及配置中心Config Server。&lt;/p&gt; &lt;p&gt;      &lt;strong&gt;   Jhipster Console&lt;/strong&gt;：封装了Elk监控 以及 sleuth zipkin 等分布式链路监控组件。&lt;/p&gt; &lt;p&gt;       &lt;strong&gt;  Jhipster Uaa:&lt;/strong&gt;  采用UAA用户登录认证 OAUTH2集中式认证，默认不使用的话则是JWT无状态认证&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;　　　      &lt;img alt="" height="347" src="https://img2018.cnblogs.com/blog/1465464/201811/1465464-20181121224534214-674928118.png" width="631"&gt;&lt;/img&gt;   &lt;img alt="" height="348" src="https://img2018.cnblogs.com/blog/1465464/201811/1465464-20181121225457294-1939329980.png" width="639"&gt;&lt;/img&gt;&lt;/strong&gt;&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;   &lt;strong&gt;         总结&lt;/strong&gt;&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;  &lt;strong&gt;          &lt;/strong&gt;上述仅仅是大体的架构，Jhipster内部使用了很多插件来进行敏捷开发，包括实体类JDL快速生成从数据库到Controller代码，开发效率非常之高。适合中小型企业采用，而且Jhipster支持DockerFile与Compose                    文件生成，可以帮助我们快速容器化服务部署。&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 />
      <guid isPermaLink="true">https://itindex.net/detail/60201-%E5%BE%AE%E6%9C%8D%E5%8A%A1-%E8%84%9A%E6%89%8B%E6%9E%B6-jhipster</guid>
      <pubDate>Sun, 08 Dec 2019 09:58:23 CST</pubDate>
    </item>
    <item>
      <title>微服务的4个设计原则和19个解决方案 - 晓晨Master - 博客园</title>
      <link>https://itindex.net/detail/60179-%E5%BE%AE%E6%9C%8D%E5%8A%A1-%E8%AE%BE%E8%AE%A1-%E5%8E%9F%E5%88%99</link>
      <description>&lt;div&gt;    &lt;blockquote&gt;      &lt;p&gt;本文转自：        &lt;a href="http://developer.51cto.com/art/201709/552085.htm"&gt;http://developer.51cto.com/art/201709/552085.htm&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&gt;    &lt;p&gt;微服务架构现在是谈到企业应用架构时必聊的话题，微服务之所以火热也是因为相对之前的应用开发方式有很多优点，如更灵活、更能适应现在需求快速变更的大环境。      &lt;br /&gt;本文将介绍微服务架构的演进、优缺点和微服务应用的设计原则，然后着重介绍作为一个“微服务应用平台”需要提供哪些能力、解决哪些问题才能更好的支撑企业应用架构。      &lt;br /&gt;微服务平台也是我目前正在参与的，还在研发过程中的平台产品，平台是以SpringCloud为基础，结合了普元多年来对企业应用的理解和产品的设计经验，逐步孵化的一个微服务应用平台。&lt;/p&gt;    &lt;h1&gt;一、微服务架构演进过程&lt;/h1&gt;    &lt;p&gt;      &lt;img src="https://images2018.cnblogs.com/blog/668104/201803/668104-20180315151631327-701779978.png"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;p&gt;近年来我们大家都体会到了互联网、移动互联带来的好处，作为IT从业者，在生活中时刻感受互联网好处的同时，在工作中可能感受的却是来自自互联网的一些压力，那就是我们传统企业的IT建设也是迫切需要转型，需要面向外部客户，我们也需要应对外部环境的快速变化、需要快速创新，那么我们的IT架构也需要向互联网企业学习作出相应的改进，来支撑企业的数字化转型。      &lt;br /&gt;我们再看一下应用架构的演进过程，回忆一下微服务架构是如何一步一步进化产生的，最早是应用是单块架构，后来为了具备一定的扩展和可靠性，就有了垂直架构，也就是加了个负载均衡，接下来是前几年比较火的SOA，主要讲了应用系统之间如何集成和互通，而到现在的微服务架构则是进一步在探讨一个应用系统该如何设计才能够更好的开发、管理更加灵活高效。      &lt;br /&gt;微服务架构的基本思想就是“围绕业务领域组件来创建应用，让应用可以独立的开发、管理和加速”。&lt;/p&gt;    &lt;h1&gt;二、微服务架构的好处&lt;/h1&gt;    &lt;p&gt;      &lt;img src="https://images2018.cnblogs.com/blog/668104/201803/668104-20180315151731229-511229297.png"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;p&gt;我们总结了四个方面的优点，分别如下：      &lt;br /&gt;是每个微服务组件都是简单灵活的，能够独立部署。不再像以前一样，应用需要一个庞大的应用服务器来支撑。      &lt;br /&gt;可以由一个小团队负责更专注专业，相应的也就更高效可靠。      &lt;br /&gt;微服务之间是松耦合的，微服务内部是高内聚的，每个微服务很容易按需扩展。      &lt;br /&gt;微服务架构与语言工具无关，自由选择合适的语言和工具，高效的完成业务目标即可。      &lt;br /&gt;看到这里，大家会觉得微服务架构挺不错，然而还会有一些疑问，什么样的应用算是一个微服务架构的应用?该怎样设计一个微服务架构的应用?那我们来一起看看我们推荐的微服务应用的设计原则。&lt;/p&gt;    &lt;h1&gt;三、微服务应用4个设计原则&lt;/h1&gt;    &lt;p&gt;      &lt;img src="https://images2018.cnblogs.com/blog/668104/201803/668104-20180315151754058-317990288.png"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;p&gt;我们总结了四个原则推荐给大家：&lt;/p&gt;    &lt;ul&gt;      &lt;li&gt;AKF拆分原则&lt;/li&gt;      &lt;li&gt;前后端分离&lt;/li&gt;      &lt;li&gt;无状态服务&lt;/li&gt;      &lt;li&gt;Restful通信风格&lt;/li&gt;&lt;/ul&gt;    &lt;h3&gt;1.AKF拆分原则&lt;/h3&gt;    &lt;p&gt;      &lt;img src="https://images2018.cnblogs.com/blog/668104/201803/668104-20180315151827430-1071777557.png"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;p&gt;AKF扩展立方体(参考《The Art of Scalability》)，是一个叫AKF的公司的技术专家抽象总结的应用扩展的三个维度。理论上按照这三个扩展模式，可以将一个单体系统，进行无限扩展。      &lt;br /&gt;X 轴 ：指的是水平复制，很好理解，就是讲单体系统多运行几个实例，做个集群加负载均衡的模式。      &lt;br /&gt;Z 轴 ：是基于类似的数据分区，比如一个互联网打车应用突然或了，用户量激增，集群模式撑不住了，那就按照用户请求的地区进行数据分区，北京、上海、四川等多建几个集群。      &lt;br /&gt;Y 轴 ：就是我们所说的微服务的拆分模式，就是基于不同的业务拆分。      &lt;br /&gt;场景说明：比如打车应用，一个集群撑不住时，分了多个集群，后来用户激增还是不够用，经过分析发现是乘客和车主访问量很大，就将打车应用拆成了三个乘客服务、车主服务、支付服务。三个服务的业务特点各不相同，独立维护，各自都可以再次按需扩展。&lt;/p&gt;    &lt;h3&gt;2.前后端分离&lt;/h3&gt;    &lt;p&gt;      &lt;img src="https://images2018.cnblogs.com/blog/668104/201803/668104-20180315151858421-1058446556.png"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;p&gt;前后端分离原则，简单来讲就是前端和后端的代码分离也就是技术上做分离，我们推荐的模式是最好直接采用物理分离的方式部署，进一步促使进行更彻底的分离。不要继续以前的服务端模板技术，比如JSP ，把Java JS HTML CSS 都堆到一个页面里，稍复杂的页面就无法维护。这种分离模式的方式有几个好处：      &lt;br /&gt;前后端技术分离，可以由各自的专家来对各自的领域进行优化，这样前端的用户体验优化效果会更好。      &lt;br /&gt;分离模式下，前后端交互界面更加清晰，就剩下了接口和模型，后端的接口简洁明了，更容易维护。      &lt;br /&gt;前端多渠道集成场景更容易实现，后端服务无需变更，采用统一的数据和模型，可以支撑前端的web UI 移动App等访问。&lt;/p&gt;    &lt;h3&gt;3.无状态服务&lt;/h3&gt;    &lt;p&gt;      &lt;img src="https://images2018.cnblogs.com/blog/668104/201803/668104-20180315151926422-1614282393.png"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;p&gt;对于无状态服务，首先说一下什么是状态：如果一个数据需要被多个服务共享，才能完成一笔交易，那么这个数据被称为状态。进而依赖这个“状态”数据的服务被称为有状态服务，反之称为无状态服务。      &lt;br /&gt;那么这个无状态服务原则并不是说在微服务架构里就不允许存在状态，表达的真实意思是要把有状态的业务服务改变为无状态的计算类服务，那么状态数据也就相应的迁移到对应的“有状态数据服务”中。      &lt;br /&gt;场景说明：例如我们以前在本地内存中建立的数据缓存、Session缓存，到现在的微服务架构中就应该把这些数据迁移到分布式缓存中存储，让业务服务变成一个无状态的计算节点。迁移后，就可以做到按需动态伸缩，微服务应用在运行时动态增删节点，就不再需要考虑缓存数据如何同步的问题。&lt;/p&gt;    &lt;h3&gt;4.Restful通信风格&lt;/h3&gt;    &lt;p&gt;      &lt;img src="https://images2018.cnblogs.com/blog/668104/201803/668104-20180315151945828-1817852831.png"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;p&gt;作为一个原则来讲本来应该是个“无状态通信原则”，在这里我们直接推荐一个实践优选的Restful 通信风格 ，因为他有很多好处：      &lt;br /&gt;无状态协议HTTP，具备先天优势，扩展能力很强。例如需要安全加密是，有现成的成熟方案HTTPS可用。      &lt;br /&gt;JSON 报文序列化，轻量简单，人与机器均可读，学习成本低，搜索引擎友好。      &lt;br /&gt;语言无关，各大热门语言都提供成熟的Restful API框架，相对其他的一些RPC框架生态更完善。      &lt;br /&gt;当然在有些特殊业务场景下，也需要采用其他的RPC框架，如thrift、avro-rpc、grpc。但绝大多数情况下Restful就足够用了。&lt;/p&gt;    &lt;h1&gt;四、微服务架构带来的问题&lt;/h1&gt;    &lt;p&gt;做到了前面讲的四个原则，那么就可以说是构建了一个微服务应用，感觉上也不复杂。但实际上微服务也不是个万金油，也是有利有弊的，接下来我们来看看引入微服务架构后带来的问题有哪些。&lt;/p&gt;    &lt;p&gt;      &lt;img src="https://images2018.cnblogs.com/blog/668104/201803/668104-20180315152009425-1011999035.png"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;p&gt;依赖服务变更很难跟踪，其他团队的服务接口文档过期怎么办?依赖的服务没有准备好，如何验证我开发的功能。      &lt;br /&gt;部分模块重复构建，跨团队、跨系统、跨语言会有很多的重复建设。      &lt;br /&gt;微服务放大了分布式架构的系列问题，如分布式事务怎么处理?依赖服务不稳定怎么办?      &lt;br /&gt;运维复杂度陡增，如：部署物数量多、监控进程多导致整体运维复杂度提升。      &lt;br /&gt;上面这些问题我们应该都遇到过，并且也会有一些解决方案，比如提供文档管理、服务治理、服务模拟的工具和框架; 实现统一认证、统一配置、统一日志框架、分布式汇总分析; 采用全局事务方案、采用异步模拟同步;搭建持续集成平台、统一监控平台等等。      &lt;br /&gt;这些解决方案折腾到最后终于搞明白了，原来我们是需要一个微服务应用平台才能整体性的解决这些问题。&lt;/p&gt;    &lt;h1&gt;五、微服务平台的19个落地实践&lt;/h1&gt;    &lt;h3&gt;1.企业IT建设的三大基础环境&lt;/h3&gt;    &lt;p&gt;我们先来宏观的看一下，一个企业的IT建设非常重要的三大基础环境：团队协作环境、个人基础环境、IT基础设施。&lt;/p&gt;    &lt;p&gt;      &lt;img src="https://images2018.cnblogs.com/blog/668104/201803/668104-20180315152301224-594538921.png"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;p&gt;团队协作环境：主要是DevOps领域的范畴，负责从需求到计划任务，团队协作，再到质量管理、持续集成和发布。      &lt;br /&gt;个人基础环境：就是本文介绍的微服务应用平台，他的目标主要就是要支撑微服务应用的设计开发测试，运行期的业务数据处理和应用的管理监控。      &lt;br /&gt;IT基础设施：就是我们通常说的各种运行环境支撑如IaaS (VM虚拟化)和CaaS (容器虚拟化)等实现方式。&lt;/p&gt;    &lt;h3&gt;2.微服务应用平台总体架构&lt;/h3&gt;    &lt;p&gt;      &lt;img src="https://images2018.cnblogs.com/blog/668104/201803/668104-20180315152325035-1750901487.png"&gt;&lt;/img&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;li&gt;服务网关： 则是负责与前端的WEB应用 移动APP 等渠道集成，对前端请求进行认真鉴权，然后路由转发。&lt;/li&gt;&lt;/ul&gt;    &lt;h3&gt;3.微服务应用平台的运行视图&lt;/h3&gt;    &lt;p&gt;      &lt;img src="https://images2018.cnblogs.com/blog/668104/201803/668104-20180315152402786-826775022.png"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;p&gt;参考上图，在运行期，作为一个微服务架构的平台与业务系统，除了业务应用本身外，还需要有接入服务、统一门户、基础服务等平台级服务来保障业务系统的可靠运行。图中的公共服务就是业务处理过程中需要用到的一些可选服务。&lt;/p&gt;    &lt;h3&gt;4.微服务平台的设计目标&lt;/h3&gt;    &lt;p&gt;      &lt;img src="https://images2018.cnblogs.com/blog/668104/201803/668104-20180315152426993-530242180.png"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;p&gt;微服务平台的主要目标主要就是要支撑微服务应用的全生命周期管理，从需求到设计开发测试，运行期的业务数据处理和应用的管理监控等，后续将从应用生命周期的几个重要阶段切入，结合前面提到的设计原则和问题，介绍平台提供的能力支撑情况。&lt;/p&gt;    &lt;h3&gt;5.微服务开发：前端、后端、混合&lt;/h3&gt;    &lt;p&gt;      &lt;img src="https://images2018.cnblogs.com/blog/668104/201803/668104-20180315152454866-1748941433.png"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;p&gt;我们一起看一下我们正在开发中的微服务应用平台EOS8.0的一些开发工具截图，了解一下开发期提供了哪些关键的能力支撑。      &lt;br /&gt;前面的设计原则中提到了一个前后端分离的原则，那么我们的开发环境中，目前支持创建前端项目、后端项目和混合项目。其中前端项目、后端项目就对应前后端分离的原则，利用平台中集成的开发工具和框架可以做到前后端开发分离，利用持续集成工具可以方便的将前端、后端项目编译打包成可独立运行的程序。混合项目则是为了兼容传统模式而保留的，为企业应用向微服务架构演进提供过渡方案。&lt;/p&gt;    &lt;h3&gt;6.服务契约与API管理&lt;/h3&gt;    &lt;p&gt;对于前面提到的微服务带来的依赖管理问题，我们可以通过平台提供的API管理能力来解决。说到API管理，那首先就用提到服务契约。平台开发工具中提供了方便的服务发布能力，能够快速的将业务功能对外发布，生成服务的规格契约，当然也可以先设计服务契约，在根据契约来生成服务的默认实现代码。      &lt;br /&gt;这里强调一下，我们提到的服务契约是一个很重要的东西，他有点类似web service的wsdl描述，主要描述服务接口的输入输出规格标准和其他一些服务调用集成相关的规格内容。&lt;/p&gt;    &lt;p&gt;      &lt;img src="https://images2018.cnblogs.com/blog/668104/201803/668104-20180315152524101-2048062730.png"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;h3&gt;7.服务契约与服务模拟&lt;/h3&gt;    &lt;p&gt;有了服务契约，我们就可以根据契约自动生成服务的文档和服务模拟测试环境，这样，开发者就可以方便的获取到依赖服务变更的情况，能够及时的根据依赖服务的变化调整自己的程序，并且能够方便的进行模拟测试验证。&lt;/p&gt;    &lt;p&gt;      &lt;img src="https://images2018.cnblogs.com/blog/668104/201803/668104-20180315152545628-2013424882.png"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;h3&gt;8.服务契约与服务编排&lt;/h3&gt;    &lt;p&gt;      &lt;img src="https://images2018.cnblogs.com/blog/668104/201803/668104-20180315152604193-964552092.png"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;p&gt;有了服务契约，那就有了服务接口的输入输出规格，那么restful的服务编排也就变得可行。在我们设计的契约标准中，还定义了调用集成相关的内容，比如服务支持的事务模式等等。通过这些约定，我们就可以采用简单图形化的方式来对业务服务流程进行编排。编排能够很大程度上简化分布式服务调用的复杂度，如同步、异步、异步模拟同步、超时重试、事务补偿等，均有服务编排引擎完成，不再完全依赖老师傅的编码能力。      &lt;br /&gt;服务编排的作用和意义很大，可以快速的将已经提供的微服务能力进行组合发布，非常适合业务的快速创新。      &lt;br /&gt;但是大家要注意，逻辑流编排的是业务流程，尽量能够简单明了，一眼看上去就明白业务含义。而业务规则推荐采用服务内部进行编码实现。千万不要将我们的 “逻辑流” 图形化服务编排完全取代程序编码，这样就会可能会走入另外一个极端，比如设计出像蜘蛛网一样的逻辑流图，简直就是灾难。&lt;/p&gt;    &lt;h3&gt;9.微服务容器&lt;/h3&gt;    &lt;p&gt;      &lt;img src="https://images2018.cnblogs.com/blog/668104/201803/668104-20180315152624228-705609113.png"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;p&gt;我们再来看一下微服务运行容器的一个逻辑图，大家可以看到，我们要做微服务架构的应用，可靠高效的微服务应用，实际上我们需要做的事情还是非常多的。如果没有一个统一的微服务容器，这些能力在每个微服务组件中都需要建设一遍，而且会五花八门，也很难集成到一起。有了统一的微服务运行容器和一些公共的基础服务，前面所提到的微服务架构下部分组件重复建设的问题也迎刃而解。&lt;/p&gt;    &lt;h3&gt;10.三方能力集成说明&lt;/h3&gt;    &lt;p&gt;我们的API管理契约文档API模拟我们是集成了Swagger的工具链。微服务应用平台的基础就是SpringCloud，从容器框架到注册发现再到安全认证这些基础方案均采用了他的能力来支撑。下面简单看下我们集成的一些开源框架和工具。&lt;/p&gt;    &lt;p&gt;      &lt;img src="https://images2018.cnblogs.com/blog/668104/201803/668104-20180315152655468-707410843.png"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;p&gt;SpringCloud在微服务平台中的定位是基础框架，本文重点是要介绍一个企业级的微服务平台在落地过程中的一些设计原则和解决方案。具体Spring Cloud相关的技术就不在文中多做介绍了，大家可以在我们的公众号里面查看相关文章。&lt;/p&gt;    &lt;h3&gt;11.服务注册发现路由&lt;/h3&gt;    &lt;p&gt;      &lt;img src="https://images2018.cnblogs.com/blog/668104/201803/668104-20180315152722810-2046432283.png"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;p&gt;接下来我们聊一下注册发现，以前的单块应用之间互相调用时配置个IP就行了，但在微服务架构下，服务提供者会有很多，手工配置IP地址又变成了一个不可行的事情。那么服务自动注册发现的方案就解决了这个问题。      &lt;br /&gt;我们的服务注册发现能力是依赖SpringCloud Eureka组件实现的。服务在启动的时候，会将自己要发布的服务注册到服务注册中心，运行时，如果需要调用其他微服务的接口，那么就要先到注册中心获取服务提供者的地址，拿到地址后，通过微服务容器内部的简单负载均衡期进行路由用。      &lt;br /&gt;一般情况，系统内微服务的调用都通过这种客户端负载的模式进行，否则就需要有很多的负载均衡进程。跨业务系统的服务调用，也可以采用这种去中心化的路由方式。当然采用SOA的模式，由中心化的服务网管来管理系统间的调用也是另一种选择，要结合企业的IT现状和需求来决定。&lt;/p&gt;    &lt;h3&gt;12.统一认证鉴权&lt;/h3&gt;    &lt;p&gt;      &lt;img src="https://images2018.cnblogs.com/blog/668104/201803/668104-20180315152743515-1488931341.png"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;p&gt;安全认证方面，我们基于Spring Security结合Auth2再加上JWT(Json web token)做安全令牌，实现统一的安全认证与鉴权，使得微服务之间能够按需隔离和安全互通。后续在统一认证和权限方面我们产品会陆续推出较完善并且扩展性良好的微服务组件，可以作为微服务平台的公共的认证和鉴权服务。再啰嗦一句，认证鉴权一定是个公共的服务，而不是多个系统各自建设。&lt;/p&gt;    &lt;h3&gt;13.日志与流水设计&lt;/h3&gt;    &lt;p&gt;      &lt;img src="https://images2018.cnblogs.com/blog/668104/201803/668104-20180315152805675-2091115381.png"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;p&gt;作为一个微服务应用平台除了提供支撑开发和运行的技术组件和框架之外，我们还提供一些运维友好的经验总结，我们一起来看一下我们推荐的日志与流水实现，先来看日志，平台默认回会提供的日志主要有三种，系统日志，引擎日志还有跟踪日志。有了这些日志，在出问题的时候能够帮助我们获取一些关键信息进行问题定位。      &lt;br /&gt;要想做到出了问题能够追根溯源，那么右边的这些流水号的设计也是非常重要的，日志与各种流水号配合，能够让我们快速定位问题发生的具体时间地点以及相关信息，能够快速还原业务交易全链路。对这些日志与流水的细节处理，对于系统运维问题定位有非常大的帮助，没有这些有用的日志内容，ELK日志收集套件搭建的再漂亮，收一对垃圾日志也是没用的。通常开源框架只是提供个框架有开发人员自由发挥，而设计一个平台则一定要考虑直接提供统一规范的基础能力。&lt;/p&gt;    &lt;h3&gt;14.集中配置管理&lt;/h3&gt;    &lt;p&gt;      &lt;img src="https://images2018.cnblogs.com/blog/668104/201803/668104-20180315152848034-634056357.png"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;p&gt;微服务分布式环境下，一个系统拆分为很多个微服务，一定要告别投产或运维手工修改配置配置的方式。需要采用集中配置管理的方式来提升运维的效率。      &lt;br /&gt;配置文件主要有运行前的静态配置和运行期的动态配置两种。静态配置通常是在编译部署包之前设置好。动态配置则是系统运行过程中需要调整的系统变量或者业务参数。要想做到集中的配置管理，那么需要注意以下几点。      &lt;br /&gt;是配置与介质分离，这个就需要通过制定规范的方式来控制。千万别把配置放在Jar包里。      &lt;br /&gt;是配置的方式要统一，格式、读写方式、变更热更新的模式尽量统一，要采用统一的配置框架      &lt;br /&gt;就是需要运行时需要有个配置中心来统一管理业务系统中的配置信息，这个就需要平台来提供配置中心服务和配置管理门户。&lt;/p&gt;    &lt;h3&gt;15.统一管理门户&lt;/h3&gt;    &lt;p&gt;      &lt;img src="https://images2018.cnblogs.com/blog/668104/201803/668104-20180315152912831-984881454.png"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;p&gt;微服务架构下，一个大的EAR、WAR应用被拆为了多个小的可独立运行的微服务程序，通常这些微服务程序都不再依赖应用服务器，不依赖传统应用服务器的话，应用服务器提供管理控制台也就没得用了，所以微服务的运行时管理需要有统一的管理门户来支撑。我们规划了的统一集中的微服务门户，可以支撑 应用开发、业务处理、应用管理、系统监控等。上图是应用管理页面，就是对我们传统意义上的业务系统进行管理，点击一个业务系统，我们就能够看到系统下有哪些微服务，每个微服务有几个节点实例再运行，可以监控微服务的子节点状态，对微服务进行配置管理和监控。&lt;/p&gt;    &lt;h3&gt;16.分布式事务问题&lt;/h3&gt;    &lt;p&gt;      &lt;img src="https://images2018.cnblogs.com/blog/668104/201803/668104-20180315152942003-610665707.png"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;p&gt;微服务架构的系统下，进程成倍增多，那么也分布式事务一致性的问题也就更加明显。我们这里说的事务一致性，不是传统说的基于数据库实现的技术事务。微服务之间是独立的、调用协议也是无状态的，因此数据库事务方案在一开始就已经不再我们考虑的范围内。我们要解决的是一定时间后的数据达到最终一致状态，准确的说就是采用传统的业务补偿与冲正方式。      &lt;br /&gt;推荐的事务一致性方案有三种：&lt;/p&gt;    &lt;ul&gt;      &lt;li&gt;可靠事件模式：即事件的发送和接收保障高可靠性，来实现事务的一致性。&lt;/li&gt;      &lt;li&gt;补偿模式：Confirm Cancel ，如果确认失败，则全部逆序取消。&lt;/li&gt;      &lt;li&gt;TCC模式：Try Confirm Cancel ，补偿模式的一种特殊实现 通常转账类交易会采用这种模式。&lt;/li&gt;&lt;/ul&gt;    &lt;blockquote&gt;      &lt;p&gt;晓晨的补充：还有 最大努力型、异步确保、2PC、3PC&lt;/p&gt;&lt;/blockquote&gt;    &lt;h3&gt;17.分布式同步调用问题&lt;/h3&gt;    &lt;p&gt;      &lt;img src="https://images2018.cnblogs.com/blog/668104/201803/668104-20180315153110897-1409171058.png"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;p&gt;微服务架构下，相对于传统部署方式，存在更多的分布式调用，那么“如何在不确定的环境中交付确定的服务”，这句话可以简单理解为，我所依赖的服务的可靠性是无法保证的情况下，我如何保证自己能够正常的提供服务，不被我依赖的其他服务拖垮?      &lt;br /&gt;我们推荐SEDA架构来解决这个问题。&lt;/p&gt;    &lt;p&gt;      &lt;img src="https://images2018.cnblogs.com/blog/668104/201803/668104-20180315153128300-1074378849.png"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;p&gt;SEDA : staged event-driven architecture本质上就是采用分布式事件驱动的模式，用异步模拟来同步，无阻塞等待，再加上资源分配隔离结起来的一个解决方案。&lt;/p&gt;    &lt;h3&gt;18.持续集成与持续交付设计&lt;/h3&gt;    &lt;p&gt;      &lt;img src="https://images2018.cnblogs.com/blog/668104/201803/668104-20180315153202906-1206746950.png"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;p&gt;在运维方面，首先我们要解决的就是持续集成和持续交付，而微服务应用平台的职责范围目前规划是只做持续集成，能够方便的用持续集成环境把程序编译成介质包和部署包。(目前规划持续部署由DevOps平台提供相应能力，微服务平台可与DevOps平台集成)      &lt;br /&gt;这里要厘清一个概念：介质，是源码编译后的产物，与环境无关，多环境下应该是可以共用的，如：jar、dockerfile;配置：则是环境相关的信息。配置+介质=部署包。      &lt;br /&gt;获取到部署包之后，微服务应用平台的职责就完成了，接下来就是运维人员各显神通来进行上线部署操作。&lt;/p&gt;    &lt;h3&gt;19.微服务平台与容器云、DevOps的关系&lt;/h3&gt;    &lt;p&gt;      &lt;img src="https://images2018.cnblogs.com/blog/668104/201803/668104-20180315153234320-2136610648.png"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;p&gt;就微服务应用平台本身来说，并不依赖DevOps和容器云，开发好的部署包可以运行在物理机、虚拟机或者是容器中。      &lt;br /&gt;然而当微服务应用平台结合了DevOps和容器云之后，我们就会发现，持续集成和交付变成了一个非常简单便捷并且又可靠的过程。      &lt;br /&gt;简单几步操作，整套开发、测试、预发或者生产环境就能够搭建完成。整个过程的复杂度都由平台给屏蔽掉了，通过三大基础环境的整合，我们能够使分散的微服务组件更简单方便的进行统一管理和运维交付。&lt;/p&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 />
      <guid isPermaLink="true">https://itindex.net/detail/60179-%E5%BE%AE%E6%9C%8D%E5%8A%A1-%E8%AE%BE%E8%AE%A1-%E5%8E%9F%E5%88%99</guid>
      <pubDate>Thu, 28 Nov 2019 09:01:11 CST</pubDate>
    </item>
    <item>
      <title>到底是否应该使用“微服务架构”？ - LinkinStar - 博客园</title>
      <link>https://itindex.net/detail/60168-%E5%BE%AE%E6%9C%8D%E5%8A%A1-%E6%9E%B6%E6%9E%84-linkinstar</link>
      <description>&lt;div&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; &lt;/p&gt;    &lt;h2&gt;什么是“微服务架构”&lt;/h2&gt;    &lt;p&gt;官方：一种将一个单一应用程序开发为一组小型服务的方法，每个服务运行在自己的进程中，服务间通信采用轻量级通信机制。这些服务围绕业务能力构建并且可以独立部署。依赖一个最小型的集中式管理。&lt;/p&gt;    &lt;p&gt;总结为几点：&lt;/p&gt;    &lt;p&gt;1、独立运行&lt;/p&gt;    &lt;p&gt;2、独立部署&lt;/p&gt;    &lt;p&gt;3、独立开发&lt;/p&gt;    &lt;p&gt;4、轻量级通信&lt;/p&gt;    &lt;p&gt;5、集中管理&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;h2&gt;“微服务架构”的优点和缺点&lt;/h2&gt;    &lt;p&gt;如果只是说一个东西的优点是没用的，只有对比来看，所以我们对比单体应用来说明其优点。&lt;/p&gt;    &lt;p&gt;1、部署：&lt;/p&gt;    &lt;p&gt;单体应用部署肯定简单，一个包扔进去，容器启动就可以了。&lt;/p&gt;    &lt;p&gt;微服务应用部署会负责，服务越多部署越麻烦，而且有些依赖与一些中间件，所以运维和部署的压力变大是肯定的。（这里并不是说一定的，已经有一些运维部署的软件方便了微服务的部署）&lt;/p&gt;    &lt;p&gt;2、开发和维护：&lt;/p&gt;    &lt;p&gt;单体应用如果要进行开发，代码即使分离的再好，那么还是在一起，所以会显得臃肿，维护起来不方便，如果需要改动一个点，整个服务必须全部重新启动。&lt;/p&gt;    &lt;p&gt;微服务开发因为本身分离，所以显得清晰，维护起来方便，一个地方的服务出现问题，只需要改动对应服务并重启对应服务即可。&lt;/p&gt;    &lt;p&gt;3、扩展：&lt;/p&gt;    &lt;p&gt;单体应用扩展可想而知，受限并且压力很大，到最后很多人会发现，加入或者扩展功能时宁可新开发一个也不愿意去依赖原来的代码就怕改了原来的代码之前的代码出现问题。&lt;/p&gt;    &lt;p&gt;微服务扩展能力较好，新加入一个功能不会对原来的系统造成影响。而且如果一个大的功能被禁用，直接停止对应服务即可。&lt;/p&gt;    &lt;p&gt;4、通信：&lt;/p&gt;    &lt;p&gt;对于单体应用来说，自己本身都是内部服务调用不存在通信问题，对于外部库来说，通信方式取决于外部库的依赖。&lt;/p&gt;    &lt;p&gt;微服务之间的通信就需要依赖比较靠谱的通信系统了，因为难免服务与服务之间会有依赖，那么通信方式的选择就尤为重要了。&lt;/p&gt;    &lt;p&gt; &lt;/p&gt;    &lt;h2&gt;到底是否应该使用“微服务架构”？&lt;/h2&gt;    &lt;p&gt;最后我们再来看看我们一开始的问题，是不是就能总结出以下几个点了。然后我结合一些书本和经验做下面一个总结，希望对你有帮助。&lt;/p&gt;    &lt;p&gt;1、系统大小&lt;/p&gt;    &lt;p&gt;这是我们首要的考虑目标，如果一个系统很小，比如一个官网，那你说做微服务就是扯淡了。那么如何确定一个系统的大小呢？可以参考一下下面这个标准。&lt;/p&gt;    &lt;p&gt;如果你的项目能分成三个或三个以上的耦合度很低的项目，那么就算大。&lt;/p&gt;    &lt;p&gt;如果你的项目数据库表超过30张，且单表数据轻松百万，那么就算大。&lt;/p&gt;    &lt;p&gt;如果你的项目之后会进行扩展，并且扩展之后会达到上面的如果，那么也算大。&lt;/p&gt;    &lt;p&gt;虽然只是经验上的估计参考，但是也从侧面体现出，如果项目不大，那么真的就没必要。&lt;/p&gt;    &lt;p&gt; &lt;/p&gt;    &lt;p&gt;2、技术能力&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;3、团队规模和时间&lt;/p&gt;    &lt;p&gt;如果你的团队规模不超过10人，那么除非你们能力都非常牛，而且都能独当一面，那么当我没说，理论上不建议。&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;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;参考文档：&lt;/p&gt;    &lt;p&gt;《SpringCloud与Docker微服务架构实战》&lt;/p&gt;    &lt;p&gt;      &lt;a href="http://www.infoq.com/cn/minibooks/microservice--from-zero" target="_blank" title=""&gt;http://www.infoq.com/cn/minibooks/microservice--from-zero&lt;/a&gt;&lt;/p&gt;    &lt;p&gt; &lt;/p&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 />
      <guid isPermaLink="true">https://itindex.net/detail/60168-%E5%BE%AE%E6%9C%8D%E5%8A%A1-%E6%9E%B6%E6%9E%84-linkinstar</guid>
      <pubDate>Sun, 24 Nov 2019 17:38:29 CST</pubDate>
    </item>
    <item>
      <title>微服务SpringCloud之GateWay熔断、限流、重试 - 社会主义接班人 - 博客园</title>
      <link>https://itindex.net/detail/60154-%E5%BE%AE%E6%9C%8D%E5%8A%A1-springcloud-gateway</link>
      <description>&lt;div&gt;    &lt;p&gt;纯洁的微笑的Spring Cloud系列博客终于学完了，也对Spring Cloud有了初步的了解。&lt;/p&gt;    &lt;p&gt;      &lt;strong&gt;修改请求路径的过滤器&lt;/strong&gt;&lt;/p&gt;    &lt;p&gt;StripPrefix Filter 是一个请求路径截取的功能，我们可以利用这个功能来做特殊业务的转发。&lt;/p&gt;    &lt;div&gt;      &lt;pre&gt;- id: StripPrefix
         uri: http://www.cnblogs.com
         predicates:
           - Path=/name/**
         filters:
           - StripPrefix=2&lt;/pre&gt;&lt;/div&gt;    &lt;p&gt;StripPrefix是当请求路径匹配到/name/**会将包含name和后边的字符串接去掉转发， StripPrefix=2就代表截取路径的个数，当访问      &lt;a href="http://localhost:8081/name/aa/5ishare"&gt;http://localhost:8081/name/aa/5ishare&lt;/a&gt;时会跳转到      &lt;a href="https://www.cnblogs.com/5ishare"&gt;https://www.cnblogs.com/5ishare&lt;/a&gt;页面。&lt;/p&gt;    &lt;p&gt; PrefixPath Filter 的作用和 StripPrefix 正相反，是在 URL 路径前面添加一部分的前缀。&lt;/p&gt;    &lt;div&gt;      &lt;pre&gt;- id: prefixpath_route
         uri: http://www.cnblogs.com
         predicates:
            - Method=GET
         filters:
            - PrefixPath=/5ishare&lt;/pre&gt;&lt;/div&gt;    &lt;p&gt;在浏览器输入      &lt;a href="http://localhost:8081/p/11831586.html"&gt;http://localhost:8081/p/11831586.html&lt;/a&gt; 时页面会跳转到       &lt;a href="https://www.cnblogs.com/5ishare/p/11831586.html"&gt;https://www.cnblogs.com/5ishare/p/11831586.html&lt;/a&gt;。&lt;/p&gt;    &lt;p&gt;      &lt;strong&gt; 限速路由器&lt;/strong&gt;&lt;/p&gt;    &lt;p&gt;限速在高并发场景中比较常用的手段之一，可以有效的保障服务的整体稳定性，Spring Cloud Gateway 提供了基于 Redis 的限流方案。所以我们首先需要添加对应的依赖包spring-boot-starter-data-redis-reactive。&lt;/p&gt;    &lt;div&gt;      &lt;pre&gt;&amp;lt;dependency&amp;gt;&amp;lt;groupId&amp;gt;org.springframework.boot&amp;lt;/groupId&amp;gt;&amp;lt;artifactId&amp;gt;spring-boot-starter-data-redis-reactive&amp;lt;/artifactId&amp;gt;&amp;lt;version&amp;gt;2.0.4.RELEASE&amp;lt;/version&amp;gt;&amp;lt;/dependency&amp;gt;&lt;/pre&gt;&lt;/div&gt;    &lt;p&gt;配置文件中需要添加 Redis 地址和限流的相关配置&lt;/p&gt;    &lt;div&gt;      &lt;img alt="" src="https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif"&gt;&lt;/img&gt;      &lt;img alt="" src="https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif"&gt;&lt;/img&gt;      &lt;div&gt;        &lt;pre&gt;server:
  port: 8081
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8088/eureka/
logging:
  level:
    org.springframework.cloud.gateway: debug
spring:
  application:
    name: SpringCloudGatewayDemo
  redis:
    host: localhost
    password:
    port: 6379
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true
      routes:
       - id: requestratelimiter_route
         uri: http://example.org
         filters:
          - name: RequestRateLimiter
            args:
             redis-rate-limiter.replenishRate: 10
             redis-rate-limiter.burstCapacity: 20
              key-resolver:&amp;quot;#{@userKeyResolver}&amp;quot;
         predicates:
           - Method=GET&lt;/pre&gt;&lt;/div&gt;View Code&lt;/div&gt;    &lt;p&gt;filter 名称必须是 RequestRateLimiter      &lt;br /&gt;redis-rate-limiter.replenishRate：允许用户每秒处理多少个请求      &lt;br /&gt;redis-rate-limiter.burstCapacity：令牌桶的容量，允许在一秒钟内完成的最大请求数      &lt;br /&gt;key-resolver：使用 SpEL 按名称引用 bean&lt;/p&gt;    &lt;p&gt;项目中设置限流的策略，创建 Config 类。根据请求参数中的 user 字段来限流，也可以设置根据请求 IP 地址来限流，设置如下:&lt;/p&gt;    &lt;div&gt;      &lt;img alt="" src="https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif"&gt;&lt;/img&gt;      &lt;img alt="" src="https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif"&gt;&lt;/img&gt;      &lt;div&gt;        &lt;pre&gt;packagecom.example.demo;importorg.springframework.cloud.gateway.filter.ratelimit.KeyResolver;importorg.springframework.context.annotation.Bean;importreactor.core.publisher.Mono;publicclassConfig {
    @BeanpublicKeyResolver ipKeyResolver() {returnexchange -&amp;gt;Mono.just(exchange.getRequest().getRemoteAddress().getHostName());
    }


    @Bean
    KeyResolver userKeyResolver() {returnexchange -&amp;gt; Mono.just(exchange.getRequest().getQueryParams().getFirst(&amp;quot;user&amp;quot;));
    }
}&lt;/pre&gt;&lt;/div&gt;View Code&lt;/div&gt;    &lt;p&gt;      &lt;strong&gt;熔断路由器&lt;/strong&gt;&lt;/p&gt;    &lt;p&gt;Spring Cloud Gateway 也可以利用 Hystrix 的熔断特性，在流量过大时进行服务降级，同样我们还是首先给项目添加上依赖。&lt;/p&gt;    &lt;div&gt;      &lt;pre&gt;&amp;lt;dependency&amp;gt;&amp;lt;groupId&amp;gt;org.springframework.cloud&amp;lt;/groupId&amp;gt;&amp;lt;artifactId&amp;gt;spring-cloud-starter-netflix-hystrix&amp;lt;/artifactId&amp;gt;&amp;lt;version&amp;gt;2.1.3.RELEASE&amp;lt;/version&amp;gt;&amp;lt;/dependency&amp;gt;&lt;/pre&gt;&lt;/div&gt;    &lt;div&gt;      &lt;pre&gt;- id: hystrix_route
         uri: lb://spring-cloud-producer
         predicates:
           - Path=/consumingserviceendpoint
         filters:
           - name: Hystrix
             args:
              name: fallbackcmd
              fallbackUri: forward:/incaseoffailureusethis&lt;/pre&gt;&lt;/div&gt;    &lt;p&gt;fallbackUri: forward:/incaseoffailureusethis配置了 fallback 时要会调的路径，当调用 Hystrix 的 fallback 被调用时，请求将转发到/incaseoffailureuset这个 URI。&lt;/p&gt;    &lt;p&gt;      &lt;strong&gt;重试路由器&lt;/strong&gt;&lt;/p&gt;    &lt;p&gt;RetryGatewayFilter 是 Spring Cloud Gateway 对请求重试提供的一个 GatewayFilter Factory&lt;/p&gt;    &lt;div&gt;      &lt;pre&gt;- id: retry_test
         uri: lb://spring-cloud-producer
         predicates:
           - Path=/retry
         filters:
           - name: Retry
             args:
              retries: 3
              statuses: BAD_GATEWAY&lt;/pre&gt;&lt;/div&gt;    &lt;p&gt;retries：重试次数，默认值是 3 次      &lt;br /&gt;statuses：HTTP 的状态返回码，取值请参考：org.springframework.http.HttpStatus      &lt;br /&gt;methods：指定哪些方法的请求需要进行重试逻辑，默认值是 GET 方法，取值参考：org.springframework.http.HttpMethod      &lt;br /&gt;series：一些列的状态码配置，取值参考：org.springframework.http.HttpStatus.Series。符合的某段状态码才会进行重试逻辑，默认值是 SERVER_ERROR，值是 5，也就是 5XX(5 开头的状态码)，共有5 个值。&lt;/p&gt;    &lt;p&gt; &lt;/p&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 />
      <guid isPermaLink="true">https://itindex.net/detail/60154-%E5%BE%AE%E6%9C%8D%E5%8A%A1-springcloud-gateway</guid>
      <pubDate>Mon, 18 Nov 2019 07:58:11 CST</pubDate>
    </item>
    <item>
      <title>Spring Cloud Alibaba | 微服务分布式事务之Seata - 极客挖掘机 - 博客园</title>
      <link>https://itindex.net/detail/60025-spring-cloud-alibaba</link>
      <description>&lt;div&gt;    &lt;h1&gt;Spring Cloud Alibaba | 微服务分布式事务之Seata&lt;/h1&gt;    &lt;blockquote&gt;      &lt;p&gt;本篇实战所使用Spring有关版本：&lt;/p&gt;      &lt;p&gt;SpringBoot:2.1.7.RELEASE&lt;/p&gt;      &lt;p&gt;Spring Cloud:Greenwich.SR2&lt;/p&gt;      &lt;p&gt;Spring CLoud Alibaba:2.1.0.RELEASE&lt;/p&gt;&lt;/blockquote&gt;    &lt;h2&gt;1. 概述&lt;/h2&gt;    &lt;p&gt;在构建微服务的过程中，不管是使用什么框架、组件来构建，都绕不开一个问题，跨服务的业务操作如何保持数据一致性。&lt;/p&gt;    &lt;h2&gt;2. 什么是分布式事务？&lt;/h2&gt;    &lt;p&gt;首先，设想一个传统的单体应用，无论多少内部调用，最后终归是在同一个数据库上进行操作来完成一向业务操作，如图：&lt;/p&gt;    &lt;p&gt;      &lt;img src="https://img2018.cnblogs.com/blog/908359/201909/908359-20190911083359133-818142336.png"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;p&gt;随着业务量的发展，业务需求和架构发生了巨大的变化，整体架构由原来的单体应用逐渐拆分成为了微服务，原来的3个服务被从一个单体架构上拆开了，成为了3个独立的服务，分别使用独立的数据源，也不在之前共享同一个数据源了，具体的业务将由三个服务的调用来完成，如图：&lt;/p&gt;    &lt;p&gt;      &lt;img src="https://img2018.cnblogs.com/blog/908359/201909/908359-20190911083359484-1763272904.png"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;p&gt;此时，每一个服务的内部数据一致性仍然有本地事务来保证。但是面对整个业务流程上的事务应该如何保证呢？这就是在微服务架构下面临的挑战，如何保证在微服务中的数据一致性。&lt;/p&gt;    &lt;h2&gt;3. 常见的分布式事务解决方案&lt;/h2&gt;    &lt;h3&gt;3.1 两阶段提交方案/XA方案&lt;/h3&gt;    &lt;p&gt;所谓的 XA 方案，即两阶段提交，有一个事务管理器的概念，负责协调多个数据库（资源管理器）的事务，事务管理器先问问各个数据库你准备好了吗？如果每个数据库都回复 ok，那么就正式提交事务，在各个数据库上执行操作；如果任何其中一个数据库回答不 ok，那么就回滚事务。&lt;/p&gt;    &lt;p&gt;分布式系统的一个难点是如何保证架构下多个节点在进行事务性操作的时候保持一致性。为实现这个目的，二阶段提交算法的成立基于以下假设：&lt;/p&gt;    &lt;ul&gt;      &lt;li&gt;        &lt;p&gt;该分布式系统中，存在一个节点作为协调者(Coordinator)，其他节点作为参与者(Cohorts)。且节点之间可以进行网络通信。&lt;/p&gt;&lt;/li&gt;      &lt;li&gt;        &lt;p&gt;所有节点都采用预写式日志，且日志被写入后即被保持在可靠的存储设备上，即使节点损坏不会导致日志数据的消失。&lt;/p&gt;&lt;/li&gt;      &lt;li&gt;        &lt;p&gt;所有节点不会永久性损坏，即使损坏后仍然可以恢复。&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;    &lt;p&gt;      &lt;img src="https://img2018.cnblogs.com/blog/908359/201909/908359-20190911083359767-2126039707.jpg"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;p&gt;      &lt;img src="https://img2018.cnblogs.com/blog/908359/201909/908359-20190911083400246-1474521639.jpg"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;h3&gt;3.2 TCC 方案&lt;/h3&gt;    &lt;p&gt;TCC的全称是：Try、Confirm、Cancel。&lt;/p&gt;    &lt;ul&gt;      &lt;li&gt;Try 阶段：这个阶段说的是对各个服务的资源做检测以及对资源进行锁定或者预留。&lt;/li&gt;      &lt;li&gt;Confirm 阶段：这个阶段说的是在各个服务中执行实际的操作。&lt;/li&gt;      &lt;li&gt;Cancel 阶段：如果任何一个服务的业务方法执行出错，那么这里就需要进行补偿，就是执行已经执行成功的业务逻辑的回滚操作。（把那些执行成功的回滚）&lt;/li&gt;&lt;/ul&gt;    &lt;p&gt;这种方案说实话几乎很少人使用，但是也有使用的场景。因为这个事务回滚实际上是严重依赖于你自己写代码来回滚和补偿了，会造成补偿代码巨大。&lt;/p&gt;    &lt;p&gt;TCC的理论有点抽象，下面我们借助一个账务拆分这个实际业务场景对TCC事务的流程做一个描述，希望对理解TCC有所帮助。&lt;/p&gt;    &lt;p&gt;业务流程：分别位于三个不同分库的帐户A、B、C，A和B一起向C转帐共80元：&lt;/p&gt;    &lt;p&gt;Try：尝试执行业务。&lt;/p&gt;    &lt;p&gt;完成所有业务检查(一致性)：检查A、B、C的帐户状态是否正常，帐户A的余额是否不少于30元，帐户B的余额是否不少于50元。&lt;/p&gt;    &lt;p&gt;预留必须业务资源(准隔离性)：帐户A的冻结金额增加30元，帐户B的冻结金额增加50元，这样就保证不会出现其他并发进程扣减了这两个帐户的余额而导致在后续的真正转帐操作过程中，帐户A和B的可用余额不够的情况。&lt;/p&gt;    &lt;p&gt;Confirm：确认执行业务。&lt;/p&gt;    &lt;p&gt;真正执行业务：如果Try阶段帐户A、B、C状态正常，且帐户A、B余额够用，则执行帐户A给账户C转账30元、帐户B给账户C转账50元的转帐操作。&lt;/p&gt;    &lt;p&gt;不做任何业务检查：这时已经不需要做业务检查，Try阶段已经完成了业务检查。&lt;/p&gt;    &lt;p&gt;只使用Try阶段预留的业务资源：只需要使用Try阶段帐户A和帐户B冻结的金额即可。&lt;/p&gt;    &lt;p&gt;Cancel：取消执行业务。&lt;/p&gt;    &lt;p&gt;释放Try阶段预留的业务资源：如果Try阶段部分成功，比如帐户A的余额够用，且冻结相应金额成功，帐户B的余额不够而冻结失败，则需要对帐户A做Cancel操作，将帐户A被冻结的金额解冻掉。&lt;/p&gt;    &lt;h2&gt;4. Spring Cloud Alibaba Seata&lt;/h2&gt;    &lt;p&gt;Seata 的方案其实一个 XA 两阶段提交的改进版，具体区别如下：&lt;/p&gt;    &lt;p&gt;      &lt;strong&gt;架构的层面&lt;/strong&gt;：&lt;/p&gt;    &lt;p&gt;      &lt;img src="https://img2018.cnblogs.com/blog/908359/201909/908359-20190911083400403-33335338.png"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;p&gt;XA 方案的 RM 实际上是在数据库层，RM 本质上就是数据库自身（通过提供支持 XA 的驱动程序来供应用使用）。&lt;/p&gt;    &lt;p&gt;而 Seata 的 RM 是以二方包的形式作为中间件层部署在应用程序这一侧的，不依赖与数据库本身对协议的支持，当然也不需要数据库支持 XA 协议。这点对于微服务化的架构来说是非常重要的：应用层不需要为本地事务和分布式事务两类不同场景来适配两套不同的数据库驱动。&lt;/p&gt;    &lt;p&gt;这个设计，剥离了分布式事务方案对数据库在 协议支持 上的要求。&lt;/p&gt;    &lt;p&gt;      &lt;strong&gt;两阶段提交&lt;/strong&gt;：&lt;/p&gt;    &lt;p&gt;      &lt;img src="https://img2018.cnblogs.com/blog/908359/201909/908359-20190911083400605-546849836.png"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;p&gt;无论 Phase2 的决议是 commit 还是 rollback，事务性资源的锁都要保持到 Phase2 完成才释放。&lt;/p&gt;    &lt;p&gt;设想一个正常运行的业务，大概率是 90% 以上的事务最终应该是成功提交的，我们是否可以在 Phase1 就将本地事务提交呢？这样 90% 以上的情况下，可以省去 Phase2 持锁的时间，整体提高效率。&lt;/p&gt;    &lt;p&gt;      &lt;img src="https://img2018.cnblogs.com/blog/908359/201909/908359-20190911083400739-1619917819.png"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;ul&gt;      &lt;li&gt;分支事务中数据的 本地锁 由本地事务管理，在分支事务 Phase1 结束时释放。&lt;/li&gt;      &lt;li&gt;同时，随着本地事务结束，连接 也得以释放。&lt;/li&gt;      &lt;li&gt;分支事务中数据的 全局锁 在事务协调器侧管理，在决议 Phase2 全局提交时，全局锁马上可以释放。只有在决议全局回滚的情况下，全局锁 才被持有至分支的 Phase2 结束。&lt;/li&gt;&lt;/ul&gt;    &lt;p&gt;这个设计，极大地减少了分支事务对资源（数据和连接）的锁定时间，给整体并发和吞吐的提升提供了基础。&lt;/p&gt;    &lt;h2&gt;5. Seata实战案例&lt;/h2&gt;    &lt;h3&gt;5.1 目标介绍&lt;/h3&gt;    &lt;p&gt;在本节，我们将通过一个实战案例来具体介绍Seata的使用方式，我们将模拟一个简单的用户购买商品下单场景，创建3个子工程，分别是 order-server （下单服务）、storage-server（库存服务）和 pay-server （支付服务），具体流程图如图：&lt;/p&gt;    &lt;p&gt;      &lt;img src="https://img2018.cnblogs.com/blog/908359/201909/908359-20190911083400943-1606086432.png"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;h3&gt;5.2 环境准备&lt;/h3&gt;    &lt;p&gt;在本次实战中，我们使用Nacos做为服务中心和配置中心，Nacos部署请参考本书的第十一章，这里不再赘述。&lt;/p&gt;    &lt;p&gt;接下来我们需要部署Seata的Server端，下载地址为：https://github.com/seata/seata/releases ，建议选择最新版本下载，目前笔者看到的最新版本为 v0.8.0 ，下载 seata-server-0.8.0.tar.gz 解压后，打开 conf 文件夹，我们需对其中的一些配置做出修改。&lt;/p&gt;    &lt;h4&gt;5.2.1 registry.conf 文件修改，如下：&lt;/h4&gt;    &lt;pre&gt;      &lt;code&gt;registry {
    type = &amp;quot;nacos&amp;quot;
    nacos {
    serverAddr = &amp;quot;192.168.0.128&amp;quot;
    namespace = &amp;quot;public&amp;quot;
    cluster = &amp;quot;default&amp;quot;
    }
}

config {
    type = &amp;quot;nacos&amp;quot;
    nacos {
    serverAddr = &amp;quot;192.168.0.128&amp;quot;
    namespace = &amp;quot;public&amp;quot;
    cluster = &amp;quot;default&amp;quot;
    }
}&lt;/code&gt;&lt;/pre&gt;    &lt;p&gt;这里我们选择使用Nacos作为服务中心和配置中心，这里做出对应的配置，同时可以看到Seata的注册服务支持：file 、nacos 、eureka、redis、zk、consul、etcd3、sofa等方式，配置支持：file、nacos 、apollo、zk、consul、etcd3等方式。&lt;/p&gt;    &lt;h4&gt;5.2.2 file.conf 文件修改&lt;/h4&gt;    &lt;p&gt;这里我们需要其中配置的数据库相关配置，具体如下：&lt;/p&gt;    &lt;pre&gt;      &lt;code&gt;## database store
db {
    ## the implement of javax.sql.DataSource, such as DruidDataSource(druid)/BasicDataSource(dbcp) etc.
    datasource = &amp;quot;dbcp&amp;quot;
    ## mysql/oracle/h2/oceanbase etc.
    db-type = &amp;quot;mysql&amp;quot;
    driver-class-name = &amp;quot;com.mysql.jdbc.Driver&amp;quot;
    url = &amp;quot;jdbc:mysql://192.168.0.128:3306/seata&amp;quot;
    user = &amp;quot;root&amp;quot;
    password = &amp;quot;123456&amp;quot;
    min-conn = 1
    max-conn = 3
    global.table = &amp;quot;global_table&amp;quot;
    branch.table = &amp;quot;branch_table&amp;quot;
    lock-table = &amp;quot;lock_table&amp;quot;
    query-limit = 100
}&lt;/code&gt;&lt;/pre&gt;    &lt;p&gt;这里数据库默认是使用mysql，需要配置对应的数据库连接、用户名和密码等。&lt;/p&gt;    &lt;h4&gt;5.2.3 nacos-config.txt 文件修改，具体如下：&lt;/h4&gt;    &lt;pre&gt;      &lt;code&gt;service.vgroup_mapping.spring-cloud-pay-server=default
service.vgroup_mapping.spring-cloud-order-server=default
service.vgroup_mapping.spring-cloud-storage-server=default&lt;/code&gt;&lt;/pre&gt;    &lt;p&gt;这里的语法为：      &lt;code&gt;service.vgroup_mapping.${your-service-gruop}=default&lt;/code&gt;，中间的      &lt;code&gt;${your-service-gruop}&lt;/code&gt;为自己定义的服务组名称，这里需要我们在程序的配置文件中配置，笔者这里直接使用程序的      &lt;code&gt;spring.application.name&lt;/code&gt;。&lt;/p&gt;    &lt;h4&gt;5.2.4 数据库初始化&lt;/h4&gt;    &lt;p&gt;需要在刚才配置的数据库中执行数据初始脚本 db_store.sql ，这个是全局事务控制的表，需要提前初始化。&lt;/p&gt;    &lt;p&gt;这里我们只是做演示，理论上上面三个业务服务应该分属不同的数据库，这里我们只是在同一台数据库下面创建三个 Schema ，分别为 db_account 、 db_order 和 db_storage ，具体如图：&lt;/p&gt;    &lt;p&gt;      &lt;img src="https://img2018.cnblogs.com/blog/908359/201909/908359-20190911083401231-2121501463.png"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;h4&gt;5.2.5 服务启动&lt;/h4&gt;    &lt;p&gt;因为我们是使用的Nacos作为配置中心，所以这里需要先执行脚本来初始化Nacos的相关配置，命令如下：&lt;/p&gt;    &lt;pre&gt;      &lt;code&gt;cd conf
sh nacos-config.sh 192.168.0.128&lt;/code&gt;&lt;/pre&gt;    &lt;p&gt;执行成功后可以打开Nacos的控制台，在配置列表中，可以看到初始化了很多 Group 为 SEATA_GROUP 的配置，如图：&lt;/p&gt;    &lt;p&gt;      &lt;img src="https://img2018.cnblogs.com/blog/908359/201909/908359-20190911083401419-5130321.png"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;p&gt;初始化成功后，可以使用下面的命令启动Seata的Server端：&lt;/p&gt;    &lt;pre&gt;      &lt;code&gt;cd bin
sh seata-server.sh -p 8091 -m file&lt;/code&gt;&lt;/pre&gt;    &lt;p&gt;启动后在 Nacos 的服务列表下面可以看到一个名为 serverAddr 的服务&lt;/p&gt;    &lt;p&gt;到这里，我们的环境准备工作就做完了，接下来开始代码实战。&lt;/p&gt;    &lt;h4&gt;5.3 代码实战&lt;/h4&gt;    &lt;p&gt;由于本示例代码偏多，这里仅介绍核心代码和一些需要注意的代码，其余代码各位读者可以访问本书配套的代码仓库获取。&lt;/p&gt;    &lt;p&gt;子工程common用来放置一些公共类，主要包含视图 VO 类和响应类 OperationResponse.java。&lt;/p&gt;    &lt;h4&gt;5.3.1 父工程 seata-nacos-jpa 依赖 pom.xml 文件&lt;/h4&gt;    &lt;p&gt;代码清单：Alibaba/seata-nacos-jpa/pom.xml&lt;/p&gt;    &lt;hr&gt;&lt;/hr&gt;    &lt;pre&gt;      &lt;code&gt;&amp;lt;dependencies&amp;gt;
    &amp;lt;dependency&amp;gt;
        &amp;lt;groupId&amp;gt;org.springframework.boot&amp;lt;/groupId&amp;gt;
        &amp;lt;artifactId&amp;gt;spring-boot-starter-actuator&amp;lt;/artifactId&amp;gt;
    &amp;lt;/dependency&amp;gt;
    &amp;lt;dependency&amp;gt;
        &amp;lt;groupId&amp;gt;org.springframework.boot&amp;lt;/groupId&amp;gt;
        &amp;lt;artifactId&amp;gt;spring-boot-starter-data-jpa&amp;lt;/artifactId&amp;gt;
    &amp;lt;/dependency&amp;gt;
    &amp;lt;dependency&amp;gt;
        &amp;lt;groupId&amp;gt;org.springframework.boot&amp;lt;/groupId&amp;gt;
        &amp;lt;artifactId&amp;gt;spring-boot-starter-web&amp;lt;/artifactId&amp;gt;
    &amp;lt;/dependency&amp;gt;

    &amp;lt;!-- Spring Cloud Nacos Service Discovery --&amp;gt;
    &amp;lt;dependency&amp;gt;
        &amp;lt;groupId&amp;gt;com.alibaba.cloud&amp;lt;/groupId&amp;gt;
        &amp;lt;artifactId&amp;gt;spring-cloud-starter-alibaba-nacos-discovery&amp;lt;/artifactId&amp;gt;
    &amp;lt;/dependency&amp;gt;
    &amp;lt;!-- Spring Cloud Nacos Config --&amp;gt;
    &amp;lt;dependency&amp;gt;
        &amp;lt;groupId&amp;gt;com.alibaba.cloud&amp;lt;/groupId&amp;gt;
        &amp;lt;artifactId&amp;gt;spring-cloud-starter-alibaba-nacos-config&amp;lt;/artifactId&amp;gt;
    &amp;lt;/dependency&amp;gt;
    &amp;lt;!-- Spring Cloud Seata --&amp;gt;
    &amp;lt;dependency&amp;gt;
        &amp;lt;groupId&amp;gt;com.alibaba.cloud&amp;lt;/groupId&amp;gt;
        &amp;lt;artifactId&amp;gt;spring-cloud-starter-alibaba-seata&amp;lt;/artifactId&amp;gt;
    &amp;lt;/dependency&amp;gt;

    &amp;lt;dependency&amp;gt;
        &amp;lt;groupId&amp;gt;mysql&amp;lt;/groupId&amp;gt;
        &amp;lt;artifactId&amp;gt;mysql-connector-java&amp;lt;/artifactId&amp;gt;
        &amp;lt;scope&amp;gt;runtime&amp;lt;/scope&amp;gt;
    &amp;lt;/dependency&amp;gt;
    &amp;lt;dependency&amp;gt;
        &amp;lt;groupId&amp;gt;org.projectlombok&amp;lt;/groupId&amp;gt;
        &amp;lt;artifactId&amp;gt;lombok&amp;lt;/artifactId&amp;gt;
        &amp;lt;optional&amp;gt;true&amp;lt;/optional&amp;gt;
    &amp;lt;/dependency&amp;gt;
    &amp;lt;dependency&amp;gt;
        &amp;lt;groupId&amp;gt;org.springframework.boot&amp;lt;/groupId&amp;gt;
        &amp;lt;artifactId&amp;gt;spring-boot-starter-test&amp;lt;/artifactId&amp;gt;
        &amp;lt;scope&amp;gt;test&amp;lt;/scope&amp;gt;
    &amp;lt;/dependency&amp;gt;
&amp;lt;/dependencies&amp;gt;

&amp;lt;dependencyManagement&amp;gt;
    &amp;lt;dependencies&amp;gt;
        &amp;lt;dependency&amp;gt;
            &amp;lt;groupId&amp;gt;org.springframework.cloud&amp;lt;/groupId&amp;gt;
            &amp;lt;artifactId&amp;gt;spring-cloud-dependencies&amp;lt;/artifactId&amp;gt;
            &amp;lt;version&amp;gt;${spring-cloud.version}&amp;lt;/version&amp;gt;
            &amp;lt;type&amp;gt;pom&amp;lt;/type&amp;gt;
            &amp;lt;scope&amp;gt;import&amp;lt;/scope&amp;gt;
        &amp;lt;/dependency&amp;gt;
        &amp;lt;dependency&amp;gt;
            &amp;lt;groupId&amp;gt;com.alibaba.cloud&amp;lt;/groupId&amp;gt;
            &amp;lt;artifactId&amp;gt;spring-cloud-alibaba-dependencies&amp;lt;/artifactId&amp;gt;
            &amp;lt;version&amp;gt;${spring-cloud-alibaba.version}&amp;lt;/version&amp;gt;
            &amp;lt;type&amp;gt;pom&amp;lt;/type&amp;gt;
            &amp;lt;scope&amp;gt;import&amp;lt;/scope&amp;gt;
        &amp;lt;/dependency&amp;gt;
    &amp;lt;/dependencies&amp;gt;
&amp;lt;/dependencyManagement&amp;gt;&lt;/code&gt;&lt;/pre&gt;    &lt;p&gt;说明：本示例是使用 JPA 作为数据库访问 ORM 层， Mysql 作为数据库，需引入 JPA 和 Mysql 相关依赖，      &lt;code&gt;spring-cloud-alibaba-dependencies&lt;/code&gt;的版本是 2.1.0.RELEASE ， 其中有关Seata的组件版本为 v0.7.1 ，虽然和服务端版本不符，经简单测试，未发现问题。&lt;/p&gt;    &lt;h4&gt;5.3.2 数据源配置&lt;/h4&gt;    &lt;p&gt;Seata 是通过代理数据源实现事务分支，所以需要配置 io.seata.rm.datasource.DataSourceProxy 的 Bean，且是 @Primary默认的数据源，否则事务不会回滚，无法实现分布式事务，数据源配置类DataSourceProxyConfig.java如下：&lt;/p&gt;    &lt;p&gt;代码清单：Alibaba/seata-nacos-jpa/order-server/src/main/java/com/springcloud/orderserver/config/DataSourceProxyConfig.java      &lt;br /&gt;***&lt;/p&gt;    &lt;pre&gt;      &lt;code&gt;@Configuration
public class DataSourceProxyConfig {
    @Bean
    @ConfigurationProperties(prefix = &amp;quot;spring.datasource&amp;quot;)
    public DruidDataSource druidDataSource() {
        return new DruidDataSource();
    }

    @Primary
    @Bean
    public DataSourceProxy dataSource(DruidDataSource druidDataSource) {
        return new DataSourceProxy(druidDataSource);
    }
}&lt;/code&gt;&lt;/pre&gt;    &lt;h4&gt;5.3.3 开启全局事务&lt;/h4&gt;    &lt;p&gt;我们在order-server服务中开始整个业务流程，需要在这里的方法上增加全局事务的注解      &lt;code&gt;@GlobalTransactional&lt;/code&gt;，具体代码如下：&lt;/p&gt;    &lt;p&gt;代码清单：Alibaba/seata-nacos-jpa/order-server/src/main/java/com/springcloud/orderserver/service/impl/OrderServiceImpl.java      &lt;br /&gt;***&lt;/p&gt;    &lt;pre&gt;      &lt;code&gt;@Service
@Slf4j
public class OrderServiceImpl implements OrderService {

    @Autowired
    private RestTemplate restTemplate;

    @Autowired
    private OrderDao orderDao;

    private final String STORAGE_SERVICE_HOST = &amp;quot;http://spring-cloud-storage-server/storage&amp;quot;;
    private final String PAY_SERVICE_HOST = &amp;quot;http://spring-cloud-pay-server/pay&amp;quot;;

    @Override
    @GlobalTransactional
    public OperationResponse placeOrder(PlaceOrderRequestVO placeOrderRequestVO) {
        Integer amount = 1;
        Integer price = placeOrderRequestVO.getPrice();

        Order order = Order.builder()
                .userId(placeOrderRequestVO.getUserId())
                .productId(placeOrderRequestVO.getProductId())
                .status(OrderStatus.INIT)
                .payAmount(price)
                .build();

        order = orderDao.save(order);

        log.info(&amp;quot;保存订单{}&amp;quot;, order.getId() != null ? &amp;quot;成功&amp;quot; : &amp;quot;失败&amp;quot;);
        log.info(&amp;quot;当前 XID: {}&amp;quot;, RootContext.getXID());

        // 扣减库存
        log.info(&amp;quot;开始扣减库存&amp;quot;);
        ReduceStockRequestVO reduceStockRequestVO = ReduceStockRequestVO.builder()
                .productId(placeOrderRequestVO.getProductId())
                .amount(amount)
                .build();
        String storageReduceUrl = String.format(&amp;quot;%s/reduceStock&amp;quot;, STORAGE_SERVICE_HOST);
        OperationResponse storageOperationResponse = restTemplate.postForObject(storageReduceUrl, reduceStockRequestVO, OperationResponse.class);
        log.info(&amp;quot;扣减库存结果:{}&amp;quot;, storageOperationResponse);

        // 扣减余额
        log.info(&amp;quot;开始扣减余额&amp;quot;);
        ReduceBalanceRequestVO reduceBalanceRequestVO = ReduceBalanceRequestVO.builder()
                .userId(placeOrderRequestVO.getUserId())
                .price(price)
                .build();

        String reduceBalanceUrl = String.format(&amp;quot;%s/reduceBalance&amp;quot;, PAY_SERVICE_HOST);
        OperationResponse balanceOperationResponse = restTemplate.postForObject(reduceBalanceUrl, reduceBalanceRequestVO, OperationResponse.class);
        log.info(&amp;quot;扣减余额结果:{}&amp;quot;, balanceOperationResponse);

        Integer updateOrderRecord = orderDao.updateOrder(order.getId(), OrderStatus.SUCCESS);
        log.info(&amp;quot;更新订单:{} {}&amp;quot;, order.getId(), updateOrderRecord &amp;gt; 0 ? &amp;quot;成功&amp;quot; : &amp;quot;失败&amp;quot;);

        return OperationResponse.builder()
                .success(balanceOperationResponse.isSuccess() &amp;amp;&amp;amp; storageOperationResponse.isSuccess())
                .build();
    }
}&lt;/code&gt;&lt;/pre&gt;    &lt;p&gt;其次，我们需要在另外两个服务的方法中增加注解      &lt;code&gt;@Transactional&lt;/code&gt;，表示开启事务。&lt;/p&gt;    &lt;p&gt;这里的远端服务调用是通过      &lt;code&gt;RestTemplate&lt;/code&gt;，需要在工程启动时将      &lt;code&gt;RestTemplate&lt;/code&gt;注入 Spring 容器中管理。&lt;/p&gt;    &lt;h4&gt;5.3.4 配置文件&lt;/h4&gt;    &lt;p&gt;工程中需在 resources 目录下增加有关Seata的配置文件 registry.conf ，如下：&lt;/p&gt;    &lt;p&gt;代码清单：Alibaba/seata-nacos-jpa/order-server/src/main/resources/registry.conf      &lt;br /&gt;***&lt;/p&gt;    &lt;pre&gt;      &lt;code&gt;registry {
  type = &amp;quot;nacos&amp;quot;
  nacos {
    serverAddr = &amp;quot;192.168.0.128&amp;quot;
    namespace = &amp;quot;public&amp;quot;
    cluster = &amp;quot;default&amp;quot;
  }
}

config {
  type = &amp;quot;nacos&amp;quot;
  nacos {
    serverAddr = &amp;quot;192.168.0.128&amp;quot;
    namespace = &amp;quot;public&amp;quot;
    cluster = &amp;quot;default&amp;quot;
  }
}&lt;/code&gt;&lt;/pre&gt;    &lt;p&gt;在 bootstrap.yml 中的配置如下：&lt;/p&gt;    &lt;p&gt;代码清单：Alibaba/seata-nacos-jpa/order-server/src/main/resources/bootstrap.yml&lt;/p&gt;    &lt;hr&gt;&lt;/hr&gt;    &lt;pre&gt;      &lt;code&gt;spring:
  application:
    name: spring-cloud-order-server
  cloud:
    nacos:
      # nacos config
      config:
        server-addr: 192.168.0.128
        namespace: public
        group: SEATA_GROUP
      # nacos discovery
      discovery:
        server-addr: 192.168.0.128
        namespace: public
        enabled: true
    alibaba:
      seata:
        tx-service-group: ${spring.application.name}&lt;/code&gt;&lt;/pre&gt;    &lt;ul&gt;      &lt;li&gt;spring.cloud.nacos.config.group ：这里的 Group 是 SEATA_GROUP ，也就是我们前面在使用 nacos-config.sh 生成 Nacos 的配置时生成的配置，它的 Group 是 SEATA_GROUP。&lt;/li&gt;      &lt;li&gt;spring.cloud.alibaba.seata.tx-service-group ：这里是我们之前在修改 Seata Server 端配置文件 nacos-config.txt 时里面配置的        &lt;code&gt;service.vgroup_mapping.${your-service-gruop}=default&lt;/code&gt;中间的        &lt;code&gt;${your-service-gruop}&lt;/code&gt;。这两处配置请务必一致，否则在启动工程后会一直报错        &lt;code&gt;no available server to connect&lt;/code&gt;。&lt;/li&gt;&lt;/ul&gt;    &lt;h4&gt;5.3.5 业务数据库初始化&lt;/h4&gt;    &lt;p&gt;数据库初始脚本位于：Alibaba/seata-nacos-jpa/sql ，请分别在三个不同的 Schema 中执行。&lt;/p&gt;    &lt;h4&gt;5.3.6 测试&lt;/h4&gt;    &lt;p&gt;测试工具我们选择使用 PostMan ，启动三个服务，顺序无关 order-server、pay-server 和 storage-server 。&lt;/p&gt;    &lt;p&gt;使用 PostMan 发送测试请求，如图：&lt;/p&gt;    &lt;p&gt;      &lt;img src="https://img2018.cnblogs.com/blog/908359/201909/908359-20190911083401660-37314168.png"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;p&gt;数据库初始化余额为 10 ，这里每次下单将会消耗 5 ，我们可以正常下单两次，第三次应该下单失败，并且回滚 db_order 中的数据。数据库中数据如图：&lt;/p&gt;    &lt;p&gt;      &lt;img src="https://img2018.cnblogs.com/blog/908359/201909/908359-20190911083401791-2106526350.png"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;p&gt;我们进行第三次下单操作，如图：&lt;/p&gt;    &lt;p&gt;      &lt;img src="https://img2018.cnblogs.com/blog/908359/201909/908359-20190911083402319-342517503.png"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;p&gt;这里看到直接报错500，查看数据库 db_order 中的数据，如图：&lt;/p&gt;    &lt;p&gt;      &lt;img src="https://img2018.cnblogs.com/blog/908359/201909/908359-20190911083401791-2106526350.png"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;p&gt;可以看到，这里的数据并未增加，我们看下子工程_rder-server的控制台打印：&lt;/p&gt;    &lt;p&gt;日志已经过简化处理&lt;/p&gt;    &lt;pre&gt;      &lt;code&gt;Hibernate: insert into orders (pay_amount, product_id, status, user_id) values (?, ?, ?, ?)
c.s.b.c.service.impl.OrderServiceImpl    : 保存订单成功
c.s.b.c.service.impl.OrderServiceImpl    : 当前 XID: 192.168.0.102:8091:2021674307
c.s.b.c.service.impl.OrderServiceImpl    : 开始扣减库存
c.s.b.c.service.impl.OrderServiceImpl    : 扣减库存结果:OperationResponse(success=true, message=操作成功, data=null)
c.s.b.c.service.impl.OrderServiceImpl    : 开始扣减余额
i.s.core.rpc.netty.RmMessageListener     : onMessage:xid=192.168.0.102:8091:2021674307,branchId=2021674308,branchType=AT,resourceId=jdbc:mysql://192.168.0.128:3306/db_order,applicationData=null
io.seata.rm.AbstractRMHandler            : Branch Rollbacking: 192.168.0.102:8091:2021674307 2021674308 jdbc:mysql://192.168.0.128:3306/db_order
i.s.rm.datasource.undo.UndoLogManager    : xid 192.168.0.102:8091:2021674307 branch 2021674308, undo_log deleted with GlobalFinished
io.seata.rm.AbstractRMHandler            : Branch Rollbacked result: PhaseTwo_Rollbacked
i.seata.tm.api.DefaultGlobalTransaction  : [192.168.0.102:8091:2021674307] rollback status:Rollbacked&lt;/code&gt;&lt;/pre&gt;    &lt;p&gt;从日志中没有可以清楚的看到，在服务order-server是先执行了订单写入操作，并且调用扣减库存的接口，通过查看storage-server的日志也可以发现，一样是先执行了库存修改操作，直到扣减余额的时候发现余额不足，开始对 xid 为      &lt;code&gt;192.168.0.102:8091:2021674307&lt;/code&gt;执行回滚操作，并且这个操作是全局回滚。&lt;/p&gt;    &lt;h2&gt;6. 注意&lt;/h2&gt;    &lt;p&gt;目前在 Seata v0.8.0 的版本中，Server端尚未支持集群部署，不建议应用于生产环境，并且开源团队计划在 v1.0.0 版本的时候可以使用与生产环境，各位读者可以持续关注这个开源项目。&lt;/p&gt;    &lt;h2&gt;7. 示例代码&lt;/h2&gt;    &lt;p&gt;      &lt;a href="https://github.com/meteor1993/SpringCloudLearning/tree/master/Alibaba/seata-nacos-jpa"&gt;Github-示例代码&lt;/a&gt;&lt;/p&gt;    &lt;p&gt;      &lt;a href="https://gitee.com/inwsy/SpringCloudLearning/tree/master/Alibaba/seata-nacos-jpa"&gt;Gitee-示例代码&lt;/a&gt;&lt;/p&gt;    &lt;blockquote&gt;      &lt;p&gt;参考资料：        &lt;a href="https://github.com/seata/seata/wiki"&gt;Seata官方文档&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&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 />
      <guid isPermaLink="true">https://itindex.net/detail/60025-spring-cloud-alibaba</guid>
      <pubDate>Wed, 11 Sep 2019 14:04:04 CST</pubDate>
    </item>
    <item>
      <title>注册中心 Consul 使用详解 - 纯洁的微笑博客</title>
      <link>https://itindex.net/detail/59995-%E4%B8%AD%E5%BF%83-consul-%E7%BA%AF%E6%B4%81</link>
      <description>&lt;div&gt;    &lt;p&gt;在上个月我们知道 Eureka 2.X 遇到困难停止开发了，但其实对国内的用户影响甚小，一方面国内大都使用的是 Eureka 1.X 系列，另一方面 Spring Cloud 支持很多服务发现的软件，Eureka 只是其中之一，下面是 Spring Cloud 支持的服务发现软件以及特性对比：&lt;/p&gt;    &lt;table&gt;      &lt;tr&gt;        &lt;th&gt;Feature&lt;/th&gt;        &lt;th&gt;euerka&lt;/th&gt;        &lt;th&gt;Consul&lt;/th&gt;        &lt;th&gt;zookeeper&lt;/th&gt;        &lt;th&gt;etcd&lt;/th&gt;&lt;/tr&gt;      &lt;tr&gt;        &lt;td&gt;服务健康检查&lt;/td&gt;        &lt;td&gt;可配支持&lt;/td&gt;        &lt;td&gt;服务状态，内存，硬盘等&lt;/td&gt;        &lt;td&gt;(弱)长连接，keepalive&lt;/td&gt;        &lt;td&gt;连接心跳&lt;/td&gt;&lt;/tr&gt;      &lt;tr&gt;        &lt;td&gt;多数据中心&lt;/td&gt;        &lt;td&gt;—&lt;/td&gt;        &lt;td&gt;支持&lt;/td&gt;        &lt;td&gt;—&lt;/td&gt;        &lt;td&gt;—&lt;/td&gt;&lt;/tr&gt;      &lt;tr&gt;        &lt;td&gt;kv 存储服务&lt;/td&gt;        &lt;td&gt;—&lt;/td&gt;        &lt;td&gt;支持&lt;/td&gt;        &lt;td&gt;支持&lt;/td&gt;        &lt;td&gt;支持&lt;/td&gt;&lt;/tr&gt;      &lt;tr&gt;        &lt;td&gt;一致性&lt;/td&gt;        &lt;td&gt;—&lt;/td&gt;        &lt;td&gt;raft&lt;/td&gt;        &lt;td&gt;paxos&lt;/td&gt;        &lt;td&gt;raft&lt;/td&gt;&lt;/tr&gt;      &lt;tr&gt;        &lt;td&gt;cap&lt;/td&gt;        &lt;td&gt;ap&lt;/td&gt;        &lt;td&gt;cp&lt;/td&gt;        &lt;td&gt;cp&lt;/td&gt;        &lt;td&gt;cp&lt;/td&gt;&lt;/tr&gt;      &lt;tr&gt;        &lt;td&gt;使用接口(多语言能力)&lt;/td&gt;        &lt;td&gt;http（sidecar）&lt;/td&gt;        &lt;td&gt;支持 http 和 dns&lt;/td&gt;        &lt;td&gt;客户端&lt;/td&gt;        &lt;td&gt;http/grpc&lt;/td&gt;&lt;/tr&gt;      &lt;tr&gt;        &lt;td&gt;watch 支持&lt;/td&gt;        &lt;td&gt;支持 long polling/大部分增量&lt;/td&gt;        &lt;td&gt;全量/支持long polling&lt;/td&gt;        &lt;td&gt;支持&lt;/td&gt;        &lt;td&gt;支持 long polling&lt;/td&gt;&lt;/tr&gt;      &lt;tr&gt;        &lt;td&gt;自身监控&lt;/td&gt;        &lt;td&gt;metrics&lt;/td&gt;        &lt;td&gt;metrics&lt;/td&gt;        &lt;td&gt;—&lt;/td&gt;        &lt;td&gt;metrics&lt;/td&gt;&lt;/tr&gt;      &lt;tr&gt;        &lt;td&gt;安全&lt;/td&gt;        &lt;td&gt;—&lt;/td&gt;        &lt;td&gt;acl /https&lt;/td&gt;        &lt;td&gt;acl&lt;/td&gt;        &lt;td&gt;https 支持（弱）&lt;/td&gt;&lt;/tr&gt;      &lt;tr&gt;        &lt;td&gt;spring cloud 集成&lt;/td&gt;        &lt;td&gt;已支持&lt;/td&gt;        &lt;td&gt;已支持&lt;/td&gt;        &lt;td&gt;已支持&lt;/td&gt;        &lt;td&gt;已支持&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;    &lt;p&gt;在以上服务发现的软件中，Euerka 和 Consul 使用最为广泛。如果大家对注册中心的概念和 Euerka 不太了解的话， 可以参考我前期的文章：      &lt;a href="http://www.ityouknow.com/springcloud/2017/05/10/springcloud-eureka.html"&gt;springcloud(二)：注册中心Eureka&lt;/a&gt;，本篇文章主要给大家介绍 Spring Cloud Consul 的使用。&lt;/p&gt;    &lt;h2&gt;Consul 介绍&lt;/h2&gt;    &lt;p&gt;Consul 是 HashiCorp 公司推出的开源工具，用于实现分布式系统的服务发现与配置。与其它分布式服务注册与发现的方案，Consul 的方案更“一站式”，内置了服务注册与发现框 架、分布一致性协议实现、健康检查、Key/Value 存储、多数据中心方案，不再需要依赖其它工具（比如 ZooKeeper 等）。使用起来也较 为简单。Consul 使用 Go 语言编写，因此具有天然可移植性(支持Linux、windows和Mac OS X)；安装包仅包含一个可执行文件，方便部署，与 Docker 等轻量级容器可无缝配合。&lt;/p&gt;    &lt;p&gt;      &lt;strong&gt;Consul 的优势：&lt;/strong&gt;&lt;/p&gt;    &lt;ul&gt;      &lt;li&gt;使用 Raft 算法来保证一致性, 比复杂的 Paxos 算法更直接. 相比较而言, zookeeper 采用的是 Paxos, 而 etcd 使用的则是 Raft。&lt;/li&gt;      &lt;li&gt;支持多数据中心，内外网的服务采用不同的端口进行监听。 多数据中心集群可以避免单数据中心的单点故障,而其部署则需要考虑网络延迟, 分片等情况等。 zookeeper 和 etcd 均不提供多数据中心功能的支持。&lt;/li&gt;      &lt;li&gt;支持健康检查。 etcd 不提供此功能。&lt;/li&gt;      &lt;li&gt;支持 http 和 dns 协议接口。 zookeeper 的集成较为复杂, etcd 只支持 http 协议。&lt;/li&gt;      &lt;li&gt;官方提供 web 管理界面, etcd 无此功能。&lt;/li&gt;      &lt;li&gt;综合比较, Consul 作为服务注册和配置管理的新星, 比较值得关注和研究。&lt;/li&gt;&lt;/ul&gt;    &lt;p&gt;      &lt;strong&gt;特性：&lt;/strong&gt;&lt;/p&gt;    &lt;ul&gt;      &lt;li&gt;服务发现&lt;/li&gt;      &lt;li&gt;健康检查&lt;/li&gt;      &lt;li&gt;Key/Value 存储&lt;/li&gt;      &lt;li&gt;多数据中心&lt;/li&gt;&lt;/ul&gt;    &lt;p&gt;      &lt;strong&gt;Consul 角色&lt;/strong&gt;&lt;/p&gt;    &lt;ul&gt;      &lt;li&gt;client: 客户端, 无状态, 将 HTTP 和 DNS 接口请求转发给局域网内的服务端集群。&lt;/li&gt;      &lt;li&gt;server: 服务端, 保存配置信息, 高可用集群, 在局域网内与本地客户端通讯, 通过广域网与其它数据中心通讯。 每个数据中心的 server 数量推荐为 3 个或是 5 个。&lt;/li&gt;&lt;/ul&gt;    &lt;p&gt;Consul 客户端、服务端还支持夸中心的使用，更加提高了它的高可用性。&lt;/p&gt;    &lt;p&gt;      &lt;img alt="" src="http://favorites.ren/assets/images/2018/springcloud/consul-server-client.png"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;p&gt;      &lt;strong&gt;Consul 工作原理：&lt;/strong&gt;&lt;/p&gt;    &lt;p&gt;      &lt;img alt="" src="http://favorites.ren/assets/images/2018/springcloud/consol_service.png"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;ul&gt;      &lt;li&gt;1、当 Producer 启动的时候，会向 Consul 发送一个 post 请求，告诉 Consul 自己的 IP 和 Port&lt;/li&gt;      &lt;li&gt;2、Consul 接收到 Producer 的注册后，每隔10s（默认）会向 Producer 发送一个健康检查的请求，检验Producer是否健康&lt;/li&gt;      &lt;li&gt;3、当 Consumer 发送 GET 方式请求 /api/address 到 Producer 时，会先从 Consul 中拿到一个存储服务 IP 和 Port 的临时表，从表中拿到 Producer 的 IP 和 Port 后再发送 GET 方式请求 /api/address&lt;/li&gt;      &lt;li&gt;4、该临时表每隔10s会更新，只包含有通过了健康检查的 Producer&lt;/li&gt;&lt;/ul&gt;    &lt;p&gt;Spring Cloud Consul 项目是针对 Consul 的服务治理实现。Consul 是一个分布式高可用的系统，它包含多个组件，但是作为一个整体，在微服务架构中为我们的基础设施提供服务发现和服务配置的工具。&lt;/p&gt;    &lt;h2&gt;Consul VS Eureka&lt;/h2&gt;    &lt;p&gt;Eureka 是一个服务发现工具。该体系结构主要是客户端/服务器，每个数据中心有一组 Eureka 服务器，通常每个可用区域一个。通常 Eureka 的客户使用嵌入式 SDK 来注册和发现服务。对于非本地集成的客户，官方提供的 Eureka 一些 REST 操作 API，其它语言可以使用这些 API 来实现对 Eureka Server 的操作从而实现一个非 jvm 语言的 Eureka Client。&lt;/p&gt;    &lt;p&gt;Eureka 提供了一个弱一致的服务视图，尽可能的提供服务可用性。当客户端向服务器注册时，该服务器将尝试复制到其它服务器，但不提供保证复制完成。服务注册的生存时间（TTL）较短，要求客户端对服务器心跳检测。不健康的服务或节点停止心跳，导致它们超时并从注册表中删除。服务发现可以路由到注册的任何服务，由于心跳检测机制有时间间隔，可能会导致部分服务不可用。这个简化的模型允许简单的群集管理和高可扩展性。&lt;/p&gt;    &lt;p&gt;Consul 提供了一些列特性，包括更丰富的健康检查，键值对存储以及多数据中心。Consul 需要每个数据中心都有一套服务，以及每个客户端的 agent，类似于使用像 Ribbon 这样的服务。Consul agent 允许大多数应用程序成为 Consul 不知情者，通过配置文件执行服务注册并通过 DNS 或负载平衡器 sidecars 发现。&lt;/p&gt;    &lt;p&gt;Consul 提供强大的一致性保证，因为服务器使用 Raft 协议复制状态 。Consul 支持丰富的健康检查，包括 TCP，HTTP，Nagios / Sensu 兼容脚本或基于 Eureka 的 TTL。客户端节点参与基于 Gossip 协议的健康检查，该检查分发健康检查工作，而不像集中式心跳检测那样成为可扩展性挑战。发现请求被路由到选举出来的 leader，这使他们默认情况下强一致性。允许客户端过时读取取使任何服务器处理他们的请求，从而实现像 Eureka 这样的线性可伸缩性。&lt;/p&gt;    &lt;p&gt;Consul 强烈的一致性意味着它可以作为领导选举和集群协调的锁定服务。Eureka 不提供类似的保证，并且通常需要为需要执行协调或具有更强一致性需求的服务运行 ZooKeeper。&lt;/p&gt;    &lt;p&gt;Consul 提供了支持面向服务的体系结构所需的一系列功能。这包括服务发现，还包括丰富的运行状况检查，锁定，密钥/值，多数据中心联合，事件系统和 ACL。Consul 和 consul-template 和 envconsul 等工具生态系统都试图尽量减少集成所需的应用程序更改，以避免需要通过 SDK 进行本地集成。Eureka 是一个更大的 Netflix OSS 套件的一部分，该套件预计应用程序相对均匀且紧密集成。因此 Eureka 只解决了一小部分问题，可以和 ZooKeeper 等其它工具可以一起使用。&lt;/p&gt;    &lt;p&gt;Consul 强一致性(C)带来的是：&lt;/p&gt;    &lt;p&gt;服务注册相比 Eureka 会稍慢一些。因为 Consul 的 raft 协议要求必须过半数的节点都写入成功才认为注册成功 
Leader 挂掉时，重新选举期间整个 Consul 不可用。保证了强一致性但牺牲了可用性。&lt;/p&gt;    &lt;p&gt;Eureka 保证高可用(A)和最终一致性：&lt;/p&gt;    &lt;p&gt;服务注册相对要快，因为不需要等注册信息 replicate 到其它节点，也不保证注册信息是否 replicate 成功
当数据出现不一致时，虽然 A, B 上的注册信息不完全相同，但每个 Eureka 节点依然能够正常对外提供服务，这会出现查询服务信息时如果请求 A 查不到，但请求 B 就能查到。如此保证了可用性但牺牲了一致性。&lt;/p&gt;    &lt;p&gt;其它方面，eureka 就是个 servlet 程序，跑在 servlet 容器中; Consul 则是 go 编写而成。&lt;/p&gt;    &lt;h2&gt;Consul 安装&lt;/h2&gt;    &lt;p&gt;Consul 不同于 Eureka 需要单独安装，访问      &lt;a href="https://www.consul.io/downloads.html"&gt;Consul 官网&lt;/a&gt;下载 Consul 的最新版本，我这里是 consul_1.2.1。&lt;/p&gt;    &lt;p&gt;根据不同的系统类型选择不同的安装包，从下图也可以看出 Consul 支持所有主流系统。&lt;/p&gt;    &lt;p&gt;      &lt;img alt="" src="http://favorites.ren/assets/images/2018/springcloud/consul_insall.png"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;p&gt;我这里以 Windows 为例，下载下来是一个 consul_1.2.1_windows_amd64.zip 的压缩包，解压是是一个 consul.exe 的执行文件。&lt;/p&gt;    &lt;p&gt;      &lt;img alt="" src="http://favorites.ren/assets/images/2018/springcloud/consul_win.png"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;p&gt;cd 到对应的目录下，使用 cmd 启动 Consul&lt;/p&gt;    &lt;div&gt;      &lt;div&gt;        &lt;pre&gt;          &lt;code&gt;cd D:\Common Files\consul
#cmd启动：
consul agent -dev        # -dev表示开发模式运行，另外还有-server表示服务模式运行&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;    &lt;p&gt;为了方便期间，可以在同级目录下创建一个 run.bat 脚本来启动，脚本内容如下：&lt;/p&gt;    &lt;div&gt;      &lt;div&gt;        &lt;pre&gt;          &lt;code&gt;consul agent -dev
pause&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;    &lt;p&gt;启动结果如下：&lt;/p&gt;    &lt;p&gt;      &lt;img alt="" src="http://favorites.ren/assets/images/2018/springcloud/consol_cmd.png"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;p&gt;启动成功之后访问：      &lt;code&gt;http://localhost:8500&lt;/code&gt;，可以看到 Consul 的管理界面&lt;/p&gt;    &lt;p&gt;      &lt;img alt="" src="http://favorites.ren/assets/images/2018/springcloud/consol_manage.png"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;p&gt;这样就意味着我们的 Consul 服务启动成功了。&lt;/p&gt;    &lt;h2&gt;Consul 服务端&lt;/h2&gt;    &lt;p&gt;接下来我们开发 Consul 的服务端，我们创建一个 spring-cloud-consul-producer 项目&lt;/p&gt;    &lt;h3&gt;添加依赖包&lt;/h3&gt;    &lt;p&gt;依赖包如下：&lt;/p&gt;    &lt;div&gt;      &lt;div&gt;        &lt;pre&gt;          &lt;code&gt;&amp;lt;dependencies&amp;gt;
	&amp;lt;dependency&amp;gt;
		&amp;lt;groupId&amp;gt;org.springframework.boot&amp;lt;/groupId&amp;gt;
		&amp;lt;artifactId&amp;gt;spring-boot-starter-actuator&amp;lt;/artifactId&amp;gt;
	&amp;lt;/dependency&amp;gt;
	&amp;lt;dependency&amp;gt;
		&amp;lt;groupId&amp;gt;org.springframework.cloud&amp;lt;/groupId&amp;gt;
		&amp;lt;artifactId&amp;gt;spring-cloud-starter-consul-discovery&amp;lt;/artifactId&amp;gt;
	&amp;lt;/dependency&amp;gt;
	&amp;lt;dependency&amp;gt;
		&amp;lt;groupId&amp;gt;org.springframework.boot&amp;lt;/groupId&amp;gt;
		&amp;lt;artifactId&amp;gt;spring-boot-starter-web&amp;lt;/artifactId&amp;gt;
	&amp;lt;/dependency&amp;gt;
	&amp;lt;dependency&amp;gt;
		&amp;lt;groupId&amp;gt;org.springframework.boot&amp;lt;/groupId&amp;gt;
		&amp;lt;artifactId&amp;gt;spring-boot-starter-test&amp;lt;/artifactId&amp;gt;
		&amp;lt;scope&amp;gt;test&amp;lt;/scope&amp;gt;
	&amp;lt;/dependency&amp;gt;
&amp;lt;/dependencies&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;    &lt;ul&gt;      &lt;li&gt;        &lt;code&gt;spring-boot-starter-actuator&lt;/code&gt;健康检查依赖于此包。&lt;/li&gt;      &lt;li&gt;        &lt;code&gt;spring-cloud-starter-consul-discovery&lt;/code&gt;Spring Cloud Consul 的支持。&lt;/li&gt;&lt;/ul&gt;    &lt;p&gt;Spring Boot 版本使用的是 2.0.3.RELEASE，Spring Cloud 最新版本是 Finchley.RELEASE 依赖于 Spring Boot 2.x.&lt;/p&gt;    &lt;div&gt;      &lt;div&gt;        &lt;pre&gt;          &lt;code&gt;&amp;lt;parent&amp;gt;
	&amp;lt;groupId&amp;gt;org.springframework.boot&amp;lt;/groupId&amp;gt;
	&amp;lt;artifactId&amp;gt;spring-boot-starter-parent&amp;lt;/artifactId&amp;gt;
	&amp;lt;version&amp;gt;2.0.3.RELEASE&amp;lt;/version&amp;gt;
	&amp;lt;relativePath/&amp;gt; &amp;lt;!-- lookup parent from repository --&amp;gt;
&amp;lt;/parent&amp;gt;

&amp;lt;dependencyManagement&amp;gt;
	&amp;lt;dependencies&amp;gt;
		&amp;lt;dependency&amp;gt;
			&amp;lt;groupId&amp;gt;org.springframework.cloud&amp;lt;/groupId&amp;gt;
			&amp;lt;artifactId&amp;gt;spring-cloud-dependencies&amp;lt;/artifactId&amp;gt;
			&amp;lt;version&amp;gt;${spring-cloud.version}&amp;lt;/version&amp;gt;
			&amp;lt;type&amp;gt;pom&amp;lt;/type&amp;gt;
			&amp;lt;scope&amp;gt;import&amp;lt;/scope&amp;gt;
		&amp;lt;/dependency&amp;gt;
	&amp;lt;/dependencies&amp;gt;
&amp;lt;/dependencyManagement&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;    &lt;p&gt;完整的 pom.xml 文件大家可以参考示例源码。&lt;/p&gt;    &lt;h3&gt;配置文件&lt;/h3&gt;    &lt;p&gt;配置文件内容如下&lt;/p&gt;    &lt;div&gt;      &lt;div&gt;        &lt;pre&gt;          &lt;code&gt;spring.application.name=spring-cloud-consul-producer
server.port=8501
spring.cloud.consul.host=localhost
spring.cloud.consul.port=8500
#注册到consul的服务名称
spring.cloud.consul.discovery.serviceName=service-producer&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;    &lt;p&gt;Consul 的地址和端口号默认是 localhost:8500 ，如果不是这个地址可以自行配置。      &lt;code&gt;spring.cloud.consul.discovery.serviceName&lt;/code&gt;是指注册到 Consul 的服务名称，后期客户端会根据这个名称来进行服务调用。&lt;/p&gt;    &lt;h3&gt;启动类&lt;/h3&gt;    &lt;div&gt;      &lt;div&gt;        &lt;pre&gt;          &lt;code&gt;@SpringBootApplication@EnableDiscoveryClientpublicclassConsulProducerApplication{publicstaticvoidmain(String[]args){SpringApplication.run(ConsulProducerApplication.class,args);}}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;    &lt;p&gt;添加了      &lt;code&gt;@EnableDiscoveryClient&lt;/code&gt;注解表示支持服务发现。&lt;/p&gt;    &lt;h3&gt;提供服务&lt;/h3&gt;    &lt;p&gt;我们在创建一个 Controller，推文提供 hello 的服务。&lt;/p&gt;    &lt;div&gt;      &lt;div&gt;        &lt;pre&gt;          &lt;code&gt;@RestControllerpublicclassHelloController{@RequestMapping(&amp;quot;/hello&amp;quot;)publicStringhello(){return&amp;quot;hello consul&amp;quot;;}}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;    &lt;p&gt;为了模拟注册均衡负载复制一份上面的项目重命名为 spring-cloud-consul-producer-2 ,修改对应的端口为 8502，修改 hello 方法的返回值为：”hello consul two”，修改完成后依次启动两个项目。&lt;/p&gt;    &lt;p&gt;这时候我们再次在浏览器访问地址：http://localhost:8500，显示如下：&lt;/p&gt;    &lt;p&gt;      &lt;img alt="" src="http://favorites.ren/assets/images/2018/springcloud/consol_producer.png"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;p&gt;我们发现页面多了 service-producer 服务，点击进去后页面显示有两个服务提供者：&lt;/p&gt;    &lt;p&gt;      &lt;img alt="" src="http://favorites.ren/assets/images/2018/springcloud/consol_producer-2.png"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;p&gt;这样服务提供者就准备好了。&lt;/p&gt;    &lt;h2&gt;Consul 消费端&lt;/h2&gt;    &lt;p&gt;我们创建一个 spring-cloud-consul-consumer 项目，pom 文件和上面示例保持一致。&lt;/p&gt;    &lt;h3&gt;配置文件&lt;/h3&gt;    &lt;p&gt;配置文件内容如下&lt;/p&gt;    &lt;div&gt;      &lt;div&gt;        &lt;pre&gt;          &lt;code&gt;spring.application.name=spring-cloud-consul-consumer
server.port=8503
spring.cloud.consul.host=127.0.0.1
spring.cloud.consul.port=8500
#设置不需要注册到 consul 中
spring.cloud.consul.discovery.register=false&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;    &lt;p&gt;客户端可以设置注册到 Consul 中，也可以不注册到 Consul 注册中心中，根据我们的业务来选择，只需要在使用服务时通过 Consul 对外提供的接口获取服务信息即可。&lt;/p&gt;    &lt;h3&gt;启动类&lt;/h3&gt;    &lt;div&gt;      &lt;div&gt;        &lt;pre&gt;          &lt;code&gt;@SpringBootApplicationpublicclassConsulConsumerApplication{publicstaticvoidmain(String[]args){SpringApplication.run(ConsulConsumerApplication.class,args);}}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;    &lt;h3&gt;进行测试&lt;/h3&gt;    &lt;p&gt;我们先来创建一个 ServiceController ,试试如果去获取 Consul 中的服务。&lt;/p&gt;    &lt;div&gt;      &lt;div&gt;        &lt;pre&gt;          &lt;code&gt;@RestControllerpublicclassServiceController{@AutowiredprivateLoadBalancerClientloadBalancer;@AutowiredprivateDiscoveryClientdiscoveryClient;/**
     * 获取所有服务
     */@RequestMapping(&amp;quot;/services&amp;quot;)publicObjectservices(){returndiscoveryClient.getInstances(&amp;quot;service-producer&amp;quot;);}/**
     * 从所有服务中选择一个服务（轮询）
     */@RequestMapping(&amp;quot;/discover&amp;quot;)publicObjectdiscover(){returnloadBalancer.choose(&amp;quot;service-producer&amp;quot;).getUri().toString();}}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;    &lt;p&gt;Controller 中有俩个方法，一个是获取所有服务名为      &lt;code&gt;service-producer&lt;/code&gt;的服务信息并返回到页面，一个是随机从服务名为      &lt;code&gt;service-producer&lt;/code&gt;的服务中获取一个并返回到页面。&lt;/p&gt;    &lt;p&gt;添加完 ServiceController 之后我们启动项目，访问地址：      &lt;code&gt;http://localhost:8503/services&lt;/code&gt;，返回：&lt;/p&gt;    &lt;div&gt;      &lt;div&gt;        &lt;pre&gt;          &lt;code&gt;[{&amp;quot;serviceId&amp;quot;:&amp;quot;service-producer&amp;quot;,&amp;quot;host&amp;quot;:&amp;quot;windows10.microdone.cn&amp;quot;,&amp;quot;port&amp;quot;:8501,&amp;quot;secure&amp;quot;:false,&amp;quot;metadata&amp;quot;:{&amp;quot;secure&amp;quot;:&amp;quot;false&amp;quot;},&amp;quot;uri&amp;quot;:&amp;quot;http://windows10.microdone.cn:8501&amp;quot;,&amp;quot;scheme&amp;quot;:null},{&amp;quot;serviceId&amp;quot;:&amp;quot;service-producer&amp;quot;,&amp;quot;host&amp;quot;:&amp;quot;windows10.microdone.cn&amp;quot;,&amp;quot;port&amp;quot;:8502,&amp;quot;secure&amp;quot;:false,&amp;quot;metadata&amp;quot;:{&amp;quot;secure&amp;quot;:&amp;quot;false&amp;quot;},&amp;quot;uri&amp;quot;:&amp;quot;http://windows10.microdone.cn:8502&amp;quot;,&amp;quot;scheme&amp;quot;:null}]&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;    &lt;p&gt;发现我们刚才创建的端口为 8501 和 8502 的两个服务端都存在。&lt;/p&gt;    &lt;p&gt;多次访问地址：      &lt;code&gt;http://localhost:8503/discover&lt;/code&gt;，页面会交替返回下面信息：&lt;/p&gt;    &lt;div&gt;      &lt;div&gt;        &lt;pre&gt;          &lt;code&gt;http://windows10.microdone.cn:8501
http://windows10.microdone.cn:8502
...&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;    &lt;p&gt;说明 8501 和 8502 的两个服务会交替出现，从而实现了获取服务端地址的均衡负载。&lt;/p&gt;    &lt;p&gt;大多数情况下我们希望使用均衡负载的形式去获取服务端提供的服务，因此使用第二种方法来模拟调用服务端提供的 hello 方法。&lt;/p&gt;    &lt;p&gt;创建  CallHelloController ：&lt;/p&gt;    &lt;div&gt;      &lt;div&gt;        &lt;pre&gt;          &lt;code&gt;@RestControllerpublicclassCallHelloController{@AutowiredprivateLoadBalancerClientloadBalancer;@RequestMapping(&amp;quot;/call&amp;quot;)publicStringcall(){ServiceInstanceserviceInstance=loadBalancer.choose(&amp;quot;service-producer&amp;quot;);System.out.println(&amp;quot;服务地址：&amp;quot;+serviceInstance.getUri());System.out.println(&amp;quot;服务名称：&amp;quot;+serviceInstance.getServiceId());StringcallServiceResult=newRestTemplate().getForObject(serviceInstance.getUri().toString()+&amp;quot;/hello&amp;quot;,String.class);System.out.println(callServiceResult);returncallServiceResult;}}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;    &lt;p&gt;使用 RestTemplate 进行远程调用。添加完之后重启 spring-cloud-consul-consumer 项目。在浏览器中访问地址：      &lt;code&gt;http://localhost:8503/call&lt;/code&gt;，依次返回结果如下：&lt;/p&gt;    &lt;div&gt;      &lt;div&gt;        &lt;pre&gt;          &lt;code&gt;hello consul
hello consul two
...&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;    &lt;p&gt;说明我们已经成功的调用了 Consul 服务端提供的服务，并且实现了服务端的均衡负载功能。通过今天的实践我们发现 Consul 提供的服务发现易用、强大。&lt;/p&gt;    &lt;p&gt;      &lt;strong&gt;        &lt;a href="https://github.com/ityouknow/spring-cloud-examples"&gt;示例代码-github&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;    &lt;p&gt;      &lt;strong&gt;        &lt;a href="https://gitee.com/ityouknow/spring-cloud-examples"&gt;示例代码-码云&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;    &lt;div&gt;      &lt;p align="center"&gt;    &lt;br /&gt;&lt;/p&gt;&lt;/div&gt;  &lt;div&gt;   &lt;div&gt;&lt;/div&gt;      &lt;div&gt;&lt;/div&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 />
      <guid isPermaLink="true">https://itindex.net/detail/59995-%E4%B8%AD%E5%BF%83-consul-%E7%BA%AF%E6%B4%81</guid>
      <pubDate>Sat, 31 Aug 2019 07:35:48 CST</pubDate>
    </item>
    <item>
      <title>微服务架构之「 访问安全 」 - 不止思考 - CSDN博客</title>
      <link>https://itindex.net/detail/59757-%E5%BE%AE%E6%9C%8D%E5%8A%A1-%E6%9E%B6%E6%9E%84-%E8%AE%BF%E9%97%AE</link>
      <description>&lt;div&gt;    &lt;p&gt;      &lt;img alt="" src="https://img-blog.csdnimg.cn/20190613122131997.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2pzandr,size_16,color_FFFFFF,t_70"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;p&gt;应用程序的访问安全又是我们每一个研发团队都必须关注的重点问题。尤其是在我们采用了微服务架构之后，项目的复杂度提升了N个级别，相应的，微服务的安全工作也就更难更复杂了。并且我们以往擅长的单体应用的安全方案对于微服务来说已经不再适用了。我们必须有一套新的方案来保障微服务架构的安全。&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;img alt="" src="https://img-blog.csdnimg.cn/2019061312261468.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2pzandr,size_16,color_FFFFFF,t_70"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;p&gt;（图片来自WillTran在slideshare分享）&lt;/p&gt;    &lt;p&gt;在应用服务器里面，我们有一个auth模块（一般采用过滤来实现），当有客户端请求进来时，所有的请求都必须首先经过这个auth来做身份验证，验证通过后，才将请求发到后面的业务逻辑。&lt;/p&gt;    &lt;p&gt;通常客户端在第一次请求的时候会带上身份校验信息（用户名和密码），auth模块在验证信息无误后，就会返回Cookie存到客户端，之后每次客户端只需要在请求中携带Cookie来访问，而auth模块也只需要校验Cookie的合法性后决定是否放行。&lt;/p&gt;    &lt;p&gt;可见，在传统单体应用中的安全架构还是蛮简单的，对外也只有一个入口，通过auth校验后，内部的用户信息都是内存/线程传递，逻辑并不是复杂，所以风险也在可控范围内。&lt;/p&gt;    &lt;p&gt;那么，当我们的项目改为微服务之后，「访问安全」又该怎么做呢。&lt;/p&gt;    &lt;p&gt;二、微服务如何实现「访问安全」？&lt;/p&gt;    &lt;p&gt;在微服务架构下，有以下三种方案可以选择，当然，用的最多的肯定还是OAuth模式。&lt;/p&gt;    &lt;ul&gt;      &lt;li&gt;        &lt;p&gt;          &lt;strong&gt;网关鉴权模式（API Gateway）&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;      &lt;li&gt;        &lt;p&gt;          &lt;strong&gt;服务自主鉴权模式&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;      &lt;li&gt;        &lt;p&gt;          &lt;strong&gt;API Token模式（OAuth2.0）&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;    &lt;p&gt;下面分别来讲一下这三种模式：&lt;/p&gt;    &lt;ol&gt;      &lt;li&gt;        &lt;p&gt;          &lt;strong&gt;网关鉴权模式（API Gateway）&lt;/strong&gt;&lt;/p&gt;        &lt;p&gt;          &lt;img alt="" src="https://img-blog.csdnimg.cn/2019061312262699.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2pzandr,size_16,color_FFFFFF,t_70"&gt;&lt;/img&gt;（图片来自WillTran在slideshare分享）&lt;/p&gt;        &lt;p&gt;通过上图可见，因为在微服务的最前端一般会有一个API网关模块（API Gateway），所有的外部请求访问微服务集群时，都会首先通过这个API Gateway，所以我们可以在这个模块里部署auth逻辑，实现统一集中鉴权，鉴权通过后，再把请求转发给后端各个服务。&lt;/p&gt;        &lt;p&gt;这种模式的优点就是，由API Gateway集中处理了鉴权的逻辑，使得后端各微服务节点自身逻辑就简单了，只需要关注业务逻辑，无需关注安全性事宜。&lt;/p&gt;        &lt;p&gt;这个模式的问题就是，API Gateway适用于身份验证和简单的路径授权（基于URL的），对于复杂数据/角色的授权访问权限，通过API Gateway很难去灵活的控制，毕竟这些逻辑都是存在后端服务上的，并非存储在API Gateway里。&lt;/p&gt;&lt;/li&gt;      &lt;li&gt;        &lt;p&gt;          &lt;strong&gt;服务自主鉴权模式&lt;/strong&gt;&lt;/p&gt;        &lt;p&gt;          &lt;img alt="" height="1" src="https://img-blog.csdnimg.cn/20190613122026917.gif" width="1"&gt;&lt;/img&gt;          &lt;img alt="" src="https://img-blog.csdnimg.cn/2019061312264697.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2pzandr,size_16,color_FFFFFF,t_70"&gt;&lt;/img&gt;（图片来自WillTran在slideshare分享）&lt;/p&gt;        &lt;p&gt;服务自主鉴权就是指不通过前端的API Gateway来控制，而是由后端的每一个微服务节点自己去鉴权。&lt;/p&gt;        &lt;p&gt;它的优点就是可以由更为灵活的访问授权策略，并且相当于微服务节点完全无状态化了。同时还可以避免API Gateway 中 auth 模块的性能瓶颈。&lt;/p&gt;        &lt;p&gt;缺点就是由于每一个微服务都自主鉴权，当一个请求要经过多个微服务节点时，会进行重复鉴权，增加了很多额外的性能开销。&lt;/p&gt;&lt;/li&gt;      &lt;li&gt;        &lt;p&gt;          &lt;strong&gt;API Token模式（OAuth2.0）&lt;/strong&gt;&lt;/p&gt;        &lt;p&gt;          &lt;img alt="" src="https://img-blog.csdnimg.cn/20190613122655867.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2pzandr,size_16,color_FFFFFF,t_70"&gt;&lt;/img&gt;（图片来自网络）&lt;/p&gt;        &lt;p&gt;如图，这是一种采用基于令牌Token的授权方式。在这个模式下，是由授权服务器（图中Authorization Server）、API网关（图中API Gateway）、内部的微服务节点几个模块组成。&lt;/p&gt;        &lt;p&gt;流程如下：&lt;/p&gt;        &lt;p&gt;第一步：客户端应用首先使用账号密码或者其它身份信息去访问授权服务器（Authorization Server）获取 访问令牌（Access Token）。&lt;/p&gt;        &lt;p&gt;第二步：拿到访问令牌（Access Token）后带着它再去访问API网关（图中API Gateway），API Gateway自己是无法判断这个Access Token是否合法的，所以走第三步。&lt;/p&gt;        &lt;p&gt;第三步：API Gateway去调用Authorization Server校验一下Access Token的合法性。&lt;/p&gt;        &lt;p&gt;第四步：如果验证完Access Token是合法的，那API Gateway就将Access Token换成JWT令牌返回。&lt;/p&gt;        &lt;p&gt;（注意：此处也可以不换成JWT而是直接返回原Access Token。但是换成JWT更好，因为Access Token是一串不可读无意义的字符串，每次验证Access Token是否合法都需要去访问Authorization Server才知道。但是JWT令牌是一个包含JOSN对象，有用户信息和其它数据的一个字符串，后面微服务节点拿到JWT之后，自己就可以做校验，减少了交互次数）。&lt;/p&gt;        &lt;p&gt;第五步：API Gateway有了JWT之后，就将请求向后端微服务节点进行转发，同时会带上这个JWT。&lt;/p&gt;        &lt;p&gt;第六步：微服务节点收到请求后，读取里面的JWT，然后通过加密算法验证这个JWT，验证通过后，就处理请求逻辑。&lt;/p&gt;        &lt;p&gt;这里面就使用到了OAuth2.0的原理，不过这只是OAuth2.0各类模式中的一种。&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;    &lt;p&gt;由于OAuth2.0目前最为常用，所以接下来我再来详细讲解一下OAuth2.0的原理和各类用法。&lt;/p&gt;    &lt;p&gt;三、详解 OAuth2.0 的「 访问安全 」？&lt;/p&gt;    &lt;p&gt;OAuth2.0是一种访问授权协议框架。它是基于Token令牌的授权方式，在不暴露用户密码的情况下，使 应用方 能够获取到用户数据的访问权限。&lt;/p&gt;    &lt;p&gt;例如：你开发了一个视频网站，可以采用第三方微信登陆，那么只要用户在微信上对这个网站授权了，那这个网站就可以在无需用户密码的情况下获取用户在微信上的头像。&lt;/p&gt;    &lt;p&gt;OAuth2.0 的流程如下图：&lt;/p&gt;    &lt;p&gt;      &lt;img alt="" src="https://img-blog.csdnimg.cn/20190613122709839.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2pzandr,size_16,color_FFFFFF,t_70"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;p&gt;OAuth2.0 里的主要名词有：&lt;/p&gt;    &lt;ul&gt;      &lt;li&gt;        &lt;p&gt;          &lt;strong&gt;资源服务器：&lt;/strong&gt;用户数据/资源存放的地方，在微服务架构中，服务就是资源服务器。在上面的例子中，微信头像存放的服务就是资源服务器。&lt;/p&gt;&lt;/li&gt;      &lt;li&gt;        &lt;p&gt;          &lt;strong&gt;资源拥有者：&lt;/strong&gt;是指用户，资源的拥有人。在上面的例子中某个微信头像的用户就是资源拥有者。&lt;/p&gt;&lt;/li&gt;      &lt;li&gt;        &lt;p&gt;          &lt;strong&gt;授权服务器：&lt;/strong&gt;是一个用来验证用户身份并颁发令牌的服务器。&lt;/p&gt;&lt;/li&gt;      &lt;li&gt;        &lt;p&gt;          &lt;strong&gt;客户端应用：&lt;/strong&gt;想要访问用户受保护资源的客户端/Web应用。在上面的例子中的视频网站就是客户端应用。&lt;/p&gt;&lt;/li&gt;      &lt;li&gt;        &lt;p&gt;          &lt;strong&gt;访问令牌：&lt;/strong&gt;Access Token，授予对资源服务器的访问权限额度令牌。&lt;/p&gt;&lt;/li&gt;      &lt;li&gt;        &lt;p&gt;          &lt;strong&gt;刷新令牌：&lt;/strong&gt;客户端应用用于获取新的 Access Token 的一种令牌。&lt;/p&gt;&lt;/li&gt;      &lt;li&gt;        &lt;p&gt;          &lt;strong&gt;客户凭证：&lt;/strong&gt;用户的账号密码，用于在 授权服务器 进行验证用户身份的凭证。&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;    &lt;p&gt;OAuth2.0有四种授权模式，也就是四种获取令牌的方式：授权码、简化式、用户名密码、客户端凭证。&lt;/p&gt;    &lt;p&gt;下面来分别讲解一下：&lt;/p&gt;    &lt;ol&gt;      &lt;li&gt;        &lt;p&gt;          &lt;strong&gt;授权码（Authorization Code）&lt;/strong&gt;&lt;/p&gt;        &lt;p&gt;授权码模式是指：客户端应用先去申请一个授权码，然后再拿着这个授权码去获取令牌的模式。这也是目前最为常用的一种模式，安全性比较高，适用于我们常用的前后端分离项目。通过前端跳转的方式去访问 授权服务器 获取授权码，然后后端再用这个授权码访问 授权服务器 以获取 访问令牌。&lt;/p&gt;        &lt;p&gt;          &lt;img alt="" src="https://img-blog.csdnimg.cn/2019061312271831.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2pzandr,size_16,color_FFFFFF,t_70"&gt;&lt;/img&gt;流程如上图。&lt;/p&gt;        &lt;p&gt;第一步，客户端的前端页面(图中UserAgent)将用户跳转到 授权服务器(Authorization Server)里进行授权，授权完成后，返回 授权码(Authorization Code)&lt;/p&gt;        &lt;p&gt;第二步，客户端的后端服务(图中Client)携带授权码(Authorization Code)去访问 授权服务器，然后获得正式的 访问令牌(Access Token)&lt;/p&gt;        &lt;p&gt;页面的前端和后端分别做不同的逻辑，前端接触不到Access Token，保证了Access Token的安全性。&lt;/p&gt;&lt;/li&gt;      &lt;li&gt;        &lt;p&gt;          &lt;strong&gt;简化式（Implicit）&lt;/strong&gt;&lt;/p&gt;        &lt;p&gt;简化模式是在项目是一个纯前端应用，在没有后端的情况下，采用的一种模式。&lt;/p&gt;        &lt;p&gt;因为这种方式令牌是直接存在前端的，所以非常不安全，因此令牌的有限期设置就不能太长。&lt;/p&gt;        &lt;p&gt;          &lt;img alt="" src="https://img-blog.csdnimg.cn/2019061312272680.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2pzandr,size_16,color_FFFFFF,t_70"&gt;&lt;/img&gt;其流程就是：&lt;/p&gt;        &lt;p&gt;第一步：应用（纯前端的应用）将用户跳转到 授权服务器(Authorization Server)里进行授权，授权完成后，授权服务器 直接将 Access Token 返回给 前端应用，令牌存储在前端页面。&lt;/p&gt;        &lt;p&gt;第二步：应用（纯前端的应用）携带 访问令牌(Access Token) 去访问资源，获取资源。&lt;/p&gt;        &lt;p&gt;在整个过程中，虽然令牌是在前端URL中直接传递，但注意，令牌在HTTP协议中不是放在URL参数字段中的，而是放在URL锚点里。因为锚点数据不会被浏览器发到服务器，因此有一定的安全保障。&lt;/p&gt;&lt;/li&gt;      &lt;li&gt;        &lt;p&gt;          &lt;strong&gt;用户名密码（Resource Owner Credentials）&lt;/strong&gt;&lt;/p&gt;        &lt;p&gt;          &lt;img alt="" src="https://img-blog.csdnimg.cn/20190613122734599.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2pzandr,size_16,color_FFFFFF,t_70"&gt;&lt;/img&gt;这种方式最容易理解了，直接使用用户的用户名/密码作为授权方式去访问 授权服务器，从而获取Access Token，这个方式因为需要用户给出自己的密码，所以非常的不安全性。一般仅在客户端应用与授权服务器、资源服务器是归属统一公司/团队，互相非常信任的情况下采用。&lt;/p&gt;&lt;/li&gt;      &lt;li&gt;        &lt;p&gt;          &lt;strong&gt;客户端凭证（Client Credentials）&lt;/strong&gt;&lt;/p&gt;        &lt;p&gt;          &lt;img alt="" src="https://img-blog.csdnimg.cn/20190613122742821.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2pzandr,size_16,color_FFFFFF,t_70"&gt;&lt;/img&gt;这是适用于服务器间通信的场景。客户端应用拿一个用户凭证去找授权服务器获取Access Token。&lt;/p&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;blockquote&gt;      &lt;p&gt;本文原创发布于微信公众号「 不止思考 」，欢迎关注。涉及 思维认知、个人成长、架构、大数据、Web技术 等。 &lt;/p&gt;      &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;&lt;/blockquote&gt;    &lt;p&gt; &lt;/p&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 />
      <guid isPermaLink="true">https://itindex.net/detail/59757-%E5%BE%AE%E6%9C%8D%E5%8A%A1-%E6%9E%B6%E6%9E%84-%E8%AE%BF%E9%97%AE</guid>
      <pubDate>Sat, 29 Jun 2019 15:36:57 CST</pubDate>
    </item>
    <item>
      <title>微信公众号开发C#系列-11、生成带参数二维码应用场景 - C#快速开发框架 - CSDN博客</title>
      <link>https://itindex.net/detail/59543-%E5%BE%AE%E4%BF%A1-%E5%85%AC%E4%BC%97-%E5%BC%80%E5%8F%91</link>
      <description>&lt;div&gt;    &lt;h2&gt;      &lt;a&gt;&lt;/a&gt;1、概述&lt;/h2&gt;    &lt;p&gt;我们在      &lt;a href="http://blog.rdiframework.net/article/211" rel="nofollow"&gt;微信公众号开发C#系列-7、消息管理-接收事件推送&lt;/a&gt;章节有对扫描带参数二维码事件的处理做了讲解。本篇主要讲解通过微信公众号开发平台提供的接口生成带参数的二维码及应用场景。&lt;/p&gt;    &lt;p&gt;微信公众号平台提供了生成带参数二维码的接口，使用该接口可以获得多个带不同场景值的二维码，用户扫描后，公众号可以接收到事件推送。      &lt;br /&gt;目前有2种类型的二维码，分别是临时二维码和永久二维码，前者有过期时间，最大为1800秒，但能够生成较多数量，后者无过期时间，数量较少（目前参数只支持1–100000）。两种二维码分别适用于帐号绑定、用户来源统计等场景。&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;如果用户已经关注公众号，在用户扫描后会自动进入会话，微信也会将带场景值扫描事件推送给开发者。&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;    &lt;p&gt;获取带参数的二维码的过程包括两步，首先创建二维码ticket，然后凭借ticket到指定URL换取二维码。&lt;/p&gt;    &lt;h2&gt;      &lt;a&gt;&lt;/a&gt;2、创建二维码ticket&lt;/h2&gt;    &lt;p&gt;每次创建二维码ticket需要提供一个开发者自行设定的参数（scene_id），分别介绍临时二维码和永久二维码的创建二维码ticket过程。&lt;/p&gt;    &lt;h3&gt;      &lt;a&gt;&lt;/a&gt;2.1、创建临时二维码接口说明&lt;/h3&gt;    &lt;p&gt;http请求方式: POST&lt;/p&gt;    &lt;pre&gt;      &lt;code&gt;URL: https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=TOKEN
POST数据格式：json
POST数据例子：
{&amp;quot;expire_seconds&amp;quot;: 604800, &amp;quot;action_name&amp;quot;: &amp;quot;QR_SCENE&amp;quot;, &amp;quot;action_info&amp;quot;: {&amp;quot;scene&amp;quot;: {&amp;quot;scene_id&amp;quot;: 123}}}&lt;/code&gt;&lt;/pre&gt;    &lt;p&gt;或者也可以使用以下POST数据创建字符串形式的二维码参数：&lt;/p&gt;    &lt;pre&gt;      &lt;code&gt;{&amp;quot;expire_seconds&amp;quot;: 604800, &amp;quot;action_name&amp;quot;: &amp;quot;QR_STR_SCENE&amp;quot;, &amp;quot;action_info&amp;quot;: {&amp;quot;scene&amp;quot;: {&amp;quot;scene_str&amp;quot;: &amp;quot;test&amp;quot;}}}&lt;/code&gt;&lt;/pre&gt;    &lt;h3&gt;      &lt;a&gt;&lt;/a&gt;2.2、创建永久二维码接口说明&lt;/h3&gt;    &lt;pre&gt;      &lt;code&gt;http请求方式: POST
URL: https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=TOKEN
POST数据格式：json
POST数据例子：{&amp;quot;action_name&amp;quot;: &amp;quot;QR_LIMIT_SCENE&amp;quot;, &amp;quot;action_info&amp;quot;: {&amp;quot;scene&amp;quot;: {&amp;quot;scene_id&amp;quot;: 123}}}

或者也可以使用以下POST数据创建字符串形式的二维码参数：
{&amp;quot;action_name&amp;quot;: &amp;quot;QR_LIMIT_STR_SCENE&amp;quot;, &amp;quot;action_info&amp;quot;: {&amp;quot;scene&amp;quot;: {&amp;quot;scene_str&amp;quot;: &amp;quot;test&amp;quot;}}}&lt;/code&gt;&lt;/pre&gt;    &lt;h3&gt;      &lt;a&gt;&lt;/a&gt;2.3、参数说明&lt;/h3&gt;    &lt;p&gt;      &lt;strong&gt;参数说明&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;expire_seconds&lt;/td&gt;        &lt;td&gt;该二维码有效时间，以秒为单位。 最大不超过2592000（即30天），此字段如果不填，则默认有效期为30秒。&lt;/td&gt;&lt;/tr&gt;      &lt;tr&gt;        &lt;td&gt;action_name&lt;/td&gt;        &lt;td&gt;二维码类型，QR_SCENE为临时的整型参数值，QR_STR_SCENE为临时的字符串参数值，QR_LIMIT_SCENE为永久的整型参数值，QR_LIMIT_STR_SCENE为永久的字符串参数值&lt;/td&gt;&lt;/tr&gt;      &lt;tr&gt;        &lt;td&gt;action_info	二维码详细信息&lt;/td&gt;        &lt;td&gt;&lt;/td&gt;&lt;/tr&gt;      &lt;tr&gt;        &lt;td&gt;scene_id&lt;/td&gt;        &lt;td&gt;场景值ID，临时二维码时为32位非0整型，永久二维码时最大值为100000（目前参数只支持1–100000）&lt;/td&gt;&lt;/tr&gt;      &lt;tr&gt;        &lt;td&gt;scene_str&lt;/td&gt;        &lt;td&gt;场景值ID（字符串形式的ID），字符串类型，长度限制为1到64&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;    &lt;p&gt;      &lt;strong&gt;返回说明&lt;/strong&gt;&lt;/p&gt;    &lt;p&gt;正确的Json返回结果:&lt;/p&gt;    &lt;pre&gt;      &lt;code&gt;{&amp;quot;ticket&amp;quot;:&amp;quot;gQH47joAAAAAAAAAASxodHRwOi8vd2VpeGluLnFxLmNvbS9xL2taZ2Z3TVRtNzJXV1Brb3ZhYmJJAAIEZ23sUwMEmm3sUw==&amp;quot;,&amp;quot;expire_seconds&amp;quot;:60,&amp;quot;url&amp;quot;:&amp;quot;http://weixin.qq.com/q/kZgfwMTm72WWPkovabbI&amp;quot;}&lt;/code&gt;&lt;/pre&gt;    &lt;table&gt;      &lt;tr&gt;        &lt;th&gt;参数&lt;/th&gt;        &lt;th align="right"&gt;说明&lt;/th&gt;&lt;/tr&gt;      &lt;tr&gt;        &lt;td&gt;ticket&lt;/td&gt;        &lt;td align="right"&gt;获取的二维码ticket，凭借此ticket可以在有效时间内换取二维码。&lt;/td&gt;&lt;/tr&gt;      &lt;tr&gt;        &lt;td&gt;expire_seconds&lt;/td&gt;        &lt;td align="right"&gt;该二维码有效时间，以秒为单位。 最大不超过2592000（即30天）。&lt;/td&gt;&lt;/tr&gt;      &lt;tr&gt;        &lt;td&gt;url&lt;/td&gt;        &lt;td align="right"&gt;二维码图片解析后的地址，开发者可根据该地址自行生成需要的二维码图片&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;    &lt;h3&gt;      &lt;a&gt;&lt;/a&gt;2.4、通过ticket换取二维码&lt;/h3&gt;    &lt;p&gt;获取二维码ticket后，开发者可用ticket换取二维码图片。请注意，本接口无须登录态即可调用。      &lt;br /&gt;请求说明&lt;/p&gt;    &lt;pre&gt;      &lt;code&gt;HTTP GET请求（请使用https协议）https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=TICKET

提醒：TICKET记得进行UrlEncode&lt;/code&gt;&lt;/pre&gt;    &lt;p&gt;返回说明&lt;/p&gt;    &lt;pre&gt;      &lt;code&gt;ticket正确情况下，http 返回码是200，是一张图片，可以直接展示或者下载。

HTTP头（示例）如下：
Accept-Ranges:bytes
Cache-control:max-age=604800
Connection:keep-alive
Content-Length:28026
Content-Type:image/jpg
Date:Wed, 16 Oct 2013 06:37:10 GMT
Expires:Wed, 23 Oct 2013 14:37:10 +0800
Server:nginx/1.4.1
错误情况下（如ticket非法）返回HTTP错误码404。&lt;/code&gt;&lt;/pre&gt;    &lt;h2&gt;      &lt;a&gt;&lt;/a&gt;3、创建与获取临时或永久二维码代码参考&lt;/h2&gt;    &lt;p&gt;我们可以直接使用Senparc.Weixin SDK提供的接口Senparc.Weixin.MP.AdvancedAPIs.QrCodeApi.Create来创建临时或永久二维码。&lt;/p&gt;    &lt;p&gt;利用Senparc.Weixin.MP.AdvancedAPIs.QrCodeApi.GetShowQrCodeUrl来获取临时或永久二维码。&lt;/p&gt;    &lt;p&gt;Senparc.Weixin.MP.AdvancedAPIs.QrCodeApi代码参考：&lt;/p&gt;    &lt;pre&gt;      &lt;code&gt;//API：http://mp.weixin.qq.com/wiki/index.php?title=%E7%94%9F%E6%88%90%E5%B8%A6%E5%8F%82%E6%95%B0%E7%9A%84%E4%BA%8C%E7%BB%B4%E7%A0%81

/// &amp;lt;summary&amp;gt;
/// 二维码接口
/// &amp;lt;/summary&amp;gt;
public static class QrCode
{
    /// &amp;lt;summary&amp;gt;
    /// 创建二维码
    /// &amp;lt;/summary&amp;gt;
    /// &amp;lt;param name=&amp;quot;expireSeconds&amp;quot;&amp;gt;该二维码有效时间，以秒为单位。 最大不超过1800。0时为永久二维码&amp;lt;/param&amp;gt;
    /// &amp;lt;param name=&amp;quot;sceneId&amp;quot;&amp;gt;场景值ID，临时二维码时为32位整型，永久二维码时最大值为1000&amp;lt;/param&amp;gt;
    /// &amp;lt;returns&amp;gt;&amp;lt;/returns&amp;gt;
    public static CreateQrCodeResult Create(string accessToken, int expireSeconds, int sceneId)
    {
        var urlFormat = &amp;quot;https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token={0}&amp;quot;;
        object data = null;
        if (expireSeconds &amp;gt; 0)
        {
            data = new
            {
                expire_seconds = expireSeconds,
                action_name = &amp;quot;QR_SCENE&amp;quot;,
                action_info = new
                {
                    scene = new
                    {
                        scene_id = sceneId
                    }
                }
            };
        }
        else
        {
            data = new
            {
                action_name = &amp;quot;QR_LIMIT_SCENE&amp;quot;,
                action_info = new
                {
                    scene = new
                    {
                        scene_id = sceneId
                    }
                }
            };
        }
        return CommonJsonSend.Send&amp;lt;CreateQrCodeResult&amp;gt;(accessToken, urlFormat, data);
    }

    /// &amp;lt;summary&amp;gt;
    /// 获取二维码（不需要AccessToken）
    /// 错误情况下（如ticket非法）返回HTTP错误码404。
    /// &amp;lt;/summary&amp;gt;
    /// &amp;lt;param name=&amp;quot;ticket&amp;quot;&amp;gt;&amp;lt;/param&amp;gt;
    /// &amp;lt;param name=&amp;quot;stream&amp;quot;&amp;gt;&amp;lt;/param&amp;gt;
    public static void ShowQrCode(string ticket, Stream stream)
    {
        var urlFormat = &amp;quot;https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket={0}&amp;quot;;
        HttpUtility.Get.Download(string.Format(urlFormat, ticket), stream);
    }
}&lt;/code&gt;&lt;/pre&gt;    &lt;h2&gt;      &lt;a&gt;&lt;/a&gt;4、二维码创建实现&lt;/h2&gt;    &lt;p&gt;要使用微信提供的永久或临时二维码的功能，我们需要界面来生成或获取二维码，如下图所示。      &lt;br /&gt;      &lt;img alt="&amp;#21019;&amp;#24314;&amp;#20108;&amp;#32500;&amp;#30721;" src="https://img-blog.csdnimg.cn/20190421175833508.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly95b25naHUuYmxvZy5jc2RuLm5ldA==,size_16,color_FFFFFF,t_70"&gt;&lt;/img&gt;      &lt;br /&gt;控制器代码参考：&lt;/p&gt;    &lt;pre&gt;      &lt;code&gt;[HttpPost]
[ValidateInput(false)]
[LoginAuthorize]
public ActionResult GenerateQrCode()
{
    string ticket = CacheFactory.Cache().GetCache&amp;lt;string&amp;gt;(&amp;quot;Weixin-Qr-Ticket&amp;quot;);
    if (string.IsNullOrEmpty(ticket))
    {
        WeixinOfficialAccountEntity currentWeixinOfficialAccountEntity = RDIFrameworkService.Instance.WeixinBasicService.GetCurrentOfficialAccountEntity(ManageProvider.Provider.Current());
        string token = currentWeixinOfficialAccountEntity.AccessToken;
        var result = Senparc.Weixin.MP.AdvancedAPIs.QrCodeApi.Create(token, 600, 10, Senparc.Weixin.MP.QrCode_ActionName.QR_SCENE);
        if (result.errcode == Senparc.Weixin.ReturnCode.请求成功)
        {
            ticket = result.ticket;
            CacheFactory.Cache().WriteCache&amp;lt;string&amp;gt;(result.ticket, &amp;quot;Weixin-Qr-Ticket&amp;quot;, DateTime.Now.AddSeconds(600));
        }
    }

    string qrUrl = Senparc.Weixin.MP.AdvancedAPIs.QrCodeApi.GetShowQrCodeUrl(ticket);
    return Content(new JsonMessage { Success = true, Data = qrUrl, Type = ResultType.Success, Message = RDIFrameworkMessage.MSG3010 }.ToString());
}&lt;/code&gt;&lt;/pre&gt;    &lt;p&gt;上面的代码我们创建了一个场景值为      &lt;strong&gt;10&lt;/strong&gt;的临时二维码。用户通过扫描这个二维码，我们就可以在服务器端做处理，扫描带参数二维码事件只需要重写OnEvent_ScanRequest事件代码即可，如下我们返回了一个文本消息，实现代码参考：&lt;/p&gt;    &lt;pre&gt;      &lt;code&gt;public override IResponseMessageBase OnEvent_ScanRequest(RequestMessageEvent_Scan requestMessage)
{
    //通过扫描关注
    var responseMessage = CreateResponseMessage&amp;lt;ResponseMessageText&amp;gt;();

    responseMessage.Content = responseMessage.Content ?? string.Format(&amp;quot;欢迎关注国思软件，通过扫描二维码进入，场景值：{0}&amp;quot;, requestMessage.EventKey);

    return responseMessage;
}&lt;/code&gt;&lt;/pre&gt;    &lt;p&gt;在上面的代码中用户扫描了带场景值的二维码进入公众号后我们返回了一个提示的文本消息。这是非常有用的功能，常用途推广，可以根据不同的二维码场景值分别做不同的业务处理，如可以统计关注的每一个粉丝从哪里来的，做到渠道推广分析，但是关注的都是同一个公众号。      &lt;br /&gt;      &lt;img alt="&amp;#36890;&amp;#36807;&amp;#25195;&amp;#25551;&amp;#24102;&amp;#22330;&amp;#26223;&amp;#20540;&amp;#30340;&amp;#20108;&amp;#32500;&amp;#30721;&amp;#36827;&amp;#20837;" src="https://img-blog.csdnimg.cn/20190421175849206.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly95b25naHUuYmxvZy5jc2RuLm5ldA==,size_16,color_FFFFFF,t_70"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;h2&gt;      &lt;a&gt;&lt;/a&gt;5、生成带参数的二维码用途&lt;/h2&gt;    &lt;p&gt;微信公众号生成带参数的二维码有何用途？&lt;/p&gt;    &lt;ol&gt;      &lt;li&gt;可以区分粉丝来源，只需要生成不同的带参数的二维码，把这些二维码分别投放到各个渠道，粉丝通过这些渠道二维码进来就可以区分粉丝来源，微号帮后台渠道粉丝列表中有粉丝数及明细；&lt;/li&gt;      &lt;li&gt;粉丝通过扫描渠道二维码关注公众号，会打标签分组，比如粉丝扫商店A、B的二维码进来的， 在微信公众号后来的用户管理中可查看到商店A/B二维码名下的粉丝明细及分组情况；&lt;/li&gt;      &lt;li&gt;可以生成多个不同的渠道二维码配置不同的营销活动，设置不同的关注回复信息，让粉丝第一时间了解活动动机，是否有兴趣参与等等；&lt;/li&gt;      &lt;li&gt;可以利用渠道二维码生成功能，可以实现微信收款前关注公众号，间接分析粉丝后续消费情况；        &lt;br /&gt;考核推广员完成任务的进度，如以推广名字生成多不个同的二维码，分配给不同的推广员，每个推广员吸引了多少粉丝关注公众号，微号帮后台都可以一一明细；&lt;/li&gt;      &lt;li&gt;带参数的二维码也叫渠道二维码或者场景二维码，生存的数量有限，且是永久二维码。当数量用完后可以删除一些不用的二维码释放出来，二次利用。&lt;/li&gt;      &lt;li&gt;其他用途。&lt;/li&gt;&lt;/ol&gt;    &lt;h2&gt;      &lt;a&gt;&lt;/a&gt;参考文章&lt;/h2&gt;    &lt;p&gt;      &lt;a href="https://mp.weixin.qq.com/wiki?t=resource/res_main&amp;id=mp1445241432" rel="nofollow"&gt;微信公众平台技术文档-官方&lt;/a&gt;&lt;/p&gt;    &lt;p&gt;      &lt;a href="https://github.com/JeffreySu/WeiXinMPSDK" rel="nofollow"&gt;Senparc.Weixin SDK + 官网示例源代码&lt;/a&gt;&lt;/p&gt;    &lt;p&gt;      &lt;a href="http://blog.rdiframework.net/article/190" rel="nofollow"&gt;RDIFramework.NET — 基于.NET的快速信息化系统开发框架 — 系列目录&lt;/a&gt;&lt;/p&gt;    &lt;p&gt;      &lt;a href="http://blog.rdiframework.net/article/169" rel="nofollow"&gt;RDIFramework.NET ━ .NET快速信息化系统开发框架 ━ 工作流程组件介绍&lt;/a&gt;&lt;/p&gt;    &lt;p&gt;      &lt;a href="http://blog.rdiframework.net/article/189" rel="nofollow"&gt;RDIFramework.NET框架SOA解决方案（集Windows服务、WinForm形式与IIS形式发布）-分布式应用&lt;/a&gt;&lt;/p&gt;    &lt;p&gt;      &lt;a href="http://blog.rdiframework.net/article/199" rel="nofollow"&gt;RDIFramework.NET代码生成器全新V3.5版本发布-重大升级&lt;/a&gt;&lt;/p&gt;    &lt;hr&gt;&lt;/hr&gt;    &lt;p&gt;一路走来数个年头，感谢RDIFramework.NET框架的支持者与使用者，大家可以通过下面的地址了解详情。      &lt;br /&gt;RDIFramework.NET官方网站：      &lt;a href="http://www.rdiframework.net/" rel="nofollow"&gt;http://www.rdiframework.net/&lt;/a&gt;      &lt;br /&gt;RDIFramework.NET官方博客：      &lt;a href="http://blog.rdiframework.net/" rel="nofollow"&gt;http://blog.rdiframework.net/&lt;/a&gt;      &lt;br /&gt;同时需要说明的，以后的所有技术文章以官方网站为准，欢迎大家收藏！      &lt;br /&gt;RDIFramework.NET框架由专业团队长期打造、一直在更新、一直在升级，请放心使用！      &lt;br /&gt;欢迎关注RDIFramework.net框架官方公众微信（微信号：guosisoft），及时了解最新动态。      &lt;br /&gt;扫描二维码立即关注      &lt;br /&gt;   &lt;br /&gt;&lt;/p&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 />
      <guid isPermaLink="true">https://itindex.net/detail/59543-%E5%BE%AE%E4%BF%A1-%E5%85%AC%E4%BC%97-%E5%BC%80%E5%8F%91</guid>
      <pubDate>Fri, 03 May 2019 17:21:18 CST</pubDate>
    </item>
    <item>
      <title>你现在发得每一条微博都将存入国家图书馆</title>
      <link>https://itindex.net/detail/59485-%E5%BE%AE%E5%8D%9A-%E5%9B%BD%E5%AE%B6-%E5%9B%BE%E4%B9%A6%E9%A6%86</link>
      <description>&lt;div&gt;  &lt;img src="http://qiniu.cdn-chuang.com/1JXU1RkVCJXU31555729199366.png"&gt;&lt;/img&gt;&lt;/div&gt; &lt;p&gt;4月19日，国家图书馆互联网信息战略保存项目在北京启动，首家互联网信息战略保存基地落户新浪。  &lt;br /&gt;&lt;/p&gt; &lt;p&gt; &lt;/p&gt; &lt;p&gt;国家图书馆馆长饶权饶权在致辞中表示，国家图书馆设立互联网信息战略保存项目，是立足于国家信息安全与社会信息化建设的长远发展，建设覆盖全国的分级分布式中文互联网信息资源采集与保存体系，及时、有效地记录时代文明发展脉络，提炼、积累与传承中华文明最新成果及其生动展现形式。为了达到这样的目标，图书馆需要与更多社会力量联合起来，共同推进互联网信息的社会化保存和服务。  &lt;br /&gt;&lt;/p&gt; &lt;p&gt; &lt;/p&gt; &lt;p&gt;微博CEO 王高飞王高飞在致辞中表示，参与互联网信息的保护和应用，是新浪义不容辞的责任。新浪将继续发挥新媒体的技术优势和平台优势，确保信息的存储安全，并且和国家图书馆一起探索和创新应用方式，让互联网信息更好的服务于提升公共服务精准性和有效性，服务于社会治理创新，发挥出最大的社会价值。  &lt;br /&gt;&lt;/p&gt; &lt;p&gt; &lt;/p&gt; &lt;p&gt;活动现场召开了以“互联网时代人类文化记忆保存的价值、路径和未来”为主题的圆桌论坛，北京大学信息管理系教授王子舟、北京师范大学新闻传播学院执行院长喻国明教授、国家图书馆副馆长孙一钢、中科院文献情报中心副主任张智雄教授、微博高级副总裁曹增辉围绕主题做了精彩对话。  &lt;br /&gt;&lt;/p&gt; &lt;p&gt; &lt;/p&gt; &lt;h3&gt;  &lt;strong&gt;数据分析专注非商业用途&lt;/strong&gt;&lt;/h3&gt; &lt;p&gt;国家图书馆互联网信息战略保存项目旨在建设覆盖全国的分级分布式中文互联网信息资源采集与保存体系，通过与国内重点数字文化生产和保存机构的合作，推动互联网信息的社会化保存与服务，构建国家互联网信息资源战略保障体系。&lt;/p&gt; &lt;p&gt; 互联网已经成为重要的信息发布和传播平台。第43次《中国互联网络发展状况统计报告》的数据显示，截至2018年，网络新闻的网民使用率达81.4%，网络视频和音乐的使用率达70%左右，此外还有42%的网民通过微博等社交媒体获取和发布信息。&lt;/p&gt; &lt;div&gt;
      &lt;img alt="" src="http://qiniu.cdn-chuang.com/1JXU1RkVCJXU31555729212955.png"&gt;&lt;/img&gt;
        
&lt;/div&gt; &lt;p&gt;互联网信息成为人类文明和社会记忆的新载体，客观反映着一定时期内政治、经济、文化和社会等方面的变迁。易逝性和不可再生性，使互联网信息的采集和保存尤为迫切，而随着移动互联网和物联网的普及，互联网信息的规模爆炸式增长，调动社会力量参与也成为互联网信息采集和保存的趋势。&lt;/p&gt; &lt;p&gt;因此，国家图书馆启动互联网信息战略保存项目。在中国境内开展互联网业务、并在相关领域处于领先地位的企业机构，均可申请成为互联网信息战略保存基地共建主体。共建主体需确保其提供的信息数据完整、有效、安全，并拥有合法所有权、知识产权或已获得相应授权。&lt;/p&gt; &lt;p&gt;为实现国家图书馆的学术优势和互联网公司的技术优势之间的协同效应，互联网信息保存项目对信息采取社会化保存模式。信息数据仍由共建主体保存，国家图书馆根据保存规范、数据遴选机制和服务需要提供使用需求，将部分数据纳入国家文献信息战略保存体系，并与共建主体联合进行分析，服务于政策决策、学术研究等非商业用途。&lt;/p&gt; &lt;p&gt;推进数字资源建设与保存工作，是国家图书馆“十三五”规划的重要工作之一。从2003年起，国家图书馆就开始尝试对互联网资源进行采集和保存。2009年，国家图书馆互联网成立信息保存保护中心，对国内外政治、经济、文化、科技等领域重要网站和重大专题资源进行采集保存。截至2018年，全国各级公共图书馆累计采集网站23000余个。&lt;/p&gt; &lt;p&gt;国家“十三五”战略规划明确指出，要加快推动数据资源共享开放和开发应用，助力社会治理创新，深化政府数据和社会数据关联分析、融合利用，提高宏观调控、市场监管、社会治理和公共服务精准性和有效性。互联网信息战略保存项目的启动，有助于提升互联网信息采集和保护的系统性和持续性，及时有效记录时代文明发展脉络，提炼、积累与传承中华优秀文明最新成果及其展现形式，保障并促进中华优秀文明的传播，不断激发中华优秀文明在现代社会的活力，实现面向全社会的公共文化服务。&lt;/p&gt; &lt;h3&gt;  &lt;strong&gt;首家互联网信息战略保存基地落户新浪&lt;/strong&gt;&lt;/h3&gt; &lt;p&gt;在20年的发展历程中，新浪一直在引领新媒体行业发展，其内容已经多次被国家图书馆采集保存。国家图书馆与新浪共建首个互联网信息战略保存基地，看重的是新浪庞大的信息数据规模和领先的信息处理能力。&lt;/p&gt; &lt;p&gt;新浪网发布的新闻和微博上公开发布的博文，都将被互联网信息战略保存基地保存。截至2018年12月，新浪网累计发布新闻超过2.1亿条、图片13亿张、视频4500万个、互动总量超过80亿，微博全站发布博文超过2000亿条、图片500亿张、视频4亿个、评论和赞总量近5000亿。新浪网和微博上新发布的内容，也将持续保存。&lt;/p&gt; &lt;p&gt;从1998年起，新浪完整经历了中国网络媒体从门户网站到博客再到微博的进化，并在每个时期都处于领先。新浪积累的信息数据，既有对媒体报道的整合，也有专家的专业观点和网民的碎片化表达。特别是微博作为社交媒体平台，不仅记录着社会重大事件的发展进程，更凭借广泛的参与性记录下公众对重大事件的态度和情感。截至2018年12月，微博月活跃用户达4.62亿，其中22岁以下的年轻群体超过40%，来自三四线城市的活跃用户超过56%。不同年龄、不同地域、不同教育和文化背景的用户发布的内容，可以为政策决策和学术研究提供多元参考，也为从公众视角开展历史和社会研究提供了宝贵资料。&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>新闻&amp;趣事</category>
      <guid isPermaLink="true">https://itindex.net/detail/59485-%E5%BE%AE%E5%8D%9A-%E5%9B%BD%E5%AE%B6-%E5%9B%BE%E4%B9%A6%E9%A6%86</guid>
      <pubDate>Sat, 20 Apr 2019 11:01:31 CST</pubDate>
    </item>
    <item>
      <title>搭建微服务器：express+https+api代理 - 馒头加梨子 - 博客园</title>
      <link>https://itindex.net/detail/59383-%E5%BE%AE%E6%9C%8D%E5%8A%A1-express-https</link>
      <description>&lt;div&gt;    &lt;h2&gt;概述&lt;/h2&gt;    &lt;p&gt;最近打算玩一下      &lt;strong&gt;service worker&lt;/strong&gt;，但是service worker只能在https下跑，所以查资料自己用纯express搭建了一个微服务器，把过程记录下来，供以后开发时参考，相信对其他人也有用。&lt;/p&gt;    &lt;p&gt;参考资料：      &lt;a href="http://www.expressjs.com.cn/4x/api.html"&gt;express官方文档&lt;/a&gt;&lt;/p&gt;    &lt;h3&gt;http服务器&lt;/h3&gt;    &lt;p&gt;首先我们用express搭建一个      &lt;strong&gt;http服务器&lt;/strong&gt;，很简单，看看官方文档就可以搭建出来了。代码如下：&lt;/p&gt;    &lt;pre&gt;      &lt;code&gt;// server.js
const express = require(&amp;apos;express&amp;apos;);
const http = require(&amp;apos;http&amp;apos;);

const app = express();
const PORT = 7088; // 写个合理的值就好
const httpServer = http.createServer(app);

app.get(&amp;apos;/&amp;apos;, function (req, res) {
  res.send(&amp;apos;hello world&amp;apos;);
});

httpServer.listen(PORT, function () {
  console.log(&amp;apos;HTTPS Server is running on: http://localhost:%s&amp;apos;, PORT);
});&lt;/code&gt;&lt;/pre&gt;    &lt;h3&gt;加入到项目中&lt;/h3&gt;    &lt;p&gt;我们的      &lt;strong&gt;理想状况&lt;/strong&gt;是，在项目目录下建立一个server文件夹，然后在server文件夹里面启动服务器，加载项目目录下的dist文件夹。&lt;/p&gt;    &lt;p&gt;所以我们加入代码解析静态资源：&lt;/p&gt;    &lt;pre&gt;      &lt;code&gt;// server.js
const express = require(&amp;apos;express&amp;apos;);
const http = require(&amp;apos;http&amp;apos;);

const app = express();
const PORT = 7088; // 写个合理的值就好
const httpServer = http.createServer(app);

app.use(&amp;apos;/&amp;apos;, express.static(&amp;apos;../dist&amp;apos;));

httpServer.listen(PORT, function () {
  console.log(&amp;apos;HTTPS Server is running on: http://localhost:%s&amp;apos;, PORT);
});&lt;/code&gt;&lt;/pre&gt;    &lt;h3&gt;加入https&lt;/h3&gt;    &lt;p&gt;我们想把http变成https，首先我们要      &lt;strong&gt;生成本地证书&lt;/strong&gt;：&lt;/p&gt;    &lt;pre&gt;      &lt;code&gt;brew install mkcert
mkcert localhost 127.0.0.1 ::1&lt;/code&gt;&lt;/pre&gt;    &lt;p&gt;上面的代码意思是说，先安装mkcert，然后用mkcert给localhost，127.0.0.1和::1这三个域名生成证书。&lt;/p&gt;    &lt;p&gt;然后我们可以在文件夹下面看到2个文件：&lt;/p&gt;    &lt;pre&gt;      &lt;code&gt;秘钥：example.com+3-key.pem
公钥：example.com+3.pem&lt;/code&gt;&lt;/pre&gt;    &lt;p&gt;我们在钥匙串里面把公钥添加信任。方法可参考：      &lt;a href="https://www.colabug.com/3479278.html"&gt;在Vue里用Service Worker来搞个中间层（React同理）&lt;/a&gt;&lt;/p&gt;    &lt;p&gt;添加完之后我们把秘钥和公钥放在certificate文件夹，然后添加到credentials.js文件中，我们通过这个文件引入秘钥和公钥：&lt;/p&gt;    &lt;pre&gt;      &lt;code&gt;// credentials.js
const path = require(&amp;apos;path&amp;apos;);
const fs = require(&amp;apos;fs&amp;apos;);

// 引入秘钥
const privateKey = fs.readFileSync(path.resolve(__dirname, &amp;apos;./certificate/example.com+3-key.pem&amp;apos;), &amp;apos;utf8&amp;apos;);
// 引入公钥
const certificate = fs.readFileSync(path.resolve(__dirname, &amp;apos;./certificate/example.com+3.pem&amp;apos;), &amp;apos;utf8&amp;apos;);

module.exports = {
  key: privateKey,
  cert: certificate
};&lt;/code&gt;&lt;/pre&gt;    &lt;p&gt;最后我们把      &lt;strong&gt;http变成https&lt;/strong&gt;，并且引入秘钥和公钥：&lt;/p&gt;    &lt;pre&gt;      &lt;code&gt;// server.js
const express = require(&amp;apos;express&amp;apos;);
const https = require(&amp;apos;https&amp;apos;);
const credentials = require(&amp;apos;./credentials&amp;apos;);

const app = express();
const SSLPORT = 7081; // 写个合理的值就好
const httpsServer = https.createServer(credentials, app);

app.use(&amp;apos;/&amp;apos;, express.static(&amp;apos;../dist&amp;apos;));

httpsServer.listen(SSLPORT, function () {
  console.log(&amp;apos;HTTPS Server is running on: https://localhost:%s&amp;apos;, SSLPORT);
});&lt;/code&gt;&lt;/pre&gt;    &lt;h3&gt;设置api代理&lt;/h3&gt;    &lt;p&gt;在项目中，我们经常遇到跨域问题，在开发时我们是通过devServer的      &lt;strong&gt;proxyTable&lt;/strong&gt;解决的，而proxyTable在打包后是无效的。所以我们需要在服务器上面      &lt;strong&gt;代理api请求&lt;/strong&gt;。代码如下：&lt;/p&gt;    &lt;pre&gt;      &lt;code&gt;// proxy.js
const proxy = require(&amp;apos;http-proxy-middleware&amp;apos;);

const authApi = &amp;apos;your-authApi-address&amp;apos;;
const commonApi = &amp;apos;your-commonApi-address&amp;apos;;

module.exports = app =&amp;gt; {
  app.use(&amp;apos;/api/auth&amp;apos;, proxy({
    target: authApi,
    changeOrigin: true,
    pathRewrite: {
      &amp;apos;/api/auth&amp;apos;: &amp;apos;/auth&amp;apos;
    },
    secure: false,
  }));

  app.use(&amp;apos;/api/common&amp;apos;, proxy({
    target: commonApi,
    changeOrigin: true,
    pathRewrite: {
      &amp;apos;/api/common&amp;apos;: &amp;apos;/api&amp;apos;
    },
    secure: false,
  }));
};&lt;/code&gt;&lt;/pre&gt;    &lt;p&gt;写法和devServer里面是一样的，因为devServer底层也是通过express实现的。&lt;/p&gt;    &lt;p&gt;然后我们在server.js里面引入上面写的代理：&lt;/p&gt;    &lt;pre&gt;      &lt;code&gt;// server.js
const express = require(&amp;apos;express&amp;apos;);
const https = require(&amp;apos;https&amp;apos;);
const setProxy = require(&amp;apos;./proxy&amp;apos;);
const credentials = require(&amp;apos;./credentials&amp;apos;);

const app = express();
const SSLPORT = 7081; // 写个合理的值就好
const httpsServer = https.createServer(credentials, app);

app.use(&amp;apos;/&amp;apos;, express.static(&amp;apos;../dist&amp;apos;));

setProxy(app);

httpsServer.listen(SSLPORT, function () {
  console.log(&amp;apos;HTTPS Server is running on: https://localhost:%s&amp;apos;, SSLPORT);
});&lt;/code&gt;&lt;/pre&gt;    &lt;h3&gt;最后&lt;/h3&gt;    &lt;p&gt;最后我们把server.js，credentials.js和proxy.js放在一起就好了啦！&lt;/p&gt;    &lt;p&gt;      &lt;strong&gt;用法：&lt;/strong&gt;只需要把整个文件夹放到项目目录，在里面运行下面的指令就好了：&lt;/p&gt;    &lt;pre&gt;      &lt;code&gt;yarn i
node server.js&lt;/code&gt;&lt;/pre&gt;    &lt;p&gt;详细代码可以参考      &lt;a href="https://github.com/sishenhei7/httpsServer"&gt;我的github&lt;/a&gt;&lt;/p&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 />
      <guid isPermaLink="true">https://itindex.net/detail/59383-%E5%BE%AE%E6%9C%8D%E5%8A%A1-express-https</guid>
      <pubDate>Fri, 22 Mar 2019 09:09:56 CST</pubDate>
    </item>
    <item>
      <title>Python +mysql 简易爬虫给新浪大 V 微博和文章做备份</title>
      <link>https://itindex.net/detail/58494-python-mysql-%E7%AE%80%E6%98%93</link>
      <description>&lt;p&gt;写了个爬虫定期抓取新浪指定用户的微博和文章,以防哪天失联了还能看备份。
GitHub:
  &lt;a href="https://github.com/HubQin/sinaCrawlerV" rel="nofollow"&gt;https://github.com/HubQin/sinaCrawlerV&lt;/a&gt;&lt;/p&gt;
 &lt;h2&gt;各文件功能：&lt;/h2&gt;
 &lt;ul&gt;
  &lt;li&gt;   &lt;a href="http://database.py" rel="nofollow"&gt;database.py&lt;/a&gt; 封装了各种 mysql 操作&lt;/li&gt;
  &lt;li&gt;   &lt;a href="http://post.py" rel="nofollow"&gt;post.py&lt;/a&gt; 抓取微博，每次抓取到上次抓取的时间为止&lt;/li&gt;
  &lt;li&gt;   &lt;a href="http://article.py" rel="nofollow"&gt;article.py&lt;/a&gt; 抓取文章，同上&lt;/li&gt;
  &lt;li&gt;   &lt;a href="http://gadget.py" rel="nofollow"&gt;gadget.py&lt;/a&gt; 用到的各种小工具&lt;/li&gt;
  &lt;li&gt;   &lt;a href="http://config.py" rel="nofollow"&gt;config.py&lt;/a&gt; 需要用到的参数&lt;/li&gt;
&lt;/ul&gt;
 &lt;h2&gt;使用：&lt;/h2&gt;
 &lt;ul&gt;
  &lt;li&gt;创建数据表：运行 sina.sql 创建数据表&lt;/li&gt;
  &lt;li&gt;完善    &lt;a href="http://config.py" rel="nofollow"&gt;config.py&lt;/a&gt; 的参数，抓取微博使用移动端的请求链接；抓取文章需要登录，这里手动登录后，查看移动端的异步请求，复制 Request Header 里面的 cookie 出来使用&lt;/li&gt;
  &lt;li&gt;命令行终端 cd 到 py 文件所在目录，运行    &lt;a href="http://pyhton.py" rel="nofollow"&gt;pyhton.py&lt;/a&gt; 和    &lt;a href="http://article.py" rel="nofollow"&gt;article.py&lt;/a&gt; ，或修改 auto.bat 文件的 cd 路径，双击改文件开始抓取&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 />
      <guid isPermaLink="true">https://itindex.net/detail/58494-python-mysql-%E7%AE%80%E6%98%93</guid>
      <pubDate>Mon, 02 Jul 2018 23:42:17 CST</pubDate>
    </item>
    <item>
      <title>2018微博内部技术分享春晚专场</title>
      <link>https://itindex.net/detail/58294-%E5%BE%AE%E5%8D%9A-%E5%86%85%E9%83%A8-%E6%8A%80%E6%9C%AF</link>
      <description>&lt;p&gt;这是公司内部分享的各部门春晚保障的技术分享。我将其中的和公司隐私相关的数据删除了，只保留了技术的介绍，总结了一些知识点。&lt;/p&gt;
 &lt;p&gt;Tim开场白。&lt;/p&gt;
 &lt;p&gt;双十一、微信红包和微博的区别（无法预期）。&lt;/p&gt;
 &lt;p&gt;三条军规。&lt;/p&gt;
 &lt;a&gt;&lt;/a&gt;
 &lt;h2&gt;孟兆飞 混合云架构下微博春晚保障&lt;/h2&gt;
 &lt;h3&gt;流量&lt;/h3&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;h3&gt;万台扩容挑战&lt;/h3&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;15分钟1000台全公司随时随地&lt;/p&gt;
 &lt;h3&gt;自动化&lt;/h3&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;h3&gt;高可用&lt;/h3&gt;
 &lt;p&gt;传统扩容，由于资源限制会失败&lt;/p&gt;
 &lt;p&gt;优化扩容：基于多种策略&lt;/p&gt;
 &lt;p&gt;DCP高可用双机房&lt;/p&gt;
 &lt;h3&gt;春节保障&lt;/h3&gt;
 &lt;p&gt;春节X台扩容、云上X台  &lt;br /&gt;流量监控&lt;/p&gt;
 &lt;p&gt;DNS问题、扩容 （16台支持万台client） UDP session?&lt;/p&gt;
 &lt;p&gt;全链路压测。演练。共享池。重点监控。&lt;/p&gt;
 &lt;h2&gt;熊超 让红包飞春晚55万qps解决方案&lt;/h2&gt;
 &lt;p&gt;超预期&lt;/p&gt;
 &lt;h3&gt;战队红包&lt;/h3&gt;
 &lt;h4&gt;业务&lt;/h4&gt;
 &lt;p&gt;满N万开奖&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;瞬间QPS高&lt;/li&gt;
  &lt;li&gt;参与人数越多，开奖越快&lt;/li&gt;
  &lt;li&gt;瞬间开奖&lt;/li&gt;
  &lt;li&gt;参与规则复杂，单次参与动作资源交互次数10+&lt;/li&gt;
&lt;/ul&gt;
 &lt;h4&gt;实现&lt;/h4&gt;
 &lt;p&gt;5台扫描， 扔队列， 30台队列机， 64组redis， 发奖发私信等&lt;/p&gt;
 &lt;p&gt;前端： 缓存、不可缓存&lt;/p&gt;
 &lt;h3&gt;红包雨&lt;/h3&gt;
 &lt;h4&gt;业务&lt;/h4&gt;
 &lt;p&gt;3次机会，10分钟任意点&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;参与用户多&lt;/li&gt;
  &lt;li&gt;拼手速、qps 55万&lt;/li&gt;
  &lt;li&gt;每次点击都有请求&lt;/li&gt;
  &lt;li&gt;中出数量巨大， 5次红包雨1.6亿&lt;/li&gt;
&lt;/ul&gt;
 &lt;h4&gt;实现&lt;/h4&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;h4&gt;优化&lt;/h4&gt;
 &lt;ul&gt;
  &lt;li&gt;代码： 重复资源链接重用、耗时步骤优化、根据日志&lt;/li&gt;
  &lt;li&gt;DBA： 监控平台&lt;/li&gt;
&lt;/ul&gt;
 &lt;h4&gt;压测评估&lt;/h4&gt;
 &lt;h2&gt;温情 陈新伍 春节百万答题&lt;/h2&gt;
 &lt;p&gt;两三周紧急开发。&lt;/p&gt;
 &lt;h3&gt;背景&lt;/h3&gt;
 &lt;p&gt;大家都在做，拉新拉活。&lt;/p&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;快速下发push设计&lt;/li&gt;
&lt;/ul&gt;
 &lt;h3&gt;简介&lt;/h3&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;发题阶段 -&amp;gt; 答题阶段 -&amp;gt; 颁奖阶段&lt;/p&gt;
 &lt;h3&gt;技术挑战&lt;/h3&gt;
 &lt;ul&gt;
  &lt;li&gt;消息实时性&lt;/li&gt;
  &lt;li&gt;   &lt;p&gt;同步答题、实时到达率&lt;/p&gt;
&lt;/li&gt;
  &lt;li&gt;   &lt;p&gt;每秒千万推送&lt;/p&gt;
&lt;/li&gt;
  &lt;li&gt;百万在线&lt;/li&gt;
&lt;/ul&gt;
 &lt;h3&gt;长连推送&lt;/h3&gt;
 &lt;ul&gt;
  &lt;li&gt;百万长连接&lt;/li&gt;
  &lt;li&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;/li&gt;
  &lt;li&gt;   &lt;p&gt;消息分发队列: Reids的PubSub (apiServer  -&amp;gt; redis)&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
 &lt;h3&gt;问答服务&lt;/h3&gt;
 &lt;ul&gt;
  &lt;li&gt;上行: 接口https&lt;/li&gt;
  &lt;li&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;视频流(SEI)下发(丢包)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;微博方案: 1为主，2为辅&lt;/p&gt;
 &lt;p&gt;发题方案：  &lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;Push+ACK： 有条件重传&lt;/li&gt;
  &lt;li&gt;Push + Push: 无条件重传&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;轮训：长链接断后自动重连降级&lt;/p&gt;
 &lt;p&gt;发题设计：根据服务器NTP， 题目和视频校准同时弹出&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;li&gt;汇总推送&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;10万级别的qps&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;ul&gt;
  &lt;li&gt;第一场就全量push,无灰度&lt;/li&gt;
  &lt;li&gt;峰值速度快，第一题为峰值&lt;/li&gt;
  &lt;li&gt;百万用户&lt;/li&gt;
&lt;/ul&gt;
 &lt;h2&gt;关里 微博搜索架构&lt;/h2&gt;
 &lt;h3&gt;架构&lt;/h3&gt;
 &lt;p&gt;trigger -&amp;gt; 数据转换 -&amp;gt; 预处理 -&amp;gt; 数据分发 --&amp;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;1000多亿次数据需要索引&lt;/p&gt;
 &lt;p&gt;单机7、8亿&lt;/p&gt;
 &lt;h2&gt;朱伟 支撑百亿级请求的微博广告运维技术实践&lt;/h2&gt;
 &lt;p&gt;运维在微博广告体系中的价值。&lt;/p&gt;
 &lt;p&gt;人工 -&amp;gt; 工具 -&amp;gt; DevOps -&amp;gt; AiOps&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;监控: 数据采集、清洗、存储。Filebeat -&amp;gt; kafka -&amp;gt; OLS -&amp;gt; Druid, kafka -&amp;gt; graphite, kafka -&amp;gt; logstash -&amp;gt; ES, 多存储graphite,druid,ES, clickhouse&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/58294-%E5%BE%AE%E5%8D%9A-%E5%86%85%E9%83%A8-%E6%8A%80%E6%9C%AF</guid>
      <pubDate>Tue, 24 Apr 2018 18:29:50 CST</pubDate>
    </item>
    <item>
      <title>Spring Cloud 微服务的那点事 - CSDN博客</title>
      <link>https://itindex.net/detail/58280-spring-cloud-%E5%BE%AE%E6%9C%8D%E5%8A%A1</link>
      <description>&lt;div&gt;    &lt;h3&gt;什么是微服务&lt;/h3&gt;    &lt;p&gt;微服务的概念源于2014年3月Martin Fowler所写的一篇文章“Microservices”。&lt;/p&gt;    &lt;p&gt;微服务架构是一种架构模式，它提倡将单一应用程序划分成一组小的服务，服务之间互相协调、互相配合，为用户提供最终价值。每个服务运行在其独立的进程中，服务与服务间采用轻量级的通信机制互相沟通（通常是基于HTTP的RESTful API）。每个服务都围绕着具体业务进行构建，并且能够被独立地部署到生产环境、类生产环境等。另外，应尽量避免统一的、集中式的服务管理机制，对具体的一个服务而言，应根据业务上下文，选择合适的语言、工具对其进行构建。&lt;/p&gt;    &lt;p&gt;微服务是一种架构风格，一个大型复杂软件应用由一个或多个微服务组成。系统中的各个微服务可被独立部署，各个微服务之间是松耦合的。每个微服务仅关注于完成一件任务并很好地完成该任务。在所有情况下，每个任务代表着一个小的业务能力。&lt;/p&gt;    &lt;h3&gt;微服务架构优势&lt;/h3&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;h3&gt;什么是Spring Boot&lt;/h3&gt;    &lt;p&gt;Spring Boot是由Pivotal团队提供的全新框架，其设计目的是用来简化新Spring应用的初始搭建以及开发过程。该框架使用了特定的方式来进行配置，从而使开发人员不再需要定义样板化的配置。用我的话来理解，就是Spring Boot其实不是什么新的框架，它默认配置了很多框架的使用方式，就像maven整合了所有的jar包，Spring Boot整合了所有的框架（不知道这样比喻是否合适）。&lt;/p&gt;    &lt;p&gt;Spring Boot简化了基于Spring的应用开发，通过少量的代码就能创建一个独立的、产品级别的Spring应用。 Spring Boot为Spring平台及第三方库提供开箱即用的设置，这样你就可以有条不紊地开始。Spring Boot的核心思想就是约定大于配置，多数Spring Boot应用只需要很少的Spring配置。采用Spring Boot可以大大的简化你的开发模式，所有你想集成的常用框架，它都有对应的组件支持。&lt;/p&gt;    &lt;h3&gt;Spring Cloud都做了哪些事&lt;/h3&gt;    &lt;p&gt;Spring Cloud是一系列框架的有序集合。它利用Spring Boot的开发便利性巧妙地简化了分布式系统基础设施的开发，如服务发现注册、配置中心、消息总线、负载均衡、断路器、数据监控等，都可以用Spring Boot的开发风格做到一键启动和部署。Spring并没有重复制造轮子，它只是将目前各家公司开发的比较成熟、经得起实际考验的服务框架组合起来，通过Spring Boot风格进行再封装屏蔽掉了复杂的配置和实现原理，最终给开发者留出了一套简单易懂、易部署和易维护的分布式系统开发工具包&lt;/p&gt;    &lt;p&gt;以下为Spring Cloud的核心功能：&lt;/p&gt;    &lt;ul&gt;      &lt;li&gt;        &lt;p&gt;分布式/版本化配置&lt;/p&gt;&lt;/li&gt;      &lt;li&gt;        &lt;p&gt;服务注册和发现&lt;/p&gt;&lt;/li&gt;      &lt;li&gt;        &lt;p&gt;路由&lt;/p&gt;&lt;/li&gt;      &lt;li&gt;        &lt;p&gt;服务和服务之间的调用&lt;/p&gt;&lt;/li&gt;      &lt;li&gt;        &lt;p&gt;负载均衡&lt;/p&gt;&lt;/li&gt;      &lt;li&gt;        &lt;p&gt;断路器&lt;/p&gt;&lt;/li&gt;      &lt;li&gt;        &lt;p&gt;分布式消息传递&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;    &lt;p&gt;我们再来看一张图：&lt;/p&gt;    &lt;p&gt;      &lt;img alt="" src="https://img-blog.csdn.net/20180207215150790?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvdzA1OTgwNTk4/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast"&gt;&lt;/img&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;通过这张图，我们来了解一下各组件配置使用运行流程：&lt;/p&gt;    &lt;ul&gt;      &lt;li&gt;        &lt;p&gt;1、请求统一通过API网关（Zuul）来访问内部服务.&lt;/p&gt;&lt;/li&gt;      &lt;li&gt;        &lt;p&gt;2、网关接收到请求后，从注册中心（Eureka）获取可用服务&lt;/p&gt;&lt;/li&gt;      &lt;li&gt;        &lt;p&gt;3、由Ribbon进行均衡负载后，分发到后端具体实例&lt;/p&gt;&lt;/li&gt;      &lt;li&gt;        &lt;p&gt;4、微服务之间通过Feign进行通信处理业务&lt;/p&gt;&lt;/li&gt;      &lt;li&gt;        &lt;p&gt;5、Hystrix负责处理服务超时熔断&lt;/p&gt;&lt;/li&gt;      &lt;li&gt;        &lt;p&gt;6、Turbine监控服务间的调用和熔断相关指标&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;    &lt;h3&gt;Spring Cloud体系介绍&lt;/h3&gt;    &lt;p&gt;上图只是Spring Cloud体系的一部分，Spring Cloud共集成了19个子项目，里面都包含一个或者多个第三方的组件或者框架！&lt;/p&gt;    &lt;p&gt;Spring Cloud 工具框架&lt;/p&gt;    &lt;p&gt;1、Spring Cloud Config 配置中心，利用git集中管理程序的配置。 &lt;/p&gt;    &lt;p&gt;2、Spring Cloud Netflix 集成众多Netflix的开源软件      &lt;br /&gt;3、Spring Cloud Bus 消息总线，利用分布式消息将服务和服务实例连接在一起，用于在一个集群中传播状态的变化       &lt;br /&gt;4、Spring Cloud for Cloud Foundry 利用Pivotal Cloudfoundry集成你的应用程序      &lt;br /&gt;5、Spring Cloud Cloud Foundry Service Broker 为建立管理云托管服务的服务代理提供了一个起点。      &lt;br /&gt;6、Spring Cloud Cluster 基于Zookeeper, Redis, Hazelcast, Consul实现的领导选举和平民状态模式的抽象和实现。      &lt;br /&gt;7、Spring Cloud Consul 基于Hashicorp Consul实现的服务发现和配置管理。      &lt;br /&gt;8、Spring Cloud Security 在Zuul代理中为OAuth2 rest客户端和认证头转发提供负载均衡      &lt;br /&gt;9、Spring Cloud Sleuth SpringCloud应用的分布式追踪系统，和Zipkin，HTrace，ELK兼容。      &lt;br /&gt;10、Spring Cloud Data Flow 一个云本地程序和操作模型，组成数据微服务在一个结构化的平台上。      &lt;br /&gt;11、Spring Cloud Stream 基于Redis,Rabbit,Kafka实现的消息微服务，简单声明模型用以在Spring Cloud应用中收发消息。      &lt;br /&gt;12、Spring Cloud Stream App Starters 基于Spring Boot为外部系统提供spring的集成      &lt;br /&gt;13、Spring Cloud Task 短生命周期的微服务，为SpringBooot应用简单声明添加功能和非功能特性。      &lt;br /&gt;14、Spring Cloud Task App Starters      &lt;br /&gt;15、Spring Cloud Zookeeper 服务发现和配置管理基于Apache Zookeeper。      &lt;br /&gt;16、Spring Cloud for Amazon Web Services 快速和亚马逊网络服务集成。      &lt;br /&gt;17、Spring Cloud Connectors 便于PaaS应用在各种平台上连接到后端像数据库和消息经纪服务。      &lt;br /&gt;18、Spring Cloud Starters （项目已经终止并且在Angel.SR2后的版本和其他项目合并）      &lt;br /&gt;19、Spring Cloud CLI 插件用Groovy快速的创建Spring Cloud组件应用。&lt;/p&gt;    &lt;blockquote&gt;      &lt;p&gt;当然这个数量还在一直增加...&lt;/p&gt;&lt;/blockquote&gt;    &lt;h3&gt;三者之间的关系&lt;/h3&gt;    &lt;p&gt;微服务是一种架构的理念，提出了微服务的设计原则，从理论为具体的技术落地提供了指导思想。Spring Boot是一套快速配置脚手架，可以基于Spring Boot快速开发单个微服务；Spring Cloud是一个基于Spring Boot实现的服务治理工具包；Spring Boot专注于快速、方便集成的单个微服务个体，Spring Cloud关注全局的服务治理框架。&lt;/p&gt;    &lt;p&gt;Spring Boot/Cloud是微服务实践的最佳落地方案。&lt;/p&gt;    &lt;h2&gt;实战经历&lt;/h2&gt;    &lt;h3&gt;遇到问题，寻找方案&lt;/h3&gt;    &lt;p&gt;2015年初的时候，因为公司业务的大量发展，我们开始对原有的业务进行拆分，新上的业务线也全部使用独立的项目来开发，项目和项目之间通过http接口进行访问。15年的业务发展非常迅速，项目数量也就相应急剧扩大，到了15底的时候项目达60多个，当项目数达到30几个的时候，其实我们就遇到了问题，经常某个项目因为扩展增加了新的IP地址，我们就需要被动的更新好几个相关的项目。服务越来越多，服务之间的调用关系也越来越复杂，有时候想画一张图来表示项目和项目之间的依赖关系，线条密密麻麻无法看清。网上有一张图可以表达我们的心情。&lt;/p&gt;    &lt;p&gt;      &lt;img alt="" src="https://img-blog.csdn.net/20180207215300109?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvdzA1OTgwNTk4/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast"&gt;&lt;/img&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;这个时候我们就想找一种方案，可以将我们这么多分布式的服务给管理起来，到网上进行了技术调研。我们发现有两款开源软件比较适合我们，一个是Dubbo，一个是Spring Cloud。&lt;/p&gt;    &lt;p&gt;其实刚开始我们是走了一些弯路的。这两款框架我们当时都不熟悉，当时国内使用Spring Cloud进行开发的企业非常的少，我在网上也几乎没找到太多应用的案例。但是Dubbo当时在国内的使用还是挺普遍的，相关的资料各方面都比较完善。因此在公司扩展新业务线众筹平台的时候，技术选型就先定了Dubbo，因为也是全新的业务没有什么负担，这个项目我们大概开发了六个月投产，上线之初也遇到了一些问题，但最终还比较顺利。&lt;/p&gt;    &lt;p&gt;在新业务线选型使用Dubbo的同时，我们也没有完全放弃Spring Cloud，我们抽出了一两名开发人员学习Spring Boot我也参与其中，为了验证Spring Boot是否可以到达实战的标准，我们在业余的时间使用Spring Boot开发了一款开源软件云收藏，经过这个项目的实战验证我们对Spring Boot就有了信心。最重要的是大家体会到使用Spring
 Boot的各种便利之后，就再也不想使用传统的方式来进行开发了。&lt;/p&gt;    &lt;p&gt;但是还有一个问题，在选择了Spring Boot进行新业务开发的同时，并没有解决我们上面的那个问题，服务于服务直接调用仍然比较复杂和传统，这时候我们就开始研究Spring Cloud。因为大家在前期对Spring Boot有了足够的了解，因此学习Sprig Cloud就显得顺风顺水了。所以在使用Dubbo半年之后，我们又全面开始拥抱Spring Cloud。&lt;/p&gt;    &lt;h3&gt;为什么选择使用Spring Cloud而放弃了Dubbo&lt;/h3&gt;    &lt;p&gt;可能大家会问，为什么选择了使用Dubbo之后，而又选择全面使用Spring Cloud呢？其中有几个原因：&lt;/p&gt;    &lt;p&gt;1）从两个公司的背景来谈：Dubbo，是阿里巴巴服务化治理的核心框架，并被广泛应用于中国各互联网公司；Spring Cloud是大名鼎鼎的Spring家族的产品。阿里巴巴是一个商业公司，虽然也开源了很多的顶级的项目，但从整体战略上来讲，仍然是服务于自身的业务为主。Spring专注于企业级开源框架的研发，不论是在中国还是在世界上使用都非常广泛，开发出通用、开源、稳健的开源框架就是他们的主业。&lt;/p&gt;    &lt;p&gt;2）从社区活跃度这个角度来对比，Dubbo虽然也是一个非常优秀的服务治理框架，并且在服务治理、灰度发布、流量分发这方面做的比Spring Cloud还好，除过当当网在基础上增加了rest支持外，已有两年多的时间几乎都没有任何更新了。在使用过程中出现问题，提交到github的Issue也少有回复。&lt;/p&gt;    &lt;p&gt;相反Spring Cloud自从发展到现在，仍然在不断的高速发展，从github上提交代码的频度和发布版本的时间间隔就可以看出，现在Spring Cloud即将发布2.0版本，到了后期会更加完善和稳定。&lt;/p&gt;    &lt;p&gt;3) 从整个大的平台架构来讲，dubbo框架只是专注于服务之间的治理，如果我们需要使用配置中心、分布式跟踪这些内容都需要自己去集成，这样无形中使用dubbo的难度就会增加。Spring Cloud几乎考虑了服务治理的方方面面，更有Spring Boot这个大将的支持，开发起来非常的便利和简单。&lt;/p&gt;    &lt;p&gt;4）从技术发展的角度来讲，Dubbo刚出来的那会技术理念还是非常先进，解决了各大互联网公司服务治理的问题，中国的各中小公司也从中受益不少。经过了这么多年的发展，互联网行业也是涌现了更多先进的技术和理念，Dubbo一直停滞不前，自然有些掉队，有时候我个人也会感到有点可惜，如果Dubbo一直沿着当初的那个路线发展，并且延伸到周边，今天可能又是另一番景象了。&lt;/p&gt;    &lt;p&gt;Spring 推出Spring Boot/Cloud也是因为自身的很多原因。Spring最初推崇的轻量级框架，随着不断的发展也越来越庞大，随着集成项目越来越多，配置文件也越来越混乱，慢慢的背离最初的理念。随着这么多年的发展，微服务、分布式链路跟踪等更多新的技术理念的出现，Spring急需一款框架来改善以前的开发模式，因此才会出现Spring Boot/Cloud项目，我们现在访问Spring官网，会发现Spring Boot和Spring Cloud已经放到首页最重点突出的三个项目中的前两个，可见Spring对这两个框架的重视程度。&lt;/p&gt;    &lt;p&gt;总结一下，dubbo曾经确实很牛逼，但是Spring Cloud是站在近些年技术发展之上进行开发，因此更具技术代表性。&lt;/p&gt;    &lt;h3&gt;如何进行微服务架构演进&lt;/h3&gt;    &lt;p&gt;当我们将所有的新业务都使用Spring Cloud这套架构之后，就会出现这样一个现象，公司的系统被分成了两部分，一部分是传统架构的项目，一部分是微服务架构的项目，如何让这两套配合起来使用就成为了关键，这时候Spring Cloud里面的一个关键组件解决了我们的问题，就是Zuul。在Spring Cloud架构体系内的所有微服务都通过Zuul来对外提供统一的访问入口，所有需要和微服务架构内部服务进行通讯的请求都走统一网关。如下图：&lt;/p&gt;    &lt;p&gt;      &lt;img alt="" src="https://img-blog.csdn.net/20180207215515549?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvdzA1OTgwNTk4/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast"&gt;&lt;/img&gt;      &lt;br /&gt;&lt;/p&gt;    &lt;p&gt;从上图可以看出我们对服务进行了分类，有四种：基础服务、业务服务、组合服务、前置服务。不同服务迁移的优先级不同&lt;/p&gt;    &lt;ul&gt;      &lt;li&gt;        &lt;p&gt;基础服务，是一些基础组件，与具体的业务无关。比如：短信服务、邮件服务。这里的服务最容易摘出来做微服务，也是我们第一优先级分离出来的服务。&lt;/p&gt;&lt;/li&gt;      &lt;li&gt;        &lt;p&gt;业务服务，是一些垂直的业务系统，只处理单一的业务类型，比如：风控系统、积分系统、合同系统。这类服务职责比较单一，根据业务情况来选择是否迁移，比如：如果突然有需求对积分系统进行大优化，我们就趁机将积分系统进行改造，是我们的第二优先级分离出来的服务。&lt;/p&gt;&lt;/li&gt;      &lt;li&gt;        &lt;p&gt;前置服务，前置服务一般为服务的接入或者输出服务，比如网站的前端服务、app的服务接口这类，这是我们第三优先级分离出来的服务。&lt;/p&gt;&lt;/li&gt;      &lt;li&gt;        &lt;p&gt;组合服务，组合服务就是涉及到了具体的业务，比如买标过程，需要调用很多垂直的业务服务，这类的服务我们一般放到最后再进行微服务化架构来改造，因为这类服务最为复杂，除非涉及到大的业务逻辑变更，我们是不会轻易进行迁移。&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;    &lt;p&gt;在这四类服务之外，新上线的业务全部使用Sprng Boot/Cloud这套技术栈。就这样，我们从开源项目云收藏开始，上线几个Spring Boot项目，到现在公司绝大部分的项目都是在Spring Cloud这个架构体系中。&lt;/p&gt;    &lt;h2&gt;经验和教训&lt;/h2&gt;    &lt;h3&gt;架构演化的步骤&lt;/h3&gt;    &lt;ul&gt;      &lt;li&gt;        &lt;p&gt;在确定使用Spring Boot/Cloud这套技术栈进行微服务改造之前，先梳理平台的服务，对不同的服务进行分类，以确认演化的节奏。&lt;/p&gt;&lt;/li&gt;      &lt;li&gt;        &lt;p&gt;先让团队熟悉Spring Boot技术，并且优先在基础服务上进行技术改造，推动改动后的项目投产上线&lt;/p&gt;&lt;/li&gt;      &lt;li&gt;        &lt;p&gt;当团队熟悉Spring Boot之后，再推进使用Spring Cloud对原有的项目进行改造。&lt;/p&gt;&lt;/li&gt;      &lt;li&gt;        &lt;p&gt;在进行微服务改造过程中，优先应用于新业务系统，前期可以只是少量的项目进行了微服务化改造，随着大家对技术的熟悉度增加，可以加快加大微服务改造的范围&lt;/p&gt;&lt;/li&gt;      &lt;li&gt;        &lt;p&gt;传统项目和微服务项目共存是一个很常见的情况，除非公司业务有大的变化，不建议直接迁移核心项目。&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;    &lt;h3&gt;服务拆分原则&lt;/h3&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;h3&gt;微服务vs传统开发&lt;/h3&gt;    &lt;p&gt;使用微服务有一段时间了，这种开发模式和传统的开发模式对比，有很大的不同。&lt;/p&gt;    &lt;ul&gt;      &lt;li&gt;        &lt;p&gt;分工不同，以前我们可能是一个一个模块，现在可能是一人一个系统。&lt;/p&gt;&lt;/li&gt;      &lt;li&gt;        &lt;p&gt;架构不同，服务的拆分是一个技术含量很高的问题，拆分是否合理对以后发展影响巨大。&lt;/p&gt;&lt;/li&gt;      &lt;li&gt;        &lt;p&gt;部署方式不同，如果还像以前一样部署估计累死了，自动化运维不可不上。&lt;/p&gt;&lt;/li&gt;      &lt;li&gt;        &lt;p&gt;容灾不同，好的微服务可以隔离故障避免服务整体down掉，坏的微服务设计仍然可以因为一个子服务出现问题导致连锁反应。&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;    &lt;h3&gt;给数据库带来的挑战&lt;/h3&gt;    &lt;p&gt;每个微服务都有自己独立的数据库，那么后台管理的联合查询怎么处理？这应该是大家会普遍遇到的一个问题，有三种处理方案。&lt;/p&gt;    &lt;p&gt;1）严格按照微服务的划分来做，微服务相互独立，各微服务数据库也独立，后台需要展示数据时，调用各微服务的接口来获取对应的数据，再进行数据处理后展示出来，这是标准的用法，也是最麻烦的用法。&lt;/p&gt;    &lt;p&gt;2) 将业务高度相关的表放到一个库中，将业务关系不是很紧密的表严格按照微服务模式来拆分，这样既可以使用微服务，也避免了数据库分散导致后台系统统计功能难以实现，是一个折中的方案。&lt;/p&gt;    &lt;p&gt;3）数据库严格按照微服务的要求来切分，以满足业务高并发，实时或者准实时将各微服务数据库数据同步到NoSQL数据库中，在同步的过程中进行数据清洗，用来满足后台业务系统的使用，推荐使用MongoDB、HBase等。&lt;/p&gt;    &lt;p&gt;三种方案在不同的公司我都使用过，第一种方案适合业务较为简单的小公司；第二种方案，适合在原有系统之上，慢慢演化为微服务架构的公司；第三种适合大型高并发的互联网公司。&lt;/p&gt;    &lt;h3&gt;微服务的经验和建议&lt;/h3&gt;    &lt;p&gt;1、建议尽量不要使用Jsp，页面开发推荐使用Thymeleaf。Web项目建议独立部署Tomcat，不要使用内嵌的Tomcat，内嵌Tomcat部署Jsp项目会偶现龟速访问的情况。&lt;/p&gt;    &lt;p&gt;2、服务编排是个好东西，主要的作用是减少项目中的相互依赖。比如现在有项目a调用项目b，项目b调用项目c...一直到h，是一个调用链，那么项目上线的时候需要先更新最底层的h再更新g...更新c更新b最后是更新项目a。这只是这一个调用链，在复杂的业务中有非常多的调用，如果要记住每一个调用链对开发运维人员来说就是灾难。&lt;/p&gt;    &lt;p&gt;有这样一个好办法可以尽量的减少项目的相互依赖，就是服务编排，一个核心的业务处理项目，负责和各个微服务打交道。比如之前是a调用b，b掉用c，c调用d，现在统一在一个核心项目W中来处理，W服务使用a的时候去调用b，使用b的时候W去调用c，举个例子：在第三方支付业务中，有一个核心支付项目是服务编排，负责处理支付的业务逻辑，W项目使用商户信息的时候就去调用“商户系统”，需要校验设备的时候就去调用“终端系统”，需要风控的时候就调用“风控系统”，各个项目需要的依赖参数都由W来做主控。以后项目部署的时候，只需要最后启动服务编排项目即可。&lt;/p&gt;    &lt;p&gt;3、不要为了追求技术而追求技术，确定进行微服务架构改造之前，需要考虑以下几方面的因素：      &lt;br /&gt;1）团队的技术人员是否已经具备相关技术基础。      &lt;br /&gt;2）公司业务是否适合进行微服务化改造，并不是所有的平台都适合进行微服务化改造，比如：传统行业有很多复杂垂直的业务系统。      &lt;br /&gt;3）Spring Cloud生态的技术有很多，并不是每一种技术方案都需要用上，适合自己的才是最好的。&lt;/p&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 />
      <guid isPermaLink="true">https://itindex.net/detail/58280-spring-cloud-%E5%BE%AE%E6%9C%8D%E5%8A%A1</guid>
      <pubDate>Sat, 21 Apr 2018 07:20:43 CST</pubDate>
    </item>
    <item>
      <title>app端用户信息自动获取--微博</title>
      <link>https://itindex.net/detail/58027-app-%E7%94%A8%E6%88%B7-%E4%BF%A1%E6%81%AF</link>
      <description>&lt;p&gt;  &lt;a href="https://github.com/zgbgx/appWeiboInfoCrawl"&gt;github地址&lt;/a&gt;&lt;/p&gt;
 &lt;h1&gt;项目目的&lt;/h1&gt;
 &lt;p&gt;在app(ios和android)端使用webview组件与js进行交互，串改页面，让用户授权登录后，获取用户关键信息，并完成自动关注一个账号。&lt;/p&gt;
 &lt;h1&gt;传统爬虫模式的局限&lt;/h1&gt;
 &lt;p&gt;传统爬虫模式，让用户在客户端在输入账号密码，然后传送到后端进行登录，爬取信息，这种方式将要面对各种人机验证措施，加密方法复杂的情况下，还得选择selenium，性能更无法保证。同时，对于个人账户，安全措施越来越严，使用代理ip进行操作，很容易造成异地登录等问题，代理ip也很可能在全网被重复使用的情况下，被封杀，频繁的代理ip切换也会带来需要二次登录等问题。  &lt;br /&gt;所以这两年年来，发现市面上越来越多的提供sdk方式的数据提供商，经过抓包及反编译sdk，发现其大多数使用webview载入第三方页面的方式完成登录，有的在登录完成之后，获取cookie传送到后端完成爬取，有的直接在app内完成所需信息的收集。&lt;/p&gt;
 &lt;h1&gt;登录&lt;/h1&gt;
 &lt;p&gt;这是微博移动端登录页  &lt;br /&gt;  &lt;img alt="weibo&amp;#21407;&amp;#31227;&amp;#21160;&amp;#31471;&amp;#30331;&amp;#24405;&amp;#39029;.png" src="http://upload-images.jianshu.io/upload_images/10280397-c258622a77703836.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" title="weibo&amp;#21407;&amp;#31227;&amp;#21160;&amp;#31471;&amp;#30331;&amp;#24405;&amp;#39029;.png"&gt;&lt;/img&gt;  &lt;br /&gt;首先使用JavaScript串改当前页面元素，让用户没法意识到这是微博官方的登录页。&lt;/p&gt;
 &lt;h2&gt;载入页面&lt;/h2&gt;
 &lt;p&gt;android&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;webView.loadUrl(LOGINPAGEURL);&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;iOS&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;[self requestUrl:self.loginPageUrl];
//请求url方法
-(void) requestUrl:(NSString*) urlString{
    NSURL* url=[NSURL URLWithString:urlString];
    NSURLRequest* request=[NSURLRequest requestWithURL:url];
    [self.webView loadRequest:request];
}&lt;/code&gt;&lt;/pre&gt;
 &lt;h2&gt;js代码注入&lt;/h2&gt;
 &lt;p&gt;首先我们注入js代码到app的webview中  &lt;br /&gt;android&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;private void injectScriptFile(String filePath) {
        InputStream input;
        try {
            input = webView.getContext().getAssets().open(filePath);
            byte[] buffer = new byte[input.available()];
            input.read(buffer);
            input.close();
            // String-ify the script byte-array using BASE64 encoding
            String encoded = Base64.encodeToString(buffer, Base64.NO_WRAP);
            String funstr = &amp;quot;javascript:(function() {&amp;quot; +
                    &amp;quot;var parent = document.getElementsByTagName(&amp;apos;head&amp;apos;).item(0);&amp;quot; +
                    &amp;quot;var script = document.createElement(&amp;apos;script&amp;apos;);&amp;quot; +
                    &amp;quot;script.type = &amp;apos;text/javascript&amp;apos;;&amp;quot; +
                    &amp;quot;script.innerHTML = decodeURIComponent(escape(window.atob(&amp;apos;&amp;quot; + encoded + &amp;quot;&amp;apos;)));&amp;quot; +
                    &amp;quot;parent.appendChild(script)&amp;quot; +
                    &amp;quot;})()&amp;quot;;
            execJsNoReturn(funstr);
        } catch (IOException e) {
            Log.e(TAG, &amp;quot;injectScriptFile: &amp;quot; + e);
        }
    }&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;iOS&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;//注入js文件
- (void) injectJsFile:(NSString *)filePath{
    NSString *jsPath = [[NSBundle mainBundle] pathForResource:filePath ofType:@&amp;quot;js&amp;quot; inDirectory:@&amp;quot;assets&amp;quot;];
    NSData *data=[NSData dataWithContentsOfFile:jsPath];
    NSString *responData =  [data base64EncodedStringWithOptions:0];
    NSString *jsStr=[NSString stringWithFormat:@&amp;quot;javascript:(function() {\
                     var parent = document.getElementsByTagName(&amp;apos;head&amp;apos;).item(0);\
                     var script = document.createElement(&amp;apos;script&amp;apos;);\
                     script.type = &amp;apos;text/javascript&amp;apos;;\
                     script.innerHTML = decodeURIComponent(escape(window.atob(&amp;apos;%@&amp;apos;)));\
                     parent.appendChild(script)})()&amp;quot;,responData];
    [self.webView evaluateJavaScript:jsStr completionHandler:^(id _Nullable htmlStr,NSError * _Nullable error){
        
    }];
}&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;我们都采用读取js文件，然后base64编码后，使用window.atob把其做为一个脚本注入到当前页面(注意：window.atob处理中文编码后会得到的编码不正确，需要使用ecodeURIComponent escape来进行正确的校正。)  &lt;br /&gt;在这里已经使用了app端，调用js的方法来创建元素。&lt;/p&gt;
 &lt;h2&gt;app端调用js方法&lt;/h2&gt;
 &lt;p&gt;android端：&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;webView.evaluateJavascript(funcStr, new ValueCallback&amp;lt;String&amp;gt;() {
            @Override
            public void onReceiveValue(String s) {

            }

        });&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;ios端：&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;[self.webView evaluateJavaScript:funcStr completionHandler:^(id _Nullable htmlStr,NSError * _Nullable error){
        
    }];&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;这两个方法可以获取返回值，正因为如此，可以使用js提取页面信息后，返回给webview，然后收集信息完成之后，汇总进行通信。&lt;/p&gt;
 &lt;h1&gt;js串改页面&lt;/h1&gt;
 &lt;pre&gt;  &lt;code&gt;//串改页面元素，让用户以为是授权登录
function getLogin(){
 var topEle=selectNode(&amp;apos;//*[@id=&amp;quot;avatarWrapper&amp;quot;]&amp;apos;);
 var imgEle=selectNode(&amp;apos;//*[@id=&amp;quot;avatarWrapper&amp;quot;]/img&amp;apos;);
 topEle.remove(imgEle);
 var returnEle=selectNode(&amp;apos;//*[@id=&amp;quot;loginWrapper&amp;quot;]/a&amp;apos;);
 returnEle.className=&amp;apos;&amp;apos;;
 returnEle.innerText=&amp;apos;&amp;apos;;
 pEle=selectNode(&amp;apos;//*[@id=&amp;quot;loginWrapper&amp;quot;]/p&amp;apos;);
 pEle.className=&amp;quot;&amp;quot;;
 pEle.innerHTML=&amp;quot;&amp;quot;;
 footerEle=selectNode(&amp;apos;//*[@id=&amp;quot;loginWrapper&amp;quot;]/footer&amp;apos;);
 footerEle.innerHTML=&amp;quot;&amp;quot;;
 var loginNameEle=selectNode(&amp;apos;//*[@id=&amp;quot;loginName&amp;quot;]&amp;apos;);
 loginNameEle.placeholder=&amp;quot;请输入用户名&amp;quot;;
 var buttonEle=selectNode(&amp;apos;//*[@id=&amp;quot;loginAction&amp;quot;]&amp;apos;);
 buttonEle.innerText=&amp;quot;请进行用户授权&amp;quot;;
 selectNode(&amp;apos;//*[@id=&amp;quot;loginWrapper&amp;quot;]/form/section/div[1]/i&amp;apos;).className=&amp;quot;&amp;quot;;
 selectNode(&amp;apos;//*[@id=&amp;quot;loginWrapper&amp;quot;]/form/section/div[2]/i&amp;apos;).className=&amp;quot;&amp;quot;;
 selectNode(&amp;apos;//*[@id=&amp;quot;loginAction&amp;quot;]&amp;apos;).className=&amp;quot;btn&amp;quot;;
 selectNode(&amp;apos;//a[@id=&amp;quot;loginAction&amp;quot;]&amp;apos;).addEventListener(&amp;apos;click&amp;apos;,transPortUnAndPw,false);
 return window.webkit;
}
function transPortUnAndPw(){
 username=selectNode(&amp;apos;//*[@id=&amp;quot;loginName&amp;quot;]&amp;apos;).value;
 pwd=selectNode(&amp;apos;//*[@id=&amp;quot;loginPassword&amp;quot;]&amp;apos;).value;
 window.webkit.messageHandlers.getInfo({body:JSON.stringify({&amp;quot;username&amp;quot;:username,&amp;quot;pwd&amp;quot;:pwd})});
}&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;使用js修改页面元素，使之看起来不会让人发觉这是weibo官方的页面。  &lt;br /&gt;修改后的页面如图：  &lt;br /&gt;  &lt;img alt="&amp;#20462;&amp;#25913;&amp;#21518;&amp;#30331;&amp;#24405;&amp;#39029;&amp;#38754;.png" src="http://upload-images.jianshu.io/upload_images/10280397-c2b2aee8b46a2417.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" title="&amp;#20462;&amp;#25913;&amp;#21518;&amp;#30331;&amp;#24405;&amp;#39029;&amp;#38754;.png"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;h1&gt;串改登录点击事件，获取用户名密码&lt;/h1&gt;
 &lt;pre&gt;  &lt;code&gt;selectNode(&amp;apos;//a[@id=&amp;quot;loginAction&amp;quot;]&amp;apos;).addEventListener(&amp;apos;click&amp;apos;,transPortUnAndPw,false);
function transPortUnAndPw(){
  username=selectNode(&amp;apos;//*[@id=&amp;quot;loginName&amp;quot;]&amp;apos;).value;
  pwd=selectNode(&amp;apos;//*[@id=&amp;quot;loginPassword&amp;quot;]&amp;apos;).value;
  window.webkit.messageHandlers.getInfo({body:JSON.stringify({&amp;quot;username&amp;quot;:username,&amp;quot;pwd&amp;quot;:pwd})});
}&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;同时串改登录点击按钮，通过js调用app webview的方法，把用户名和密码传递给app webview 完成信息收集。&lt;/p&gt;
 &lt;h2&gt;js调用webview的方法&lt;/h2&gt;
 &lt;p&gt;android端：&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;// js代码
window.weibo.getPwd(JSON.stringify({&amp;quot;username&amp;quot;:username,&amp;quot;pwd&amp;quot;:pwd}));
//Java代码
webView.addJavascriptInterface(new WeiboJsInterface(), &amp;quot;weibo&amp;quot;);
public class WeiboJsInterface {
        @JavascriptInterface
        public void getPwd(String returnValue) {
            try {
                unpwDict = new JSONObject(returnValue);
            } catch (JSONException e) {
                e.printStackTrace();
            }
        }
    }&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;android通过实现一个@JavaScriptInterface接口，把这个方法添加类添加到webview的浏览器内核之上，当调用这个方法时，会触发android端的调用。  &lt;br /&gt;ios端：&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;//js代码
window.webkit.messageHandlers.getInfo({body:JSON.stringify({&amp;quot;username&amp;quot;:username,&amp;quot;pwd&amp;quot;:pwd})});
//oc代码
WKUserContentController *userContentController = [[WKUserContentController alloc] init];
 [userContentController addScriptMessageHandler:self name:@&amp;quot;getInfo&amp;quot;];

- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message
{
    
    self.unpwDict=[self getReturnDict:message.body];
}&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;ios方式，实现方式与此类似，不过由于我对oc以及ios开发不熟悉，代码运行不符合期望,希望专业的能指正。&lt;/p&gt;
 &lt;h1&gt;个人信息获取&lt;/h1&gt;
 &lt;h2&gt;直接提取页面的难点&lt;/h2&gt;
 &lt;p&gt;webview这个组件，无论是在android端 onPageFinished方法还是ios端的didFinishNavigation方法，都无法正确判定页面是否加载完全。所以对于很多页面，还是选择走接口&lt;/p&gt;
 &lt;h1&gt;请求接口&lt;/h1&gt;
 &lt;p&gt;本项目中，获取用户自己的微博，关注，和分析，都是使用接口，拿到预览页，直接解析数，对于关键的参数，需要仔细抓包获取  &lt;br /&gt;  &lt;img alt="&amp;#25235;&amp;#21253;1.png" src="http://upload-images.jianshu.io/upload_images/10280397-b14b37bd91c4ab4c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" title="&amp;#25235;&amp;#21253;1.png"&gt;&lt;/img&gt;  &lt;br /&gt;仔细分析 “我”这个标签下的请求情况，发现  &lt;a href="https://m.weibo.cn/home/me?format=cards%E8%BF%99%E4%B8%AA%E9%93%BE%E6%8E%A5%E5%8C%85%E5%90%AB%E7%94%A8%E6%88%B7%E6%A0%B8%E5%BF%83%E6%95%B0%E6%8D%AE"&gt;https://m.weibo.cn/home/me?fo...&lt;/a&gt;，通过这个请求，获取核心参数，然后，获取用户的微博 关注 粉丝的预览页面。  &lt;br /&gt;然后通过&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;JSON.stringify(JSON.parse(document.getElementsByTagName(&amp;apos;pre&amp;apos;)[0].innerText))&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;获取json字符串，并传到app端进行解析。  &lt;br /&gt;解析及多次请求的逻辑&lt;/p&gt;
 &lt;h1&gt;请求页面&lt;/h1&gt;
 &lt;p&gt;也有页面，如个人资料，页面较简单，可以使用js提取&lt;/p&gt;
 &lt;h3&gt;js代码&lt;/h3&gt;
 &lt;pre&gt;  &lt;code&gt;function getPersonInfo(){
  var name=selectNodeText(&amp;apos;//*[@id=&amp;quot;J_name&amp;quot;]&amp;apos;);
  var sex=selectNodeText(&amp;apos;/*[@id=&amp;quot;sex&amp;quot;]/option[@selected]&amp;apos;);
  var location=selectNodeText(&amp;apos;//*[@id=&amp;quot;J_location&amp;quot;]&amp;apos;);
  var year=selectNodeText(&amp;apos;//*[@id=&amp;quot;year&amp;quot;]/option[@selected]&amp;apos;);
  var month=selectNodeText(&amp;apos;//*[@id=&amp;quot;month&amp;quot;]/option[@selected]&amp;apos;);
  var day=selectNodeText(&amp;apos;//*[@id=&amp;quot;day&amp;quot;]/option[@selected]&amp;apos;);
  var email=selectNodeText(&amp;apos;//*[@id=&amp;quot;J_email&amp;quot;]&amp;apos;);
  var blog=selectNodeText(&amp;apos;//*[@id=&amp;quot;J_blog&amp;quot;]&amp;apos;);
  if(blog==&amp;apos;输入博客地址&amp;apos;){
    blog=&amp;apos;未填写&amp;apos;;
  }
  var qq=selectNodeText(&amp;apos;//*[@id=&amp;quot;J_QQ&amp;quot;]&amp;apos;);
  if(qq==&amp;apos;QQ帐号&amp;apos;){
    qq=&amp;quot;未填写&amp;quot;;
  }
  birthday=year+&amp;apos;-&amp;apos;+month+&amp;apos;-&amp;apos;+day;
  theDict={&amp;apos;name&amp;apos;:name,&amp;apos;sex&amp;apos;:sex,&amp;apos;localtion&amp;apos;:location,&amp;apos;birthday&amp;apos;:birthday,&amp;apos;email&amp;apos;:email,&amp;apos;blog&amp;apos;:blog,&amp;apos;qq&amp;apos;:qq};
  return JSON.stringify({&amp;apos;personInfomation&amp;apos;:theDict});
}&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;由于webview不支持 $x 的xpath写法，为了方便，使用原生的XPathEvaluator, 实现了特定的提取。&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;function selectNodes(sXPath) {
  var evaluator = new XPathEvaluator();
  var result = evaluator.evaluate(sXPath, document, null, XPathResult.ANY_TYPE, null);
  if (result != null) {
    var nodeArray = [];
    var nodes = result.iterateNext();
    while (nodes) {
      nodeArray.push(nodes);
      nodes = result.iterateNext();
    }
    return nodeArray;
  }
  return null;
};
//选取子节点
function selectChildNode(sXPath, element) {
  var evaluator = new XPathEvaluator();
  var newResult = evaluator.evaluate(sXPath, element, null, XPathResult.ANY_TYPE, null);
  if (newResult != null) {
    var newNode = newResult.iterateNext();
    return newNode;
  }
}

function selectChildNodeText(sXPath, element) {
  var evaluator = new XPathEvaluator();
  var newResult = evaluator.evaluate(sXPath, element, null, XPathResult.ANY_TYPE, null);
  if (newResult != null) {
    var newNode = newResult.iterateNext();
    if (newNode != null) {
      return newNode.textContent.replace(/(^\s*)|(\s*$)/g, &amp;quot;&amp;quot;); ;
    } else {
      return &amp;quot;&amp;quot;;
    }
  }
}

function selectChildNodes(sXPath, element) {
  var evaluator = new XPathEvaluator();
  var newResult = evaluator.evaluate(sXPath, element, null, XPathResult.ANY_TYPE, null);
  if (newResult != null) {
    var nodeArray = [];
    var newNode = newResult.iterateNext();
    while (newNode) {
      nodeArray.push(newNode);
      newNode = newResult.iterateNext();
    }
    return nodeArray;
  }
}

function selectNodeText(sXPath) {
  var evaluator = new XPathEvaluator();
  var newResult = evaluator.evaluate(sXPath, document, null, XPathResult.ANY_TYPE, null);
  if (newResult != null) {
    var newNode = newResult.iterateNext();
    if (newNode) {
      return newNode.textContent.replace(/(^\s*)|(\s*$)/g, &amp;quot;&amp;quot;); ;
    }
    return &amp;quot;&amp;quot;;
  }
}
function selectNode(sXPath) {
  var evaluator = new XPathEvaluator();
  var newResult = evaluator.evaluate(sXPath, document, null, XPathResult.ANY_TYPE, null);
  if (newResult != null) {
    var newNode = newResult.iterateNext();
    if (newNode) {
      return newNode;
    }
    return null;
  }
}&lt;/code&gt;&lt;/pre&gt;
 &lt;h1&gt;自动关注用户&lt;/h1&gt;
 &lt;p&gt;由于个人微博页面 onPageFinished与didFinishNavigation这两个方法无法判定页面是否加载完全，  &lt;br /&gt;为了解决这个问题，在android端，使用拦截url，判定页面加载图片的数量来确定，是否，加载完全&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;//由于页面的正确加载onPageFinieshed和onProgressChanged都不能正确判定，所以选择在加载多张图片后，判定页面加载完成。
            //在这样的情况下，自动点击元素，完成自动关注用户。
            @Override
            public void onLoadResource(WebView view, String url) {
                if (webView.getUrl().contains(AUTOFOCUSURL) &amp;amp;&amp;amp; url.contains(&amp;quot;jpg&amp;quot;)) {
                    newIndex++;
                    if (newIndex == 5) {
                        webView.post(new Runnable() {
                            @Override
                            public void run() {
                                injectJsUseXpath(&amp;quot;autoFocus.js&amp;quot;);
                                execJsNoReturn(&amp;quot;autoFocus();&amp;quot;);
                            }
                        });
                    }
                }
                super.onLoadResource(view, url);
            }&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;js 自动点击&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;function autoFocus(){
  selectNode(&amp;apos;//span[@class=&amp;quot;m-add-box&amp;quot;]&amp;apos;).click();
}&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;在ios端，使用访问接口的方式  &lt;br /&gt;  &lt;img alt="&amp;#25235;&amp;#21253;2.png" src="http://upload-images.jianshu.io/upload_images/10280397-d3ed5bcb8c443191.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" title="&amp;#25235;&amp;#21253;2.png"&gt;&lt;/img&gt;  &lt;br /&gt;除了目标用户的id外，还有一个st字符串，通过chrome的search，定位，然后通过js提取&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;function getSt(){
  return config[&amp;apos;st&amp;apos;];
}&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;然后构造post，请求，完成关注&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;- (void) autoFocus:(NSString*) st{
    //Wkwebview采用js模拟完成表单提交
    NSString *jsStr=[NSString stringWithFormat:@&amp;quot;function post(path, params) {var method = \&amp;quot;post\&amp;quot;; \
                     var form = document.createElement(\&amp;quot;form\&amp;quot;); \
                     form.setAttribute(\&amp;quot;method\&amp;quot;, method); \
                     form.setAttribute(\&amp;quot;action\&amp;quot;, path); \
                     for(var key in params) { \
                     if(params.hasOwnProperty(key)) { \
                     var hiddenField = document.createElement(\&amp;quot;input\&amp;quot;);\
                     hiddenField.setAttribute(\&amp;quot;type\&amp;quot;, \&amp;quot;hidden\&amp;quot;);\
                     hiddenField.setAttribute(\&amp;quot;name\&amp;quot;, key);\
                     hiddenField.setAttribute(\&amp;quot;value\&amp;quot;, params[key]);\
                     form.appendChild(hiddenField);\
                     }\
                     }\
                     document.body.appendChild(form);\
                     form.submit();\
                     }\
                     post(&amp;apos;https://m.weibo.cn/api/friendships/create&amp;apos;,{&amp;apos;uid&amp;apos;:&amp;apos;1195242865&amp;apos;,&amp;apos;st&amp;apos;:&amp;apos;%@&amp;apos;});&amp;quot;,st];
    [self execJsNoReturn:jsStr];
}&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;ios WkWebview没有post请求，接口，所以构造一个表单提交，完成post请求。  &lt;br /&gt;完成，一个自动关注，当然，构造一个用户id的列表，很简单就可以实现自动关注多个用户。&lt;/p&gt;
 &lt;h1&gt;关于cookie&lt;/h1&gt;
 &lt;p&gt;如果需要爬取的数据量大，可以选择爬取少量关键信息后，把cookie传到后端处理  &lt;br /&gt;android 端 cookie处理&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;CookieSyncManager.createInstance(context);  
CookieManager cookieManager = CookieManager.getInstance(); &lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;通过cookieManage对象可以获取cookie字符串，传送到后端，继续爬取&lt;/p&gt;
 &lt;p&gt;ios端cookie处理&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;NSDictionary *cookie = [AppInfo shareAppInfo].userModel.cookies;&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;处理方式与android端类似。&lt;/p&gt;
 &lt;h1&gt;总结&lt;/h1&gt;
 &lt;p&gt;对于数据工程师来说，webview有点类似于selenium，但是运行在服务端的selenium，有太多的局限性。webview的在客户端运行，就像一个用户就是一台肉机。  &lt;br /&gt;以webview为基础，使用app收集信息加以利用，现阶段大多数人都还没意识到，但是，市场上的产品已经越来越多，特别是那些对数据有特殊需要的各种金融机构。  &lt;br /&gt;对于普通用户来说，不要轻易在一个app上登录第三方账户，信息泄露，财产损失，在按下登录或者本例中的假装授权后，都是不可避免的。&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>安全 大数据 网页爬虫 ios android</category>
      <guid isPermaLink="true">https://itindex.net/detail/58027-app-%E7%94%A8%E6%88%B7-%E4%BF%A1%E6%81%AF</guid>
      <pubDate>Wed, 07 Feb 2018 14:24:39 CST</pubDate>
    </item>
    <item>
      <title>[原]机器学习在热门微博推荐系统的应用</title>
      <link>https://itindex.net/detail/57975-%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0-%E5%BE%AE%E5%8D%9A-%E6%8E%A8%E8%8D%90%E7%B3%BB%E7%BB%9F</link>
      <description>&lt;p&gt;近年来，机器学习在搜索、广告、推荐等领域取得了非常突出的成果，成为最引人注目的技术热点之一。微博也在机器学习方面做了广泛的探索，其中在推荐领域，将机器学习技术应用于微博最主要的产品之一——热门微博，并取得了显著的效果提升。&lt;/p&gt;



 &lt;h3&gt;热门微博推荐系统介绍&lt;/h3&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;ul&gt;
  &lt;li&gt;大规模：需要处理微博上的海量用户和海量内容；&lt;/li&gt;
  &lt;li&gt;时效性：微博内容的生产周期短，变化较快。&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;p&gt;整体的推荐技术框架如图1。&lt;/p&gt;

 &lt;p&gt;&lt;/p&gt; &lt;center&gt;  &lt;img alt="&amp;#22270;&amp;#29255;&amp;#25551;&amp;#36848;" src="http://img.blog.csdn.net/20180124190757561" title=""&gt;&lt;/img&gt;&lt;/center&gt;  &lt;br /&gt;
 &lt;center&gt;图1 热门微博推荐技术框架&lt;/center&gt; &lt;p&gt;&lt;/p&gt;

 &lt;p&gt;基础层：分为内容建模和用户建模两部分。内容建模主要是微博内容的语义识别，包括主题模型、实体词识别、文本分类和图片分类。用户建模对用户建立完整的画像，包括用户自然属性（性别/年龄）、用户兴趣、用户聚类和用户之间的关系（亲密度等）。&lt;/p&gt;

 &lt;p&gt;推荐层：我们通过用户行为、微博内容等进行实时判断，通过多个召回算法获取不同候选集。再对召回的候选集进行融合。具体的召回算法如下：&lt;/p&gt;

 &lt;ul&gt;
  &lt;li&gt;User-based协同推荐：找出与当前User X最相似的N个User，并根据N个User对某Item的打分估计X对该Item的打分。&lt;/li&gt;
  &lt;li&gt;Item-based协同推荐：我们计算不同mid的共现概率，取出满足一定阈值且排在top的mid作为协同mid的候选。&lt;/li&gt;
  &lt;li&gt;Content-based推荐：通过自然语言处理、图像识别等算法，对微博文本、图片、视频等内容打标签；通过用户行为和微博内容标签，挖掘用户的兴趣标签。基于内容标签和兴趣标签的匹配，提供基于内容的推荐候选。&lt;/li&gt;
&lt;/ul&gt;

 &lt;p&gt;排序层：每类召回策略都会召回一定的候选微博，这些候选微博去重后需要统一做排序。排序使用的模型包括逻辑回归、GBDT/FM、DNN等。排序框架大致可以分为三部分：&lt;/p&gt;

 &lt;ul&gt;
  &lt;li&gt;特征工程：特征的预处理、离散化、归一化、特征组合等，生成训练模型需要的样本数据。&lt;/li&gt;
  &lt;li&gt;模型工具：基于样本数据，使用不同的模型做训练、评估，生成模型训练结果。&lt;/li&gt;
  &lt;li&gt;排序引擎：在线模型LOAD，提取出相对应的特征并且做特征映射，并利用机器学习排序算法，对多策略召回的推荐候选进行融合和打分重排。&lt;/li&gt;
&lt;/ul&gt;



 &lt;h3&gt;热门微博的机器学习推荐&lt;/h3&gt;

 &lt;p&gt;协同过滤推荐是目前业界常用的推荐算法之一。协同过滤推荐是利用users和items的关系矩阵来对user和item进行建模，从而进行推荐的一类算法。其主要分为两种：基于user的协同过滤推荐和基于item的协同过滤推荐。在热门微博业务场景下，一个item是指一条微博。下面介绍基于用户的协同过滤推荐和基于微博的协同过滤推荐两方面的实践。&lt;/p&gt;

 &lt;p&gt;&lt;/p&gt; &lt;center&gt;  &lt;img alt="&amp;#22270;&amp;#29255;&amp;#25551;&amp;#36848;" src="http://img.blog.csdn.net/20180124191240039" title=""&gt;&lt;/img&gt;&lt;/center&gt;  &lt;br /&gt;
 &lt;center&gt;表1 User－Item关系矩阵&lt;/center&gt; &lt;p&gt;&lt;/p&gt;

 &lt;p&gt;  &lt;strong&gt;大规模user-based协同推荐&lt;/strong&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;获取该用户群在历史一段时间内喜好的若干微博作为候选；&lt;/li&gt;
  &lt;li&gt;计算该用户群对各个候选微博的喜好程度；&lt;/li&gt;
  &lt;li&gt;将喜好程度最高的N条微博推荐给当前用户。&lt;/li&gt;
&lt;/ul&gt;

 &lt;p&gt;上述步骤中，最关键的是a。用户的相似度刻画，直接影响推荐的准确度；用户的相似用户群的规模，直接影响推荐的个性化程度。相似用户群的方案有很多，常见的有聚类、K近邻。它们的优劣对比如下。&lt;/p&gt;

 &lt;p&gt;&lt;/p&gt; &lt;center&gt;  &lt;img alt="&amp;#22270;&amp;#29255;&amp;#25551;&amp;#36848;" src="http://img.blog.csdn.net/20180124191404297" title=""&gt;&lt;/img&gt;&lt;/center&gt;  &lt;br /&gt;
 &lt;center&gt;表2 聚类、K近邻方案对比&lt;/center&gt; &lt;p&gt;&lt;/p&gt;

 &lt;p&gt;最终，根据我们的业务场景，选择了聚类方案。鉴于业务的特性，我们还要对聚类结果有额外的要求：每个类别内包含的优质用户数量要尽量相近。我们的解决方案是只用优质用户做训练同时保证聚类均匀，全部用户做预测。所以接下来要解决的问题是选择聚类算法、用户的向量表征、控制聚类均匀。&lt;/p&gt;

 &lt;p&gt;尽管聚类算法有很多，但它们依然基本上都还是在K-Means算法的框架下，因此我们直接选用K-Means算法。关于用数学向量表示用户。值得注意的是，当解决实际聚类问题时，一般情况下，问题对象的向量表征比聚类算法本身对最终效果影响更大。&lt;/p&gt;

 &lt;p&gt;首先，我们考虑直接用关系矩阵的行向量作为用户的向量表示。在微博推荐的场景下，item的数量是快速增长的，因此只能使用历史上一段时间内的用户-微博关系矩阵。同时，矩阵是集群稀疏的，当我们用较短历史数据训练聚类时，效果表现不好。所以，我们尽可能拉长历史来保证用户向量中包含充足的信息，然而，K-Means对高维数据的训练效率极低。我们尽量平衡训练效率和聚类效果，但效果很差，各个类别规模极其不均匀，不能满足需求。&lt;/p&gt;

 &lt;p&gt;所以，我们考虑了三个降维方案：LDA、Word2Vec、Doc2Vec。&lt;/p&gt;

 &lt;ul&gt;
  &lt;li&gt;LDA：虽然LDA训练出来的主题分布可以作为特征向量，但是LDA本身不强调向量间距离的概念，可与后面K-Means算法的训练过程不相匹配，所以效果不佳，淘汰。&lt;/li&gt;
  &lt;li&gt;Word2Vec：强调向量间的距离，适合K-means。但是当使用Word2Vec时我们要微博ID当成句子ID，微博的阅读者序列作为句子内容，用户ID作为词。按照微博的特性，这么处理的话，语料里“句子”长度的分布会非常不均匀。所以最终也没有选用。&lt;/li&gt;
  &lt;li&gt;Doc2Vec：强调向量间的距离，适合K-means。把用户ID当成句子ID、用户的阅读序列作为句子内容，微博ID作为词进行训练时，语料里“句子”长度的分布会均匀很多，效果较好。&lt;/li&gt;
&lt;/ul&gt;

 &lt;p&gt;所以最终选择了Doc2Vec对用户向量进行降维。然后使用低维向量进行聚类，结果明显改善，类别规模变得很均匀，符合我们的需求。&lt;/p&gt;

 &lt;p&gt;在线部分，在线部分只需要记录几小时内每个聚类下的用户群体对各个微博的行为，经过简单的加权计算、排序、取Top。当为某用户推荐时，只需查到相应的聚类ID对应的推荐列表。在线计算开销极小。&lt;/p&gt;

 &lt;p&gt;  &lt;strong&gt;大规模item-based协同推荐&lt;/strong&gt;&lt;/p&gt;

 &lt;p&gt;基于微博的协同过滤推荐的基本原理是：如果看了微博A的用户很大比例都去看了微博B，那么应该给只看了微博A的用户推荐微博B。这个原理的实现就是计算任意两个微博的相关性。关键点时设计相关性公式。我们迭代了三个版本的相关性公式。&lt;/p&gt;

 &lt;p&gt;第一版，我们将相关性抽象为：&lt;/p&gt;

 &lt;p&gt;&lt;/p&gt; &lt;center&gt;  &lt;img alt="&amp;#22270;&amp;#29255;&amp;#25551;&amp;#36848;" src="http://img.blog.csdn.net/20180124192700550" title=""&gt;&lt;/img&gt;&lt;/center&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;center&gt;  &lt;img alt="&amp;#22270;&amp;#29255;&amp;#25551;&amp;#36848;" src="http://img.blog.csdn.net/20180124193008518" title=""&gt;&lt;/img&gt;&lt;/center&gt; &lt;p&gt;&lt;/p&gt;

 &lt;p&gt;在公式中我们加入了变量expo（B），表示B在用户的页面里曝光了。按新公式实现后，召回大幅度提升。&lt;/p&gt;

 &lt;p&gt;第三版，我们试图解决关系矩阵稀疏的问题。在微博场景中，很多微博是相似的，但是它们拥有不同的微博ID。这会天然地造成矩阵稀疏，从而相关性计算不准确。举个例子：&lt;/p&gt;

 &lt;p&gt;&lt;/p&gt; &lt;center&gt;  &lt;img alt="&amp;#22270;&amp;#29255;&amp;#25551;&amp;#36848;" src="http://img.blog.csdn.net/20180124193119685" title=""&gt;&lt;/img&gt;&lt;/center&gt; &lt;p&gt;&lt;/p&gt;

 &lt;p&gt;假设B  &lt;sub&gt;i&lt;/sub&gt;和B  &lt;sub&gt;j&lt;/sub&gt;是描述的同一个内容，且R  &lt;sub&gt;ABi&lt;/sub&gt;和R  &lt;sub&gt;ABj&lt;/sub&gt;都略低于阈值，那么B  &lt;sub&gt;i&lt;/sub&gt;和B  &lt;sub&gt;j&lt;/sub&gt;是不能作为A的协同推荐微博的，这显然是不合理的。&lt;/p&gt;

 &lt;p&gt;为了解决这个问题，我们改进了算法。首先将相似微博B  &lt;sub&gt;i&lt;/sub&gt;和B  &lt;sub&gt;j&lt;/sub&gt;聚合成B，然后计算相关性。流程如下：&lt;/p&gt;

 &lt;p&gt;&lt;/p&gt; &lt;center&gt;  &lt;img alt="&amp;#22270;&amp;#29255;&amp;#25551;&amp;#36848;" src="http://img.blog.csdn.net/20180124193503068" title=""&gt;&lt;/img&gt;&lt;/center&gt; &lt;p&gt;&lt;/p&gt;

 &lt;p&gt;改进后，覆盖率有得到了进一步的提升。&lt;/p&gt;



 &lt;h3&gt;热门微博的机器学习排序&lt;/h3&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;center&gt;  &lt;img alt="&amp;#22270;&amp;#29255;&amp;#25551;&amp;#36848;" src="http://img.blog.csdn.net/20180124193741081" title=""&gt;&lt;/img&gt;&lt;/center&gt;  &lt;br /&gt;
 &lt;center&gt;图2 排序模型的特征空间&lt;/center&gt; &lt;p&gt;&lt;/p&gt;

 &lt;p&gt;如图2所示，排序模型可被认为是建立在物料、用户、环境的三维特征空间。每个维度上，从零点向外的方向代表从具体到泛化。例如，物料轴从零点开始，分别为物料按mid（微博id）、细粒度标签、粗粒度标签、作者、形式划分等。&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;strong&gt;多目标机器学习排序&lt;/strong&gt;&lt;/p&gt;

 &lt;p&gt;通常的ctr预估排序，只以点击率为目标。而热门微博业务会有多个目标，所以需要考虑多目标的排序。实践表明，多个目标之间往往没有很强的正相关关系。因此，如何在排序模型中兼顾多个目标，使得每个目标都有增长，就非常重要。在热门微博的机器学习排序中，我们实验了两种方法：&lt;/p&gt;

 &lt;ul&gt;
  &lt;li&gt;每个目标各自使用一个模型，做模型融合。例如热门微博需要考虑转发、评论和赞，分别训练预估转发率模型、预估评论率、预估赞率的模型，以这三个模型的预测结果，做加权后，做为排序分数。&lt;/li&gt;
&lt;/ul&gt;

 &lt;p&gt;&lt;/p&gt; &lt;center&gt;  &lt;img alt="&amp;#22270;&amp;#29255;&amp;#25551;&amp;#36848;" src="http://img.blog.csdn.net/20180124195647578" title=""&gt;&lt;/img&gt;&lt;/center&gt;  &lt;br /&gt;
 &lt;center&gt;图3 多目标机器学习模型的融合&lt;/center&gt; &lt;p&gt;&lt;/p&gt;

 &lt;p&gt;模型融合后，以提升所有正向行为的概率为总目标，给各个模型分配以不同的权重。该方法的优点在于，分别建模不同的目标，以快速的多组实验来调整权重，以找到权重参数的更优解。缺点在于需要同时训练多个模型，开发成本高。&lt;/p&gt;

 &lt;ul&gt;
  &lt;li&gt;所有目标使用一个模型，在标注正样本时，考虑多个目标。例如对于转发和赞，在标注正样本时，给予不同的权重，使它们综合体现在模型目标中。如表3，每行为一条训练样本，根据业务需要，把转发、评论、赞的权重分别设置为1、2、5。&lt;/li&gt;
&lt;/ul&gt;

 &lt;p&gt;&lt;/p&gt; &lt;center&gt;  &lt;img alt="&amp;#22270;&amp;#29255;&amp;#25551;&amp;#36848;" src="http://img.blog.csdn.net/20180124195733356" title=""&gt;&lt;/img&gt;&lt;/center&gt;  &lt;br /&gt;
 &lt;center&gt;表3 通过样本标注不同权重，一个模型兼顾多个目标&lt;/center&gt; &lt;p&gt;&lt;/p&gt;

 &lt;p&gt;该方法通过对不同正向行为给予不同权重，将多目标问题转化为单目标问题。它的优点在于，一个模型同时兼顾了多个目标，使不同目标的权重体现在损失函数中，参与模型优化求解，便于平衡多个目标的效果。&lt;/p&gt;

 &lt;ul&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;另外，热门微博的分片线性模型结合了多目标的优化。根据不同人群的行为偏好，在分片时设置不同的多目标权重。&lt;/p&gt;

 &lt;p&gt;分片线性模型：&lt;/p&gt;

 &lt;p&gt;&lt;/p&gt; &lt;center&gt;  &lt;img alt="&amp;#22270;&amp;#29255;&amp;#25551;&amp;#36848;" src="http://img.blog.csdn.net/20180124194732325" title=""&gt;&lt;/img&gt;&lt;/center&gt; &lt;p&gt;&lt;/p&gt;

 &lt;p&gt;其中π  &lt;sub&gt;i&lt;/sub&gt;为分片的作用范围，对应分片内为1，其他为0。&lt;/p&gt;

 &lt;p&gt;在热门微博业务中，分片为不同的人群（80 / 90后、男 / 女）。&lt;/p&gt;

 &lt;p&gt;分片的多目标模型（以指数加权为例）：&lt;/p&gt;

 &lt;p&gt;&lt;/p&gt; &lt;center&gt;  &lt;img alt="&amp;#22270;&amp;#29255;&amp;#25551;&amp;#36848;" src="http://img.blog.csdn.net/20180124194859194" title=""&gt;&lt;/img&gt;&lt;/center&gt; &lt;p&gt;&lt;/p&gt;

 &lt;p&gt;其中π  &lt;sub&gt;i&lt;/sub&gt;为分片的作用范围，ω  &lt;sub&gt;ij&lt;/sub&gt;为i个分片内第j个目   &lt;br /&gt;
标模型的参数，δ  &lt;sub&gt;ij&lt;/sub&gt;为第i个分片内第j个目标模型的指数权重。&lt;/p&gt;



 &lt;h3&gt;机器学习效果评估&lt;/h3&gt;

 &lt;p&gt;对于协同过滤推荐，我们设计了一个量度m，来模拟估计上线后实际效果。假设有N+1天的历史行为日志。首先，用1-N天的用户-微博矩阵，为每一个用户计算出第N+1天协同推荐的候选微博集合C。然后将第N＋1天的真实曝光微博集合E与C做交集，得到集合Ec；将第N＋1天的真实点击微博集合A与C做交集，得到集合Ac。最后计算Ac／Ec作为量度。&lt;/p&gt;

 &lt;p&gt;对于排序算法，采用了离线AUC评估和线上的ABTest评估。&lt;/p&gt;

 &lt;p&gt;机器学习应用于热门微博推荐系统后，业务指标和用户体验都得到了显著提高。&lt;/p&gt;



 &lt;h3&gt;总结和展望&lt;/h3&gt;

 &lt;p&gt;我们将机器学习相关技术应用于热门微博业务，并结合业务特色对算法做了进一步的拓展。&lt;/p&gt;

 &lt;p&gt;推荐算法方面，基于用户的协同过滤推荐我们使用user embedding＋Kmeans方案来平衡算法效果、离线计算规模和线上响应速度。基于微博的协同过滤推荐我们升级了两次相关度计算公式，来解决行为稀疏和重复内容的导致的数据稀疏的问题。&lt;/p&gt;

 &lt;p&gt;排序算法方面，大规模特征组合在特征工程实践中总结的一些规律和原则，多目标机器学习排序是为了兼顾多个业务目标而做的尝试和探索，分片线性模型是结合热门微博业务知识完善线性模型的结构和效果。&lt;/p&gt;

 &lt;p&gt;未来推荐和排序算法仍有很大的提升空间，在以下两方面：&lt;/p&gt;

 &lt;ul&gt;
  &lt;li&gt;深度学习和embedding应用于热门微博推荐；&lt;/li&gt;
  &lt;li&gt;海量uid应用于热门微博排序模型，进一步提升模型个性化。&lt;/li&gt;
&lt;/ul&gt;

 &lt;blockquote&gt;
    &lt;p&gt;作者简介：    &lt;br /&gt;
     &lt;strong&gt;侯雷平，微博资深算法工程师&lt;/strong&gt;，主要负责机器学习算法在热门微博个性化排序、个性化推荐等业务中的应用。毕业于南开大学，熟悉推荐系统、广告系统。研究方向是机器学习排序、自然语言处理、个性化推荐等。    &lt;br /&gt;
     &lt;strong&gt;苏传捷，微博算法工程师&lt;/strong&gt;，AI lab项目成员。主要研究方向是推荐系统、自然语言处理。曾负责文本分类、实体识别、用户建模以及特征工程。目前专注于深度学习与增强学习在推荐系统的应用和创新。    &lt;br /&gt;
     &lt;strong&gt;朱红垒，微博兴趣流研发技术负责人&lt;/strong&gt;，算法总监。毕业于哈尔滨工业大学，目前负责热门微博、同城、访客等微博兴趣流业务的技术研发工作。主要技术方向为机器学习、推荐系统、自然语言处理、大数据等。&lt;/p&gt;
&lt;/blockquote&gt;

 &lt;hr&gt;&lt;/hr&gt;

 &lt;p&gt;&lt;/p&gt; &lt;center&gt;  &lt;img alt="&amp;#22270;&amp;#29255;&amp;#25551;&amp;#36848;" src="http://img.blog.csdn.net/20180123143944905" title=""&gt;&lt;/img&gt;&lt;/center&gt; &lt;p&gt;&lt;/p&gt;
                     &lt;div&gt;
                        作者：qq_40027052 发表于2018/1/25 14:41:49   &lt;a href="http://blog.csdn.net/qq_40027052/article/details/79161696"&gt;原文链接&lt;/a&gt;
                    &lt;/div&gt;
                     &lt;div&gt;
                        阅读：441 评论：0   &lt;a href="http://blog.csdn.net/qq_40027052/article/details/79161696#comments" target="_blank"&gt;查看评论&lt;/a&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 />
      <guid isPermaLink="true">https://itindex.net/detail/57975-%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0-%E5%BE%AE%E5%8D%9A-%E6%8E%A8%E8%8D%90%E7%B3%BB%E7%BB%9F</guid>
      <pubDate>Thu, 25 Jan 2018 14:41:49 CST</pubDate>
    </item>
    <item>
      <title>使用shell通过微信公众号发送模板消息 - CSDN博客</title>
      <link>https://itindex.net/detail/57749-shell-%E5%BE%AE%E4%BF%A1-%E5%85%AC%E4%BC%97</link>
      <description>&lt;div&gt;    &lt;p&gt;如下通过shell脚本实现，通过微信公众号发送模板消息到个人微信号。&lt;/p&gt;    &lt;h3&gt;1.配置微信公众号&lt;/h3&gt;    &lt;p&gt;由于没有认证的公众号，只能通过自己申请的个人订阅号（可以自行申请），并到开发者工具中开通公众平台测试帐号实现该功能。      &lt;br /&gt;      &lt;strong&gt;1.获取测试公众号appID和appsecret&lt;/strong&gt;      &lt;br /&gt;      &lt;img alt="&amp;#36825;&amp;#37324;&amp;#20889;&amp;#22270;&amp;#29255;&amp;#25551;&amp;#36848;" src="http://img.blog.csdn.net/20170505224434267?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvemh1X3RpYW53ZWk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" title=""&gt;&lt;/img&gt;      &lt;br /&gt;      &lt;strong&gt;*2.关注测试号二维码获取用户openid&lt;/strong&gt;      &lt;br /&gt;      &lt;img alt="&amp;#36825;&amp;#37324;&amp;#20889;&amp;#22270;&amp;#29255;&amp;#25551;&amp;#36848;" src="http://img.blog.csdn.net/20170505224542576?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvemh1X3RpYW53ZWk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" title=""&gt;&lt;/img&gt;      &lt;br /&gt;      &lt;strong&gt;3.新增模板获取模板ID&lt;/strong&gt;      &lt;br /&gt;      &lt;img alt="&amp;#36825;&amp;#37324;&amp;#20889;&amp;#22270;&amp;#29255;&amp;#25551;&amp;#36848;" src="http://img.blog.csdn.net/20170505224744905?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvemh1X3RpYW53ZWk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" title=""&gt;&lt;/img&gt;      &lt;br /&gt;      &lt;img alt="&amp;#36825;&amp;#37324;&amp;#20889;&amp;#22270;&amp;#29255;&amp;#25551;&amp;#36848;" src="http://img.blog.csdn.net/20170505224756428?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvemh1X3RpYW53ZWk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" title=""&gt;&lt;/img&gt;      &lt;br /&gt;得到模板id： OA0PX8pqc2X7t_y05y5GxZ8LutBpu341FIYSeQOkno&lt;/p&gt;    &lt;h3&gt;2.通过脚本实现消息发送&lt;/h3&gt;    &lt;p&gt;这里就不啰嗦了，直接上shell脚本代码，具体看注释&lt;/p&gt;    &lt;pre&gt;      &lt;code&gt;#!/bin/sh# 微信消息发送脚本 zhutw#全局配置--#微信公众号appIDappID=wx*******0ebde756#微信公众号appsecretappsecret=138********0446e9ae04f2#微信公众号发送消息模板tpl_id=OA0PX8pqc2X7t_-y05y5GxZ8LutBpu341FIYSeQOkno#消息模板：#   {{first.DATA}}#   项目名称：{{name.DATA}}#   报警时间：{{date.DATA}}##   {{remark.DATA}}#获取微信公众号AccessToken，并缓存到本地 函数getAccessToken(){if[-f&amp;quot;$HOME/.wechat_accesstoken&amp;quot;];thenaccess_token=`cat$HOME/.wechat_accesstoken | awk -F&amp;quot;:&amp;quot;&amp;apos;{print $1}&amp;apos;`
        expires_in=`cat$HOME/.wechat_accesstoken | awk -F&amp;quot;:&amp;quot;&amp;apos;{print $2}&amp;apos;`
        time=`cat$HOME/.wechat_accesstoken | awk -F&amp;quot;:&amp;quot;&amp;apos;{print $3}&amp;apos;`if[ -z$access_token] || [ -z$expires_in] || [ -z$time];thenrm-f$HOME/.wechat_accesstoken
            getAccessTokenfielsecontent=$(curl&amp;quot;https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&amp;amp;appid=$appID&amp;amp;secret=$appsecret&amp;quot;)echo&amp;quot;get content:$content&amp;quot;access_token=`echo$content| awk -F&amp;quot;\&amp;quot;&amp;quot;&amp;apos;{print $4}&amp;apos;`
        expires_in=`echo$content| awk -F&amp;quot;\&amp;quot;&amp;quot;&amp;apos;{print $7}&amp;apos;| cut-d&amp;quot;}&amp;quot;-f1|cut -c2-`echo&amp;quot;access_token =$access_token&amp;quot;echo&amp;quot;expires_in =$expires_in&amp;quot;time=$(date +%s)echo&amp;quot;$access_token:$expires_in:$time&amp;quot;&amp;gt;$HOME/.wechat_accesstokenif[ -z$access_token] || [ -z$expires_in] || [ -z$time];thenecho&amp;quot;not get access_token&amp;quot;exit0fifiremain=$[$(date +%s) -$time]
    limit=$[$expires_in-60]if[$remain-gt$limit];thenrm-f$HOME/.wechat_accesstoken
        getAccessTokenfi}#发送消息函数sendMessage(){#消息json体message=`cat &amp;lt;&amp;lt; EOF
    {&amp;quot;touser&amp;quot;:&amp;quot;$openid&amp;quot;,&amp;quot;template_id&amp;quot;:&amp;quot;$tpl_id&amp;quot;,&amp;quot;url&amp;quot;:&amp;quot;$url&amp;quot;,&amp;quot;data&amp;quot;:{&amp;quot;first&amp;quot;: {&amp;quot;value&amp;quot;:&amp;quot;$first&amp;quot;,&amp;quot;color&amp;quot;:&amp;quot;#FF0000&amp;quot;},&amp;quot;name&amp;quot;:{&amp;quot;value&amp;quot;:&amp;quot;$name&amp;quot;,&amp;quot;color&amp;quot;:&amp;quot;#173177&amp;quot;},&amp;quot;date&amp;quot;: {&amp;quot;value&amp;quot;:&amp;quot;$date&amp;quot;,&amp;quot;color&amp;quot;:&amp;quot;#173177&amp;quot;},&amp;quot;remark&amp;quot;:{&amp;quot;value&amp;quot;:&amp;quot;$remark&amp;quot;,&amp;quot;color&amp;quot;:&amp;quot;#FF0000&amp;quot;}
    }
     }
EOF
`echo&amp;quot;send message :$message&amp;quot;curl -X POST -H&amp;quot;Content-Type: application/json&amp;quot;https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=$access_token-d&amp;quot;$message&amp;quot;}#帮助信息函数usage(){
    cat &amp;lt;&amp;lt;EOF
usage:$0[-u openids-ssummary -n name -t time-ddetail-llink] [-h]
    u   wechat user openid , multiple comma separated
    s   message summary
    n   project name
    t   alarm time
    d   message detail
    l   link address
    h   output thishelpandexitEOF
}#获取脚本执行参数whilegetopts&amp;quot;:u:s:n:t:d:h:l:&amp;quot;op;docase$opinu)
        openids=&amp;quot;$OPTARG&amp;quot;;;
        s)
        first=&amp;quot;$OPTARG&amp;quot;;;
            n)
               name=&amp;quot;$OPTARG&amp;quot;;;
            t)
        date=&amp;quot;$OPTARG&amp;quot;;;
            d)
        remark=&amp;quot;$OPTARG&amp;quot;;;
        l)
        url=&amp;quot;$OPTARG&amp;quot;;;
        *)
        usageexit0;;esacdone#判断条件满足发送消息if[[ -n$openids&amp;amp;&amp;amp; -n$first&amp;amp;&amp;amp; -n$name&amp;amp;&amp;amp; -n$date]];thengetAccessToken
    OLD_IFS=&amp;quot;$IFS&amp;quot;IFS=&amp;quot;,&amp;quot;arr=($openids)
    IFS=&amp;quot;$OLD_IFS&amp;quot;foropenidin${arr[@]}dosendMessagedoneexit$?elseecho&amp;quot;params error.&amp;quot;usageexit1fi&lt;/code&gt;&lt;/pre&gt;    &lt;p&gt;关于微信公众号接口说明查看如下接口wiki：      &lt;br /&gt;      &lt;a href="https://mp.weixin.qq.com/wiki"&gt;https://mp.weixin.qq.com/wiki&lt;/a&gt;      &lt;br /&gt;1.开始开发-&amp;gt;获取access_token      &lt;br /&gt;2.消息管理-&amp;gt;模板消息接口&lt;/p&gt;    &lt;h3&gt;3.接收到消息&lt;/h3&gt;    &lt;p&gt;执行命令脚本即上述shell脚本内容。记得设置脚本执行权限 chmod +x sendMessageForWechat      &lt;br /&gt;      &lt;code&gt;shell        &lt;br /&gt;./sendMessageForWechat -u o4bHbvjL9aWoRCa29vdOQ9aJMq0w -s &amp;quot;192.168.1.90磁盘空间不足&amp;quot; -n 测试系统 -t &amp;quot;2017-01-15 13:00:10&amp;quot; -d &amp;quot;磁盘已使用超过80%，剩余5G，请及时处理&amp;quot; -l &amp;quot;http://m.baidu.com&amp;quot;        &lt;br /&gt;&lt;/code&gt;      &lt;br /&gt;      &lt;img alt="&amp;#36825;&amp;#37324;&amp;#20889;&amp;#22270;&amp;#29255;&amp;#25551;&amp;#36848;" src="http://img.blog.csdn.net/20170505230951822?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvemh1X3RpYW53ZWk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" title=""&gt;&lt;/img&gt;      &lt;br /&gt;点击消息，打开百度链接。。。&lt;/p&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 />
      <guid isPermaLink="true">https://itindex.net/detail/57749-shell-%E5%BE%AE%E4%BF%A1-%E5%85%AC%E4%BC%97</guid>
      <pubDate>Tue, 05 Dec 2017 17:12:22 CST</pubDate>
    </item>
    <item>
      <title>微博爬虫“免登录”技巧详解及Java实现</title>
      <link>https://itindex.net/detail/57482-%E5%BE%AE%E5%8D%9A-%E7%88%AC%E8%99%AB-%E7%99%BB%E5%BD%95</link>
      <description>&lt;blockquote&gt;  &lt;p&gt;本文源地址：   &lt;a href="http://www.fullstackyang.com/archives/434.html"&gt;http://www.fullstackyang.com/...&lt;/a&gt;，转发请注明该地址或segmentfault地址，谢谢！&lt;/p&gt;&lt;/blockquote&gt;
 &lt;h2&gt;一、微博一定要登录才能抓取？&lt;/h2&gt;
 &lt;p&gt;目前，对于微博的爬虫，大部分是基于模拟微博账号登录的方式实现的，这种方式如果真的运营起来，实际上是一件非常头疼痛苦的事，你可能每天都过得提心吊胆，生怕新浪爸爸把你的那些账号给封了，而且现在随着实名制的落地，获得账号的渠道估计也会变得越来越少。&lt;/p&gt;
 &lt;p&gt;但是日子还得继续，在如此艰难的条件下，为了生存爬虫们必须寻求进化。好在上帝关门的同时会随手开窗，微博在其他诸如头条，一点等这类新媒体平台的冲击之下，逐步放开了信息流的查看权限。现在的微博即便在不登录的状态下，依然可以看到很多微博信息流，而我们的落脚点就在这里。&lt;/p&gt;
 &lt;p&gt;本文详细介绍如何获取相关的Cookie并重新封装Httpclient达到免登录的目的，以支持微博上的各项数据抓取任务。下面就从微博首页  &lt;a href="http://weibo.com"&gt;http://weibo.com&lt;/a&gt;开始。&lt;/p&gt;
 &lt;h2&gt;二、准备工作&lt;/h2&gt;
 &lt;p&gt;准备工作很简单，一个现代浏览器（你知道我为什么会写”现代”两个字），以及httpclient（我用的版本是4.5.3）&lt;/p&gt;
 &lt;p&gt;跟登录爬虫一样，免登录爬虫也是需要装载Cookie。这里的Cookie是用来标明游客身份，利用这个Cookie就可以在微博平台中访问那些允许访问的内容了。&lt;/p&gt;
 &lt;p&gt;这里我们可以使用浏览器的network工具来看一下，请求  &lt;a href="http://weibo.com"&gt;http://weibo.com&lt;/a&gt;之后服务器都返回哪些东西，当然事先清空一下浏览器的缓存。&lt;/p&gt;
 &lt;p&gt;不出意外，应该可以看到下图中的内容  &lt;br /&gt;  &lt;img alt="http://www.fullstackyang.com/wp-content/uploads/2017/09/network.jpg" src="https://segmentfault.com/img/bVViB2?w=782&amp;h=403" title="http://www.fullstackyang.com/wp-content/uploads/2017/09/network.jpg"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;第1次请求weibo.com的时候，其状态为302重定向，也就是说这时并没有真正地开始加载页面，而最后一个请求weibo.com的状态为200，表示了请求成功，对比两次请求的header：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="weibo_header-1024x206.jpg" src="https://segmentfault.com/img/bVViHt?w=1024&amp;h=206" title="weibo_header-1024x206.jpg"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;明显地，中间的这些过程给客户端加载了各种Cookie，从而使得可以顺利访问页面，接下来我们逐个进行分析。&lt;/p&gt;
 &lt;h2&gt;三、抽丝剥茧&lt;/h2&gt;
 &lt;p&gt;第2个请求是  &lt;a href="https://passport.weibo.com/visitor"&gt;https://passport.weibo.com/vi...&lt;/a&gt;……，各位可以把这个url复制出来，用httpclient单独访问一下这个url，可以看到返回的是一个html页面，里面有一大段Javascript脚本，另外头部还引用一个JS文件mini_original.js，也就是第3个请求。脚本的功能比较多，就不一一叙述了，简单来说就是微博访问的入口控制，而值得我们注意的是其中的一个function：&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt; // 为用户赋予访客身份 。
    var incarnate = function (tid, where, conficence) {
        var gen_conf = &amp;quot;&amp;quot;;
        var from = &amp;quot;weibo&amp;quot;;
        var incarnate_intr = window.location.protocol + &amp;quot;//&amp;quot; + window.location.host + &amp;quot;/visitor/visitor?a=incarnate&amp;amp;t=&amp;quot; + encodeURIComponent(tid) + &amp;quot;&amp;amp;w=&amp;quot; + encodeURIComponent(where) + &amp;quot;&amp;amp;c=&amp;quot; + encodeURIComponent(conficence) + &amp;quot;&amp;amp;gc=&amp;quot; + encodeURIComponent(gen_conf) + &amp;quot;&amp;amp;cb=cross_domain&amp;amp;from=&amp;quot; + from + &amp;quot;&amp;amp;_rand=&amp;quot; + Math.random();
        url.l(incarnate_intr);
    };&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;这里是为请求者赋予一个访客身份，而控制跳转的链接也是由一些参数拼接起来的，也就是上图中第6个请求。所以下面的工作就是获得这3个参数：tid，w（where），c（conficence，从下文来看应为confidence，大概是新浪工程师的手误）。继续阅读源码，可以看到该function是tid.get方法的回调函数，而这个tid则是定义在那个mini_original.js中的一个对象，其部分源码为：&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;    var tid = {
        key: &amp;apos;tid&amp;apos;,
        value: &amp;apos;&amp;apos;,
        recover: 0,
        confidence: &amp;apos;&amp;apos;,
        postInterface: postUrl,
        fpCollectInterface: sendUrl,
        callbackStack: [],
        init: function () {
            tid.get();
        },
        runstack: function () {
            var f;
            while (f = tid.callbackStack.pop()) {
                f(tid.value, tid.recover, tid.confidence);//注意这里，对应上述的3个参数
            }
        },
        get: function (callback) {
            callback = callback || function () {
            };
            tid.callbackStack.push(callback);
            if (tid.value) {
                return tid.runstack();
            }
            Store.DB.get(tid.key, function (v) {
                if (!v) {
                    tid.getTidFromServer();
                } else {
                    ……
                }
            });
        },
    ……
    }
……
 getTidFromServer: function () {
            tid.getTidFromServer = function () {
            };
            if (window.use_fp) {
                getFp(function (data) {
                    util.postData(window.location.protocol + &amp;apos;//&amp;apos; + window.location.host + &amp;apos;/&amp;apos; + tid.postInterface, &amp;quot;cb=gen_callback&amp;amp;fp=&amp;quot; + encodeURIComponent(data), function (res) {
                        if (res) {
                            eval(res);
                        }
                    });
                });
            } else {
                util.postData(window.location.protocol + &amp;apos;//&amp;apos; + window.location.host + &amp;apos;/&amp;apos; + tid.postInterface, &amp;quot;cb=gen_callback&amp;quot;, function (res) {
                    if (res) {
                        eval(res);
                    }
                });
            }
        },
……
//获得参数
window.gen_callback = function (fp) {
        var value = false, confidence;
        if (fp) {
            if (fp.retcode == 20000000) {
                confidence = typeof(fp.data.confidence) != &amp;apos;undefined&amp;apos; ? &amp;apos;000&amp;apos; + fp.data.confidence : &amp;apos;100&amp;apos;;
                tid.recover = fp.data.new_tid ? 3 : 2;
                tid.confidence = confidence = confidence.substring(confidence.length - 3);
                value = fp.data.tid;
                Store.DB.set(tid.key, value + &amp;apos;__&amp;apos; + confidence);
            }
        }
        tid.value = value;
        tid.runstack();
    };&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;显然，tid.runstack()是真正执行回调函数的地方，这里就能看到传入的3个参数。在get方法中，当cookie为空时，tid会调用getTidFromServer，这时就产生了第5个请求  &lt;a href="https://passport.weibo.com/visitor/genvisitor"&gt;https://passport.weibo.com/vi...&lt;/a&gt;，它需要两个参数cb和fp，其参数值可以作为常量：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="http://www.fullstackyang.com/wp-content/uploads/2017/09/genvisitor-1024x98.jpg" src="https://segmentfault.com/img/bVViIc?w=1024&amp;h=98" title="http://www.fullstackyang.com/wp-content/uploads/2017/09/genvisitor-1024x98.jpg"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;该请求的结果返回一串json&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;{
  &amp;quot;msg&amp;quot;: &amp;quot;succ&amp;quot;,
  &amp;quot;data&amp;quot;: {
    &amp;quot;new_tid&amp;quot;: false,
    &amp;quot;confidence&amp;quot;: 95,
    &amp;quot;tid&amp;quot;: &amp;quot;kIRvLolhrCR5iSCc80tWqDYmwBvlRVlnY2+yvCQ1VVA=&amp;quot;
  },
  &amp;quot;retcode&amp;quot;: 20000000
}&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;其中就包含了tid和confidence，这个confidence，我猜大概是推测客户端是否真实的一个置信度，不一定出现，根据window.gen_callback方法，不出现时默认为100，另外当new_tid为真时参数where等于3，否则等于2。&lt;/p&gt;
 &lt;p&gt;此时3个参数已经全部获得，现在就可以用httpclient发起上面第6个请求，返回得到另一串json：&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;{
  &amp;quot;msg&amp;quot;: &amp;quot;succ&amp;quot;,
  &amp;quot;data&amp;quot;: {
    &amp;quot;sub&amp;quot;: &amp;quot;_2AkMu428tf8NxqwJRmPAcxWzmZYh_zQjEieKYv572JRMxHRl-yT83qnMGtRCnhyR4ezQQZQrBRO3gVMwM5ZB2hQ..&amp;quot;,
    &amp;quot;subp&amp;quot;: &amp;quot;0033WrSXqPxfM72-Ws9jqgMF55529P9D9WWU2MgYnITksS2awP.AX-DQ&amp;quot;
  },
  &amp;quot;retcode&amp;quot;: 20000000
}&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;参考最后请求weibo.com的header，这里的sub和subp就是最终要获取的cookie值。大家或许有一个小疑问，第一个Cookie怎么来的，没用吗？是的，这个Cookie是第一次访问weibo.com产生的，经过测试可以不用装载。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="http://www.fullstackyang.com/wp-content/uploads/2017/09/cookie-1.jpg" src="https://segmentfault.com/img/bVViGo?w=1120&amp;h=209" title="http://www.fullstackyang.com/wp-content/uploads/2017/09/cookie-1.jpg"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;最后我们用上面两个Cookie装载到HttpClient中请求一次weibo.com，就可以获得完整的html页面了，下面就是见证奇迹的时刻：&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;&amp;lt;!doctype html&amp;gt;
&amp;lt;html&amp;gt;
&amp;lt;head&amp;gt;
&amp;lt;meta charset=&amp;quot;utf-8&amp;quot;&amp;gt;
&amp;lt;meta http-equiv=&amp;quot;X-UA-Compatible&amp;quot; content=&amp;quot;IE=edge,chrome=1&amp;quot;&amp;gt;
&amp;lt;meta name=&amp;quot;viewport&amp;quot; content=&amp;quot;initial-scale=1,minimum-scale=1&amp;quot; /&amp;gt;
&amp;lt;meta content=&amp;quot;随时随地发现新鲜事！微博带你欣赏世界上每一个精彩瞬间，了解每一个幕后故事。分享你想表达的，让全世界都能听到你的心声！&amp;quot; name=&amp;quot;description&amp;quot; /&amp;gt;
&amp;lt;link rel=&amp;quot;mask-icon&amp;quot; sizes=&amp;quot;any&amp;quot; href=&amp;quot;//img.t.sinajs.cn/t6/style/images/apple/wbfont.svg&amp;quot; color=&amp;quot;black&amp;quot; /&amp;gt;
&amp;lt;link rel=&amp;quot;shortcut icon&amp;quot; type=&amp;quot;image/x-icon&amp;quot; href=&amp;quot;/favicon.ico&amp;quot; /&amp;gt;
&amp;lt;script type=&amp;quot;text/javascript&amp;quot;&amp;gt;
try{document.execCommand(&amp;quot;BackgroundImageCache&amp;quot;, false, true);}catch(e){}
&amp;lt;/script&amp;gt;
&amp;lt;title&amp;gt;微博-随时随地发现新鲜事&amp;lt;/title&amp;gt;
&amp;lt;link href=&amp;quot;//img.t.sinajs.cn/t6/style/css/module/base/frame.css?version=6c9bf6ab3b33391f&amp;quot; type=&amp;quot;text/css&amp;quot; rel=&amp;quot;stylesheet&amp;quot; charset=&amp;quot;utf-8&amp;quot; /&amp;gt;
&amp;lt;link href=&amp;quot;//img.t.sinajs.cn/t6/style/css/pages/growth/login_v5.css?version=6c9bf6ab3b33391f&amp;quot; type=&amp;quot;text/css&amp;quot; rel=&amp;quot;stylesheet&amp;quot; charset=&amp;quot;utf-8&amp;quot;&amp;gt;
&amp;lt;link href=&amp;quot;//img.t.sinajs.cn/t6/skin/default/skin.css?version=6c9bf6ab3b33391f&amp;quot; type=&amp;quot;text/css&amp;quot; rel=&amp;quot;stylesheet&amp;quot; id=&amp;quot;skin_style&amp;quot; /&amp;gt;
&amp;lt;script type=&amp;quot;text/javascript&amp;quot;&amp;gt;
var $CONFIG = {};
$CONFIG[&amp;apos;islogin&amp;apos;] = &amp;apos;0&amp;apos;;
$CONFIG[&amp;apos;version&amp;apos;] = &amp;apos;6c9bf6ab3b33391f&amp;apos;;
$CONFIG[&amp;apos;timeDiff&amp;apos;] = (new Date() - 1505746970000);
$CONFIG[&amp;apos;lang&amp;apos;] = &amp;apos;zh-cn&amp;apos;;
$CONFIG[&amp;apos;jsPath&amp;apos;] = &amp;apos;//js.t.sinajs.cn/t5/&amp;apos;;
$CONFIG[&amp;apos;cssPath&amp;apos;] = &amp;apos;//img.t.sinajs.cn/t5/&amp;apos;;
$CONFIG[&amp;apos;imgPath&amp;apos;] = &amp;apos;//img.t.sinajs.cn/t5/&amp;apos;;
$CONFIG[&amp;apos;servertime&amp;apos;] = 1505746970;
$CONFIG[&amp;apos;location&amp;apos;]=&amp;apos;login&amp;apos;;
$CONFIG[&amp;apos;bigpipe&amp;apos;]=&amp;apos;false&amp;apos;;
$CONFIG[&amp;apos;bpType&amp;apos;]=&amp;apos;login&amp;apos;;
$CONFIG[&amp;apos;mJsPath&amp;apos;] = [&amp;apos;//js{n}.t.sinajs.cn/t5/&amp;apos;, 1, 2];
$CONFIG[&amp;apos;mCssPath&amp;apos;] = [&amp;apos;//img{n}.t.sinajs.cn/t5/&amp;apos;, 1, 2];
$CONFIG[&amp;apos;redirect&amp;apos;] = &amp;apos;&amp;apos;;
$CONFIG[&amp;apos;vid&amp;apos;]=&amp;apos;1008997495870&amp;apos;;
&amp;lt;/script&amp;gt;
&amp;lt;style&amp;gt;#js_style_css_module_global_WB_outframe{height:42px;}&amp;lt;/style&amp;gt;
&amp;lt;/head&amp;gt;
……&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;如果之前有微博爬虫开发经验的小伙伴，看到这里，一定能想出来很多玩法了吧。&lt;/p&gt;
 &lt;h2&gt;四、代码实现&lt;/h2&gt;
 &lt;p&gt;下面附上我的源码，通过上面的详细介绍，应该已经比较好理解，因此这里就简单地说明一下：&lt;/p&gt;
 &lt;ol&gt;
  &lt;li&gt;我把Cookie获取的过程做成了一个静态内部类，其中需要发起2次请求，一次是genvisitor获得3个参数，另一次是incarnate获得Cookie值；&lt;/li&gt;
  &lt;li&gt;如果Cookie获取失败，会调用HttpClientInstance.changeProxy来改变代理IP，然后重新获取，直到获取成功为止；&lt;/li&gt;
  &lt;li&gt;在使用时，出现了IP被封或无法正常获取页面等异常情况，外部可以通过调用cookieReset方法，重新获取一个新的Cookie。这里还是要声明一下，科学地使用爬虫，维护世界和平是程序员的基本素养；&lt;/li&gt;
  &lt;li&gt;虽然加了一些锁的控制，但是还未在高并发场景实测过，不能保证百分百线程安全，如使用下面的代码，请根据需要自行修改，如有问题也请大神们及时指出，拜谢！&lt;/li&gt;
  &lt;li&gt;HttpClientInstance是我用单例模式重新封装的httpclient，对于每个传进来的请求重新包装了一层RequestConfig，并且使用了代理IP；&lt;/li&gt;
  &lt;li&gt;不是所有的微博页面都可以抓取得到，但是博文，评论，转发等基本的数据还是没有问题的；&lt;/li&gt;
  &lt;li&gt;后续我也会把代码push到github上，请大家支持，谢谢！&lt;/li&gt;
&lt;/ol&gt;
 &lt;pre&gt;  &lt;code&gt;import com.fullstackyang.httpclient.HttpClientInstance;
import com.fullstackyang.httpclient.HttpRequestUtils;
import com.google.common.base.Strings;
import com.google.common.collect.Maps;
import com.google.common.net.HttpHeaders;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.client.config.CookieSpecs;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.json.JSONObject;
 
import java.io.UnsupportedEncodingException;
import java.math.BigDecimal;
import java.net.URLEncoder;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
 
/**
 * 微博免登陆请求客户端
 *
 * @author fullstackyang
 */
@Slf4j
public class WeiboClient {
 
    private static CookieFetcher cookieFetcher = new CookieFetcher();
 
    private volatile String cookie;
 
    public WeiboClient() {
        this.cookie = cookieFetcher.getCookie();
    }
 
    private static Lock lock = new ReentrantLock();
 
    public void cookieReset() {
        if (lock.tryLock()) {
            try {
                HttpClientInstance.instance().changeProxy();
                this.cookie = cookieFetcher.getCookie();
                log.info(&amp;quot;cookie :&amp;quot; + cookie);
            } finally {
                lock.unlock();
            }
        }
    }
 
    /**
     * get方法，获取微博平台的其他页面
     * @param url
     * @return
     */
    public String get(String url) {
        if (Strings.isNullOrEmpty(url))
            return &amp;quot;&amp;quot;;
 
        while (true) {
            HttpGet httpGet = new HttpGet(url);
            httpGet.addHeader(HttpHeaders.COOKIE, cookie);
            httpGet.addHeader(HttpHeaders.HOST, &amp;quot;weibo.com&amp;quot;);
            httpGet.addHeader(&amp;quot;Upgrade-Insecure-Requests&amp;quot;, &amp;quot;1&amp;quot;);
 
            httpGet.setConfig(RequestConfig.custom().setSocketTimeout(3000)
                    .setConnectTimeout(3000).setConnectionRequestTimeout(3000).build());
            String html = HttpClientInstance.instance().tryExecute(httpGet, null, null);
            if (html == null)
                cookieReset();
            else return html;
        }
    }
 
     /**
     * 获取访问微博时必需的Cookie
     */
    @NoArgsConstructor
    static class CookieFetcher {
 
        static final String PASSPORT_URL = &amp;quot;https://passport.weibo.com/visitor/visitor?entry=miniblog&amp;amp;a=enter&amp;amp;url=http://weibo.com/?category=2&amp;quot;
                + &amp;quot;&amp;amp;domain=.weibo.com&amp;amp;ua=php-sso_sdk_client-0.6.23&amp;quot;;
 
        static final String GEN_VISITOR_URL = &amp;quot;https://passport.weibo.com/visitor/genvisitor&amp;quot;;
 
        static final String VISITOR_URL = &amp;quot;https://passport.weibo.com/visitor/visitor?a=incarnate&amp;quot;;
 
        private String getCookie() {
            Map&amp;lt;String, String&amp;gt; map;
            while (true) {
                map = getCookieParam();
                if (map.containsKey(&amp;quot;SUB&amp;quot;) &amp;amp;&amp;amp; map.containsKey(&amp;quot;SUBP&amp;quot;) &amp;amp;&amp;amp;
                        StringUtils.isNoneEmpty(map.get(&amp;quot;SUB&amp;quot;), map.get(&amp;quot;SUBP&amp;quot;)))
                    break;
                HttpClientInstance.instance().changeProxy();
            }
            return &amp;quot; YF-Page-G0=&amp;quot; + &amp;quot;; _s_tentry=-; SUB=&amp;quot; + map.get(&amp;quot;SUB&amp;quot;) + &amp;quot;; SUBP=&amp;quot; + map.get(&amp;quot;SUBP&amp;quot;);
        }
 
        private Map&amp;lt;String, String&amp;gt; getCookieParam() {
            String time = System.currentTimeMillis() + &amp;quot;&amp;quot;;
            time = time.substring(0, 9) + &amp;quot;.&amp;quot; + time.substring(9, 13);
            String passporturl = PASSPORT_URL + &amp;quot;&amp;amp;_rand=&amp;quot; + time;
 
            String tid = &amp;quot;&amp;quot;;
            String c = &amp;quot;&amp;quot;;
            String w = &amp;quot;&amp;quot;;
            {
                String str = postGenvisitor(passporturl);
                if (str.contains(&amp;quot;\&amp;quot;retcode\&amp;quot;:20000000&amp;quot;)) {
                    JSONObject jsonObject = new JSONObject(str).getJSONObject(&amp;quot;data&amp;quot;);
                    tid = jsonObject.optString(&amp;quot;tid&amp;quot;);
                    try {
                        tid = URLEncoder.encode(tid, &amp;quot;utf-8&amp;quot;);
                    } catch (UnsupportedEncodingException e) {
                    }
                    c = jsonObject.has(&amp;quot;confidence&amp;quot;) ? &amp;quot;000&amp;quot; + jsonObject.getInt(&amp;quot;confidence&amp;quot;) : &amp;quot;100&amp;quot;;
                    w = jsonObject.optBoolean(&amp;quot;new_tid&amp;quot;) ? &amp;quot;3&amp;quot; : &amp;quot;2&amp;quot;;
                }
            }
            String s = &amp;quot;&amp;quot;;
            String sp = &amp;quot;&amp;quot;;
            {
                if (StringUtils.isNoneEmpty(tid, w, c)) {
                    String str = getVisitor(tid, w, c, passporturl);
                    str = str.substring(str.indexOf(&amp;quot;(&amp;quot;) + 1, str.indexOf(&amp;quot;)&amp;quot;));
                    if (str.contains(&amp;quot;\&amp;quot;retcode\&amp;quot;:20000000&amp;quot;)) {
                        System.out.println(new JSONObject(str).toString(2));
                        JSONObject jsonObject = new JSONObject(str).getJSONObject(&amp;quot;data&amp;quot;);
                        s = jsonObject.getString(&amp;quot;sub&amp;quot;);
                        sp = jsonObject.getString(&amp;quot;subp&amp;quot;);
                    }
 
                }
            }
            Map&amp;lt;String, String&amp;gt; map = Maps.newHashMap();
            map.put(&amp;quot;SUB&amp;quot;, s);
            map.put(&amp;quot;SUBP&amp;quot;, sp);
            return map;
        }
 
        private String postGenvisitor(String passporturl) {
 
            Map&amp;lt;String, String&amp;gt; headers = Maps.newHashMap();
            headers.put(HttpHeaders.ACCEPT, &amp;quot;*/*&amp;quot;);
            headers.put(HttpHeaders.ORIGIN, &amp;quot;https://passport.weibo.com&amp;quot;);
            headers.put(HttpHeaders.REFERER, passporturl);
 
            Map&amp;lt;String, String&amp;gt; params = Maps.newHashMap();
            params.put(&amp;quot;cb&amp;quot;, &amp;quot;gen_callback&amp;quot;);
            params.put(&amp;quot;fp&amp;quot;, fp());
 
            HttpPost httpPost = HttpRequestUtils.createHttpPost(GEN_VISITOR_URL, headers, params);
 
            String str = HttpClientInstance.instance().execute(httpPost, null);
            return str.substring(str.indexOf(&amp;quot;(&amp;quot;) + 1, str.lastIndexOf(&amp;quot;&amp;quot;));
        }
 
        private String getVisitor(String tid, String w, String c, String passporturl) {
            String url = VISITOR_URL + &amp;quot;&amp;amp;t=&amp;quot; + tid + &amp;quot;&amp;amp;w=&amp;quot; + &amp;quot;&amp;amp;c=&amp;quot; + c.substring(c.length() - 3)
                    + &amp;quot;&amp;amp;gc=&amp;amp;cb=cross_domain&amp;amp;from=weibo&amp;amp;_rand=0.&amp;quot; + rand();
 
            Map&amp;lt;String, String&amp;gt; headers = Maps.newHashMap();
            headers.put(HttpHeaders.ACCEPT, &amp;quot;*/*&amp;quot;);
            headers.put(HttpHeaders.HOST, &amp;quot;passport.weibo.com&amp;quot;);
            headers.put(HttpHeaders.COOKIE, &amp;quot;tid=&amp;quot; + tid + &amp;quot;__0&amp;quot; + c);
            headers.put(HttpHeaders.REFERER, passporturl);
 
            HttpGet httpGet = HttpRequestUtils.createHttpGet(url, headers);
            httpGet.setConfig(RequestConfig.custom().setCookieSpec(CookieSpecs.STANDARD).build());
            return HttpClientInstance.instance().execute(httpGet, null);
        }
 
        private static String rand() {
            return new BigDecimal(Math.floor(Math.random() * 10000000000000000L)).toString();
        }
 
        private static String fp() {
            JSONObject jsonObject = new JSONObject();
            jsonObject.put(&amp;quot;os&amp;quot;, &amp;quot;1&amp;quot;);
            jsonObject.put(&amp;quot;browser&amp;quot;, &amp;quot;Chrome59,0,3071,115&amp;quot;);
            jsonObject.put(&amp;quot;fonts&amp;quot;, &amp;quot;undefined&amp;quot;);
            jsonObject.put(&amp;quot;screenInfo&amp;quot;, &amp;quot;1680*1050*24&amp;quot;);
            jsonObject.put(&amp;quot;plugins&amp;quot;,
                    &amp;quot;Enables Widevine licenses for playback of HTML audio/video content. (version: 1.4.8.984)::widevinecdmadapter.dll::Widevine Content Decryption Module|Shockwave Flash 26.0 r0::pepflashplayer.dll::Shockwave Flash|::mhjfbmdgcfjbbpaeojofohoefgiehjai::Chrome PDF Viewer|::internal-nacl-plugin::Native Client|Portable Document Format::internal-pdf-viewer::Chrome PDF Viewer&amp;quot;);
            return jsonObject.toString();
        }
    }
}&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>网页爬虫 httpclient java</category>
      <guid isPermaLink="true">https://itindex.net/detail/57482-%E5%BE%AE%E5%8D%9A-%E7%88%AC%E8%99%AB-%E7%99%BB%E5%BD%95</guid>
      <pubDate>Tue, 19 Sep 2017 09:48:42 CST</pubDate>
    </item>
    <item>
      <title>用Python爬取微博数据生成词云图片</title>
      <link>https://itindex.net/detail/57415-python-%E5%BE%AE%E5%8D%9A-%E6%95%B0%E6%8D%AE</link>
      <description>&lt;p&gt;很早之前写过一篇怎么利用微博数据制作词云图片出来，之前的写得不完整，而且只能使用自己的数据，现在重新整理了一下，任何的微博数据都可以制作出来，放在今天应该比较应景。&lt;/p&gt; 
   &lt;p&gt;一年一度的虐汪节，是继续蹲在角落默默吃狗粮还是主动出击告别单身汪加入散狗粮的行列就看你啦，七夕送什么才有心意，程序猿可以试试用一种特别的方式来表达你对女神的心意。有一个创意是把她过往发的微博整理后用词云展示出来。本文教你怎么用Python快速创建出有心意词云，即使是Python小白也能分分钟做出来。&lt;/p&gt; 
   &lt;h3&gt;准备工作&lt;/h3&gt; 
   &lt;p&gt;本环境基于Python3，理论上Python2.7也是可行的，先安装必要的第三方依赖包：&lt;/p&gt; 
   &lt;pre&gt;
  &lt;code&gt;# requirement.txt
jieba==0.38
matplotlib==2.0.2
numpy==1.13.1
pyparsing==2.2.0
requests==2.18.4
scipy==0.19.1
wordcloud==1.3.1&lt;/code&gt;&lt;/pre&gt; 
   &lt;p&gt;requirement.txt文件中包含上面的几个依赖包，如果用pip方式安装失败，推荐使用Anaconda安装&lt;/p&gt; 
   &lt;pre&gt;
  &lt;code&gt;pip install -r requirement.txt&lt;/code&gt;&lt;/pre&gt; 
   &lt;h3&gt;第一步：分析网址&lt;/h3&gt; 
   &lt;p&gt;打开微博移动端网址 https://m.weibo.cn/searchs ，找到女神的微博ID，进入她的微博主页，分析浏览器发送请求的过程&lt;/p&gt; 
   &lt;p&gt;  &lt;img src="http://www.phpxs.com/uploads/201709/01/15042360531.png"&gt;&lt;/img&gt;&lt;/p&gt; 
   &lt;p&gt;打开 Chrome 浏览器的调试功能，选择 Network 菜单，观察到获取微博数据的的接口是 https://m.weibo.cn/api/container/getIndex ，后面附带了一连串的参数，这里面有些参数是根据用户变化的，有些是固定的，先提取出来。&lt;/p&gt; 
   &lt;pre&gt;
  &lt;code&gt;uid=1192515960&amp;amp;
luicode=10000011&amp;amp;
lfid=100103type%3D3%26q%3D%E6%9D%8E%E5%86%B0%E5%86%B0&amp;amp;
featurecode=20000320&amp;amp;
type=user&amp;amp;
containerid=1076031192515960&lt;/code&gt;&lt;/pre&gt; 
   &lt;p&gt;再来分析接口的返回结果，返回数据是一个JSON字典结构，total 是微博总条数，每一条具体的微博内容封装在 cards 数组中，具体内容字段是里面的 text 字段。很多干扰信息已隐去。&lt;/p&gt; 
   &lt;pre&gt;
  &lt;code&gt;{
    &amp;quot;cardlistInfo&amp;quot;: {
        &amp;quot;containerid&amp;quot;: &amp;quot;1076031192515960&amp;quot;,
        &amp;quot;total&amp;quot;: 4754,
        &amp;quot;page&amp;quot;: 2
    },
    &amp;quot;cards&amp;quot;: [
        {
            &amp;quot;card_type&amp;quot;: 9,
            &amp;quot;mblog&amp;quot;: {
                &amp;quot;created_at&amp;quot;: &amp;quot;08-26&amp;quot;,
                &amp;quot;idstr&amp;quot;: &amp;quot;4145069944506080&amp;quot;,
                &amp;quot;text&amp;quot;: &amp;quot;瑞士一日游圆满结束...&amp;quot;,
            }
        }]
}&lt;/code&gt;&lt;/pre&gt; 
   &lt;h3&gt;第二步：构建请求头和查询参数&lt;/h3&gt; 
   &lt;p&gt;分析完网页后，我们开始用 requests 模拟浏览器构造爬虫获取数据，因为这里获取用户的数据无需登录微博，所以我们不需要构造 cookie信息，只需要基本的请求头即可，具体需要哪些头信息也可以从浏览器中获取，首先构造必须要的请求参数，包括请求头和查询参数。&lt;/p&gt; 
   &lt;p&gt;  &lt;img src="http://www.phpxs.com/uploads/201709/01/15042360532.png"&gt;&lt;/img&gt;&lt;/p&gt; 
   &lt;pre&gt;
  &lt;code&gt;headers = {
    &amp;quot;Host&amp;quot;: &amp;quot;m.weibo.cn&amp;quot;,
    &amp;quot;Referer&amp;quot;: &amp;quot;https://m.weibo.cn/u/1705822647&amp;quot;,
    &amp;quot;User-Agent&amp;quot;: &amp;quot;Mozilla/5.0 (iPhone; CPU iPhone OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) &amp;quot;
                  &amp;quot;Version/9.0 Mobile/13B143 Safari/601.1&amp;quot;,
}

params = {&amp;quot;uid&amp;quot;: &amp;quot;{uid}&amp;quot;,
          &amp;quot;luicode&amp;quot;: &amp;quot;20000174&amp;quot;,
          &amp;quot;featurecode&amp;quot;: &amp;quot;20000320&amp;quot;,
          &amp;quot;type&amp;quot;: &amp;quot;uid&amp;quot;,
          &amp;quot;value&amp;quot;: &amp;quot;1705822647&amp;quot;,
          &amp;quot;containerid&amp;quot;: &amp;quot;{containerid}&amp;quot;,
          &amp;quot;page&amp;quot;: &amp;quot;{page}&amp;quot;}&lt;/code&gt;&lt;/pre&gt; 
   &lt;ul&gt; 
     &lt;li&gt;uid是微博用户的id&lt;/li&gt; 
     &lt;li&gt;containerid虽然不什么意思，但也是和具体某个用户相关的参数&lt;/li&gt; 
     &lt;li&gt;page 分页参数&lt;/li&gt; 
  &lt;/ul&gt; 
   &lt;h3&gt;第三步：构造简单爬虫&lt;/h3&gt; 
   &lt;p&gt;通过返回的数据能查询到总微博条数 total，爬取数据直接利用 requests 提供的方法把 json 数据转换成 Python 字典对象，从中提取出所有的 text 字段的值并放到 blogs 列表中，提取文本之前进行简单过滤，去掉无用信息。顺便把数据写入文件，方便下次转换时不再重复爬取。&lt;/p&gt; 
   &lt;pre&gt;
  &lt;code&gt;def fetch_data(uid=None, container_id=None):
    &amp;quot;&amp;quot;&amp;quot;
    抓取数据，并保存到CSV文件中
    :return:
    &amp;quot;&amp;quot;&amp;quot;
    page = 0
    total = 4754
    blogs = []
    for i in range(0, total // 10):
        params[&amp;apos;uid&amp;apos;] = uid
        params[&amp;apos;page&amp;apos;] = str(page)
        params[&amp;apos;containerid&amp;apos;] = container_id
        res = requests.get(url, params=params, headers=HEADERS)
        cards = res.json().get(&amp;quot;cards&amp;quot;)

        for card in cards:
            # 每条微博的正文内容
            if card.get(&amp;quot;card_type&amp;quot;) == 9:
                text = card.get(&amp;quot;mblog&amp;quot;).get(&amp;quot;text&amp;quot;)
                text = clean_html(text)
                blogs.append(text)
        page += 1
        print(&amp;quot;抓取第{page}页，目前总共抓取了 {count} 条微博&amp;quot;.format(page=page, count=len(blogs)))
        with codecs.open(&amp;apos;weibo1.txt&amp;apos;, &amp;apos;w&amp;apos;, encoding=&amp;apos;utf-8&amp;apos;) as f:
            f.write(&amp;quot;\n&amp;quot;.join(blogs))&lt;/code&gt;&lt;/pre&gt; 
   &lt;p&gt;  &lt;img src="http://www.phpxs.com/uploads/201709/01/15042360533.gif"&gt;&lt;/img&gt;&lt;/p&gt; 
   &lt;h3&gt;第四步：分词处理并构建词云&lt;/h3&gt; 
   &lt;p&gt;爬虫了所有数据之后，先进行分词，这里用的是结巴分词，按照中文语境将句子进行分词处理，分词过程中过滤掉停止词，处理完之后找一张参照图，然后根据参照图通过词语拼装成图。&lt;/p&gt; 
   &lt;pre&gt;
  &lt;code&gt;def generate_image():
    data = []
    jieba.analyse.set_stop_words(&amp;quot;./stopwords.txt&amp;quot;)

    with codecs.open(&amp;quot;weibo1.txt&amp;quot;, &amp;apos;r&amp;apos;, encoding=&amp;quot;utf-8&amp;quot;) as f:
        for text in f.readlines():
            data.extend(jieba.analyse.extract_tags(text, topK=20))
        data = &amp;quot; &amp;quot;.join(data)
        mask_img = imread(&amp;apos;./52f90c9a5131c.jpg&amp;apos;, flatten=True)
        wordcloud = WordCloud(
            font_path=&amp;apos;msyh.ttc&amp;apos;,
            background_color=&amp;apos;white&amp;apos;,
            mask=mask_img
        ).generate(data)
        plt.imshow(wordcloud.recolor(color_func=grey_color_func, random_state=3),
                   interpolation=&amp;quot;bilinear&amp;quot;)
        plt.axis(&amp;apos;off&amp;apos;)
        plt.savefig(&amp;apos;./heart2.jpg&amp;apos;, dpi=1600)&lt;/code&gt;&lt;/pre&gt; 
   &lt;p&gt;最终效果图：&lt;/p&gt; 
   &lt;p&gt;  &lt;img src="http://www.phpxs.com/uploads/201709/01/15042360534.png"&gt;&lt;/img&gt;&lt;/p&gt; 
   &lt;p&gt;完整代码可以在公众号（Python之禅）回复“qixi”获取&lt;/p&gt; 
   &lt;p&gt;关注公众号『Python之禅』（id：vttalk）获取最新文章&lt;/p&gt; 
   &lt;p&gt;  &lt;img src="http://www.phpxs.com/uploads/201709/01/15042360545.jpg"&gt;&lt;/img&gt;&lt;/p&gt; 
   &lt;p&gt; &lt;/p&gt; 
   &lt;p&gt;来自：https://foofish.net/python-word-cloud.html&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 />
      <guid isPermaLink="true">https://itindex.net/detail/57415-python-%E5%BE%AE%E5%8D%9A-%E6%95%B0%E6%8D%AE</guid>
      <pubDate>Fri, 01 Sep 2017 08:00:00 CST</pubDate>
    </item>
  </channel>
</rss>

