【brpc学习实践十一】session-local与thread-local应用与brpc抽象工厂模式实践
什么是session-local与thread-local
百度内的检索程序大量地使用了thread-local storage (缩写TLS),有些是为了缓存频繁访问的对象以避免反复创建,有些则是为了在全局函数间隐式地传递状态。你应当尽量避免后者,这样的函数难以测试,不设置thread-local变量甚至无法运行。session-local与thread-local是brpc解决thread-local问题的两种机制,实际上还有一种,可自行查阅官网文档。这两种机制其实就是提供了一个保存rpc请求过程中的用户关注数据的保存、处理接口。
session-local
session-local data与一次server端RPC绑定: 从进入service回调开始,到调用server端的done结束,不管该service是同步还是异步处理。 session-local data会尽量被重用,在server停止前不会被删除。
设置ServerOptions.session_local_data_factory后访问Controller.session_local_data()即可获得session-local数据。若没有设置,Controller.session_local_data()总是返回NULL。
若ServerOptions.reserved_session_local_data大于0,Server会在提供服务前就创建这么多个数据。
struct MySessionLocalData {
MySessionLocalData() : x(123) {
}
int x;
};
class EchoServiceImpl : public example::EchoService {
public:
...
void Echo(google::protobuf::RpcController* cntl_base,
const example::EchoRequest* request,
example::EchoResponse* response,
google::protobuf::Closure* done) {
...
brpc::Controller* cntl = static_cast<brpc::Controller*>(cntl_base);
// Get the session-local data which is created by ServerOptions.session_local_data_factory
// and reused between different RPC.
MySessionLocalData* sd = static_cast<MySessionLocalData*>(cntl->session_local_data());
if (sd == NULL) {
cntl->SetFailed("Require ServerOptions.session_local_data_factory to be set with a correctly implemented instance");
return;
}
...
struct ServerOptions {
...
// The factory to create/destroy data attached to each RPC session.
// If this field is NULL, Controller::session_local_data() is always NULL.
// NOT owned by Server and must be valid when Server is running.
// Default: NULL
const DataFactory* session_local_data_factory;
// Prepare so many session-local data before server starts, so that calls
// to Controller::session_local_data() get data directly rather than
// calling session_local_data_factory->Create() at first time. Useful when
// Create() is slow, otherwise the RPC session may be blocked by the
// creation of data and not served within timeout.
// Default: 0
size_t reserved_session_local_data;
};
session_local_data_factory的类型为DataFactory,我们需要实现其中的CreateData和DestroyData。
brpc的DataFactory源码:
class DataFactory {
public:
virtual ~DataFactory() {
}
// Implement this method to create a piece of data.
// Notice that this method is const.
// Returns the data, NULL on error.
virtual void* CreateData() const = 0;
// Implement this method to destroy a piece of data that was created
// by Create().
// Notice that this method is const.
virtual void DestroyData(void*) const = 0;
};
实际上只是实现了一个抽象工厂的基类,对于产品族、具体工厂类都没有定义,这些是由具体业务去实现的。
注意:CreateData和DestroyData会被多个线程同时调用,**必须线程安全。**通常我们会在继承DataFactory的相关操作中添加锁保障线程安全。后面会有例子讲到。
class MySessionLocalDataFactory : public brpc::DataFactory {
public:
void* CreateData() const {
return new MySessionLocalData;
}
void DestroyData(void* d) const {
delete static_cast<MySessionLocalData*>(d);
}
};
MySessionLocalDataFactory g_session_local_data_factory;
int main(int argc, char* argv[]) {
...
brpc::Server server;
brpc::ServerOptions options;
...
options.session_local_data_factory = &g_session_local_data_factory;
...
我们需要将具体产品类的对象指针传入DestroyData以便brpc能帮我们回收资源,这里的资源回收是无需我们人工干预的,brpc已经帮我们进行管理:
如下源码:
在server start的时候就会根据传入的tls参数来进行启动一个sever bthread,随后在server结束时我们传入的具体session local数据会被抽象DestroyData指针调用destroy掉。
// Init _keytable_pool always. If the server was stopped before, the pool
// should be destroyed in Join().
_keytable_pool = new bthread_keytable_pool_t;
if (bthread_keytable_pool_init(_keytable_pool) != 0) {
LOG(ERROR) << "Fail to init _keytable_pool";
delete _keytable_pool;
_keytable_pool = NULL;
return -1;
}
if (_options.thread_local_data_factory) {
_tl_options.thread_local_data_factory = _options.thread_local_data_factory;
if (bthread_key_create2(&_tl_options.<