﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>BlogJava-&lt;DIV id="ddm_Title"&gt;&lt;H1&gt;大大毛&amp;nbsp;&lt;i class="little"&gt;的笔记&lt;/i&gt;&lt;/H1&gt;&lt;H2&gt;&amp;nbsp&amp;nbsp;DDM's Note&lt;/H2&gt;&lt;/DIV&gt;-文章分类-&lt;b value="0501" class="ddm_subItem"&gt;Nifi&lt;/b&gt;</title><link>http://www.blogjava.net/tw-ddm/category/55331.html</link><description>&lt;div id="ddm_subTitle"&gt;
&lt;h2 class="catchline"&gt;
        哪怕没有办法一定有说法,&lt;br/&gt;
        就算没有鸽子一定有乌鸦,&lt;br/&gt;
        固执无罪&amp;nbsp;梦想有价,&lt;br/&gt;
        让他们惊讶.
&lt;/h2&gt;
&lt;/div&gt;</description><language>zh-cn</language><lastBuildDate>Fri, 12 Apr 2019 00:07:17 GMT</lastBuildDate><pubDate>Fri, 12 Apr 2019 00:07:17 GMT</pubDate><ttl>60</ttl><item><title>Nifi同步数据的几种方法</title><link>http://www.blogjava.net/tw-ddm/articles/433715.html</link><dc:creator>大大毛</dc:creator><author>大大毛</author><pubDate>Thu, 11 Apr 2019 09:27:00 GMT</pubDate><guid>http://www.blogjava.net/tw-ddm/articles/433715.html</guid><wfw:comment>http://www.blogjava.net/tw-ddm/comments/433715.html</wfw:comment><comments>http://www.blogjava.net/tw-ddm/articles/433715.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/tw-ddm/comments/commentRss/433715.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/tw-ddm/services/trackbacks/433715.html</trackback:ping><description><![CDATA[<span style="font-family: Arial; font-size: 10pt;">经常会遇到将Table从一个DB同步到另一个DB的需求，不同需求下，可使用的处理方式会有不同：</span><br /><br /><span style="font-family: Arial; font-size: 10pt;">1.&nbsp;使用特定的Processor</span><br /><span style="font-family: Arial; font-size: 10pt;">比如Upsert或自行开发的Processor</span><br /><br /><span style="font-family: Arial; font-size: 10pt;">2.&nbsp;在Nifi流程中自行构建SQL及其绑定参数</span><br /><span style="font-family: Arial; font-size: 10pt;">比如借用ConvertJsonToSQL的二次加工</span><br /><br /><span style="font-family: Arial; font-size: 10pt;">3.&nbsp;RouteOnAttribute的分支</span><br /><span style="font-family: Arial; font-size: 10pt;">最多能分出Delete，Update是无能为力的，而且还要考量资料顺序</span><br /><br /><span style="font-family: Arial; font-size: 10pt;">4.&nbsp;仅新增 +&nbsp;DB层面的二次处理。这个其实是可以适用于所有情况，Nifi流程贼简单，但DB上的东西就多了</span><br /><span style="font-family: Arial; font-size: 10pt;">. 在收方加多一张tmp表，结构与正式表一致，但就是没有key。Nifi同步资料指到tmp表</span><br /><span style="font-family: Arial; font-size: 10pt;">.&nbsp;在tmp表上加多Trigger，在这里面Coding去控制Insert、Update和Delete. Trigger里面除了不能Truncate&nbsp;Table，其它啥都能搞</span><br /><span style="font-family: Arial; font-size: 10pt;">.&nbsp;加多一个Job或Event，定时去清tmp表，防止它爆炸</span><br /><img src ="http://www.blogjava.net/tw-ddm/aggbug/433715.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/tw-ddm/" target="_blank">大大毛</a> 2019-04-11 17:27 <a href="http://www.blogjava.net/tw-ddm/articles/433715.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>RouteOnAttribute的用法</title><link>http://www.blogjava.net/tw-ddm/articles/433714.html</link><dc:creator>大大毛</dc:creator><author>大大毛</author><pubDate>Thu, 11 Apr 2019 08:55:00 GMT</pubDate><guid>http://www.blogjava.net/tw-ddm/articles/433714.html</guid><wfw:comment>http://www.blogjava.net/tw-ddm/comments/433714.html</wfw:comment><comments>http://www.blogjava.net/tw-ddm/articles/433714.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/tw-ddm/comments/commentRss/433714.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/tw-ddm/services/trackbacks/433714.html</trackback:ping><description><![CDATA[<div style="display: inline-block;"><h5><span style="font-family: Arial; background-color: #ffffff; font-size: 10pt;">RouteOnAttribute</span></h5><span style="background-color: #ffffff; font-size: 10pt; font-family: Arial;">&nbsp; &nbsp; 这个组件的是用途是根据Attribute的值进行Route分流，从输入输出的角度来看，它可以把一个Input分成多个Output出来，它的分支不同于程序中的Switch语法，而等效于多条的IF语句，也就是说若Output的条件全部都符合，它是可以把1个输出Copy到多个输出的，所以它也可以用于条件复制的应用上。<br />&nbsp; &nbsp; 关于资料落地的文章里我有提到过Route的使用范围，它应该用在能够以Key做分支条件的场景，也就是说相同的Key一定会走固定的Output出来，这样才不会出现资料乱序的状况。下面有两个示例，第一个是典型的分支用法，第二个比较有意思，它的作用相当于Oracle中的Decode语法<br /><br /></span><h5><span style="background-color: #ffffff; font-size: 10pt; font-family: Arial;">示例1</span></h5></div><div><span style="font-size: 13.3333px;">&nbsp; &nbsp; 使用Route做流程的分支，根据一个叫SO的栏位是否为空决定走不同的流程(左右的UpdateAttribute可以把它们想象成两个完全不同的处理流程来看)<br /><br /><img src="http://www.blogjava.net/images/blogjava_net/tw-ddm/Route1.png" width="600" height="216" alt="" /><br /><br /><br /></span><span style="font-family: Arial; font-style: italic; background-color: #ffffff; font-size: 10pt;"><strong>&nbsp; &nbsp; RouteOnAttribute</strong>，</span><span style="background-color: #ffffff; font-size: 10pt; font-family: Arial;">作用是根据Attribute的Bool值来决定是否进入该分支</span><br /><span style="font-size: 13.3333px;"><img src="http://www.blogjava.net/images/blogjava_net/tw-ddm/RouteOnAttributeProcessor1.png" width="600" height="443" alt="" /><br /></span><ul style="font-size: 12px; margin-left: 1em; background-color: #ffffff;"><li><span style="font-size: 8pt;"><font face="Comic Sans MS"><em>HasSO / NonSO</em></font><font face="Trebuchet MS, 宋体">：这是我自行定义的两个Route名称 (不是属性名称)，Value是一个表达式，若它的值=true，则Output会进该Route</font></span></li><ul><li style="font-family: &quot;Trebuchet MS&quot;, 宋体;"><span style="font-size: 8pt;">Nifi的表达式语法不怎么好写，官方的文档上有些东西并不支持 (不确定是不是Nifi版本缘故)。这里还是可以看得出来就只是判断一个叫SO的Attribute的值是否为空 ---&nbsp;还记得有些Processor里面还有叫"Null Value Representation"的属性吧，若是那里配成"null"那这里也要与之匹配。</span></li></ul><li style="font-family: &quot;Trebuchet MS&quot;, 宋体;"><span style="font-size: 8pt;">经过该Processor处理后，SO为空的会走右端逻辑，而不为空的则会走左段逻辑，同时它会加多一个叫"RouteOnAttribute.Route"的Attribute，内容即为Route名称<br /></span></li></ul><span style="font-size: 13.3333px;"><br /></span><h5><span style="font-size: 13.3333px;">示例2：</span></h5><span style="font-size: 13.3333px;">&nbsp; &nbsp; 根据多个栏位是否有值(不为空)，让它们能够进入不同的Route，后面再根据Route名称去动态的取值，它的特点是Nifi流程并没有出现分支(Connection上是勾了所有的Route)，只是为不同的数据设上了一个变量名称<br /><br /><img src="http://www.blogjava.net/images/blogjava_net/tw-ddm/Route2.png" width="600" height="298" alt="" /><br /><br /></span><span style="font-family: Arial; font-style: italic; background-color: #ffffff; font-size: 10pt;"><strong>&nbsp; &nbsp; RouteOnAttribute</strong>，</span><span style="font-family: Arial; background-color: #ffffff; font-size: 10pt;">借用</span><span style="background-color: #ffffff; font-size: 10pt; font-family: Arial;">分支名称在后面搞事，这里相当于是给资料加上了一个变量名称</span><br /><span style="font-size: 13.3333px;"><img src="http://www.blogjava.net/images/blogjava_net/tw-ddm/RouteOnAttributeProcessor2.png" width="600" height="442" alt="" /><br /></span><ul style="margin-left: 1em; background-color: #ffffff;"><li style="font-size: 12px;"><span style="font-size: 8pt;"><font face="Comic Sans MS"><em>MO /&nbsp;MODELFAMILY / UPN / USN</em></font><font face="Trebuchet MS, 宋体">：这是定义的四个Route名称，判断条件都很简单，就只是不为空</font></span></li><ul><li style="font-size: 12px;"><span style="font-size: 8pt;"><font face="Trebuchet MS, 宋体">值得注意的是这4个条件并非是互斥条件，比如有一笔资料它的MO、USN都不为空，那么就会同时进入两个Route进行输出，所以<span style="color: red;">Output笔数会是2</span></font></span></li><span style="font-size: 13.3333px; font-size: 13.3333px;"></span><li><font face="Trebuchet MS, 宋体"><span style="font-size: 10.6667px;">下面这是进入UPN这个Route后的资料上的Attribute: "RouteOnAttribute.Route"<br /><br /><img src="http://www.blogjava.net/images/blogjava_net/tw-ddm/Route3.png" width="600" height="405" alt="" /></span></font></li></ul></ul><span style="font-size: 13.3333px;"><br /><br /></span><span style="font-family: Arial; font-style: italic; background-color: #ffffff; font-size: 10pt;"><strong>&nbsp; &nbsp; UpdateAttribute</strong>，</span><span style="background-color: #ffffff; font-size: 10pt; font-family: Arial;">比较精彩的用法</span><br /><span style="font-size: 13.3333px;"><img src="http://www.blogjava.net/images/blogjava_net/tw-ddm/UpdateAttributeProcessor4.png" width="600" height="442" alt="" /><br /></span><ul style="font-size: 12px; margin-left: 1em; background-color: #ffffff;"><li><span style="font-size: 8pt;"><font face="Comic Sans MS"><em>mqttTopic</em></font><font face="Trebuchet MS, 宋体">：这是定义的一个MQTT的Topic变量，它的内容是可变动的，会根据Route的不同产生不同的结果</font></span></li><ul><li><span style="font-size: 8pt;"><font face="Trebuchet MS, 宋体">MO/MODELFAMILY/UPN/USN这4个条件有任一不为空，则会要求推送Topic： xxxx/【Type】/【Value】/yyyy</font></span></li><span style="font-size: 13.3333px;"></span><ul><li><span style="font-size: 8pt;"><font face="Trebuchet MS, 宋体">【Type】:&nbsp;为"mo"、"modelfamily"、"upn"、"usn"这四个值之一</font></span></li><span style="font-size: 13.3333px;"></span><li><span style="font-size: 8pt;"><font face="Trebuchet MS, 宋体">【Value】:&nbsp;为MO/MODELFAMILY/UPN/USN这四个Attribute的取值 (即 ${MO} / ${MODELFAMILY}&nbsp;/ ${UPN}&nbsp;/ ${USN}&nbsp;的值)，这里使用了双层<span style="color: red;">${${"RouteOnAttribute.Route"}}</span>的取值方法来实现动态的取用变量值</font></span></li></ul></ul></ul><span style="font-size: 13.3333px;"><br /><br /><br /></span></div><img src ="http://www.blogjava.net/tw-ddm/aggbug/433714.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/tw-ddm/" target="_blank">大大毛</a> 2019-04-11 16:55 <a href="http://www.blogjava.net/tw-ddm/articles/433714.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Kafka资料落地至MariaDB （带Key的新增、修改和删除）</title><link>http://www.blogjava.net/tw-ddm/articles/433713.html</link><dc:creator>大大毛</dc:creator><author>大大毛</author><pubDate>Thu, 11 Apr 2019 07:40:00 GMT</pubDate><guid>http://www.blogjava.net/tw-ddm/articles/433713.html</guid><wfw:comment>http://www.blogjava.net/tw-ddm/comments/433713.html</wfw:comment><comments>http://www.blogjava.net/tw-ddm/articles/433713.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/tw-ddm/comments/commentRss/433713.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/tw-ddm/services/trackbacks/433713.html</trackback:ping><description><![CDATA[<h5>需求：</h5><span style="font-family: Arial; font-size: 10pt;">&nbsp; &nbsp; 比较前一篇文章来说，仅加多Delete的行为。例如我仅需要Status=1的资料，所以对于资料落地来讲，最合适的莫过于下面这样，Table也可以自动保持最少量的有效资料<br /></span><span style="font-family: Arial; font-size: 10pt;">&nbsp; &nbsp; 1.&nbsp;新资料Status=0，执行Insert;<br /></span><span style="font-family: Arial; font-size: 10pt;">&nbsp; &nbsp; 2.&nbsp;资料修改Status=0，执行Update;<br /></span><span style="font-family: Arial; font-size: 10pt;">&nbsp; &nbsp; 3.&nbsp;资料状态变更Status=1，执行Delete;<br /></span><span style="font-family: Arial; font-size: 10pt;">&nbsp; &nbsp; 4.&nbsp;若资料状态重新变更为0，则又会执行Insert;</span><br /><br /><h5><span style="font-family: Arial; font-size: 10pt;">思路：</span></h5><span style="font-family: Arial; font-size: 10pt;">&nbsp; </span><span style="font-family: Arial; font-size: 10pt;">&nbsp; 按理来说，只要通过分支Route就可以将Insert/Update与Delete作业分成两条Nifi支流(看网上确实有很多这么整法的)，但是用Route有一个问题处理不了，那就是资料顺序的正确性你是无法保证的。对于小数据量的场景来说，每笔Key的多次操作间隔可能会比较长，所以它不会有什么问题，但大数据量的情况下，两同相同Key值的资料走Route后被处理的顺序混乱就会造成最终资料结果的异常(比如应该是先Insert再Delete，结果却是发现资料还躺在Table中)。而大数据量在使用Kafka做为数据源时就不可避免会出现：即使业务数据量确实不大，但对于积累了好几天的数据再进行接收时，那一瞬间的数据量也会是很大的。<br /></span><span style="font-size: 10pt; font-family: Arial;">&nbsp; &nbsp;所以我们能做的就是动态决定执行Delete和Insert。</span><br /><br /><h5>解决方案：</h5><span style="font-family: Arial; font-size: 10pt;">虽然与<a href="http://www.blogjava.net/tw-ddm/articles/433711.html">前一篇</a>来说差异不大，但Nifi流程上却有很大不同，下面会详细描述为什么要这样做<br /><img src="http://www.blogjava.net/images/blogjava_net/tw-ddm/ReceiveData_Delete.png" alt="" /><br /><br /><br /></span><h5><span style="font-family: Arial; font-size: 10pt;">Processor及其设定：</span></h5><em style="font-family: &quot;Comic Sans MS&quot;; font-size: 13.3333px; background-color: #ffffff;"><strong>&nbsp; &nbsp; ConsumeKafkaRecord、SplitJson、Connection、EvaluateJsonPath</strong></em><span style="font-family: &quot;Trebuchet MS&quot;, 宋体; font-style: italic; background-color: #ffffff; font-size: 10pt;">，</span><span style="font-family: &quot;Trebuchet MS&quot;, 宋体; background-color: #ffffff; font-size: 10pt;">与前一章的一样，只是不同数据下解析的属性有所不同，这里不再详述。</span><span style="font-family: &quot;Trebuchet MS&quot;, 宋体; background-color: #ffffff; font-size: 10pt;"><br /></span><span style="font-family: Arial; font-size: 10pt;"><br /></span><em style="font-family: &quot;Comic Sans MS&quot;; font-size: 13.3333px; background-color: #ffffff;"><strong>&nbsp; &nbsp; UpdateAttribute</strong></em><span style="font-family: &quot;Trebuchet MS&quot;, 宋体; font-style: italic; background-color: #ffffff; font-size: 10pt;">，</span><span style="font-family: &quot;Trebuchet MS&quot;, 宋体; background-color: #ffffff; font-size: 10pt;">作用是从Kafka中Consume出资料(以Record的形态），这里使用Record是因为源数据就是以Record的方式存上去的 (Avro Schema)<br /><blockquote style="margin: 0 0 0 40px; border: none; padding: 0px;"><span style="font-family: &quot;Trebuchet MS&quot;, 宋体; background-color: #ffffff; font-size: 10pt;"><img src="http://www.blogjava.net/images/blogjava_net/tw-ddm/UpdateAttributeProcessor2.png" width="600" height="442" alt="" /></span></blockquote></span><ul style="font-size: 12px; font-family: &quot;Trebuchet MS&quot;, 宋体; margin-left: 1em; background-color: #ffffff;"><li><span style="font-size: 8pt;"><em>SQL1</em>：这才是这次花招的关键，在这里根据STATUS自行构建SQL语句</span></li><ul><li><span style="font-size: 8pt;"><em>Status=0</em>：构建的就是Delete&nbsp;From xxx Where Key1=? and Key2=? 这样的删除语句</span></li><li><span style="font-size: 8pt;"><em>Status!=0</em>：构建的就是Replace Into xxx (Key1,Key2,col3,col4) Values ( ?, ?, ?, ?) 这样的Insert or Update语句</span></li></ul><li>经此Processor处理后，资料落地所需的SQL就构建好了，后续的问题就是如何去绑定参数和执行</li></ul><br /><em style="font-family: &quot;Comic Sans MS&quot;; font-size: 13.3333px; background-color: #ffffff;"><strong>&nbsp; &nbsp; ReplaceText</strong></em><span style="font-family: &quot;Trebuchet MS&quot;, 宋体; font-style: italic; background-color: #ffffff; font-size: 10pt;">，</span><span style="font-family: &quot;Trebuchet MS&quot;, 宋体; background-color: #ffffff; font-size: 10pt;">作用是对FlowFile进行文本替换，这里使用它来直接产生我所需的JSON内容</span><br /><span style="font-family: Arial; font-size: 10pt;"><blockquote style="margin: 0 0 0 40px; border: none; padding: 0px;"><span style="font-family: Arial; font-size: 10pt;"><img src="http://www.blogjava.net/images/blogjava_net/tw-ddm/ReplaceTextProcessor2.png" width="600" height="442" alt="" /></span></blockquote></span><ul style="font-family: &quot;Trebuchet MS&quot;, 宋体; margin-left: 1em; background-color: #ffffff;"><li style="font-size: 12px;"><span style="font-size: 8pt;"><em>Search&nbsp;Value</em>：这里使用的是Default的</span>(?s)(^.*$)，作用就是把原先的整份文件全部换掉</li><li style="font-size: 12px;"><em style="font-size: 8pt;">Replacement Value</em><span style="font-size: 8pt;">：这里放的就是一个固定结构的JSON，可以看到里面的属性值都是使用的Attribute (它们的值来源于前面的EvaluateJsonPath从源JSON文件中的提取)</span></li><ul style="font-size: 12px;"></ul><li><span style="font-size: 10.6667px;">细心的朋友可以发现这里是与前一篇文章的最大不同，这里没有使用AttributeToJson去直接产生JSON文件，而使用的是更加笨拙的方式</span></li><ul><li><span style="font-size: 10.6667px;">前面的文章有提过，我们产生的Attribute以及AttributeToJson所生成JSON中各属性的顺序问题，结论是怎么搞它都不是我所想象到的顺序。但是ConvertJsonToSQL这个东东却很实在，它确确实实是按JSON中属性的顺序去生成的SQL以及参数名称(还记得参数名称sql.args.</span><span style="font-size: 10.6667px; color: red;">1</span><span style="font-size: 10.6667px;">.value中的这个顺序</span><span style="font-size: 10.6667px; color: red;">1</span><span style="font-size: 10.6667px;">么)，所以问题就来了：</span><br /></li><ul><li style="font-size: 12px;">SQL由于必须要有Delete和Replace，所以它们的参数个数一定是不同的，而Delete压的参数又是我们的Key，所以就必须要保证ConvertJsonToSQL生成属性的顺序，这样我们才能够保证我们的两个Key一定会是sql.args.1和sql.args.2</li></ul><span style="font-family: Arial; font-size: 10pt;"></span><ul><li style="font-size: 12px;">换句话说，如果AttributesToJson若是能够保证JSON属性顺序的话，那就不用这么费劲</li></ul></ul></ul><span style="font-family: Arial; font-size: 10pt;"><br /></span><em style="font-family: &quot;Comic Sans MS&quot;; font-size: 13.3333px; background-color: #ffffff;"><strong>&nbsp; &nbsp; ConvertJsonToSQL</strong></em><span style="font-family: &quot;Trebuchet MS&quot;, 宋体; font-style: italic; background-color: #ffffff; font-size: 10pt;">，</span><span style="font-family: &quot;Trebuchet MS&quot;, 宋体; font-size: 13.3333px; background-color: #ffffff;">与前文一样，以Insert的方式生成SQL和绑定参数即可</span><br /><span style="font-family: Arial; font-size: 10pt;"><br /></span><em style="font-family: &quot;Comic Sans MS&quot;; font-size: 13.3333px; background-color: #ffffff;"><strong>&nbsp; &nbsp; UpdateAttribute</strong></em><span style="font-family: &quot;Trebuchet MS&quot;, 宋体; font-style: italic; background-color: #ffffff; font-size: 10pt;">，</span><span style="background-color: #ffffff;"><font face="Trebuchet MS, 宋体"><span style="font-size: 10pt;">终于用到了它的Delete功能，作用是清除掉多余的SQL绑定参数</span></font><br /><blockquote style="font-family: &quot;Trebuchet MS&quot;, 宋体; font-size: 10pt; margin: 0px 0px 0px 40px; border: none; padding: 0px;"><span style="font-family: &quot;Trebuchet MS&quot;, 宋体; background-color: #ffffff; font-size: 10pt;"><img src="http://www.blogjava.net/images/blogjava_net/tw-ddm/UpdateAttributeProcessor3.png" width="600" height="441" alt="" /><br /></span></blockquote></span><ul style="font-family: &quot;Trebuchet MS&quot;, 宋体; margin-left: 1em; background-color: #ffffff;"><li style="font-size: 12px;"><span style="font-size: 8pt;"><em>Delete&nbsp;Attributes&nbsp;Expression</em>：这里我根据Delete的条件(STATUS=0)去删除多余的SQL绑定参数</span></li><ul><li style="font-size: 12px;"><span style="font-size: 8pt;">这里的写法比较死，我Hard-code删除掉大与2的其它所有参数("&nbsp;</span><span style="font-size: 8pt; color: red;">*</span><span style="font-size: 8pt;"> "</span><span style="font-size: 8pt;">是一个通配符，" </span><span style="font-size: 8pt; color: red;">|</span><span style="font-size: 8pt;"> "是一个多条件的间隔符)，感觉上还有更好的写法</span></li></ul><li style="font-size: 12px;"><span style="font-size: 8pt;">至此我们就可以保证绑定参数的数量与SQL语法参数个数一致 (不一致它死给你看)</span></li><ul style="font-size: 12px;"></ul></ul><span style="background-color: #ffffff;"></span><span style="background-color: #ffffff;"><span style="font-family: &quot;Trebuchet MS&quot;, 宋体; background-color: #ffffff; font-size: 10pt;"><br /></span></span><em style="font-family: &quot;Comic Sans MS&quot;; font-size: 13.3333px; background-color: #ffffff;"><strong>&nbsp; &nbsp; PutSQL</strong></em><span style="font-family: &quot;Trebuchet MS&quot;, 宋体; font-style: italic; background-color: #ffffff; font-size: 10pt;">，</span><span style="font-family: &quot;Trebuchet MS&quot;, 宋体; background-color: #ffffff; font-size: 10pt;">这里仍然是执行SQL，这里使用配置参数的形式让它执行我们的SQL</span><br /><span style="font-family: Arial; font-size: 10pt;"><blockquote style="margin: 0 0 0 40px; border: none; padding: 0px;"><span style="font-family: Arial; font-size: 10pt;"><img src="http://www.blogjava.net/images/blogjava_net/tw-ddm/PutSQLProcessor2.png" width="600" height="440" alt="" /></span></blockquote></span><span style="font-family: Arial; font-size: 10pt;"><br /></span><ul style="font-family: &quot;Trebuchet MS&quot;, 宋体; margin-left: 1em; background-color: #ffffff;"><li style="font-size: 12px;"><span style="font-size: 8pt;"><em>SQL&nbsp;Statement</em>：前面用UpdateAttribute产生的SQL1参数，它会根据STATUS=0去判断是使用DELETE还是REPLACE语法</span></li><ul><li style="font-size: 12px;"><span style="font-size: 8pt;">这个属性压上后，无论SQL1是不是为空，这个组件都不会再去管FlowFile的内容(空属性时是把FlowFile的内容当成SQL去执行的)</span></li></ul></ul><img src ="http://www.blogjava.net/tw-ddm/aggbug/433713.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/tw-ddm/" target="_blank">大大毛</a> 2019-04-11 15:40 <a href="http://www.blogjava.net/tw-ddm/articles/433713.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Kafka资料落地至MariaDB （带Key的新增、修改）</title><link>http://www.blogjava.net/tw-ddm/articles/433711.html</link><dc:creator>大大毛</dc:creator><author>大大毛</author><pubDate>Thu, 11 Apr 2019 06:14:00 GMT</pubDate><guid>http://www.blogjava.net/tw-ddm/articles/433711.html</guid><wfw:comment>http://www.blogjava.net/tw-ddm/comments/433711.html</wfw:comment><comments>http://www.blogjava.net/tw-ddm/articles/433711.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/tw-ddm/comments/commentRss/433711.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/tw-ddm/services/trackbacks/433711.html</trackback:ping><description><![CDATA[<h5>需求：</h5><span style="font-size: 10pt;">&nbsp; &nbsp; 接收Kafka资料，资料具有Key列(多列)，有新增、修改但无删除，需要同步落地至MariaDB</span><br /><h5>解决方案(仅新增、修改)：</h5><span style="font-size: 10pt;">&nbsp; &nbsp; 这个场景是最常见的，资料不会有被删除的状态，所有的更新就只有Insert Or Update这两种状态，先上实例的图 (两边的LogMessage是为了接收Fail，有感叹号是避免一起开启的时候它也被开启----这样failure的讯息就不会再卡在Connection中了)<br /><img src="http://www.blogjava.net/images/blogjava_net/tw-ddm/ReceiveData_NoDelete.png" width="1332" height="546" alt="" /><br /></span><br /><h6><span style="font-size: 10pt; font-family: Arial;">思路：</span></h6><span style="font-size: 10pt; font-family: Arial;">&nbsp; &nbsp; 因为记录只有新增和修改两种状态，理论上说这两种的SQL非常接近，所以可以做以下考量<br /></span><span style="font-size: 10pt; font-family: Arial;">&nbsp; &nbsp; 1. Processor层面是否支援Update&nbsp;Or Insert<br /></span><span style="font-size: 10pt;">&nbsp; &nbsp; &nbsp;</span><span style="font-size: 10pt; font-family: Arial;"> &gt;&nbsp;查网上讯息有个叫Upsert，不过在Nifi中查找，只有一个支援Mongo的组件具有这个功能</span><span style="font-size: 10pt;"><br /></span><span style="font-size: 10pt; font-family: Arial;">&nbsp; &nbsp; 2. DB层面是否支援<br /></span><span style="font-family: Arial; font-size: 10pt;">&nbsp; &nbsp; &nbsp; &gt; Maria DB有个 "REPLACE INTO"&nbsp;的语法是可以支持Insert&nbsp;Or&nbsp;Update，虽然简单看了下介绍说是会依主键或唯一索引去先做定位，如果定位到已经存在则先做删除再进行新增（伪Update），但确实可以达成我们的目的，不是吗？</span><br /><br /><h6><font face="Arial"><span style="font-size: 10pt; font-family: Arial;">Processor及其设定：</span></font></h6><div><em style="font-family: &quot;Comic Sans MS&quot;; font-size: 13.3333px; background-color: #ffffff;"><strong>&nbsp; &nbsp; ConsumeKafkaRecord</strong></em><span style="font-size: 10pt;">，作用是从Kafka中Consume出资料(以Record的形态），这里使用Record是因为源数据就是以Record的方式存上去的 (Avro Schema)</span></div><blockquote style="margin: 0 0 0 40px; border: none; padding: 0px;"><img src="http://www.blogjava.net/images/blogjava_net/tw-ddm/ConsumeKafkaProcessor.png" width="600" height="440" alt="" /></blockquote><ul style="font-size: 12px; margin-left: 1em; background-color: #ffffff;"><li><span style="font-size: 8pt;"><font face="Comic Sans MS"><em>Kafka&nbsp;Brokers</em></font><font face="Trebuchet MS, 宋体">：Kafka的Broker列表，多个Broker以逗号分隔，类似www.broker1.com:9193,www.broker2.com:9193这样的形式配置</font></span></li><li><span style="font-size: 8pt;"><font face="Comic Sans MS"><em>Topic&nbsp;Name</em></font><font face="Trebuchet MS, 宋体">：需要Consume的Kafka&nbsp;Topic名称</font></span></li><li style="font-family: &quot;Trebuchet MS&quot;, 宋体;">Record&nbsp;Reader/Writer：关于Record所需要设定的Reader和Writer，要先行在Configure中设定，当然也要设定好Schema&nbsp;&nbsp;<br /></li><li><span style="font-size: 8pt;"><font face="Comic Sans MS"><em>Group ID</em></font>：Consumer所要设定的ID，这个的设定要依Kafka的配置来，现在我们一般就只有单个的Partition，所以会要求每个Processor都设定有不同</span></li><li style="font-family: &quot;Trebuchet MS&quot;, 宋体;">Offset&nbsp;Reset：需要设定为"earliest"，这样就会依GroupID没有收过的资料来进行收取，否则就只会收新推上去的资料。第一次玩的兄弟经常坑在GroupID和Offset&nbsp;Reset这两项上，若是收不到资料则有&nbsp; 可能就是GroupID没有换成新的(旧的已经收过一次就不会重新再收)，或者是Offset Reset =&nbsp;latest又没有新资料推上去~~~<br /></li><li><span style="font-size: 8pt;"><font face="Comic Sans MS"><em>Max&nbsp;Poll&nbsp;Records和SCHEDULING中的Run&nbsp;Schedule</em></font>：需要根据实际接收的速度来进行调整。经过观察发现Consume的速度超快，但整个Nifi&nbsp;Flow的速度会卡在其它需要做解析或读写DB的Processor外 (通常解析JSON会是前面的关卡)，所以任由Consumer的高速读取就会造成整个Nifi流程在后段被卡住。造成这个的主要原因其实就在于kafka处理的高速上，所以当有新换GroupID或新流程时，Kafka上积累的海量资料就会在一瞬间被接收下来，然后就是各种红 (其实红了也没事，它会自动向上推，让前一个Processor停止处理)。<br /></span></li><ul><li style="font-family: &quot;Trebuchet MS&quot;, 宋体;">若是常态下的资料推送量就已经超过了你的Nifi处理速度，那么就要考量使用多个线程处理或者是从源头的Kafka上就把资料分割开来&nbsp;&nbsp;</li><li><span style="font-size: 8pt;"><font face="Comic Sans MS"><em>SCHEDULING的Cocurrent Tasks</em></font>：这个Default=1，就是当前Processor需要开起来的线程数。但是这个设置需要当心，你需要仔细考量过你的资料流是否允许乱序 (多线程时当然不可能还能保证资料处理的顺序)，所以它是仅适用于不Care资料处理顺序的场景，例如每笔Key就只会有一笔资料，而且哪笔资料先收后收无所谓</span></li></ul><br /></ul><div style="display: inline-block;"><em style="color: #333333; font-family: &quot;Comic Sans MS&quot;; font-size: 13.3333px; background-color: #ffffff;"><strong>&nbsp; &nbsp; SplitJson</strong></em><span style="color: #333333; font-size: 10pt;">，作用就只是简单的把一个JSON数组切开成单个的JSON。Consume出来的会是个数组，这跟你存放进去的单笔讯息是不是数组没什么关系。</span></div><div><span style="font-size: 13.3333px;"><br /></span><div><em style="font-family: &quot;Comic Sans MS&quot;; font-size: 13.3333px; background-color: #ffffff;"><strong>&nbsp; &nbsp; Connection</strong></em><span style="font-size: 10pt;">，就是Processor中的那根带箭头的连线，它的作用是连接不同的Processor并且它还具有缓存池的的一个用途，除了把数据从A导流向B外，还可以将B暂时处理不动的资料存放在自带的缓存池中，若是缓存池达到上限，则Nifi会自动让A暂停处理直至B缓过劲~~~</span></div></div><blockquote style="margin: 0 0 0 40px; border: none; padding: 0px;"><div><div><span style="font-size: 10pt;"><img src="http://www.blogjava.net/images/blogjava_net/tw-ddm/Connection.png" width="600" height="442" alt="" /></span></div></div></blockquote><div><ul style="font-size: 12px; margin-left: 1em; background-color: #ffffff;"><li><span style="font-size: 8pt;"><em>Back Pressure Object Threshold / Back Pressure Data Size Threshold</em>：最大缓存的消息笔数 /&nbsp;最大缓存消息的体积，两者任一超过就会让上游Processor处理暂停</span></li><li><span style="font-size: 8pt;"><em>Available Prioritizers</em>：出入缓存池的顺序控制，Default是空，通常来说都应该要设成FIFO先进先出的方式</span></li><span style="font-size: 13.3333px;"></span><ul><li><span style="font-size: 8pt;">不设定这个经常会造成Nifi资料处理丢失的假象，A1,A2,A3,A4，最后看到的不是A4而是A3，会让人以为A4被玩掉了，其实只是A4被先处理，而A3变成了最后一笔状态。而且这种错误很难被发现!!</span></li></ul></ul><span style="font-size: 13.3333px;"><br /><br /></span></div><div><div style="display: inline-block;"><em style="color: #333333; font-family: &quot;Comic Sans MS&quot;; font-size: 13.3333px; background-color: #ffffff;"><strong>&nbsp; &nbsp; EvaluateJsonPath1</strong></em><span style="color: #333333; font-size: 10pt;">，这个元件的作用是解析JSON，它也只能简单的解析，想在Value中对取出来的值做一些处理好象是不允许的....</span></div></div><blockquote style="margin: 0 0 0 40px; border: none; padding: 0px;"><div><span style="font-size: 13.3333px;"><img src="http://www.blogjava.net/images/blogjava_net/tw-ddm/EvaluateJsonProcessor1.png" width="600" height="441" alt="" /></span></div></blockquote><div><ul style="font-size: 12px; margin-left: 1em; background-color: #ffffff;"><li><span style="font-size: 8pt;"><em>Destination</em>：表示解析出来的内容是成为Attribute，还是直接替换Flow&nbsp;File内容，这里设定是做为属性，所以Processor处理后就可以在Flow&nbsp;File上看到多出自定义的那些属性以及它们的值</span></li><li><span style="font-size: 8pt;"><em>Return Type</em>：返回值的类型，这种简单从JSON中取值的可以使用Auto-detect即可</span></li><li><span style="font-size: 8pt;"><em>Path&nbsp;Not&nbsp;Found Behavior</em>：是说如果设定需要解析的JSON路径不存在时的处理行为</span></li><li style="font-family: &quot;Trebuchet MS&quot;, 宋体;">Null Value Representation：这个对于Null值的处理， "empty string"会将null设为空字符串(MO=)，另外一个"the string 'null'"则是会将null设为"null"这样的字符串 (MO="null")<br /></li><li><span style="font-size: 8pt;"><em>MO/MODELFAMILY/....</em>：这些是我手工添加的属性名称，需要根据JSON长样来设，对应Value设定的$.MO则是表示MO的值来源于JSON第一层的"MO"节点。</span></li><ul><li><span style="font-size: 8pt;">需要注意的一点是属性名称貌似是会区分大小写的，所以可以看到我全部使用的大写</span></li></ul><li style="font-family: &quot;Trebuchet MS&quot;, 宋体;">截图是运行时态的Procssor，停止运行时PROPERTIES上会有一个 +&nbsp;号，点它即可以新增自己的属性<br /></li><ul><li>有一点比较奇怪的地方，就是通过+号维护进去的多个属性，它们的排列顺序却不是你手工新增的顺序，这点引发另外一处的一个疑问，会在下面讲<br /><br /></li></ul></ul></div><div><div style="display: inline-block;"><em style="color: #333333; font-family: &quot;Comic Sans MS&quot;; font-size: 13.3333px; background-color: #ffffff;"><strong>&nbsp; &nbsp; EvaluateJsonPath2</strong></em><span style="color: #333333; font-size: 10pt;">，当然也是要从JSON中解析，只不过我是要把整个JSON的内容都保留下来，由于它们要求的设定不同，所以被迫要撕成两个元件来做</span></div></div><blockquote style="margin: 0 0 0 40px; border: none; padding: 0px;"><div><span style="font-size: 13.3333px;"><img src="http://www.blogjava.net/images/blogjava_net/tw-ddm/EvaluateJsonProcessor2.png" width="600" height="442" alt="" /></span></div></blockquote><div><ul style="font-size: 12px; margin-left: 1em; background-color: #ffffff;"><li><span style="font-size: 8pt;"><em>Destination</em>：这个设定仍然是属性</span></li><li><span style="font-size: 8pt;"><em>Return Type</em>：json，第一个解析元件虽然可以随意设置，但把这两种合并成一个元件并使用Auto时就会报错，所以看起来第一种简单属性实际上只支持Scalar吧...</span></li><li><span style="font-size: 8pt;"><em>JSONDATA</em>：我定义的一个属性名称，注意Value中设定的"@"符号，它表示整份FlowFile的内容(前面已经转成一个JSON)</span></li><li style="font-family: &quot;Trebuchet MS&quot;, 宋体;"><span style="font-size: 8pt;">这个JSONDATA是因为我的需求，因为Kafka上的资料来源于其它系统，而我其实只需要其中的少量几个栏位 (前一个EvaluateJsonPath解析的那些)，为了备查数据上的其它栏位以及在后续使用，所以才要把整份JSON都保留到DB中去 (说得这么高端，实际的原因却是他们的JSON属性是用程序硬拼字串拼出来的，有的东西实在是在Nifi中搞不出来......)</span></li></ul><span style="font-size: 13.3333px;"><br /></span></div><div><div><div style="display: inline-block;"><span style="color: #333333; font-size: 13.3333px; background-color: #ffffff;"><font face="Comic Sans MS"><strong><em>&nbsp; &nbsp; UpdateAttribute</em></strong></font></span><span style="color: #333333; font-size: 10pt;">，元件用途是对FlowFile的Attrubute进行修改，这里是拿来对解析出来的值进行再加工以及添加新属性</span></div></div></div><blockquote style="margin: 0 0 0 40px; border: none; padding: 0px;"><div><div><div style="display: inline-block;"><span style="color: #333333; font-size: 10pt;"><img src="http://www.blogjava.net/images/blogjava_net/tw-ddm/UpdateAttributeProcessor1.png" width="600" height="441" alt="" /></span></div></div></div></blockquote><div><div><div style="display: inline-block;"><ul style="font-size: 12px; margin-left: 1em; background-color: #ffffff;"><li><span style="font-size: 8pt;"><em>Delete&nbsp;Attributes&nbsp;Expression</em>：这个属性如果有设置就表示该Processor为Delete属性的状态，会忽略你新加的那些属性处理，只专心做好一件事"删除符合条件的属性"</span></li><li><span style="font-size: 8pt;"><em>PROVIDER</em>：这是一个新的属性，它并没有包含在JSON中，是为表示数据来源而新加的</span></li><li><span style="font-size: 8pt;"><em>SO</em>：这个就是前面<div style="display: inline-block;">EvaluateJsonPath1解析出来的某个值，那个元件无法直接加工，所以放在这里做的二次加工，去掉前导0</div></span></li></ul><span style="color: #333333; font-size: 10pt;"><br /></span></div></div></div><div><div><div style="display: inline-block;"><span style="color: #333333; font-size: 13.3333px; background-color: #ffffff;"><font face="Comic Sans MS"><strong><em>&nbsp; &nbsp; AttributesToJson</em></strong></font></span><span style="color: #333333; font-size: 10pt;">，作用是将一堆Attribute转换为Json，当然就只能是那种简单结构的Json，这里使用它是为了配合后面一个Processor的使用</span></div></div></div><blockquote style="margin: 0 0 0 40px; border: none; padding: 0px;"><div><div><div style="display: inline-block;"><span style="color: #333333; font-size: 10pt;"><img src="http://www.blogjava.net/images/blogjava_net/tw-ddm/AttributeToJsonProcessor.png" width="600" height="441" alt="" /></span></div></div></div></blockquote><div><div style="display: inline-block;"><ul style="margin-left: 1em; background-color: #ffffff;"><li style="font-size: 12px;"><span style="font-size: 8pt;"><em>Attributes&nbsp;List</em>：拿来生成JSON的属性列表，这里我其实把EvaluateJsonPath1、EvaluateJsonPath2和UpdateAttribute产生的属性都放上去了 (它们就是我落地MariaDB的Table列)</span></li><ul><li><span style="font-size: 10.6667px;">不得不说的一个灰常遗憾的结果：那就是生成的JSON属性顺序绝对不是你在List中写的属性顺序，我比较怀疑是在前面几个组件生成Attribute的顺序，但更让人遗憾的是它们的顺序也不会是你维护它们的顺序。这个结果会导致我们在另外的Case 2中会碰到一个不可逾越的障碍~~~~</span></li></ul><li style="font-size: 12px;"><span style="font-size: 8pt;"><em>Attributes Regular Expression</em>：符合条件的正则表达式</span></li><li style="font-size: 12px;"><span style="font-size: 8pt;"><em>Destination</em>： 这个属性在&nbsp;<div style="display: inline-block;">EvaluateJsonPath上</div><div style="display: inline-block;">就有， 它可以让结果成为一个新的属性还是直接替换FlowFile的内容， Default是直接换掉FlowFile的内容。</div></span></li></ul><span style="color: #333333; font-size: 10pt;"></span><span style="color: #333333; font-size: 10pt;"><br /></span></div></div><span style="color: #333333; font-size: 13.3333px; background-color: #ffffff;"><font face="Comic Sans MS"><strong><em>&nbsp; &nbsp; ConvertJsonToSQL</em></strong></font></span><span style="color: #333333; font-size: 10pt;">，作用是根据JSON内容转换成SQL语句以及语句所要的参数，经过这一关后FlowFile的内容就变成SQL语句，然后Attribute中多出一些参数</span><br /><blockquote style="margin: 0px 0px 0px 40px; border: none; padding: 0px;"><div style="display: inline-block;"><span style="color: #333333; font-size: 10pt;"><img src="http://www.blogjava.net/images/blogjava_net/tw-ddm/ConvertJsonToSqlProcessor.png" width="600" height="441" alt="" /><br /></span></div></blockquote><div><div><div style="display: inline-block;"><ul style="font-size: 12px; margin-left: 1em; background-color: #ffffff;"><li><span style="font-size: 8pt;"><em>JDBC&nbsp;Connection&nbsp;Pool</em>：Configrue中指定的MariaDB连接字符串，那里直接有指定Schema</span></li><li><span style="font-size: 8pt;"><em>Statement Type：</em>这个有INSERT、UPDATE、DELETE这3个选项，若是Mongo的那个组件就会看到有UPSERT(Update or Insert)，其它各类的都木有~~~，这里我使用的是INSERT选项，后面通过玩的一点小花招把它再折腾为REPLACE&nbsp;INTO</span></li><li><span style="font-size: 8pt;"><em>Update&nbsp;Keys</em>： 这个属性可以不填，它是For&nbsp;Update时使用的<div style="display: inline-block;">。</div></span></li><li><span style="font-size: 8pt;"><em>SQL&nbsp;Parameter&nbsp;Attribute&nbsp;Prefix</em>： default =&nbsp;sql，它其实影响到组件处理后生成的SQL语句参数叫什么，设为sql，最后就会看到生成出来<div style="display: inline-block;">。如下图就是处理后的Attribute样式，它会产生sql.args.X.type和sql.args.X.value，这一组合起来就对应于SQL中第一个?参数的类型及值，&#8221;sql"就是我们这里设置的前缀名称 (充分考虑到大家会想要搞事)</div></span></li></ul><span style="color: #333333; font-size: 10pt;"></span></div></div></div><blockquote style="margin: 0 0 0 40px; border: none; padding: 0px;"><img src="http://www.blogjava.net/images/blogjava_net/tw-ddm/SQLParmeters.png" width="600" height="406" alt="" /><br /></blockquote><div><div><div style="display: inline-block;"><span style="color: #333333; font-size: 10pt;"><div style="display: inline-block;"></div></span></div></div></div><div><div><div style="display: inline-block;"><div><div style="display: inline-block;"><span style="color: #333333; font-size: 13.3333px; background-color: #ffffff;"><font face="Comic Sans MS"><strong><em>&nbsp; &nbsp; ReplaceText</em></strong></font></span><span style="color: #333333; font-size: 10pt;">，作用是文本替换，这里就是我们处理Update&nbsp;Or&nbsp;Insert的关键，直接把SQL语句换掉它</span></div></div></div></div></div><blockquote style="margin: 0 0 0 40px; border: none; padding: 0px;"><div><div><div style="display: inline-block;"><div><div style="display: inline-block;"><span style="color: #333333; font-size: 10pt;"><div style="display: inline-block;"><img src="http://www.blogjava.net/images/blogjava_net/tw-ddm/ReplaceTextProcessor.png" width="600" height="441" alt="" /></div></span></div></div></div></div></div></blockquote><div><div><div style="display: inline-block;"><div><div style="display: inline-block;"><ul style="font-size: 12px; margin-left: 1em; background-color: #ffffff;"><li><span style="font-size: 8pt;"><em>Search Value</em>：在FlowFile中查找的字符串，它支持正则</span></li><li><span style="font-size: 8pt;"><em>Replacement&nbsp;Value：</em>替换的值，这里就是简单的把Insert Into (x1,x2,x3) values (?,?,?)处理为Replace Into (x1,x2,x3) values (?,?,?)而已，Replace&nbsp;Or&nbsp;Insert的行为交给DB去做<br /><br /></span></li></ul><span style="color: #333333; font-size: 10pt;"><div style="display: inline-block;"></div></span></div></div></div></div></div><div><div><div style="display: inline-block;"><div><div style="display: inline-block;"><div><div style="display: inline-block;"><span style="color: #333333; font-size: 13.3333px; background-color: #ffffff;"><font face="Comic Sans MS"><strong><em>&nbsp; &nbsp; PutSQL</em></strong></font></span><span style="color: #333333; font-size: 10pt;">，作用是在DB上执行一段SQL语句</span></div></div></div></div></div></div></div><blockquote style="margin: 0 0 0 40px; border: none; padding: 0px;"><div><div><div style="display: inline-block;"><div style="display: inline-block;"><div><div style="display: inline-block;"><span style="color: #333333; font-size: 10pt;"><img src="http://www.blogjava.net/images/blogjava_net/tw-ddm/PutSQLProcessor.png" width="600" height="444" alt="" /></span></div></div></div></div></div></div></blockquote><div><div><div style="display: inline-block;"><div style="display: inline-block;"><ul style="font-size: 12px; margin-left: 1em; background-color: #ffffff;"><li><span style="font-size: 8pt;"><em>JDBC&nbsp;Connection&nbsp;Pool</em>：前面ConvertJsonToSQL转换SQL时就有用过，指定数据库的连接</span></li><li><span style="font-size: 8pt;"><em>SQL&nbsp;Statement：</em>需执行的SQL，为空时表示使用前面传递过来的FlowFile的内容(已经是一个SQL语句)<br /><br /></span></li></ul><div><br /><h5>总结：</h5></div></div></div></div><span style="font-size: 10pt;">&nbsp; &nbsp; 这是一个带有Key值(多个Key列)的无删除行为的资料接收，所以可以利用AttributeToJSON去将提取出来的有用属性重新生成JSON文件，并直接利用ConvertJsonToSQL转换为Insert语句及对应的绑定参数，这里借用了MariaDB提供的Replace&nbsp;Into机制去自动使用表上的Key键去做Update更新，所以整个Nifi&nbsp;Flow还是比较简单。在后续文章中会讲到带Delete行为的资料接收方法以及无Key更新的解决方案</span></div><img src ="http://www.blogjava.net/tw-ddm/aggbug/433711.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/tw-ddm/" target="_blank">大大毛</a> 2019-04-11 14:14 <a href="http://www.blogjava.net/tw-ddm/articles/433711.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Oracle资料推送MQTT</title><link>http://www.blogjava.net/tw-ddm/articles/433709.html</link><dc:creator>大大毛</dc:creator><author>大大毛</author><pubDate>Wed, 10 Apr 2019 07:25:00 GMT</pubDate><guid>http://www.blogjava.net/tw-ddm/articles/433709.html</guid><wfw:comment>http://www.blogjava.net/tw-ddm/comments/433709.html</wfw:comment><comments>http://www.blogjava.net/tw-ddm/articles/433709.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/tw-ddm/comments/commentRss/433709.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/tw-ddm/services/trackbacks/433709.html</trackback:ping><description><![CDATA[<h5><span style="font-size: 10pt;">需求</span></h5>
<blockquote style="margin: 0 0 0 40px; border: none; padding: 0px;"><span style="font-size: 10pt; font-family: Arial;">将资料从Oracle推至MQTT，资料结果使用JSON格式</span><br />
<span style="font-size: 10pt; font-family: Arial;">场景1：直接推送</span><br />
<span style="font-size: 10pt; font-family: Arial;">场景2：仅当资料有变更时才推送</span></blockquote><br />
<h5><span style="font-size: 10pt;">解决方案</span></h5>
<span style="font-size: 10pt; font-family: &quot;Comic Sans MS&quot;;">QueryDatabaseTable --&gt; ConvertAvroToJSON --&gt; PublishMQTT</span><br />
<img src="http://www.blogjava.net/images/blogjava_net/tw-ddm/Oracle2MQTT.png" width="299" height="406" alt="" /><br />
<br />
<h6><span style="font-size: 10pt;">Processor及其设定：</span></h6>
<blockquote style="margin: 0px 0px 0px 40px; border: none; padding: 0px;"><span style="font-size: 10pt; font-family: &quot;Comic Sans MS&quot;;"><em><strong>QueryDatabaseTable</strong></em></span><span style="font-size: 10pt;">，</span><span style="font-size: 10pt; font-family: Arial;">作用是从DB中捞取资料，可以想象Nifi把它转成一个Select语句在执行</span></blockquote><br />
<img src="http://www.blogjava.net/images/blogjava_net/tw-ddm/QueryDbProcessor.png" width="600" height="440" alt="" /><br />
<ul>
     <li><span style="font-size: 8pt; font-family: &quot;Comic Sans MS&quot;;"><em>Database&nbsp;Connection&nbsp;Pooling&nbsp;Service</em></span><span style="font-size: 8pt;">：在Configure中设定的数据库连接，可以在Processor&nbsp;Group中被共用</span></li>
     <li><span style="font-size: 8pt; font-family: &quot;Comic Sans MS&quot;;"><em>Database Type</em></span><span style="font-size: 8pt;">：这个设定的是Oracle。其实它与数据库连接设定有点重叠，那边已经有指定是哪一种类型的DB，这里需要再指定，我想会不会是利用它来生成不同的Select查询语法?</span></li>
     <li><span style="font-size: 8pt; font-family: &quot;Comic Sans MS&quot;;"><em>Table Name</em></span><span style="font-size: 8pt;">：表名，我这里使用的是View名称，View其实就已经对Table做出一些限定，可以挑选列及设定查询条件</span></li>
     <li><span style="font-size: 8pt; font-family: &quot;Comic Sans MS&quot;;"><em>Maximum-value Columns</em></span><span style="font-size: 8pt;">：这个属性很重要，如果不设定则Nifi在查询的时候会捞取所有的资料，如果有设定某个列，则Nifi仅会捞取&#8220;新&#8221;资料</span></li>
     <ul>
         <li><span style="font-size: 8pt;">例如图上设定&#8220;BATCHID&#8221;这个列。第一次Nifi启动时捞取资料中最大BATCHID = 10，则下一次Nifi再次启动时就只会捞取BATCHID&gt;10的资料，并且会自动记录下已经捞过的最大值</span></li>
         <li><span style="font-size: 8pt;">最大值保存在下面这里，Processor上右键选择&#8220;View&nbsp;State&#8221;<br />
         <img src="http://www.blogjava.net/images/blogjava_net/tw-ddm/QueryDb_ViewState1.png" width="440" height="226" alt="" /><br />
         <br />
         </span></li>
         <li><span style="font-size: 8pt;">下图可以看到当前最新的值，点&#8220;Clear&nbsp;State&#8221;则可以将保留值清空（Nifi下次启动时就会捞取所有资料）<br />
         <img src="http://www.blogjava.net/images/blogjava_net/tw-ddm/QueryDb_ViewState2.png" width="600" height="440" alt="" /><br />
         </span></li>
     </ul>
     <li><span style="font-size: 8pt;">对于仅需要简单拉取资料的场景1来说，&#8220;Maximum-value Columns</span><span style="font-size: 8pt;">&#8221;置空即可；而对于仅在资料有更新时才要拉取的场景2来说，则需要设定并且在View中做出一些调整才可以达成</span></li>
     <ul>
         <li><span style="font-size: 8pt;">当有资料更新时，则被更新资料的BatchID会是更新的值，所以只要在View中虚拟BatchID列 = Max(BatchID)，就可以达成有资料更新才要拉取的效果</span></li>
     </ul>
</ul>
<blockquote style="margin: 0 0 0 40px; border: none; padding: 0px;"><span style="font-size: 10pt; font-family: &quot;Comic Sans MS&quot;;"><em><strong>ConvertAvroToJSON</strong></em></span><span style="font-size: 10pt;">，将Avro类型资料转换为JSON，这个可以不用改设定</span></blockquote><br />
<blockquote style="margin: 0px 0px 0px 40px; border: none; padding: 0px;"><span style="font-size: 10pt; font-family: &quot;Comic Sans MS&quot;;"><em><strong>PublishMQTT</strong></em></span><span style="font-size: 10pt;">，将资料Publish到MQTT指定Topic</span></blockquote>
<div><img src="http://www.blogjava.net/images/blogjava_net/tw-ddm/PublishMQTTProcessor.png" width="600" height="382" alt="" /></div>
<ul>
     <li><span style="font-size: 8pt; font-family: &quot;Lucida Console&quot;;"><em>Broker URI</em></span><span style="font-size: 8pt; font-family: &quot;Lucida Console&quot;;">：</span><span style="font-size: 8pt; font-family: Arial;">MQTT的Broker地址</span></li>
     <li><span style="font-size: 8pt; font-family: &quot;Comic Sans MS&quot;;"><em>Client&nbsp;ID</em>：</span><span style="font-size: 8pt; font-family: Arial;">发布MQTT的Client端ID（注意不要多个Processor使用相同的Client&nbsp;ID，这样Processor容易被卡死）</span></li>
     <li><span style="font-size: 8pt; font-family: &quot;Comic Sans MS&quot;;"><em>Topic</em>：</span><span style="font-size: 8pt; font-family: Arial;">发布至MQTT的Topic名称</span></li>
     <li><span style="font-size: 8pt;"><em style="font-family: &quot;Comic Sans MS&quot;;">Retain&nbsp;Message</em>：</span><span style="font-size: 8pt; font-family: Arial;">MQTT的遗言属性，即是否保留推送的消息（若设为false，则仅有当前连上MQTT的客户端才能收到这笔消息）</span></li>
</ul>
<br />
<img src ="http://www.blogjava.net/tw-ddm/aggbug/433709.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/tw-ddm/" target="_blank">大大毛</a> 2019-04-10 15:25 <a href="http://www.blogjava.net/tw-ddm/articles/433709.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>