Java tutorial
/* * Copyright (c) 2015 The Jupiter Project * * Licensed 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 org.jupiter.registry; import io.netty.bootstrap.Bootstrap; import io.netty.buffer.ByteBuf; import io.netty.channel.*; import io.netty.channel.socket.nio.NioSocketChannel; import io.netty.handler.codec.MessageToByteEncoder; import io.netty.handler.codec.ReplayingDecoder; import io.netty.util.Attribute; import io.netty.util.AttributeKey; import io.netty.util.ReferenceCountUtil; import org.jupiter.common.concurrent.ConcurrentSet; import org.jupiter.common.util.Maps; import org.jupiter.common.util.Pair; import org.jupiter.common.util.SystemClock; import org.jupiter.common.util.internal.logging.InternalLogger; import org.jupiter.common.util.internal.logging.InternalLoggerFactory; import org.jupiter.rpc.UnresolvedAddress; import org.jupiter.rpc.channel.JChannel; import org.jupiter.transport.Acknowledge; import org.jupiter.transport.JConnection; import org.jupiter.transport.JOption; import org.jupiter.transport.JProtocolHeader; import org.jupiter.transport.exception.ConnectFailedException; import org.jupiter.common.util.Signal; import org.jupiter.transport.exception.IoSignals; import org.jupiter.transport.netty.NettyTcpConnector; import org.jupiter.transport.netty.channel.NettyChannel; import org.jupiter.transport.netty.handler.AcknowledgeEncoder; import org.jupiter.transport.netty.handler.IdleStateChecker; import org.jupiter.transport.netty.handler.connector.ConnectionWatchdog; import org.jupiter.transport.netty.handler.connector.ConnectorIdleStateTrigger; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.util.List; import java.util.concurrent.ConcurrentMap; import static java.util.concurrent.TimeUnit.SECONDS; import static org.jupiter.common.util.JConstants.WRITER_IDLE_TIME_SECONDS; import static org.jupiter.common.util.Preconditions.checkNotNull; import static org.jupiter.common.util.StackTraceUtil.stackTrace; import static org.jupiter.registry.RegisterMeta.Address; import static org.jupiter.registry.RegisterMeta.ServiceMeta; import static org.jupiter.serialization.SerializerHolder.serializerImpl; import static org.jupiter.transport.JProtocolHeader.*; import static org.jupiter.transport.exception.IoSignals.ILLEGAL_MAGIC; import static org.jupiter.transport.exception.IoSignals.ILLEGAL_SIGN; /** * The client of registration center. * * jupiter * org.jupiter.registry * * @author jiachun.fjc */ public class ConfigClient extends NettyTcpConnector { private static final InternalLogger logger = InternalLoggerFactory.getInstance(ConfigClient.class); private static final AttributeKey<ConcurrentSet<ServiceMeta>> C_SUBSCRIBE_KEY = AttributeKey .valueOf("client.subscribed"); private static final AttributeKey<ConcurrentSet<RegisterMeta>> C_PUBLISH_KEY = AttributeKey .valueOf("client.published"); // ack, ???? private final ConcurrentMap<Long, MessageNonAck> messagesNonAck = Maps.newConcurrentHashMap(); // handlers private final ConnectorIdleStateTrigger idleStateTrigger = new ConnectorIdleStateTrigger(); private final MessageHandler handler = new MessageHandler(); private final MessageEncoder encoder = new MessageEncoder(); private final AcknowledgeEncoder ackEncoder = new AcknowledgeEncoder(); // ?ConfigClient??channel private volatile Channel channel; private AbstractRegistryService registryService; public ConfigClient(AbstractRegistryService registryService) { this(registryService, 1); } public ConfigClient(AbstractRegistryService registryService, int nWorkers) { super(nWorkers); this.registryService = checkNotNull(registryService, "registryService"); } @Override protected void doInit() { // child options config().setOption(JOption.SO_REUSEADDR, true); config().setOption(JOption.CONNECT_TIMEOUT_MILLIS, (int) SECONDS.toMillis(3)); // channel factory bootstrap().channel(NioSocketChannel.class); } /** * ConfigClient??, async? */ @Override public JConnection connect(UnresolvedAddress address, boolean async) { setOptions(); final Bootstrap boot = bootstrap(); final SocketAddress socketAddress = InetSocketAddress.createUnresolved(address.getHost(), address.getPort()); // ?watchdog final ConnectionWatchdog watchdog = new ConnectionWatchdog(boot, timer, socketAddress, null) { @Override public ChannelHandler[] handlers() { return new ChannelHandler[] { this, new IdleStateChecker(timer, 0, WRITER_IDLE_TIME_SECONDS, 0), idleStateTrigger, new MessageDecoder(), encoder, ackEncoder, handler }; } }; watchdog.setReconnect(true); try { ChannelFuture future; synchronized (bootstrapLock()) { boot.handler(new ChannelInitializer<NioSocketChannel>() { @Override protected void initChannel(NioSocketChannel ch) throws Exception { ch.pipeline().addLast(watchdog.handlers()); } }); future = boot.connect(socketAddress); } // ?synchronized??? future.sync(); channel = future.channel(); } catch (Throwable t) { throw new ConnectFailedException("connects to [" + address + "] fails", t); } return new JConnection(address) { @Override public void setReconnect(boolean reconnect) { watchdog.setReconnect(reconnect); } }; } /** * Sent the subscription information to config server. */ public void doSubscribe(ServiceMeta serviceMeta) { registryService.subscribeSet().add(serviceMeta); Message msg = new Message(); msg.sign(SUBSCRIBE_SERVICE); msg.data(serviceMeta); Channel ch = channel; // MessageHandler#channelActive()write if (attachSubscribeEventOnChannel(serviceMeta, ch)) { ch.writeAndFlush(msg).addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE); MessageNonAck msgNonAck = new MessageNonAck(msg, ch); messagesNonAck.put(msgNonAck.id, msgNonAck); } } /** * Publishing service to config server. */ public void doRegister(RegisterMeta meta) { registryService.registerMetaSet().add(meta); Message msg = new Message(); msg.sign(PUBLISH_SERVICE); msg.data(meta); Channel ch = channel; // MessageHandler#channelActive()write if (attachPublishEventOnChannel(meta, ch)) { ch.writeAndFlush(msg).addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE); MessageNonAck msgNonAck = new MessageNonAck(msg, ch); messagesNonAck.put(msgNonAck.id, msgNonAck); } } /** * Notify to config server unpublish corresponding service. */ public void doUnregister(RegisterMeta meta) { registryService.registerMetaSet().remove(meta); Message msg = new Message(); msg.sign(PUBLISH_CANCEL_SERVICE); msg.data(meta); channel.writeAndFlush(msg).addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE); MessageNonAck msgNonAck = new MessageNonAck(msg, channel); messagesNonAck.put(msgNonAck.id, msgNonAck); } private void handleAcknowledge(Acknowledge ack) { messagesNonAck.remove(ack.sequence()); } // channel(??) private static boolean attachPublishEventOnChannel(RegisterMeta meta, Channel channel) { Attribute<ConcurrentSet<RegisterMeta>> attr = channel.attr(C_PUBLISH_KEY); ConcurrentSet<RegisterMeta> registerMetaSet = attr.get(); if (registerMetaSet == null) { ConcurrentSet<RegisterMeta> newRegisterMetaSet = new ConcurrentSet<>(); registerMetaSet = attr.setIfAbsent(newRegisterMetaSet); if (registerMetaSet == null) { registerMetaSet = newRegisterMetaSet; } } return registerMetaSet.add(meta); } // channel(?) private static boolean attachSubscribeEventOnChannel(ServiceMeta serviceMeta, Channel channel) { Attribute<ConcurrentSet<ServiceMeta>> attr = channel.attr(C_SUBSCRIBE_KEY); ConcurrentSet<ServiceMeta> serviceMetaSet = attr.get(); if (serviceMetaSet == null) { ConcurrentSet<ServiceMeta> newServiceMetaSet = new ConcurrentSet<>(); serviceMetaSet = attr.setIfAbsent(newServiceMetaSet); if (serviceMetaSet == null) { serviceMetaSet = newServiceMetaSet; } } return serviceMetaSet.add(serviceMeta); } static class MessageNonAck { private final long id; private final Message msg; private final Channel channel; private final long timestamp = SystemClock.millisClock().now(); public MessageNonAck(Message msg, Channel channel) { this.msg = msg; this.channel = channel; id = msg.sequence(); } } /** * ************************************************************************************************** * Protocol * ? * 2 1 1 8 4 * * * MAGIC Sign Status Invoke Id Body Length Body Content * * * * ?16 * = 2 // MAGIC = (short) 0xbabe * + 1 // ??, ?? * + 1 // * + 8 // ? id long * + 4 // ?body, int */ static class MessageDecoder extends ReplayingDecoder<MessageDecoder.State> { public MessageDecoder() { super(State.HEADER_MAGIC); } // ?? private final JProtocolHeader header = new JProtocolHeader(); @Override protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception { switch (state()) { case HEADER_MAGIC: checkMagic(in.readShort()); // MAGIC checkpoint(State.HEADER_SIGN); case HEADER_SIGN: header.sign(in.readByte()); // ?? checkpoint(State.HEADER_STATUS); case HEADER_STATUS: in.readByte(); // no-op checkpoint(State.HEADER_ID); case HEADER_ID: header.id(in.readLong()); // ?id checkpoint(State.HEADER_BODY_LENGTH); case HEADER_BODY_LENGTH: header.bodyLength(in.readInt()); // ? checkpoint(State.BODY); case BODY: switch (header.sign()) { case PUBLISH_SERVICE: case OFFLINE_NOTICE: { byte[] bytes = new byte[header.bodyLength()]; in.readBytes(bytes); Message msg = serializerImpl().readObject(bytes, Message.class); msg.sign(header.sign()); out.add(msg); break; } case ACK: { byte[] bytes = new byte[header.bodyLength()]; in.readBytes(bytes); Acknowledge ack = serializerImpl().readObject(bytes, Acknowledge.class); out.add(ack); break; } default: throw ILLEGAL_SIGN; } checkpoint(State.HEADER_MAGIC); } } private static void checkMagic(short magic) throws Signal { if (MAGIC != magic) { throw ILLEGAL_MAGIC; } } enum State { HEADER_MAGIC, HEADER_SIGN, HEADER_STATUS, HEADER_ID, HEADER_BODY_LENGTH, BODY } } /** * ************************************************************************************************** * Protocol * ? * 2 1 1 8 4 * * * MAGIC Sign Status Invoke Id Body Length Body Content * * * * ?16 * = 2 // MAGIC = (short) 0xbabe * + 1 // ??, ?? * + 1 // * + 8 // ? id long * + 4 // ?body, int */ @ChannelHandler.Sharable static class MessageEncoder extends MessageToByteEncoder<Message> { @Override protected void encode(ChannelHandlerContext ctx, Message msg, ByteBuf out) throws Exception { byte[] bytes = serializerImpl().writeObject(msg); out.writeShort(MAGIC).writeByte(msg.sign()).writeByte(0).writeLong(0).writeInt(bytes.length) .writeBytes(bytes); } } @ChannelHandler.Sharable class MessageHandler extends ChannelInboundHandlerAdapter { @SuppressWarnings("unchecked") @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { if (msg instanceof Message) { Message obj = (Message) msg; switch (obj.sign()) { case PUBLISH_SERVICE: Pair<ServiceMeta, List<RegisterMeta>> data = (Pair<ServiceMeta, List<RegisterMeta>>) obj.data(); registryService.notify(data.getKey(), data.getValue(), obj.getVersion()); ctx.channel().writeAndFlush(new Acknowledge(obj.sequence())) // ?ACK .addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE); logger.info("Publish from ConfigServer {}, provider count: {}, version: {}.", data.getKey(), data.getValue().size(), obj.getVersion()); break; case OFFLINE_NOTICE: Address address = (Address) obj.data(); registryService.offline(address); logger.info("Offline notice on {}.", address); break; } } else if (msg instanceof Acknowledge) { handleAcknowledge((Acknowledge) msg); } else { logger.warn("Unexpected msg type received: {}.", msg.getClass()); ReferenceCountUtil.release(msg); } } @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { Channel ch = (channel = ctx.channel()); // ? for (ServiceMeta serviceMeta : registryService.subscribeSet()) { // doSubscribe()write if (!attachSubscribeEventOnChannel(serviceMeta, ch)) { continue; } Message msg = new Message(); msg.sign(SUBSCRIBE_SERVICE); msg.data(serviceMeta); ch.writeAndFlush(msg).addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE); MessageNonAck msgNonAck = new MessageNonAck(msg, ch); messagesNonAck.put(msgNonAck.id, msgNonAck); } // ??? for (RegisterMeta meta : registryService.registerMetaSet()) { // doRegister()write if (!attachPublishEventOnChannel(meta, ch)) { continue; } Message msg = new Message(); msg.sign(PUBLISH_SERVICE); msg.data(meta); ch.writeAndFlush(msg).addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE); MessageNonAck msgNonAck = new MessageNonAck(msg, ch); messagesNonAck.put(msgNonAck.id, msgNonAck); } } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { JChannel jChannel = NettyChannel.attachChannel(ctx.channel()); if (cause instanceof Signal) { IoSignals.handleSignal((Signal) cause, jChannel); } else { logger.error("An exception has been caught {}, on {}.", stackTrace(cause), jChannel); } } } private class AckTimeoutScanner implements Runnable { @SuppressWarnings("InfiniteLoopStatement") @Override public void run() { for (;;) { try { for (MessageNonAck m : messagesNonAck.values()) { if (SystemClock.millisClock().now() - m.timestamp > SECONDS.toMillis(10)) { // if (messagesNonAck.remove(m.id) == null) { continue; } if (m.channel.isActive()) { MessageNonAck msgNonAck = new MessageNonAck(m.msg, m.channel); messagesNonAck.put(msgNonAck.id, msgNonAck); m.channel.writeAndFlush(m.msg) .addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE); } } } Thread.sleep(300); } catch (Throwable t) { logger.error("An exception has been caught while scanning the timeout acknowledges {}.", t); } } } } { Thread t = new Thread(new AckTimeoutScanner(), "ack.timeout.scanner"); t.setDaemon(true); t.start(); } }