前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >DDL-脏数据层的实现

DDL-脏数据层的实现

作者头像
健程之道
发布2019-11-03 21:02:46
7780
发布2019-11-03 21:02:46
举报
文章被收录于专栏:健程之道健程之道

在我们的项目中,经常会有一些数据会涉及到频繁更改。如果每次都从数据库中读取再修改,这样不仅浪费时间,而且还更加危险。那此时我们究竟该如何解决这个问题呢?此时,DDL(脏数据层)就出现了。

首先说一下为什么操作不能保证原子性就会危险,因为这时就很有可能出现同时修改的情况,最终的结果极有可能并不是你所希望的(除非这些操作都是幂等性,但这种情况应该比较少)。如果是利用数据库中的锁,一来我在项目中用的比较少,二来也增加了维护难度。当然,有人说可以利用CAS,那针对一些复杂的情况(比如类里面属性的修改会有一些相关性,你的一次更改需要涉及几个属性等),可能你还是需要单独设计一套系统,而且还会有经典的ABA问题。如果你是利用CAS解决的,希望能够在下方评论区告知,就当互相学习。

那现在来说说DDL层具体是什么。DDL全称是Dirty Data Layer,即脏数据层。针对那些在系统运行经常会更改的domain类,我们将其再做一次封装,组成一个类似map的形式。单独由一组线程来管理这些map,每当有数据改动时,我们就往这个map中添加内容,而我们的线程则定期向数据库中写入内容。这样做的好处,首先是让你的每一次操作都没有IO的参与,提高了相应速度,而且定时提交意味着你可以把原本的几次提交变成为1次,减少了和数据库的交互。当然,缺点也是存在的,如果你的系统是分布式,那么你的这个DDL层的实现可能就没有那么方便,因为这些数据你可能需要存储在类似Redis这种共享缓存中,因此每次的拿和取就需要封装一下(这个应该算是小问题,因为原本就算你用的是本地缓存,所有操作依旧是需要封装的,只不过你的IO消耗由原本的数据库变成了共享缓存)。接下来,我就针对本地缓存的情况来具体实现一个DDL。

定义操作

这是我定义出的一些操作:

代码语言:javascript
复制
public interface IDirtyEntity {

  //region manage content

  /**
   * 获取entity的内容。
   */
  Object getContent();

  /**
   * 获取entity的内容。 获取的内容是复制的对象,属性值是调用该方法时的属性值。
   */
  Object copyContent();

  //endregion

  //region persisting flag

  /**
   * 是否正在进行持久化
   */
  boolean isPersisting();

  /**
   * 设置正在持久化标志
   */
  void setPersistingFlag();

  /**
   * 清除正在持久化标志
   */
  void clearPersistingFlag();

  //endregion

  //region persist state

  /**
   * 设置为脏数据状态
   */
  void setDirtyState();

  /**
   * 清除脏数据状态
   */
  void clearDirtyState();

  /**
   * 当前持久化状态。
   *
   * @see PersistState
   */
  PersistState currentPersistState();

  //endregion

  //region get/set field

  /**
   * 获取属性值。
   */
  Object getField(String fieldName);

  /**
   * 设置属性值。
   */
  void setField(String fieldName, Object value);

  /**
   * 设置多个属性的值。
   */
  void setFields(List<EntityField> fields);

  /**
   * 增加int类型属性的值。
   */
  void addInt(String fieldName, int delta);

  /**
   * 增加long类型属性的值。
   */
  void addLong(String fieldName, long delta);

  //endregion

  //region manage dirty field

  /**
   * 标记脏数据字段
   */
  void addDirtyField(String fieldName);

  /**
   * 获取修改过的属性。
   */
  List<EntityField> getAndClearDirtyFields();

  //endregion

  //region wrapper implement

  /**
   * 返回id的属性名。
   */
  String getIdFieldName();

  /**
   * 返回id
   */
  String getId();

  /**
   * 返回DATA的class
   */
  Class getDataClass();

  //endregion
}

分类

DDL解决的是数据频繁更改的问题,其实这里的更改说的并不准确,并不仅仅只是update,还有insert。用过mongodb的应该清楚有一种叫upsert的操作,就是找到就修改,找不到就添加。我们这里就需要将我们的数据分成两类:Detachable(可拆分的)、Nondetachable(不可拆分的)

可拆分的,就意味着你针对这个数据的修改最小可以精确到其中的一个属性,项目中大多数都属于这种情况。

不可拆分的,即每次都是以一个整体添加,比如一次交易,每次添加都是一个整体,不可能说你先提交买方,再提交卖方,后面还会修改买方。这种类型大多都是一条记录,整体存入数据库。

因此,我们来定义一下这两种结构: 可拆分的类型:

代码语言:javascript
复制
import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cglib.beans.BeanCopier;
import org.springframework.cglib.beans.BeanMap;
public abstract class DetachableDirtyEntityAdapter implements IDirtyEntity {

  private static final Logger log = LoggerFactory.getLogger(DetachableDirtyEntityAdapter.class);

  /**
   * 数据属性的map引用
   */
  private BeanMap beanMap;

  private final BeanCopier beanCopier;


  public DetachableDirtyEntityAdapter(Object content, BeanCopier beanCopier) {
    Preconditions.checkNotNull(content);
    Preconditions.checkNotNull(beanCopier);

    this.content = newEmptyContentInstance();
    this.beanCopier = beanCopier;
    this.beanCopier.copy(content, this.content, null);
    this.beanMap = BeanMap.create(this.content);
  }

  //region manage content

  /**
   * 数据的内容。
   */
  private Object content;

  @Override
  public Object getContent() {
    return content;
  }

  private Object newEmptyContentInstance() {
    Class cls = getDataClass();
    try {
      return cls.newInstance();
    } catch (Exception e) {
      log.error("initiate {} failed: {}", cls.getSimpleName(), e.getMessage());
      return null;
    }
  }

  @Override
  public synchronized Object copyContent() {

    Object copy = newEmptyContentInstance();
    beanCopier.copy(this.content, copy, null);
    return copy;
  }

  //endregion

  //region persisting flag

  private volatile boolean persisting = false;

  @Override
  public boolean isPersisting() {
    return persisting;
  }

  @Override
  public void setPersistingFlag() {
    this.persisting = true;
  }

  @Override
  public void clearPersistingFlag() {
    this.persisting = false;
  }

  //endregion

  //region persist state

  @Override
  public void setDirtyState() {
    throw new UnsupportedOperationException();
  }

  @Override
  public void clearDirtyState() {
    throw new UnsupportedOperationException();
  }

  @Override
  public synchronized PersistState currentPersistState() {
    int dirtySize = dirtyFieldNames.size();

    if (dirtySize == 0) {
      return PersistState.PERSISTED;
    } else {
      return PersistState.DIRTY;
    }
  }

  //endregion

  //region get/set field

  @Override
  public synchronized Object getField(String fieldName) {
    Preconditions.checkArgument(!Strings.isNullOrEmpty(fieldName));

    return beanMap.get(fieldName);
  }

  @Override
  public synchronized void setField(String fieldName, Object value) {
    Preconditions.checkArgument(!Strings.isNullOrEmpty(fieldName));

    beanMap.put(fieldName, value);
    dirtyFieldNames.add(fieldName);
  }

  @Override
  public synchronized void setFields(List<EntityField> fields) {
    Preconditions.checkNotNull(fields);

    for (EntityField f : fields) {
      beanMap.put(f.getName(), f.getValue());
      dirtyFieldNames.add(f.getName());
    }
  }

  @Override
  public synchronized void addInt(String fieldName, int delta) {
    Preconditions.checkArgument(!Strings.isNullOrEmpty(fieldName));

    int origin = (int) beanMap.get(fieldName);
    beanMap.put(fieldName, origin + delta);
    dirtyFieldNames.add(fieldName);
  }

  @Override
  public synchronized void addLong(String fieldName, long delta) {
    Preconditions.checkArgument(!Strings.isNullOrEmpty(fieldName));

    long origin = (long) beanMap.get(fieldName);
    beanMap.put(fieldName, origin + delta);
    dirtyFieldNames.add(fieldName);
  }

  //endregion

  //region manage dirty fields

  /**
   * 当前entity的包含脏数据的属性名列表。
   */

  private final HashSet<String> dirtyFieldNames = new HashSet<>(16);

  @Override
  public void addDirtyField(String fieldName) {
    Preconditions.checkArgument(!Strings.isNullOrEmpty(fieldName));
    dirtyFieldNames.add(fieldName);
  }

  @Override
  public synchronized List<EntityField> getAndClearDirtyFields() {
    ArrayList<EntityField> list = new ArrayList<>();

    for (String f : dirtyFieldNames) {
      list.add(new EntityField(f, beanMap.get(f)));
    }

    // 清空dirtyFieldNames, 记录上一次持久化的事件
    dirtyFieldNames.clear();
    return list;
  }

  //endregion
}

不可拆分的类型:

代码语言:javascript
复制
import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cglib.beans.BeanCopier;
import org.springframework.cglib.beans.BeanMap;
public abstract class NonDetachableDirtyEntityAdapter implements IDirtyEntity {

  private static final Logger log = LoggerFactory.getLogger(NonDetachableDirtyEntityAdapter.class);

  /**
   * 数据属性的map引用
   */
  private BeanMap beanMap;

  private final BeanCopier beanCopier;


  public NonDetachableDirtyEntityAdapter(Object content, BeanCopier beanCopier) {
    Preconditions.checkNotNull(content);
    Preconditions.checkNotNull(beanCopier);

    this.content = newEmptyContentInstance();
    this.beanCopier = beanCopier;
    this.beanCopier.copy(content, this.content, null);
    this.beanMap = BeanMap.create(this.content);
  }

  //region manage content

  /**
   * 数据的内容。
   */
  private Object content;

  @Override
  public Object getContent() {
    return content;
  }

  private Object newEmptyContentInstance() {
    Class cls = getDataClass();
    try {
      return cls.newInstance();
    } catch (Exception e) {
      log.error("initiate {} failed: {}", cls.getSimpleName(), e.getMessage());
      return null;
    }
  }

  @Override
  public synchronized Object copyContent() {

    Object copy = newEmptyContentInstance();
    beanCopier.copy(this.content, copy, null);
    return copy;
  }

  //endregion

  //region persisting flag

  private volatile boolean persisting = false;

  @Override
  public boolean isPersisting() {
    return persisting;
  }

  @Override
  public void setPersistingFlag() {
    this.persisting = true;
  }

  @Override
  public void clearPersistingFlag() {
    this.persisting = false;
  }

  //endregion

  //region persist state

  private volatile PersistState persistState = PersistState.DIRTY;

  @Override
  public void setDirtyState() {
    persistState = PersistState.DIRTY;
  }

  @Override
  public void clearDirtyState() {
    persistState = PersistState.PERSISTED;
  }

  @Override
  public PersistState currentPersistState() {
    return persistState;
  }

  //endregion

  //region get/set field

  @Override
  public synchronized Object getField(String fieldName) {
    Preconditions.checkArgument(!Strings.isNullOrEmpty(fieldName));

    return beanMap.get(fieldName);
  }

  @Override
  public synchronized void setField(String fieldName, Object value) {
    Preconditions.checkArgument(!Strings.isNullOrEmpty(fieldName));

    beanMap.put(fieldName, value);
  }

  @Override
  public synchronized void setFields(List<EntityField> fields) {
    Preconditions.checkNotNull(fields);

    for (EntityField f : fields) {
      beanMap.put(f.getName(), f.getValue());
    }
  }

  @Override
  public synchronized void addInt(String fieldName, int delta) {
    Preconditions.checkArgument(!Strings.isNullOrEmpty(fieldName));

    int origin = (int) beanMap.get(fieldName);
    beanMap.put(fieldName, origin + delta);
  }

  @Override
  public synchronized void addLong(String fieldName, long delta) {
    Preconditions.checkArgument(!Strings.isNullOrEmpty(fieldName));

    long origin = (long) beanMap.get(fieldName);
    beanMap.put(fieldName, origin + delta);
  }

  //endregion

  //region manage dirty fields
  @Override
  public void addDirtyField(String fieldName) {
    throw new UnsupportedOperationException();
  }

  @Override
  public synchronized List<EntityField> getAndClearDirtyFields() {
    throw new UnsupportedOperationException();
  }

  //endregion
}

两种类型最大的不同在于真正往数据库中存储时,前者是可以单独字段存储,后者是整体存储,因此最后和DirtyField相关的操作便需要注意,NondetachableDirtyEntityAdapter不需要记录DirtyFields。

针对原本类中属性的复制和存储,我这儿用的是spring提供的BeanCopier,如果你有什么更高效的工具,欢迎在下方留言。(我一直在找一种深度克隆高效的组件,试过kryo,但如果实现序列化接口,其效率和正常的set/get大概相差10倍,如果有好的组件,希望一并告知)。

以上就是DDL的准备工作,其实后面的工作就是将具体的类做一个封装,再封装针对该类的所有操作,然后另写一个线程组执行往数据库的写入操作。这个工作其实针对各个项目都有其特殊的地方,博主在这儿就不具体展示了,有兴趣的话大家可以在下方留言。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-02-14,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 健程之道 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 定义操作
  • 分类
相关产品与服务
对象存储
对象存储(Cloud Object Storage,COS)是由腾讯云推出的无目录层次结构、无数据格式限制,可容纳海量数据且支持 HTTP/HTTPS 协议访问的分布式存储服务。腾讯云 COS 的存储桶空间无容量上限,无需分区管理,适用于 CDN 数据分发、数据万象处理或大数据计算与分析的数据湖等多种场景。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档