使用 redisson 的应用程序运行时出错,报错信息:
Exception in thread "main" org.redisson.client.RedisConnectionException: At least two sentinels should be defined in Redis configuration! SENTINEL SENTINELS command returns empty result!
at org.redisson.connection.SentinelConnectionManager.<init>(SentinelConnectionManager.java:188)
at org.redisson.config.ConfigSupport.createConnectionManager(ConfigSupport.java:197)
at org.redisson.Redisson.<init>(Redisson.java:120)
at org.redisson.Redisson.create(Redisson.java:160)
at com.ericsson.jee.iam.common.redisson.util.RedissonFactory.initRedissonObject(RedissonFactory.java:27)
at test.Test.main(Test.java:17)
连接使用的 URL:sentinel://redis:26379?. . .
你可能会觉得奇怪,报错明明说了至少需要2个 sentinel,但是 URL 却只写了一个 (redis:26379),是不是这里出的问题?
其实不然,redisson 在启动的时候,会通过 URL 中配置的 sentinel 信息,访问 sentinel 节点,然后从 sentinel 节点获取同一集群中其他 sentinel 节点的信息,因此 URL 中只需要一个 sentinel 的信息就可以了。很多时候我们在 URL 中填写多个 sentinel 的连接信息,是出于容错的考虑,如果使用第一个 sentinel 的信息连接不上,那就使用第二个,依次类推。
从 URL 中可以看出,sentinel 连接信息使用的是域名 redis,而不是 IP 地址,因此需要先确认 DNS 的可用。本次实验环境在本地安装了 bind9 用以提供 DNS 服务。
helowken@helowken-mint ~ $ ping redis
PING redis.ostechnix.lan (127.0.0.1) 56(84) bytes of data.
64 bytes from localhost (127.0.0.1): icmp_seq=1 ttl=64 time=0.016 ms
64 bytes from localhost (127.0.0.1): icmp_seq=2 ttl=64 time=0.031 ms
64 bytes from localhost (127.0.0.1): icmp_seq=3 ttl=64 time=0.031 ms
/etc/resolv.conf 内容:
helowken@helowken-mint ~ $ cat /etc/resolv.conf
nameserver 127.0.0.1
nameserver 202.96.128.166
nameserver 202.96.134.133
nameserver 163.15.15.15
search xxx.yyy ostechnix.lan
nameserver 表示 DNS 服务器地址:
search 表示自动添加到域名后面的搜索域:
使用 nslookup 可以再次确认 DNS 的可用:
helowken@helowken-mint ~ $ nslookup redis 127.0.0.1
Server: 127.0.0.1
Address: 127.0.0.1#53
Name: redis.ostechnix.lan
Address: 127.0.0.1
使用 dig 查询完整的 DNS 记录信息
helowken@helowken-mint ~ $ dig @127.0.0.1 redis.ostechnix.lan
; <<>> DiG 9.10.3-P4-Ubuntu <<>> @127.0.0.1 redis.ostechnix.lan
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 5064
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 2, ADDITIONAL: 3
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;redis.ostechnix.lan. IN A
;; ANSWER SECTION:
redis.ostechnix.lan. 86400 IN A 127.0.0.1
;; AUTHORITY SECTION:
ostechnix.lan. 86400 IN NS pri.ostechnix.lan.
ostechnix.lan. 86400 IN NS sec.ostechnix.lan.
;; ADDITIONAL SECTION:
pri.ostechnix.lan. 86400 IN A 192.168.1.200
sec.ostechnix.lan. 86400 IN A 192.168.1.201
;; Query time: 0 msec
;; SERVER: 127.0.0.1#53(127.0.0.1)
;; WHEN: Sat Nov 02 02:28:49 CST 2019
;; MSG SIZE rcvd: 132
从 dig 的输出中可以看到返回了地址记录:
;; ANSWER SECTION:
redis.ostechnix.lan. 86400 IN A 127.0.0.1
综上所述,可以得知:
使用 redis-cli 确认 sentinel 是可以连接的:
helowken@helowken-mint ~/redis-2.8.21 $ src/redis-cli -h redis -p 26379
redis:26379> sentinel get-master-addr-by-name mymaster
1) "127.0.0.1"
2) "6379"
接下来使用 dig 尝试往 “202.96.128.166” 发送查询请求:
helowken@helowken-mint ~ $ dig @202.96.128.166 redis.ostechnix.lan
; <<>> DiG 9.10.3-P4-Ubuntu <<>> @202.96.128.166 redis.ostechnix.lan
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NXDOMAIN, id: 18859
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 1, ADDITIONAL: 0
;; QUESTION SECTION:
;redis.ostechnix.lan. IN A
;; AUTHORITY SECTION:
. 1800 IN SOA a.root-servers.net. nstld.verisign-grs.com. 2019110101 1800 900 604800 86400
;; Query time: 8 msec
;; SERVER: 202.96.128.166#53(202.96.128.166)
;; WHEN: Sat Nov 02 02:40:34 CST 2019
;; MSG SIZE rcvd: 112
没有看到 “ANSWER SECTION”,也就是说没有查找到地址记录。(202.96.134.133 结果类似)
再尝试 dig “163.15.15.15”:
helowken@helowken-mint ~ $ dig @163.15.15.15 redis.ostechnix.lan
; <<>> DiG 9.10.3-P4-Ubuntu <<>> @163.15.15.15 redis.ostechnix.lan
; (1 server found)
;; global options: +cmd
;; connection timed out; no servers could be reached
发现连接超时。
如果依次往所有 nameserver 查询 “redis.xxx.yyy”,会发现:
因为源码内容较多,这里直接给出主要的逻辑:
为了更容易和准确地得到实验结果,我稍微更改了一下 netty 的源码,调整的内容有:
本实验环节为了简单,不配置 CNAME 记录。
nameserver 127.0.0.1
nameserver 202.96.128.166
nameserver 202.96.134.133
nameserver 163.15.15.15
search xxx.yyy ostechnix.lan
allowedQueries | Result |
---|---|
1 ~ 3 | Pass |
>= 4 | Failed |
结果分析:
nameserver 127.0.0.1
nameserver 202.96.128.166
nameserver 202.96.134.133
nameserver 163.15.15.15
search ostechnix.lan xxx.yyy
allowedQueries | Result |
---|---|
N (N >= 1) | Pass |
结果分析:
nameserver 202.96.128.166
nameserver 202.96.134.133
nameserver 163.15.15.15
nameserver 127.0.0.1
search xxx.yyy ostechnix.lan
allowedQueries | Result |
---|---|
1 ~ 3 | Failed |
4 ~ 6 | Pass |
7 | Failed |
>= 8 | Pass |
结果分析:
PS:你还可以进行更多的实验,根据上面的逻辑进行分析。
为了保证 Netty 查找 DNS 能成功,最好遵循以下准则:
PS:
查找 DNS:
io.netty.resolver.dns.DnsResolveContext
设置查询超时和 allowedQueries:io.netty.resolver.dns.DnsNameResolverBuilder
以下是模拟 Netty 发送 DNS 请求的代码,根据 Netty 的源码得来。
package io.netty.handler.codec.dns;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.PooledByteBufAllocator;
import io.netty.util.internal.PlatformDependent;
import io.netty.util.internal.StringUtil;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.InetSocketAddress;
public class DnsClient {
private static final InetSocketAddress address = new InetSocketAddress("localhost", 53);
private static final DnsRecordDecoder recordDecoder = DnsRecordDecoder.DEFAULT;
private static final int BUF_LENGTH = 1024;
public static void main(String[] args) throws Exception {
int id = PlatformDependent.threadLocalRandom().nextInt(65536 - 1) + 1;
DnsQuestion question = new DefaultDnsQuestion(
"redis.ostechnix.lan.",
new DnsRecordType(1, "A")
);
DnsQuery query = new DatagramDnsQuery(null, address, id);
query.setRecursionDesired(true);
query.addRecord(DnsSection.QUESTION, question);
DnsRecord optResource = new AbstractDnsOptPseudoRrRecord(4096, 0, 0) {
};
query.addRecord(DnsSection.ADDITIONAL, optResource);
ByteBuf buf = new PooledByteBufAllocator().ioBuffer(BUF_LENGTH);
new DnsQueryEncoder(DnsRecordEncoder.DEFAULT).encode(query, buf);
byte[] bs = new byte[buf.readableBytes()];
buf.getBytes(0, bs);
DnsQuery dnsQuery = sendAndReceive(bs);
print(dnsQuery);
}
private static void print(DnsQuery msg) {
System.out.println(msg);
System.out.println("===========================");
StringBuilder sb = new StringBuilder();
printSection(sb, msg, DnsSection.ADDITIONAL);
printSection(sb, msg, DnsSection.ANSWER);
System.out.println(sb);
}
private static void printSection(StringBuilder sb, DnsQuery message, DnsSection section) {
final int count = message.count(section);
for (int i = 0; i < count; i++) {
DnsRecord record = message.recordAt(section, i);
if (record instanceof DefaultDnsRawRecord)
printRecord((DefaultDnsRawRecord) record, sb);
}
}
private static void printRecord(DefaultDnsRawRecord record, StringBuilder buf) {
final DnsRecordType type = record.type();
if (type == DnsRecordType.OPT)
return;
buf.append(StringUtil.NEWLINE).append(StringUtil.TAB);
buf.append(record.name().isEmpty() ? "" : record.name()).append(" => ");
ByteBuf byteBuf = record.content();
if (type.intValue() == DnsRecordType.A.intValue()) {
byte[] bs = new byte[byteBuf.readableBytes()];
byteBuf.readBytes(bs);
try {
buf.append(
InetAddress.getByAddress(bs)
);
} catch (Exception e) {
e.printStackTrace();
}
} else {
buf.append(byteBuf.readableBytes()).append("B)");
}
}
private static DnsQuery sendAndReceive(byte[] bs) throws Exception {
DatagramSocket socket = new DatagramSocket();
DatagramPacket packet = new DatagramPacket(bs, bs.length, address);
socket.send(packet);
packet = new DatagramPacket(new byte[BUF_LENGTH], BUF_LENGTH);
socket.receive(packet);
ByteBuf buf = new PooledByteBufAllocator().ioBuffer(BUF_LENGTH);
buf.writeBytes(packet.getData(), 0, packet.getLength());
return decode(buf);
}
private static DnsQuery decode(ByteBuf buf) throws Exception {
final DnsQuery query = newQuery(buf);
final int questionCount = buf.readUnsignedShort();
final int answerCount = buf.readUnsignedShort();
final int authorityRecordCount = buf.readUnsignedShort();
final int additionalRecordCount = buf.readUnsignedShort();
decodeQuestions(query, buf, questionCount);
decodeRecords(query, DnsSection.ANSWER, buf, answerCount);
decodeRecords(query, DnsSection.AUTHORITY, buf, authorityRecordCount);
decodeRecords(query, DnsSection.ADDITIONAL, buf, additionalRecordCount);
return query;
}
private static DnsQuery newQuery(ByteBuf buf) {
final int id = buf.readUnsignedShort();
final int flags = buf.readUnsignedShort();
// if (flags >> 15 == 1) {
// throw new CorruptedFrameException("not a query");
// }
final DnsQuery query =
new DatagramDnsQuery(
null,
address,
id,
DnsOpCode.valueOf((byte) (flags >> 11 & 0xf)));
query.setRecursionDesired((flags >> 8 & 1) == 1);
query.setZ(flags >> 4 & 0x7);
return query;
}
private static void decodeQuestions(DnsQuery query, ByteBuf buf, int questionCount) throws Exception {
for (int i = questionCount; i > 0; i--) {
query.addRecord(DnsSection.QUESTION, recordDecoder.decodeQuestion(buf));
}
}
private static void decodeRecords(
DnsQuery query, DnsSection section, ByteBuf buf, int count) throws Exception {
for (int i = count; i > 0; i--) {
final DnsRecord r = recordDecoder.decodeRecord(buf);
if (r == null) {
break;
}
query.addRecord(section, r);
}
}
}
以下是 本实验中 bind9 的 DNS 配置文件:
helowken@helowken-mint ~ $ cat /etc/bind/for.ostechnix.lan
$TTL 86400
@ IN SOA pri.ostechnix.lan. root.ostechnix.lan. (
2011071001 ;Serial
3600 ;Refresh
1800 ;Retry
604800 ;Expire
86400 ;Minimum TTL
)
$ORIGIN ostechnix.lan.
@ IN NS pri.ostechnix.lan.
@ IN NS sec.ostechnix.lan.
@ IN A 192.168.1.200
@ IN A 192.168.1.201
@ IN A 192.168.1.202
pri IN A 192.168.1.200
sec IN A 192.168.1.201
client IN A 192.168.1.202
redis IN A 127.0.0.1
redis IN AAAA ::1