OpenStack中memcached的使用和實現


概述

主要分享下個人對Liberty版本openstack中cache使用的理解,由於作者水平有限,難免有所錯誤,疏漏,還望批評指正。

openstack中可以使用cache層來緩存數據,Liberty版本主要有以下幾種場景:

  • 存儲函數執行結果 keystone heat nova等項目把一些固定的屬性和查詢請求的結果放到cache里面,加速訪問。
  • 存儲keystone token token創建完成之后,不需要修改,會有大量的讀操作,適合放到cache中
  • 存儲keystonemiddleware token 為neutron,cinder,nova等各個項目緩存從keystone獲得的token。
  • 存儲horizon用戶會話數據 主要是django支持使用

存儲函數執行結果

優勢在於,很多查詢函數的結果是固定的,但是又比較常用,查詢一次之后,按照key-value存到cache中,再次查詢時不需要訪問數據庫,直接從內存緩存中根據key將結果取出,可以提高很多速度。或者還有一些查詢請求,查詢的參數千變萬化,但是結果只有固定的幾類,這種更適合使用cache加速。

Liberty 版本openstack主要是nova和keystone這方面用的比較多,來看nova中的一個例子:

def get_instance_availability_zone(context, instance):
    """Return availability zone of specified instance."""
    host = instance.get('host')
    if not host:
        # 如果虛擬機還沒有被分配到主機上,就把創建虛擬機時指定的zone信息取出返回
        az = instance.get('availability_zone')
        return az

    #虛擬機所在的zone取決於虛擬機所在物理機的歸屬的zone,所以可以生成一個
    #'azcache-主機名'樣式的字符串作為cache key      
    cache_key = _make_cache_key(host)
    #獲取一個連接cache的client
    cache = _get_cache()
    #嘗試取出這個key在cache中的值,作為zone信息,可能為空,也可能直接是結果
    az = cache.get(cache_key)
    #取出實例對象中的availability_zone屬性
    az_inst = instance.get('availability_zone')
    if az_inst is not None and az != az_inst:
        #對象屬性中有zone信息,且和cache取到的zone信息不一致,那么需要重新獲取zone信息
        #並且更新到cache中
        az = None
    if not az:
        #如果cache中沒有取到zone信息,或者是在上一步中zone信息被清空,重新獲取zone信息
        elevated = context.elevated()
        az = get_host_availability_zone(elevated, host)
        #更新cache
        cache.set(cache_key, az)
    #返回zone信息
    return az  

這樣除第一次查詢到這台物理機上的虛擬機外,查詢這台物理機上的任何虛擬機所屬的zone,再也不需要訪問數據庫,這樣極大地減少了對數據庫的請求數量,提高了響應速度。
這里支持的cache后端包括memcached,redis,mongondb或者是python的dict.目前主流openstack發行版推薦的選項是memcached,簡單穩定,性能和功能夠用。

存儲keystone token

keystone中除了Fernet格式的token外,其他格式的token都需要keystone由存儲起來,存儲支持以下幾種driver:

  • sql 將token存放在數據庫中,使用這種方法需要定期清理過期token,防止token表太大影響性能。
  • memcache 將token存放到memcache中,token本身創建之后不會被修改,只會被讀,所以很適合放到cache中,加速訪問,放到cache中也不需要寫腳本定期清理數據庫中的過期token。
  • memcache_pool 在memcache的基礎上,實現了memcache的連接池,可以在線程之間復用連接。

一個常見的memcache token driver的配置可能是這樣的:

[token]
caching = true
provider = keystone.token.providers.uuid.Provider
driver = keystone.token.persistence.backends.memcache.Token
[memcache]
servers = controller-1:11211,controller-2:11211,controller-3:11211

memcache driver的缺點在於,memcache本身是分布式的設計,但是並不是高可用的,如果controller-1上的的cache服務被重啟,這個節點上的所有token都會丟失掉,會帶來一些錯誤。

比這更糟糕的是,如果controller1網絡不可達或者宕機,那么我們會觀察到幾乎每個openstack api請求都會有3s以上的卡頓。這是因為openstack默認使用python-memcached訪問memcache,它提供的操作keystone的client繼承自Thread.local類,和構建它的線程綁定。openstack服務啟動后,會啟動一定數量的子進程,每個http request到達,會有一個子進程接收,孵化一個線程去處理這個請求。如果用到memcache,線程會調用python-memcached
構建一個client類,通過這個client的實例對memcache進行操作。如果訪問到網絡不可達的memcache節點,卡住,操作超時,將這個節點標識為30秒內不可用,在這個線程內,不會再因此而卡住,但是這個http請求結束之后,下一個http請求過來,重新孵化的線程會reinit這個client,新的client丟失了舊的client的狀態,還是可能會訪問到卡住的memcache節點上。

社區之所以要做memcache_pool,就是為了解決這個問題,將client統一由pool管理起來,memcache節點的狀態,也由pool管理起來,這樣每個子進程里只會卡第一次,所以強烈推薦使用memcache_pool驅動而不是memcache。社區將memcache_pool的代碼從keystone復制到oslo_cache項目中,希望所有使用memcache的項目都通過它的memcachepool去訪問,避免這個問題。其中,nova在M版本支持,heat在L版本支持。

具體的各個服務如何配置使用memcache_pool driver這里不再贅述。

存儲keystonemiddleware token

我們請求任何openstack服務時,該服務都要校驗請求中提供的token是否合理,這部分代碼顯然不是每個項目都自己實現一遍,它在keystonemiddleware項目實現,並作為filter配置在各個項目的api-paste.ini文件中,如下所示:

[filter:authtoken]
paste.filter_factory = keystonemiddleware.auth_token:filter_factory

當請求到達服務時,由keystonemiddleware訪問keystone來查詢請求攜帶的token是否合法,通常我們一個token會使用很多次,所以keystonemiddleware建議使用memcache緩存,把從keystone取到的token緩存一段時間,默認是300秒,以減少對keystone的壓力,提高性能。kolla項目中nova keystonemiddleware配置示例如下:

[keystone_authtoken]
[keystone_authtoken]
auth_uri = {{ internal_protocol }}://{{ kolla_internal_fqdn }}:{{ keystone_public_port }}
auth_url = {{ admin_protocol }}://{{ kolla_internal_fqdn }}:{{ keystone_admin_port }}
auth_plugin = password
project_domain_id = default
user_domain_id = default
project_name = service
username = {{ nova_keystone_user }}
password = {{ nova_keystone_password }}

memcache_security_strategy = ENCRYPT
memcache_secret_key = {{ memcache_secret_key }}
memcached_servers = {% for host in groups['memcached'] %}{{ hostvars[host]['ansible_' + hostvars[host]['api_interface']]['ipv4']['address'] }}:{{ memcached_port }}{% if not loop.last %},{% endif %}{% endfor %}

一直以來,如果不配置其中的memcached_servers的的話,每個服務每個子進程都會創建一個字典作為cache存放從keystone獲得的token,但是字典內容都是相似的,如果使用統一的memcache,就不會有這方面的問題。現在master版本keystonemiddleware認為這是不合適的,會導致不一致的結果和較高的內存占用,計划在N或者O版本移除這個特性。

存儲horizon用戶會話數據

這個是django支持的功能,這里不作討論,有興趣的可以看The Django Book 2.0--中文版

總結

cache相關的具體的配置項可以參考kolla項目中的cache配置,應該還是准確合適的。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM