Nacos加持,灰度配置与管理

1,449 阅读2分钟

公众号搜索“码路印记”,点关注不迷路!

在上一篇文章《记一次反射应用实践》 中,灰度判断逻辑仅仅使用随机数进行模拟。由于示例Demo使用了Dubbo+Nacos技术,而Nacos也是可以作为配置中心的。所以我就现学现卖,把Nacos集成进来做下尝试,让我们的Demo功能更加丰富。

灰度设计

首先回到我们的问题本身。对于大型的系统,新老系统交替意味着引入变更,变更就有带来风险的可能,为了让我们的新老系统交替时风险可控,需要让新系统支持灰度发布,逐步放量。

批量灰度就是常用的一种手段,比如在以用户维度的业务系统中,可以对用户ID对100取模,就可以把用户分为100份。我们可以先对10%的用户开放新功能,灰度一段时间后,若系统运行正常,可以指逐步开放到20%、50%直到100%;并且灰度应该支持动态修改,一来可以在不走线上发布的情况下动态实现灰度批量的扩大或缩小,二来可以在发现新系统bug时立刻关闭灰度开关,及时止血。另外,新系统需要有联调、测试的阶段,尤其在线上系统需要挑选个别测试用户试用,所以需要通过白名单功能开放特殊通道;同理,若个别用户由于某些原因不能使用新功能,可以通过黑名单功能对这些用户进行屏蔽。

总结一下就是,批量灰度可以通过灰度批量百分比、白名单、黑名单来实现,三者之间的优先级为白名单>黑名单>灰度百分比,下面的代码体现了这一逻辑。

/**
 * 百分比灰度配置
 * <p>
 * 优先级:白名单 > 黑名单 > 灰度规则
 *
 * @author raysonxin
 * @since 2021/1/23
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class PercentGrayModel {

    /**
     * 百分比,整数。取值范围:[0,100],0代表关闭,100代表全量
     */
    private Integer percent;

    /**
     * 白名单
     */
    private List<Long> whiteList;

    /**
     * 黑名单
     */
    private List<Long> blackList;

    /**
     * 检查目标是否命中灰度
     *
     * @param target 目标
     * @return true-命中,false-未命中
     */
    public boolean hitGray(Long target) {
        // 数据异常
        if (null == target || target < 0L) {
            return false;
        }

        // 命中白名单,返回true
        if (!ObjectUtils.isEmpty(whiteList) && whiteList.contains(target)) {
            return true;
        }

        // 命中黑名单,返回false
        if (!ObjectUtils.isEmpty(blackList) && blackList.contains(target)) {
            return false;
        }

        // 无百分比灰度规则
        if (percent == null || percent < 0) {
            return false;
        }

        // 大于等于100,相当于全量
        if (percent >= 100) {
            return true;
        }

        // 按照灰度百分比计算是否命中
        return target % 100 < percent;
    }
}

Nacos配置管理

新建配置

为了方便,我直接使用了Nacos的Docker版本,安装Docker CE后,大家可以直接到这个地址下载并运行。浏览器输入http://localhost:8848/nacos,默认用户名和密码都是nacos。点击“配置管理-配置列表-➕”新增配置,然后发布即可(如下图):

  • Data ID:com.rsxtech.demo.consumer:gray-rule.json
  • Group:DEFAULT_GROUP
  • 配置内容:如下图Json结构。

image.png

代码集成

Nacos提供了Java SDK,同时提供了Spring、Spring Boot、Spring Cloud等方式的支持。本来我想使用Spring Boot版本的API,但是一直有问题未解决,最好只好使用Java SDK了。Java SDK集成也比较简单,可以参考这里查看SDK的使用姿势。

我们主要使用Nacos配置管理的两个API:

  • 获取配置:在应用程序启动时,主动从Nacos获取配置内容;
  • 监听配置修改:当Nacos配置修改时,可实时接收最新配置并生效。

首先在application.properties中增加配置项nacos.server-addr,然后新增配置管理类TransferGray,代码如下所示:

/**
 * 灰度配置获取
 *
 * @author raysonxin
 * @since 2021/1/23
 */
@Component
@Slf4j
public class TransferGray {

    @Value("${nacos.server-addr}")
    private String nacosServer;

    private static final String dataId = "com.rsxtech.demo.consumer:gray-rule.json";
    private static final String group = "DEFAULT_GROUP";

    /**
     * 配置内容
     */
    public static PercentGrayModel grayConfig;

    @PostConstruct
    public void init() throws NacosException, JsonProcessingException {
        Properties properties = new Properties();
        properties.put("serverAddr", nacosServer);

        ConfigService configService = NacosFactory.createConfigService(properties);

        // 启动后,主动拉取配置。
        String content = configService.getConfig(dataId, group, 5000);
        log.info("TransferGrayConfig get config={}", content);
        if (ObjectUtils.isEmpty(content)) {
            return;
        }

        ObjectMapper objectMapper = new ObjectMapper();
        grayConfig = objectMapper.readValue(content, PercentGrayModel.class);

        // 当Nacos中配置变更时,可接收新的内容。
        configService.addListener(dataId, group, new Listener() {
            @Override
            public Executor getExecutor() {
                return null;
            }

            @Override
            public void receiveConfigInfo(String s) {
                log.info("TransferGrayConfig get config={}", s);
                try {
                    ObjectMapper objectMapper = new ObjectMapper();
                    grayConfig = objectMapper.readValue(content, PercentGrayModel.class);
                } catch (Exception ex) {
                    log.error("TransferGrayConfig parse config error", ex);
                }
            }
        });
    }
}

配置获取

在代码中打个断点,启动应用程序,会看到我们获取到了配置内容。 image.png

配置发布与监听

在receiveConfigInfo方法中打个断点,然后打开刚才新建的配置项,进入编辑页;修改一下percent值,点击发布,即可发现我们的断点命中了并获取到了最新的配置内容。如下两图所示: image.png image.png

灰度接入

现在就可以修改之前的needTransfer方法了。这里我在原来的接口参数中增加了userId,便于按照灰度策略做判断。

@Data
@ToString
public class HelloRequestCmd implements Serializable {

    private static final long serialVersionID = 1L;

    /**
     * 用户id
     * */
    private Long userId;

    /**
     * 姓名
     */
    private String name;

    /**
     * 问候语
     */
    private String greeting;
}

为了简单起见,接口转发是针对当前方法参数做了转换与判断,如下所示:

    /**
     * 灰度判断逻辑
     *
     * @return true-命中灰度,执行转发;false-不转发
     */
    private boolean needTransfer(Object[] args) {
        // 这里先简单处理,实际情况下需要根据参数特点,编写一个方法解析灰度参数。
        // 比如我们的老系统,所有接口都包含一个用户请求对象,就可以找个找个参数,然后执行灰度判断。
        HelloRequestCmd cmd = (HelloRequestCmd) args[0];
        PercentGrayModel percentGrayModel = TransferGray.grayConfig;
        return percentGrayModel.hitGray(cmd.getUserId());
    }

实际情况下,判断接口是否转发需要根据每个方法的参数特点编写合适的解析方式,因为需要转发的方法参数是已知的,所以我们是有办法拿到的。往往我们系统中,为了统一封装请求参数,会把一些公共的参数放到参数基类或者HTTP请求头中,这会大大增加我们处理的便利。

测试效果

现在就可以做下测试了,这里我使用了IDEA自带的HTTP测试工具。对应的白名单、黑名单、灰度测试结果截图如下:

白名单测试

image.png

黑名单测试

image.png

正常灰度测试

image.png image.png

公众号搜索“码路印记”,点关注不迷路!