├── Consistent-Hash.md ├── LICENSE ├── LinkedHashMap.md ├── Linux-normal.md ├── Linux-normal2.md ├── Markdown小计.md ├── ReentrantLock.md ├── SSM1.md ├── SSM10.md ├── SSM11.md ├── SSM12.md ├── SSM13.md ├── SSM14.md ├── SSM15.md ├── SSM16.md ├── SSM17.md ├── SSM2.md ├── SSM3.md ├── SSM4.md ├── SSM5.md ├── SSM6.md ├── SSM7.md ├── SSM8.md ├── SSM9.md ├── SpringBoot └── SpringBoot-tips.md ├── Synchronize.md ├── algorithm ├── LRU-cache.md ├── consistent-hash.md ├── time wheel.md └── timer-detail.md ├── annual-summary ├── 2018.md ├── 2019.md ├── 2020.md ├── 2021.md ├── 2022.md ├── 2023.md ├── 2024.md ├── GoodBye 2016,Welcome 2017 | 码农砌墙记.md ├── img.png ├── img_1.png └── img_2.png ├── baidu-google.md ├── basic ├── Go-OOP.md ├── parameter-trans.md └── python-oop.md ├── compilation └── Lexer.md ├── concurrent ├── ArrayBlockingQueue.md ├── CountDownLatch.md ├── threadpool-01.md └── threadpool-02.md ├── cs ├── Linux Pipe.md └── not easy emoji.md ├── desigin-patterns └── template-method.md ├── distributed-lock ├── distributed-lock-redis-update.md └── distributed-lock-redis.md ├── distributed └── distributed-discovery-zk.md ├── elasticsearch └── ElasticSearch VS MySQL.md ├── exam └── 2018-06-07-The-university-entrance-exam.md ├── framework-design ├── dynamic-rpc.md ├── grpc.md ├── sharding-db-02.md ├── sharding-db-03.md └── sharding-db.md ├── gjson ├── gjson01.md ├── gjson02.md ├── xjson03.md └── xjson04-bitwisee-operation.md ├── go ├── for-mistake.md ├── github-actions.md ├── go channel vs java BlockingQueue.md ├── go-benchmark-test.md ├── go-grpc-client-gui.md ├── go-lib.md ├── go-timeout.md ├── gorm-optimistic.md ├── lru-cache.md ├── observer.md ├── one-hour-write-cli-app.md ├── other-lang-singleton-pattern.md ├── slice pointer.key └── slice pointer.md ├── gscript ├── gscript01.md ├── gscript02-antlr-statement.md ├── gscript03-scope-func.md ├── gscript04-preview.md ├── gscript05-write-site.md ├── gscript06-operator-overloading.md ├── gscript07-return.md ├── gscript08-write-site-enhance.md ├── gscript10-write-native-lib.md └── gscript11-closure.md ├── guava ├── guava-bloom-filter.md ├── guava-cache-2.md └── guava-cache.md ├── https.md ├── istio └── istio1.12-upgrade-fix.md ├── java-reflect.md ├── java-senior ├── ArrayList VS LinkedList.md ├── ConcurrentHashMap.md ├── JVM-Troubleshoot.md ├── JVM-concurrent-HashSet-problem.md ├── OOM-Disruptor.md ├── ThreadPool.md ├── coding-online-analysis.md ├── concurrent-in-action.md ├── concurrent-in-action2.md ├── design-if-else.md ├── java-memary-allocation.md └── thread-communication.md ├── java-thread1.md ├── java-thread2.md ├── k8s ├── cloudnative-java.md └── grafana-variable.md ├── kafka ├── kafka-consumer.md └── kafka-product.md ├── metrics └── relabel_configs_ metric_relabel_configs.md ├── netty ├── Netty(1)TCP-Heartbeat.md ├── Netty(2)Thread-model.md ├── Netty(3)TCP-Sticky.md ├── cim01-started.md ├── cim02-v1.0.1.md ├── cim03-heartbeat.md └── million-sms-push.md ├── newObject.md ├── normal-skill1.md ├── normal-skill2.md ├── normal-skill3.md ├── ob ├── 2024-cloud-native.md ├── Bookkeeper-storage.md ├── Build-ower-AI-robot.md ├── ChatGPT-hole.md ├── Golang-interview-01.md ├── Helm-tips.md ├── Kubernetes-tricks.md ├── OTel-demo.md ├── OTel-member.md ├── OpenTelemetry-01-trace.md ├── OpenTelemetry-02-metrics.md ├── OpenTelemetry-client-log-mdc.md ├── OpenTelemetry-create-instrumentation.md ├── OpenTelemetry-custom-instrument.md ├── OpenTelemetry-enterprise.md ├── OpenTelemetry-getstart.md ├── OpenTelemetry-grpc-principle.md ├── OpenTelemetry-metrics-concept.md ├── OpenTelemetry-trace-concept.md ├── Pulsar Proposal.md ├── Pulsar test framework.md ├── Pulsar-Broker-Interceptor.md ├── Pulsar-Client.md ├── Pulsar-Delete-Topic.md ├── Pulsar-JWT.md ├── Pulsar-SQL.md ├── Pulsar-loadbalance.md ├── Pulsar3.0-new-feature.md ├── Pulsar3.0-upgrade.md ├── StarRocks-MV-refresh-Principle.md ├── StarRocks-build-in-local.md ├── StarRocks-dev-env-build.md ├── StarRocks-dev-shard-data-build.md ├── StarRocks-meta.md ├── VictoriaLogs-Intro.md ├── about-opensource-argument.md ├── cim-2.0.0.md ├── cim-client-sdk.md ├── codereview-practice.md ├── create-a-plugin-for-cprobe.md ├── git-tips-rebase.md ├── go-5-tips.md ├── go-loop-fix.md ├── how-operator-working.md ├── how-to-involve-OpenSource.md ├── how-to-monitoring-nginx.md ├── how-to-write-otel-extensions.md ├── istio-install-problem.md ├── jdk21+springboot+OTel+SPI.md ├── k8s-0-start.md ├── k8s-Ingress.md ├── k8s-Istio01.md ├── k8s-Istio02.md ├── k8s-configmap.md ├── k8s-grpc-lb.md ├── k8s-istio03.md ├── k8s-log-collect.md ├── k8s-monitor-pod.md ├── k8s-probe.md ├── k8s-pulsar-scale.md ├── k8s-question-01.md ├── k8s-restart-pod.md ├── k8s-rollout.md ├── k8s-service.md ├── newsletter │ ├── Newsletter01-20231013.md │ ├── Newsletter02-20231022.md │ ├── Newsletter03-20231027.md │ ├── Newsletter04-20231103.md │ ├── Newsletter05-20231110.md │ ├── Newsletter06-20231117.md │ ├── Newsletter07-20231124.md │ ├── Newsletter08-20231201.md │ ├── Newsletter09-20231208.md │ ├── Newsletter10-20231215.md │ ├── Newsletter10-20231222.md │ ├── Newsletter12-20231229.md │ ├── Newsletter12-202401012.md │ ├── Newsletter12-20240105.md │ └── Newsletter14-20240223.md ├── novice-contribute-open-source.md ├── operator-e2e-test.md ├── otel-replace-sw.md ├── podcasts-english-0-vim.md ├── podcasts-english-1-vim.md ├── pulsar-client-zero-consumer.md ├── pulsar-slow-consume.md ├── translate-pulsar-2023-year-in-review.md ├── translate-pulsar-3.2.0.md ├── unit-test.md ├── ✅开源项目如何做集成测试.md ├── 如何在平淡的工作中整理出有价值的简历.md ├── 如何选择可以搞钱的技术栈.md ├── 推荐一些值得学习的开源项目和框架.md ├── 🎉how-to-be-committer.md ├── 💢线上高延迟请求排查.md └── 🤳cim-support-integration-test.md ├── open source └── how to contribute open-source project.md ├── personal ├── 1W-star-update.md ├── 1W-star.md ├── Interview-experience.md ├── find-job-experience.md └── how-to-be-developer.md ├── pulsar ├── pulsar-interrupted.md ├── pulsar-load-banance.md ├── pulsar-perf-test.md ├── pulsar-repeat-consume.md └── pulsar-start.md ├── sbc └── sbc7-Distributed-Limit.md ├── sbc1.md ├── sbc2.md ├── sbc3.md ├── sbc4.md ├── sbc5.md ├── sbc6.md ├── skill ├── 1Kstar.md ├── first-blog.md └── resume.md ├── spring └── spring-bean-lifecycle.md ├── ssm └── SSM18-seconds-kill.md ├── starrocks └── StarRocks-upgrade.md ├── tools └── blog-toolbox.md ├── translation ├── hidden-gems-goland.md ├── how-to-use-git-efficiently.md ├── new-developer-friendly-features-after-java-8.md ├── translation-What Is The Best Programming Language to Start.md └── useful-unknown-java-features.md ├── translation1-12 cool things you can do with GitHub.md ├── troubleshoot ├── SpringCloud-Feign-dynamic-url.md ├── cpu-percent-100-02.md ├── cpu-percent-100.md ├── dubbo-start-slow.md ├── pulsar-repeat-consume.md ├── thread-gone.md └── thread-gone2.md ├── vlog ├── Basketball Day one.md ├── Chinese-coder-daily.md ├── Chinese-coder-weekends-01.md └── Chinese-coder-weekends-02.md ├── volatile.md ├── wheel ├── cicada1.md ├── cicada2.md ├── cicada3.md ├── cicada4.md ├── cicada5.md ├── cicada6.md ├── cicada7-exception-handle.md ├── cicada8-spi.md ├── cicada9-proxy.md ├── feign-plus.md └── feign-plus2.md └── 第一次总结.md /Consistent-Hash.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 一致性 Hash 算法分析 3 | date: 2018/01/08 02:01:36 4 | categories: 5 | - 算法 6 | --- 7 | 8 | ![](https://i.loli.net/2019/04/29/5cc656bda3f66.jpg) 9 | 10 | 当我们在做数据库分库分表或者是分布式缓存时,不可避免的都会遇到一个问题: 11 | 12 | 如何将数据均匀的分散到各个节点中,并且尽量的在加减节点时能使受影响的数据最少。 13 | 14 | ## Hash 取模 15 | 随机放置就不说了,会带来很多问题。通常最容易想到的方案就是 `hash 取模`了。 16 | 17 | 可以将传入的 Key 按照 `index = hash(key) % N` 这样来计算出需要存放的节点。其中 hash 函数是一个将字符串转换为正整数的哈希映射方法,N 就是节点的数量。 18 | 19 | 这样可以满足数据的均匀分配,但是这个算法的容错性和扩展性都较差。 20 | 21 | 比如增加或删除了一个节点时,所有的 Key 都需要重新计算,显然这样成本较高,为此需要一个算法满足分布均匀同时也要有良好的容错性和拓展性。 22 | 23 | 24 | 25 | ## 一致 Hash 算法 26 | 27 | 一致 Hash 算法是将所有的哈希值构成了一个环,其范围在 `0 ~ 2^32-1`。如下图: 28 | 29 | ![](https://i.loli.net/2019/05/08/5cd1b9fe31560.jpg) 30 | 31 | 之后将各个节点散列到这个环上,可以用节点的 IP、hostname 这样的唯一性字段作为 Key 进行 `hash(key)`,散列之后如下: 32 | 33 | ![](https://i.loli.net/2019/05/08/5cd1ba01ebc22.jpg) 34 | 35 | 之后需要将数据定位到对应的节点上,使用同样的 `hash 函数` 将 Key 也映射到这个环上。 36 | 37 | ![](https://i.loli.net/2019/05/08/5cd1ba05955b9.jpg) 38 | 39 | 这样按照顺时针方向就可以把 k1 定位到 `N1节点`,k2 定位到 `N3节点`,k3 定位到 `N2节点`。 40 | 41 | ### 容错性 42 | 这时假设 N1 宕机了: 43 | 44 | ![](https://i.loli.net/2019/05/08/5cd1ba07908f0.jpg) 45 | 46 | 依然根据顺时针方向,k2 和 k3 保持不变,只有 k1 被重新映射到了 N3。这样就很好的保证了容错性,当一个节点宕机时只会影响到少少部分的数据。 47 | 48 | ### 拓展性 49 | 50 | 当新增一个节点时: 51 | 52 | ![](https://i.loli.net/2019/05/08/5cd1ba0c7519c.jpg) 53 | 54 | 在 N2 和 N3 之间新增了一个节点 N4 ,这时会发现受印象的数据只有 k3,其余数据也是保持不变,所以这样也很好的保证了拓展性。 55 | 56 | ## 虚拟节点 57 | 到目前为止该算法依然也有点问题: 58 | 59 | 当节点较少时会出现数据分布不均匀的情况: 60 | 61 | ![](https://i.loli.net/2019/05/08/5cd1ba12a3b62.jpg) 62 | 63 | 这样会导致大部分数据都在 N1 节点,只有少量的数据在 N2 节点。 64 | 65 | 为了解决这个问题,一致哈希算法引入了虚拟节点。将每一个节点都进行多次 hash,生成多个节点放置在环上称为虚拟节点: 66 | 67 | ![](https://i.loli.net/2019/05/08/5cd1ba1490a67.jpg) 68 | 69 | 计算时可以在 IP 后加上编号来生成哈希值。 70 | 71 | 这样只需要在原有的基础上多一步由虚拟节点映射到实际节点的步骤即可让少量节点也能满足均匀性。 72 | 73 | ## 号外 74 | 最近在总结一些 Java 相关的知识点,感兴趣的朋友可以一起维护。 75 | 76 | > 地址: [https://github.com/crossoverJie/Java-Interview](https://github.com/crossoverJie/Java-Interview) 77 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 crossoverJie 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Linux-normal.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Linux(一)常用命令 3 | date: 2016/4/10 21:01:36 4 | categories: 5 | - Linux笔记 6 | tags: 7 | - Linux 8 | --- 9 | # 前言 10 | > 由于现在JAVA开发的很多应用都是部署到Linux系统上的,因此了解和掌握一些Linux的常用命令是非常有必要的,以下就是在Java开发过程中一些常用的命令。 11 | 12 | ---------- 13 | 14 | # 常用命令 15 | 1. 查找文件 16 | `find / -name log.txt` 17 | 根据名称查找在 /目录下的 log.txt文件。 18 | 19 | `find .-name "*.xml"` 20 | 递归查找所有的xml文件。 21 | 22 | `find .-name "*.xml"|xargs grep "hello"` 23 | 递归查找所有包含hello的xml文件。 24 | 25 | `ls -l grep 'jar'` 26 | 查找当前目录中的所有jar文件。 27 | 28 | 2. 检查一个文件是否运行 29 | `ps –ef|grep tomecate` 30 | 检查所有有关tomcat的进程。 31 | 32 | 3. 终止线程 33 | `kill -9 19979 ` 34 | 终止线程号为19979的线程 35 | 36 | 4. 查看文件,包括隐藏文件。 37 | `ls -al` 38 | 39 | 5. 查看当前工作目录。 40 | `pwd` 41 | 42 | 6. 复制文件包括其子文件到指定目录 43 | `cp -r source target` 44 | 复制source文件到target目录中。 45 | 46 | 7. 创建一个目录 47 | `mkdir new` 48 | 创建一个new的目录 49 | 50 | 8. 删除目录(前提是此目录是空目录) 51 | `rmdir source` 52 | 删除source目录。 53 | 54 | 9. 删除文件 包括其子文件 55 | `rm -rf file` 56 | 删除file文件和其中的子文件。 57 | `-r`表示向下递归,不管有多少目录一律删除 58 | `-f`表示强制删除,不做任何提示。 59 | 60 | 10. 移动文件 61 | `mv /temp/movefile /target` 62 | 63 | 11. 切换用户 64 | `su -username` 65 | 66 | 12. 查看ip 67 | `ifconfig` 68 | 注意是 `ifconfig` 不是windows中的`ipconfig` 69 | 70 | # 总结 71 | 以上就是在Linux下开发Java应用常用的Linux命令,如有遗漏请在评论处补充,我将不定期添加。 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /Markdown小计.md: -------------------------------------------------------------------------------- 1 | title: Markdown小计 #可以改成中文的,如“新文章” 2 | date: 2016-05-06 16:04:09 #发表日期,一般不改动 3 | categories: 4 | - blog #文章文类 5 | tags: 6 | - Markdown #文章标签,多于一项时用这种格式,只有一项时使用tags: blog 7 | --- 8 | 9 | `# 标题` 10 | # 表示标题 一个#号代表一级标题,以此类推。 11 | 12 | `* 无序列表` 13 | 14 | * 无序列表 15 | 16 | `> 引用` 17 | 18 | > 引用 19 | 20 | `[http://www.baidu.com](http://www.baidu.com "百度")` 21 | 22 | [百度](http://www.baidu.com "百度") 23 | 24 | 25 | `![艾弗森](http://i.imgur.com/TLnZ2S6.jpg)插入图片` 26 | 27 | ![艾弗森](http://i.imgur.com/TLnZ2S6.jpg) 28 | -------------------------------------------------------------------------------- /SSM10.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: SSM(十) 项目重构-互联网项目的Maven结构 3 | date: 2017/03/04 01:44:54 4 | categories: 5 | - SSM 6 | tags: 7 | - Maven 8 | - 重构 9 | --- 10 | ![互联网项目的maven.jpg](https://ooo.0o0.ooo/2017/03/03/58b992d46fd02.jpg) 11 | 12 | # 前言 13 | 14 | 很久没有更新博客了,之前定下周更逐渐成了月更。怎么感觉像我追过的一部动漫。 15 | 这个博文其实很早就想写了。 16 | 之前所有的代码都是在一个模块里面进行开发,这和maven的理念是完全不相符的,最近硬是抽了一个时间来对项目的结构进行了一次重构。 17 | 18 | > 先来看看这次重构之后的目录结构 19 | 20 | ![1.jpg](https://ooo.0o0.ooo/2017/03/04/58b99366edad6.jpg) 21 | 22 | 23 | 24 | # 为什么需要分模块 25 | > 至于为什么要分模块呢? 26 | 27 | 我们设想一个这样的场景: 28 | 在现在的互联网开发中,会把一个很大的系统拆分成各个子系统用于降低他们之间的耦合度。 29 | 30 | 在一个子项目中通常都会为`API`、`WEB`、`Service`等模块。 31 | 而且当项目够大时,这些通常都不是一个人能完成的工作,需要一个团队来各司其职。 32 | 33 | 想象一下:当之前所有的项目都在一个模块的时候,A改动了API,需要`Deploy`代码。而B也改动了`service`的代码,但并没有完全做完。所以A在提交`build`的时候就会报错 34 | 35 | 而且在整个项目足够大的时候,这个`build`的时间也是很影响效率的。 36 | 37 | 但让我将各个模块之间分开之后效果就不一样了。我修改了`API`我就只需要管我的就行,不需要整个项目进行`build`。 38 | 39 | 而且当有其他项目需要依赖我这个`API`的时候也只需要依赖`API`即可,不用整个项目都依赖过去。 40 | 41 | 42 | # 各个模块的作用 43 | 44 | 来看下这次我所分的模块。 45 | 46 | ## ROOT 47 | 这是整个项目的根节点。 48 | 先看一下其中的`pom.xml`: 49 | 50 | ```xml 51 | com.crossoverJie 52 | SSM 53 | pom 54 | 2.0.0 55 | 56 | 57 | SSM-API 58 | SSM-BOOT 59 | SSM-SERVICE 60 | SSM-WEB 61 | 62 | 63 | 64 | UTF-8 65 | 4.1.4.RELEASE 66 | 2.5.0 67 | 6.0.1 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | com.crossoverJie 76 | SSM-API 77 | 2.0.0 78 | 79 | 80 | 81 | ``` 82 | 83 | 我截取了其中比较重点的配置。 84 | 85 | 由于这是父节点,所以我的`packag`类型使用的是`pom`。 86 | 其中分别有着四个子模块。 87 | 88 | 其中重点看下``这个标签。 89 | 如果使用的是`IDEA`这个开发工具的话是可以看到如下图: 90 | ![2.jpg](https://ooo.0o0.ooo/2017/03/04/58b997c0232ea.jpg) 91 | 92 | 标红的有一个向下的箭头,点一下就可以进入子模块中相同的依赖。 93 | 这样子模块就不需要配置具体的版本了,统一由父模块来进行维护,对之后的版本升级也带来了好处。 94 | 95 | ## SSM-API 96 | 接下来看下`API`这个模块: 97 | 98 | 通常这个模块都是用于定义外部接口的,以及改接口所依赖的一些`DTO类`。 99 | 一般这个模块都是拿来给其他项目进行依赖,并和本项目进行数据交互的。 100 | 101 | ## SSM-BOOT 102 | `BOOT`这个模块比较特殊。 103 | 可以看到这里没有任何代码,只有一个`rpc`的配置文件。 104 | 通常这个模块是用于给我们内部项目进行依赖的,并不像上面的`API`模块一样给其他部门或者是项目进行依赖的。 105 | 106 | 因为在我们的`RPC`调用的时候,用`dubbo`来举例,是需要配置所依赖的`consumer`。 107 | 108 | 但如果是我们自己内部调用的话我们就可以把需要调用自己的`dubbo`服务提供者配置在这里,这样的话我们自己调用就只需要依赖这个`BOOT`就可以进行调用了。 109 | 110 | 哦对了,`BOOT`同时还会依赖`API`,这样才实现了只依赖`BOOT`就可以调用自己内部的`dubbo`服务了。 111 | 如下所示: 112 | ```xml 113 | 114 | 115 | junit 116 | junit 117 | test 118 | 119 | 120 | 121 | com.crossoverJie 122 | SSM-API 123 | 124 | 125 | 126 | ``` 127 | 128 | ## SSM-SERVICE 129 | `SERVICE`模块就比较好理解了。 130 | 是处理具体业务逻辑的地方,也是对之前的API的实现。 131 | 132 | 通常这也是一个`web`模块,所以我的`pom`类型是`WAR`。 133 | 134 | ## SSM-WEB 135 | 其实`WEB`模块和`SERVICE`模块有点重合了。通常来说这个模块一般在一个对外提供`http`访问接口的项目中。 136 | 137 | 这里只是为了展示项目结构,所以也写在了这里。 138 | 139 | 他的作用和`service`差不多,都是`WAR`的类型。 140 | 141 | # 总结 142 | 这次没有实现什么特别的功能,只是对一些还没有接触过这种项目结构开发的童鞋能起到一些引导作用。 143 | 144 | 具体源码还请关注我的`github`。 145 | 146 | > 项目地址:[https://github.com/crossoverJie/SSM.git](https://github.com/crossoverJie/SSM.git) 147 | 148 | > 个人博客地址:[http://crossoverjie.top](http://crossoverjie.top)。 149 | 150 | > GitHub地址:[https://github.com/crossoverJie](https://github.com/crossoverJie)。 151 | 152 | 153 | -------------------------------------------------------------------------------- /SSM6.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: SSM(六)跨域传输 3 | date: 2016/10/18 13:44:54 4 | categories: 5 | - SSM 6 | tags: 7 | - Java 8 | - JSONP 9 | - JSON 10 | --- 11 | ![](https://i.loli.net/2019/05/08/5cd1ba153e527.jpg) 12 | 13 | # 前言 14 | 不知大家在平时的开发过程中有没有遇到过跨域访问资源的问题,我不巧在上周就碰到一个这样的问题,幸运的是在公司前端同学的帮忙下解决了该问题。 15 | 16 | ## 什么是跨域问题? 17 | 1. 只要协议、域名、端口有任何一个不同,都被当作是不同的域 18 | 2. 只要是在不同域中是无法进行通信的。 19 | 20 | 21 | 22 | 基于以上的的出发点,我们又有跨域共享资源的需求(`譬如现在流行的前后端分离之后分别部署的情况`),本文所采用的解决办法是`JSONP`,说到`JSONP`就会首先想到`JSON`。虽然只有一字之差但意义却完全不一样,首先科普一下`JSON`。 23 | 24 | 25 | # JSON 26 | > 其实现在`JSON`已经是相当流行了,只要涉及到前后端的数据交互大都都是采用的JSON(不管是web还是android和IOS),所以我这里就举一个例子,就算是没有用过的同学也能很快明白其中的意思。 27 | 28 | ## PostMan 29 | 首先给大家安利一款后端开发的利器`PostMan`,可以用于模拟几乎所有的`HTTP`请求,在开发阶段调试后端接口非常有用。 30 | 这是一个Chrome插件,可以直接在google商店搜索直接下载(当然前提你懂得)。 31 | 之后界面就如下: 32 | 33 | ![](https://i.loli.net/2019/05/08/5cd1ba18947ba.jpg) 34 | 35 | 界面非常简洁,有点开发经验的童鞋应该都会使用,不太会用的直接google下就可以了比较简单。 36 | 接着我们就可以利用`PostMan`来发起一次请求获取`JSON`了。这里以我`SSM`项目为例,也正好有暴露一个JSON的接口。地址如下: 37 | [http://www.crossoverjie.top/SSM/content_load](http://www.crossoverjie.top/SSM/content_load)。 38 | 直接在`POSTMAN`中的地址栏输入该地址,采用`GET`的方式请求,之后所返回的就是JSON格式的字符串。 39 | 由于`Javascript`原生的就支持JSON,所以解析起来非常方便。 40 | 41 | # JSONP 42 | 好了,终于可以谈谈`JSONP`了。之前说道`JSONP`是用来解决跨域问题的,那么他是如何解决的呢。 43 | 经过我们开发界的前辈们发现,HTML中拥有`SRC`属性的标签都不受跨域的影响,比如:` 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | ``` 80 | 其中我们采用了JQuery给我封装好的函数,这样就可以自动帮我们解析了。 81 | 首先我们来看下代码中的[http://www.crossoverjie.top/SSM/jsonpInfo?callback=getUser&userId=3](http://www.crossoverjie.top/SSM/jsonpInfo?callback=getUser&userId=3)这个地址返回的是什么内容,还是放到`POSTMAN`中执行如下: 82 | ![3](http://img.blog.csdn.net/20161018005211291)。 83 | 可以看到我们所传递的`callback`参数带着查询的数据又原封不动的返回给我们了,这样的话即使我们不使用`JQuery`给我封装好的函数,我们自定义一个和`callback`名称一样的函数一样是可以解析其中的数据的,只是`Jquery`帮我们做了而已。 84 | 85 | 前端没问题了,那么后端又是如何实现的呢?也很简单,如下: 86 | ```java 87 | @RequestMapping(value = "/jsonpInfo",method = { RequestMethod.GET }) 88 | @ResponseBody 89 | public Object jsonpInfo(String callback,Integer userId) throws IOException { 90 | User user = userService.getUserById(userId); 91 | JSONPObject jsonpObject = new JSONPObject(callback,user) ; 92 | return jsonpObject ; 93 | } 94 | ``` 95 | 后端采用了`jackson`中的`JSONPObject`这个类的一个构造方法,只需要将`callback`字段和需要转成`JSON`字符串的对象放进去即可。 96 | 需要主要的是需要使用`@ResponseBody`注解才能成功返回。 97 | 98 | # 总结 99 | 其实网上还有其他的方法来处理跨域问题,不过我觉得这样的方式最为简单。同样JSONP也是有缺点的,比如:只支持`GET`方式的HTTP请求。 100 | 以上代码依然在博主的[SSM](https://github.com/crossoverJie/SSM)项目中,如有需要可以直接`FORK`。 101 | 102 | > 项目地址:[https://github.com/crossoverJie/SSM.git](https://github.com/crossoverJie/SSM.git) 103 | 104 | > 个人博客地址:[http://crossoverjie.top](http://crossoverjie.top)。 105 | 106 | > GitHub地址:[https://github.com/crossoverJie](https://github.com/crossoverJie)。 107 | -------------------------------------------------------------------------------- /Synchronize.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: synchronized 关键字原理 3 | date: 2018/01/14 22:13:22 4 | categories: 5 | - Java 进阶 6 | tags: 7 | - Java 8 | - synchronize 9 | --- 10 | 11 | ![](https://i.loli.net/2019/05/08/5cd1ba276f063.jpg) 12 | 13 | 14 | 众所周知 `synchronized` 关键字是解决并发问题常用解决方案,有以下三种使用方式: 15 | 16 | - 同步普通方法,锁的是当前对象。 17 | - 同步静态方法,锁的是当前 `Class` 对象。 18 | - 同步块,锁的是 `()` 中的对象。 19 | 20 | 21 | 实现原理: 22 | `JVM` 是通过进入、退出对象监视器( `Monitor` )来实现对方法、同步块的同步的。 23 | 24 | 具体实现是在编译之后在同步方法调用前加入一个 `monitor.enter` 指令,在退出方法和异常处插入 `monitor.exit` 的指令。 25 | 26 | 其本质就是对一个对象监视器( `Monitor` )进行获取,而这个获取过程具有排他性从而达到了同一时刻只能一个线程访问的目的。 27 | 28 | 而对于没有获取到锁的线程将会阻塞到方法入口处,直到获取锁的线程 `monitor.exit` 之后才能尝试继续获取锁。 29 | 30 | 流程图如下: 31 | 32 | ![](https://i.loli.net/2019/05/08/5cd1ba2bbda92.jpg) 33 | 34 | 35 | 通过一段代码来演示: 36 | 37 | ```java 38 | public static void main(String[] args) { 39 | synchronized (Synchronize.class){ 40 | System.out.println("Synchronize"); 41 | } 42 | } 43 | ``` 44 | 45 | 使用 `javap -c Synchronize` 可以查看编译之后的具体信息。 46 | 47 | ``` 48 | public class com.crossoverjie.synchronize.Synchronize { 49 | public com.crossoverjie.synchronize.Synchronize(); 50 | Code: 51 | 0: aload_0 52 | 1: invokespecial #1 // Method java/lang/Object."":()V 53 | 4: return 54 | 55 | public static void main(java.lang.String[]); 56 | Code: 57 | 0: ldc #2 // class com/crossoverjie/synchronize/Synchronize 58 | 2: dup 59 | 3: astore_1 60 | **4: monitorenter** 61 | 5: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream; 62 | 8: ldc #4 // String Synchronize 63 | 10: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 64 | 13: aload_1 65 | **14: monitorexit** 66 | 15: goto 23 67 | 18: astore_2 68 | 19: aload_1 69 | 20: monitorexit 70 | 21: aload_2 71 | 22: athrow 72 | 23: return 73 | Exception table: 74 | from to target type 75 | 5 15 18 any 76 | 18 21 18 any 77 | } 78 | ``` 79 | 80 | 可以看到在同步块的入口和出口分别有 `monitorenter,monitorexit` 81 | 指令。 82 | 83 | 84 | ## 锁优化 85 | `synchronized` 很多都称之为重量锁,`JDK1.6` 中对 `synchronized` 进行了各种优化,为了能减少获取和释放锁带来的消耗引入了`偏向锁`和`轻量锁`。 86 | 87 | 88 | ### 轻量锁 89 | 当代码进入同步块时,如果同步对象为无锁状态时,当前线程会在栈帧中创建一个锁记录(`Lock Record`)区域,同时将锁对象的对象头中 `Mark Word` 拷贝到锁记录中,再尝试使用 `CAS` 将 `Mark Word` 更新为指向锁记录的指针。 90 | 91 | 如果更新**成功**,当前线程就获得了锁。 92 | 93 | 如果更新**失败** `JVM` 会先检查锁对象的 `Mark Word` 是否指向当前线程的锁记录。 94 | 95 | 如果是则说明当前线程拥有锁对象的锁,可以直接进入同步块。 96 | 97 | 不是则说明有其他线程抢占了锁,如果存在多个线程同时竞争一把锁,**轻量锁就会膨胀为重量锁**。 98 | 99 | #### 解锁 100 | 轻量锁的解锁过程也是利用 `CAS` 来实现的,会尝试锁记录替换回锁对象的 `Mark Word` 。如果替换成功则说明整个同步操作完成,失败则说明有其他线程尝试获取锁,这时就会唤醒被挂起的线程(此时已经膨胀为`重量锁`) 101 | 102 | 轻量锁能提升性能的原因: 103 | 104 | 认为大多数锁在整个同步周期都不存在竞争,所以使用 `CAS` 比使用互斥开销更少。但如果锁竞争激烈,轻量锁就不但有互斥的开销,还有 `CAS` 的开销,甚至比重量锁更慢。 105 | 106 | ### 偏向锁 107 | 108 | 为了进一步的降低获取锁的代价,`JDK1.6` 之后还引入了偏向锁。 109 | 110 | 偏向锁的特征是:锁不存在多线程竞争,并且应由一个线程多次获得锁。 111 | 112 | 当线程访问同步块时,会使用 `CAS` 将线程 ID 更新到锁对象的 `Mark Word` 中,如果更新成功则获得偏向锁,并且之后每次进入这个对象锁相关的同步块时都不需要再次获取锁了。 113 | 114 | #### 释放锁 115 | 当有另外一个线程获取这个锁时,持有偏向锁的线程就会释放锁,释放时会等待全局安全点(这一时刻没有字节码运行),接着会暂停拥有偏向锁的线程,根据锁对象目前是否被锁来判定将对象头中的 `Mark Word` 设置为无锁或者是轻量锁状态。 116 | 117 | 偏向锁可以提高带有同步却没有竞争的程序性能,但如果程序中大多数锁都存在竞争时,那偏向锁就起不到太大作用。可以使用 `-XX:-userBiasedLocking=false` 来关闭偏向锁,并默认进入轻量锁。 118 | 119 | 120 | ### 其他优化 121 | 122 | #### 适应性自旋 123 | 在使用 `CAS` 时,如果操作失败,`CAS` 会自旋再次尝试。由于自旋是需要消耗 `CPU` 资源的,所以如果长期自旋就白白浪费了 `CPU`。`JDK1.6`加入了适应性自旋: 124 | 125 | > 如果某个锁自旋很少成功获得,那么下一次就会减少自旋。 126 | 127 | ## 总结 128 | 129 | synchronized 现在已经不像以前那么重了,拿 1.8 中的 ConcurrentHashMap 就可以看出,里面大量的使用了 synchronized 来进行同步。 130 | 131 | ## 号外 132 | 最近在总结一些 Java 相关的知识点,感兴趣的朋友可以一起维护。 133 | 134 | > 地址: [https://github.com/crossoverJie/Java-Interview](https://github.com/crossoverJie/Java-Interview) 135 | -------------------------------------------------------------------------------- /annual-summary/2019.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 2019年度总结 3 | date: 2019-12-30 08:35:14 4 | categories: 5 | - annual-summary 6 | --- 7 | 8 | 9 | ![](https://i.loli.net/2020/01/02/o5DSAQsdU6FhjLW.jpg) 10 | 11 | # 前言 12 | 13 | 消失两个多月后我胡汉三又回来了,比较遗憾的是这并不是一篇技术文,有兴趣的朋友就当做故事看吧。 14 | 15 | 所以这其实是一份年终总结 16 | 17 | 其实这段期间一直有朋友在问我咋不接着更新公众号了?甚至一点消息都没了。 18 | 19 | 真不是不更,主要有以下几个原因: 20 | 21 | 22 | 23 | 我对这个号的定位是【原创技术】博主,大家应该也知道,技术号是一种非常垂直的领域,能写的东西也就那么多;以我个人的工作阅历其实已经快被榨干了。 24 | 25 | 平时大家应该也能看得出,不少公众号逐渐开始转载和原创混着来,甚至有些已经是全职的转载号了。 26 | 27 | 当然我不是说这样不好,每个人的选择不同;只是我对我个人的要求是:要么就不写,不然就得是有意义的原创。 28 | 29 | 乍一看还挺有骨气,然而现实却是两个多月没更新了。 30 | 31 | ![](https://i.loli.net/2020/01/02/rkDSUtpo39myNdn.jpg) 32 | 33 | 所以我现在也在不断学习,希望今年能再给大家带来一些有意思的内容。 34 | 35 | --- 36 | 37 | 第二个原因今年下半年我换了份工作,面对全新的领域需要花上不少时间去学习;同时也需要完成几件人生大事,所以总得有侧重点。 38 | 39 | 具体内容请接着往下看。 40 | 41 | # 回顾 42 | 43 | 2019年对我来说真的是意义重大的一年,今年发生了许多大事;用我妈的话来说就是喜事连连。 44 | ![](https://i.loli.net/2020/01/02/FtRviQBrpJkoSul.jpg) 45 | 46 | ## 新家 47 | 48 | 确实每一件事对我来说都不是小事;首先是终于搬进了新家。 49 | 50 | ![](https://i.loli.net/2020/01/02/lh4PkDSMgd15qzs.jpg) 51 | 52 | 从去年接房到经历半年的装修终于是搬进了新家,告别了 3 年多的租房日子;但也不是一帆风顺,从装修中踩的坑,以及和家人意见不一致导致的分歧都让这个新房来之不易。 53 | 54 | 期间为了让我的房间看起来更有极客范,满足所谓的”程序员“逼格;我还特地搞了一套智能家居,现在用了半年发现优点还是明显,但也偶尔会在智能和智障中来回切换。 55 | 56 | 原本我打算拍一期与智能家居相关的视频来着,也是由于上面两个原因耽搁了,大家感兴趣的话也可以和大家分享。 57 | 58 | 谈到视频,今年我还更新了 10 期 vlog。 59 | 60 | ![](https://i.loli.net/2020/01/02/gsPxD45fhdkUbAn.jpg) 61 | 62 | 其实效果也不错,也有好多小伙伴反馈挺有意思,但也是自己懒就停更了,来年也得捡起来了,做视频我也很感兴趣,年纪大了后再看看当年的自己我想会很有意思。 63 | 64 | ## 新车 65 | 66 | 第二件大事自然就是提车了,这事其实完全没在我今年的计划清单里的。 67 | 68 | 我从拿证到提车中间只间隔了一个周末。 69 | 70 | 当然第一次事故来的也比较快,好在现在胆子也越来越大;刚提车那段时间,早上为了躲避早高峰我甚至六点过就开车去公司,同样的下班也是8、9点钟才最后一个走。让我一度怀疑我这车买来是遭罪的。 71 | 72 | ![](https://i.loli.net/2020/01/02/2mncqwDOCokUS5a.jpg) 73 | 74 | 还一度成为了公司笑柄,好在现在三个多月已经没那么怂了,当然我也不敢再立 flag 了,第一次剐蹭就是某天早上在公司炫耀不再早起后出事的。 75 | 76 | 77 | ## 领证 78 | 79 | ![](https://i.loli.net/2020/01/02/NvRlXHOQyo5G26F.jpg) 80 | 81 | 毫不犹豫这是今年办的最重要的一件事;从 2011 年开始的异地校园恋到现在我都不敢想象真的成了。前几天我填了一个调查问卷,文末让填写婚姻情况,当我习惯性的准备填写未婚时发现我居然也结婚了,当时的心情就像被陌生人归还丢失的钱包,不禁感慨还是好心人多啊。 82 | 83 | 现在想想我的节奏是去年求婚、今年领证、明年婚礼;看似间隔了挺长但也循序渐进,希望各位读者朋友也早日脱单。 84 | 85 | # 工作 86 | 87 | 今年还有一件大事就是我换了份工作,换工作的原因也不是公司不好,而是和我的职业发展规划有些许偏差,同时压力也大到爆炸。 88 | 89 | 毫不夸张的说,最严重那段时间我甚至都不敢看到邮件、微信、短信等一系列与工作相关的通知,一度还有幻听,总感觉有微信 at 我的那个声音。 90 | 91 | 当然除了这些我也真的非常感谢我之前的领导,非常的信任我,也是我迄今为止遇到的最 nice 的领导之一。 92 | 93 | 94 | 好,马屁拍完后简单聊聊我现在的工作。现在这家公司是一家纯互联网的创业公司,也是少数可以盈利并活过三年的创业公司。 95 | 96 | 公司氛围真的非常好,也是我工作以来氛围最 nice 的团队,注意这里没有”之一“。 97 | 98 | 每周三我们有宅男快乐日(其实就是下班后一起打球),成为了每周大家最期待的日子。 99 | 100 | ![](https://i.loli.net/2020/01/02/swXvuSV98MYJrFf.jpg) 101 | 102 | 而我的工作内容也发生了变化,由上家公司偏团队管理转换为我熟悉的纯技术人员,可能有人说这不相当于降级了嘛?但其实我在加入这个团队时就明确表示不想做管理,我享受在我这个年龄阶段做纯研发工作的过程。 103 | 104 | 也许再过几年我可能会后悔,但 who care 呢,现在舒服就够了。 105 | 106 | > 顺便插个广告,我们团队来年也要扩充人马,欢迎从外边回重庆及重庆本地的有志青年联系我😋。 107 | 108 | # 技术 109 | 110 | 其实就技术上来说今年本身进步并不多,主要是下半年换工作后我的主力语言由 Java 切换为”人生苦短,我用 Python“了。期间大部分是在用 Python 来完成工程实践,也有部分工作是由我来维护 Java 相关的内容;随着我们业务的发展、人员的扩充不可避免的会由弱类型语言转换强类型,这是目前大部分技术团队发展的规律。 111 | 112 | 所以就 Java 本身来说我也会继续学习,后面也会继续更新相关的技术文章,待我 Python 入化境后也会尝试写写相关内容。 113 | 114 | 115 | ## 开源 116 | 117 | 同样的下半年之后其实我也没怎么投入精力到开源项目中了,有好多 issues 都还没来得及处理。最近在写一个 orm 相关的框架,断断续续写了两个月,本来打算月初分享出来也被耽搁了。 118 | 119 | > 这其实最近再使用 Python ORM 时觉得还不错的一个 features 移植过来的。 120 | 121 | ![](https://i.loli.net/2020/01/02/o7Yfem1WFSNniyl.jpg) 122 | 123 | 年初本来想在年底冲一冲争取访问量过百万,同样的还是因为没有更新现在还差一点🤣。 124 | 125 | 公众号倒是自增长到了 2W 关注,这点我倒是真没想到,毕竟上次打开这个后台也是两个月前的事了🥶。 126 | 127 | ![](https://i.loli.net/2020/01/02/cdJIeo7MB5vWLqR.jpg) 128 | 129 | 130 | # 总结 131 | 132 | 现在来看看去年立下的 flag : 133 | 134 | ![](https://i.loli.net/2020/01/02/FEengTOklbX5Jz1.jpg) 135 | 136 | 好吧,只完成了一项;前几天看到有人说:不要在年初定目标,因为当前大脑容易兴奋不易看清自己。 137 | 138 | 但目标不在年初立那还有啥意义?所以还是按照历史传统立个 flag: 139 | 140 | - 婚礼办了,去日本度个蜜月。 141 | - Python 玩的更熟一些,同时能把之前的项目经验带到团队中。 142 | - 减肥、健身;这个计划从前年就开始了。 143 | - vlog 得接着拍起来,再买台无人机换个拍摄视角。 144 | - 最后就是技术输出了,我预计应该是做不到一周一更了,但两周一更还是可以保证。 145 | 146 | 147 | -------------------------------------------------------------------------------- /annual-summary/2021.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 2021 年度报告 3 | date: 2022/01/27 08:13:26 4 | categories: 5 | - annual-summary 6 | --- 7 | 8 | ![](https://s2.loli.net/2022/03/13/3KxYVB5liTZvHzJ.jpg) 9 | 10 | 不知不觉年终总结就像每个人的 KPI 一样,年底不总结一下感觉今年就白过了似的。 11 | 12 | 今年时间真的感觉过的特别快,经常感觉工资刚发不久结果没几天又到了发薪日;再也没有小时候一个暑假都能过一年的感觉。 13 | 14 | 15 | 16 | # 生活 17 | 18 | 生活上来说最大的变化也许就是年龄+1了,今年也是我们结婚两年恋爱十年的时间;十年这样的跨度现在想想还是觉得不可思议。 19 | 20 | ![](https://s2.loli.net/2023/01/17/ozGCbWA48hgFeTL.png) 21 | 22 | 好在目前为止我们双方父母都没有催生,一切都顺其自然吧。 23 | 24 | --- 25 | 26 | 虽然每天都是公司、家里两点一线的生活,但没想到的是今年居然喜欢上和我毫无关系的一项运动:足球。 27 | 28 | 原本是公司每周组织的足球活动 14 缺 1,没事就去踢了一次,结果发现还挺好玩; 29 | 30 | ![](https://s2.loli.net/2023/01/17/gIENyimZAD2BVSM.png) 31 | ![](https://s2.loli.net/2023/01/17/C7Sk3vtoW9wGzFL.png) 32 | 虽然每次报名的都是原有的篮球队员,此消彼长自然篮球就没啥人报名了😂。 33 | 34 | 贴几段足球小视频: 35 | 36 | 37 | 38 | 39 | 40 | 41 | ![](https://s2.loli.net/2023/01/17/TkHo1C5KsWLSRdO.png) 42 | > 今年最后一场 43 | 44 | # 工作 45 | 46 | 总的来说今年工作上的变化是最大的,其实简单来说就是我们被收购了;之前总是在网上看别人公司的小道消息吃瓜,没想到这次吃到自己头上来了。 47 | 48 | 幸运的是我个人受到的影响不大,毕竟也不是公司领导层,我们只需要做好自己的事情就行了。 49 | 50 | 当然落实到我们日常工作最明显的变化可能就是开发流程的变化了,这点在后文会具体说明。 51 | 52 | ![](https://s2.loli.net/2023/01/17/AbgWsZxPXw5t9fi.png) 53 | 今年因为主导了几个系统的重构以及部门内部技术的推动,相对去年来说成绩上还是有所提升的,所以年底也评了优,算是对我工作的肯定吧。 54 | 55 | 但其实我个人不是特别满意,几个项目推动了一半,最终也没达到预期目标,只能寄托于来年了。 56 | 57 | 58 | # 技术 59 | 60 | 由于公司的调整自然也带来了我们技术栈的变化;简单来说经历了几个阶段: 61 | 62 | ![](https://s2.loli.net/2023/01/17/xJNL2B8YVd5R37M.png) 63 | 64 | 其实每个阶段都和公司业务+组织架构+业务现状有着千丝万缕的关系;个人的喜好很难起决定性作用。 65 | 66 | 所以绕来绕去,今年我又得开始写 `Java` 了;最近这三年时间从 `Java` 转到 `Python`,体验到了各种便捷的语法糖,又写了将近两年 `Go` 之后体验到了大道至简的优雅。 67 | 68 | 三种语言都各有优势,但从内心深处来讲我还是更愿意写 Go;可能是不想再去卷很难用到的八股文、配置繁琐的 `maven` 等。 69 | 70 | 由于团队内部有些同事没有接触过 `Java` ,所以让我以新手角度带大家一起学习;新版本的 JDK 语法糖+ `lombok` + `mapstruct` 这类工具,配合上最新的 IDEA 开发起来也是非常舒服的。 71 | 72 | 用单纯的 `SpringBoot` 结合 `k8s` 后,之前的 [https://github.com/crossoverJie/feign-plus](https://github.com/crossoverJie/feign-plus) 就有用武之处了。 73 | 74 | ## 博客 75 | ![](https://s2.loli.net/2023/01/17/pNwLoPSGD34c8fr.png) 76 | ![](https://s2.loli.net/2023/01/17/jbenoQNFK6qlCpr.png) 77 | 78 | 今年的技术原创博客产量也不高,满打满算将近 20 篇;其中大部分都与 Go 有关,近期确实大部分时间都是在写 Go,但也只是用了点皮毛;不出意外的话来年会 Java 和 Go 的内容都会写一点。 79 | 80 | ## 开源 81 | 82 | 今年的开源项目上我最喜欢的应该就是 [https://github.com/crossoverJie/ptg](https://github.com/crossoverJie/ptg) 83 | 84 | > 这是命令行的接口压测工具,同时也是一个 `gRPC` 的客户端 app。 85 | 86 | ![](https://s2.loli.net/2023/01/17/svaM2ml69kGH4jJ.gif) 87 | 88 | UI 确实是我的极限了,我自己还有部分小伙伴使用了一段时间还是挺好用的。 89 | 90 | > 最近正在加 stream 调用相关的功能。 91 | 92 | --- 93 | 其实在公司内部也有用过 Go 重构过调度中心,就是大家用的挺多的 `xxl-job`。 94 | 95 | 由于我们的项目都是 `gRPC` 协议,同时运维体系之类的原因就用 Go 重写了一版。 96 | 97 | ![](https://s2.loli.net/2023/01/17/2cRFaLHPYEdVJh3.png) 98 | 99 | 最终在每日百万次数的调度下成功率`≈99.9%`,已经可以满足业务使用了; 100 | 101 | 但后期如果业务上不再使用 `Go` 的话难免会有些可惜,所以我也在想和公司沟通下,可以把这个调度中心开源出来,同时以前也说过我们内部也做了一个 Go 的业务框架,现有的调度业务代码接入调度中心也是通过该业务框架实现的。 102 | 103 | 所以也准备都开源出来,但时间上还不好说,总之希望来年还能有机会多写写 Go,能参与开源是最好的。 104 | 105 | --- 106 | 107 | 最后希望来年疫情能彻底结束了,至少能不限制的跨省旅旅游;也希望有机会能把前年办的健身卡利用起来,根据前几年的经验来看 flag 还是要少立。 108 | 109 | -------------------------------------------------------------------------------- /annual-summary/2022.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 2022 年度总结 3 | date: 2023/01/18 08:08:08 4 | categories: 5 | - annual-summary 6 | --- 7 | 8 | ![](https://s2.loli.net/2023/01/17/C9tczrjDlwfVqvR.png) 9 | 10 | 一转眼 2022 年又过去了,不多不少距离上次写年终总结过去了 365 天;今年的艰难情况想必大家都亲身经历过了;如果要举行卖惨大会的话今年也许我能排的上号。 11 | 12 | 13 | 14 | # 生活 15 | 16 | 今年对大家影响最大的事应该都是疫情了,在年底的最后几天家里的老人还是没顶住疫情的冲击离开了,原以为成年后我已经看淡了生老病死,直到我现在敲下这几行字时才发现这么难过。 17 | 18 | 悲伤的事暂且不提,还是聊聊今年生活上的好事吧。 19 | 20 | ## 健身 21 | 首先是健身这个我念叨了几年的运动今年终于被我提上了议程。 22 | 23 | > 本质原因是请了私教,果然是花自己的钱才会心疼。 24 | 25 | ![](https://s2.loli.net/2023/01/17/ojP8sQXracSHM3u.png) 26 | ![](https://s2.loli.net/2023/01/17/LWSkPHGEMf1R2UO.png) 27 | 28 | 体重也由巅峰的 75kg 降到了66kg 左右。 29 | ![](https://s2.loli.net/2023/01/17/R38tM2FOocjHGxI.png) 30 | 31 | 32 | 33 | 一段视频便能看出差距。 34 | 35 | ## 游戏 36 | 今年不记得被哪个视频安利了微软的 XGP 服务,冲动下单了 xbox,顺道集齐了御三家的全家桶。 37 | ![](https://s2.loli.net/2023/01/17/DzUT7yHYm59iZ2R.jpg) 38 | 39 | 不得不说 XGP 服务是真的香,游戏也很多,我玩的最多的就是光环、地平线5、奥日这几款游戏;原以为 xbox 后续会成为我的主力机,直到几个月后我在 tb 奸商那儿购买的 XGP 服务被微软退款后我就没怎么碰了。 40 | 41 | 后面老头环上线,也是我唯一一款花钱购买的 xbox 游戏,在被老头环揉拧了几周后手残党也被劝退,一直到现在估计三个月没开过机了。 42 | 43 | 不过最近倒还喜欢上玩 Steam 上的一些独立小游戏,特别是肉鸽类型的,比如这个“吸血鬼幸存者”玩着真的非常上头。 44 | 45 | ![](https://s2.loli.net/2023/01/17/6Y7lmhtQRMy2TiU.png) 46 | 47 | 当然今年最期待依然是那个带我入主机坑的“塞尔达传说”,恨不得现在马上快进到5月12号发售日。 48 | 49 | ## 世界杯 50 | 51 | 今年还有件大事那就是世界杯的召开,真没想到我还会对足球这么感兴趣;因为当时是封控在家远程工作,所以我几乎把凌晨三点场的都看完了。 52 | 53 | 那段时间因为离职心情还比较 EMO,感谢世界杯带给了我一个月的快乐时间。 54 | 55 | 当然不出意外的在世界杯期间发生了意外。 56 | 57 | ![](https://s2.loli.net/2023/01/17/jUMOHidkxapyRbW.jpg) 58 | 59 | 大半夜睡得好好的,眼睛被我老婆的手指甲刨到了,连夜赶往医院,最后就成了“带土”的 cos 低配版。 60 | 61 | # 工作 62 | 63 | 经常都有大佬说三年是在一家公司的敏感时间,如果感觉不到提升那就需要适当的跳出舒适圈,其实我压根没这个打算,但生活总在你没准备好的时候推你一把。 64 | 65 | ![](https://s2.loli.net/2023/01/17/3MGYFZ1fCvLsXBz.png) 66 | ![](https://s2.loli.net/2023/01/17/qcZoD2Mr7YX1Ble.jpg) 67 | 68 | 由于不可抗力因素,我还是离开了这家我有生以来呆的最开心的一家公司;虽然有许多不舍,但江湖总会相见。 69 | 70 | ![](https://s2.loli.net/2023/01/17/ztTP1F9ChJ23AR8.png) 71 | **这不我微信里最活跃的依然还是那个群。** 72 | 73 | --- 74 | 后面我花了一个月的时间把重庆大大小小的公司几乎都看了一遍,甚至还差点去成都工作了;最后阴差阳错的来到了现在的公司做我之前非常向往的基础架构+中间件研发,目前也比较满意。 75 | 76 | > 找工作那段时间也碰到许多有意思的和狗血的事情,年后单独分享。 77 | 78 | 79 | # 技能 80 | 81 | 82 | ## [GScript](https://github.com/crossoverJie/gscript) 83 | ![](https://s2.loli.net/2023/01/17/9O4pzlC6T2ISKkA.png) 84 | 85 | 今年个人最满意的就是恶补了编译原理的知识,顺带还做了一个[脚本语言](https://gscript.crossoverjie.top/);现在已经可以拿来编写网站了;也算是一个小目标达成吧。 86 | 87 | 回想起开发 gscript 的那段时间,真的是没日没夜的干,每完成一个功能就开心的飞起。 88 | 89 | ## 云原生 90 | 91 | ![](https://s2.loli.net/2023/01/17/kHa4Demu15OiytE.png) 92 | 除此之外在来到现在这家公司后接触了大量 k8s 相关的知识点,也算是把之前学到的理论实践上了;这不昨晚上才在生产环境升级了 Pulsar,这个技能树终于点亮了一些叶子节点。 93 | 94 | 95 | ## 博客 96 | ![](https://s2.loli.net/2023/01/17/q3CLIhWoM8yEtGY.png) 97 | ![](https://s2.loli.net/2023/01/17/hSxHZDsEArRFow1.png) 98 | 99 | 今年的技术博客产出居然 23 篇,其中大部分都是和编译原理相关的,也是我一步步学习编译原理到实现脚本语言的过程。 100 | 101 | --- 102 | ![](https://s2.loli.net/2023/01/17/mEPTMByJIAKaiwX.png) 103 | 同时今年也养成了每日看一篇英文博客的习惯,坚持了几个月效果还是很明显的;比如以前我非常排斥看一些英文资料,要么靠一键翻译,要么就直接只看中文内容。 104 | 105 | 现在几乎没有这种排斥的感觉了,大部分英文内容也会耐心的阅读完,这点在我订阅了 Pulsar 的开发组邮件后越发明显,明显的能够知道他们在讲些什么,这点与我多年前订阅 Dubbo 社区邮件的感觉完全不同。 106 | 107 | # 目标 108 | 以上就是整年的流水账式的回顾,又到了经典的保留立 flag 环节。 109 | 110 | - 首先是健身保持,都说健身是按年算的,希望到今年四月份为期一年的时候能看到健身的痕迹。 111 | - 今年好歹的出去玩一玩,比如港澳地区或是日本,念叨几年了。 112 | - 工作技术上能够再提交几个 Pulsar 的 PR,最好是能融入社区;混个脸熟。 113 | - 云原生和编译原理相关的继续学起来,下半年把 GScript 实现为编写型语言。 114 | -------------------------------------------------------------------------------- /annual-summary/GoodBye 2016,Welcome 2017 | 码农砌墙记.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: GoodBye 2016,Welcome 2017 | 码农砌墙记 3 | date: 2016-12-31 23:35:14 4 | categories: 5 | - annual-summary 6 | --- 7 | # 前言 8 | 9 | ![](https://i.loli.net/2019/05/08/5cd1c3667b05e.jpg) 10 | 11 | 12 | 早在这个月初的时候我就很想写一篇年终总结了,因为这一年相对于去年确实是经历的太多了。 13 | 结果一直等到31号,在家里和媳妇吃完晚饭就马上打开电脑开码。 14 | 15 | # 五月二十三-第一次跳槽 16 | > **根据整年的时间线开始第一件大事自然就是换公司了。** 17 | 18 | 先来点前景提要:我是14年11月份参加工作的。当时其实还没有毕业就在一家给大型企业做定制软件开发的公司实习。刚开始工作的时候什么事情都觉得非常新奇,一个在学校学的东西能运用到实际开发中并能给用户带来便利让我觉得做码农真是一件非常正确的选择啊(ps当时真是太年轻)。 19 | 20 | 后来真是造化弄人,当时负责我参与的这个项目的负责人跳槽了,我自然就成了整个公司最熟悉此项目的人了。现在不得不佩服公司老板真是心大啊,居然让一个实习生来负责这个项目。就这样我成了整个项目的负责人,从之后的开发到测试到上线到后面的维护几乎都是我一个人在负责。 21 | 来一张当时上线的截图: 22 | ![QQ20161231-222737@2x.jpg](https://ooo.0o0.ooo/2017/05/07/590ea72eac0e7.jpg) 23 | 24 | 25 | 由于这次项目的顺利验收,公司也对我越来越信任。之后也就理所当然的又负责了几个项目。 26 | 27 | 28 | > 虽然离开了但真的非常感谢公司当时对一个什么都不懂的新人给予信任。 29 | 30 | 之后随着技术的提升我接触了github、v站这样的技术论坛,逐渐的发现天外有天,我这点雕虫小技真的完全不算什么,真正机遇与挑战并存的地方是互联网。 31 | 32 | 但是此时我已经在这家公司做了一年多了,突然离开这个舒适圈来到一个陌生的环境是需要很大勇气的,或者说需要一个刺激点。 33 | 34 | 正好[@嘟爷](http://tengj.top)成了这个导火索。那个时候我正在搭我的个人博客正好看到了他的文章,觉得写得非常好。而且正好他也正准备转向互联网,于是我给他写了一封很长的邮件说了我心中的一些疑惑与顾虑让他给点建议。 35 | 36 | 在他的建议之下我才开始投递简历准备换一家互联网公司,感谢嘟爷给了我一个这么正确的建议。 37 | 38 | 之后我顺利的进入了一个创业公司,开始了`狭义`的互联网开发道路,为什么是狭义请接着往后看。 39 | 40 | # 搭建个人博客 41 | > 搭建博客这事也是必须的拿出来说一说的。 42 | 43 | 上面说到我看了嘟爷的博客才开始搭建自己的博客,到现在为止由于我的拖延症(`加上是真的懒`)一共写了20篇。不能说写的有多好,但确实是我在工作和学习中的一些总结。 44 | 45 | 让我意外的是我博客的访问量,下图是我`cnzz`的统计截图: 46 | 47 | ![](https://i.loli.net/2019/05/08/5cd1c36b578a3.jpg) 48 | 49 | # 六月二十一-开源项目 50 | 关于开源项目,之前我在`github`上面看很多优秀的开源项目,也很佩服那些作者,于是就想着自己能不能也搞一个,但是一来就造个轮子对我来说确实有点不现实。 51 | 52 | 于是我换了一个思路,由于现在我勉强也不算是新入门的菜鸟了,但我是从菜鸟过来的,深知刚开始的时候找资料的痛苦。不是资料太老就是没有体系,讲一点是一点的那种。 53 | 54 | 于是就有了现在这个项目: 55 | `会不定期更新一些在实际开发中使用的技巧(ps:目前不是很忙基本上一周一更)。 没有复杂的业务流程,更不是XXXX系统,只有一些技术的分享。` 56 | 57 | 从六月二十一号到现在还是有100多颗星了: 58 | 59 | ![](https://i.loli.net/2019/05/08/5cd1c36f7774a.jpg) 60 | 61 | # 九月二十三-第二次跳槽 62 | 看到这里是不是觉得我有病啊,怎么又是跳槽。。。 63 | 64 | 其实我也不想,我在上面说到开始了我的`狭义`互联网开发,为什么是狭义呢? 65 | 66 | 因为做了一段时间才发现这个项目除了是部署在云服务器上和有一个微信端之外和我之前所做的项目貌似没有本质上的区别,还是一个管理系统。 67 | 68 | 这里我不评价公司的业务,但是公司的技术总监在修改问题的时候是直接在云服务器上登陆数据库删除数据,会不会觉得很奇葩。最奇葩的是删除的时候忘了写`where`条件导致把整张表的数据都删了,这个时候如果是你你会不会怀疑那啥。。 69 | 70 | 除此之外技术总监本人还是挺好的,不过我更觉得他适合做销售总监。 71 | 72 | 加上后来公司的业务没有发展起来,所做的系统又老是出问题(联想上文),加上还在流传我们技术部要裁人。那我还不如自己走(现在V站逛多了突然觉得好亏)。 73 | 74 | 于是我开始了我的第二次跳槽,前后时间才间隔4个月,不得不感慨命运弄人啊。 75 | 76 | 之后我来到现在这家员工5000余人的真正的互联网公司,开始了真正意义的互联网开发。这里必须得感谢我的面试官也是我现在这个项目的leader,给了我这个互联网菜鸟机会。 77 | 78 | 不过命运总是如此的相识,明年也就是下周他就换部门了,意味着现在这个项目我又成`负责人`了。希望一切顺利吧。 79 | 80 | 81 | # 技术相关 82 | 前面说到我是九月份的时候才进入这家正真意义的互联网公司的,所以体术提升最明显也是在这段时间。 83 | 84 | 这段时间所学的起码是我在前面两家公司一年都学不到的,这里我大致列了一下: 85 | 86 | - 熟悉了一个互联网产品的生命周期(关于开发、测试、预发布、灰度以及上线) 87 | - 熟悉了一些关于`并发`、`主从`、`缓存`、`调度`、`容器`这些主流的技术。 88 | - 最重要的一点,学会了不加班不舒服斯基。 89 | 90 | # 身体相关 91 | 不知是错觉还是什么,感觉今年看到`IT`行业猝死或者是出事的新闻越来越多,加上我这个今年才22岁的青年有时候也会腰疼脖子酸,导致我对于身体也是越来越担忧。 92 | 93 | 其实我从初中的时候就开始打篮球,在工作之前也是对篮球完全是痴迷的状态,每天不打球就浑身难受。刚工作的那段时间还能坚持每周末去打球,但是今年能做到一个月打一次都非常难得了。。 94 | 95 | 再此,我立个`flag`,明天下午出去打球,明年坚持至少每两周打一次球。 96 | 97 | # 2017小目标 98 | 到这里也基本上总结的差不多了,还有半个小时就是17年了。 99 | 100 | 还是定一个17年的小目标吧: 101 | > - 博客坚持写,至少保持两周一更。 102 | > - 开源项目坚持维护,争取造一个轮子出来。 103 | > - 坚持锻炼,我还得养家糊口。 104 | > - 最后希望家人朋友都平平安安。 105 | -------------------------------------------------------------------------------- /annual-summary/img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crossoverJie/blog-post/970289c43b750b2c0b6a5f8bd86aecb111e5c502/annual-summary/img.png -------------------------------------------------------------------------------- /annual-summary/img_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crossoverJie/blog-post/970289c43b750b2c0b6a5f8bd86aecb111e5c502/annual-summary/img_1.png -------------------------------------------------------------------------------- /annual-summary/img_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crossoverJie/blog-post/970289c43b750b2c0b6a5f8bd86aecb111e5c502/annual-summary/img_2.png -------------------------------------------------------------------------------- /exam/2018-06-07-The-university-entrance-exam.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 记于 2018 年高考! 3 | date: 2018/06/06 22:20:46 4 | categories: 5 | - 小情绪 6 | --- 7 | 8 | ## 2012/02/28 9 | 10 | ![](https://i.loli.net/2019/05/08/5cd1d5256dfa7.jpg) 11 | 12 | > 2012年二月二十八日。 13 | 14 | 这天学校举行了`高考 100 天誓师大会`,当时完全不知道意味着什么,只感觉现场热血沸腾、激情高涨,心里告诉自己就算只剩下 100 天我也能考上清华其次也是北大。 15 | 16 | ## 2012/06/03 17 | 18 | > 2012年六月三日。 19 | 20 | ![](https://i.loli.net/2019/05/08/5cd1d528366b0.jpg) 21 | 22 | 晚自习拿出前段时间刚拍的毕业合照,恨死摄影师,没有抓拍到我最帅的角度😡。 23 | 24 | 25 | 26 | ## 2012/06/04 27 | 28 | > 2012年六月四日。 29 | 30 | ![](https://i.loli.net/2019/05/08/5cd1d52ded397.jpg) 31 | 32 | 离校前的最后一晚,我们像往常每周的音乐晚自习一样,由音乐委员(@猪娅)带着大家唱可米小子的青春纪念册。 33 | 34 | 小红姐(班主任)特别的没来查岗。 35 | 36 | 心里想着,这就是青春嘛?也不过如此。 37 | 38 | 大家拿着热和的手机(才发的,平时会收)肆意的拍着照片: 39 | 40 | ![](https://i.loli.net/2019/05/08/5cd1d530ae860.jpg) 41 | 42 | 那时没有美颜、没有修图,一切都是那么和谐。 43 | 44 | 45 | ## 2012/06/05 46 | 47 | > 2012年六月五日。 48 | 49 | 是进津(江津)赶考,走时特地在六食堂买了一个包子,没想到是在学校最后一顿早餐。 50 | 51 | 车上大家有说有笑,嗯,就像是资深导游带的一个低价旅游团,每人心里充满了惊喜却不知即将面临什么。 52 | 53 | ![](https://i.loli.net/2019/05/08/5cd1d5331159c.jpg) 54 | 55 | ## 2012/06/07 56 | 57 | > 2012年六月七日。 58 | 59 | 大家在各自的考场奋笔疾书,用两天四场考试来为高中三年画上句号。 60 | 61 | 有的梦想进入理想的大学、和心仪的 `TA` 长相厮守,当然也有回家继承百万家产😂。 62 | 63 | 而我当时只想快速的结束这一切,高中三年,特别是高三这年真的是够了。每天做不完的卷子,背不完的诗词,还得想着为陈家老祖宗出一位正儿八经的大学生。 64 | 65 | 所以考试完全采用人卷合一的心态(能做就做,不会就过)快速的过完了这两天。 66 | 67 | ![](https://i.loli.net/2019/05/08/5cd1d5364e5cc.jpg) 68 | 69 | 这些作文题目还看得懂嘛。。 70 | 71 | ## 2018/06/07 72 | 73 | > **2018**年六月七日。 74 | 75 | 高中学过许多关于时光飞逝的成语、古诗,但都没有亲身体会那么深刻! 76 | 77 | 六年时间,红了樱桃,绿了芭蕉。 78 | 79 | 有的步入职场、升职加薪、求婚成功、穿上婚纱、组建家庭、初为人母。 80 | 81 | 每人都过着各自的生活,但一旦相见就有数不尽的话题(@江源),逃课打球、翻墙上网、暗恋女神、天天向上、作业卷子。 82 | 83 | 这句话送给高2012级10班的所有同学: 84 | 85 | ***愿你出走半生,归来仍是少年*** 86 | 87 | 一大波图片即将袭来: 88 | 89 | 摆拍虽好,不要抽烟哦: 90 | 91 | ![](https://i.loli.net/2019/05/08/5cd1d538da15f.jpg) 92 | 93 | 94 | 小红姐生日快乐,永远十八: 95 | 96 | ![](https://i.loli.net/2019/05/08/5cd1d53fe6643.jpg) 97 | 98 | 集体生日,年年十八: 99 | 100 | ![](https://i.loli.net/2019/05/08/5cd1d542680ea.jpg) 101 | 102 | ![](https://i.loli.net/2019/05/08/5cd1d544d8c0c.jpg) 103 | 104 | 状元书摊,不是第一不卖: 105 | 106 | ![](https://i.loli.net/2019/05/08/5cd1d54662671.jpg) 107 | -------------------------------------------------------------------------------- /framework-design/dynamic-rpc.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 动态代理与RPC 3 | date: 2020/04/28 09:10:36 4 | categories: 5 | - cim 6 | - rpc 7 | - 动态代理 8 | tags: 9 | - Java 10 | - Netty 11 | --- 12 | 13 | ![](https://i.loli.net/2020/04/28/mDN2XjShVI4TBLx.jpg) 14 | 15 | # 前言 16 | 17 | ![](https://i.loli.net/2020/04/28/dMDreQw6JAk87Gs.jpg) 18 | 随着最近关注 [cim](https://github.com/crossoverJie/cim) 项目的人越发增多,导致提的问题以及 Bug 也在增加,在修复问题的过程中难免代码洁癖又上来了。 19 | 20 | 看着一两年前写的东西总是怀疑这真的是出自自己手里嘛?有些地方实在忍不住了便开始了漫漫重构之路。 21 | 22 | 23 | 24 | # 前后对比 25 | 26 | 在开始之前先简单介绍一下 `cim` 这个项目,下面是它的架构图: 27 | ![](https://i.loli.net/2020/04/28/LfaP3sFtclEoVX5.jpg) 28 | 29 | 简单来说就是一个 IM 即时通讯系统,主要有以下部分组成: 30 | 31 | - `IM-server` 自然就是服务端了,用于和客户端保持长连接。 32 | - `IM-client` 客户端,可以简单认为是类似于的 QQ 这样的客户端工具;当然功能肯定没那么丰富,只提供了一些简单消息发送、接收的功能。 33 | - `Route` 路由服务,主要用于客户端鉴权、消息的转发等;提供一些 http 接口,可以用于查看系统状态、在线人数等功能。 34 | 35 | 当然服务端、路由都可以水平扩展。 36 | 37 | --- 38 | 39 | ![](https://i.loli.net/2020/04/28/eBumPJbrvyQsHTF.jpg) 40 | 41 | 这是一个消息发送的流程图,假设现在部署了两个服务端 A、B 和一个路由服务;其中 `ClientA` 和 `ClientB` 分别和服务端 A、B 保持了长连接。 42 | 43 | 当 `ClientA` 向 `ClientB` 发送一个 `hello world` 时,整个的消息流转如图所示: 44 | 45 | 1. 先通过 `http` 将消息发送到 `Route` 服务。 46 | 2. 路由服务得知 `ClientB` 是连接在 `ServerB` 上;于是再通过 `http` 将消息发送给 `ServerB`。 47 | 3. 最终 `ServerB` 将消息通过与 `ClientB` 的长连接通道 `push` 下去,至此消息发送成功。 48 | 49 | 这里我截取了 `ClientA` 向 `Route` 发起请求的代码: 50 | ![](https://i.loli.net/2020/04/28/7FRrUh1o4GPJCuI.jpg) 51 | 可以看到这就是利用 `okhttp` 发起了一个 `http` 请求,这样虽然能实现功能,但其实并不优雅。 52 | 53 | 举个例子:假设我们需要对接支付宝的接口,这里发送一个 http 请求自然是没问题;但对于支付宝内部各部门直接互相调用接口时那就不应该再使用原始的 http 请求了。 54 | 55 | 应该是由服务提供方提供一个 `api` 包,服务消费者只需要依赖这个包就可以实现接口调用。 56 | 57 | > 当然最终使用的是 http、还是自定义私有协议都可以。 58 | 59 | 也类似于我们在使用 `Dubbo` 或者是 `SpringCloud` 时,通常是直接依赖一个 `api` 包,便可以像调用一个本地方法一样调用远程服务了,并且完全屏蔽了底层细节,不管是使用的 http 还是 其他私有协议都没关系,对于调用者来说完全不关心。 60 | 61 | 这么一说是不是有内味了,这不就是 RPC 的官方解释嘛。 62 | 63 | 对应到这里也是同样的道理,`Client` 、`Route`、`Server` 本质上都是一个系统,他们互相的接口调用也应当是走 `RPC` 才合理。 64 | 65 | 66 | 所以我重构之后的变成这样了: 67 | 68 | ![](https://i.loli.net/2020/04/28/R4sqwHxAMYmz7f9.jpg) 69 | 70 | 是不是代码也简洁了许多,就和调用本地方法一样了,而且这样也有几个好处: 71 | 72 | - 完全屏蔽了底层细节,可以更好的实现业务及维护代码。 73 | - 即便是服务提供方修改了参数,在编译期间就能很快发现,而像之前那样调用是完全不知情的,所以也增加了风险。 74 | 75 | 76 | 77 | # 绕不开的动态代理 78 | 79 | 下面来聊聊具体是如何实现的。 80 | 81 | 其实在上文[《动态代理的实际应用》](https://crossoverjie.top/2020/03/30/wheel/cicada9-proxy/) 中也有讲到,原理是类似的。 82 | 83 | 要想做到对调用者无感知,就得创建一个接口的代理对象;在这个代理对象中实现编码、调用、解码的过程。 84 | 85 | ![](https://i.loli.net/2020/04/28/REoaJPXzOlmBjnK.jpg) 86 | 87 | 对应到此处其实就是创建一个 `routeApi` 的代理对象,关键就是这段代码: 88 | 89 | ```java 90 | RouteApi routeApi = new ProxyManager<>(RouteApi.class, routeUrl, okHttpClient).getInstance(); 91 | ``` 92 | 93 | 完整源码如下: 94 | ![](https://i.loli.net/2020/04/28/Z3aJOxrDyeNqzo2.jpg) 95 | 96 | 其中的 `getInstance()` 函数就是返回了需要被代理的接口对象;而其中的 `ProxyInvocation` 则是一个实现了 `InvocationHandler` 接口的类,这套代码就是利用 `JDK` 实现动态代理的三板斧。 97 | 98 | ![](https://i.loli.net/2020/04/28/N6CoDig2SHjb7lI.jpg) 99 | 100 | 查看 `ProxyInvocation` 的源码会发现当我们调用被代理接口的任意一个方法时,都会执行这里的 `invoke()` 方法。 101 | 102 | 而 `invoke()` 方法自然就实现了上图中提到的:编码、远程调用、解码的过程;相信大家很容易看明白,由于不是本次探讨的重点就不过多介绍了。 103 | 104 | # 总结 105 | 106 | 其实理解这些就也就很容易看懂 `Dubbo` 这类 `RPC` 框架的核心源码了,总体的思路也是类似的,只不过使用的私有协议,所以在编解码时会有所不同。 107 | 108 | 所以大家要是想自己动手实现一个 `RPC` 框架,不妨参考这个思路试试,当用自己写的代码跑通一个 `RPC` 的 `helloworld` 时的感觉是和自己整合了一个 `Dubbo`、`SpringCloud` 这样的第三方框架的感觉是完全不同的。 109 | 110 | 本文的所有源码: 111 | 112 | [https://github.com/crossoverJie/cim](https://github.com/crossoverJie/cim) 113 | 114 | **你的点赞与分享是对我最大的支持** 115 | -------------------------------------------------------------------------------- /framework-design/sharding-db-02.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 分表后需要注意的二三事 3 | date: 2019/06/13 08:10:00 4 | categories: 5 | - 架构 6 | tags: 7 | - db 8 | --- 9 | 10 | 11 | ![](https://i.loli.net/2019/06/13/5d012e6a6fd7729354.jpg) 12 | 13 | # 前言 14 | 15 | 本篇是上一篇[《一次分表踩坑实践的探讨》](https://crossoverjie.top/2019/04/16/framework-design/sharding-db/),所以还没看过的朋友建议先看上文。 16 | 17 | 18 | 还是先来简单回顾下上次提到了哪些内容: 19 | 20 | - 分表策略:哈希、时间归档等。 21 | - 分表字段的选择。 22 | - 数据迁移方案。 23 | 24 | 25 | 26 | 而本篇文章的背景是在我们上线这段时间遇到的一些问题并尝试解决的方案。 27 | 28 | # 问题产生 29 | 30 | 之前提到在分表应用上线前我们需要将原有表的数据迁移到新表中,这样才能保证业务不受影响。 31 | 32 | ![](https://i.loli.net/2019/06/13/5d0126128f1e678414.jpg) 33 | 34 | 所以我们单独写了一个迁移应用,它负责将大表中的数据迁移到 64 张分表,而再迁移过程中产生的数据毕竟是少数,最后在上线当晚再次迁移过去即可。 35 | 36 | 一切想的很美好,当这个应用上线后却发现没这么简单。 37 | 38 | ## 数据库负载升高 39 | 40 | 首先第一个问题是数据库自己就顶不住了,在我们上这个迁移程序之前数据库的压力本身就比较大,这个应用一上去就成了最后一根稻草。 41 | 42 | 最后导致的结果是:所有连接了数据库的程序大部分的操作都出现超时,获取不到数据库连接等一系列的异常。 43 | 44 | 最后没办法我们只能把这个应用放到凌晨执行,但其实后面观察发现依然不行。 45 | 46 | 虽说凌晨的业务量下降,但依然有少部分的请求过来,也会出现各种数据库异常。 47 | 48 | 再一个是迁移程序的效率也非常低下,按照这样是速度,我们预估了一下迁移时间,大约需要 10 几天才能把三张最大的表(3、4亿的数据)迁移到分表中。 49 | 50 | 51 | 于是我们换了一个方案,将这个迁移程序在从库中运行,最后再用运维的方法将分表直接导入进主库。 52 | 53 | 因为从库的压力要比主库小很多,对业务的影响很小,同时迁移的效率也要快很多。 54 | 55 | 即便是这样也花了一晚上+一个白天的时间才将一张 1亿的数据迁移完成,但是业务上的压力越来越大,数据量再不断新增,这个效率依然不够。 56 | 57 | # 兼容方案 58 | 59 | 最终没办法只有想一个不迁移数据的方案,但是新产生的数据还是往分表里写,至少保证大表的数据不再新增。 60 | 61 | 但这样对于以前的数据咋办呢?总不能不让看了吧。 62 | 63 | 其实对于数据的操作无非就分为`增删改查`,就这四种操作来看看如何兼容。 64 | 65 | ## 新增 66 | 67 | ![](https://i.loli.net/2019/06/13/5d012612de57f13422.jpg) 68 | 69 | 新增最简单,所有的数据根据分表规则直接写入新表,这样可以保证老表的数据不再新增。 70 | 71 | ## 删除 72 | 73 | 删除就要比新增稍微复杂一些,比如用户想要删除他个人产生的一条信息(比如说是订单数据),有可能这个数据在新表也可能在老表。 74 | 75 | ![](https://i.loli.net/2019/06/13/5d01261336daf24547.jpg) 76 | 77 | 所以删除时优先删除新表(毕竟新产生的数据访问的频次越高),如果删除失败再从老表删除一次。 78 | 79 | ## 修改 80 | 81 | ![](https://i.loli.net/2019/06/13/5d01261380d6599845.jpg) 82 | 83 | 而修改同理,同样的会不确定数据存在于哪里,所以先要修改新表,失败后再次修改老表。 84 | 85 | ## 查询 86 | 87 | 查询相对就要复杂一些了,因为这些大表的数据大部分都是存放一个用户产生的多条记录(比如一个用户的订单信息)。 88 | 89 | 这时在页面上通常都会有分页,并且按照时间进行排序。 90 | 91 | 麻烦的地方就出在这里:既然是要分页那就有可能出现要查询一部分分表数据和原来的大表数据做组合。 92 | 93 | 所以这里的查询其实分为三种情况。 94 | 95 | ![](https://i.loli.net/2019/06/13/5d012613da28015150.jpg) 96 | 97 | - 首先查询的时候要计算这个用户所在分表中的数据可以分为几页。 98 | - 第一步首先判断当前页是否可以在分表中全部获取,如果可以则直接从分表中取出数据返回(假设分页中总共可以查询 2 页数据,当前为第 1 页,那就全部取分表数据)。 99 | - 如果不可以就要判断当前页数在分表中是否取不到任何一条数据,如果是则直接取老表数据(比如现在要取第 5 页的数据,分表中一共才只有 2 页数据,所以第 5 页数据只能全部从老表中获取)。 100 | - 但如果分表和老表都存在一部分数据时,则需要同时取两张表然后做一个汇总再返回。 101 | 102 | **这种逻辑只适用于根据分表字段进行查询分页的前提下** 103 | 104 | 105 | 106 | --- 107 | 108 | 109 | 110 | 我想肯定会有朋友提出这样是否会有性能问题? 111 | 112 | 同时如果在计算分表分页数量时出现并发写入的情况,导致分页数量不准从而对后续的查询出现影响该怎么处理? 113 | 114 | 首先第一个性能问题: 115 | 116 | 其实这个要看怎么取舍,为了这样的兼容目的其实会比常规查询多出几个步骤: 117 | 118 | - 判断当前页是否可以在分表中查询。 119 | - 当新老表中都有数据时候需要额外多查询一张大表。 120 | 121 | 第一个判断逻辑其实是在内存中计算,这个损耗我觉得完全可以忽略不计。 122 | 123 | 至于第二步确实会有损耗,毕竟多查了一张表。 124 | 125 | 但在分表之前所有的数据都是从老表中获取的,当时的业务也没有出现问题;现在多的只是查询分表而已,但分表的数据量肯定要比大表小的多,而且有索引,所以这个效率也不会慢多少。 126 | 127 | 而且根据局部性原理及用户的使用习惯来看,老表中的数据很少会去查询,随着时间的推移所有的数据肯定都会从分表中获取,逐渐老表就会成为历史表。 128 | 129 | 130 | 而第二个并发带来的问题我觉得影响也不大,一定要这个分页准的前提肯定得是加锁了,但为了这样一个不痒的小问题却带来性能的下降,我觉得是不划算的。 131 | 132 | 而且后续我们也可以慢慢的将老表的数据迁移到新表,这样就可以完全去掉这个兼容逻辑了,所有的数据都从分表中获取。 133 | 134 | 135 | # 总结 136 | 137 | 还是之前那句话,这里的各种操作、方法不适合所有人,毕竟脱离场景都是耍牛氓。 138 | 139 | 比如分表搞的早,业务上允许一定的时间将数据迁移到分表那就不会有这次的兼容处理。 140 | 141 | 甚至一开始业务规划合理、团队架构师看的长远,一来就将关键数据分表存储那根本就不会有数据迁移这个流程(大厂有经验的团队可能,小公司小作坊都得靠自己摸索)。 142 | 143 | 144 | 这段期间也被数据库折腾惨了,数据库是最后一根稻草果然也不是瞎说的。 145 | 146 | 147 | 148 | 149 | **你的点赞与分享是对我最大的支持** 150 | -------------------------------------------------------------------------------- /gjson/xjson03.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: XJSON 是如何实现四则运算的? 3 | date: 2022/07/12 08:12:36 4 | categories: 5 | - xjson 6 | - compiler 7 | tags: 8 | - go 9 | --- 10 | 11 | ![](https://tva1.sinaimg.cn/large/e6c9d24ely1h42y3ylnbuj20wi0lomz7.jpg) 12 | # 前言 13 | 14 | 在[上一篇](https://crossoverjie.top/2022/07/04/gjson/gjson02/)中介绍了 `xjson` 的功能特性以及使用查询语法快速方便的获取 `JSON` 中的值。 15 | 16 | ![](https://tva1.sinaimg.cn/large/e6c9d24ely1h42btsa4cgj21cs0mmjuh.jpg) 17 | 18 | 同时这次也更新了一个版本,主要是两个升级: 19 | 20 | 1. 对转义字符的支持。 21 | 2. 性能优化,大约提升了30%⬆️。 22 | 23 | 24 | ## 转义字符 25 | 26 | 先说第一个转义字符,不管是原始 `JSON` 字符串中存在转义字符,还是查询语法中存在转义字符都已经支持,具体用法如下: 27 | 28 | ```go 29 | str = `{"1a.b.[]":"b"}` 30 | get = Get(str, "1a\\.b\\.\\[\\]") 31 | assert.Equal(t, get.String(), "b") 32 | 33 | str = `{".":"b"}` 34 | get = Get(str, "\\.") 35 | assert.Equal(t, get.String(), "b") 36 | 37 | str = `{"a":"{\"a\":\"123\"}"}` 38 | get = Get(str, "a") 39 | fmt.Println(get) 40 | assert.Equal(t, get.String(), "{\"a\":\"123\"}") 41 | assert.Equal(t, Get(get.String(), "a").String(), "123") 42 | 43 | str = `{"a":"{\"a\":[1,2]}"}` 44 | get = Get(str, "a") 45 | fmt.Println(get) 46 | assert.Equal(t, get.String(), "{\"a\":[1,2]}") 47 | assert.Equal(t, Get(get.String(), "a[0]").Int(), 1) 48 | ``` 49 | 50 | ## 性能优化 51 | 性能也有部分优化,大约比上一版本提升了 30%。 52 | 53 | ```go 54 | pkg: github.com/crossoverJie/xjson/benckmark 55 | cpu: Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz 56 | BenchmarkDecode-12 14968 77130 ns/op 44959 B/op 1546 allocs/op 57 | PASS 58 | 59 | ------------------------------------ 60 | pkg: github.com/crossoverJie/xjson/benckmark 61 | cpu: Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz 62 | BenchmarkDecode-12 19136 62960 ns/op 41593 B/op 1407 allocs/op 63 | PASS 64 | ``` 65 | 66 | 但总体来说还有不少优化空间,主要是上限毕竟低,和官方库比还是有不小的差距。 67 | 68 | # 实现四则运算 69 | 70 | 接下来聊聊四则运算是如何实现的,这本身算是一个比较有意思的 `feature`,虽然用的场景不多🙂。 71 | 72 | 先来看看是如何使用的: 73 | 74 | ```go 75 | json :=`{"alice":{"age":10},"bob":{"age":20},"tom":{"age":20}}` 76 | query := "(alice.age+bob.age) * tom.age" 77 | arithmetic := GetWithArithmetic(json, query) 78 | assert.Equal(t, arithmetic.Int(), 600) 79 | ``` 80 | 输入一个 `JSON` 字符串以及计算公式然后得到计算结果。 81 | 82 | 其实实现原理也比较简单,总共分为是三步: 83 | 1. 对 `json` 进行词法分析,得到一个四则运算的第一步 `token`。 84 | 2. 基于该 `token` 流,生产出最终的四则运算表达式,比如 `(3+2)*5` 85 | 3. 调用四则运算处理器,拿到最终结果。 86 | 87 | --- 88 | 先看第一步,根据 `(alice.age+bob.age) * tom.age` 解析出 `token`: 89 | ![](https://tva1.sinaimg.cn/large/e6c9d24ely1h43ekglsqej21g6078jsl.jpg) 90 | 91 | 第二步,解析该 token,碰到 `Identifier` 类型时,将其解析为具体的数据。 92 | ![](https://tva1.sinaimg.cn/large/e6c9d24ely1h43em2y8q3j21ha0l2tcj.jpg) 93 | 而其他类型的 token 直接拼接字符串即可,最终生成表达式:`(10+20)*20` 94 | 95 | > 这一步的核心功能是由 `xjson.Get(json, query)` 函数提供的。 96 | 97 | 关键代码如下图所示: 98 | ![](https://tva1.sinaimg.cn/large/e6c9d24ely1h43epvk5t8j20u00v20ws.jpg) 99 | 100 | 最终的目的就是能够生成一个表达式,只要拿到这个四则运算表达式便能得到最终计算结果。 101 | 102 | 而最终的计算逻辑其实也挺简单,构建一个 AST 树,然后深度遍历递归求解即可,如下图所示: 103 | ![](https://tva1.sinaimg.cn/large/e6c9d24ely1h43eugwrduj20n80n4mxz.jpg) 104 | 105 | > 这一步的核心功能是有之前实现的脚本解释器 [gscipt](https://github.com/crossoverJie/gscript/blob/4897b0dd0e4110820c1e69f7a692d90640325cbd/syntax/parse.go#L10) 提供的。 106 | 107 | 感兴趣的朋友可以查看源码。 108 | 109 | # 总结 110 | 111 | 一个 `JSON` 库的功能其实并不多,欢迎大家分享平时用 `JSON` 库的常用功能;也欢迎大家体验下这个库。 112 | 113 | 源码地址: 114 | [https://github.com/crossoverJie/xjson](https://github.com/crossoverJie/xjson) -------------------------------------------------------------------------------- /go/go-grpc-client-gui.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 撸了一个可调试 gRPC 的 GUI 客户端 3 | date: 2021/11/28 08:11:16 4 | categories: 5 | - Go 6 | tags: 7 | - grpc 8 | --- 9 | 10 | 11 | ![go-grpc-client-gui.md---008i3skNly1gwuz3q9a2nj30rs0rs3z1.jpg](https://i.loli.net/2021/11/29/FzbRvSVafJ6glCm.jpg) 12 | 13 | # 前言 14 | 15 | 平时大家写完 `gRPC` 接口后是如何测试的?往往有以下几个方法: 16 | 17 | 1. 写单测代码,自己模拟客户端测试。 18 | ![go-grpc-client-gui.md---008i3skNly1gwv0138u2ij31eq0lwn07.jpg](https://i.loli.net/2021/11/29/OVNjXQbYal7o9kP.jpg) 19 | 20 | 2. 可以搭一个 `gRPC-Gateway` 服务,这样就可以在 `postman` 中进行模拟。 21 | 22 | 23 | 24 | 但这两种方法都不是特别优雅;第一种方法当请求结构体嵌套特别复杂时,在代码中维护起来就不是很直观;而且代码会特别长。 25 | 26 | 第二种方法在 postman 中与请求 HTTP 接口一样,看起来非常直观;但需要额为维护一个 `gRPC-Gateway` 服务,同时接口定义发生变化时也得重新发布,使用起来稍显复杂。 27 | 28 | 于是我经过一番搜索找到了两个看起来还不错的工具: 29 | 30 | - [BloomRPC](https://github.com/bloomrpc/bloomrpc) 31 | - [https://github.com/fullstorydev/grpcui](https://github.com/fullstorydev/grpcui) 32 | 33 | ![](https://i.loli.net/2021/11/29/zJ5I12HNfpso6XK.jpg) 34 | 35 | 首先看 `BloomRPC` 页面美观,功能也很完善;但却有个非常难受的地方,那就是不支持 `int64` 数据的请求, 会有精度问题。 36 | 37 | ![](https://i.loli.net/2021/11/29/tAFs8zbN5yRJo7d.jpg) 38 | > 这里我写了一个简单的接口,直接将请求的 `int64` 返回回来。 39 | 40 | ```go 41 | func (o *Order) Create(ctx context.Context, in *v1.OrderApiCreate) (*v1.Order, error) { 42 | fmt.Println(in.OrderId) 43 | return &v1.Order{ 44 | OrderId: in.OrderId, 45 | Reason: nil, 46 | }, nil 47 | } 48 | ``` 49 | 会发现服务端收到的数据精度已经丢失了。 50 | 51 | 这个在我们大量使用 `int64` 的业务中非常难受,大部分接口都没法用了。 52 | 53 | --- 54 | ![](https://i.loli.net/2021/11/29/VaBdYTsGiKzMnr8.jpg) 55 | `grpcui` 是我在使用了 `BloomRPC` 一段时间之后才发现的工具,功能也比较完善; `BloomRPC` 中的精度问题也不存在。 56 | 57 | 但由于我之前已经习惯了在 `BloomRPC` 中去调试接口,加上日常开发过程中我的浏览器几乎都是开了几十个 tap 页面,导致在其中找到 `grpcui` 不是那么方便。 58 | 59 | 所以我就想着能不能有一个类似于 `BloomRPC` 的独立 APP,也支持 `int64` 的工具。 60 | 61 | --- 62 | 63 | ## 准备 64 | 65 | 找了一圈,貌似没有发现。恰好前段时间写了一个 `gRPC` 的压测工具,其实已经把该 APP 需要的核心功能也就是泛化调用实现了。 66 | 67 | 由于核心能力是用 Go 实现的,所以这个 APP 最好也是用 Go 来写,这样复用代码会更方便一些;正好也想看看用 Go 来实现 GUI 应用效果如何。 68 | 69 | 但可惜 Go 并没有提供原生的 GUI 库支持,最后翻来找去发现了一个库:[fyne](https://github.com/fyne-io/fyne) 70 | 71 | 从 `star` 上看用的比较多,同时也支持跨平台打包;所以最终就决定使用该库在构建这个应用。 72 | 73 | 74 | # 核心功能 75 | 76 | 整个 App 的交互流程我参考了 `BloomRPC` ,但作为一个不懂审美、设计的后端开发来说,整个过程中最难的就是布局了。 77 | 78 | ![go-grpc-client-gui.md---008i3skNly1gwv8ft1l4rj30rs0eimxs.jpg](https://i.loli.net/2021/11/29/lUmXMxyZcQ3dtuW.jpg) 79 | 80 | 这是我花了好几个晚上调试出来的第一版页面,虽然也能用但查看请求和响应数据非常不方便。 81 | 82 | 于是又花了一个周末最终版如下(乍一看貌似没区别): 83 | 84 | ![ptg-min.gif](https://i.loli.net/2021/11/29/GnPF5UESwNrojOl.gif) 85 | 86 | 虽然页面上与 `BloomRPC` 还有一定差距,但也不影响使用;关键是 `int64` 的问题解决了;又可以愉快的撸码了。 87 | 88 | ## 安装 89 | 90 | 有类似需求也想体验的朋友可以在这里下载使用: 91 | [https://github.com/crossoverJie/ptg/releases/download/0.0.2/ptg-mac-gui.tar](https://github.com/crossoverJie/ptg/releases/download/0.0.2/ptg-mac-gui.tar) 92 | 93 | 由于我手上暂时没有 `Windows` 电脑,所以就没有打包 exe 程序;有相关需求的朋友可以自行下载源码编译: 94 | 95 | ```go 96 | git clone git@github.com:crossoverJie/ptg.git 97 | cd ptg 98 | make pkg-win 99 | ``` 100 | 101 | # 后续计划 102 | 当前版本的功能还比较简陋,只支持常用的 `unary` 调用;后续也会逐步加上 `stream`、`metadata`、工作空间的存储与还原等支持。 103 | 104 | 对页面、交互有建议也欢迎提出。 105 | 106 | ![](https://i.loli.net/2021/11/29/zTkSKE2HWPVhfgA.jpg) 107 | 108 | > 原本是准备上传到 `brew` 方便安装的,结果折腾了一晚上因为数据不够被拒了,所以对大家有帮助或者感兴趣的话帮忙点点关注(咋有种直播带货的感觉🐶) 109 | 110 | 源码地址:[https://github.com/crossoverJie/ptg](https://github.com/crossoverJie/ptg) 111 | -------------------------------------------------------------------------------- /go/lru-cache.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 用 Go 实现一个 LRU cache 3 | date: 2021/12/20 08:11:16 4 | categories: 5 | - Go 6 | tags: 7 | - LRU cache 8 | --- 9 | 10 | ![](https://s2.loli.net/2021/12/20/Q6LHFvrl81RUVAm.jpg) 11 | 12 | # 前言 13 | 14 | 早在几年前写过关于 `LRU cache` 的文章: 15 | [https://crossoverjie.top/2018/04/07/algorithm/LRU-cache/](https://crossoverjie.top/2018/04/07/algorithm/LRU-cache/) 16 | 17 | 当时是用 Java 实现的,最近我在完善 [ptg](https://github.com/crossoverJie/ptg) 时正好需要一个最近最少使用的数据结构来存储历史记录。 18 | 19 | > ptg: Performance testing tool (Go), 用 Go 实现的 gRPC 客户端调试工具。 20 | 21 | Go 官方库中并没有相关的实现,考虑到程序的简洁就不打算依赖第三方库,自己写一个;本身复杂度也不高,没有几行代码。 22 | 23 | 24 | 25 | 配合这个数据结构,我便在 [ptg](https://github.com/crossoverJie/ptg) 中实现了请求历史记录的功能: 26 | 27 | > 将每次的请求记录存储到 lru cache 中,最近使用到的历史记录排在靠前,同时也能提供相关的搜索功能;具体可见下图。 28 | 29 | ![](https://s2.loli.net/2021/12/20/Arq5pxZoF2ksnXW.gif) 30 | 31 | # 实现 32 | 33 | ![](https://s2.loli.net/2021/12/20/1MXvgZ32WaQ8U4s.jpg) 34 | 35 | 实现原理没什么好说的,和 `Java` 的一样: 36 | 37 | - 一个双向链表存储数据的顺序 38 | - 一个 `map` 存储最终的数据 39 | - 当数据达到上限时移除链表尾部数据 40 | - 将使用到的 `Node` 移动到链表的头结点 41 | 42 | 虽然 Go 比较简洁,但好消息是基本的双向链表结构还是具备的。 43 | 44 | ![](https://s2.loli.net/2021/12/20/nEHFimku5AYOqv9.jpg) 45 | 46 | 所以基于此便定义了一个 `LruCache`: 47 | 48 | ![](https://s2.loli.net/2021/12/20/iT2ZORsy4tL5mBC.jpg) 49 | 50 | 根据之前的分析: 51 | 52 | - `size` 存储缓存大小。 53 | - 链表存储数据顺序。 54 | - `map` 存储数据。 55 | - `lock` 用于控制并发安全。 56 | 57 | ![](https://s2.loli.net/2021/12/20/zpGjK4BWcITwfA2.jpg) 58 | 59 | 接下来重点是两个函数:写入、查询。 60 | 61 | 写入时判断是否达到容量上限,达到后删除尾部数据;否则就想数据写入头部。 62 | 63 | 而获取数据时,这会将查询到的结点移动到头结点。 64 | 65 | 这些结点操作都由 List 封装好了的。 66 | ![](https://s2.loli.net/2021/12/20/xUquXg2BMoTdGFC.jpg) 67 | 68 | 所以使用起来也比较方便。 69 | 70 | 最终就是通过这个 `LruCache` 实现了上图的效果,想要了解更多细节的可以参考源码: 71 | 72 | [https://github.com/crossoverJie/ptg/blob/main/gui/lru.go](https://github.com/crossoverJie/ptg/blob/main/gui/lru.go) 73 | 74 | 75 | -------------------------------------------------------------------------------- /go/slice pointer.key: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crossoverJie/blog-post/970289c43b750b2c0b6a5f8bd86aecb111e5c502/go/slice pointer.key -------------------------------------------------------------------------------- /gscript/gscript01.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 几百行代码实现一个脚本解释器 3 | date: 2022/05/30 08:14:36 4 | categories: 5 | - gscript 6 | - compiler 7 | tags: 8 | - go 9 | --- 10 | ![](https://tva1.sinaimg.cn/large/e6c9d24ely1h2pp039p8mj20rs0rsmy0.jpg) 11 | 12 | # 前言 13 | 最近又在重新学习编译原理了,其实两年前也复习过,当初是为了能实现通过 `MySQL` 的 `DDL` 生成 `Python` 中 `sqlalchemy` 的 `model`。 14 | 15 | ![](https://i.loli.net/2020/03/23/dLpAoxf4BwEj81S.gif) 16 | 相关文章在这里:[手写一个词法分析器](https://crossoverjie.top/2020/03/23/compilation/Lexer/) 17 | 18 | 19 | 20 | 虽然完成了相关功能,但现在看来其实实现的比较糙的,而且也只运用到了词法分析;所以这次我的目的是可以通过词法分析->语法分析->语义分析 最终能实现一个功能完善的脚本"语言"。 21 | 22 | # 效果 23 | 24 | 现在也有了一些阶段性的成果,如下图所示: 25 | ![](https://tva1.sinaimg.cn/large/e6c9d24ely1h2pp97cs39j20hi0cwgmo.jpg) 26 | ![](https://tva1.sinaimg.cn/large/e6c9d24ely1h2pp9ez7ibj20im0hcgnd.jpg) 27 | 28 | 目前具备以下基本功能: 29 | - 变量声明与赋值(只支持 int) 30 | - 二次运算(优先级支持) 31 | - 语法检查 32 | - debug 模式,可以打印 AST 33 | 34 | 感兴趣的朋友可以在这里查看源码: 35 | [https://github.com/crossoverJie/gscript](https://github.com/crossoverJie/gscript) 36 | 37 | 本地有 go 环境的话也可以安装运行。 38 | 39 | ```shell 40 | go get github.com/crossoverJie/gscript 41 | gscript -h 42 | ``` 43 | 44 | 或者直接下载二进制文件运行:[https://github.com/crossoverJie/gscript/releases](https://github.com/crossoverJie/gscript/releases) 45 | 46 | # 实现 47 | 48 | 当前版本是使用 go 编写的,确实也如标题所说,核心代码还不到 1k 行代码,当然这也和目前功能简陋有关。 49 | 50 | 不过麻雀虽小五脏俱全,从当前版本还是运用到了编译原理中的部分知识:词法、语法分析。 51 | 52 | ![](https://tva1.sinaimg.cn/large/e6c9d24ely1h2pq6r1ilkj210k0ngq4l.jpg) 53 | 54 | 基本实现流程如上图: 55 | 56 | - 通过词法分析器将源码中解析出 token 57 | - 再通过对 token 推导生成出抽象语法树(AST) 58 | - 如果语法语法出现错误,这一步骤便会抛出编译失败,比如 `2*(1+` 少了一个括号。 59 | 60 | 因为没有使用类似于 `ANTLR` 这样工具来辅助生成代码(不然功能也不会只有这么点),所以其中的词法、语法分析都是手写的,代码量并不大,对于想要调试的朋友可以直接查看源码。 61 | 62 | 词法分析器:`token/token.go:39` 63 | 语法分析器:`syntax/syntax.go` 64 | 65 | 其中会涉及到一些概念,比如有限状态机、递归下降算法等知识点就没在本文讨论了,后续这个项目功能更加完善后也会重头整理。 66 | 67 | # 规划 68 | 69 | 最后是画饼阶段了,不出意外后续会继续新增如下功能: 70 | - 更多的基础类型,string/long 之类的。 71 | - 变量作用域、函数。 72 | - 甚至是闭包。 73 | - OOP 肯定也少不了。 74 | 75 | 这些特性都实现后那也算是一个"现代"的脚本语言了,后续我也会继续更新学习和实现过程中的有趣内容。 76 | 77 | 源码地址: 78 | [https://github.com/crossoverJie/gscript](https://github.com/crossoverJie/gscript) -------------------------------------------------------------------------------- /gscript/gscript06-operator-overloading.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 手写编程语言-实现运算符重载 3 | date: 2022/09/18 08:08:08 4 | categories: 5 | - gscript 6 | - compiler 7 | tags: 8 | - go 9 | - antlr 10 | - 运算符重载 11 | --- 12 | 13 | 14 | ![](https://tva1.sinaimg.cn/large/e6c9d24ely1h6at95ynkaj21900u07aw.jpg) 15 | 16 | # 前言 17 | 18 | 先带来日常的 [GScript](https://github.com/crossoverJie/gscript) 更新:新增了可变参数的特性,语法如下: 19 | 20 | ```js 21 | int add(string s, int ...num){ 22 | println(s); 23 | int sum = 0; 24 | for(int i=0;i 36 | 37 | 得益于可变参数,所以新增了格式化字符串的内置函数: 38 | 39 | ```js 40 | //formats according to a format specifier and writes to standard output. 41 | printf(string format, any ...a){} 42 | 43 | //formats according to a format specifier and returns the resulting string. 44 | string sprintf(string format, any ...a){} 45 | ``` 46 | 47 | 下面重点看看 [GScript](https://github.com/crossoverJie/gscript#operator-overloading) 所支持的运算符重载是如何实现的。 48 | # 使用 49 | 50 | 运算符重载其实也是多态的一种表现形式,我们可以重写运算符的重载函数,从而改变他们的计算规则。 51 | 52 | ```go 53 | println(100+2*2); 54 | ``` 55 | 以这段代码的运算符为例,输出的结果自然是:104. 56 | 57 | 但如果我们是对两个对象进行计算呢,举个例子: 58 | ```java 59 | class Person{ 60 | int age; 61 | Person(int a){ 62 | age = a; 63 | } 64 | } 65 | Person p1 = Person(10); 66 | Person p2 = Person(20); 67 | Person p3 = p1+p2; 68 | ``` 69 | 70 | 这样的写法在 `Java/Go` 中都会报编译错误,这是因为他们两者都不支持运算符重载; 71 | 72 | 但 `Python/C#` 是支持的,相比之下我觉得 `C#` 的实现方式更符合 `GScript` 语法,所以参考 C# 实现了以下的语法规则。 73 | 74 | ```js 75 | Person operator + (Person p1, Person p2){ 76 | Person pp = Person(p1.age+p2.age); 77 | return pp; 78 | } 79 | Person p3 = p1+p2; 80 | println("p3.age="+p3.age); 81 | assertEqual(p3.age, 30); 82 | ``` 83 | 84 | 有几个硬性条件: 85 | 86 | - 函数名必须是 `operator` 87 | - 名称后跟上运算符即可。 88 | 89 | > 目前支持的运算符有:+-*/ == != < <= > >= 90 | 91 | 92 | # 实现 93 | 94 | 以前在使用 `Python` 运算符重载时就有想过它是如何实现的?但没有深究,这次借着自己实现相关功能从而需要深入理解。 95 | 96 | 其中重点就为两步: 97 | 98 | 1. 编译期间:记录所有的重载函数和运算符的关系。 99 | 2. 运行期:根据当前的运算找到声明的函数,直接运行即可。 100 | 101 | 102 | 103 | 第一步的重点是扫描所有的重载函数,将重载函数与运算符存放起来,需要关注的是函数的返回值与运算符类型。 104 | 105 | ```go 106 | // OpOverload 重载符 107 | type OpOverload struct { 108 | function *Func 109 | tokenType int 110 | } 111 | 112 | // 运算符重载自定义函数 113 | opOverloads []*symbol.OpOverload 114 | ``` 115 | 116 | 在编译器中使用一个切片存放。 117 | 118 | 而在运行期中当两个入参类型相同时,则需要查找重载函数。 119 | ![](https://tva1.sinaimg.cn/large/e6c9d24ely1h6b310mefaj21ky05u3zz.jpg) 120 | 121 | ```go 122 | // GetOpFunction 获取运算符重载函数 123 | // 通过返回值以及运算符号(+-*/) 匹配重载函数 124 | func (a *AnnotatedTree) GetOpFunction(returnType symbol.Type, tokenType int) *symbol.Func { 125 | for _, overload := range a.opOverloads { 126 | isType := overload.GetFunc().GetReturnType().IsType(returnType) 127 | if isType && overload.GetTokenType() == tokenType { 128 | return overload.GetFunc() 129 | } 130 | } 131 | return nil 132 | } 133 | ``` 134 | 135 | 查找方式就是通过编译期存放的数据进行匹配,拿到重载函数后自动调用便实现了重载。 136 | 137 | 感兴趣的朋友可以查看相关代码: 138 | 139 | - 编译期:[https://github.com/crossoverJie/gscript/blob/ae729ce7d4cf39fe115121993fcd2222716755e5/resolver/type_scope_resolver.go#L127](https://github.com/crossoverJie/gscript/blob/ae729ce7d4cf39fe115121993fcd2222716755e5/resolver/type_scope_resolver.go#L127) 140 | 141 | - 运行期:[https://github.com/crossoverJie/gscript/blob/499236af549be47ff827c6d55de1fc8e5600b9b3/visitor.go#L387](https://github.com/crossoverJie/gscript/blob/499236af549be47ff827c6d55de1fc8e5600b9b3/visitor.go#L387) 142 | 143 | # 总结 144 | 145 | 146 | 运算符重载其实并不是一个常用的功能;因为会改变运算符的语义,比如明明是加法却在重载函数中写为减法。 147 | 148 | 这会使得代码阅读起来困难,但在某些情况下我们又非常希望语言本身能支持运算符重载。 149 | 150 | 比如在 Go 中常用的一个第三方精度库`decimal.Decimal`,进行运算时只能使用 `d1.Add(d2)` 这样的函数,当运算复杂时: 151 | 152 | ```go 153 | a5 = (a1.Add(a2).Add(a3)).Mul(a4); 154 | ``` 155 | ```go 156 | a5 = (a1+a2+a3)*a4; 157 | ``` 158 | 159 | 就不如下面这种直观,所以有利有弊吧,多一个选项总不是坏事。 160 | 161 | GScript 源码: 162 | [https://github.com/crossoverJie/gscript](https://github.com/crossoverJie/gscript) -------------------------------------------------------------------------------- /https.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 科普-为自己的博客免费加上小绿锁 3 | date: 2017/05/07 01:01:54 4 | categories: 5 | - 科普 6 | tags: 7 | - HTTP 8 | - HTTPS 9 | --- 10 | 11 | 12 | ![https.jpg](https://ooo.0o0.ooo/2017/05/07/590edced5545e.jpg) 13 | 14 | 在如今的`HTTPS`大当其道的情况下自己的博客要是还没有用上。作为互联网的螺丝钉(`码农`)岂不是很没面子。 15 | 16 | # 使用CLOUDFLARE 17 | 这里使用[CLOUDFLARE](http://www.CLOUDFLARE.com)来提供`HTTPS`服务。 18 | 19 | - 在其官网进行注册,按照提示添加好自己的域名即可。 20 | - 之后需要在自己域名的提供商处修改`DNS服务器`,我是在万网购买的修改后如下图: 21 | ![1.jpg](https://ooo.0o0.ooo/2017/05/07/590edd1a4cfd0.jpg) 22 | 其中的`DNS服务器地址`由`CLOUDFLARE`是提供的。 23 | 修改完成之后通常需要等待一段时间才能生效。 24 | - 接着在`CLOUDFLARE`配置`DNS`解析: 25 | ![DNS解析.jpg](https://ooo.0o0.ooo/2017/05/07/590edd4913c2b.jpg) 26 | 点击`CLOUDFLARE`顶部的`DNS`进行如我上图中的配置,和之前的配置没有什么区别。 27 | 28 | 等待一段时间之后发现使用`HTTP`,`HTTPS`都能访问,但是最好还是能在访问`HTTP`的时候能强制跳转到`HTTPS`. 29 | 30 | - 在`CLOUDFLARE`菜单栏点击`page-rules`之后新建一个`page rule`: 31 | ![强制https.jpg](https://ooo.0o0.ooo/2017/05/07/590edd73a9f9d.jpg) 32 | 这样整个网站的请求都会强制到请求到`HTTPS`. 33 | 34 | 35 | 36 | # 主题配置 37 | 由于我才用的是`Hexo`中的`Next`主题,其中配置了`CNZZ`站长统计。其中配置的`CNZZ`统计JS是才用的`HTTP`。导致在首页的时候`chrome`一直提示感叹号。 38 | 修改站点`themes/next/layout/_scripts/third-party/analytics`目录下的`cnzz-analytics.swig`文件 39 | ``` 40 | {% if theme.cnzz_siteid %} 41 | 42 |
43 | 44 |
45 | 46 | {% endif %} 47 | ``` 48 | 之后再进行构建的时候就会使用`HTTPS`. 49 | 50 | > 值得注意一点的是之后文章中所使用的图片都要用`HTTPS`的地址了,不然`chrome`会提示感叹号。 51 | 52 | 53 | > 个人博客地址:[http://crossoverjie.top](http://crossoverjie.top)。 54 | 55 | > GitHub地址:[https://github.com/crossoverJie](https://github.com/crossoverJie)。 56 | 57 | 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /java-senior/OOM-Disruptor.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 强如 Disruptor 也发生内存溢出? 3 | date: 2018/08/29 19:10:36 4 | categories: 5 | - Java 进阶 6 | tags: 7 | - Java 8 | - Thread 9 | - concurrent 10 | - JVM 11 | - OOM 12 | --- 13 | 14 | ![](https://i.loli.net/2019/05/08/5cd1d2226b558.jpg) 15 | 16 | # 前言 17 | 18 | `OutOfMemoryError` 问题相信很多朋友都遇到过,相对于常见的业务异常(数组越界、空指针等)来说这类问题是很难定位和解决的。 19 | 20 | 本文以最近碰到的一次线上内存溢出的定位、解决问题的方式展开;希望能对碰到类似问题的同学带来思路和帮助。 21 | 22 | 主要从`表现-->排查-->定位-->解决` 四个步骤来分析和解决问题。 23 | 24 | 25 | 26 | # 表象 27 | 28 | 最近我们生产上的一个应用不断的爆出内存溢出,并且随着业务量的增长出现的频次越来越高。 29 | 30 | 该程序的业务逻辑非常简单,就是从 Kafka 中将数据消费下来然后批量的做持久化操作。 31 | 32 | 而现象则是随着 Kafka 的消息越多,出现的异常的频次就越快。由于当时还有其他工作所以只能让运维做重启,并且监控好堆内存以及 GC 情况。 33 | 34 | > 重启大法虽好,可是依然不能根本解决问题。 35 | 36 | # 排查 37 | 38 | 于是我们想根据运维之前收集到的内存数据、GC 日志尝试判断哪里出现问题。 39 | 40 | ![](https://i.loli.net/2019/05/08/5cd1d229ad931.jpg) 41 | 42 | 结果发现老年代的内存使用就算是发生 GC 也一直居高不下,而且随着时间推移也越来越高。 43 | 44 | 结合 jstat 的日志发现就算是发生了 FGC 老年代也已经回收不了,内存已经到顶。 45 | 46 | ![](https://i.loli.net/2019/05/08/5cd1d22b93e86.jpg) 47 | 48 | 甚至有几台应用 FGC 达到了上百次,时间也高的可怕。 49 | 50 | 这说明应用的内存使用肯定是有问题的,有许多赖皮对象始终回收不掉。 51 | 52 | # 定位 53 | 54 | 由于生产上的内存 dump 文件非常大,达到了几十G。也是由于我们的内存设置太大有关。 55 | 56 | 所以导致想使用 MAT 分析需要花费大量时间。 57 | 58 | 因此我们便想是否可以在本地复现,这样就要好定位的多。 59 | 60 | 为了尽快的复现问题,我将本地应用最大堆内存设置为 150M。 61 | 62 | 63 | 然后在消费 Kafka 那里 Mock 为一个 while 循环一直不断的生成数据。 64 | 65 | 同时当应用启动之后利用 VisualVM 连上应用实时监控内存、GC 的使用情况。 66 | 67 | 结果跑了 10 几分钟内存使用并没有什么问题。根据图中可以看出,每产生一次 GC 内存都能有效的回收,所以这样并没有复现问题。 68 | 69 | ![](https://i.loli.net/2019/05/08/5cd1d22c8db27.jpg) 70 | 71 | 72 | 没法复现问题就很难定位了。于是我们 review 代码,发现生产的逻辑和我们用 while 循环 Mock 数据还不太一样。 73 | 74 | 查看生产的日志发现每次从 Kafka 中取出的都是几百条数据,而我们 Mock 时每次只能产生**一条**。 75 | 76 | 为了尽可能的模拟生产情况便在服务器上跑着一个生产者程序,一直源源不断的向 Kafka 中发送数据。 77 | 78 | 果然不出意外只跑了一分多钟内存就顶不住了,观察左图发现 GC 的频次非常高,但是内存的回收却是相形见拙。 79 | 80 | ![](https://i.loli.net/2019/05/08/5cd1d22d99ae5.jpg) 81 | 82 | 同时后台也开始打印内存溢出了,这样便复现出问题。 83 | 84 | # 解决 85 | 86 | 从目前的表现来看就是内存中有许多对象一直存在强引用关系导致得不到回收。 87 | 88 | 于是便想看看到底是什么对象占用了这么多的内存,利用 VisualVM 的 HeapDump 功能可以立即 dump 出当前应用的内存情况。 89 | 90 | ![](https://i.loli.net/2019/05/08/5cd1d234c531b.jpg) 91 | 92 | 结果发现 `com.lmax.disruptor.RingBuffer` 类型的对象占用了将近 50% 的内存。 93 | 94 | 看到这个包自然就想到了 `Disruptor` 环形队列。 95 | 96 | 再次 review 代码发现:从 Kafka 里取出的 700 条数据是直接往 Disruptor 里丢的。 97 | 98 | 这里也就能说明为什么第一次模拟数据没复现问题了。 99 | 100 | 模拟的时候是一个对象放进队列里,而生产的情况是 700 条数据放进队列里。这个数据量是 700 倍的差距。 101 | 102 | 而 Disruptor 作为一个环形队列,再对象没有被覆盖之前是一直存在的。 103 | 104 | 我也做了一个实验,证明确实如此。 105 | 106 | ![](https://i.loli.net/2019/05/08/5cd1d235d0fac.jpg) 107 | 108 | 我设置队列大小为 8 ,从 0~9 往里面写 10 条数据,当写到 8 的时候就会把之前 0 的位置覆盖掉,后面的以此类推(类似于 HashMap 的取模定位)。 109 | 110 | 所以在生产上假设我们的队列大小是 1024,那么随着系统的运行最终肯定会导致 1024 个位置上装满了对象,而且每个位置是 700 个! 111 | 112 | 于是查看了生产上 Disruptor 的 RingBuffer 配置,结果是:`1024*1024`。 113 | 114 | 这个数量级就非常吓人了。 115 | 116 | 为了验证是否是这个问题,我在本地将该值换为 2 ,一个最小值试试。 117 | 118 | 同样的 128M 内存,也是通过 Kafka 一直源源不断的取出数据。通过监控如下: 119 | 120 | ![](https://i.loli.net/2019/05/08/5cd1d2379a1ce.jpg) 121 | 122 | 跑了 20 几分钟系统一切正常,每当一次 GC 都能回收大部分内存,最终呈现锯齿状。 123 | 124 | 这样问题就找到了,不过生产上这个值具体设置多少还得根据业务情况测试才能知道,但原有的 1024*1024 是绝对不能再使用了。 125 | 126 | # 总结 127 | 128 | 虽然到了最后也就改了一行代码(还没改,直接修改配置),但这排查过程我觉得是有意义的。 129 | 130 | 也会让大部分觉得 JVM 这样的黑盒难以下手的同学有一个直观的感受。 131 | 132 | `同时也得感叹 Disruptor 东西虽好,也不能乱用哦!` 133 | 134 | 相关演示代码查看: 135 | 136 | [https://github.com/crossoverJie/JCSprout/tree/master/src/main/java/com/crossoverjie/disruptor](https://github.com/crossoverJie/JCSprout/tree/master/src/main/java/com/crossoverjie/disruptor) 137 | 138 | **你的点赞与转发是最大的支持。** 139 | 140 | -------------------------------------------------------------------------------- /java-senior/design-if-else.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 利用策略模式优化过多 if else 代码 3 | date: 2019/01/30 16:50:36 4 | categories: 5 | - Java 进阶 6 | - 设计模式 7 | tags: 8 | - Java 9 | - 策略模式 10 | --- 11 | 12 | ![](https://i.loli.net/2019/05/05/5ccef1ffd774f.jpg) 13 | 14 | # 前言 15 | 16 | 不出意外,这应该是年前最后一次分享,本次来一点实际开发中会用到的小技巧。 17 | 18 | 19 | 20 | 比如平时大家是否都会写类似这样的代码: 21 | 22 | ```java 23 | if(a){ 24 | //dosomething 25 | }else if(b){ 26 | //doshomething 27 | }else if(c){ 28 | //doshomething 29 | } else{ 30 | ////doshomething 31 | } 32 | ``` 33 | 34 | 条件少还好,一旦 `else if` 过多这里的逻辑将会比较混乱,并很容易出错。 35 | 36 | 比如这样: 37 | 38 | ![](https://i.loli.net/2019/05/05/5ccef63089d79.jpg) 39 | 40 | > 摘自 [cim](https://github.com/crossoverJie/cim) 中的一个客户端命令的判断条件。 41 | 42 | 43 | 刚开始条件较少,也就没管那么多直接写的;现在功能多了导致每次新增一个 `else` 条件我都得仔细核对,生怕影响之前的逻辑。 44 | 45 | 这次终于忍无可忍就把他重构了,重构之后这里的结构如下: 46 | 47 | ![](https://i.loli.net/2019/05/05/5ccef221d1575.jpg) 48 | 49 | 最后直接变为两行代码,简洁了许多。 50 | 51 | 而之前所有的实现逻辑都单独抽取到其他实现类中。 52 | 53 | ![](https://i.loli.net/2019/05/05/5ccef23ed1f7c.jpg) 54 | ![](https://i.loli.net/2019/05/05/5ccef256abc9b.jpg) 55 | 56 | 这样每当我需要新增一个 `else` 逻辑,只需要新增一个类实现同一个接口便可完成。每个处理逻辑都互相独立互不干扰。 57 | 58 | 59 | # 实现 60 | 61 | ![](https://i.loli.net/2019/05/05/5ccef2723156b.jpg) 62 | 63 | 按照目前的实现画了一个草图。 64 | 65 | 整体思路如下: 66 | - 定义一个 `InnerCommand` 接口,其中有一个 `process` 函数交给具体的业务实现。 67 | - 根据自己的业务,会有多个类实现 `InnerCommand` 接口;这些实现类都会注册到 `Spring Bean` 容器中供之后使用。 68 | - 通过客户端输入命令,从 `Spring Bean` 容器中获取一个 `InnerCommand` 实例。 69 | - 执行最终的 `process` 函数。 70 | 71 | 72 | 主要想实现的目的就是不在有多个判断条件,只需要根据当前客户端的状态动态的获取 `InnerCommand` 实例。 73 | 74 | 从源码上来看最主要的就是 `InnerCommandContext` 类,他会根据当前客户端命令动态获取 `InnerCommand` 实例。 75 | 76 | ![](https://i.loli.net/2019/05/05/5ccef28c2eedc.jpg) 77 | 78 | - 第一步是获取所有的 `InnerCommand` 实例列表。 79 | - 根据客户端输入的命令从第一步的实例列表中获取类类型。 80 | - 根据类类型从 `Spring` 容器中获取具体实例对象。 81 | 82 | 83 | 因此首先第一步需要维护各个命令所对应的类类型。 84 | 85 | ![](https://i.loli.net/2019/05/05/5ccef2b72abb8.jpg) 86 | 87 | 所以在之前的枚举中就维护了命令和类类型的关系,只需要知道命令就能知道他的类类型。 88 | 89 | 90 | 这样才能满足只需要两行代码就能替换以前复杂的 `if else`,同时也能灵活扩展。 91 | 92 | ```java 93 | InnerCommand instance = innerCommandContext.getInstance(msg); 94 | instance.process(msg) ; 95 | ``` 96 | 97 | # 总结 98 | 99 | 当然还可以做的更灵活一些,比如都不需要显式的维护命令和类类型的对应关系。 100 | 101 | 只需要在应用启动时扫描所有实现了 `InnerCommand` 接口的类即可,在 [cicada](https://github.com/TogetherOS/cicada) 中有类似实现,感兴趣的可以自行[查看](https://github.com/TogetherOS/cicada)。 102 | 103 | 这样一些小技巧希望对你有所帮助。 104 | 105 | 106 | 以上所有源码可以在这里查看: 107 | 108 | [https://github.com/crossoverJie/cim](https://github.com/crossoverJie/cim) 109 | 110 | 111 | **你的点赞与分享是对我最大的支持** 112 | -------------------------------------------------------------------------------- /java-thread2.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: java多线程(二)有返回值的多线程 3 | date: 2016/5/27 17:39:16 4 | categories: 5 | - java多线程 6 | tags: 7 | - Java 8 | - Callable 9 | - ExecutorService 10 | - Future 11 | - Executors 12 | --- 13 | # 前言 14 | 15 | 之前我们使用多线程要么是继承`Thread`类,要么是实现`Runnable`接口,然后重写一下`run()`方法即可。 16 | 但是只有的话如果有死锁、对共享资源的访问和随时监控线程状态就不行了,于是在Java5之后就有了Callable接口。 17 | 18 | ---------- 19 | 20 | # 简单的实现有返回值的线程 21 | 代码如下: 22 | `CallableFuture`类 23 | ```java 24 | package top.crosssoverjie.study.Thread; 25 | 26 | import java.util.concurrent.ExecutionException; 27 | import java.util.concurrent.ExecutorService; 28 | import java.util.concurrent.Executors; 29 | import java.util.concurrent.Future; 30 | 31 | public class CallableFuture { 32 | public static void main(String[] args) { 33 | //创建一个线程池 34 | ExecutorService pool = Executors.newFixedThreadPool(3) ; 35 | 36 | //创建三个有返回值的任务 37 | CallableTest2 c1 = new CallableTest2("线程1") ; 38 | CallableTest2 c2 = new CallableTest2("线程2") ; 39 | CallableTest2 c3 = new CallableTest2("线程3") ; 40 | 41 | Future f1 = pool.submit(c1) ; 42 | Future f2 = pool.submit(c2) ; 43 | Future f3 = pool.submit(c3) ; 44 | 45 | try { 46 | System.out.println(f1.get().toString()); 47 | System.out.println(f2.get().toString()); 48 | System.out.println(f3.get().toString()); 49 | } catch (InterruptedException e) { 50 | e.printStackTrace(); 51 | } catch (ExecutionException e) { 52 | e.printStackTrace(); 53 | }finally{ 54 | pool.shutdown(); 55 | } 56 | 57 | } 58 | } 59 | ``` 60 | 61 | ·CallableTest2·类: 62 | ```java 63 | package top.crosssoverjie.study.Thread; 64 | 65 | import java.util.concurrent.Callable; 66 | 67 | public class CallableTest2 implements Callable { 68 | private String name ; 69 | 70 | public CallableTest2(String name) { 71 | this.name = name; 72 | } 73 | 74 | @Override 75 | public Object call() throws Exception { 76 | return name+"返回了东西"; 77 | } 78 | 79 | 80 | } 81 | ``` 82 | 运行结果: 83 | ``` 84 | 线程1返回了东西 85 | 线程2返回了东西 86 | 线程3返回了东西 87 | ``` 88 | 89 | ---------- 90 | # 总结 91 | 以上就是一个简单的例子,需要了解更多详情可以去看那几个类的API。 92 | 93 | 94 | -------------------------------------------------------------------------------- /k8s/cloudnative-java.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 云原生背景下如何配置 JVM 内存 3 | date: 2023/05/15 08:08:08 4 | categories: 5 | - cloudnative 6 | tags: 7 | - K8s 8 | - Java 9 | - JVM 10 | --- 11 | 12 | 13 | ![image.png](https://s2.loli.net/2023/05/12/IAxSF3oZ1j8GHbi.png) 14 | 15 | # 背景 16 | 17 | 前段时间业务研发反馈说是他的应用内存使用率很高,导致频繁的重启,让我排查下是怎么回事; 18 | 19 | 在这之前我也没怎么在意过这个问题,正好这次排查分析的过程做一个记录。 20 | 21 | 22 | 23 | 首先我查看了监控面板里的 Pod 监控: 24 | ![WeChatWorkScreenshot_ac6f8d80-bdb4-469e-af1a-b2199c9ee288.png](https://s2.loli.net/2023/05/14/wyYu8SI7eGprqmQ.png) 25 | 26 | 发现确实是快满了,而此时去查看应用的 JVM 占用情况却只有30%左右;说明并不是应用内存满了导致 JVM 的 OOM,而是 Pod 的内存满了,导致 Pod 的内存溢出,从而被 k8s 杀掉了。 27 | 28 | 而 `k8s` 为了维持应用的副本数量就得重启一个 Pod,所以看起来就是应用运行一段时间后就被重启。 29 | 30 | --- 31 | 32 | ![WeChatWorkScreenshot_6213e2f8-c429-4d33-acdd-e639275dd92b.png](https://s2.loli.net/2023/05/14/Lhkjys1TEQUKV86.png) 33 | 而这个应用配置的是 JVM 8G,容器申请的内存是16G,所以 Pod 的内存占用看起来也就 50% 左右。 34 | 35 | # 容器的原理 36 | 37 | 在解决这个问题之前还是先简单了解下容器的运行原理,因为在 k8s 中所有的应用都是运行在容器中的,而容器本质上也是运行在宿主机上的一个个进程而已。 38 | 39 | 但我们使用 Docker 的时候会感觉每个容器启动的应用之间互不干扰,从文件系统、网络、CPU、内存这些都能完全隔离开来,就像两个运行在不同的服务器中的应用。 40 | 41 | 其实这一点也不是啥黑科技,Linux 早就支持 2.6.x 的版本就已经支持 `namespace` 隔离了,使用 `namespace` 可以将两个进程完全隔离。 42 | 43 | 仅仅将资源隔离还不够,还需要限制对资源的使用,比如 CPU、内存、磁盘、带宽这些也得做限制;这点也可以使用 `cgroups` 进行配置。 44 | 45 | 它可以限制某个进程的资源,比如宿主机是 4 核 CPU,8G 内存,为了保护其他容器,必须给这个容器配置使用上限:1核 CPU,2G内存。 46 | 47 | ![image.png](https://s2.loli.net/2023/05/14/dzcHK6G8VZQuFC5.png) 48 | 49 | 这张图就很清晰的表示了 `namespace` 和 `cgroups` 在容器技术中的作用,简单来说就是: 50 | 51 | - namespace 负责隔离 52 | - cgroups 负责限制 53 | 54 | 在 k8s 中也有对应的提现: 55 | 56 | ```yaml 57 | resources: 58 | requests: 59 | memory: 1024Mi 60 | cpu: 0.1 61 | limits: 62 | memory: 1024Mi 63 | cpu: 4 64 | ``` 65 | 66 | 这个资源清单表示该应用至少需要为一个容器分配一个 0.1 核和 1024M 的资源,CPU 的最高上限为 4 个核心。 67 | 68 | # 不同的OOM 69 | 70 | 回到本次的问题,可以确认是容器发生了 OOM 从而导致被 k8s 重启,这也是我们配置 limits 的作用。 71 | 72 | > k8s 内存溢出导致容器退出会出现 exit code 137 的一个 event 日志。 73 | 74 | 75 | 因为该应用的 JVM 内存配置和容器的配置大小是一样的,都是8GB,但 Java 应用还有一些非 JVM 管理的内存,比如堆外内存之类的,这样很容易就导致容器内存大小超过了限制的 8G 了,也就导致了容器内存溢出。 76 | 77 | # 云原生背景的优化 78 | 79 | 因为这个应用本身使用的内存不多,所以建议将堆内存限制到 4GB,这样就避免了容器内存超限,从而解决了问题。 80 | 81 | 当然之后我们也会在应用配置栏里加上建议:推荐 JVM 的配置小于容器限制的 2/3,预留一些内存。 82 | 83 | 其实本质上还是开发模式没有转变过来,以传统的 Java 应用开发模式甚至都不会去了解容器的内存大小,因为以前大家的应用都是部署在一个内存较大的虚拟机上,所以感知不到容器内存的限制。 84 | 85 | 从而误以为将两者画了等号,这一点可能在 Java 应用中尤为明显,毕竟多了一个 JVM;甚至在老版本的 JDK 中如果没有设置堆内存大小,无法感知到容器的内存限制,从而自动生成的 Xmx 大于了容器的内存大小,以致于 OOM。 86 | -------------------------------------------------------------------------------- /k8s/grafana-variable.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Grafana 变量转义处理 3 | date: 2023/06/26 08:08:08 4 | categories: 5 | - cloudnative 6 | tags: 7 | - Grafana 8 | --- 9 | 10 | Grafana 是一款强大的可视化工具,不止是用于 Prometheus 做数据源,还可以集成数据库、日志等作为数据源整体使用。 11 | 12 | 最近我在配置一个监控面板,其中的数据由 Prometheus 和 MySQL 组成;简单来说就是一个指标的查询条件是从数据库中来的。 13 | 14 | 15 | 16 | ``` 17 | pulsar_subscription_back_log_no_delayed{topic=~"$topic",subscription=~"$subscription"} 18 | ``` 19 | 20 | 其中的 topic 数据是从 MySQL 中来的,其实就是在 Grafana 声明一个变量,从数据库返回了一个列表。 21 | 22 | ![](https://s2.loli.net/2023/06/25/OE37acurFIQjVNH.png) 23 | 24 | 因为我们的查询条件是 `topic=~"$topic"`是正则匹配,所以理论上应该把所有的 `topic` 关联的数据都查询出来。 25 | 26 | ![](https://s2.loli.net/2023/06/25/WMetKBAvg24hzZk.png) 27 | 28 | 但实际情况是任何数据都查不到。 29 | 30 | 查看发出去的原始请求后才发现问题出在哪里: 31 | 32 | ![](https://s2.loli.net/2023/06/25/AUXs9lnHoYMQjhO.png) 33 | 34 | 原来是选择所有 topic 后 grafana 会~~~~自动对参数转义,这个我查了好多资料包括咨询 ChatGPT 都没有得到解决。 35 | 36 | 经过多次测试,发现只要开启多选 grafana 就会自动转义。 37 | ![](https://s2.loli.net/2023/06/25/ao51AysPEeiTQNr.png) 38 | 39 | 最后我只能想到一个不需要生成多行记录的办法:将所有数据合并成一条记录。 40 | 41 | ![](https://s2.loli.net/2023/06/25/o7Xaf3NKD1rystn.png) 42 | 43 | 这样的话就只会生成一条数据,其中包含了所有的 topic,也就避免了被转义。 44 | 45 | > SQL 中的 CONCAT 函数其实我也不知道怎么使用,还是 ChatGPT 告诉我的。 46 | 47 | ![](https://s2.loli.net/2023/06/25/InPYWyiqAL1xRfK.png) 48 | 49 | 最后便能完美的查询出数据了。 50 | 51 | 有碰到类似问题的朋友可以尝试这个方法,我估计用到这个场景的并不多,不然 ChatGPT 也不会不知道。 52 | 53 | -------------------------------------------------------------------------------- /newObject.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 对象的创建与内存分配 3 | date: 2018/01/18 02:01:36 4 | categories: 5 | - Java 进阶 6 | --- 7 | 8 | ![](https://i.loli.net/2019/05/08/5cd1ba311fbe7.jpg) 9 | 10 | ## 创建对象 11 | 12 | 当 `JVM` 收到一个 `new` 指令时,会检查指令中的参数在常量池是否有这个符号的引用,还会检查该类是否已经被[加载](https://github.com/crossoverJie/Java-Interview/blob/master/MD/ClassLoad.md)过了,如果没有的话则要进行一次类加载。 13 | 14 | 接着就是分配内存了,通常有两种方式: 15 | 16 | - 指针碰撞 17 | - 空闲列表 18 | 19 | 使用指针碰撞的前提是堆内存是**完全工整**的,用过的内存和没用的内存各在一边每次分配的时候只需要将指针向空闲内存一方移动一段和内存大小相等区域即可。 20 | 21 | 当堆中已经使用的内存和未使用的内存**互相交错**时,指针碰撞的方式就行不通了,这时就需要采用空闲列表的方式。虚拟机会维护一个空闲的列表,用于记录哪些内存是可以进行分配的,分配时直接从可用内存中直接分配即可。 22 | 23 | 堆中的内存是否工整是有**垃圾收集器**来决定的,如果带有压缩功能的垃圾收集器就是采用指针碰撞的方式来进行内存分配的。 24 | 25 | 26 | 27 | 分配内存时也会出现并发问题: 28 | 29 | 这样可以在创建对象的时候使用 `CAS` 这样的乐观锁来保证。 30 | 31 | 也可以将内存分配安排在每个线程独有的空间进行,每个线程首先在堆内存中分配一小块内存,称为本地分配缓存(`TLAB : Thread Local Allocation Buffer`)。 32 | 33 | 分配内存时,只需要在自己的分配缓存中分配即可,由于这个内存区域是线程私有的,所以不会出现并发问题。 34 | 35 | 可以使用 `-XX:+/-UseTLAB` 参数来设定 JVM 是否开启 `TLAB` 。 36 | 37 | 内存分配之后需要对该对象进行设置,如对象头。对象头的一些应用可以查看 [Synchronize 关键字原理](https://github.com/crossoverJie/Java-Interview/blob/master/MD/Synchronize.md)。 38 | 39 | ### 对象访问 40 | 41 | 一个对象被创建之后自然是为了使用,在 Java 中是通过栈来引用堆内存中的对象来进行操作的。 42 | 43 | 对于我们常用的 `HotSpot` 虚拟机来说,这样引用关系是通过直接指针来关联的。 44 | 45 | 如图: 46 | 47 | ![](https://i.loli.net/2019/05/08/5cd1ba333efb1.jpg) 48 | 49 | 这样的好处就是:在 Java 里进行频繁的对象访问可以提升访问速度(相对于使用句柄池来说)。 50 | 51 | ## 内存分配 52 | 53 | 54 | ### Eden 区分配 55 | 简单的来说对象都是在堆内存中分配的,往细一点看则是优先在 `Eden` 区分配。 56 | 57 | 这里就涉及到堆内存的划分了,为了方便垃圾回收,JVM 将对内存分为新生代和老年代。 58 | 59 | 而新生代中又会划分为 `Eden` 区,`from Survivor、to Survivor` 区。 60 | 61 | 其中 `Eden` 和 `Survivor` 区的比例默认是 `8:1:1`,当然也支持参数调整 `-XX:SurvivorRatio=8`。 62 | 63 | 当在 `Eden` 区分配内存不足时,则会发生 `minorGC` ,由于 `Java` 对象多数是**朝生夕灭**的特性,所以 `minorGC` 通常会比较频繁,效率也比较高。 64 | 65 | 当发生 `minorGC` 时,JVM 会根据[复制算法](https://github.com/crossoverJie/Java-Interview/blob/145064ecf867e898ad025f3467b7ada9086fc8dd/MD/GarbageCollection.md#%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6%E7%AE%97%E6%B3%95)将存活的对象拷贝到另一个未使用的 `Survivor` 区,如果 `Survivor` 区内存不足时,则会使用分配担保策略将对象移动到老年代中。 66 | 67 | 谈到 `minorGC` 时,就不得不提到 `fullGC(majorGC)` ,这是指发生在老年代的 `GC` ,不论是效率还是速度都比 `minorGC ` 慢的多,回收时还会发生 `stop the world` 使程序发生停顿,所以应当尽量避免发生 `fullGC` 。 68 | 69 | ### 老年代分配 70 | 71 | 也有一些情况会导致对象直接在老年代分配,比如当分配一个大对象时(大的数组,很长的字符串),由于 `Eden` 区没有足够大的连续空间来分配时,会导致提前触发一次 `GC`,所以尽量别频繁的创建大对象。 72 | 73 | 因此 `JVM` 会根据一个阈值来判断大于该阈值对象直接分配到老年代,这样可以避免在新生代频繁的发生 `GC`。 74 | 75 | 76 | 对于一些在新生代的老对象 `JVM` 也会根据某种机制移动到老年代中。 77 | 78 | JVM 是根据记录对象年龄的方式来判断该对象是否应该移动到老年代,根据新生代的复制算法,当一个对象被移动到 `Survivor` 区之后 JVM 就给该对象的年龄记为1,每当熬过一次 `minorGC` 后对象的年龄就 +1 ,直到达到阈值(默认为15)就移动到老年代中。 79 | 80 | > 可以使用 `-XX:MaxTenuringThreshold=15` 来配置这个阈值。 81 | 82 | 83 | ## 总结 84 | 85 | 虽说这些内容略显枯燥,但当应用发生不正常的 `GC` 时,可以方便更快的定位问题。 86 | 87 | ## 号外 88 | 最近在总结一些 Java 相关的知识点,感兴趣的朋友可以一起维护。 89 | 90 | > 地址: [https://github.com/crossoverJie/Java-Interview](https://github.com/crossoverJie/Java-Interview) 91 | -------------------------------------------------------------------------------- /normal-skill1.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 日常记录(一)MySQL被锁解决方案 3 | date: 2016/6/5 0:04:56 4 | categories: 5 | - 日常记录 6 | tags: 7 | - MySQL 8 | --- 9 | # 前言 10 | > 由于前段时间为了让部署在Linux中的项目访问另一台服务器的MySQL,经过各种折腾就把`root`用户给弄出问题了,导致死活登不上`PS:Linux中的项目还是没有连上。。`(这是后话了。)。经过各种查阅资料终于找到解决方法了。 11 | 12 | 报错如下: 13 | `Access denied for user 'root'@'localhost' (using password:YES)` 14 | 15 | ---------- 16 | 17 | # 关闭MySQL服务,修改MySQL初始文件 18 | 打开MySQL目录下的`my-default.ini`文件,如图: 19 | ![](http://i.imgur.com/eUDlxik.png) 20 | 在最后一行加入`skip-grant-tables`之后保存。 21 | 然后重启MySQL服务。 22 | 23 | 24 | ---------- 25 | # 用命令行登录MySQL修改ROOT账号密码 26 | 用命令行登录MySQL输入`mysql -uroot -p`,不用输入密码,直接敲回车即可进入。如下图: 27 | ![](http://i.imgur.com/pENPn4Y.png) 28 | 之后执行以下语句修改ROOT用户密码: 29 | - `use mysql;` 30 | - `update user set password=PASSWORD("你的密码") where user='root';` 31 | 32 | # 还原`my-default.ini`文件 33 | 最后还原配置文件,之后重启MySQL服务即可正常登录了。 34 | ![](http://i.imgur.com/rZO0ghR.png) 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /normal-skill2.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 日常记录(二)SpringMvc导出Excel 3 | date: 2016/6/14 20:38:06 4 | categories: 5 | - 日常记录 6 | tags: 7 | - poi 8 | - Java 9 | --- 10 | # 前言 11 | > 相信很多朋友在实际工作中都会要将数据导出成Excel的需求,通常这样的做法有两种。 12 | > 一是采用JXL来生成Excel,之后保存到服务器,然后在生成页面之后下载该文件。 13 | > 二是使用POI来生成Excel,之后使用Stream的方式输出到前台直接下载`(ps:当然也可以生成到服务器中再下载。)`。这里我们讨论第二种。 14 | > *至于两种方式的优缺点请自行[百度](http://www.baidu.com)*。 15 | 16 | ---------- 17 | 18 | # Struts2的方式 19 | 通常我会将已经生成好的`HSSFWorkbook`放到一个`InputStream`中,然后再到xml配置文件中将返回结果更改为`stream`的方式。如下: 20 | ```java 21 | private void responseData(HSSFWorkbook wb) throws IOException { 22 | ByteArrayOutputStream baos = new ByteArrayOutputStream(); 23 | wb.write(baos); 24 | baos.flush(); 25 | byte[] aa = baos.toByteArray(); 26 | excelStream = new ByteArrayInputStream(aa, 0, aa.length); 27 | baos.close(); 28 | } 29 | ``` 30 | 31 | 配置文件: 32 | ```java 33 | 34 | 35 | excelStream 36 | application/vnd.ms-excel 37 | attachment;filename="Undefined.xls" 38 | 39 | 40 | ``` 41 | 这样即可达到点击链接即可直接下载文件的目的。 42 | 43 | ---------- 44 | # SpringMVC的方式 45 | 先贴代码: 46 | ```java 47 | @RequestMapping("/exportXxx.action") 48 | public void exportXxx(HttpServletRequest request, HttpServletResponse response, 49 | @RequestParam(value="scheduleId", defaultValue="0")int scheduleId){ 50 | HSSFWorkbook wb = createExcel(scheduleId) ; 51 | try { 52 | response.setHeader("Content-Disposition", "attachment; filename=appointmentUser.xls"); 53 | response.setContentType("application/vnd.ms-excel; charset=utf-8") ; 54 | OutputStream out = response.getOutputStream() ; 55 | wb.write(out) ; 56 | out.flush(); 57 | out.close(); 58 | } catch (IOException e) { 59 | e.printStackTrace(); 60 | } 61 | } 62 | ``` 63 | 其实springMVC和Struts2的原理上是一样的,只是Struts2是才去配置文件的方式。首先是使用`createExcel()`这个方法来生成Excel并返回,最后利用r`response`即可向前台输出Excel,这种方法是通用的,也可以试用与`Servlet、Struts2等`。我们只需要在`response`的头信息中设置相应的输出信息即可。 64 | 65 | 66 | ---------- 67 | # 总结 68 | 不管是使用`Struts2`,还是使用`SpringMVC`究其根本都是使用的`response`,所以只要我们把`response`理解透了不管是下载图片、world、Excel还是其他什么文件都是一样的。 -------------------------------------------------------------------------------- /normal-skill3.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 日常记录(三)更换Hexo主题 3 | date: 2016/6/18 0:05:24 4 | categories: 5 | - 日常记录 6 | tags: 7 | - Hexo 8 | --- 9 | # 前言 10 | > 由于博主的喜新厌旧,再经过一段时间对上一个主题的审美疲劳加上我专(zhuang)研(bi)的精神 11 | > 于是就想找一个B格较高的主题。经过一段时间的查找发现NexT这个主题简洁而不失华丽,低调而不失逼格(就不收广告费了)特别适合我,接着就着手开干。 12 | 13 | 14 | ---------- 15 | # 安装NexT主题 16 | ## 从Git上克隆主题 17 | 这里我就不介绍有关Hexo的东西了,默认是知道如何搭建Hexo博客的。还不太清楚的请自行百度。 18 | 首先将NexT主题先克隆到自己电脑上: 19 | - `cd your-hexo-site` 20 | - `git clone https://github.com/iissnan/hexo-theme-next themes/next`。 21 | ## 安装主题 22 | 接下来我们只需要将站点下的`_config.yml`配置文件中的主题配置更换成Next,如下图: 23 | ![](http://i.imgur.com/GhhnMDs.png) 24 | 其实这样主题就配好了,是不是很简单 25 | ![](http://i.imgur.com/wQmHabT.gif)。 26 | 27 | ---------- 28 | # NexT主题配置 29 | ## Hexo配置文件相关配置 30 | Next主题的个人头像是在Hexo配置文件中的。 31 | ![](http://i.imgur.com/Wranuhu.png) 32 | NexT同样也支持多说配置,我们只需要将你自己的多说账号也配置到Hexo的配置文件中即可。 33 | `duoshuo_shortname: your name` 34 | ## Next配置文件相关配置 35 | NexT主题非常吸引我的一点就是他支持打赏功能,这让我这种穷逼程序猿又看到了生路(多半也没人会给我打赏),以下一段配置即可在每篇博文下边开启打赏功能。 36 | ![](http://i.imgur.com/EvWY5v0.png) 37 | 微信也是可以的,但是我找了半天没有找到生成微信支付码的地方。 38 | 其他的一些配置我觉得都比较简单,看官方的帮助文档也是完全可以的,有问题的我们可以再讨论。 39 | 40 | 41 | ---------- 42 | # 一个绕坑指南 43 | 我在换完NexT之后发现在首页 44 | ![](http://i.imgur.com/JA451zP.png) 45 | 这里显示的分类和便签的统计都是对的,但是点进去之后就是空白的。我查看了Hexo和NexT的文档发现我写的没有任何问题,之后就懵逼了。。。![](http://i.imgur.com/dQxzrn6.gif)各位有碰到这个问题的可以往下看。 46 | ## 绕坑 47 | 之后我仔细的查阅了NexT的文档,发现他所使用的tags和categories文件夹下的`index.md`的格式是这样的: 48 | ``` 49 | --- 50 | title: tags 51 | date: 2016-06-16 02:13:06 52 | type: "tags" 53 | --- 54 | ``` 55 | 这和我之前使用的`JackMan`主题是完全不一样的(有关JackMan主题可以自行查阅)。 56 | 之后我讲categories文件下的`index.md`文件也换成这样的格式就没有问题了。如果你和我一样眼神不好的话建议配副眼镜。 57 | # 总结 58 | 其实以上的很多东西都是在[NexT官方文档](http://theme-next.iissnan.com/)里查得到的,接下来我会尝试提一点`pull request`来更加深入的了解Hexo。 -------------------------------------------------------------------------------- /ob/Bookkeeper-storage.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 白话 Pulsar Bookkeeper 的存储模型 3 | date: 2024/01/15 19:20:59 4 | categories: 5 | - OB 6 | tags: 7 | - Pulsar 8 | - Bookkeeper 9 | --- 10 | 11 | 12 | ![](https://s2.loli.net/2024/01/15/xU7FZCPjckRMBAD.png) 13 | 14 | 15 | 最近我们的 Pulsar 存储有很长一段时间数据一直得不到回收,但消息确实已经是 ACK 了,理论上应该是会被回收的,随着时间流逝不但没回收还一直再涨,最后在没找到原因的情况下就只有一直不停的扩容。 16 | 17 | > 最后磁盘是得到了回收,过程先不表,之后再讨论。 18 | 19 | 为了防止类似的问题再次发生,我们希望可以监控到磁盘维度,能够列出各个日志文件的大小以及创建时间。 20 | 21 | 这时就需要对 `Pulsar` 的存储模型有一定的了解,也就有了这篇文章。 22 | 23 | 24 | 25 | ![image.png](https://s2.loli.net/2024/01/15/EjpuQKJN5MVIW8r.png) 26 | 讲到 Pulsar 的存储模型,本质上就是 Bookkeeper 的存储模型。 27 | 28 | Pulsar 所有的消息读写都是通过 Bookkeeper 实现的。 29 | 30 | > `Bookkeeper` 是一个可扩展、可容错、低延迟的日志存储数据库,基于 Append Only 模型。(数据只能追加不能修改) 31 | 32 | 33 | ![image.png](https://s2.loli.net/2024/01/15/tMITYocL89ZapVG.png) 34 | 35 | 36 | 这里我利用 Pulsar 和 Bookkeeper 的 Admin API 列出了 Broker 和 BK 中 Ledger 分别占用的磁盘空间。 37 | 38 | > 关于这个如何获取和计算的,后续也准备提交给社区。 39 | 40 | # 背景 41 | 42 | 但和我们实际 `kubernetes` 中的磁盘占用量依然对不上,所以就想看看在 BK 中实际的存储日志和 `Ledger` 到底差在哪里。 43 | 44 | > 知道 Ledger 就可以通过 Ledger 的元数据中找到对应的 topic,从而判断哪些 topic 的数据导致统计不能匹配。 45 | 46 | Bookkeeper 有提提供一个Admin API 可以返回当前 BK 所使用了哪些日志文件的接口: 47 | [https://bookkeeper.apache.org/docs/admin/http#endpoint-apiv1bookielist_disk_filefile_typetype](https://bookkeeper.apache.org/docs/admin/http#endpoint-apiv1bookielist_disk_filefile_typetype) 48 | 49 | ![](https://s2.loli.net/2024/01/15/ZOMcNSG53Ttz4eo.png) 50 | 51 | 从返回的结果可以看出,落到具体的磁盘上只有一个文件名称,是无法知道具体和哪些 Ledger 进行关联的,也就无法知道具体的 topic 了。 52 | 53 | 此时只能大胆假设,应该每个文件和具体的消息 ID 有一个映射关系,也就是索引。 54 | 所以需要搞清楚这个索引是如何运行的。 55 | 56 | 57 | # 存储模型 58 | 59 | ![](https://s2.loli.net/2024/01/15/uvPC1Y8kdOJ3m5B.png) 60 | 61 | 我查阅了一些网上的文章和源码大概梳理了一个存储流程: 62 | 63 | 1. BK 收到写入请求,数据会异步写入到 `Journal`/`Entrylog` 64 | 2. Journal 直接顺序写入,并且会快速清除已经写入的数据,所以需要的磁盘空间不多(所以从监控中其实可以看到 Journal 的磁盘占有率是很低的)。 65 | 3. 考虑到会随机读消息,EntryLog 在写入前进行排序,保证落盘的数据中同一个 Ledger 的数据尽量挨在一起,充分利用 PageCache. 66 | 4. 最终数据的索引通过 `LedgerId+EntryId` 生成索引信息存放到 `RockDB` 中(`Pulsar` 的场景使用的是 `DbLedgerStorage` 实现)。 67 | 5. 读取数据时先从获取索引,然后再从磁盘读取数据。 68 | 6. 利用 `Journal` 和 `EntryLog` 实现消息的读写分离。 69 | 70 | 71 | 简单来说 BK 在存储数据的时候会进行双写,`Journal` 目录用于存放写的数据,对消息顺序没有要求,写完后就可以清除了。 72 | 73 | 而 `Entry` 目录主要用于后续消费消息进行读取使用,大部分场景都是顺序读,毕竟我们消费消息的时候很少会回溯,所以需要充分利用磁盘的 PageCache,将顺序的消息尽量的存储在一起。 74 | 75 | > 同一个日志文件中可能会存放多个 Ledger 的消息,这些数据如果不排序直接写入就会导致乱序,而消费时大概率是顺序的,但具体到磁盘的表现就是随机读了,这样读取效率较低。 76 | 77 | 78 | 所以我们使用 `Helm` 部署 `Bookkeeper` 的时候需要分别指定 `journal` 和 `ledgers` 的目录 79 | 80 | ```yaml 81 | volumes: 82 | # use a persistent volume or emptyDir 83 | persistence: true 84 | journal: 85 | name: journal 86 | size: 20Gi 87 | local_storage: false 88 | multiVolumes: 89 | - name: journal0 90 | size: 10Gi 91 | # storageClassName: existent-storage-class 92 | mountPath: /pulsar/data/bookkeeper/journal0 93 | - name: journal1 94 | size: 10Gi 95 | # storageClassName: existent-storage-class 96 | mountPath: /pulsar/data/bookkeeper/journal1 97 | ledgers: 98 | name: ledgers 99 | size: 50Gi 100 | local_storage: false 101 | storageClassName: sc 102 | # storageClass: 103 | # ... useMultiVolumes: false 104 | multiVolumes: 105 | - name: ledgers0 106 | size: 1000Gi 107 | # storageClassName: existent-storage-class 108 | mountPath: /pulsar/data/bookkeeper/ledgers0 109 | - name: ledgers1 110 | size: 1000Gi 111 | # storageClassName: existent-storage-class 112 | mountPath: /pulsar/data/bookkeeper/ledgers1 113 | ``` 114 | 115 | ---- 116 | 117 | ![](https://s2.loli.net/2024/01/15/nJeNDFbu2MIQp5a.png) 118 | 每次在写入和读取数据的时候都需要通过消息 ID 也就是 ledgerId 和 entryId 来获取索引信息。 119 | 120 | > 也印证了之前索引的猜测。 121 | 122 | 所以借助于 BK 读写分离的特性,我们还可以单独优化存储。 123 | 124 | 比如写入 `Journal` 的磁盘因为是顺序写入,所以即便是普通的 `HDD` 硬盘速度也很快。 125 | 126 | 大部分场景下都是读大于写,所以我们可以单独为 `Ledger` 分配高性能 SSD 磁盘,按需使用。 127 | 128 | > 因为在最底层的日志文件中无法直接通过 ledgerId 得知占用磁盘的大小,所以我们实际的磁盘占用率对不上的问题依然没有得到解决,这个问题我还会持续跟进,有新的进展再继续同步。 129 | 130 | #Blog #Pulsar -------------------------------------------------------------------------------- /ob/Build-ower-AI-robot.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 我用我的270篇文章做了一个数字 AI 替身 3 | date: 2024/09/23 21:54:01 4 | categories: 5 | - AI 6 | tags: 7 | - AI 8 | --- 9 | 10 | 23 年在 ChatGPT 刚出来的时候就在 [V 站](https://www.v2ex.com/t/931521)上看到有一个看到有大佬用自己的微信聊天记录和博客文章生成了一个 AI 替身: 11 | 12 | ![image.png](https://s2.loli.net/2024/09/23/7xPdy54cIEm6pnR.png) 13 | 14 | 15 | 当时就想着自己做一个,不过当时实现起来还比较复杂,直到如今 AI 已经越来越普及,想做一个自己的 AI 替身成本也非常低了。 16 | 17 | 于是就有了下图里的效果: 18 | ![](https://s2.loli.net/2024/09/23/vmJhURZKsg96yaY.png) 19 | ![](https://s2.loli.net/2024/09/23/tOzlgqvEMdm6LFo.png) 20 | 21 | 和自己的内容这么对话还挺有意思的,现在大家就可以直接在我公众号回复消息和”他“聊天。 22 | ![](https://s2.loli.net/2024/09/23/7CiykumvIdALDZe.jpg) 23 | 24 | 25 | > 也可以通过小程序来使用: 26 | ![](https://s2.loli.net/2024/09/23/9bdLkeP6XGorKAJ.png) 27 | 28 | 29 | ## 如何搭建 30 | 这里使用的数据源全都是我发布在公众号里的 260 篇文章。 31 | ![](https://s2.loli.net/2024/09/23/NWae9gRJbPHO6M8.png) 32 | 33 | 34 | 能够直接获取到微信公众号的数据一定是腾讯自己的产品,其实这个产品叫做:[腾讯元器](https://yuanqi.tencent.com/),是腾讯大模型团队基于混元大模型推出的智能创作工具。 35 | 36 | 37 | 我们可以自定义 prompt、数据源、插件来实现自己的 AI 机器人,或者类似的交互产品。 38 | 39 | 直接创建一个智能体,然后编写对应的提示词即可,使用起来非常简单,官方也提供了一些 `prompt` 的示例: 40 | ![image.png](https://s2.loli.net/2024/09/23/sOhwLG28i9qTcBz.png) 41 | 42 | 根据自己的需求来填写就可以了。 43 | 44 | 最主要的还是创建一个知识库,也就是你的数据源,好在这里直接整合了公众号的数据; 45 | ![](https://s2.loli.net/2024/09/23/sXALurKi5BRET23.png) 46 | 47 | 48 | 直接授权就可以使用,同时还可以每天定时更新,非常方便。 49 | 50 | ![](https://s2.loli.net/2024/09/23/TEJZ56MNCBV3aLh.png) 51 | 52 | 53 | 它会根据你的问题来判断是否用知识库的内容来回答,所以即便是问一些知识库不存在的内容也能拿到结果。 54 | 55 | --- 56 | 57 | ![](https://s2.loli.net/2024/09/23/AJbU4s1FXq7rSxO.png) 58 | 除此之外还可以上传你本地的文件,所以即便是你没有写公众号也可以上传自己整理的内容。 59 | 60 | 61 | 有兴趣的朋友可以试试尝尝鲜,后续我可以持续完善这个知识库,比如输入一些代码,之后再有向我咨询问题的朋友就可以先去问问”他“, 62 | 63 | 大家可以直接在公众号里和”对话“,说不定还有意外收获🐶。 -------------------------------------------------------------------------------- /ob/ChatGPT-hole.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 使用 ChatGPT 碰到的坑 3 | date: 2023/07/18 11:47:52 4 | categories: 5 | - ChatGPT 6 | tags: 7 | - Syslog 8 | - Go 9 | --- 10 | 11 | ![](https://s2.loli.net/2023/07/14/YtqXVJmfNokCwyE.png) 12 | 13 | 最近在使用 ChatGPT 的时候碰到一个小坑,因为某些特殊情况我需要使用 `syslog` 向 `logbeat` 中发送日志。 14 | 15 | 由于这是一个比较古老的协议,确实也没接触过,所以就想着让 ChatGPT 帮我生成个例子。 16 | 17 | 18 | 19 | 原本我已经在 Go 中将这个流程跑通,所以其实只需要将代码转换为 Java 就可以了,这个我还是很信任 `ChatGPT` 的; 20 | > 现在我挺多结构化数据的转换都交给了 ChatGPT,省去了不少小工具。 21 | 22 | 于是便有了这段对话: 23 | ![image.png](https://s2.loli.net/2023/07/17/6MHlRKOtZ2rJocd.png) 24 | ![image.png](https://s2.loli.net/2023/07/17/SzCGBuiN6AvR7Zo.png) 25 | 看起来挺正常的,我那过来改改确实也能用。 26 | 27 | 28 | --- 29 | 直到快上线的时候,我发现一些元信息丢失了,比如日志生产者的 `hostname, PID` 等,然而这个信息在 Go 却没有丢失。 30 | 31 | 于是我反复调试了之前生成的代码,依然没有找到问题。 32 | 33 | 没办法,就只有去翻翻 Go 源码,想看看最终发出去的数据长什么样子,最后看到这样几行代码: 34 | ![](https://s2.loli.net/2023/07/17/kJnoR4stKwYvCg8.png) 35 | ![image.png](https://s2.loli.net/2023/07/17/tOHvgx2ZzyrAEh9.png) 36 | 37 | 这样一看就很清晰了,只是按照 `<%d>%s %s %s[%d]: %s%s` 的格式将生成的字符串通过网络发送出去。 38 | 39 | 既然这样 Java 代码也很好写了: 40 | 41 | ```java 42 | Socket socket = new Socket(hostname,port); 43 | socket.setKeepAlive(true); 44 | OutputStream os = socket.getOutputStream(); 45 | PrintWriter pw = new PrintWriter(os, true); 46 | 47 | String format = String.format("<%d>%s %s %s[%d]: %s%s", 6 , rfc3164DateFormat.format(new Date()), "test", "test", 0, message, "\n"); 48 | 49 | pw.println(format); 50 | ``` 51 | 经过测试数据终于对了。 52 | 53 | 之后我就在想这么简单的一个问题 Google 上不可能没有吧,于是直接搜索了 `Java syslog` 关键字,结果直接就有一个现成的库。 54 | ![](https://s2.loli.net/2023/07/17/Fm6XBnOdxQ9PAKY.png) 55 | 56 | 57 | ![](https://s2.loli.net/2023/07/17/c7PCjmZnboReQtp.png) 58 | 59 | 而且实现也是类似的。 60 | 61 | 我相信应该有不少朋友也有被 ChatGPT 一本正经的胡说八道误导过,至少在当前的环境下一些简单的东西我还是决定优先 `Google`。 -------------------------------------------------------------------------------- /ob/Golang-interview-01.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Golang 基础面试题 01 3 | date: 2023/09/12 20:58:03 4 | categories: 5 | - Golang 6 | tags: 7 | - 面试 8 | --- 9 | ![Golang 面试题合集.png](https://s2.loli.net/2023/09/12/xJgnyReWs2mp7Pr.png) 10 | 11 | # 背景 12 | 13 | 在之前的文章中分享了 [k8s](https://crossoverjie.top/2023/08/17/ob/k8s-question-01/) 相关的面试题,本文我们重点来讨论和 k8s 密切相关的 Go 语言面试题。 14 | 15 | 这几年随着云原生的兴起,大部分后端开发者,特别是 Java 开发者都或多或少的想学习一些 Go 相关的技能,所以今天分享的内容比较初级,适合 Go 语言初学者。 16 | 17 | ![image.png](https://s2.loli.net/2023/09/12/oheqNwJt3KvsgDM.png) 18 | 19 | 20 | 本文内容依然来自于这个仓库 21 | [https://github.com/bregman-arie/devops-exercises](https://github.com/bregman-arie/devops-exercises) 22 | 23 | 24 | 25 | 以下是具体内容: 26 | > ()的内容是我的补充部分。 27 | 28 | # Go 101 29 | ## Go 语言有哪些特点 30 | - Go 是一种强类型静态语言,变量的类型必须在声明的时候指定(但可以使用类型推导),在运行时不能修改变量类型(与 `Python` 这类动态类型语言不同)。 31 | - 足够的简单,通常一个周末就能学会 32 | - 编译速度够快 33 | - 内置并发(相对于 Java 的并发来说非常简单) 34 | - 内置垃圾收集 35 | - 多平台支持 36 | - 可以打包到一个二进制文件中,所有运行时需要依赖的库都会被打包进这个二进制文件中,非常适合于分发。 37 | 38 | ## Go 是一种编译型的静态类型语言,正确还是错误 39 | 正确✅ 40 | 41 | ## 为什么有些函数是以大写字母开头的 42 | 这是因为 Go 语言中首字母大写的函数和变量是可以导出的,也就是可以被其他包所引用;类似于 Java 中的 `public` 和 `private` 关键字。 43 | 44 | # 变量和数据类型 45 | 46 | ## 简洁和常规声明变量方式 47 | 48 | ```go 49 | package main 50 | 51 | import "fmt" 52 | 53 | func main() { 54 | x := 2 // 只能在函数内使用,自动类型推导 55 | var y int = 2 56 | 57 | fmt.Printf("x: %v. y: %v", x, y) 58 | } 59 | ``` 60 | 61 | 62 | ## 正确✅还是错误❌ 63 | - 可以重复声明变量❌(强类型语言的特性) 64 | - 变量一旦声明,就必须使用✅(避免声明无效变量,增强代码可读性) 65 | 66 | 67 | ## 下面这段代码的结果是什么? 68 | 69 | ```go 70 | package main 71 | 72 | import "fmt" 73 | 74 | func main() { 75 | var userName 76 | userName = "user" 77 | fmt.Println(userName) 78 | } 79 | ``` 80 | 81 | 编译错误,变量 `userName` 没有声明类型;修改为这样是可以的: 82 | ```go 83 | func main() { 84 | var userName string 85 | userName = "user" 86 | fmt.Println(userName) 87 | } 88 | ``` 89 | 90 | ## `var x int = 2` and `x := 2` 这两种声明变量的区别 91 | 结果上来说是相等的,但 `x := 2` 只能在函数体类声明。 92 | 93 | ## 下面这段代码的结果是声明? 94 | ```go 95 | package main 96 | 97 | import "fmt" 98 | 99 | x := 2 100 | 101 | func main() { 102 | x = 3 103 | fmt.Println(x) 104 | } 105 | ``` 106 | 107 | 编译错误,`x := 2` 不能在函数体外使用, `x = 3` 没有指定类型,除非使用 `x := 3` 进行类型推导。 108 | 109 | ## 如何使用变量声明块(至少三个变量) 110 | 111 | ```go 112 | package main 113 | 114 | import "fmt" 115 | 116 | var ( 117 | x bool = false 118 | y int = 0 119 | z string = "false" 120 | ) 121 | 122 | func main() { 123 | fmt.Printf("The type of x: %T. The value of x: %v\n", x, x) 124 | fmt.Printf("The type of y: %T. The value of y: %v\n", y, y) 125 | fmt.Printf("The type of z: %T. The value of z: %v\n", y, y) 126 | } 127 | ``` 128 | 变量块配合 `go fmt` 格式化之后的代码对齐的非常工整,强迫症的福音。 129 | 130 | Go 的基础面试题也蛮多的,我们先从基础的开始,今后后继续更新相关面试题,难度也会逐渐提高,感兴趣的朋友请持续关注。 131 | #GO #面试 132 | -------------------------------------------------------------------------------- /ob/Helm-tips.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 使用 Helm 管理应用的一些 Tips 3 | date: 2023/10/07 20:36:14 4 | categories: 5 | - Helm 6 | tags: 7 | - CloudNative 8 | --- 9 | ![Helm tips.png](https://s2.loli.net/2023/10/08/9HKS1lNqyGMson5.png) 10 | 11 | # 背景 12 | `Helm` 是一个 **Kubernetes** 的包管理工具,有点类似于 `Mac` 上的 `brew`,`Python` 中的 `PIP`;可以很方便的帮我们直接在 `kubernetes` 中安装某个应用。 13 | 14 | 比如我们可以直接使用以下命令方便的在 k8s 集群安装和卸载 `MySQL`: 15 | ```bash 16 | helm install my-sql oci://registry-1.docker.io/bitnamicharts/mysql -n mysql 17 | 18 | helm uninstall my-mysql -n mysql 19 | ``` 20 | 21 | 22 | 对于一些复杂的应用使用 Helm 一键安装会更简单,以 Pulsar 举例: 23 | ![image.png](https://s2.loli.net/2023/10/08/ig4koZIFlUT5Bt1.png) 24 | 它有着多个组件,比如 bookkeeper、zookeeper、broker、proxy 等,各个组件还有着依赖关系。 25 | 26 | 如果我们手动安装流程会比较繁琐,而使用 Helm 时便非常简单: 27 | ```bash 28 | helm repo add apache https://pulsar.apache.org/charts 29 | 30 | helm install my-pulsar apache/pulsar --version 3.0.0 -n pulsar 31 | ``` 32 | 33 | > 当然他也只是帮我们生成了部署所需要的 yaml 文件,也没有太多黑科技。 34 | 35 | # 升级 36 | 37 | 看似简单的工具我在实际线上使用的时候也踩过一个坑,最大的一个问题就是某次升级 Pulsar 的时候生成的 yaml 文件是空的,导致整个集群被删除了😭。 38 | 39 | 还好最后使用 `helm rollback version` 将集群恢复过来了,我们的持久化数据也还在。 40 | 41 | 而出现这个问题的原因是我执行了下面这个命令: 42 | ```bash 43 | helm upgrade pulsar ./charts/pulsar --version 2.9.2 -f charts/pulsar/values-2.10.3.yaml -n pulsar 44 | ``` 45 | 46 | 我们是将 `pulsar` 的 `Helm-Chart` 源码下载到本地,然后修改 `value.yaml` 的方式执行升级的。 47 | 48 | 当时执行命令的时候没有注意,在一个没有 `values-2.10.3.yaml` 文件的目录下执行的,导致生成的 `yaml` 文件是空的,也就导致 k8s 在 `pulsar` 这个 `namespace` 下删除了所有的资源。 49 | 50 | ## 模拟升级 51 | 为了避免今后再次出现类似的问题,需要在升级前先模拟升级: 52 | ```shell 53 | helm upgrade pulsar ./charts/pulsar --version 2.9.2 -f charts/pulsar/values-2.10.3.yaml -n pulsar --dry-run --debug > debug.yaml 54 | ``` 55 | 56 | 其中关键的 `dry-run` 和 `debug` 参数可以指定模拟升级和输出详细的内容。 57 | 58 | 这样我们就可以在升级前先查看 `debug.yaml` 里的内容是不是符合我们的预期。 59 | # 对比升级 60 | 61 | 但这样并不能直观的看出哪些地方是我们修改的,还好社区已经有了相关的插件,可以帮我们高亮显示修改的地方。 62 | 63 | ```shell 64 | helm plugin install https://github.com/databus23/helm-diff 65 | ``` 66 | 我们先安装好这个 helm 插件。 67 | 68 | 然后在升级前先使用该插件: 69 | ```shell 70 | helm diff upgrade pulsar ./charts/pulsar --version 2.9.2 -f charts/pulsar/values-2.10.3.yaml -n pulsar 71 | ``` 72 | 73 | ![image.png](https://s2.loli.net/2023/10/08/V1k5gdhLASfq9JR.png) 74 | 75 | 这样就可以高亮显示出修改的内容。 76 | 77 | > 不用担心这个命令会直接升级,它会自动加上 --dry-run --debug 参数。 78 | 79 | 更多命令可以参考官方文档: 80 | [https://github.com/databus23/helm-diff](https://github.com/databus23/helm-diff) 81 | 82 | Helm 功能很强,在操作生产环境的时候必须得谨慎,都是血淋淋的教训啊。 -------------------------------------------------------------------------------- /ob/Kubernetes-tricks.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 【译】几个你或许并不知道 kubernetes 技巧 3 | date: 2024/06/03 18:05:25 4 | categories: 5 | - 翻译 6 | - kubernetes 7 | tags: 8 | - kubernetes 9 | --- 10 | 11 | ![](https://s2.loli.net/2024/06/03/AoNyHhS4sl96tFx.png) 12 | 13 | 14 | 原文链接: https://overcast.blog/13-kubernetes-tricks-you-didnt-know-647de6364472 15 | 16 | # 使用 PreStop 优雅关闭 Pod 17 | 18 | ```yaml 19 | apiVersion: v1 20 | kind: Pod 21 | metadata: 22 | name: graceful-shutdown-example 23 | spec: 24 | containers: 25 | - name: sample-container 26 | image: nginx 27 | lifecycle: 28 | preStop: 29 | exec: 30 | command: ["/bin/sh", "-c", "sleep 30 && nginx -s quit"] 31 | ``` 32 | 33 | PreStop 允许 Pod 在终止前执行一个命令或者是脚本,使用它就可以在应用退出前释放一些资源,确保应用可以优雅退出。 34 | 35 | 比如可以在 Nginx 的 Pod 退出前将当前的请求执行完毕。 36 | 37 | 38 | 39 | # 使用临时容器调试 Pod 40 | 临时容器可以不修改一个运行的容器的前提下调试容器,可以很方便的调试一些生产环境的 bug,可以避免重启应用。 41 | 42 | ```bash 43 | kubectl alpha debug -it podname --image=busybox --target=containername 44 | ``` 45 | 46 | 生产环境谨慎使用,只有在当前环境下无法排查问题的时候才使用。 47 | 48 | # 基于自定义的 Metrics 自动扩容Pod 49 | 50 | kubernetes 是提供了 HPA 机制可以跟进 CPU 内存等标准数据进行自动扩缩容,但有时我们需要根据自定义的数据进行扩缩容。 51 | 52 | 比如某个接口的延迟、队列大小等。 53 | 54 | ```yaml 55 | apiVersion: autoscaling/v2beta2 56 | kind: HorizontalPodAutoscaler 57 | metadata: 58 | name: custom-metric-hpa 59 | spec: 60 | scaleTargetRef: 61 | apiVersion: apps/v1 62 | kind: Deployment 63 | name: your-application 64 | minReplicas: 1 65 | maxReplicas: 10 66 | metrics: 67 | - type: Pods 68 | pods: 69 | metric: 70 | name: your_custom_metric 71 | target: 72 | type: AverageValue 73 | averageValue: 10 74 | ``` 75 | 76 | # 用 Init Containers 配置启动脚本 77 | 78 | 初始化容器可以在应用容器启动前运行,我们可以使用它来初始化应用需要的配置、等待依赖的服务启动完成等工作: 79 | 80 | ```yaml 81 | apiVersion: v1 82 | kind: Pod 83 | metadata: 84 | name: myapp-pod 85 | spec: 86 | containers: 87 | - name: myapp-container 88 | image: myapp 89 | initContainers: 90 | - name: init-myservice 91 | image: busybox 92 | command: ['sh', '-c', 'until nslookup myservice; do echo waiting for myservice; sleep 2; done;'] 93 | ``` 94 | 95 | 比如这个初始化容器会等待 myservice 可用后才会启动应用。 96 | 97 | 需要注意的是如果初始化容器会阻塞应用启动,所以要避免在初始化容器里执行耗时操作。 98 | 99 | # Node 亲和性调度 100 | 当我们需要将某些应用部署到硬件配置较高的节点时(比如需要 SSD 硬盘),就可以使用节点亲和性来部署应用: 101 | 102 | ```yaml 103 | apiVersion: v1 104 | kind: Pod 105 | metadata: 106 | name: with-node-affinity 107 | spec: 108 | containers: 109 | - name: with-node-affinity 110 | image: nginx 111 | affinity: 112 | nodeAffinity: 113 | requiredDuringSchedulingIgnoredDuringExecution: 114 | nodeSelectorTerms: 115 | - matchExpressions: 116 | - key: disktype 117 | operator: In 118 | values: 119 | - ssd 120 | ``` 121 | 122 | 这个 Pod 会被部署到有这个 `disktype=ssd` 标签的 节点上。 123 | 124 | # 动态配置:ConfigMap 和 Secrets 125 | ConfigMap 和 Secrets可以动态注入到 Pod 中,避免对这些配置硬编码。 126 | 127 | ConfigMap 适合非敏感的数据,Secrets 适合敏感的数据。 128 | 129 | ```yaml 130 | # ConfigMap Example 131 | apiVersion: v1 132 | kind: ConfigMap 133 | metadata: 134 | name: app-config 135 | data: 136 | config.json: | 137 | { 138 | "key": "value", 139 | "databaseURL": "http://mydatabase.example.com" 140 | } 141 | 142 | # Pod Spec using ConfigMap 143 | apiVersion: v1 144 | kind: Pod 145 | metadata: 146 | name: myapp-pod 147 | spec: 148 | containers: 149 | - name: myapp-container 150 | image: myapp 151 | volumeMounts: 152 | - name: config-volume 153 | mountPath: /etc/config 154 | volumes: 155 | - name: config-volume 156 | configMap: 157 | name: app-config 158 | ``` 159 | 160 | 这样在应用中就可以通过这路径 `/etc/config/config.json` 读取数据了。 161 | 162 | > 当然也可以把这些数据写入到环境变量中。 163 | 164 | 165 | 以上这些个人技巧用的最多的是: 166 | - 临时容器调试 Pod,特别是业务容器缺少一些命令时。 167 | - Init Container 等待依赖的服务启动完成。 168 | - Node 亲和性调度。 169 | - ConfigMap 是基础操作了。 -------------------------------------------------------------------------------- /ob/OTel-member.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 一年时间从小白成为 OpenTelemetry Member 有感 3 | date: 2025/04/15 17:40:37 4 | categories: 5 | - OpenTelemetry 6 | tags: 7 | - OpenTelemetry 8 | --- 9 | ![](https://s2.loli.net/2025/04/15/5mCgyLhDGfvXojx.jpg) 10 | ![](https://s2.loli.net/2025/04/16/KdHYWrMjm5qXlsw.png) 11 | 前段时间申请成为了 OpenTelemetry 的 Member 通过了,算是完成了一个阶段性目标;从 24 年的 2 月份的第一个 issue 到现在刚好一年的时间。 12 | 13 | 14 | 15 | --- 16 | 17 | ![](https://s2.loli.net/2025/04/15/rVMBdSCNvt4Wu6l.jpg) 18 | 19 | 这事也挺突然的,源自于年初我发了一个 24 年的年终总结,提到了希望在今年争取成为 Member,然后[谭总](https://github.com/JaredTan95)就提醒我可以自己去申请,只要找到两个 `sponsors` 支持就可以了。 20 | 21 | > 我之前不知道这个 Member 是自己申请的,没注意看社区的文档(之前的 `Apache` 社区都是邀请制)。 22 | 23 | 24 | ![](https://s2.loli.net/2025/04/16/LsOdlTzYXP7pUrb.jpg) 25 | 26 | 于是我提交了相关的 [issue](https://github.com/open-telemetry/community/issues/2642),列举了自己做的一些贡献(PR 和 issue),也找到了之前经常帮我 review 的[Rao](https://github.com/steverao) 哥作为 sponsor. 27 | 28 | 不出意外,没等两天就收到了邀请。 29 | # 参与社区 30 | 31 | 32 | OpenTelemetry 作为和厂商无关的可观测标准,非常开放和包容,也是我参与过的社区最多元的开源项目,几乎每个子项目都有上百人参与,他们都来自于不同的公司和个人,在这样的背景下社区自然就会更佳和谐,很难出现某个公司或者个人主导项目的发,风险自然也会小很多。 33 | 34 | 35 | ![](https://s2.loli.net/2025/04/16/3BzuCehpcf4Ek2j.png) 36 | 37 | 38 | 39 | OTel 的技术栈主要是可以分为下面三个部分: 40 | - 客户端:负责上报可观测数据(Trace、Metrics、Logs) 41 | - OTel collector:处理客户端上报的数据 42 | - 数据存储:存储日志、指标、trace 等数据 43 | 44 | 以上每个模块都是 OpenTelemetry 非常重要的组成部分,大家可以都挑感兴趣的部分去参与。 45 | 46 | 作为一个可观测标准,客户端自然就需要支持大部分的技术栈,所以我们常用的语言和技术栈都有对应的实现: 47 | ![](https://s2.loli.net/2024/04/16/zWAVoHaZORI83js.png) 48 | 49 | 这一部分的工作量也非常大,靠个人实现和维护肯定不现实,所以社区非常欢迎大家都来做贡献。 50 | 51 | --- 52 | 53 | ![](https://s2.loli.net/2025/04/16/IzJl4feYQoZ1iKH.png) 54 | 55 | ![](https://s2.loli.net/2025/04/16/gqwplH3s7NGOvnI.png) 56 | 57 | 拿我常用的 Java 来说目前支持了这些框架和库,但依然没有支持全,我们可以在这里的 [issue](https://github.com/open-telemetry/opentelemetry-java-instrumentation/issues?q=sort%3Aupdated-desc+state%3Aopen+label%3A%22contribution+welcome%22) 列表里找到社区需要大家贡献的内容。 58 | 59 | ## SIG 小组 60 | 61 | 社区也准备许多兴趣小组([SIG](https://github.com/open-telemetry/community#special-interest-groups))来解决特定领域的问题: 62 | ![](https://s2.loli.net/2025/04/16/3yDvJUBHj2KwNnk.png) 63 | 64 | 65 | ![](https://s2.loli.net/2025/04/16/vHGnjZPdtR9yauI.png) 66 | 67 | 大家也可以订阅日历参与周会,基本上每个兴趣小组都会定期组织,拿 Java 的来说就是每周四的 UTC+8 的早上九点都会举行。 68 | 69 | 70 | 71 | ![](https://s2.loli.net/2025/04/15/EvgYrcqA36mwf8e.png) 72 | ![](https://s2.loli.net/2025/04/15/pxw6dDBaZLW4hRc.png) 73 | 74 | 之前参加过两次,都是 zoom 的线上会议(老外的习惯是开摄像头),如果自己口语尚可的话和社区主要的 maintainer 直接沟通效率会高很多。 75 | 76 | 当然如果不能开口的话, zoom 也是实时字幕的功能,理解起来问题也不是很大。 77 | 78 | 79 | ![](https://s2.loli.net/2025/04/16/pSDJUFRe6fwy4ct.png) 80 | 81 | 如果以可以成为 Member 的角度,目前我看了一些申请,提交了两个或以上的 PR 应该都可以申请通过,前提是线下提前和你找的 sponsor 达成一致就可以了。 82 | 83 | > 带着这个目的也挺好的,做开源项目往往就是靠爱发电,有这个 Member 的身份也可以作为正向激励,鼓励继续参与。 84 | 85 | 86 | # 总结 87 | 当然成为 Member 只是第一步,随着社区参与的深入度后面还有[其他的角色](https://github.com/open-telemetry/community/blob/main/guides/contributor/membership.md#membership-levels): 88 | ![](https://s2.loli.net/2025/04/16/ebtp5I9wByNYmkd.png) 89 | 90 | 比如 triager 可以分配 issue、approver 可以批准代码、maintainer 就是某个模块的具体负责人了,后面就再接再厉吧。 -------------------------------------------------------------------------------- /ob/Pulsar Proposal.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 如何给开源项目发起提案 3 | date: 2023/12/21 11:09:52 4 | categories: 5 | - OB 6 | tags: 7 | - Pulsar 8 | --- 9 | 10 | # 背景 11 | 12 | 前段时间在使用 Pulsar 的 admin API 时,发现其中的一个接口响应非常慢: 13 | ```java 14 | admin.topics().getPartitionedStats(topic); 15 | ``` 16 | 使用 curl 拿到的响应结果非常大,同时也非常耗时: 17 | ![image.png](https://s2.loli.net/2023/12/21/dMsUq1eFNz9IoYC.png) 18 | 19 | 具体的 issue 在这里:[https://github.com/apache/pulsar/issues/21200](https://github.com/apache/pulsar/issues/21200) 20 | 21 | 22 | 后面经过分析,是因为某些 topic 的生产者和消费者非常多,导致这个查询 topic 统计的接口数据量非常大。 23 | ![image.png](https://s2.loli.net/2023/12/21/CIwr3qivSQyPnEx.png) 24 | 25 | 但在我这个场景其实是不需要这些生产者和消费者信息的,现在就导致这个 topic 无法查看状态,所以就建议新增两个参数可以过滤这两个字段。 26 | # 流程 27 | 因为涉及到新增 API 了,所以社区维护者就建议我起草一个提案试试: 28 | ![image.png](https://s2.loli.net/2023/12/21/qyDmVHRsFBoewiQ.png) 29 | 30 | ## 什么时候需要提案 31 | 32 | 此时就涉及到什么情况下需要给社区发起一个提案的问题了。 33 | ![image.png](https://s2.loli.net/2023/12/21/VH8NqwgWcROLXhP.png) 34 | 在官方的提案指南中有着详细的说明,简单来说就是: 35 | - 对任何模块新增了 API、或者是重大改动的新特性、监控指标、配置参数时都需要发起提案 36 | - 对应的如果只是对现有 bug 的修复、文档等一些可控的变更时,是不需要发起提案的,直接提交 PR 即可。 37 | 38 | ### 提案步骤 39 | 40 | #### 起草 41 | 首先第一步就是根据官方模版起草一个提案: 42 | 重点描述背景、目的、详细设计等。 43 | ![image.png](https://s2.loli.net/2023/12/21/eT7xQEk3li6Rdyp.png) 44 | 并发起一个 PR,如果不确定怎么写的话可以参考已经合并了的提案。 45 | 46 | #### 邮件讨论 47 | 之后则是将这个 PR 发送到开发组邮箱中,让社区成员参与讨论。 48 | 49 | ![image.png](https://s2.loli.net/2023/12/21/cAlZLYGEOqiyMoh.png) 50 | 这一步可能会比较耗时,提案内容可能会被反复修改。 51 | 52 | 发起提案的一个重要目的是可以让社区成员进行讨论,评估是否需要这个提案或者是否 53 | 有其他解决方法。 54 | #### 发起投票 55 | 经过讨论,如果提案获得通过后就可以发起投票了,至少需要有三个 binding 通过的投票后这个提案就通过了。 56 | 57 | > 虽然任何人都可以参与投票,但社区只会考虑 PMC 的投票建议;投票的时效性也只有 48h。 58 | 59 | ![image.png](https://s2.loli.net/2023/12/21/SiHbGyRzX1kBTjt.png) 60 | 61 | 48 小时候便可以发一个投票结果的邮件,如果达到通过条件便可以通知参与投票的 PMC 合并这个 PR 了。 62 | ![image.png](https://s2.loli.net/2023/12/21/r7tm8pyVER6QvCf.png) 63 | 64 | 65 | #### 实现提案 66 | 之后就是没啥好说的实现过程,因为通常我们是需要在提案里详细描述实现过程以及涉及到修改的地方。 67 | 68 | # 总结 69 | 70 | 只要提案被 review 通过后实现起来就非常简单了,跟着提案里的流程实现就好了。 71 | 72 | > 这点非常类似于我们在企业中对某个业务做技术方案,如果大家都按照类似的流程严格审核方案,那实现起来是非常快的,而且可以尽量的减少事后扯皮。 73 | 74 | 所以最后我的实现 PR 提交之后,都没有任何的修改意见,直接就合并了;也大大降低了审核人员的负担,提高整体效率。 75 | 76 | 以上就是我第一次参与 Pulsar 社区的提案过程,我猜测其他社区的流程也是大差不差;其中重点就是异步沟通;大家都认可之后真的会比实时通信的效率高很多。 77 | 78 | 具体的提案细节可以阅读官方指南 [https://github.com/apache/pulsar/blob/master/pip/README.md](https://github.com/apache/pulsar/blob/master/pip/README.md) 79 | 80 | #Blog #Pulsar -------------------------------------------------------------------------------- /ob/Pulsar3.0-new-feature.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Pulsar3.0新功能介绍 3 | date: 2024/01/03 23:04:58 4 | categories: 5 | - OB 6 | tags: 7 | - Pulsar 8 | --- 9 | 10 | ![Pulsar3.0-NewFeature.png](https://s2.loli.net/2024/01/03/1QuX3wI6P8hefLa.png) 11 | 12 | 13 | 在上一篇文章 [Pulsar3.0 升级指北](https://crossoverjie.top/2023/12/24/ob/Pulsar3.0-upgrade/)讲了关于升级 Pulsar 集群的关键步骤与灾难恢复,本次主要分享一些 `Pulsar3.0` 的新功能与可能带来的一些问题。 14 | # 升级后所遇到的问题 15 | 先来个欲扬先抑,聊聊升级后所碰到的问题吧。 16 | 17 | 其中有两个问题我们感知比较明显,特别是第一个。 18 | 19 | 20 | 21 | ## topic被删除 22 | 23 | 我们在上个月某天凌晨从 `2.11.2` 升级到 `3.0.1` 之后,进行了上一篇文章中所提到的功能性测试,发现没什么问题,觉得一切都还挺顺利的,半个小时搞定后就下班了。 24 | 25 | 26 | 结果哪知道第二天是被电话叫醒的,有部分业务反馈业务重启之后就无法连接到 Pulsar 了。 27 | 28 | ![image.png](https://s2.loli.net/2024/01/02/KUAnZ8W65jO3x7d.png) 29 | 最终定位是 topic 被删除了。 30 | 31 | > 其中的细节还蛮多的,修复过程也是一波三折,后面我会单独写一篇文章来详细梳理这个过程。 32 | 33 | 34 | 在这个 issue 和 PR 中有详细的描述: 35 | https://github.com/apache/pulsar/issues/21653 36 | https://github.com/apache/pulsar/pull/21704 37 | 38 | 感兴趣的朋友也可以先看看。 39 | 40 | ## 监控指标丢失 41 | 第二个问题不是那么严重,是升级后发现 bookkeeper 的一些监控指标丢失了,比如这里的写入延迟: 42 | ![image.png](https://s2.loli.net/2024/01/02/9c7qs4CX1lejOIn.png) 43 | 我也定位了蛮久,但不管是官方的 docker 镜像还是源码编译都无法复现这个问题。 44 | 45 | 最终丢失的指标有这些: 46 | - bookkeeper_server_ADD_ENTRY_REQUEST 47 | - bookkeeper_server_ADD_ENTRY_BLOCKED 48 | - bookkeeper_server_READ_ENTRY_BLOCKED 49 | - bookie_journal_JOURNAL_CB_QUEUE_SIZE 50 | - bookie_read_cache_hits_count 51 | - bookie_read_cache_misses_count 52 | - bookie_DELETED_LEDGER_COUNT 53 | - bookie_MAJOR_COMPACTION_COUNT 54 | 55 | 详细内容可以参考这个 issue: 56 | [https://github.com/apache/pulsar/issues/21766](https://github.com/apache/pulsar/issues/21766) 57 | 58 | 59 | # 新特性 60 | 讲完了遇到的 bug,再来看看带来的新特性,重点介绍我们用得上的特性。 61 | ## 支持低负载均衡 62 | 63 | ![image.png](https://s2.loli.net/2024/01/02/KVpW4DyNimlMhqH.png) 64 | 65 | 当我们升级或者是重启 broker 的时候,全部重启成功后其实会发现最后重启的那个 broker 是没有流量的。 66 | 67 | 这个原理和优化在之前写过的 [Pulsar负载均衡原理及优化](https://crossoverjie.top/2023/02/07/pulsar/pulsar-load-banance/#Pulsar-%E8%B4%9F%E8%BD%BD%E5%9D%87%E8%A1%A1%E5%8E%9F%E7%90%86) 其实有详细介绍。 68 | 69 | 本次 3.0 终于将那个优化发版了,之后只要我们配置 `lowerBoundarySheddingEnabled: true` 就能开启这个低负载均衡的一个特性,使得低负载的 broker 依然有流量进入。 70 | 71 | 72 | ## 跳过空洞消息 73 | ![image.png](https://s2.loli.net/2024/01/02/nj2IyteVUQ79SBZ.png) 74 | Pulsar 可能会因为消息消费异常导致游标出现空洞,从而导致磁盘得不到释放; 75 | 76 | 所以我们有一个定时任务,会定期扫描积压消息的 topic 判断是否存在空洞消息,如果存在便可以在管理台使用 skipMessage API 跳过空洞消息,从而释放磁盘。 77 | 78 | 但在 3.0 之前这个跳过 API 存在 bug,只要跳过的数量超过 8 时,实际跳过的数量就会小于 8. 79 | 80 | 具体 issue 和修复过程在这里: 81 | https://github.com/apache/pulsar/issues/20262 82 | https://github.com/apache/pulsar/pull/20326 83 | 84 | 总之这个问题在 3.0 之后也是修复了,有类似需求的朋友也可以使用。 85 | 86 | ## 新的负载均衡器 87 | 88 | 同时也支持了一个新的负载均衡器,解决了以下问题: 89 | - 以前的负载均衡大量依赖 zk,当 topic 数量增多时对扩展性带来问题。 90 | - 新的负载均衡器使用 `non-persistent` 来存储负载信息,就不再依赖 zk 。 91 | - 以前的负载均衡器需要依赖 `leader broker` 进行重定向到具体的 broker,其实这些重定向并无意义,徒增了系统开销。 92 | - 新的负载均衡器使用了 SystemTopic 来存放 topic 的所有权信息,这样每个 broker 都可以拿到数据,从而不再需要从 leader broker 重定向了。 93 | 94 | 更多完整信息可以参考这个 PIP: [PIP-192: New Pulsar Broker Load Balancer](https://github.com/apache/pulsar/issues/16691) 95 | 96 | ## 支持大规模延迟消息 97 | 第二个重大特性是支持大规模延迟消息,相信是有不少企业选择 Pulsar 也是因为他原生就支持延迟消息。 98 | 99 | 我们也是大量在业务中使用延迟消息,以往的延迟消息有着以下一些问题: 100 | - 内存开销过大,延迟消息的索引都是保存在内存中,即便是可以分布在多个 broker 中分散存储,但消耗依然较大 101 | - 重点优化了索引的内存占有量。 102 | - 重启 broker 时会消耗大量时候重建索引 103 | - 支持了索引快照,最大限度的降低了构建索引的资源消耗。 104 | 105 | 106 | 107 | # 待优化功能 108 | ## 监控面板优化 109 | 最后即便是升级到了 3.0 依然还有一些待优化的功能,在之前的 [从 Pulsar Client 的原理到它的监控面板](https://crossoverjie.top/2023/08/03/ob/Pulsar-Client/)中有提到给客户端加了一些监控埋点信息。 110 | 111 | 最终使用下来发现还缺一个 ack 耗时的一个面板,其实日常碰到最多的问题就是突然不能消费了(或者消费过慢)。 112 | 113 | 这时如果有这样的耗时面板,首先就可以定位出是否是消费者本身的问题。 114 | 115 | ![image.png](https://s2.loli.net/2024/01/03/YFoy4PfnRbz72qX.png) 116 | 目前还在开发中,大概类似于这样的数据。 117 | 118 | 119 | # 总结 120 | 121 | Pulsar3.0 是 Pulsar 的第一个 LTS 版本,推荐尽快升级可以获得长期支持。 122 | 但只要是软件就会有 bug,即便是 LTS 版本,所以大家日常使用碰到 Bug 建议多向社区反馈,一起推动 Pulsar 的进步。 123 | 124 | 125 | #Blog #Pulsar -------------------------------------------------------------------------------- /ob/StarRocks-build-in-local.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 如何在本地打包 StarRocks 发行版 3 | date: 2025/05/12 17:47:52 4 | categories: 5 | - StarRocks 6 | tags: 7 | - StarRocks 8 | --- 9 | 最近我们在使用 StarRocks 的时候碰到了一些小问题: 10 | - 重启物化视图的时候会导致视图全量刷新,大量消耗资源。 11 | - 修复 PR:[https://github.com/StarRocks/starrocks/pull/57371](https://github.com/StarRocks/starrocks/pull/57371) 12 | - excluded_refresh_tables 参数与 MV 不在一个数据库的时候,无法生效。 13 | - 修复 PR:[https://github.com/StarRocks/starrocks/pull/58752](https://github.com/StarRocks/starrocks/pull/58752) 14 | 15 | 而提交的 PR 是有发布流程的,通常需要间隔一段时间才会发布版本,但是我们线上又等着用这些修复,没办法就只有在本地打包了。 16 | 17 | 好在社区已经考虑到这种场景了,专门为我们提供了打包的镜像。 18 | 19 | 20 | 21 | 22 | > FE 是 Java 开发的,本地构建还比较容易,而 BE 是基于 cpp 开发的,构建环境比较复杂,在统一的 docker 镜像里构建会省去不少环境搭建流程。 23 | 24 | 我们先要拉取对应的打包镜像: 25 | ```shell 26 | starrocks/dev-env-ubuntu:3.3.9 27 | ``` 28 | 29 | 根据自己的版本号拉取即可,比如我这里使用的是 3.3.9 的版本。 30 | 31 | 然后需要根据我使用的 tag 拉取一个我们自己的开发分支,在这个分支上将修复的代码手动合并进来。 32 | 33 | 然后便可以开始打包了。 34 | 35 | ```shell 36 | git clone git@github.com:StarRocks/starrocks.git /xx/starrocks 37 | 38 | docker run -it -v /xx/starrocks/.m2:/root/.m2 \ 39 | -v /xx/starrocks:/root/starrocks \ 40 | --name 3.3.9 -d starrocks/dev-env-ubuntu:3.3.9 41 | 42 | docker exec -it 3.3.9 bash 43 | 44 | cd /root/starrocks/ 45 | 46 | ./build.sh --fe --clean 47 | ``` 48 | 49 | 我们需要将宿主机的代码磁盘挂载到镜像里,这样镜像就会使用我们的源码进行编译构建。 50 | 51 | 最终会在 `/xx/starrocks/output` 目录生成我们的目标文件。 52 | 53 | ![](https://s2.loli.net/2025/05/14/RqDW2k9telrP4YN.png) 54 | 55 | ## 替换目标镜像 56 | 57 | 既然 fe 的各种 jar 包都已经构建出来了,那就可以基于这些 jar 包手动打出 fe 的 image 了。 58 | 59 | 我们可以参考官方例子,使用 `fe-ubuntu.Dockerfile` 来构建 FE 的镜像。 60 | 61 | ```shell 62 | DOCKER_BUILDKIT=1 docker build --build-arg ARTIFACT_SOURCE=local --build-arg LOCAL_REPO_PATH=. -f fe-ubuntu.Dockerfile -t fe-ubuntu:main ../../.. 63 | ``` 64 | 65 | 除此之外还有更简单的方式,也是更加稳妥的方法。 66 | 67 | 我们可以直接使用官方的镜像作为基础镜像,只替换其中核心的 `starrocks-fe.jar` 。 68 | > 这个 jar 包会在编译的时候构建出来 69 | 70 | 因为 `starrocks-fe.jar` 也是通过同样的镜像打包出来的,所以运行起来不会出现兼容性问题(同样的 jdk 版本),而且也能保证原有的镜像没有修改。 71 | 72 | ```dockerfile 73 | FROM starrocks/fe-ubuntu:3.3.9 74 | COPY starrocks-fe.jar /opt/starrocks/fe/lib/ 75 | ``` 76 | 77 | ```shell 78 | docker build -t fe-ubuntu:3.3.9-fix-{branch} . 79 | ``` 80 | 81 | 这样我们就可以放心的替换线上的镜像了。 82 | 83 | 参考链接: 84 | - [https://docs.starrocks.io/zh/docs/developers/build-starrocks/Build_in_docker/](https://docs.starrocks.io/zh/docs/developers/build-starrocks/Build_in_docker/) 85 | - https://github.com/StarRocks/starrocks/blob/759a838ae15b91056233f180aedc88da67a84937/docker/dockerfiles/fe/README.md#L15 -------------------------------------------------------------------------------- /ob/StarRocks-meta.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 深入理解 StarRocks 的元数据管理 3 | date: 2024/11/11 18:44:37 4 | categories: 5 | - StarRocks 6 | tags: 7 | - StarRocks 8 | --- 9 | 10 | # 背景 11 | 12 | 最近在排查 `starrocks` 线上的一个告警日志: 13 | 14 | ![](https://s2.loli.net/2024/09/26/QtMIBdmL7OciVJa.png) 15 | 16 | 17 | 每隔一段时间都会打印 `base-table` 也就是物化视图的基表被删除了,但其实表还在,也没人去删除;我们就怀疑是否真的表被删除了(可能是 bug)。 18 | 19 | 与此同时还有物化视图 inactive 的日志,也怀疑如果视图是 inactive 之后会导致业务使用有问题。 20 | 21 | 为了确认这个日志是否对使用影响,就得需要搞清楚它出现的原因;于是我就着手从日志打印的地方开始排查。 22 | 23 | 24 | 25 | # 问题排查 26 | 27 | ![](https://s2.loli.net/2024/09/26/2T4sGfw1YC63EuP.png) 28 | 从这个代码可以看出,是在查询表的信息的时候没有查到,从而导致日志打印 base-table 被 dropped 了。 29 | 30 | 而我查询了几天的 `drop table` 的日志,依然没有找到可能是程序 bug 导致被删除的痕迹。 31 | 32 | > 好在 starrocks 的日志打印非常详细,包含了线程名称、类+方法名称,还有具体的代码函数,很容易就定位日志输出的地方。 33 | 34 | 35 | ## 元数据 36 | 37 | 只是为何会调用到这里还需要阅读源码从而找到原因,在开始之前需要先了解一下 starrocks 元数据的一些基本概念。 38 | 39 | > 其实在这篇文章:[StarRocks 元数据管理及 FE 高可用机制](https://xie.infoq.cn/article/6f2f9f56916f0eb2fdb6b001a)中已经有全面的介绍,只是这篇文章有点早了,和现在最新的代码不太匹配。 40 | 41 | 在 StarRocks 元数据中会保存 Database、Table 等信息。 42 | 43 | 这些数据定期保存在 `fe/meta` 目录中。 44 | ![](https://s2.loli.net/2024/09/27/3C4GaXM5BlWmNIw.png) 45 | 46 | StarRocks 对元数据的每一次操作(增删改查数据库、表、物化视图)都会生成 editLog 的操作日志。 47 | 48 | ![image.png](https://s2.loli.net/2024/09/27/5hbDBHGwtarE8fj.png) 49 | 50 | > 新建数据库、修改表名称等 51 | 52 | 当 StarRocks 的 FE 集群部署时,会由 leader 的 FE 启动一个 checkpoint 线程,定时扫描当前的元数据是否需要生成一个 `image.${JournalId}` 的文件。 53 | 54 | ![](https://s2.loli.net/2024/09/20/lQCkBnNWIZ4GwuV.png) 55 | > 其实就是判断当前日志数量是否达到上限(默认是 5w)生成一次。 56 | 57 | 58 | 59 | 具体的流程如下: 60 | ![](https://s2.loli.net/2024/09/27/zgy6ZaQ7b1ceWkm.png) 61 | 62 | - 判断当前是否需要将日志生成 image 63 | - 加载当前 image 里的元数据到内存 64 | - 从 bdb 中读取最新的 Journal,然后进行重放(replay):其实就是更新刚才加载到内存中的元数据。 65 | - 基于内存中的元数据重新生成一份 image 文件 66 | - 删除历史的 image 文件 67 | - 将生成的 image 文件名称通知 FE 的 follower 节点,让他们下载到本地,从而可以实现 image 同步。 68 | 69 | ![](https://s2.loli.net/2024/09/27/Hd1NRzgfSy2xECW.png) 70 | ![](https://s2.loli.net/2024/09/27/QiTHLpOfJ19oAam.png) 71 | 72 | > 通知 follower 下载 image。 73 | 74 | 75 | 76 | ## 元数据同步流程 77 | 78 | 完整的流程图如下图: 79 | ![](https://i.imgur.com/txqTt0U.png) 80 | 81 | 在这个流程图有一个关键 `loadImage` 流程: 82 | ![](https://s2.loli.net/2024/09/27/MoWjm8SKsgx2GXh.png) 83 | 84 | 他会读取 image 这个文件里的数据,然后反序列化后加载到内存里,主要就是恢复数据库和表。 85 | 86 | 还会对每个表调用一次 `onReload()` 函数,而这个函数会只 MV(`MATERIALIZED VIEWS`) 生效。 87 | 88 | 89 | 这个函数正好就是在文初提到的这个函数 `com.starrocks.catalog.MaterializedView#onReloadImpl`: 90 | ![](https://s2.loli.net/2024/09/26/2T4sGfw1YC63EuP.png) 91 | 92 | 从他的实现来看就是判断视图所依赖的基表是否存在,如果有一个不存在就会将当前基表置为 inactive。 93 | 94 | 如果碰到视图的基表也是视图,那就递归再 reload 一次。 95 | 96 | ## 复现问题 97 | 98 | 既然知晓了这个加载流程,再结合源码应该不难看出这里的问题所在了。 99 | 100 | ![](https://s2.loli.net/2024/09/27/MoWjm8SKsgx2GXh.png) 101 | 从这里的加载数据库可以看出端倪,如果我的视图和基表不在同一个数据库里,此时先加载视图是不是就会出现问题? 102 | 103 | 加载视图的时候会判断基表是否存在,而此时基表所在的数据库还没加载到内存里,自然就会查询不到从而出现那个日志。 104 | 105 | 我之前一直在本地模拟,因为都是在同一个数据库里的基表和视图,所以一直不能复现。 106 | 107 | 只要将基表和视图分开在不同的数据库中,让视图先于数据库前加载就会触发这个日志。 108 | # 修复问题 109 | 110 | 要修复这个问题也很简单,只要等到所有的数据库都表都加载完毕后再去 reload 物化视图就可以了。 111 | 112 | 当我回到 main 分支准备着手修改时,发现这个问题已经被修复了: 113 | https://github.com/StarRocks/starrocks/pull/51002 114 | 115 | ![](https://s2.loli.net/2024/09/27/pzWPnoF2MIji9Kw.png) 116 | 117 | 修复过程也很简单,就是 reload 时跳过了 MV,等到所有的数据都加载完之后会在 `com.starrocks.server.GlobalStateMgr#postLoadImage` 手动加载 `MV`。 118 | 119 | ![](https://s2.loli.net/2024/09/27/7JCLyU6umlRnqvE.png) 120 | 121 | 这个 PR 修复的问题也是我一开始提到的,会打印许多令人误解的日志。 122 | 123 | 到这里就可以解释文章开头的那个问题了:打印的这个 base-table 被删除的日志对业务来说没有影响,只是一个 bug 导致出现了这个日志。 124 | 125 | 额外提一句,这个日志也比较迷,没有打印数据库名称,如果有数据库名称的话可能会更快定位到这个问题。 126 | 127 | 参考文章: 128 | - https://xie.infoq.cn/article/6f2f9f56916f0eb2fdb6b001a 129 | - https://github.com/StarRocks/starrocks/pull/51002 -------------------------------------------------------------------------------- /ob/VictoriaLogs-Intro.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: VictoriaLogs:一款超低占用的 ElasticSearch 替代方案 3 | date: 2023/08/23 15:46:25 4 | categories: 5 | - VictoriaLogs 6 | tags: 7 | - CloudNative 8 | --- 9 | ![image.png](https://s2.loli.net/2023/08/23/t5soejn8vw1aZil.png) 10 | 11 | # 背景 12 | 13 | 前段时间我们想实现 `Pulsar` 消息的追踪流程,追踪实现的效果图如下: 14 | ![image.png](https://s2.loli.net/2023/08/02/H7pjinzQ5EWR2tF.png) 15 | 16 | 实现其实比较简单,其中最重要的就是如何存储消息。 17 | 18 | 19 | > 消息的读取我们是通过 Pulsar 自带的 BrokerInterceptor 实现的,对这个感兴趣的朋友后面会单独做一个分享。 20 | 21 | 22 | 23 | 根据这里的显示内容我们大概需要存储这些信息: 24 | - 客户端地址 25 | - 消息发布时间 26 | - 分发消费者、订阅者名称 27 | - ACK 消费者、订阅者名称 28 | - 消息 ID 29 | 最终捋了下: 30 | ![image.png](https://s2.loli.net/2023/08/23/MWbVcu6dm1NfaAB.png) 31 | 32 | 都以两个 `consumer` 计算: 33 | 一条消息占用内存:`140+ 535*2 + 536*2 =2282byte` 34 | 存储三天:`TPS * 86400 * 3`=`TPS*259200` 条 35 | 总存储: 36 | `2282*TPS*259200≈ 百GB` 37 | 38 | 根据我们的 `TPS` 计算,三天的大概会使用到 上百 G 的存储,这样首先就排除了 `Redis` 这种内存型数据库。 39 | 40 | 同样的换成 `MySQL` 存储也不划算,因为其实这些数据并不算那么重要。 41 | 42 | 做了几个技术选型都不太满意,不是资源开销太大就是没有相关的运维经验。 43 | 44 | 后面在领导的提醒下,我们使用的 `VictoriaMetrics` 开源了一个 `VictoriaLogs`,虽然当时的版本还是 `0.1.0`,使用过他们家 Metrics 的应该都会比较信任他们的技术能力,所以就调研了一下。 45 | 46 | 具体的信息可以查看官方文档: 47 | [https://docs.victoriametrics.com/VictoriaLogs/](https://docs.victoriametrics.com/VictoriaLogs/) 48 | 49 | ![image.png](https://s2.loli.net/2023/08/23/8LDNOGEC6Aslvzn.png) 50 | 51 | 52 | 简单来说就是它也是一个日志存储数据库,并且有着极低的资源占有率,相对于 `ElasticSearch` 来说内存、磁盘、CPU 都是几十倍的下降率。 53 | 54 | ![image.png](https://s2.loli.net/2023/08/23/U9v1HCgRDtLsdif.png) 55 | 56 | 通过官方的压测对比图会发现确实在各方面对 ES 都是碾压。 57 | ![image.png](https://s2.loli.net/2023/08/23/3Epxdzie8q5tVmY.png) 58 | 59 | 官方宣传的第一反应是不能全信,于是我自己压测了一下,果然 CPU 内存 磁盘的占用都是极低的。 60 | 61 | > 同时也发现运维部署确实简单,直接一个 helm install 就搞定,就是一个二进制文件,不会依赖第二个组件。 62 | 63 | 按照刚才同样的数据存储三天,只需要不到 6G 的磁盘空间,我们生产环境已经平稳运行一段时间了。 64 | ![image.png](https://s2.loli.net/2023/08/23/kzrxA89EeNnQ7Ro.png) 65 | 因为我们是批量写入数据的,所以在最高峰 20K 的 `TPS` 下 `CPU` 使用不到 0.1 核,内存使用最高 `120M`,这点确实是对 ES 碾压了。 66 | 67 | ![image.png](https://s2.loli.net/2023/08/23/wn2BduNX63PyYV9.png) 68 | 磁盘占用也是非常少。 69 | 70 | 这些有点得归功于它有些的压缩、编解码算法,以及 `Golang` 带来的相对于 `Java` 的极低资源占用。 71 | 72 | # 还存在的问题 73 | 74 | 如果一切都这么完美的话那 `VictoriaLogs` 确实也太变态了, 自然他也有一些不太完美的地方。 75 | 76 | ## 分词功能有限 77 | 78 | 首先第一个是分词功能有限,只能做简单的搜索,无法做到类似于 ES 的各种分词,插件当然也别想了。 79 | 80 | ## 不支持集群 81 | 82 | 当前版本不支持集群部署,也就是无法横向扩展了;不过幸好他的的单机性能已经非常强了。 83 | 84 | 这也是目前阶段部署简单的原因。 85 | 86 | ## 过期时间无法混用 87 | 88 | `VictoriaLogs` 支持为数据配置过期时间自动删除,有点类似于 Redis,它会在后台启动一个协程定期判断数据是否过期,但只能对所有数据统一设置。 89 | 90 | 比如我想在 `VictoriaLogs` 中存放两种不同类型的数据,同时他们的过期删除时间也不相同;比如一个是三天删除,一个是三月后删除。 91 | 92 | 这样的需求目前是无法实现的,只能部署两个 `VictoriaLogs`. 93 | 94 | ## 默认无法查询所有字段 95 | ![image.png](https://s2.loli.net/2023/08/24/2Wk6VUXQoEYvZJ1.png) 96 | 97 | 由于 `VictoriaLogs` 可以存储非结构化数据,默认情况下只能查询内置的三个字段,我们自定义的字段目前没法自动查询,需要我们手动指定。 98 | 99 | 这个倒不是致命问题,只是使用起来稍微麻烦一些;社区也有一些反馈,相信不久就会优化该功能。 100 | ![image.png](https://s2.loli.net/2023/08/24/XYxz8tTDmw6arf9.png) 101 | ![image.png](https://s2.loli.net/2023/08/24/pgQCPsLvXfMc7nF.png) 102 | 103 | 104 | - [https://github.com/VictoriaMetrics/VictoriaMetrics/issues/4780](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/4780) 105 | - [https://github.com/VictoriaMetrics/VictoriaMetrics/issues/4513](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/4513) 106 | 107 | ## 没有官方 SDK 108 | ![image.png](https://s2.loli.net/2023/08/24/bXPKRIy7Ojf1elE.png) 109 | 110 | 这也是个有了更好的一个功能,目前只能根据 REST API 自己编写。 111 | 112 | # 总结 113 | 114 | 当前我们只用来存储 `Pulsar` 链路追踪数据,目前看来非常稳定,各方面资源占用极少;所以后续我们会陆续讲一些日志类型的数据迁移过来,比如审计日志啥的。 115 | 116 | 之后再逐步完善功能后,甚至可以将所有应用存放在 `ElasticSeach` 中的日志也迁移过来,这样确实能省下不少资源。 117 | 118 | 总得来说 `VictoriaLogs` 资源占用极少,如果只是拿来存储日志相关的数据,没有很强的分词需求那它将非常合适。 119 | 120 | 截止到目前最新版也才 `0.3.0` 还有很大的进步空间,有类似需求的可以持续关注。 121 | #Blog #Vlogs #CloudNative -------------------------------------------------------------------------------- /ob/about-opensource-argument.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 顶级开源社区都能吵起来? 3 | date: 2024/03/20 11:15:51 4 | categories: 5 | - OB 6 | tags: 7 | - OpenSource 8 | --- 9 | 10 | # 起因 11 | 12 | 因为订阅了 Pulsar 的开发者邮件,前段时间看到一封标题为[《(Apache committer criteria) [ANNOUNCE] New Committer: Asaf Mesika》](https://lists.apache.org/thread/gzx4j9q0xdtcvrfvvq72t9tm2rt9h3r7)的邮件。 13 | 14 | 乍一看以为是欢迎 `Asaf Mesika` 成为 Committer,但仔细一看不太对劲,这内容也太多了,以往的欢迎都是简单的 `Congratulations!` 作为回复,这篇内容明显有点多了,于是便仔细看了下。 15 | 16 | 17 | 18 | 19 | ![](https://s2.loli.net/2024/03/20/aASNcDpnwVIjHFv.png) 20 | 21 | # 争论 22 | 大概的意思是这封邮件的作者 `Kalwit` 对成为 Committer 的标准产生了疑问: 23 | 24 | > 他觉得本次提名成为 Committer 的大部分贡献都是一些文档相关的内容,还有少部分是与监控相关的提案。 25 | > 他们团队使用 Pulsar 有一段时间了,但目前还未发现稳定的 Pulsar 版本;大部分的 Review 都是来自同一公司(streamnative)。 26 | > 看起来是整个 Pulsar 项目由某一家公司控制了,他们当初选择从 Kafka 切换到 Pulsar 就是因为 Kafka 由 Confluent 控制,才选择一个更加开放的社区。 27 | 28 | 29 | 这样的一封有着“讨伐”意味的邮件一经发出,自然是一石激起千层浪,社区里很多成员都发表了回复。 30 | 31 | 这里我挑选了几个代表性的回复: 32 | ![](https://s2.loli.net/2024/03/21/8ZEBJrVpiW6KYXk.png) 33 | 大概意思就是 Pulsar 是一个开放性项目,任何人都可以参加,每两周也有 Zoom 会议,也是每个人都可以参加。 34 | 35 | 在远程的社区异步沟通过程中,很有可能你的请求没有得到及时的响应,这很正常。 36 | 37 | Pulsar 是由社区开发负责维护的,没有公司对此负责,因此没有得到响应时是没有公司可以责怪的;需要大家一起来解决问题,并不一定是需要 PMC(项目管理委员会成员)还是 committer才能提出意见,任何人都可以发表自己的看法。 38 | 39 | 但这个过程中大家的身份都是志愿者,需要大家自发的去做这些事情。 40 | 41 | 42 | 后续 Kalwit 又继续回复了一些邮件,总体内容就是对社区治理存在疑惑;特别是担心社区背后由某一家公司作为主导,从而导致社区和公司的利益进行绑定。 43 | 44 | 当然社区的观点依然是,Pulsar 社区不受某一具体公司掌控,并举了具体数据: 45 | ![](https://s2.loli.net/2024/03/21/UDiRHjMWdO8xsSI.png) 46 | 在 41 个 PMC 成员中,只有 9 位是 StreamNative 的员工,41 位 committer 中有 13 位是 StreamNative 的员工。 47 | 48 | > 其实以我目前在社区的观感,确实是 streamnative 公司社区维护者更加活跃,其他的一些 committer 可能由于工作变动啥的很少再贡献项目了。 49 | 50 | 51 | 52 | ## 提案被否 53 | `Kalwit` 举了一些例子认为这些 PIP 提案没有获得通过,但是 SN 团队提出的提案大部分都能通过。 54 | 55 | 我觉得这确实是一种客观现象,但可能更多的原因并不是 SN 公司想要主导 Pulsar 社区的进展,而是他们在社区之外(不管是线上还是线下)进行过额外的沟通,也许在提交草案之前就已经达成了初步一致了,所以在提案审核阶段只需要做一些具体的调整就很容易被通过。 56 | 57 | 我自己也提过一些提案,大概提交了三个只有一个通过了;我个人的感受是这个过程中响应时间确实不可控(毕竟是异步沟通),但并不会存在某个团队想要控制哪些提案可以通过,哪些提案不行的这种说法。 58 | 59 | 都是在就事论事的讨论事情,而且不通过的话也会由相关的回复和建议,确实大部分情况也是我考虑不周。 60 | 61 | 62 | 我也看过 asafm 的贡献,其中关于 Pulsar 集成 [OpenTelemetry](https://github.com/apache/pulsar/pull/21080) 的提案确实是下了功夫的(一万多字的内容),从头讲解了 OpenTelemetry 的概念,以及 Pulsar 需要做哪些事情来集成。 63 | 64 | # 个人感受 65 | 66 | ## 厂商绑定 67 | 看完之后我个人的感受是 `Kalwit` 或者他的团队在参与社区的时候应该是进展不顺利,一些提案或者改动没有得到支持,但看到 SN 公司提交的内容更快得到响应,所以得出了以上的结论:Pulsar 社区由 SN 公司进行了主导。 68 | 69 | 他的顾虑也不是没有道理,就像他说的 Kafka 社区由 confluence 公司主导,类似的还有 Dubbo 社区由阿里主导、Golang 由 google 主导。 70 | 71 | 但项目如果加入了 Apache 那他原本的公司其实已经失去了对项目的所有权,只是刚开始的一些 PMC/committer 大部分会是这个公司的员工,毕竟他们是项目的发起者,也更加熟悉整个系统。 72 | 73 | 如果社区发展的健康,后续应该会补充一些其他开发者,这些开发者不受雇于之前发起的项目的公司,甚至是以个人身份加入;只有这样社区就会更加多样化,出现“一言堂”的几率就会大大降低。 74 | 75 | 我觉得造成这种现象的原因和一开始该项目是由某一个特定公司发起有有很大的原因,比如 Dubbo、Golang,所以他们公司在社区的声浪更大,自己公司的需求优先级也会更高,毕竟会有来自同一公司的更多的人来审核这些需求。 76 | 77 | 虽说如前面邮件里回复的:`社区是由志愿者自愿维护的`,但不可否认的是在这些做开源项目商业化的公司内有一批人就在专门维护社区工作。 78 | 79 | 他们会把自己商业化过程中遇到的一些问题,或者是新的 feature 也提交给社区,但这里的区别是他们是拿工资的,积极性肯定要比在社区用爱发电的开发者更积极。 80 | 81 | 这样就会导致社区中最活跃的那批人大概率是靠社区养活自己的人,但这也不是什么坏事;如果你个人或者公司强依赖于某一个开源项目,那也可以想办法多做贡献,成为 committer,这样在一些需要投票的环节也能有一席之位。 82 | 83 | 84 | ## 厂商无关 85 | 当然也有对应的不是由某一个厂商发起的项目,比如我最近参与较多的 `OpenTelemetry` 社区。 86 | 87 | ![](https://s2.loli.net/2024/03/21/DZ2KAX5Wklpm7tr.png) 88 | 按照官方说法有着 1000 多位独立的开发者,代表了超过 180 家公司,在维护者的列表中也可以看到大多数都是来自于不同的公司: 89 | ![](https://s2.loli.net/2024/03/21/jHgCdyoanmzGR8D.png) 90 | 91 | 所以自然也就没有某一厂商主导的说法,所以想要避免这类事情再次发生,最好的方法还是吸纳更多的开发者加入,只有社区成员丰富起来社区才好良性发展。 92 | 93 | 94 | 95 | 参考链接: 96 | - https://lists.apache.org/thread/gzx4j9q0xdtcvrfvvq72t9tm2rt9h3r7 97 | - https://github.com/apache/pulsar/pull/21080 98 | - https://opentelemetry.io/blog/2024/opentelemetry-announced-support-for-profiling/ -------------------------------------------------------------------------------- /ob/cim-2.0.0.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 时隔五年 9K star 的 IM 项目发布 v2.0.0 了 3 | date: 2024/11/04 11:11:48 4 | categories: 5 | - IM 6 | tags: 7 | - IM 8 | --- 9 | 10 | 最近业余时间花了小三个月重构了 [cim](https://github.com/crossoverJie/cim),也将版本和升级到了 [v2.0.0](https://github.com/crossoverJie/cim/releases/tag/v2.0.0),合并了十几个 PR 同时也新增了几位开发者。 11 | 12 | ![image.png](https://s2.loli.net/2024/10/12/yKzedUZ8DQlVwTC.png) 13 | 14 | > 其中有两位也是咱们星球里的小伙伴🎉 15 | 16 | 17 | # 介绍 18 | 上次发版还是在五年前了: 19 | ![](https://s2.loli.net/2024/10/12/WCP1Vn62SeBNAmZ.png) 20 | 21 | 因为确实已经很久没有更新了,在开始之前还是先介绍 [cim](https://github.com/crossoverJie/cim/) 是什么。 22 | 23 | 这里有一张简单的使用图片: 24 | ![Oct-14-2024 11-09-54-min.gif](https://s2.loli.net/2024/10/14/pBvDML4HVgyYZxS.gif) 25 | 同时以前也有录过相关的视频: 26 | 27 | 通过 [cim](https://github.com/crossoverJie/cim) 这个名字和视频可以看出,它具备 IM 即时通讯的基本功能,同时基于它可以实现: 28 | - 即时通讯 29 | - 消息推送 30 | - IOT 消息平台 31 | 32 | 现在要在本地运行简单许多了,前提是有 docker 就可以了。 33 | 34 | ```bash 35 | docker run --rm --name zookeeper -d -p 2181:2181 zookeeper:3.9.2 36 | docker run --rm --name redis -d -p 6379:6379 redis:7.4.0 37 | 38 | git clone https://github.com/crossoverJie/cim.git 39 | cd cim 40 | mvn clean package -DskipTests=true 41 | cd cim-server && cim-client && cim-forward-route 42 | mvn clean package spring-boot:repackage -DskipTests=true 43 | ``` 44 | 45 | ## 架构 46 | [cim](https://github.com/crossoverJie/cim) 的架构图如下: 47 | ![](https://s2.loli.net/2024/10/13/O7wVi8QYr3lMFJo.png) 48 | 主要分为三个部分: 49 | - Client 基本交互功能 50 | - 消息收发 51 | - 消息查询 52 | - 延迟消息 53 | - Route 提供了消息路由以及相关的管理功能 54 | - API 转发 55 | - 消息推送 56 | - 会话管理 57 | - 可观测性 58 | - Server 主要就提供长链接能力,以及真正的消息推送 59 | 60 | 同时还有元数据中心(支持扩展实现)、消息存储等组件; 61 | 62 | 不管是客户端、route、server 都是支持集群: 63 | - route 由于是无状态,可以任意扩展 64 | - server 通过注册中心也支持集群部署,当发生宕机或者是扩容时,客户端会通过心跳和重连机制保证可用性。 65 | 66 | 所以整个架构不存在**单点**,同时比较简单清晰的,大部分组件都支持可扩展。 67 | ## 流程 68 | 69 | ![](https://s2.loli.net/2024/10/13/8teMn7BSa5VWuvi.png) 70 | 71 | 72 | 73 | 为了更方便理解,花了一个流程图。 74 | - server 在启动之后会先在元数据中心注册 75 | - 同时 route 会订阅元数据中的 server 信息 76 | - 客户端登陆时会调用 route 获取一个 server 的节点信息 77 | - 然后发起登陆请求。 78 | - 成功之后会保持长链接。 79 | - 客户端向发送消息时会调用 route 接口来发起消息 80 | - route 根据长链接关系选择 server 进行消息推送 81 | ## v2.0.0 82 | 83 | 接下来介绍下本次 [v2.0.0](https://github.com/crossoverJie/cim/releases/tag/v2.0.0) 有哪些重大变更,毕竟是修改了大的版本号。 84 | 85 | 这里列举一些重大的改动: 86 | ![image.png](https://s2.loli.net/2024/10/12/mRGDV6hBCTAblcI.png) 87 | 88 | - 首先是支持了元数据中心,解耦了 zookeeper,也支持自定义实现。 89 | - 支持了集成测试,可以保证提交的 PR 对现有功能的影响降到最低,代码质量有一定保证;review 代码时更加放心。 90 | - 单独抽离了 `client-sdk`,代码耦合性更好且更易维护。 91 | - 服务之间调用的 RPC 完成了重构 92 | - 支持了动态 URL 93 | - 泛型数据解析 94 | - 还有社区小伙伴贡献的一些 bug 修复、`RpcProxyManager` 的 IOC 支持等特性。 95 | 96 | 97 | 98 | # 总结 99 | 100 | 更多的部署和使用可以参考项目首页的 README,有详细的介绍。 101 | 102 | [cim](https://github.com/crossoverJie/cim) 目前还需要优化的地方非常多;接下来的重点是实现 ACK,同时会完善一下通讯协议。 103 | ![image.png](https://s2.loli.net/2024/10/14/l7RIZfYOsmM1N3P.png) 104 | 105 | todo 列表我也添加了很多,所以非常推荐感兴趣的朋友可以先看看 todo 列表,说不定就有你感兴趣的可以参与一下。 106 | 107 | -------------------------------------------------------------------------------- /ob/codereview-practice.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 我的 CodeReview 实战经验 3 | date: 2025/05/21 10:39:04 4 | categories: 5 | - OB 6 | tags: 7 | - OpenSource 8 | --- 9 | # 背景 10 | 11 | Code Review 是大家日常开发过程中很常见的流程,当然也不排除一些团队为了快速上线,只要功能测试没问题就直接省去了 Code Review。 12 | 13 | 我个人觉得再忙的团队 Code Review 还是很有必要的(甚至可以事后再 Review),好处很多: 14 | - 跳出个人开发的思维误区,更容易发现问题 15 | - 增进团队交流,提高整体的技术氛围 16 | - 团队水平检测器,不管是审核者还是被审核的,review 几次后大概就知道是什么水平了 17 | 18 | 通常 Code Review 有两种场景,一种是公司内部,还有就是开源社区。 19 | 20 | 21 | 22 | # 开源社区 23 | 24 | 先说开源社区,最近也在做 [cim](https://github.com/crossoverJie/cim/pull/170) 项目里做 Review,同时也在 Pulsar、OpenTelemetry、StarRocks 这些项目里做过 Reviewer。 25 | 26 | 以下是一些我参与 Code Review 的一些经验: 27 | 28 | 29 | ## 先提 issue 30 | 在提交 PR 进行 Code Review 之前最好先提交一个 issue 和社区讨论下,你的这个改动社区是否接受。 31 | 32 | 我见过一些事前没有提前沟通,然后提交了一个很复杂的 PR,会导致维护者很难 Review,同时也会打击参与者的积极性。 33 | 34 | 所以强烈建议一些复杂的修改一定先要提前和社区沟通,除非这是一些十拿九稳的问题。 35 | 36 | ## 个人 CI 37 | 一些大型项目往往都有完善的 CI 流程来保证代码质量,通常都有以下的校验: 38 | 39 | - 各种测试流程(单元测试、集成测试) 40 | - 代码 Code Style 检测 41 | - 安全、依赖检测等 42 | 43 | 如果一个 PR 连 CI 都没跑过,其实也没有提前 Review 的必要了,所以在提 PR 之前都建议先在自己的 repo 里将主要的 CI 都跑过再提交 PR。 44 | 45 | 这个在 Pulsar 的[官方贡献流程](https://pulsar.apache.org/contribute/personal-ci/)里也有单独提到。 46 | ![](https://s2.loli.net/2025/05/26/kYQj1ecNCs3HbaB.png) 47 | 48 | ![](https://s2.loli.net/2025/05/26/eImx2GPq5AsbBap.png) 49 | 50 | 51 | 同时在 [PR 模板](https://github.com/apache/pulsar/blob/master/.github/PULL_REQUEST_TEMPLATE.md)里也有提到,建议先在自己的 fork 的 repo 里完成 CI 之后再提交到 `upstream`。 52 | 53 | ![](https://s2.loli.net/2025/05/29/3KhSawogqksm1I9.png) 54 | 55 | 这个其实也很简单,我们只要给自己的 repo 提交一个 PR,然后在 repo 设置中开启 Action,之后就会触发 CI 了。 56 | 57 | 58 | ![](https://s2.loli.net/2025/05/26/QqpCzHJnjGV2R8P.png) 59 | 60 | 如果自己的 PR 还需要频繁的提交修改,那建议可以先修改为 draft,这样可以提醒维护者稍后再做 Review。 61 | 62 | 同时也不建议提交一个过大的 PR,尽量控制在 500 行改动以内,这样才方便 Review。 63 | 64 | ## Review 代码 65 | 66 | ![](https://s2.loli.net/2025/05/29/RtXAc1KYJ5FhDfG.png) 67 | 68 | Github 有提供代码对比页面,但也只是简单的代码高亮,没法像 IDE 这样提供函数跳转等功能。 69 | 70 | ![](https://s2.loli.net/2025/05/26/2kAVKWr45T7ZFRg.png) 71 | 72 | 所以对于 Reviewer 来说,最好是在本地 IDE 中添加 PR 的 repo,这样就可以直接切换到 PR 的分支,然后再本地跟代码,也更好调试。 73 | 74 | 有相关的修改建议可以直接在 github 页面上进行评论,这样两者结合起来 Review,效率会更高。 75 | 76 | Review 代码其实不比写代码轻松,所以对免费帮你做 Review 的要多保持一些瑞思拜。 77 | 78 | ## AI Review 79 | 80 | 现在 Github 已经支持 copilot 自动 Review 了,它可以帮我们总结变更,同时对一些参加的错误提供修改建议。 81 | ![](https://s2.loli.net/2025/05/26/1jBs9oOcMQ4t3e5.png) 82 | 83 | 84 | 使用它还是可以帮我们省不少事情,推荐开启。 85 | # 企业内部 86 | 87 | 在企业内部做 Code Review 流程上要简单许多,毕竟沟通成本要低一些,往往都是达成一致之后才会开始开发,所以重点就是 Review 的过程了。 88 | 89 | 既然是在公司内部,那就要发挥线下沟通的优势了;当然在开始前还是建议在内部的代码工具里比如说 gitlab 中提交一个 MR,先让参会人员都提前看看大概修改了哪些内容,最好是提前在 gitlab 中评论,带着问题开会讨论。 90 | 91 | 实际 Review 过程应该尽量关注业务逻辑与设计,而不是代码风格、格式等细枝末节的问题。 92 | 93 | 提出修改意见的时候也要对事不对人,我见过好几次在 Review 现场吵起来的场景,就是代入了一些主观情绪,被 Review 的觉得自己能力被质疑,从而产生了一些冲突。 94 | 95 | 96 | Code Review 做得好的话整个团队都会一起进步,对个人来说参与一些优质开源项目的 Code Review 也会学到很多东西。 97 | 98 | 99 | 100 | 101 | -------------------------------------------------------------------------------- /ob/go-5-tips.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 【译】五个我最近在 Go 里学到的小技巧 3 | date: 2024/07/02 18:42:39 4 | categories: 5 | - 翻译 6 | - Go 7 | tags: 8 | - Go 9 | --- 10 | 11 | 原文链接:[https://medium.com/@andreiboar/5-small-tips-i-recently-learned-in-go-cf52d50cf129](https://medium.com/@andreiboar/5-small-tips-i-recently-learned-in-go-cf52d50cf129) 12 | 13 | # 让编译器计算数组数量 14 | 我们在 Go 通常很少使用数组 arrays,一般使用切片 Slice 来代替; 15 | 16 | 但是当你需要使用的时候,如果你对需要指定数量大小感到很烦时可以使用 `[...]` 让编译器自动帮我们计算数组大小: 17 | 18 | ```go 19 | package main 20 | 21 | import "fmt" 22 | 23 | func main() { 24 | arr := [3]int{1, 2, 3} 25 | sameArr := [...]int{1, 2, 3} // Use ... instead of 3 26 | 27 | // Arrays are equivalent 28 | fmt.Println(arr) 29 | fmt.Println(sameArr) 30 | } 31 | ``` 32 | 33 | 34 | 35 | # 使用 go run . 替换 go run main.go 36 | 37 | 每当我用 Go 写第一行代码时,我都习惯于开始写 `main.go`: 38 | 39 | ```go 40 | package main 41 | 42 | import "fmt" 43 | 44 | func main() { 45 | sayHello() 46 | } 47 | 48 | func sayHello() { 49 | fmt.Println("Hello!") 50 | } 51 | ``` 52 | 53 | 但是当 `main.go` 变得越来越大时,我喜欢把一些结构体移动到新的文件里,还是在 main 这个包中。 54 | 55 | main.go: 56 | 57 | ```go 58 | package main 59 | 60 | func main() { 61 | sayHello() 62 | } 63 | ``` 64 | 65 | say_hello.go: 66 | 67 | ```go 68 | package main 69 | 70 | import "fmt" 71 | 72 | func sayHello() { 73 | fmt.Println("Hello!") 74 | } 75 | ``` 76 | 77 | 此时使用 `go run main.go` 将会得到以下的错误: 78 | 79 | ```shell 80 | # command-line-arguments 81 | ./main.go:4:2: undefined: sayHello 82 | ``` 83 | 84 | 此时可以使用 `go run .` 来解决这个问题。 85 | 86 | # 使用下划线让你的数字变得更易读 87 | 你知道可以使用下划线使得你的长数字更易读吗? 88 | 89 | ```go 90 | package main 91 | 92 | import "fmt" 93 | 94 | func main() { 95 | number := 10000000 96 | better := 10_000_000 97 | 98 | fmt.Println(number == better) 99 | } 100 | ``` 101 | 102 | # 可以在同一个包下有不同的测试包 103 | 104 | 在 Go 中我通常认为一个目录下只能有一个包,但也不是完全正确的。 105 | 106 | 假设你有一个包名为:`yourpackage` 此时你可以还可以在同一个目录下创建一个名为 `yourpackage_test` 的包,同时在这个包里编写你的测试代码。 107 | 108 | 这样做的好处是,那些没有被 exporter 的函数在 `yourpackage_test` 包下是不能直接访问的,确保测试的是被暴露的函数。 109 | 110 | # 多次传递相同参数的简单方法 111 | 112 | 在使用字符串格式化函数时,我总是觉得必须重复一个多次使用的参数很烦人: 113 | 114 | ```go 115 | package main 116 | 117 | import "fmt" 118 | 119 | func main() { 120 | name := "Bob" 121 | fmt.Printf("My name is %s. Yes, you heard that right: %s\n", name, name) 122 | } 123 | ``` 124 | 还好还有更简便的方法,这样只需要传递一次参数: 125 | 126 | ```go 127 | package main 128 | 129 | import "fmt" 130 | 131 | func main() { 132 | name := "Bob" 133 | fmt.Printf("My name is %[1]s. Yes, you heard that right: %[1]s\n", name) 134 | } 135 | ``` 136 | 137 | 在这个 Twitter 里发现的: 138 | ![](https://s2.loli.net/2024/07/02/vaMP9CXwTEFcGKI.png) 139 | 140 | 希望你今天学到了一些新东西,最近有没有发现一些你从来不知道的 Golang 小技巧? -------------------------------------------------------------------------------- /ob/go-loop-fix.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Go 语言史诗级更新-循环Bug修复 3 | date: 2023/09/24 18:05:25 4 | categories: 5 | - Golang 6 | tags: 7 | - loop 8 | --- 9 | ![](https://s2.loli.net/2023/09/24/rU7IujkPWX1TRQM.png) 10 | # 背景 11 | 12 | 前两天 `Golang` 的官方博客更新了一篇文章:[Fixing For Loops in Go 1.22](https://go.dev/blog/loopvar-preview) 13 | 14 | 看这个标题的就是修复了 Go 循环的 bug,这真的是史诗级的更新;我身边接触到的大部分 Go 开发者都犯过这样的错误,包括我自己,所以前两年我也写过类似的博客: 15 | [简单的 for 循环也会踩的坑](https://crossoverjie.top/2021/12/28/go/for-mistake/) 16 | 17 | 18 | 19 | 先来简单回顾下使用使用 for 循环会碰到的问题: 20 | ```go 21 | list := []*Demo{{"a"}, {"b"}} 22 | for _, v := range list { 23 | go func() { 24 | fmt.Println("name="+v.Name) 25 | }() 26 | } 27 | 28 | type Demo struct { 29 | Name string 30 | } 31 | ``` 32 | 33 | 预期的结果应该是打印 `a,b`,但实际打印的却是`b,b`。 34 | 35 | ![image.png](https://s2.loli.net/2023/09/24/I98GMk5efvNUDbT.png) 36 | 37 | [Let's Encrypt: CAA Rechecking bug](https://bugzilla.mozilla.org/show_bug.cgi?id=1619047) 38 | 类似的问题连 `mozilla` 团队也没能幸免,所以也确实是一个非常常见的问题,这样的写法符合大部分的开发者的直觉,毕竟其他语言这么使用也没有问题。 39 | 40 | 当然在现阶段要解决也很简单,要么就是在使用之前先复制一次,或者使用闭包传参: 41 | ```go 42 | // 复制 43 |  list := []*Demo{{"a"}, {"b"}} 44 |  for _, v := range list { 45 |   temp:=v 46 |   go func() { 47 |    fmt.Println("name="+temp.Name) 48 |   }() 49 |  } 50 | 51 | // 闭包 52 |  list := []*Demo{{"a"}, {"b"}} 53 |  for _, v := range list { 54 |   go func(temp *Demo) { 55 |    fmt.Println("name="+temp.Name) 56 |   }(v) 57 |  } 58 | ``` 59 | 60 | 61 | 还好官方也意识到了这个问题: 62 | ![image.png](https://s2.loli.net/2023/09/24/6NTZSijCofypK54.png) 63 | 所以在 1.22 中我们可以不用再写这个 `



v:=v`这个多余的复制语句了,也不会出现上面的问题。 64 | 65 | 我们在 1.21 中可以使用环境变量预览这个特性: 66 | ```go 67 | ❯ GOEXPERIMENT=loopvar go test 68 | name=b 69 | name=a 70 | ``` 71 | 在 1.22 发布后建议大家都可以升级了,将这种恶心的 bug 扼杀在摇篮里。 72 | 73 | 1.22 后带来了一个好消息是今后少了一道面试题,坏消息是又新增了一个 1.22 版本带来了哪些变化的面试题😂 74 | 75 | 更多详情可以参看官方播客:[https://go.dev/blog/loopvar-preview](https://go.dev/blog/loopvar-preview) -------------------------------------------------------------------------------- /ob/k8s-probe.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: k8s入门到实战-应用探针 3 | date: 2023/11/25 23:20:13 4 | categories: 5 | - OB 6 | tags: 7 | --- 8 | ![Probe.png](https://s2.loli.net/2023/11/26/5uwvC1TrsjMYDZF.png) 9 | 10 | 今天进入 `kubernetes` 的运维部分(并不是运维 `kubernetes`,而是运维应用),其实日常我们大部分使用 `kubernetes` 的功能就是以往运维的工作,现在云原生将运维和研发关系变得更紧密了。 11 | 12 | 13 | 今天主要讲解 `Probe` 探针相关的功能,探针最实用的功能就是可以控制应用优雅上线。 14 | 15 | # 就绪探针 16 | 举个例子,当我们的 service 关联了多个 Pod 的时候,其中一个 Pod 正在重启但还没达到可以对外提供服务的状态,这时候如果有流量进入。 17 | 18 | 那这个请求肯定就会出现异常,从而导致问题,所以我们需要一个和 `kubernetes` 沟通的渠道,告诉它什么时候可以将流量放进来。 19 | ![image.png](https://s2.loli.net/2023/11/26/StHngQR4K9vCxjf.png) 20 | 比如如图所示的情况,红色 `Pod` 在未就绪的时候就不会有流量。 21 | 22 | 使用就绪探针就可以达到类似的效果: 23 | ```yaml 24 | readinessProbe: 25 | failureThreshold: 3 26 | httpGet: 27 | path: /ping 28 | port: 8081 29 | scheme: HTTP 30 | periodSeconds: 3 31 | successThreshold: 1 32 | timeoutSeconds: 1 33 | ``` 34 | 这个配置也很直接: 35 | - 配置一个 HTTP 的 ping 接口 36 | - 每三秒检测一次 37 | - 失败 3 次则认为检测失败 38 | - 成功一次就认为检测成功 39 | 40 | > 但没有配置就绪探针时,一旦 Pod 的 `Endpoint` 加入到 service 中(Pod 进入 `Running` 状态),请求就有可能被转发过来,所以配置就绪探针是非常有必要的。 41 | 42 | # 启动探针 43 | 而启动探针往往是和就绪探针搭配干活的,如果我们一个 Pod 启动时间过长,比如超过上面配置的失败检测次数,此时 Pod 就会被 kubernetes 重启,这样可能会进入无限重启的循环。 44 | 45 | 所以启动探针可以先检测一次是否已经启动,直到启动成功后才会做后续的检测。 46 | ```yaml 47 | startupProbe: 48 | failureThreshold: 30 49 | httpGet: 50 | path: /ping 51 | port: 8081 52 | scheme: HTTP 53 | periodSeconds: 5 54 | successThreshold: 1 55 | timeoutSeconds: 1 56 | ``` 57 | 58 | > 我这里两个检测接口是同一个,具体得根据自己是实际业务进行配置; 59 | > 比如应用端口启动之后并不代表业务已经就绪了,可能某些基础数据还没加载到内存中,这个时候就需要自己写其他的接口来配置就绪探针了。 60 | 61 | 62 | ![image.png](https://s2.loli.net/2023/11/26/AskpbIJiBovPGZ7.png) 63 | 64 | 所有关于探针相关的日志都可以在 Pod 的事件中查看,比如如果一个应用在启动的过程中频繁重启,那就可以看看是不是某个探针检测失败了。 65 | 66 | # 存活探针 67 | 68 | 存活探针往往是用于保证应用高可用的,虽然 kubernetes 可以在 Pod 退出后自动重启,比如 `Pod OOM`;但应用假死他是检测不出来的。 69 | 70 | 为了保证这种情况下 Pod 也能被自动重启,就可以配合存活探针使用: 71 | ```yaml 72 | livenessProbe: 73 | failureThreshold: 3 74 | httpGet: 75 | path: /ping 76 | port: 8081 77 | scheme: HTTP 78 | periodSeconds: 3 79 | successThreshold: 1 80 | timeoutSeconds: 1 81 | ``` 82 | 83 | 一旦接口响应失败,kubernetes 就会尝试重启。 84 | 85 | ![image.png](https://s2.loli.net/2023/11/26/khZlsDHLyX2WOxT.png) 86 | 87 | # 总结 88 | ![image.png](https://s2.loli.net/2023/11/26/jRqSIbk4HmnsTWl.png) 89 | 90 | 以上探针配置最好是可以在研效平台可视化配置,这样维护起来也比较简单。 91 | 92 | 探针是维护应用健康的必要手段,强烈推荐大家都进行配置。 93 | 94 | 本文的所有源码在这里可以访问: 95 | [https://github.com/crossoverJie/k8s-combat](https://github.com/crossoverJie/k8s-combat) 96 | #Blog -------------------------------------------------------------------------------- /ob/k8s-question-01.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: k8s 常见面试题 01 3 | date: 2023/08/17 22:33:43 4 | categories: 5 | - Interview 6 | tags: 7 | - k8s 8 | --- 9 | ![](https://s2.loli.net/2023/08/17/hnWciw54ml6oPdg.jpg) 10 | 11 | 前段时间在这个视频中分享了 [https://github.com/bregman-arie/devops-exercises](https://github.com/bregman-arie/devops-exercises) 这个知识仓库。 12 | 13 | 14 | 15 | 这次继续分享里面的内容,本次主要以 k8s 相关的问题为主。 16 | 17 | 18 | 19 | ## k8s 是什么,为什么企业选择使用它 20 | k8s 是一个开源应用,给用户提供了管理、部署、扩展容器的能力,以下几个例子更容易理解: 21 | - 你可以将容器运行在不同的机器或节点中,并且可以将一些变化同步给这些容器,简单来说我们只需要编写 `yaml` 文件,告诉 `k8s` 我的预期是什么,其中同步变化的过程全部都交给 k8s 去完成。 22 | > 其实就是我们常说的声明式 API 23 | - 第二个特点刚才已经提到了,它可以帮我们一键管理多个容器,同步所有的变更。 24 | - 可以根据当前的负载调整应用的副本数,负载高就新创建几个应用实例,低就降低几个,这个可以手动或自动完成。 25 | 26 | ## 什么时候使用或者不使用 k8s 27 | - 如果主要还是使用物理机这种低级别的基础设施的话,不太建议使用 `k8s`,这种情况通常是比较传统的业务,没有必要使用 `k8s`。 28 | - 第二种情况是如果是小团队,或者容器规模较小时也不建议使用,除非你想使用 k8s 的滚动发布和自扩容能力, 29 | >不过这些功能运维自己写工具也能实现。 30 | 31 | ## k8s 有哪些特性 32 | - 是自我修复,`k8s` 对容器有着健康检测,比如使用启动探针、存活探针等,或者是容器 `OOM` 后也会重启应用尝试修复。 33 | - 自带负载均衡,使用 `service` 可以将流量自动负载到后续 Pod 中,如果 Pod 提供的是 http 服务这个够用了,但如果是 grpc 这样的长链接,就需要使用 istio 这类服务网格,他可以识别出协议类型,从而做到请求级别的负载均衡。 34 | - `Operator` 自动运维能力:k8s 可以根据应用的运行情况自动调整当前集群的 Pod 数量、存储等,拿 `Pulsar` 举例,当流量激增后自动新增 `broker`,磁盘不足时自动扩容等。 35 | - 滚动更新能力:当我们发版或者是回滚版本的时候,k8s 会等待新的容器启动之后才会将流量切回来,同时逐步停止老的实例。 36 | - 水平扩展能力:可以灵活的新增或者是减少副本的数量,当然也可以自动控制。 37 | - 数据加密:使用 `secret` 可以保存一些敏感的配置或者文件。 38 | 39 | ## k8s 有着哪些对象 40 | 这个就是考察我们对 `k8s` 是否是熟悉了,常用的有: 41 | - Pod 42 | - Service 43 | - ReplicationController 44 | - DaemonSet 45 | - namespace 46 | - ConfigMap 47 | 这个其实知道没有太多作用,主要还是得知道在不同场景如何使用不同的组件。 48 | 49 | ## 哪些字段是必须的 50 | 这个问题我也觉得意义不大,只要写过 `yaml` 就会知道了,`metadata, kind, apiVersion` 51 | 52 | ```yaml 53 | apiVersion: apps/v1 54 | kind: Deployment 55 | metadata: 56 | labels: 57 | app: app 58 | name: app 59 | ``` 60 | 61 | ## kubectl 是什么 62 | 其实就是一个 k8s 的 命令行客户端。 63 | 64 | ## 当你部署应用的时候哪些对象用的比较多 65 | - 第一个肯定是 `deployment`,这应该是最常见的部署方式。 66 | - `service`: 可以将流量负载到 Pod 中。 67 | - `Ingress`: 如果需要从集群外访问 Pod 就得需要 `Ingress` 然后 配合域名访问。 68 | 69 | ## 为什么没有 `k get containers` 这个命令 70 | 这个问题主要是看对 `Pod` 的理解,因为在 `k8s` 中 `Pod` 就是最小的单位了,如果想要访问容器可以在 Pod 中访问。 71 | 72 | 我们可以加上 `-c` 参数进入具体的容器。 73 | ``` 74 | kubectl exec -it app -c istio-proxy 75 | ``` 76 | 77 | ## 你认为使用使用 k8s 的最佳实践是什么 78 | 这个主要是看日常使用时有没有遇到什么坑了: 79 | - 第一个就是要验证 `yaml` 内容是否正确,这个确实很重要,一旦执行错了后果很严重,比如使用 helm 的时候最好岂容 `dry-run` 和 `debug`,先看看生成的 `yaml` 是否是预期想要的。 80 | > helm upgrade app --dry-run --debug 81 | - 第二个限制资源的使用,比如 CPU 和 内存,这个也很重要,如果不设置一旦应用出现 bug 可能导致整个 k8s 集群都受到影响。 82 | - 为 Pod,deployment 指定标签,用于分组。 83 | 84 | ```yaml 85 | # 资源限制 86 | resources: 87 | limits: 88 | cpu: 200m 89 | memory: 200Mi 90 | requests: 91 | cpu: 100m 92 | memory: 100Mi 93 | ``` 94 | 95 | > 参考来源:https://github.com/bregman-arie/devops-exercises/blob/master/topics/kubernetes/README.md#kubernetes-101 96 | 97 | 后续部分内容也有出视频版,强烈建议大家关注我的 B 站或者是视频号: 98 | ![image.png](https://s2.loli.net/2023/08/17/joO3wpCAEMtY2yW.jpg) 99 | ![image.png](https://s2.loli.net/2023/08/17/2gcNDC4M3x91Sbh.jpg) 100 | 101 | 102 | #Blog #K8s 103 | -------------------------------------------------------------------------------- /ob/k8s-rollout.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: k8s入门到实战-滚动更新与优雅停机 3 | date: 2023/11/29 14:40:10 4 | categories: 5 | - OB 6 | tags: 7 | --- 8 | 9 | ![rollout.png](https://s2.loli.net/2023/11/29/BPVLoC2UfX5Drv8.png) 10 | 11 | 当我们在生产环境发布应用时,必须要考虑到当前系统还有用户正在使用的情况,所以尽量需要做到不停机发版。 12 | 13 | 14 | 15 | 所以在发布过程中理论上之前的 v1 版本依然存在,必须得等待 v2 版本启动成功后再删除历史的 v1 版本。 16 | > 如果 v2 版本启动失败 v1 版本不会做任何操作,依然能对外提供服务。 17 | 18 | # 滚动更新 19 | ![image.png](https://s2.loli.net/2023/11/29/stqYlaFwecvhouS.png) 20 | 21 | 这是我们预期中的发布流程,要在 kubernetes 使用该功能也非常简单,只需要在 spec 下配置相关策略即可: 22 | 23 | ```yaml 24 | spec: 25 | strategy: 26 | rollingUpdate: 27 | maxSurge: 25% 28 | maxUnavailable: 25% 29 | type: RollingUpdate 30 | ``` 31 | 这个配置的含义是: 32 | - 使用滚动更新,当然还有 **Recreate** 用于删除旧版本的 Pod,我们基本不会用这个策略。 33 | - `maxSurge`:滚动更新过程中可以最多超过预期 Pod 数量的百分比,当然也可以填整数。 34 | - `maxUnavailable`:滚动更新过程中最大不可用 Pod 数量超过预期的百分比。 35 | 36 | 这样一旦我们更新了 Pod 的镜像时,kubernetes 就会先创建一个新版本的 Pod 等待他启动成功后再逐步更新剩下的 Pod。 37 | ![](https://s2.loli.net/2023/11/29/s52LOSvECPReUnT.png) 38 | 39 | # 优雅停机 40 | 滚动升级过程中不可避免的又会碰到一个优雅停机的问题,毕竟是需要停掉老的 Pod。 41 | 42 | 这时我们需要注意两种情况: 43 | - 停机过程中,已经进入 Pod 的请求需要执行完毕才能退出。 44 | - 停机之后不能再有请求路由到已经停机的 Pod 45 | 46 | 第一个问题如果我们使用的是 `Go`,可以使用一个钩子来监听 `kubernetes` 发出的退出信号: 47 | ```go 48 | quit := make(chan os.Signal) 49 | signal.Notify(quit, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT, syscall.SIGPIPE) 50 | go func() { 51 | <-quit 52 | log.Printf("quit signal received, exit \n") 53 | os.Exit(0) 54 | }() 55 | ``` 56 | 在这里执行对应的资源释放。 57 | 58 | 如果使用的是 `spring boot` 也有对应的配置: 59 | ```yaml 60 | server: 61 | shutdown: "graceful" 62 | spring: 63 | lifecycle: 64 | timeout-per-shutdown-phase: "20s" 65 | ``` 66 | 当应用收到退出信号后,spring boot 将不会再接收新的请求,并等待现有的请求处理完毕。 67 | 68 | 但 kubernetes 也不会无限等待应用将 Pod 将任务执行完毕,我们可以在 Pod 中配置 69 | ```yaml 70 | terminationGracePeriodSeconds: 30 71 | ``` 72 | 来定义需要等待多长时间,这里是超过 30s 之后就会强行 kill Pod。 73 | > 具体值大家可以根据实际情况配置 74 | 75 | --- 76 | ```yaml 77 | spec: 78 | containers: 79 | - name: example-container 80 | image: example-image 81 | lifecycle: 82 | preStop: 83 | exec: 84 | command: ["sh", "-c", "sleep 10"] 85 | ``` 86 | 同时我们也可以配置 `preStop` 做一个 sleep 来确保 `kubernetes` 将准备删除的 Pod 在 `Iptable` 中已经更新了之后再删除 `Pod`。 87 | 88 | 这样可以避免第二种情况:已经删除的 `Pod` 依然还有请求路由过来。 89 | 具体可以参考 `spring boot` 文档: 90 | [https://docs.spring.io/spring-boot/docs/2.4.4/reference/htmlsingle/#cloud-deployment-kubernetes-container-lifecycle](https://docs.spring.io/spring-boot/docs/2.4.4/reference/htmlsingle/#cloud-deployment-kubernetes-container-lifecycle) 91 | 92 | # 回滚 93 | 回滚其实也可以看作是升级的一种,只是升级到了历史版本,在 `kubernetes` 中回滚应用非常简单。 94 | ```shell 95 | # 回滚到上一个版本 96 | k rollout undo deployment/abc 97 | # 回滚到指定版本 98 | k rollout undo daemonset/abc --to-revision=3 99 | ``` 100 | 同时 kubernetes 也能保证是滚动回滚的。 101 | # 优雅重启 102 | 在之前的 [如何优雅重启 kubernetes 的 Pod](https://crossoverjie.top/2023/10/19/ob/k8s-restart-pod/) 那篇文章中写过,如果想要优雅重启 Pod 也可以使用 rollout 命令,它也也可以保证是滚动重启。 103 | ```shell 104 | k rollout restart deployment/nginx 105 | ``` 106 | 107 | 使用 `kubernetes` 的滚动更新确实要比我们以往的传统运维简单许多,就几个命令的事情之前得写一些复杂的运维脚本才能实现。 108 | 109 | 本文的所有源码在这里可以访问: 110 | [https://github.com/crossoverJie/k8s-combat](https://github.com/crossoverJie/k8s-combat) 111 | #Blog #K8s 112 | -------------------------------------------------------------------------------- /ob/newsletter/Newsletter01-20231013.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 技术阅读周刊第一期 3 | date: 2023/10/13 16:22:13 4 | categories: 5 | - OB 6 | - Newsletter 7 | tags: 8 | - Tech 9 | --- 10 | ![Newsletter01.png](https://s2.loli.net/2023/10/13/qHMR6zDI529ZsEf.png) 11 | 12 | 我自己平时有每天阅读文章的习惯,也会将这些文章保存起来并做一些记录,今天在看阮一峰的科技爱好者周刊时突然想到我也可以将这些看过的觉得不错的内容分享出来。 13 | ![image.png](https://s2.loli.net/2023/10/13/TaQZ2MyX4xEIedR.png) 14 | 15 | 顺便也可以让大伙参与留下自己觉得不错的内容,互相学习。 16 | 17 | 18 | 以下便是第一期的内容: 19 | 20 | # [Istio 中的负载均衡详解及多集群路由实践](https://jimmysong.io/blog/demystifying-the-load-balancing-in-istio/) 🌟🌟🌟 21 | 22 | 1. 介绍了客户端负载均衡和服务端负载均衡的特点和应用场景。 23 | 2. 引申出 Istio 使用 Envoy 做客户端负载均衡的方案。 24 | 3. 介绍 Istio 支持的一些负载均衡算法。 25 | 4. 如何为具体的服务配置负载均衡,以及如何编写 `DestinationRule` `和VirtualService` 26 | 27 | # [理解 gRPC 协议](https://taoshu.in/grpc.html)🌟🌟🌟🌟 28 | 1. 首先是介绍了 JSON 编码的缺点,可读性高,但性能差。 29 | 2. PB 性能好,但可读性差,同时还需要配套工具,比如 .proto 格式 IDL 文件来做额外的接口描述。 30 | 3. 接口请求是底层依然是 http 协议,不过是 http/2 协议,但是请求的映射是直接使用 .proto 文件的描述。 31 | 4. 消息格式描述了消息体前有五个字节,第一个字节描述了是否压缩,后续四个字节描述了消息大小。 32 | 5. 因为是 stream 协议的关系,才加了这五个字节,因为每次请求都是同一个连接,为了要区分不同的请求就需要在这五个字节来区分了。 33 | 34 | # [Protocol Buffers 编码](https://taoshu.in/pb-encoding.html)🌟🌟🌟🌟 35 | > 配合上一篇一起阅读更加 36 | 37 | 详细讲解了 PB 编码的原理。 38 | 1. 定长数据都比较简单,主要是解决变长字符串的问题。 39 | 2. 以 `websocket` 为例,`websocket` 的是三挡变速,而 PB 引入了 
VarInts 实现了无级变速,**但前提是字段不能太多**。 40 | 3. 使用了 Tag 代替了字段名,但坏处就是解码必须需要 PB 文件,也就是需要通过 PB 文件生成目标语言。 41 | 4. 同时 Tag 也不能更改,更改后解码端得同步更新。 42 | 43 | # The top 7 software engineering workflow tips I wish I knew earlier 🧰 🌟🌟🌟🌟 44 | > 作者使用多年的提高工作效率的七个习惯 45 | 1. Git 相关,别名,我觉得对我来说是自动补全+历史记录更好用 46 | 2. 编码相关,别直接使用查找,可以多使用 IDE 快捷键+AI 编程 47 | 3. 记录学到的知识,比如 Notion,现在我使用 Obsidian 48 | 4. 使用 Todo 记录自己的灵感,脑子不是拿来存储这些东西的,是拿来做创造力相关的事情(这个我也是使用的 Obsidian 插件 Memos)。 49 | 5. 可视化沟通,比如使用截图 App,写文档等。 50 | 6. 使用密码 App,比如 1Password 51 | 7. 使用窗口管理 App 52 | 53 | # [conc](https://github.com/sourcegraph/conc):Go 语言的并发工具库🌟🌟🌟🌟 54 | > Better structured concurrency for go 55 | 56 | 这是项目的介绍,简单来说就是封装了一些使用 goroutine 的常用操作: 57 | - 使用 `conc.WaitGroup` 替代标准的 `sync.WaitGroup`,并提供了安全运行的特性,不用担心 panic。 58 | - 使用 `pool.ResultPool` 可以拿到执行的结果,`Javaer` 是不是似曾相识。 59 | - `iter.Map/iter.ForEach` 可以直接并发 Map 或者是迭代 slice。 60 | 61 | ![image.png](https://s2.loli.net/2023/10/13/iwhN8qW5MmpXfQV.png) 62 | 这里举了个例子,如果我们想要写出一个安全的 `goroutine` 程序,大概需要写左边那么多的代码,而使用 `conc` 会简单很多,也更加易读。 63 | 64 | 其实从这里就不难看出,`conc` 只是将这些代码封装好了,感兴趣的也可以看看源码,代码不多,很快就可以看完。 65 | 66 | 67 | > 以上内容和评分纯主观参考,均没有使用类似于 ChatGPT 这类 AI 工具进行总结,绝对是传统人肉阅读归纳,匠心工艺。 68 | > 69 | > 上榜文章都很不错,推荐大家去阅读原文。 70 | 71 | 文章链接: 72 | - https://jimmysong.io/blog/demystifying-the-load-balancing-in-istio/ 73 | - https://github.com/sourcegraph/conc 74 | - https://taoshu.in/pb-encoding.html 75 | - https://taoshu.in/grpc.html 76 | - https://careercutler.substack.com/p/the-top-7-software-engineering-workflow?ref=dailydev 77 | -------------------------------------------------------------------------------- /ob/newsletter/Newsletter05-20231110.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 技术阅读周刊第第5️⃣期 3 | date: 2023/11/10 17:28:25 4 | categories: 5 | - OB 6 | - Newsletter 7 | tags: 8 | --- 9 | ![image.png](https://s2.loli.net/2023/11/10/aywEgYVeC9vL8pZ.png) 10 | 11 | 12 | **技术阅读周刊,每周更新。** 13 | ## 历史更新 14 | - [20231013:第一期](https://crossoverjie.top/2023/10/13/ob/newsletter/Newsletter01-20231013/) 15 | - [20231022:第二期](https://crossoverjie.top/2023/10/22/ob/newsletter/Newsletter02-20231022/) 16 | - [20231027:第三期](https://crossoverjie.top/2023/10/27/ob/newsletter/Newsletter03-20231027/) 17 | - [20231027:第四期](https://crossoverjie.top/2023/11/03/ob/newsletter/Newsletter04-20231103/) 18 | 19 | ## How to Use OpenTelemetry in Go. OpenTelemetry is a powerful… | by Akanksha Rana | KloudMate 20 | URL: https://blog.kloudmate.com/how-to-use-opentelemetry-in-go-e416ca01c499 21 | > 作者一步步带你使用 golang 配置了 `OpenTelemetry`,不过由于 Go 不支持 agent,还是没有 Java 方便,很多地方都需要硬编码。 22 | 23 | ```go 24 | tp := trace.NewTracerProvider( 25 | trace.WithBatcher(exp), 26 | trace.WithResource(newResource()), 27 | ) 28 | defer func() { 29 | if err := tp.Shutdown(context.Background()); err != nil { 30 | l.Fatal(err) 31 | }}() 32 | otel.SetTracerProvider(tp) 33 | ``` 34 | 35 | ![image.png](https://s2.loli.net/2023/11/07/wFtivm6abfVrgSD.png) 36 | 37 | 最终会输出到文件中,适配起来倒也蛮简单的。 38 | ## What happens when you create a pod in Kubernetes | by Daniele Polencic | ITNEXT 39 | URL: https://itnext.io/what-happens-when-you-create-a-pod-in-kubernetes-6b789b6db8a8 40 | > 本文讲解了一个 Pod 在 `kubernetes` 中创建的主要流程。 41 | 42 | ![image.png](https://s2.loli.net/2023/11/08/2V6ei8XnJ9t1Zj3.png) 43 | ![image.png](https://s2.loli.net/2023/11/08/gKhrCXLtkzicI4F.png) 44 | ![image.png](https://s2.loli.net/2023/11/08/mfGkSAFtdvOCyLx.png) 45 | ![image.png](https://s2.loli.net/2023/11/08/XG7co1AF9blViaM.png) 46 | ![image.png](https://s2.loli.net/2023/11/08/EXVFnQYz9Arkyx2.png) 47 | 48 | - 客户端校验 `yaml` 格式是否正确 49 | - 成功后会将 `yaml` 写入 `etcd`. 50 | - 之后会将 Pod 信息写入调度队列 51 | - 调度队列获取该任务,然后选择一个合适的 Node 节点部署 Pod 52 | - 等待 Pod 启动成功通过 Prob 探针校验 53 | - 将 Pod 的 `IP:Port` 信息作为 `endpoint` 存储在 `etcd` 54 | - 如果有创建 `service`,会将这个 `endpoint` 和 `service` 进行绑定 55 | - 之后其余的组件就可以使用这个 service,比如 `service mesh`、`ingress`、`kube-proxy`、`coreDNS` 等组件。 56 | 57 | ## 像Redis作者那样,使用Go实现一个聊天服务器,不到100行代码 58 | URL: https://colobu.com/2023/10/29/implement-a-small-chat-server-like-antirez-in-100-lines/ 59 | > 前段时间 Redis 作者用 C 语言写了一个简单的聊天服务器,作者使用 Go 实现了类似的功能,代码量也很少,适合新手联系( Go +goroutine 确实比 Java 写起来要简单) 60 | ![image.png](https://s2.loli.net/2023/11/09/af9pk2lC51ujIgq.png) 61 | 62 | 有几个核心流程: 63 | - 每次创建一个连接时,都会将这个连接保存在内存里,使用 conn 作为 key 64 | - 每次发送消息时会将消息发到一个内部 chan 中,然后异步读取 chan 并通过 conn 发送消息 65 | ## Five API Performance Optimization Tricks that Every Java Developer Must Know | by lance | Javarevisited | Medium 66 | URL: https://medium.com/javarevisited/five-api-performance-optimization-tricks-that-every-java-developer-must-know-75324ee1d244 67 | > 作者讲了一些常见的 API 优化手段,不止是 Java 开发者适用。 68 | 69 | 1. 并行调用 70 | 2. 避免长事务:避免 RPC 和查询逻辑与事务代码放在一起,应该拆分。 71 | 3. 添加合适的索引 72 | 4. 数据库返回少量的数据 73 | 5. 加缓存 74 | 75 | ## Are you correctly using Optional, Collections, and Null in your Java code ? | by Abhishek Singh | Medium 76 | URL: https://medium.com/@abhisheksinghjava/are-you-correctly-using-optional-collections-and-null-in-your-java-code-5d2b8617d47c 77 | > Java 介绍了 `Optional` 的正确用法 78 | ![image.png](https://s2.loli.net/2023/11/10/u5xIePQBwz3iS8Z.png) 79 | ![image.png](https://s2.loli.net/2023/11/10/I2AiOCdvaDbXrG4.png) 80 | 1. 入参不需要 `Optional` 81 | 2. 私有方法返回不需要 `Optional` 82 | 4. 公共方法返回使用 `Optional`,因为有些开发者可能不会判断 null。 83 | 5. 集合类数据返回不需要返回 `Optional/null`, 而是返回空集合。 84 | 85 | 文章链接: 86 | - https://blog.kloudmate.com/how-to-use-opentelemetry-in-go-e416ca01c499 87 | - https://itnext.io/what-happens-when-you-create-a-pod-in-kubernetes-6b789b6db8a8 88 | - https://colobu.com/2023/10/29/implement-a-small-chat-server-like-antirez-in-100-lines/ 89 | - https://medium.com/javarevisited/five-api-performance-optimization-tricks-that-every-java-developer-must-know-75324ee1d244 90 | - https://medium.com/@abhisheksinghjava/are-you-correctly-using-optional-collections-and-null-in-your-java-code-5d2b8617d47c 91 | 92 | #Newletters -------------------------------------------------------------------------------- /ob/newsletter/Newsletter06-20231117.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 技术阅读周刊第第6️⃣期 3 | date: 2023/11/17 17:28:25 4 | categories: 5 | - OB 6 | - Newsletter 7 | tags: 8 | --- 9 | ![image.png](https://s2.loli.net/2023/11/17/yZANlmctghPTJDj.png) 10 | 11 | **技术阅读周刊,每周更新。** 12 | ## 历史更新 13 | - [20231013:第一期](https://crossoverjie.top/2023/10/13/ob/newsletter/Newsletter01-20231013/) 14 | - [20231022:第二期](https://crossoverjie.top/2023/10/22/ob/newsletter/Newsletter02-20231022/) 15 | - [20231027:第三期](https://crossoverjie.top/2023/10/27/ob/newsletter/Newsletter03-20231027/) 16 | - [20231103:第四期](https://crossoverjie.top/2023/11/03/ob/newsletter/Newsletter04-20231103/) 17 | - [20231007:第五期](https://crossoverjie.top/2023/11/10/ob/newsletter/Newsletter05-20231110/ni) 18 | 19 | ## 5 Skills the Best Engineers I Know Have in Common 20 | URL: https://www.developing.dev/p/5-skills-all-10x-engineers-have?utm_source=post-email-title&publication_id=1340878&post_id=138715343&utm_campaign=email-post-title&isFreemail=true&r=4buvd&utm_medium=email 21 | > 作者讲述了他身边最好的工程师都具备的五个通用技能 22 | 23 | ![image.png](https://s2.loli.net/2023/11/13/5NCshr4JWIemotP.png) 24 | 25 | - 技术的深度与广度 26 | - 对于最好的工程师来说,深度和广度他们都会掌握 27 | - 要保持好奇心,好奇心是学习任何新东西的原始动力 28 | - 和身边厉害的工程师一起工作,会快速从他们身上学到东西 29 | - 不用权威去影响他人 30 | - 我理解的是不是依靠资历、经验来向他人输出观点;而是就事论事,利用知识、技能来输出。 31 | - 锻炼写作和口语 32 | - 学会销售 33 | - 提升他人 34 | - 分享知识,写 WIKI、做分享 35 | - 团队协作:codereview、团队讨论等 36 | - 构建工具,解决大家遇到的一些共性问题。 37 | - 要有主人公意识 38 | - 这些工程师都有主人公意识。 39 | - 像老板一样思考问题 40 | 41 | 42 | ## Explaining 9 Types of API Testing 43 | URL: https://blog.bytebytego.com/p/ep83-explaining-9-types-of-api-testing?ref=dailydev 44 | ![](https://s2.loli.net/2023/11/14/LrPN8GQ4FjuTISa.gif) 45 | 46 | > 介绍了九种常见的 API 测试方法 47 | - 冒烟测试:简单的验证 API 是否可以正常工作 48 | - 功能测试:根据需求进行测试,有预期结果进行比较 49 | - 集成测试:结合多个 API 完成集成测试,更完善的功能测试 50 | - 回归测试:确保新增功能没有影响到原有的 API 51 | - 负载测试:模拟不同的负载进行测试,测出系统可支持的最大容量 52 | - 压力测试:模拟高负载场景,在这种压力情况下观察 API 行为 53 | - 安全测试:模拟外部安全测试 54 | - UI测试:配合 UI 交互进行功能测试 55 | - 模糊测试:对 API 进行无效输入,尝试让 API 崩溃 56 | 57 | > 实际情况可能并不会分的这么细,往往会将一些步骤合并在一起。 58 | 59 | ## Prometheus 14 点实践经验分享 60 | URL: https://mp.weixin.qq.com/s/z2IVP26swYaTeiPTeOMoQw 61 | ![image.png](https://s2.loli.net/2023/11/16/8TKa5VBeX4jfdMr.png) 62 | 这是一篇 17 年的 `Prometheus` 使用分享,但放到现在一点也不过时。 63 | - 使用 USE 理论来判断资源是否健康 64 | - Utilization 利用率 65 | - Saturation 饱和率 66 | - Errors 错误 67 | - 使用 RED 理论 68 | - Request rate 请求速率 69 | - Error rate 错误速率 70 | - Duration 持续时间 71 | - 指标命名需要有规范 72 | - 通常使用框架生成的都没啥问题 73 | - 可以参考 Prometheus 的官方实践 https://prometheus.io/docs/practices/naming/ 74 | - 注意指标基数 75 | - 避免基数爆炸的,比如不能使用 user_id, trace_id 等作为指标的 label 76 | - 统计失败+总量而不要统计失败+成功量 77 | - 告警症状而非原因 78 | - 告警规则需要配置持续时间,避免无效告警 79 | - 查询时候通常先求 rate() 再求 sum() 80 | 81 | ## 程序员可能必读书单推荐(一) - 面向信仰编程 82 | URL: https://draveness.me//books-1 83 | > draveness 大佬推荐的都是一些偏低层的,静得下心的可以看看,我觉得我是看不下来的。 84 | 85 | - SICP 《计算机程序的构造和解释》 86 | - CTMCP 《计算机程序设计的概念、技术和模型》 87 | - DDIA 《设计数据密集型应用》 88 | 89 | ## TOP 20 Go最佳实践 90 | URL: https://colobu.com/2023/11/17/golang-quick-reference-top-20-best-coding-practices/ 91 | > 都是一些基本套路,各种语言的使用者都推荐掌握 92 | 93 | - 适当使用缩进,推荐统一使用 `gofmt` 94 | - 变量和函数名具有意义 95 | - 限制行长度,IDE 通常都会有提示 96 | - 使用常量代替魔法值 97 | - 显示处理错误 98 | - 避免使用全局变量 99 | - 使用结构体处理复杂逻辑,更易维护 100 | - 使用 `goroutines` 处理并发 101 | - 使用 Recover 处理 panic 102 | - 避免使用 Init 函数,更容易维护 103 | - 使用 Defer 清理资源 104 | - 使用复合字面值而非构造函数 105 | - 使用显示返回值而非具名返回值,也是代码更易读 106 | - 避免变量屏蔽,易读性 107 | - 使用接口抽象 108 | 109 | 文章链接: 110 | - https://www.developing.dev/p/5-skills-all-10x-engineers-have?utm_source=post-email-title&publication_id=1340878&post_id=138715343&utm_campaign=email-post-title&isFreemail=true&r=4buvd&utm_medium=email 111 | - https://blog.bytebytego.com/p/ep83-explaining-9-types-of-api-testing?ref=dailydev 112 | - https://mp.weixin.qq.com/s/z2IVP26swYaTeiPTeOMoQw 113 | - https://draveness.me//books-1 114 | - https://colobu.com/2023/11/17/golang-quick-reference-top-20-best-coding-practices/ 115 | 116 | #Newletters -------------------------------------------------------------------------------- /ob/newsletter/Newsletter09-20231208.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 技术阅读周刊第9️⃣期 3 | date: 2023/12/08 17:28:25 4 | categories: 5 | - OB 6 | - Newsletter 7 | tags: 8 | --- 9 | ![](https://s2.loli.net/2023/12/08/CXqYjtI7fBUlhma.png) 10 | 11 | 12 | **技术阅读周刊,每周更新。** 13 | 14 | ## 历史更新 15 | - [20231107:第五期](https://crossoverjie.top/2023/11/10/ob/newsletter/Newsletter05-20231110/) 16 | - [20231117:第六期](https://crossoverjie.top/2023/11/17/ob/newsletter/Newsletter06-20231117/) 17 | - [20231124:第七期](https://crossoverjie.top/2023/11/24/ob/newsletter/Newsletter07-20231124/) 18 | - [20231201:第八期](https://crossoverjie.top/2023/12/01/ob/newsletter/Newsletter08-20231201/) 19 | 20 | 21 | ## 美团技术博客十周年,感谢一路相伴 - 美团技术团队 22 | URL: https://tech.meituan.com/2023/12/04/ten-years-of-meituan-technology-blog.html 23 | ![image.png](https://s2.loli.net/2023/12/06/1z4f7UOyJ2HPpu6.png) 24 | > 美团技术博客更新十周年了,这个博客确实在广大开发者心中都是有口皆碑的;记得当初在这里看过 HashMap 的原理分析、动态线程池等技术; 25 | > 现在也有加到订阅列表里,有更新时会第一时间阅读 26 | 27 | ## CompletableFuture原理与实践-外卖商家端API的异步化 - 美团技术团队 28 | URL: https://tech.meituan.com/2022/05/12/principles-and-practices-of-completablefuture.html 29 | ![image.png](https://s2.loli.net/2023/12/06/F42yeBdRsJOkon8.png) 30 | > 本文描述了美团对 API 做异步优化的过程,最终选择了 CompletableFuture 的过程 31 | > `CompletableFuture` 使用起来的坑还是蛮多的,推荐大家都应该阅读下。 32 | 33 | - 明确知道自己的代码运行在哪个线程上,如果不传入线程池那就是公共的 `ForkJoinPool` 线程池,可能会有阻塞的情况;也可以直接传入自定义的线程池 34 | - 线程池循环使用可能会引起死锁,当 A 线程依赖于 B 线程的执行结果时,如果此时是同一个线程池,并且线程池已满,B 线程一直得不到机会执行,那 A 线程也无法运行,从而导致死锁。 35 | - `CompletableFuture` 的异常往往会被包装为CompletionException,所以最好是要异常工具类进行提取 36 | ```java 37 | public class ExceptionUtils { 38 | public static Throwable extractRealException(Throwable throwable) { 39 | //这里判断异常类型是否为CompletionException、ExecutionException,如果是则进行提取,否则直接返回。 40 | if (throwable instanceof CompletionException || throwable instanceof ExecutionException) { 41 | if (throwable.getCause() != null) { 42 | return throwable.getCause(); 43 | } 44 | } 45 | return throwable; 46 | 47 | ``` 48 | 49 | ## 没错,数据库确实应该放入 K8s 里! 50 | URL: https://mp.weixin.qq.com/s/QJn6-EzPp7PXar-GdMITCA 51 | > 虽然这是一篇软文,不过其中几个论据确实是有道理的。 52 | > 而 K8s 的控制器则是基于另一种思路:**机器能做的事就不应该由人来做**。通过 Operator,可以实现**24 小时不间断地同步期望状态和实际状态**,而这是用 Ansible 很难实现的,你用 Ansible 实现是想写个定时任务嘛? 53 | 54 | - 复杂度: 55 | - Sealos 提供了一键安装命令,有效降低其复杂度 56 | - 稳定性: 57 | - 一个良好的软件架构会不断提升和收敛其鲁棒性,并逐渐减少对人的依赖,比如使用 Oracle 的人喝茶时间一定比用开源 MySQL 的人喝茶时间多 58 | - 性能: 59 | - 而且,容器对数据库性能的影响几乎可以忽略不计,真正重要的是磁盘 IO 和网络带宽时延等因素。 60 | 61 | 目前市面上大部分云服务厂商所提供的数据库服务也都是跑在 kubernetes 中的。 62 | 63 | ## deckarep/golang-set: A simple, battle-tested and generic set type for the Go language. Trusted by Docker, 1Password, Ethereum and Hashicorp. 64 | URL: https://github.com/deckarep/golang-set 65 | ![image.png](https://s2.loli.net/2023/12/08/ru6dAXPx3G7WlL4.png) 66 | > 一个泛型的 Go Set 库, 还提供了一些集合常用的操作工具,比如 Contains/Difference/Intersect 等函数。 67 | 68 | 已经被这些公司采用了: 69 | - Ethereum 70 | - Docker 71 | - 1Password 72 | - Hashicorp 73 | 74 | ```go 75 | // Syntax example, doesn't compile. 76 | mySet := mapset.NewSet[T]() // T 是具体的类型 77 | 78 | // Therefore this code creates an int set 79 | mySet := mapset.NewSet[int]() 80 | 81 | // Or perhaps you want a string set 82 | mySet := mapset.NewSet[string]() 83 | 84 | type myStruct struct { 85 | name string 86 | age uint8 87 | } 88 | 89 | // Alternatively a set of structs 90 | mySet := mapset.NewSet[myStruct]() 91 | 92 | // Lastly a set that can hold anything using the any or empty interface keyword: interface{}. This is effectively removes type safety. 93 | mySet := mapset.NewSet[any]() 94 | ``` 95 | 96 | 文章链接: 97 | - https://tech.meituan.com/2023/12/04/ten-years-of-meituan-technology-blog.html 98 | - https://tech.meituan.com/2022/05/12/principles-and-practices-of-completablefuture.html 99 | - https://mp.weixin.qq.com/s/QJn6-EzPp7PXar-GdMITCA 100 | - https://github.com/deckarep/golang-set 101 | 102 | #Newletters -------------------------------------------------------------------------------- /ob/newsletter/Newsletter12-202401012.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 技术阅读周刊第十四期:Golang 作者 Rob Pike 在 GopherConAU 上的分享 3 | date: 2024/01/12 17:28:25 4 | categories: 5 | - OB 6 | - Newsletter 7 | tags: 8 | --- 9 | 10 | 11 | ![](https://s2.loli.net/2024/01/11/YMoyEKwUubfZA9a.png) 12 | 13 | 14 | 15 | **技术阅读周刊,每周更新。** 16 | 17 | ## 历史更新 18 | - [20231215:第十期](https://crossoverjie.top/2023/12/15/ob/newsletter/Newsletter10-20231215/) 19 | - [20231122:第十一期](https://crossoverjie.top/2023/12/22/ob/newsletter/Newsletter10-20231222/) 20 | - [20231129:第十二期](https://crossoverjie.top/2023/12/29/ob/newsletter/Newsletter12-20231229/) 21 | - [20240105:第十三期:一些提高生产力的终端命令](https://crossoverjie.top/2024/01/05/ob/newsletter/Newsletter12-20240105/) 22 | 23 | ## What We Got Right, What We Got Wrong 24 | URL: https://commandcenter.blogspot.com/2024/01/what-we-got-right-what-we-got-wrong.html?utm_source=changelog-news 25 | > 本文是 `Golang` 核心作者之一 [Rob Pike](http://robpike.blogspot.com/) 去年底在澳大利亚 GopherConAU 会议上的分享;总结了 Go 语言 14 年来的做对了哪些事情、做错了哪些事情。 26 | > 27 | ![](https://s2.loli.net/2024/01/10/1MRo4ZTPwzmd6F2.png) 28 | 29 | 主要包括: 30 | - 语言涉及 31 | - 社区管理 32 | - 项目运营 33 | 等方面,感兴趣的还可以看油管视频。 34 | [https://www.youtube.com/watch?v=yE5Tpp2BSGw](https://www.youtube.com/watch?v=yE5Tpp2BSGw) 35 | 36 | 37 | ## Top 10 Platform Engineering Tools You Should Consider in 2024 | by Romaric Philogène | Jan, 2024 | Medium 38 | URL: https://medium.com/@rphilogene/top-10-platform-engineering-tools-you-should-consider-in-2024-892e6e211b85 39 | ![](https://s2.loli.net/2024/01/11/VUhodB7rn12lYwR.png) 40 | 41 | > 本文介绍了作为一个平台工程师需要掌握的工具。 42 | 43 | 先定义了什么是平台工程师: 44 | 为研发人员提供平台资源进行开发,让开发人员可以在云环境中自助完成整个软件生命周期的各个环节,比如基础环境搭建、代码 pipelines、监控等。 45 | 46 | 以下是会用到的工具: 47 | - kubernetes:这个就不用多讲了。 48 | - Crossplane:用于管理多集群的 kubernetes 49 | - Qovery:内部开发者平台 50 | - Github/Gitlab CI/CD 51 | - ArgoCD:kubernetes 原生提供的持续部署工具。 52 | - Docker 53 | - Terraform: 基础设施自动化工具,可以通过声明式配置文件实现多云基础设施的部署和管理。 54 | - Datadog:监控和日志分析平台,当然也可以使用 Prometheus/Grafana 等 55 | 56 | ## Load Balancing Algorithms Explained Visually 57 | URL: https://blog.quastor.org/p/load-balancing-algorithms-explained-visually?utm_source=tldrwebdev 58 | > 本文介绍了一些负载均衡算法以及其优缺点。 59 | 60 | ![Round Robin_1.gif](https://s2.loli.net/2024/01/11/NGV9YWJ6g7Dx2sy.gif) 61 | 62 | 1. 轮询算法(Round Robin):每个请求按顺序分配到不同服务器。实现简单,但不能考虑服务器负载情况。 63 | 64 | 2. 加权轮询(Weighted Round Robin):考虑服务器性能给各服务器设置权重,请求分配按权重比例进行。仍然不能实时反应服务器负载变化。 65 | 66 | 3. 最少连接数(Least Connections):实时监测各服务器连接数,将请求分配到连接数最少的服务器上。实现较复杂,需要定期探测各服务器状态。 67 | 68 | 4. 最短响应时间(Least Response Time):监测各服务器响应时间,分配给响应最快的服务器。 69 | 70 | 5. 双随机选择(Power of Two Choices):随机选择两台服务器,将请求分配给负载较轻的一台。减少监测开销。 71 | 72 | 6. 一致哈希(Consistent Hashing):根据请求关键信息计算哈希值,将请求分配给对应的机器范围。解决主机添加和删除问题。 73 | 74 | 7. 其他算法如根据磁盘、内存利用率进行负载分配等。 75 | 76 | 77 | ## What problem did Go actually solve for Google 78 | URL: https://www.reddit.com/r/golang/comments/176b5pn/what_problem_did_go_actually_solve_for_google/ 79 | > 这是一个 Reddit 上的帖子,OP 的问题是 Rob 在之前的分享中提到 Golang 创建的原因是要解决 Google 内部的问题,但没有具体讲 Google 到底遇到了什么问题? 80 | > 什么问题是几百种编程语言都无法解决的问题? 81 | 82 | 以下是一些高赞回答: 83 | - 更快的本地编译速度 84 | - 对新手来说更好理解的代码 85 | - 更严格的代码风格,使得大家的代码都差不多。 86 | - 更容易编写并发程序 87 | 88 | 总体来说, Go 主要解决的是在大型分布式系统中如何更高效地进行协作开发、实现高性能又易维护。这正是 Google 当时最关心的问题。 89 | 90 | 文章链接: 91 | - https://commandcenter.blogspot.com/2024/01/what-we-got-right-what-we-got-wrong.html?utm_source=changelog-news 92 | - https://medium.com/@rphilogene/top-10-platform-engineering-tools-you-should-consider-in-2024-892e6e211b85 93 | - https://blog.quastor.org/p/load-balancing-algorithms-explained-visually?utm_source=tldrwebdev 94 | 95 | 96 | #Newletters -------------------------------------------------------------------------------- /ob/newsletter/Newsletter12-20240105.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 技术阅读周刊第十三期:一些提高生产力的终端命令 3 | date: 2024/01/05 17:28:25 4 | categories: 5 | - OB 6 | - Newsletter 7 | tags: 8 | --- 9 | 10 | 11 | ![](https://s2.loli.net/2024/01/05/wU8gPSzaoitBerC.png) 12 | 13 | 14 | 15 | **技术阅读周刊,每周更新。** 16 | 17 | ## 历史更新 18 | - [20231201:第八期](https://crossoverjie.top/2023/12/01/ob/newsletter/Newsletter08-20231201/) 19 | - [20231215:第十期](https://crossoverjie.top/2023/12/15/ob/newsletter/Newsletter10-20231215/) 20 | - [20231122:第十一期](https://crossoverjie.top/2023/12/22/ob/newsletter/Newsletter10-20231222/) 21 | - [20231129:第十二期](https://crossoverjie.top/2023/12/29/ob/newsletter/Newsletter12-20231229/) 22 | 23 | ## 生存还是毁灭?一文带你看懂 Pulsar 的消息保留和过期策略-腾讯云开发者社区-腾讯云 24 | URL: https://cloud.tencent.com/developer/article/2245703 25 | 26 | ![](https://s2.loli.net/2024/01/05/ZYRxDydaIn2W4jJ.png) 27 | 28 | >本文分析了 Pulsar 消息的生命周期,主要是如何保留和回收消息 29 | 30 | - TTL(Time To Live):使得未 ACK 的消息进入 ACK 状态,供后续回收的时候使用 31 | - **Retention**保留策略:默认情况下已经被所有订阅者 ACK 的消息会被立即回收掉,配置保留策略可以保留一定时间、一定数量的 ACK 消息,利用这个时间差可以做消息回查 32 | - Backlog: 未被确认消息的集合,也就是积压消息;也可以配置只保留一定时间、数量的消息,从而减少磁盘压力;当超过我们配置的阈值时,有以下几种选择: 33 | ![image.png](https://s2.loli.net/2024/01/04/pdEKHRT4v2fAkBm.png) 34 | 35 | ## 这些流行的K8S工具,你都用上了吗 36 | URL: https://mp.weixin.qq.com/s/EC-YLm71YB4cMDoTjrdfyg 37 | > 推荐了一些常用的 kubernetes 管理工具 38 | 39 | ![](https://s2.loli.net/2024/01/05/kWbLtETcey9Gmzp.png) 40 | 41 | 42 | - Helm: kubernetes 平台的必备的包管理工具 43 | - 本地运行的 kubernetes 工具:有时候需要在本地进行开发和测试,这类工具就很有用: 44 | - Docker Desktop 45 | - minikube 46 | - kind 47 | - k3s 48 | - 这类工具在ingress、负载均衡、集群外访问等需要单独配置。 49 | - 集群自动缩放器:用于缩放底层节点 50 | - 一些云服务厂商自动集成了这类功能,如果是自建集群: 51 | - kubernetes Autoscaler 52 | - Karpenter 53 | - 备份和迁移 54 | - 如果部署了有状态的应用,需要进行数据迁移和备份时,可以使用 `velero` 55 | - 命令行工具 56 | - kube-ps1 用于终端的 kubernetes 命令提示 57 | - kubectx 用于在终端进行 集群、namespace 上下文切换 58 | - IDE 59 | - OpenLens 一个客户端可视化 app,用于方便管理 kubernetes 集群 60 | 61 | ## 3 Terminal Commands to Increase Your Productivity - DEV Community 62 | URL: https://dev.to/pankajgupta221b/3-terminal-commands-to-increase-your-productivity-57dm?ref=dailydev 63 | > 作者介绍了几个常用的可以提高生产力的终端命令 64 | 65 | ![](https://s2.loli.net/2024/01/05/9UQjHvpaLEqMldW.png) 66 | 67 | ### alias 别名 68 | 别名非常好用,以下是我常用的一些别名: 69 | 70 | ```shell 71 | -='cd -' 72 | ...=../.. 73 | ....=../../.. 74 | .....=../../../.. 75 | ......=../../../../.. 76 | 1='cd -' 77 | 2='cd -2' 78 | 3='cd -3' 79 | 4='cd -4' 80 | 5='cd -5' 81 | 6='cd -6' 82 | 7='cd -7' 83 | 8='cd -8' 84 | 9='cd -9' 85 | dc=docker 86 | jdk11='export JAVA_HOME=~/jdk/jdk-11.0.16.1.jdk/Contents/Home' 87 | jdk17='export JAVA_HOME=~/Users/chenjie/Documents/dev~/jdk/jdk-17.0.1.jdk/Contents/Home/' 88 | jdk21='export JAVA_HOME=~/jdk/jdk-21.0.1.jdk/Contents/Home' 89 | jdk8='export JAVA_HOME=' 90 | k=kubectl 91 | pp='sh hexo-push.sh' 92 | ``` 93 | ### pbcopy 94 | 这个在有时候需要 debug 日志或者复制一些长文本到剪贴板里非常有用 95 | 96 | ```shell 97 | cat xx.properties |grep timeout | pbcopy 98 | ``` 99 | 这样就可以把 timeout 这个关键字从文件中复制到粘贴板,我就可以将它复制到其他地方使用。 100 | 101 | ### 反向搜索 102 | 在终端中使用 ctrl+r 就可以根据关键字在历史命令中查找命令,这个在忘记了一些命令但只记得关键字的时候非常有用。 103 | ![](https://s2.loli.net/2024/01/05/dfuDG6L2n7h5BNP.png) 104 | 105 | 我这里使用的终端是 Warp ,交互上更加好用一些。 106 | 107 | ### cal 108 | 可以用于显示日历 109 | ![](https://s2.loli.net/2024/01/05/5yCL9oKWIrjB2Dc.png) 110 | 111 | 112 | 113 | 114 | 文章链接: 115 | - https://cloud.tencent.com/developer/article/2245703 116 | - https://mp.weixin.qq.com/s/EC-YLm71YB4cMDoTjrdfyg 117 | - https://dev.to/pankajgupta221b/3-terminal-commands-to-increase-your-productivity-57dm?ref=dailydev 118 | 119 | #Newletters -------------------------------------------------------------------------------- /ob/novice-contribute-open-source.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 新手如何快速参与开源项目 3 | date: 2023/08/05 13:37:58 4 | categories: 5 | - OpenSource 6 | tags: 7 | - Pulsar 8 | --- 9 | ![image.png](https://s2.loli.net/2023/08/05/5mXrStCDVHNxF7f.png) 10 | 11 | 12 | # 前言 13 | 开源这件事情在软件开发领域一直是一个高频话题,我们工作中不管是使用到的工具还是第三方库都离不开开源的支持。 14 | 15 | 近期由于工作的原因,我需要经常和 `Apache Pulsar` 社区沟通,同时也会将日常碰到的问题反馈给社区,包括一些 `bug` ,一些我能修的也是顺带就提了一些 `PR`。 16 | 17 | 18 | 之前或多或少我也参与过其他的开源社区,但和现在的还是有些许的不同: 19 | - 以前我更多的是个人开源项目,偶尔也会有其他开发者向我的仓库贡献代码。 20 | - 也参与过其他个人作者或者是社区性质的项目,但流程上没有那么正规或者是`标准`。 21 | 22 | 简单来说就是以前就是小打小闹,`Pulsar` 毕竟是 `Apache` 社区的顶级项目,参与的整个流程要求也会比较复杂,当然学到的知识也会更多。 23 | 24 | ![image.png](https://s2.loli.net/2023/08/05/uSRKlvh8q4sTpWj.png) 25 | 26 | ![image.png](https://s2.loli.net/2023/08/05/DJm145yigIhEvwe.png) 27 | 这半年时间大大小小提了十几个 PR ,也逐渐捋清楚了一些上手的方法和套路,所以如果你也想参与开源,但苦于不知道如何入门,看完后希望对你有所帮助。 28 | 29 | # 为什么参与开源 30 | 首先还是来聊聊参与开源的好处,了解之后也许会让你有路转粉。 31 | 32 | 首先最明显的一点就是让你对贡献的这个项目更加深入的了解,我们常常都在面试的时候被问到对 XX 框架的熟悉程度,哪怕你在简历里写的天花乱坠也没有是这个项目 `Contributor` 更具有说服力。 33 | 34 | 其次是沟通交流能力也会得到锻炼,开源社区往往都是以 `github issue/PR`,或者是 `Mailing List` 的方式沟通交流,这样的沟通方式和我们常用的微信、QQ 这类及时通讯工具有着本质的区别。 35 | 36 | 往往需要我们有了冷静的思考加上清晰的描述才会将自己的观点发布出去,这样不自觉的就会养成自己的总结能力,这个能力对于`内容创意内容工作者`来说非常重要。 37 | 38 | 还有一个更明显的好处就是对个人的能力背书,大家常说的 `show me the code`,而 `GitHub` 就是最好的方式。 39 | 40 | 当你是某个知名开源项目的 `Contributor` 甚至是 `Committer/PMC` 就已经足够证明自己的能力了。 41 | 42 | # 如何参与 43 | 44 | 如何参与呢,其实也很简单,不外乎有以下几种方式(由易到难): 45 | - 一些 `typo` 类的修复。 46 | - 回答社区中用户的问题。 47 | - 使用过程中遇到 `bug`,直接反馈,有兴趣的话最好是自己能修复。 48 | - 修复现有 `issue` 列表中未解决的 `bug`。 49 | - 软件不具备自己需要的功能时提交 `feature` 提案并实现。 50 | 51 | 不管是哪种方式我的建议是在准备贡献之前都应该先看看官方提供的贡献指南,通常在官网就能查看。 52 | 53 | > 即便是最简单的修复 typo,因为越是专业的项目每个 PR 的合并都是严谨的,提前了解后可以避免犯一些基本错误从而影响积极性。 54 | 55 | 这里我以 `Pulsar` 为例: 56 | ![image.png](https://s2.loli.net/2023/08/05/8TGyjSXChsaoPc6.png) 57 | 官网有着详细的贡献指南,包括环境搭建、代码约定、`PR/git commit` 语义等各种规范。 58 | 59 | 这里我重点强调 PR 的语义,一个好的 PR 规范更容易引起社区成员的注意,毕竟我们每一次提交都需要 `Committer` 的同意才能合并。 60 | 61 | ![image.png](https://s2.loli.net/2023/08/05/gfbEBjc4dXVLPtw.png) 62 | 还是以 `Pulsar` 为例,在提交 PR 前一定得先看看这里的规范要求,不然很可能第一步就会吃瘪。 63 | 64 | 65 | ## 可能遇到的问题 66 | 下面讲讲贡献过程中可能会碰到的问题。 67 | 68 | 在上面讲到的难度排序中将修复个人 issue 排在了其他 issue 之前了,这是因为往往对自己提交的 bug 更熟悉,而社区其他人反馈的问题大概率会被老手认领。 69 | 70 | 加上自己也不熟悉,可能在自己研究复现的过程中就把自己劝退了。 71 | 72 | ### 认领 issue 73 | 这里还有个小技巧,当我们准备修复一个不是自己提交的 issue 时,最好是在评论区让 `Committer` 将这个任务分配给你,这样社区成员就不会做重复工作了。 74 | 75 | ![image.png](https://s2.loli.net/2023/08/05/KBh1HRd8EyziuQP.png) 76 | 类似于这样。 77 | 78 | 同时我们在查找可以修复的 `issue` 时也要注意这个 issue 有没有被认领以及是否有 PR 关联。 79 | 80 | ![image.png](https://s2.loli.net/2023/08/05/pOPybK7lmX1v8oU.png) 81 | 82 | 有时候 `issue` 并没有被指定但也有相关 `PR` 在处理该问题了,这时我们就可以过滤掉这个 `issue`。 83 | 84 | 85 | ### help want 86 | ![image.png](https://s2.loli.net/2023/08/05/QMwDlTWd3iFPU7c.png) 87 | 也可以找找带有 `help want` 标签的 `issue`,这类问题往往会相对简单,修复起来也更容易。 88 | 89 | ### 社区反馈较慢 90 | 91 | 还有一个比较常见的问题是自己提交的 issue 或者是 PR 迟迟没有人处理。 92 | 93 | 我们可以先看看这个 issue 对应的代码最近主要是哪些人在维护,这个在 IDE 中配合 `GitToolBox` 插件就很容易看出来。 94 | 95 | ![image.png](https://s2.loli.net/2023/08/05/ojErZnigXbqFvam.png) 96 | 后面的 ID 往往是 `PR` 号,我们可以通过这个 `PR` 找到对应的作者,然后尝试在 `issue` 评论区艾特对方。 97 | 98 | 如果依然没有回复,那我们也可以给开发组发送邮件。 99 | ![image.png](https://s2.loli.net/2023/08/05/SuT5Fb4wG2BnrV1.png) 100 | 如果还是没有回复,比如我这个😂 101 | 102 | 那也还有一个办法,就是尝试在社交媒体(GitHub 首页、技术群)上找到 Committer 的微信,直接私聊的方式让对方帮忙推进。 103 | ![image.png](https://s2.loli.net/2023/08/05/J8r6lBbhMgziFGv.png) 104 | 105 | 106 | 当然也有一些项目长期没有维护了,这种 PR 要做好心里准备,很有可能对方不会理你;这点在国内某个企业的开源项目中比较常见。 107 | # 总结 108 | 109 | 总的来说想要做好开源得有耐心和长期坚持,同时给自己带来的好处也是物超所值的,`Apache` 这类专业的社区我也才参与了半年,后续也会长期坚持下去,也希望哪天可以积累到成为 `Committer` 后再和大家分享。 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | #Pulsar #OpenSource -------------------------------------------------------------------------------- /ob/translate-pulsar-3.2.0.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 【译】Apache Pulsar 3.2.0 发布 3 | date: 2024/02/27 10:37:24 4 | categories: 5 | - 翻译 6 | tags: 7 | - Pulsar 8 | --- 9 | [原文链接](https://pulsar.apache.org/blog/2024/02/12/announcing-apache-pulsar-3-2/) 10 | 11 | Pulsar3.2.0 于 2024-02-05 发布,提供了一些新特性和修复了一些 bug ,共有 57 位开发者提交了 88 次 commit。 12 | 13 | 以下是一些关键特性介绍. 14 | 15 | # 速率限制 16 | 17 | 在 3.2 中对速率限制做了重构: 18 | [PIP-322 Pulsar Rate Limiting Refactoring](https://github.com/apache/pulsar/blob/master/pip/pip-322.md). 19 | 20 | 速率限制器是 Pulsar 服务质量(Qos)保证的重要渠道,主要解决了以下问题: 21 | - 速率限制器的高 CPU 负载 22 | - 大量的锁竞争会影响 `Netty IO` 线程,从而增加其他 topic 的发送延迟 23 | - 更好的代码封装 24 | 25 | # Topic 压缩时会删除 Null-key 消息 26 | 27 | Pulsar 支持 [Topic 压缩](https://pulsar.apache.org/docs/3.2.x/concepts-topic-compaction/),在 3.2 之前的版本中 topic 压缩时会保留 Null key 的消息。 28 | 29 | 从 3.2.0 开始将会修改默认行为,默认不会保留,这可以减少存储。如果想要恢复以前的策略可以在 broker.conf 中新增配置: 30 | ```properties 31 | topicCompactionRetainNullKey=true 32 | ``` 33 | 具体信息请参考:[PIP-318](https://github.com/apache/pulsar/blob/master/pip/pip-318.md). 34 | 35 | # WebSocket 的新特性 36 | - 支持多个 topic 消费:[PIP-307](https://github.com/apache/pulsar/blob/master/pip/pip_307.md). 37 | - 端对端加密 [PIP-290](https://github.com/apache/pulsar/blob/master/pip/pip-290.md). 38 | 39 | # CLI 的用户体验改进 40 | 41 | - [CLI 可以配置内存限制](https://github.com/apache/pulsar/pull/20663) 42 | - [允许通过正则或者是文件批量删除 topic](https://github.com/apache/pulsar/pull/21664) 43 | - [通过 `pulsar-admin clusters list` 可以打印当前使用的 cluster](https://github.com/apache/pulsar/pull/20614) 44 | 45 | # 构建系统的改进 46 | 3.2.0 中引入了PIP-326: [Bill of Materials(BOM)](https://github.com/apache/pulsar/blob/master/pip/pip-326.md) 来简化依赖管理。 47 | 48 | # 参与其中 49 | 50 | Pulsar 是发展最快的开源项目之一,被 Apache 基金会评选为参与度前五的项目,社区欢迎对开源、消息系统、streaming 感兴趣的参与贡献🎉,可以通过以下资源与社区保持联系: 51 | 52 | - 阅读贡献手册  [Apache Pulsar Contribution Guide](https://pulsar.apache.org/contribute/) 开始你的第一个贡献。 53 | - 访问 [Pulsar GitHub repository](https://github.com/apache/pulsar), 关注 [@apache_pulsar](https://twitter.com/apache_pulsar) 的 Twitter/X , 加入 slack 社区 [Pulsar community on Slack](https://apache-pulsar.slack.com/). 54 | 55 | 🔗参考链接: 56 | - https://github.com/apache/pulsar/blob/master/pip/pip-318.md 57 | - https://pulsar.apache.org/docs/3.2.x/concepts-topic-compaction/ 58 | - https://github.com/apache/pulsar/blob/master/pip/pip-322.md 59 | - https://github.com/apache/pulsar/blob/master/pip/pip_307.md 60 | - https://github.com/apache/pulsar/blob/master/pip/pip-290.md 61 | - https://github.com/apache/pulsar/pull/20663 62 | - https://github.com/apache/pulsar/pull/20614 63 | - https://github.com/apache/pulsar/blob/master/pip/pip-326.md 64 | - https://pulsar.apache.org/contribute/ -------------------------------------------------------------------------------- /ob/✅开源项目如何做集成测试.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: ✅开源项目如何做集成测试 3 | date: 2024/07/09 11:15:25 4 | categories: 5 | - OB 6 | tags: 7 | --- 8 | 9 | 之前有朋友问如何做集成测试,今天就重点讲讲这个集成测试在开源项目中是如何做的。 10 | 11 | 通常是需要对外提供服务的开源项目都需要集成测试: 12 | - Pulsar 13 | - Kafka 14 | - Dubbo 等 15 | 16 | 17 | 而只提供本地类库的项目通常只需要编写单元测试即可: 18 | - Hutool 19 | - Apache Commmon 20 | 21 | 22 | 以我接触到的服务型应用主要分为两类:一个是 Java 应用一个是 Golang 应用。 23 | 24 | # 🐳Golang 25 | Golang 因为工具链没有 Java 那么强大,所以大部分的集成测试的功能都是通过编写 Makefile 和 shell 脚本实现的。 26 | 27 | 还是以我熟悉的 Pulsar 的 go-client 为例,它在 GitHub 的集成测试是通过 GitHub action 触发的,定义如下: 28 | ![](https://s2.loli.net/2024/05/20/f2196pujo8m7KRe.png) 29 | 最终调用的是 Makefile 中的 test 命令,并且把需要测试的 Golang 版本传入进去。 30 | 31 | ![](https://s2.loli.net/2024/05/20/YpwtSHnLXqU1xQj.png) 32 | 33 | `Dockerfile`: 34 | ![](https://s2.loli.net/2024/05/20/1ySGWF46U7EC2rk.png) 35 | 36 | 37 | 这个镜像简单来说就是将 Pulsar 的镜像作为基础运行镜像(这里面包含了 Pulsar 的服务端),然后将这个 pulsar-client-go 的代码复制进去编译。 38 | 39 | 接着运行: 40 | ```shell 41 | cd /pulsar/pulsar-client-go && ./scripts/run-ci.sh 42 | ``` 43 | 也就是测试脚本。 44 | 45 | ![](https://s2.loli.net/2024/05/20/2Afmdu8ozRvH9FC.png) 46 | 47 | 测试脚本的逻辑也很简单: 48 | - 启动 pulsar 服务端 49 | - 运行测试代码 50 | 因为所有的测试代码里连接服务端的地址都是 `localhost`,所以可以直接连接。 51 | ![](https://s2.loli.net/2024/05/20/C1RHxTkuz25Mlj8.png) 52 | 53 | 通过这里的 [action](https://github.com/apache/pulsar-client-go/actions/runs/9014510238/job/24768797555) 日志可以跟踪所有的运行情况。 54 | 55 | # ☕Java 56 | 57 | Java 因为工具链强大,所以集成测试几乎不需要用 Makefile 和脚本配合执行。 58 | 59 | 还是以 Pulsar 为例,它的集成测试是需要模拟在本地启动一个服务端,然后再运行测试代码。 60 | 61 | > 这个的好处是任何一个单测都可以在本地直接运行,而 Go 的代码还需要先在本地启动一个服务端,测试起来比较麻烦。 62 | 63 | 64 | 来看看它是如何实现的,我以其中一个 [BrokerClientIntegrationTest](https://github.com/apache/pulsar/blob/631b13ad23d7e48c6e82d38f97c23d129062cb7c/pulsar-broker/src/test/java/org/apache/pulsar/client/impl/BrokerClientIntegrationTest.java#L117)为例: 65 | ![](https://s2.loli.net/2024/05/20/9PbioA3RQLMBy6J.png) 66 | ![](https://s2.loli.net/2024/05/20/blKePdxTUIkgRD3.png) 67 | 会在单测启动的时候先启动服务端。 68 | 69 | ![](https://s2.loli.net/2024/05/20/gzY3lyTGuEDUwZF.png) 70 | 71 | 最终会调用 PulsarTestContext 的 build 函数启动 broker(服务端),而执行单测也只需要使用 mvn 就可以自动触发这些单元测试。 72 | ![](https://s2.loli.net/2024/05/20/N15amZihWI73Qyw.png) 73 | 只是每一个单测都需要启停服务端,所以要把 Pulsar 的所有单测跑完通常需要 1~2 个小时。 74 | 75 | 所以这些集成测试本质上都是先要把测试环境构建出来,再跑对应的测试代码;后续也打算给 [cim](https://github.com/crossoverJie/cim) 加上集成测试实操一下。 -------------------------------------------------------------------------------- /ob/如何在平淡的工作中整理出有价值的简历.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 如何在平淡的工作中整理出有价值的简历 3 | date: 2024/12/10 15:07:10 4 | categories: 5 | - OB 6 | tags: 7 | --- 8 | 今天在 HackNews 上看到一个[帖子](https://news.ycombinator.com/item?id=41937892):你们是否很难回忆起在工作中做了哪些贡献? 9 | 10 | ![](https://s2.loli.net/2024/10/25/ItaHwFoWzG3ASZV.png) 11 | 12 | 我觉得挺多人都有类似的问题,通常都是在需要面试或者内部晋升的时候才开始思考这些问题,这时候在想的话难免会有遗漏。 13 | 14 | 结合帖子里的回答我整理了以下以下方法。 15 | 16 | 17 | 18 | ## 每日记录 19 | 20 | 好记性不如烂笔头,每日做好工作记录,周末再做一次汇总; 21 | 有部分公司应该就有类似的制度(日报、周报),但那是写给公司看的,这是写给自己整理的; 22 | 对自己来说只需要整理有用的内容,去掉那些工作中需要的废话。 23 | 24 | 这里推荐可以使用 Obsidian 的 daily 插件,每天点击一下日历就会生成一份文档,周末的时候再点击就会自动将周一到周五的内容进行汇总。 25 | ![](https://s2.loli.net/2024/10/25/vQVkq34zCsTMDZh.png) 26 | ![](https://s2.loli.net/2024/10/25/RvtwnAP4DQmbYWz.png) 27 | ![](https://s2.loli.net/2024/10/25/iCV5YKoyXOmxhsA.png) 28 | 29 | > 建议是在做之前就记录下来,而不是等到今天结束了再记录,此时要么不想记,要么已经忘了。 30 | > 周末汇总的时候可以提炼下,如果是要写到简历里应该怎么写? 31 | 32 | 33 | 同样的观点我在播客[代码之外](https://www.xiaoyuzhoufm.com/episode/65954bca6d045a7f5e7a9286)中也有听到,每天在下班的时候进行总结有以下的好处: 34 | - 更有仪式感,做完这个后一天的工作就结束了。 35 | - 可以学会将任务切分,提高对工作的掌控感。 36 | - 长期坚持下来可以增强对任务完成时间的准确预估。 37 | 38 | 39 | ## Git log 汇总 40 | 41 | 还可以使用 `git log --author='` 汇总你对提交记录,然后交给 AI 帮我们总结,这个感觉更适合做工作汇报。 42 | 43 | ```shell 44 | git log --pretty=format:"%h - %an, %ar : %s" --author='crossoverJie' | pbcopy 45 | ``` 46 | 在 macOS 中可以使用 `pbcopy` 命令将输出内容复制到粘贴板,然后我们只需要复制到 chatgpt 中就可以帮我们提炼总结了(但这里的前提是自己的每次提交都是有意义的备注) 47 | 48 | ```shell 49 | git log --author="your_username" --since="2024-01-01" --until="2024-12-31" 50 | ``` 51 | 也可以加上时间筛选,更加精确的统计。 52 | 53 | ## 定时更新简历 54 | 55 | 我个人是建议每个季度都更新一下自己的简历,看看有哪些新的东西可以写上去,这也是回顾自己这段时间工作的有效手段,毕竟简历就是要给人看的美化版自己。 56 | 57 | ![](https://s2.loli.net/2024/10/25/BpqVocTehZbUFD6.png) 58 |   59 | 这个帖子还有提到面试时不要害怕写自己不熟的技术栈,即便是只在自己的个人项目中使用过(看来国外和我们也类似) 60 | 61 | 这个我觉得得是面试情况而定,如果应聘的 1~3 年的初中级岗位,也不是大厂,那可以这么写,但对于业界都知道的一些大厂(比如阿里、字节)这些面试大概率不会只问表面问题,技术栈写的越多对自己也越没有好处。 62 | 63 | 本质上就是需要大家多总结,多参考。 64 | 65 | 参考链接: 66 | - https://news.ycombinator.com/item?id=41937892 67 | - https://www.xiaoyuzhoufm.com/episode/65954bca6d045a7f5e7a9286 -------------------------------------------------------------------------------- /ob/如何选择可以搞钱的技术栈.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 如何选择可以搞钱的技术栈 3 | date: 2024/11/26 22:50:58 4 | categories: 5 | - OB 6 | tags: 7 | --- 8 | # 前言 9 | 10 | 之前在公司主要负责可观测性和 Pulsar 消息队列相关的内容,最近系统比较稳定,只需要做日常运维,所以就抽出时间逐步在接触 OLAP 相关的技术栈。 11 | 12 | 我们用的是 [StarRocks](https://www.starrocks.io/),也是目前比较流行的 OLAP 数据库;在接触的这段时间以来,让我越发感觉到选对一个靠谱的技术方向的重要性。 13 | 14 | 15 | 16 | 这里以 [Pulsar](https://pulsar.apache.org/) 举例,Pulsar 也是 Apache 的顶级项目,有一定的技术门槛,同时也解决以往消息队列的一些问题(多租户、低延迟、存算分离等) 但如果把它作为一个商业产品的话,相对来说付费能力还不够强。 17 | 18 | 其实也能想得到,就单纯的消息中间件在市场上能讲的故事不多,可以将它融入到其他的生态里,比如数据处理、业务解耦等,但总归是配角,没有它虽然没那么优雅,但也可以玩。 19 | 20 | 同理 OpenTelemetry 也是类似的,它用于可观测性系统,主要是就是拿来排查问题的,对于企业来说也谈不上刚需。 21 | 22 | 他们两个都有一个共同的特点:小公司不需要(或者自己维护开源版,量小也不容易暴露问题),大公司选择单独的团队自己维护,市场蛋糕较小。 23 | 24 | 即便是部分中厂可能选择购买云服务,一般也会选择和自己现有技术栈配套的云厂商,比如已经用了大部分阿里云的产品,这种周边服务也会尽可能的在阿里云上选择替代方案,毕竟这样的风险更小,而且国内的产品大多都写还 ALL IN。 25 | 26 | > 可能更多的还是一些政企、金融等业务会选择这些开源产品的企业版,大部分因为需要私有化部署,不太方便直接使用公有云。 27 | 28 | 而更刚需的往往都是和数据相关的,比如数据库、MongoDB、ElasticSearch 、kubernetes 等,如果想要提升下非业务水平,倒是可以深入一下这些技术栈。 29 | 30 | # 举例 31 | 32 | 拿数据库来说,任何公司都需要,即便是小公司也不敢在生产环境自己维护数据库,一般也会购买云产品,或者是招一个 DBA。 33 | 34 | 同理还有云原生相关的基础技术栈,比如 kubernetes 以及围绕着 kubernetes 周边的生态。 35 | 36 | k8s 作为云原生的基础底座,只要涉及到上云就离不开它,不管小厂选择云服务还是大厂自己托管都得需要相关技能。 37 | 38 | 即便不是直接做 kubernetes 开发也得需要了解相关的知识,对自己理解整个系统是如何运转的很大的帮助。 39 | 40 | 41 | 除此之外还有也有个简单的方法:就是看看你们公司为哪些服务买单。 42 | 43 | 以我最近接触到的 StarRocks 为例,也是和数据处理相关的公司,他们在疫情期间成立的商业化公司,这几年非但没受到影响反而还在增长。 44 | 45 | ![](https://s2.loli.net/2024/10/10/26uepDnY1yPzBaV.png) 46 | 47 | > 看到他们的招聘还蛮活跃。 48 | 49 | 即便是厂商更倾向于选择云厂商的数据服务,StarRocks 这类原厂公司或多或少也会参与进去提供一些技术支持。 50 | 51 | 不过可能有人的第一反应是这些产品的技术门槛较高,上手比较困难,但其实这些往往都是自己给自己上的难度。 52 | 53 | 以我最近提交的一个 PR 来说: 54 | https://github.com/StarRocks/starrocks/pull/50926 55 | 56 | 我之前根本就没有接触过 OLAP 相关的内容,但只要有场景,可以在本地 debug 复现,任何问题都很好解决,即便是这是你不熟的技术栈。 57 | 58 | 比如 ES 也是 Java 写的,如果你们公司为它付费了,那不如多花时间研究一下,不一定是需要改它的核心逻辑,上层也有很多东西可以参与的。 59 | 60 | 而一旦我们对这些技术栈熟悉之后,今后在换工作时就有着其他人不具备的优势,甚至可以加入这些技术背后的商业公司。 61 | 62 | 而这些公司大部分都是满足开发者的喜好:比如远程办公、技术驱动等,大家不妨从现在就可以试试。 63 | 64 | 65 | # 总结 66 | 不管是哪种技术最终都是要转换为我们到手的收入,所以选择对收入更加敏感的技术栈还是很有必要的。 67 | 68 | 以上的内容主要是针对后端开发,当然这里并不包含想要做独立开发的技术栈,主要还是用于求职。 69 | 70 | 大家可以看看自己公司以及曾经的公司有对哪些技术付费,或者是一些都需要的刚需通用的技术栈,深入这些技能对搞钱或多或少都有好处。 -------------------------------------------------------------------------------- /ob/💢线上高延迟请求排查.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 💢线上高延迟请求排查 3 | date: 2024/10/29 18:21:42 4 | categories: 5 | - 问题排查 6 | tags: 7 | - Java 8 | --- 9 | 10 | 前几天排查了一个业务接口执行高延迟的问题,也挺有参考意义的,分享一下排查过程。 11 | 12 | 现象是业务反馈有一个接口业务逻辑其实很简单,但是调用一次耗时,如下图所示: 13 | ![](https://s2.loli.net/2024/10/16/Am9VkNZ5Ep4Uj6G.png) 14 | 15 | 16 | # 排查应用运行状态 17 | 首先第一步需要查看当时的应用运行状态,包含当时的日志、JVM 的各种监控等。 18 | 19 | 因为我们接入了 `OpenTelemetry`,所以 `trace` 和日志是可以关联起来的。 20 | > 点击链路系统旁边的日志按钮可以直接跳转。 21 | 22 | 可以通过 `trace_id` 查询到相关日志: 23 | ![](https://s2.loli.net/2024/10/16/W5ow6KpdCaOk2f7.png) 24 | 25 | 通过日志可以看出耗时大约在 4s 多一点,然后结合代码发现这两段日志分别是在进入一个核心业务方法之前和方法内打印的。 26 | 27 | ![](https://s2.loli.net/2024/10/16/XeqoaGPx8kEmSrD.png) 28 | 29 | 而第一行日志是在一个自定义限流器中打印的,这个限流器是使用 `Guava` 的 `RateLimiter`实现的。 30 | 31 | 我的第一反应是不是这个限流器当时限流了,从而导致阻塞了;但查看了当时的 QPS 发现完全低于限流器的配置,所以基本可以排除它的嫌疑了。 32 | 33 | ## JVM 监控 34 | ![](https://s2.loli.net/2024/10/16/f3H6VBFRpCN7Yza.png) 35 | 36 | ![](https://s2.loli.net/2024/10/16/zvKPyXuScQwmiYN.png) 37 | 38 | 之后我们查询当时的 JVM 监控发现当时的 GC 频繁,而堆内存也正好发生了一次回收,初步判断是 GC 导致的本次问题。 39 | 40 | 但为啥会导致频繁的 GC 呢,还需要继续排查。 41 | 42 | 43 | ## 内存排查 44 | 我们在应用诊断中集成了 [Pyroscope](https://github.com/grafana/pyroscope)的持续剖析,可以实时查看内存的占用情况。 45 | ![](https://s2.loli.net/2024/10/16/Ow5WksxJan9G8py.png) 46 | 47 | ![image.png](https://s2.loli.net/2024/10/16/CbPhVJ4mDyFxicX.png) 48 | 49 | 50 | 通过内存分析发现有大量的 JSON 序列化占用了大量的内存,同时还发现 Pod 已经被重启好几次了: 51 | ![image.png](https://s2.loli.net/2024/10/16/iKHCFodeVPM9A68.png) 52 | 53 | ![image.png](https://s2.loli.net/2024/10/16/31aTS7yqNCKlFJQ.png) 54 | 55 | 查看原因发现是 Pod OOM 导致的。 56 | 57 | 因此非常有可能是 GC 导致的,恰好那段时间发生了 GC 内存也有明显变化。 58 | 59 | ![](https://s2.loli.net/2024/10/16/f3H6VBFRpCN7Yza.png) 60 | 61 | ![](https://s2.loli.net/2024/10/16/zvKPyXuScQwmiYN.png) 62 | 63 | ![](https://s2.loli.net/2024/10/16/hsXUAZCIGY12gFk.png) 64 | 65 | 最后再通过 arthas 确认了 GC 非常频繁,可以确认目前的资源是是非常紧张的,咨询业务之后得知该应用本身占用的资源就比较大,没有太多优化空间,所以最终决定还是加配置。 66 | ![](https://s2.loli.net/2024/10/16/VGyrCAZgjx64wHP.png) 67 | ![image.png](https://s2.loli.net/2024/10/17/zIEjeMxvkgLomZ4.png) 68 | 还是提高硬件效率最高,目前运行半个月之后 Pod 内存表现稳定,没有出现一次 OOM 的异常。 69 | 70 | 71 | # 总结 72 | 虽然最后的处理的方式是简单粗暴的,但其中的过程还是有意义的,遇到不同的情况也有不同的处理方式。 73 | 74 | 比如在排查过程中发现内存消耗异常,通过内存分析发现代码可以优化,那就优化代码逻辑。 75 | 76 | 如果是堆内存占用不大,但是 Pod 还是 OOM 导致重启,那就要看看 JVM 的内存分配是否合理,应该多预留一些内存给堆外使用。 77 | 78 | 但这个过程需要有**完善的可观测系统的**支撑,比如日志、监控等,如果没有这些数据,再回头排查问题就会比较困难。 79 | 80 | 总之这个排查过程才是最主要的,大家还有什么排查问题的小 tips 也欢迎在评论区分享。 -------------------------------------------------------------------------------- /personal/1W-star-update.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 1.6W star 的 JCSprout 阅读体验大提升 3 | date: 2018/11/06 00:11:12 4 | categories: 5 | - Person 6 | - GitHub 7 | --- 8 | 9 | ![](https://i.loli.net/2019/05/08/5cd1c66de1a6d.jpg) 10 | 11 | --- 12 | 13 | 14 | 15 | 万万没想到 [JCSprout](https://github.com/crossoverJie/JCSprout) 截止目前居然有将近`1.6W star`。真的非常感谢各位大佬的支持。 16 | 17 | 18 | 19 | ![](https://i.loli.net/2019/05/08/5cd1c67179a40.jpg) 20 | 21 | 22 | 23 | 年初时创建这个 `repo` 原本只是想根据自己面试与被面试的经历记录一些核心知识点,结果却是越写越多。 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 在我自己宣传和其他技术大佬(包括阮大)的助攻之下连续两个月都在 `GitHub trending Java`片区的榜首。 32 | 33 | 34 | 35 | 甚至有一次还一跃到整个 `GitHub` 的第一,同时还有帮助一些同学拿到了大厂 `offer`。 36 | 37 | ![](https://i.loli.net/2019/05/08/5cd1c674953cd.jpg) 38 | 39 | 40 | 41 | 扯了这么多进入这次的正题。 42 | 43 | 之前有一朋友建议将文档以 `gitbook` 的形式查看,一直没有时间弄。直到有一天我看到了 [docsify](https://docsify.js.org/#/) 这个项目,瞬间被它的外观,阅读方式所吸引。于是抽了一晚上把所有的文章全部迁移过去。 44 | 45 | 46 | 47 | 现在打开 https://crossoverjie.top/JCSprout/ 即可看到全新的主页,大概长这样: 48 | 49 | 50 | 51 | ![](https://i.loli.net/2019/05/08/5cd1c679a9a01.jpg) 52 | 53 | 54 | 55 | --- 56 | 57 | ![](https://i.loli.net/2019/05/08/5cd1c67c0db08.jpg) 58 | 59 | 60 | 61 | 确实不管从颜值还是阅读方式来说都非常不错;希望新的界面能让更多的人看的进去学到点东西。 62 | 63 | 64 | 65 | > 同时也更新完善了其中的一些内容。比如有些写的早的内容其实并不完善,也优化的处理了。 66 | 67 | 68 | 69 | 同时欢迎更多朋友参与进来,不管是提新的点子、修改 `bug` 都是可以。 70 | 71 | 72 | 73 | 之前的文章也留了不少坑,包括 `cicada` 还有好几个 `bug` 待处理、推送的示例代码以及 `Kafka` 源码的后续更新。 74 | 75 | 76 | 77 | 突然有点像写长篇小说的感觉,还好没有多少人催更🤣。 78 | 79 | 不出意外本周会再更新一篇,请持续关注。 80 | 81 | 82 | 83 | **你的点赞与转发是最大的支持。** 84 | -------------------------------------------------------------------------------- /personal/1W-star.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: GitHub 1W star 成就达成! 3 | date: 2018/08/17 00:11:12 4 | categories: 5 | - Person 6 | - GitHub 7 | --- 8 | 9 | ![](https://i.loli.net/2019/05/08/5cd1d25b1f39e.jpg) 10 | 11 | ## 起因 12 | 13 | 感谢各位大佬的支持收获了人生第一个(很有可能也是唯一一个)`1W star` 项目。 14 | 15 | ![](https://i.loli.net/2019/05/08/5cd1d25f5280d.jpg) 16 | 17 | 从今年一月份创建项目至今 8 个月时间。 18 | 19 | 一共关闭了 27 个 `issue`,47 个 `RP`,总共有 11 位小伙伴参与维护。 20 | 21 | 神奇般的连续两个月上了 GitHub Java 热门榜首。 22 | 23 | 24 | 25 | ![](https://i.loli.net/2019/05/08/5cd1d260b9c1e.jpg) 26 | 27 | ![](https://i.loli.net/2019/05/08/5cd1d262c6582.jpg) 28 | 29 | 整个热度走势图也是一路向北: 30 | 31 | ![](https://i.loli.net/2019/05/08/5cd1d2638a052.jpg) 32 | 33 | 34 | 过程中也有许多朋友反馈得到了帮助,自己确实没想到能起到这么好的作用。 35 | 36 | ## 更名 37 | 38 | 趁这机会我想给项目重新换个名字,因为我发现做到现在这里面并不仅仅包含面试的内容。 39 | 40 | 我们也不应该只为了面试而使用该项目,里面所有的技术内容我认为都应该对实际开发起到帮助,让大家少走弯路。 41 | 42 | 所有我把项目重命名为: 43 | 44 | `JCSprout : Java Core Sprout:处于萌芽阶段的 Java 核心知识库。` 45 | 46 | 还创建了 logo。 47 | 48 | ![](https://i.loli.net/2019/05/08/5cd1d26412262.jpg) 49 | 50 | 整个技术道路非常漫长,这一点小成就只是一颗小萌芽,希望最后能生根发芽。 51 | 52 | ## 未来 53 | 54 | 今后我依然会持续维护,也希望更多的朋友参与进来。 55 | 56 | 还要把之前给自己挖的坑填好: 57 | 58 | - HTTP 59 | - SpringBoot 60 | - Tomcat 等 61 | 62 | 后期还会继续添加一些源码解析、最佳实践等内容。 63 | 64 | 还没有关注的朋友难道不想进来看看嘛?最新地址: 65 | 66 | [https://github.com/crossoverJie/JCSprout](https://github.com/crossoverJie/JCSprout) 67 | 68 | 69 | **你的点赞与转发是最大的支持。** 70 | -------------------------------------------------------------------------------- /pulsar/pulsar-repeat-consume.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 通过 Pulsar 源码彻底解决重复消费问题 3 | date: 2023/02/27 08:08:08 4 | categories: 5 | - Pulsar 6 | tags: 7 | - Consumer 8 | - Pulsar 9 | --- 10 | 11 | ![](https://s2.loli.net/2023/02/26/Oz94bQasM2Einok.png) 12 | 13 | # 背景 14 | 最近真是和 `Pulsar` 杠上了,业务团队反馈说是线上有个应用消息重复消费。 15 | 16 | ![](https://s2.loli.net/2023/02/26/c2eZuTPUvrlB1YF.png) 17 | 18 | 而且在测试环境是可以稳定复现的,根据经验来看一般能稳定复现的都比较好解决。 19 | 20 | 21 | 22 | # 定位问题 23 | 24 | 接着便是定位问题了,根据之前的经验让业务按照这几种情况先排查一下: 25 | ![](https://s2.loli.net/2023/02/26/IrvxGDQuaSt7AOE.png) 26 | 27 | 通过排查:1,2可以排除了。 28 | 1. 没有相关日志 29 | 2. 存在异常,但最外层也捕获了,所以不管有无异常都会 ACK。 30 | 31 | 第三个也在消费的入口和提交消息出计算了时间,最终发现都是在2s左右 ACK 的。 32 | 33 | 伪代码如下: 34 | 35 | ```java 36 | Consumer consumer = client.newConsumer() 37 | .subscriptionType(SubscriptionType.Shared) 38 | .enableRetry(true) 39 | .topic(topic) 40 | .ackTimeout(30, TimeUnit.SECONDS) 41 | .subscriptionName("my-sub") 42 | .messageListener(new MessageListener() { 43 | @SneakyThrows 44 | @Override 45 | public void received(Consumer consumer, Message msg) { 46 | log.info("msg_id{}",msg.getMessageId().toString()); 47 | TimeUnit.SECONDS.sleep(2); 48 | consumer.acknowledge(msg); 49 | } 50 | }) 51 | .subscribe(); 52 | ``` 53 | 54 | 那这就很奇怪了,因为代码里配置的 ackTimeout 是 30s,理论上来说是不会存在超时导致消息重发的。 55 | 56 | 为了排除是否是超时引起的,直接将业务代码注释掉了,等于是消息收到后立即就 ACK,经过测试发现这样确实就没有重复消费了。 57 | 58 | 59 | 为了再次确认是不是和 ackTimeout 有关,直接将 `.ackTimeout(30, TimeUnit.SECONDS)` 注释掉后测试,发现也没有重复消费了。 60 | 61 | # 确认原因 62 | 63 | 既然如此那一定是和这个配置有关了,但看代码确实没有超时,为了定位具体原因只有去看 client 的源码了。 64 | 65 | 66 | 67 | 这里简单梳理下消息的消费的流程: 68 | 1. 根据 `.receiverQueueSize(1000)` 的配置,默认情况下 broker 会直接给客户端推送 1000 条消息。 69 | 2. 客户端将这 1000 条消息保存到内部队列中。 70 | 3. 如果使用同步消费 `receive()` 时,本质上就是去 `take` 这个内部队列。 71 | 4. 如果是使用的是 `messageListener` 异步消费并配置 `ackTimeout`,每当从队列里获得一条消息后便会把这条消息加入 `UnAckedMessageTracker` 内部的一个时间轮中,定时检测顶部是否存在消息,如果存在则会触发重新投递。 72 | 4.1 加入时间轮后,`异步`调用我们自定义的事件,这个异步操作是提交到一个无界队列中由单个线程依次排队执行(这点是这次问题的关键) 73 | 5. 业务 ACK 的时候会从时间轮中删除消息,所以如果消息 ACK 的足够快,在第四步就不会获取到消息进行重新投递。 74 | 75 | ![](https://s2.loli.net/2023/02/26/2PuOadlU6oRqHVN.png) 76 | 77 | 整体流程如上图,代码细节如下图: 78 | ![](https://s2.loli.net/2023/02/26/jMOqBUe912cdEWg.png) 79 | 80 | 所以问题的根本原因就是写入时间轮(`UnAckedMessageTracker`)开始倒计时的线程和回调业务逻辑的不是同一个线程。 81 | 82 | 如果业务执行耗时,等到消息从那个单线程的无界队列中取出来的时候很有可能已经过了 ackTimeou 的时间,从而导致了超时重发。 83 | 84 | 也就是用户所理解的 `ackTimeout` 周期(应该进入回调时候开始计时)和 SDK 实现的不一致造成的。 85 | 86 | 87 | 之后我再次确认同样的代码换为同步消费是没有问题的,不会导致重复消费: 88 | 89 | ```java 90 | while (true) { 91 | Message msg = consumer.receive(); 92 | log.info( 93 | "consumer Message received: " + new String(msg.getData()) + msg.getMessageId().toString()); 94 | TimeUnit.SECONDS.sleep(2); 95 | consumer.acknowledge(msg); 96 | } 97 | ``` 98 | 99 | 查看代码后发现同步代码的获取消息和加入 `UnAckedMessageTracker` 时间轮是同步的,也就不会出现超时的问题。 100 | ![](https://s2.loli.net/2023/02/26/AUiDgXYO7QvINTF.png) 101 | 102 | # 总结 103 | 104 | 所以其实 是`messageListener` 异步消费的 ackTimeout 的语义是有问题的,需要将加入 `UnAckedMessageTracker` 处移动到回调函数中同步调用。 105 | 106 | 我查看了最新的 `2.11.x` 版本的代码依然没有修复,正准备提个 PR 切换到 master 时才发现已经有相关的 PR 了,只是还没有发版。 107 | 108 | 修复的背景和思路也是类似的,具体参考: 109 | 110 | https://github.com/apache/pulsar/pull/18911 111 | 112 | 其实业务中并不推荐使用 ackTimeout 这个配置了,不好预估时间从而导致超时,而且我相信大部分业务配置好 `ackTImeout` 后直到后续出问题的时候才想起来要改。 113 | 所以干脆一开始就不要使用。 114 | 115 | 在 go 版本的 SDK 中直接废弃掉了这个参数,推荐使用 nack API 替换。 116 | 117 | ![](https://s2.loli.net/2023/02/26/kQaZAcJi6WjNDXq.png) 118 | 119 | 120 | 121 | -------------------------------------------------------------------------------- /sbc4.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: sbc(四)应用限流 3 | date: 2017/08/11 18:13:22 4 | categories: 5 | - sbc 6 | tags: 7 | - Java 8 | - SpringBoot 9 | - SpringCloud 10 | - RateLimiter 11 | --- 12 | 13 | ![pexels-photo-306198.jpeg](https://i.loli.net/2017/08/11/598c8c87529b1.jpeg) 14 | 15 | 16 | # 前言 17 | 18 | > 在一个高并发系统中对流量的把控是非常重要的,当巨大的流量直接请求到我们的服务器上没多久就可能造成接口不可用,不处理的话甚至会造成整个应用不可用。 19 | 20 | 比如最近就有个这样的需求,我作为客户端要向`kafka`生产数据,而`kafka`的消费者则再源源不断的消费数据,并将消费的数据全部请求到`web服务器`,虽说做了负载(有4台`web服务器`)但业务数据的量也是巨大的,每秒钟可能有上万条数据产生。如果生产者直接生产数据的话极有可能把`web服务器`拖垮。 21 | 22 | 对此就必须要做限流处理,每秒钟生产一定限额的数据到`kafka`,这样就能极大程度的保证`web`的正常运转。 23 | 24 | **其实不管处理何种场景,本质都是降低流量保证应用的高可用。** 25 | 26 | 27 | # 常见算法 28 | 29 | 对于限流常见有两种算法: 30 | 31 | - 漏桶算法 32 | - 令牌桶算法 33 | 34 | 漏桶算法比较简单,就是将流量放入桶中,漏桶同时也按照一定的速率流出,如果流量过快的话就会溢出(`漏桶并不会提高流出速率`)。溢出的流量则直接丢弃。 35 | 36 | 如下图所示: 37 | 38 | ![漏桶算法,来自网络.png](https://i.loli.net/2017/08/11/598c905caa8cb.png) 39 | 40 | 41 | 42 | 这种做法简单粗暴。 43 | 44 | `漏桶算法`虽说简单,但却不能应对实际场景,比如突然暴增的流量。 45 | 46 | 这时就需要用到`令牌桶算法`: 47 | 48 | `令牌桶`会以一个恒定的速率向固定容量大小桶中放入令牌,当有流量来时则取走一个或多个令牌。当桶中没有令牌则将当前请求丢弃或阻塞。 49 | 50 | ![令牌桶算法-来自网络.gif](https://i.loli.net/2017/08/11/598c91f2a33af.gif) 51 | 52 | > 相比之下令牌桶可以应对一定的突发流量. 53 | 54 | # RateLimiter实现 55 | 56 | 对于令牌桶的代码实现,可以直接使用`Guava`包中的`RateLimiter`。 57 | 58 | ```java 59 | @Override 60 | public BaseResponse getUserByFeignBatch(@RequestBody UserReqVO userReqVO) { 61 | //调用远程服务 62 | OrderNoReqVO vo = new OrderNoReqVO() ; 63 | vo.setReqNo(userReqVO.getReqNo()); 64 | 65 | RateLimiter limiter = RateLimiter.create(2.0) ; 66 | //批量调用 67 | for (int i = 0 ;i< 10 ; i++){ 68 | double acquire = limiter.acquire(); 69 | logger.debug("获取令牌成功!,消耗=" + acquire); 70 | BaseResponse orderNo = orderServiceClient.getOrderNo(vo); 71 | logger.debug("远程返回:"+JSON.toJSONString(orderNo)); 72 | } 73 | 74 | UserRes userRes = new UserRes() ; 75 | userRes.setUserId(123); 76 | userRes.setUserName("张三"); 77 | 78 | userRes.setReqNo(userReqVO.getReqNo()); 79 | userRes.setCode(StatusEnum.SUCCESS.getCode()); 80 | userRes.setMessage("成功"); 81 | 82 | return userRes ; 83 | } 84 | ``` 85 | 86 | [详见此](https://github.com/crossoverJie/springboot-cloud/blob/master/sbc-user/user/src/main/java/com/crossoverJie/sbcuser/controller/UserController.java#L82:L105)。 87 | 88 | 调用结果如下: 89 | 90 | ![1.jpg](https://i.loli.net/2017/08/11/598c960f8983f.jpg) 91 | 92 | 代码可以看出以每秒向桶中放入两个令牌,请求一次消耗一个令牌。所以每秒钟只能发送两个请求。按照图中的时间来看也确实如此(返回值是获取此令牌所消耗的时间,差不多也是每500ms一个)。 93 | 94 | 使用`RateLimiter `有几个值得注意的地方: 95 | 96 | 允许`先消费,后付款`,意思就是它可以来一个请求的时候一次性取走几个或者是剩下所有的令牌甚至多取,但是后面的请求就得为上一次请求买单,它需要等待桶中的令牌补齐之后才能继续获取令牌。 97 | 98 | # 总结 99 | 100 | 针对于单个应用的限流 `RateLimiter` 够用了,如果是分布式环境可以借助 `Redis` 来完成。 101 | 102 | 最近也怼了一个,可以[参考](https://crossoverjie.top/2018/04/28/sbc/sbc7-Distributed-Limit/)。 103 | 104 | > 项目:[https://github.com/crossoverJie/springboot-cloud](https://github.com/crossoverJie/springboot-cloud) 105 | 106 | > 博客:[http://crossoverjie.top](http://crossoverjie.top)。 -------------------------------------------------------------------------------- /skill/1Kstar.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 1K star+ 的项目是如何炼成的? 3 | date: 2018/5/15 01:00:44 4 | categories: 5 | - 小技巧 6 | tags: 7 | - GitHub 8 | --- 9 | 10 | ![alarm-clock-art-background-1037993.jpg](https://i.loli.net/2018/05/14/5af935ddc27e1.jpg) 11 | 12 | # 前言 13 | 14 | 首先标题党一下,其实这篇文章主要是记录我的第二个过 `1K star` 的项目 [Java-Interview](https://github.com/crossoverJie/Java-Interview),顺便分享下其中的过程及经验。 15 | 16 | ![4.png](https://i.loli.net/2018/05/15/5af9b89e8bff8.png) 17 | 18 | 19 | # 需求选择 20 | 21 | ## [Java-Interview](https://github.com/crossoverJie/Java-Interview) 22 | 23 | 之所以要做这个项目主要是当时我正在面阿里的两个部门,非常幸运的是技术面都过了。其中的过程真是让我受益匪浅更是印象深刻,所以就想把期间的问题记录下来,加上自己的理解希望能对其他朋友起到帮助。 24 | 25 | 正好那段时间也是传说中的`金三银四`,所以无形中也叫顺势而为吧😏。 26 | 27 | 28 | 29 | ## [SSM](https://github.com/crossoverJie/SSM) 30 | 31 | 这个项目的历史就比较悠久了,我看了下第一次提交差不多是两年前。 32 | 33 | 从这个名字也可以看出当初还是一个刚入行没多久的小菜鸟,因为之前在学 Java 的时候真的走了很多冤枉路,所以从头开始记录到现在整个过程所学到的东西,踩过的坑。 34 | 35 | 由于是面向小白,入门简单,上手较快也取的了一定的关注。 36 | 37 | 38 | 其实从这两个项目可以看出选择一个**方向是很重要的**。 39 | 40 | 以及该项目解决了什么问题,长期的规划,受众是哪些都要考虑清楚(怎么有点像做产品😅,其实这就是你自己的产品)。 41 | 42 | 比如这两个项目的目标: 43 | 44 | - Java-Interview:持续更新面试问题,希望能让面试者知其然也知其所以然。 45 | - SSM:博主从小白到现在实际开发所遇到的问题记录,以及实战经验,现在逐渐会分享一些难点以及底层。受众大多是小白。 46 | 47 | 48 | # 文档很重要 49 | 50 | 既然项目做出来是给人用的,那文档就显得至关重要了。 51 | 52 | 就像日常和前端怼接口时,有一个标准的文档输出比在白板上折腾半天要高的多。 53 | 54 | ![C0DA2F29-C334-46BC-8BED-14CD6B6C5349.png](https://i.loli.net/2018/05/14/5af9491b5b119.png) 55 | 56 | 其实仔细观察 GitHub 上热门的项目,会发现他们的文档几乎都有一些共同结构: 57 | 58 | - 简单描述项目是干什么的。 59 | - 快速启动。 60 | - 最近更新。 61 | - Q/A 答疑。 62 | - 项目截图。 63 | 64 | 主要目的就是要简单易读,快速上手。 65 | 66 | 然后把一些复杂的如系统设计、开发指南等可以放到 wiki 中。 67 | 68 | > 切记不要什么东西都往 README.MD 中写,保持一个简洁的文档可以加分哦。 69 | 70 | 当然也可以在首页加入一些徽章如: 71 | 72 | ![3.png](https://i.loli.net/2018/05/15/5af9b66c55453.png) 73 | 74 | 也能起到一些积极作用。 75 | 76 | # 积极推荐 77 | 78 | 代码质量这个就不多说了,这应该是最基本的要求。 79 | 80 | 俗话说:酒香不怕巷子深。 81 | 82 | 但对于做开源项目来说就不太适应了,当你幸辛苦苦做了一个自认为很不错的项目,结果一年过去了都无人问津,这不免会有点打击积极性。 83 | 84 | 所以适当的自我推荐就很有必要了。 85 | 86 | ![7D819139-647F-43E3-9DB2-AB80A3E6BC7B.png](https://i.loli.net/2018/05/14/5af94d4c99929.png) 87 | 88 | ![1.jpg](https://i.loli.net/2018/05/14/5af94dd69c3ef.jpg) 89 | 90 | ![2.png](https://i.loli.net/2018/05/14/5af94ea82ab5d.png) 91 | 92 | 93 | 上图是我博客、项目的主要流量来源。 94 | 95 | 下面是我自身体验比较优质的推荐渠道: 96 | 97 | - [开发者头条](https://toutiao.io/u/257810/):由于截图的时候没有新发文章,之前那篇[秒杀架构实践](https://toutiao.io/posts/zavy6s)发了之后博客 80% 的流量都是从头条过来的,而且质量很高,不得不点个赞。 98 | - [并发编程网](http://ifeve.com/author/crossoverjie/): 并发编程网是由阿里大牛清英(买了那本《并发编程的艺术》就被圈粉了)创办的,其中的文章质量普遍较高(导致也会有一点写作门槛)。由于网站的流量也比较高,只要你的文章质量不错肯定会得到好处。 99 | - [掘金](https://juejin.im/user/576d4aaf7db2a20054ea4544):掘金这两年也比较火,是专门做开发者内容的,也是网站流量不错。 100 | - [开源中国](https://my.oschina.net/crossoverjie/blog):开源中国的博客也不错,自己也有代码托管,但我还是更喜欢用 GitHub,一般上了编辑推荐都会有不错的访问量。 101 | - [V2EX](https://www.v2ex.com/member/crossoverJie):大名鼎鼎的 V 站,其实受众较少,正因为如此也形成了独有的文化,因此也是我每天比逛(摸鱼)的网站,由于受众大多是开发者所以也能得到很多有用的反馈。 102 | - 大佬推荐:最快捷的方式其实就是口口相传,其中当然是大佬的效率最高。之前有个[纯洁的微笑](http://www.ityouknow.com/)、[程序猿DD](http://blog.didispace.com/) 都投过稿,也能带来不错的流量。 103 | - [简书](https://www.jianshu.com/u/e2d07947c112):本来不想推荐简书的(之前的事件以及现在鸡汤太多),但是流量还可以,现在就纯粹当做博客备份的工具了。 104 | 105 | 106 | > 坚持下来之后会发现:只要自己坚持、保证质量最后会形成自己的阅读圈子,到后面甚至会有其他朋友主动来找你分享,这些都是自我提升的过程。 107 | 108 | 109 | # 不忘初心 110 | 111 | 当初做的第一个开源项目就是 [SSM](https://github.com/crossoverJie/SSM),完全受够学习时找资料的痛苦,也得到了很多人的帮助,所以才有了该项目。 112 | 113 | 平时工作中或多或少都会用到开源项目,其实我们大部分人也写不出 Spring、Guava 这样的项目,只是再这过程中可以参与进去,收获也是非常丰富的。 114 | 115 | 两年前参与开源到现在有收到面试邀请、物质奖励这些都是正面积极的,可以鼓励我们接着做下去。 116 | 117 | 但最多的还是在这过程中结识了很多朋友,技术能力提升也很明显,这些都是保持自我可持续发展的必要条件。 -------------------------------------------------------------------------------- /skill/resume.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 什么样的简历不会被丢进回收站 3 | date: 2018/8/21 01:00:44 4 | categories: 5 | - 小技巧 6 | tags: 7 | - 简历 8 | --- 9 | 10 | 11 | ![](https://i.loli.net/2019/05/08/5cd1d24dd66d9.jpg) 12 | 13 | ## 前言 14 | 15 | 从去年校招开始到现在负责部门的面试,从我手上流走的简历多多少少也有上百封了。 16 | 17 | 同时最近秋招又要开始了,就想着把我这一年来筛选简历的经验分享给大家,多少还是能提高一些命中率。 18 | 19 | ## 突出优势 20 | 21 | **「简历」**自然是突出**简单**的好,相信大部分面试官都不是全职做面试工作;多数都是工作之余筛选简历。 22 | 23 | 就我的情况来说,每天都需要在工作中挤出一部分时间从 10 几份简历中挑选出比较靠谱的。 24 | 25 | 总共大概花费 5 分钟的时间,平均算下来差不多一份简历只有 30S。现在我终于相信当初语文老师说:“高考语文作文阅卷只有几十秒的时间”。 26 | 27 | 既然时间很短,就需要像写作文一样突出亮点。 28 | 29 | 30 | 31 | 32 | ### 博客、GitHub 33 | 34 | 举个例子,如果我在简历开头的个人介绍栏有看到个人博客、GitHub 链接等,一般都会点进去瞧瞧。 35 | 36 | 不知道是否是城市原因,我这里几乎 10 份简历中有两份贴有个人博客、1 份贴有 GitHub 链接。 37 | 38 | 哪怕里面的内容不是非常吸引人,但相比来说这样的简历会比其他多花上一些阅读时间,自然印象就更加深刻。 39 | 40 | 如果同时内容还非常不错,那就更是加分项了。 41 | 42 | 这就和上篇[《如何成为一位「不那么差」的程序员》](https://crossoverjie.top/2018/08/12/personal/how-to-be-developer/)不谋而合: 43 | 44 | ![](https://i.loli.net/2019/05/08/5cd1d24f5692a.jpg) 45 | 46 | ### 项目特色 47 | 48 | 通常简历的核心区域就是项目介绍。 49 | 50 | 这块我觉得可以适当减少项目具体的业务描述(自然不是不写),因为具体的项目了解一般会在简历评估通过后在面试中详聊。 51 | 52 | 所以这里我建议重点描述下自己解决了什么问题,优化了什么地方;比如: 53 | 54 | - 解决了 XX 服务请求超时的问题。 55 | - 优化了接口,将 QPS 由 1000 提升到了 5000 等等。 56 | 57 | 大概是这个方向的介绍。 58 | 59 | ## 需要避免 60 | 61 | 同时简历中也有许多需要注意的地方。 62 | 63 | 首先是少用精通的字眼,真的精通也就算了,不然一定会被仔细询问。 64 | 65 | 再一个是基本错误尽量少出,比如这样的: 66 | 67 | ![](https://i.loli.net/2019/05/08/5cd1d2503aa95.jpg) 68 | 69 | 这是当时难得看到贴了 GitHub 地址,名字居然还写错。不过我还是点进去看了,也是没啥营养。 70 | 71 | 72 | 甚至之前还收到一封简历,最近一次的工作经历竟然是公司 `CEO`,但一看工作年纪也才 25 岁工作三年而已。 73 | 74 | 这样的描述就非常尴尬,建议如果是创业者的身份没什么问题。但这么大一个 `title` 显然不适合拿来面试。 75 | 76 | 77 | 还有就是附件格式,建议最好使用 `PDF` 这样通用的格式在所有的操作系统打开都没问题。 78 | 79 | word 就非常容易出现变形,比如下面这样的我只能看到身高。 80 | 81 | ![](https://i.loli.net/2019/05/08/5cd1d2510311a.jpg) 82 | 83 | 84 | ## 1~3 年 85 | 86 | 由于现阶段我主要关注的是 1~5 年这个范围,通常也会分为两个阶段。 87 | 88 | 1~3 年多数是初中级岗位,这部分朋友我觉得应当把简历重心放在学习能力、积极主动性上面。 89 | 90 | 因为在项目经验并没有那么丰富,所有需要从其他方面体现出自己的优势。比如说扎实的基础。 91 | 92 | ## 3~5 年 93 | 94 | 3~5 年一般是中高级岗位,这时我觉得需要突出自己解决问题的能力、设计产出方案这些技能表现出来。 95 | 96 | 同时最好在简历中体现出并发、多线程、分布式相关的经验。 97 | 98 | 最怕的就是这个阶段给人的感觉还是 1~3 年的水平,但要的薪资可是 N 倍。 99 | 100 | 101 | 102 | ## 总结 103 | 104 | 最后推荐一个在线简历模板:http://cv.ftqq.com/ 。(不是广告,我个人也在用。easy 大佬看到了记得给我广告费。) 105 | 106 | 以上全是我个人主观感受,欢迎留言讨论。 107 | 108 | 109 | 110 | **你的点赞与转发是最大的支持。** 111 | -------------------------------------------------------------------------------- /tools/blog-toolbox.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 图床失效了?也许你应该试试这个工具 3 | date: 2019/05/08 01:50:36 4 | categories: 5 | - 工具 6 | tags: 7 | - Java 8 | - 策略模式 9 | --- 10 | 11 | ![](https://i.loli.net/2019/05/08/5cd1cbf638ec7.png) 12 | 13 | # 前言 14 | 15 | 经过几个小伙伴的提醒,发现个人博客中的许多图片都裂了无法访问;原因就不多说,既然出现问题就得要解决。 16 | 17 | 18 | 19 | ![](https://i.loli.net/2019/05/08/5cd1c6bfd8ff2.jpg) 20 | 21 | ![](https://i.loli.net/2019/05/08/5cd1cc4598afb.jpg) 22 | 23 | 原本我的处理方式非常简单粗暴:找到原有的图片重新下载下来上传到新的可用图床再把图片地址替换。 24 | 25 | 这样搞了一两篇之后我就绝望了。。。 26 | 27 | 之前为了代码能在公众号里也有好的阅读体验,所以能截图的我绝不贴代码,导致一篇文章多的得有十几张图片。 28 | 29 | 30 | 好在哪位大佬说过“以人肉XX为耻”,这种重复劳动力完全可自动化;于是便有了本次的这个工具。 31 | 32 | 它可以一行命令把你所有 `Markdown` 写的内容中的图片全部替换为新的图床。 33 | 34 | 运行效果如下: 35 | 36 | ![](https://i.loli.net/2019/05/08/5cd1cc7612c25.gif) 37 | 38 | ![](https://i.loli.net/2019/05/08/5cd1cd6062d2a.png) 39 | 40 | 41 | 42 | # 使用 43 | 44 | 可以直接在这个地址下载 jar 包运行:https://github.com/crossoverJie/blog.toolbox/releases/download/v0.0.1/blog.toolbox-0.0.1-SNAPSHOT.jar 45 | 46 | 当然也可以下载源码编译运行: 47 | 48 | ```shell 49 | git clone https://github.com/crossoverJie/blog.toolbox 50 | mvn clean package 51 | java -jar nows-0.0.1-SNAPSHOT.jar --app.downLoad.path=/xx/img /xx/xx/path 100 52 | ``` 53 | 54 | 看运行方式也知道,其实就是用 `SpringBoot` 写了一个工具用于批量下载文中出现的图片同时上传后完成替换。 55 | 56 | - 其中 `app.downLoad.path` 是用于将下载的图片保存到本地磁盘的目录。 57 | - `/xx/xx/path` 则是扫描 `.md` 文件的目录,会递归扫描所有出所有文件。 58 | - 100 则是需要替换文件的数量,默认是按照文件修改时间排序。 59 | 60 | 如果自己的图片较多的话还是有几个坑需要注意下。 61 | 62 | 63 | ## 线程数量 64 | 65 | 默认是启动了两个线程去遍历文件、上传下载图片、更新文本等内容,其中的网络 IO 其实挺耗时的,所以其实可以适当的多开些线程来提高任务的执行效率。 66 | 67 | 但线程过多也许会触发图床的保护机制,同时也和自己电脑配置有关,这个得结合实际情况考虑了。 68 | 69 | 所以可以通过 `--app.thread=6` 这样的参数来调整线程数量。 70 | 71 | ## 图床限制 72 | 73 | 这个是图片过多一定是大概率出现的,上传请求的频次过高很容易被限流封 IP。 74 | 75 | ```json 76 | {"code":"error","msg":"Upload file count limit. Time left 1027 second."} 77 | ``` 78 | 79 | 目前来看是封 IP 居多,所以可以通过走代理、换网络的方式来解决。 80 | 81 | 当然如果是自搭图床可以无视。 82 | 83 | 84 | ## 重试 85 | 86 | 由于我使用的是免费图床,上传过程中偶尔也会出现上传失败的情况,因此默认是有 5 次重试机制的;如果五次都失败了那么大概率是 IP 被封了。 87 | 88 | > 即便是 ip 被封后只要换了新的 ip 重新执行程序它会自动过滤掉已经替换的图片,不会再做无用功,这点可以放心。 89 | 90 | ## 图片保存 91 | 92 | ![](https://i.loli.net/2019/05/08/5cd1d002b6cff.jpg) 93 | 94 | 默认情况下,下载的图片会保存在本地,我也建议借此机会自己本地都缓存一份,同时名字还和文中的名字一样,避免今后图床彻底挂掉后连恢复的机会都没有。 95 | 96 | 97 | # 总结 98 | 99 | 这个程序的代码就没怎么讲了,确实也挺简单,感兴趣的可以自己下来看看。 100 | 101 | 目前功能也很单一,自用完全够了;看后续大家是否还有其他需求再逐渐完善吧,比如: 102 | 103 | - 图床上传失败自动切换到可用图床。 104 | - 整体处理效率提升。 105 | - 任务执行过程中更好的进度展现等。 106 | 107 | 108 | 再次贴一下源码地址: 109 | 110 | [https://github.com/crossoverJie/blog.toolbox](https://github.com/crossoverJie/blog.toolbox) 111 | 112 | **你的点赞与分享是对我最大的支持** 113 | -------------------------------------------------------------------------------- /translation/hidden-gems-goland.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 【译】Goland 中的隐藏宝石 3 | date: 2022/07/28 08:03:13 4 | categories: 5 | - 翻译 6 | tags: 7 | - IDE 8 | --- 9 | 10 | 11 | **[原文链接](https://blog.jetbrains.com/go/2022/07/21/hidden-gems-in-goland/)** 12 | 13 | 14 | ![](https://tva1.sinaimg.cn/large/e6c9d24ely1h4lrsooqqjj212w0k6t9k.jpg) 15 | 16 | 17 | 在日常使用 `Goland` 时,团队收集了一些可以帮助我们专注于创造的同时减少重复工作的小技巧。 18 | 如果你是在 `IDEA` 中使用的 `Go` 插件,或者其他 `IntelliJ` 的产品,同样也有这些特性。 19 | 20 | 21 | 22 | # 行排序 23 | 24 | 当你在查看文本文件时,行排序非常有用;按照字母排序后能够帮我们更好的阅读,同时也容易找到重复的行。 25 | 26 | 在菜单栏中使用 `Edit | Sort Lines or Edit | Reverse Lines`可以帮我们快速的对选中的代码或者是整个文件进行排序;或者也可以使用快速命令执行这个操作。 27 | 28 | ![](https://lh4.googleusercontent.com/G6g_eIinHwfZchGFPW9cBWYqYzrLuDTQYFafJZ0U0XlibbgANGVZwfgu7UM7bdN1Kr5tiPxk1ELV5F6sgQILyJKyDiziwUGqBOZxUWugfxNvZ9kw4KQBbl9zv-Z4oj8Uxru3Y12glEkhvWAqXxy0R-Q) 29 | 30 | # 打开对比窗口 31 | 32 | 打开一个对比窗口可以帮助我们对比任何文件、文件夹、文本;举个例子,将复制的内容粘贴到对比窗口中,IDE 会类似于版本控制系统那样展示两者的差异。 33 | 34 | 当然也可以用快速指令打开对比窗口(double shift)。 35 | 36 | ![](https://lh4.googleusercontent.com/2GtGBX33TZw7WEyVgSYwYcRozVp4AYp8xNYUp4fXtjWXiwolR5ikJdf-AoROpJw1A2HKyolrLR5HAdYUYWbIgJydX01FBOlUQ54BMHh7KS9Jda1Slc0QQp_N-uGwYsBBKAr-yhtsiVWTNrSB6PpYeIA) 37 | 38 | 此外你也可以在 IDE 编辑器的任何地方右键鼠标选择与当前粘贴板数据进行对比。 39 | 40 | > 这个功能很棒,可以替换掉以前大部分用 BeyondCompare 的场景了。 41 | 42 | # 暂存文件 43 | 44 | 有时候你需要一个随意的地方来编写一段文本,与当前工作相关的一些记录,也或是与当前项目上下文无关的草稿代码;这时候就需要用到暂存文件了。 45 | 46 | 暂存文件可不只是简单的笔记,它支持语法高亮、代码提示以及所有和这个文件类型相关的特性。 47 | 48 | 暂存文件与当前项目无关,你可以在任意项目中访问到这些文件,这样你就不需要离开 IDE 到其他地方来保存这些文件了。 49 | 50 | 可以在菜单栏中新建暂存文件`File | New | Scratch File or`,也可以使用快捷键 `⇧ ⌘ N`. 51 | 52 | ![](https://lh4.googleusercontent.com/d-HxnmVYaZOJq8mqJzCMagroGVpg6i7E2VF2j44MhGsqluWKRXENxgZI4sy8pLNaYex6hxSD9Yg0hNM06PgKvKjifGNYYfbA21C4mCiQAN0GctH2SK2fW9DFg1boZ3G2gZyradsaGVH08clG96s1KnY) 53 | 54 | 55 | > 通常使用这个功能来存放和运行一些测试或者是实例代码。 56 | 57 | # 多行光标 58 | 59 | 多行光标可以让你快速在多个地方同时修改代码,同时它也支持代码提示以及实时模板。 60 | 61 | 开启多行光标可以双击 `⌥/Ctrl` 后不要释放,然后点击上下箭头键。使用 `Escape` 键可以退出多行光标。 62 | 63 | ![](https://lh3.googleusercontent.com/Zb_1_CiZAP0_6rvAKurH-LsP3OOXqUufkLeeOTWtsCj2EtHAgPZ7sJq3_39oLwwT8bL8gH1eLynMLCQoBI73pUi5STUozXcCOBFry4lGLI-XVEAQYSrQ-opyFv1S_HKt56jYwDAimcFWskDbPpp85nQ) 64 | 65 | > 这个在批量修改代码时非常有用。 66 | 67 | # 批量折叠和展开 68 | 69 | 在阅读复杂长篇代码的过程中有时候很难弄懂代码结构,即便是代码是我们自己写的。 70 | 71 | 这也容易解决,批量折叠和展开可以快速帮我们浏览代码,快捷键是:macOS:`⇧⌘- /⇧⌘+`,Windows/Linux: `Ctrl+Shift+NumPad + / Ctrl+Shift+NumPad`。 72 | 73 | IDE 可以帮我们折叠/展开选中的代码,如果没有选中则是处理整个文件。 74 | 75 | 也可以使用 macOS:` ⌥⌘- / ⌥⌘+`, Windows/Linux:`Ctrl+Alt+NumPad + / Ctrl+Alt+NumPad` 来递归的处理代码,IDE 将会折叠/展开当前代码片段或者是他们包含的片段。 76 | 77 | ![](https://lh5.googleusercontent.com/cYtEgj2G98zshGwM-1a91f6_kqP1ZjLdWA_yQOCsXOo_0KQC4O9HL1Lphs-vdN71kiD_XjZ_Rh5oDo8zhuh9u7KuSacMFqfv6U1F0kXd8zJT3uF3f0GkZgu1P-OgAPGrG77ByWn5UmcK-uIdZ0Iahqo) 78 | 79 | # 最近文件 80 | 81 | 最近文件可以帮助我们快速跳转到最近经常打开的文件,当我们使用 macOS:`⌘+E` Windows/Linux:`Ctrl + E` 打开最近文件对话框的时,再使用`⌘+E`可以再次过滤只显示已经修改过的文件,这样可以帮我们更精准的查找。 82 | 83 | ![](https://lh5.googleusercontent.com/dfCbbr1RJYJGM12VmuNf7ebgvi01W3yseLvHLELhaMSyTy_MK2N3VmgXxJqcgJ3NVlYzsX9PV3_qiUA9cy_T8_Z5HGY9FDYyn6AwT9Xk6wTieDHl89hKf0JsCeV3XNZEgPcB9TgjbM8CH4o12RyRhfQ) 84 | 85 | 这些特性可能有些并不常用,一旦用上一次解决问题后会发现 `IntelliJ` 的 `IDE` 功能非常强大,如果你还发现了一些其他有用的特性请在留言区分享。 -------------------------------------------------------------------------------- /translation/how-to-use-git-efficiently.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 【译】如何高效的使用 Git 3 | date: 2018/09/07 02:02:13 4 | categories: 5 | - 翻译 6 | tags: 7 | - Git 8 | --- 9 | 10 | 11 | **[原文链接](https://medium.freecodecamp.org/how-to-use-git-efficiently-54320a236369)** 12 | 13 | ![](https://i.loli.net/2019/05/08/5cd1d1659a42a.jpg) 14 | 15 | > 代码昨天还是运行好好的今天就不行了。 16 | 17 | > 代码被删了。 18 | 19 | > 突然出现了一个奇怪的 bug,但是没人知道怎么回事。 20 | 21 | 22 | 如果你出现过上面的任何一种情况,那本篇文章就是为你准备的。 23 | 24 | 除了知道 `git add`, `git commit` , `git push` 之外,Git 中还需要其他重要的技术需要掌握。长远来看对我们是有帮助的。这里我将向你展示 Git 的最佳实践。 25 | 26 | 27 | 28 | 29 | # Git 工作流 30 | 31 | 当有多个开发者同时涉及到一个项目时那么就非常有必要正确使用 Git 工作流。 32 | 33 | 这里我将介绍一种工作流,它在一个多人大型项目中将非常有用。 34 | 35 | ![](https://i.loli.net/2019/05/08/5cd1d16785c42.jpg) 36 | 37 | 38 | # 前言 39 | 40 | 突然有一天,你成为了一个项目的技术 Leader 并计划做出下一个 Facebook。在这个项目中你有三个开发人员。 41 | 42 | 1. Alice:一个开发小白。 43 | 2. Bob:拥有一年工作经验,了解基本开发。 44 | 3. John:三年开发经验,熟练开发技能。 45 | 4. 你:该项目的技术负责人。 46 | 47 | # Git 开发流程 48 | 49 | ## Master 分支 50 | 51 | 1. Master 分支应该始终和生产环境保持一致。 52 | 2. 由于 master 和生产代码是一致的,所以没有人包括技术负责人能在 master 上直接开发。 53 | 3. 真正的开发代码应当写在其他分支上。 54 | 55 | ## Release(发布) 分支 56 | 57 | 1. 当项目开始时,第一件事情就是创建发布分支。发布分支是基于 master 分支创建而来。 58 | 2. 所有与本项目相关的代码都在发布分支中,这个分支也是一个以 `release/` 开头的普通分支。 59 | 3. 比如这次的发布分支名为 `release/fb`。 60 | 4. 可能有多个项目都基于同一份代码运行,因此对于每一个项目来说都需要创建一个独立的发布分支。假设现在还有一个项目正在并行运行,那就得为这个项目创建一个单独的发布分支比如 `release/messenger`。 61 | 5. 需要单独的发布分支的原因是:多个并行项目是基于同一份代码运行的,但是项目之间不能有冲突。 62 | 63 | ## Feature(功能分支) branch 64 | 65 | 1. 对于应用中的每一个功能都应该创建一个独立的功能分支,这会确保这些功能能被单独构建。 66 | 2. 功能分支也和其他分支一样,只是以 `feature/` 开头。 67 | 3. 现在作为技术 Leader,你要求 Alice 去做 Facebook 的登录页面。因此他创建了一个新的功能分支。把他命名为 `feature/login`。Alice 将会在这个分支上编写所有的登录代码。 68 | 4. 这个功能分支通常是基于 Release(发布) 分支 创建而来。 69 | 5. Bob 的任务为创建添加好友页面,因此他创建了一个名为 `feature/friendrequest` 的功能分支。 70 | 6. John 则被安排构建消息流,因此创建了一个 `feature/newsfeed` 的功能分支。 71 | 7. 所有的开发人员都在自己的分支上进行开发,目前为止都很正常。 72 | 8. 现在当 Alice 完成了他的登录开发,他需要将他的功能分支 `feature/login` 发送给 Release(发布) 分支。这个过程是通过发起一个 `pull request` 完成的。 73 | 74 | 75 | ## Pull request 76 | 77 | 首先 `pull request` 不能和 `git pull` 搞混了。 78 | 79 | 开发人员不能直接向 Release(发布) 分支推送代码,技术 Leader 需要在功能分支合并到 Release(发布) 分支之前做好代码审查。这也是通过 `pull request` 完成的。 80 | 81 | Alice 能够按照如下 GitHub 方式提交 `pull request`。 82 | 83 | ![](https://i.loli.net/2019/05/08/5cd1d16833c7d.jpg) 84 | 85 | 在分支名字的旁边有一个 “New pull request” 按钮,点击之后将会显示如下界面: 86 | 87 | ![](https://i.loli.net/2019/05/08/5cd1d168da700.jpg) 88 | 89 | - 比较分支是 Alice 的功能分支 `feature/login`。 90 | - base 分支则应该是发布分支 `release/fb`。 91 | 92 | 点击之后 Alice 需要为这个 `pull request` 输入名称和描述,最后再点击 “Create Pull Request” 按钮。 93 | 94 | 同时 Alice 需要为这个 `pull request` 指定一个 reviewer。作为技术 Leader 的你被选为本次 `pull request` 的 reviewer。 95 | 96 | 你完成代码审查之后就需要把这个功能分支合并到 Release(发布) 分支。 97 | 98 | 现在你已经把 `feature/login` 分支合并到 `release/fb`,并且 Alice 非常高兴他的代码被合并了。 99 | 100 | ## 代码冲突 😠 101 | 102 | 1. Bob 完成了他的编码工作,同时向 `release/fb` 分支发起了一个 `pull request`。 103 | 2. 因为发布分支已经合并了登录的代码,这时代码冲突发生了。解决冲突和合并代码是 reviewer 的责任。在这样的情况下,作为技术 Leader 就需要解决冲突和合并代码了。 104 | 3. 现在 John 也已经完成了他的开发,同时也想把代码合并到发布分支。但 John 非常擅长于解决代码冲突。他将 `release/fb` 上最新的代码合并到他自己的功能分支 `feature/newsfeed` (通过 git pull 或 git merge 命令)。同时他解决了所有存在的冲突,现在 `feature/newsfeed` 已经有了所有发布分支 `release/fb` 的代码。 105 | 4. 最后 John 创建了一个 `pull request`,由于 John 已经解决了所有问题,所以本次 `pull request` 不会再有冲突了。 106 | 107 | 因此通常有两种方式来解决代码冲突: 108 | 109 | - `pull request` 的 reviewer 需要解决所有的代码冲突。 110 | - 开发人员需要确保将发布分支的最新代码合并到功能分支,并且解决所有的冲突。 111 | 112 | 113 | # 还是 Master 分支 114 | 115 | 116 | 一旦项目完成,发布分支的代码需要合并回 master 分支,同时需要发布到生产环境。 117 | 118 | 因此生产环境中的代码总是和 master 分支保持一致。同时对于今后的任何项目来说都是要确保 master 代码是最新的。 119 | 120 | 121 | 122 | 123 | > 我们现在团队就是按照这样的方式进行开发,确实可以尽可能的减少代码管理上的问题。 124 | 125 | 126 | # 题外话 127 | 128 | 像之前那篇[《如何成为一位「不那么差」的程序员》](https://crossoverjie.top/2018/08/12/personal/how-to-be-developer/#English-%E6%8C%BA%E9%87%8D%E8%A6%81)说的那样,建议大家都多看看国外的优质博客。 129 | 130 | 甚至尝试和作者交流,经过沟通原作者也会在原文中贴上我的翻译链接。大家互惠互利使好的文章转播的更广。 131 | 132 | ![](https://i.loli.net/2019/05/08/5cd1d16a1159d.jpg) 133 | 134 | ![](https://i.loli.net/2019/05/08/5cd1d16b0f8c8.jpg) 135 | 136 | 137 | **你的点赞与转发是最大的支持。** 138 | -------------------------------------------------------------------------------- /translation/translation-What Is The Best Programming Language to Start.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 【译】对于初学者什么是最好的编程语言? 3 | date: 2018/04/12 01:02:13 4 | categories: 5 | - 翻译 6 | --- 7 | 8 | 9 | ### [原文链接](https://hackernoon.com/what-is-the-best-programming-language-to-start-8ca8fb5e9a60) 10 | 11 | Python?Java?Ruby?JavaScript?有非常多的选择。选择一种编程语言开始你的编码之旅不应该是一件艰巨的任务。 12 | 13 | ![](https://i.loli.net/2019/05/08/5cd1d7b300f5c.jpg) 14 | 15 | 事实上:你将要学习的语言并不是特别重要,更重要的是学习编程的理念。对于任何编程语言来说知识的可传递性都是至关重要的。 16 | 17 | 我学习的第一门语言是 Java,学习了循环,while 循环,条件,函数,面向对象编程和许多编程理念。 18 | 19 | 然而,选择一门能在编程领域轻松找到工作的语言是更好的选择。对于初学者来说,我这里有一份列表推荐给你: 20 | 21 | 22 | 23 | ### Python 24 | 25 | Python 在美国大学里是最受欢迎的入门型语言。 26 | 27 | 就像 JavaScript 一样,Python 也非常灵活,现在被用于构建生物信息学的 web 应用。我强烈推荐你学习 Python,它是很棒的入门选择。 28 | 29 | ### Java 30 | 31 | Java 是企业环境中使用最多的语言,根据 TIOBE 统计 Java 长年占据编程语言榜首。同时 Java 是强类型地静态语言,可以更容易地去描述一些编程理念。 32 | 33 | Java 作为最常使用的语言,你可以很轻松地在这段编程之旅中找到 Java 的相关课程和指南来获得帮助。你还可以使用 Java 构建服务端应用、Android APP 等应用程序。 34 | 35 | ### Ruby 36 | 37 | Ruby 是我最喜欢的编程语言,它编写简单,容易理解并且使用顺手。 38 | 39 | 就像 JavaScript 一样,它学起来简单但是不易掌握。Ruby 在很多公司中被广泛应用,比如 Airbnb, EBANX, Shopify, Twitter, GitHub 等等。它还有一个超赞的 7*24 小时的在线社区随时提供帮助。 40 | Ruby 以 Ruby on Rails 框架著称,它可以帮你很轻松的构建整个 web 应用。 41 | 42 | 43 | 44 | ### JavaScript 45 | 46 | JavaScript 是我用过的最灵活的语言之一。 47 | 48 | 你能用它构建控制台程序,桌面软件,手机 APP,前端开发,后端开发等等。它是一个很不错的编程语言,简单易学但难以掌握。 49 | 50 | 我建议你学习并掌握 JavaScript ,但不是作为第一门语言。 51 | 52 | 对于初学者来说 JavaScript 很难调试并且不容易学习编程理念比如异步,原型,面向对象等等。 53 | 54 | ### 不要纠结语言 55 | 56 | 你需要通过选择一门语言来学习编程理念,当你学完之后你将花费较小的学习曲线来学习任何其他的语言。 57 | 58 | 59 | 如果你想要学习如何学习一门新语言的话,可以阅读我的文章 “[How to Learn a New Programming Language or Framework](https://hackernoon.com/what-is-the-best-programming-language-to-start-8ca8fb5e9a60)”,将会非常有用。 60 | 61 | 62 | -------------------------------------------------------------------------------- /troubleshoot/cpu-percent-100-02.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 又一次生产 CPU 高负载排查实践 3 | date: 2019/06/18 08:15:36 4 | categories: 5 | - 问题排查 6 | - Java 进阶 7 | tags: 8 | - Java 9 | - Thread 10 | --- 11 | 12 | ![](https://i.loli.net/2019/06/18/5d07c34d2973a58018.jpg) 13 | 14 | # 前言 15 | 16 | 前几日早上打开邮箱收到一封监控报警邮件:某某 ip 服务器 CPU 负载较高,请研发尽快排查解决,发送时间正好是凌晨。 17 | 18 | 其实早在去年我也处理过类似的问题,并记录下来:[《一次生产 CPU 100% 排查优化实践》](https://crossoverjie.top/2018/12/17/troubleshoot/cpu-percent-100/) 19 | 20 | 不过本次问题产生的原因却和上次不太一样,大家可以接着往下看。 21 | 22 | 23 | 24 | # 问题分析 25 | 26 | 收到邮件后我马上登陆那台服务器,看了下案发现场还在(负载依然很高)。 27 | 28 | 于是我便利用这类问题的排查套路定位一遍。 29 | 30 | --- 31 | 32 | 首先利用 `top -c` 将系统资源使用情况实时显示出来 (`-c` 参数可以完整显示命令)。 33 | 34 | 接着输入`大写 P` 将应用按照 `CPU` 使用率排序,第一个就是使用率最高的程序。 35 | 36 | 果不其然就是我们的一个 `Java` 应用。 37 | 38 | 这个应用简单来说就是定时跑一些报表使的,每天凌晨会触发任务调度,正常情况下几个小时就会运行完毕。 39 | 40 | --- 41 | 42 | 常规操作第二步自然是得知道这个应用中最耗 `CPU` 的线程到底再干嘛。 43 | 44 | 利用 `top -Hp pid` 然后输入 `P` 依然可以按照 `CPU` 使用率将线程排序。 45 | 46 | 这时我们只需要记住线程的 ID 将其转换为 16 进制存储起来,通过 `jstack pid >pid.log` 生成日志文件,利用刚才保存的 16 进制进程 `ID` 去这个线程快照中搜索即可知道消耗 `CPU` 的线程在干啥了。 47 | 48 | 如果你嫌麻烦,我也强烈推荐阿里开源的问题定位神器 `arthas` 来定位问题。 49 | 50 | 比如上述操作便可精简为一个命令 `thread -n 3` 即可将最忙碌的三个线程快照打印出来,非常高效。 51 | 52 | > 更多关于 arthas 使用教程请参考[官方文档](https://alibaba.github.io/)。 53 | 54 | 由于之前忘记截图了,这里我直接得出结论吧: 55 | 56 | 最忙绿的线程是一个 `GC` 线程,也就意味着它在忙着做垃圾回收。 57 | 58 | ## GC 查看 59 | 60 | 排查到这里,有经验的老司机一定会想到:多半是应用内存使用有问题导致的。 61 | 62 | 于是我通过 `jstat -gcutil pid 200 50` 将内存使用、gc 回收状况打印出来(每隔 200ms 打印 50次)。 63 | 64 | ![](https://i.loli.net/2019/06/18/5d07c3505157663161.jpg) 65 | 66 | 从图中可以得到以下几个信息: 67 | 68 | - `Eden` 区和 `old` 区都快占满了,可见内存回收是有问题的。 69 | - `fgc` 回收频次很高,10s 之内发生了 8 次回收(`(866493-866485)/ (200 *5)`)。 70 | - 持续的时间较长,fgc 已经发生了 8W 多次。 71 | 72 | ## 内存分析 73 | 74 | 既然是初步定位是内存问题,所以还是得拿一份内存快照分析才能最终定位到问题。 75 | 76 | 通过命令 `jmap -dump:live,format=b,file=dump.hprof pid` 可以导出一份快照文件。 77 | 78 | 这时就得借助 `MAT` 这类的分析工具出马了。 79 | 80 | 81 | # 问题定位 82 | 83 | ![](https://i.loli.net/2019/06/18/5d07c351bb18f94417.jpg) 84 | 85 | 通过这张图其实很明显可以看出,在内存中存在一个非常大的字符串,而这个字符串正好是被这个定时任务的线程引用着。 86 | 87 | ![](https://i.loli.net/2019/06/18/5d07c3533538b39583.jpg) 88 | 89 | 大概算了一下这个字符串所占的内存为 258m 左右,就一个字符串来说已经是非常大的对象了。 90 | 91 | 那这个字符串是咋产生的呢? 92 | 93 | 其实看上图中的引用关系及字符串的内容不难看出这是一个 `insert` 的 `SQL` 语句。 94 | 95 | 这时不得不赞叹 `MAT` 这个工具,他还能帮你预测出这个内存快照可能出现问题地方同时给出线程快照。 96 | 97 | ![](https://i.loli.net/2019/06/18/5d07c353d3d2114320.jpg) 98 | 99 | ![](https://i.loli.net/2019/06/18/5d07c3546d13811049.jpg) 100 | 101 | 最终通过这个线程快照找到了具体的业务代码: 102 | 103 | 他调用一个写入数据库的方法,而这个方法会拼接一个 `insert` 语句,其中的 `values` 是循环拼接生成,大概如下: 104 | 105 | ```xml 106 | 107 | insert into xx (files) 108 | values 109 | 110 | xxx 111 | 112 | 113 | ``` 114 | 115 | 所以一旦这个 list 非常大时,这个拼接的 SQL 语句也会很长。 116 | 117 | ![](https://i.loli.net/2019/06/18/5d07c35504bf848706.jpg) 118 | 119 | 通过刚才的内存分析其实可以看出这个 `List` 也是非常大的,也就导致了最终的这个 `insert` 语句占用的内存巨大。 120 | 121 | # 优化策略 122 | 123 | 既然找到问题原因那就好解决了,有两个方向: 124 | 125 | - 控制源头 `List` 的大小,这个 `List` 也是从某张表中获取的数据,可以分页获取;这样后续的 `insert` 语句就会减小。 126 | - 控制批量写入数据的大小,其实本质还是要把这个拼接的 `SQL` 长度降下来。 127 | - 整个的写入效率需要重新评估。 128 | 129 | # 总结 130 | 131 | 本次问题从分析到解决花的时间并不长,也还比较典型,其中的过程再总结一下: 132 | 133 | - 首先定位消耗 `CPU` 进程。 134 | - 再定位消耗 `CPU` 的具体线程。 135 | - 内存问题 `dump` 出快照进行分析。 136 | - 得出结论,调整代码,测试结果。 137 | 138 | 最后愿大家都别接到生产告警。 139 | 140 | 141 | **你的点赞与分享是对我最大的支持** 142 | 143 | -------------------------------------------------------------------------------- /troubleshoot/dubbo-start-slow.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: What?一个 Dubbo 服务启动要两个小时! 3 | date: 2019/07/05 08:10:36 4 | categories: 5 | - 问题排查 6 | tags: 7 | - Java 8 | - Thread 9 | - Dubbo 10 | --- 11 | 12 | ![](https://i.loli.net/2019/07/04/5d1e14ea0052047268.jpg) 13 | 14 | 15 | # 前言 16 | 17 | 前几天在测试环境碰到一个非常奇怪的与 `dubbo` 相关的问题,事后我在网上搜索了一圈并没有发现类似的帖子或文章,于是便有了这篇。 18 | 19 | 希望对还未碰到或正在碰到的朋友有所帮助。 20 | 21 | 22 | 23 | # 现象 24 | 25 | 现象是这样的,有一天测试在测试环境重新部署一个 `dubbo` 应用的时候发现应用`“启动不起来”`。 26 | 27 | 但过几个小时候之后又能自己慢慢恢复,并能够对外提供 `dubbo` 服务。 28 | 29 | > 但其实经过我后续排查发现刚开始其实并不是启动不起来,而是启动速度非常缓慢,所以当应用长时间启动后才会对外提供服务。 30 | 31 | ![](https://i.loli.net/2019/07/04/5d1e14eb2d3b613329.jpg) 32 | 33 | 而这个速度慢到居然要花费 `2 个小时`。 34 | 35 | 导致的一个结果是测试完全不敢在测试环境发版验证了,每验证一个功能修复一个 `bug` 就得等上两个小时,这谁受得了😂。 36 | 37 | > 而且经过多次观察,确实每次都是花费两小时左右应用才能启动起来。 38 | 39 | # 尝试解决 40 | 41 | 最后测试顶不住了,只能让我这个`“事故报告撰写专家”`来看看。 42 | 43 | 当我得知这个问题的现象时其实完全没当一回事: 44 | 45 | > 都不用想,这不就是主线程阻塞了嘛,先看看是否在初始化的时候数据库、Zookeeper 之类的连不上导致阻塞了-------来之多次事故处理的经验告诉我。 46 | 47 | 48 | 于是我把这事打回给测试让他先找运维排查下,不到万不得已不要影响我 `Touch fish`🐳。 49 | 50 | 第二天一早看到测试同学的微信头像跳动时我都已经准备接受又一句 `“膜拜大佬👍”` 的回复时,却收到 “网络一切正常,没人动过,再不解决就要罢工了🤬”。 51 | 52 | 53 | 好吧,忽悠不过去了。 54 | 55 | 首先这类问题的排查方向应该不会错,就是主线程阻塞了,至于是啥导致的阻塞就不能像之前那样瞎猜了。 56 | 57 | 我将应用重启后用 `jstack pid` 将线程快照打印到终端,直接拉到最后看看 `main` 线程到底在干啥。 58 | 59 | 前几次的快照都是很正常: 60 | 61 | 加载 `Spring` ---->连接 `Zookeeper` ---> 连接 `Redis`,都是依次执行下来没有阻塞。 62 | 63 | 64 | 隔了一段后应用确实还没起来,我再次 `jstack` 后得到如下信息: 65 | 66 | ![](https://i.loli.net/2019/07/04/5d1e14ec2185426171.jpg) 67 | 68 | ## 翻源码 69 | 70 | 我一直等了十几分钟再多次 `jstack` 得到的快照得到的信息都是一样的。 71 | 72 | ![](https://i.loli.net/2019/07/04/5d1e14ec2185426171.jpg) 73 | 74 | 如图所示可见主线程是卡在了 dubbo 的某个方法 `ServiceConfig.java` 的 303 行中。 75 | 76 | 于是我找到此处的源码: 77 | 78 | ![](https://i.loli.net/2019/07/04/5d1e14ec68e8259933.jpg) 79 | 80 | 简单来说这里的逻辑就是要获取本机的 `IP` 将其注册到 `Zookeeper` 中用于其他服务调用。 81 | 82 | ![](https://i.loli.net/2019/07/04/5d1e14eca624d69950.jpg) 83 | 84 | 再往下跟就如堆栈中一样是卡在了 `Inet4AddressImpl.getLocalHostName` 处。 85 | 86 | 87 | 但这是一个 `native` 方法,我们应用也根本干涉不了,最终的现象就是调用这个本地方法非常耗时。 88 | 89 | 于是这问题貌似也阻塞在这儿了,没有太多办法。 90 | 91 | # 最终解决 92 | 93 | 既然这是一个 native 方法,那说明和应用本身没有啥关系(确实也是这样,这个问题是突然间出现的。) 94 | 95 | 那是否是服务器本身的问题呢,想到在 `native` 方法里是获取本机的 `hostname`,那是否和这个 `hostname` 有关系呢。 96 | 97 | ![](https://i.loli.net/2019/07/04/5d1e14ecd59a795394.jpg) 98 | 99 | > 这是在我自己的阿里云服务器上测试,真正的测试环境不是这个名字。 100 | 101 | 102 | 拿到服务器 `hostname` 后再尝试 `ping` 这个 `hostname`,奇怪的现象发生了: 103 | 104 | 命令刚开始会卡住一段时间(大概几十秒),然后才会输出 `hostname` 对应的 `ip` 以及对应的延迟。 105 | 106 | 而当我直接 `ping` 这个 `ip` 时却能快速响应后面的输出。 107 | 108 | 最后我尝试在 /etc/hosts 配置文件中加入了对应的 host 配置: 109 | 110 | ```xml 111 | xx.xx.xx.xx(ip) hostname 112 | ``` 113 | 114 | 再次 `ping hostname` 的效果就和直接 `ping ip` 一样了。 115 | 116 | **于是我再次重启应用,一切都正常了。** 117 | 118 | # 总结 119 | 120 | 最后根据我调整的内容尝试分析下本次问题的原因: 121 | 122 | - 当 `Dubbo` 在启动获取本地 ip 时,是通过服务器 `hostname` 从 `dns` 服务器返回当前的 ip 地址。 123 | - 由于 `dns` 服务器或者是本地服务器与 dns 服务器之间存在网络问题,导致这个过程的时间被拉长(猜测)。 124 | - 我在本地的 `host` 文件中配置后,就相当于本地有一个缓存,优先取本地配置的 ip ,避免了和 dns 服务器交互的过程,所以速度提升了。 125 | 126 | 127 | 虽然问题得到解决了,但还是有几个疑问: 128 | 129 | 第一个是为什么和 `DNS` 服务器的交互会这么慢,即便是慢也没有像应用那样需要 2 个小时才能返回,这里我也没搞得太清楚,有相关经验的朋友可以留言讨论。 130 | 131 | 第二就是 Dubbo 在这个依赖外部获取资源时健壮性是否可以做的更好,虽说我这问题估计也几人碰到。 132 | 133 | 对于这种长时间没有启动成功的问题是否可以加上提示,比如直接抛出异常退出程序,将问题可能的原因告诉开发者,方便排查问题。 134 | 135 | **你的点赞与分享是对我最大的支持** 136 | 137 | -------------------------------------------------------------------------------- /troubleshoot/pulsar-repeat-consume.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Pulsar 重复消费? 3 | date: 2022/03/18 08:15:36 4 | categories: 5 | - 问题排查 6 | - Java 进阶 7 | tags: 8 | - Pulsar 9 | - Consumer 10 | --- 11 | 12 | ![](https://tva1.sinaimg.cn/large/e6c9d24ely1h09wy1o5v8j20rs0rs408.jpg) 13 | 14 | 15 | 16 | # 背景 17 | 18 | 许久没有分享 Java 相关的问题排查了,最近帮同事一起排查了一个问题: 19 | 20 | > 在使用 `Pulsar` 消费时,发生了同一条消息反复消费的情况。 21 | 22 | 23 | 24 | # 排查 25 | 26 | 当他告诉我这个现象的时候我就持怀疑态度,根据之前使用的经验 Pulsar 在官方文档以及 API 中都解释过: 27 | 28 | ![](https://tva1.sinaimg.cn/large/e6c9d24ely1h0c6a9vzvuj216y05gdhd.jpg) 29 | ![](https://tva1.sinaimg.cn/large/e6c9d24ely1h0c6apssrmj21t00o8afc.jpg) 30 | 只有当设置了消费的 `ackTimeout` 并超时消费时才会重复投递消息,默认情况下是关闭的,查看代码也确实没有开启。 31 | 32 | 那会不会是调用了 `negativeAcknowledge()` 方法呢(调用该方法也会触发重新投递),因为我们使了一个第三方库 [https://github.com/majusko/pulsar-java-spring-boot-starter](https://github.com/majusko/pulsar-java-spring-boot-starter) 只有当抛出异常时才会调用该方法。 33 | 34 | 查阅代码之后也没有地方抛出异常,甚至整个过程中都没看到异常产生;这就有点诡异了。 35 | 36 | ## 复现 37 | 38 | 为了捋清楚整个事情的来龙去脉,详细了解了他的使用流程; 39 | 40 | 其实也就是业务出现了 `bug`,他在消息消费时 `debug` 然后进行单步调试,当走完一次调试后,没多久马上又收到了同样的消息。 41 | 42 | 但奇怪的是也不是每次 `debug` 后都能重复消费,我们都说如果一个 `bug` 能 100% 完全复现,那基本上就解决一大半了。 43 | 44 | 所以我们排查的第一步就是完全复现这个问题。 45 | 46 | --- 47 | 48 | 为了排除掉是 IDEA 的问题(虽然极大概率不太可能)既然是 `debug` 的时候产生的问题,那其实转换到代码也就是 `sleep` 嘛,所以我们打算在消费逻辑里直接 `sleep` 一段时间看能否复现。 49 | 50 | 经过测试,`sleep` 几秒到几十秒都无法复现,最后索性 `sleep` 一分钟,神奇的事情发生了,每次都成功复现! 51 | 52 | 既然能成功复现那就好说了,因为我自己的业务代码也有使用到 `Pulsar` 的地方,为了方便调试就准备在自己的项目里再复现一次。 53 | 54 | 结果诡异的事情再次发生,我这里又不能复现了。 55 | 56 | > 虽然这才是符合预期的,但这就没法调了呀。 57 | 58 | 59 | 本着相信现代科学的前提,我们俩唯一的区别就是项目不一样了,为此我对比了两边的代码。 60 | 61 | ```java 62 | @PulsarConsumer( 63 | topic = xx, 64 | clazz = Xx.class, 65 | subscriptionType = SubscriptionType.Shared 66 | ) 67 | public void consume(Data msg) { 68 | log.info("consume msg:{}", msg.getOrderId()); 69 | Lock lock = redisLockRegistry.obtain(msg.getOrderId()); 70 | if (lock.tryLock()) { 71 | try { 72 | orderService.do(msg.getOrderId()); 73 | } catch (Exception e) { 74 | log.error("consumer msg:{} err:", msg.toString(), e); 75 | } finally { 76 | lock.unlock(); 77 | } 78 | } 79 | 80 | } 81 | ``` 82 | 83 | 结果不出所料,同事那边的代码加了锁;一个基于 Redis 的分布式锁,这时我一拍大腿不会是解锁的时候超时了导致抛了异常吧。 84 | 85 | 为了验证这个问题,在能复现的基础上我在框架的 `Pulsar` 消费处打了断点: 86 | ![](https://tva1.sinaimg.cn/large/e6c9d24ely1h0c4tmq9dhj22zg0hon4e.jpg) 87 | ![](https://tva1.sinaimg.cn/large/e6c9d24ely1h0c5xve3qaj21ss070q4u.jpg) 88 | 果然破案了,异常提示已经非常清楚了:加锁已经过了超时时间。 89 | 90 | 进入异常后直接 `negative` 消息,同时异常也被吃掉了,所以之前没有发现。 91 | 92 | ![](https://tva1.sinaimg.cn/large/e6c9d24ely1h0dckg14crj21l60maq88.jpg) 93 | 查阅了 `RedisLockRegistry` 的源码,默认超时时间正好是一分钟,所以之前我们 `sleep` 几十秒也无法复现这个问题。 94 | 95 | # 总结 96 | 97 | 事后我向同事了解了下为啥这里要加锁,因为我看下来完全没有加锁的必要;结果他是因为从别人那里复制的代码才加上的,压根没想那么多。 98 | 99 | 所以这事也能得出一些教训: 100 | 101 | - ctrl C/V 虽然方便,但也得充分考虑自己的业务场景。 102 | - 使用一些第三方 API 时,需要充分了解其作用、参数。 103 | 104 | 105 | 106 | 107 | **你的点赞与分享是对我最大的支持** 108 | 109 | -------------------------------------------------------------------------------- /troubleshoot/thread-gone2.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 线程池中你不容错过的一些细节 3 | date: 2019/03/26 08:15:36 4 | categories: 5 | - 问题排查 6 | - Java 进阶 7 | tags: 8 | - Java 9 | - Thread 10 | - concurrent 11 | --- 12 | 13 | ![](https://i.loli.net/2019/04/29/5cc655fb0f18c.jpg) 14 | 15 | # 背景 16 | 17 | 上周分享了一篇[《一个线程罢工的诡异事件》](https://crossoverjie.top/2019/03/12/troubleshoot/thread-gone/),最近也在公司内部分享了这个案例。 18 | 19 | 无独有偶,在内部分享的时候也有小伙伴问了之前分享时所提出的一类问题: 20 | 21 | 22 | 23 | ![](https://i.loli.net/2019/05/08/5cd1bec753b00.jpg) 24 | 25 | ![](https://i.loli.net/2019/05/08/5cd1bec8136a7.jpg) 26 | 27 | ![](https://i.loli.net/2019/05/08/5cd1bec8ca8f7.jpg) 28 | 29 | ![](https://i.loli.net/2019/05/08/5cd1bec980288.jpg) 30 | 31 | 这其实是一类共性问题,我认为主要还是两个原因: 32 | 33 | - 我自己确实也没讲清楚,之前画的那张图还需要再完善,有些误导。 34 | - 第二还是大家对线程池的理解不够深刻,比如今天要探讨的内容。 35 | 36 | 37 | # 线程池的工作原理 38 | 39 | 首先还是来复习下线程池的基本原理。 40 | 41 | 我认为线程池它就是一个**调度任务**的工具。 42 | 43 | 众所周知在初始化线程池会给定线程池的大小,假设现在我们有 1000 个线程任务需要运行,而线程池的大小为 10~20,在真正运行任务的过程中他肯定不会创建这1000个线程同时运行,而是充分利用线程池里这 10~20 个线程来调度这1000个任务。 44 | 45 | 而这里的 10~20 个线程最后会由线程池封装为 `ThreadPoolExecutor.Worker` 对象,而这个 `Worker` 是实现了 Runnable 接口的,所以他自己本身就是一个线程。 46 | 47 | # 深入分析 48 | 49 | ![](https://i.loli.net/2019/05/08/5cd1bec9eea43.jpg) 50 | 51 | 这里我们来做一个模拟,创建了一个核心线程、最大线程数、阻塞队列都为2的线程池。 52 | 53 | 这里假设线程池已经完成了预热,也就是线程池内部已经创建好了两个线程 `Worker`。 54 | 55 | 当我们往一个线程池丢一个任务会发生什么事呢? 56 | 57 | ![](https://i.loli.net/2019/05/08/5cd1bed130c18.jpg) 58 | 59 | - 第一步是生产者,也就是任务提供者他执行了一个 execute() 方法,本质上就是往这个内部队列里放了一个任务。 60 | - 之前已经创建好了的 Worker 线程会执行一个 `while` 循环 ---> 不停的从这个`内部队列`里获取任务。(这一步是竞争的关系,都会抢着从队列里获取任务,由这个队列内部实现了线程安全。) 61 | - 获取得到一个任务后,其实也就是拿到了一个 `Runnable` 对象(也就是 `execute(Runnable task)` 这里所提交的任务),接着执行这个 `Runnable` 的 **run() 方法,而不是 start()**,这点需要注意后文分析原因。 62 | 63 | 结合源码来看: 64 | 65 | ![](https://i.loli.net/2019/05/08/5cd1bed1e2402.jpg) 66 | 67 | 从图中其实就对应了刚才提到的二三两步: 68 | 69 | - `while` 循环,从 `getTask()` 方法中一直不停的获取任务。 70 | - 拿到任务后,执行它的 run() 方法。 71 | 72 | 这样一个线程就调度完毕,然后再次进入循环从队列里取任务并不断的进行调度。 73 | 74 | # 再次解释之前的问题 75 | 76 | 接下来回顾一下我们上一篇文章所提到的,导致一个线程没有运行的根本原因是: 77 | 78 | > 在单个线程的线程池中一但抛出了未被捕获的异常时,线程池会回收当前的线程并创建一个新的 `Worker`; 79 | > 它也会一直不断的从队列里获取任务来执行,但由于这是一个消费线程,**根本没有生产者往里边丢任务**,所以它会一直 waiting 在从队列里获取任务处,所以也就造成了线上的队列没有消费,业务线程池没有执行的问题。 80 | 81 | 结合之前的那张图来看: 82 | 83 | ![](https://i.loli.net/2019/05/08/5cd1bed23cd35.jpg) 84 | 85 | 这里大家问的最多的一个点是,为什么会没有是`根本没有生产者往里边丢任务`,图中不是明明画的有一个 `product` 嘛? 86 | 87 | 这里确实是有些不太清楚,再次强调一次: 88 | 89 | **图中的 product 是往内部队列里写消息的生产者,并不是往这个 Consumer 所在的线程池中写任务的生产者。** 90 | 91 | 因为即便 `Consumer` 是一个单线程的线程池,它依然具有一个常规线程池所具备的所有条件: 92 | 93 | - Worker 调度线程,也就是线程池运行的线程;虽然只有一个。 94 | - 内部的阻塞队列;虽然长度只有1。 95 | 96 | 再次结合图来看: 97 | 98 | ![](https://i.loli.net/2019/05/08/5cd1bed2bd074.jpg) 99 | 100 | 所以之前提到的【没有生产者往里边丢任务】是指右图放大后的那一块,也就是内部队列并没有其他线程往里边丢任务执行 `execute()` 方法。 101 | 102 | 而一旦发生未捕获的异常后,`Worker1` 被回收,顺带的它所调度的线程 `task1`(这个task1 也就是在执行一个 while 循环消费左图中的那个队列) 也会被回收掉。 103 | 104 | 新创建的 `Worker2` 会取代 `Worker1` 继续执行 `while` 循环从内部队列里获取任务,但此时这个队列就一直会是空的,所以也就是处于 `Waiting` 状态。 105 | 106 | 107 | > 我觉得这波解释应该还是讲清楚了,欢迎还没搞明白的朋友留言讨论。 108 | 109 | # 为什是 run() 而不是 start() 110 | 111 | 问题搞清楚后来想想为什么线程池在调度的时候执行的是 `Runnable` 的 `run()` 方法,而不是 `start()` 方法呢? 112 | 113 | 我相信大部分没有看过源码的同学心中第一个印象就应该是执行的 `start()` 方法; 114 | 115 | 因为不管是学校老师,还是网上大牛讲的都是只有执行了` start()` 方法后操作系统才会给我们创建一个独立的线程来运行,而 `run()` 方法只是一个普通的方法调用。 116 | 117 | 而在线程池这个场景中却恰好就是要利用它**只是一个普通方法调用**。 118 | 119 | 回到我在文初中所提到的:我认为线程池它就是一个**调度任务**的工具。 120 | 121 | 假设这里是调用的 `Runnable` 的 `start` 方法,那会发生什么事情。 122 | 123 | 如果我们往一个核心、最大线程数为 2 的线程池里丢了 1000 个任务,**那么它会额外的创建 1000 个线程,同时每个任务都是异步执行的,一下子就执行完毕了**。 124 | 125 | 从而没法做到由这两个 `Worker` 线程来调度这 1000 个任务,而只有当做一个同步阻塞的 `run()` 方法调用时才能满足这个要求。 126 | 127 | > 这事也让我发现一个奇特的现象:就是网上几乎没人讲过为什么在线程池里是 run 而不是 start,不知道是大家都觉得这是基操还是没人仔细考虑过。 128 | 129 | # 总结 130 | 131 | 针对之前线上事故的总结上次已经写得差不多了,感兴趣的可以翻回去看看。 132 | 133 | 这次呢可能更多是我自己的总结,比如写一篇技术博客时如果大部分人对某一个知识点讨论的比较热烈时,那一定是作者要么讲错了,要么没讲清楚。 134 | 135 | 这点确实是要把自己作为一个读者的角度来看,不然很容易出现之前的一些误解。 136 | 137 | 在这之外呢,我觉得对于线程池把这两篇都看完同时也理解后对于大家理解线程池,利用线程池完成工作也是有很大好处的。 138 | 139 | 如果有在面试中加分的记得回来点赞、分享啊。 140 | 141 | 142 | **你的点赞与分享是对我最大的支持** 143 | 144 | -------------------------------------------------------------------------------- /vlog/Basketball Day one.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: VLOG-008:Basketball Day One 3 | date: 2019/04/21 15:00:00 4 | categories: 5 | - VLOG 6 | --- 7 | 8 | ![](https://i.loli.net/2019/04/29/5cc652f48e019.jpg) 9 | 10 | 周末轻松一下。 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /vlog/Chinese-coder-daily.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: VLOG-004:国产程序员的一天 3 | date: 2019/02/20 08:00:00 4 | categories: 5 | - VLOG 6 | --- 7 | 8 | ![](https://i.loli.net/2019/04/29/5cc656ede2e8e.jpg) 9 | 10 | `VLOG` 近些年非常流行,最近这段时间我也拍了一些来记录生活。 11 | 12 | 之前一直想记录自己上班生活的一天;至于为什么标题要加上一个`国产`两字,是因为之前看到一位国外女程序媛的一天(视频链接见底部),这次是想让大家看看在天朝国情下的反差。 13 | 14 | `a day in the life of a software engineer:https://www.youtube.com/watch?v=rqX8PFcOpxA` 15 | 16 | 17 | 18 | # 正片开始 19 | 20 | 21 | 22 | 23 | 24 | ## 08:00 25 | 26 | > 起床洗漱。 27 | 28 | 29 | ## 8:20 30 | 31 | > 出门到轻轨站。 32 | 33 | ## 8:30 34 | 35 | > 到达轻轨站。 36 | 37 | ## 9:00 38 | 39 | > 到达公司开始干活。 40 | 41 | ## 10:00 42 | 43 | > 一个电话远程面试。 44 | 45 | ## 11:00 46 | 47 | > 一个电话远程面试。 48 | 49 | 中间有一段对自己这些天来面试经历的一些分享,视频加快了但是重点内容都打有字幕。 50 | 51 | ## 12:00 52 | 53 | > 午饭时间。 54 | 55 | ## 15:00 56 | 57 | > 部门内部会议到 16 点。 58 | 59 | ## 17:00 60 | 61 | > 一个电话远程面试。 62 | 63 | ## 19:00 64 | 65 | > 有时间做自己的撸码工作。 66 | 67 | ## 20:00 68 | 69 | > 准备回家。 70 | 71 | 72 | ## 22:00 73 | 74 | > 接女朋友下班。 75 | 76 | 77 | ## 00:30 78 | 79 | > 完成一个算法,收工睡觉。 80 | 81 | 82 | 83 | 下一次录工作 VLOG 应该是等我当老板咯,希望别是有生之年系列。。。 -------------------------------------------------------------------------------- /vlog/Chinese-coder-weekends-01.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: VLOG-006:一个程序员的周末(上) 3 | date: 2019/03/21 08:00:00 4 | categories: 5 | - VLOG 6 | --- 7 | 8 | ![](https://i.loli.net/2019/04/29/5cc6563f56b0c.jpg) 9 | 10 | 上次发了一个[《VLOG-004:国产程序员的一天》](https://crossoverjie.top/2019/02/20/vlog/Chinese-coder-daily)评论和播放量都还不错,这次趁热打铁更新了周末业余生活是怎么样的。 11 | 12 | 在这个视频中你将看到: 13 | 14 | - 作为一个居家好男人是如何体现自我价值的? 15 | - 我是如何产出一份技术博客? 16 | - 女朋友不在家如何正规消遣时间? 17 | - 如何撸得代码、下得厨房讨女朋友开心? 18 | 19 | # 正片开始 20 | 21 | 22 | 23 | 24 | 25 | 26 | 这个视频是上部分,主要记录的是在家里的生活。 27 | 28 | # 起床 29 | 30 | 可能是由于平时早起习惯了,我居然快改掉了多年懒床的毛病,近期周末都是8、9点的样子就会起床。 31 | 32 | 起床后磨蹭半天一不小心就到了饭点,由于女朋友上早班一个人的话就点了外卖。 33 | 34 | ![](https://i.loli.net/2019/05/08/5cd1beb5b9f63.jpg) 35 | 吃饭的同时会看看一部下饭神剧《武林外传》。 36 | 37 | # 家务 38 | 39 | 开始洗衣服,洗衣服的同时需要收衣服、叠衣服从而实现个人价值。 40 | 41 | # 周报 42 | 43 | ![](https://i.loli.net/2019/05/08/5cd1bebc05aa7.jpg) 44 | 45 | 准备开始写周报,结果一不小心沉浸于电视。。。 46 | 47 | 2个小时后周报完成。 48 | 49 | 50 | # PPT 51 | 52 | ![](https://i.loli.net/2019/05/08/5cd1bebcb7a89.jpg) 53 | 54 | 完成下周技术分享的 PPT,但受限于设计细胞整个 PPT 将比较难看。 55 | 56 | 57 | # 技术博客 58 | 59 | ![](https://i.loli.net/2019/05/08/5cd1bebe980fd.jpg) 60 | 61 | 开始写一篇技术博客,分享了我个人的一些习惯: 62 | 63 | - 先在本子上打好提纲。 64 | - 加上一个二次元配图。 65 | - 写博客时需要注意的点。 66 | 67 | # 消遣 68 | 69 | ![](https://i.loli.net/2019/05/08/5cd1bec0065b2.jpg) 70 | 71 | 玩 `switch` ,买游戏花的钱已经都可以再买一个 `switch` 了,果真是买游戏送主机;主要玩: 72 | 73 | - 马里奥赛车8 74 | - 喷射乌贼娘 75 | - 塞尔达传说 76 | - 任天堂明星大乱斗 77 | 78 | # 晚饭 79 | 80 | ![](https://i.loli.net/2019/05/08/5cd1bec0ae362.jpg) 81 | 82 | 作为王刚师傅的在线弟子,深的宽油真传,必将料理出感动人心的美味;本次为大家带来的是`口味猪肝`。 83 | 84 | 全程高能,请大家饭前观看(吃完饭后会忍不住想吃)。 85 | 86 | # END 87 | 88 | 看到这里想必你已经对开始的几个问题有了答案了吧,来核对下我的标准答案吧。 89 | 90 | 91 | - 作为一个居家好男人是如何体现自我价值的?---->你的价值就是洗碗、洗衣服收拾家务。 92 | - 我是如何产出一份技术博客?---->提前构思目录、文章配图。 93 | - 女朋友不在家如何正规消遣时间?---->必须是 `switch` 啊,不然你以为是啥。。 94 | - 如何撸得代码、下得厨房讨女朋友开心?---->看 20 遍王刚师傅的视频自学成才。 95 | 96 | 97 | 下一期将会为大家带来室外篇,作为一个大学毕业到现在成功长膘 20 斤的篮球爱好者,如何科学运动长肉,你值得期待🤫。 98 | -------------------------------------------------------------------------------- /vlog/Chinese-coder-weekends-02.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: VLOG-007:一个程序员的周末(中) 3 | date: 2019/03/30 08:00:00 4 | categories: 5 | - VLOG 6 | --- 7 | 8 | ![](https://i.loli.net/2019/04/29/5cc655bdd866b.jpg) 9 | 10 | 承接上期[《VLOG-006:一个程序员的周末(上)》](https://crossoverjie.top/2019/03/21/vlog/Chinese-coder-weekends-01/) 11 | 12 | 本次是周末日常的中篇,也是我每周做的最频繁的一件事情【陪女朋友逛街】。 13 | 14 | 15 | 16 | 导致的结果是我已经清楚的知道所在商圈里大部分门店里的休息位置。 17 | 18 | 哪里坐着舒服、哪里的信号比较好、哪家店逛不久会马上走,所以最好是不要坐下。 19 | 20 | 可谓是“旱的旱死,涝的涝死” 21 | 22 | # 正片开始 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /第一次总结.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 第一次总结 3 | date: 2016-05-07 19:35:14 4 | categories: 5 | - blog #文章文类 6 | tags: 7 | - Markdown 8 | - 总结 9 | --- 10 | # 前言 11 | --- 12 | 13 | 昨天到今天一共花了差不多两天的时间终于把博客搭好了。还买了一个域名,现在就迫不及待的想把 14 | 这段内容写下来。 15 | 16 | --- 17 | 18 | # 感谢 19 | --- 20 | 首先非常感谢 [嘟爷](http://tengj.top/ "嘟爷的博客")的帮忙,没有这些资料我可能还得自己研究好一段时间。 21 | 22 | # 过程 23 | --- 24 | 25 | 我是前天无意间在微博上看到嘟爷的一篇博文,就仔细看了下,发现写的非常好,然后就将他所有的博文大致的浏览了一下。 26 | 27 | 我很早以前就打算搭一个博客,但是百度了一下发现还是挺麻烦的,加上最近也比较忙所有一直也就没有做,直到看到这篇博文才顺利的搭起了这个博客。中途遇到不少问题也都顺利解决了,真是学到了不少的东西。 28 | 29 | - 熟练了Markdown语法。 30 | - 真正使用了编辑神器 **Sublime**。 31 | - 使用阿里云解析了github和coding里的Pages服务。 32 | - hexo和常用的主题配置。 33 | 34 | # 我的配置 35 | --- 36 | ## Hexo配置 37 | --- 38 | ![Hexo配置](http://i.imgur.com/IqaE8CF.png) 39 | 40 | 41 | --- 42 | ## JackMan配置 43 | imglogo: 44 | enable: true ## display image logo true/false. 45 | src: img/logo.gif ## `.svg` and `.png` are recommended,please put image into the theme folder `/jacman/source/img`. 46 | favicon: img/favicon.ico ## size:32px*32px,`.ico` is recommended,please put image into the theme folder `/jacman/source/img`. 47 | apple_icon: img/jacman.jpg ## size:114px*114px,please put image into the theme folder `/jacman/source/img`. 48 | author_img: img/author.jpg ## size:220px*220px.display author avatar picture.if don't want to display,please don't set this. 49 | banner_img: #img/banner.jpg ## size:1920px*200px+. Banner Picture 50 | ### Theme Color 51 | theme_color: 52 | theme: '#2ca6cb' ##the defaut theme color is blue 53 | 54 | # 代码高亮主题 55 | # available: default | night 56 | highlight_theme: night 57 | 58 | #### index post is expanding or not 59 | index: 60 | expand: false ## default is unexpanding,so you can only see the short description of each post. 61 | excerpt_link: Read More 62 | 63 | close_aside: false #close sidebar in post page if true 64 | mathjax: false #enable mathjax if true 65 | 66 | ### Creative Commons License Support, see http://creativecommons.org/ 67 | ### you can choose: by , by-nc , by-nc-nd , by-nc-sa , by-nd , by-sa , zero 68 | creative_commons: none 69 | 70 | # 结束语 71 | 72 | 以上。。我做这个博客的初衷一是为了记录我的整个程序猿生涯的故事,二是希望能有大神能在过程中指出我的错误,能让我的水平更进一步。 --------------------------------------------------------------------------------