pika中锁的应用

参考代码:pika-2.4.0
pika作为类redis的存储系统,为了弥补在性能上的不足,在整个系统中大量使用多线程的结构,涉及到多线程编程,势必需要为线程加锁来保证数据访问的一致性和有效性。其中主要用到了三种锁:

简介

1.互斥锁

pika-2.4.0
./include/pika_binlog.h:  slash::Mutex mutex_;  //保证写入binlog的命令顺序性
./include/pika_hub_manager.h:  slash::Mutex hub_stage_protector_;
./include/pika_hub_manager.h:  slash::Mutex sending_window_protector_;
./include/pika_monitor_thread.h:  slash::Mutex monitor_mutex_protector_;
./include/pika_slot.h:    slash::Mutex slotsmgrt_cond_mutex_;
./include/pika_server.h:  slash::Mutex slave_mutex_; // protect slaves_;
./include/pika_server.h:  //slash::Mutex slotsmgrt_protector_;
./include/pika_server.h:  slash::Mutex binlogbg_mutex_; //主从binlog同步
./include/pika_server.h:  slash::Mutex slowlog_mutex_;
./include/pika_server.h:  slash::Mutex ping_thread_protector_; //主从维护心跳

//admin命令中比如bgsave 、dbsync、keyscan之间会有互斥操作
在flushall命令中添加bgsave_protector_、bgsave_protector_是否多余)
./include/pika_server.h:  slash::Mutex bgsave_protector_;
./include/pika_server.h:  slash::Mutex db_sync_protector_;
./include/pika_server.h:  slash::Mutex key_scan_protector_;

2.读写锁

pika2.4.0

./include/pika_binlog.h:  pthread_rwlock_t rwlock_; //dealmsg中可写命令都要记录入binlog中,且都需按序写入
./include/pika_conf.h:  pthread_rwlock_t rwlock_;   //conf中各配置项使用的读写锁
./include/pika_slot.h:    pthread_rwlock_t rwlock_db_;
./include/pika_slot.h:    pthread_rwlock_t rwlock_batch_;
./include/pika_slot.h:    pthread_rwlock_t rwlock_ones_;
./include/pika_server.h:  pthread_rwlock_t rwlock_; //对db保护,flushall、flushdb、ReadonlyCmd获取写锁,其他dealmsg中命令(set、get等)获取读锁
./include/pika_server.h:  pthread_rwlock_t state_protector_; //protect below, use for master-slave mode


pika3.2

./include/pika_binlog.h:  pthread_rwlock_t rwlock_;  
./include/pika_binlog_reader.h:  pthread_rwlock_t rwlock_;
./include/pika_cmd_table_manager.h:  pthread_rwlock_t map_protector_;
./include/pika_conf.h:  pthread_rwlock_t rwlock_;
./include/pika_meta.h:  pthread_rwlock_t rwlock_;
./include/pika_partition.h:  pthread_rwlock_t db_rwlock_;
./include/pika_repl_server.h:  pthread_rwlock_t client_conn_rwlock_;
./include/pika_rm.h:  pthread_rwlock_t partitions_rw_;
./include/pika_server.h:  pthread_rwlock_t tables_rw_;
./include/pika_server.h:  pthread_rwlock_t state_protector_; //protect below, use for master-slave mode
./include/pika_server.h:  pthread_rwlock_t slowlog_protector_;   
./include/pika_table.h:  pthread_rwlock_t partitions_rw_;

3.行锁

./include/pika_server.h:  slash::RecordMutex mutex_record_;//同一时间只有一个线程对一个key进行写操作

锁在命令中应用

下面从命令分类的角度去看不同类型的命令。在pika执行过程中,需要加几道锁。
1.Suspend的admin命令:trySync、BgSave、FlushAll、Flushdb、readonly

   写锁:RWLockWriter(rwlock_)-->互斥锁:slash::Mutex(bgsave_protector_、db_sync_protector_等)-->跟命令本身操作相关的其他锁
   注意:并不是所有的admin命令都会写入binlog中,目前查看的只有flush相关命令会。

2.非Suspend的admin命令 :info、config等

   读锁:RWLockReader(rwlock_)-->跟命令本身操作相关的其他锁

3.读命令:get、hget等

   读锁:RWLockReader(rwlock_)

4.写命令:set、hset等

   pika2.4 读锁:RWLockReader(rwlock_)--->pika行锁slash::RecordMutex(mutex_record_)-->binlog互斥锁g_pika_server->logger_->Lock()
   pika3.2 读锁:RWLockReader(rwlock_)--->pika行锁slash::RecordMutex(mutex_record_)-->blackwidow记录锁:ScopeRecordLock(lock_mgr_)-->binlog互斥锁g_pika_server->logger_->Lock()

应用场景

1.挂起指令

在挂起指令的执行中,会添加写锁,以确保,此时没有其他任何指令的执行。其他指令(非suspend的admin命令以及读写命令)会添加读锁,可以并行访问。
其中pika-2.4.0挂起指令有:

trysync
bgsave
flushall
Flushdb
readonly

作用和意义:
保证当前服务器在执行挂起指令时,起到阻写作用。比如flush操作会涉及到创建新db的操作,此时其他命令都不应该执行。

2.行锁

行锁,用于对一个key加锁,保证同一时间只有一个线程对一个key进行操作。
在pika2.4.0中只应用于对key的写操作(写命令)。

作用和意义:
pika中存取的数据都是(key,value)数据,不同key所对应的数据完全独立,所以只需要对key加锁可以保证数据在并发访问时的一致性,行锁相对来说,锁定粒度小,也可以保证数据访问的高效性。

应用场景:
在pika系统中,主要在应用于两个地方,在系统上层指令过程中和在数据引擎层面。对于写指令(如SET,HSET)除了需要更新数据库状态,还涉及到pika的增量同步,需要在binlog中添加所执行的写指令,用于保证master和slave的数据库状态一致。故一条写指令的执行,主要有两个部分:

  1. 更改数据库状态
  2. 将指令添加到binlog中

pika3.2 其加锁情况,如下图(图片来源于网络):


image.png

在图中可以看到,pika对同一个key,加了两次行锁.在实际应用中,pika上所加的锁就已经能够保证数据访问的正确性。如果只是为了pika所需要的业务,层面使用行锁是多余的,但是blackwidow的设计初衷就是通过对rocksdb的改造和封装提供一套完整的类redis数据访问的解决方案,而不仅仅是为pika提供数据库引擎。这种设计思路也是秉承了Unix中的设计原则:Write programs that do one thing and do it well。

这样设计大大降低了pika与blackwidow之间的耦合,也使得blackwidow可以被单独拿出来测试和使用,在pika中的数据迁移工具就是完全使用blackwidow来完成,不必依赖任何pika相关的东西。

具体实现:
在pika系统中,一把行锁就可以维护所有key。在行锁的实现上是将一个key与一把互斥锁相绑定,并将其放入哈希表中维护,来保证每次访问key的线程只有一个,但是不可能也不需要为每一个key保留一把互斥锁,只需要当有多条线程访问同一个key时才需要锁,在所有线程都访问结束之后,就可以销毁这个绑定key的互斥锁,释放资源。具体实现如下:

class RecordLock {
 public:
  RecordLock(port::RecordMutex *mu, const std::string &key)
      : mu_(mu), key_(key) {
        mu_->Lock(key_);
      }
  ~RecordLock() { mu_->Unlock(key_); }

 private:
  port::RecordMutex *const mu_;
  std::string key_;

  // No copying allowed
  RecordLock(const RecordLock&);
  void operator=(const RecordLock&);
};

void RecordMutex::Lock(const std::string &key) {
  mutex_.Lock();
  std::unordered_map<std::string, RefMutex *>::const_iterator it = records_.find(key);

  if (it != records_.end()) {
    //log_info ("tid=(%u) >Lock key=(%s) exist, map_size=%u", pthread_self(), key.c_str(), records_.size());
    RefMutex *ref_mutex = it->second;
    ref_mutex->Ref();
    mutex_.Unlock();

    ref_mutex->Lock();
    //log_info ("tid=(%u) <Lock key=(%s) exist", pthread_self(), key.c_str());
  } else {
    //log_info ("tid=(%u) >Lock key=(%s) new, map_size=%u ++", pthread_self(), key.c_str(), records_.size());
    RefMutex *ref_mutex = new RefMutex();

    records_.insert(std::make_pair(key, ref_mutex));
    ref_mutex->Ref();
    mutex_.Unlock();

    ref_mutex->Lock();
    //log_info ("tid=(%u) <Lock key=(%s) new", pthread_self(), key.c_str());
  }
}
完整代码可参考:slash_mutex.cc slash_mutex.h
参考 https://www.w3cschool.cn/pika/pika-4j1f2dk1.html

3. 互斥锁

应用场景:
上图中在执行add binlog过程中,pika用互斥锁(./include/pika_binlog.h: pthread_rwlock_t rwlock_):保证同一时间只有一个线程往binlog中写入数据,从而保证binlog写入的顺序性。

其次,pika内部有些命令内部的操作是互斥的,比如在做flushall涉及到换新DB,此时是不允许进行bgsave和scan操作的,否则会发生错误。为了保证顺利进行,进行flushall操作之前,就需要先加互斥锁bgsave_protector_、key_scan_protector_。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。