这个板块主要提及的是自我重建，自我修复的部分。羞耻感一个人不被看见、不被接纳、不被爱会产生羞耻感。羞耻感是指：当个体意识到自己的行为、想法或自身特征违反了社会公认的道德准则、价值观，或是没达到自己内心期望的标准时，所产生的一种痛苦、难堪、甚至贬低自我的感受。与羞愧不一样，羞耻关乎“我是谁”这个问题，是做人的失败；而羞愧关乎“我做了什么”，是做事的失败。羞耻感会在亲密关系中出现，是因为权力不对等，强势方对弱势方的审视和排斥，通常是先有结论，再寻找或者杜撰各种证据和标准。强势方不断羞辱弱势方获得权力，弱势方害怕被伤害、被抛弃，就主动放弃自己的权益，竭力讨好强势方，试图获取强势方的“肯定”、“支持”或“恩宠”。羞耻感会让人产生回避型为和掩饰情绪：回避行为包括了回避他人和回避自我这两个方向，前者让人主动孤立自己，后者让人陷入各种成瘾行为。掩饰情绪则包括了攻击他人和攻击自我这两个方向，前者增加关系冲突，后者让人陷入抑郁。比如别人的感受和诉求永远是第一位的，自己总是可有可无的靠后。超越羞耻感要敢于直面它，对改变保持耐心，主动去面对羞耻感，重新修剪自己；用优势视角看待自己，意识到自身的清醒和勇气，主动发挥自己的优势；借助人际环境的力量，在与人的互动中，不要接近那些指责你不行，否定你的成就，将成就归因于运气，甚至要让你把信任全数建立在他身上的人。超越羞耻感就要一点点做自我尊重的事情，培养自己的高自尊。自尊作为名词时，是指的我们对自己自我价值的和能力的感觉，高自尊是指我们觉得自己是有价值的、有能力的、值得被爱的。而自尊作为动词时，是指我们对自己的尊重和喜欢，是觉得自己值得拥有想要的生活，也能用行动来争取这样的生活。在超越羞耻感时，要寻求帮助，自我挑战羞耻感，不要去相信别人合理化后的行为，要看到本质，不然羞耻感就会作为一种控制手段被利用。如果因为羞耻感，而无法在亲密关系中做真实的自己，那么就会给自己、伴侣和关系都带来困扰。培养自我需要树立信念，找到这份信念的来源和喂养这份信念的人、事、物，然后切断供养、消除不合理的信念，培养有利于自我的信念。只有当我们直面放下对”被爱“的幻想时，我们才会获得更多的改变力量。在这个过程中或许会遭受到很多挫折，因为当我们设定目标，并全力以赴时，其实这是一次冒险，冒险就意味着可能会失败，直面冒险带来的失败可能，和面对失败时的坚持，恰恰也是我们赢得自我尊重的最好的机会。成功超越羞耻感的标志是：不再用”是否足够成功、足够优秀“和”是否符合他人期待和外界的标准“来定义自我价值，而是纯粹能够因为”我存在“而感到安稳和喜悦。允许自己真实、允许自己不完美、允许自己偶尔笨拙和犯错，坚信真实的自己值得被爱和尊重。当下的社会环境对弱势群体并不友好，因为环境本身就在源源不断地催生羞耻感，环境告诉你”是因为你不好，所以你才得不到你想要的“，或者是”因为你不好，所以我才不给你你想要的“，但是很多时候，事实并非如此。我们无法在短时间内改变大环境，但是可以从对自己、对身边人更包容、更友善开始，努力塑造更好的环境——主动参与创造理想中的环境，这也是一种对羞耻感的超越。冲突冲突换个角度上来讲，其实是”爱正在存在“的迹象，也是”爱可以发展“的契机。因为在亲密关系中，冲突正是双方在争夺做自我的权力和空间。当一个人爱自己，就会更有勇气为了自己的权益面对冲突。当两个人都确信彼此相爱着，两个人也都会更有勇气直面冲突，因为双方知道这并不会立刻导致关系破裂，其实和爱是一种能力一样，冲突也是一种能力。好的冲突是双方表达真实想法和需求、增进彼此了解、改善关系质量的机会。一些人正是因为重视关系，希望通过解决问题来让关系更好、更稳固，于是主动挑起了冲突。很多时候，糟糕的并不是冲突本身，而是冲突发生时双方的处理方式。如果有更多的倾听和合作，那么就有机会共赢。即便有一方退让、做出了牺牲，彼此也有机会用新的事情弥补另一方，让感恩和公平得以在关系中存在。亲密关系中冲突的定义是指：当个体的动机、目标、信念、观点和行为妨碍别人或与别人矛盾，就会发生人机冲突。冲突产生于差异，既可能表现为一时的情绪，也可能表现为持久的信念和人格。对亲密关系的想法也会产生冲突，例如当对方有强烈的完美主义倾向，以理性和体面作为表现；而另一方觉得亲密关系是时刻和谐的，以感性和灵活作为表现；两者的差异就会产生冲突。不爱了确实会导致更多的冲突，因为不爱所以只想要自私地做自己，当两个人都这样，差异必然导致冲突。但是冲突本身不一定会导致不爱了，很多时候是破化性冲突中地指责和攻击扩大了关系的裂缝——比如“别烦了”这就是指责，遭受指责的一方既可能感觉自己不再被爱了，也可能在认同指责时的内容后变得“不爱自己”了，于是，不仅关系中的爱在消失，对自己的爱也在消失。指责是为了避责和控制，常见的有“你太敏感了”，这些指责隐藏着因果关系，本质上都是在转移焦点，让人忽视真正的错误和有错之人，为了避责。其中“攻击人的不完美”相对更常见，也更容易让我们陷入自责。一个人好不好和是不是会被人喜欢其实没有直接关系。相比于因为你很好所以我喜欢你，更常见的事，因为我喜欢你所以我觉得你很好。反过来也是一样的，被讨厌也不是意味着你不好。没有人是完美的，当一个人想避责时，总能找到一些理由来指责对方，尤其是像性格、外表这样充满主观喜好的领域。因为当动机是避责和控制时，指责比讲道理要来的更有效，所以可以转移焦点，以对方的标准来审视自己，这样的就会引发羞耻、恐惧，而限制我们维护自身权益，争取更多自由的意识和动力，这都会让控制变得更容易。所以任何时候，面对对方的指责时，第一时间要做的并不是听对方说了什么，而是思考对方为什么要这么说，也就是思考他的动机，正在以什么方式做到具体的哪些“趋利避害和享乐主义”。错位的指责——你不向着我，这种错位的指责不仅容易让当事人错失改变和成长的机会，时间长了，还会破坏各种长期关系，尤其是深度的长期关系——因为磨合中必然包含着批评，批评如果能够带来积极调整，关系质量才能提升。想要偏爱的人恰恰是缺爱的人，长期在权衡和牺牲中以交易心态换取爱，对自己值得爱这件事情不抱期待，所以才会说你不向着我，不过这是他的课题。情绪的积累在瞬间爆发，则会成为一次次跨越时空的联合指责，如果从小经历着“情绪忽视”，则会让人形成“情绪表达”上的习得性无助，觉得表达情绪没有用，想不到也不习惯去表达，于是会期待对方能看到自己的情绪。如果能遇到主动观察你情绪的人是一种幸运，但是自己需要培养表达自己情绪的能力。其实上述的这些冲突之所以会让你感到困扰，实际上是因为自己的内心也存在冲突，外部的冲突激发了你内在的自我审视。审视本可以是建设性的，是自我觉察和反思的一部分，但是有一种自我审视是消极的，就是习惯站在别人的角度来审视自己，用别人的评价标准来评价自己，发现自己不符合别人的期待和标准时陷入自我攻击。处于持续的关注压力和评判压力下的人，会养成这种不好的自我审视，出于压力和愧疚感很可能会产生“我必须符合期待”这样的认知模式。严重的时候会出现逃避主义，直接把自身的存在给模糊掉，不再体验自身存在时，被外部压力持续凝视的部分也就全部消失了。更严重的可能出现自毁倾向，也就是故意搞砸，因为这种“我不够好”的感觉，持续的、毫无解脱希望的焦虑会转化为自我惩罚的冲动。要知道，长期正处于被凝视的环境中会逐渐模糊自己的主体性，甚至造成一些创伤。蔡格尼克效应描述了，人对未完成的执着可能更为深刻，因为期待引起的心理张力得不到满足，从而致使念念不忘，在这种情况下，需要训练自己放弃的能力，保持空的境界。在我们社会环境中，大部分人从出生开始就不断地被“分类”，而分类是简单的贴标签行为，分类很容易让人形成“我们、你们和他们”的割裂，让交流减少、误解增多，这对爱来说并不是有好的环境，冲突也会增加。而且很多分类不仅是分类，还包含等级划分，等级划分容易造成更多的压制和剥夺，结果在权力的阴影下，爱濒临消失，冲突也容易进一步升级。一开始，我们进行分类和等级划分可能只是为了更好的关心、管理和效率，但是人性的阴暗面会放大自私和欲念，逐渐背离曾经的私心。当资源稀缺、世界环境也并不和平友善的情况下，分裂和冲突可能会越来越多，越来越严重，爱也会变得越来越艰难、越来越难得。其实每个人都有差异，尤其是当信念、价值观和行为模式存在比较大的差异时，冲突发生的概率就会更大。也即，对于深度且长期的亲密关系来说，更好的爱与被爱经常需要通过建设性的冲突来解决。但是，对于爱的安全感不足的人来说，容易将分歧误解为关系破裂的信号，或者将冲突等同于被拒绝或抛弃的前兆，对于关系中的不确定性和差异性会更为敏感。这就可能在压力和情绪影响下采取两种自动反应：压抑自己的需求来维持表面的和谐，也就是直接回避冲突；或者以破坏性的方式表达不满（比如指责、攻击、抱怨等），试图通过控制对方来会获得安全感。如果关系的双方都能在面临冲突时，不再仅仅关注冲突本身，把冲突看作彼此想要在差异中寻求自己与相爱的平衡，就会让两个人更有可能达成某种程度上的一直，还能深化理解、增强感情联结。很多时候，我们讨论更好的爱和相爱，总是在关注更好的沟通、关心和给予，但是其实培养冲突的能力是尤为重要的，无论是爱自己还是爱他人。每一次成功的冲突管理，都在加固关系的韧性，让两个人在差异中既能保持自我，又能有更相爱的我们。重建自我分手是后如何度过人生低谷，如何在“爱过”中存在自己的意义。分手的“爱过”中有我们的天真与憧憬，有毫无保留的付出和信任，有情到深处的牺牲和依赖，还有曾经把对方视为人生的执着。关系破裂会因为不同的关系程度，造成不一样的损伤。当一个人的人格和心理都稳定且又独立时，分手造成的痛苦会比较有限。如果双方都在关系中真正被对方看见过，那么分手后，其实在这段关系中不断地完整了自己，那么留下来的可能是自豪、意义和感恩。不同的依恋类型的分手表现是不一样的：安全型依恋的人，对他们来说分手是一种选择、一段经历，他们对未来人生的憧憬并不会因此承受巨大的冲击。但是焦虑型依恋的人，在感情中常常患得患失，极度渴望伴侣的关注和回应，这些可能会加速分手，而分手后又会陷入更大的崩溃。焦虑型依恋人格的人，分手后可能还会陷入那种找到“爱还在”或者是“爱消失了”的证据。哪怕对方已经明确表示不想继续了，他们也很难接受这个结果，甚至可能会突破底线去挽回对方，然后陷入一段权力不平等的感情关系里。如果挽回不成，还容易长时间陷入自责、怨恨和焦虑中。回避型依恋的人，面上是没有影响的，夜深人静时即便想起来了，但是很快也会压下去。分手对他们来说是一种解脱，帮助他们挣脱了亲密关系带来的紧张和不安，但是加速了他们的孤单和迷茫，让他们的感情世界仿佛再次被冰雪冻住了。其实分手后的痛苦不一定和对方相关，依赖、痛苦实际上可能和自己有关系——我们的依恋类型影响了我们对分手、对关系破裂这件事的认知和感受。正因为这样，分手后，我们的感受、想法和表现，恰好是一个让我们更深入也更全面体察自己的依恋类型的机会。在充分了解自己依恋类型的基础上，我们也能慢慢看见并疗愈背后的创伤，试着学习和自己的依恋类型相处。我们在塑造关系，关系同时也在塑造我们，分手会让我们和关系都现出原形，自尊感会被关系影响，关注、接纳和爱都会提升我们的自尊感，让我们在爱与被爱中践行自我尊重的行动。要知道，爱与迷恋是不同的，没有真正的被看见就只是一种迷恋。迷恋往往开始于被对方某个突出的特质强烈吸引，比如出众的外貌、独特的才华等，更像是一种瞬间被点燃的激情，满脑子都是对方，迫不及待想要靠近。但是这种感觉本质上来说，只是自我情绪的高度投入，是为了满足自己的幻想和渴望。迷恋状态下的人会刻意在对方面前表现最好的一面，也希望对方时时刻刻完美，迷恋下的人们更多在追求享受浪漫的激情。而爱不同，爱是一种更深层次的情感链接，是真正的看见，是对彼此优点和缺点的全盘接纳，愿意为对方的幸福去付出，即便要牺牲自己的利益也甘之如饴。爱会让两个人自然袒露自己的不足与脆弱，当激情淡化后，依然能从彼此的陪伴中感受到温暖和安心。为什么有的感情会分手，因为仅仅是一种迷恋，受到性别偏见和浪漫主义爱情文化的影响，很多“爱”的底色都只是一时激情的迷恋，再加上缺少关于爱的认知和能力，迷恋也没有机会发展成为爱，于是就容易陷入“幻想——失望——分手”的困境循环。当然也有通过分手才能实现满足的需求，从需求角度上来看，有的关系到了一定程度了，因为这段关系无法满足一方或者双方的需求，而分手可以，就会导致分手。对于焦虑型依恋的人来说，分手可能会迎来对方的挽回，所以他们的意图是找到这种“爱的证据”，于是会选择分手，但是意图却是再次相爱以满足他的情感需求。亲密关系中的需求可以分为三类：保底需求：不满足关系就会破裂，但是满足了也不会加分。比如，对于一些人来说，忠诚或基本的物质保障是保底需求。核心需求：这些需求你很重视，满足了会让你对关系更满意，大幅度加分。比如，越来越多的人开始把情绪价值的满足视为核心需求。中奖需求：类似彩票中奖，不中奖是常态，但是一旦中了就是惊喜。如果你想要分析为什么分手，可以从这三个类别出发，如果说不清对方或者自己的需求，那么就是了解太有限了，没有看见过真实的自己和对方，所以也没法用彼此想要的方式来爱与被爱。要知道，当需求差异太大，同样的关系很难让两个人都感到满足，就很容易导致分手。亲密关系存在两种形式：交换关系和共有关系。交换关系格外注重公平，付出就期待同等的回报。有这种想法的人，很可能只有在认为自己能获益的时候，才会关注回应对方的需求。交换取向会比较隐蔽，用一些客观条件去交换，一旦觉得你没有交换价值了，就会提出分手。与交换关系相对的，是共有关系。共有关系往往表现为：两个人都不计较付出和回报，也渴望对对方的需求做出回应，认为对方的幸福就是自己的幸福，也是爱存在的表现。共有关系其实并不是不在意公平，只是关心的是长期的总体上的公平，相信彼此都在意对方的付出和需要，都会为彼此的幸福和长远发展考虑。但是，由于女性主义崛起后，亲密关系中公平的幻像被打破了，于是不公的现实展露出来。社交媒体上的知识和其他人的故事，或者自己正在经历的现实，让更多的人意识到只有自己在秉持着共生关系，对方只是交换关系，导致分手。但是更为复杂的是，当我们带着对公平的关注，再次尝试踏入一段新的关系时，很可能会很难全然去享受彼此的付出和给予，暂时的、细微的不公都会让我们警铃大作，让关系往交换取向倾斜，丧失了一部分的浇灌爱的养分，增加分手的概率。分手了也不一定是不爱了，因为爱是彼此成就，当分手更有助于成就时，那么分手也是爱的行动。而且如果叠加我们其他人生角色的爱、职责和价值观，以及环境的影响，即便两个人之间有爱，关系也不一定能圆满。反过来也是一样的，关系圆满，也不一定是因为爱。爱能成长，是人的伟大之处，也是人生的一种幸运。那么分手后该怎么办？怎么在“丧失”中创造意义？接纳现实、承担责任、超越羞耻之后，如何找到自己真正想要的东西、做出自己真正想要做的决定、创造出独属于自己的意义？一是找到自己的自主动机，心理学的自我决定论将人的动机分为自主动机和受控动机，前者是指：我这么做是因为我想做，是出于重视和享受，情绪比较积极。后者是指：我这么做是为了别人的期望或别人的需求，是不得不这么做，情绪比较消极。自主动机是自由的，而受控动机是被绑架的选择。我们可以想象如果事情做了之后，如果只是松了一口气，那可能就只是受控动机；如果是喜悦更多，还有希望感和新的憧憬，那么就是自主动机。如果分手后想挽回，是因为想要爱和幸福，那么这是自主动机，但是如果是因为忍受不了分手的痛苦和煎熬，则是受控动机。分手之后要在“丧失”中创造意义，需要我们静下心来，体察自己的情绪和想法，识别出这些情绪和想法背后的动机，并判断这些动机究竟是自主动机还是受控动机，然后尝试放下那些受控动机，努力实现那些自主动机。要知道，哪怕是自己，跨越时空后要做到完全的理解和感同身受也不容易。所以对于伴侣、朋友、加入，我们依然希望对方能更多地了解我、理解我，但是我们也要更多地了解自己、表达自己。如何爱自己本章会围绕对话、归因和承诺三个话题，来聊聊如何爱自己。首先是对话，对话其实就是沟通，沟通是一个传递信息以达成意图的过程，但是对话双方的编码特点和解码特点不同，而且还会受到噪音和干扰的影响。亲密关系由于经历更久，更熟悉，在编码和解码这块会不断提升默契；但是更关键的是心理和思维模式的变化，当然恰恰是因为彼此太熟悉了，也可能会导致双方都意识不到这些变化，会让人下意识地以为对方是这么想的，会忘记像刚开始一样，认真思考对方“实际上是怎么想的”，于是解码常常会在自以为是中出错，又在自以为是中错过修改的机会。一场好的沟通需要根据具体的人、具体的事、具体的情境进行，不仅需要注意对方说了什么，做了什么，还要留意双方的情绪、想法和处境，等等，如果只是把焦点放在说了什么、做了什么，而忽视了彼此怎么想、怎么看时，就很容易导致沟通问题。其实自我对话更为重要，当我们自己与自己对话时，可以将编码和解码有意识地做到更接近沟通意图时，沟通才会更顺畅。而且情绪管理、压力管理、坚持和自律等的背后，其实也都包含自我对话。而且如果没有觉察并培养自我对话的能力时，我们的自我对话更大程度上会受到和他人对话的影响，因为和他人的对话会构成自我对话的素材，我们自我对话的内容常常是从家人或朋友的互动信息库中取得的。当学习到的信息库是指责、贬低和恐惧的信息，就会导致自己不断内化这些信息，那么自我对话也会常常是指责、贬低和恐惧。于是就会变得越来越愧疚和胆小，或者变得孤僻，或者不断地放弃底线来争取友情或爱情。如果是安全、相信和希望的信息，那么就会变得充满安全感、希望和信任。如果是探索和选择的信息，那么就会变得更为开放、包容和建设性。要学会纠正自我对话，在不爱的自我对话中，自我会立刻、不假思索地去全盘接受这样的信息，但是这样不对，在关怀视角下，要去体察自己的情绪，看到情绪背后的想法、需求和价值观，找到他们从哪里来，要告诉我们什么，然后去接纳回应它们。优势视角也是如此。在爱的自我对话中，有三个要素——客观经历的事实、自动反应的主管想法、主动选择的想法解读。其中最后一个就是我们能慢慢练习的要素。除了上面的关怀视角、优势视角、成长型视角也是一个带着爱来自我对话的视角。当自己如果做了很多能做的事情了，随着人生的发展，可能可以用来成长的时间、精力确实有限，那就至少要看到客观上的局限，而不是在主观上去责怪自己。我们有时需要向过去的自己学习，有时需要和现在的自己和解——拿过去的自己和现在的自己进行比较可能并不公平。二是归因，研究发现，伴侣双方的一般归因模式会影响他们对关系的满意度。其中，维持痛苦型归因会增加不满、矛盾和冲突，而增进关系型归因会让彼此更友善、更合作，也更满足。维持痛苦型归因认为，伴侣的负面行为都是故意的、习惯性的、改不了的；而正面行动都是无心的、偶然的、维持不了的。但是增进关系型归因则认为，伴侣的负面行为是偶然的，对行为原因做了善意的理解，还会肯定伴侣的体贴和慷慨。其实练习增进关系型归因是寻找能让你更爱自己、更尊重自己、对自己和未来更有信心的答案。（这里增进关系，是增进我们与自己的关系，而不是与对方增进关系）三是承诺，承诺分为三种：个人承诺、道德承诺和强迫承诺。简单来说就是：我想要、我应该和我必须。人品和所在的环境往往是道德承诺和强迫承诺的根基。如何运用这三种承诺爱自己呢？个人承诺是唤醒自己对健康状态的憧憬；道德承诺是考虑对他人的责任；强迫承诺可以是社交属性的、也可以是经济属性的，就是让自己带上紧箍咒。出于个人承诺、道德承诺和强迫承诺，我们可以用希望和恐惧的力量，因为希望感、责任感和对失去的恐惧，咬咬牙坚持自主动机的自律和管理。那么如何更好相爱？爱情由三个元素构成——激情、亲密和承诺。只有激情的爱只是一种迷恋；只有亲密，没有激情和承诺，这只能算是喜欢——例如友情；如果只有承诺的爱，那么只能是一种空虚的爱，缺少动力和爱的细节。对于短期关系来说，激情和承诺或许可以单方面形成，但是亲密需要两个人一起努力。对于长期关系来说，激情、亲密和承诺三个元素都需要两个人一起努力。关系中的爱成长或者消失的过程，也是这三个元素变化的过程，经营亲密关系，也是在经营这三个元素。两个人能不能经营好一段关系，感情的作用有限，更重要的是双方的能力，尤其是亲密和承诺的能力。很多时候，一个人无法与另一个保持亲密，其实并非因为他不想，而是他不会，他也未曾跟自己保持过亲密，很少跟自己进行爱的对话，不知道如何和另一个人展开爱的对话来增进亲密。这一点在男性身上更为常见，因为性别偏见文化宣导的男性气概，并不倡导带着爱来进行自我对话，也不鼓励他们向内进行自我探索，于是他们不懂怎么和伴侣用爱的对话来增进亲密。如果再加上固定型思维的影响，他们也很少学习怎么去改进，只会跟自己说：只要我功成名就，伴侣会有的，爱也会有的，而且会有很多。幸福要知道，幸福并不是常态，不幸福也不一定代表两人有缺陷。因为本质上幸福是违反本能的事情，幸福并不是大脑追求的，生存才是，哪怕是在痛苦中生存。演化心理学指出：为了适应环境而演化，不一定是变得更好，只是变得更能在环境中存活。人的大脑会更关注危险，为了生存提供更多保障。但是大脑会逐步演变成一个问题解决机器。而要拥有幸福，我们需要充分地感知当下拥有地一切、感恩那些我们珍视的。本能总是让我们想要拥有的更多，但是，拥有的更多就会更幸福吗？我们的大脑会让我们额外关注和群体归属有关的一切，比如常常和其他人做比较；但是另一方面，我们的自由意志又让我们追求独特性，于是，在合群和独特两者的挣扎中，我们愈发迷茫和焦虑，也更难抵达幸福的状态。我们总说要接纳自我，接纳自我的一个底层议题就是接纳我们生而为人的本能。当本能发生的时候，我们不再责怪本能，也不再责怪自己，我们便能获得一些坦然和平静，也能集中精力、发挥自由意志，去选择、去行动、去改变。即便是克服了本能，或许我们离幸福还是很远，因为从小到大的环境并没有教导我们走向幸福，学校思维也是一种依赖权威、被动选择、缺乏主体性的思维。在学校思维的影响下，我们容易盲从外界告诉我们幸福的标准和方法，也容易依赖别人为我们的幸福负责，但是时间hi上每个人都是独一无二的，没有可以完全照抄的标准答案。两个人都觉得好的亲密关系，需要两个人都清晰的知道自己要的是什么，自己对高质量的亲密关系有什么期待。然后根据期待，为亲密关系指定目标和规则，用心践行，对彼此的变化保持敏感，当变化来临时，也能通过沟通，灵活调整新的目标和规则。要把自己作为自己幸福的第一负责人，也要互相为对方的幸福负责，换句话就是独立与亲密共存。成长的核心是认识与接纳，认识你自己，接纳你自己。认识亲密关系，也接纳爱与或不爱的现实。好奇心好奇心是一个人内在的驱动力，相比于优绩注意等外界压力的鞭策，好奇心是一种探索和享受。要知道人类是叙事的产物，我们通过叙述自己和周围人的故事为人生赋予意义——我们的身份认同是主观的记忆和叙事影响的，而不是客观事实，同时这样的叙事也会影响我们对未来的选择。如果怀有好奇心，那么对你的记忆和叙事就依然具有改变的可能性。好奇心会让我们关注危险的同时，唤起对未知和对未来的憧憬。好奇心还会提升人际关系质量，因为好奇心会让我们更愿意了解彼此，更主动探索、观察和倾听与提问。比如，在交流时，自恋或者自卑会让人更多去表现自己或者关注对方对自己的评价，但是好奇心会让人更多地通过提问来了解对方。两个人这样做的时候，交流的双方也会感受到更多的情感流动。如果是一些提问是为了刺探隐私或者其他自私的意图，例如一些封闭性问题，可能只是问了引导你说出某些话，或者进行一些负面评价，则不是好奇心引起的。因为真正的好奇心是探索式提问，会更多采用开放性问题。人天生就具有扩展自我认知、能力和经验的内在动机，如果好奇心减少，没那么就越可能依赖自己固有的思维定势来认识他人；更麻烦的是，当一个人缺乏好奇心，容易把与自己信念不一致的信息视为一种威胁，于是会封闭自己的思维，抗拒接纳与自己相反的意见甚至连倾听都很难做得到。缺乏好奇心可能会演变成为”各说各话“的孤立状态。然而好奇心已经越来越稀缺，我们在研究上会把好奇心分为两类：一个是消遣好奇心：表现为新鲜刺激的广泛兴趣（如刷社交媒体，追求娱乐信息等）目的是缓解无聊，或寻求感官刺激，具有短暂和分散的特点。另一个是认识性好奇：指向深度理解的求知欲（如钻研一个问题直至弄懂），与学习动机和求知成长直接相关，具有持久和目标导向性的特点。除了基因、财富和社会地位等，好奇心其实也是一种代际传递的资本。因为孩子的好奇心需要爱作为保障，所以也需要重要养育者的意识和能力做保障。社会环境中，为了自身利益，很多商业一直在巧取豪夺我们的注意力，精准剖析我们的欲望和弱点，精心设计算法，制造信息茧房，让我们沉沦于即时的快感、依赖消费来满足情绪需求和关于自我的一切。关于自我，其实有两种主要的驱动力：我想知道我是谁；和我想我自我感觉更良好。这两者都被商业市场扭曲和利用了。当然很多组织也在打压认识性好奇，因为探索会带来风险，占据各种资源，而且还不一定成功。要培养认识性好奇，需要克制诱惑和依赖。对于人类来说，基本、适度的权力也因此很关键，因为权力来源于对稀缺且有价值资源的掌控。但是有时候，”缺爱“会阻碍一个人发展对金钱的认识和掌控力。跨越阶层的过程也很容易影响这一点。这个社会环境缺少对人、对生命的基本尊重，以”金钱“来衡量人的价值并给予相应的对待时，那么对失去”尊重“的恐惧便会转化为对缺少”金钱“的恐惧。病态的社会环境就会造成病态的金钱态度，让人让人很难掌握并且用好金钱这个被普遍性资源来发展认识性好奇。最后讲下如何培养认识性好奇：我们的心智活动可以分为三个系统：自主心智、算法心智和反省心智自主心智：直觉和本能，包括过往经验的累积和内化学习后的结果。特点是快速、自动化、不需要额外的思考，主要负责我们的日常习惯、本能反应和情绪化决策。算法心智：理性思考和计算，或者说至少是意识层面的思考和计算。特点是相对较慢、逻辑化、需要认知努力反省心智：关注自己的目标，基于与目标相关的信念和各种信息做出最优化行动。和大部分环境、组织与关系并不欢迎认识性好奇一样，反省心智一样不受欢迎，因为反省心智让人独立自主且不可预测。权力者会通过叙事来不断驯化你的自主心智和反省心智，直到权力低位者本能遵守他们的规则并实现他们的目标。规则和目标是有价值的，可以提供思考和行为的框架，避免茫然和明显错误的选择，但是盲目遵守会制约我们的深度思考和自由空间。探索和行动的过程中，让我们意识到自己的是思考模式和行为模式，以及背后的动力，这个就是认识性好奇的实践过程，让我们实现自我人生的意义。
《曹雪敏》的这本书很值得一品，很多视角都在引领我们从现代角度，贯穿原生家庭、性别偏见、权力关系这几个不同视角，从社会、父母到两性，诠释了为什么爱情变得困难，怎么样爱自己。正如作者所说：所有关系的背后，都是我们与自己的关系，先让自己成为发光的人，再考虑要不要投入一段感情。不爱了，正在成为一种时代情绪统计数据呈现出来的“不爱了”可能是一种主动的选择，是人自由意志的体现，不再盲目踏入爱情的陷阱，不再盲从“人生就要结婚生小孩”这样的传统剧本，这无疑是一种进步。原生家庭为什么我们不爱了，因为爱的主体是人，一个人的童年经历会影响他成年后的自尊、风险感知、择偶策略、养育后代的风格等。情感创伤：就是我们经历过的但是还没有释怀的失望与绝望。创伤会让人丧失对未来的想象力和创造力，使得大脑自动关闭了对其他可能性的想象和探索。如果以扭曲的自我概念和解释方式来理解自己和世界，以伤害性的模式和他人、和这个世界发生互动，那么创伤带来的影响是深远的。个人边界被侵犯，会破坏一个人的“公正世界假设”——不再认为世界是可预测的、可信任的，对世界没有安全感，就容易丧失好奇心和探索的勇气。而贫穷会放大所有危险因素带来的影响。如何改变这种状态：不再扮演受害者的角色，愿意自我负责。自我负责不是把一切结果都归于自己，也不是“只靠自己”来孤立地寻求改变，而是分离课题后，专注于自己的课题和目标。找到你的自由：分辨可以改变的和不能改变的，接纳改变不了的，改变可以改变的。模仿——要知道好的相处方式是怎么样的，并且勤加练习。接纳自己在成长过程中地反复，做好迎接挑战甚至痛苦的准备。改变不一定会让你收获大团圆结局，所以还要拥抱开放的结局。有时候，一段关系中如果的确没有爱，也的确没有爱的意愿和爱的能力，那么“不爱了”反而是“爱自己”的选择，“爱自己”才是每个人无论如何都要追求的过程和结局。性别偏见性别偏见：指的是基于性别的刻板印象和不公平对待，不仅是个人的观点或态度，更是根植于文化、传统、社会结构和制度之中的系统性问题。在性别偏见下，男性对女性的“爱”的体验和行为，也许只是一种情绪、一种冲突或者是一种策略；反之亦然，女性对男性的“爱”，或许是男性的外在、霸道、财富和地位等。当人看到对方的真实性，知道对方的伪装、自私、卑劣和怯懦之后，可能会打破爱情的滤镜，这正是这个时代单身人士正在经历的觉醒，而女性尤为强烈，因为其受到的压迫更现实。女性在原生家庭中感受不到爱与尊重，忽视对未来的长期规划，忙于照顾他人，而且受到母父的控制，重复其人生。而逃离原生家庭之后，在与男性的交往中，很可能只是进入了男性剥削女性的另一个环境。不少男性在“爱情”中其实一直在自私地权衡利弊，当然女性也有可能有，不过追求的结果不同。因为男性在社会环境中容易形成固定性思维，只愿意待在被认可地舒适圈中，往往害怕挑战，遇到问题选择逃避，甚至把质疑当成挑衅，倒打一耙。其实婚姻和爱情是分离的，婚姻本质上是法律制度和契约，两个人结婚，是自愿接受制度赋予地权益和责任，是个人和国家制度的合作，也是国家制度对个人的管控，整个婚姻法中没有一条关于爱情的条例。婚姻和爱情是两回事，爱情是个人意志，是自由的；而婚姻是现实，也是法律和社会关系，有相应的限制、规则和功能。爱是关系的动力和目标之一，但不是必须的。现实生活中的关系相处需要至少不冲突的关系目标、相应的能力和人格基础，这些反而是必须的。婚姻从进化心理学角度来看，其实一直是统治者更需要婚姻，因为这能稳定社群，这点与费孝通的《生育制度》有所契同。而这些目的没有一项和爱情相关。女性的心理问题，来源于实际体验到的痛苦和文化引导的“爱情带来幸福”的故事不一样；而男性，则是因为实际体验到的挫折，和一直被告知的“男性天赋异禀、无所不能”的故事不一样。婚姻不见得带来幸福，很多时候只是上位者的利益诉求罢了。人最终都要直面人性，靠自己积累权力、武器和钱，用它们来管理人性，赢得自由和幸福。但是是否就对婚恋完全失望呢？不见得，因为人终会走向死亡，在死亡这一终极的孤独和毁灭面前，爱与被爱始终是救赎。不平等问题的背后其实都是在涉及权力关系，而权力和偏见一样，都是爱的敌人。权力关系权力——一种可以自己执行后无需考量他者想法与感受的特殊能力。当关系变成了权力取向的关系时，则全是由高位者的意志来决定关系的发展，我们常说的共识、尊重、理解、沟通和欣赏，通常都发生在平等取向的亲密关系中。而权力取向的亲密关系中，控制和支配是主要的底色。高位者会持续改变低位者的思想和行为来满足自己，同时抵抗低位者施加的任何带有企图的影响，让低位者觉得不公平又无力。如果从政治角度思考，那么就是：人会致力于去做对自己有利的事情，而不是对他人有利的事情。也就是对方不会关注你的利益，换句话说：如果他怎么对你，你都不会离开，甚至会继续对他好，那么他为什么要用行动爱你，对你好呢？要知道，权力高位者对伴侣就算情感上有爱，也会爱得越来越偷懒，优先级越来越靠后，最后从行动上来看就是“不爱”了。在充满了性别偏见、阶级偏见、种族偏见等各种偏见的社会文化中，一个人只要认同这些偏见，无论是受益方还是受害方，他可能就根本没法形成爱的能力。因为容易一旦受挫就找补偿，于是各种各样的通过控制别人来找回权力感的行为就出现了。而在浪漫爱情文化的影响下，一些女性对爱情中自己的权力怀有过高的期待。加上婚恋初期，尤其是追求期、生育前，男方和相关方可能会让女性感到被关注、被优待的权力感，这个时候就形成了权力感的巅峰期。但是在这之后，就开始了心理受挫的人生。其实权力的来源是对方的爱或情欲——权力只是暂存在爱或情欲的对象身上，你甚至无法控制，对方一旦感情下降或者消失，或者需求被满足，则这种权力就会被收回。家庭的控制也是源于此，在两性中找不到权力的感受了，就开始从孩子或者伴侣身上找补偿，追求作为家长或长辈的权力感。权力会给人一种控制感和地位感，控制感一直是人类的底层心理需求之一。当自我受到威胁时会让一个人依赖并拥护一个结构简单、能给他控制感和地位感的外部环境，这也被称为“泛化结构的确认”。世界很大，欲壑难填，所以要对高位者祛魅，因为欲望变高时，会选择追求完全的掌控，来满足自己受挫的、空虚的权力感。其实现代的社会环境对爱并不友好，因为偏见很普遍、价值观也很单一，再叠加经济环境和社交媒体的影响，人的权力感欲望和受挫感体验都在与日俱增，没有心力去爱。而更为麻烦的是，爱另外一个人本来就是一种冒险，会面临新的失控和受挫的风险，当人格和心理状态不稳定的时候，这个过程堪比受刑。关系稳定下来就容易爆发——通过无理由的作、闹和控制来补偿自我，最后伤害感情和关系，陷入恶性循环。那么怎么获取权力，而又不滥用权力呢？要掌握权力，在自我认知必须要有主题思想，简单来说就是“我要”以及“我可以”，要知道，权力彰显其威力的地方，并不是低位者对高位者表面上的服从，而是低位者内心深处发生的一种转变，低位者会吸收并认同高位者的渴望，从而将高位者的渴望内化为自身的渴望。更为严峻的是，低位者不仅会心甘情愿、带着优越感和意义感来遵循高位者的意志，甚至主动预先感知高位者尚未言明的想法，并将其作为自己内心自然而然产生的意志去践行。然而要知道，社会运转的体系确实不同，平权不是科学系统的目标，也不符合系统内各个团体的利益最大化。接下来聊关系认知，关于浪漫主义最危险的地方则是——过度相信爱情和关系，忽视了人性—趋利避害和享乐主义才是人类行为更为稳固的动机。在亲密关系中也是如此，关系想要长久、稳固且高质量，除了感情，双方还要保持势均力敌的权力，用好两个人趋利避害和享乐主义的动机。看清楚对方认为什么是利，什么是害，也就是了解对方的权力来源。然后用他想要的“利”来拉拢他，不想要的“害”来限制他、规劝他，也就是进行利益输送。但人也不全是趋利避害的理性动物，所以还需要观察他享受什么快乐会让他失去理性，提供这样的快乐作为正面激励，拿走这样的快乐作为负面反馈。要保持权力，最重要的其实不是对方如何，关系如何，而是自己——自己要不怕离开对方，也不怕离开婚恋关系。所以要进入一段长期的婚恋关系，一定要先有积极的自我认知，培养自尊感和权力感，始终有离开的底气和资源。在长期的婚恋关系中，爱是上限，人品是底线，权力是最好的支持方，也是最坏的迫害方。要知道——爱只存在于权力平等的关系之内。所以大家会发现：婚恋关系的权力管理不亚于职场中的勾心斗角，但是回报却不一定比得上职场，甚至风险比职场还大。因为婚恋中，处于低位的人，对人生和心理都是消耗，而且越深入了解，对方掌握信息越多，那么造成的伤害也就越大。越是长期的婚恋关系，越会受到来自文化、外界、人性等方方面面的考验。只有直面这些威胁和阴暗，并找到应对它们的方法，才能让爱成长，不被扭曲甚至毁灭。接下来谈一下权力关系中的直接显现：冷暴力冷暴力指的是通过冷淡、轻视、谩骂、疏远和漠不关心等方式，致使他人的身心受到侵犯和伤害。冷暴力的内核是施虐者对受虐者情感上的淡漠。迷惑性：一种是实行功能上的职责，但是没有精神陪伴。不同于校园暴力、职场暴力，婚恋关系和亲自关系由于功能性职责的履行，很容易让受虐者在认知上觉得关系似乎一切正常，但是感受上却又矛盾且挣扎，最后否认自己的感受失去自我。另一种则是讨好，因为讨好本质上是一种欺骗，讨好者隐藏了自己的需求和想法，用不真实的自我营造出了一段虚假的关系。两个人则很难真心交流，情感上实际也是淡漠的，关系很难深入发展。表现为“好的不够真实”。长期反复的冷暴力实际上是一种有毒的慢性压力，面对这种情况，如果还能产生愤怒，则需要把攻击性逐级释放，从内耗自己满足对方，演变为爱护自己，让自己略微舒服点满足对方，到最后撤离，专心爱自己。冷暴力会摧毁我们的自尊调节能力，自尊本质上是一种感觉，高自尊指的是有自己的自尊调节，能察觉到自己对自我的感觉，还有相对稳定的自我评价体系。有成长型思维，可以通过失落看到自己内心的渴望，在渴望中找到改变的动力，激励安慰自己。而低自尊，要么完全诋毁自己，要么认为全是外界的错。所以要避免“煤气灯效应”，也即通过扭曲受害者眼中的真实来控制对方的情感操控，施虐者命名违背了诺言，但是却说自己没说过，是你记错了，混乱受害者的记忆和思维。冷暴力的动机：一种非恶意的动机是自动反应，因为冲突对于施虐者来说，意味着精神生命在遭受威胁、爱在消失，所以一旦发生冲突，他们整个人都在被恐惧、失望、无助和攻击性等情绪轰炸，所以陷入了木僵状态，不肯直面冲突，做好冲突管理。背后或许有创伤应激，行为表现为冷暴力，内核却是一种过度防卫，是内心脆弱的一种反映。如果冲突背后的问题一直得不到解决，那么关系质量就提升不了，最后冲突愈发频繁，爱也就被消磨了。这样的受虐者需要一直跟对方整个家族的创伤相处，磨损很严重。对方的课题应该由对方自己解决，而不是受虐者的课题。而恶意的动机，则是优越感在作祟，施虐者在受虐者值得肯定的地方嫉妒的发狂，所以身上潜在的自卑被激发了。还有一种冷暴力是养成类型的，双方互动中，施虐者做错了事情，但是不认错，受虐者还沉醉于浪漫爱情文化叙事结构中，所以互动中形成了权力关系的不对等，做错的人站在了高位，加剧了冷暴力。那么遭遇了冷暴力，我们该怎么办？勇敢直面冷暴力，全面认识，看清楚对方，即便有冲击，要通过新的认知和支持系统，疗愈自己。寻找支持系统，找到能安抚自己情绪、让自己感到安全和自信的支持系统。思考和行动，其实共情能力强、以忠诚为价值观、习惯向内归因、指责自己、对外却攻击性弱的人，往往才是容易陷在冷暴力中的人。与其当救世主，不如直接指出对方错在哪里、你想要对方怎么做。因为爱和关系本来就能容纳攻击性。尤其在亲子关系中，允许孩子恨自己，是家长给好孩子最伟大的爱。当出现冷暴力时其实就是不爱了，不要高估对方的感情和爱的能力，也不要高估自己的承受力，试图改变对方和关系。人性矛盾——出轨在现实生活中，其实出轨的评论更多的是道德批判，出轨的恐怖之处在于，你不知道该信任什么了，所有的过去叠加“出轨”这个事实之后，开始变得清晰可见而又扑朔迷离。根据数据统计，随着网络和市场发展，出轨率只会上升而不会下降，因为色欲和情欲始终是强有力的消费动力。色欲和情欲最接近本能和本我，让人忘记现实，直接感受到最纯粹的冲动、存在感和价值感。而且现代社会中所谓的受害者有罪论——不被爱才是第三者，将爱情进行了内外狙击。出轨的推动力实际上是人的需求，而这些需求和爱有时候并不互相排斥，因为爱是一种情感，而人有多重角色下的各种需求，情感本身不能满足一个人的所有需求。比如我们自己爱自己，但是仍旧会有欺骗自己，背叛自己的时候。很多被出轨者寄希望于出轨者负起责任，但是负责是一种态度，更是一种能力，当对方出轨的时候其实已经是一种失控，背后可能有生理需求，也可能是心理需求，在没有找到健康的方式满足需求之前，即便他有意识想要改变或者负责，但是也可能是做不到的。总而远之：人在关系里做不了完整的自己，便可能通过出轨来寻找缺失的自我。如果想要避免，则需要在遇见之前就建立有不错的满足需求的方式，这样在一起之后便可以更专注地面对新的需求。出轨者需要认识到自己的需求，找到满足自己需求的方式，培养管理需求的意识和能力，才能结束这个状态。爱是否必然与独占、嫉妒挂钩？这个未必有标准答案。同样的，人的需求是复杂的，一位伴侣，一段关系就真的可以满足一个人的所有需求嘛？未必，面对个体的局限，复杂的需求，是否有其他办法解决呢？也未必有标准答案，所以如果想要寻求长期关系，则需要坦诚地面对这个问题，互相聊聊。因为即便是开放式关系，也解决不了这个问题。性与爱的关系是复杂的，爱是看向对方，满足对方；而性是看向自己，取悦自己。亲密关系其实也会给性生活带来挑战，因为亲密关系是多重角色的互动，彼此尊重、欣赏、压力和内疚等都会影响性欲；性需要放下所有身份角色的限制，听从身体最原始的欲望，当两个人的性欲节奏和性癖好不一致的时候，这种挑战则会更大。出轨也能导致权力不对等，如果你害怕背叛，那么就拥有不了这段感情，因为一旦你遇见了且开始退让，则是权力不平等的加剧，也是对自身自尊的一次重创。除了权力方面，还有注意力偏差，焦虑型依恋和低自尊的人都容易发生负面注意和信息误读，当反复发生放大时，两个人都不开心，关系质量不好，那么出轨概率也会提高。其实——好的亲密会让人变得更为独立；反过来，好的独立也会让人拥有更好的亲密。对归属感、存在感和价值感的寻求，是安全感的本质，但是实际上应该把安全感放在自己身上，不依赖对方和这段关系。因为一旦沉迷于这个安全感的追寻，则会停止对自己、对对方、对生活的探索，对各种环境变化和需求变化视而不见，结果两个人的需求都得不到回应。遇到出轨要怎么思考，应该从趋利避害和享乐主义的视角去看待，不要把自己和被出轨的对象做比对，而是考虑出轨者的动机，然后尽可能把注意力放回自身，疗愈自身受伤点。受伤点大致可以分为四种：关系受伤、自恋受伤、自尊受伤和信念受伤。关系受伤：失去对方、失去关系，惋惜过去的美好回忆和计划的美好未来，在情绪上难过高于愤怒。可以通过仪式进行哀悼，改掉两个人在一起的习惯和依赖，不断回归自我。自恋受伤：质疑自己的魅力和对方的眼光，和第三者疯狂对比，痛苦的是失去面子。短期内要破除自己不行的想法，长期来要避免进入优越感的依赖，因为优越感会伤害各种关系。自尊受伤：留在这段关系但是毫无改变，失望的是自己背叛了自己。短期内要多和欣赏自己的人在一起，找回自我。长期要理解人性、情绪和压力管理的能力、沟通技能，更新自己对亲密关系和人生的打算。信念受伤：不相信爱情，不相信忠诚和利他，觉得人都是自私自利的，无非是暂时的权衡利弊和诱惑不够大导致的表面功夫。短期来看是能带入走出这个关系，但是长期会丧失建立亲密关系的能力和机会，关键是对人和世界丧失积极乐观的信念，可能变得冷漠和空虚。需要多方学习，在思辨中找到独属于自己人生的答案。出轨者的道歉——一定要坦诚，宁愿接受关系破裂，也不要冒险撒更多的谎言。道歉的关键并不是你做了什么、错在哪儿，而是你做的给对方造成了哪些伤害和后果，要集中在关系手上中的公平问题和责任问题上。如果道歉，要尽可能理解被出轨者的受伤点，让被出轨者知道你理解他因此承担和承受了什么，你理解他的情绪和各种反应。而是否继续这段关系，因为存续关系本身就是一次冒险，想要继续的话，需要确保自己有冒险的底气和能力，比如不怕对方的背叛。如果害怕就不要。然后就是分析问题，如果能满足需求，只是警报，则可以坐下来谈谈；如果是丧钟，那就要接受对方不爱了这个事实，反复直面各种不爱了的表现和细节，直到涌现出绝望和哀悼的情绪，这样才能结束这段关系，放弃虚幻的希望。要知道——长期关系中的主动忠诚其实是很难也很幸运的一件事，其实不是只有出轨是背叛，所有让两人远离亲密关系的行动都是背叛。很多时候，有爱的关系走向出轨，是无数个细小的背叛累积的结果。相比关系中的彼此忠诚，其实更要对自己忠诚，不要背叛自己，守护自己的健康，践行自己的价值观、捍卫自己的权益、照顾自己的情绪和情感需求。爱自己、对自己忠诚，是我们靠自己就能把控的事情。中年危机传统意义上的中年危机不但提前了，还扩散了，全年龄段都在遭遇身心上的危机。这是健康问题、信息爆炸和经济问题等多重冲击下的结果。健康问题让我们提前面临衰老和身心的失控，信息爆炸又冲击着我们的自我认同感、人生意义感和希望感。经济问题又加剧我们的各种不甘、无奈甚至是绝望。我们身上的角色越多，这些冲击就越多。因为角色越多意味着我们面临的取舍就越多，每个人的时间、精力、金钱有限，自己的快乐和角色的责任也经常冲突，结果难免面临自己和亲密之人的无奈与失望。中年危机会让人逐渐失望、恐惧甚至绝望，而爱需要敞开自己去冒险和相信，所以如果丧失了探索欲和创造力，也就失去了爱自己、爱他人、爱世界的意愿和能力。所以在危机之下，我们如果能主动探索：除了我过去所经历的、所获得的、所成就的一切、除了我所扮演的那些角色、我还是谁？我究竟是谁？才能把危机转化为新生。这场旅程中会遇到各种打破你意识观念的事情，例如：意识到父母可能并不爱你，并且永远不会承认他们对你造成的伤害并为此感到抱歉。再比如，意识到这个世界对性别、阶层如此不公平，需要头破血流去做斗争，才有好转的希望。这一路都是丧失，丧失依靠，丧失希望。会发现拥有的无非是幻想，我们要依靠自己走下去，但是其实我们也经常辜负自己，这样也更令人难过。但是人生没有所谓的“应该版本”，放下执念，自己无非是人生无常的一个经历者，这样注意力才能回到当下，关注眼前的人和事，才能去爱，去生活。感恩练习——注意并记住自己拥有的；哀悼练习——直面并接纳自己失去的。中年之路的另一个重要的心理课题是——错误与承担，错误要承认，因为逃避只会循环往复犯错。好的认错其实一直是一件很需要勇气和智慧的事，如果直面了糟糕的结果，但是无法承担背后的责任，内在的压力就会激增。面对无法挽回的错误，最终只能释怀，要试着更好地归因——不要极端向内归因，也不要极端向外，要尽可能接近现实。在这个基础上要做到正确认识自我，要有明确的自我概念：现实自我：个体实际所具有的特性，是个体对自己实际情况的认知。理想自我：个体期望自己拥有的特性，是个体对自己未来状态的期望和目标。应该自我：个体认为自己应该具有的特性，是基于责任、义务等产生的对自己的要求。世俗文化将女性的理想自我框进了婚恋生育中，对女性的应该自我提了很多费时费力、遥不可及、服务于男性的要求，所以女性的现实自我很难在实际生活中体验到自我认同感；男性也是这样，自我概念的应该自我和理想自我都是无所不能和光宗耀祖，但是现实自我却屡屡碰壁。——两性都渐渐找不到“我是谁”以及“我为什么或者”的答案。这里还有优绩主义社会文化的影响，优绩主义文化是一种以绩效、成果和成就为核心衡量标准的文化理念。意思就是只看比较和结果，不看价值观和过程。一旦陷入优绩主义，就容易丧失自我，而且也容易养成固定型思维。认同优绩主义的人，经常互相喂养优越感，所以一旦在世俗意义上跌落，人机关系也就破灭，优越感也就失去了。要认识到：价值观是自我意识的重要组成部分。只有当理想自我和应该自我的要求符合我们的价值观，并且我们可以靠自己的努力和环境的帮助，让现实自我不断接近理想自我和应该自我的时候，一个人的自我概念才能整合且稳定。但是，现在有很多没有价值观的空心人，或者自己真正认同的价值观被层层枷锁锁住了。在优绩主义文化盛行的当下，只有结果，只有成功和失败，就很难有多元价值观发展和践行的空间。如果在中年危机的冲击下，有那么一个人可以无条件接纳你，那么无疑是幸运的。但是如果没有，就要主动承担起整理自己人际关系的责任。其实“婚姻不是人的第二次投胎”，经济独立以后，“第二次投胎”已经开始了，因为这时候已经有了选择人人际环境的基础。要交流我们的情绪，而不是带着情绪去交流。交流自己的情绪，需要正念，意思就是要体察自己的感受，为情绪命名，看到情绪背后的想法、需求和价值观，用见证者的角度而不是评判者来看待这一切。不要以旧目标——对情绪的指责来实现，这样会带来压力；而是接纳情绪，从根本上接受这种情绪可能会一直存在，在这个基础上不断探索自我关怀和实现价值观的方式，更为灵活。总之，中年之路既是拥抱并肯定过去的自我曾付出的努力，也是宽容此刻依然不完美的自我和世界，然后在彻底“不爱了”的失望和绝望中，看清并接纳虚假的表象和真实的本质，不再爱表象的结果和标签，而是爱真实的自我和世界，并且用行动去实践不爱，也实践爱。
Async注解与虚拟线程现代Spring应用程序都面临着同样的难题：如何在不消耗过多CPU资源或阻塞线程的情况下，快速运行大量任务？提到并发处理，我们经常听到三个概念：虚拟线程、线程池和 @Async注解。实际上这三者是完全不同的东西，起作用的地方也不同：• 虚拟线程（Virtual Threads）：JVM的一种执行模型• 线程池（Thread Pools）：资源管理器• @Async注解：Spring的任务路由工具，将工作分发给你选择的执行器理解它们的区别对于应用的延迟、云成本和可靠性至关重要。选择错误可能导致P95延迟飙升、队列堆积和超时问题。虚拟线程虚拟线程它们在阻塞I/O操作时能够快速地地暂停和恢复。核心优势• 高并发：可以创建数百万个虚拟线程• 低成本：内存占用极小（约8KB vs 传统线程的2MB）• 高效I/O：在I/O阻塞时自动让出CPU资源适用场景// 适合：大量I/O密集型任务 @Async public CompletableFuture<String> fetchDataFromAPI(String url) { // 网络请求、数据库查询等 return restTemplate.getForObject(url, String.class); }线程池线程池是一种资源管理策略，通过预先创建固定数量的线程来处理任务，避免频繁创建和销毁线程的开销。配置示例@Configuration public class AsyncConfig { @Bean(name = "taskExecutor") public TaskExecutor taskExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(10); executor.setMaxPoolSize(50); executor.setQueueCapacity(100); executor.setThreadNamePrefix("async-task-"); executor.initialize(); return executor; } }@Async注解@Async是Spring提供的任务路由器，它将方法调用转发给指定的执行器处理。基本用法@Service public class UserService { @Async("taskExecutor") public CompletableFuture<User> processUser(Long userId) { // 异步处理逻辑 User user = userRepository.findById(userId); // 耗时操作... return CompletableFuture.completedFuture(user); } }配置虚拟线程执行器要使用虚拟线程功能，需要满足以下版本要求：• Java 21+：虚拟线程正式版本• Spring Boot 3.2+：完整支持虚拟线程配置• Spring Framework 6.1+：底层框架支持@Configuration @EnableAsync public class VirtualThreadConfig { @Bean(name = "virtualThreadExecutor") public TaskExecutor virtualThreadExecutor() { return new TaskExecutorAdapter( Executors.newVirtualThreadPerTaskExecutor() ); } @Bean(name = "fixedThreadPool") public TaskExecutor fixedThreadPool() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(20); executor.setMaxPoolSize(40); executor.setQueueCapacity(200); executor.setThreadNamePrefix("fixed-pool-"); executor.initialize(); return executor; } }Service中使用@Service public class DataProcessingService { // 使用虚拟线程处理I/O密集型任务 @Async("virtualThreadExecutor") public CompletableFuture<String> fetchExternalData(String url) { // 网络请求逻辑 return CompletableFuture.completedFuture( restTemplate.getForObject(url, String.class) ); } // 使用传统线程池处理CPU密集型任务 @Async("fixedThreadPool") public CompletableFuture<Integer> calculateHash(String data) { // CPU密集型计算 return CompletableFuture.completedFuture( data.hashCode() * complexCalculation(data) ); } }注意事项虚拟线程使用陷阱避免在虚拟线程中使用synchronized• 可能导致平台线程被固定（pinning）• 推荐使用ReentrantLock谨慎使用ThreadLocal• 虚拟线程数量巨大，可能导致内存泄漏性能监控@Component public class ThreadMonitor { @EventListener public void handleAsyncTaskExecution(AsyncTaskExecutionEvent event) { log.info("Task executed on: {} thread", Thread.currentThread().isVirtual() ? "virtual" : "platform"); } }总结在Spring应用中选择合适的并发模型需要考虑以下几点：虚拟线程 vs 线程池：如何选择？对比维度虚拟线程传统线程池适用任务类型I/O密集型任务CPU密集型任务并发能力支持数百万并发受限于线程池大小内存占用极低（约8KB/线程）较高（约2MB/线程）创建成本几乎为零有一定开销阻塞处理自动让出CPU阻塞整个线程资源控制难以精确控制可精确控制并发数适用场景• 微服务API调用 • 数据库查询 • 文件读写 • 网络通信 • 高并发请求处理• 复杂计算 • 图像处理 • 数据分析 • 需要限制并发数 • 对资源有严格控制要求虚拟线程并不是银弹，传统线程池也有其存在价值。关键是要根据具体场景选择合适的工具，并通过@Async注解灵活地进行任务路由。小贴士：在生产环境中，建议同时配置虚拟线程和传统线程池，针对不同类型的任务使用不同的执行器，以获得最佳性能表现。
OpenResty、Tengine、NginxOpenResty官网地址：https://openresty.org/cn/OpenResty® 是一个基于 Nginx 与 Lua 的高性能 Web 平台，其内部集成了大量精良的 Lua 库、第三方模块以及大多数的依赖项。用于方便地搭建能够处理超高并发、扩展性极高的动态 Web 应用、Web 服务和动态网关。OpenResty® 通过汇聚各种设计精良的 Nginx 模块（主要由 OpenResty 团队自主开发），从而将 Nginx 有效地变成一个强大的通用 Web 应用平台。这样，Web 开发人员和系统工程师可以使用 Lua 脚本语言调动 Nginx 支持的各种 C 以及 Lua 模块，快速构造出足以胜任 10K 乃至 1000K 以上单机并发连接的高性能 Web 应用系统。OpenResty® 的目标是让你的Web服务直接跑在 Nginx 服务内部，充分利用 Nginx 的非阻塞 I/O 模型，不仅仅对 HTTP 客户端请求,甚至于对远程后端诸如 MySQL、PostgreSQL、Memcached 以及 Redis 等都进行一致的高性能响应OpenResty其实就是一个基于Nginx的Web平台，但它最牛的地方在于集成了LuaJIT和很多精心编写的Lua库。简单来说，就是让你可以在Nginx里面写Lua代码，这听起来可能没什么，但用过的人都知道这有多香。比如，你产品经理突然要求在用户访问某个页面时，根据用户的地理位置显示不同的内容。如果用传统的方式，要么改后端代码重新发布，要么搭建一套复杂的CDN规则。但用OpenResty，我直接在nginx.conf里写了几行Lua代码就搞定了：location /api/content { access_by_lua_block { local ip = ngx.var.remote_addr local geo = require "resty.geoip" local country = geo.lookup(ip) if country == "CN" then ngx.var.backend = "china_backend" else ngx.var.backend = "global_backend" end } proxy_pass http://$backend; }这种灵活性是原生Nginx根本做不到的。你想想，如果每次这种小需求都要动用整个开发流程，那得多麻烦。OpenResty的安装也很简单，在Rocky Linux release 8.10上直接用yum就行：sudo yum install yum-utils sudo yum-config-manager --add-repo https://openresty.org/package/centos/openresty.repo sudo yum install openresty装完之后，你会发现它的目录结构和Nginx差不多，配置文件还是在/usr/local/openresty/nginx/conf/nginx.conf。但是多了很多Lua相关的模块，比如lua-resty-redis、lua-resty-mysql这些，基本上常用的都给你准备好了。#这是我修改后的文件 [root@webtest ~]# cat /usr/local/openresty/nginx/conf/nginx.conf worker_processes 1; error_log logs/error.log; events { worker_connections 1024; } http { server { listen 8080; location / { default_type text/html; content_by_lua_block { ngx.say(" hello, world ") } } } }Tengine官网：https://tengine.taobao.org/Tengine是阿里巴巴开源的Web服务器，基于Nginx开发。说到阿里的技术，那肯定是在高并发场景下被虐过千百遍才开源出来的，所以在性能和稳定性方面确实有两把刷子。安装官网下载编译安装包，上传到服务器[root@webtest ~]# tar -xf tengine-3.1.0.tar.gz [root@webtest ~]# cd tengine-3.1.0 [root@webtest ~]# ./configure [root@webtest ~]# make [root@webtest ~]# make installTengine最让我印象深刻的是它的动态模块加载功能。以前用Nginx，如果要加个模块，必须重新编译整个服务器，这在生产环境简直是噩梦。但Tengine可以动态加载模块，就像这样：dso { load ngx_http_lua_module.so; load ngx_http_geoip_module.so; }重启服务就能生效，不用重新编译。这个功能在我们需要快速响应业务需求的时候特别有用。还有一个让我很喜欢的功能是Tengine的健康检查。原生Nginx的upstream健康检查功能比较弱，基本上就是被动检查，后端服务挂了还要等用户请求失败才知道。但Tengine提供了主动健康检查：upstream backend { server 192.168.1.10:8080; server 192.168.1.11:8080; check interval=3000 rise=2 fall=5 timeout=1000 type=http; check_http_send "HEAD / HTTP/1.0\r\n\r\n"; check_http_expect_alive http_2xx http_3xx; }这样配置后，Tengine会每3秒主动检查一次后端服务器的健康状态，发现问题立即摘除，用户基本感知不到。实际使用用OpenResty做API网关，用Tengine做静态资源服务器和负载均衡器。OpenResty常用模块参考：https://www.cnblogs.com/phpworld/p/10969297.html用OpenResty做API网关，比如我们需要做接口限流，传统方式要么在应用层做，要么用Redis + Lua脚本。但OpenResty直接内置了限流模块：location /api/ { access_by_lua_block { local limit_req = require "resty.limit.req" local lim, err = limit_req.new("my_limit_req_store", 200, 100) if not lim then ngx.log(ngx.ERR, "failed to instantiate a resty.limit.req object: ", err) return ngx.exit(500) end local key = ngx.var.binary_remote_addr local delay, err = lim:incoming(key, true) if not delay then if err == "rejected" then return ngx.exit(503) end end } proxy_pass http://backend; }这样就实现了每个IP每秒最多200个请求，突发100个请求的限流策略。不过OpenResty也有一些坑，主要是Lua代码的调试比较麻烦。不像应用代码可以打断点单步调试，Lua代码基本上只能靠日志调试。我一般会在关键位置加上这样的日志：ngx.log(ngx.ERR, "debug: variable value is ", ngx.var.some_variable)然后在error.log里查看输出。Tengine的话，我主要用它做七层负载均衡。它的负载均衡算法比Nginx丰富一些，支持一致性哈希、最少连接数等多种算法：upstream backend { consistent_hash $request_uri; server 192.168.1.10:8080 weight=3; server 192.168.1.11:8080 weight=2; server 192.168.1.12:8080 weight=1; }这种一致性哈希在缓存场景下特别有用，可以保证相同的请求总是路由到同一台服务器。选择建议选择建议的话，我觉得主要看使用场景：如果你需要在Web服务器层面做很多业务逻辑处理，比如API网关、动态内容生成、复杂的访问控制等，那OpenResty是首选。它的Lua生态很丰富，基本上你能想到的功能都有现成的库。如果你主要做负载均衡和静态资源服务，对稳定性和性能要求比较高，那Tengine是个不错的选择。特别是在高并发场景下，Tengine的表现确实比原生Nginx要好一些。当然，如果你的需求比较简单，就是做个简单的反向代理或者静态文件服务，那原生Nginx也完全够用，没必要为了用新技术而用新技术。注意细节部署这两个服务器的时候，有几个地方需要注意。OpenResty的话，记得要合理设置Lua代码缓存。在开发环境可以关闭缓存方便调试：lua_code_cache off;但在生产环境一定要开启，否则性能会很差。还有就是要注意Lua代码的内存使用。OpenResty使用LuaJIT，内存管理和标准Lua有些不同。如果你的Lua代码里有大量的字符串操作或者表操作，要注意及时释放内存：-- 及时设置为nil释放内存 local big_table = {} -- ... 使用big_table big_table = nil collectgarbage()Tengine的话，要注意动态模块的加载顺序。有些模块之间有依赖关系，加载顺序错了会导致启动失败。一般来说，基础模块要先加载，业务模块后加载。监控方面，两者都支持Nginx的标准监控接口，可以用nginx-module-vts或者stub_status模块。我一般会配置一个内部监控接口：location /nginx_status { allow 127.0.0.1; allow 10.0.0.0/8; deny all; stub_status on; }然后用Prometheus + Grafana做监控大盘，效果还不错。
数据库访问五大核心技术简介在项目开发时，数据库访问技术是必不可少的，它直接决定了数据持久化的效率、开发维护的便捷性以及系统整体的性能表现。本文对比五种主流数据库访问技术：JOOQ 以类型安全的 SQL 和数据库优先设计著称，适合复杂查询与性能敏感场景，但需接受代码生成和商业授权限制；MyBatis 通过轻量级框架平衡了 SQL 控制与开发效率，支持动态 SQL 和多数据库适配，但需手动管理 SQL 与事务，类型安全较弱。此外，其他常见技术如 JDBC（原生但冗余）、Hibernate/JPA（全自动化 ORM 但学习曲线陡峭）、Spring Data JDBC/JPA（简化 CRUD 但灵活性受限）也各有适用场景。JDBC技术JDBC（Java数据库连接）是Java中为数据库连接提供基础支持的核心API。它是一个底层API，可对SQL执行和结果处理提供直接控制。可以把JDBC看作是Java数据库编程领域的“汇编语言”——功能强大，但需要更多手动操作。如下示例：private final DataSource dataSource ; public User queryByJdbc(Long id) { String sql = "SELECT * FROM a_user WHERE id = ?"; try (Connection conn = this.dataSource.getConnection(); PreparedStatement stmt = conn.prepareStatement(sql)) { stmt.setLong(1, id); try (ResultSet rs = stmt.executeQuery()) { if (rs.next()) { return mapRowToUser(rs); } } } return null; } private User mapRowToUser(ResultSet rs) throws SQLException { User user = new User(); user.setId(rs.getLong("id")); user.setName(rs.getString("name")); // ... return user; }JDBC 适用场景性能关键型应用：需要最大程度控制 SQL 执行，以优化性能复杂查询：ORM 框架难以表达的复杂 SQL 查询遗留系统集成：需要与现有存储过程（Stored Procedures）交互简单数据访问的微服务：数据访问模式简单，无需 ORM 框架的额外开销。学习目的：理解数据库底层原理和 JDBC 核心机制JDBC 优势最大性能控制：直接执行 SQL，避免 ORM 框架的额外开销完整 SQL 功能支持：可使用数据库特有的优化和功能（如特定语法、索引提示）轻量级：内存占用小，启动速度快行为可预测：编写的 SQL 会被精确执行，无 ORM 的隐式行为通用兼容性：适用于所有支持 JDBC 的数据库（MySQL、Oracle、PostgreSQL 等）JDBC 劣势冗余代码多：需要手动处理连接、语句（Statement）和结果集（ResultSet）的创建与关闭手动对象映射：需自行将数据库行（Row）转换为 Java 对象（POJO）SQL 注入风险：若未正确使用参数化查询（PreparedStatement），易受攻击数据库移植性问题：原生 SQL 可能在不同数据库间不兼容（如分页语法差异）无内置缓存：需自行实现缓存策略（如使用 Redis 或本地缓存）注：Spring中，JdbcTemplate与JdbcClient（Spring 6+）通过模板化设计，将原生JDBC的冗余代码量降低70%，实现高效数据库访问。Hibernate技术Hibernate 是一个成熟的对象关系映射（ORM）框架，它通过面向对象编程的方式抽象化数据库操作。该框架能够自动处理 Java 对象与数据库表之间的映射关系，为面向对象开发者提供了一种更符合自然编程习惯的模型。private final SessionFactory sessionFactory ; public User queryByHibernate(Long id) { try(Session session = sessionFactory.openSession()) { User user = session.find(User.class, id) ; return user ; } }我们还可以使用HQL语句进行查询User user = session.createQuery("FROM User WHERE id = ?1", User.class) .setParameter(1, id, Long.class) .getSingleResult() ;Hibernate适用场景具备复杂实体间关系的复杂领域模型需要高级缓存和延迟加载策略的应用程序跨数据库兼容性需求熟悉 ORM 概念且愿意投入时间学习 Hibernate 的团队开发速度比精细调优性能更重要的应用程序Hibernate 优势自动对象映射：无需手动处理 ResultSet强大的缓存机制：内置一级和二级缓存延迟加载：仅在需要时加载数据，提升性能数据库无关性：最小代码修改即可切换数据库高级功能：条件查询、批量处理、连接池成熟的生态系统：丰富的文档和社区支持Hibernate 劣势学习曲线陡峭：配置和概念复杂（会话、事务、缓存）性能开销：ORM 层增加计算成本N+1 查询问题：配置不当可能导致低效查询内存占用高：相对来说对象图和缓存可能消耗大量内存Spring Data JPA技术Spring Data JPA 是基于 JPA（Java 持久化 API）构建的高层抽象框架，通常以 Hibernate 作为底层实现。它通过基于存储库（Repository）的数据访问方式，遵循“约定优于配置”原则，极大减少了样板代码的编写。// 实体定义 @Entity @Table(name = "a_user") public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id ; private String name ; // 其它字段 // getters, setters } // 定义Repository public interface UserRepository extends JpaRepository<User, Long> { } // Service private final UserRepository userRepository ; public User queryById(Long id) { return this.userRepository.findById(id).orElse(null); }Spring Data JPA 使用场景基于 Spring 的应用，希望采用一致的编程模型需要快速原型开发或实现标准 CRUD 操作的场景数据访问模式复杂度为简单至中等的项目团队更倾向于“约定优于配置”的开发方式Spring Data JPA 优势极简样板代码：通过 Repository 接口自动生成实现约定式查询：方法名自动转换为数据库查询深度整合 Spring 生态：与 Spring Boot、Security 等无缝集成多数据存储支持：轻松切换 JPA、MongoDB、Redis 等存储方案内置分页排序：直接支持常见操作，无需额外开发持续活跃开发：定期更新并新增功能Spring Data JPA 劣势查询控制受限：复杂查询可能需要自定义实现强依赖 Spring：与 Spring 框架高度耦合“魔法”行为：方法名约定可能导致混淆和错误性能开销：在 JPA/Hibernate 之上增加抽象层灵活性较低：针对特定场景的优化难度较大JOOQ技术JOOQ 采用了一种独特的方式：通过流畅的 API（Fluent API）生成类型安全的 SQL 语句。它将 SQL 视为一等语言，同时提供编译时检查和代码补全等优势。JOOQ 会根据数据库架构自动生成 Java 代码，确保应用代码与数据库结构始终保持同步。首先，引入依赖<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jooq</artifactId> </dependency>接下来，配置编译插件，该插件会生成对应表的类信息<plugin> <groupId>org.jooq</groupId> <artifactId>jooq-codegen-maven</artifactId> <executions> <execution> <id>generate-jooq-classes</id> <phase>generate-sources</phase> <goals> <goal>generate</goal> </goals> </execution> </executions> <dependencies> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.33</version> </dependency> </dependencies> <configuration> <jdbc> <driver>com.mysql.cj.jdbc.Driver</driver> <url> jdbc:mysql://localhost:3306/tx?serverTimezone=GMT%2B8&useSSL=false&characterEncoding=UTF-8&rewriteBatchedStatements=true&cachePrepStmts=true </url> <user>root</user> <password>xxxooo</password> </jdbc> <generator> <database> <name>org.jooq.meta.mysql.MySQLDatabase</name> <includes>a_user</includes> <excludes></excludes> <inputSchema>tx</inputSchema> </database> <target> <packageName>com.pack.db.generated.jooq</packageName> <directory>src/main/java</directory> </target> <generate> <pojos>true</pojos> <daos>false</daos> <records>false</records> <javaTimeTypes>true</javaTimeTypes> </generate> </generator> </configuration> </plugin>编译后，将生成包结构。最后，我们就可以基于生成的这些类进行相关的操作了，如果你设置生成了DAO，那么就更简单了，直接通过DAO进行操作。如下示例，通过DSL操作：private final DSLContext dsl ; public AUser queryByJooq(Long id) { return dsl.selectFrom(A_USER) .where(A_USER.ID.eq(id)) .fetchOneInto(AUser.class); }JOOQ 适用场景复杂分析查询和报表应用数据库优先（Database-first）开发模式需要精细控制 SQL 但又不愿放弃类型安全的应用拥有强大 SQL 专业知识的团队JOOQ 优势类型安全的 SQL：编译时检查可防止 SQL 语法错误数据库优先模式：生成的代码与模式变更保持同步完整 SQL 支持：可访问高级数据库特性与函数卓越性能：与原生 JDBC 相比开销极小强大工具链：IDE 支持自动补全与重构多数据库支持：兼容多种数据库系统JOOQ 劣势代码生成依赖：需要构建时进行模式自省商业授权限制：专业功能需付费许可学习曲线：API 区别于标准 ORM 框架模式耦合：数据库模式变更需重新生成代码对象映射有限：自动化程度低于传统 ORM构建复杂度：需额外配置代码生成步骤MyBatis技术MyBatis 是一款优秀的持久层框架，它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO（Plain Old Java Objects，普通老式 Java 对象）为数据库中的记录。首先，引入依赖<dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>3.0.5</version> </dependency>接下来，简单配置mybatis: configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl type-aliases-package: com.pack.db.domain最后，定义Mapper@Select("SELECT * FROM a_user WHERE id = #{id}") @Results({ @Result(property = "id", column = "id"), @Result(property = "name", column = "name"), @Result(property = "phone", column = "phone"), @Result(property = "idNo", column = "id_no"), @Result(property = "age", column = "age"), @Result(property = "email", column = "email") }) User queryById(@Param("id") Long id);你也可以通过XML定义具体的SQL。private final UserMapper userMapper ; public User queryByMyBatis(Long id) { return this.userMapper.queryById(id) ; }MyBatis 适用场景需要灵活控制 SQL：项目需要高度定制化 SQL 查询，且希望保持对 SQL 的直接控制复杂存储过程或动态 SQL：需要频繁调用数据库存储过程，或动态生成 SQL 语句多数据库兼容性：项目需支持多种数据库，且希望减少数据库方言差异带来的问题MyBatis 优势SQL 灵活性：直接编写 SQL，支持复杂查询、动态 SQL 和存储过程调用轻量级：无侵入式设计，学习成本低于完整 ORM 框架（如 Hibernate）性能可控：减少 ORM 的隐式查询和缓存开销，适合性能敏感场景结果集映射：支持灵活的 Java 对象与数据库表字段映射（如 XML 或注解配置）多数据库支持：通过方言配置适配不同数据库的 SQL 语法差异社区生态：成熟稳定，拥有大量第三方插件和工具支持MyBatis 劣势手动管理 SQL：需手动编写和维护大量 SQL，增加开发工作量类型安全较弱：SQL 与 Java 代码分离，编译时无法检查字段名或表名错误事务管理复杂：需显式配置事务（如 Spring 集成可缓解此问题）动态 SQL 繁琐：复杂动态条件需使用 <if>、<choose> 等标签，代码可读性下降缓存机制有限：内置一级缓存较弱，二级缓存需额外配置且易引发脏数据问题XML 配置冗余：大量 SQL 定义在 XML 文件中，维护成本较高（注解方式可部分缓解）
SpringBoot Jar包冲突在线检测1. 痛点背景在 Spring Boot 项目开发和运维中，Jar 包冲突是让开发者最头疼的问题之一：常见冲突场景类重复：不同依赖引入了相同的类，启动时报 ClassCastException 或 NoSuchMethodError版本冲突：同一个库的不同版本混用，行为不一致，线上才暴露问题日志混乱：SLF4J + Logback + Log4j 多个实现共存，日志输出异常驱动重复：MySQL 5.x 和 8.x 驱动同时存在，连接异常现有方案的局限性• mvn dependency:tree：只能编译期分析，无法反映运行时 classpath• IDE 插件：需要人工操作，无法自动化集成• 第三方工具：过重，难以嵌入 Spring Boot 应用考虑引入一个轻量、可嵌入、运行时可见的 Jar 包冲突检测工具。2. 技术方案设计核心架构运行时扫描 → 冲突检测 → 配置化建议 → Web 可视化 ↓ ↓ ↓ ClassLoader 规则引擎 模板系统 适配器 智能分析 变量替换 关键技术点1. 多环境 ClassLoader 适配public List<URL> getClasspathUrls() { ClassLoaderclassLoader= Thread.currentThread().getContextClassLoader(); List<URL> urls = newArrayList<>(); // 遍历所有 ClassLoader 层级 ClassLoadercurrent= classLoader; while (current != null) { if (current instanceof URLClassLoader urlClassLoader) { urls.addAll(Arrays.asList(urlClassLoader.getURLs())); } current = current.getParent(); } // Spring Boot LaunchedURLClassLoader 特殊处理 if (classLoader.getClass().getName().contains("LaunchedURLClassLoader")) { urls.addAll(extractFromLaunchedClassLoader(classLoader)); } return urls.stream().distinct().toList(); }2. 三维冲突检测算法// 类重复检测 Map<String, List<JarInfo>> classToJarsMap = newHashMap<>(); for (JarInfo jar : jars) { for (String className : jar.getClasses()) { classToJarsMap.computeIfAbsent(className, k -> newArrayList<>()).add(jar); } } // 版本冲突检测 Map<String, List<JarInfo>> nameToJarsMap = jars.stream() .collect(Collectors.groupingBy(JarInfo::getName)); // JAR 重复检测（基于签名） Map<String, List<JarInfo>> signatureMap = jars.stream() .collect(Collectors.groupingBy(this::generateJarSignature));3. 配置化规则引擎完全摒弃硬编码，通过 YAML 配置定义所有规则：conflict: advisor: rules: slf4j-logging: patterns: [".*slf4j.*", ".*logback.*", ".*log4j.*"] severity:HIGH advice:|3. 核心实现Jar包扫描器支持开发环境和生产环境的智能扫描：@Component publicclassJarScanner { public List<JarInfo> scanJars() { List<JarInfo> jars = newArrayList<>(); List<URL> urls = classLoaderAdapter.getClasspathUrls(); for (URL url : urls) { Stringpath= url.getPath(); if (shouldExclude(path)) continue; if (path.endsWith(".jar")) { // 扫描 JAR 文件 jars.add(scanJarFile(url)); } elseif (path.contains("target/classes")) { // 扫描开发环境类目录 jars.add(scanClassesDirectory(url)); } } return jars; } private JarInfo scanJarFile(URL url) { try (JarFilejar=newJarFile(newFile(url.toURI()))) { JarInfojarInfo=newJarInfo(); jarInfo.setName(extractJarName(jar.getName())); jarInfo.setVersion(extractVersion(jar)); // 扫描所有类文件 List<String> classes = jar.stream() .filter(entry -> entry.getName().endsWith(".class")) .map(entry -> entry.getName() .replace("/", ".") .replace(".class", "")) .toList(); jarInfo.setClasses(classes); return jarInfo; } catch (Exception e) { logger.warn("Failed to scan jar: {}", url, e); returnnull; } } }配置化建议生成器核心亮点：零硬编码，完全配置驱动@Component @ConfigurationProperties(prefix = "conflict.advisor") publicclassConflictAdvisor { private Map<String, RuleDefinition> rules = newHashMap<>(); private List<SeverityRule> severityRules = newArrayList<>(); publicvoidgenerateAdvice(List<ConflictInfo> conflicts) { for (ConflictInfo conflict : conflicts) { Stringidentifier= extractIdentifier(conflict); // 查找匹配的规则 for (RuleDefinition rule : rules.values()) { if (rule.matches(identifier)) { conflict.setSeverity(rule.getSeverity()); conflict.setAdvice(formatAdvice(rule.getAdvice(), conflict)); break; } } } } private String formatAdvice(String template, ConflictInfo conflict) { Map<String, String> variables = buildVariables(conflict); Stringresult= template; for (Map.Entry<String, String> entry : variables.entrySet()) { result = result.replace("${" + entry.getKey() + "}", entry.getValue()); } return result; } // 支持的模板变量 private Map<String, String> buildVariables(ConflictInfo conflict) { Map<String, String> variables = newHashMap<>(); variables.put("className", conflict.getClassName()); variables.put("conflictType", getConflictTypeText(conflict.getType())); variables.put("jarCount", String.valueOf(conflict.getConflictingJars().size())); variables.put("jars", conflict.getConflictingJars().stream() .map(jar -> jar.getName() + ":" + jar.getVersion()) .collect(Collectors.joining(", "))); variables.put("jarList", conflict.getConflictingJars().stream() .map(jar -> jar.getName() + ":" + jar.getVersion()) .collect(Collectors.joining("\n"))); return variables; } }前端界面 冲突详情 类名/Jar包名 严重程度 修复建议 4. 配置化规则系统规则定义语法conflict: advisor: rules: # 规则名称 database-driver: # 匹配模式（支持正则表达式） patterns: -".*mysql.*" -".*postgresql.*" -".*Driver.*" # 严重程度 severity:CRITICAL # 建议模板（支持变量替换） advice:|支持的模板变量变量名说明示例${className}冲突的类名或JAR名org.slf4j.Logger${conflictType}冲突类型类重复、版本冲突${jarCount}冲突JAR包数量3${jars}JAR包列表（逗号分隔）slf4j-api:1.7.36, slf4j-api:2.0.9${jarList}JAR包列表（换行分隔）用于详细展示${versions}版本列表1.7.36, 2.0.9严重程度规则支持多维度匹配条件：severity-rules: # 关键组件 - 严重 -patterns: [".*logger.*", ".*driver.*", ".*datasource.*"] severity:CRITICAL conflict-types: [CLASS_DUPLICATE, VERSION_CONFLICT] # 框架组件 - 高 -patterns: [".*spring.*", ".*hibernate.*"] severity:HIGH conflict-types: [VERSION_CONFLICT] # 大量冲突 - 中等 -min-jar-count:4 severity:MEDIUM conflict-types: [CLASS_DUPLICATE]5. 实战效果展示检测结果示例假设项目中存在以下冲突：依赖配置：<dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.8.16</version> </dependency> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-core</artifactId> <version>5.8.19</version> </dependency>检测结果：{ "conflicts":[ { "className":"cn.hutool.core.util.StrUtil", "type":"CLASS_DUPLICATE", "severity":"MEDIUM", "conflictingJars":[ {"name":"hutool-all","version":"5.8.16"}, {"name":"hutool-core","version":"5.8.19"} ], "advice":"工具库冲突...\n解决方案：\n1. 选择一个 hutool 版本\n2. 排除传递依赖..." } ], "summary":{ "totalJars":45, "conflictCount":1, "scanTimeMs":127 } } #### Web 界面效果 **概览面板**：总 JAR 数、冲突数量、扫描耗时 **严重程度分布**：CRITICAL/HIGH/MEDIUM/LOW 分类统计 **详细列表**：类名、冲突类型、涉及 JAR、修复建议 ### 6. 企业级应用场景 #### 开发阶段集成 @ComponentpublicclassConflictDetectionStartupRunnerimplementsCommandLineRunner { @Override publicvoidrun(String... args)throws Exception { if (isDevelopmentEnvironment()) { ScanResultresult= performConflictScan(); if (result.getConflicts().size() > 0) { logger.warn("发现 {} 个依赖冲突，建议访问 http://localhost:8080 查看详情", result.getConflicts().size()); } } }} #### CI/CD 流水线集成 !/bin/bash在 CI 阶段运行冲突检测java -jar conflict-detector.jar --mode=ci --output=report.json检查冲突数量CONFLICTS=$(cat report.json | jq '.summary.conflictCount')if [ $CONFLICTS -gt 0 ]; thenecho "发现 $CONFLICTS 个依赖冲突，请检查报告" exit 1fi ### 7.总结 本工具通过配置化规则和运行时扫描，实现了对 Jar 包冲突的自动检测和修复建议。无论开发环境还是生产环境，都可以直观地看到冲突详情，并及时处理。 https://github.com/yuboon/java-examples/tree/master/springboot-jar-conflict
高性能短链构建短链必须性这里列举3个原因来讲一下短链存在的必要性。相对安全短链不容易暴露访问参数，生成方式可以完全迎合短信平台的规则，能够有效地规避关键词、域名屏蔽等风险，而原始 URL地址，很可能因为包含特殊字符被短信系统误判，导致链接无法跳转。平台限制短信发送平台有字数限制，在整条短信字数不变的前提下，把链接缩短，其他部分的文字描述就能增加，这样似乎更能达到该短信的实际目的（比如，营销）。短链构成短链的组成通常包含两个部分：域名 + 随机码https://www.ownsyou.com/s2TYdWd短链的域名最好和其他业务域名分开，而且要尽量简短，可以不具备业务含义（比如：xyz.com），因为短链大部分是用于营销，可能会被三方平台屏蔽。(非营销无需考虑这个问题)短链的随机码需要全局唯一，建议10位以下。短链跳转原理首先，我们先看一个短链跳转的简单例子，如下代码，定义了一个 302重定向的代码示例：import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.servlet.view.RedirectView; @Controller publicclass RedirectController { @GetMapping("/{shortCode}") public RedirectView redirect(@PathVariable String shortCode) { String destUrl = "https://yuanjava.com"; // destUrl = getDestUrlByShortCode(shortCode); //真实的业务逻辑 returnnew RedirectView(destUrl); }接着，在浏览器访问短链”http://127.0.0.1:8080/s2TYdWd” 后，请求会被重定向到 https://yuanjava.com 从ws抓包可以看到 302状态码并且请求被 Location到另外一个 URL，整个交互流程图如下：用户通过短链访问到短链系统，然后通过302重定向到目标系统最后，总结下短链跳转的核心思想：生成随机码，将随机码和目标 URL（长链）的映射关系存入数据库；用域名+随机码生成短链，并推送给目标用户；当用户点击短链后，请求会先到达短链系统，短链系统根据随机码查找出对应的目标 URL，接着将请求 302重定向到目标 URL（长链）；关于重定向有 301 和 302两种，如何选择？302，代表临时重定向：每次请求短链，请求都会先到达短链系统，然后重定向到目标 URL（长链），这样，方便短链系统做一些统计点击数等操作；通常采用 302301，代表永久重定向：第一次请求拿到目标长链接后，下次再次请求短链，请求不会到达短链系统，而是直接跳转到浏览器缓存的目标 URL（长链），短链系统只能统计到第一次访问的数据；一般不采用 301。短链生成从短链组成章节可知：短链=域名+随机码，因此，如何生成短链的问题变成了如何生成一个全局唯一的随机码，通常会有 3种做法：Base62Base62 表示法是一种基数为62的数制系统，包含26个英文大写字母（A-Z），26个英文小写字母（a-z）和10个数字（0-9）。这样，共有62个字符可以用来表示数值。如下代码：import java.security.SecureRandom; publicclass RandomCodeGenerator { privatestaticfinal String CHAR_62 = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; privatestaticfinal SecureRandom random = new SecureRandom(); public static String generateRandomCode(int length) { StringBuilder sb = new StringBuilder(length); for (int i = 0; i < length; i++) { int rndCharAt = random.nextInt(CHAR_62.length()); char rndChar = CHAR_62.charAt(rndCharAt); sb.append(rndChar); } return sb.toString(); }对于 Base62算法，如果是生成 6位随机数有 62^6 - 1 = 56800235583（568亿多），如果是生成 7位随机数有 62^7 - 1 = 3521614606208（3.5万亿多），足够使用。Hash算法Hash算法是我们最容易想到的办法，比如 MD5, SHA-1, SHA-256, MurmurHash, 但是这种算法生成的 Hash值还是比较长，常用的做法是把这个 Hash值进行 62/64进行压缩。如下代码，通过 Google的 MurmurHash算法把长链 Hash成一个 32位的 10进制正数，然后再转换成62进制（压缩），这样就可以得到一个 6位随机数，import com.google.common.hash.HashFunction; import com.google.common.hash.Hashing; import java.nio.charset.StandardCharsets; publicclass MurmurHashToBase62 { privatestaticfinal String BASE62 = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; public static String toBase62(int value) { StringBuilder sb = new StringBuilder(); while (value > 0) { sb.insert(0, BASE62.charAt(value % 62)); value /= 62; } return sb.toString(); } public static void main(String[] args) { // 长链 String input = "https://yuanjava.cnposts/short-link-system/design?code=xsd&page=1"; // 长链利用 MurmurHash算法生成 32位 10进制数 HashFunction hashFunction = Hashing.murmur3_32(); int hash = hashFunction.hashString(input, StandardCharsets.UTF_8).asInt(); if (hash < 0) { hash = hash & 0x7fffffff; // Convert to positive by dropping the sign bit } // 将 32位 10进制数 转换成 62进制 String base62Hash = toBase62(hash); System.out.println("base62Hash:" + base62Hash); } }全局唯一ID很多大中型公司都会有自己全局唯一 ID 的生成服务器，可以使用这些服务器生成的 ID来保证全局唯一，也可以使用雪花算法生成全局唯一的ID，再经过 62/64进制压缩。冲突解决对于上述 3种方法的前 2种：Base62 或者 Hash算法，因为本质上都是哈希函数，所以，不可避免地会产生哈希冲突，尽管冲突的概率已经很低很低了，so，万一冲突了，该如何解决呢？要解决冲突，首先需要检测 Hash冲突，通常来说有 2种检测方法。数据库锁这里以 MySQL数据库为例，表结构如下：CREATE TABLE`short_url_map` ( `id`int(11) unsignedNOTNULL AUTO_INCREMENT, `long_url`varchar(160) DEFAULTNULLCOMMENT'长链', `short_url`varchar(10) DEFAULTNULLCOMMENT'短链', `gmt_create`int(11) DEFAULTNULLCOMMENT'创建时间', PRIMARY KEY (`id`), UNIQUEINDEX'short_url' ('short_url') ) ENGINE=InnoDBDEFAULTCHARSET=utf8;首先创建一张长链和短链的关系映射表，然后通过给 short_url字段添加唯一锁，这样，当数据插入时，如果存在 Hash冲突（short_url值相等），数据库就会抛错，插入失败，因此，可以在业务代码里捕获对应的错误，这样就能检测出冲突。也可以先用 short_url去查询，如果能查到数据，说明 short_url存在 Hash冲突了。对于这种通过查询数据库或者依赖于数据库唯一锁的机制，因为都涉及DB操作，对数据库是一个额外的开销，如果流量比较大的话，需要保证数据库的性能。布隆过滤器在 DB操作的上游增加一个布隆过滤器，在长链生成短链后， 先用短链在布隆过滤器中进行查找，如果存在就代表冲突了，如果不存在，说明 DB里不存在此短链，可以插入。对于布隆过滤器的选择，单机可以采用 Google的布隆过滤器，分布式可以使用 RedisBloom。检测出了冲突，需要如何解决冲突？再 Hash，可以在长链后面拼接一个 UUID之类的随机字符串，然后再次进行 Hash，用得出的新值再进行上述检测，这样 Hash冲突的概率又大大大降低了。高并发场景的架构在流量不大的情况，上述方法怎么折腾似乎都合理，但是，为了架构的健壮性，很多时候需要考虑高并发，大流量的场景，因此架构需要支持水平扩展，比如：采用微服务功能模块分离，比如，短链生成服务和长链查询服务分离功能模块需要支持水平扩容，比如：短链生成服务和长链查询服务能支持动态扩容缓解数据库压力，比如，分区，分库分表，主从，读写分离等机制服务的限流，自保机制完善的监控和预警机制
后端性能优化MySQL主要针对索引，对于MySQL优化的博文已经很多了，可以参阅，这里主要讲关于索引的几个点。索引：MySQL的所有C端查询我们要尽量保证必须用到索引，尤其是那种数据量增长快的表。所以不要在索引字段进行函数运算，不要使用取反等等。联合索引：联合索引使用可能是最多的了，sql没写对可能就导致了全表扫描，记住最左匹配原则，将区分度高的字段放在前面。分页查询：C端经常展示一些列表，比如文章列表，作品列表等，基本都用到了分页查询，这里需要注意的是order by fieldName。要保证fieldName在索引中，不然就可能会出现filesort了，这是个外排序，在数据量大的时候是很致命的。在我们这个项目中，我没说的时候，所有的分页查询都有这个问题。养成个好习惯：创建表的时候就设计好索引，不要到最后出事了再去加，那会数据量很大再加就迟了。分库分表一般预估年增量记录在500W以内都不需要分，我们之前是1000W以内就不分。如果只是临时做活动使用的就更不需要分了，除非活动期间量真的达到了几亿。简单介绍下分的原则数据量大就分表，否则单表的查询性能肯定是下降很多并发高就分库，因为单库的资源就那么多，支持的连接数不会太高。大多数情况下分库分表是一起的，因为量大和并发高经常是同时出现。一开始分就给足资源，二次分表成本太大，在快手一般上来就是10个库和1000张表。需要特别注意的是shardKey，围绕用户展开的就用userId来做shardKey，这在绝大多数情况下没问题的。如何分，见仁见智，可以使用shardingjdbc，优点很明显是减少开发成本。也可以自己手动分，优点也很明显，便于定制化和制定规则。如果你犹豫不决就shardingjdbc吧，尤其是小团队的时候。注意事项1、减少操作数据库次数比如我要批量更新用户的奖励，有人会写这样的代码for (UserReward r : rewardList) { rewardDao.update(r); }如果rewardList的size=10,那会操作数据库10次，也就是10次IO，而明显我们可以一次批量更新解决：rewardDao.batchUpdate(rewardList);虽然看着是那么理所当然，我们的项目甚至不少地方有下面类似的代码User user = userService.getById(userId); // doSomething userService.updateById(user); // doSomething2 userService.updateById(user); // doSomething3 userService.updateById(user);明明可以一次update就解决的为什么要update多次？引申一下，有的时候甚至都不需要操作数据库，这种情况很多时候不容易发现。举个例子，之前活动的时候在活动页面会展示任务信息和用户获得的奖励。// 查询用户任务 UserTask userTask = userTaskService.getById(id, userId); // 构建外显响应 UserTaskResp userTaskVo = buildVo(userTask); // 从结算表查询结算信息，从而得知获得的奖励 UserTaskSettlement s = userTaskSettlementService.getByTaskId(userTaskId, userId); // 填充奖励信息 fillReward(userTaskVo, s);活动页的流量是很大的，平白无故增大了结算表的流量。因为大部分用户是完不成任务的，所以根本没有获得奖励，所以可以判断用户是否完成了任务，只有完成了任务才去查询结算表即可。使用迭代器查询如果我们要查询的数据量很多，比如瓜分活动需要查出来所有参与并完成活动的用户，假设就一张表，有2000W数据，满足要求的用户可能有百万。如何做？直接一个批量查询的sql？肯定是不行的会出现包溢出异常一次查询大量数据到内存，会占用大量内存甚至full gc。正确的做法是分批查询，比如每次查询200条，然后进行处理。当然，不介意写while这种循环，一来不优雅，二来容易写出死循环的代码，最后也没法复用。可以参考下面的迭代器写法import com.google.common.collect.AbstractIterator; import org.apache.commons.collections.CollectionUtils; import java.util.Iterator; import java.util.List; import java.util.function.BiFunction; import java.util.function.Function; /** * @Description 大批量数据迭代器 主要是针对表中数据过多循环查询问题 * List<User> list = Lists.newArrayList(new User(1,"1"), new User(2,"1"), new User(3,"1"), * new User(4,"1"),new User(5,"1"),new User(6,"1"),new User(7,"1")); * SimpleSingeFieldIterable<Integer, User> iterable = new SimpleSingeFieldIterable<>(0, 2, * (position, count) -> list.stream().sorted(Comparator.comparing(User::getId)) * .filter(x -> x.getId() > position).limit(count).collect(Collectors.toList()), User::getId); * for (List<User> list1 : iterable) { * System.out.println(ObjectJsonMapper.toJSON(list1)); * } */ public class SimpleSingeFieldIterable<I extends Comparable, R> implements Iterable<List<R>> { private final BiFunction<I, Integer, List<R>> searchDAO; private I searchPosition; private final Integer count; private final Function<R, I> model2IdFunction; public SimpleSingeFieldIterable(I searchPosition, Integer count, BiFunction<I, Integer, List<R>> searchDAO, Function<R, I> model2IdFunction) { this.searchPosition = searchPosition; this.count = count; this.searchDAO = searchDAO; this.model2IdFunction = model2IdFunction; } @Override public Iterator<List<R>> iterator() { return new SingeFieldIterator(); } class SingeFieldIterator extends AbstractIterator<List<R>> { private boolean needContinue = true; @Override protected List<R> computeNext() { if (!needContinue) { return endOfData(); } List<R> result = searchDAO.apply(searchPosition, count); if (CollectionUtils.isEmpty(result)) { needContinue = false; return endOfData(); } if (result.size() < count) { needContinue = false; } searchPosition = model2IdFunction.apply(result.get(result.size() - 1)); return CollectionUtils.isNotEmpty(result) ? result : endOfData(); } } }使用// 每次查询出来200条DocLibrary SimpleSingeFieldIterable<Long, DocLibrary> docIterable = new SimpleSingeFieldIterable<>(0L, 200, (minId, count) -> baseMapper.getAllList(minId, count), DocLibrary::getId); AtomicInteger counter = new AtomicInteger(0); docIterable.forEach(list -> { LOGGER.info("start batchUpdate! count : {}", counter.get()); List<DocLibrary> effectiveList = list.stream() .filter(x -> StringUtils.isEmpty(x.getFileType())).collect(Collectors.toList()); for (DocLibrary doc : effectiveList) { String fileName = doc.getFileName(); String suffix = StringUtils.substringAfterLast(fileName, "."); doc.setFileType(suffix); } baseMapper.batchUpdate(list); LOGGER.info("start batchUpdate! count : {}", counter.addAndGet(list.size())); }); @Mapper public interface DocLibraryMapper extends BaseMapper<DocLibrary> { void batchUpdate(List<DocLibrary> list); @Select("select * from doc_library where id > #{minId} order by id asc limit #{size}") List<DocLibrary> getAllList(@Param("minId") long minId, @Param("size") int size); }做好SQL监控对于大型C端项目，数据量可能很大，一定要做好SQL监控，也就是sql调用量，qps，耗时等。毕竟人是不可靠的，即使你能保证你没问题，但你保证不了别人。缓存关于缓存Redis，这里只做一些简单罗列。1、使用Redis加快访问：大厂里面肯定没问题，小厂里面很多项目几乎看不使用redis，全是直接搂数据库。注意下缓存一致性即可，大部分场景使用Cache Aside策略即可。2、Redis大key问题：大的String，比如1M。集合数据太多，比如5000个。这都是大key问题，redis处理仍然是单线程，大key会拖慢整个redis，并且影响带宽。常用解决方案就是拆分成多个小key，放到多个节点即可。3、redis热key问题：如果某个redis的key访问量很高，那么这个key就是热key，比如我们可以认为该key的qps达到5000（看业务）那么就是热key，解决方案：使用本地缓存：可以是guava的cache也可以是快手开源的全局本地缓存。使用memcached：memcached扛热点能力可比redis好多了。冗余写，随机读：也就是写的时候写多个副本到不同的redis节点，读的时候选其中一个节点读。4、关于缓存击穿，一般发生在缓存过期后大量请求落到数据库，这个缓存不一定是说redis，也可以是本地缓存如果是本地缓存，一般就是guava的cache，使用 load miss 方法即可，也就是cache.get(key, Callable)，内部是会加锁的。如果是redis，那么最容易想到的就是分布式锁，当然，也可以写个cacheSetter服务，思路是保证相同的key落到同一台服务器，然后使用jvm级别的锁去加锁处理即可；import com.google.common.collect.Lists; import org.apache.commons.collections4.CollectionUtils; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.CountDownLatch; /** * @Description 分布式加载缓存的rpc服务，如果部署了多台机器那么调用端最好使用id做一致性hash保证相同id的请求打到同一台机器。 **/ public abstract class AbstractCacheSetterService implements CacheSetterService { private final ConcurrentMap<String, CountDownLatch> loadCache = new ConcurrentHashMap<>(); private final Object lock = new Object(); @Override public void load(Collection<String> needLoadIds) { if (CollectionUtils.isEmpty(needLoadIds)) { return; } CountDownLatch latch; Collection<CountDownLatch> loadingLatchList; synchronized (lock) { loadingLatchList = excludeLoadingIds(needLoadIds); needLoadIds = Collections.unmodifiableCollection(needLoadIds); latch = saveLatch(needLoadIds); } System.out.println("needLoadIds:" + needLoadIds); try { if (CollectionUtils.isNotEmpty(needLoadIds)) { loadCache(needLoadIds); } } finally { release(needLoadIds, latch); block(loadingLatchList); } } /** * 加锁 * @param loadingLatchList 需要加锁的id对应的CountDownLatch */ protected void block(Collection<CountDownLatch> loadingLatchList) { if (CollectionUtils.isEmpty(loadingLatchList)) { return; } System.out.println("block:" + loadingLatchList); loadingLatchList.forEach(l -> { try { l.await(); } catch (InterruptedException e) { e.printStackTrace(); } }); } /** * 释放锁 * @param needLoadIds 需要释放锁的id集合 * @param latch 通过该CountDownLatch来释放锁 */ private void release(Collection<String> needLoadIds, CountDownLatch latch) { if (CollectionUtils.isEmpty(needLoadIds)) { return; } synchronized (lock) { needLoadIds.forEach(id -> loadCache.remove(id)); } if (latch != null) { latch.countDown(); } } /** * 加载缓存，比如根据id从db查询数据，然后设置到redis中 * @param needLoadIds 加载缓存的id集合 */ protected abstract void loadCache(Collection<String> needLoadIds); /** * 对需要加载缓存的id绑定CountDownLatch，后续相同的id请求来了从map中找到CountDownLatch，并且await，直到该线程加载完了缓存 * @param needLoadIds 能够正在去加载缓存的id集合 * @return 公用的CountDownLatch */ protected CountDownLatch saveLatch(Collection<String> needLoadIds) { if (CollectionUtils.isEmpty(needLoadIds)) { return null; } CountDownLatch latch = new CountDownLatch(1); needLoadIds.forEach(loadId -> loadCache.put(loadId, latch)); System.out.println("loadCache:" + loadCache); return latch; } /** * 哪些id正在加载数据，此时持有相同id的线程需要等待 * @param ids 需要加载缓存的id集合 * @return 正在加载的id所对应的CountDownLatch集合 */ private Collection<CountDownLatch> excludeLoadingIds(Collection<String> ids) { List<CountDownLatch> loadingLatchList = Lists.newArrayList(); Iterator<String> iterator = ids.iterator(); while (iterator.hasNext()) { String id = iterator.next(); CountDownLatch latch = loadCache.get(id); if (latch != null) { loadingLatchList.add(latch); iterator.remove(); } } System.out.println("loadingLatchList:" + loadingLatchList); return loadingLatchList; } }如果能接受一定的脏数据，那么甚至可以设置缓存永不过期，但是要设置个逻辑过期字段,如果过期了异步加载即可。String get(String key) { V v = redis.get(key); if (v.getLogicTime() < System.currentTimeMills()) { String mutexkey = buildMutexKey(key); if (redis.set(mutexKey, "1", "ex", 100, "nx")) { executor.execute(() -> {//重建缓存}) } } return v.getValue(); }集群隔离，为了保证业务直接不互相影响，最好做下集群隔离，比如任务系统使用task集群，奖励系统使用reward集群。多级缓存：如果并发超高，可以考虑使用多级缓存，比如对于活动系统，在后台创建活动然后下发给用户，活动相关的基础信息是不变的，完全可以使用本地缓存+redis+mysql，设置可以加一层全局本地缓存。内存布隆过滤器实战案例布隆过滤可用来高效判断一个元素是否存在于一个集合（小概率存在假阳性，需要二次判断），因此可以用来作为流量漏斗。实际场景例如某个功能只对少部分用户开放，功能入口的查询流量很高，需要高性能的判断用户是否拥有该功能权限，但权限查询接口扛不住这么高的流量，此时使用布隆过滤则可以过滤掉所有无权限用户的请求，仅处理少部分可能有权限的用户查询即可。由于源数据查询服务性能有限，增加了一层对外查询服务，在该服务启动时增加钩子，需要从源数据查询服务拉取到全量的源数据构建内存中的BloomFilter，构建完成后再开始对外提供服务。若源数据会更新，则运行期间需要定时reload源数据以重新构建BloomFilter。对外查询服务通过BloomFilter来拦截掉大部分明确不存在于源数据集中的查询请求，从而使得源数据查询服务得到保护。消息队列与流量聚合MQMQ(消息队列)主要是来做异步削峰的，比如直播间点赞，收礼等，如果服务端收到请求后直接同步操作数据库那么晚高峰的时候对数据库就是灾难的，所以必须使用消息队列来处理。场景介绍这里就举两个常见的例子电商秒杀：每次用户秒杀的结果虽然在页面展示了，实际上一般是把请求丢到了消息队列了，后续慢慢消费处理（db中库存的改动等）；直播间收礼：在一些大V开直播的时候，直播收礼的流量10W/s很正常，服务端需要对礼物进行归类和统计等处理，这显然也可以使用mq去异步处理；注意事项我们需要关注的是消费速度和性能之间的平衡，如果消费过快，那么下游扛不住，如果消费过慢又会导致消息堆积，消费到冷数据并影响业务。解决思路拿RocketMQ来举例，消费者从broker拉取的消息是极快的，一般这里不会成为性能瓶颈，往往成为性能瓶颈的是业务的IO操作，所以发现消息堆积了的话，先从IO那块想办法解决。比如是不是流量增大了导致消费跟不上，这时候可以考虑在不影响性能的情况下调整消费线程数或者消费者扩容。或者是不是一些IO接口出问题了。流量聚合流量聚合简单来说就是把多次的请求整合为一个请求处理，显然只有在业务对单次的请求不敏感时并且能接受一定延迟时才能使用，比如直播间点赞，主播对单次点赞根本不敏感，直播间展示的赞数也不是实时的，完全可以对多次点赞进行聚合，最后再进行一些列判断再累计起来存放到db或缓存中，直播间从db或缓存中进行拉取。比如我要统计每天消耗大模型的token数，当每次有相关接口调用的时候就将消耗的token数发到消息队列，然后在消费者侧进行流量聚合。再比如，我们使用binlog监听工具用户任务表（散了1000张表），每次产生用户任务信息我们就往ES写(超高流量)，然后在后台我们去ES搜索，所以我们也可以每次监听到消息后进行聚合，最后批量写到ES。那么这个聚合的工具需要什么功能呢？简单来说主要就三点提供个能存放数据的容器当达到指定数量时输出容器中的数据当达到指定时候时输出容器中的数据刚好快手提供了这么个工具-BufferTrigger，这个工具在快手内部大量使用，尤其是主站直播，下面将简单介绍下这个简单易用的工具。public class BufferTrigger<E> { private static final Logger logger = getLogger(BufferTrigger.class); private final BlockingQueue<E> queue; private final int batchSize; private final long lingerMs; private final ThrowableConsumer<List<E>, Exception> consumer; private final ScheduledExecutorService scheduledExecutorService; private final ReentrantLock lock = new ReentrantLock(); private final AtomicBoolean running = new AtomicBoolean(); private BufferTrigger(long lingerMs, int batchSize, int bufferSize, ThrowableConsumer<List<E>, Exception> consumer) { this.lingerMs = lingerMs; this.batchSize = batchSize; this.queue = new LinkedBlockingQueue<>(max(bufferSize, batchSize)); this.consumer = consumer; this.scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(); // 执行一次调度 this.scheduledExecutorService.schedule(new BatchConsumerRunnable(), this.lingerMs, MILLISECONDS); } public void enqueue(E element) { try { queue.put(element); tryTrigBatchConsume(); } catch (InterruptedException e) { currentThread().interrupt(); } } private void tryTrigBatchConsume() { if (queue.size() >= batchSize) { if (lock.tryLock()) { try { if (queue.size() >= batchSize) { if (!running.get()) { // prevent repeat enqueue this.scheduledExecutorService.execute(this::doBatchConsumer); running.set(true); } } } finally { lock.unlock(); } } } } public void manuallyDoTrigger() { doBatchConsumer(); } private void doBatchConsumer() { lock.lock(); try { running.set(true); while (!queue.isEmpty()) { List<E> toConsumeData = new ArrayList<>(min(batchSize, queue.size())); queue.drainTo(toConsumeData, batchSize); if (!toConsumeData.isEmpty()) { doConsume(toConsumeData); } } } finally { running.set(false); lock.unlock(); } } private void doConsume(List<E> toConsumeData) { try { consumer.accept(toConsumeData); } catch (Throwable e) { logger.error("doConsume failed", e); } } private class BatchConsumerRunnable implements Runnable { @Override public void run() { try { doBatchConsumer(); } finally { scheduledExecutorService.schedule(this, lingerMs, MILLISECONDS); } } } public long getPendingChanges() { return queue.size(); } public static <E> BufferTriggerBuilder<E> newBuilder() { return new BufferTriggerBuilder<>(); } public static class BufferTriggerBuilder<E> { private int batchSize; private int bufferSize; private Duration duration; private ThrowableConsumer<List<E>, Exception> consumer; public BufferTriggerBuilder<E> batchSize(int batchSize) { this.batchSize = batchSize; return this; } public BufferTriggerBuilder<E> bufferSize(int bufferSize) { this.bufferSize = bufferSize; return this; } public BufferTriggerBuilder<E> duration(Duration duration) { this.duration = duration; return this; } public BufferTriggerBuilder<E> consumer(ThrowableConsumer<List<E>, Exception> consumer) { this.consumer = consumer; return this; } public BufferTrigger<E> build() { Preconditions.checkArgument(batchSize > 0, "batchSize 必须大于0"); Preconditions.checkArgument(bufferSize > 0, "bufferSize 必须大于0"); Preconditions.checkNotNull(duration, "duration未设置"); Preconditions.checkNotNull(consumer, "消费函数未设置"); return new BufferTrigger<>(duration.toMillis(), batchSize, bufferSize, consumer); } } }测试public class BufferTriggerDemo { private static AtomicInteger counter = new AtomicInteger(0); public static void main(String[] args) throws InterruptedException { BufferTrigger<Long> trigger = BufferTrigger.<Long>newBuilder() .batchSize(10) .bufferSize(100) .duration(Duration.ofSeconds(5)) .consumer(BufferTriggerDemo::consume) .build(); for (long i = 0; i < 10000; i ++) { trigger.enqueue(i); TimeUnit.MILLISECONDS.sleep(100); } } private static void consume(List<Long> list) { System.out.printf("次数 %d, result : %s \n", counter.incrementAndGet(), list); } }full gc陷阱需要注意的是BufferTrigger是单线程消费的，这个是一个比较大的陷阱，做活动的时候是踩了坑的，尤其是在消费操作中涉及到大量io操作的场景，因为在流量很高的时候可能会出现消费速度跟不上生产速度，这很容易导致full gc问题。所以如果有必要的话需要使用线程池来提升消费速度。消息队列和BufferTrigger的组合方式有没有考虑过消息队列的消费速度过慢，如何在不影响下游性能的情况下提升消费速？比如直播点赞，在mq每次收到一条点赞消息的时候是不是就可以使用BufferTrigger来进行聚合？然后每分钟消费一次，在流量剧增的时候是不是能十倍以上的提速？线程池 线程池作为jdk的一个重要组件，同时也是性能优化的常客，不得不谈。1、如果异步，尤其现在大模型的场景，基本上是必不可少。比如你要请求的两个接口没有关联性，可以考虑使用线程池去并发请求。2、对于非核心逻辑我们也可以使用线程池处理，比如用户完成任务后要给他发个触达（短信，私信等），可以使用线程池去异步处理。3、当然，存在明显的问题就是服务重启就没了，所以对性能要求高的服务还是得使用MQ。4、服务端大多是IO型操作，所以能够将线程数调大一点，起始值我们可以设置为核数*2。要设置一个合理的值，我们可以加上监控，尤其要监控活跃线程数和堆积数，从而来调整一个合适的值，并且帮助快速发现问题。之前出现一个问题可以作为借鉴，线上大模型相关接口经常卡住，查了半天猜测是线程池里面的任务处理耗时太长，甚至卡住了，导致新的请求进了队列并一直得不到消费，为了验证这个问题，加上了线程池监控，发现确实和猜测的吻合。1、如果你使用rpc，比如thirft，那么一定要使用自定义的线程池，并且监控，之前在小爱的时候就出现rpc接口在早高峰耗时上涨，究其原因是因为流量上涨，导致线程池处理不过来，跟上面说的问题很类似。2、此外建议给线程池命名，这样在发生问题使用jstack来dump线程堆栈的时候好分析问题。[root@turbodesk-api-canary-c9b685f9-ctclm home]# jstack 1 > thread.log [root@turbodesk-api-canary-c9b685f9-ctclm home]# ll total 752 -rw-r--r-- 1 root root 769667 Jan 17 17:47 thread.log [root@turbodesk-api-canary-c9b685f9-ctclm home]# grep java.lang.Thread.State thread.log | awk '{print $2$3$4$5}' | sort | uniq -c 69 RUNNABLE 3 TIMED_WAITING(onobjectmonitor) 8 TIMED_WAITING(parking) 6 TIMED_WAITING(sleeping) 2 WAITING(onobjectmonitor) 706 WAITING(parking)可以使用[Smart Java thread dump analyzer - thread dump analysis in seconds](https://fastthread.io/)来帮忙分析如果你的接口是间歇性的有大量请求，可以将核心线程数调小一些，避免白创建太多线程处于waitting状态。系统能创建的线程数是有限的，如何计算我就不谈了，比如我们的系统最多能创建1W个线程，之前有个同事的接口，在每次请求的时候创建了只有一个线程的线程池，导致服务经常重启。其他技巧请求丢弃在很多业务场景下，丢弃部分请求完全不影响业务，给个合理的提示的话用户根本无法感知，这其中最常见的就是秒杀、抢红包、发弹幕这类业务，请求量很大，但只有少部分请求能拿到钱，大部分请求直接丢弃都是没问题的，到时候告诉用户没抢到或者抢没了或者弹幕发生成功即可。预处理这类场景在抢红包的时候经常用到，可能很多人都知道很多app抢红包其实并不是在用户每次去抢的时候再去计算金额并落库的，一般是在红包发出去的时候就计算好了，比如1W块钱，100个人，那么就会随机生成100个数额推入到缓存的队列，用户抢的时候从队列直接pop即可。当然，可以通过消息队列异步落库，也就是同步写缓存，异步写db。又或者是在用户完成活动的时候得到一个资格，然后在指定的时间点去瓜分1W块钱，其实也是在这时间点之前就把能用的金额和获得资格的人拿出来，计算出每个人的金额再存储到缓存和db，最后用户虽然说是去抢，其实就是从缓存中把已经给他存好的钱拿出来了而已。
GraalVM替代JVM的方式方法和不足GraalVM 基本上可以看成是Oracle大力发展的新一代JVM，但是目前的使用情况还不大乐观，而且逐渐发现大家都有往GO语言转的趋势，本文具体分析一下替代方案和不足之处，为什么大家还是更希望使用GO。GraalVM与JVM关系JVM 全称 Java 虚拟机，我们都知道，Java 程序是运行在虚拟机上的，虚拟机提供 Java 运行时，支持解释执行和部分的（JIT）即时编译器，并且负责分配和管理 Java 运行所需的内存，我们所说的各种垃圾收集器都工作在 JVM 中。比如 Oracle JDK、OpenJDK ，默认的 JVM 是 HotSpot 虚拟机。GraalVM 可以完全取代上面提到的那几种虚拟机，比如 HotSpot。把你之前运行在 HotSpot 上的代码直接平移到 GraalVM 上，不用做任何的改变，甚至都感知不到，项目可以完美的运行。GraalVM主要想搭建一个Framework，最终目的是实现支持任何一种语言，可以共同跑在 GraalVM 上，不存在跨语言调用的壁垒。GraalVM 和JDK关系Java 虚拟机都是内置在 JDK 中的，比如Orcale JDK、OpenJDK，默认内置的都是 HotSpot 虚拟机。GraalVM 也是一种 JDK，一种高性能的 JDK。完全可以用它替代 OpenJDK、Orcale JDK。GraalVM 如何运行 Java 程序GraalVM - 还包含 Graal （JIT）即时编译器，可以结合 HotSpot 使用GraalVM – 是一种高性能 JDK，旨在加速 Java 应用程序性能，同时消耗更少的资源。GraalVM - 是一种支持多语言混编的虚拟机程序，不仅可以运行 JVM 系列的语言，也可支持其他语言。GraalVM 提供了两种方式来运行 Java 程序。第一种：结合 HotSpot 使用GraalVM 包含 Graal （JIT）即时编译器，自从 JDK 9u 版本之后，Orcale JDK 和 OpenJDK 就集成了 Graal 即时编译器。这样Java 既有解释运行也有即时编译。当程序运行时，解释器首先发挥作用，代码可以直接执行。随着时间推移，即时编译器逐渐发挥作用，把越来越多的代码编译优化成本地代码，来获取更高的执行效率。即时编译器可以选择性地编译热点代码，省去了很多编译时间，也节省很多的空间。比如多次执行的方法或者循环、递归等。JDK 默认使用的是 C2 即时编译器，C2是用C++编写的。而使用下面的参数可以用 Graal 替换 C2。-XX:+UnlockExperimentalVMOptions -XX:+UseJVMCICompilerGraal 编译器是用 Java 实现的，用 Java 实现自己的编译器。Graal 基于一些假设的条件，采取更加激进的方式进行优化。采用 Graal 编译器之后，对性能有会有一定的提升。但是Graal，VM不支持JDK8第二种：AOT 编译本地可执行程序AOT 提前编译，是相对于即时编译而言的。AOT在运行过程中耗费 CPU 资源来进行即时编译，而程序也能够在启动的瞬间就达到理想的性能。例如 C 和 C++语言采用的是AOT静态编译，直接将代码转换成机器码执行。而 Java 一直采用的是解释 + 即时编译技术，大多数情况下 Java 即时编译的性能并不比静态编译差，但是还是一直朝着 AOT 编译的方向努力。但是 Java 对于 AOT 来说有一些难点，比如类的动态加载和反射调用。GraalVM 的 AOT 编译实际上是借助了 SubstrateVM 编译框架，可以将 SubstrateVM 理解为一个内嵌精简版的 JVM，包含异常处理，同步，线程管理，内存管理（垃圾回收）和 JNI 等组件。SubstrateVM 的启动时间非常短，内存开销非常少。除了运行时占用的内存少之外，用这种方式最终生成的可执行文件也非常小。这对于云端部署非常友好。目前很多场景下都使用 Docker容器的方式部署，打一个 Java 程序的镜像包要包含完整的 JVM 环境和编译好的 Jar 包。而AOT 方式可以最大限度的缩小 Docker 镜像的体积。缺点对于反射这种纯粹在运行时才能确定的部分，不可能完全通过优化编译器解决，只能通过增加配置的方式解决。多语言支持GraalVM 中的另一个核心组件 Truffle是一个用 Java 写就的语言实现框架。基于 Truffle 的语言实现仅需用 Java 实现词法分析、语法分析以及针对语法分析所生成的抽象语法树（Abstract Syntax Tree，AST）的解释执行器，便可以享用由 Truffle 提供的各项运行时优化。就一个完整的 Truffle 语言实现而言，由于实现本身以及其所依赖的 Truffle 框架部分都是用 Java 实现的，因此它可以运行在任何 Java 虚拟机之上。当然，如果 Truffle 运行在附带了 Graal 编译器的 Java 虚拟机之上，那么它将调用 Graal 编译器所提供的API，主动触发对 Truffle 语言的即时编译，将对 AST 的解释执行转换为执行即时编译后的机器码。安装并使用GraalVm社区版是基于OpenJDK 11.0.17, 17.0.5, 19.0.1，如果你想用免费的，只能将程序升级到 JDK 11 以上了。对于Linux来说，下载下来的压缩包，直接解压，然后配置环境变量。把解压目录配置到环境变量的 JAVA_HOME就可以了。解压好其实就相当于安装完毕了，查看一下版本。进入到解压目录下的bin目录中，运行 java -version。运行结果和平常使用JDK也没什么差别。代码运行这里不讲常用的测试环境代码运行方式，无非是做好配置，然后运行。这里主要讲一下关于使用AOT编译为机器码，然后以可执行文件的形式来做：native-image 可以命令行的形式执行，也可以在配合 Maven 执行，这里主要讲用Maven搭配的方式。1、安装native-image 工具包native-image 是用来进行 AOT 编译打包的工具，先把这个装上，才能进行后面的步骤。安装好 GraalVM 后，在 bin目录下有一个叫做 gu的工具，用这个工具安装，如果将 bin目录添加到环境中，直接下面的命令安装就行了。gu install native-image如果没有将 bin目录加到环境变量中，要进入到 bin目录下，执行下面的命令安装。./gu install native-image这个过程可能比较慢，因为要去 github 上下载东西。2、配置 Maven配置各种版本 <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>${java.specification.version} </maven.compiler.source> <maven.compiler.target>${java.specification.version}</maven.compiler.target> <native.maven.plugin.version>0.9.12</native.maven.plugin.version> <imageName>graalvm-demo-image</imageName> <mainClass>org.graalvm.HelloWorld</mainClass> </properties>native.maven.plugin.version是要用到的编译为可执行程序的 Maven 插件版本。imageName是生成的可执行程序的名称。mainClass是入口类全名称。配置 build 插件 <build> <plugins> <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>exec-maven-plugin</artifactId> <version>3.0.0</version> <executions> <execution> <id>java-agent</id> <goals> <goal>exec</goal> </goals> <configuration> <executable>java</executable> <workingDirectory>${project.build.directory}</workingDirectory> <arguments> <argument>-classpath</argument> <classpath/> <argument>${mainClass}</argument> </arguments> </configuration> </execution> <execution> <id>native</id> <goals> <goal>exec</goal> </goals> <configuration> <executable>${project.build.directory}/${imageName}</executable> <workingDirectory>${project.build.directory}</workingDirectory> </configuration> </execution> </executions> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.8.1</version> <configuration> ${maven.compiler.source} <target>${maven.compiler.source}</target> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> <version>3.2.2</version> <configuration> <archive> <manifest> <addClasspath>true</addClasspath> <mainClass>${mainClass}</mainClass> </manifest> </archive> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-assembly-plugin</artifactId> <executions> <execution> <phase>package</phase> <goals> <goal>single</goal> </goals> </execution> </executions> <configuration> <archive> <manifest> <addClasspath>true</addClasspath> <mainClass>${mainClass}</mainClass> </manifest> </archive> <descriptorRefs> <descriptorRef>jar-with-dependencies</descriptorRef> </descriptorRefs> </configuration> </plugin> </plugins> </build>配置 profiles <profiles> <profile> <id>native</id> <build> <plugins> <plugin> <groupId>org.graalvm.buildtools</groupId> <artifactId>native-maven-plugin</artifactId> <version>${native.maven.plugin.version}</version> <extensions>true</extensions> <executions> <execution> <id>build-native</id> <goals> <goal>build</goal> </goals> <phase>package</phase> </execution> <execution> <id>test-native</id> <goals> <goal>test</goal> </goals> <phase>test</phase> </execution> </executions> <configuration> <fallback>false</fallback> <buildArgs> <arg>-H:DashboardDump=fortune -H:+DashboardAll</arg> </buildArgs> <agent> <enabled>true</enabled> <options> experimental-class-loader-support </options> </agent> </configuration> </plugin> </plugins> </build> </profile> </profiles>3、使用 maven 编译，打包成本地可执行程序。执行 Maven 命令mvn clean package或者mvn -Pnative -Dagent package 编译打包的过程比较慢，因为要直接编译成机器码，所以比一般的编译过程要慢一些。看到下面的输入日志，说明打包成功了。4、运行可执行程序包，打开 target 目录，已经看到了graalvm-demo-image可执行程序包了。然后就可以运行它了，进入到目录下，执行下面的命令运行，可以看到正常输出了。注意了，这时候已经是没有用到本地 JVM 了。缺点不能直接编译，需要先用 -agentlib 模式运行程序生成编译需要的配置文件，这个过程需要把程序的大部分功能都运行一遍，以免漏掉反射代码导致编译后的程序运行异常。这一步要耗费大量时间精力，非常难受。使用别的技术直接编译代码就可以了，而使用 native-image 还要有这样一个前置步骤，非常不方便。编译速度过慢，比直接用javac编译慢好几倍，体验较差。windows上只有 Serial GC（这相当于直接放弃了windows平台），linux上有Serial GC和G1，ZGC之类不支持。（垃圾回收机制根据操作系统平台不同支持不同）编译出来的包比原本的 jvm + jar 加起来还大，导致运维会比较麻烦，网络不畅通或者使用堡垒机会比较难受。native-image 编译出来的程序不支持arthas、jprofiler、visualvm等问题排查和性能分析工具，直接导致java程序一大优势丧失。因为不是jit，峰值性能不如java，对于服务器程序来说也不合适好处就是节省内存、代码不会被破解，但是对于GO来说，这些直接都被取消了，当然GO最麻烦的其实是需要重新学一门语言比较难受。GO作为一门Native语言，编译速度快到可以在开发阶段考虑实时编译热更新，但是使用quarkus和SpringBoot Native修改一行的编译还需要时间。最麻烦的可能是一些第三方库，要支持这些第三方库还需要考虑JDK版本的兼容，但是你知道很多东西其实没有一直更新和维护的。社会环境问题首先刚研发的新技术是需要一个生态和环境的，我不知道国外生态怎么样啊，但是国内生态都是KPI，在这种环境下新技术的出现如果不能很明显提高生产效率，而实际上去提高运维效率和一些不是很重要的成本（对于企业来说，硬盘和内存条成本不高，反而是人力成本更高），所以在国内这个土壤就不太适应，当然不乏一些技术大牛会看好这项技术的发展，还有一些性价比玩家会考虑用最低配的硬件来实现最高的性能，但是对于市场化的东西来说就没有可接受性。或许得等SpringBoot4上线之后，才能解决这个问题。
怀化之旅-2025.5.5今天的路线是玉皇宫——人民公园——市政府——市民广场——回家。起床！今天的早餐时白糖包和香菇干笋包（好吧，这个也放了辣椒哈哈哈）玉皇宫早上来参拜一下玉皇宫，据说这是这边最大的道教宫殿，还蛮有名气，其实这个确实挺好看的，当然师傅们可能就是香火比较少，所以遇到每个人都会问是不是来拜拜的。我碰到了一对情侣也是来参观的，他们不好意思，我就和师傅说了，我们仨都是来参观的，师傅作了作揖，也很愉快地让我们进去了，只是特地要求按照规矩来，不要走中间，左右两侧进入。山大门在修缮，有很多神像都在修：玉灵官、哼哈二将、青龙、白虎、朱雀、玄武、龙王、武陵山神、城隍爷。这里就不给大家放神像的图片了，因为还在修复中，他们估计也不太愿意让人见到这个模样。踏着阶梯上来可以在这里拍到很不错的一个整体景色可以看到对面的小区，也可以看到怀化市的风景。这里你要说百家姓呢，倒也不一定，主要还是怀化这边的姓氏，类似于祠堂一样的感觉吧，不过确实供奉了一些神像例如女娲、炎帝、黄帝，所以也算是比较有意思的宫殿。财神殿的话也是肯定会有的了。如果你去佛教的话一般就是地藏王在右侧，然后左侧珈蓝关公，所以怀化的玉皇宫是比较纯粹的道教了。走，去市政府如果开电动可能更快，但是我不急，在这里和一个老奶奶聊起了天。对方捡到了一张身份证，还问我能不能帮忙送给他，因为是沅陵县的实在太远了，而且我要返程了，我看了下失主年龄不算很大，估计会补办的，就让她别再担心了。老奶奶问我哪里人，我说广东的，她说她有个佛山亲戚清明节带他们一起去佛山玩了。但是由于熟悉这里的环境，觉得还是回来比较舒服。她还问我来这里干嘛，是来出差吗？哈哈哈，我看起来很像来出差的吗？我记得我搭高铁来的时候，隔壁坐的怀化阿姨也问我是不是来出差，可能怀化这座城市还是更偏向做生意吧。离市政府最近的地方其实是妇幼保健院，所以就在这里下车了。老大爷们富有生活气息地在这里唱歌，搞艺术创作。说实话，我觉得古城还不如人民公园，这里跑跑步放松放松，看看山水也是很不错的。整个怀化地政务都在这一圈了，有不同的业务局，例如住建局啊等等。因为想去法院看看，所以得去以前的市民广场，就在这里等公交，当然实际上要去的是“市委”站。法院怀化蛮神奇的，真的有很多广东肠粉，粤菜也不少，有很多早茶，虽然正宗不正宗我没试过，但是总归可能和广东的情谊也蛮深。怀化最近都在宣传这个，其实你在哪里都能看到。茶颜悦色要离开怀化回广州了，离开前再来尝试喝一杯茶颜悦色吧，这次我要喝声声乌龙了。谢谢某人推荐，很好喝。在这里打车回酒店收拾东西，打车正好遇到了拼车的，遇到了个带小孩的母亲。我也不是很急，所以我就跟司机说可以的，没问题。我觉得母亲带小孩还蛮幸苦，司机还一直跟我说现在的年轻人比较自私自我，我觉得吧应该要分情况考虑吧，也希望大家能多给这个社会一些包容的机会。候车厅好多人啊！回到广州了。