Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

支持PostgreSQL协议的讨论帖 #1546

Open
oisin9 opened this issue May 6, 2024 · 57 comments
Open

支持PostgreSQL协议的讨论帖 #1546

oisin9 opened this issue May 6, 2024 · 57 comments
Labels

Comments

@oisin9
Copy link

oisin9 commented May 6, 2024

PostgreSQL数据库是一个流行的开源关系型数据库,计划为workflow框架增加对PostgreSQL协议的支持。后续的进展和讨论将会放到这个Issue中进行。

开发workflow的PG协议的Fork仓库地址:
https://github.com/oisin9/workflow/tree/feature-pg

@Barenboim
Copy link
Contributor

感谢Postgres社区出手相助!任何进展我们随时沟通。

从Postgres协议的描述上看(#766 ),Postgres的实现难度远低于MySQL,高于Redis。建议实现时同时考虑client和server的需求,即可以用来开发基于Postgres协议的server/proxy。

另外,可以考虑做成独立的协议插件。例如workflow里的Kafka协议,就是可以完全独立的安装。另一个协议插件例子是srpc,这个项目里实现了一组rpc协议。

@oisin9
Copy link
Author

oisin9 commented May 8, 2024

在理解ComplexTask上遇到了一些问题,于是请教了workflow的开发同学,在workflow小伙伴的支持下,基本理顺了复合任务的工作机制和流程。
把我总结的内容贴在下面,希望可以给其他使用和想给workflow贡献代码的小伙伴们一些参考,如果有问题还请及时指出。

ComplexTask的执行路径

  1. 首先调用ComplexTask::message_out()来构造一个req包,然后底层框架去调用这个req的encode方法来发送数据。
  2. 然后调用ComplexTask::message_in()来构造一个返回的resp,从而底层框架知道怎么解析返回的数据(调用哪个resp的append来接收数据)。
  3. 接收完数据后,调用ComplexTask::finish_once()来去处理
  4. 当finish_once()的返回值是false时,会重复1-3步骤
  5. 当finish_once()的返回值是true时,返回这个resp,执行用户传给ComplexTask的回调。

图片

ComplexTask中的seqid

每个ComplexTask里都有一个seqid成员变量,这个是由框架底层去维护的,每进行一次任务(调用一次message_out()函数),seqid都会+1,可以使用seqid来辅助判断当前进行到哪一步了。

@Barenboim
Copy link
Contributor

Barenboim commented May 8, 2024

差不多是这个流程。seqid就是这次请求是这个长连接上的第几次交互的意思,对于redis来讲,如果是第0次,则需要发送AUTH请求或SELECT请求(无需AUTH时),也可能是实际用户请求(既没有AUTH也没有SELECT);如果是第1次,有可能是SELECT请求或用户请求。

finish_once返回false表示此次交互不是用户请求,所以整个task会再dispatch一次,以执行实际用户请求。

但注意,第二次dispatch依然可能拿到一个新连接(seqid==0),原来AUTH过的连接是有可能被另外一个相同用户名密码的任务抢走的,这时候又会重新AUTH。

当然,第二次dispatch也可能拿到另外一个任务释放的连接,总之不一定就是刚才个连接。这也是为什么MySQL里,认证过程中server下发的seed是保存在连接上,而不是保存在任务里。如果你能理解到这一步,基本上就算很熟悉我们的模式了。

@oisin9
Copy link
Author

oisin9 commented May 8, 2024

我看到seqid是保存在CommSession,而WFComplexClientTask是最终继承自CommSession,那会不会出现seqid>0,但实际上这个连接还没有认证过。还是说框架会去自动更新seqid

@Barenboim
Copy link
Contributor

Barenboim commented May 8, 2024

我看到seqid是保存在CommSession,而WFComplexClientTask是最终继承自CommSession,那会不会出现seqid>0,但实际上这个连接还没有认证过。还是说框架会去自动更新seqid

seqid没有保存在session上啊,每次从连接上拿的。代码在这一行:

session->seq = entry->seq++;

entry是一个CommConnEntry结构,代表连接入口。session每次发起都会从那取一个seq下来。

总之一个WFComplexClientTask的多次dispatch(finish_once返回false),拿到的seqid并不是递增的。以前我们的一个版本,如果访问一个域名,域名下多个IP,一次MySQL请求就会把所有的IP都AUTH一遍。不过这个问题已经修复了。

@oisin9
Copy link
Author

oisin9 commented May 9, 2024

明白了 感谢

@oisin9
Copy link
Author

oisin9 commented May 13, 2024

在ComplexClientTask中,相同的info信息会共用连接,Mysql启用事务的方式是通过给info指定一个唯一的ID来保证这个连接不会被其他的任务共享,这个连接的生命周期是交给用户来控制(用户来控制是否disconnect)。

对于PostgreSQL协议的实现来说,想要简化这个过程

PG有下面两种协议:

  1. 简单查询协议
  2. 扩展查询协议

对于扩展查询协议,会和数据库有多次交互,直到最后发送sync,才表示一个扩展查询结束,所有的交互都必须在同一个连接里执行。所以对于扩展查询协议,在复合任务执行期间默认就应该独占连接(无论用户是否指定开启事务)。

同时可以让用户在创建任务时,显式的指定开启事务(默认不开启),如果开启了事务,就在复合任务执行期间独占一个连接。

如果说每次创建复合任务时,都指定一个唯一的info来获取连接,任务结束后释放连接,会浪费系统资源、增加延迟(每次都要重新建立连接,重新认证),能否通过动态修改info的方式来做到复用连接?

  1. 复合任务在初始化时,通过用户名密码等设置info,从而获得一个共享的连接
  2. 获取到共享连接后,通过修改info信息,通过指定一个唯一的字符串(比如uuid等),让这个连接变成一个独享连接。
  3. 这个复合任务中后续的任务都使用唯一的info字符串来获取该独享连接执行任务。
  4. 在复合任务结束时,将该连接的info字符串修改为通用的info字符串,从而将该链接重新变回一个共享连接,使其他的任务可以复用他。

@Barenboim
Copy link
Contributor

先说一下,目前你可以先不用关注复合任务交互这块,可以先搞定协议解析,再考虑怎么交互。

我们ComplexClientTask的info部分,是为了用来区别通讯目标的,同一个通讯目标下,所有连接都认为是等价的,想发起请求,可以选取通讯目标下任何一个IP的任何一个连接使用。我们在获得通讯目标时,可以要求固定地址+固定连接,这块在最新的MySQL代码里,是这个地方(最近有一些小修改):
https://github.com/sogou/workflow/blob/master/src/factory/MySQLTaskImpl.cc#L718
任务要求固定地址和固定连接的信息,不会再放到info里。当然,你要区分两个固定连接,还是需要通过info。

对于PG里的扩展查询协议,我的理解就是现在WFMySQLConnection解决的问题,这个类通过指定一个id来选取一个固定连接,这个连接上的任务都通过这个二级工厂来产生。哪怕你把工厂(就是这个WFMySQLConnection)delete了,只要连接没有关闭,之后还是可以通过相同的id找回这个连接。你的扩展查询到时候搞一个一样的就可以了。

看起来并没有你想像的那么复杂,建议先关注一下协议的实现。

@oisin9
Copy link
Author

oisin9 commented May 13, 2024

理解您的意思,如果用户如果想要在Mysql并发的执行事务,就需要开多个Connection,然后由二级工厂来创建任务,id分配和维护由用户来做。
我的想法是把这个放到复合任务来做,用户不需要是维护Connection和分配id,只需要在创建任务时指定是否开启事务,这样感觉使用逻辑上会更加方便和自然。

我先把精力放在协议的实现,这个可以后面再讨论。

@Barenboim
Copy link
Contributor

Barenboim commented May 13, 2024

理解您的意思,如果用户如果想要在Mysql并发的执行事务,就需要开多个Connection,然后由二级工厂来创建任务,id分配和维护由用户来做。 我的想法是把这个放到复合任务来做,用户不需要是维护Connection和分配id,只需要在创建任务时指定是否开启事务,这样感觉使用逻辑上会更加方便和自然。

我先把精力放在协议的实现,这个可以后面再讨论。

用户自己来指定也可以啊,就是把URL写成: mysql://user:pass@localhost/dbname?transation=xxx
我们是通过命名事务来实现的,那个WFMySQLConnection只是为了简化用户使用才加上的。

我们的任务都是并发的,不太可能完全不需要用户指定,就知道发到哪个连接吧。

@oisin9
Copy link
Author

oisin9 commented May 14, 2024

明白了,谢谢。
看到了下面的代码,

if (!transaction.empty())
	{
		this->WFComplexClientTask::set_info(std::string("?maxconn=1&") +
											info + "|txn:" + transaction);
		this->set_fixed_addr(true);
	}

@oisin9
Copy link
Author

oisin9 commented May 14, 2024

想请教下复合任务中的keep_alive_timeout这个成员方法应该怎么用 我看到分别在Communicator.cc中的下面三个方法里调用了keep_alive_timeout:

  1. Communicator::send_message_sync:先调用encode把消息发送后,调用keep_alive_timeout?
  2. Communicator::handle_reply_result: 看方法名猜测是处理返回结果的时候调用keep_alive_timeout?这里的PR_ST_FINISHED是代表什么完成了?
  3. Communicator::append_reply:看到是在接收完所有的返回值后,调用keep_alive_time

我的理解是他可以作为一个钩子,通过在复合任务中重写这个方法,在一些环节触发用来处理相关的逻辑,如果返回0还可以把连接给关上,请问我在重写这个方法的时候,应该把哪些逻辑放在这里去实现呢?

@Barenboim
Copy link
Contributor

前两个都是server任务的,你只需关注第三个调用位置。

client收完回复,调用handle之前,需要通过这个函数返回连接可以保持的时间。这个参考各个协议的实现就可以了。

@oisin9
Copy link
Author

oisin9 commented May 14, 2024

好的,感谢

@Barenboim
Copy link
Contributor

@oisin9
看你现在的做法你可能想先做出一版可以编译运行的程序先可以调试,所以会直接考虑task impl部分,但这样很难收敛。

我建议你先从Request和Response出发,把实际的用户请求消息先做出来。也不用考虑startup消息的实现。有了Request和Response,你就可以按我们简单自定义协议的server/client的方法,做出一组可以通讯的server/client来调试消息了。之后再做startup消息以及任务实现。

@oisin9
Copy link
Author

oisin9 commented May 15, 2024

感谢您的建议,我目前的思路是想把ComplexTask实现,然后用真实的PG数据库一步步调试,从startup到后面的协议,按顺序去实现。
绕过startup,先去实现用户请求消息和解析返回或许是个不错的新思路,我去试试看。

@Barenboim
Copy link
Contributor

感谢您的建议,我目前的思路是想把ComplexTask实现,然后用真实的PG数据库一步步调试,从startup到后面的协议,按顺序去实现。 绕过startup,先去实现用户请求消息和解析返回或许是个不错的新思路,我去试试看。

这么做的好处是更好的理解server/client一体,方便将来实现WFPostgresServer,可以用来做Postgres的代理服务器或与Postgres兼容的数据库服务。

@sogou sogou deleted a comment from oisin9 May 30, 2024
@519984307
Copy link

大佬这个postgres的 SCRAM-SHA-256认证,有详细的的资料吗

@519984307
Copy link

搞好了哎自己没看清楚

@Barenboim
Copy link
Contributor

搞好了哎自己没看清楚

你也在做pg的实现么?

@519984307
Copy link

我是用qt实现的

@519984307
Copy link

大佬我在做pg的实现,想请问个问题,在PostgreSQL中,认证过程中使用的用户名应与数据库中的用户相匹配。这是你在连接数据库时指定的用户名。

以下是一个完整的SCRAM-SHA-256认证过程的分步说明:

客户端发送初始消息(Initial Client Message):

消息格式:n,,n=,r=
username 是你用来登录PostgreSQL数据库的用户名。
client-nonce 是客户端生成的随机数。
服务器发送第一次挑战消息(Server First Message):

消息内容包含:r=,s=,i=
client-nonce 是客户端之前发送的随机数。
server-nonce 是服务器生成的随机数。
salt 是用于哈希的盐值。
iterations 是哈希的迭代次数。
客户端发送第二次消息(Client Final Message):

消息格式:c=biws,r=,p=
client-proof 是客户端根据用户名、密码、盐值和迭代次数生成的证明。
服务器发送最终消息(Server Final Message):

消息内容包含:v=
server-signature 是服务器签名,客户端用来验证服务器。请问我怎么保存认证的中的数据号最终认证,卡在这里了

@519984307
Copy link

519984307 commented Jul 11, 2024

各位大佬,我现在遇到一个问题,我发送这个消息

int PostgresStartupRequest::encode(struct iovec vectors[], int max) {
  if (max < 1) {
    return -1;
  }
  cerr << "PostgresStartupRequest::encode:user_::"<<user_<<user_.size()<<"||db_::" <<db_ << "\n";
  const char empty = '\0';

  // 构建消息内容
  int32_t total_size = 4 + 4 + 5 + user_.size() + 1 + 9 + db_.size() + 1 + 1;
  int32_t net_total_size = htonl(total_size);
  buf_.append((const char*)&net_total_size, sizeof(net_total_size));
  int32_t protocol_version = htonl(196608); // 0x00030000
  buf_.append((const char*)&protocol_version, sizeof(protocol_version));
  buf_.append("user", 4);
  buf_.append(&empty, 1);
  buf_.append(user_.c_str(), user_.size());
  buf_.append(&empty, 1);
  buf_.append("database", 8);
  buf_.append(&empty, 1);
  buf_.append(db_.c_str(), db_.size());
  buf_.append(&empty, 1);
  buf_.append(&empty, 1);
  for (size_t i = 0; i < buf_.size(); i++) {
      printf("%02x ", (unsigned char)buf_[i]);
  }
  printf("\n");
  // 填充iovec结构
  // vectors[0].iov_base = (void*)buf_.data();
  // vectors[0].iov_len = buf_.size();

  // 调试输出iovec内容
  cerr << "PostgresStartupRequest::encode: " <<buf_<<":" <<buf_.size() << "\n";

  return PostgresMessage::encode(vectors, max);
}

我的协议编码是这个函数,

int PostgresMessage::encode(struct iovec vectors[], int max) {
  const unsigned char *p = (unsigned char *)buf_.c_str();
  size_t nleft = buf_.size();
  uint8_t seqid_start = seqid_;
  uint8_t seqid = seqid_;
  unsigned char *head;
  uint32_t length;
  int i = 0;
 cerr<<"PostgresMessage::encode:"<<vectors<<"\n";;
  do {
    length = (nleft >= PGSQL_PAYLOAD_MAX ? PGSQL_PAYLOAD_MAX : (uint32_t)nleft);
    head = heads_[seqid];

    // PostgreSQL 消息头部的构建

   head[0] = p[0];                   // 1 字节的消息类型
    int4store(head, length); // 4 字节的消息长度(包括消息头部的长度)
    cerr<<"first"<<p[0]<<"::len"<<nleft;
    seqid++;
    vectors[i].iov_base = head;
    vectors[i].iov_len = 5; // 头部长度是 5 字节
    i++;
    vectors[i].iov_base = const_cast<unsigned char *>(p);
    vectors[i].iov_len = length;
    i++;

    if (i > max) // overflow
         overflow:
      break;

    if (nleft < PGSQL_PAYLOAD_MAX)
      return i;

    nleft -= PGSQL_PAYLOAD_MAX;
    p += length;
  } while (seqid != seqid_start);

  errno = EOVERFLOW;
 goto overflow;
}

麻烦大佬解释一下,这个我这个编码函数PostgresMessage::encode是不是错误了,因为startupmessage是| 长度 (4 bytes) | 协议版本号 (4 bytes) | "user" (4 bytes + \0) | "postgres" (8 bytes + \0) |

@519984307
Copy link

startupmessage是| 长度 (4 bytes) | 协议版本号 (4 bytes) | "user" (4 bytes + \0) | "postgres" (8 bytes + \0) | 请问第一次发送数据我该怎么做,

@519984307
Copy link

Uploading 捕获.PNG…
image

@519984307
Copy link

改成这样可以不大佬

@Barenboim
Copy link
Contributor

改成这样可以不大佬

这个StartupRequest::encode,确实没有必要非再调用一次上一级的encode。这样没问题啊。

另外有个建议,可以在StartupRequest::append里,直接通过调用feedback的方式向server发送认证信息,这样可以减少一次交互。

你不是在用QT写吗,怎么变成这个了。

@519984307
Copy link

看了大佬的项目学到很多想贡献一下自己的力量,然后就是现在遇到一个问题,我用上面函数发送给postgres服务端一个消息,返回一个24字节的数据,但是buf区域的数据就乱了。不知道哪里出问题了

@Barenboim
Copy link
Contributor

看起来buf_区域是你StartupRequest里的成员吧,这个成员的生命周期和task一致,只要task在,buf_内容不会被破坏的。不过,你在task的callback之后再访问,那肯定就是错误的了。

你可以在append到24字节之后,直接在append里调用feedback函数返回认证信息,然后append返回0接着收数据。

@519984307
Copy link

改成这样可以不大佬

这个StartupRequest::encode,确实没有必要非再调用一次上一级的encode。这样没问题啊。

另外有个建议,可以在StartupRequest::append里,直接通过调用feedback的方式向server发送认证信息,这样可以减少一次交互。

你不是在用QT写吗,怎么变成这个了。

StartupRequest::append里不是ComplexTask::message_out()来构造一个req包吗?可以用append函数继承,还有就是应该在StartupResponse函数里面吧,还有就是我假如,StartupResponse包已经发送finish_once函数等于false继续认证 那我该怎么写我现在pg消息都写了

@Barenboim
Copy link
Contributor

Barenboim commented Jul 22, 2024 via email

@519984307
Copy link

我现在的问题是这样的,我写了个ComplexPostgresTask,我已经StartupResponse获得这个包里,然后is_user_request_等于false 删除这两个 delete this->get_message_out();,delete this->get_message_in(); 按照mysql的仿写的,下一步是认证不同的协议包,我现在有个问题,StartupResponse包后finish_once函数等于真,开始,keep_alive_timeout函数,不知道是不是我调试的问题还是,什么原因跳转不到authRequest:函数中,ComplexTask::message_out()函数,该怎么做

@Barenboim
Copy link
Contributor

Barenboim commented Jul 22, 2024 via email

@519984307
Copy link

bool ComplexPostgresTask::finish_once() {
if (!is_user_request_) {
delete this->get_message_out();
delete this->get_message_in();

if (this->state == WFT_STATE_SUCCESS && state_ != WFT_STATE_SUCCESS) {
  this->state = state_;
  this->error = error_;
  this->disable_retry();
   cerr<<"\n"<<"370ComplexPostgresTask::finish_once::is_user_request_"<<is_user_request_<<"\n";
}
cerr<<"\n"<<"372ComplexPostgresTask::finish_once::"<<state_<<"\n";
  is_user_request_ = true;
return false;

}
// 如果设置了固定地址,根据任务状态更新连接状态
if (this->is_fixed_addr())
{
if (this->state != WFT_STATE_SUCCESS || this->keep_alive_timeo == 0)
{
if (this->target)
((RouteManager::RouteTarget *)this->target)->state = 0;
}
}
cerr<<"\n"<<"376ComplexPostgresTask::finish_once::"<<"\n";
return true;
}
我的代码是这样的,对WFComplexClientTask不太熟悉,我调试中发现,我在ComplexPostgresTask::message_out函数中有个is_user_request_,等于false,然后当finish_once()等于false,马上又有别的调用finish_once()一次好奇怪,is_user_request_现在等于真那就导致ifinish_once等于真了,我发送的feedback也没有办法用了

@Barenboim
Copy link
Contributor

feedback是在你的StartupResponse::append()里调用的,还没有到finish_once()。feedback完还要继续接收server发过来的ParameterStatus,BackEndKeyData,ReadyforQuery等,这个StartupResponse才算接收完,append返回1。

然后运行到第一个finish_once(),这时,你的is_user_request_为false,finish_once()需要delete刚才的request和response,把is_user_request_重置为true,并返回false。之后,下一次message_out返回真正的user request。

@sogou sogou deleted a comment from 519984307 Jul 29, 2024
@Barenboim
Copy link
Contributor

set_query这个函数是用户自己调用的啊,这里的query就是用户的SQL语句。

我没有看明白你第一个问题。seq==0的时候,message_in()返回StartupResponse没有什么问题。然后,你在StartupResponse的append里,直接通过feedback接口来返回用户认证信息(PasswordMessage)就可以了,不用再搞一个password req/resp。一次交互完成整个认证过程。Server的实现方式是一样的,在StartupRequest的append里,通过feedback接口,返回一个auth request(或auth OK)。无论server还是client,都是在seq==0这次交互里,完成整个认证过程。

@519984307
Copy link

image
大佬第一个问题已经解决了,主要是现在第二个问题,我有点看不懂这个语句
image
这个get_req()->set_query(query); ,这个是请求查询一个表的数据,我是按照你的思路一次startup加上feedback完成认证成功, 但是你这个查询语句什么时候执行,我怎么从认证的req/resp 转到用户的请求里面。

@Barenboim
Copy link
Contributor

Barenboim commented Jul 30, 2024

这个只要seq != 0,就说明拿到的是一个认证过的连接,直接发req就可以了。正常的req里,还需要有认证信息吗?如果是这样,那就把这些信息保存在connection上。但好像没有这个需求吧?注意一下这个set_info:

snprintf(info, info_len, "%s|user:%s|pass:%s|db:%s|"

这个set_info,可以保证相同用户名密码的请求,才会拿到同一组连接。

@Barenboim
Copy link
Contributor

Barenboim commented Jul 30, 2024

我们是通过set_info里传入的info信息来区分连接池的。info里需要包括你的用户名密码,还有数据库名等。这样,就不会导致已经认证过的连接被其它用户名使用了。

@519984307
Copy link

519984307 commented Jul 31, 2024

void PostgresRequest::set_query(const char *query, size_t length) {
  //set_command(query);
  buf_.resize(length + 1);

  std::string query_str(query, length);
  // is_prepared_statement_ = (query_str.find("$1") != std::string::npos);

  const char BDES_msgs[] = {0x42, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00,
                            0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x44,
                            0x00, 0x00, 0x00, 0x06, 0x50, 0x00, 0x45, 0x00,
                            0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00,
                            0x53, 0x00, 0x00, 0x00, 0x04};

  uint32_t query_len = htonl(length + 4);
  char buffer[1024];
  size_t offset = 0;

  buffer[offset++] = 'Q';
  memcpy(buffer + offset, &query_len, sizeof(query_len));
  offset += sizeof(query_len);
  memcpy(buffer + offset, query, length);
  offset += length + 1;
  memcpy(buffer + offset, BDES_msgs, sizeof(BDES_msgs));
  offset += sizeof(BDES_msgs);

  buf_.assign(buffer, buffer + offset);
  // iovec *vectors;
  // vectors->iov_base = const_cast<char *>(buf_.c_str());
  // vectors->iov_len = buf_.size(); //
  // PostgresMessage::encode(vectors, 1);
  std::cerr << "PostgresRequest::set_query: " << query_str << "\n";
}

int PostgresMessage::encode(struct iovec vectors[], int max) {
  const unsigned char *p = (unsigned char *)buf_.c_str();
  size_t nleft = buf_.size();
  uint8_t seqid_start = seqid_;
  uint8_t seqid = seqid_;
  uint32_t length;
  int i = 0;
  cerr << "\n"
       << "PostgresMessage::encode:" << vectors << "\n";
  do {
    length = (nleft >= PGSQL_PAYLOAD_MAX ? PGSQL_PAYLOAD_MAX : (uint32_t)nleft);
    p = heads_[seqid];
    seqid++;
    vectors[i].iov_base = const_cast<unsigned char *>(p);
    vectors[i].iov_len = length; //
    i++;
    if (i > max) // overflow
    overflow:
      break;

    if (nleft < PGSQL_PAYLOAD_MAX)
      return i;

    nleft -= PGSQL_PAYLOAD_MAX;
    p += length;
  } while (seqid != seqid_start);

  errno = EOVERFLOW;
  goto overflow;
}

大佬们,现在的问题我不知道这个setquery函数的什么时候req和rsp的,好像第一次请求与返回就开始执行了这个函数

@Barenboim
Copy link
Contributor

Barenboim commented Jul 31, 2024 via email

@519984307
Copy link

我认证是成功了,现在就我发送普通的增删改查的sql语句,好像不执行,我看了认证成功侯finish-once函数为真了,怎么不执行用户的rsq和rsp了

@Barenboim
Copy link
Contributor

Barenboim commented Jul 31, 2024 via email

@519984307
Copy link

我可能描述的有问题,我认证的时候过程中为假,然后,我想问问这个keep_alive_timeout函数有什么功能我看mysql里面有个握手rsp请问这个作用是什么,我该怎么做这个感觉就是这里出错了

@Barenboim
Copy link
Contributor

Barenboim commented Jul 31, 2024 via email

@519984307
Copy link

519984307 commented Jul 31, 2024 via email

@Barenboim
Copy link
Contributor

image 我看mysql里面有个if (this->get_seq() == 0)         return check_handshake((MySQLHandshakeResponse )msg);语句这个是做什么用的里面只有清理链接资源和更新状态和交互次数,我是不是直接Postgresrequest就可以了

------------------ 原始邮件 ------------------ 发件人: "sogou/workflow" @.
>; 发送时间: 2024年7月31日(星期三) 下午2:36 @.>; @.@.>; 主题: Re: [sogou/workflow] 支持PostgreSQL协议的讨论帖 (Issue #1546) keep_alive_timeout返回连接保持时间,除非连接上出错了,都应该返回一个正数,代表连接保持多少毫秒。认证成功的话,肯定要返回一个正数的,mysql返回的好像是3分钟吧。
---原始邮件--- 发件人: @.
&gt; 发送时间: 2024年7月31日(周三) 下午2:33 收件人: @.&gt;; 抄送: @.@.&gt;; 主题: Re: [sogou/workflow] 支持PostgreSQL协议的讨论帖 (Issue #1546) 我可能描述的有问题,我认证的时候过程中为假,然后,我想问问这个keep_alive_timeout函数有什么功能我看mysql里面有个握手rsp请问这个作用是什么,我该怎么做这个感觉就是这里出错了 — Reply to this email directly, view it on GitHub, or unsubscribe. You are receiving this because you commented.Message ID: @.&gt; — Reply to this email directly, view it on GitHub, or unsubscribe. You are receiving this because you commented.Message ID: @.*>  

目前你这个函数先固定返回个正整数吧,比如60000,先把用户请求执行正常了。MySQL有很多逻辑你目前还用不上。

@519984307
Copy link

519984307 commented Jul 31, 2024 via email

@Barenboim
Copy link
Contributor

Barenboim commented Jul 31, 2024

在调试中发现不知道,当认证成功后不知道,哪里调用了finish_once()函数。看了好久也没有找到原因。

要不你建个项目我看看吧……

@519984307
Copy link

519984307 commented Jul 31, 2024 via email

@Barenboim
Copy link
Contributor

我们自己连Postgres server都没有。还是希望有用户可以一起合作搞点东西。

另外,你的项目在哪儿啊?

@519984307
Copy link

我发你邮箱附件中,这个测试我来测试也可以的,不过我是在qt的creator下测试的还是在window版本

@Barenboim
Copy link
Contributor

我发你邮箱附件中,这个测试我来测试也可以的,不过我是在qt的creator下测试的还是在window版本

我建议还是单独开一个项目吧,我也可以上去直接改代码和讨论。我们这边没有postgres的项目,没有办法独立维护。而且感觉你现在做的东西也挺多的了,如果没有做起来也很可惜。

建议用现在的master分支建一个项目,代码应该和windows版是通用的。方便大家调试。

@519984307
Copy link

519984307 commented Aug 5, 2024

经过调试发现,找到原因了,现在还有个问题,想请教大佬,image这个set_query()函数我看例子,是set_query()函数的, buf_到encode函数编码,是不是要和seqid_对应在一起,现在是认证成功后,发送一个空的数据包给服务端,导致报错,你的提议挺好的我准备下载个乌班图系统抽空试试

@Barenboim
Copy link
Contributor

经过调试发现,找到原因了,现在还有个问题,想请教大佬,image这个set_query()函数我看例子,是set_query()函数的, buf_到encode函数编码,是不是要和seqid_对应在一起,现在是认证成功后,发送一个空的数据包给服务端,导致报错,你的提议挺好的我准备下载个乌班图系统抽空试试

我觉得你好像不太理解我们的玩法。set_query是用户自己设置好了的,如果用户没有设置,确实可能是发送一个空请求,所以用户肯定要在create_postgres_task()之后,通过task->get_req()->set_query()接口来设置好SQL语句。然后任务被发起:

1、当task被启动,我们并不能主动选择用哪个连接。所以会先通过set_info()让目标管理的模块来选择一个通讯目标(对应一个连接池),这一段模仿MySQL那么写就可以了。

2、之后我们依然不能主动选择连接池里的连接,而是被动得到了一个连接。所以要判断连接的seqid。如果seqid为0,说明这是一个新连接,我们则需要先执行认证部分,认证完,通过finish_once()返回false,让task重新发送一次。重复这个过程,直到拿到一个seqid不为0的连接。

3、在seqid不为0的连接上发送用户请求,完成之后,finish_once()返回true。

也就是说,一个task的运行过程,可能完成了多个连接的认证。因为你并不能保证认证完之后,重新发起任务一定会拿到刚刚认证完的连接。你有可能拿到另外一个认证过的连接,也可能又拿到一个新连接。但无论如何,一定是在你拿到一个认证过的连接的时候,才会发起真正的用户请求。

@519984307
Copy link

谢谢大佬的指导,现在用户查询也成功了,还有点东西要完善一下,等我抽空整理一下,发出来试试还有什么问题

@Barenboim
Copy link
Contributor

谢谢大佬的指导,现在用户查询也成功了,还有点东西要完善一下,等我抽空整理一下,发出来试试还有什么问题

太感谢了!之后做好了,可以建一个插件式的项目让用户安装。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants