如何将Heroku的Java应用连接到云原生数据库

242 阅读6分钟

啊哈,伙计们!我从一个短暂的假期中回来了,准备继续我的宠物项目:Java中的地理分布式信使!

如果你对我的开发之旅是如何开始的(以及正在进行的)感兴趣,请查看本系列中的前几篇文章。

在上一篇文章中,我推出了我的应用程序的第一个版本,它在Heroku中运行,并使用YugabyteDB Managed作为云原生分布式数据库。我现在有信心,地理信使可以容忍区域级的中断。今天,我将看一下Heroku和YugabyteDB Managed的几种连接选项。

你可能会问,"这两个SaaS产品之间的连接有什么问题?"

首先,你的YugabyteDB Managed实例一旦部署,就不会对整个互联网可见。你必须提供应用程序、服务、虚拟机等的IP地址来连接到数据库。这对云原生数据库来说是一个常见的、合理的要求。对于MongoDB Atlas、Amazon Aurora以及任何其他认真对待安全问题的云原生数据库都是如此。

默认情况下,YugabyteDB的管理IP允许列表是空的。这意味着我的地理信使的请求将被拒绝。

Geo-messenger's requests rejected

你可能会笑着说:"来吧,Denis,只要在Heroku中找到你的应用程序的静态IP地址,然后把它添加到YugabyteDB中!"

这正是我的想法,伙计然而,Heroku在通用运行时环境中不提供静态IP地址。因此,我要么需要切换到一个包括私人空间的企业计划,要么寻找其他选择。由于考虑到成本问题(即:便宜),我选择了后者。

因此,如果你仍然和我一起在这个旅程中,那么,正如海盗们常说的,"All Hand Hoy!"意思是,"每个人都在甲板上!"让我们回顾一下我用我的应用程序验证的各种连接选项。

允许所有连接

一个粗暴的解决方案是允许所有连接到我的YugabyteDB托管实例。我通过将0.0.0.0/0 地址添加到IP允许列表中来做到这一点。

Add the 0.0.0.0/0 address to the IP allow list

如果你处于早期开发阶段(并希望专注于编码而不是基础设施设置),或者如果你在VPC网络中部署完整的解决方案,我推荐这个选项。我在不停地编码,不想分心,所以我把0.0.0.0/0 ,作为一个捷径。

使用SOCK5代理

一旦我的编码速度放慢,我决定为Heroku和YugabyteDB的托管连接问题找到一个更优雅的解决方案。很明显,我不希望我的数据库实例对整个互联网保持开放。

我的下一步是什么?好吧,作为一个在技术行业有近20年经验的工程师,我知道该怎么做。我打开浏览器,在谷歌上搜索*"heroku静态ip地址java"。*

我很快意识到,这个问题是如此普遍,以至于Heroku市场上充满了可以通过静态IP地址代理Heroku请求的附加组件。(Btw,如果你想提供你的代理,这是一个很好的商业机会:成立一个创业公司怎么样?)

然后我浪费了一个小时来尝试不同的代理。没有任何工作。YugabyteDB拒绝了来自我的地理信使的请求。我挠了挠头,然后又挠了挠头。在第三次挠头之前,我意识到所有这些代理插件只对HTTP流量起作用。RESTGraphQL和其他依赖HTTP的协议。我的应用程序使用一个JDBC驱动,打开与数据库的直接套接字连接,以PostgreSQL的线级协议交换信息。

我的下一步是什么?我相信你已经猜到了!我再次求助于谷歌,这次是搜索*"*heroku socks5 proxy for postgres",最后找到了对我有用的Fixie Socks插件。

逐步的说明如下:

  1. 我安装了Fixie Socks附加组件heroku addons:create fixie-socks:handlebar -a geo-distributed-messenger 。你可以把 "handlebar "换成另一个层级。这个我每月要花9美元,用于2,000个请求。还有一个每月100个请求的免费选项,叫做 "握把"。

  2. 我在仪表板上找到了我的静态IP,你可以用这个命令启动:
    heroku addons:open fixie-socks

  3. 我把这些IP添加到YugabyteDB的管理IP允许列表中。

  4. 然后我引入了自定义环境变量USE_FIXIE_SOCKS ,指示我的应用程序使用或绕过代理heroku config:set USE_FIXIE_SOCKS=true -a geo-distributed-messenger

USE_FIXIE_SOCKS 被设置为 "true "时,应用程序配置了两个JVM级别的属性(socksProxyHostsocksProxyPort ),要求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托管,并看到应用程序的工作空间、通道和消息。

Application workspaces, channels, and messages in YugabyteDB Managed

只对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网络对等,但我会让你验证这一点

在地平线上有什么?

好了,伙计们。这篇文章总结了我对目前在单一云区运行的应用程序版本的想法。现在,让我在进入下一个里程碑之前做一个短暂的休息:接下来,我需要地理信使在几个云区域内发挥作用。我将着眼于谷歌云工具来自动部署。我会向你报告的