attr = channel.attr(ChannelAttrKeys.highPerformanceWriter);
65 | HighPerformanceChannelWriter writer = attr.get();
66 | if (null == writer) {
67 | HighPerformanceChannelWriter old = attr.setIfAbsent(writer = new HighPerformanceChannelWriter(channel));
68 | if (null != old) {
69 | writer = old;
70 | }
71 | }
72 | return writer;
73 | }
74 |
75 | private void writeAndFlush() {
76 | while (fetchTask()) {
77 | Protocol protocol = null;
78 | while (null != (protocol = queue.poll())) {
79 | channel.write(protocol, channel.voidPromise());
80 | }
81 | }
82 | channel.flush();
83 | }
84 |
85 | /**
86 | * @return true if we have to recheck queues
87 | */
88 | private boolean fetchTask() {
89 | int old = state.getAndDecrement();
90 | if (old == State.RUNNING_GOT_TASKS.ordinal()) {
91 | return true;
92 | } else if (old == State.RUNNING_NO_TASKS.ordinal()) {
93 | return false;
94 | } else {
95 | throw new AssertionError();
96 | }
97 | }
98 |
99 | /**
100 | * @return true if caller has to schedule task execution
101 | */
102 | private boolean addTask() {
103 | // fast track for high-load applications
104 | // atomic get is cheaper than atomic swap
105 | // for both this thread and fetching thread
106 | if (state.get() == State.RUNNING_GOT_TASKS.ordinal())
107 | return false;
108 |
109 | int old = state.getAndSet(State.RUNNING_GOT_TASKS.ordinal());
110 | return old == State.WAITING.ordinal();
111 | }
112 |
113 | static final class WriteAndFlushTask implements Runnable {
114 |
115 | private final HighPerformanceChannelWriter writer;
116 |
117 | public WriteAndFlushTask(HighPerformanceChannelWriter writer) {
118 | this.writer = writer;
119 | }
120 |
121 | @Override
122 | public void run() {
123 | writer.writeAndFlush();
124 | }
125 | }
126 |
127 | private enum State {
128 | /** actor is not currently running */
129 | WAITING,
130 | /** actor is running, and has no more tasks */
131 | RUNNING_NO_TASKS,
132 | /** actor is running, but some queues probably updated, actor needs to recheck them */
133 | RUNNING_GOT_TASKS,
134 | }
135 | }
136 |
--------------------------------------------------------------------------------
/sailfish-kernel/src/main/java/sailfish/remoting/channel/DefaultExchangeChannelChooserFactory.java:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * Copyright 2016-2016 spccold
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | *
17 | */
18 | package sailfish.remoting.channel;
19 |
20 | import java.util.concurrent.atomic.AtomicInteger;
21 |
22 | import sailfish.remoting.exceptions.ExceptionCode;
23 | import sailfish.remoting.exceptions.SailfishException;
24 |
25 | /**
26 | * Default implementation which uses simple round-robin to choose next
27 | * {@link ExchangeChannel} until which {@link ExchangeChannel#isAvailable()}
28 | * return true or all children has been chosen.
29 | *
30 | * @author spccold
31 | * @version $Id: DefaultExchangeChannelChooserFactory.java, v 0.1 2016年11月22日
32 | * 下午4:40:01 spccold Exp $
33 | */
34 | public class DefaultExchangeChannelChooserFactory implements ExchangeChannelChooserFactory {
35 |
36 | public static final DefaultExchangeChannelChooserFactory INSTANCE = new DefaultExchangeChannelChooserFactory();
37 |
38 | private DefaultExchangeChannelChooserFactory() { }
39 |
40 | @Override
41 | public ExchangeChannelChooser newChooser(ExchangeChannel[] channels, ExchangeChannel[] deadChannels) {
42 | if (isPowerOfTwo(channels.length)) {
43 | return new PowerOfTowExchangeChannelChooser(channels, deadChannels);
44 | } else {
45 | return new GenericExchangeChannelChooser(channels, deadChannels);
46 | }
47 | }
48 |
49 | private static boolean isPowerOfTwo(int val) {
50 | return (val & -val) == val;
51 | }
52 |
53 | private static final class PowerOfTowExchangeChannelChooser implements ExchangeChannelChooser {
54 | private final AtomicInteger idx = new AtomicInteger();
55 | private final ExchangeChannel[] channels;
56 | private final ExchangeChannel[] deadChannels;
57 |
58 | PowerOfTowExchangeChannelChooser(ExchangeChannel[] channels, ExchangeChannel[] deadChannels) {
59 | this.channels = channels;
60 | this.deadChannels = deadChannels;
61 | }
62 |
63 | @Override
64 | public ExchangeChannel next() throws SailfishException {
65 | if(channels.length == 1){//one connection check
66 | if(null == channels[0] || !channels[0].isAvailable()){
67 | throw new SailfishException(ExceptionCode.EXCHANGER_NOT_AVAILABLE, "exchanger is not available!");
68 | }
69 | return channels[0];
70 | }
71 |
72 | int arrayIndex = 0;
73 | int currentIndex = idx.getAndIncrement();
74 | for (int i = 0; i < channels.length; i++) {
75 | ExchangeChannel currentChannel = channels[arrayIndex = ((currentIndex++) & channels.length - 1)];
76 | if (null != currentChannel && currentChannel.isAvailable()) {
77 | if (null != deadChannels[arrayIndex]) {
78 | deadChannels[arrayIndex] = null;
79 | }
80 | return currentChannel;
81 | }
82 | deadChannels[arrayIndex] = currentChannel;
83 | }
84 | throw new SailfishException(ExceptionCode.EXCHANGER_NOT_AVAILABLE, "exchanger is not available!");
85 | }
86 | }
87 |
88 | private static final class GenericExchangeChannelChooser implements ExchangeChannelChooser {
89 | private final AtomicInteger idx = new AtomicInteger();
90 | private final ExchangeChannel[] channels;
91 | private final ExchangeChannel[] deadChannels;
92 |
93 | GenericExchangeChannelChooser(ExchangeChannel[] channels, ExchangeChannel[] deadChannels) {
94 | this.channels = channels;
95 | this.deadChannels = deadChannels;
96 | }
97 |
98 | @Override
99 | public ExchangeChannel next() throws SailfishException {
100 | int arrayIndex = 0;
101 | int currentIndex = idx.getAndIncrement();
102 | for (int i = 0; i < channels.length; i++) {
103 | ExchangeChannel currentChannel = channels[arrayIndex = Math.abs((currentIndex++) % channels.length)];
104 | if (null != currentChannel && currentChannel.isAvailable()) {
105 | if (null != deadChannels[arrayIndex]) {
106 | deadChannels[arrayIndex] = null;
107 | }
108 | return currentChannel;
109 | }
110 | deadChannels[arrayIndex] = currentChannel;
111 | }
112 | throw new SailfishException(ExceptionCode.EXCHANGER_NOT_AVAILABLE, "exchanger is not available!");
113 | }
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/sailfish-kernel/src/main/java/sailfish/remoting/configuration/NegotiateConfig.java:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * Copyright 2016-2016 spccold
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | *
17 | */
18 | package sailfish.remoting.configuration;
19 |
20 | import java.io.ByteArrayInputStream;
21 | import java.io.ByteArrayOutputStream;
22 | import java.io.DataInputStream;
23 | import java.io.DataOutputStream;
24 | import java.io.IOException;
25 | import java.util.UUID;
26 |
27 | import sailfish.remoting.channel.ChannelType;
28 | import sailfish.remoting.constants.Opcode;
29 | import sailfish.remoting.protocol.RequestProtocol;
30 | import sailfish.remoting.utils.ArrayUtils;
31 |
32 | /**
33 | *
34 | * @author spccold
35 | * @version $Id: NegotiateConfig.java, v 0.1 2016年11月22日 下午2:58:33 spccold Exp $
36 | */
37 | public class NegotiateConfig {
38 | private byte idleTimeout;
39 | private byte maxIdleTimeout;
40 |
41 | private final UUID uuid;
42 | private final byte type;
43 | private final short connections;
44 | private final short writeConnections;
45 | private final boolean reversed;
46 |
47 | private short index;
48 |
49 | public NegotiateConfig(byte idleTimeout, byte maxIdleTimeout, UUID uuid, byte type, short connections,
50 | short writeConnections, short index, boolean reversed) {
51 | this.idleTimeout = idleTimeout;
52 | this.maxIdleTimeout = maxIdleTimeout;
53 |
54 | this.uuid = uuid;
55 | this.type = type;
56 | this.connections = connections;
57 | this.writeConnections = writeConnections;
58 | this.reversed = reversed;
59 |
60 | this.index = index;
61 | }
62 |
63 | public byte idleTimeout() {
64 | return this.idleTimeout;
65 | }
66 |
67 | public byte maxIdleTimeout() {
68 | return this.maxIdleTimeout;
69 | }
70 |
71 | public UUID uuid() {
72 | return this.uuid;
73 | }
74 |
75 | public byte type() {
76 | return this.type;
77 | }
78 |
79 | public short connections() {
80 | return this.connections;
81 | }
82 |
83 | public short writeConnections() {
84 | return this.writeConnections;
85 | }
86 |
87 | public NegotiateConfig index(short index) {
88 | this.index = index;
89 | return this;
90 | }
91 |
92 | public short index() {
93 | return this.index;
94 | }
95 |
96 | public boolean reversed() {
97 | return this.reversed;
98 | }
99 |
100 | public boolean isRead() {
101 | return ChannelType.read.code() == type;
102 | }
103 |
104 | public boolean isWrite() {
105 | return ChannelType.write.code() == type;
106 | }
107 |
108 | public boolean isReadWrite(){
109 | return ChannelType.readwrite.code() == type;
110 | }
111 |
112 | public void reverseIndex(){
113 | if(!this.reversed){
114 | return;
115 | }
116 | if(isReadWrite()){
117 | this.index = (short)ArrayUtils.reverseArrayIndex(this.connections, this.index);
118 | }else if(isRead()){
119 | int readConnections = this.connections - this.writeConnections;
120 | this.index = (short) ArrayUtils.reverseArrayIndex(readConnections, index);
121 | }else if(isWrite()){
122 | this.index = (short) ArrayUtils.reverseArrayIndex(this.writeConnections, index);
123 | }
124 | }
125 |
126 | public NegotiateConfig deepCopy() {
127 | return new NegotiateConfig(idleTimeout, maxIdleTimeout, uuid, type, connections,
128 | writeConnections, index, reversed);
129 | }
130 |
131 | public RequestProtocol toNegotiateRequest() throws IOException {
132 | int size = 1 + 1 + 16 + 1 + 2 + 2 + 2 + 1;
133 | try (ByteArrayOutputStream baos = new ByteArrayOutputStream(size);
134 | DataOutputStream dos = new DataOutputStream(baos);) {
135 | dos.writeByte(this.idleTimeout);
136 | dos.writeByte(this.maxIdleTimeout);
137 | dos.writeLong(this.uuid.getMostSignificantBits());
138 | dos.writeLong(this.uuid.getLeastSignificantBits());
139 | dos.writeByte(this.type);
140 | dos.writeShort(this.connections);
141 | dos.writeShort(this.writeConnections);
142 | dos.writeShort(this.index);
143 | dos.writeBoolean(this.reversed);
144 | return RequestProtocol.newHeartbeat().opcode(Opcode.HEARTBEAT_WITH_NEGOTIATE).body(baos.toByteArray());
145 | }
146 | }
147 |
148 | public static NegotiateConfig fromNegotiate(byte[] negotiateData) throws IOException {
149 | try (DataInputStream dis = new DataInputStream(new ByteArrayInputStream(negotiateData))) {
150 | return new NegotiateConfig(dis.readByte(), dis.readByte(), new UUID(dis.readLong(), dis.readLong()),
151 | dis.readByte(), dis.readShort(), dis.readShort(), dis.readShort(), dis.readBoolean());
152 | }
153 | }
154 | }
155 |
--------------------------------------------------------------------------------
/sailfish-kernel/src/test/java/sailfish/remoting/RecycleTest.java:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * Copyright 2016-2016 spccold
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | *
17 | */
18 | package sailfish.remoting;
19 |
20 | import java.util.concurrent.CountDownLatch;
21 |
22 | import org.junit.Assert;
23 | import org.junit.Test;
24 |
25 | import io.netty.util.Recycler;
26 | import io.netty.util.internal.SystemPropertyUtil;
27 |
28 | /**
29 | * @author spccold
30 | * @version $Id: RecycleCheckTest.java, v 0.1 2016年12月1日 下午11:55:23 spccold Exp $
31 | */
32 | public class RecycleTest {
33 |
34 | @Test
35 | public void testRecycleInSyncThread() throws Exception{
36 | //recycle
37 | Resource2 resource1 = Resource2.newInstance();
38 | resource1.recycle();
39 |
40 | //don't recycle
41 | resource1 = Resource2.newInstance();
42 |
43 | Resource2 temp =null;
44 | // By default we allow one push to a Recycler for each 8th try on handles that were never recycled before.
45 | // This should help to slowly increase the capacity of the recycler while not be too sensitive to allocation
46 | // bursts.
47 | int stackDefaultRatioMask = SystemPropertyUtil.getInt("io.netty.recycler.ratio", 8) - 1;
48 | for(int i =0; i< stackDefaultRatioMask; i++){
49 | temp = Resource2.newInstance();
50 | temp.recycle();
51 | Assert.assertTrue(temp != Resource2.newInstance());
52 | }
53 |
54 | temp = Resource2.newInstance();
55 | temp.recycle();
56 | Assert.assertTrue(temp == Resource2.newInstance());
57 | }
58 |
59 | /**
60 | * 异步线程recycle测试
61 | *
62 | * 每个异步recycle的线程都会产生一个WeakOrderQueue, 多个线程产的的WeakOrderQueue会形成链, 如下所示
63 | * Recycler.Stack.head -> WeakOrderQueue@2(thread2产生) -> WeakOrderQueue@1(thread1产生) -> null
64 | * Recycler.Stack.cursor最初指向Recycler.Stack.head(例如最初指向WeakOrderQueue@1), 但是当产生新的WeakOrderQueue时
65 | * (例如WeakOrderQueue@2)时,Recycler.Stack.cursor指针并不会及时调整, 知道此次Recycler.Stack.pop结束,Recycler.Stack.cursor
66 | * 才会重新指向Recycler.Stack.head, 即指向WeakOrderQueue链的头
67 | *
68 | * @throws Exception
69 | */
70 | @Test
71 | public void testRecycleInAsyncThread() throws Exception{
72 | final CountDownLatch latch = new CountDownLatch(1);
73 | final Resource resource1 = Resource.newInstance();
74 | //recycle in thread1
75 | new Thread(){
76 | @Override
77 | public void run() {
78 | resource1.recycle();
79 | latch.countDown();
80 | }
81 | }.start();
82 |
83 | latch.await();
84 | Resource resource2 = Resource.newInstance();
85 | Assert.assertTrue(resource1 == resource2);
86 | resource2.recycle();
87 |
88 | final CountDownLatch latch2 = new CountDownLatch(1);
89 | final Resource resource3 = Resource.newInstance();
90 | //recycle in thread2
91 | new Thread(){
92 | @Override
93 | public void run() {
94 | resource3.recycle();
95 | latch2.countDown();
96 | }
97 | }.start();
98 |
99 | latch2.await();
100 | Resource resource4 = Resource.newInstance();
101 | //thread2中recycle时并没有调整Recycler.Stack.cursor, 此时Recycler.Stack.cursor还指向thread1产生的WeakOrderQueue1
102 | //此时Recycler.Stack.pop只能从WeakOrderQueue1开始遍历,但resource3 recycle在thread2产生的WeakOrderQueue2,所有pop为null,会创建新的对象
103 | Assert.assertTrue(resource4 != resource3);
104 | //上次Resource.newInstance()已经让Recycler.Stack.cursor重新指向WeakOrderQueue的头,所以这次可以获取到thread2中recycle的resource3
105 | Assert.assertTrue(Resource.newInstance() == resource3);
106 | }
107 |
108 | static final class Resource{
109 |
110 | private static final Recycler RECYCLER = new Recycler(){
111 | @Override
112 | protected Resource newObject(Recycler.Handle handle) {
113 | return new Resource(handle);
114 | }
115 | };
116 |
117 | public static Resource newInstance(){
118 | return RECYCLER.get();
119 | }
120 |
121 | private final Recycler.Handle handle;
122 | private Resource(Recycler.Handle handle) {
123 | this.handle = handle;
124 | }
125 |
126 | public void recycle() {
127 | handle.recycle(this);
128 | }
129 | }
130 |
131 | static final class Resource2{
132 |
133 | private static final Recycler RECYCLER = new Recycler(){
134 | @Override
135 | protected Resource2 newObject(Recycler.Handle handle) {
136 | return new Resource2(handle);
137 | }
138 | };
139 |
140 | public static Resource2 newInstance(){
141 | return RECYCLER.get();
142 | }
143 |
144 | private final Recycler.Handle handle;
145 | private Resource2(Recycler.Handle handle) {
146 | this.handle = handle;
147 | }
148 |
149 | public void recycle() {
150 | handle.recycle(this);
151 | }
152 | }
153 | }
154 |
--------------------------------------------------------------------------------
/sailfish-kernel/src/main/java/sailfish/remoting/channel/MultiConnectionsExchangeChannelGroup.java:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * Copyright 2016-2016 spccold
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | *
17 | */
18 | package sailfish.remoting.channel;
19 |
20 | import io.netty.bootstrap.Bootstrap;
21 | import io.netty.channel.EventLoopGroup;
22 | import io.netty.util.concurrent.EventExecutorGroup;
23 | import sailfish.remoting.Address;
24 | import sailfish.remoting.Tracer;
25 | import sailfish.remoting.configuration.NegotiateConfig;
26 | import sailfish.remoting.exceptions.SailfishException;
27 | import sailfish.remoting.handler.MsgHandler;
28 | import sailfish.remoting.protocol.Protocol;
29 |
30 | /**
31 | * @author spccold
32 | * @version $Id: MultiConnectionsExchangeChannelGroup.java, v 0.1 2016年11月22日 下午4:01:53 spccold Exp
33 | * $
34 | */
35 | public abstract class MultiConnectionsExchangeChannelGroup extends AbstractConfigurableExchangeChannelGroup {
36 |
37 | private final ExchangeChannel[] children;
38 | private final ExchangeChannel[] deadChildren;
39 | private final ExchangeChannelChooserFactory.ExchangeChannelChooser chooser;
40 | private final MsgHandler msgHandler;
41 | private final Tracer tracer;
42 |
43 | protected MultiConnectionsExchangeChannelGroup(Tracer tracer, MsgHandler msgHandler, Address address,
44 | short connections, int connectTimeout, int reconnectInterval, byte idleTimeout, byte maxIdleTimeOut,
45 | boolean lazy, boolean reverseIndex, NegotiateConfig config, ExchangeChannelGroup parentGroup,
46 | EventLoopGroup loopGroup, EventExecutorGroup executorGroup) throws SailfishException {
47 |
48 | this.tracer = tracer;
49 | this.msgHandler = msgHandler;
50 |
51 | children = new ExchangeChannel[connections];
52 | deadChildren = new ExchangeChannel[connections];
53 |
54 | if (null == config) {
55 | config = new NegotiateConfig(idleTimeout, maxIdleTimeOut, id(), ChannelType.readwrite.code(),
56 | (short) connections, (short) connections, (short) 0, reverseIndex);
57 | }
58 |
59 | Bootstrap bootstrap = null;
60 | for (short i = 0; i < connections; i++) {
61 | boolean success = false;
62 | final NegotiateConfig deepCopy = config.deepCopy().index(i);
63 | parentGroup = (null == parentGroup ? this : parentGroup);
64 | bootstrap = configureBoostrap(address, connectTimeout, deepCopy, parentGroup, loopGroup, executorGroup);
65 | try {
66 | children[i] = newChild(parentGroup, bootstrap, reconnectInterval, lazy, deepCopy.isRead());
67 | success = true;
68 | } catch (SailfishException cause) {
69 | throw cause;
70 | } finally {
71 | if (!success) {
72 | close(Integer.MAX_VALUE);
73 | }
74 | }
75 | }
76 |
77 | chooser = DefaultExchangeChannelChooserFactory.INSTANCE.newChooser(children, deadChildren);
78 | }
79 |
80 | @Override
81 | public ExchangeChannel next() throws SailfishException {
82 | return chooser.next();
83 | }
84 |
85 | /**
86 | * Return the number of {@link ExchangeChannel} this implementation uses. This number is the
87 | * maps 1:1 to the connections it use.
88 | */
89 | public final int channelOCount() {
90 | return children.length;
91 | }
92 |
93 | public void close(int timeout) {
94 | if (this.isClosed()) {
95 | return;
96 | }
97 | synchronized (this) {
98 | if (this.isClosed()) {
99 | return;
100 | }
101 | this.closed = true;
102 | for (int i = 0; i < children.length; i++) {
103 | deadChildren[i] = null;
104 | if (null != children[i]) {
105 | children[i].close(timeout);
106 | }
107 | }
108 | }
109 | }
110 |
111 | @Override
112 | public boolean isAvailable() {
113 | if (this.isClosed()) {
114 | return false;
115 | }
116 |
117 | if (children.length == 1) {// one connection check
118 | return (null != children[0] && children[0].isAvailable());
119 | }
120 |
121 | // can hit most of the time
122 | if (deadChildren[0] == null || deadChildren[0].isAvailable()) {
123 | return true;
124 | }
125 |
126 | for (int i = 1; i < children.length; i++) {
127 | if (deadChildren[i] == null || deadChildren[i].isAvailable()) {
128 | return true;
129 | }
130 | }
131 | return false;
132 | }
133 |
134 | @Override
135 | public MsgHandler getMsgHander() {
136 | return msgHandler;
137 | }
138 |
139 | @Override
140 | public Tracer getTracer() {
141 | return tracer;
142 | }
143 |
144 | /**
145 | * Create a new {@link ExchangeChannel} which will later then accessible via the {@link #next()}
146 | * method.
147 | */
148 | protected abstract ExchangeChannel newChild(ExchangeChannelGroup parent, Bootstrap bootstrap, int reconnectInterval,
149 | boolean lazy, boolean readChannel) throws SailfishException;
150 | }
151 |
--------------------------------------------------------------------------------
/sailfish-kernel/src/main/java/sailfish/remoting/channel/AbstractConfigurableExchangeChannelGroup.java:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * Copyright 2016-2016 spccold
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | *
17 | */
18 | package sailfish.remoting.channel;
19 |
20 | import java.util.UUID;
21 | import java.util.concurrent.CountDownLatch;
22 |
23 | import static sailfish.remoting.constants.ChannelAttrKeys.OneTime;
24 |
25 | import io.netty.bootstrap.Bootstrap;
26 | import io.netty.buffer.PooledByteBufAllocator;
27 | import io.netty.channel.ChannelInitializer;
28 | import io.netty.channel.ChannelOption;
29 | import io.netty.channel.ChannelPipeline;
30 | import io.netty.channel.EventLoopGroup;
31 | import io.netty.channel.WriteBufferWaterMark;
32 | import io.netty.channel.socket.SocketChannel;
33 | import io.netty.handler.timeout.IdleStateHandler;
34 | import io.netty.util.concurrent.EventExecutorGroup;
35 | import sailfish.remoting.Address;
36 | import sailfish.remoting.NettyPlatformIndependent;
37 | import sailfish.remoting.codec.RemotingDecoder;
38 | import sailfish.remoting.codec.RemotingEncoder;
39 | import sailfish.remoting.configuration.NegotiateConfig;
40 | import sailfish.remoting.constants.ChannelAttrKeys;
41 | import sailfish.remoting.eventgroup.ClientEventGroup;
42 | import sailfish.remoting.handler.HeartbeatChannelHandler;
43 | import sailfish.remoting.handler.NegotiateChannelHandler;
44 | import sailfish.remoting.handler.ConcreteRequestHandler;
45 |
46 | /**
47 | * @author spccold
48 | * @version $Id: AbstractConfigurableExchangeChannelGroup.java, v 0.1 2016年11月23日 下午3:47:32 spccold
49 | * Exp $
50 | */
51 | public abstract class AbstractConfigurableExchangeChannelGroup extends AbstractExchangeChannelGroup {
52 |
53 | protected AbstractConfigurableExchangeChannelGroup() {
54 | super(UUID.randomUUID());
55 | }
56 |
57 | protected Bootstrap configureBoostrap(Address remoteAddress, int connectTimeout, NegotiateConfig config,
58 | ExchangeChannelGroup channelGroup, EventLoopGroup loopGroup, EventExecutorGroup executorGroup) {
59 | Bootstrap boot = newBootstrap();
60 | if (null == loopGroup) {
61 | loopGroup = ClientEventGroup.INSTANCE.getLoopGroup();
62 | }
63 | if (null == executorGroup) {
64 | executorGroup = ClientEventGroup.INSTANCE.getExecutorGroup();
65 | }
66 | boot.group(loopGroup);
67 | boot.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, connectTimeout);
68 | boot.remoteAddress(remoteAddress.host(), remoteAddress.port());
69 | boot.handler(newChannelInitializer(config, channelGroup, executorGroup));
70 | return boot;
71 | }
72 |
73 | private Bootstrap newBootstrap() {
74 | Bootstrap boot = new Bootstrap();
75 | boot.channel(NettyPlatformIndependent.channelClass());
76 | boot.option(ChannelOption.TCP_NODELAY, true);
77 | // replace by heart beat
78 | boot.option(ChannelOption.SO_KEEPALIVE, false);
79 | // default is pooled direct
80 | // ByteBuf(io.netty.util.internal.PlatformDependent.DIRECT_BUFFER_PREFERRED)
81 | boot.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);
82 | // 32kb(for massive long connections, See
83 | // http://www.infoq.com/cn/articles/netty-million-level-push-service-design-points)
84 | // 64kb(RocketMq remoting default value)
85 | boot.option(ChannelOption.SO_SNDBUF, 32 * 1024);
86 | boot.option(ChannelOption.SO_RCVBUF, 32 * 1024);
87 | // temporary settings, need more tests
88 | boot.option(ChannelOption.WRITE_BUFFER_WATER_MARK, new WriteBufferWaterMark(8 * 1024, 32 * 1024));
89 | //default is true, reduce thread context switching
90 | boot.option(ChannelOption.SINGLE_EVENTEXECUTOR_PER_GROUP, true);
91 | return boot;
92 | }
93 |
94 | private ChannelInitializer newChannelInitializer(final NegotiateConfig config,
95 | final ExchangeChannelGroup channelGroup, final EventExecutorGroup executorGroup) {
96 | return new ChannelInitializer() {
97 | @Override
98 | protected void initChannel(SocketChannel ch) throws Exception {
99 | ChannelPipeline pipeline = ch.pipeline();
100 | ch.attr(ChannelAttrKeys.maxIdleTimeout).set(config.maxIdleTimeout());
101 | ch.attr(ChannelAttrKeys.channelGroup).set(channelGroup);
102 | ch.attr(ChannelAttrKeys.clientSide).set(true);
103 | ch.attr(OneTime.awaitNegotiate).set(new CountDownLatch(1));
104 | ch.attr(OneTime.channelConfig).set(config);
105 | // TODO should increase ioRatio when every ChannelHandler bind to executorGroup?
106 | pipeline.addLast(executorGroup,
107 | RemotingEncoder.INSTANCE,
108 | new RemotingDecoder(),
109 | new IdleStateHandler(config.idleTimeout(), 0, 0),
110 | HeartbeatChannelHandler.INSTANCE,
111 | NegotiateChannelHandler.INSTANCE,
112 | ConcreteRequestHandler.INSTANCE);
113 | }
114 | };
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/sailfish-kernel/src/main/java/sailfish/remoting/channel/SingleConnctionExchangeChannel.java:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * Copyright 2016-2016 spccold
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | *
17 | */
18 | package sailfish.remoting.channel;
19 |
20 | import java.util.Map;
21 | import java.util.concurrent.locks.LockSupport;
22 |
23 | import io.netty.bootstrap.Bootstrap;
24 | import io.netty.channel.Channel;
25 | import io.netty.handler.timeout.IdleStateHandler;
26 | import sailfish.remoting.ReconnectManager;
27 | import sailfish.remoting.Tracer;
28 | import sailfish.remoting.constants.ChannelAttrKeys.OneTime;
29 | import sailfish.remoting.exceptions.ExceptionCode;
30 | import sailfish.remoting.exceptions.SailfishException;
31 | import sailfish.remoting.handler.MsgHandler;
32 | import sailfish.remoting.protocol.Protocol;
33 | import sailfish.remoting.protocol.ResponseProtocol;
34 | import sailfish.remoting.utils.ChannelUtil;
35 | import sailfish.remoting.utils.CollectionUtils;
36 | import sailfish.remoting.utils.ParameterChecker;
37 |
38 | /**
39 | * @author spccold
40 | * @version $Id: SingleConnctionExchangeChannel.java, v 0.1 2016年11月21日 下午11:05:58 spccold Exp $
41 | */
42 | public abstract class SingleConnctionExchangeChannel extends AbstractExchangeChannel {
43 | private final Bootstrap reusedBootstrap;
44 |
45 | private volatile boolean reconnectting = false;
46 | private final int reconnectInterval;
47 |
48 | /**
49 | * Create a new instance
50 | *
51 | * @param parent
52 | * the {@link ExchangeChannelGroup} which is the parent of this instance and belongs
53 | * to it
54 | * @param connectTimeout
55 | * connect timeout in milliseconds
56 | * @param reconnectInterval
57 | * reconnect interval in milliseconds for {@link ReconnectManager}
58 | * @param idleTimeout
59 | * idle timeout in seconds for {@link IdleStateHandler}
60 | * @param maxIdleTimeOut
61 | * max idle timeout in seconds for {@link ChannelEventsHandler}
62 | * @param doConnect
63 | * connect to remote peer or not when initial
64 | */
65 | protected SingleConnctionExchangeChannel(Bootstrap bootstrap, ExchangeChannelGroup parent, int reconnectInterval,
66 | boolean doConnect) throws SailfishException {
67 | super(parent);
68 | this.reusedBootstrap = bootstrap;
69 | this.reconnectInterval = reconnectInterval;
70 | if (doConnect) {
71 | this.channel = doConnect();
72 | }
73 | }
74 |
75 | @SuppressWarnings("deprecation")
76 | @Override
77 | public Channel doConnect() throws SailfishException {
78 | try {
79 | Channel channel = reusedBootstrap.connect().syncUninterruptibly().channel();
80 | channel.attr(OneTime.awaitNegotiate).get().await();
81 | channel.attr(OneTime.awaitNegotiate).remove();
82 | return channel;
83 | } catch (Throwable cause) {
84 | throw new SailfishException(cause);
85 | }
86 | }
87 |
88 | @Override
89 | public void recover() {
90 | // add reconnect task
91 | ReconnectManager.INSTANCE.addReconnectTask(this, reconnectInterval);
92 | }
93 |
94 | @Override
95 | public Channel update(Channel newChannel) {
96 | synchronized (this) {
97 | this.reconnectting = false;
98 | if (this.isClosed()) {
99 | ChannelUtil.closeChannel(newChannel);
100 | return channel;
101 | }
102 | Channel old = channel;
103 | this.channel = newChannel;
104 | return old;
105 | }
106 | }
107 |
108 | @Override
109 | public boolean isAvailable() {
110 | boolean isAvailable = false;
111 | if (!reconnectting && !(isAvailable = super.isAvailable())) {
112 | synchronized (this) {
113 | if (!reconnectting && !(isAvailable = super.isAvailable())) {
114 | recover();
115 | this.reconnectting = true;
116 | }
117 | }
118 | }
119 | return isAvailable && super.isWritable();
120 | }
121 |
122 | @Override
123 | public void close(int timeout) {
124 | ParameterChecker.checkNotNegative(timeout, "timeout");
125 | if (this.isClosed()) {
126 | return;
127 | }
128 | synchronized (this) {
129 | if (this.isClosed()) {
130 | return;
131 | }
132 | this.closed = true;
133 | // deal unfinished requests, response with channel closed exception
134 | long start = System.currentTimeMillis();
135 | while (CollectionUtils.isNotEmpty(getTracer().peekPendingRequests(this))
136 | && (System.currentTimeMillis() - start < timeout)) {
137 | LockSupport.parkNanos(1000 * 1000 * 10L);
138 | }
139 | Map pendingRequests = getTracer().popPendingRequests(this);
140 | if (CollectionUtils.isNotEmpty(pendingRequests)) {
141 | for (Integer packetId : pendingRequests.keySet()) {
142 | getTracer().erase(ResponseProtocol.newErrorResponse(packetId,
143 | new SailfishException(ExceptionCode.UNFINISHED_REQUEST,
144 | "unfinished request because of channel:" + channel.toString() + " be closed")));
145 | }
146 | }
147 | ChannelUtil.closeChannel(channel);
148 | }
149 | }
150 |
151 | @Override
152 | public MsgHandler getMsgHander() {
153 | return parent().getMsgHander();
154 | }
155 |
156 | @Override
157 | public Tracer getTracer() {
158 | return parent().getTracer();
159 | }
160 | }
161 |
--------------------------------------------------------------------------------
/sailfish-kernel/src/test/java/sailfish/remoting/ProtocolTest.java:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * Copyright 2016-2016 spccold
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | *
17 | */
18 | package sailfish.remoting;
19 |
20 | import org.junit.Assert;
21 | import org.junit.Test;
22 |
23 | import io.netty.buffer.ByteBuf;
24 | import io.netty.buffer.ByteBufAllocator;
25 | import sailfish.remoting.constants.CompressType;
26 | import sailfish.remoting.constants.LangType;
27 | import sailfish.remoting.constants.RemotingConstants;
28 | import sailfish.remoting.constants.SerializeType;
29 | import sailfish.remoting.exceptions.SailfishException;
30 | import sailfish.remoting.protocol.RequestProtocol;
31 | import sailfish.remoting.protocol.ResponseProtocol;
32 |
33 | /**
34 | *
35 | * @author spccold
36 | * @version $Id: ProtocolTest.java, v 0.1 2016年11月3日 上午11:17:13 jileng Exp $
37 | */
38 | public class ProtocolTest {
39 |
40 | @Test
41 | public void testRequestProtocol() throws SailfishException{
42 | RequestProtocol send = RequestProtocol.newInstance();
43 | send.body(new byte[]{1,2,3,4});
44 | send.compressType(CompressType.NON_COMPRESS);
45 | send.heartbeat(false);
46 | send.langType(LangType.JAVA);
47 | send.oneway(false);
48 | send.opcode((short)1);
49 | send.packetId(1);
50 | send.serializeType(SerializeType.NON_SERIALIZE);
51 |
52 | ByteBuf output = ByteBufAllocator.DEFAULT.buffer(128);
53 | send.serialize(output);
54 |
55 | Assert.assertTrue(output.readShort() == RemotingConstants.SAILFISH_MAGIC);
56 | RequestProtocol receive = RequestProtocol.newInstance();
57 | Assert.assertTrue(send == receive);
58 |
59 | receive.deserialize(output, output.readInt());
60 | Assert.assertArrayEquals(send.body(), receive.body());
61 | Assert.assertTrue(receive.compressType() == CompressType.NON_COMPRESS);
62 | Assert.assertFalse(receive.heartbeat());
63 | Assert.assertTrue(receive.langType() == LangType.JAVA);
64 | Assert.assertFalse(receive.oneway());
65 | Assert.assertTrue(1 == receive.opcode());
66 | Assert.assertTrue(1 == receive.packetId());
67 | Assert.assertTrue(receive.serializeType() == SerializeType.NON_SERIALIZE);
68 |
69 |
70 | output.clear();
71 | send.body(new byte[]{-1, -1, -1, -1});
72 | send.heartbeat(true);
73 | send.oneway(true);
74 | send.langType(LangType.CPP);
75 | send.serializeType(SerializeType.PROTOBUF_SERIALIZE);
76 | send.compressType(CompressType.LZ4_COMPRESS);
77 | send.opcode((short)100);
78 | send.packetId(1000);
79 | send.serialize(output);
80 |
81 | Assert.assertTrue(output.readShort() == RemotingConstants.SAILFISH_MAGIC);
82 | receive = RequestProtocol.newInstance();
83 | Assert.assertTrue(send == receive);
84 |
85 | receive.deserialize(output, output.readInt());
86 | Assert.assertArrayEquals(send.body(), receive.body());
87 | Assert.assertTrue(receive.compressType() == CompressType.LZ4_COMPRESS);
88 | Assert.assertTrue(receive.heartbeat());
89 | Assert.assertTrue(receive.langType() == LangType.CPP);
90 | Assert.assertTrue(receive.oneway());
91 | Assert.assertTrue(100 == receive.opcode());
92 | Assert.assertTrue(1000 == receive.packetId());
93 | Assert.assertTrue(receive.serializeType() == SerializeType.PROTOBUF_SERIALIZE);
94 | }
95 |
96 | @Test
97 | public void testResponseProtocol() throws SailfishException{
98 | ResponseProtocol send = ResponseProtocol.newInstance();
99 | send.body(new byte[]{1,2,3,4});
100 | send.compressType(CompressType.GZIP_COMPRESS);
101 | send.heartbeat(false);
102 | send.packetId(1);
103 | send.result((byte)0);
104 | send.serializeType(SerializeType.JDK_SERIALIZE);
105 |
106 | ByteBuf output = ByteBufAllocator.DEFAULT.buffer(128);
107 | send.serialize(output);
108 |
109 | ResponseProtocol receive = ResponseProtocol.newInstance();
110 | Assert.assertTrue(send == receive);
111 | Assert.assertTrue(output.readShort() == RemotingConstants.SAILFISH_MAGIC);
112 | receive.deserialize(output, output.readInt());
113 | Assert.assertArrayEquals(send.body(), receive.body());
114 | Assert.assertTrue(send.compressType() == CompressType.GZIP_COMPRESS);
115 | Assert.assertTrue(send.serializeType() == SerializeType.JDK_SERIALIZE);
116 | Assert.assertFalse(receive.heartbeat());
117 | Assert.assertTrue(1 == receive.packetId());
118 | Assert.assertTrue(0 == receive.result());
119 |
120 |
121 | output.clear();
122 | send.heartbeat(true);
123 | send.serialize(output);
124 |
125 | Assert.assertTrue(output.readShort() == RemotingConstants.SAILFISH_MAGIC);
126 | receive = ResponseProtocol.newInstance();
127 | Assert.assertTrue(send == receive);
128 | receive.deserialize(output, output.readInt());
129 | Assert.assertTrue(receive.heartbeat());
130 | }
131 | }
132 |
133 |
--------------------------------------------------------------------------------
/sailfish-kernel/src/main/java/sailfish/remoting/channel/AbstractExchangeChannel.java:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * Copyright 2016-2016 spccold
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | *
17 | */
18 | package sailfish.remoting.channel;
19 |
20 | import java.net.SocketAddress;
21 | import java.util.UUID;
22 |
23 | import io.netty.channel.Channel;
24 | import io.netty.channel.ChannelFuture;
25 | import sailfish.remoting.RequestControl;
26 | import sailfish.remoting.ResponseCallback;
27 | import sailfish.remoting.exceptions.ExceptionCode;
28 | import sailfish.remoting.exceptions.SailfishException;
29 | import sailfish.remoting.future.BytesResponseFuture;
30 | import sailfish.remoting.future.ResponseFuture;
31 | import sailfish.remoting.protocol.RequestProtocol;
32 | import sailfish.remoting.protocol.ResponseProtocol;
33 |
34 | /**
35 | * @author spccold
36 | * @version $Id: AbstractExchangeChannel.java, v 0.1 2016年11月21日 下午10:49:12 spccold Exp $
37 | */
38 | public abstract class AbstractExchangeChannel implements ExchangeChannel {
39 | /** underlying channel */
40 | protected volatile Channel channel;
41 | protected volatile boolean closed = false;
42 |
43 | private final ExchangeChannelGroup parent;
44 |
45 | protected AbstractExchangeChannel(ExchangeChannelGroup parent) {
46 | this.parent = parent;
47 | }
48 |
49 | @Override
50 | public ExchangeChannelGroup parent() {
51 | return parent;
52 | }
53 |
54 | @Override
55 | public ExchangeChannel next() {
56 | return this;
57 | }
58 |
59 | @Override
60 | public UUID id() {
61 | return null;
62 | }
63 |
64 | @Override
65 | public SocketAddress localAddress() {
66 | if (null == channel) {
67 | return null;
68 | }
69 | return channel.localAddress();
70 | }
71 |
72 | @Override
73 | public SocketAddress remoteAdress() {
74 | if (null == channel) {
75 | return null;
76 | }
77 | return channel.remoteAddress();
78 | }
79 |
80 | @Override
81 | public boolean isAvailable() {
82 | return null != channel && channel.isOpen() && channel.isActive();
83 | }
84 |
85 | protected boolean isWritable(){
86 | return (null != channel && channel.isWritable());
87 | }
88 |
89 | @Override
90 | public void close() {
91 | close(0);
92 | }
93 |
94 | @Override
95 | public boolean isClosed() {
96 | return closed;
97 | }
98 |
99 | @Override
100 | public void oneway(byte[] data, RequestControl requestControl) throws SailfishException {
101 | RequestProtocol protocol = RequestProtocol.newRequest(requestControl);
102 | protocol.oneway(true);
103 | protocol.body(data);
104 |
105 | if(requestControl.preferHighPerformanceWriter()){
106 | HighPerformanceChannelWriter.write(channel, protocol);
107 | return;
108 | }
109 |
110 | if (requestControl.sent() && requestControl.timeout() > 0) {
111 | ChannelFuture future = channel.writeAndFlush(protocol);
112 | waitWriteDone(future, requestControl.timeout(), protocol, false);
113 | return;
114 | }
115 | // reduce memory consumption
116 | channel.writeAndFlush(protocol, channel.voidPromise());
117 | }
118 |
119 | @Override
120 | public ResponseFuture request(byte[] data, RequestControl requestControl) throws SailfishException {
121 | return requestWithFuture(data, null, requestControl);
122 | }
123 |
124 | @Override
125 | public void request(byte[] data, ResponseCallback callback, RequestControl requestControl)
126 | throws SailfishException {
127 | requestWithFuture(data, callback, requestControl);
128 | }
129 |
130 | @Override
131 | public void response(ResponseProtocol response) throws SailfishException {
132 | channel.writeAndFlush(response, channel.voidPromise());
133 | }
134 |
135 | private ResponseFuture requestWithFuture(byte[] data, ResponseCallback callback,
136 | RequestControl requestControl) throws SailfishException {
137 | final RequestProtocol protocol = RequestProtocol.newRequest(requestControl);
138 | protocol.oneway(false);
139 | protocol.body(data);
140 |
141 | ResponseFuture respFuture = new BytesResponseFuture(protocol.packetId(), getTracer());
142 | respFuture.setCallback(callback, requestControl.timeout());
143 | // trace before write
144 | getTracer().trace(this, protocol.packetId(), respFuture);
145 |
146 | if(requestControl.preferHighPerformanceWriter()){
147 | HighPerformanceChannelWriter.write(channel, protocol);
148 | return respFuture;
149 | }
150 |
151 | if (requestControl.sent()) {
152 | ChannelFuture future = channel.writeAndFlush(protocol);
153 | waitWriteDone(future, requestControl.timeout(), protocol, true);
154 | return respFuture;
155 | }
156 |
157 | channel.writeAndFlush(protocol, channel.voidPromise());
158 | return respFuture;
159 | }
160 |
161 | private void waitWriteDone(ChannelFuture future, int timeout, RequestProtocol request, boolean needRemoveTrace)
162 | throws SailfishException {
163 | boolean done = future.awaitUninterruptibly(timeout);
164 | if (!done) {
165 | // useless at most of time when do writeAndFlush(...) invoke
166 | future.cancel(true);
167 | if (needRemoveTrace) {
168 | getTracer().remove(request.packetId());
169 | }
170 | throw new SailfishException(ExceptionCode.WRITE_TIMEOUT,
171 | String.format("write to remote[%s] timeout, protocol[%s]", channel.remoteAddress(), request));
172 | }
173 | if (!future.isSuccess()) {
174 | if (needRemoveTrace) {
175 | getTracer().remove(request.packetId());
176 | }
177 | throw new SailfishException(ExceptionCode.CHANNEL_WRITE_FAIL,
178 | String.format("write to remote[%s] fail, protocol[%s]", channel.remoteAddress(), request),
179 | future.cause());
180 | }
181 | }
182 | }
183 |
--------------------------------------------------------------------------------
/sailfish-kernel/src/main/java/sailfish/remoting/DefaultServer.java:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * Copyright 2016-2016 spccold
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | *
17 | */
18 | package sailfish.remoting;
19 |
20 | import java.util.Collection;
21 |
22 | import io.netty.bootstrap.ServerBootstrap;
23 | import io.netty.buffer.PooledByteBufAllocator;
24 | import io.netty.channel.Channel;
25 | import io.netty.channel.ChannelInitializer;
26 | import io.netty.channel.ChannelOption;
27 | import io.netty.channel.ChannelPipeline;
28 | import io.netty.channel.EventLoopGroup;
29 | import io.netty.channel.WriteBufferWaterMark;
30 | import io.netty.channel.socket.SocketChannel;
31 | import io.netty.handler.timeout.IdleStateHandler;
32 | import io.netty.util.concurrent.DefaultThreadFactory;
33 | import io.netty.util.concurrent.EventExecutorGroup;
34 | import sailfish.remoting.channel.ExchangeChannelGroup;
35 | import sailfish.remoting.codec.RemotingDecoder;
36 | import sailfish.remoting.codec.RemotingEncoder;
37 | import sailfish.remoting.configuration.ExchangeServerConfig;
38 | import sailfish.remoting.constants.ChannelAttrKeys;
39 | import sailfish.remoting.constants.RemotingConstants;
40 | import sailfish.remoting.eventgroup.ServerEventGroup;
41 | import sailfish.remoting.exceptions.SailfishException;
42 | import sailfish.remoting.handler.DefaultMsgHandler;
43 | import sailfish.remoting.handler.HeartbeatChannelHandler;
44 | import sailfish.remoting.handler.MsgHandler;
45 | import sailfish.remoting.handler.NegotiateChannelHandler;
46 | import sailfish.remoting.handler.ConcreteRequestHandler;
47 | import sailfish.remoting.protocol.Protocol;
48 | import sailfish.remoting.utils.ChannelUtil;
49 | import sailfish.remoting.utils.ParameterChecker;
50 |
51 | /**
52 | *
53 | * @author spccold
54 | * @version $Id: ExchangeServer.java, v 0.1 2016年10月26日 下午3:52:19 jileng Exp $
55 | */
56 | public class DefaultServer implements Server {
57 |
58 | private volatile boolean isClosed = false;
59 | private final ExchangeServerConfig config;
60 | private final MsgHandler msgHandler;
61 | private Channel channel;
62 |
63 | public DefaultServer(ExchangeServerConfig config) {
64 | this.config = ParameterChecker.checkNotNull(config, "ExchangeServerConfig");
65 | this.msgHandler = new DefaultMsgHandler(config.getRequestProcessors());
66 | }
67 |
68 | public void start() throws SailfishException {
69 | ServerBootstrap boot = newServerBootstrap();
70 | EventLoopGroup accept = NettyPlatformIndependent.newEventLoopGroup(1,
71 | new DefaultThreadFactory(RemotingConstants.SERVER_ACCEPT_THREADNAME));
72 | if (null != config.getEventLoopGroup()) {
73 | boot.group(accept, config.getEventLoopGroup());
74 | } else {
75 | boot.group(accept, ServerEventGroup.INSTANCE.getLoopGroup());
76 | }
77 | final EventExecutorGroup executor = (null != config.getEventExecutorGroup() ? config.getEventExecutorGroup()
78 | : ServerEventGroup.INSTANCE.getExecutorGroup());
79 | boot.localAddress(config.address().host(), config.address().port());
80 | boot.childHandler(new ChannelInitializer() {
81 | @Override
82 | protected void initChannel(SocketChannel ch) throws Exception {
83 | ChannelPipeline pipeline = ch.pipeline();
84 | ch.attr(ChannelAttrKeys.OneTime.idleTimeout).set(config.idleTimeout());
85 | ch.attr(ChannelAttrKeys.maxIdleTimeout).set(config.maxIdleTimeout());
86 | ch.attr(ChannelAttrKeys.exchangeServer).set(DefaultServer.this);
87 | pipeline.addLast(executor,
88 | RemotingEncoder.INSTANCE,
89 | new RemotingDecoder(),
90 | new IdleStateHandler(config.idleTimeout(), 0, 0),
91 | HeartbeatChannelHandler.INSTANCE,
92 | NegotiateChannelHandler.INSTANCE,
93 | ConcreteRequestHandler.INSTANCE);
94 | }
95 | });
96 | try {
97 | channel = boot.bind().syncUninterruptibly().channel();
98 | } catch (Throwable cause) {
99 | throw new SailfishException(cause);
100 | }
101 | }
102 |
103 | private ServerBootstrap newServerBootstrap() {
104 | ServerBootstrap serverBoot = new ServerBootstrap();
105 | serverBoot.channel(NettyPlatformIndependent.serverChannelClass());
106 | // connections wait for accept
107 | serverBoot.option(ChannelOption.SO_BACKLOG, 1024);
108 | serverBoot.option(ChannelOption.SO_REUSEADDR, true);
109 | // replace by heart beat
110 | serverBoot.childOption(ChannelOption.SO_KEEPALIVE, false);
111 | serverBoot.childOption(ChannelOption.TCP_NODELAY, true);
112 | serverBoot.childOption(ChannelOption.SO_SNDBUF, 32 * 1024);
113 | serverBoot.childOption(ChannelOption.SO_RCVBUF, 32 * 1024);
114 | // temporary settings, need more tests
115 | serverBoot.childOption(ChannelOption.WRITE_BUFFER_WATER_MARK, new WriteBufferWaterMark(8 * 1024, 32 * 1024));
116 | serverBoot.childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);
117 | //default is true, reduce thread context switching
118 | serverBoot.childOption(ChannelOption.SINGLE_EVENTEXECUTOR_PER_GROUP, true);
119 | return serverBoot;
120 | }
121 |
122 | @Override
123 | public void close() {
124 | close(0);
125 | }
126 |
127 | @Override
128 | public void close(int timeout) {
129 | if(isClosed()){
130 | return;
131 | }
132 | synchronized (this) {
133 | if (isClosed())
134 | return;
135 | ChannelUtil.closeChannel(channel);
136 | }
137 | }
138 |
139 | @Override
140 | public boolean isClosed() {
141 | return this.isClosed;
142 | }
143 |
144 | public MsgHandler getMsgHandler() {
145 | return msgHandler;
146 | }
147 |
148 | @Override
149 | public Collection listChannelGroups() {
150 | return NegotiateChannelHandler.uuid2ChannelGroup.values();
151 | }
152 | }
153 |
--------------------------------------------------------------------------------
/sailfish-kernel/src/test/java/sailfish/remoting/ExchangeChannelChooserTest.java:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * Copyright 2016-2016 spccold
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | *
17 | */
18 | package sailfish.remoting;
19 |
20 | import org.junit.Assert;
21 | import org.junit.Test;
22 |
23 | import sailfish.remoting.channel.DefaultExchangeChannelChooserFactory;
24 | import sailfish.remoting.channel.EmptyExchangeChannel;
25 | import sailfish.remoting.channel.ExchangeChannelChooserFactory;
26 | import sailfish.remoting.exceptions.SailfishException;
27 |
28 | /**
29 | * @author spccold
30 | * @version $Id: ExchangeChannelChooserTest.java, v 0.1 2016年11月25日 下午8:35:52 spccold Exp $
31 | */
32 | public class ExchangeChannelChooserTest {
33 |
34 | @Test
35 | public void testPowerOfTwo() throws Exception{
36 | int connections = 1;
37 |
38 | //test one connection
39 | MockExchangeChannel[] channels = new MockExchangeChannel[connections];
40 | initMockExchangeChannelArray(channels);
41 | MockExchangeChannel[] deadChannels = new MockExchangeChannel[connections];
42 | ExchangeChannelChooserFactory.ExchangeChannelChooser chooser = DefaultExchangeChannelChooserFactory.INSTANCE.newChooser(channels, deadChannels);
43 | Assert.assertNotNull(chooser.next());
44 |
45 | channels[0].setAvailable(false);
46 | try{
47 | chooser.next();
48 | Assert.assertFalse(true);
49 | }catch(SailfishException cause){
50 | Assert.assertTrue(true);
51 | }
52 |
53 | //test two connections
54 | connections = 2;
55 | channels = new MockExchangeChannel[connections];
56 | initMockExchangeChannelArray(channels);
57 | deadChannels = new MockExchangeChannel[connections];
58 | chooser = DefaultExchangeChannelChooserFactory.INSTANCE.newChooser(channels, deadChannels);
59 |
60 | //try three times(greater than connections)
61 | MockExchangeChannel mock = (MockExchangeChannel)chooser.next();
62 | Assert.assertNotNull(mock);
63 | Assert.assertTrue(mock.index() == 0);
64 |
65 | mock = (MockExchangeChannel)chooser.next();
66 | Assert.assertNotNull(mock);
67 | Assert.assertTrue(mock.index() == 1);
68 |
69 | mock = (MockExchangeChannel)chooser.next();
70 | Assert.assertNotNull(mock);
71 | Assert.assertTrue(mock.index() == 0);
72 |
73 | mock = (MockExchangeChannel)chooser.next();
74 | Assert.assertNotNull(mock);
75 | Assert.assertTrue(mock.index() == 1);
76 |
77 | //try to let MockExchangeChannel(index:0) unavailable
78 | channels[0].setAvailable(false);
79 |
80 | mock = (MockExchangeChannel)chooser.next();
81 | Assert.assertNotNull(mock);
82 | Assert.assertTrue(mock.index() == 1);
83 |
84 | //try to let MockExchangeChannel(index:0) available, MockExchangeChannel(index:1) unavailable
85 | channels[0].setAvailable(true);
86 | channels[1].setAvailable(false);
87 |
88 | mock = (MockExchangeChannel)chooser.next();
89 | Assert.assertNotNull(mock);
90 | Assert.assertTrue(mock.index() == 0);
91 |
92 | //try to let all channels unavailable
93 | channels[0].setAvailable(false);
94 | try{
95 | chooser.next();
96 | Assert.assertFalse(true);
97 | }catch(SailfishException cause){
98 | Assert.assertTrue(true);
99 | }
100 |
101 | try{
102 | chooser.next();
103 | Assert.assertFalse(true);
104 | }catch(SailfishException cause){
105 | Assert.assertTrue(true);
106 | }
107 | }
108 |
109 | @Test
110 | public void testGeneric() throws Exception{
111 | int connections =3;
112 | //test one connection
113 | MockExchangeChannel[] channels = new MockExchangeChannel[connections];
114 | initMockExchangeChannelArray(channels);
115 | MockExchangeChannel[] deadChannels = new MockExchangeChannel[connections];
116 | ExchangeChannelChooserFactory.ExchangeChannelChooser chooser = DefaultExchangeChannelChooserFactory.INSTANCE.newChooser(channels, deadChannels);
117 |
118 | MockExchangeChannel mock = (MockExchangeChannel)chooser.next();
119 | Assert.assertNotNull(mock);
120 | Assert.assertTrue(mock.index() == 0);
121 |
122 | mock = (MockExchangeChannel)chooser.next();
123 | Assert.assertNotNull(mock);
124 | Assert.assertTrue(mock.index() == 1);
125 |
126 | mock = (MockExchangeChannel)chooser.next();
127 | Assert.assertNotNull(mock);
128 | Assert.assertTrue(mock.index() == 2);
129 |
130 | mock = (MockExchangeChannel)chooser.next();
131 | Assert.assertNotNull(mock);
132 | Assert.assertTrue(mock.index() == 0);
133 |
134 | // let index:1, index:2 unavailable
135 | channels[1].setAvailable(false);
136 | channels[2].setAvailable(false);
137 |
138 | mock = (MockExchangeChannel)chooser.next();
139 | Assert.assertNotNull(mock);
140 | Assert.assertTrue(mock.index() == 0);
141 |
142 | //let index:0 unavailable
143 | channels[0].setAvailable(false);
144 | try{
145 | chooser.next();
146 | Assert.assertFalse(true);
147 | }catch(SailfishException cause){
148 | Assert.assertTrue(true);
149 | }
150 |
151 | //let index:2 available
152 | channels[2].setAvailable(true);
153 | Assert.assertNotNull(chooser.next());
154 | }
155 |
156 | private void initMockExchangeChannelArray(MockExchangeChannel[] channels){
157 | if(null == channels){
158 | throw new NullPointerException("channels");
159 | }
160 | for(int i = 0; i< channels.length; i++){
161 | channels[i] = new MockExchangeChannel(i);
162 | }
163 | }
164 |
165 | private static class MockExchangeChannel extends EmptyExchangeChannel{
166 | private int index;
167 | private boolean isAvailable = true;
168 | public MockExchangeChannel(int index) {
169 | this.index = index;
170 | }
171 |
172 | public int index(){
173 | return index;
174 | }
175 |
176 | public void setAvailable(boolean isAvailable){
177 | this.isAvailable = isAvailable;
178 | }
179 |
180 | @Override
181 | public boolean isAvailable() {
182 | return this.isAvailable;
183 | }
184 | }
185 | }
--------------------------------------------------------------------------------