Official website: http://t.vipgp88.com/tyurl -> http://tinyurl.vipgp88.com/
Skywalking website: http://t.vipgp88.com/skywk -> http://skywalking.vipgp88.com/
Kibana website: http://t.vipgp88.com/kibna -> http://kibana.vipgp88.com/
Sentinel website: http://t.vipgp88.com/sntel -> http://sentinel.vipgp88.com/
Dubbo Admin website: http://t.vipgp88.com/dubbo -> http://dubbo.admin.vipgp88.com/
RocketMQ website: http://t.vipgp88.com/rktmq -> http://rocketmq.vipgp88.com/
Nacos website: http://t.vipgp88.com/nacos -> http://nacos.vipgp88.com/
- 输入长地址,系统生成短url
- 输入长地址,用户自定义短url
- 访问短url, 跳转长地址
- 用户自定义短url使用前面5位,系统生成的短url使用剩下的位数,因此用户自定义的与系统生成的不会产生竞争
- 短地址生成后不会更新和删除
- 支持一段时间内相同的长地址生成相同的短url
- 活跃用户5.5亿, 每天发表2贴,1/10有链接,那么每天有1.1亿个url
- 写并发量为:110,000,000 / 24 / 60/ 60 = 1273
- 考虑写巅峰20倍,写qps为25460,较高
- 读写比例10:1,读qps为254600,较高
- 7位字符
- Base62
- 总量62^7=3 521 614 606 208, 约有35216亿, 每天消耗1.1亿,可以使用87年
- 短url 64字节,长url 256字节
- 一天的消耗:(((64+256)B)x1.1x10,000,000)/(1024x1024x1024) = 32GB
- 一年的容量:(((64+256)B)x1.1x10,000,000x365)/(1024x1024x1024x1024) = 11TB
- 一天产生1.1亿条记录,1年401.5亿条记录,5年2007.5亿条记录
- 位图每一个位是1bit
- 1天消耗:1.1x100000000/8/1024/1024=13m
- 5年消耗:(1.1x100000000/8/1024/1024/1024)x5x365=23G
- 7位短地址key总共需消耗:62^7bit=410G
- 一台服务器带宽20Mbps, 一个请求字节数1KB, 则极限QPS=20Mbps/8/1KB=2560
- DNS负载 -> LVS四层负载 -> Nginx七层负载
- mysql分库分表,主从主备,读写分离
- 一致性hash
- innodb默认页大小16KB(show variables like 'innodb_page_size')
- 一个索引节点大小:8(id, bigint)+辅助数据9=17B,每页节点数:16KB/17B=960, 索引为960叉树
- 树高如果是4的话,960的3次方=884736000,单表记录能到8亿,查找一个值最多需要3次磁盘IO。树高如果是3的话,960的2次方=921600,单表记录92万,2次磁盘IO
- 单表1000万条记录,基本上磁盘IO在2-3次左右
- 5年容量规划,5个节点,每个节点400亿条记录,单表1000万条记录,每个节点4000张表
- redis分片,主从主备,读写分离
- 一致性hash
- 本地caffine缓存
- redis支持ShardedJedis和JedisCluster, 但ShardedJedis不支持Lua
- 多级缓存
- 加锁读库
- 过半随机失效策略
- 防止缓存击穿、缓存穿透、缓存雪崩
- ID生成器本地分发
- 数据库写支持2种模式:同步提交和异步组提交。同步提交的瓶颈在数据库性能,组提交的难点在数据一致性
- 数据库组提交,解决数据库写瓶颈
- 数据一致性保证:WAL,2阶段提交,graceful shutdown, crash safe(txn+snapshot), 引入分布式消息队列RocketMQ做rollback
- IO优化:padding,顺序写,零拷贝,同步延迟组提交,异步组提交
- Web层 -> 接入层 -> 服务层 -> 存储层
- tinyurl-ui, web层入口,React项目
- tinyurl-api, 接入api层,api级别本地缓存,SpringBoot Maven项目
- tinyurl-dubbo-provider, dubbo服务层,实现所有的业务逻辑,SpringBoot Maven项目
- doraemon-parent, ID生成器dubbo服务,SpringBoot Maven项目
- 基于数据库号段预读
- 双buffer
- 3台1c2g,2Mbps(峰值),原生部署3个节点的zookeeper集群(注册中心)和3个节点的nacos集群(配置中心)
- 1台2c8g,1Mbps(峰值), docker部署ELK、skywalking、rocketmq(2nameserver2master2slave)
- 1台4c8g,100Mbps(峰值), docker部署3主3从redis cluster集群,原生部署nginx
- 1台2c16g,2Mbps(峰值),原生部署DubboAdmin、Sentinel Website、doraemon-server dubbo服务
- 3台4c8g,100Mbps(峰值), 原生部署3个节点的tinyurl-api、3节点的tinyurl-dubbo-provider
- 1台1c2g RDS实例, 250g SSD, 新建3个主库,从库没有部署,读写分离时直接连接3个主库当从库
- 健康检测:云服务商提供
- 日志: ELK
- 调用链监控:Skywalking
- 指标监控:Prometheus
- 流量监控:Sentinel
- git+jenkins
- ab, jmeter
- arthas, top(1/H), jstat, jstack, jps
- sh in bin: startup.sh, shutdown.sh, stats.sh
- 热点探测
- 安全防刷
- 数据统计
- ECS - network: 流入57Mbps,流出53Mbps
- ab: qps 15891
启动参数:关闭偏向锁
nohup java -jar -Xmx2048m -Xms2048m -Xmn1024m -XX:SurvivorRatio=3 -XX:MetaspaceSize=256m -XX:-UseBiasedLocking -XX:+UnlockDiagnosticVMOptions -XX:+LogVMOutput -XX:LogFile=/opt/tinyurl/logs/vm.log -XX:+PrintHeapAtGC -XX:+PrintTenuringDistribution -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCApplicationStoppedTime -Xloggc:/opt/tinyurl/logs/tinyurl-provider-gc.log -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/opt/tinyurl/logs/tinyurl-provider-dump.log -javaagent:/opt/skywalking/provideragent/skywalking-agent.jar -Djava.net.preferIPv4Stack=true -Dcsp.sentinel.api.port=8085 -Dproject.name=tinyurl-dubbo-provider -Dcsp.sentinel.dashboard.server=127.0.0.1:4899 tinyurl-dubbo-provider.jar --spring.profiles.active=prod > /opt/tinyurl/logs/tinyurl-provider.log 2>&1 &
echo "start successfully!"
- 应用参数:设置数据库同步提交
# 开启数据库同步提交
tiny.url.db.commit.sync=true
# ID生成器预取号段
leaf_alloc.step=15000
- dubbo service provider - ECS - CPU: 40%使用率
- 数据库:cpu 56.22%, IOPS 1382.25次/秒
- ECS - network: 流入9Mbps,流出12Mbps
5. ab: qps 1476
- stats: nginx->api 耗时0.8ms,api->dubbo provider 耗时1.2ms
nginx-stats:
api-stats:
dubbo provider stats: 接口耗时65.43ms,其中数据库事务插入耗时65ms
- 应用参数:设置数据库异步提交2阶段P日志同步提交
# ID生成器每次预取15000号段
leaf_alloc.step=15000
# 开启数据库异步提交
tiny.url.db.commit.sync=false
# 开启2PhaseCommit-P日志同步提交
txn.prepare.log.delay.commit=false
# 数据库组提交时间,过半随机策略,10s为基数
tiny.url.group.commit.sync.delay=10000
# 2PhaseCommit-C日志每隔5秒异步提交一次
txn.log.commit.delay=5000
# 2PhaseCommit-C日志累计超过10万次记录异步提交一次
txn.log.commit.no.delay.count=100000
- dubbo service provider - ECS - CPU: 40%使用率
- 磁盘:写IOPS 1342个,写吞吐量14MBps
- 数据库:cpu 1%, IOPS 17.5次/秒
- ECS - network: 流入6Mbps,流出4Mbps
6. ab: qps 432
- stats: nginx -> api 耗时0.5ms,api->dubbo provider耗时0.5ms
nginx-stats:
api-stats:
dubbo provider stats: 接口耗时229.8ms,其中写磁盘耗时229.03ms,包括 sync disk 耗时2.27ms和锁竞争耗时226.76ms
- 应用参数:设置数据库异步提交2阶段P日志延迟提交
# ID生成器每次预取15000号段
leaf_alloc.step=15000
# 开启数据库异步提交
tiny.url.db.commit.sync=false
# 开启日志延迟提交
txn.prepare.log.delay.commit=true
# 延迟等待算法用timed-barrier
txn.prepare.log.delay.mode=timed-barrier
# 数据库组提交时间,过半随机策略,10s为基数
tiny.url.group.commit.sync.delay=10000
# 禁用重复ID检查
tiny.url.id.duplicate.validate.enable=false
# 延迟7毫秒提交,本机环境最佳值
txn.prepare.log.timed.barrier.sync.delay=7
# 设置1个segment,本机环境最佳值
txn.prepare.log.timed.barrier.segments=1
# 设置日志文件大小,64M `
pre.alloc.size=64
# 2PhaseCommit-C日志每隔5秒异步提交一次
txn.log.commit.delay=5000
# 2PhaseCommit-C日志累计超过10万次记录异步提交一次
txn.log.commit.no.delay.count=100000
- dubbo service provider - ECS - CPU: 80%使用率
- 磁盘:写IOPS 223个,写吞吐量 32MBps
- 数据库:cpu 4%, IOPS 21次/秒
- ECS - network: 流入32Mbps 流出22Mbps
6. ab: qps 2872
- stats: nginx -> api 耗时2ms,api->dubbo provider耗时3ms
nginx stats:
api stats:
dubbo-provider stats: 接口耗时28.1ms,其中写磁盘耗时15ms,包括 sync disk 耗时8.8ms和锁竞争耗时6.2ms