You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何提升Clojure航班预订事务的执行速度?

Optimizing Your Parallel Clojure Flight Reservation System with Refs

Let’s dive into why your ref-based parallel implementation isn’t living up to speed expectations and fix it with practical, Clojure-idiomatic tweaks. The core issue with most slow ref-based systems is transaction contention and overly broad transaction scopes—let’s tackle that first.

1. Split Flight State into Granular Refs (Critical Fix)

Your current serial implementation uses a single atom for all flights, which works for single-threaded processing. But when moving to refs, a single ref for the entire flight collection means every booking or sale operation locks the whole set. Parallel threads will constantly conflict, leading to repeated transaction retries and slower performance than the serial version.

Fix: Store Each Flight in Its Own Ref

Instead of a single ref/atom for all flights, use a map (held in an atom) where each key is a flight ID, and the value is a ref containing that flight’s state. This way, modifying one flight only locks that specific ref—no more global contention.

;; Initialize flights as a map of flight IDs to refs
(def flights-by-id (atom {}))

(defn initialize-flights [initial-flights]
  (reset! flights-by-id (into {} (map (fn [f] [(:id f) (ref f)]) initial-flights))))

2. Shrink Transaction Scope to the Minimum

Transactions are expensive when they run long or touch shared state. In your original code, you might be running the entire flight lookup and booking logic inside a transaction. Instead, do as much work as possible outside the transaction, then only wrap the critical state modification in a transaction.

Example: Optimized process-customer

(defn process-customer [customer]
  ;; Step 1: Find suitable flight and price OUTSIDE the transaction
  (let [{:keys [from to seats budget id customer-id]} customer
        ;; Get all relevant flights from the atom (no locking needed here)
        relevant-flights (filter (fn [[_ flight-ref]]
                                   (let [f @flight-ref]
                                     (and (= (:from f) from) (= (:to f) to))))
                                 @flights-by-id)
        ;; Calculate available prices outside transaction
        flight-price-pairs (keep (fn [[flight-id flight-ref]]
                                   (let [f @flight-ref
                                         lowest-price (lowest-available-price f seats)]
                                     (when (and lowest-price (<= lowest-price budget))
                                       {:flight-id flight-id :price lowest-price :flight-ref flight-ref})))
                                 relevant-flights)
        cheapest-option (first (sort-by :price flight-price-pairs))]

    (if cheapest-option
      ;; Step 2: Only wrap the booking modification in a transaction
      (dosync
       (let [{:keys [flight-ref price flight-id]} cheapest-option
             updated-flight (book @flight-ref price seats)]
         (alter flight-ref (constantly updated-flight))
         (log "Customer" customer-id "booked" seats "seats on flight" flight-id "at $" price)))
      (log "Customer" customer-id "did not find a suitable flight."))))

This way, the transaction only touches one flight ref, and all the lookup/filtering work is done without locking—drastically reducing contention.

3. Use commute Instead of alter for Commutative Operations

For operations like updating seat counts (which are commutative—order of updates doesn’t change the final result), use commute instead of alter. commute tells Clojure it can merge the changes instead of retrying the entire transaction when there’s a conflict.

Modified book with commute

(defn- book-flight-seats [flight price seats]
  (update flight :pricing
          (fn [pricing]
            (mapv (fn [[p a t]]
                    (if (= p price)
                      [p (- a seats) (+ t seats)]
                      [p a t]))
                  pricing))))

;; In process-customer, replace alter with commute:
(commute flight-ref book-flight-seats price seats)

commute is perfect here because two concurrent bookings on the same flight will just have their seat adjustments merged—no need to retry the transaction if another thread modified the flight in between.

4. Parallelize Customer Processing Properly

Your serial code uses doseq to process customers one by one. For the parallel version, use pmap or future to process multiple customers at once, but be mindful of thread pool limits (Clojure’s default pmap uses a thread pool equal to your CPU core count).

(defn process-customers-parallel [customers]
  (Thread/sleep 100)
  ;; Use pmap to process customers in parallel
  (pmap process-customer customers)
  (reset! finished-processing? true))

5. Optimize the Sales Process for Parallelism

Your current sales process modifies multiple flights at once. With granular refs, you can update each discounted flight in parallel instead of in a single transaction.

(defn start-sale [flight-ids]
  (log "Start sale for flights" flight-ids)
  ;; Update each flight ref in parallel
  (pmap (fn [flight-id]
          (when-let [flight-ref (get @flights-by-id flight-id)]
            (dosync
             (commute flight-ref update-pricing 0.80))))
        flight-ids))

(defn end-sale [flight-ids]
  (log "End sale")
  (pmap (fn [flight-id]
          (when-let [flight-ref (get @flights-by-id flight-id)]
            (dosync
             (commute flight-ref update-pricing 1.25))))
        flight-ids))

This way, each flight’s price update is an independent transaction, avoiding locking the entire flight set.

Key Takeaways

  • Granular state is the biggest win for parallel ref-based systems—avoid global state locks at all costs.
  • Minimize transaction scope: Do non-state-modifying work outside transactions.
  • Choose the right transaction function: Use commute for commutative operations to reduce retries.
  • Parallelize work correctly: Use pmap or future for independent customer/sale operations.

With these changes, your parallel implementation should outperform the serial version significantly, especially as the number of flights and customers grows.

内容的提问来源于stack exchange,提问作者Gakuo

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.27 06:48:32