SpringBoot2.x+Dubbo2.7.3+Redis实现分布式开发
这个这个由于公司经费不够,没有放Zookeeper的服务器,所以这次研究一下这个冷门。使用Redis作为注册中心来实现分布式开发。
1.dependency配置
有关Dubbo 的配置
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>2.7.3</version>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-registry-redis</artifactId>
<version>2.7.3</version>
</dependency>
2.applications.properties
因为redis的0库和1库都被占用了,所以这里使用2库。
#dubbo.registry.address格式:redis://username:password@ip:port?db.index:指定数据库,&...一些别的参数
dubbo.application.name=dubbo-redis-server
dubbo.scan.base-packages=com.ld.service
dubbo.registry.id=dubbo-registry-test
dubbo.registry.address=redis://username:password@127.0.0.1:6379?db.index=2
dubbo.metadata-report.address=redis://username:password@127.0.0.1:6379?db.index=2
dubbo.provider.timeout=15000
dubbo.protocol.name=dubbo
dubbo.protocol.port=20880
3.demo
实现就和zookeeper作为注册中心的使用一样了。
interface:
public interface TestService {
public String test();
}
impl:
import com.ld.service.TestService;
import org.apache.dubbo.config.annotation.Service;
@Service(version = "1.0")
public class TestServiceImpl implements TestService {
@Override
public String test() {
return "hello world.";
}
}
test:
import com.ld.service.TestService;
import org.apache.dubbo.config.ApplicationConfig;
import org.apache.dubbo.config.ReferenceConfig;
import org.apache.dubbo.config.RegistryConfig;
import org.junit.jupiter.api.Test;
public class DubboTest {
@Test
public void test(){
ApplicationConfig application = new ApplicationConfig();
application.setName("yyy");
// 连接注册中心配置
RegistryConfig registry = new RegistryConfig();
// registry.setAddress("127.0.0.1:2182");
registry.setAddress("redis://username:password@127.0.0.1:6379?db.index=2");
// registry.setUsername("aaa");
// registry.setPassword("bbb");
// 注意:ReferenceConfig为重对象,内部封装了与注册中心的连接,以及与服务提供方的连接
// 引用远程服务
ReferenceConfig<TestService> reference = new ReferenceConfig<>(); // 此实例很重,封装了与注册中心的连接以及与提供者的连接,请自行缓存,否则可能造成内存和连接泄漏
reference.setApplication(application);
reference.setRegistry(registry); // 多个注册中心可以用setRegistries()
reference.setInterface(TestService.class);
reference.setVersion("1.0");
TestService testService = reference.get();
String aaa = testService.test();
System.out.println(aaa);
}
}
4. 遇到的坑比问题
问题1:dubbo.registry.address和dubbo.metadata-report.address这两个的值不知道怎么写了?
因为官网上也没有给个比较全的地址格式,所以只能靠自己琢磨了。因为它是使用org.apache.dubbo.common.URL来解析url 的,所以我们就去看看这个类,然后找到类这段代码:
public static URL valueOf(String url) {
if (url != null && (url = url.trim()).length() != 0) {
String protocol = null;
String username = null;
String password = null;
String host = null;
int port = 0;
String path = null;
Map<String, String> parameters = null;
int i = url.indexOf("?");
if (i >= 0) {
String[] parts = url.substring(i + 1).split("&");
parameters = new HashMap();
String[] var10 = parts;
int var11 = parts.length;
for(int var12 = 0; var12 < var11; ++var12) {
String part = var10[var12];
part = part.trim();
if (part.length() > 0) {
int j = part.indexOf(61);
if (j >= 0) {
parameters.put(part.substring(0, j), part.substring(j + 1));
} else {
parameters.put(part, part);
}
}
}
url = url.substring(0, i);
}
i = url.indexOf("://");
if (i >= 0) {
if (i == 0) {
throw new IllegalStateException("url missing protocol: \"" + url + "\"");
}
protocol = url.substring(0, i);
url = url.substring(i + 3);
} else {
i = url.indexOf(":/");
if (i >= 0) {
if (i == 0) {
throw new IllegalStateException("url missing protocol: \"" + url + "\"");
}
protocol = url.substring(0, i);
url = url.substring(i + 1);
}
}
i = url.indexOf("/");
if (i >= 0) {
path = url.substring(i + 1);
url = url.substring(0, i);
}
i = url.lastIndexOf("@");
if (i >= 0) {
username = url.substring(0, i);
int j = username.indexOf(":");
if (j >= 0) {
password = username.substring(j + 1);
username = username.substring(0, j);
}
url = url.substring(i + 1);
}
i = url.lastIndexOf(":");
if (i >= 0 && i < url.length() - 1 && url.lastIndexOf("%") <= i) {
port = Integer.parseInt(url.substring(i + 1));
url = url.substring(0, i);
}
if (url.length() > 0) {
host = url;
}
return new URL(protocol, username, password, host, port, path, parameters);
} else {
throw new IllegalArgumentException("url == null");
}
}
代码很长,但是是主要的,所以我们得好好研究一下,最后琢磨出来一个地址:redis://username:password@host:port,虽然不知道能不能用,但是最起码有个地址。
问题2:redis只有密码没有用户名,不写用户用就报错Invalid url, password without?
如果redis没有密码,那配置和zookeeper一样,但是有了密码就不行了,因为redis访问要密码啊,那就开始配置密码,配置完成之后会发现启动报错了,
IllegalArgumentException:Invalid url, password without username!
因为它解析url使用的是URL类,
去查看源码发现,在URL类中有这么个判断
如果设置可密码,肯定有用户名非空的校验,那我们就自己瞎写i一个用户名,然后竟然就给通过了。。。
问题3: 因为公司redis的0库已经再用了,所以这个dubbo服务我想注册到别的库里面,但是参数如何配置?
查看redis注册类org.apache.dubbo.registry.redis.RedisRegistry。进去就看到了这么个构造函数:
public RedisRegistry(URL url) {
super(url);
if (url.isAnyHost()) {
throw new IllegalStateException("registry address == null");
} else {
GenericObjectPoolConfig config = new GenericObjectPoolConfig();
。。。
String address;
String host;
int port;
for(Iterator var6 = addresses.iterator(); var6.hasNext(); this.jedisPools.put(address, new JedisPool(config, host, port, url.getParameter("timeout", 1000), StringUtils.isEmpty(url.getPassword()) ? null : url.getPassword(), url.getParameter("db.index", 0)))) {
address = (String)var6.next();
int i = address.indexOf(58);
if (i > 0) {
host = address.substring(0, i);
port = Integer.parseInt(address.substring(i + 1));
} else {
host = address;
port = 6379;
}
}
this.reconnectPeriod = url.getParameter("reconnect.period", 3000);
String group = url.getParameter("group", "dubbo");
if (!group.startsWith("/")) {
group = "/" + group;
}
if (!group.endsWith("/")) {
group = group + "/";
}
。。。
}
}
}
发现这里都是配置一些redis的参数,找到了这么一句
url.getParameter("db.index", 0)
感觉有可能是这个,然后就修改了一下地址:redis://username:password@host:port?db.index=2,然后重新启动。发现成功写到2库里。
问题4: dubbo2.7以后增加了元数据中心,所以我也装模作样的配置了一下:dubbo.metadata-report.address=redis://username:password@host:port?db.index=2。启动后发现,然并卵啊,没有在2库中找到相关的元数据。竟然在默认的0库找到了
去查看解析这个配置的源码,找到这个构造函数:
发现它竟然没有主动设置redis库,那肯定就是使用了默认0库了,这还能忍,直接废弃了,按照相同的目录在自己的项目里新建一个相同的类,然后把代码复制过来。
相同的路径,相同的名字来个类。然后把代码复制过来。
把设置redis的地方修改下:
public RedisMetadataReport(URL url) {
super(url);
this.timeout = url.getParameter("timeout", 1000);
this.password = url.getPassword();
if (url.getParameter("cluster", false)) {
this.jedisClusterNodes = new HashSet();
List<URL> urls = url.getBackupUrls();
Iterator var3 = urls.iterator();
while(var3.hasNext()) {
URL tmpUrl = (URL)var3.next();
this.jedisClusterNodes.add(new HostAndPort(tmpUrl.getHost(), tmpUrl.getPort()));
}
} else {
this.pool = new JedisPool(new JedisPoolConfig(), url.getHost(), url.getPort(), this.timeout, StringUtils.isEmpty(url.getPassword()) ? null : url.getPassword(), url.getParameter("db.index", 0));
// this.pool = new JedisPool(new JedisPoolConfig(), url.getHost(), url.getPort(), this.timeout, url.getPassword());
}
注释掉之前的。
this.pool = new JedisPool(new JedisPoolConfig(), url.getHost(), url.getPort(), this.timeout, url.getPassword());
新加上指定数据库的JedisPool构造函数:
this.pool = new JedisPool(new JedisPoolConfig(), url.getHost(), url.getPort(), this.timeout, StringUtils.isEmpty(url.getPassword()) ? null : url.getPassword(), url.getParameter("db.index", 0));
然后启动,问题解决。。。。