AE-Memcached 优化记录

优化背景和目的

  • 学习Memcached 代码
  • 将 Memcached 的代码成为自己的技术积累
  • 优化Memcache 代码,提高自己系统分析能力

源代码托管于Github上:

https://github.com/zuocheng-liu/ae-memcached

性能优化

网络模型的优化

  • 网络IO多路复用 + 单线程

  • 将 Redis 异步库 移植至 Memcached

优化动态申请内存机制

  • 使用预分配,减小系统调用 malloc、realloc、free的次数,主要出现在新建/关闭链接时,会有较多的系统调用

部分小的函数使用宏代替

优化Memcache协议命令的解析

  • 调整各个命令的解析顺序,把get 和 set 命令放到最前面

软件架构优化

软件架构优化,保证关键代码性能不变

使用宏加强代码复用

  • 重构verbose日志
  • 重构网络库
  • 重构slab

命令模式重构 Memcache 协议

  • 创建command_service类,统一管理命令的解析、处理

更深层次的抽象

将 stats 、 settings 、 logger 和全局资源进行抽象

解耦

  • 将各个模块接口化,减少模块间耦合,尤其是 slab item memcached之间的耦合
  • 依赖注入原则,增强各个模块的复用,其中mem_cache模块 settings等可以形成框架。
  • logger
  • command service

线程安全的单例模式-以C++代码为例

本文描述3种场景下的单例模式:

  • 进程体内无线程的单例模式
  • 进程体内多线程单例模式
  • 在单个线程体中的单例模式

本文所写单例模式代码都使用懒汉模式。

进程体内单例

注意问题:

  • 如果进程体中运行多个线程,则需要考虑多线程同步访问的问题。
  • 如果进程体重没有运行多个线程,则不需要考虑多线程同步访问。
  • 使用线程同步锁保证多进程同步

使用场景举例 :

  • 日志类、文件读写类
  • 资源管理类

代码示例:

进程体内没有运行多线程的单例模式,无需考虑线程同步与互斥

class Singleton {
  public:
    static Singleton* getInstance() {
        if (NULL == instance) {
          instance = new SingletonInside();
        }
        return instance;
    }
  private:
    SingletonInside(){}
    ~SingletonInside() {}
    static Singleton* instance;
};

Singleton::instance = NULL;    

进程体内运行多线程单例模式,使用系统mutex保证线程安全

class Singleton {
  public:
    static Singleton* getInstance() {
        pthread_once(&g_once_control, InitOnce);
        pthread_mutex_lock(&mutex);  // lock
        if (NULL == instance) {
          instance = new SingletonInside();
        }
        pthread_mutex_unlock(&mutex); // unlock
        return instance;
    }
  private:
    SingletonInside() {

    }
    ~SingletonInside() {
       pthread_mutex_destroy(&mutex);   // destroy lock
    }
    static void InitOnce(void) {
      pthread_mutex_init(&mutex,NULL);  // init lock
    }
    Singleton* instance;
    static pthread_once_t g_once_control;
    static pthread_mutex_t mutex;
};
Singleton::instance = NULL;
pthread_once_t Singleton::g_once_control = PTHREAD_ONCE_INIT;

单个线程体中的单例

某些资源在单个线程体内需要保持单例,即每个线程体内都保持唯一。每个线程体内,对象相互隔离,则无需考虑线程安全问题。

此种单例模式的实例需要调用系统提供的 TLS 接口,放于线程局部存储中。

使用场景举例:

  • 多路复用Socket封装类
  • 网络上下文环境类
  • 线程安全的资源

代码示例

class Singleton {
  public:
    static Singleton* getInstance() {
       pthread_once(&g_once_control, InitOnce);
       Singleton* instance = (Singleton*)pthread_getspecific(g_thread_data_key);

        if (NULL == instance) {
          instance = new SingletonInside();
          pthread_setspecific(g_thread_data_key, (void*)Singleton)
        }
        return instance;
    }
  private:
    SingletonInside() {}
    ~SingletonInside() {
       pthread_key_delete(g_thread_data_key);
    }
    static void InitOnce(void) {
      pthread_key_create(&g_thread_data_key, NULL);
    }

    static pthread_once_t g_once_control;
    static pthread_key_t g_thread_data_key;
};

pthread_once_t Singleton::g_once_control = PTHREAD_ONCE_INIT;
pthread_key_t Singleton::g_thread_data_key;

如果使用 Poco库 TreadLocal ,代码还会简洁很多,性能上也会好很多

    class Singleton {
      public:
        static Singleton* getInstance() {
            if (NULL == instance) {
              instance = new SingletonInside();
            }
            return instance;
        }
      private:
        SingletonInside() {}
        ~SingletonInside() {
           delete instance;
        }
        static Poco::ThreadLocal<Singleton> instance;
    };
Poco::ThreadLocal<singleton> Singleton::instance = NULL;

总结

  • 无论程序是多进程运行还是多线程运行,代码都要尽量兼容线程安全

不同设计模式的适用场景总结

软件设计过程中,该选择那种设计模式,除了借鉴直接的经验,还可以从以下方面考虑:

  • 领域对象模型中,类之间的关系是什么(继承、实现、关联、依赖、组合、聚合)
  • 容易变化的部分是什么,即易于扩展的部分
  • 不容易变化的部分是什么,即需要复用的部分

按照我们的思路,设计模式之所以多种多样,就是对应的领域模型中上面三个方面有很大不同。把握住以上几点,就能正确地选用设计模式

门面模式,Facade Pattern

  • 门面类和被门面类封装的类之间是继承或者依赖关系
  • 类的内部接口、内部逻辑经常变化;不被需要的对外接口容易变化;
  • 类的对外部接口不容易变

适配器模式, Adapter Pattern

  • 适配器类和被适配的类之间是继承或者依赖关系
  • 需要适配的类,或者其接口容易变化
  • 适配的类的接口不容易变化

原型模式, Adapter Pattern

  • 不同类之间有共同的父类
  • 类的部分属性、方法容易变化
  • 类的另外一部分属性、方法不容易变化,且构建复杂,成本很高

单例模式,Singleton Pattern

  • 类在全局唯一存在,其他类与单例类是
  • 类的唯一性不会变化

多例模式,Multition Pattern

  • 多例类的对象之间是聚合关系
  • 多例类的对象的数量容易变化

策略模式,Strategy Pattern

  • 对象和算法,是依赖关系
  • 逻辑框架不容易变化
  • 策略算法容易变化

代理模式,Proxy Pattern

  • 代理和被代理的类之间是依赖关系
  • 代理者的接口不容易变化
  • 被代理者的接口具体实现容易变化

工厂模式,Factory Methond Pattern

  • 工厂模式产生的对象一般有相同的父类(继承关系)
  • 由工厂产生对象不会变
  • 工厂产生对象所属的类的种类是不断变化(增多或减少)

抽象工厂模式,Abstract Factory Pattern

  • 同工厂模式
  • 变化的是,工厂创建对象所属的类的接口会不断变化

门面模式,Facade Pattern

  • 封装类和被封装的类之间是依赖关系
  • 接口类(封装类)的接口不容易变化
  • 实现类(被封装的类)的接口容易变化

Adapter Pattern

  • 同门面模式
  • 适配器类更倾向于兼容现有系统的接口需求

模版模式,Template Method Pattern

建造者模式,Builder Pattern

  • 构建、配置、注入依赖容易变化,且较复杂

桥梁模式,Bridge Pattern

命令模式,Command Pattern

装饰模式,Decorator Pattern

迭代器模式,Iterator Pattern

组合模式,Composite Pattern

观察者模式,Observer Pattern

  • 观察者和被观察者是关联关系
  • 被观察者的数量或种类容易变化

责任链模式,Chain of Responsibility Pattern

访问者模式,Visitor Pattern

状态模式,State Patter

原型模式,Prototype Pattern

中介者模式,Mediator Pattern

解释器模式,Interpreter Pattern

亨元模式,Flyweight Pattern

备忘录模式,Memento Pattern

什么是设计模式 – 总结

设计模式是如何诞生的?

  • 领域中有些东西是不变的,有些是不停变化的
  • 不变的东西和变化的东西之间,总存在着某些特定的关系,符合某种特定的规则和规律
  • 因为这些特定关系,经验丰富的设计者总在做重复的设计,也不断地复用自己的设计。他们为了设计的通用,也提出了非常好设计
  • 没有经验的设计者,却察觉不到这些关系,做不出好的设计
  • 经验丰富的设计者向没有经验的设计者传授好的、被重复使用的设计和经验,并讲述那些事物之间某些亘古不变的关系,这些就是设计模式的雏形
  • 将这些关系和设计进行整理和总结之后,设计模式诞生了。

设计模式是什么?

  • 是对程序设计人员经常遇到的设计问题的可再现的解决方案(The Smalltalk Companion)
  • 建立了一系列描述如何完成软件开发领域中特定任务的规则
  • 关注与复用可重复出现的结构设计方案
  • 提出了一个发生在特定设计环境中的可重复出现的设计问题,并提供解决方案
  • 识别并确定类和实例层次上或组件层次上的抽象关系

设计模式的作用?

  • 让开发人员能够控制系统的复杂度,不至于失控
  • 增强设计的可伸缩性/可扩展性(Scalable/scalability)
  • 增强设计的复用
  • 设计领域的通用语言(传递、沟通),设计模式具有很强的业务表述力

初学者掌握设计模式的方法

  • 模仿 – 最快的方法,学习和内化都比较迅速
  • 学习书本 – 比较而言学习较快,内化很慢
  • 不断重构自己的代码,自己去领悟设计模式 – 学习效率最慢,但是理解最透彻,内化效果最好的方法
  • 团队讨论、交流共享 – 学习较快、内化较快,但是时间成本很高,而且团队讨论往往不是高频率的活动

如何提升自己使用设计模式的能力

  • 学习设计模式的使用场景,简单地使用常用的设计模式
  • 改造设计模式,使之更好地满足业务场景
  • 总结业务场景,创建和组合新的设计模式
  • 将重复使用的设计模式及设计模式组合,创建框架。框架形成后,完善和丰富它。
  • 总之,把设计模式使用好,离不开经验,离不开积累
  • 在经验和积累之上,再创新。发现和创造新的模式、新的思想,并将这些新东西注入框架。

Service Locator Patten 总结

核心思想

将构建依赖的接口彻底与依赖者分离,并将此依赖作为“服务”绑定到一个标识符,而后依赖者则可通过这个标识符获取被绑定的依赖。

优点

  • 上层逻辑不依赖于服务接口的具体实现,实现解耦
  • 提高系统的可扩展性
  • 逻辑分层更加简洁清晰,使不同层次的开发人员各司其职,提高团队开发效率

适用场景

与 (Abstract) Factory Patten 比较

  • Factory Patten 创建的所有结果对象多有统一的抽象接口的(更具一般性的)对象
  • Service Locator Patten 创建的服务,异构性程度比较大,接口缺少一般性
  • Factory Patten 单纯为创建对象
  • Service Locator Patten 职能更多些,包括创建服务、配置服务、注入服务的依赖等等

与 Dependence Injection Container 配合

  • Service Locator Patten 和 Dependence Injection并不互斥
  • Service Locator Patten 在定位服务时,创建服务、配置服务、注入服务的依赖通过Dependence Injection Container实现,可以进一步解耦

与 Proxy Patten 配合

  • 对创建的服务进行抽象,增强服务的一般性(即抽象出统一接口)

与 Singleton Patten 配合

  • 承担特定职责的Service Locator的对象无需多次实例化,节省开销
  • 对Service Locator定位的服务缓存做集中存储,存储数据结构存于Service Locator的单例对象即可

优化和扩展

  • 与多种设计模式配合使用
  • 在Service Locator类里定义服务实例化对象的缓冲池,使服务只实例化1次,节省开销

代码示例