遇到的问题:

  1. 发号器选择(最终选择为使用乐观锁方式实现的数据库发号)
  2. 数据存储(mysql)
  3. 为什么不使用雪花算法发号
  4. 发号器并发测试tps不高怎么解决
  5. mysql数据库字段值默认不区分大小写,导致短链重复

发号器选择:

1: 雪花算法 ,
2:数据库乐观锁发号(不停的更新数据库中的一条数据来发号) 3:多个数据库乐观锁发号器(相当于2的扩展)

数据存储:

1,关系数据库mysql存储,数据结构上使用分库分表和读写分离,使用的组件是ShardingJdbc ,分库是通过业务去区分,不同的业务场景数据存储到不同的库中, 每个库中直接使用100个表来存储数据
分库字段为dbShare,路由规则为 :database-strategy.inline.algorithm-expression = ds$->{dbShare}

分表字段为 tableShare, 路由规则为: table-strategy.inline.algorithm-expression = t_shorturl$->{tableShare % 100}

2,使用Hbase(因为不熟悉,所以没使用)

为什么不使用雪花算法发号:

首先我们的短链是一个62进制的字符串(网上的短链生成规则都是这个,原理是低进制转换为高进制长度会减短,具体请自行百度),这个字符串是通过发号器分发的一个Long类型数据转换而来,
雪花算法的正好是生成一个Long类型数据,看起来很合适,但是,雪花算法的发号是随着时间增长而增长的,即使把起始偏移量设置的很接近当前时间,但是很快会增长到一个很大的数字,这时转换后的短链至少都会有10位长度,但是我们的要求是6为最多,所以只能抛弃这个方案

发号器并发测试tps不高怎么解决:

  1. 首先发号不能一个一个发,这样单点发号器肯定不能抗住很高的并发,所以我们可以一段一段的发,比如每次发给一个微服务的实例1000个号,预存到内存,这样就可以减少很多并发,不过这个方案的问题是一旦服务重启,那么内存中未消耗的号码会被浪费,这时就要根据你们自己的业务来权衡这个值需要设置为多少了.

  2. 即使分段发,当你有批量生成短链的业务时,这样每一次请求就会消耗更多的号,如 :你每次发1000个号 ,但是批量接口每次也消耗1000个号,那么这样就和一次发一个好没有区别了.
    对于2的问题解决方案:
    2.1. 限制批量接口的生成数量,不能超过发号的个数1/10,但是这样每次发号需要更多个,那么浪费的可能也更大
    2.2. 水平扩展,发号器相当于数据表中一条数据而已(具体实现后面有介绍),那么我们可以使用100个发号器,每个发号器发不同段的号码,如:1发号器发出的号码对100取余都是1 ,2发号器发出的号码对100取余都是2 ,以此类推. 这样就可以瞬间把并发能力提高100倍.这样90%的业务场景都能抗住非常高的tps了

  3. 发号器编码优化
    对于以上的发号器方案,虽然可以达到很高的tps,但是因为使用的是乐观锁,也就是快速失败(fastfail)的思想,这样就会导致发号失败的概率会比较高,经过我所在公司的压力测试,一般会达到0.12%左右,还是不能达到公司的0.01%错误率要求,此时就需要稍微降低一点tps要求,降低错误率。
    public Long getNumber(Integer times,long time,TimeUnit timeUnit) 使用此方法发号,输入合适的参数,增加重试的次数,可以大大降低发号失败的概率。在我自己的压力测试过程中,我们的参数是time=5秒,times=10次,此时的压力测试结果tps已经能达到类390多,发号失败率已经降到0.0%。(以上压力测试结果都是线上真实环境,服务器为5个节点,都是1核1g的配置。压力测试时,cpu和内存压力都很小,主要的瓶颈是在数据库。)

mysql数据库字段值默认不区分大小写,导致短链重复:

因为mysql数据库的值不区分大小写,那么发号后的短链gd,gD,Gd,GD在数据库中都是同一个号(因为测试环境我使用的3个表,所以肯定有一个数据会重复插入),但是实际应该是多个,那么插入数据库时就会违反了唯一索引原则,导致插入失败.但是因为一开始不知道这个问题,一直以为是发号器问题,导致发号重复了,经过千辛万苦的排出得出这么个结论.唉…

发号器: 数据库中字段

id: 发号器id ,
min_id: 当前发号器已经发出号码段的最小id
max_id: 当前发号器已经发出号码段的最大id
step: 每次发号的个数
(number_step: 发号的间隔,实际是你有多少个发号器就是多少,发号开始后就不能改变,此字段我没有存在数据库,而是放在发号的业务代码中,且这个字段是跟具体的发号实现相关的,所以看个人选择)

原理 : 乐观锁 ,每次发号先查数据库,然后以发号器id 和查到的max_id(min_id)作为条件来更新表数据,如果成功表示发号成功,失败则发号失败,此时你可以再尝试重新发号即可.

数据表的字段 :

id , shortUrl ,longUrl ,dbShare ,tableShare , 其他业务字段等

分库分表的配置 :

##所有数据源配置 共有两个主库master0,master1,四个从库master0slave0,master0slave1,master1slave0,master1slave1
spring.shardingsphere.datasource.names = master0,master1,master0slave0,master1slave0
#master0 主库0 作为默认库使用
spring.shardingsphere.datasource.master0.type = com.zaxxer.hikari.HikariDataSource
spring.shardingsphere.datasource.master0.driver-class-name = com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.master0.jdbc-url = jdbc:mysql://xxx:3306/short_url?useUnicode=true&characterEncoding=UTF-8&useSSL=true&serverTimezone=Asia/Shanghai
spring.shardingsphere.datasource.master0.username =
spring.shardingsphere.datasource.master0.password =
#master0slave0 主库0的从库0
spring.shardingsphere.datasource.master0slave0.type = com.zaxxer.hikari.HikariDataSource
spring.shardingsphere.datasource.master0slave0.driver-class-name = com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.master0slave0.jdbc-url = jdbc:mysql://xxx:3306/short_url?useUnicode=true&characterEncoding=UTF-8&useSSL=true&serverTimezone=Asia/Shanghai
spring.shardingsphere.datasource.master0slave0.username =
spring.shardingsphere.datasource.master0slave0.password =

#master1 主库1 要配置分库规则才能定位到这个库
spring.shardingsphere.datasource.master1.type = com.zaxxer.hikari.HikariDataSource
spring.shardingsphere.datasource.master1.driver-class-name = com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.master1.jdbc-url = jdbc:mysql://xxx:3306/short_url?useUnicode=true&characterEncoding=UTF-8&useSSL=true&serverTimezone=Asia/Shanghai
spring.shardingsphere.datasource.master1.username =
spring.shardingsphere.datasource.master1.password =
#master1slave0 主库1的从库0
spring.shardingsphere.datasource.master1slave0.type = com.zaxxer.hikari.HikariDataSource
spring.shardingsphere.datasource.master1slave0.driver-class-name = com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.master1slave0.jdbc-url = jdbc:mysql://xxx:3306/short_url?useUnicode=true&characterEncoding=UTF-8&useSSL=true&serverTimezone=Asia/Shanghai
spring.shardingsphere.datasource.master1slave0.username =
spring.shardingsphere.datasource.master1slave0.password =

#t_shorturl表的分库分表策略配置
spring.shardingsphere.sharding.tables.t_shorturl.actual-data-nodes = ds−>0..1.tshorturl->{0..1}.t_shorturl−>0..1.ts​horturl->{0…99}
spring.shardingsphere.sharding.tables.t_shorturl.database-strategy.inline.sharding-column = dbShare
spring.shardingsphere.sharding.tables.t_shorturl.database-strategy.inline.algorithm-expression = ds−>dbSharespring.shardingsphere.sharding.tables.tshorturl.table−strategy.inline.sharding−column=tableSharespring.shardingsphere.sharding.tables.tshorturl.table−strategy.inline.algorithm−expression=tshorturl->{dbShare} spring.shardingsphere.sharding.tables.t_shorturl.table-strategy.inline.sharding-column = tableShare spring.shardingsphere.sharding.tables.t_shorturl.table-strategy.inline.algorithm-expression = t_shorturl−>dbSharespring.shardingsphere.sharding.tables.ts​horturl.table−strategy.inline.sharding−column=tableSharespring.shardingsphere.sharding.tables.ts​horturl.table−strategy.inline.algorithm−expression=ts​horturl->{tableShare % 100}

#读写分离主从配置
spring.shardingsphere.sharding.master-slave-rules.ds0.master-data-source-name = master0
spring.shardingsphere.sharding.master-slave-rules.ds0.slave-data-source-names = master0slave0
spring.shardingsphere.sharding.master-slave-rules.ds1.master-data-source-name = master1
spring.shardingsphere.sharding.master-slave-rules.ds1.slave-data-source-names = master1slave0
#日志打印配置
spring.shardingsphere.props.sql.show = false
#不参与分库分表的默认库配置 (即:master0,master0slave0 会作为默认的库用来读写)
spring.shardingsphere.sharding.default-data-source-name = ds0

发号器代码:

import com.ec.business.shorturl.domain.NumberDTO;
import com.ec.business.shorturl.infrastructure.GeneratorMapper;
import com.ec.common.config.ShortUrlApolloConfig;
import org.apache.commons.lang3.RandomUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;/*** 标志生成器(发号器)** @author 020102*/
@Repository
public class SignGenerator {/**  一些配置属性  */@Autowiredprivate ShortUrlApolloConfig shortUrlApolloConfig;@Autowiredpublic SignGenerator(GeneratorMapper dbGenerator) {this.dbGenerator = dbGenerator;}/**  操作数据库Mapper */private final GeneratorMapper dbGenerator;private NumberDTO numberDtoRange = null;/*** 发号步长,此值一旦确定就不能改变*/private final Long numberStep = 100L;/*** 发号器** @param times 重试次数* @return*/public Long getNumber(Integer times) {Long number = null;while (times > 0 && (number = this.getNumber()) == null) {try {Thread.sleep(5);} catch (InterruptedException e) {}times--;}return number;}/*** 发号器** @param times 重试次数* @param time 时间* @param timeUnit 时间time的单位* @return*/public Long getNumber(Integer times,long time,TimeUnit timeUnit) {Long number = null;long end = TimeUnit.MILLISECONDS.convert(time,timeUnit)+System.currentTimeMillis();do{number = this.getNumber();times--;}while(number==null&&(end>System.currentTimeMillis()||times>0));return number;}/*** 发号器** @return 号码结果*/private Long getNumber() {synchronized (SignGenerator.class) {if (numberDtoRange == null || numberDtoRange.getNextNumber() > numberDtoRange.getCurrent_max_id()) {/* 还没发过号 或者 已使用完了则重新获取号段 */numberDtoRange = getNumberRange(RandomUtils.nextInt(1, 101));}if (numberDtoRange == null) {/* 发号失败 */return null;} else {/* 发号成功 */Long number = numberDtoRange.getNextNumber();numberDtoRange.setNextNumber(number + numberStep);return number;}}}/*** 获取号段* shortUrlApolloConfig.getIncrementStep() 为每次发号个数* @param id 发号器id* @return*/private NumberDTO getNumberRange(Integer id) {NumberDTO byId = dbGenerator.getByGeneratorId(id);Long oldMax = byId.getCurrent_max_id();byId.setOld_current_max_id(oldMax);byId.setCurrent_min_id(oldMax + numberStep);byId.setCurrent_max_id(oldMax + shortUrlApolloConfig.getIncrementStep() * numberStep);byId.setNextNumber(byId.getCurrent_min_id());byId.setIncrement_step(shortUrlApolloConfig.getIncrementStep());int update = dbGenerator.update(byId);if (update > 0) {return byId;} else {return null;}}}

记一次短链系统设计:相关推荐

  1. 系统设计.短链系统设计

    目录 啥是短链 为啥需要短链 短链原理 301 和 302 的区别 短链系统 方案演进 直接记录 长链和短链映射表,直接查询 存储映射关系,用HashCode优化查询效率 Redis替代DB存储映射关 ...

  2. 短链系统设计-服务设计

    2 Service 服务 - 逻辑块聚类与接口设计 该系统其实很简单,只需要有一个 service即可:URL Service.由于 tiny url只有一个 UrlService: 本身其实就是个小 ...

  3. 短链系统设计-场景需求及性能要求分析

    如脉脉,不会纵容你发太长的网址,会给你转成短链. 1 Scenario 场景 根据一个 long url 生成一个short url. 如 http://www.javaedge.com => ...

  4. 短链系统设计-用户自定义短链

    5 用户自定义短链接 实现一个顾客短网址,使得顾客能创立他们自己的短网址.即你需要在前文基础上再实现一个 createCustom. 需实现三个方法: long2Short(url) 把一个长网址转换 ...

  5. 短链系统设计-存储设计

    3 Storage 数据存取(最能体现实践经验) select 选存储结构 scheme 细化数据表 3.1 SQL V.S NoSQL 需要事务吗?No,nosql+1 需要丰富的 sql quer ...

  6. 系统设计——如何设计一个高性能的短链接系统?

    短链系统设计看起来很简单,但如何设计一个高性能短链系统呢,这也是面试中非常常见的一道设计题. 首先,为什么要用短链? 短链跳转的基本原理是什么? 短链生成的几种方法你知道吗? 高性能短链的架构如何设计 ...

  7. 如何设计一个短链服务?

    相信很多小伙伴都使用过短链服务,但如果让你实现一个短链服务,你知道怎么实现吗?其实实现短链服务并不是很难,最主要还是需要知道一些设计思路,还需要有一些基础技术知识,例如:哈希算法.全局发号器等. 短链 ...

  8. 短链有啥好处,用长链不香吗

    原文链接 前言 今天,我们来谈谈如何设计一个高性能短链系统,短链系统设计看起来很简单,但每个点都能展开很多知识点,也是在面试中非常适合考察侯选人的一道设计题,本文将会结合我们生产上稳定运行两年之久的高 ...

  9. 如何设计一个高性能短链系统?

    目录 前言 短链有啥好处,用长链不香吗 短链跳转的基本原理 短链生成的几种方法 1.哈希算法 如何缩短域名 如何解决哈希冲突的问题? 2.自增序列算法 高性能短链的架构设计 总结 前言 今天,我们来谈 ...

最新文章

  1. python介绍和用途-Python对象与引用的介绍
  2. python io流,Python io流会在列表理解中自动关闭吗?
  3. IDEA的UML图介绍(一)
  4. JAVA学习之常用集合List,Set,Map
  5. Android之ADB常用命令
  6. 十面阿里,菜鸟,天猫,蚂蚁金服题目总汇
  7. (实模式+保护模式)模式切换的过程步骤(代码+文字解析)
  8. XX公司定制开发的仓库管理系统
  9. android恢复出厂设置的流程
  10. mysql 二次方函数_MySQL SQRT函数:求二次方根
  11. 从“无影”看云电脑的未来
  12. 八问顺丰,被你们偷的iPad究竟何时能赔?
  13. INTERVAL函数的使用
  14. [原创] PS鼠绘人物五官详解之嘴巴
  15. LAMP环境部署及搭建网校系统
  16. java 延时发送邮件_基于SpringBoot实现定时发送邮件过程解析
  17. Winform通过按钮打开文件夹以及打开文件
  18. Docker部署onlyoffice并完成20最大连接数破解
  19. 如何让自己变得更优质?
  20. springboot 控制台输出错误信息_Spring boot使用logback实现日志配置

热门文章

  1. Attention机制学习记录(四)之Transformer
  2. 【机器学习】机器学习基本概念/术语
  3. WonderTrader的wtpy简洁搭建流程
  4. java 下载apk并安装-代码实例
  5. [微小说]根际大战——守护圣树
  6. Arduino实验二十三 倾斜开关实验
  7. 彩色打印输出文本(echo,grep,perl)
  8. 论文阅读:EMPIRICAL ANALYSIS OF UNLABELED ENTITY PROBLEM IN NAMED ENTITY RECOGNITION
  9. python的int的作用_int在python中的含义以及用法
  10. python对建筑设计的帮助_建筑师为什么要会python编程?