好客若干优化点
潘忠显 / 2020-06-28
QueueEntry
可以用 Protobuf 定义封装
几类函数
- set/get类
- 序列化与反序列化
- 状态判断IsXXX
- 枚举与描述的转换
- 内部函数,如根据几个字段拼出队列名
前三类都可以直接使用Protobuf的类来实现
枚举与描述的转换
关于枚举和描述的转换,Protobuf也是支持的。使用Protobuf自带的Message例子:
message Person {
required string name = 1;
required int32 id = 2;
optional string email = 3;
enum PhoneType {
MOBILE = 0;
HOME = 1;
WORK = 2;
}
message PhoneNumber {
required string number = 1;
optional PhoneType type = 2 [default = HOME];
}
repeated PhoneNumber phones = 4;
};
实际打印枚举对应的字符串:
std::cout << Person_PhoneType_MOBILE << std::endl;
std::cout << Person_PhoneType_Name(Person_PhoneType_MOBILE) << std::endl;
以若干字段作为参数的内部函数
因为Protobuf产生的类被标记为final,无法被继承,所以如果要使用成员函数做一些操作,需要使用其他的函数。
可以命名为PersonHelper(?)
string concatenateNameEmail(const proto::Person &person) { ... }
几种 Key
QueueIndex + "_" + GroupId + "_" + SendRuleQueueName
QueueIndex + "_" + GroupId + "_" + SendRuleQueueName + "_status"
QueueIndex + "_" + GroupId + "_" + SendRuleQueueName + "_estimated"
使用Protobuf压缩
input stream 是用来解析的。 output stream 是序列化产生的。
简单的单例实现
static Singleton& get() {
static Singleton instance;
return instance;
}
另外一个实现“
https://www.theimpossiblecode.com/blog/c11-generic-singleton-pattern/
.inl文件的作用
#include "src/store_mgr_v2/common/store_conn_mgt_timer.inl"
StoreConnMgtTimer
start()
具体的逻辑为:
- 判断是否启动
- 创建定时器
- 注册处理函数
- run定时器
- start_flag=true
不是线程安全的。当多个线程中同时start可能造成多new定时器和处理函数指针。
但其实这种只会在main中启动一次的也可以不用考虑线程安全的问题。
可以比较Envoy项目中的 Envoy::Event::Timer
和 Envoy::Event::Scheduler
。
Envoy::Event::Timer
时钟
using SystemTime = std::chrono::time_point<std::chrono::system_clock>;
using MonotonicTime = std::chrono::time_point<std::chrono::steady_clock>;
StoreClientMgt
负责管理与存储的连接。功能:
- 定时更新下游连接
- 获取可用连接,第一个可用/随机可用/指定index
- free连接
- release连接
获取IP信息的函数可以单独拎出去。
ServerInfo的内容。
StoreClientMgt 管理一个cluster,包括一批服务发现类型相同的服务器信息(由StoreServerMgtInfo决定的)。
模板类型参数 StoreClsName
是用来。
继承自RefCounter和使用StoreSmartPtr的目的,都是为了使用智能指针,可以使用shared_ptr完全替代。
DummyQueue
和 QueueInterface
的关系,后者就是个接口/纯虚类,前者是其一种实现。封装了Redis的相关操作。
DummyQueue
保存RedisContext的上下文信息。使用DummyQueue的对象进行redis操作,应当加锁。
类似的还有 TglogClient
和 CdbClient
。这几个类为了使用StoreClientMgt管理,需要提供 set_store_idx
和 set_version
两个函数,以及单个ServerInfo的存储。
几个相关类的关系:
typedef Smart_Ptr_T <TglogClient> TglogClient_Ptr;
typedef StoreClientMgt<TglogClient_Ptr, TglogClient> TglogClientMgt;
typedef StoreConnMgtTimer<TglogClientMgt> TglogClientMgtTimer;
typedef StorePtrMgt<TglogClient_Ptr, TglogClientMgt> TglogStorePtrMgt;
#define PSGT_TglogClient_Mgt Singleton_T<TglogClientMgt>::getInstance()
#define PSGT_TglogClient_Mgt_Timer Singleton_T<TglogClientMgtTimer>::getInstance()
TglogClientMgt
是为了管理几个服务地址的所有连接的池子。
TglogClientMgtTimer
是为了通过 TglogClientMgt
来定时刷新连接的定时器。
TglogStorePtrMgt
是为了析构时释放连接的。
ClientMgt
是管理一个
Q: 是否一定需要使用模板来定义TglogClientMgt? 看起来几个Client实现都是继承自相同的类,都实现了几个固定的参数,是否可以用类来替换?
配置散落各处
- 路由、发货规则等在 config.db
- 下游IDIP的集群信息在配置文件
- 发货速率控制在 redis 中
- 回调参数在命令行上
开发方面,会造成需要多个线程去分别更新不同的配置。
运维方面,会给运维工作带来困扰,有的要管理端操作下发配置,有的有修改启动参数重启进程,有的则要下发配置文件。
与IDIP的关系
因为IDIP支持异步处理(返回中带有请求中的req_id),因此发送IDIP请求的线程池和处理IDIP返回的线程池是独立的(其实也有关系)。
使用单例维护连接池管理对象
StoreClientMgt
以CDB连接的管理来说明几种对象/名称的含义
- CdbClient_Ptr: CdbClient 的智能指针
- CdbClientMgt: CdbClient_Ptr, CdbClient 的StoreClientMgt
- CdbClientMgtTimer: CdbClientMgt 的连接管理定时器
- PSGT_CdbClient_Mgt: CdbClientMgt 的单例
- PSGT_CdbClient_Mgt_Timer: CdbClientMgtTimer 的单例
【优化】有两个类模板是这么写的:
template <typename StoreSmartPtr, typename StoreClsName>
class StoreClientMgt {
public:
StoreClientMgt();
};
template <typename StoreSmartPtr, typename StoreSmartPtrMgt>
class StorePtrMgt {
public:
StorePtrMgt(StoreSmartPtr ptr);
};
根据上边的关系可以得知,StoreSmartPtr是StoreClsName的智能指针。所以
第一个类模板可以简化为:
template <typename StoreClsName>
class StoreClientMgt {
public:
typedef Smart_Ptr_T<StoreClsName> StoreSmartPtr;
StoreClientMgt();
};
也就是StoreClientMgt变成只需要接受一个形参类型的类模板,因此第二个类也可以进行简化:
template <typename StoreClsName>
class StorePtrMgt {
public:
typedef Smart_Ptr_T<StoreClsName> StoreSmartPtr;
typedef StoreClientMgt<StoreClsName> StoreSmartPtrMgt;
StorePtrMgt(StoreSmartPtr ptr);
};
定义新类型使用使用 using
和 typedef
的区别
using SchedulerPtr = std::unique_ptr
There is no difference between creating a type alias using typedef and creating a type alias using using. However, using is more powerful since one it can be templated (creating what’s called an alias template).
typedef int A; // ok
using B = int; // ok
template <class T>
typedef std::pair<T, T> TT; // error
template <class T>
using TT = std::pair<T, T>; // ok
On the other hand, typedef allows you to declare multiple types at once (not that you would do this often):
// declares I to be an alias for int,
// P to be an alias for int*,
// and F to be an alias for int (*)()
typedef int I, *P, (*F)();
typename的使用
template <class T>
string GetRealIpFromHeaderXff(T &headers) {
string ret;
typename T::iterator it;
it = headers.find("X-Forwarded-For")) == headers.end();
...
在模板中使用
返回成员变量引用:
const int &QueueEntry::get_retry_count() const {
return retry_count_;
}