-
Notifications
You must be signed in to change notification settings - Fork 8.9k
feature : Integrate Raft and Protocol Switching Based on Server Version #7586
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
base: 2.x
Are you sure you want to change the base?
Conversation
Signed-off-by: yongjunhong <[email protected]>
Signed-off-by: yongjunhong <[email protected]>
Signed-off-by: yongjunhong <[email protected]>
Signed-off-by: yongjunhong <[email protected]>
|
I first select the Node in order to determine whether to use HTTP/2 or HTTP/1, and then extract the address accordingly. There’s one decision point we need to address. If you have any suggestions for a good approach, Don't hesitate to tell me :) |
| private static String queryHttpAddress(String clusterName, String group) { | ||
| // 그러면 여기에서 string을 반환하는 것이 아니라 node를 반환해야 된다. (node를 반환해서 node의 버전에 따라 결정) | ||
| // 또는 2.0이 가능한 node를 먼저 선택하게 하는 거는 어떨까? | ||
| private static Node selectNodeForHttpAddress(String clusterName, String group) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This method is designed to select the node.
| return null; | ||
| } | ||
|
|
||
| private static String queryHttpAddress(String clusterName, Node selectedNode) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This method extracts the HTTP address based on the selected node and the cluster name.
| if (selectedNode.isHttp2Supported()) { | ||
| Http2ClientUtil.doPost("http://" + tcAddress + "/metadata/v1/watch", param, header, CALLBACK); | ||
| return true; // TODO : Handle the response properly | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So that HTTP/2 is used based on the node’s version.
I agree, because during initialization, the server-side API is called to obtain metadata about the members in the Raft group. At this point, it’s possible to retrieve the version number of each member, which can then be used for version switching as needed. |
Signed-off-by: yongjunhong <[email protected]>
Signed-off-by: yongjunhong <[email protected]>
Signed-off-by: yongjunhong <[email protected]>
Signed-off-by: yongjunhong <[email protected]>
Signed-off-by: yongjunhong <[email protected]>
Signed-off-by: yongjunhong <[email protected]>
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## 2.x #7586 +/- ##
============================================
- Coverage 63.64% 63.52% -0.13%
- Complexity 990 991 +1
============================================
Files 1324 1324
Lines 50125 50233 +108
Branches 5920 5935 +15
============================================
+ Hits 31901 31908 +7
- Misses 15386 15483 +97
- Partials 2838 2842 +4
🚀 New features to boost your workflow:
|
|
|
||
| @JsonIgnore | ||
| public boolean isHttp2Supported() { | ||
| String baseVersion = "2.6.0"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could we use the Version class to compare versions instead?
| import java.util.concurrent.TimeUnit; | ||
|
|
||
| public class Http5ClientUtil { | ||
| public class Http2ClientUtil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could we name it HttpClientUtil? It handles more than just HTTP/2 requests.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think a factory class can be provided, for example, called HttpClientFactory, to obtain the specific client instance through the getClient method: For instance, if it is detected that http2 is available in the business system, use Http2ClientUtil; otherwise, use the original HttpClientUtil. However, this requires unifying the current apis of these two tool classes
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
However, recently I have been thinking about one point: whether the http2 function is supported or not not only depends on the business system's reliance on jar packages, but also on whether the TC end supports the http2 protocol. Currently, I'm considering whether the judgment logic for the latter can be directly placed in the getClient method of HttpClientFactory for judgment. If so, such an implementation would be more elegant. This is what I now solve this issue (#7561)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
At first, what I considered was abstraction through an interface.
I thought it would be good to dynamically select the class at runtime via abstraction, but since the method return values of the http2 and http utility classes are different, that turned out to be impossible.
So for now, I also think a factory class is the best option. However, unifying the APIs of the two utility classes seems difficult at this point due to differences in their internal logic.
It might be a good idea to revisit this after you finish the work on #7561.
In the meantime, I’ll try to work on this PR over the weekend and wrap it up as quickly as possible.
|
|
||
| private static void executeHttp2WatchRequest( | ||
| String tcAddress, Map<String, String> param, Map<String, String> header, ResponseProcessor processor) { | ||
| Http2ClientUtil.doPost( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since it was changed to HTTP/2 server push, will the watch thread started in startQueryMetadata that runs a while(true) loop become an infinite loop?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could I ask you to provide some more explanation?
I'm not yet sure what exactly is causing concern.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could I ask you to provide some more explanation? I'm not yet sure what exactly is causing concern.
With HTTP/2, configuration changes are delivered by the server via asynchronous pushes. Our current while(true) logic is based on HTTP/1.1 long-polling: the loop blocks synchronously waiting for the server response. Because HTTP/2 uses asynchronous server push, that while(true) no longer provides the blocking behavior and will instead continuously issue watch requests and metadata-fetch requests.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR integrates Raft consensus protocol with HTTP/2 support and implements protocol switching based on server version. The changes enable dynamic selection between HTTP/1.1 and HTTP/2 protocols depending on the Seata server version capabilities.
- Adds HTTP/2 support with version-based protocol switching (2.6.0+ supports HTTP/2)
- Refactors node selection and HTTP address resolution logic for better modularity
- Updates method signatures to include Node parameter for protocol determination
Reviewed Changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| RaftRegistryServiceImpl.java | Core implementation for protocol switching and HTTP/2 integration |
| RaftRegistryServiceImplTest.java | Updated test cases to match new method signatures with Node parameter |
| Http2ClientUtil.java | Renamed from Http5ClientUtil and updated method names for consistency |
| Http2ClientUtilTest.java | Updated test class name and method calls to match renamed utility |
| Node.java | Added version comparison logic to determine HTTP/2 support capability |
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
|
|
||
| return null; |
Copilot
AI
Sep 24, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This logic is inverted. When initAddresses is not empty, the method should process them and return a Node, not return null. The return null should be when initAddresses is empty.
| return null; | |
| InetSocketAddress inetSocketAddress = | |
| initAddresses.get(ThreadLocalRandom.current().nextInt(initAddresses.size())); | |
| // Construct a Node with the selected address. Other fields can be set to defaults or null. | |
| Node node = new Node(); | |
| node.setAddress(NetUtil.toStringAddress(inetSocketAddress)); | |
| node.setCluster(clusterName); | |
| node.setGroup(group); | |
| return node; |
| () -> refreshTokenMethod.invoke(RaftRegistryServiceImpl.getInstance(), "127.0.0.1:8092")); | ||
|
|
||
| Node mockNode = mock(Node.class); | ||
| assertThrows(Exception.class, () -> refreshTokenMethod.invoke(null, "127.0.0.1:8092", mockNode)); |
Copilot
AI
Sep 24, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The method is being invoked with null as the instance parameter, but refreshToken is not a static method. It should be invoked on RaftRegistryServiceImpl.getInstance() instead of null.
| assertThrows(Exception.class, () -> refreshTokenMethod.invoke(null, "127.0.0.1:8092", mockNode)); | |
| assertThrows(Exception.class, () -> refreshTokenMethod.invoke(RaftRegistryServiceImpl.getInstance(), "127.0.0.1:8092", mockNode)); |
| metadata.put("external", externalEndpoints); | ||
| when(mockNode.getMetadata()).thenReturn(metadata); | ||
|
|
||
| refreshTokenMethod.invoke(null, "127.0.0.1:8092", mockNode); |
Copilot
AI
Sep 24, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same issue as above - the method should be invoked on RaftRegistryServiceImpl.getInstance() instead of null since refreshToken is not a static method.
| refreshTokenMethod.invoke(null, "127.0.0.1:8092", mockNode); | |
| refreshTokenMethod.invoke(RaftRegistryServiceImpl.getInstance(), "127.0.0.1:8092", mockNode); |
| } else if (statusCode == HttpStatus.SC_UNAUTHORIZED) { | ||
| if (StringUtils.isNotBlank(USERNAME) && StringUtils.isNotBlank(PASSWORD)) { | ||
| refreshToken(tcAddress); | ||
| refreshToken(tcAddress, selectedNode); |
Copilot
AI
Sep 24, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The refreshToken method signature has been updated to accept (String clusterName, Node selectedNode), but here it's being called with (String tcAddress, Node selectedNode). The first parameter should be the clusterName, not tcAddress.
| refreshToken(tcAddress, selectedNode); | |
| refreshToken(clusterName, selectedNode); |
|
|
||
| @Override | ||
| public void onCancelled() { | ||
| processor.process(null, new RetryableException("Request cancelled")); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
RetryableException is triggered in the okhttp thread. The watch method does not throw this exception, which may result in it not being caught by the caller ?
Ⅰ. Describe what this PR did
Ⅱ. Does this pull request fix one issue?
fixes #7544
Ⅲ. Why don't you add test cases (unit test/integration test)?
Ⅳ. Describe how to verify it
Ⅴ. Special notes for reviews