使用统一转换服务来处理不同数据展现的思路和实现

本文示例代码:https://github.com/flym/train-propertytranslate

本文描述了这样一个场景:
针对于一个功能场景,第三方过来的数据格式均不相同,但需要通过一个统一的功能接口来进行调用,然后根据不同来源数据格式进行不同的数据展现。在后端实现时,尽量不要通过if else进行硬编码,而是通过配置的方式来完成数据的处理和呈现。

场景中提到了几个概念,如下:

  • 功能场景和数据来源:分组信息,针对同一个来源,其格式是相同的。不同来源的数据格式不一样
  • 统一功能接口:调用入口是统一的,即方法名,参数定义,以及返回格式都是相同的,仅参数内容不相同
  • 配置化:场景是通用的,可以通过配置来实现,而不是在业务代码中硬编码
  • 不同的数据展现:可以针对不同的分组和场景通过模板来进行渲染,而模板本身是可以配置的,这样即隔离了数据封装这一层。

举例如下, 如下的一个数据内容:

{
    "trade_fullinfo_get_response": {
        "trade": {
            "seller_nick": "我在测试",
            "pic_name": "T1jVXXXePbXXaoPB6a_091917.jpg",

            "receiver_name": "东方不败",
            "buyer_message": "要送的礼物的,不要忘记的哦",
            "receiver_city": "杭州市",
            "receiver_district": "西湖区",
            "orders": {
                "order": [
                    {
                        "title": "苹果",
                        "unit": "个",
                        "oid": 1,
                        "price": 1.1,
                        "sum": 1
                    }
                ]
            },
        }
    }
}

通过转换服务处理,可以展现为下面两种不同的数据内容(以html渲染为例)

渲染场景1
渲染场景2

本文从数据格式,转换器,转换过程,数据渲染几个方面来描述这一思路.

继续阅读“使用统一转换服务来处理不同数据展现的思路和实现”

一种使用Logback进行日志脱敏的方法和实现

本文描述了一种使用logback进行项目中打印的日志进行脱敏的一种处理方法,通过底层消息转换,字段过滤,以及数据加密,对象lazy化多种方法来完成整个实现体系。

参考文章:https://blog.csdn.net/lrcxl/article/details/78723756 关于实现log4j2日志脱敏的一种方案

整个实现分为以下4个步骤

  1. 底层消息转换 logback层提供扩展方法,允许注入额外的参数填充实现
  2. 字段过滤 参数toString化工具处理
  3. 数据加密 信息加/解密处理
  4. 对象lazy化, 多种信息转换实现

1 logback层提供扩展方法,允许注入额外的参数填充实现

以下实现中修改了logback-classic的源码, 修改点为 LoggingEvent#getFormattedMessage

我们在logback-classic版本中提供了用于处理消息和参数进行格式化的处理类接口如下

public interface LogbackMessageFormat {
    String format(String message, Object[] argumentArray);
}

其默认实现为 

public class Slf4jLogbackMessageFormat implements LogbackMessageFormat {
    @Override
    public String format(String message, Object[] argumentArray) {
        return MessageFormatter.arrayFormat(message, argumentArray).getMessage();
    }
}

此即为原logback-classic的默认实现。 为了让外部注入额外的实现,提供了一个工厂类,以让外部设置新的处理实现,如下参考所示:

public class LogbackMessageFormatFactory {
    @Getter
    @Setter
    private static LogbackMessageFormat INSTANCE = new Slf4jLogbackMessageFormat();
}

通过此工厂方法,即可设置自己的实现了,一个用于脱敏的参考实现如下

public class SelfImpl implements LogbackMessageFormat {
    @Override
    public String format(String message, Object[] argumentArray) {
        ......
        //以下代码为将参数对象toString化,相应的脱敏即隐藏在此方法中
        for(int i = 0; i < size; i++) {
            argumentArray[i] = ToStringUtils.toString(argumentArray[i]);
        }

        //调用原始格式化信息为字符串
        return StringUtils.format(message, argumentArray);
    }
}

继续阅读“一种使用Logback进行日志脱敏的方法和实现”

ZooKeeper Watcher 机制源码解释(转)

本文转自 https://www.ibm.com/developerworks/cn/opensource/os-cn-apache-zookeeper-watcher/index.html

ZooKeeper 允许客户端向服务端注册一个 Watcher 监听,当服务端的一些指定事件触发了这个 Watcher,那么就会向指定客户端发送一个事件通知来实现分布式的通知功能。

ZooKeeper 的 Watcher 机制主要包括客户端线程、客户端 WatchManager 和 ZooKeeper 服务器三部分。在具体工作流程上,简单地讲,客户端在向 ZooKeeper 服务器注册 Watcher 的同时,会将 Watcher 对象存储在客户端的 WatchManager 中。当 ZooKeeper 服务器端触发 Watcher 事件后,会向客户端发送通知,客户端线程从 WatchManager 中取出对应的 Watcher 对象来执行回调逻辑。

    private final HashMap<String, HashSet<Watcher>> watchTable =new HashMap<String, HashSet<Watcher>>();

    public synchronized void addWatch(String path, Watcher watcher) {
        HashSet<Watcher> list = watchTable.get(path);
        if(list == null) {
            list = new HashSet<Watcher>(4);
            watchTable.put(path, list);
        }
        list.add(watcher);

        HashSet<String> paths = watch2Paths.get(watcher);
        if(paths == null) {
            // cnxns typically have many watches, so use default cap here
            paths = new HashSet<String>();
            watch2Paths.put(watcher, paths);
        }
        paths.add(path);
    }

继续阅读“ZooKeeper Watcher 机制源码解释(转)”

使用greys找到泄漏的本地线程变量值

在程序代码中,出现过这样一种情况,在一个新的线程组中,尝试去获取一个threadLocal的值,按照相应的程序代码,应该是不能获取到的。但是在实际的运行过程中,却发现能够获取到相应的变量值。
在我们的程序代码中,使用了shiro来存储相应的用户信息,即一个简化版的的权限管理程序。其中,在标准的web程序中,shiro拦截器会把相应的会话信息存储在session中,并且在应用代码中,可以通过一个threadLocal的ThreadContext来获取相应的subject,进而拿到相应的会话值.相应的代码如下所示:

    public static UserId getUserId() {
        Optional<Subject> optional = Optional.ofNullable(ThreadContext.getSubject());
        return optional.map(t -> t.getSession(false))
                .map(t -> t.getAttribute("userId"))
                .orElse(null);
    }

因为相应的数据为web调用时才会注入到sessionId,而如果是一个定时类的任务,那么理论上应该是不会有相应的session对象存在,那么即不会有相应的subject存在,在调用此方法时即会返回在调用getSubject时失败。但是在实际上时,却发生调用subject不为null,进而在调用getSession方法时出现了错误信息。

相应的调用链看起来应该像是这样

  1. 线程1调用了 ThreadContext.setSubject 方法,设置了session信息
  2. 线程1完成整个业务方法的运行,相应的session信息被销毁,但相应的removeSubject方法并没有被调用
  3. 线程1重新以任务的形式执行任务代码,其中调用了getSubject,但不能拿到相应的数据

在这种调用链中,我们只看到了最终的现场,但原始的设置值的现场即不能复现,即不清楚数据是什么时候,哪个调用任务进行处理的。整个问题称之为数据泄漏,即数据在不应该出现的地方出现了。
处理这种问题,一种作法即是异常标记法。详细的步骤如下所示:

  1. 在调用 ThreadContext.setSubject 时,设置一个异常线程栈(Exception),此里面封装了当前调用的整个路径
  2. 将异常线程栈(字符串形式)存储在一个threadLocal变量中,方便后面获取
  3. 在调用 getSubject 时,如果发生了相应的错误信息,即表示复现了相应的错误场景
  4. 这时候即将之前存储到threadLocal中的字符串获取出来,打印出原始的setSubject调用路径

整个作法参考于alibaba druid的数据库连接泄漏检测。

本文介绍了在不修改源代码的情况下,并且在线上环境,使用greys,来监控相应的调用,当出现了满足条件的场景时,自动打印出相应的调用路径。

继续阅读“使用greys找到泄漏的本地线程变量值”

基于jgroups分布式缓存实现redis服务的fallback处理

现在的系统当中,实现缓存均是采用redis来完成,一个典型的场景即是将用户的会话信息存放于redis当中,以实现分布式缓存以及服务的高可用。当nginx在进行请求分发时,不需要采用ip hash,随机分发请求,后端直接读取中央缓存即可完成相应的业务处理。但这种场景同样存在一个问题,即是缓存的高可用问题。特别是相应的redis服务由外部提供时,这个问题更加明显。初期redis服务为一个单点时,当服务挂掉时,会导致整个服务将不再可用。

解决此问题有2个方案,一个是再使用同样的策略保证redis的高可用,如部署redis集群或者是sentinel模式,另外一种是直接放弃对redis的高依赖性,在应用层直接进行缓存的fallback支持。在本场景中,我们仅将redis作为一个缓存层使用,它的一些其它特性如发布订阅暂不考虑。

在本文中,我们使用了hystrix+caffeine+jgroups的方案,来完成整个redis的fallback化处理,通过hystrix实现对redis调用的断路及fallback处理,使用caffine提供高效本地缓存,使用jgroups完成多系统间的数据复制分发。 主要实现的目的在于,当redis挂掉之后,能够切换到本地分布式缓存,保证系统后续可用(不过之前存放在redis中的数据完全不再可见,会导致一部分的问题,如会话信息会丢失。可考虑仅将会话信息直接复制在本地缓存当中)

本方案适用于redis短时间由于网络或其它原因连接不上,但在相对短的时间内(如几分钟,不超过1个小时等)即会恢复的情况,这样本地缓存不会存储很多东西的情况.

继续阅读“基于jgroups分布式缓存实现redis服务的fallback处理”

greys在线诊断工具的主要实现

greys是一个使用java management tool进程注入javaagent实现在线系统的诊断一个工具。原github为(https://github.com/oldmanpushcart/greys-anatomy),其主要的功能在于系统不停机的情况下。可以查看系统中的线程信息,cpu使用情况,jmx信息,以及某个方法在运行时的调用栈,调用参数等。

一个典型的场景就是线上某个功能出bug,但是系统中并没有记录参数信息,这时候即可通过这个功能注入agent,临时地打印出这个调用方法的参数,以方便定位相应的问题。如无此工具,则只能改代码,然后重新上线。在这种情况下,可能出错的场景就不能再复现。(当然也有其它工具(如log monitor)作到在线系统参数记录开关的目的,这里不作描述)

本文主要描述greys是如何工作的,包括如何注入到在线系统,然后脚本client与注入后server端的交互,以及如何实现一个简单的参数拦截记录功能。从整个实现机制层面描述其工作原理。

继续阅读“greys在线诊断工具的主要实现”