啊哈,伙计们!我从一个短暂的假期中回来了,准备继续我的宠物项目:Java中的地理分布式信使!
如果你对我的开发之旅是如何开始的(以及正在进行的)感兴趣,请查看本系列中的前几篇文章。
在上一篇文章中,我推出了我的应用程序的第一个版本,它在Heroku中运行,并使用YugabyteDB Managed作为云原生分布式数据库。我现在有信心,地理信使可以容忍区域级的中断。今天,我将看一下Heroku和YugabyteDB Managed的几种连接选项。
你可能会问,"这两个SaaS产品之间的连接有什么问题?"
首先,你的YugabyteDB Managed实例一旦部署,就不会对整个互联网可见。你必须提供应用程序、服务、虚拟机等的IP地址来连接到数据库。这对云原生数据库来说是一个常见的、合理的要求。对于MongoDB Atlas、Amazon Aurora以及任何其他认真对待安全问题的云原生数据库都是如此。
默认情况下,YugabyteDB的管理IP允许列表是空的。这意味着我的地理信使的请求将被拒绝。

你可能会笑着说:"来吧,Denis,只要在Heroku中找到你的应用程序的静态IP地址,然后把它添加到YugabyteDB中!"
这正是我的想法,伙计然而,Heroku在通用运行时环境中不提供静态IP地址。因此,我要么需要切换到一个包括私人空间的企业计划,要么寻找其他选择。由于考虑到成本问题(即:便宜),我选择了后者。
因此,如果你仍然和我一起在这个旅程中,那么,正如海盗们常说的,"All Hand Hoy!"意思是,"每个人都在甲板上!"让我们回顾一下我用我的应用程序验证的各种连接选项。
允许所有连接
一个粗暴的解决方案是允许所有连接到我的YugabyteDB托管实例。我通过将0.0.0.0/0 地址添加到IP允许列表中来做到这一点。

如果你处于早期开发阶段(并希望专注于编码而不是基础设施设置),或者如果你在VPC网络中部署完整的解决方案,我推荐这个选项。我在不停地编码,不想分心,所以我把0.0.0.0/0 ,作为一个捷径。
使用SOCK5代理
一旦我的编码速度放慢,我决定为Heroku和YugabyteDB的托管连接问题找到一个更优雅的解决方案。很明显,我不希望我的数据库实例对整个互联网保持开放。
我的下一步是什么?好吧,作为一个在技术行业有近20年经验的工程师,我知道该怎么做。我打开浏览器,在谷歌上搜索*"heroku静态ip地址java"。*
我很快意识到,这个问题是如此普遍,以至于Heroku市场上充满了可以通过静态IP地址代理Heroku请求的附加组件。(Btw,如果你想提供你的代理,这是一个很好的商业机会:成立一个创业公司怎么样?)
然后我浪费了一个小时来尝试不同的代理。没有任何工作。YugabyteDB拒绝了来自我的地理信使的请求。我挠了挠头,然后又挠了挠头。在第三次挠头之前,我意识到所有这些代理插件只对HTTP流量起作用。REST、GraphQL和其他依赖HTTP的协议。我的应用程序使用一个JDBC驱动,打开与数据库的直接套接字连接,以PostgreSQL的线级协议交换信息。
我的下一步是什么?我相信你已经猜到了!我再次求助于谷歌,这次是搜索*"*heroku socks5 proxy for postgres",最后找到了对我有用的Fixie Socks插件。
逐步的说明如下:
-
我安装了Fixie Socks附加组件
heroku addons:create fixie-socks:handlebar -a geo-distributed-messenger。你可以把 "handlebar "换成另一个层级。这个我每月要花9美元,用于2,000个请求。还有一个每月100个请求的免费选项,叫做 "握把"。 -
我在仪表板上找到了我的静态IP,你可以用这个命令启动:
heroku addons:open fixie-socks。 -
我把这些IP添加到YugabyteDB的管理IP允许列表中。
-
然后我引入了自定义环境变量
USE_FIXIE_SOCKS,指示我的应用程序使用或绕过代理heroku config:set USE_FIXIE_SOCKS=true -a geo-distributed-messenger。
当USE_FIXIE_SOCKS 被设置为 "true "时,应用程序配置了两个JVM级别的属性(socksProxyHost 和socksProxyPort ),要求Java通过代理发送所有网络请求。
if (System.getenv("USE_FIXIE_SOCKS") != null) {
boolean useFixie = Boolean.valueOf(System.getenv("USE_FIXIE_SOCKS"));
if (useFixie) {
System.out.println("Setting up Fixie Socks Proxy");
// The FIXIE_SOCKS_HOST variable is added by the add-on to the environment
String[] fixieData = System.getenv("FIXIE_SOCKS_HOST").split("@");
String[] fixieCredentials = fixieData[0].split(":");
String[] fixieUrl = fixieData[1].split(":");
String fixieHost = fixieUrl[0];
String fixiePort = fixieUrl[1];
String fixieUser = fixieCredentials[0];
String fixiePassword = fixieCredentials[1];
System.setProperty("socksProxyHost", fixieHost);
System.setProperty("socksProxyPort", fixiePort);
System.out.println("Enabled Fixie Socks Proxy:" + fixieHost);
Authenticator.setDefault(new ProxyAuthenticator(fixieUser, fixiePassword));
}
}
在Heroku中重新启动应用程序后,我可以连接到YugabyteDB托管,并看到应用程序的工作空间、通道和消息。

只对YugabyteDB的连接使用代理
尽管SOCKS5代理方法起了作用,我仍然对我的实现不完全满意。什么问题呢?看看这两行就知道了。
System.setProperty("socksProxyHost", fixieHost);
System.setProperty("socksProxyPort", fixiePort);
这些是与JVM有关的设置,要求每个TCP/IP连接都要经过我的代理。这就过火了。我只需要代理与YugabyteDB托管的套接字连接。
幸运的是,这项任务在Java中很容易解决。你只需要提供一个自定义ProxySelector的实现。这就是我所做的,引入我自己的DatabaseProxySelector类。
public class DatabaseProxySelector extends ProxySelector {
private String proxyHost;
private int proxyPort;
public DatabaseProxySelector(String proxyHost, int proxyPort) {
this.proxyHost = proxyHost;
this.proxyPort = proxyPort;
}
@Override
public List<Proxy> select(URI uri) {
// YugabyteDB Managed host always ends with `ybdb.io`
if (uri.toString().contains("ybdb.io")) {
System.out.println("Using the proxy for YugabyteDB Managed: " + uri);
final InetSocketAddress proxyAddress = InetSocketAddress
.createUnresolved(proxyHost, proxyPort);
return Collections.singletonList(new Proxy(Type.SOCKS, proxyAddress));
}
return Collections.singletonList(Proxy.NO_PROXY);
}
@Override
public void connectFailed(URI uri, SocketAddress sa, IOException ioe) {
new IOException("Failed to connect to the proxy", ioe).printStackTrace();
}
}
正如你所看到的,DatabaseProxySelector ,只对YugabyteDB管理的URI启用Fixie Socks代理。最后,该选择器实例在应用程序启动时被创建。
if (System.getenv("USE_FIXIE_SOCKS") != null) {
boolean useFixie = Boolean.valueOf(System.getenv("USE_FIXIE_SOCKS"));
if (useFixie) {
System.out.println("Setting up Fixie Socks Proxy");
String[] fixieData = System.getenv("FIXIE_SOCKS_HOST").split("@");
String[] fixieCredentials = fixieData[0].split(":");
String[] fixieUrl = fixieData[1].split(":");
String fixieHost = fixieUrl[0];
String fixiePort = fixieUrl[1];
String fixieUser = fixieCredentials[0];
String fixiePassword = fixieCredentials[1];
DatabaseProxySelector proxySelector = new DatabaseProxySelector(fixieHost, Integer.parseInt(fixiePort));
ProxySelector.setDefault(proxySelector);
Authenticator.setDefault(new ProxyAuthenticator(fixieUser, fixiePassword));
System.out.println("Enabled Fixie Socks Proxy:" + fixieHost);
}
}
探索私有空间
在开始的时候,我提到Heroku的私有空间功能也应该工作(如果相信Heroku的技术文档)。为了完整起见,我在文章中加入了这个选项。理论上,你可以在Heroku中设置这些私有空间,并与YugabyteDB管理的VPC网络对等,但我会让你验证这一点
在地平线上有什么?
好了,伙计们。这篇文章总结了我对目前在单一云区运行的应用程序版本的想法。现在,让我在进入下一个里程碑之前做一个短暂的休息:接下来,我需要地理信使在几个云区域内发挥作用。我将着眼于谷歌云工具来自动部署。我会向你报告的