<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>ike‘s blog</title><description>ike‘s blog</description><link>https://blog.ikeno.top/</link><language>zh_CN</language><item><title>mac软件推荐</title><link>https://blog.ikeno.top/posts/macsoftware/</link><guid isPermaLink="true">https://blog.ikeno.top/posts/macsoftware/</guid><description>新mac必安装的软件推荐</description><pubDate>Fri, 13 Dec 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;2024年度最香产品：MAC MINI M4 正式登场！
经过几天的抢购，我终于在京东买到了 MAC MINI M4，发货等了一个月。到手后折腾了几天，虽然新鲜劲儿过了，但作为办公电脑，它的表现还是不错的。
回想上次用 Mac Mini 是在 M1 刚发售时，买来玩了几个月，最后发现自己并没有太大需求，于是出掉了。这次我以 3500 元的价格再买了一台 M4，希望它能作为我的工作电脑。&lt;/p&gt;
&lt;p&gt;废话不多说，下面是我这边记录的一些已安装的软件，都是一些必备的好用软件：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Microsoft Office/Excel/PPT 三件套
办公必备，没啥好介绍的，工作中一定会用到，比 WPS 好用多了。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;BetterDisplay
我用的是 4K 显示器，分辨率适配后字体变得非常小，图标和软件也显得很小。为了不降低分辨率，我使用了 BetterDisplay，这个软件不仅能调整分辨率，还能修改亮度，算是一个不错的解决方案。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;超级右键
Mac 里的复制、粘贴、删除等操作相比 Windows 确实不那么顺手。超级右键可以扩展右键功能，例如直接快速打开常用目录、删除文件、新建文件、剪切粘贴等，使用起来非常便利。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;VSCode
宇宙第一 IDE，不解释。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Termius
用来登录服务器。Windows 上一般使用 MobaXterm，因此在 Mac 上也想找一个好用的终端，最终发现 Termius 非常好用。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;有道翻译
主要用于划词翻译，记单词、翻译句子等，算是学习英语的工具。不过有时复制时容易误点。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Chrome
习惯了跨系统的多端浏览器同步书签，所以还是选择 Chrome 浏览器。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;CotEditor
我想找一个类似 Notepad++ 的轻量级文本编辑器。试了几个 Atom 之类的，感觉都太繁琐。后来在商店里发现 CotEditor，确实不错，功能够用。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Pixea
比系统自带的图片浏览器好用，简洁方便。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;ClashX
懂的都懂，实用的梯子。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Keeweb
密码管理软件，使用体验还不错。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;希望这些软件能对使用 Mac Mini 的朋友们有所帮助！&lt;/p&gt;
</content:encoded></item><item><title>用Siri来控制Windows电脑开机关机吧</title><link>https://blog.ikeno.top/posts/smartplug/</link><guid isPermaLink="true">https://blog.ikeno.top/posts/smartplug/</guid><description>用siri来控制windows电脑开机和关机</description><pubDate>Mon, 26 Feb 2024 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;前因&lt;/h2&gt;
&lt;p&gt;自从手机从iphone换成Android后，咱的13香就没啥用了，只能放桌子上当时钟，感觉有点浪费啊，所以研究了下苹果的HomeKit，用旧手机当智能中控岂不更好！网上查了下，貌似也蛮简单的。开搞开搞！&lt;/p&gt;
&lt;h2&gt;准备&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;买一个能够连接homekit的智能插座，网上蛮多的，一个插座也就20~50不等，我买的是meross的，好像说是外贸货，反正到手是日本那边的，还行。买来后插上电，用手机先连接2.4g的wifi后扫描下二维码后按提示就可以让插座联网了，加入后只要网络正常，就可以用手机控制插座开关了。&lt;/li&gt;
&lt;li&gt;设置电脑bios，通电后自动启动机器。我的外星人台式机是开机后按F2就可以进入bios，然后找到高级选项--电源选项里有个开机改成通电就Power On就好了。&lt;/li&gt;
&lt;li&gt;给Windows账号加上密码，就是登陆要输入密码才能登陆，这样可以让其他手机或者服务器进行ssh登陆。&lt;/li&gt;
&lt;li&gt;电脑安装OpenSSH。打开Powershell，然后&lt;code&gt;Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0&lt;/code&gt;命令，出现进度条后等待安装完毕。然后net start sshd即可启动ssh服务。当然别忘了在防火墙的入站规则里加上TCP的22端口规则允许。还有就是在Windows的服务里找到OpenSSH然后手动改成自动，这样就开机自动启动啦。然后还有就是要把电脑的IP改成静态的，不能DHCP动态分配。最后可以尝试在Powershell里&lt;code&gt;ssh 你的用户名@你的服务器IP&lt;/code&gt;，然后输入你的密码就可以正常SSH登陆啦！&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;嘿 Siri&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;准备工作都弄完后，现在就轮到iphone了，先在家庭里创建2个场景，一个是关电脑，就是控制插座关闭电源，另一个的开电脑，控制插座开启电源。弄完后点击试试，看看插座是不是可以正常点一下就开启和关闭了。&lt;/li&gt;
&lt;li&gt;打开苹果的快捷指令，创建一个新的快捷指令，名称叫&lt;code&gt;打开电脑&lt;/code&gt;,里面只需要添加&lt;code&gt;开电脑&lt;/code&gt;的场景即可。这样只需要喊&quot;嘿！sir，打开电脑&quot; ，就可以开启插座然后通电开机啦。&lt;/li&gt;
&lt;li&gt;再创建一个新的快捷指令，名称叫&lt;code&gt;关闭电脑&lt;/code&gt;,然后添加&lt;code&gt;通过SSH发送命令&lt;/code&gt;,把服务器的ip、22端口号、用户名、密码都填写，脚本的话填写&lt;code&gt;shutdown -s -t 1&lt;/code&gt;。然后再添加等待60秒，最后再添加&lt;code&gt;关电脑&lt;/code&gt;的场景即可。这样通过siri喊&quot;嘿！sir，关闭电脑&quot;，就可以实现快速关机了！&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;语音控制&lt;/h2&gt;
&lt;p&gt;上面通过嘿siri来控制快捷指令来运行关机貌似会有点小问题，因为要等待60s后才会执行下一步，siri好像默认不会等那么久，所以换一种方式，用iphone的&lt;code&gt;辅助功能&lt;/code&gt;。里面有个语音控制，开启后新增语音&lt;code&gt;关闭电脑&lt;/code&gt;然后运行对应的快捷指令即可，这下连siri都不用了，直接说关闭电脑就好了！&lt;/p&gt;
&lt;h2&gt;控制中台&lt;/h2&gt;
&lt;p&gt;iphone的家庭里其实还有一个小功能，就是开启引导界面，然后输入密码设置，你会发现界面会钉在家庭的控制页面，且不会切换，这样就相当于把手机作为了中台，只需要连接电源然后挂在墙上或者桌子上，就可以当一个完美的控制中台啦~&lt;/p&gt;
&lt;h2&gt;最后&lt;/h2&gt;
&lt;p&gt;家里马上也要开始装修弄家具了，之前也有了解一些智能家居，但感觉都不是很实用，大部分也就是一些回家后自动开灯，开电器。起床自动打开窗帘和播放音乐之类的，为了这些多花好几万感觉不是很划算，那还不如就简单点用智能插座加场景设置和快捷指令来控制，这样成本低，自己diy，体验也不错。虽然有一些局限性，但本来就图新鲜，好玩就行了~&lt;/p&gt;
</content:encoded></item><item><title>clickhouse的一些优化建议和工具</title><link>https://blog.ikeno.top/posts/clickhouse_cloki_tip/</link><guid isPermaLink="true">https://blog.ikeno.top/posts/clickhouse_cloki_tip/</guid><description>clickhouse踩坑</description><pubDate>Tue, 09 Jan 2024 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;起因&lt;/h2&gt;
&lt;p&gt;自从用cloki+clickhouse来替代日志系统后真是一波三折，问题不断Orz。
只能感叹不亏是小众的开源工具，还是一堆坑，下面记一下踩到的一些坑和对clickhouse的优化。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Q&lt;/strong&gt;：主查询最后的查询会进行全表查询，但是left join的time_series_dist表巨大，有几百G，几十亿条数据，导致每次查询都得全部读取到内存里，一次查询占用80G内存，查询5分钟还查不出结果！&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;# 查询语句
WITH sel_a AS
    (
        SELECT
            samples.string AS string,
            samples.fingerprint AS fingerprint,
            samples.timestamp_ns AS timestamp_ns
        FROM cloki.samples_v3_dist AS samples
        WHERE ((samples.timestamp_ns &amp;gt;= 1704440225031000000) AND (samples.timestamp_ns &amp;lt;= 1704440525031000000)) AND (samples.fingerprint IN (
            SELECT sel_1.fingerprint
            FROM
            (
                SELECT fingerprint
                FROM cloki.time_series_gin
                WHERE (key = &apos;app&apos;) AND (val = &apos;myapp&apos;)
            ) AS sel_1
            ANY INNER JOIN
            (
                SELECT fingerprint
                FROM cloki.time_series_gin
                WHERE (key = &apos;cluster&apos;) AND (val = &apos;test-prod&apos;)
            ) AS sel_2 ON sel_1.fingerprint = sel_2.fingerprint
        ))
        ORDER BY timestamp_ns DESC
        LIMIT 100
    )
SELECT
    JSONExtractKeysAndValues(time_series.labels, &apos;String&apos;) AS labels,
    sel_a.*
FROM sel_a
ANY LEFT JOIN cloki.time_series_dist AS time_series ON sel_a.fingerprint = time_series.fingerprint
ORDER BY
    labels DESC,
    timestamp_ns DESC
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;A&lt;/strong&gt;: 估计是开源cloki根据的bug，而且和orderby也有区别，调查了蛮多优化建议，其中比较有用的是&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;让表的orderby和查询的orderby一致，这样可以减少最后排序时间，所以我把ORDER BY fingerprint改成ORDER BY (fingerprint, labels)。&lt;/li&gt;
&lt;li&gt;left join后的表尽量要是小表，不过这里是固定了所以没法优化，所以只能减少表的大小，还好这个表只是存储标签，把TTL（生命周期）改成1天后就好多了。&lt;/li&gt;
&lt;li&gt;网上还有说让经常查询的key作为主键分区，相当于做了索引，也可以加快查询速度和效率，所以我PARTITION BY date改成了PARTITION BY fingerprint，但其实是有问题的！
因为PARTITION BY是分区，如果是date的话，一天一个分区，这样查询如果是跨天查询就很快，如果改成fingerprint会变成每一个数据一个分区！CK集群直接报错Too many parts (100018) in all partitions in total，分区中存在过多的数据分片了，赶紧删了重新改回来了date，没有完全懂的东西还是不要乱动的好。当然可以把分区时间改成半天，也有一定的效果。&lt;pre&gt;&lt;code&gt; CREATE TABLE cloki.time_series
 (
     `date` Date,
     `fingerprint` UInt64,
     `labels` String,
     `name` String,
   INDEX idx_fingerprint_labels (fingerprint) TYPE minmax GRANULARITY 8192
 )
 ENGINE = ReplicatedReplacingMergeTree(&apos;/clickhouse/tables/time_series/{shard}&apos;, &apos;{replica}&apos;, date)
 PARTITION BY date
 ORDER BY (fingerprint, labels)
 TTL date + toIntervalDay(1)
 SETTINGS index_granularity = 8192, ttl_only_drop_parts = 1, merge_with_ttl_timeout = 3600
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;R&lt;/strong&gt;：查询问题解决后，基本上每次查询都可以在10s内完成，而且内存也不需要太大了，clickhouse里也有一些users.xml的default标签参数可以优化查询啥的，例如&lt;br /&gt;
&lt;strong&gt;max_threads&lt;/strong&gt; 这个值不要太高，一般是默认就好了，并不是说线程越高越好，越高内存占用也越大&lt;br /&gt;
&lt;strong&gt;distributed_product_mode&lt;/strong&gt;  ReplicatedReplacingMergeTree+Distributed的分布式表一定要弄为global，如果只是MergeTree+Distributed可以local&lt;br /&gt;
&lt;strong&gt;group_by_overflow_mode&lt;/strong&gt;  默认是查询失败就throw丢弃，也可以改成查询超出内存后返回部分结果，一般不需要改&lt;br /&gt;
&lt;strong&gt;max_memory_usage&lt;/strong&gt; 单个查询最大多少内存，默认是10g，当然也可以改大，我是改成90%的内存大小&lt;br /&gt;
&lt;strong&gt;max_bytes_before_external_group_by&lt;/strong&gt; groupby超了多少内存后就写入到磁盘里，防止内存占用过多，虽然会影响效率，一般是改成max_memory_usage的一半&lt;br /&gt;
&lt;strong&gt;max_bytes_before_external_sort&lt;/strong&gt; orderby超了多少内存后就写入到磁盘里，防止内存占用过多，虽然会影响效率，一般是改成max_memory_usage的一半&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Q&lt;/strong&gt;：clickhouse其实更适合一次性插入几百万的数据，而不是短时间内大量一条一条的数据插入，就好像现在cloki每一条日志都是一个insert请求，导致大量的单条插入给到clickhouse，然后ck集群的同步会扛不住，会报错merge速度比不上insert速度，然后zookeeper或clickhouse-keeper的负载非常高，触发表readonly的保护机制，只要没merge完就不给写入，因为没有缓存的中间件，一堆日志写入失败丢失。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;A&lt;/strong&gt;：说白了就是clickhouse的使用方法不对，如果是数据量不大还好，官方说只要超过500k每秒的插入，基本上就会导致同步不过来表readonly无法写入。所以网上有一些解决方法：&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;用buffer表来做缓冲，&lt;code&gt;Buffer(database, table, num_layers, min_time, max_time, min_rows, max_rows, min_bytes, max_bytes [,flush_time [,flush_rows [,flush_bytes]]])&lt;/code&gt;， 用下面的例子里的参数解释就是，如果满足最大max的100s或者100w行或者100m的数据其中一个条件，就触发缓存写入到目标表，又或者同时满足所有min的条件过了10s且1w行且10m的数据就会触发写入数据到目标表。不过这个方法官方也说了其实不怎么适用，因为本身clickhouse还是不适合用于短时间大量单条数据的插入，更适合单条插入大量数据的场景，官方建议是每秒不超过1条插入语句。&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;  CREATE TABLE cloki.samples_v3_buffer
(
    `fingerprint` UInt64,
    `timestamp_ns` Int64 CODEC(DoubleDelta),
    `value` Float64 CODEC(Gorilla),
    `string` String CODEC(ZSTD(5))
)
ENGINE = Buffer(&apos;cloki&apos;, &apos;samples_v3&apos;, 16, 10, 100, 10000, 1000000, 10000000, 100000000)
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;buffer表不管用，那就只能想想其他办法，还好这个问题也有解决，&lt;a href=&quot;https://github.com/nikepan/clickhouse-bulk&quot;&gt;clickhouse-bulk&lt;/a&gt; 是第三方的开源小工具，主要目的就是把小插入变成大插入，只需要设置好配置文件&lt;code&gt;nohup ./clickhouse-bulk -config config.json &amp;gt;/dev/null 2&amp;gt;&amp;amp;1 &amp;amp;&lt;/code&gt;启动即可，所有的小插入会先缓存然后满足配置条件后写入到clickhouse集群里，而且写入失败会生成dump文件，重新尝试插入，相当于缓存了。&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;    {
    &quot;listen&quot;: &quot;:8124&quot;,   #启动的端口，clickhouse是8123 ，这个是8124，小心别漏了分号
    &quot;flush_count&quot;: 1000000,  #缓存多少行后写入，我这边指定的是100w行写入一次
    &quot;flush_interval&quot;: 5000,  #缓存多少秒后写入，我这边指定5秒后写入
    &quot;clean_interval&quot;: 0,     #清理内部表的频率，例如插入到不同的临时表，或作为解决query_id等问题的方法，单位：毫秒
    &quot;remove_query_id&quot;: true,  #有些驱动程序发送的query_id会阻止批量插入
    &quot;dump_check_interval&quot;: 60, #尝试发送转储文件的间隔（秒）；-1表示禁用，就是重新尝试插入dump文件的时间
    &quot;debug&quot;: false,  # 记录传入的请求日志
    &quot;log_queries&quot;: true, 
    &quot;dump_dir&quot;: &quot;dumps&quot;,   # 转储未发送的数据的目录（如果ClickHouse出错）
    &quot;clickhouse&quot;: {
      &quot;down_timeout&quot;: 60,  # 服务器宕机时等待的时间（秒）
      &quot;connect_timeout&quot;: 10,  # 等待服务器连接的时间（秒）
      &quot;tls_server_name&quot;: &quot;&quot;, # 覆盖用于证书验证的TLS serverName（例如，如果在多个节点上共享相同的&quot;cluster&quot;证书）
      &quot;insecure_tls_skip_verify&quot;: false, # 证书验证
      &quot;servers&quot;: [
        &quot;http://127.0.0.1:8123&quot;
      ]
    },
    &quot;use_tls&quot;: false,
    &quot;tls_cert_file&quot;: &quot;&quot;,
    &quot;tls_key_file&quot;: &quot;&quot;
  }
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;最后&lt;/h1&gt;
&lt;p&gt;到目前为止大部分问题解决了，虽然还有一些小问题，但日志系统也慢慢趋于稳定，clickhouse数据库号称比传统mysql数据库快一万倍，但坑还是蛮多的，目前国内用的其实不多，但以后估计会变成香饽饽吧。&lt;/p&gt;
</content:encoded></item><item><title>2023sumppary</title><link>https://blog.ikeno.top/posts/2023sumppary/</link><guid isPermaLink="true">https://blog.ikeno.top/posts/2023sumppary/</guid><description>随便写一写2023吧</description><pubDate>Thu, 28 Dec 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;迟来的2023年终总结：&lt;/p&gt;
&lt;p&gt;&amp;lt;audio controls&amp;gt;
&amp;lt;source src=&quot;http://music.163.com/song/media/outer/url?id=409872504.mp3&quot; type=&quot;audio/mpeg&quot;&amp;gt;
&amp;lt;/audio&amp;gt;&lt;/p&gt;
&lt;h2&gt;关于工作&lt;/h2&gt;
&lt;p&gt;工作今年算运气不错吧，去年年底被裁员后，本来以为以咱的垃圾学历和技术，再加上互联网寒冬，估计要失业蛮久的。但运气看来还好，不到一个月就找到了新公司，薪资变化不大，不加班，标准的975，比之前公司感觉好了很多。好像我找工作一直都运气蛮好的，平均面试3次基本上都有一次可以过，毕业到现在也有8年多了吧，从上海到深圳，换了也有4家公司了，这是第五家了，平均2年一家公司，然后有一家是被裁员，也算是什么都经历过了。也不清楚程序员这行能干多久，有存款才有勇气。&lt;/p&gt;
&lt;h2&gt;关于开发&lt;/h2&gt;
&lt;p&gt;今年工作有了时间，就想着完成一些以前就想做的开发项目了，博客也算之一，所以就有了这个博客，虽然是用的开源的框架，但也弄了些自己的东西，例如分享啊，数据统计啊，评论，域名，CDN什么的，虽然没什么流量，但在网络上有了自己的一个网站还是蛮开心的。&lt;/p&gt;
&lt;p&gt;除了博客外因为今年chatgpt的普及，很多以前很费时间的技术都可以简单的开发了，例如前端页面之类的，虽然技术力有限，但还是弄了一个chrome插件游戏来玩玩，还是蛮有趣的，以后有好点子说不定还可以继续开发一些好玩的浏览器插件。&lt;/p&gt;
&lt;h2&gt;关于旅游&lt;/h2&gt;
&lt;p&gt;今年总算可以正常出国旅游了，5月去了上海玩3天，10月去了东京-京都-大阪玩了8天。抛开工作烦恼和国内的压力，能到国外享受旅游真的很放松，看什么都很新鲜。就是国庆旅游花费贵了些，明年应该会办理三年多次的签证，以后淡季去玩应该会便宜很多。希望明年可以有另一半可以一起旅游hh&lt;/p&gt;
&lt;h2&gt;关于游戏&lt;/h2&gt;
&lt;p&gt;看了下PlayStation的游玩记录，今年竟然都没有超过10小时的ps5游戏时间，甚至都弄不出年度游玩报告，看来今年大部分时间都是在玩电脑和手游了，现在独占游戏越来越少，steam价格也便宜，还可以打mod。看来以后主机的使用率应该还会变低。明年还有老头环DLC、碧蓝幻想relink、黑神话，还是蛮期待的。&lt;/p&gt;
&lt;h2&gt;最后&lt;/h2&gt;
&lt;p&gt;感觉也没啥总结的了，希望明年越来越好吧，暴富！脱单！&lt;/p&gt;
</content:encoded></item><item><title>记录一下我的第一个网页游戏Mazeball</title><link>https://blog.ikeno.top/posts/mazeball_game/</link><guid isPermaLink="true">https://blog.ikeno.top/posts/mazeball_game/</guid><description>mazeball</description><pubDate>Mon, 27 Nov 2023 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;游戏链接：&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://ball.ikeno.top&quot;&gt;MazeBall&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;截图：&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://github.com/Tokoy/MazeBall/blob/main/img/screenshot.png&quot; alt=&quot;snapshot&quot; /&gt;{width=200 height=200 }&lt;/p&gt;
&lt;h2&gt;游戏规则：&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;鼠标左键控制小球左右移动，点击页面，小球会根据鼠标的相对位置来移动。 如果鼠标在小球的左边则左移，鼠标在小球的右边则右移，其他则不动。&lt;/li&gt;
&lt;li&gt;鼠标右键控制小球向下移动。&lt;/li&gt;
&lt;li&gt;键盘wsad也可以控制上下左右移动。&lt;/li&gt;
&lt;li&gt;倒计时归零则游戏结束。&lt;/li&gt;
&lt;li&gt;碰到红色障碍物则游戏结束。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;    游戏的玩法其实就是在限定的时间内看看能到达第几关，因为每通关一关只会增加5秒倒计时，但层数会增加，所以到越到后面迷宫越长，操作的时间是不够的。
而且很容易出现误触导致失败，所以还是得小心控制小球移动，毕竟是突发奇想的一个简单益智类小游戏,可玩性不高，不过还是挺有意思的~&lt;/p&gt;
&lt;p&gt;    我也尝试上架了下chrome的插件商店，虽然没什么人下载，不过也算是熟悉了下发布插件的流程啦，只需要开通账号后绑定下信用卡（我用的招商visa卡），
然后一次性支付5美元就可以上传插件了，填写一些应用描述和截图提交审核即可，还是蛮简单的。&lt;/p&gt;
</content:encoded></item><item><title>如何使用Clickhouse的索引</title><link>https://blog.ikeno.top/posts/clickhouse_index/</link><guid isPermaLink="true">https://blog.ikeno.top/posts/clickhouse_index/</guid><description>用clickhouse的索引来提高检索效率</description><pubDate>Wed, 18 Oct 2023 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;简介&lt;/h2&gt;
&lt;p&gt;    影响ClickHouse查询性能的因素很多。在大多数情况下，关键因素是ClickHouse在计算查询WHERE子句条件时是否可以使用主键。因此，选择适用于最常见查询模式的主键对于有效的表设计至关重要。
    用户通常依赖ClickHouse获取时间序列类型的数据，但他们通常希望根据其他业务维度(如客户id、网站URL或产品编号)分析相同的数据。在这种情况下，查询性能可能会相当差，因为可能需要对每个列值进行完整扫描才能应用WHERE子句条件。虽然ClickHouse在这些情况下仍然相对较快，但评估数百万或数十亿个单独的值将导致“非索引”查询的执行速度比基于主键的查询慢得多。
    ClickHouse提供了一种不同类型的索引，在特定情况下可以显著提高查询速度。这些结构被标记为“跳过(Skip)”索引，因为它们使ClickHouse能够跳过读取保证没有匹配值的重要数据块。&lt;/p&gt;
&lt;h2&gt;介绍&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;Clickhouse索引的特点为: 排序索引+稀疏索引+列式存储, 因此相应的Clickhouse最合适的场景就是基于排序字段的范围过滤后的聚合查询。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;因为排序索引, 所有基于排序字段的查询会明显由于MR类型计算, 否则Hive/Spark这类动态资源的更优
由于稀疏索引, 点查询的效率可能没有KV型数据库高, 因此适合相对大范围的过滤条件
因为列式存储, 数据压缩率高, 对应做聚合查询效率也会更高.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/blockquote&gt;
&lt;h2&gt;实践&lt;/h2&gt;
&lt;p&gt;因为表索引不好在创建表后再进行创建，所以最好在创建表的时候就计划好并创建，例如cloki的表主要是samples_v3和time_series_gin表是用作主要查询。&lt;br /&gt;
对于cloki.time_series_gin表：&lt;br /&gt;
对key列进行索引：&lt;strong&gt;ALTER TABLE cloki.time_series_gin_ ADD INDEX idx_key(key) TYPE minmax GRANULARITY 8192;&lt;/strong&gt;&lt;br /&gt;
对val列进行索引：&lt;strong&gt;ALTER TABLE cloki.time_series_gin_ ADD INDEX idx_val(val) TYPE minmax GRANULARITY 8192;&lt;/strong&gt;&lt;br /&gt;
对fingerprint列进行索引：&lt;strong&gt;ALTER TABLE cloki.time_series_gin_ ADD INDEX idx_fingerprint(fingerprint) TYPE minmax GRANULARITY 8192;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;对于cloki.samples_v3表：&lt;br /&gt;
对timestamp_ns列进行索引：&lt;strong&gt;ALTER TABLE cloki.samples_v3_ ADD INDEX idx_timestamp_ns(timestamp_ns) TYPE minmax GRANULARITY 8192;&lt;/strong&gt;&lt;br /&gt;
对fingerprint列进行索引：&lt;strong&gt;ALTER TABLE cloki.samples_v3_ ADD INDEX idx_fingerprint(fingerprint) TYPE minmax GRANULARITY 8192;&lt;/strong&gt;&lt;br /&gt;
对string列进行索引：&lt;strong&gt;ALTER TABLE cloki.samples_v3_ ADD INDEX idx_string(string) TYPE minmax GRANULARITY 8192;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;如果需要对已有的数据也进行索引，需要 &lt;strong&gt;ALTER TABLE cloki.samples_v3_ MATERIALIZE INDEX idx_timestamp_ns;&lt;/strong&gt; 数据量较大，会非常慢。&lt;/p&gt;
&lt;h2&gt;参考&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://clickhouse.com/docs/en/optimize/skipping-indexes&quot;&gt;官方文档&lt;/a&gt;&lt;br /&gt;
&lt;a href=&quot;https://saintbacchus.github.io/2021/08/15/%E6%B7%B1%E5%85%A5%E6%B5%85%E5%87%BAClickhouse-%E7%B4%A2%E5%BC%95%E7%BB%93%E6%9E%84%E8%AE%BE%E8%AE%A1/&quot;&gt;深入浅出clickhouse-index&lt;/a&gt;&lt;/p&gt;
</content:encoded></item><item><title>如何从zookeeper切换为clickhouse—keeper</title><link>https://blog.ikeno.top/posts/clickhouse_keeper/</link><guid isPermaLink="true">https://blog.ikeno.top/posts/clickhouse_keeper/</guid><description>用clickhouse—keeper来搭建clickhouse集群</description><pubDate>Tue, 17 Oct 2023 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;简介&lt;/h2&gt;
&lt;p&gt;    clickhouse-keeper和zookeeper的功能类似，都能实现数据复制和分布式DDL查询，但也有不同之处，clickhouse分布式表使用zookeeper作为元数据的存储，客户端每次读写分布式表都会读写zookeeper，zookeeper是个小型的日志文件系统，在大范围读写时会进入只读模式。&lt;br /&gt;
    clickhouse官方为了解决这个问题，自己开发了clickhouse-keeper来代替。clickhouse-keeper用C++语言编写，在Clickhouse的21.8版本开始引入，目前22.5版的写性能和zookeeper相当，读的性能比zookeeper好，最新版本已经读写性能都超过zookeeper，更加适配大量数据的Clickhouse集群。&lt;/p&gt;
&lt;h2&gt;对比&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;zookeeper：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;使用java开发&lt;/li&gt;
&lt;li&gt;运维不便&lt;/li&gt;
&lt;li&gt;要求独立部署&lt;/li&gt;
&lt;li&gt;zxid overflow问题&lt;/li&gt;
&lt;li&gt;snapshot和log没有经过压缩&lt;/li&gt;
&lt;li&gt;不支持读的线性一致性&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;keeper：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;使用c++开发，技术栈与ck统一&lt;/li&gt;
&lt;li&gt;即可独立部署，又可集成到ck中&lt;/li&gt;
&lt;li&gt;没有zxid overflow问题&lt;/li&gt;
&lt;li&gt;读写性能更好&lt;/li&gt;
&lt;li&gt;支持对snapshot和log的压缩和校验&lt;/li&gt;
&lt;li&gt;支持读写的线性一致性&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;部署&lt;/h2&gt;
&lt;p&gt;因为当前Clickhouse集群是用zookeeper作为数据复制和分布式查询的，切换为Clickhouse-keeper需要保留现有的数据，所以需要进行数据迁移，官方提供了相关的方法：&lt;br /&gt;
1、准备 clickhouse-keeper的配置文件 (config.xml)&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;keeper_server&amp;gt;
      &amp;lt;tcp_port&amp;gt;9181&amp;lt;/tcp_port&amp;gt;
      &amp;lt;server_id&amp;gt;1&amp;lt;/server_id&amp;gt;  &amp;lt;!--第一台是1,第二台是2,第三台是3--&amp;gt;
      &amp;lt;log_storage_path&amp;gt;/var/lib/clickhouse/coordination/log&amp;lt;/log_storage_path&amp;gt;
      &amp;lt;snapshot_storage_path&amp;gt;/var/lib/clickhouse/coordination/snapshots&amp;lt;/snapshot_storage_path&amp;gt;

    &amp;lt;coordination_settings&amp;gt;
        &amp;lt;operation_timeout_ms&amp;gt;10000&amp;lt;/operation_timeout_ms&amp;gt;
        &amp;lt;session_timeout_ms&amp;gt;30000&amp;lt;/session_timeout_ms&amp;gt;
        &amp;lt;raft_logs_level&amp;gt;warning&amp;lt;/raft_logs_level&amp;gt;
    &amp;lt;/coordination_settings&amp;gt;

    &amp;lt;raft_configuration&amp;gt;
        &amp;lt;server&amp;gt;
            &amp;lt;id&amp;gt;1&amp;lt;/id&amp;gt;
            &amp;lt;hostname&amp;gt;10.0.0.1&amp;lt;/hostname&amp;gt;
            &amp;lt;port&amp;gt;9444&amp;lt;/port&amp;gt;
        &amp;lt;/server&amp;gt;
        &amp;lt;server&amp;gt;
            &amp;lt;id&amp;gt;2&amp;lt;/id&amp;gt;
            &amp;lt;hostname&amp;gt;10.0.0.2&amp;lt;/hostname&amp;gt;
            &amp;lt;port&amp;gt;9444&amp;lt;/port&amp;gt;
        &amp;lt;/server&amp;gt;
        &amp;lt;server&amp;gt;
            &amp;lt;id&amp;gt;3&amp;lt;/id&amp;gt;
            &amp;lt;hostname&amp;gt;10.0.0.3&amp;lt;/hostname&amp;gt;
            &amp;lt;port&amp;gt;9444&amp;lt;/port&amp;gt;
        &amp;lt;/server&amp;gt;
    &amp;lt;/raft_configuration&amp;gt;
&amp;lt;/keeper_server&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;2、停止所有zk节点&lt;br /&gt;
3、重启zk leader节点，并再次停止(这一步是为了让leader节点生成一份snapshot)&lt;br /&gt;
4、运行clickhouse-keeper-converter（安装Clickhouse自带命令），生成keeper的snapshot文件&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#示例命令
clickhouse-keeper-converter --zookeeper-logs-dir /var/lib/zookeeper/version-2 --zookeeper-snapshots-dir /var/lib/zookeeper/version-2 --output-dir /var/lib/clickhouse/keeper/snapshots
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;5、参考第一部修改配置，指定snapshots，启动keeper, 使其加载上一步中的snapshot。具体可以参考官方文档
6、重启clickhouse-server&lt;/p&gt;
&lt;h2&gt;参考&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://clickhouse.com/docs/zh/operations/clickhouse-keeper&quot;&gt;官方文档ZH&lt;/a&gt;&lt;br /&gt;
&lt;a href=&quot;https://clickhouse.com/docs/en/guides/sre/keeper/clickhouse-keeper&quot;&gt;官方文档EN&lt;/a&gt;&lt;/p&gt;
</content:encoded></item><item><title>cloki分布式查询和clickhouse副本存储</title><link>https://blog.ikeno.top/posts/cloki/</link><guid isPermaLink="true">https://blog.ikeno.top/posts/cloki/</guid><description>cloki和clickhouse的一些坑</description><pubDate>Mon, 04 Sep 2023 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;相关开源工具&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/ClickHouse/ClickHouse&quot;&gt;clickhouse&lt;/a&gt; 【文档：https://clickhouse.com/docs/zh】&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/metrico/qryn&quot;&gt;cloki&lt;/a&gt;  【文档：https://qryn.metrico.in/#/installation】&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;cloki&lt;/h2&gt;
&lt;p&gt;记录一些部署cloki分布式遇到的一些坑吧，cloki因为开源的，但是活跃度不高，所以蛮多坑的。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;s&gt;默认表不支持分布式&lt;/s&gt;  &lt;strong&gt;最新的版本已经支持分布式表，只需要加CLUSTER_NAME的环境变量即可自动创建分布式表&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;下面的sql仅供参考，也可以自动创建完成后&lt;code&gt;show create table your_table_name&lt;/code&gt;来查看创建表语句然后自己修改替换。&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;// NOTE: You also need to set &quot;distributed_product_mode&quot; to &quot;global&quot; in your profile.
// https://clickhouse.com/docs/en/operations/settings/settings-profiles/

CREATE TABLE cloki.samples_read on cluster ck_cluster(
    `fingerprint` UInt64, 
    `timestamp_ms` Int64, 
    `value` Float64, 
    `string` String
)
ENGINE = Merge(&apos;cloki&apos;, &apos;^(samples|samples_v2)$&apos;);

////

CREATE VIEW cloki.samples_read_v2_1 on cluster ck_cluster(
    `fingerprint` UInt64, 
    `timestamp_ns` Int64, 
    `value` Float64, 
    `string` String
) AS SELECT fingerprint, timestamp_ms * 1000000 AS timestamp_ns, value, string FROM cloki.samples_read;

////

CREATE TABLE cloki.samples_read_v2_2 on cluster ck_cluster(
    `fingerprint` UInt64, 
    `timestamp_ns` Int64, 
    `value` Float64, 
    `string` String
)
ENGINE = Merge(&apos;cloki&apos;, &apos;^(samples_read_v2_1|samples_v3)$&apos;);

////

CREATE TABLE cloki.samples_v3_ on cluster ck_cluster(
    `fingerprint` UInt64, 
    `timestamp_ns` Int64 CODEC(DoubleDelta), 
    `value` Float64 CODEC(Gorilla), 
    `string` String
)
ENGINE = ReplicatedMergeTree(&apos;/clickhouse/ck_cluster/tables/{shard}/{uuid}&apos;, &apos;{replica}&apos;) 
PARTITION BY toStartOfDay(toDateTime(timestamp_ns / 1000000000)) 
ORDER BY timestamp_ns TTL toDateTime(timestamp_ns / 1000000000) + toIntervalDay(3650) 
SETTINGS index_granularity = 8192, ttl_only_drop_parts = 1, merge_with_ttl_timeout = 3600;

CREATE TABLE cloki.samples_v3 on cluster ck_cluster(
    `fingerprint` UInt64, 
    `timestamp_ns` Int64 CODEC(DoubleDelta), 
    `value` Float64 CODEC(Gorilla), 
    `string` String
)
ENGINE = Distributed(&apos;ck_cluster&apos;, &apos;cloki&apos;, &apos;samples_v3_&apos;, fingerprint);

////

CREATE TABLE cloki.settings_ on cluster ck_cluster(
    `fingerprint` UInt64, 
    `type` String, 
    `name` String, 
    `value` String, 
    `inserted_at` DateTime64(9, &apos;UTC&apos;)
)
ENGINE = ReplicatedReplacingMergeTree(&apos;/clickhouse/ck_cluster/tables/{shard}/{uuid}&apos;, &apos;{replica}&apos;, inserted_at) 
ORDER BY fingerprint 
SETTINGS index_granularity = 8192;

CREATE TABLE cloki.settings on cluster ck_cluster(
    `fingerprint` UInt64, 
    `type` String, 
    `name` String, 
    `value` String, 
    `inserted_at` DateTime64(9, &apos;UTC&apos;)
)
ENGINE = Distributed(&apos;ck_cluster&apos;, &apos;cloki&apos;, &apos;settings_&apos;, fingerprint);

////

CREATE TABLE cloki.time_series_ on cluster ck_cluster(
    `date` Date, 
    `fingerprint` UInt64, 
    `labels` String, 
    `name` String
)
ENGINE = ReplicatedReplacingMergeTree(&apos;/clickhouse/ck_cluster/tables/{shard}/{uuid}&apos;, &apos;{replica}&apos;, date) 
PARTITION BY date 
ORDER BY fingerprint TTL date + toIntervalDay(3650) 
SETTINGS index_granularity = 8192, ttl_only_drop_parts = 1, merge_with_ttl_timeout = 3600;

CREATE TABLE cloki.time_series on cluster ck_cluster(
    `date` Date, 
    `fingerprint` UInt64, 
    `labels` String, 
    `name` String
)
ENGINE = Distributed(&apos;ck_cluster&apos;, &apos;cloki&apos;, &apos;time_series_&apos;, fingerprint);

////

CREATE TABLE cloki.time_series_gin_ on cluster ck_cluster(
    `date` Date, 
    `key` String, 
    `val` String, 
    `fingerprint` UInt64
)
ENGINE = ReplicatedReplacingMergeTree(&apos;/clickhouse/ck_cluster/tables/{shard}/{uuid}&apos;, &apos;{replica}&apos;) 
PARTITION BY date 
ORDER BY (key, val, fingerprint) TTL date + toIntervalDay(3650) 
SETTINGS index_granularity = 8192, ttl_only_drop_parts = 1, merge_with_ttl_timeout = 3600;

CREATE TABLE cloki.time_series_gin on cluster ck_cluster(
    `date` Date, 
    `key` String, 
    `val` String, 
    `fingerprint` UInt64
)
ENGINE = Distributed(&apos;ck_cluster&apos;, &apos;cloki&apos;, &apos;time_series_gin_&apos;, fingerprint);

////

CREATE MATERIALIZED VIEW cloki.time_series_gin_view TO cloki.time_series_gin (
    `date` Date, 
    `key` String, 
    `val` String, 
    `fingerprint` UInt64
) AS SELECT date, pairs.1 AS key, pairs.2 AS val, fingerprint FROM cloki.time_series ARRAY JOIN JSONExtractKeysAndValues(time_series.labels, &apos;String&apos;) AS pairs;

////

CREATE TABLE cloki.ver_ on cluster ck_cluster(
    `k` UInt64, 
    `ver` UInt64
)
ENGINE = ReplicatedReplacingMergeTree(&apos;/clickhouse/ck_cluster/tables/{shard}/{uuid}&apos;, &apos;{replica}&apos;, ver) 
ORDER BY k 
SETTINGS index_granularity = 8192;

CREATE TABLE cloki.ver on cluster ck_cluster(
    `k` UInt64, 
    `ver` UInt64
)
ENGINE = Distributed(&apos;ck_cluster&apos;, &apos;cloki&apos;, &apos;ver_&apos;, k);

////设置下日志默认保存时间，当前是7天

INSERT INTO cloki.settings (`fingerprint`, `type`, `name`, `value`, `inserted_at`) 
VALUES (990984054, &apos;rotate&apos;, &apos;v3_samples_days&apos;, &apos;7&apos;, NOW())
, (4103757074, &apos;rotate&apos;, &apos;v3_time_series_days&apos;, &apos;7&apos;, NOW())
, (cityHash64(&apos;update_v3_5&apos;), &apos;update&apos;,
     &apos;v3_1&apos;, toString(toUnixTimestamp(NOW())), NOW());

////

INSERT INTO cloki.ver (`k`, `ver`) 
VALUES (1, 10);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;然后记得设置clickhouse的distributed_product_mode配置，可以添加到users.xml里&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;profiles&amp;gt;
...
        &amp;lt;default&amp;gt;
            ...
            &amp;lt;distributed_product_mode&amp;gt;global&amp;lt;/distributed_product_mode&amp;gt;
            ...
        &amp;lt;/default&amp;gt;
...
&amp;lt;/profiles&amp;gt;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;最后重启clickhouse后检查下是否设置成功了&lt;/strong&gt;
&lt;code&gt;select * from system.settings where name like &apos;%product%&apos;&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;clickhouse&lt;/h2&gt;
&lt;p&gt;clickhouse 的分布式查询和副本配置也是需要注意&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ReplicatedMergeTree：
ReplicatedMergeTree是ClickHouse中的一种表引擎，用于在多个节点上复制和分布数据，以提供数据冗余和故障恢复能力。它使用了分片和复制的概念，并将数据按照主键范围分布在不同的节点上。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;ReplicatedMergeTree使用MergeTree的存储和索引结构，该结构适合于大规模数据的插入和查询操作。数据按照主键进行排序，并且以数据块（block）的形式存储。每个数据块可以在多个副本节点上进行复制，以提供冗余和容错能力。&lt;/p&gt;
&lt;p&gt;当数据写入ReplicatedMergeTree表时，数据会被分成多个数据块，并按照主键的顺序插入到合适的位置。数据块的数量和大小可以通过配置进行调整，以满足特定的需求。数据块在节点之间进行复制，以确保数据的冗余存储。&lt;/p&gt;
&lt;p&gt;ReplicatedMergeTree表还提供了数据合并和清理机制。ClickHouse会周期性地合并数据块，以减少存储空间的使用和提高查询性能。同时，它还支持数据的分区和副本的自动管理。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Distributed：
Distributed是ClickHouse中的另一种表引擎，用于将查询分发到多个节点上进行并行处理。它提供了分布式查询的能力，可以加速大规模数据查询操作。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Distributed表引擎通过逻辑上的表映射和查询分发来实现分布式查询。在创建Distributed表时，需要指定一个或多个表作为底层数据源。当执行查询时，ClickHouse会将查询解析为子查询，并将其分发到底层数据源上进行并行处理。最后，查询结果会被汇总到一个结果集中返回给用户。&lt;/p&gt;
&lt;p&gt;Distributed表引擎隐藏了分布式查询的复杂性，并提供了透明的接口。用户可以像查询单个表一样查询Distributed表，而不需要关心底层数据的分布和位置。&lt;/p&gt;
&lt;p&gt;配置需要注意分片配置和副本配置以及宏配置&lt;/p&gt;
</content:encoded></item><item><title>使用ilogtail+cloki+clickhouse来做日志系统吧</title><link>https://blog.ikeno.top/posts/clickhouse_cloki/</link><guid isPermaLink="true">https://blog.ikeno.top/posts/clickhouse_cloki/</guid><description>搭建轻量级日志系统架构</description><pubDate>Wed, 30 Aug 2023 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;相关开源工具&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/ClickHouse/ClickHouse&quot;&gt;clickhouse&lt;/a&gt; 【文档：https://clickhouse.com/docs/zh】&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/metrico/qryn&quot;&gt;cloki&lt;/a&gt;  【文档：https://qryn.metrico.in/#/installation】&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/alibaba/ilogtail&quot;&gt;ilogtail&lt;/a&gt;  【文档：https://ilogtail.gitbook.io/】&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;简介&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;clickhouse&lt;/strong&gt;就不说了，可以参考我前面的博文，这里主要介绍一下cloki和ilogtail。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;ilogtail&lt;/strong&gt;是阿里开源的一款轻量级的日志采集工具，针对k8s环境也有很好的优化，比filebeat轻，资源消耗低，采集效率也快一些。目前官方也支持多种输入、处理和输出，
其中就包含输出到loki或者clickhouse。（要注意logtail是阿里自带的一个日志采集工具，只能采集到阿里的sls，ilogtail是开源社区的，支持多种插件）&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;cloki&lt;/strong&gt;也是国外开源的一个工具，类似于loki，API也和loki一模一样，但是后台可以关联到更高效的clickhouse作为存储。（目前相关文章不多，但可用）&lt;/p&gt;
&lt;h2&gt;部署&lt;/h2&gt;
&lt;p&gt;因为所有的都是部署在k8s上的，所以直接上yaml文件吧&lt;/p&gt;
&lt;h4&gt;cloki&lt;/h4&gt;
&lt;p&gt;cloki-deployment.yml&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;apiVersion: apps/v1
kind: Deployment
metadata:
  name: cloki
  labels:
    io.metrico.service: cloki
spec:
  replicas: 1
  selector:
    matchLabels:
      io.metrico.service: cloki
  strategy: {}
  template:
    metadata:
      annotations:
        qryn.cmd: qryn.dev
      creationTimestamp: null
      labels:
        io.metrico.service: cloki
    spec:
      containers:
        - env:
            - name: CLICKHOUSE_SERVER                   # 具体配置参考文档
              value: clickhouse                     
            - name: CLICKHOUSE_AUTH
              value: &apos;default:xxxxxx&apos;
            - name: CLICKHOUSE_PORT
              value: &apos;8123&apos;
            - name: CLICKHOUSE_DB
              value: cloki
            - name: CLICKHOUSE_TSDB
              value: cloki
            - name: FASTIFY_METRICS
              value: &apos;true&apos;
            - name: DEBUG
              value: &apos;true&apos;
            - name: FASTIFY_BODYLIMIT
              value: &apos;52428800&apos;
            - name: CLUSTER_NAME     #用于ck集群
              value: &apos;my_cluster&apos;
          image: qxip/qryn:latest
          name: cloki
          ports:
            - containerPort: 3100
          resources: {}
      restartPolicy: Always
status: {}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;cloki-service.yml&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;apiVersion: v1
kind: Service
metadata:
  creationTimestamp: null
  labels:
    io.metrico.service: cloki
  name: cloki
spec:
  ports:
    - name: &quot;3100&quot;
      port: 3100
      targetPort: 3100
  selector:
    io.metrico.service: cloki
status:
  loadBalancer: {}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;kubectl apply -f cloki-service.yaml,cloki-deployment.yaml -n ops&lt;/code&gt;&lt;/p&gt;
&lt;h4&gt;ilogtai&lt;/h4&gt;
&lt;p&gt;ilogtail-configmap.yaml&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;apiVersion: v1
kind: ConfigMap
metadata:
  name: ilogtail-user-cm
  namespace: ops
data:
  loki_stdout.yaml: |
    enable: true
    inputs:
      - Type: service_docker_stdout
        Stderr: false
        Stdout: true                # 只采集标准输出
        IncludeK8sLabel:            # 采集nginx的日志，这里可以修改为label或者其他指定，具体看文档
          app: nginx
    processors:
      - Type: processor_default    # 默认不做数据处理
        SourceKey: content
    flushers:
      - Type: flusher_loki         #输出也可以改成stdout来测试，或者直接输出到clickhouse，具体看文档
        URL: http://cloki:3100/loki/api/v1/push
        TenantID: ilogtail
        StaticLabels:
          source: ilogtail

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ilogtail-daemonset.yaml&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: ilogtail-ds
  namespace: ops
  labels:
    k8s-app: logtail-ds
spec:
  selector:
    matchLabels:
      k8s-app: logtail-ds
  template:
    metadata:
      labels:
        k8s-app: logtail-ds
    spec:
      tolerations:
        - operator: Exists                    # deploy on all nodes
      containers:
        - name: logtail
          env:
            - name: ALIYUN_LOG_ENV_TAGS       # add log tags from env
              value: _node_name_|_node_ip_
            - name: _node_name_
              valueFrom:
                fieldRef:
                  apiVersion: v1
                  fieldPath: spec.nodeName
            - name: _node_ip_
              valueFrom:
                fieldRef:
                  apiVersion: v1
                  fieldPath: status.hostIP
            - name: cpu_usage_limit           # iLogtail&apos;s self monitor cpu limit
              value: &quot;1&quot;
            - name: mem_usage_limit           # iLogtail&apos;s self monitor mem limit
              value: &quot;512&quot;
          image: &amp;gt;-
            sls-opensource-registry.cn-shanghai.cr.aliyuncs.com/ilogtail-community-edition/ilogtail:latest
          imagePullPolicy: IfNotPresent
          resources:
            limits:
              cpu: 1000m
              memory: 1Gi
            requests:
              cpu: 400m
              memory: 384Mi
          volumeMounts:
            - mountPath: /var/run                       # for container runtime socket
              name: run
            - mountPath: /logtail_host                  # for log access on the node
              mountPropagation: HostToContainer
              name: root
              readOnly: true
            - mountPath: /usr/local/ilogtail/checkpoint # for checkpoint between container restart
              name: checkpoint
            - mountPath: /usr/local/ilogtail/user_yaml_config.d # mount config dir
              name: user-config
              readOnly: true
      dnsPolicy: ClusterFirstWithHostNet
      hostNetwork: true
      volumes:
        - hostPath:
            path: /var/run
            type: Directory
          name: run
        - hostPath:
            path: /
            type: Directory
          name: root
        - hostPath:
            path: /etc/ilogtail-ilogtail-ds/checkpoint
            type: DirectoryOrCreate
          name: checkpoint
        - configMap:
            defaultMode: 420
            name: ilogtail-user-cm
          name: user-config
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;部署完后ilogtail就作为daemonset开始采集nginx的日志并输出到cloki里啦，因为loki是可以直接与grafana集成的，所以只需要在grafana里把cloki作为数据源加上，就可以直接可视化查询了！&lt;/p&gt;
&lt;/blockquote&gt;
</content:encoded></item><item><title>clickhouse集群部署指南</title><link>https://blog.ikeno.top/posts/clickhouse_cluster_install/</link><guid isPermaLink="true">https://blog.ikeno.top/posts/clickhouse_cluster_install/</guid><description>快速安装部署clickhouse集群</description><pubDate>Fri, 11 Aug 2023 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;起因&lt;/h2&gt;
&lt;p&gt;网上找到的集群安装博文各个都是复制粘贴，还缺胳膊少腿MD，各种坑，自己梳理下搭建过程。&lt;/p&gt;
&lt;h2&gt;步骤&lt;/h2&gt;
&lt;h3&gt;zookeeper安装&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;wget https://downloads.apache.org/zookeeper/stable/apache-zookeeper-3.6.3-bin.tar.gz   #注意要下载bin.tar.gz
tar -xvf apache-zookeeper-3.6.3-bin.tar.gz  #解压到目录
cp zoo_sample.cfg zoo.cfg                   #参考下面zoo.cfg配置
mkdir -p /data/zookeeper/{data,logs}        #创建数据目录
echo &quot;1&quot; &amp;gt; /data/zookeeper/data/myid        #注意myid里的数字一定要和cfg里的server顺序一致
./zkServer.sh start                         #启动zk
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;zoo.cfg配置参考&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;tickTime=2000
dataDir=/data/zookeeper/data
dataLogDir=/data/zookeeper/logs
clientPort=2181
admin.serverPort=2182
initLimit=10
syncLimit=5
server.1=zk1:2888:3888
server.2=zk2:2888:3888
server.3=zk3:2888:3888
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;clickhouse安装&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;yum install -y yum-utils
yum-config-manager --add-repo https://packages.clickhouse.com/rpm/clickhouse.repo
yum install -y clickhouse-server clickhouse-client
chmod a+x /etc/clickhouse-server/*
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;配置&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;新建一个&lt;code&gt;/etc/clickhouse-server/metrika.xml&lt;/code&gt;文件，添加zookeeper和集群配置&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;yandex&amp;gt;
    &amp;lt;clickhouse_remote_servers&amp;gt;
        &amp;lt;!--自定义集群名称--&amp;gt;
        &amp;lt;ck_cluster&amp;gt;
            &amp;lt;!--定义集群的分片数量--&amp;gt;
            &amp;lt;shard&amp;gt;
                &amp;lt;internal_replication&amp;gt;true&amp;lt;/internal_replication&amp;gt;
                &amp;lt;replica&amp;gt;
                    &amp;lt;host&amp;gt;ck1&amp;lt;/host&amp;gt;
                    &amp;lt;port&amp;gt;9000&amp;lt;/port&amp;gt;
                    &amp;lt;user&amp;gt;default&amp;lt;/user&amp;gt;  &amp;lt;!--默认是default用户--&amp;gt;
                    &amp;lt;password&amp;gt;xxxxxxxxx&amp;lt;/password&amp;gt; &amp;lt;!--你在user.xml里配置的密码--&amp;gt;
                &amp;lt;/replica&amp;gt;
            &amp;lt;/shard&amp;gt;
            &amp;lt;shard&amp;gt;
                &amp;lt;internal_replication&amp;gt;true&amp;lt;/internal_replication&amp;gt;
                &amp;lt;replica&amp;gt;
                    &amp;lt;host&amp;gt;ck2&amp;lt;/host&amp;gt;
                    &amp;lt;port&amp;gt;9000&amp;lt;/port&amp;gt;
                    &amp;lt;user&amp;gt;default&amp;lt;/user&amp;gt;
                    &amp;lt;password&amp;gt;xxxxxxxxx&amp;lt;/password&amp;gt;
                &amp;lt;/replica&amp;gt;
            &amp;lt;/shard&amp;gt;
            &amp;lt;shard&amp;gt;
                &amp;lt;internal_replication&amp;gt;true&amp;lt;/internal_replication&amp;gt;
                &amp;lt;replica&amp;gt;
                    &amp;lt;host&amp;gt;ck3&amp;lt;/host&amp;gt;
                    &amp;lt;port&amp;gt;9000&amp;lt;/port&amp;gt;
                    &amp;lt;user&amp;gt;default&amp;lt;/user&amp;gt;
                    &amp;lt;password&amp;gt;xxxxxxxxx&amp;lt;/password&amp;gt;
                &amp;lt;/replica&amp;gt;
            &amp;lt;/shard&amp;gt;
        &amp;lt;/ck_cluster&amp;gt;
    &amp;lt;/clickhouse_remote_servers&amp;gt;

    &amp;lt;networks&amp;gt;
        &amp;lt;ip&amp;gt;::&amp;lt;/ip&amp;gt;
    &amp;lt;/networks&amp;gt;

    &amp;lt;macros&amp;gt;
        &amp;lt;replica&amp;gt;ck1&amp;lt;/replica&amp;gt;  &amp;lt;!--此处填写各个节点名称，唯一值，不能重复--&amp;gt;
    &amp;lt;/macros&amp;gt;

    &amp;lt;zookeeper-servers&amp;gt;
        &amp;lt;node index=&quot;1&quot;&amp;gt;    &amp;lt;!--index 是你部署zookeeper的时候设置的myid--&amp;gt;
            &amp;lt;host&amp;gt;zk1&amp;lt;/host&amp;gt;
            &amp;lt;port&amp;gt;2181&amp;lt;/port&amp;gt;
        &amp;lt;/node&amp;gt;
        &amp;lt;node index=&quot;2&quot;&amp;gt;
            &amp;lt;host&amp;gt;zk2&amp;lt;/host&amp;gt;
            &amp;lt;port&amp;gt;2181&amp;lt;/port&amp;gt;
        &amp;lt;/node&amp;gt;
        &amp;lt;node index=&quot;3&quot;&amp;gt;
            &amp;lt;host&amp;gt;zk3&amp;lt;/host&amp;gt;
            &amp;lt;port&amp;gt;2181&amp;lt;/port&amp;gt;
        &amp;lt;/node&amp;gt;
    &amp;lt;/zookeeper-servers&amp;gt;


    &amp;lt;clickhouse_compression&amp;gt; &amp;lt;!--可加可不加--&amp;gt;
        &amp;lt;case&amp;gt;
            &amp;lt;min_part_size&amp;gt;10000000000&amp;lt;/min_part_size&amp;gt;
            &amp;lt;min_part_size_ratio&amp;gt;0.01&amp;lt;/min_part_size_ratio&amp;gt;
            &amp;lt;method&amp;gt;lz4&amp;lt;/method&amp;gt;
        &amp;lt;/case&amp;gt;
    &amp;lt;/clickhouse_compression&amp;gt;
&amp;lt;/yandex&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;修改&lt;code&gt;/etc/clickhouse-server/config.xml&lt;/code&gt;文件，把&lt;code&gt;listen_host&lt;/code&gt;标签取消注释，并新增标签启用metrika的配置，如果默认有相关标签就注释掉，prometheus的标签可以用来监控集群状态。&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;    &amp;lt;listen_host&amp;gt;::&amp;lt;/listen_host&amp;gt;
    &amp;lt;include_from&amp;gt;/etc/clickhouse-server/metrika.xml&amp;lt;/include_from&amp;gt;
    &amp;lt;macros incl=&quot;macros&quot; optional=&quot;true&quot;/&amp;gt;
    &amp;lt;!-- &amp;lt;zookeeper incl=&quot;zookeeper-servers&quot; optional=&quot;true&quot; /&amp;gt; --&amp;gt;# 这个是用来指定metrika里的clickhouse_remote_servers标签，如果是用clickhouse-keeper则不需要
    &amp;lt;remote_servers incl=&quot;clickhouse_remote_servers&quot;/&amp;gt; # 这个是用来指定metrika里的clickhouse_remote_servers标签

    #辅助zookeeper，如果同步的数据量太大，一个zookeeper同步不过来，经常出现readonly表，则可以加多几个辅助zookeeper，只需要在表引擎地址前指定zookeeper名称即可，例如
    #ENGINE = ReplicatedReplacingMergeTree(&apos;zookeeper2:/clickhouse/tables/samples/{shard}&apos;, &apos;{replica}&apos;)
    #但不能和主zookeeper一样，可以用自带的clickhouse-keeper，zookeeper用来做辅助
    &amp;lt;auxiliary_zookeepers&amp;gt; 
      &amp;lt;zookeeper2&amp;gt;
        &amp;lt;node&amp;gt;
            &amp;lt;host&amp;gt;10.0.0.1&amp;lt;/host&amp;gt;
            &amp;lt;port&amp;gt;2181&amp;lt;/port&amp;gt;
        &amp;lt;/node&amp;gt;
        &amp;lt;node&amp;gt;
            &amp;lt;host&amp;gt;10.0.0.2&amp;lt;/host&amp;gt;
            &amp;lt;port&amp;gt;2181&amp;lt;/port&amp;gt;
        &amp;lt;/node&amp;gt;
        &amp;lt;node&amp;gt;
            &amp;lt;host&amp;gt;10.0.0.3&amp;lt;/host&amp;gt;
            &amp;lt;port&amp;gt;2181&amp;lt;/port&amp;gt;
        &amp;lt;/node&amp;gt;
      &amp;lt;/zookeeper2&amp;gt;
    &amp;lt;/auxiliary_zookeepers&amp;gt;

    &amp;lt;prometheus&amp;gt;
        &amp;lt;endpoint&amp;gt;/metrics&amp;lt;/endpoint&amp;gt;
        &amp;lt;port&amp;gt;9363&amp;lt;/port&amp;gt;
        &amp;lt;metrics&amp;gt;true&amp;lt;/metrics&amp;gt;
        &amp;lt;events&amp;gt;true&amp;lt;/events&amp;gt;
        &amp;lt;asynchronous_metrics&amp;gt;true&amp;lt;/asynchronous_metrics&amp;gt;
        &amp;lt;status_info&amp;gt;true&amp;lt;/status_info&amp;gt;
    &amp;lt;/prometheus&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;在config.d目录里新增&lt;code&gt;keeper.xml&lt;/code&gt;文件：&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;?xml version=&quot;1.0&quot; ?&amp;gt;
&amp;lt;yandex&amp;gt;
    &amp;lt;keeper_server&amp;gt;
        &amp;lt;tcp_port&amp;gt;9181&amp;lt;/tcp_port&amp;gt;
        &amp;lt;server_id&amp;gt;1&amp;lt;/server_id&amp;gt; #这里也要记得不同服务器要不一样
        &amp;lt;log_storage_path&amp;gt;/data/clickhouse/coordination/log&amp;lt;/log_storage_path&amp;gt;
        &amp;lt;snapshot_storage_path&amp;gt;/data/clickhouse/coordination/snapshots&amp;lt;/snapshot_storage_path&amp;gt;

      &amp;lt;raft_configuration&amp;gt;
            &amp;lt;server&amp;gt;
               &amp;lt;id&amp;gt;1&amp;lt;/id&amp;gt;
                 &amp;lt;hostname&amp;gt;10.0.0.1&amp;lt;/hostname&amp;gt;
               &amp;lt;port&amp;gt;9444&amp;lt;/port&amp;gt;
          &amp;lt;/server&amp;gt;
          &amp;lt;server&amp;gt;
               &amp;lt;id&amp;gt;2&amp;lt;/id&amp;gt;
                 &amp;lt;hostname&amp;gt;10.0.0.2&amp;lt;/hostname&amp;gt;
               &amp;lt;port&amp;gt;9444&amp;lt;/port&amp;gt;
          &amp;lt;/server&amp;gt;
          &amp;lt;server&amp;gt;
               &amp;lt;id&amp;gt;3&amp;lt;/id&amp;gt;
                 &amp;lt;hostname&amp;gt;10.0.0.3&amp;lt;/hostname&amp;gt;
               &amp;lt;port&amp;gt;9444&amp;lt;/port&amp;gt;
          &amp;lt;/server&amp;gt;
      &amp;lt;/raft_configuration&amp;gt;

    &amp;lt;/keeper_server&amp;gt;

    &amp;lt;zookeeper&amp;gt;
        &amp;lt;node&amp;gt;
            &amp;lt;host&amp;gt;10.0.0.1&amp;lt;/host&amp;gt; #不同服务器这个ip要换，尽量指定自己的zookeeper，防止都集中到单个一样的zookeeper里
            &amp;lt;port&amp;gt;9181&amp;lt;/port&amp;gt;
        &amp;lt;/node&amp;gt;
    &amp;lt;/zookeeper&amp;gt;

    &amp;lt;distributed_ddl&amp;gt;
        &amp;lt;path&amp;gt;/clickhouse/cluster/task_queue/ddl&amp;lt;/path&amp;gt;
    &amp;lt;/distributed_ddl&amp;gt;
&amp;lt;/yandex&amp;gt;

&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;如果需要添加密码的话修改&lt;code&gt;/etc/clickhouse-server/user.xml&lt;/code&gt;文件，把明文密码加到password标签中即可,或者也可以使用SHA256加密后的密码，请将其放置在 password_sha256_hex 配置段。&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;#shell生成加密密码的示例
  PASSWORD=$(base64 &amp;lt; /dev/urandom | head -c8); echo &quot;$PASSWORD&quot;; echo -n &quot;$PASSWORD&quot; | sha256sum | tr -d &apos;-&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;最后启动clickhouse-server.service
&lt;code&gt;service clickhouse-server restart&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;启动后可以&lt;code&gt;lsof -i:8123&lt;/code&gt; 查看端口是否有启动，&lt;code&gt;service clickhouse-server status&lt;/code&gt;服务是否正常启动，&lt;code&gt;/var/log/clickhouse-server/clickhouse-server.err.log&lt;/code&gt;相关日志是否都正常，没有报错。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;8123是默认客户端端口，用于接收客户端的连接和处理来自客户端的查询请求。
9000是默认数据端口，用于处理 ClickHouse 数据节点之间的内部通信。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;没问题后执行&lt;code&gt;clickhouse-client&lt;/code&gt;后即可登陆clickhouse。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;登陆clickhouse后&lt;code&gt;select * from system.clusters\G;&lt;/code&gt; 看下是否节点都可以正常，在服务器里&lt;code&gt;echo stat | nc 127.0.0.1 9181&lt;/code&gt;检查9181端口的keeper是否也正常,如果都显示正常那就搭建完毕啦！&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;ps: &lt;code&gt;users.xml&lt;/code&gt;里也可以配置一些自定义的配置，具体可以参考官方文档,下面是一些我这边用到的：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;        &amp;lt;default&amp;gt;
          &amp;lt;load_balancing&amp;gt;random&amp;lt;/load_balancing&amp;gt; #负载均衡
          &amp;lt;distributed_product_mode&amp;gt;global&amp;lt;/distributed_product_mode&amp;gt; #分布式表一定得改成global，默认好像是local，只查询自己
          &amp;lt;group_by_overflow_mode&amp;gt;throw&amp;lt;/group_by_overflow_mode&amp;gt; #如果内存查爆了，直接中断查询丢出报错，默认是throw，也可以改成不报错，丢出部分查询数据
          &amp;lt;max_memory_usage&amp;gt;64000000000&amp;lt;/max_memory_usage&amp;gt; #单个查询最大能使用多大内存，这里是60g左右
          &amp;lt;max_bytes_before_external_group_by&amp;gt;32000000000&amp;lt;/max_bytes_before_external_group_by&amp;gt; #group_by如果聚合大于30g，则溢出到磁盘了，用io代替内存
          &amp;lt;max_bytes_before_external_sort&amp;gt;32000000000&amp;lt;/max_bytes_before_external_sort&amp;gt;#order_by如果聚合大于30g，则溢出到磁盘了，用io代替内存
        &amp;lt;/default&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>快速安装部署clickhouse和cloki</title><link>https://blog.ikeno.top/posts/clickhouse_install/</link><guid isPermaLink="true">https://blog.ikeno.top/posts/clickhouse_install/</guid><description>快速安装部署clickhouse和cloki</description><pubDate>Fri, 28 Jul 2023 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;安装&lt;/h2&gt;
&lt;h3&gt;clickhouse&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;yum install -y yum-utils
yum-config-manager --add-repo https://packages.clickhouse.com/rpm/clickhouse.repo
yum install -y clickhouse-server clickhouse-client
chmod a+x /etc/clickhouse-server/*
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;等待安装完成后修改&lt;code&gt;/etc/clickhouse-server/config.xml&lt;/code&gt;文件，把&lt;code&gt;listen_host&lt;/code&gt;标签取消注释&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    &amp;lt;listen_host&amp;gt;::1&amp;lt;/listen_host&amp;gt;
    &amp;lt;listen_host&amp;gt;127.0.0.1&amp;lt;/listen_host&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果需要添加密码的话修改&lt;code&gt;/etc/clickhouse-server/user.xml&lt;/code&gt;文件，把明文密码加到password标签中即可,或者也可以使用SHA256加密后的密码，请将其放置在 password_sha256_hex 配置段。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#shell生成加密密码的示例
  PASSWORD=$(base64 &amp;lt; /dev/urandom | head -c8); echo &quot;$PASSWORD&quot;; echo -n &quot;$PASSWORD&quot; | sha256sum | tr -d &apos;-&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;最后启动clickhouse-server.service
&lt;code&gt;systemctl start clickhouse-server.service&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;启动后可以&lt;code&gt;lsof -i:8123&lt;/code&gt; 查看端口是否有，&lt;code&gt;systemctl status clickhouse-server.service&lt;/code&gt;服务是否正常启动。&lt;/p&gt;
&lt;p&gt;没问题后执行&lt;code&gt;clickhouse-client&lt;/code&gt;后即可登陆clickhouse。&lt;/p&gt;
&lt;h3&gt;cloki&lt;/h3&gt;
&lt;p&gt;安装命令&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;yum install npm
npm install nodejs
npm install -g cloki pm2
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;安装完成后启动,记得把yourpassword修改下&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cd $(dirname $(readlink -f `which cloki`)) \
  &amp;amp;&amp;amp; CLICKHOUSE_SERVER=&quot;localhost&quot; CLICKHOUSE_AUTH=&quot;default:yourpassword&quot; CLICKHOUSE_DB=&quot;cloki&quot; \
  pm2 start cloki --name &quot;cloki&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;查看状态&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pm2 status cloki
pm2 save
pm2 startup
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;使用&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/1423657/143876342-85531041-aca5-4892-a218-e8775674867d.gif&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;cloki是支持promtail、logstash、fluentd等常用的日志采集工具，这边用promtail为例。
config.yaml&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;server:
  http_listen_port: 29080
  grpc_listen_port: 0

positions:
  filename: /opt/promtail/positions.yaml

clients:
  - url: http://172.0.0.100:3100/loki/api/v1/push

scrape_configs:
- job_name: test-log
  pipeline_stages:
  static_configs:
  - targets:
      - localhost
    labels:
      hostname: test
      cluster: sz-test
      app: test
      __path__: /var/log/*log
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;配置完毕后启动promtail就可以采集路径下的日志并push到loki的api并存储到clickhouse中。&lt;/p&gt;
&lt;p&gt;登陆部署的服务器3100端口即可访问cloki的查询界面，示例：
&lt;img src=&quot;https://camo.githubusercontent.com/d16076f6719ff4e69dc9114dbbf496f53749de8f2a85ff62acd7637cb871dfe4/68747470733a2f2f692e696d6775722e636f6d2f7942616246334c2e706e67&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;参考&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/metrico/qryn/wiki/Installation-&amp;amp;-Usage&quot;&gt;官方GITHUB&lt;/a&gt;&lt;/p&gt;
</content:encoded></item><item><title>快速部署thanos架构</title><link>https://blog.ikeno.top/posts/prometheus_thanos/</link><guid isPermaLink="true">https://blog.ikeno.top/posts/prometheus_thanos/</guid><description>如何快速部署thanos，修改你的prometheus联邦架构变成thanos架构</description><pubDate>Fri, 21 Jul 2023 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;介绍&lt;/h2&gt;
&lt;p&gt;Thanos 是一个用于扩展 Prometheus 的开源项目，通过将多个 Prometheus 实例和其他数据源组合在一起，实现了跨多个集群和地理位置的高可用性、长期存储和查询功能。Thanos 架构可以被描述为以下四个组件：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Prometheus：Thanos 使用 Prometheus 作为数据源，用于收集、存储和查询监控数据。Prometheus 是一个功能强大的开源监控系统，可以监控各种应用程序、服务和网络基础设施。&lt;/li&gt;
&lt;li&gt;Thanos Sidecar：Thanos Sidecar 是一个负责将 Prometheus 数据推送到远程存储桶的代理。它可以将 Prometheus 实例的数据集成到 Thanos 系统中，通过使用 Thanos Store API，使得 Prometheus 实例的数据在 Thanos 中可查询。&lt;/li&gt;
&lt;li&gt;Thanos Store Gateway：Thanos Store Gateway 是一个用于跨多个 Prometheus 实例聚合、存储和查询数据的组件。它可以将 Prometheus 实例和远程存储桶中的数据作为一个整体进行查询，提供了一个统一的数据视图。&lt;/li&gt;
&lt;li&gt;Thanos Query：Thanos Query 是一个用于查询 Thanos Store Gateway 中存储的数据的组件。它提供了一个类似于 Prometheus 的查询语言，并能够跨多个 Prometheus 实例和存储桶进行查询，以提供全局的监控数据视图。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;通过以上四个组件的协作，Thanos 架构实现了扩展性和高可用性，能够有效地存储和查询大量的监控数据。Thanos 还提供了许多其他的功能，例如数据压缩、数据分片、数据去重、数据合并等，可以进一步提高数据存储和查询的效率。&lt;/p&gt;
&lt;p&gt;ps：thanos的receive模式这里就先不讲了，有兴趣的可以百度或者&lt;a href=&quot;https://github.com/thanos-io/thanos&quot;&gt;官网&lt;/a&gt;查看&lt;/p&gt;
&lt;h2&gt;前因&lt;/h2&gt;
&lt;p&gt;网上检索thanos大部分是简介或者架构啥的，部署也都是容器部署，不够简单上手，其实基础的thanos架构搭建很容易。&lt;/p&gt;
&lt;h2&gt;安装&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/thanos-io/thanos/releases/&quot;&gt;github地址&lt;/a&gt;&lt;br /&gt;
下载后直接放到linux目录下就好了，每个prometheus节点都要部署一个！&lt;/p&gt;
&lt;h2&gt;部署&lt;/h2&gt;
&lt;p&gt;先直接上service文件：
&lt;strong&gt;thanos-query.service&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[Unit]
Description=Thanos Query
After=network-online.target

[Service]
Restart=on-failure
# --query.replica-label &quot;replica&quot; --query.replica-label &quot;datacenter&quot; --&amp;gt; 加上后，thanos query 查询同一节点的数据时，会自动去重
ExecStart=/opt/thanos/thanos query --http-address 0.0.0.0:19192 --grpc-address=0.0.0.0:11901 --store=10.0.0.1:19090,10.0.0.2:19090

[Install]
WantedBy=multi-user.target
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;thanos-sidecar.service&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[Unit]
Description=Thanos Sidecar
After=network-online.target

[Service]
Restart=on-failure
# --prometheus.url 配置对应的prometheus 地址
# --tsdb.path 配置对应的prometheus 数据目录
ExecStart=/opt/thanos/thanos sidecar --prometheus.url=http://localhost:9090 --tsdb.path=/data/prometheus-data --grpc-address=0.0.0.0:19090 --http-address=0.0.0.0:19091

[Install]
WantedBy=multi-user.target
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;步骤&lt;/h2&gt;
&lt;p&gt;service文件和thanos文件都放好后，只需要
1、修改prometheus的配置新增额外标签：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;global:
  scrape_interval:     15s # Set the scrape interval to every 15 seconds. Default is every 1 minute.
  evaluation_interval: 15s # Evaluate rules every 15 seconds. The default is every 1 minute.
  external_labels:
        datacenter: prometheus-02  #额外标签
rule_files:
   - &quot;rules/*.yaml&quot;
···下面配置省略···
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;2、在prometheus节点上都启动sidecar的服务。&lt;br /&gt;
3、在thanos节点上启动query的服务。&lt;br /&gt;
4、确认服务都正常启动完毕。&lt;/p&gt;
&lt;p&gt;完毕！然后就可以访问thanos的http://10.0.0.1:19192 就可以访问thanos页面啦，点击Stores就可以看到有几个thanos节点正常连接了。&lt;/p&gt;
&lt;h2&gt;后续&lt;/h2&gt;
&lt;p&gt;如果你单个prometheus太大了，也可以使用thanos的架构来进行水平分片，把job_name多分几个，降低单个prometheus负载。
如果还是会超时或者负载严重，可以换成receive模式，相当于prometheus把数据直接远程写入到thanos，然后thanos直接从本地数据里进行指标拉取。&lt;/p&gt;
</content:encoded></item><item><title>如何使用go的jwt</title><link>https://blog.ikeno.top/posts/golang_jwt/</link><guid isPermaLink="true">https://blog.ikeno.top/posts/golang_jwt/</guid><description>在golang语言中是如何使用jwt的吧</description><pubDate>Tue, 04 Jul 2023 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;什么是jwt&lt;/h2&gt;
&lt;p&gt;JWT 是一种轻量级的身份认证和授权机制，它可以在不同的系统之间安全地传递信息，JWT 的信息是以 JSON 格式存储在 Token 中，包含三部分：头部（header）、载荷（payload）和签名（signature）。其中，载荷部分包含了一些声明信息，比如 Token 的有效期、Token 的颁发者、Token 的使用者等。&lt;/p&gt;
&lt;h2&gt;jwt bearer和bearer token的区别&lt;/h2&gt;
&lt;p&gt;Bearer是HTTP授权标头的一种类型，用于指示在HTTP请求中使用OAuth 2.0访问令牌进行身份验证。&lt;/p&gt;
&lt;p&gt;JWT Bearer是使用JSON Web Token（JWT）进行身份验证的一种方式，它将JWT作为Bearer令牌的值发送到服务器。&lt;/p&gt;
&lt;p&gt;Bearer Token则是OAuth 2.0协议中使用的一种访问令牌类型，它表示访问令牌的类型是Bearer。Bearer Token是一种无状态的令牌，它通常具有一定的有效期，在有效期内可以使用该令牌进行身份验证和授权。Bearer Token可以使用各种技术实现，如JWT、OAuth 2.0、OpenID Connect等。&lt;/p&gt;
&lt;p&gt;这里我们用的就是Bearer Token&lt;/p&gt;
&lt;h2&gt;安装jwt包&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;go get github.com/dgrijalva/jwt-go
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;一般jwt是配合go gin一起使用的&lt;/p&gt;
&lt;h2&gt;如何使用jwt&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;
import (
	&quot;time&quot;

	&quot;github.com/dgrijalva/jwt-go&quot;
)


type TokenInterface interface {
	GenerateToken(id int, accountName string) (string, error)
	ParseToken(token string) (*Claims, error)
}

type Token struct {
	JwtSecret  string // 加密秘钥
	ExpireTime int    // 多少小时过期
}

func NewToken(f ...func(token *Token)) TokenInterface {
	t := &amp;amp;Token{}
	for _, i := range f {
		i(t)
	}
	// 未赋值则初始化
	if t.ExpireTime == 0 {
		t.ExpireTime = 24
	}
	if t.JwtSecret == &quot;&quot; {
		t.JwtSecret = &quot;JwtSecret&quot;
	}
	return t
}

type Claims struct {
	UserID      int    `json:&quot;user_id&quot;`
	AccountName string `json:&quot;account_name&quot;`
	jwt.StandardClaims
}

// GenerateToken 生成Token
func (t *Token) GenerateToken(id int, accountName string) (string, error) {
	nowTime := time.Now()
	expireTime := nowTime.Add(time.Duration(t.ExpireTime) * time.Hour)

	claims := Claims{
		UserID:      id,
		AccountName: accountName,
		StandardClaims: jwt.StandardClaims{
			ExpiresAt: time.Now().Add(TokenExpireDuration).Unix(),
			Issuer:    &quot;my-project&quot;,
		},
	}

	tokenClaims := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	token, err := tokenClaims.SignedString([]byte(t.JwtSecret))

	return token, err
}

// ParseToken 解析Token
func (t *Token) ParseToken(token string) (*Claims, error) {
	tokenClaims, err := jwt.ParseWithClaims(token, &amp;amp;Claims{}, func(token *jwt.Token) (interface{}, error) {
		return []byte(t.JwtSecret), nil
	})

	if tokenClaims != nil {
		if claims, ok := tokenClaims.Claims.(*Claims); ok &amp;amp;&amp;amp; tokenClaims.Valid {
			return claims, nil
		}
	}
	return nil, err
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;func ValidatorQueryInfo(ctx *gin.Context) {

	authHeader := ctx.Request.Header.Get(&quot;Authorization&quot;)
	if authHeader == &quot;&quot; {
		err = errors.New(&quot;请求头中的auth为空&quot;)
		return
	}
	parts := strings.SplitN(authHeader, &quot; &quot;, 2)
	if !(len(parts) == 2 &amp;amp;&amp;amp; parts[0] == &quot;Bearer&quot;) {
		err = errors.New(&quot;请请求头中的auth格式错误&quot;)
		return
	}
	t := jwt.NewToken()
	claims, err := t.ParseToken(parts[1])
	if err != nil {
		err = errors.New(&quot;无效的token&quot;)
		return
	}
	log.DefaultLogs.Log.Error(&quot;jwt解析正常&quot;, claims)
	return
}

&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>如何修改git commit记录</title><link>https://blog.ikeno.top/posts/gitcommand/</link><guid isPermaLink="true">https://blog.ikeno.top/posts/gitcommand/</guid><description>git修改github里不小心泄露的账号密码和私钥</description><pubDate>Wed, 07 Jun 2023 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;不小心在github上传了个人隐私或者私钥密码之类的代码，虽然下次代码里已经修复了，但是！commit记录里还是会有的，如果遇到有心人去扫描或者查询还是会泄露的。
所以需要把以前的commit记录和历史都改掉！或者删掉，保护互联网个人信息，从你我做起！&lt;/p&gt;
&lt;h2&gt;修改历史commit的邮箱和用户名&lt;/h2&gt;
&lt;p&gt;用git filter-branch命令来修改：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;git filter-branch命令 让您通过重写&amp;lt;rev-list选项&amp;gt;中提到的分支来重写Git修订历史记录，并在每个修订版上应用自定义过滤器。这些过滤器可以修改每个树（例如，删除文件或对所有文件运行perl重写）或每个提交的信息。否则，将保留所有信息（包括原始提交时间或合并信息）。&lt;a href=&quot;https://github.com/apachecn/git-doc-zh/blob/master/docs/59.md&quot;&gt;详细概要&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;git filter-branch --env-filter &apos;
OLD_EMAIL=&quot;你的旧邮箱&quot;
CORRECT_NAME=&quot;你的新用户名&quot;
CORRECT_EMAIL=&quot;你的新邮箱地址&quot;
if [ &quot;$GIT_COMMITTER_EMAIL&quot; = &quot;$OLD_EMAIL&quot; ]
then
    export GIT_COMMITTER_NAME=&quot;$CORRECT_NAME&quot;
    export GIT_COMMITTER_EMAIL=&quot;$CORRECT_EMAIL&quot;
fi
if [ &quot;$GIT_AUTHOR_EMAIL&quot; = &quot;$OLD_EMAIL&quot; ]
then
    export GIT_AUTHOR_NAME=&quot;$CORRECT_NAME&quot;
    export GIT_AUTHOR_EMAIL=&quot;$CORRECT_EMAIL&quot;
fi
&apos; --tag-name-filter cat -- --branches --tags
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;等全部修改完成后用 &lt;code&gt;git push --force&lt;/code&gt;命令即可修改完毕&lt;/p&gt;
&lt;h2&gt;修改历史commit的文件&lt;/h2&gt;
&lt;p&gt;&amp;lt;font color=&quot;#dd0000&quot;&amp;gt;相关文件可能也会删除！请提前备份好然后重新上传&amp;lt;/font&amp;gt;
同样用git filterbranch命令来修改，先把代码git clone到本地后，然后&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git filter-branch --force --index-filter &apos;git rm --cached --ignore-unmatch path/to/sensitive_info&apos; --prune-empty --tag-name-filter cat -- --all
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;或者&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git filter-branch --tree-filter &apos;rm -f path/to/sensitive_info&apos; HEAD
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其中 &lt;code&gt;path/to/sensitive_info&lt;/code&gt; 是要删除敏感信息的文件路径，可以使用通配符 &lt;code&gt;*&lt;/code&gt; 删除所有文件中的敏感信息。&lt;/p&gt;
&lt;p&gt;最后&lt;code&gt;git push --force&lt;/code&gt;下就好啦，相关文件的提交记录就会全部删除。&lt;/p&gt;
</content:encoded></item><item><title>业务容器化需要注意的一些地方</title><link>https://blog.ikeno.top/posts/containerization/</link><guid isPermaLink="true">https://blog.ikeno.top/posts/containerization/</guid><description>java业务上云的一些注意点</description><pubDate>Thu, 01 Jun 2023 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;废话不多说，直接上yaml文件。&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;apiVersion: apps/v1
kind: Deployment
metadata:
  name: java-web
  namespace: ops
  labels:
    app: java-web
spec:
  selector:
    matchLabels:
      app: java-web
  replicas: 2
  strategy:
    type: RollingUpdate
  template:
    metadata:
      name: java-web
      annotations:   # 第一点：新增注解
        loki.io/scrape: &apos;true&apos; # 抓取直接输出的日志到loki里
        prometheus.io/path: /metrics #采集的监控地址
        prometheus.io/port: &apos;2112&apos;  #采集暴露的监控端口
        prometheus.io/scrape: &apos;true&apos;  #打开采集开关
      labels:
        app: java-web
    spec:
      imagePullSecrets:  # 第二点：添加权限
        - name: my-docker #只有该用户才有pull镜像的权限
      containers:
        - name: java-web
          image: hb.ops.top/ops/java-web:5917f92c
          imagePullPolicy: IfNotPresent
          readinessProbe:  # 第三点：新增存活检测，当前是针对监控端口进行存活检测，也就是说只有当监控端口起来了，程序才算完全启动
            httpGet:
              port: 2112
              path: /metrics/prometheus
            initialDelaySeconds: 60
            successThreshold: 1
            failureThreshold: 3
            timeoutSeconds: 5
          startupProbe:
            httpGet:
              port: 2112
              path: /metrics/prometheus
            initialDelaySeconds: 60
            successThreshold: 1
            failureThreshold: 3
            timeoutSeconds: 5
          livenessProbe:
            httpGet:
              port: 2112
              path: /metrics/prometheus
            initialDelaySeconds: 60
            successThreshold: 1
            failureThreshold: 3
            timeoutSeconds: 5
          resources: # 第四点：设置资源限制，如果是java程序，建议limit比request大，这样可以合理分配堆内存和非堆内存
            requests:
              memory: 1024Mi
            limits:
              memory: 2048Mi
          ports:
            - name: web-port
              containerPort: 8889
          env: # 第五点：设置环境变量，可以设置时区，指定EVN环境，添加JAVA参数等
            - name: JVM_OPTS
              value: -javaagent:/opt/skywalking/skywalking-agent.jar -Xmx1G -Xms1G
            - name: TZ
              value: Asia/Shanghai
            - name: APOLLO_LABEL
              value: gray
            - name: SW_AGENT_NAME
              value: &apos;k8s-java-web&apos;
            - name: SW_AGENT_COLLECTOR_BACKEND_SERVICES
              value: &apos;10.0.0.123:11800&apos;
            - name: ENV
              value: prod

---

apiVersion: v1
kind: Service
metadata:
  labels:
    app: java-web
  name: java-web
  namespace: ops
spec:
  ports:
  - name: java-web-port
    port: 8889
    protocol: TCP
    targetPort: 8889
    nodePort: 30009
  selector:
    app: java-web
  sessionAffinity: None
  type: NodePort
status:
  loadBalancer: {}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>游戏企划</title><link>https://blog.ikeno.top/posts/gamedesign/</link><guid isPermaLink="true">https://blog.ikeno.top/posts/gamedesign/</guid><description>现在是幻想时间！来幻想一个游戏吧</description><pubDate>Fri, 26 May 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;类型：网页游戏
标签：RPG，卡牌，冒险，像素
简介：主角是一名冒险者，游戏初期只有公会，公会接任务，公会负责发布新的任务，玩家接取任务后开始冒险。
玩法：（参考兰斯，龙之皇冠）
游戏分为探索和战斗：
探索：玩家只需要选择3个卡牌其中一个进行路线选择，触发事件（战斗，恢复，故事，邂逅，陷阱等）
战斗：只需要选择随机的卡牌，3张牌随机，行动完后抽一张，最多5张，不同的卡片消耗的行动值不一样，普通攻击不消耗。攻击牌，防御牌，魔法牌，道具牌等（开发初期战斗可以先不做，直接扣血）&lt;/p&gt;
&lt;p&gt;游戏属于开源的，任何人和团队都可以上传任务，但要包含完整的任务剧情，新的角色以及相应的角色对话故事或道具卡牌等，运营只负责提供游戏框架，作为公会发布任务。
只在新角色入队才需要支付签约费用，付费与作者分成。角色后续故事也可以作者免费发布，甚至限定条件，只有组队了的才能接取&lt;/p&gt;
&lt;p&gt;角色任务：完整的剧情，对话，事件，战斗，立绘，卡牌&lt;/p&gt;
&lt;p&gt;后续场所可以新增酒馆，教会、温泉和旅店等场所
教会可以捐款，获得圣水，无效一次攻击&lt;/p&gt;
&lt;p&gt;初始职业：盗贼，盾卫，魔法师，冒险者（主角）&lt;/p&gt;
&lt;p&gt;战斗：
横版战棋回合制
上面画面是像素人物，分左右，左边友方，右边敌方。
格子6*6，可选择站位。优先攻击前卫，但也有魔法攻击和偷袭之类的。&lt;/p&gt;
&lt;p&gt;下面是4个角色的技能，选择好后点击开始战斗，下一次默认还是保存上一次的选择。&lt;/p&gt;
&lt;p&gt;人物属性：
行动值MP
体力值HP
物理攻击力（倍率计算）
魔法攻击力（倍率计算）
物理防御力（倍率计算）
魔法防御力（倍率计算）&lt;/p&gt;
&lt;p&gt;状态：
物理：中毒，流血，眩晕
魔法：冰冻，燃烧，麻痹&lt;/p&gt;
</content:encoded></item><item><title>Prometheus Metrics精简优化2</title><link>https://blog.ikeno.top/posts/prometheus_metrics2/</link><guid isPermaLink="true">https://blog.ikeno.top/posts/prometheus_metrics2/</guid><description>查询prometheus采集了哪些指标，用到了哪些指标以及那些没有用到</description><pubDate>Tue, 16 May 2023 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;无意中看到一个可以查询当前prometheus有用到哪些指标的工具，感觉对大的prometheus集群应该蛮有用的，试试能不能降低一些prometheus负责吧。&lt;/p&gt;
&lt;h2&gt;工具&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;mimirtool&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Mimirtool 是一个 CLI 工具，可用于涉及 Grafana Mimir 或 Grafana Cloud Metrics 的 Prometheus 兼容任务的各种操作。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;下载方法&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;curl -fLo mimirtool https://github.com/grafana/mimir/releases/latest/download/mimirtool-linux-amd64
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;使用方法&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;提取Grafana 仪表板中用到的指标：&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;mimirtool analyze grafana --address=http://localhost:3000 --key=&quot;${GRAFANA_API_TOKEN}&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;grafana的apikey可以在grafana页面的设置里获取，生成一个admin的key就好啦,运行完上面的命令后就可以得到&lt;code&gt;metrics-in-grafana.json&lt;/code&gt;文件，里面就是grafana用到了哪些指标。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;提取prometheus中rules用到的指标：&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;./mimirtool analyze rule-file my-prometheus-rule.yaml
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;my-prometheus-rule.yaml文件是prometheus的rules里的规则集合，把所有的规则都汇总到了这个文件里，要注意格式正确。运行命令后就会生成&lt;code&gt;metrics-in-ruler.json&lt;/code&gt;文件，里面是prometheus的规则用到了哪些指标。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;和当前prometheus采集的指标进行对比：&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;./mimirtool analyze prometheus --address=http://localhost:9090
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;运行后会生成&lt;code&gt;prometheus-metrics.json&lt;/code&gt;文件，这个就是我们最终要的文件了，里面包含了哪些指标在用，哪些指标没有在用，然后就可以根据这些数据，修改prometheus的采集规则，筛选掉一些不用的指标。&lt;/p&gt;
&lt;h2&gt;总结&lt;/h2&gt;
&lt;p&gt;其实还是要结合prometheus的TSDB里的TOP指标来分析哪些指标是很大且无用的，并不是一味的drop指标就好，有些指标可能只是单纯的没有加相关的告警规则，例如ingress_control相关的指标和etcd相关的指标，还是需要结合告警来判断！&lt;/p&gt;
</content:encoded></item><item><title>来在线听音乐吧</title><link>https://blog.ikeno.top/posts/music-web/</link><guid isPermaLink="true">https://blog.ikeno.top/posts/music-web/</guid><description>发现一个不错的音乐网站！</description><pubDate>Fri, 05 May 2023 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;国内的音乐app都得收费才能听完整版，找到一个国外的音乐分享网站，音乐齐全还可以分享！&lt;/h2&gt;
&lt;p&gt;自己动手 丰衣足食！后续看看能不能弄一个自动同步网易云音乐歌单或者排行榜之类的东西&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://audiomack.com/&quot;&gt;audiomack&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;iframe src=&quot;https://audiomack.com/embed/XxShadow_SoulxX/song/official-zi-nandism-subtitle-official-audio&quot; scrolling=&quot;no&quot; width=&quot;100%&quot; height=&quot;252&quot; scrollbars=&quot;no&quot; frameborder=&quot;0&quot;&amp;gt;&amp;lt;/iframe&amp;gt;&lt;/p&gt;
</content:encoded></item><item><title>Chatgpt的API入门</title><link>https://blog.ikeno.top/posts/chatgptapi-guide/</link><guid isPermaLink="true">https://blog.ikeno.top/posts/chatgptapi-guide/</guid><description>简单介绍下如何使用chatgpt的API吧</description><pubDate>Tue, 18 Apr 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;em&gt;chatgpt.py&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import openai
from PIL import Image
import requests

class AI:
    def __init__(self):
        # 可以不需要指定api_base，用默认的就好了，前提是网络通，默认需要翻墙
        self.api_base = &quot;https://XXX/api/v1&quot;
        self.api_key = &quot;sk-XXX&quot;
        
    def chat(self,prompt,msg):
        openai.api_base = self.api_base
        openai.api_key = self.api_key
        chat_completion = openai.ChatCompletion.create(
        model=&quot;gpt-3.5-turbo&quot;,
        messages = [
            # 系统消息，它有助于设置助手的行为
            {&quot;role&quot;: &quot;system&quot;, &quot;content&quot;: prompt},
            # 用户消息，输入你的问题吧
            {&quot;role&quot;: &quot;user&quot;, &quot;content&quot;: msg},
            # 我们还可以添加以前的对话，但你需要用while true循环把之前的消息也发送过去，很费token
            # {&quot;role&quot;: &quot;assistant&quot;, &quot;content&quot;: messages},
        ],
        )
        response = chat_completion.choices[0].message.content
        print(response)
        return 

    def image(self,prompt):
        openai.api_base = self.api_base
        openai.api_key = self.api_key
        image_completion = openai.Image.create(
            #提示要生成什么样的图片
            prompt=prompt,
            #数量
            n=1,
            #大小
            size=&quot;512x512&quot;
        )
        image_url = image_completion[&apos;data&apos;][0][&apos;url&apos;]
        r = requests.get(image_url)
        with open(&apos;test.png&apos;, &apos;wb&apos;) as f:
            f.write(r.content)
            img = Image.open(&apos;test.png&apos;)
            img.show()
        return 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;main.py&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from service.chatgpt import AI
import json

def main():
    with open(&apos;data.json&apos;, &quot;r&quot;, encoding=&quot;utf-8&quot;) as f:
        msg = json.dumps(json.load(f))
        prompt = &quot;你现在是一个告警AI机器人,我会发送过去24小时的告警数据统计信息给你，请生成日报，要有时间范围、告警总数、TOP5告警，告警级别、告警分析、告警建议&quot;
        ai = AI()
        ai.chat(prompt,msg)
    
    
if __name__ == &quot;__main__&quot;:
    main()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;收费&lt;/strong&gt;：官方是 0.002 美刀/1000token，但是使用api必须把账号升级为付费账号，也就说要绑定国外的信用卡，很麻烦，目前只能用depay这种虚拟信用卡充USD来实现，或者用国内的一些&lt;a href=&quot;https://console.openai-asia.com/&quot;&gt;代理&lt;/a&gt;之类的了，希望以后有更方便的方法吧。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;参考&lt;/strong&gt;：&lt;a href=&quot;https://platform.openai.com/docs/guides/chat&quot;&gt;官方API文档&lt;/a&gt;&lt;/p&gt;
</content:encoded></item><item><title>简单搭建国内也可以使用的chatgpt</title><link>https://blog.ikeno.top/posts/chatgpt-web/</link><guid isPermaLink="true">https://blog.ikeno.top/posts/chatgpt-web/</guid><description>来搭建一个不需要翻墙，手机也可以方便使用的Chatgpt吧！</description><pubDate>Thu, 06 Apr 2023 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;起因&lt;/h2&gt;
&lt;p&gt;因为目前chatgpt是禁止国内访问的（包括HK和TW），所以要访问chatgpt就得翻墙，但是目前我用的梯子都非常不稳定！
经常性的掉线，就很烦，2023年了，没法用google和chatgpt还怎么当程序员！经过我github上翻来翻去，总算找到几个
大佬开源的webui，再配上vercel或railway之类的应用托管网站，然后再配上自己的域名，完美解决了国内访问问题！&lt;/p&gt;
&lt;p&gt;PS：目前chatgpt的&lt;a href=&quot;ai.com&quot;&gt;web界面&lt;/a&gt;是免费的，只要你注册了openai账号，就可以一直使用，但是api不是，免费账号
使用api只能用3个月，且有额度限制，我的第一批注册的用户基本上4月1号就都过期了，虽然api付费很便宜，基本上10美元
可以用一年，但需要绑定国外的卡，国内只能用depay之类的虚拟信用卡，很麻烦orz&lt;/p&gt;
&lt;h2&gt;准备&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/Yidadaa/ChatGPT-Next-Web&quot;&gt;Yidadaa/ChatGPT-Next-Web&lt;/a&gt;
这个webui界面简洁，对移动端优化也不错，但只支持apikey方式使用。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/Chanzhaoyu/chatgpt-web&quot;&gt;Chanzhaoyu/chatgpt-web&lt;/a&gt;
这个webui支持apikey方式也支持session-token的方式。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/adams549659584/go-proxy-bingai&quot;&gt;adams549659584/go-proxy-bingai&lt;/a&gt;
新增一个bing的，github页面里有详细的介绍，基本上也是可以一键部署。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;自己的域名（需要注册阿里云账号，然后填写信息模板申请后购买域名，一般top的域名第一年只需要9块钱）&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;搭建&lt;/h2&gt;
&lt;p&gt;搭建方式其实都差不多&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;点击进入到对应的github项目里，然后fork项目到自己的仓库里。&lt;/li&gt;
&lt;li&gt;打开&lt;a href=&quot;https://vercel.com&quot;&gt;vercel&lt;/a&gt; 或者 &lt;a href=&quot;https://railway.app/&quot;&gt;railway&lt;/a&gt;,用github账号登陆。&lt;/li&gt;
&lt;li&gt;新建project,选择从github仓库里导入,选择对应的仓库（如果找不到仓库，则需要加一下权限）。&lt;/li&gt;
&lt;li&gt;按提示，需要填写一些&lt;strong&gt;环境变量&lt;/strong&gt;，具体变量参考项目里的README，这里举例：
&lt;strong&gt;ChatGPT-Next-Web&lt;/strong&gt;：
&lt;em&gt;OPENAI_API_KEY&lt;/em&gt; ：你的apikey&lt;br /&gt;
&lt;em&gt;CODE&lt;/em&gt; ：自定义访问密码&lt;br /&gt;
&lt;br /&gt;
&lt;strong&gt;chatgpt-web&lt;/strong&gt;:&lt;br /&gt;
&lt;em&gt;OPENAI_API_KEY&lt;/em&gt; 和 &lt;em&gt;OPENAI_ACCESS_TOKEN&lt;/em&gt; 二选一(OPENAI_ACCESS_TOKEN的话可以登陆&lt;a href=&quot;https://chat.openai.com/api/auth/session&quot;&gt;chat.openai.com&lt;/a&gt;获取）
&lt;em&gt;AUTH_SECRET_KEY&lt;/em&gt; : 你的session&lt;br /&gt;
&lt;em&gt;PORT&lt;/em&gt; : 3002&lt;/li&gt;
&lt;li&gt;点击部署，等待部署完成。&lt;/li&gt;
&lt;li&gt;点击domain，新建你的domain，例如我的是chat.ikeno.top,然后会生成一个cname的地址&lt;/li&gt;
&lt;li&gt;登陆阿里云账号的DNS解析里，找到我的ikeno.top新增二级域名前缀&lt;strong&gt;chat&lt;/strong&gt;，对应解析为cname，然后把地址也填写上&lt;strong&gt;cname.vercel-dns.com&lt;/strong&gt;（如果是railway的填railway提供的地址），新增完毕后就可以解析成功啦！&lt;/li&gt;
&lt;li&gt;登陆你的域名看看，是不是已经可以正常访问了！而且不需要梯子了，手机也可以流畅访问！输入你设置好的账号密码就可以丝滑提问啦。&lt;/li&gt;
&lt;/ol&gt;
</content:encoded></item><item><title>Prometheus Metrics精简优化</title><link>https://blog.ikeno.top/posts/prometheus_metrics/</link><guid isPermaLink="true">https://blog.ikeno.top/posts/prometheus_metrics/</guid><description>Prometheus Metrics精简，优化Metrics数量，减少prometheus负载</description><pubDate>Tue, 04 Apr 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;em&gt;Prometheus&lt;/em&gt;的&lt;strong&gt;TSDB Status&lt;/strong&gt;里可以查看TOP10的指标：&lt;strong&gt;Top 10 series count by metric names&lt;/strong&gt;,参考这个来优化指标吧！&lt;/p&gt;
&lt;h2&gt;筛选&lt;/h2&gt;
&lt;p&gt;推荐使用&lt;strong&gt;metric_relabel_configs&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#保留
  metric_relabel_configs: 
  - source_labels: [__name__]
    regex: etcd_disk_backend_commit_duration_seconds_bucket|up
    action: keep   
#去除
  metric_relabel_configs:
  - source_labels: [__name__]
    regex: nginx_filter_.*
    action: drop 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;或者使用&lt;strong&gt;whitelist_regex&lt;/strong&gt;或者&lt;strong&gt;blacklist_regex&lt;/strong&gt;
举例：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 只监控以http开头的指标
whitelist_regex: ^http.*

# 不监控以http开头的指标
blacklist_regex: ^http.*
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;合并&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;kube-apiserver&lt;/em&gt;的&lt;strong&gt;apiserver_request_duration_seconds_bucket&lt;/strong&gt;指标数量太多尝试进行合并：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;将0.1、0.2、0.5、1、2、5、10、30和+Inf的桶(bucket)合并为0.1的桶(bucket)，将0.3、0.6、1.5、3、6、15、30、60、120、300、600、1800、3600和+Inf的桶(bucket)合并为0.3的桶(bucket)，以此类推。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;    relabel_configs:													
      - source_labels: [le]
        regex: &quot;0\\.1|0\\.2|0\\.5|1|2|5|10|30|\\+Inf&quot;
        action: replace
        target_label: le
        replacement: &quot;0.1&quot;
      - source_labels: [le]
        regex: &quot;0\\.3|0\\.6|1\\.5|3|6|15|30|60|120|300|600|1800|3600|\\+Inf&quot;
        action: replace
        target_label: le
        replacement: &quot;0.3&quot;
      - source_labels: [le]
        regex: &quot;0\\.4|0\\.7|2\\.5|4|7|25|50|100|250|500|1000|1800|3600|\\+Inf&quot;
        action: replace
        target_label: le
        replacement: &quot;0.4&quot;
      - source_labels: [le]
        regex: &quot;1\\.5|5|15|30|60|300|1800|3600|\\+Inf&quot;
        action: replace
        target_label: le
        replacement: &quot;1.5&quot;
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>Kubernetes的探针机制</title><link>https://blog.ikeno.top/posts/k8s-probe/</link><guid isPermaLink="true">https://blog.ikeno.top/posts/k8s-probe/</guid><description>讲一下k8s里的探针</description><pubDate>Tue, 28 Mar 2023 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;演示&lt;/h2&gt;
&lt;p&gt;k8s里探针有三种：存活(livenessProbe)、就绪(readinessProbe)和启动(startupProbe)探针&lt;/p&gt;
&lt;p&gt;直接演示一下代码吧&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;....省略其他....
      containers:
        - name: nginx
          image: registry.k8s.io/busybox:v1
          imagePullPolicy: IfNotPresent
          readinessProbe:
            httpGet:
              port: 1111
              path: /metrics/prometheus
            initialDelaySeconds: 60
            successThreshold: 1
            failureThreshold: 3
            timeoutSeconds: 5
          startupProbe:
            httpGet:
              port: 1111
              path: /metrics/prometheus
            initialDelaySeconds: 60
            successThreshold: 1
            failureThreshold: 3
            timeoutSeconds: 5
          livenessProbe:
            httpGet:
              port: 1111
              path: /metrics/prometheus
            initialDelaySeconds: 60
            successThreshold: 1
            failureThreshold: 3
            timeoutSeconds: 5
          resources:
            requests:
              memory: 1024Mi
            limits:
              memory: 2048Mi
....省略其他...
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;解释&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;readinessProbe&lt;/strong&gt;: 用于确定容器是否已准备好接收网络流量。&lt;/p&gt;
&lt;p&gt;当探针发现容器已经准备就绪时，Kubernetes 将开始将流量引导到容器中。
使用 HTTP GET 方法检查容器的 /metrics/prometheus 路径是否可用，如果容器返回状态码 200，则认为容器已准备好接收流量。&lt;/p&gt;
&lt;p&gt;&lt;em&gt;initialDelaySeconds&lt;/em&gt; 表示在容器启动后多少秒后开始检查。
&lt;em&gt;successThreshold&lt;/em&gt; 表示成功的最小连续计数。
&lt;em&gt;failureThreshold&lt;/em&gt; 表示失败的最小连续计数。
&lt;em&gt;timeoutSeconds&lt;/em&gt; 表示检查超时的秒数。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;startupProbe&lt;/strong&gt;: 用于确定容器是否正在启动中。&lt;/p&gt;
&lt;p&gt;当探针发现容器正在启动时，Kubernetes 将等待一段时间，以允许容器完成启动过程，然后再检查容器是否已准备就绪。&lt;/p&gt;
&lt;p&gt;&lt;em&gt;initialDelaySeconds、successThreshold、failureThreshold 和 timeoutSeconds&lt;/em&gt; 参数的含义与 readinessProbe 中的相同。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;livenessProbe&lt;/strong&gt;: 用于确定容器是否正在运行。&lt;/p&gt;
&lt;p&gt;当探针发现容器已停止运行时，Kubernetes 将自动重新启动容器。&lt;/p&gt;
&lt;p&gt;&lt;em&gt;initialDelaySeconds、successThreshold、failureThreshold 和 timeoutSeconds&lt;/em&gt; 参数的含义与 readinessProbe 中的相同。&lt;/p&gt;
&lt;h2&gt;使用场景&lt;/h2&gt;
&lt;p&gt;一般更新业务容器的时候，不加探针的话容器只要能正常启动，原先的容器就会被kill掉，但其实这个时候新的业务容器还没完全起来，这时候就会出现空挡，导致业务不可用，如果加上了探针，只有当新的容器业务完全起来了后，可以检测到监控指标了，才会把旧的kill掉，这样就可以无缝的滚动更新。而且有时候应用可能卡死了，内部出现一些error导致程序不可用，但容器并没有检测到挂掉之类的，探针这时候也可以检测到，然后自动重启。&lt;/p&gt;
&lt;h2&gt;其他&lt;/h2&gt;
&lt;p&gt;一般业务上云，要对业务的日志，监控，以及探针都需要添加，已确保业务上线后可以充分检测到运行情况，方便运维和开发定位和发现问题。&lt;/p&gt;
&lt;h2&gt;文章&lt;/h2&gt;
&lt;p&gt;推荐可以看看官方的文档
&lt;a href=&quot;https://kubernetes.io/zh-cn/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/&quot;&gt;配置存活、就绪和启动探针&lt;/a&gt;&lt;/p&gt;
</content:encoded></item><item><title>本地部署chatAI【chatglm】</title><link>https://blog.ikeno.top/posts/chatglm_webui-guide/</link><guid isPermaLink="true">https://blog.ikeno.top/posts/chatglm_webui-guide/</guid><description>让你的计算机变成AI吧</description><pubDate>Mon, 20 Mar 2023 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;硬件需求&lt;/h2&gt;
&lt;p&gt;内存：16G以上&lt;br /&gt;
显卡：6G显存以上&lt;/p&gt;
&lt;h2&gt;下载地址&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;模型：&lt;/strong&gt;
&lt;a href=&quot;https://huggingface.co/THUDM/chatglm-6b&quot;&gt;chatglm-6b&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Github：&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;B站 &lt;a href=&quot;https://space.bilibili.com/55123&quot;&gt;大江户战士&lt;/a&gt; UP主基于WEBUI开发的CHATUI界面，感兴趣可以关注下
&lt;a href=&quot;https://github.com/OedoSoldier/chatglm_webui&quot;&gt;chatglm_webui&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;安装方法&lt;/h2&gt;
&lt;p&gt;进入到chatglm_webui文件夹里运行以下命令安装依赖&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pip install -r requirements.txt
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;可能会遇到问题&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Q：python版本太低&lt;br /&gt;
A: 官网下载安装python3.6或以上的版本，然后修改系统环境变量 &lt;code&gt;PATH&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Q：C++依赖安装失败&lt;br /&gt;
A：下载C++的编译工具依赖：microsoft visual c++ build tools&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Q：依赖安装失败&lt;br /&gt;
A：尝试用管理员权限启动CMD的&lt;code&gt;命令提示符&lt;/code&gt;然后安装&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Q：&lt;code&gt;AssertionError: Torch not compiled with CUDA enabled&lt;/code&gt;&lt;br /&gt;
A：安装的python Torch是不包含显卡的版本，&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Q：&lt;code&gt;ERROR: No matching distribution found for torch==1.13.1+cu117&lt;/code&gt;&lt;br /&gt;
A：&lt;code&gt;python -m pip install torch==1.13.1+cu117 torchvision==0.14.1+cu117 --extra-index-url https://download.pytorch.org/whl/cu117 --no-cache-dir&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Q：&lt;code&gt;python sentencepiece failed&lt;/code&gt; 或者 &lt;code&gt;src/sentencepiece/sentencepiece_wrap.cxx(2822): fatal error C1083:&lt;/code&gt;
A：尝试升级python3.6或以上的版本并安装microsoft visual c++ build tools&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Q：&lt;code&gt;Pip install sentencepiece failure&lt;/code&gt;&lt;br /&gt;
A: 尝试直接下载&lt;a href=&quot;https://files.pythonhosted.org/packages/fd/45/6d0eb609d5cd81df094aab71a867b2ab6b315ffd592e78fb94a625c4d6aa/sentencepiece-0.1.3.tar.gz&quot;&gt;sentencepiece文件&lt;/a&gt;然后放到python的&lt;code&gt;site-packages&lt;/code&gt;目录里，不行就升级pip或者更换python版本。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;运行方法&lt;/h2&gt;
&lt;p&gt;1.把下载好的&lt;code&gt;chatglm-6b&lt;/code&gt;文件夹直接放到&lt;code&gt;chatglm_webui&lt;/code&gt;目录下
2.运行&lt;code&gt;python main.py --low_vram&lt;/code&gt; （因为我的显卡只有8G显存，所以加了low_vram）&lt;/p&gt;
&lt;p&gt;参数：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;--path&lt;/code&gt;：指定模型所在文件夹，根目录不需要加&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--low_vram&lt;/code&gt;：4-bit 量化，6GB 显存可用&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--med_vram&lt;/code&gt;：8-bit 量化，10GB 显存可用&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--cpu&lt;/code&gt;：CPU运行，32G 内存可用&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--low_ram&lt;/code&gt;：CPU运行，16G 内存可用&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;UI界面&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://s1.ax1x.com/2023/03/20/pptlV9H.png&quot; alt=&quot;UI&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;总结&lt;/h2&gt;
&lt;p&gt;当前单机版本的AI还属于调优阶段，没法和ChatGPT进行比较的，模型大小和参数的数量也不是一个数量级别，属于低智能AI。但meta的LLaMA和200G的模型也都开源了，相信过不了多久就可以做到真正的部署可实用的个人AI智能了，期待未来！&lt;/p&gt;
</content:encoded></item><item><title>AI绘画工具webui简单入门 之 高清化</title><link>https://blog.ikeno.top/posts/sdguide2/</link><guid isPermaLink="true">https://blog.ikeno.top/posts/sdguide2/</guid><description>如何把生成的图高清化</description><pubDate>Sun, 19 Mar 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;strong&gt;Webui直接生成出来的图会很模糊，而且有些地方细节不是很好，如果用作4k壁纸会感觉很别扭，所以需要高清修复。&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;步骤&lt;/h2&gt;
&lt;p&gt;打开webui的图生图，调整参数为：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;缩放模式：&lt;code&gt;拉伸&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;采样方法(Sampler)：&lt;code&gt;DPM adaptive&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;宽度和高度在原有的基础上+64 ：&lt;code&gt;1280*720  --&amp;gt; 1344*784  &lt;/code&gt;&lt;/li&gt;
&lt;li&gt;重绘幅度(Denoising)：&lt;code&gt;0.15&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;脚本：&lt;code&gt;使用SD放大（SD upscale）&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;放大算法：&lt;code&gt;R-ESRGAN 4x+ Anime6B&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;其他参数默认即可，最后点击生成等待成果吧！&lt;/p&gt;
&lt;h2&gt;放大前：&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://img.ikeno.top/20230322xskx4sold.png&quot; alt=&quot;放大前|inline&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;放大后：&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://img.ikeno.top/202303223455651915.png&quot; alt=&quot;放大后|inline&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;其他&lt;/h2&gt;
&lt;p&gt;最后再推荐一个网站，如果想要把生成的图做壁纸的话，可以把动漫图片用算法最大放大到2k甚至4K。&lt;br /&gt;
&lt;strong&gt;&lt;a href=&quot;https://waifu2x.udp.jp/&quot;&gt;waifu2x&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
</content:encoded></item><item><title>写在地下城邂逅Ⅳ·灾厄篇·完结之后</title><link>https://blog.ikeno.top/posts/dungeondeai/</link><guid isPermaLink="true">https://blog.ikeno.top/posts/dungeondeai/</guid><description>地错第四季完结纪念</description><pubDate>Sat, 18 Mar 2023 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;BGM&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;TV动画《期待在地下城邂逅有错吗 Ⅳ 深章 灾厄篇》片尾曲
切り傷&lt;br /&gt;
歌手：sajou no hana&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&amp;lt;audio controls&amp;gt;
&amp;lt;source src=&quot;http://music.163.com/song/media/outer/url?id=2024541034.mp3&quot; type=&quot;audio/mpeg&quot;&amp;gt;
Your browser does not support the audio element.
&amp;lt;/audio&amp;gt;&lt;/p&gt;
&lt;h3&gt;感想&lt;/h3&gt;
&lt;p&gt;地错第四季也终于完结啦，新番里最好看的，不枉我每周四蹲首播啃生肉看完，这一季完完整整的把小说第14卷全部一次性讲完啦，完全是琉主场，琉的过去，眷族团灭的原因以及琉内心的变化，&lt;/p&gt;
&lt;p&gt;最后也终于释怀了自己的过去，找到了属于自己的正义! &lt;s&gt;加入白兔后宫团（不是&lt;/s&gt;&lt;/p&gt;
&lt;p&gt;不得不给节操社(J.C.STAFF)点个赞，还是保持了一贯的高制作水准，没有特别的崩坏，这一季看下来不管是剧情节奏还是画面音乐都非常棒！果然很喜欢这种王道的剧情啊，男孩与女孩的相遇，互相拯救的故事什么的，真是太棒了！&lt;/p&gt;
&lt;p&gt;最后一幕琉的笑容真的太 太 太好看啦！&lt;/p&gt;
&lt;p&gt;最近也把新的18卷小说看完了，白兔终于也到level5了，女神芙蕾雅线故事的高潮阶段也终于全部讲完，最后琉Level6救场的场景太帅啦！最后还直球告白贝尔！我宣布，&lt;strong&gt;琉股暴涨！诸君，我喜欢elf！&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/static/images/danjun.png&quot; alt=&quot;琉|inline&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;最后&lt;/h3&gt;
&lt;p&gt;外传剑姬神圣谭13 14卷貌似也快出了，看来大森真的想把每一个地错的角色魅力和眷族故事都展示出来，大森你给点力，学学某河马打字机，多出点！孩子爱看！&lt;/p&gt;
</content:encoded></item><item><title>如何部署gitalk作为评论系统</title><link>https://blog.ikeno.top/posts/gitalk-vercel/</link><guid isPermaLink="true">https://blog.ikeno.top/posts/gitalk-vercel/</guid><description>给自己的博客添加第三方的评论系统</description><pubDate>Wed, 15 Mar 2023 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Gitalk：&lt;/h2&gt;
&lt;p&gt;本来搭建好了博客，但是总觉的少了一些功能，没错，博客怎么能少了评论功能！（虽然可能没什么人评论）
但本着&lt;strong&gt;我可以不用但不能没有&lt;/strong&gt;的精神，刚好也有大大推荐了gitalk，就研究了下，发现集成第三方评论系统&lt;a href=&quot;https://github.com/gitalk/gitalk&quot;&gt;gitalk&lt;/a&gt;集成非常简单,只需要申请github账号的Secret然后前端里加下面一段代码就好了&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    &amp;lt;script&amp;gt;
      import &apos;gitalk/dist/gitalk.css&apos;;
      import Gitalk from &apos;gitalk&apos;;
    
      const gitalk = new Gitalk({
        clientID: myclientID,
        clientSecret: mySecret,
        repo: &apos;myrepo&apos;,
        owner: &apos;myusername&apos;,
        admin: [&apos;myusername&apos;],
        id: window.location.pathname,
        distractionFreeMode: false
      });
    
      gitalk.render(&apos;gitalk-container&apos;);
    &amp;lt;/script&amp;gt;
    
    &amp;lt;div id=&quot;gitalk-container&quot;&amp;gt;&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;就搞定了！但是又有一个问题，就是ClientID和Secret是明文的，放到github上太危险了，那有什么好方法呢，查了下，vercel是支持环境变量的，astro也支持，那就好办了。
查了一番官方文档，然后进行了以下操作，成功把vercel关联到项目，然后生成了.env文件。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npm i -g vercel
vercel --version
vercel login
vercel link --yes
vercel env ls
vercel env pull
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;接下来就简单了，只需要在Vercel的项目&lt;code&gt;Setting&lt;/code&gt;中把Environment Variables添加下&lt;code&gt;myclientID&lt;/code&gt;和&lt;code&gt;mySecret&lt;/code&gt;，然后在代码里替换成&lt;code&gt;import.meta.env.CLIENT_ID&lt;/code&gt;和&lt;code&gt;import.meta.env.CLIENT_Secret&lt;/code&gt;就搞定！现在再push到github仓库里，就没有安全隐患了~&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    &amp;lt;script&amp;gt;
      import &apos;gitalk/dist/gitalk.css&apos;;
      import Gitalk from &apos;gitalk&apos;;
    
      const gitalk = new Gitalk({
        clientID: import.meta.env.CLIENT_ID,
        clientSecret: import.meta.env.CLIENT_Secret,
        repo: &apos;myrepo&apos;,
        owner: &apos;myusername&apos;,
        admin: [&apos;myusername&apos;],
        id: window.location.pathname,
        distractionFreeMode: false
      });
    
      gitalk.render(&apos;gitalk-container&apos;);
    &amp;lt;/script&amp;gt;
    
    &amp;lt;div id=&quot;gitalk-container&quot;&amp;gt;&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Twikoo&lt;/h2&gt;
&lt;p&gt;astro也支持twikoo的评论系统，后端参考官网&lt;a href=&quot;https://twikoo.js.org/&quot;&gt;Twikoo文档&lt;/a&gt;,前端只需要&lt;code&gt;npm i twikoo&lt;/code&gt;然后配置&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;script&amp;gt;
      import twikoo from &quot;twikoo&quot;;
      twikoo({
        envId: &quot;https://your vercel site&quot;, // your environment ID or url
        el: &quot;#tcomment&quot;, // the container ID which will show the comment
        //lang: &quot;en-GB&quot;, // language for the comment template. for the full list, refer to https://github.com/imaegoo/twikoo/blob/main/src/client/utils/i18n/index.js
      }).then(() =&amp;gt; {
        console.log(&quot;comment loading success&quot;);
      });
&amp;lt;/script&amp;gt;
&amp;lt;div id=&quot;tcomment&quot;&amp;gt;&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;就可以了，当然别忘了再env.d.ts里吧&lt;code&gt;declare module &quot;twikoo&quot;;&lt;/code&gt;加上,声明下twikoo就不会报声明类型错误了。&lt;/p&gt;
&lt;h2&gt;Analytics&lt;/h2&gt;
&lt;p&gt;velcel还支持查看流量访问，只需要&lt;code&gt;npm i -g vercel&lt;/code&gt;安装完velcel后在官网项目里启动&lt;code&gt;Analytics&lt;/code&gt;里的&lt;code&gt;Audiences&lt;/code&gt;,然后创建完目录后在页面里引入下面的代码就搞定了~&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;script defer src=&quot;/_vercel/insights/script.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>AI绘画工具webui简单入门 之 工具安装</title><link>https://blog.ikeno.top/posts/sdguide1/</link><guid isPermaLink="true">https://blog.ikeno.top/posts/sdguide1/</guid><description>介绍一下如何使用webui工具生成绘画</description><pubDate>Tue, 14 Mar 2023 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;工具下载地址：&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/AUTOMATIC1111/stable-diffusion-webui&quot;&gt;stable-diffusion-webui&lt;/a&gt; (推荐)&lt;/p&gt;
&lt;p&gt;1.下载完成后运行webui-user.bat文件，会下载相关的依赖，国内网络可能需要翻墙或者用镜像站(修改launch.py文件把github地址都改为国内的镜像站地址)，也可以推荐&lt;code&gt;dev-sidecar&lt;/code&gt;这个免费工具加速github。
或者可以直接上b站检索&lt;code&gt;秋葉aaaki&lt;/code&gt;up主，直接下载整合包,再或者直接下载第三方&lt;a href=&quot;https://github.com/EmpireMediaScience/A1111-Web-UI-Installer/releases/download/V1.7.0/A1111.Web.UI.Autoinstaller.v1.7.0.exe&quot;&gt;客户端&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;2.安装完成后再运行webui-user.bat文件后等待出现&lt;code&gt;Running on local URL:  http://127.0.0.1:7860&lt;/code&gt; 就可以访问127.0.0.1:7860打开webui页面啦。&lt;/p&gt;
&lt;p&gt;3.打开后先别急，需要安装一些基础的插件，点击扩展，从网址安装，然后把git地址粘贴后安装即可，我这边推荐几个最常用的：&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/dtlnor/stable-diffusion-webui-localization-zh_CN&quot;&gt;汉化插件&lt;/a&gt;  安装完后：setting &amp;gt;&amp;gt; user interface &amp;gt;&amp;gt; Localization (requires restart) &amp;gt;&amp;gt; 选择 zh-CN &amp;gt;&amp;gt; apply setting &amp;gt;&amp;gt; reload ui&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/Mikubill/sd-webui-controlnet.git&quot;&gt;Control插件&lt;/a&gt;  用来加载lora相关模型。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/DominikDoom/a1111-sd-webui-tagcomplete&quot;&gt;关键字补全插件&lt;/a&gt;  用来补全prompt关键字的，适合英文小白~&lt;/p&gt;
&lt;p&gt;4.安装完成后到&lt;code&gt;设置&lt;/code&gt;里的&lt;code&gt;显示所有页面&lt;/code&gt;，&lt;code&gt;快捷设置列表&lt;/code&gt;填写&lt;code&gt;sd_model_checkpoint,sd_vae&lt;/code&gt;然后重启webui，基础工具就安装好啦，接下来就是模型了！&lt;/p&gt;
&lt;h2&gt;模型下载地址：&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://civitai.com/&quot;&gt;Civitai&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;1.注册好账号后就可以查看很多网上分享的模型，找一个你最喜欢的，例如Counterfeit，点击下载即可。&lt;/p&gt;
&lt;p&gt;2.下载好后记得要把模型放到 &lt;code&gt;\webui\models\Stable-diffusion&lt;/code&gt; 目录下，这样才会读取到模型，Lora的模型要放到&lt;code&gt;\webui\models\Lora&lt;/code&gt;里,有模型后就可以生成AI图片啦！&lt;/p&gt;
&lt;p&gt;当然只是简单的模型还是不够的，后续进阶还需要用到vae模型，EasyNegative模型，lora模型，openpose模型，高清化等，这个后续有时间再梳理下，慢慢学习进步！&lt;/p&gt;
&lt;p&gt;分享例子：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;关键字：
((masterpiece,best quality)),1girl, solo, animal ears, rabbit, barefoot, knees up, dress, sitting, rabbit ears, short sleeves, looking at viewer, grass, short hair, smile, white hair, puffy sleeves, outdoors, puffy short sleeves, bangs, on ground, full body, animal, white dress, sunlight, brown eyes, dappled sunlight, day, depth of field&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;Negative关键字：
EasyNegative, extra fingers,fewer fingers,&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;Steps: 20, Sampler: DPM++ 2M Karras, CFG scale: 10, Seed: 414619472, Size: 512x728, Model hash: 59ea4aa1d8, Model: cetusMix_cetusVersion3, Denoising strength: 0.7, Hires upscale: 2, Hires steps: 20, Hires upscaler: SwinIR_4x&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&quot;https://i2.100024.xyz/2023/03/14/xskx4s.webp&quot; alt=&quot;兔子|inline&quot; /&gt;&lt;/p&gt;
</content:encoded></item><item><title>grafanadb迁移到mysql</title><link>https://blog.ikeno.top/posts/grafanadb/</link><guid isPermaLink="true">https://blog.ikeno.top/posts/grafanadb/</guid><description>把grafana默认的sqllite3数据库迁移到mysql</description><pubDate>Mon, 13 Mar 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;因为安全需求需要把grafana的数据库迁移到云上mysql，所以总结了一些遇到的问题：&lt;/p&gt;
&lt;p&gt;先说步骤：&lt;/p&gt;
&lt;p&gt;1.&lt;code&gt;sqlite3 /var/lib/grafana/grafana.db .dump &amp;gt; grafana.sql&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;2.&lt;code&gt;shell mysql -u username -p -h localhost database_name &amp;lt; grafana.sql &lt;/code&gt;&lt;/p&gt;
&lt;p&gt;3.修改/etc/grafana/grafana.ini，添加&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;database type = mysql
host = localhost:3306
name = database_name
user = username
password = userpassword
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;4.然后重启grafana即可
&lt;code&gt;systemctl restart grafana-server&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;PS:但是mysql语法和sqllite语法是有区别的，所以第二步之前需要修改grafana.sql的语法格式：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;PRAGMA foreign_keys=OFF;BEGIN TRANSACTION;没有这个语句，需要删掉&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;🐦📊之类的表情最好不要有，存储会报错，虽然也可以解决（data是列名，TEXT是类型)：
ALTER TABLE dashboard_version CHANGE data data  TEXT CHARACTER SET utf8mb4 ;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;AUTOINCREMENT  改为 AUTO_INCREMENT&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;TEXT默认一般不能有DEFAULT，最好改为VARCHAR(255)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;TEXT格式是不能作为UNIQUE  INDEX的  必须要修改为VARCHAR(255)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;datetime 可能会出现格式问题 2023-03-02 14:36:35.578526458+8:00  只要把最后的+8:00去掉就可以了&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>kubectl常用命令</title><link>https://blog.ikeno.top/posts/kubectl/</link><guid isPermaLink="true">https://blog.ikeno.top/posts/kubectl/</guid><description>kubectl是k8s里非常频繁使用的命令，常用的几个使用方法</description><pubDate>Fri, 10 Mar 2023 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;用于替换现有的容器镜像或版本&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;kubectl set image deployment/DeploymentName -n NAMESPACE *=slpcat/rocketmq-exporter:latest
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;滚动的重启容器(优雅)&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;kubectl rollout restart deployment/DeploymentName -n NAMESPACE
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;回滚容器&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;kubectl rollout undo deployment/DeploymentName -n NAMESPACE`
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;设置/修改环境变量&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;kubectl set env deployment nginx-deploy DEPLOY_DATE=&quot;$(date)&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;通过bash获得 pod 中某个容器的TTY，相当于登录容器&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;kubectl exec -it &amp;lt;pod-name&amp;gt; -c &amp;lt;container-name&amp;gt;  bash
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;查看容器的日志&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;kubectl logs &amp;lt;pod-name&amp;gt;
kubectl logs -f &amp;lt;pod-name&amp;gt; ### 实时查看日志
kubectl log  &amp;lt;pod-name&amp;gt;  -c &amp;lt;container_name&amp;gt; ### 若 pod 只有一个容器，可以不加 -c 
kubectl logs -l app=frontend ### 返回所有标记为 app=frontend 的 pod 的合并日志。
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;查看注释&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;kubectl explain pod
kubectl explain pod.apiVersion
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;查看节点 labels&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;kubectl get node --show-labels
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;重启 pod&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;kubectl get pod &amp;lt;POD名称&amp;gt; -n &amp;lt;NAMESPACE名称&amp;gt; -o yaml | kubectl replace --force -f -
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;修改网络类型&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;kubectl patch service istio-ingressgateway -n istio-system -p &apos;{&quot;spec&quot;:{&quot;type&quot;:&quot;NodePort&quot;}}&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;伸缩 pod 副本&lt;/h3&gt;
&lt;h3&gt;可用于将Deployment及其Pod缩小为零个副本，实际上杀死了所有副本。当您将其缩放回1/1时，将创建一个新的Pod，重新启动您的应用程序。`&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;kubectl scale deploy/nginx-1 --replicas=0
kubectl scale deploy/nginx-1 --replicas=1
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;查看前一个 pod 的日志，logs -p 选项&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;kubectl logs --tail 100 -p user-klvchen-v1.0-6f67dcc46b-5b4qb &amp;gt; pre.log
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;复制容器里的文件&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;kubectl cp my-namespace/my-pod:/path/to/remote/file /path/to/local/file
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;一些Pod的yaml文件的资源清单和解释&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;apiVersion: v1     #必选，版本号，例如v1
kind: Pod       　 #必选，资源类型，例如 Pod
metadata:       　 #必选，元数据
  name: string     #必选，Pod名称
  namespace: string  #Pod所属的命名空间,默认为&quot;default&quot;
  labels:       　　  #自定义标签列表
    - name: string      　          
spec:  #必选，Pod中容器的详细定义
  containers:  #必选，Pod中容器列表
  - name: string   #必选，容器名称
    image: string  #必选，容器的镜像名称
    imagePullPolicy: [ Always|Never|IfNotPresent ]  #获取镜像的策略 
    command: [string]   #容器的启动命令列表，如不指定，使用打包时使用的启动命令
    args: [string]      #容器的启动命令参数列表
    workingDir: string  #容器的工作目录
    volumeMounts:       #挂载到容器内部的存储卷配置
    - name: string      #引用pod定义的共享存储卷的名称，需用volumes[]部分定义的的卷名
      mountPath: string #存储卷在容器内mount的绝对路径，应少于512字符
      readOnly: boolean #是否为只读模式
    ports: #需要暴露的端口库号列表
    - name: string        #端口的名称
      containerPort: int  #容器需要监听的端口号
      hostPort: int       #容器所在主机需要监听的端口号，默认与Container相同
      protocol: string    #端口协议，支持TCP和UDP，默认TCP
    env:   #容器运行前需设置的环境变量列表
    - name: string  #环境变量名称
      value: string #环境变量的值
    resources: #资源限制和请求的设置
      limits:  #资源限制的设置
        cpu: string     #Cpu的限制，单位为core数，将用于docker run --cpu-shares参数
        memory: string  #内存限制，单位可以为Mib/Gib，将用于docker run --memory参数
      requests: #资源请求的设置
        cpu: string    #Cpu请求，容器启动的初始可用数量
        memory: string #内存请求,容器启动的初始可用数量
    lifecycle: #生命周期钩子
        postStart: #容器启动后立即执行此钩子,如果执行失败,会根据重启策略进行重启
        preStop: #容器终止前执行此钩子,无论结果如何,容器都会终止
    livenessProbe:  #对Pod内各容器健康检查的设置，当探测无响应几次后将自动重启该容器
      exec:       　 #对Pod容器内检查方式设置为exec方式
        command: [string]  #exec方式需要制定的命令或脚本
      httpGet:       #对Pod内个容器健康检查方法设置为HttpGet，需要制定Path、port
        path: string
        port: number
        host: string
        scheme: string
        HttpHeaders:
        - name: string
          value: string
      tcpSocket:     #对Pod内个容器健康检查方式设置为tcpSocket方式
         port: number
       initialDelaySeconds: 0       #容器启动完成后首次探测的时间，单位为秒
       timeoutSeconds: 0    　　    #对容器健康检查探测等待响应的超时时间，单位秒，默认1秒
       periodSeconds: 0     　　    #对容器监控检查的定期探测时间设置，单位秒，默认10秒一次
       successThreshold: 0
       failureThreshold: 0
       securityContext:
         privileged: false
  restartPolicy: [Always | Never | OnFailure]  #Pod的重启策略
  nodeName: &amp;lt;string&amp;gt; #设置NodeName表示将该Pod调度到指定到名称的node节点上
  nodeSelector: obeject #设置NodeSelector表示将该Pod调度到包含这个label的node上
  imagePullSecrets: #Pull镜像时使用的secret名称，以key：secretkey格式指定
  - name: string
  hostNetwork: false   #是否使用主机网络模式，默认为false，如果设置为true，表示使用宿主机网络
  volumes:   #在该pod上定义共享存储卷列表
  - name: string    #共享存储卷名称 （volumes类型有很多种）
    emptyDir: {}       #类型为emtyDir的存储卷，与Pod同生命周期的一个临时目录。为空值
    hostPath: string   #类型为hostPath的存储卷，表示挂载Pod所在宿主机的目录
      path: string      　　        #Pod所在宿主机的目录，将被用于同期中mount的目录
    secret:       　　　#类型为secret的存储卷，挂载集群与定义的secret对象到容器内部
      scretname: string  
      items:     
      - key: string
        path: string
    configMap:         #类型为configMap的存储卷，挂载预定义的configMap对象到容器内部
      name: string
      items:
      - key: string
        path: string

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在kubernetes中基本所有资源的一级属性都是一样的，主要包含5部分：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;apiVersion 版本，由kubernetes内部定义，版本号必须可以用 kubectl api-versions 查询到&lt;/li&gt;
&lt;li&gt;kind 类型，由kubernetes内部定义，版本号必须可以用 kubectl api-resources 查询到&lt;/li&gt;
&lt;li&gt;metadata 元数据，主要是资源标识和说明，常用的有name、namespace、labels等&lt;/li&gt;
&lt;li&gt;spec 描述，这是配置中最重要的一部分，里面是对各种资源配置的详细描述&lt;/li&gt;
&lt;li&gt;status 状态信息，里面的内容不需要定义，由kubernetes自动生成&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;spec&lt;/strong&gt;描述是主要关注的：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;containers &amp;lt;[]Object&amp;gt; 容器列表，用于定义容器的详细信息&lt;/li&gt;
&lt;li&gt;nodeName 根据nodeName的值将pod调度到指定的Node节点上&lt;/li&gt;
&lt;li&gt;nodeSelector &amp;lt;map[]&amp;gt; 根据NodeSelector中定义的信息选择将该Pod调度到包含这些label的Node 上&lt;/li&gt;
&lt;li&gt;hostNetwork 是否使用主机网络模式，默认为false，如果设置为true，表示使用宿主机网络&lt;/li&gt;
&lt;li&gt;volumes &amp;lt;[]Object&amp;gt; 存储卷，用于定义Pod上面挂在的存储信息&lt;/li&gt;
&lt;li&gt;restartPolicy 重启策略，表示Pod在遇到故障的时候的处理策略&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;这里推荐一个k8s的可视化工具 k9s&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;详细介绍：&lt;/strong&gt; &lt;a href=&quot;https://mp.weixin.qq.com/s?__biz=MzI0MDQ4MTM5NQ==&amp;amp;mid=2247510913&amp;amp;idx=2&amp;amp;sn=202da04302a9c2d1e14d709f3a833b06&amp;amp;chksm=e918ce9dde6f478b9b83c31898277473b747c6719bbbf81ad95350695201e619e4eb4379ead7&amp;amp;scene=178&amp;amp;cur_album_id=1790241575034290179#rd&quot;&gt;Kubernetes 集群管理工具 K9S&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;github:&lt;/strong&gt; &lt;a href=&quot;https://github.com/derailed/k9s/releases&quot;&gt;项目地址&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;k9s是基于终端的资源仪表板。它只有一个命令行界面。无论在Kubernetes仪表板Web UI上做什么，都可以在终端使用K9s仪表板工具进行相同的操作。k9s持续关注Kubernetes集群，并提供命令以使用集群上定义的资源。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;使用：下载后放到/bin/k9s 即可使用，需要安装kubectl
example: k9s -n ops  #即可查看ops的namespace
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;一些K8s的文章&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://openai.com/research/scaling-kubernetes-to-7500-nodes&quot;&gt;【openai】我们把k8s集群扩展到了7500个节点&lt;/a&gt;&lt;/p&gt;
</content:encoded></item><item><title>开始写博客啦</title><link>https://blog.ikeno.top/posts/talk01/</link><guid isPermaLink="true">https://blog.ikeno.top/posts/talk01/</guid><description>总之先mark一些不错的网站吧，说不定哪天用的上呢</description><pubDate>Thu, 09 Mar 2023 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;开始试试写博客，虽然感觉没啥特别要记录的，但也算一个小小的开始吧。&lt;/h2&gt;
&lt;p&gt;先记录一些实用的网址吧：&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://png.cm/&quot;&gt;简单图床&lt;/a&gt;  图床（不推荐）&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://vercel.com/&quot;&gt;vercel&lt;/a&gt; 海外站点托管平台&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://markdown.lovejade.cn/&quot;&gt;MARKDOWN 编辑器&lt;/a&gt; MARKDOWN在线编辑器&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.sojson.com/convert/subnetmask.html&quot;&gt;子网掩码计算&lt;/a&gt; 子网掩码计算&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://tool.lu/timestamp/&quot;&gt;unix 时间戳转换&lt;/a&gt; 时间戳转换&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://ai.com&quot;&gt;CHATGPT&lt;/a&gt; AICHAT&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://k8syaml.com/&quot;&gt;K8Syaml 生成器&lt;/a&gt; 在线生成 K8S 的 yaml 文件&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.json.cn/&quot;&gt;JSON 在线解析&lt;/a&gt; 在线解析 Json 格式数据&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://geotraceroute.com/&quot;&gt;路由跳转跟踪&lt;/a&gt; 查询路由跳转&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://sms-activate.org/cn&quot;&gt;海外手机号&lt;/a&gt; 海外的 SMS 手机号&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://freedns.afraid.org/subdomain/&quot;&gt;freeDNS&lt;/a&gt; 免费的 DNS 域名&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://huggingface.co/&quot;&gt;HuggingFace&lt;/a&gt; 相关模型下载&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/lllyasviel/ControlNet&quot;&gt;ControlNet&lt;/a&gt; SD 的插件&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://app.simplelogin.io/dashboard/&quot;&gt;simplelogin&lt;/a&gt; 匿名邮箱&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://stackblitz.com/&quot;&gt;stackblitz&lt;/a&gt; 在线的IDE编辑器&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://console.upstash.com/kafka&quot;&gt;upstash&lt;/a&gt; 免费的海外redis,kafka数据库&lt;/p&gt;
&lt;p&gt;&amp;lt;audio controls&amp;gt;
&amp;lt;source src=&quot;http://music.163.com/song/media/outer/url?id=409872504.mp3&quot; type=&quot;audio/mpeg&quot;&amp;gt;
Your browser does not support the audio element.
&amp;lt;/audio&amp;gt;&lt;/p&gt;
</content:encoded></item><item><title>隐私政策URL</title><link>https://blog.ikeno.top/posts/security_url/</link><guid isPermaLink="true">https://blog.ikeno.top/posts/security_url/</guid><description>mazeball的隐私政策URL</description><pubDate>Sun, 01 Jan 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;本软件尊重并保护所有使用服务用户的个人隐私权。为了给您提供更准确、更有个性化的服务，本软件会按照本隐私权政策的规定使用和披露您的个人信息。但本软件将以高度的勤勉、审慎义务对待这些信息。除本隐私权政策另有规定外，在未征得您事先许可的情况下，本软件不会将这些信息对外披露或向第三方提供。本软件会不时更新本隐私权政策。您在同意本软件服务使用协议之时，即视为您已经同意本隐私权政策全部内容。本隐私权政策属于本软件服务使用协议不可分割的一部分。&lt;/p&gt;
&lt;p&gt;1.适用范围&lt;/p&gt;
&lt;p&gt;a)在您使用本软件网络服务，本软件自动接收并记录的您的手机上的信息，包括但不限于使用的语言、访问日期和时间、软硬件特征信息及您需求的网页记录等数据；&lt;/p&gt;
&lt;p&gt;2.信息的使用&lt;/p&gt;
&lt;p&gt;a)在获得您的数据之后，本软件会将其上传至服务器，以生成您的排行榜数据，以便您能够更好地使用服务。&lt;/p&gt;
&lt;p&gt;3.信息披露&lt;/p&gt;
&lt;p&gt;a)本软件不会将您的信息披露给不受信任的第三方。&lt;/p&gt;
&lt;p&gt;b)根据法律的有关规定，或者行政或司法机构的要求，向第三方或者行政、司法机构披露；&lt;/p&gt;
&lt;p&gt;c)如您出现违反中国有关法律、法规或者相关规则的情况，需要向第三方披露；&lt;/p&gt;
&lt;p&gt;4.信息存储和交换&lt;/p&gt;
&lt;p&gt;本软件收集的有关您的信息和资料将保存在本软件及（或）其关联公司的服务器上，这些信息和资料可能传送至您所在国家、地区或本软件收集信息和资料所在地的境外并在境外被访问、存储和展示。&lt;/p&gt;
</content:encoded></item><item><title>Markdown Example</title><link>https://blog.ikeno.top/posts/markdown/</link><guid isPermaLink="true">https://blog.ikeno.top/posts/markdown/</guid><description>A simple example of a Markdown blog post.</description><pubDate>Sat, 01 Oct 2022 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;An h1 header&lt;/h1&gt;
&lt;p&gt;Paragraphs are separated by a blank line.&lt;/p&gt;
&lt;p&gt;2nd paragraph. &lt;em&gt;Italic&lt;/em&gt;, &lt;strong&gt;bold&lt;/strong&gt;, and &lt;code&gt;monospace&lt;/code&gt;. Itemized lists
look like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;this one&lt;/li&gt;
&lt;li&gt;that one&lt;/li&gt;
&lt;li&gt;the other one&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Note that --- not considering the asterisk --- the actual text
content starts at 4-columns in.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Block quotes are
written like so.&lt;/p&gt;
&lt;p&gt;They can span multiple paragraphs,
if you like.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Use 3 dashes for an em-dash. Use 2 dashes for ranges (ex., &quot;it&apos;s all
in chapters 12--14&quot;). Three dots ... will be converted to an ellipsis.
Unicode is supported. ☺&lt;/p&gt;
&lt;h2&gt;An h2 header&lt;/h2&gt;
&lt;p&gt;Here&apos;s a numbered list:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;first item&lt;/li&gt;
&lt;li&gt;second item&lt;/li&gt;
&lt;li&gt;third item&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Note again how the actual text starts at 4 columns in (4 characters
from the left side). Here&apos;s a code sample:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# Let me re-iterate ...
for i in 1 .. 10 { do-something(i) }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As you probably guessed, indented 4 spaces. By the way, instead of
indenting the block, you can use delimited blocks, if you like:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;define foobar() {
    print &quot;Welcome to flavor country!&quot;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;(which makes copying &amp;amp; pasting easier). You can optionally mark the
delimited block for Pandoc to syntax highlight it:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import time
# Quick, count to ten!
for i in range(10):
    # (but not *too* quick)
    time.sleep(0.5)
    print i
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;An h3 header&lt;/h3&gt;
&lt;p&gt;Now a nested list:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;First, get these ingredients:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;carrots&lt;/li&gt;
&lt;li&gt;celery&lt;/li&gt;
&lt;li&gt;lentils&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Boil some water.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Dump everything in the pot and follow
this algorithm:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; find wooden spoon
 unimage pot
 stir
 image pot
 balance wooden spoon precariously on pot handle
 wait 10 minutes
 goto first step (or shut off burner when done)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Do not bump wooden spoon or it will fall.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Notice again how text always lines up on 4-space indents (including
that last line which continues item 3 above).&lt;/p&gt;
&lt;p&gt;Here&apos;s a link to &lt;a href=&quot;http://foo.bar&quot;&gt;a website&lt;/a&gt;, to a &lt;a href=&quot;local-doc.html&quot;&gt;local
doc&lt;/a&gt;, and to a &lt;a href=&quot;#an-h2-header&quot;&gt;section heading in the current
doc&lt;/a&gt;. Here&apos;s a footnote [^1].&lt;/p&gt;
&lt;p&gt;[^1]: Footnote text goes here.&lt;/p&gt;
&lt;p&gt;Tables can look like this:&lt;/p&gt;
&lt;p&gt;size material color&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;9 leather brown
10 hemp canvas natural
11 glass transparent&lt;/p&gt;
&lt;p&gt;Table: Shoes, their sizes, and what they&apos;re made of&lt;/p&gt;
&lt;p&gt;(The above is the caption for the table.) Pandoc also supports
multi-line tables:&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;keyword text&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;red Sunsets, apples, and
other red or reddish
things.&lt;/p&gt;
&lt;p&gt;green Leaves, grass, frogs
and other things it&apos;s
not easy being.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;A horizontal rule follows.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Here&apos;s a definition list:&lt;/p&gt;
&lt;p&gt;apples
: Good for making applesauce.
oranges
: Citrus!
tomatoes
: There&apos;s no &quot;e&quot; in tomatoe.&lt;/p&gt;
&lt;p&gt;Again, text is indented 4 spaces. (Put a blank line between each
term/definition pair to spread things out more.)&lt;/p&gt;
&lt;p&gt;Here&apos;s a &quot;line block&quot;:&lt;/p&gt;
&lt;p&gt;| Line one
| Line too
| Line tree&lt;/p&gt;
&lt;p&gt;and images can be specified like so:&lt;/p&gt;
&lt;p&gt;Inline math equations go in like so: $\omega = d\phi / dt$. Display
math should get its own line and be put in in double-dollarsigns:&lt;/p&gt;
&lt;p&gt;$$I = \int \rho R^{2} dV$$&lt;/p&gt;
&lt;p&gt;And note that you can backslash-escape any punctuation characters
which you wish to be displayed literally, ex.: `foo`, *bar*, etc.&lt;/p&gt;
</content:encoded></item><item><title>Include Video in the Posts</title><link>https://blog.ikeno.top/posts/video/</link><guid isPermaLink="true">https://blog.ikeno.top/posts/video/</guid><description>This post demonstrates how to include embedded video in a blog post.</description><pubDate>Mon, 01 Aug 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Just copy the embed code from YouTube or other platforms, and paste it in the markdown file.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;---
title: Include Video in the Post
published: 2023-10-19
// ...
---

&amp;lt;iframe width=&quot;100%&quot; height=&quot;468&quot; src=&quot;https://www.youtube.com/embed/5gIf0_xpFPI?si=N1WTorLKL0uwLsU_&quot; title=&quot;YouTube video player&quot; frameborder=&quot;0&quot; allowfullscreen&amp;gt;&amp;lt;/iframe&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;YouTube&lt;/h2&gt;
&lt;p&gt;&amp;lt;iframe width=&quot;100%&quot; height=&quot;468&quot; src=&quot;https://www.youtube.com/embed/5gIf0_xpFPI?si=N1WTorLKL0uwLsU_&quot; title=&quot;YouTube video player&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot; allowfullscreen&amp;gt;&amp;lt;/iframe&amp;gt;&lt;/p&gt;
&lt;h2&gt;Bilibili&lt;/h2&gt;
&lt;p&gt;&amp;lt;iframe width=&quot;100%&quot; height=&quot;468&quot; src=&quot;//player.bilibili.com/player.html?bvid=BV1fK4y1s7Qf&amp;amp;p=1&quot; scrolling=&quot;no&quot; border=&quot;0&quot; frameborder=&quot;no&quot; framespacing=&quot;0&quot; allowfullscreen=&quot;true&quot;&amp;gt; &amp;lt;/iframe&amp;gt;&lt;/p&gt;
</content:encoded></item><item><title>Markdown Extended Features</title><link>https://blog.ikeno.top/posts/markdown-extended/</link><guid isPermaLink="true">https://blog.ikeno.top/posts/markdown-extended/</guid><description>Read more about Markdown features in Fuwari</description><pubDate>Sun, 01 May 2022 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;GitHub Repository Cards&lt;/h2&gt;
&lt;p&gt;You can add dynamic cards that link to GitHub repositories, on page load, the repository information is pulled from the GitHub API.&lt;/p&gt;
&lt;p&gt;::github{repo=&quot;Fabrizz/MMM-OnSpotify&quot;}&lt;/p&gt;
&lt;p&gt;Create a GitHub repository card with the code &lt;code&gt;::github{repo=&quot;&amp;lt;owner&amp;gt;/&amp;lt;repo&amp;gt;&quot;}&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;::github{repo=&quot;saicaca/fuwari&quot;}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Admonitions&lt;/h2&gt;
&lt;p&gt;Following types of admonitions are supported: &lt;code&gt;note&lt;/code&gt; &lt;code&gt;tip&lt;/code&gt; &lt;code&gt;important&lt;/code&gt; &lt;code&gt;warning&lt;/code&gt; &lt;code&gt;caution&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;:::note
Highlights information that users should take into account, even when skimming.
:::&lt;/p&gt;
&lt;p&gt;:::tip
Optional information to help a user be more successful.
:::&lt;/p&gt;
&lt;p&gt;:::important
Crucial information necessary for users to succeed.
:::&lt;/p&gt;
&lt;p&gt;:::warning
Critical content demanding immediate user attention due to potential risks.
:::&lt;/p&gt;
&lt;p&gt;:::caution
Negative potential consequences of an action.
:::&lt;/p&gt;
&lt;h3&gt;Basic Syntax&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;:::note
Highlights information that users should take into account, even when skimming.
:::

:::tip
Optional information to help a user be more successful.
:::
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Custom Titles&lt;/h3&gt;
&lt;p&gt;The title of the admonition can be customized.&lt;/p&gt;
&lt;p&gt;:::note[MY CUSTOM TITLE]
This is a note with a custom title.
:::&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;:::note[MY CUSTOM TITLE]
This is a note with a custom title.
:::
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;GitHub Syntax&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;[!TIP]
&lt;a href=&quot;https://github.com/orgs/community/discussions/16925&quot;&gt;The GitHub syntax&lt;/a&gt; is also supported.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;&amp;gt; [!NOTE]
&amp;gt; The GitHub syntax is also supported.

&amp;gt; [!TIP]
&amp;gt; The GitHub syntax is also supported.
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>Simple Guides for Fuwari</title><link>https://blog.ikeno.top/posts/guide/</link><guid isPermaLink="true">https://blog.ikeno.top/posts/guide/</guid><description>How to use this blog template.</description><pubDate>Fri, 01 Apr 2022 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;image image source: &lt;a href=&quot;https://image.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/208fc754-890d-4adb-9753-2c963332675d/width=2048/01651-1456859105-(colour_1.5),girl,_Blue,yellow,green,cyan,purple,red,pink,_best,8k,UHD,masterpiece,male%20focus,%201boy,gloves,%20ponytail,%20long%20hair,.jpeg&quot;&gt;Source&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This blog template is built with &lt;a href=&quot;https://astro.build/&quot;&gt;Astro&lt;/a&gt;. For the things that are not mentioned in this guide, you may find the answers in the &lt;a href=&quot;https://docs.astro.build/&quot;&gt;Astro Docs&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Front-matter of Posts&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;---
title: My First Blog Post
published: 2023-09-09
description: This is the first post of my new Astro blog.
image: ./image.jpg
tags: [Foo, Bar]
category: Front-end
draft: false
---
&lt;/code&gt;&lt;/pre&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Attribute&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;title&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The title of the post.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;published&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The date the post was published.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;description&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;A short description of the post. Displayed on index page.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;image&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The image image path of the post.&amp;lt;br/&amp;gt;1. Start with &lt;code&gt;http://&lt;/code&gt; or &lt;code&gt;https://&lt;/code&gt;: Use web image&amp;lt;br/&amp;gt;2. Start with &lt;code&gt;/&lt;/code&gt;: For image in &lt;code&gt;public&lt;/code&gt; dir&amp;lt;br/&amp;gt;3. With none of the prefixes: Relative to the markdown file&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;tags&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The tags of the post.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;category&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The category of the post.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;draft&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;If this post is still a draft, which won&apos;t be displayed.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;Where to Place the Post Files&lt;/h2&gt;
&lt;p&gt;Your post files should be placed in &lt;code&gt;src/content/posts/&lt;/code&gt; directory. You can also create sub-directories to better organize your posts and assets.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;src/content/posts/
├── post-1.md
└── post-2/
    ├── image.png
    └── index.md
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>Golang net/http &amp; HTTP Serve 源码分析</title><link>https://blog.ikeno.top/posts/golang/</link><guid isPermaLink="true">https://blog.ikeno.top/posts/golang/</guid><description>很多Go web框架都通过封装 net/http 来实现核心功能，因此学习 net/http 是研究 Gin等框架的基础。</description><pubDate>Wed, 09 Mar 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;em&gt;&lt;strong&gt;这篇是转载的！不过还是很有用的，go的http可以推荐用gin，下次梳理下gin的教程好了&lt;/strong&gt;&lt;/em&gt;
&lt;img src=&quot;https://pic.lookcos.cn/i/usr/uploads/2023/02/3697706570.png&quot; alt=&quot;Go HTTP Server的大致处理流程|wide&quot; /&gt;&lt;/p&gt;
&lt;p&gt;服务器在收到请求时，首先进入路由 Router，接着路由会根据 request 请求的路径，找到对应的处理器(Handler)，处理器再根据 request 进行处理并构造 response 进行返回。&lt;/p&gt;
&lt;h2&gt;利用标准库实现一个简单HTTP Server&lt;/h2&gt;
&lt;p&gt;向&lt;strong&gt;main.go&lt;/strong&gt;文件写入如下内容：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;package main

import (
    &quot;fmt&quot;
    &quot;net/http&quot;
)

// 方法一
type HelloContext struct {
    content string
}

func(h *HelloContext) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, h.content)
}

// 方法二
func helloHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, &quot;Hello, net/http! v2\n&quot;)
}

func main() {
    http.Handle(&quot;/v1&quot;, &amp;amp;HelloContext{content: &quot;Hello, net/http! v1\n&quot;})
    http.HandleFunc(&quot;/v2&quot;, helloHandler)
    http.ListenAndServe(&quot;:8080&quot;, nil)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;运行后，可以用 curl 工具进行测试：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mac:~ $ curl 127.0.0.1:8080/v1
Hello, net/http! v1
mac:~ $ curl 127.0.0.1:8080/v2
Hello, net/http! v2
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这段代码我们用 http.Handle 和 http.HandleFunc 两种方法分别在路径 /v1 和 /v2 上注册了两个 http.Handler。注意：Handle 和 Handler 是两个东西。&lt;br /&gt;
这两个 Handler 都对 request 进行了处理，并且通过 fmt.Fprintf 方法写入并返回数据。&lt;/p&gt;
&lt;h2&gt;处理器&lt;/h2&gt;
&lt;h3&gt;http.Handler&lt;/h3&gt;
&lt;p&gt;先来了解一下 http.Handler (处理器)，&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;type Handler interface {
 ServeHTTP(ResponseWriter, *Request)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;它被定义为一个拥有 ServeHTTP 方法的接口，也就是说任何类型，只要实现了 ServeHTTP 方法，就实现了 http.Handler 接口。&lt;/p&gt;
&lt;p&gt;ServeHTTP 方法会读取 *Request 信息，并且向 ResponseWriter 写入 header 与 body 内容。&lt;/p&gt;
&lt;h2&gt;路由注册&lt;/h2&gt;
&lt;h3&gt;http.Handle&lt;/h3&gt;
&lt;p&gt;从 main函数出发，来看 http.Handle 函数源码:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func Handle(pattern string, handler Handler) { 
  DefaultServeMux.Handle(pattern, handler) 
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可以看到，http.Handle 函数调用了 DefaultServeMux.Handle 方法。&lt;/p&gt;
&lt;h3&gt;http.HandleFunc&lt;/h3&gt;
&lt;p&gt;再来看 http.HandleFunc 的源码：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
 DefaultServeMux.HandleFunc(pattern, handler)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;它也调用了 DefaultServeMux.HandleFunc 方法，再看此方法源码：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
 if handler == nil {
  panic(&quot;http: nil handler&quot;)
 }
 mux.Handle(pattern, HandlerFunc(handler))
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;不难看出，http.Handle 和 http.HandleFunc 都调用了一个和 ServeMux 对象的 Handle 方法有关。&lt;/p&gt;
&lt;p&gt;这两个方法的作用都是将传入的处理器 (Handler) 注册到对应的路由规则 (pattern)上。&lt;/p&gt;
&lt;p&gt;比如，倒数第三行将 处理器 helloHandler 注册到了路由规则 (路径) /v2 上。这样，当 HTTP 请求的地址是 /v2的时候，就由处理器 helloHandler 来负责处理请求，并且响应。&lt;/p&gt;
&lt;p&gt;mux.Handle 方法中还有一个 http.HandlerFunc ，注意不是 HandleFunc。&lt;/p&gt;
&lt;h2&gt;适配器与处理器&lt;/h2&gt;
&lt;h3&gt;http.HandlerFunc&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;type HandlerFunc func(ResponseWriter, *Request)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;HandlerFunc 可以理解为一个适配器，它允许使用普通的函数成为处理器 Handler 对象，前提是这个普通函数拥有 func(ResponseWriter, *Request) 签名。&lt;/p&gt;
&lt;p&gt;上文说到，任何类型只要实现了 ServeHTTP 方法，那它就实现了 Handler接口，它就是一个 Handler 类型。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
 f(w, r)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里呢，非常的巧妙，HandlerFunc 类型实现了 ServeHTTP 方法，并且又将 ServeHTTP方法的参数传给了自身。&lt;/p&gt;
&lt;p&gt;也就是说：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;一个普通的函数，只要参数是 ResponseWriter 和 *Request，或者换种标准点的说法，它的函数签名为 func(ResponseWriter,*Request)，那么它就是 HandlerFunc 类型。&lt;/li&gt;
&lt;li&gt;由于 HandlerFunc 自身实现了 ServeHTTP方法，所以这个普通函数又实现了 Handler 接口，成了 Handler 类型。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;到这里，如何注册、DefaultServeMux 和 ServeMux 是什么，我们暂时还不知道，为了便于理解，这个下文再说。&lt;/p&gt;
&lt;h2&gt;监听与服务&lt;/h2&gt;
&lt;h3&gt;http.ListenAndServe&lt;/h3&gt;
&lt;p&gt;接着往下走，看一下 http.ListenAndServe 做了哪些事情：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// ListenAndServe listens on the TCP network address addr and then calls
// Serve with handler to handle requests on incoming connections.
// Accepted connections are configured to enable TCP keep-alives.
//
// The handler is typically nil, in which case the DefaultServeMux is used.
//
// ListenAndServe always returns a non-nil error.
func ListenAndServe(addr string, handler Handler) error {
 server := &amp;amp;Server{Addr: addr, Handler: handler}
 return server.ListenAndServe()
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;不难看出，http.ListenAndServe 负责监听 TCP 网络地址 addr, 代码中写的是&lt;code&gt;:8080&lt;/code&gt; 也即是监听 8080 端口，并且处理相关的请求。&lt;/p&gt;
&lt;p&gt;这里传入的第二个参数是 Handler 类型，根据注释可以看出：如果传入值为 nil ，那么将会使用 DefaultServeMux 。&lt;/p&gt;
&lt;h2&gt;服务复用器&lt;/h2&gt;
&lt;h3&gt;DefaultServeMux&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// DefaultServeMux is the default ServeMux used by Serve.
var DefaultServeMux = &amp;amp;defaultServeMux

var defaultServeMux ServeMux
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;说白了，DefaultServeMux 是 ServeMux 类型的一个实例，由标准库创建。&lt;/p&gt;
&lt;p&gt;下面看 ServeMux 结构体的源码。&lt;/p&gt;
&lt;h3&gt;ServeMux&lt;/h3&gt;
&lt;p&gt;ServeMux 是一个结构体，它的作用是服务复用器。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;type ServeMux struct {
 mu    sync.RWMutex
 m     map[string]muxEntry
 es    []muxEntry // slice of entries sorted from longest to shortest.
 hosts bool       // whether any patterns contain hostnames
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;因为涉及并发，所以这里有个读写锁 mu，主要用于保护下面的 map 类型的成员 m。&lt;/p&gt;
&lt;p&gt;es 与 hosts 和路由规则匹配有关。&lt;/p&gt;
&lt;p&gt;这里重点关注一下 m，它是一个 map ，key 是 string 类型的路由表达式，val 是 muxEntry 类型的结构体。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;type muxEntry struct {
 h       Handler
 pattern string
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;muxEntry 结构体，描述了路由规则 pattern 对应的处理器  h。&lt;/p&gt;
&lt;h3&gt;mux.Handle&lt;/h3&gt;
&lt;p&gt;上文中，http.Handle 和 http.HandleFunc 都调用了 mux.Handle 方法。&lt;/p&gt;
&lt;p&gt;它是结构体 ServeMux 的方法，也就是说，此方法主要把 Handler 对象注册到给定的 pattern 上，也即&lt;strong&gt;路由注册&lt;/strong&gt;。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// Handle registers the handler for the given pattern.
// If a handler already exists for pattern, Handle panics.
func (mux *ServeMux) Handle(pattern string, handler Handler) {
  // 为了保护 ServeMux 成员 map 类型的 m 的读写，分别在方法开始和结束的时候进行加锁和解锁的操作。
 mux.mu.Lock()
 defer mux.mu.Unlock()
 // 如果路由规则 pattern 为空，则直接 panic。
 if pattern == &quot;&quot; {
  panic(&quot;http: invalid pattern&quot;)
 }
  // 如果 http.Handler 类型的处理器 handler 为空，则panic。
 if handler == nil {
  panic(&quot;http: nil handler&quot;)
 }
  // 如果路由规则 pattern 已经存在，则直接 panic。
 if _, exist := mux.m[pattern]; exist {
  panic(&quot;http: multiple registrations for &quot; + pattern)
 }
 // 如果成员 m 为空，则 make 一个新的 map。
 if mux.m == nil {
  mux.m = make(map[string]muxEntry)
 }
  // 创建一个 muxEntry，并将 pattern 对应的 Handler 放进去。
 e := muxEntry{h: handler, pattern: pattern}
  // 写入 m， key 为 pattern ，value 为新建的 muxEntry 类型的 e ，也即新增一个路由规则。
 mux.m[pattern] = e
  // 如果路由规则以字符 / 结尾，则给将新建的 muxEntry 类型的 e 放到成员 es 中。
  // es 是一个切片，使用 http.appendSorted 方法加入元素，以确保 es 中的元素(路由)是从最长到最短。
 if pattern[len(pattern)-1] == &apos;/&apos; {
  mux.es = appendSorted(mux.es, e)
 }
  // 最后，如果路由规则不是以字符 / 开头，那么给成员 hosts 赋值 true 。
 if pattern[0] != &apos;/&apos; {
  mux.hosts = true
 }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;mux.ServeHTTP&lt;/h3&gt;
&lt;p&gt;我把 ServeMux 的 ServeHTTP 方法简称为 mux.ServeHTTP，下文也是一样。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
 if r.RequestURI == &quot;*&quot; {
  if r.ProtoAtLeast(1, 1) {
   w.Header().Set(&quot;Connection&quot;, &quot;close&quot;)
  }
  w.WriteHeader(StatusBadRequest)
  return
 }
 h, _ := mux.Handler(r)
 h.ServeHTTP(w, r)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ServeMux 结构体同样实现了 ServeHTTP 方法，也即它也实现了 Handler 接口，是一个 Handler 类型的对象。&lt;/p&gt;
&lt;p&gt;但它并不负责处理具体的请求，篇幅有限，这里给出，调用的 mux.Handler 方法签名：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func (mux *ServeMux) handler(host, path string) (h Handler, pattern string)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;总的来说，mux.ServeHTTP  调用了 mux.Handler 方法，通过 host 和 path 找到具体的 处理器 Handler 和路由规则 pattern ，然后让对应的 Handler 的 ServeHTTP 方法去处理请求。&lt;/p&gt;
&lt;h2&gt;连接与请求的处理&lt;/h2&gt;
&lt;p&gt;其实搞懂上面方法以及其之间的关系，对于进一步的学习 Go Web 框架 (比如 Gin ) 就已经有很大的帮助了。从监听与服务开始，代码更加底层，这里我主要关心的是，一次请求是如何到达 ServeHTTP 的。&lt;/p&gt;
&lt;p&gt;http.ListenAndServe 方法中，使用传入的监听地址 addr 和处理器 handler 初始化一个 HTTP 服务器 http.Server。&lt;/p&gt;
&lt;p&gt;Server 结构体，主要定义了需要跑一个 HTTP Server 所需要的参数：&lt;/p&gt;
&lt;h3&gt;Server&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;type Server struct {
 Addr string
 Handler Handler // handler to invoke, http.DefaultServeMux if nil
  
 TLSConfig *tls.Config
 ReadTimeout time.Duration
 ReadHeaderTimeout time.Duration
 WriteTimeout time.Duration
 IdleTimeout time.Duration
 MaxHeaderBytes int
 TLSNextProto map[string]func(*Server, *tls.Conn, Handler)
 ConnState func(net.Conn, ConnState)
 ErrorLog *log.Logger
 BaseContext func(net.Listener) context.Context
 ConnContext func(ctx context.Context, c net.Conn) context.Context
 inShutdown atomicBool // true when server is in shutdown
 disableKeepAlives int32     // accessed atomically.
 nextProtoOnce     sync.Once // guards setupHTTP2_* init
 nextProtoErr      error     // result of http2.ConfigureServer if used
  
 mu         sync.Mutex
 listeners  map[*net.Listener]struct{}
 activeConn map[*conn]struct{}
 doneChan   chan struct{}
 onShutdown []func()
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这些参数不是重点，接着往下。&lt;/p&gt;
&lt;h3&gt;server.ListenAndServe&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;func (srv *Server) ListenAndServe() error {
 if srv.shuttingDown() {
  return ErrServerClosed
 }
 addr := srv.Addr
 if addr == &quot;&quot; {
  addr = &quot;:http&quot;
 }
 ln, err := net.Listen(&quot;tcp&quot;, addr)
 if err != nil {
  return err
 }
 return srv.Serve(ln)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Server 结构体的 ListenAndServe 方法会监听 TCP 网络地址 addr ，然后调用 srv.Serve 处理传入连接的请求。&lt;/p&gt;
&lt;h3&gt;srv.Serve&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;func (srv *Server) Serve(l net.Listener) error {
 // ...省略部分
 for {
    // 循环监听 TCP 连接
  rw, err := l.Accept()
  if err != nil {
    ...省略部分 
  connCtx := ctx
  if cc := srv.ConnContext; cc != nil {
   connCtx = cc(connCtx, rw)
   if connCtx == nil {
    panic(&quot;ConnContext returned nil&quot;)
   }
  }
  tempDelay = 0
  c := srv.newConn(rw)
  c.setState(c.rwc, StateNew, runHooks) // before Serve can return
  go c.serve(connCtx)
 }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Serve 方法在 Listenner l 上接受传入的连接，并且为每一个连接创建 goroutine 。这些 gorutines 会读取请求并且调用 srv.Handler 去响应它们。&lt;/p&gt;
&lt;h3&gt;c.Serve&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// Serve a new connection.
func (c *conn) serve(ctx context.Context) {
  // ...
 for {
    // 循环接受请求，一个连接可以处理多个请求
  w, err := c.readRequest(ctx)
  if c.r.remain != c.server.initialReadLimitSize() {
   // If we read any bytes off the wire, we&apos;re active.
   c.setState(c.rwc, StateActive, runHooks)
  }
  // 这行代码是重点
  serverHandler{c.server}.ServeHTTP(w, w.req)
  inFlightResponse = nil
  w.cancelCtx()
  if c.hijacked() {
   return
  }
 }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;serverHandler&lt;/h3&gt;
&lt;p&gt;serverHandler 结构体是一个代理，它会代理 server 的 Handler 或 DefaultServeMux 。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;type serverHandler struct {
 srv *Server
}

func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {
  // 这个 handler 就是最初 http.ListenAndServe 传入的 Handler 类型的 handler 。
 handler := sh.srv.Handler
  // 如果 http.ListenAndServe 第二个参数是 nil，那么使用 DefaultServeMux 。
 if handler == nil {
  handler = DefaultServeMux
 }
 if req.RequestURI == &quot;*&quot; &amp;amp;&amp;amp; req.Method == &quot;OPTIONS&quot; {
  handler = globalOptionsHandler{}
 }
  
 if req.URL != nil &amp;amp;&amp;amp; strings.Contains(req.URL.RawQuery, &quot;;&quot;) {
  var allowQuerySemicolonsInUse int32
  req = req.WithContext(context.WithValue(req.Context(), silenceSemWarnContextKey, func() {
   atomic.StoreInt32(&amp;amp;allowQuerySemicolonsInUse, 1)
  }))
  defer func() {
   if atomic.LoadInt32(&amp;amp;allowQuerySemicolonsInUse) == 0 {
    sh.srv.logf(&quot;http: URL query contains semicolon, which is no longer a supported separator; parts of the query may be stripped when parsed; see golang.org/issue/25192&quot;)
   }
  }()
 }
  // 调用 handler 的 ServeHTTP 方法处理请求。
 handler.ServeHTTP(rw, req)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;到这里，连接中的请求，就交给了 handler.ServeHTTP 也即 mux.ServeHTTP 方法来处理。&lt;/p&gt;
&lt;p&gt;然后 mux.ServeHTTP 方法中，mux.Handler 方法，会根据 request 中的 host 和 path 信息，找到对应的 Handler， 这个 Handler 再处理信息。&lt;/p&gt;
&lt;h3&gt;总结&lt;/h3&gt;
&lt;p&gt;Go net/http 标准库，能让我们轻易地写出一个高性能的 HTTP Server，但肯定不能满足实际业务开发，比如动态路由、中间件、鉴权等这是标准库所不具有的。&lt;/p&gt;
&lt;p&gt;很多重复性的工作和常用的工具与特性要由框架来封装和实现，go 很多高性能框架 比如 Gin 都是直接封装了 net/http，这一点难能可贵，由此可见 Go 标准库的价值。&lt;/p&gt;
&lt;p&gt;所以学习优秀 Go web 框架的前提就是弄清楚 net/http Server 部分的源码，同时，也能方便更好的去使用和优化框架。&lt;br /&gt;
本文所使用的源码均来自 go 1.18.3，部分方法说明翻译自官方注释。&lt;/p&gt;
&lt;p&gt;如有不当之处，请批评指出。&lt;/p&gt;
</content:encoded></item></channel></rss>