LSM树
LSM树(Log-Structured-Merge-Tree)是一种采用追加写、数据有序以及将随机 I/O 转换为顺序 I/O的延迟更新,批量写入硬盘的数据结构
牺牲了小部分读性能,而大幅度提高了写性能,所以很适合写多读少的场景,已经用于LevelDB等开源产品中,阿里云的OTS也是基于此。
LSM树会将所有的数据插入、修改、删除等操作保存在内存中,当此类操作达到一定的数据量后,再批量地写入到磁盘当中。而在写入磁盘时,会和以前的数据做合并。在合并过程中,并不会像B+树一样,在原数据的位置上修改,而是直接插入新的数据,从而避免了随机写。
结构LSM树的结构是横跨内存和磁盘的,包含memtable、immutable memtable、SSTable等多个部分。
memtable
memtable是在内存中的数据结构,用以保存最近的一些更新操作,当写数据到memtable中时,会先通过WAL的方式备份到磁盘中,以防数据因为内存掉电而丢失。
预写式日志(Write-ahead logging,缩写 WAL)是关系数据库系统中用于提供原子性和持久性(ACID属性中的两个) ...
实现LRU
要求这也是leetcode的题目之一——《146. LRU缓存设计》
运用你所掌握的数据结构,设计和实现一个 LRU (最近最少使用) 缓存机制。它应该支持以下操作: 获取数据 get 和 写入数据 put。
进阶:
你是否可以在 O(1) 时间复杂度内完成这两种操作?
解答:双链表 + 哈希表
可以使用 HashMap 存储 key,这样可以做到 save 和 get key 的时间都是 O(1),而 HashMap 的 Value 指向双向链表实现的 LRU 的 Node 节点
用队列可以吗
不行,队列只能做到先进先出,但是重复用到中间的数据时无法把中间的数据移动到顶端。
用单链表不行吗
单链表能实现新来的放头部,最久不用的在尾部删除。但删除的时候需要遍历到尾部,因为单链表只有头指针。在用到已经用到过的数据时,还要遍历整合链表,来确定是否用过,然后再遍历到响应位置来剔除的节点,并重新放在头部
代码12345678910111213141516171819202122232425262728293031323334353637#include <list> ...
Rocketmq原理
术语
Topic 主题:即为发布或者订阅的主题,topic一般由多个队列组成,队列会平均的散列到多个Broker上面。Producer的发送机制会保证消息尽量平均的散列到所有队列上面去,最终的效果是所有的消息会平均的落在每个Broker上面。
Broker : 主要负责消息的存储、投递和查询以及服务高可用保证。即消息队列服务器,生产者生产消息到 Broker ,消费者从 Broker 拉取消息并消费。一个 Topic 分布在多个 Broker上,一个 Broker 可以配置多个 Topic ,它们是多对多的关系。如果某个 Topic 消息量很大,应该给它多配置几个队列,并且尽量多分布在不同 Broker 上,以减轻某个 Broker 的压力 。Topic 消息量都比较均匀的情况下,如果某个 Broker 上的队列越多,则该 Broker 压力越大。
NameServer : 本质上是一个注册中心,主要提供两个功能:Broker管理和路由信息管理 。Broker会将自己的信息注册到 NameServer 中,此时 NameServer 就存放了很多 Broker 的信息(Broker的路 ...
微服务架构下的Java ThreadLocal实践——缓存重复RPC结果
背景微服务架构下,自己负责的业务需要调用其他业务提供的服务,服务调用一般是通过RPC(Remote Procedure Call,远程过程调用)来完成的
根据业务复杂度,代码复杂度也可能比较高,函数层数可能会较深,纵观整个函数栈,可能会多次调用业务B提供的BService,甚至有可能入参都是一样的
假设互联网用户点外卖的场景,有这样的业务逻辑(仅作示例),整个流程的入口在OrderServiceImpl类的order方法:
123456789101112131415161718192021222324252627282930313233343536373839404142434445class OrderServiceImpl { // 假设BService提供用户的信息 @Autowired BService bService; ... public Result order(String userId, Product product) throws ServiceException{ // 判断用户是 ...
记一次由gmock引发的代码重构
背景某次在写完业务代码后,我习惯性地把单测补齐(团队要求单测增量覆盖率80%,发布会检测这一指标进行卡点),我们的C++代码使用的是gmock+gtest这套框架,https://google.github.io/googletest/gmock_for_dummies.html
增量代码的单测一般流程是这样的:假设A类某函数foo中,新增了几行代码,其中调用了B类的testMethod函数,
使用gmock_gen.py脚本(gmock提供),根据B.h文件生成B.mock文件
新建或打开A_test.cpp单测文件,在其中编写A类的单测代码
mock掉testMethod函数,使用Assert_xx来判断foo函数的执行流是否符合预期、关键出参入参是否符合预期
现象这次改动,我需要在B类的testMethod中新增了一个参数param11,测试代码如下:
123456class B { ... virtual bool testMethod(const std::string ¶m1, const std::string ¶m2 ...
微服务架构下的Java Parallel Stream实践——优化RT与串联traceid
背景
在业务开发中,需要将一些图片mediaId批量调用多媒体服务的接口(FileService#copyMedia),从而实现图片替换的功能
具体链路如下,客户端RT可认为从一个请求从发出到响应的经过时间:
12345用户客户端 --> MyService --> FileService#copyMedia ↓ LaterService ↓用户客户端 <-- ....
第一版代码
因为项目工期较紧,第一版代码重点主要在功能完整性和准确性上,当时的做法是批量解析图片id,再批量调接口,再批量替换原位
在跨团队的开发联调与端到端测试后,第一版代码发布上线了,功能符合预期,也没啥bug,就是我们的报警群经常报rpc timeout的报警,客户端接口的RT也涨了不少(特别是99分位RT)
经过定位发现,copyMediaId接口会去访问oss生成新的mediaId,当资源跨地域时,RT显著增大,而且有些特定行业的用户(如电商)可能请求中有30、40张图片,这对copyMediaI ...
缓存一致性
缓存一致性缓存在计算机系统是无处不在,在CPU层面有L1-L3的Cache,在Linux中有TLB加速虚拟地址和物理地址的转换,在浏览器有本地缓存、手机有本地缓存等。缓存在计算机系统中有非常重要的地位,其主要作用是提高响应速度、减少磁盘访问等,本文主要讨论在高并发系统中的缓存系统。
一般来说,缓存由三座大山需要跨越,雪崩、穿透、击穿
缓存雪崩 Cache Avalanche理解:系统像雪崩一样崩了
问题:如果缓存系统故障,大量的请求无法从缓存完成数据请求,就全量汹涌冲向磁盘数据库系统,导致数据库被打死,整个系统彻底崩溃。比如由于大量的热数据设置了相同或接近的过期时间,导致缓存在某一时刻密集失效,大量请求全部转发到 DB,或者是某个冷数据瞬间涌入大量访问,这些查询在缓存 MISS 后,并发的将请求透传到 DB,DB 瞬时压力过载从而拒绝服务。
解决方案:目前常见的预防缓存雪崩的解决方案,主要是通过对 key 的 TTL 时间加随机数,打散 key 的淘汰时间来尽量规避,但是不能彻底规避。
缓存穿透 Cache Penetration理解:请求过来了 转了一圈 一无所获 就像穿过透明地带一 ...
Java8函数式编程
Java8 函数式编程Lambda表达式
Lambda表达式可以简化匿名内部类
普通的匿名内部类:在这个例子中,我们创建了一个新对象,它实现了ActionListener 接口。这个接口只有一个方法actionPerformed,当用户点击屏幕上的按钮时,button 就会调用这个方法。匿名内部类实现了该方法。
123456例2-1 使用匿名内部类将行为和按钮单击进行关联button.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent event) { System.out.println("button clicked"); }});
若用Lambda表达式,则可以化简,注意event不需要指定类型,后台可以根据addActionListener的函数签名推断event的类型
12例2-2 使用Lambda 表达式将行为和按钮单击进行关联button.addActionListener(event -&g ...
业界常用的分布式ID生成算法
分布式id生成
对于IM应用来说,消息ID(或称序列号)是个看似不起眼,但非常重要的东西之一。
1)UUID:这种方法简单直观,可以很好的保证唯一性,但对于技术洁癖的人来说ID长度会有点长,而且是无序的;
2)使用时间戳长整数:这个最偷懒,用在吞吐量不大的场景下,凑活也能用,但存在重复的风险,也无法保证分布式下的唯一性;
3)使用twitter开源的snowflake算法:在分布式高并发的情况下,这也是个不错的选择;
4)按用户使用独立的ID生成空间生成顺序的ID:比如微信的消息序列号生成策略就很不错。
分布式唯一id:snowflake算法思考snowflake算法的Java实现
snowflake是Twitter开源的分布式ID生成算法,结果是一个long型的ID。其核心思想是:使用41bit作为毫秒数,10bit作为机器的ID(5个bit是数据中心,5个bit的机器ID),12bit作为毫秒内的流水号(意味着每个节点在每毫秒可以产生 4096 个 ID),最后还有一个符号位,永远是0。
优点:快,简单,熟悉原理后可更根据时机情况调整位段
缺点:依赖机器时间。解决方法可参考美团 ...
C++新特性
C++11 新特性(现在已经不新了)auto关键字:编译器根据初始值自动推导出类型,但是不能用于函数传参以及数组类型的推导,这是编译器在编译阶段完成的,没有改变C++是静态语言的事实。
auto 是值类型
auto 一般会忽略顶层const,请显式的指定const autos
auto& 是左值引用类型
auto&& 是转发引用(可以是左值引用,也可以是右值引用)
但如果想根据表达式获得对应的值类别,C++14可以使用decltype(auto) a = expr;,之前只能使用啰嗦地decltyp(expr) a = expr;
nullptr:一种特殊的空指针类型,能转换成其他任意类型的指针,而NULL一般被宏定义为0,遇到重载时可能会出现问题
decltype:查询表达式的类型,不会对表达式求值,经常与auto配合追踪函数的返回值类型,
decltype可返回表达式所表示类型(包括顶层const与引用)
decltype((variable))的结果永远是引用,而decltype(variable)的结果只有当variable本身是引用时才是引用
...