Golang中检测 Data Race

Data Race概念 Data Race (数据竞争),是并发系统中最常见和最难调试的错误类型之一,当两个线程(协程)同时访问同一个变量并且至少其中一次访问是写入时,就会发生 Data Race 。Java 内存模型(JMM)定义了明确的 Happens-Before 规则和提供 Volatile 机制来初步保证程序的执行顺序,Go内存模型(GMM)同样也有相同的规则定义,此外 Golang 还提供了一种检测 Data Race 的机制,帮助开发者更好的分析并发代码的正确性。 go在1.1版本时支持data race 检测。 Data Race 和 Race Condition 的区别: 这两个概念非常容易混淆,stackoverflow上也有讨论这个问题,其实我认为这篇文章很好的区别了它们,所以我进行了翻译。 检测 Data Race 使用 -race 参数可以用来检测这种竞态: $ go test -race mypkg // to test the package $ go run -race mysrc.go // to run the source file $ go build -race mycmd // to build the command $ go install -race mypkg // to install the package func main() { c := make(chan int) m := make(map[string]int) go func() { m["a"] = 1 // 访问map冲突 c <- 1 }() m["a"] = 2 // 访问map冲突 <-c for k, v := range m { fmt....

2024-06-20 · 5 min · 873 words · Hank

Data Race vs Race Condition

原文地址: https://blog.regehr.org/archives/490, 翻译并略作改动。 竞态条件(race Condition)是当事件的时间或顺序影响程序的正确性时发生的缺陷。一般来说,需要某种外部计时或排序非确定性来产生竞态条件;典型的例子有上下文切换、操作系统信号、多处理器上的内存操作和硬件 中断。 当程序中有两次内存访问时,就会发生数据竞争(Data Race): 目标为同一内存位置 由两个线程同时执行 不是读取操作 不是同步操作 上边这个定义来自微软研究院的 Sebastian Burckhardt。该定义的两个方面需要注意: “同时”意味着没有像锁这样的东西强制一个操作在另一个操作之前或之后发生。 “不是同步操作”是指程序可能包含特殊的内存操作,例如用于实现锁的操作,这些操作本身并不同步。 在实践中,它们两者存在相当大的重合:许多 Race Condition 是由 Data Race 引起的,并且许多 Data Race 导致 Race Condition。另一方面,两者也可以相互独立,可能产生没有 Data Race 的 Race Condition,也可能产生没有 Race Condition 的 Data Race。 让我们从一个在两个银行账户之间转移资金的简单函数开始: transfer1 (amount, account_from, account_to) { if (account_from.balance < amount) return NOPE; account_to.balance += amount; account_from.balance -= amount; return YEP; } 当然,这并不是银行真正转移资金的方式,但这个例子非常有用。我们知道,账户余额应该是非负的,并且转移之后不能凭空创造(多出)或损失(丢失)金钱。当在没有外部同步的情况下从多个线程调用时,该函数会产生 Data Race(多个线程可以同时尝试更新帐户余额)和 Race Condition(在并行上下文中它将创造或损失金钱)。 我们可以尝试这样修复它: transfer2 (amount, account_from, account_to) { atomic { bal = account_from....

2024-06-18 · 2 min · 235 words · Hank

Golang中singleflight的实现原理

我们知道了singleflight的用法,使用 singleflight 我们可以抑制多个请求,极大地节约带宽、增加系统吞吐量、提升性能。那么,singleflight 底层是如何实现的呢?本文我们来分析一番。 整体结构 singleflight 的核心是将同时段的多个请求抑制,只有一个请求能够真正请求资源,其他请求都阻塞。上一篇提到,singleflight的公开api仅包括: Group 对象: 它表示处理"相同数据"的一系列工作,在这里“重复请求”将会被抑制 Result 对象: 表示执行真正业务逻辑的结果对象 Do 方法: 执行请求抑制 DoChan 方法: 与Do相同,只是结果返回 <-chan Result 从这些api我们大致可以知道,调用 Do 或者 DoChan 方法就是我们所述的核心“请求”,可以猜测: 对于同一个 key,首先调用的会执行真正的逻辑,方法返回之前的后续所有相同的 key 调用都会阻塞,当第一个请求返回后,阻塞的这些调用就直接使用其返回值作为自己的返回值了。 顺着上述猜想的逻辑,我们看看singleflight的源码实现。代码不多,算上注释一共就200来行,我们来一一分析。 Group struct 首先看看 Group 的代码: type Group struct { mu sync.Mutex // protects m m map[string]*call // lazily initialized } Group 表示处理相同数据的一系列工作,这些工作存储到一个 map[string]*call 的结构中,为了保证并发安全,Group 内部持有 sync.Mutex 锁用来保护这个 map 的读写。 Group 有一个非常重要的两个方法 Do 和 DoChan, 在上一篇已经介绍过了。 再来回顾一下 Do 方法的定义: func (g *Group) Do(key string, fn func() (interface{}, error)) (v interface{}, err error, shared bool) 已经介绍过,这里再详细看看这些参数:...

2024-06-10 · 4 min · 657 words · Hank

使用 Let‘S Encrypt 为您的网站申请免费证书

网站的https证书过期了,一直使用阿里云的免费ssl证书,但是现在阿里云调整了策略,证书有效期从1年缩短到3个月了,所以我决定放弃阿里云转而使用 Let’s Encrypt 申请免费证书。 简介 Let’s Encrypt 是一家免费、开放、自动化的公益性证书颁发机构(CA), 由互联网安全研究组(ISRG)运作,详细介绍可以看这里。 申请证书 按照官方文档,对于没有命令行访问权限的网站,比如wordpress、cPanel等,可以通过控制台设置或者申请后手动上传证书;而我的网站服务器托管在阿里云,拥有命令行访问权限,所以直接使用官方推荐的 Certbot ACME 客户端来管理证书,这里详细记录下步骤。 CertBot 文档有详细的安装步骤,我的 ubuntu14 上的步骤如下: 安装 snapd snap 是linux的应用程序包,可以从其自身的snap store中安装、管理软件,有点类似包管理器如 apt、yum,但是不依赖linux发行版,安全、跨平台且无依赖;而 snapd 则是一个自动管理和维护 snap 的后台服务,它们之间的区别请参阅官方文档。 Ubuntu 16.04LTS版及之上的版本已经集成了snapd,无需再安装了,而我的是 14,所以需要手动安装。 sudo apt update sudo apt install snapd 测试是否安装成功,可以安装官方的 hello-world 程序: $ snap install hello-world 2024-06-09T16:15:39+08:00 INFO Waiting for automatic snapd restart... hello-world 6.4 from Canonical✓ installed 执行并成功输出信息表示安装成功: $ hello-world Hello World! 删除 certbot-auto 和任何 Certbot OS 软件包 如果您使用操作系统包管理器(如 apt 、 dnf 或 yum 安装了任何 Certbot 包,则应在安装 Certbot snap 之前删除它们,以确保运行命令 certbot 时,将使用 snap,而不是从操作系统包管理器进行安装。执行此操作的确切命令取决于您的操作系统,但常见的示例是 sudo apt-get remove certbot 、 sudo dnf remove certbot 或 sudo yum remove certbot 。...

2024-06-09 · 2 min · 374 words · Hank

Golang中singleflight的用法

在开发过程中,尤其是web server,有时候我们需要并发的请求数据,对于某些数据,同时发起的多个请求其实拿到的数据都是相同的,如果不处理这类请求,那么每个请求都会获取一遍数据,这无疑是对资源的浪费。比如要从数据库查询相同 id 的一条数据,并发的多个请求都会执行一次 sql 语句,增加了数据库的压力。 有没有一种方案,当多个请求同时发起后,只有第一个请求去获取数据,在它返回之前,其他请求都各自阻塞等待直到真正执行数据获取的请求返回后,直接拿它的结果?我们把这种将同时发起的多个请求转为一个请求去执行真正业务逻辑的情况称为“请求抑制”。在 Go 中,singleflight 就是用来处理请求抑制的。 简介 singleflight 包全路径为 golang.org/x/sync/singleflight, 目前版本是 v0.7.0。 前边提到,singleflight 用于抑制同一时间获取相同数据的重复请求。当存在多个重复请求时,singleflight 保证只有一个请求能执行,其他请求阻塞,直到前边的请求返回后直接将其返回值共享(shared)给阻塞的这些请求。 在使用之前,首先要理解何为"重复请求",如何区分"相同数据"。 相同数据:指并发下当前时间段多个请求获取的数据是完全相同的,比如获取全局的配置、查询天气数据等。 重复请求:指处理相同数据时,在一个请求从发起到返回之前这段时间,又有其他多个请求发起,那么这些请求就是重复请求。 理解了这两点,现在我们来看看 singleflight 的用法。 用法 singleflight 整体设计比较简单,公开的 api包括: Group 对象: 它表示处理"相同数据"的一系列工作,在这里“重复请求”将会被抑制 Result 对象: 表示执行真正业务逻辑的结果对象 Do 方法: 执行请求抑制,后文详述 DoChan 方法: 与Do相同,只是结果返回 <-chan Result Do 方法 Do 方法表示执行请求抑制,其定义如下: func (g *Group) Do(key string, fn func() (interface{}, error)) (v interface{}, err error, shared bool) { // ... } 首先,它需要在 singleflight.Group 实例下执行,用于抑制这一组请求。 两个参数:...

2024-05-18 · 2 min · 408 words · Hank

自由与梦想

今天终于读完了《我在北京送快递》一书,这本15万字的书前前后后读完居然花了两个多月的时间——习惯使然,我读什么书都喜欢字斟句酌,这一点让我有些苦恼:一方面是因为进度非常缓慢,想象一下,读一本人物传记我还在拿着笔一边阅读一边给不认识的字词标注笔记……,另一方面,由于阅读的周期很长,导致容易半途而废,可能前半部分阅读的很仔细,后半部分就走马观花甚至放弃了。不过这本书我还算比较认真的读完了,我想谈一谈我对“自由”的理解。 作者在尾声的“后记”一章中阐述了自己对“自由”的观点:这些年来自己经常处于一边工作、一边写作的生活状态中,工作时无法全心投入写作,而写作时也无法积极投身工作,作者自己希望的自由是“在高度发展上的个人意识的个人追求和自我实现”。他认为,追求自己热衷的事情,形成与别人不同的自我意识上的区别,那么这种追求是自由的;相反,表面上看似自由,却并没有实现自我价值,只是迫于某种原因而不得不去做,又或者是安于现状,非个人追求或无法实现自我,那么这些都不能算是真正的自由。 作者写到,在迫于生计而不得不付诸大量时间和精力在工作上时,自己便无法专注于自己喜欢的事情——写作,自身也被疲倦、困意、复杂的人际关系所折磨,,身心俱疲,就算挣到了钱但自己并不快乐,这种生活不能算是自由;而之后回到老家,全身心投入写作,不必为生计而担忧,每天作者自己喜欢的事情——阅读、写作、与其他作者交流写作创意等等,自己的阅读量、写作水平都有很大的提升,这在知识的高度上与其他人拉开了差距、形成了区别,自身的追求有了一定的成果,自己也觉得开心、快乐,这样的生活是自由的。 我很喜欢作者的这个观点。简而言之,作者所述的自由,在我看来是“做自己想做的事,走自己想走的路”。 每个人心中都会有梦想,那就是我们最想做的事情,最想走的路,只是多数时候我们为生活所迫东奔西走,仿佛随波逐流的沙粒,被生活磨平了棱角,早就忘记了自己的梦想是什么了。**“人们只有两条路,一条需要用心走,叫做梦想;另一条需要用脚走,叫做现实。”**诚然,现实与梦想仿佛总是相对立的两面。对于大多数人而言,梦想往往是高不可攀的,只能深埋于心,为了生计不得不放弃梦想而从事自己并不喜欢的工作的人不计其数,为追逐梦想而奋斗此生最终获得成功的人寥寥无几。 在人生的道路上,现实与梦想常常交织着,而在这交织之中,自由似乎成了一种奢侈。对许多人而言,梦想是一种遥不可及的幻影,而现实却是残酷的枷锁,将他们紧紧束缚。在这样的环境下,自由似乎只存在于幻想之中。 普通的农民每天的工作就是完成农务,整日与农田、二十四节气打交道,农忙时努力播种、收获,农闲时与朋友们一起打打牌、聊聊天。我相信,他们大多觉得生活本就如此,自给自足也挺好,除非生活实在拮据迫不得已才会跑出去打工赚钱。此时,或许他们早已习惯了这样的生活,早已忘记年轻时候自己想要做的事情了。对于他们而言,无论忙碌或是清闲,可能都不重要,年复一年重复着同样的事情,循规蹈矩地过着自己的生活,这就是最简单的自由和幸福吧。 在车水马龙的城里,白领们的知识水平比普通农民要高出许多,他们终日奔波忙碌,所选择的工作就是自己所追求的梦想吗?我相信大多连“事业”都称不上。他们所做的只是“为老板打工”,赚取自己的劳动报酬罢了,他们的工作心态总是“尽力完成我能力范围内的工作”的得过且过,很少有人会将自己的工作真正的当成一份事业来打拼,因为就算你再努力,那也是为别人赚钱,对自己并没有多大意义。所以,大多数白领会默默地选择平庸,只是在偶尔看到少数人成功之后才能激起心中的激情,也许那只是害怕自己被周围人超越的恐慌,然后疯狂地努力一段时间,随后逐渐发现无论自己多努力似乎都无法提升自己到一个新的高度,慢慢难以坚持最后果断放弃并又再次平庸下去,这就样反反复复,最终一无所成。 当我们回首自己的生活时,是否还记得小时候那个满怀梦想的自己?那个对未来充满信心、对世界充满好奇的自己是否还在内心深处?然而,随着年岁的增长,生活的压力和社会的现实往往会让我们逐渐放弃那些美好的梦想。我们被迫在现实的洪流中奔波,为了生计为了生活,不得不选择一条看似安稳但缺少激情的道路。 梦想的实现谈何容易?马云当初创业时,初创团队“十八罗汉”吃住都其家里,十几人就挤在几十平方的房子里,他们没日没夜的工作,饿了就吃泡面,困了就打地铺小睡一会儿……但是,团队人人都充满着激情,没有一个人轻言放弃,因为他们知道他们现在是在为了梦想而奋斗,奋斗着并奋不顾身。就在阿里巴巴上市的那一天,这十八罗汉人人都身价过亿、功成名就、梦想成真。回望这一路坎坷,我想他们应该是无怨无悔了吧?至少,为了梦想,再苦再累也都是甘之如饴吧。这样的生活,看似处处是桎梏,却总能令人无限神往,这又何尝不是一种自由呢?马云当然属于成功者,这些年义无反顾的追梦,等到如今大获成功之后,可能他才发现原来自己的人生还有其他更想做的事情,所以他开办学校、拍电影、搞演唱会……有钱真任性,但是他有任性的资本,试问此时,为所欲为的马云是否获得了真正的自由呢?答案只有他自己知道,看似风光无限,可能高处不胜寒的寂寞只有他自己懂得了。 当我们审视自己的生活,回顾梦想与现实的交织,或许我们会发现,自由并非遥不可及的幻影,而是一种内心的坚定和勇气。在生活的征途中,或许我们会因为生计而忙碌,会因为社会的压力而迷失,但只要我们保持对梦想的信念,不忘初心,奋力前行,那梦想的光芒就会一直照亮前行的路。或许成功并非是终点,而是一种过程,而真正的自由,其实就是坚持不懈地追寻内心的信仰,敢于实现自己的梦想,敢于选择一条不一样的道路。因为只有在追逐梦想的路上,我们才能找到真正属于自己的自由和幸福。愿我们都能保持对梦想的热爱,勇敢追寻内心的声音,活出真正的自己,活出自由的精彩人生。

2024-04-26 · 1 min · 11 words · Hank

这本区块链爱好者必读作品终于迎来第三版

学习区块链的书籍我推荐这两本,我在这里已经推荐过一次: Mastering Bitcoin Mastering Ethereum 比特币开创了一门新的技术——区块链,而以太坊则在比特币的理念基础上进行再创新和升华,二者是近十多年来区块链技术的代表。因此,学习区块链技术,绝对离不开它们。恰好,这两本书就完全阐述了比特币和以太坊的技术。 这两本书都由同一名作者编写,他是一名有十多年区块链研究经验的爱好者,两本书的传播度非常广,是学习、研究区块链技术的必读作品。 《Mastering Ethereum》翻译为《精通以太坊》,目前该书可以在 github 上免费阅读,地址在这里,翻译版在这里。 《Mastering Bitcoin》中文译作《精通比特币》,本书详细介绍了比特币底层的实现逻辑,目前版本是第二版。但是随着比特币2017、2021两次的重大升级,添加了隔离见证、Taproot、Schnorr签名等重大改进,不论是逻辑上还是技术上都有着很大的学习难度,第二版是在2017升级之前刊印的,现在未免显得有点过时了。如果你想学习比特币的底层知识,可以在这里阅读到本书的中文版。 让人兴奋的是,2023年《Mastering Bitcoin》迎来了它的第三个版本,它由原作者 Andreas Antonopoulos 和另一位区块链爱好者 David Harding 联合编著,在第二版的基础上补全了这几年比特币的多个升级技术。可惜的是,本书目前还没有中文版本,但你可以直接在 github 上阅读英文版,地址在这里。 另外,为了便于阅读,我也fork了本书,编译并搭建了在线阅读网站,打开即可直接阅读,可以选择单页阅读整书也可以分章节阅读。 区块链技术的学习是一个枯燥和费力的过程,但历史的车轮总是向前推进的,技术在进步,坚持下来者方能迎接蜕变。 相关阅读: Bitcoin’s Taproot Upgrade: What You Should Know https://github.com/ajtowns/taproot-review

2024-03-20 · 1 min · 34 words · Hank

ios如何同步obsidian笔记仓库?

对于技术人而言,经常需要记录、整理大量的笔记内容,形成自己的知识库。一款好的笔记软件我认为需要具备以下几个条件: 必须:跨平台,同时支持桌面电脑(Windows,Mac,Linux)和手机(Android,iOS) 必须:支持同步,在多台设备中打开任何一台都能接接着编写笔记 必须:实时存储,就算突然断电、司机也不会丢失已写笔记内容 必须:支持代码高亮,更便于友好地阅读代码 必须:支持 Markdown 格式,快速编写文档必备格式,谁用谁知道 必须:支持多重备份,最好是本地一份、远端一份,首选支持git同步的 可选:支持双向链接,这样笔记与笔记之间就可以形成关联关系,慢慢积累后就形成了自己的知识库 可选:支持笔记导出,比如导出 pdf 等格式,便于分享,如果能一键发布到常见博客如 hexo、wordpress、jekyll 更好 我用过诸多笔记软件,但都存在或多或少的问题,无法满足上述要求,后来一直使用网易的有道笔记,它支持一键保存笔记,不过编辑器实在难用,markdown的图片要自己搭建图床。 阮一峰推荐的笔记软件是 github.dev,可以看看他的这篇文章。不过我更喜欢原生的app软件,我选择 obsidian,因为它免费而且支持上述的大部分需求,尤其是第6点,对于不想将笔记存储保存给笔记软件厂商而言非常好,直接使用git同步。 现在,我基本上都是用 obsidian 来记笔记和写文章,它支持双向链接,很容易形成知识体系,而且具备丰富的插件支持,具体特性可以自己咨询网络。如果对于格式非常多的文章,我也会使用 AsciiDoc 格式来编写,相比于 Markdown 它更加强大,但是语法也更复杂。Obsidian 目前并不支持,非常遗憾。 在使用 Obsidian 时,最大的问题就是手机端的同步。我是用 Github 存储笔记,手机端没有很好的同步方案,官方的同步方式无法满足需求,而且需要付费。我的需求是,macOS、windows、iPhone、iPad 四种设备上需要从 github 同步我的笔记,没有安卓端同步的需要,所以我使用 iSH 这个 app,它开源免费,支持 ios,完美的解决了我的问题。 什么是 iSH 它是一个在 ios 上模拟 Linux 环境的 app,使用的是 alpine linux,在你的 iPhone、iPad上都可以运行并创建 Linux Shell 环境。正好我可以使用它来同步 git 仓库到我的 ios 设备上。 安装 ish app 从 ios 应用商店下载 iSH Shell,注意看名称和logo: 下载好后打开,你就得到了一个 Linux Shell 环境。整体界面与 Shell 一样,这里重点说一下工具栏: Tab:点击后可以自动补全命令 Ctrl:pc 的 ctrl 健,当你需要执行 Ctrl + C 时需要用到 Esc:pc 的 Esc健 Arrows:点击后上下左右拖动可以实现 pc 的方向键的功能,常用的是向上拖动调出最新一条历史记录命令 提示! 下边的操作,需要输出一些命令,手机上输入不太方便,记得点击工具栏这个 Tab,可以自动补全。...

2024-01-06 · 2 min · 348 words · Hank

2023年终回顾和2024新年展望

每至年终,总慨时光飞逝,悔不该浪费光阴而不自知。欲提笔总结一年之种种,却感人生多琐事、岁月少芳华,生活不过柴米油盐酱醋茶。常人者,虽一年之大事鲜有,却亦欲略举一二。 2023年,整个国际形势依旧不太妙,俄乌冲突、巴以冲突、缅甸内战,各国都在想法设法为自己牟利,战乱频发,新冠疫情之后本来就衰弱的国际经济形势更是雪上加霜。逐年变暖的气候,给我们的家园带来了严峻的挑战:火灾、洪水、泥石流、地震、台风…… 2023国际十大新闻 2023中国十大新闻 作为一介庶民,本该独善其身,”各人自扫门前雪,莫管他人网上霜“,然人是社会人,践行之何其难也! 博客 这一年,撰写的文章倒是很少。一方面,感觉没什么可写的,可能自己正慢慢变得懒散了;另一方面,每每要提笔写些什么,发现对要写的东西还了解的不够透彻,只好搁浅。新的一年,得试着多见多听多写,多思考。这是本年的文章列表: 又一个大佬辞世,技术人该何去何从? Redis集群中的 CROSSSLOT Keys Error Nginx Bad Gateway和no live upstreams错误分析 依赖注入库wire使用入门 使用cli框架开发CLI程序 (译)Go1.13中处理错误 (译)Go错误是值 (译)Go模块:管理依赖项 (译)Go 中的字符串、字节、符文和字符 (译)数组、切片和字符串 - “append” 原理 技能 2023除了继续 golang 之外,也复习了一下 python3,包括: 阅读了《Python编程 从入门到实践 第2版》,里边基础讲解不是很透彻,实例倒是不错 廖雪峰的Python教程 语言都是互通的,但建议技术人至少掌握一门静态语言(Java、Go、Rust等)和一门动态脚本语言(Python,Ruby等),以便应对不同的场景,比如使用脚本语言快速开发一些小工具。2024年的工作中应该会大量使用 Go 和 Python。 2024打算加强区块链的课程,包括: 北大肖臻区块链课程 精读《精通以太坊》,之前只是泛读了一遍 精读《精通比特币》 读书是充实自我、提升自我最好的方式。 2023年读了几本书: 《Go语言精进之路1》:掌握go语言编程思想的必要书籍,尤其是从 java 转 go,很多套路与 java 不同,参考本书可以快速掌握 go 的一些编程技巧,本书还有下册,准备2024研究 《朱元璋传》:描写朱元璋的一生,讲述其如何从一个被逼进入寺庙混口饭吃的和尚,到建国当皇帝开辟传奇道路。本书内容较多,适合泛读。 《任正非传》:讲述了任正非与华为的故事,想要了解华为、了解任正非的人必读。 《苏东坡传》:林语堂的作品,讲述大才苏东坡传奇的人生故事。读此书除了了解苏轼一生大才却屡遭贬谪、坎坷的一生,更应该学习其豁达、乐观的人生境界。 《乡土中国》:目前正在读,看费孝通用浅显的文字讲述什么是中国乡土社会。 2024年打算读这几本书: 《MySQL技术内幕:InnoDB存储引擎》 《Go语言精进之路2》 《我在北京送快递》,豆瓣评分很高,了解北漂一簇的辛酸历程。 《三体》:中国科幻巅峰作品,电视剧看了没感觉,必须来读书了。 《人性的弱点》 读一本经济学的图书,拒绝做经济小白 我的豆瓣读书:https://book.douban.com/people/hankmo/ 生活 2023年,生活上平平淡淡才是真,没什么起起落落的故事。 作为技术人,总有或这或那的职业病,现在也不想去想这些问题了,快乐生活每一天,因为明天和意外永远不知道哪一个先到,管他作甚?我们要做的,就是努力锻炼身体,保持充足的睡眠,勤于自律,戒骄戒躁!...

2024-01-02 · 1 min · 77 words · Hank

又一个大佬辞世,技术人该何去何从?

陈皓,网名左耳朵耗子,技术圈亲切地称他为“耗子叔”、“皓哥”,是一位资深技术大咖、骨灰级程序员,前阿里云资深架构师、天猫开发总监、亚马逊高级研发经理、汤森路透基础架构师和高级研发经理,他的博客酷壳每篇文章的阅读量高达数十万,其发布的专栏课程购买量也近20万,可见其影响力惊人! 然而,这位技术大佬在2023年5月13日因心梗而与世长辞,享年47岁。英年早逝,无不令听者痛心、见者流泪! 我很早都在拜读他的文章了,之前他一直在csdn撰写了大量的技术分享类博文,后来又自建博客。其博文范围广泛,涉及架构、技术、管理、生活方方面面,其文风犀利、见解独到、个性鲜明,每每读之均令人深受启发。这位技术传道者除了爱写作、爱分享,而且还一直处于不断学习、提升自己的状态,其生前博客还发表着GoLang、Rust、区块链等等技术的文章,实乃吾辈之楷模! 有段时间没有翻阅老师的博客了,昨天一打开发现老师并没有更新文章,最近的一篇仍然停留在《是微服务架构不香还是云不香?》 翻阅博客之时,偶然看到评论中说老师此时的消息,难以置信,无比痛心,不得不感叹命运多变、世事无常。 作为技术人的我们,用技术改变着这个世界,却无法避免被这个世界所抛弃。一方面,技术人要被公司体制所折磨,公司的利益高于一切,技术只为公司的利益服务,只要有利可图,随时可以要求技术人加班加点干活,而不管需求是否合理、技术是否可达;另一方面,技术人大多是一个隐藏于后台的微小角色,他们不受重视、不被尊重,肩负着强于身体承受能力数倍的工作量,却因为一些技术问题被公司批评,甚至辞退。加班、熬夜、饮食不规律、运动少、职业病、亚健康等等词汇都是贴在每一个技术人身上的标签,年轻时不断地过度消耗着身体,殊不知随着年龄的增长,这些“负债”总是要一点点偿还,甚至是付出生命的代价。 正如陈皓在其痛恨手册中所述:“ 痛恨各种不从研发团队出发,不从团队和项目出发的流程、方法论、咨询师、SQA、流程部门。 痛恨那些为所欲为的,为了自己商业目标牺牲用户利益的中国IT企业。 痛恨中国的C2C式的那种简单的抄袭和复制。 ……” 在这喧杂的社会,我们不得不沉下心来思考:我们会不会是下一个陈皓?我们又该何去何从呢?推荐大家阅读《软技能:代码之外的生存指南》一书,该书从职业规划、自我营销、自我学习提升个人专业技能,到理财、健身、精神来修炼自身体质等多个层面向我们展示了技术人应该如何进行技能、身心双修。 这里大致罗列作者的观点,值得我们深思: 职业:每一位技术人都需要学会管理自己的职业生涯。树立明确的目标,指定可靠、周密的计划,什么时候做什么都经过精心的安排,然后朝着目标努力。 自我营销:你的营销手段决定了你的营销对象是受益还是受损。营销需要人们的关 注,以便让人们关注你,关注你的产品。优秀的营销会将人们的需要或者期待与能够满足此愿望的产品或服务关联起来。“实现价值在先,要求回报在后”。做自己的产品、写博客、发视频,让跟多的人认识你。 学习:“活到老,学到老”。尤其是技术人,在这个飞速变化的世界里,学习的能力是至关重要的。技术人绝不能固步自封,忽视自己的技能发展,否则就会被社会淘汰。 生产力:“外行静坐等待灵感,其他人则唤起激情努力工作”。如何克服拖延症,“做该做的事”,我们需要运用方法、使用工具、培养习惯,提升自己的生产力。 理财:技术人耗费青春拿到较高的薪酬,但是如果不会理财,纵使有万贯家财,最终也可能毁于一旦。 健身:“身体是革命的本钱”,长期的久坐令技术人大多处于亚健康的状态,我们需要健身,只有强健的身体才能更好的发展自己。我认为这是大部分技术人忽略的一项,但它确实重中之重! 精神:我们并不是简单的机器——我们是人类。我们不只是一个与思想相连的躯壳。我们不能只下达指示然后就期望身体能完成这些指令。这个世界存在着另一股很强大的力量,它能带领我们走 上成功之路,把我们推向成功。这种力量为——精神。身体强健了,还要求我们保持良好的精神,乐观、豁达的心态,积极上下的生活态度,都是技术人所必须的。我们不只是只在电脑上敲敲打打的码农,我们更是父母、儿女、丈夫和妻子,积极地融入生活、享受生活吧。 陈皓老师走了,仍然有无数个陈皓前仆后继地为技术而奋斗着。我们执着与技术的同时,别忘了我们仍然是这个社会的一份子,更需要积极、乐观的去享受生活的美好! 谨以此文悼念陈皓老师,一路走好!

2023-12-27 · 1 min · 24 words · Hank

Redis集群中的 CROSSSLOT Keys Error

场景 Redis单节点没有问题,切换到Redis Cluster后业务上某一功能出错: CROSSSLOT Keys in request don't hash to the same slot 出错的代码: var ctx = context.TODO() _, err := uq.queueImpl.rc.TxPipelined(ctx, func(pip redis.Pipeliner) error { cmd := pip.LPush(ctx, uq.key, dest...) if cmd.Err() != nil { return cmd.Err() } return pip.SAdd(context.Background(), uq.setkey, dest...).Err() }) 这段代码的逻辑是向 list 中push一条数据,再向一个 set 加入数据,两步操作通过 pipline 在同一个事务中完成。 问题分析 错误的大概意思就是: 请求中跨槽的key没有被hash到相同的hash槽中。通过代码分析,事务中的两次操作的key并不相同,他们没有被hash到同一个hash槽从而出现上述错误。 什么是hash槽 Redis Cluster 规范中的 Key distribution model(key分布模型)说明如下: Redis集群中将key的存储空间划分为16384个slot,整个集群可以支持最大16384个主节点,实际上建议不超过1000个节点。每一个主节点又可以处理16384个 hash slot。整个集群将hash slot分布于不同的节点组成高可用集群,单个节点有多个副本以便在节点故障时将重新平衡节点,达到高可用的目的。关于可用性保证,可以看这里。 如下的公式用于计算key的hash slot: HASH_SLOT = CRC16(key) mod 16384 Redis将相同hash值的slot分布到同一个node下,如下图所示:...

2023-10-17 · 1 min · 123 words · Hank

Nginx Bad Gateway和no live upstreams错误分析

最近项目的生产环境中客户端出现大量的Nginx 502 Bad Gateway错误,逐步排查最终定位到是由于被ddos攻击造成服务器资源耗尽无法响应造成的问题。遂整理过程著文以记之。 场景 线上4个节点,每个节点都有两个相同服务通过Nginx作负载均衡,均采用Nginx默认值,未做过多配置,配置类似: upstream test-server { server 127.0.0.1:8002; server 127.0.0.1:8001; } 客户端出现大量的 502 Bad Gateway 信息,查看Nginx错误日志,信息如下: no live upstreams while connecting to upstream 初步定位问题,发现后台出现了很多莫名其妙的错误,查看Nginx错误日志发现也打印了很多上述错误。怀疑是后台某个接口请求出错导致返回了 500,导致Nginx将这个服务下线,后经过排查,后台确实存在一些错误信息,但是出错上述错误的时间不匹配。 后整理思路并认真思考,发现可能思路存在偏差:后台http状态码怎么会影响Nginx将服务下线呢? 因为Http状态码表示http响应的状态,也就是表示响应结果的正确与否,比如 2xx 表示服务端正确处理并响应了数据,4xx 表示客户端存在错误妨碍了服务端的处理,5xx 表示服务端错误导致处理失败了。但是,能够返回这些状态码说明服务端连接正常,只是由于特定原因导致服务端响应错误了。返回了这些状态码Nginx当真会将服务下线吗?试想一下,某一个客户端请求查询一条不存在的数据导致服务端处理时抛出异常并响应 500 状态码,然后Nginx认为服务不可用并将其下线,导致一定时间内所有的请求都返回上述错误,那么罪魁祸首到底是服务端还是客户端?Nginx这么处理是不是太过了呢?Nginx肯定不会这么来设计。 所以,我猜测Nginx不可能如此是非不分,应该是别的原因导致的。 查阅upstream官方文档,看到这么两个参数配置: fail_timeout=time: 设置多少时间内不能与下游服务成功通信 max_fails 次后可以认为下游服务不可用,这是一个周期时间,即每隔 fail_timeout 时间都会进行统计,默认为 10 秒。 max_fails=number: 设置在 fail_timeout 时间内与下游服务通信失败的次数,达到该次数后认为服务不可用,默认为1。 按照Nginx的默认设置,也就是说每个10秒统计下有服务的通信失败次数,达到1次就认为服务不可用,此时Ngingx会将其踢下线,后续所有转发到该服务的请求都会返回 no live upstreams while connecting to upstream,直到下一个10秒再重新处理(max_fails 为0又重新将服务上线)。 关键在于这个 通信失败 的理解。通信失败,表示Nginx转发请求给服务,但是服务没有任何响应,而不是一开始怀疑的 HTTP 状态不是200,能成功响应,不论是什么状态码,都应该认为与服务通信成功。 实践才能出真知,为了验证我的猜想,必须进行实验。 实验 使用golang编写一个http服务,代码如下: var inErr bool func main() { port := os....

2023-10-07 · 4 min · 837 words · Hank

依赖注入库wire使用入门

依赖注入(Dependency injection)不是一个新概念了,早在2004年 Martin Fowler 就在其文章Inversion of Control Containers and the Dependency Injection pattern提出了依赖注入模式。在Java的世界中,随处可见依赖注入的使用,如Spring框架的核心之一就是控制反转(IoC),而依赖注入就是 IoC 思想的一种技术实现,比如我们常用的 Spring 的注解 @Autowire,还有 Jsr250规范定义的 @Resource 注解都实现了依赖注入。 简单而言,依赖注入就是以构建松散耦合的程序为目的、以控制反转为基本思想而在软件工程实现的一种编程技术,它使得程序不需要关注对象的创建逻辑,只是通过函数或者属性告诉程序自己需要什么对象,关注所依赖对象的使用逻辑,从而将对象的创建和使用过程分离开。可见,依赖注入技术遵循依赖倒置原则。 虽然 GO 不是面向对象的语言,但是它也有依赖注入实现,常用的依赖注入框架包括 google 自身出品的 wire、Uber的dig、Facebook的inject等等。本文将介绍 wire 的基本使用。 不使用依赖注入时 在开始介绍 wire 之前,我们来编写一个简单的程序,该程序可以创建问候者并向您问好。 我们创建一个 greeter.go 文件,它包含以下几个结构体: type Message string type Greeter struct { Msg Message } type Event struct { Greeter Greeter } Message 表示消息,Greeter 表示问候者,它需要一个 Message,Event 表示一个事件,它用来触发问候。 现在,我们需要增加创建这些结构体的方法: func NewMessage(name string) Message { return Message(fmt.Sprintf("hello, %s!", name)) } func NewGreeter(msg Message) Greeter { return Greeter{Msg: msg} } func NewEvent(g Greeter) Event { return Event{Greeter: g} } func (g Greeter) Greet() Message { return g....

2023-08-06 · 3 min · 608 words · Hank

使用cli框架开发CLI程序

Go语言编译后的程序本身就是一个可用于命令行的可执行文件,而且Go天生支持CLI程序(command line interface),这得益于Go精简的语法以及自身支持Flag来解析命令行的选项参数等。但是,基于Go原始能力开发CLI程序仍然非常繁琐,如解析参数就是一个非常麻烦的工作。幸好,有许多非常强大的库可以用来简化我们的工作: cobra: 一个非常强大的用于构建CLI程序的库,官方地址见这儿 urfave/cli: 另一个使用广泛的CLI开发库,同样足够强大且简单易上手,官方地址见这儿 survey: 一个强大的构建交互式命令行程序的库,详情见这里 在开发CLI之前,你可以阅读Go官方的构建CLI程序指南。本文介绍如何使用 urfave/cli 库开发完整的CLI程序。 CLI程序 命令行界面(CLI,command line interface) 是一种通过用户或客户端发出的命令以及设备或程序以文本行形式做出的响应与设备或计算机程序进行交互的方式。 以上是维基百科的解释,简单而言就是控制台程序,我们需要通过控台执行程序并输入程序内置支持的选项、参数等完成与程序的交互以实现功能。 一般而言,CLI都具备这些功能: 命令:一个CLI程序应该至少支持一个命令,才能用来实现功能,大多CLI都支持多个命令,而且命令下还支持多个的子命令,用来将功能细分 选项:选项分为全局选项和命令选项,全局选项表示对所有命令都可以使用的选项,而命令选项这仅对特定命令有效 参数:CLI支持用户通过控制台传入参数告诉其特定信息,一般情况会通过选项指定参数来区分不同的用途,也可以直接传递给命令 帮助:展示给用户如何使用当前程序的帮助信息 输出:程序处理完成后展示给用户的结果信息 别名:命令和选项都应该支持别名,当命令和选项太长时用来简化输入 当然,CLI还包括程序退出码、错误等信息,不再一一列举。 cli框架简介 urfave/cli 是一个简单、快速且有趣的包,用于在 Go 中构建命令行应用程序。目标是使开发人员能够以富有表现力的方式编写快速且可分发的命令行应用程序。 目前最新支持的版本是 v2,这也是目前使用最广泛、功能强大的版本。 官方使用文档: https://cli.urfave.org/v2/getting-started/ 仓库地址: https://github.com/urfave/cli 接下来,我们将创建一个CLI应用并逐步完善它。 创建应用 创建一个cli目录,然后初始化go模块: $ mkdir cli $ cd cli $ go mod init 编辑 go.mod 文件,将模块名称改为 cli_demo,然后安装 urfave/cli: $ go get github.com/urfave/cli/v2 新建 main.go 文件作为程序的入口,编写代码如下: package main import ( "fmt" "github.com/urfave/cli/v2" "os" ) func main() { cliApp := cli....

2023-07-16 · 4 min · 831 words · Hank

以太坊入门之交易

"上一篇" 介绍了MetaMask钱包的安装和使用,并成功获取到部分测试以太币(ETH)并向另外一个账户发起了一笔转账,这笔转账称之为"交易",我们将在本篇来介绍它。 1. 以太坊的账户 首先,在学习交易之前,我们需要了解以太坊的账户体系。 以太坊的账户分为两种:外部账户和合约账户。 外部账户:外部账户一般由钱包创建,具有私钥,通过私钥控制和区块链网络和智能合约的访问; 合约账户:以太坊中智能合约部署后对应着一个合约账户,合约账户拥有智能合约代码,并受其逻辑控制。 简单而言,外部账户就是区块链的用户通过钱包创建的账户,而合约账户由合约代码编写者所有,对应着一个智能合约,由 EVM 执行。 合约账户和外部账户地址没有明显的区别,都是长度 20字节、160 位、转为十六进制(0x开头)后为 40 个字符的一个串,形如 0x829BD824B016326A401D083B33D092293333A830 两者的区别: 启动交易者(首先发起交易的一方)必须是拥有私钥的外部账户,也就是说只有用户可以启动交易;但是智能合约可以通过调用别的智能合约来对交易做出响应,比如,用户A通过智能合约A发起了一笔交易,虽然智能合约A调用智能合约B也可以发起了一笔交易,但是这整个过程中交易的启动者还是用户A的外部账户 外部账户和合约账户都可以发送和接收区块链货币(如以太币),但是交易目标如果是合约账户,则会使得合约账户拥有的智能合约代码在 EVM 中执行,外部账户则不会 合约账户没有私钥,智能合约编写者通过代码逻辑来保证安全性,外部账户拥有私钥,用户需要通过妥善的保管私钥来保证账户的安全性 2. 以太币单位 交易与货币息息相关,以太坊发行的货币为以太币(Ether, ETH,符号 Ξ),其基本单位为 wei,ETH与wei的关系如下: 1 ETH = 10^18 wei 此外,还有比较常用的 gwei (gigawei): 1 ETH = 10^9 gwei 1 gwei = 10^9 wei Table 1. 以太币的面额和单位 值(以wei为单位) 指数 通用名称 标准名称 1 1 wei wei 1,000 103 babbage kilowei or femtoether 1,000,000 106 lovelace megawei or picoether...

2022-07-16 · 2 min · 373 words · Hank

以太坊入门之MetaMask钱包的安装和使用

区块链中,与用户直接相关的一个重要组件是钱包。用户通过钱包来访问链上的资金、查看账户地址、转账交易等操作。可以说,没有钱包,用户就不能正常访问链上资金,可见其重要程度。而诸多钱包中,MetaMask是一款可以直接在浏览器中使用的、实现了钱包标准 BIP-39 的钱包,也是大多数使用者选择的入门级钱包。 1. 区块链钱包简介 与普通的实物钱包不同,区块链钱包的核心目的是用来保存用户的公钥和私钥,从而保证访问区块链中账户资金的权限,即:证明钱是你的,而不是存储资金。 Figure 1. 区块链钱包(图片来源网络) 钱包的一个重要的误区是:钱包存储资金。其实,钱包并不会直接存储区块链上的资金。可以将其看做一个银行账户,当你需要取钱时,只需告诉银行要取多少钱,然后银行就可以检查账户的余额是否充足并进行取款,账户只是一个逻辑划分,钱始终是存在银行。同理,区块链钱包也只是在区块链上开了一个账户,钱始终在区块链网络中。 开通银行账户,我们必须设置交易密码,已验证你有访问账户的权限;同样的,区块链钱包中,私钥就相当于账户密码,每产生一笔交易时,都需要通过钱包的私钥来进行签名,已验证你拥有操作账户资金的权限。 但是,与银行账户不同,银行是中心化的资金机构,除了验证账户密码,还有其他手段比如身份证来验证取款人身份,忘记密码还可以重置。但在区去中心化的区块链中,私钥是你唯一的凭证,一旦私钥泄露,那么任何人都可以访问你的账户。 因此,钱包可以看成是一个私钥圈,保存了多组公钥私钥对。现在的钱包大多采用随机串生成助记词,再加密生成种子密钥,最后再以种子密钥为根生成密钥树的方式来生成密钥。这里仅说明钱包的概念,关于钱包的底层原理,后续在详细讨论。 Figure 2. 钱包中的密钥树(图片来源网络) 2. 什么是MetaMask MetaMask 是一款简单易用的区块链钱包,除了 App 版本的钱包,它还提供了基于浏览器的插件,包括 Chrome、Firefox等,而且它内置了 Web3,可以直接与以太坊区块链交互并开发 DApp 网页应用,这也是大多开发者选择它的原因。 3. 安装MetaMask 安装 MetaMask 很简单,以 Chrome 为例,直接从 Google 商店安装即可,前提是需要自带梯子。步骤如下: 1、进入google应用商店,直达地址在 这里,也可以自己搜索MetaMask,第一个小狐狸头像的就是 2、点击 "添加至Chrome",然后弹出框点击"添加至扩展"即可,然后会下载 Chrome 插件,耐心等待 3、下载完成后,Chrome会自动安装,完成后会进入扩展插件地址 点击开始使用,进入钱包创建页面 4、创建钱包 如果您以前有钱包,并记得助记词,那么可以直接通过助记词导入钱包,没有则点击"创建钱包"按钮新建,下一步点击"我同意",进入密码设置页面: 设置并牢记自己的密码,以后登录钱包是需要用到,然后点击"创建"按钮后,会出现一个介绍的视频页面,直接点击"下一步" 5、备份助记词 这一步很重要,助记词作为恢复秘钥的唯一手段,需要十分安全地做备份,一般建议手抄写在纸上并保存,其他电子存储方式均存在泄漏风险 什么是助记词? 按照密钥生成策略分,钱包分为两类:非确定性钱包和确定性钱包,前者的每一个私有都是由不同的随机数生成的,私钥间没有任何关联,备份和恢复非常麻烦;而确定性钱包则是通过一个随机串作为种子密钥来生成各种私钥,只要备份种子密钥就可以恢复出所有的私钥。 但是,由于种子密钥长度长而且毫无规律,非常难以记录和输入,因此人们为这个种子密钥按照一定的算法来为其匹配单词表中的一组顺序固定、数量固定(与种子密钥长度有关)的单词,这样便于备份和恢复,这有点类似密码本,这些单词就成为助记词(mnemonic)。 因此,助记词可以看做种子密钥的别名,其安全性与之等同,必须安全保存。 备份好好点击下一步,开始确认助记词 6、确认助记词 按顺序点击刚才备份的助记词,然后确认即可 7、完成创建 现在,所有工作都完成了,您已经拥有了自己的钱包。为了方便使用,可以将 MetaMask 固定到工具栏上,以后就可以直接点击小狐狸按钮打开钱包了: 4. MetaMask的使用 点击工具栏的小狐狸图标,打开MetaMask主界面,如下: 钱包中几个重要概念: 区块链网络:区块链网络表示该钱包可以连接和访问的区块链网络,默认是以太坊主网,MetaMask还自带了以太坊的测试网络,这些测试网络功能与主网络相同,只是以测试为目的而搭建的,可以从测试网络上免费获得测试用的ETH(实际并无任何价值),这些测试网络包括 Ropsten、Rinkeby等。此外,MetaMask 还包括本地客户端的网络 localhost,如果默认网络中没有需要的网络则可以自己添加 账户地址:账户地址是一个 ECDSA 公钥通过 Keccak 哈希计算后最后边的160位(40个十六进制字符)字符,简单而言就是通过你的公钥进行哈希计算后的一个40长度的字符串,相同于银行账户的账号,区块链上的资金往来必须要知道账户地址,并且地址的所有数据(比如交易记录、资金余额等)都是公开透明的。MetaMask中,点击一下账户地址可以快速复制...

2022-06-11 · 1 min · 123 words · Hank

什么是以太坊

上一篇 简单介绍了什么是区块链,也介绍了以太坊的产生和基本概念,其中提到: 以太坊 是一个去中心化的、开源的、图灵完备的、有智能合约功能的区块链开放平台。作为开放平台和”世界超级计算机”,以太坊以智能合约为核心为去中心化应用(DApp)建设提供了整套解决方案。这一篇我们将详细介绍什么是以太坊。 1. 为什么要学习以太坊 以太坊是目前最大的区块链开发平台,也是“世界计算机”。以太坊是区块链开发工作的必经之路,原因如下: 以太坊是目前最大的区块链开发平台,拥有庞大的用户群和社区 以太坊发展早,技术相对成熟,资料、文档众多 以太坊生态体系完备,除了智能合约、DApp、DAO、DeFi,还提供了 ENS、Swarm等DApp构建所需技术体系 以太坊减缓了区块链陡峭的学习曲线,以应用入手,使得区块链的开发上手快速、简单 以太坊与 JavaScript 结合紧密,开发者学习难度小 总之,以太坊好比微信开放平台,DApp开发好比基于微信公众号、小程序的开发,我们不用太关注底层的东西,而是使用以太坊和微信已经提供好的功能来开发自己的DApp和微信公众号,只是以太坊是基于去中心化的区块链,而微信则是中心化应用。 2. 以太坊的诞生 上一篇 介绍了什么是区块链,其中提到, Vitalik Buterin (V神)在2013年发表了《以太坊:一个下一代智能合约和去中心化应用平台》,提出了以太坊的基本构想。 V神是比特币的狂热粉丝,他不断思考着如何扩展比特币和其智能合约协议 Mastercoin,并提出了一种可替代 Mastercoin 的专用合约语言的更灵活和脚本的智能合约机制,这只是一个他的初步构想,尽管比特币团队非常感兴趣,但是这种想法过于超前,而且与比特币的开发计划存在冲突,所以并没有得到支持。 V神 2013年12月,V神发表了 以太坊白皮书,勾勒出以太坊背后的思想:一个图灵完备的通用目的的区块链。以太坊白皮书发布后,不断有区块链爱好者向V神提交并反馈自己的想法,帮助其不断完善这个想法。其中,Gavin Wood(Mastering Ethereum的作者之一)在C++语言上对以太坊给与了极大地帮助,后来成为了以太坊的联合创始人和CTO,并编写了 以太坊黄皮书。 Gavin Wood 白皮书与黄皮书 白皮书由V神编写,旨在提出改进比特币背后区块链技术的一种构想,使其成为一种通用的、适用于多个方面而不仅仅是虚拟货币的区块链技术,这仅仅是一种理论,在以太坊的成长过程中得以逐步实现; 黄皮书是由以太坊CTO Gavin Wood 博士编写,旨在定义以太坊的技术规范,详细描述了以太坊背后的技术实现细节,其中包含了大量的数学公式,比如,黄皮书定义了产生每一笔交易包含的通用字段、如何验证交易,以太坊虚拟机规范(EVM)等等。黄皮书着重于以太坊技术规范的制定,包含大量数学公式,专业性很强,比较难懂,这里[1]有一个面向普通大众的浅黄版本。 Figure 1. 白皮书 vs 黄皮书 V神与Gavin Wood一起,不断完善以太坊的协议层和技术体系,逐步实现“可编程的、安全的、通用的区块链”这一目标。2015年7月30日,第一个以太坊区块被成功挖出,以太坊作为“世界计算机”正式运行并对外提供服务。 3. 以太坊发展的四大阶段 以太坊的发展并不是一帆风顺的,以太坊的发展整体上分为四个阶段 [2],代号分别为:Frontier(边疆)、Homestead(家园)、Metropolis(都会) 和 Serenity(宁静),每一个阶段都以"硬分叉"的方式发布,因此新版本不能与老版本兼容。并且,每一个阶段都在不断完善其新功能和安全性,比如2016年以太坊遭受DAO攻击,而分叉了 "以太坊经典" 和 "以太坊" 这样的相互竞争、并行的以太坊版本。 阶段和硬分叉按照区块高度编号并标注日期,包括: Frontier: 区块高度为0,以太坊的初始阶段,持续时间为2015年7月30日至2016年3月。 Ice Age: 冰河期,区块高度为 200,000,引入指数难度增加的硬分叉,以便在准备就绪时将激励机制过度到Pos(权益证明)。 Homestead:家园,区块高度 1,150,000, 以太坊的第二阶段,发布于2016年3月。 DAO: 区块高度 1192000,以太坊遭受严重的攻击而不得不硬分叉,从而产生了 以太坊经典(Ethereum Classic, ETC) 和 以太坊(ETH) 两个相互竞争的系统 [3]。...

2022-05-09 · 2 min · 238 words · Hank

什么是区块链

区块链是这几年非常火热的一个话题,越来越多的人加入到”区块“大军之中。他们其中一些人,积极投入到挖矿、炒币行列之中,甚至发行自己的数字货币来创收,这部分人社称之为”币圈“;另一部分人,深入探究区块链底层技术,期望不断改进并将区块链技术运用到更多领域中,这部分人社称之为”链圈“。越来越多的人积极群涌入区块链,游弋于“币圈"和"链圈"之间,忙的不亦乐乎。 本文将简单介绍区块链的一些入门知识,并介绍区块链开发所需要具备的基础知识,希望对准备进入区块链的开发者们会有一些帮助。 1. 什么是区块链 区块链(Blockchain)源于 比特币,它其实是对比特币的底层技术的一种扩展和创新。2008年,一位网名“中本聪”的日本大佬发布了一篇名为 《比特币:一种点对点的电子现金系统 的论文(比特币 白皮书),文中总结了之前的数字货币发明,提出了一种完全去中心化的数字货币系统,即比特币。文中并没有提及“区块链”一词,而是使用了“区块”(Block)和“链”(Chain)来描述比特币底层技术,后来人们将区块、链合并在一起,正式提出“区块链”(Blockchian)这一概念。 区块链是一系列技术的组合,包括P2P动态组网、密码学、基于密码学的共享账本、共识机制、智能合约等。简单而言,区块链可以看做一个全球互联的超级账本,链中的每一台计算机通过P2P组网连接并保留了账本的副本,这样,链中的计算机都有一份账本。每一笔交易都需要记录到账本中,当一笔交易产生后,产生这笔交易的计算机将交易信息广播出去,其他链上的计算机就会收到交易信息,它们会进行一系列复杂计算,最终由率先计算完成的计算机将这笔交易信息记录到账本上。具体的计算机制其实是由不同的区块链设计好的,与该区块链的结构相关。比如按照比特币的设计,计算机需要将10分钟内的全部交易打包,再加上一个随机数,然后穷举随机数计算一个256位的哈希值,使得其符合一定的条件,这样就可以获得交易记账权,一旦计算完成并记账,该计算机要快速将计算的区块广播出去,由其他计算机来验证计算的正确性,防止他人篡改。 每产生一笔交易并不是立即就要被区块的各个节点记账,而是按设计要求被打包到一起,比特币设计为每10分钟将所有新交易打包(而以太坊时间更短,大约9秒左右),形成了一个区块(Block),然后将区块广播出去,区块链中的计算机都需要验证区块的正确性,并将其写入自己的账本中,完成记账。区块链则是由一个个相互连接的区块组成的,每一个区块都记录了上一个区块的 HASH 值,第一个区块没有上一个区块的 HASH 值,称之为”创世区块“。计算哈希值竞争记账权的计算机称为“矿工”,计算的过程称为”挖矿“,挖矿成功则获得交易记账权,记录打包好的交易信息并写入最新的区块,称为”出块“,最新出块的矿工可以获得一定的手续费收入。 区块链中的计算机都有账本,那么如果确定各个账本数据的一致性呢?区块链采用”共识机制“来保证大部分诚实计算机的账本一致性,这样就实现了完全去中心化。常用的共识机制有Pow(工作量证明机制,例如挖矿机制)、PoS(权益证明)、DPos(股权授权证明)和分布式一致性算法(如拜占庭容错算法、Paxos、Raft)。 与传统的中心化应用相比,区块链具有如下特点: 去中心化:区块链本质是基于P2P对等网络的分布式系统,链上的每一个节点都存有相同的数据,通过共识机制来决定交易数据的同步,不需要中心化服务器。但是目前,还不能做到完全的去中心化,某些场景还是具备中心化的特征。 高度开放:公有链上的区块链系统通常都是开源的,任何人都可以查询区块链上的数据,智能合约代码高度公开、透明,数据存储与链上所有节点中,不用担心中心化应用的资金安全等一系列问题。 高度可用:区块链应用由区块链上的每一个节点共同维护,任何一个或几个节点宕机不影响整个应用的可用性。 高度安全:区块链技术采用一系列加密措施(如非对称加密、哈希算法、椭圆曲线算法等)来对交易进行加密和签名,保证交易不能被伪造、篡改,然后借助分布式系统中各个节点的共识算法形成强大算力来抵御破坏者的攻击,从而保证链上数据的安全性。 匿名性:区块链上的用户信息都是匿名的,仅对外暴露钱包公钥和地址,就可以完成交易,完全不需要用户的真实身份信息。 2. 什么是以太坊 比特币作为一种虚拟数字货币,为金融领域开创了一种全新的去中心化货币系统。比特币的狂热粉们一直在探索,比特币技术能否使用到其他非金融领域呢? 以太坊 的出现,为区块链技术迎来了崭新的时代。 2013年,比特币的忠实拥护者 Vitalik Buterin (人称V神)提出了一种构想:区块链不应该仅用于金融领域,而是应该开放出来形成一个开放平台,供人们开发更多的去中心化应用,为此他提出了以太坊,并发表了《以太坊:一个下一代智能合约和去中心化应用平台》,也就是后来的 《以太坊白皮书》。 以太坊 是一个去中心化的、开源的、图灵完备的、有智能合约功能的区块链开放平台。 首先,以太坊是一条发行有自己货币(ether,以太币)的区块链;其次,以太坊具备开发去中心化应用功能,它具有能够在以太坊虚拟机(EVM)上运行的 智能合约 功能,开发者可以编写智能合约来自主控制去中心化应用;第三,以太坊优化了PoW(工作量证明机制),并提供了PoS权益证明共识机制,大大加快了区块链交易确认速度(比特币确定一笔交易需要10分钟,而以太坊减少到9秒左右),为去中心化应用开发提供了必要条件。 总之,以太坊是一个区块链开放平台,通过它可以开发自己的区块链 去中心化(Decentralization)应用(Dapps)。 以太坊被称为"第二代的区块链平台", 这个网站 用车站的例子动态、形象地展示了以太坊和比特币之间的一些差别,非常生动有趣。 3. 区块链中的一些基本概念 了解了区块链和以太坊,现在我们来看看区块链中的一些概念。 区块链:Blockchain,共享的分布式账本,交易附加到区块上存储并记录。 区块:Block,多个交易被打包为区块并存储,区块之间相互连接,形成一个链状结构,每一个区块都有一个哈希值加以区分,区块在链中的长度称为区块高度。 钱包:Wallet,存储用户虚拟货币的地方,对外暴露公共地址和公钥,内部包含有绝对私密的私钥信息。 以太坊:Ethereum,一个基于区块链的去中心化运行智能合约的平台,旨在解决与审查,欺诈和第三方干扰相关的问题。 主链:Mainnet,正式运行的区块链的主网络,一个区块链项目经过前期的技术开发后,最终都会发布到区块链主网上线,比如 以太坊主网。 侧链:Sidechains,侧链实质上不是特指某个区块链,而是指遵守侧链协议的所有区块链,该名词是相对于主链而言的。侧链协议是指可以让主币安全地从区块链主链转移到其他区块链,又可以从其他区块链安全地返回主链的一种协议。侧链与主链共存,并可以扩展主链的功能、提高交易速度等,比如ETM就是以太坊ETH的侧链。 测试链:Testnet,与主链功能相同的测试网络,以测试为目的搭建,可以免费从水龙头获得测试币在测试链上进行开发测试,如 币安测试链。 交易:由原始帐户签署的提交到以太坊区块链的数据,并以特定地址为目标。交易包含元数据,例如交易的燃气限额(Gas limit)。 区块链浏览器:查询区块链中数据的工具网站,可以详细查询区块链上的详细信息,如 以太坊浏览器、 币安链浏览器等等。 Dapp:去中心化应用,基于区块链技术开发的应用程序,数据存储于区块链中。 DAO:去中心化自治组织,一种将组织的管理和运营规则以智能合约的形式编码在区块链上,从而在没有集中控制或第三方干预的情况下自主运行的组织形式。DAO 有望成为应对不确定、多样、复杂环境的一种新型有效组织。 以太币:Ether,以太坊生态系统中使用的本地货币,在执行智能合约时承担gas费用,它的符号是 Ξ. 以太坊虚拟机:EVM,Ethereum Virtual Machine, 基于栈的、执行智能合约字节码的虚拟机。 智能合约:Smart Contract,在以太坊的虚拟机中执行的程序,有开发者编写以控制链上业务逻辑。...

2022-04-17 · 1 min · 213 words · Hank

Typora For Mac、Windows破解版和免费版下载

Typora是一个所见即所得(WYSIWYG)的markdown编辑器,由于其快捷、方便,并且配备强大的快捷键来快速编写markdown文档,一经发布就深受大批用户(尤其是程序猿)的喜爱,也是迄今为止我用过的最好用的markdown编辑器了! Typora有如下特性: 轻量级,性能非常高,启动时间在秒级 所见即所得,也是其最大的特性,它只需要一个界面就可以完美展示渲染后的markdown格式,而不需要像大多数markdown编辑器需要两个界面,一个显示源码,一个显示渲染结果 支持表格、UML图、数学公式,编写方便 支持大量展示主题,可以下载使用他人发布的主体,也支持自定义主题 支持markdown自动目录 支持图片拖拽调整位置,支持调整图片显示大小,支持搭建自定义图床 支持导出PDF格式文件 Typora在1.0之前的版本都是免费使用的,从1.0开始,官方宣布Typora收费,价格14.99$,人民币89元,价格不算太贵,有条件请支持正版! 本站从网络上搜索好了一些平台的破解版本和最后的免费版本,请根据需要自行下载,下载地址如下: Typora for Mac 1.2.3破解版下载:链接: https://pan.baidu.com/s/1g-xT_AaagWBvzvKVBDGkmg 密码: grvk Typora for Windows 1.2.4破解版下载:链接: https://pan.baidu.com/s/1-tfx_Bz0ScExaqrixlFVzg 密码: 5it7 Typora for Mac 0.11.18免费版下载:链接: https://pan.baidu.com/s/1Xlt6fhEF_TLJpXBryPRI6w 密码: mrei Typora for Windows 0.11.18免费版下载:链接: https://pan.baidu.com/s/1DRorRI4gX9b_sgk0WdhxOA 提取码: ttsx Typora for Linux 0.11.18免费版下载:链接: https://pan.baidu.com/s/1O1aA0p_DTzzWkMCAD0g5cQ 提取码: hm84 下载声明 本站所有下载资源均来自互联网,由站长搜集整理,版本归原作者所有,如有侵权请联系站长删除。 本站所有下载资源仅供个人学习和研究使用,请勿用于商业用途,请在下载后24小时内删除。

2022-04-14 · 1 min · 49 words · Hank

读《Java并发》— Java内存模型

"上一章" 介绍了线程安全性,这一章称为"对象的共享",书中重点介绍了如何共享和发布对象,这两章是并发编程中非常基础却很重要的部分。在本章,首先介绍了什么是可见性问题,然后介绍了Java内存模型,讨论什么是内存可见性以及java保证内存可见性的方式,在此基础上介绍如何设计线程安全的对象,如何使用线程封闭技术和设计不可变对象来避免同步,最后再重点探讨如何安全地发布对象。由于内容较多,我将这一章分拆为几篇来阐述自己对本章的理解,这是第一篇。 Java的并发机制基于共享内存,要理解对象间的共享关系,则离不开对象间的内存关系,这涉及到本章要介绍的一个重要概念:Java内存模型,又称 JMM。 1. 内存可见性 上边提到,Java的并发机制是采用的是 共享内存模型,因此,在并发环境中保证对象间的内存可见性是并发编程解决的主要问题。 什么是内存可见性?可见性是一个复杂的问题,它表示程序中的变量在写入值后是否能够立即读取到。在单线程环境中,由于写入变量和读取变量都是在单线程中进行的,因此能够保证总能读取到修改后的值。但在多线程环境下,却无法保证,可能一个线程修改变量的值,而另外的线程并不能正确读取被修改的变量的值,除非我们使用变量同步等机制来保证可见性。 为什么多线程环境下变量就不能保证可见性了呢?稍后介绍JMM时再来讨论,先看一个示例。 内存可见性(Memory Visibility):某些线程修改了变量的值,正在读该变量的线程能够立即读取到修改后的值。 @NotThreadSafe public class UnsafeSequence { private int count; public void increment() { count++; } public int getCount() { return count; } } 上边的示例,count变量被多个线程共享,因此不能保证 getCount() 总能读取到 increment() 增加后的值。那是不是在 increment() 方法上使用 synchronized 进行同步就能保证可见性了呢?答案是不行。虽然使用同步能够保证只有一个线程修改count的值,但是其他多个线程仍然可能读到 失效的值,因此必须在 getCount() 上也使用同步,见 "这里"。 再看一个示例,如下边的代码: @NotThreadSafe public class NoVisibility { private static boolean ready; private static int anInt; private static class VariableReader implements Runnable { @Override public void run() { while (!...

2022-03-28 · 3 min · 578 words · Hank

读《Java并发》— 线程安全性

上一章 介绍过,并发编程会带来诸多挑战,最基本的就是 线程安全性,但这又是一个非常复杂的主题。这一章重点介绍了什么是线程安全性、原子性、竞态条件等概念,以及在Java中如何通过加锁来确保线程安全性。 1. 什么是线程安全性 1.1. 正确性 要理解什么是线程安全性,必须先明白什么是正确性,正确性是线程安全性的核心概念。 正确性的含义是,某个类的行为与其规范完全一致。 — Java并发编程实战 这个定义从静态视角出发强调了类的规范重要性。通常,我们不会定义类的详细规范,但是我们应该为类和方法提供文档注释,来说明类是否是线程安全的,以及对于线程安全性如何保证。尤其在方法上,应该明确规定该方法是否已经保证了线程安全,调用者是否应该在同步机制内调用该方法等等。 下边是 ArrayList 的类文档注释的节选,它告诉调用者关于线程安全的内容: Note that this implementation is not synchronized. If multiple threads access an ArrayList instance concurrently, and at least one of the threads modifies the list structurally, it must be synchronized externally. (A structural modification is any operation that adds or deletes one or more elements, or explicitly resizes the backing array; merely setting the value of an element is not a structural modification....

2022-01-25 · 6 min · 1177 words · Hank

读《Java并发》— 并发简史

这一章主要讲述线程的发展历史,以及并发编程带来的优势和挑战。 1. 线程的发展 1946年第一台计算机诞生,一直到20世纪50年代中期,这时候的计算机没有操作系统的概念,采用手工操作的方式工作,每次只能有一个人使用计算机。此时的手工操作方式,用户独占全机,昂贵的计算机资源得不到充分利用。 后来,随着计算机的发展,出现了批处理系统、多道程序系统,它们都提升了计算机的资源利用率。1961年,分时系统(Time Sharing System)出现,此时的一台计算机可以供多个用户终端同时连接并使用,就好像自己在独占计算机一样。分时系统将CPU的运行时间分成很短的时间片,按时间片分配给不同的连接终端使用,这充分利用了计算机资源,看起来就好像多个用户在同时使用计算机一样。 再后来,通用操作系统出现(General Operating System),它隐藏了硬件调用接口,为应用程序员提供调用硬件资源的更好、更简单、更清晰的系统调用接口,程序员就可以直接面向操作系统来编写程序,而不用关注硬件。 计算机的发展 [1],都在试图解决三个问题: 资源利用率:尽可能的利用昂贵的计算机资源,如CPU、内存,而不是使其大部分时间处于等待状态 公平性:不同的用户对计算机都有同等使用权,而不是排队等待 便利性:可以同时执行多个程序,每个程序执行一个任务并在必要时相互通信 这三个问题促使进程和线程的出现。计算机可以同时运行多个程序,每个程序一个进程;而同一个进程中同样存在多个线程(轻量级进程),它们共享进程范围内的资源,如内存句柄和文件句柄,每个线程都有自己的程序计数器、局部变量等,而且同一个进程的多个线程都可以被调度到CPU上运行。但是,线程之间存在着共享资源(进程的内存),如果没有协同机制,那么多个线程在同时处理共享内存时会存在安全问题,比如一个线程读某个变量而一个线程同时在写这个变量,如果没有线程同步机制,则该变量的值变得不可预测。 举个例子,假设进程为多条高速公路,它们之间彼此独立,而线程就好比高速公路的多个车道,它们共享这条高速路的资源,如指示牌、测速设备等,线程中执行的任务就是一辆小汽车,它们相安无事的飞驰在自己的车道上,一旦它们开始抢夺道路资源,不遵守交通规则,超速、违规变道,那么后果可想而知。 2. 多线程的优势 Java是门支持并发编程的语言,充分利用其提供的并发特性来编写高并发程序,是信息化时代发展所需。多线程程序的优势可以从几个方面来说明: 多线程程序能够极大的发挥处理器的强大能力。现代的计算机,CPU核心越来越多、处理频率越来越快、制造工艺越来越精,计算机的价格也越来越亲民,个人普通电脑都普遍是四核心、八核心甚至配置更高。在单核心CPU中,可能多线程程序不但没能提升处理性能,反而由于线程上下文切换的开销导致程序性能降低;但是,在多核心时代,多线程能够发挥处理器的强大能力,每个核心都能同时处理一个任务而不会造成上下文切换,如果还停留在单线程程序,这无疑是极大的浪费计算机资源,如果有100个CPU,那么单线程程序会造成99%的CPU都处于空闲状态。 建模更简单。首先要明确,编写多线程程序与单线程程序相比,肯定是复杂且容易出错的,那么为什么说建模更简单呢?这需要从管理线程中执行的任务角度去分析。在单线程中执行多个任务,与多个线程每个线程仅执行一个任务相比,哪个更容易管理呢?很明显是后者。虽然多线程中的任务彼此可能存在通信,但是从模型上看,一个线程只管理一个任务,职责更单一。 异步事件简化处理。这一点可以理解为编程模型的简化。单线程和多线程程序,在IO模型的选择上存在很大的不同。单线程程序,要提高并发请求数,底层需要使用异步IO、非阻塞IO或者IO多路复用模型 [2],但他们都是由操作系统底层支持,编程难度大;而多线程程序,由于应用层面本身采用了多线程模型,底层的IO则可以选择同步IO(异步的事情多线程做了),这就降低了开发难度。比如单线程模型中,Nginx采用多进程单线程和IO多路复用模型的架构,具备极高的并发处理能力;Redis采用单线程和IO多路复用模型,同样具备很高的处理效率。 响应更灵敏的用户界面。Java的GUI框架AWT和Swing采用事件分发线程来替代传统的主事件循环,并可将将耗时的处理任务放到单独的线程中异步执行,从而提高用户界面的响应速度。 3. 多线程的挑战 多线程是一把双刃剑,带来诸多优势时也让开发者面临更大的挑战。 1、开发难度问题 由于多线程程序运行的不确定性,给开发难度带来很大的挑战。相对于单线程程序,多线程编程更难以掌握,不仅需要充分理解编程语言级的多线程支持,还需要掌握操作系统层级的多线程模型。尽管如此,多线程程序仍然难以调试和跟踪,尤其是产生 "活跃性" 问题时往往无法重现。 2、安全性问题 什么是安全性?安全性指的是程序永远不会返回错误的结果。看下边的示例: public class UnsafeSequence { private int count; public int increment() { return count++; } public int getCount() { return count; } } 多个线程调用 increment 方法,由于 count++ 包含了 读取、修改、写入三步内存操作,这三步不是一个原子操作,存在多个线程重复写同一值的情况,如下图示意: Figure 1. 线程竞争导致错误的结果 由于线程在同时操作相同的count域,一个线程可能会覆盖另一个线程写入的值(线程竞争),最终count得到错误的值6,而不是预期的7. 线程安全本身是一个非常复杂的问题,由于线程之间存在资源共享,如果多个线程交替执行又没有有效的同步机制,执行结果将难以预料。尽管Java提供了足够的同步机制来保证线程安全性,但是这就要求开发者必须完全理解多线程程序的执行原理和Java的同步机制,这给程序开发带来很大的难度。 3、活跃性问题: 怎么理解活跃性?活跃性指的是如果线程存活则会保持运行,如果线程被销毁了当然就不活跃而是死亡了。说白了,如果线程还活着,那么它就能够 正确处理任务,即使它暂时被阻塞,但是如果后边它被唤醒后仍然能够继续运行。但是有时候,可能某些原因(比如程序bug),线程存活但是却始终得不到运行,或者运行着错误的任务,此时就产生了活跃性问题。最典型的活跃性问题就是 死锁,还包括 饥饿 和 活锁。...

2022-01-22 · 1 min · 125 words · Hank

读《Java并发》— 简介

这是个人的《Java并发编程实战》阅读笔记整理系列的第一篇文章,这个系列包括对书中并发编程内容的整理、总结以及个人的一些感悟和实践心得。 1. 简介 《Java并发编程实战》是 Brian Goetz 等 6 位 Java 大师合著的介绍 Java 并发编程的经典著作,这部名著由浅入深的介绍了 Java 并发编程的诸多知识,是一本完美的Java并发参考手册, 豆瓣评分 9.0,可见其受欢迎程度。 1.1. 内容概要 《Java并发编程实战》从并发性和线程安全性的基本概念出发,介绍了如何使用类库提供的基本并发构建块,用于避免并发危险、构造线程安全的类及验证线程安全的规则,如何将小的线程安全类组合成更大的线程安全类,如何利用线程来提高并发应用程序的吞吐量,如何识别可并行执行的任务,如何提高单线程子系统的响应性,如何确保并发程序执行预期任务,如何提高并发代码的性能和可伸缩性等内容,最后介绍了一些高级主题,如显式锁、原子变量、非阻塞算法以及如何开发自定义的同步工具类。 这本书介绍的是 Java 中 "并发编程" 这样的高级主题,不太适合初学者阅读。书中诸多的内容都较难理解,比如线程的 "竞态条件"、"同步机制"、"活跃性" 等等,如果对并发知之甚少,不熟悉操作系统层面的相关内容,阅读起来比较吃力。 1.2. 作者简介 本书作者都是Java Community Process JSR 166专家组(并发工具)的主要成员,并在其他很多JCP专家组里任职。Brian Goetz有20多年的软件咨询行业经验,并著有至少75篇关于Java开发的文章。Tim Peierls是“现代多处理器”的典范,他在BoxPop.biz、唱片艺术和戏剧表演方面也颇有研究。Joseph Bowbeer是一个Java ME专家,他对并发编程的兴趣始于Apollo计算机时代。David Holmes是《The Java Programming Language》一书的合著者,任职于Sun公司。Joshua Bloch是Google公司的首席Java架构师,《Effective Java》一书的作者,并参与著作了《Java Puzzlers》。Doug Lea是《Concurrent Programming》一书的作者,纽约州立大学 Oswego分校的计算机科学教授。 也许你对这几位大师不怎么熟悉,但是你一定读过它们的著作,尤其是 Joshua Bloch 的《Effective Java》,这本书也是 Java 开发者必读的书籍,目前已经出到了第三版。 如果经常编写并发程序,那么对于 Doug Lea 一定不会陌生,因为 Java 5 开始的很多并发工具(它们都在 java.util.concurrent 包中,简称 juc 包)都出自他的手笔,如 AbstractQueuedSynchronizer、AtomicXxx 原子类、BlockingQueue、ConcurrentMap、ExecutorService 等等,几乎整个并发工具包的类都是他实现的。...

2022-01-20 · 1 min · 125 words · Hank

2021过去了,我都干了些啥?

2021年又过去了,不得不让人感叹岁月如梭、光阴似箭!正值元旦假期,刚好有时间梳理一下这过去一年的种种,思来想去,还是不过"平凡"二字罢了。 1. 去年计划完成情况 每年的年底,我都会做一下年终总结,也定制一下新的一年的计划。现在回过头去看,发现去年的计划完成度也只有40%,很多事情还是没有能够完成。一方面,计划可能定得过高,完成起来有一定的难度;另一方面,自身的自制力还有待提高,很多事情没有能够坚持去完成;再者,诸如读书这样的事情,如果阅读了但是没有去理解、梳理和总结,还是很难形成系统的经久不忘的知识。 先从工作和生活上来聊聊计划之内的事情,然后,作为一个技术人,着重从技术和个人能力修炼上自我剖析一下去年一年所完成的技能。 1.1. 生活篇 从2021年开始,我计划每年带父母、老婆和孩子们出去旅游至少一次,2021年完成了这一目标,虽然走的不远,但是回想起来还是满满的幸福感。 作为技术人,平时难免加班加点的工作,陪伴家人的时间很少。所以,一家人出去旅游一下也是一种不错的选择。我们在端午节花了几天时间去了峨眉山、乐山大佛,本想着错开人流高峰时间,结果没想到去乐山大佛的时候还是遇到了人流洪峰,整整排队3个小时才看到了大佛😅。两天下来,从攀登峨眉山的兴奋,到看到金顶的激动,到漫长排队看大佛的百无聊赖,到看到大佛的肃然起敬,再到后来的精疲力尽,真的是波澜起伏、人间百味!玩了两天下来,我们都已经疲惫不堪,不过远离尘世的喧嚣,静下心来仔细品味一下生活,享受人生中的酸甜苦辣,何尝不是一件幸福的事情呢? Figure 1. 峨眉山金顶(图片来源网络) 生活习惯上,仍然没有太大的变化。虽然计划着要改变自己晚睡的习惯,然后还是没有做到。明明知道熬夜带来的危害,但还是深夜十二点还在电脑上敲敲打打的写着代码……今年,希望自己能够克服这个毛病,养成早睡早起的习惯,做到严格自律,以免将来后悔。正如《自律的人生更自由》里说的:与其躺到病床上后悔,不如现在就做出改变。 1.2. 工作篇 年初时,老总制定了明确的目标,每个阶段完成的任务都列的清清楚楚,可是完成情况却很糟糕,主要是酒店行业的产品在疫情影响下推动起来相当困难。比如,我们推出的一套酒店服务人员综合素质能力培养的产品,本来预计下半年应该会有比较上规模的用户使用,可惜在疫情影响下,酒店经营者自身尚且难保,更别说在员工的软硬技能上再下功夫了。这一点在2020年全年已经体现的非常突出了,可以说新冠疫情对酒店行业的冲击是常人难以想象的,虽然2021年酒店业有一些复苏,但是同2020年比还是增长不大,而且实现营收增长的多为企业级连锁酒店。 在公司待了5年了,目前而言公司的业务线还没有真正地走上正轨,前期的产品铺垫几乎没有收到任何效果,尤其是在这几年疫情的影响下,每迈出一步都显得那么沉重。今年,我们着重再打造酒店人员实习平台,解决职业院校学生实习管理难、数字化程度低、信息化建设落后等问题,尽管现在产品已经足够完备,但是缺少验证她的用户群,22年的工作重心必定在产品的推广和优化上。希望新的一年,公司能够顺利推动业务,打磨出更高效、更易用的产品。 1.3. 技术篇 学习的技术,最好的入口是官方文档,大多的技术官网都会有n分钟guide,可以教你一步步由浅入深入的学习。当然,前提是英语必须要好,否则阅读起来非常吃力,而且会影响我们的阅读的积极性。当然,也可以借助 Chrome 的翻译工具来阅读,不过这种机器翻译准确性很难保证,所以我经常是先阅读英文,不懂的地方或者不重要的地方在通过翻译工具去阅读。技术的学习离不开实践,如果只阅读不实践,最后读过的东西也成了过眼云烟。 去年,我学习了这些技术,很多技术都是经常使用,但是没有系统地梳理它们,所以花了一些时间专门去进行系统性的学习。现在看来,在技术上花费的时间还是太少,今年需要付出更多的时间来继续钻研技术。 Spring Cloud 去年,我将 Spring Cloud 从原来的 Edgware 版本升级到了 Hoxton 版本,将 Spring Cloud 的很多组件都换成了 Spring Cloud Alibaba 的,比如注册中心从 Eureka 换成了 Nacos。升级过程中,还是遇到了很多问题,最后都一一解决了。一个最大的问题是网关,原来的是 Zuul 1.x 版本,后来换成了 Spring Cloud Gateway,但是后者是基于 WebFlux 的,这是一个实现了响应式标准 Reactive Streams 的异步响应式编程框架,底层默认使用 reactor 框架,这对于我们团队而言是一个全新的技术,我们不得不下功夫去学习。 WebFlux 与响应式编程 响应式编程是一门较新的技术,目前的响应式框架,除了较早的 RxJava 和 Vert.x 之外,还包括专用于开发云原生应用的 Quarkus,以及 Spring 家族的 WebFlux。 在项目中,使用 Spring Cloud Gateway,必须先去学习 WebFlux 和响应式编程,目前来说,对其掌握还不够系统和全面,所以今年的目标是要系统的去学习 WebFlux 和响应式编程。...

2022-01-03 · 1 min · 173 words · Hank

什么是时序攻击Timing Attack

最近在维护一个老项目的时候,看到一段密码匹配的代码,感觉很奇怪,于是遍寻资料,最终还是很有收获。 1. 密码匹配 事情是这样的,一个项目遇到输入的密码总是提示密码错误的问题,于是debug跟踪了一下代码,发现该项目使用了 Spring Security 3.2.2 的 PasswordEncoder 来做密码MD5加密和密码匹配验证,PasswordEncoder 只是一个接口,其实现使用的是 StandardPasswordEncoder 类,它的密码匹配代码引起了我的注意: public boolean matches(CharSequence rawPassword, String encodedPassword) { (1) byte[] digested = decode(encodedPassword); byte[] salt = subArray(digested, 0, saltGenerator.getKeyLength()); return matches(digested, digest(rawPassword, salt)); } private boolean matches(byte[] expected, byte[] actual) { (2) if (expected.length != actual.length) { return false; } int result = 0; for (int i = 0; i < expected.length; i++) { result |= expected[i] ^ actual[i]; (3) } return result == 0; } 1 验证密码是否匹配 2 将密文按照十六进制解码为字节数组,然后在进行比较 3 两个字节位运算 这段代码要做的事情很简单,比较两个加密的密文,验证他们是否相同。注意看真正比较的 matches 方法(标记2),它首先比较两个字节数组的长度,不同则直接返回 false,否则循环遍历,依次进行位运算来,最后查看位运算的结果 result 是不是0,0则说明相同,否则说明不同。...

2021-12-30 · 2 min · 293 words · Hank

Java反射之AnnotatedType接口

上一篇Java反射之获取注解的AnnotatedElement接口介绍了 AnnotatedElement 接口,它是用反射获取注解的顶级接口。其实现类或子接口包括 Class、TypeVariable、Parameter、Package、AccessibleObject、GenericDeclaration 等,这些在前边的文章中已经介绍过。另外,它还有一个重要的接口 AnnotatedType,本篇将介绍它。 1. 再说注解 JDK1.5推出了注解功能,给Java语言带来非常强大的诸多特性。Java注解有几种保留策略:源码、运行时、类,可以在创建注解时通过 @Retention 元注解来指定,它需要引用 RetentionPolicy 中定义的策略: SOURCE: 注解只在源码层面保留,编译后被丢弃 CLASS: 默认的保留策略,注解将由编译器记录在类文件中,但在运行时不需要由 JVM 保留 RUNTIME: 注解不仅保留在类文件,在运行时 JVM 也会保留它,因此可以通过反射获取注解 注解的作用大概可以分为以下几种: 提供编译期特性:Java提供的一些注解,可以进行编译期特性支持,比如 @SuppressWarnings, @Deprecated 和 @Override 等可以提供编译期检查,@FunctionalInterface 标注函数式接口等 生成帮助文档:Java的 @Documented 注解可以用来标记生成 javadoc 时保留类或接口上的注解 运行时获取注解实现特定逻辑:这个是使用最多的场景,使用注解,并通过反射获取注解,来实现自身业务逻辑,大多数框架都使用注解来实现高级特性,比如 Spring 的 @Bean、 @Service 等注解 一句话,有了注解,我们可以方便的按需实现自身业务逻辑。在JDK1.8之前呢,注解所能表示标注的位置有限,JDK1.8又新增了两种标注位置,支持将注解标注在任何类型上。 2. 再说注解标注的位置 还记得 上一篇 介绍注解标注的位置信息吗?文中提到,通过 ElementType 枚举来定义注解可标注的位置信息,其他包括下边两项: ElementType.TYPE_PARAMETER: 标注在类型参数上,比如泛型参数T、E上 ElementType.TYPE_USE: 使用类型,注解可以标注在任何类型变量上 它们都是在JDK1.8时新增的,TYPE_PARAMETER 主要目的在于支持将注解标注在泛型类型的参数上,而 TYPE_USE 范围更广,可以将注解标注在任何Java类型上。也就是说,JDK1.8之前,注解只能标注在特定的位置(类、方法、包、参数、域、构造器、本地变量、注解),不能标注在泛型参数上,JDK1.8之后,可以标注在任何类型上。 使用TYPE_USE就可以省略其他 ElementType 元素吗? 既然 TYPE_USE 可以表示标注在所有类型上,那么是不是就不需要其他 ElementType 元素了呢?肯定不是!TYPE_USE 有其特殊的目的,它表示 “类型使用” 类型,其实就是指任何Java类型,但是它只能用 AnnotatedType 来描述并获取注解信息(见这里)。...

2021-11-25 · 5 min · 1042 words · Hank

Java反射之获取注解的AnnotatedElement接口

在Java反射的体系结构一文中说过,AnnotatedElement 接口定义了通过反射获取注解信息的方法,它是一个顶层接口,能够被标记注解的反射类都实现了该接口从而得到反射获取注解的能力,比如 Class、Field、Constructor、Method、Parameter 都实现了该接口。本文将详细介绍 AnnotatedElement。 1. 再说注解 注解是JDK1.5推出的新特性,用于标注Java中的特定元素,以便通过反射来获取它们,并实现不同的功能。本文不会讨论注解的基本知识,有兴趣的可以自行查阅相关资料。但是注解有一些特性需要细说一番。 1.1. 注解标注的位置 Java中,注解可以标注在多个位置上,常见的包括类、接口上、方法(构造函数)、方法参数(构造函数参数)、成员域上以及标注在注解上(注解的注解),甚至可以将注解标注在 package-info.java 文件中定义的包上,这些位置是在定义注解时通过元注解 @Target 来指定的,比如向下边这样: @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.PACKAGE, ElementType.TYPE, ElementType.FIELD}) public @interface CustomAnno { String value() default ""; } 可以看到,通过 ElementType 来指定注解可以标注的位置,它定义了以下几种位置: ElementType.TYPE: 标注在类型上,包括接口、类、枚举等 ElementType.FIELD: 标注在类、接口的成员域上,包括枚举的元素 ElementType.CONSTRUCTOR: 标注在构造器上 ElementType.ANNOTATION_TYPE: 标注在其他注解上 ElementType.METHOD: 标注在方法上 ElementType.PARAMETER: 标注在方法参数上 ElementType.LOCAL_VARIABLE: 标注本地变量上,即构造器、静态代码块、方法等的内部变量 ElementType.PACKAGE: 标注在 packag-info.java 文件中定义的包上 ElementType.TYPE_PARAMETER: 标注在类型参数上,比如泛型参数T、E上 ElementType.TYPE_USE: 使用类型,注解可以标注在任何类型变量上 上边的最后两个是在JDK1.8新增的,用于在注解标注在泛型上的情况,涉及到 AnnotatedType 接口,我们将在下一篇专门行文来介绍它。 注解用于包上的例子: package-info.java @CustomAnno("package") package com.belonk.lang.reflect; 其他标注位置比较常见,就不在赘述了。 1.2. 注解的继承 Java中定义注解时,可以指定其是否可以被继承。注解继承指的是父类标注的注解可以作用于子类,而不是说一个注解继承另一个注解。比如,自定义如下注解: @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Inherited (1) @interface MyAnno { String value() default ""; } 1 标记了元注解 @Inherited 即表示该注解可以继承 然后,编写一个父类,它被标注了自定义注解:...

2021-11-21 · 3 min · 569 words · Hank

Java反射之获取泛型类定义的具体类型

通过上一篇Java反射之表示所有类型的Type接口一文,我们知道 Type 接口及其五大组件是用来描述Java的各种类型,主要包括原始类型和泛型类型。很多时候,我们需要获取到泛型类上定义的具体类型,从而完成一些业务逻辑。比如,最常见的情景就是JSON的反序列化,需要将JSON字符串反序列化为泛型的具体类型,比如反序列化为 List<User>,这就要求每一个 List 中的元素都是 User 对象。那么,如何获取到泛型类上定义的具体类型呢?这就是本文要阐述的内容。 1. 通过Class能获取泛型类型吗? 在/2021/11/09/reflect-class.html中说过,Class 类上有一个 getTypeParameters() 方法用来获取类上的类型变量。通过它能不能获取到泛型类型呢?看下边的示例: class Parent<T> { (1) } 1 定义一个泛型类 这里定义一个泛型类,现在尝试通过它的实例对象来获取泛型类型,代码如下: public void printParent() { Parent<String> parent = new Parent<>(); (1) TypeVariable<? extends Class<? extends Parent>>[] parentTypeParameters = parent.getClass().getTypeParameters(); (2) System.out.println(Arrays.toString(parentTypeParameters)); } 1 创建 Parent 类的实例 2 获取类型变量 上边的示例并不能拿到实例上的 String 类型,而只能拿到 Parent 类上定义的泛型类型 T,因此,输出结果为: [T] 很显然,这个结果不是我们想要的,我们想要获取到 String 这个类。在 Java反射之创建泛型数组 一文中说过,泛型是编译期特性,在运行时泛型会被擦除。因此,我们想通过`Class` 在运行时用反射直接去获取泛型类型,看起来是不行的。 既然直接获取不行,那么有没有别的办法呢? 在上一篇Java反射之表示所有类型的Type接口一文中说过,Type 的参数化类型组件 ParameterizedType 可以获取整个泛型定义,那么要获取类上的泛型,肯定少不了它! 但是,Field 通过 getGenericType() 方法可以获取到参数化类型,Constructor、Method 可以通过 getGenericParameterTypes() 获取泛型参数或者通过 getGenericReturnType() 方法获取泛型值的泛型类型,而翻看 Class 类的 api,与泛型相关的看似有用的只有两个方法:...

2021-11-15 · 3 min · 495 words · Hank

Java反射之表示所有类型的Type接口

前边说过,JDK1.5推出了反射功能,并介绍了反射的类设计结构,其中提到了 Type 接口,它是JDK1.5新增的接口,表示Java中的所有类型。本文介绍这个接口及其五大组件。 Figure 1. Type及其组件 1. Type的五大组件概览 Type 接口表示Java中的类型,这些类型包括原始类型、参数化类型、数组类型、类型变量和基本类型,前边介绍的 Class 就是其组件之一。Type 及其组件用于通过反射来描述Java各种类型,比如描述泛型的定义,获取泛型参数个数、类型、泛型通配符的上下边界等等。一个典型的应用场景是获取真实的泛型类型,我们将在后续文章单独介绍。 Type 用其五大组件来描述上边提到这些类型,类图如下: Figure 2. Type类图 ParameterizedType: 表示参数化类型,即整个泛型的定义,如 List<String> 可以用 ParameterizedType 来描述; TypeVariable: 表示类型变量,即泛型申明中的具体类型,比如: List<E> 用参数化类型来描述,而其中的 E 就用 TypeVariable 来描述; WildcardType: 表示泛型通配符类型,即泛型申明中带 ? 通配符,比如:List<? extends Number> 中 ? extends Number 就用 WildcardType 来描述; GenericArrayType: 表示泛型数组,比如申明一个泛型数组的域 T[] array,它就用 GenericArrayType 来描述; Class: 表示运行时的类或接口,在前边Java反射的体系结构一文中已经介绍过了,不再赘述。 一句话总结,Type 的 Class 组件用来描述Java类或接口等原始类型和基本类型,其他几个组件描述泛型类型,包括泛型的变量、通配符以及泛型数组。 现在,来看看这几个组件的用法。 2. 参数化类型ParameterizedType 什么是参数化类型?其实就是指泛型,具体而言,JDK5推出泛型之后,原来的具体的类型可以申明为泛型,变得"可被参数化"了,比如:List 是一个具体类型,但是可以申明为 List<String>、List<Integer> ,就仿佛可以申明不同的参数,类似方法可以申明不同的参数一样(其实泛型是编译期特性,它两实际上是同一类型,都是 List,这在 Java反射之创建泛型数组 已经说过了)。 所以,ParameterizedType 用来描述泛型类型,比如:List<T>、List<String>、User<T extends Number>、List<?...

2021-11-14 · 4 min · 804 words · Hank

Java反射之创建泛型数组

前边介绍了Java泛型类设计体系中的一些常用类,本篇将着重介绍 Array 类和如何创建泛型数组。 1. Java能创建泛型数组吗? 1.1. 泛型类型擦除 首先,我们知道,泛型是JDK1.5推出的编译期特性,什么是编译期?即是说在编译的时候有效,编译代码时编译器能够对泛型进行检查和校验。但是如果在运行时,泛型其实已经无效了,泛型类型被擦除了。比如下面的代码: 代码清单1 List<String> stringList = new ArrayList<>(); List<Integer> integerList = new ArrayList<>(); System.out.println(stringList.getClass() == integerList.getClass()); 结果输出为 true。stringList 只能添加 String 类型的数据,而 integerList 也只能添加 Integer,不能加入别的类型的数据。为什么?因为它们申明了泛型类型,编译器就会对类型兼容性进行检查,类型不符则会抛出异常。但是,他们的 Class 确是相同的,即是说,在运行时它们是相同的类型,都是 ArrayList,而泛型类型被擦除了。 1.2. 尝试创建泛型数组 那么,我们申明泛型数组会怎么样呢?看下边的代码: 代码清单2 class GenericArray1<T> { private final T[] array; (1) private int index; public GenericArray1(int size) { this.array = (T[]) new Object[size]; (2) } public void add(T item) { (3) array[index++] = item; } public T[] array() { return array; } } 1 申明泛型数组成员域 2 构造函数中创建Object数组,然后强转为泛型类型 3 数组中只能添加特定类型的元素 上边的代码,试图创建一个泛型数组,它只能添加某一种类型的数据,然后通过 array() 方法返回这个数组。然而,标记3的 add 方法虽然得益于泛型实现了限制了数组中只能添加特定的某一类型元素,看似没有问题,实际上运行时标记2的这行代码会抛出 ClassCastException,告诉你Object数组不能转为特定类型的数组。创建了 Object 数组, 它可以容纳任何元素,但是如果强制转型为特定类型的数组,可容纳的元素类型范围变窄了,这不是向下转型吗?jvm不允许这么做。...

2021-11-10 · 3 min · 469 words · Hank

Java反射的体系结构

反射是JDK5推出的非常重要的特性,通过反射,开发者可以在运行时动态的创建类实例,获取、更改、调用类的相关信息、方法等。反射也是各大框架使用的主要技术,如知名的Spring framework框架。 本文将介绍反射的类设计体系,并配备一些简单示例来介绍反射常用API的使用,本文示例都是采用JDK1.8编写。 1. 反射类设计 Java专门为反射设计了名为java.lang.reflect包,官方API文档在这里: 反射官方文档,该包提供了一系列类共同组成反射体系。 1.1. 反射类概览 既然反射是在运行时动态描述类信息的,那么描述类信息到底是些什么东西?类信息除了类、接口、数组、枚举等常见类型的基本信息外,还包括类型中的域、方法、构造器等以及它们的修饰符(public、static、volatile、private、final、native、synchronized 等),另外,还包括方法参数和注解。反射类如下表所示: Table 1. 反射中的类 类 描述 Class 位于java.lang包,描述类、接口的运行时状态,枚举、数组也是一种类,而注解是一种特殊的接口。每一个类型都有一个Class对象与之对应 Field 描述类型中的域信息 Constructor 描述类型中的构造函数信息 Method 类型中的方法信息描述 Parameter JDK8新增,提供有关方法参数的信息,包括其名称和修饰符。它还提供了一种获取参数属性的替代方法。 Modifier 类、域、方法、构造函数等的访问修饰信息工具类 AnnotatedElement 反射获取注解的顶层接口 Type Java中的所有类型的顶层接口 Array 用于动态创建和处理数组 Proxy 动态代理的支持类 这里暂时介绍基础的Class、Field、Constructor、Method、Parameter、Modifier,其他几个类会在后续文章中单独介绍。现在,我们来看看类图的设计。 1.2. 反射类图 整个反射的类设计见下图: Figure 1. 反射类图 图中只列出了比较基础且重要的类,从图中可以看到,反射的类结构并不算复杂,顶层主要包括几个重要接口和类: AnnotatedElement: 定义了反射获取java注解的方法,如 getAnnotations() 等,实现该接口的类意味着可以获取注解信息; Member: 定义获取类成员(域、构造函数、方法)相关的标识符信息的接口,比如获取名称、标识符等; Type: 所有类型的顶层接口,用来描述java中的类型,比如泛型类型、类字节码等,包含五大组件,其中包括最重要和常见的 Class 对象,后续文章将单独介绍这个接口和其组件; GenericDeclaration: 表示泛型申明的接口,继承自 AnnotatedElement,内部定义了 getTypeParameters() 方法,用于获取类型变量(TypeVariable,该接口为 Type 接口的一个子组件); AccessibleObject: 表示可访问对象,Field、Method 和 Constructor 对象的基类,用来检查和修改它们的访问权限:公共、包、私有访问,比如:调用其 setAccessible(boolean) 方法修改域的访问权限,然后才能修改域的值; Executable: 表示可执行的对象,其实就是方法和构造函数的基类;...

2021-11-09 · 5 min · 869 words · Hank

MindManager windows 2020中文版本下载,带正版激活码

MindManger是Mindjet公司开发一款思维导图软件,目前已经更新到了2021版本。与其他几大思维导图软件相比,个人最钟爱MindManger,因为他功能更强大、界面更友好,支持强大的备注体系,插图片、查表格都可以,可以直接用来做知识体系整理,非常方便。 这里介绍的是2020版本,提供激活码,能够正常激活使用。 官网地址: https://www.mindjet.com/ 下载地址 由于版权限制,请关注公众号并在后台回复6001获取下载地址! 如果链接失效,请在公众号后台留言,我会第一时间补上。 软件截图: 扫码关注公众号,查看更多推荐资源! 下载声明 本站所有下载资源均来自互联网,由站长搜集整理,版本归原作者所有,如有侵权请联系站长删除。 本站所有下载资源仅供个人学习和研究使用,请勿用于商业用途,请在下载后24小时内删除。

2021-07-14 · 1 min · 12 words · Hank

Expert One-on-One J2EE Development without EJB中文版PDF下载

你的J2EE项目是否耗费了你太多的时间?它们是否难以调试?它们是否效率不彰?也许你还在使用传统的J2EE方案,然而这种主案太过复杂,而且并非真正面向对象。这里的很多问题都与EJB有关:EJB是一种复杂的技术,但它没有兑现自己曾经的承诺。 1. 站长评价 这是一本很古老的书,同时也是一本介绍Spring框架设计理念、出现缘由的书,也是一本介绍软件设计的书! 在Java开发领域,Spring框架应该说是久负盛名,而且是Java Web开发必备武器。但是,随着Spring的发展壮大, Spring涵盖的东西也越来越多:Spring Boot、Spring Cloud等等以Spring框架(Spring Framework)为基础的Spring生态系统。 所以,对于初学Spring的人而言,可能不知道从何入手。所以,推荐先阅读这本书,本书的作者也是Spring框架的发起者。他在本书中详细介绍了Spring框架的出现原因、Spring框架如何解决一系列企业级问题,如控制反转(IoC)、事务控制、远程调用等等。同时,他还详细阐述了Spring框架架构设计理念和技术抉择,对比当时流行的企业级框架EJB的种种缺陷,一一介绍了Spring框架如何采用轻量级的方式来解决企业级J2EE问题。总之,是一本深入了解Spring框架的经典著作。不论你是初学Spring,还是对Spring已经了如指掌,都可以从本书中学到很多东西。 本书的前5章都在讲述EJB相关的内容,如果不想了解EJB可以直接从第六章开始阅读。 丰富指数:☆☆☆☆ 难度指数:☆☆☆☆ 推荐指数:☆☆☆☆ 迫不及待想要阅读它了?点此立即下载本书的PDF版本(仅供可以学习研究使用,切勿用于商业用途)! 2. 内容简介 在这本实战手册中,你将看到另一种截然不同的方案:没有EJB,却可以创建质量更高的应用程序,所需的时间和成本则更低。你将学会如何充分利用各种实用的技巧和工具,包括时下流行的Spring框架和Hibernate两个开源工具。你将看到如何高效地解决企业级应用的核心问题,例如事务管理、持久化、远程调用和web设计。你将了解这种新的方案给可测试性、性能和可伸缩性带来怎样的影响,并亲身体验轻量级架构如何大幅降低项目开发所需的时间和工作量。 自从servlet、EJB、JSP等J2EE技术发布之初,本书作者Rod Johnson就一直在使用这些技术,他对于这些技术的优劣利弊了如指掌。现在,通过这本书,你将可以面对面地分享他的专家经验。 你将从本书学到…… 如何针对自己的应用程序找到 简单、 易维护的架构;在不使用EJB的情况下有效地管理事务;如何利用AOP和loC解决企业级软件开发中的常见问题;web层设计,以web层在设计良好的J2EE应用中的地位;J2EE应用中的数据访问技术,包括BC、Hibernate和O;如何利用开源产品提升生产率、减少编码量;如何从设计层面上改善性能和可伸缩性。 “传统的J2EE设计思路尤其是EJB日益让架构师和开发者们灰心丧气,我这本书正是为这些人而写的。本书将告诉读者,如何从现在开始用更清晰、更高效的方案去替代EJB,并开始迈向web应用的新时代。” 3. 作者简介 Rod Johnson,Spring框架的创始人,同时也是SpringSource的联合创始人。Spring是面向切面编程(AOP)和控制反转(IoC)的容器框架。Rod的畅销书Expert One-on-One J2EE Design and Development(2002年出版)是迄今为止J2EE领域最具影响力的书之一。 4. 目录 第1章 为什么要“j2ee without ejb” 聚光灯下的ejb j2ee还剩什么? 站在十字路口的j2ee 前行的路 主旋律 轻量级框架和容器 我们还应该使用ejb吗? 小结 第2章 目标 生产率 问题 传统j2ee方案解决生产率问题的办法 提升生产率更好的办法 oo 业务需求的重要性 经验过程的重要性 小结 第3章 各种架构 架构性构件 .业务服务层 向外部暴露业务对象 数据访问层,或eis层 j2ee架构 两种ejb架构 两种非ejb架构 j2ee架构实例...

2021-06-15 · 3 min · 450 words · Hank

HTTP权威指南中文高清版PDF下载

HTTP是Web的基础,这里所说的Web,不仅只是通常意义上的Browser/Server端的开发,而是Web Service。大多数人对HTTP的了解也就是这种程度了(没有读此书之前的我也是这样)。此书的意义在于,它让你知道,HTTP不仅只有一个简单的GET,HTTP可以做为一种通用的分布式编程的协议。现如今Web上大行其道的所谓Restful Web Service,其基础就是HTTP,而提出Rest这个概念的Roy Fielding,也是HTTP协议(RFC2616)的主要作者之一。 1. 站长评价 Web开发离不开Http,这本《HTTP权威指南》确实是介绍HTTP协议最权威的一本书籍,虽然古老(2012年出版)但是内容丰富,从Http0.9到未来的HTTP-NG,从HTTP基础到HTTP通信安全,可以说涵盖了HTTP协议的方方面面。不论是刚入行的新手,又或是有多年经验的程序大师,反复研读这本巨著肯定会受益匪浅! 丰富指数:☆☆☆☆ 难度指数:☆☆☆ 推荐指数:☆☆☆☆ 迫不及待了?点此立即下载! 2. 内容简介 HTTP(HyperText Transfer Protocol,超文本传输协议)是Web客户端与服务器交互文档和信息时所使用的协议,是每个成功Web事务的幕后推手。众所周知,我们每天访问公司内部网络、搜索绝版书籍、研究统计信息时所使用的浏览器的核心就是HTTP。但HTTP的应用远不仅仅是浏览Web内容。由于HTTP既简单又普及,很多其他网络应用程序也选择了它,尤其是采用SOAP和XML-RPC这样的Web服务。 本书详细解释了HTTP协议,包括它是如何工作的,如何用它来开发基于Web的应用程序。但本书并不只介绍了HTTP,还探讨了HTTP有效工作所依赖的所有其他核心因特网技术。尽管HTTP是本书的中心内容,但本书的本质是理解Web的工作原理,以及如何将这些知识应用到Web编程和管理之中去,主要涵盖HTTP的技术运作方式、产生动机、性能和目标,以及一些相关技术问题。 本书是HTTP协议及相关Web技术方面的著作,主要内容包括: HTTP方法、首部以及状态码 优化代理和缓存的方法 设计Web机器人和爬虫的策略 Cookies、认证以及安全HTTP 国际化及内容协商 重定向及负载平衡策略 本书由具有多年实践经验的专家编写,通过简洁语言和大量翔实的细节图解帮助读者形象地理解Web幕后所发生的事情,详细说明了Web上每条请求的实际运行情况。要想高效地进行Web开发,所有Web程序员、管理员和应用程序开发者都应该熟悉HTTP。很多书籍只介绍了Web的使用方式,而本书则深入说明了Web的工作原理。 3. 作者简介 David Gourley,Endeca的首席技术官(Chief TechnologyOfficer),负责Endeca产品的研究及开发。Endeca开发的因特网及内部网络信息访问解决方案为企业级数据的导航及研究提供了一些新的方式。在到Endeca工作之前,David是Inktomi基础工程组的一员,他在那儿帮助开发了Inktomi的因特网搜索数据库,是Inktomi的Web缓存产品的主要开发者。David在加州大学伯克利分校获得了计算机科学的学士学位,还拥有Web技术方面的几项专利。 BrianTotty,出任了Inktomi公司(这是1996年他参与建立的一家公司)研发部副总裁,在公司中他负责Web缓存、流媒体及因特网搜索技术的研发工作。他曾是SiliconGraphics公司的一名科学家,他在那儿为高性能网络和超级计算机系统设计软件并对其进行优化。在那之前,他是苹果计算机公司高级技术组的一名工程师。Brian在伊利诺伊大学Urbana-Champaign分校获得了计算机科学的博士学位,在MIT获得了计算机科学及电子工程的学士学位,在那里他获得了计算机系统研究的Organick奖。他还为加州大学扩展系统开发并讲授了一些屡获殊荣的因特网技术方面的课程。 MarjorieSayer在Inktomi公司负责编写Web缓存方面的软件。在加州大学伯克利分校获得了数学硕士和博士学位之后,一直致力于数学课程的改革。从1990年开始致力于能量资源管理、并行系统软件、电话和网络方面的写作。 Sailu Reddy目前在Inktomi公司负责嵌入式的性能增强型HTTP代理的开发。Sailu从事复杂软件系统的开发已经有12年了,从1995年开始深入Web架构的研发工作。他是Netscape Web服务器、Web代理产品,以及后面几代产品的核心工程师。他具备HTTP应用程序、数据压缩技术、数据库引擎以及合作管理等方面的技术经验。Sailu在亚里桑那大学获得了信息系统的硕士学位并握有Web技术方面的多项专利。 AnshuAggarwal是Inktomi公司的工程总监。他领导着Inktomi公司Web缓存产品的协议处理工程组,从1997年就开始参与Inktomi的Web技术设计工作。Anshu在科罗拉多大学Boulder分校获得了计算机科学的硕士和博士学位,从事分布式多处理器的内存一致性技术研究。他还拥有电子工程的硕士和学士学位。Anshu撰写了多篇技术论文,还拥有两项专利。 4. 目录 第一部分 HTTP:Web的基础 第1章 HTTP概述 1.1 HTTP——因特网的多媒体信使 1.2 Web客户端和服务器 1.3 资源 1.3.1 媒体类型 1.3.2 URI 1.3.3 URL 1.3.4 URN 1.4 事务 1.4.1 方法 1.4.2 状态码 1.4.3 Web页面中可以包含多个对象 1.5 报文 1.6 连接...

2021-04-12 · 6 min · 1228 words · Hank

Spring扩展原理

Spring的扩展点很多,本文讨论Spring的 BeanPostProcessor、BeanFactoryPostProcessor、ApplicationListener 和 SmartInitializingSingleton 这四个扩展的基础类或接口。 1. Bean后置处理器 bean后置处理器用于bean创建对象初始化前后进行拦截工作,因此它是spring容器扩展必不可少的组件。 1.1. BeanPostProcessor 允许自定义修改新bean实例的工厂钩子,例如,检查标记接口或用代理包装bean。 通常,通过标记接口等填充bean的后处理器将在初始化前实现后处理,而使用代理包装bean的后处理器通常在初始化后实现后处理。 注册表 ApplicationContext 可以在其bean定义中自动检测 BeanPostProcessor bean,并将这些后处理器应用于随后创建的任何bean。一个普通的bean factory允许对后处理器进行编程注册,将它们应用于通过bean工厂创建的所有bean。 顺序 在 ApplicationContext 中自动检测到的 BeanPostProcessor bean将根据 org.springframework.core.PriorityOrdered 和 org.springframework.core.ordered 语义进行排序。相反,以编程方式向 BeanFactory 注册的 BeanPostProcessor bean将按注册顺序应用;通过实现 PriorityOrdered 或 Ordered 接口表示的任何排序语义对于以编程方式注册的后处理器都将被忽略。此外,BeanPostProcessor bean不考虑 @Order 注解。 public interface BeanPostProcessor { /** * 在传入的bean属性设置之后、初始化之前调用。在任何bean初始化回调(如initializengbean的 * afterPropertiesSet或自定义init方法)之前,将此BeanPostProcessor应用于给定的新bean实例。 * bean已经填充了属性值。返回的bean实例可以是原始实例的包装器。 * 默认实现按原样返回给定的bean。 */ @Nullable default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { return bean; } /** * 在传入的bean初始化完成之后调用 * 在任何bean初始化回调(如initializengbean的afterPropertiesSet或自定义init方法)之后, * 将此BeanPostProcessor应用于给定的新bean实例。bean已经填充了属性值。 * 返回的bean实例可以是原始实例的包装器。 * 对于FactoryBean,将为FactoryBean实例和由FactoryBean创建的对象(从Spring 2....

2020-08-12 · 4 min · 804 words · Hank

Java设计模式(2)-软件设计遵循的七大原则

设计模式为面向对象软件设计的前进道路照亮了明灯,而设计模式遵循面向对象软件设计的七大原则,这些原则也是设计模式的基础。 1. 单一职责原则 单一职责原则(Single Responsibility),它的定义是:对类来说的,一个类应该只负责一项职责。即是说,一个类只负责一种职责,让其职责单一化,如果有其他的职责则应该设计另外的类来进行协助。比如:订单处理类只负责处理订单,而不该去处理支付的逻辑。单一职责原则也可以引申到方法、类、模块甚至服务和子系统中。 类的职责单一,并不是说一个类只有一个方法,而是说一个类只承担实现一中业务职责,如:OrderService只负责处理订单相关的逻辑,而不会涉及支付相关的逻辑。 下面来看看具体的例子。假设我们设计一个Animal类,来管理动物的飞行、陆行、游水等动作。 1.1. 基础示例 这个版本的设计如下图所示: Figure 1. 初版设计 初版的代码如下: Animal类 // 一个类负责动物的飞行方法 abstract class Animal { public void fly() { System.out.println(getName() + "能够飞行"); } public void runOnLand() { System.out.println(getName() + "能在陆地上跑"); } public void swimInWater() { System.out.println(getName() + "能在水中游"); } protected abstract String getName(); } 这里设计了一个抽象类,里边有各种方法来打印某种动物(具备名称)的这些行为。 然后,各种动物都可以继承 Animal : Animal下的具体动物 // 小鸟 class Bird extends Animal { @Override public String getName() { return "小鸟"; } } // 大雁 class WildGoose extends Animal { @Override public String getName() { return "大雁"; } } // 狗 class Dog extends Animal { @Override public String getName() { return "狗"; } } // ……其他动物 调用方的代码如下:...

2020-05-27 · 6 min · 1094 words · Hank

Java设计模式(1)-什么是设计模式?

什么是设计模式?顾名思义,设计模式是一种软件设计所使用的方法,适用于面向对象软件设计。设计模式是一种方法,或者说是一种方法论,它是软件前辈们经历了无数的软件工程设计中的痛点和教训之后,总结出来的经验的结晶。设计模式是一种软件设计方法,不是代码,也不是规范。 1. 设计模式的提出 如果设计出优秀的软件一直是软件从业者探索的目标。优秀的软件,必须具备可复用、可扩展、可维护、高性能等特点,既要保证系统的高内聚、低耦合的特点,又要保证系统随时应对各种变化的灵活性。然很,这往往依赖于软件设计者的个人经验积累,而新手往往对此无从下手。 设计模式的提出,为软件设计指明了方向。 设计模式概念的提出,要追溯到1994年。在1994年,由Erich Gamma、Richard Helm、Ralph Johnson和John Vlissides 四为软件大师合著了一本名为 Design Patterns - Elements of Reusable Object-Oriented Software(中文译名:设计模式 - 可复用面向对象软件的基础)的书,该书首次提到了软件开发中设计模式的概念。 Figure 1. 设计模式-可复用面向对象软件设计的基础 Ps: 公众号后台回复关键字"设计模式"下载此书 该书总结了面向对象设计中最有价值的经验,并总结出了23中最常见和最有价值的设计模式,为软件设计之路开启了一盏明灯,此书的四名作者又被称为GOF(Gang Of Four,四人帮)。这23种设计模式主要基于面向对象设计的两种基本原则: 对接口编程而不是对实现编程 优先使用对象组合而不是继承 2. 设计模式的概念 设计模式来源于城市和建筑模式: 每一个模式描述了一个在我们周围不断重复发生的问题,以及该问题解决方案的核心。这样,你就能一次又一次的使用该方案而不必做重复劳动。 — Christopher Alexander 这个思想同样适用于面向对象的设计模式。一般而言,设计模式包含四大要素: 名称(pattern name): 描述模式解决的问题、方案和效果 问题(problem): 描述何时使用模式、设计的问题以及问题存在的背景 解决方案(solution): 描述了设计的组成部分、他们之间的相互关系和协作的方式 效果(consequence): 描述了使用模式应该达到的效果和应该权衡的问题 3. 设计模式的类型 书中对设计模式进行了详细阐述,并将设计模式分为三大类型: 3.1. 创建型模式(Creational) 对象怎么创建?Java中,最简单的是使用关键 new 来创建一个对象实例,然而,我们需要思考:我们需要这么多对象实例吗?这种方式是我们需要的吗?这种方式是最好的吗?创建型模式抽象了对象实例化过程,它描述了如何去创建对象,而无需关注实例化细节。创建型模式包括: 简单工厂模式(Factory Pattern)、 抽象工厂模式(Abstract Factory Pattern)、 工厂方法模式(Factory Method Pattern) 单例模式(Singleton Pattern) 原型模式(Prototype Pattern) 建造者模式(Builder Pattern)...

2020-05-26 · 1 min · 148 words · Hank

给Spring中注册Bean的几种方式

1. 使用@Bean定义单个Bean 基于 @Bean 注解导入单个Bean。这种方式跟xml中 <bean> 标签等价,可以添加外部自定义Bean,但是需要自己创建Bean实例,而且只能导入单个Bean。注解定义如下: @Bean注解定义 @Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Bean { // 自定义bean的名称 @AliasFor("name") String[] value() default {}; // 同value属性,自定义bean的名称 @AliasFor("value") String[] name() default {}; // 设置当前注入的bean是否可用于自动注入,默认是true。 // 如果设置为false,那么即使该bean注入到Spring了,在自动注入时也会找不到bean而抛出NoSuchBeanDefinitionException异常。 // 5.1版本新增 boolean autowireCandidate() default true; (1) // 自定义Bean的初始化方法名称,Spring 在Bean初始化时会调用该方法 String initMethod() default ""; // 自定义Bean的销毁方法名称,Spring在容器关闭时会调用该方法进行自定义Bean销毁工作 String destroyMethod() default AbstractBeanDefinition.INFER_METHOD; } 1 功能与 @Primary 注解相关,都用于自动注入时Bean的选择,而 @Primary 用于指定注入时存在多个Bean实例时优先用哪个,而 autowireCandidate 属性则是设置Bean是否参与自动注入,true 则参与,false 则不参与(即使有Bean实例也可能在自动注入时抛出 NoSuchBeanDefinitionException 异常) @Bean 一般标注在方法上,Bean的名称默认就是方法名,也可以通过 name 属性来指定名称。每次只能注册一个Bean,默认注册的Bean都是单例的,可以通过 @Scope 注解来定义Bean的范围。另外,尽管 @Bean 定义了生命周期的初始化和销毁方法,但是这种方式是硬编码的方式配置,不推荐使用。...

2020-05-07 · 4 min · 825 words · Hank

分布式事务的几种解决方案

分布式系统,与传统单体架构系统相比,其结构复杂的多,系统间靠网络传输数据,系统存在诸多不确定因素,如硬件故障、网络波动等,都会影响整个系统的稳定性。而分布式事务更是分布式系统的一大难题,本篇将讨论业界分布式事务的几种常见解决方案。 1. 数据库事务 百度百科对事务的定义: 数据库事务( transaction)是访问并可能操作各种数据项的一个数据库操作序列,这些操作要么全部执行,要么全部不执行,是一个不可分割的工作单位。 解读一下这个定义: 事务产生于一个数据库操作序列:一组读取或者更新数据库等操作时,事务是一种机制,来保证了最终数据的一致性, 事务是一个不可分割的单元:一组操作不能拆分,是一个整体 事务执行:要么全部执行,要么全部不执行,不能部分执行 通常,数据库的事务是通过数据库日志来保证的,所有数据库的操作都记录日志,如果数据库发生宕机,那么通过读取日志可以知道操作时需要回滚还是提交。 众所周知,数据库事务有ACID四大特性,满足ACID特性的事务,一般称之为刚性事务,常见的单体应用(单个数据源)内部都是采用刚性事务。 最常见的事务的例子就是银行转账: 2. 分布式事务 分布式事务,指的是分布式系统间完成的事务机制,百度百科定义如下: 数据库事务( transaction)是访问并可能操作各种数据项的一个数据库操作序列,这些操作要么全部执行,要么全部不执行,是一个不可分割的工作单位。 分布式同数据库事务一样,都用以保证数据的一致性,只是事务的参与者由原来的单数据源的一组操作序列变成了分布式系统的多个子系统。 举个例子: 用户下单流程,如下图所示: 首先,订单系统订单支付完成,然后调用库存系统扣减库存,最后再调用积分系统给账户添加积分,第1、2、3步必须全部执行成功,那么下单操作才算成功,否则,有一步出现错误,那么下单操作就是失败的。与传统事务相比,分布式事务范围在数据库事务之上进行了放大,各个系统除了有本系统事务外,还需要符合整个分布式系统的事务机制,才能保证最终的数据一致性。 但是,在分布式系统中,由于其固有的复杂性,很难保证事务的ACID特性,这种刚性事务在分布式系统中不适用,这就要提到两个概念:CAP定理和BASE理论。 2.1. CAP理论 2000年,名为Eric Brewer的教授在PODC研讨会上提出了CAP:一致性、可用性和分区容错性三者无法在分布式系统中被同时满足,并且最多只能满足其中两个!他还对CAP进行明确的定义: C(Consistency,一致性):所有的节点上的数据时刻保持同步 A(Avallable,可用性):每个请求都能接受到一个响应,无论响应成功或失败 P(Partition tolerance,分区容错性):系统应该能持续提供服务,即使系统内部有消息丢失(分区) CAP理论的提出,揭露了分布式系统的本质。 怎么理解CAP? 一致性,其实就是分布式系统各节点间数据达成一致;可用性,分布式系统始终保持可用,即使出现了数据不一致;分区容错性,分布式系统必须有一定的容错能力,当系统数据丢失或者某些节点不可用(分区)系统还能继续提供服务,系统容错是不可缺少的。 既然CAP三者不能同时满足,而分布式系统中,分区容错性是不可或缺的,因此,只能在CP和AP中间选择。 由于在CAP理论要求各节点数据时刻保持同步,但是这样的强一致性只能在CP中保证,而选择满足AP时无法保证,这就引出BASE理论。 2.2. BASE理论 eBay的架构师Dan Pritchett源于对大规模分布式系统的实践总结,在ACM上发表文章提出BASE理论,BASE理论是对CAP理论的延伸,核心思想是即使无法做到强一致性,但应用可以采用适合的方式达到最终一致性(Eventual Consitency)。 BA: Basically Available(基本可用) S: Soft state(软状态) E: Eventually consistent(最终一致性) 显然,BASE理论满足CAP中的AP,从而舍弃了强一致性C。 满足BASE理论,放弃强一致性,数据最终达到一致的事务我们称之为柔性事务(Flexible Transactions)。 如果分布式系统满足AP,那么虽然数据不可能时刻保持一致,但是可以达到最终一致;如果满足产CP,那么分布式系统舍弃了高可用性,却可以保持数据强一致。分布式系统中,常见的两个服务注册中心Eureka、Consul,前者满足的是AP,而后者满足的是CP。 3. 分布式事务的解决方案 分布式事务解决的核心问题就是分布式系统中各个节点的数据一致性问题,选择强一致还是最终一致,需要根据具体业务需要合理做出选择。目前,常见的几种分布式事务解决方案有2PC(两阶段提交)、3PC(三阶段提交)、TCC(补偿性事务)、本地消息表等。 3.1. 2PC 两阶段提交(2PC),其实是XA协议的标准实现,XA协议由Tuxedo首先提出的,并交给X/Open组织,作为资源管理器(数据库)与事务管理器的接口标准。目前,Oracle、Informix、DB2和Sybase等各大数据库厂家都提供对XA的支持。 2PC事务包括几个角色:事务协调者(coordinator)、事务参与者(participant )、资源管理器(resource manager,RM): 事务协调者:负责收集事务参与者的信息,并决定事务提交/回滚操作 事务参与者:按照事务协调者要求参与事务,可进行事务预提交、提交或者回滚操作 资源管理器:事务参与者管理的数据库 2PC将事务的管理分为两个阶段: 1、第一阶段(prepare):事务协调者要求每个事务参与者预提交(precommit)事务,事务并不会真正提交,并反馈(vote)提交结果 2、第二阶段(commit/rollback):事务协调者收集参与者信息,如果每个参与者都反馈可提交事务,那么协调者下发commit指令给参与者要求提交事务,否则,如果有参与者反馈回滚事务,则协调者下发abort指令要求参与者都回滚事务...

2020-04-15 · 1 min · 170 words · Hank

Java编程思想(第4版)中文高清版PDF下载

Java编程思想一书赢得了全球程序员的广泛赞誉,即使是最晦涩的概念,在Bruce Eckel的文字亲和力和小而直接的编程示例面前也会化解于无形。从Java的基础语法到最高级特性(深入的面向对象概念、多线程、自动项目构建、单元测试和调试等),本书都能逐步指导你轻松掌握。 1. 站长评价 Java程序员必备参考书、工具书,长期霸占Java编程书籍排行榜榜首的神级著作!Java编程必读书籍,需要反复研读、反复体会编程思想! 丰富指数:☆☆☆☆ 难度指数:☆☆☆ 推荐指数:☆☆☆☆☆ 迫不及待了?点此立即下载! 2. 内容简介 本书赢得了全球程序员的广泛赞誉,即使是最晦涩的概念,在Bruce Eckel的文字亲和力和小而直接的编程示例面前也会化解于无形。从Java的基础语法到最高级特性(深入的面向对象概念、多线程、自动项目构建、单元测试和调试等),本书都能逐步指导你轻松掌握。 从本书获得的各项大奖以及来自世界各地的读者评论中,不难看出这是一本经典之作。本书的作者拥有多年教学经验,对C、C++以及Java语言都有独到、深入的见解,以通俗易懂及小而直接的示例解释了一个个晦涩抽象的概念。本书共22章,包括操作符、控制执行流程、访问权限控制、复用类、多态、接口、通过异常处理错误、字符串、泛型、数组、容器深入研究、Java I/O系统、枚举类型、并发以及图形化用户界面等内容。这些丰富的内容,包含了Java语言基础语法以及高级特性,适合各个层次的Java程序员阅读,同时也是高等院校讲授面向对象程序设计语言以及Java语言的绝佳教材和参考书。 第4版特点: 适合初学者与专业人员的经典的面向对象叙述方式,为更新的Java SE5/6增加了新的示例和章节。 测验框架显示程序输出。 设计模式贯穿于众多示例中:适配器、桥接器、职责链、命令、装饰器、外观、工厂方法、享元、点名、数据传输对象、空对象、代理、单例、状态、策略、模板方法以及访问者。 为数据传输引入了XML,为用户界面引入了SWT和Flash。 重新撰写了有关并发的章节,有助于读者掌握线程的相关知识。 专门为第4版以及Java SE5/6重写了700多个编译文件中的500多个程序。 支持网站包含了所有源代码、带注解的解决方案指南、网络日志以及多媒体学习资料。 覆盖了所有基础知识,同时论述了高级特性。 详细地阐述了面向对象原理。 在线可获得Java讲座CD,其中包含Bruce Eckel的全部多媒体讲座。 3. 作者简介 Bruce Eckel是MindView公司(www.MindView.net)的总裁,该公司向客户提供软件咨询和培训。他是C标准委员会拥有表决权的成员之一,拥有应用物理学学士和计算机工程硕士学位。除本书外,他还是《C编程思想》的作者,并与人合著了《C++编程思想 第2卷》(这两本书的英文影印版及中文版均已由机械工业出版社引进出版)及其他著作。他已经发表了150多篇论文,还经常参加世界各地的研讨会并进行演讲。 4. 目录 读者评论 前言 简介 第1章 对象导论 1.1 抽象过程 1.2 每个对象都有一个接口 1.3 每个对象都提供服务 1.4 被隐藏的具体实现 1.5 复用具体实现 1.6 继承 1.6.1 “是一个”(is-a)与“像是一个”(is-like-a)关系 1.7 伴随多态的可互换对象 1.8 单根继承结构 1.9 容器 1.9.1 参数化类型(范型) 1.10 对象的创建和生命期 1.11 异常处理:处理错误 1.12 并发编程...

2020-04-14 · 2 min · 350 words · Hank

Spring IoC容器实现原理

1. IOC容器接口设计 Spring IOC容器接口设计如下图所示: Figure 1. Spring IoC容器接口设计 IOC容器两个核心的接口为BeanFacotry 和ApplicationContext,前者提供容器管理Bean的基本功能,后者则扩展 BeanFactory,提供了容器级的强大功能,如国际化支持、资源加载、事件、应用上下文环境等。 2. BeanFactory BeanFactory ,即bean工厂,定义IOC容器的基本规范,提供管理容器Bean的基本功能,如获取bean、判断单例、原型、判断包含bean、获取bean类型和别名等。下边有三个子接口 ListableBeanFactory、HierarchicalBeanFactory、AutowireCapableBeanFactory,他们具备不同的功能。 ListableBeanFactory 扩展 BeanFacoty ,增加了列举容器中bean的功能,BeanFactory 提供查询单个Bean的方法,而 ListableBeanFactory 可以查询多个Bean,如获取 BeanDefinition 的名称、数量、按类型查询bean和bean的名称等。 HierarchicalBeanFactory 在 BeanFactory 的基础上增加了父级 BeanFactory 的功能,即 BeanFacotry 可以有层级关系了。 AutowireCapableBeanFactory 在 BeanFactory 的基础上增加自动注入相关的功能。 ConfigurableBeanFactory 这是一个配置接口,扩展 HierarchicalBeanFactory,用于给 BeanFactory 提供各种配置功能,如配置 parentBeanFactory、beanClassLoader 添加后置处理器等等。 ConfigurableListableBeanFactory 这是一个配置接口,提供了大量方法来扩展 ConfigurableBeanFactory 和 ListableBeanFactory ,大多数可列举的bean工厂都需要实现它,除了 ConfigurableBeanFactory 的配置功能外,它还具备分析和修改bean定义以及预实例化单例bean的的能力。 3. ApplicationContext BeanFactory 提供了管理bean的基础功能,而 ApplicationContext 则在其基础上有用强大的容器特性。ApplicationContext 作为应用上下文,是Spring IOC容器的核心接口,具备 BeanFactory 基础管理bean的功能,还继承了 EnvironmentCapable 、 MessageSource 、 ResourceLoader 、 ApplicationEventPublisher ,具有资源加载、国际化支持、容器事件支持、系统环境支持等能力。...

2020-04-08 · 3 min · 627 words · Hank

Docker时间和系统时间不匹配,相差8小时

使用 maven docker 插件部署时,发现docker容器时间与本地时间相差8小时。 因为docker的时区与系统时区不同,docker容器默认的时区采用的UTC, 而中国时区为CTS,与之差8小时(东8区),可以进入容器使用date命令查看时区: root@pro-server-3:~/trainer# date Fri Mar 12 12:56:40 CST 2021 CST: China Standard Time,UTC+8:00 中国沿海时间(北京时间) UTC: Universal Time Coordinated 世界协调时间 所以设置一下Docker容器时区即可,我采用的docker maven插件,所以增加如下命令: 意思就是容器构建时执行 RUN 命令修改系统时间 /etc/timezone,将其设置为上海时间,问题解决。

2020-03-11 · 1 min · 30 words · Hank

Spring Bean自动扫描原理

Spring 基于注解的Bean自动扫描是由后置处理器 ConfigurationClassPostProcessor 来实现的。 1. ConfigurationClassPostProcessor类 ConfigurationClassPostProcessor 是一个 BeanDefinitionRegistryPostProcessor 实现,它的核心功能就是在容器启动时处理Spring的Java代码配置类(被 @Configuration 注解标记)。而且该类实现了 PriorityOrdered 接口,表示它是一个高优先级的后置处理器,会首先被执行。 ConfigurationClassPostProcessor 的定义如下: public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPostProcessor, PriorityOrdered, ResourceLoaderAware, BeanClassLoaderAware, EnvironmentAware { // 省略代码 } 2. 注册bean定义 创建 AnnotationConfigApplicationContext 时,其构造函数如下: AnnotationConfigApplicationContext构造函数 public AnnotationConfigApplicationContext(Class<?>... componentClasses) { this(); register(componentClasses); refresh(); } 首先,会调用 this() 方法调用构造函数: public AnnotationConfigApplicationContext() { this.reader = new AnnotatedBeanDefinitionReader(this); this.scanner = new ClassPathBeanDefinitionScanner(this); } 此时会创建 AnnotatedBeanDefinitionReader 和 ClassPathBeanDefinitionScanner 对象,前者用来读取配置类(@Configuration 注解标注)所定义的Bean定义信息,而后者用来扫描*classpath*下的bean定义信息。 AnnotatedBeanDefinitionReader 内部创建实例时会执行: AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry) 该方法会向容器注册几个默认的组件,各有不同的作用,其中就包括 ConfigurationClassPostProcessor,它的组件名称为 org....

2020-03-10 · 9 min · 1827 words · Hank

Docker开启监听TCP端口,解决修改docker配置文件无效

Jenkins配置了Docker插件,结果发现不能远程调用远端的docker,原来是tcp端口没有打开。 ubuntu版本号:16.04 root@ubuntu:/etc/docker# cat /proc/version Linux version 4.4.0-62-generic (buildd@lcy01-30) (gcc version 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu1~16.04.4) ) #83-Ubuntu SMP Wed Jan 18 14:10:15 UTC 2017 ubuntu docker配置文件在 /etc/default/docker jenkins docker plugin链接远程docker时,需要docker开启tcp端口,编辑docker配置文件添加配置: vi /etc/default/docker 添加内容: DOCKER_OPTS="-H tcp://0.0.0.0:2376 -H unix:///var/run/docker.sock" 注意大小写,否则docker可能启动不起来。 重启docker: service docker restart 发现无效,查看docker进程,并未添加DOCKER_OPTS参数信息: root@ubuntu:/etc/docker# ps -ef | grep docker root 22829 1 0 14:28 ? 00:00:00 /usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock 编辑文件: vi /lib/systemd/system/docker.service 在[service]节点添加如下内容: EnvironmentFile=/etc/default/docker 然后修改ExecStart为: ExecStart=/usr/bin/dockerd -H fd:// $DOCKER_OPTS --containerd=/run/containerd/containerd....

2020-02-18 · 1 min · 97 words · Hank

IntelliJ IDEA 2019.3.3激活

IntelliJ IDEA 2019.3.3版本已经可以成功激活了,更新了非常多的性特性并极大的提升了性能。理论上此种激活方式适合2019.3的所有版本,不过笔者仅测试了3.3版本。 1. 激活步骤 具体激活步骤如下: 1、下载最新版本的idea, 目前版本为2019.3.3 2、下载用于激活的代理压缩包,下载地址如下: 下载地址 由于版权限制,请关注公众号并在后台回复6002获取下载地址!如果链接失效,请在公众号后台留言,我会第一时间补上。 3、解压缩下载的代理包,得到jetbrains-agent.jar,把它放到你认为合适的文件夹内 4、启动IDE,如果需要注册,选择:试用(Evaluate for free)进入IDE 5、点击你要注册的IDE菜单:"Configure" 或 "Help" → "Edit Custom VM Options …​" 如果提示是否要创建文件,请点"Yes",在打开的vmoptions编辑窗口末行添加: -javaagent:/absolute/path/to/jetbrains-agent.jar 一定要自己确认好路径(不要使用中文路径),要使用绝对路径,填错会导致IDE打不开! 另外,一个vmoptions内只能有一个-javaagent参数。 示例: mac: -javaagent:/Users/neo/jetbrains-agent.jar linux: -javaagent:/home/neo/jetbrains-agent.jar windows: -javaagent:C:\Users\neo\jetbrains-agent.jar 6、重启你的IDE 7、点击IDE菜单 "Help" → "Register…​" 或 "Configure" → "Manage License…​" 支持两种注册方式:License server 和 Activation code: 1). 选择License server方式,地址填入:http://fls.jetbrains-agent.com (注意与之前2.x的地址不同,通常会自动填上) 或者点击按钮:"Discover Server"来自动填充地址。 2). 选择Activation code方式离线激活,请使用压缩包内的ACTIVATION_CODE.txt中的注册码激活 推荐使用第一种方式。 OK,激活成功! 2. 常见问题 1、如果vmoptions中地址填错了,怎么办? 默认情况下,idea的VMoptions配置文件在用户目录中,我的mac电脑在: Contents> vi /Users/sun/Library/Preferences/IntelliJIdea2019.3/idea.vmoptions 如果还是填错了,可以找到用户目录下的这个文件,直接修改为正确的地址即可!...

2020-02-14 · 1 min · 78 words · Hank

Spring IoC容器启动过程

1. 容器启动入口 一般而言,都使用 ApplicationContext 容器实现,这里使用了基于注解的 AnnotationConfigApplicationContext 实现。 要跟踪Spring的源码,可能需要结合不同的功能测试代码,以便跟踪源码时查看Spring具体的功能实现。比如:要了解Spring @ComponentScan 注解的原理,则首先需要编写一个配置类(@Configuration),然后定义组件扫描。 本文的跟踪代码的过程可能涉及多个部分的测试代码,但是容器启动的代码都类似,详细的代码见 Github。 容器启动测试代码[1] AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(ScopeConfig.class); (1) System.out.println("IOC容器创建完成..."); User user = applicationContext.getBean(User.class); User aUser = applicationContext.getBean(User.class); System.out.println(user == aUser); 1 构造一个 AnnotationConfigApplicationContext 对象。 AnnotationConfigApplicationContext 的构造函数如下: AnnotationConfigApplicationContext构造函数 public AnnotationConfigApplicationContext(Class<?>... componentClasses) { this(); register(componentClasses); refresh(); } 1.1. ApplicationContext 类实现关系 AnnotationConfigApplicationContext 扩展了 GenericApplicationContext,增加了基于java配置类的支持,而 GenericApplicationContext 内部持有一个 DefaultListableBeanFactory 实例,而且仅允许刷新一次容器: GenericApplicationContext构造函数 public GenericApplicationContext() { this.beanFactory = new DefaultListableBeanFactory(); } 1.2. 创建bean定义读取器 首先,在构造函数中会执行 this() 方法调用构造函数:...

2020-02-06 · 7 min · 1345 words · Hank

修改docker容器端口映射

在docker run创建并运行容器的时候,可以通过 -p 指定端口映射规则。但是,我们经常会遇到刚开始忘记设置端口映射或者设置错了需要修改。当docker start运行容器后并没有提供一个-p选项或设置,让你修改指定端口映射规则。那么这种情况我们该怎么处理呢?今天Docker君教你如何修改运行中的docker容器的端口映射? 方法一:删除原有容器,重新建新容器 这个解决方案最为简单,把原来的容器删掉,重新建一个。当然这次不要忘记加上端口映射。 优缺点: 优点是简单快捷,在测试环境使用较多。缺点是如果是数据库镜像,那重新建一个又要重新配置一次,就比较麻烦了。 方法二:修改容器配置文件,重启docker服务 容器的配置文件路径: /var/lib/docker/containers/[hash_of_the_container]/hostconfig.json 其中的 hashofthecontainer 是docker镜像的hash值,可以通过 docker ps 或者 docker inspect containername 查看。(CONTAINER ID就可以看出来) 文件中其中有一项是PortBindings,其中8080/tcp对应的是容器内部的8080端口,HostPort对应的是映射到宿主机的端口9190。8361/tcp对应的是容器内部的8361端口,HostPort对应的是映射到宿主机的端口9191。按需修改端口,然后重启docker服务,再启动容器服务就可以了。 systemctl restart docker 优缺点: 这个方法的优点是没有副作用,操作简单。缺点是需要重启整个docker服务,如果在同一个宿主机上运行着多个容器服务的话,就会影响其他容器服务。 方法三:利用docker commit新构镜像 docker commit:把一个容器的文件改动和配置信息commit到一个新的镜像。这个在测试的时候会非常有用,把容器所有的文件改动和配置信息导入成一个新的docker镜像,然后用这个新的镜像重起一个容器,这对之前的容器不会有任何影响。 1、停止docker容器 docker stop container01 2、commit该docker容器 docker commit container01 new_image:tag 3、用前一步新生成的镜像重新起一个容器 docker run --name container02 -p 80:80 new_image:tag 优缺点: 这种方式的优点是不会影响统一宿主机上的其他容器,缺点是管理起来显得比较乱,没有第二种方法那么直观。

2020-01-20 · 1 min · 52 words · Hank

Docker入门系列八——使用Dockerfile构建镜像

Dockfile就是一个文本文件,里边包含了一行行的指令,用来描述如何创建自定义镜像。 1. 使用 使用 docker build 命令来基于Dockerfile文件和上下文构建镜像,构建上下文指的是特定路径(PATH或URL)的文件集合,PATH用来指定本地文件系统目录,而URL用来指定Git仓库的地址,它们包含的所有文件(子目录或子模块)都会被递归处理。在大多数情况下,最好以空目录作为上下文,并将Dockerfile保存在该目录中。仅添加构建Dockerfile所需的文件。 docker build命令语法: docker build [OPTIONS] PATH | URL | - 例如: $ docker build . Sending build context to Docker daemon 6.51 MB ... 上边的命令会被docker daemon程序来处理而不是docker CLI,构建进程将发送指定路径下的Dockerfile文件到docker daemon。 Dockerfiie通过命令来关联构建上下文的文件,如 COPY、ADD 等命令。另外,可以通过 .dockerignore文件来排除文件或目录,以提高构建速度和性能。 一般而言,Dockerfile文件应该位于构建上下文的根目录中,但是,也允许使用 -f 选项来指定它的位置: $ docker build -f /path/to/a/Dockerfile . 也可以在镜像构建完成后指定tag: $ docker build -t shykes/myapp . 多次使用 -t 选项可以指定多个tag。 Docker daemon运行Dockfile之前会先进行校验,校验失败会输出错误信息: $ docker build -t test/myapp . Sending build context to Docker daemon 2....

2019-11-15 · 4 min · 711 words · Hank

使用Springboot开发websocket程序(四)——使用RabbitMQ作为STOMP消息代理

上一篇,我们在介绍了Spring中如何使用websocket的子协议stomp,并使用简单的基于内存的stomp消息代理来编写了一个web聊天室实例。基于内存的stomp消息代理,虽然能够满足基本需求,但还是存在一些不足,比如由于stomp代理在应用内部,多个外部websocket应用需要消息互通,那么就难以满足了。在本篇,我们来学习如何使用RabbitMQ作为stomp代理。 1. 为何要使用外部消息代理 简单消息代理,能够满足单websocket应用的需要,但是如果有多个websocket应用,他们之间需要进行消息共享,那么就需要做大量的工作才能实现了。其实,MQ一个最重要的作用就在于能个在各个系统间解耦。引入外部MQ作为stomp消息代理,很好的解决了多系统消息共享的问题,只要其支持stomp协议。RabbitMQ本身提供了对STOMP的支持,加上后结构变化如下: 前边的是单应用时的结构,后边为怎么了RabbitMQ过后,多个应用程序结构。 2. RabbitMQ对STOMP的支持 RabbitMQ对stomp协议的支持是通过插件的方式,默认stomp插件是关闭的,我们需要先启用之。 2.1. 启用插件 进入rabbitmq所在服务器,然后控制台输入如下命令来启用stomp插件: ``rabbitmq-plugins enable rabbitmq_stomp`` 然后可以查看插件是否启用成功: ``rabbitmq-plugins list`` 2.2. 插件配置 默认情况下,STOMP将会监听61613端口,默认的用户名和密码都为guest。通过配置文件来配置: ubuntu下rabbitmq的配置文件在/etc/rabbitmq/rabbitmq.conf,找到stomp开头的选项,就可以进行配置了 比如配置STOMP监听端口: ``stomp.listeners.tcp.1 = 12345`` RabbitMQ中STOMP适配器连接时如果用户名和密码使用默认的guest/guest,则可以忽略,如果需要修改,则配置如下: stomp.default_user = guest stomp.default_pass = guest 2.3. Destinations STOMP规范并没有规定消息代理来支持什么样的目的地(destination),只是根据消息头的destination的值来判断消息发送的目的地,一般由消息代理自定义支持,RabbitMQ中定义了几种destination类型: #exchange[/exchange]: 发送到任意的routing key和订阅任意的binding key #queue[/queue]: 发送和订阅队列,该队列由STOMP管理 #amqqueue[/amq/queue]: 发送和订阅外部创建的队列 #topic[/topic]: 发送和订阅到topic #temptopic[/temp-queue/]: 创建临时的队列(使用reply-to请求头) 现在,我们结合代码来看看Spring中对RabbitMQ的这几类destination是如何支持的。 3. Spring中使用RabbitMQ消息代理 我们通过一个demo来看看如何在Spring中使用RabbitMQ支持的这几个destination,整体界面如下; 下边的示例仅贴上部分关键代码,完整的代码可以参看文末的源码。 首先,我们创建一个名为03-websocket-stomp-rabbitmq的springboot工程,引入如下依赖: <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-amqp</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency> <dependency> <groupId>io.projectreactor</groupId> <artifactId>reactor-net</artifactId> <version>2.0.5.RELEASE</version> </dependency> <dependency> <groupId>io.netty</groupId> <artifactId>netty-all</artifactId> <version>4....

2019-11-12 · 3 min · 509 words · Hank

使用Springboot开发websocket程序(三)——基于子协议STOMP的web聊天室

我们使用Spring Boot对原生WebSocket的支持来编写了一个简单的聊天室程序。它仅支持群发,如果要做到点对点消息,我们还需要编写代码自己实现。本篇,我们将使用websocket的子协议STOMP来更新我们的聊天室程序,使其可以支持单聊。 1. STOMP 1.1. 为什么要支持子协议 WebSocket是一种简单的通信协议,而并非消息协议。它是TCP之上的非常薄的一层,所做的事情仅仅是将字节流转换为消息流(文本或二进制)。至于消息是什么含义,怎么路由和解析,都交由应用程序自身来决定。HTTP协议是一种应用级协议,它明确告诉我们请求的地址、格式、编码等等信息,但是websocket与之不同,websocket并不提供详细的信息来告诉我们如何路由和处理消息。因此,对于开发大型应用程序而言,websocke级别太低了,要实现非常复杂的功能,我们需要进行大量的编码工作。这就好比,大多JAVA WEB开发都会选择使用Spring框架,而不是基于Servlet API来实现。 基于这个原因,WebSocket RFC定义了 子协议的使用规范。在握手阶段,客户端和服务端使用Sec-WebSocket-Protocol请求头来通知彼此使用子协议,即更高级的、应用级的协议。当然,也可以不使用子协议,但是客户端和服务端仍然需要定义消息的格式。使用更规范的通用消息协议,更能让应用程序开发和维护变得简单。STOMP就是这样的一个消息协议,Spring框架提供了对其的支持。 1.2. 什么是STOMP STOMP即Simple (or Streaming) Text Orientated Messaging Protocol,中文称简单(流)文本定向消息协议,它是一种简单的互操作协议**,旨在通过消息代理在客户端之间传递异步消息,STOMP协议具有简单行和互操作性,因此在多种语言和多种平台上得到广泛地应用。。 STOMP是由于需要通过脚本语言(例如Ruby,Python和Perl)连接到企业消息代理而产生的,旨在对消息进行简单的消息处理,例如可靠地发送单个消息并断开连接或在给定目的地上消耗所有消息等等。它是其他开放消息协议(例如AMQP)和JMS代理(例如OpenWire)中使用的实现特定wire protocol的替代。STOMP仅仅实现部分常用的消息API,而不是实现完整的消息API,因此更加轻量和简单。 STOMP除了支持文本消息外,也支持二进制消息,默认使用UTF-8的编码格式。 STOMP有几个比较重要的概念: frame:帧,即STOMP客户端和服务端发送的数据 COMMAND: frame的命令,由STOMP规范定义,用来表示消息的特定用途,比如连接服务端、发送消息等,每个frame都有命令 destination:消息发送的目的地址,通过消息头来表示,如destination:/topic/chat STOMP基于frame,frame的模型源于HTTP,一个frame由命令、一组可选的标头和可选的主体组成,客户端和服务端发送frame来进行通信。frame的格式如下: COMMAND header1:value1 header2:value2 Body^@ frame分为两个部分:消息头和消息体。消息头第一行为命令,然后跟key:value格式的头信息;然后是消息体,消息体跟消息头之间用一个空行分隔。最后,消息体后跟八位空字节(上文用^@表示)。另外,frame的命令和消息头信息都区分大小写。 STOMP客户端支持的命令包括:SEND、SUBSCRIBE、UNSUBSCRIBE、BEGIN、COMMIT、ABORT、ACK、NACK、DISCONNECT、CONNECT、STOMP 服务端支持的命令包括:CONNECTED、MESSAGE、RECEIPT、ERROR 只有几种消息可以包含消息体:SEND、MESSAGE、ERROR 举个例子,客户端连接到服务端,则会想服务端发送如下的frame: CONNECT accept-version:1.0,1.1,2.0 host:stomp.github.org ^@ 服务端接收连接请求则会发送下边的frame给客户端,否则发送ERROR frame: CONNECTED version:1.2 ^@ 1.3. 使用STOMP的优点 使用STOMP作为子协议,与使用原生WebSocket相比,Spring框架和Spring Security可以提供更丰富的编程模型。具体的优点有以下几点: 无需自定义消息协议和消息格式 可以使用STOMP客户端,包括Spring框架中的Java客户端 消息代理(例如RabbitMQ,ActiveMQ和其他代理)可以用于管理订阅和广播消息 可以在任意数量的@Controller中组织应用程序逻辑,并根据STOMP消息头将消息路由给它们,而对于给定的连接,可以使用单个WebSocketHandler处理原始WebSocket消息 可以使用Spring Security基于STOMP 目标(destinations)和消息类型来对消息进行安全处理 详细内容请看STOMP规范: https://stomp.github.io/stomp-specification-1.2.html 2. Spring Boot中使用STOMP 使用STOMP有如此多的好处,我们看看Spring中如何使用。 在Spring boot中使用STOMP,大概需要以下几步: 1、首先,添加配置类,配置STOMP端点;然后,配置消息代理,Spring内置有简单消息代理(基于内存),也支持如RabbitMQ、ActiveMQ等专业消息代理中间件;配置哪些消息路径由应用程序处理 2、编写Controller处理消息,消息处理方法上加上@MessageMapping,结合@SendTo、@SendToUser...

2019-11-02 · 5 min · 914 words · Hank