当前位置: 首页 > news >正文

Nacos源码—9.Nacos升级gRPC分析七

大纲

10.gRPC客户端初始化分析

11.gRPC客户端的心跳机制(健康检查)

12.gRPC服务端如何处理客户端的建立连接请求

13.gRPC服务端如何映射各种请求与对应的Handler处理类

14.gRPC简单介绍

10.gRPC客户端初始化分析

(1)gRPC客户端代理初始化的源码

(2)gRPC客户端启动的源码

(3)gRPC客户端发起与服务端建立连接请求的源码

(1)gRPC客户端代理初始化的源码

Nacos客户端注册服务实例时会调用NacosNamingService的registerInstance()方法,接着会调用NamingClientProxyDelegate的registerService()方法,然后判断注册的服务实例是不是临时的。如果注册的服务实例是临时的,那么就使用gRPC客户端代理去进行注册。如果注册的服务实例不是临时的,那么就使用HTTP客户端代理去进行注册。

NacosNamingService的init()方法在创建客户端代理,也就是执行NamingClientProxyDelegate的构造方法时,便会创建和初始化gRPC客户端代理NamingGrpcClientProxy。

创建和初始化gRPC客户端代理NamingGrpcClientProxy时,首先会由RpcClientFactory的createClient()方法创建一个RpcClient对象,并将GrpcClient对象赋值给NamingGrpcClientProxy的rpcClient属性,然后调用NamingGrpcClientProxy的start()方法启动RPC客户端连接。

在NamingGrpcClientProxy的start()方法中,会先注册一个用于处理服务端推送请求的NamingPushRequestHandler,然后调用RpcClient的start()方法启动RPC客户端即RpcClient对象,最后将NamingGrpcClientProxy自己作为订阅者向通知中心进行注册。

public class NacosNamingService implements NamingService {...private NamingClientProxy clientProxy;private void init(Properties properties) throws NacosException {...this.clientProxy = new NamingClientProxyDelegate(this.namespace, serviceInfoHolder, properties, changeNotifier);}...@Overridepublic void registerInstance(String serviceName, Instance instance) throws NacosException {registerInstance(serviceName, Constants.DEFAULT_GROUP, instance);}@Overridepublic void registerInstance(String serviceName, String groupName, Instance instance) throws NacosException {NamingUtils.checkInstanceIsLegal(instance);//调用NamingClientProxy的注册方法registerService(),其实就是NamingClientProxyDelegate.registerService()方法clientProxy.registerService(serviceName, groupName, instance);}...
}//客户端代理
public class NamingClientProxyDelegate implements NamingClientProxy {private final NamingHttpClientProxy httpClientProxy;private final NamingGrpcClientProxy grpcClientProxy;public NamingClientProxyDelegate(String namespace, ServiceInfoHolder serviceInfoHolder, Properties properties, InstancesChangeNotifier changeNotifier) throws NacosException {...//初始化HTTP客户端代理this.httpClientProxy = new NamingHttpClientProxy(namespace, securityProxy, serverListManager, properties, serviceInfoHolder);//初始化gRPC客户端代理this.grpcClientProxy = new NamingGrpcClientProxy(namespace, securityProxy, serverListManager, properties, serviceInfoHolder);}...@Overridepublic void registerService(String serviceName, String groupName, Instance instance) throws NacosException {getExecuteClientProxy(instance).registerService(serviceName, groupName, instance);}private NamingClientProxy getExecuteClientProxy(Instance instance) {return instance.isEphemeral() ? grpcClientProxy : httpClientProxy;}...
}//gRPC客户端代理
public class NamingGrpcClientProxy extends AbstractNamingClientProxy {private final String namespaceId;private final String uuid;    private final Long requestTimeout;    private final RpcClient rpcClient;private final NamingGrpcRedoService redoService;//初始化gRPC客户端代理public NamingGrpcClientProxy(String namespaceId, SecurityProxy securityProxy, ServerListFactory serverListFactory, Properties properties, ServiceInfoHolder serviceInfoHolder) throws NacosException {super(securityProxy);this.namespaceId = namespaceId;this.uuid = UUID.randomUUID().toString();this.requestTimeout = Long.parseLong(properties.getProperty(CommonParams.NAMING_REQUEST_TIMEOUT, "-1"));Map<String, String> labels = new HashMap<String, String>();labels.put(RemoteConstants.LABEL_SOURCE, RemoteConstants.LABEL_SOURCE_SDK);labels.put(RemoteConstants.LABEL_MODULE, RemoteConstants.LABEL_MODULE_NAMING);//1.通过RpcClientFactory.createClient()方法创建一个GrpcSdkClient对象实例,然后赋值给rpcClient属性this.rpcClient = RpcClientFactory.createClient(uuid, ConnectionType.GRPC, labels);this.redoService = new NamingGrpcRedoService(this);//2.启动gRPC客户端代理NamingGrpcClientProxystart(serverListFactory, serviceInfoHolder);}private void start(ServerListFactory serverListFactory, ServiceInfoHolder serviceInfoHolder) throws NacosException {rpcClient.serverListFactory(serverListFactory);//注册连接监听器rpcClient.registerConnectionListener(redoService);//1.注册一个用于处理服务端推送请求的NamingPushRequestHandlerrpcClient.registerServerRequestHandler(new NamingPushRequestHandler(serviceInfoHolder));//2.启动RPC客户端RpcClientrpcClient.start();//3.将NamingGrpcClientProxy自己作为订阅者向通知中心进行注册NotifyCenter.registerSubscriber(this);}...@Overridepublic void registerService(String serviceName, String groupName, Instance instance) throws NacosException {NAMING_LOGGER.info("[REGISTER-SERVICE] {} registering service {} with instance {}", namespaceId, serviceName, instance);redoService.cacheInstanceForRedo(serviceName, groupName, instance);//执行服务实例的注册doRegisterService(serviceName, groupName, instance);}//Execute register operation.public void doRegisterService(String serviceName, String groupName, Instance instance) throws NacosException {//创建请求参数对象InstanceRequest request = new InstanceRequest(namespaceId, serviceName, groupName, NamingRemoteConstants.REGISTER_INSTANCE, instance);//向服务端发起请求requestToServer(request, Response.class);redoService.instanceRegistered(serviceName, groupName);}private <T extends Response> T requestToServer(AbstractNamingRequest request, Class<T> responseClass) throws NacosException {try {request.putAllHeader(getSecurityHeaders(request.getNamespace(), request.getGroupName(), request.getServiceName()));//实际会调用RpcClient.request()方法发起gRPC请求Response response = requestTimeout < 0 ? rpcClient.request(request) : rpcClient.request(request, requestTimeout);if (ResponseCode.SUCCESS.getCode() != response.getResultCode()) {throw new NacosException(response.getErrorCode(), response.getMessage());}if (responseClass.isAssignableFrom(response.getClass())) {return (T) response;}NAMING_LOGGER.error("Server return unexpected response '{}', expected response should be '{}'", response.getClass().getName(), responseClass.getName());} catch (Exception e) {throw new NacosException(NacosException.SERVER_ERROR, "Request nacos server failed: ", e);}throw new NacosException(NacosException.SERVER_ERROR, "Server return invalid response");}...
}public class RpcClientFactory {private static final Map<String, RpcClient> CLIENT_MAP = new ConcurrentHashMap<>();...//create a rpc client.public static RpcClient createClient(String clientName, ConnectionType connectionType, Integer threadPoolCoreSize, Integer threadPoolMaxSize, Map<String, String> labels) {if (!ConnectionType.GRPC.equals(connectionType)) {throw new UnsupportedOperationException("unsupported connection type :" + connectionType.getType());}return CLIENT_MAP.computeIfAbsent(clientName, clientNameInner -> {LOGGER.info("[RpcClientFactory] create a new rpc client of " + clientName);try {//创建GrpcClient对象GrpcClient client = new GrpcSdkClient(clientNameInner);//设置线程核心数和最大数client.setThreadPoolCoreSize(threadPoolCoreSize);client.setThreadPoolMaxSize(threadPoolMaxSize);client.labels(labels);return client;} catch (Throwable throwable) {LOGGER.error("Error to init GrpcSdkClient for client name :" + clientName, throwable);throw throwable;}});}...
}

(2)gRPC客户端启动的源码

NamingGrpcClientProxy的start()方法会通过调用RpcClient的start()方法,来启动RPC客户端即RpcClient对象。

在RpcClient的start()方法中,首先会利用CAS来修改RPC客户端(RpcClient)的状态,也就是将RpcClient.rpcClientStatus属性从INITIALIZED更新为STARTING。

然后会创建一个核心线程数为2的线程池,并提交两个任务。任务一是处理连接成功或连接断开时的线程,任务二是处理重连或健康检查的线程。

接着会创建Connection连接对象,也就是在while循环中调用GrpcClient的connectToServer()方法,尝试与服务端建立连接。如果连接失败,则会抛出异常并且进行重试,由于是同步连接,所以最大重试次数是3。

最后当客户端与服务端成功建立连接后,会把对应的Connection连接对象赋值给RpcClient.currentConnection属性,并且修改RpcClient.rpcClientStatus属性即RPC客户端状态为RUNNING。

如果客户端与服务端连接失败,则会通过异步尝试进行连接,也就是调用RpcClient的switchServerAsync()方法,往RpcClient的reconnectionSignal队列中放入一个ReconnectContext对象,reconnectionSignal队列中的元素会交给任务2来处理。

public abstract class RpcClient implements Closeable {protected volatile AtomicReference<RpcClientStatus> rpcClientStatus = new AtomicReference<>(RpcClientStatus.WAIT_INIT);protected ScheduledExecutorService clientEventExecutor;protected BlockingQueue<ConnectionEvent> eventLinkedBlockingQueue = new LinkedBlockingQueue<>();//在NamingGrpcClientProxy初始化 -> 调用RpcClient.start()方法时,会将GrpcClient.connectToServer()方法的返回值赋值给currentConnection属性protected volatile Connection currentConnection;private final BlockingQueue<ReconnectContext> reconnectionSignal = new ArrayBlockingQueue<>(1);...public final void start() throws NacosException {//利用CAS来修改RPC客户端(RpcClient)的状态,从INITIALIZED更新为STARTINGboolean success = rpcClientStatus.compareAndSet(RpcClientStatus.INITIALIZED, RpcClientStatus.STARTING);if (!success) {return;}//接下来创建调度线程池执行器,并提交两个任务clientEventExecutor = new ScheduledThreadPoolExecutor(2, r -> {Thread t = new Thread(r);t.setName("com.alibaba.nacos.client.remote.worker");t.setDaemon(true);return t;});//任务1:处理连接成功或连接断开时的线程clientEventExecutor.submit(() -> {...     });//任务2:处理重连或健康检查的线程clientEventExecutor.submit(() -> {...});//创建连接对象Connection connectToServer = null;rpcClientStatus.set(RpcClientStatus.STARTING);//重试次数为3次int startUpRetryTimes = RETRY_TIMES;//在while循环中尝试与服务端建立连接,最多循环3次while (startUpRetryTimes > 0 && connectToServer == null) {try {startUpRetryTimes--;//获取服务端信息ServerInfo serverInfo = nextRpcServer();LoggerUtils.printIfInfoEnabled(LOGGER, "[{}] Try to connect to server on start up, server: {}", name, serverInfo);//调用GrpcClient.connectToServer()方法建立和服务端的长连接connectToServer = connectToServer(serverInfo);} catch (Throwable e) {LoggerUtils.printIfWarnEnabled(LOGGER, "[{}] Fail to connect to server on start up, error message = {}, start up retry times left: {}", name, e.getMessage(), startUpRetryTimes);}}//如果连接成功,connectToServer对象就不为空if (connectToServer != null) {LoggerUtils.printIfInfoEnabled(LOGGER, "[{}] Success to connect to server [{}] on start up, connectionId = {}", name, connectToServer.serverInfo.getAddress(), connectToServer.getConnectionId());//连接对象赋值,currentConnection其实就是一个在客户端使用的GrpcConnection对象实例this.currentConnection = connectToServer;//更改RPC客户端RpcClient的状态rpcClientStatus.set(RpcClientStatus.RUNNING);//往eventLinkedBlockingQueue队列放入ConnectionEvent事件eventLinkedBlockingQueue.offer(new ConnectionEvent(ConnectionEvent.CONNECTED));} else {//尝试进行异步连接switchServerAsync();}registerServerRequestHandler(new ConnectResetRequestHandler());    //register client detection request.registerServerRequestHandler(request -> {if (request instanceof ClientDetectionRequest) {return new ClientDetectionResponse();}return null;});}protected ServerInfo nextRpcServer() {String serverAddress = getServerListFactory().genNextServer();//获取服务端信息return resolveServerInfo(serverAddress);}private ServerInfo resolveServerInfo(String serverAddress) {Matcher matcher = EXCLUDE_PROTOCOL_PATTERN.matcher(serverAddress);if (matcher.find()) {serverAddress = matcher.group(1);}String[] ipPortTuple = serverAddress.split(Constants.COLON, 2);int defaultPort = Integer.parseInt(System.getProperty("nacos.server.port", "8848"));String serverPort = CollectionUtils.getOrDefault(ipPortTuple, 1, Integer.toString(defaultPort));return new ServerInfo(ipPortTuple[0], NumberUtils.toInt(serverPort, defaultPort));}public void switchServerAsync() {//异步注册逻辑switchServerAsync(null, false);}protected void switchServerAsync(final ServerInfo recommendServerInfo, boolean onRequestFail) {//往reconnectionSignal队列里放入一个对象reconnectionSignal.offer(new ReconnectContext(recommendServerInfo, onRequestFail));}...
}

(3)gRPC客户端发起与服务端建立连接请求的源码

gRPC客户端与服务端建立连接的方法是GrpcClient的connectToServer()方法。该方法首先会获取进行网络通信的端口号,因为gRPC服务需要额外占用一个端口的,所以这个端口号是在Nacos的8848基础上 + 偏移量1000,变成9848。

在建立连接之前,会先检查一下服务端,如果没问题才发起连接请求,接着就会调用GrpcConnection的sendRequest()方法发起连接请求,最后返回GrpcConnection连接对象。

public abstract class GrpcClient extends RpcClient {...@Overridepublic Connection connectToServer(ServerInfo serverInfo) {try {if (grpcExecutor == null) {this.grpcExecutor = createGrpcExecutor(serverInfo.getServerIp());}//获取端口号:gRPC服务需要额外占用一个端口的,这个端口是在Nacos 8848的基础上,+ 偏移量1000,所以是9848int port = serverInfo.getServerPort() + rpcPortOffset();RequestGrpc.RequestFutureStub newChannelStubTemp = createNewChannelStub(serverInfo.getServerIp(), port);if (newChannelStubTemp != null) {//检查一下服务端,没问题才会发起RPC连接请求Response response = serverCheck(serverInfo.getServerIp(), port, newChannelStubTemp);if (response == null || !(response instanceof ServerCheckResponse)) {shuntDownChannel((ManagedChannel) newChannelStubTemp.getChannel());return null;}BiRequestStreamGrpc.BiRequestStreamStub biRequestStreamStub = BiRequestStreamGrpc.newStub(newChannelStubTemp.getChannel());//创建连接对象GrpcConnection grpcConn = new GrpcConnection(serverInfo, grpcExecutor);grpcConn.setConnectionId(((ServerCheckResponse) response).getConnectionId());//create stream request and bind connection event to this connection.//创建流请求并将连接事件绑定到此连接StreamObserver<Payload> payloadStreamObserver = bindRequestStream(biRequestStreamStub, grpcConn);//stream observer to send response to servergrpcConn.setPayloadStreamObserver(payloadStreamObserver);grpcConn.setGrpcFutureServiceStub(newChannelStubTemp);grpcConn.setChannel((ManagedChannel) newChannelStubTemp.getChannel());//send a  setup request.ConnectionSetupRequest conSetupRequest = new ConnectionSetupRequest();conSetupRequest.setClientVersion(VersionUtils.getFullClientVersion());conSetupRequest.setLabels(super.getLabels());conSetupRequest.setAbilities(super.clientAbilities);conSetupRequest.setTenant(super.getTenant());//发起连接请求grpcConn.sendRequest(conSetupRequest);//wait to register connection setupThread.sleep(100L);return grpcConn;}return null;} catch (Exception e) {LOGGER.error("[{}]Fail to connect to server!,error={}", GrpcClient.this.getName(), e);}return null;}private Response serverCheck(String ip, int port, RequestGrpc.RequestFutureStub requestBlockingStub) {try {if (requestBlockingStub == null) {return null;}ServerCheckRequest serverCheckRequest = new ServerCheckRequest();Payload grpcRequest = GrpcUtils.convert(serverCheckRequest);//向服务端发送一个检查请求ListenableFuture<Payload> responseFuture = requestBlockingStub.request(grpcRequest);Payload response = responseFuture.get(3000L, TimeUnit.MILLISECONDS);//receive connection unregister response here,not check response is success.return (Response) GrpcUtils.parse(response);} catch (Exception e) {LoggerUtils.printIfErrorEnabled(LOGGER, "Server check fail, please check server {} ,port {} is available , error ={}", ip, port, e);return null;}}private StreamObserver<Payload> bindRequestStream(final BiRequestStreamGrpc.BiRequestStreamStub streamStub, final GrpcConnection grpcConn) {//调用BiRequestStreamStub.requestBiStream()方法连接服务端return streamStub.requestBiStream(new StreamObserver<Payload>() {@Overridepublic void onNext(Payload payload) {LoggerUtils.printIfDebugEnabled(LOGGER, "[{}]Stream server request receive, original info: {}", grpcConn.getConnectionId(), payload.toString());try {Object parseBody = GrpcUtils.parse(payload);final Request request = (Request) parseBody;if (request != null) {try {Response response = handleServerRequest(request);if (response != null) {response.setRequestId(request.getRequestId());sendResponse(response);} else {LOGGER.warn("[{}]Fail to process server request, ackId->{}", grpcConn.getConnectionId(), request.getRequestId());}} catch (Exception e) {LoggerUtils.printIfErrorEnabled(LOGGER, "[{}]Handle server request exception: {}", grpcConn.getConnectionId(), payload.toString(), e.getMessage());Response errResponse = ErrorResponse.build(NacosException.CLIENT_ERROR, "Handle server request error");errResponse.setRequestId(request.getRequestId());sendResponse(errResponse);}}} catch (Exception e) {LoggerUtils.printIfErrorEnabled(LOGGER, "[{}]Error to process server push response: {}", grpcConn.getConnectionId(), payload.getBody().getValue().toStringUtf8());}}@Overridepublic void onError(Throwable throwable) {boolean isRunning = isRunning();boolean isAbandon = grpcConn.isAbandon();if (isRunning && !isAbandon) {LoggerUtils.printIfErrorEnabled(LOGGER, "[{}]Request stream error, switch server,error={}", grpcConn.getConnectionId(), throwable);if (rpcClientStatus.compareAndSet(RpcClientStatus.RUNNING, RpcClientStatus.UNHEALTHY)) {switchServerAsync();}} else {LoggerUtils.printIfWarnEnabled(LOGGER, "[{}]Ignore error event,isRunning:{},isAbandon={}", grpcConn.getConnectionId(), isRunning, isAbandon);}}@Overridepublic void onCompleted() {boolean isRunning = isRunning();boolean isAbandon = grpcConn.isAbandon();if (isRunning && !isAbandon) {LoggerUtils.printIfErrorEnabled(LOGGER, "[{}]Request stream onCompleted, switch server", grpcConn.getConnectionId());if (rpcClientStatus.compareAndSet(RpcClientStatus.RUNNING, RpcClientStatus.UNHEALTHY)) {switchServerAsync();}} else {LoggerUtils.printIfInfoEnabled(LOGGER, "[{}]Ignore complete event,isRunning:{},isAbandon={}", grpcConn.getConnectionId(), isRunning, isAbandon);}}});}...
}

(4)总结

11.gRPC客户端的心跳机制(健康检查)

(1)线程任务一:处理连接成功或连接断开时的通知

(2)线程任务二:处理重连或健康检查

RpcClient的start()方法会调用GrpcClient的connectToServer()方法连接服务端,不管连接是否成功,最后都会往不同的阻塞队列中添加事件。

如果连接成功,那么就往RpcClient的eventLinkedBlockingQueue添加连接事件。如果连接失败,那么就往RpcClient的reconnectionSignal队列添加重连对象。而这两个阻塞队列中的数据处理,便是由执行RpcClient的start()方法时启动的两个线程任务进行处理的。

(1)线程任务一:处理连接成功或连接断开时的通知

这个任务主要在连接成功或者连接断开时,修改一些属性状态。通过eventLinkedBlockingQueue的take()方法从队列取到连接事件后,会判断连接事件是否建立连接还是断开连接。

如果是建立连接,那么就调用RpcClient的notifyConnected()方法,把执行NamingGrpcClientProxy的start()方法时所注册的NamingGrpcRedoService对象的connected属性设置为true。

如果是断开连接,那么就调用RpcClient的notifyDisConnected()方法,把执行NamingGrpcClientProxy的start()方法时所注册的NamingGrpcRedoService对象的connected属性设置为false。

public abstract class RpcClient implements Closeable {protected volatile AtomicReference<RpcClientStatus> rpcClientStatus = new AtomicReference<>(RpcClientStatus.WAIT_INIT);protected ScheduledExecutorService clientEventExecutor;protected BlockingQueue<ConnectionEvent> eventLinkedBlockingQueue = new LinkedBlockingQueue<>();private final BlockingQueue<ReconnectContext> reconnectionSignal = new ArrayBlockingQueue<>(1);//listener called where connection's status changed. 连接状态改变的监听器protected List<ConnectionEventListener> connectionEventListeners = new ArrayList<>();...public final void start() throws NacosException {//利用CAS来修改RPC客户端(RpcClient)的状态,从INITIALIZED更新为STARTINGboolean success = rpcClientStatus.compareAndSet(RpcClientStatus.INITIALIZED, RpcClientStatus.STARTING);if (!success) {return;}//接下来创建调度线程池执行器,并提交两个任务clientEventExecutor = new ScheduledThreadPoolExecutor(2, r -> {Thread t = new Thread(r);t.setName("com.alibaba.nacos.client.remote.worker");t.setDaemon(true);return t;});//任务1:处理连接成功或连接断开时的线程clientEventExecutor.submit(() -> {while (!clientEventExecutor.isTerminated() && !clientEventExecutor.isShutdown()) {ConnectionEvent take;try {take = eventLinkedBlockingQueue.take();if (take.isConnected()) {notifyConnected();} else if (take.isDisConnected()) {notifyDisConnected();}} catch (Throwable e) {// Do nothing}}   });//任务2:向服务端上报心跳或重连的线程clientEventExecutor.submit(() -> {...});}...//Notify when client new connected.protected void notifyConnected() {if (connectionEventListeners.isEmpty()) {return;}LoggerUtils.printIfInfoEnabled(LOGGER, "[{}] Notify connected event to listeners.", name);for (ConnectionEventListener connectionEventListener : connectionEventListeners) {try {connectionEventListener.onConnected();} catch (Throwable throwable) {LoggerUtils.printIfErrorEnabled(LOGGER, "[{}] Notify connect listener error, listener = {}", name, connectionEventListener.getClass().getName());}}}//Notify when client disconnected.protected void notifyDisConnected() {if (connectionEventListeners.isEmpty()) {return;}LoggerUtils.printIfInfoEnabled(LOGGER, "[{}] Notify disconnected event to listeners", name);for (ConnectionEventListener connectionEventListener : connectionEventListeners) {try {connectionEventListener.onDisConnect();} catch (Throwable throwable) {LoggerUtils.printIfErrorEnabled(LOGGER, "[{}] Notify disconnect listener error, listener = {}", name, connectionEventListener.getClass().getName());}}}...//Register connection handler. Will be notified when inner connection's state changed.//在执行NamingGrpcClientProxy.start()方法时会将NamingGrpcRedoService对象注册到connectionEventListeners中public synchronized void registerConnectionListener(ConnectionEventListener connectionEventListener) {LoggerUtils.printIfInfoEnabled(LOGGER, "[{}] Registry connection listener to current client:{}", name, connectionEventListener.getClass().getName());this.connectionEventListeners.add(connectionEventListener);}...
}public class NamingGrpcRedoService implements ConnectionEventListener {private volatile boolean connected = false;...@Overridepublic void onConnected() {connected = true;LogUtils.NAMING_LOGGER.info("Grpc connection connect");}@Overridepublic void onDisConnect() {connected = false;LogUtils.NAMING_LOGGER.warn("Grpc connection disconnect, mark to redo");synchronized (registeredInstances) {registeredInstances.values().forEach(instanceRedoData -> instanceRedoData.setRegistered(false));}synchronized (subscribes) {subscribes.values().forEach(subscriberRedoData -> subscriberRedoData.setRegistered(false));}LogUtils.NAMING_LOGGER.warn("mark to redo completed");}...
}

(2)线程任务二:处理重连或健康检查

如果RpcClient的start()方法在调用GrpcClient的connectToServer()方法连接服务端时失败了,那么会往RpcClient.reconnectionSignal队列添加重连对象的,而这个任务就会获取reconnectionSignal队列中的重连对象进行重连。

因为reconnectionSignal中的数据是当连接失败时放入的,所以如果从reconnectionSignal中获取不到重连对象,等同于连接成功。

注意:这个任务从reconnectionSignal阻塞队列中获取重连对象时,调用的是阻塞队列的take()方法,而不是阻塞队列的poll()方法。BlockingQueue的take()方法,如果读取不到数据,会一直处于阻塞状态。BlockingQueue的poll()方法,在指定的时间内读取不到数据,会返回null。

情况一:如果从reconnectionSignal队列中获取到的重连对象为null

首先判断存活时间是否大于 5s,如果大于则调用RpcClient.healthCheck()方法发起健康检查的RPC请求。健康检查的触发方法是currentConnection.request()方法,健康检查的请求类型是HealthCheckRequest。

如果健康检查成功,只需刷新存活时间即可。如果健康检查失败,则需要尝试与服务端重新建立连接。

情况二:如果从reconnectionSignal队列中获取到的重连对象不为null

那么就调用RpcClient的reconnect()方法进行重新连接,该方法会通过GrpcClient的connectToServer()方法尝试与服务端建立连接。

public abstract class RpcClient implements Closeable {protected volatile AtomicReference<RpcClientStatus> rpcClientStatus = new AtomicReference<>(RpcClientStatus.WAIT_INIT);protected ScheduledExecutorService clientEventExecutor;protected BlockingQueue<ConnectionEvent> eventLinkedBlockingQueue = new LinkedBlockingQueue<>();private final BlockingQueue<ReconnectContext> reconnectionSignal = new ArrayBlockingQueue<>(1);...public final void start() throws NacosException {//利用CAS来修改RPC客户端(RpcClient)的状态,从INITIALIZED更新为STARTINGboolean success = rpcClientStatus.compareAndSet(RpcClientStatus.INITIALIZED, RpcClientStatus.STARTING);if (!success) {return;}//接下来创建调度线程池执行器,并提交两个任务clientEventExecutor = new ScheduledThreadPoolExecutor(2, r -> {Thread t = new Thread(r);t.setName("com.alibaba.nacos.client.remote.worker");t.setDaemon(true);return t;});//任务1:处理连接成功或连接断开时的线程clientEventExecutor.submit(() -> {...     });//任务2:向服务端上报心跳或重连的线程clientEventExecutor.submit(() -> {while (true) {try {if (isShutdown()) {break;}//这里从reconnectionSignal阻塞队列中获取任务不是调用take()方法,而是调用poll()方法,并且指定了5s的最大读取时间//BlockingQueue的take()方法,如果读取不到数据,会一直处于阻塞状态//BlockingQueue的poll()方法,在指定的时间内读取不到数据,会返回nullReconnectContext reconnectContext = reconnectionSignal.poll(keepAliveTime, TimeUnit.MILLISECONDS);//reconnectContext为null,说明从reconnectionSignal中获取不到数据//由于reconnectionSignal中的数据是当连接失败时放入的//所以从reconnectionSignal中获取不到数据,等同于连接成功if (reconnectContext == null) {//check alive time.//检查存活时间,默认存活时间为5s,超过5s就需要做健康检查if (System.currentTimeMillis() - lastActiveTimeStamp >= keepAliveTime) {//调用RpcClient.healthCheck()方法,发起健康检查请求boolean isHealthy = healthCheck();//如果向服务端发起健康检查请求失败,则需要尝试重新建立连接if (!isHealthy) {if (currentConnection == null) {continue;}LoggerUtils.printIfInfoEnabled(LOGGER, "[{}] Server healthy check fail, currentConnection = {}", name, currentConnection.getConnectionId());//判断连接状态是否关闭,如果是则结束异步任务RpcClientStatus rpcClientStatus = RpcClient.this.rpcClientStatus.get();if (RpcClientStatus.SHUTDOWN.equals(rpcClientStatus)) {break;}//修改RpcClient的连接状态为不健康boolean statusFLowSuccess = RpcClient.this.rpcClientStatus.compareAndSet(rpcClientStatus, RpcClientStatus.UNHEALTHY);//给reconnectContext属性赋值,准备尝试重连if (statusFLowSuccess) {//重新赋值,注意这里没有continue,所以逻辑会接着往下执行reconnectContext = new ReconnectContext(null, false);} else {continue;}} else {//如果向服务端发起健康检查请求成功,则刷新RpcClient的存活时间lastActiveTimeStamp = System.currentTimeMillis();continue;}} else {continue;}}if (reconnectContext.serverInfo != null) {//clear recommend server if server is not in server list.//如果服务器不在服务器列表中,则清除推荐服务器,即设置reconnectContext.serverInfo为nullboolean serverExist = false;//遍历服务端列表for (String server : getServerListFactory().getServerList()) {ServerInfo serverInfo = resolveServerInfo(server);if (serverInfo.getServerIp().equals(reconnectContext.serverInfo.getServerIp())) {serverExist = true;reconnectContext.serverInfo.serverPort = serverInfo.serverPort;break;}}//reconnectContext.serverInfo不存在服务端列表中,就清除服务器信息,设置reconnectContext.serverInfo为nullif (!serverExist) {LoggerUtils.printIfInfoEnabled(LOGGER, "[{}] Recommend server is not in server list, ignore recommend server {}", name, reconnectContext.serverInfo.getAddress());reconnectContext.serverInfo = null;}}//进行重新连接,RpcClient.reconnect()方法中会调用GrpcClient.connectToServer()方法尝试与服务端建立连接reconnect(reconnectContext.serverInfo, reconnectContext.onRequestFail);} catch (Throwable throwable) {//Do nothing}}});}private boolean healthCheck() {HealthCheckRequest healthCheckRequest = new HealthCheckRequest();if (this.currentConnection == null) {return false;}try {//利用currentConnection连接对象,发起RPC请求,请求类型是HealthCheckRequestResponse response = this.currentConnection.request(healthCheckRequest, 3000L);//not only check server is ok, also check connection is register.return response != null && response.isSuccess();} catch (NacosException e) {//ignore}return false;}...
}

(3)总结

相关文章:

Nacos源码—9.Nacos升级gRPC分析七

大纲 10.gRPC客户端初始化分析 11.gRPC客户端的心跳机制(健康检查) 12.gRPC服务端如何处理客户端的建立连接请求 13.gRPC服务端如何映射各种请求与对应的Handler处理类 14.gRPC简单介绍 10.gRPC客户端初始化分析 (1)gRPC客户端代理初始化的源码 (2)gRPC客户端启动的源码…...

与智能体高效协作:Kimi交互逻辑探索与提示词设计实践【附kimi提示词合集下载】

引言&#xff1a;智能时代的人机协作新范式 在持续使用多款AI助手完成技术文档分析、数据分析等任务后&#xff0c;我逐渐意识到工具效能的核心不仅在于技术参数&#xff0c;更在于使用者对交互逻辑的理解深度。本文将基于实际项目经验&#xff0c;探讨智能体交互的本质规律&a…...

Web 架构之负载均衡会话保持

文章目录 一、引言二、思维导图三、负载均衡会话保持的概念3.1 定义3.2 作用 四、负载均衡会话保持的实现方式4.1 基于 IP 地址原理代码示例&#xff08;以 Nginx 为例&#xff09;注释 4.2 基于 Cookie原理代码示例&#xff08;以 HAProxy 为例&#xff09;注释 4.3 基于 SSL …...

遨游卫星电话与普通手机有什么区别?

在数字化浪潮席卷全球的今天&#xff0c;通信设备的角色早已超越传统语音工具&#xff0c;成为连接物理世界与数字世界的核心枢纽。然而&#xff0c;当普通手机在都市丛林中游刃有余时&#xff0c;面对偏远地区、危险作业场景的应急通信需求&#xff0c;其局限性便显露无遗。遨…...

【Redis】谈谈Redis的设计

Redis&#xff08;Remote Dictionary Service&#xff09;是一个高性能的内存键值数据库&#xff0c;其设计核心是速度、简单性和灵活性。以下从架构、数据结构、持久化、网络模型等方面解析 Redis 的设计实现原理&#xff1a; 1. 核心设计思想 内存优先&#xff1a;数据主要存…...

聊天项目总结

目前项目 完成了个人信息修改&#xff0c;添加好友&#xff0c;创建群聊&#xff0c;添加群聊&#xff0c;在线状态&#xff0c;删除好友&#xff0c;退出群&#xff0c;解散群&#xff0c;好友申请&#xff0c;群资料修改&#xff0c;群管理&#xff0c;群主转让&#xff0c;…...

智能手表整机装配作业指导书(SOP)

&#x1f4c4; 智能手表整机装配作业指导书&#xff08;SOP&#xff09; 产品名称&#xff1a;Aurora Watch S1 产品型号&#xff1a;AWS1-BG22 版本号&#xff1a;SOP-AWS1-V1.0 编制日期&#xff1a;2025年5月6日 编制单位&#xff1a;制造工程部&#xff08;ME&#xff09;…...

c语言第一个小游戏:贪吃蛇小游戏05

贪吃蛇脱缰自动向右走&#xff1a;脱缰的野蛇 #include <curses.h> #include <stdlib.h> struct snake{ int hang; int lie; struct snake *next; }; struct snake *head; struct snake *tail; void initNcurse() { initscr(); keypad(stdscr,1); } int …...

ES6中的解构

在 JavaScript&#xff08;包括 TypeScript&#xff09;中&#xff0c;数组解构和对象解构是 ES6 引入的两个非常实用的语法特性&#xff0c;它们可以帮助我们更方便地从数组或对象中提取数据。 一、数组解构&#xff08;Array Destructuring&#xff09; &#x1f4cc; 基本用…...

Pycharm的终端执行allure命令出现command not found

Pycharm的接口自动化项目用的是venv虚拟环境&#xff0c;已下载和配置好Allure路径&#xff0c;查看Allure版本正常。 问题&#xff1a;在重新打开Pycham的项目时&#xff0c;在Pycharm终端中执行allure相关命令就会报错zsh: allure: command not found 原因&#xff1a;在PyC…...

[ctfshow web入门] web72

信息收集 下载index.php并查看&#xff0c;和上题差不多 error_reporting(0); ini_set(display_errors, 0); // 你们在炫技吗&#xff1f; if(isset($_POST[c])){$c $_POST[c];eval($c);$s ob_get_contents();ob_end_clean();echo preg_replace("/[0-9]|[a-z]/i",…...

【Folium】使用离线地图

文章目录 相关文献离线地图下载Folium 使用离线地图 相关文献 Folium — Folium 0.19.5 documentationOffline Map Maker 离线地图下载 我们使用 Offline Map Maker 进行地图下载。 特别注意&#xff1a;Folium 默认支持 WGS84 坐标系&#xff0c;建议下载 WGS84 坐标系的地…...

嵌入式自学第二十天(5.13)

&#xff08;1&#xff09;线性表顺序存储的优缺点&#xff1a; 优点&#xff1a;无需为表中逻辑关系添加额外存储空间&#xff1b; 可以快速随机访问元素&#xff0c;时间复杂度O(1)。 缺点&#xff1a;插入删除需要移动元素O(n&#xff09;&#xff1b; 无法动态存储。 …...

ThingsBoard3.9.1 MQTT Topic(4)

本章中的主题适用于网关设备。 1.网关订阅设备属性的topic&#xff1a;v1/gateway/attributes/response 订阅后接收到的响应格式。 { "id":3, "device":"m1", "values":{ "version":"V1.2"…...

centos中JDK_PATH 如何设置

在 CentOS 7.9 中设置 JDK_PATH&#xff08;即 JAVA_HOME&#xff09;的步骤如下。JAVA_HOME 是一个环境变量&#xff0c;用于指向 Java 开发工具包&#xff08;JDK&#xff09;的安装路径。 1. 查找 JDK 安装路径 首先&#xff0c;你需要找到 JDK 的安装路径。可以通过以下命…...

一次讲清 FP32 / FP16 / BF16 / INT8 / INT4

一次讲清 FP32 / FP16 / BF16 / INT8 / INT4 目标&#xff1a;让你3 分钟读懂格式原理&#xff0c;5 分钟学会选型。 只记一句&#xff1a;“指数定范围&#xff0c;尾数定精度&#xff1b;位宽定显存&#xff0c;硬件定成本”。 1 | 为什么要有这么多格式&#xff1f; …...

PH热榜 | 2025-05-13

1. FirstQuadrant 标语&#xff1a;通过以人为本的人工智能来最大化B2B销售 介绍&#xff1a;销售人工智能&#xff0c;帮助创始人和收益团队提高效率&#xff0c;保持组织有序&#xff0c;并促成更多交易。它通过简化销售幕后工作&#xff0c;确保每个细节都不会遗漏。 产品…...

java基础-泛型

文章目录 目录 文章目录 前言 一、泛型的作用 1.类型安全 2.通用性 这里再举个例子 二、泛型的实现 1.泛型类 2.泛型接口 3.泛型方法 4.T符号的起源&#xff08;额外&#xff09; 三、泛型擦除 四、泛型通配符 1.上界通配符&#xff08; &#xff09; 为什么用于…...

对抗帕金森:在疾病阴影下,如何重掌生活主动权?

帕金森病&#xff0c;一种影响全球超 1000 万人的神经退行性疾病&#xff0c;正无声地改变着患者的生活轨迹。随着大脑中多巴胺分泌减少&#xff0c;患者逐渐出现肢体震颤、肌肉僵硬、步态迟缓等症状&#xff0c;甚至连扣纽扣、端水杯这类日常动作都变得艰难。更棘手的是&#…...

网络协议与系统架构分析实战:工具与方法全解

网络协议与系统架构分析实战&#xff1a;工具与方法全解 在互联网系统的开发、运维与安全分析中&#xff0c;协议解析与抓包分析是不可或缺的核心技能。本文将系统梳理主流协议解析工具、协议自动识别方案&#xff0c;并结合实际抓包案例&#xff0c;讲解如何还原和推测底层系…...

使用PocketFlow构建Web Search Agent

前言 本文介绍的是PocketFlow的cookbook中的pocketflow-agent部分。 回顾一下PocketFlow的核心架构&#xff1a; 每一个节点的架构&#xff1a; 具体介绍可以看上一篇文章&#xff1a; “Pocket Flow&#xff0c;一个仅用 100 行代码实现的 LLM 框架” 实现效果 这个Web S…...

基于STM32、HAL库的TLV320AIC3204IRHBR音频接口芯片驱动程序设计

一、简介: ADAU1701JSTZ-RL 是一款高性能音频编解码器 (Codec),专为便携式和低功耗应用设计。它集成了 ADC、DAC、麦克风前置放大器、耳机放大器和数字信号处理功能,支持 I2S/PCM 音频接口和 I2C 控制接口,非常适合与 STM32 微控制器配合使用。 二、硬件接口: 典型的 ST…...

轻量级高性能推理引擎MNN 学习笔记 02.MNN主要API

1. MNN 主要API 注意&#xff1a;本学习笔记只介绍了我在学习过程中常用的API &#xff0c;更多MNN API 请参考官方文档。 1.1. 推理时操作流程 创建Interpreter &#xff1a; createFromFile()通过Interpreter创建Session &#xff1a;createSession()设置输入数据: getSes…...

STM32 ADC 模数转换器详解:原理、配置与应用

STM32 ADC 模数转换器详解&#xff1a;原理、配置与应用 在嵌入式系统中&#xff0c;模数转换&#xff08;ADC&#xff09;是实现传感器信号采集、信号处理等任务的关键环节。STM32 微控制器作为一款功能强大的 32 位微控制器&#xff0c;其内置的 ADC 模块为开发者提供了高效…...

18.Excel数据透视表:第1部分创建数据透视表

一 什么是数据透视表 通过万花筒可以用不同的方式査看里面画面图像&#xff0c;在excel中可以将数据透视表看作是对准数据的万花筒&#xff0c;用不同角度去观察数据&#xff0c;也可以旋转数据&#xff0c;对数据进行重新排列&#xff0c;对大量的数据可以快速的汇总和建立交叉…...

AI 模型训练轻量化技术在军事领域的实战应用与技术解析

AI 模型训练轻量化技术在军事领域的实战应用与技术解析 一、引言 在人工智能与军事领域深度融合的当下&#xff0c;AI 模型训练轻量化技术正成为破解战场资源限制的关键钥匙。通过模型压缩、量化、剪枝等核心技术&#xff0c;轻量化模型在算力受限、通信不稳定的复杂战场环境中…...

科学养生,开启健康生活

在快节奏的现代生活中&#xff0c;健康养生成为人们关注的焦点。科学合理的养生方式&#xff0c;无需依赖传统医学理论&#xff0c;也能有效提升生活质量&#xff0c;为身体注入活力。​ 均衡饮食是养生的基础。每天应保证摄入足够的蛋白质、碳水化合物和脂肪&#xff0c;同时…...

高效跨平台文件传输与管理的工具

软件介绍 这款名为 Coolmuster Mobile Transfer 的工具是一款多平台支持的文件传输工具&#xff0c;能高效地在不同设备间进行文件传输与管理。 适用场景 它适用于多种场景&#xff0c;无论是个人文件整理、家庭成员间资料共享&#xff0c;还是企业场景下的工作文件处理&…...

如何优化 Linux 服务器的磁盘 I/O 性能

# 优化 Linux 服务器磁盘 I/O 性能的全面指南 ## 1. 识别 I/O 瓶颈 首先确定是否存在 I/O 瓶颈以及瓶颈位置&#xff1a; bash # 使用 iostat 查看磁盘 I/O 统计 iostat -x 1 # 使用 iotop 查看进程级 I/O 使用情况 iotop # 使用 vmstat 查看系统整体 I/O 情况 vmstat 1 …...

Python基础学习-Day23

目录 基础概念转换器&#xff08;transformer&#xff09;估计器&#xff08;estimator&#xff09;管道&#xff08;pipeline&#xff09; 实例pipeline 基础概念 pipeline在机器学习领域可以翻译为“管道”&#xff0c;也可以翻译为“流水线”&#xff0c;是机器学习中一个重…...

【Ubuntu】扩充磁盘大小

sudo apt-get install gparted 安装完成后&#xff0c;搜索gparted软件&#xff0c;打开gparted 参考...

数据治理域——日志数据采集设计

摘要 本文主要介绍了Web页面端日志采集的设计。首先阐述了页面浏览日志采集&#xff0c;包括客户端日志采集的实现方式、采集内容及技术亮点。接着介绍了无线客户端端日志采集&#xff0c;包括UserTrack的核心设计、移动端与浏览器端采集差异以及典型应用场景崩溃分析。最后探…...

Dinky 安装部署并配置提交 Flink Yarn 任务

官方文档 https://www.dinky.org.cn/docs/1.1/deploy_guide/normal_deploy 版本 dinky 1.1.0、1.2.3 当前最新发布版本为 1.2.3 &#xff0c;但是官方文档最新稳定版为 1.1 &#xff0c;所以先选择 1.1.0&#xff0c;验证通过后&#xff0c;再尝试 1.2.3 &#xff0c;发现 1…...

杰理-701-手表sdk无法电脑连接经典蓝牙

杰理-701-手表sdk无法电脑连接经典蓝牙 只有手机可以连接经典蓝牙播放音乐&#xff0c;电脑无法连接&#xff0c;需要关闭emitter功能 交流q群&#xff1a;187115320...

Timsort 算法

文章目录 1 基础理解1.1 定义和原理1.2 工作原理 2 算法实现2.1 Python 代码实现2.1.1 代码2.1.2 核心逻辑计算最小运行长度&#xff08;calc_min_run(n)&#xff09;插入排序&#xff08;insertion_sort(arr, left, right)&#xff09; 2.2 Java 代码实现2.3 C 代码实现 3 逻辑…...

Go构建高并发权重抽奖系统:从设计到优化全流程指南

引言&#xff1a;为何需要专业抽奖系统&#xff1f; 在现代互联网应用中&#xff0c;抽奖系统被广泛用于营销活动、用户激励等场景。一个好的抽奖系统需要满足&#xff1a; 公平性&#xff1a;确保概率分布准确高性能&#xff1a;支持高并发抽奖请求安全性&#xff1a;防止作…...

深度学习计算

深度学习的飞速发展离不开强大的计算能力支撑。从张量计算到 GPU 加速&#xff0c;从自动微分到分布式计算&#xff0c;深度学习计算的每一项技术都如同精密仪器中的关键齿轮&#xff0c;推动着模型性能的不断提升。本文深入剖析深度学习计算的核心技术、优化策略以及前沿趋势&…...

【Bluedroid】蓝牙 HID DEVICE 初始化流程源码解析

本文深入剖析Android蓝牙协议栈中HID设备&#xff08;BT-HD&#xff09;服务的初始化与启用流程&#xff0c;从接口初始化、服务掩码管理、服务请求路由到属性回调通知&#xff0c;完整展现蓝牙HID服务激活的技术路径。通过代码逻辑梳理&#xff0c;揭示服务启用的核心机制&…...

Kotlin 中的 Unit 类型的作用以及 Java 中 Void 的区别

在 Kotlin 中&#xff0c;Unit 类型和 Java 中的 void 关键字都用于表示“没有返回值”的函数&#xff0c;但它们在设计理念、类型系统和实际使用中有显著的区别。 1 Kotlin 中的 Unit 类型 表示无返回值&#xff1a; 当函数不返回有意义的值时&#xff0c;Kotlin 使用 Unit …...

Gemini 2.5 推动视频理解进入新时代

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…...

Spark Streaming 内部运行机制详解

核心思想&#xff1a;将实时数据流切割为“微批次”&#xff0c;利用 Spark Core 的批处理能力进行准实时计算。 1. 核心流程拆解 数据接收&#xff08;Input Data Stream&#xff09; 输入源&#xff1a;Kafka、Flume、Socket 等实时数据流。 接收器&#xff08;Receiver&…...

Feign+Resilience4j实现微服务熔断机制:原理与实战

引言&#xff1a;为什么需要熔断器&#xff1f; 在微服务架构中&#xff0c;服务间的依赖调用变得非常普遍。想象一下这样的场景&#xff1a;订单服务依赖支付服务&#xff0c;支付服务又依赖银行网关服务。如果银行网关服务出现故障&#xff0c;故障会向上蔓延&#xff0c;导…...

什么是SparkONYarn模式

1. 什么是 Spark on YARN&#xff1f; Spark on YARN 是 Apache Spark 的一种部署模式&#xff0c;允许 Spark 应用程序在 Hadoop YARN 集群上运行&#xff0c;充分利用 YARN 的资源管理和调度能力。这种模式将 Spark 与 Hadoop 生态深度集成&#xff0c;使企业能够在同一集群…...

鸿蒙北向应用开发: deveco5.0 创建开源鸿蒙项目

本地已经安装deveco5.0 使用5.0创建开源鸿蒙项目 文件->新建->新建项目 直接创建空项目,一路默认 next 直接编译项目 直接连接开源鸿蒙5.0开发板编译会提示 compatibleSdkVersion and releaseType of the app do not match the apiVersion and releaseType on the dev…...

操作系统:内存管理

目录 1、主要目标 2、核心概念和技术 2.1 物理内存与虚拟内存 2.2 内存分页机制 2.3 页面置换算法 3、监控与性能优化 3.1 查看物理内存 3.2 查看虚拟内存 3.3 性能问题 1> 内存不足&#xff08;OOM&#xff09; 2> 内存泄漏 3> 内存碎片 3.4 性能优化策…...

腾讯优化DeepSeek的DeepEP通信框架:开启AI大模型训练新时代

事件背景 在人工智能&#xff08;AI&#xff09;技术迅猛发展的当下&#xff0c;大规模AI模型训练的需求与日俱增。高效的数据通信成为了提升AI模型训练效率的关键环节。混合专家模型&#xff08;MoE&#xff09;作为一种高效的大模型架构&#xff0c;通过动态分配专家网络处理…...

CSP-J普及组第一轮真题单选题专项训练(二)

CSP-J普及组第一轮真题单选题专项训练(二) (共15题,每2分,共30分;每题有且有一个正确选项) 1、一个 32 位整型变量占用()个字节。 A. 32 B. 128 C. 4 D. 8 2、在内存储器中每个存储单元都被赋予一个唯一的序号,称为 A、下标 B、序号 C、地址 D、编号 3、编译器的主要…...

Android加固工具测评:易盾、顶象、360加固哪款更好用?

应用安全已经成为每个开发者和企业关注的核心问题。随着黑客技术的不断升级&#xff0c;单一的安全措施已经无法有效应对各种复杂的攻击威胁。Android加固工具应运而生&#xff0c;成为了提升应用安全的关键利器。这些加固工具通过代码混淆、加密、防篡改等技术手段&#xff0c…...

C++ 字符格式化输出

文章目录 一、简介二、实现代码三、实现效果 一、简介 这里使用std标准库简单实现一个字符格式化输出&#xff0c;方便后续的使用&#xff0c;它有点类似Qt中的QString操作。 二、实现代码 FMTString.hpp #pragma once#include <cmath> #include <cstdio> #include…...

内存中的“BANK”

一、BANK的定义与物理结构 基本概念 BANK&#xff08;存储体&#xff09; 是内存芯片内部的一个逻辑或物理分区&#xff0c;每个BANK由存储单元阵列、地址解码电路和缓冲器组成&#xff0c;用于分块管理内存操作。 作用&#xff1a;通过并行操作减少访问冲突&#xff0c;提升内…...