Bootstrap

处理集群问题的一些思路

1、妙用nginx的重试机制

        nginx的上有服务器为A和B,当请求经过nginx转发到达A时,A突然宕机后,该怎么让请求继续得以执行成功返回响应呢?这就需要利用nginx的重试机制。

        重试就是再次尝试,容错的一种策略。当nginx收到A服务器的响应或者压根就没有响应时,nginx会根据默认或定义的配置,再将请求转发给其他节点,以顺利执行。

官方API:
Syntax:	proxy_next_upstream error | timeout | invalid_header | http_500 | http_502 | http_503 | http_504 | http_403 | http_404 | off ...;
Default:	proxy_next_upstream error timeout;
Context:	http, server, location
———————————————————————————————————————————————————————————————————————————————————————————
proxy_next_upstream error ;
proxy_next_upstream_timeout 6s;
proxy_next_upstream_tries 3;
 
upstream test {
    # 下面的配置其实是nginx自带的被动健康检查
    server 127.0.0.1:8001 fail_timeout=60s max_fails=2; # Server A
    server 127.0.0.1:8002 fail_timeout=60s max_fails=2; # Server B
    server 127.0.0.1:8003 fail_timeout=60s max_fails=2; # Server C
}

2、心跳检测,避免集群环境下的socket连接断开

        socket连接是浏览器与应用服务器之间通信最为轻便的方式,正常情况下,浏览器与服务器之间一直在互通消息,以保持socket持续连接。但在集群环境下,浏览器与服务器之间多了第三者---nginx,而nginx会与应用服务器之间存在超时断开的功能,默认是60s。

        如何避免因为nginx与应用服务间读超时而导致的socket断开呢?把超时时间调大不就完事了嘛。但你总不能调大无限大吧,再大的值也有边界,用户在边界时间操作仍然会出现问题。

proxy_read_timeout 600s;

        所以,必须采用心跳检测机制!这里的心跳检测,是指浏览器轮询向服务发送消息,以保持socket的持续连接。

Socket socket = new WebSocket(url);
socket.send(msg);

3、巧用redis计数器,实现编码规则的唯一性

        这里编码规则不是指雪花主键,而是业务相关的编码规则。我有专门一篇文章讲到集群模式下的雪花主键生成策略,感兴趣的可以移步。还是回归到编码规则吧,比如给某个业务的编码制定规则YYYYMMDD-XXX,那就是年+月+日+当天编号,例如20210821-001、20210821-002、20210821-003等。后面的数字是绝对不允许重复的,那么在集群(nginx)+并发(jmeter压测)情况下该如何实现呢?

        这里就需要redis的计数器功能

        该命令具有原子性,可以保证取数唯一,所以我们只需要调一下现成的API即可。

//之前用的java.util.concurrent.atomic.AtomicLong,现在改用redis计数器
final rs = BeanUtils.getBeanWithNonNull(RedissonClient.class);
RAtomicLong r = rs.getAtomicLong(CommCodeNumberCached.CODENUMBER_COUNTER+this.cacheKey);
long nextSerial = redisCurNumber.incrementAndGet();

4、【开发库环境】要与【测试库环境】彻底隔离,保持纯净

       其实我们已经保持了纯净,但问题是某些同事为了更方便地复现问题,修改缺陷,所以直接拿到了测试的redis连接信息。这就导致了开发人员连接开发数据库,却连测试的reids环境,造成测试环境的redis值经常变化,所以测试环境总是出问题。

        原因很简单,但排查过程却很艰难,主要是不易复现。

5、集群中的应用服务器时间应统一

        为了避免时间不统一的问题,我们产品中取时间都是根据Java方法获取从应用服务器获取的的,不再用Oracle提供的如sysdate等时间。但在集群环境下,各个应用节点的时间也可能存在不一致的情况,这就会导致很多操作出现顺序混乱的问题。

        这里提出几点解决办法:

        1、应用服务器统一指定获取时间的URL;

        2、统一从redis中获取时间

127.0.0.1:6382> time
1) "1495780564"
2) "894089"

6、本地缓存和集群缓存的API使用一定要兼容

        产品同时支持ehcache缓存和redis缓存,同用org.springframework.cache这套API;在用的时候,是有分别的;

//DefaultQueryCached
private static final CachableConcurrentHashMap<ConcurrentHashMap<String, String>> queryCached = new CachableConcurrentHashMap<>(CacheConstants.MODULE_FRW_QUICKQUERY);

//在使用本地缓存时,是可以这样写的,因为本地缓存用的还是本地的物理地址
ConcurrentHashMap<String, String> queryNoMap = queryCached.get(pgmId);
queryNoMap = new ConcurrentHashMap<>();
queryCached.put(pgmId, queryNoMap);
queryNoMap.put(userId, queryNo);

//但这种写法在redis缓存就不允许了,因为取出,设值的操作已经结束,再次修改定义的变量queryNoMap无济于事了.所以需要调整一下顺序
ConcurrentHashMap<String, String> queryNoMap = queryCached.get(pgmId);
queryNoMap = new ConcurrentHashMap<>();
queryNoMap.put(userId, queryNo);
queryCached.put(pgmId, queryNoMap);

7、附件上传限制-nginx的配置

        链路上的节点多了,那么限制也会变多,这个问题是由于nginx限制了请求体大小,拦截了请求.nginx.conf添加client_max_body_size 2048M;配置即可解决。

8、java应用读取不到properties中属性文件

        仅在测试环境有这种问题,本机环境都是好的;后来发现测试集群中,A节点是好的,B节点则不行,则对比两者java与properties文件,完全一致。后来用editPlus看到了两个properties的编码不一致,A节点是UTF-8,B是UTF-8 BOM。原来是测试人员用WinSCP软件直接修改配置文件,保存成了BOM的格式。

悦读

道可道,非常道;名可名,非常名。 无名,天地之始,有名,万物之母。 故常无欲,以观其妙,常有欲,以观其徼。 此两者,同出而异名,同谓之玄,玄之又玄,众妙之门。

;