redis与mysql的数据一致性问题(事务一致性)
案例:考虑一个在线购物应用,其中有一个购物车服务,购物车信息存储在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()
解决方案:
- 使用MySQL事务确保原子性:
在MySQL中执行购物车更新操作时,将相关操作包裹在事务中,以确保它们的原子性。如果任何一个操作失败,整个事务将被回滚,防止不一致的数据状态。 - 使用Redis的WATCH和MULTI命令实现乐观锁:
使用Redis的WATCH和MULTI命令,通过乐观锁的方式确保Redis中购物车缓存的原子性更新。如果在执行事务前发现被监视的键(购物车缓存键)被其他客户端修改,则事务会被取消。
# 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()