Low Gravity

在这里,我们用算法解决问题,用代码表达思想

即时编译(Just-In-Time Compilation,简称JIT)是一种编译技术,它在代码即将首次执行时进行编译,因此得名“即时编译”。JIT是动态编译的一种特例。随着时间的发展,JIT的概念已经扩展,现在常被用来泛指动态编译;然而,狭义的JIT编译与更广泛的动态编译之间仍存在区别。动态编译(Dynamic Compilation)指的是在程序运行时进行编译,而静态编译(Static Compilation,也称事前编译,Ahead-Of-Time Compilation,简称AOT)则是在程序运行前完成编译。自适应动态编译(Adaptive Dynamic Compilation)也是一种动态编译技术,它先让程序以某种方式运行,收集信息后再进行编译,从而实现更高层次的优化。

阅读全文 »

Elasticsearch 虽提供强大搜索功能,但默认排序在复杂业务场景下渐显局限。电商需综合商品热度、评分等因素排序;新闻平台看重时效性与权威性;企业知识管理系统要考虑文档重要性等。Function Score Query 应运而生,它允许开发者依据业务规则自定义打分机制,实现精准个性化排序,以满足多样化需求,提升用户搜索体验与业务竞争力。

阅读全文 »

IK 能把一段中文切成词,但在业务里,知道“切出了什么”有时还不够。面对“施耐德继电器”这样的文本,我还希望分词结果能告诉下游:施耐德 是品牌,继电器 是品类;遇到物料号时,也应该保留对应的业务类型。

普通扩展词典可以让 IK 识别新词,却不会自动区分品牌、品类和物料号。为了补上这部分信息,我顺着 IK 的词典加载与词元输出流程做了一次改造。这篇记录只整理其中最关键的路径,以及二开时实际改动的地方。

阅读全文 »

analysis, 中文意思分析,指的是es在文档发送之前对文档正文的执行过程,以添加到inverted index, 这一过程包含一下几个步骤:

  • 使用字符过滤器过滤字符,由char filter完成
  • 分词,将input按指定的规则分割为多个token, 由tokanizer完成
  • token过滤, 将上一步的tokens按一定规则进行处理, 由token filter完成
  • 存入索引
阅读全文 »

缓存作为一种介于应用程序和数据库之间的中间层,能够有效地存储频繁访问的数据,减少对数据库的直接查询操作,从而显著提高系统的性能。它通过将热点数据临时存储在快速访问的存储介质(如 Redis)中,使得后续的相同请求可以直接从缓存中获取数据,避免了重复的数据库查询开销,大大缩短了数据的响应时间。

然而,缓存的引入虽然带来了性能上的提升,却也引发了一系列新的问题,其中最为关键的就是缓存与数据库之间的数据一致性问题。由于缓存和数据库是两个独立的存储系统,它们的数据更新操作可能无法实时同步,导致在某些情况下,缓存中的数据与数据库中的数据不一致。这种不一致性可能会给用户带来困惑,影响业务的正常运行,例如用户可能获取到过期或错误的数据,进而影响用户体验和业务决策的准确性。

因此,在设计和实现包含缓存的系统架构时,如何确保缓存与数据库之间的数据一致性成为了一个亟待解决的重要问题。这需要深入分析各种可能导致数据不一致的场景和原因,研究并制定有效的治理方案,在保证系统性能的前提下,尽可能降低数据不一致出现的概率,以满足业务对数据准确性和可靠性的要求。

阅读全文 »

因为可见性问题导致处理器不同核心看到的内存操作顺序不一致,从而看起来就像是指令被重排了一样

JMM在编译器重排阶段, 会禁止特定类型的编译器重排, 在处理器重排序阶段,通过在指令之间插入内存屏障来禁止特定类型的处理器重排, 从而确保了在不同的编译器和不同的处理器平台上,不会应为重排序规则不一致导致非预期的运行结果

现代处理器与内存进行数据交互,会先将数据写到自己的缓冲区中, 每个处理器都有自己的缓冲区, 这种特性就会导致缓冲区中的数据和内存的数据存在不一致, 例如核心A执行两条指令, 指令1是更新x的值, 指令2是读取y的值, 核心A更新了x并写入缓冲区,然后去主存读了y; 核心B执行两条指令, 指令1是更新y的值, 指令2是读取x的值, 核心B更新了y的值写入缓冲区, 然后去主存读了x的值, 因为两个核心的缓冲器都还未刷新,所以他们读到的都是旧值, 或者是只刷新了一个缓冲区; 这些都会导致在指令层面看起来就像是发生了指令重排一样, 其实核心是因为可见性问题,所以也会有人将这种现象称之为内存系统指令重排。

阅读全文 »

单机容量不够时,把计算和数据分散到更多机器上,是一条很自然的扩展路径。普通服务器可以逐步增加,故障也有机会被限制在局部,系统不必长期依赖价格高昂且扩展空间有限的单机配置。

但分布式并没有消除复杂度,只是改变了复杂度出现的位置。原来由进程内调用、本地磁盘和同一个时钟隐含保证的事情,拆开后都要通过网络协议重新建立:服务要找到彼此,数据要分片和复制,节点要判断远端是否存活,还要对操作顺序和最终结果达成一致。

阅读全文 »

单机事务把提交和回滚封装在一个数据库内部。操作一旦跨过数据库或服务边界,情况就变了:一个节点已经执行成功,另一个节点没有响应,协调者既不能确认对方失败,也不能确认成功消息只是暂时没有返回。此时要解决的不只是如何提交,还包括故障期间要不要等待、恢复后如何续接,以及重复消息会不会把同一笔业务执行两次。

2PC、3PC 和 TCC 都试图组织一次跨节点提交,CAP、异步复制与 Quorum 则解释了网络分区出现后为什么总要舍弃一些东西。把这些内容放在一起看,可以更清楚地分辨:哪些方案在解决原子提交,哪些方案在调整一致性与可用性的边界。

阅读全文 »

C10K 讨论的是一台服务器如何同时维持一万条网络连接。这里的“一万”指并发连接数,不等同于每秒完成一万次请求。放到今天,这个数量并不夸张;但在“一条连接对应一个进程”的服务模型里,连接数增加意味着进程数也随之增加,服务器很快就会把大量资源花在连接之间的调度上。

I/O 多路复用改变的正是这一点:程序不再为每条连接准备一个独立进程,而是让少量线程同时观察大量文件描述符,只处理已经具备读写条件的连接。selectpollepoll 都在解决这个问题,只是随着连接数上升,它们查找就绪事件的成本差别很大。

阅读全文 »

多个线程共同操作一份状态时,仅仅保证“一次只有一个线程修改”还不够。消费者拿到锁以后可能发现队列为空,生产者拿到锁以后也可能发现队列已满;它们需要在条件不成立时暂停,并在状态变化后继续竞争执行机会。

管程把共享状态、访问状态的操作、互斥规则和条件等待放进同一个抽象里。Java 的 synchronizedwait/notify 组合正体现了这套思路:前者保护状态,后者协调线程什么时候可以继续。

阅读全文 »
0%