分库分表时一般有必要自定义生成uuid,大企业一般有自己的uuid生成服务,其他它的实现很简单。我们以订单号为例,组成可以是"业务标识号+年月日+当日自增数字格式化",如0001201608140000020。当然,如果我们用"业务标识号+用户唯一标识+当前时间"也是可以达到uuid的目的的,但用户唯一标识是敏感信息且可能不太方便处理为数字,所以弄一套uuid生成服务是很有必要的。本文就来研究下怎么实现自增数字,且性能能满足企业中的多方业务调用。起初,我想的是DB+Redis,后来想想用Redis不仅会相对降低稳定性,更是一种舍近求远的做法,所以,我最终的做法是DB+本地缓存(内存)。不说了,直接上代码。
public class UuidModel implements Serializable { private static final long serialVersionUID = 972714740313784893L; private String name; private long start; private long end; // above is DB column private long oldStart; private long oldEnd; private long now;
package com.itlong.bjxizhan.uuid; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.List; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; /** * Created by shenhongxi on 2016/8/12. */ public class UuidContext { private static final Logger log = LoggerFactory.getLogger(UuidContext.class); // 缓存DB中的截止数 public static ConcurrentMap<String, Long> endCache = new ConcurrentHashMap<String,Long>(); // 缓存当前增加到的数值 public static ConcurrentMap<String, Long> nowCache = new ConcurrentHashMap<String,Long>(); // 缓存共享对象 public static ConcurrentMap<String, UuidModel> uuidCache = new ConcurrentHashMap<String, UuidModel>(); // 缓存配置 public static ConcurrentMap<String, Config> configCache = new ConcurrentHashMap<String, Config>(); static UuidDao uuidDao; /** * 根据名称更新号段 直至成功 * @param um * @return */ public static UuidModel updateUuid(UuidModel um, int length){ boolean updated = false; do{ UuidModel _um = uuidDao.findByName(um.getName()); int cacheSize = 1000; Config config = getConfig(um.getName()); if (config != null) { cacheSize = config.getCacheSize(); } // 判断是否需要重置 条件为:1.配置的重置数<新段的截止数 则需要重置 // 2.新段的截止数大于需要获取的位数 则需要重置 long resetNum = config.getResetNum(); // 取得新段的截止数 long newEnd = _um.getEnd() + cacheSize; um.setOldEnd(_um.getEnd()); um.setOldStart(_um.getStart()); if ((resetNum < newEnd) || (String.valueOf(newEnd).length() > length)) { // 需要重置为0开始段 um.setStart(0); um.setEnd(cacheSize); } else { // 取新段 um.setStart(_um.getEnd()); um.setEnd(_um.getEnd() + cacheSize); } // 最终的更新成功保证了多实例部署时,各实例持有的号段不同 updated = uuidDao.update(um); } while (!updated); return um; } /** * 载入内存 * @param um */ public static void loadMemory(UuidModel um){ endCache.put(um.getName(), um.getEnd()); nowCache.put(um.getName(), um.getStart()); uuidCache.put(um.getName(), um); } public static Config getConfig(String name) { Config config = configCache.get(name); if (config == null) { config = configCache.get("default"); } return config; } }
package com.itlong.bjxizhan.uuid; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.text.SimpleDateFormat; import java.util.Date; /** * Created by shenhongxi on 2016/8/12. */ public class UuidServiceImpl implements UuidService { private static final Logger log = LoggerFactory.getLogger(UuidServiceImpl.class); private UuidDao uuidDao; @Override public String nextUuid(String name) { // 日期 + format(nextUuid(name, cacheSize, length)) } private synchronized long nextUuid(String name, int cacheSize, int length) { UuidModel um = UuidContext.uuidCache.get(name); Long nowUuid = null; try { if (um != null) { synchronized (um) { nowUuid = UuidContext.nowCache.get(name); Config cm = UuidContext.getConfig(name); // 判断是否到达预警值 if (UuidContext.nowCache.get(name).intValue() == cm.getWarnNum()) { log.warn("警告:" + name + "号段已达到预警值."); } log.info("dbNum:" + UuidContext.endCache.get(name) + ",nowNum:" + UuidContext.nowCache.get(name)); // 判断内存中号段是否用完 if (UuidContext.nowCache.get(name).compareTo(UuidContext.endCache.get(name)) >= 0) { // 更新号段 UuidContext.updateUuid(um, length); nowUuid = um.getStart() + 1; UuidContext.endCache.put(name, um.getEnd()); UuidContext.nowCache.put(name, nowUuid); } else { nowUuid += 1; // 是否需要重置 判断自增号位数是否大于length参数 if (String.valueOf(nowUuid).length() > length) { // 更新号段,需要重置 nowUuid = 1l; UuidContext.updateUuid(um, 0); UuidContext.endCache.put(name, um.getEnd()); UuidContext.nowCache.put(name, nowUuid); UuidContext.uuidCache.put(name, um); } else { // 直接修改缓存的值就可以了 UuidContext.nowCache.put(name, nowUuid); } } } } else { synchronized (this) { um = UuidContext.uuidCache.get(name); if (um != null) { return nextUuid(name, cacheSize, length); } nowUuid = 1l; // 如果缓存不存在,那么就新增到数据库 UuidModel um2 = new UuidModel(); um2.setName(name); um2.setStart(0); um2.setEnd(cacheSize); uuidDao.insert(um2); // 还要同时在缓存的map中加入 UuidContext.endCache.put(name, um2.getEnd()); UuidContext.nowCache.put(name, nowUuid); UuidContext.uuidCache.put(name, um2); } } } catch (Exception e) { log.error("生成uuid error", e); if (e.getMessage() != null && (e.getMessage().indexOf("UNIQUE KEY") >= 0 || e.getMessage().indexOf("PRIMARY KEY") >= 0)) { UuidModel _um = new UuidModel(); _um.setName(name); // 更新号段 UuidContext.updateUuid(_um, length); // 载入缓存 UuidContext.loadMemory(_um); // 继续获取 return nextUuid(name, cacheSize, length); } throw new RuntimeException("生成uuid error"); } return nowUuid; } }
值得一提的是,DB+本地缓存的思路同样可以用于抢购时的库存计算。
相关推荐
总的来说,分库分表是解决大数据量下数据库性能问题的有效手段,但同时也需要考虑其带来的复杂性和运维挑战。在Java开发中,合理地利用像ShardingSphere这样的中间件,可以简化这一过程并确保系统的高效运行。
在IT行业中,数据库设计是至关重要的,特别是在大型系统中,分库分表是解决高并发、大数据量场景下的常见策略。然而,随着数据的分散,如何生成全局唯一ID(Global Unique Identifier)成为一个关键问题。本话题将...
分库分表是应对大数据量、高并发场景下的数据库优化策略,旨在解决单表数据量过大、并发处理能力不足等问题。本文将深入探讨分库分表的基本概念、关键问题及其解决方案,并介绍相关开源工具和实际应用案例。 **一、...
分库分表是为了应对高并发、大数据量场景下的性能瓶颈,但是它带来了如分布式事务、分布式主键、跨库查询以及数据迁移等问题。 分布式事务是其中的一大难点,因为它涉及到多个数据库间的协调与一致性保证。解决这个...
7. ID生成策略:分库分表后,自增ID可能会冲突,需要设计全局唯一ID生成策略,例如雪花算法、UUID等。 8. 数据迁移与备份:分库分表后,数据迁移和备份也需要考虑各个库表之间的关系,确保数据完整性。 9. 监控与...
在这个主题下,我们将深入探讨分库分表的概念、原因、实现方式以及面试中可能涉及的相关问题。 首先,分库分表是为了应对单个数据库在数据量增大时性能下降的问题。当数据量达到一定程度,查询效率会显著降低,影响...
在分布式数据库环境中,确保分库分表后的ID全局唯一性是一项关键挑战。传统的自增ID在单库单表中能够很好地工作,但在分布式系统中,由于数据分散在多个数据库或表中,简单的自增策略无法满足全局唯一性的需求。在...
在大数据量的时候,会涉及分库分表,使用自增ID可能会导致ID重复,使用UUID是无序的,在创建主键索引的时候会频繁的修改索引树内的索引位置,让索引更新的效率很低等问题。索引此时就引入了雪花ID,它既能保证ID的...
分库分表是一种数据库扩展策略,用于解决单个数据库在高并发、大数据量场景下的性能瓶颈问题。在大型系统中,随着用户数量和业务规模的增长,数据库的压力会急剧增加,这时就需要采用分库分表的方式来分散负载,提高...
Spring Boot等框架提供了对分库分表的支持,如ShardingSphere等中间件可以帮助开发者实现这些策略,简化数据库的扩展和维护工作。 总之,MySQL数据库的分表分库优化是应对高并发、大数据量场景的有效手段。通过对...
Java编程语言在面试中常常涉及的关键知识点包括方法的重载(Overloading)和重写(Overriding)、多态性、Synchronized关键字的理解以及数据库的分库分表策略。下面将详细阐述这些概念。 1. **方法重载(Overloading)**...
- 分库分表是解决大数据量下数据库性能瓶颈的方法,通过水平拆分降低单表压力。 - “分库分表之后全局id咋生成?”涉及到分布式ID生成器,如Snowflake、Twitter的UUID等方案。 - “46_体验一下面试官对于分库分表...
分库分表或数据迁移会导致问题。 4. 批量生成ID 一次性生成多个ID,减少数据库访问次数,但可能导致ID不连续,且不便于水平扩展。 5. Redis自增 Redis的单线程特性可以保证ID的唯一性和顺序,但增加了中间件的...
随着数据量的不断增长,数据库的分库分表策略需要一个全局唯一的ID来标识每条记录,传统的数据库自增ID不再适用。因此,引入专门的分布式ID生成器解决方案成为必要。 ### UUID方案 UUID(Universally Unique ...
框架核心包,其他工程均依赖此包,核心特性如下:极简Controller,基于sharding-sphere的多数据源、分库分表支持,基于Mybatis 实现的 Db + Record 极简模式,附带物理分页实现,基于Consul的服务注册、发现,服务...
- **具体实现**:结合分库分表的场景,探讨如何在实际项目中实现全局唯一ID的生成与分配。 #### 6. RPC底层通讯原理之Netty线程模型源码分析 - **RPC通信基础**:了解远程过程调用(RPC)的基本概念和工作原理。 -...