面試官問:Redis的操作如何與資料庫事務保持一致

  • 2019 年 12 月 17 日
  • 筆記

  • redis如何與資料庫保持一致性的問題?

場景:如果我們在開發過程中遇到這樣的一種情況,我們刪除 redis中token 的同時 也需要修改資料庫中 儲存的 token 的狀態為不可用的狀態。如果這個時候我們不做處理的話,通常是先刪除redis中的token,然後在進行資料庫的修改。但是如果這個時候redis中的token刪除成功了,但是在執行資料庫操作之氣程式報錯了。那這個時候redis中的token已經被刪除了,但是資料庫中的token狀態還是可用的狀態,這個時候就導致了數據不一致的問題。

這時候我們需要使用統一的事務來進行解決這個問題,.但是如果只是單純的使用資料庫事務並不能解決這個問題,因為這個操作也涉及到了redis,所以這個時候我們應該使用 redis事務+資料庫的事務 來保證事務一致性的問題。

如果只是單純添加了 @Transactional(聲明式事務)只能保證資料庫的數據一致性問題,但是是無法控制redis中的事務的。redis中也是存在事務的。

解決方案:

我們可以使用自定義方法使用編程式事務 我們使用 begin(即控制reids事務也控制資料庫事務)、commit、rollback 都需要實現控制redis事務和資料庫事務。

下面直接上程式碼 ,

  • 這個類包裝了redis事務和資料庫事務,一起開啟事務,一起提交事務,一起回滾事務
  • redisUtils相關程式碼:
  • 在寫業務程式碼時 大致這樣寫

程式碼如下 自己copy用:

RedisDataSoureceTransaction 程式碼:

@Component  @Scope(ConfigurableListableBeanFactory.SCOPE_PROTOTYPE)  public class RedisDataSoureceTransaction {   @Autowired   private RedisUtil redisUtil;   /**   * 數據源事務管理器   */   @Autowired   private DataSourceTransactionManager dataSourceTransactionManager;   /**   * 開始事務 採用默認傳播行為   *   * @return   */   public TransactionStatus begin() {   // 手動begin資料庫事務   TransactionStatus transaction = dataSourceTransactionManager.getTransaction(new DefaultTransactionAttribute());   redisUtil.begin();//   return transaction;   }   /**   * 提交事務   *   * @param transactionStatus   * 事務傳播行為   * @throws Exception   */   public void commit(TransactionStatus transactionStatus) throws Exception {   if (transactionStatus == null) {   throw new Exception("transactionStatus is null");   }   // 支援Redis與資料庫事務同時提交   dataSourceTransactionManager.commit(transactionStatus);   redisUtil.exec();   }   /**   * 回滾事務   *   * @param transactionStatus   * @throws Exception   */   public void rollback(TransactionStatus transactionStatus) throws Exception {   if (transactionStatus == null) {   throw new Exception("transactionStatus is null");   }   dataSourceTransactionManager.rollback(transactionStatus);   redisUtil.discard();   }

redisutil相關程式碼:

/**   * 開啟Redis 事務   *   * @param isTransaction   */  public void begin() {   // 開啟Redis 事務許可權   stringRedisTemplate.setEnableTransactionSupport(true);   // 開啟事務   stringRedisTemplate.multi();  }  /**   * 提交事務   *   * @param isTransaction   */  public void exec() {   // 成功提交事務   stringRedisTemplate.exec();  }  /**   * 回滾Redis 事務   */  public void discard() {   stringRedisTemplate.discard();  }

業務的大致模板程式碼:

@authwared  private RedisDataSoureceTransaction manualTransaction    TransactionStatus transactionStatus = null;  try {   // // ####開啟手動事務   transactionStatus = manualTransaction.begin();     // 刪除或者更新資料庫的數據   ...........(業務程式碼省略)     // 刪除或者更新redis的值   .............(業務程式碼省略)     // #######提交事務   manualTransaction.commit(transactionStatus);    } catch (Exception e) {   try {   // 回滾事務   manualTransaction.rollback(transactionStatus);   } catch (Exception e1) {   }    }