写点什么

bboss http 负载均衡器使用指南

作者:大河
  • 2022 年 8 月 29 日
    湖南
  • 本文字数:22200 字

    阅读完需:约 73 分钟

bboss http负载均衡器使用指南

bboss http 负载均衡器使用指南

bboss http 一个简单而功能强大的 http/https 负载均衡器模块,基于 http/https 协议实现客户端-服务端点到点的负载均衡和集群容灾功能,本文介绍其使用方法。


项目源码


https://github.com/bbossgroups/bboss-http


httpproxy 案例:基于 apollo 进行配置管理、节点自动发现、路由规则自动切换,源码地址


https://github.com/bbossgroups/httpproxy-apollo

1.负载均衡器特色

bboss http 基于 http/https 协议实现客户端-服务端点到点的负载均衡和集群容灾功能,具有以下特色


1.服务负载均衡(目前提供RoundRobin负载算法)2.服务健康检查3.服务容灾故障恢复4.服务自动发现(zk,etcd,consul,eureka,db,其他第三方注册中心)5.本地地址清单与远程配置中心动态地址管理相结合,远程不可用时,采用本地地址6.动态监听路由变化7.分组服务管理可以配置多组服务集群地址,每一组地址清单支持的配置格式:http://ip:porthttps://ip:portip:port(默认http协议)多个地址用逗号分隔8.服务安全认证(配置basic账号和口令)9.服务采用http连接池10.主备路由/异地灾备特色
10.1.负载均衡器主备功能,如果主节点全部挂掉,请求转发到可用的备用节点,如果备用节点也挂了,就抛出异常,如果主节点恢复正常,那么请求重新发往主节点 10.2. 异地灾备,服务采用异地灾备模式部署,服务优先调用本地,当本地服务全部挂掉,服务请求转发到异地服务,如果本地服务部分恢复或者全部恢复,那么请求重新发往本地服务11.提供丰富的rpc api方法
复制代码


2.导入 http 负载均衡器

在工程中导入以下 maven 坐标即可


<dependency>   <groupId>com.bbossgroups</groupId>   <artifactId>bboss-http</artifactId>   <version>6.0.3</version></dependency>
复制代码


如果是 gradle 工程,导入方法如下:


implementation 'com.bbossgroups:bboss-http:6.0.3'
复制代码

3.负载均衡组件




org.frameworkset.spi.remote.http.HttpRequestProxy
复制代码

3.1 负载均衡组件 API

3.1.1 初始化 http proxy

HttpRequestProxy.startHttpPools(Map configs); --通过在代码中设置参数,初始化 httpproxy


HttpRequestProxy.startHttpPools(String configFile);--加载配置文件(classpath 相对路径)中设置参数,初始化 httpproxy

1)加载配置文件启动示例

//加载配置文件,启动负载均衡器HttpRequestProxy.startHttpPools("application.properties");
复制代码

2)加载 Map 属性配置启动负载均衡器示例

简单的配置和启动


Map<String,Object> configs = new HashMap<String,Object>();
configs.put("http.health","/health.html");//health监控检查地址必须配置,否则将不会启动健康检查机制
//如果指定hosts那么就会采用配置的地址作为初始化地址清单configs.put("http.hosts,","192.168.137.1:9200,192.168.137.2:9200,192.168.137.3:9200");
HttpRequestProxy.startHttpPools(configs);
复制代码


启动时指定服务发现机制


       Map<String,Object> configs = new HashMap<String,Object>();
DemoHttpHostDiscover demoHttpHostDiscover = new DemoHttpHostDiscover(); configs.put("http.discoverService",demoHttpHostDiscover);//设置服务发现组件

configs.put("http.health","/health.html");//health监控检查地址必须配置,否则将不会启动健康检查机制//如果指定hosts那么就会采用配置的地址作为初始化地址清单,后续通过discoverService服务发现的地址都会加入到清单中,去掉的服务也会从清单中剔除configs.put("http.hosts,","192.168.137.1:9200,192.168.137.2:9200,192.168.137.3:9200"); HttpRequestProxy.startHttpPools(configs);
复制代码

3)加载 apollo 配置启动 httpproxy

指定 apollo 命名空间和配置参数变化监听器(自定义)


    /**     * 从apollo加载配置启动http proxy:     * 配置了两个连接池:default,schedule     * apollo namespace:  application     * 服务地址变化发现监听器: org.frameworkset.http.client.AddressConfigChangeListener     */
HttpRequestProxy.startHttpPoolsFromApollo("application","org.frameworkset.http.client.AddressConfigChangeListener");
复制代码


指定 apollo 命名空间并监听服务节点及路由规则变化


       /**       * 1.服务健康检查       * 2.服务负载均衡       * 3.服务容灾故障恢复       * 4.服务自动发现(apollo,zk,etcd,consul,eureka,db,其他第三方注册中心)       * 配置了两个连接池:default,report       * 本示例演示基于apollo提供配置管理、服务自动发现以及灰度/生产,主备切换功能       */
HttpRequestProxy.startHttpPoolsFromApolloAwaredChange("application");
复制代码

3.1.2 调用服务 API 及示例

HttpRequestProxy.httpGetforString


HttpRequestProxy.httpXXX


HttpRequestProxy.sendXXX


提供了两套方法:一套方法是带服务组名称的方法,一套方法是不带服务组名称的方法(默认 default 服务组)


服务地址都是相对地址,例如:/testBBossIndexCrud,最终地址会被解析为


http://ip:port/testBBossIndexCrud 或者 https://ip:port/testBBossIndexCrud


默认服务组示例


//以get方式发送请求String data = HttpRequestProxy.httpGetforString("/testBBossIndexCrud");//以get方式发送请求,将返回的json数据封装为AgentRule对象AgentRule agentRule = HttpRequestProxy.httpGetforObject("/testBBossIndexCrud?id=1",AgentRule.class);//以RequestBody方式,将params对象转换为json报文post方式推送到服务端,将相应json报文转换为AgentRule对象返回AgentRule agentRule = HttpRequestProxy.sendJsonBody( params, "/testBBossIndexCrud",AgentRule.class);//以post方式发送请求,将返回的json数据封装为AgentRule对象,方法第二个参数为保存请求参数的map对象AgentRule data = HttpRequestProxy.httpPostForObject("/testBBossIndexCrud",(Map)null,AgentRule.class);//以post方式发送请求,将返回的json数据封装为AgentRule对象List集合,方法第二个参数为保存请求参数的map对象                List<AgentRule> datas = HttpRequestProxy.httpPostForList("/testBBossIndexCrud",(Map)null,AgentRule.class);//以post方式发送请求,将返回的json数据封装为AgentRule对象Set集合,方法第二个参数为保存请求参数的map对象                Set<AgentRule> dataSet = HttpRequestProxy.httpPostForSet("/testBBossIndexCrud",(Map)null,AgentRule.class);//以post方式发送请求,将返回的json数据封装为AgentRule对象Map集合,方法第二个参数为保存请求参数的map对象                Map<String,AgentRule> dataMap = HttpRequestProxy.httpPostForMap("/testBBossIndexCrud",(Map)null,String.class,AgentRule.class);
复制代码


指定服务组示例


String data = HttpRequestProxy.httpGetforString("report","/testBBossIndexCrud");AgentRule agentRule = HttpRequestProxy.httpGetforObject("report","/testBBossIndexCrud",AgentRule.class);AgentRule agentRule = HttpRequestProxy.sendJsonBody("report", params, "/testBBossIndexCrud",AgentRule.class);
AgentRule data = HttpRequestProxy.httpPostForObject("report","/testBBossIndexCrud",(Map)null,AgentRule.class); List<AgentRule> datas = HttpRequestProxy.httpPostForList("report","/testBBossIndexCrud",(Map)null,AgentRule.class); Set<AgentRule> dataSet = HttpRequestProxy.httpPostForSet("report","/testBBossIndexCrud",(Map)null,AgentRule.class); Map<String,AgentRule> dataMap = HttpRequestProxy.httpPostForMap("report","/testBBossIndexCrud",(Map)null,String.class,AgentRule.class);
复制代码

3.2 http 负载均衡器配置和启动

http 负载均衡器配置非常简单,可以通过配置文件方式和代码方式对 http 负载均衡器进行配置

3.2.1 配置文件方式

在配置文件中添加以下内容-resources\application.properties


http.poolNames = default,schedule##http连接池配置http.timeoutConnection = 5000http.timeoutSocket = 50000http.connectionRequestTimeout=10000http.retryTime = 0http.maxLineLength = -1http.maxHeaderCount = 200http.maxTotal = 200http.defaultMaxPerRoute = 100http.soReuseAddress = falsehttp.soKeepAlive = falsehttp.timeToLive = 3600000http.keepAlive = 3600000http.keystore =http.keyPassword =# ssl 主机名称校验,是否采用default配置,# 如果指定为default,就采用DefaultHostnameVerifier,否则采用 SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIERhttp.hostnameVerifier =
# 服务代理配置# 服务全认证账号配置http.authAccount=elastichttp.authPassword=changeme# ha proxy 集群负载均衡地址配置http.hosts=192.168.137.1:808,192.168.137.1:809,192.168.137.1:810# https服务必须带https://协议头#http.hosts=https://192.168.137.1:808,https://192.168.137.1:809,https://192.168.137.1:810
# 健康检查服务http.health=/health# 健康检查定时时间间隔,单位:毫秒,默认3秒http.healthCheckInterval=3000# 服务地址自动发现功能http.discoverService=org.frameworkset.http.client.DemoHttpHostDiscover# 定时运行服务发现方法时间间隔,单位:毫秒,默认10秒http.discoverService.interval=10000##告警服务使用的http连接池配置schedule.http.timeoutConnection = 5000schedule.http.timeoutSocket = 50000schedule.http.connectionRequestTimeout=10000schedule.http.retryTime = 0schedule.http.maxLineLength = -1schedule.http.maxHeaderCount = 200schedule.http.maxTotal = 200schedule.http.defaultMaxPerRoute = 100schedule.http.soReuseAddress = falseschedule.http.soKeepAlive = falseschedule.http.timeToLive = 3600000schedule.http.keepAlive = 3600000schedule.http.keystore =schedule.http.keyPassword =# ssl 主机名称校验,是否采用default配置,# 如果指定为default,就采用DefaultHostnameVerifier,否则采用 SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIERschedule.http.hostnameVerifier =# 告警服务使用服务代理配置# 服务全认证账号配置schedule.http.authAccount=elasticschedule.http.authPassword=changeme# ha proxy 集群负载均衡地址配置schedule.http.hosts=192.168.137.1:808,192.168.137.1:809,192.168.137.1:810# https服务必须带https://协议头# schedule.http.hosts=https://192.168.137.1:808,https://192.168.137.1:809,https://192.168.137.1:810
# 健康检查服务schedule.http.health=/health# 健康检查定时时间间隔,单位:毫秒,默认3秒schedule.http.healthCheckInterval=3000# 服务地址自动发现功能schedule.http.discoverService=org.frameworkset.http.client.DemoHttpHostDiscover# 定时运行服务发现方法时间间隔,单位:毫秒,默认10秒schedule.http.discoverService.interval=10000
复制代码


上面配置了 default 和 schedule 两组服务配置,每组包含两部分内容:


  • http 连接池配置

  • 服务负载均衡配置


http 连接池配置这里不着重说明,只介绍服务负载均衡相关配置


# 服务代理配置# 服务全认证账号和口令配置http.authAccount=elastichttp.authPassword=changeme# ha proxy 集群负载均衡地址配置,初始地址清单,# 还可以通过http.discoverService动态发现新的负载地址、移除关停的负载地址,也可以不配置初始地址# 这样初始地址完全由http.discoverService对应的服务发现功能来提供http.hosts=192.168.137.1:808,192.168.137.1:809,192.168.137.1:810# https服务必须带https://协议头#http.hosts=https://192.168.137.1:808,https://192.168.137.1:809,https://192.168.137.1:810# 健康检查服务,服务端提供的一个监控服务检查地址,当服务节点不可用时,就会启动健康检查,根据healthCheckInterval参数,按一定的时间间隔探测health对应的服务是否正常,如果正常,那么服务即可用,健康检查线程停止(直到服务不可用时,再次启动检查机制),否则继续监测http.health=/health# 健康检查定时时间间隔,单位:毫秒,默认3秒http.healthCheckInterval=3000# 服务地址自动发现功能,必须继承抽象类org.frameworkset.spi.remote.http.proxy.HttpHostDiscover# 实现抽象方法discoverhttp.discoverService=org.frameworkset.http.client.DemoHttpHostDiscover
复制代码


org.frameworkset.http.client.DemoHttpHostDiscover 的实现如下:


package org.frameworkset.http.client; 
import org.frameworkset.spi.assemble.GetProperties;import org.frameworkset.spi.remote.http.ClientConfiguration;import org.frameworkset.spi.remote.http.HttpHost;import org.frameworkset.spi.remote.http.proxy.HttpHostDiscover;import org.frameworkset.spi.remote.http.proxy.HttpServiceHostsConfig;
import java.util.ArrayList;import java.util.List;

public class DemoHttpHostDiscover extends HttpHostDiscover { private int count = 0; @Override protected List<HttpHost> discover(HttpServiceHostsConfig httpServiceHostsConfig, ClientConfiguration configuration, GetProperties context) { //直接构造并返回三个服务地址的列表对象 List<HttpHost> hosts = new ArrayList<HttpHost>(); // https服务必须带https://协议头,例如https://192.168.137.1:808 HttpHost host = new HttpHost("192.168.137.1:808"); hosts.add(host); if(count != 2) {//模拟添加和去除节点 host = new HttpHost("192.168.137.1:809"); hosts.add(host); } else{ System.out.println("aa"); } host = new HttpHost("192.168.137.1:810"); hosts.add(host); count ++; return hosts; } /** * 返回null或者false,忽略对返回的null或者空的hosts进行处理; * 返回true,要对null或者空的hosts进行处理,这样会导致所有的地址不可用 * * @return 默认返回null */ protected Boolean handleNullOrEmptyHostsByDiscovery(){ return null; }}
复制代码

3.2.2 加载配置文件启动负载均衡器

HttpRequestProxy.startHttpPools("application.properties");
复制代码

3.2.3 代码方式配置和启动负载均衡器

单集群

配置和启动


 Map<String,Object> configs = new HashMap<String,Object>(); configs.put("http.health","/health");//health监控检查地址必须配置,否则将不会启动健康检查机制 
DemoHttpHostDiscover demoHttpHostDiscover = new DemoHttpHostDiscover(); configs.put("http.discoverService",demoHttpHostDiscover);//注册服务发现机制,服务自动发现(zk,etcd,consul,eureka,db,其他第三方注册中心)
//启动负载均衡器 HttpRequestProxy.startHttpPools(configs);
复制代码


服务调用示例


 String data = HttpRequestProxy.httpGetforString("/testBBossIndexCrud");//获取字符串报文Map data = HttpRequestProxy.httpGetforObject("/testBBossIndexCrud",Map.class);//获取对象数据
复制代码

多集群

配置和启动:两个集群 default,report


/**       * 1.服务健康检查       * 2.服务负载均衡       * 3.服务容灾故障恢复       * 4.服务自动发现(zk,etcd,consul,eureka,db,其他第三方注册中心)       * 配置了两个服务集群组:default,report       */      Map<String,Object> configs = new HashMap<String,Object>();      configs.put("http.poolNames","default,report");    //default组配置        configs.put("http.health","/health");//health监控检查地址必须配置,否则将不会启动健康检查机制
DemoHttpHostDiscover demoHttpHostDiscover = new DemoHttpHostDiscover(); configs.put("http.discoverService",demoHttpHostDiscover);
//report组配置 configs.put("report.http.health","/health");//health监控检查地址必须配置,否则将不会启动健康检查机制
configs.put("report.http.discoverService","org.frameworkset.http.client.DemoHttpHostDiscover"); //启动负载均衡器 HttpRequestProxy.startHttpPools(configs);
复制代码


服务调用


 String data = HttpRequestProxy.httpGetforString("/testBBossIndexCrud");//在default集群上执行请求,无需指定集群名称
String data = HttpRequestProxy.httpGetforString("report","/testBBossIndexCrud");//在report集群上执行请求
复制代码

3.2.4 配置参数中使用环境变量

可以在配置参数中使用环境变量,环境变量引用方式:


简单方式


http.routing=#[area]


混合方式


http.routing=#[area]dddd#[sadfasdf]


需要在 os(linux,unix,windows)中配置名称为 area 和 sadfasdf 的环境变量。


以 http.routing 为例进行说明。


为了避免配置文件混乱,可以将 http.routing 对应的值做成环境变量,配置文件中只需要引用环境变量名称即可:


http.routing=#[area]#bboss支持混合模式的变量和常量拼接,例如#http.routing=#[county]aaac#[area]
复制代码


这样我们在 os(linux,unix,windows)中配置名称为 area 的环境变量即可。

3.2.5 spring boot 配置和使用 http proxy

示例工程源码获取地址:https://github.com/bbossgroups/bestpractice/tree/master/springboot-starter


首先需要在工程中导入 bboss spring boot starter 的 maven 坐标:


<dependency>  <groupId>com.bbossgroups</groupId>  <artifactId>bboss-spring-boot-starter</artifactId>  <version>5.9.8</version></dependency>
复制代码

3.2.5.1 单 http 服务池

在 spring boot 配置文件application.properties中添加 http proxy 的配置参数


##es client http服务配置spring.bboss.http.name=defaultspring.bboss.http.timeoutConnection = 5000spring.bboss.http.timeoutSocket = 5000spring.bboss.http.connectionRequestTimeout=5000spring.bboss.http.retryTime = 1spring.bboss.http.maxLineLength = -1spring.bboss.http.maxHeaderCount = 200spring.bboss.http.maxTotal = 400spring.bboss.http.defaultMaxPerRoute = 200spring.bboss.http.soReuseAddress = falsespring.bboss.http.soKeepAlive = falsespring.bboss.http.timeToLive = 3600000spring.bboss.http.keepAlive = 3600000spring.bboss.http.keystore =spring.bboss.http.keyPassword =# ssl 主机名称校验,是否采用default配置,# 如果指定为default,就采用DefaultHostnameVerifier,否则采用 SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIERspring.bboss.http.hostnameVerifier =#每隔多少毫秒校验空闲connection,自动释放无效链接# -1 或者0不检查spring.bboss.http.validateAfterInactivity=2000# 每次获取connection时校验连接,true,校验,false不校验,有性能开销,推荐采用# validateAfterInactivity来控制连接是否有效# 默认值falsespring.bboss.http.staleConnectionCheckEnabled=false#* 自定义重试控制接口,必须实现接口方法#* public interface CustomHttpRequestRetryHandler  {#*   public boolean retryRequest(IOException exception, int executionCount, HttpContext context,ClientConfiguration configuration);#* }#* 方法返回true,进行重试,false不重试spring.bboss.http.customHttpRequestRetryHandler=org.frameworkset.spi.remote.http.ConnectionResetHttpRequestRetryHandler
# 服务代理配置# 服务全认证账号配置
spring.bboss.http.authAccount=elasticspring.bboss.http.authPassword=changeme# ha proxy 集群负载均衡地址配置#spring.bboss.http.hosts=192.168.137.1:808,192.168.137.1:809,192.168.137.1:810spring.bboss.http.hosts=192.168.137.1:9200# 健康检查服务spring.bboss.http.health=/spring.bboss.http.healthCheckInterval=1000# 服务地址自动发现功能#spring.bboss.http.discoverService.serviceClass=com.test.DiscoverService# 定时运行服务发现方法时间间隔,单位:毫秒,默认10秒spring.bboss.http.discoverService.interval=10000
# handleNullOrEmptyHostsByDiscovery#false,忽略对返回的null或者空的hosts进行处理#true,要对null或者空的hosts进行处理,这样会导致所有的地址不可用http.discoverService.handleNullOrEmptyHostsByDiscovery=false
复制代码


服务调用示例


/* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * *      http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */package com.frameworkset.sqlexecutor;

import com.frameworkset.common.poolman.SQLExecutor;import com.frameworkset.util.SimpleStringUtil;import org.frameworkset.spi.boot.BBossStarter;import org.frameworkset.spi.remote.http.HttpRequestProxy;import org.junit.Test;import org.junit.runner.RunWith;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.test.context.SpringBootTest;import org.springframework.test.context.junit4.SpringRunner;
import java.util.HashMap;import java.util.List;import java.util.Map;
/** * testCase必须和spring boot application启动类在同一个包路径下面,否则会报错: * java.lang.IllegalStateException: Unable to find a @SpringBootConfiguration, you need to use @Context
* @author yinbp [122054810@qq.com] */@RunWith(SpringRunner.class)@SpringBootTest
public class BBossStarterTestCase { @Autowired private BBossStarter bbossStarterDefault; private static Logger logger = LoggerFactory.getLogger(BBossStarterTestCase.class); @Test public void testMultiBBossESStarterDefault() throws Exception { Map params = new HashMap(); params.put("testp","aaaaa"); params.put("dff","zzzz"); List<Map> datas = HttpRequestProxy.httpPostForList("default","/demoproject/file/getUserInfo.page",params,Map.class); logger.info(SimpleStringUtil.object2json(datas)); }

@Test public void testFirstQuery() throws Exception {
List<Map> datas = SQLExecutor.queryListWithDBName(Map.class,"default","select * from user"); logger.info(SimpleStringUtil.object2json(datas)); }
}
复制代码


关键代码,需要在组件中导入 starter 组件:


    @Autowired    private BBossStarter bbossStarterDefault;
复制代码

3.2.5.2 多 http 服务池配置

在 spring boot 配置文件application-multi.properties中添加 http proxy 的配置参数


##es client http服务配置spring.bboss.default.http.name=defaultspring.bboss.default.http.timeoutConnection = 5000spring.bboss.default.http.timeoutSocket = 5000spring.bboss.default.http.connectionRequestTimeout=5000spring.bboss.default.http.retryTime = 1spring.bboss.default.http.maxLineLength = -1spring.bboss.default.http.maxHeaderCount = 200spring.bboss.default.http.maxTotal = 400spring.bboss.default.http.defaultMaxPerRoute = 200spring.bboss.default.http.soReuseAddress = falsespring.bboss.default.http.soKeepAlive = falsespring.bboss.default.http.timeToLive = 3600000spring.bboss.default.http.keepAlive = 3600000spring.bboss.default.http.keystore =spring.bboss.default.http.keyPassword =# ssl 主机名称校验,是否采用default配置,# 如果指定为default,就采用DefaultHostnameVerifier,否则采用 SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIERspring.bboss.default.http.hostnameVerifier =#每隔多少毫秒校验空闲connection,自动释放无效链接# -1 或者0不检查spring.bboss.default.http.validateAfterInactivity=2000# 每次获取connection时校验连接,true,校验,false不校验,有性能开销,推荐采用# validateAfterInactivity来控制连接是否有效# 默认值falsespring.bboss.default.http.staleConnectionCheckEnabled=false#* 自定义重试控制接口,必须实现接口方法#* public interface CustomHttpRequestRetryHandler  {#*   public boolean retryRequest(IOException exception, int executionCount, HttpContext context,ClientConfiguration configuration);#* }#* 方法返回true,进行重试,false不重试spring.bboss.default.http.customHttpRequestRetryHandler=org.frameworkset.spi.remote.http.ConnectionResetHttpRequestRetryHandler
# 服务代理配置# 服务全认证账号配置
spring.bboss.default.http.authAccount=elasticspring.bboss.default.http.authPassword=changeme# ha proxy 集群负载均衡地址配置#spring.bboss.default.http.hosts=192.168.137.1:808,192.168.137.1:809,192.168.137.1:810spring.bboss.default.http.hosts=10.13.11.117:8082# 健康检查服务spring.bboss.default.http.health=/spring.bboss.default.http.healthCheckInterval=1000# 服务地址自动发现功能#spring.bboss.default.http.discoverService.serviceClass=com.test.DiscoverService# 定时运行服务发现方法时间间隔,单位:毫秒,默认10秒spring.bboss.default.http.discoverService.interval=10000
# handleNullOrEmptyHostsByDiscovery#false,忽略对返回的null或者空的hosts进行处理#true,要对null或者空的hosts进行处理,这样会导致所有的地址不可用spring.bboss.default.http.discoverService.handleNullOrEmptyHostsByDiscovery=false
# 数据库数据源配置spring.bboss.default.db.name = firstdsspring.bboss.default.db.user = rootspring.bboss.default.db.password = 123456spring.bboss.default.db.driver = com.mysql.jdbc.Driverspring.bboss.default.db.url = jdbc:mysql://10.13.11.117:3306/mysqlspring.bboss.default.db.usePool = truespring.bboss.default.db.validateSQL = select 1



##second es client http服务配置spring.bboss.second.http.name=secondspring.bboss.second.http.timeoutConnection = 5000spring.bboss.second.http.timeoutSocket = 5000spring.bboss.second.http.connectionRequestTimeout=5000spring.bboss.second.http.retryTime = 1spring.bboss.second.http.maxLineLength = -1spring.bboss.second.http.maxHeaderCount = 200spring.bboss.second.http.maxTotal = 400spring.bboss.second.http.secondMaxPerRoute = 200spring.bboss.second.http.soReuseAddress = falsespring.bboss.second.http.soKeepAlive = falsespring.bboss.second.http.timeToLive = 3600000spring.bboss.second.http.keepAlive = 3600000spring.bboss.second.http.keystore =spring.bboss.second.http.keyPassword =# ssl 主机名称校验,是否采用second配置,# 如果指定为second,就采用secondHostnameVerifier,否则采用 SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIERspring.bboss.second.http.hostnameVerifier =#每隔多少毫秒校验空闲connection,自动释放无效链接# -1 或者0不检查spring.bboss.second.http.validateAfterInactivity=2000# 每次获取connection时校验连接,true,校验,false不校验,有性能开销,推荐采用# validateAfterInactivity来控制连接是否有效# 默认值falsespring.bboss.second.http.staleConnectionCheckEnabled=false#* 自定义重试控制接口,必须实现接口方法#* public interface CustomHttpRequestRetryHandler {#* public boolean retryRequest(IOException exception, int executionCount, HttpContext context,ClientConfiguration configuration);#* }#* 方法返回true,进行重试,false不重试spring.bboss.second.http.customHttpRequestRetryHandler=org.frameworkset.spi.remote.http.ConnectionResetHttpRequestRetryHandler
# 服务代理配置# 服务全认证账号配置
spring.bboss.second.http.authAccount=elasticspring.bboss.second.http.authPassword=changeme# ha proxy 集群负载均衡地址配置#spring.bboss.second.http.hosts=192.168.137.1:808,192.168.137.1:809,192.168.137.1:810spring.bboss.second.http.hosts=10.13.11.117:8082# 健康检查服务spring.bboss.second.http.health=/spring.bboss.second.http.healthCheckInterval=1000# 服务地址自动发现功能#spring.bboss.second.http.discoverService.serviceClass=com.test.DiscoverService# 定时运行服务发现方法时间间隔,单位:毫秒,默认10秒spring.bboss.second.http.discoverService.interval=10000
# handleNullOrEmptyHostsByDiscovery#false,忽略对返回的null或者空的hosts进行处理#true,要对null或者空的hosts进行处理,这样会导致所有的地址不可用spring.bboss.second.http.discoverService.handleNullOrEmptyHostsByDiscovery=false
# 数据库数据源配置spring.bboss.second.db.name = seconddsspring.bboss.second.db.user = rootspring.bboss.second.db.password = 123456spring.bboss.second.db.driver = com.mysql.jdbc.Driverspring.bboss.second.db.url = jdbc:mysql://10.13.11.117:3306/mysqlspring.bboss.second.db.usePool = truespring.bboss.second.db.validateSQL = select 1

复制代码


服务调用实例:


/* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * *      http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */package com.frameworkset.sqlexecutor;

import com.frameworkset.common.poolman.SQLExecutor;import com.frameworkset.util.SimpleStringUtil;import org.frameworkset.spi.boot.BBossStarter;import org.frameworkset.spi.remote.http.HttpRequestProxy;import org.junit.Test;import org.junit.runner.RunWith;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.test.context.SpringBootTest;import org.springframework.test.context.ActiveProfiles;import org.springframework.test.context.junit4.SpringRunner;
import java.util.HashMap;import java.util.List;import java.util.Map;
/** * testCase必须和spring boot application启动类在同一个包路径下面,否则会报错: * java.lang.IllegalStateException: Unable to find a @SpringBootConfiguration, you need to use @Context * 多集群演示功能测试用例,spring boot配置项以spring.bboss.集群名称开头,例如: * spring.bboss.db.firstds * spring.bboss.db.secondds * spring.bboss.http.default * spring.bboss.http.second * 两个集群通过 com.frameworkset.sqlexecutor.MultiSTartConfigurer * 对应的配置文件为application-multi.properties文件 * @author yinbp [122054810@qq.com] */@RunWith(SpringRunner.class)@SpringBootTest
@ActiveProfiles("multi")public class MultiBBossStartersTestCase { @Autowired private BBossStarter bbossStarterDefault; private static Logger logger = LoggerFactory.getLogger(MultiBBossStartersTestCase.class); @Test public void testMultiBBossESStarterDefault() throws Exception { Map params = new HashMap(); params.put("testp","aaaaa"); params.put("dff","zzzz"); List<Map> datas = HttpRequestProxy.httpPostForList("default","/demoproject/file/getUserInfo.page",params,Map.class); logger.info(SimpleStringUtil.object2json(datas)); }
@Test public void testMultiBBossESStarterSecond() throws Exception { Map params = new HashMap(); params.put("testp","aaaaa"); params.put("dff","zzzz"); List<Map> datas = HttpRequestProxy.sendJsonBodyForList("second",params,"/demoproject/file/getUserInfo.page",Map.class); logger.info(SimpleStringUtil.object2json(datas)); }
@Test public void testFirstQuery() throws Exception {
List<Map> datas = SQLExecutor.queryListWithDBName(Map.class,"firstds","select * from user"); logger.info(SimpleStringUtil.object2json(datas)); }
@Test public void testSecondQuery() throws Exception {
List<Map> datas = SQLExecutor.queryListWithDBName(Map.class,"secondds","select * from user"); logger.info(SimpleStringUtil.object2json(datas)); }}
复制代码


关键代码,需要在组件中导入 starter 组件:


    @Autowired    private BBossStarter bbossStarterDefault;
复制代码

3.2.6 failAllContinue 配置

failAllContinue 配置如果所有节点都被标记为不可用时,可以通过控制开关设置返回故障节点用于处理请求,如果请求能够被正常处理则将节点标记为正常节点 默认值 true 非 spring boot 项目配置


 http.failAllContinue = true
复制代码


spring boot 配置项


 spring.bboss.http.failAllContinue = true
复制代码

4 使用负载均衡器调用服务

HttpRequestProxy 组件提供了非常丰富的服务调用 API,可以去查看对应的 api 了解。

4.1 一个简单的案例

使用负载均衡器调用服务,在指定服务集群组 report 调用 rest 服务/testBBossIndexCrud,返回 json 字符串报文,通过循环调用,测试负载均衡机制


@Test   public void testGet(){      String data = HttpRequestProxy.httpGetforString("report","/testBBossIndexCrud");      System.out.println(data);      do {         try {            data = HttpRequestProxy.httpGetforString("report","/testBBossIndexCrud");         } catch (Exception e) {            e.printStackTrace();         }         try {            Thread.sleep(3000l);         } catch (Exception e) {            break;         }         try {            data = HttpRequestProxy.httpGetforString("report","/testBBossIndexCrud");         } catch (Exception e) {            e.printStackTrace();         }         try {            data = HttpRequestProxy.httpGetforString("report","/testBBossIndexCrud");         } catch (Exception e) {            e.printStackTrace();         }//       break;      }      while(true);   }
复制代码

4.2 返回简单结果对象或者 基本数据类型

@ResponseBodypublic Map<String, Object> queryUrl(String isParam,String url ) {
Map<String, Object> ajaxData = new HashMap<>(); ajaxData.put(HNanConstant.resultCode, HNanConstant.SUCCESS); ajaxData.put(HNanConstant.resultInfo, ""); Map params = new HashMap(); params.put("isParam",isParam); params.put("url",url); try { //返回简单结果对象或者 基本数据类型 String data = HttpRequestProxy.sendJsonBody(params, "/visualops/webpage/queryUrl.api", String.class);//返回String int data = HttpRequestProxy.sendJsonBody(params, "/visualops/webpage/queryUrl.api", int.class);//返回int short data = HttpRequestProxy.sendJsonBody(params, "/visualops/webpage/queryUrl.api", short.class);//返回short long data = HttpRequestProxy.sendJsonBody(params, "/visualops/webpage/queryUrl.api", long.class);//返回long float data = HttpRequestProxy.sendJsonBody(params, "/visualops/webpage/queryUrl.api", float.class);//返回float double data = HttpRequestProxy.sendJsonBody(params, "/visualops/webpage/queryUrl.api", double.class);//返回double boolean data = HttpRequestProxy.sendJsonBody(params, "/visualops/webpage/queryUrl.api", boolean.class);//返回boolean Map data = HttpRequestProxy.sendJsonBody(params, "/visualops/webpage/queryUrl.api", Map.class);//返回map对象 PoBean data = HttpRequestProxy.sendJsonBody(params, "/visualops/webpage/queryUrl.api", PoBean.class);//返回普通java对象 if(data != null){ ajaxData.put("data", data); } else { ajaxData.put("data", url); } } catch (Exception e) { log.error("queryUrl error", e); ajaxData.put(HNanConstant.resultCode, HNanConstant.FAIL); } return ajaxData;
}
复制代码

4.3 返回集合对象

可以非常方便地返回 List,Set,Map 集合对象


public @ResponseBodyMap<String, Object> queryErrorMessage(HttpServletRequest request) {    String param = request.getParameter("data");    Map<String, Object> ajaxData = new HashMap<>();    ajaxData.put(HNanConstant.resultCode, HNanConstant.SUCCESS);    ajaxData.put(HNanConstant.resultInfo, "");    try {       List<ErrorInfo> result = HttpRequestProxy.sendJsonBodyForList(param, "/visualops/webpage/queryErrorMessage.api", ErrorInfo.class);            Set<ErrorInfo> set = HttpRequestProxy.sendJsonBodyForSet(param, "/visualops/webpage/queryErrorMessage.api", ErrorInfo.class);            Map<String,ErrorInfo> map = HttpRequestProxy.sendJsonBodyForMap(param, "/visualops/webpage/queryErrorMessage.api", String.class,ErrorInfo.class);        ajaxData.put("data", result);    } catch (Exception e) {        log.error("queryErrorMessage error", e);        ajaxData.put(HNanConstant.resultCode, HNanConstant.FAIL);    }    return ajaxData;}
复制代码


总之,通过 HttpRequestProxy 调用服务就像我们调用本地类方法一样,支持 post、get、put、delete、head http 请求处理方法,提供 or mapping 机制,可以根据实际需要使用相关 api 即可。

5.服务发现机制的两种工作模式

本文开头介绍了 http 负载均衡器服务发现支持从各种数据源获取和发现服务地址:


zookeeper,etcd,consul,eureka,db,其他第三方注册中心


为了支持第三方注册中心,服务发现机制的提供两种工作模式:

5.1 主动发现模式

bboss 通过调用 http.discoverService 配置的服务发现方法,定时从数据库和注册中心中查询最新的服务地址数据清单,本文上面介绍的 http.discoverService 就是一种主动定时发现模式

5.2 被动发现模式

监听 apollo,zookeeper,etcd,consul,eureka 等配置中心中管理的服务节点地址清单数据变化,监听路由规则变化,更新本地服务器地址清单,适用于发布订阅模式,对应的 api


//当路由规则发生变化时,可以调用下面的api更新HttpProxyUtil.changeRouting(String poolName,String newCurrentRounte) //切换服务组poolName对应的路由规则//当服务节点发生变化时,可以调用下面的api更新public static void handleDiscoverHosts(String poolName, List<HttpHost> hosts) //更新服务节点
//当路由规则和服务节点同时发生变化时,调用下面的api更新 /** * * @param poolName 服务组名称 * @param hosts 新的主机节点信息 * @param newCurrentRounte 新的路由组 */ public static void handleDiscoverHosts(String poolName, List<HttpHost> hosts,String newCurrentRounte)
复制代码


被动发现模式示例代码如下:


//模拟被动获取监听地址清单List<HttpHost> hosts = new ArrayList<HttpHost>();// https服务必须带https://协议头,例如https://192.168.137.1:808HttpHost host = new HttpHost("192.168.137.1:808");hosts.add(host);
host = new HttpHost("192.168.137.1:809"); hosts.add(host);
host = new HttpHost("192.168.137.1:810");hosts.add(host);//将被动获取到的地址清单加入服务地址组report中,动态节点发现HttpProxyUtil.handleDiscoverHosts("report",hosts);
//动态路由切换
HttpProxyUtil.changeRouting(String poolName,String newCurrentRounte)
复制代码

5.3 基于 apollo 配置中心被动发现模式示例

首先定义一个 apollo 监听器


package org.frameworkset.http.client;

import com.ctrip.framework.apollo.ConfigChangeListener;import com.ctrip.framework.apollo.enums.PropertyChangeType;import com.ctrip.framework.apollo.model.ConfigChange;import com.ctrip.framework.apollo.model.ConfigChangeEvent;import org.frameworkset.spi.remote.http.HttpHost;import org.frameworkset.spi.remote.http.proxy.HttpProxyUtil;import org.slf4j.Logger;import org.slf4j.LoggerFactory;
import java.util.ArrayList;import java.util.List;
/** * <p>Description: </p> * <p></p> * <p>Copyright (c) 2020</p> * @Date 2020/8/2 20:07 * @author biaoping.yin * @version 1.0 */public class AddressConfigChangeListener implements ConfigChangeListener { private static Logger logger = LoggerFactory.getLogger(AddressConfigChangeListener.class); private void handleDiscoverHosts(String _hosts,String poolName){ if(_hosts != null && !_hosts.equals("")){ String[] hosts = _hosts.split(","); List<HttpHost> httpHosts = new ArrayList<HttpHost>(); HttpHost host = null; for(int i = 0; i < hosts.length; i ++){ host = new HttpHost(hosts[i]); httpHosts.add(host); } //将被动获取到的地址清单加入服务地址组report中 HttpProxyUtil.handleDiscoverHosts(poolName,httpHosts); } } /** * //模拟被动获取监听地址清单 * List<HttpHost> hosts = new ArrayList<HttpHost>(); * // https服务必须带https://协议头,例如https://192.168.137.1:808 * HttpHost host = new HttpHost("192.168.137.1:808"); * hosts.add(host); * * host = new HttpHost("192.168.137.1:809"); * hosts.add(host); * * host = new HttpHost("192.168.137.1:810"); * hosts.add(host); * //将被动获取到的地址清单加入服务地址组report中 * HttpProxyUtil.handleDiscoverHosts("schedule",hosts); */ public void onChange(ConfigChangeEvent changeEvent) { logger.info("Changes for namespace {}", changeEvent.getNamespace()); ConfigChange change = null; for (String key : changeEvent.changedKeys()) { if(key.equals("schedule.http.hosts")){//schedule集群 change = changeEvent.getChange(key); logger.info("Found change - key: {}, oldValue: {}, newValue: {}, changeType: {}", change.getPropertyName(), change.getOldValue(), change.getNewValue(), change.getChangeType()); if(change.getChangeType() == PropertyChangeType.MODIFIED) { String _hosts = change.getNewValue(); handleDiscoverHosts(_hosts, "schedule"); }
} else if(key.equals("http.hosts")){//default集群 change = changeEvent.getChange(key); logger.info("Found change - key: {}, oldValue: {}, newValue: {}, changeType: {}", change.getPropertyName(), change.getOldValue(), change.getNewValue(), change.getChangeType()); if(change.getChangeType() == PropertyChangeType.MODIFIED) { String _hosts = change.getNewValue(); handleDiscoverHosts(_hosts, "default"); }

}
} }}
复制代码


从 apollo 加载配置启动 http proxy:


    @Before    public void startPool(){//    HttpRequestProxy.startHttpPools("application.properties");        /**         * 从apollo加载配置启动http proxy:         * 配置了两个连接池:default,schedule         * apollo namespace:  application         * 服务地址变化发现监听器: org.frameworkset.http.client.AddressConfigChangeListener         */
HttpRequestProxy.startHttpPoolsFromApollo("application","org.frameworkset.http.client.AddressConfigChangeListener"); } @Test public void testGet(){ String data = null; try { //服务调用 data = HttpRequestProxy.httpGetforString("schedule", "/testBBossIndexCrud"); } catch (Exception e){ e.printStackTrace(); } System.out.println(data); do { try { data = HttpRequestProxy.httpGetforString("schedule","/testBBossIndexCrud"); } catch (Exception e) { e.printStackTrace(); } try { Thread.sleep(3000l); } catch (Exception e) { break; }
} while(true); }
复制代码


Apollo 配置使用参考文档:https://esdoc.bbossgroups.com/#/apollo-config


直接调用 http proxy api 监听路由变化和节点变化


       /**       * 1.服务健康检查       * 2.服务负载均衡       * 3.服务容灾故障恢复       * 4.服务自动发现(apollo,zk,etcd,consul,eureka,db,其他第三方注册中心)       * 配置了两个连接池:default,report       * 本示例演示基于apollo提供配置管理、服务自动发现以及灰度/生产,主备切换功能       */

HttpRequestProxy.startHttpPoolsFromApolloAwaredChange("application");
复制代码

6.主备和异地灾备配置和服务发现

主备和异地灾备配置和服务发现


地址格式配置,其中 routing 标识服务地址或者路由标记


ip:port|routing


例如:


#指定了每个地址对应的地区信息,可以按照地区信息进行路由http.hosts=192.168.137.1:808|beijing,192.168.137.1:809|beijing,192.168.137.1:810|shanghai
复制代码


指定 beijing 地区路由信息或者主节点标识信息


# 指定本地区信息,系统按地区部署时,指定地区信息,# 不同的地区请求只路由到本地区(beijing)对应的服务器,shanghai的服务器作为backup服务器,# 当本地(beijing)的服务器都不可用时,才将请求转发到可用的上海服务器http.routing=beijing
复制代码


指定 shanghai 地区路由信息或者主节点标识信息


# 指定本地区信息,系统按地区部署时,指定地区信息,# 不同的地区请求只路由到本地区(shanghai)对应的服务器,beijing的服务器作为backup服务器,# 当本地(shanghai)的服务器都不可用时,才将请求转发到可用的beijing服务器http.routing=shanghai
复制代码


为了避免配置文件混乱,可以将 http.routing 对应的值做成环境变量,配置文件中只需要引用环境变量名称即可:


http.routing=#[area]#bboss支持混合模式的变量和常量拼接,例如#http.routing=#[county]aaac#[area]
复制代码


这样我们在 os(linux,unix,windows)中配置名称为 area 的环境变量即可。


带路由信息的服务发现机制:可以动态变化服务地址的 routing 信息


package org.frameworkset.http.client;

import org.frameworkset.spi.assemble.GetProperties;import org.frameworkset.spi.remote.http.ClientConfiguration;import org.frameworkset.spi.remote.http.HttpHost;import org.frameworkset.spi.remote.http.proxy.HttpHostDiscover;import org.frameworkset.spi.remote.http.proxy.HttpServiceHostsConfig;
import java.util.ArrayList;import java.util.List;
public class DemoHttpHostDiscover extends HttpHostDiscover { private int count = 0; @Override protected List<HttpHost> discover(HttpServiceHostsConfig httpServiceHostsConfig, ClientConfiguration configuration, GetProperties context) {
List<HttpHost> hosts = new ArrayList<HttpHost>(); HttpHost host = new HttpHost("192.168.137.1:808|beijing"); hosts.add(host); if(count != 2) {//模拟添加和去除节点 host = new HttpHost("192.168.137.1:809|beijing"); hosts.add(host); } else{ System.out.println("aa"); } //可以动态变化服务地址的routing信息,模拟改变路由信息 if(count > 10 && count < 15) { host = new HttpHost("192.168.137.1:810|beijing"); } else{ host = new HttpHost("192.168.137.1:810|shanghai"); } hosts.add(host); count ++; return hosts; } /** * 返回null或者false,忽略对返回的null或者空的hosts进行处理; * 返回true,要对null或者空的hosts进行处理,这样会导致所有的地址不可用 * * @return 默认返回null */ protected Boolean handleNullOrEmptyHostsByDiscovery(){ return null; }}
复制代码

7.健康检查服务

可以通过 http.health 属性指定健康检查服务,服务为相对地址,不需要指定 ip 和端口,例如:


  • 设置默认集群组健康服务


configs.put("http.health","/health.html");//health监控检查地址必须配置,否则将不会启动健康检查机
复制代码


  • 设置特定集群组健康服务


configs.put("report.http.health","/health.html");//health监控检查地址必须配置,否则将不会启动健康检查机
复制代码


  • 健康检查服务开启条件 http.healthCheckInterval 必须大于 0,单位毫秒,默认值-1,设置示例:http.healthCheckInterval=1000 同时必须设置 http.health 健康检查服务,例如:http.health=/health.html


bboss 以 get 方式发送 http.health 对应的健康检查服务请求,健康检查服务只需要响应状态码为 200-300 即认为服务节点健康可用

8.安全认证

bboss 支持 http basic 认证机制,亦可以通过添加 header 参数实现 jwt token 认证机制。


http basic 认证机制设置


# 服务全认证账号配置schedule.http.authAccount=elasticschedule.http.authPassword=changeme
复制代码


jwt token 认证设置,直接申请 jwt token,并经 token 设置到 http header 中,模拟 token 有效期管理的组件(可以根据实际场景调整):TokenManger


//判断服务token是否过期,如果过期则需要重新调用token服务申请token            TokenInfo tokenInfo = tokenManager.getTokenInfo();            String token = "Bearer " + tokenInfo.getAccess_token();//"Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJqdGkiOiJkZWZhdWx0XzYxNTE4YjlmM2UyYmM3LjEzMDI5OTkxIiwiaWF0IjoxNjMyNzM0MTExLCJuYmYiOjE2MzI3MzQxMTEsImV4cCI6MTYzMjc0MTMxMSwiZGV2aWNlX2lkIjoiYXBwMDMwMDAwMDAwMDAwMSIsImFwcF9pZCI6ImFwcDAzIiwidXVpZCI6ImFkZmRhZmFkZmFkc2ZlMzQxMzJmZHNhZHNmYWRzZiIsInNlY3JldCI6ImFwcDAzMVEyVzNFd29ybGQxMzU3OVBhc3NBU0RGIiwiaXNzdWVfdGltZSI6MTYzMjczNDExMSwiand0X3NjZW5lIjoiZGVmYXVsdCJ9.mSl-JBUV7gTUapn9yV-VLfoU7dm-gxC7pON62DnD-9c";            Map<String,String> headers = new LinkedHashMap<>();                        headers.put("Authorization",token);            Map datas = HttpRequestProxy.sendJsonBody("jwtservice",params,"/api/queryUsers.api", headers,Map.class);
复制代码


ssl 证书配置,参考文档:设置ssl证书

9.开发交流

bboss http 交流 QQ 群:21220580,166471282


bboss http 微信公众号:



用户头像

大河

关注

还未添加个人签名 2022.08.15 加入

还未添加个人简介

评论

发布
暂无评论
bboss http负载均衡器使用指南_HTTP_大河_InfoQ写作社区