1. 程式人生 > >EOS使用者資源管理resource_limits深入解析

EOS使用者資源管理resource_limits深入解析

簡介

eos中使用者資源分3類,1)ram,2)cpu,3)net,其中ram資源的獲取主要通過變種的Bancor演算法實現ram的自動定價,去中心化交易,具體的實現可以檢視eosio.system合約中的exchange_state.cpp,使用者的cpu跟net資源通過抵押eos來獲取,抵押eos形成抵押權重相應為cpu_weight,net_weight,抵押的越多可以獲取的資源越多,這跟ram不一樣,ram可以使用的多少是通過eos購買,而net跟cpu是通過eos抵押。

跟resource_limits相關的四張表

這個4張表為:

void resource_limits_manager::add_indices() {
   _db.add_index<resource_limits_index>();
   _db.add_index<resource_usage_index>();
   _db.add_index<resource_limits_state_index>();
   _db.add_index<resource_limits_config_index>();
}
  • resource_limits_index的表結構定義如下:
   struct resource_limits_object : public chainbase::object<resource_limits_object_type, resource_limits_object> {

      OBJECT_CTOR(resource_limits_object)

      id_type id;
      account_name owner;
      bool pending = false;

      int64_t net_weight = -1;    // 自己抵押的eos+別人給你抵押的eos
      int64_t cpu_weight = -1;    // 自己抵押的eos+別人給你抵押的eos
      int64_t ram_bytes = -1;     // 最大可用的記憶體

   };
  • resource_usage_index表結構定義如下:
   struct resource_usage_object : public chainbase::object<resource_usage_object_type, resource_usage_object> {
      OBJECT_CTOR(resource_usage_object)

      id_type id;
      account_name owner;

      usage_accumulator        net_usage;        // net的指數移動平均累加器
      usage_accumulator        cpu_usage;       // cpu的指數移動平均累加器

      uint64_t                 ram_usage = 0;         // 已使用的記憶體
   };
  • resource_limits_state_index的表結構定義為resource_limits_config_object,該表定義了塊的cpu跟net的限制引數和使用者cpu和net的window_size
  • resource_limits_config_index的表結構定義如下:
class resource_limits_state_object : public chainbase::object<resource_limits_state_object_type, resource_limits_state_object> {
      OBJECT_CTOR(resource_limits_state_object);
      id_type id;

      usage_accumulator average_block_net_usage;     // block的net指數移動平均累加器
      usage_accumulator average_block_cpu_usage;    // block的cpu指數移動平均累加器

      uint64_t pending_net_usage = 0ULL;            // 記錄pending block的net總和(包含在該塊中的所有trx net使用相加)
      uint64_t pending_cpu_usage = 0ULL;          // 記錄pending block的cpu總和(包含在該塊中的所有trx cpu使用相加)

      uint64_t total_net_weight = 0ULL;            // 所有使用者的net_weight相加和
      uint64_t total_cpu_weight = 0ULL;          // 所有使用者的cpu_weight相加和
      uint64_t total_ram_bytes = 0ULL;           // 所有使用者的ram_bytes相加和

      uint64_t virtual_net_limit = 0ULL;          // 虛擬net最大可使用量/block,只有當所有使用者都最大限度使用他們的net時它才是虛擬的
      uint64_t virtual_cpu_limit = 0ULL;         // 虛擬cpu最大可使用量/block,只有當所有使用者都最大限度使用他們的cpu時它才是虛擬的

   };

: 關於上述結構體中定義的指數移動平均累加器usage_accumulator 型別,其實它本質上就是K線圖中的均線,你可以把resource_limits_state_object 中的pending_net_usage看成是net的使用在塊序列上的均線

使用者資源的變化

eos中的net跟cpu資源不像ram,net跟cpu資源不需要購買而是靠使用者抵押eos來租用這些資源,並且使用者已使用的net跟cpu資源會隨著時間推移成指數衰減(因為net_usage,cpu_usage的定義都是usage_accumulator )。例如假設usera目前有10k的net最大可用資源(相對一個視窗而言),並且usera已經使用了9k,那麼還剩1k,但是隨著時間的推移,你最終會發現usera的已使用資源變成了0k,也就是說使用者net跟cpu的資源限制只作用在一段時間內,例如上述usera一段時間內net的最大使用值限制在10k以內,這個時間段稱為window_size。

  • 使用者資源最大限制的設定
    set_account_limits函式用來對一個使用者的ram,net,cpu資源最大使用進行設定,該函式主要是在eosio.system系統合約買賣記憶體,更改bw之後通過呼叫set_resource_limits函式被呼叫
bool resource_limits_manager::set_account_limits( const account_name& account, int64_t ram_bytes, int64_t net_weight, int64_t cpu_weight) 
  • 使用者資源的使用
    1)ram資源的使用,任何涉及到使用記憶體的地方最終都會呼叫add_pending_ram_usage函式,把記憶體的使用多少記在某個賬號上
void resource_limits_manager::add_pending_ram_usage( const account_name account, int64_t ram_delta ) {
...
   _db.modify( usage, [&]( auto& u ) {
     u.ram_usage += ram_delta;
   });
}

2)cpu和net資源的使用,每一筆trx執行成功之後都會呼叫add_transaction_usage,目的就是更改相應使用者的resource_usage_index表中的net_usage和cpu_usage,如果已經超出使用者的最大可以使用的資源值則會丟擲異常

void resource_limits_manager::add_transaction_usage(const flat_set<account_name>& accounts, uint64_t cpu_usage, uint64_t net_usage, uint32_t time_slot ) 
  • 使用者資源的換算
    上面使用者的resource_limits_object 中記錄的只是cpu跟net的權重cpu_weight,net_weight ,那麼究竟如何把權重換算到cpu跟net的頻寬呢? 這裡以cpu為例(net同理)
auto max_user_use_in_window = (virtual_cpu_capacity_in_window * cpu_weight) / all_user_weight;

max_user_use_in_window:視窗內使用者可以使用的最大cpu時間
可以看到max_user_use_in_window 跟all_user_weight成反比,all_user_weight為所有使用者的cpu_weight相加的和,這裡virtual_cpu_capacity_in_window表示視窗內所有使用者可以使用的cpu時間數,這個值等於

uint128_t window_size = config.account_cpu_usage_average_window
uint128_t virtual_cpu_capacity_in_window = (uint128_t)(elastic ? state.virtual_cpu_limit : config.cpu_limit_parameters.max) * window_size;

elastic是否為彈性,一般都為true,那麼可以看成 virtual_cpu_capacity_in_window = state.virtual_cpu_limit * window_size,上述可以看到window_size它等於config.account_cpu_usage_average_window,這個值在resource_limits_private.hpp中定義,它最終等於24 * 60 * 60 * 2,因為出塊是每0.5s出一塊,所以這個視窗大小其實是正好是一天,也就是說virtual_cpu_capacity_in_window表示一天內所有使用者可以使用的虛擬cpu時間數,之所以是虛擬的是因為當所有使用者都這麼去做時系統根本沒有這麼多的cpu時間,這就好像銀行的擠兌,當大家在同一時間去銀行提現時銀行根本沒有那麼多的現金給你。接下來我們講講的state.virtual_cpu_limit是怎麼來的,virtual_cpu_limit你可以簡單的把它看成是當前塊可以使用的最大虛擬cpu

  • virtual_cpu_limit
    virtual_cpu_limit的更新在函式resource_limits_manager::process_block_usage(uint32_t block_num)中,該函式會在每產生一個區塊後都會呼叫,具體的更新函式為update_virtual_cpu_limit
static uint64_t update_elastic_limit(uint64_t current_limit, uint64_t average_usage, const elastic_limit_parameters& params) {
   uint64_t result = current_limit;
   // params.target 為20000us 即0.02s
   if (average_usage > params.target ) {
      result = result * params.contract_rate;    // 縮小比例 99/100
   } else {
      result = result * params.expand_rate;    // 擴大比例 1000/999
   }
  // params.max 為200000us 即0.2s
   return std::min(std::max(result, params.max), params.max * params.max_multiplier);
}

void resource_limits_state_object::update_virtual_cpu_limit( const resource_limits_config_object& cfg ) {
   virtual_cpu_limit = update_elastic_limit(virtual_cpu_limit, average_block_cpu_usage.average(), cfg.cpu_limit_parameters);
}

上述cfg.cpu_limit_parameters這個值我先直接給出它為{20000(塊的縮放參考cpu時間), 20000(塊最大cpu時間0.2s)0, 120(average_block_cpu_usage的視窗大小), 1000, {99, 100}(縮小比例), {1000, 999}(擴大比例)},至於這個值是怎麼來的,可以通過./cleos -u http://api.eosnewyork.io get table eosio eosio global命令來獲取global引數之後代入得到

average_usage:它等於傳入的引數average_block_cpu_usage.average(),它表示塊cpu使用均線的當前值

update_elastic_limit函式的大體邏輯,如果average_usage > params.target (0.02s),那麼對當前virtual_cpu_limit進行縮小,反之則擴大,通過return std::min(std::max(result, params.max), params.max * params.max_multiplier); 可以發現事實上virtual_cpu_limit 最小值為params.max 即0.2s,然後它會根據average_usage是否大於params.target 進行縮放但最大不會超過params.max * params.max_multiplier 即200s,最終 0.2s <= virtual_cpu_limit <= 200s,然而實際上真正的塊時間average_usage是不可能超過params.max的(因為在add_transaction_usage函式中有EOS_ASSERT( state.pending_cpu_usage <= config.cpu_limit_parameters.max, block_resource_exhausted, "Block has insufficient cpu resources" );)
所以說這個是virtual,虛擬的cpu限制,不過我們可以把virtual_cpu_limit 看成一個指標,它是一個對cpu使用程度的指標,這個值越大說明cpu越空閒,所以max_user_use_in_window 即使用者看到的你可以使用的最大cpu使用時間,沒錯這確實是你可以使用的最大cpu時間,你完全可以去這麼做去使用掉這些cpu時間完全沒有問題,但問題是其他人沒有跟你一樣也在同一個視窗中去使用這些cpu時間,如果其他人也這麼做了,那麼對不起根本沒有這麼多cpu時間,這個很有意思就像銀行的擠兌,我們知道系統中塊的最大cpu使用時間為0.2s,然後0.2s <= virtual_cpu_limit <= 200s,virtual_cpu_limit等於0.2s的時候說明系統已經100%繁忙,這個情況下每個使用者能使用的cpu時間為max_user_use_in_window 並且可以同時在同一個視窗內使用,就好比銀行如果它的存款準備金率是100%那麼就不怕擠兌,同理virtual_cpu_limit>0.2s 意味著系統對cpu時間進行了增發(對應銀行存款準備金率小於100%),這裡之所以可以這麼做的前提跟其實跟銀行一樣,人們不會在同一時刻來消費他們的所有cpu時間(在銀行中就是擠兌)。

最終 virtual_cpu_limit它是什麼,它就是塊cpu時間的增發,大家想想如果不增發,那麼這0.2s塊cpu時間按照cpu_weight/total_cpu_weight 分攤每個使用者得到的cpu時間恐怕都不夠執行一筆最簡單的交易


轉自:https://www.jianshu.com/p/51d2ba3f677c