1

问题:我们在生产中使用了几个 lambdas 和 dynamodb 表,当发布新版本的代码时,我们有时会剥离属性或将属性添加到表类(使用 com.amazonaws.services.dynamodbv2.datamodeling 的 Java 代码)高级 API。当我们部署新版本的代码并查询表时,如果现有项目不存在新属性,或者我们从代码中删除属性。它破坏了代码,因为我们的 Item 对象与生产数据不一致。

我们希望通过添加具有默认值的额外属性或删除现有项目的属性来避免处理 prod 中的数据。在我们部署新版本之前,出于各种有关竞争条件和一致性的原因。如果我们在代码级别处理它会更可取,如果属性不存在自动添加默认值。或者让代码忽略项目/表格类中未定义的属性。这可以使用高级java sdk api吗?

我们提出的另一个解决方案是创建一个服务,该服务提供 delta(代码项对象和 prod 中的数据之间的变化),由一个 pretraffic lambda 执行,该 lambda 在部署新版本的 lambda 时处理数据。然而,我们想避免这种情况。

package com.ourcompany.module.dynamodb.items;

import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBAttribute;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBHashKey;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBTable;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBVersionAttribute;
import lombok.Data;

@Data
@DynamoDBTable(tableName = "Boxes")
public class BoxItem {

@DynamoDBHashKey(attributeName = "boxID")
private String channelID;

#This is the field we added, the previous version did not have this field, in prod we have many items without this attribute
@DynamoDBAttribute(attributeName = "lastTimeAccess")
private String lastTimeAccess;

@DynamoDBAttribute(attributeName = "initTime")
private String initTime;

@DynamoDBAttribute(attributeName = "boxIDhash")
private String streamBoxIDHash;

@DynamoDBAttribute(attributeName = "CFD")
private String cfd;

@DynamoDBAttribute(attributeName = "originDomain")
private String originDomain;

@DynamoDBAttribute(attributeName = "lIP")
private String lIP;

@DynamoDBAttribute(attributeName = "pDomain")
private String pDomain;

上面是我们更改的项目,添加了属性。

package com.ourcompany.shared.module.repository.dynamob;

import ...

public class DynamoDbRepository<Item, Key> {

private final DynamoDBMapper mapper;
private static final Logger logger = LogManager.getLogger(DynamoDbRepository.class);

@Inject
public DynamoDbRepository() {
    val client = AmazonDynamoDBClientBuilder
            .standard()
            .withRegion(Regions.US_EAST_1) // TODO: hardcoded now
            .withRequestHandlers(new TracingHandler(AWSXRay.getGlobalRecorder()))
            .build();


    DynamoDBMapperConfig dynamoDBMapperConfig = new DynamoDBMapperConfig.Builder()
                                                   .withSaveBehavior(DynamoDBMapperConfig.SaveBehavior.UPDATE_SKIP_NULL_ATTRIBUTES)
                                                   .withTableNameResolver(new DynamoDBTableNameResolver())
                                                   .build();

    mapper = new DynamoDBMapper(client, dynamoDBMapperConfig);

}
/*
* Many accessor methods are listed here below is the one where we have issue,
*/
public List<Item> findBy(Map<String, Condition> filter, final Class<Item> clazz) throws Exception {
    try {
        logger.trace("DynamoDbRepository findBy(filter, class)");
        val scanExpression = new DynamoDBScanExpression().withScanFilter(filter).withConsistentRead(true);
        PaginatedScanList<Item> ls = mapper.scan(clazz, scanExpression);
        ls.loadAllResults();
        return ls;
    } catch (Exception ex) {
        logger.trace(ex.getMessage());
        throw handleException(ex);
    }
}

以上是我们的 Dynamob DB 映射器类,但只包含有问题的方法。我们能够通过记录跟踪到 logger.trace("DynamoDbRepository findBy(filter, class)"); 行,并且我们知道问题出现在映射器中。但是它不会吐出异常,所以我们看不到实际的错误。我们必须通过从 prod 中的表中清除所有数据来解决该问题,然后让新版本的代码使用属性重新填充条目并且代码正常工作。

4

2 回答 2

1

对于一个小窗口或者如果您运行一个长期的拆分测试,您将遇到这个问题。

我们通过以下方式解决:

  1. 无论哪个 lambda 使用属性,请确保它们检查属性是否存在并对其进行处理。如果所需的属性不存在,则抛出错误并假设它失败。如果您在事务路径中使用它,这可能是一个问题,但会让您知道失败的原因以及如何修复它。这是用于拆分测试。
  2. 构建您的代码以向后兼容至少一个版本。确保在所需版本到位后删除代码。
  3. 如果窗口很小且负载不重,您可以让服务无法捕获较新的版本。

希望能帮助到你。

于 2018-08-27T19:23:24.120 回答
0

只是关于这个问题的更新。在听取了@zapl 关于尝试打印堆栈跟踪的建议后,我发现 AWS DynamoDB Mapper 或 SDK 的工作方式绝对没有问题。我期待从 SDK 中捕获一些堆栈跟踪,但没有,经过更仔细的跟踪后,我发现 Java 开发人员误诊了这个问题,真正的问题是他们有逻辑来过滤依赖于新字段的流。因此,故事的教训,架构代码以向后兼容至少落后一个版本!

于 2018-09-04T18:59:15.473 回答