首页 文章 精选 留言 我的

精选列表

搜索[最权威安装],共10000篇文章
优秀的个人博客,低调大师

滴滴史上严重服务故障,罪魁祸首是底层软件 or “降本增笑”?

2023年11月27日晚间,滴滴因系统故障导致App服务异常,不显示定位且无法打车。11月27日晚,滴滴出行进行了回复:非常抱歉,由于系统故障。 2023年11月28日早间,滴滴出行消息称,网约车等服务已恢复,骑车等在陆续修复中。11月28日,在滴滴发出公告的同时,记者在上海、深圳等地使用滴滴呼叫网约车,发现网约车功能并未恢复使用,网络加载异常,仍无法打车。11月28日,滴滴向记者回应称,网约车服务已恢复,司机乘客权益陆续恢复补发。 11月29日,滴滴再次发文致歉,称初步确定事故起因是底层系统软件发生故障。 来源:https://weibo.com/2838754010/NuMAAaUEl 在滴滴官方发布这份公告之前,已经有资深IT技术人士分析:“从表现上看,打车、共享单车全挂,不同的业务板块之间应该是有隔离的,说明问题出在更加底层的基础设施。攻击者一般只能访问到应用层,基础设施访问不到。要么是被攻击者打穿,要么是自己系统操作不慎挂了。即便是前者,也算是一种系统缺陷,才会被打穿。” 360安全专家认为,滴滴闪崩背后的技术原因可能有六种: 第一,系统更新升级过程中出现了编程错误、逻辑错误或未处理的异常情况:一般情况下,互联网厂商发布更新都会在晚上,与滴滴发生故障的时间也能对应,当然业务升级维护是放量更新,但现在滴滴全平台、全业务都故障了,说明肯定是他“家里”的问题。 第二,服务器故障:比如滴滴的核心机房,可能恒温恒湿环境出了问题,导致服务器过热、CPU烧了,或者核心机房所在地发生了自然灾害如地震、洪水、海啸等,这种情况下,硬件需要重新更换,里面的服务软件也需要重新配置,恢复周期相对较长,但这个可能性比较小。 第三,第三方服务故障:滴滴的后台架构可能使用了第三方服务或者组件。如果第三方出了问题,也可能会影响滴滴的正常运行。但出于安全性考虑,滴滴可能不会将核心业务托管给第三方,不过这个可能性也较小。 第四,DDOS攻击:黑客采用分布式拒绝服务的方式,抢占了大量的服务器资源,导致用户无法访问,但这个不太可能,因为DDos不会导致数据出错,而且滴滴从体量上来说,有足够的成本和能力去对抗。 第五,其他网络攻击:某些黑灰产团伙可能会通过拖库盗取数据,然后在暗网上售卖,在这个过程中不排除会有误操作,破坏了数据库。 第六,勒索病毒:网络攻击黑客对滴滴的底层数据、业务代码进行了加密。据披露现象,用户的账单和打车数据都算错了,存在一定可能是滴滴为了避免更大损失主动暂停了业务。近期勒索攻击事件屡屡发生,月初,某金融机构就是因为遭遇勒索病毒攻击造成了业务停摆。 不过也有网络安全公司专家认为,如果是来自外部的黑客攻击,公司一般会在第一时间进行声明。他猜测更集中于滴滴发生了内部重大业务调整,或有新业务接入原系统,但没有做好预案,导致关联业务或关联系统出现重大故障,这是大公司系统故障最常见的原因。 因此对于滴滴此次大规模的长时间故障,有行业人士认为,降本增效可能也是原因之一。 该人士认为,互联网公司核心业务频繁宕机,且长时间宕机,是降本增效的附属品之一。系统投资少了,维护资源少了,程序员更换频繁了,BUG就多。 他举例称,一般在业务上行阶段都有冗余,为了迎接随时爆发的订单,上行阶段要维持负载的上限不能过大,比如平时70%,这样遇到一个小爆发不用担心会出问题,足以应对小高峰;但是下行期的逻辑就不同了,负载很高的时候抗一抗就行了,虽然后面遇到小高峰可能会难受,但是随着时间的推移总体负载会下降。 最后来看一下网传的消息,有同行说滴滴这次严重故障是K8S的问题导致,当时SRE工程师定位了三个小时没定位到。

优秀的个人博客,低调大师

「X」Embedding in NLP|一文读懂 2023 年流行的 20 个 NLP 模型

在上一篇文章中,我们已经科普了什么是自然语言处理(NLP)、常见用例及其与向量数据库的结合。今天,依然是「X」Embedding in NLP 系列专题,本文为初阶第二篇,我们将深入介绍在 2023 年爆火的大语言模型 NLP 模型,包括 BERT、XLNet 等基础模型和 GPT、PaLM 等。 01.火爆 2023 年的 10 大大语言模型 大语言模型(LLM)是一种机器学习模型,可以执行各种 NLP 任务,包括文本翻译、回答问题、根据知识库分类和生成词汇等。大语言模型中的“大”体现在其架构使用的参数数量上,常见的 LLM 都包含数十亿个参数。以下是在 2023 年爆火且备受关注的 LLM。 OpenAI 推出的 GPT 系列 *GPT,全称 Generative pre-trained transformers,即生成式预训练 Transformer 模型 GPT-3 于2021年发布,包含 1750 亿个参数。 能够完成翻译、问答、写作论文,甚至生成代码等任务。 从模型架构而言,GPT-3 是只带有解码器(decoder)的 transformer 模型。 是最后一个由 OpenAI 公开参数数量的 GPT 模型。 自 2022 年 9 月起由微软独家使用。 GPT-3.5 2022 年推出的升级版 GPT 模型,包含参数更少。 ChatGPT 训练所用的模型是基于 GPT-3.5 模型微调而来的。GPT-3.5 一经推出即迅速走红,短短五天内吸引 100 万用户,用户总数在 2 个月内达到 1 亿。 GPT-3.5 模型基于截至 2021 年 9 月的数据进行训练,比之前版本的模型更具有时效性。 必应(Bing)搜索引擎中最初集成了 GPT-3.5,但目前使用的是 GPT-4。 GPT-4 GPT 系列中的最新版本,于 2023 年发布。 GPT-4 是多模态模型,支持图像和文本类型的输入。 在微软 Azure AI 的人工智能超级计算机上训练,比以往任何模型都更具创造力和协作性。 Google 推出的 PaLM 2 于 2023 年推出,展现 Google 在机器学习和 Responsible AI 领域积累的成果。 相比 PaLM,PaLM 2 基于并行多语言文本和更大的语料库进行预训练。 在高级推理、翻译和代码生成方面表现出色。 Meta 和 Microsoft 推出的 LLama2 于 2023 年发布,提供三种参数规格(70 亿、130 亿和 700 亿)的基础模型。 LLama 2 Chat 包括基础模型和针对对话功能微调的模型。 功能丰富,性能强大,专为查询和自然语言理解等任务设计。 Meta 专注于打造教育型应用产品,因此 LLaMA-2 是适用于 EdTech 平台理想的 AI 助手。 Anthropic 推出的 Claude 2 于 2023 年推出,擅长复杂的推理任务。 聚焦于 Constitutional AI,引导 AI 根据一些原则或规则进行自我完善和监督,避免产生有害或不友善的输出。 Claude 2 是一个友好的助手,能够完成用户发出的各种自然语言指令。 xAI 推出的 Grok-1 埃隆·马斯克的公司 xAI 于 2023 年宣布推出 Grok-1,用于巧妙回答几乎所有问题。 灵感来源于《银河系漫游指南》。 通过 𝕏 平台实时获取真实世界中的知识。 技术创新研究所(Technology Innovation Institute)推出的 Falcon 于 2023 年开源的模型。 包含 1800 亿参数,参数数量超过 Hugging Face Open LLM 排行榜上的 Llama。 基于高质量数据集训练,数据集中包含文本和代码,涵盖各种语言和方言。 Cohere 推出的 Cohere 2022 年由加拿大初创公司 Cohere 推出的开源多语言模型。 基于多样的数据集训练,能够理解超过 100 种语言的文本。 Oracle 和 Salesforce 产品中已接入 Cohere,主要用于语言生成、文本内容概括和情感分析等任务。 02.10 大基础 NLP 模型 BERT(基于 Transformer 的双向编码器表示技术) BERT 最初于 2018 年由 Jacob Devlin 在其论文《BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding》中首次提出。 BERT 模型的主要突破是,它在训练过程中查看文本时,以双向方式扫描文本,不是从左到右,也不是从左到左和从右到左的组合。 BERT 模型有两种配置——BERT(base)和 BERT(large),区别在于可配置参数数量。BERT(base)包含 1.1 亿参数, BERT(large)包含 3.45 亿参数。 XLNet XLNet 于 2019 年在论文《XLNet: Generalized Autoregressive Pretraining for Language Understanding》中发布。 XLNet使用排列语言建模(Permutation Language Modeling) 来融合自回归(autoregressive, AR)和去噪自编码(autoencoding, AE) 模型的优点。 传统的模型基于前一个词的上下文预测句子中的词。但与之不同的是,XLNet 的排列语言建模考虑了词之间的相互依赖关系。 XLNet 性能比 BERT 提高了 2-15%。 RoBERTa(强力优化的 BERT 方法) RoBERTa 于 2019 年在论文《RoBERTa: A Robustly Optimized BERT Pretraining Approach》中提出。 RoBERTa 改进了 BERT 的架构和训练流程。具体而言,RoBERTa 去掉下一句预测(NSP)任务,采用了更大的训练数据集,并使用了动态掩码替换静态掩码。 RoBERTa 性能比 BERT 提高了 2-20%。 ALBERT(轻量级的 BERT) ALBERT 模型于 2019 年在论文《ALBERT: A Lite BERT for Self-supervised Learning of Language Representations》中提出。 ALBERT 基于 BERT 模型改进,其主要亮点是在保持性能的同时显著减少了参数数量。 AlBERT 实现了跨层参数共享。也就是说,12 层 Encoder 共享一套参数。而 BERT 中每层 Encoder 都有一组参数。 StructBERT StructBERT 于 2019 年在论文《StructBERT: Incorporating Language Structures into Pre-training for Deep Language Understanding》中提出。 StructBERT 基于 BERT,将语言结构信息引入到预训练任务中。 StructBERT 还引入了单词结构目标(WSO),它有助于模型学习单词的排序。 T5(文本到文本的 Transformer) T5 在 2019 年的论文《Exploring the Limits of Transfer Learning with a Unified Text-to-Text Transformer》中被提出。T5 全称为“Text-To-Text Transfer Transformer”。 T5 作者还发布了一个数据集,取名为“Colossal Clean Crawled Corpus (超大型干净爬取数据)”,简称 C4。 T5 将所有 NLP 任务都转化成 Text-to-Text (文本到文本)任务。 T5 模型提供 5 种不同的参数配置:T5-small(6000 万参数)、T5-base(2.2 亿参数)、T5-large(7.7 亿参数)、T5-3B(30 亿参数)、T5-11B(110 亿参数)。 SentenceTransformers SentenceTransformers 最初于 2019 年在发论文《Sentence-BERT: Sentence Embeddings using Siamese BERT-Networks》中发表。 SentenceTransformers 是一个可以用于句子、文本和图像 embedding 的 Python 库 SentenceTransformers 可以计算超过 100 种语言的句子或文本 embedding。 SentenceTransformers 框架基于 PyTorch 和 Transformers,提供许多针对各种任务调优的预训练模型。 ERNIE(通过知识集成的增强表示) ERNIE 由百度开发,于 2019 年在论文《ERNIE: Enhanced Language Representation with Informative Entities》中首次被介绍,并由百度的研究人员在计算语言学协会(ACL)会议上展示。 ERNIE 将现实知识整合到预训练的语言模型中,从而理解人类语言的细微差别并提高各种 NLP 任务的表现。 ERNIE 提供不同版本。而且,ERNIE 随时间推移不断更新和完善,从而在广泛的 NLP 任务上取得更好的性能。 CTRL(可控文本生成) CTRL 由 Salesforce Research 在 2019 年 NeurIPS 论文《CTRL: A Conditional Transformer Language Model》中提出。 CTRL 允许用户控制生成文本的风格和内容。 CTRL 模型能够生成多样且可控的文本,用户对语言生成过程更可控。 ELECTRA ELECTRA 在 2020 年的论文《ELECTRA: Pre-training Text Encoders as Discriminators Rather Than Generators》中提出。 ELECTRA 提出了新的预训练任务和框架,把生成式的 Masked language model(MLM) 预训练任务改成了判别式的 Replaced token detection(RTD) 任务,判断当前token是否被语言模型替换过。 ELECTRA体积越小,相比于BERT就提升得越明显。

优秀的个人博客,低调大师

古老的 npm 包 request 已弃用,每周下载量达 1400 万+

去年我们报导过 HTTP 客户端 request 库将被弃用的计划,目前该项目的 npm 与 GitHub 主页均已显示,自 2020 年 2 月 11 日起,request 已完全弃用,预计不会有新的变更。 request 是添加到 npm 包仓库最早的模块之一,有众多应用依赖于request,目前其每周的下载量基本都在 1400 万以上,足见它在 HTTP 领域与 npm 中的地位。 但随着现代 JavaScript 的发展,request 的局限性也愈发体现出来,其核心模式也稍显过时。项目发起人 Mikeal Rogers表示也曾尝试通过改变以适应变化,但后来发现可行性非常低,因为兼容性问题很大。因此他决定废弃 request,重新打造一个项目。 在 Mikeal 写于去年 3 月份的说明中,他表示:“对于 JavaScript 生态,request 可以做的最有价值的事情是进入维护模式,并停止考虑新特性或主要版本。”同时他也对弃用 request 作了具体规划。如今 request 已经正式宣告退出,建议开发者尽快迁移。

优秀的个人博客,低调大师

可能是讲分布式系统到位的一篇文章

如果现在让你阐述一下什么是“分布式系统”,你脑子里第一下跳出来的是什么?我想,此时可以用苏东坡先生的一句诗,来形象地描述大家对分布式系统的认识: 横看成岭侧成峰,远近高低各不同。 我觉得每个人脑子里一下子涌现出来的肯定是非常具象的东西,就像下面这些: 如果你一下子想到的是XX中心、XX服务,意味着你把服务化的模式(SOA、ESB、微服务)和分布式系统错误地划上了等号。 那么,什么是“服务化”呢?服务化就像企业当中将相同岗位的人员划分到同一个部门管理,以此来收敛特定的工作入口,再进行二次分配,以提高人员利用率和劳动成果的复用度。服务化的本质是“分治”,而“分治”的前提是先要拆,然后才谈得上如何治。这时,高内聚、低耦合的思想在拆分过程中起到了一个非常重要的作用,因为这可以尽可能地降低拆分后不同组件间进行协作的复杂度。所以重要的是“怎么拆“,还有如何循序渐进地拆,而这个过程中你究竟是采用了何种服务化模式(比如SOA、ESB、微服务等)并不是关键。 为什么说“怎么拆”最重要呢?我来举个例子,企业的组织架构包括三种模型:职能型、项目型、矩阵型。你可以把这里的企业理解为一个“分布式系统”,把后面的3种模型理解为这个分布式系统的3种形态。作为这个“系统”的所有人,你需要考虑如何拆分它,才能使得各功能组件相互之间可以更好地协作。假设,你要将一个总计10000名员工的企业按“职能型”拆分成20个部门,得到的结果是每个部门500人。 这时,如果工作是流水线式的上下游关系。一个部门完工了再交给下一个部门。 那么这时候是高内聚、低耦合的。因为一个工种只与另一个工种产生了关联,并且仅有一次。 但如果工作需要频繁的由不同职能的人员同时进行,会导致同一个部门可能与多个部门产生联系。 那么,这时是低内聚、高耦合的。因为一个工种需要和其他多个工种产生关联并且远不止一次。 可以看到服务化体现了“分治”的效果,这也是分布式系统的核心思想,因此从“分治”这个本质上来看,服务化的确是分布式系统,但分布式系统不仅仅停留在那些服务化的模式上。 我相信,你在工作中参与开发的任何软件系统,到处都存在着需要拆分的地方,除非它的功能极简到只需要计算一个1+1。比如,当我们在电商平台点击“提交订单”的时候,会涉及生成订单、扣除积分、扣除库存等等动作。电商系统初期所有的功能可能都在一个系统里面,那么这些操作可以写在一个方法体里吗?我想只要代码能够成功运行,大部分人是不会管你怎么写的。但是如果这时需要增加一个红包功能呢?相信你或多或少遇到过在几百上千行代码中去增改功能的事情,其中的痛苦应该深有体会。 要解决这个问题就是要做拆分,通过梳理、归类,将不同的紧密相关的部分收敛到一个独立的逻辑体中,这个逻辑体可以是函数、类以及命名空间,等等。所以,从这个角度来说“分治”的问题其实早就存在我们的工作中,就看我们是否有去关注它了。因此,这并不只是我们在进行服务化时才需要考虑的问题。 那么如何才能做好这个事情,更好的拆分能力正是我们需要掌握的。如果只是因为看到其他人这么拆,我也这么拆,根据“二八原则”,或许“依样画葫芦”可以达到80%的契合度,但是往往那剩下的20%会是耗费我们80%精力的“大麻烦”。要知道,只有掌握了核心主旨,才能更快地找到最理想的高内聚、低耦合方案。 “分布式系统”是各种中间件吗? 又或许,听到分布式系统,你想到了某某MQ框架、某某RPC框架、某某DAL框架,把运用中间件和分布式系统错误地划上了等号。 这里需要搞清楚的是,中间件起到的是标准化的作用。中间件只是承载这些标准化想法的介质、工具,可以起到引导和约束的效果,以此起到大大降低系统复杂度和协作成本的作用。我们来分别看一下: MQ框架标准化了不同应用程序间非实时异步通信的方式。 RPC框架标准化了不同应用程序间实时通讯的方式。 DAL(Data Access Layer,数据访问层)框架标准化了应用程序和数据库之间通讯的方式。 所以,虽然分布式系统中会运用中间件,但分布式系统却不仅仅停留在用了什么中间件上。你需要清楚每一类中间件背后是对什么进行了标准化,它的目的是什么,带来了哪些副作用,等等。只有如此,你才能真正识别不同技术框架之间的区别,找到真正适合当前系统的技术框架。 那么标准是拍脑袋决定的吗?肯定不是,正如前面所说每一次标准化都是有目的的,需要产生价值。比如,大部分中间件都具备这样一个价值: 为了在软件系统的迭代过程中,避免将精力过多地花费在某个子功能下众多差异不大的选项中。 在现实中,这点更多时候出现在技术层面的中间件里,比如,数据库访问框架的作用是为了标准化操作不同数据库的差异,使得上层应用程序不用纠结于该怎么与mysql交互或者该怎么与SQL SERVER交互。因为与业务相比,技术层面“稳定”多了,所以做标准化更有价值,更能获得长期收益。但“稳定”是相对的,哪怕单纯在业务层面也存在相对稳定的部分。 比如,你可以想象一下“盛饭”的场景,在大多数情况下其中相对稳定的是什么,不稳定的是什么。想完之后看下面的示例。 从这个示例里我们发现,不稳定的部分都已经成为变量了,那么剩下的这个方法体起到的作用和前面提到的中间件是一样的,它标准化,标准化了盛饭的过程。所以识别相对稳定的部分是什么,如何把它们提炼出来,并且围绕这些点进行标准化,才是我们需要掌握的能力。而锻炼这个能力和需要这个能力的地方同样并不局限于分布式系统。 列举这些现象只是想说,我们在认知一个分布式系统的时候,内在胜于表象,掌握一个扎实的理论基本功更为重要。而且,这些训练场无处不在。 海市蜃楼般的“分布式系统” 我相信,自从进入移动时代以来,各种高大上的系统架构图越来越频繁地出现,你的眼前充斥着各种主流、非主流的眼花缭乱的技术框架。你不由得肃然起敬一番,心中呐喊着:“对,这就是我想去的地方,我想参与甚至实现一个这样牛逼的分布式系统,再也不想每天只是增删改查了。” 得不到的事物总是美好的,但往往我们也会过度地高估它的美好。与此类似,高大上的架构图背后呈现的系统的确也是一个成熟分布式系统的样貌,但我们要清楚一点:罗马不是一日建成的。 而且,“分布式”这个词只是意味着形态上是散列状的,而“一分为二”和“一分为N”本质上并没有区别。所以,很多小项目或者大型项目的初期所搭配的基础套餐“单程序+单数据库”,同样可以理解为分布式系统,其中遇到的问题很多同样也存在于成熟的分布式系统中。 想象一下,下面的场景是否在“单程序+单数据库”项目中出现过? log记录执行成功,但是数据库的数据没发生变化; 进程内的缓存数据更新了,但是数据库更新失败了。 这里我们停顿30秒,思考一下为什么会出现这些问题? 这里需要我们先思考一下“软件”是什么。软件的本质是一套代码,而代码只是一段文字,除了提供文字所表述的信息之外,本身无法“动”起来。但是,想让它“动”起来,使其能够完成一件我们指定的事情,前提是需要一个宿主来给予它生命。这个宿主就是计算机,它可以让代码变成一连串可执行的“动作”,然后通过数据这个“燃料”的触发,“动”起来。这个持续的活动过程,又被描述为一个运行中的“进程”。 那么除了我们开发的系统是软件,数据库也是软件,前者负责运算,后者负责存储运算后的结果(也可称为“状态”),分工协作。 所以,“单程序+单数据库”为什么也是分布式系统这个问题就很明白了。因为我们所编写的程序运行时所在的进程,和程序中使用到的数据库所在的进程,并不是同一个。也因此导致了,让这两个进程(系统)完成各自的部分,而后最终完成一件完整的事,变得不再像由单个个体独自完成这件事那么简单。这就如“两人三足”游戏一样,如何尽可能地让外部看起来像是一个整体、自然地前进。 所以,我们可以这么理解,涉及多个进程协作才能提供一个完整功能的系统就是“分布式系统”。 那么再回到上面举例的两个场景,我们在思考“单程序+单数据库”项目中遇到的这些问题背后的原因和解决它的过程时,与我们在一个成熟的分布式系统中的遭遇是一样的,例如数据一致性。当然,这只是分布式系统核心概念的冰山一角。 维基百科对“分布式系统”的宏观定义是这样的: 分布式系统是一种其组件位于不同的联网计算机上的系统,然后通过互相传递消息来进行通信和协调。为了达到共同的目标,这些组件会相互作用。 我们可以再以大小关系来解释它:把需要进行大量计算的工程数据分割成小块,由多台计算机分别计算,然后将结果统一合并得出数据结论的科学。这本质上就是“分治”。而“单程序+单数据库”组合的系统也包含了至少两个进程,“麻雀虽小五脏俱全”,这也是“分布式系统”。 总结 现在,我们搞清楚了,看待一个“分布式系统”的时候,内在胜于表象。以及,只要涉及多个进程协作才能提供一个完整功能的系统,就是“分布式系统”。 我相信还有很多其他景象出现你的脑海中,但这大多数都是分布式系统的本质产生的“化学反应”,进而形成的结果。如果停留在这些表象上,那么我们最终将无法寻找到“分布式系统”的本质,也就无法得到真正的“道”,更不会真正具备驾驭这些形态各异的“分布式系统”的能力。 所以,希望你在学习分布式系统的时候,不要因追逐“术”而丢了“道”。没有“道”只有“术”是空壳,最终会走火入魔,学得越多,会越混乱,到处都是矛盾和疑惑。 因此,我们这个系列除了教给你在具体场景下的最佳实践,还会和你讲解为什么这样做,以及该如何去权衡不同方案。不会过多的讲述具体的技术框架,大部分内容围绕理论展开,欲使每个人能够掌握好这些分布式中的基础理论和思路,修炼好自己的内功。 我将在后续的文章中,以一个项目的初期到成熟期作为路线图,带领你循序渐进地深入到分布式系统中,层层递进地去剥开它的本质,并且围绕这个本质去思考(是什么问题,有哪些方式可以解决,什么时候该用何种种方式等等),让你知其然且知其所以然,形成一套完整的知识体系,完成核心“骨架”的塑造。而在此之后,你自己在课外学习时,就可以去填充“血肉”部分,逐渐丰满自己。未来,大家的区别就在于胖一点和瘦一点,但只要能很好地完成工作,胖瘦又有何影响? 更多深度好文请关注微信公众号数据星河(bdg-store)

优秀的个人博客,低调大师

缓存架构之史上讲的明白的RabbitMQ可靠消息传输实战演练

一、背景介绍:消息可靠传递的重要性 比如:某个广告主(如:天猫)想在我们的平台(如:今日头条)投放广告,当通过我们的广告系统新建广告的时候,该消息在同步给redis缓存(es)的时候丢失了,而我们又没有发现,造成该广告无法正常显示出来,那这损失就打了,如果1天都没有该广告的投放记录,那就有可能是上百万的损失了,所以消息的可靠传输多我们的广告系统也是很重要的。 其实,生活中这样的场景很场景,再比如:交易系统、订单系统都必须保证消息的可靠传输,否则,损失是巨大的!!! 二、如何保证消息的可靠传递呢? 1、设置交换机、队列和消息都为持久化 **持久化:**保证在服务器重启的时候可以保持不丢失相关信息,重点解决服务器的异常崩溃而导致的消息丢失问题。但是,将所有的消息都设置为持久化,会严重影响RabbitMQ的性能,写入硬盘的速度比写入内存的速度慢的不只一点点。对于可靠性不是那么高的消息可以不采用持久化处理以提高整体的吞吐率,在选择是否要将消息持久化时,需要在可靠性和吞吐量之间做一个权衡。 处于某种应用场景,如:大流量的订单交易系统,为了不影响性能,我们可以不设置持久化,但是我们会定时扫描数据库中的未发送成功的消息,进行重试发送,实际应用场景,我们其实有很多解决方案,不要故步自封,换个角度多想想,只有经历多了,才能应用的更加得心应手。 1)交换机的持久化 @Bean DirectExchange advanceExchange() { return new DirectExchange(exchangeName); } 注释:查看源码,易知,默认是持久化的 2)队列的持久化 @Bean public Queue advanceQueue() { return new Queue(queueName); } 注释:查看源码,易知,默认是持久化的 3)消息的持久化 当我们使用RabbitTemplate调用了 convertAndSend(String exchange, String routingKey, final Object object) 方法。默认就是持久化模式 注意: 持久化的消息在到达队列时就被写入到磁盘,并且如果可以,持久化的消息也会在内存中保存一份备份,这样可以提高一定的性能,只有在内存吃紧的时候才会从内存中清楚。 非持久化的消息一般只保存在内存中,在内存吃紧的时候会被换入到磁盘中,以节省内存空间。 2、生产者消息确认机制 当消息发送出去之后,我们如何知道消息有没有正确到达exchange呢?如果在这个过程中,消息丢失了,我们根本不知道发生了什么,也不知道是什么原因导致消息发送失败了 为解决这个问题,主要有如下两种方案: 通过事务机制实现 通过生产者消息确认机制(publisher confirm)实现 但是使用事务机制实现会严重降低RabbitMQ的消息吞吐量,我们采用一种轻量级的方案——生产者消息确认机制 什么是消息确认机制? 简而言之,就是:生产者发送的消息一旦被投递到所有匹配的队列之后,就会发送一个确认消息给生产者,这就使得生产者知晓消息已经正确到达了目的地。 如果消息和队列是持久化存储的,那么确认消息会在消息写入磁盘之后发出。 再补充一个Mandatory参数:当Mandatory参数设为true时,如果目的不可达,会发送消息给生产者,生产者通过一个回调函数来获取该信息。 3、消费者消息确认机制 为了保证消息从队列可靠地到达消费者,RabbitMQ提供了消费者消息确认机制(message acknowledgement)。采用消息确认机制之后,消费者就有足够的时间来处理消息,不用担心处理消息过程中消费者进程挂掉后消息丢失的问题,因为RabbitMQ会一直等待并持有消息,直到消费者确认了该消息。 4、死信队列 DLX,Dead Letter Exchange 的缩写,又死信邮箱、死信交换机。DLX就是一个普通的交换机,和一般的交换机没有任何区别。 当消息在一个队列中变成死信(dead message)时,通过这个交换机将死信发送到死信队列中(指定好相关参数,rabbitmq会自动发送)。 什么是死信呢?什么样的消息会变成死信呢? 消息被拒绝(basic.reject或basic.nack)并且requeue=false. 消息TTL过期 队列达到最大长度(队列满了,无法再添加数据到mq中) 应用场景分析:在定义业务队列的时候,可以考虑指定一个死信交换机,并绑定一个死信队列,当消息变成死信时,该消息就会被发送到该死信队列上,这样就方便我们查看消息失败的原因了 **如何使用死信交换机呢? 定义业务(普通)队列的时候指定参数: x-dead-letter-exchange: 用来设置死信后发送的交换机 x-dead-letter-routing-key:用来设置死信的routingKey@Bean public Queue helloQueue() { //将普通队列绑定到私信交换机上 Map<String, Object> args = new HashMap<>(2); args.put(DEAD_LETTER_QUEUE_KEY, deadExchangeName); args.put(DEAD_LETTER_ROUTING_KEY, deadRoutingKey); Queue queue = new Queue(queueName, true, false, false, args); return queue; } 三、实战演练 项目代码下载地址:https://gitee.com/jikeh/JiKeHCN-RELEASE.git项目名:spring-boot-rabbitmq-reliability 1、开启生产者消息确认机制 # 开启发送确认 spring.rabbitmq.publisher-confirms=true # 开启发送失败退回 spring.rabbitmq.publisher-returns=true 2、开启消费者消息确认机制 # 开启ACK spring.rabbitmq.listener.simple.acknowledge-mode=manual 3、基本配置 @Configuration public class RabbitConfig { public final static String queueName = "hello_queue"; /** * 死信队列: */ public final static String deadQueueName = "dead_queue"; public final static String deadRoutingKey = "dead_routing_key"; public final static String deadExchangeName = "dead_exchange"; /** * 死信队列 交换机标识符 */ public static final String DEAD_LETTER_QUEUE_KEY = "x-dead-letter-exchange"; /** * 死信队列交换机绑定键标识符 */ public static final String DEAD_LETTER_ROUTING_KEY = "x-dead-letter-routing-key"; @Bean public Queue helloQueue() { //将普通队列绑定到私信交换机上 Map<String, Object> args = new HashMap<>(2); args.put(DEAD_LETTER_QUEUE_KEY, deadExchangeName); args.put(DEAD_LETTER_ROUTING_KEY, deadRoutingKey); Queue queue = new Queue(queueName, true, false, false, args); return queue; } /** * 死信队列: */ @Bean public Queue deadQueue() { Queue queue = new Queue(deadQueueName, true); return queue; } @Bean public DirectExchange deadExchange() { return new DirectExchange(deadExchangeName); } @Bean public Binding bindingDeadExchange(Queue deadQueue, DirectExchange deadExchange) { return BindingBuilder.bind(deadQueue).to(deadExchange).with(deadRoutingKey); } } 注释:hell_queue就配置了死信交换机、死信队列 4、生产者核心代码 @Component public class HelloSender implements RabbitTemplate.ConfirmCallback, RabbitTemplate.ReturnCallback{ @Autowired private RabbitTemplate rabbitTemplate; public void send(String exchange, String routingKey) { String context = "你好现在是 " + new Date(); System.out.println("send content = " + context); this.rabbitTemplate.setMandatory(true); this.rabbitTemplate.setConfirmCallback(this); this.rabbitTemplate.setReturnCallback(this); this.rabbitTemplate.convertAndSend(exchange, routingKey, context); } /** * 确认后回调: * @param correlationData * @param ack * @param cause */ @Override public void confirm(CorrelationData correlationData, boolean ack, String cause) { if (!ack) { System.out.println("send ack fail, cause = " + cause); } else { System.out.println("send ack success"); } } /** * 失败后return回调: * * @param message * @param replyCode * @param replyText * @param exchange * @param routingKey */ @Override public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) { System.out.println("send fail return-message = " + new String(message.getBody()) + ", replyCode: " + replyCode + ", replyText: " + replyText + ", exchange: " + exchange + ", routingKey: " + routingKey); } } 5、消费者核心代码 @Component @RabbitListener(queues = RabbitConfig.queueName) public class HelloReceiver { @RabbitHandler public void process(String hello, Channel channel, Message message) throws IOException { try { Thread.sleep(2000); System.out.println("睡眠2s"); } catch (InterruptedException e) { e.printStackTrace(); } try { //告诉服务器收到这条消息 已经被我消费了 可以在队列删掉;否则消息服务器以为这条消息没处理掉 后续还会在发 channel.basicAck(message.getMessageProperties().getDeliveryTag(),false); System.out.println("receiver success = " + hello); } catch (Exception e) { e.printStackTrace(); //丢弃这条消息 channel.basicNack(message.getMessageProperties().getDeliveryTag(), false,false); System.out.println("receiver fail"); } } } 6、测试生产者消息确认功能:分为4种场景来测试 //1、exchange, queue 都正确, confirm被回调, ack=true @RequestMapping("/send1") @ResponseBody public String send1() { helloSender.send(null, RabbitConfig.queueName); return "success"; } //2、exchange 错误, queue 正确, confirm被回调, ack=false @RequestMapping("/send2") @ResponseBody public String send2() { helloSender.send("fail-exchange", RabbitConfig.queueName); return "success"; } //3、exchange 正确, queue 错误, confirm被回调, ack=true; return被回调 replyText:NO_ROUTE @RequestMapping("/send3") @ResponseBody public String send3() { helloSender.send(null, "fail-queue"); return "success"; } //4、exchange 错误, queue 错误, confirm被回调, ack=false @RequestMapping("/send4") @ResponseBody public String send4() { helloSender.send("fail-exchange", "fail-queue"); return "success"; } 7、测试消费者消息确认功能 1)当添加这行代码的时候:channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);测试结果:消息被正常消费,消息从队列中删除 2)当注释掉这行代码的时候:channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);测试结果:消息会被重复消费,一直保留在队列当中 8、测试死信队列 当执行这行代码的时候:channel.basicNack(message.getMessageProperties().getDeliveryTag(), false,false);消息会被加入到死信队列中: 四、拓展 除了我们上面讲的基本可靠性保证外,其实还有很多性能优化方案、可靠性保证方案:集群监控、流控、镜像队列、HAProxy+Keeplived高可靠负载均衡 我们后续会继续分享上述内容,欢迎持续关注…… 下节课,我们将会将该功能应用到缓存架构上了 本文来自云栖社区合作伙伴“开源中国” 本文作者:王练 原文链接

优秀的个人博客,低调大师

【Java入门提高篇】Day25 史上详细的HashMap红黑树解析

当当当当当当当,好久不见,最近又是换工作,又是换房子,忙的不可开交,断更了一小段时间,最重要的一篇迟迟出不来,每次都犹抱琵琶半遮面,想要把它用通俗易懂的方式进行说明,确实有一定的难度,可愁煞我也,但自己挖的坑,哭着也要把它补上。请允许我当一回标题党。 好了,言归正传,本篇主要内容便是介绍HashMap的男二号——TreeNode(男一号还是给Node吧,毕竟是TreeNode的爷爷,而且普通节点一般来说也比TreeNode要多),本篇主要从以下几个方面介绍: 1.红黑树介绍 2.TreeNode结构 3.树化的过程 4.红黑树的左旋和右旋 5.TreeNode的左旋和右旋 6.红黑树的插入 7.TreeNode的插入 8.红黑树的删除 9.TreeNode的删除 讲解红黑树的部分算是理论部分,讲解TreeNode的部分则是代码实践部分,配合服用效果更加。 保守估计,仔细食用本篇大约需要半小时,请各位细细品尝。 红黑树介绍 什么是红黑树?嗯,首先,它是一颗树,所谓的树,便是长的像这样的东西 不像树?emmmm,你把它想象成一颗倒过来的树就好了,A~H都是树的节点,每个节点有零个或者多个子节点,或者说多个孩子,但除了根节点以外,每个节点都只有一个父节点,也称只有一个父亲(老王嘿嘿一笑)。最上面的A是根节点,最下面的D、H、F、G是叶子节点。每一个非根节点有且只有一个父节点;树是具有一层一层的层次结构,这里A位于第一层,B、C位于第二层,依次类推。将左边的B节点部分(包括BDEH)拿出来,则又是一颗树,称为树的子树。 好了,知道树是什么东西了,那么红黑树是什么样的呢? 红黑树,本质上来说是一颗二叉搜索树。嗯,还是先说说这个二叉搜索树吧。二叉代表它的节点最多有两个子节点,而且左右有顺序,不能颠倒,分别叫左孩子和右孩子,这两个节点互为兄弟节点,嗯,其实叫法根现实里的叫法差不多,以下图为例,4、9互为兄弟,7是他们的父亲,9是2的叔叔,8是2的堂兄弟,很简单吧。说完了称谓,再来说说用途,既然叫做搜索树表示它的用途是为了更快的搜索和查找而设计的,所以这棵树本身满足一定的排序规则,即树中的任何节点的值大于它的左孩子,且小于它的右孩子。任意节点的左、右子树也分别为二叉查找树。嗯,结合下图意会一下: 而红黑树,就跟它的名字一样,又红又黑,红黑并进,理实交融,节点是非红即黑的,看起来就像这样: 红黑树的主要特性: (1)每个节点要么是黑色,要么是红色。(节点非黑即红) (2)根节点是黑色。 (3)每个叶子节点(NIL)是黑色。 (4)如果一个节点是红色的,则它的子节点必须是黑色的。(也就是说父子节点不能同时为红色) (5)从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点。(这一点是平衡的关键) 说简单也简单,其实就是一颗比较平衡的又红又黑的二叉树嘛。 TreeNode结构 既然我们已经知道红黑树长什么样了,那么我们再来看看HashMap中的TreeNode代码里是如何表示的: /** * 用于Tree bins 的Entry。 扩展LinkedHashMap.Entry(进而扩展Node),因此可以用作常规节点或链接节点的扩展。 */ static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> { TreeNode<K,V> parent; // 红黑树父节点 TreeNode<K,V> left; TreeNode<K,V> right; TreeNode<K,V> prev; // 删除后需要取消链接 boolean red; TreeNode(int hash, K key, V val, Node<K,V> next) { super(hash, key, val, next); } //省略后续代码 TreeNode继承自LinkedHashMap中的内部类——LinkedHashMap.Entry,而这个内部类又继承自Node,所以算是Node的孙子辈了。我们再来看看它的几个属性,parent用来指向它的父节点,left指向左孩子,right指向右孩子,prev则指向前一个节点(原链表中的前一个节点),注意,这些字段跟Entry,Node中的字段一样,是使用默认访问权限的,所以子类可以直接使用父类的属性。 树化的过程 在前几篇中已经有所介绍,当HashMap桶中的元素个数超过一定数量时,就会树化,也就是将链表转化为红黑树的结构。 public V put(K key, V value) { return putVal(hash(key), key, value, false, true); } final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) { ...省略部分代码... else { for (int binCount = 0; ; ++binCount) { if ((e = p.next) == null) { p.next = newNode(hash, key, value, null); //当桶中元素个数超过阈值(8)时就进行树化 if (binCount >= TREEIFY_THRESHOLD - 1) treeifyBin(tab, hash); break; } ...省略部分代码... } final void treeifyBin(Node<K,V>[] tab, int hash) { int n, index; Node<K,V> e; if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY) resize(); else if ((e = tab[index = (n - 1) & hash]) != null) { TreeNode<K,V> hd = null, tl = null; do { //将节点替换为TreeNode TreeNode<K,V> p = replacementTreeNode(e, null); if (tl == null) //hd指向头结点 hd = p; else { //这里其实是将单链表转化成了双向链表,tl是p的前驱,每次循环更新指向双链表的最后一个元素,用来和p相连,p是当前节点 p.prev = tl; tl.next = p; } tl = p; } while ((e = e.next) != null); if ((tab[index] = hd) != null) //将链表进行树化 hd.treeify(tab); } } 从代码中可以看到,在treeifyBin函数中,先将所有节点替换为TreeNode,然后再将单链表转为双链表,方便之后的遍历和移动操作。而最终的操作,实际上是调用TreeNode的方法treeify进行的。 final void treeify(Node<K,V>[] tab) { //树的根节点 TreeNode<K,V> root = null; //x是当前节点,next是后继 for (TreeNode<K,V> x = this, next; x != null; x = next) { next = (TreeNode<K,V>)x.next; x.left = x.right = null; //如果根节点为null,把当前节点设置为根节点 if (root == null) { x.parent = null; x.red = false; root = x; } else { K k = x.key; int h = x.hash; Class<?> kc = null; //这里循环遍历,进行二叉搜索树的插入 for (TreeNode<K,V> p = root;;) { //p指向遍历中的当前节点,x为待插入节点,k是x的key,h是x的hash值,ph是p的hash值,dir用来指示x节点与p的比较,-1表示比p小,1表示比p大,不存在相等情况,因为HashMap中是不存在两个key完全一致的情况。 int dir, ph; K pk = p.key; if ((ph = p.hash) > h) dir = -1; else if (ph < h) dir = 1; //如果hash值相等,那么判断k是否实现了comparable接口,如果实现了comparable接口就使用compareTo进行进行比较,如果仍旧相等或者没有实现comparable接口,则在tieBreakOrder中比较 else if ((kc == null && (kc = comparableClassFor(k)) == null) || (dir = compareComparables(kc, k, pk)) == 0) dir = tieBreakOrder(k, pk); TreeNode<K,V> xp = p; if ((p = (dir <= 0) ? p.left : p.right) == null) { x.parent = xp; if (dir <= 0) xp.left = x; else xp.right = x; //进行插入平衡处理 root = balanceInsertion(root, x); break; } } } } //确保给定节点是桶中的第一个元素 moveRootToFront(tab, root); } //这里不是为了整体排序,而是为了在插入中保持一致的顺序 static int tieBreakOrder(Object a, Object b) { int d; //用两者的类名进行比较,如果相同则使用对象默认的hashcode进行比较 if (a == null || b == null || (d = a.getClass().getName(). compareTo(b.getClass().getName())) == 0) d = (System.identityHashCode(a) <= System.identityHashCode(b) ? -1 : 1); return d; } 这里的逻辑其实不复杂,仅仅是循环遍历当前树,然后找到可以该节点可以插入的位置,依次和遍历节点比较,比它大则跟其右孩子比较,小则与其左孩子比较,依次遍历,直到找到左孩子或者右孩子为null的位置进行插入。 真正复杂一点的地方在于balanceInsertion函数,这个函数中,将红黑树进行插入平衡处理,保证插入节点后仍保持红黑树的性质。这个函数稍后在TreeNode的插入中进行介绍,这里先看看moveRootToFront,这个函数是将root节点移动到桶中的第一个元素,也就是链表的首节点,这样做是因为在判断桶中元素类型的时候会对链表进行遍历,将根节点移动到链表前端可以确保类型判断时不会出现错误。 /** * 把给定节点设为桶中的第一个元素 */ static <K,V> void moveRootToFront(Node<K,V>[] tab, TreeNode<K,V> root) { int n; if (root != null && tab != null && (n = tab.length) > 0) { int index = (n - 1) & root.hash; //first指向链表第一个节点 TreeNode<K,V> first = (TreeNode<K,V>)tab[index]; if (root != first) { //如果root不是第一个节点,则将root放到第一个首节点位置 Node<K,V> rn; tab[index] = root; TreeNode<K,V> rp = root.prev; if ((rn = root.next) != null) ((TreeNode<K,V>)rn).prev = rp; if (rp != null) rp.next = rn; if (first != null) first.prev = root; root.next = first; root.prev = null; } //这里是防御性编程,校验更改后的结构是否满足红黑树和双链表的特性 //因为HashMap并没有做并发安全处理,可能在并发场景中意外破坏了结构 assert checkInvariants(root); } } 红黑树的左旋和右旋 左旋和右旋,顾名思义嘛,就是将节点以某个节点为中心向左或者向右进行旋转操作以保持二叉树的平衡,让我们看图说话: 图画的有点大。将就着看一下吧,左旋相当于以要旋转的节点为中心,将子树整体向左旋转,该节点变成子树的根节点,原来的父节点A变成了左孩子,如果右孩子C有左孩子D,则将其变为A的右孩子。说起来好像有点绕,可以联系图进行形象化的理解,当节点A向左旋转之后,C的左孩子D可以理解为因为重力作用掉到A的右孩子位置,嗯,就是这样。右旋也是类似理解即可。 TreeNode的左旋和右旋 了解了左旋和右旋,让我们看看代码里是怎样实现的: /** * 左旋 */ static <K,V> TreeNode<K,V> rotateLeft(TreeNode<K,V> root, TreeNode<K,V> p) { //这里的p即上图的A节点,r指向右孩子即C,rl指向右孩子的左孩子即D,pp为p的父节点 TreeNode<K,V> r, pp, rl; if (p != null && (r = p.right) != null) { if ((rl = p.right = r.left) != null) rl.parent = p; //将p的父节点的孩子节点指向r if ((pp = r.parent = p.parent) == null) (root = r).red = false; else if (pp.left == p) pp.left = r; else pp.right = r; //将p置为r的左节点 r.left = p; p.parent = r; } return root; } /** * 右旋 */ static <K,V> TreeNode<K,V> rotateRight(TreeNode<K,V> root, TreeNode<K,V> p) { //这里的p即上图的A节点,l指向左孩子即C,lr指向左孩子的右孩子即E,pp为p的父节点 TreeNode<K,V> l, pp, lr; if (p != null && (l = p.left) != null) { if ((lr = p.left = l.right) != null) lr.parent = p; if ((pp = l.parent = p.parent) == null) (root = l).red = false; else if (pp.right == p) pp.right = l; else pp.left = l; l.right = p; p.parent = l; } return root; } 其实,也很简单嘛。23333 红黑树的插入 现在来看看一个比较麻烦一点的操作,红黑树的插入,首先找到这个节点要插入的位置,即一层一层比较,大的放右边,小的放左边,直到找到为null的节点放入即可,但是如何在插入的过程保持红黑树的特性呢,想想好像比较头疼,但是再仔细想想其实就会发现,其实只有这么几种情况: 1.插入的为根节点,则直接把颜色改成黑色即可。 2.插入的节点的父节点是黑色节点,则不需要调整,因为插入的节点会初始化为红色节点,红色节点是不会影响树的平衡的。 3.插入的节点的祖父节点为null,即插入的节点的父节点是根节点,直接插入即可(因为根节点肯定是黑色)。 4.插入的节点父节点和祖父节点都存在,并且其父节点是祖父节点的左节点。这种情况稍微麻烦一点,又分两种子情况: i.插入节点的叔叔节点是红色,则将父亲节点和叔叔节点都改成黑色,然后祖父节点改成红色即可。 ii.插入节点的叔叔节点是黑色或不存在: a.若插入节点是其父节点的右孩子,则将其父节点左旋, b.若为左孩子,则将其父节点变成黑色节点,将其祖父节点变成红色节点,然后将其祖父节点右旋。 5.插入的节点父节点和祖父节点都存在,并且其父节点是祖父节点的右节点。这种情况跟上面是类似的,分两种子情况: i.插入节点的叔叔节点是红色,则将父亲节点和叔叔节点都改成黑色,然后祖父节点改成红色即可。 ii.插入节点的叔叔节点是黑色或不存在: a.若插入节点是其父节点的左孩子,则将其父节点右旋 b.若为右孩子,则将其父节点变成黑色节点,将其祖父节点变成红色节点,然后将其祖父节点左旋。 然后重复进行上述操作,直到变成1或2情况时则结束变换。说半天,可能还是云里雾里,一图胜千言,让我们从无到有构建一颗红黑树,假设插入的顺序为:10,5,9,3,6,7,19,32,24,17(数字是我拍脑袋瞎想的。) 先来插个10,为情景1,直接改成黑色即可,再插入5,为情景2,比10小,放到10的左孩子位置,插入9,比10小,但是比5大,放到5的右孩子位置,此时,为情景4iia,左旋后变成了情景4iib,变色右旋即可完成转化。插入3后为情景4i,将父节点和叔叔节点同时变色即可,插入6不需要调整,插入7后为情景5i,变色即可。插入19不需要调整,插入32,变成了5iib,左旋变色即可,插入24,变成5iia,右旋后变成5i,变色即可,最后插入17,完美。 看图说话是不是就简单明了了,看在我画图这么辛苦的份上,点个关注给个赞可好(滑稽)。 TreeNode的插入 了解了红黑树的删除之后,我们再来看下TreeNode中是怎样用代码实现的: static <K,V> TreeNode<K,V> balanceInsertion(TreeNode<K,V> root, TreeNode<K,V> x) { x.red = true; for (TreeNode<K,V> xp, xpp, xppl, xppr;;) { //情景1:父节点为null if ((xp = x.parent) == null) { x.red = false; return x; } //情景2,3:父节点是黑色节点或者祖父节点为null else if (!xp.red || (xpp = xp.parent) == null) return root; //情景4:插入的节点父节点和祖父节点都存在,并且其父节点是祖父节点的左节点 if (xp == (xppl = xpp.left)) { //情景4i:插入节点的叔叔节点是红色 if ((xppr = xpp.right) != null && xppr.red) { xppr.red = false; xp.red = false; xpp.red = true; x = xpp; } //情景4ii:插入节点的叔叔节点是黑色或不存在 else { //情景4iia:插入节点是其父节点的右孩子 if (x == xp.right) { root = rotateLeft(root, x = xp); xpp = (xp = x.parent) == null ? null : xp.parent; } //情景4iib:插入节点是其父节点的左孩子 if (xp != null) { xp.red = false; if (xpp != null) { xpp.red = true; root = rotateRight(root, xpp); } } } } //情景5:插入的节点父节点和祖父节点都存在,并且其父节点是祖父节点的右节点 else { //情景5i:插入节点的叔叔节点是红色 if (xppl != null && xppl.red) { xppl.red = false; xp.red = false; xpp.red = true; x = xpp; } //情景5ii:插入节点的叔叔节点是黑色或不存在 else {· //情景5iia:插入节点是其父节点的左孩子 if (x == xp.left) { root = rotateRight(root, x = xp); xpp = (xp = x.parent) == null ? null : xp.parent; } //情景5iib:插入节点是其父节点的右孩子 if (xp != null) { xp.red = false; if (xpp != null) { xpp.red = true; root = rotateLeft(root, xpp); } } } } } } 其实就是一毛一样的,对号入座即可。 红黑树的删除 讲完插入,接下来我们来说说删除,删除的话,比插入还要复杂一点,请各位看官先深呼吸,做好阅读准备。 之前已经说过,红黑树是一颗特殊的二叉搜索树,所以进行删除操作时,其实是先进行二叉搜索树的删除,然后再进行调整。所以,其实这里分为两部分内容:1.二叉搜索树的删除,2.红黑树的删除调整。 二叉搜索树的删除主要有这么几种情景: 情景1:待删除的节点无左右孩子。 情景2:待删除的节点只有左孩子或者右孩子。 情景3:待删除的节点既有左孩子又有右孩子。 对于情景1,直接删除即可,情景2,则直接把该节点的父节点指向它的左孩子或者右孩子即可,情景3稍微复杂一点,需要先找到其右子树的最左孩子(或者左子树的最右孩子),即左(右)子树中序遍历时的第一个节点,然后将其与待删除的节点互换,最后再删除该节点(如果有右子树,则右子树上位)。总之,就是先找到它的替代者,找到之后替换这个要删除的节点,然后再把这个节点真正删除掉。 其实二叉搜索树的删除总体来说还是比较简单的,删除完之后,如果替代者是红色节点,则不需要调整,如果是黑色节点,则会导致左子树和右子树路径中黑色节点数量不一致,需要进行红黑树的调整,跟上面一样,替代节点为其父节点的左孩子与右孩子的情况类似,所以这里只说其为左孩子的情景(PS:上一步的寻找替换节点使用的是右子树的最左节点,所以该节点如果有孩子,只能是右孩子): 情景1:只有右孩子且为红色,直接用右孩子替换该节点然后变成黑色即可。 (D代表替代节点,即要被删除的节点,之前在经过二叉搜索树的删除后,D节点其实已经被删除了,这里为了方便理解这个变化过程,所以把这个节点也画出来了,所以当前的初始状态是待删除节点与其替换节点互换位置与颜色之后的状态) 情景2:只有右孩子且为黑色,那么删除该节点会导致父节点的左子树路径上黑色节点减一,此时只能去借助右子树,从右子树中借一个红色节点过来即可,具体取决于右子树的情况,这里又分成两种: i.兄弟节点是红色,则此时父节点是黑色,且兄弟节点肯定有两个孩子,且兄弟节点的左右子树路径上均有两个黑色节点,此时只需将兄弟节点与父节点颜色互换,然后将父节点左旋,左旋后,兄弟节点的左子树SL挂到了父节点p的右孩子位置,这时会导致p的右子树路径上的黑色节点比左子树多一,此时再SL置为红色即可。 ii.兄弟节点是黑色,那么就只能打它孩子的主意了,这里主要关注远侄子(兄弟节点的右孩子,即SR)的颜色情况,这里分成两种情况: a.远侄子SR是黑色,近侄子任意(白色代表颜色可为任意颜色),则先将S转为红色,然后右旋,再将SL换成P节点颜色,P涂成黑色,S也涂成黑色,再进行左旋即可。其实简单说就是SL上位,替换父节点位置。 b.远侄子SR为红色,近侄子任意(该子树路径中有且仅有一个黑色节点),则先将兄弟节点与父节点颜色互换,将SR涂成黑色,再将父节点左旋即可。 emmmm...好像也不是很麻烦嘛(逃)。 TreeNode的删除节点 TreeNode删除节点其实也是两步走,先进行二叉搜索树的删除,然后再进行红黑树的调整,跟之前的情况分析是一致的。 final void removeTreeNode(HashMap<K,V> map, Node<K,V>[] tab, boolean movable) { ...... //p是待删除节点,replacement用于后续的红黑树调整,指向的是p或者p的继承者。 //如果p是叶子节点,p==replacement,否则replacement为p的右子树中最左节点 if (replacement != p) { //若p不是叶子节点,则让replacement的父节点指向p的父节点 TreeNode<K,V> pp = replacement.parent = p.parent; if (pp == null) root = replacement; else if (p == pp.left) pp.left = replacement; else pp.right = replacement; p.left = p.right = p.parent = null; } //若待删除的节点p时红色的,则树平衡未被破坏,无需进行调整。 //否则删除节点后需要进行调整 TreeNode<K,V> r = p.red ? root : balanceDeletion(root, replacement); //p为叶子节点,则直接将p从树中清除 if (replacement == p) { // detach TreeNode<K,V> pp = p.parent; p.parent = null; if (pp != null) { if (p == pp.left) pp.left = null; else if (p == pp.right) pp.right = null; } } } 麻烦的地方就在删除节点后的调整了,所有逻辑都在balanceDeletion函数里,两个参数分别表示根节点和删除节点的继承者,来看看它的具体实现: static <K,V> TreeNode<K,V> balanceDeletion(TreeNode<K,V> root, TreeNode<K,V> x) { for (TreeNode<K,V> xp, xpl, xpr;;) { //x为空或x为根节点,直接返回 if (x == null || x == root) return root; //x为根节点,染成黑色,直接返回(因为调整过后,root并不一定指向删除操作过后的根节点,如果之前删除的是root节点,则x将成为新的根节点) else if ((xp = x.parent) == null) { x.red = false; return x; } //如果x为红色,则无需调整,返回 else if (x.red) { x.red = false; return root; } //x为其父节点的左孩子 else if ((xpl = xp.left) == x) { //如果它有红色的兄弟节点xpr,那么它的父亲节点xp一定是黑色节点 if ((xpr = xp.right) != null && xpr.red) { xpr.red = false; xp.red = true; //对父节点xp做左旋转 root = rotateLeft(root, xp); //重新将xp指向x的父节点,xpr指向xp新的右孩子 xpr = (xp = x.parent) == null ? null : xp.right; } //如果xpr为空,则向上继续调整,将x的父节点xp作为新的x继续循环 if (xpr == null) x = xp; else { //sl和sr分别为其近侄子和远侄子 TreeNode<K,V> sl = xpr.left, sr = xpr.right; if ((sr == null || !sr.red) && (sl == null || !sl.red)) { xpr.red = true; //若sl和sr都为黑色或者不存在,即xpr没有红色孩子,则将xpr染红 x = xp; //本轮结束,继续向上循环 } else { //否则的话,就需要进一步调整 if (sr == null || !sr.red) { if (sl != null) //若左孩子为红,右孩子不存在或为黑 sl.red = false; //左孩子染黑 xpr.red = true; //将xpr染红 root = rotateRight(root, xpr); //右旋 xpr = (xp = x.parent) == null ? null : xp.right; //右旋后,xpr指向xp的新右孩子,即上一步中的sl } if (xpr != null) { xpr.red = (xp == null) ? false : xp.red; //xpr染成跟父节点一致的颜色,为后面父节点xp的左旋做准备 if ((sr = xpr.right) != null) sr.red = false; //xpr新的右孩子染黑,防止出现两个红色相连 } if (xp != null) { xp.red = false; //将xp染黑,并对其左旋,这样就能保证被删除的X所在的路径又多了一个黑色节点,从而达到恢复平衡的目的 root = rotateLeft(root, xp); } //到此调整已经完毕,进入下一次循环后将直接退出 x = root; } } } //x为其父节点的右孩子,跟上面类似 else { // symmetric if (xpl != null && xpl.red) { xpl.red = false; xp.red = true; root = rotateRight(root, xp); xpl = (xp = x.parent) == null ? null : xp.left; } if (xpl == null) x = xp; else { TreeNode<K,V> sl = xpl.left, sr = xpl.right; if ((sl == null || !sl.red) && (sr == null || !sr.red)) { xpl.red = true; x = xp; } else { if (sl == null || !sl.red) { if (sr != null) sr.red = false; xpl.red = true; root = rotateLeft(root, xpl); xpl = (xp = x.parent) == null ? null : xp.left; } if (xpl != null) { xpl.red = (xp == null) ? false : xp.red; if ((sl = xpl.left) != null) sl.red = false; } if (xp != null) { xp.red = false; root = rotateRight(root, xp); } x = root; } } } } } 呼。。。终于。。酝酿了好多天的一篇文章总算是写完了,为了尽量确认转换的准确性,找了很多资料进行参考,过程中花了不少时间,曾多次准备放弃。。。不过总算是没有死在娘胎里,也算是完成了一桩心事,开心。. 之后还会继续更新,欢迎大家继续关注。也欢迎大家前来打脸真正重要的东西,用眼睛是看不见的。

优秀的个人博客,低调大师

一份中肯的Java学习路线+资源分享(拒绝傻逼式分享)

这是一篇针对Java初学者,或者说在Java学习路线上出了一些问题(不知道该学什么、不知道整体的学习路线是什么样的) 第一步:Java基础(一个月左右) 推荐视频: 下面的是黑马内部视频,我比较推荐的资料(因为提供的配套资料以及软件都很齐全,可以降低你的学习难度。大家想必也知道,真正学习一个东西之间真正让我们头疼的是各种环境的配置、搭建已经软件、jar包等东西的下载) 因为某些原因,不方便在这里直接发送百度链接,关注我的微信公众号“Java面试通关手册”回复“资源分享第一波”即可领取。 除此之外,你也可以去慕课网上面查找自己没有掌握好的知识点(看之前,看一下课程评价和评分,有些老师的讲课不敢恭维,)。你也可以查找相关博客来掌握某些知识点,不过前期还是推荐看视频 推荐书籍 《Head First Java.第二版》可以说是我的Java启蒙书籍了,特别适合新手读当然也适合我们用来温故Java知识点。《Java核心技术卷1+卷2》很棒的两本书,建议有点Java基础之后再读,介绍的还是比较深入的,非常推荐。 推荐文字资源 Java 教程(菜鸟教程) 多线程系列学习与面试 Java IO与NIO学习与面试 第二步:J2EE基础(一个月左右) 从HTML->CSS->JS->BootStrap->Jquery前端学习再到JSP的学习。还是推荐黑马视频,理由就不多提了。因为某些原因,不方便在这里直接发送百度链接,关注我的微信公众号“Java面试通关手册”回复“资源分享第一波”即可领取。还是老样子,你也可以去慕课网上面查找自己没有掌握好的知识点(看之前,看一下课程评价和评分,有些老师的讲课不敢恭维,)。你也可以查找相关博客来掌握某些知识点,不过前期还是推荐看视频。 推荐书籍 《Java Web整合开发王者归来》很适合打Javaweb知识基础的书籍。 推荐文字资源 JSP 教程(菜鸟教程) 第三步:Liunx和Redis的学习以及简单商城项目(10天左右) 大概需要2天就够了,对于Linux只需掌握Java程序员经常使用的一些Linux命令即可,因为后面我们可能会在Linux上搭建nginx环境或者Redis环境等等。 还是推荐黑马视频,理由就不多提了。因为某些原因,不方便在这里直接发送百度链接,关注我的微信公众号“Java面试通关手册”回复“资源分享第一波”即可领取。 第四步:企业主流框架+实战项目(50天左右) 包括hibernate、struts2、Spring(重点!重点!重点!,后面如果要学SpringBoot话,这个也是基础)、Maven的使用、Mybatis框架、SpringMVC框架、lucence&Solr等等。 还是推荐黑马视频,理由就不多提了。因为某些原因,不方便在这里直接发送百度链接,关注我的微信公众号“Java面试通关手册”回复“资源分享第一波”即可领取。 推荐书籍 《Spring MVC+MYBatis企业应用实战》学习SSM比较新的一本书了,书中Spring版本是4.0以上,所以当做工具书来读也很不错。 《Spring in action 》不建议当做入门书籍读,入门的话可以找点国人的书或者视频看。这本定位就相当于是关于Spring的新华字典,只有一些基本概念的介绍和示例,涵盖了Spring的各个方面,但都不够深入。就像作者在最后一页写的那样:“学习Spring,这才刚刚开始”。 《深入分析Java Web技术内幕》感觉还行,涉及的东西也蛮多,推荐阅读,建议学完再读。 《大型网站技术架构:核心原理与案例分析+李智慧》 这本书我读过,基本不需要你有什么基础啊~读起来特别轻松,但是却可以学到很多东西,非常推荐了。另外我写过这本书的思维导图,关注我的微信公众号:“Java面试通关手册”回复“大型网站技术架构”即可领取思维导图。 第五步:这只是刚刚开始 如果你去的是非常普通的小公司的,这些当然足够了。但是如果你想去的是BAT、京东、今日头条这样的大公司的话,数据结构、算法、JVM、计算机网络这些东西都对你的面试太重要了。为此我做了一个开源Java学习指南,上面涵盖了这些东西。 Java面试通关手册(Java学习指南):https://github.com/Snailclimb/Java_Guide(欢迎大家Star以及提出你们宝贵的建议) 另外就是现在微服务非常非常火,建议大家有时间可以看看SpringBoot以及SpringCloud这些“新东西”,有Spring基础学起来还算快的。SpringBoot的资源关注我的公众号:“Java面试通关手册”回复“springboot”即可免费领取。 最后 一些资源: 史诗级Java/JavaWeb学习资源免费分享(包括黑马内部视频+相关配套学习资料+Java Spring 技术栈构建前后台团购网站+Java SSM开发大众点评后端) 阿里分布式开源框架DUBBO 入门+ 进阶+ 项目实战视频教程 Java从零到企业级电商项目实战 Java企业级电商项目架构演进之路 Tomcat集群与Redis分布式 实战 :玩转算法面试 从真题到思维全面提升算法思维 Spring Boot企业级博客系统实战视频教程 Java Spring boot 企业微信点餐系统 可能是是最全的Springboot基础视频分享,告别无视频可学 上面的一些书籍,我已经整理在了网盘,关注我的公众号:“Java面试通关手册”回复“Java书籍推荐”即可免费领取。 然后大家还有什么问题的话,可以在我的微信公众号后台(Java面试通关手册)给我说或者加我微信:bwcx9393,我会根据自己的学习经验给了说一下自己的看法。最后,祝大家都能越走越远。

优秀的个人博客,低调大师

读书节该买的书,我都帮你们挑出来了

点击关注异步图书,置顶公众号 每天与你分享 IT好书 技术干货 职场知识 ​ ​过完漫长的冬天,送走了倒春寒,转眼4月也即将过半,我们有那么多的节日要过,对爱读书的真爱粉儿而言,读书节这个大日子,不放点福利哪行? 就在昨天异步社区掌柜的发布异步社区3.X版本,社区做了近三年最大的一次升级改版, 还没看过的读者请看这篇《异步社区进入3.X时代!这些新玩法你一定要知道》,其中不仅仅优化了UI界面、写作环境,最最关键的重点就是:异步社区实现了多介质内容服务,不仅可以购买纸质书还可以购买电子书(e读版电子书和推送版电子书)、视频课程;同时不管你在哪里购买了异步图书,只要扫描印在图书上的二维码,将图书加入“我的书架”,就能够随时接收到这本书相关的信息推送和服务,还会有优惠购买本书电子书的专享权益。 话说到这儿,该说说福利啦!异步社区全场满99减20元/199减50元。同时来一组近期值得买的书单,如果你还没入手,这次机会不可错过。 编程新书 纸书/电子书同步 ​ ​《Python神经网络编程》 [英]塔里克·拉希德(Tariq Rashid)著 点击封面购买纸书 当前,深度学习和人工智能的发展和应用给人们留下了深刻的印象。神经网络是深度学习和人工智能的关键元素,然而,真正了解神经网络工作机制的人少之又少。本书用轻松的笔触,一步一步揭示了神经网络的数学思想,并介绍如何使用Python 3.5编程语言开发神经网络。 本书将带领您进行一场妙趣横生却又有条不紊的旅行——从一个非常简单的想法开始,逐步理解神经网络的工作机制。您无需任何超出中学范围的数学知识,并且本书还给出易于理解的微积分简介。本书的目标是让尽可能多的普通读者理解神经网络。读者将学习使用Python开发自己的神经网络,训练它识别手写数字,甚至可以与专业的神经网络相媲美。 本书适合想要了解深度学习、人工智能和神经网络的读者阅读,尤其适合想要通过Python编程进行神经网络开发的读者参考。 ​ ​《“笨办法”学C语言》 [美] 泽德 A. 肖(Zed A. Shaw) 著 点击封面购买纸书 本书会随书附赠5个多小时充满激情的视频,这是一套完整的C语言视频课程! 作者Zed A.Shaw为急于自我提高编程技能(不限语言)的C语言初学者构建了一套完美的课程,只要跟着学,你就会像迄今为止Zed教过的数百万程序员一样获得成功!只要你能自律、投入和坚持!本书内容十分浅显易读,只要花2天到1周就可以读完,读完后既可以获得几千行代码的C编程经验。本书会让你的每一分钟投入都有回报。你很快就能学会世界上最强大的编程语言之一,成为一名C程序员。 在本书中,你将通过完成52个精心设计的习题来学会C语言。阅读书里的习题,看作者提供的视频,照着录入代码(不要复制和粘贴!),修正自己的错误,观察程序的运行。在这个过程中,你将会了解好的现代C代码长什么样子,如何有效地思考代码,如何更加有效地找出和修正错误。最重要地是,你将掌握严密的防御性编程技术,不管你使用什么编程语言,利用这些技术你都可以创建避免缺陷并抵御恶意行为的软件。本书通过实用的项目,让你学以致用,从而对自己新学会的技能更有信心。Zed将教会你编写出色的C代码所需具备的诸多关键技能。​ ​《Git高手之路》 Jakub Narębski著 点击封面购买纸书 本书面向所有的Git用户,全面细致地向读者介绍有关Git的各项实用技巧,充分发掘它的潜力,更好地实现项目版本管理。学习本书,可以帮助读者更好地运用Git,提升软件开发效率。 ​ ​《JavaScript忍者秘籍 第2版》 [美] John,Resig(莱西格),Bear,Bibeault(贝比奥特),Josip ... 著 点击封面购买纸书 JavaScript语言非常重要,相关的技术图书也很多,但至今市面没有一本对JavaScript语言的重要部分(函数、闭包和原型)进行深入、全面介绍的图书,也没有一本讲述跨浏览器代码编写的图书。而本书弥补了这一空缺,是由jQuery库创始人编写的一本深入剖析JavaScript语言的书。 《JavaScript 忍者秘籍(第2版)》使用实际的案例清晰地诠释每一个核心概念和技术。本书向读者介绍了如何掌握 JavaScript 核心的概念,诸如函数、闭包、对象、原型和 promise,同时还介绍了 JavaScript API, 包括 DOM、事件和计时器。你将学会测试、跨浏览器开发,所有这些都是高级JavaScript开发者应该掌握的技能。 ​ ​《深入理解Spring Cloud与微服务构建》 方志朋 著 点击封面购买纸书 纵览全书,文字清晰明了,通过理论结合实践的方式介绍了Spring Cloud的每一个组件的实践,并解读了部分源代码。图文并茂,语言朴实,不愧为“简单”之名。本书融合了作者实施微服务的一线经验和心得,具体指导了Spring Cloud在落地方面的实践,非常值得参考。 ​ ​《微服务分布式架构开发实战》 龚鹏rico 著 点击封面购买纸书 本书并没有过多的探讨理论性的东西,基于现有成熟框架,围绕实际项目中遇见的具体需求,以微服务分布式架构的角度去逐一分解并且实现这些需求。掌握这些知识的读者,完全有能力快速搭建出可靠、高效、灵活的微服务分布式架构。 ​ ​《文本上的算法——深入浅出自然语言处理》 路彦雄 著 点击封面购买纸书 微信整合搜索算法组组长路彦雄全新作品,深入浅出讲解自然语言处理和机器学习技术,微博总阅读量超30万次。 本书结合作者多年学习和从事自然语言处理相关工作的经验,力图用生动形象的方式深入浅出地介绍自然语言处理的理论、方法和技术。本书抛弃掉繁琐的证明,提取出算法的核心,帮助读者尽快地掌握自然语言处理所必备的知识和技能。本书适合从事自然语言处理相关研究和工作的读者参考,尤其适合想要了解和掌握机器学习或者自然语言处理技术的读者阅读。 ​ ​《数据结构 Python语言描述》 【美】Kenneth A. Lambert(兰伯特)著 点击封面购买纸书 在计算机科学中,数据结构是一门进阶性课程,概念抽象,难度较大。Python语言的语法简单,交互性强。用Python来讲解数据结构等主题,比C语言等实现起来更为容易,更为清晰。 ​ ​《Python程序设计(第3版)》 【美】John Zelle(策勒)著 点击封面购买纸书 Python之父作序推荐 ,Python 3 编程入门经典。本书以Python语言为工具教授计算机程序设计。本书强调解决问题、设计和编程是计算机科学的核心技能。本书特色鲜明、示例生动有趣、内容易读易学,适合Python入门程序员阅读,也适合高校计算机专业的教师和学生参考。 ​ ​《SQL优化核心思想》 罗炳森, 黄超, 钟侥著 点击封面购买纸书 本书内容源自“道森起点”高级SQL优化实战班,由专业团队倾力打造,集多年SQL优化实战经验和专职教学经验于一体。 结构化查询语言(Structured Query Language,SQL)是一种功能强大的数据库语言。它基于关系代数运算,功能丰富、语言简洁、使用方便灵活,已成为关系数据库的标准语言。本书旨在引导读者掌握SQL优化技能,以更好地提升数据库性能。 编程经典 纸书/电子书 ​ ​《C Primer Plus(第6版)中文版》 【美】Stephen Prata(史蒂芬 普拉达)著 点击封面购买纸书 经久不衰的C语言畅销经典教程、针对C11标准进行全面更新 与以前的版本一样,作者的目标仍旧是为读者提供一本入门型、条理清晰、见解深刻的C语言教程。作者把基础的编程概念与C语言的细节很好地融合在一起,并通过大量短小精悍的示例同时演示一两个概念,通过学以致用的方式鼓励读者掌握新的主题。 每章末尾的复习题和编程练习题进一步强化了重要的信息,有助于读者理解和消化那些难以理解的概念。本书采用了友好、易于使用的编排方式,不仅适合打算认真学习C语言编程的学生阅读,也适合那些精通其他编程语言,但希望更好地掌握C语言这门核心语言的开发人员阅读。 ​ ​《C++ Primer Plus(第6版)中文版》 【美】Stephen Prata著 点击封面购买纸书 一本经久不衰的C++畅销经典教程;首本支持C++11新标准的程序设计图书。 它被誉为“开发人员学习C++的必备教程,没有之一”! 《C++ Primer Plus(第6版)中文版》可以说是一本面向从未学习过C语言甚至是从未学习过编程的人的入门书籍,它的首章从基础内容讲起,先介绍了传统编程的规则,后面才着重讲解有关面向对象——C++的精髓之一——的有关内容。整个书的结构安排较为合理,难度爬升较慢。 如果你是一个从未学过C语言(或者压根没学会C)的读者,那么,我相信这本书更适合你。​ ​《Python核心编程(第3版)》 【美】Wesley Chun(卫斯理 春)著 点击封面购买纸书 Python是一种灵活、可靠且具有表现力的编程语言,它将编译语言的强大与脚本语言的简洁性、快速开发特性整合起来。在本书中,Python开发人员兼企业培训师Wesley Chun会帮助您将Python技能提升到更高的水平。 本书涵盖了成为一名技术全面的Python开发人员所需的一切内容。本书讲解了应用开发相关的多个领域,而且书中的内容可以立即应用到项目开发中。此外,本书还包含了一些使用Python 2和Python 3编写的代码案例,以及一些代码移植技巧。有些代码片段甚至无须修改就可以运行在Python 2.x或Python 3.x上。 本书适合具有一定经验的Python开发人员阅读 ​ ​《软技能 代码之外的生存指南》 约翰 Z.森梅兹 著 点击封面购买纸书 这是一本真正从“人”(而非技术也非管理)的角度关注软件开发人员自身发展的书。书中论述的内容既涉及生活习惯,又包括思维方式,凸显技术中“人”的因素,全面讲解软件行业从业人员所需知道的所有“软技能”。 对大多数软件开发人员而言,编码才是有趣的,而如何与客户、同事以及经理们打交道,如何保证工作效率,如何保障财务安全,如何保持自己的体形,如何找到真爱……这些则统统被视为畏途。本书恰恰可以在这些方面帮到你!​ ​ ​《代码整洁之道》 马丁 著 点击封面购买纸书 细节之中自有天地,整洁成就卓越代码。尽管糟糕的代码也能运行,但如果代码不整洁,会使整个开发团队泥足深陷,写得不好的代码每年都要耗费难以计数的时间和资源。然而这种情况并非无法避免。 这本书是软件工程大师马丁经典力作,由互联网产品与运营专家韩磊献译,本书荣获第13届Jolt大奖。 从《代码整洁之道》中可以学到:好代码和糟糕的代码之间的区别:如何编写好代码,如何将糟糕的代码转化为好代码:如何创建好名称、好函数、好对象和好类;如何格式化代码以实现其可读性的优化:如何在不妨碍代码逻辑的前提下充分实现错误处理;如何进行单元测试和测试驱动开发。 ​ ​《代码整洁之道 程序员的职业素养》 罗伯特·C.马丁著 点击封面购买纸书 成功的程序员在以往的工作和生活中都曾经历过大大小小的不确定性,承受过永无休止的压力。他们之所以能够成功,是因为拥有一个共同点,都深切关注创建软件所需的各项实践。他们将软件开发视为一种需要精雕细琢加以修炼的技艺,他们以专业人士的标准要求自己,他们具有职业素养。 软件开发大师Robert C. Martin在书中介绍了真实软件技艺中的各项原则、技术、工具和实践,展示了怎么以自豪、自尊和自信的心态进行软件开发,怎么取得卓越表现和丰硕成果,怎么做到有效沟通和确切估算,怎么以坦诚的心态面对困难,并引导读者认识到专业程序员肩负的责任重大,阐述了什么才是程序员的职业素养。​ ​《编程珠玑(第2版 修订版)》 乔恩·本特利(Jon Bentley)著 点击封面购买纸书 本书作者Jon Bentley,世界计算机科学家,被誉为影响算法发展的十位大师之一。 多年以来,当程序员们推选出心爱的计算机图书时,《编程珠玑》总是位于前列。正如自然界里珍珠出自细沙对牡蛎的磨砺,计算机科学大师JonBentley以其独有的洞察力和创造力,从磨砺程序员的实际问题中凝结出一篇篇不朽的编程“珠玑”,成为世界计算机界名刊《ACM通讯》历史上受欢迎的专栏,结集为两部不朽的计算机科学经典名著,影响和激励着一代又一代程序员和计算机科学工作者。本书为首卷,主要讨论计算机科学中本质的问题:如何正确选择和高效地实现算法。 在书中,作者选取许多具有典型意义的复杂编程和算法问题,生动描绘了历史上众大师们在探索解决方案中发生的轶事、走过的弯路和不断精益求精的历程,引导读者像真正的程序员和软件工程师那样富于创新性地思考,并透彻阐述和总结了许多独特而精妙的设计原则、思考和解决问题的方法以及实用程序设计技巧。​​ ​《编程珠玑(续 修订版)》 乔恩·本特利著 点击封面购买纸书 本书是计算机科学方面的经典名著《编程珠玑》的姊妹篇,讲述了对于程序员有共性的知识。延续了《编程珠玑》的特色,通过一些精心设计的有趣而又颇具指导意义的程序,对实用程序设计技巧及基本设计原则进行透彻而睿智的描述,为复杂的编程问题提供清晰而完备的解决思路。涵盖了程序员操纵程序的技术、程序员取舍的技巧、输入和输出设计以及算法示例,这些内容结合成一个有机的整体,如一串串珠玑展示给程序员。(Jon Bentley) ​ ​ ​《重构 改善既有代码的设计》 马丁·福勒(Martin Fowler)著 点击封面购买纸书 软件开发的不朽经典,生动阐述重构原理和具体做法,普通程序员进阶到编程高手必须修炼的秘笈。 重构,一言以蔽之,就是在不改变外部行为的前提下,有条不紊地改善代码。多年前,正是本书原版的出版,使重构终于从编程高手们的小圈子走出,成为众多普通程序员日常开发工作中不可或缺的一部分。本书也因此成为与《设计模式》齐名的经典著作,被译为中、德、俄、日等众多语言,在世界范围内畅销不衰。 本书凝聚了软件开发社区专家多年摸索而获得的宝贵经验,拥有不因时光流逝而磨灭的价值。今天,无论是重构本身,业界对重构的理解,还是开发工具对重构的支持力度,都与本书出版时不可同日而语,但书中所蕴涵的意味和精华,依然值得反复咀嚼,而且往往能够常读常新。 视频课程 新品 ​《TMMi精华课程》 测试成熟度模型集成 点击封面购买 TMMi(测试成熟度模型集成)是国际非营利性组织TMMi基金会开发和维护的一个测试成熟度模型。使用TMMi,组织可以通过有资质的评估师来客观地评估和改进他们的测试过程。 TMMi当前在国内逐渐得到认可和普及。本课程并不包含详细的TMMi的完整描述,而是对模型的精华部分进行概要的描述,此外,还涉及TMMi评估方法和TMMi实施的部分实践。 课程目录: TMMi基金会主席 Erik van Veenendaal 推荐(04:06)[试看] 业界资深专家 Donald Craigie 推荐(05:18)[试看] TMMi主任评估师任亮推荐(03:46)[试看] 周震漪先生推荐(05:07)[试看] 李士湘先生推荐(03:34)[试看] 徐盛先生推荐(03:53)[试看] 模型优势(04:20) 成本与收益(03:18)[试看] 国内外现状(01:47) TMMi 1级简介(02:47) TMMi 2级简介(02:53) TMMi 3级简介(02:26) TMMi 4级简介(03:59) TMMi 5级简介(02:04) 过程域的结构(02:46) 通用目标和目的简介(02:06) TMMi评估简介(02:39) TMMi主任评估师和评估师简介(03:31) TMMi实施主要步骤(02:47) TMMi实施成功的关键因素(04:37) ​ ​《算法学习与应用从入门到精通》 点击封面购买 算法是程序的灵魂,只有掌握了算法,才能轻松地驾驭程序开发。算法能够告诉开发者在面对一个项目功能时用什么思路去实现,有了这个思路后,编程工作只需遵循这个思路去实现即可。本课程是图书《算法学习与应用从入门到精通》知识点讲解,适合算法研究和学习的初学者,也适合有一定算法基础的读者。 课程目录: 算法学习与应用从入门到精通视频教程——知识点讲解 第1章 算法是程序的灵魂(07:47)[试看] 第2章 常用的算法思想(15:57) 第3章 线性表、队列和栈(17:33) 第4章 树(18:06) 第5章 图(22:05) 第6章 查找算法(18:04) 第7章 内部排序算法(23:24) 第8章 外部排序算法(21:50) 第9章 经典的数据结构问题(02:52) 第10章 解决数学问题(04:40) 第11章 解决趣味问题(08:17) 第12章 解决图像问题(04:26) 第13章 算法的经典问题(06:22) 第14章 解决奥赛问题(09:07) 第15章 常见算法应用实践(04:46) 第16章 俄罗斯方块游戏(01:56) 第17章 学生成绩管理系统(02:08) 第18章 绘图板系统(01:32) 第19章 UDP传输系统(02:13) 第20章 推箱子游戏(02:06) ​ ​《奔跑吧Linux内核》视频教程第一季:内存管理初级》 张天飞 点击封面购买 课程基于Linux 4.0内核,主要选取了Linux内核中最基本最常用的内存管理、进程管理、并发与同步以及中断管理这4个内核模块进行讲述。全书共分为6章,依次介绍了ARM体系结构、Linux内存管理、进程调度管理、并发与同步、中断管理、内核调试技巧等内容。本书的每节内容都是一个Linux内核的话题或者技术点,读者可以根据每小节前的问题进行思考,进而围绕问题进行内核源代码的分析。 课程目录: 奔跑吧Linux内核 预告片 运维高级如何单步调试RHEL—— CENTOS7的内核一(04:00)[试看] 序言一: Linux内核学习方法论(09:13) 序言2-1 Linux发行版和开发板的选择(13:56) 序言2-2 搭建Qemu+gdb单步调试内核(13:51)[试看] 序言2-3 搭建Eclipse图形化调试内核(10:59)[试看] 奔跑2-0-0 内存管理硬件知识(15:25) 奔跑2.0.1 内存管理总览一(23:26)[试看] 奔跑2-0-2 内存管理总览二(07:35) 奔跑2-0-3 内存管理常用术语(09:49) 奔跑2.0.4 内存管理究竟管些什么东西(28:02) 奔跑2-1-2 物理内存初始化(11:13) 奔跑2.0.5内存管理代码框架导读(38:09) 奔跑2-1-0 DDR简介(06:47) 奔跑2-2-1 ARM64页表的映射(10:58) 奔跑2.1.1 物理内存三大数据结(19:39) 奔跑2-2-0 ARM32页表的映射(08:54) 奔跑2-2-2 页表映射例子分析(11:59) 奔跑2-2-3 ARM32页表映射那些奇葩的事(09:42) 奔跑2-3-1 内存布局一(10:35) 奔跑2-3-2 内存布局二(13:30) 实战运维1:查看系统内存信息的工具(一)(20:19) 实战运维2:查看系统内存信息的工具(二)(16:32) 实战运维3:读懂内核log中的内存管理信息(25:35) 实战运维4:读懂 proc meminfo(27:59) 实战运维5:Linux运维能力进阶线路图(09:40)[试看] 实战运维6:Linux内存管理参数调优(一)(19:46) 实战运维7:Linux内存管理参数调优(二)(31:20) 实战运维8:Linux内存管理参数调优(三)(22:58) 今日互动 你读过哪些异步图书的经典书?截止时间4月23日17时,留言+转发本活动到朋友圈,小编将抽奖选出5名读者赠送e读版100元异步社区代金券一张,(留言点赞最多的自动获得一张)。 ​ 推荐阅读 2018年4月新书书单 异步图书最全Python书单 一份程序员必备的算法书单 第一本Python神经网络编程图书 ​​ ​长按二维码,可以关注我们哟 每天与你分享IT好文。 在“异步图书”后台回复“关注”,即可免费获得2000门在线视频课程;推荐朋友关注根据提示获取赠书链接,免费得异步e读版图书一本。赶紧来参加哦! 扫一扫上方二维码,回复“关注”参与活动! 点击阅读原文,直达异步社区参加99减20元/199减50元促销活动(时间:4月19-4月46日) 阅读原文

优秀的个人博客,低调大师

简单快速开发C\S架构程序用简单的不分层最快的效率

用通用权限管理系统组件开发一个简易的日积月累功能的代码实现,运行效果如下效果,很多通用的小功能系统组件自动都实现了,那开发应用程序会变得又快又简单了。 具体代码参考如下: 1 // -------------------------------------------------------------------- 2 // All Rights Reserved , Copyright (C) 2012 , Hairihan TECH, Ltd. 3 // -------------------------------------------------------------------- 4 5 usingSystem; 6 usingSystem.Data; 7 usingSystem.Windows.Forms; 8 9 namespaceDotNet.WinForm 10{ 11 usingDotNet.Business; 12 usingDotNet.Utilities; 13 14 /// <summary> 15 /// FrmKnowledge.cs 16 /// 日积月累 17 /// 18 /// 修改记录 19 /// 20 /// 2012.09.03 版本:1.0 JiRiGaLa 修改功能页面编写。 21 /// 22 /// 版本:1.0 23 /// 24 /// <author> 25 /// <name> JiRiGaLa </name> 26 /// <date> 2012.09.03 </date> 27 /// </author> 28 /// </summary> 29 public partial classFrmKnowledge : BaseForm 30 { 31 publicFrmKnowledge() 32 { 33 InitializeComponent(); 34 } 35 36 /// <summary> 37 /// 日积月累的知识库 38 /// </summary> 39 DataTable dtKnowledge = null; 40 41 /// <summary> 42 /// 当前显示第几条 43 /// </summary> 44 intCurrentIndex = 0; 45 46 #regionpublic override void ShowEntity() 显示内容 47 /// <summary> 48 /// 显示内容 49 /// </summary> 50 public override voidShowEntity() 51 { 52 // 显示信息 53 BaseCommentEntity commentEntity = newBaseCommentEntity(dtKnowledge.Rows[ this.CurrentIndex]); 54 this.txtContents.Text = commentEntity.Contents; 55 } 56 #endregion 57 58 public override voidSetControlState() 59 { 60 if( this.dtKnowledge != null&& this.dtKnowledge.Rows.Count > 0) 61 { 62 this.btnNext.Enabled = true; 63 if( this.CurrentIndex == this.dtKnowledge.Rows.Count - 1) 64 { 65 this.btnNext.Enabled = false; 66 } 67 this.btnPrevious.Enabled = true; 68 if( this.CurrentIndex == 0) 69 { 70 this.btnPrevious.Enabled = false; 71 } 72 } 73 } 74 75 #regionpublic override void FormOnLoad() 加载窗体 76 /// <summary> 77 /// 加载窗体 78 /// </summary> 79 public override voidFormOnLoad() 80 { 81 // 获取数据 82 SQLBuilder sqlBuilder = newSQLBuilder( this.UserCenterDbHelper); 83 sqlBuilder.BeginSelect( " BaseKnowledge "); 84 // 只获取前200个数据就可以了,减小网络传递数据的网络带宽。 85 sqlBuilder.SelectTop( 200); 86 if(! string.IsNullOrEmpty( this.EntityId)) 87 { 88 sqlBuilder.SetWhere(BaseCommentEntity.FieldId, this.EntityId); 89 } 90 // 这里是为了每次显示的数据都是乱序的,数据测循序是被打乱的。 91 sqlBuilder.SetOrderBy( " NEWID() "); 92 dtKnowledge = sqlBuilder.EndSelect(); 93 if(dtKnowledge.Rows.Count > 1) 94 { 95 this.CurrentIndex = newRandom().Next( 0, dtKnowledge.Rows.Count - 1); 96 } 97 // 显示实体 98 this.ShowEntity(); 99 100 // 显示日积月累 101 stringshowKnowledge = DotNetService.Instance.ParameterService.GetParameter(BaseSystemInfo.UserInfo, " User ", " ShowKnowledg ", " Show "); 102 if(! string.IsNullOrEmpty(showKnowledge)) 103 { 104 this.chkShowKnowledge.Checked = showKnowledge.Equals( true.ToString()); 105 } 106 } 107 #endregion 108 109 private voidchkShowKnowledge_CheckedChanged( objectsender, EventArgs e) 110 { 111 if( this.FormLoaded) 112 { 113 DotNetService.Instance.ParameterService.SetParameter(BaseSystemInfo.UserInfo, " User ", " ShowKnowledg ", " Show ", this.chkShowKnowledge.Checked.ToString()); 114 } 115 } 116 117 private voidbtnPrevious_Click( objectsender, EventArgs e) 118 { 119 if( this.CurrentIndex > 0) 120 { 121 this.CurrentIndex--; 122 this.ShowEntity(); 123 } 124 this.SetControlState(); 125 } 126 127 private voidbtnNext_Click( objectsender, EventArgs e) 128 { 129 if( this.CurrentIndex < this.dtKnowledge.Rows.Count - 1) 130 { 131 this.CurrentIndex++; 132 this.ShowEntity(); 133 } 134 this.SetControlState(); 135 } 136 137 private voidbtnColse_Click( objectsender, EventArgs e) 138 { 139 this.Close(); 140 } 141 } 142} 本文转自 jirigala 51CTO博客,原文链接:http://blog.51cto.com/2347979/1196196,如需转载请自行联系原作者

资源下载

更多资源
Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Spring

Spring

Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

用户登录
用户注册