ceph原始碼解析--osd篇
Ceph分散式檔案系統的程式碼分析的文章網上是比較少的,本團隊成員對ceph做過詳細的程式碼閱讀,包括mds、osd、client等模組,但是缺少條理清晰的文件總結。暫且先放上OSD的程式碼分析,等後續整理陸續放上其它模組的。
1 OSD的基本結構
主要的類,涉及的執行緒,工作的方式
1.1 類OSD
該類主要用以處理網路訊息,與mds客戶端等之間的網路連線的維護。當收到客戶端或者mds對物件的資料請求後,交給相關的類進行處理。
1.1.1 主要物件
ObjectStore store; /對object訪問介面的封裝**/
OSDSuperblock superblock; 主要是版本號等資訊
OSDMapRef osdmap;
1.1.2 OSD中的執行緒池
[1] op_tp:
op_wq(this, g_conf->osd_op_thread_timeout, &op_tp)
scrub_finalize_wq(this, g_conf->osd_scrub_finalize_thread_timeout, &op_tp)
這裡的op_wq是當OSD中當有請求操作時,會將該操作分配給所屬的PG處理:
涉及的操作型別包括:CEPH_MSG_OSD_OP(client op) , MSG_OSD_SUBOP(for replication etc.) ,MSG_OSD_SUBOPREPLY。這些操作都要交給PG處理。
通過方法enqueue_op(pg, op);加入佇列
// add to pg's op_queue
pg->op_queue.push_back(op); //該pg中加入該操作
op_wq.queue(pg); //由於該pg有了操作,將pg入隊,op_tp中的執行緒會處理
其中op_wq的定義如下:
struct OpWQ : public ThreadPool::WorkQueue {
OSD *osd; OpWQ(OSD *o, time_t ti, ThreadPool *tp) : ThreadPool::WorkQueue<PG>("OSD::OpWQ", ti, ti*10, tp), osd(o) {} bool _enqueue(PG *pg); void _dequeue(PG *pg) { assert(0); } bool _empty() { return osd->op_queue.empty(); } PG *_dequeue(); void _process(PG *pg) { osd->dequeue_op(pg); } void _clear() { assert(osd->op_queue.empty()); }
} op_wq;
OpWQ主要操作osd->op_queue,即deque op_queue;
[2] recovery_tp
recovery_wq(this, g_conf->osd_recovery_thread_timeout, &recovery_tp)
struct RecoveryWQ : public ThreadPool::WorkQueue {
OSD *osd;
RecoveryWQ(OSD *o, time_t ti, ThreadPool *tp)
: ThreadPool::WorkQueue<PG>("OSD::RecoveryWQ", ti, ti*10, tp), osd(o) {}
RecoveryWQ 主要操作osd->recovery_queue,實際上封裝與recovery相關的操作,這裡recovery操作具體由每個PG執行。
void _process(PG *pg) {
osd->do_recovery(pg);
}
[3] disk_tp
remove_wq(this, g_conf->osd_remove_thread_timeout, &disk_tp)
osd->backlog_queue
// backlogs
xlist<PG*> backlog_queue;
rep_scrub_wq(this, g_conf->osd_scrub_thread_timeout, &disk_tp)
struct RepScrubWQ : public ThreadPool::WorkQueue<MOSDRepScrub> {
private:
OSD *osd;
list rep_scrub_queue;
snap_trim_wq(this, g_conf->osd_snap_trim_thread_timeout, &disk_tp)
osd->snap_trim_queue
// -- snap trimming --
xlist snap_trim_queue;
backlog_wq(this, g_conf->osd_backlog_thread_timeout, &disk_tp)
osd->backlog_queue
// backlogs
xlist backlog_queue;
[4] command_tp
command_wq(this, g_conf->osd_command_thread_timeout, &command_tp)
list command_queue;
osd->command_queue
void _process(Command *c) {
osd->osd_lock.Lock();
osd->do_command(c->con, c->tid, c->cmd, c->indata);
osd->osd_lock.Unlock();
delete c;
}
1.2 PG
PG,物件訪問的上層控制,確定讀取的物件的位置等資訊,對物件的實際的讀寫資料控制由FileStore完成。
Ceph系統中為了管理物件,將物件進行了分組。PG即place_group就是ceph中的分組。
1.2.1 主要物件
class PG {
struct Info { 描述一個PG的基本資訊
pg_t pgid;
pg_stat_t stats;
struct History {} 建立的版本號,修改時間等
}
struct Query { Query - used to ask a peer for information about a pg.向其他OSD查詢一個pg的資訊
__s32 type;
eversion_t since;
Info::History history;
}
struct Log { incremental log of recent pg changes. pg修改的日誌
struct Entry {
__s32 op;
hobject_t soid;
osd_reqid_t reqid;
uint64_t offset; // [soft state] my offset on disk
}
list log; // the actual log.
}
IndexLog - adds in-memory index of the log, by oid. 日誌在記憶體中的索引
struct IndexedLog : public Log {
hash_map<hobject_t,Entry*> objects; // ptrs into log. be careful! 每個物件對應的日誌
hash_map caller_ops;
list<Entry>::iterator complete_to; // recovery pointers
}
class OndiskLog {
uint64_t tail; // first byte of log.
uint64_t head;
}
struct Missing { //summary of missing objects.
//kept in memory, as a supplement to Log.
map missing; // oid -> (need v, have v)
map<version_t, hobject_t> rmissing; // v -> oid
}
list op_queue; // op queue PG操作的佇列
// pg state
Info info;
const coll_t coll;
IndexedLog log;
hobject_t log_oid;
hobject_t biginfo_oid;
OndiskLog ondisklog;
Missing missing;
int role; // 0 = primary, 1 = replica, -1=none. 該pg的角色,主,備
/* Encapsulates PG recovery process */ PG recover處理的過程
class RecoveryState {
RecoveryMachine machine;
RecoveryCtx *rctx;
}
}
父類PG主要是用以對PG本身的維護,對PG的修改,日誌的管理等。
Srcub的過程:
PG收集其管理的所有的objects,並向PG的副本請求物件的資訊,進行物件狀態的異常檢查。
ReplicatedPG主要用以操作物件,物件操作介面的封裝。
1.3 FileStore
負責向osd裝置中資料的讀寫,作為類OSD的成員物件store出現。
1.4 FileJournal
負責日誌的管理,通過日誌恢復資料等,作為類OSD的成員物件journal出現。
2 OSD讀寫資料的過程
2.1 客戶端發起請求的過程
int Client::ll_read(Fh *fh, loff_t off, loff_t len, bufferlist *bl)
int Client::read(Fh *f, int64t offset, uint64_t size, bufferlist *bl)
int Client::readsync(Fh *f, uint64_t off, uint64_t len, bufferlist *bl)
//前幾個引數均在結構體Inode中
Inode *in = f->inode;
filer->read_trunc(in->ino, &in->layout, in->snapid,
pos, left, &tbl, 0,
in->truncate_size, in->truncate_seq,
onfinish);
int read_trunc(inodeno_t ino,
ceph_file_layout *layout,
snapid_t snap,
uint64_t offset,
uint64_t len,
bufferlist *bl, // ptr to data
int flags,
uint64_t truncate_size,
__u32 truncate_seq,
Context *onfinish)
向osd讀取資料的過程:
1 將要讀取資料的長度和偏移轉化為要訪問的物件
file_to_extents(ino, layout, offset, len, extents);
2 向osd發起請求
objecter->sg_read(extents, snap, bl, flags, onfinish);
Filer.h
//計算需要讀取的資料所在的extent,extent沿用了brtfs檔案系統的概念
// ino ==> extents, extent實際上是object,offset
根據檔案偏移訪問物件的過程:
void Filer::file_to_extents(inodeno_t ino, ceph_file_layout *layout,
uint64_t offset, uint64_t len,
vector<ObjectExtent>& extents)
_u32 objectsize = layout->fl_object_size;
_u32 su = layout->flstripe_unit;
_u32 stripecount = layout->fl_stripe_count;
uint64_t stripes_per_object = object_size / su;
每個物件有兩部分ino和objectno
// layout into objects
uint64_t blockno = cur / su; // which block
uint64_t stripeno = blockno / stripe_count; // which horizontal stripe (Y)
uint64_t stripepos = blockno % stripe_count; // which object in the object set (X)
uint64_t objectsetno = stripeno / stripes_per_object; // which object set
uint64_t objectno = objectsetno * stripe_count + stripepos; // object id
object_t oid = file_object_t(ino, objectno);
ObjectExtent *ex = 0;//主要由下面的兩個引數組成
ex->oloc = objecter->osdmap->file_to_object_locator(*layout);
ex->oid = oid;
object_locator_t file_to_object_locator(const ceph_file_layout& layout) const {
return object_locator_t(layout.fl_pg_pool, layout.fl_pg_preferred);
}
Objecter.h
void sg_read_trunc(vector& extents, snapid_t snap, bufferlist *bl, int flags,
uint64_t trunc_size, __u32 trunc_seq, Context *onfinish)
//對集合中的每個ObjectExtent進行處理
Objecter.h tid_t read_trunc(const object_t& oid, const object_locator_t& oloc,
uint64_t off, uint64_t len, snapid_t snap, bufferlist *pbl, int flags,
uint64_t trunc_size, __u32 trunc_seq,
Context *onfinish,
eversion_t *objver = NULL, ObjectOperation *extra_ops = NULL)
//該函式發出請求
Objecter.h tid_t read_trunc(const object_t& oid, const object_locator_t& oloc,
uint64_t off, uint64_t len, snapid_t snap, bufferlist *pbl, int flags,
uint64_t trunc_size, __u32 trunc_seq,
Context *onfinish,
eversion_t *objver = NULL, ObjectOperation *extra_ops = NULL)
2.2 OSD的op_tp執行緒處理資料讀取
處理的過程如下:
OpWQ的 void process(PG *pg) 到 osd->dequeueop(pg);中的程式碼如下:
if (op->get_type() == CEPH_MSG_OSD_OP) {
if (op_is_discardable((MOSDOp*)op))
op->put();
else
pg->do_op((MOSDOp*)op); // do it now
àvoid ReplicatedPG::do_op(MOSDOp *op)
à ReplicatedPG::do_op(MOSDOp *op)
à prepare_transaction(ctx); int ReplicatedPG::prepare_transaction(OpContext *ctx)
àint result = do_osd_ops(ctx, ctx->ops, ctx->outdata);
int ReplicatedPG::do_osd_ops(OpContext *ctx, vector& ops, bufferlist& odata)
該函式的case CEPH_OSD_OP_READ: 分支
int r = osd->store->read(coll, soid, op.extent.offset, op.extent.length, bl);
可以看到最終到了FileStore物件中。
int FileStore::read(coll_t cid, const hobject_t& oid,
uint64_t offset, size_t len, bufferlist& bl)
read函式中主要呼叫了int fd = lfn_open(cid, oid, O_RDONLY);
我們可以看到定位一個物件需要的引數:
int FileStore::lfn_open(coll_t cid, const hobject_t& oid, int flags, mode_t mode)
r = get_index(cid, &index);
get_index的過程:在當前正在使用的index集合中判斷是否正在被使用,如果被使用需要等待釋放,否則建立索引。
int IndexManager::get_index(coll_t c, const char *path, Index *index) {
Mutex::Locker l(lock);
while (1) {
/// Currently in use CollectionIndices
// map > col_indices;
if (!col_indices.count(c)) {
int r = build_index(c, path, index);
if (r < 0)
return r;
(*index)->set_ref(*index);
col_indices[c] = (*index);
break;
}else {
cond.Wait(lock);
}
}
return 0;
}
建立索引的過程:
int IndexManager::build_index(coll_t c, const char *path, Index *index) {
*index = Index(new FlatIndex(path),
RemoveOnDelete(c, this));
或者:
*index = Index(new HashIndex(path, g_conf->filestore_merge_threshold,
g_conf->filestore_split_multiple, version),
RemoveOnDelete(c, this));
這裡coll_t的定義為:
class coll_t {
public:
const static coll_t META_COLL;
const static coll_t TEMP_COLL;
coll_t()
: str("meta")
{ }
std::string str;
coll_t實際上代表了一個目錄,目錄中是物件的集合。HashIndex在一定的條件下會拆分或者合併其擁有的子集合。
r = index->lookup(oid, &path, &exist);
r = ::open(path->path(), flags, mode);
3 OSD中的日誌、事務
這裡對物件的寫或者修改操作最終會交給FileStore物件處理,提交到該物件的巢狀類OpSequencer中的連結串列q中,日誌的序列號加入到連結串列jq中。在flush時,根據日誌的序列號保證了日誌未flush前,操作不會寫入磁碟。
在一個操作的處理過程中,最終由PG發出處理該動作。上述的序列關係記錄在PG物件中的ObjectStore::Sequencer osr;中。
3.1 對於物件的操作的處理過程
對object的操作最終由PG類進行處理,過程如下:
ReplicatedPG::do_op
1 如果是CEPH_OSD_FLAG_PGOP,由do_pg_op處理返回。
2 如果該pg狀態為: finalizing_scrub並且有寫操作(CEPH_OSD_FLAG_WRITE),加入到waiting_for_active。
3 如果該物件在missing列表中:is_missing_object,加入等待列表wait_for_missing_object。
4 如果該物件在degraded列表並且有寫操作,加入對一個的等待列表wait_for_degraded_object。
5 從磁碟或者快取中讀取物件的屬性資訊:find_object_context
6 如果失敗,不能找到,將操作加入到miss等待列表:wait_for_missing_object
7 根據得到的物件的資訊判斷,如果是讀請求並且是lost狀態,返回出錯
8 根據pg的mode判斷該osd_op的合法性,如果不成功加入到mode的等待列表中
9 遍歷該op中的ops,獲得每個操作涉及的物件的資訊,加入集合src_obc中。
10 如果是write操作,相應的檢查snap version
11 通過加讀鎖,進行操作prepare_transaction,操作完後解除讀鎖。ObjectContext:: ondisk_read_lock
該函式中如果是讀操作讀取該物件的資訊
寫操作只進行基本的檢查
ReplicatedPG::prepare_transaction 執行操作,此時資料、日誌都在記憶體中。
1> do_osd_ops
int ReplicatedPG::do_osd_ops(OpContext *ctx, vector& ops,bufferlist& odata)
CEPH_OSD_OP_WRITE分支:
/**將資料寫入到事務快取中*/
t.write(coll, soid, op.extent.offset, op.extent.length, nbl);
2> do_osd_op_effects
3> 如果是讀請求返回
4> 修改操作新增日誌
ctx->log.push_back(Log::Entry(logopcode, soid, ctx->at_version, old_version, ctx->reqid, ctx->mtime));
12 準備迴應MOSDOpReply,如果是read操作或者是上一步出錯,迴應。
13 執行到這裡只能是寫操作。
append_log(ctx->log, pg_trim_to, ctx->local_t);
PG::append_log
1> 將ctx中的log加入到事務ctx->local_t中的快取中。
建立新的RepGather,rep_op,並執行:
14 向該pg的副本傳送此次請求:
ReplicatedPG::issue_repop
向PG的acting列表中的osd傳送訊息MOSDSubOp。
當其他的osd收到該請求後:
1> OSD::handle_sub_op此時只是將該op壓入佇列中
2> 在函式OSD::dequeue_op處理該請求:
ReplicatedPG::do_sub_op
ReplicatedPG::sub_op_modify ------------------------此時執行對osd的資料修改動作
將修改操作作為事務提交到佇列中:
int r = osd->store->queue_transactions(&osr, rm->tls, onapply, oncommit);
這裡將該操作提交給了兩個執行緒池的,第一個執行緒池負責將日誌寫入磁碟。第二個負責執行該操作。如果沒有使用btrfs檔案系統作為osd儲存,會先進行日誌的過程,即將操作加入到日誌佇列中,當日志寫入磁碟後,通過回撥將操作加入到操作佇列中。
這裡註冊的兩個回撥:
Context *oncommit = new C_OSD_RepModifyCommit(rm); 當日志寫入磁碟後被呼叫
Context *onapply = new C_OSD_RepModifyApply(rm); 當該操作被處理後被呼叫
ReplicatedPG::sub_op_modify_applied
MOSDSubOpReply CEPH_OSD_FLAG_ACK
ReplicatedPG::sub_op_modify_commit
MOSDSubOpReply CEPH_OSD_FLAG_ONDISK
當收到其他的osd的迴應時:
OSD::handle_sub_op_reply
ReplicatedPG::do_sub_op_reply
sub_op_modify_reply(r);
ReplicatedPG::repop_ack
如果是CEPH_OSD_FLAG_ONDISK,則從下面集合中刪除:
repop->waitfor_disk.erase(fromosd);
repop->waitfor_ack.erase(fromosd);
否則:
repop->waitfor_ack.erase(fromosd);
每收到一次ack,都會呼叫函式eval_repop
15 eval_repop
當已經收到其他的osd迴應時(程式碼中的註釋的意思):
apply_repop 執行此次動作。執行的過程與其他的osd執行過程類似。該函式將 repop->applying = true;
多註冊了一個回撥:ReplicatedPG::C_OSD_OndiskWriteUnlock::finish
當repop->waitfor_disk.empty()為空時:
此時向請求的發出者回應:MOSDOpReply CEPH_OSD_FLAG_ACK | CEPH_OSD_FLAG_ONDISK
當repop->waitfor_ack.empty()為空時:
向此次請求的發出者回應:MOSDOpReply CEPH_OSD_FLAG_ACK
此時寫入的資料已經可讀,但未commit
注意,兩個迴應中,第一個如果迴應了就包含了第二個。兩種迴應只存在一個。
當repop->waitfor_ack.empty() && repop->waitfor_disk.empty()兩者都為空時,將此次的repop操作從佇列中刪除。
3.2 修改操作的處理
可以看到對於修改操作,需要通過日誌、事務進行處理,將操作加入到日誌,事務的過程為:
FileStore::queue_transactions的過程:
這裡將該操作提交給了兩個執行緒池的,第一個執行緒池負責將日誌寫入磁碟。第二個負責執行該操作。如果沒有使用btrfs檔案系統作為osd儲存,會先進行日誌的過程,即將操作加入到日誌佇列中,當日志寫入磁碟後,通過回撥將操作加入到操作佇列中。
當日志可寫時:
1 建立FileStore:: Op op = build_op(tls, onreadable, onreadable_sync);
2 op_queue_reserve_throttle(o);
==> FileStore::_op_queue_reserve_throttle 當佇列的運算元過多,或者佇列中操作資料長度過大,阻塞等待。在某個操作處理結束後,_void_process_finish會喚醒。
3 o->op = op_submit_start(); ==>ops_submitting.push_back 獲得操作的序列號
4如果m_filestore_journal_parallel,即這裡將該操作同時加入到日誌佇列和FileStore的操作佇列中。
1>opjournal_transactions(o->tls, o->op, ondisk); 日誌提交到日誌佇列的過程
如果日誌可寫
journal->submit_entry(op, tbl, data_align, onjournal);
->completions.push_back(onjournal)
-> writeq.push_back (write_item(seq, e, alignment))
否則加入等待佇列:commit_waiters[op].push_back(onjournal);
2>queue_op(osr, o);
_op_apply_start(o->op);àJournalingObjectStore::_op_apply_start
當不是blocked狀態時,沒有處理,如果是blocked狀態,等待被喚醒
osr->queue(o); 加入到OpSequencer的佇列q中
op_wq.queue(osr); 此時將該操作加入到FileStore物件的op_wq佇列中。
5如果m_filestore_journal_writeahead(當btrfs沒有enable時為true)
相關推薦
ceph原始碼解析--osd篇
Ceph分散式檔案系統的程式碼分析的文章網上是比較少的,本團隊成員對ceph做過詳細的程式碼閱讀,包括mds、osd、client等模組,但是缺少條理清晰的文件總結。暫且先放上OSD的程式碼分析,等後續整理陸續放上其它模組的。
1 OSD的基本結構
主要的類,涉及的執行
dubbo原始碼解析——概要篇
這次原始碼解析借鑑《肥朝》前輩的dubbo原始碼解析,進行原始碼學習。總結起來就是先總體,後區域性.也就是先把需要注意的概念先丟擲來,把整體架構圖先畫出來.讓讀者拿著"地圖"跟著我的腳步,並且每一步我都
ss-libev 原始碼解析local篇(2):ss_local和socks5客戶端握手
上一篇說到ss-libev建立listen_ctx_t物件用於監聽客戶端連線,呼叫accept_cb處理來自客戶端的新連線,建立server_t物件用於處理和客戶端之間的互動。本篇分析來自客戶端的SOCK5連線的建立以及傳輸資料的過程。
首先,回憶一下使用ne
Multibit原始碼解析學習篇之---傳送比特幣
/**package org.multibit.viewsystem.swing.action;
* Complete the transaction to work out the fee) and then show the send bitcoin confirm dialog.
史上最全Universal-Image-Loader原始碼解析————快取篇
背景
在我寫下這篇部落格的時候,我還是一名二本學校計算機專業大四的應屆畢業生,自學Android開發快兩年了,這兩年的時間裡面,其實真的是感慨萬千,兩年的時間裡面,Android的很多事情也都見識過了,其實Android對於新手入門在就業方面是相當不友好的事情。都說第一個吃螃蟹的人最
ss-libev 原始碼解析local篇(5):ss-local之remote_send_cb
remote_send_cb這個回撥函式的工作是將從客戶端收取來的資料轉發給ss-server。在之前閱讀server_recv_cb程式碼時可以看到,在STAGE_STREAM階段有幾種可能都會開啟remote->fd的寫事件的監聽,從而當有寫事件觸發時
ss-libev 原始碼解析udp篇 (2)
UDP relay的程式碼基本都在udprelay.c中,無論ss-local還是ss-server的程式碼都在一起,使用巨集MODULE_LOCAL,MODULE_REMOTE等區分開。程式碼雖然不是很多,但是由於ss-local和ss-server以及ss-
Andfix熱修復框架原理及原始碼解析-上篇
1.不知道如何使用的同學,建議看看我上一篇寫的介紹熱補丁和Andfix的使用,這樣你才有一個大概的框架。通過使用Andfix,其實我們心中會有一個大概的輪廓,它的工作原理,大概就是,所謂的補丁檔
ElementUI 簡要原始碼解析——Basic篇
Layout 佈局
row
佈局元件中的父元件,用於控制子元件。很簡單的一個佈局標籤,主要通過 justify 和 align 控制子元素的對齊方式,使用 render 函式通過傳入的 tag 屬性控制生成的標籤。
在這裡推薦學習下 render 函式和 JSX 的寫法,因為之後比較複雜的元件都是通過 ren
myBatis原始碼解析-日誌篇(1)
上半年在進行知識儲備,下半年爭取寫一點好的部落格來記錄自己原始碼之路。在學習原始碼的路上也掌握了一些設計模式,可所謂一舉兩得。本次打算寫Mybatis的原始碼解讀。
準備工作
1. 下載mybatis原始碼
下載地址:https://github.com/mybatis/mybatis-3
2.
myBatis原始碼解析-快取篇(2)
上一章分析了mybatis的原始碼的日誌模組,像我們經常說的mybatis一級快取,二級快取,快取究竟在底層是怎樣實現的。此次開始分析快取模組
1. 原始碼位置,mybatis原始碼包位於org.apache.ibatis.cache下,如圖
2. 先從org.apache.ibatis.cache下的cac
myBatis原始碼解析-資料來源篇(3)
前言:我們使用mybatis時,關於資料來源的配置多使用如c3p0,druid等第三方的資料來源。其實mybatis內建了資料來源的實現,提供了連線資料庫,池的功能。在分析了快取和日誌包的原始碼後,接下來分析mybatis中的資料來源實現。
類圖:mybatis中關於資料來源的原始碼包路徑如下:
myBatis原始碼解析-反射篇(4)
前沿
前文分析了mybatis的日誌包,快取包,資料來源包。原始碼實在有點難頂,在分析反射包時,花費了較多時間。廢話不多說,開始原始碼之路。
反射包feflection在mybatis路徑如下:
原始碼解析
1 property包-主要對類的屬性進行操作的工
一篇文章徹底讀懂HashMap之HashMap原始碼解析(下)
put函式原始碼解析
//put函式入口,兩個引數:key和value
public V put(K key, V value) {
/*下面分析這個函式,注意前3個引數,後面
2個引數這裡不太重要,因為所有的put
操作後面的2個引數預設值都一樣 */
一篇文章徹底讀懂HashMap之HashMap原始碼解析(上)
就身邊同學的經歷來看,HashMap是求職面試中名副其實的“明星”,基本上每一加公司的面試多多少少都有問到HashMap的底層實現原理、原始碼等相關問題。
在秋招面試準備過程中,博主閱讀過很多關於HashMap原始碼分析的文章,漫長的拼湊式閱讀之後,博主沒有看到過
.NET Core實戰專案之CMS 第三章 入門篇-原始碼解析配置檔案及依賴注入
作者:依樂祝 原文連結:https://www.cnblogs.com/yilezhu/p/9998021.html
寫在前面
上篇文章我給大家講解了ASP.NET Core的概念及為什麼使用它,接著帶著你一步一步的配置了.NET Core的開發環境並建立了一個ASP.NET Core的mvc專
Dubbo原始碼解析之consumer呼叫篇
閱讀須知
dubbo版本:2.6.0
spring版本:4.3.8
文章中使用/* */註釋的方法會做深入分析
正文
在分析consumer初始化時,我們看到了關聯服務引用建立代理的過程,最終會呼叫JavassistProxyFactory的getP
rxJava和rxAndroid原始碼解析系列四之subscribeOn和observeOn的理解(學習終結篇)
本篇文章主要解決subscribeOn和observeOn這兩個方法為什麼subscribeOn只有一次有效果,observeOn切換多次回撥的都有效果。
不知道朋友有沒有看過rxandroid的原始碼,如果看過的話,就會迎刃而解,沒什麼疑慮啦。沒看過原始碼的朋友,可以看看我這個系列的前幾篇文章
Dubbo原始碼解析之provider呼叫篇
閱讀須知
dubbo版本:2.6.0
spring版本:4.3.8
文章中使用/* */註釋的方法會做深入分析
正文
在之前的原始碼分析文章中,我們看到了dubbo用netty作為底層的網路通訊框架,熟悉netty的同學應該知道,使用netty時我們會使用它
JDK8 HashMap原始碼解析,一篇文章徹底讀懂HashMap
在秋招面試準備中博主找過很多關於HashMap的部落格,但是秋招結束後回過頭來看,感覺沒有一篇全面、通俗易懂的講解HashMap文章(可能是博主沒有找到),所以在秋招結束後,寫下了這篇文章,盡最大的努力把HashMap原始碼講解的通俗易懂,並且儘量涵蓋面試中HashM