千万级并发架构下,关系型数据库应该如何优化?大厂是如何做分库分表的! (6)

那么我们为什么需要这个序列号,设想下,如果是同一毫秒同一台机器来请求,那么我们怎么保证他的唯一性,这个时候,我们就能用到我们的序列号,

目的是为了保证同一毫秒内同一机器生成的ID是唯一的,这个其实就是为了满足我们ID的这个高并发,就是保证我同一毫秒进来的并发场景的唯一性

12位(bit)可以表示的最大正整数是2^12-1=4095,即可以用0、1、2、3、....4094这4095个数字,来表示同一机器同一时间截(毫秒)内产生的4095个ID序号。

12位2进制,如果全部都是1的情况下,那么最终的值就是4095,也就是12bit能够存储的最大的数字是4095.

image-20210715213656945

图6-11 分库分表之后的数据DML操作

有序需要用到全局id,所以在user_info表需要添加一个唯一id的字段。

image-20210715215839419

图6-12

配置完成之后,在如下代码中引入signal方法。

@Slf4j @RestController @RequestMapping("/users") public class UserInfoController { @Autowired IUserInfoService userInfoService; SnowFlakeGenerator snowFlakeGenerator=new SnowFlakeGenerator(1,1,1); @PostMapping("/batch") public void user(@RequestBody List<UserInfo> userInfos){ log.info("begin UserInfoController.user"); userInfoService.saveBatch(userInfos); } @PostMapping public void signal(@RequestBody UserInfo userInfo){ Long bizId=snowFlakeGenerator.nextId(); userInfo.setBizId(bizId); String table=ConsistentHashing.getServer(bizId.toString()); log.info("UserInfoController.signal:{}",table); MybatisPlusConfig.TABLE_NAME.set(table); userInfoService.save(userInfo); } }

并且,需要增加一个mybatis拦截器,针对user_info表进行拦截和替换,从而实现动态表的路由。

@Configuration public class MybatisPlusConfig { public static ThreadLocal<String> TABLE_NAME = new ThreadLocal<>(); @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor(DbType.MYSQL); interceptor.addInnerInterceptor(paginationInnerInterceptor); DynamicTableNameInnerInterceptor dynamicTableNameInnerInterceptor = new DynamicTableNameInnerInterceptor(); Map<String, TableNameHandler> tableNameHandlerMap = new HashMap<>(); tableNameHandlerMap.put("user_info", (sql, tableName) -> TABLE_NAME.get()); dynamicTableNameInnerInterceptor.setTableNameHandlerMap(tableNameHandlerMap); interceptor.addInnerInterceptor(dynamicTableNameInnerInterceptor); return interceptor; } }

至此,一个基础的分库分表的演练就完成了,但问题仍然还未完全解决。

非分片键查询

我们对user_info表的分片,是基于biz_id来实现的,也就是意味着如果我们想查询某张表的数据,必须先要使用biz_id路由找到对应的表才能查询到。

那么问题来了,如果查询的字段不是分片键(也就是不是biz_id),比如本次分库分表实战案例中,运营端查询就有根据名字、手机号、性别等字段来查,这时候我们并不知道去哪张表查询这些信息。

非分片键和分片键建立映射关系

第一种解决办法就是,把非分片键和分片键建立映射关系,比如login_name -> biz_id 建立映射,相当于建立一个简单的索引,当基于login_name查询数据时,先通过映射表查询出login_name对应的biz_id,再通过biz_id定位到目标表。

映射表的只有两列,可以成再很多的数据,当数据量过大时,也可以对映射表做水平拆分。 同时这种映射关系其实就是k-v键值对的关系,所以我们可以使用k-v缓存来存储提升性能。

同时因为这种映射关系的变更频率很低,所以缓存命中率很高,性能也很好。

用户端数据库和运营端数据库进行分离

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zzdsws.html