libmysqlclient.a and libmysqlclient_r.a的区别

本文探讨了 MySQL 客户端库中 libmysqlclient_r.a 的线程安全特性及其在多线程应用中的使用方式。解释了新版本中不再提供 '_r' 后缀版本的原因,并说明了其与 libmysqlclient.a 在线程安全性上的差异。

libmysqlclient_r.a is thread-safe


Newer versions of the MySQL client distributions do not include the "_r" version. Some may have a symbolic link from libmyqslclient_r.a to libmyqslclient.a


libmysqlclient_r.a is "re-entrant". https://en.wikipedia.org/wiki/Reentrant_%28subroutine%29 But as Garret pointed out, there is no difference in newer versions (both are re-entrant).


libmysqlclient_r is guaranteed to be thread-safe per connection. However, MySQL documentations prior to MySQL 5.5 are vague on whether multi-threaded applications can link to libmysqlclient as long as there are no simultaneous access on a single MySQL connection handle.


建立连接语句

# ln -s ./mysql/libmysqlclient_r.so libmysqlclient_r.so
# ln -s ./mysql/libmysqlclient.so libmysqlclient.so

把import asyncio import websockets import json import struct import datetime import logging import traceback import os import sys from contextlib import contextmanager from typing import Any, Dict, List, Optional import mysql.connector from mysql.connector import pooling from mysql.connector import errorcode # ====================== # 增强的配置参数 # ====================== CONFIG = { "TIME_FORMATS": [ "%Y-%m-%d %H:%M:%S", "%Y/%m/%d %H:%M:%S", "%Y%m%d%H%M%S", "%d-%b-%Y %H:%M:%S" ], "WEBSOCKET_HOST": "0.0.0.0", # 监听所有接口 "WEBSOCKET_PORT": 8765, "MAX_CONNECTIONS": 100, "DB_WRITE_BATCH_SIZE": 50, "DB_WRITE_TIMEOUT": 5.0, "LOG_FILE": "sensor_server.log", "LOG_LEVEL": "DEBUG", # 增加日志级别配置 # MySQL数据库配置 "MYSQL": { "HOST": "localhost", "PORT": 3306, "USER": "root", "PASSWORD": "Adu@123.", "DATABASE": "CF_HIDB", "POOL_SIZE": 5, "POOL_NAME": "sensor_pool", "POOL_RESET_SESSION": True, "CONNECT_TIMEOUT": 5 # 连接超时时间(秒) } } # ====================== # 增强的日志系统 # ====================== class SensitiveDataFilter(logging.Filter): def filter(self, record: logging.LogRecord) -> bool: if not hasattr(record, 'msg') or not record.msg: return True if not isinstance(record.msg, str): try: record.msg = str(record.msg) except Exception: return True # 过滤MySQL密码 password = CONFIG["MYSQL"]["PASSWORD"] if password: record.msg = record.msg.replace(password, "[REDACTED]") return True def setup_logging() -> logging.Logger: sensitive_filter = SensitiveDataFilter() # 配置日志级别 log_level = getattr(logging, CONFIG.get("LOG_LEVEL", "INFO").upper(), logging.INFO) logger = logging.getLogger("SensorServer") logger.setLevel(log_level) # 清除所有现有处理器 for handler in logger.handlers[:]: logger.removeHandler(handler) # 文件处理器 file_handler = logging.FileHandler(CONFIG["LOG_FILE"]) file_handler.setFormatter(logging.Formatter( '%(asctime)s - %(name)s - %(levelname)s - %(message)s' )) file_handler.addFilter(sensitive_filter) logger.addHandler(file_handler) # 控制台处理器 console_handler = logging.StreamHandler() console_handler.setFormatter(logging.Formatter( '%(asctime)s - %(name)s - %(levelname)s - %(message)s' )) console_handler.addFilter(sensitive_filter) logger.addHandler(console_handler) return logger # 全局日志器 logger = setup_logging() # ====================== # 增强的MySQL数据库管理 (带超时处理) # ====================== class MySQLDatabaseManager: def __init__(self): self.data_buffer = [] self.last_write_time = datetime.datetime.now() self.connection_pool = None self.initialize_pool() self.initialize_db() def initialize_pool(self): """安全地初始化连接池""" try: logger.debug("尝试创建MySQL连接池...") self.connection_pool = pooling.MySQLConnectionPool( pool_name=CONFIG["MYSQL"]["POOL_NAME"], pool_size=CONFIG["MYSQL"]["POOL_SIZE"], pool_reset_session=CONFIG["MYSQL"]["POOL_RESET_SESSION"], host=CONFIG["MYSQL"]["HOST"], port=CONFIG["MYSQL"]["PORT"], user=CONFIG["MYSQL"]["USER"], password=CONFIG["MYSQL"]["PASSWORD"], database=CONFIG["MYSQL"]["DATABASE"], connect_timeout=CONFIG["MYSQL"]["CONNECT_TIMEOUT"] ) logger.info("MySQL连接池创建成功") except mysql.connector.Error as err: logger.error(f"创建MySQL连接池失败: {err}") # 尝试重新连接 self.reconnect() except Exception as e: logger.critical(f"连接池初始化意外错误: {e}") sys.exit(1) def reconnect(self): """尝试重新连接数据库""" logger.warning("尝试重新连接数据库...") retries = 3 for attempt in range(1, retries + 1): try: self.initialize_pool() logger.info("数据库重新连接成功") return except Exception as e: logger.error(f"重新连接尝试 {attempt}/{retries} 失败: {e}") if attempt < retries: logger.info(f"{5 * attempt}秒后重试...") time.sleep(5 * attempt) logger.critical("无法连接到数据库,服务器退出") sys.exit(1) @contextmanager def _get_connection(self): """安全地获取数据库连接""" if not self.connection_pool: self.reconnect() try: conn = self.connection_pool.get_connection() if not conn.is_connected(): conn.reconnect() yield conn except mysql.connector.Error as err: logger.error(f"获取数据库连接失败: {err}") if err.errno == errorcode.CR_SERVER_GONE_ERROR: self.reconnect() raise finally: if 'conn' in locals() and conn.is_connected(): conn.close() def initialize_db(self): """初始化数据库表结构 (带重试机制)""" max_attempts = 3 for attempt in range(1, max_attempts + 1): try: with self._get_connection() as conn: cursor = conn.cursor() # 创建设备表 cursor.execute(''' CREATE TABLE IF NOT EXISTS devices ( id INT AUTO_INCREMENT PRIMARY KEY, device_id VARCHAR(50) NOT NULL UNIQUE, first_seen TIMESTAMP DEFAULT CURRENT_TIMESTAMP, last_seen TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ) ''') # 创建传感器数据表 cursor.execute(''' CREATE TABLE IF NOT EXISTS sensor_data ( id BIGINT AUTO_INCREMENT PRIMARY KEY, device_id INT NOT NULL, timestamp TIMESTAMP NOT NULL, sensor_values JSON NOT NULL, received_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, INDEX idx_device_id (device_id), INDEX idx_timestamp (timestamp), FOREIGN KEY (device_id) REFERENCES devices(id) ON DELETE CASCADE ) ''') conn.commit() logger.info("数据库表结构初始化成功") return except Exception as e: logger.error(f"数据库初始化失败 (尝试 {attempt}/{max_attempts}): {e}") if attempt < max_attempts: logger.info(f"{2 * attempt}秒后重试...") time.sleep(2 * attempt) logger.critical("数据库初始化失败,服务器退出") sys.exit(1) def insert_sensor_data(self, device_id: str, timestamp: str, sensor_values: List[float]) -> None: """将数据添加到缓冲区,批量写入数据库""" self.data_buffer.append({ 'device_id': device_id, 'timestamp': timestamp, 'sensor_values': sensor_values }) # 检查是否满足批量写入条件 now = datetime.datetime.now() buffer_full = len(self.data_buffer) >= CONFIG["DB_WRITE_BATCH_SIZE"] timeout_reached = (now - self.last_write_time).total_seconds() >= CONFIG["DB_WRITE_TIMEOUT"] if buffer_full or timeout_reached: self.flush_buffer() def flush_buffer(self) -> None: """安全地将缓冲区数据写入数据库""" if not self.data_buffer: return try: with self._get_connection() as conn: cursor = conn.cursor() # 批量写入设备数据 device_ids = [] for data in self.data_buffer: # 获取或创建设备ID device_db_id = self._get_or_create_device(conn, data['device_id']) device_ids.append(device_db_id) # 准备批量插入传感器数据 sensor_data = [] for i, data in enumerate(self.data_buffer): sensor_data.append(( device_ids[i], data['timestamp'], json.dumps(data['sensor_values']) )) # 批量插入传感器数据 cursor.executemany( "INSERT INTO sensor_data (device_id, timestamp, sensor_values) " "VALUES (%s, %s, %s)", sensor_data ) conn.commit() logger.info(f"成功写入 {len(self.data_buffer)} 条数据到MySQL数据库") # 清空缓冲区 self.data_buffer.clear() self.last_write_time = datetime.datetime.now() except Exception as e: logger.error(f"数据库写入失败: {e}") logger.error(f"异常详情:\n{traceback.format_exc()}") # 保留数据以便稍后重试 def _get_or_create_device(self, conn, device_id: str) -> int: """获取或创建设备记录 (带错误处理)""" cursor = conn.cursor() try: cursor.execute( "SELECT id FROM devices WHERE device_id = %s", (device_id,) ) device = cursor.fetchone() if device: device_id = device[0] cursor.execute( "UPDATE devices SET last_seen = CURRENT_TIMESTAMP WHERE id = %s", (device_id,) ) return device_id else: cursor.execute( "INSERT INTO devices (device_id) VALUES (%s)", (device_id,) ) return cursor.lastrowid except mysql.connector.Error as err: logger.error(f"设备操作失败: {err}") # 简单处理:返回一个虚拟ID,实际应用中应根据需要处理 return -1 # ====================== # 数据包解析 # ====================== def parse_timestamp(timestamp_bytes: bytes) -> str: """解析时间戳字节数据为字符串""" try: timestamp_str = timestamp_bytes.decode('ascii').strip() except UnicodeDecodeError: try: timestamp_str = timestamp_bytes.decode('latin-1').strip() except UnicodeDecodeError: timestamp_str = timestamp_bytes.hex() logger.warning(f"时间戳包含无效字符,使用十六进制: {timestamp_str}") return timestamp_str cleaned = ''.join(c for c in timestamp_str if c.isdigit() or c in '- :/') for fmt in CONFIG["TIME_FORMATS"]: try: dt = datetime.datetime.strptime(cleaned, fmt) return dt.strftime("%Y-%m-%d %H:%M:%S") except ValueError: continue logger.warning(f"无法解析时间戳: {timestamp_str} (清理后: {cleaned})") return cleaned def parse_sensor_values(data: bytes, start_index: int) -> List[float]: """解析传感器值列表""" values = [] index = start_index max_count = (len(data) - index) // 4 for _ in range(max_count): if index + 4 > len(data): break value_bytes = data[index:index + 4] index += 4 try: value = struct.unpack('<f', value_bytes)[0] values.append(value) except struct.error: try: value = struct.unpack('>f', value_bytes)[0] values.append(value) except struct.error: continue return values def parse_binary_packet(raw_data: bytes) -> Optional[Dict]: """解析二进制数据包""" try: HEADER = b'$\x00\x08\x00\x00\x00' if not raw_data.startswith(HEADER): return None DEVICE_ID_START = len(HEADER) DEVICE_ID_END = DEVICE_ID_START + 8 if len(raw_data) < DEVICE_ID_END: return None device_id_bytes = raw_data[DEVICE_ID_START:DEVICE_ID_END] TIMESTAMP_START = DEVICE_ID_END TIMESTAMP_END = TIMESTAMP_START + 19 if len(raw_data) < TIMESTAMP_END: return None timestamp_bytes = raw_data[TIMESTAMP_START:TIMESTAMP_END] SENSOR_DATA_START = TIMESTAMP_END sensor_values = parse_sensor_values(raw_data, SENSOR_DATA_START) return { 'device_id': decode_with_fallback(device_id_bytes), 'timestamp': parse_timestamp(timestamp_bytes), 'sensor_values': sensor_values } except Exception as e: logger.error(f"解析错误: {str(e)}") return None def decode_with_fallback(data: bytes) -> str: """带回退机制的字节解码""" try: return data.decode('ascii').strip() except UnicodeDecodeError: try: return data.decode('latin-1').strip() except UnicodeDecodeError: return data.hex() # ====================== # 增强的WebSocket服务器 (带连接监控) # ====================== class SensorWebSocketServer: def __init__(self, host: str, port: int, db_manager: MySQLDatabaseManager): self.host = host self.port = port self.db_manager = db_manager self.connections = set() self.server = None self.stats = { 'total_connections': 0, 'total_messages': 0, 'valid_messages': 0, 'invalid_messages': 0 } self.start_time = None self.connection_monitor_task = None async def handler(self, websocket, path: str) -> None: """处理WebSocket连接""" client_ip = websocket.remote_address[0] if websocket.remote_address else "unknown" self.connections.add(websocket) self.stats['total_connections'] += 1 logger.info(f"客户端连接: {client_ip},当前连接数: {len(self.connections)}") try: async for message in websocket: self.stats['total_messages'] += 1 if not isinstance(message, bytes): self.stats['invalid_messages'] += 1 continue parsed_data = parse_binary_packet(message) if parsed_data: self.stats['valid_messages'] += 1 self.db_manager.insert_sensor_data( parsed_data['device_id'], parsed_data['timestamp'], parsed_data['sensor_values'] ) else: self.stats['invalid_messages'] += 1 except websockets.exceptions.ConnectionClosed: logger.info(f"客户端断开: {client_ip}") except Exception as e: logger.error(f"处理客户端 {client_ip} 时出错: {str(e)}") finally: if websocket in self.connections: self.connections.remove(websocket) logger.info(f"客户端断开,剩余连接数: {len(self.connections)}") async def start(self) -> None: """启动WebSocket服务器""" self.server = await websockets.serve( self.handler, self.host, self.port, max_size=2 ** 20, # 1MB ping_interval=30, # 更频繁的ping ping_timeout=15, close_timeout=10, max_queue=CONFIG["MAX_CONNECTIONS"] ) self.start_time = datetime.datetime.now() logger.info(f"WebSocket服务器启动在 ws://{self.host}:{self.port}") # 启动连接监控任务 self.connection_monitor_task = asyncio.create_task(self.monitor_connections()) async def monitor_connections(self): """监控连接状态""" while True: await asyncio.sleep(60) logger.debug(f"连接状态: {len(self.connections)} 个活跃连接") async def stop(self) -> None: """停止WebSocket服务器""" if self.connection_monitor_task: self.connection_monitor_task.cancel() if self.server: self.server.close() await self.server.wait_closed() logger.info("WebSocket服务器已停止") def get_stats(self) -> Dict: """获取服务器统计信息""" return { **self.stats, 'current_connections': len(self.connections), 'uptime': str(datetime.datetime.now() - self.start_time) if self.start_time else "0:00:00" } # ====================== # 增强的主程序 (带健康检查) # ====================== async def main(): # 初始化MySQL数据库管理器 logger.debug("初始化数据库管理器...") db_manager = MySQLDatabaseManager() # 创建WebSocket服务器 logger.debug("创建WebSocket服务器...") server = SensorWebSocketServer( CONFIG["WEBSOCKET_HOST"], CONFIG["WEBSOCKET_PORT"], db_manager ) # 启动服务器 logger.debug("启动WebSocket服务器...") await server.start() try: # 初始健康检查 logger.debug("执行初始健康检查...") await health_check(server, db_manager) # 运行主循环 logger.info("服务器进入主循环") while True: await asyncio.sleep(5) db_manager.flush_buffer() # 每分钟健康检查 now = datetime.datetime.now() if now.second < 5: # 每分钟前5秒执行 await health_check(server, db_manager) except asyncio.CancelledError: logger.info("服务器停止请求") except KeyboardInterrupt: logger.info("收到停止信号,正在关闭服务器...") finally: # 确保所有缓冲数据都写入数据库 db_manager.flush_buffer() await server.stop() async def health_check(server, db_manager): """服务器健康检查""" stats = server.get_stats() # 数据库连接检查 db_status = "OK" try: with db_manager._get_connection() as conn: cursor = conn.cursor() cursor.execute("SELECT 1") if cursor.fetchone()[0] != 1: db_status = "ERROR" except Exception: db_status = "ERROR" logger.info( f"健康检查 | " f"连接数: {stats['current_connections']} | " f"消息: {stats['total_messages']} | " f"数据库: {db_status} | " f"运行时间: {stats['uptime']}" ) # 启动程序 if __name__ == "__main__": # 记录启动配置 logger.info("=" * 50) logger.info("启动传感器数据采集服务器 (增强版)") logger.info(f"时间: {datetime.datetime.now()}") logger.info(f"监听地址: {CONFIG['WEBSOCKET_HOST']}:{CONFIG['WEBSOCKET_PORT']}") logger.info(f"MySQL主机: {CONFIG['MYSQL']['HOST']}:{CONFIG['MYSQL']['PORT']}") logger.info(f"数据库: {CONFIG['MYSQL']['DATABASE']}") logger.info("=" * 50) try: # 设置事件循环策略 (解决Windows上的问题) if sys.platform == 'win32': asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy()) asyncio.run(main()) except Exception as e: logger.critical(f"服务器意外停止: {e}") logger.critical(f"异常详情:\n{traceback.format_exc()}") finally: logger.info("=" * 50) logger.info("服务器已停止运行") logger.info("=" * 50) 部署到docker中
06-21
<think>我们正在解决缺少libmysqlclient.so.18库文件的问题。根据引用,常见解决方案包括: 1. 确保已安装包含该库的MySQL相关包(如MySQL客户端库)。 2. 手动下载并安装包含该库的RPM包(如引用3中所示)。 3. 创建符号链接(如果已安装其他版本,如引用4中出现了libmysqlclient.so.20,可能需要创建软链接)。 4. 添加库路径到ld.so.conf并运行ldconfig更新(如引用2)。 根据引用3,我们可以通过安装MySQL官方提供的兼容包来解决。具体步骤: 1. 下载兼容包(例如mysql-community-libs-compat,它包含旧版库文件)。 2. 安装该RPM包。 3. 然后再次尝试安装或运行需要libmysqlclient.so.18的应用程序。 另外,引用5提到在CentOS 7中,默认安装的是MariaDB,完全卸载后安装MySQL 5.7,但定时任务中报错缺少libmysqlclient.so.18。这可能是因为环境变量问题或者cron的环境与用户环境不同。解决方法可能是确保在cron环境中也能找到该库,可以通过在脚本中设置LD_LIBRARY_PATH或者确保库文件在系统默认库路径中(通过ldconfig更新)。 步骤总结: 步骤1:检查是否已安装该库 使用命令:`find / -name libmysqlclient.so.18* 2>/dev/null` 如果找到,则可能是路径未加入ldconfig,转到步骤4。 步骤2:如果没有找到,则安装兼容包 根据系统版本(如CentOS 7)下载对应的RPM包,例如: ``` wget https://dev.mysql.com/get/Downloads/MySQL-5.7/mysql-community-libs-compat-5.7.25-1.el7.x86_64.rpm rpm -ivh mysql-community-libs-compat-5.7.25-1.el7.x86_64.rpm ``` 步骤3:安装后再次检查库文件是否存在 通常安装后库文件会出现在/usr/lib64或/usr/lib下,或者MySQL的库目录(如/usr/local/mysql/lib)。 步骤4:将库文件所在目录加入ldconfig配置 如果库文件不在系统默认库路径中,需要将其加入: 例如,如果库文件在`/usr/local/mysql/lib`,则执行: ``` echo "/usr/local/mysql/lib" >> /etc/ld.so.conf.d/mysql.conf ldconfig ``` 步骤5:如果系统中有其他版本的库(如libmysqlclient.so.20),可以尝试创建软链接(但需谨慎,可能引起版本冲突) 例如: ``` ln -s /usr/local/mysql/lib/libmysqlclient.so.20 /usr/lib64/libmysqlclient.so.18 ``` 但更推荐使用兼容包,因为软链接可能不兼容。 步骤6:对于cron环境问题(如引用5),可以在cron执行的脚本开头设置LD_LIBRARY_PATH 例如,在备份脚本的开头添加: ``` export LD_LIBRARY_PATH=/usr/local/mysql/lib:$LD_LIBRARY_PATH ``` 根据引用4,有时即使安装了MySQL,库文件可能也不在预期的位置。因此,需要根据实际安装情况调整。 因此,我们推荐先尝试安装兼容包(步骤2),然后更新ldconfig(步骤4)。如果问题仍然存在,再考虑环境变量设置(步骤6)或创建软链接(步骤5,但要注意)。 下面我们按照步骤给出详细解决方案。</think>### 解决缺少 libmysqlclient.so.18 的方法 #### 1. **安装 MySQL 兼容库(推荐)** 通过 MySQL 官方提供的兼容包安装: ```bash # 下载兼容包(CentOS 7 示例) wget https://dev.mysql.com/get/Downloads/MySQL-5.7/mysql-community-libs-compat-5.7.25-1.el7.x86_64.rpm # 安装 RPM 包 rpm -ivh mysql-community-libs-compat-5.7.25-1.el7.x86_64.rpm # 更新动态链接库 ldconfig ``` 此包包含 `libmysqlclient.so.18`,安装后自动放入系统库路径(如 `/usr/lib64/`)[^3]。 #### 2. **手动添加库路径** 若已存在库文件但路径未识别(如 MySQL 自定义安装路径): ```bash # 检查库文件位置(示例路径) find / -name libmysqlclient.so.18 2>/dev/null # 将路径添加到配置(例如 /usr/local/mysql/lib) echo "/usr/local/mysql/lib" >> /etc/ld.so.conf ldconfig # 刷新配置 ``` 验证:`ldconfig -p | grep libmysqlclient.so.18`[^2][^4]。 #### 3. **解决 cron 环境问题** 若命令行正常但 cron 报错: ```bash # 在脚本开头添加环境变量 export LD_LIBRARY_PATH=/usr/lib64:/usr/local/mysql/lib:$LD_LIBRARY_PATH ``` 或直接在 crontab 中设置: ```bash SHELL=/bin/bash PATH=/sbin:/bin:/usr/sbin:/usr/bin LD_LIBRARY_PATH=/usr/lib64:/usr/local/mysql/lib ``` 确保 cron 任务能访问库路径[^5]。 #### 4. **检查版本冲突** 卸载冲突的 MariaDB 或旧版 MySQL: ```bash # 检查已安装包 rpm -qa | grep -E 'mysql|mariadb' # 卸载冲突包(谨慎操作) rpm -e mariadb-libs-5.5.68-1.el7.x86_64 # 示例 ``` 再重新安装所需依赖[^5]。 --- ### 关键注意事项 1. **权限问题** 执行 `ldconfig` 和安装 RPM 需 root 权限。 2. **路径一致性** 确保应用程序查找的库路径与 `ldconfig` 配置一致,可通过 `ldd /path/to/your/app` 验证。 3. **系统兼容性** 从 MySQL 官网下载 RPM 时需匹配操作系统版本(如 EL7 对应 CentOS 7)[^3]。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值