Thursday, April 21, 2022

V2EX - 技术

V2EX - 技术


写代码不格式化的,都是垃圾,不接受反驳!

Posted: 21 Apr 2022 06:19 AM PDT

github 个人主页的这个 team 怎么去掉?

Posted: 21 Apr 2022 06:14 AM PDT

救命,主页挂个这个好下头

我只是给提了个 typo issue,也妹加入这个 team,为啥会在主页出现

自己用来判断国内法定节假日的 js 库,分享给各位

Posted: 21 Apr 2022 06:13 AM PDT

https://www.npmjs.com/package/festival_chn

https://github.com/vueadmin/festival_chn

最新更新记录

目前仅支持 2022 年的日期判断

2.2.1

  • 新增法定节假日判断,调休日判断
  • 法定节假日新增 isSwap 字段,用于判断该假期是否有调休日
  • 法定节假日新增 swapDate 字段,如果 isSwaptrue,则该字段展示本法定节假日的调休日数组
  • 新增 type 字段 1/正常日,2/法定节假日,3/调休日

NPM

npm i festival_chn 

USE

import festival from "festival_chn"; 

PARMAES

date 需要符合 "yyyy-mm-dd" 格式 const date = "2022-01-01" const res = festival(date); 

RETURN

字段名称 字段类型 返回状态 说明
name String 必返 节假日名称 /调休 /正常
type Number 必返 1/正常 2/节假日 3/调休日
isSwap Boolean 法定节假日必返 true/有调休 false/无调休
swapDate Array 法定节假日有调休日必返 调休日组成的数组
{     name: '春节',     type: 2,     isSwap: true,     swapDate: [         "2022-01-29"         "2022-01-30"     ] } 

一到两台机器小成本部部署的 Redis 服务一般是如何提高可用性的?

Posted: 21 Apr 2022 06:01 AM PDT

实际生产中应该不是所有业务都是几十个节点的大型集群吧,应该也有这种小型部署的 redis ,一般这种部署是如何提高可用性的呢?

方案 1 、单实例部署,采用 rdb+aof 落盘(无法感知到服务是否挂了)

方案 2 、两台机器部署一主一从,主机不落盘,从机 rdb+aof 落盘(同样无法感知到主服务是否挂了,不过按这个方案的负载能力应该比上个方案大)

方案 3 、两机器一主一从,另外开一个单节点哨兵用来获取当前可用的服务地址(不过落盘如何设置似乎又成了问题)

生产部署一般是怎么办的呢,有啥经验吗?

香港基去反代美国鸡,是什么样的速度呢?

Posted: 21 Apr 2022 05:50 AM PDT

好奇一件事情,用腾讯云香港去反代美国的 vps ,内容不复杂,就是一堆图片(正经图床,但体积接近 1TB 了,只能考虑出海买大盘鸡存它们)。 不知道这个组合是种怎样的体验。 因为不是 VPS 收藏爱好者,还真没有海外资源让我体验体验……有前辈们指点指点吗?

🐒 野生架构师成长之路(1)——游戏与现实、乌龟与火箭

Posted: 21 Apr 2022 05:49 AM PDT

cover

🎉 系列开篇词

在今年的 1 月 22 号,我发表了第一篇技术周刊,在 2 月份的时候将周刊名称定为 「野生架构师」周刊,之所以取这个名称是因为我本身就是一个通过自学成为架构师的人,周刊内容也是我作为这样一个角色而想要分享的。

「野生架构师」在很多人看来是一个贬义词,但在我看来他是一个中性词,没有贬义也没有褒义,「野生」是形容自学,而非野路子,虽然是依靠自学,但依然要学习很多科班的知识,「架构师」也并不比工程师高大上,只是工程师更专注于编写代码,而架构师除了代码外,还需做更多团队、软件工程等方面的工作。

在架构师这样一个岗位上,经常需要编写内部文档,或给团队成员指明一些方向,这样的内容往往不是为自己而写,而是 为他人而写,这就奠定了一个非常好的传播基础——分享对他人有价值的内容。

从周刊开始,我想把自己所写的内容分享给更多的人,算是正式开始了对外输出的创作之路。除了周刊,我还想进行更多的内容输出,于是有了这个《野生架构师成长之路》系列。

这个系列的文章是为那些想自学成为一个优秀的程序员、高级软件工程师或架构师的人而写的。诚然网上有许多的技术资料教你如何成为一个架构师,但他们提供的更多的是的技术资料,而缺乏身为架构师的心路历程或感悟,或者说缺乏像我这样的野生架构师的经历分享,那么希望这系列的文章能够填补这片空白,影响到更多想从事或正在从事计算机软件开发工作的人。

在文章中,我会描述一些个人经历,再附加一些对过往经历的思考,那就让我们先从一段故事开始吧。

📖 游戏与现实、乌龟与火箭

在《超级马里奥:奥德赛》这款游戏中,马里奥可以附身在许多物体上,包括乌龟和火箭,其实这可以看作是一个寓言故事,在现实中,我们前进的速度既可以像乌龟一样,也可以像火箭一样,人是一个变量,在乌龟与火箭的状态之中游离,当我们奔跑的动力足够强时足以附身在火箭上。

🎮 兴趣起源于爱玩游戏

最早对编程产生兴趣是在高中时期,当时沉迷于网络游戏而忽略了学习,由于对游戏过于沉迷,对这个虚拟世界感到十分好奇,经常思考它的经济系统、战斗系统、图像动画都是如何实现的等等,带着这种好奇心,我梦想着成为一个游戏开发者,渐渐在心底埋下了一个想成为程序员的种子。

🐢 毫不费力地进入乌龟时代

在高中时期,我是一个游戏瘾君子,所有的生活都围绕着网络游戏,学习完全被我抛开了,到了高三的时候,我甚至都没有去参加过一次考试,因为班主任对我说,「你不参加考试是不会拉低班级平均分的」,所以我有「特权」不去参加考试。

第一次高考时我考了 333 分,恐怕是年级倒数,当时我对分数已经完全无所谓了,之所以记得这个分数只是因为它很好记。高考那个漫长的暑假开始时,我已经不打算再读书了。

可能是因为我想做程序员,在那个暑假结束后,我还是决定继续读书,所有这一切都是我自己选择的。

摆在我面前的有两条路:一,回去复读一年,以当时的心智,我认为回去复读一年也考不上大学;二,去读中专,通过中专再升大专或本科。

试想一下,在这种情况下你还能相信自己能学好编程吗?事实上,我却从来没有怀疑过自己,且听我一一道来。

🚀 在兴趣的驱使下进入火箭时代

我选择了第二条路,去读了中专,这一年上课时我大多数时候仍然不听讲,周末跟室友一起去通宵打游戏,但我完整地自学了数学和计算机相关的课程,数学课本上的每道题都做过。

第二次参加(三校生)高考时,考前一晚我彻夜未眠,已经非常重视这次的考试,好在第二天考完数学后放松了下来,在最后一道 10 分的题目没做出来的情况下考了 140 分,最后考试总分是班级第一名,还差 20 分就能够选择一些较差的本科。

后来我在本校继续读了一年多的大专,在大专这样的环境下,老师一个学期也讲不完一本薄薄的课本,而我一般在学期的第一个月看完所有的课本,剩余的时间则自己找优秀的书籍来学习。

我的大专生活很简单,每天学习编程,休闲时间主要是看电影,这时我已经不打游戏了,关于我在那时的学习状态,这篇 《残简 · 学校的回忆》 中有所描述。

大三就要出来实习了,那是 2010 年,刚满 19 周岁的我在深圳找了一份工作,月薪 5000 元,没想到的是自己竟然成了主力开发。对了,虽然是出来实习,但我并没有做过实习生。

🪃 面对现实,但不逃避现实

这时的我,时刻想的是回去读高中,因为我的心智已经有所成长,我相信自己回去读高中一定可以考个好的大学。

在大专时,我的遗憾是没有交到好朋友,跟周围的同学格格不入,几乎只有自己在学习,在高中时一起疯玩的同学都成为了一生挚友,而大专时的我则显得有些孤独,我向往着与优秀的人一起学习、成长和竞争,好胜心令我想击败优秀的人。

但因为现实等诸多原因,我选择继续工作,那一两年我时常梦回高中。

在工作的前四年,我以年均 30 多本书的学习速度在进步和成长着。期间我短暂入职过一家游戏公司做服务端开发,也看了一些游戏开发方面的书籍,但因为那款游戏太烂以及要持续赚钱等因素,我没有选择转行成为一个游戏程序员。

面对现实,没有实现大学梦,也没有实现游戏开发梦,这不是一个好的示范,但没有人知道你背后经历过什么,所以不要自怨自艾。我们需要做的不是逃避现实,而是继续努力着,成为自己想成为的人,最终我当上了一个小厂的架构师,不值得炫耀,但我想这是一个好的示范。

🤔 故事里的启示

故事讲完了,在乌龟与火箭状态的转变上,我认为以下几点非常重要:

🤩 做兴趣使然的程序员

兴趣是最好的老师,没有兴趣就去培养它,否则不要进入这个行业。

2022 年了,程序员仍然是一个高薪职业,因此许多大学新生选择了计算机相关的专业,出于职业发展的考虑这是无可厚非的,但千万别忽视了兴趣因素。

如何确定自己是否对编程感兴趣?像我一样,很多程序员一开始对编程产生兴趣都是出于对游戏的热爱,或者喜欢计算机和软件这类事物,喜欢思考它背后的实现原理,通过这些激发了自己的求知欲,进而通过探索和学习发现了其中的美妙之处。

在心里埋下一个种子固然是好事,但许多人并没有这样的机会,大多数人在高中时期并没有想清楚自己未来要从事什么职业,不过没有关系,没有兴趣可以培养兴趣,编程是一项创造性的活动,只要你对创造事物感兴趣,对编程的兴趣也是可以慢慢培养的。

不管我们最初是出于兴趣还是赚钱而学习编程,在学习的过程中如果能够感受到编程之美,那就有足够的动力支撑我们坚持下去,如果没有发现编程之美,那就继续寻找。

😎 一点一滴地建立自信

自信心是靠自己一点一滴建立起来,不去努力是无法知道自己的潜能的。

如果有足够的自信,我也不会在面临第二次高考时彻夜难免,当我们从低谷开始向上时,是避免不了自我怀疑的,只能靠自己一点一点地建立自信,在前面的故事中,我描绘的是一些较大的事件,而在生活中,我们需要从小的事情开始,逐步让自己重建信心,如果每天看 1 小时编程书籍太困难,那就先从 15 分钟开始,推荐大家阅读 原子習慣 (豆瓣) ,小的习惯让我们更容易坚持,也更容易对自己产生信心。

🧐 找到现实的解法

在学习的过程中可能会碰壁,想法可能会改变,但要找到解法,不要停下脚步,面对现实时也一样。

学习编程,可以改变一个人的思维方式,这些思维方式可以运用到学习和生活当中——这世界充满着一个个的谜题等待着我们去解决。

面对现实等问题时,去解决而不是逃避它。

🤠 选择驶入新的道路

进入舒适区是毫不费力的,有时自己都意识不到,但当开始努力时,就不要懊恼于过去的「乌龟」状态,self-pity 是一个陷进,不要落入这种状态。

没有回去读高中,没有考上好的大学,因此有了许多遗憾,但谁又没有遗憾呢? 自我怜悯是一条死路 ,选择停在那里或是驶入一条新的道路,决定权在我们手上。

🫡 不要做大多数

对于年轻的读者朋友,我希望他们能够鼓起勇气,即使你像我一样高中三年都没有学习,只要努力一年,上大学是一件相对容易的事情。

不管我们现在的学习或者编程水平如何,只要肯下苦功夫,一年时间足以产生质的飞跃。

在互联网时代,有足够多的自学资源,通过自学可以改变一个人一生的轨迹。

不要迷信一些统计数据,不要听别人说编程很难,不要听别人说创业很难,因为大多数人没有努力学习,大多数人没有准备好就去创业。

不要做大多数。

❓ 还会写些什么?

作为这个系列的开篇,我写的是自己早期的心路历程,后面还会写哪些内容,我也不知道,还没有仔细去想,欢迎读书朋友告诉我,说说你想了解和想看的,帮助我写出对他人有益的内容。

接口调试工具, apifox vs apipost vs postman vs yapi

Posted: 21 Apr 2022 05:32 AM PDT

在伟图上,真的能问出点东西。
Apifox 最近广告很多呀,去年看到的,最近试了下,界面确实还挺友好,然后搜了下这家公司,我的妈,21 年创办,注册资金 100W ,虽然融资能融到超多钱,但是他新,他小,做出来的东西真的比市面上其他产品要好吗。
另外问下接口测试工具有没有安全性问题

各位大佬指点下字符串模式查找问题

Posted: 21 Apr 2022 05:31 AM PDT

背景

使用虚拟机+OpenWrt+Menthust 解决校园网认证问题,但是会有共享网络检测问题。目前现象是使用 80 端口很大概率被封,用 IPtables 禁止 80 端口的数据出 WALN 口就没事,分析是根据 HTTP 的 User-Agent 请求头判定的,但是现在挺多 APP 还用了 80 端口加速视频、图片什么的。目前找到了两种方案,分别是xmurp-uaUA2F,前者是内核修改,后者是用户态修改方案,但是不好使,访问 HTTP 页面,UA 并没修改。所以我参考他们的自己写了一个,同时也学习下 C 语言及 Linux 方面的东西.

方案

我采用的是第二个方案,设置规则,让符合条件的包交给用户态程序处理,模块是 NFQUEUE 。现在进度是交叉编译环境、测试环境都准备好了,代码进行到在 TCP payload 中查找 User-agent 并替换,想请问下那种查找算法效率高些?以下是我已进行的分析:

  • TCP payload 最大载荷是 1500 - IP 头(20) - TCP 头(20) = 1460 (Bytes),数据量也不是太大,直接暴力或许可以,UA2F 采用的就是暴力的方案
  • 查找过程是否需要利用 HTTP 协议结构,比如对于 HTTP 1.x 在遇到第一个换行前不需要匹配 User-Agent 字符。但是这样就需要考虑协议版本的问题了,还需要识别协议的版本。若是直接搜索 User-Agent 字符串,就可以不用考虑这些
  • 修改的时候,UA 信息可能被分片了,UA 主要信息在第二个包,但是User-Agent字段却在前一个包,搜索不出来,第二个包好像不太好修改?修改成统一 UA 可能增大 TCP payload 的数据,导致 IP 层分片?感觉去掉各平台的标识就行了,比如 Android 、IOS 、Win 等标识不同时出现。考虑分片增加复杂度了,感觉这种情况可以不考虑,账号拉黑应该有个阈值的,不会有这么多数据包巧合。
  • 一个 HTTP 的 TCP 链接,UA 应该大概率出现在第一个数据包(不考虑建立链接的包),后续的包应该不会出现了 UA ,所以可以借助 iptable 的标记功能,对后续的包快速放行。但是 HTTP2 的多路复用后续可能还会出现含义 UA 信息的包,不知道理解的对不对?。现现在大多数还是 HTTP 1.x 的?我观察虎牙直播的就是

查找算法我感觉Rabin-Karp Algorithm应该合适,hh

问题是:

  • 采用何种方案定位 UA 位置?
  • 定位后检索平台标识符替换,还是替换成统一的 UA ?

为何可以这样传递参数

Posted: 21 Apr 2022 05:29 AM PDT

初学,有个疑惑的地方,例如

file, err := os.Open("/path/to/file.txt") scanner := bufio.NewScanner(file) 

这里 file 获传入 bufio.NewScanner ,但是文档写的是

func Open(name string) (*File, error) func NewScanner(r io.Reader) *Scanner 

这里接受的是参数是 r io.Reader ,为何 file 可以传过去,是否是因为 file 实现了接口导致的,我在文档里面也没有看到有提及,如果是这样在哪里可以看到哪些参数实现了哪些接口呢,不能要用的时候一个个去断言吧?

Mac 下代替 Xshell 的工具 item2 vs FinalShell vs ssh shell

Posted: 21 Apr 2022 05:27 AM PDT

主要连 Linux 服务器用,求推荐,要用就用最好的

如何避免 while、for、if 的滥用?

Posted: 21 Apr 2022 05:24 AM PDT

要是一个方法包含很多 while 、for 、if 或 case 啥,代码不仅丑,维护性又烂,大家咋避免这情况的?

[求助] docker logs 自动输出当天零点生成的日志文件

Posted: 21 Apr 2022 04:46 AM PDT

我使用 docker 部署了一个服务,并且使用 tail -f 输出日志,便于 docker logs 的时候查看

但是日志文件每天零点会切割,生成一个新的,已日期命名的日志文件,如何自动输出这个新的日志文件内容呢(因为新的日志会写到这个新的日志文件中)?

  • 需要保存日志文件
  • 服务在 docker 里是非前台的

xdm 想想办法,有啥好的建议吗

k8s 如何实时监控 deployment 的部署结果

Posted: 21 Apr 2022 04:37 AM PDT

比如当 apply 一个新的 deployment 的时候, 如果部署成功了只能通过日志观测, 但是日志报警却无法配置成实时, 希望是能有一个工具可以监控 deployment 对应的 pod 状态, 然后通知到 IM 里面. 做了一些搜索但是没有找到类似的组件, 目前使用的是阿里的 ACK 托管集群. 求教目前比较常见的解决方案有哪些

求助大佬:多数据源下 postgres 重启引起 springboot 应用数据库连接断开

Posted: 21 Apr 2022 04:05 AM PDT

这是我的多数据源配置

spring:   servlet:     multipart:       max-file-size: 64MB       max-request-size: 64MB   jpa:     hibernate:       ddl-auto: update     show-sql: true     properties:       hibernate:         naming:           physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl         primary-dialect: org.hibernate.dialect.PostgreSQLDialect         secondary-dialect: org.hibernate.dialect.PostgreSQLDialect         temp:           use_jdbc_metadata_defaults: false     database-platform: org.hibernate.dialect.PostgreSQL10Dialect   primary:     datasource:       driver-class-name: org.postgresql.Driver       jdbc-url: jdbc:postgresql://postgres-0.postgres.postgres:5432/platform       username: postgres       password: postgres       # JPA 重连       test-on-borrow: true       validation-query: SELECT 1       # 连接池名称       pool-name: HikariPool-1       ## 最小空闲连接数       minimum-idle: 10       ## 最大连接数       maximum-pool-size: 20       ## 空闲连接存活最大时间       idle-timeout: 30000       ## 最长生命周期       max-lefetime: 30000       ## 连接超时时间       connection-timeout: 30000   secondary:     datasource:       driver-class-name: org.postgresql.Driver       jdbc-url: jdbc:postgresql://postgres-0.postgres.postgres:5432/keycloak       username: postgres       password: postgres       # JPA 重连       test-on-borrow: true       validation-query: SELECT 1       # 连接池名称       pool-name: HikariPool-1       ## 最小空闲连接数       minimum-idle: 10       ## 最大连接数       maximum-pool-size: 20       ## 空闲连接存活最大时间       idle-timeout: 30000       ## 最长生命周期       max-lefetime: 30000       ## 连接超时时间       connection-timeout: 30000 

这是我的 JAVA 多数据源

@Configuration public class DataSourceConfig {     private static Logger logger = LoggerFactory.getLogger(DataSourceConfig.class);      @Bean(name = "primaryDataSource")     @Qualifier("primaryDataSource")     @ConfigurationProperties(prefix = "spring.primary.datasource")     @Primary     public DataSource primaryDataSource() {         return DataSourceBuilder.create().build();     }      @Bean(name = "secondaryDataSource")     @Qualifier("secondaryDataSource")     @ConfigurationProperties(prefix = "spring.secondary.datasource")     public DataSource secondaryDataSource() {         return DataSourceBuilder.create().build();     } }  public class PrimaryConfig {     @Autowired     @Qualifier("primaryDataSource")     private DataSource primaryDataSource;      @Primary     @Bean(name = "entityManagerPrimary")     public EntityManager entityManager(EntityManagerFactoryBuilder builder) {         return Objects.requireNonNull(entityManagerFactoryPrimary(builder).getObject()).createEntityManager();     }      @Primary     @Bean(name = "entityManagerFactoryPrimary")     public LocalContainerEntityManagerFactoryBean entityManagerFactoryPrimary(EntityManagerFactoryBuilder builder) {         return builder.dataSource(primaryDataSource)                 .properties(getVendorProperties(primaryDataSource))                 .packages("net.skycloud.platform.licensemanager.model", "net.skycloud.platform.mail.model",                         "net.skycloud.platform.perm.model", "net.skycloud.platform.model")                 .persistenceUnit("primaryPersistenceUnit")                 .build();     }      @Autowired     private JpaProperties jpaProperties;     @Autowired     private HibernateProperties hibernateProperties;     private Map<String, Object> getVendorProperties() {         return hibernateProperties.determineHibernateProperties(jpaProperties.getProperties(), new HibernateSettings());     }      private Map<String, String> getVendorProperties(DataSource dataSource) {         Map<String, String> jpaProperties = new HashMap<>(16);         jpaProperties.put("hibernate.hbm2ddl.auto", "update");         jpaProperties.put("hibernate.show_sql", "true");         //        jpaProperties.put("hibernate.format_sql", "true");         jpaProperties.put("hibernate.dialect", "org.hibernate.dialect.PostgreSQLDialect");         //        jpaProperties.put("hibernate.dialect", "net.skycloud.platform.common.config.PgDialect");         //        jpaProperties.put("hibernate.current_session_context_class",         //        "org.springframework.orm.hibernate5.SpringSessionContext");         return jpaProperties;     }      @Primary     @Bean(name = "transactionManagerPrimary")     public PlatformTransactionManager transactionManagerPrimary(EntityManagerFactoryBuilder builder) {         return new JpaTransactionManager(entityManagerFactoryPrimary(builder).getObject());     } }  public class SecondaryConfig {     @Autowired     @Qualifier("secondaryDataSource")     private DataSource secondaryDataSource;      @Bean(name = "entityManagerSecondary")     public EntityManager entityManager(EntityManagerFactoryBuilder builder) {         return Objects.requireNonNull(entityManagerFactorySecondary(builder).getObject()).createEntityManager();     }      @Bean(name = "entityManagerFactorySecondary")     public LocalContainerEntityManagerFactoryBean entityManagerFactorySecondary(EntityManagerFactoryBuilder builder) {         return builder.dataSource(secondaryDataSource)                 .properties(getVendorProperties(secondaryDataSource))                 .packages("net.skycloud.platform.user.keycloakmodel")                 .persistenceUnit("secondaryPersistenceUnit")                 .build();     }      @Autowired     private JpaProperties jpaProperties;      @Autowired     private HibernateProperties hibernateProperties;      @Autowired     private Environment env;      private Map<String, Object> getVendorProperties() {         return hibernateProperties.determineHibernateProperties(jpaProperties.getProperties(), new HibernateSettings());     }      private Map<String, String> getVendorProperties(DataSource dataSource) {         Map<String, String> jpaProperties = new HashMap<>(16);         jpaProperties.put("hibernate.hbm2ddl.auto", "none");         jpaProperties.put("hibernate.show_sql", "true");         //        jpaProperties.put("hibernate.format_sql", "true");         //        jpaProperties.put("hibernate.dialect", "org.hibernate.dialect.PostgreSQLDialect");         jpaProperties.put("hibernate.dialect", "net.skycloud.platform.common.config.PgDialect");         //        jpaProperties.put("hibernate.current_session_context_class",         //        "org.springframework.orm.hibernate5.SpringSessionContext");         return jpaProperties;     }      //用来作为数据库事务回滚的限定词     //@Transactional(rollbackFor = OAPMException.class, value = "transactionManagerSecondary")     //事务管理器     @Bean(name = "transactionManagerSecondary")     PlatformTransactionManager transactionManagerSecondary(EntityManagerFactoryBuilder builder) {         return new JpaTransactionManager(entityManagerFactorySecondary(builder).getObject());     } } 

当我再 k8s 上重启 postgres 时,访问任意接口报错如下:

javax.persistence.PersistenceException: org.hibernate.exception.GenericJDBCException: could not prepare statement 	at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:154) 	at org.hibernate.query.internal.AbstractProducedQuery.list(AbstractProducedQuery.java:1626) 	at org.hibernate.query.internal.AbstractProducedQuery.getSingleResult(AbstractProducedQuery.java:1665) 	at net.skycloud.platform.user.repository.UserDao.findOneByUsername(UserDao.java:121) 	at net.skycloud.platform.user.service.UserService.getUserInfo(UserService.java:184) 	at net.skycloud.platform.user.service.UserService$$FastClassBySpringCGLIB$$e1720440.invoke(<generated>) 	at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218) 	at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:783) 	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) 	at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:753) 	at org.springframework.validation.beanvalidation.MethodValidationInterceptor.invoke(MethodValidationInterceptor.java:123) 	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) 	at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:753) 	at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:698) 	at net.skycloud.platform.user.service.UserService$$EnhancerBySpringCGLIB$$a191bfb.getUserInfo(<generated>) 	at net.skycloud.platform.user.api.rest.UserController.getCurrentUser(UserController.java:358) 	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) 	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) 	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) 	at java.base/java.lang.reflect.Method.invoke(Method.java:566) 	at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:205) 	at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:150) 	at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:117) 	at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:895) 	at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:808) 	at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) 	at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1067) 	at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:963) 	at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006) 	at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:898) 	at javax.servlet.http.HttpServlet.service(HttpServlet.java:645) 	at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883) 	at javax.servlet.http.HttpServlet.service(HttpServlet.java:750) 	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:227) 	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) 	at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53) 	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) 	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) 	at org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter.doFilterInternal(WebMvcMetricsFilter.java:96) 	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) 	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) 	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) 	at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) 	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) 	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) 	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) 	at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:197) 	at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97) 	at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:541) 	at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:135) 	at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92) 	at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78) 	at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:687) 	at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:360) 	at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:399) 	at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65) 	at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:889) 	at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1743) 	at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) 	at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191) 	at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659) 	at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) 	at java.base/java.lang.Thread.run(Thread.java:834) Caused by: org.hibernate.exception.GenericJDBCException: could not prepare statement 	at org.hibernate.exception.internal.StandardSQLExceptionConverter.convert(StandardSQLExceptionConverter.java:42) 	at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:113) 	at org.hibernate.engine.jdbc.internal.StatementPreparerImpl$StatementPreparationTemplate.prepareStatement(StatementPreparerImpl.java:186) 	at org.hibernate.engine.jdbc.internal.StatementPreparerImpl.prepareQueryStatement(StatementPreparerImpl.java:151) 	at org.hibernate.loader.Loader.prepareQueryStatement(Loader.java:2122) 	at org.hibernate.loader.Loader.executeQueryStatement(Loader.java:2059) 	at org.hibernate.loader.Loader.executeQueryStatement(Loader.java:2037) 	at org.hibernate.loader.Loader.doQuery(Loader.java:956) 	at org.hibernate.loader.Loader.doQueryAndInitializeNonLazyCollections(Loader.java:357) 	at org.hibernate.loader.Loader.doList(Loader.java:2868) 	at org.hibernate.loader.Loader.doList(Loader.java:2850) 	at org.hibernate.loader.Loader.listIgnoreQueryCache(Loader.java:2682) 	at org.hibernate.loader.Loader.list(Loader.java:2677) 	at org.hibernate.loader.custom.CustomLoader.list(CustomLoader.java:338) 	at org.hibernate.internal.SessionImpl.listCustomQuery(SessionImpl.java:2186) 	at org.hibernate.internal.AbstractSharedSessionContract.list(AbstractSharedSessionContract.java:1204) 	at org.hibernate.query.internal.NativeQueryImpl.doList(NativeQueryImpl.java:177) 	at org.hibernate.query.internal.AbstractProducedQuery.list(AbstractProducedQuery.java:1617) 	... 61 common frames omitted Caused by: java.sql.SQLException: Connection is closed 	at com.zaxxer.hikari.pool.ProxyConnection$ClosedConnection.lambda$getClosedConnection$0(ProxyConnection.java:515) 	at com.sun.proxy.$Proxy155.prepareStatement(Unknown Source) 	at com.zaxxer.hikari.pool.ProxyConnection.prepareStatement(ProxyConnection.java:337) 	at com.zaxxer.hikari.pool.HikariProxyConnection.prepareStatement(HikariProxyConnection.java) 	at org.hibernate.engine.jdbc.internal.StatementPreparerImpl$5.doPrepare(StatementPreparerImpl.java:149) 	at org.hibernate.engine.jdbc.internal.StatementPreparerImpl$StatementPreparationTemplate.prepareStatement(StatementPreparerImpl.java:176) 	... 76 common frames omitted 

试了很多办法没有解决,各位大佬我该怎么解决这个问题?

基于 WebRTC 做的音视频通话,但是在几款硬件设备上声音比较小,该从哪些方面优化呢?

Posted: 21 Apr 2022 04:00 AM PDT

如何完全卸载 ie

Posted: 21 Apr 2022 03:52 AM PDT

测试给了个 ie9 的 bug ,但是系统自带的是 ie11,准备重装一下,就在开始才看点了一下卸载 ie,然后重装 ie9 提示"已有更高版本的 ie",去 C 盘的 program/files 文件夹下果然还有 ie 的文件夹,这就很尴尬,现在 windows 附件里面没有 ie ,不知道怎么打开 ie ,同时也没法完全卸载,求助各位大佬该怎么操作

bytes.Buffer 包的疑惑

Posted: 21 Apr 2022 03:37 AM PDT

bytes.Buffer 包的疑惑 请教各位一个问题,bytes.Buffer 包,在使用 bytes.NewBuffer 时候是如何调用到这个 func (b *Buffer) String() string 这个方法呢? 因为看源码 bytes.NewBuffer 要求传入一个[]byte 类型,返回一个 Buffer 类型,这个是如何调用到 String()这个方法,把[]byte 转换为 string 的呢? s1 是一个字符串类型 s2 := ([]byte(s1)) x := bytes.NewBuffer(s2) fmt.Println(x) //显示出来是字符串,肯定是上面方法的作用,但是不知道是如果调用的 研究了半天,可能是我太菜,所以来请教各位

装 Linux 必须占用整个 u 盘吗

Posted: 21 Apr 2022 03:37 AM PDT

一直都是直接烧写,现在手里有个装了 PE 系统的 u 盘,里面还有几个 win 镜像,不想被抹掉啊

后端开发现在值得入手 MacBook Pro 14 吗

Posted: 21 Apr 2022 03:17 AM PDT

目前用的是 2016 年购入的 Thinkpad T460
最近想换机器了,不玩游戏,也没用过 Mac 系统
使用 Macbook 的小伙伴说说开发体验如何
JetBrains 全家桶支持的怎么样
目前技术栈主要是 Java Go Docker
也考虑过其他机器,甚至是台式机(预算 1 万左右)
很纠结不玩游戏的话有没有必要上游戏本

备选如下:
新出的 ThinkBook 14+
华硕天选 3 12 代英特尔酷睿 i7 15.6 英寸游戏笔记本电脑(i7-12700H 16G 512G RTX3060 2.5K 165Hz 100%P3)灰
ROG 魔霸新锐 2022 15.6 英寸 2.5K 165Hz 游戏本笔记本电脑(R9-6900HX 液金导热 16G 512G RTX3060 140W)
联想(Lenovo)拯救者刃 7000K 2022 游戏电脑主机(12 代 i5-12400F RTX3060 12GB LHR 显卡 16G DDR5 512G SSD)

关于如何在 js 中根据<微博 ID>获取<用户 ID>的讨论~~

Posted: 21 Apr 2022 03:09 AM PDT

问题描述

  • 需求: 微博国际版手机分享的 URL 转 PC 版本的 URL

例如:一段手机微博国际版分享的链接: https://share.api.weibo.cn/share/297710421,4757706592224909.html?weibo_id=4757706592224909

需转换成: https://weibo.com/1749127163/Lodd99kND

其中

  • 微博 ID 是: 4757706592224909 或者 Lodd99kND (两者是等价的, 进制不同)
  • 用户 ID 是: 1749127163 (目前缺获取这个的方法)

为什么有这个需求

手机版本的分享链接在 PC 上看的不方便, 所以需要转换到 PC 版本.

目前已知的可行性办法

研究了一番, 可以通过国际版链接的微博 ID 在 chrome 匿名模式获取 UID,

  • 例: 在 chrome 的匿名模式下访问以下 url, 然后在 html 中获取到对应的用户 ID (普通模式下由于有 cookie 信息, 无法打开下面的链接)

  • https://weibo.com/8888/4757706592224909 (只能无 cookie 或匿名模式访问)

以上办法, 需要在匿名模式打开 URL 才能获取 html 的数据,

迫于不知道 JS 如何仿照 chrome 开启匿名 https 请求, 所以 js 小白来这里发帖求助 JS 如何进行匿名请求.

如果有其他国际版 URL 转换 PC 的方式, 也欢迎提供下, Thanks♪(・ω・)ノ, 超感谢

PHP 和 Serverless 的结合, 大家怎么看

Posted: 21 Apr 2022 02:45 AM PDT

PHP + Serverless

有个系列文章,传统的 PHP 应用可以一键迁移到 Serverless 平台, 没有了机器的概念, 按请求次数收费, 弹性高可用。

美团动态线程池实践思路,开源了

Posted: 21 Apr 2022 12:35 AM PDT

大家好,今天我们来聊一个比较实用的话题,动态可监控的线程池实践,全新开源项目(DynamicTp)地址在下方,欢迎 star 交流学习。


项目地址

gitee 地址https://gitee.com/yanhom/dynamic-tp

github 地址https://github.com/lyh200/dynamic-tp


系列文章

动态线程池框架( DynamicTp ),监控及源码解析篇

动态线程池( DynamicTp )之动态调整 Tomcat 、Jetty 、Undertow 线程池参数篇

美团动态线程池实践思路开源项目( DynamicTp ),线程池源码解析及通知告警篇


写在前面

稍微有些 Java 编程经验的小伙伴都知道,Java 的精髓在 juc 包,这是大名鼎鼎的 Doug Lea 老爷 子的杰作,评价一个程序员 Java 水平怎么样,一定程度上看他对 juc 包下的一些技术掌握的怎么样,这也是面试中的基本上必问的一些技术点之一。

juc 包主要包括:

1.原子类( AtomicXXX )

2.锁类( XXXLock )

3.线程同步类( AQS 、CountDownLatch 、CyclicBarrier 、Semaphore 、Exchanger )

4.任务执行器类( Executor 体系类,包括今天的主角 ThreadPoolExecutor )

5.并发集合类( ConcurrentXXX 、CopyOnWriteXXX )相关集合类

6.阻塞队列类( BlockingQueue 继承体系类)

7.Future 相关类

8.其他一些辅助工具类

多线程编程场景下,这些类都是必备技能,会这些可以帮助我们写出高质量、高性能、少 bug 的代码,同时这些也是 Java 中比较难啃的一些技术,需要持之以恒,学以致用,在使用中感受他们带来的奥妙。

上边简单罗列了下 juc 包下功能分类,这篇文章我们主要来介绍动态可监控线程池的,所以具体内容也就不展开讲了,以后有时间单独来聊吧。看这篇文章前,希望读者最好有一定的线程池 ThreadPoolExecutor 使用经验,不然看起来会有点懵。

如果你对 ThreadPoolExecutor 不是很熟悉,推荐阅读下面两篇文章

javadoop: https://www.javadoop.com/post/java-thread-pool

美团技术博客: https://tech.meituan.com/2020/04/02/java-pooling-pratice-in-meituan.html


背景

使用 ThreadPoolExecutor 过程中你是否有以下痛点呢?

1.代码中创建了一个 ThreadPoolExecutor ,但是不知道那几个核心参数设置多少比较合适

2.凭经验设置参数值,上线后发现需要调整,改代码重启服务,非常麻烦

3.线程池相对开发人员来说是个黑盒,运行情况不能感知到,直到出现问题

如果你有以上痛点,动态可监控线程池( DynamicTp )或许能帮助到你。

如果看过 ThreadPoolExecutor 的源码,大概可以知道其实它有提供一些 set 方法,可以在运行时动态去修改相应的值,这些方法有:

public void setCorePoolSize(int corePoolSize); public void setMaximumPoolSize(int maximumPoolSize); public void setKeepAliveTime(long time, TimeUnit unit); public void setThreadFactory(ThreadFactory threadFactory); public void setRejectedExecutionHandler(RejectedExecutionHandler handler); 

现在大多数的互联网项目其实都会微服务化部署,有一套自己的服务治理体系,微服务组件中的分布式配置中心扮演的就是动态修改配置, 实时生效的角色。那么我们是否可以结合配置中心来做运行时线程池参数的动态调整呢?答案是肯定的,而且配置中心相对都是高可用的, 使用它也不用过于担心配置推送出现问题这类事儿,而且也能减少研发动态线程池组件的难度和工作量。

综上,可以总结出以下的背景

  • 广泛性:在 Java 开发中,想要提高系统性能,线程池已经是一个 90%以上的人都会选择使用的基础工具

  • 不确定性:项目中可能会创建很多线程池,既有 IO 密集型的,也有 CPU 密集型的,但线程池的参数并不好确定;需要有套机制在运行过程中动态去调整参数

  • 无感知性,线程池运行过程中的各项指标一般感知不到;需要有套监控报警机制在事前、事中就能让开发人员感知到线程池的运行状况,及时处理

  • 高可用性,配置变更需要及时推送到客户端;需要有高可用的配置管理推送服务,配置中心是现在大多数互联网系统都会使用的组件,与之结合可以大幅度减少开发量及接入难度


简介

我们基于配置中心对线程池 ThreadPoolExecutor 做一些扩展,实现对运行中线程池参数的动态修改,实时生效; 以及实时监控线程池的运行状态,触发设置的报警策略时报警,报警信息会推送办公平台(钉钉、企微等)。 报警维度包括(队列容量、线程池活性、拒绝触发、任务执行等待超时等);同时也会定时采集线程池指标数据供监控平台可视化使用。 使我们能时刻感知到线程池的负载,根据情况及时调整,避免出现问题影响线上业务。

特性

  • 参考美团线程池实践,对线程池参数动态化管理,增加监控、报警等增强功能

  • 基于 Spring 框架,现只支持 SpringBoot 项目使用,轻量级,引入 starter 即可使用

  • 基于配置中心实现线程池参数动态调整,实时生效;集成主流配置中心,已支持 Nacos 、Apollo 、Zookeeper 、Consul , 同时也提供 SPI 接口可自定义扩展实现

  • 内置通知报警功能,提供多种报警维度(配置变更通知、活性报警、容量阈值报警、拒绝触发报警、任务执行或等待超时报警), 默认支持企业微信、钉钉报警,同时提供 SPI 接口可自定义扩展实现

  • 内置线程池指标采集功能,支持通过 MicroMeter 、JsonLog 日志输出、Endpoint 三种方式,可通过 SPI 接口自定义扩展实现

  • 集成管理常用第三方组件的线程池,已集成 SpringBoot 内置 WebServer ( Tomcat 、Undertow 、Jetty )的线程池管理

  • 提供任务包装功能,实现 TaskWrapper 接口即可,如 TtlTaskWrapper 可以支持线程池上下文信息传递

  • JUC 普通线程池也可以被框架监控(@DynamicTp )

  • 参考 Tomcat 线程池提供了 io 密集型场景使用的 EagerDtpExecutor


架构设计

主要分四大模块

  • 配置变更监听模块:

    1.监听特定配置中心的指定配置文件(已实现 Nacos 、Apollo 、Zookeeper 、Consul ),可通过内部提供的 SPI 接口扩展其他实现

    2.解析配置文件内容,内置实现 yml 、properties 配置文件的解析,可通过内部提供的 SPI 接口扩展其他实现

    3.通知线程池管理模块实现刷新

  • 线程池管理模块:

    1.服务启动时从配置中心拉取配置,生成线程池实例注册到内部线程池注册中心以及 Spring 容器中

    2.监听模块监听到配置变更时,将变更信息传递给管理模块,实现线程池参数的刷新

    3.代码中通过依赖注入(推荐)或者 getExecutor()方法根据线程池名称来获取线程池实例

  • 监控模块:

    实现监控指标采集以及输出,默认提供以下三种方式,也可通过内部提供的 SPI 接口扩展其他实现

    1.默认实现 JsonLog 输出到磁盘,可以自己采集解析日志,存储展示

    2.MicroMeter 采集,引入 MicroMeter 相关依赖,暴露相关端点

    3.暴雷自定义 Endpoint 端点,可通过 http 方式实时访问

  • 通知告警模块:

    对接办公平台,实现通知告警功能,默认实现钉钉、企微,可通过内部提供的 SPI 接口扩展其他实现,通知告警类型如下

    1.线程池主要参数变更通知

    2.阻塞队列容量达到设置的告警阈值

    3.线程池活性达到设置的告警阈值

    4.触发拒绝策略告警,格式:A/B ,A:该报警项前后两次报警区间累加数量,B:该报警项累计总数

    5.任务执行超时告警,格式:A/B ,A:该报警项前后两次报警区间累加数量,B:该报警项累计总数

    6.任务等待超时告警,格式:A/B ,A:该报警项前后两次报警区间累加数量,B:该报警项累计总数


使用

1.引入对应配置中心的依赖

2.配置中心配置线程池实例,配置参考下文(给出的是全配置项,配置项都有默认值)

3.启动类加 @EnableDynamicTp 注解

4.使用 @Resource 或 @Autowired 注解注入,或通过 DtpRegistry.getExecutor("name")获取

5.普通 JUC 线程池想要被监控,可以 @Bean 定义时加 @DynamicTp 注解

6.tips:动态线程池实例服务启动时会根据配置中心的配置动态注册到 Spring 容器中,建议不要用 @Bean 编程式重复声明同一线程池实例,直接配置在配置中心就行

7.详细参考下文及 Example 示例

  • maven 依赖
  1. apollo 应用用接入用此依赖

        <dependency>         <groupId>io.github.lyh200</groupId>         <artifactId>dynamic-tp-spring-boot-starter-apollo</artifactId>         <version>1.0.4</version>     </dependency> 
  2. spring-cloud 场景下的 nacos 应用接入用此依赖

        <dependency>         <groupId>io.github.lyh200</groupId>         <artifactId>dynamic-tp-spring-cloud-starter-nacos</artifactId>         <version>1.0.4</version>     </dependency> 
  3. 非 spring-cloud 场景下的 nacos 应用接入用此依赖

        <dependency>         <groupId>io.github.lyh200</groupId>         <artifactId>dynamic-tp-spring-boot-starter-nacos</artifactId>         <version>1.0.4</version>     </dependency> 
  4. zookeeper 配置中心应用接入

        <dependency>         <groupId>io.github.lyh200</groupId>         <artifactId>dynamic-tp-spring-boot-starter-zookeeper</artifactId>         <version>1.0.4</version>     </dependency> 

    application.yml 需配置 zk 地址节点信息

        spring:       application:         name: dynamic-tp-zookeeper-demo       dynamic:         tp:           config-type: properties         # zookeeper 只支持 properties 配置           zookeeper:             config-version: 1.0.0             zk-connect-str: 127.0.0.1:2181             root-node: /configserver/dev             node: dynamic-tp-zookeeper-demo 
  5. spring-cloud-starter-zookeeper-config 应用接入

        <dependency>         <groupId>io.github.lyh200</groupId>         <artifactId>dynamic-tp-spring-cloud-starter-zookeeper</artifactId>         <version>1.0.4</version>     </dependency> 

    注:配置中心配置文件参考 example-zookeeper-cloud/resource 下的 config.txt ,该文件可以通过ZKUI工具导入到Zookeeper

  6. spring-cloud-starter-consul-config 应用接入

        <dependency>         <groupId>io.github.lyh200</groupId>         <artifactId>dynamic-tp-spring-cloud-starter-consul</artifactId>         <version>1.0.4</version>     </dependency> 

    注:配置中心配置文件参考 example-consul-cloud/resource 下的 dynamic-tp-cloud-consul-demo-dtp.yml

  • 线程池配置( yml 类型)

    spring:   dynamic:     tp:       enabled: true       enabledBanner: true        # 是否开启 banner 打印,默认 true       enabledCollect: false      # 是否开启监控指标采集,默认 false       collectorType: logging     # 监控数据采集器类型( JsonLog | MicroMeter ),默认 logging       logPath: /home/logs        # 监控日志数据路径,默认 ${user.home}/logs       monitorInterval: 5         # 监控时间间隔(报警判断、指标采集),默认 5s       nacos:                     # nacos 配置,不配置有默认值(规则 name-dev.yml 这样),cloud 应用不需要配置         dataId: dynamic-tp-demo-dev.yml         group: DEFAULT_GROUP       apollo:                    # apollo 配置,不配置默认拿 apollo 配置第一个 namespace         namespace: dynamic-tp-demo-dev.yml       configType: yml            # 配置文件类型       platforms:                 # 通知报警平台配置         - platform: wechat           urlKey: 3a7500-1287-4bd-a798-c5c3d8b69c  # 替换           receivers: test1,test2                   # 接受人企微名称         - platform: ding           urlKey: f80dad441fcd655438f4a08dcd6a     # 替换           secret: SECb5441fa6f375d5b9d21           # 替换,非 sign 模式可以没有此值           receivers: 15810119805                   # 钉钉账号手机号       tomcatTp:                                    # tomcat web server 线程池配置           minSpare: 100           max: 400       jettyTp:                                     # jetty web server 线程池配置           min: 100           max: 400       undertowTp:                                  # undertow web server 线程池配置           coreWorkerThreads: 100                   # 核心线程数           maxWorkerThreads: 400                    # 最大线程数           workerKeepAlive: 40                            executors:                                   # 动态线程池配置,都有默认值,采用默认值的可以不配置该项,减少配置量         - threadPoolName: dtpExecutor1           executorType: common                     # 线程池类型 common 、eager:适用于 io 密集型           corePoolSize: 6           maximumPoolSize: 8           queueCapacity: 200           queueType: VariableLinkedBlockingQueue   # 任务队列,查看源码 QueueTypeEnum 枚举类           rejectedHandlerType: CallerRunsPolicy    # 拒绝策略,查看 RejectedTypeEnum 枚举类           keepAliveTime: 50           allowCoreThreadTimeOut: false           threadNamePrefix: test                         # 线程名前缀           waitForTasksToCompleteOnShutdown: false        # 参考 spring 线程池设计           awaitTerminationSeconds: 5                     # 单位( s )           preStartAllCoreThreads: false                  # 是否预热核心线程,默认 false           runTimeout: 200                                # 任务执行超时阈值,目前只做告警用,单位( ms )           queueTimeout: 100                              # 任务在队列等待超时阈值,目前只做告警用,单位( ms )           taskWrapperNames: ["ttl"]                          # 任务包装器名称,集成 TaskWrapper 接口           notifyItems:                     # 报警项,不配置自动会按默认值配置(变更通知、容量报警、活性报警、拒绝报警、任务超时报警)             - type: capacity               # 报警项类型,查看源码 NotifyTypeEnum 枚举类               enabled: true               threshold: 80                # 报警阈值               platforms: [ding,wechat]     # 可选配置,不配置默认拿上层 platforms 配置的所以平台               interval: 120                # 报警间隔(单位:s )             - type: change               enabled: true             - type: liveness               enabled: true               threshold: 80             - type: reject               enabled: true               threshold: 1             - type: run_timeout               enabled: true               threshold: 1             - type: queue_timeout               enabled: true               threshold: 1 
  • 线程池配置( properties 类型),具体请看 example-zookeeper 项目下的配置文件

  • 定义线程池 Bean (可选),建议直接配置在配置中心;但是如果想后期再添加到配置中心,可以先用 @Bean 声明(方便依赖注入)

    @Configuration public class DtpConfig {        /**    * 通过{@link DynamicTp} 注解定义普通 juc 线程池,会享受到该框架监控功能,注解名称优先级高于方法名    *    * @return 线程池实例    */   @DynamicTp("commonExecutor")   @Bean   public ThreadPoolExecutor commonExecutor() {       return (ThreadPoolExecutor) Executors.newFixedThreadPool(1);   }    /**    * 通过{@link ThreadPoolCreator} 快速创建一些简单配置的动态线程池    * tips: 建议直接在配置中心配置就行,不用 @Bean 声明    *    * @return 线程池实例    */   @Bean   public DtpExecutor dtpExecutor1() {       return ThreadPoolCreator.createDynamicFast("dtpExecutor1");   }    /**    * 通过{@link ThreadPoolBuilder} 设置详细参数创建动态线程池(推荐方式),    * ioIntensive ,参考 tomcat 线程池设计,实现了处理 io 密集型任务的线程池,具体参数可以看代码注释    *    * tips: 建议直接在配置中心配置就行,不用 @Bean 声明    * @return 线程池实例    */   @Bean   public DtpExecutor ioIntensiveExecutor() {       return ThreadPoolBuilder.newBuilder()               .threadPoolName("ioIntensiveExecutor")               .corePoolSize(20)               .maximumPoolSize(50)               .queueCapacity(2048)               .ioIntensive(true)               .buildDynamic();   }    /**    * tips: 建议直接在配置中心配置就行,不用 @Bean 声明    * @return 线程池实例    */   @Bean   public ThreadPoolExecutor dtpExecutor2() {       return ThreadPoolBuilder.newBuilder()               .threadPoolName("dtpExecutor2")               .corePoolSize(10)               .maximumPoolSize(15)               .keepAliveTime(15000)               .timeUnit(TimeUnit.MILLISECONDS)               .workQueue(QueueTypeEnum.SYNCHRONOUS_QUEUE.getName(), null, false)               .waitForTasksToCompleteOnShutdown(true)               .awaitTerminationSeconds(5)               .buildDynamic();   } } 
  • 代码调用,从 DtpRegistry 中根据线程池名称获取,或者通过依赖注入方式(推荐,更优雅)

    1 )依赖注入方式使用,优先推荐依赖注入方式,不能使用依赖注入的场景可以使用方式 2

    @Resource private ThreadPoolExecutor dtpExecutor1;  public void exec() {    dtpExecutor1.execute(() -> System.out.println("test")); } 

    2 )通过 DtpRegistry 注册器获取

    public static void main(String[] args) {    DtpExecutor dtpExecutor = DtpRegistry.getDtpExecutor("dtpExecutor1");    dtpExecutor.execute(() -> System.out.println("test")); } 
  • 更详细使用实例请参考example工程


注意事项

  • 服务启动时会根据配置中心配置的 executors 动态生成线程池实例注册到 spring 容器中,动态线程池建议直接配置在配置中心中, 同一线程池实例不要用 @Bean 编程式重复配置,虽然会覆盖掉

  • 阻塞队列只有 VariableLinkedBlockingQueue 类型可以修改 capacity ,该类型功能和 LinkedBlockingQueue 相似, 只是 capacity 不是 final 类型,可以修改,VariableLinkedBlockingQueue 参考 RabbitMq 的实现

  • 启动看到如下日志输出证明接入成功

    |  __ \                            (_) |__   __| | |  | |_   _ _ __   __ _ _ __ ___  _  ___| |_ __ | |  | | | | | '_ \ / _` | '_ ` _ | |/ __| | '_ \ | |__| | |_| | | | | (_| | | | | | | | (__| | |_) | |_____/ __, |_| |_|__,_|_| |_| |_|_|___|_| .__/          __/ |                              | |         |___/                               |_|  :: Dynamic Thread Pool ::  DynamicTp register dtpExecutor, source: beanPostProcessor, executor: DtpMainPropWrapper(dtpName=dynamic-tp-test-1, corePoolSize=6, maxPoolSize=8, keepAliveTime=50, queueType=VariableLinkedBlockingQueue, queueCapacity=200, rejectType=RejectedCountableCallerRunsPolicy, allowCoreThreadTimeOut=false) 
  • 配置变更会推送通知消息,且会高亮变更的字段

    DynamicTp refresh, name: [dtpExecutor2], changed keys: [corePoolSize, queueCapacity], corePoolSize: [6 => 4], maxPoolSize: [8 => 8], queueType: [VariableLinkedBlockingQueue => VariableLinkedBlockingQueue], queueCapacity: [200 => 2000], keepAliveTime: [50s => 50s], rejectedType: [CallerRunsPolicy => CallerRunsPolicy], allowsCoreThreadTimeOut: [false => false] 

通知报警

  • 触发报警阈值会推送相应报警消息(活性、容量、拒绝、任务等待超时、任务执行超时),且会高亮显示相应字段

告警

  • 配置变更会推送通知消息,且会高亮变更的字段

变更通知


监控

监控数据

通过 collectType 属性配置监控指标采集类型,默认 logging

  • MicroMeter:通过引入相关 MicroMeter 依赖采集到相应的平台 (如 Prometheus ,InfluxDb...)

  • Logging:定时采集指标数据以 Json 日志格式输出磁盘,地址 ${logPath}/dy namictp/${appName}.monitor.log

    {"datetime": "2022-04-17 11:35:15.208", "app_name": "dynamic-tp-nacos-cloud-demo", "thread_pool_metrics": {"activeCount":0,"queueSize":0,"largestPoolSize":0,"poolSize":0,"rejectHandlerName":"CallerRunsPolicy","queueCapacity":2000,"fair":false,"queueTimeoutCount":0,"rejectCount":0,"waitTaskCount":0,"taskCount":0,"runTimeoutCount":0,"queueRemainingCapacity":2000,"corePoolSize":4,"queueType":"VariableLinkedBlockingQueue","completedTaskCount":0,"dynamic":true,"maximumPoolSize":6,"poolName":"dtpExecutor1"}} {"datetime": "2022-04-17 11:35:15.209", "app_name": "dynamic-tp-nacos-cloud-demo", "thread_pool_metrics": {"activeCount":0,"queueSize":0,"largestPoolSize":0,"poolSize":0,"rejectHandlerName":"CallerRunsPolicy","queueCapacity":2000,"fair":false,"queueTimeoutCount":0,"rejectCount":0,"waitTaskCount":0,"taskCount":0,"runTimeoutCount":0,"queueRemainingCapacity":2000,"corePoolSize":2,"queueType":"TaskQueue","completedTaskCount":0,"dynamic":true,"maximumPoolSize":4,"poolName":"dtpExecutor2"}} {"datetime": "2022-04-17 11:35:15.209", "app_name": "dynamic-tp-nacos-cloud-demo", "thread_pool_metrics": {"activeCount":0,"queueSize":0,"largestPoolSize":0,"poolSize":0,"queueCapacity":2147483647,"fair":false,"queueTimeoutCount":0,"rejectCount":0,"waitTaskCount":0,"taskCount":0,"runTimeoutCount":0,"queueRemainingCapacity":2147483647,"corePoolSize":1,"queueType":"LinkedBlockingQueue","completedTaskCount":0,"dynamic":false,"maximumPoolSize":1,"poolName":"commonExecutor"}} {"datetime": "2022-04-17 11:35:15.209", "app_name": "dynamic-tp-nacos-cloud-demo", "thread_pool_metrics": {"activeCount":0,"queueSize":0,"largestPoolSize":100,"poolSize":100,"queueCapacity":2147483647,"fair":false,"queueTimeoutCount":0,"rejectCount":0,"waitTaskCount":0,"taskCount":177,"runTimeoutCount":0,"queueRemainingCapacity":2147483647,"corePoolSize":100,"queueType":"TaskQueue","completedTaskCount":177,"dynamic":false,"maximumPoolSize":400,"poolName":"tomcatWebServerTp"}} 
  • 暴露 EndPoint 端点(dynamic-tp),可以通过 http 方式请求

    [     {         "dtp_name": "remoting-call",         "core_pool_size": 6,         "maximum_pool_size": 12,         "queue_type": "SynchronousQueue",         "queue_capacity": 0,         "queue_size": 0,         "fair": false,         "queue_remaining_capacity": 0,         "active_count": 0,         "task_count": 21760,         "completed_task_count": 21760,         "largest_pool_size": 12,         "pool_size": 6,         "wait_task_count": 0,         "reject_count": 124662,         "reject_handler_name": "CallerRunsPolicy"     },     {         "max_memory": "228 MB",         "total_memory": "147 MB",         "free_memory": "44.07 MB",         "usable_memory": "125.07 MB"     } ] 

介绍文章

美团动态线程池实践思路,开源了

动态线程池框架( DynamicTp ),监控及源码解析篇

动态线程池( DynamicTp ),动态调整 Tomcat 、Jetty 、Undertow 线程池参数篇

美团动态线程池实践思路开源项目( DynamicTp ),线程池源码解析及通知告警篇


联系我

对项目有什么想法或者建议,可以加我微信拉交流群,或者创建 issues ,一起完善项目

公众号:CodeFox

微信:yanhom1314

有办法把 Desktop 版的 SSH banner 换成 Server 的样式吗

Posted: 20 Apr 2022 11:22 PM PDT

就是在命令区域前面的文字,我不太清楚叫什么(但是在 OpenWrt 上是由一个 banner 文件控制的),想问一下如何改成服务器的样式。

nginx 限制访问频率 [WordPress]

Posted: 20 Apr 2022 11:19 PM PDT

在本地测试静态 html 文件的时候问没有问题。

静态 html 时 nginx 配置

在 http 块

limit_req_zone $binary_remote_addr zone=req_zone:1m rate=1r/m; 

1 分钟限制访问 1 次

在 server 块

    location /test.html {         root /home/admin/www;         limit_req zone=req_zone;         }          

python 使用 requests 压测,除了第一次返回 200 ,其余都是 503 。

而在 wordpress 的 nginx 里面: http 块不变,

server 块如下:

    location ~ \.php$ {         root           /var/www/html;         fastcgi_pass   php-mysql:9000;         fastcgi_index  index.php;         fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;         include        fastcgi_params;     }          location ~ \wp-login.php$ {      limit_req zone=req_zone;      }  

同样的压测代码,发现限制不起作用。 请教下运维的高手同学,哪里出问题了。

如何解决 dto input output 数量太多的问题

Posted: 20 Apr 2022 09:36 PM PDT

比如我一个表接口 crud ,要出来 4 个 input 4 个 output , 后端有没啥减少一些这些对象的办法,像 typescript 那种一样, 通过 omit partial 这些约束, 但是有些表单验证又是在 input 里面。

密集多次 http 请求外部接口怎么操作比较好?

Posted: 20 Apr 2022 09:35 PM PDT

先行感谢各位大佬解答。背景如下:
#1 本人 Java 菜鸡一枚。所以有些问题可能有些白痴。
#2 一个 Spring Boot 的 Web 应用,一台阿里金融云 ECS ( 2 core 8GB ),后接一台阿里金融云 RDS ,无 redis 等中间件。
#3 目标场景是用户从页面提交业务数据后,后端要将业务数据写入 MySQL ,并组装 Xml 报文后 http 发往第三方接口,从第三方接口获取返回数据后,再返回给 Web 页面,也就是说这个过程中,Web 页面是一直在等待后端同步返回结果的(有进度条样式一直在转)。而这个场景有 10%的部分是要拆分成最多 30 份,对应地去请求外部接口 30 次的( 30 份数据来自一次页面提交)。
#4 外部接口性能较差,一次请求耗时平均为 5s 左右,请求高峰期可能会达到 20s 。

因为业务的特性,会倾向于用户在页面点击提交按钮后很快( 30s 内)得到后端返回的处理结果。所以如果 30 份数据用串行的方式去请求外部接口,那最理想情况也是 5 * 30s=150s 了。所以问题是:
#1 能不能通过 CompletableFuture ,注入自定义的线程池(而非 JVM 的线程池),同时开启 30 个线程去并行执行这些外部请求。简单测试过外部接口对于并发请求的表现,100 个并发请求,1/10 响应用时 5s ,1/10 响应用时 10 几 s...最后 1/10 响应用时需要 40s (可接受)。
#2 上述方案一个 Web 提交就可能要开启 30 个线程,虽然这种需要开启 30 个线程的页面提交基本上不会一下子进来两个。但是!如果真的就有两个或者三个客户在同时触发了这个场景,需要考虑些什么吗?避免带来不可预料的异常或者崩溃。
#3 上述方案如果不可行的话,有没有更合理的解决方案?期望是用户页面同步得到结果,不要异步的....会增加复杂性,搞不动了...

多谢各位,帮忙孩子...

tailwind 样式问题

Posted: 20 Apr 2022 09:25 PM PDT

求教大佬们 tailwind npm run serve 和 npm run build 里打包出来的 整体效果看起来不太一样 打包的感觉内容上整体大点,字号也粗点。。大概会是什么情况导致的呢

安卓开机动画如何反转 y 轴

Posted: 20 Apr 2022 09:20 PM PDT

公司一个项目采购换了新安卓屏,板子接线要反过来才方便,我写了个小脚本自动替换开机动画,请问audio_conf.txt文件中有什么参数可以反转 y 轴么

FastAPI 跨域工作不正常, CORS 可以支持二级域名吗?

Posted: 20 Apr 2022 08:24 PM PDT

在 github pages 的网页里调用自己的 fastapi 后端,需要单独设置一下 CORS 。目前后端使用下面的代码是能正常工作的

from fastapi.middleware.cors import CORSMiddleware  app = FastAPI()  app.add_middleware(     CORSMiddleware,     allow_origins=["*", ],     allow_credentials=True,     allow_methods=["*"],     allow_headers=["*"], ) 

但是将*号改成自己的网址的话却会显示请求被 cors 拒绝,搞不太清楚哪步出了问题

    allow_origins=["https://my.github.io", ] # 改成这样以后就访问不了了 

是因为 cors 只支持 https://*.github.io 这种写法吗?还是后端反向代理的原因,请求经过 nginx 反向代理到 fastapi 以后 origin 变了?

团队乱糟糟,要不要主动站出来?

Posted: 20 Apr 2022 08:25 AM PDT

情况是这样的,我是公司老员工,手下 5,6 个人吧。

以前直接跟老板汇报的,老板不懂技术,去年空降了个技术总监。后面向技术总监汇报。

空降的技术总监为了很多事情抓在自己手里做了很多业务&技术上的调整(包括招了一些自己人)。

其实我是能理解的,我只是拿工资干活,不参与这些事情。技术总监也觉得我 ok ,是值的信任的,有些时候还站在我这边。

遇到一些问题,在群里我都当透明人,尽量解答。

但是最近团队的业务进度明显出了一些问题(开发慢,问题多),因为分工的问题(划了几条业务线),其它业务线是他的新人在做,我不能参与,但其实如果让我或者带着他新人做能很快解决并上线。(技术总监希望他的人能掌握一些业务)

我觉得这里面浪费卷了大量时间。现在我不知道怎么搞了,到底要不要跟老板反馈?一方面是老板对我比较信任,同时我对公司也比较有感情,希望事情能做好。但是另一方面我不想得罪技术总监

No comments:

Post a Comment