案例:考虑一个在线购物应用,其中有一个购物车服务,购物车信息存储在MySQL中,同时为了提高性能,购物车中的商品数量也被缓存到了Redis。用户在购物车中添加商品时,需要保证购物车数量在MySQL和Redis中的更新是原子性的,以避免不一致的情况。
# Python代码示例 - 添加商品到购物车的逻辑
import redis
import MySQLdb
def add_to_cart(user_id, product_id, quantity):
redis_client = redis.StrictRedis(host='localhost', port=6379, db=0)
mysql_conn = MySQLdb.connect(host='localhost', user='user', password='password', db='ecommerce')
cursor = mysql_conn.cursor()
try:
# 开始MySQL事务
mysql_conn.begin()
# 从MySQL中获取购物车中的商品数量
cursor.execute(f'SELECT quantity FROM shopping_carts WHERE user_id={user_id} AND product_id={product_id}')
result = cursor.fetchone()
if result:
# 商品已存在,更新数量
new_quantity = result[0] + quantity
cursor.execute(f'UPDATE shopping_carts SET quantity={new_quantity} WHERE user_id={user_id} AND product_id={product_id}')
else:
# 商品不存在,插入新记录
cursor.execute(f'INSERT INTO shopping_carts (user_id, product_id, quantity) VALUES ({user_id}, {product_id}, {quantity})')
# 提交MySQL事务
mysql_conn.commit()
# 更新Redis中购物车缓存
redis_client.hset(f'user:{user_id}:cart', product_id, quantity)
except Exception as e:
# 发生异常,回滚MySQL事务
mysql_conn.rollback()
print(f"Error: {e}")
finally:
cursor.close()
mysql_conn.close()解决方案:
# Python代码示例 - 使用Redis的WATCH和MULTI命令实现乐观锁
import redis
def add_to_cart_atomic(user_id, product_id, quantity):
redis_client = redis.StrictRedis(host='localhost', port=6379, db=0)
mysql_conn = MySQLdb.connect(host='localhost', user='user', password='password', db='ecommerce')
cursor = mysql_conn.cursor()
try:
# 使用WATCH监视购物车缓存键
with redis_client.pipeline() as pipe:
while True:
try:
# 开启Redis事务
pipe.watch(f'user:{user_id}:cart')
# 获取当前购物车中商品的数量
current_quantity = int(pipe.hget(f'user:{user_id}:cart', product_id) or 0)
# 开始Redis事务
pipe.multi()
# 计算新的商品数量
new_quantity = current_quantity + quantity
# 更新购物车缓存
pipe.hset(f'user:{user_id}:cart', product_id, new_quantity)
# 执行Redis事务
pipe.execute()
# 开始MySQL事务
mysql_conn.begin()
# 更新MySQL中购物车数量
cursor.execute(f'INSERT INTO shopping_carts (user_id, product_id, quantity) VALUES ({user_id}, {product_id}, {quantity}) ON DUPLICATE KEY UPDATE quantity=quantity+{quantity}')
# 提交MySQL事务
mysql_conn.commit()
break
except redis.WatchError:
# 被监视的键被其他客户端修改,重新尝试
continue
except Exception as e:
# 发生异常,回滚MySQL事务
mysql_conn.rollback()
print(f"Error: {e}")
finally:
cursor.close()
mysql_conn.close()