C++Linux进阶项目分析-仿写Redis之Qedis
Linux开发架构之路 2024-12-13

为什么要学这个项目

传统的webserver已经烂大街了,只有一个webserver项目大概率是找不到工作的,今天给大家分享一个C++Linux进阶项目-仿写Redis之Qedis,Redis是C++ Linux开发必备的核心知识,通过学习Qedis开源项目,不仅可以深入理解redis,也更能提升自己的编程能力,比如C++11任意函数作为任务的线程池,Reactor网络模型的C++封装,时间轮定时器,Redis数据结构的实现等。

视频地址

C++Linux进阶项目分析-仿写Redis之Qedis(B站搜索程序员老廖)

1.项目介绍


Qedis网上很多编译方式是错误的。

C++后台开发偏基础服务,比如分布式存储相关的岗位,基本上都是使用c++。

校招的时候如果想从事分布式存储相关的岗位,则需要深入掌握MySQL/Redis,而且即使不是做分布式存储,C++后台开发也是必须掌握Redis,所以建议大家可以参考Qedis项目仿写Redis,这样和其他人写webserver就有区分度。


Qedis 是一个基于 C++11 实现的 Redis 服务器,支持集群功能,并使用 LevelDB 作为持久化存储。该项目旨在提供一个高性能、可扩展的内存数据库解决方案,适用于需要快速数据访问和处理的应用场景。

Qedis 可以广泛应用于需要高性能数据存储和访问的场景,例如:

  • 实时数据分析:Qedis 的高性能和低延迟特性使其成为实时数据分析的理想选择。

  • 缓存系统:Qedis 可以作为缓存层,加速应用程序的数据访问速度。

  • 消息队列:结合 Redis 的发布/订阅功能,Qedis 可以用于构建高效的消息队列系统

2 项目快速启动

确保系统已安装 C++11 编译器。

2.1 安装 LevelDB 库

2.1.0 leveldb介绍

LevelDB是一种快速的键-值存储库,由Google开发,用于提供高性能的数据持久性存储。它通常被用作支持各种应用程序的底层数据库引擎,包括分布式数据库、区块链、分布式文件系统等。

2.1.1 下载Level

git clone https://gitclone.com/github.com/google/leveldb

2.1.2 下载编译leveldb需要的googletest、benchmark

1、进入下载好的leveldb

cd leveldb

2、进入third_party目录

cd third_party

3、下载

git clone https://gitclone.com/github.com/google/googletestgit clone https://gitclone.com/github.com/google/benchmark

2.1.3 编译安装

回到leveldb目录

cd ../mkdir build && cd build# 步骤4: 使用CMake生成构建文件,这里以Debug模式为例cmake -DCMAKE_BUILD_TYPE=Debug -DBUILD_SHARED_LIBS=1 ..makesudo make install

2.1.4 刷新环境

sudo ldconfig

2.1.5 c++测试范例

测试leveldb,文件名为hello.cc,内容如下所示:

#include #include #include #include using namespace leveldb; int main(){ leveldb::DB* db; leveldb::Options options; options.create_if_missing = true; // 打开一个数据库,不存在就创建 leveldb::Status status = leveldb::DB::Open(options, "/tmp/testdb", &db); assert(status.ok()); // 插入一个键值对 status = db->Put(leveldb::WriteOptions(), "hello", "LevelDB"); assert(status.ok()); // 读取键值对 std::string value; status = db->Get(leveldb::ReadOptions(), "hello", &value);  assert(status.ok()); std::cout << value << std::endl;  delete db; return 0;}

编译代码:

g++ hello.cc -o hello -lpthread -lleveldb

执行程序:

./hello

显示:

LevelDB

2.2 下载和编译Qedis

git clone https://github.com/loveyacper/Qedis.gitcd Qedis


mkdir buildcd build# 以debug模式编译目的是为了方便后续debug源码cmake -DCMAKE_BUILD_TYPE=Debug ..make

编译后的执行文件在Qedis/bin目录,所以需要切换目录:

lqf@ubuntu:~/long/Qedis/build$ cd ../bin/

2.3 启动Qedis和测试

需要确保之前已经安装过redis,因为测试我们需要redis-cli,但redis-server不要启动。

2.3.1 默认方式启动Qedis

lqf@ubuntu:~/long/Qedis/bin$ ./qedis_server

打印:

 _____ _____ _____ _ _____  / _ \ | ____| |  _  \ | | /  ___/ | | | |   | |__   | | | | | | | |___    Qedis(1.0.0) 64 bits, another redis written in C++11 | | | |   | __|  | | | | | | \___  \   Port: 6379 | |_| |_  | |___  | |_| | | |  ___| |   Author: Bert Young \_______| |_____| |_____/ |_| /_____/   https://github.com/loveyacper/Qedis  start log thread2024-11-27[14:49:20.419][USR]:Success: listen on (127.0.0.1:6379)

通过redis-cli控制台进行测试:

# 启动redis-cli控制台lqf@ubuntu:~/long/Qedis/bin$ redis-cli # 设置key127.0.0.1:6379> set key qedisOK# 获取key,测试正常127.0.0.1:6379> get key"qedis"127.0.0.1:6379> 

2.3.2 通过配置文件启动Qedis

如果需要使用leveldb做持久化,需要修改配置文件Qedis/qedis.conf ,将backend 设置为1:


然后重新启动qedis_server,注意你配置文件路径

./qedis_server ~/long/Qedis/qedis.conf

打印:

Load libqedismodule.dylib failed because runtime error Load libnotexist.dylib  failed because runtime error

/ _ \ | | |  _  \ | | /  ___/| | | |   | |__   | | | | | | | |___    Qedis(1.0.0) 64 bits, another redis written in C++11| | | |   | |  | | | | | | _  \   Port: 6379| || |  | |_  | |_| | | |  _| |   Author: Bert Young___| |_| |_/ |_| /_/   https://github.com/loveyacper/Qedisstart log thread2024-11-2818:32:21.938:Success: listen on (127.0.0.1:6379)2024-11-2818:32:21.946:Open leveldb dump02024-11-2818:32:21.951:Open leveldb dump12024-11-2818:32:21.957:Open leveldb dump22024-11-2818:32:21.961:Open leveldb dump32024-11-2818:32:21.967:Open leveldb dump42024-11-2818:32:21.971:Open leveldb dump52024-11-2818:32:21.976:Open leveldb dump62024-11-2818:32:21.980:Open leveldb dump72024-11-2818:32:21.984:Open leveldb dump82024-11-2818:32:21.989:Open leveldb dump92024-11-2818:32:21.994:Open leveldb dump102024-11-2818:32:21.999:Open leveldb dump112024-11-2818:32:22.004:Open leveldb dump122024-11-2818:32:22.009:Open leveldb dump132024-11-2818:32:22.014:Open leveldb dump142024-11-2818:32:22.019:Open leveldb dump15

3 如何仿写redis

  1. 熟悉redis原理,但这个不是说一定要每行代码都看懂,推荐书籍:《Redis设计与实现

  2. 熟悉leveldb,也是不说一定要每行代码看懂,主要是先理解下leveldb如何使用,我这里有整理的leveldb相关的资料,大家可以加laoliao6668微信获取。

  3. 熟悉Qedis源码,参考Qedis仿写redis,前期可以先照抄Qedis源码都可以,我这里主要梳理Qedis的流程。

4 Qedis框架流程

这里是服务器开源项目调试的经典流程,不只局限于qedis源码。

4.0 通过gdb启动服务程序

我们通过gdb的方式启动qedis_server

gdb ./qedis_server

如果需要设置配置文件,则在gdb启动后,这样就能加载配置文件

(gdb) set args ../qedis.conf

4.1 main函数在哪个文件

4.1.1 获取main函数所在源文件位置

在main函数打断点:

(gdb) b mainBreakpoint 1 at 0x5f60:file/home/lqf/long/Qedis/QedisSvr/Qedis.cc, line 447.

这样能快速定位main函数所在的文件,结合vscode,点击


自动跳转到main函数,是不是非常方便。


这里我们没有提供配置文件参数,默认是前台运行,前台运行更方便debug

4.1.2 r运行程序

我们输入r先运行到main函数再打其他断点,输入r然后回车:

Breakpoint 1 at 0x5f60: file /home/lqf/long/Qedis/QedisSvr/Qedis.cc, line 447.(gdb) rStarting program: /home/lqf/long/Qedis/bin/qedis_server [Thread debugging using libthread_db enabled]Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1". Breakpoint 1, main (ac=1, av=0x7fffffffe038) at /home/lqf/long/Qedis/QedisSvr/Qedis.cc:447447 {(gdb)

接下来我们就要根据tcp 服务的套路打断点:

4.2 qedis服务框架

设置断点

所有的tcp服务器都遵循

函数:

  • bind

  • listen

  • accept

  • epoll_wait

  • epoll_ctl

  • 发送 send或者write,不同的项目可能用的函数不一样,先断点send

  • 接收recv或者read,不同的项目可能用的函数不一样,先断点recv

(gdb) b bindBreakpoint 2 at 0x7ffff7b71380: file ../sysdeps/unix/syscall-template.S, line 78.(gdb) b listenBreakpoint 3 at 0x7ffff7b714e0: file ../sysdeps/unix/syscall-template.S, line 78.(gdb) b acceptBreakpoint 4 at 0x7ffff798e4b0: accept. (2 locations)(gdb) b epoll_waitBreakpoint 5 at 0x7ffff7b70630: file ../sysdeps/unix/sysv/linux/epoll_wait.c, line 28. (gdb) b epoll_ctlBreakpoint 6 at 0x7ffff7b70d10: file ../sysdeps/unix/syscall-template.S, line 78.(gdb) b sendBreakpoint 7 at 0x7ffff798e770: send. (2 locations)(gdb) b recvBreakpoint 8 at 0x7ffff798e5f0: recv. (2 locations)

bind和listen查看调用栈

打好断点后,我们输入c继续运行程序:

Thread 1 "qedis_server" hit Breakpoint 2, bind () at ../sysdeps/unix/syscall-template.S:7878 ../sysdeps/unix/syscall-template.S: No such file or directory.

通过查看堆栈的方式:

#0 bind () at ../sysdeps/unix/syscall-template.S:78#1 0x00007ffff7f11289 in Internal::ListenSocket::Bind (this=this@entry=0x5555555759a0, addr=...) at /home/lqf/long/Qedis/QBase/ListenSocket.cc:48#2 0x00007ffff7f1935a in Server::TCPBind (this=out>, addr=..., tag=1) at /home/lqf/long/Qedis/QBase/Server.cc:57#3 0x000055555555a64e in Qedis::_Init (this=0x7fffffffde10) at /home/lqf/long/Qedis/QedisSvr/Qedis.cc:290#4 0x00007ffff7f18924 in Server::MainLoop (this=0x7fffffffde10, daemon=out>) at /home/lqf/long/Qedis/QBase/Server.cc:128#5 0x0000555555559fc5 in main (ac=1, av=0x7fffffffe038) at /home/lqf/long/Qedis/QedisSvr/Qedis.cc:464

可以清晰看到服务程序如何bind和listen


listen同理也是使用bt查看堆栈,

从这里63行也可以看出来这里是封装了NetThreadPool 网络线程池,查看源码是支持:

  • recvThread_接收线程;

  • sendThread_发送线程。

class NetThreadPool{ std::shared_ptrrecvThread_; std::shared_ptrsendThread_; ....}

接收数据相关:

RecvThread::Run()

发送数据相关:

SendThread::Run( )

继续运行触发epoll_ctl

#0 epoll_ctl () at ../sysdeps/unix/syscall-template.S:78#1 0x00007ffff7f10880 in Epoll::AddSocket (epfd=, socket=socket@entry=5, events=events@entry=1,  ptr=ptr@entry=0x5555555759a0) at /home/lqf/long/Qedis/QBase/EPoller.cc:28#2 0x00007ffff7f108d1 in Epoller::AddSocket (this=0x555555573f50, sock=5, events=1, userPtr=0x5555555759a0) at /home/lqf/long/Qedis/QBase/EPoller.cc:75#3 0x00007ffff7f118a8 in Internal::NetThread::_AddSocket (this=this@entry=0x555555573c20,  task=std::shared_ptr(use count 2, weak count 1) = {...}, events=) at /home/lqf/long/Qedis/QBase/NetThreadPool.cc:73#4 0x00007ffff7f126f5 in Internal::NetThread::_TryAddNewTasks (this=0x555555573c20) at /home/lqf/long/Qedis/QBase/NetThreadPool.cc:67#5 0x00007ffff7f13348 in Internal::RecvThread::Run (this=0x555555573c20) at /home/lqf/long/Qedis/QBase/NetThreadPool.cc:93

从堆栈可以看出来此时是把当前的fd加入到epoll处理,我们也看看

Qedis/QBase/NetThreadPool.cc

void NetThread::_TryAddNewTasks(){ if (newCnt_ > 0 && mutex_.try_lock()) {  NewTasks  tmp; newTasks_.swap(tmp); //newTasks_是任务队列,可以跟踪下 是在哪里push任务 newCnt_ = 0;  mutex_.unlock();  auto iter(tmp.begin()), end (tmp.end());  for (; iter != end; ++ iter)  _AddSocket(iter->first, iter->second);  }}

当前文件搜索newTasks_关键字可以找到NetThread::AddSocket,可以增加断点NetThread::AddSocket

void NetThread::AddSocket(PSOCKET task, uint32_t events){ std::lock_guard<std::mutex>    guard(mutex_); newTasks_.push_back(std::make_pair(task, events));  ++ newCnt_;  assert (newCnt_ == static_cast<int>(newTasks_.size()));}

把socket任务加入到对应的线程进行处理,比如SendThread::Run函数里调用tcpSock->Send()发送数据给客户端。需要源码的朋友可以加laoliao6668微信获取

继续运行触发epoll_wait的调用

Thread 3 "qedis_server" hit Breakpoint 5, epoll_wait (epfd=3, events=0x5555555759f0, maxevents=maxevents@entry=1,  timeout=timeout@entry=1) at ../sysdeps/unix/sysv/linux/epoll_wait.c:2828 ../sysdeps/unix/sysv/linux/epoll_wait.c: No such file or directory.(gdb) bt#0 epoll_wait (epfd=3, events=0x5555555759f0, maxevents=maxevents@entry=1, timeout=timeout@entry=1) at ../sysdeps/unix/sysv/linux/epoll_wait.c:28#1 0x00007ffff7f10c3a in Epoller::Poll (timeoutMs=out>, maxEvent=out>, events=...,  this=out>) at /usr/include/c++/10/bits/stl_vector.h:1043#2 Epoller::Poll (this=0x555555573f50, events=std::vector of length 0, capacity 0, maxEvent=1, timeoutMs=1) at /home/lqf/long/Qedis/QBase/EPoller.cc:99#3 0x00007ffff7f1339d in Internal::RecvThread::Run (this=0x555555573c20)

此时可以清晰看到RecvThread::Run 就是epoll_wait所在的线程。这里注意tasks_表示的是class Socket ,后续线程池获取到这个Socket再调用对应的函数做处理

void RecvThread::Run(){ // init log; g_logLevel = logALL; g_logDest  = logFILE; if (g_logLevel && g_logDest) { g_log = LogManager::Instance().CreateLog(g_logLevel, g_logDest, "recvthread_log"); }  std::deque::iterator it;  int loopCount = 0; while (IsAlive()) { _TryAddNewTasks();  if (tasks_.empty()) { std::this_thread::sleep_for(std::chrono::microseconds(100)); continue; }  const int nReady = poller_->Poll(firedEvents_, static_cast<int>(tasks_.size()), 1); for (int i = 0; i < nReady; ++ i) { assert (!(firedEvents_[i].events & EventTypeWrite));  Socket* sock = (Socket* )firedEvents_[i].userdata;  if (firedEvents_[i].events & EventTypeRead)  { if (!sock->OnReadable()) //处理新连接 { sock->OnError(); } }  if (firedEvents_[i].events & EventTypeError) { sock->OnError(); } }  if (nReady == 0) loopCount *= 2;  if (++ loopCount < 100000) continue;  loopCount = 0;  for (auto it(tasks_.begin()); //遍历任务,判断socket任务是不是有效的 it != tasks_.end(); ) { if ((*it)->Invalid()) { NetThreadPool::Instance().DisableRead(*it); //禁止读事件 RemoveSocket(*it, EventTypeRead); //从epoll 移除socket fd it = tasks_.erase(it); //如果是无效的从任务队列中移除 } else { ++ it; } } }}

知道epoll_wait在哪个线程后,可以先停止该函数的断点 。

然后继续运行,此时如果客户端不发消息,此时没有函数触发。

发送线程和接收线程是独立的。

redis-cli连接qedis触发accept

#0 __libc_accept (fd=5, addr=addr@entry=..., len=len@entry=0x7ffff7024b54) at ../sysdeps/unix/sysv/linux/accept.c:24#1 0x00007ffff7f11591 in Internal::ListenSocket::_Accept (this=this@entry=0x5555555759a0) at /home/lqf/long/Qedis/QBase/ListenSocket.cc:78#2 0x00007ffff7f115de in Internal::ListenSocket::OnReadable (this=0x5555555759a0) at /home/lqf/long/Qedis/QBase/ListenSocket.cc:85#3 0x00007ffff7f133d3 in Internal::RecvThread::Run (this=0x555555573c20) at /home/lqf/long/Qedis/QBase/NetThreadPool.cc:110

即是:

bool ListenSocket::OnReadable(){ while (true) { int connfd = _Accept(); if (connfd >= 0) { //创建对象绑定新连接 Server::Instance()->NewConnection(connfd, tag_); } 
 
void Server::NewConnection(int sock, int tag, const std::function<void ()>& cb){ if (sock == INVALID_SOCKET) return;  auto conn = _OnNewConnection(sock, tag);  if (!conn) { Socket::CloseSocket(sock); return; }  conn->SetOnDisconnect(cb);  if (NetThreadPool::Instance().AddSocket(conn, EventTypeRead | EventTypeWrite)) //先连接 加入可读 可写事件 tasks_.AddTask(conn); //加到队列}

QBase/Server.cc代码需要花时间研究。重点函数:

erver::_RunLogic()

在哪里被调用,可以断点

并重点查看StreamSocket.h的class StreamSocket

分析哪里接收的客户端数据

recv函数没有触发,那要断点read,或者readv

StreamSocket::Recv()调用了readv

int StreamSocket::Recv(){ if (recvBuf_.Capacity() == 0) { recvBuf_.InitCapacity(64 * 1024); // First recv data, allocate buffer }  BufferSequence  buffers; recvBuf_.GetSpace(buffers); if (buffers.count == 0) { WRN << "Recv buffer is full"; return 0; }  int ret = static_cast<int>(::readv(localSock_, buffers.buffers, static_cast<int>(buffers.count))); if (ret == ERRORSOCKET && (EAGAIN == errno || EWOULDBLOCK == errno)) return 0;  if (ret > 0) recvBuf_.AdjustWritePtr(ret);  return (0 == ret) ? EOFSOCKET : ret;}

,所以需要断点:StreamSocket::Recv

断点触发情况:

Thread 3 "qedis_server" hit Breakpoint 11, StreamSocket::Recv (this=this@entry=0x7ffff0001820) at /home/lqf/long/Qedis/QBase/StreamSocket.cc:4646 {(gdb) bt#0 StreamSocket::Recv (this=this@entry=0x7ffff0001820) at /home/lqf/long/Qedis/QBase/StreamSocket.cc:46#1 0x00007ffff7f1b0b4 in StreamSocket::OnReadable (this=0x7ffff0001820) at /home/lqf/long/Qedis/QBase/StreamSocket.cc:122#2 0x00007ffff7f133d3 in Internal::RecvThread::Run (this=0x555555573c20) at /home/lqf/long/Qedis/QBase/NetThreadPool.cc:110

StreamSocket::Recv读取到的数据由StreamSocket::DoMsgParse函数处理,所以也要断点

#0 StreamSocket::DoMsgParse (this=0x7ffff0001820) at /home/lqf/long/Qedis/QBase/StreamSocket.cc:219#1 0x00007ffff7f1cafb in Internal::TaskManager::DoMsgParse (this=0x7fffffffde20) at /home/lqf/long/Qedis/QBase/TaskManager.cc:99#2 0x00007ffff7f18ace in Server::MainLoop (this=0x7fffffffde10, daemon=out>) at /home/lqf/long/Qedis/QBase/Server.cc:139#3 0x0000555555559fc5 in main (ac=1, av=0x7fffffffe038) at /home/lqf/long/Qedis/QedisSvr/Qedis.cc:464

可以看到是在main函数处理 redis的协议

StreamSocket::DoMsgParse

后续请继续跟踪:

QMigrateClient::_HandlePacket

分析哪里发送的客户端数据

send函数没有触发,那要断点write,或者writev

通过搜索源码找到

StreamSocket::_Send调用了writev

int StreamSocket::_Send(const BufferSequence& bf){ auto total = bf.TotalBytes(); if (total == 0) return 0;  int ret = static_cast<int>(::writev(localSock_, bf.buffers, static_cast<int>(bf.count))); if (ERRORSOCKET == ret && (EAGAIN == errno || EWOULDBLOCK == errno)) { epollOut_ = true; ret = 0; } else if (ret > 0 && static_cast<size_t>(ret) < total) { epollOut_ = true; } else if (static_cast<size_t>(ret) == total) { epollOut_ = false; }  return ret;}

所以需要断点StreamSocket::_Send

#0 StreamSocket::_Send (this=this@entry=0x7ffff0001820, bf=...) at /home/lqf/long/Qedis/QBase/StreamSocket.cc:72#1 0x00007ffff7f1a900 in StreamSocket::Send (this=this@entry=0x7ffff0001820) at /home/lqf/long/Qedis/QBase/StreamSocket.cc:148#2 0x00007ffff7f1395c in Internal::SendThread::Run (this=0x555555573f80)

需要源码的朋友可以加laoliao6668微信获取

5 默认启动的线程

5.1 主线程

Thread 1 "qedis_server" received signal SIGINT, Interrupt.0x00007ffff7b2e23f in __GI___clock_nanosleep (clock_id=clock_id@entry=0, flags=flags@entry=0,  req=req@entry=0x7fffffffdd20, rem=rem@entry=0x7fffffffdd20) at ../sysdeps/unix/sysv/linux/clock_nanosleep.c:7878 in ../sysdeps/unix/sysv/linux/clock_nanosleep.c(gdb) thread 1[Switching to thread 1 (Thread 0x7ffff7827780 (LWP 11433))]#0  0x00007ffff7b2e23f in __GI___clock_nanosleep (clock_id=clock_id@entry=0, flags=flags@entry=0,  req=req@entry=0x7fffffffdd20, rem=rem@entry=0x7fffffffdd20) at ../sysdeps/unix/sysv/linux/clock_nanosleep.c:7878 in ../sysdeps/unix/sysv/linux/clock_nanosleep.c(gdb) bt#0  0x00007ffff7b2e23f in __GI___clock_nanosleep (clock_id=clock_id@entry=0, flags=flags@entry=0,  req=req@entry=0x7fffffffdd20, rem=rem@entry=0x7fffffffdd20) at ../sysdeps/unix/sysv/linux/clock_nanosleep.c:78#1  0x00007ffff7b33ec7 in __GI___nanosleep (requested_time=requested_time@entry=0x7fffffffdd20,  remaining=remaining@entry=0x7fffffffdd20) at nanosleep.c:27#2  0x00007ffff7f18b15 in std::this_thread::sleep_for > (__rtime=..., __rtime=...) at /usr/include/c++/10/thread:401#3  Server::MainLoop (this=0x7fffffffde10, daemon=) at /home/lqf/long/Qedis/QBase/Server.cc:140#4  0x0000555555559fc5 in main (ac=1, av=0x7fffffffe038) at /home/lqf/long/Qedis/QedisSvr/Qedis.cc:464

5.2 监控线程ThreadPool::_MonitorRoutine()

(gdb) thread 2[Switching to thread 2 (Thread 0x7ffff7826700 (LWP 11775))]#0  0x00007ffff7b2e23f in __GI___clock_nanosleep (clock_id=clock_id@entry=0, flags=flags@entry=0,  req=req@entry=0x7ffff7825e20, rem=rem@entry=0x7ffff7825e20) at ../sysdeps/unix/sysv/linux/clock_nanosleep.c:7878 in ../sysdeps/unix/sysv/linux/clock_nanosleep.c(gdb) bt#0  0x00007ffff7b2e23f in __GI___clock_nanosleep (clock_id=clock_id@entry=0, flags=flags@entry=0,  req=req@entry=0x7ffff7825e20, rem=rem@entry=0x7ffff7825e20) at ../sysdeps/unix/sysv/linux/clock_nanosleep.c:78#1  0x00007ffff7b33ec7 in __GI___nanosleep (requested_time=requested_time@entry=0x7ffff7825e20,  remaining=remaining@entry=0x7ffff7825e20) at nanosleep.c:27#2  0x00007ffff7f2348d in std::this_thread::sleep_for > (__rtime=..., __rtime=...) at /usr/include/c++/10/thread:401#3  ThreadPool::_MonitorRoutine (this=0x7ffff7f33600 ) at /home/lqf/long/Qedis/QBase/Threads/ThreadPool.cc:94#4  0x00007ffff7d4e793 in ?? () from /lib/x86_64-linux-gnu/libstdc++.so.6#5  0x00007ffff7983609 in start_thread (arg=) at pthread_create.c:477#6  0x00007ffff7b70353 in clone () at ../sysdeps/unix/sysv/linux/x86_64/clone.S:95

5.3 发送线程SendThread::Run

其实这里是运行在线程池创建的线程里,但一直独占了线程池的一个线程。

(gdb) thread 3[Switching to thread 3 (Thread 0x7ffff7025700 (LWP 11776))]#0  0x00007ffff7b2e23f in __GI___clock_nanosleep (clock_id=clock_id@entry=0, flags=flags@entry=0,  req=req@entry=0x7ffff7024c20, rem=rem@entry=0x7ffff7024c20) at ../sysdeps/unix/sysv/linux/clock_nanosleep.c:7878 in ../sysdeps/unix/sysv/linux/clock_nanosleep.c(gdb) bt#0  0x00007ffff7b2e23f in __GI___clock_nanosleep (clock_id=clock_id@entry=0, flags=flags@entry=0,  req=req@entry=0x7ffff7024c20, rem=rem@entry=0x7ffff7024c20) at ../sysdeps/unix/sysv/linux/clock_nanosleep.c:78#1  0x00007ffff7b33ec7 in __GI___nanosleep (requested_time=requested_time@entry=0x7ffff7024c20,  remaining=remaining@entry=0x7ffff7024c20) at nanosleep.c:27#2  0x00007ffff7f13a61 in std::this_thread::sleep_for > (__rtime=..., __rtime=...) at /usr/include/c++/10/thread:401#3  Internal::SendThread::Run (this=0x555555573f80) at /home/lqf/long/Qedis/QBase/NetThreadPool.cc:194。。。。。。#27 ThreadPool::_WorkerRoutine (this=0x7ffff7f33600 ) at /home/lqf/long/Qedis/QBase/Threads/ThreadPool.cc:83

5.4 接收线程RecvThread::Run

其实这里是运行在线程池创建的线程里,但一直独占了线程池的一个线程。

(gdb) thread 4[Switching to thread 4 (Thread 0x7ffff6824700 (LWP 11777))]#0 0x00007ffff7b7068e in epoll_wait (epfd=3, events=0x5555555759f0, maxevents=maxevents@entry=1, timeout=timeout@entry=1) at ../sysdeps/unix/sysv/linux/epoll_wait.c:3030 ../sysdeps/unix/sysv/linux/epoll_wait.c: No such file or directory.(gdb) bt#0 0x00007ffff7b7068e in epoll_wait (epfd=3, events=0x5555555759f0, maxevents=maxevents@entry=1, timeout=timeout@entry=1) at ../sysdeps/unix/sysv/linux/epoll_wait.c:30#1 0x00007ffff7f10c3a in Epoller::Poll (timeoutMs=, maxEvent=, events=...,  this=) at /usr/include/c++/10/bits/stl_vector.h:1043#2 Epoller::Poll (this=0x555555573f50, events=std::vector of length 0, capacity 0, maxEvent=1, timeoutMs=1) at /home/lqf/long/Qedis/QBase/EPoller.cc:99#3 0x00007ffff7f1339d in Internal::RecvThread::Run (this=0x555555573c20) at /home/lqf/long/Qedis/QBase/NetThreadPool.cc:101。。。 #25 std::__future_base::_Task_statevoid (Internal::RecvThread::*(std::shared_ptr))()> ()>, std::allocator, void ()>::_M_run() (this=0x555555574960) at /usr/include/c++/10/future:1459#26 0x00007ffff7f2322a in std::function<void ()>::operator()() const (this=0x7ffff6823e10) at /usr/include/c++/10/bits/std_function.h:622#27 ThreadPool::_WorkerRoutine (this=0x7ffff7f33600 ) at /home/lqf/long/Qedis/QBase/Threads/ThreadPool.cc:83

可以看到,发送/接收线程,其实都是线程池分配的线程运行的。

线程:

void ThreadPool::_CreateWorker(){ std::thread t([this]() { this->_WorkerRoutine(); } ); workers_.push_back(std::move(t));}

5.5 持久化线程AOFThread::Run

其实这里是运行在线程池创建的线程里,但一直独占了线程池的一个线程。

(gdb) thread 5[Switching to thread 5 (Thread 0x7ffff5d7e700 (LWP 11778))]#0 0x00007ffff7b2e23f in __GI___clock_nanosleep (clock_id=clock_id@entry=0, flags=flags@entry=0,  req=req@entry=0x7ffff5d7daf0, rem=rem@entry=0x7ffff5d7daf0) at ../sysdeps/unix/sysv/linux/clock_nanosleep.c:7878 ../sysdeps/unix/sysv/linux/clock_nanosleep.c: No such file or directory.(gdb) bt#0 0x00007ffff7b2e23f in __GI___clock_nanosleep (clock_id=clock_id@entry=0, flags=flags@entry=0,  req=req@entry=0x7ffff5d7daf0, rem=rem@entry=0x7ffff5d7daf0) at ../sysdeps/unix/sysv/linux/clock_nanosleep.c:78#1 0x00007ffff7b33ec7 in __GI___nanosleep (requested_time=requested_time@entry=0x7ffff5d7daf0,  remaining=remaining@entry=0x7ffff5d7daf0) at nanosleep.c:27#2 0x00007ffff7f64db1 in std::this_thread::sleep_for<long, std::ratio<1l, 1000l> > (__rtime=..., __rtime=...) at /usr/include/c++/10/thread:401#3 qedis::QAOFThreadController::AOFThread::Run (this=0x55555558c920) at /home/lqf/long/Qedis/QedisCore/QAOF.cc:167#4 0x00007ffff7f66b53 in std::__invoke_impl<void, void (qedis::QAOFThreadController::AOFThread::*&)(), std::shared_ptr&> (__f=, __f=, __t=...) at /usr/include/c++/10/bits/invoke.h:73#5 std::__invoke<void (qedis::QAOFThreadController::AOFThread::*&)(), std::shared_ptr&> (__fn=) at /usr/include/c++/10/bits/invoke.h:95#6 std::_Bind<void (qedis::QAOFThreadController::AOFThread::*(std::shared_ptr))()>::__call<void, , 0ul>(std::tuple<>&&, std::_Index_tuple<0ul>) (__args=..., this=) at /usr/include/c++/10/functional:416。。。。。。。#31 0x00007ffff7f2322a in std::function<void ()>::operator()() const (this=0x7ffff5d7de10) at /usr/include/c++/10/bits/std_function.h:622#32 ThreadPool::_WorkerRoutine (this=0x7ffff7f33600 ) at /home/lqf/long/Qedis/QBase/Threads/ThreadPool.cc:83

5.6 日志线程LogThread::Run

其实这里是运行在线程池创建的线程里,但一直独占了线程池的一个线程。

(gdb) thread 6[Switching to thread 6 (Thread 0x7ffff557d700 (LWP 11779))]#0 0x00007ffff7b2e23f in __GI___clock_nanosleep (clock_id=clock_id@entry=0, flags=flags@entry=0,  req=req@entry=0x7ffff557cc30, rem=rem@entry=0x7ffff557cc30) at ../sysdeps/unix/sysv/linux/clock_nanosleep.c:7878 in ../sysdeps/unix/sysv/linux/clock_nanosleep.c(gdb) bt#0 0x00007ffff7b2e23f in __GI___clock_nanosleep (clock_id=clock_id@entry=0, flags=flags@entry=0,  req=req@entry=0x7ffff557cc30, rem=rem@entry=0x7ffff557cc30) at ../sysdeps/unix/sysv/linux/clock_nanosleep.c:78#1 0x00007ffff7b33ec7 in __GI___nanosleep (requested_time=requested_time@entry=0x7ffff557cc30,  remaining=remaining@entry=0x7ffff557cc30) at nanosleep.c:27#2 0x00007ffff7f208fd in std::this_thread::sleep_for<long, std::ratio<1l, 1000l> > (__rtime=..., __rtime=...) at /usr/include/c++/10/thread:401#3 LogManager::LogThread::Run (this=0x555555573320) at /home/lqf/long/Qedis/QBase/Log/Logger.cc:662#4 0x00007ffff7f20dc3 in std::__invoke_impl<void, void (LogManager::LogThread::*&)(), std::shared_ptr&> (__f=, __f=, __t=...) at /usr/include/c++/10/bits/invoke.h:73#5 std::__invoke<void (LogManager::LogThread::*&)(), std::shared_ptr&> (__fn=) at /usr/include/c++/10/bits/invoke.h:95#6 std::_Bind<void (LogManager::LogThread::*(std::shared_ptr))()>::__call<void, , 0ul>(std::tuple<>&&, std::_Index_tuple<0ul>) (__args=..., this=) at /usr/include/c++/10/functional:416#7 std::_Bind<void (LogManager::LogThread::*(std::shared_ptr))()>::operator()<, void>() ( this=) at /usr/include/c++/10/functional:499#8 std::__invoke_impl<void, std::_Bind<void (LogManager::LogThread::*(std::shared_ptr))()>&>(std::__invoke_other, std::_Bind<void (LogManager::LogThread::*(std::shared_ptr))()>&) (__f=...) at /usr/include/c++/10/bits/invoke.h:60#9 std::__invoke<std::_Bind<void (LogManager::LogThread::*(std::shared_ptr))()>&>(std::_Bind<void (LogManager::LogThread::*(std::shared_ptr))()>&) (__fn=...) at /usr/include/c++/10/bits/invoke.h:95#10 std::_Bind<std::_Bind<void (LogManager::LogThread::*(std::shared_ptr))()> ()>::__call<void>(std::tuple<>&&, std::_Index_tuple<>) (__args=..., this=) at /usr/include/c++/10/functional:416#11 std::_Bind<std::_Bind<void (LogManager::LogThread::*(std::shared_ptr))()> ()>::operator()<, void>() (this=) at /usr/include/c++/10/functional:499#12 std::__invoke_impl<void, std::_Bind<std::_Bind<void (LogManager::LogThread::*(std::shared_ptr)) 。。。。#30 std::_Function_handler<void (), ThreadPool::ExecuteTask<std::_Bind<void (LogManager::LogThread::*(std::shared_ptr))()>>(std::_Bind<void (LogManager::LogThread::*(std::shared_ptr))()>&&)::{lambda()#1}>::_M_invoke(std::_Any_data const&) (__functor=...) at /usr/include/c++/10/bits/std_function.h:291#31 0x00007ffff7f2322a in std::function<void ()>::operator()() const (this=0x7ffff557ce10) at /usr/include/c++/10/bits/std_function.h:622#32 ThreadPool::_WorkerRoutine (this=0x7ffff7f33600 )--Typefor more, q to quit, c to continue without paging-- at /home/lqf/long/Qedis/QBase/Threads/ThreadPool.cc:83

6 命令执行操作level-db

操作leveldb

redis命令和leveldb封装映射

#0  qedis::QCommandTable::AliasCommand (aliases=std::map with 0 elements) at /home/lqf/long/Qedis/QedisCore/QCommand.cc:208#1  0x000055555555a6f1 in Qedis::_Init (this=0x7fffffffde10) at /home/lqf/long/Qedis/QedisSvr/Qedis.cc:297#2  0x00007ffff7f18924 in Server::MainLoop (this=0x7fffffffde10, daemon=) at /home/lqf/long/Qedis/QBase/Server.cc:128#3  0x0000555555559fc5 in main (ac=1, av=0x7fffffffe038) at /home/lqf/long/Qedis/QedisSvr/Qedis.cc:464

初始化db

#0 0x0000555555559930 in qedis::QStore::Init(int)@plt ()#1 0x000055555555a704 in Qedis::_Init (this=0x7fffffffde10) at /home/lqf/long/Qedis/QedisSvr/Qedis.cc:298#2 0x00007ffff7f18924 in Server::MainLoop (this=0x7fffffffde10, daemon=out>) at /home/lqf/long/Qedis/QBase/Server.cc:128#3 0x0000555555559fc5 in main (ac=1, av=0x7fffffffe038) at /home/lqf/long/Qedis/QedisSvr/Qedis.cc:464

my_hash作用

Thread 1 "qedis_server" hit Breakpoint 6, qedis::dictGenHashFunction (key=0x55555558d710, len=4) at /home/lqf/long/Qedis/QedisCore/QHelper.cc:2626 unsigned int dictGenHashFunction(const void* key, int len) {(gdb) bt#0 qedis::dictGenHashFunction (key=0x55555558d710, len=4) at /home/lqf/long/Qedis/QedisCore/QHelper.cc:26#1 0x00007ffff7f7b206 in qedis::my_hash::operator() (this=this@entry=0x555555578a90, str="key2") at /home/lqf/long/Qedis/QedisCore/QHelper.cc:71  at /usr/include/c++/10/bits/unordered_map.h:985#5 qedis::QStore::SetValue (this=this@entry=0x7ffff7fc66c0 , key="key2",  value=...) at /home/lqf/long/Qedis/QedisCore/QStore.cc:612#6 0x00007ffff7fa4488 in qedis::SetValue (key="key2", value="d", exclusive=out>) at /home/lqf/long/Qedis/QedisCore/QString.cc:82#7 0x00007ffff7fa4a7a in qedis::set (params=..., reply=0x7ffff0001730) at /home/lqf/long/Qedis/QedisCore/QString.cc:89#8 0x00007ffff7f69e45 in qedis::QClient::_HandlePacket (this=0x7ffff00015c0,  start=0x7ffff0011960 "*3\r\n$3\r\nset\r\n$4\r\nkey2\r\n$1\r\nd\r\n", bytes=out>) at /home/lqf/long/Qedis/QedisCore/QClient.h:48#9 0x00007ffff7f1a680 in StreamSocket::DoMsgParse (this=0x7ffff00015c0)--Typefor more, q to quit, c to continue without paging-- at /home/lqf/long/Qedis/QBase/StreamSocket.cc:227#10 0x00007ffff7f1cafb in Internal::TaskManager::DoMsgParse (this=0x7fffffffde20) at /home/lqf/long/Qedis/QBase/TaskManager.cc:99#11 0x00007ffff7f18ace in Server::MainLoop (this=this@entry=0x7fffffffde10, daemon=out>) at /home/lqf/long/Qedis/QBase/Server.cc:139#12 0x0000555555559fc5 in main (ac=1, av=0x7fffffffe038) at /home/lqf/long/Qedis/QedisSvr/Qedis.cc:464设置hash的目的是什么?为什么这里要抽象一层出来?这里貌似是如果不需要持久化存储时,就不用到leveldb

什么时候才会初始化 leveldb

需要配置文件开启持久化才行

#0 qedis::QStore::InitDumpBackends (this=0x7ffff7fc66c0 ) at /home/lqf/long/Qedis/QedisCore/QStore.cc:787#1 0x000055555555a738 in Qedis::_Init (this=0x7fffffffe410) at /home/lqf/long/Qedis/QedisSvr/Qedis.cc:302#2 0x00007ffff7f18924 in Server::MainLoop (this=0x7fffffffe410, daemon=out>) at /home/lqf/long/Qedis/QBase/Server.cc:128#3 0x0000555555559fc5 in main (ac=2, av=0x7fffffffe638) at /home/lqf/long/Qedis/QedisSvr/Qedis.cc:464
 
void QStore::InitDumpBackends(){ assert (waitSyncKeys_.empty());  if (g_config.backend == BackEndNone) //默认是不需要持久化 return;  if (g_config.backend == BackEndLeveldb) { waitSyncKeys_.resize(store_.size()); for (size_t i = 0; i < store_.size(); ++ i) { std::unique_ptrdb(new QLeveldb); QString dbpath = g_config.backendPath + std::to_string(i); if (!db->Open(dbpath.data())) assert(false); else USR << "Open leveldb " << dbpath;  backends_.push_back(std::move(db)); } } else  { // ERROR: unsupport backend return; }  for (int i = 0; i < static_cast<int>(backends_.size()); ++ i) { auto timer = TimerManager::Instance().CreateTimer(); timer->Init(1000 / g_config.backendHz); timer->SetCallback([&, i] () { int oldDb = QSTORE.SelectDB(i); QSTORE.DumpToBackends(i); QSTORE.SelectDB(oldDb); });  TimerManager::Instance().AddTimer(timer); }}

如何持久化

通过定时器定时刷新

(gdb) bt#0 qedis::QLeveldb::Put (this=0x55555558cec0, key="key", obj=..., absttl=-1) at /home/lqf/long/Qedis/QedisCore/QLeveldb.cc:74#1 0x00007ffff7f9f665 in qedis::QStore::DumpToBackends ( this=0x7ffff7fc66c0 , dbno=) at /home/lqf/long/Qedis/QedisCore/QStore.cc:850#2 0x00007ffff7f9f8dd in operator() (__closure=0x5555556dcc30) at /home/lqf/long/Qedis/QedisCore/QStore.cc:820#3 std::__invoke_impl<void, const qedis::QStore::InitDumpBackends()::&> (__f=...) at /usr/include/c++/10/bits/invoke.h:60#4 std::__invoke<const qedis::QStore::InitDumpBackends()::&> (__fn=...) at /usr/include/c++/10/bits/invoke.h:95#5 std::_Bind()>::__call_c<void> (__args=...,  this=0x5555556dcc30) at /usr/include/c++/10/functional:427#6 std::_Bind<qedis::QStore::InitDumpBackends()::<lambda()>()>::operator()<> (this=0x5555556dcc30) at /usr/include/c++/10/functional:511#7 operator() (this=0x5555556dcc30) at /home/lqf/long/Qedis/QBase/Timer.h:61#8 std::__invoke_impl<void, Timer::SetCallback<qedis::QStore::InitDumpBackends()::<lambda()>, {}>::<lambda()>&> (__f=...) at /usr/include/c++/10/bits/invoke.h:60#9 std::__invoke_r<void, Timer::SetCallback<qedis::QStore::InitDumpBackends()::<lambda()>, {}>::&> (__fn=...) at /usr/include/c++/10/bits/invoke.h:153#10 std::_Function_handler<void(), Timer::SetCallback, {}>:: >::_M_invoke(const std::_Any_data &) (__functor=...) at /usr/include/c++/10/bits/std_function.h:291#11 0x00007ffff7f1d7ca in std::function<void ()>::operator()() const (this=0x5555556dcc30) at /usr/include/c++/10/bits/std_function.h:622#12 Timer::OnTimer (this=0x5555556dcc30) at /home/lqf/long/Qedis/QBase/Timer.cc:201#13 Timer::OnTimer (this=0x5555556dcc30) at /home/lqf/long/Qedis/QBase/Timer.cc:193--Type <RET> for more, q to quit, c to continue without paging--#14 0x00007ffff7f1e181 in TimerManager::UpdateTimers (this=0x7ffff7f31a60 ,  now=...) at /home/lqf/long/Qedis/QBase/Timer.cc:330#15 0x000055555555b7c3 in Qedis::_RunLogic (this=0x7fffffffdde0) at /home/lqf/long/Qedis/QedisSvr/Qedis.cc:431#16 0x00007ffff7f18ace in Server::MainLoop (this=0x7fffffffdde0, daemon=) at /home/lqf/long/Qedis/QBase/Server.cc:139#17 0x0000555555559fc5 in main (ac=2, av=0x7fffffffe008) at /home/lqf/long/Qedis/QedisSvr/Qedis.cc:464

持久化最终由 QStore::DumpToBackends函数负责,不管是删除还是添加。需要源码的朋友可以加laoliao6668微信获取

哪个线程执行具体的k-v操作

是主线程执行k-v操作

断点

b ExecuteCmd

然后redis-cli执行set get命令操作

(gdb) bt#0 qedis::QCommandTable::ExecuteCmd (params=std::vector of length 3, capacity 4 = {...},  info=0x7ffff7fc4d90 1872>, reply=0x7ffff0001730) at /home/lqf/long/Qedis/QedisCore/QCommand.cc:249#1 0x00007ffff7f69e45 in qedis::QClient::_HandlePacket (this=0x7ffff00015c0,  start=0x7ffff00119a4 "*3\r\n$3\r\nset\r\n$3\r\nkey\r\n$1\r\nd\r\n",  bytes=out>) at /home/lqf/long/Qedis/QedisCore/QClient.h:48#2 0x00007ffff7f1a680 in StreamSocket::DoMsgParse (this=0x7ffff00015c0) at /home/lqf/long/Qedis/QBase/StreamSocket.cc:227#3 0x00007ffff7f1cafb in Internal::TaskManager::DoMsgParse (this=0x7fffffffde20) at /home/lqf/long/Qedis/QBase/TaskManager.cc:99#4 0x00007ffff7f18ace in Server::MainLoop (this=0x7fffffffde10, daemon=out>) at /home/lqf/long/Qedis/QBase/Server.cc:139#5 0x0000555555559fc5 in main (ac=1, av=0x7fffffffe038) at /home/lqf/long/Qedis/QedisSvr/Qedis.cc:464


QError QCommandTable::ExecuteCmd(const std::vector& params, const QCommandInfo* info, UnboundedBuffer* reply){ if (params.empty()) { ReplyError(QError_param, reply); return QError_param; }  if (!info) { ReplyError(QError_unknowCmd, reply); return QError_unknowCmd; }  if (!info->CheckParamsCount(static_cast<int>(params.size()))) { ReplyError(QError_param, reply); return QError_param; }  return info->handler(params, reply); //执行这里,看看进入哪个函数}

set get是是字符串操作,触发QedisCore/QString.cc相关函数的调用

QError set(const std::vector& params, UnboundedBuffer* reply){ SetValue(params[1], params[2]); //存储kv FormatOK(reply); return QError_ok;}

k-v设置都是在主线程处理?需要源码的朋友可以加laoliao6668微信获取

具体操作levedb的封装

QedisCore/QLeveldb.h

QedisCore/QLeveldb.cc

断点

QLeveldb::Put

7 性能测试

需要注意的是这个Qedis项目,新版本的redis-benchmark并不支持,是下载的redis 3.2的版本,我也把源码包一起打包了。

./redis-benchmark -t set,get -n 1000000 -c 1

刚开始时,性能比较差,所以使用tcpdump 分析客户端的请求和响应

sudo tcpdump -A  -i lo host 127.0.0.1 and port 6379

10:59:39.162891 IP localhost.55876 > localhost.6379: Flags [P.], seq 13951:13996, ack 1551, win 512, options [nop,nop,TS val 3294051496 ecr 3294051496], length 45: RESP "SET" "key:rand_int" "xxx" E..a.6@.@._^.........D....}..........U..... .W<..W<.*3 $3 SET $16 key:rand_int$3 xxx 10:59:39.192597 29毫秒才收到回复 IP localhost.6379 > localhost.55876: Flags [P.], seq 1551:1556, ack 13996, win 16384, options [nop,nop,TS val 3294051526 ecr 3294051496], length 5: RESP "OK" E..9F.@.@..............D......~...@..-..... .W<..W<.+OK 10:59:39.192741  0.14毫秒又继续发送请求了 IP localhost.55876 > localhost.6379: Flags [P.], seq 13996:14041, ack 1556, win 512, options [nop,nop,TS val 3294051526 ecr 3294051526], length 45: RESP "SET" "key:rand_int" "xxx" E..a.7@.@._].........D....~..........U..... .W<..W<.*3 $3 SET $16 key:rand_int$3 xxx 10:59:39.207409 IP 14.6毫秒才收到回复,localhost.6379 > localhost.55876: Flags [P.], seq 1556:1561, ack 14041, win 16384, options [nop,nop,TS val 3294051541 ecr 3294051526], length 5: RESP "OK" E..9F.@.@..............D......~9..@..-..... .W<..W<.+OK 10:59:39.207589 0.18毫秒又继续发送请求了 IP localhost.55876 > localhost.6379: Flags [P.], seq 14041:14086, ack 1561, win 512, options [nop,nop,TS val 3294051541 ecr 3294051541], length 45: RESP "SET" "key:rand_int" "xxx" E..a.8@.@._.........D....~9.........U..... .W<..W<.*3 $3 SET $16 key:rand_int$3 xxx 10:59:39.222298 14.8毫秒才收到回复 IP localhost.6379 > localhost.55876: Flags [P.], seq 1561:1566, ack 14086, win 16384, options [nop,nop,TS val 3294051555 ecr 3294051541], length 5: RESP "OK" E..9F.@.@..............D......~f..@..-..... .W<..W<.+OK 10:59:39.222530 IP localhost.55876 > localhost.6379: Flags [P.], seq 14086:14131, ack 1566, win 512, options [nop,nop,TS val 3294051556 ecr 3294051555], length 45: RESP "SET" "key:rand_int" "xxx" E..a.9@.@._[.........D....~f.........U..... .W<..W<.*3 $3 SET $16 key:rand_int$3 xxx 10:59:39.230025 IP localhost.55876 > localhost.6379: Flags [F.], seq 14131, ack 1566, win 512, options [nop,nop,TS val 3294051563 ecr 3294051555], length 0 E..4.:@.@._..........D....~..........(..... .W<..W<. 10:59:39.237170 IP localhost.6379 > localhost.55876: Flags [P.], seq 1566:1571, ack 14132, win 16384, options [nop,nop,TS val 3294051570 ecr 3294051556], length 5: RESP "OK" E..9F.@.@..............D......~...@..-..... .W<..W<.+OK 10:59:39.237296 IP localhost.55876 > localhost.6379: Flags [R], seq 2914614932, win 0, length 0 E..(..@.@.<..........D....~.....P....`..

./redis-benchmark -t set,get -n 1000000 -c 1 -P 100

pipe line模式,性能有提升到1万qps,但此模式下redis-server能到50万qps

E...m.@.@............H..k.r....p..O.........T8..T8.*3$3SET$16key:__rand_int__$3xxx*3$3SET$16

其他参数,参考redis-benchmark的使用。


声明: 本文转载自其它媒体或授权刊载,目的在于信息传递,并不代表本站赞同其观点和对其真实性负责,如有新闻稿件和图片作品的内容、版权以及其它问题的,请联系我们及时删除。(联系我们,邮箱:evan.li@aspencore.com )
0
评论
  • 相关技术文库
  • C语言
  • 编程
  • 软件开发
  • 程序
  • C语言最全入门知识汇总

    C语言知识整理

    8小时前
  • 串行A/D芯片TLC1543 应用

    首先了解下串行A/D芯片TLC1543的参数   有6种工作模式:   本程序使用了2种工作模式来编程,具体看时序图:     源程序: uint Read_AD(uchar AD_Channel) //从TLC1543读取采样值,形参AD_Channel是采样的通道号...

    前天
  • AT89C52单片机芯片实现以太网接口-基于RTL8019

    随着互联网的迅速发展,网络用户飞速增长,在使用计算机进行网络互联的同时,各种家电设备、仪表设备及工业中数据采集与控制设备也在逐步走向网络化,基于此结合专用的以太网控制芯片RTL8019学习了利用单片机实现以...

    前天
  • AT89C52单片机芯片实现段距离无线传输

    在一些特殊的应用场合,单片机通信不能采用有线数据传输方式,而需要采用短距离的无线数据传输方式。短距离的无线传输具有抗干扰能力强、可靠性高、安全性好、受地理条件限制少、安装灵活等优点,可以利用单片机和专...

    前天
  • AT89C52单片机芯片实现智能信号发生器

    单片机不仅在控制系统有很大的应用,在信号的产生方面也有独特的应用。在现代电子学的各个领域中,常常需要高精度切频率方便可调节的信号发生器,可以利用单片集成芯片实现函数信号发生器,这种信号发生器风产生多种...

    前天
  • 单片机实现接触式的IC卡读写控制

    从上高中开始,我们学校用的餐卡就为接触式的IC卡,在校园里还分布着很多的IC卡电话,上大学以后学校使用的校园一卡通,为非接触式是射频式IC卡,因此对IC卡有了兴趣,在学习单片机的过程中,了解到单片机可以实现IC...

    前天
  • DS12C887的日历时钟显示系统设计

    在银行或者其他的公共场合中,经常会看到显示实时信息的显示屏,其中包括年、月、日、星期、时间等,本例子的功能是在51单片机系统中设置、获取、记录实时的日历时钟信息并通过数码管显示,选用日历时钟芯片DS12C887...

    前天
  • MAX1898的智能充电器设计

    单片机在控制方面不仅可以在工业控制方面大展神通,如果用在控制一些很有意思的芯片方面也可以实现实用的功能。比如用单片机控制冲电芯片实现手机的智能充电器设计,基于次参考有关资料学习了基于MAX1898的智能充电...

    前天
  • 单片机控制步进电机系统

    学习了单片机在的基本输入输出和在数据采集领域的应用,接着学习了单片机在控制领域的应用。在控制系统中,通常要控制机械部件的平移和转动,这些机械部件的驱动大都采用交流电机、直流电机和步进电机等,其中步进电...

    前天
  • MPX4105数字气压计设计

    单片机不仅可以用于温度测量,也可以用来测量气压这类非电信号,就要用到气压传感器。气压传感器的原理是将气压信息转换为电流或者电压输出,转换后的电压或者电流常为模拟信号,因此还必须进行A/D转换,以满足单片...

    前天
下载排行榜
更多
评测报告
更多
广告