如何将SMPP会话Java对象存储至Oracle数据库以实现Spring Boot多Pod间复用
如何将SMPP会话Java对象存储至Oracle数据库以实现Spring Boot多Pod间复用
我太懂你现在的处境了——22个Spring Boot Pod嗷嗷待哺,但SMPP服务器只给开5个会话配额,每个会话又能扛1000+请求,不用共享资源简直是暴殄天物。用Oracle这个共享DB来做会话的“调度中心”确实是个靠谱的思路,但先给你提个醒:SMPP会话对象本身是没法直接存数据库的,因为它绑定了当前Pod的网络连接、IO流这些活资源,序列化存成BLOB再读出来也没法用。咱们得换个思路:存会话的元数据和状态,用数据库做锁和资源调度,实现跨Pod的会话复用。
下面给你一步步拆解实现方案:
1. 先给Oracle建个会话管理表
首先得在Oracle里建一张表,用来记录每个SMPP会话的核心信息和状态,相当于咱们的“会话注册表”。这里给你个参考的建表SQL:
CREATE TABLE SMPP_SESSIONS ( ID NUMBER GENERATED ALWAYS AS IDENTITY PRIMARY KEY, SMPP_SERVER VARCHAR2(100) NOT NULL, -- SMPP服务器地址 SMPP_PORT NUMBER NOT NULL, -- SMPP端口 SYSTEM_ID VARCHAR2(50) NOT NULL, -- 账号ID PASSWORD VARCHAR2(50) NOT NULL, -- 密码 STATUS VARCHAR2(20) DEFAULT 'DISCONNECTED' CHECK (STATUS IN ('CONNECTED', 'IDLE', 'IN_USE', 'DISCONNECTED')), -- 会话状态 LAST_ACTIVE_TIME TIMESTAMP DEFAULT SYSTIMESTAMP, -- 最后活跃时间,用来判断会话是否存活 OWNER_POD_ID VARCHAR2(100), -- 当前持有会话的Pod标识(可选,方便排查问题) CREATED_TIME TIMESTAMP DEFAULT SYSTIMESTAMP ); -- 给状态字段建索引,提升查询效率 CREATE INDEX IDX_SMPP_SESSIONS_STATUS ON SMPP_SESSIONS(STATUS);
2. 核心逻辑:用DB做会话的锁和调度器
咱们的目标是让22个Pod共享5个会话,核心就是同一时间只有一个Pod能使用某个会话,数据库的行级锁刚好能帮咱们实现这个“抢资源”的逻辑。具体流程是这样的:
- 当某个Pod需要发送短信时,先去DB里找状态为
CONNECTED或IDLE的会话,用SELECT ... FOR UPDATE SKIP LOCKED语法获取一个未被锁定的会话(这个语法是Oracle 12c+支持的,能直接跳过已经被其他Pod锁定的行,避免大家排队等同一个会话)。 - 拿到会话后,把它的状态改成
IN_USE,标记当前Pod为持有者,然后用会话的元数据(服务器地址、账号密码这些)初始化或复用连接。 - 用完会话后,把状态改回
IDLE,更新最后活跃时间,释放锁让其他Pod能用。 - 如果没有可用会话,就检查当前已创建的会话数量是否达到5个上限,没到的话就新建一个会话,并存入DB;已经到上限的话,就等待一会儿再重试,或者抛出异常提示资源不足。
3. 会话生命周期的维护
光有调度还不够,得确保会话的状态是准确的,避免出现“僵尸会话”:
- 心跳机制:每个持有会话的Pod,每隔10-30秒就更新一次该会话的
LAST_ACTIVE_TIME,证明自己还在正常使用。 - 僵尸会话清理:写一个定时任务(比如用Spring的
@Scheduled),定期扫描会话表,把LAST_ACTIVE_TIME超过阈值(比如1分钟)且状态为IN_USE的会话,强制改成DISCONNECTED,释放资源。 - 断开重连:当Pod检测到SMPP会话断开时,立即更新DB里的会话状态为
DISCONNECTED,然后释放锁,让其他Pod可以重新初始化这个会话。
4. Spring Boot里的代码实现示例
给你写个简化版的会话管理器Bean,大概是这个样子:
@Component public class SmppSessionManager { @Autowired private JdbcTemplate jdbcTemplate; // SMPP服务器允许的最大会话数 private static final int MAX_SESSIONS = 5; // 会话超时阈值(比如1分钟) private static final long SESSION_TIMEOUT_SECONDS = 60; public SmppSession getAvailableSession() throws RuntimeException { // 先尝试获取可用的会话元数据 SmppSessionMetadata metadata = fetchAvailableSessionMetadata(); if (metadata != null) { // 检查会话是否还存活 if (isSessionAlive(metadata)) { // 标记会话为正在使用 markSessionAsInUse(metadata.getId()); // 根据元数据获取或创建会话连接(这里假设你有封装好的SMPP连接工具类) return SmppConnectionUtil.getOrCreateSession(metadata); } else { // 会话已失效,更新状态为断开,然后尝试新建 updateSessionStatus(metadata.getId(), "DISCONNECTED"); return createNewSession(); } } else { // 没有可用会话,检查是否能新建 int currentSessionCount = getCurrentSessionCount(); if (currentSessionCount < MAX_SESSIONS) { return createNewSession(); } else { throw new RuntimeException("所有SMPP会话都在使用中,请稍后重试"); } } } // 获取可用的会话元数据(带行级锁) private SmppSessionMetadata fetchAvailableSessionMetadata() { String sql = "SELECT id, smpp_server, smpp_port, system_id, password, last_active_time " + "FROM smpp_sessions " + "WHERE status IN ('CONNECTED', 'IDLE') " + "FOR UPDATE SKIP LOCKED " + "FETCH FIRST 1 ROW ONLY"; try { return jdbcTemplate.queryForObject(sql, (rs, rowNum) -> { SmppSessionMetadata metadata = new SmppSessionMetadata(); metadata.setId(rs.getLong("id")); metadata.setSmppServer(rs.getString("smpp_server")); metadata.setSmppPort(rs.getInt("smpp_port")); metadata.setSystemId(rs.getString("system_id")); metadata.setPassword(rs.getString("password")); metadata.setLastActiveTime(rs.getTimestamp("last_active_time")); return metadata; }); } catch (EmptyResultDataAccessException e) { return null; // 没有可用会话 } } // 检查会话是否存活 private boolean isSessionAlive(SmppSessionMetadata metadata) { long elapsedSeconds = ChronoUnit.SECONDS.between(metadata.getLastActiveTime().toInstant(), Instant.now()); return elapsedSeconds < SESSION_TIMEOUT_SECONDS; } // 其他辅助方法:markSessionAsInUse、updateSessionStatus、createNewSession、getCurrentSessionCount等 // 这里就不一一写全了,核心就是操作数据库更新状态和统计数量 }
关键注意事项
- 千万别存会话对象本身:再强调一遍,SMPP会话里的socket连接、IO流都是和当前进程绑定的,序列化存进DB完全没用,必须存元数据,按需重建连接。
- 行级锁的使用:
FOR UPDATE SKIP LOCKED是这个方案的核心,能避免多个Pod同时争抢同一个会话,提升并发效率。 - 定时任务要靠谱:清理僵尸会话的定时任务必须保证稳定运行,不然会出现会话资源被占着用不了的情况。
这样一套下来,就能让22个Spring Boot Pod乖乖共享那5个SMPP会话了,既符合SMPP服务器的限制,又能最大化利用资源。
内容来源于stack exchange
相关产品推荐
相关产品推荐

