/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.sofa.registry.jraft.bootstrap; import com.alipay.remoting.ConnectionEventType; import com.alipay.remoting.rpc.RpcClient; import com.alipay.sofa.jraft.RouteTable; import com.alipay.sofa.jraft.Status; import com.alipay.sofa.jraft.conf.Configuration; import com.alipay.sofa.jraft.entity.PeerId; import com.alipay.sofa.jraft.option.CliOptions; import com.alipay.sofa.jraft.rpc.CliClientService; import com.alipay.sofa.jraft.rpc.impl.AbstractBoltClientService; import com.alipay.sofa.jraft.rpc.impl.cli.BoltCliClientService; import com.alipay.sofa.registry.jraft.command.ProcessRequest; import com.alipay.sofa.registry.jraft.command.ProcessResponse; import com.alipay.sofa.registry.jraft.handler.NotifyLeaderChangeHandler; import com.alipay.sofa.registry.jraft.handler.RaftClientConnectionHandler; import com.alipay.sofa.registry.log.Logger; import com.alipay.sofa.registry.log.LoggerFactory; import com.alipay.sofa.registry.remoting.bolt.ConnectionEventAdapter; import com.alipay.sofa.registry.remoting.bolt.SyncUserProcessorAdapter; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; /** * * @author shangyu.wh * @version $Id: RaftClient.java, v 0.1 2018-05-16 11:40 shangyu.wh Exp $ */ public class RaftClient { private static final Logger LOGGER = LoggerFactory.getLogger(RaftClient.class); private BoltCliClientService cliClientService; private RpcClient rpcClient; private CliOptions cliOptions; private String groupId; private Configuration conf; private AtomicBoolean started = new AtomicBoolean(false); /** * @param groupId * @param confStr Example: 127.0.0.1:8081,127.0.0.1:8082,127.0.0.1:8083 */ public RaftClient(String groupId, String confStr) { this.groupId = groupId; conf = new Configuration(); if (!conf.parse(confStr)) { throw new IllegalArgumentException("Fail to parse conf:" + confStr); } cliOptions = new CliOptions(); cliClientService = new BoltCliClientService(); } /** * @param groupId * @param confStr * @param cliClientService */ public RaftClient(String groupId, String confStr, AbstractBoltClientService cliClientService) { this.groupId = groupId; conf = new Configuration(); if (!conf.parse(confStr)) { throw new IllegalArgumentException("Fail to parse conf:" + confStr); } cliOptions = new CliOptions(); this.cliClientService = (BoltCliClientService) cliClientService; } /** * raft client start */ public void start() { if (started.compareAndSet(false, true)) { RouteTable.getInstance().updateConfiguration(groupId, conf); cliClientService.init(cliOptions); rpcClient = cliClientService.getRpcClient(); RaftClientConnectionHandler raftClientConnectionHandler = new RaftClientConnectionHandler( this); rpcClient.addConnectionEventProcessor(ConnectionEventType.CONNECT, new ConnectionEventAdapter(ConnectionEventType.CONNECT, raftClientConnectionHandler, null)); rpcClient.addConnectionEventProcessor(ConnectionEventType.CLOSE, new ConnectionEventAdapter(ConnectionEventType.CLOSE, raftClientConnectionHandler, null)); rpcClient.addConnectionEventProcessor(ConnectionEventType.EXCEPTION, new ConnectionEventAdapter(ConnectionEventType.EXCEPTION, raftClientConnectionHandler, null)); //reset leader notify NotifyLeaderChangeHandler notifyLeaderChangeHandler = new NotifyLeaderChangeHandler( groupId, cliClientService); rpcClient .registerUserProcessor(new SyncUserProcessorAdapter(notifyLeaderChangeHandler)); } } /** * stop cliClientService */ public void shutdown() { if (cliClientService != null) { cliClientService.shutdown(); } } /** * repick leader * @return */ public PeerId refreshLeader() { return refreshLeader(cliClientService, groupId, cliOptions.getRpcDefaultTimeout()); } public static PeerId refreshLeader(CliClientService cliClientService, String groupId, int timeout) { try { Status status = RouteTable.getInstance().refreshLeader(cliClientService, groupId, timeout); if (!status.isOk()) { throw new IllegalStateException(String.format("Refresh leader failed,error=%s", status.getErrorMsg())); } PeerId leader = RouteTable.getInstance().selectLeader(groupId); LOGGER.info("Leader is {}", leader); //avoid refresh leader config ip list must be current list,list maybe change by manage status = RouteTable.getInstance().refreshConfiguration(cliClientService, groupId, timeout); if (!status.isOk()) { throw new IllegalStateException(String.format( "Refresh configuration failed, error=%s", status.getErrorMsg())); } return leader; } catch (Exception e) { LOGGER.error("Refresh leader failed", e); throw new IllegalStateException("Refresh leader failed", e); } } /** * get leader * @return */ public PeerId getLeader() { PeerId leader = RouteTable.getInstance().selectLeader(groupId); if (leader == null) { leader = refreshLeader(); } return leader; } /** * raft client send request * @param request * @return */ public Object sendRequest(ProcessRequest request) { try { if (!started.get()) { LOGGER.error("Client must be started before send request!"); throw new IllegalStateException("Client must be started before send request!"); } PeerId peer = getLeader(); LOGGER.info("Raft client send message {} to url {}", request, peer.getEndpoint() .toString()); Object response = this.rpcClient.invokeSync(peer.getEndpoint().toString(), request, cliOptions.getRpcDefaultTimeout()); if (response == null) { LOGGER.error("Send process request has no response return!"); throw new RuntimeException("Send process request has no response return!"); } ProcessResponse cmd = (ProcessResponse) response; if (cmd.getSuccess()) { return cmd.getEntity(); } else { String redirect = cmd.getRedirect(); if (redirect != null && !redirect.isEmpty()) { return redirectRequest(request, redirect); } else { throw new IllegalStateException("Server error:" + cmd.getEntity()); } } } catch (Exception e) { LOGGER.error("Send process request error!", e); throw new RuntimeException("Send process request error!" + e.getMessage(), e); } } private Object redirectRequest(ProcessRequest request, String redirect) { try { PeerId redirectLead = new PeerId(); if (!redirectLead.parse(redirect)) { throw new IllegalArgumentException("Fail to parse serverId:" + redirect); } //wait for onLeaderStart TimeUnit.MILLISECONDS.sleep(1000); LOGGER.info("Redirect request send to return peer {},request {}", redirect, request); Object response = this.rpcClient.invokeSync(redirectLead.getEndpoint().toString(), request, cliOptions.getRpcDefaultTimeout()); ProcessResponse cmd = (ProcessResponse) response; if (cmd.getSuccess()) { RouteTable.getInstance().updateLeader(groupId, redirectLead); return cmd.getEntity(); } else { refreshLeader(); throw new IllegalStateException("Redirect request server error:" + cmd.getEntity()); } } catch (Exception e) { LOGGER.error("Redirect process request error!", e); throw new RuntimeException("Redirect process request error!" + e.getMessage(), e); } } /** * Getter method for property <tt>groupId</tt>. * * @return property value of groupId */ public String getGroupId() { return groupId; } }