(译)Handles are the better pointers

作者的一个经验,把指针转换成index-handles,也就是如何写不涉及分配和指针的程序

背景

  • 0.5到1百万行代码,智能指针管理内存
  • 万到十万以上的小对象,堆分配,智能指针管理

这种管理方式基本不会有segfault,有问题的代码也都能抓到问题根源

但是存在问题

  • dog-slow?遍地都是cache-miss
  • 内存碎片问题
  • fake memory leaks 内存指针占用的内存没有及时回收,看上去像是内存泄漏了,而这种问题通过内存检测工具也检查不出来

作者给的方法有一定借鉴意义(甚至对于那些GC语言,因为会面临同样的场景,大量小对象)

但是可能要要求创建销毁对象尽可能的集中,某些场景可能不可行,也就是面向数据

这里的使用场景是游戏场景,有大量小对象频繁创建删除

简要概括

  • 所有的内存管理集中成一个模块,用这个模块来管理分配
  • 把小对象分类,每个小对象有自己的数组指针-索引来管理
  • 创建对象,返回index-handle就可以了
  • 需要的话,就把handle强转指针来用,并且任何地方都不要保存指针

感觉这是个很常见的CRTP分配器套路,尽可能的复用内存,对于代理型应用,都会这么设计,作者是游戏型应用,也是有大量小对象大量的创建删除的。一个好的分配器能降低系统碎片且让cpu利用率高,不轻易cache miss –这些都影响延迟

下面作者说了针对他们这个场景的几点优势

  • 管理高效
  • cache friendly
    • 内存数据尽可能的热
  • 不会有很多的内存碎片

全面用这个index-handle替换指针的优势

  • 避免直接访问内存,安全
  • 只有分配器组件知道这个内存分配的细节和指针信息,别人只能通过index拿到 (其实index也可以隐藏起来)
  • 内存增长更安全,会尽可能复用,不会激增

但是这要求应用本身不能乱用指针,因为引用的对象随时会变

这个东西和普通的CRTP 内存池还不太一样。代码我还没有读。有些作者的设计点我没有领会完整。后面有时间会看看

ref

  • https://floooh.github.io/2018/06/17/handles-vs-pointers.html
  • 作者的封装库 https://github.com/floooh/sokol#sokol_gfxh
  • 背景文档 https://floooh.github.io/2018/06/02/one-year-of-c.html 也值得一看
  • 我是看lobste上的推荐看的这篇文章,还有https://ourmachinery.com/post/data-structures-part-1-bulk-data/ 后面也要看看

Read More

Seastar资料整理以及介绍

  • https://forrestsu.github.io/posts/archi-seastar/seastar-future-promise/ future promise原理

future promise穿成链表,包装task回调

  • https://zhuanlan.zhihu.com/p/113119124

介绍了主要特点 核间通信,以及一些优化上的优点

用到的无锁队列

使用的应用

cpv-framework: A web framework written in c++ based on seastar framework

redpanda: A Kafka replacement for mission critical systems

Scylla: A fast and reliable NoSQL data store compatible with Cassandra and DynamoDB

smf: rpc框架,用的fbs

seastar侵染性太强,沾上就得用


Read More

folly资料整理以及介绍以及一些看到的用法

  • IOThreadPool :每个线程中包含一个 EventLoop ,即一个 epoll 的处理器。添加任务时,添加到通知队列, epoll 循环可以收到通知,处理任务。额外还可以添加IO事件回调。
  • CPUThreadPool :和 IOThreadPool 相比,它简单一些,添加任务时,添加到阻塞队列,以信号的形式通知线程,空闲线程执行任务。
  • ConcurrentHashMap 基于hazard pointer https://zhuanlan.zhihu.com/p/104308755
  • 为啥有concurrenthashmap还要又个atomichashmap? 实现原理差不多 https://github.com/facebook/folly/blob/master/folly/concurrency/ConcurrentHashMap.h
  • iobuf 参考的tcp协议栈里的mbuf https://github.com/facebook/folly/blob/master/folly/io/IOBuf.h

    • 这个东西我看redpanda也用了。要省network 浪费, iobuf避免不了

wangle介绍

https://my.oschina.net/fileoptions/blog/881909

  • Synchronized lock guard和metux的封装。省事儿了
  folly::Synchronized<Meta> meta_;
  meta_.withRLock([&](auto& cluster_meta) {
 .......
  });
  • Singleton

一个组合使用的例子

namespace {
struct PrivateTag {};
}  // namespace
namespace my {

class Scheduler {
 private:
  std::unique_ptr<folly::CPUThreadPoolExecutor> executor_;

 public:
  static std::shared_ptr<Scheduler> GetInstance();
  Scheduler() {
  executor_ = std::make_unique<folly::CPUThreadPoolExecutor>(
      4, std::make_shared<folly::NamedThreadFactory>("worker"));}
  ~Scheduler() { executor_->stop(); }
};

static folly::Singleton<Scheduler, PrivateTag> scheduler_singleton;
std::shared_ptr<Scheduler> Scheduler::GetInstance() { return the_singleton.try_get(); }
}

比较常规没啥说的

  • ThreadLocalPRNG

底层实现是thread local singleton,所以就规避了反复创建random device的问题

folly::ThreadLocalPRNG rng;
auto rand = folly::Random::rand32(min, max, rng);
  • folly::ReadMostlySharedPtr

读优化的shared ptr。不过用了这个就不能和普通shared ptr互通了

  • ThreadCachedInt

原理可以看这个https://blog.csdn.net/cjk_cynosure/article/details/109320285

思路就是thread_local 然后聚合一下。这样就避免了原子加减

之前也见过类似的思路,https://travisdowns.github.io/blog/2020/07/06/concurrency-costs.html

原子数组+线性探测,大量使用ThreadCachedInt

没啥说的


Read More

七月待读 need review

我发现越攒越多了这东西

https://medium.com/swlh/building-rest-api-backed-by-redis-ae8ff4818460

https://github.com/mhewedy-playground/RecipeAPI/blob/master/main.go

https://tech.marksblogg.com/omnisci-macos-macbookpro-mbp.html

https://medium.com/google-cloud/spanners-sql-story-79bda8bb632d

https://zhuanlan.zhihu.com/p/141891855

https://fauna.com/blog/demystifying-database-systems-part-4-isolation-levels-vs-consistency-levels

https://pingcap.com/blog/how-tidb-htap-makes-truly-hybrid-workloads-possible/

https://blog.yugabyte.com/why-distributed-sql-beats-polyglot-persistence-for-building-microservices/?utm_source=db_weekly&utm_medium=sponsored_link&utm_campaign=july172020

https://vladmihalcea.com/sql-left-join/

https://tiledb.com/blog/tiledb-closes-15m-series-a-for-industry-s-first-universal-data-engine-2020-07-14

https://ketanbhatt.com/db-concurrency-defects/

leveldb pdf 已经读过了。记录一下

https://yuerblog.cc/wp-content/uploads/leveldb%E5%AE%9E%E7%8E%B0%E8%A7%A3%E6%9E%90.pdf

C++ 中把 lambda 优雅地转化为函数指针 · Terark/terarkdb Wiki

https://github.com/Terark/terarkdb/wiki/C%EF%BC%8B%EF%BC%8B-%E4%B8%AD%E6%8A%8A-lambda-%E4%BC%98%E9%9B%85%E5%9C%B0%E8%BD%AC%E5%8C%96%E4%B8%BA%E5%87%BD%E6%95%B0%E6%8C%87%E9%92%88

Read More

python笔记以及遇到的坑

## pyyaml

  • pyyaml会有特殊的字符串转换,比如把yes转换成true,所以必须要把yes用引号括起来,或者定制yaml的构造函数,定制resolver,见参考链接的做法。

    • 修改意见:主动引号括起来。或者换名字。定制resolver杀鸡牛刀
  • pyyaml对于一些key会加引号,当初由于上层应用不是标准yaml库解析,不能解析引号,所以采用了ruamel.yaml这个库,设置指定的dumper可以让字段不带引号,但是这个库的实现有很大问题,建议别用,而且你能搜到该库作者的很多SO网站回答推广,真有你的啊

    • 不要用,出来混早晚要还的。千万别取巧
  • eventlet 0.25版本,会依赖dnspython库,这个库不能用2.0版本,接口不兼容

    • 解决方法 固定dnspython在1.16或者升级eventlet到0.25.2以上

xml

之前以为xml库难用,其实是我不理解xml

hdfs的配置文件格式xml ,我需要解析一个配置项,改值

  <property>
      <name>dfs.client.failover.max.attempts</name>
      <value>10</value>
      <description>
      if multiply namenodes are configured, it is the max retry times when the dfs client try to issue a RPC call. default is 75.
      </description>
  </property>
  <property>
      <name>dfs.ratelimiter.enabled</name>
      <value>false</value>
      <description>
      ratelimiter enabled or not.
      </description>
  </property>
  
  <property>
      <name>limiter.bandwidth</name>
      <value>62914560</value>
      <description>
      the limited highest bandwidth.
      </description>
  </property>
  
  <property>
      <name>limiter.iops</name>
      <value>60000</value>
      <description>
      the limited highest iops.
      </description>
  </property>
  

主要是限流的改动,全去掉

依据我之前使用json yaml的经验,都是kv型的,怎么也改不了value字段的值

部门高手写了这么段代码,真暴力

xml是树型的,都是子节点,也就是说上面匹配到了text,那下一个肯定是value字段。。。

我之前的考虑就是奔着value字段改的。笨比了。

  import sys
  import xml.etree.ElementTree as ET
  
  tree = ET.parse('hdfs-client.xml')
  root = tree.getroot()
  
  found = False
  for child in root.iter():
      if child.text == 'dfs.client.failover.max.attempts':
          found = True
          continue
      if found:
          child.text = "111"
          found = False
          break
  
  
  tree.write("hdfs-client.xml")
  
  • xml真是太让我头疼了

进程连接不上

  • 网络问题
  • 进程卡死

不要很自信的说网络问题。自己模块写的有问题进程卡死也是有可能的

Python核心语法

  • 共享引用 在原处更改

is == 区别

  • 小的整数和字符串缓存复用了 sys.getrefcount(1)

  • 函数
    • def 生成函数对象并复制给函数名 lambda直接返回函数
    • yield 返回一个结果对象,保存上下文
    • global 模块级变量 nonlocal 类似global,用于闭包
    • 闭包,有点像c++里的函数对象 python中类实现__call__(self)也行,书上说这种写法过于重型
  • 参数
    • 传参是引用还是传值 In-Place Change
    • 避免 主动传值[:],传tuple
    • 任一参数*pargs kargs pargs是元组 **kargs是字典,用来解包 引申-》Keyword-Only 用来指定元组和字典。单独使用做占位参数,则需要调用方保证指定所有入参(a=8这种)注意必须是最后一个参数
  • 再谈函数
    • map(functor, iterable-obj) std::for_each filter std::remove functools.reduce std::acc,
    • 迭代,next()语法糖
      • yield,生成器,for,range这些都是生成器函数
      • 生成器表达式
      • 可迭代对象
  • 模块
    • import from from 危害,模块冲突
    • import 决定将__name__ = ‘main‘解释成模块还是函数
    • 运算符重载
    • __str__
    • __repr__
    • 继承,真正意义上的接口。不会调用基类构造函数
from abc import abstractmethod
@abstractmethod

__getitem__ 买一送一

_iter__

__next__

__del__

__getaddr__ 属性钩子

__slot__

  • 装饰器与元类
    • 类做装饰器
    • 函数装饰类 元类
class tracer:
  def __init__(self, func):
    self.func = func;
  def __call__(self,*args):
    self.func(*args)
  • 异常
    • try except finally
    • raise assert
    • with as
      • with 环境管理协议 enter exit
      • 主动释放with内资源。包括多线程的锁条件变量之类dir

ref

  • https://stackoverflow.com/questions/34282703/yes-or-no-dumping-as-yes-or-no-in-pyyaml
  • https://github.com/eventlet/eventlet/issues/619

Read More

(译)Beyond Relational Databases:A Focus on Redis, MongoDB, and ClickHouse


percona的什么白皮书,我看就是一堆吐槽

关系型数据库不是万金油,用错反而是瓶颈

数据模型万金油,反而限制了扩展能力

sql好用但是不够灵活,json人人爱

ACID很重要,但是代价也很大,这也是关系型数据库的采用点

由于保证事务可靠,也就限制了扩展能力,数据模型简单不要求事务,上内存kv数据库

mysql也能存大数据,但是能存不能用,没啥意义,所以去掉索引,去掉关系限制,列式数据库诞生了


KV数据库

最开始的目的是做个数据库前端无状态缓存

要快,一般都是hashtable。不是为了替代关系型数据库,只是一项中间件技术

后来KV数据库发展成有简单协议的有命令有状态的服务了。不得不提redis

个人认为 redis能打败memcache,社区好,功能性强,memcache就是单纯的做无状态中间件。这种东西mysql内部也做小型cache,比如2Q啥的。不如一个组件强。但是组件本身也要考虑淘汰以及热点击穿的问题。这就是另一个话题了

典型代表redis提供了一些很好的特性,能让业务更简化,比如TTL, list,比如lua支持,LRU淘汰策略,多key事务,阻塞命令等等

做mysql前端最好不过,做个纯粹的数据库也可以(不落盘不停机)


文档数据库

最早来自邮件存储需求

json对象,sparse index 不存在的key就不要,非常灵活

灵活模式不等于没有模式

典型代表 mongodb 不止提供文档接口,还提供了分片管理,方便水平扩展,加钱上机器就行了

还有一些牛逼特性,比如聚合动作方便map-reduce ,geo相关功能,以及可插拔引擎(wiretiger/rocksdb)

劣势

嵌套表效率低

多表join查询

多表事务


列式存储

只要几列数据为什么不用覆盖索引?

  • 查询灵活难道要每一列都建索引吗?不现实
  • 插入要改动索引,复杂度爆炸延迟爆炸

作者没用Cassandra和hbase举例,而是用了clickhouse

  • 兼容大多数SQL
  • 也有索引,针对MergeTree引擎,支持主键,支持range查
  • Dictionaries 查表不用join
  • 低延迟,查询不用担心阻塞
  • 近似估计等等特殊特性

对于读敏感的大数据集,十分好用

OLTP


ref
  1. https://learn.percona.com/hubfs/ExpertOpinion/Beyond%20Relational%20Databases.pdf

Read More

改造glog 提供日志轮转


这个是同事的修改经验,写的挺有意思的,我直接抄过来了

glog是写日志的。选型pika使用,没改。存在的弊端

  • 没有日志删除。最新glog是按照日志轮转的,而不是按照个数/大小轮转
    • glog支持按照大小来切割轮转,但是不支持清理,清理,一个unlink的事儿。
  • 各种日志级别的信息是区分保存的,对于查看来说很不方便

我的同事在这两点上根据pika已有的结构来进行优化,优化的很巧妙:

  • 加上日志个数的glog接口,暴露出来,也就是实现一个接口来unlink
    • 对应的,所有的日志名字要保留到一个数组里。那这个数组的更新也要加锁
      • LogFileObject::CreateLogfile 更新数组,新增接口里改数组
    • 不能频繁检查。放到epoll时间时间里,定时触发
  • 对于第二点,在LogFileObject::Write里有具体的格式,level,去掉就行。LogDestination::FlushLogFilesUnsafe里去掉不同level的flush,改成一个

LogFileObject是具体的操作 LogDestination是底层具体的写入,会持有LogFileObject对象,所有接口从LogMessage暴露

总之挺巧妙的。学习一波经验

ref

  1. https://github.com/google/glog

Read More

改造pika如何去掉key锁


这个是同事的修改经验,虽然是针对业务而言的,但是这个思路十分的降维打击,我直接抄过来了

现有模型是slot-proxy-shard结构,上层有代理来转发,shard节点,也就是pika,只负责一部分数据

但是pika本身是有key锁的

比如https://github.com/Qihoo360/blackwidow/blob/ae38f5b4c5c01c7f8b9deec58db752e056659264/src/redis_lists.cc#L273

Status RedisLists::LInsert(const Slice& key,
                           const BeforeOrAfter& before_or_after,
                           const std::string& pivot,
                           const std::string& value,
                           int64_t* ret) {
  *ret = 0;
  rocksdb::WriteBatch batch;
  ScopeRecordLock l(lock_mgr_, key);
  std::string meta_value;
  Status s = db_->Get(default_read_options_, handles_[0], key, &meta_value);
  if (s.ok()) {
    ParsedListsMetaValue parsed_lists_meta_value(&meta_value);
    if (parsed_lists_meta_value.IsStale()) {
      return Status::NotFound("Stale");
    } else if (parsed_lists_meta_value.count() == 0) {
      return Status::NotFound();
    } else {
        ...

前面已经有一层proxy转发了,这层转发是根据hash来算还是根据range来算不重要,重要的是到计算节点,已经缩小了key范围了,还要加锁,这层锁是可以优化掉的

  • 保证落到这个db的key的顺序,也就是说,相同hash/range用同一个连接,保证了命令的顺序,就不会有锁的问题,锁也就可以省掉。从上层来解决掉这个锁的问题
    • shard节点的命令处理线程,要保证hash/range相同的用同一个连接线程工作。多一点计算,省掉一个锁。

pika单机版,用的是同一个db,同一个db是很难去掉key锁的。要想应用上面的改造,也要改成多db模式,把db改成slot,然后根据slot划分线程,然后根据线程来分配命令,保证命令的顺序,省掉锁。

省掉key锁的收益是很大的。尤其是一个命令里有很多次get,get很吃延迟,导致锁占用很长,导致积少成多的影响


Read More

rocksdb涉及到关闭开启的时间优化


这个是同事调参的经验,我直接抄过来了

不少都是经常提到的

rocksdb配置:

  • compaction加速设置compaction_readahead_size 很常规。值可以改改试试,16k 256k都有
  • wal日志调小 max_manifest_file_size, max-total-wal-size
  • close db的时候停掉compaction
    • rocksdb里有compaction任务,可能还会耗时,能停掉就更好了
  • close会主动flush flush可能触发compaction和write stall。先跳过
  • open会读wal恢复memtable,所以最好不要有wal,close的时候刷掉
  • targetbase和放大因子要根据自身的存储来调整 比如写入hdfs,设置60M就比较合适,不会频繁更新元数据

打开rocksdb文件过多 GetFileSize时间太长

  • 看到一个PR https://github.com/facebook/rocksdb/pull/6353/files 6.8版本在open阶段规避调用 GetFileSize

  • 我们这边同事的解决方案是hack OnAddFile调用的地方,改成多线程cv来添加。

这两个优化点位置不一样,一个在打开 恢复校验阶段,一个在打开阶段,这个打开阶段OnAddFile有GetFileSize,所以在OnAddFile之前,调用一把查文件大小,避开GetFileSize

    std::vector<LiveFileMetaData> metadata;

    impl->mutex_.Lock();
    impl->versions_->GetLiveFilesMetaData(&metadata);
    impl->mutex_.Unlock();

后面OnAddFile阶段就不用查size了,通过这里拿到的metadata,但是有的文件查不到,这里,原生rocksdb用的是GetFileSize,同事是用多线程异步去查,相比原生一个一个查能加速一点


Read More


^