<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:blogger='http://schemas.google.com/blogger/2008' xmlns:georss='http://www.georss.org/georss' xmlns:gd="http://schemas.google.com/g/2005" xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-6819483672105967231</id><updated>2026-08-10T22:29:08.797+08:00</updated><category term="Java"/><category term="Linux"/><category term="Amazon"/><category term="Python3"/><category term="Jersey"/><category term="Android"/><category term="MongoDB"/><category term="Spring"/><category term="Vespa"/><category term="Cloud"/><category term="Maven"/><category term="Markdown Here"/><category term="Security"/><category term="logback"/><category term="Spring Boot"/><category term="Windows"/><category term="jQuery"/><category term="Ethereum"/><category term="OpenStack"/><category term="Python2"/><category term="JavaMail"/><category term="Spring Data JPA"/><category term=".NET"/><category term="AWS"/><category term="DynamoDB"/><category term="JUnit"/><category term="Tomcat"/><category term="Apache HttpClient"/><category term="Concurrency"/><category term="Foswiki"/><category term="HTTPS"/><category term="Java GUI"/><category term="JavaScript"/><category term="Jetty"/><category term="MySQL"/><category term="Reactive Programming"/><category term="SWT"/><category term="Android ROM"/><category term="Architecture"/><category term="JMeter"/><category term="PostgreSQL"/><category term="RESTful"/><category term="SSL"/><category term="docker"/><category term="AWS SDK v2"/><category term="Ceph"/><category term="Design Pattern"/><category term="Distributed Data Store"/><category term="Elasticsearch"/><category term="Google Map"/><category term="Java dictionary"/><category term="Spark"/><category term="Splunk"/><category term="Yahoo"/><category term="eclipse"/><category term="slf4j"/><category term="AI"/><category term="Blockchain"/><category term="Crawler"/><category term="Groovy"/><category term="Gson"/><category term="SQLite"/><category term="Spring MVC"/><category term="VMware"/><category term="dom4j"/><category term="git"/><category term="log4j"/><category term="AOP"/><category term="Algorithm"/><category term="Apache"/><category term="Athenz"/><category term="C/C++"/><category term="Django"/><category term="FortiGate"/><category term="GCP"/><category term="GlusterFS"/><category term="Guice"/><category term="JAX-RS 2.x"/><category term="JDBM"/><category term="JSR-310"/><category term="JUnit 5"/><category term="Jackson"/><category term="Java Collection"/><category term="License"/><category term="OpenTelemetry"/><category term="PHP"/><category term="Regular Expression"/><category term="Scala"/><category term="Selenium"/><category term="Spring WebFlux"/><category term="Stable Diffusion"/><category term="WEKA"/><category term="dnsjava"/><category term="jNetPcap"/><category term="pyVmomi"/><category term="Access"/><category term="AngularJS"/><category term="Apache Commons CLI"/><category term="Apache Commons FileUpload"/><category term="Apache Commons Net"/><category term="Apache Nutch"/><category term="Apache POI"/><category term="AutoValue"/><category term="Bouncy Castle"/><category term="Bugzilla"/><category term="CORS"/><category term="Clean Code"/><category term="Continuous Delivery"/><category term="Continuous Integration"/><category term="Cucumber"/><category term="DDD"/><category term="DNS"/><category term="ECS"/><category term="Embedding"/><category term="Fargate"/><category term="Github Copilot"/><category term="Gitlab"/><category term="GoDaddy"/><category term="Gradle"/><category term="HTML5"/><category term="HTTP"/><category term="Hardware"/><category term="Hibernate"/><category term="HtmlUnit"/><category term="Information Retrieval"/><category term="JFace"/><category term="JNI"/><category term="JXL"/><category term="Jasypt"/><category term="Jest"/><category term="Jupyter"/><category term="Keras"/><category term="MapStruct"/><category term="Mesos"/><category term="Microsoft"/><category term="NFS"/><category term="Ollama"/><category term="PPPoE"/><category term="Privoxy"/><category term="RFC"/><category term="RabbitMQ"/><category term="Reactor"/><category term="SVN"/><category term="Server-Side Rendering"/><category term="Smart Home"/><category term="Spock"/><category term="Spring Cache"/><category term="Storm"/><category term="Telnet"/><category term="TensorFlow"/><category term="VS Code"/><category term="Virtual Machine"/><category term="WireShark"/><category term="Word"/><category term="YARN"/><category term="dd-wrt"/><category term="encoding"/><category term="guava"/><category term="http4s"/><category term="jclouds"/><category term="jspf"/><category term="logstash"/><category term="microservice"/><title type='text'>黑毛到白毛的攻城獅之路</title><subtitle type='html'>&lt;p&gt;Software entities (class, modules, functions, etc.) should be open for extension, but closed for modification.&lt;br&gt;- Bertrand Meyer&lt;/p&gt;&#xa;&lt;p&gt;Junior programmers create simple solutions to simple problems. Senior programmers create complex solutions to complex problems. Great programmers find simple solutions to complex problems.&lt;br&gt;- Charles Connell&lt;/p&gt;&#xa;&lt;p&gt;註1：本部落格的範例程式碼在 2015 年以前的文章中，大多是以全型空白做縮排。如需服用，請自行用文字編輯器的取代功能把全型空白取代成半型空白。&lt;br&gt;&#xa;註2：本部落格的內容授權請參閱部落格底部的授權宣告。&lt;/p&gt;</subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://jimwayne.blogspot.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6819483672105967231/posts/default?redirect=false'/><link rel='alternate' type='text/html' href='http://jimwayne.blogspot.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><link rel='next' type='application/atom+xml' href='http://www.blogger.com/feeds/6819483672105967231/posts/default?start-index=26&amp;max-results=25&amp;redirect=false'/><author><name>Wayne Yeh</name><uri>http://www.blogger.com/profile/02148905864753873930</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>595</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>25</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-6819483672105967231.post-3193224227758027601</id><published>2026-08-10T22:26:18.088+08:00</published><updated>2026-08-10T22:29:08.797+08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="Vespa"/><title type='text'>Vespa groups 的使用時機</title><content type='html'>&lt;div class=&quot;markdown-here-wrapper&quot; data-md-url=&quot;https://www.blogger.com/blog/post/edit/6819483672105967231/3193224227758027601&quot;&gt;&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;很久沒寫部落格了，因為現在在 AI 時代，其實有點覺得找不到寫部落格的動機 XD…不過因為去年到今年持續使用 Vespa 的 groups 時，遇到一些問題而因此跟 Vespa 團隊有不少討論，簡單紀錄一下這些經驗和結論。然後先承認這篇是我寫完簡單的草稿之後給 AI 幫我補細節的，因為我懶得再自己去找範例跟畫表格 XDDD。&lt;/p&gt;
&lt;h1 id=&quot;vespa-flat-mode-group-mode-scaling-&quot; style=&quot;border-bottom: 1px solid rgb(221, 221, 221); font-size: 1.6em; font-weight: bold; margin: 1.3em 0px 1em; padding: 0px;&quot;&gt;Vespa 的 flat mode、group mode 與 scaling 策略&lt;/h1&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;首先是什麼時候應該使用 flat mode，什麼時候應該使用 group mode？&lt;/p&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;大體上的經驗是，content node 開到 50 個以上時，就應該開始考慮轉成 group mode。group mode 下，每個 group 都保存一份完整資料，而 Vespa 會把一個 query 只送到其中一個 group，再由該 group 內的 content nodes 平行處理。因此，增加 group 可以增加 query throughput，同時避免每個 query 都要 fan-out 到整個 cluster。&lt;/p&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;再來，使用 group mode 時，應該要：&lt;/p&gt;
&lt;ol style=&quot;margin: 1.2em 0px; padding-left: 2em;&quot;&gt;
&lt;li style=&quot;margin: 0.5em 0px;&quot;&gt;依 group 數量 scaling&lt;/li&gt;
&lt;li style=&quot;margin: 0.5em 0px;&quot;&gt;依 group 內 node 數量 scaling&lt;/li&gt;
&lt;li style=&quot;margin: 0.5em 0px;&quot;&gt;兩者同時啟用&lt;/li&gt;
&lt;/ol&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;簡要答案是：看情況，但幾乎沒有理由使用 (3)，大致上是 (1) 或 (2) 選一個。&lt;/p&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;不使用 (3) 的主要原因是難以預測。當 scaling 行為觸發時，增加 group 或增加 group 內 node，都有可能改善當前資源壓力；但很難預測 Vespa 最後會採用哪一種拓撲。因此 query latency、資料重分布量，以及成本的變化都會比較不穩定。&lt;/p&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;以下範例以 Vespa Cloud 的 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;services.xml&lt;/code&gt; 為例：&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; border-spacing: 0px; border: 0px; font: inherit; margin: 1.2em 0px; padding: 0px;&quot;&gt;
&lt;thead&gt;
&lt;tr style=&quot;background-color: white; border-color: rgb(204, 204, 204) currentcolor currentcolor; border-image: none; border-style: solid none none; border-width: 1px 0px 0px; margin: 0px; padding: 0px;&quot;&gt;
&lt;th style=&quot;background-color: #f0f0f0; border: 1px solid rgb(204, 204, 204); font-size: 1em; font-weight: bold; margin: 0px; padding: 0.5em 1em;&quot;&gt;策略&lt;/th&gt;
&lt;th style=&quot;background-color: #f0f0f0; border: 1px solid rgb(204, 204, 204); font-size: 1em; font-weight: bold; margin: 0px; padding: 0.5em 1em;&quot;&gt;Vespa 設定範例&lt;/th&gt;
&lt;th style=&quot;background-color: #f0f0f0; border: 1px solid rgb(204, 204, 204); font-size: 1em; font-weight: bold; margin: 0px; padding: 0.5em 1em;&quot;&gt;Scaling 時發生什麼事&lt;/th&gt;
&lt;th style=&quot;background-color: #f0f0f0; border: 1px solid rgb(204, 204, 204); font-size: 1em; font-weight: bold; margin: 0px; padding: 0.5em 1em;&quot;&gt;對 query latency 的影響&lt;/th&gt;
&lt;th style=&quot;background-color: #f0f0f0; border: 1px solid rgb(204, 204, 204); font-size: 1em; font-weight: bold; margin: 0px; padding: 0.5em 1em;&quot;&gt;對 memory / 資料容量的影響&lt;/th&gt;
&lt;th style=&quot;background-color: #f0f0f0; border: 1px solid rgb(204, 204, 204); font-size: 1em; font-weight: bold; margin: 0px; padding: 0.5em 1em;&quot;&gt;適合情境&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody style=&quot;border: 0px; margin: 0px; padding: 0px;&quot;&gt;
&lt;tr style=&quot;background-color: white; border-color: rgb(204, 204, 204) currentcolor currentcolor; border-image: none; border-style: solid none none; border-width: 1px 0px 0px; margin: 0px; padding: 0px;&quot;&gt;
&lt;td style=&quot;border: 1px solid rgb(204, 204, 204); font-size: 1em; margin: 0px; padding: 0.5em 1em;&quot;&gt;(1) 只依 group 數量 scaling&lt;/td&gt;
&lt;td style=&quot;border: 1px solid rgb(204, 204, 204); font-size: 1em; margin: 0px; padding: 0.5em 1em;&quot;&gt;&lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;&amp;lt;nodes groups=&quot;[2, 4]&quot; group-size=&quot;8&quot;&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;border: 1px solid rgb(204, 204, 204); font-size: 1em; margin: 0px; padding: 0.5em 1em;&quot;&gt;每個 group 固定 8 台 node；Vespa 在 2 到 4 個 group 之間擴縮。&lt;/td&gt;
&lt;td style=&quot;border: 1px solid rgb(204, 204, 204); font-size: 1em; margin: 0px; padding: 0.5em 1em;&quot;&gt;最穩定。新增一個 group 後，每個 query 仍只會查固定的 8 台 node。&lt;/td&gt;
&lt;td style=&quot;border: 1px solid rgb(204, 204, 204); font-size: 1em; margin: 0px; padding: 0.5em 1em;&quot;&gt;現有 group 的 node memory 使用率不會下降，因為每個新 group 都必須保存完整資料；只是整體 memory 與成本會隨 group 數增加。&lt;/td&gt;
&lt;td style=&quot;border: 1px solid rgb(204, 204, 204); font-size: 1em; margin: 0px; padding: 0.5em 1em;&quot;&gt;query 流量變動大、希望擴縮後 latency 行為可預期的情境。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background-color: #f8f8f8; background-color: white; border-color: rgb(204, 204, 204) currentcolor currentcolor; border-image: none; border-style: solid none none; border-width: 1px 0px 0px; margin: 0px; padding: 0px;&quot;&gt;
&lt;td style=&quot;border: 1px solid rgb(204, 204, 204); font-size: 1em; margin: 0px; padding: 0.5em 1em;&quot;&gt;(2) 只依 group 內 node 數量 scaling&lt;/td&gt;
&lt;td style=&quot;border: 1px solid rgb(204, 204, 204); font-size: 1em; margin: 0px; padding: 0.5em 1em;&quot;&gt;&lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;&amp;lt;nodes groups=&quot;3&quot; group-size=&quot;[8, 32]&quot;&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;border: 1px solid rgb(204, 204, 204); font-size: 1em; margin: 0px; padding: 0.5em 1em;&quot;&gt;group 數固定為 3；總 node 數在 8 到 32 台之間變動，因此每個 group 的 node 數會增加或減少。&lt;/td&gt;
&lt;td style=&quot;border: 1px solid rgb(204, 204, 204); font-size: 1em; margin: 0px; padding: 0.5em 1em;&quot;&gt;有些微影響。group 變大時，query 要 fan-out 給更多 content node；scale-out 幅度越大，影響通常也越明顯。&lt;/td&gt;
&lt;td style=&quot;border: 1px solid rgb(204, 204, 204); font-size: 1em; margin: 0px; padding: 0.5em 1em;&quot;&gt;每個 group 的資料被分到更多 node，單一 node 的 memory、disk 與 CPU 壓力都會下降。&lt;/td&gt;
&lt;td style=&quot;border: 1px solid rgb(204, 204, 204); font-size: 1em; margin: 0px; padding: 0.5em 1em;&quot;&gt;query 流量相對穩定，但資料量、index size 或 memory 壓力會持續變動的情境。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background-color: white; border-color: rgb(204, 204, 204) currentcolor currentcolor; border-image: none; border-style: solid none none; border-width: 1px 0px 0px; margin: 0px; padding: 0px;&quot;&gt;
&lt;td style=&quot;border: 1px solid rgb(204, 204, 204); font-size: 1em; margin: 0px; padding: 0.5em 1em;&quot;&gt;(3) 同時依 group 與 group 內 node 數量 scaling&lt;/td&gt;
&lt;td style=&quot;border: 1px solid rgb(204, 204, 204); font-size: 1em; margin: 0px; padding: 0.5em 1em;&quot;&gt;&lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;&amp;lt;nodes groups=&quot;[2, 4]&quot; group-size=&quot;[8, 32]&quot;&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;border: 1px solid rgb(204, 204, 204); font-size: 1em; margin: 0px; padding: 0.5em 1em;&quot;&gt;Vespa 可以同時改變 group 數與每組 node 數。&lt;/td&gt;
&lt;td style=&quot;border: 1px solid rgb(204, 204, 204); font-size: 1em; margin: 0px; padding: 0.5em 1em;&quot;&gt;最難預測：可能是增加 group，也可能是增加每組 node，兩者對 latency 的效果不同。&lt;/td&gt;
&lt;td style=&quot;border: 1px solid rgb(204, 204, 204); font-size: 1em; margin: 0px; padding: 0.5em 1em;&quot;&gt;同樣難預測：可能新增完整資料副本，也可能只是把既有 group 的資料分散到更多 node。&lt;/td&gt;
&lt;td style=&quot;border: 1px solid rgb(204, 204, 204); font-size: 1em; margin: 0px; padding: 0.5em 1em;&quot;&gt;通常不建議。除非已經理解 autoscaler 的實際行為，並可接受 topology 變動的不確定性。&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;(1) 的 group 組成是固定的，scaling 是增加或減少 group。因為每個 group 都要包含完整資料，所以增減 group 不會降低既有 node 的 memory 使用率；scale-out 的本質是增加一整份資料副本。不過 query 每次面對的 node 數不變，因此 latency 表現最穩定。&lt;/p&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;(2) 的 group 個數不變，但增加或減少 group 內的 node 個數。Vespa 會把 query 送給被選中的 group 內所有 content node，所以 group 內 node 越多，query 的 fan-out 對象也越多，對 latency 會有些微影響。不過相對地，資料會分散到更多 node，單一 node 的 memory 與 disk 壓力會下降，因此比較適合查詢流量穩定、但資料量變動大的情境。&lt;/p&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;最後一個實務上的提醒：如果最終確實需要同時改變 group 數與 group size，建議拆成兩次調整，中間等資料重分布完成。這樣比較容易維持完整 query coverage，也比較容易觀察每次 topology 變動帶來的影響。&lt;/p&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;參考資料：&lt;/p&gt;
&lt;ul style=&quot;margin: 1.2em 0px; padding-left: 2em;&quot;&gt;
&lt;li style=&quot;margin: 0.5em 0px;&quot;&gt;&lt;a href=&quot;https://docs.vespa.ai/en/operations/autoscaling.html&quot;&gt;Vespa Autoscaling&lt;/a&gt;&lt;/li&gt;
&lt;li style=&quot;margin: 0.5em 0px;&quot;&gt;&lt;a href=&quot;https://docs.vespa.ai/en/content/elasticity.html&quot;&gt;Vespa Content Cluster Elasticity&lt;/a&gt;&lt;/li&gt;
&lt;li style=&quot;margin: 0.5em 0px;&quot;&gt;&lt;a href=&quot;https://docs.vespa.ai/en/performance/topology-and-resizing&quot;&gt;Vespa Topology and Resizing&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div style=&quot;font-size: 0em; height: 0; margin: 0; max-height: 0; max-width: 0; overflow: hidden; padding: 0; width: 0;&quot; title=&quot;MDH:PHA+5b6I5LmF5rKS5a+r6YOo6JC95qC85LqG77yM5Zug54K654++5Zyo5ZyoIEFJIOaZguS7o++8
jOWFtuWvpuaciem7nuimuuW+l+aJvuS4jeWIsOWvq+mDqOiQveagvOeahOWLleapnyBYRC4uLuS4
jemBjuWboOeCuuWOu+W5tOWIsOS7iuW5tOaMgee6jOS9v+eUqCBWZXNwYSDnmoQgZ3JvdXBzIOaZ
gu+8jOmBh+WIsOS4gOS6m+WVj+mhjOiAjOWboOatpOi3nyBWZXNwYSDlnJjpmormnInkuI3lsJHo
qI7oq5bvvIznsKHllq7ntIDpjITkuIDkuIvpgJnkupvntpPpqZflkozntZDoq5bjgILnhLblvozl
hYjmib/oqo3pgJnnr4fmmK/miJHlr6vlroznsKHllq7nmoTojYnnqL/kuYvlvozntaYgQUkg5bmr
5oiR6KOc57Sw56+A55qE77yM5Zug54K65oiR5oe25b6X5YaN6Ieq5bex5Y675om+56+E5L6L6Lef
55Wr6KGo5qC8IFhERETjgII8YnI+PGJyPiMgVmVzcGEg55qEIGZsYXQgbW9kZeOAgWdyb3VwIG1v
ZGUg6IiHIHNjYWxpbmcg562W55WlPGJyPjxicj7pppblhYjmmK/ku4DpurzmmYLlgJnmh4noqbLk
vb/nlKggZmxhdCBtb2Rl77yM5LuA6bq85pmC5YCZ5oeJ6Kmy5L2/55SoIGdyb3VwIG1vZGXvvJ88
YnI+PGJyPuWkp+mrlOS4iueahOe2k+mpl+aYr++8jGNvbnRlbnQgbm9kZSDplovliLAgNTAg5YCL
5Lul5LiK5pmC77yM5bCx5oeJ6Kmy6ZaL5aeL6ICD5oWu6L2J5oiQIGdyb3VwIG1vZGXjgIJncm91
cCBtb2RlIOS4i++8jOavj+WAiyBncm91cCDpg73kv53lrZjkuIDku73lrozmlbTos4fmlpnvvIzo
gIwgVmVzcGEg5pyD5oqK5LiA5YCLIHF1ZXJ5IOWPqumAgeWIsOWFtuS4reS4gOWAiyBncm91cO+8
jOWGjeeUseipsiBncm91cCDlhafnmoQgY29udGVudCBub2RlcyDlubPooYzomZXnkIbjgILlm6Dm
raTvvIzlop7liqAgZ3JvdXAg5Y+v5Lul5aKe5YqgIHF1ZXJ5IHRocm91Z2hwdXTvvIzlkIzmmYLp
gb/lhY3mr4/lgIsgcXVlcnkg6YO96KaBIGZhbi1vdXQg5Yiw5pW05YCLIGNsdXN0ZXLjgII8YnI+
PGJyPuWGjeS+hu+8jOS9v+eUqCBncm91cCBtb2RlIOaZgu+8jOaHieipsuimge+8mjxicj48YnI+
MS4g5L6dIGdyb3VwIOaVuOmHjyBzY2FsaW5nPGJyPjIuIOS+nSBncm91cCDlhacgbm9kZSDmlbjp
h48gc2NhbGluZzxicj4zLiDlhanogIXlkIzmmYLllZ/nlKg8YnI+PGJyPuewoeimgeetlOahiOaY
r++8mueci+aDheazge+8jOS9huW5vuS5juaykuacieeQhueUseS9v+eUqCAoMynvvIzlpKfoh7Tk
uIrmmK8gKDEpIOaIliAoMikg6YG45LiA5YCL44CCPGJyPjxicj7kuI3kvb/nlKggKDMpIOeahOS4
u+imgeWOn+WboOaYr+mbo+S7pemgkOa4rOOAgueVtiBzY2FsaW5nIOihjOeCuuinuOeZvOaZgu+8
jOWinuWKoCBncm91cCDmiJblop7liqAgZ3JvdXAg5YWnIG5vZGXvvIzpg73mnInlj6/og73mlLnl
loTnlbbliY3os4fmupDlo5PlipvvvJvkvYblvojpm6PpoJDmuKwgVmVzcGEg5pyA5b6M5pyD5o6h
55So5ZOq5LiA56iu5ouT5pKy44CC5Zug5q2kIHF1ZXJ5IGxhdGVuY3njgIHos4fmlpnph43liIbl
uIPph4/vvIzku6Xlj4rmiJDmnKznmoTororljJbpg73mnIPmr5TovIPkuI3nqanlrprjgII8YnI+
PGJyPuS7peS4i+evhOS+i+S7pSBWZXNwYSBDbG91ZCDnmoQgYHNlcnZpY2VzLnhtbGAg54K65L6L
77yaPGJyPjxicj58IOetlueVpSB8IFZlc3BhIOioreWumuevhOS+iyB8IFNjYWxpbmcg5pmC55m8
55Sf5LuA6bq85LqLIHwg5bCNIHF1ZXJ5IGxhdGVuY3kg55qE5b2x6Z+/IHwg5bCNIG1lbW9yeSAv
IOizh+aWmeWuuemHj+eahOW9semfvyB8IOmBqeWQiOaDheWigyB8PGJyPnwgLS0tIHwgLS0tIHwg
LS0tIHwgLS0tIHwgLS0tIHwgLS0tIHw8YnI+fCAoMSkg5Y+q5L6dIGdyb3VwIOaVuOmHjyBzY2Fs
aW5nIHwgYCZsdDtub2RlcyBncm91cHM9IlsyLCA0XSIgZ3JvdXAtc2l6ZT0iOCImZ3Q7YCB8IOav
j+WAiyBncm91cCDlm7rlrpogOCDlj7Agbm9kZe+8m1Zlc3BhIOWcqCAyIOWIsCA0IOWAiyBncm91
cCDkuYvplpPmk7TnuK7jgIIgfCDmnIDnqanlrprjgILmlrDlop7kuIDlgIsgZ3JvdXAg5b6M77yM
5q+P5YCLIHF1ZXJ5IOS7jeWPquacg+afpeWbuuWumueahCA4IOWPsCBub2Rl44CCIHwg54++5pyJ
IGdyb3VwIOeahCBub2RlIG1lbW9yeSDkvb/nlKjnjofkuI3mnIPkuIvpmY3vvIzlm6Dngrrmr4/l
gIvmlrAgZ3JvdXAg6YO95b+F6aCI5L+d5a2Y5a6M5pW06LOH5paZ77yb5Y+q5piv5pW06auUIG1l
bW9yeSDoiIfmiJDmnKzmnIPpmqggZ3JvdXAg5pW45aKe5Yqg44CCIHwgcXVlcnkg5rWB6YeP6K6K
5YuV5aSn44CB5biM5pyb5pO057iu5b6MIGxhdGVuY3kg6KGM54K65Y+v6aCQ5pyf55qE5oOF5aKD
44CCIHw8YnI+fCAoMikg5Y+q5L6dIGdyb3VwIOWFpyBub2RlIOaVuOmHjyBzY2FsaW5nIHwgYCZs
dDtub2RlcyBncm91cHM9IjMiIGdyb3VwLXNpemU9Ils4LCAzMl0iJmd0O2AgfCBncm91cCDmlbjl
m7rlrprngrogM++8m+e4vSBub2RlIOaVuOWcqCA4IOWIsCAzMiDlj7DkuYvplpPororli5XvvIzl
m6DmraTmr4/lgIsgZ3JvdXAg55qEIG5vZGUg5pW45pyD5aKe5Yqg5oiW5rib5bCR44CCIHwg5pyJ
5Lqb5b6u5b2x6Z+/44CCZ3JvdXAg6K6K5aSn5pmC77yMcXVlcnkg6KaBIGZhbi1vdXQg57Wm5pu0
5aSaIGNvbnRlbnQgbm9kZe+8m3NjYWxlLW91dCDluYXluqbotorlpKfvvIzlvbHpn7/pgJrluLjk
uZ/otormmI7poa/jgIIgfCDmr4/lgIsgZ3JvdXAg55qE6LOH5paZ6KKr5YiG5Yiw5pu05aSaIG5v
ZGXvvIzllq7kuIAgbm9kZSDnmoQgbWVtb3J544CBZGlzayDoiIcgQ1BVIOWjk+WKm+mDveacg+S4
i+mZjeOAgiB8IHF1ZXJ5IOa1gemHj+ebuOWwjeepqeWumu+8jOS9huizh+aWmemHj+OAgWluZGV4
IHNpemUg5oiWIG1lbW9yeSDlo5PlipvmnIPmjIHnuozororli5XnmoTmg4XlooPjgIIgfDxicj58
ICgzKSDlkIzmmYLkvp0gZ3JvdXAg6IiHIGdyb3VwIOWFpyBub2RlIOaVuOmHjyBzY2FsaW5nIHwg
YCZsdDtub2RlcyBncm91cHM9IlsyLCA0XSIgZ3JvdXAtc2l6ZT0iWzgsIDMyXSImZ3Q7YCB8IFZl
c3BhIOWPr+S7peWQjOaZguaUueiuiiBncm91cCDmlbjoiIfmr4/ntYQgbm9kZSDmlbjjgIIgfCDm
nIDpm6PpoJDmuKzvvJrlj6/og73mmK/lop7liqAgZ3JvdXDvvIzkuZ/lj6/og73mmK/lop7liqDm
r4/ntYQgbm9kZe+8jOWFqeiAheWwjSBsYXRlbmN5IOeahOaViOaenOS4jeWQjOOAgiB8IOWQjOao
o+mbo+mgkOa4rO+8muWPr+iDveaWsOWinuWujOaVtOizh+aWmeWJr+acrO+8jOS5n+WPr+iDveWP
quaYr+aKiuaXouaciSBncm91cCDnmoTos4fmlpnliIbmlaPliLDmm7TlpJogbm9kZeOAgiB8IOmA
muW4uOS4jeW7uuitsOOAgumZpOmdnuW3sue2k+eQhuinoyBhdXRvc2NhbGVyIOeahOWvpumam+ih
jOeCuu+8jOS4puWPr+aOpeWPlyB0b3BvbG9neSDororli5XnmoTkuI3norrlrprmgKfjgIIgfDxi
cj48YnI+KDEpIOeahCBncm91cCDntYTmiJDmmK/lm7rlrprnmoTvvIxzY2FsaW5nIOaYr+WinuWK
oOaIlua4m+WwkSBncm91cOOAguWboOeCuuavj+WAiyBncm91cCDpg73opoHljIXlkKvlrozmlbTo
s4fmlpnvvIzmiYDku6Xlop7muJsgZ3JvdXAg5LiN5pyD6ZmN5L2O5pei5pyJIG5vZGUg55qEIG1l
bW9yeSDkvb/nlKjnjofvvJtzY2FsZS1vdXQg55qE5pys6LOq5piv5aKe5Yqg5LiA5pW05Lu96LOH
5paZ5Ymv5pys44CC5LiN6YGOIHF1ZXJ5IOavj+asoemdouWwjeeahCBub2RlIOaVuOS4jeiuiu+8
jOWboOatpCBsYXRlbmN5IOihqOePvuacgOepqeWumuOAgjxicj48YnI+KDIpIOeahCBncm91cCDl
gIvmlbjkuI3ororvvIzkvYblop7liqDmiJbmuJvlsJEgZ3JvdXAg5YWn55qEIG5vZGUg5YCL5pW4
44CCVmVzcGEg5pyD5oqKIHF1ZXJ5IOmAgee1puiiq+mBuOS4reeahCBncm91cCDlhafmiYDmnIkg
Y29udGVudCBub2Rl77yM5omA5LulIGdyb3VwIOWFpyBub2RlIOi2iuWkmu+8jHF1ZXJ5IOeahCBm
YW4tb3V0IOWwjeixoeS5n+i2iuWkmu+8jOWwjSBsYXRlbmN5IOacg+acieS6m+W+ruW9semfv+OA
guS4jemBjuebuOWwjeWcsO+8jOizh+aWmeacg+WIhuaVo+WIsOabtOWkmiBub2Rl77yM5Zau5LiA
IG5vZGUg55qEIG1lbW9yeSDoiIcgZGlzayDlo5PlipvmnIPkuIvpmY3vvIzlm6DmraTmr5TovIPp
ganlkIjmn6XoqaLmtYHph4/nqanlrprjgIHkvYbos4fmlpnph4/ororli5XlpKfnmoTmg4XlooPj
gII8YnI+PGJyPuacgOW+jOS4gOWAi+WvpuWLmeS4iueahOaPkOmGku+8muWmguaenOacgOe1guei
uuWvpumcgOimgeWQjOaZguaUueiuiiBncm91cCDmlbjoiIcgZ3JvdXAgc2l6Ze+8jOW7uuitsOaL
huaIkOWFqeasoeiqv+aVtO+8jOS4remWk+etieizh+aWmemHjeWIhuW4g+WujOaIkOOAgumAmeao
o+avlOi8g+WuueaYk+e2reaMgeWujOaVtCBxdWVyeSBjb3ZlcmFnZe+8jOS5n+avlOi8g+WuueaY
k+ingOWvn+avj+asoSB0b3BvbG9neSDororli5XluLbkvobnmoTlvbHpn7/jgII8YnI+PGJyPuWP
g+iAg+izh+aWme+8mjxicj48YnI+LSBbVmVzcGEgQXV0b3NjYWxpbmddKGh0dHBzOi8vZG9jcy52
ZXNwYS5haS9lbi9vcGVyYXRpb25zL2F1dG9zY2FsaW5nLmh0bWwpPGJyPi0gW1Zlc3BhIENvbnRl
bnQgQ2x1c3RlciBFbGFzdGljaXR5XShodHRwczovL2RvY3MudmVzcGEuYWkvZW4vY29udGVudC9l
bGFzdGljaXR5Lmh0bWwpPGJyPi0gW1Zlc3BhIFRvcG9sb2d5IGFuZCBSZXNpemluZ10oaHR0cHM6
Ly9kb2NzLnZlc3BhLmFpL2VuL3BlcmZvcm1hbmNlL3RvcG9sb2d5LWFuZC1yZXNpemluZyk8L3A+
PGRpdiBjbGFzcz0ibm90cmFuc2xhdGUiIGlkPSJraXNzLXRyYW5zbGF0b3ItYm94Ij48L2Rpdj4=&quot;&gt;​&lt;/div&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimwayne.blogspot.com/feeds/3193224227758027601/comments/default' title='張貼留言'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/6819483672105967231/3193224227758027601' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6819483672105967231/posts/default/3193224227758027601'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6819483672105967231/posts/default/3193224227758027601'/><link rel='alternate' type='text/html' href='http://jimwayne.blogspot.com/2026/08/vespa-groups.html' title='Vespa groups 的使用時機'/><author><name>Wayne Yeh</name><uri>http://www.blogger.com/profile/02148905864753873930</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6819483672105967231.post-9108751511988450646</id><published>2025-10-20T11:50:00.004+08:00</published><updated>2025-10-20T11:50:16.487+08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="Spark"/><title type='text'>[筆記] Apache Spark 的 AQE 造成的 Execution Plan 過大</title><content type='html'>Ref: https://medium.com/towards-data-engineering/apache-spark-wtf-i-love-it-when-a-plan-comes-together-part-iii-296a44d323dd</content><link rel='replies' type='application/atom+xml' href='http://jimwayne.blogspot.com/feeds/9108751511988450646/comments/default' title='張貼留言'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/6819483672105967231/9108751511988450646' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6819483672105967231/posts/default/9108751511988450646'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6819483672105967231/posts/default/9108751511988450646'/><link rel='alternate' type='text/html' href='http://jimwayne.blogspot.com/2025/10/apache-spark-aqe-execution-plan.html' title='[筆記] Apache Spark 的 AQE 造成的 Execution Plan 過大'/><author><name>Wayne Yeh</name><uri>http://www.blogger.com/profile/02148905864753873930</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6819483672105967231.post-5543473744108926594</id><published>2025-07-31T22:06:00.006+08:00</published><updated>2025-07-31T22:07:53.030+08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="Github Copilot"/><category scheme="http://www.blogger.com/atom/ns#" term="VS Code"/><title type='text'>[筆記] VS Code 的 Copilot 對話紀錄位置</title><content type='html'>&lt;div class=&quot;markdown-here-wrapper&quot; data-md-url=&quot;https://www.blogger.com/blog/post/edit/6819483672105967231/5543473744108926594&quot;&gt;&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt; 今天突然遇到對話歷史紀錄消失的問題，本來以為是對話紀錄太老了被刪除了，不過實際上或許是 VS Code Insider 更新的緣故，導致舊的格式可能讀不到之類的….？總之實際上對話紀錄還在，只是 VS Code Insider 上看不到。&lt;/p&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;想找回對話紀錄的話，以 Mac 來說，可以去 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;~/Library/Application Support/Code - Insiders/User/workspaceStorage/{uuid}/chatSessions&lt;/code&gt; 資料夾裡面找，對話紀錄會以 JSON 形式存在那裡。&lt;/p&gt;
&lt;div style=&quot;font-size: 0em; height: 0; margin: 0; max-height: 0; max-width: 0; overflow: hidden; padding: 0; width: 0;&quot; title=&quot;MDH:PHA+Jm5ic3A75LuK5aSp56qB54S26YGH5Yiw5bCN6Kmx5q235Y+y57SA6YyE5raI5aSx55qE5ZWP
6aGM77yM5pys5L6G5Lul54K65piv5bCN6Kmx57SA6YyE5aSq6ICB5LqG6KKr5Yiq6Zmk5LqG77yM
5LiN6YGO5a+m6Zqb5LiK5oiW6Kix5pivIFZTIENvZGUgSW5zaWRlciDmm7TmlrDnmoTnt6PmlYXv
vIzlsI7oh7ToiIrnmoTmoLzlvI/lj6/og73oroDkuI3liLDkuYvpoZ7nmoQuLi4u77yf57i95LmL
5a+m6Zqb5LiK5bCN6Kmx57SA6YyE6YKE5Zyo77yM5Y+q5pivIFZTIENvZGUgSW5zaWRlciDkuIrn
nIvkuI3liLDjgII8YnI+PGJyPuaDs+aJvuWbnuWwjeipsee0gOmMhOeahOipse+8jOS7pSBNYWMg
5L6G6Kqq77yM5Y+v5Lul5Y67IGB+L0xpYnJhcnkvQXBwbGljYXRpb24gU3VwcG9ydC9Db2RlIC0g
SW5zaWRlcnMvVXNlci93b3Jrc3BhY2VTdG9yYWdlL3t1dWlkfS9jaGF0U2Vzc2lvbnNgIOizh+aW
meWkvuijoemdouaJvu+8jOWwjeipsee0gOmMhOacg+S7pSBKU09OIOW9ouW8j+WtmOWcqOmCo+ij
oeOAgjxicj48YnI+PC9wPg==&quot;&gt;​&lt;/div&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimwayne.blogspot.com/feeds/5543473744108926594/comments/default' title='張貼留言'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/6819483672105967231/5543473744108926594' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6819483672105967231/posts/default/5543473744108926594'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6819483672105967231/posts/default/5543473744108926594'/><link rel='alternate' type='text/html' href='http://jimwayne.blogspot.com/2025/07/vs-code-copilot.html' title='[筆記] VS Code 的 Copilot 對話紀錄位置'/><author><name>Wayne Yeh</name><uri>http://www.blogger.com/profile/02148905864753873930</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6819483672105967231.post-2769929417631814993</id><published>2025-04-21T21:02:00.002+08:00</published><updated>2025-04-21T21:02:27.804+08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="AWS"/><category scheme="http://www.blogger.com/atom/ns#" term="ECS"/><category scheme="http://www.blogger.com/atom/ns#" term="Fargate"/><title type='text'>ECS Fargate 在 non-root 環境下使用 MountPoints</title><content type='html'>&lt;div class=&quot;markdown-here-wrapper&quot; data-md-url=&quot;https://www.blogger.com/blog/post/edit/6819483672105967231/2769929417631814993&quot;&gt;&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;在 Fargate 預設使用 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;MountPoints&lt;/code&gt; 去利用 ephemeral storage 作為兩個 container 之間分享資源的手段時，可能會遇到如果 container 本身是 non-root user 的話，會導致無法寫入 ephemeral storage 的問題。&lt;/p&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;這是因為 Fargate 的 ephemeral storage 預設只能夠被 root 寫入。而這個問題，目前能夠做的 workaround 如下，基本上是在 Dockerfile 中，將要作為 ephemeral storage 的目錄設定為 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;VOLUME&lt;/code&gt;，就可以保留路徑內的資料不會被 bind mount 遮蓋，而且也會有權限可以讀寫該路徑。&lt;/p&gt;
&lt;pre style=&quot;font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; font-size: 1em; line-height: 1.2em; margin: 1.2em 0px;&quot;&gt;&lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border-radius: 3px; border: 1px solid rgb(204, 204, 204); border: 1px solid rgb(234, 234, 234); display: block !important; display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; overflow: auto; padding: 0.5em 0.7em; padding: 0px 0.3em; white-space: pre-wrap; white-space: pre;&quot;&gt;USER node
RUN touch /var/log/exported/examplefile
VOLUME [&quot;/var/log/exported&quot;]
&lt;/code&gt;&lt;/pre&gt;&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;詳情可以參考 &lt;a href=&quot;https://github.com/aws/containers-roadmap/issues/938#issuecomment-1791505363&quot;&gt;GitHub issue&lt;/a&gt;。&lt;/p&gt;
&lt;div style=&quot;font-size: 0em; height: 0; margin: 0; max-height: 0; max-width: 0; overflow: hidden; padding: 0; width: 0;&quot; title=&quot;MDH:PHA+5ZyoIEZhcmdhdGUg6aCQ6Kit5L2/55SoIGBNb3VudFBvaW50c2Ag5Y675Yip55SoIGVwaGVt
ZXJhbCBzdG9yYWdlIOS9nOeCuuWFqeWAiyBjb250YWluZXIg5LmL6ZaT5YiG5Lqr6LOH5rqQ55qE
5omL5q615pmC77yM5Y+v6IO95pyD6YGH5Yiw5aaC5p6cIGNvbnRhaW5lciDmnKzouqvmmK8gbm9u
LXJvb3QgdXNlciDnmoToqbHvvIzmnIPlsI7oh7TnhKHms5Xlr6vlhaUgZXBoZW1lcmFsIHN0b3Jh
Z2Ug55qE5ZWP6aGM44CCPGJyPjxicj7pgJnmmK/lm6DngrogRmFyZ2F0ZSDnmoQgZXBoZW1lcmFs
IHN0b3JhZ2Ug6aCQ6Kit5Y+q6IO95aSg6KKrIHJvb3Qg5a+r5YWl44CC6ICM6YCZ5YCL5ZWP6aGM
77yM55uu5YmN6IO95aSg5YGa55qEIHdvcmthcm91bmQg5aaC5LiL77yM5Z+65pys5LiK5piv5Zyo
IERvY2tlcmZpbGUg5Lit77yM5bCH6KaB5L2c54K6IGVwaGVtZXJhbCBzdG9yYWdlIOeahOebrumM
hOioreWumueCuiBgVk9MVU1FYO+8jOWwseWPr+S7peS/neeVmei3r+W+keWFp+eahOizh+aWmeS4
jeacg+iiqyBiaW5kIG1vdW50IOmBruiTi++8jOiAjOS4lOS5n+acg+acieasiumZkOWPr+S7peiu
gOWvq+ipsui3r+W+keOAgjxicj48YnI+YGBgPGJyPlVTRVIgbm9kZTxicj5SVU4gdG91Y2ggL3Zh
ci9sb2cvZXhwb3J0ZWQvZXhhbXBsZWZpbGU8YnI+Vk9MVU1FIFsiL3Zhci9sb2cvZXhwb3J0ZWQi
XTxicj5gYGA8YnI+PGJyPuips+aDheWPr+S7peWPg+iAgyBbR2l0SHViIGlzc3VlXVtmYXJnYXRl
LXZvbHVtZV3jgII8YnI+PGJyPltmYXJnYXRlLXRhc2stc3RvcmFnZV06IGh0dHBzOi8vZG9jcy5h
d3MuYW1hem9uLmNvbS9BbWF6b25FQ1MvbGF0ZXN0L2RldmVsb3Blcmd1aWRlL2ZhcmdhdGUtdGFz
ay1zdG9yYWdlLmh0bWw8YnI+W2ZhcmdhdGUtdm9sdW1lXTogaHR0cHM6Ly9naXRodWIuY29tL2F3
cy9jb250YWluZXJzLXJvYWRtYXAvaXNzdWVzLzkzOCNpc3N1ZWNvbW1lbnQtMTc5MTUwNTM2Mzxi
cj48YnI+PC9wPg==&quot;&gt;​&lt;/div&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimwayne.blogspot.com/feeds/2769929417631814993/comments/default' title='張貼留言'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/6819483672105967231/2769929417631814993' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6819483672105967231/posts/default/2769929417631814993'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6819483672105967231/posts/default/2769929417631814993'/><link rel='alternate' type='text/html' href='http://jimwayne.blogspot.com/2025/04/ecs-fargate-non-root-mountpoints.html' title='ECS Fargate 在 non-root 環境下使用 MountPoints'/><author><name>Wayne Yeh</name><uri>http://www.blogger.com/profile/02148905864753873930</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6819483672105967231.post-5571093999496248356</id><published>2025-02-22T22:20:00.005+08:00</published><updated>2025-02-22T22:21:02.905+08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="Spark"/><title type='text'>Spark 新手筆記（二）：在本機打開 Spark History Server 查看 event logs</title><content type='html'>&lt;div class=&quot;markdown-here-wrapper&quot; data-md-url=&quot;https://www.blogger.com/blog/post/edit/6819483672105967231/5571093999496248356&quot;&gt;&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;這篇要紀錄如何使用 Spark History Server。因為我們專案在使用 AWS EMR 時，是可以在 AWS 上打開 Spark History Server，但一方面打開要開很久，而且一段時間沒去存取的話，Server 又會自動被關掉，另一方面在 AWS 開的 History Server 常遇到一些奇怪的問題，但好像又沒辦法對它做什麼調整，所以就在想說有沒有辦法把資料下載到本機，在本機開 Spark History Server 呢？結論來說，是可以的，這篇就會紀錄在本機要如何使用 Spark History Server。&lt;/p&gt;
&lt;h1 id=&quot;-spark-&quot; style=&quot;border-bottom: 1px solid rgb(221, 221, 221); font-size: 1.6em; font-weight: bold; margin: 1.3em 0px 1em; padding: 0px;&quot;&gt;選擇合適的 Spark 版本&lt;/h1&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;首先在開始之前，先提一下我試了一個下午的結果，目前 Spark 最新版的 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;3.5.x&lt;/code&gt; 因為底下會用到 Jackson &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;2.15.x&lt;/code&gt;，這個版本的 Jackson 引入了 JSON 長度的限制，而且現在這個限制似乎是無法調整的。Github 上有對應的 &lt;a href=&quot;https://issues.apache.org/jira/browse/SPARK-49872&quot;&gt;issue&lt;/a&gt; 與 &lt;a href=&quot;https://github.com/apache/spark/pull/49163&quot;&gt;PR&lt;/a&gt; 想要改成沒有限制長度，但寫這篇文章的現在，這個 PR 還沒有被 merge。回到問題本身，具體來說，這個問題會導致長度超過 20M 的 Spark event logs 沒辦法被 History Server 讀取，會拋出類似以下的錯誤訊息：&lt;/p&gt;
&lt;pre style=&quot;font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; font-size: 1em; line-height: 1.2em; margin: 1.2em 0px;&quot;&gt;&lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border-radius: 3px; border: 1px solid rgb(204, 204, 204); border: 1px solid rgb(234, 234, 234); display: block !important; display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; overflow: auto; padding: 0.5em 0.7em; padding: 0px 0.3em; white-space: pre-wrap; white-space: pre;&quot;&gt;URI:    /history/application_1739961940470_0001/1/jobs/
STATUS:    500
MESSAGE:    com.fasterxml.jackson.core.exc.StreamConstraintsException: String length (20054016) exceeds the maximum length (20000000)
SERVLET:    org.apache.spark.deploy.history.HistoryServer$$anon$1-2787de58
CAUSED BY:    com.fasterxml.jackson.core.exc.StreamConstraintsException: String length (20054016) exceeds the maximum length (20000000)
&lt;/code&gt;&lt;/pre&gt;&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;而以本機來說，最簡單又快速的解法，就是去下載 Spark &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;3.4.x&lt;/code&gt; 或更早以前的版本，&lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;3.4.x&lt;/code&gt; 版的 Spark 使用的 Jackson 版本是 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;2.14.x&lt;/code&gt;，就可以避開這個問題了。&lt;/p&gt;
&lt;h1 id=&quot;-history-server-&quot; style=&quot;border-bottom: 1px solid rgb(221, 221, 221); font-size: 1.6em; font-weight: bold; margin: 1.3em 0px 1em; padding: 0px;&quot;&gt;設定相關的 History Server 參數&lt;/h1&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;這個步驟可能不一定是必要的，只是因為 History Server 都有預設設定，但預設設定不見得符合需求，所以適度調整一下會比較方便。&lt;/p&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;首先，History Server 預設會去 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;file://tmp/event-logs&lt;/code&gt; 資料夾尋找 event logs，但如果不想把 event logs 放在暫存資料夾，可以指定一個別的資料夾讓 History Server 去讀取。作法是把 Spark 資料夾中的 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;conf/spark-defaults.conf.template&lt;/code&gt; 更名成 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;conf/spark-defaults.conf&lt;/code&gt;，然後寫上以下的設定：&lt;/p&gt;
&lt;pre style=&quot;font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; font-size: 1em; line-height: 1.2em; margin: 1.2em 0px;&quot;&gt;&lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border-radius: 3px; border: 1px solid rgb(204, 204, 204); border: 1px solid rgb(234, 234, 234); display: block !important; display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; overflow: auto; padding: 0.5em 0.7em; padding: 0px 0.3em; white-space: pre-wrap; white-space: pre;&quot;&gt;spark.history.fs.logDirectory=/YOUR/PREFERRED/PATH
&lt;/code&gt;&lt;/pre&gt;&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;另外 Spark History Server 預設使用的記憶體是 1GB，但如果你會讀非常龐大的 event logs 的話，1GB 很有可能會不夠用，這時打開 History Server 後，可能會在 job 頁面看到類似以下的錯誤訊息：&lt;/p&gt;
&lt;pre style=&quot;font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; font-size: 1em; line-height: 1.2em; margin: 1.2em 0px;&quot;&gt;&lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border-radius: 3px; border: 1px solid rgb(204, 204, 204); border: 1px solid rgb(234, 234, 234); display: block !important; display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; overflow: auto; padding: 0.5em 0.7em; padding: 0px 0.3em; white-space: pre-wrap; white-space: pre;&quot;&gt;URI:    /history/application_1739961940470_0001/1/jobs/
STATUS:    500
MESSAGE:    org.sparkproject.guava.util.concurrent.ExecutionError: java.lang.OutOfMemoryError: Java heap space
SERVLET:    org.apache.spark.deploy.history.HistoryServer$$anon$1-27b71f50
CAUSED BY:    org.sparkproject.guava.util.concurrent.ExecutionError: java.lang.OutOfMemoryError: Java heap space
CAUSED BY:    java.lang.OutOfMemoryError: Java heap space
&lt;/code&gt;&lt;/pre&gt;&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;這時要增加可用的記憶體，可以去修改 daemon memory 設定，這個設定是放在另一個檔案，所以要先把 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;conf/spark-env.sh.template&lt;/code&gt; 改名成 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;conf/spark-env.sh&lt;/code&gt;，然後在裡面加上以下的設定，把記憶體改成你想要的用量，這邊的例子是改成 4GB。&lt;/p&gt;
&lt;pre style=&quot;font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; font-size: 1em; line-height: 1.2em; margin: 1.2em 0px;&quot;&gt;&lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border-radius: 3px; border: 1px solid rgb(204, 204, 204); border: 1px solid rgb(234, 234, 234); display: block !important; display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; overflow: auto; padding: 0.5em 0.7em; padding: 0px 0.3em; white-space: pre-wrap; white-space: pre;&quot;&gt;SPARK_DAEMON_MEMORY=&quot;4g&quot;
&lt;/code&gt;&lt;/pre&gt;&lt;h1 id=&quot;-spark-history-server&quot; style=&quot;border-bottom: 1px solid rgb(221, 221, 221); font-size: 1.6em; font-weight: bold; margin: 1.3em 0px 1em; padding: 0px;&quot;&gt;啟動與關閉 Spark History Server&lt;/h1&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;在本機啟動 History Server，只要去 Spark 資料夾內的 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;sbin&lt;/code&gt; 資料夾裡執行 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;start-history-server&lt;/code&gt; 即可。執行後如果想看 History Server 有沒有預期的反應，可以去查看 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;/logs&lt;/code&gt; 資料夾裡的 log。&lt;/p&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;以下是執行的範例：&lt;/p&gt;
&lt;pre style=&quot;font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; font-size: 1em; line-height: 1.2em; margin: 1.2em 0px;&quot;&gt;&lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border-radius: 3px; border: 1px solid rgb(204, 204, 204); border: 1px solid rgb(234, 234, 234); display: block !important; display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; overflow: auto; padding: 0.5em 0.7em; padding: 0px 0.3em; white-space: pre-wrap; white-space: pre;&quot;&gt;$ cd spark-3.4.4-bin-hadoop3-scala2.13/sbin/
$ ./start-history-server
starting org.apache.spark.deploy.history.HistoryServer, logging to /mnt/d/spark-3.4.4-bin-hadoop3-scala2.13/logs/spark-jimwayne-org.apache.spark.deploy.history.HistoryServer-1-JW-HOMEPC-12700.out
$ tail -f ../logs/spark-jimwayne-org.apache.spark.deploy.history.HistoryServer-1-JW-HOMEPC-12700.out
25/02/22 22:12:56 INFO SecurityManager: Changing view acls groups to:
25/02/22 22:12:56 INFO SecurityManager: Changing modify acls groups to:
25/02/22 22:12:56 INFO SecurityManager: SecurityManager: authentication disabled; ui acls disabled; users with view permissions: jimwayne; groups with view permissions: EMPTY; users with modify permissions: jimwayne; groups with modify permissions: EMPTY
25/02/22 22:12:56 INFO FsHistoryProvider: History server ui acls disabled; users with admin permissions: ; groups with admin permissions:
25/02/22 22:12:56 INFO JettyUtils: Start Jetty 0.0.0.0:18080 for HistoryServerUI
25/02/22 22:12:56 INFO Utils: Successfully started service &#39;HistoryServerUI&#39; on port 18080.
25/02/22 22:12:56 INFO HistoryServer: Bound HistoryServer to 0.0.0.0, and started at http://10.255.255.254:18080
25/02/22 22:12:56 INFO FsHistoryProvider: Parsing file:/mnt/d/spark-events/application_1739961940470_0001_1 for listing data...
25/02/22 22:12:57 INFO FsHistoryProvider: Looking for end event; skipping 1919341552 bytes from file:/mnt/d/spark-events/application_1739961940470_0001_1...
25/02/22 22:12:57 INFO FsHistoryProvider: Finished parsing file:/mnt/d/spark-events/application_1739961940470_0001_1
&lt;/code&gt;&lt;/pre&gt;&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;上面可以從 log 看出，啟動成功後，Spark History Server 會開在 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;http://localhost:18080&lt;/code&gt;，並且會自動開始讀取 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;spark.history.fs.logDirectory&lt;/code&gt; 設定的資料夾裡面的 event logs 檔案。因為我已經在裡面放了一個從 EMR 下載下來並解壓縮的檔案，所以它可以掃到並開始解析這個檔案。&lt;/p&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;最後，用瀏覽器打開 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;http://localhost:18080&lt;/code&gt; 就可以進入 Spark History Server 查看了。要看在雲端或其他環境執行的狀況，就去把那邊的 Spark event logs 下載回來，放在 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;spark.history.fs.logDirectory&lt;/code&gt; 指定的目錄即可。&lt;/p&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;PS. 目前測試 History Server 好像無法接受 ZIP，雖然 ChatGPT 跟我說 Spark History Server 可以支援壓縮檔…。&lt;/p&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;到這裡，就可以避開文章最開始提到 AWS EMR 的 Spark History Server 常常打不開的各種奇怪問題了！雖然說第一步還是得先打開 EMR 上的 Spark History Server，並且上去下載 event logs 啦 😆&lt;/p&gt;
&lt;div style=&quot;font-size: 0em; height: 0; margin: 0; max-height: 0; max-width: 0; overflow: hidden; padding: 0; width: 0;&quot; title=&quot;MDH:6YCZ56+H6KaB57SA6YyE5aaC5L2V5L2/55SoIFNwYXJrIEhpc3RvcnkgU2VydmVy44CC5Zug54K6
5oiR5YCR5bCI5qGI5Zyo5L2/55SoIEFXUyBFTVIg5pmC77yM5piv5Y+v5Lul5ZyoIEFXUyDkuIrm
iZPplosgU3BhcmsgSGlzdG9yeSBTZXJ2ZXLvvIzkvYbkuIDmlrnpnaLmiZPplovopoHplovlvojk
uYXvvIzogIzkuJTkuIDmrrXmmYLplpPmspLljrvlrZjlj5bnmoToqbHvvIxTZXJ2ZXIg5Y+I5pyD
6Ieq5YuV6KKr6Zec5o6J77yM5Y+m5LiA5pa56Z2i5ZyoIEFXUyDplovnmoQgSGlzdG9yeSBTZXJ2
ZXIg5bi46YGH5Yiw5LiA5Lqb5aWH5oCq55qE5ZWP6aGM77yM5L2G5aW95YOP5Y+I5rKS6L6m5rOV
5bCN5a6D5YGa5LuA6bq86Kq/5pW077yM5omA5Lul5bCx5Zyo5oOz6Kqq5pyJ5rKS5pyJ6L6m5rOV
5oqK6LOH5paZ5LiL6LyJ5Yiw5pys5qmf77yM5Zyo5pys5qmf6ZaLIFNwYXJrIEhpc3RvcnkgU2Vy
dmVyIOWRou+8n+e1kOirluS+huiqqu+8jOaYr+WPr+S7peeahO+8jOmAmeevh+Wwseacg+e0gOmM
hOWcqOacrOapn+imgeWmguS9leS9v+eUqCBTcGFyayBIaXN0b3J5IFNlcnZlcuOAgjxicj48YnI+
IyDpgbjmk4flkIjpgannmoQgU3Bhcmsg54mI5pysPGJyPjxicj7pppblhYjlnKjplovlp4vkuYvl
iY3vvIzlhYjmj5DkuIDkuIvmiJHoqabkuobkuIDlgIvkuIvljYjnmoTntZDmnpzvvIznm67liY0g
U3Bhcmsg5pyA5paw54mI55qEIGAzLjUueGAg5Zug54K65bqV5LiL5pyD55So5YiwIEphY2tzb24g
YDIuMTUueGDvvIzpgJnlgIvniYjmnKznmoQgSmFja3NvbiDlvJXlhaXkuoYgSlNPTiDplbfluqbn
moTpmZDliLbvvIzogIzkuJTnj77lnKjpgJnlgIvpmZDliLbkvLzkuY7mmK/nhKHms5Xoqr/mlbTn
moTjgIJHaXRodWIg5LiK5pyJ5bCN5oeJ55qEIFtpc3N1ZV1bU1BBUkstNDk4NzJdIOiIhyBbUFJd
W1NQQVJLLVBSLTQ5MTYzXSDmg7PopoHmlLnmiJDmspLmnInpmZDliLbplbfluqbvvIzkvYblr6vp
gJnnr4fmlofnq6DnmoTnj77lnKjvvIzpgJnlgIsgUFIg6YKE5rKS5pyJ6KKrIG1lcmdl44CC5Zue
5Yiw5ZWP6aGM5pys6Lqr77yM5YW36auU5L6G6Kqq77yM6YCZ5YCL5ZWP6aGM5pyD5bCO6Ie06ZW3
5bqm6LaF6YGOIDIwTSDnmoQgU3BhcmsgZXZlbnQgbG9ncyDmspLovqbms5XooqsgSGlzdG9yeSBT
ZXJ2ZXIg6K6A5Y+W77yM5pyD5ouL5Ye66aGe5Ly85Lul5LiL55qE6Yyv6Kqk6KiK5oGv77yaPGJy
Pjxicj5gYGA8YnI+VVJJOsKgwqDCoCAvaGlzdG9yeS9hcHBsaWNhdGlvbl8xNzM5OTYxOTQwNDcw
XzAwMDEvMS9qb2JzLzxicj5TVEFUVVM6wqDCoMKgIDUwMDxicj5NRVNTQUdFOsKgwqDCoCBjb20u
ZmFzdGVyeG1sLmphY2tzb24uY29yZS5leGMuU3RyZWFtQ29uc3RyYWludHNFeGNlcHRpb246IFN0
cmluZyBsZW5ndGggKDIwMDU0MDE2KSBleGNlZWRzIHRoZSBtYXhpbXVtIGxlbmd0aCAoMjAwMDAw
MDApPGJyPlNFUlZMRVQ6wqDCoMKgIG9yZy5hcGFjaGUuc3BhcmsuZGVwbG95Lmhpc3RvcnkuSGlz
dG9yeVNlcnZlciQkYW5vbiQxLTI3ODdkZTU4PGJyPkNBVVNFRCBCWTrCoMKgwqAgY29tLmZhc3Rl
cnhtbC5qYWNrc29uLmNvcmUuZXhjLlN0cmVhbUNvbnN0cmFpbnRzRXhjZXB0aW9uOiBTdHJpbmcg
bGVuZ3RoICgyMDA1NDAxNikgZXhjZWVkcyB0aGUgbWF4aW11bSBsZW5ndGggKDIwMDAwMDAwKTxi
cj5gYGA8YnI+PGJyPuiAjOS7peacrOapn+S+huiqqu+8jOacgOewoeWWruWPiOW/q+mAn+eahOin
o+azle+8jOWwseaYr+WOu+S4i+i8iSBTcGFyayBgMy40LnhgIOaIluabtOaXqeS7peWJjeeahOeJ
iOacrO+8jGAzLjQueGAg54mI55qEIFNwYXJrIOS9v+eUqOeahCBKYWNrc29uIOeJiOacrOaYryBg
Mi4xNC54YO+8jOWwseWPr+S7pemBv+mWi+mAmeWAi+WVj+mhjOS6huOAgjxicj48YnI+W1NQQVJL
LTQ5ODcyXTogaHR0cHM6Ly9pc3N1ZXMuYXBhY2hlLm9yZy9qaXJhL2Jyb3dzZS9TUEFSSy00OTg3
Mjxicj5bU1BBUkstUFItNDkxNjNdOiBodHRwczovL2dpdGh1Yi5jb20vYXBhY2hlL3NwYXJrL3B1
bGwvNDkxNjM8YnI+PGJyPiMg6Kit5a6a55u46Zec55qEIEhpc3RvcnkgU2VydmVyIOWPg+aVuDxi
cj48YnI+6YCZ5YCL5q2l6amf5Y+v6IO95LiN5LiA5a6a5piv5b+F6KaB55qE77yM5Y+q5piv5Zug
54K6IEhpc3RvcnkgU2VydmVyIOmDveaciemgkOioreioreWumu+8jOS9humgkOioreioreWumuS4
jeimi+W+l+espuWQiOmcgOaxgu+8jOaJgOS7pemBqeW6puiqv+aVtOS4gOS4i+acg+avlOi8g+aW
ueS+v+OAgjxicj48YnI+6aaW5YWI77yMSGlzdG9yeSBTZXJ2ZXIg6aCQ6Kit5pyD5Y67IGBmaWxl
Oi8vdG1wL2V2ZW50LWxvZ3NgIOizh+aWmeWkvuWwi+aJviBldmVudCBsb2dz77yM5L2G5aaC5p6c
5LiN5oOz5oqKIGV2ZW50IGxvZ3Mg5pS+5Zyo5pqr5a2Y6LOH5paZ5aS+77yM5Y+v5Lul5oyH5a6a
5LiA5YCL5Yil55qE6LOH5paZ5aS+6K6TIEhpc3RvcnkgU2VydmVyIOWOu+iugOWPluOAguS9nOaz
leaYr+aKiiBTcGFyayDos4fmlpnlpL7kuK3nmoQgYGNvbmYvc3BhcmstZGVmYXVsdHMuY29uZi50
ZW1wbGF0ZWAg5pu05ZCN5oiQIGBjb25mL3NwYXJrLWRlZmF1bHRzLmNvbmZg77yM54S25b6M5a+r
5LiK5Lul5LiL55qE6Kit5a6a77yaPGJyPjxicj5gYGA8YnI+c3BhcmsuaGlzdG9yeS5mcy5sb2dE
aXJlY3Rvcnk9L1lPVVIvUFJFRkVSUkVEL1BBVEg8YnI+YGBgPGJyPjxicj7lj6blpJYgU3Bhcmsg
SGlzdG9yeSBTZXJ2ZXIg6aCQ6Kit5L2/55So55qE6KiY5oa26auU5pivIDFHQu+8jOS9huWmguae
nOS9oOacg+iugOmdnuW4uOm+kOWkp+eahCBldmVudCBsb2dzIOeahOipse+8jDFHQiDlvojmnInl
j6/og73mnIPkuI3lpKDnlKjvvIzpgJnmmYLmiZPplosgSGlzdG9yeSBTZXJ2ZXIg5b6M77yM5Y+v
6IO95pyD5ZyoIGpvYiDpoIHpnaLnnIvliLDpoZ7kvLzku6XkuIvnmoTpjK/oqqToqIrmga/vvJo8
YnI+PGJyPmBgYDxicj5VUkk6wqDCoMKgIC9oaXN0b3J5L2FwcGxpY2F0aW9uXzE3Mzk5NjE5NDA0
NzBfMDAwMS8xL2pvYnMvPGJyPlNUQVRVUzrCoMKgwqAgNTAwPGJyPk1FU1NBR0U6wqDCoMKgIG9y
Zy5zcGFya3Byb2plY3QuZ3VhdmEudXRpbC5jb25jdXJyZW50LkV4ZWN1dGlvbkVycm9yOiBqYXZh
LmxhbmcuT3V0T2ZNZW1vcnlFcnJvcjogSmF2YSBoZWFwIHNwYWNlPGJyPlNFUlZMRVQ6wqDCoMKg
IG9yZy5hcGFjaGUuc3BhcmsuZGVwbG95Lmhpc3RvcnkuSGlzdG9yeVNlcnZlciQkYW5vbiQxLTI3
YjcxZjUwPGJyPkNBVVNFRCBCWTrCoMKgwqAgb3JnLnNwYXJrcHJvamVjdC5ndWF2YS51dGlsLmNv
bmN1cnJlbnQuRXhlY3V0aW9uRXJyb3I6IGphdmEubGFuZy5PdXRPZk1lbW9yeUVycm9yOiBKYXZh
IGhlYXAgc3BhY2U8YnI+Q0FVU0VEIEJZOsKgwqDCoCBqYXZhLmxhbmcuT3V0T2ZNZW1vcnlFcnJv
cjogSmF2YSBoZWFwIHNwYWNlPGJyPmBgYDxicj48YnI+6YCZ5pmC6KaB5aKe5Yqg5Y+v55So55qE
6KiY5oa26auU77yM5Y+v5Lul5Y675L+u5pS5IGRhZW1vbiBtZW1vcnkg6Kit5a6a77yM6YCZ5YCL
6Kit5a6a5piv5pS+5Zyo5Y+m5LiA5YCL5qqU5qGI77yM5omA5Lul6KaB5YWI5oqKIGBjb25mL3Nw
YXJrLWVudi5zaC50ZW1wbGF0ZWAg5pS55ZCN5oiQIGBjb25mL3NwYXJrLWVudi5zaGDvvIznhLbl
vozlnKjoo6HpnaLliqDkuIrku6XkuIvnmoToqK3lrprvvIzmioroqJjmhrbpq5TmlLnmiJDkvaDm
g7PopoHnmoTnlKjph4/vvIzpgJnpgornmoTkvovlrZDmmK/mlLnmiJAgNEdC44CCPGJyPmBgYDxi
cj5TUEFSS19EQUVNT05fTUVNT1JZPSI0ZyI8YnI+YGBgPGJyPjxicj4jIOWVn+WLleiIh+mXnOmW
iSBTcGFyayBIaXN0b3J5IFNlcnZlcjxicj48YnI+5Zyo5pys5qmf5ZWf5YuVIEhpc3RvcnkgU2Vy
dmVy77yM5Y+q6KaB5Y67IFNwYXJrIOizh+aWmeWkvuWFp+eahCBgc2JpbmAg6LOH5paZ5aS+6KOh
5Z+36KGMIGBzdGFydC1oaXN0b3J5LXNlcnZlcmAg5Y2z5Y+v44CC5Z+36KGM5b6M5aaC5p6c5oOz
55yLIEhpc3RvcnkgU2VydmVyIOacieaykuaciemgkOacn+eahOWPjeaHie+8jOWPr+S7peWOu+af
peeciyBgL2xvZ3NgIOizh+aWmeWkvuijoeeahCBsb2fjgII8YnI+PGJyPuS7peS4i+aYr+Wft+ih
jOeahOevhOS+i++8mjxicj5gYGA8YnI+JCBjZCBzcGFyay0zLjQuNC1iaW4taGFkb29wMy1zY2Fs
YTIuMTMvc2Jpbi88YnI+JCAuL3N0YXJ0LWhpc3Rvcnktc2VydmVyPGJyPnN0YXJ0aW5nIG9yZy5h
cGFjaGUuc3BhcmsuZGVwbG95Lmhpc3RvcnkuSGlzdG9yeVNlcnZlciwgbG9nZ2luZyB0byAvbW50
L2Qvc3BhcmstMy40LjQtYmluLWhhZG9vcDMtc2NhbGEyLjEzL2xvZ3Mvc3Bhcmstamltd2F5bmUt
b3JnLmFwYWNoZS5zcGFyay5kZXBsb3kuaGlzdG9yeS5IaXN0b3J5U2VydmVyLTEtSlctSE9NRVBD
LTEyNzAwLm91dDxicj4kIHRhaWwgLWYgLi4vbG9ncy9zcGFyay1qaW13YXluZS1vcmcuYXBhY2hl
LnNwYXJrLmRlcGxveS5oaXN0b3J5Lkhpc3RvcnlTZXJ2ZXItMS1KVy1IT01FUEMtMTI3MDAub3V0
PGJyPjI1LzAyLzIyIDIyOjEyOjU2IElORk8gU2VjdXJpdHlNYW5hZ2VyOiBDaGFuZ2luZyB2aWV3
IGFjbHMgZ3JvdXBzIHRvOjxicj4yNS8wMi8yMiAyMjoxMjo1NiBJTkZPIFNlY3VyaXR5TWFuYWdl
cjogQ2hhbmdpbmcgbW9kaWZ5IGFjbHMgZ3JvdXBzIHRvOjxicj4yNS8wMi8yMiAyMjoxMjo1NiBJ
TkZPIFNlY3VyaXR5TWFuYWdlcjogU2VjdXJpdHlNYW5hZ2VyOiBhdXRoZW50aWNhdGlvbiBkaXNh
YmxlZDsgdWkgYWNscyBkaXNhYmxlZDsgdXNlcnMgd2l0aCB2aWV3IHBlcm1pc3Npb25zOiBqaW13
YXluZTsgZ3JvdXBzIHdpdGggdmlldyBwZXJtaXNzaW9uczogRU1QVFk7IHVzZXJzIHdpdGggbW9k
aWZ5IHBlcm1pc3Npb25zOiBqaW13YXluZTsgZ3JvdXBzIHdpdGggbW9kaWZ5IHBlcm1pc3Npb25z
OiBFTVBUWTxicj4yNS8wMi8yMiAyMjoxMjo1NiBJTkZPIEZzSGlzdG9yeVByb3ZpZGVyOiBIaXN0
b3J5IHNlcnZlciB1aSBhY2xzIGRpc2FibGVkOyB1c2VycyB3aXRoIGFkbWluIHBlcm1pc3Npb25z
OiA7IGdyb3VwcyB3aXRoIGFkbWluIHBlcm1pc3Npb25zOjxicj4yNS8wMi8yMiAyMjoxMjo1NiBJ
TkZPIEpldHR5VXRpbHM6IFN0YXJ0IEpldHR5IDAuMC4wLjA6MTgwODAgZm9yIEhpc3RvcnlTZXJ2
ZXJVSTxicj4yNS8wMi8yMiAyMjoxMjo1NiBJTkZPIFV0aWxzOiBTdWNjZXNzZnVsbHkgc3RhcnRl
ZCBzZXJ2aWNlICdIaXN0b3J5U2VydmVyVUknIG9uIHBvcnQgMTgwODAuPGJyPjI1LzAyLzIyIDIy
OjEyOjU2IElORk8gSGlzdG9yeVNlcnZlcjogQm91bmQgSGlzdG9yeVNlcnZlciB0byAwLjAuMC4w
LCBhbmQgc3RhcnRlZCBhdCBodHRwOi8vMTAuMjU1LjI1NS4yNTQ6MTgwODA8YnI+MjUvMDIvMjIg
MjI6MTI6NTYgSU5GTyBGc0hpc3RvcnlQcm92aWRlcjogUGFyc2luZyBmaWxlOi9tbnQvZC9zcGFy
ay1ldmVudHMvYXBwbGljYXRpb25fMTczOTk2MTk0MDQ3MF8wMDAxXzEgZm9yIGxpc3RpbmcgZGF0
YS4uLjxicj4yNS8wMi8yMiAyMjoxMjo1NyBJTkZPIEZzSGlzdG9yeVByb3ZpZGVyOiBMb29raW5n
IGZvciBlbmQgZXZlbnQ7IHNraXBwaW5nIDE5MTkzNDE1NTIgYnl0ZXMgZnJvbSBmaWxlOi9tbnQv
ZC9zcGFyay1ldmVudHMvYXBwbGljYXRpb25fMTczOTk2MTk0MDQ3MF8wMDAxXzEuLi48YnI+MjUv
MDIvMjIgMjI6MTI6NTcgSU5GTyBGc0hpc3RvcnlQcm92aWRlcjogRmluaXNoZWQgcGFyc2luZyBm
aWxlOi9tbnQvZC9zcGFyay1ldmVudHMvYXBwbGljYXRpb25fMTczOTk2MTk0MDQ3MF8wMDAxXzE8
YnI+YGBgPGJyPjxicj7kuIrpnaLlj6/ku6Xlvp4gbG9nIOeci+WHuu+8jOWVn+WLleaIkOWKn+W+
jO+8jFNwYXJrIEhpc3RvcnkgU2VydmVyIOacg+mWi+WcqCBgaHR0cDovL2xvY2FsaG9zdDoxODA4
MGDvvIzkuKbkuJTmnIPoh6rli5Xplovlp4voroDlj5YgYHNwYXJrLmhpc3RvcnkuZnMubG9nRGly
ZWN0b3J5YCDoqK3lrprnmoTos4fmlpnlpL7oo6HpnaLnmoQgZXZlbnQgbG9ncyDmqpTmoYjjgILl
m6DngrrmiJHlt7LntpPlnKjoo6HpnaLmlL7kuobkuIDlgIvlvp4gRU1SIOS4i+i8ieS4i+S+huS4
puino+Wjk+e4rueahOaqlOahiO+8jOaJgOS7peWug+WPr+S7peaOg+WIsOS4pumWi+Wni+ino+ae
kOmAmeWAi+aqlOahiOOAgjxicj48YnI+5pyA5b6M77yM55So54CP6Ka95Zmo5omT6ZaLIGBodHRw
Oi8vbG9jYWxob3N0OjE4MDgwYCDlsLHlj6/ku6XpgLLlhaUgU3BhcmsgSGlzdG9yeSBTZXJ2ZXIg
5p+l55yL5LqG44CC6KaB55yL5Zyo6Zuy56uv5oiW5YW25LuW55Kw5aKD5Z+36KGM55qE54uA5rOB
77yM5bCx5Y675oqK6YKj6YKK55qEIFNwYXJrIGV2ZW50IGxvZ3Mg5LiL6LyJ5Zue5L6G77yM5pS+
5ZyoIGBzcGFyay5oaXN0b3J5LmZzLmxvZ0RpcmVjdG9yeWAg5oyH5a6a55qE55uu6YyE5Y2z5Y+v
44CCPGJyPjxicj5QUy4g55uu5YmN5ris6KmmIEhpc3RvcnkgU2VydmVyIOWlveWDj+eEoeazleaO
peWPlyBaSVDvvIzpm5bnhLYgQ2hhdEdQVCDot5/miJHoqqogU3BhcmsgSGlzdG9yeSBTZXJ2ZXIg
5Y+v5Lul5pSv5o+05aOT57iu5qqULi4u44CCPGJyPjxicj7liLDpgJnoo6HvvIzlsLHlj6/ku6Xp
gb/plovmlofnq6DmnIDplovlp4vmj5DliLAgQVdTIEVNUiDnmoQgU3BhcmsgSGlzdG9yeSBTZXJ2
ZXIg5bi45bi45omT5LiN6ZaL55qE5ZCE56iu5aWH5oCq5ZWP6aGM5LqG77yB6ZuW54S26Kqq56ys
5LiA5q2l6YKE5piv5b6X5YWI5omT6ZaLIEVNUiDkuIrnmoQgU3BhcmsgSGlzdG9yeSBTZXJ2ZXLv
vIzkuKbkuJTkuIrljrvkuIvovIkgZXZlbnQgbG9ncyDllaYg7aC97biGPGJyPjxicj4=&quot;&gt;​&lt;/div&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimwayne.blogspot.com/feeds/5571093999496248356/comments/default' title='張貼留言'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/6819483672105967231/5571093999496248356' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6819483672105967231/posts/default/5571093999496248356'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6819483672105967231/posts/default/5571093999496248356'/><link rel='alternate' type='text/html' href='http://jimwayne.blogspot.com/2025/02/spark-spark-history-server-event-logs.html' title='Spark 新手筆記（二）：在本機打開 Spark History Server 查看 event logs'/><author><name>Wayne Yeh</name><uri>http://www.blogger.com/profile/02148905864753873930</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6819483672105967231.post-6590890917650158576</id><published>2025-02-19T01:09:00.005+08:00</published><updated>2025-02-19T22:35:47.278+08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="Maven"/><category scheme="http://www.blogger.com/atom/ns#" term="Scala"/><category scheme="http://www.blogger.com/atom/ns#" term="Spark"/><title type='text'>Spark 新手筆記（一）：在本機安裝 Spark 並執行一個簡單的 Scala 程式</title><content type='html'>&lt;div class=&quot;markdown-here-wrapper&quot; data-md-url=&quot;https://www.blogger.com/blog/post/edit/6819483672105967231/6590890917650158576&quot;&gt;&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;最近開始參與 Spark 的專案，所以有空的時候會開始做一些 Spark 新手筆記 😆。我的環境是 Windows 11，不過實際上在不同環境的作法應該沒有差太多的感覺，但或許會有一些小地方有微妙的不同就是了。因為我自己的電腦是 Windows 11，但平常比較習慣在 Linux-based 的環境開發，所以實際上文章中會紀錄的東西，大體上會是 Windows 11 + WSL2 的狀況，指令原則上我都是在 WSL2 的 Ubuntu 裡執行的。&lt;/p&gt;
&lt;h1 id=&quot;spark-&quot; style=&quot;border-bottom: 1px solid rgb(221, 221, 221); font-size: 1.6em; font-weight: bold; margin: 1.3em 0px 1em; padding: 0px;&quot;&gt;Spark 環境準備&lt;/h1&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;首先，根據 Spark 官方文件來看，Spark 本身不需要安裝，只需要下載並執行相應的指令即可。因此主要需要準備的環境是 Java、Maven，並且下載 Spark。其中 Java 跟 Maven 怎麼安裝，我就直接略過了，這裡簡單說明我目前的環境：&lt;/p&gt;
&lt;ul style=&quot;margin: 1.2em 0px; padding-left: 2em;&quot;&gt;
&lt;li style=&quot;margin: 0.5em 0px;&quot;&gt;Java: &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;21.0.6+7-Ubuntu-124.04.1&lt;/code&gt;&lt;/li&gt;
&lt;li style=&quot;margin: 0.5em 0px;&quot;&gt;Maven: &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;3.8.8&lt;/code&gt; &lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;在 Spark 官方網站下載 Spark 時，因為目前的最新版是 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;3.5.4&lt;/code&gt;，因此我是選擇以下的選項下載的：&lt;/p&gt;
&lt;ul style=&quot;margin: 1.2em 0px; padding-left: 2em;&quot;&gt;
&lt;li style=&quot;margin: 0.5em 0px;&quot;&gt;Spark: &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;3.5.4&lt;/code&gt;&lt;/li&gt;
&lt;li style=&quot;margin: 0.5em 0px;&quot;&gt;Package Type: &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;Pre-built for Apache Hadoop 3.3 and later (Scala 2.13)&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;其中 Package Type 其實我目前也不太確定效果是什麼，但因為我的範例專案會用 Scala，因此就選了名字有 Scala 的 🙃。&lt;/p&gt;
&lt;h1 id=&quot;-&quot; style=&quot;border-bottom: 1px solid rgb(221, 221, 221); font-size: 1.6em; font-weight: bold; margin: 1.3em 0px 1em; padding: 0px;&quot;&gt;準備範例專案&lt;/h1&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;新手的第一件事就是先弄個 Hello World，不過 Spark 印 Hello World 好像有點弱，所以我弄了一個比 Hello World 複雜一滴滴的範例專案，順便也簡單試了一下 Spark 與 Scala 的寫法。&lt;/p&gt;
&lt;pre style=&quot;font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; font-size: 1em; line-height: 1.2em; margin: 1.2em 0px;&quot;&gt;&lt;code class=&quot;hljs language-sh&quot; style=&quot;-moz-text-size-adjust: none; background-color: #f8f8f8; background: rgb(35, 36, 31); border-radius: 3px; border-radius: 3px; border: 1px solid rgb(204, 204, 204); border: 1px solid rgb(234, 234, 234); color: #f8f8f2; display: block !important; display: block; display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; overflow-x: auto; overflow: auto; padding: 0.5em 0.7em; padding: 0.5em; padding: 0px 0.3em; white-space: pre-wrap; white-space: pre;&quot;&gt;git clone git@github.com:jimwayneyeh/example-spark-scala.git
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;接著因為要把打包的 JAR 丟去給 Spark 執行，所以我們需要把這個專案打包成 JAR。&lt;/p&gt;
&lt;pre style=&quot;font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; font-size: 1em; line-height: 1.2em; margin: 1.2em 0px;&quot;&gt;&lt;code class=&quot;hljs language-sh&quot; style=&quot;-moz-text-size-adjust: none; background-color: #f8f8f8; background: rgb(35, 36, 31); border-radius: 3px; border-radius: 3px; border: 1px solid rgb(204, 204, 204); border: 1px solid rgb(234, 234, 234); color: #f8f8f2; display: block !important; display: block; display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; overflow-x: auto; overflow: auto; padding: 0.5em 0.7em; padding: 0.5em; padding: 0px 0.3em; white-space: pre-wrap; white-space: pre;&quot;&gt;&lt;span class=&quot;hljs-built_in&quot; style=&quot;color: #e6db74;&quot;&gt;cd&lt;/span&gt; example-spark-scala
mvn clean package
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;打包出來的檔案會在 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;./target/example-spark-scala-1.0-SNAPSHOT.jar&lt;/code&gt;，這個路徑先記住，等等會用到。在那之前先來看一下這個範例專案裡寫了什麼。首先可以先看 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;tw.jimwayneyeh.example.spark_scala.App&lt;/code&gt; 這個類別，它的內容如下：&lt;/p&gt;
&lt;pre style=&quot;font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; font-size: 1em; line-height: 1.2em; margin: 1.2em 0px;&quot;&gt;&lt;code class=&quot;hljs language-scala&quot; style=&quot;-moz-text-size-adjust: none; background-color: #f8f8f8; background: rgb(35, 36, 31); border-radius: 3px; border-radius: 3px; border: 1px solid rgb(204, 204, 204); border: 1px solid rgb(234, 234, 234); color: #f8f8f2; display: block !important; display: block; display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; overflow-x: auto; overflow: auto; padding: 0.5em 0.7em; padding: 0.5em; padding: 0px 0.3em; white-space: pre-wrap; white-space: pre;&quot;&gt;&lt;span class=&quot;hljs-class&quot;&gt;&lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #66d9ef; color: #f92672;&quot;&gt;object&lt;/span&gt; &lt;span class=&quot;hljs-title&quot; style=&quot;color: #a6e22e; color: #f8f8f2; font-style: italic;&quot;&gt;App&lt;/span&gt; {&lt;/span&gt;

  &lt;span class=&quot;hljs-function&quot; style=&quot;color: #f92672;&quot;&gt;&lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #66d9ef; color: #f92672;&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;hljs-title&quot; style=&quot;color: #a6e22e;&quot;&gt;main&lt;/span&gt;(&lt;/span&gt;args: &lt;span class=&quot;hljs-type&quot; style=&quot;color: #e6db74;&quot;&gt;Array&lt;/span&gt;[&lt;span class=&quot;hljs-type&quot; style=&quot;color: #e6db74;&quot;&gt;String&lt;/span&gt;]): &lt;span class=&quot;hljs-type&quot; style=&quot;color: #e6db74;&quot;&gt;Unit&lt;/span&gt; = {
    println(&lt;span class=&quot;hljs-string&quot; style=&quot;color: #e6db74;&quot;&gt;&quot;Hello World!&quot;&lt;/span&gt;)

    &lt;span class=&quot;hljs-function&quot; style=&quot;color: #f92672;&quot;&gt;&lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #66d9ef; color: #f92672;&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;hljs-title&quot; style=&quot;color: #a6e22e;&quot;&gt;spark&lt;/span&gt; =&lt;/span&gt; &lt;span class=&quot;hljs-type&quot; style=&quot;color: #e6db74;&quot;&gt;SparkSession&lt;/span&gt;.builder().appName(&lt;span class=&quot;hljs-string&quot; style=&quot;color: #e6db74;&quot;&gt;&quot;exmaple app&quot;&lt;/span&gt;).getOrCreate()
    &lt;span class=&quot;hljs-function&quot; style=&quot;color: #f92672;&quot;&gt;&lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #66d9ef; color: #f92672;&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;hljs-title&quot; style=&quot;color: #a6e22e;&quot;&gt;customerDf&lt;/span&gt; =&lt;/span&gt;
      spark.read.options(&lt;span class=&quot;hljs-type&quot; style=&quot;color: #e6db74;&quot;&gt;Map&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot; style=&quot;color: #e6db74;&quot;&gt;&quot;header&quot;&lt;/span&gt; -&amp;gt; &lt;span class=&quot;hljs-string&quot; style=&quot;color: #e6db74;&quot;&gt;&quot;true&quot;&lt;/span&gt;)).csv(&lt;span class=&quot;hljs-string&quot; style=&quot;color: #e6db74;&quot;&gt;&quot;customers-100.csv&quot;&lt;/span&gt;)
    println(s&lt;span class=&quot;hljs-string&quot; style=&quot;color: #e6db74;&quot;&gt;&quot;Count: ${customerDf.count()}&quot;&lt;/span&gt;)

    spark.stop()
  }

}
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;因為程式碼很短，就不一一贅述了，簡單來說就是先建立一個 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;SparkSession&lt;/code&gt;，讀取專案根目錄的 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;customers-100.csv&lt;/code&gt; 檔，這個 CSV 檔是從 &lt;a href=&quot;https://github.com/datablist/sample-csv-files&quot;&gt;sample-csv-files&lt;/a&gt; 下載的範例 CSV 檔。CSV 讀取進來變成 data frame，指定它是有標頭的，然後算出這個 data frame 的個數並印出來。&lt;/p&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;讀取檔案的部份要特別注意的是，這裡給的路徑是相對於專案目錄的路徑，但檔案並非放在 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;src/main/resources&lt;/code&gt; 資料夾內。我詢問 ChatGPT 時，ChatGPT 回答說 Spark 正常運作時，資料都是從外部路徑帶進來的，所以一般來說 Spark 程式並不會讀取 JAR 裡面的資源。這個理由聽起來是感覺蠻合理的，畢竟會特地用 Spark 執行的運算，資料量應該都不會小，也不會有人想把資料放進 JAR 裡吧 😆。&lt;/p&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;另外也看一下一個最簡單的 Spark 專案，如果用 Maven 打包的話，需要什麼東西。POM 裡面只需要以下的 dependencies 即可。&lt;/p&gt;
&lt;pre style=&quot;font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; font-size: 1em; line-height: 1.2em; margin: 1.2em 0px;&quot;&gt;&lt;code class=&quot;hljs language-xml&quot; style=&quot;-moz-text-size-adjust: none; background-color: #f8f8f8; background: rgb(35, 36, 31); border-radius: 3px; border-radius: 3px; border: 1px solid rgb(204, 204, 204); border: 1px solid rgb(234, 234, 234); color: #f8f8f2; display: block !important; display: block; display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; overflow-x: auto; overflow: auto; padding: 0.5em 0.7em; padding: 0.5em; padding: 0px 0.3em; white-space: pre-wrap; white-space: pre;&quot;&gt;&lt;span class=&quot;hljs-tag&quot; style=&quot;color: #f8f8f2;&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-title&quot; style=&quot;color: #a6e22e; color: #f92672;&quot;&gt;dependency&lt;/span&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;hljs-tag&quot; style=&quot;color: #f8f8f2;&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-title&quot; style=&quot;color: #a6e22e; color: #f92672;&quot;&gt;groupId&lt;/span&gt;&amp;gt;&lt;/span&gt;org.scala-lang&lt;span class=&quot;hljs-tag&quot; style=&quot;color: #f8f8f2;&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-title&quot; style=&quot;color: #a6e22e; color: #f92672;&quot;&gt;groupId&lt;/span&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;hljs-tag&quot; style=&quot;color: #f8f8f2;&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-title&quot; style=&quot;color: #a6e22e; color: #f92672;&quot;&gt;artifactId&lt;/span&gt;&amp;gt;&lt;/span&gt;scala-library&lt;span class=&quot;hljs-tag&quot; style=&quot;color: #f8f8f2;&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-title&quot; style=&quot;color: #a6e22e; color: #f92672;&quot;&gt;artifactId&lt;/span&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;hljs-tag&quot; style=&quot;color: #f8f8f2;&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-title&quot; style=&quot;color: #a6e22e; color: #f92672;&quot;&gt;version&lt;/span&gt;&amp;gt;&lt;/span&gt;${scala.version}&lt;span class=&quot;hljs-tag&quot; style=&quot;color: #f8f8f2;&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-title&quot; style=&quot;color: #a6e22e; color: #f92672;&quot;&gt;version&lt;/span&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;hljs-tag&quot; style=&quot;color: #f8f8f2;&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-title&quot; style=&quot;color: #a6e22e; color: #f92672;&quot;&gt;dependency&lt;/span&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;hljs-tag&quot; style=&quot;color: #f8f8f2;&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-title&quot; style=&quot;color: #a6e22e; color: #f92672;&quot;&gt;dependency&lt;/span&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;hljs-comment&quot; style=&quot;color: #75715e;&quot;&gt;&amp;lt;!-- Spark dependency --&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;hljs-tag&quot; style=&quot;color: #f8f8f2;&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-title&quot; style=&quot;color: #a6e22e; color: #f92672;&quot;&gt;groupId&lt;/span&gt;&amp;gt;&lt;/span&gt;org.apache.spark&lt;span class=&quot;hljs-tag&quot; style=&quot;color: #f8f8f2;&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-title&quot; style=&quot;color: #a6e22e; color: #f92672;&quot;&gt;groupId&lt;/span&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;hljs-tag&quot; style=&quot;color: #f8f8f2;&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-title&quot; style=&quot;color: #a6e22e; color: #f92672;&quot;&gt;artifactId&lt;/span&gt;&amp;gt;&lt;/span&gt;spark-sql_${scala.binary.version}&lt;span class=&quot;hljs-tag&quot; style=&quot;color: #f8f8f2;&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-title&quot; style=&quot;color: #a6e22e; color: #f92672;&quot;&gt;artifactId&lt;/span&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;hljs-tag&quot; style=&quot;color: #f8f8f2;&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-title&quot; style=&quot;color: #a6e22e; color: #f92672;&quot;&gt;version&lt;/span&gt;&amp;gt;&lt;/span&gt;3.5.4&lt;span class=&quot;hljs-tag&quot; style=&quot;color: #f8f8f2;&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-title&quot; style=&quot;color: #a6e22e; color: #f92672;&quot;&gt;version&lt;/span&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;hljs-tag&quot; style=&quot;color: #f8f8f2;&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-title&quot; style=&quot;color: #a6e22e; color: #f92672;&quot;&gt;scope&lt;/span&gt;&amp;gt;&lt;/span&gt;provided&lt;span class=&quot;hljs-tag&quot; style=&quot;color: #f8f8f2;&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-title&quot; style=&quot;color: #a6e22e; color: #f92672;&quot;&gt;scope&lt;/span&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;hljs-tag&quot; style=&quot;color: #f8f8f2;&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-title&quot; style=&quot;color: #a6e22e; color: #f92672;&quot;&gt;dependency&lt;/span&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;其中可以特別注意到，Spark 的 dependency 的 scope 會是 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;provided&lt;/code&gt;，這應該是因為實際執行時，JAR 會跑在 Spark 上，所以 Spark dependencies 應該在執行環境上都有。&lt;/p&gt;
&lt;h1 id=&quot;-spark-&quot; style=&quot;border-bottom: 1px solid rgb(221, 221, 221); font-size: 1.6em; font-weight: bold; margin: 1.3em 0px 1em; padding: 0px;&quot;&gt;在 Spark 執行&lt;/h1&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;範例專案的 JAR 準備好以後，回到前面下載好的 Spark 壓縮檔，把它解壓縮後進入 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;/bin&lt;/code&gt; 資料夾，並且執行以下的 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;spark-submit&lt;/code&gt; 指令，就可以啟動一個本機的 Spark 環境執行剛剛的範例專案了。&lt;/p&gt;
&lt;pre style=&quot;font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; font-size: 1em; line-height: 1.2em; margin: 1.2em 0px;&quot;&gt;&lt;code class=&quot;hljs language-sh&quot; style=&quot;-moz-text-size-adjust: none; background-color: #f8f8f8; background: rgb(35, 36, 31); border-radius: 3px; border-radius: 3px; border: 1px solid rgb(204, 204, 204); border: 1px solid rgb(234, 234, 234); color: #f8f8f2; display: block !important; display: block; display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; overflow-x: auto; overflow: auto; padding: 0.5em 0.7em; padding: 0.5em; padding: 0px 0.3em; white-space: pre-wrap; white-space: pre;&quot;&gt;./spark-submit --class &lt;span class=&quot;hljs-string&quot; style=&quot;color: #e6db74;&quot;&gt;&quot;tw.jimwayneyeh.example.spark_scala.App&quot;&lt;/span&gt; --master local /PATH/TO/PROJECT/target/example-spark-scala-&lt;span class=&quot;hljs-number&quot; style=&quot;color: #ae81ff;&quot;&gt;1.0&lt;/span&gt;-SNAPSHOT.jar
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;等了一下之後，就可以看到 console 輸出的 Spark logs 了。這裡我貼了很長，是因為順便想要紀錄它的執行過程，之後想要分析它的內容。不過現階段重要的是 log 當中格式沒有日期的那兩行，因為程式碼是用 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;println()&lt;/code&gt; 來輸出的，所以會忽略 logging 格式直接在 console 印出只有 message 的 log。可以看出它算出了 data frame 裡面有 100 筆資料。&lt;/p&gt;
&lt;pre style=&quot;font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; font-size: 1em; line-height: 1.2em; margin: 1.2em 0px;&quot;&gt;&lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border-radius: 3px; border: 1px solid rgb(204, 204, 204); border: 1px solid rgb(234, 234, 234); display: block !important; display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; overflow: auto; padding: 0.5em 0.7em; padding: 0px 0.3em; white-space: pre-wrap; white-space: pre;&quot;&gt;Hello World!
Count: 100
&lt;/code&gt;&lt;/pre&gt;&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;以下則是紀錄一下 log 當中印出跟 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;App.scala&lt;/code&gt; 有關的部份，這些 log 就是實際在排程並執行範例程式時的過程了。&lt;/p&gt;
&lt;pre style=&quot;font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; font-size: 1em; line-height: 1.2em; margin: 1.2em 0px;&quot;&gt;&lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border-radius: 3px; border: 1px solid rgb(204, 204, 204); border: 1px solid rgb(234, 234, 234); display: block !important; display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; overflow: auto; padding: 0.5em 0.7em; padding: 0px 0.3em; white-space: pre-wrap; white-space: pre;&quot;&gt;25/02/19 00:38:51 INFO SparkContext: Created broadcast 0 from csv at App.scala:14
25/02/19 00:38:51 INFO FileSourceScanExec: Planning scan with bin packing, max size: 4211559 bytes, open cost is considered as scanning 4194304 bytes.
25/02/19 00:38:52 INFO SparkContext: Starting job: csv at App.scala:14
25/02/19 00:38:52 INFO DAGScheduler: Got job 0 (csv at App.scala:14) with 1 output partitions
25/02/19 00:38:52 INFO DAGScheduler: Final stage: ResultStage 0 (csv at App.scala:14)
25/02/19 00:38:52 INFO DAGScheduler: Parents of final stage: List()
25/02/19 00:38:52 INFO DAGScheduler: Missing parents: List()
25/02/19 00:38:52 INFO DAGScheduler: Submitting ResultStage 0 (MapPartitionsRDD[3] at csv at App.scala:14), which has no missing parents
25/02/19 00:38:52 INFO MemoryStore: Block broadcast_1 stored as values in memory (estimated size 13.8 KiB, free 434.2 MiB)
25/02/19 00:38:52 INFO MemoryStore: Block broadcast_1_piece0 stored as bytes in memory (estimated size 6.5 KiB, free 434.2 MiB)
25/02/19 00:38:52 INFO BlockManagerInfo: Added broadcast_1_piece0 in memory on 10.255.255.254:43927 (size: 6.5 KiB, free: 434.4 MiB)
25/02/19 00:38:52 INFO SparkContext: Created broadcast 1 from broadcast at DAGScheduler.scala:1585
25/02/19 00:38:52 INFO DAGScheduler: Submitting 1 missing tasks from ResultStage 0 (MapPartitionsRDD[3] at csv at App.scala:14) (first 15 tasks are for partitions Vector(0))
25/02/19 00:38:52 INFO TaskSchedulerImpl: Adding task set 0.0 with 1 tasks resource profile 0
25/02/19 00:38:52 INFO TaskSetManager: Starting task 0.0 in stage 0.0 (TID 0) (10.255.255.254, executor driver, partition 0, PROCESS_LOCAL, 9896 bytes) 
25/02/19 00:38:52 INFO Executor: Running task 0.0 in stage 0.0 (TID 0)
25/02/19 00:38:52 INFO CodeGenerator: Code generated in 12.528525 ms
25/02/19 00:38:52 INFO FileScanRDD: Reading File path: file:///mnt/g/scala/example-spark-scala/customers-100.csv, range: 0-17255, partition values: [empty row]
25/02/19 00:38:52 INFO CodeGenerator: Code generated in 40.709858 ms
25/02/19 00:38:53 INFO Executor: Finished task 0.0 in stage 0.0 (TID 0). 1800 bytes result sent to driver
25/02/19 00:38:53 INFO TaskSetManager: Finished task 0.0 in stage 0.0 (TID 0) in 697 ms on 10.255.255.254 (executor driver) (1/1)
25/02/19 00:38:53 INFO TaskSchedulerImpl: Removed TaskSet 0.0, whose tasks have all completed, from pool
25/02/19 00:38:53 INFO DAGScheduler: ResultStage 0 (csv at App.scala:14) finished in 0.890 s
25/02/19 00:38:53 INFO DAGScheduler: Job 0 is finished. Cancelling potential speculative or zombie tasks for this job
25/02/19 00:38:53 INFO TaskSchedulerImpl: Killing all running tasks in stage 0: Stage finished
25/02/19 00:38:53 INFO DAGScheduler: Job 0 finished: csv at App.scala:14, took 0.965247 s
25/02/19 00:38:53 INFO CodeGenerator: Code generated in 17.506485 ms
25/02/19 00:38:53 INFO FileSourceStrategy: Pushed Filters: 
25/02/19 00:38:53 INFO FileSourceStrategy: Post-Scan Filters:
25/02/19 00:38:53 INFO MemoryStore: Block broadcast_2 stored as values in memory (estimated size 199.5 KiB, free 434.0 MiB)
25/02/19 00:38:53 INFO MemoryStore: Block broadcast_2_piece0 stored as bytes in memory (estimated size 34.4 KiB, free 433.9 MiB)
25/02/19 00:38:53 INFO BlockManagerInfo: Added broadcast_2_piece0 in memory on 10.255.255.254:43927 (size: 34.4 KiB, free: 434.3 MiB)
25/02/19 00:38:53 INFO SparkContext: Created broadcast 2 from csv at App.scala:14
25/02/19 00:38:53 INFO FileSourceScanExec: Planning scan with bin packing, max size: 4211559 bytes, open cost is considered as scanning 4194304 bytes.
25/02/19 00:38:53 INFO FileSourceStrategy: Pushed Filters: 
25/02/19 00:38:53 INFO FileSourceStrategy: Post-Scan Filters:
25/02/19 00:38:53 INFO CodeGenerator: Code generated in 12.86595 ms
25/02/19 00:38:53 INFO MemoryStore: Block broadcast_3 stored as values in memory (estimated size 199.4 KiB, free 433.7 MiB)
25/02/19 00:38:53 INFO MemoryStore: Block broadcast_3_piece0 stored as bytes in memory (estimated size 34.4 KiB, free 433.7 MiB)
25/02/19 00:38:53 INFO BlockManagerInfo: Added broadcast_3_piece0 in memory on 10.255.255.254:43927 (size: 34.4 KiB, free: 434.3 MiB)
25/02/19 00:38:53 INFO SparkContext: Created broadcast 3 from count at App.scala:15
25/02/19 00:38:53 INFO FileSourceScanExec: Planning scan with bin packing, max size: 4211559 bytes, open cost is considered as scanning 4194304 bytes.
25/02/19 00:38:54 INFO DAGScheduler: Registering RDD 13 (count at App.scala:15) as input to shuffle 0
25/02/19 00:38:54 INFO DAGScheduler: Got map stage job 1 (count at App.scala:15) with 1 output partitions
25/02/19 00:38:54 INFO DAGScheduler: Final stage: ShuffleMapStage 1 (count at App.scala:15)
25/02/19 00:38:54 INFO DAGScheduler: Parents of final stage: List()
25/02/19 00:38:54 INFO DAGScheduler: Missing parents: List()
25/02/19 00:38:54 INFO DAGScheduler: Submitting ShuffleMapStage 1 (MapPartitionsRDD[13] at count at App.scala:15), which has no missing parents
25/02/19 00:38:54 INFO MemoryStore: Block broadcast_4 stored as values in memory (estimated size 18.8 KiB, free 433.7 MiB)
25/02/19 00:38:54 INFO MemoryStore: Block broadcast_4_piece0 stored as bytes in memory (estimated size 9.1 KiB, free 433.7 MiB)
25/02/19 00:38:54 INFO BlockManagerInfo: Added broadcast_4_piece0 in memory on 10.255.255.254:43927 (size: 9.1 KiB, free: 434.3 MiB)
25/02/19 00:38:54 INFO SparkContext: Created broadcast 4 from broadcast at DAGScheduler.scala:1585
25/02/19 00:38:54 INFO DAGScheduler: Submitting 1 missing tasks from ShuffleMapStage 1 (MapPartitionsRDD[13] at count at App.scala:15) (first 15 tasks are for partitions Vector(0))
25/02/19 00:38:54 INFO TaskSchedulerImpl: Adding task set 1.0 with 1 tasks resource profile 0
25/02/19 00:38:54 INFO TaskSetManager: Starting task 0.0 in stage 1.0 (TID 1) (10.255.255.254, executor driver, partition 0, PROCESS_LOCAL, 9885 bytes)
25/02/19 00:38:54 INFO Executor: Running task 0.0 in stage 1.0 (TID 1)
25/02/19 00:38:54 INFO CodeGenerator: Code generated in 11.085534 ms
25/02/19 00:38:54 INFO FileScanRDD: Reading File path: file:///mnt/g/scala/example-spark-scala/customers-100.csv, range: 0-17255, partition values: [empty row]
25/02/19 00:38:54 INFO CodeGenerator: Code generated in 7.906767 ms
25/02/19 00:38:54 INFO Executor: Finished task 0.0 in stage 1.0 (TID 1). 2105 bytes result sent to driver
25/02/19 00:38:54 INFO TaskSetManager: Finished task 0.0 in stage 1.0 (TID 1) in 320 ms on 10.255.255.254 (executor driver) (1/1)
25/02/19 00:38:54 INFO TaskSchedulerImpl: Removed TaskSet 1.0, whose tasks have all completed, from pool
25/02/19 00:38:54 INFO DAGScheduler: ShuffleMapStage 1 (count at App.scala:15) finished in 0.351 s
25/02/19 00:38:54 INFO DAGScheduler: looking for newly runnable stages
25/02/19 00:38:54 INFO DAGScheduler: running: HashSet()
25/02/19 00:38:54 INFO DAGScheduler: waiting: HashSet()
25/02/19 00:38:54 INFO DAGScheduler: failed: HashSet()
25/02/19 00:38:54 INFO BlockManagerInfo: Removed broadcast_2_piece0 on 10.255.255.254:43927 in memory (size: 34.4 KiB, free: 434.3 MiB)
25/02/19 00:38:54 INFO BlockManagerInfo: Removed broadcast_1_piece0 on 10.255.255.254:43927 in memory (size: 6.5 KiB, free: 434.3 MiB)
25/02/19 00:38:54 INFO BlockManagerInfo: Removed broadcast_0_piece0 on 10.255.255.254:43927 in memory (size: 34.4 KiB, free: 434.4 MiB)
25/02/19 00:38:54 INFO CodeGenerator: Code generated in 12.077153 ms
25/02/19 00:38:54 INFO SparkContext: Starting job: count at App.scala:15
25/02/19 00:38:54 INFO DAGScheduler: Got job 2 (count at App.scala:15) with 1 output partitions
25/02/19 00:38:54 INFO DAGScheduler: Final stage: ResultStage 3 (count at App.scala:15)
25/02/19 00:38:54 INFO DAGScheduler: Parents of final stage: List(ShuffleMapStage 2)
25/02/19 00:38:54 INFO DAGScheduler: Missing parents: List()
25/02/19 00:38:54 INFO DAGScheduler: Submitting ResultStage 3 (MapPartitionsRDD[16] at count at App.scala:15), which has no missing parents
25/02/19 00:38:54 INFO MemoryStore: Block broadcast_5 stored as values in memory (estimated size 13.1 KiB, free 434.1 MiB)
25/02/19 00:38:54 INFO MemoryStore: Block broadcast_5_piece0 stored as bytes in memory (estimated size 6.1 KiB, free 434.1 MiB)
25/02/19 00:38:54 INFO BlockManagerInfo: Added broadcast_5_piece0 in memory on 10.255.255.254:43927 (size: 6.1 KiB, free: 434.4 MiB)
25/02/19 00:38:54 INFO SparkContext: Created broadcast 5 from broadcast at DAGScheduler.scala:1585
25/02/19 00:38:54 INFO DAGScheduler: Submitting 1 missing tasks from ResultStage 3 (MapPartitionsRDD[16] at count at App.scala:15) (first 15 tasks are for partitions Vector(0))
25/02/19 00:38:54 INFO TaskSchedulerImpl: Adding task set 3.0 with 1 tasks resource profile 0
25/02/19 00:38:54 INFO TaskSetManager: Starting task 0.0 in stage 3.0 (TID 2) (10.255.255.254, executor driver, partition 0, NODE_LOCAL, 9276 bytes)
25/02/19 00:38:54 INFO Executor: Running task 0.0 in stage 3.0 (TID 2)
25/02/19 00:38:54 INFO ShuffleBlockFetcherIterator: Getting 1 (60.0 B) non-empty blocks including 1 (60.0 B) local and 0 (0.0 B) host-local and 0 (0.0 B) push-merged-local and 0 (0.0 B) remote blocks
25/02/19 00:38:54 INFO ShuffleBlockFetcherIterator: Started 0 remote fetches in 15 ms
25/02/19 00:38:54 INFO CodeGenerator: Code generated in 8.620798 ms
25/02/19 00:38:54 INFO Executor: Finished task 0.0 in stage 3.0 (TID 2). 4084 bytes result sent to driver
25/02/19 00:38:54 INFO TaskSetManager: Finished task 0.0 in stage 3.0 (TID 2) in 97 ms on 10.255.255.254 (executor driver) (1/1)
25/02/19 00:38:54 INFO TaskSchedulerImpl: Removed TaskSet 3.0, whose tasks have all completed, from pool
25/02/19 00:38:54 INFO DAGScheduler: ResultStage 3 (count at App.scala:15) finished in 0.108 s
25/02/19 00:38:54 INFO DAGScheduler: Job 2 is finished. Cancelling potential speculative or zombie tasks for this job
25/02/19 00:38:54 INFO TaskSchedulerImpl: Killing all running tasks in stage 3: Stage finished
25/02/19 00:38:54 INFO DAGScheduler: Job 2 finished: count at App.scala:15, took 0.120309 s
Count: 100
25/02/19 00:38:54 INFO SparkContext: SparkContext is stopping with exitCode 0.
&lt;/code&gt;&lt;/pre&gt;&lt;div style=&quot;font-size: 0em; height: 0; margin: 0; max-height: 0; max-width: 0; overflow: hidden; padding: 0; width: 0;&quot; title=&quot;MDH:5pyA6L+R6ZaL5aeL5Y+D6IiHIFNwYXJrIOeahOWwiOahiO+8jOaJgOS7peacieepuueahOaZguWA
meacg+mWi+Wni+WBmuS4gOS6myBTcGFyayDmlrDmiYvnrYboqJgg7aC97biG44CC5oiR55qE55Kw
5aKD5pivIFdpbmRvd3MgMTHvvIzkuI3pgY7lr6bpmpvkuIrlnKjkuI3lkIznkrDlooPnmoTkvZzm
s5Xmh4noqbLmspLmnInlt67lpKrlpJrnmoTmhJ/oprrvvIzkvYbmiJboqLHmnIPmnInkuIDkupvl
sI/lnLDmlrnmnInlvq7lppnnmoTkuI3lkIzlsLHmmK/kuobjgILlm6DngrrmiJHoh6rlt7HnmoTp
m7vohabmmK8gV2luZG93cyAxMe+8jOS9huW5s+W4uOavlOi8g+e/kuaFo+WcqCBMaW51eC1iYXNl
ZCDnmoTnkrDlooPplovnmbzvvIzmiYDku6Xlr6bpmpvkuIrmlofnq6DkuK3mnIPntIDpjITnmoTm
nbHopb/vvIzlpKfpq5TkuIrmnIPmmK8gV2luZG93cyAxMSArIFdTTDIg55qE54uA5rOB77yM5oyH
5Luk5Y6f5YmH5LiK5oiR6YO95piv5ZyoIFdTTDIg55qEIFVidW50dSDoo6Hln7fooYznmoTjgII8
YnI+PGJyPiMgU3Bhcmsg55Kw5aKD5rqW5YKZPGJyPjxicj7pppblhYjvvIzmoLnmk5ogU3Bhcmsg
5a6Y5pa55paH5Lu25L6G55yL77yMU3Bhcmsg5pys6Lqr5LiN6ZyA6KaB5a6J6KOd77yM5Y+q6ZyA
6KaB5LiL6LyJ5Lim5Z+36KGM55u45oeJ55qE5oyH5Luk5Y2z5Y+v44CC5Zug5q2k5Li76KaB6ZyA
6KaB5rqW5YKZ55qE55Kw5aKD5pivIEphdmHjgIFNYXZlbu+8jOS4puS4lOS4i+i8iSBTcGFya+OA
guWFtuS4rSBKYXZhIOi3nyBNYXZlbiDmgI7purzlronoo53vvIzmiJHlsLHnm7TmjqXnlaXpgY7k
uobvvIzpgJnoo6HnsKHllq7oqqrmmI7miJHnm67liY3nmoTnkrDlooPvvJo8YnI+PGJyPiogSmF2
YTogYDIxLjAuNis3LVVidW50dS0xMjQuMDQuMWA8YnI+KiBNYXZlbjogYDMuOC44YCA8YnI+PGJy
PuWcqCBTcGFyayDlrpjmlrnntrLnq5nkuIvovIkgU3Bhcmsg5pmC77yM5Zug54K655uu5YmN55qE
5pyA5paw54mI5pivIGAzLjUuNGDvvIzlm6DmraTmiJHmmK/pgbjmk4fku6XkuIvnmoTpgbjpoIXk
uIvovInnmoTvvJo8YnI+PGJyPiogU3Bhcms6IGAzLjUuNGA8YnI+KiBQYWNrYWdlIFR5cGU6IGBQ
cmUtYnVpbHQgZm9yIEFwYWNoZSBIYWRvb3AgMy4zIGFuZCBsYXRlciAoU2NhbGEgMi4xMylgPGJy
Pjxicj7lhbbkuK0gUGFja2FnZSBUeXBlIOWFtuWvpuaIkeebruWJjeS5n+S4jeWkqueiuuWumuaV
iOaenOaYr+S7gOm6vO+8jOS9huWboOeCuuaIkeeahOevhOS+i+WwiOahiOacg+eUqCBTY2FsYe+8
jOWboOatpOWwsemBuOS6huWQjeWtl+aciSBTY2FsYSDnmoQg7aC97bmD44CCPGJyPjxicj4jIOa6
luWCmeevhOS+i+WwiOahiDxicj48YnI+5paw5omL55qE56ys5LiA5Lu25LqL5bCx5piv5YWI5byE
5YCLIEhlbGxvIFdvcmxk77yM5LiN6YGOIFNwYXJrIOWNsCBIZWxsbyBXb3JsZCDlpb3lg4/mnInp
u57lvLHvvIzmiYDku6XmiJHlvITkuobkuIDlgIvmr5QgSGVsbG8gV29ybGQg6KSH6Zuc5LiA5ru0
5ru055qE56+E5L6L5bCI5qGI77yM6aCG5L6/5Lmf57Ch5Zau6Kmm5LqG5LiA5LiLIFNwYXJrIOiI
hyBTY2FsYSDnmoTlr6vms5XjgII8YnI+PGJyPmBgYHNoPGJyPmdpdCBjbG9uZSBnaXRAZ2l0aHVi
LmNvbTpqaW13YXluZXllaC9leGFtcGxlLXNwYXJrLXNjYWxhLmdpdDxicj5gYGA8YnI+PGJyPuaO
peiRl+WboOeCuuimgeaKiuaJk+WMheeahCBKQVIg5Lif5Y6757WmIFNwYXJrIOWft+ihjO+8jOaJ
gOS7peaIkeWAkemcgOimgeaKiumAmeWAi+WwiOahiOaJk+WMheaIkCBKQVLjgII8YnI+PGJyPmBg
YHNoPGJyPmNkIGV4YW1wbGUtc3Bhcmstc2NhbGE8YnI+bXZuIGNsZWFuIHBhY2thZ2U8YnI+YGBg
PGJyPjxicj7miZPljIXlh7rkvobnmoTmqpTmoYjmnIPlnKggYC4vdGFyZ2V0L2V4YW1wbGUtc3Bh
cmstc2NhbGEtMS4wLVNOQVBTSE9ULmphcmDvvIzpgJnlgIvot6/lvpHlhYjoqJjkvY/vvIznrYnn
rYnmnIPnlKjliLDjgILlnKjpgqPkuYvliY3lhYjkvobnnIvkuIDkuIvpgJnlgIvnr4TkvovlsIjm
oYjoo6Hlr6vkuobku4DpurzjgILpppblhYjlj6/ku6XlhYjnnIsgYHR3LmppbXdheW5leWVoLmV4
YW1wbGUuc3Bhcmtfc2NhbGEuQXBwYCDpgJnlgIvpoZ7liKXvvIzlroPnmoTlhaflrrnlpoLkuIvv
vJo8YnI+PGJyPmBgYHNjYWxhPGJyPm9iamVjdCBBcHAgezxicj48YnI+wqAgZGVmIG1haW4oYXJn
czogQXJyYXlbU3RyaW5nXSk6IFVuaXQgPSB7PGJyPsKgwqDCoCBwcmludGxuKCJIZWxsbyBXb3Js
ZCEiKTxicj48YnI+wqDCoMKgIHZhbCBzcGFyayA9IFNwYXJrU2Vzc2lvbi5idWlsZGVyKCkuYXBw
TmFtZSgiZXhtYXBsZSBhcHAiKS5nZXRPckNyZWF0ZSgpPGJyPsKgwqDCoCB2YWwgY3VzdG9tZXJE
ZiA9PGJyPsKgwqDCoMKgwqAgc3BhcmsucmVhZC5vcHRpb25zKE1hcCgiaGVhZGVyIiAtJmd0OyAi
dHJ1ZSIpKS5jc3YoImN1c3RvbWVycy0xMDAuY3N2Iik8YnI+wqDCoMKgIHByaW50bG4ocyJDb3Vu
dDogJHtjdXN0b21lckRmLmNvdW50KCl9Iik8YnI+PGJyPsKgwqDCoCBzcGFyay5zdG9wKCk8YnI+
wqAgfTxicj48YnI+fTxicj5gYGA8YnI+PGJyPuWboOeCuueoi+W8j+eivOW+iOefre+8jOWwseS4
jeS4gOS4gOi0hei/sOS6hu+8jOewoeWWruS+huiqquWwseaYr+WFiOW7uueri+S4gOWAiyBgU3Bh
cmtTZXNzaW9uYO+8jOiugOWPluWwiOahiOagueebrumMhOeahCBgY3VzdG9tZXJzLTEwMC5jc3Zg
IOaqlO+8jOmAmeWAiyBDU1Yg5qqU5piv5b6eIFtzYW1wbGUtY3N2LWZpbGVzXShodHRwczovL2dp
dGh1Yi5jb20vZGF0YWJsaXN0L3NhbXBsZS1jc3YtZmlsZXMpIOS4i+i8ieeahOevhOS+iyBDU1Yg
5qqU44CCQ1NWIOiugOWPlumAsuS+huiuiuaIkCBkYXRhIGZyYW1l77yM5oyH5a6a5a6D5piv5pyJ
5qiZ6aCt55qE77yM54S25b6M566X5Ye66YCZ5YCLIGRhdGEgZnJhbWUg55qE5YCL5pW45Lim5Y2w
5Ye65L6G44CCPGJyPjxicj7oroDlj5bmqpTmoYjnmoTpg6jku73opoHnibnliKXms6jmhI/nmoTm
mK/vvIzpgJnoo6HntabnmoTot6/lvpHmmK/nm7jlsI3mlrzlsIjmoYjnm67pjITnmoTot6/lvpHv
vIzkvYbmqpTmoYjkuKbpnZ7mlL7lnKggYHNyYy9tYWluL3Jlc291cmNlc2Ag6LOH5paZ5aS+5YWn
44CC5oiR6Kmi5ZWPIENoYXRHUFQg5pmC77yMQ2hhdEdQVCDlm57nrZToqqogU3Bhcmsg5q2j5bi4
6YGL5L2c5pmC77yM6LOH5paZ6YO95piv5b6e5aSW6YOo6Lev5b6R5bi26YCy5L6G55qE77yM5omA
5Lul5LiA6Iis5L6G6KqqIFNwYXJrIOeoi+W8j+S4puS4jeacg+iugOWPliBKQVIg6KOh6Z2i55qE
6LOH5rqQ44CC6YCZ5YCL55CG55Sx6IG96LW35L6G5piv5oSf6Ka66KC75ZCI55CG55qE77yM55Wi
56uf5pyD54m55Zyw55SoIFNwYXJrIOWft+ihjOeahOmBi+eul++8jOizh+aWmemHj+aHieipsumD
veS4jeacg+Wwj++8jOS5n+S4jeacg+acieS6uuaDs+aKiuizh+aWmeaUvumAsiBKQVIg6KOh5ZCn
IO2gve24huOAgjxicj48YnI+5Y+m5aSW5Lmf55yL5LiA5LiL5LiA5YCL5pyA57Ch5Zau55qEIFNw
YXJrIOWwiOahiO+8jOWmguaenOeUqCBNYXZlbiDmiZPljIXnmoToqbHvvIzpnIDopoHku4Dpurzm
nbHopb/jgIJQT00g6KOh6Z2i5Y+q6ZyA6KaB5Lul5LiL55qEIGRlcGVuZGVuY2llcyDljbPlj6/j
gII8YnI+PGJyPmBgYHhtbDxicj4mbHQ7ZGVwZW5kZW5jeSZndDs8YnI+wqDCoMKgICZsdDtncm91
cElkJmd0O29yZy5zY2FsYS1sYW5nJmx0Oy9ncm91cElkJmd0Ozxicj7CoMKgwqAgJmx0O2FydGlm
YWN0SWQmZ3Q7c2NhbGEtbGlicmFyeSZsdDsvYXJ0aWZhY3RJZCZndDs8YnI+wqDCoMKgICZsdDt2
ZXJzaW9uJmd0OyR7c2NhbGEudmVyc2lvbn0mbHQ7L3ZlcnNpb24mZ3Q7PGJyPiZsdDsvZGVwZW5k
ZW5jeSZndDs8YnI+Jmx0O2RlcGVuZGVuY3kmZ3Q7ICZsdDshLS0gU3BhcmsgZGVwZW5kZW5jeSAt
LSZndDs8YnI+wqDCoMKgICZsdDtncm91cElkJmd0O29yZy5hcGFjaGUuc3BhcmsmbHQ7L2dyb3Vw
SWQmZ3Q7PGJyPsKgwqDCoCAmbHQ7YXJ0aWZhY3RJZCZndDtzcGFyay1zcWxfJHtzY2FsYS5iaW5h
cnkudmVyc2lvbn0mbHQ7L2FydGlmYWN0SWQmZ3Q7PGJyPsKgwqDCoCAmbHQ7dmVyc2lvbiZndDsz
LjUuNCZsdDsvdmVyc2lvbiZndDs8YnI+wqDCoMKgICZsdDtzY29wZSZndDtwcm92aWRlZCZsdDsv
c2NvcGUmZ3Q7PGJyPiZsdDsvZGVwZW5kZW5jeSZndDs8YnI+YGBgPGJyPjxicj7lhbbkuK3lj6/k
u6XnibnliKXms6jmhI/liLDvvIxTcGFyayDnmoQgZGVwZW5kZW5jeSDnmoQgc2NvcGUg5pyD5piv
IGBwcm92aWRlZGDvvIzpgJnmh4noqbLmmK/lm6Dngrrlr6bpmpvln7fooYzmmYLvvIxKQVIg5pyD
6LeR5ZyoIFNwYXJrIOS4iu+8jOaJgOS7pSBTcGFyayBkZXBlbmRlbmNpZXMg5oeJ6Kmy5Zyo5Z+3
6KGM55Kw5aKD5LiK6YO95pyJ44CCPGJyPjxicj4jIOWcqCBTcGFyayDln7fooYw8YnI+PGJyPuev
hOS+i+WwiOahiOeahCBKQVIg5rqW5YKZ5aW95Lul5b6M77yM5Zue5Yiw5YmN6Z2i5LiL6LyJ5aW9
55qEIFNwYXJrIOWjk+e4ruaqlO+8jOaKiuWug+ino+Wjk+e4ruW+jOmAsuWFpSBgL2JpbmAg6LOH
5paZ5aS+77yM5Lim5LiU5Z+36KGM5Lul5LiL55qEIGBzcGFyay1zdWJtaXRgIOaMh+S7pO+8jOWw
seWPr+S7peWVn+WLleS4gOWAi+acrOapn+eahCBTcGFyayDnkrDlooPln7fooYzliZvliZvnmoTn
r4TkvovlsIjmoYjkuobjgII8YnI+PGJyPmBgYHNoPGJyPi4vc3Bhcmstc3VibWl0IC0tY2xhc3Mg
InR3LmppbXdheW5leWVoLmV4YW1wbGUuc3Bhcmtfc2NhbGEuQXBwIiAtLW1hc3RlciBsb2NhbCAv
UEFUSC9UTy9QUk9KRUNUL3RhcmdldC9leGFtcGxlLXNwYXJrLXNjYWxhLTEuMC1TTkFQU0hPVC5q
YXI8YnI+YGBgPGJyPjxicj7nrYnkuobkuIDkuIvkuYvlvozvvIzlsLHlj6/ku6XnnIvliLAgY29u
c29sZSDovLjlh7rnmoQgU3BhcmsgbG9ncyDkuobjgILpgJnoo6HmiJHosrzkuoblvojplbfvvIzm
mK/lm6DngrrpoIbkvr/mg7PopoHntIDpjITlroPnmoTln7fooYzpgY7nqIvvvIzkuYvlvozmg7Po
poHliIbmnpDlroPnmoTlhaflrrnjgILkuI3pgY7nj77pmo7mrrXph43opoHnmoTmmK8gbG9nIOeV
tuS4reagvOW8j+aykuacieaXpeacn+eahOmCo+WFqeihjO+8jOWboOeCuueoi+W8j+eivOaYr+eU
qCBgcHJpbnRsbigpYCDkvobovLjlh7rnmoTvvIzmiYDku6XmnIPlv73nlaUgbG9nZ2luZyDmoLzl
vI/nm7TmjqXlnKggY29uc29sZSDljbDlh7rlj6rmnIkgbWVzc2FnZSDnmoQgbG9n44CC5Y+v5Lul
55yL5Ye65a6D566X5Ye65LqGIGRhdGEgZnJhbWUg6KOh6Z2i5pyJIDEwMCDnrYbos4fmlpnjgII8
YnI+YGBgPGJyPkhlbGxvIFdvcmxkITxicj5Db3VudDogMTAwPGJyPmBgYDxicj7ku6XkuIvliYfm
mK/ntIDpjITkuIDkuIsgbG9nIOeVtuS4reWNsOWHuui3nyBgQXBwLnNjYWxhYCDmnInpl5znmoTp
g6jku73vvIzpgJnkupsgbG9nIOWwseaYr+Wvpumam+WcqOaOkueoi+S4puWft+ihjOevhOS+i+eo
i+W8j+aZgueahOmBjueoi+S6huOAgjxicj48YnI+YGBgPGJyPjI1LzAyLzE5IDAwOjM4OjUxIElO
Rk8gU3BhcmtDb250ZXh0OiBDcmVhdGVkIGJyb2FkY2FzdCAwIGZyb20gY3N2IGF0IEFwcC5zY2Fs
YToxNDxicj4yNS8wMi8xOSAwMDozODo1MSBJTkZPIEZpbGVTb3VyY2VTY2FuRXhlYzogUGxhbm5p
bmcgc2NhbiB3aXRoIGJpbiBwYWNraW5nLCBtYXggc2l6ZTogNDIxMTU1OSBieXRlcywgb3BlbiBj
b3N0IGlzIGNvbnNpZGVyZWQgYXMgc2Nhbm5pbmcgNDE5NDMwNCBieXRlcy48YnI+MjUvMDIvMTkg
MDA6Mzg6NTIgSU5GTyBTcGFya0NvbnRleHQ6IFN0YXJ0aW5nIGpvYjogY3N2IGF0IEFwcC5zY2Fs
YToxNDxicj4yNS8wMi8xOSAwMDozODo1MiBJTkZPIERBR1NjaGVkdWxlcjogR290IGpvYiAwIChj
c3YgYXQgQXBwLnNjYWxhOjE0KSB3aXRoIDEgb3V0cHV0IHBhcnRpdGlvbnM8YnI+MjUvMDIvMTkg
MDA6Mzg6NTIgSU5GTyBEQUdTY2hlZHVsZXI6IEZpbmFsIHN0YWdlOiBSZXN1bHRTdGFnZSAwIChj
c3YgYXQgQXBwLnNjYWxhOjE0KTxicj4yNS8wMi8xOSAwMDozODo1MiBJTkZPIERBR1NjaGVkdWxl
cjogUGFyZW50cyBvZiBmaW5hbCBzdGFnZTogTGlzdCgpPGJyPjI1LzAyLzE5IDAwOjM4OjUyIElO
Rk8gREFHU2NoZWR1bGVyOiBNaXNzaW5nIHBhcmVudHM6IExpc3QoKTxicj4yNS8wMi8xOSAwMDoz
ODo1MiBJTkZPIERBR1NjaGVkdWxlcjogU3VibWl0dGluZyBSZXN1bHRTdGFnZSAwIChNYXBQYXJ0
aXRpb25zUkREWzNdIGF0IGNzdiBhdCBBcHAuc2NhbGE6MTQpLCB3aGljaCBoYXMgbm8gbWlzc2lu
ZyBwYXJlbnRzPGJyPjI1LzAyLzE5IDAwOjM4OjUyIElORk8gTWVtb3J5U3RvcmU6IEJsb2NrIGJy
b2FkY2FzdF8xIHN0b3JlZCBhcyB2YWx1ZXMgaW4gbWVtb3J5IChlc3RpbWF0ZWQgc2l6ZSAxMy44
IEtpQiwgZnJlZSA0MzQuMiBNaUIpPGJyPjI1LzAyLzE5IDAwOjM4OjUyIElORk8gTWVtb3J5U3Rv
cmU6IEJsb2NrIGJyb2FkY2FzdF8xX3BpZWNlMCBzdG9yZWQgYXMgYnl0ZXMgaW4gbWVtb3J5IChl
c3RpbWF0ZWQgc2l6ZSA2LjUgS2lCLCBmcmVlIDQzNC4yIE1pQik8YnI+MjUvMDIvMTkgMDA6Mzg6
NTIgSU5GTyBCbG9ja01hbmFnZXJJbmZvOiBBZGRlZCBicm9hZGNhc3RfMV9waWVjZTAgaW4gbWVt
b3J5IG9uIDEwLjI1NS4yNTUuMjU0OjQzOTI3IChzaXplOiA2LjUgS2lCLCBmcmVlOiA0MzQuNCBN
aUIpPGJyPjI1LzAyLzE5IDAwOjM4OjUyIElORk8gU3BhcmtDb250ZXh0OiBDcmVhdGVkIGJyb2Fk
Y2FzdCAxIGZyb20gYnJvYWRjYXN0IGF0IERBR1NjaGVkdWxlci5zY2FsYToxNTg1PGJyPjI1LzAy
LzE5IDAwOjM4OjUyIElORk8gREFHU2NoZWR1bGVyOiBTdWJtaXR0aW5nIDEgbWlzc2luZyB0YXNr
cyBmcm9tIFJlc3VsdFN0YWdlIDAgKE1hcFBhcnRpdGlvbnNSRERbM10gYXQgY3N2IGF0IEFwcC5z
Y2FsYToxNCkgKGZpcnN0IDE1IHRhc2tzIGFyZSBmb3IgcGFydGl0aW9ucyBWZWN0b3IoMCkpPGJy
PjI1LzAyLzE5IDAwOjM4OjUyIElORk8gVGFza1NjaGVkdWxlckltcGw6IEFkZGluZyB0YXNrIHNl
dCAwLjAgd2l0aCAxIHRhc2tzIHJlc291cmNlIHByb2ZpbGUgMDxicj4yNS8wMi8xOSAwMDozODo1
MiBJTkZPIFRhc2tTZXRNYW5hZ2VyOiBTdGFydGluZyB0YXNrIDAuMCBpbiBzdGFnZSAwLjAgKFRJ
RCAwKSAoMTAuMjU1LjI1NS4yNTQsIGV4ZWN1dG9yIGRyaXZlciwgcGFydGl0aW9uIDAsIFBST0NF
U1NfTE9DQUwsIDk4OTYgYnl0ZXMpIDxicj4yNS8wMi8xOSAwMDozODo1MiBJTkZPIEV4ZWN1dG9y
OiBSdW5uaW5nIHRhc2sgMC4wIGluIHN0YWdlIDAuMCAoVElEIDApPGJyPjI1LzAyLzE5IDAwOjM4
OjUyIElORk8gQ29kZUdlbmVyYXRvcjogQ29kZSBnZW5lcmF0ZWQgaW4gMTIuNTI4NTI1IG1zPGJy
PjI1LzAyLzE5IDAwOjM4OjUyIElORk8gRmlsZVNjYW5SREQ6IFJlYWRpbmcgRmlsZSBwYXRoOiBm
aWxlOi8vL21udC9nL3NjYWxhL2V4YW1wbGUtc3Bhcmstc2NhbGEvY3VzdG9tZXJzLTEwMC5jc3Ys
IHJhbmdlOiAwLTE3MjU1LCBwYXJ0aXRpb24gdmFsdWVzOiBbZW1wdHkgcm93XTxicj4yNS8wMi8x
OSAwMDozODo1MiBJTkZPIENvZGVHZW5lcmF0b3I6IENvZGUgZ2VuZXJhdGVkIGluIDQwLjcwOTg1
OCBtczxicj4yNS8wMi8xOSAwMDozODo1MyBJTkZPIEV4ZWN1dG9yOiBGaW5pc2hlZCB0YXNrIDAu
MCBpbiBzdGFnZSAwLjAgKFRJRCAwKS4gMTgwMCBieXRlcyByZXN1bHQgc2VudCB0byBkcml2ZXI8
YnI+MjUvMDIvMTkgMDA6Mzg6NTMgSU5GTyBUYXNrU2V0TWFuYWdlcjogRmluaXNoZWQgdGFzayAw
LjAgaW4gc3RhZ2UgMC4wIChUSUQgMCkgaW4gNjk3IG1zIG9uIDEwLjI1NS4yNTUuMjU0IChleGVj
dXRvciBkcml2ZXIpICgxLzEpPGJyPjI1LzAyLzE5IDAwOjM4OjUzIElORk8gVGFza1NjaGVkdWxl
ckltcGw6IFJlbW92ZWQgVGFza1NldCAwLjAsIHdob3NlIHRhc2tzIGhhdmUgYWxsIGNvbXBsZXRl
ZCwgZnJvbSBwb29sPGJyPjI1LzAyLzE5IDAwOjM4OjUzIElORk8gREFHU2NoZWR1bGVyOiBSZXN1
bHRTdGFnZSAwIChjc3YgYXQgQXBwLnNjYWxhOjE0KSBmaW5pc2hlZCBpbiAwLjg5MCBzPGJyPjI1
LzAyLzE5IDAwOjM4OjUzIElORk8gREFHU2NoZWR1bGVyOiBKb2IgMCBpcyBmaW5pc2hlZC4gQ2Fu
Y2VsbGluZyBwb3RlbnRpYWwgc3BlY3VsYXRpdmUgb3Igem9tYmllIHRhc2tzIGZvciB0aGlzIGpv
Yjxicj4yNS8wMi8xOSAwMDozODo1MyBJTkZPIFRhc2tTY2hlZHVsZXJJbXBsOiBLaWxsaW5nIGFs
bCBydW5uaW5nIHRhc2tzIGluIHN0YWdlIDA6IFN0YWdlIGZpbmlzaGVkPGJyPjI1LzAyLzE5IDAw
OjM4OjUzIElORk8gREFHU2NoZWR1bGVyOiBKb2IgMCBmaW5pc2hlZDogY3N2IGF0IEFwcC5zY2Fs
YToxNCwgdG9vayAwLjk2NTI0NyBzPGJyPjI1LzAyLzE5IDAwOjM4OjUzIElORk8gQ29kZUdlbmVy
YXRvcjogQ29kZSBnZW5lcmF0ZWQgaW4gMTcuNTA2NDg1IG1zPGJyPjI1LzAyLzE5IDAwOjM4OjUz
IElORk8gRmlsZVNvdXJjZVN0cmF0ZWd5OiBQdXNoZWQgRmlsdGVyczogPGJyPjI1LzAyLzE5IDAw
OjM4OjUzIElORk8gRmlsZVNvdXJjZVN0cmF0ZWd5OiBQb3N0LVNjYW4gRmlsdGVyczo8YnI+MjUv
MDIvMTkgMDA6Mzg6NTMgSU5GTyBNZW1vcnlTdG9yZTogQmxvY2sgYnJvYWRjYXN0XzIgc3RvcmVk
IGFzIHZhbHVlcyBpbiBtZW1vcnkgKGVzdGltYXRlZCBzaXplIDE5OS41IEtpQiwgZnJlZSA0MzQu
MCBNaUIpPGJyPjI1LzAyLzE5IDAwOjM4OjUzIElORk8gTWVtb3J5U3RvcmU6IEJsb2NrIGJyb2Fk
Y2FzdF8yX3BpZWNlMCBzdG9yZWQgYXMgYnl0ZXMgaW4gbWVtb3J5IChlc3RpbWF0ZWQgc2l6ZSAz
NC40IEtpQiwgZnJlZSA0MzMuOSBNaUIpPGJyPjI1LzAyLzE5IDAwOjM4OjUzIElORk8gQmxvY2tN
YW5hZ2VySW5mbzogQWRkZWQgYnJvYWRjYXN0XzJfcGllY2UwIGluIG1lbW9yeSBvbiAxMC4yNTUu
MjU1LjI1NDo0MzkyNyAoc2l6ZTogMzQuNCBLaUIsIGZyZWU6IDQzNC4zIE1pQik8YnI+MjUvMDIv
MTkgMDA6Mzg6NTMgSU5GTyBTcGFya0NvbnRleHQ6IENyZWF0ZWQgYnJvYWRjYXN0IDIgZnJvbSBj
c3YgYXQgQXBwLnNjYWxhOjE0PGJyPjI1LzAyLzE5IDAwOjM4OjUzIElORk8gRmlsZVNvdXJjZVNj
YW5FeGVjOiBQbGFubmluZyBzY2FuIHdpdGggYmluIHBhY2tpbmcsIG1heCBzaXplOiA0MjExNTU5
IGJ5dGVzLCBvcGVuIGNvc3QgaXMgY29uc2lkZXJlZCBhcyBzY2FubmluZyA0MTk0MzA0IGJ5dGVz
Ljxicj4yNS8wMi8xOSAwMDozODo1MyBJTkZPIEZpbGVTb3VyY2VTdHJhdGVneTogUHVzaGVkIEZp
bHRlcnM6IDxicj4yNS8wMi8xOSAwMDozODo1MyBJTkZPIEZpbGVTb3VyY2VTdHJhdGVneTogUG9z
dC1TY2FuIEZpbHRlcnM6PGJyPjI1LzAyLzE5IDAwOjM4OjUzIElORk8gQ29kZUdlbmVyYXRvcjog
Q29kZSBnZW5lcmF0ZWQgaW4gMTIuODY1OTUgbXM8YnI+MjUvMDIvMTkgMDA6Mzg6NTMgSU5GTyBN
ZW1vcnlTdG9yZTogQmxvY2sgYnJvYWRjYXN0XzMgc3RvcmVkIGFzIHZhbHVlcyBpbiBtZW1vcnkg
KGVzdGltYXRlZCBzaXplIDE5OS40IEtpQiwgZnJlZSA0MzMuNyBNaUIpPGJyPjI1LzAyLzE5IDAw
OjM4OjUzIElORk8gTWVtb3J5U3RvcmU6IEJsb2NrIGJyb2FkY2FzdF8zX3BpZWNlMCBzdG9yZWQg
YXMgYnl0ZXMgaW4gbWVtb3J5IChlc3RpbWF0ZWQgc2l6ZSAzNC40IEtpQiwgZnJlZSA0MzMuNyBN
aUIpPGJyPjI1LzAyLzE5IDAwOjM4OjUzIElORk8gQmxvY2tNYW5hZ2VySW5mbzogQWRkZWQgYnJv
YWRjYXN0XzNfcGllY2UwIGluIG1lbW9yeSBvbiAxMC4yNTUuMjU1LjI1NDo0MzkyNyAoc2l6ZTog
MzQuNCBLaUIsIGZyZWU6IDQzNC4zIE1pQik8YnI+MjUvMDIvMTkgMDA6Mzg6NTMgSU5GTyBTcGFy
a0NvbnRleHQ6IENyZWF0ZWQgYnJvYWRjYXN0IDMgZnJvbSBjb3VudCBhdCBBcHAuc2NhbGE6MTU8
YnI+MjUvMDIvMTkgMDA6Mzg6NTMgSU5GTyBGaWxlU291cmNlU2NhbkV4ZWM6IFBsYW5uaW5nIHNj
YW4gd2l0aCBiaW4gcGFja2luZywgbWF4IHNpemU6IDQyMTE1NTkgYnl0ZXMsIG9wZW4gY29zdCBp
cyBjb25zaWRlcmVkIGFzIHNjYW5uaW5nIDQxOTQzMDQgYnl0ZXMuPGJyPjI1LzAyLzE5IDAwOjM4
OjU0IElORk8gREFHU2NoZWR1bGVyOiBSZWdpc3RlcmluZyBSREQgMTMgKGNvdW50IGF0IEFwcC5z
Y2FsYToxNSkgYXMgaW5wdXQgdG8gc2h1ZmZsZSAwPGJyPjI1LzAyLzE5IDAwOjM4OjU0IElORk8g
REFHU2NoZWR1bGVyOiBHb3QgbWFwIHN0YWdlIGpvYiAxIChjb3VudCBhdCBBcHAuc2NhbGE6MTUp
IHdpdGggMSBvdXRwdXQgcGFydGl0aW9uczxicj4yNS8wMi8xOSAwMDozODo1NCBJTkZPIERBR1Nj
aGVkdWxlcjogRmluYWwgc3RhZ2U6IFNodWZmbGVNYXBTdGFnZSAxIChjb3VudCBhdCBBcHAuc2Nh
bGE6MTUpPGJyPjI1LzAyLzE5IDAwOjM4OjU0IElORk8gREFHU2NoZWR1bGVyOiBQYXJlbnRzIG9m
IGZpbmFsIHN0YWdlOiBMaXN0KCk8YnI+MjUvMDIvMTkgMDA6Mzg6NTQgSU5GTyBEQUdTY2hlZHVs
ZXI6IE1pc3NpbmcgcGFyZW50czogTGlzdCgpPGJyPjI1LzAyLzE5IDAwOjM4OjU0IElORk8gREFH
U2NoZWR1bGVyOiBTdWJtaXR0aW5nIFNodWZmbGVNYXBTdGFnZSAxIChNYXBQYXJ0aXRpb25zUkRE
WzEzXSBhdCBjb3VudCBhdCBBcHAuc2NhbGE6MTUpLCB3aGljaCBoYXMgbm8gbWlzc2luZyBwYXJl
bnRzPGJyPjI1LzAyLzE5IDAwOjM4OjU0IElORk8gTWVtb3J5U3RvcmU6IEJsb2NrIGJyb2FkY2Fz
dF80IHN0b3JlZCBhcyB2YWx1ZXMgaW4gbWVtb3J5IChlc3RpbWF0ZWQgc2l6ZSAxOC44IEtpQiwg
ZnJlZSA0MzMuNyBNaUIpPGJyPjI1LzAyLzE5IDAwOjM4OjU0IElORk8gTWVtb3J5U3RvcmU6IEJs
b2NrIGJyb2FkY2FzdF80X3BpZWNlMCBzdG9yZWQgYXMgYnl0ZXMgaW4gbWVtb3J5IChlc3RpbWF0
ZWQgc2l6ZSA5LjEgS2lCLCBmcmVlIDQzMy43IE1pQik8YnI+MjUvMDIvMTkgMDA6Mzg6NTQgSU5G
TyBCbG9ja01hbmFnZXJJbmZvOiBBZGRlZCBicm9hZGNhc3RfNF9waWVjZTAgaW4gbWVtb3J5IG9u
IDEwLjI1NS4yNTUuMjU0OjQzOTI3IChzaXplOiA5LjEgS2lCLCBmcmVlOiA0MzQuMyBNaUIpPGJy
PjI1LzAyLzE5IDAwOjM4OjU0IElORk8gU3BhcmtDb250ZXh0OiBDcmVhdGVkIGJyb2FkY2FzdCA0
IGZyb20gYnJvYWRjYXN0IGF0IERBR1NjaGVkdWxlci5zY2FsYToxNTg1PGJyPjI1LzAyLzE5IDAw
OjM4OjU0IElORk8gREFHU2NoZWR1bGVyOiBTdWJtaXR0aW5nIDEgbWlzc2luZyB0YXNrcyBmcm9t
IFNodWZmbGVNYXBTdGFnZSAxIChNYXBQYXJ0aXRpb25zUkREWzEzXSBhdCBjb3VudCBhdCBBcHAu
c2NhbGE6MTUpIChmaXJzdCAxNSB0YXNrcyBhcmUgZm9yIHBhcnRpdGlvbnMgVmVjdG9yKDApKTxi
cj4yNS8wMi8xOSAwMDozODo1NCBJTkZPIFRhc2tTY2hlZHVsZXJJbXBsOiBBZGRpbmcgdGFzayBz
ZXQgMS4wIHdpdGggMSB0YXNrcyByZXNvdXJjZSBwcm9maWxlIDA8YnI+MjUvMDIvMTkgMDA6Mzg6
NTQgSU5GTyBUYXNrU2V0TWFuYWdlcjogU3RhcnRpbmcgdGFzayAwLjAgaW4gc3RhZ2UgMS4wIChU
SUQgMSkgKDEwLjI1NS4yNTUuMjU0LCBleGVjdXRvciBkcml2ZXIsIHBhcnRpdGlvbiAwLCBQUk9D
RVNTX0xPQ0FMLCA5ODg1IGJ5dGVzKTxicj4yNS8wMi8xOSAwMDozODo1NCBJTkZPIEV4ZWN1dG9y
OiBSdW5uaW5nIHRhc2sgMC4wIGluIHN0YWdlIDEuMCAoVElEIDEpPGJyPjI1LzAyLzE5IDAwOjM4
OjU0IElORk8gQ29kZUdlbmVyYXRvcjogQ29kZSBnZW5lcmF0ZWQgaW4gMTEuMDg1NTM0IG1zPGJy
PjI1LzAyLzE5IDAwOjM4OjU0IElORk8gRmlsZVNjYW5SREQ6IFJlYWRpbmcgRmlsZSBwYXRoOiBm
aWxlOi8vL21udC9nL3NjYWxhL2V4YW1wbGUtc3Bhcmstc2NhbGEvY3VzdG9tZXJzLTEwMC5jc3Ys
IHJhbmdlOiAwLTE3MjU1LCBwYXJ0aXRpb24gdmFsdWVzOiBbZW1wdHkgcm93XTxicj4yNS8wMi8x
OSAwMDozODo1NCBJTkZPIENvZGVHZW5lcmF0b3I6IENvZGUgZ2VuZXJhdGVkIGluIDcuOTA2NzY3
IG1zPGJyPjI1LzAyLzE5IDAwOjM4OjU0IElORk8gRXhlY3V0b3I6IEZpbmlzaGVkIHRhc2sgMC4w
IGluIHN0YWdlIDEuMCAoVElEIDEpLiAyMTA1IGJ5dGVzIHJlc3VsdCBzZW50IHRvIGRyaXZlcjxi
cj4yNS8wMi8xOSAwMDozODo1NCBJTkZPIFRhc2tTZXRNYW5hZ2VyOiBGaW5pc2hlZCB0YXNrIDAu
MCBpbiBzdGFnZSAxLjAgKFRJRCAxKSBpbiAzMjAgbXMgb24gMTAuMjU1LjI1NS4yNTQgKGV4ZWN1
dG9yIGRyaXZlcikgKDEvMSk8YnI+MjUvMDIvMTkgMDA6Mzg6NTQgSU5GTyBUYXNrU2NoZWR1bGVy
SW1wbDogUmVtb3ZlZCBUYXNrU2V0IDEuMCwgd2hvc2UgdGFza3MgaGF2ZSBhbGwgY29tcGxldGVk
LCBmcm9tIHBvb2w8YnI+MjUvMDIvMTkgMDA6Mzg6NTQgSU5GTyBEQUdTY2hlZHVsZXI6IFNodWZm
bGVNYXBTdGFnZSAxIChjb3VudCBhdCBBcHAuc2NhbGE6MTUpIGZpbmlzaGVkIGluIDAuMzUxIHM8
YnI+MjUvMDIvMTkgMDA6Mzg6NTQgSU5GTyBEQUdTY2hlZHVsZXI6IGxvb2tpbmcgZm9yIG5ld2x5
IHJ1bm5hYmxlIHN0YWdlczxicj4yNS8wMi8xOSAwMDozODo1NCBJTkZPIERBR1NjaGVkdWxlcjog
cnVubmluZzogSGFzaFNldCgpPGJyPjI1LzAyLzE5IDAwOjM4OjU0IElORk8gREFHU2NoZWR1bGVy
OiB3YWl0aW5nOiBIYXNoU2V0KCk8YnI+MjUvMDIvMTkgMDA6Mzg6NTQgSU5GTyBEQUdTY2hlZHVs
ZXI6IGZhaWxlZDogSGFzaFNldCgpPGJyPjI1LzAyLzE5IDAwOjM4OjU0IElORk8gQmxvY2tNYW5h
Z2VySW5mbzogUmVtb3ZlZCBicm9hZGNhc3RfMl9waWVjZTAgb24gMTAuMjU1LjI1NS4yNTQ6NDM5
MjcgaW4gbWVtb3J5IChzaXplOiAzNC40IEtpQiwgZnJlZTogNDM0LjMgTWlCKTxicj4yNS8wMi8x
OSAwMDozODo1NCBJTkZPIEJsb2NrTWFuYWdlckluZm86IFJlbW92ZWQgYnJvYWRjYXN0XzFfcGll
Y2UwIG9uIDEwLjI1NS4yNTUuMjU0OjQzOTI3IGluIG1lbW9yeSAoc2l6ZTogNi41IEtpQiwgZnJl
ZTogNDM0LjMgTWlCKTxicj4yNS8wMi8xOSAwMDozODo1NCBJTkZPIEJsb2NrTWFuYWdlckluZm86
IFJlbW92ZWQgYnJvYWRjYXN0XzBfcGllY2UwIG9uIDEwLjI1NS4yNTUuMjU0OjQzOTI3IGluIG1l
bW9yeSAoc2l6ZTogMzQuNCBLaUIsIGZyZWU6IDQzNC40IE1pQik8YnI+MjUvMDIvMTkgMDA6Mzg6
NTQgSU5GTyBDb2RlR2VuZXJhdG9yOiBDb2RlIGdlbmVyYXRlZCBpbiAxMi4wNzcxNTMgbXM8YnI+
MjUvMDIvMTkgMDA6Mzg6NTQgSU5GTyBTcGFya0NvbnRleHQ6IFN0YXJ0aW5nIGpvYjogY291bnQg
YXQgQXBwLnNjYWxhOjE1PGJyPjI1LzAyLzE5IDAwOjM4OjU0IElORk8gREFHU2NoZWR1bGVyOiBH
b3Qgam9iIDIgKGNvdW50IGF0IEFwcC5zY2FsYToxNSkgd2l0aCAxIG91dHB1dCBwYXJ0aXRpb25z
PGJyPjI1LzAyLzE5IDAwOjM4OjU0IElORk8gREFHU2NoZWR1bGVyOiBGaW5hbCBzdGFnZTogUmVz
dWx0U3RhZ2UgMyAoY291bnQgYXQgQXBwLnNjYWxhOjE1KTxicj4yNS8wMi8xOSAwMDozODo1NCBJ
TkZPIERBR1NjaGVkdWxlcjogUGFyZW50cyBvZiBmaW5hbCBzdGFnZTogTGlzdChTaHVmZmxlTWFw
U3RhZ2UgMik8YnI+MjUvMDIvMTkgMDA6Mzg6NTQgSU5GTyBEQUdTY2hlZHVsZXI6IE1pc3Npbmcg
cGFyZW50czogTGlzdCgpPGJyPjI1LzAyLzE5IDAwOjM4OjU0IElORk8gREFHU2NoZWR1bGVyOiBT
dWJtaXR0aW5nIFJlc3VsdFN0YWdlIDMgKE1hcFBhcnRpdGlvbnNSRERbMTZdIGF0IGNvdW50IGF0
IEFwcC5zY2FsYToxNSksIHdoaWNoIGhhcyBubyBtaXNzaW5nIHBhcmVudHM8YnI+MjUvMDIvMTkg
MDA6Mzg6NTQgSU5GTyBNZW1vcnlTdG9yZTogQmxvY2sgYnJvYWRjYXN0XzUgc3RvcmVkIGFzIHZh
bHVlcyBpbiBtZW1vcnkgKGVzdGltYXRlZCBzaXplIDEzLjEgS2lCLCBmcmVlIDQzNC4xIE1pQik8
YnI+MjUvMDIvMTkgMDA6Mzg6NTQgSU5GTyBNZW1vcnlTdG9yZTogQmxvY2sgYnJvYWRjYXN0XzVf
cGllY2UwIHN0b3JlZCBhcyBieXRlcyBpbiBtZW1vcnkgKGVzdGltYXRlZCBzaXplIDYuMSBLaUIs
IGZyZWUgNDM0LjEgTWlCKTxicj4yNS8wMi8xOSAwMDozODo1NCBJTkZPIEJsb2NrTWFuYWdlcklu
Zm86IEFkZGVkIGJyb2FkY2FzdF81X3BpZWNlMCBpbiBtZW1vcnkgb24gMTAuMjU1LjI1NS4yNTQ6
NDM5MjcgKHNpemU6IDYuMSBLaUIsIGZyZWU6IDQzNC40IE1pQik8YnI+MjUvMDIvMTkgMDA6Mzg6
NTQgSU5GTyBTcGFya0NvbnRleHQ6IENyZWF0ZWQgYnJvYWRjYXN0IDUgZnJvbSBicm9hZGNhc3Qg
YXQgREFHU2NoZWR1bGVyLnNjYWxhOjE1ODU8YnI+MjUvMDIvMTkgMDA6Mzg6NTQgSU5GTyBEQUdT
Y2hlZHVsZXI6IFN1Ym1pdHRpbmcgMSBtaXNzaW5nIHRhc2tzIGZyb20gUmVzdWx0U3RhZ2UgMyAo
TWFwUGFydGl0aW9uc1JERFsxNl0gYXQgY291bnQgYXQgQXBwLnNjYWxhOjE1KSAoZmlyc3QgMTUg
dGFza3MgYXJlIGZvciBwYXJ0aXRpb25zIFZlY3RvcigwKSk8YnI+MjUvMDIvMTkgMDA6Mzg6NTQg
SU5GTyBUYXNrU2NoZWR1bGVySW1wbDogQWRkaW5nIHRhc2sgc2V0IDMuMCB3aXRoIDEgdGFza3Mg
cmVzb3VyY2UgcHJvZmlsZSAwPGJyPjI1LzAyLzE5IDAwOjM4OjU0IElORk8gVGFza1NldE1hbmFn
ZXI6IFN0YXJ0aW5nIHRhc2sgMC4wIGluIHN0YWdlIDMuMCAoVElEIDIpICgxMC4yNTUuMjU1LjI1
NCwgZXhlY3V0b3IgZHJpdmVyLCBwYXJ0aXRpb24gMCwgTk9ERV9MT0NBTCwgOTI3NiBieXRlcyk8
YnI+MjUvMDIvMTkgMDA6Mzg6NTQgSU5GTyBFeGVjdXRvcjogUnVubmluZyB0YXNrIDAuMCBpbiBz
dGFnZSAzLjAgKFRJRCAyKTxicj4yNS8wMi8xOSAwMDozODo1NCBJTkZPIFNodWZmbGVCbG9ja0Zl
dGNoZXJJdGVyYXRvcjogR2V0dGluZyAxICg2MC4wIEIpIG5vbi1lbXB0eSBibG9ja3MgaW5jbHVk
aW5nIDEgKDYwLjAgQikgbG9jYWwgYW5kIDAgKDAuMCBCKSBob3N0LWxvY2FsIGFuZCAwICgwLjAg
QikgcHVzaC1tZXJnZWQtbG9jYWwgYW5kIDAgKDAuMCBCKSByZW1vdGUgYmxvY2tzPGJyPjI1LzAy
LzE5IDAwOjM4OjU0IElORk8gU2h1ZmZsZUJsb2NrRmV0Y2hlckl0ZXJhdG9yOiBTdGFydGVkIDAg
cmVtb3RlIGZldGNoZXMgaW4gMTUgbXM8YnI+MjUvMDIvMTkgMDA6Mzg6NTQgSU5GTyBDb2RlR2Vu
ZXJhdG9yOiBDb2RlIGdlbmVyYXRlZCBpbiA4LjYyMDc5OCBtczxicj4yNS8wMi8xOSAwMDozODo1
NCBJTkZPIEV4ZWN1dG9yOiBGaW5pc2hlZCB0YXNrIDAuMCBpbiBzdGFnZSAzLjAgKFRJRCAyKS4g
NDA4NCBieXRlcyByZXN1bHQgc2VudCB0byBkcml2ZXI8YnI+MjUvMDIvMTkgMDA6Mzg6NTQgSU5G
TyBUYXNrU2V0TWFuYWdlcjogRmluaXNoZWQgdGFzayAwLjAgaW4gc3RhZ2UgMy4wIChUSUQgMikg
aW4gOTcgbXMgb24gMTAuMjU1LjI1NS4yNTQgKGV4ZWN1dG9yIGRyaXZlcikgKDEvMSk8YnI+MjUv
MDIvMTkgMDA6Mzg6NTQgSU5GTyBUYXNrU2NoZWR1bGVySW1wbDogUmVtb3ZlZCBUYXNrU2V0IDMu
MCwgd2hvc2UgdGFza3MgaGF2ZSBhbGwgY29tcGxldGVkLCBmcm9tIHBvb2w8YnI+MjUvMDIvMTkg
MDA6Mzg6NTQgSU5GTyBEQUdTY2hlZHVsZXI6IFJlc3VsdFN0YWdlIDMgKGNvdW50IGF0IEFwcC5z
Y2FsYToxNSkgZmluaXNoZWQgaW4gMC4xMDggczxicj4yNS8wMi8xOSAwMDozODo1NCBJTkZPIERB
R1NjaGVkdWxlcjogSm9iIDIgaXMgZmluaXNoZWQuIENhbmNlbGxpbmcgcG90ZW50aWFsIHNwZWN1
bGF0aXZlIG9yIHpvbWJpZSB0YXNrcyBmb3IgdGhpcyBqb2I8YnI+MjUvMDIvMTkgMDA6Mzg6NTQg
SU5GTyBUYXNrU2NoZWR1bGVySW1wbDogS2lsbGluZyBhbGwgcnVubmluZyB0YXNrcyBpbiBzdGFn
ZSAzOiBTdGFnZSBmaW5pc2hlZDxicj4yNS8wMi8xOSAwMDozODo1NCBJTkZPIERBR1NjaGVkdWxl
cjogSm9iIDIgZmluaXNoZWQ6IGNvdW50IGF0IEFwcC5zY2FsYToxNSwgdG9vayAwLjEyMDMwOSBz
PGJyPkNvdW50OiAxMDA8YnI+MjUvMDIvMTkgMDA6Mzg6NTQgSU5GTyBTcGFya0NvbnRleHQ6IFNw
YXJrQ29udGV4dCBpcyBzdG9wcGluZyB3aXRoIGV4aXRDb2RlIDAuPGJyPmBgYDxicj48YnI+&quot;&gt;​&lt;/div&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimwayne.blogspot.com/feeds/6590890917650158576/comments/default' title='張貼留言'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/6819483672105967231/6590890917650158576' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6819483672105967231/posts/default/6590890917650158576'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6819483672105967231/posts/default/6590890917650158576'/><link rel='alternate' type='text/html' href='http://jimwayne.blogspot.com/2025/02/spark-spark-scala.html' title='Spark 新手筆記（一）：在本機安裝 Spark 並執行一個簡單的 Scala 程式'/><author><name>Wayne Yeh</name><uri>http://www.blogger.com/profile/02148905864753873930</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6819483672105967231.post-4989849313614214309</id><published>2025-02-05T01:19:00.001+08:00</published><updated>2025-02-05T01:19:29.594+08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="AI"/><category scheme="http://www.blogger.com/atom/ns#" term="Ollama"/><category scheme="http://www.blogger.com/atom/ns#" term="Windows"/><title type='text'>在 Windows 11 使用 Ollama 執行 Chat AI</title><content type='html'>&lt;div class=&quot;markdown-here-wrapper&quot; data-md-url=&quot;https://www.blogger.com/blog/post/edit/6819483672105967231/4989849313614214309&quot; markdown-here-wrapper-content-modified=&quot;true&quot;&gt;&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;在本機想要執行大型語言模型，目前常見的方法就是透過 Ollama。所以這篇稍微紀錄一下需要做哪些事。不過其實要做的很少，跟之前裝 Stable Diffusion 比起來，Ollama 非常容易 😆&lt;/p&gt;
&lt;ol style=&quot;margin: 1.2em 0px; padding-left: 2em;&quot;&gt;
&lt;li style=&quot;margin: 0.5em 0px;&quot;&gt;首先，先去 &lt;a href=&quot;https://ollama.com&quot;&gt;Ollama 官網&lt;/a&gt;下載 Ollama 安裝，這個步驟就跟一般裝 Windows 應用程式一樣，有安裝介面，所以沒什麼特殊的動作。&lt;/li&gt;
&lt;li style=&quot;margin: 0.5em 0px;&quot;&gt;如果有 nVIdia 顯卡，建議裝一下 CUDA Toolkit。&lt;/li&gt;
&lt;li style=&quot;margin: 0.5em 0px;&quot;&gt;打開 PowerShell，輸入 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;ollama -h&lt;/code&gt;，會列出 Ollama 可以用的指令。&lt;/li&gt;
&lt;/ol&gt;
&lt;pre style=&quot;font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; font-size: 1em; line-height: 1.2em; margin: 1.2em 0px;&quot;&gt;&lt;code class=&quot;hljs language-shell&quot; style=&quot;-moz-text-size-adjust: none; background-color: #f8f8f8; background: rgb(35, 36, 31); border-radius: 3px; border-radius: 3px; border: 1px solid rgb(204, 204, 204); border: 1px solid rgb(234, 234, 234); color: #f8f8f2; display: block !important; display: block; display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; overflow-x: auto; overflow: auto; padding: 0.5em 0.7em; padding: 0.5em; padding: 0px 0.3em; white-space: pre-wrap; white-space: pre;&quot;&gt;&amp;gt; ollama -h
Large language model runner

Usage:
  ollama [flags]
  ollama [command]

Available Commands:
  serve       Start ollama
  create      Create a model from a Modelfile
  show        Show information for a model
  run         Run a model
  stop        Stop a running model
  pull        Pull a model from a registry
  push        Push a model to a registry
  list        List models
  ps          List running models
  cp          Copy a model
  rm          Remove a model
  help        Help about any command

Flags:
  -h, --help      help for ollama
  -v, --version   Show version information

Use &quot;ollama [command] --help&quot; for more information about a command.
&lt;/code&gt;&lt;/pre&gt;
&lt;ol style=&quot;margin: 1.2em 0px; padding-left: 2em;&quot;&gt;
&lt;li style=&quot;margin: 0.5em 0px;&quot;&gt;在進入下一步開始正式執行之前，建議可以考慮在環境變數設定 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;OLLAMA_MODELS&lt;/code&gt;。這是因為 Ollama 預設會在使用者目錄下的 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;/.ollama&lt;/code&gt; 資料夾中放下載的 model，有些比較大的 model 甚至有幾百 GB 的，因此建議透過設定 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;OLLAMA_MODELS&lt;/code&gt; 環境變數，讓 Ollama 把 model 下載到其他非 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;C:&lt;/code&gt; 的路徑。注意設了環境變數以後，要把 Ollama 完全關閉再重啟才會生效。&lt;/li&gt;
&lt;li style=&quot;margin: 0.5em 0px;&quot;&gt;然後就可以實驗了，先拿 Llama 3.2 試試。輸入 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;ollama run llama3.2&lt;/code&gt; 就可以自動下載 Llama 3.2 的 model 並執行。&lt;/li&gt;
&lt;/ol&gt;
&lt;pre style=&quot;font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; font-size: 1em; line-height: 1.2em; margin: 1.2em 0px;&quot;&gt;&lt;code class=&quot;hljs language-shell&quot; style=&quot;-moz-text-size-adjust: none; background-color: #f8f8f8; background: rgb(35, 36, 31); border-radius: 3px; border-radius: 3px; border: 1px solid rgb(204, 204, 204); border: 1px solid rgb(234, 234, 234); color: #f8f8f2; display: block !important; display: block; display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; overflow-x: auto; overflow: auto; padding: 0.5em 0.7em; padding: 0.5em; padding: 0px 0.3em; white-space: pre-wrap; white-space: pre;&quot;&gt;&amp;gt; ollama run llama3.2
pulling manifest
pulling dde5aa3fc5ff... 100% ▕███████████████████████████████████████████████████████████████▏ 2.0 GB
pulling 966de95ca8a6... 100% ▕███████████████████████████████████████████████████████████████▏ 1.4 KB
pulling fcc5a6bec9da... 100% ▕███████████████████████████████████████████████████████████████▏ 7.7 KB
pulling a70ff7e570d9... 100% ▕███████████████████████████████████████████████████████████████▏ 6.0 KB
pulling 56bb8bd477a5... 100% ▕███████████████████████████████████████████████████████████████▏   96 B
pulling 34bb5ab01051... 100% ▕███████████████████████████████████████████████████████████████▏  561 B
verifying sha256 digest
writing manifest
success
&amp;gt;&amp;gt;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;接著就可以開始跟 Llama 對話了。之後大概就只剩要用哪個 model 之類的而已，可以參考 Ollama 的 models 頁面，看看有哪些 model 以及哪些大小可以選。&lt;/p&gt;
&lt;div style=&quot;font-size: 0em; height: 0; margin: 0; max-height: 0; max-width: 0; overflow: hidden; padding: 0; width: 0;&quot; title=&quot;MDH:aW5zdGFsbCBvbGxhbWE8YnI+ZG93bmxvYWQgbW9kZWw8YnI+aW5zdGFsbCBudmlkaWEgYXBwICZh
bXA7IGN1ZGE8YnI+PGJyPjxicj7lnKjmnKzmqZ/mg7PopoHln7fooYzlpKflnovoqp7oqIDmqKHl
novvvIznm67liY3luLjopovnmoTmlrnms5XlsLHmmK/pgI/pgY4gT2xsYW1h44CC5omA5Lul6YCZ
56+H56iN5b6u57SA6YyE5LiA5LiL6ZyA6KaB5YGa5ZOq5Lqb5LqL44CC5LiN6YGO5YW25a+m6KaB
5YGa55qE5b6I5bCR77yM6Lef5LmL5YmN6KOdIFN0YWJsZSBEaWZmdXNpb24g5q+U6LW35L6G77yM
T2xsYW1hIOmdnuW4uOWuueaYkyDtoL3tuIY8YnI+PGJyPjEuIOmmluWFiO+8jOWFiOWOuyBbT2xs
YW1hIOWumOe2sl1bb2xsYW1hXeS4i+i8iSBPbGxhbWEg5a6J6KOd77yM6YCZ5YCL5q2l6amf5bCx
6Lef5LiA6Iis6KOdIFdpbmRvd3Mg5oeJ55So56iL5byP5LiA5qij77yM5pyJ5a6J6KOd5LuL6Z2i
77yM5omA5Lul5rKS5LuA6bq854m55q6K55qE5YuV5L2c44CCPGJyPjEuIOWmguaenOaciSBuVklk
aWEg6aGv5Y2h77yM5bu66K2w6KOd5LiA5LiLIENVREEgVG9vbGtpdOOAgjxicj4xLiDmiZPplosg
UG93ZXJTaGVsbO+8jOi8uOWFpSBgb2xsYW1hIC1oYO+8jOacg+WIl+WHuiBPbGxhbWEg5Y+v5Lul
55So55qE5oyH5Luk44CCPGJyPjxicj5gYGBzaGVsbDxicj4mZ3Q7IG9sbGFtYSAtaDxicj5MYXJn
ZSBsYW5ndWFnZSBtb2RlbCBydW5uZXI8YnI+PGJyPlVzYWdlOjxicj7CoCBvbGxhbWEgW2ZsYWdz
XTxicj7CoCBvbGxhbWEgW2NvbW1hbmRdPGJyPjxicj5BdmFpbGFibGUgQ29tbWFuZHM6PGJyPsKg
IHNlcnZlwqDCoMKgwqDCoMKgIFN0YXJ0IG9sbGFtYTxicj7CoCBjcmVhdGXCoMKgwqDCoMKgIENy
ZWF0ZSBhIG1vZGVsIGZyb20gYSBNb2RlbGZpbGU8YnI+wqAgc2hvd8KgwqDCoMKgwqDCoMKgIFNo
b3cgaW5mb3JtYXRpb24gZm9yIGEgbW9kZWw8YnI+wqAgcnVuwqDCoMKgwqDCoMKgwqDCoCBSdW4g
YSBtb2RlbDxicj7CoCBzdG9wwqDCoMKgwqDCoMKgwqAgU3RvcCBhIHJ1bm5pbmcgbW9kZWw8YnI+
wqAgcHVsbMKgwqDCoMKgwqDCoMKgIFB1bGwgYSBtb2RlbCBmcm9tIGEgcmVnaXN0cnk8YnI+wqAg
cHVzaMKgwqDCoMKgwqDCoMKgIFB1c2ggYSBtb2RlbCB0byBhIHJlZ2lzdHJ5PGJyPsKgIGxpc3TC
oMKgwqDCoMKgwqDCoCBMaXN0IG1vZGVsczxicj7CoCBwc8KgwqDCoMKgwqDCoMKgwqDCoCBMaXN0
IHJ1bm5pbmcgbW9kZWxzPGJyPsKgIGNwwqDCoMKgwqDCoMKgwqDCoMKgIENvcHkgYSBtb2RlbDxi
cj7CoCBybcKgwqDCoMKgwqDCoMKgwqDCoCBSZW1vdmUgYSBtb2RlbDxicj7CoCBoZWxwwqDCoMKg
wqDCoMKgwqAgSGVscCBhYm91dCBhbnkgY29tbWFuZDxicj48YnI+RmxhZ3M6PGJyPsKgIC1oLCAt
LWhlbHDCoMKgwqDCoMKgIGhlbHAgZm9yIG9sbGFtYTxicj7CoCAtdiwgLS12ZXJzaW9uwqDCoCBT
aG93IHZlcnNpb24gaW5mb3JtYXRpb248YnI+PGJyPlVzZSAib2xsYW1hIFtjb21tYW5kXSAtLWhl
bHAiIGZvciBtb3JlIGluZm9ybWF0aW9uIGFib3V0IGEgY29tbWFuZC48YnI+YGBgPGJyPjxicj4x
LiDlnKjpgLLlhaXkuIvkuIDmraXplovlp4vmraPlvI/ln7fooYzkuYvliY3vvIzlu7rorbDlj6/k
u6XogIPmha7lnKjnkrDlooPorormlbjoqK3lrpogYE9MTEFNQV9NT0RFTFNg44CC6YCZ5piv5Zug
54K6IE9sbGFtYSDpoJDoqK3mnIPlnKjkvb/nlKjogIXnm67pjITkuIvnmoQgYC8ub2xsYW1hYCDo
s4fmlpnlpL7kuK3mlL7kuIvovInnmoQgbW9kZWzvvIzmnInkupvmr5TovIPlpKfnmoQgbW9kZWwg
55Sa6Iez5pyJ5bm+55m+IEdCIOeahO+8jOWboOatpOW7uuitsOmAj+mBjuioreWumiBgT0xMQU1B
X01PREVMU2Ag55Kw5aKD6K6K5pW477yM6K6TIE9sbGFtYSDmioogbW9kZWwg5LiL6LyJ5Yiw5YW2
5LuW6Z2eIGBDOmAg55qE6Lev5b6R44CC5rOo5oSP6Kit5LqG55Kw5aKD6K6K5pW45Lul5b6M77yM
6KaB5oqKIE9sbGFtYSDlrozlhajpl5zplonlho3ph43llZ/miY3mnIPnlJ/mlYjjgII8YnI+MS4g
54S25b6M5bCx5Y+v5Lul5a+m6amX5LqG77yM5YWI5ou/IExsYW1hIDMuMiDoqaboqabjgILovLjl
haUgYG9sbGFtYSBydW4gbGxhbWEzLjJgIOWwseWPr+S7peiHquWLleS4i+i8iSBMbGFtYSAzLjIg
55qEIG1vZGVsIOS4puWft+ihjOOAgjxicj48YnI+YGBgc2hlbGw8YnI+Jmd0OyBvbGxhbWEgcnVu
IGxsYW1hMy4yPGJyPnB1bGxpbmcgbWFuaWZlc3Q8YnI+cHVsbGluZyBkZGU1YWEzZmM1ZmYuLi4g
MTAwJSDilpXilojilojilojilojilojilojilojilojilojilojilojilojilojilojilojiloji
lojilojilojilojilojilojilojilojilojilojilojilojilojilojilojilojilojilojiloji
lojilojilojilojilojilojilojilojilojilojilojilojilojilojilojilojilojilojiloji
lojilojilojilojilojilojilojilojilojilo8gMi4wIEdCPGJyPnB1bGxpbmcgOTY2ZGU5NWNh
OGE2Li4uIDEwMCUg4paV4paI4paI4paI4paI4paI4paI4paI4paI4paI4paI4paI4paI4paI4paI
4paI4paI4paI4paI4paI4paI4paI4paI4paI4paI4paI4paI4paI4paI4paI4paI4paI4paI4paI
4paI4paI4paI4paI4paI4paI4paI4paI4paI4paI4paI4paI4paI4paI4paI4paI4paI4paI4paI
4paI4paI4paI4paI4paI4paI4paI4paI4paI4paI4paI4paPIDEuNCBLQjxicj5wdWxsaW5nIGZj
YzVhNmJlYzlkYS4uLiAxMDAlIOKWleKWiOKWiOKWiOKWiOKWiOKWiOKWiOKWiOKWiOKWiOKWiOKW
iOKWiOKWiOKWiOKWiOKWiOKWiOKWiOKWiOKWiOKWiOKWiOKWiOKWiOKWiOKWiOKWiOKWiOKWiOKW
iOKWiOKWiOKWiOKWiOKWiOKWiOKWiOKWiOKWiOKWiOKWiOKWiOKWiOKWiOKWiOKWiOKWiOKWiOKW
iOKWiOKWiOKWiOKWiOKWiOKWiOKWiOKWiOKWiOKWiOKWiOKWiOKWiOKWjyA3LjcgS0I8YnI+cHVs
bGluZyBhNzBmZjdlNTcwZDkuLi4gMTAwJSDilpXilojilojilojilojilojilojilojilojiloji
lojilojilojilojilojilojilojilojilojilojilojilojilojilojilojilojilojilojiloji
lojilojilojilojilojilojilojilojilojilojilojilojilojilojilojilojilojilojiloji
lojilojilojilojilojilojilojilojilojilojilojilojilojilojilojilojilo8gNi4wIEtC
PGJyPnB1bGxpbmcgNTZiYjhiZDQ3N2E1Li4uIDEwMCUg4paV4paI4paI4paI4paI4paI4paI4paI
4paI4paI4paI4paI4paI4paI4paI4paI4paI4paI4paI4paI4paI4paI4paI4paI4paI4paI4paI
4paI4paI4paI4paI4paI4paI4paI4paI4paI4paI4paI4paI4paI4paI4paI4paI4paI4paI4paI
4paI4paI4paI4paI4paI4paI4paI4paI4paI4paI4paI4paI4paI4paI4paI4paI4paI4paI4paP
wqDCoCA5NiBCPGJyPnB1bGxpbmcgMzRiYjVhYjAxMDUxLi4uIDEwMCUg4paV4paI4paI4paI4paI
4paI4paI4paI4paI4paI4paI4paI4paI4paI4paI4paI4paI4paI4paI4paI4paI4paI4paI4paI
4paI4paI4paI4paI4paI4paI4paI4paI4paI4paI4paI4paI4paI4paI4paI4paI4paI4paI4paI
4paI4paI4paI4paI4paI4paI4paI4paI4paI4paI4paI4paI4paI4paI4paI4paI4paI4paI4paI
4paI4paI4paPwqAgNTYxIEI8YnI+dmVyaWZ5aW5nIHNoYTI1NiBkaWdlc3Q8YnI+d3JpdGluZyBt
YW5pZmVzdDxicj5zdWNjZXNzPGJyPiZndDsmZ3Q7Jmd0Ozxicj5gYGAgPGJyPjxicj7mjqXokZfl
sLHlj6/ku6Xplovlp4vot58gTGxhbWEg5bCN6Kmx5LqG44CC5LmL5b6M5aSn5qaC5bCx5Y+q5Ymp
6KaB55So5ZOq5YCLIG1vZGVsIOS5i+mhnueahOiAjOW3su+8jOWPr+S7peWPg+iAgyBPbGxhbWEg
55qEIG1vZGVscyDpoIHpnaLvvIznnIvnnIvmnInlk6rkupsgbW9kZWwg5Lul5Y+K5ZOq5Lqb5aSn
5bCP5Y+v5Lul6YG444CCPGJyPjxicj5bb2xsYW1hXTogaHR0cHM6Ly9vbGxhbWEuY29tPGJyPltv
bGxhbWEtbW9kZWxzXTogaHR0cHM6Ly9vbGxhbWEuY29tL3NlYXJjaDxicj5bSG93LXRvLXJ1bi1P
bGxhbWEtT3Blbi1XZWJVSS1vbi1XaW5kb3dzXTogaHR0cHM6Ly93d3cuajAwMGUuY29tL0FJL0hv
dy10by1ydW4tT2xsYW1hLU9wZW4tV2ViVUktb24tV2luZG93cy5odG1s&quot;&gt;​&lt;/div&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimwayne.blogspot.com/feeds/4989849313614214309/comments/default' title='張貼留言'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/6819483672105967231/4989849313614214309' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6819483672105967231/posts/default/4989849313614214309'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6819483672105967231/posts/default/4989849313614214309'/><link rel='alternate' type='text/html' href='http://jimwayne.blogspot.com/2025/02/windows-11-ollama-chat-ai.html' title='在 Windows 11 使用 Ollama 執行 Chat AI'/><author><name>Wayne Yeh</name><uri>http://www.blogger.com/profile/02148905864753873930</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6819483672105967231.post-4225436626764499954</id><published>2024-12-25T01:07:00.002+08:00</published><updated>2024-12-25T01:07:21.708+08:00</updated><title type='text'>[筆記] Fluentd log driver 的 fluent-buffer-limit 定義</title><content type='html'>&lt;p&gt;在過去，fluent-buffer-limit 的定義是用 byte 來指定的，但在 docker 19.0.3 以後，同樣的參數，定義變成 log 行數了。所以這有可能導致 docker log driver 使用了遠比預期還多的記憶體。&lt;/p&gt;&lt;p&gt;https://github.com/moby/moby/issues/41488&lt;br /&gt;&lt;/p&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimwayne.blogspot.com/feeds/4225436626764499954/comments/default' title='張貼留言'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/6819483672105967231/4225436626764499954' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6819483672105967231/posts/default/4225436626764499954'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6819483672105967231/posts/default/4225436626764499954'/><link rel='alternate' type='text/html' href='http://jimwayne.blogspot.com/2024/12/fluentd-log-driver-fluent-buffer-limit.html' title='[筆記] Fluentd log driver 的 fluent-buffer-limit 定義'/><author><name>Wayne Yeh</name><uri>http://www.blogger.com/profile/02148905864753873930</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6819483672105967231.post-1214688813294648307</id><published>2024-11-26T23:45:00.007+08:00</published><updated>2024-11-26T23:47:20.740+08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="Apache HttpClient"/><category scheme="http://www.blogger.com/atom/ns#" term="Java"/><category scheme="http://www.blogger.com/atom/ns#" term="Markdown Here"/><title type='text'>[筆記] Apache HTTP Client 5 的注意事項</title><content type='html'>&lt;div class=&quot;markdown-here-wrapper&quot; data-md-url=&quot;https://www.blogger.com/blog/post/edit/6819483672105967231/1214688813294648307&quot;&gt;&lt;ul style=&quot;margin: 1.2em 0px; padding-left: 2em;&quot;&gt;
&lt;li style=&quot;margin: 0.5em 0px;&quot;&gt;要控制 timeout，應該使用的是 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;RequestConfig&lt;/code&gt; 裡的 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;connectTimeout&lt;/code&gt;，而不是 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;responseTimeout&lt;/code&gt;。&lt;/li&gt;
&lt;li style=&quot;margin: 0.5em 0px;&quot;&gt;&lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;responseTimeout&lt;/code&gt; 從原始碼來看，跟 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;socketTimeout&lt;/code&gt; 是差不多的東西。&lt;/li&gt;
&lt;li style=&quot;margin: 0.5em 0px;&quot;&gt;&lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;socketTimeout&lt;/code&gt; 代表的意思是距離上一次 network I/O 的時間差。也就是說，如果設定 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;socketTimeout&lt;/code&gt; 為 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;n&lt;/code&gt;ms，則每 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;n-1&lt;/code&gt; ms 收到一個封包，也是不會觸發 timeout 的。&lt;/li&gt;
&lt;/ul&gt;
&lt;div style=&quot;font-size: 0em; height: 0; margin: 0; max-height: 0; max-width: 0; overflow: hidden; padding: 0; width: 0;&quot; title=&quot;MDH:KiDopoHmjqfliLYgdGltZW91dO+8jOaHieipsuS9v+eUqOeahOaYryBgUmVxdWVzdENvbmZpZ2Ag
6KOh55qEIGBjb25uZWN0VGltZW91dGDvvIzogIzkuI3mmK8gYHJlc3BvbnNlVGltZW91dGDjgII8
YnI+KiBgcmVzcG9uc2VUaW1lb3V0YCDlvp7ljp/lp4vnorzkvobnnIvvvIzot58gYHNvY2tldFRp
bWVvdXRgIOaYr+W3ruS4jeWkmueahOadseilv+OAgjxicj4qIGBzb2NrZXRUaW1lb3V0YCDku6Po
oajnmoTmhI/mgJ3mmK/ot53pm6LkuIrkuIDmrKEgbmV0d29yayBJL08g55qE5pmC6ZaT5beu44CC
5Lmf5bCx5piv6Kqq77yM5aaC5p6c6Kit5a6aIGBzb2NrZXRUaW1lb3V0YCDngrogYG5gbXPvvIzl
iYfmr48gYG4tMWAgbXMg5pS25Yiw5LiA5YCL5bCB5YyF77yM5Lmf5piv5LiN5pyD6Ke455m8IHRp
bWVvdXQg55qE44CCPGJyPg==&quot;&gt;​&lt;/div&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimwayne.blogspot.com/feeds/1214688813294648307/comments/default' title='張貼留言'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/6819483672105967231/1214688813294648307' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6819483672105967231/posts/default/1214688813294648307'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6819483672105967231/posts/default/1214688813294648307'/><link rel='alternate' type='text/html' href='http://jimwayne.blogspot.com/2024/11/apache-http-client-5.html' title='[筆記] Apache HTTP Client 5 的注意事項'/><author><name>Wayne Yeh</name><uri>http://www.blogger.com/profile/02148905864753873930</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6819483672105967231.post-6580879817111945891</id><published>2024-07-12T22:36:00.002+08:00</published><updated>2024-07-12T22:38:10.380+08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="AWS"/><title type='text'>[筆記] AWS ECS 問題排查筆記</title><content type='html'>&lt;p&gt;快速筆記，最近花了非常多時間在研究為什麼 ECS 底下的 EC2 作業系統從 CentOS 7 升級成 Amazon Linux 2023 後，上頭的 ECS container 出現一堆問題。&lt;/p&gt;&lt;p&gt;主要遇到的問題似乎是 I/O 的緣故。EBS I/O 超過部署時設定的 throughput 的話，會導致 EC2 變得很不穩定，例如無法 SSH 進入 EC2、或者 EC2 上的 ECS agent 會斷線等等。這個問題可能會衍生出 EC2 上的 ECS container 無法正常寫檔案，要寫入檔案的內容可能會一直被 cache 在 dirty page 上，導致 container 的記憶體用量超出預期。&lt;/p&gt;&lt;p&gt;至於為什麼 EBS I/O 的用量會超出部署的設定，這點暫時還沒有結論，不過 architect 有發現 Amazon Linux 2023 寫 system journal 寫蠻多的，相較來說，CentOS 幾乎不寫 system journal。&lt;br /&gt;&lt;/p&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimwayne.blogspot.com/feeds/6580879817111945891/comments/default' title='張貼留言'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/6819483672105967231/6580879817111945891' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6819483672105967231/posts/default/6580879817111945891'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6819483672105967231/posts/default/6580879817111945891'/><link rel='alternate' type='text/html' href='http://jimwayne.blogspot.com/2024/07/aws-ecs.html' title='[筆記] AWS ECS 問題排查筆記'/><author><name>Wayne Yeh</name><uri>http://www.blogger.com/profile/02148905864753873930</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6819483672105967231.post-6646824919076133770</id><published>2024-06-15T21:54:00.006+08:00</published><updated>2024-06-15T22:00:45.467+08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="Java"/><category scheme="http://www.blogger.com/atom/ns#" term="Markdown Here"/><category scheme="http://www.blogger.com/atom/ns#" term="Spring"/><category scheme="http://www.blogger.com/atom/ns#" term="Spring Boot"/><category scheme="http://www.blogger.com/atom/ns#" term="Spring Cache"/><title type='text'>Spring Cache + AspectJ 背後的大致流程</title><content type='html'>&lt;div class=&quot;markdown-here-wrapper&quot; data-md-url=&quot;https://draft.blogger.com/blog/post/edit/6819483672105967231/6646824919076133770&quot;&gt;&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;因為最近在專案中整合 Spring Cache，不過原生的 Spring Cache 支援的功能有點基本，我們想要擴充它的功能，所以需要稍微了解一下 Spring Cache 背後到底怎麼運作的，我們才有辦法找出比較合適的擴充方式。&lt;/p&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;網路上可以找到一些中國網友的原始碼解析，不過大多數找到的都是用預設的 Spring AOP，但我們專案的狀況會使用 AspectJ，所以這邊會紀錄 AspectJ 的狀況是如何運作的。&lt;/p&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;另外，因為我們還在用 Spring Boot 2.7，所以這裡會先關注在 Spring v5.3.31 版的原始碼。相關的 Spring Cache 官方文件可以參考&lt;a href=&quot;https://docs.spring.io/spring-framework/docs/5.3.31/reference/html/integration.html#cache&quot;&gt;這裡&lt;/a&gt;。&lt;/p&gt;
&lt;h1 id=&quot;spring-cache-&quot; style=&quot;border-bottom: 1px solid rgb(221, 221, 221); font-size: 1.6em; font-weight: bold; margin: 1.3em 0px 1em; padding: 0px;&quot;&gt;Spring Cache 的使用方式&lt;/h1&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;那麼要從哪裡開始追蹤呢？先來看一下 Spring Cache 怎麼使用的。&lt;/p&gt;
&lt;pre style=&quot;font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; font-size: 1em; line-height: 1.2em; margin: 1.2em 0px;&quot;&gt;&lt;code class=&quot;hljs language-java&quot; style=&quot;background-color: #f8f8f8; background: rgb(35, 36, 31); border-radius: 3px; border-radius: 3px; border: 1px solid rgb(204, 204, 204); border: 1px solid rgb(234, 234, 234); color: #f8f8f2; display: block !important; display: block; display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; overflow-x: auto; overflow: auto; padding: 0.5em 0.7em; padding: 0.5em; padding: 0px 0.3em; text-size-adjust: none; white-space: pre-wrap; white-space: pre;&quot;&gt;&lt;span class=&quot;hljs-annotation&quot; style=&quot;color: #75715e;&quot;&gt;@Configuration&lt;/span&gt;
&lt;span class=&quot;hljs-annotation&quot; style=&quot;color: #75715e;&quot;&gt;@EnableCaching&lt;/span&gt;
&lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #f92672;&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-class&quot;&gt;&lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #66d9ef; color: #f92672;&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;hljs-title&quot; style=&quot;color: #a6e22e; color: #f8f8f2; font-style: italic;&quot;&gt;AppConfig&lt;/span&gt; &lt;/span&gt;{
}

&lt;span class=&quot;hljs-annotation&quot; style=&quot;color: #75715e;&quot;&gt;@Cacheable&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot; style=&quot;color: #e6db74;&quot;&gt;&quot;books&quot;&lt;/span&gt;)
&lt;span class=&quot;hljs-function&quot; style=&quot;color: #f92672;&quot;&gt;&lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #66d9ef; color: #f92672;&quot;&gt;public&lt;/span&gt; Book &lt;span class=&quot;hljs-title&quot; style=&quot;color: #a6e22e;&quot;&gt;findBook&lt;/span&gt;&lt;span class=&quot;hljs-params&quot; style=&quot;color: #f8f8f2;&quot;&gt;(ISBN isbn)&lt;/span&gt; &lt;/span&gt;{...}
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;Spring Cache 最基本的使用方式，就是像上面的例子那樣，用 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;@Cacheable&lt;/code&gt; 註解來告訴 Spring 說 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;findBook(...)&lt;/code&gt; 這個 method 要被 cache，並且需要提供一個 configuration，上面有 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;@EnableCaching&lt;/code&gt; 來要求要啟用 cache。這裡其實還需要一點其他的設定，不過因為不是這裡的重點，所以我都先略過 😆。總之，起點就是 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;@EnableCaching&lt;/code&gt; 了。&lt;/p&gt;
&lt;h1 id=&quot;cache-&quot; style=&quot;border-bottom: 1px solid rgb(221, 221, 221); font-size: 1.6em; font-weight: bold; margin: 1.3em 0px 1em; padding: 0px;&quot;&gt;Cache 的載入流程&lt;/h1&gt;
&lt;h2 id=&quot;-configuration&quot; style=&quot;border-bottom: 1px solid rgb(238, 238, 238); font-size: 1.4em; font-weight: bold; margin: 1.3em 0px 1em; padding: 0px;&quot;&gt;載入 Configuration&lt;/h2&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;首先從 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;@EnableCaching&lt;/code&gt; 這個註解開始看，它的內容大概是這樣：&lt;/p&gt;
&lt;pre style=&quot;font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; font-size: 1em; line-height: 1.2em; margin: 1.2em 0px;&quot;&gt;&lt;code class=&quot;hljs language-java&quot; style=&quot;background-color: #f8f8f8; background: rgb(35, 36, 31); border-radius: 3px; border-radius: 3px; border: 1px solid rgb(204, 204, 204); border: 1px solid rgb(234, 234, 234); color: #f8f8f2; display: block !important; display: block; display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; overflow-x: auto; overflow: auto; padding: 0.5em 0.7em; padding: 0.5em; padding: 0px 0.3em; text-size-adjust: none; white-space: pre-wrap; white-space: pre;&quot;&gt;&lt;span class=&quot;hljs-annotation&quot; style=&quot;color: #75715e;&quot;&gt;@Target&lt;/span&gt;(ElementType.TYPE)
&lt;span class=&quot;hljs-annotation&quot; style=&quot;color: #75715e;&quot;&gt;@Retention&lt;/span&gt;(RetentionPolicy.RUNTIME)
&lt;span class=&quot;hljs-annotation&quot; style=&quot;color: #75715e;&quot;&gt;@Documented&lt;/span&gt;
&lt;span class=&quot;hljs-annotation&quot; style=&quot;color: #75715e;&quot;&gt;@Import&lt;/span&gt;(CachingConfigurationSelector.class)
&lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #f92672;&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-annotation&quot; style=&quot;color: #75715e;&quot;&gt;@interface&lt;/span&gt; EnableCaching {
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;可以注意到，它宣告了 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;@Import&lt;/code&gt;，因此 Spring 會使用它指定的 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;CachingConfigurationSelector&lt;/code&gt; 來得知到載入什麼東西。&lt;/p&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;&lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;CachingConfigurationSelector&lt;/code&gt; (&lt;a href=&quot;https://github.com/spring-projects/spring-framework/blob/v5.3.31/spring-context/src/main/java/org/springframework/cache/annotation/CachingConfigurationSelector.java&quot;&gt;git&lt;/a&gt;) 是一個透過 parent 實作了 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;ImportSelector&lt;/code&gt; 的類別，因此它主要提供 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;selectImports&lt;/code&gt; 這個方法，用來告訴 Spring 說要載入的 class 的 FQDN 是什麼。比較重要的內容如下：&lt;/p&gt;
&lt;pre style=&quot;font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; font-size: 1em; line-height: 1.2em; margin: 1.2em 0px;&quot;&gt;&lt;code class=&quot;hljs language-java&quot; style=&quot;background-color: #f8f8f8; background: rgb(35, 36, 31); border-radius: 3px; border-radius: 3px; border: 1px solid rgb(204, 204, 204); border: 1px solid rgb(234, 234, 234); color: #f8f8f2; display: block !important; display: block; display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; overflow-x: auto; overflow: auto; padding: 0.5em 0.7em; padding: 0.5em; padding: 0px 0.3em; text-size-adjust: none; white-space: pre-wrap; white-space: pre;&quot;&gt;&lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #f92672;&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-class&quot;&gt;&lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #66d9ef; color: #f92672;&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;hljs-title&quot; style=&quot;color: #a6e22e; color: #f8f8f2;&quot;&gt;CachingConfigurationSelector&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #f92672;&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;hljs-title&quot; style=&quot;color: #a6e22e; color: #f8f8f2;&quot;&gt;AdviceModeImportSelector&lt;/span&gt;&amp;lt;&lt;span class=&quot;hljs-title&quot; style=&quot;color: #a6e22e; color: #f8f8f2; font-style: italic;&quot;&gt;EnableCaching&lt;/span&gt;&amp;gt; &lt;/span&gt;{
  ...
  &lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #f92672;&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #f92672;&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #f92672;&quot;&gt;final&lt;/span&gt; String CACHE_ASPECT_CONFIGURATION_CLASS_NAME =
            &lt;span class=&quot;hljs-string&quot; style=&quot;color: #e6db74;&quot;&gt;&quot;org.springframework.cache.aspectj.AspectJCachingConfiguration&quot;&lt;/span&gt;;
  ...

  &lt;span class=&quot;hljs-annotation&quot; style=&quot;color: #75715e;&quot;&gt;@Override&lt;/span&gt;
  &lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #f92672;&quot;&gt;public&lt;/span&gt; String[] selectImports(AdviceMode adviceMode) {
    &lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #f92672;&quot;&gt;switch&lt;/span&gt; (adviceMode) {
      &lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #f92672;&quot;&gt;case&lt;/span&gt; PROXY:
        &lt;span class=&quot;hljs-function&quot; style=&quot;color: #f92672;&quot;&gt;&lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #66d9ef; color: #f92672;&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;hljs-title&quot; style=&quot;color: #a6e22e;&quot;&gt;getProxyImports&lt;/span&gt;&lt;span class=&quot;hljs-params&quot; style=&quot;color: #f8f8f2;&quot;&gt;()&lt;/span&gt;&lt;/span&gt;;
      &lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #f92672;&quot;&gt;case&lt;/span&gt; ASPECTJ:
        &lt;span class=&quot;hljs-function&quot; style=&quot;color: #f92672;&quot;&gt;&lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #66d9ef; color: #f92672;&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;hljs-title&quot; style=&quot;color: #a6e22e;&quot;&gt;getAspectJImports&lt;/span&gt;&lt;span class=&quot;hljs-params&quot; style=&quot;color: #f8f8f2;&quot;&gt;()&lt;/span&gt;&lt;/span&gt;;
      &lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #f92672;&quot;&gt;default&lt;/span&gt;:
        &lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #f92672;&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #f92672;&quot;&gt;null&lt;/span&gt;;
    }
  }

  ...
  &lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #f92672;&quot;&gt;private&lt;/span&gt; String[] getAspectJImports() {
    List&amp;lt;String&amp;gt; result = &lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #f92672;&quot;&gt;new&lt;/span&gt; ArrayList&amp;lt;&amp;gt;(&lt;span class=&quot;hljs-number&quot; style=&quot;color: #ae81ff;&quot;&gt;2&lt;/span&gt;);
    result.add(CACHE_ASPECT_CONFIGURATION_CLASS_NAME);
    &lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #f92672;&quot;&gt;if&lt;/span&gt; (jsr107Present &amp;amp;&amp;amp; jcacheImplPresent) {
      result.add(JCACHE_ASPECT_CONFIGURATION_CLASS_NAME);
    }
    &lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #f92672;&quot;&gt;return&lt;/span&gt; StringUtils.toStringArray(result);
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;在 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;selectImports()&lt;/code&gt; 中，會依據註解設定的 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;adviceMode&lt;/code&gt; 決定 import 時要給哪個 class name，因為前面提到我們要用的是 AspectJ，並且我們並沒有要用 JSR-107，所以這裡就只看純 AspectJ 的狀況。所以可以看出，這裡如果 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;adviseMode&lt;/code&gt; 設定為 AspectJ 的話，它會回覆的 FQDN 是 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;org.springframework.cache.aspectj.AspectJCachingConfiguration&lt;/code&gt;。&lt;/p&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;於是接著來看一下 AspectJ 的 Configuration (&lt;a href=&quot;https://github.com/spring-projects/spring-framework/blob/v5.3.31/spring-aspects/src/main/java/org/springframework/cache/aspectj/AspectJCachingConfiguration.java&quot;&gt;git&lt;/a&gt;) 寫了什麼～。&lt;/p&gt;
&lt;pre style=&quot;font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; font-size: 1em; line-height: 1.2em; margin: 1.2em 0px;&quot;&gt;&lt;code class=&quot;hljs language-java&quot; style=&quot;background-color: #f8f8f8; background: rgb(35, 36, 31); border-radius: 3px; border-radius: 3px; border: 1px solid rgb(204, 204, 204); border: 1px solid rgb(234, 234, 234); color: #f8f8f2; display: block !important; display: block; display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; overflow-x: auto; overflow: auto; padding: 0.5em 0.7em; padding: 0.5em; padding: 0px 0.3em; text-size-adjust: none; white-space: pre-wrap; white-space: pre;&quot;&gt;&lt;span class=&quot;hljs-annotation&quot; style=&quot;color: #75715e;&quot;&gt;@Configuration&lt;/span&gt;(proxyBeanMethods = &lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #f92672;&quot;&gt;false&lt;/span&gt;)
&lt;span class=&quot;hljs-annotation&quot; style=&quot;color: #75715e;&quot;&gt;@Role&lt;/span&gt;(BeanDefinition.ROLE_INFRASTRUCTURE)
&lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #f92672;&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-class&quot;&gt;&lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #66d9ef; color: #f92672;&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;hljs-title&quot; style=&quot;color: #a6e22e; color: #f8f8f2;&quot;&gt;AspectJCachingConfiguration&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #f92672;&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;hljs-title&quot; style=&quot;color: #a6e22e; color: #f8f8f2; font-style: italic;&quot;&gt;AbstractCachingConfiguration&lt;/span&gt; &lt;/span&gt;{

  &lt;span class=&quot;hljs-annotation&quot; style=&quot;color: #75715e;&quot;&gt;@Bean&lt;/span&gt;(name = CacheManagementConfigUtils.CACHE_ASPECT_BEAN_NAME)
  &lt;span class=&quot;hljs-annotation&quot; style=&quot;color: #75715e;&quot;&gt;@Role&lt;/span&gt;(BeanDefinition.ROLE_INFRASTRUCTURE)
  &lt;span class=&quot;hljs-function&quot; style=&quot;color: #f92672;&quot;&gt;&lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #66d9ef; color: #f92672;&quot;&gt;public&lt;/span&gt; AnnotationCacheAspect &lt;span class=&quot;hljs-title&quot; style=&quot;color: #a6e22e;&quot;&gt;cacheAspect&lt;/span&gt;&lt;span class=&quot;hljs-params&quot; style=&quot;color: #f8f8f2;&quot;&gt;()&lt;/span&gt; &lt;/span&gt;{
    AnnotationCacheAspect cacheAspect = AnnotationCacheAspect.aspectOf();
    cacheAspect.configure(&lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #f92672;&quot;&gt;this&lt;/span&gt;.errorHandler, &lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #f92672;&quot;&gt;this&lt;/span&gt;.keyGenerator, &lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #f92672;&quot;&gt;this&lt;/span&gt;.cacheResolver, &lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #f92672;&quot;&gt;this&lt;/span&gt;.cacheManager);
    &lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #f92672;&quot;&gt;return&lt;/span&gt; cacheAspect;
  }

}
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;這裡最主要就是提供一個 Bean，這個 Bean 是用來建立 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;AnnotationCacheAspect&lt;/code&gt; 的，並且在建立的同時會把註解上的設定、以及其他需要建立的例如 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;ErrorHandler&lt;/code&gt;、&lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;KeyGenerator&lt;/code&gt;、&lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;CacheResolver&lt;/code&gt;、&lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;CacheManager&lt;/code&gt; 等東西都一起注入進去。&lt;/p&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;所以到這裡稍微總結一下，這整段代表的意思，是當 Spring 讀到 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;@EnableCaching&lt;/code&gt; 註解時，會基於這個 Selector 決定去載入指定的 Configuration，然後就會依據 Configuration 的內容去建立包含 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;CacheManager&lt;/code&gt; 等等的 Bean，並且初始化一個 Aspect，把那些 Aspect 需要用到的 Bean 注入到 Aspect 當中。&lt;/p&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;Aspect 被初始化以後，後面的主要工作應該就是 AspectJ 的範疇了。不過因為我目前還沒有很完整地看過 AspectJ 的文件，所以只有大略知道一些資訊，如果紀錄有誤歡迎提醒 😆。&lt;/p&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;AspectJ 在縫合的時候，會讓 Aspect 擁有 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;aspectOf()&lt;/code&gt; 的 method (&lt;a href=&quot;https://eclipse.dev/aspectj/doc/released/adk15notebook/ataspectj-aspectof.html&quot;&gt;doc&lt;/a&gt;)，所以 Spring 才有辦法呼叫 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;aspectOf()&lt;/code&gt; 來取得這個 Aspect，並且對它做設定。&lt;/p&gt;
&lt;h1 id=&quot;-aspect&quot; style=&quot;border-bottom: 1px solid rgb(221, 221, 221); font-size: 1.6em; font-weight: bold; margin: 1.3em 0px 1em; padding: 0px;&quot;&gt;載入並註冊 Aspect&lt;/h1&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;前段被建立的 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;AnnotationCacheAspect&lt;/code&gt; (&lt;a href=&quot;https://github.com/spring-projects/spring-framework/blob/v5.3.31/spring-aspects/src/main/java/org/springframework/cache/aspectj/AnnotationCacheAspect.aj&quot;&gt;git&lt;/a&gt;) 是一個 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;.aj&lt;/code&gt; 檔，所以內容基本上就是 AspectJ 的定義了：&lt;/p&gt;
&lt;pre style=&quot;font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; font-size: 1em; line-height: 1.2em; margin: 1.2em 0px;&quot;&gt;&lt;code class=&quot;hljs language-java&quot; style=&quot;background-color: #f8f8f8; background: rgb(35, 36, 31); border-radius: 3px; border-radius: 3px; border: 1px solid rgb(204, 204, 204); border: 1px solid rgb(234, 234, 234); color: #f8f8f2; display: block !important; display: block; display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; overflow-x: auto; overflow: auto; padding: 0.5em 0.7em; padding: 0.5em; padding: 0px 0.3em; text-size-adjust: none; white-space: pre-wrap; white-space: pre;&quot;&gt;&lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #f92672;&quot;&gt;public&lt;/span&gt; aspect AnnotationCacheAspect extends AbstractCacheAspect {

  &lt;span class=&quot;hljs-function&quot; style=&quot;color: #f92672;&quot;&gt;&lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #66d9ef; color: #f92672;&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-title&quot; style=&quot;color: #a6e22e;&quot;&gt;AnnotationCacheAspect&lt;/span&gt;&lt;span class=&quot;hljs-params&quot; style=&quot;color: #f8f8f2;&quot;&gt;()&lt;/span&gt; &lt;/span&gt;{
    &lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #f92672;&quot;&gt;super&lt;/span&gt;(&lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #f92672;&quot;&gt;new&lt;/span&gt; AnnotationCacheOperationSource(&lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #f92672;&quot;&gt;false&lt;/span&gt;));
  }

  &lt;span class=&quot;hljs-javadoc&quot; style=&quot;color: #75715e;&quot;&gt;/**
   * Matches the execution of any public method in a type with the @{@link Cacheable}
   * annotation, or any subtype of a type with the {@code&lt;span class=&quot;hljs-javadoctag&quot;&gt; @Cacheable&lt;/span&gt;} annotation.
   */&lt;/span&gt;
  &lt;span class=&quot;hljs-function&quot; style=&quot;color: #f92672;&quot;&gt;&lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #66d9ef; color: #f92672;&quot;&gt;private&lt;/span&gt; pointcut &lt;span class=&quot;hljs-title&quot; style=&quot;color: #a6e22e;&quot;&gt;executionOfAnyPublicMethodInAtCacheableType&lt;/span&gt;&lt;span class=&quot;hljs-params&quot; style=&quot;color: #f8f8f2;&quot;&gt;()&lt;/span&gt; :
      &lt;span class=&quot;hljs-title&quot; style=&quot;color: #a6e22e;&quot;&gt;execution&lt;/span&gt;&lt;span class=&quot;hljs-params&quot; style=&quot;color: #f8f8f2;&quot;&gt;(&lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #66d9ef; color: #f92672;&quot;&gt;public&lt;/span&gt; * ((@Cacheable *)&lt;/span&gt;+).*&lt;span class=&quot;hljs-params&quot; style=&quot;color: #f8f8f2;&quot;&gt;(..)&lt;/span&gt;) &amp;amp;&amp;amp; &lt;span class=&quot;hljs-title&quot; style=&quot;color: #a6e22e;&quot;&gt;within&lt;/span&gt;&lt;span class=&quot;hljs-params&quot; style=&quot;color: #f8f8f2;&quot;&gt;(@Cacheable *)&lt;/span&gt;&lt;/span&gt;;

  ...

  &lt;span class=&quot;hljs-javadoc&quot; style=&quot;color: #75715e;&quot;&gt;/**
   * Definition of pointcut from super aspect - matched join points will have Spring
   * cache management applied.
   */&lt;/span&gt;
  &lt;span class=&quot;hljs-function&quot; style=&quot;color: #f92672;&quot;&gt;&lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #66d9ef; color: #f92672;&quot;&gt;protected&lt;/span&gt; pointcut &lt;span class=&quot;hljs-title&quot; style=&quot;color: #a6e22e;&quot;&gt;cacheMethodExecution&lt;/span&gt;&lt;span class=&quot;hljs-params&quot; style=&quot;color: #f8f8f2;&quot;&gt;(Object cachedObject)&lt;/span&gt; :
    &lt;span class=&quot;hljs-params&quot; style=&quot;color: #f8f8f2;&quot;&gt;(executionOfAnyPublicMethodInAtCacheableType()&lt;/span&gt;
        || &lt;span class=&quot;hljs-title&quot; style=&quot;color: #a6e22e;&quot;&gt;executionOfAnyPublicMethodInAtCacheEvictType&lt;/span&gt;&lt;span class=&quot;hljs-params&quot; style=&quot;color: #f8f8f2;&quot;&gt;()&lt;/span&gt;
        || &lt;span class=&quot;hljs-title&quot; style=&quot;color: #a6e22e;&quot;&gt;executionOfAnyPublicMethodInAtCachePutType&lt;/span&gt;&lt;span class=&quot;hljs-params&quot; style=&quot;color: #f8f8f2;&quot;&gt;()&lt;/span&gt;
        || &lt;span class=&quot;hljs-title&quot; style=&quot;color: #a6e22e;&quot;&gt;executionOfAnyPublicMethodInAtCachingType&lt;/span&gt;&lt;span class=&quot;hljs-params&quot; style=&quot;color: #f8f8f2;&quot;&gt;()&lt;/span&gt;
        || &lt;span class=&quot;hljs-title&quot; style=&quot;color: #a6e22e;&quot;&gt;executionOfCacheableMethod&lt;/span&gt;&lt;span class=&quot;hljs-params&quot; style=&quot;color: #f8f8f2;&quot;&gt;()&lt;/span&gt;
        || &lt;span class=&quot;hljs-title&quot; style=&quot;color: #a6e22e;&quot;&gt;executionOfCacheEvictMethod&lt;/span&gt;&lt;span class=&quot;hljs-params&quot; style=&quot;color: #f8f8f2;&quot;&gt;()&lt;/span&gt;
        || &lt;span class=&quot;hljs-title&quot; style=&quot;color: #a6e22e;&quot;&gt;executionOfCachePutMethod&lt;/span&gt;&lt;span class=&quot;hljs-params&quot; style=&quot;color: #f8f8f2;&quot;&gt;()&lt;/span&gt;
        || &lt;span class=&quot;hljs-title&quot; style=&quot;color: #a6e22e;&quot;&gt;executionOfCachingMethod&lt;/span&gt;&lt;span class=&quot;hljs-params&quot; style=&quot;color: #f8f8f2;&quot;&gt;()&lt;/span&gt;)
      &amp;amp;&amp;amp; &lt;span class=&quot;hljs-title&quot; style=&quot;color: #a6e22e;&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;hljs-params&quot; style=&quot;color: #f8f8f2;&quot;&gt;(cachedObject)&lt;/span&gt;&lt;/span&gt;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;這裡我把其他部份都略過了，只先看它定義關於 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;@Cacheable&lt;/code&gt; 的部份。它定義了一個 pointcut 叫做 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;executionOfAnyPublicMethodInAtCacheableType()&lt;/code&gt;，會被橫切到所有有標註 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;@Cacheable&lt;/code&gt; 的 public method 上。那麼 pointcut 要執行的邏輯是什麼呢？這就需要看它的 parent &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;AbstractCacheAspect&lt;/code&gt; (&lt;a href=&quot;https://github.com/spring-projects/spring-framework/blob/v5.3.31/spring-aspects/src/main/java/org/springframework/cache/aspectj/AbstractCacheAspect.aj&quot;&gt;git&lt;/a&gt;) 了。&lt;/p&gt;
&lt;pre style=&quot;font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; font-size: 1em; line-height: 1.2em; margin: 1.2em 0px;&quot;&gt;&lt;code class=&quot;hljs language-java&quot; style=&quot;background-color: #f8f8f8; background: rgb(35, 36, 31); border-radius: 3px; border-radius: 3px; border: 1px solid rgb(204, 204, 204); border: 1px solid rgb(234, 234, 234); color: #f8f8f2; display: block !important; display: block; display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; overflow-x: auto; overflow: auto; padding: 0.5em 0.7em; padding: 0.5em; padding: 0px 0.3em; text-size-adjust: none; white-space: pre-wrap; white-space: pre;&quot;&gt;&lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #f92672;&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #f92672;&quot;&gt;abstract&lt;/span&gt; aspect AbstractCacheAspect extends CacheAspectSupport implements DisposableBean {
  ...
  &lt;span class=&quot;hljs-annotation&quot; style=&quot;color: #75715e;&quot;&gt;@SuppressAjWarnings&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot; style=&quot;color: #e6db74;&quot;&gt;&quot;adviceDidNotMatch&quot;&lt;/span&gt;)
  &lt;span class=&quot;hljs-function&quot; style=&quot;color: #f92672;&quot;&gt;Object &lt;span class=&quot;hljs-title&quot; style=&quot;color: #a6e22e;&quot;&gt;around&lt;/span&gt;&lt;span class=&quot;hljs-params&quot; style=&quot;color: #f8f8f2;&quot;&gt;(&lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #66d9ef; color: #f92672;&quot;&gt;final&lt;/span&gt; Object cachedObject)&lt;/span&gt; : &lt;span class=&quot;hljs-title&quot; style=&quot;color: #a6e22e;&quot;&gt;cacheMethodExecution&lt;/span&gt;&lt;span class=&quot;hljs-params&quot; style=&quot;color: #f8f8f2;&quot;&gt;(cachedObject)&lt;/span&gt; &lt;/span&gt;{
    MethodSignature methodSignature = (MethodSignature) thisJoinPoint.getSignature();
    Method method = methodSignature.getMethod();

    CacheOperationInvoker aspectJInvoker = &lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #f92672;&quot;&gt;new&lt;/span&gt; CacheOperationInvoker() {
        &lt;span class=&quot;hljs-function&quot; style=&quot;color: #f92672;&quot;&gt;&lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #66d9ef; color: #f92672;&quot;&gt;public&lt;/span&gt; Object &lt;span class=&quot;hljs-title&quot; style=&quot;color: #a6e22e;&quot;&gt;invoke&lt;/span&gt;&lt;span class=&quot;hljs-params&quot; style=&quot;color: #f8f8f2;&quot;&gt;()&lt;/span&gt; &lt;/span&gt;{
          &lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #f92672;&quot;&gt;try&lt;/span&gt; {
            &lt;span class=&quot;hljs-function&quot; style=&quot;color: #f92672;&quot;&gt;&lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #66d9ef; color: #f92672;&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;hljs-title&quot; style=&quot;color: #a6e22e;&quot;&gt;proceed&lt;/span&gt;&lt;span class=&quot;hljs-params&quot; style=&quot;color: #f8f8f2;&quot;&gt;(cachedObject)&lt;/span&gt;&lt;/span&gt;;
          }
          &lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #f92672;&quot;&gt;catch&lt;/span&gt; (Throwable ex) {
            &lt;span class=&quot;hljs-function&quot; style=&quot;color: #f92672;&quot;&gt;&lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #66d9ef; color: #f92672;&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #66d9ef; color: #f92672;&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;hljs-title&quot; style=&quot;color: #a6e22e;&quot;&gt;ThrowableWrapper&lt;/span&gt;&lt;span class=&quot;hljs-params&quot; style=&quot;color: #f8f8f2;&quot;&gt;(ex)&lt;/span&gt;&lt;/span&gt;;
          }
        }
    };

    &lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #f92672;&quot;&gt;try&lt;/span&gt; {
      &lt;span class=&quot;hljs-function&quot; style=&quot;color: #f92672;&quot;&gt;&lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #66d9ef; color: #f92672;&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;hljs-title&quot; style=&quot;color: #a6e22e;&quot;&gt;execute&lt;/span&gt;&lt;span class=&quot;hljs-params&quot; style=&quot;color: #f8f8f2;&quot;&gt;(aspectJInvoker, thisJoinPoint.getTarget()&lt;/span&gt;, method, thisJoinPoint.&lt;span class=&quot;hljs-title&quot; style=&quot;color: #a6e22e;&quot;&gt;getArgs&lt;/span&gt;&lt;span class=&quot;hljs-params&quot; style=&quot;color: #f8f8f2;&quot;&gt;()&lt;/span&gt;)&lt;/span&gt;;
    }
    &lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #f92672;&quot;&gt;catch&lt;/span&gt; (CacheOperationInvoker.ThrowableWrapper th) {
      AnyThrow.throwUnchecked(th.getOriginal());
      &lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #f92672;&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #f92672;&quot;&gt;null&lt;/span&gt;; &lt;span class=&quot;hljs-comment&quot; style=&quot;color: #75715e;&quot;&gt;// never reached&lt;/span&gt;
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;可以看出，它定義了一個 around 並綁定在 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;cacheMethodExecution()&lt;/code&gt; 上，而 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;cacheMethodExecution()&lt;/code&gt; 這個 pointcut 則是定義一系列的 pointcuts。所以我的理解是，它應該是把 around 綁到所有它列出來的 pointcut，也就是像是 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;@Cacheable&lt;/code&gt;、&lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;`CachePut&lt;/code&gt; 等等的 pointcut。&lt;/p&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;接著來看這個 around 裡面的內容。它首先先建立了一個 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;CacheOperationInvoker&lt;/code&gt; 的 instance，這個 invoker 的目的是用來呼叫被 cache 的 method 的，接著就去呼叫 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;execute()&lt;/code&gt;，把 JoinPoint 的資訊以及剛剛建立的 invoker 都傳進去。&lt;/p&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;&lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;execute()&lt;/code&gt; 是 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;AbstractCacheAspect&lt;/code&gt; 從它的 parent &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;CacheAspectSupport&lt;/code&gt; 繼承來的，所以接著就再去看 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;CacheAspectSupport&lt;/code&gt; 裡的內容。不過裡面內容蠻多的，所以只先節錄了 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;execute()&lt;/code&gt; 的部份：&lt;/p&gt;
&lt;pre style=&quot;font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; font-size: 1em; line-height: 1.2em; margin: 1.2em 0px;&quot;&gt;&lt;code class=&quot;hljs language-java&quot; style=&quot;background-color: #f8f8f8; background: rgb(35, 36, 31); border-radius: 3px; border-radius: 3px; border: 1px solid rgb(204, 204, 204); border: 1px solid rgb(234, 234, 234); color: #f8f8f2; display: block !important; display: block; display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; overflow-x: auto; overflow: auto; padding: 0.5em 0.7em; padding: 0.5em; padding: 0px 0.3em; text-size-adjust: none; white-space: pre-wrap; white-space: pre;&quot;&gt;&lt;span class=&quot;hljs-annotation&quot; style=&quot;color: #75715e;&quot;&gt;@Nullable&lt;/span&gt;
&lt;span class=&quot;hljs-function&quot; style=&quot;color: #f92672;&quot;&gt;&lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #66d9ef; color: #f92672;&quot;&gt;protected&lt;/span&gt; Object &lt;span class=&quot;hljs-title&quot; style=&quot;color: #a6e22e;&quot;&gt;execute&lt;/span&gt;&lt;span class=&quot;hljs-params&quot; style=&quot;color: #f8f8f2;&quot;&gt;(CacheOperationInvoker invoker, Object target, Method method, Object[] args)&lt;/span&gt; &lt;/span&gt;{
  &lt;span class=&quot;hljs-comment&quot; style=&quot;color: #75715e;&quot;&gt;// Check whether aspect is enabled (to cope with cases where the AJ is pulled in automatically)&lt;/span&gt;
  &lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #f92672;&quot;&gt;if&lt;/span&gt; (&lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #f92672;&quot;&gt;this&lt;/span&gt;.initialized) {
    Class&amp;lt;?&amp;gt; targetClass = getTargetClass(target);
    CacheOperationSource cacheOperationSource = getCacheOperationSource();
    &lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #f92672;&quot;&gt;if&lt;/span&gt; (cacheOperationSource != &lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #f92672;&quot;&gt;null&lt;/span&gt;) {
      Collection&amp;lt;CacheOperation&amp;gt; operations = cacheOperationSource.getCacheOperations(method, targetClass);
      &lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #f92672;&quot;&gt;if&lt;/span&gt; (!CollectionUtils.isEmpty(operations)) {
        &lt;span class=&quot;hljs-function&quot; style=&quot;color: #f92672;&quot;&gt;&lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #66d9ef; color: #f92672;&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;hljs-title&quot; style=&quot;color: #a6e22e;&quot;&gt;execute&lt;/span&gt;&lt;span class=&quot;hljs-params&quot; style=&quot;color: #f8f8f2;&quot;&gt;(invoker, method,
            &lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #66d9ef; color: #f92672;&quot;&gt;new&lt;/span&gt; CacheOperationContexts(operations, method, args, target, targetClass)&lt;/span&gt;)&lt;/span&gt;;
      }
    }
  }

  &lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #f92672;&quot;&gt;return&lt;/span&gt; invoker.invoke();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;這段最主要的行為，是在檢查自己初始化的狀態，如果初始化還沒完成，就直接呼叫 invoker，否則就把 cache 的服務 (即 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;CacheOperationSource&lt;/code&gt;) 準備好，然後再去呼叫第二個 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;execute()&lt;/code&gt;。&lt;/p&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;第二個 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;execute()&lt;/code&gt; (&lt;a href=&quot;https://github.com/spring-projects/spring-framework/blob/v5.3.37/spring-context/src/main/java/org/springframework/cache/interceptor/CacheAspectSupport.java#L374&quot;&gt;git&lt;/a&gt;) 內容更長，所以這裡也是先篩掉一些內容，只先看最單純的部份：&lt;/p&gt;
&lt;pre style=&quot;font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; font-size: 1em; line-height: 1.2em; margin: 1.2em 0px;&quot;&gt;&lt;code class=&quot;hljs language-java&quot; style=&quot;background-color: #f8f8f8; background: rgb(35, 36, 31); border-radius: 3px; border-radius: 3px; border: 1px solid rgb(204, 204, 204); border: 1px solid rgb(234, 234, 234); color: #f8f8f2; display: block !important; display: block; display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; overflow-x: auto; overflow: auto; padding: 0.5em 0.7em; padding: 0.5em; padding: 0px 0.3em; text-size-adjust: none; white-space: pre-wrap; white-space: pre;&quot;&gt;&lt;span class=&quot;hljs-function&quot; style=&quot;color: #f92672;&quot;&gt;&lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #66d9ef; color: #f92672;&quot;&gt;private&lt;/span&gt; Object &lt;span class=&quot;hljs-title&quot; style=&quot;color: #a6e22e;&quot;&gt;execute&lt;/span&gt;&lt;span class=&quot;hljs-params&quot; style=&quot;color: #f8f8f2;&quot;&gt;(&lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #66d9ef; color: #f92672;&quot;&gt;final&lt;/span&gt; CacheOperationInvoker invoker, Method method, CacheOperationContexts contexts)&lt;/span&gt; &lt;/span&gt;{
  &lt;span class=&quot;hljs-comment&quot; style=&quot;color: #75715e;&quot;&gt;// Special handling of synchronized invocation&lt;/span&gt;
  &lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #f92672;&quot;&gt;if&lt;/span&gt; (contexts.isSynchronized()) {
    ...
  }

  ...

  &lt;span class=&quot;hljs-comment&quot; style=&quot;color: #75715e;&quot;&gt;// Check if we have a cached value matching the conditions&lt;/span&gt;
  Cache.ValueWrapper cacheHit = findCachedItem(contexts.get(CacheableOperation.class));

  ...

  Object cacheValue;
  Object returnValue;

  &lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #f92672;&quot;&gt;if&lt;/span&gt; (cacheHit != &lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #f92672;&quot;&gt;null&lt;/span&gt; &amp;amp;&amp;amp; !hasCachePut(contexts)) {
    &lt;span class=&quot;hljs-comment&quot; style=&quot;color: #75715e;&quot;&gt;// If there are no put requests, just use the cache hit&lt;/span&gt;
    cacheValue = cacheHit.get();
    returnValue = wrapCacheValue(method, cacheValue);
  }
  &lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #f92672;&quot;&gt;else&lt;/span&gt; {
    &lt;span class=&quot;hljs-comment&quot; style=&quot;color: #75715e;&quot;&gt;// Invoke the method if we don&#39;t have a cache hit&lt;/span&gt;
    returnValue = invokeOperation(invoker);
    cacheValue = unwrapReturnValue(returnValue);
  }

  ...

  &lt;span class=&quot;hljs-comment&quot; style=&quot;color: #75715e;&quot;&gt;// Process any collected put requests, either from @CachePut or a @Cacheable miss&lt;/span&gt;
  &lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #f92672;&quot;&gt;for&lt;/span&gt; (CachePutRequest cachePutRequest : cachePutRequests) {
    cachePutRequest.apply(cacheValue);
  }

  ...

  &lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #f92672;&quot;&gt;return&lt;/span&gt; returnValue;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;節錄下來的部份就是 Spring Cache 實際上操作 cache 服務和被 cache 的 method 的行為了。首先它透過 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;findCachedItem()&lt;/code&gt; 取得 cache 的結果，如果 cache hit 了，就把它放進 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;returnValue&lt;/code&gt; 中；反之，如果 cache miss 了，則會去執行被 cache 的 method 取得最新的執行結果，然後放進 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;returnValue&lt;/code&gt; 裡。&lt;/p&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;中間有一部分收集 put requests 的程式碼被我過濾掉了沒貼進來，不過大致上就是最後它會依據收集到要做的 put requests 開始一個一個 apply 到 cache 中，完成 cache 的更新。&lt;/p&gt;
&lt;div style=&quot;font-size: 0em; height: 0; margin: 0; max-height: 0; max-width: 0; overflow: hidden; padding: 0; width: 0;&quot; title=&quot;MDH:PGRpdj7lm6DngrrmnIDov5HlnKjlsIjmoYjkuK3mlbTlkIggU3ByaW5nIENhY2hl77yM5LiN6YGO
5Y6f55Sf55qEIFNwcmluZyBDYWNoZSDmlK/mj7TnmoTlip/og73mnInpu57ln7rmnKzvvIzmiJHl
gJHmg7PopoHmk7TlhYXlroPnmoTlip/og73vvIzmiYDku6XpnIDopoHnqI3lvq7kuobop6PkuIDk
uIsgU3ByaW5nIENhY2hlIOiDjOW+jOWIsOW6leaAjum6vOmBi+S9nOeahO+8jOaIkeWAkeaJjeac
iei+puazleaJvuWHuuavlOi8g+WQiOmBqeeahOaTtOWFheaWueW8j+OAgjwvZGl2PjxkaXY+PGJy
PjwvZGl2PjxkaXY+57ay6Lev5LiK5Y+v5Lul5om+5Yiw5LiA5Lqb5Lit5ZyL57ay5Y+L55qE5Y6f
5aeL56K86Kej5p6Q77yM5LiN6YGO5aSn5aSa5pW45om+5Yiw55qE6YO95piv55So6aCQ6Kit55qE
IFNwcmluZyBBT1DvvIzkvYbmiJHlgJHlsIjmoYjnmoTni4Dms4HmnIPkvb/nlKggQXNwZWN0Su+8
jOaJgOS7pemAmemCiuacg+e0gOmMhCBBc3BlY3RKIOeahOeLgOazgeaYr+WmguS9lemBi+S9nOea
hOOAgjwvZGl2PjxkaXY+PGJyPjwvZGl2PjxkaXY+5Y+m5aSW77yM5Zug54K65oiR5YCR6YKE5Zyo
55SoIFNwcmluZyBCb290IDIuN++8jOaJgOS7pemAmeijoeacg+WFiOmXnOazqOWcqCBTcHJpbmcg
djUuMy4zMSDniYjnmoTljp/lp4vnorzjgILnm7jpl5znmoQgU3ByaW5nIENhY2hlIOWumOaWueaW
h+S7tuWPr+S7peWPg+iAg1vpgJnoo6FdW3NwcmluZy1jYWNoZV3jgII8L2Rpdj48ZGl2Pjxicj48
L2Rpdj48ZGl2PiMgU3ByaW5nIENhY2hlIOeahOS9v+eUqOaWueW8jzwvZGl2PjxkaXY+PGJyPjwv
ZGl2PjxkaXY+6YKj6bq86KaB5b6e5ZOq6KOh6ZaL5aeL6L+96Lmk5ZGi77yf5YWI5L6G55yL5LiA
5LiLIFNwcmluZyBDYWNoZSDmgI7purzkvb/nlKjnmoTjgII8L2Rpdj48ZGl2Pjxicj48L2Rpdj48
ZGl2PmBgYGphdmE8L2Rpdj48ZGl2PkBDb25maWd1cmF0aW9uPC9kaXY+PGRpdj5ARW5hYmxlQ2Fj
aGluZzwvZGl2PjxkaXY+cHVibGljIGNsYXNzIEFwcENvbmZpZyB7PC9kaXY+PGRpdj59PC9kaXY+
PGRpdj48YnI+PC9kaXY+PGRpdj5AQ2FjaGVhYmxlKCJib29rcyIpPC9kaXY+PGRpdj5wdWJsaWMg
Qm9vayBmaW5kQm9vayhJU0JOIGlzYm4pIHsuLi59PC9kaXY+PGRpdj5gYGA8L2Rpdj48ZGl2Pjxi
cj48L2Rpdj48ZGl2PlNwcmluZyBDYWNoZSDmnIDln7rmnKznmoTkvb/nlKjmlrnlvI/vvIzlsLHm
mK/lg4/kuIrpnaLnmoTkvovlrZDpgqPmqKPvvIznlKggYEBDYWNoZWFibGVgIOiou+ino+S+huWR
iuiotCBTcHJpbmcg6KqqIGBmaW5kQm9vayguLi4pYCDpgJnlgIsgbWV0aG9kIOimgeiiqyBjYWNo
Ze+8jOS4puS4lOmcgOimgeaPkOS+m+S4gOWAiyBjb25maWd1cmF0aW9u77yM5LiK6Z2i5pyJIGBA
RW5hYmxlQ2FjaGluZ2Ag5L6G6KaB5rGC6KaB5ZWf55SoIGNhY2hl44CC6YCZ6KOh5YW25a+m6YKE
6ZyA6KaB5LiA6bue5YW25LuW55qE6Kit5a6a77yM5LiN6YGO5Zug54K65LiN5piv6YCZ6KOh55qE
6YeN6bue77yM5omA5Lul5oiR6YO95YWI55Wl6YGOIO2gve24huOAgue4veS5i++8jOi1t+m7nuWw
seaYryBgQEVuYWJsZUNhY2hpbmdgIOS6huOAgjwvZGl2PjxkaXY+PGJyPjwvZGl2PjxkaXY+IyBD
YWNoZSDnmoTovInlhaXmtYHnqIs8L2Rpdj48ZGl2PiMjIOi8ieWFpSBDb25maWd1cmF0aW9uPC9k
aXY+PGRpdj48YnI+PC9kaXY+PGRpdj7pppblhYjlvp4gYEBFbmFibGVDYWNoaW5nYCDpgJnlgIvo
qLvop6Pplovlp4vnnIvvvIzlroPnmoTlhaflrrnlpKfmpoLmmK/pgJnmqKPvvJo8L2Rpdj48ZGl2
Pjxicj48L2Rpdj48ZGl2PmBgYGphdmE8L2Rpdj48ZGl2PkBUYXJnZXQoRWxlbWVudFR5cGUuVFlQ
RSk8L2Rpdj48ZGl2PkBSZXRlbnRpb24oUmV0ZW50aW9uUG9saWN5LlJVTlRJTUUpPC9kaXY+PGRp
dj5ARG9jdW1lbnRlZDwvZGl2PjxkaXY+QEltcG9ydChDYWNoaW5nQ29uZmlndXJhdGlvblNlbGVj
dG9yLmNsYXNzKTwvZGl2PjxkaXY+cHVibGljIEBpbnRlcmZhY2UgRW5hYmxlQ2FjaGluZyB7PC9k
aXY+PGRpdj59PC9kaXY+PGRpdj5gYGA8L2Rpdj48ZGl2Pjxicj48L2Rpdj48ZGl2PuWPr+S7peaz
qOaEj+WIsO+8jOWug+Wuo+WRiuS6hiBgQEltcG9ydGDvvIzlm6DmraQgU3ByaW5nIOacg+S9v+eU
qOWug+aMh+WumueahCBgQ2FjaGluZ0NvbmZpZ3VyYXRpb25TZWxlY3RvcmAg5L6G5b6X55+l5Yiw
6LyJ5YWl5LuA6bq85p2x6KW/44CCPC9kaXY+PGRpdj48YnI+PC9kaXY+PGRpdj5gQ2FjaGluZ0Nv
bmZpZ3VyYXRpb25TZWxlY3RvcmAgKFtnaXRdW2NhY2hlLWNvbmZpZ3VyYXRpb24tc2VsZWN0b3Jd
KSDmmK/kuIDlgIvpgI/pgY4gcGFyZW50IOWvpuS9nOS6hiBgSW1wb3J0U2VsZWN0b3JgIOeahOmh
nuWIpe+8jOWboOatpOWug+S4u+imgeaPkOS+myBgc2VsZWN0SW1wb3J0c2Ag6YCZ5YCL5pa55rOV
77yM55So5L6G5ZGK6Ki0IFNwcmluZyDoqqropoHovInlhaXnmoQgY2xhc3Mg55qEIEZRRE4g5piv
5LuA6bq844CC5q+U6LyD6YeN6KaB55qE5YWn5a655aaC5LiL77yaPC9kaXY+PGRpdj48YnI+PC9k
aXY+PGRpdj5gYGBqYXZhPC9kaXY+PGRpdj5wdWJsaWMgY2xhc3MgQ2FjaGluZ0NvbmZpZ3VyYXRp
b25TZWxlY3RvciBleHRlbmRzIEFkdmljZU1vZGVJbXBvcnRTZWxlY3RvciZsdDtFbmFibGVDYWNo
aW5nJmd0OyB7PC9kaXY+PGRpdj4mbmJzcDsgLi4uPC9kaXY+PGRpdj4mbmJzcDsgcHJpdmF0ZSBz
dGF0aWMgZmluYWwgU3RyaW5nIENBQ0hFX0FTUEVDVF9DT05GSUdVUkFUSU9OX0NMQVNTX05BTUUg
PTwvZGl2PjxkaXY+PHNwYW4gc3R5bGU9IndoaXRlLXNwYWNlOiBub3JtYWw7Ij48c3BhbiBzdHls
ZT0id2hpdGUtc3BhY2U6cHJlIj4JCQk8L3NwYW4+Im9yZy5zcHJpbmdmcmFtZXdvcmsuY2FjaGUu
YXNwZWN0ai5Bc3BlY3RKQ2FjaGluZ0NvbmZpZ3VyYXRpb24iOzwvc3Bhbj48L2Rpdj48ZGl2PiZu
YnNwOyAuLi48L2Rpdj48ZGl2Pjxicj48L2Rpdj48ZGl2PiZuYnNwOyBAT3ZlcnJpZGU8L2Rpdj48
ZGl2PiZuYnNwOyBwdWJsaWMgU3RyaW5nW10gc2VsZWN0SW1wb3J0cyhBZHZpY2VNb2RlIGFkdmlj
ZU1vZGUpIHs8L2Rpdj48ZGl2PiZuYnNwOyAmbmJzcDsgc3dpdGNoIChhZHZpY2VNb2RlKSB7PC9k
aXY+PGRpdj4mbmJzcDsgJm5ic3A7ICZuYnNwOyBjYXNlIFBST1hZOjwvZGl2PjxkaXY+Jm5ic3A7
ICZuYnNwOyAmbmJzcDsgJm5ic3A7IHJldHVybiBnZXRQcm94eUltcG9ydHMoKTs8L2Rpdj48ZGl2
PiZuYnNwOyAmbmJzcDsgJm5ic3A7IGNhc2UgQVNQRUNUSjo8L2Rpdj48ZGl2PiZuYnNwOyAmbmJz
cDsgJm5ic3A7ICZuYnNwOyByZXR1cm4gZ2V0QXNwZWN0SkltcG9ydHMoKTs8L2Rpdj48ZGl2PiZu
YnNwOyAmbmJzcDsgJm5ic3A7IGRlZmF1bHQ6PC9kaXY+PGRpdj4mbmJzcDsgJm5ic3A7ICZuYnNw
OyAmbmJzcDsgcmV0dXJuIG51bGw7PC9kaXY+PGRpdj4mbmJzcDsgJm5ic3A7IH08L2Rpdj48ZGl2
PiZuYnNwOyB9PC9kaXY+PGRpdj48YnI+PC9kaXY+PGRpdj4mbmJzcDsgLi4uPC9kaXY+PGRpdj4m
bmJzcDsgcHJpdmF0ZSBTdHJpbmdbXSBnZXRBc3BlY3RKSW1wb3J0cygpIHs8L2Rpdj48ZGl2PiZu
YnNwOyAmbmJzcDsgTGlzdCZsdDtTdHJpbmcmZ3Q7IHJlc3VsdCA9IG5ldyBBcnJheUxpc3QmbHQ7
Jmd0OygyKTs8L2Rpdj48ZGl2PiZuYnNwOyAmbmJzcDsgcmVzdWx0LmFkZChDQUNIRV9BU1BFQ1Rf
Q09ORklHVVJBVElPTl9DTEFTU19OQU1FKTs8L2Rpdj48ZGl2PiZuYnNwOyAmbmJzcDsgaWYgKGpz
cjEwN1ByZXNlbnQgJmFtcDsmYW1wOyBqY2FjaGVJbXBsUHJlc2VudCkgezwvZGl2PjxkaXY+Jm5i
c3A7ICZuYnNwOyAmbmJzcDsgcmVzdWx0LmFkZChKQ0FDSEVfQVNQRUNUX0NPTkZJR1VSQVRJT05f
Q0xBU1NfTkFNRSk7PC9kaXY+PGRpdj4mbmJzcDsgJm5ic3A7IH08L2Rpdj48ZGl2PiZuYnNwOyAm
bmJzcDsgcmV0dXJuIFN0cmluZ1V0aWxzLnRvU3RyaW5nQXJyYXkocmVzdWx0KTs8L2Rpdj48ZGl2
PjxzcGFuIHN0eWxlPSJ3aGl0ZS1zcGFjZTogbm9ybWFsOyI+PHNwYW4gc3R5bGU9IndoaXRlLXNw
YWNlOnByZSI+CTwvc3Bhbj59PC9zcGFuPjwvZGl2PjxkaXY+YGBgPC9kaXY+PGRpdj48YnI+PC9k
aXY+PGRpdj7lnKggYHNlbGVjdEltcG9ydHMoKWAg5Lit77yM5pyD5L6d5pOa6Ki76Kej6Kit5a6a
55qEIGBhZHZpY2VNb2RlYCDmsbrlrpogaW1wb3J0IOaZguimgee1puWTquWAiyBjbGFzcyBuYW1l
77yM5Zug54K65YmN6Z2i5o+Q5Yiw5oiR5YCR6KaB55So55qE5pivIEFzcGVjdErvvIzkuKbkuJTm
iJHlgJHkuKbmspLmnInopoHnlKggSlNSLTEwN++8jOaJgOS7pemAmeijoeWwseWPqueci+e0lCBB
c3BlY3RKIOeahOeLgOazgeOAguaJgOS7peWPr+S7peeci+WHuu+8jOmAmeijoeWmguaenCBgYWR2
aXNlTW9kZWAg6Kit5a6a54K6IEFzcGVjdEog55qE6Kmx77yM5a6D5pyD5Zue6KaG55qEIEZRRE4g
5pivIGBvcmcuc3ByaW5nZnJhbWV3b3JrLmNhY2hlLmFzcGVjdGouQXNwZWN0SkNhY2hpbmdDb25m
aWd1cmF0aW9uYOOAgjwvZGl2PjxkaXY+PGJyPjwvZGl2PjxkaXY+5pa85piv5o6l6JGX5L6G55yL
5LiA5LiLIEFzcGVjdEog55qEIENvbmZpZ3VyYXRpb24gKFtnaXRdW2FzcGVjdGotY2FjaGluZy1j
b25maWd1cmF0aW9uXSkg5a+r5LqG5LuA6bq8772e44CCPC9kaXY+PGRpdj48YnI+PC9kaXY+PGRp
dj5gYGBqYXZhPC9kaXY+PGRpdj5AQ29uZmlndXJhdGlvbihwcm94eUJlYW5NZXRob2RzID0gZmFs
c2UpPC9kaXY+PGRpdj5AUm9sZShCZWFuRGVmaW5pdGlvbi5ST0xFX0lORlJBU1RSVUNUVVJFKTwv
ZGl2PjxkaXY+cHVibGljIGNsYXNzIEFzcGVjdEpDYWNoaW5nQ29uZmlndXJhdGlvbiBleHRlbmRz
IEFic3RyYWN0Q2FjaGluZ0NvbmZpZ3VyYXRpb24gezwvZGl2PjxkaXY+PGJyPjwvZGl2PjxkaXY+
Jm5ic3A7IEBCZWFuKG5hbWUgPSBDYWNoZU1hbmFnZW1lbnRDb25maWdVdGlscy5DQUNIRV9BU1BF
Q1RfQkVBTl9OQU1FKTwvZGl2PjxkaXY+Jm5ic3A7IEBSb2xlKEJlYW5EZWZpbml0aW9uLlJPTEVf
SU5GUkFTVFJVQ1RVUkUpPC9kaXY+PGRpdj4mbmJzcDsgcHVibGljIEFubm90YXRpb25DYWNoZUFz
cGVjdCBjYWNoZUFzcGVjdCgpIHs8L2Rpdj48ZGl2PiZuYnNwOyAmbmJzcDsgQW5ub3RhdGlvbkNh
Y2hlQXNwZWN0IGNhY2hlQXNwZWN0ID0gQW5ub3RhdGlvbkNhY2hlQXNwZWN0LmFzcGVjdE9mKCk7
PC9kaXY+PGRpdj4mbmJzcDsgJm5ic3A7IGNhY2hlQXNwZWN0LmNvbmZpZ3VyZSh0aGlzLmVycm9y
SGFuZGxlciwgdGhpcy5rZXlHZW5lcmF0b3IsIHRoaXMuY2FjaGVSZXNvbHZlciwgdGhpcy5jYWNo
ZU1hbmFnZXIpOzwvZGl2PjxkaXY+Jm5ic3A7ICZuYnNwOyByZXR1cm4gY2FjaGVBc3BlY3Q7PC9k
aXY+PGRpdj4mbmJzcDsgfTwvZGl2PjxkaXY+PGJyPjwvZGl2PjxkaXY+fTwvZGl2PjxkaXY+YGBg
PC9kaXY+PGRpdj48YnI+PC9kaXY+PGRpdj7pgJnoo6HmnIDkuLvopoHlsLHmmK/mj5DkvpvkuIDl
gIsgQmVhbu+8jOmAmeWAiyBCZWFuIOaYr+eUqOS+huW7uueriyBgQW5ub3RhdGlvbkNhY2hlQXNw
ZWN0YCDnmoTvvIzkuKbkuJTlnKjlu7rnq4vnmoTlkIzmmYLmnIPmioroqLvop6PkuIrnmoToqK3l
rprjgIHku6Xlj4rlhbbku5bpnIDopoHlu7rnq4vnmoTkvovlpoIgYEVycm9ySGFuZGxlcmDjgIFg
S2V5R2VuZXJhdG9yYOOAgWBDYWNoZVJlc29sdmVyYOOAgWBDYWNoZU1hbmFnZXJgIOetieadseil
v+mDveS4gOi1t+azqOWFpemAsuWOu+OAgjwvZGl2PjxkaXY+PGJyPjwvZGl2PjxkaXY+5omA5Lul
5Yiw6YCZ6KOh56iN5b6u57i957WQ5LiA5LiL77yM6YCZ5pW05q615Luj6KGo55qE5oSP5oCd77yM
5piv55W2IFNwcmluZyDoroDliLAgYEBFbmFibGVDYWNoaW5nYCDoqLvop6PmmYLvvIzmnIPln7rm
lrzpgJnlgIsgU2VsZWN0b3Ig5rG65a6a5Y676LyJ5YWl5oyH5a6a55qEIENvbmZpZ3VyYXRpb27v
vIznhLblvozlsLHmnIPkvp3mk5ogQ29uZmlndXJhdGlvbiDnmoTlhaflrrnljrvlu7rnq4vljIXl
kKsgYENhY2hlTWFuYWdlcmAg562J562J55qEIEJlYW7vvIzkuKbkuJTliJ3lp4vljJbkuIDlgIsg
QXNwZWN077yM5oqK6YKj5LqbIEFzcGVjdCDpnIDopoHnlKjliLDnmoQgQmVhbiDms6jlhaXliLAg
QXNwZWN0IOeVtuS4reOAgjwvZGl2PjxkaXY+PGJyPjwvZGl2PjxkaXY+QXNwZWN0IOiiq+WIneWn
i+WMluS7peW+jO+8jOW+jOmdoueahOS4u+imgeW3peS9nOaHieipsuWwseaYryBBc3BlY3RKIOea
hOevhOeWh+S6huOAguS4jemBjuWboOeCuuaIkeebruWJjemChOaykuacieW+iOWujOaVtOWcsOec
i+mBjiBBc3BlY3RKIOeahOaWh+S7tu+8jOaJgOS7peWPquacieWkp+eVpeefpemBk+S4gOS6m+iz
h+ioiu+8jOWmguaenOe0gOmMhOacieiqpOatoei/juaPkOmGkiDtoL3tuIbjgII8L2Rpdj48ZGl2
Pjxicj48L2Rpdj48ZGl2PkFzcGVjdEog5Zyo57ir5ZCI55qE5pmC5YCZ77yM5pyD6K6TIEFzcGVj
dCDmk4HmnIkgYGFzcGVjdE9mKClgIOeahCBtZXRob2QgKFtkb2NdW2FzcGVjdG9mXSnvvIzmiYDk
u6UgU3ByaW5nIOaJjeaciei+puazleWRvOWPqyBgYXNwZWN0T2YoKWAg5L6G5Y+W5b6X6YCZ5YCL
IEFzcGVjdO+8jOS4puS4lOWwjeWug+WBmuioreWumuOAgjwvZGl2PjxkaXY+PGJyPjwvZGl2Pjxk
aXY+IyDovInlhaXkuKboqLvlhoogQXNwZWN0PC9kaXY+PGRpdj48YnI+PC9kaXY+PGRpdj7liY3m
rrXooqvlu7rnq4vnmoQgYEFubm90YXRpb25DYWNoZUFzcGVjdGAgKFtnaXRdW2Fubm90YXRpb24t
Y2FjaGUtYXNwZWN0LWFqXSkg5piv5LiA5YCLIGAuYWpgIOaqlO+8jOaJgOS7peWFp+WuueWfuuac
rOS4iuWwseaYryBBc3BlY3RKIOeahOWumue+qeS6hu+8mjwvZGl2PjxkaXY+PGJyPjwvZGl2Pjxk
aXY+YGBgamF2YTwvZGl2PjxkaXY+cHVibGljIGFzcGVjdCBBbm5vdGF0aW9uQ2FjaGVBc3BlY3Qg
ZXh0ZW5kcyBBYnN0cmFjdENhY2hlQXNwZWN0IHs8L2Rpdj48ZGl2Pjxicj48L2Rpdj48ZGl2PiZu
YnNwOyBwdWJsaWMgQW5ub3RhdGlvbkNhY2hlQXNwZWN0KCkgezwvZGl2PjxkaXY+Jm5ic3A7ICZu
YnNwOyBzdXBlcihuZXcgQW5ub3RhdGlvbkNhY2hlT3BlcmF0aW9uU291cmNlKGZhbHNlKSk7PC9k
aXY+PGRpdj4mbmJzcDsgfTwvZGl2PjxkaXY+PGJyPjwvZGl2PjxkaXY+Jm5ic3A7IC8qKjwvZGl2
PjxkaXY+Jm5ic3A7ICZuYnNwOyogTWF0Y2hlcyB0aGUgZXhlY3V0aW9uIG9mIGFueSBwdWJsaWMg
bWV0aG9kIGluIGEgdHlwZSB3aXRoIHRoZSBAe0BsaW5rIENhY2hlYWJsZX08L2Rpdj48ZGl2PiZu
YnNwOyAmbmJzcDsqIGFubm90YXRpb24sIG9yIGFueSBzdWJ0eXBlIG9mIGEgdHlwZSB3aXRoIHRo
ZSB7QGNvZGUgQENhY2hlYWJsZX0gYW5ub3RhdGlvbi48L2Rpdj48ZGl2PiZuYnNwOyAmbmJzcDsq
LzwvZGl2PjxkaXY+Jm5ic3A7IHByaXZhdGUgcG9pbnRjdXQgZXhlY3V0aW9uT2ZBbnlQdWJsaWNN
ZXRob2RJbkF0Q2FjaGVhYmxlVHlwZSgpIDo8L2Rpdj48ZGl2PiZuYnNwOyAmbmJzcDsgJm5ic3A7
IGV4ZWN1dGlvbihwdWJsaWMgKiAoKEBDYWNoZWFibGUgKikrKS4qKC4uKSkgJmFtcDsmYW1wOyB3
aXRoaW4oQENhY2hlYWJsZSAqKTs8L2Rpdj48ZGl2PiZuYnNwOyZuYnNwOzwvZGl2PjxkaXY+Jm5i
c3A7IC4uLjwvZGl2PjxkaXY+PGJyPjwvZGl2PjxkaXY+Jm5ic3A7IC8qKjwvZGl2PjxkaXY+Jm5i
c3A7ICZuYnNwOyogRGVmaW5pdGlvbiBvZiBwb2ludGN1dCBmcm9tIHN1cGVyIGFzcGVjdCAtIG1h
dGNoZWQgam9pbiBwb2ludHMgd2lsbCBoYXZlIFNwcmluZzwvZGl2PjxkaXY+Jm5ic3A7ICZuYnNw
OyogY2FjaGUgbWFuYWdlbWVudCBhcHBsaWVkLjwvZGl2PjxkaXY+Jm5ic3A7ICZuYnNwOyovPC9k
aXY+PGRpdj4mbmJzcDsgcHJvdGVjdGVkIHBvaW50Y3V0IGNhY2hlTWV0aG9kRXhlY3V0aW9uKE9i
amVjdCBjYWNoZWRPYmplY3QpIDo8L2Rpdj48ZGl2PiZuYnNwOyAmbmJzcDsgKGV4ZWN1dGlvbk9m
QW55UHVibGljTWV0aG9kSW5BdENhY2hlYWJsZVR5cGUoKTwvZGl2PjxkaXY+Jm5ic3A7ICZuYnNw
OyAmbmJzcDsgJm5ic3A7IHx8IGV4ZWN1dGlvbk9mQW55UHVibGljTWV0aG9kSW5BdENhY2hlRXZp
Y3RUeXBlKCk8L2Rpdj48ZGl2PiZuYnNwOyAmbmJzcDsgJm5ic3A7ICZuYnNwOyB8fCBleGVjdXRp
b25PZkFueVB1YmxpY01ldGhvZEluQXRDYWNoZVB1dFR5cGUoKTwvZGl2PjxkaXY+Jm5ic3A7ICZu
YnNwOyAmbmJzcDsgJm5ic3A7IHx8IGV4ZWN1dGlvbk9mQW55UHVibGljTWV0aG9kSW5BdENhY2hp
bmdUeXBlKCk8L2Rpdj48ZGl2PiZuYnNwOyAmbmJzcDsgJm5ic3A7ICZuYnNwOyB8fCBleGVjdXRp
b25PZkNhY2hlYWJsZU1ldGhvZCgpPC9kaXY+PGRpdj4mbmJzcDsgJm5ic3A7ICZuYnNwOyAmbmJz
cDsgfHwgZXhlY3V0aW9uT2ZDYWNoZUV2aWN0TWV0aG9kKCk8L2Rpdj48ZGl2PiZuYnNwOyAmbmJz
cDsgJm5ic3A7ICZuYnNwOyB8fCBleGVjdXRpb25PZkNhY2hlUHV0TWV0aG9kKCk8L2Rpdj48ZGl2
PiZuYnNwOyAmbmJzcDsgJm5ic3A7ICZuYnNwOyB8fCBleGVjdXRpb25PZkNhY2hpbmdNZXRob2Qo
KSk8L2Rpdj48ZGl2PiZuYnNwOyAmbmJzcDsgJm5ic3A7ICZhbXA7JmFtcDsgdGhpcyhjYWNoZWRP
YmplY3QpOzwvZGl2PjxkaXY+YGBgPC9kaXY+PGRpdj48YnI+PC9kaXY+PGRpdj7pgJnoo6HmiJHm
iorlhbbku5bpg6jku73pg73nlaXpgY7kuobvvIzlj6rlhYjnnIvlroPlrprnvqnpl5zmlrwgYEBD
YWNoZWFibGVgIOeahOmDqOS7veOAguWug+Wumue+qeS6huS4gOWAiyBwb2ludGN1dCDlj6vlgZog
YGV4ZWN1dGlvbk9mQW55UHVibGljTWV0aG9kSW5BdENhY2hlYWJsZVR5cGUoKWDvvIzmnIPooqvm
qavliIfliLDmiYDmnInmnInmqJnoqLsgYEBDYWNoZWFibGVgIOeahCBwdWJsaWMgbWV0aG9kIOS4
iuOAgumCo+m6vCBwb2ludGN1dCDopoHln7fooYznmoTpgo/ovK/mmK/ku4DpurzlkaLvvJ/pgJnl
sLHpnIDopoHnnIvlroPnmoQgcGFyZW50IGBBYnN0cmFjdENhY2hlQXNwZWN0YCAoW2dpdF1bYWJz
dHJhY3QtY2FjaGUtYXNwZWN0XSkg5LqG44CCPC9kaXY+PGRpdj48YnI+PC9kaXY+PGRpdj5gYGBq
YXZhPC9kaXY+PGRpdj5wdWJsaWMgYWJzdHJhY3QgYXNwZWN0IEFic3RyYWN0Q2FjaGVBc3BlY3Qg
ZXh0ZW5kcyBDYWNoZUFzcGVjdFN1cHBvcnQgaW1wbGVtZW50cyBEaXNwb3NhYmxlQmVhbiB7PC9k
aXY+PGRpdj4mbmJzcDsgLi4uPC9kaXY+PGRpdj4mbmJzcDsgQFN1cHByZXNzQWpXYXJuaW5ncygi
YWR2aWNlRGlkTm90TWF0Y2giKTwvZGl2PjxkaXY+Jm5ic3A7IE9iamVjdCBhcm91bmQoZmluYWwg
T2JqZWN0IGNhY2hlZE9iamVjdCkgOiBjYWNoZU1ldGhvZEV4ZWN1dGlvbihjYWNoZWRPYmplY3Qp
IHs8L2Rpdj48ZGl2PiZuYnNwOyAmbmJzcDsgTWV0aG9kU2lnbmF0dXJlIG1ldGhvZFNpZ25hdHVy
ZSA9IChNZXRob2RTaWduYXR1cmUpIHRoaXNKb2luUG9pbnQuZ2V0U2lnbmF0dXJlKCk7PC9kaXY+
PGRpdj4mbmJzcDsgJm5ic3A7IE1ldGhvZCBtZXRob2QgPSBtZXRob2RTaWduYXR1cmUuZ2V0TWV0
aG9kKCk7PC9kaXY+PGRpdj48YnI+PC9kaXY+PGRpdj4mbmJzcDsgJm5ic3A7IENhY2hlT3BlcmF0
aW9uSW52b2tlciBhc3BlY3RKSW52b2tlciA9IG5ldyBDYWNoZU9wZXJhdGlvbkludm9rZXIoKSB7
PC9kaXY+PGRpdj4mbmJzcDsgJm5ic3A7ICZuYnNwOyAmbmJzcDsgcHVibGljIE9iamVjdCBpbnZv
a2UoKSB7PC9kaXY+PGRpdj4mbmJzcDsgJm5ic3A7ICZuYnNwOyAmbmJzcDsgJm5ic3A7IHRyeSB7
PC9kaXY+PGRpdj4mbmJzcDsgJm5ic3A7ICZuYnNwOyAmbmJzcDsgJm5ic3A7ICZuYnNwOyByZXR1
cm4gcHJvY2VlZChjYWNoZWRPYmplY3QpOzwvZGl2PjxkaXY+Jm5ic3A7ICZuYnNwOyAmbmJzcDsg
Jm5ic3A7ICZuYnNwOyB9PC9kaXY+PGRpdj4mbmJzcDsgJm5ic3A7ICZuYnNwOyAmbmJzcDsgJm5i
c3A7IGNhdGNoIChUaHJvd2FibGUgZXgpIHs8L2Rpdj48ZGl2PiZuYnNwOyAmbmJzcDsgJm5ic3A7
ICZuYnNwOyAmbmJzcDsgJm5ic3A7IHRocm93IG5ldyBUaHJvd2FibGVXcmFwcGVyKGV4KTs8L2Rp
dj48ZGl2PiZuYnNwOyAmbmJzcDsgJm5ic3A7ICZuYnNwOyAmbmJzcDsgfTwvZGl2PjxkaXY+Jm5i
c3A7ICZuYnNwOyAmbmJzcDsgJm5ic3A7IH08L2Rpdj48ZGl2PiZuYnNwOyAmbmJzcDsgfTs8L2Rp
dj48ZGl2Pjxicj48L2Rpdj48ZGl2PiZuYnNwOyAmbmJzcDsgdHJ5IHs8L2Rpdj48ZGl2PiZuYnNw
OyAmbmJzcDsgJm5ic3A7IHJldHVybiBleGVjdXRlKGFzcGVjdEpJbnZva2VyLCB0aGlzSm9pblBv
aW50LmdldFRhcmdldCgpLCBtZXRob2QsIHRoaXNKb2luUG9pbnQuZ2V0QXJncygpKTs8L2Rpdj48
ZGl2PiZuYnNwOyAmbmJzcDsgfTwvZGl2PjxkaXY+Jm5ic3A7ICZuYnNwOyBjYXRjaCAoQ2FjaGVP
cGVyYXRpb25JbnZva2VyLlRocm93YWJsZVdyYXBwZXIgdGgpIHs8L2Rpdj48ZGl2PiZuYnNwOyAm
bmJzcDsgJm5ic3A7IEFueVRocm93LnRocm93VW5jaGVja2VkKHRoLmdldE9yaWdpbmFsKCkpOzwv
ZGl2PjxkaXY+Jm5ic3A7ICZuYnNwOyAmbmJzcDsgcmV0dXJuIG51bGw7IC8vIG5ldmVyIHJlYWNo
ZWQ8L2Rpdj48ZGl2PiZuYnNwOyAmbmJzcDsgfTwvZGl2PjxkaXY+Jm5ic3A7IH08L2Rpdj48ZGl2
Pn08L2Rpdj48ZGl2PmBgYDwvZGl2PjxkaXY+PGJyPjwvZGl2PjxkaXY+5Y+v5Lul55yL5Ye677yM
5a6D5a6a576p5LqG5LiA5YCLIGFyb3VuZCDkuKbntoHlrprlnKggYGNhY2hlTWV0aG9kRXhlY3V0
aW9uKClgIOS4iu+8jOiAjCBgY2FjaGVNZXRob2RFeGVjdXRpb24oKWAg6YCZ5YCLIHBvaW50Y3V0
IOWJh+aYr+Wumue+qeS4gOezu+WIl+eahCBwb2ludGN1dHPjgILmiYDku6XmiJHnmoTnkIbop6Pm
mK/vvIzlroPmh4noqbLmmK/mioogYXJvdW5kIOe2geWIsOaJgOacieWug+WIl+WHuuS+hueahCBw
b2ludGN1dO+8jOS5n+WwseaYr+WDj+aYryBgQENhY2hlYWJsZWDjgIFgYENhY2hlUHV0YCDnrYnn
rYnnmoQgcG9pbnRjdXTjgII8L2Rpdj48ZGl2Pjxicj48L2Rpdj48ZGl2PuaOpeiRl+S+hueci+mA
meWAiyBhcm91bmQg6KOh6Z2i55qE5YWn5a6544CC5a6D6aaW5YWI5YWI5bu656uL5LqG5LiA5YCL
IGBDYWNoZU9wZXJhdGlvbkludm9rZXJgIOeahCBpbnN0YW5jZe+8jOmAmeWAiyBpbnZva2VyIOea
hOebrueahOaYr+eUqOS+huWRvOWPq+iiqyBjYWNoZSDnmoQgbWV0aG9kIOeahO+8jOaOpeiRl+Ww
seWOu+WRvOWPqyBgZXhlY3V0ZSgpYO+8jOaKiiBKb2luUG9pbnQg55qE6LOH6KiK5Lul5Y+K5Ymb
5Ymb5bu656uL55qEIGludm9rZXIg6YO95YKz6YCy5Y6744CCPC9kaXY+PGRpdj48YnI+PC9kaXY+
PGRpdj5gZXhlY3V0ZSgpYCDmmK8gYEFic3RyYWN0Q2FjaGVBc3BlY3RgIOW+nuWug+eahCBwYXJl
bnQgYENhY2hlQXNwZWN0U3VwcG9ydGAg57m85om/5L6G55qE77yM5omA5Lul5o6l6JGX5bCx5YaN
5Y6755yLIGBDYWNoZUFzcGVjdFN1cHBvcnRgIOijoeeahOWFp+WuueOAguS4jemBjuijoemdouWF
p+Wuueigu+WkmueahO+8jOaJgOS7peWPquWFiOevgOmMhOS6hiBgZXhlY3V0ZSgpYCDnmoTpg6jk
u73vvJo8L2Rpdj48ZGl2Pjxicj48L2Rpdj48ZGl2PmBgYGphdmE8L2Rpdj48ZGl2PkBOdWxsYWJs
ZTwvZGl2PjxkaXY+cHJvdGVjdGVkIE9iamVjdCBleGVjdXRlKENhY2hlT3BlcmF0aW9uSW52b2tl
ciBpbnZva2VyLCBPYmplY3QgdGFyZ2V0LCBNZXRob2QgbWV0aG9kLCBPYmplY3RbXSBhcmdzKSB7
PC9kaXY+PGRpdj4mbmJzcDsgLy8gQ2hlY2sgd2hldGhlciBhc3BlY3QgaXMgZW5hYmxlZCAodG8g
Y29wZSB3aXRoIGNhc2VzIHdoZXJlIHRoZSBBSiBpcyBwdWxsZWQgaW4gYXV0b21hdGljYWxseSk8
L2Rpdj48ZGl2PiZuYnNwOyBpZiAodGhpcy5pbml0aWFsaXplZCkgezwvZGl2PjxkaXY+Jm5ic3A7
ICZuYnNwOyBDbGFzcyZsdDs/Jmd0OyB0YXJnZXRDbGFzcyA9IGdldFRhcmdldENsYXNzKHRhcmdl
dCk7PC9kaXY+PGRpdj4mbmJzcDsgJm5ic3A7IENhY2hlT3BlcmF0aW9uU291cmNlIGNhY2hlT3Bl
cmF0aW9uU291cmNlID0gZ2V0Q2FjaGVPcGVyYXRpb25Tb3VyY2UoKTs8L2Rpdj48ZGl2PiZuYnNw
OyAmbmJzcDsgaWYgKGNhY2hlT3BlcmF0aW9uU291cmNlICE9IG51bGwpIHs8L2Rpdj48ZGl2PiZu
YnNwOyAmbmJzcDsgJm5ic3A7IENvbGxlY3Rpb24mbHQ7Q2FjaGVPcGVyYXRpb24mZ3Q7IG9wZXJh
dGlvbnMgPSBjYWNoZU9wZXJhdGlvblNvdXJjZS5nZXRDYWNoZU9wZXJhdGlvbnMobWV0aG9kLCB0
YXJnZXRDbGFzcyk7PC9kaXY+PGRpdj4mbmJzcDsgJm5ic3A7ICZuYnNwOyBpZiAoIUNvbGxlY3Rp
b25VdGlscy5pc0VtcHR5KG9wZXJhdGlvbnMpKSB7PC9kaXY+PGRpdj4mbmJzcDsgJm5ic3A7ICZu
YnNwOyAmbmJzcDsgcmV0dXJuIGV4ZWN1dGUoaW52b2tlciwgbWV0aG9kLDwvZGl2PjxkaXY+Jm5i
c3A7ICZuYnNwOyAmbmJzcDsgJm5ic3A7ICZuYnNwOyAmbmJzcDsgbmV3IENhY2hlT3BlcmF0aW9u
Q29udGV4dHMob3BlcmF0aW9ucywgbWV0aG9kLCBhcmdzLCB0YXJnZXQsIHRhcmdldENsYXNzKSk7
PC9kaXY+PGRpdj4mbmJzcDsgJm5ic3A7ICZuYnNwOyB9PC9kaXY+PGRpdj4mbmJzcDsgJm5ic3A7
IH08L2Rpdj48ZGl2PiZuYnNwOyB9PC9kaXY+PGRpdj48YnI+PC9kaXY+PGRpdj4mbmJzcDsgcmV0
dXJuIGludm9rZXIuaW52b2tlKCk7PC9kaXY+PGRpdj59PC9kaXY+PGRpdj5gYGA8L2Rpdj48ZGl2
Pjxicj48L2Rpdj48ZGl2PumAmeauteacgOS4u+imgeeahOihjOeCuu+8jOaYr+WcqOaqouafpeiH
quW3seWIneWni+WMlueahOeLgOaFi++8jOWmguaenOWIneWni+WMlumChOaykuWujOaIkO+8jOWw
seebtOaOpeWRvOWPqyBpbnZva2Vy77yM5ZCm5YmH5bCx5oqKIGNhY2hlIOeahOacjeWLmSAo5Y2z
IGBDYWNoZU9wZXJhdGlvblNvdXJjZWApIOa6luWCmeWlve+8jOeEtuW+jOWGjeWOu+WRvOWPq+es
rOS6jOWAiyBgZXhlY3V0ZSgpYOOAgjwvZGl2PjxkaXY+PGJyPjwvZGl2PjxkaXY+56ys5LqM5YCL
IGBleGVjdXRlKClgIChbZ2l0XVtjYWNoZS1hc3BlY3Qtc3VwcG9ydC0zNzRdKSDlhaflrrnmm7Tp
lbfvvIzmiYDku6XpgJnoo6HkuZ/mmK/lhYjnr6nmjonkuIDkupvlhaflrrnvvIzlj6rlhYjnnIvm
nIDllq7ntJTnmoTpg6jku73vvJo8L2Rpdj48ZGl2Pjxicj48L2Rpdj48ZGl2PmBgYGphdmE8L2Rp
dj48ZGl2PnByaXZhdGUgT2JqZWN0IGV4ZWN1dGUoZmluYWwgQ2FjaGVPcGVyYXRpb25JbnZva2Vy
IGludm9rZXIsIE1ldGhvZCBtZXRob2QsIENhY2hlT3BlcmF0aW9uQ29udGV4dHMgY29udGV4dHMp
IHs8L2Rpdj48ZGl2PiZuYnNwOyAvLyBTcGVjaWFsIGhhbmRsaW5nIG9mIHN5bmNocm9uaXplZCBp
bnZvY2F0aW9uPC9kaXY+PGRpdj4mbmJzcDsgaWYgKGNvbnRleHRzLmlzU3luY2hyb25pemVkKCkp
IHs8L2Rpdj48ZGl2PiZuYnNwOyAmbmJzcDsgLi4uPC9kaXY+PGRpdj4mbmJzcDsgfTwvZGl2Pjxk
aXY+PGJyPjwvZGl2PjxkaXY+Jm5ic3A7IC4uLjwvZGl2PjxkaXY+PGJyPjwvZGl2PjxkaXY+Jm5i
c3A7IC8vIENoZWNrIGlmIHdlIGhhdmUgYSBjYWNoZWQgdmFsdWUgbWF0Y2hpbmcgdGhlIGNvbmRp
dGlvbnM8L2Rpdj48ZGl2PiZuYnNwOyBDYWNoZS5WYWx1ZVdyYXBwZXIgY2FjaGVIaXQgPSBmaW5k
Q2FjaGVkSXRlbShjb250ZXh0cy5nZXQoQ2FjaGVhYmxlT3BlcmF0aW9uLmNsYXNzKSk7PC9kaXY+
PGRpdj48YnI+PC9kaXY+PGRpdj4mbmJzcDsgLi4uPC9kaXY+PGRpdj48YnI+PC9kaXY+PGRpdj4m
bmJzcDsgT2JqZWN0IGNhY2hlVmFsdWU7PC9kaXY+PGRpdj4mbmJzcDsgT2JqZWN0IHJldHVyblZh
bHVlOzwvZGl2PjxkaXY+PGJyPjwvZGl2PjxkaXY+Jm5ic3A7IGlmIChjYWNoZUhpdCAhPSBudWxs
ICZhbXA7JmFtcDsgIWhhc0NhY2hlUHV0KGNvbnRleHRzKSkgezwvZGl2PjxkaXY+Jm5ic3A7ICZu
YnNwOyAvLyBJZiB0aGVyZSBhcmUgbm8gcHV0IHJlcXVlc3RzLCBqdXN0IHVzZSB0aGUgY2FjaGUg
aGl0PC9kaXY+PGRpdj4mbmJzcDsgJm5ic3A7IGNhY2hlVmFsdWUgPSBjYWNoZUhpdC5nZXQoKTs8
L2Rpdj48ZGl2PiZuYnNwOyAmbmJzcDsgcmV0dXJuVmFsdWUgPSB3cmFwQ2FjaGVWYWx1ZShtZXRo
b2QsIGNhY2hlVmFsdWUpOzwvZGl2PjxkaXY+Jm5ic3A7IH08L2Rpdj48ZGl2PiZuYnNwOyBlbHNl
IHs8L2Rpdj48ZGl2PiZuYnNwOyAmbmJzcDsgLy8gSW52b2tlIHRoZSBtZXRob2QgaWYgd2UgZG9u
J3QgaGF2ZSBhIGNhY2hlIGhpdDwvZGl2PjxkaXY+Jm5ic3A7ICZuYnNwOyByZXR1cm5WYWx1ZSA9
IGludm9rZU9wZXJhdGlvbihpbnZva2VyKTs8L2Rpdj48ZGl2PiZuYnNwOyAmbmJzcDsgY2FjaGVW
YWx1ZSA9IHVud3JhcFJldHVyblZhbHVlKHJldHVyblZhbHVlKTs8L2Rpdj48ZGl2PiZuYnNwOyB9
PC9kaXY+PGRpdj48YnI+PC9kaXY+PGRpdj4mbmJzcDsgLi4uPC9kaXY+PGRpdj48YnI+PC9kaXY+
PGRpdj4mbmJzcDsgLy8gUHJvY2VzcyBhbnkgY29sbGVjdGVkIHB1dCByZXF1ZXN0cywgZWl0aGVy
IGZyb20gQENhY2hlUHV0IG9yIGEgQENhY2hlYWJsZSBtaXNzPC9kaXY+PGRpdj4mbmJzcDsgZm9y
IChDYWNoZVB1dFJlcXVlc3QgY2FjaGVQdXRSZXF1ZXN0IDogY2FjaGVQdXRSZXF1ZXN0cykgezwv
ZGl2PjxkaXY+Jm5ic3A7ICZuYnNwOyBjYWNoZVB1dFJlcXVlc3QuYXBwbHkoY2FjaGVWYWx1ZSk7
PC9kaXY+PGRpdj4mbmJzcDsgfTwvZGl2PjxkaXY+PGJyPjwvZGl2PjxkaXY+Jm5ic3A7IC4uLjwv
ZGl2PjxkaXY+PGJyPjwvZGl2PjxkaXY+Jm5ic3A7IHJldHVybiByZXR1cm5WYWx1ZTs8L2Rpdj48
ZGl2Pn08L2Rpdj48ZGl2PmBgYDwvZGl2PjxkaXY+PGJyPjwvZGl2PjxkaXY+56+A6YyE5LiL5L6G
55qE6YOo5Lu95bCx5pivIFNwcmluZyBDYWNoZSDlr6bpmpvkuIrmk43kvZwgY2FjaGUg5pyN5YuZ
5ZKM6KKrIGNhY2hlIOeahCBtZXRob2Qg55qE6KGM54K65LqG44CC6aaW5YWI5a6D6YCP6YGOIGBm
aW5kQ2FjaGVkSXRlbSgpYCDlj5blvpcgY2FjaGUg55qE57WQ5p6c77yM5aaC5p6cIGNhY2hlIGhp
dCDkuobvvIzlsLHmiorlroPmlL7pgLIgYHJldHVyblZhbHVlYCDkuK3vvJvlj43kuYvvvIzlpoLm
npwgY2FjaGUgbWlzcyDkuobvvIzliYfmnIPljrvln7fooYzooqsgY2FjaGUg55qEIG1ldGhvZCDl
j5blvpfmnIDmlrDnmoTln7fooYzntZDmnpzvvIznhLblvozmlL7pgLIgYHJldHVyblZhbHVlYCDo
o6HjgII8L2Rpdj48ZGl2Pjxicj48L2Rpdj48ZGl2PuS4remWk+acieS4gOmDqOWIhuaUtumbhiBw
dXQgcmVxdWVzdHMg55qE56iL5byP56K86KKr5oiR6YGO5r++5o6J5LqG5rKS6LK86YCy5L6G77yM
5LiN6YGO5aSn6Ie05LiK5bCx5piv5pyA5b6M5a6D5pyD5L6d5pOa5pS26ZuG5Yiw6KaB5YGa55qE
IHB1dCByZXF1ZXN0cyDplovlp4vkuIDlgIvkuIDlgIsgYXBwbHkg5YiwIGNhY2hlIOS4re+8jOWu
jOaIkCBjYWNoZSDnmoTmm7TmlrDjgII8L2Rpdj48ZGl2Pjxicj48L2Rpdj48ZGl2PltzcHJpbmct
Y2FjaGVdOiBodHRwczovL2RvY3Muc3ByaW5nLmlvL3NwcmluZy1mcmFtZXdvcmsvZG9jcy81LjMu
MzEvcmVmZXJlbmNlL2h0bWwvaW50ZWdyYXRpb24uaHRtbCNjYWNoZTwvZGl2PjxkaXY+W2NhY2hl
YWJsZV06IGh0dHBzOi8vZ2l0aHViLmNvbS9zcHJpbmctcHJvamVjdHMvc3ByaW5nLWZyYW1ld29y
ay9ibG9iL3Y1LjMuMzEvc3ByaW5nLWNvbnRleHQvc3JjL21haW4vamF2YS9vcmcvc3ByaW5nZnJh
bWV3b3JrL2NhY2hlL2Fubm90YXRpb24vQ2FjaGVhYmxlLmphdmE8L2Rpdj48ZGl2PltjYWNoZS1j
b25maWd1cmF0aW9uLXNlbGVjdG9yXTogaHR0cHM6Ly9naXRodWIuY29tL3NwcmluZy1wcm9qZWN0
cy9zcHJpbmctZnJhbWV3b3JrL2Jsb2IvdjUuMy4zMS9zcHJpbmctY29udGV4dC9zcmMvbWFpbi9q
YXZhL29yZy9zcHJpbmdmcmFtZXdvcmsvY2FjaGUvYW5ub3RhdGlvbi9DYWNoaW5nQ29uZmlndXJh
dGlvblNlbGVjdG9yLmphdmE8L2Rpdj48ZGl2Plthc3BlY3RqLWNhY2hpbmctY29uZmlndXJhdGlv
bl06IGh0dHBzOi8vZ2l0aHViLmNvbS9zcHJpbmctcHJvamVjdHMvc3ByaW5nLWZyYW1ld29yay9i
bG9iL3Y1LjMuMzEvc3ByaW5nLWFzcGVjdHMvc3JjL21haW4vamF2YS9vcmcvc3ByaW5nZnJhbWV3
b3JrL2NhY2hlL2FzcGVjdGovQXNwZWN0SkNhY2hpbmdDb25maWd1cmF0aW9uLmphdmE8L2Rpdj48
ZGl2Plthc3BlY3RvZl06IGh0dHBzOi8vZWNsaXBzZS5kZXYvYXNwZWN0ai9kb2MvcmVsZWFzZWQv
YWRrMTVub3RlYm9vay9hdGFzcGVjdGotYXNwZWN0b2YuaHRtbDwvZGl2PjxkaXY+W2Fubm90YXRp
b24tY2FjaGUtYXNwZWN0LWFqXTogaHR0cHM6Ly9naXRodWIuY29tL3NwcmluZy1wcm9qZWN0cy9z
cHJpbmctZnJhbWV3b3JrL2Jsb2IvdjUuMy4zMS9zcHJpbmctYXNwZWN0cy9zcmMvbWFpbi9qYXZh
L29yZy9zcHJpbmdmcmFtZXdvcmsvY2FjaGUvYXNwZWN0ai9Bbm5vdGF0aW9uQ2FjaGVBc3BlY3Qu
YWo8L2Rpdj48ZGl2PlthYnN0cmFjdC1jYWNoZS1hc3BlY3RdOiBodHRwczovL2dpdGh1Yi5jb20v
c3ByaW5nLXByb2plY3RzL3NwcmluZy1mcmFtZXdvcmsvYmxvYi92NS4zLjMxL3NwcmluZy1hc3Bl
Y3RzL3NyYy9tYWluL2phdmEvb3JnL3NwcmluZ2ZyYW1ld29yay9jYWNoZS9hc3BlY3RqL0Fic3Ry
YWN0Q2FjaGVBc3BlY3QuYWo8L2Rpdj48ZGl2PltjYWNoZS1hc3BlY3Qtc3VwcG9ydC0zNzRdOiBo
dHRwczovL2dpdGh1Yi5jb20vc3ByaW5nLXByb2plY3RzL3NwcmluZy1mcmFtZXdvcmsvYmxvYi92
NS4zLjM3L3NwcmluZy1jb250ZXh0L3NyYy9tYWluL2phdmEvb3JnL3NwcmluZ2ZyYW1ld29yay9j
YWNoZS9pbnRlcmNlcHRvci9DYWNoZUFzcGVjdFN1cHBvcnQuamF2YSNMMzc0PC9kaXY+PGRpdj48
YnI+PC9kaXY+&quot;&gt;​&lt;/div&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimwayne.blogspot.com/feeds/6646824919076133770/comments/default' title='張貼留言'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/6819483672105967231/6646824919076133770' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6819483672105967231/posts/default/6646824919076133770'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6819483672105967231/posts/default/6646824919076133770'/><link rel='alternate' type='text/html' href='http://jimwayne.blogspot.com/2024/06/spring-cache-aspectj.html' title='Spring Cache + AspectJ 背後的大致流程'/><author><name>Wayne Yeh</name><uri>http://www.blogger.com/profile/02148905864753873930</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6819483672105967231.post-8336070158343683284</id><published>2024-05-05T14:09:00.007+08:00</published><updated>2024-05-05T14:55:11.594+08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="Embedding"/><category scheme="http://www.blogger.com/atom/ns#" term="Markdown Here"/><category scheme="http://www.blogger.com/atom/ns#" term="Vespa"/><title type='text'>Matryoshka Representation Learning (MRL) 與 Vespa</title><content type='html'>&lt;div class=&quot;markdown-here-wrapper&quot; data-md-url=&quot;https://draft.blogger.com/blog/post/edit/6819483672105967231/8336070158343683284&quot;&gt;&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;這幾天經由 Vespa 的文章，讀到 OpenAI 在今年（2024 年）二月份的一篇在解說 &lt;a href=&quot;https://aniketrege.github.io/blog/2024/mrl/&quot; title=&quot;Matryoshka Representation Learning (MRL) from the Ground Up&quot;&gt;MRL (Matryoshka Representation Learning) 的文章&lt;/a&gt;，看起來真的相當有趣！不過我並不是這個領域的專業，所以細部的內容我也沒有完全理解，這篇筆記大體只是我自己在應用上的基本理解。如果內容有誤也歡迎留言提醒。😇&lt;/p&gt;
&lt;h1 id=&quot;mrl-&quot; style=&quot;border-bottom: 1px solid rgb(221, 221, 221); font-size: 1.6em; font-weight: bold; margin: 1.3em 0px 1em; padding: 0px;&quot;&gt;MRL 是什麼？&lt;/h1&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;Matryoshka doll 就是俄羅斯娃娃，而 Matryoshka Representation Learning 就是一種利用類似俄羅斯娃娃的特性（小的娃娃可以被裝進大的娃娃裡）做出來的 Embedding 方法，或者說是東西的表達手法。&lt;/p&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;那麼 MRL 跟其他 Embedding 方法最大的差異是什麼呢？就我目前的理解，最大的差異就是上述提到的，它具有俄羅斯娃娃的特性。具體來說，就是小的 Embedding 可以被放在大的 Embedding 裡，或者說是大的 Embedding 切割掉尾巴後，就幾乎等於是小的 Embedding。這個特性最主要的優勢，是在於我們可以不用針對不同維度的 Embedding 分別儲存不同的 Embedding，而是可以統一存一個 Embedding，在需要高維的時候使用 Embedding 裡的比較多內容、需要低維（更快更低成本）的時候則使用 Embedding 裡的一部份內容即可。&lt;/p&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;&lt;img alt=&quot;俄羅斯娃娃&quot; src=&quot;https://media0.giphy.com/media/v1.Y2lkPTc5MGI3NjExdXNscHUzajM3ejVhbjR5dXJwczE4N2Y4a28wc3plNW9ucjRmN25jZyZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/nthoYgQ91Up2u7qmcE/giphy.gif&quot; /&gt;&lt;/p&gt;
&lt;h1 id=&quot;vespa-mrl&quot; style=&quot;border-bottom: 1px solid rgb(221, 221, 221); font-size: 1.6em; font-weight: bold; margin: 1.3em 0px 1em; padding: 0px;&quot;&gt;Vespa 與 MRL&lt;/h1&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;&lt;img src=&quot;https://aniketrege.github.io/assets/img/blog/mrl-method.png&quot; width=&quot;80%&quot; /&gt;&lt;/p&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;那麼回到 Vespa 的使用情境，MRL 可以帶來什麼效果呢？首先如同 &lt;a href=&quot;https://aniketrege.github.io/blog/2024/mrl/&quot; title=&quot;Matryoshka Representation Learning (MRL) from the Ground Up&quot;&gt;MRL 文章&lt;/a&gt;在上圖中提到的，MRL 會很適合用在 multi-phase ranking 的場合，例如先使用低維度來找出較符合結果的 candidates，然後第二階段時再針對這些 candidates 使用高維度來做更精準的計算，找出最佳的結果。&lt;/p&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;在 &lt;a href=&quot;https://blog.vespa.ai/matryoshka-embeddings-in-vespa/&quot; title=&quot;Exploring the potential of OpenAI Matryoshka 🪆 embeddings with Vespa&quot;&gt;Vespa 二月份的文章&lt;/a&gt;中，首先提到他們做了一點基本的實驗，發現 MRL 產生的 Embedding 在使用相同模型的情況下，產生 8-dimension 跟 3072-dimension 的 Embedding，確實中間只差了 scale 而已，本質上非常相似。接著他們實際實驗了把 3072-dimension 的 Embedding，裁掉尾巴並只留下最前面的 8-dimension，然後去跟一開始就用 8-dimension 產生的 Embedding 做 cosine similarity 比較，發現兩者的相似度高達 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;99.99999999999996%&lt;/code&gt; 。&lt;/p&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;最後，Vespa 團隊用 COVID-19 的資料集做了實際的搜尋實驗，分別有以下幾種：&lt;/p&gt;
&lt;ul style=&quot;margin: 1.2em 0px; padding-left: 2em;&quot;&gt;
&lt;li style=&quot;margin: 0.5em 0px;&quot;&gt;query_exact - 使用完整的 3072-dimension 並且不使用 ANN，也就是做最精準的語義搜尋。&lt;/li&gt;
&lt;li style=&quot;margin: 0.5em 0px;&quot;&gt;query_256 - 改為使用 256-dimension 的低維度 Embedding，同樣也不使用 ANN。&lt;/li&gt;
&lt;li style=&quot;margin: 0.5em 0px;&quot;&gt;query_256_ann - 使用 256-dimension 的低維度 Embedding，同時使用 ANN 做語義搜尋。&lt;/li&gt;
&lt;li style=&quot;margin: 0.5em 0px;&quot;&gt;query_rerank - 使用 256-dimension 的 Embedding 作為 1st-phase，選出 top 100，然後用 3072-dimension 作為 2nd-phase&lt;br /&gt;結果如下：&lt;/li&gt;
&lt;/ul&gt;
&lt;pre style=&quot;font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; font-size: 1em; line-height: 1.2em; margin: 1.2em 0px;&quot;&gt;&lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border-radius: 3px; border: 1px solid rgb(204, 204, 204); border: 1px solid rgb(234, 234, 234); display: block !important; display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; overflow: auto; padding: 0.5em 0.7em; padding: 0px 0.3em; white-space: pre-wrap; white-space: pre;&quot;&gt;run query_exact
.................................................. avg query time 2.7918 s
run query_256
.................................................. avg query time 0.3040 s
run query_256_ann
.................................................. avg query time 0.0252 s
run query_rerank
.................................................. avg query time 0.0310 s

query_exact     ndcg_cut_10: 0.7870
query_256     ndcg_cut_10: 0.7574
query_256_ann     ndcg_cut_10: 0.7552
query_rerank     ndcg_cut_10: 0.7886
&lt;/code&gt;&lt;/pre&gt;&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;先看效能的部份，從 3072-dimensions 改為使用 256-dimensions 可以大幅加快搜尋速度，query time 只有原本的大約 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;11%&lt;/code&gt; 而已。而使用 ANN 的話，跟最開始的 query_exact 比較，甚至是只用了不到 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;1%&lt;/code&gt; 的時間！但效能提昇了，搜尋結果變差了多少呢？從 NDCG 來看，差異從 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;0.787&lt;/code&gt; 下降到 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;0.755&lt;/code&gt; ，其實差異並沒有非常顯著。&lt;/p&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;如果我們直接比較 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;query_exact&lt;/code&gt; 跟 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;query_rerank&lt;/code&gt; 的話，得到的結果是 query time 是原本的 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;1.1%&lt;/code&gt; ，而 NDCG 幾乎一樣！從數據來看，MRL 的潛力真的相當大 😍&lt;/p&gt;
&lt;div style=&quot;font-size: 0em; height: 0; margin: 0; max-height: 0; max-width: 0; overflow: hidden; padding: 0; width: 0;&quot; title=&quot;MDH:PHA+6YCZ5bm+5aSp57aT55SxIFZlc3BhIOeahOaWh+eroO+8jOiugOWIsCBPcGVuQUkg5Zyo5LuK
5bm077yIMjAyNCDlubTvvInkuozmnIjku73nmoTkuIDnr4flnKjop6PoqqogW01STCAoTWF0cnlv
c2hrYSBSZXByZXNlbnRhdGlvbiBMZWFybmluZykg55qE5paH56ugXVttcmxd77yM55yL6LW35L6G
55yf55qE55u455W25pyJ6Laj77yB5LiN6YGO5oiR5Lim5LiN5piv6YCZ5YCL6aCY5Z+f55qE5bCI
5qWt77yM5omA5Lul57Sw6YOo55qE5YWn5a655oiR5Lmf5rKS5pyJ5a6M5YWo55CG6Kej77yM6YCZ
56+H562G6KiY5aSn6auU5Y+q5piv5oiR6Ieq5bex5Zyo5oeJ55So5LiK55qE5Z+65pys55CG6Kej
44CC5aaC5p6c5YWn5a655pyJ6Kqk5Lmf5q2h6L+O55WZ6KiA5o+Q6YaS44CC7aC97biHPGJyPjxi
cj4jIE1STCDmmK/ku4DpurzvvJ88YnI+PGJyPk1hdHJ5b3Noa2EgZG9sbCDlsLHmmK/kv4TnvoXm
lq/lqIPlqIPvvIzogIwmbmJzcDtNYXRyeW9zaGthIFJlcHJlc2VudGF0aW9uIExlYXJuaW5nIOWw
seaYr+S4gOeoruWIqeeUqOmhnuS8vOS/hOe+heaWr+Wog+Wog+eahOeJueaAp++8iOWwj+eahOWo
g+Wog+WPr+S7peiiq+ijnemAsuWkp+eahOWog+Wog+ijoe+8ieWBmuWHuuS+hueahCBFbWJlZGRp
bmcg5pa55rOV77yM5oiW6ICF6Kqq5piv5p2x6KW/55qE6KGo6YGU5omL5rOV44CCPGJyPjxicj7p
gqPpurwgTVJMIOi3n+WFtuS7liBFbWJlZGRpbmcg5pa55rOV5pyA5aSn55qE5beu55Ww5piv5LuA
6bq85ZGi77yf5bCx5oiR55uu5YmN55qE55CG6Kej77yM5pyA5aSn55qE5beu55Ww5bCx5piv5LiK
6L+w5o+Q5Yiw55qE77yM5a6D5YW35pyJ5L+E576F5pav5aiD5aiD55qE54m55oCn44CC5YW36auU
5L6G6Kqq77yM5bCx5piv5bCP55qEIEVtYmVkZGluZyDlj6/ku6XooqvmlL7lnKjlpKfnmoQgRW1i
ZWRkaW5nIOijoe+8jOaIluiAheiqquaYr+Wkp+eahCBFbWJlZGRpbmcg5YiH5Ymy5o6J5bC+5be0
5b6M77yM5bCx5bm+5LmO562J5pa85piv5bCP55qEIEVtYmVkZGluZ+OAgumAmeWAi+eJueaAp+ac
gOS4u+imgeeahOWEquWLou+8jOaYr+WcqOaWvOaIkeWAkeWPr+S7peS4jeeUqOmHneWwjeS4jeWQ
jOe2reW6pueahCBFbWJlZGRpbmcg5YiG5Yil5YSy5a2Y5LiN5ZCM55qEIEVtYmVkZGluZ++8jOiA
jOaYr+WPr+S7pee1seS4gOWtmOS4gOWAiyBFbWJlZGRpbmfvvIzlnKjpnIDopoHpq5jntq3nmoTm
mYLlgJnkvb/nlKggRW1iZWRkaW5nIOijoeeahOavlOi8g+WkmuWFp+WuueOAgemcgOimgeS9jue2
re+8iOabtOW/q+abtOS9juaIkOacrO+8ieeahOaZguWAmeWJh+S9v+eUqCBFbWJlZGRpbmcg6KOh
55qE5LiA6YOo5Lu95YWn5a655Y2z5Y+v44CCPGJyPjxicj4hW+S/hOe+heaWr+Wog+Wog10oaHR0
cHM6Ly9tZWRpYTAuZ2lwaHkuY29tL21lZGlhL3YxLlkybGtQVGM1TUdJM05qRXhkWE5zY0hVemFq
TTNlalZoYmpSNWRYSndjekU0TjJZNGEyOHdjM3BsTlc5dWNqUm1OMjVqWnlabGNEMTJNVjlwYm5S
bGNtNWhiRjluYVdaZllubGZhV1FtWTNROVp3L250aG9ZZ1E5MVVwMnU3cW1jRS9naXBoeS5naWYp
PGJyPjxicj4jIFZlc3BhIOiIhyBNUkw8YnI+PGJyPiZsdDtpbWcgc3JjPSJodHRwczovL2FuaWtl
dHJlZ2UuZ2l0aHViLmlvL2Fzc2V0cy9pbWcvYmxvZy9tcmwtbWV0aG9kLnBuZyIgd2lkdGg9Ijgw
JSIgLyZndDs8L3A+PHA+PGJyPumCo+m6vOWbnuWIsCBWZXNwYSDnmoTkvb/nlKjmg4XlooPvvIxN
Ukwg5Y+v5Lul5bi25L6G5LuA6bq85pWI5p6c5ZGi77yf6aaW5YWI5aaC5ZCMIFtNUkwg5paH56ug
XVttcmxd5Zyo5LiK5ZyW5Lit5o+Q5Yiw55qE77yMTVJMIOacg+W+iOmBqeWQiOeUqOWcqCBtdWx0
aS1waGFzZSByYW5raW5nIOeahOWgtOWQiO+8jOS+i+WmguWFiOS9v+eUqOS9jue2reW6puS+huaJ
vuWHuui8g+espuWQiOe1kOaenOeahCBjYW5kaWRhdGVz77yM54S25b6M56ys5LqM6ZqO5q615pmC
5YaN6Yed5bCN6YCZ5LqbIGNhbmRpZGF0ZXMg5L2/55So6auY57at5bqm5L6G5YGa5pu057K+5rqW
55qE6KiI566X77yM5om+5Ye65pyA5L2z55qE57WQ5p6c44CCPGJyPjxicj7lnKggW1Zlc3BhIOS6
jOaciOS7veeahOaWh+eroF1bdmVzcGEtbXJsXeS4re+8jOmmluWFiOaPkOWIsOS7luWAkeWBmuS6
huS4gOm7nuWfuuacrOeahOWvpumpl++8jOeZvOePviBNUkwg55Si55Sf55qEIEVtYmVkZGluZyDl
nKjkvb/nlKjnm7jlkIzmqKHlnovnmoTmg4Xms4HkuIvvvIznlKLnlJ8gOC1kaW1lbnNpb24g6Lef
IDMwNzItZGltZW5zaW9uIOeahCBFbWJlZGRpbmfvvIznorrlr6bkuK3plpPlj6rlt67kuoYgc2Nh
bGUg6ICM5bey77yM5pys6LOq5LiK6Z2e5bi455u45Ly844CC5o6l6JGX5LuW5YCR5a+m6Zqb5a+m
6amX5LqG5oqKIDMwNzItZGltZW5zaW9uIOeahCBFbWJlZGRpbmfvvIzoo4HmjonlsL7lt7TkuKbl
j6rnlZnkuIvmnIDliY3pnaLnmoQgOC1kaW1lbnNpb27vvIznhLblvozljrvot5/kuIDplovlp4vl
sLHnlKggOC1kaW1lbnNpb24g55Si55Sf55qEIEVtYmVkZGluZyDlgZogY29zaW5lIHNpbWlsYXJp
dHkg5q+U6LyD77yM55m854++5YWp6ICF55qE55u45Ly85bqm6auY6YGUIGA5OS45OTk5OTk5OTk5
OTk5NiVgIOOAgjxicj48YnI+5pyA5b6M77yMVmVzcGEg5ZyY6ZqK55SoIENPVklELTE5IOeahOiz
h+aWmembhuWBmuS6huWvpumam+eahOaQnOWwi+Wvpumpl++8jOWIhuWIpeacieS7peS4i+W5vueo
ru+8mjxicj4qJm5ic3A7cXVlcnlfZXhhY3QgLSDkvb/nlKjlrozmlbTnmoQgMzA3Mi1kaW1lbnNp
b24g5Lim5LiU5LiN5L2/55SoIEFOTu+8jOS5n+WwseaYr+WBmuacgOeyvua6lueahOiqnue+qeaQ
nOWwi+OAgjxicj4qJm5ic3A7cXVlcnlfMjU2IC0g5pS554K65L2/55SoIDI1Ni1kaW1lbnNpb24g
55qE5L2O57at5bqmIEVtYmVkZGluZ++8jOWQjOaoo+S5n+S4jeS9v+eUqCBBTk7jgII8YnI+KiBx
dWVyeV8yNTZfYW5uIC0g5L2/55SoIDI1Ni1kaW1lbnNpb24g55qE5L2O57at5bqmIEVtYmVkZGlu
Z++8jOWQjOaZguS9v+eUqCBBTk4g5YGa6Kqe576p5pCc5bCL44CCPGJyPiogcXVlcnlfcmVyYW5r
IC0g5L2/55SoIDI1Ni1kaW1lbnNpb24g55qEIEVtYmVkZGluZyDkvZzngrogMXN0LXBoYXNl77yM
6YG45Ye6IHRvcCAxMDDvvIznhLblvoznlKggMzA3Mi1kaW1lbnNpb24g5L2c54K6IDJuZC1waGFz
ZTxicj7ntZDmnpzlpoLkuIvvvJo8YnI+PGJyPmBgYDxicj5ydW4gcXVlcnlfZXhhY3Q8YnI+Li4u
Li4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4gYXZnIHF1ZXJ5
IHRpbWUgMi43OTE4IHM8YnI+cnVuIHF1ZXJ5XzI1Njxicj4uLi4uLi4uLi4uLi4uLi4uLi4uLi4u
Li4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLiBhdmcgcXVlcnkgdGltZSAwLjMwNDAgczxicj5y
dW4gcXVlcnlfMjU2X2Fubjxicj4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4u
Li4uLi4uLi4uLi4uLiBhdmcgcXVlcnkgdGltZSAwLjAyNTIgczxicj5ydW4gcXVlcnlfcmVyYW5r
PGJyPi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uIGF2
ZyBxdWVyeSB0aW1lIDAuMDMxMCBzPGJyPjxicj5xdWVyeV9leGFjdCA8Zm9udCBkYXRhLWtlZXAt
b3JpZ2luYWwtdGFnPSJmYWxzZSIgZGF0YS1vcmlnaW5hbC1hdHRycz0ieyZxdW90O3N0eWxlJnF1
b3Q7OiZxdW90OyZxdW90O30iIHN0eWxlPSJ3aGl0ZS1zcGFjZTogcHJlOyI+CTwvZm9udD5uZGNn
X2N1dF8xMDogMC43ODcwPGJyPnF1ZXJ5XzI1NiA8Zm9udCBkYXRhLWtlZXAtb3JpZ2luYWwtdGFn
PSJmYWxzZSIgZGF0YS1vcmlnaW5hbC1hdHRycz0ieyZxdW90O3N0eWxlJnF1b3Q7OiZxdW90OyZx
dW90O30iIHN0eWxlPSJ3aGl0ZS1zcGFjZTogcHJlOyI+CTwvZm9udD5uZGNnX2N1dF8xMDogMC43
NTc0PGJyPnF1ZXJ5XzI1Nl9hbm4gPGZvbnQgZGF0YS1rZWVwLW9yaWdpbmFsLXRhZz0iZmFsc2Ui
IGRhdGEtb3JpZ2luYWwtYXR0cnM9InsmcXVvdDtzdHlsZSZxdW90OzomcXVvdDsmcXVvdDt9IiBz
dHlsZT0id2hpdGUtc3BhY2U6IHByZTsiPgk8L2ZvbnQ+bmRjZ19jdXRfMTA6IDAuNzU1Mjxicj5x
dWVyeV9yZXJhbmsgPGZvbnQgZGF0YS1rZWVwLW9yaWdpbmFsLXRhZz0iZmFsc2UiIGRhdGEtb3Jp
Z2luYWwtYXR0cnM9InsmcXVvdDtzdHlsZSZxdW90OzomcXVvdDsmcXVvdDt9IiBzdHlsZT0id2hp
dGUtc3BhY2U6IHByZTsiPgk8L2ZvbnQ+bmRjZ19jdXRfMTA6IDAuNzg4Njxicj5gYGA8YnI+PGJy
PuWFiOeci+aViOiDveeahOmDqOS7ve+8jOW+niAzMDcyLWRpbWVuc2lvbnMg5pS554K65L2/55So
IDI1Ni1kaW1lbnNpb25zIOWPr+S7peWkp+W5heWKoOW/q+aQnOWwi+mAn+W6pu+8jHF1ZXJ5IHRp
bWUg5Y+q5pyJ5Y6f5pys55qE5aSn57SEIGAxMSVgIOiAjOW3suOAguiAjOS9v+eUqCBBTk4g55qE
6Kmx77yM6Lef5pyA6ZaL5aeL55qEIHF1ZXJ5X2V4YWN0IOavlOi8g++8jOeUmuiHs+aYr+WPqueU
qOS6huS4jeWIsCBgMSVgIOeahOaZgumWk++8geS9huaViOiDveaPkOaYh+S6hu+8jOaQnOWwi+e1
kOaenOiuiuW3ruS6huWkmuWwkeWRou+8n+W+niBORENHIOS+hueci++8jOW3rueVsOW+niBgMC43
ODdgIOS4i+mZjeWIsCBgMC43NTVgIO+8jOWFtuWvpuW3rueVsOS4puaykuaciemdnuW4uOmhr+iR
l+OAgjxicj48YnI+5aaC5p6c5oiR5YCR55u05o6l5q+U6LyDIGBxdWVyeV9leGFjdGAg6LefIGBx
dWVyeV9yZXJhbmtgIOeahOipse+8jOW+l+WIsOeahOe1kOaenOaYryBxdWVyeSB0aW1lIOaYr+WO
n+acrOeahCBgMS4xJWAg77yM6ICMIE5EQ0cg5bm+5LmO5LiA5qij77yB5b6e5pW45pOa5L6G55yL
77yMTVJMIOeahOa9m+WKm+ecn+eahOebuOeVtuWkpyDtoL3tuI08YnI+PGJyPlttcmxdOiZuYnNw
O2h0dHBzOi8vYW5pa2V0cmVnZS5naXRodWIuaW8vYmxvZy8yMDI0L21ybC8gIk1hdHJ5b3Noa2Eg
UmVwcmVzZW50YXRpb24gTGVhcm5pbmcgKE1STCkgZnJvbSB0aGUgR3JvdW5kIFVwIjxicj5bdmVz
cGEtbXJsXTombmJzcDtodHRwczovL2Jsb2cudmVzcGEuYWkvbWF0cnlvc2hrYS1lbWJlZGRpbmdz
LWluLXZlc3BhLyAiRXhwbG9yaW5nIHRoZSBwb3RlbnRpYWwgb2YgT3BlbkFJIE1hdHJ5b3Noa2Eg
7aC+7bqGIGVtYmVkZGluZ3Mgd2l0aCBWZXNwYSI8L3A+&quot;&gt;​&lt;/div&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimwayne.blogspot.com/feeds/8336070158343683284/comments/default' title='張貼留言'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/6819483672105967231/8336070158343683284' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6819483672105967231/posts/default/8336070158343683284'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6819483672105967231/posts/default/8336070158343683284'/><link rel='alternate' type='text/html' href='http://jimwayne.blogspot.com/2024/05/matryoshka-representation-learning-mrl.html' title='Matryoshka Representation Learning (MRL) 與 Vespa'/><author><name>Wayne Yeh</name><uri>http://www.blogger.com/profile/02148905864753873930</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6819483672105967231.post-6750300674940196454</id><published>2024-03-18T23:03:00.001+08:00</published><updated>2024-03-18T23:03:48.345+08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="AI"/><category scheme="http://www.blogger.com/atom/ns#" term="Markdown Here"/><category scheme="http://www.blogger.com/atom/ns#" term="Stable Diffusion"/><title type='text'>Stable Diffusion 加速</title><content type='html'>&lt;div class=&quot;markdown-here-wrapper&quot; data-md-url=&quot;https://draft.blogger.com/blog/post/edit/6819483672105967231/6750300674940196454&quot;&gt;&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;簡要紀錄一下目前測試過的 Stable Diffusion 加速方法。之後如果有再看到其他通用的作法，也會視情況再紀錄過來這篇文章。&lt;/p&gt;
&lt;ul style=&quot;margin: 1.2em 0px; padding-left: 2em;&quot;&gt;
&lt;li style=&quot;margin: 0.5em 0px;&quot;&gt;xformer&lt;/li&gt;
&lt;li style=&quot;margin: 0.5em 0px;&quot;&gt;cuDNN&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;目前這裡紀錄的時間，會用以下的環境設定來測試時間。至於為什麼用這個呢？其實沒什麼原因，就隨便找一個 XD。&lt;/p&gt;
&lt;ul style=&quot;margin: 1.2em 0px; padding-left: 2em;&quot;&gt;
&lt;li style=&quot;margin: 0.5em 0px;&quot;&gt;Windows 11&lt;/li&gt;
&lt;li style=&quot;margin: 0.5em 0px;&quot;&gt;Stable Diffusion webui 1.8.0&lt;/li&gt;
&lt;li style=&quot;margin: 0.5em 0px;&quot;&gt;checkpoint 模型：&lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;Mistoon_Anime v3.0&lt;/code&gt;&lt;/li&gt;
&lt;li style=&quot;margin: 0.5em 0px;&quot;&gt;正向提示詞：&lt;pre style=&quot;font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; font-size: 1em; line-height: 1.2em; margin: 1.2em 0px;&quot;&gt;&lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border-radius: 3px; border: 1px solid rgb(204, 204, 204); border: 1px solid rgb(234, 234, 234); display: block !important; display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; overflow: auto; padding: 0.5em 0.7em; padding: 0px 0.3em; white-space: pre-wrap; white-space: pre;&quot;&gt;masterpiece,best quality,1girl,solo,girls und panzer,messy hair,smallbreasts,brown hair,long hair,orange eyes,side pony tail,beach,swimsuit,swim suit,smug,full body,
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li style=&quot;margin: 0.5em 0px;&quot;&gt;反向提示詞：&lt;pre style=&quot;font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; font-size: 1em; line-height: 1.2em; margin: 1.2em 0px;&quot;&gt;&lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border-radius: 3px; border: 1px solid rgb(204, 204, 204); border: 1px solid rgb(234, 234, 234); display: block !important; display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; overflow: auto; padding: 0.5em 0.7em; padding: 0px 0.3em; white-space: pre-wrap; white-space: pre;&quot;&gt;EasyNegative,(worst quality:1.4),(low quality:1.4),
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li style=&quot;margin: 0.5em 0px;&quot;&gt;batch size：4&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id=&quot;-xformer&quot; style=&quot;border-bottom: 1px solid rgb(221, 221, 221); font-size: 1.6em; font-weight: bold; margin: 1.3em 0px 1em; padding: 0px;&quot;&gt;啟用 xformer&lt;/h1&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;這個選項目前還不太清楚傷害是什麼，似乎基本是無害的，不過有看到像是 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;openpose&lt;/code&gt; 會建議記憶體較低的話可以開，有可能實際上是對成品有點傷害。但對 Stable Diffusion 生圖的效能影響很大，所以如果手上的顯卡不是那麼強大的話，推薦可以考慮。具體來說，我自己的簡單測試結果是啟用後，生圖的時間大約減少了 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;48%&lt;/code&gt;（&lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;33.2s&lt;/code&gt; -&amp;gt; &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;17.3s&lt;/code&gt;），也就是減少將近一半！&lt;/p&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;啟用方法在現在很簡單，因為 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;xformer&lt;/code&gt; 已經變成 Stable Diffusion webui 的內建套件了，只是預設沒有啟用而已，所以只需要在 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;webui-user.bat&lt;/code&gt; 裡的 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;call webui.bat&lt;/code&gt; 後面加上 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;--xformers&lt;/code&gt; 就可以了。詳細的說明可以參考 Stable Diffusion webui 的 wiki 中關於 &lt;a href=&quot;https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Xformers&quot;&gt;xformer&lt;/a&gt; 的說明。&lt;/p&gt;
&lt;pre style=&quot;font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; font-size: 1em; line-height: 1.2em; margin: 1.2em 0px;&quot;&gt;&lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border-radius: 3px; border: 1px solid rgb(204, 204, 204); border: 1px solid rgb(234, 234, 234); display: block !important; display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; overflow: auto; padding: 0.5em 0.7em; padding: 0px 0.3em; white-space: pre-wrap; white-space: pre;&quot;&gt;call webui.bat --xformers
&lt;/code&gt;&lt;/pre&gt;&lt;h1 id=&quot;-cudnn&quot; style=&quot;border-bottom: 1px solid rgb(221, 221, 221); font-size: 1.6em; font-weight: bold; margin: 1.3em 0px 1em; padding: 0px;&quot;&gt;安裝 cuDNN&lt;/h1&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;這個部份可以參考 &lt;a href=&quot;https://www.reddit.com/r/StableDiffusion/comments/y71q5k/4090_cudnn_performancespeed_fix_automatic1111/?rdt=44330&quot;&gt;reddit&lt;/a&gt; 的討論，不過我自己實際測試的結果，cuDNN 有沒有裝好像沒有差別，cuDNN 單獨測試、跟同時啟用 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;xformer&lt;/code&gt; 都有測過，但結果都沒有顯著差異。測試出來的數據如下：&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; border-spacing: 0px; border: 0px; font: inherit; margin: 1.2em 0px; padding: 0px;&quot;&gt;
&lt;thead&gt;
&lt;tr style=&quot;background-color: white; border-bottom-color: initial; border-bottom-style: initial; border-image: initial; border-left-color: initial; border-left-style: initial; border-right-color: initial; border-right-style: initial; border-top-color: rgb(204, 204, 204); border-top-style: solid; border-width: 1px 0px 0px; margin: 0px; padding: 0px;&quot;&gt;
&lt;th style=&quot;background-color: #f0f0f0; border: 1px solid rgb(204, 204, 204); font-size: 1em; font-weight: bold; margin: 0px; padding: 0.5em 1em;&quot;&gt;features&lt;/th&gt;
&lt;th style=&quot;background-color: #f0f0f0; border: 1px solid rgb(204, 204, 204); font-size: 1em; font-weight: bold; margin: 0px; padding: 0.5em 1em;&quot;&gt;time&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody style=&quot;border: 0px; margin: 0px; padding: 0px;&quot;&gt;
&lt;tr style=&quot;background-color: white; border-bottom-color: initial; border-bottom-style: initial; border-image: initial; border-left-color: initial; border-left-style: initial; border-right-color: initial; border-right-style: initial; border-top-color: rgb(204, 204, 204); border-top-style: solid; border-width: 1px 0px 0px; margin: 0px; padding: 0px;&quot;&gt;
&lt;td style=&quot;border: 1px solid rgb(204, 204, 204); font-size: 1em; margin: 0px; padding: 0.5em 1em;&quot;&gt;vanilla&lt;/td&gt;
&lt;td style=&quot;border: 1px solid rgb(204, 204, 204); font-size: 1em; margin: 0px; padding: 0.5em 1em;&quot;&gt;33.2s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background-color: #f8f8f8; background-color: white; border-bottom-color: initial; border-bottom-style: initial; border-image: initial; border-left-color: initial; border-left-style: initial; border-right-color: initial; border-right-style: initial; border-top-color: rgb(204, 204, 204); border-top-style: solid; border-width: 1px 0px 0px; margin: 0px; padding: 0px;&quot;&gt;
&lt;td style=&quot;border: 1px solid rgb(204, 204, 204); font-size: 1em; margin: 0px; padding: 0.5em 1em;&quot;&gt;cudnn&lt;/td&gt;
&lt;td style=&quot;border: 1px solid rgb(204, 204, 204); font-size: 1em; margin: 0px; padding: 0.5em 1em;&quot;&gt;33.2s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background-color: white; border-bottom-color: initial; border-bottom-style: initial; border-image: initial; border-left-color: initial; border-left-style: initial; border-right-color: initial; border-right-style: initial; border-top-color: rgb(204, 204, 204); border-top-style: solid; border-width: 1px 0px 0px; margin: 0px; padding: 0px;&quot;&gt;
&lt;td style=&quot;border: 1px solid rgb(204, 204, 204); font-size: 1em; margin: 0px; padding: 0.5em 1em;&quot;&gt;xformer&lt;/td&gt;
&lt;td style=&quot;border: 1px solid rgb(204, 204, 204); font-size: 1em; margin: 0px; padding: 0.5em 1em;&quot;&gt;17.3s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background-color: #f8f8f8; background-color: white; border-bottom-color: initial; border-bottom-style: initial; border-image: initial; border-left-color: initial; border-left-style: initial; border-right-color: initial; border-right-style: initial; border-top-color: rgb(204, 204, 204); border-top-style: solid; border-width: 1px 0px 0px; margin: 0px; padding: 0px;&quot;&gt;
&lt;td style=&quot;border: 1px solid rgb(204, 204, 204); font-size: 1em; margin: 0px; padding: 0.5em 1em;&quot;&gt;cudnn + xformer&lt;/td&gt;
&lt;td style=&quot;border: 1px solid rgb(204, 204, 204); font-size: 1em; margin: 0px; padding: 0.5em 1em;&quot;&gt;17.6s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;不過實際上這好像是要去更新 Stable Diffusion 裡的 PyTorch 使用到的 cuDNN 函式庫，因為裡面本來就已經有那 7 個 DLL 檔案了，安裝過程是要把下載下來的 cuDNN 裡的 DLL 覆蓋進去 PyTorch 的資料夾。所以也有可能純粹是因為我用 Stable Diffusion 1.8.0，裡面的 PyTorch 已經更新了它的 cuDNN，所以我測起來才測不出差別。&lt;/p&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;安裝方法的部份，由於這裡安裝的目的是要給 Stable Diffusion 使用，所以 nVidia 現在有提供的 Windows installer 好像是沒什麼用，因為它會把那些 DLL 裝進 Program Files 裡 XD。還有一點是 webui 1.8.0 的 PyTorch 使用的 cuDNN 似乎是 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;8.x&lt;/code&gt; 版，所以要注意不要下載到最新的 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;9.0&lt;/code&gt; 版，所以推薦是直接去官方的 &lt;a href=&quot;https://developer.download.nvidia.com/compute/cudnn/redist/cudnn/windows-x86_64/&quot;&gt;archive&lt;/a&gt; 頁面下載，在我下載的這個時候，&lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;8.x&lt;/code&gt; 版中的最新版是 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;8.9.7.29&lt;/code&gt;。如果下載錯的話，要把檔案複製到 PyTorch 裡的時候，會發現所有檔案都是新增進去，沒有覆蓋到任何一個檔案，這樣它就不會真的被使用到。下載完以後，把資料夾裡 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;\bin&lt;/code&gt; 裡的總共 7 個 DLL 檔案全部複製到 Stable Diffusion webui 裡的 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;\venv\Lib\site-packages\torch\lib&lt;/code&gt; 並取代裡面同樣檔名的檔案即可。&lt;/p&gt;
&lt;div style=&quot;font-size: 0em; height: 0; margin: 0; max-height: 0; max-width: 0; overflow: hidden; padding: 0; width: 0;&quot; title=&quot;MDH:PGRpdj7nsKHopoHntIDpjITkuIDkuIvnm67liY3muKzoqabpgY7nmoQgU3RhYmxlIERpZmZ1c2lv
biDliqDpgJ/mlrnms5XjgILkuYvlvozlpoLmnpzmnInlho3nnIvliLDlhbbku5bpgJrnlKjnmoTk
vZzms5XvvIzkuZ/mnIPoppbmg4Xms4Hlho3ntIDpjITpgY7kvobpgJnnr4fmlofnq6DjgII8L2Rp
dj48ZGl2Pjxicj48L2Rpdj48ZGl2PiogeGZvcm1lcjwvZGl2PjxkaXY+KiBjdUROTjwvZGl2Pjxk
aXY+PGJyPjwvZGl2PjxkaXY+55uu5YmN6YCZ6KOh57SA6YyE55qE5pmC6ZaT77yM5pyD55So5Lul
5LiL55qE55Kw5aKD6Kit5a6a5L6G5ris6Kmm5pmC6ZaT44CC6Iez5pa854K65LuA6bq855So6YCZ
5YCL5ZGi77yf5YW25a+m5rKS5LuA6bq85Y6f5Zug77yM5bCx6Zqo5L6/5om+5LiA5YCLIFhE44CC
PC9kaXY+PGRpdj4qIFdpbmRvd3MgMTE8L2Rpdj48ZGl2PiogU3RhYmxlIERpZmZ1c2lvbiB3ZWJ1
aSAxLjguMDwvZGl2PjxkaXY+KiBjaGVja3BvaW50IOaooeWei++8mmBNaXN0b29uX0FuaW1lIHYz
LjBgPC9kaXY+PGRpdj4qIOato+WQkeaPkOekuuipnu+8mjwvZGl2PjxkaXY+Jm5ic3A7IGBgYDwv
ZGl2PjxkaXY+Jm5ic3A7IG1hc3RlcnBpZWNlLGJlc3QgcXVhbGl0eSwxZ2lybCxzb2xvLGdpcmxz
IHVuZCBwYW56ZXIsbWVzc3kgaGFpcixzbWFsbGJyZWFzdHMsYnJvd24gaGFpcixsb25nIGhhaXIs
b3JhbmdlIGV5ZXMsc2lkZSBwb255IHRhaWwsYmVhY2gsc3dpbXN1aXQsc3dpbSBzdWl0LHNtdWcs
ZnVsbCBib2R5LDwvZGl2PjxkaXY+Jm5ic3A7IGBgYDwvZGl2PjxkaXY+KiDlj43lkJHmj5DnpLro
qZ7vvJo8L2Rpdj48ZGl2PiZuYnNwOyBgYGA8L2Rpdj48ZGl2PiZuYnNwOyBFYXN5TmVnYXRpdmUs
KHdvcnN0IHF1YWxpdHk6MS40KSwobG93IHF1YWxpdHk6MS40KSw8L2Rpdj48ZGl2PiZuYnNwOyBg
YGA8L2Rpdj48ZGl2PiogYmF0Y2ggc2l6Ze+8mjQ8L2Rpdj48ZGl2Pjxicj48L2Rpdj48ZGl2PiMg
5ZWf55SoIHhmb3JtZXI8L2Rpdj48ZGl2Pjxicj48L2Rpdj48ZGl2PumAmeWAi+mBuOmgheebruWJ
jemChOS4jeWkqua4healmuWCt+Wus+aYr+S7gOm6vO+8jOS8vOS5juWfuuacrOaYr+eEoeWus+ea
hO+8jOS4jemBjuacieeci+WIsOWDj+aYryBgb3BlbnBvc2VgIOacg+W7uuitsOiomOaGtumrlOi8
g+S9jueahOipseWPr+S7pemWi++8jOacieWPr+iDveWvpumam+S4iuaYr+WwjeaIkOWTgeaciem7
nuWCt+Wus+OAguS9huWwjSBTdGFibGUgRGlmZnVzaW9uIOeUn+WclueahOaViOiDveW9semfv+W+
iOWkp++8jOaJgOS7peWmguaenOaJi+S4iueahOmhr+WNoeS4jeaYr+mCo+m6vOW8t+Wkp+eahOip
se+8jOaOqOiWpuWPr+S7peiAg+aFruOAguWFt+mrlOS+huiqqu+8jOaIkeiHquW3seeahOewoeWW
rua4rOippue1kOaenOaYr+WVn+eUqOW+jO+8jOeUn+WclueahOaZgumWk+Wkp+e0hOa4m+WwkeS6
hiBgNDglYO+8iGAzMy4yc2AgLSZndDsgYDE3LjNzYO+8ie+8jOS5n+WwseaYr+a4m+WwkeWwh+i/
keS4gOWNiu+8gTwvZGl2PjxkaXY+PGJyPjwvZGl2PjxkaXY+5ZWf55So5pa55rOV5Zyo54++5Zyo
5b6I57Ch5Zau77yM5Zug54K6IGB4Zm9ybWVyYCDlt7LntpPorormiJAgU3RhYmxlIERpZmZ1c2lv
biB3ZWJ1aSDnmoTlhaflu7rlpZfku7bkuobvvIzlj6rmmK/poJDoqK3mspLmnInllZ/nlKjogIzl
t7LvvIzmiYDku6Xlj6rpnIDopoHlnKggYHdlYnVpLXVzZXIuYmF0YCDoo6HnmoQgYGNhbGwgd2Vi
dWkuYmF0YCDlvozpnaLliqDkuIogYC0teGZvcm1lcnNgIOWwseWPr+S7peS6huOAguips+e0sOea
hOiqquaYjuWPr+S7peWPg+iAgyBTdGFibGUgRGlmZnVzaW9uIHdlYnVpIOeahCB3aWtpIOS4remX
nOaWvCBbeGZvcm1lcl1beGZvcm1lcl0g55qE6Kqq5piO44CCPC9kaXY+PGRpdj48YnI+PC9kaXY+
PGRpdj5gYGA8L2Rpdj48ZGl2PmNhbGwgd2VidWkuYmF0IC0teGZvcm1lcnM8L2Rpdj48ZGl2PmBg
YDwvZGl2PjxkaXY+PGJyPjwvZGl2PjxkaXY+W3hmb3JtZXJdOiBodHRwczovL2dpdGh1Yi5jb20v
QVVUT01BVElDMTExMS9zdGFibGUtZGlmZnVzaW9uLXdlYnVpL3dpa2kvWGZvcm1lcnM8L2Rpdj48
ZGl2Pjxicj48L2Rpdj48ZGl2PiMg5a6J6KOdIGN1RE5OPC9kaXY+PGRpdj48YnI+PC9kaXY+PGRp
dj7pgJnlgIvpg6jku73lj6/ku6Xlj4PogIMgW3JlZGRpdF1bcmVkZGl0LWN1ZG5uXSDnmoToqI7o
q5bvvIzkuI3pgY7miJHoh6rlt7Hlr6bpmpvmuKzoqabnmoTntZDmnpzvvIxjdUROTiDmnInmspLm
nInoo53lpb3lg4/mspLmnInlt67liKXvvIxjdUROTiDllq7njajmuKzoqabjgIHot5/lkIzmmYLl
lZ/nlKggYHhmb3JtZXJgIOmDveaciea4rOmBju+8jOS9hue1kOaenOmDveaykuaciemhr+iRl+W3
rueVsOOAgua4rOippuWHuuS+hueahOaVuOaTmuWmguS4i++8mjwvZGl2PjxkaXY+PGJyPjwvZGl2
PjxkaXY+fCBmZWF0dXJlcyB8IHRpbWUgfDwvZGl2PjxkaXY+fCAtLS0tLS0tIHwgLS0tLS0gfDwv
ZGl2PjxkaXY+fCB2YW5pbGxhIHwgMzMuMnMgfDwvZGl2PjxkaXY+fCBjdWRubiZuYnNwOyAmbmJz
cDt8IDMzLjJzIHw8L2Rpdj48ZGl2PnwgeGZvcm1lciB8IDE3LjNzIHw8L2Rpdj48ZGl2PnwgY3Vk
bm4gKyB4Zm9ybWVyIHwgMTcuNnMgfDwvZGl2PjxkaXY+PGJyPjwvZGl2PjxkaXY+5LiN6YGO5a+m
6Zqb5LiK6YCZ5aW95YOP5piv6KaB5Y675pu05pawIFN0YWJsZSBEaWZmdXNpb24g6KOh55qEIFB5
VG9yY2gg5L2/55So5Yiw55qEIGN1RE5OIOWHveW8j+W6q++8jOWboOeCuuijoemdouacrOS+huWw
seW3sue2k+aciemCoyA3IOWAiyBETEwg5qqU5qGI5LqG77yM5a6J6KOd6YGO56iL5piv6KaB5oqK
5LiL6LyJ5LiL5L6G55qEIGN1RE5OIOijoeeahCBETEwg6KaG6JOL6YCy5Y67IFB5VG9yY2gg55qE
6LOH5paZ5aS+44CC5omA5Lul5Lmf5pyJ5Y+v6IO957SU57K55piv5Zug54K65oiR55SoIFN0YWJs
ZSBEaWZmdXNpb24gMS44LjDvvIzoo6HpnaLnmoQgUHlUb3JjaCDlt7LntpPmm7TmlrDkuoblroPn
moQgY3VETk7vvIzmiYDku6XmiJHmuKzotbfkvobmiY3muKzkuI3lh7rlt67liKXjgII8L2Rpdj48
ZGl2Pjxicj48L2Rpdj48ZGl2PuWuieijneaWueazleeahOmDqOS7ve+8jOeUseaWvOmAmeijoeWu
ieijneeahOebrueahOaYr+imgee1piBTdGFibGUgRGlmZnVzaW9uIOS9v+eUqO+8jOaJgOS7pSBu
VmlkaWEg54++5Zyo5pyJ5o+Q5L6b55qEIFdpbmRvd3MgaW5zdGFsbGVyIOWlveWDj+aYr+aykuS7
gOm6vOeUqO+8jOWboOeCuuWug+acg+aKiumCo+S6myBETEwg6KOd6YCyIFByb2dyYW0gRmlsZXMg
6KOhIFhE44CC6YKE5pyJ5LiA6bue5pivIHdlYnVpIDEuOC4wIOeahCBQeVRvcmNoIOS9v+eUqOea
hCBjdUROTiDkvLzkuY7mmK8gYDgueGAg54mI77yM5omA5Lul6KaB5rOo5oSP5LiN6KaB5LiL6LyJ
5Yiw5pyA5paw55qEIGA5LjBgIOeJiO+8jOaJgOS7peaOqOiWpuaYr+ebtOaOpeWOu+WumOaWueea
hCBbYXJjaGl2ZV1bY3Vkbm4td2luLWFyY2hpdmVdIOmggemdouS4i+i8ie+8jOWcqOaIkeS4i+i8
ieeahOmAmeWAi+aZguWAme+8jGA4LnhgIOeJiOS4reeahOacgOaWsOeJiOaYryBgOC45LjcuMjlg
44CC5aaC5p6c5LiL6LyJ6Yyv55qE6Kmx77yM6KaB5oqK5qqU5qGI6KSH6KO95YiwIFB5VG9yY2gg
6KOh55qE5pmC5YCZ77yM5pyD55m854++5omA5pyJ5qqU5qGI6YO95piv5paw5aKe6YCy5Y6777yM
5rKS5pyJ6KaG6JOL5Yiw5Lu75L2V5LiA5YCL5qqU5qGI77yM6YCZ5qij5a6D5bCx5LiN5pyD55yf
55qE6KKr5L2/55So5Yiw44CC5LiL6LyJ5a6M5Lul5b6M77yM5oqK6LOH5paZ5aS+6KOhIGBcYmlu
YCDoo6HnmoTnuL3lhbEgNyDlgIsgRExMIOaqlOahiOWFqOmDqOikh+ijveWIsCBTdGFibGUgRGlm
ZnVzaW9uIHdlYnVpIOijoeeahCBgXHZlbnZcTGliXHNpdGUtcGFja2FnZXNcdG9yY2hcbGliYCDk
uKblj5bku6Poo6HpnaLlkIzmqKPmqpTlkI3nmoTmqpTmoYjljbPlj6/jgII8L2Rpdj48ZGl2Pjxi
cj48L2Rpdj48ZGl2PltyZWRkaXQtY3Vkbm5dOiBodHRwczovL3d3dy5yZWRkaXQuY29tL3IvU3Rh
YmxlRGlmZnVzaW9uL2NvbW1lbnRzL3k3MXE1ay80MDkwX2N1ZG5uX3BlcmZvcm1hbmNlc3BlZWRf
Zml4X2F1dG9tYXRpYzExMTEvP3JkdD00NDMzMDwvZGl2PjxkaXY+W2N1ZG5uLXdpbi1hcmNoaXZl
XTogaHR0cHM6Ly9kZXZlbG9wZXIuZG93bmxvYWQubnZpZGlhLmNvbS9jb21wdXRlL2N1ZG5uL3Jl
ZGlzdC9jdWRubi93aW5kb3dzLXg4Nl82NC88L2Rpdj48ZGl2Pjxicj48L2Rpdj4=&quot;&gt;​&lt;/div&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimwayne.blogspot.com/feeds/6750300674940196454/comments/default' title='張貼留言'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/6819483672105967231/6750300674940196454' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6819483672105967231/posts/default/6750300674940196454'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6819483672105967231/posts/default/6750300674940196454'/><link rel='alternate' type='text/html' href='http://jimwayne.blogspot.com/2024/03/stable-diffusion_18.html' title='Stable Diffusion 加速'/><author><name>Wayne Yeh</name><uri>http://www.blogger.com/profile/02148905864753873930</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6819483672105967231.post-155000003261130058</id><published>2024-03-07T01:22:00.012+08:00</published><updated>2025-02-03T22:50:05.706+08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="AI"/><category scheme="http://www.blogger.com/atom/ns#" term="Markdown Here"/><category scheme="http://www.blogger.com/atom/ns#" term="Stable Diffusion"/><title type='text'>Stable Diffusion 基本環境安裝</title><content type='html'>&lt;div class=&quot;markdown-here-wrapper&quot; data-md-url=&quot;https://www.blogger.com/blog/post/edit/6819483672105967231/155000003261130058&quot;&gt;&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;講到繪圖 AI，在初期最有名的應該就是 Midjourney (MJ) 和 Stable Diffusion (SD) 了。後來雖然有不少其他名字陸續冒出來，不過最開始的這兩個，還是依然維持著它們的名氣。因為 side project 的關係，想要試試看繪圖 AI，所以就簡單研究了一下這兩個，發現 Stable Diffusion 可以在自己電腦上面跑，不用買訂閱制的服務！所以就決定嘗試它了！&lt;/p&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;說是這麼說，但簡單嘗試後發現一個很大的問題，Stable Diffusion 對非 nVidia 顯示卡的使用者相當不友善 😓。雖然不是完全不能用，但就圖片產生速度非常慢，而且環境準備也很麻煩。因為我的顯示卡是 AMD 的大概七年前的中階顯卡，搜尋了一下發現要用 AMD 算圖的環境設定超麻煩，所以立刻就放棄了 😆。試完了一下用 CPU 算圖，不過使用經驗是很多 model 都會有問題，而能用的又很慢，跑一張圖要跑 10 幾分鐘，所以試玩一下後就決定還是要去買新的顯示卡了。於是這篇就會簡單紀錄一下在 Windows 10 的 nVidia 顯卡以及 Stable Diffusion 的基本準備。&lt;/p&gt;
&lt;h1 id=&quot;-nvidia-&quot; style=&quot;border-bottom: 1px solid rgb(221, 221, 221); font-size: 1.6em; font-weight: bold; margin: 1.3em 0px 1em; padding: 0px;&quot;&gt;準備 NVIDIA 環境&lt;/h1&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;NVIDIA 環境具體來說，就是顯示卡的驅動程式跟 CUDA 相關的東西。這個步驟不算太難，主要要安裝的是以下幾個東西：&lt;/p&gt;
&lt;ul style=&quot;margin: 1.2em 0px; padding-left: 2em;&quot;&gt;
&lt;li style=&quot;margin: 0.5em 0px;&quot;&gt;NVIDIA Geforece Experience（2025 更新：後來改名叫 Nvidia App）&lt;/li&gt;
&lt;li style=&quot;margin: 0.5em 0px;&quot;&gt;CUDA Toolkit&lt;/li&gt;
&lt;li style=&quot;margin: 0.5em 0px;&quot;&gt;NVIDIA cuDNN&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;其中 Geforece Experience 其實本來不是必要的，不過因為顯示卡驅動程式好像都跟在這裡面，所以多半會一起裝進去。至於 cuDNN 真的就是非必要的，我自己的實際實驗是沒裝它也可以正常使用 Stable Diffusion，不過看官方的說明，感覺這個應該是對計算會有加速的效果之類的，所以還是裝一裝會比較好（但我現在還沒裝 😆）。&lt;/p&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;CUDA Toolkit 跟 cuDNN 安裝的版本都應該要參考自己的顯示卡和驅動程式對應的版本，可以分別參考官方網站的 &lt;a href=&quot;https://developer.nvidia.com/cuda-gpus&quot;&gt;CUDA GPU&lt;/a&gt; 和 &lt;a href=&quot;https://docs.nvidia.com/cuda/cuda-toolkit-release-notes/index.html#cuda-toolkit-major-component-versions&quot;&gt;CUDA Driver&lt;/a&gt;。舉例來說，在 CUDA Driver 頁面中有寫到 nVidia 驅動程式對應的 CUDA Driver 版本，所以要去看自己的驅動程式應該對應哪一個版本的 CUDA Driver。我的顯示卡有支援最新版，而在寫這篇文章的時候，CUDA Toolkit 最新版是 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;12.3&lt;/code&gt; (&lt;a href=&quot;https://developer.nvidia.com/cuda-downloads&quot;&gt;最新版的下載位置&lt;/a&gt;) ，不過有個超級重要的經驗是，不要安裝 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;12.3&lt;/code&gt; 版 😆。我試過各種方法都沒辦法成功把 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;12.3&lt;/code&gt; 裝進去，安裝程式會一直顯示安裝失敗，但又不明確提示為什麼失敗。查了 NVIDIA 官方論壇以及 Reddit 的文章後，最後發現改安裝 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;12.2&lt;/code&gt; (&lt;a href=&quot;https://developer.nvidia.com/cuda-12-2-0-download-archive&quot;&gt;下載位置&lt;/a&gt;) 就可以順利安裝完成。&lt;/p&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;至於 cuDNN，因為我自己沒裝，所以如果之後有裝了再回來補充，可以先參考官方網站的&lt;a href=&quot;https://docs.nvidia.com/deeplearning/cudnn/installation/windows.html&quot;&gt;說明&lt;/a&gt;。&lt;/p&gt;
&lt;h1 id=&quot;-stable-diffusion-&quot; style=&quot;border-bottom: 1px solid rgb(221, 221, 221); font-size: 1.6em; font-weight: bold; margin: 1.3em 0px 1em; padding: 0px;&quot;&gt;準備 Stable Diffusion 環境&lt;/h1&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;Stable Diffusion 環境有超多文章和影片在教了，例如&lt;a href=&quot;https://forum.gamer.com.tw/C.php?bsn=60030&amp;amp;snA=635965&quot;&gt;這篇&lt;/a&gt;，這裡就簡單紀錄了 😆。也可以參考這個&lt;a href=&quot;https://www.youtube.com/watch?v=qIJW5AsrnF0&amp;amp;list=PLFw-rGSBdSUgooeJeX9HXRsMxpbYERvSn&quot;&gt;影片&lt;/a&gt;。&lt;/p&gt;
&lt;ol style=&quot;margin: 1.2em 0px; padding-left: 2em;&quot;&gt;
&lt;li style=&quot;margin: 0.5em 0px;&quot;&gt;安裝 Windows Store 的 Python 3.7。&lt;/li&gt;
&lt;li style=&quot;margin: 0.5em 0px;&quot;&gt;下載 &lt;a href=&quot;https://github.com/AUTOMATIC1111/stable-diffusion-webui/releases&quot;&gt;Stable Diffusion webui&lt;/a&gt;。&lt;/li&gt;
&lt;li style=&quot;margin: 0.5em 0px;&quot;&gt;在 webui-user.bat 裡面設定 Python 位置。&lt;/li&gt;
&lt;li style=&quot;margin: 0.5em 0px;&quot;&gt;執行 webui-user.bat，它會在 venv 資料夾裡建出環境。因為要下載不少東西，會需要一段時間。&lt;/li&gt;
&lt;/ol&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;到這裡其實 Stable Diffusion 就弄好了。不過聽說 Stable Diffusion 還有必裝外掛，裝了以後它才有實用價值。所以下面還會再紀錄一些外掛的基本資訊。&lt;/p&gt;
&lt;h1 id=&quot;stable-diffusion-&quot; style=&quot;border-bottom: 1px solid rgb(221, 221, 221); font-size: 1.6em; font-weight: bold; margin: 1.3em 0px 1em; padding: 0px;&quot;&gt;Stable Diffusion 外掛&lt;/h1&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;外掛們據說是 Stable Diffusion 非常重要的東西，有些外掛沒裝，會讓 Stable Diffusion 很難控制，所以會有常見的一些必裝外掛。這裡逐步紀錄一些我自己找到的資訊，不過使用細節等到我有更多經驗後，再看要不要寫點文章紀錄吧～。&lt;/p&gt;
&lt;ul style=&quot;margin: 1.2em 0px; padding-left: 2em;&quot;&gt;
&lt;li style=&quot;margin: 0.5em 0px;&quot;&gt;翻譯&lt;ul style=&quot;margin: 0px; margin: 1.2em 0px; padding-left: 1em; padding-left: 2em;&quot;&gt;
&lt;li style=&quot;margin: 0.5em 0px;&quot;&gt;&lt;a href=&quot;https://github.com/benlisquare/stable-diffusion-webui-localization-zh_TW&quot;&gt;stable-diffusion-webui-localization-zh_TW&lt;/a&gt; - 繁體中文翻譯，推薦再搭配雙語外掛&lt;/li&gt;
&lt;li style=&quot;margin: 0.5em 0px;&quot;&gt;&lt;a href=&quot;https://github.com/journey-ad/sd-webui-bilingual-localization&quot;&gt;sd-webui-bilingual-localization&lt;/a&gt; - 雙語外掛。對入門來說，可以同時看到翻譯的中文和原文的英文，方便對照和熟悉用詞。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li style=&quot;margin: 0.5em 0px;&quot;&gt;提示詞&lt;ul style=&quot;margin: 0px; margin: 1.2em 0px; padding-left: 1em; padding-left: 2em;&quot;&gt;
&lt;li style=&quot;margin: 0.5em 0px;&quot;&gt;&lt;a href=&quot;https://github.com/Physton/sd-webui-prompt-all-in-one&quot;&gt;sd-webui-prompt-all-in-one&lt;/a&gt; - 可以直接從 webui 介面裡安裝。主要功能是提供不少類型的正向和負向提示詞，另外也可以自動幫忙將提示詞翻譯成英文。對於英文詞彙沒那麼熟悉的話，會非常有幫助。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li style=&quot;margin: 0.5em 0px;&quot;&gt;&lt;a href=&quot;https://github.com/Mikubill/sd-webui-controlnet&quot;&gt;ControlNet&lt;/a&gt; - 可以直接從 webui 介面裡安裝。主要用來提昇對圖的控制，必裝，否則 Stable Diffusion 產生的圖太隨機了很難控制。&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;另外紀錄有討論到各種外掛以及提示詞的文章：&lt;/p&gt;
&lt;ul style=&quot;margin: 1.2em 0px; padding-left: 2em;&quot;&gt;
&lt;li style=&quot;margin: 0.5em 0px;&quot;&gt;&lt;a href=&quot;https://vocus.cc/article/64868acbfd8978000122b077&quot;&gt;Stable Diffusion基礎 — 外掛（Extensions）&lt;/a&gt;&lt;/li&gt;
&lt;li style=&quot;margin: 0.5em 0px;&quot;&gt;&lt;a href=&quot;https://gooptions.cc/stable/&quot;&gt;stable diffusion prompt 教學：解釋prompt使用規則、符號、參數與範例，用產生器幫忙&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div style=&quot;font-size: 0em; height: 0; margin: 0; max-height: 0; max-width: 0; overflow: hidden; padding: 0; width: 0;&quot; title=&quot;MDH:PGRpdj7orJvliLDnuarlnJYgQUnvvIzlnKjliJ3mnJ/mnIDmnInlkI3nmoTmh4noqbLlsLHmmK8g
TWlkam91cm5leSAoTUopIOWSjCBTdGFibGUgRGlmZnVzaW9uIChTRCkg5LqG44CC5b6M5L6G6ZuW
54S25pyJ5LiN5bCR5YW25LuW5ZCN5a2X6Zm457qM5YaS5Ye65L6G77yM5LiN6YGO5pyA6ZaL5aeL
55qE6YCZ5YWp5YCL77yM6YKE5piv5L6d54S257at5oyB6JGX5a6D5YCR55qE5ZCN5rCj44CC5Zug
54K6IHNpZGUgcHJvamVjdCDnmoTpl5zkv4LvvIzmg7PopoHoqaboqabnnIvnuarlnJYgQUnvvIzm
iYDku6XlsLHnsKHllq7noJTnqbbkuobkuIDkuIvpgJnlhanlgIvvvIznmbznj74gU3RhYmxlIERp
ZmZ1c2lvbiDlj6/ku6XlnKjoh6rlt7Hpm7vohabkuIrpnaLot5HvvIzkuI3nlKjosrfoqILplrHl
iLbnmoTmnI3li5nvvIHmiYDku6XlsLHmsbrlrprlmJfoqablroPkuobvvIE8L2Rpdj48ZGl2Pjxi
cj48L2Rpdj48ZGl2PuiqquaYr+mAmem6vOiqqu+8jOS9huewoeWWruWYl+ippuW+jOeZvOePvuS4
gOWAi+W+iOWkp+eahOWVj+mhjO+8jFN0YWJsZSBEaWZmdXNpb24g5bCN6Z2eIG5WaWRpYSDpoa/n
pLrljaHnmoTkvb/nlKjogIXnm7jnlbbkuI3lj4vlloQg7aC97biT44CC6ZuW54S25LiN5piv5a6M
5YWo5LiN6IO955So77yM5L2G5bCx5ZyW54mH55Si55Sf6YCf5bqm6Z2e5bi45oWi77yM6ICM5LiU
55Kw5aKD5rqW5YKZ5Lmf5b6I6bq754Wp44CC5Zug54K65oiR55qE6aGv56S65Y2h5pivIEFNRCDn
moTlpKfmpoLkuIPlubTliY3nmoTkuK3pmo7poa/ljaHvvIzmkJzlsIvkuobkuIDkuIvnmbznj77o
poHnlKggQU1EIOeul+WclueahOeSsOWig+ioreWumui2hem6u+eFqe+8jOaJgOS7peeri+WIu+Ww
seaUvuajhOS6hiDtoL3tuIbjgILoqablrozkuobkuIDkuIvnlKggQ1BVIOeul+Wclu+8jOS4jemB
juS9v+eUqOe2k+mpl+aYr+W+iOWkmiBtb2RlbCDpg73mnIPmnInllY/poYzvvIzogIzog73nlKjn
moTlj4jlvojmhaLvvIzot5HkuIDlvLXlnJbopoHot5EgMTAg5bm+5YiG6ZCY77yM5omA5Lul6Kmm
546p5LiA5LiL5b6M5bCx5rG65a6a6YKE5piv6KaB5Y676LK35paw55qE6aGv56S65Y2h5LqG44CC
5pa85piv6YCZ56+H5bCx5pyD57Ch5Zau57SA6YyE5LiA5LiL5ZyoIFdpbmRvd3MgMTAg55qEIG5W
aWRpYSDpoa/ljaHku6Xlj4ogU3RhYmxlIERpZmZ1c2lvbiDnmoTln7rmnKzmupblgpnjgII8L2Rp
dj48ZGl2Pjxicj48L2Rpdj48ZGl2PiMg5rqW5YKZIE5WSURJQSDnkrDlooM8L2Rpdj48ZGl2Pjxi
cj48L2Rpdj48ZGl2Pk5WSURJQSDnkrDlooPlhbfpq5TkvoboqqrvvIzlsLHmmK/poa/npLrljaHn
moTpqYXli5XnqIvlvI/ot58gQ1VEQSDnm7jpl5znmoTmnbHopb/jgILpgJnlgIvmraXpqZ/kuI3n
rpflpKrpm6PvvIzkuLvopoHopoHlronoo53nmoTmmK/ku6XkuIvlub7lgIvmnbHopb/vvJo8L2Rp
dj48ZGl2Pjxicj48L2Rpdj48ZGl2PiogTlZJRElBIEdlZm9yZWNlIEV4cGVyaWVuY2XvvIgyMDI1
IOabtOaWsO+8muW+jOS+huaUueWQjeWPqyBOdmlkaWEgQXBw77yJPC9kaXY+PGRpdj4qIENVREEg
VG9vbGtpdDwvZGl2PjxkaXY+KiBOVklESUEgY3VETk48L2Rpdj48ZGl2Pjxicj48L2Rpdj48ZGl2
PuWFtuS4rSBHZWZvcmVjZSBFeHBlcmllbmNlIOWFtuWvpuacrOS+huS4jeaYr+W/heimgeeahO+8
jOS4jemBjuWboOeCuumhr+ekuuWNoempheWLleeoi+W8j+WlveWDj+mDvei3n+WcqOmAmeijoemd
ou+8jOaJgOS7peWkmuWNiuacg+S4gOi1t+ijnemAsuWOu+OAguiHs+aWvCBjdUROTiDnnJ/nmoTl
sLHmmK/pnZ7lv4XopoHnmoTvvIzmiJHoh6rlt7HnmoTlr6bpmpvlr6bpqZfmmK/mspLoo53lroPk
uZ/lj6/ku6XmraPluLjkvb/nlKggU3RhYmxlIERpZmZ1c2lvbu+8jOS4jemBjueci+WumOaWueea
hOiqquaYju+8jOaEn+imuumAmeWAi+aHieipsuaYr+WwjeioiOeul+acg+acieWKoOmAn+eahOaV
iOaenOS5i+mhnueahO+8jOaJgOS7pemChOaYr+ijneS4gOijneacg+avlOi8g+Wlve+8iOS9huaI
keePvuWcqOmChOaykuijnSDtoL3tuIbvvInjgII8L2Rpdj48ZGl2Pjxicj48L2Rpdj48ZGl2PkNV
REEgVG9vbGtpdCDot58gY3VETk4g5a6J6KOd55qE54mI5pys6YO95oeJ6Kmy6KaB5Y+D6ICD6Ieq
5bex55qE6aGv56S65Y2h5ZKM6amF5YuV56iL5byP5bCN5oeJ55qE54mI5pys77yM5Y+v5Lul5YiG
5Yil5Y+D6ICD5a6Y5pa557ay56uZ55qEIFtDVURBIEdQVV1bQ1VEQS1HUFUtY29tcGF0aWJpbGl0
eV0g5ZKMIFtDVURBIERyaXZlcl1bQ1VEQS1kcml2ZXItdmVyc2lvbl3jgILoiInkvovkvoboqqrv
vIzlnKggQ1VEQSBEcml2ZXIg6aCB6Z2i5Lit5pyJ5a+r5YiwIG5WaWRpYSDpqYXli5XnqIvlvI/l
sI3mh4nnmoQgQ1VEQSBEcml2ZXIg54mI5pys77yM5omA5Lul6KaB5Y6755yL6Ieq5bex55qE6amF
5YuV56iL5byP5oeJ6Kmy5bCN5oeJ5ZOq5LiA5YCL54mI5pys55qEIENVREEgRHJpdmVy44CC5oiR
55qE6aGv56S65Y2h5pyJ5pSv5o+05pyA5paw54mI77yM6ICM5Zyo5a+r6YCZ56+H5paH56ug55qE
5pmC5YCZ77yMQ1VEQSBUb29sa2l0IOacgOaWsOeJiOaYryBgMTIuM2AgKFvmnIDmlrDniYjnmoTk
uIvovInkvY3nva5dW0NVREEtTEFURVNUXSkg77yM5LiN6YGO5pyJ5YCL6LaF57Sa6YeN6KaB55qE
57aT6amX5piv77yM5LiN6KaB5a6J6KOdIGAxMi4zYCDniYgg7aC97biG44CC5oiR6Kmm6YGO5ZCE
56iu5pa55rOV6YO95rKS6L6m5rOV5oiQ5Yqf5oqKIGAxMi4zYCDoo53pgLLljrvvvIzlronoo53n
qIvlvI/mnIPkuIDnm7Tpoa/npLrlronoo53lpLHmlZfvvIzkvYblj4jkuI3mmI7norrmj5DnpLrn
grrku4DpurzlpLHmlZfjgILmn6XkuoYgTlZJRElBIOWumOaWueirluWjh+S7peWPiiBSZWRkaXQg
55qE5paH56ug5b6M77yM5pyA5b6M55m854++5pS55a6J6KOdIGAxMi4yYCAoW+S4i+i8ieS9jee9
rl1bQ1VEQS0xMi0yXSkg5bCx5Y+v5Lul6aCG5Yip5a6J6KOd5a6M5oiQ44CCPC9kaXY+PGRpdj48
YnI+PC9kaXY+PGRpdj7oh7PmlrwgY3VETk7vvIzlm6DngrrmiJHoh6rlt7HmspLoo53vvIzmiYDk
u6XlpoLmnpzkuYvlvozmnInoo53kuoblho3lm57kvoboo5zlhYXvvIzlj6/ku6XlhYjlj4PogIPl
rpjmlrnntrLnq5nnmoRb6Kqq5piOXVtjdUROTi1pbnN0YWxsYXRpb25d44CCPC9kaXY+PGRpdj48
YnI+PC9kaXY+PGRpdj48YnI+PC9kaXY+PGRpdj5bQ1VEQS1HUFUtY29tcGF0aWJpbGl0eV06IGh0
dHBzOi8vZGV2ZWxvcGVyLm52aWRpYS5jb20vY3VkYS1ncHVzPC9kaXY+PGRpdj5bQ1VEQS1kcml2
ZXItdmVyc2lvbl06IGh0dHBzOi8vZG9jcy5udmlkaWEuY29tL2N1ZGEvY3VkYS10b29sa2l0LXJl
bGVhc2Utbm90ZXMvaW5kZXguaHRtbCNjdWRhLXRvb2xraXQtbWFqb3ItY29tcG9uZW50LXZlcnNp
b25zPGJyPltDVURBLUxBVEVTVF06IGh0dHBzOi8vZGV2ZWxvcGVyLm52aWRpYS5jb20vY3VkYS1k
b3dubG9hZHM8YnI+PC9kaXY+PGRpdj5bQ1VEQS0xMi0yXTogaHR0cHM6Ly9kZXZlbG9wZXIubnZp
ZGlhLmNvbS9jdWRhLTEyLTItMC1kb3dubG9hZC1hcmNoaXZlPC9kaXY+PGRpdj5bY3VETk4taW5z
dGFsbGF0aW9uXTogaHR0cHM6Ly9kb2NzLm52aWRpYS5jb20vZGVlcGxlYXJuaW5nL2N1ZG5uL2lu
c3RhbGxhdGlvbi93aW5kb3dzLmh0bWw8L2Rpdj48ZGl2Pjxicj48L2Rpdj48ZGl2PiMg5rqW5YKZ
IFN0YWJsZSBEaWZmdXNpb24g55Kw5aKDPC9kaXY+PGRpdj48YnI+PC9kaXY+PGRpdj5TdGFibGUg
RGlmZnVzaW9uIOeSsOWig+aciei2heWkmuaWh+eroOWSjOW9seeJh+WcqOaVmeS6hu+8jOS+i+Wm
glvpgJnnr4ddW2dhbWVyLXR1dG9yaWFsXe+8jOmAmeijoeWwseewoeWWrue0gOmMhOS6hiDtoL3t
uIbjgILkuZ/lj6/ku6Xlj4PogIPpgJnlgItb5b2x54mHXVtzZC1uZXdiaWUtdmlkZW9d44CCPC9k
aXY+PGRpdj48YnI+PC9kaXY+PGRpdj4xLiDlronoo50gV2luZG93cyBTdG9yZSDnmoQgUHl0aG9u
IDMuN+OAgjwvZGl2PjxkaXY+Mi4g5LiL6LyJIFtTdGFibGUgRGlmZnVzaW9uIHdlYnVpXVtzdGFi
bGUtZGlmZnVzaW9uLXdlYnVpXeOAgjwvZGl2PjxkaXY+My4g5ZyoIHdlYnVpLXVzZXIuYmF0IOij
oemdouioreWumiBQeXRob24g5L2N572u44CCPC9kaXY+PGRpdj40LiDln7fooYwgd2VidWktdXNl
ci5iYXTvvIzlroPmnIPlnKggdmVudiDos4fmlpnlpL7oo6Hlu7rlh7rnkrDlooPjgILlm6Dngrro
poHkuIvovInkuI3lsJHmnbHopb/vvIzmnIPpnIDopoHkuIDmrrXmmYLplpPjgII8L2Rpdj48ZGl2
Pjxicj48L2Rpdj48ZGl2PuWIsOmAmeijoeWFtuWvpiBTdGFibGUgRGlmZnVzaW9uIOWwseW8hOWl
veS6huOAguS4jemBjuiBveiqqiBTdGFibGUgRGlmZnVzaW9uIOmChOacieW/heijneWkluaOm++8
jOijneS6huS7peW+jOWug+aJjeacieWvpueUqOWDueWAvOOAguaJgOS7peS4i+mdoumChOacg+WG
jee0gOmMhOS4gOS6m+WkluaOm+eahOWfuuacrOizh+ioiuOAgjwvZGl2PjxkaXY+PGJyPjwvZGl2
PjxkaXY+IyBTdGFibGUgRGlmZnVzaW9uIOWkluaOmzwvZGl2PjxkaXY+PGJyPjwvZGl2PjxkaXY+
5aSW5o6b5YCR5pOa6Kqq5pivIFN0YWJsZSBEaWZmdXNpb24g6Z2e5bi46YeN6KaB55qE5p2x6KW/
77yM5pyJ5Lqb5aSW5o6b5rKS6KOd77yM5pyD6K6TIFN0YWJsZSBEaWZmdXNpb24g5b6I6Zuj5o6n
5Yi277yM5omA5Lul5pyD5pyJ5bi46KaL55qE5LiA5Lqb5b+F6KOd5aSW5o6b44CC6YCZ6KOh6YCQ
5q2l57SA6YyE5LiA5Lqb5oiR6Ieq5bex5om+5Yiw55qE6LOH6KiK77yM5LiN6YGO5L2/55So57Sw
56+A562J5Yiw5oiR5pyJ5pu05aSa57aT6amX5b6M77yM5YaN55yL6KaB5LiN6KaB5a+r6bue5paH
56ug57SA6YyE5ZCn772e44CCPC9kaXY+PGRpdj48YnI+PC9kaXY+PGRpdj4qIOe/u+itrzwvZGl2
PjxkaXY+Jm5ic3A7ICogW3N0YWJsZS1kaWZmdXNpb24td2VidWktbG9jYWxpemF0aW9uLXpoX1RX
XVtsb2NhbGl6YXRpb24temhfVFddIC0g57mB6auU5Lit5paH57+76K2v77yM5o6o6Jam5YaN5pCt
6YWN6ZuZ6Kqe5aSW5o6bPC9kaXY+PGRpdj4mbmJzcDsgKiBbc2Qtd2VidWktYmlsaW5ndWFsLWxv
Y2FsaXphdGlvbl1bYmlsaW5ndWFsLWxvY2FsaXphdGlvbl0gLSDpm5noqp7lpJbmjpvjgILlsI3l
haXploDkvoboqqrvvIzlj6/ku6XlkIzmmYLnnIvliLDnv7vora/nmoTkuK3mloflkozljp/mlofn
moToi7HmlofvvIzmlrnkvr/lsI3nhaflkoznhp/mgonnlKjoqZ7jgII8L2Rpdj48ZGl2Piog5o+Q
56S66KmePC9kaXY+PGRpdj4mbmJzcDsgKiBbc2Qtd2VidWktcHJvbXB0LWFsbC1pbi1vbmVdW3By
b21wdC1hbGwtaW4tb25lXSAtIOWPr+S7peebtOaOpeW+niB3ZWJ1aSDku4vpnaLoo6Hlronoo53j
gILkuLvopoHlip/og73mmK/mj5DkvpvkuI3lsJHpoZ7lnovnmoTmraPlkJHlkozosqDlkJHmj5Dn
pLroqZ7vvIzlj6blpJbkuZ/lj6/ku6Xoh6rli5Xluavlv5nlsIfmj5DnpLroqZ7nv7vora/miJDo
i7HmlofjgILlsI3mlrzoi7HmlofoqZ7lvZnmspLpgqPpurznhp/mgonnmoToqbHvvIzmnIPpnZ7l
uLjmnInluavliqnjgII8L2Rpdj48ZGl2PiogW0NvbnRyb2xOZXRdW2NvbnRyb2xuZXRdIC0g5Y+v
5Lul55u05o6l5b6eIHdlYnVpIOS7i+mdouijoeWuieijneOAguS4u+imgeeUqOS+huaPkOaYh+Ww
jeWclueahOaOp+WItu+8jOW/heijne+8jOWQpuWJhyBTdGFibGUgRGlmZnVzaW9uIOeUoueUn+ea
hOWcluWkqumaqOapn+S6huW+iOmbo+aOp+WItuOAgjwvZGl2PjxkaXY+PGJyPjwvZGl2PjxkaXY+
W2dhbWVyLXR1dG9yaWFsXTogaHR0cHM6Ly9mb3J1bS5nYW1lci5jb20udHcvQy5waHA/YnNuPTYw
MDMwJmFtcDtzbkE9NjM1OTY1PC9kaXY+PGRpdj5bc2QtbmV3YmllLXZpZGVvXTogaHR0cHM6Ly93
d3cueW91dHViZS5jb20vd2F0Y2g/dj1xSUpXNUFzcm5GMCZhbXA7bGlzdD1QTEZ3LXJHU0JkU1Vn
b29lSmVYOUhYUnNNeHBiWUVSdlNuPC9kaXY+PGRpdj5bc3RhYmxlLWRpZmZ1c2lvbi13ZWJ1aV06
IGh0dHBzOi8vZ2l0aHViLmNvbS9BVVRPTUFUSUMxMTExL3N0YWJsZS1kaWZmdXNpb24td2VidWkv
cmVsZWFzZXM8L2Rpdj48ZGl2Pltsb2NhbGl6YXRpb24temhfVFddOiBodHRwczovL2dpdGh1Yi5j
b20vYmVubGlzcXVhcmUvc3RhYmxlLWRpZmZ1c2lvbi13ZWJ1aS1sb2NhbGl6YXRpb24temhfVFc8
L2Rpdj48ZGl2PltiaWxpbmd1YWwtbG9jYWxpemF0aW9uXTogaHR0cHM6Ly9naXRodWIuY29tL2pv
dXJuZXktYWQvc2Qtd2VidWktYmlsaW5ndWFsLWxvY2FsaXphdGlvbjwvZGl2PjxkaXY+W3Byb21w
dC1hbGwtaW4tb25lXTogaHR0cHM6Ly9naXRodWIuY29tL1BoeXN0b24vc2Qtd2VidWktcHJvbXB0
LWFsbC1pbi1vbmU8L2Rpdj48ZGl2Pltjb250cm9sbmV0XTogaHR0cHM6Ly9naXRodWIuY29tL01p
a3ViaWxsL3NkLXdlYnVpLWNvbnRyb2xuZXQ8L2Rpdj48ZGl2Pjxicj48L2Rpdj48ZGl2PuWPpuWk
lue0gOmMhOacieiojuirluWIsOWQhOeoruWkluaOm+S7peWPiuaPkOekuuipnueahOaWh+eroO+8
mjwvZGl2PjxkaXY+KiBbU3RhYmxlIERpZmZ1c2lvbuWfuuekjiAtLSDlpJbmjpvvvIhFeHRlbnNp
b25z77yJXShodHRwczovL3ZvY3VzLmNjL2FydGljbGUvNjQ4NjhhY2JmZDg5NzgwMDAxMjJiMDc3
KTwvZGl2PjxkaXY+KiBbc3RhYmxlIGRpZmZ1c2lvbiBwcm9tcHQg5pWZ5a2477ya6Kej6YeLcHJv
bXB05L2/55So6KaP5YmH44CB56ym6Jmf44CB5Y+D5pW46IiH56+E5L6L77yM55So55Si55Sf5Zmo
5bmr5b+ZXShodHRwczovL2dvb3B0aW9ucy5jYy9zdGFibGUvKTwvZGl2Pg==&quot;&gt;​&lt;/div&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimwayne.blogspot.com/feeds/155000003261130058/comments/default' title='張貼留言'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/6819483672105967231/155000003261130058' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6819483672105967231/posts/default/155000003261130058'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6819483672105967231/posts/default/155000003261130058'/><link rel='alternate' type='text/html' href='http://jimwayne.blogspot.com/2024/03/stable-diffusion.html' title='Stable Diffusion 基本環境安裝'/><author><name>Wayne Yeh</name><uri>http://www.blogger.com/profile/02148905864753873930</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6819483672105967231.post-5499015724371595966</id><published>2023-10-23T22:02:00.005+08:00</published><updated>2023-10-23T22:24:57.780+08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="Markdown Here"/><category scheme="http://www.blogger.com/atom/ns#" term="Vespa"/><title type='text'>Vespa 的 parent document + weightedSet 撞牆筆記</title><content type='html'>&lt;div class=&quot;markdown-here-wrapper&quot; data-md-url=&quot;https://www.blogger.com/blog/post/edit/6819483672105967231/5499015724371595966&quot;&gt;&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;簡要紀錄，最近遇到在 parent document 裡使用 weightedSet 做資料篩選的時候，本來還沒加 weightedSet 時，latency 是大約 25ms，加了 weightedSet 之後變成大約 730ms。&lt;/p&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;以下是當時的 schema 設定，簡要來說就是有個讓多個 child document 共用的欄位，叫做 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;sharedField&lt;/code&gt;，它被 child document 繼承時，欄位名字會叫做 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;fieldInParentDoc&lt;/code&gt;。&lt;/p&gt;
&lt;pre style=&quot;font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; font-size: 1em; line-height: 1.2em; margin: 1.2em 0px;&quot;&gt;&lt;code class=&quot;hljs language-javascript&quot; style=&quot;background-color: #f8f8f8; background: rgb(35, 36, 31); border-radius: 3px; border-radius: 3px; border: 1px solid rgb(204, 204, 204); border: 1px solid rgb(234, 234, 234); color: #f8f8f2; display: block !important; display: block; display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; overflow-x: auto; overflow: auto; padding: 0.5em 0.7em; padding: 0.5em; padding: 0px 0.3em; text-size-adjust: none; white-space: pre-wrap; white-space: pre;&quot;&gt;# Schema &lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #f92672;&quot;&gt;in&lt;/span&gt; parent &lt;span class=&quot;hljs-built_in&quot; style=&quot;color: #e6db74;&quot;&gt;document&lt;/span&gt;.
field sharedField type weightedset&amp;lt;string&amp;gt; {
    indexing: attribute
    attribute: fast-search
}

# Schema &lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #f92672;&quot;&gt;in&lt;/span&gt; child &lt;span class=&quot;hljs-built_in&quot; style=&quot;color: #e6db74;&quot;&gt;document&lt;/span&gt;
field id type string {
    indexing: attribute
    attribute: fast-search
}

field parentReference type reference&amp;lt;parent_doc&amp;gt; {
    indexing: attribute
}

import field parentReference.sharedField as fieldInParentDoc {}
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;接著，在查詢時使用的 query 大約是這樣：&lt;/p&gt;
&lt;pre style=&quot;font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; font-size: 1em; line-height: 1.2em; margin: 1.2em 0px;&quot;&gt;&lt;code class=&quot;hljs language-sql&quot; style=&quot;background-color: #f8f8f8; background: rgb(35, 36, 31); border-radius: 3px; border-radius: 3px; border: 1px solid rgb(204, 204, 204); border: 1px solid rgb(234, 234, 234); color: #f8f8f2; display: block !important; display: block; display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; overflow-x: auto; overflow: auto; padding: 0.5em 0.7em; padding: 0.5em; padding: 0px 0.3em; text-size-adjust: none; white-space: pre-wrap; white-space: pre;&quot;&gt;&lt;span class=&quot;hljs-operator&quot;&gt;&lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #f92672;&quot;&gt;SELECT&lt;/span&gt; * &lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #f92672;&quot;&gt;FROM&lt;/span&gt; * sources &lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #f92672;&quot;&gt;WHERE&lt;/span&gt; (id contains &lt;span class=&quot;hljs-string&quot; style=&quot;color: #e6db74;&quot;&gt;&quot;my_id_1&quot;&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #f92672;&quot;&gt;OR&lt;/span&gt; id contains &lt;span class=&quot;hljs-string&quot; style=&quot;color: #e6db74;&quot;&gt;&quot;my_id_2&quot;&lt;/span&gt;) &lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #f92672;&quot;&gt;AND&lt;/span&gt; weightedSet(fieldInParentDoc, {&lt;span class=&quot;hljs-string&quot; style=&quot;color: #e6db74;&quot;&gt;&quot;my_key&quot;&lt;/span&gt;:&lt;span class=&quot;hljs-number&quot; style=&quot;color: #ae81ff;&quot;&gt;1&lt;/span&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;實際上 ID 欄位是唯一值，並且 query 當中會有 5 個 ID，所以搜尋結果一定只會有 5 筆紀錄。理論上這應該會是很快的 query，但在加上後半段的 parent document 篩選條件後，速度就爆增了 28 倍。&lt;/p&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;不過如果幫這個 parent document 欄位設定 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;rank: filter&lt;/code&gt;，如下所示，讓它改成 bit vector 的形式，速度會變快不少。具體來說，專案裡測試的結果是會從 730ms 變成約 90ms。雖然還是很慢，但至少勉強還過得去…。&lt;/p&gt;
&lt;pre style=&quot;font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; font-size: 1em; line-height: 1.2em; margin: 1.2em 0px;&quot;&gt;&lt;code class=&quot;hljs language-javascript&quot; style=&quot;background-color: #f8f8f8; background: rgb(35, 36, 31); border-radius: 3px; border-radius: 3px; border: 1px solid rgb(204, 204, 204); border: 1px solid rgb(234, 234, 234); color: #f8f8f2; display: block !important; display: block; display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; overflow-x: auto; overflow: auto; padding: 0.5em 0.7em; padding: 0.5em; padding: 0px 0.3em; text-size-adjust: none; white-space: pre-wrap; white-space: pre;&quot;&gt;# Schema &lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #f92672;&quot;&gt;in&lt;/span&gt; parent &lt;span class=&quot;hljs-built_in&quot; style=&quot;color: #e6db74;&quot;&gt;document&lt;/span&gt;.
field sharedField type weightedset&amp;lt;string&amp;gt; {
    indexing: attribute
    attribute: fast-search
    rank: filter
}
&lt;/code&gt;&lt;/pre&gt;
&lt;div style=&quot;font-size: 0em; height: 0; margin: 0; max-height: 0; max-width: 0; overflow: hidden; padding: 0; width: 0;&quot; title=&quot;MDH:57Ch6KaB57SA6YyE77yM5pyA6L+R6YGH5Yiw5ZyoIHBhcmVudCBkb2N1bWVudCDoo6Hkvb/nlKgg
d2VpZ2h0ZWRTZXQg5YGa6LOH5paZ56+p6YG455qE5pmC5YCZ77yM5pys5L6G6YKE5rKS5YqgIHdl
aWdodGVkU2V0IOaZgu+8jGxhdGVuY3kg5piv5aSn57SEIDI1bXPvvIzliqDkuoYgd2VpZ2h0ZWRT
ZXQg5LmL5b6M6K6K5oiQ5aSn57SEIDczMG1z44CCPGJyPjxicj7ku6XkuIvmmK/nlbbmmYLnmoQg
c2NoZW1hIOioreWumu+8jOewoeimgeS+huiqquWwseaYr+acieWAi+iuk+WkmuWAiyBjaGlsZCBk
b2N1bWVudCDlhbHnlKjnmoTmrITkvY3vvIzlj6vlgZogYHNoYXJlZEZpZWxkYO+8jOWug+iiqyBj
aGlsZCBkb2N1bWVudCDnubzmib/mmYLvvIzmrITkvY3lkI3lrZfmnIPlj6vlgZogYGZpZWxkSW5Q
YXJlbnREb2Ng44CCPGJyPjxicj5gYGBqYXZhc2NyaXB0PGJyPiMgU2NoZW1hIGluIHBhcmVudCBk
b2N1bWVudC48YnI+ZmllbGQgc2hhcmVkRmllbGQgdHlwZSB3ZWlnaHRlZHNldCZsdDtzdHJpbmcm
Z3Q7IHs8YnI+wqDCoCDCoGluZGV4aW5nOiBhdHRyaWJ1dGU8YnI+wqDCoCDCoGF0dHJpYnV0ZTog
ZmFzdC1zZWFyY2g8YnI+fTxicj48YnI+IyBTY2hlbWEgaW4gY2hpbGQgZG9jdW1lbnQ8YnI+Zmll
bGQgaWQgdHlwZSBzdHJpbmcgezxicj7CoMKgIMKgaW5kZXhpbmc6IGF0dHJpYnV0ZTxicj7CoMKg
IMKgYXR0cmlidXRlOiBmYXN0LXNlYXJjaDxicj59PGJyPjxicj5maWVsZCBwYXJlbnRSZWZlcmVu
Y2UgdHlwZSByZWZlcmVuY2UmbHQ7cGFyZW50X2RvYyZndDsgezxicj7CoMKgIMKgaW5kZXhpbmc6
IGF0dHJpYnV0ZTxicj59PGJyPjxicj5pbXBvcnQgZmllbGQgcGFyZW50UmVmZXJlbmNlLnNoYXJl
ZEZpZWxkIGFzIGZpZWxkSW5QYXJlbnREb2Mge308YnI+YGBgPGJyPjxicj7mjqXokZfvvIzlnKjm
n6XoqaLmmYLkvb/nlKjnmoQgcXVlcnkg5aSn57SE5piv6YCZ5qij77yaPGJyPjxicj5gYGBzcWw8
YnI+U0VMRUNUICogRlJPTSAqIHNvdXJjZXMgV0hFUkUgKGlkIGNvbnRhaW5zICJteV9pZF8xIiBP
UiBpZCBjb250YWlucyAibXlfaWRfMiIpIEFORCB3ZWlnaHRlZFNldChmaWVsZEluUGFyZW50RG9j
LCB7Im15X2tleSI6MX0pOzxicj5gYGA8YnI+PGJyPuWvpumam+S4iiBJRCDmrITkvY3mmK/llK/k
uIDlgLzvvIzkuKbkuJQgcXVlcnkg55W25Lit5pyD5pyJIDUg5YCLIElE77yM5omA5Lul5pCc5bCL
57WQ5p6c5LiA5a6a5Y+q5pyD5pyJIDUg562G57SA6YyE44CC55CG6KuW5LiK6YCZ5oeJ6Kmy5pyD
5piv5b6I5b+r55qEIHF1ZXJ577yM5L2G5Zyo5Yqg5LiK5b6M5Y2K5q6155qEIHBhcmVudCBkb2N1
bWVudCDnr6npgbjmop3ku7blvozvvIzpgJ/luqblsLHniIblop7kuoYgMjgg5YCN44CCPGJyPjxi
cj7kuI3pgY7lpoLmnpzluavpgJnlgIsgcGFyZW50IGRvY3VtZW50IOashOS9jeioreWumiBgcmFu
azogZmlsdGVyYO+8jOWmguS4i+aJgOekuu+8jOiuk+Wug+aUueaIkCBiaXQgdmVjdG9yIOeahOW9
ouW8j++8jOmAn+W6puacg+iuiuW/q+S4jeWwkeOAguWFt+mrlOS+huiqqu+8jOWwiOahiOijoea4
rOippueahOe1kOaenOaYr+acg+W+niA3MzBtcyDorormiJDntIQgOTBtc+OAgumblueEtumChOaY
r+W+iOaFou+8jOS9huiHs+WwkeWLieW8t+mChOmBjuW+l+WOuy4uLuOAgjxicj48YnI+YGBgamF2
YXNjcmlwdDxicj4jIFNjaGVtYSBpbiBwYXJlbnQgZG9jdW1lbnQuPGJyPmZpZWxkIHNoYXJlZEZp
ZWxkIHR5cGUgd2VpZ2h0ZWRzZXQmbHQ7c3RyaW5nJmd0OyB7PGJyPsKgwqAgwqBpbmRleGluZzog
YXR0cmlidXRlPGJyPsKgwqAgwqBhdHRyaWJ1dGU6IGZhc3Qtc2VhcmNoPGJyPsKgwqAgwqByYW5r
OiBmaWx0ZXI8YnI+fTxicj5gYGA8YnI+PGJyPg==&quot;&gt;​&lt;/div&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimwayne.blogspot.com/feeds/5499015724371595966/comments/default' title='張貼留言'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/6819483672105967231/5499015724371595966' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6819483672105967231/posts/default/5499015724371595966'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6819483672105967231/posts/default/5499015724371595966'/><link rel='alternate' type='text/html' href='http://jimwayne.blogspot.com/2023/10/vespa-parent-document-weightedset.html' title='Vespa 的 parent document + weightedSet 撞牆筆記'/><author><name>Wayne Yeh</name><uri>http://www.blogger.com/profile/02148905864753873930</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6819483672105967231.post-8802405604475650061</id><published>2023-10-11T02:10:00.002+08:00</published><updated>2023-10-23T22:07:28.730+08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="Markdown Here"/><category scheme="http://www.blogger.com/atom/ns#" term="Vespa"/><title type='text'>在 Vespa 使用語義搜尋（一）：簡要的基本知識</title><content type='html'>&lt;div class=&quot;markdown-here-wrapper&quot; data-md-url=&quot;https://www.blogger.com/blog/post/edit/6819483672105967231/8802405604475650061&quot;&gt;&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;在搜尋的世界裡，語義搜尋（semantic search）是近代非常熱門的作法，它可以做到一些傳統的文字搜尋（text matching）做不到的事情。概念上來說，語義搜尋就是把句子的語義萃取出來，轉換成數學裡的向量來表達，然後再利用向量距離來衡量兩個句子的相關程度。這篇文章會簡要紀錄一點語義搜尋時，可能需要知道的基本知識。不過由於這裡面比較多跟資料科學比較有關的知識，在這方面我是個外行，所以這篇基本上都會比較以偏外行的視角在看待這些知識。&lt;/p&gt;
&lt;h1 id=&quot;vector-search&quot; style=&quot;border-bottom: 1px solid rgb(221, 221, 221); font-size: 1.6em; font-weight: bold; margin: 1.3em 0px 1em; padding: 0px;&quot;&gt;Vector Search&lt;/h1&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;技術上來說，語義搜尋也可以稱之為 Vector Search，畢竟語義搜尋是透過向量來做搜尋。而要做語義搜尋，第一要務就是「要如何把一個字串轉換成向量」？這個轉換很大幅度決定了語義搜尋的結果，畢竟轉換成什麼樣的向量，就會決定它跟什麼東西比較近了。其中，把文字轉換成向量的行為，稱為詞嵌入（&lt;a href=&quot;https://en.wikipedia.org/wiki/Embedding&quot;&gt;Embedding&lt;/a&gt;）。&lt;/p&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;Embedding 有很多種技術，近年來比較著名的演算法，就我所知大概是 BERT、FastText 等等的。具體來說，依據我們選擇的 embedding 演算法，會產生出不同維度的向量，這些向量放在向量空間中，就可以接著被用來計算點跟點的距離，並用以決定兩段文字是否相關。&lt;/p&gt;
&lt;h1 id=&quot;transformer&quot; style=&quot;border-bottom: 1px solid rgb(221, 221, 221); font-size: 1.6em; font-weight: bold; margin: 1.3em 0px 1em; padding: 0px;&quot;&gt;Transformer&lt;/h1&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;&lt;a href=&quot;https://zh.wikipedia.org/zh-tw/Transformer%E6%A8%A1%E5%9E%8B&quot;&gt;Transformer&lt;/a&gt; 是 Google 在 2017 年發表的神經網路架構，就我的理解是，這個架構最大的不同是讓神經網路的計算能夠平行化，使得大型模型的訓練變成了可能。目前比較常聽到的像是 GPT（Generative Pre-trained Transformers）、BERT（Bidirectional Encoder Representations from Transformers） 等模型，都是基於 Transformer 架構開發出來的。&lt;/p&gt;
&lt;h1 id=&quot;hugging-face&quot; style=&quot;border-bottom: 1px solid rgb(221, 221, 221); font-size: 1.6em; font-weight: bold; margin: 1.3em 0px 1em; padding: 0px;&quot;&gt;Hugging Face&lt;/h1&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;由於市場上現在有很多種模型架構和應用環境，例如有些模型用 TensorFlow、有些用 PyTorch 等等。Hugging Face 是用來提供統一的 API 介面，使得不同架構的模型能夠互通。細節可以參考&lt;a href=&quot;https://ithelp.ithome.com.tw/articles/10291739&quot;&gt;文章&lt;/a&gt;。&lt;/p&gt;
&lt;h1 id=&quot;onnx&quot; style=&quot;border-bottom: 1px solid rgb(221, 221, 221); font-size: 1.6em; font-weight: bold; margin: 1.3em 0px 1em; padding: 0px;&quot;&gt;ONNX&lt;/h1&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;ONNX（Open Neural Network Exchange）是一種神經網路的資料交換格式，用來讓不同架構（例如 Google 的 Tensorflow、Meta 的 PyTorch、微軟的 CNTX 等）訓練出來的模型能夠互通在其他架構。不過據說 Tensorflow 因為發展較早，已經有自己成熟的環境，因此並不支援 ONNX，但有工具能夠將 Tensorflow 模型轉換成 ONNX 格式，如 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;tf2onnx&lt;/code&gt;。&lt;/p&gt;
&lt;h1 id=&quot;nearest-neighbor-search&quot; style=&quot;border-bottom: 1px solid rgb(221, 221, 221); font-size: 1.6em; font-weight: bold; margin: 1.3em 0px 1em; padding: 0px;&quot;&gt;Nearest Neighbor Search&lt;/h1&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;NN（Nearest Neighbor）是在向量空間中尋找最近的點的問題。比較著名的解法是 KNN（&lt;a href=&quot;https://zh.wikipedia.org/zh-tw/K-%E8%BF%91%E9%82%BB%E7%AE%97%E6%B3%95&quot;&gt;K-nearest neighbor algorithm&lt;/a&gt;）。&lt;/p&gt;
&lt;h1 id=&quot;approximate-nearest-neighbor-search&quot; style=&quot;border-bottom: 1px solid rgb(221, 221, 221); font-size: 1.6em; font-weight: bold; margin: 1.3em 0px 1em; padding: 0px;&quot;&gt;Approximate Nearest Neighbor Search&lt;/h1&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;ANN（Approximate Nearest Neighbor Search）是改良版的 NN，目標是用比較粗略的方式，在向量空間中找出距離最近的點。Vespa 在使用 ANN 去計算尋找最近的點時，使用的演算法是 HNSW。&lt;/p&gt;
&lt;div style=&quot;font-size: 0em; height: 0; margin: 0; max-height: 0; max-width: 0; overflow: hidden; padding: 0; width: 0;&quot; title=&quot;MDH:5Zyo5pCc5bCL55qE5LiW55WM6KOh77yM6Kqe576p5pCc5bCL77yIc2VtYW50aWMgc2VhcmNo77yJ
5piv6L+R5Luj6Z2e5bi454ax6ZaA55qE5L2c5rOV77yM5a6D5Y+v5Lul5YGa5Yiw5LiA5Lqb5YKz
57Wx55qE5paH5a2X5pCc5bCL77yIdGV4dCBtYXRjaGluZ++8ieWBmuS4jeWIsOeahOS6i+aDheOA
guamguW/teS4iuS+huiqqu+8jOiqnue+qeaQnOWwi+WwseaYr+aKiuWPpeWtkOeahOiqnue+qeiQ
g+WPluWHuuS+hu+8jOi9ieaPm+aIkOaVuOWtuOijoeeahOWQkemHj+S+huihqOmBlO+8jOeEtuW+
jOWGjeWIqeeUqOWQkemHj+i3nembouS+huihoemHj+WFqeWAi+WPpeWtkOeahOebuOmXnOeoi+W6
puOAgumAmeevh+aWh+eroOacg+ewoeimgee0gOmMhOS4gOm7nuiqnue+qeaQnOWwi+aZgu+8jOWP
r+iDvemcgOimgeefpemBk+eahOWfuuacrOefpeitmOOAguS4jemBjueUseaWvOmAmeijoemdouav
lOi8g+Wkmui3n+izh+aWmeenkeWtuOavlOi8g+aciemXnOeahOefpeitmO+8jOWcqOmAmeaWuemd
ouaIkeaYr+WAi+WkluihjO+8jOaJgOS7pemAmeevh+WfuuacrOS4iumDveacg+avlOi8g+S7peWB
j+WkluihjOeahOimluinkuWcqOeci+W+hemAmeS6m+efpeitmOOAgjxicj48YnI+IyBWZWN0b3Ig
U2VhcmNoPGJyPjxicj7mioDooZPkuIrkvoboqqrvvIzoqp7nvqnmkJzlsIvkuZ/lj6/ku6XnqLHk
uYvngrogVmVjdG9yIFNlYXJjaO+8jOeVouern+iqnue+qeaQnOWwi+aYr+mAj+mBjuWQkemHj+S+
huWBmuaQnOWwi+OAguiAjOimgeWBmuiqnue+qeaQnOWwi++8jOesrOS4gOimgeWLmeWwseaYr+OA
jOimgeWmguS9leaKiuS4gOWAi+Wtl+S4sui9ieaPm+aIkOWQkemHj+OAje+8n+mAmeWAi+i9ieaP
m+W+iOWkp+W5heW6puaxuuWumuS6huiqnue+qeaQnOWwi+eahOe1kOaenO+8jOeVouern+i9ieaP
m+aIkOS7gOm6vOaoo+eahOWQkemHj++8jOWwseacg+axuuWumuWug+i3n+S7gOm6vOadseilv+av
lOi8g+i/keS6huOAguWFtuS4re+8jOaKiuaWh+Wtl+i9ieaPm+aIkOWQkemHj+eahOihjOeCuu+8
jOeoseeCuuipnuW1jOWFpe+8iFtFbWJlZGRpbmddW2VtYmVkZGluZ13vvInjgII8YnI+PGJyPkVt
YmVkZGluZyDmnInlvojlpJrnqK7mioDooZPvvIzov5HlubTkvobmr5TovIPokZflkI3nmoTmvJTn
rpfms5XvvIzlsLHmiJHmiYDnn6XlpKfmpoLmmK8gQkVSVOOAgUZhc3RUZXh0IOetieetieeahOOA
guWFt+mrlOS+huiqqu+8jOS+neaTmuaIkeWAkemBuOaTh+eahCBlbWJlZGRpbmcg5ryU566X5rOV
77yM5pyD55Si55Sf5Ye65LiN5ZCM57at5bqm55qE5ZCR6YeP77yM6YCZ5Lqb5ZCR6YeP5pS+5Zyo
5ZCR6YeP56m66ZaT5Lit77yM5bCx5Y+v5Lul5o6l6JGX6KKr55So5L6G6KiI566X6bue6Lef6bue
55qE6Led6Zui77yM5Lim55So5Lul5rG65a6a5YWp5q615paH5a2X5piv5ZCm55u46Zec44CCPGJy
Pjxicj4jIFRyYW5zZm9ybWVyPGJyPjxicj5bVHJhbnNmb3JtZXJdW3RyYW5zZm9ybWVyXSDmmK8g
R29vZ2xlIOWcqCAyMDE3IOW5tOeZvOihqOeahOelnue2k+e2sui3r+aetuani++8jOWwseaIkeea
hOeQhuino+aYr++8jOmAmeWAi+aetuani+acgOWkp+eahOS4jeWQjOaYr+iuk+elnue2k+e2sui3
r+eahOioiOeul+iDveWkoOW5s+ihjOWMlu+8jOS9v+W+l+Wkp+Wei+aooeWei+eahOiok+e3tOiu
iuaIkOS6huWPr+iDveOAguebruWJjeavlOi8g+W4uOiBveWIsOeahOWDj+aYryBHUFTvvIhHZW5l
cmF0aXZlIFByZS10cmFpbmVkIFRyYW5zZm9ybWVyc++8ieOAgUJFUlTvvIhCaWRpcmVjdGlvbmFs
IEVuY29kZXIgUmVwcmVzZW50YXRpb25zIGZyb20gVHJhbnNmb3JtZXJz77yJIOetieaooeWei++8
jOmDveaYr+WfuuaWvCBUcmFuc2Zvcm1lciDmnrbmp4vplovnmbzlh7rkvobnmoTjgII8YnI+PGJy
PiMgSHVnZ2luZyBGYWNlPGJyPjxicj7nlLHmlrzluILloLTkuIrnj77lnKjmnInlvojlpJrnqK7m
qKHlnovmnrbmp4vlkozmh4nnlKjnkrDlooPvvIzkvovlpoLmnInkupvmqKHlnovnlKggVGVuc29y
Rmxvd+OAgeacieS6m+eUqCBQeVRvcmNoIOetieetieOAgkh1Z2dpbmcgRmFjZSDmmK/nlKjkvobm
j5DkvpvntbHkuIDnmoQgQVBJIOS7i+mdou+8jOS9v+W+l+S4jeWQjOaetuani+eahOaooeWei+iD
veWkoOS6kumAmuOAgue0sOevgOWPr+S7peWPg+iAg1vmlofnq6BdW2h1Z2dpbmdfZmFjZV3jgII8
YnI+PGJyPiMgT05OWDxicj48YnI+T05OWO+8iE9wZW4gTmV1cmFsIE5ldHdvcmsgRXhjaGFuZ2Xv
vInmmK/kuIDnqK7npZ7ntpPntrLot6/nmoTos4fmlpnkuqTmj5vmoLzlvI/vvIznlKjkvoborpPk
uI3lkIzmnrbmp4vvvIjkvovlpoIgR29vZ2xlIOeahCBUZW5zb3JmbG9344CBTWV0YSDnmoQgUHlU
b3JjaOOAgeW+rui7n+eahCBDTlRYIOetie+8ieiok+e3tOWHuuS+hueahOaooeWei+iDveWkoOS6
kumAmuWcqOWFtuS7luaetuani+OAguS4jemBjuaTmuiqqiBUZW5zb3JmbG93IOWboOeCuueZvOWx
lei8g+aXqe+8jOW3sue2k+acieiHquW3seaIkOeGn+eahOeSsOWig++8jOWboOatpOS4puS4jeaU
r+aPtCBPTk5Y77yM5L2G5pyJ5bel5YW36IO95aSg5bCHIFRlbnNvcmZsb3cg5qih5Z6L6L2J5o+b
5oiQIE9OTlgg5qC85byP77yM5aaCIGB0ZjJvbm54YOOAgjxicj48YnI+IyBOZWFyZXN0IE5laWdo
Ym9yIFNlYXJjaDxicj48YnI+Tk7vvIhOZWFyZXN0IE5laWdoYm9y77yJ5piv5Zyo5ZCR6YeP56m6
6ZaT5Lit5bCL5om+5pyA6L+R55qE6bue55qE5ZWP6aGM44CC5q+U6LyD6JGX5ZCN55qE6Kej5rOV
5pivIEtOTu+8iFtLLW5lYXJlc3QgbmVpZ2hib3IgYWxnb3JpdGhtXVtrbm5d77yJ44CCPGJyPjxi
cj4jIEFwcHJveGltYXRlIE5lYXJlc3QgTmVpZ2hib3IgU2VhcmNoPGJyPjxicj5BTk7vvIhBcHBy
b3hpbWF0ZSBOZWFyZXN0IE5laWdoYm9yIFNlYXJjaO+8ieaYr+aUueiJr+eJiOeahCBOTu+8jOeb
ruaomeaYr+eUqOavlOi8g+eyl+eVpeeahOaWueW8j++8jOWcqOWQkemHj+epuumWk+S4reaJvuWH
uui3nembouacgOi/keeahOm7nuOAglZlc3BhIOWcqOS9v+eUqCBBTk4g5Y676KiI566X5bCL5om+
5pyA6L+R55qE6bue5pmC77yM5L2/55So55qE5ryU566X5rOV5pivIEhOU1fjgII8YnI+PGJyPltl
bWJlZGRpbmddOiBodHRwczovL2VuLndpa2lwZWRpYS5vcmcvd2lraS9FbWJlZGRpbmc8YnI+W2Jl
cnRfdnNfZmFzdHRleHRdOiBodHRwczovL3d3dy5saW5rZWRpbi5jb20vcHVsc2UvYmVydC12cy1m
YXN0LXRleHQtc3J1dGhpLXMvPGJyPlt0cmFuc2Zvcm1lcl06IGh0dHBzOi8vemgud2lraXBlZGlh
Lm9yZy96aC10dy9UcmFuc2Zvcm1lciVFNiVBOCVBMSVFNSU5RSU4Qjxicj5baHVnZ2luZ19mYWNl
XTogaHR0cHM6Ly9pdGhlbHAuaXRob21lLmNvbS50dy9hcnRpY2xlcy8xMDI5MTczOTxicj5ba25u
XTogaHR0cHM6Ly96aC53aWtpcGVkaWEub3JnL3poLXR3L0stJUU4JUJGJTkxJUU5JTgyJUJCJUU3
JUFFJTk3JUU2JUIzJTk1&quot;&gt;​&lt;/div&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimwayne.blogspot.com/feeds/8802405604475650061/comments/default' title='張貼留言'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/6819483672105967231/8802405604475650061' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6819483672105967231/posts/default/8802405604475650061'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6819483672105967231/posts/default/8802405604475650061'/><link rel='alternate' type='text/html' href='http://jimwayne.blogspot.com/2023/10/vespa.html' title='在 Vespa 使用語義搜尋（一）：簡要的基本知識'/><author><name>Wayne Yeh</name><uri>http://www.blogger.com/profile/02148905864753873930</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6819483672105967231.post-7417745587316216852</id><published>2023-09-14T21:40:00.005+08:00</published><updated>2023-10-05T22:40:27.209+08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="PPPoE"/><category scheme="http://www.blogger.com/atom/ns#" term="Windows"/><title type='text'>系統碟備份的經驗紀錄</title><content type='html'>&lt;p&gt;這兩天因為換系統碟，把古老的 Intel 240GB 換成美光 MX500 1TB，所以遇到一些奇怪的問題。簡單做個筆記。&lt;/p&gt;&lt;p&gt;首先是系統碟備份的部份，我是直接用 Macrium Reflect v8.0.6758。本來要下載安裝檔，正要安裝時發現原來電腦裡本來就有裝了，於是就直接用裝好的版本。不過新版的疑似沒有個人使用的免費版了？用這個直接在 Windows 裡就能夠對系統碟做 clone disk 了，還蠻方便的。&lt;/p&gt;&lt;p&gt;磁碟轉移完成後，我又做了一件事是把一顆快壞掉的硬碟拔掉，之前把那顆硬碟設成虛擬磁碟和暫存資料區，現在就直接不用它了。不過我完全忘了自己做了什麼設定，所以後來遇到奇奇怪怪的問題，其實都跟它有關。&lt;/p&gt;&lt;p&gt;首先遇到的問題，是 Line 沒辦法打開圖片，把 Line 重裝以後甚至變成連打開圖片需要的套件都裝不了，下載完後會顯示無法安裝。接著遇到我想刪掉 Line 重裝，結果用 Firefox 要下載時，發現 Firefox 無法下載檔案了，所有連結點了都是立刻失敗（但用 Firefox 以外的瀏覽器就可以下載）。接著我想找系統清理的軟體，嘗試想清理看看什麼登錄檔之類的，然後發現微軟也有出相關的軟體，就去下載微軟的安裝檔，結果安裝檔下載下來雙擊安裝時也失敗，無法安裝。最後是在 Edge 上下載某個檔案並從 Edge 點擊安裝時，才終於彈出一個錯誤訊息說「找不到 T:\」.....。這時我才終於注意到，原來這一系列奇奇怪怪的問題，都是因為某種暫存檔的設定，是設定在被我拔掉的那顆硬碟磁區上.....。於是去改了環境變數的 TEMP 和 TMP 路徑，換成另一顆硬碟，一切就恢復正常了。&lt;/p&gt;&lt;p&gt;OS：過程中我刪掉了 Line 和 Firefox，害我好多東西要重新設定....。但還好 Firefox ESR 移除重裝，它還找得到原本的百來個分頁，算是一個小雀幸了....。&lt;/p&gt;&lt;p&gt;除了 TEMP 造成的問題以外，還有另一個有點沒頭緒的問題是，換成新硬碟後，PPPoE 就再也連不上了。費了一番功夫（主要是依靠 ChatGPT 跟 Google）後，發現修復的方法似乎是先移除 PPPoE 設定，然後去裝置管理員，把所有「網路介面卡」底下的 WAN Miniport 全部移除，然後讓它重新掃描一次硬體變更，自動裝回那些 WAN Miniport 後，再重設一次 PPPoE，就可以修復了。也可以參考&lt;a href=&quot;https://learn.microsoft.com/zh-tw/troubleshoot/windows-server/networking/troubleshoot-error-720-when-establishing-a-vpn-connection&quot; target=&quot;_blank&quot;&gt;微軟的說明&lt;/a&gt;。&lt;br /&gt;&lt;/p&gt;&lt;span&gt;&lt;/span&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimwayne.blogspot.com/feeds/7417745587316216852/comments/default' title='張貼留言'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/6819483672105967231/7417745587316216852' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6819483672105967231/posts/default/7417745587316216852'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6819483672105967231/posts/default/7417745587316216852'/><link rel='alternate' type='text/html' href='http://jimwayne.blogspot.com/2023/09/blog-post.html' title='系統碟備份的經驗紀錄'/><author><name>Wayne Yeh</name><uri>http://www.blogger.com/profile/02148905864753873930</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6819483672105967231.post-3611776508903972936</id><published>2023-08-31T21:49:00.005+08:00</published><updated>2023-08-31T21:49:34.952+08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="Java"/><title type='text'>[書籤] GC Log Analyzer</title><content type='html'>&lt;p&gt;Azul 有提供免費的 GC log analyzer 可以用來把 gc log 圖形化。&lt;/p&gt;&lt;p&gt;https://docs.azul.com/prime/GC-Log-Analyzer&lt;br /&gt;&lt;/p&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimwayne.blogspot.com/feeds/3611776508903972936/comments/default' title='張貼留言'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/6819483672105967231/3611776508903972936' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6819483672105967231/posts/default/3611776508903972936'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6819483672105967231/posts/default/3611776508903972936'/><link rel='alternate' type='text/html' href='http://jimwayne.blogspot.com/2023/08/gc-log-analyzer.html' title='[書籤] GC Log Analyzer'/><author><name>Wayne Yeh</name><uri>http://www.blogger.com/profile/02148905864753873930</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6819483672105967231.post-7685579774835825085</id><published>2023-08-15T17:41:00.003+08:00</published><updated>2023-08-15T17:41:21.862+08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="Java"/><category scheme="http://www.blogger.com/atom/ns#" term="MapStruct"/><title type='text'>MapStruct 不支援 lombok fluent accessors</title><content type='html'>&lt;p&gt;快速筆記，MapStruct 沒有要支援 lombok 的 fluent accessors，因為這樣他們會掃出所有的 method 而無法正確判斷哪個 method 才是 getter/setter。&lt;/p&gt;&lt;p&gt;可以參考 Github issue：https://github.com/mapstruct/mapstruct/issues/3286&lt;br /&gt;&lt;/p&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimwayne.blogspot.com/feeds/7685579774835825085/comments/default' title='張貼留言'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/6819483672105967231/7685579774835825085' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6819483672105967231/posts/default/7685579774835825085'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6819483672105967231/posts/default/7685579774835825085'/><link rel='alternate' type='text/html' href='http://jimwayne.blogspot.com/2023/08/mapstruct-lombok-fluent-accessors.html' title='MapStruct 不支援 lombok fluent accessors'/><author><name>Wayne Yeh</name><uri>http://www.blogger.com/profile/02148905864753873930</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6819483672105967231.post-4230653899511994543</id><published>2023-04-26T23:22:00.005+08:00</published><updated>2023-04-26T23:56:03.628+08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="Java"/><category scheme="http://www.blogger.com/atom/ns#" term="Markdown Here"/><category scheme="http://www.blogger.com/atom/ns#" term="OpenTelemetry"/><title type='text'>基本的 OpenTelemetry Metrics 設定：讓 Counter 只紀錄差異</title><content type='html'>&lt;div class=&quot;markdown-here-wrapper&quot; data-md-url=&quot;https://www.blogger.com/blog/post/edit/6819483672105967231/4230653899511994543&quot;&gt;&lt;p style=&quot;margin: 0px 0px 1.2em;&quot;&gt; 上一篇文章中，我用 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;LongCounter&lt;/code&gt; 來計算收到的 event 數量，並且讓 OTEL 每一秒輸出一次資料到 log 裡。不過其實這個作法跟我本來想像的結果不太相同，因為我想像的是例如第一秒收到 5 個 event、第二秒收到 7 個 event，OTEL 輸出的 Metrics 應該要是：&lt;/p&gt;
&lt;ul style=&quot;margin: 1.2em 0px; padding-left: 2em;&quot;&gt;
&lt;li style=&quot;margin: 0.5em 0px;&quot;&gt;第一個 data point 是 5&lt;/li&gt;
&lt;li style=&quot;margin: 0.5em 0px;&quot;&gt;第二個 data point 是 7&lt;/li&gt;
&lt;li style=&quot;margin: 0.5em 0px;&quot;&gt;第三個 data point 是 0&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em;&quot;&gt;但事實上當時寫出來的&lt;a href=&quot;https://github.com/jimwayneyeh/otel-example/tree/v0.0.1-shapshot&quot;&gt;範例程式&lt;/a&gt;會輸出的結果會是：&lt;/p&gt;
&lt;ul style=&quot;margin: 1.2em 0px; padding-left: 2em;&quot;&gt;
&lt;li style=&quot;margin: 0.5em 0px;&quot;&gt;第一個 data point 是 5&lt;/li&gt;
&lt;li style=&quot;margin: 0.5em 0px;&quot;&gt;第二個 data point 是 12&lt;/li&gt;
&lt;li style=&quot;margin: 0.5em 0px;&quot;&gt;第三個 data point 是 12&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em;&quot;&gt;也就是說，範例程式紀錄下來的是累計值，是從 application 啟動開始累計到現在的所有數值的加總。但我希望紀錄下來的其實是差異值，每次 Exporter 輸出完 Metrics 後，我希望它重置 counter。&lt;/p&gt;
&lt;h1 id=&quot;-exporter-&quot; style=&quot;border-bottom: 1px solid rgb(221, 221, 221); font-size: 1.6em; font-weight: bold; margin: 1.3em 0px 1em; padding: 0px;&quot;&gt;設定 Exporter 輸出差異值&lt;/h1&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em;&quot;&gt;要達成紀錄的是差異值，要做的事情是去設定 Exporter 的 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;AggregationTemporality&lt;/code&gt;。具體設定的方法其實每個 Exporter 都有點不一樣，不過都是在 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;...Builder&lt;/code&gt; 的階段控制的。這裡繼續以 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;LoggingMetricExporter&lt;/code&gt; 為例，&lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;LoggingMetricExporter&lt;/code&gt; 設定 temporality 的方法如下：&lt;/p&gt;
&lt;pre style=&quot;font-family: Consolas, Inconsolata, Courier, monospace; font-size: 1em; line-height: 1.2em; margin: 1.2em 0px;&quot;&gt;&lt;code class=&quot;hljs language-java&quot; style=&quot;-moz-text-size-adjust: none; background-color: #f8f8f8; background: rgb(35, 36, 31) none repeat scroll 0% 0%; border-radius: 3px; border: 1px solid rgb(234, 234, 234); color: #f8f8f2; display: block; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; overflow-x: auto; overflow: auto; padding: 0px 0.3em; white-space: pre;&quot;&gt;LoggingMetricExporter.create(AggregationTemporality.DELTA);
&lt;/code&gt;&lt;/pre&gt;
&lt;h1 id=&quot;-&quot; style=&quot;border-bottom: 1px solid rgb(221, 221, 221); font-size: 1.6em; font-weight: bold; margin: 1.3em 0px 1em; padding: 0px;&quot;&gt;測試方法&lt;/h1&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em;&quot;&gt;設定了 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;AggregationTemporality&lt;/code&gt; 之後，把測試的腳本稍微弄複雜一點，大體來說就是改成用 2 個 thread 模擬送出 1M 個 event，然後觀察 OTEL 紀錄到什麼東西。為了簡化測試，我把一些本來隨機的東西都拿掉，會比較方便觀察數字的正確性 XD。&lt;/p&gt;
&lt;pre style=&quot;font-family: Consolas, Inconsolata, Courier, monospace; font-size: 1em; line-height: 1.2em; margin: 1.2em 0px;&quot;&gt;&lt;code class=&quot;hljs language-java&quot; style=&quot;-moz-text-size-adjust: none; background-color: #f8f8f8; background: rgb(35, 36, 31) none repeat scroll 0% 0%; border-radius: 3px; border: 1px solid rgb(234, 234, 234); color: #f8f8f2; display: block; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; overflow-x: auto; overflow: auto; padding: 0px 0.3em; white-space: pre;&quot;&gt;&lt;span class=&quot;hljs-annotation&quot; style=&quot;color: #75715e;&quot;&gt;@Override&lt;/span&gt;
&lt;span class=&quot;hljs-function&quot; style=&quot;color: #f92672;&quot;&gt;&lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #f92672;&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #f92672;&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;hljs-title&quot; style=&quot;color: #a6e22e;&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;hljs-params&quot; style=&quot;color: #f8f8f2;&quot;&gt;(String... args)&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #f92672;&quot;&gt;throws&lt;/span&gt; Exception &lt;/span&gt;{
    log.info(&lt;span class=&quot;hljs-string&quot; style=&quot;color: #e6db74;&quot;&gt;&quot;Run&quot;&lt;/span&gt;);

    produceEvents(&lt;span class=&quot;hljs-number&quot; style=&quot;color: #ae81ff;&quot;&gt;1&lt;/span&gt;_000_000, &lt;span class=&quot;hljs-number&quot; style=&quot;color: #ae81ff;&quot;&gt;10&lt;/span&gt;);

    log.info(&lt;span class=&quot;hljs-string&quot; style=&quot;color: #e6db74;&quot;&gt;&quot;Done&quot;&lt;/span&gt;);
}

&lt;span class=&quot;hljs-function&quot; style=&quot;color: #f92672;&quot;&gt;&lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #f92672;&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #f92672;&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;hljs-title&quot; style=&quot;color: #a6e22e;&quot;&gt;produceEvents&lt;/span&gt;&lt;span class=&quot;hljs-params&quot; style=&quot;color: #f8f8f2;&quot;&gt;(&lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #f92672;&quot;&gt;int&lt;/span&gt; numOfEvents, &lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #f92672;&quot;&gt;int&lt;/span&gt; numOfThreads)&lt;/span&gt; &lt;/span&gt;{
    var executor = Executors.newFixedThreadPool(numOfThreads);

    &lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #f92672;&quot;&gt;for&lt;/span&gt; (&lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #f92672;&quot;&gt;int&lt;/span&gt; i = &lt;span class=&quot;hljs-number&quot; style=&quot;color: #ae81ff;&quot;&gt;0&lt;/span&gt;; i &amp;lt; numOfEvents; i++) {
        executor.submit(() -&amp;gt; {
            &lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #f92672;&quot;&gt;if&lt;/span&gt; (random.nextBoolean()) {
                processor1.receiveEvent(generateRandomEvent());
            } &lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #f92672;&quot;&gt;else&lt;/span&gt; {
                processor2.receiveEvent(generateRandomEvent());
            }
        });
    }
}

&lt;span class=&quot;hljs-function&quot; style=&quot;color: #f92672;&quot;&gt;&lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #f92672;&quot;&gt;private&lt;/span&gt; Event &lt;span class=&quot;hljs-title&quot; style=&quot;color: #a6e22e;&quot;&gt;generateRandomEvent&lt;/span&gt;&lt;span class=&quot;hljs-params&quot; style=&quot;color: #f8f8f2;&quot;&gt;()&lt;/span&gt; &lt;/span&gt;{
    var eventType = &lt;span class=&quot;hljs-string&quot; style=&quot;color: #e6db74;&quot;&gt;&quot;create&quot;&lt;/span&gt;;

    &lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #f92672;&quot;&gt;return&lt;/span&gt; Event.builder()
            .eventType(eventType)
            .owner(&lt;span class=&quot;hljs-string&quot; style=&quot;color: #e6db74;&quot;&gt;&quot;owner-1&quot;&lt;/span&gt;)
            .build();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em;&quot;&gt;完整的範例程式可以參考&lt;a href=&quot;https://github.com/jimwayneyeh/otel-example/tree/e58ccd1c56ff4630aaacd93f76a07fd2a8c5ff7e&quot;&gt;這裡&lt;/a&gt;。&lt;/p&gt;
&lt;h1 id=&quot;-&quot; style=&quot;border-bottom: 1px solid rgb(221, 221, 221); font-size: 1.6em; font-weight: bold; margin: 1.3em 0px 1em; padding: 0px;&quot;&gt;執行結果&lt;/h1&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em;&quot;&gt;以下是執行時，OTEL 輸出的結果。&lt;/p&gt;
&lt;pre style=&quot;font-family: Consolas, Inconsolata, Courier, monospace; font-size: 1em; line-height: 1.2em; margin: 1.2em 0px;&quot;&gt;&lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: block; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; overflow: auto; padding: 0px 0.3em; white-space: pre;&quot;&gt;INFO  i.o.e.l.LoggingMetricExporter [PeriodicMetricReader-1] Received a collection of 1 metrics for export.
INFO  i.o.e.l.LoggingMetricExporter [PeriodicMetricReader-1] metric: ImmutableMetricData{resource=Resource{schemaUrl=null, attributes={service.name=&quot;otel-example&quot;, telemetry.sdk.language=&quot;java&quot;, telemetry.sdk.name=&quot;opentelemetry&quot;, telemetry.sdk.version=&quot;1.22.0&quot;}}, instrumentationScopeInfo=InstrumentationScopeInfo{name=event-consumer, version=1.0.0, schemaUrl=null, attributes={}}, name=eventType, description=Metrics for the event consuming., unit=1, type=LONG_SUM, data=ImmutableSumData{points=[ImmutableLongPointData{startEpochNanos=1682522136538247300, epochNanos=1682522137553247500, attributes={eventType=&quot;create&quot;, owner=&quot;owner-1&quot;}, value=584412, exemplars=[]}], monotonic=true, aggregationTemporality=DELTA}}
INFO  i.o.e.l.LoggingMetricExporter [PeriodicMetricReader-1] Received a collection of 1 metrics for export.
INFO  i.o.e.l.LoggingMetricExporter [PeriodicMetricReader-1] metric: ImmutableMetricData{resource=Resource{schemaUrl=null, attributes={service.name=&quot;otel-example&quot;, telemetry.sdk.language=&quot;java&quot;, telemetry.sdk.name=&quot;opentelemetry&quot;, telemetry.sdk.version=&quot;1.22.0&quot;}}, instrumentationScopeInfo=InstrumentationScopeInfo{name=event-consumer, version=1.0.0, schemaUrl=null, attributes={}}, name=eventType, description=Metrics for the event consuming., unit=1, type=LONG_SUM, data=ImmutableSumData{points=[ImmutableLongPointData{startEpochNanos=1682522137553247500, epochNanos=1682522138548844500, attributes={eventType=&quot;create&quot;, owner=&quot;owner-1&quot;}, value=415588, exemplars=[]}], monotonic=true, aggregationTemporality=DELTA}}
DEBUG i.o.s.m.e.PeriodicMetricReader [PeriodicMetricReader-1] No metric data to export - skipping export.
DEBUG i.o.s.m.e.PeriodicMetricReader [PeriodicMetricReader-1] No metric data to export - skipping export.
&lt;/code&gt;&lt;/pre&gt;&lt;p style=&quot;margin: 0px 0px 1.2em;&quot;&gt;文字太長了，稍微摘要一下：&lt;/p&gt;
&lt;pre style=&quot;font-family: Consolas, Inconsolata, Courier, monospace; font-size: 1em; line-height: 1.2em; margin: 1.2em 0px;&quot;&gt;&lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: block; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; overflow: auto; padding: 0px 0.3em; white-space: pre;&quot;&gt;....attributes={eventType=&quot;create&quot;, owner=&quot;owner-1&quot;}, value=584412, exemplars=[]....
....attributes={eventType=&quot;create&quot;, owner=&quot;owner-1&quot;}, value=415588, exemplars=[]....
....No metric data to export - skipping export.
....No metric data to export - skipping export.
&lt;/code&gt;&lt;/pre&gt;&lt;p style=&quot;margin: 0px 0px 1.2em;&quot;&gt;可以看到它第一秒紀錄到的 count 是 584,412、第二秒紀錄到的是 415,588，兩秒合計就是 1M，正好是我的測試程式送出的數目。兩秒過去後，因為後續沒有再呼叫 method，所以 OTEL 沒有繼續收到新的 metric data，就會看到 skipping export 的訊息。&lt;/p&gt;
&lt;div style=&quot;font-size: 0em; height: 0px; margin: 0px; max-height: 0px; max-width: 0px; overflow: hidden; padding: 0px; width: 0px;&quot; title=&quot;MDH:wqDkuIrkuIDnr4fmlofnq6DkuK3vvIzmiJHnlKggYExvbmdDb3VudGVyYCDkvoboqIjnrpfmlLbl
iLDnmoQgZXZlbnQg5pW46YeP77yM5Lim5LiU6K6TIE9URUwg5q+P5LiA56eS6Ly45Ye65LiA5qyh
6LOH5paZ5YiwIGxvZyDoo6HjgILkuI3pgY7lhbblr6bpgJnlgIvkvZzms5Xot5/miJHmnKzkvobm
g7Plg4/nmoTntZDmnpzkuI3lpKrnm7jlkIzvvIzlm6DngrrmiJHmg7Plg4/nmoTmmK/kvovlpoLn
rKzkuIDnp5LmlLbliLAgNSDlgIsgZXZlbnTjgIHnrKzkuoznp5LmlLbliLAgNyDlgIsgZXZlbnTv
vIxPVEVMIOi8uOWHuueahCBNZXRyaWNzIOaHieipsuimgeaYr++8mjxicj48YnI+KiDnrKzkuIDl
gIsgZGF0YSBwb2ludCDmmK8gNTxicj4qIOesrOS6jOWAiyBkYXRhIHBvaW50IOaYryA3PGJyPiog
56ys5LiJ5YCLIGRhdGEgcG9pbnQg5pivIDA8YnI+PGJyPuS9huS6i+WvpuS4iueVtuaZguWvq+WH
uuS+hueahFvnr4TkvovnqIvlvI9dW3ByZXZpb3VzLWFydGljbGVd5pyD6Ly45Ye655qE57WQ5p6c
5pyD5piv77yaPGJyPjxicj4qIOesrOS4gOWAiyBkYXRhIHBvaW50IOaYryA1PGJyPiog56ys5LqM
5YCLIGRhdGEgcG9pbnQg5pivIDEyPGJyPiog56ys5LiJ5YCLIGRhdGEgcG9pbnQg5pivIDEyPGJy
Pjxicj7kuZ/lsLHmmK/oqqrvvIznr4TkvovnqIvlvI/ntIDpjITkuIvkvobnmoTmmK/ntK/oqIjl
gLzvvIzmmK/lvp4gYXBwbGljYXRpb24g5ZWf5YuV6ZaL5aeL57Sv6KiI5Yiw54++5Zyo55qE5omA
5pyJ5pW45YC855qE5Yqg57i944CC5L2G5oiR5biM5pyb57SA6YyE5LiL5L6G55qE5YW25a+m5piv
5beu55Ww5YC877yM5q+P5qyhIEV4cG9ydGVyIOi8uOWHuuWujCBNZXRyaWNzIOW+jO+8jOaIkeW4
jOacm+Wug+mHjee9riBjb3VudGVy44CCPGJyPjxicj4jIOioreWumiBFeHBvcnRlciDovLjlh7rl
t67nlbDlgLw8YnI+PGJyPuimgemBlOaIkOe0gOmMhOeahOaYr+W3rueVsOWAvO+8jOimgeWBmuea
hOS6i+aDheaYr+WOu+ioreWumiBFeHBvcnRlciDnmoQgYEFnZ3JlZ2F0aW9uVGVtcG9yYWxpdHlg
44CC5YW36auU6Kit5a6a55qE5pa55rOV5YW25a+m5q+P5YCLIEV4cG9ydGVyIOmDveaciem7nuS4
jeS4gOaoo++8jOS4jemBjumDveaYr+WcqCBgLi4uQnVpbGRlcmAg55qE6ZqO5q615o6n5Yi255qE
44CC6YCZ6KOh57m857qM5LulIGBMb2dnaW5nTWV0cmljRXhwb3J0ZXJgIOeCuuS+i++8jGBMb2dn
aW5nTWV0cmljRXhwb3J0ZXJgIOioreWumiB0ZW1wb3JhbGl0eSDnmoTmlrnms5XlpoLkuIvvvJo8
YnI+PGJyPmBgYGphdmE8YnI+TG9nZ2luZ01ldHJpY0V4cG9ydGVyLmNyZWF0ZShBZ2dyZWdhdGlv
blRlbXBvcmFsaXR5LkRFTFRBKTs8YnI+YGBgPGJyPjxicj4jIOa4rOippuaWueazlTxicj48YnI+
6Kit5a6a5LqGIGBBZ2dyZWdhdGlvblRlbXBvcmFsaXR5YCDkuYvlvozvvIzmiormuKzoqabnmoTo
hbPmnKznqI3lvq7lvITopIfpm5zkuIDpu57vvIzlpKfpq5TkvoboqqrlsLHmmK/mlLnmiJDnlKgg
MiDlgIsgdGhyZWFkIOaooeaTrOmAgeWHuiAxTSDlgIsgZXZlbnTvvIznhLblvozop4Dlr58gT1RF
TCDntIDpjITliLDku4DpurzmnbHopb/jgILngrrkuobnsKHljJbmuKzoqabvvIzmiJHmiorkuIDk
upvmnKzkvobpmqjmqZ/nmoTmnbHopb/pg73mi7/mjonvvIzmnIPmr5TovIPmlrnkvr/op4Dlr5/m
lbjlrZfnmoTmraPnorrmgKcgWETjgII8YnI+PGJyPmBgYGphdmE8YnI+QE92ZXJyaWRlPGJyPnB1
YmxpYyB2b2lkIHJ1bihTdHJpbmcuLi4gYXJncykgdGhyb3dzIEV4Y2VwdGlvbiB7PGJyPsKgwqDC
oCBsb2cuaW5mbygiUnVuIik7PGJyPjxicj7CoMKgwqAgcHJvZHVjZUV2ZW50cygxXzAwMF8wMDAs
IDEwKTs8YnI+PGJyPsKgwqDCoCBsb2cuaW5mbygiRG9uZSIpOzxicj59PGJyPjxicj5wcml2YXRl
IHZvaWQgcHJvZHVjZUV2ZW50cyhpbnQgbnVtT2ZFdmVudHMsIGludCBudW1PZlRocmVhZHMpIHs8
YnI+wqDCoMKgIHZhciBleGVjdXRvciA9IEV4ZWN1dG9ycy5uZXdGaXhlZFRocmVhZFBvb2wobnVt
T2ZUaHJlYWRzKTs8YnI+PGJyPsKgwqDCoCBmb3IgKGludCBpID0gMDsgaSAmbHQ7IG51bU9mRXZl
bnRzOyBpKyspIHs8YnI+wqDCoMKgwqDCoMKgwqAgZXhlY3V0b3Iuc3VibWl0KCgpIC0mZ3Q7IHs8
YnI+wqDCoMKgwqDCoMKgwqDCoMKgwqDCoCBpZiAocmFuZG9tLm5leHRCb29sZWFuKCkpIHs8YnI+
wqDCoMKgwqDCoMKgwqDCoMKgwqDCoMKgwqDCoMKgIHByb2Nlc3NvcjEucmVjZWl2ZUV2ZW50KGdl
bmVyYXRlUmFuZG9tRXZlbnQoKSk7PGJyPsKgwqDCoMKgwqDCoMKgwqDCoMKgwqAgfSBlbHNlIHs8
YnI+wqDCoMKgwqDCoMKgwqDCoMKgwqDCoMKgwqDCoMKgIHByb2Nlc3NvcjIucmVjZWl2ZUV2ZW50
KGdlbmVyYXRlUmFuZG9tRXZlbnQoKSk7PGJyPsKgwqDCoMKgwqDCoMKgwqDCoMKgwqAgfTxicj7C
oMKgwqDCoMKgwqDCoCB9KTs8YnI+wqDCoMKgIH08YnI+fTxicj48YnI+cHJpdmF0ZSBFdmVudCBn
ZW5lcmF0ZVJhbmRvbUV2ZW50KCkgezxicj7CoMKgwqAgdmFyIGV2ZW50VHlwZSA9ICJjcmVhdGUi
Ozxicj48YnI+wqDCoMKgIHJldHVybiBFdmVudC5idWlsZGVyKCk8YnI+wqDCoMKgwqDCoMKgwqDC
oMKgwqDCoCAuZXZlbnRUeXBlKGV2ZW50VHlwZSk8YnI+wqDCoMKgwqDCoMKgwqDCoMKgwqDCoCAu
b3duZXIoIm93bmVyLTEiKTxicj7CoMKgwqDCoMKgwqDCoMKgwqDCoMKgIC5idWlsZCgpOzxicj59
PGJyPmBgYDxicj48YnI+5a6M5pW055qE56+E5L6L56iL5byP5Y+v5Lul5Y+D6ICDW+mAmeijoV1b
ZXhhbXBsZS1wcm9qZWN0XeOAgjxicj48YnI+IyDln7fooYzntZDmnpw8YnI+PGJyPuS7peS4i+aY
r+Wft+ihjOaZgu+8jE9URUwg6Ly45Ye655qE57WQ5p6c44CCPGJyPjxicj5gYGA8YnI+SU5GT8Kg
IGkuby5lLmwuTG9nZ2luZ01ldHJpY0V4cG9ydGVyIFtQZXJpb2RpY01ldHJpY1JlYWRlci0xXSBS
ZWNlaXZlZCBhIGNvbGxlY3Rpb24gb2YgMSBtZXRyaWNzIGZvciBleHBvcnQuPGJyPklORk/CoCBp
Lm8uZS5sLkxvZ2dpbmdNZXRyaWNFeHBvcnRlciBbUGVyaW9kaWNNZXRyaWNSZWFkZXItMV0gbWV0
cmljOiBJbW11dGFibGVNZXRyaWNEYXRhe3Jlc291cmNlPVJlc291cmNle3NjaGVtYVVybD1udWxs
LCBhdHRyaWJ1dGVzPXtzZXJ2aWNlLm5hbWU9Im90ZWwtZXhhbXBsZSIsIHRlbGVtZXRyeS5zZGsu
bGFuZ3VhZ2U9ImphdmEiLCB0ZWxlbWV0cnkuc2RrLm5hbWU9Im9wZW50ZWxlbWV0cnkiLCB0ZWxl
bWV0cnkuc2RrLnZlcnNpb249IjEuMjIuMCJ9fSwgaW5zdHJ1bWVudGF0aW9uU2NvcGVJbmZvPUlu
c3RydW1lbnRhdGlvblNjb3BlSW5mb3tuYW1lPWV2ZW50LWNvbnN1bWVyLCB2ZXJzaW9uPTEuMC4w
LCBzY2hlbWFVcmw9bnVsbCwgYXR0cmlidXRlcz17fX0sIG5hbWU9ZXZlbnRUeXBlLCBkZXNjcmlw
dGlvbj1NZXRyaWNzIGZvciB0aGUgZXZlbnQgY29uc3VtaW5nLiwgdW5pdD0xLCB0eXBlPUxPTkdf
U1VNLCBkYXRhPUltbXV0YWJsZVN1bURhdGF7cG9pbnRzPVtJbW11dGFibGVMb25nUG9pbnREYXRh
e3N0YXJ0RXBvY2hOYW5vcz0xNjgyNTIyMTM2NTM4MjQ3MzAwLCBlcG9jaE5hbm9zPTE2ODI1MjIx
Mzc1NTMyNDc1MDAsIGF0dHJpYnV0ZXM9e2V2ZW50VHlwZT0iY3JlYXRlIiwgb3duZXI9Im93bmVy
LTEifSwgdmFsdWU9NTg0NDEyLCBleGVtcGxhcnM9W119XSwgbW9ub3RvbmljPXRydWUsIGFnZ3Jl
Z2F0aW9uVGVtcG9yYWxpdHk9REVMVEF9fTxicj5JTkZPwqAgaS5vLmUubC5Mb2dnaW5nTWV0cmlj
RXhwb3J0ZXIgW1BlcmlvZGljTWV0cmljUmVhZGVyLTFdIFJlY2VpdmVkIGEgY29sbGVjdGlvbiBv
ZiAxIG1ldHJpY3MgZm9yIGV4cG9ydC48YnI+SU5GT8KgIGkuby5lLmwuTG9nZ2luZ01ldHJpY0V4
cG9ydGVyIFtQZXJpb2RpY01ldHJpY1JlYWRlci0xXSBtZXRyaWM6IEltbXV0YWJsZU1ldHJpY0Rh
dGF7cmVzb3VyY2U9UmVzb3VyY2V7c2NoZW1hVXJsPW51bGwsIGF0dHJpYnV0ZXM9e3NlcnZpY2Uu
bmFtZT0ib3RlbC1leGFtcGxlIiwgdGVsZW1ldHJ5LnNkay5sYW5ndWFnZT0iamF2YSIsIHRlbGVt
ZXRyeS5zZGsubmFtZT0ib3BlbnRlbGVtZXRyeSIsIHRlbGVtZXRyeS5zZGsudmVyc2lvbj0iMS4y
Mi4wIn19LCBpbnN0cnVtZW50YXRpb25TY29wZUluZm89SW5zdHJ1bWVudGF0aW9uU2NvcGVJbmZv
e25hbWU9ZXZlbnQtY29uc3VtZXIsIHZlcnNpb249MS4wLjAsIHNjaGVtYVVybD1udWxsLCBhdHRy
aWJ1dGVzPXt9fSwgbmFtZT1ldmVudFR5cGUsIGRlc2NyaXB0aW9uPU1ldHJpY3MgZm9yIHRoZSBl
dmVudCBjb25zdW1pbmcuLCB1bml0PTEsIHR5cGU9TE9OR19TVU0sIGRhdGE9SW1tdXRhYmxlU3Vt
RGF0YXtwb2ludHM9W0ltbXV0YWJsZUxvbmdQb2ludERhdGF7c3RhcnRFcG9jaE5hbm9zPTE2ODI1
MjIxMzc1NTMyNDc1MDAsIGVwb2NoTmFub3M9MTY4MjUyMjEzODU0ODg0NDUwMCwgYXR0cmlidXRl
cz17ZXZlbnRUeXBlPSJjcmVhdGUiLCBvd25lcj0ib3duZXItMSJ9LCB2YWx1ZT00MTU1ODgsIGV4
ZW1wbGFycz1bXX1dLCBtb25vdG9uaWM9dHJ1ZSwgYWdncmVnYXRpb25UZW1wb3JhbGl0eT1ERUxU
QX19PGJyPkRFQlVHIGkuby5zLm0uZS5QZXJpb2RpY01ldHJpY1JlYWRlciBbUGVyaW9kaWNNZXRy
aWNSZWFkZXItMV0gTm8gbWV0cmljIGRhdGEgdG8gZXhwb3J0IC0gc2tpcHBpbmcgZXhwb3J0Ljxi
cj5ERUJVRyBpLm8ucy5tLmUuUGVyaW9kaWNNZXRyaWNSZWFkZXIgW1BlcmlvZGljTWV0cmljUmVh
ZGVyLTFdIE5vIG1ldHJpYyBkYXRhIHRvIGV4cG9ydCAtIHNraXBwaW5nIGV4cG9ydC48YnI+YGBg
PGJyPjxicj7mloflrZflpKrplbfkuobvvIznqI3lvq7mkZjopoHkuIDkuIvvvJo8YnI+PGJyPmBg
YDxicj4uLi4uYXR0cmlidXRlcz17ZXZlbnRUeXBlPSJjcmVhdGUiLCBvd25lcj0ib3duZXItMSJ9
LCB2YWx1ZT01ODQ0MTIsIGV4ZW1wbGFycz1bXS4uLi48YnI+Li4uLmF0dHJpYnV0ZXM9e2V2ZW50
VHlwZT0iY3JlYXRlIiwgb3duZXI9Im93bmVyLTEifSwgdmFsdWU9NDE1NTg4LCBleGVtcGxhcnM9
W10uLi4uPGJyPi4uLi5ObyBtZXRyaWMgZGF0YSB0byBleHBvcnQgLSBza2lwcGluZyBleHBvcnQu
PGJyPi4uLi5ObyBtZXRyaWMgZGF0YSB0byBleHBvcnQgLSBza2lwcGluZyBleHBvcnQuPGJyPmBg
YDxicj48YnI+5Y+v5Lul55yL5Yiw5a6D56ys5LiA56eS57SA6YyE5Yiw55qEIGNvdW50IOaYryA1
ODQsNDEy44CB56ys5LqM56eS57SA6YyE5Yiw55qE5pivIDQxNSw1ODjvvIzlhannp5LlkIjoqIjl
sLHmmK8gMU3vvIzmraPlpb3mmK/miJHnmoTmuKzoqabnqIvlvI/pgIHlh7rnmoTmlbjnm67jgILl
hannp5LpgY7ljrvlvozvvIzlm6DngrrlvoznuozmspLmnInlho3lkbzlj6sgbWV0aG9k77yM5omA
5LulIE9URUwg5rKS5pyJ57m857qM5pS25Yiw5paw55qEIG1ldHJpYyBkYXRh77yM5bCx5pyD55yL
5YiwIHNraXBwaW5nIGV4cG9ydCDnmoToqIrmga/jgII8YnI+PGJyPjxicj5bcHJldmlvdXMtYXJ0
aWNsZV06ICZsdDtodHRwczovL2dpdGh1Yi5jb20vamltd2F5bmV5ZWgvb3RlbC1leGFtcGxlL3Ry
ZWUvdjAuMC4xLXNoYXBzaG90Jmd0OyA8YnI+W3RlbXBvcmFsaXR5XTogJmx0O2h0dHBzOi8vb3Bl
bnRlbGVtZXRyeS5pby9kb2NzL3JlZmVyZW5jZS9zcGVjaWZpY2F0aW9uL21ldHJpY3MvZGF0YS1t
b2RlbC8jdGVtcG9yYWxpdHkmZ3Q7PGJyPltleGFtcGxlLXByb2plY3RdOiAmbHQ7aHR0cHM6Ly9n
aXRodWIuY29tL2ppbXdheW5leWVoL290ZWwtZXhhbXBsZS90cmVlL2U1OGNjZDFjNTZmZjQ2MzBh
YWFjZDkzZjc2YTA3ZmQyYThjNWZmN2UmZ3Q7&quot;&gt;​&lt;/div&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimwayne.blogspot.com/feeds/4230653899511994543/comments/default' title='張貼留言'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/6819483672105967231/4230653899511994543' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6819483672105967231/posts/default/4230653899511994543'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6819483672105967231/posts/default/4230653899511994543'/><link rel='alternate' type='text/html' href='http://jimwayne.blogspot.com/2023/04/opentelemetry-metrics-counter.html' title='基本的 OpenTelemetry Metrics 設定：讓 Counter 只紀錄差異'/><author><name>Wayne Yeh</name><uri>http://www.blogger.com/profile/02148905864753873930</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6819483672105967231.post-8606835726555028636</id><published>2023-01-28T21:34:00.003+08:00</published><updated>2023-04-26T23:55:38.989+08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="AOP"/><category scheme="http://www.blogger.com/atom/ns#" term="Markdown Here"/><category scheme="http://www.blogger.com/atom/ns#" term="OpenTelemetry"/><category scheme="http://www.blogger.com/atom/ns#" term="Spring"/><category scheme="http://www.blogger.com/atom/ns#" term="Spring Boot"/><title type='text'>基本的 OpenTelemetry Metrics 設定：紀錄累計數目並透過 Spring AOP 攔截方法</title><content type='html'>&lt;div class=&quot;markdown-here-wrapper&quot; data-md-url=&quot;https://www.blogger.com/blog/post/edit/6819483672105967231/8606835726555028636&quot;&gt;&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;接續著前面的 Spring AOP，接著要在 Spring AOP 之上接 OpenTelemetry。實際上接 OpenTelemetry 才是我的目的，AOP 只是希望接 metric/tracing 時可以不要碰商業邏輯的程式碼而已 XD。&lt;/p&gt;
&lt;h1 id=&quot;-opentelemetry&quot; style=&quot;border-bottom: 1px solid rgb(221, 221, 221); font-size: 1.6em; font-weight: bold; margin: 1.3em 0px 1em; padding: 0px;&quot;&gt;什麼是 OpenTelemetry&lt;/h1&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;簡要來說，OpenTelemetry（簡稱 OTEL）是結合了 CNCF 發展的 OpenTracing 和 Google 發展的 OpenCensus 兩個專案後的結果 [&lt;a href=&quot;https://www.cncf.io/blog/2019/05/21/a-brief-history-of-opentelemetry-so-far/&quot;&gt;1&lt;/a&gt;]，目的是為了提供 observibility telemetry。它涵蓋了三大主題：tracing、metrics、logs。tracing 能夠整合上下游的關係，提供完整的 profiling 資訊；metrics 能夠提供統計型的數據，讓我們可以快速了解系統的狀態；logs 則是文字型的資料。&lt;/p&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;不過就我目前的了解，OTEL 因為是在 2019 年才合併，到現在似乎還是沒有到非常完整，各個語言的支援有些可能還是有點缺漏。以 Java 來說，目前 tracing 和 metrics 的支援是比較好，logs 則還處於實驗中。細節可以參考 OTEL 官網中關於 Java SDK 的狀態頁 [&lt;a href=&quot;https://opentelemetry.io/docs/instrumentation/java/#status-and-releases&quot;&gt;2&lt;/a&gt;]。&lt;/p&gt;
&lt;h1 id=&quot;opentelemetry-metrics-&quot; style=&quot;border-bottom: 1px solid rgb(221, 221, 221); font-size: 1.6em; font-weight: bold; margin: 1.3em 0px 1em; padding: 0px;&quot;&gt;OpenTelemetry Metrics 建置&lt;/h1&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;因為我目前的目的是要建立 Metrics 的環境，把我的 Java application 的一些自訂資訊輸出到 Metrics 上，讓我們得以透過統計資訊了解系統的狀態，所以這篇主要只會紀錄關於 Metrics 的建置範例。&lt;/p&gt;
&lt;h2 id=&quot;-&quot; style=&quot;border-bottom: 1px solid rgb(238, 238, 238); font-size: 1.4em; font-weight: bold; margin: 1.3em 0px 1em; padding: 0px;&quot;&gt;範例的目的&lt;/h2&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;首先稍微簡介一下，這篇文章中的範例是設定成什麼背景、要解決什麼問題。承襲上一篇 Spring AOP 文章，我有一個 method 如下，這個 method 的角色是一個 event consumer，就是在收某種 queue 送過來的訊息。文章的目標是要透過 AOP 插入一個能夠統計收到的訊息的類型的 Metrics。有了這個 Metrics，就可以知道系統總共處理什麼量級的訊息，並且也可以用來做更細緻的統計，例如 Event 的設計是有分 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;eventType&lt;/code&gt; 和 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;owner&lt;/code&gt;，代表的是某個人送出的 Create/Update/Delete 指令，而 Metrics 希望能夠讓我們有能力得知例如在指定時間區間內，某個人送了多少指令、或者是總共有多少的 Create 指令等等。&lt;/p&gt;
&lt;pre style=&quot;font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; font-size: 1em; line-height: 1.2em; margin: 1.2em 0px;&quot;&gt;&lt;code class=&quot;hljs language-java&quot; style=&quot;-moz-text-size-adjust: none; background-color: #f8f8f8; background: rgb(35, 36, 31) none repeat scroll 0% 0%; border-radius: 3px; border-radius: 3px; border: 1px solid rgb(204, 204, 204); border: 1px solid rgb(234, 234, 234); color: #f8f8f2; display: block !important; display: block; display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; overflow-x: auto; overflow: auto; padding: 0.5em 0.7em; padding: 0.5em; padding: 0px 0.3em; white-space: pre-wrap; white-space: pre;&quot;&gt;&lt;span class=&quot;hljs-annotation&quot; style=&quot;color: #75715e;&quot;&gt;@Slf&lt;/span&gt;4j
&lt;span class=&quot;hljs-annotation&quot; style=&quot;color: #75715e;&quot;&gt;@Component&lt;/span&gt;
&lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #f92672;&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-class&quot;&gt;&lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #66d9ef; color: #f92672;&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;hljs-title&quot; style=&quot;color: #a6e22e; color: #f8f8f2; font-style: italic;&quot;&gt;FakeEventProcessor&lt;/span&gt; &lt;/span&gt;{
    &lt;span class=&quot;hljs-function&quot; style=&quot;color: #f92672;&quot;&gt;&lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #66d9ef; color: #f92672;&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #66d9ef; color: #f92672;&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;hljs-title&quot; style=&quot;color: #a6e22e;&quot;&gt;receiveEvent&lt;/span&gt;&lt;span class=&quot;hljs-params&quot; style=&quot;color: #f8f8f2;&quot;&gt;(Event event)&lt;/span&gt; &lt;/span&gt;{
        log.info(&lt;span class=&quot;hljs-string&quot; style=&quot;color: #e6db74;&quot;&gt;&quot;Receive: {}&quot;&lt;/span&gt;, event);
    }
}

&lt;span class=&quot;hljs-annotation&quot; style=&quot;color: #75715e;&quot;&gt;@Builder&lt;/span&gt;
&lt;span class=&quot;hljs-annotation&quot; style=&quot;color: #75715e;&quot;&gt;@Getter&lt;/span&gt;
&lt;span class=&quot;hljs-annotation&quot; style=&quot;color: #75715e;&quot;&gt;@Accessors&lt;/span&gt;(fluent = &lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #f92672;&quot;&gt;true&lt;/span&gt;)
&lt;span class=&quot;hljs-annotation&quot; style=&quot;color: #75715e;&quot;&gt;@ToString&lt;/span&gt;
&lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #f92672;&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-class&quot;&gt;&lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #66d9ef; color: #f92672;&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;hljs-title&quot; style=&quot;color: #a6e22e; color: #f8f8f2; font-style: italic;&quot;&gt;Event&lt;/span&gt; &lt;/span&gt;{
    &lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #f92672;&quot;&gt;private&lt;/span&gt; String eventType;
    &lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #f92672;&quot;&gt;private&lt;/span&gt; String owner;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;完整的範例程式碼，可以參考 [&lt;a href=&quot;https://github.com/jimwayneyeh/otel-example/tree/050ce4068daa2f069b2e6b3cb42f8311a82d7aa9&quot;&gt;3&lt;/a&gt;]。&lt;/p&gt;
&lt;h2 id=&quot;gradle-&quot; style=&quot;border-bottom: 1px solid rgb(238, 238, 238); font-size: 1.4em; font-weight: bold; margin: 1.3em 0px 1em; padding: 0px;&quot;&gt;Gradle 設定&lt;/h2&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;在 Gradle 中，需要加入以下的 dependencies：&lt;/p&gt;
&lt;pre style=&quot;font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; font-size: 1em; line-height: 1.2em; margin: 1.2em 0px;&quot;&gt;&lt;code class=&quot;hljs language-java&quot; style=&quot;-moz-text-size-adjust: none; background-color: #f8f8f8; background: rgb(35, 36, 31) none repeat scroll 0% 0%; border-radius: 3px; border-radius: 3px; border: 1px solid rgb(204, 204, 204); border: 1px solid rgb(234, 234, 234); color: #f8f8f2; display: block !important; display: block; display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; overflow-x: auto; overflow: auto; padding: 0.5em 0.7em; padding: 0.5em; padding: 0px 0.3em; white-space: pre-wrap; white-space: pre;&quot;&gt;&lt;span class=&quot;hljs-comment&quot; style=&quot;color: #75715e;&quot;&gt;// BOMs&lt;/span&gt;
implementation(platform(&lt;span class=&quot;hljs-string&quot; style=&quot;color: #e6db74;&quot;&gt;&quot;io.opentelemetry:opentelemetry-bom:1.22.0&quot;&lt;/span&gt;))
implementation(platform(&lt;span class=&quot;hljs-string&quot; style=&quot;color: #e6db74;&quot;&gt;&quot;io.opentelemetry:opentelemetry-bom-alpha:1.22.0-alpha&quot;&lt;/span&gt;))

implementation(&lt;span class=&quot;hljs-string&quot; style=&quot;color: #e6db74;&quot;&gt;&quot;io.opentelemetry:opentelemetry-api&quot;&lt;/span&gt;)
implementation(&lt;span class=&quot;hljs-string&quot; style=&quot;color: #e6db74;&quot;&gt;&quot;io.opentelemetry:opentelemetry-sdk&quot;&lt;/span&gt;)
implementation(&lt;span class=&quot;hljs-string&quot; style=&quot;color: #e6db74;&quot;&gt;&quot;io.opentelemetry:opentelemetry-semconv&quot;&lt;/span&gt;)

&lt;span class=&quot;hljs-comment&quot; style=&quot;color: #75715e;&quot;&gt;// Exporter&lt;/span&gt;
implementation(&lt;span class=&quot;hljs-string&quot; style=&quot;color: #e6db74;&quot;&gt;&quot;io.opentelemetry:opentelemetry-exporter-logging&quot;&lt;/span&gt;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;這裡可以看到 BOM 會有兩個，其中 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;opentelemetry-bom-alpha&lt;/code&gt; 是用來設定 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;opentelemetry-semconv&lt;/code&gt; 的 BOM，而 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;opentelemetry-semconv&lt;/code&gt; 的用途，在我目前的範例程式裡，好像只有初始化 OTEL 時要給的 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;ResourceAttribute&lt;/code&gt; 會用到它…。另外因為這裡我先實驗的目標是最簡單的 Metrics，所以是採取 Logging 作為 Metrics 的 Exporter。換句話說，就是我寫入的 Metrics 會以 log 的形式被輸出。&lt;/p&gt;
&lt;h2 id=&quot;-opentelemetry&quot; style=&quot;border-bottom: 1px solid rgb(238, 238, 238); font-size: 1.4em; font-weight: bold; margin: 1.3em 0px 1em; padding: 0px;&quot;&gt;初始化 OpenTelemetry&lt;/h2&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;要使用 OTEL 的 Metrics 之前，需要先在系統裡初始化一個 OpenTelemetry 的 instance。我的範例中會是使用 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;@Configuration&lt;/code&gt; 來讓 Spring 幫忙注入。&lt;/p&gt;
&lt;pre style=&quot;font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; font-size: 1em; line-height: 1.2em; margin: 1.2em 0px;&quot;&gt;&lt;code class=&quot;hljs language-java&quot; style=&quot;-moz-text-size-adjust: none; background-color: #f8f8f8; background: rgb(35, 36, 31) none repeat scroll 0% 0%; border-radius: 3px; border-radius: 3px; border: 1px solid rgb(204, 204, 204); border: 1px solid rgb(234, 234, 234); color: #f8f8f2; display: block !important; display: block; display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; overflow-x: auto; overflow: auto; padding: 0.5em 0.7em; padding: 0.5em; padding: 0px 0.3em; white-space: pre-wrap; white-space: pre;&quot;&gt;&lt;span class=&quot;hljs-annotation&quot; style=&quot;color: #75715e;&quot;&gt;@Configuration&lt;/span&gt;
&lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #f92672;&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-class&quot;&gt;&lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #66d9ef; color: #f92672;&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;hljs-title&quot; style=&quot;color: #a6e22e; color: #f8f8f2; font-style: italic;&quot;&gt;OpenTelemetryConfiguration&lt;/span&gt; &lt;/span&gt;{

    &lt;span class=&quot;hljs-annotation&quot; style=&quot;color: #75715e;&quot;&gt;@Bean&lt;/span&gt;(destroyMethod = &lt;span class=&quot;hljs-string&quot; style=&quot;color: #e6db74;&quot;&gt;&quot;&quot;&lt;/span&gt;)
    &lt;span class=&quot;hljs-function&quot; style=&quot;color: #f92672;&quot;&gt;&lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #66d9ef; color: #f92672;&quot;&gt;public&lt;/span&gt; OpenTelemetry &lt;span class=&quot;hljs-title&quot; style=&quot;color: #a6e22e;&quot;&gt;getTelemetry&lt;/span&gt;&lt;span class=&quot;hljs-params&quot; style=&quot;color: #f8f8f2;&quot;&gt;()&lt;/span&gt; &lt;/span&gt;{
        var resource = Resource.getDefault()
                .merge(Resource.create(
                        Attributes.of(ResourceAttributes.SERVICE_NAME, &lt;span class=&quot;hljs-string&quot; style=&quot;color: #e6db74;&quot;&gt;&quot;otel-example&quot;&lt;/span&gt;)));

        var sdkMeterProvider = SdkMeterProvider.builder()
                .registerMetricReader(
                        PeriodicMetricReader.builder(LoggingMetricExporter.create())
                                .setInterval(Duration.ofSeconds(&lt;span class=&quot;hljs-number&quot; style=&quot;color: #ae81ff;&quot;&gt;1&lt;/span&gt;))
                                .build())
                .setResource(resource)
                .build();

        var openTelemetry = OpenTelemetrySdk.builder()
                .setMeterProvider(sdkMeterProvider)
                .buildAndRegisterGlobal();

        &lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #f92672;&quot;&gt;return&lt;/span&gt; openTelemetry;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;這裡首先用 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;Resource&lt;/code&gt; 做基本的環境設定，具體來說就只是設定一個 resource name 而已。接著因為我要產出 Metrics，所以需要的是 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;SdkMeterProvider&lt;/code&gt;。MeterProvider 的設定是輸出到 Logging，而且外面再包裝一層定時輸出的 MetricReader，設定為每一秒輸出一次。最後建出 OTEL 的 instance，把剛剛建立的 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;SdkMeterProvider&lt;/code&gt; 設定為它的 MeterProvider 即可。&lt;/p&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;在翻閱文件時，有個小細節是文件上有提到，如果是在為 library 建立 telemetry 的話，就建議不要 register global。雖然目前我還不太了解 register global 是什麼意思就是…。&lt;/p&gt;
&lt;h2 id=&quot;-aspect-method-metrics&quot; style=&quot;border-bottom: 1px solid rgb(238, 238, 238); font-size: 1.4em; font-weight: bold; margin: 1.3em 0px 1em; padding: 0px;&quot;&gt;建立 Aspect 為指定的 Method 插入 Metrics&lt;/h2&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;文章最開頭有提到，我想要在插入 Metrics 統計的同時，不去修改既有的商業邏輯，所以 Metrics 的統計應該要發生在別的 class 而不應該直接寫在 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;FakeEventProcessor&lt;/code&gt; 中。因此我會另外建立一個 Aspect class，這個 class 會讓 Spring 注入上面寫到的 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;OpenTelemetry&lt;/code&gt; instance，然後在每次 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;FakeEventProcessor&lt;/code&gt; 的 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;receiveEvent(..)&lt;/code&gt; 被呼叫時，都攔截執行並把 event 的內容紀錄在 Metrics 當中。&lt;/p&gt;
&lt;pre style=&quot;font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; font-size: 1em; line-height: 1.2em; margin: 1.2em 0px;&quot;&gt;&lt;code class=&quot;hljs language-java&quot; style=&quot;-moz-text-size-adjust: none; background-color: #f8f8f8; background: rgb(35, 36, 31) none repeat scroll 0% 0%; border-radius: 3px; border-radius: 3px; border: 1px solid rgb(204, 204, 204); border: 1px solid rgb(234, 234, 234); color: #f8f8f2; display: block !important; display: block; display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; overflow-x: auto; overflow: auto; padding: 0.5em 0.7em; padding: 0.5em; padding: 0px 0.3em; white-space: pre-wrap; white-space: pre;&quot;&gt;&lt;span class=&quot;hljs-annotation&quot; style=&quot;color: #75715e;&quot;&gt;@Slf&lt;/span&gt;4j
&lt;span class=&quot;hljs-annotation&quot; style=&quot;color: #75715e;&quot;&gt;@Aspect&lt;/span&gt;
&lt;span class=&quot;hljs-annotation&quot; style=&quot;color: #75715e;&quot;&gt;@Component&lt;/span&gt;
&lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #f92672;&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-class&quot;&gt;&lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #66d9ef; color: #f92672;&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;hljs-title&quot; style=&quot;color: #a6e22e; color: #f8f8f2; font-style: italic;&quot;&gt;TelemetryAspect&lt;/span&gt; &lt;/span&gt;{

    &lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #f92672;&quot;&gt;private&lt;/span&gt; OpenTelemetry telemetry;

    &lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #f92672;&quot;&gt;private&lt;/span&gt; Meter meter;
    &lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #f92672;&quot;&gt;private&lt;/span&gt; LongCounter eventCounter;

    &lt;span class=&quot;hljs-annotation&quot; style=&quot;color: #75715e;&quot;&gt;@Autowired&lt;/span&gt;
    &lt;span class=&quot;hljs-function&quot; style=&quot;color: #f92672;&quot;&gt;&lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #66d9ef; color: #f92672;&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-title&quot; style=&quot;color: #a6e22e;&quot;&gt;TelemetryAspect&lt;/span&gt;&lt;span class=&quot;hljs-params&quot; style=&quot;color: #f8f8f2;&quot;&gt;(OpenTelemetry telemetry)&lt;/span&gt; &lt;/span&gt;{
        log.trace(&lt;span class=&quot;hljs-string&quot; style=&quot;color: #e6db74;&quot;&gt;&quot;Initiate aspect...&quot;&lt;/span&gt;);
        &lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #f92672;&quot;&gt;this&lt;/span&gt;.telemetry = telemetry;
        initiateMeter();
    }

    &lt;span class=&quot;hljs-function&quot; style=&quot;color: #f92672;&quot;&gt;&lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #66d9ef; color: #f92672;&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #66d9ef; color: #f92672;&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;hljs-title&quot; style=&quot;color: #a6e22e;&quot;&gt;initiateMeter&lt;/span&gt;&lt;span class=&quot;hljs-params&quot; style=&quot;color: #f8f8f2;&quot;&gt;()&lt;/span&gt; &lt;/span&gt;{
        meter = telemetry.meterBuilder(&lt;span class=&quot;hljs-string&quot; style=&quot;color: #e6db74;&quot;&gt;&quot;event-consumer&quot;&lt;/span&gt;)
                .setInstrumentationVersion(&lt;span class=&quot;hljs-string&quot; style=&quot;color: #e6db74;&quot;&gt;&quot;1.0.0&quot;&lt;/span&gt;)
                .build();

        eventCounter = meter.counterBuilder(&lt;span class=&quot;hljs-string&quot; style=&quot;color: #e6db74;&quot;&gt;&quot;eventType&quot;&lt;/span&gt;)
                .setDescription(&lt;span class=&quot;hljs-string&quot; style=&quot;color: #e6db74;&quot;&gt;&quot;Metrics for the event consuming.&quot;&lt;/span&gt;)
                .setUnit(&lt;span class=&quot;hljs-string&quot; style=&quot;color: #e6db74;&quot;&gt;&quot;1&quot;&lt;/span&gt;)
                .build();
    }

    &lt;span class=&quot;hljs-annotation&quot; style=&quot;color: #75715e;&quot;&gt;@Before&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot; style=&quot;color: #e6db74;&quot;&gt;&quot;execution(* tw.jimwayneyeh.example.otel.FakeEventProcessor.receiveEvent(..))&quot;&lt;/span&gt;)
    &lt;span class=&quot;hljs-function&quot; style=&quot;color: #f92672;&quot;&gt;&lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #66d9ef; color: #f92672;&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #66d9ef; color: #f92672;&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;hljs-title&quot; style=&quot;color: #a6e22e;&quot;&gt;before&lt;/span&gt;&lt;span class=&quot;hljs-params&quot; style=&quot;color: #f8f8f2;&quot;&gt;(JoinPoint joinPoint)&lt;/span&gt; &lt;/span&gt;{
        var event = (Event) joinPoint.getArgs()[&lt;span class=&quot;hljs-number&quot; style=&quot;color: #ae81ff;&quot;&gt;0&lt;/span&gt;];
        eventCounter.add(&lt;span class=&quot;hljs-number&quot; style=&quot;color: #ae81ff;&quot;&gt;1&lt;/span&gt;, Attributes.of(
                AttributeKey.stringKey(&lt;span class=&quot;hljs-string&quot; style=&quot;color: #e6db74;&quot;&gt;&quot;eventType&quot;&lt;/span&gt;), event.eventType(),
                AttributeKey.stringKey(&lt;span class=&quot;hljs-string&quot; style=&quot;color: #e6db74;&quot;&gt;&quot;owner&quot;&lt;/span&gt;), event.owner()));
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;在上述的程式碼中，首先我在 Aspect 被初始化時，會去初始化一個 Meter，因為這個 Meter 是統計數字，所以就直接命名為 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;eventCounter&lt;/code&gt;。接著在 JointPoint 中，每次 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;receiveEvent(..)&lt;/code&gt; 被呼叫時，AOP 會攔截這個 method 呼叫，取得 method 呼叫中送進來的 event 物件，並且把為 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;eventCounter&lt;/code&gt; +1。其中 +1 時加的對象，是對 eventType &amp;amp; owner 做 +1。&lt;/p&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;最後執行的結果會長這樣：&lt;/p&gt;
&lt;pre style=&quot;font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; font-size: 1em; line-height: 1.2em; margin: 1.2em 0px;&quot;&gt;&lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border-radius: 3px; border: 1px solid rgb(204, 204, 204); border: 1px solid rgb(234, 234, 234); display: block !important; display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; overflow: auto; padding: 0.5em 0.7em; padding: 0px 0.3em; white-space: pre-wrap; white-space: pre;&quot;&gt;INFO  t.j.e.o.FakeEventProcessor [main] Receive: Event(eventType=update, owner=owner-0)
INFO  t.j.e.o.FakeEventProcessor [main] Receive: Event(eventType=update, owner=owner-1)
INFO  t.j.e.o.FakeEventProcessor [main] Receive: Event(eventType=update, owner=owner-1)
INFO  t.j.e.o.FakeEventProcessor [main] Receive: Event(eventType=update, owner=owner-1)
INFO  t.j.e.o.FakeEventProcessor [main] Receive: Event(eventType=delete, owner=owner-1)
INFO  t.j.e.o.FakeEventProcessor [main] Receive: Event(eventType=create, owner=owner-0)
INFO  t.j.e.o.FakeEventProcessor [main] Receive: Event(eventType=update, owner=owner-0)
INFO  t.j.e.o.FakeEventProcessor [main] Receive: Event(eventType=delete, owner=owner-1)
INFO  t.j.e.o.FakeEventProcessor [main] Receive: Event(eventType=create, owner=owner-0)
INFO  t.j.e.o.FakeEventProcessor [main] Receive: Event(eventType=delete, owner=owner-0)
INFO  t.j.e.otel.Run [main] Sleep...
INFO  i.o.e.l.LoggingMetricExporter [PeriodicMetricReader-1] Received a collection of 1 metrics for export.
INFO  i.o.e.l.LoggingMetricExporter [PeriodicMetricReader-1] metric: ImmutableMetricData{resource=Resource{schemaUrl=null, attributes={service.name=&quot;otel-example&quot;, telemetry.sdk.language=&quot;java&quot;, telemetry.sdk.name=&quot;opentelemetry&quot;, telemetry.sdk.version=&quot;1.22.0&quot;}}, instrumentationScopeInfo=InstrumentationScopeInfo{name=event-consumer, version=1.0.0, schemaUrl=null, attributes={}}, name=eventType, description=Metrics for the event consuming., unit=1, type=LONG_SUM, data=ImmutableSumData{points=[ImmutableLongPointData{startEpochNanos=1674906020221141200, epochNanos=1674906021227565100, attributes={eventType=&quot;delete&quot;, owner=&quot;owner-1&quot;}, value=2, exemplars=[]}, ImmutableLongPointData{startEpochNanos=1674906020221141200, epochNanos=1674906021227565100, attributes={eventType=&quot;create&quot;, owner=&quot;owner-0&quot;}, value=2, exemplars=[]}, ImmutableLongPointData{startEpochNanos=1674906020221141200, epochNanos=1674906021227565100, attributes={eventType=&quot;delete&quot;, owner=&quot;owner-0&quot;}, value=1, exemplars=[]}, ImmutableLongPointData{startEpochNanos=1674906020221141200, epochNanos=1674906021227565100, attributes={eventType=&quot;update&quot;, owner=&quot;owner-0&quot;}, value=2, exemplars=[]}, ImmutableLongPointData{startEpochNanos=1674906020221141200, epochNanos=1674906021227565100, attributes={eventType=&quot;update&quot;, owner=&quot;owner-1&quot;}, value=3, exemplars=[]}], monotonic=true, aggregationTemporality=CUMULATIVE}}
&lt;/code&gt;&lt;/pre&gt;&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;最上面 10 行是在 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;FakeEventProcessor&lt;/code&gt; 裡面寫的 log，然後因為我的 logging 設定為每秒輸出，所以故意讓主程式睡了一下，以避免主程式跑完迴圈以後就自己關掉了 XD。&lt;/p&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;&lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;i.o.e.l.LoggingMetricExporter&lt;/code&gt; 這段就是 Metrics 輸出的內容，稍微格式化一下：&lt;/p&gt;
&lt;pre style=&quot;font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; font-size: 1em; line-height: 1.2em; margin: 1.2em 0px;&quot;&gt;&lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border-radius: 3px; border: 1px solid rgb(204, 204, 204); border: 1px solid rgb(234, 234, 234); display: block !important; display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; overflow: auto; padding: 0.5em 0.7em; padding: 0px 0.3em; white-space: pre-wrap; white-space: pre;&quot;&gt;ImmutableMetricData {
    resource = Resource {
        schemaUrl = null, attributes = {
            service.name = &quot;otel-example&quot;,
            telemetry.sdk.language = &quot;java&quot;,
            telemetry.sdk.name = &quot;opentelemetry&quot;,
            telemetry.sdk.version = &quot;1.22.0&quot;
        }
    }, instrumentationScopeInfo = InstrumentationScopeInfo {
        name = event - consumer, version = 1.0 .0, schemaUrl = null, attributes = {}
    }, name = eventType, description = Metrics
    for the event consuming., unit = 1, type = LONG_SUM, data = ImmutableSumData {
        points = [ImmutableLongPointData {
            startEpochNanos = 1674906020221141200, epochNanos = 1674906021227565100, attributes = {
                eventType = &quot;delete&quot;,
                owner = &quot;owner-1&quot;
            }, value = 2, exemplars = []
        }, ImmutableLongPointData {
            startEpochNanos = 1674906020221141200, epochNanos = 1674906021227565100, attributes = {
                eventType = &quot;create&quot;,
                owner = &quot;owner-0&quot;
            }, value = 2, exemplars = []
        }, ImmutableLongPointData {
            startEpochNanos = 1674906020221141200, epochNanos = 1674906021227565100, attributes = {
                eventType = &quot;delete&quot;,
                owner = &quot;owner-0&quot;
            }, value = 1, exemplars = []
        }, ImmutableLongPointData {
            startEpochNanos = 1674906020221141200, epochNanos = 1674906021227565100, attributes = {
                eventType = &quot;update&quot;,
                owner = &quot;owner-0&quot;
            }, value = 2, exemplars = []
        }, ImmutableLongPointData {
            startEpochNanos = 1674906020221141200, epochNanos = 1674906021227565100, attributes = {
                eventType = &quot;update&quot;,
                owner = &quot;owner-1&quot;
            }, value = 3, exemplars = []
        }], monotonic = true, aggregationTemporality = CUMULATIVE
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;可以看到，Metrics 紀錄到的結果是：&lt;/p&gt;
&lt;ul style=&quot;margin: 1.2em 0px; padding-left: 2em;&quot;&gt;
&lt;li style=&quot;margin: 0.5em 0px;&quot;&gt;&lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;owner-1&lt;/code&gt; &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;delete&lt;/code&gt;: 1&lt;/li&gt;
&lt;li style=&quot;margin: 0.5em 0px;&quot;&gt;&lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;owner-0&lt;/code&gt; &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;create&lt;/code&gt;: 2&lt;/li&gt;
&lt;li style=&quot;margin: 0.5em 0px;&quot;&gt;&lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;owner-0&lt;/code&gt; &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;delete&lt;/code&gt;: 1&lt;/li&gt;
&lt;li style=&quot;margin: 0.5em 0px;&quot;&gt;&lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;owner-0&lt;/code&gt; &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;update&lt;/code&gt;: 2&lt;/li&gt;
&lt;li style=&quot;margin: 0.5em 0px;&quot;&gt;&lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;owner-1&lt;/code&gt; &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;update&lt;/code&gt;: 3&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;確實結果是符合 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;FakeEventProcessor&lt;/code&gt; 的結果～。&lt;/p&gt;
&lt;div style=&quot;font-size: 0em; height: 0; margin: 0; max-height: 0; max-width: 0; overflow: hidden; padding: 0; width: 0;&quot; title=&quot;MDH:PHA+5o6l57qM6JGX5YmN6Z2i55qEIFNwcmluZyBBT1DvvIzmjqXokZfopoHlnKggU3ByaW5nIEFP
UCDkuYvkuIrmjqUgT3BlblRlbGVtZXRyeeOAguWvpumam+S4iuaOpSBPcGVuVGVsZW1ldHJ5IOaJ
jeaYr+aIkeeahOebrueahO+8jEFPUCDlj6rmmK/luIzmnJvmjqUgbWV0cmljL3RyYWNpbmcg5pmC
5Y+v5Lul5LiN6KaB56Kw5ZWG5qWt6YKP6Lyv55qE56iL5byP56K86ICM5beyIFhE44CCPGJyPjxi
cj4jIOS7gOm6vOaYryBPcGVuVGVsZW1ldHJ5PGJyPjxicj7nsKHopoHkvoboqqrvvIxPcGVuVGVs
ZW1ldHJ577yI57Ch56ixIE9URUzvvInmmK/ntZDlkIjkuoYgQ05DRiDnmbzlsZXnmoQgT3BlblRy
YWNpbmcg5ZKMIEdvb2dsZSDnmbzlsZXnmoQgT3BlbkNlbnN1cyDlhanlgIvlsIjmoYjlvoznmoTn
tZDmnpwgW1sxXVtvdGVsLWhpc3RvcnldXe+8jOebrueahOaYr+eCuuS6huaPkOS+myBvYnNlcnZp
YmlsaXR5IHRlbGVtZXRyeeOAguWug+a2teiTi+S6huS4ieWkp+S4u+mhjO+8mnRyYWNpbmfjgIFt
ZXRyaWNz44CBbG9nc+OAgnRyYWNpbmcg6IO95aSg5pW05ZCI5LiK5LiL5ri455qE6Zec5L+C77yM
5o+Q5L6b5a6M5pW055qEIHByb2ZpbGluZyDos4foqIrvvJttZXRyaWNzIOiDveWkoOaPkOS+m+e1
seioiOWei+eahOaVuOaTmu+8jOiuk+aIkeWAkeWPr+S7peW/q+mAn+S6huino+ezu+e1seeahOeL
gOaFi++8m2xvZ3Mg5YmH5piv5paH5a2X5Z6L55qE6LOH5paZ44CCPGJyPjxicj7kuI3pgY7lsLHm
iJHnm67liY3nmoTkuobop6PvvIxPVEVMIOWboOeCuuaYr+WcqCAyMDE5IOW5tOaJjeWQiOS9te+8
jOWIsOePvuWcqOS8vOS5jumChOaYr+aykuacieWIsOmdnuW4uOWujOaVtO+8jOWQhOWAi+iqnuio
gOeahOaUr+aPtOacieS6m+WPr+iDvemChOaYr+aciem7nue8uua8j+OAguS7pSBKYXZhIOS+huiq
qu+8jOebruWJjSB0cmFjaW5nIOWSjCBtZXRyaWNzIOeahOaUr+aPtOaYr+avlOi8g+Wlve+8jGxv
Z3Mg5YmH6YKE6JmV5pa85a+m6amX5Lit44CC57Sw56+A5Y+v5Lul5Y+D6ICDIE9URUwg5a6Y57ay
5Lit6Zec5pa8IEphdmEgU0RLIOeahOeLgOaFi+mggSBbWzJdW290ZWwtamF2YS1zdGF0dXNdXeOA
gjxjb2RlPjxicj48YnI+PC9jb2RlPiMgT3BlblRlbGVtZXRyeSBNZXRyaWNzIOW7uue9rjxicj48
L3A+PHA+PGJyPuWboOeCuuaIkeebruWJjeeahOebrueahOaYr+imgeW7uueriyBNZXRyaWNzIOea
hOeSsOWig++8jOaKiuaIkeeahCBKYXZhIGFwcGxpY2F0aW9uIOeahOS4gOS6m+iHquioguizh+io
iui8uOWHuuWIsCBNZXRyaWNzIOS4iu+8jOiuk+aIkeWAkeW+l+S7pemAj+mBjue1seioiOizh+io
iuS6huino+ezu+e1seeahOeLgOaFi++8jOaJgOS7pemAmeevh+S4u+imgeWPquacg+e0gOmMhOmX
nOaWvCBNZXRyaWNzIOeahOW7uue9ruevhOS+i+OAgjxicj48YnI+IyMg56+E5L6L55qE55uu55qE
PGJyPjxicj7pppblhYjnqI3lvq7nsKHku4vkuIDkuIvvvIzpgJnnr4fmlofnq6DkuK3nmoTnr4Tk
vovmmK/oqK3lrprmiJDku4Dpurzog4zmma/jgIHopoHop6Pmsbrku4DpurzllY/poYzjgILmib/o
pbLkuIrkuIDnr4cgU3ByaW5nIEFPUCDmlofnq6DvvIzmiJHmnInkuIDlgIsgbWV0aG9kIOWmguS4
i++8jOmAmeWAiyBtZXRob2Qg55qE6KeS6Imy5piv5LiA5YCLIGV2ZW50IGNvbnN1bWVy77yM5bCx
5piv5Zyo5pS25p+Q56iuIHF1ZXVlIOmAgemBjuS+hueahOioiuaBr+OAguaWh+eroOeahOebruao
meaYr+imgemAj+mBjiBBT1Ag5o+S5YWl5LiA5YCL6IO95aSg57Wx6KiI5pS25Yiw55qE6KiK5oGv
55qE6aGe5Z6L55qEIE1ldHJpY3PjgILmnInkuobpgJnlgIsgTWV0cmljc++8jOWwseWPr+S7peef
pemBk+ezu+e1see4veWFseiZleeQhuS7gOm6vOmHj+e0mueahOioiuaBr++8jOS4puS4lOS5n+WP
r+S7peeUqOS+huWBmuabtOe0sOe3u+eahOe1seioiO+8jOS+i+WmgiBFdmVudCDnmoToqK3oqIjm
mK/mnInliIYgYGV2ZW50VHlwZWAg5ZKMIGBvd25lcmDvvIzku6PooajnmoTmmK/mn5DlgIvkurrp
gIHlh7rnmoQgQ3JlYXRlL1VwZGF0ZS9EZWxldGUg5oyH5Luk77yM6ICMIE1ldHJpY3Mg5biM5pyb
6IO95aSg6K6T5oiR5YCR5pyJ6IO95Yqb5b6X55+l5L6L5aaC5Zyo5oyH5a6a5pmC6ZaT5Y2A6ZaT
5YWn77yM5p+Q5YCL5Lq66YCB5LqG5aSa5bCR5oyH5Luk44CB5oiW6ICF5piv57i95YWx5pyJ5aSa
5bCR55qEIENyZWF0ZSDmjIfku6TnrYnnrYnjgII8YnI+PGJyPmBgYGphdmE8YnI+QFNsZjRqPGJy
PkBDb21wb25lbnQ8YnI+cHVibGljIGNsYXNzIEZha2VFdmVudFByb2Nlc3NvciB7PGJyPiZuYnNw
OyZuYnNwOyZuYnNwOyBwdWJsaWMgdm9pZCByZWNlaXZlRXZlbnQoRXZlbnQgZXZlbnQpIHs8YnI+
Jm5ic3A7Jm5ic3A7Jm5ic3A7Jm5ic3A7Jm5ic3A7Jm5ic3A7Jm5ic3A7IGxvZy5pbmZvKCJSZWNl
aXZlOiB7fSIsIGV2ZW50KTs8YnI+Jm5ic3A7Jm5ic3A7Jm5ic3A7IH08YnI+fTxicj48YnI+QEJ1
aWxkZXI8YnI+QEdldHRlcjxicj5AQWNjZXNzb3JzKGZsdWVudCA9IHRydWUpPGJyPkBUb1N0cmlu
Zzxicj5wdWJsaWMgY2xhc3MgRXZlbnQgezxicj4mbmJzcDsmbmJzcDsmbmJzcDsgcHJpdmF0ZSBT
dHJpbmcgZXZlbnRUeXBlOzxicj4mbmJzcDsmbmJzcDsmbmJzcDsgcHJpdmF0ZSBTdHJpbmcgb3du
ZXI7PGJyPn08YnI+YGBgPGJyPjxicj7lrozmlbTnmoTnr4TkvovnqIvlvI/norzvvIzlj6/ku6Xl
j4PogIMgW1szXVtvdGVsLWV4YW1wbGVdXeOAgjxicj48YnI+IyMgR3JhZGxlIOioreWumjxicj48
YnI+5ZyoIEdyYWRsZSDkuK3vvIzpnIDopoHliqDlhaXku6XkuIvnmoQgZGVwZW5kZW5jaWVz77ya
PGJyPjxicj5gYGBqYXZhPGJyPi8vIEJPTXM8YnI+aW1wbGVtZW50YXRpb24ocGxhdGZvcm0oImlv
Lm9wZW50ZWxlbWV0cnk6b3BlbnRlbGVtZXRyeS1ib206MS4yMi4wIikpPGJyPmltcGxlbWVudGF0
aW9uKHBsYXRmb3JtKCJpby5vcGVudGVsZW1ldHJ5Om9wZW50ZWxlbWV0cnktYm9tLWFscGhhOjEu
MjIuMC1hbHBoYSIpKTxicj48YnI+aW1wbGVtZW50YXRpb24oImlvLm9wZW50ZWxlbWV0cnk6b3Bl
bnRlbGVtZXRyeS1hcGkiKTxicj5pbXBsZW1lbnRhdGlvbigiaW8ub3BlbnRlbGVtZXRyeTpvcGVu
dGVsZW1ldHJ5LXNkayIpPGJyPmltcGxlbWVudGF0aW9uKCJpby5vcGVudGVsZW1ldHJ5Om9wZW50
ZWxlbWV0cnktc2VtY29udiIpPGJyPjxicj4vLyBFeHBvcnRlcjxicj5pbXBsZW1lbnRhdGlvbigi
aW8ub3BlbnRlbGVtZXRyeTpvcGVudGVsZW1ldHJ5LWV4cG9ydGVyLWxvZ2dpbmciKTxicj5gYGA8
YnI+PGJyPumAmeijoeWPr+S7peeci+WIsCBCT00g5pyD5pyJ5YWp5YCL77yM5YW25LitIGBvcGVu
dGVsZW1ldHJ5LWJvbS1hbHBoYWAg5piv55So5L6G6Kit5a6aIGBvcGVudGVsZW1ldHJ5LXNlbWNv
bnZgIOeahCBCT03vvIzogIwgYG9wZW50ZWxlbWV0cnktc2VtY29udmAg55qE55So6YCU77yM5Zyo
5oiR55uu5YmN55qE56+E5L6L56iL5byP6KOh77yM5aW95YOP5Y+q5pyJ5Yid5aeL5YyWIE9URUwg
5pmC6KaB57Wm55qEIGBSZXNvdXJjZUF0dHJpYnV0ZWAg5pyD55So5Yiw5a6DLi4u44CC5Y+m5aSW
5Zug54K66YCZ6KOh5oiR5YWI5a+m6amX55qE55uu5qiZ5piv5pyA57Ch5Zau55qEIE1ldHJpY3Pv
vIzmiYDku6XmmK/mjqHlj5YgTG9nZ2luZyDkvZzngrogTWV0cmljcyDnmoQgRXhwb3J0ZXLjgILm
j5vlj6XoqbHoqqrvvIzlsLHmmK/miJHlr6vlhaXnmoQgTWV0cmljcyDmnIPku6UgbG9nIOeahOW9
ouW8j+iiq+i8uOWHuuOAgjxicj48YnI+IyMg5Yid5aeL5YyWIE9wZW5UZWxlbWV0cnk8YnI+PGJy
PuimgeS9v+eUqCBPVEVMIOeahCBNZXRyaWNzIOS5i+WJje+8jOmcgOimgeWFiOWcqOezu+e1seij
oeWIneWni+WMluS4gOWAiyBPcGVuVGVsZW1ldHJ5IOeahCBpbnN0YW5jZeOAguaIkeeahOevhOS+
i+S4reacg+aYr+S9v+eUqCBgQENvbmZpZ3VyYXRpb25gIOS+huiukyBTcHJpbmcg5bmr5b+Z5rOo
5YWl44CCPGJyPjxicj5gYGBqYXZhPGJyPkBDb25maWd1cmF0aW9uPGJyPnB1YmxpYyBjbGFzcyBP
cGVuVGVsZW1ldHJ5Q29uZmlndXJhdGlvbiB7PGJyPjxicj4mbmJzcDsmbmJzcDsmbmJzcDsgQEJl
YW4oZGVzdHJveU1ldGhvZCA9ICIiKTxicj4mbmJzcDsmbmJzcDsmbmJzcDsgcHVibGljIE9wZW5U
ZWxlbWV0cnkgZ2V0VGVsZW1ldHJ5KCkgezxicj4mbmJzcDsmbmJzcDsmbmJzcDsmbmJzcDsmbmJz
cDsmbmJzcDsmbmJzcDsgdmFyIHJlc291cmNlID0gUmVzb3VyY2UuZ2V0RGVmYXVsdCgpPGJyPiZu
YnNwOyZuYnNwOyZuYnNwOyZuYnNwOyZuYnNwOyZuYnNwOyZuYnNwOyZuYnNwOyZuYnNwOyZuYnNw
OyZuYnNwOyZuYnNwOyZuYnNwOyZuYnNwOyZuYnNwOyAubWVyZ2UoUmVzb3VyY2UuY3JlYXRlKDxi
cj4mbmJzcDsmbmJzcDsmbmJzcDsmbmJzcDsmbmJzcDsmbmJzcDsmbmJzcDsmbmJzcDsmbmJzcDsm
bmJzcDsmbmJzcDsmbmJzcDsmbmJzcDsmbmJzcDsmbmJzcDsmbmJzcDsmbmJzcDsmbmJzcDsmbmJz
cDsmbmJzcDsmbmJzcDsmbmJzcDsmbmJzcDsgQXR0cmlidXRlcy5vZihSZXNvdXJjZUF0dHJpYnV0
ZXMuU0VSVklDRV9OQU1FLCAib3RlbC1leGFtcGxlIikpKTs8YnI+PGJyPiZuYnNwOyZuYnNwOyZu
YnNwOyZuYnNwOyZuYnNwOyZuYnNwOyZuYnNwOyB2YXIgc2RrTWV0ZXJQcm92aWRlciA9IFNka01l
dGVyUHJvdmlkZXIuYnVpbGRlcigpPGJyPiZuYnNwOyZuYnNwOyZuYnNwOyZuYnNwOyZuYnNwOyZu
YnNwOyZuYnNwOyZuYnNwOyZuYnNwOyZuYnNwOyZuYnNwOyZuYnNwOyZuYnNwOyZuYnNwOyZuYnNw
OyAucmVnaXN0ZXJNZXRyaWNSZWFkZXIoPGJyPiZuYnNwOyZuYnNwOyZuYnNwOyZuYnNwOyZuYnNw
OyZuYnNwOyZuYnNwOyZuYnNwOyZuYnNwOyZuYnNwOyZuYnNwOyZuYnNwOyZuYnNwOyZuYnNwOyZu
YnNwOyZuYnNwOyZuYnNwOyZuYnNwOyZuYnNwOyZuYnNwOyZuYnNwOyZuYnNwOyZuYnNwOyBQZXJp
b2RpY01ldHJpY1JlYWRlci5idWlsZGVyKExvZ2dpbmdNZXRyaWNFeHBvcnRlci5jcmVhdGUoKSk8
YnI+Jm5ic3A7Jm5ic3A7Jm5ic3A7Jm5ic3A7Jm5ic3A7Jm5ic3A7Jm5ic3A7Jm5ic3A7Jm5ic3A7
Jm5ic3A7Jm5ic3A7Jm5ic3A7Jm5ic3A7Jm5ic3A7Jm5ic3A7Jm5ic3A7Jm5ic3A7Jm5ic3A7Jm5i
c3A7Jm5ic3A7Jm5ic3A7Jm5ic3A7Jm5ic3A7Jm5ic3A7Jm5ic3A7Jm5ic3A7Jm5ic3A7Jm5ic3A7
Jm5ic3A7Jm5ic3A7Jm5ic3A7IC5zZXRJbnRlcnZhbChEdXJhdGlvbi5vZlNlY29uZHMoMSkpPGJy
PiZuYnNwOyZuYnNwOyZuYnNwOyZuYnNwOyZuYnNwOyZuYnNwOyZuYnNwOyZuYnNwOyZuYnNwOyZu
YnNwOyZuYnNwOyZuYnNwOyZuYnNwOyZuYnNwOyZuYnNwOyZuYnNwOyZuYnNwOyZuYnNwOyZuYnNw
OyZuYnNwOyZuYnNwOyZuYnNwOyZuYnNwOyZuYnNwOyZuYnNwOyZuYnNwOyZuYnNwOyZuYnNwOyZu
YnNwOyZuYnNwOyZuYnNwOyAuYnVpbGQoKSk8YnI+Jm5ic3A7Jm5ic3A7Jm5ic3A7Jm5ic3A7Jm5i
c3A7Jm5ic3A7Jm5ic3A7Jm5ic3A7Jm5ic3A7Jm5ic3A7Jm5ic3A7Jm5ic3A7Jm5ic3A7Jm5ic3A7
Jm5ic3A7IC5zZXRSZXNvdXJjZShyZXNvdXJjZSk8YnI+Jm5ic3A7Jm5ic3A7Jm5ic3A7Jm5ic3A7
Jm5ic3A7Jm5ic3A7Jm5ic3A7Jm5ic3A7Jm5ic3A7Jm5ic3A7Jm5ic3A7Jm5ic3A7Jm5ic3A7Jm5i
c3A7Jm5ic3A7IC5idWlsZCgpOzxicj48YnI+Jm5ic3A7Jm5ic3A7Jm5ic3A7Jm5ic3A7Jm5ic3A7
Jm5ic3A7Jm5ic3A7IHZhciBvcGVuVGVsZW1ldHJ5ID0gT3BlblRlbGVtZXRyeVNkay5idWlsZGVy
KCk8YnI+Jm5ic3A7Jm5ic3A7Jm5ic3A7Jm5ic3A7Jm5ic3A7Jm5ic3A7Jm5ic3A7Jm5ic3A7Jm5i
c3A7Jm5ic3A7Jm5ic3A7Jm5ic3A7Jm5ic3A7Jm5ic3A7Jm5ic3A7IC5zZXRNZXRlclByb3ZpZGVy
KHNka01ldGVyUHJvdmlkZXIpPGJyPiZuYnNwOyZuYnNwOyZuYnNwOyZuYnNwOyZuYnNwOyZuYnNw
OyZuYnNwOyZuYnNwOyZuYnNwOyZuYnNwOyZuYnNwOyZuYnNwOyZuYnNwOyZuYnNwOyZuYnNwOyAu
YnVpbGRBbmRSZWdpc3Rlckdsb2JhbCgpOzxicj48YnI+Jm5ic3A7Jm5ic3A7Jm5ic3A7Jm5ic3A7
Jm5ic3A7Jm5ic3A7Jm5ic3A7IHJldHVybiBvcGVuVGVsZW1ldHJ5Ozxicj4mbmJzcDsmbmJzcDsm
bmJzcDsgfTxicj59PGJyPmBgYDxicj48YnI+6YCZ6KOh6aaW5YWI55SoIGBSZXNvdXJjZWAg5YGa
5Z+65pys55qE55Kw5aKD6Kit5a6a77yM5YW36auU5L6G6Kqq5bCx5Y+q5piv6Kit5a6a5LiA5YCL
IHJlc291cmNlIG5hbWUg6ICM5bey44CC5o6l6JGX5Zug54K65oiR6KaB55Si5Ye6IE1ldHJpY3Pv
vIzmiYDku6XpnIDopoHnmoTmmK8gYFNka01ldGVyUHJvdmlkZXJg44CCTWV0ZXJQcm92aWRlciDn
moToqK3lrprmmK/ovLjlh7rliLAgTG9nZ2luZ++8jOiAjOS4lOWklumdouWGjeWMheijneS4gOWx
pOWumuaZgui8uOWHuueahCBNZXRyaWNSZWFkZXLvvIzoqK3lrprngrrmr4/kuIDnp5LovLjlh7rk
uIDmrKHjgILmnIDlvozlu7rlh7ogT1RFTCDnmoQgaW5zdGFuY2XvvIzmiorliZvliZvlu7rnq4vn
moQgYFNka01ldGVyUHJvdmlkZXJgIOioreWumueCuuWug+eahCBNZXRlclByb3ZpZGVyIOWNs+WP
r+OAgjxicj48YnI+5Zyo57+76Zax5paH5Lu25pmC77yM5pyJ5YCL5bCP57Sw56+A5piv5paH5Lu2
5LiK5pyJ5o+Q5Yiw77yM5aaC5p6c5piv5Zyo54K6IGxpYnJhcnkg5bu656uLIHRlbGVtZXRyeSDn
moToqbHvvIzlsLHlu7rorbDkuI3opoEgcmVnaXN0ZXIgZ2xvYmFs44CC6ZuW54S255uu5YmN5oiR
6YKE5LiN5aSq5LqG6KejIHJlZ2lzdGVyIGdsb2JhbCDmmK/ku4DpurzmhI/mgJ3lsLHmmK8uLi7j
gII8YnI+PGJyPiMjIOW7uueriyBBc3BlY3Qg54K65oyH5a6a55qEIE1ldGhvZCDmj5LlhaUgTWV0
cmljczxicj48YnI+5paH56ug5pyA6ZaL6aCt5pyJ5o+Q5Yiw77yM5oiR5oOz6KaB5Zyo5o+S5YWl
IE1ldHJpY3Mg57Wx6KiI55qE5ZCM5pmC77yM5LiN5Y675L+u5pS55pei5pyJ55qE5ZWG5qWt6YKP
6Lyv77yM5omA5LulIE1ldHJpY3Mg55qE57Wx6KiI5oeJ6Kmy6KaB55m855Sf5Zyo5Yil55qEIGNs
YXNzIOiAjOS4jeaHieipsuebtOaOpeWvq+WcqCBgRmFrZUV2ZW50UHJvY2Vzc29yYCDkuK3jgILl
m6DmraTmiJHmnIPlj6blpJblu7rnq4vkuIDlgIsgQXNwZWN0IGNsYXNz77yM6YCZ5YCLIGNsYXNz
IOacg+iukyBTcHJpbmcg5rOo5YWl5LiK6Z2i5a+r5Yiw55qEIGBPcGVuVGVsZW1ldHJ5YCBpbnN0
YW5jZe+8jOeEtuW+jOWcqOavj+asoSBgRmFrZUV2ZW50UHJvY2Vzc29yYCDnmoQgYHJlY2VpdmVF
dmVudCguLilgIOiiq+WRvOWPq+aZgu+8jOmDveaUlOaIquWft+ihjOS4puaKiiBldmVudCDnmoTl
haflrrnntIDpjITlnKggTWV0cmljcyDnlbbkuK3jgII8YnI+PGJyPmBgYGphdmE8YnI+QFNsZjRq
PGJyPkBBc3BlY3Q8YnI+QENvbXBvbmVudDxicj5wdWJsaWMgY2xhc3MgVGVsZW1ldHJ5QXNwZWN0
IHs8YnI+PGJyPiZuYnNwOyZuYnNwOyZuYnNwOyBwcml2YXRlIE9wZW5UZWxlbWV0cnkgdGVsZW1l
dHJ5Ozxicj48YnI+Jm5ic3A7Jm5ic3A7Jm5ic3A7IHByaXZhdGUgTWV0ZXIgbWV0ZXI7PGJyPiZu
YnNwOyZuYnNwOyZuYnNwOyBwcml2YXRlIExvbmdDb3VudGVyIGV2ZW50Q291bnRlcjs8YnI+PGJy
PiZuYnNwOyZuYnNwOyZuYnNwOyBAQXV0b3dpcmVkPGJyPiZuYnNwOyZuYnNwOyZuYnNwOyBwdWJs
aWMgVGVsZW1ldHJ5QXNwZWN0KE9wZW5UZWxlbWV0cnkgdGVsZW1ldHJ5KSB7PGJyPiZuYnNwOyZu
YnNwOyZuYnNwOyZuYnNwOyZuYnNwOyZuYnNwOyZuYnNwOyBsb2cudHJhY2UoIkluaXRpYXRlIGFz
cGVjdC4uLiIpOzxicj4mbmJzcDsmbmJzcDsmbmJzcDsmbmJzcDsmbmJzcDsmbmJzcDsmbmJzcDsg
dGhpcy50ZWxlbWV0cnkgPSB0ZWxlbWV0cnk7PGJyPiZuYnNwOyZuYnNwOyZuYnNwOyZuYnNwOyZu
YnNwOyZuYnNwOyZuYnNwOyBpbml0aWF0ZU1ldGVyKCk7PGJyPiZuYnNwOyZuYnNwOyZuYnNwOyB9
PGJyPjxicj4mbmJzcDsmbmJzcDsmbmJzcDsgcHJpdmF0ZSB2b2lkIGluaXRpYXRlTWV0ZXIoKSB7
PGJyPiZuYnNwOyZuYnNwOyZuYnNwOyZuYnNwOyZuYnNwOyZuYnNwOyZuYnNwOyBtZXRlciA9IHRl
bGVtZXRyeS5tZXRlckJ1aWxkZXIoImV2ZW50LWNvbnN1bWVyIik8YnI+Jm5ic3A7Jm5ic3A7Jm5i
c3A7Jm5ic3A7Jm5ic3A7Jm5ic3A7Jm5ic3A7Jm5ic3A7Jm5ic3A7Jm5ic3A7Jm5ic3A7Jm5ic3A7
Jm5ic3A7Jm5ic3A7Jm5ic3A7IC5zZXRJbnN0cnVtZW50YXRpb25WZXJzaW9uKCIxLjAuMCIpPGJy
PiZuYnNwOyZuYnNwOyZuYnNwOyZuYnNwOyZuYnNwOyZuYnNwOyZuYnNwOyZuYnNwOyZuYnNwOyZu
YnNwOyZuYnNwOyZuYnNwOyZuYnNwOyZuYnNwOyZuYnNwOyAuYnVpbGQoKTs8YnI+PGJyPiZuYnNw
OyZuYnNwOyZuYnNwOyZuYnNwOyZuYnNwOyZuYnNwOyZuYnNwOyBldmVudENvdW50ZXIgPSBtZXRl
ci5jb3VudGVyQnVpbGRlcigiZXZlbnRUeXBlIik8YnI+Jm5ic3A7Jm5ic3A7Jm5ic3A7Jm5ic3A7
Jm5ic3A7Jm5ic3A7Jm5ic3A7Jm5ic3A7Jm5ic3A7Jm5ic3A7Jm5ic3A7Jm5ic3A7Jm5ic3A7Jm5i
c3A7Jm5ic3A7IC5zZXREZXNjcmlwdGlvbigiTWV0cmljcyBmb3IgdGhlIGV2ZW50IGNvbnN1bWlu
Zy4iKTxicj4mbmJzcDsmbmJzcDsmbmJzcDsmbmJzcDsmbmJzcDsmbmJzcDsmbmJzcDsmbmJzcDsm
bmJzcDsmbmJzcDsmbmJzcDsmbmJzcDsmbmJzcDsmbmJzcDsmbmJzcDsgLnNldFVuaXQoIjEiKTxi
cj4mbmJzcDsmbmJzcDsmbmJzcDsmbmJzcDsmbmJzcDsmbmJzcDsmbmJzcDsmbmJzcDsmbmJzcDsm
bmJzcDsmbmJzcDsmbmJzcDsmbmJzcDsmbmJzcDsmbmJzcDsgLmJ1aWxkKCk7PGJyPiZuYnNwOyZu
YnNwOyZuYnNwOyB9PGJyPjxicj4mbmJzcDsmbmJzcDsmbmJzcDsgQEJlZm9yZSgiZXhlY3V0aW9u
KCogdHcuamltd2F5bmV5ZWguZXhhbXBsZS5vdGVsLkZha2VFdmVudFByb2Nlc3Nvci5yZWNlaXZl
RXZlbnQoLi4pKSIpPGJyPiZuYnNwOyZuYnNwOyZuYnNwOyBwdWJsaWMgdm9pZCBiZWZvcmUoSm9p
blBvaW50IGpvaW5Qb2ludCkgezxicj4mbmJzcDsmbmJzcDsmbmJzcDsmbmJzcDsmbmJzcDsmbmJz
cDsmbmJzcDsgdmFyIGV2ZW50ID0gKEV2ZW50KSBqb2luUG9pbnQuZ2V0QXJncygpWzBdOzxicj4m
bmJzcDsmbmJzcDsmbmJzcDsmbmJzcDsmbmJzcDsmbmJzcDsmbmJzcDsgZXZlbnRDb3VudGVyLmFk
ZCgxLCBBdHRyaWJ1dGVzLm9mKDxicj4mbmJzcDsmbmJzcDsmbmJzcDsmbmJzcDsmbmJzcDsmbmJz
cDsmbmJzcDsmbmJzcDsmbmJzcDsmbmJzcDsmbmJzcDsmbmJzcDsmbmJzcDsmbmJzcDsmbmJzcDsg
QXR0cmlidXRlS2V5LnN0cmluZ0tleSgiZXZlbnRUeXBlIiksIGV2ZW50LmV2ZW50VHlwZSgpLDxi
cj4mbmJzcDsmbmJzcDsmbmJzcDsmbmJzcDsmbmJzcDsmbmJzcDsmbmJzcDsmbmJzcDsmbmJzcDsm
bmJzcDsmbmJzcDsmbmJzcDsmbmJzcDsmbmJzcDsmbmJzcDsgQXR0cmlidXRlS2V5LnN0cmluZ0tl
eSgib3duZXIiKSwgZXZlbnQub3duZXIoKSkpOzxicj4mbmJzcDsmbmJzcDsmbmJzcDsgfTxicj48
YnI+YGBgPGJyPjxicj7lnKjkuIrov7DnmoTnqIvlvI/norzkuK3vvIzpppblhYjmiJHlnKggQXNw
ZWN0IOiiq+WIneWni+WMluaZgu+8jOacg+WOu+WIneWni+WMluS4gOWAiyBNZXRlcu+8jOWboOeC
uumAmeWAiyBNZXRlciDmmK/ntbHoqIjmlbjlrZfvvIzmiYDku6XlsLHnm7TmjqXlkb3lkI3ngrog
YGV2ZW50Q291bnRlcmDjgILmjqXokZflnKggSm9pbnRQb2ludCDkuK3vvIzmr4/mrKEgYHJlY2Vp
dmVFdmVudCguLilgIOiiq+WRvOWPq+aZgu+8jEFPUCDmnIPmlJTmiKrpgJnlgIsgbWV0aG9kIOWR
vOWPq++8jOWPluW+lyBtZXRob2Qg5ZG85Y+r5Lit6YCB6YCy5L6G55qEIGV2ZW50IOeJqeS7tu+8
jOS4puS4lOaKiueCuiBgZXZlbnRDb3VudGVyYCArMeOAguWFtuS4rSArMSDmmYLliqDnmoTlsI3o
saHvvIzmmK/lsI0gZXZlbnRUeXBlICZhbXA7IG93bmVyIOWBmiArMeOAgjxicj48YnI+5pyA5b6M
5Z+36KGM55qE57WQ5p6c5pyD6ZW36YCZ5qij77yaPGJyPjxicj5gYGA8YnI+SU5GTyZuYnNwOyB0
LmouZS5vLkZha2VFdmVudFByb2Nlc3NvciBbbWFpbl0gUmVjZWl2ZTogRXZlbnQoZXZlbnRUeXBl
PXVwZGF0ZSwgb3duZXI9b3duZXItMCk8YnI+SU5GTyZuYnNwOyB0LmouZS5vLkZha2VFdmVudFBy
b2Nlc3NvciBbbWFpbl0gUmVjZWl2ZTogRXZlbnQoZXZlbnRUeXBlPXVwZGF0ZSwgb3duZXI9b3du
ZXItMSk8YnI+SU5GTyZuYnNwOyB0LmouZS5vLkZha2VFdmVudFByb2Nlc3NvciBbbWFpbl0gUmVj
ZWl2ZTogRXZlbnQoZXZlbnRUeXBlPXVwZGF0ZSwgb3duZXI9b3duZXItMSk8YnI+SU5GTyZuYnNw
OyB0LmouZS5vLkZha2VFdmVudFByb2Nlc3NvciBbbWFpbl0gUmVjZWl2ZTogRXZlbnQoZXZlbnRU
eXBlPXVwZGF0ZSwgb3duZXI9b3duZXItMSk8YnI+SU5GTyZuYnNwOyB0LmouZS5vLkZha2VFdmVu
dFByb2Nlc3NvciBbbWFpbl0gUmVjZWl2ZTogRXZlbnQoZXZlbnRUeXBlPWRlbGV0ZSwgb3duZXI9
b3duZXItMSk8YnI+SU5GTyZuYnNwOyB0LmouZS5vLkZha2VFdmVudFByb2Nlc3NvciBbbWFpbl0g
UmVjZWl2ZTogRXZlbnQoZXZlbnRUeXBlPWNyZWF0ZSwgb3duZXI9b3duZXItMCk8YnI+SU5GTyZu
YnNwOyB0LmouZS5vLkZha2VFdmVudFByb2Nlc3NvciBbbWFpbl0gUmVjZWl2ZTogRXZlbnQoZXZl
bnRUeXBlPXVwZGF0ZSwgb3duZXI9b3duZXItMCk8YnI+SU5GTyZuYnNwOyB0LmouZS5vLkZha2VF
dmVudFByb2Nlc3NvciBbbWFpbl0gUmVjZWl2ZTogRXZlbnQoZXZlbnRUeXBlPWRlbGV0ZSwgb3du
ZXI9b3duZXItMSk8YnI+SU5GTyZuYnNwOyB0LmouZS5vLkZha2VFdmVudFByb2Nlc3NvciBbbWFp
bl0gUmVjZWl2ZTogRXZlbnQoZXZlbnRUeXBlPWNyZWF0ZSwgb3duZXI9b3duZXItMCk8YnI+SU5G
TyZuYnNwOyB0LmouZS5vLkZha2VFdmVudFByb2Nlc3NvciBbbWFpbl0gUmVjZWl2ZTogRXZlbnQo
ZXZlbnRUeXBlPWRlbGV0ZSwgb3duZXI9b3duZXItMCk8YnI+SU5GTyZuYnNwOyB0LmouZS5vdGVs
LlJ1biBbbWFpbl0gU2xlZXAuLi48YnI+SU5GTyZuYnNwOyBpLm8uZS5sLkxvZ2dpbmdNZXRyaWNF
eHBvcnRlciBbUGVyaW9kaWNNZXRyaWNSZWFkZXItMV0gUmVjZWl2ZWQgYSBjb2xsZWN0aW9uIG9m
IDEgbWV0cmljcyBmb3IgZXhwb3J0Ljxicj5JTkZPJm5ic3A7IGkuby5lLmwuTG9nZ2luZ01ldHJp
Y0V4cG9ydGVyIFtQZXJpb2RpY01ldHJpY1JlYWRlci0xXSBtZXRyaWM6IEltbXV0YWJsZU1ldHJp
Y0RhdGF7cmVzb3VyY2U9UmVzb3VyY2V7c2NoZW1hVXJsPW51bGwsIGF0dHJpYnV0ZXM9e3NlcnZp
Y2UubmFtZT0ib3RlbC1leGFtcGxlIiwgdGVsZW1ldHJ5LnNkay5sYW5ndWFnZT0iamF2YSIsIHRl
bGVtZXRyeS5zZGsubmFtZT0ib3BlbnRlbGVtZXRyeSIsIHRlbGVtZXRyeS5zZGsudmVyc2lvbj0i
MS4yMi4wIn19LCBpbnN0cnVtZW50YXRpb25TY29wZUluZm89SW5zdHJ1bWVudGF0aW9uU2NvcGVJ
bmZve25hbWU9ZXZlbnQtY29uc3VtZXIsIHZlcnNpb249MS4wLjAsIHNjaGVtYVVybD1udWxsLCBh
dHRyaWJ1dGVzPXt9fSwgbmFtZT1ldmVudFR5cGUsIGRlc2NyaXB0aW9uPU1ldHJpY3MgZm9yIHRo
ZSBldmVudCBjb25zdW1pbmcuLCB1bml0PTEsIHR5cGU9TE9OR19TVU0sIGRhdGE9SW1tdXRhYmxl
U3VtRGF0YXtwb2ludHM9W0ltbXV0YWJsZUxvbmdQb2ludERhdGF7c3RhcnRFcG9jaE5hbm9zPTE2
NzQ5MDYwMjAyMjExNDEyMDAsIGVwb2NoTmFub3M9MTY3NDkwNjAyMTIyNzU2NTEwMCwgYXR0cmli
dXRlcz17ZXZlbnRUeXBlPSJkZWxldGUiLCBvd25lcj0ib3duZXItMSJ9LCB2YWx1ZT0yLCBleGVt
cGxhcnM9W119LCBJbW11dGFibGVMb25nUG9pbnREYXRhe3N0YXJ0RXBvY2hOYW5vcz0xNjc0OTA2
MDIwMjIxMTQxMjAwLCBlcG9jaE5hbm9zPTE2NzQ5MDYwMjEyMjc1NjUxMDAsIGF0dHJpYnV0ZXM9
e2V2ZW50VHlwZT0iY3JlYXRlIiwgb3duZXI9Im93bmVyLTAifSwgdmFsdWU9MiwgZXhlbXBsYXJz
PVtdfSwgSW1tdXRhYmxlTG9uZ1BvaW50RGF0YXtzdGFydEVwb2NoTmFub3M9MTY3NDkwNjAyMDIy
MTE0MTIwMCwgZXBvY2hOYW5vcz0xNjc0OTA2MDIxMjI3NTY1MTAwLCBhdHRyaWJ1dGVzPXtldmVu
dFR5cGU9ImRlbGV0ZSIsIG93bmVyPSJvd25lci0wIn0sIHZhbHVlPTEsIGV4ZW1wbGFycz1bXX0s
IEltbXV0YWJsZUxvbmdQb2ludERhdGF7c3RhcnRFcG9jaE5hbm9zPTE2NzQ5MDYwMjAyMjExNDEy
MDAsIGVwb2NoTmFub3M9MTY3NDkwNjAyMTIyNzU2NTEwMCwgYXR0cmlidXRlcz17ZXZlbnRUeXBl
PSJ1cGRhdGUiLCBvd25lcj0ib3duZXItMCJ9LCB2YWx1ZT0yLCBleGVtcGxhcnM9W119LCBJbW11
dGFibGVMb25nUG9pbnREYXRhe3N0YXJ0RXBvY2hOYW5vcz0xNjc0OTA2MDIwMjIxMTQxMjAwLCBl
cG9jaE5hbm9zPTE2NzQ5MDYwMjEyMjc1NjUxMDAsIGF0dHJpYnV0ZXM9e2V2ZW50VHlwZT0idXBk
YXRlIiwgb3duZXI9Im93bmVyLTEifSwgdmFsdWU9MywgZXhlbXBsYXJzPVtdfV0sIG1vbm90b25p
Yz10cnVlLCBhZ2dyZWdhdGlvblRlbXBvcmFsaXR5PUNVTVVMQVRJVkV9fTxicj5gYGA8YnI+PGJy
PuacgOS4iumdoiAxMCDooYzmmK/lnKggYEZha2VFdmVudFByb2Nlc3NvcmAg6KOh6Z2i5a+r55qE
IGxvZ++8jOeEtuW+jOWboOeCuuaIkeeahCBsb2dnaW5nIOioreWumueCuuavj+enkui8uOWHuu+8
jOaJgOS7peaVheaEj+iuk+S4u+eoi+W8j+edoeS6huS4gOS4i++8jOS7pemBv+WFjeS4u+eoi+W8
j+i3keWujOi/tOWciOS7peW+jOWwseiHquW3semXnOaOieS6hiBYROOAgjxicj48YnI+YGkuby5l
LmwuTG9nZ2luZ01ldHJpY0V4cG9ydGVyYCDpgJnmrrXlsLHmmK8gTWV0cmljcyDovLjlh7rnmoTl
haflrrnvvIznqI3lvq7moLzlvI/ljJbkuIDkuIvvvJo8YnI+PGJyPmBgYDxicj5JbW11dGFibGVN
ZXRyaWNEYXRhIHs8YnI+Jm5ic3A7Jm5ic3A7ICZuYnNwO3Jlc291cmNlID0gUmVzb3VyY2Ugezxi
cj4mbmJzcDsmbmJzcDsgJm5ic3A7Jm5ic3A7Jm5ic3A7Jm5ic3A7IHNjaGVtYVVybCA9IG51bGws
IGF0dHJpYnV0ZXMgPSB7PGJyPiZuYnNwOyZuYnNwOyAmbmJzcDsmbmJzcDsmbmJzcDsmbmJzcDsg
Jm5ic3A7Jm5ic3A7Jm5ic3A7IHNlcnZpY2UubmFtZSA9ICJvdGVsLWV4YW1wbGUiLDxicj4mbmJz
cDsmbmJzcDsgJm5ic3A7Jm5ic3A7Jm5ic3A7Jm5ic3A7ICZuYnNwOyZuYnNwOyZuYnNwOyB0ZWxl
bWV0cnkuc2RrLmxhbmd1YWdlID0gImphdmEiLDxicj4mbmJzcDsmbmJzcDsgJm5ic3A7Jm5ic3A7
Jm5ic3A7Jm5ic3A7ICZuYnNwOyZuYnNwOyZuYnNwOyB0ZWxlbWV0cnkuc2RrLm5hbWUgPSAib3Bl
bnRlbGVtZXRyeSIsPGJyPiZuYnNwOyZuYnNwOyAmbmJzcDsmbmJzcDsmbmJzcDsmbmJzcDsgJm5i
c3A7Jm5ic3A7Jm5ic3A7IHRlbGVtZXRyeS5zZGsudmVyc2lvbiA9ICIxLjIyLjAiPGJyPiZuYnNw
OyZuYnNwOyAmbmJzcDsmbmJzcDsmbmJzcDsmbmJzcDsgfTxicj4mbmJzcDsmbmJzcDsgJm5ic3A7
fSwgaW5zdHJ1bWVudGF0aW9uU2NvcGVJbmZvID0gSW5zdHJ1bWVudGF0aW9uU2NvcGVJbmZvIHs8
YnI+Jm5ic3A7Jm5ic3A7ICZuYnNwOyZuYnNwOyZuYnNwOyZuYnNwOyBuYW1lID0gZXZlbnQgLSBj
b25zdW1lciwgdmVyc2lvbiA9IDEuMCAuMCwgc2NoZW1hVXJsID0gbnVsbCwgYXR0cmlidXRlcyA9
IHt9PGJyPiZuYnNwOyZuYnNwOyAmbmJzcDt9LCBuYW1lID0gZXZlbnRUeXBlLCBkZXNjcmlwdGlv
biA9IE1ldHJpY3M8YnI+Jm5ic3A7Jm5ic3A7ICZuYnNwO2ZvciB0aGUgZXZlbnQgY29uc3VtaW5n
LiwgdW5pdCA9IDEsIHR5cGUgPSBMT05HX1NVTSwgZGF0YSA9IEltbXV0YWJsZVN1bURhdGEgezxi
cj4mbmJzcDsmbmJzcDsgJm5ic3A7Jm5ic3A7Jm5ic3A7Jm5ic3A7IHBvaW50cyA9IFtJbW11dGFi
bGVMb25nUG9pbnREYXRhIHs8YnI+Jm5ic3A7Jm5ic3A7ICZuYnNwOyZuYnNwOyZuYnNwOyZuYnNw
OyAmbmJzcDsmbmJzcDsmbmJzcDsgc3RhcnRFcG9jaE5hbm9zID0gMTY3NDkwNjAyMDIyMTE0MTIw
MCwgZXBvY2hOYW5vcyA9IDE2NzQ5MDYwMjEyMjc1NjUxMDAsIGF0dHJpYnV0ZXMgPSB7PGJyPiZu
YnNwOyZuYnNwOyAmbmJzcDsmbmJzcDsmbmJzcDsmbmJzcDsgJm5ic3A7Jm5ic3A7Jm5ic3A7ICZu
YnNwOyZuYnNwOyZuYnNwOyBldmVudFR5cGUgPSAiZGVsZXRlIiw8YnI+Jm5ic3A7Jm5ic3A7ICZu
YnNwOyZuYnNwOyZuYnNwOyZuYnNwOyAmbmJzcDsmbmJzcDsmbmJzcDsgJm5ic3A7Jm5ic3A7Jm5i
c3A7IG93bmVyID0gIm93bmVyLTEiPGJyPiZuYnNwOyZuYnNwOyAmbmJzcDsmbmJzcDsmbmJzcDsm
bmJzcDsgJm5ic3A7Jm5ic3A7Jm5ic3A7IH0sIHZhbHVlID0gMiwgZXhlbXBsYXJzID0gW108YnI+
Jm5ic3A7Jm5ic3A7ICZuYnNwOyZuYnNwOyZuYnNwOyZuYnNwOyB9LCBJbW11dGFibGVMb25nUG9p
bnREYXRhIHs8YnI+Jm5ic3A7Jm5ic3A7ICZuYnNwOyZuYnNwOyZuYnNwOyZuYnNwOyAmbmJzcDsm
bmJzcDsmbmJzcDsgc3RhcnRFcG9jaE5hbm9zID0gMTY3NDkwNjAyMDIyMTE0MTIwMCwgZXBvY2hO
YW5vcyA9IDE2NzQ5MDYwMjEyMjc1NjUxMDAsIGF0dHJpYnV0ZXMgPSB7PGJyPiZuYnNwOyZuYnNw
OyAmbmJzcDsmbmJzcDsmbmJzcDsmbmJzcDsgJm5ic3A7Jm5ic3A7Jm5ic3A7ICZuYnNwOyZuYnNw
OyZuYnNwOyBldmVudFR5cGUgPSAiY3JlYXRlIiw8YnI+Jm5ic3A7Jm5ic3A7ICZuYnNwOyZuYnNw
OyZuYnNwOyZuYnNwOyAmbmJzcDsmbmJzcDsmbmJzcDsgJm5ic3A7Jm5ic3A7Jm5ic3A7IG93bmVy
ID0gIm93bmVyLTAiPGJyPiZuYnNwOyZuYnNwOyAmbmJzcDsmbmJzcDsmbmJzcDsmbmJzcDsgJm5i
c3A7Jm5ic3A7Jm5ic3A7IH0sIHZhbHVlID0gMiwgZXhlbXBsYXJzID0gW108YnI+Jm5ic3A7Jm5i
c3A7ICZuYnNwOyZuYnNwOyZuYnNwOyZuYnNwOyB9LCBJbW11dGFibGVMb25nUG9pbnREYXRhIHs8
YnI+Jm5ic3A7Jm5ic3A7ICZuYnNwOyZuYnNwOyZuYnNwOyZuYnNwOyAmbmJzcDsmbmJzcDsmbmJz
cDsgc3RhcnRFcG9jaE5hbm9zID0gMTY3NDkwNjAyMDIyMTE0MTIwMCwgZXBvY2hOYW5vcyA9IDE2
NzQ5MDYwMjEyMjc1NjUxMDAsIGF0dHJpYnV0ZXMgPSB7PGJyPiZuYnNwOyZuYnNwOyAmbmJzcDsm
bmJzcDsmbmJzcDsmbmJzcDsgJm5ic3A7Jm5ic3A7Jm5ic3A7ICZuYnNwOyZuYnNwOyZuYnNwOyBl
dmVudFR5cGUgPSAiZGVsZXRlIiw8YnI+Jm5ic3A7Jm5ic3A7ICZuYnNwOyZuYnNwOyZuYnNwOyZu
YnNwOyAmbmJzcDsmbmJzcDsmbmJzcDsgJm5ic3A7Jm5ic3A7Jm5ic3A7IG93bmVyID0gIm93bmVy
LTAiPGJyPiZuYnNwOyZuYnNwOyAmbmJzcDsmbmJzcDsmbmJzcDsmbmJzcDsgJm5ic3A7Jm5ic3A7
Jm5ic3A7IH0sIHZhbHVlID0gMSwgZXhlbXBsYXJzID0gW108YnI+Jm5ic3A7Jm5ic3A7ICZuYnNw
OyZuYnNwOyZuYnNwOyZuYnNwOyB9LCBJbW11dGFibGVMb25nUG9pbnREYXRhIHs8YnI+Jm5ic3A7
Jm5ic3A7ICZuYnNwOyZuYnNwOyZuYnNwOyZuYnNwOyAmbmJzcDsmbmJzcDsmbmJzcDsgc3RhcnRF
cG9jaE5hbm9zID0gMTY3NDkwNjAyMDIyMTE0MTIwMCwgZXBvY2hOYW5vcyA9IDE2NzQ5MDYwMjEy
Mjc1NjUxMDAsIGF0dHJpYnV0ZXMgPSB7PGJyPiZuYnNwOyZuYnNwOyAmbmJzcDsmbmJzcDsmbmJz
cDsmbmJzcDsgJm5ic3A7Jm5ic3A7Jm5ic3A7ICZuYnNwOyZuYnNwOyZuYnNwOyBldmVudFR5cGUg
PSAidXBkYXRlIiw8YnI+Jm5ic3A7Jm5ic3A7ICZuYnNwOyZuYnNwOyZuYnNwOyZuYnNwOyAmbmJz
cDsmbmJzcDsmbmJzcDsgJm5ic3A7Jm5ic3A7Jm5ic3A7IG93bmVyID0gIm93bmVyLTAiPGJyPiZu
YnNwOyZuYnNwOyAmbmJzcDsmbmJzcDsmbmJzcDsmbmJzcDsgJm5ic3A7Jm5ic3A7Jm5ic3A7IH0s
IHZhbHVlID0gMiwgZXhlbXBsYXJzID0gW108YnI+Jm5ic3A7Jm5ic3A7ICZuYnNwOyZuYnNwOyZu
YnNwOyZuYnNwOyB9LCBJbW11dGFibGVMb25nUG9pbnREYXRhIHs8YnI+Jm5ic3A7Jm5ic3A7ICZu
YnNwOyZuYnNwOyZuYnNwOyZuYnNwOyAmbmJzcDsmbmJzcDsmbmJzcDsgc3RhcnRFcG9jaE5hbm9z
ID0gMTY3NDkwNjAyMDIyMTE0MTIwMCwgZXBvY2hOYW5vcyA9IDE2NzQ5MDYwMjEyMjc1NjUxMDAs
IGF0dHJpYnV0ZXMgPSB7PGJyPiZuYnNwOyZuYnNwOyAmbmJzcDsmbmJzcDsmbmJzcDsmbmJzcDsg
Jm5ic3A7Jm5ic3A7Jm5ic3A7ICZuYnNwOyZuYnNwOyZuYnNwOyBldmVudFR5cGUgPSAidXBkYXRl
Iiw8YnI+Jm5ic3A7Jm5ic3A7ICZuYnNwOyZuYnNwOyZuYnNwOyZuYnNwOyAmbmJzcDsmbmJzcDsm
bmJzcDsgJm5ic3A7Jm5ic3A7Jm5ic3A7IG93bmVyID0gIm93bmVyLTEiPGJyPiZuYnNwOyZuYnNw
OyAmbmJzcDsmbmJzcDsmbmJzcDsmbmJzcDsgJm5ic3A7Jm5ic3A7Jm5ic3A7IH0sIHZhbHVlID0g
MywgZXhlbXBsYXJzID0gW108YnI+Jm5ic3A7Jm5ic3A7ICZuYnNwOyZuYnNwOyZuYnNwOyZuYnNw
OyB9XSwgbW9ub3RvbmljID0gdHJ1ZSwgYWdncmVnYXRpb25UZW1wb3JhbGl0eSA9IENVTVVMQVRJ
VkU8YnI+Jm5ic3A7Jm5ic3A7ICZuYnNwO308YnI+fTxicj5gYGA8YnI+PGJyPuWPr+S7peeci+WI
sO+8jE1ldHJpY3Mg57SA6YyE5Yiw55qE57WQ5p6c5piv77yaPGJyPiogYG93bmVyLTFgIGBkZWxl
dGVgOiAxPGJyPiogYG93bmVyLTBgIGBjcmVhdGVgOiAyPGJyPiogYG93bmVyLTBgIGBkZWxldGVg
OiAxPGJyPiogYG93bmVyLTBgIGB1cGRhdGVgOiAyPGJyPiogYG93bmVyLTFgIGB1cGRhdGVgOiAz
PGJyPjxicj7norrlr6bntZDmnpzmmK/nrKblkIggYEZha2VFdmVudFByb2Nlc3NvcmAg55qE57WQ
5p6c772e44CCPGJyPjxicj5bb3RlbC1oaXN0b3J5XTogaHR0cHM6Ly93d3cuY25jZi5pby9ibG9n
LzIwMTkvMDUvMjEvYS1icmllZi1oaXN0b3J5LW9mLW9wZW50ZWxlbWV0cnktc28tZmFyLzxicj5b
b3RlbC1qYXZhLXN0YXR1c106IGh0dHBzOi8vb3BlbnRlbGVtZXRyeS5pby9kb2NzL2luc3RydW1l
bnRhdGlvbi9qYXZhLyNzdGF0dXMtYW5kLXJlbGVhc2VzPGJyPltvdGVsLWV4YW1wbGVdOiBodHRw
czovL2dpdGh1Yi5jb20vamltd2F5bmV5ZWgvb3RlbC1leGFtcGxlL3RyZWUvMDUwY2U0MDY4ZGFh
MmYwNjliMmU2YjNjYjQyZjgzMTFhODJkN2FhOTxicj4mbmJzcDs8L3A+&quot;&gt;​&lt;/div&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimwayne.blogspot.com/feeds/8606835726555028636/comments/default' title='張貼留言'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/6819483672105967231/8606835726555028636' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6819483672105967231/posts/default/8606835726555028636'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6819483672105967231/posts/default/8606835726555028636'/><link rel='alternate' type='text/html' href='http://jimwayne.blogspot.com/2023/01/opentelemetry-metrics-spring-aop.html' title='基本的 OpenTelemetry Metrics 設定：紀錄累計數目並透過 Spring AOP 攔截方法'/><author><name>Wayne Yeh</name><uri>http://www.blogger.com/profile/02148905864753873930</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6819483672105967231.post-3326694108456395503</id><published>2023-01-28T08:27:00.002+08:00</published><updated>2023-01-28T08:46:00.704+08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="AOP"/><category scheme="http://www.blogger.com/atom/ns#" term="Markdown Here"/><category scheme="http://www.blogger.com/atom/ns#" term="Spring"/><category scheme="http://www.blogger.com/atom/ns#" term="Spring Boot"/><title type='text'>Spring AOP 基本使用</title><content type='html'>&lt;div class=&quot;markdown-here-wrapper&quot; data-md-url=&quot;https://draft.blogger.com/blog/post/edit/6819483672105967231/3326694108456395503&quot; markdown-here-wrapper-content-modified=&quot;true&quot;&gt;&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;去年年底終於幫公司的專案升級成 Spring Boot，下一個 wish list 就是把一些討厭的邏輯轉成 AOP 的方式插入，然後就可以去除掉一些討厭的關聯了。不過因為自己實際上還沒有真的玩過 AOP，所以這篇就紀錄一點基本的東西。&lt;span&gt;&lt;/span&gt;&lt;/p&gt;&lt;a name=&#39;more&#39;&gt;&lt;/a&gt;&lt;p&gt;&lt;/p&gt;
&lt;h1 id=&quot;aop&quot; style=&quot;border-bottom: 1px solid rgb(221, 221, 221); font-size: 1.6em; font-weight: bold; margin: 1.3em 0px 1em; padding: 0px;&quot;&gt;AOP&lt;/h1&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;AOP 的主要概念就是橫向切入，或者換句話說是用外掛的方式，把本來不屬於程式碼的邏輯 “外掛” 到程式碼當中。這種作法最主要的優點就是，外掛上去的邏輯可以放在別的地方，不會直接跟主邏輯綁在一起，因此也不會影響到主邏輯自身的程式碼。舉例來說，原本的程式碼可能是 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;A -&amp;gt; B.method()&lt;/code&gt;，表示 class A 的某一行去呼叫 class B 的 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;method()&lt;/code&gt; 方法。套上 AOP 時，在編譯期間，程式碼的呼叫會被編譯成 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;A -&amp;gt; aop.method() -&amp;gt; B.method()&lt;/code&gt;，也就是說 A 跟 B 的呼叫過程被額外加入了 AOP 的邏輯。&lt;/p&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;外掛是怎麼做到的，就我目前的理解是有兩種作法，一種是直接用 code generation 的方式，在程式碼編譯期間把外掛的程式碼包進去。所以雖然原始碼中是寫 A 呼叫 B，但編譯後產生的中繼碼，實際上寫的是 A 呼叫 AOP、然後 AOP 呼叫 B。第二種作法是 runtime proxy，就是透過 reflection 的方式把 aop 掛到 A 和 B 之間。兩種作法的目的是一樣的，但第一種作法顯然執行效率會比較高，因為第二種作法是執行期間才動態做 proxy。依據 &lt;a href=&quot;https://docs.spring.io/spring-framework/docs/5.3.23/reference/html/core.html#aop&quot;&gt;Spring AOP&lt;/a&gt; 的文件，第一種作法是在 Spring AOP 裡直接使用 AspectJ 的話，AspectJ 會使用的作法；而第二種作法則是 Spring AOP 預設採取的作法。&lt;/p&gt;
&lt;h1 id=&quot;spring-aop&quot; style=&quot;border-bottom: 1px solid rgb(221, 221, 221); font-size: 1.6em; font-weight: bold; margin: 1.3em 0px 1em; padding: 0px;&quot;&gt;Spring AOP&lt;/h1&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;Spring AOP 算是簡易版的 AspectJ，功能比較受限，例如 Spring AOP 只能做 method 的攔截，其他 field 攔截等等的是不支援的。詳細的說明可以參考 &lt;a href=&quot;https://docs.spring.io/spring-framework/docs/5.3.23/reference/html/core.html#aop-introduction-spring-defn&quot;&gt;Spring AOP Capabilities and Goals&lt;/a&gt;。&lt;/p&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;不過整體來說，最重要的一件事是，Spring AOP 只能夠對 Spring 管理的 bean 做攔截，這點很重要～。所以如果發現 AOP 一直沒發生效果，第一個先確認想要攔截的對象是不是被 Spring 管理的 bean。&lt;/p&gt;
&lt;h1 id=&quot;spring-aop-&quot; style=&quot;border-bottom: 1px solid rgb(221, 221, 221); font-size: 1.6em; font-weight: bold; margin: 1.3em 0px 1em; padding: 0px;&quot;&gt;Spring AOP 簡易範例&lt;/h1&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;題外話，話說我在實驗時，有先叫 ChatGPT 產生範例程式碼給我，但結果它產生的程式碼其實有不少細節是錯的….。看起來 ChatGPT 把 Spring AOP 跟 AspectJ 混在一起使用的樣子…。&lt;/p&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;回歸正題 XD。這裡用一個很簡單的範例先展示 Spring AOP 的用法。首先需要引入 AOP 相關的 dependency，在 Spring Boot 可以簡單地放入 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;spring-boot-starter-aop&lt;/code&gt; 就好。&lt;/p&gt;
&lt;pre style=&quot;font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; font-size: 1em; line-height: 1.2em; margin: 1.2em 0px;&quot;&gt;&lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border-radius: 3px; border: 1px solid rgb(204, 204, 204); border: 1px solid rgb(234, 234, 234); display: block !important; display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; overflow: auto; padding: 0.5em 0.7em; padding: 0px 0.3em; white-space: pre-wrap; white-space: pre;&quot;&gt;implementation(&quot;org.springframework.boot:spring-boot-starter-aop&quot;)
&lt;/code&gt;&lt;/pre&gt;&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;這個範例裡，我建立了一個 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;CommandLineRunner&lt;/code&gt;，作為 Spring Boot 的啟動對象。這個 Runner 裡面會隨機產生 10 個 Event，然後將 Event 餵給 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;FakeEventProcessor&lt;/code&gt;，所以我最後要被 AOP 插入的對象是 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;FakeEventProcessor&lt;/code&gt;。需要注意的是，這裡 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;FakeEventProcessor&lt;/code&gt; 是用注入的方式產生的，而不是自己呼叫 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;new&lt;/code&gt;，因為上面提到了 Spring AOP 只能對 Spring 管理的 bean 發生效用。&lt;/p&gt;
&lt;pre style=&quot;font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; font-size: 1em; line-height: 1.2em; margin: 1.2em 0px;&quot;&gt;&lt;code class=&quot;hljs language-java&quot; style=&quot;-moz-text-size-adjust: none; background-color: #f8f8f8; background: rgb(35, 36, 31) none repeat scroll 0% 0%; border-radius: 3px; border-radius: 3px; border: 1px solid rgb(204, 204, 204); border: 1px solid rgb(234, 234, 234); color: #f8f8f2; display: block !important; display: block; display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; overflow-x: auto; overflow: auto; padding: 0.5em 0.7em; padding: 0.5em; padding: 0px 0.3em; white-space: pre-wrap; white-space: pre;&quot;&gt;&lt;span class=&quot;hljs-annotation&quot; style=&quot;color: #75715e;&quot;&gt;@Slf&lt;/span&gt;4j
&lt;span class=&quot;hljs-annotation&quot; style=&quot;color: #75715e;&quot;&gt;@Component&lt;/span&gt;
&lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #f92672;&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-class&quot;&gt;&lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #66d9ef; color: #f92672;&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;hljs-title&quot; style=&quot;color: #a6e22e; color: #f8f8f2;&quot;&gt;Run&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #f92672;&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;hljs-title&quot; style=&quot;color: #a6e22e; color: #f8f8f2; font-style: italic;&quot;&gt;CommandLineRunner&lt;/span&gt; &lt;/span&gt;{
    &lt;span class=&quot;hljs-annotation&quot; style=&quot;color: #75715e;&quot;&gt;@Autowired&lt;/span&gt;
    &lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #f92672;&quot;&gt;private&lt;/span&gt; FakeEventProcessor processor;

    &lt;span class=&quot;hljs-annotation&quot; style=&quot;color: #75715e;&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;hljs-function&quot; style=&quot;color: #f92672;&quot;&gt;&lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #66d9ef; color: #f92672;&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #66d9ef; color: #f92672;&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;hljs-title&quot; style=&quot;color: #a6e22e;&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;hljs-params&quot; style=&quot;color: #f8f8f2;&quot;&gt;(String... args)&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #66d9ef; color: #f92672;&quot;&gt;throws&lt;/span&gt; Exception &lt;/span&gt;{
        &lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #f92672;&quot;&gt;int&lt;/span&gt; loop = &lt;span class=&quot;hljs-number&quot; style=&quot;color: #ae81ff;&quot;&gt;10&lt;/span&gt;;
        &lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #f92672;&quot;&gt;while&lt;/span&gt;(loop-- &amp;gt; &lt;span class=&quot;hljs-number&quot; style=&quot;color: #ae81ff;&quot;&gt;0&lt;/span&gt;) {
            var event = generateRandomEvent();
            processor.receiveEvent(event);
        }
    }
}

&lt;span class=&quot;hljs-annotation&quot; style=&quot;color: #75715e;&quot;&gt;@Builder&lt;/span&gt;
&lt;span class=&quot;hljs-annotation&quot; style=&quot;color: #75715e;&quot;&gt;@Getter&lt;/span&gt;
&lt;span class=&quot;hljs-annotation&quot; style=&quot;color: #75715e;&quot;&gt;@Accessors&lt;/span&gt;(fluent = &lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #f92672;&quot;&gt;true&lt;/span&gt;)
&lt;span class=&quot;hljs-annotation&quot; style=&quot;color: #75715e;&quot;&gt;@ToString&lt;/span&gt;
&lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #f92672;&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-class&quot;&gt;&lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #66d9ef; color: #f92672;&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;hljs-title&quot; style=&quot;color: #a6e22e; color: #f8f8f2; font-style: italic;&quot;&gt;Event&lt;/span&gt; &lt;/span&gt;{
    &lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #f92672;&quot;&gt;private&lt;/span&gt; String eventType;
    &lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #f92672;&quot;&gt;private&lt;/span&gt; String owner;
}

&lt;span class=&quot;hljs-annotation&quot; style=&quot;color: #75715e;&quot;&gt;@Slf&lt;/span&gt;4j
&lt;span class=&quot;hljs-annotation&quot; style=&quot;color: #75715e;&quot;&gt;@Component&lt;/span&gt;
&lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #f92672;&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-class&quot;&gt;&lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #66d9ef; color: #f92672;&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;hljs-title&quot; style=&quot;color: #a6e22e; color: #f8f8f2; font-style: italic;&quot;&gt;FakeEventProcessor&lt;/span&gt; &lt;/span&gt;{
    &lt;span class=&quot;hljs-function&quot; style=&quot;color: #f92672;&quot;&gt;&lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #66d9ef; color: #f92672;&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #66d9ef; color: #f92672;&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;hljs-title&quot; style=&quot;color: #a6e22e;&quot;&gt;receiveEvent&lt;/span&gt;&lt;span class=&quot;hljs-params&quot; style=&quot;color: #f8f8f2;&quot;&gt;(Event event)&lt;/span&gt; &lt;/span&gt;{
        log.info(&lt;span class=&quot;hljs-string&quot; style=&quot;color: #e6db74;&quot;&gt;&quot;Receive: {}&quot;&lt;/span&gt;, event);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;接著宣告一個 Aspect，目的是我想要攔截 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;FakeEventProcessor&lt;/code&gt; 每次呼叫 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;receiveEvent()&lt;/code&gt; 的時候，都在它被呼叫前先呼叫我要插入的邏輯。&lt;/p&gt;
&lt;pre style=&quot;font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; font-size: 1em; line-height: 1.2em; margin: 1.2em 0px;&quot;&gt;&lt;code class=&quot;hljs language-java&quot; style=&quot;-moz-text-size-adjust: none; background-color: #f8f8f8; background: rgb(35, 36, 31) none repeat scroll 0% 0%; border-radius: 3px; border-radius: 3px; border: 1px solid rgb(204, 204, 204); border: 1px solid rgb(234, 234, 234); color: #f8f8f2; display: block !important; display: block; display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; overflow-x: auto; overflow: auto; padding: 0.5em 0.7em; padding: 0.5em; padding: 0px 0.3em; white-space: pre-wrap; white-space: pre;&quot;&gt;&lt;span class=&quot;hljs-annotation&quot; style=&quot;color: #75715e;&quot;&gt;@Slf&lt;/span&gt;4j
&lt;span class=&quot;hljs-annotation&quot; style=&quot;color: #75715e;&quot;&gt;@Aspect&lt;/span&gt;
&lt;span class=&quot;hljs-annotation&quot; style=&quot;color: #75715e;&quot;&gt;@Component&lt;/span&gt;
&lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #f92672;&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-class&quot;&gt;&lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #66d9ef; color: #f92672;&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;hljs-title&quot; style=&quot;color: #a6e22e; color: #f8f8f2; font-style: italic;&quot;&gt;TelemetryAspect&lt;/span&gt; &lt;/span&gt;{

    &lt;span class=&quot;hljs-function&quot; style=&quot;color: #f92672;&quot;&gt;&lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #66d9ef; color: #f92672;&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-title&quot; style=&quot;color: #a6e22e;&quot;&gt;TelemetryAspect&lt;/span&gt;&lt;span class=&quot;hljs-params&quot; style=&quot;color: #f8f8f2;&quot;&gt;()&lt;/span&gt; &lt;/span&gt;{
        log.info(&lt;span class=&quot;hljs-string&quot; style=&quot;color: #e6db74;&quot;&gt;&quot;Initiate aspect...&quot;&lt;/span&gt;);
    }

    &lt;span class=&quot;hljs-annotation&quot; style=&quot;color: #75715e;&quot;&gt;@Before&lt;/span&gt;(&lt;span class=&quot;hljs-string&quot; style=&quot;color: #e6db74;&quot;&gt;&quot;execution(* tw.jimwayneyeh.example.otel.FakeEventProcessor.receiveEvent(..))&quot;&lt;/span&gt;)
    &lt;span class=&quot;hljs-function&quot; style=&quot;color: #f92672;&quot;&gt;&lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #66d9ef; color: #f92672;&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot; style=&quot;color: #66d9ef; color: #f92672;&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;hljs-title&quot; style=&quot;color: #a6e22e;&quot;&gt;before&lt;/span&gt;&lt;span class=&quot;hljs-params&quot; style=&quot;color: #f8f8f2;&quot;&gt;(JoinPoint joinPoint)&lt;/span&gt; &lt;/span&gt;{
        Event event = (Event) joinPoint.getArgs()[&lt;span class=&quot;hljs-number&quot; style=&quot;color: #ae81ff;&quot;&gt;0&lt;/span&gt;];
        log.trace(&lt;span class=&quot;hljs-string&quot; style=&quot;color: #e6db74;&quot;&gt;&quot;join &#39;{}()&#39;: {}&quot;&lt;/span&gt;, joinPoint.getSignature().getName(), event);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;這裡我使用的攔截方式是 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;@Before&lt;/code&gt;，表示在目標被執行前先執行攔截的邏輯，並且攔截的對象是 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;@Before&lt;/code&gt; 裡定義的 method &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;tw.jimwayneyeh.example.otel.FakeEventProcessor.receiveEvent(..)&lt;/code&gt;。而攔截後，我要拿出傳入的 event 並且印出 log，這樣我才能確認它攔截到的是什麼東西。&lt;/p&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;最後這段程式碼跑起來，會跑出以下的結果：&lt;/p&gt;
&lt;pre style=&quot;font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; font-size: 1em; line-height: 1.2em; margin: 1.2em 0px;&quot;&gt;&lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border-radius: 3px; border: 1px solid rgb(204, 204, 204); border: 1px solid rgb(234, 234, 234); display: block !important; display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; overflow: auto; padding: 0.5em 0.7em; padding: 0px 0.3em; white-space: pre-wrap; white-space: pre;&quot;&gt;TRACE t.j.e.o.TelemetryAspect [main] join &#39;receiveEvent()&#39;: Event(eventType=delete, owner=owner-0)
INFO  t.j.e.o.FakeEventProcessor [main] Receive: Event(eventType=delete, owner=owner-0)
TRACE t.j.e.o.TelemetryAspect [main] join &#39;receiveEvent()&#39;: Event(eventType=update, owner=owner-1)
INFO  t.j.e.o.FakeEventProcessor [main] Receive: Event(eventType=update, owner=owner-1)
TRACE t.j.e.o.TelemetryAspect [main] join &#39;receiveEvent()&#39;: Event(eventType=update, owner=owner-6)
INFO  t.j.e.o.FakeEventProcessor [main] Receive: Event(eventType=update, owner=owner-6)
...(ignored)
&lt;/code&gt;&lt;/pre&gt;&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;這裡只列了最開始三行，因為後面都差不多 XD。可以看到針對每個 event 的呼叫，攔截的 log 會先出現（join 開頭的 log），然後才出現原本 method 執行的寫 log（Receive 開頭的 log），並且兩個 log 印出來的 event 是完全一樣的，表示 Aspect 確實被插入到 method call 之前了。&lt;/p&gt;
&lt;div style=&quot;font-size: 0em; height: 0; margin: 0; max-height: 0; max-width: 0; overflow: hidden; padding: 0; width: 0;&quot; title=&quot;MDH:5Y675bm05bm05bqV57WC5pa85bmr5YWs5Y+455qE5bCI5qGI5Y2H57Sa5oiQIFNwcmluZyBCb290
77yM5LiL5LiA5YCLIHdpc2ggbGlzdCDlsLHmmK/miorkuIDkupvoqI7ljq3nmoTpgo/ovK/ovYnm
iJAgQU9QIOeahOaWueW8j+aPkuWFpe+8jOeEtuW+jOWwseWPr+S7peWOu+mZpOaOieS4gOS6m+io
juWOreeahOmXnOiBr+S6huOAguS4jemBjuWboOeCuuiHquW3seWvpumam+S4iumChOaykuacieec
n+eahOeOqemBjiBBT1DvvIzmiYDku6XpgJnnr4flsLHntIDpjITkuIDpu57ln7rmnKznmoTmnbHo
pb/jgII8YnI+PGJyPiMgQU9QPGJyPjxicj5BT1Ag55qE5Li76KaB5qaC5b+15bCx5piv5qmr5ZCR
5YiH5YWl77yM5oiW6ICF5o+b5Y+l6Kmx6Kqq5piv55So5aSW5o6b55qE5pa55byP77yM5oqK5pys
5L6G5LiN5bGs5pa856iL5byP56K855qE6YKP6LyvICLlpJbmjpsiIOWIsOeoi+W8j+eivOeVtuS4
reOAgumAmeeoruS9nOazleacgOS4u+imgeeahOWEqum7nuWwseaYr++8jOWkluaOm+S4iuWOu+ea
hOmCj+i8r+WPr+S7peaUvuWcqOWIpeeahOWcsOaWue+8jOS4jeacg+ebtOaOpei3n+S4u+mCj+i8
r+e2geWcqOS4gOi1t++8jOWboOatpOS5n+S4jeacg+W9semfv+WIsOS4u+mCj+i8r+iHqui6q+ea
hOeoi+W8j+eivOOAguiIieS+i+S+huiqqu+8jOWOn+acrOeahOeoi+W8j+eivOWPr+iDveaYryBg
QSAtJmd0OyBCLm1ldGhvZCgpYO+8jOihqOekuiBjbGFzcyBBIOeahOafkOS4gOihjOWOu+WRvOWP
qyBjbGFzcyBCIOeahCBgbWV0aG9kKClgIOaWueazleOAguWll+S4iiBBT1Ag5pmC77yM5Zyo57eo
6K2v5pyf6ZaT77yM56iL5byP56K855qE5ZG85Y+r5pyD6KKr57eo6K2v5oiQIGBBIC0mZ3Q7IGFv
cC5tZXRob2QoKSAtJmd0OyBCLm1ldGhvZCgpYO+8jOS5n+WwseaYr+iqqiBBIOi3nyBCIOeahOWR
vOWPq+mBjueoi+iiq+mhjeWkluWKoOWFpeS6hiBBT1Ag55qE6YKP6Lyv44CCPGJyPjxicj7lpJbm
jpvmmK/mgI7purzlgZrliLDnmoTvvIzlsLHmiJHnm67liY3nmoTnkIbop6PmmK/mnInlhannqK7k
vZzms5XvvIzkuIDnqK7mmK/nm7TmjqXnlKggY29kZSBnZW5lcmF0aW9uIOeahOaWueW8j++8jOWc
qOeoi+W8j+eivOe3qOitr+acn+mWk+aKiuWkluaOm+eahOeoi+W8j+eivOWMhemAsuWOu+OAguaJ
gOS7pemblueEtuWOn+Wni+eivOS4reaYr+WvqyBBIOWRvOWPqyBC77yM5L2G57eo6K2v5b6M55Si
55Sf55qE5Lit57m856K877yM5a+m6Zqb5LiK5a+r55qE5pivIEEg5ZG85Y+rIEFPUOOAgeeEtuW+
jCBBT1Ag5ZG85Y+rIELjgILnrKzkuoznqK7kvZzms5XmmK8gcnVudGltZSBwcm94ee+8jOWwseaY
r+mAj+mBjiByZWZsZWN0aW9uIOeahOaWueW8j+aKiiBhb3Ag5o6b5YiwIEEg5ZKMIEIg5LmL6ZaT
44CC5YWp56iu5L2c5rOV55qE55uu55qE5piv5LiA5qij55qE77yM5L2G56ys5LiA56iu5L2c5rOV
6aGv54S25Z+36KGM5pWI546H5pyD5q+U6LyD6auY77yM5Zug54K656ys5LqM56iu5L2c5rOV5piv
5Z+36KGM5pyf6ZaT5omN5YuV5oWL5YGaIHByb3h544CC5L6d5pOaIFtTcHJpbmcgQU9QXVtzcHJp
bmctYW9wXSDnmoTmlofku7bvvIznrKzkuIDnqK7kvZzms5XmmK/lnKggU3ByaW5nIEFPUCDoo6Hn
m7TmjqXkvb/nlKggQXNwZWN0SiDnmoToqbHvvIxBc3BlY3RKIOacg+S9v+eUqOeahOS9nOazle+8
m+iAjOesrOS6jOeoruS9nOazleWJh+aYryBTcHJpbmcgQU9QIOmgkOioreaOoeWPlueahOS9nOaz
leOAgjxicj48YnI+IyBTcHJpbmcgQU9QPGJyPjxicj5TcHJpbmcgQU9QIOeul+aYr+ewoeaYk+eJ
iOeahCBBc3BlY3RK77yM5Yqf6IO95q+U6LyD5Y+X6ZmQ77yM5L6L5aaCIFNwcmluZyBBT1Ag5Y+q
6IO95YGaIG1ldGhvZCDnmoTmlJTmiKrvvIzlhbbku5YgZmllbGQg5pSU5oiq562J562J55qE5piv
5LiN5pSv5o+055qE44CC6Kmz57Sw55qE6Kqq5piO5Y+v5Lul5Y+D6ICDIFtTcHJpbmcgQU9QIENh
cGFiaWxpdGllcyBhbmQgR29hbHNdW3NwcmluZy1hb3AtY2FwLWFuZC1nb2FsXeOAgjxicj48YnI+
5LiN6YGO5pW06auU5L6G6Kqq77yM5pyA6YeN6KaB55qE5LiA5Lu25LqL5piv77yMU3ByaW5nIEFP
UCDlj6rog73lpKDlsI0gU3ByaW5nIOeuoeeQhueahCBiZWFuIOWBmuaUlOaIqu+8jOmAmem7nuW+
iOmHjeimge+9nuOAguaJgOS7peWmguaenOeZvOePviBBT1Ag5LiA55u05rKS55m855Sf5pWI5p6c
77yM56ys5LiA5YCL5YWI56K66KqN5oOz6KaB5pSU5oiq55qE5bCN6LGh5piv5LiN5piv6KKrIFNw
cmluZyDnrqHnkIbnmoQgYmVhbuOAgjxicj48YnI+IyBTcHJpbmcgQU9QIOewoeaYk+evhOS+izxi
cj48YnI+6aGM5aSW6Kmx77yM6Kmx6Kqq5oiR5Zyo5a+m6amX5pmC77yM5pyJ5YWI5Y+rIENoYXRH
UFQg55Si55Sf56+E5L6L56iL5byP56K857Wm5oiR77yM5L2G57WQ5p6c5a6D55Si55Sf55qE56iL
5byP56K85YW25a+m5pyJ5LiN5bCR57Sw56+A5piv6Yyv55qELi4uLuOAgueci+i1t+S+hiBDaGF0
R1BUIOaKiiBTcHJpbmcgQU9QIOi3nyBBc3BlY3RKIOa3t+WcqOS4gOi1t+S9v+eUqOeahOaoo+Wt
kC4uLuOAgjxicj48YnI+5Zue5q245q2j6aGMIFhE44CC6YCZ6KOh55So5LiA5YCL5b6I57Ch5Zau
55qE56+E5L6L5YWI5bGV56S6IFNwcmluZyBBT1Ag55qE55So5rOV44CC6aaW5YWI6ZyA6KaB5byV
5YWlIEFPUCDnm7jpl5znmoQgZGVwZW5kZW5jee+8jOWcqCBTcHJpbmcgQm9vdCDlj6/ku6XnsKHl
lq7lnLDmlL7lhaUgYHNwcmluZy1ib290LXN0YXJ0ZXItYW9wYCDlsLHlpb3jgII8YnI+PGJyPmBg
YDxicj5pbXBsZW1lbnRhdGlvbigib3JnLnNwcmluZ2ZyYW1ld29yay5ib290OnNwcmluZy1ib290
LXN0YXJ0ZXItYW9wIik8YnI+YGBgPGJyPjxicj7pgJnlgIvnr4Tkvovoo6HvvIzmiJHlu7rnq4vk
uobkuIDlgIsgYENvbW1hbmRMaW5lUnVubmVyYO+8jOS9nOeCuiBTcHJpbmcgQm9vdCDnmoTllZ/l
i5XlsI3osaHjgILpgJnlgIsgUnVubmVyIOijoemdouacg+maqOapn+eUoueUnyAxMCDlgIsgRXZl
bnTvvIznhLblvozlsIcgRXZlbnQg6aS157WmIGBGYWtlRXZlbnRQcm9jZXNzb3Jg77yM5omA5Lul
5oiR5pyA5b6M6KaB6KKrIEFPUCDmj5LlhaXnmoTlsI3osaHmmK8gYEZha2VFdmVudFByb2Nlc3Nv
cmDjgILpnIDopoHms6jmhI/nmoTmmK/vvIzpgJnoo6EgYEZha2VFdmVudFByb2Nlc3NvcmAg5piv
55So5rOo5YWl55qE5pa55byP55Si55Sf55qE77yM6ICM5LiN5piv6Ieq5bex5ZG85Y+rIGBuZXdg
77yM5Zug54K65LiK6Z2i5o+Q5Yiw5LqGIFNwcmluZyBBT1Ag5Y+q6IO95bCNIFNwcmluZyDnrqHn
kIbnmoQgYmVhbiDnmbznlJ/mlYjnlKjjgII8YnI+PGJyPjxicj48YnI+YGBgamF2YTxicj5AU2xm
NGo8YnI+QENvbXBvbmVudDxicj5wdWJsaWMgY2xhc3MgUnVuIGltcGxlbWVudHMgQ29tbWFuZExp
bmVSdW5uZXIgezxicj7CoMKgwqAgQEF1dG93aXJlZDxicj7CoMKgwqAgcHJpdmF0ZSBGYWtlRXZl
bnRQcm9jZXNzb3IgcHJvY2Vzc29yOzxicj48YnI+wqDCoMKgIEBPdmVycmlkZTxicj7CoMKgwqAg
cHVibGljIHZvaWQgcnVuKFN0cmluZy4uLiBhcmdzKSB0aHJvd3MgRXhjZXB0aW9uIHs8YnI+wqDC
oMKgwqDCoMKgwqAgaW50IGxvb3AgPSAxMDs8YnI+wqDCoMKgwqDCoMKgwqAgd2hpbGUobG9vcC0t
ICZndDsgMCkgezxicj7CoMKgwqDCoMKgwqDCoMKgwqDCoMKgIHZhciBldmVudCA9IGdlbmVyYXRl
UmFuZG9tRXZlbnQoKTs8YnI+wqDCoMKgwqDCoMKgwqDCoMKgwqDCoCBwcm9jZXNzb3IucmVjZWl2
ZUV2ZW50KGV2ZW50KTs8YnI+wqDCoMKgwqDCoMKgwqAgfTxicj7CoMKgwqAgfTxicj59PGJyPjxi
cj5AQnVpbGRlcjxicj5AR2V0dGVyPGJyPkBBY2Nlc3NvcnMoZmx1ZW50ID0gdHJ1ZSk8YnI+QFRv
U3RyaW5nPGJyPnB1YmxpYyBjbGFzcyBFdmVudCB7PGJyPsKgwqDCoCBwcml2YXRlIFN0cmluZyBl
dmVudFR5cGU7PGJyPsKgwqDCoCBwcml2YXRlIFN0cmluZyBvd25lcjs8YnI+fTxicj48YnI+QFNs
ZjRqPGJyPkBDb21wb25lbnQ8YnI+cHVibGljIGNsYXNzIEZha2VFdmVudFByb2Nlc3NvciB7PGJy
PsKgwqDCoCBwdWJsaWMgdm9pZCByZWNlaXZlRXZlbnQoRXZlbnQgZXZlbnQpIHs8YnI+wqDCoMKg
wqDCoMKgwqAgbG9nLmluZm8oIlJlY2VpdmU6IHt9IiwgZXZlbnQpOzxicj7CoMKgwqAgfTxicj59
PGJyPmBgYDxicj48YnI+5o6l6JGX5a6j5ZGK5LiA5YCLIEFzcGVjdO+8jOebrueahOaYr+aIkeaD
s+imgeaUlOaIqiBgRmFrZUV2ZW50UHJvY2Vzc29yYCDmr4/mrKHlkbzlj6sgYHJlY2VpdmVFdmVu
dCgpYCDnmoTmmYLlgJnvvIzpg73lnKjlroPooqvlkbzlj6vliY3lhYjlkbzlj6vmiJHopoHmj5Ll
haXnmoTpgo/ovK/jgII8YnI+PGJyPmBgYGphdmE8YnI+QFNsZjRqPGJyPkBBc3BlY3Q8YnI+QENv
bXBvbmVudDxicj5wdWJsaWMgY2xhc3MgVGVsZW1ldHJ5QXNwZWN0IHs8YnI+PGJyPsKgwqDCoCBw
dWJsaWMgVGVsZW1ldHJ5QXNwZWN0KCkgezxicj7CoMKgwqDCoMKgwqDCoCBsb2cuaW5mbygiSW5p
dGlhdGUgYXNwZWN0Li4uIik7PGJyPsKgwqDCoCB9PGJyPjxicj7CoMKgwqAgQEJlZm9yZSgiZXhl
Y3V0aW9uKCogdHcuamltd2F5bmV5ZWguZXhhbXBsZS5vdGVsLkZha2VFdmVudFByb2Nlc3Nvci5y
ZWNlaXZlRXZlbnQoLi4pKSIpPGJyPsKgwqDCoCBwdWJsaWMgdm9pZCBiZWZvcmUoSm9pblBvaW50
IGpvaW5Qb2ludCkgezxicj7CoMKgwqDCoMKgwqDCoCBFdmVudCBldmVudCA9IChFdmVudCkgam9p
blBvaW50LmdldEFyZ3MoKVswXTs8YnI+wqDCoMKgwqDCoMKgwqAgbG9nLnRyYWNlKCJqb2luICd7
fSgpJzoge30iLCBqb2luUG9pbnQuZ2V0U2lnbmF0dXJlKCkuZ2V0TmFtZSgpLCBldmVudCk7PGJy
PsKgwqDCoCB9PGJyPn08YnI+YGBgPGJyPjxicj7pgJnoo6HmiJHkvb/nlKjnmoTmlJTmiKrmlrnl
vI/mmK8gYEBCZWZvcmVg77yM6KGo56S65Zyo55uu5qiZ6KKr5Z+36KGM5YmN5YWI5Z+36KGM5pSU
5oiq55qE6YKP6Lyv77yM5Lim5LiU5pSU5oiq55qE5bCN6LGh5pivIGBAQmVmb3JlYCDoo6Hlrprn
vqnnmoQgbWV0aG9kIGB0dy5qaW13YXluZXllaC5leGFtcGxlLm90ZWwuRmFrZUV2ZW50UHJvY2Vz
c29yLnJlY2VpdmVFdmVudCguLilg44CC6ICM5pSU5oiq5b6M77yM5oiR6KaB5ou/5Ye65YKz5YWl
55qEIGV2ZW50IOS4puS4lOWNsOWHuiBsb2fvvIzpgJnmqKPmiJHmiY3og73norroqo3lroPmlJTm
iKrliLDnmoTmmK/ku4DpurzmnbHopb/jgII8YnI+PGJyPuacgOW+jOmAmeauteeoi+W8j+eivOi3
kei1t+S+hu+8jOacg+i3keWHuuS7peS4i+eahOe1kOaenO+8mjxicj48YnI+YGBgPGJyPlRSQUNF
IHQuai5lLm8uVGVsZW1ldHJ5QXNwZWN0IFttYWluXSBqb2luICdyZWNlaXZlRXZlbnQoKSc6IEV2
ZW50KGV2ZW50VHlwZT1kZWxldGUsIG93bmVyPW93bmVyLTApPGJyPklORk/CoCB0LmouZS5vLkZh
a2VFdmVudFByb2Nlc3NvciBbbWFpbl0gUmVjZWl2ZTogRXZlbnQoZXZlbnRUeXBlPWRlbGV0ZSwg
b3duZXI9b3duZXItMCk8YnI+VFJBQ0UgdC5qLmUuby5UZWxlbWV0cnlBc3BlY3QgW21haW5dIGpv
aW4gJ3JlY2VpdmVFdmVudCgpJzogRXZlbnQoZXZlbnRUeXBlPXVwZGF0ZSwgb3duZXI9b3duZXIt
MSk8YnI+SU5GT8KgIHQuai5lLm8uRmFrZUV2ZW50UHJvY2Vzc29yIFttYWluXSBSZWNlaXZlOiBF
dmVudChldmVudFR5cGU9dXBkYXRlLCBvd25lcj1vd25lci0xKTxicj5UUkFDRSB0LmouZS5vLlRl
bGVtZXRyeUFzcGVjdCBbbWFpbl0gam9pbiAncmVjZWl2ZUV2ZW50KCknOiBFdmVudChldmVudFR5
cGU9dXBkYXRlLCBvd25lcj1vd25lci02KTxicj5JTkZPwqAgdC5qLmUuby5GYWtlRXZlbnRQcm9j
ZXNzb3IgW21haW5dIFJlY2VpdmU6IEV2ZW50KGV2ZW50VHlwZT11cGRhdGUsIG93bmVyPW93bmVy
LTYpPGJyPi4uLihpZ25vcmVkKTxicj5gYGA8YnI+PGJyPumAmeijoeWPquWIl+S6huacgOmWi+Wn
i+S4ieihjO+8jOWboOeCuuW+jOmdoumDveW3ruS4jeWkmiBYROOAguWPr+S7peeci+WIsOmHneWw
jeavj+WAiyBldmVudCDnmoTlkbzlj6vvvIzmlJTmiKrnmoQgbG9nIOacg+WFiOWHuuePvu+8iGpv
aW4g6ZaL6aCt55qEIGxvZ++8ie+8jOeEtuW+jOaJjeWHuuePvuWOn+acrCBtZXRob2Qg5Z+36KGM
55qE5a+rIGxvZ++8iFJlY2VpdmUg6ZaL6aCt55qEIGxvZ++8ie+8jOS4puS4lOWFqeWAiyBsb2cg
5Y2w5Ye65L6G55qEIGV2ZW50IOaYr+WujOWFqOS4gOaoo+eahO+8jOihqOekuiBBc3BlY3Qg56K6
5a+m6KKr5o+S5YWl5YiwIG1ldGhvZCBjYWxsIOS5i+WJjeS6huOAgjxicj48YnI+PGJyPltzcHJp
bmctYW9wXTogaHR0cHM6Ly9kb2NzLnNwcmluZy5pby9zcHJpbmctZnJhbWV3b3JrL2RvY3MvNS4z
LjIzL3JlZmVyZW5jZS9odG1sL2NvcmUuaHRtbCNhb3A8YnI+W3NwcmluZy1hb3AtY2FwLWFuZC1n
b2FsXTogaHR0cHM6Ly9kb2NzLnNwcmluZy5pby9zcHJpbmctZnJhbWV3b3JrL2RvY3MvNS4zLjIz
L3JlZmVyZW5jZS9odG1sL2NvcmUuaHRtbCNhb3AtaW50cm9kdWN0aW9uLXNwcmluZy1kZWZuPGJy
Pjxicj4=&quot;&gt;​&lt;/div&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimwayne.blogspot.com/feeds/3326694108456395503/comments/default' title='張貼留言'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/6819483672105967231/3326694108456395503' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6819483672105967231/posts/default/3326694108456395503'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6819483672105967231/posts/default/3326694108456395503'/><link rel='alternate' type='text/html' href='http://jimwayne.blogspot.com/2023/01/spring-aop.html' title='Spring AOP 基本使用'/><author><name>Wayne Yeh</name><uri>http://www.blogger.com/profile/02148905864753873930</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6819483672105967231.post-1225417313404937567</id><published>2022-11-10T23:28:00.002+08:00</published><updated>2023-02-08T00:39:52.810+08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="Java"/><category scheme="http://www.blogger.com/atom/ns#" term="Jetty"/><category scheme="http://www.blogger.com/atom/ns#" term="Markdown Here"/><category scheme="http://www.blogger.com/atom/ns#" term="Spring Boot"/><title type='text'>Spring Boot 啟用 mTLS</title><content type='html'>&lt;div class=&quot;markdown-here-wrapper&quot; data-md-url=&quot;https://www.blogger.com/blog/post/edit/6819483672105967231/1225417313404937567&quot; markdown-here-wrapper-content-modified=&quot;true&quot;&gt;&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;最近在幫專案導入 Spring Boot，導入時比較麻煩的地方，是要保持專案的權限控制，也就是要讓它依然能夠正常以 Mutual TLS 的方式進行驗證。雖然在這個狀況下，往好處想是我們已經有現成的測試程序，能夠用來驗證是否 Mutual TLS 有正常運作，不過當驗證異常的時候，要找出問題還是蠻費工夫的…。這篇會稍微紀錄一點相關的資訊。&lt;/p&gt;
&lt;h1 id=&quot;-mutual-tls-&quot; style=&quot;border-bottom: 1px solid rgb(221, 221, 221); font-size: 1.6em; font-weight: bold; margin: 1.3em 0px 1em; padding: 0px;&quot;&gt;什麼是 Mutual TLS？&lt;/h1&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;Mutual TLS（或者簡稱 mTLS）是驗證範圍更寬一點的 HTTPS，一般討論 HTTPS 時，比較常見的討論對象是單向的 TLS，也就是讓客戶端驗證伺服器是否真的是它宣稱的伺服器。而 Mutual TLS 則是雙向的 TLS，不但客戶端要驗證伺服器、伺服器也要驗證客戶端。換句話說，伺服器和客戶端都需要有私鑰和憑證。&lt;/p&gt;
&lt;h1 id=&quot;tls-&quot; style=&quot;border-bottom: 1px solid rgb(221, 221, 221); font-size: 1.6em; font-weight: bold; margin: 1.3em 0px 1em; padding: 0px;&quot;&gt;TLS 相關名詞解釋&lt;/h1&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;TLS 在運作時，會需要有公鑰（public key）和私鑰（private key），這裡就不解釋各自的用途了。實際 TLS 在運作時，通常會以私鑰和憑證（certificate）的形式在表示，其中憑證的內容是包含了公鑰、發行者（Issuer）和有效時間等資訊的檔案。也就是說，憑證會拿來提供給對方做驗證。&lt;/p&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;在使用 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;curl&lt;/code&gt; 時，會注意到 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;curl&lt;/code&gt; 允許的 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;--key&lt;/code&gt; 和 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;--cert&lt;/code&gt; 的格式是 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;PEM&lt;/code&gt;，這是因為 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;curl&lt;/code&gt; 就只支援 PEM 格式。不過在 Java 的世界中，比較麻煩的是 Java 一般不支援 PEM，所以網路上的文章常常會看到有使用 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;keytool&lt;/code&gt; 的流程，這個流程是為了把 PEM 格式的私鑰和憑證，轉換成 PKCS #12（&lt;a href=&quot;https://en.wikipedia.org/wiki/PKCS_12&quot;&gt;.p12&lt;/a&gt;）或者 Java KeyStore 格式（&lt;a href=&quot;https://en.wikipedia.org/wiki/Java_KeyStore&quot;&gt;.jks&lt;/a&gt;）的檔案。可以參考&lt;a href=&quot;https://zh.wikipedia.org/zh-hant/%E5%85%AC%E9%92%A5%E5%AF%86%E7%A0%81%E5%AD%A6%E6%A0%87%E5%87%86&quot;&gt;公鑰密碼學標準&lt;/a&gt;。&lt;/p&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;除了 KeyStore 以外，另外還會有個 TrustStore 的玩意兒。兩個的功能剛好相反，KeyStore 是用來讓別人做驗證、TrustStore 的功能則是用來驗證別人。所以說，KeyStore 的產生會使用自己的私鑰和憑證，這是因為產生出來的 KeyStore 內容也會包含自己的憑證資訊。&lt;/p&gt;
&lt;h1 id=&quot;-spring-boot-ssl&quot; style=&quot;border-bottom: 1px solid rgb(221, 221, 221); font-size: 1.6em; font-weight: bold; margin: 1.3em 0px 1em; padding: 0px;&quot;&gt;設定 Spring Boot 的 SSL&lt;/h1&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;具體設定方法，可以參考官方文件中 &lt;a href=&quot;https://docs.spring.io/spring-boot/docs/2.7.4/reference/html/howto.html#howto.webserver.configure-ssl&quot;&gt;How-to 章節的 SSL 部份&lt;/a&gt;。不過我個人其實覺得官方文件寫得還挺籠統的…。&lt;/p&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;實際我自己在設定時，在 Spring Boot + Jetty 底下有遇到幾種奇怪的狀況：&lt;/p&gt;
&lt;ol style=&quot;margin: 1.2em 0px; padding-left: 2em;&quot;&gt;
&lt;li style=&quot;margin: 0.5em 0px;&quot;&gt;使用 keystore（JKS） 和 truststore，然後 Spring Boot 顯示有正常啟動 HTTPS，但結果用 curl 或 openssl 檢查時，都檢查不到有 HTTPS。&lt;/li&gt;
&lt;li style=&quot;margin: 0.5em 0px;&quot;&gt;使用 PEM，然後 HTTPS 正常啟動，憑證也能正常被客戶端讀取，但 mTLS 一直失敗。&lt;/li&gt;
&lt;/ol&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;最後這裡直接紀錄目前會成功的版本，是採用 PKCS12 的格式讓 Spring Boot 讀取。&lt;/p&gt;
&lt;pre style=&quot;font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; font-size: 1em; line-height: 1.2em; margin: 1.2em 0px;&quot;&gt;&lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border-radius: 3px; border: 1px solid rgb(204, 204, 204); border: 1px solid rgb(234, 234, 234); display: block !important; display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; overflow: auto; padding: 0.5em 0.7em; padding: 0px 0.3em; white-space: pre-wrap; white-space: pre;&quot;&gt;server.port=8443
server.ssl.enabled=true
server.ssl.key-store=/path/to/key/store/my_keystore.p12  # PKCS12 檔案的路徑
server.ssl.key-alias=accio  # 別名，在產生 PKCS12 檔的時候指定的名字
server.ssl.key-store-password=mypassword  # PKCS12 的密碼
server.ssl.trust-store=/path/to/trust/store/my_truststore.jks  # trust store 檔案的路徑
server.ssl.trust-store-type=JCEKS  # trust store 檔案的格式
server.ssl.trust-store-password=mypassword  # trust store 的密碼
server.ssl.client-auth=want  # 啟用 mTLS，但不強制
&lt;/code&gt;&lt;/pre&gt;&lt;h1 id=&quot;-https-&quot; style=&quot;border-bottom: 1px solid rgb(221, 221, 221); font-size: 1.6em; font-weight: bold; margin: 1.3em 0px 1em; padding: 0px;&quot;&gt;如何檢查 HTTPS？&lt;/h1&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;使用 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;curl&lt;/code&gt; 或者 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;openssl&lt;/code&gt; 都可以做檢查 HTTPS。&lt;/p&gt;
&lt;h2 id=&quot;openssl&quot; style=&quot;border-bottom: 1px solid rgb(238, 238, 238); font-size: 1.4em; font-weight: bold; margin: 1.3em 0px 1em; padding: 0px;&quot;&gt;openssl&lt;/h2&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;用 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;openssl&lt;/code&gt; 時，就是讓 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;openssl&lt;/code&gt; 模擬成 client，指令如下：&lt;/p&gt;
&lt;pre style=&quot;font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; font-size: 1em; line-height: 1.2em; margin: 1.2em 0px;&quot;&gt;&lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border-radius: 3px; border: 1px solid rgb(204, 204, 204); border: 1px solid rgb(234, 234, 234); display: block !important; display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; overflow: auto; padding: 0.5em 0.7em; padding: 0px 0.3em; white-space: pre-wrap; white-space: pre;&quot;&gt;openssl s_client -connect localhost:8443
&lt;/code&gt;&lt;/pre&gt;&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;以我的狀況，如果 HTTPS 啟動但有問題 (?) 的話，會出現以下這樣的訊息。&lt;/p&gt;
&lt;pre style=&quot;font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; font-size: 1em; line-height: 1.2em; margin: 1.2em 0px;&quot;&gt;&lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border-radius: 3px; border: 1px solid rgb(204, 204, 204); border: 1px solid rgb(234, 234, 234); display: block !important; display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; overflow: auto; padding: 0.5em 0.7em; padding: 0px 0.3em; white-space: pre-wrap; white-space: pre;&quot;&gt;[root@a976d6ac5637 ~]# openssl s_client -connect localhost:8443
CONNECTED(00000003)
140387372447632:error:14077410:SSL routines:SSL23_GET_SERVER_HELLO:sslv3 alert handshake failure:s23_clnt.c:769:
---
no peer certificate available
---
No client certificate CA names sent
---
SSL handshake has read 7 bytes and written 289 bytes
---
New, (NONE), Cipher is (NONE)
Secure Renegotiation IS NOT supported
Compression: NONE
Expansion: NONE
No ALPN negotiated
SSL-Session:
    Protocol  : TLSv1.2
    Cipher    : 0000
    Session-ID:
    Session-ID-ctx:
    Master-Key:
    Key-Arg   : None
    Krb5 Principal: None
    PSK identity: None
    PSK identity hint: None
    Start Time: 1667406122
    Timeout   : 300 (sec)
    Verify return code: 0 (ok)
---
&lt;/code&gt;&lt;/pre&gt;&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;訊息中可以明確看出，&lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;openssl&lt;/code&gt; 並沒有找到合適的 cipher 能夠進行加密連線。&lt;/p&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;而如果是正常的 HTTPS 的話，會像是這樣：&lt;/p&gt;
&lt;pre style=&quot;font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; font-size: 1em; line-height: 1.2em; margin: 1.2em 0px;&quot;&gt;&lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border-radius: 3px; border: 1px solid rgb(204, 204, 204); border: 1px solid rgb(234, 234, 234); display: block !important; display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; overflow: auto; padding: 0.5em 0.7em; padding: 0px 0.3em; white-space: pre-wrap; white-space: pre;&quot;&gt;[root@a976d6ac5637 ~]# openssl s_client -connect google.com:443
CONNECTED(00000005)
depth=3 C = BE, O = GlobalSign nv-sa, OU = Root CA, CN = GlobalSign Root CA
verify return:1
depth=2 C = US, O = Google Trust Services LLC, CN = GTS Root R1
verify return:1
depth=1 C = US, O = Google Trust Services LLC, CN = GTS CA 1C3
verify return:1
depth=0 CN = *.google.com
verify return:1
---
Certificate chain
 0 s:/CN=*.google.com
   i:/C=US/O=Google Trust Services LLC/CN=GTS CA 1C3
 1 s:/C=US/O=Google Trust Services LLC/CN=GTS CA 1C3
   i:/C=US/O=Google Trust Services LLC/CN=GTS Root R1
 2 s:/C=US/O=Google Trust Services LLC/CN=GTS Root R1
   i:/C=BE/O=GlobalSign nv-sa/OU=Root CA/CN=GlobalSign Root CA
---
Server certificate
-----BEGIN CERTIFICATE-----
....(ignored)....
-----END CERTIFICATE-----
subject=/CN=*.google.com
issuer=/C=US/O=Google Trust Services LLC/CN=GTS CA 1C3
---
No client certificate CA names sent
Server Temp Key: ECDH, X25519, 253 bits
---
SSL handshake has read 7270 bytes and written 281 bytes
---
New, TLSv1/SSLv3, Cipher is ECDHE-RSA-CHACHA20-POLY1305
Server public key is 2048 bit
Secure Renegotiation IS supported
Compression: NONE
Expansion: NONE
No ALPN negotiated
SSL-Session:
    Protocol  : TLSv1.2
    Cipher    : ECDHE-RSA-CHACHA20-POLY1305
    Session-ID: D0F1CB0DAE17297143F933D8C93BB905D088849CAAFED06698803C8F14367771
    Session-ID-ctx:
    Master-Key: ADC789FEFEF3A3747E6741D23B090CAA3E1BBEB8B6DED6EB44910E45257F9C977378CCAB42C387828EDC2CC8D4B91059
    TLS session ticket lifetime hint: 100800 (seconds)
    TLS session ticket:
    ....(ignored)....

    Start Time: 1668083091
    Timeout   : 7200 (sec)
    Verify return code: 0 (ok)
---
&lt;/code&gt;&lt;/pre&gt;&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;部分細節我就直接刪掉了，不過可以看出正常狀況它應該要能解析出 server 的憑證內容，並且顯示使用什麼協定來跟伺服器溝通。&lt;/p&gt;
&lt;h2 id=&quot;-jetty-log&quot; style=&quot;border-bottom: 1px solid rgb(238, 238, 238); font-size: 1.4em; font-weight: bold; margin: 1.3em 0px 1em; padding: 0px;&quot;&gt;附錄：Jetty 的 log&lt;/h2&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;由於我用的是 Jetty，因此如果想追 Jetty 這邊的 log，可以打開 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;org.eclipse.jetty&lt;/code&gt; 的 debug log，從訊息中可以看出，Jetty 在啟用 HTTPS 時，會建立 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;SslContextFactory&lt;/code&gt; 的物件來進行（廢話）。更具體來說，應該會是 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;SslContextFactory.Server&lt;/code&gt; 物件。雖然說，出現這段看起來有啟動 HTTPS 的 log，實際上並不代表 HTTPS 就有正常被啟動了。我的實驗過程有遇到一個情況是 log 顯示 HTTPS 似乎啟動了，但結果真的用 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;curl&lt;/code&gt; 跟 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;openssl&lt;/code&gt; 打都找不到憑證…。&lt;/p&gt;
&lt;pre style=&quot;font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; font-size: 1em; line-height: 1.2em; margin: 1.2em 0px;&quot;&gt;&lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border-radius: 3px; border: 1px solid rgb(204, 204, 204); border: 1px solid rgb(234, 234, 234); display: block !important; display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; overflow: auto; padding: 0.5em 0.7em; padding: 0px 0.3em; white-space: pre-wrap; white-space: pre;&quot;&gt;2022-11-02 15:48:15,603 [main] DEBUG org.eclipse.jetty.util.component.AbstractLifeCycle - starting SslConnectionFactory@40ee0a22{SSL-&amp;gt;HTTP/1.1}
2022-11-02 15:48:15,603 [main] DEBUG org.eclipse.jetty.util.component.AbstractLifeCycle - starting Server@7bde1f3a[provider=null,keyStore=file:////path/to/key/store/my_keystore.p12,trustStore=file:////path/to/trust/store/my_truststore.jks]
2022-11-02 15:48:15,636 [main] DEBUG org.eclipse.jetty.util.ssl.SslContextFactory - managers=[org.eclipse.jetty.util.ssl.AliasedX509ExtendedKeyManager@5cc152f9] for Server@7bde1f3a[provider=null,keyStore=file:////path/to/key/store/my_keystore.p12,trustStore=file:////path/to/trust/store/my_truststore.jks]
2022-11-02 15:48:15,640 [main] DEBUG org.eclipse.jetty.util.ssl.SslContextFactory - Selected Protocols [TLSv1.3, TLSv1.2] of [TLSv1.3, TLSv1.2, TLSv1.1, TLSv1, SSLv3, SSLv2Hello]
2022-11-02 15:48:15,640 [main] DEBUG org.eclipse.jetty.util.ssl.SslContextFactory - Selected Ciphers   [TLS_AES_256_GCM_SHA384, ....]
2022-11-02 15:48:15,640 [main] DEBUG org.eclipse.jetty.util.ssl.SslContextFactory - Customize sun.security.ssl.SSLEngineImpl@1117cc7c
2022-11-02 15:48:15,641 [main] DEBUG org.eclipse.jetty.util.component.AbstractLifeCycle - STARTED @119497ms Server@7bde1f3a[provider=null,keyStore=file:////path/to/key/store/my_keystore.p12,trustStore=file:////path/to/trust/store/my_truststore.jks]
2022-11-02 15:48:15,641 [main] DEBUG org.eclipse.jetty.util.ssl.SslContextFactory - Customize sun.security.ssl.SSLEngineImpl@4797023d
2022-11-02 15:48:15,641 [main] DEBUG org.eclipse.jetty.util.component.AbstractLifeCycle - STARTED @119497ms SslConnectionFactory@40ee0a22{SSL-&amp;gt;HTTP/1.1}
&lt;/code&gt;&lt;/pre&gt;&lt;div style=&quot;font-size: 0em; height: 0; margin: 0; max-height: 0; max-width: 0; overflow: hidden; padding: 0; width: 0;&quot; title=&quot;MDH:PHA+5pyA6L+R5Zyo5bmr5bCI5qGI5bCO5YWlIFNwcmluZyBCb29077yM5bCO5YWl5pmC5q+U6LyD
6bq754Wp55qE5Zyw5pa577yM5piv6KaB5L+d5oyB5bCI5qGI55qE5qyK6ZmQ5o6n5Yi277yM5Lmf
5bCx5piv6KaB6K6T5a6D5L6d54S26IO95aSg5q2j5bi45LulIE11dHVhbCBUTFMg55qE5pa55byP
6YCy6KGM6amX6K2J44CC6ZuW54S25Zyo6YCZ5YCL54uA5rOB5LiL77yM5b6A5aW96JmV5oOz5piv
5oiR5YCR5bey57aT5pyJ54++5oiQ55qE5ris6Kmm56iL5bqP77yM6IO95aSg55So5L6G6amX6K2J
5piv5ZCmIE11dHVhbCBUTFMg5pyJ5q2j5bi46YGL5L2c77yM5LiN6YGO55W26amX6K2J55Ww5bi4
55qE5pmC5YCZ77yM6KaB5om+5Ye65ZWP6aGM6YKE5piv6KC76LK75bel5aSr55qELi4u44CC6YCZ
56+H5pyD56iN5b6u57SA6YyE5LiA6bue55u46Zec55qE6LOH6KiK44CCPGJyPjxicj4jIOS7gOm6
vOaYryBNdXR1YWwgVExT77yfPGJyPjxicj5NdXR1YWwgVExT77yI5oiW6ICF57Ch56ixIG1UTFPv
vInmmK/pqZforYnnr4TlnI3mm7Tlr6zkuIDpu57nmoQgSFRUUFPvvIzkuIDoiKzoqI7oq5YgSFRU
UFMg5pmC77yM5q+U6LyD5bi46KaL55qE6KiO6KuW5bCN6LGh5piv5Zau5ZCR55qEIFRMU++8jOS5
n+WwseaYr+iuk+WuouaItuerr+mpl+itieS8uuacjeWZqOaYr+WQpuecn+eahOaYr+Wug+Wuo+eo
seeahOS8uuacjeWZqOOAguiAjCBNdXR1YWwgVExTIOWJh+aYr+mbmeWQkeeahCBUTFPvvIzkuI3k
vYblrqLmiLbnq6/opoHpqZforYnkvLrmnI3lmajjgIHkvLrmnI3lmajkuZ/opoHpqZforYnlrqLm
iLbnq6/jgILmj5vlj6XoqbHoqqrvvIzkvLrmnI3lmajlkozlrqLmiLbnq6/pg73pnIDopoHmnInn
p4HpkbDlkozmhpHorYnjgII8YnI+PGJyPiMgVExTIOebuOmXnOWQjeipnuino+mHizxicj48YnI+
VExTIOWcqOmBi+S9nOaZgu+8jOacg+mcgOimgeacieWFrOmRsO+8iHB1YmxpYyBrZXnvvInlkozn
p4HpkbDvvIhwcml2YXRlIGtlee+8ie+8jOmAmeijoeWwseS4jeino+mHi+WQhOiHqueahOeUqOmA
lOS6huOAguWvpumamyBUTFMg5Zyo6YGL5L2c5pmC77yM6YCa5bi45pyD5Lul56eB6ZGw5ZKM5oaR
6K2J77yIY2VydGlmaWNhdGXvvInnmoTlvaLlvI/lnKjooajnpLrvvIzlhbbkuK3mhpHorYnnmoTl
haflrrnmmK/ljIXlkKvkuoblhazpkbDjgIHnmbzooYzogIXvvIhJc3N1ZXLvvInlkozmnInmlYjm
mYLplpPnrYnos4foqIrnmoTmqpTmoYjjgILkuZ/lsLHmmK/oqqrvvIzmhpHorYnmnIPmi7/kvobm
j5DkvpvntablsI3mlrnlgZrpqZforYnjgII8YnI+PGJyPuWcqOS9v+eUqCBgY3VybGAg5pmC77yM
5pyD5rOo5oSP5YiwIGBjdXJsYCDlhYHoqLHnmoQgYC0ta2V5YCDlkowgYC0tY2VydGAg55qE5qC8
5byP5pivIGBQRU1g77yM6YCZ5piv5Zug54K6IGBjdXJsYCDlsLHlj6rmlK/mj7QgUEVNIOagvOW8
j+OAguS4jemBjuWcqCBKYXZhIOeahOS4lueVjOS4re+8jOavlOi8g+m6u+eFqeeahOaYryBKYXZh
IOS4gOiIrOS4jeaUr+aPtCBQRU3vvIzmiYDku6XntrLot6/kuIrnmoTmlofnq6DluLjluLjmnIPn
nIvliLDmnInkvb/nlKggYGtleXRvb2xgIOeahOa1geeoi++8jOmAmeWAi+a1geeoi+aYr+eCuuS6
huaKiiBQRU0g5qC85byP55qE56eB6ZGw5ZKM5oaR6K2J77yM6L2J5o+b5oiQIFBLQ1MgIzEy77yI
Wy5wMTJdW3BrY3MxMl3vvInmiJbogIUgSmF2YSBLZXlTdG9yZSDmoLzlvI/vvIhbLmprc11bamtz
Xe+8ieeahOaqlOahiOOAguWPr+S7peWPg+iAg1vlhazpkbDlr4bnorzlrbjmqJnmupZdW3B1Ymxp
Yy1rZXktc3RhbmRhcmRd44CCPGJyPjxicj7pmaTkuoYgS2V5U3RvcmUg5Lul5aSW77yM5Y+m5aSW
6YKE5pyD5pyJ5YCLIFRydXN0U3RvcmUg55qE546p5oSP5YWS44CC5YWp5YCL55qE5Yqf6IO95Ymb
5aW955u45Y+N77yMS2V5U3RvcmUg5piv55So5L6G6K6T5Yil5Lq65YGa6amX6K2J44CBVHJ1c3RT
dG9yZSDnmoTlip/og73liYfmmK/nlKjkvobpqZforYnliKXkurrjgILmiYDku6XoqqrvvIxLZXlT
dG9yZSDnmoTnlKLnlJ/mnIPkvb/nlKjoh6rlt7HnmoTnp4HpkbDlkozmhpHorYnvvIzpgJnmmK/l
m6DngrrnlKLnlJ/lh7rkvobnmoQgS2V5U3RvcmUg5YWn5a655Lmf5pyD5YyF5ZCr6Ieq5bex55qE
5oaR6K2J6LOH6KiK44CCPGJyPjxicj4jIOioreWumiBTcHJpbmcgQm9vdCDnmoQgU1NMPGJyPjxi
cj7lhbfpq5ToqK3lrprmlrnms5XvvIzlj6/ku6Xlj4PogIPlrpjmlrnmlofku7bkuK0gW0hvdy10
byDnq6Dnr4DnmoQgU1NMIOmDqOS7vV1bc3ByaW5nLWJvb3Qtc3NsXeOAguS4jemBjuaIkeWAi+S6
uuWFtuWvpuimuuW+l+WumOaWueaWh+S7tuWvq+W+l+mChOaMuuexoOe1seeahC4uLuOAgjxicj48
YnI+5a+m6Zqb5oiR6Ieq5bex5Zyo6Kit5a6a5pmC77yM5ZyoIFNwcmluZyBCb290ICsgSmV0dHkg
5bqV5LiL5pyJ6YGH5Yiw5bm+56iu5aWH5oCq55qE54uA5rOB77yaPGJyPjxicj4xLiDkvb/nlKgg
a2V5c3RvcmXvvIhKS1PvvIkg5ZKMIHRydXN0c3RvcmXvvIznhLblvowgU3ByaW5nIEJvb3Qg6aGv
56S65pyJ5q2j5bi45ZWf5YuVIEhUVFBT77yM5L2G57WQ5p6c55SoIGN1cmwg5oiWIG9wZW5zc2wg
5qqi5p+l5pmC77yM6YO95qqi5p+l5LiN5Yiw5pyJIEhUVFBT44CCPGJyPjIuIOS9v+eUqCBQRU3v
vIznhLblvowgSFRUUFMg5q2j5bi45ZWf5YuV77yM5oaR6K2J5Lmf6IO95q2j5bi46KKr5a6i5oi2
56uv6K6A5Y+W77yM5L2GIG1UTFMg5LiA55u05aSx5pWX44CCPGJyPjxicj7mnIDlvozpgJnoo6Hn
m7TmjqXntIDpjITnm67liY3mnIPmiJDlip/nmoTniYjmnKzvvIzmmK/mjqHnlKggUEtDUzEyIOea
hOagvOW8j+iukyBTcHJpbmcgQm9vdCDoroDlj5bjgII8YnI+PGJyPmBgYDxicj5zZXJ2ZXIucG9y
dD04NDQzPGJyPnNlcnZlci5zc2wuZW5hYmxlZD10cnVlPGJyPnNlcnZlci5zc2wua2V5LXN0b3Jl
PS9wYXRoL3RvL2tleS9zdG9yZS9teV9rZXlzdG9yZS5wMTImbmJzcDsgIyBQS0NTMTIg5qqU5qGI
55qE6Lev5b6RPGJyPnNlcnZlci5zc2wua2V5LWFsaWFzPWFjY2lvJm5ic3A7ICMg5Yil5ZCN77yM
5Zyo55Si55SfIFBLQ1MxMiDmqpTnmoTmmYLlgJnmjIflrprnmoTlkI3lrZc8YnI+c2VydmVyLnNz
bC5rZXktc3RvcmUtcGFzc3dvcmQ9bXlwYXNzd29yZCZuYnNwOyAjIFBLQ1MxMiDnmoTlr4bnorw8
YnI+c2VydmVyLnNzbC50cnVzdC1zdG9yZT0vcGF0aC90by90cnVzdC9zdG9yZS9teV90cnVzdHN0
b3JlLmprcyZuYnNwOyAjIHRydXN0IHN0b3JlIOaqlOahiOeahOi3r+W+kTxicj5zZXJ2ZXIuc3Ns
LnRydXN0LXN0b3JlLXR5cGU9SkNFS1MmbmJzcDsgIyB0cnVzdCBzdG9yZSDmqpTmoYjnmoTmoLzl
vI88YnI+c2VydmVyLnNzbC50cnVzdC1zdG9yZS1wYXNzd29yZD1teXBhc3N3b3JkJm5ic3A7ICMg
dHJ1c3Qgc3RvcmUg55qE5a+G56K8PGJyPnNlcnZlci5zc2wuY2xpZW50LWF1dGg9d2FudCZuYnNw
OyAjIOWVn+eUqCBtVExT77yM5L2G5LiN5by35Yi2PGJyPmBgYDxicj48YnI+IyDlpoLkvZXmqqLm
n6UgSFRUUFPvvJ88YnI+PGJyPuS9v+eUqCBgY3VybGAg5oiW6ICFIGBvcGVuc3NsYCDpg73lj6/k
u6XlgZrmqqLmn6UgSFRUUFPjgII8YnI+PGJyPiMjIG9wZW5zc2w8YnI+PGJyPueUqCBgb3BlbnNz
bGAg5pmC77yM5bCx5piv6K6TIGBvcGVuc3NsYCDmqKHmk6zmiJAgY2xpZW5077yM5oyH5Luk5aaC
5LiL77yaPGJyPjxicj5gYGA8YnI+b3BlbnNzbCBzX2NsaWVudCAtY29ubmVjdCBsb2NhbGhvc3Q6
ODQ0Mzxicj5gYGA8YnI+PGJyPuS7peaIkeeahOeLgOazge+8jOWmguaenCBIVFRQUyDllZ/li5Xk
vYbmnInllY/poYwgKD8pIOeahOipse+8jOacg+WHuuePvuS7peS4i+mAmeaoo+eahOioiuaBr+OA
gjxicj48YnI+YGBgPGJyPltyb290QGE5NzZkNmFjNTYzNyB+XSMgb3BlbnNzbCBzX2NsaWVudCAt
Y29ubmVjdCBsb2NhbGhvc3Q6ODQ0Mzxicj5DT05ORUNURUQoMDAwMDAwMDMpPGJyPjE0MDM4NzM3
MjQ0NzYzMjplcnJvcjoxNDA3NzQxMDpTU0wgcm91dGluZXM6U1NMMjNfR0VUX1NFUlZFUl9IRUxM
Tzpzc2x2MyBhbGVydCBoYW5kc2hha2UgZmFpbHVyZTpzMjNfY2xudC5jOjc2OTo8YnI+LS0tPGJy
Pm5vIHBlZXIgY2VydGlmaWNhdGUgYXZhaWxhYmxlPGJyPi0tLTxicj5ObyBjbGllbnQgY2VydGlm
aWNhdGUgQ0EgbmFtZXMgc2VudDxicj4tLS08YnI+U1NMIGhhbmRzaGFrZSBoYXMgcmVhZCA3IGJ5
dGVzIGFuZCB3cml0dGVuIDI4OSBieXRlczxicj4tLS08YnI+TmV3LCAoTk9ORSksIENpcGhlciBp
cyAoTk9ORSk8YnI+U2VjdXJlIFJlbmVnb3RpYXRpb24gSVMgTk9UIHN1cHBvcnRlZDxicj5Db21w
cmVzc2lvbjogTk9ORTxicj5FeHBhbnNpb246IE5PTkU8YnI+Tm8gQUxQTiBuZWdvdGlhdGVkPGJy
PlNTTC1TZXNzaW9uOjxicj4mbmJzcDsmbmJzcDsmbmJzcDsgUHJvdG9jb2wmbmJzcDsgOiBUTFN2
MS4yPGJyPiZuYnNwOyZuYnNwOyZuYnNwOyBDaXBoZXImbmJzcDsmbmJzcDsmbmJzcDsgOiAwMDAw
PGJyPiZuYnNwOyZuYnNwOyZuYnNwOyBTZXNzaW9uLUlEOjxicj4mbmJzcDsmbmJzcDsmbmJzcDsg
U2Vzc2lvbi1JRC1jdHg6PGJyPiZuYnNwOyZuYnNwOyZuYnNwOyBNYXN0ZXItS2V5Ojxicj4mbmJz
cDsmbmJzcDsmbmJzcDsgS2V5LUFyZyZuYnNwOyZuYnNwOyA6IE5vbmU8YnI+Jm5ic3A7Jm5ic3A7
Jm5ic3A7IEtyYjUgUHJpbmNpcGFsOiBOb25lPGJyPiZuYnNwOyZuYnNwOyZuYnNwOyBQU0sgaWRl
bnRpdHk6IE5vbmU8YnI+Jm5ic3A7Jm5ic3A7Jm5ic3A7IFBTSyBpZGVudGl0eSBoaW50OiBOb25l
PGJyPiZuYnNwOyZuYnNwOyZuYnNwOyBTdGFydCBUaW1lOiAxNjY3NDA2MTIyPGJyPiZuYnNwOyZu
YnNwOyZuYnNwOyBUaW1lb3V0Jm5ic3A7Jm5ic3A7IDogMzAwIChzZWMpPGJyPiZuYnNwOyZuYnNw
OyZuYnNwOyBWZXJpZnkgcmV0dXJuIGNvZGU6IDAgKG9rKTxicj4tLS08YnI+YGBgPGJyPjxicj7o
qIrmga/kuK3lj6/ku6XmmI7norrnnIvlh7rvvIxgb3BlbnNzbGAg5Lim5rKS5pyJ5om+5Yiw5ZCI
6YGp55qEIGNpcGhlciDog73lpKDpgLLooYzliqDlr4bpgKPnt5rjgII8YnI+PGJyPuiAjOWmguae
nOaYr+ato+W4uOeahCBIVFRQUyDnmoToqbHvvIzmnIPlg4/mmK/pgJnmqKPvvJo8YnI+PGJyPmBg
YDxicj5bcm9vdEBhOTc2ZDZhYzU2Mzcgfl0jIG9wZW5zc2wgc19jbGllbnQgLWNvbm5lY3QgZ29v
Z2xlLmNvbTo0NDM8YnI+Q09OTkVDVEVEKDAwMDAwMDA1KTxicj5kZXB0aD0zIEMgPSBCRSwgTyA9
IEdsb2JhbFNpZ24gbnYtc2EsIE9VID0gUm9vdCBDQSwgQ04gPSBHbG9iYWxTaWduIFJvb3QgQ0E8
YnI+dmVyaWZ5IHJldHVybjoxPGJyPmRlcHRoPTIgQyA9IFVTLCBPID0gR29vZ2xlIFRydXN0IFNl
cnZpY2VzIExMQywgQ04gPSBHVFMgUm9vdCBSMTxicj52ZXJpZnkgcmV0dXJuOjE8YnI+ZGVwdGg9
MSBDID0gVVMsIE8gPSBHb29nbGUgVHJ1c3QgU2VydmljZXMgTExDLCBDTiA9IEdUUyBDQSAxQzM8
YnI+dmVyaWZ5IHJldHVybjoxPGJyPmRlcHRoPTAgQ04gPSAqLmdvb2dsZS5jb208YnI+dmVyaWZ5
IHJldHVybjoxPGJyPi0tLTxicj5DZXJ0aWZpY2F0ZSBjaGFpbjxicj4mbmJzcDswIHM6L0NOPSou
Z29vZ2xlLmNvbTxicj4mbmJzcDsmbmJzcDsgaTovQz1VUy9PPUdvb2dsZSBUcnVzdCBTZXJ2aWNl
cyBMTEMvQ049R1RTIENBIDFDMzxicj4mbmJzcDsxIHM6L0M9VVMvTz1Hb29nbGUgVHJ1c3QgU2Vy
dmljZXMgTExDL0NOPUdUUyBDQSAxQzM8YnI+Jm5ic3A7Jm5ic3A7IGk6L0M9VVMvTz1Hb29nbGUg
VHJ1c3QgU2VydmljZXMgTExDL0NOPUdUUyBSb290IFIxPGJyPiZuYnNwOzIgczovQz1VUy9PPUdv
b2dsZSBUcnVzdCBTZXJ2aWNlcyBMTEMvQ049R1RTIFJvb3QgUjE8YnI+Jm5ic3A7Jm5ic3A7IGk6
L0M9QkUvTz1HbG9iYWxTaWduIG52LXNhL09VPVJvb3QgQ0EvQ049R2xvYmFsU2lnbiBSb290IENB
PGJyPi0tLTxicj5TZXJ2ZXIgY2VydGlmaWNhdGU8YnI+LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0t
LS0tPGJyPi4uLi4oaWdub3JlZCkuLi4uPGJyPi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS08YnI+
c3ViamVjdD0vQ049Ki5nb29nbGUuY29tPGJyPmlzc3Vlcj0vQz1VUy9PPUdvb2dsZSBUcnVzdCBT
ZXJ2aWNlcyBMTEMvQ049R1RTIENBIDFDMzxicj4tLS08YnI+Tm8gY2xpZW50IGNlcnRpZmljYXRl
IENBIG5hbWVzIHNlbnQ8YnI+U2VydmVyIFRlbXAgS2V5OiBFQ0RILCBYMjU1MTksIDI1MyBiaXRz
PGJyPi0tLTxicj5TU0wgaGFuZHNoYWtlIGhhcyByZWFkIDcyNzAgYnl0ZXMgYW5kIHdyaXR0ZW4g
MjgxIGJ5dGVzPGJyPi0tLTxicj5OZXcsIFRMU3YxL1NTTHYzLCBDaXBoZXIgaXMgRUNESEUtUlNB
LUNIQUNIQTIwLVBPTFkxMzA1PGJyPlNlcnZlciBwdWJsaWMga2V5IGlzIDIwNDggYml0PGJyPlNl
Y3VyZSBSZW5lZ290aWF0aW9uIElTIHN1cHBvcnRlZDxicj5Db21wcmVzc2lvbjogTk9ORTxicj5F
eHBhbnNpb246IE5PTkU8YnI+Tm8gQUxQTiBuZWdvdGlhdGVkPGJyPlNTTC1TZXNzaW9uOjxicj4m
bmJzcDsmbmJzcDsmbmJzcDsgUHJvdG9jb2wmbmJzcDsgOiBUTFN2MS4yPGJyPiZuYnNwOyZuYnNw
OyZuYnNwOyBDaXBoZXImbmJzcDsmbmJzcDsmbmJzcDsgOiBFQ0RIRS1SU0EtQ0hBQ0hBMjAtUE9M
WTEzMDU8YnI+Jm5ic3A7Jm5ic3A7Jm5ic3A7IFNlc3Npb24tSUQ6IEQwRjFDQjBEQUUxNzI5NzE0
M0Y5MzNEOEM5M0JCOTA1RDA4ODg0OUNBQUZFRDA2Njk4ODAzQzhGMTQzNjc3NzE8YnI+Jm5ic3A7
Jm5ic3A7Jm5ic3A7IFNlc3Npb24tSUQtY3R4Ojxicj4mbmJzcDsmbmJzcDsmbmJzcDsgTWFzdGVy
LUtleTogQURDNzg5RkVGRUYzQTM3NDdFNjc0MUQyM0IwOTBDQUEzRTFCQkVCOEI2REVENkVCNDQ5
MTBFNDUyNTdGOUM5NzczNzhDQ0FCNDJDMzg3ODI4RURDMkNDOEQ0QjkxMDU5PGJyPiZuYnNwOyZu
YnNwOyZuYnNwOyBUTFMgc2Vzc2lvbiB0aWNrZXQgbGlmZXRpbWUgaGludDogMTAwODAwIChzZWNv
bmRzKTxicj4mbmJzcDsmbmJzcDsmbmJzcDsgVExTIHNlc3Npb24gdGlja2V0Ojxicj4mbmJzcDsm
bmJzcDsmbmJzcDsgLi4uLihpZ25vcmVkKS4uLi48YnI+PGJyPiZuYnNwOyZuYnNwOyZuYnNwOyBT
dGFydCBUaW1lOiAxNjY4MDgzMDkxPGJyPiZuYnNwOyZuYnNwOyZuYnNwOyBUaW1lb3V0Jm5ic3A7
Jm5ic3A7IDogNzIwMCAoc2VjKTxicj4mbmJzcDsmbmJzcDsmbmJzcDsgVmVyaWZ5IHJldHVybiBj
b2RlOiAwIChvayk8YnI+LS0tPGJyPmBgYDxicj48YnI+6YOo5YiG57Sw56+A5oiR5bCx55u05o6l
5Yiq5o6J5LqG77yM5LiN6YGO5Y+v5Lul55yL5Ye65q2j5bi454uA5rOB5a6D5oeJ6Kmy6KaB6IO9
6Kej5p6Q5Ye6IHNlcnZlciDnmoTmhpHorYnlhaflrrnvvIzkuKbkuJTpoa/npLrkvb/nlKjku4Dp
urzljZTlrprkvobot5/kvLrmnI3lmajmup3pgJrjgII8YnI+PGJyPiMjIOmZhOmMhO+8mkpldHR5
IOeahCBsb2c8YnI+PGJyPueUseaWvOaIkeeUqOeahOaYryBKZXR0ee+8jOWboOatpOWmguaenOaD
s+i/vSBKZXR0eSDpgJnpgornmoQgbG9n77yM5Y+v5Lul5omT6ZaLIGBvcmcuZWNsaXBzZS5qZXR0
eWAg55qEIGRlYnVnIGxvZ++8jOW+nuioiuaBr+S4reWPr+S7peeci+WHuu+8jEpldHR5IOWcqOWV
n+eUqCBIVFRQUyDmmYLvvIzmnIPlu7rnq4sgYFNzbENvbnRleHRGYWN0b3J5YCDnmoTnianku7bk
vobpgLLooYzvvIjlu6LoqbHvvInjgILmm7Tlhbfpq5TkvoboqqrvvIzmh4noqbLmnIPmmK8gYFNz
bENvbnRleHRGYWN0b3J5LlNlcnZlcmAg54mp5Lu244CC6ZuW54S26Kqq77yM5Ye654++6YCZ5q61
55yL6LW35L6G5pyJ5ZWf5YuVIEhUVFBTIOeahCBsb2fvvIzlr6bpmpvkuIrkuKbkuI3ku6Pooagg
SFRUUFMg5bCx5pyJ5q2j5bi46KKr5ZWf5YuV5LqG44CC5oiR55qE5a+m6amX6YGO56iL5pyJ6YGH
5Yiw5LiA5YCL5oOF5rOB5pivIGxvZyDpoa/npLogSFRUUFMg5Ly85LmO5ZWf5YuV5LqG77yM5L2G
57WQ5p6c55yf55qE55SoIGBjdXJsYCDot58gYG9wZW5zc2xgIOaJk+mDveaJvuS4jeWIsOaGkeit
iS4uLuOAgjxicj48YnI+YGBgPGJyPjIwMjItMTEtMDIgMTU6NDg6MTUsNjAzIFttYWluXSBERUJV
RyBvcmcuZWNsaXBzZS5qZXR0eS51dGlsLmNvbXBvbmVudC5BYnN0cmFjdExpZmVDeWNsZSAtIHN0
YXJ0aW5nIFNzbENvbm5lY3Rpb25GYWN0b3J5QDQwZWUwYTIye1NTTC0mZ3Q7SFRUUC8xLjF9PGJy
PjIwMjItMTEtMDIgMTU6NDg6MTUsNjAzIFttYWluXSBERUJVRyBvcmcuZWNsaXBzZS5qZXR0eS51
dGlsLmNvbXBvbmVudC5BYnN0cmFjdExpZmVDeWNsZSAtIHN0YXJ0aW5nIFNlcnZlckA3YmRlMWYz
YVtwcm92aWRlcj1udWxsLGtleVN0b3JlPWZpbGU6Ly8vL3BhdGgvdG8va2V5L3N0b3JlL215X2tl
eXN0b3JlLnAxMix0cnVzdFN0b3JlPWZpbGU6Ly8vL3BhdGgvdG8vdHJ1c3Qvc3RvcmUvbXlfdHJ1
c3RzdG9yZS5qa3NdPGJyPjIwMjItMTEtMDIgMTU6NDg6MTUsNjM2IFttYWluXSBERUJVRyBvcmcu
ZWNsaXBzZS5qZXR0eS51dGlsLnNzbC5Tc2xDb250ZXh0RmFjdG9yeSAtIG1hbmFnZXJzPVtvcmcu
ZWNsaXBzZS5qZXR0eS51dGlsLnNzbC5BbGlhc2VkWDUwOUV4dGVuZGVkS2V5TWFuYWdlckA1Y2Mx
NTJmOV0gZm9yIFNlcnZlckA3YmRlMWYzYVtwcm92aWRlcj1udWxsLGtleVN0b3JlPWZpbGU6Ly8v
L3BhdGgvdG8va2V5L3N0b3JlL215X2tleXN0b3JlLnAxMix0cnVzdFN0b3JlPWZpbGU6Ly8vL3Bh
dGgvdG8vdHJ1c3Qvc3RvcmUvbXlfdHJ1c3RzdG9yZS5qa3NdPGJyPjIwMjItMTEtMDIgMTU6NDg6
MTUsNjQwIFttYWluXSBERUJVRyBvcmcuZWNsaXBzZS5qZXR0eS51dGlsLnNzbC5Tc2xDb250ZXh0
RmFjdG9yeSAtIFNlbGVjdGVkIFByb3RvY29scyBbVExTdjEuMywgVExTdjEuMl0gb2YgW1RMU3Yx
LjMsIFRMU3YxLjIsIFRMU3YxLjEsIFRMU3YxLCBTU0x2MywgU1NMdjJIZWxsb108YnI+MjAyMi0x
MS0wMiAxNTo0ODoxNSw2NDAgW21haW5dIERFQlVHIG9yZy5lY2xpcHNlLmpldHR5LnV0aWwuc3Ns
LlNzbENvbnRleHRGYWN0b3J5IC0gU2VsZWN0ZWQgQ2lwaGVycyZuYnNwOyZuYnNwOyBbVExTX0FF
U18yNTZfR0NNX1NIQTM4NCwgLi4uLl08YnI+MjAyMi0xMS0wMiAxNTo0ODoxNSw2NDAgW21haW5d
IERFQlVHIG9yZy5lY2xpcHNlLmpldHR5LnV0aWwuc3NsLlNzbENvbnRleHRGYWN0b3J5IC0gQ3Vz
dG9taXplIHN1bi5zZWN1cml0eS5zc2wuU1NMRW5naW5lSW1wbEAxMTE3Y2M3Yzxicj4yMDIyLTEx
LTAyIDE1OjQ4OjE1LDY0MSBbbWFpbl0gREVCVUcgb3JnLmVjbGlwc2UuamV0dHkudXRpbC5jb21w
b25lbnQuQWJzdHJhY3RMaWZlQ3ljbGUgLSBTVEFSVEVEIEAxMTk0OTdtcyBTZXJ2ZXJAN2JkZTFm
M2FbcHJvdmlkZXI9bnVsbCxrZXlTdG9yZT1maWxlOi8vLy9wYXRoL3RvL2tleS9zdG9yZS9teV9r
ZXlzdG9yZS5wMTIsdHJ1c3RTdG9yZT1maWxlOi8vLy9wYXRoL3RvL3RydXN0L3N0b3JlL215X3Ry
dXN0c3RvcmUuamtzXTxicj4yMDIyLTExLTAyIDE1OjQ4OjE1LDY0MSBbbWFpbl0gREVCVUcgb3Jn
LmVjbGlwc2UuamV0dHkudXRpbC5zc2wuU3NsQ29udGV4dEZhY3RvcnkgLSBDdXN0b21pemUgc3Vu
LnNlY3VyaXR5LnNzbC5TU0xFbmdpbmVJbXBsQDQ3OTcwMjNkPGJyPjIwMjItMTEtMDIgMTU6NDg6
MTUsNjQxIFttYWluXSBERUJVRyBvcmcuZWNsaXBzZS5qZXR0eS51dGlsLmNvbXBvbmVudC5BYnN0
cmFjdExpZmVDeWNsZSAtIFNUQVJURUQgQDExOTQ5N21zIFNzbENvbm5lY3Rpb25GYWN0b3J5QDQw
ZWUwYTIye1NTTC0mZ3Q7SFRUUC8xLjF9PGJyPkB0eWVoMDE8YnI+PGJyPmBgYDxicj48YnI+W3Br
Y3MxMl06IGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL1BLQ1NfMTI8YnI+W2prc106IGh0
dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL0phdmFfS2V5U3RvcmU8YnI+W3B1YmxpYy1rZXkt
c3RhbmRhcmRdOiBodHRwczovL3poLndpa2lwZWRpYS5vcmcvemgtaGFudC8lRTUlODUlQUMlRTkl
OTIlQTUlRTUlQUYlODYlRTclQTAlODElRTUlQUQlQTYlRTYlQTAlODclRTUlODclODY8YnI+W3Nw
cmluZy1ib290LXBrY3MxXTogaHR0cHM6Ly9kZW50cmFzc2kuZGUvMjAxOC8wOS8yNS9zZWN1cmlu
Zy1hLXNwcmluZy1ib290LWFwcGxpY2F0aW9uLXdpdGgtcGtjcy0xLXBlbS1maWxlcy88YnI+W3Nw
cmluZy1ib290LXNzbF06IGh0dHBzOi8vZG9jcy5zcHJpbmcuaW8vc3ByaW5nLWJvb3QvZG9jcy8y
LjcuNC9yZWZlcmVuY2UvaHRtbC9ob3d0by5odG1sI2hvd3RvLndlYnNlcnZlci5jb25maWd1cmUt
c3NsPC9wPg==&quot;&gt;​&lt;/div&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimwayne.blogspot.com/feeds/1225417313404937567/comments/default' title='張貼留言'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/6819483672105967231/1225417313404937567' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6819483672105967231/posts/default/1225417313404937567'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6819483672105967231/posts/default/1225417313404937567'/><link rel='alternate' type='text/html' href='http://jimwayne.blogspot.com/2022/11/spring-boot-mtls.html' title='Spring Boot 啟用 mTLS'/><author><name>Wayne Yeh</name><uri>http://www.blogger.com/profile/02148905864753873930</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6819483672105967231.post-731105499527792278</id><published>2022-10-23T22:20:00.006+08:00</published><updated>2023-10-23T22:23:02.415+08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="Markdown Here"/><title type='text'>Markdown Here 樣式筆記</title><content type='html'>&lt;div class=&quot;markdown-here-wrapper&quot; data-md-url=&quot;https://www.blogger.com/blog/post/edit/6819483672105967231/731105499527792278&quot; markdown-here-wrapper-content-modified=&quot;true&quot;&gt;&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;紀錄目前 Markdown Here plugin 使用的設定。&lt;/p&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;Syntax Highlight 模板：&lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;Monokai Sublime&lt;/code&gt;&lt;/p&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;CSS：&lt;br /&gt;這裡主要是模板以外再複寫一次 blockquote 的語法，讓部落格本來就在用的 blockquote 樣式維持原狀。&lt;/p&gt;
&lt;pre style=&quot;font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; font-size: 1em; line-height: 1.2em; margin: 1.2em 0px;&quot;&gt;&lt;code class=&quot;hljs language-css&quot; style=&quot;background-color: #f8f8f8; background: rgb(35, 36, 31); border-radius: 3px; border-radius: 3px; border: 1px solid rgb(204, 204, 204); border: 1px solid rgb(234, 234, 234); color: #f8f8f2; display: block !important; display: block; display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; overflow-x: auto; overflow: auto; padding: 0.5em 0.7em; padding: 0.5em; padding: 0px 0.3em; text-size-adjust: none; white-space: pre-wrap; white-space: pre;&quot;&gt;&lt;span class=&quot;hljs-comment&quot; style=&quot;color: #75715e;&quot;&gt;/*
 * NOTE:
 * - The use of browser-specific styles (-moz-, -webkit-) should be avoided.
 *   If used, they may not render correctly for people reading the email in
 *   a different browser than the one from which the email was sent.
 * - The use of state-dependent styles (like a:hover) don&#39;t work because they
 *   don&#39;t match at the time the styles are made explicit. (In email, styles
 *   must be explicitly applied to all elements -- stylesheets get stripped.)
 */&lt;/span&gt;

&lt;span class=&quot;hljs-comment&quot; style=&quot;color: #75715e;&quot;&gt;/* This is the overall wrapper, it should be treated as the `body` section. */&lt;/span&gt;
&lt;span class=&quot;hljs-class&quot;&gt;.markdown-here-wrapper&lt;/span&gt; &lt;span class=&quot;hljs-rules&quot;&gt;{
&lt;span class=&quot;hljs-rule&quot;&gt;}&lt;/span&gt;&lt;/span&gt;

&lt;span class=&quot;hljs-comment&quot; style=&quot;color: #75715e;&quot;&gt;/* To add site specific rules, you can use the `data-md-url` attribute that we
   add to the wrapper element. Note that rules like this are used depending
   on the URL you&#39;re *sending* from, not the URL where the recipient views it.
*/&lt;/span&gt;
&lt;span class=&quot;hljs-comment&quot; style=&quot;color: #75715e;&quot;&gt;/* .markdown-here-wrapper[data-md-url*=&quot;mail.yahoo.&quot;] ul { color: red; } */&lt;/span&gt;

&lt;span class=&quot;hljs-tag&quot; style=&quot;color: #f8f8f2;&quot;&gt;pre&lt;/span&gt;, &lt;span class=&quot;hljs-tag&quot; style=&quot;color: #f8f8f2;&quot;&gt;code&lt;/span&gt; &lt;span class=&quot;hljs-rules&quot;&gt;{
  &lt;span class=&quot;hljs-rule&quot;&gt;&lt;span class=&quot;hljs-attribute&quot; style=&quot;color: #f92672;&quot;&gt;font-size&lt;/span&gt;:&lt;span class=&quot;hljs-value&quot; style=&quot;color: #f92672;&quot;&gt; &lt;span class=&quot;hljs-number&quot; style=&quot;color: #ae81ff;&quot;&gt;0.85em&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;;
  &lt;span class=&quot;hljs-rule&quot;&gt;&lt;span class=&quot;hljs-attribute&quot; style=&quot;color: #f92672;&quot;&gt;font-family&lt;/span&gt;:&lt;span class=&quot;hljs-value&quot; style=&quot;color: #f92672;&quot;&gt; Consolas, Inconsolata, Courier, monospace&lt;/span&gt;&lt;/span&gt;;
&lt;span class=&quot;hljs-rule&quot;&gt;}&lt;/span&gt;&lt;/span&gt;

&lt;span class=&quot;hljs-tag&quot; style=&quot;color: #f8f8f2;&quot;&gt;code&lt;/span&gt; &lt;span class=&quot;hljs-rules&quot;&gt;{
  &lt;span class=&quot;hljs-rule&quot;&gt;&lt;span class=&quot;hljs-attribute&quot; style=&quot;color: #f92672;&quot;&gt;margin&lt;/span&gt;:&lt;span class=&quot;hljs-value&quot; style=&quot;color: #f92672;&quot;&gt; &lt;span class=&quot;hljs-number&quot; style=&quot;color: #ae81ff;&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;hljs-number&quot; style=&quot;color: #ae81ff;&quot;&gt;0.15em&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;;
  &lt;span class=&quot;hljs-rule&quot;&gt;&lt;span class=&quot;hljs-attribute&quot; style=&quot;color: #f92672;&quot;&gt;padding&lt;/span&gt;:&lt;span class=&quot;hljs-value&quot; style=&quot;color: #f92672;&quot;&gt; &lt;span class=&quot;hljs-number&quot; style=&quot;color: #ae81ff;&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;hljs-number&quot; style=&quot;color: #ae81ff;&quot;&gt;0.3em&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;;
  &lt;span class=&quot;hljs-rule&quot;&gt;&lt;span class=&quot;hljs-attribute&quot; style=&quot;color: #f92672;&quot;&gt;white-space&lt;/span&gt;:&lt;span class=&quot;hljs-value&quot; style=&quot;color: #f92672;&quot;&gt; pre-wrap&lt;/span&gt;&lt;/span&gt;;
  &lt;span class=&quot;hljs-rule&quot;&gt;&lt;span class=&quot;hljs-attribute&quot; style=&quot;color: #f92672;&quot;&gt;border&lt;/span&gt;:&lt;span class=&quot;hljs-value&quot; style=&quot;color: #f92672;&quot;&gt; &lt;span class=&quot;hljs-number&quot; style=&quot;color: #ae81ff;&quot;&gt;1px&lt;/span&gt; solid &lt;span class=&quot;hljs-hexcolor&quot; style=&quot;color: #ae81ff;&quot;&gt;#EAEAEA&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;;
  &lt;span class=&quot;hljs-rule&quot;&gt;&lt;span class=&quot;hljs-attribute&quot; style=&quot;color: #f92672;&quot;&gt;background-color&lt;/span&gt;:&lt;span class=&quot;hljs-value&quot; style=&quot;color: #f92672;&quot;&gt; &lt;span class=&quot;hljs-hexcolor&quot; style=&quot;color: #ae81ff;&quot;&gt;#F8F8F8&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;;
  &lt;span class=&quot;hljs-rule&quot;&gt;&lt;span class=&quot;hljs-attribute&quot; style=&quot;color: #f92672;&quot;&gt;border-radius&lt;/span&gt;:&lt;span class=&quot;hljs-value&quot; style=&quot;color: #f92672;&quot;&gt; &lt;span class=&quot;hljs-number&quot; style=&quot;color: #ae81ff;&quot;&gt;3px&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;;
  &lt;span class=&quot;hljs-rule&quot;&gt;&lt;span class=&quot;hljs-attribute&quot; style=&quot;color: #f92672;&quot;&gt;display&lt;/span&gt;:&lt;span class=&quot;hljs-value&quot; style=&quot;color: #f92672;&quot;&gt; inline&lt;/span&gt;&lt;/span&gt;; &lt;span class=&quot;hljs-comment&quot; style=&quot;color: #75715e;&quot;&gt;/* added to fix Yahoo block display of inline code */&lt;/span&gt;
&lt;span class=&quot;hljs-rule&quot;&gt;}&lt;/span&gt;&lt;/span&gt;

&lt;span class=&quot;hljs-tag&quot; style=&quot;color: #f8f8f2;&quot;&gt;pre&lt;/span&gt; &lt;span class=&quot;hljs-rules&quot;&gt;{
  &lt;span class=&quot;hljs-rule&quot;&gt;&lt;span class=&quot;hljs-attribute&quot; style=&quot;color: #f92672;&quot;&gt;font-size&lt;/span&gt;:&lt;span class=&quot;hljs-value&quot; style=&quot;color: #f92672;&quot;&gt; &lt;span class=&quot;hljs-number&quot; style=&quot;color: #ae81ff;&quot;&gt;1em&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;;
  &lt;span class=&quot;hljs-rule&quot;&gt;&lt;span class=&quot;hljs-attribute&quot; style=&quot;color: #f92672;&quot;&gt;line-height&lt;/span&gt;:&lt;span class=&quot;hljs-value&quot; style=&quot;color: #f92672;&quot;&gt; &lt;span class=&quot;hljs-number&quot; style=&quot;color: #ae81ff;&quot;&gt;1.2em&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;;
&lt;span class=&quot;hljs-rule&quot;&gt;}&lt;/span&gt;&lt;/span&gt;

&lt;span class=&quot;hljs-tag&quot; style=&quot;color: #f8f8f2;&quot;&gt;pre&lt;/span&gt; &lt;span class=&quot;hljs-tag&quot; style=&quot;color: #f8f8f2;&quot;&gt;code&lt;/span&gt; &lt;span class=&quot;hljs-rules&quot;&gt;{
  &lt;span class=&quot;hljs-rule&quot;&gt;&lt;span class=&quot;hljs-attribute&quot; style=&quot;color: #f92672;&quot;&gt;white-space&lt;/span&gt;:&lt;span class=&quot;hljs-value&quot; style=&quot;color: #f92672;&quot;&gt; pre&lt;/span&gt;&lt;/span&gt;;
  &lt;span class=&quot;hljs-rule&quot;&gt;&lt;span class=&quot;hljs-attribute&quot; style=&quot;color: #f92672;&quot;&gt;overflow&lt;/span&gt;:&lt;span class=&quot;hljs-value&quot; style=&quot;color: #f92672;&quot;&gt; auto&lt;/span&gt;&lt;/span&gt;; &lt;span class=&quot;hljs-comment&quot; style=&quot;color: #75715e;&quot;&gt;/* fixes issue #70: Firefox/Thunderbird: Code blocks with horizontal scroll would have bad background colour */&lt;/span&gt;
  &lt;span class=&quot;hljs-rule&quot;&gt;&lt;span class=&quot;hljs-attribute&quot; style=&quot;color: #f92672;&quot;&gt;border-radius&lt;/span&gt;:&lt;span class=&quot;hljs-value&quot; style=&quot;color: #f92672;&quot;&gt; &lt;span class=&quot;hljs-number&quot; style=&quot;color: #ae81ff;&quot;&gt;3px&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;;
  &lt;span class=&quot;hljs-rule&quot;&gt;&lt;span class=&quot;hljs-attribute&quot; style=&quot;color: #f92672;&quot;&gt;border&lt;/span&gt;:&lt;span class=&quot;hljs-value&quot; style=&quot;color: #f92672;&quot;&gt; &lt;span class=&quot;hljs-number&quot; style=&quot;color: #ae81ff;&quot;&gt;1px&lt;/span&gt; solid &lt;span class=&quot;hljs-hexcolor&quot; style=&quot;color: #ae81ff;&quot;&gt;#CCC&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;;
  &lt;span class=&quot;hljs-rule&quot;&gt;&lt;span class=&quot;hljs-attribute&quot; style=&quot;color: #f92672;&quot;&gt;padding&lt;/span&gt;:&lt;span class=&quot;hljs-value&quot; style=&quot;color: #f92672;&quot;&gt; &lt;span class=&quot;hljs-number&quot; style=&quot;color: #ae81ff;&quot;&gt;0.5em&lt;/span&gt; &lt;span class=&quot;hljs-number&quot; style=&quot;color: #ae81ff;&quot;&gt;0.7em&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;;
  &lt;span class=&quot;hljs-rule&quot;&gt;&lt;span class=&quot;hljs-attribute&quot; style=&quot;color: #f92672;&quot;&gt;display&lt;/span&gt;:&lt;span class=&quot;hljs-value&quot; style=&quot;color: #f92672;&quot;&gt; block &lt;span class=&quot;hljs-important&quot;&gt;!important&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;; &lt;span class=&quot;hljs-comment&quot; style=&quot;color: #75715e;&quot;&gt;/* added to counteract the Yahoo-specific `code` rule; without this, code blocks in Blogger are broken */&lt;/span&gt;
&lt;span class=&quot;hljs-rule&quot;&gt;}&lt;/span&gt;&lt;/span&gt;

&lt;span class=&quot;hljs-comment&quot; style=&quot;color: #75715e;&quot;&gt;/* In edit mode, Wordpress uses a `* { font: ...;} rule+style that makes highlighted
code look non-monospace. This rule will override it. */&lt;/span&gt;
&lt;span class=&quot;hljs-class&quot;&gt;.markdown-here-wrapper&lt;/span&gt;&lt;span class=&quot;hljs-attr_selector&quot; style=&quot;color: #e6db74;&quot;&gt;[data-md-url*=&quot;wordpress.&quot;]&lt;/span&gt; &lt;span class=&quot;hljs-tag&quot; style=&quot;color: #f8f8f2;&quot;&gt;code&lt;/span&gt; &lt;span class=&quot;hljs-tag&quot; style=&quot;color: #f8f8f2;&quot;&gt;span&lt;/span&gt; &lt;span class=&quot;hljs-rules&quot;&gt;{
  &lt;span class=&quot;hljs-rule&quot;&gt;&lt;span class=&quot;hljs-attribute&quot; style=&quot;color: #f92672;&quot;&gt;font&lt;/span&gt;:&lt;span class=&quot;hljs-value&quot; style=&quot;color: #f92672;&quot;&gt; inherit&lt;/span&gt;&lt;/span&gt;;
&lt;span class=&quot;hljs-rule&quot;&gt;}&lt;/span&gt;&lt;/span&gt;

&lt;span class=&quot;hljs-comment&quot; style=&quot;color: #75715e;&quot;&gt;/* Wordpress adds a grey background to `pre` elements that doesn&#39;t go well with
our syntax highlighting. */&lt;/span&gt;
&lt;span class=&quot;hljs-class&quot;&gt;.markdown-here-wrapper&lt;/span&gt;&lt;span class=&quot;hljs-attr_selector&quot; style=&quot;color: #e6db74;&quot;&gt;[data-md-url*=&quot;wordpress.&quot;]&lt;/span&gt; &lt;span class=&quot;hljs-tag&quot; style=&quot;color: #f8f8f2;&quot;&gt;pre&lt;/span&gt; &lt;span class=&quot;hljs-rules&quot;&gt;{
  &lt;span class=&quot;hljs-rule&quot;&gt;&lt;span class=&quot;hljs-attribute&quot; style=&quot;color: #f92672;&quot;&gt;background-color&lt;/span&gt;:&lt;span class=&quot;hljs-value&quot; style=&quot;color: #f92672;&quot;&gt; transparent&lt;/span&gt;&lt;/span&gt;;
&lt;span class=&quot;hljs-rule&quot;&gt;}&lt;/span&gt;&lt;/span&gt;

&lt;span class=&quot;hljs-comment&quot; style=&quot;color: #75715e;&quot;&gt;/* This spacing has been tweaked to closely match Gmail+Chrome &quot;paragraph&quot; (two line breaks) spacing.
Note that we only use a top margin and not a bottom margin -- this prevents the
&quot;blank line&quot; look at the top of the email (issue #243).
*/&lt;/span&gt;
&lt;span class=&quot;hljs-tag&quot; style=&quot;color: #f8f8f2;&quot;&gt;p&lt;/span&gt; &lt;span class=&quot;hljs-rules&quot;&gt;{
  &lt;span class=&quot;hljs-comment&quot; style=&quot;color: #75715e;&quot;&gt;/* !important is needed here because Hotmail/Outlook.com uses !important to
     kill the margin in &amp;lt;p&amp;gt;. We need this to win. */&lt;/span&gt;
  &lt;span class=&quot;hljs-rule&quot;&gt;&lt;span class=&quot;hljs-attribute&quot; style=&quot;color: #f92672;&quot;&gt;margin&lt;/span&gt;:&lt;span class=&quot;hljs-value&quot; style=&quot;color: #f92672;&quot;&gt; &lt;span class=&quot;hljs-number&quot; style=&quot;color: #ae81ff;&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;hljs-number&quot; style=&quot;color: #ae81ff;&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;hljs-number&quot; style=&quot;color: #ae81ff;&quot;&gt;1.2em&lt;/span&gt; &lt;span class=&quot;hljs-number&quot; style=&quot;color: #ae81ff;&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;hljs-important&quot;&gt;!important&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;;
&lt;span class=&quot;hljs-rule&quot;&gt;}&lt;/span&gt;&lt;/span&gt;

&lt;span class=&quot;hljs-tag&quot; style=&quot;color: #f8f8f2;&quot;&gt;table&lt;/span&gt;, &lt;span class=&quot;hljs-tag&quot; style=&quot;color: #f8f8f2;&quot;&gt;pre&lt;/span&gt;, &lt;span class=&quot;hljs-tag&quot; style=&quot;color: #f8f8f2;&quot;&gt;dl&lt;/span&gt;, &lt;span class=&quot;hljs-tag&quot; style=&quot;color: #f8f8f2;&quot;&gt;blockquote&lt;/span&gt;, &lt;span class=&quot;hljs-tag&quot; style=&quot;color: #f8f8f2;&quot;&gt;q&lt;/span&gt;, &lt;span class=&quot;hljs-tag&quot; style=&quot;color: #f8f8f2;&quot;&gt;ul&lt;/span&gt;, &lt;span class=&quot;hljs-tag&quot; style=&quot;color: #f8f8f2;&quot;&gt;ol&lt;/span&gt; &lt;span class=&quot;hljs-rules&quot;&gt;{
  &lt;span class=&quot;hljs-rule&quot;&gt;&lt;span class=&quot;hljs-attribute&quot; style=&quot;color: #f92672;&quot;&gt;margin&lt;/span&gt;:&lt;span class=&quot;hljs-value&quot; style=&quot;color: #f92672;&quot;&gt; &lt;span class=&quot;hljs-number&quot; style=&quot;color: #ae81ff;&quot;&gt;1.2em&lt;/span&gt; &lt;span class=&quot;hljs-number&quot; style=&quot;color: #ae81ff;&quot;&gt;0&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;;
&lt;span class=&quot;hljs-rule&quot;&gt;}&lt;/span&gt;&lt;/span&gt;

&lt;span class=&quot;hljs-tag&quot; style=&quot;color: #f8f8f2;&quot;&gt;ul&lt;/span&gt;, &lt;span class=&quot;hljs-tag&quot; style=&quot;color: #f8f8f2;&quot;&gt;ol&lt;/span&gt; &lt;span class=&quot;hljs-rules&quot;&gt;{
  &lt;span class=&quot;hljs-rule&quot;&gt;&lt;span class=&quot;hljs-attribute&quot; style=&quot;color: #f92672;&quot;&gt;padding-left&lt;/span&gt;:&lt;span class=&quot;hljs-value&quot; style=&quot;color: #f92672;&quot;&gt; &lt;span class=&quot;hljs-number&quot; style=&quot;color: #ae81ff;&quot;&gt;2em&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;;
&lt;span class=&quot;hljs-rule&quot;&gt;}&lt;/span&gt;&lt;/span&gt;

&lt;span class=&quot;hljs-tag&quot; style=&quot;color: #f8f8f2;&quot;&gt;li&lt;/span&gt; &lt;span class=&quot;hljs-rules&quot;&gt;{
  &lt;span class=&quot;hljs-rule&quot;&gt;&lt;span class=&quot;hljs-attribute&quot; style=&quot;color: #f92672;&quot;&gt;margin&lt;/span&gt;:&lt;span class=&quot;hljs-value&quot; style=&quot;color: #f92672;&quot;&gt; &lt;span class=&quot;hljs-number&quot; style=&quot;color: #ae81ff;&quot;&gt;0.5em&lt;/span&gt; &lt;span class=&quot;hljs-number&quot; style=&quot;color: #ae81ff;&quot;&gt;0&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;;
&lt;span class=&quot;hljs-rule&quot;&gt;}&lt;/span&gt;&lt;/span&gt;

&lt;span class=&quot;hljs-comment&quot; style=&quot;color: #75715e;&quot;&gt;/* Space paragraphs in a list the same as the list itself. */&lt;/span&gt;
&lt;span class=&quot;hljs-tag&quot; style=&quot;color: #f8f8f2;&quot;&gt;li&lt;/span&gt; &lt;span class=&quot;hljs-tag&quot; style=&quot;color: #f8f8f2;&quot;&gt;p&lt;/span&gt; &lt;span class=&quot;hljs-rules&quot;&gt;{
  &lt;span class=&quot;hljs-comment&quot; style=&quot;color: #75715e;&quot;&gt;/* Needs !important to override rule above. */&lt;/span&gt;
  &lt;span class=&quot;hljs-rule&quot;&gt;&lt;span class=&quot;hljs-attribute&quot; style=&quot;color: #f92672;&quot;&gt;margin&lt;/span&gt;:&lt;span class=&quot;hljs-value&quot; style=&quot;color: #f92672;&quot;&gt; &lt;span class=&quot;hljs-number&quot; style=&quot;color: #ae81ff;&quot;&gt;0.5em&lt;/span&gt; &lt;span class=&quot;hljs-number&quot; style=&quot;color: #ae81ff;&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;hljs-important&quot;&gt;!important&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;;
&lt;span class=&quot;hljs-rule&quot;&gt;}&lt;/span&gt;&lt;/span&gt;

&lt;span class=&quot;hljs-comment&quot; style=&quot;color: #75715e;&quot;&gt;/* Smaller spacing for sub-lists */&lt;/span&gt;
&lt;span class=&quot;hljs-tag&quot; style=&quot;color: #f8f8f2;&quot;&gt;ul&lt;/span&gt; &lt;span class=&quot;hljs-tag&quot; style=&quot;color: #f8f8f2;&quot;&gt;ul&lt;/span&gt;, &lt;span class=&quot;hljs-tag&quot; style=&quot;color: #f8f8f2;&quot;&gt;ul&lt;/span&gt; &lt;span class=&quot;hljs-tag&quot; style=&quot;color: #f8f8f2;&quot;&gt;ol&lt;/span&gt;, &lt;span class=&quot;hljs-tag&quot; style=&quot;color: #f8f8f2;&quot;&gt;ol&lt;/span&gt; &lt;span class=&quot;hljs-tag&quot; style=&quot;color: #f8f8f2;&quot;&gt;ul&lt;/span&gt;, &lt;span class=&quot;hljs-tag&quot; style=&quot;color: #f8f8f2;&quot;&gt;ol&lt;/span&gt; &lt;span class=&quot;hljs-tag&quot; style=&quot;color: #f8f8f2;&quot;&gt;ol&lt;/span&gt; &lt;span class=&quot;hljs-rules&quot;&gt;{
  &lt;span class=&quot;hljs-rule&quot;&gt;&lt;span class=&quot;hljs-attribute&quot; style=&quot;color: #f92672;&quot;&gt;margin&lt;/span&gt;:&lt;span class=&quot;hljs-value&quot; style=&quot;color: #f92672;&quot;&gt; &lt;span class=&quot;hljs-number&quot; style=&quot;color: #ae81ff;&quot;&gt;0&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;;
  &lt;span class=&quot;hljs-rule&quot;&gt;&lt;span class=&quot;hljs-attribute&quot; style=&quot;color: #f92672;&quot;&gt;padding-left&lt;/span&gt;:&lt;span class=&quot;hljs-value&quot; style=&quot;color: #f92672;&quot;&gt; &lt;span class=&quot;hljs-number&quot; style=&quot;color: #ae81ff;&quot;&gt;1em&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;;
&lt;span class=&quot;hljs-rule&quot;&gt;}&lt;/span&gt;&lt;/span&gt;

&lt;span class=&quot;hljs-comment&quot; style=&quot;color: #75715e;&quot;&gt;/* Use Roman numerals for sub-ordered-lists. (Like Github.) */&lt;/span&gt;
&lt;span class=&quot;hljs-tag&quot; style=&quot;color: #f8f8f2;&quot;&gt;ol&lt;/span&gt; &lt;span class=&quot;hljs-tag&quot; style=&quot;color: #f8f8f2;&quot;&gt;ol&lt;/span&gt;, &lt;span class=&quot;hljs-tag&quot; style=&quot;color: #f8f8f2;&quot;&gt;ul&lt;/span&gt; &lt;span class=&quot;hljs-tag&quot; style=&quot;color: #f8f8f2;&quot;&gt;ol&lt;/span&gt; &lt;span class=&quot;hljs-rules&quot;&gt;{
  &lt;span class=&quot;hljs-rule&quot;&gt;&lt;span class=&quot;hljs-attribute&quot; style=&quot;color: #f92672;&quot;&gt;list-style-type&lt;/span&gt;:&lt;span class=&quot;hljs-value&quot; style=&quot;color: #f92672;&quot;&gt; lower-roman&lt;/span&gt;&lt;/span&gt;;
&lt;span class=&quot;hljs-rule&quot;&gt;}&lt;/span&gt;&lt;/span&gt;

&lt;span class=&quot;hljs-comment&quot; style=&quot;color: #75715e;&quot;&gt;/* Use letters for sub-sub-ordered lists. (Like Github.) */&lt;/span&gt;
&lt;span class=&quot;hljs-tag&quot; style=&quot;color: #f8f8f2;&quot;&gt;ul&lt;/span&gt; &lt;span class=&quot;hljs-tag&quot; style=&quot;color: #f8f8f2;&quot;&gt;ul&lt;/span&gt; &lt;span class=&quot;hljs-tag&quot; style=&quot;color: #f8f8f2;&quot;&gt;ol&lt;/span&gt;, &lt;span class=&quot;hljs-tag&quot; style=&quot;color: #f8f8f2;&quot;&gt;ul&lt;/span&gt; &lt;span class=&quot;hljs-tag&quot; style=&quot;color: #f8f8f2;&quot;&gt;ol&lt;/span&gt; &lt;span class=&quot;hljs-tag&quot; style=&quot;color: #f8f8f2;&quot;&gt;ol&lt;/span&gt;, &lt;span class=&quot;hljs-tag&quot; style=&quot;color: #f8f8f2;&quot;&gt;ol&lt;/span&gt; &lt;span class=&quot;hljs-tag&quot; style=&quot;color: #f8f8f2;&quot;&gt;ul&lt;/span&gt; &lt;span class=&quot;hljs-tag&quot; style=&quot;color: #f8f8f2;&quot;&gt;ol&lt;/span&gt;, &lt;span class=&quot;hljs-tag&quot; style=&quot;color: #f8f8f2;&quot;&gt;ol&lt;/span&gt; &lt;span class=&quot;hljs-tag&quot; style=&quot;color: #f8f8f2;&quot;&gt;ol&lt;/span&gt; &lt;span class=&quot;hljs-tag&quot; style=&quot;color: #f8f8f2;&quot;&gt;ol&lt;/span&gt; &lt;span class=&quot;hljs-rules&quot;&gt;{
  &lt;span class=&quot;hljs-rule&quot;&gt;&lt;span class=&quot;hljs-attribute&quot; style=&quot;color: #f92672;&quot;&gt;list-style-type&lt;/span&gt;:&lt;span class=&quot;hljs-value&quot; style=&quot;color: #f92672;&quot;&gt; lower-alpha&lt;/span&gt;&lt;/span&gt;;
&lt;span class=&quot;hljs-rule&quot;&gt;}&lt;/span&gt;&lt;/span&gt;

&lt;span class=&quot;hljs-tag&quot; style=&quot;color: #f8f8f2;&quot;&gt;dl&lt;/span&gt; &lt;span class=&quot;hljs-rules&quot;&gt;{
  &lt;span class=&quot;hljs-rule&quot;&gt;&lt;span class=&quot;hljs-attribute&quot; style=&quot;color: #f92672;&quot;&gt;padding&lt;/span&gt;:&lt;span class=&quot;hljs-value&quot; style=&quot;color: #f92672;&quot;&gt; &lt;span class=&quot;hljs-number&quot; style=&quot;color: #ae81ff;&quot;&gt;0&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;;
&lt;span class=&quot;hljs-rule&quot;&gt;}&lt;/span&gt;&lt;/span&gt;

&lt;span class=&quot;hljs-tag&quot; style=&quot;color: #f8f8f2;&quot;&gt;dl&lt;/span&gt; &lt;span class=&quot;hljs-tag&quot; style=&quot;color: #f8f8f2;&quot;&gt;dt&lt;/span&gt; &lt;span class=&quot;hljs-rules&quot;&gt;{
  &lt;span class=&quot;hljs-rule&quot;&gt;&lt;span class=&quot;hljs-attribute&quot; style=&quot;color: #f92672;&quot;&gt;font-size&lt;/span&gt;:&lt;span class=&quot;hljs-value&quot; style=&quot;color: #f92672;&quot;&gt; &lt;span class=&quot;hljs-number&quot; style=&quot;color: #ae81ff;&quot;&gt;1em&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;;
  &lt;span class=&quot;hljs-rule&quot;&gt;&lt;span class=&quot;hljs-attribute&quot; style=&quot;color: #f92672;&quot;&gt;font-weight&lt;/span&gt;:&lt;span class=&quot;hljs-value&quot; style=&quot;color: #f92672;&quot;&gt; bold&lt;/span&gt;&lt;/span&gt;;
  &lt;span class=&quot;hljs-rule&quot;&gt;&lt;span class=&quot;hljs-attribute&quot; style=&quot;color: #f92672;&quot;&gt;font-style&lt;/span&gt;:&lt;span class=&quot;hljs-value&quot; style=&quot;color: #f92672;&quot;&gt; italic&lt;/span&gt;&lt;/span&gt;;
&lt;span class=&quot;hljs-rule&quot;&gt;}&lt;/span&gt;&lt;/span&gt;

&lt;span class=&quot;hljs-tag&quot; style=&quot;color: #f8f8f2;&quot;&gt;dl&lt;/span&gt; &lt;span class=&quot;hljs-tag&quot; style=&quot;color: #f8f8f2;&quot;&gt;dd&lt;/span&gt; &lt;span class=&quot;hljs-rules&quot;&gt;{
  &lt;span class=&quot;hljs-rule&quot;&gt;&lt;span class=&quot;hljs-attribute&quot; style=&quot;color: #f92672;&quot;&gt;margin&lt;/span&gt;:&lt;span class=&quot;hljs-value&quot; style=&quot;color: #f92672;&quot;&gt; &lt;span class=&quot;hljs-number&quot; style=&quot;color: #ae81ff;&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;hljs-number&quot; style=&quot;color: #ae81ff;&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;hljs-number&quot; style=&quot;color: #ae81ff;&quot;&gt;1em&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;;
  &lt;span class=&quot;hljs-rule&quot;&gt;&lt;span class=&quot;hljs-attribute&quot; style=&quot;color: #f92672;&quot;&gt;padding&lt;/span&gt;:&lt;span class=&quot;hljs-value&quot; style=&quot;color: #f92672;&quot;&gt; &lt;span class=&quot;hljs-number&quot; style=&quot;color: #ae81ff;&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;hljs-number&quot; style=&quot;color: #ae81ff;&quot;&gt;1em&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;;
&lt;span class=&quot;hljs-rule&quot;&gt;}&lt;/span&gt;&lt;/span&gt;

&lt;span class=&quot;hljs-tag&quot; style=&quot;color: #f8f8f2;&quot;&gt;blockquote&lt;/span&gt;, &lt;span class=&quot;hljs-tag&quot; style=&quot;color: #f8f8f2;&quot;&gt;q&lt;/span&gt; &lt;span class=&quot;hljs-rules&quot;&gt;{
  &lt;span class=&quot;hljs-rule&quot;&gt;&lt;span class=&quot;hljs-attribute&quot; style=&quot;color: #f92672;&quot;&gt;border-left&lt;/span&gt;:&lt;span class=&quot;hljs-value&quot; style=&quot;color: #f92672;&quot;&gt; &lt;span class=&quot;hljs-number&quot; style=&quot;color: #ae81ff;&quot;&gt;4px&lt;/span&gt; solid &lt;span class=&quot;hljs-hexcolor&quot; style=&quot;color: #ae81ff;&quot;&gt;#DDD&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;;
  &lt;span class=&quot;hljs-rule&quot;&gt;&lt;span class=&quot;hljs-attribute&quot; style=&quot;color: #f92672;&quot;&gt;padding&lt;/span&gt;:&lt;span class=&quot;hljs-value&quot; style=&quot;color: #f92672;&quot;&gt; &lt;span class=&quot;hljs-number&quot; style=&quot;color: #ae81ff;&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;hljs-number&quot; style=&quot;color: #ae81ff;&quot;&gt;1em&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;;
  &lt;span class=&quot;hljs-rule&quot;&gt;&lt;span class=&quot;hljs-attribute&quot; style=&quot;color: #f92672;&quot;&gt;color&lt;/span&gt;:&lt;span class=&quot;hljs-value&quot; style=&quot;color: #f92672;&quot;&gt; &lt;span class=&quot;hljs-hexcolor&quot; style=&quot;color: #ae81ff;&quot;&gt;#777&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;;
  &lt;span class=&quot;hljs-rule&quot;&gt;&lt;span class=&quot;hljs-attribute&quot; style=&quot;color: #f92672;&quot;&gt;quotes&lt;/span&gt;:&lt;span class=&quot;hljs-value&quot; style=&quot;color: #f92672;&quot;&gt; none&lt;/span&gt;&lt;/span&gt;;
&lt;span class=&quot;hljs-rule&quot;&gt;}&lt;/span&gt;&lt;/span&gt;

&lt;span class=&quot;hljs-tag&quot; style=&quot;color: #f8f8f2;&quot;&gt;blockquote&lt;/span&gt;&lt;span class=&quot;hljs-pseudo&quot; style=&quot;color: #e6db74;&quot;&gt;::before&lt;/span&gt;, &lt;span class=&quot;hljs-tag&quot; style=&quot;color: #f8f8f2;&quot;&gt;blockquote&lt;/span&gt;&lt;span class=&quot;hljs-pseudo&quot; style=&quot;color: #e6db74;&quot;&gt;::after&lt;/span&gt;, &lt;span class=&quot;hljs-tag&quot; style=&quot;color: #f8f8f2;&quot;&gt;q&lt;/span&gt;&lt;span class=&quot;hljs-pseudo&quot; style=&quot;color: #e6db74;&quot;&gt;::before&lt;/span&gt;, &lt;span class=&quot;hljs-tag&quot; style=&quot;color: #f8f8f2;&quot;&gt;q&lt;/span&gt;&lt;span class=&quot;hljs-pseudo&quot; style=&quot;color: #e6db74;&quot;&gt;::after&lt;/span&gt; &lt;span class=&quot;hljs-rules&quot;&gt;{
  &lt;span class=&quot;hljs-rule&quot;&gt;&lt;span class=&quot;hljs-attribute&quot; style=&quot;color: #f92672;&quot;&gt;content&lt;/span&gt;:&lt;span class=&quot;hljs-value&quot; style=&quot;color: #f92672;&quot;&gt; none&lt;/span&gt;&lt;/span&gt;;
&lt;span class=&quot;hljs-rule&quot;&gt;}&lt;/span&gt;&lt;/span&gt;

&lt;span class=&quot;hljs-tag&quot; style=&quot;color: #f8f8f2;&quot;&gt;blockquote&lt;/span&gt;&lt;span class=&quot;hljs-rules&quot;&gt;{&lt;span class=&quot;hljs-rule&quot;&gt;&lt;span class=&quot;hljs-attribute&quot; style=&quot;color: #f92672;&quot;&gt;border-left&lt;/span&gt;:&lt;span class=&quot;hljs-value&quot; style=&quot;color: #f92672;&quot;&gt; &lt;span class=&quot;hljs-number&quot; style=&quot;color: #ae81ff;&quot;&gt;15px&lt;/span&gt; solid &lt;span class=&quot;hljs-hexcolor&quot; style=&quot;color: #ae81ff;&quot;&gt;#c76c0c&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;;&lt;span class=&quot;hljs-rule&quot;&gt;&lt;span class=&quot;hljs-attribute&quot; style=&quot;color: #f92672;&quot;&gt;font-family&lt;/span&gt;:&lt;span class=&quot;hljs-value&quot; style=&quot;color: #f92672;&quot;&gt; Georgia, serif&lt;/span&gt;&lt;/span&gt;;&lt;span class=&quot;hljs-rule&quot;&gt;&lt;span class=&quot;hljs-attribute&quot; style=&quot;color: #f92672;&quot;&gt;font-size&lt;/span&gt;:&lt;span class=&quot;hljs-value&quot; style=&quot;color: #f92672;&quot;&gt;&lt;span class=&quot;hljs-number&quot; style=&quot;color: #ae81ff;&quot;&gt;15px&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;;&lt;span class=&quot;hljs-rule&quot;&gt;&lt;span class=&quot;hljs-attribute&quot; style=&quot;color: #f92672;&quot;&gt;text-align&lt;/span&gt;:&lt;span class=&quot;hljs-value&quot; style=&quot;color: #f92672;&quot;&gt; justify&lt;/span&gt;&lt;/span&gt;;&lt;span class=&quot;hljs-rule&quot;&gt;&lt;span class=&quot;hljs-attribute&quot; style=&quot;color: #f92672;&quot;&gt;background&lt;/span&gt;:&lt;span class=&quot;hljs-value&quot; style=&quot;color: #f92672;&quot;&gt; &lt;span class=&quot;hljs-hexcolor&quot; style=&quot;color: #ae81ff;&quot;&gt;#fff&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;;&lt;span class=&quot;hljs-rule&quot;&gt;&lt;span class=&quot;hljs-attribute&quot; style=&quot;color: #f92672;&quot;&gt;line-height&lt;/span&gt;:&lt;span class=&quot;hljs-value&quot; style=&quot;color: #f92672;&quot;&gt; &lt;span class=&quot;hljs-number&quot; style=&quot;color: #ae81ff;&quot;&gt;1.2&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;;
&lt;span class=&quot;hljs-rule&quot;&gt;&lt;span class=&quot;hljs-attribute&quot; style=&quot;color: #f92672;&quot;&gt;display&lt;/span&gt;:&lt;span class=&quot;hljs-value&quot; style=&quot;color: #f92672;&quot;&gt;block&lt;/span&gt;&lt;/span&gt;;&lt;span class=&quot;hljs-rule&quot;&gt;&lt;span class=&quot;hljs-attribute&quot; style=&quot;color: #f92672;&quot;&gt;margin&lt;/span&gt;:&lt;span class=&quot;hljs-value&quot; style=&quot;color: #f92672;&quot;&gt; &lt;span class=&quot;hljs-number&quot; style=&quot;color: #ae81ff;&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;hljs-number&quot; style=&quot;color: #ae81ff;&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;hljs-number&quot; style=&quot;color: #ae81ff;&quot;&gt;20px&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;;&lt;span class=&quot;hljs-rule&quot;&gt;&lt;span class=&quot;hljs-attribute&quot; style=&quot;color: #f92672;&quot;&gt;padding&lt;/span&gt;:&lt;span class=&quot;hljs-value&quot; style=&quot;color: #f92672;&quot;&gt; &lt;span class=&quot;hljs-number&quot; style=&quot;color: #ae81ff;&quot;&gt;15px&lt;/span&gt; &lt;span class=&quot;hljs-number&quot; style=&quot;color: #ae81ff;&quot;&gt;20px&lt;/span&gt; &lt;span class=&quot;hljs-number&quot; style=&quot;color: #ae81ff;&quot;&gt;15px&lt;/span&gt; &lt;span class=&quot;hljs-number&quot; style=&quot;color: #ae81ff;&quot;&gt;45px&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;;&lt;span class=&quot;hljs-rule&quot;&gt;&lt;span class=&quot;hljs-attribute&quot; style=&quot;color: #f92672;&quot;&gt;position&lt;/span&gt;:&lt;span class=&quot;hljs-value&quot; style=&quot;color: #f92672;&quot;&gt; relative&lt;/span&gt;&lt;/span&gt;; &lt;span class=&quot;hljs-rule&quot;&gt;&lt;span class=&quot;hljs-attribute&quot; style=&quot;color: #f92672;&quot;&gt;font-size&lt;/span&gt;:&lt;span class=&quot;hljs-value&quot; style=&quot;color: #f92672;&quot;&gt; &lt;span class=&quot;hljs-number&quot; style=&quot;color: #ae81ff;&quot;&gt;16px&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;;&lt;span class=&quot;hljs-rule&quot;&gt;&lt;span class=&quot;hljs-attribute&quot; style=&quot;color: #f92672;&quot;&gt;color&lt;/span&gt;:&lt;span class=&quot;hljs-value&quot; style=&quot;color: #f92672;&quot;&gt; &lt;span class=&quot;hljs-hexcolor&quot; style=&quot;color: #ae81ff;&quot;&gt;#666&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;;
&lt;span class=&quot;hljs-rule&quot;&gt;&lt;span class=&quot;hljs-attribute&quot; style=&quot;color: #f92672;&quot;&gt;border-right&lt;/span&gt;:&lt;span class=&quot;hljs-value&quot; style=&quot;color: #f92672;&quot;&gt; &lt;span class=&quot;hljs-number&quot; style=&quot;color: #ae81ff;&quot;&gt;2px&lt;/span&gt; solid &lt;span class=&quot;hljs-hexcolor&quot; style=&quot;color: #ae81ff;&quot;&gt;#c76c0c&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;;&lt;span class=&quot;hljs-rule&quot;&gt;&lt;span class=&quot;hljs-attribute&quot; style=&quot;color: #f92672;&quot;&gt;box-shadow&lt;/span&gt;:&lt;span class=&quot;hljs-value&quot; style=&quot;color: #f92672;&quot;&gt; &lt;span class=&quot;hljs-number&quot; style=&quot;color: #ae81ff;&quot;&gt;2px&lt;/span&gt; &lt;span class=&quot;hljs-number&quot; style=&quot;color: #ae81ff;&quot;&gt;2px&lt;/span&gt; &lt;span class=&quot;hljs-number&quot; style=&quot;color: #ae81ff;&quot;&gt;15px&lt;/span&gt; &lt;span class=&quot;hljs-hexcolor&quot; style=&quot;color: #ae81ff;&quot;&gt;#ccc&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;;&lt;span class=&quot;hljs-rule&quot;&gt;&lt;span class=&quot;hljs-attribute&quot; style=&quot;color: #f92672;&quot;&gt;-webkit-box-shadow&lt;/span&gt;:&lt;span class=&quot;hljs-value&quot; style=&quot;color: #f92672;&quot;&gt; &lt;span class=&quot;hljs-number&quot; style=&quot;color: #ae81ff;&quot;&gt;2px&lt;/span&gt; &lt;span class=&quot;hljs-number&quot; style=&quot;color: #ae81ff;&quot;&gt;2px&lt;/span&gt; &lt;span class=&quot;hljs-number&quot; style=&quot;color: #ae81ff;&quot;&gt;15px&lt;/span&gt; &lt;span class=&quot;hljs-hexcolor&quot; style=&quot;color: #ae81ff;&quot;&gt;#ccc&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;;&lt;span class=&quot;hljs-rule&quot;&gt;&lt;span class=&quot;hljs-attribute&quot; style=&quot;color: #f92672;&quot;&gt;-moz-box-shadow&lt;/span&gt;:&lt;span class=&quot;hljs-value&quot; style=&quot;color: #f92672;&quot;&gt; &lt;span class=&quot;hljs-number&quot; style=&quot;color: #ae81ff;&quot;&gt;2px&lt;/span&gt; &lt;span class=&quot;hljs-number&quot; style=&quot;color: #ae81ff;&quot;&gt;2px&lt;/span&gt; &lt;span class=&quot;hljs-number&quot; style=&quot;color: #ae81ff;&quot;&gt;15px&lt;/span&gt; &lt;span class=&quot;hljs-hexcolor&quot; style=&quot;color: #ae81ff;&quot;&gt;#ccc&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;;&lt;span class=&quot;hljs-rule&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;hljs-tag&quot; style=&quot;color: #f8f8f2;&quot;&gt;blockquote&lt;/span&gt;&lt;span class=&quot;hljs-pseudo&quot; style=&quot;color: #e6db74;&quot;&gt;::before&lt;/span&gt;&lt;span class=&quot;hljs-rules&quot;&gt;{&lt;span class=&quot;hljs-rule&quot;&gt;&lt;span class=&quot;hljs-attribute&quot; style=&quot;color: #f92672;&quot;&gt;font-size&lt;/span&gt;:&lt;span class=&quot;hljs-value&quot; style=&quot;color: #f92672;&quot;&gt; &lt;span class=&quot;hljs-number&quot; style=&quot;color: #ae81ff;&quot;&gt;50px&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;;&lt;span class=&quot;hljs-rule&quot;&gt;&lt;span class=&quot;hljs-attribute&quot; style=&quot;color: #f92672;&quot;&gt;position&lt;/span&gt;:&lt;span class=&quot;hljs-value&quot; style=&quot;color: #f92672;&quot;&gt; absolute&lt;/span&gt;&lt;/span&gt;;&lt;span class=&quot;hljs-rule&quot;&gt;&lt;span class=&quot;hljs-attribute&quot; style=&quot;color: #f92672;&quot;&gt;color&lt;/span&gt;:&lt;span class=&quot;hljs-value&quot; style=&quot;color: #f92672;&quot;&gt; &lt;span class=&quot;hljs-hexcolor&quot; style=&quot;color: #ae81ff;&quot;&gt;#999&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;;&lt;span class=&quot;hljs-rule&quot;&gt;&lt;span class=&quot;hljs-attribute&quot; style=&quot;color: #f92672;&quot;&gt;left&lt;/span&gt;:&lt;span class=&quot;hljs-value&quot; style=&quot;color: #f92672;&quot;&gt; &lt;span class=&quot;hljs-number&quot; style=&quot;color: #ae81ff;&quot;&gt;10px&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;;&lt;span class=&quot;hljs-rule&quot;&gt;&lt;span class=&quot;hljs-attribute&quot; style=&quot;color: #f92672;&quot;&gt;top&lt;/span&gt;:&lt;span class=&quot;hljs-value&quot; style=&quot;color: #f92672;&quot;&gt;&lt;span class=&quot;hljs-number&quot; style=&quot;color: #ae81ff;&quot;&gt;5px&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;;&lt;span class=&quot;hljs-rule&quot;&gt;&lt;span class=&quot;hljs-attribute&quot; style=&quot;color: #f92672;&quot;&gt;font-weight&lt;/span&gt;:&lt;span class=&quot;hljs-value&quot; style=&quot;color: #f92672;&quot;&gt; bold&lt;/span&gt;&lt;/span&gt;;&lt;span class=&quot;hljs-rule&quot;&gt;&lt;span class=&quot;hljs-attribute&quot; style=&quot;color: #f92672;&quot;&gt;content&lt;/span&gt;:&lt;span class=&quot;hljs-value&quot; style=&quot;color: #f92672;&quot;&gt; &lt;span class=&quot;hljs-string&quot; style=&quot;color: #e6db74;&quot;&gt;&quot;\201C&quot;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;;&lt;span class=&quot;hljs-rule&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;hljs-tag&quot; style=&quot;color: #f8f8f2;&quot;&gt;blockquote&lt;/span&gt; &lt;span class=&quot;hljs-tag&quot; style=&quot;color: #f8f8f2;&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;hljs-rules&quot;&gt;{&lt;span class=&quot;hljs-rule&quot;&gt;&lt;span class=&quot;hljs-attribute&quot; style=&quot;color: #f92672;&quot;&gt;cursor&lt;/span&gt;:&lt;span class=&quot;hljs-value&quot; style=&quot;color: #f92672;&quot;&gt; pointer&lt;/span&gt;&lt;/span&gt;;&lt;span class=&quot;hljs-rule&quot;&gt;&lt;span class=&quot;hljs-attribute&quot; style=&quot;color: #f92672;&quot;&gt;color&lt;/span&gt;:&lt;span class=&quot;hljs-value&quot; style=&quot;color: #f92672;&quot;&gt; &lt;span class=&quot;hljs-hexcolor&quot; style=&quot;color: #ae81ff;&quot;&gt;#c76c0c&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;;&lt;span class=&quot;hljs-rule&quot;&gt;&lt;span class=&quot;hljs-attribute&quot; style=&quot;color: #f92672;&quot;&gt;text-decoration&lt;/span&gt;:&lt;span class=&quot;hljs-value&quot; style=&quot;color: #f92672;&quot;&gt; none&lt;/span&gt;&lt;/span&gt;;&lt;span class=&quot;hljs-rule&quot;&gt;&lt;span class=&quot;hljs-attribute&quot; style=&quot;color: #f92672;&quot;&gt;background&lt;/span&gt;:&lt;span class=&quot;hljs-value&quot; style=&quot;color: #f92672;&quot;&gt; &lt;span class=&quot;hljs-hexcolor&quot; style=&quot;color: #ae81ff;&quot;&gt;#eee&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;;&lt;span class=&quot;hljs-rule&quot;&gt;&lt;span class=&quot;hljs-attribute&quot; style=&quot;color: #f92672;&quot;&gt;padding&lt;/span&gt;:&lt;span class=&quot;hljs-value&quot; style=&quot;color: #f92672;&quot;&gt; &lt;span class=&quot;hljs-number&quot; style=&quot;color: #ae81ff;&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;hljs-number&quot; style=&quot;color: #ae81ff;&quot;&gt;3px&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;;&lt;span class=&quot;hljs-rule&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;hljs-tag&quot; style=&quot;color: #f8f8f2;&quot;&gt;blockquote&lt;/span&gt; &lt;span class=&quot;hljs-tag&quot; style=&quot;color: #f8f8f2;&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;hljs-pseudo&quot; style=&quot;color: #e6db74;&quot;&gt;:hover&lt;/span&gt;&lt;span class=&quot;hljs-rules&quot;&gt;{&lt;span class=&quot;hljs-rule&quot;&gt;&lt;span class=&quot;hljs-attribute&quot; style=&quot;color: #f92672;&quot;&gt;color&lt;/span&gt;:&lt;span class=&quot;hljs-value&quot; style=&quot;color: #f92672;&quot;&gt; &lt;span class=&quot;hljs-hexcolor&quot; style=&quot;color: #ae81ff;&quot;&gt;#555&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;;&lt;span class=&quot;hljs-rule&quot;&gt;}&lt;/span&gt;&lt;/span&gt;

&lt;span class=&quot;hljs-tag&quot; style=&quot;color: #f8f8f2;&quot;&gt;h1&lt;/span&gt;, &lt;span class=&quot;hljs-tag&quot; style=&quot;color: #f8f8f2;&quot;&gt;h2&lt;/span&gt;, &lt;span class=&quot;hljs-tag&quot; style=&quot;color: #f8f8f2;&quot;&gt;h3&lt;/span&gt;, &lt;span class=&quot;hljs-tag&quot; style=&quot;color: #f8f8f2;&quot;&gt;h4&lt;/span&gt;, &lt;span class=&quot;hljs-tag&quot; style=&quot;color: #f8f8f2;&quot;&gt;h5&lt;/span&gt;, &lt;span class=&quot;hljs-tag&quot; style=&quot;color: #f8f8f2;&quot;&gt;h6&lt;/span&gt; &lt;span class=&quot;hljs-rules&quot;&gt;{
  &lt;span class=&quot;hljs-rule&quot;&gt;&lt;span class=&quot;hljs-attribute&quot; style=&quot;color: #f92672;&quot;&gt;margin&lt;/span&gt;:&lt;span class=&quot;hljs-value&quot; style=&quot;color: #f92672;&quot;&gt; &lt;span class=&quot;hljs-number&quot; style=&quot;color: #ae81ff;&quot;&gt;1.3em&lt;/span&gt; &lt;span class=&quot;hljs-number&quot; style=&quot;color: #ae81ff;&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;hljs-number&quot; style=&quot;color: #ae81ff;&quot;&gt;1em&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;;
  &lt;span class=&quot;hljs-rule&quot;&gt;&lt;span class=&quot;hljs-attribute&quot; style=&quot;color: #f92672;&quot;&gt;padding&lt;/span&gt;:&lt;span class=&quot;hljs-value&quot; style=&quot;color: #f92672;&quot;&gt; &lt;span class=&quot;hljs-number&quot; style=&quot;color: #ae81ff;&quot;&gt;0&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;;
  &lt;span class=&quot;hljs-rule&quot;&gt;&lt;span class=&quot;hljs-attribute&quot; style=&quot;color: #f92672;&quot;&gt;font-weight&lt;/span&gt;:&lt;span class=&quot;hljs-value&quot; style=&quot;color: #f92672;&quot;&gt; bold&lt;/span&gt;&lt;/span&gt;;
&lt;span class=&quot;hljs-rule&quot;&gt;}&lt;/span&gt;&lt;/span&gt;

&lt;span class=&quot;hljs-tag&quot; style=&quot;color: #f8f8f2;&quot;&gt;h1&lt;/span&gt; &lt;span class=&quot;hljs-rules&quot;&gt;{
  &lt;span class=&quot;hljs-rule&quot;&gt;&lt;span class=&quot;hljs-attribute&quot; style=&quot;color: #f92672;&quot;&gt;font-size&lt;/span&gt;:&lt;span class=&quot;hljs-value&quot; style=&quot;color: #f92672;&quot;&gt; &lt;span class=&quot;hljs-number&quot; style=&quot;color: #ae81ff;&quot;&gt;1.6em&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;;
  &lt;span class=&quot;hljs-rule&quot;&gt;&lt;span class=&quot;hljs-attribute&quot; style=&quot;color: #f92672;&quot;&gt;border-bottom&lt;/span&gt;:&lt;span class=&quot;hljs-value&quot; style=&quot;color: #f92672;&quot;&gt; &lt;span class=&quot;hljs-number&quot; style=&quot;color: #ae81ff;&quot;&gt;1px&lt;/span&gt; solid &lt;span class=&quot;hljs-hexcolor&quot; style=&quot;color: #ae81ff;&quot;&gt;#ddd&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;;
&lt;span class=&quot;hljs-rule&quot;&gt;}&lt;/span&gt;&lt;/span&gt;

&lt;span class=&quot;hljs-tag&quot; style=&quot;color: #f8f8f2;&quot;&gt;h2&lt;/span&gt; &lt;span class=&quot;hljs-rules&quot;&gt;{
  &lt;span class=&quot;hljs-rule&quot;&gt;&lt;span class=&quot;hljs-attribute&quot; style=&quot;color: #f92672;&quot;&gt;font-size&lt;/span&gt;:&lt;span class=&quot;hljs-value&quot; style=&quot;color: #f92672;&quot;&gt; &lt;span class=&quot;hljs-number&quot; style=&quot;color: #ae81ff;&quot;&gt;1.4em&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;;
  &lt;span class=&quot;hljs-rule&quot;&gt;&lt;span class=&quot;hljs-attribute&quot; style=&quot;color: #f92672;&quot;&gt;border-bottom&lt;/span&gt;:&lt;span class=&quot;hljs-value&quot; style=&quot;color: #f92672;&quot;&gt; &lt;span class=&quot;hljs-number&quot; style=&quot;color: #ae81ff;&quot;&gt;1px&lt;/span&gt; solid &lt;span class=&quot;hljs-hexcolor&quot; style=&quot;color: #ae81ff;&quot;&gt;#eee&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;;
&lt;span class=&quot;hljs-rule&quot;&gt;}&lt;/span&gt;&lt;/span&gt;

&lt;span class=&quot;hljs-tag&quot; style=&quot;color: #f8f8f2;&quot;&gt;h3&lt;/span&gt; &lt;span class=&quot;hljs-rules&quot;&gt;{
  &lt;span class=&quot;hljs-rule&quot;&gt;&lt;span class=&quot;hljs-attribute&quot; style=&quot;color: #f92672;&quot;&gt;font-size&lt;/span&gt;:&lt;span class=&quot;hljs-value&quot; style=&quot;color: #f92672;&quot;&gt; &lt;span class=&quot;hljs-number&quot; style=&quot;color: #ae81ff;&quot;&gt;1.3em&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;;
&lt;span class=&quot;hljs-rule&quot;&gt;}&lt;/span&gt;&lt;/span&gt;

&lt;span class=&quot;hljs-tag&quot; style=&quot;color: #f8f8f2;&quot;&gt;h4&lt;/span&gt; &lt;span class=&quot;hljs-rules&quot;&gt;{
  &lt;span class=&quot;hljs-rule&quot;&gt;&lt;span class=&quot;hljs-attribute&quot; style=&quot;color: #f92672;&quot;&gt;font-size&lt;/span&gt;:&lt;span class=&quot;hljs-value&quot; style=&quot;color: #f92672;&quot;&gt; &lt;span class=&quot;hljs-number&quot; style=&quot;color: #ae81ff;&quot;&gt;1.2em&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;;
&lt;span class=&quot;hljs-rule&quot;&gt;}&lt;/span&gt;&lt;/span&gt;

&lt;span class=&quot;hljs-tag&quot; style=&quot;color: #f8f8f2;&quot;&gt;h5&lt;/span&gt; &lt;span class=&quot;hljs-rules&quot;&gt;{
  &lt;span class=&quot;hljs-rule&quot;&gt;&lt;span class=&quot;hljs-attribute&quot; style=&quot;color: #f92672;&quot;&gt;font-size&lt;/span&gt;:&lt;span class=&quot;hljs-value&quot; style=&quot;color: #f92672;&quot;&gt; &lt;span class=&quot;hljs-number&quot; style=&quot;color: #ae81ff;&quot;&gt;1em&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;;
&lt;span class=&quot;hljs-rule&quot;&gt;}&lt;/span&gt;&lt;/span&gt;

&lt;span class=&quot;hljs-tag&quot; style=&quot;color: #f8f8f2;&quot;&gt;h6&lt;/span&gt; &lt;span class=&quot;hljs-rules&quot;&gt;{
  &lt;span class=&quot;hljs-rule&quot;&gt;&lt;span class=&quot;hljs-attribute&quot; style=&quot;color: #f92672;&quot;&gt;font-size&lt;/span&gt;:&lt;span class=&quot;hljs-value&quot; style=&quot;color: #f92672;&quot;&gt; &lt;span class=&quot;hljs-number&quot; style=&quot;color: #ae81ff;&quot;&gt;1em&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;;
  &lt;span class=&quot;hljs-rule&quot;&gt;&lt;span class=&quot;hljs-attribute&quot; style=&quot;color: #f92672;&quot;&gt;color&lt;/span&gt;:&lt;span class=&quot;hljs-value&quot; style=&quot;color: #f92672;&quot;&gt; &lt;span class=&quot;hljs-hexcolor&quot; style=&quot;color: #ae81ff;&quot;&gt;#777&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;;
&lt;span class=&quot;hljs-rule&quot;&gt;}&lt;/span&gt;&lt;/span&gt;

&lt;span class=&quot;hljs-tag&quot; style=&quot;color: #f8f8f2;&quot;&gt;table&lt;/span&gt; &lt;span class=&quot;hljs-rules&quot;&gt;{
  &lt;span class=&quot;hljs-rule&quot;&gt;&lt;span class=&quot;hljs-attribute&quot; style=&quot;color: #f92672;&quot;&gt;padding&lt;/span&gt;:&lt;span class=&quot;hljs-value&quot; style=&quot;color: #f92672;&quot;&gt; &lt;span class=&quot;hljs-number&quot; style=&quot;color: #ae81ff;&quot;&gt;0&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;;
  &lt;span class=&quot;hljs-rule&quot;&gt;&lt;span class=&quot;hljs-attribute&quot; style=&quot;color: #f92672;&quot;&gt;border-collapse&lt;/span&gt;:&lt;span class=&quot;hljs-value&quot; style=&quot;color: #f92672;&quot;&gt; collapse&lt;/span&gt;&lt;/span&gt;;
  &lt;span class=&quot;hljs-rule&quot;&gt;&lt;span class=&quot;hljs-attribute&quot; style=&quot;color: #f92672;&quot;&gt;border-spacing&lt;/span&gt;:&lt;span class=&quot;hljs-value&quot; style=&quot;color: #f92672;&quot;&gt; &lt;span class=&quot;hljs-number&quot; style=&quot;color: #ae81ff;&quot;&gt;0&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;;
  &lt;span class=&quot;hljs-rule&quot;&gt;&lt;span class=&quot;hljs-attribute&quot; style=&quot;color: #f92672;&quot;&gt;font-size&lt;/span&gt;:&lt;span class=&quot;hljs-value&quot; style=&quot;color: #f92672;&quot;&gt; &lt;span class=&quot;hljs-number&quot; style=&quot;color: #ae81ff;&quot;&gt;1em&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;;
  &lt;span class=&quot;hljs-rule&quot;&gt;&lt;span class=&quot;hljs-attribute&quot; style=&quot;color: #f92672;&quot;&gt;font&lt;/span&gt;:&lt;span class=&quot;hljs-value&quot; style=&quot;color: #f92672;&quot;&gt; inherit&lt;/span&gt;&lt;/span&gt;;
  &lt;span class=&quot;hljs-rule&quot;&gt;&lt;span class=&quot;hljs-attribute&quot; style=&quot;color: #f92672;&quot;&gt;border&lt;/span&gt;:&lt;span class=&quot;hljs-value&quot; style=&quot;color: #f92672;&quot;&gt; &lt;span class=&quot;hljs-number&quot; style=&quot;color: #ae81ff;&quot;&gt;0&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;;
&lt;span class=&quot;hljs-rule&quot;&gt;}&lt;/span&gt;&lt;/span&gt;

&lt;span class=&quot;hljs-tag&quot; style=&quot;color: #f8f8f2;&quot;&gt;tbody&lt;/span&gt; &lt;span class=&quot;hljs-rules&quot;&gt;{
  &lt;span class=&quot;hljs-rule&quot;&gt;&lt;span class=&quot;hljs-attribute&quot; style=&quot;color: #f92672;&quot;&gt;margin&lt;/span&gt;:&lt;span class=&quot;hljs-value&quot; style=&quot;color: #f92672;&quot;&gt; &lt;span class=&quot;hljs-number&quot; style=&quot;color: #ae81ff;&quot;&gt;0&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;;
  &lt;span class=&quot;hljs-rule&quot;&gt;&lt;span class=&quot;hljs-attribute&quot; style=&quot;color: #f92672;&quot;&gt;padding&lt;/span&gt;:&lt;span class=&quot;hljs-value&quot; style=&quot;color: #f92672;&quot;&gt; &lt;span class=&quot;hljs-number&quot; style=&quot;color: #ae81ff;&quot;&gt;0&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;;
  &lt;span class=&quot;hljs-rule&quot;&gt;&lt;span class=&quot;hljs-attribute&quot; style=&quot;color: #f92672;&quot;&gt;border&lt;/span&gt;:&lt;span class=&quot;hljs-value&quot; style=&quot;color: #f92672;&quot;&gt; &lt;span class=&quot;hljs-number&quot; style=&quot;color: #ae81ff;&quot;&gt;0&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;;
&lt;span class=&quot;hljs-rule&quot;&gt;}&lt;/span&gt;&lt;/span&gt;

&lt;span class=&quot;hljs-tag&quot; style=&quot;color: #f8f8f2;&quot;&gt;table&lt;/span&gt; &lt;span class=&quot;hljs-tag&quot; style=&quot;color: #f8f8f2;&quot;&gt;tr&lt;/span&gt; &lt;span class=&quot;hljs-rules&quot;&gt;{
  &lt;span class=&quot;hljs-rule&quot;&gt;&lt;span class=&quot;hljs-attribute&quot; style=&quot;color: #f92672;&quot;&gt;border&lt;/span&gt;:&lt;span class=&quot;hljs-value&quot; style=&quot;color: #f92672;&quot;&gt; &lt;span class=&quot;hljs-number&quot; style=&quot;color: #ae81ff;&quot;&gt;0&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;;
  &lt;span class=&quot;hljs-rule&quot;&gt;&lt;span class=&quot;hljs-attribute&quot; style=&quot;color: #f92672;&quot;&gt;border-top&lt;/span&gt;:&lt;span class=&quot;hljs-value&quot; style=&quot;color: #f92672;&quot;&gt; &lt;span class=&quot;hljs-number&quot; style=&quot;color: #ae81ff;&quot;&gt;1px&lt;/span&gt; solid &lt;span class=&quot;hljs-hexcolor&quot; style=&quot;color: #ae81ff;&quot;&gt;#CCC&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;;
  &lt;span class=&quot;hljs-rule&quot;&gt;&lt;span class=&quot;hljs-attribute&quot; style=&quot;color: #f92672;&quot;&gt;background-color&lt;/span&gt;:&lt;span class=&quot;hljs-value&quot; style=&quot;color: #f92672;&quot;&gt; white&lt;/span&gt;&lt;/span&gt;;
  &lt;span class=&quot;hljs-rule&quot;&gt;&lt;span class=&quot;hljs-attribute&quot; style=&quot;color: #f92672;&quot;&gt;margin&lt;/span&gt;:&lt;span class=&quot;hljs-value&quot; style=&quot;color: #f92672;&quot;&gt; &lt;span class=&quot;hljs-number&quot; style=&quot;color: #ae81ff;&quot;&gt;0&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;;
  &lt;span class=&quot;hljs-rule&quot;&gt;&lt;span class=&quot;hljs-attribute&quot; style=&quot;color: #f92672;&quot;&gt;padding&lt;/span&gt;:&lt;span class=&quot;hljs-value&quot; style=&quot;color: #f92672;&quot;&gt; &lt;span class=&quot;hljs-number&quot; style=&quot;color: #ae81ff;&quot;&gt;0&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;;
&lt;span class=&quot;hljs-rule&quot;&gt;}&lt;/span&gt;&lt;/span&gt;

&lt;span class=&quot;hljs-tag&quot; style=&quot;color: #f8f8f2;&quot;&gt;table&lt;/span&gt; &lt;span class=&quot;hljs-tag&quot; style=&quot;color: #f8f8f2;&quot;&gt;tr&lt;/span&gt;&lt;span class=&quot;hljs-pseudo&quot; style=&quot;color: #e6db74;&quot;&gt;:nth-child(2n)&lt;/span&gt; &lt;span class=&quot;hljs-rules&quot;&gt;{
  &lt;span class=&quot;hljs-rule&quot;&gt;&lt;span class=&quot;hljs-attribute&quot; style=&quot;color: #f92672;&quot;&gt;background-color&lt;/span&gt;:&lt;span class=&quot;hljs-value&quot; style=&quot;color: #f92672;&quot;&gt; &lt;span class=&quot;hljs-hexcolor&quot; style=&quot;color: #ae81ff;&quot;&gt;#F8F8F8&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;;
&lt;span class=&quot;hljs-rule&quot;&gt;}&lt;/span&gt;&lt;/span&gt;

&lt;span class=&quot;hljs-tag&quot; style=&quot;color: #f8f8f2;&quot;&gt;table&lt;/span&gt; &lt;span class=&quot;hljs-tag&quot; style=&quot;color: #f8f8f2;&quot;&gt;tr&lt;/span&gt; &lt;span class=&quot;hljs-tag&quot; style=&quot;color: #f8f8f2;&quot;&gt;th&lt;/span&gt;, &lt;span class=&quot;hljs-tag&quot; style=&quot;color: #f8f8f2;&quot;&gt;table&lt;/span&gt; &lt;span class=&quot;hljs-tag&quot; style=&quot;color: #f8f8f2;&quot;&gt;tr&lt;/span&gt; &lt;span class=&quot;hljs-tag&quot; style=&quot;color: #f8f8f2;&quot;&gt;td&lt;/span&gt; &lt;span class=&quot;hljs-rules&quot;&gt;{
  &lt;span class=&quot;hljs-rule&quot;&gt;&lt;span class=&quot;hljs-attribute&quot; style=&quot;color: #f92672;&quot;&gt;font-size&lt;/span&gt;:&lt;span class=&quot;hljs-value&quot; style=&quot;color: #f92672;&quot;&gt; &lt;span class=&quot;hljs-number&quot; style=&quot;color: #ae81ff;&quot;&gt;1em&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;;
  &lt;span class=&quot;hljs-rule&quot;&gt;&lt;span class=&quot;hljs-attribute&quot; style=&quot;color: #f92672;&quot;&gt;border&lt;/span&gt;:&lt;span class=&quot;hljs-value&quot; style=&quot;color: #f92672;&quot;&gt; &lt;span class=&quot;hljs-number&quot; style=&quot;color: #ae81ff;&quot;&gt;1px&lt;/span&gt; solid &lt;span class=&quot;hljs-hexcolor&quot; style=&quot;color: #ae81ff;&quot;&gt;#CCC&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;;
  &lt;span class=&quot;hljs-rule&quot;&gt;&lt;span class=&quot;hljs-attribute&quot; style=&quot;color: #f92672;&quot;&gt;margin&lt;/span&gt;:&lt;span class=&quot;hljs-value&quot; style=&quot;color: #f92672;&quot;&gt; &lt;span class=&quot;hljs-number&quot; style=&quot;color: #ae81ff;&quot;&gt;0&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;;
  &lt;span class=&quot;hljs-rule&quot;&gt;&lt;span class=&quot;hljs-attribute&quot; style=&quot;color: #f92672;&quot;&gt;padding&lt;/span&gt;:&lt;span class=&quot;hljs-value&quot; style=&quot;color: #f92672;&quot;&gt; &lt;span class=&quot;hljs-number&quot; style=&quot;color: #ae81ff;&quot;&gt;0.5em&lt;/span&gt; &lt;span class=&quot;hljs-number&quot; style=&quot;color: #ae81ff;&quot;&gt;1em&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;;
&lt;span class=&quot;hljs-rule&quot;&gt;}&lt;/span&gt;&lt;/span&gt;

&lt;span class=&quot;hljs-tag&quot; style=&quot;color: #f8f8f2;&quot;&gt;table&lt;/span&gt; &lt;span class=&quot;hljs-tag&quot; style=&quot;color: #f8f8f2;&quot;&gt;tr&lt;/span&gt; &lt;span class=&quot;hljs-tag&quot; style=&quot;color: #f8f8f2;&quot;&gt;th&lt;/span&gt; &lt;span class=&quot;hljs-rules&quot;&gt;{
 &lt;span class=&quot;hljs-rule&quot;&gt;&lt;span class=&quot;hljs-attribute&quot; style=&quot;color: #f92672;&quot;&gt;font-weight&lt;/span&gt;:&lt;span class=&quot;hljs-value&quot; style=&quot;color: #f92672;&quot;&gt; bold&lt;/span&gt;&lt;/span&gt;;
  &lt;span class=&quot;hljs-rule&quot;&gt;&lt;span class=&quot;hljs-attribute&quot; style=&quot;color: #f92672;&quot;&gt;background-color&lt;/span&gt;:&lt;span class=&quot;hljs-value&quot; style=&quot;color: #f92672;&quot;&gt; &lt;span class=&quot;hljs-hexcolor&quot; style=&quot;color: #ae81ff;&quot;&gt;#F0F0F0&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;;
&lt;span class=&quot;hljs-rule&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;div style=&quot;font-size: 0em; height: 0; margin: 0; max-height: 0; max-width: 0; overflow: hidden; padding: 0; width: 0;&quot; title=&quot;MDH:57SA6YyE55uu5YmNIE1hcmtkb3duIEhlcmUgcGx1Z2luIOS9v+eUqOeahOioreWumuOAgjxicj48
YnI+U3ludGF4IEhpZ2hsaWdodCDmqKHmnb/vvJpgTW9ub2thaSBTdWJsaW1lYDxicj48YnI+Q1NT
77yaPGJyPmBgYGNzczxicj48ZGl2Pi8qPC9kaXY+PGRpdj4mbmJzcDsqIE5PVEU6PC9kaXY+PGRp
dj4mbmJzcDsqIC0gVGhlIHVzZSBvZiBicm93c2VyLXNwZWNpZmljIHN0eWxlcyAoLW1vei0sIC13
ZWJraXQtKSBzaG91bGQgYmUgYXZvaWRlZC48L2Rpdj48ZGl2PiZuYnNwOyombmJzcDsgJm5ic3A7
SWYgdXNlZCwgdGhleSBtYXkgbm90IHJlbmRlciBjb3JyZWN0bHkgZm9yIHBlb3BsZSByZWFkaW5n
IHRoZSBlbWFpbCBpbjwvZGl2PjxkaXY+Jm5ic3A7KiZuYnNwOyAmbmJzcDthIGRpZmZlcmVudCBi
cm93c2VyIHRoYW4gdGhlIG9uZSBmcm9tIHdoaWNoIHRoZSBlbWFpbCB3YXMgc2VudC48L2Rpdj48
ZGl2PiZuYnNwOyogLSBUaGUgdXNlIG9mIHN0YXRlLWRlcGVuZGVudCBzdHlsZXMgKGxpa2UgYTpo
b3ZlcikgZG9uJ3Qgd29yayBiZWNhdXNlIHRoZXk8L2Rpdj48ZGl2PiZuYnNwOyombmJzcDsgJm5i
c3A7ZG9uJ3QgbWF0Y2ggYXQgdGhlIHRpbWUgdGhlIHN0eWxlcyBhcmUgbWFkZSBleHBsaWNpdC4g
KEluIGVtYWlsLCBzdHlsZXM8L2Rpdj48ZGl2PiZuYnNwOyombmJzcDsgJm5ic3A7bXVzdCBiZSBl
eHBsaWNpdGx5IGFwcGxpZWQgdG8gYWxsIGVsZW1lbnRzIC0tIHN0eWxlc2hlZXRzIGdldCBzdHJp
cHBlZC4pPC9kaXY+PGRpdj4mbmJzcDsqLzwvZGl2PjxkaXY+PGJyPjwvZGl2PjxkaXY+LyogVGhp
cyBpcyB0aGUgb3ZlcmFsbCB3cmFwcGVyLCBpdCBzaG91bGQgYmUgdHJlYXRlZCBhcyB0aGUgYGJv
ZHlgIHNlY3Rpb24uICovPC9kaXY+PGRpdj4ubWFya2Rvd24taGVyZS13cmFwcGVyIHs8L2Rpdj48
ZGl2Pn08L2Rpdj48ZGl2Pjxicj48L2Rpdj48ZGl2Pi8qIFRvIGFkZCBzaXRlIHNwZWNpZmljIHJ1
bGVzLCB5b3UgY2FuIHVzZSB0aGUgYGRhdGEtbWQtdXJsYCBhdHRyaWJ1dGUgdGhhdCB3ZTwvZGl2
PjxkaXY+Jm5ic3A7ICZuYnNwO2FkZCB0byB0aGUgd3JhcHBlciBlbGVtZW50LiBOb3RlIHRoYXQg
cnVsZXMgbGlrZSB0aGlzIGFyZSB1c2VkIGRlcGVuZGluZzwvZGl2PjxkaXY+Jm5ic3A7ICZuYnNw
O29uIHRoZSBVUkwgeW91J3JlICpzZW5kaW5nKiBmcm9tLCBub3QgdGhlIFVSTCB3aGVyZSB0aGUg
cmVjaXBpZW50IHZpZXdzIGl0LjwvZGl2PjxkaXY+Ki88L2Rpdj48ZGl2Pi8qIC5tYXJrZG93bi1o
ZXJlLXdyYXBwZXJbZGF0YS1tZC11cmwqPSJtYWlsLnlhaG9vLiJdIHVsIHsgY29sb3I6IHJlZDsg
fSAqLzwvZGl2PjxkaXY+PGJyPjwvZGl2PjxkaXY+cHJlLCBjb2RlIHs8L2Rpdj48ZGl2PiZuYnNw
OyBmb250LXNpemU6IDAuODVlbTs8L2Rpdj48ZGl2PiZuYnNwOyBmb250LWZhbWlseTogQ29uc29s
YXMsIEluY29uc29sYXRhLCBDb3VyaWVyLCBtb25vc3BhY2U7PC9kaXY+PGRpdj59PC9kaXY+PGRp
dj48YnI+PC9kaXY+PGRpdj5jb2RlIHs8L2Rpdj48ZGl2PiZuYnNwOyBtYXJnaW46IDAgMC4xNWVt
OzwvZGl2PjxkaXY+Jm5ic3A7IHBhZGRpbmc6IDAgMC4zZW07PC9kaXY+PGRpdj4mbmJzcDsgd2hp
dGUtc3BhY2U6IHByZS13cmFwOzwvZGl2PjxkaXY+Jm5ic3A7IGJvcmRlcjogMXB4IHNvbGlkICNF
QUVBRUE7PC9kaXY+PGRpdj4mbmJzcDsgYmFja2dyb3VuZC1jb2xvcjogI0Y4RjhGODs8L2Rpdj48
ZGl2PiZuYnNwOyBib3JkZXItcmFkaXVzOiAzcHg7PC9kaXY+PGRpdj4mbmJzcDsgZGlzcGxheTog
aW5saW5lOyAvKiBhZGRlZCB0byBmaXggWWFob28gYmxvY2sgZGlzcGxheSBvZiBpbmxpbmUgY29k
ZSAqLzwvZGl2PjxkaXY+fTwvZGl2PjxkaXY+PGJyPjwvZGl2PjxkaXY+cHJlIHs8L2Rpdj48ZGl2
PiZuYnNwOyBmb250LXNpemU6IDFlbTs8L2Rpdj48ZGl2PiZuYnNwOyBsaW5lLWhlaWdodDogMS4y
ZW07PC9kaXY+PGRpdj59PC9kaXY+PGRpdj48YnI+PC9kaXY+PGRpdj5wcmUgY29kZSB7PC9kaXY+
PGRpdj4mbmJzcDsgd2hpdGUtc3BhY2U6IHByZTs8L2Rpdj48ZGl2PiZuYnNwOyBvdmVyZmxvdzog
YXV0bzsgLyogZml4ZXMgaXNzdWUgIzcwOiBGaXJlZm94L1RodW5kZXJiaXJkOiBDb2RlIGJsb2Nr
cyB3aXRoIGhvcml6b250YWwgc2Nyb2xsIHdvdWxkIGhhdmUgYmFkIGJhY2tncm91bmQgY29sb3Vy
ICovPC9kaXY+PGRpdj4mbmJzcDsgYm9yZGVyLXJhZGl1czogM3B4OzwvZGl2PjxkaXY+Jm5ic3A7
IGJvcmRlcjogMXB4IHNvbGlkICNDQ0M7PC9kaXY+PGRpdj4mbmJzcDsgcGFkZGluZzogMC41ZW0g
MC43ZW07PC9kaXY+PGRpdj4mbmJzcDsgZGlzcGxheTogYmxvY2sgIWltcG9ydGFudDsgLyogYWRk
ZWQgdG8gY291bnRlcmFjdCB0aGUgWWFob28tc3BlY2lmaWMgYGNvZGVgIHJ1bGU7IHdpdGhvdXQg
dGhpcywgY29kZSBibG9ja3MgaW4gQmxvZ2dlciBhcmUgYnJva2VuICovPC9kaXY+PGRpdj59PC9k
aXY+PGRpdj48YnI+PC9kaXY+PGRpdj4vKiBJbiBlZGl0IG1vZGUsIFdvcmRwcmVzcyB1c2VzIGEg
YCogeyBmb250OiAuLi47fSBydWxlK3N0eWxlIHRoYXQgbWFrZXMgaGlnaGxpZ2h0ZWQ8L2Rpdj48
ZGl2PmNvZGUgbG9vayBub24tbW9ub3NwYWNlLiBUaGlzIHJ1bGUgd2lsbCBvdmVycmlkZSBpdC4g
Ki88L2Rpdj48ZGl2Pi5tYXJrZG93bi1oZXJlLXdyYXBwZXJbZGF0YS1tZC11cmwqPSJ3b3JkcHJl
c3MuIl0gY29kZSBzcGFuIHs8L2Rpdj48ZGl2PiZuYnNwOyBmb250OiBpbmhlcml0OzwvZGl2Pjxk
aXY+fTwvZGl2PjxkaXY+PGJyPjwvZGl2PjxkaXY+LyogV29yZHByZXNzIGFkZHMgYSBncmV5IGJh
Y2tncm91bmQgdG8gYHByZWAgZWxlbWVudHMgdGhhdCBkb2Vzbid0IGdvIHdlbGwgd2l0aDwvZGl2
PjxkaXY+b3VyIHN5bnRheCBoaWdobGlnaHRpbmcuICovPC9kaXY+PGRpdj4ubWFya2Rvd24taGVy
ZS13cmFwcGVyW2RhdGEtbWQtdXJsKj0id29yZHByZXNzLiJdIHByZSB7PC9kaXY+PGRpdj4mbmJz
cDsgYmFja2dyb3VuZC1jb2xvcjogdHJhbnNwYXJlbnQ7PC9kaXY+PGRpdj59PC9kaXY+PGRpdj48
YnI+PC9kaXY+PGRpdj4vKiBUaGlzIHNwYWNpbmcgaGFzIGJlZW4gdHdlYWtlZCB0byBjbG9zZWx5
IG1hdGNoIEdtYWlsK0Nocm9tZSAicGFyYWdyYXBoIiAodHdvIGxpbmUgYnJlYWtzKSBzcGFjaW5n
LjwvZGl2PjxkaXY+Tm90ZSB0aGF0IHdlIG9ubHkgdXNlIGEgdG9wIG1hcmdpbiBhbmQgbm90IGEg
Ym90dG9tIG1hcmdpbiAtLSB0aGlzIHByZXZlbnRzIHRoZTwvZGl2PjxkaXY+ImJsYW5rIGxpbmUi
IGxvb2sgYXQgdGhlIHRvcCBvZiB0aGUgZW1haWwgKGlzc3VlICMyNDMpLjwvZGl2PjxkaXY+Ki88
L2Rpdj48ZGl2PnAgezwvZGl2PjxkaXY+Jm5ic3A7IC8qICFpbXBvcnRhbnQgaXMgbmVlZGVkIGhl
cmUgYmVjYXVzZSBIb3RtYWlsL091dGxvb2suY29tIHVzZXMgIWltcG9ydGFudCB0bzwvZGl2Pjxk
aXY+Jm5ic3A7ICZuYnNwOyAmbmJzcDtraWxsIHRoZSBtYXJnaW4gaW4gJmx0O3AmZ3Q7LiBXZSBu
ZWVkIHRoaXMgdG8gd2luLiAqLzwvZGl2PjxkaXY+Jm5ic3A7IG1hcmdpbjogMCAwIDEuMmVtIDAg
IWltcG9ydGFudDs8L2Rpdj48ZGl2Pn08L2Rpdj48ZGl2Pjxicj48L2Rpdj48ZGl2PnRhYmxlLCBw
cmUsIGRsLCBibG9ja3F1b3RlLCBxLCB1bCwgb2wgezwvZGl2PjxkaXY+Jm5ic3A7IG1hcmdpbjog
MS4yZW0gMDs8L2Rpdj48ZGl2Pn08L2Rpdj48ZGl2Pjxicj48L2Rpdj48ZGl2PnVsLCBvbCB7PC9k
aXY+PGRpdj4mbmJzcDsgcGFkZGluZy1sZWZ0OiAyZW07PC9kaXY+PGRpdj59PC9kaXY+PGRpdj48
YnI+PC9kaXY+PGRpdj5saSB7PC9kaXY+PGRpdj4mbmJzcDsgbWFyZ2luOiAwLjVlbSAwOzwvZGl2
PjxkaXY+fTwvZGl2PjxkaXY+PGJyPjwvZGl2PjxkaXY+LyogU3BhY2UgcGFyYWdyYXBocyBpbiBh
IGxpc3QgdGhlIHNhbWUgYXMgdGhlIGxpc3QgaXRzZWxmLiAqLzwvZGl2PjxkaXY+bGkgcCB7PC9k
aXY+PGRpdj4mbmJzcDsgLyogTmVlZHMgIWltcG9ydGFudCB0byBvdmVycmlkZSBydWxlIGFib3Zl
LiAqLzwvZGl2PjxkaXY+Jm5ic3A7IG1hcmdpbjogMC41ZW0gMCAhaW1wb3J0YW50OzwvZGl2Pjxk
aXY+fTwvZGl2PjxkaXY+PGJyPjwvZGl2PjxkaXY+LyogU21hbGxlciBzcGFjaW5nIGZvciBzdWIt
bGlzdHMgKi88L2Rpdj48ZGl2PnVsIHVsLCB1bCBvbCwgb2wgdWwsIG9sIG9sIHs8L2Rpdj48ZGl2
PiZuYnNwOyBtYXJnaW46IDA7PC9kaXY+PGRpdj4mbmJzcDsgcGFkZGluZy1sZWZ0OiAxZW07PC9k
aXY+PGRpdj59PC9kaXY+PGRpdj48YnI+PC9kaXY+PGRpdj4vKiBVc2UgUm9tYW4gbnVtZXJhbHMg
Zm9yIHN1Yi1vcmRlcmVkLWxpc3RzLiAoTGlrZSBHaXRodWIuKSAqLzwvZGl2PjxkaXY+b2wgb2ws
IHVsIG9sIHs8L2Rpdj48ZGl2PiZuYnNwOyBsaXN0LXN0eWxlLXR5cGU6IGxvd2VyLXJvbWFuOzwv
ZGl2PjxkaXY+fTwvZGl2PjxkaXY+PGJyPjwvZGl2PjxkaXY+LyogVXNlIGxldHRlcnMgZm9yIHN1
Yi1zdWItb3JkZXJlZCBsaXN0cy4gKExpa2UgR2l0aHViLikgKi88L2Rpdj48ZGl2PnVsIHVsIG9s
LCB1bCBvbCBvbCwgb2wgdWwgb2wsIG9sIG9sIG9sIHs8L2Rpdj48ZGl2PiZuYnNwOyBsaXN0LXN0
eWxlLXR5cGU6IGxvd2VyLWFscGhhOzwvZGl2PjxkaXY+fTwvZGl2PjxkaXY+PGJyPjwvZGl2Pjxk
aXY+ZGwgezwvZGl2PjxkaXY+Jm5ic3A7IHBhZGRpbmc6IDA7PC9kaXY+PGRpdj59PC9kaXY+PGRp
dj48YnI+PC9kaXY+PGRpdj5kbCBkdCB7PC9kaXY+PGRpdj4mbmJzcDsgZm9udC1zaXplOiAxZW07
PC9kaXY+PGRpdj4mbmJzcDsgZm9udC13ZWlnaHQ6IGJvbGQ7PC9kaXY+PGRpdj4mbmJzcDsgZm9u
dC1zdHlsZTogaXRhbGljOzwvZGl2PjxkaXY+fTwvZGl2PjxkaXY+PGJyPjwvZGl2PjxkaXY+ZGwg
ZGQgezwvZGl2PjxkaXY+Jm5ic3A7IG1hcmdpbjogMCAwIDFlbTs8L2Rpdj48ZGl2PiZuYnNwOyBw
YWRkaW5nOiAwIDFlbTs8L2Rpdj48ZGl2Pn08L2Rpdj48ZGl2Pjxicj48L2Rpdj48ZGl2PmJsb2Nr
cXVvdGUsIHEgezwvZGl2PjxkaXY+Jm5ic3A7IGJvcmRlci1sZWZ0OiA0cHggc29saWQgI0RERDs8
L2Rpdj48ZGl2PiZuYnNwOyBwYWRkaW5nOiAwIDFlbTs8L2Rpdj48ZGl2PiZuYnNwOyBjb2xvcjog
Izc3Nzs8L2Rpdj48ZGl2PiZuYnNwOyBxdW90ZXM6IG5vbmU7PC9kaXY+PGRpdj59PC9kaXY+PGRp
dj48YnI+PC9kaXY+PGRpdj5ibG9ja3F1b3RlOjpiZWZvcmUsIGJsb2NrcXVvdGU6OmFmdGVyLCBx
OjpiZWZvcmUsIHE6OmFmdGVyIHs8L2Rpdj48ZGl2PiZuYnNwOyBjb250ZW50OiBub25lOzwvZGl2
PjxkaXY+fTwvZGl2PjxkaXY+PGJyPjwvZGl2PjxkaXY+YmxvY2txdW90ZXtib3JkZXItbGVmdDog
MTVweCBzb2xpZCAjYzc2YzBjO2ZvbnQtZmFtaWx5OiBHZW9yZ2lhLCBzZXJpZjtmb250LXNpemU6
MTVweDt0ZXh0LWFsaWduOiBqdXN0aWZ5O2JhY2tncm91bmQ6ICNmZmY7bGluZS1oZWlnaHQ6IDEu
Mjs8L2Rpdj48ZGl2PmRpc3BsYXk6YmxvY2s7bWFyZ2luOiAwIDAgMjBweDtwYWRkaW5nOiAxNXB4
IDIwcHggMTVweCA0NXB4O3Bvc2l0aW9uOiByZWxhdGl2ZTsgZm9udC1zaXplOiAxNnB4O2NvbG9y
OiAjNjY2OzwvZGl2PjxkaXY+Ym9yZGVyLXJpZ2h0OiAycHggc29saWQgI2M3NmMwYztib3gtc2hh
ZG93OiAycHggMnB4IDE1cHggI2NjYzstd2Via2l0LWJveC1zaGFkb3c6IDJweCAycHggMTVweCAj
Y2NjOy1tb3otYm94LXNoYWRvdzogMnB4IDJweCAxNXB4ICNjY2M7fTwvZGl2PjxkaXY+YmxvY2tx
dW90ZTo6YmVmb3Jle2ZvbnQtc2l6ZTogNTBweDtwb3NpdGlvbjogYWJzb2x1dGU7Y29sb3I6ICM5
OTk7bGVmdDogMTBweDt0b3A6NXB4O2ZvbnQtd2VpZ2h0OiBib2xkO2NvbnRlbnQ6ICJcMjAxQyI7
fTwvZGl2PjxkaXY+YmxvY2txdW90ZSBhe2N1cnNvcjogcG9pbnRlcjtjb2xvcjogI2M3NmMwYzt0
ZXh0LWRlY29yYXRpb246IG5vbmU7YmFja2dyb3VuZDogI2VlZTtwYWRkaW5nOiAwIDNweDt9PC9k
aXY+PGRpdj5ibG9ja3F1b3RlIGE6aG92ZXJ7Y29sb3I6ICM1NTU7fTwvZGl2PjxkaXY+PGJyPjwv
ZGl2PjxkaXY+aDEsIGgyLCBoMywgaDQsIGg1LCBoNiB7PC9kaXY+PGRpdj4mbmJzcDsgbWFyZ2lu
OiAxLjNlbSAwIDFlbTs8L2Rpdj48ZGl2PiZuYnNwOyBwYWRkaW5nOiAwOzwvZGl2PjxkaXY+Jm5i
c3A7IGZvbnQtd2VpZ2h0OiBib2xkOzwvZGl2PjxkaXY+fTwvZGl2PjxkaXY+PGJyPjwvZGl2Pjxk
aXY+aDEgezwvZGl2PjxkaXY+Jm5ic3A7IGZvbnQtc2l6ZTogMS42ZW07PC9kaXY+PGRpdj4mbmJz
cDsgYm9yZGVyLWJvdHRvbTogMXB4IHNvbGlkICNkZGQ7PC9kaXY+PGRpdj59PC9kaXY+PGRpdj48
YnI+PC9kaXY+PGRpdj5oMiB7PC9kaXY+PGRpdj4mbmJzcDsgZm9udC1zaXplOiAxLjRlbTs8L2Rp
dj48ZGl2PiZuYnNwOyBib3JkZXItYm90dG9tOiAxcHggc29saWQgI2VlZTs8L2Rpdj48ZGl2Pn08
L2Rpdj48ZGl2Pjxicj48L2Rpdj48ZGl2PmgzIHs8L2Rpdj48ZGl2PiZuYnNwOyBmb250LXNpemU6
IDEuM2VtOzwvZGl2PjxkaXY+fTwvZGl2PjxkaXY+PGJyPjwvZGl2PjxkaXY+aDQgezwvZGl2Pjxk
aXY+Jm5ic3A7IGZvbnQtc2l6ZTogMS4yZW07PC9kaXY+PGRpdj59PC9kaXY+PGRpdj48YnI+PC9k
aXY+PGRpdj5oNSB7PC9kaXY+PGRpdj4mbmJzcDsgZm9udC1zaXplOiAxZW07PC9kaXY+PGRpdj59
PC9kaXY+PGRpdj48YnI+PC9kaXY+PGRpdj5oNiB7PC9kaXY+PGRpdj4mbmJzcDsgZm9udC1zaXpl
OiAxZW07PC9kaXY+PGRpdj4mbmJzcDsgY29sb3I6ICM3Nzc7PC9kaXY+PGRpdj59PC9kaXY+PGRp
dj48YnI+PC9kaXY+PGRpdj50YWJsZSB7PC9kaXY+PGRpdj4mbmJzcDsgcGFkZGluZzogMDs8L2Rp
dj48ZGl2PiZuYnNwOyBib3JkZXItY29sbGFwc2U6IGNvbGxhcHNlOzwvZGl2PjxkaXY+Jm5ic3A7
IGJvcmRlci1zcGFjaW5nOiAwOzwvZGl2PjxkaXY+Jm5ic3A7IGZvbnQtc2l6ZTogMWVtOzwvZGl2
PjxkaXY+Jm5ic3A7IGZvbnQ6IGluaGVyaXQ7PC9kaXY+PGRpdj4mbmJzcDsgYm9yZGVyOiAwOzwv
ZGl2PjxkaXY+fTwvZGl2PjxkaXY+PGJyPjwvZGl2PjxkaXY+dGJvZHkgezwvZGl2PjxkaXY+Jm5i
c3A7IG1hcmdpbjogMDs8L2Rpdj48ZGl2PiZuYnNwOyBwYWRkaW5nOiAwOzwvZGl2PjxkaXY+Jm5i
c3A7IGJvcmRlcjogMDs8L2Rpdj48ZGl2Pn08L2Rpdj48ZGl2Pjxicj48L2Rpdj48ZGl2PnRhYmxl
IHRyIHs8L2Rpdj48ZGl2PiZuYnNwOyBib3JkZXI6IDA7PC9kaXY+PGRpdj4mbmJzcDsgYm9yZGVy
LXRvcDogMXB4IHNvbGlkICNDQ0M7PC9kaXY+PGRpdj4mbmJzcDsgYmFja2dyb3VuZC1jb2xvcjog
d2hpdGU7PC9kaXY+PGRpdj4mbmJzcDsgbWFyZ2luOiAwOzwvZGl2PjxkaXY+Jm5ic3A7IHBhZGRp
bmc6IDA7PC9kaXY+PGRpdj59PC9kaXY+PGRpdj48YnI+PC9kaXY+PGRpdj50YWJsZSB0cjpudGgt
Y2hpbGQoMm4pIHs8L2Rpdj48ZGl2PiZuYnNwOyBiYWNrZ3JvdW5kLWNvbG9yOiAjRjhGOEY4Ozwv
ZGl2PjxkaXY+fTwvZGl2PjxkaXY+PGJyPjwvZGl2PjxkaXY+dGFibGUgdHIgdGgsIHRhYmxlIHRy
IHRkIHs8L2Rpdj48ZGl2PiZuYnNwOyBmb250LXNpemU6IDFlbTs8L2Rpdj48ZGl2PiZuYnNwOyBi
b3JkZXI6IDFweCBzb2xpZCAjQ0NDOzwvZGl2PjxkaXY+Jm5ic3A7IG1hcmdpbjogMDs8L2Rpdj48
ZGl2PiZuYnNwOyBwYWRkaW5nOiAwLjVlbSAxZW07PC9kaXY+PGRpdj59PC9kaXY+PGRpdj48YnI+
PC9kaXY+PGRpdj50YWJsZSB0ciB0aCB7PC9kaXY+PGRpdj4mbmJzcDtmb250LXdlaWdodDogYm9s
ZDs8L2Rpdj48ZGl2PiZuYnNwOyBiYWNrZ3JvdW5kLWNvbG9yOiAjRjBGMEYwOzwvZGl2PjxkaXY+
fTwvZGl2PmBgYA==&quot;&gt;​&lt;/div&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimwayne.blogspot.com/feeds/731105499527792278/comments/default' title='張貼留言'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/6819483672105967231/731105499527792278' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6819483672105967231/posts/default/731105499527792278'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6819483672105967231/posts/default/731105499527792278'/><link rel='alternate' type='text/html' href='http://jimwayne.blogspot.com/2022/10/markdown-here.html' title='Markdown Here 樣式筆記'/><author><name>Wayne Yeh</name><uri>http://www.blogger.com/profile/02148905864753873930</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6819483672105967231.post-2208057883847236711</id><published>2022-09-04T23:51:00.007+08:00</published><updated>2022-10-16T12:52:50.690+08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="Information Retrieval"/><category scheme="http://www.blogger.com/atom/ns#" term="Markdown Here"/><title type='text'>[筆記] 詞彙的建立</title><content type='html'>&lt;div class=&quot;markdown-here-wrapper&quot; data-md-url=&quot;https://www.blogger.com/blog/post/edit/6819483672105967231/2208057883847236711&quot;&gt;&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;在 IR 系統的前處理中，因為我們想要把資料建成 posting list，會需要先有相關的詞彙，才有辦法依據詞彙建立相應的 posting list。不過這時會有幾個常見的問題需要處理。&lt;/p&gt;
&lt;ul style=&quot;margin: 1.2em 0px; padding-left: 2em;&quot;&gt;
&lt;li style=&quot;margin: 0.5em 0px;&quot;&gt;&lt;a href=&quot;https://nlp.stanford.edu/IR-book/html/htmledition/tokenization-1.html&quot;&gt;Tokenization&lt;/a&gt;&lt;/li&gt;
&lt;li style=&quot;margin: 0.5em 0px;&quot;&gt;&lt;a href=&quot;https://nlp.stanford.edu/IR-book/html/htmledition/dropping-common-terms-stop-words-1.html&quot;&gt;Stop Words&lt;/a&gt;&lt;/li&gt;
&lt;li style=&quot;margin: 0.5em 0px;&quot;&gt;&lt;a href=&quot;https://nlp.stanford.edu/IR-book/html/htmledition/normalization-equivalence-classing-of-terms-1.html&quot;&gt;Normalization&lt;/a&gt;&lt;/li&gt;
&lt;li style=&quot;margin: 0.5em 0px;&quot;&gt;&lt;a href=&quot;https://nlp.stanford.edu/IR-book/html/htmledition/stemming-and-lemmatization-1.html&quot;&gt;Stemming and Lemmatization&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;這篇會簡要紀錄一下書中提到的考量點。&lt;/p&gt;
&lt;h2 id=&quot;1-tokenization&quot; style=&quot;border-bottom: 1px solid rgb(238, 238, 238); font-size: 1.4em; font-weight: bold; margin: 1.3em 0px 1em; padding: 0px;&quot;&gt;1. Tokenization&lt;/h2&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;Tokenization 是把句子分割並萃取的技術，萃取出來的單位被稱為 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;token&lt;/code&gt;，或者也可能被稱為 word、term，但事實上有時候我們可能會想要把 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;token&lt;/code&gt; 跟其他名詞做一點定義上的區分。以下是書中對於 token 的定義：&lt;/p&gt;
&lt;blockquote style=&quot;margin: 1.2em 0px;&quot;&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt; A  token   is an instance of a sequence of characters in some particular document that are grouped together as a useful semantic unit for processing.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;除了 token 以外，另外還有 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;type&lt;/code&gt; 和 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;term&lt;/code&gt; 這兩個名詞。書中提供的定義分別如下：&lt;/p&gt;
&lt;blockquote style=&quot;margin: 1.2em 0px;&quot;&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;A  type  is the class of all tokens containing the same character sequence. &lt;/p&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;A  term  is a (perhaps normalized) type that is included in the IR system’s dictionary.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;直接用書中提供的例子應該會比較好懂。舉例來說，如果我們想對下述的句子做 tokenization：&lt;/p&gt;
&lt;pre style=&quot;font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; font-size: 1em; line-height: 1.2em; margin: 1.2em 0px;&quot;&gt;&lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border-radius: 3px; border: 1px solid rgb(204, 204, 204); border: 1px solid rgb(234, 234, 234); display: block !important; display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; overflow: auto; padding: 0.5em 0.7em; padding: 0px 0.3em; white-space: pre-wrap; white-space: pre;&quot;&gt;to sleep perchance to dream
&lt;/code&gt;&lt;/pre&gt;&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;此時，會有 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;to&lt;/code&gt;, &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;sleep&lt;/code&gt;, &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;perchance&lt;/code&gt;, &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;to&lt;/code&gt;, &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;dream&lt;/code&gt; 共 5 個 token；但只會有 4 個 type：&lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;to&lt;/code&gt;, &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;sleep&lt;/code&gt;, &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;perchance&lt;/code&gt;, &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;dream&lt;/code&gt;，因為有兩個 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;to&lt;/code&gt; 是重複的；而如果 IR 系統中有處理 stop words，我們可能會把 to 視為是 stop words 而將它移除，此時最終在 IR 系統中被 index 的 term 就只會有 3 個：&lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;sleep&lt;/code&gt;, &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;perchance&lt;/code&gt;, &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;dream&lt;/code&gt; 。&lt;/p&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;那麼我們通常會如何做 tokenziation 呢？初步看起來，似乎就是直接針對空白做分割，然後可能把標點符號什麼的都去除掉就可以了？然而現實上，還是會出現一些比較困難的問題，導致我們可能還需要考慮別的因子。例如：&lt;/p&gt;
&lt;ul style=&quot;margin: 1.2em 0px; padding-left: 2em;&quot;&gt;
&lt;li style=&quot;margin: 0.5em 0px;&quot;&gt;&lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;&#39;&lt;/code&gt; 在英文中，可以代表所有格關係、也可以代表縮寫。&lt;/li&gt;
&lt;li style=&quot;margin: 0.5em 0px;&quot;&gt;不同語言有自己的語言特徵。&lt;/li&gt;
&lt;li style=&quot;margin: 0.5em 0px;&quot;&gt;特定領域可能有自己特殊的 type。&lt;/li&gt;
&lt;li style=&quot;margin: 0.5em 0px;&quot;&gt;&lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;-&lt;/code&gt; 在英文中也存在多種不同用法。&lt;/li&gt;
&lt;li style=&quot;margin: 0.5em 0px;&quot;&gt;多個字組成的詞彙無法單純用空白切割來處理。&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;以下會分別針對這些狀況做點簡單的描述。&lt;/p&gt;
&lt;h3 id=&quot;1-1-apostrophe-&quot; style=&quot;font-size: 1.3em; font-weight: bold; margin: 1.3em 0px 1em; padding: 0px;&quot;&gt;1.1. Apostrophe (‘)&lt;/h3&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;&lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;&#39;&lt;/code&gt; 這個符號在英文中有多種用法，例如以下這個句子：&lt;/p&gt;
&lt;pre style=&quot;font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; font-size: 1em; line-height: 1.2em; margin: 1.2em 0px;&quot;&gt;&lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border-radius: 3px; border: 1px solid rgb(204, 204, 204); border: 1px solid rgb(234, 234, 234); display: block !important; display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; overflow: auto; padding: 0.5em 0.7em; padding: 0px 0.3em; white-space: pre-wrap; white-space: pre;&quot;&gt;Mr. O&#39;Neill thinks that the boys&#39; stories about Chile&#39;s capital aren&#39;t amusing.
&lt;/code&gt;&lt;/pre&gt;&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;這裡同時出現了三種不同的用法：&lt;/p&gt;
&lt;ul style=&quot;margin: 1.2em 0px; padding-left: 2em;&quot;&gt;
&lt;li style=&quot;margin: 0.5em 0px;&quot;&gt;O’Neill 中的 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;&#39;&lt;/code&gt; 是名字的一部分。&lt;/li&gt;
&lt;li style=&quot;margin: 0.5em 0px;&quot;&gt;boys’ 和 Chile’s 中的 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;&#39;&lt;/code&gt; 是所有格。&lt;/li&gt;
&lt;li style=&quot;margin: 0.5em 0px;&quot;&gt;aren’t 中的 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;&#39;&lt;/code&gt; 是縮寫。&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;此時針對 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;&#39;&lt;/code&gt; 做的處理的不同，可能會導致後續 IR 系統的 matching 受到影響。例如如果我們將 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;&#39;&lt;/code&gt; 拆開的話，&lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;aren&#39;t&lt;/code&gt; 被拆成 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;aren&lt;/code&gt; 跟 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;t&lt;/code&gt; 看起來就會很奇怪。同時，&lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;O&#39;Neill&lt;/code&gt; 被拆成 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;O&lt;/code&gt; 跟 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;Neill&lt;/code&gt; 會導致搜尋時使用 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;o&#39;neil&lt;/code&gt; 會無法 match 到相應的 token。&lt;/p&gt;
&lt;h3 id=&quot;1-2-&quot; style=&quot;font-size: 1.3em; font-weight: bold; margin: 1.3em 0px 1em; padding: 0px;&quot;&gt;1.2. 語言特徵&lt;/h3&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;tokenization 的行為往往跟語言有很大的關係，不同語言的 tokenization 作法會有蠻大的不同。例如中文和日文就沒有空白分隔的特性，每個字都是連在一起的。&lt;/p&gt;
&lt;h3 id=&quot;1-3-&quot; style=&quot;font-size: 1.3em; font-weight: bold; margin: 1.3em 0px 1em; padding: 0px;&quot;&gt;1.3. 領域特徵&lt;/h3&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;在特定領域，有可能有領域內專屬的詞彙需要能夠被識別成 term，例如 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;C++&lt;/code&gt; 和 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;C#&lt;/code&gt;、或者飛機型號 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;B-52&lt;/code&gt;（戰略轟炸機）。另外網際網路中的 URL（&lt;a href=&quot;https://xxx）、e-mail（abc@abc.com）、IP&quot;&gt;https://xxx）、e-mail（abc@abc.com）、IP&lt;/a&gt; 位址（a.b.c.d）等等可能也會需要能夠被視為是獨立的 term。&lt;/p&gt;
&lt;h3 id=&quot;1-4-hyphenation-&quot; style=&quot;font-size: 1.3em; font-weight: bold; margin: 1.3em 0px 1em; padding: 0px;&quot;&gt;1.4. 連字符號 Hyphenation (-)&lt;/h3&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;英文中連字符號的處理可能會很複雜，書中舉出幾種連字符號的用法：&lt;/p&gt;
&lt;ul style=&quot;margin: 1.2em 0px; padding-left: 2em;&quot;&gt;
&lt;li style=&quot;margin: 0.5em 0px;&quot;&gt;分隔單字之間的母音，像是 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;co-education&lt;/code&gt;。這個例子實際上它代表的是 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;coeducaion&lt;/code&gt;。&lt;/li&gt;
&lt;li style=&quot;margin: 0.5em 0px;&quot;&gt;將多個名詞連接成一個新的名稱，例如 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;Hewlett-Packard&lt;/code&gt;。&lt;/li&gt;
&lt;li style=&quot;margin: 0.5em 0px;&quot;&gt;多個詞語的組成結果，例如 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;the hold-him-back-and-drag-him-away maneuver&lt;/code&gt;。這個例子應該要被拆解成各自獨立的 term。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;1-5-&quot; style=&quot;font-size: 1.3em; font-weight: bold; margin: 1.3em 0px 1em; padding: 0px;&quot;&gt;1.5. 特殊詞彙&lt;/h3&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;這類的資訊其實我覺得也可以算是領域特徵的一種。例如書中舉的一個例子是 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;New York University&lt;/code&gt;（紐約大學）跟 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;York University&lt;/code&gt;（約克大學）。如果他們沒有被獨立視為 term，而是純粹依據空白拆開的話，那麼搜尋 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;York University&lt;/code&gt; 時就會 match 到 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;New York University&lt;/code&gt; 了，但這顯然並非是使用者想找的東西。&lt;/p&gt;
&lt;h2 id=&quot;2-stop-words&quot; style=&quot;border-bottom: 1px solid rgb(238, 238, 238); font-size: 1.4em; font-weight: bold; margin: 1.3em 0px 1em; padding: 0px;&quot;&gt;2. Stop Words&lt;/h2&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;停用詞（Stop Words）的目的，是去除出現頻率過高、但又跟文件本身關係不大的詞彙。不過由於去除停用詞的行為，有時會導致意義的流失，所以現代的 IR 系統比較會考慮不去除停用詞，改為將停用詞賦予較低的權重，使其影響變小。舉例來說，&lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;flights to London&lt;/code&gt; 如果把 &lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;to&lt;/code&gt; 給去除了，&lt;code style=&quot;background-color: #f8f8f8; border-radius: 3px; border: 1px solid rgb(234, 234, 234); display: inline; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 0.85em; margin: 0px 0.15em; padding: 0px 0.3em; white-space: pre-wrap;&quot;&gt;flights to London&lt;/code&gt; 的意義就流失了。&lt;/p&gt;
&lt;hr /&gt;
&lt;p style=&quot;margin: 0px 0px 1.2em !important;&quot;&gt;其實還有兩個小節，不過等有空時再補筆記了…。&lt;br /&gt;是說搜尋是個在現代非常常見的行為，但最近的研究總覺得這個領域有點冷門？&lt;/p&gt;
&lt;div style=&quot;font-size: 0em; height: 0; margin: 0; max-height: 0; max-width: 0; overflow: hidden; padding: 0; width: 0;&quot; title=&quot;MDH:PHA+5ZyoIElSIOezu+e1seeahOWJjeiZleeQhuS4re+8jOWboOeCuuaIkeWAkeaDs+imgeaKiuiz
h+aWmeW7uuaIkCBwb3N0aW5nIGxpc3TvvIzmnIPpnIDopoHlhYjmnInnm7jpl5znmoToqZ7lvZnv
vIzmiY3mnInovqbms5Xkvp3mk5roqZ7lvZnlu7rnq4vnm7jmh4nnmoQgcG9zdGluZyBsaXN044CC
5LiN6YGO6YCZ5pmC5pyD5pyJ5bm+5YCL5bi46KaL55qE5ZWP6aGM6ZyA6KaB6JmV55CG44CCPC9w
PjxwPiogW1Rva2VuaXphdGlvbl1bdG9rZW5pemF0aW9uXTxicj4qIFtTdG9wIFdvcmRzXVtzdG9w
V29yZHNdPGJyPiogW05vcm1hbGl6YXRpb25dW25vcm1hbGl6YXRpb25dPGJyPiogW1N0ZW1taW5n
IGFuZCBMZW1tYXRpemF0aW9uXVtzdGVtbWluZ108L3A+PHA+6YCZ56+H5pyD57Ch6KaB57SA6YyE
5LiA5LiL5pu45Lit5o+Q5Yiw55qE6ICD6YeP6bue44CCPC9wPjxwPiMjIDEuIFRva2VuaXphdGlv
bjwvcD48cD5Ub2tlbml6YXRpb24g5piv5oqK5Y+l5a2Q5YiG5Ymy5Lim6JCD5Y+W55qE5oqA6KGT
77yM6JCD5Y+W5Ye65L6G55qE5Zau5L2N6KKr56ix54K6IGB0b2tlbmDvvIzmiJbogIXkuZ/lj6/o
g73ooqvnqLHngrogd29yZOOAgXRlcm3vvIzkvYbkuovlr6bkuIrmnInmmYLlgJnmiJHlgJHlj6/o
g73mnIPmg7PopoHmioogYHRva2VuYCDot5/lhbbku5blkI3oqZ7lgZrkuIDpu57lrprnvqnkuIrn
moTljYDliIbjgILku6XkuIvmmK/mm7jkuK3lsI3mlrwgdG9rZW4g55qE5a6a576p77yaPGJyPjxi
cj4mZ3Q7ICBBCjxhIG5hbWU9IjIwOTEiPjwvYT4gPGk+dG9rZW48L2k+IDxhIG5hbWU9InA6dG9r
ZW4tZGVmbiI+PC9hPiAgaXMgYW4gaW5zdGFuY2Ugb2YgYQpzZXF1ZW5jZSBvZiBjaGFyYWN0ZXJz
IGluIHNvbWUgcGFydGljdWxhciBkb2N1bWVudCB0aGF0CmFyZSBncm91cGVkIHRvZ2V0aGVyIGFz
IGEgdXNlZnVsIHNlbWFudGljIHVuaXQgZm9yCnByb2Nlc3NpbmcuPC9wPjxwPjwvcD48cD7pmaTk
uoYgdG9rZW4g5Lul5aSW77yM5Y+m5aSW6YKE5pyJIGB0eXBlYCDlkowgYHRlcm1gIOmAmeWFqeWA
i+WQjeipnuOAguabuOS4reaPkOS+m+eahOWumue+qeWIhuWIpeWmguS4i++8mjxicj48YnI+Jmd0
OyBBIDxhIG5hbWU9IjIwOTQiPjwvYT4gPGk+dHlwZTwvaT4gIGlzIHRoZSBjbGFzcyBvZiBhbGwg
dG9rZW5zCmNvbnRhaW5pbmcgdGhlIHNhbWUgY2hhcmFjdGVyIHNlcXVlbmNlLiA8YnI+PGJyPiZn
dDsgQSA8YSBuYW1lPSIyMDk2Ij48L2E+IDxpPnRlcm08L2k+ICBpcyBhCihwZXJoYXBzIG5vcm1h
bGl6ZWQpIHR5cGUgdGhhdCBpcyBpbmNsdWRlZCBpbiB0aGUgSVIgc3lzdGVtJ3MKZGljdGlvbmFy
eS48YnI+PGJyPuebtOaOpeeUqOabuOS4reaPkOS+m+eahOS+i+WtkOaHieipsuacg+avlOi8g+Wl
veaHguOAguiIieS+i+S+huiqqu+8jOWmguaenOaIkeWAkeaDs+WwjeS4i+i/sOeahOWPpeWtkOWB
miB0b2tlbml6YXRpb27vvJo8YnI+PGJyPmBgYDxicj50byBzbGVlcCBwZXJjaGFuY2UgdG8gZHJl
YW08YnI+YGBgPGJyPjxicj7mraTmmYLvvIzmnIPmnIkgYHRvYCwgYHNsZWVwYCwgYHBlcmNoYW5j
ZWAsIGB0b2AsIGBkcmVhbWAg5YWxIDUg5YCLIHRva2Vu77yb5L2G5Y+q5pyD5pyJIDQg5YCLIHR5
cGXvvJpgdG9gLCBgc2xlZXBgLCBgcGVyY2hhbmNlYCwgYGRyZWFtYO+8jOWboOeCuuacieWFqeWA
iyBgdG9gIOaYr+mHjeikh+eahO+8m+iAjOWmguaenCBJUiDns7vntbHkuK3mnInomZXnkIYgc3Rv
cCB3b3Jkc++8jOaIkeWAkeWPr+iDveacg+aKiiB0byDoppbngrrmmK8gc3RvcCB3b3JkcyDogIzl
sIflroPnp7vpmaTvvIzmraTmmYLmnIDntYLlnKggSVIg57O757Wx5Lit6KKrIGluZGV4IOeahCB0
ZXJtIOWwseWPquacg+aciSAzIOWAi++8mmBzbGVlcGAsIGBwZXJjaGFuY2VgLCBgZHJlYW1gIOOA
gjxicj48YnI+6YKj6bq85oiR5YCR6YCa5bi45pyD5aaC5L2V5YGaIHRva2VuemlhdGlvbiDlkaLv
vJ/liJ3mraXnnIvotbfkvobvvIzkvLzkuY7lsLHmmK/nm7TmjqXph53lsI3nqbrnmb3lgZrliIbl
ibLvvIznhLblvozlj6/og73miormqJnpu57nrKbomZ/ku4DpurznmoTpg73ljrvpmaTmjonlsLHl
j6/ku6XkuobvvJ/nhLbogIznj77lr6bkuIrvvIzpgoTmmK/mnIPlh7rnj77kuIDkupvmr5TovIPl
m7Dpm6PnmoTllY/poYzvvIzlsI7oh7TmiJHlgJHlj6/og73pgoTpnIDopoHogIPmha7liKXnmoTl
m6DlrZDjgILkvovlpoLvvJo8YnI+PGJyPiogYCdgIOWcqOiLseaWh+S4re+8jOWPr+S7peS7o+ih
qOaJgOacieagvOmXnOS/guOAgeS5n+WPr+S7peS7o+ihqOe4ruWvq+OAgjxicj4qIOS4jeWQjOiq
nuiogOacieiHquW3seeahOiqnuiogOeJueW+teOAgjxicj4qIOeJueWumumgmOWfn+WPr+iDveac
ieiHquW3seeJueauiueahCB0eXBl44CCPGJyPiogYC1gIOWcqOiLseaWh+S4reS5n+WtmOWcqOWk
mueoruS4jeWQjOeUqOazleOAgjxicj4qIOWkmuWAi+Wtl+e1hOaIkOeahOipnuW9meeEoeazleWW
rue0lOeUqOepuueZveWIh+WJsuS+huiZleeQhuOAgjxicj48YnI+5Lul5LiL5pyD5YiG5Yil6Yed
5bCN6YCZ5Lqb54uA5rOB5YGa6bue57Ch5Zau55qE5o+P6L+w44CCPGJyPjxicj4jIyMgMS4xLiBB
cG9zdHJvcGhlICgnKTxicj48YnI+YCdgIOmAmeWAi+espuiZn+WcqOiLseaWh+S4reacieWkmueo
rueUqOazle+8jOS+i+WmguS7peS4i+mAmeWAi+WPpeWtkO+8mjxicj48YnI+YGBgPGJyPk1yLiBP
J05laWxsCnRoaW5rcyB0aGF0IHRoZSBib3lzJyBzdG9yaWVzIGFib3V0IENoaWxlJ3MgY2FwaXRh
bCBhcmVuJ3QgYW11c2luZy4KPGJyPmBgYDxicj48YnI+6YCZ6KOh5ZCM5pmC5Ye654++5LqG5LiJ
56iu5LiN5ZCM55qE55So5rOV77yaPGJyPjxicj4qIE8nTmVpbGwg5Lit55qEIGAnYCDmmK/lkI3l
rZfnmoTkuIDpg6jliIbjgII8YnI+KiBib3lzJyDlkowgQ2hpbGUncyDkuK3nmoQgYCdgIOaYr+aJ
gOacieagvOOAgjxicj4qIGFyZW4ndCDkuK3nmoQgYCdgIOaYr+e4ruWvq+OAgjxicj48YnI+5q2k
5pmC6Yed5bCNIGAnYCDlgZrnmoTomZXnkIbnmoTkuI3lkIzvvIzlj6/og73mnIPlsI7oh7Tlvozn
uowgSVIg57O757Wx55qEIG1hdGNoaW5nIOWPl+WIsOW9semfv+OAguS+i+WmguWmguaenOaIkeWA
keWwhyBgJ2Ag5ouG6ZaL55qE6Kmx77yMYGFyZW4ndGAg6KKr5ouG5oiQIGBhcmVuYCDot58gYHRg
IOeci+i1t+S+huWwseacg+W+iOWlh+aAquOAguWQjOaZgu+8jGBPJ05laWxsYCDooqvmi4bmiJAg
YE9gIOi3nyBgTmVpbGxgIOacg+WwjuiHtOaQnOWwi+aZguS9v+eUqCBgbyduZWlsYCDmnIPnhKHm
s5UgbWF0Y2gg5Yiw55u45oeJ55qEIHRva2Vu44CCPGJyPjxicj4jIyMgMS4yLiDoqp7oqIDnibnl
vrU8YnI+PGJyPnRva2VuaXphdGlvbiDnmoTooYzngrrlvoDlvoDot5/oqp7oqIDmnInlvojlpKfn
moTpl5zkv4LvvIzkuI3lkIzoqp7oqIDnmoQgdG9rZW5pemF0aW9uIOS9nOazleacg+acieigu+Wk
p+eahOS4jeWQjOOAguS+i+WmguS4reaWh+WSjOaXpeaWh+WwseaykuacieepuueZveWIhumalOea
hOeJueaAp++8jOavj+WAi+Wtl+mDveaYr+mAo+WcqOS4gOi1t+eahOOAgjxicj48YnI+IyMjIDEu
My4g6aCY5Z+f54m55b61PGJyPjxicj7lnKjnibnlrprpoJjln5/vvIzmnInlj6/og73mnInpoJjl
n5/lhaflsIjlsaznmoToqZ7lvZnpnIDopoHog73lpKDooqvorZjliKXmiJAgdGVybe+8jOS+i+Wm
giBgQysrYCDlkowgYEMjYOOAgeaIluiAhemjm+apn+Wei+iZnyBgQi01MmDvvIjmiLDnlaXovZ/n
grjmqZ/vvInjgILlj6blpJbntrLpmpvntrLot6/kuK3nmoQgVVJM77yIaHR0cHM6Ly94eHjvvInj
gIFlLW1haWzvvIhhYmNAYWJjLmNvbe+8ieOAgUlQIOS9jeWdgO+8iGEuYi5jLmTvvInnrYnnrYnl
j6/og73kuZ/mnIPpnIDopoHog73lpKDooqvoppbngrrmmK/njajnq4vnmoQgdGVybeOAgjxicj48
YnI+IyMjIDEuNC4g6YCj5a2X56ym6JmfIEh5cGhlbmF0aW9uICgtKTxicj48YnI+6Iux5paH5Lit
6YCj5a2X56ym6Jmf55qE6JmV55CG5Y+v6IO95pyD5b6I6KSH6Zuc77yM5pu45Lit6IiJ5Ye65bm+
56iu6YCj5a2X56ym6Jmf55qE55So5rOV77yaPGJyPjxicj4qIOWIhumalOWWruWtl+S5i+mWk+ea
hOavjemfs++8jOWDj+aYryBgY28tZWR1Y2F0aW9uYOOAgumAmeWAi+S+i+WtkOWvpumam+S4iuWu
g+S7o+ihqOeahOaYryBgY29lZHVjYWlvbmDjgII8YnI+KiDlsIflpJrlgIvlkI3oqZ7pgKPmjqXm
iJDkuIDlgIvmlrDnmoTlkI3nqLHvvIzkvovlpoIgYEhld2xldHQtUGFja2FyZGDjgII8YnI+KiDl
pJrlgIvoqZ7oqp7nmoTntYTmiJDntZDmnpzvvIzkvovlpoIgYHRoZSBob2xkLWhpbS1iYWNrLWFu
ZC1kcmFnLWhpbS1hd2F5IG1hbmV1dmVyYOOAgumAmeWAi+S+i+WtkOaHieipsuimgeiiq+aLhuin
o+aIkOWQhOiHqueNqOeri+eahCB0ZXJt44CCPGJyPjxicj4jIyMgMS41LiDnibnmroroqZ7lvZk8
YnI+PGJyPumAmemhnueahOizh+ioiuWFtuWvpuaIkeimuuW+l+S5n+WPr+S7peeul+aYr+mgmOWf
n+eJueW+teeahOS4gOeoruOAguS+i+WmguabuOS4reiIieeahOS4gOWAi+S+i+WtkOaYryBgTmV3
IFlvcmsgVW5pdmVyc2l0eWDvvIjntJDntITlpKflrbjvvInot58gYFlvcmsgVW5pdmVyc2l0eWDv
vIjntITlhYvlpKflrbjvvInjgILlpoLmnpzku5blgJHmspLmnInooqvnjajnq4voppbngrogdGVy
be+8jOiAjOaYr+e0lOeyueS+neaTmuepuueZveaLhumWi+eahOipse+8jOmCo+m6vOaQnOWwiyBg
WW9yayBVbml2ZXJzaXR5YCDmmYLlsLHmnIMgbWF0Y2gg5YiwIGBOZXcgWW9yayBVbml2ZXJzaXR5
YCDkuobvvIzkvYbpgJnpoa/nhLbkuKbpnZ7mmK/kvb/nlKjogIXmg7Pmib7nmoTmnbHopb/jgII8
YnI+PGJyPiMjIDIuIFN0b3AgV29yZHM8YnI+PGJyPuWBnOeUqOipnu+8iFN0b3AgV29yZHPvvInn
moTnm67nmoTvvIzmmK/ljrvpmaTlh7rnj77poLvnjofpgY7pq5jjgIHkvYblj4jot5/mlofku7bm
nKzouqvpl5zkv4LkuI3lpKfnmoToqZ7lvZnjgILkuI3pgY7nlLHmlrzljrvpmaTlgZznlKjoqZ7n
moTooYzngrrvvIzmnInmmYLmnIPlsI7oh7TmhI/nvqnnmoTmtYHlpLHvvIzmiYDku6Xnj77ku6Pn
moQgSVIg57O757Wx5q+U6LyD5pyD6ICD5oWu5LiN5Y676Zmk5YGc55So6Kme77yM5pS554K65bCH
5YGc55So6Kme6LOm5LqI6LyD5L2O55qE5qyK6YeN77yM5L2/5YW25b2x6Z+/6K6K5bCP44CC6IiJ
5L6L5L6G6Kqq77yMYGZsaWdodHMgdG8gTG9uZG9uYCDlpoLmnpzmioogYHRvYCDntabljrvpmaTk
uobvvIxgZmxpZ2h0cyB0byBMb25kb25gIOeahOaEj+e+qeWwsea1geWkseS6huOAgjxicj48YnI+
LS0tLTxicj48YnI+5YW25a+m6YKE5pyJ5YWp5YCL5bCP56+A77yM5LiN6YGO562J5pyJ56m65pmC
5YaN6KOc562G6KiY5LqGLi4u44CCPGJyPuaYr+iqquaQnOWwi+aYr+WAi+WcqOePvuS7o+mdnuW4
uOW4uOimi+eahOihjOeCuu+8jOS9huacgOi/keeahOeglOeptue4veimuuW+l+mAmeWAi+mgmOWf
n+aciem7nuWGt+mWgO+8nzxicj48YnI+W3Rva2VuaXphdGlvbl06IGh0dHBzOi8vbmxwLnN0YW5m
b3JkLmVkdS9JUi1ib29rL2h0bWwvaHRtbGVkaXRpb24vdG9rZW5pemF0aW9uLTEuaHRtbDxicj5b
c3RvcFdvcmRzXTogaHR0cHM6Ly9ubHAuc3RhbmZvcmQuZWR1L0lSLWJvb2svaHRtbC9odG1sZWRp
dGlvbi9kcm9wcGluZy1jb21tb24tdGVybXMtc3RvcC13b3Jkcy0xLmh0bWw8YnI+W25vcm1hbGl6
YXRpb25dOiBodHRwczovL25scC5zdGFuZm9yZC5lZHUvSVItYm9vay9odG1sL2h0bWxlZGl0aW9u
L25vcm1hbGl6YXRpb24tZXF1aXZhbGVuY2UtY2xhc3Npbmctb2YtdGVybXMtMS5odG1sPGJyPltz
dGVtbWluZ106IGh0dHBzOi8vbmxwLnN0YW5mb3JkLmVkdS9JUi1ib29rL2h0bWwvaHRtbGVkaXRp
b24vc3RlbW1pbmctYW5kLWxlbW1hdGl6YXRpb24tMS5odG1sPGJyPjwvcD48cD48YnI+PC9wPg==&quot;&gt;​&lt;/div&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimwayne.blogspot.com/feeds/2208057883847236711/comments/default' title='張貼留言'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/6819483672105967231/2208057883847236711' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6819483672105967231/posts/default/2208057883847236711'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6819483672105967231/posts/default/2208057883847236711'/><link rel='alternate' type='text/html' href='http://jimwayne.blogspot.com/2022/08/blog-post.html' title='[筆記] 詞彙的建立'/><author><name>Wayne Yeh</name><uri>http://www.blogger.com/profile/02148905864753873930</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry></feed>