- 第一章 小滴课堂-海量数据处理商用短链平台大课介绍
- 第二章 不在其位要谋其政,技术Leader能力模型提升
- 第三章 商用短链平台介绍和技术Leader常用方法论介绍
- 按照依赖
- 配置yum源(比较慢,不用)
- 配置yum源 使用国内的
- 查看版本
- 1. 安装docker
- 2. 查看docker版本
- 3. 启动docker
- 4. 查看docker 启动状态
- 改为下面内容,然后重启docker
- 查看信息
- 安装mysql8,让容器使用宿主机的时间,容器时间与宿主机时间同步
- Mysql工具连接测试
- 连接数配置
- 第六章 短链平台项目创建+git代码管理+开发分层规范讲解
- 第七章 商用短链平台实战-账号微服务+流量包设计
- —————sms短信配置———————
- 第十二章 账号微服务-阿里云OSS接入实战
- 阿里云OSS配置
- 数据源 ds0 第一个数据库
- 指定traffic表的数据分布情况,配置数据节点,行表达式标识符使用 ${…} 或 $->{…},但前者与 Spring 本身的文件占位符冲突,所以在 Spring 环境中建议使用 $->{…}
- 水平分表策略+行表达式分片
- id生成策略
- id生成策略
- —————服务注册和发现———————
- ———-redis连接配置———-
- ———-分库分表数据源配置———-
- 指定traffic表的数据分布情况,配置数据节点,行表达式标识符使用 ${…} 或 $->{…},但前者与 Spring 本身的文件占位符冲突,所以在 Spring 环境中建议使用 $->{…}
- 水平分表策略+行表达式分片
- id生成策略
- —————sms短信配置———————
- —————阿里云OSS配置———————
- —————短链组,策略:水平分库,不水平分表———————
- 先进行水平分库, 水平分库策略,行表达式分片
- ds0配置
- ds1配置
- dsa配置
- —————短链,策略:分库+分表———————
- 先进行水平分库,然后再水平分表
- 水平分表策略,自定义策略。 真实库.逻辑表
- id生成策略
- —————配置默认数据库,比如短链域名,不分库分表———————
- 默认id生成策略
- 第二十五章 短链服务冗余双写-链路测试和异常消息处理实战
- 第二十六章 短链服务冗余双写问题抛出-MQ消费者链路开发
- 第二十七章 短链码生成端选择和Lua分布式锁实战
- 第二十八章 短链服务-冗余双写B端分库分表和链路测试
- 第二十九章 短链服务-冗余双写架构删除和更新开发实战
- 第三十章 短链服务-冗余双写架构删除和更新消费者开发实战
- 第三十一章 流量包商品服务需求和库表讲解
- 第三十二章 流量包订单模块需求讲解和库表介绍
- 第三十三章 流量包商品服务-下单模块开发和订单防重提交
- 第三十四章 流量包商品服务-多场景自定义注解防重提交实战
- 第三十五章 下单接口-超时关闭订单功能设计+延迟队列实战
- 第三十六章 短链平台-从0教你掌握微信支付-最新V3版-准备阶段
- 第三十七章 密码安全-对称加密和非对称加密技术你知道多少
- 商户号
- 公众号id 需要和商户号绑定
- 商户证书序列号,需要和证书对应
- api密钥
- 商户私钥路径(微信服务端会根据证书序列号,找到证书获取公钥进行解密数据)
- 支付成功页面跳转
- 支付成功,回调通知
- —————kafka配置———————
- —————服务注册和发现———————
- 设置日志级别,ERROR/WARN/INFO/DEBUG,默认是INFO以上才显示
- 1.先安装yml
- 2.设置阿里云镜像
- 3.查看可安装的docker版本
- 4. 安装docker
- 5. 查看docker版本
- 6. 启动docker
- 7. 查看docker 启动状态
- 查看端口占用命令安装
- 1.先安装yml
- 2.设置阿里云镜像
- 3. 安装docker
- 4. 查看docker版本
- 5. 启动docker
- 6. 查看docker 启动状态
- 1.先安装yml
- 2.设置阿里云镜像
- 3. 安装docker
- 4. 查看docker版本
- 5. 启动docker
- 6. 查看docker 启动状态
- 这条配置表示开启xpack认证机制
- 创建目录
- 宿主机权限不够
- 启动运行
- —————服务注册和发现———————
- 用于rpc调用token验证
- ———-redis连接配置———-
- ———-分库分表数据源配置———-
- 指定traffic表的数据分布情况,配置数据节点,行表达式标识符使用 ${…} 或 $->{…},但前者与 Spring 本身的文件占位符冲突,所以在 Spring 环境中建议使用 $->{…}
- 水平分表策略+行表达式分片
- id生成策略
- —————sms短信配置———————
- —————阿里云OSS配置———————
- 需要手工创建虚拟主机
- 消息确认方式,manual(手动ack) 和auto(自动ack); 消息消费重试到达指定次数进到异常交换机和异常队列,需要改为自动ack确认消息
- 开启重试,消费者代码不能添加try catch捕获不往外抛异常
- 最大重试次数
- 重试消息的时间间隔,5秒
- —————xxl-job配置———————
- http://address01,http://address02“">调度中心部署地址,多个配置逗号分隔 “http://address01,http://address02“
- 执行器token,非空时启用 xxl-job, access token
- 执行器app名称,和控制台那边配置一样的名称,不然注册不上去
- [选填]执行器注册:优先使用该配置作为注册地址,为空时使用内嵌服务 ”IP:PORT“ 作为注册地址。
- 从而更灵活的支持容器类型执行器动态IP和动态映射端口问题。
- [选填]执行器IP :默认为空表示自动获取IP(即springboot容器的ip和端口,可以自动获取,也可以指定),多网卡时可手动设置指定IP,该IP不会绑定Host仅作为通讯实用;地址信息用于 “执行器注册” 和 “调度中心请求并触发任务”,
- [选填]执行器端口号:小于等于0则自动获取;默认端口为9999,单机部署多个执行器时,注意要配置不同执行器端口;
- 执行器日志文件存储路径,需要对该路径拥有读写权限;为空则使用默认路径
- 执行器日志保存天数
- 应用名称
- 服务注册发现
- 设置日志级别,ERROR/WARN/INFO/DEBUG,默认是INFO以上才显示
- 启动
- 重新加载配置文件
- 1.打开文件
- 2.添加环境变量
- 3.编译生效
- 1.捕获网络流量,表示监听80端口发生的所有网络活动记录到stdout
- 2.重放,将原始流量重放到其他环境中,同一个服务器但是端口不同,多个亦可
- 3.捕抓流量请求并保存到文件中,实际会保存为requests.gor文件名
- 4.从保存下来的流量文件中提取流量向某个端口输出
- 5.请求过滤指定路径流量,使用该机制,只记录/account-server/api路径下的请求
- 流量录制的监听命令
- —input-file 从文件中获取请求数据,重放的时候5倍速度重放; 比如10秒20条请求,扩大2倍后就是5秒就跑完20条请求
- —input-file-loop 无限循环,而不是读完这个文件就停止
- http://xdclass.net">—output-http 发送请求到 http://xdclass.net
- —stats —output-http-stats 每 5 秒输出一次 TPS 数据
- 查看当前连接数
- 查看最大连接数
- 查看连接睡眠时间,默认是 28800,相对较少调整这个,不能太短,也不能过长;
- 代码封装
- 第八十章 海量数据项目大课-商用短链平台总结
愿景:”让编程不再难学,让技术与生活更加有趣”
更多架构课程请访问 xdclass.net
第一章 小滴课堂-海量数据处理商用短链平台大课介绍
第1集 海量数据处理商用短链平台大课介绍
简介:海量数据处理商用短链平台大课介绍
- 课程核心介绍
- 目标岗位能力说明
- 适合人员
第2集 商用短链平台技术栈介绍和观看形式-更新周期
简介:商用短链平台技术栈介绍和观看形式-更新周期
第3集 海量数据处理商用短链平台项目核心亮点《上》
简介:海量数据处理商用短链平台项目亮点《上》
第4集 海量数据处理商用短链平台项目核心亮点《下》
简介:海量数据处理商用短链平台项目亮点《下》
第5集 大课解决的问题和跳槽职业发展规划
简介:大课解决的问题和跳槽职业发展规划
第6集 大课疑惑解答+学习准备
简介:大课疑惑解答+学习准备
愿景:”让编程不再难学,让技术与生活更加有趣”
更多架构课程请访问 xdclass.net
第二章 不在其位要谋其政,技术Leader能力模型提升
第1集 互联网大厂里 技术Leader的能力模型-不单写代码
简介:职级的能力模型,不要只顾自己的一亩三分地
前言先说
- 可能同学到现在都不知道【商用短链平台】是什么,没关系,后续讲完前面几章我们就开始了
- 前面几章是讲 产品方法论,就是技术Leader需要掌握的内容
不管你是否校招、社招、工作5到10年的人
- 不要说【二当家小D】瞎扯淡!!!不讲技术只讲虚幻的
- 我需要给大家打个强心剂!!!!
- 大课软硬技能都有,但是层级高的或者野心大的,就必须学
【不画大饼】看我们课程不会让你成为CTO、阿里P7、P8
- 课程让你成为CTO、阿里P7、P8的助力器,
- 我很鼓励团队的人进行充电,不管是线上还是线下
始终相信
- 量变->质变,没量变的人,是不可能有质变的
- 光输出不输入,你觉得能成长多少
浅谈Leader的好处和坏处
当leader好处
- 待遇高、股票、奖金
- 接触面、圈子广
- 软技能多方面提升
当leader坏处
- 多数是晚上才能安心写代码
- 背负团队KPI:技术+业务
- 操心事多
你上司为啥是你上司?(也就是大家需要提升的方向,并非一朝一夕,而是先有意识,然后不断积累)
- 能力模型
多个能力汇总起来就是商业化产品构建能力,负责业务产品线一号位的能力
- 帮企业找到新的盈利方向
- 商业模式(外) × 发展空间(外) × 核心壁垒(内) × 企业文化(内)
接下去重点教大家《产品+运营》这块的能力,然后为啥需要这能力,就不会觉得虚幻了
掌握这些能力的好处多多!!!!!
如果选一个好的公司,进公司如何转岗去一个好的部门-》发现能力
知道未来1~3年哪些方向好,万一站在风口上是吧,薪资待遇、股票期权
- 蚂蚁的一个同事:虽然是P7,但是第一批员工,如果蚂蚁上市-据说是整层楼最有钱的了
避免进入错的方向:公司倒闭、裁员、发不出工资
技术Leader的必备能力
- 公司技术产品的研发,比如算法平台、DevOps平台、规则引擎等
第2集 技术人的产品运营能力提升-竞品分析
简介:竞品分析-入职-提升-运营能力
什么是竞品分析
- 竞品:竞争对手的产品,叫竞品
- 分析:对这些收集的竞品加以分析,从而对自己的产品设计产生帮助
为什么要做竞品分析
- 熟悉产品
利用现有的商业模式去改进
- 抖音、视频号
- 拼多多、淘宝特价版(明星部门重要性-同事转岗去特价版,年终奖20个月+,然后晋升绿色通道)
谁去做?
- 产品
- 运营
- 技术leader
为啥是技术leader去做?
- C端产品文档,这个基本就不用技术去做了
但是大厂里面都是做C端的吗?
- BI后台
- 自动化测试平台
- APP应用配置中心
- 性能分析平台
- 看下阿里云产品线:https://www.aliyun.com/product/list
第3集 跳出自己的技术思维模型-上司给你团队安排任务
简介:跳出自己的技术思维模型-上司给你团队安排任务
目标
- 跳出自己的技术思维模型,去分析产品需求背后的合理性和必要性
- 让自己更快理解业务需求,从而进行开发
- 看需求文档
- 写需求文档
说说我的情况
- 产品经理:常规进行操作,技术负责开发
- 老板开会:探讨新业务、然后就是技术负责人进行分析
- 团队的产品经理:P7产品专家,技术出身,然后转产品
沟通起来特别轻松
- 数据埋点、AB测试、流量模型、扩容、性能
- 偶尔还能帮技术定位问题、看监控等
大老板阿里CEO张勇在20年内部也发话了
阿里未来的产品经理都是技术出身
- 技术:研发能力强
- 产品:思维能力强
- 技术+产品:知道解决方案、研发工时、技术合理性、数据能力、分析能力
多数例子
- 百度:李彦宏
- 腾讯:马化腾
- 微信:张小龙
- 字节:张一鸣
- 360:周鸿祎
- 阿里:马云(但是对技术是最看重的)
- Facebook:扎克伯格
- 微软:比尔盖茨
大气的老板:想做个短链平台,赋能公司业务,然后开启商业化对外出售
- 让你写-知道怎么写不
- 小滴短链商用平台需求文档地址: https://zhuanlan.zhihu.com/p/428514067
愿景:”让编程不再难学,让技术与生活更加有趣”
更多架构课程请访问 xdclass.net
第三章 商用短链平台介绍和技术Leader常用方法论介绍
第1集 商用短链平台业务介绍+需求背景说明
简介: 短链平台业务介绍+需求背景说明
- 什么是短链平台,应用场景有哪些
- 短链长啥样?
业务背景:为啥需要短链?
- 公司电商产品推广、业务活动页、广告落地页 缺少实时【数据反馈和渠道效果分析】
老项目业务推广【没人维护,无法做埋点】需要统计效果
- APP和营销活动发送营销短信链接过长,【浪费短信发送费用】
- 国内【反垄断后】微信、抖音、淘宝 流量互通,很多知识付费公司需要做 私域流量、社群运营
- 可以对外做产品输出,实现商业化能力增加公司营收
- 积累终端数据和人群数据,为公司未来产品人群做策略助力
更多。。。。
- 盈利点
用户按量付费,根据流量包选择付费购买对应的套餐
不同流量包权益不一样
- 每天可以创建的短链次数不一样
- 流量包使用时间限制、支持流量包叠加
- 注册用户每天有一定免费使用次数,但是不能查看数据
- 短链平台产品目标
满足公司现有业务的营销推广需求、数据分析和拉新促活能力
- 对外进行付费商用,支持企业私有化部署
- 首年日活用户: 10万
- 首年日新增短链数据:10万*50 = 500万
- 年新增用户数:50万/1年
- 年营收目标: 10万付费用户 * 客单价200元 = 2千万
- 新增短链:50条/用户每日
- 年私有化部署用户数:1K用户 * 3万/单价 = 3千万
上面需求背景、产品目标是怎么来的?为啥能想到这些?有没风险?有没遗漏的内容?
- 这个就是做事、思考 的方法论!!!
- 接下去几集重点要掌握(不要求一次性彻底掌握, 但是要有大概的认知,然后边学边实践,这个才能成长)
第2集 为什么需要学方法论 避免无章法做事和跳槽面试
简介: 避免无章法管理和做事,为什么需要学方法论
- 方法论:通俗来说就是【做事套路,解决问题的方法(手段/途径/工具)】 喜欢学术性的介绍可以百度更多的
你是否被问过类似这些 方法论技术面试题(考查你的逻辑思维是否清晰)
线上接口响应慢,你会怎么定位问题
- 监控统计问题、机房问题、某个节点问题、服务器带宽问题、CPU内存问题、网关问题、业务逻辑问题、并发量问题、数据库问题
sql查询耗时久,你会怎么排查优化
java应用发生了OOM,你会怎么排查问题
订单支付成功率下滑严重,你会怎么排查问题
RabbitMQ和RocketMQ技术选型,你会怎么选,考虑哪些因素
为什么要学方法论,常见的有哪些是必备的
新业务规划(PEST)
- 上线被下架 (K12教育、互联网金融)
运营推广(AARRR)
- 不被产品牵着走,但是也不盲目
SWOT态势分析法
- 个人成长、技术解决方案
团队管理(SMART)
- 合适的人合适的事
- 工作量分配
- 跳槽面试(逻辑考查)
第3集 公司行业前景分析-PEST方法论-选择大于努力
简介: 公司行业前景分析-PEST方法论-选择大于努力
PEST方法论
指的是政治(Political)、经济(Economic)、社会(Social)和技术(Technological)
- 政治环境主要是看我们的国家现在是否鼓励相关的业务
- 经济环境又可以分为宏观经济和微观经济,包括居民消费水平、产业结构
- 社会环境则是说跟社会的风俗习惯是否吻合
- 技术环境当然就是说的我们的技术实力(ASML光刻机)
案例:BAT区块链大牛找你开发特币交易平台app
- P 国家出台了相关政策、法律法规监管
- E 大家有闲钱,现在人居可支配收入高
- S 人口规模大,生活方式改变,投资理财越来越多
- T 团队已有对应的技术,行业领先,移动互联网成熟
案例延伸
互联网金融贷款产品 p2p
高中在线辅导产品
初中同学找你开发电商平台-对标淘宝、拼多多
聪明的人做聪明的事,避免自己入坑
行业选择错:白忙活几年,领域专家也吃亏
公司裁员、倒闭发不出工资:白干几个月,辛苦大半年,一夜回到解放前
大厂也一样:字节、阿里、腾讯、京东等
第4集 技术Leader必备方法论-用户增长的数据分析模型AARRR
简介: 技术Leader必备方法论-用户增长的数据分析模型AARRR
什么是AARRR用户增长模型
- AARRR是Acquisition、Activation、Retention、Revenue、Referral 五个单词的缩写,对应用户生命周期中的5个重要环节。
- 通俗来说就是一个产品从0~1到100的方法论
- 指引产品运营在不同的产品运营阶段,思考哪些关键节点,更好各个节点的指标数据
AARRR详细解释
- 获取:新用户首单免费/低价(瑞幸、拼多多)、厂商预装(手机)、买量投放
- 激活:app推送、短信推送、产品价值激活
- 留存:签到、活动短信推送、平台价值提供
- 收益:平台广告、电商变现、付费会员、融资、软件服务
- 传播:好友助力、分享抽奖、兄弟砍我一刀
第5集 技术Leader必备方法论-SWOT态势分析法-个人能力与技术解决方案
简介: 技术Leader必备方法论-SWOT态势分析法-个人能力与技术解决方案
什么是SWOT态势分析
- 官方:用来确定企业自身的竞争优势、劣势、外部市场的机会和威胁,从而将公司的战略与公司内部资源、外部环境有机地结合起来的一种科学的分析方法
4个单词的缩写 优势=strength、劣势=weakness、机会=opportunity、威胁=threats
- 优势和弱势是内部环境的分析,机会和威胁是对于外部环境的分析
``` 外部的机会正好是你的优势,赶紧利用起来
外部的机会但是你的劣势,需要改进
自身具有优势但外部存在威胁,就需要时刻思考、保持警惕
是威胁又是你的劣势,就规避并消除
![](img/image-20211103125244799.png#alt=image-20211103125244799)
-
案例应用场景
-
个人做技术Leader能力分析(工作3年,高级java,技术能力不错,项目组长刚离职)
-
优势:技术不错、对公司业务熟悉(个人内部)
-
劣势:项目管理能力不足、PPT汇报能力不足(个人内部)
-
机会: 独立负责的项目把控、直接和领导汇报,成为管理层(个人外部)
-
威胁:项目的规范机制没有建立、项目的核心难点没有攻破、加班比较多(个人外部)
-
技术解决方案分析(团队熟悉RabbitMQ,新来的组长熟悉RocketMQ,技术选型思考)
- 优势:RabbitMQ团队多人用过、AMQP跨语言、模型API丰富(团队内部)
- 劣势:阅读过源码的人过少, Erlang开发,二次修改不容易,项目组长对这个不熟悉(团队内部)
- 机会:项目可以快速上线,减少采坑(团队外部)
- 威胁:未来可能有更强大的MQ产品出现或公司改动架构(团队外部)
-
总结:根据SWOT进行充分分析,然后进行取舍选择,考虑更全面(对比没用这个分析你会怎么选择)
<a name="66f08107"></a>
#### 第6集 技术Leader必备方法论SMART衡量需求、工作的利器
**简介: 技术Leader必备方法论SMART衡量需求、工作的利器**
-
什么是SMART方法论
-
源于国外管理大师的《管理的实践》
-
是为了利于员工更加明确高效地工作,更是为了管理者将来对员工实施绩效考核提供了考核目标和考核标准,使考核更加科学化、规范化
-
是5个单词的缩写
- SMART原则【目标管理、设置】
- Specific:目标要具体
- Measurable:目标成果要可衡量(量化)
- Attainable:目标要可实现,避免过高/过低
- Relevant:与其他目标有一定的相关性
- Time bound:目标必须有明确的期限
-
意义:在制定工作目标或者任务目标时,考虑一下目标与计划是不是SMART化的。只有具备SMART化的计划才是具有良好可实施性的,也才能指导保证计划得以实现
-
案例
- 短链平台项目组成员,每天需要开晨会-并记录 ( 周一 早上团队开周会,每个人列出本周事项安排)
![](img/image-20211103132853684.png#alt=image-20211103132853684)
- 张三的周报如下
- 周三前完成短链平台数据库设计并输出相关sql文件(SMART)
- 周四前完成短链平台整体架构搭建和工作任务拆分并评审完成(SMART)
- 周二完成短链微服务开发工作 (SM RT)
- 开发账号微服务 (S)
- 解决老项目的bug ( )
- 周二完成优化电商项目里面的下单接口RT响应时间,把500ms优化到200ms响应(SMART)
![](https://file.xdclass.net/note/2020/javaweb/%E5%9B%BE%E7%89%87/logo.png#alt=logo) **愿景:"让编程不再难学,让技术与生活更加有趣" **
**更多架构课程请访问 xdclass.net**
<a name="8324eb53"></a>
### 第四章 商用短链平台-功能需求介绍和微服务拆分讲解
<a name="1952de4d"></a>
#### 第1集 学以致用-商用短链平台需求文档拆分和总结
**简介:学以致用-短链平台实战需求文档拆分和总结**
- 看了前面的方法论和拓展知识,我们再来巩固下需求文档和功能
- 小滴短链商用平台需求文档地址: [https://zhuanlan.zhihu.com/p/428514067](https://zhuanlan.zhihu.com/p/428514067)
<a name="0ee11249"></a>
#### 第2集 商用短链平台-微服务拆分和技术栈版本说明
**简介:海量数据处理商用短链平台-微服务拆分和技术栈版本说明**
-
Maven聚合工程拆分
- dcloud-common
- 公共依赖包
- dcloud-app
- Flink+Kafka实时计算
- dcloud-account
- 账号+流量包微服务
- dcloud-data
- 数据可视化微服务
- dcloud-gateway
- 业务网关
- dcloud-link
- 短链微服务
- dcloud-shop
- 流量包商品+支付微服务
-
微服务技术栈+前置中间件版本说明
- JDK11
- SpringBoot 2.5.5
- SpringCloud 2020.0.4
- AlibabaCloud 2021.1
- Sharding-JDBC 4.1.1
- Mysql 8.0
- Nacos 2.0.2
- Redis 6.2.4
- RabbitQM 3.8.15
- Kafka : wurstmeister/kafka:2.13-2.7.0
- 为啥有RabbitMQ还要有Kafka(单机写入TPS约在百万条/秒,最大的优点,就是吞吐量高)
- 一个是业务MQ、一个大数据流式处理的MQ,建议分开
- 还有更多的中间件用的时候再安装
<a name="b3e4db94"></a>
#### 第3集 商用短链平台-业务架构图讲解
**简介:商用短链平台-业务架构图讲解**
- 整体业务应用架构图
![](img/image-20211105080403794.png#alt=image-20211105080403794)
- 各个微服务模块解析
![](img/image-20211104151551376.png#alt=image-20211104151551376)
![](https://file.xdclass.net/note/2020/javaweb/%E5%9B%BE%E7%89%87/logo.png#alt=logo) **愿景:"让编程不再难学,让技术与生活更加有趣" **
**更多架构课程请访问 xdclass.net**
<a name="07197cf7"></a>
### 第五章 阿里云Linux服务器选配和常用中间件环境安装
<a name="3a663876"></a>
#### 第1集 云服务器配置和搭建Docker相关讲解
**简介:云服务器配置和搭建Docker环境讲解**
-
云服务器购买
-
厂商
-
阿里云:[https://www.aliyun.com/](https://www.aliyun.com/)
-
腾讯云:[https://cloud.tencent.com/](https://cloud.tencent.com/)
-
亚马逊云:[https://aws.amazon.com/](https://aws.amazon.com/)
-
腾讯云新用户折扣地址
- [https://cloud.tencent.com/act/cps/redirect?redirect=1575&cps_key=3cb5dbec53a023baba788acac8d11871&from=console](https://cloud.tencent.com/act/cps/redirect?redirect=1575&cps_key=3cb5dbec53a023baba788acac8d11871&from=console)
-
阿里云新用户地址(如果地址失效,联系我或者客服即可,买2核8g或者16g以上,如果资金允许-买3年)
- [https://www.aliyun.com/minisite/goods?userCode=r5saexap&share_source=copy_link](https://www.aliyun.com/minisite/goods?userCode=r5saexap&share_source=copy_link)
-
服务器说明
阿里云-服务器根据情况买1~2台就行, 建议买2核8g以上可以1年或者3年长久,建议大课群里3人组队,每人买1台就够用的了,组合起来就是3台了,大家学习时间基本是错开的,然后MQ、mysql、nacos 约定好命名空间,就不会冲突的
也可以自己账号买1台长久使用,然后结尾部署的时候使用按量付费的形式就行购买多节点则省钱
PS:建议用新用户注册比如家人的(手机号、身份证、设备),购买成功后可以换绑手机号
- 环境问题说明
- 务必使用CentOS 7 以上版本,64位系统,不要在Windows系统操作!!!!推荐是CentOS 7.8
- 带宽可以选择固定带宽1M~5M都行,或者按量付费(带宽过小,后续Jenkins构建推送镜像比较慢)
- 避免使用本地用虚拟机(后续部署很多中间件+本地多个微服务开发会很卡,且容易采坑)
- 谁都不能保证每个人-硬件组成-系统版本-虚拟机软件版本都一样
- 出现问题,大家结合报错日志搜索博文解决
- 少数同学 -Win7、Win8、Win10、Mac、虚拟机等等,可能存在兼容问题
- 记得重置密码和配置网络安全组相关操作,我这边就不多说了
![](https://file.xdclass.net/note/2021/redis/img/image-20210405121007100.png#alt=image-20210405121007100)
- Docker安装
按照依赖
yum install -y yum-utils device-mapper-persistent-data lvm2
配置yum源(比较慢,不用)
yum-config-manager —add-repo https://download.docker.com/linux/centos/docker-ce.repo
配置yum源 使用国内的
yum-config-manager —add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
查看版本
yum list docker-ce —showduplicates | sort -r
1. 安装docker
yum -y install docker-ce-20.10.10-3.el7
2. 查看docker版本
docker -v
3. 启动docker
systemctl start docker
4. 查看docker 启动状态
systemctl status docker
检查安装结果。 docker info
启动使用Docker systemctl start docker #运行Docker守护进程 systemctl stop docker #停止Docker守护进程 systemctl restart docker #重启Docker守护进程
docker ps查看容器 docker stop 容器id
修改镜像仓库 vim /etc/docker/daemon.json
改为下面内容,然后重启docker
{ “debug”:true,”experimental”:true, “registry-mirrors”:[“https://pb5bklzr.mirror.aliyuncs.com","https://hub-mirror.c.163.com","https://docker.mirrors.ustc.edu.cn“] }
查看信息
docker info
注意:不使用1.13.1版本,该版本在jenkins使用docker命令时会说找不到配置文件!
<a name="45ca822b"></a>
#### 第2集 云服务器基础设施安装之Mysql8.0+Redis6.X安装
**简介:云服务器基础设施安装之Mysql8.0+Redis6.X安装**
- Mysql8.0安装
安装mysql8,让容器使用宿主机的时间,容器时间与宿主机时间同步
docker run \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORD=root \ -v /home/data/mysql/data:/var/lib/mysql:rw \ -v /etc/localtime:/etc/localtime:ro \ —name root \ —restart=always \ -d mysql:8.0
Mysql工具连接测试
连接数配置
show variables like ‘%max_connections%’; set GLOBAL max_connections=5000; set GLOBAL mysqlx_max_connections=5000;
- Redis6安装
docker run -itd —name my_redis1 -p 6379:6379 -v /mydata/redis/data:/data redis:6.2.4 —requirepass root
进入容器的redis docker exec -it 容器id redis-cli
工具测试连接
<a name="83e46b0e"></a>
#### 第3集 云服务器基础设施安装之Nacos2.x+Mysql8配置持久化-避坑
**简介:云服务器基础设施安装之Nacos2.x+Mysql8配置持久化-避坑**
-
Nacos持久化SQL数据脚本
- 第5章第3集资料里面
- 默认登录
- 账户nacos
- 密码 nacos
-
Nacos2.x安装(生产环境让运维人员配置网络,不暴露公网)
- 配置中心需要加认证信息才可以访问
开源版本的 Nacos server 配置中,不会对客户端鉴权,即任何能访问 Nacos server 的用户,都可以直接获取 Nacos 中存储的配置,假如一个黑客攻进了企业内网,就能获取所有的业务配置,这样肯定会有安全隐患。
需要先开启 Nacos server 的鉴权,在 Nacos server 上修改 application.properties 中的 nacos.core.auth.enabled 值为 true 即可
```
docker run -d \
-e NACOS_AUTH_ENABLE=true \
-e MODE=standalone \
-e JVM_XMS=128m \
-e JVM_XMX=128m \
-e JVM_XMN=128m \
-p 8848:8848 \
-e SPRING_DATASOURCE_PLATFORM=mysql \
-e MYSQL_SERVICE_HOST=192.168.200.204 \
-e MYSQL_SERVICE_PORT=3306 \
-e MYSQL_SERVICE_USER=root \
-e MYSQL_SERVICE_PASSWORD=root \
-e MYSQL_SERVICE_DB_NAME=nacos_config \
-e MYSQL_SERVICE_DB_PARAM='characterEncoding=utf8&connectTimeout=10000&socketTimeout=30000&autoReconnect=true&useSSL=false' \
--restart=always \
--privileged=true \
-v /home/data/nacos/logs:/home/nacos/logs \
--name nacos \
nacos/nacos-server:2.0.2
第4集 云服务器基础设施安装之RabbitMQ安装
简介:云服务器基础设施安装之RabbitMQ安装
- RabbitMQ安装
docker run -d --name xd_rabbit -e RABBITMQ_DEFAULT_USER=admin -e RABBITMQ_DEFAULT_PASS=password -p 15672:15672 -p 5672:5672 rabbitmq:3.8.15-management
#网络安全组记得开放端口
4369 erlang 发现口
5672 client 端通信口
15672 管理界面 ui 端口
25672 server 间内部通信口
访问管理界面
ip:15672
愿景:”让编程不再难学,让技术与生活更加有趣”
更多架构课程请访问 xdclass.net
第六章 短链平台项目创建+git代码管理+开发分层规范讲解
第1集 短链平台实战-Maven聚合工程创建微服务项目
简介:Maven聚合工程创建微服务项目实战
Maven聚合工程拆分
dcloud-common
- 公共依赖包
dcloud-app
- Flink+Kafka实时计算
dcloud-account
- 账号+流量包微服务
dcloud-data
- 数据可视化微服务
dcloud-gateway
- 业务网关
dcloud-link
- 短链微服务
dcloud-shop
- 流量包商品+支付微服务
创建项目(记得删除聚合工程src目录)
添加依赖
<properties>
<!--JDK版本,如果是jdk8则这里是 1.8-->
<java.version>11</java.version>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<spring.boot.version>2.5.5</spring.boot.version>
<spring.cloud.version>2020.0.4</spring.cloud.version>
<alibaba.cloud.version>2021.1</alibaba.cloud.version>
<mybatisplus.boot.starter.version>3.4.0</mybatisplus.boot.starter.version>
<lombok.version>1.18.16</lombok.version>
<commons.lang3.version>3.9</commons.lang3.version>
<commons.codec.version>1.15</commons.codec.version>
<xxl-job.version>2.3.0</xxl-job.version>
<aliyun.oss.version>3.10.2</aliyun.oss.version>
<captcha.version>1.1.0</captcha.version>
<docker.image.prefix>dcloud</docker.image.prefix>
<redission.version>3.10.1</redission.version>
<jwt.version>0.7.0</jwt.version>
<sharding-jdbc.version>4.1.1</sharding-jdbc.version>
<!--跳过单元测试-->
<skipTests>true</skipTests>
<junit.version>4.12</junit.version>
<druid.version>1.1.16</druid.version>
</properties>
<!--锁定版本-->
<dependencyManagement>
<dependencies>
<!--https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-dependencies/2.3.3.RELEASE-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring.boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-dependencies/Hoxton.SR8-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring.cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--https://mvnrepository.com/artifact/com.alibaba.cloud/spring-cloud-alibaba-dependencies/2.2.1.RELEASE-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${alibaba.cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--mybatis plus和springboot整合-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatisplus.boot.starter.version}</version>
</dependency>
<!--https://mvnrepository.com/artifact/org.projectlombok/lombok/1.18.16-->
<!--scope=provided,说明它只在编译阶段生效,不需要打入包中, Lombok在编译期将带Lombok注解的Java文件正确编译为完整的Class文件-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
<!--<scope>provided</scope>-->
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>${commons.lang3.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/commons-codec/commons-codec -->
<!--用于加密-->
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>${commons.codec.version}</version>
</dependency>
<!--验证码kaptcha依赖包-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>kaptcha-spring-boot-starter</artifactId>
<version>${captcha.version}</version>
</dependency>
<!--阿里云oss-->
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>${aliyun.oss.version}</version>
</dependency>
<!-- JWT相关 -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>${jwt.version}</version>
</dependency>
<!--分布式锁-->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>${redission.version}</version>
</dependency>
<!--https://mvnrepository.com/artifact/org.apache.shardingsphere/sharding-jdbc-spring-boot-starter-->
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-jdbc-spring-boot-starter</artifactId>
<version>${sharding-jdbc.version}</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.xuxueli/xxl-job-core -->
<dependency>
<groupId>com.xuxueli</groupId>
<artifactId>xxl-job-core</artifactId>
<version>${xxl-job.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<!-- 代码库 -->
<repositories>
<repository>
<id>maven-ali</id>
<url>http://maven.aliyun.com/nexus/content/groups/public//</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
<updatePolicy>always</updatePolicy>
<checksumPolicy>fail</checksumPolicy>
</snapshots>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>public</id>
<name>aliyun nexus</name>
<url>http://maven.aliyun.com/nexus/content/groups/public/</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
</pluginRepository>
</pluginRepositories>
<!--module不用添加打包版本信息-->
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.1</version>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring.boot.version}</version>
<configuration>
<fork>true</fork>
<addResources>true</addResources>
</configuration>
</plugin>
</plugins>
</build>
第2集 Gitee仓库介绍和项目纳入版本管理+ignore文件配置
简介:开源中国Gitee仓库-介绍和项目纳入版本管理+ignore文件配置置
Git介绍
- 是一个版本管理工具, 其作用就是可以让你更好的管理你的程序,比如你原来提交过的内容,以后虽然修改了,但是通过git这个工具,可以把你原来提交的内容重现出来,这样对于你后来才意识到的一些错误的更改,可以进行还原
基于git协议的代码仓库
- github 全球最大同性交友社区
- gitee 开源中国
- gitlab 开源的git仓库平台,阿里等大厂就是基于这个搭建
- codeup 阿里云上的免费git仓库
gitee仓库地址
大课项目必须用git吗?
- 如果是因为不会git,而不用则不行,因为这个是互联网公司必备的
- 可以看联系客服看小滴课堂 git+gitlab专题视频
本地安装文档
大课项目加入git管理(是公钥设置,不是秘钥设置)
本地生成公钥 ssh-keygen -t rsa -C “794666918@qq.com“
- 进入项目目录
配置gitignore文件
- 根目录创建文件 .gitignore
# Compiled class file
*.class
# Log file
*.log
# BlueJ files
*.ctxt
# Mobile Tools for Java (J2ME)
.mtj.tmp/
# Package Files #
*.jar
*.war
*.nar
*.ear
*.zip
*.tar.gz
*.rar
.DS_Store
.idea
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
第3集 阿里编码规范里面Manager分层介绍-和开发规范说明
简介:阿里编码规范里面Manager分层介绍-专用名词和POJO实体类约定
开发人员:张三、李四、王五
一定要避免单点故障
- 一个微服务起码两个人熟悉:一个是主程一个是技术leader ,推荐是团队里面两个开发人员
- N方库说明
一方库: 本工程内部子项目模块依赖的库(jar 包)。
二方库: 公司内部发布到中央仓库,可供公司内部其它应用依赖的库(jar包)。
三方库: 公司之外的开源库(jar 包)。
- POJO实体类
POJO(Plain Ordinary Java Object): 在手册中,POJO 专指只有 setter / getter / toString的简单类,包括DO/DTO/BO/VO等, 禁止命名成xxxPOJO
- 各个层级约束规范
A) Service/DAO层方法命名规约
1) 获取单个对象的方法用get做前缀。
2) 获取多个对象的方法用list做前缀,复数形式结尾如:listObjects。
3) 获取统计值的方法用count做前缀。
4) 插入的方法用save/insert做前缀。
5) 删除的方法用remove/delete做前缀。
6) 修改的方法用update做前缀。
B) 领域模型命名规约
1) 数据对象:xxxDO,xxx即为数据表名。
2) 一般数据传输对象:xxxDTO,xxx为业务领域相关的名称,项目里面也用VO。
3) 展示对象:xxxVO,也就是响应给前端的实体包装类。
4) 接收前端json对象请求的命名为 XXXRequest
Manager分层说明 通用业务处理层,它有如下特征
- 对第三方平台封装的层,预处理返回结果及转化异常信息
- 对Service层通用能力的下沉,如缓存方案、中间件通用处理;
- 与DAO层交互,对多个DAO的组合复用。
·
更多开发规范,可以参考阿里巴巴编码手册(资料里面有 第6章第3集资料里面)
- 有兴趣的同学也可以安装IDEA编码扫描插件,不过前期可以先不用开启,会影响电脑卡顿和强迫症
第4集【重要】dcloud-common通用模块配置使用实战和新版改动
简介:短链平台dcloud-common通用模块配置使用实战
- pom文件配置
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--项目中添加 spring-boot-starter-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--数据库连接-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--mybatis plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
<!--单元测试-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<!--redis客户端-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<exclusions>
<exclusion>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<!--用于加密-->
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
</dependency>
<!-- JWT相关 -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
</dependency>
<!--redisson分布式锁-->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
</dependency>
<!--Hoxton.M2版本之后不再使用Ribbon而是使用spring-cloud-loadbalancer,所以不引入spring-cloud-loadbalancer会报错,所以加入spring-cloud-loadbalancer依赖 并且在nacos中排除ribbon依赖,不然loadbalancer无效 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
<!--配置中心, 留坑,后续用的时候再讲-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!--Feign远程调用-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--限流依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<!--限流持久化到nacos-->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
<!--Springboot项目整合spring-kafka依赖包配置-->
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka</artifactId>
</dependency>
<!--引入AMQP-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<!--spring cache依赖包-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-jdbc-spring-boot-starter</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/com.xuxueli/xxl-job-core -->
<!--分布式调度-->
<dependency>
<groupId>com.xuxueli</groupId>
<artifactId>xxl-job-core</artifactId>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
</dependencies>
第5集 统一接口响应协议-响应工具类封装
简介:统一接口响应协议和响应工具类封装
统一业务状态码 BizCodeEnum开发
- 状态码定义约束,共6位数,前三位代表服务,后3位代表接口
- 比如 商品服务210
/**
* 小滴课堂,愿景:让技术不再难学
*
* @Description 状态码定义约束,共6位数,前三位代表服务,后3位代表接口
* 比如 商品服务210,购物车是220、用户服务230,403代表权限
*
**/
public enum BizCodeEnum {
/**
* 短链分组
*/
GROUP_REPEAT(23001,"分组名重复"),
GROUP_OPER_FAIL(23503,"分组名操作失败"),
GROUP_NOT_EXIST(23404,"分组不存在"),
/**
*验证码
*/
CODE_TO_ERROR(240001,"接收号码不合规"),
CODE_LIMITED(240002,"验证码发送过快"),
CODE_ERROR(240003,"验证码错误"),
CODE_CAPTCHA_ERROR(240101,"图形验证码错误"),
/**
* 账号
*/
ACCOUNT_REPEAT(250001,"账号已经存在"),
ACCOUNT_UNREGISTER(250002,"账号不存在"),
ACCOUNT_PWD_ERROR(250003,"账号或者密码错误"),
ACCOUNT_UNLOGIN(250004,"账号未登录"),
/**
* 短链
*/
SHORT_LINK_NOT_EXIST(260404,"短链不存在"),
/**
* 订单
*/
ORDER_CONFIRM_PRICE_FAIL(280002,"创建订单-验价失败"),
ORDER_CONFIRM_REPEAT(280008,"订单恶意-重复提交"),
ORDER_CONFIRM_TOKEN_EQUAL_FAIL(280009,"订单令牌缺少"),
ORDER_CONFIRM_NOT_EXIST(280010,"订单不存在"),
/**
* 支付
*/
PAY_ORDER_FAIL(300001,"创建支付订单失败"),
PAY_ORDER_CALLBACK_SIGN_FAIL(300002,"支付订单回调验证签失败"),
PAY_ORDER_CALLBACK_NOT_SUCCESS(300003,"支付宝回调更新订单失败"),
PAY_ORDER_NOT_EXIST(300005,"订单不存在"),
PAY_ORDER_STATE_ERROR(300006,"订单状态不正常"),
PAY_ORDER_PAY_TIMEOUT(300007,"订单支付超时"),
/**
* 流控操作
*/
CONTROL_FLOW(500101,"限流控制"),
CONTROL_DEGRADE(500201,"降级控制"),
CONTROL_AUTH(500301,"认证控制"),
/**
* 流量包操作
*/
TRAFFIC_FREE_NOT_EXIST(600101,"免费流量包不存在,联系客服"),
TRAFFIC_REDUCE_FAIL(600102,"流量不足,扣减失败"),
TRAFFIC_EXCEPTION(600103,"流量包数据异常,用户无流量包"),
/**
* 通用操作码
*/
OPS_REPEAT(110001,"重复操作"),
OPS_NETWORK_ADDRESS_ERROR(110002,"网络地址错误"),
/**
* 文件相关
*/
FILE_UPLOAD_USER_IMG_FAIL(700101,"用户头像文件上传失败");
@Getter
private String message;
@Getter
private int code;
private BizCodeEnum(int code, String message){
this.code = code;
this.message = message;
}
}
- 接口统一协议 JsonData工具类开发
@Data
@AllArgsConstructor
@NoArgsConstructor
public class JsonData {
/**
* 状态码 0 表示成功
*/
private Integer code;
/**
* 数据
*/
private Object data;
/**
* 描述
*/
private String msg;
/**
* 获取远程调用数据
* 注意事项:
* 支持多单词下划线专驼峰(序列化和反序列化)
*
*
* @param typeReference
* @param <T>
* @return
*/
public <T> T getData(TypeReference<T> typeReference){
return JSON.parseObject(JSON.toJSONString(data),typeReference);
}
/**
* 成功,不传入数据
* @return
*/
public static JsonData buildSuccess() {
return new JsonData(0, null, null);
}
/**
* 成功,传入数据
* @param data
* @return
*/
public static JsonData buildSuccess(Object data) {
return new JsonData(0, data, null);
}
/**
* 失败,传入描述信息
* @param msg
* @return
*/
public static JsonData buildError(String msg) {
return new JsonData(-1, null, msg);
}
/**
* 自定义状态码和错误信息
* @param code
* @param msg
* @return
*/
public static JsonData buildCodeAndMsg(int code, String msg) {
return new JsonData(code, null, msg);
}
/**
* 传入枚举,返回信息
* @param codeEnum
* @return
*/
public static JsonData buildResult(BizCodeEnum codeEnum){
return JsonData.buildCodeAndMsg(codeEnum.getCode(),codeEnum.getMessage());
}
}
第6集 微服务自定义全局异常+处理器handler开发
简介:自定义全局异常+处理器开发
- 自定义全局异常
/**
* 全局异常处理
*/
@Data
public class BizException extends RuntimeException {
private Integer code;
private String msg;
public BizException(Integer code, String message) {
super(message);
this.code = code;
this.msg = message;
}
public BizException(BizCodeEnum bizCodeEnum) {
super(bizCodeEnum.getMsg());
this.code = bizCodeEnum.getCode();
this.msg = bizCodeEnum.getMsg();
}
}
- 自定义异常处理器
@ControllerAdvice
@Slf4j
public class ExceptionHandle {
@ExceptionHandler(value = Exception.class)
@ResponseBody
public JsonData handle(Exception e) {
if (e instanceof BizException) {
BizException bizException = (BizException) e;
log.info("[业务异常]{}", e);
return JsonData.buildError(bizException.getMsg(), bizException.getCode());
} else {
log.info("[系统异常]{}", e);
return JsonData.buildError("全局异常,未知错误");
}
}
}
第7集 common通用工具和时间格式化工具类讲解
简介:common通用工具和时间格式化工具类讲解
- 时间格式化工具类封装
public class TimeUtil {
/**
* 默认日期格式
*/
private static final String DEFAULT_PATTERN = "yyyy-MM-dd HH:mm:ss";
/**
* 默认日期格式
*/
private static final DateTimeFormatter DEFAULT_DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern(DEFAULT_PATTERN);
private static final ZoneId DEFAULT_ZONE_ID = ZoneId.systemDefault();
/**
* LocalDateTime 转 字符串,指定日期格式
* @param time
* @param pattern
* @return
*/
public static String format(LocalDateTime localDateTime,String pattern){
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern);
String timeStr = formatter.format(localDateTime.atZone(DEFAULT_ZONE_ID));
return timeStr;
}
/**
* Date 转 字符串, 指定日期格式
* @param time
* @param pattern
* @return
*/
public static String format(Date time,String pattern){
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern);
String timeStr = formatter.format(time.toInstant().atZone(DEFAULT_ZONE_ID));
return timeStr;
}
/**
* Date 转 字符串,默认日期格式
* @param time
* @return
*/
public static String format(Date time){
String timeStr = DEFAULT_DATE_TIME_FORMATTER.format(time.toInstant().atZone(DEFAULT_ZONE_ID));
return timeStr;
}
/**
* timestamp 转 字符串,默认日期格式
*
* @param time
* @return
*/
public static String format(long timestamp) {
String timeStr = DEFAULT_DATE_TIME_FORMATTER.format(new Date(timestamp).toInstant().atZone(DEFAULT_ZONE_ID));
return timeStr;
}
/**
* 字符串 转 Date
*
* @param time
* @return
*/
public static Date strToDate(String time) {
LocalDateTime localDateTime = LocalDateTime.parse(time, DEFAULT_DATE_TIME_FORMATTER);
return Date.from(localDateTime.atZone(DEFAULT_ZONE_ID).toInstant());
}
/**
* 获取当天剩余的秒数,用于流量包过期配置
* @param currentDate
* @return
*/
public static Integer getRemainSecondsOneDay(Date currentDate) {
LocalDateTime midnight = LocalDateTime.ofInstant(currentDate.toInstant(),
ZoneId.systemDefault()).plusDays(1).withHour(0).withMinute(0)
.withSecond(0).withNano(0);
LocalDateTime currentDateTime = LocalDateTime.ofInstant(currentDate.toInstant(),
ZoneId.systemDefault());
long seconds = ChronoUnit.SECONDS.between(currentDateTime, midnight);
return (int) seconds;
}
}
- Json序列化工具类封装
public class JsonUtil {
private static final ObjectMapper mapper = new ObjectMapper();
static {
//设置可用单引号
mapper.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true);
//序列化的时候序列对象的所有属性
mapper.setSerializationInclusion(JsonInclude.Include.ALWAYS);
//反序列化的时候如果多了其他属性,不抛出异常
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
//如果是空对象的时候,不抛异常
mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
//取消时间的转化格式,默认是时间戳,可以取消,同时需要设置要表现的时间格式
mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
mapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
}
/**
* 对象转为Json字符串
* @param data
* @return
*/
public static String obj2Json(Object obj) {
String jsonStr = null;
try {
jsonStr = mapper.writeValueAsString(obj);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
return jsonStr;
}
/**
* json字符串转为对象
* @param str
* @param valueType
* @return
*/
public static <T> T json2Obj(String jsonStr, Class<T> beanType) {
T obj = null;
try {
obj = mapper.readValue(jsonStr, beanType);
} catch (Exception e){
e.printStackTrace();
}
return obj;
}
/**
* json数据转换成pojo对象list
* @param jsonData
* @param beanType
* @return
*/
public static <T> List<T> json2List(String jsonData, Class<T> beanType) {
JavaType javaType = mapper.getTypeFactory().constructParametricType(List.class, beanType);
try {
List<T> list = mapper.readValue(jsonData, javaType);
return list;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 对象转为byte数组
* @param data
* @return
*/
public static byte[] obj2Bytes(Object obj) {
byte[] byteArr = null;
try {
byteArr = mapper.writeValueAsBytes(obj);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
return byteArr;
}
/**
* byte数组转为对象
* @param byteArr
* @param valueType
* @return
*/
public static <T> T bytes2Obj(byte[] byteArr, Class<T> beanType) {
T obj = null;
try {
obj = mapper.readValue(byteArr, beanType);
} catch (Exception e) {
e.printStackTrace();
}
return obj;
}
}
- common工具大集合
@Slf4j
public class CommonUtil {
/**
* 获取ip
*
* @param request
* @return
*/
public static String getIpAddr(HttpServletRequest request) {
String ipAddress = null;
try {
ipAddress = request.getHeader("x-forwarded-for");
if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
ipAddress = request.getHeader("Proxy-Client-IP");
}
if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
ipAddress = request.getHeader("WL-Proxy-Client-IP");
}
if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
ipAddress = request.getRemoteAddr();
if (ipAddress.equals("127.0.0.1")) {
// 根据网卡取本机配置的IP
InetAddress inet = null;
try {
inet = InetAddress.getLocalHost();
} catch (UnknownHostException e) {
e.printStackTrace();
}
ipAddress = inet.getHostAddress();
}
}
// 对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割
if (ipAddress != null && ipAddress.length() > 15) {
// "***.***.***.***".length()
// = 15
if (ipAddress.indexOf(",") > 0) {
ipAddress = ipAddress.substring(0, ipAddress.indexOf(","));
}
}
} catch (Exception e) {
ipAddress = "";
}
return ipAddress;
}
/**
* 获取全部请求头
* @param request
* @return
*/
public static Map<String, String> getAllRequestHeader(HttpServletRequest request){
Enumeration<String> headerNames = request.getHeaderNames();
Map<String, String> map = new HashMap<>();
while (headerNames.hasMoreElements()) {
String key = (String)headerNames.nextElement();
//根据名称获取请求头的值
String value = request.getHeader(key);
map.put(key,value);
}
return map;
}
/**
* MD5加密
*
* @param data
* @return
*/
public static String MD5(String data) {
try {
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] array = md.digest(data.getBytes("UTF-8"));
StringBuilder sb = new StringBuilder();
for (byte item : array) {
sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3));
}
return sb.toString().toUpperCase();
} catch (Exception exception) {
}
return null;
}
/**
* 获取验证码随机数
*
* @param length
* @return
*/
public static String getRandomCode(int length) {
String sources = "0123456789";
Random random = new Random();
StringBuilder sb = new StringBuilder();
for (int j = 0; j < length; j++) {
sb.append(sources.charAt(random.nextInt(9)));
}
return sb.toString();
}
/**
* 获取当前时间戳
*
* @return
*/
public static long getCurrentTimestamp() {
return System.currentTimeMillis();
}
/**
* 生成uuid
*
* @return
*/
public static String generateUUID() {
return UUID.randomUUID().toString().replaceAll("-", "").substring(0, 32);
}
/**
* 获取随机长度的串
*
* @param length
* @return
*/
private static final String ALL_CHAR_NUM = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
public static String getStringNumRandom(int length) {
//生成随机数字和字母,
Random random = new Random();
StringBuilder saltString = new StringBuilder(length);
for (int i = 1; i <= length; ++i) {
saltString.append(ALL_CHAR_NUM.charAt(random.nextInt(ALL_CHAR_NUM.length())));
}
return saltString.toString();
}
/**
* 响应json数据给前端
*
* @param response
* @param obj
*/
public static void sendJsonMessage(HttpServletResponse response, Object obj) {
response.setContentType("application/json; charset=utf-8");
try (PrintWriter writer = response.getWriter()) {
writer.print(JsonUtil.obj2Json(obj));
response.flushBuffer();
} catch (IOException e) {
log.warn("响应json数据给前端异常:{}",e);
}
}
}
愿景:”让编程不再难学,让技术与生活更加有趣”
更多架构课程请访问 xdclass.net
第七章 商用短链平台实战-账号微服务+流量包设计
第1集 账号微服务和流量包数据库表+索引规范讲解《上》
简介:账号微服务和流量包数据库表+索引规范讲解
短链平台大课难度层级曲线图:由浅入深
索引规范
- 主键索引名为 pk_字段名; pk即 primary key;
- 唯一索引名为 uk_字段名;uk 即 unique key
- 普通索引名则为 idx_字段名;idx 即index 的简称
- account表
CREATE TABLE `account` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`account_no` bigint DEFAULT NULL,
`head_img` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '头像',
`phone` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '手机号',
`pwd` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '密码',
`secret` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '盐,用于个人敏感信息处理',
`mail` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '邮箱',
`username` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '用户名',
`auth` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '认证级别,DEFAULT,REALNAME,ENTERPRISE,访问次数不一样',
`gmt_create` datetime DEFAULT CURRENT_TIMESTAMP,
`gmt_modified` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_phone` (`phone`) USING BTREE,
UNIQUE KEY `uk_account` (`account_no`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
第2集 账号微服务和流量包数据库表+索引规范讲解《下》
简介:账号微服务和流量包数据库表+索引规范讲解
- 账号和流量包的关系:一对多
traffic流量包表
思考点
- 海量数据下每天免费次数怎么更新?
- 海量数据付费流量套餐包每天次数限制怎么更新?
- 高性能扣减流量包设计怎么做?
- 流量包数据更新处理-高并发下分布式事务怎么解决
CREATE TABLE `traffic` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`day_limit` int DEFAULT NULL COMMENT '每天限制多少条,短链',
`day_used` int DEFAULT NULL COMMENT '当天用了多少条,短链',
`total_limit` int DEFAULT NULL COMMENT '总次数,活码才用',
`account_no` bigint DEFAULT NULL COMMENT '账号',
`out_trade_no` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '订单号',
`level` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '产品层级:FIRST青铜、SECOND黄金、THIRD钻石',
`expired_date` date DEFAULT NULL COMMENT '过期日期',
`plugin_type` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '插件类型',
`product_id` bigint DEFAULT NULL COMMENT '商品主键',
`gmt_create` datetime DEFAULT CURRENT_TIMESTAMP,
`gmt_modified` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_trade_no` (`out_trade_no`,`account_no`) USING BTREE,
KEY `idx_account_no` (`account_no`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
- traffic_task 流量包任务表(先建-后续会讲用途)
CREATE TABLE `traffic_task` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`account_no` bigint DEFAULT NULL,
`traffic_id` bigint DEFAULT NULL,
`use_times` int DEFAULT NULL,
`lock_state` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '锁定状态锁定LOCK 完成FINISH-取消CANCEL',
`message_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '唯一标识',
`gmt_create` datetime DEFAULT CURRENT_TIMESTAMP,
`gmt_modified` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_msg_id` (`message_id`) USING BTREE,
KEY `idx_release` (`account_no`,`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
第3集 账号微服务-流量包业务模型概念补充
简介:账号微服务流量包业务模型概念补充
- 业务模型补充
第4集 项目引入Mybatis-plus-generator代码自动生成工具
简介:介绍Mybatis-plus-generator代码自动化生成工具
介绍
- 底层是模板引擎技术,可以自定义生成的java类模板
- 基础版mybatis-genarator
- 进阶版mybatis-plus-genarator
注意
- 使用起来和普通版的mybatis generator一样,但是这个纯代码,不用复杂xml配置
- 任何框架,不要使用过多的侵入或者框架定制化深的内容,防止后续改动耦合性高,成本大
添加依赖
- 统一Common项目添加,各个项目测试类里面配置
<!-- 代码自动生成依赖 begin -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.4.1</version>
</dependency>
<!-- velocity -->
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
<version>2.0</version>
</dependency>
<!-- 代码自动生成依赖 end-->
- 代码(标记TODO的记得修改)
package com.example.db;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.config.DataSourceConfig;
import com.baomidou.mybatisplus.generator.config.GlobalConfig;
import com.baomidou.mybatisplus.generator.config.PackageConfig;
import com.baomidou.mybatisplus.generator.config.StrategyConfig;
import com.baomidou.mybatisplus.generator.config.rules.DateType;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
/**
* @author yinxin
* @version 1.0
* @Description:
* @date 2022/6/210:32
*/
public class MyBatisPlusGenerator {
public static void main(String[] args) {
//1. 全局配置
GlobalConfig config = new GlobalConfig();
// 是否支持AR模式
config.setActiveRecord(true)
// 作者
.setAuthor("二当家小D")
// 生成路径,最好使用绝对路径,window路径是不一样的
//TODO TODO TODO TODO
.setOutputDir("D:\\Code\\项目实战\\dcloud-short-link\\dcloud-account\\src\\main\\java")
// 文件覆盖
.setFileOverride(true)
// 主键策略
.setIdType(IdType.AUTO)
.setDateType(DateType.ONLY_DATE)
// 设置生成的service接口的名字的首字母是否为I,默认Service是以I开头的
.setServiceName("%sService")
//实体类结尾名称
.setEntityName("%sDO")
//生成基本的resultMap
.setBaseResultMap(true)
//不使用AR模式
.setActiveRecord(false)
//生成基本的SQL片段
.setBaseColumnList(true);
//2. 数据源配置
DataSourceConfig dsConfig = new DataSourceConfig();
// 设置数据库类型
dsConfig.setDbType(DbType.MYSQL)
.setDriverName("com.mysql.cj.jdbc.Driver")
//TODO TODO TODO TODO
.setUrl("jdbc:mysql://192.168.200.204:3306/cloud_account?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai")
.setUsername("root")
.setPassword("root");
//3. 策略配置globalConfiguration中
StrategyConfig stConfig = new StrategyConfig();
//全局大写命名
stConfig.setCapitalMode(true)
// 数据库表映射到实体的命名策略
.setNaming(NamingStrategy.underline_to_camel)
//使用lombok
.setEntityLombokModel(true)
//使用restcontroller注解
.setRestControllerStyle(true)
// 生成的表, 支持多表一起生成,以数组形式填写
//TODO TODO TODO TODO
.setInclude("account","traffic","traffic_task");
//4. 包名策略配置
PackageConfig pkConfig = new PackageConfig();
pkConfig.setParent("com.example")
.setMapper("mapper")
.setService("service")
.setController("controller")
.setEntity("model")
.setXml("mapper");
//5. 整合配置
AutoGenerator ag = new AutoGenerator();
ag.setGlobalConfig(config)
.setDataSource(dsConfig)
.setStrategy(stConfig)
.setPackageInfo(pkConfig);
//6. 执行操作
ag.execute();
System.out.println("======= Done 相关代码生成完毕 ========");
}
}
导入生成好的代码
- model (为啥不放common项目,如果是确定每个服务都用到的依赖或者类才放到common项目)
- mapper 类接口拷贝
- resource/mapper文件夹 xml脚本拷贝
- controller
- service 不拷贝
- Mybatis plus配置控制台打印日志
#配置plus打印sql日志
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
第5集 账号微服务注册Nacos+配置文件增加
简介:账号微服务注册Nacos+配置文件增加
启动账号微服务
排除sharding-jdbc依赖
<exclusions>
<exclusion>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-jdbc-spring-boot-starter</artifactId>
</exclusion>
</exclusions>
增加main函数主类 ``` @MapperScan(“net.xdclass.mapper”) @EnableTransactionManagement @EnableFeignClients @EnableDiscoveryClient @SpringBootApplication public class AccountApplication {
public static void main(String[] args) { SpringApplication.run(AccountApplication.class, args); }
}
- 配置文件
cloud: nacos: discovery: server-addr: 120.79.150.146:8848 username: nacos password: nacos
datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://120.79.150.146:3306/dcloud_account?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai username: root password: xdclass.net168
-
多个微服务增加配置+代码生成配置映入
![](https://file.xdclass.net/note/2020/javaweb/%E5%9B%BE%E7%89%87/logo.png#alt=logo) **愿景:"让编程不再难学,让技术与生活更加有趣" **
**更多架构课程请访问 xdclass.net**
<a name="bd5a9492"></a>
### 第八章 账号微服务注册模块+短信验证码+阿里云OSS开发实战
<a name="91b73ba7"></a>
#### 第1集 账号微服务注册功能和流程介绍
**简介:介绍微服务注册功能和流程介绍**
-
功能需求
- 使用手机号注册,已经注册的手机号不能重复注册,密码不能使用简单的MD5加密
- 用户上传头像需要用文件存储
-
安全需求
- 高并发下账号唯一性 注册邮箱或者手机验证码不能被恶意调用
- 验证码+唯一索引
- 头像文件存储访问方便扩容和管理
- 阿里云OSS
- 针对每个功能,初级开发和高级开发的思路是不一样
- 产品经理提业务需求 安全需求就是自己的经验,不然最终背锅的还是自己
- 高并发处理
- 异步+池化思想
-
短链平台选择
- 使用短信验证码注册
![](img/image-20211109144101996.png#alt=image-20211109144101996)
- 头像存储使用阿里云OSS
<a name="684a30ed"></a>
#### 第2集 第三方短信验证码平台接入申请操作指引
**简介:账号微服务短信验证码接入申请操作指引**
-
短信验证码平台选择考虑点
- 各个类型短信价格
- 短信到达率、到达时间
- 短信内容变量灵活,方便支持多场景
- 支持多种推广内容的短信发放,例如业务推广、新产品宣讲、会员关怀等内容的短信
- 多维度数据统计-查看请求量、发送成功量、失败量、等
![](img/image-20211109145558667.png#alt=image-20211109145558667)
-
短信平台
- 阿里云:[https://www.aliyun.com/product/sms](https://www.aliyun.com/product/sms)
- 推荐
- 腾讯云:[https://cloud.tencent.com/product/sms](https://cloud.tencent.com/product/sms)
- 推荐
- 第三方厂商:[https://market.aliyun.com/products/57000002/cmapi00046920.html](https://market.aliyun.com/products/57000002/cmapi00046920.html)
- 提供测试模板、免审核、测试成本更低
-
选择申请接入
-
阿里云市场:[https://market.console.aliyun.com/imageconsole/index.htm](https://market.console.aliyun.com/imageconsole/index.htm)
-
参数
AppKey:204000913 AppSecret:UaIdIkE9gEdjeZRGYLpgOq5FYAAYBfbD 复制
AppCode:6999d4df3e7d48028470bbe517169a8d 复制
免费测试的模板ID: M72CB42894
<a name="fb63d971"></a>
#### 第3集 账号微服务短信验证码发送工具类封装实战
**简介:账号微服务短信验证码发送工具类封装实战**
- RestTemplate配置
@Bean
public RestTemplate restTemplate(ClientHttpRequestFactory factory) {
return new RestTemplate(factory);
}
@Bean
public ClientHttpRequestFactory simpleClientHttpRequestFactory() {
SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
factory.setReadTimeout(10000);
factory.setConnectTimeout(10000);
return factory;
}
- SmsConfig配置
—————sms短信配置———————
sms: app-code: 6999d4df3e7d48028470bbe517169a8d template-id: M72CB42894
@ConfigurationProperties(prefix = “sms”) @Configuration @Data public class SmsConfig {
private String templateId;
private String appCode;
}
- SmsComponent工具类封装
private void send(String to, String templateId, String value) {
String url = String.format(urlTemplate, to, templateId, value);
HttpHeaders headers = new HttpHeaders();
//最后在header中的格式(中间是英文空格)为Authorization:APPCODE 83359fd73fe94948385f570e3c139105
headers.set("Authorization", "APPCODE " + smsConfig.getAppCode());
HttpEntity<String> entity = new HttpEntity<>(headers);
ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.POST, entity, String.class);
log.info("url={},body={}", url, response.getBody());
if (response.getStatusCode() == HttpStatus.OK) {
log.info("发送短信成功,响应信息:{}", response.getBody());
} else {
log.error("发送短信失败,响应信息:{}", response.getBody());
}
}
<a name="d762bb19"></a>
#### 第4集 账号微服务短信验证码发送工具类单元测试
**简介:账号微服务短信验证码发送工具类单元测试**
- 单元测试
@RunWith(SpringRunner.class) @SpringBootTest(classes = AccountApplication.class) @Slf4j public class SmsTest {
@Autowired
private SmsComponent smsComponent;
@Test
public void testSendSms(){
smsComponent.sendCode("13113777337","M72CB42894","223344");
}
}
![](https://file.xdclass.net/note/2020/javaweb/%E5%9B%BE%E7%89%87/logo.png#alt=logo) **愿景:"让编程不再难学,让技术与生活更加有趣" **
**更多架构课程请访问 xdclass.net**
<a name="3d77e11d"></a>
### 第九章 架构核心技术-池化思想-异步结合 性能优化最佳实践《上》
<a name="a3e32de6"></a>
#### 第1集 接口压测和常用压力测试工具对比
**简介:目前用的常用测试工具对比**
-
LoadRunner
- 性能稳定,压测结果及细粒度大,可以自定义脚本进行压测,但是太过于重大,功能比较繁多
-
Apache AB(单接口压测最方便)
- 模拟多线程并发请求,ab命令对发出负载的计算机要求很低,既不会占用很多CPU,也不会占用太多的内存,但却会给目标服务器造成巨大的负载, 简单DDOS攻击等
-
Webbench
- webbench首先fork出多个子进程,每个子进程都循环做web访问测试。子进程把访问的结果通过pipe告诉父进程,父进程做最终的统计结果。
-
Jmeter (GUI )
- 开源免费,功能强大,在互联网公司普遍使用
- 压测不同的协议和应用
-
1. Web - HTTP, HTTPS (Java, NodeJS, PHP, ASP.NET, …)
-
2. SOAP / REST Webservices
-
3. FTP
-
4. Database via JDBC
-
5. LDAP 轻量目录访问协议
-
6. Message-oriented middleware (MOM) via JMS
-
7. Mail - SMTP(S), POP3(S) and IMAP(S)
-
8. TCP等等
- 使用场景及优点
- 1)功能测试
- 2)压力测试
- 3)分布式压力测试
- 4)纯java开发
- 5)上手容易,高性能
- 4)提供测试数据分析
- 5)各种报表数据图形展示
- 压测工具本地快速安装Jmeter5.x
- 需要安装JDK8 以上
- 建议安装JDK环境,虽然JRE也可以,但是压测https需要JDK里面的 keytool工具
- 快速下载 [https://jmeter.apache.org/download_jmeter.cgi](https://jmeter.apache.org/download_jmeter.cgi)
- 文档地址:[http://jmeter.apache.org/usermanual/get-started.html](http://jmeter.apache.org/usermanual/get-started.html)
-
目录
bin:核心可执行文件,包含配置
jmeter.bat: windows启动文件(window系统一定要配置显示文件拓展名)
jmeter: mac或者linux启动文件
jmeter-server:mac或者Liunx分布式压测使用的启动文件
jmeter-server.bat:window分布式压测使用的启动文件
jmeter.properties: 核心配置文件
extras:插件拓展的包
lib:核心的依赖包
-
Jmeter语言版本中英文切换
- 控制台修改 menu -> options -> choose language
-
配置文件修改
- bin目录 -> jmeter.properties
- 默认 #language=en
- 改为 language=zh_CN
<a name="53899add"></a>
#### 第2集 Jmeter5.X基础功能组件介绍+线程组和Sampler
**简介:讲解Jmeter里面GUI菜单栏主要组件**
-
添加->threads->线程组(控制总体并发)
线程数:虚拟用户数。一个虚拟用户占用一个进程或线程
准备时长(Ramp-Up Period(in seconds)):全部线程启动的时长,比如100个线程,20秒,则表示20秒内 100个线程都要启动完成,每秒启动5个线程
循环次数:每个线程发送的次数,假如值为5,100个线程,则会发送500次请求,可以勾选永远循环
-
线程组->添加-> Sampler(采样器) -> Http (一个线程组下面可以增加几个Sampler)
名称:采样器名称 注释:对这个采样器的描述 web服务器: 默认协议是http 默认端口是80 服务器名称或IP :请求的目标服务器名称或IP地址
路径:服务器URL
-
查看测试结果
线程组->添加->监听器->察看结果树 线程组->添加->监听器->聚合报告
-
常规压测流程
-
内网环境
-
非GUI下压测
-
停止其他无关资源进程
-
压测机和被压测机器隔离
<a name="518c0198"></a>
#### 第3集 调用第三方服务组件改造+Jmeter5.x性能压测实践
**简介:调用第三方服务组件改造+Jmeter5.x性能压测实践**
-
埋点http请求得出请求响应耗时【粗略统计,非线上大量数据测试得出】
-
增加代码NotifyController、NotifyService
- test方法测试
-
压测参数配置
- 200并发
- 2秒启动
- 循环500次
-
同步发送+resttemplate未池化
- 错误:Connection timed out
- 400到500 qps
<a name="20e53c2c"></a>
#### 第4集 高并发下异步请求解决方案- @Async注解应用实战
**简介:高并发下异步请求解决方案一- @Async组件应用实战**
-
问题
- 由于发送短信涉及到网络通信, 因此sendMessage方法可能会有一些延迟. 为了改善用户体验, 我们可以使用异步发送短信的方法
-
什么是异步任务
- 异步调用是相对于同步调用而言的,同步调用是指程序按预定顺序一步步执行,每一步必须等到上一步执行完后才能执行,异步调用则无需等待上一步程序执行完即可执行
- 多线程就是一种实现异步调用的方式
- MQ也是一种宏观上的异步
-
使用场景
- 适用于处理log、发送邮件、短信……等
- 涉及到网络IO调用等操作
-
使用方式
- 启动类里面使用@EnableAsync注解开启功能,自动扫描
- 定义异步任务类并使用@Component标记组件被容器扫描,异步方法加上[@Async ](/Async )
-
注意:@Async失效情况
-
注解@Async的方法不是public方法
-
注解@Async的返回值只能为void或者Future
-
注解@Async方法使用static修饰也会失效
-
spring无法扫描到异步类,没加注解[@Async ](/Async ) 或 @EnableAsync注解
-
调用方与被调方不能在同一个类
- Spring 在扫描bean的时候会扫描方法上是否包含@Async注解,动态地生成一个子类(即proxy代理类),当这个有注解的方法被调用的时候,实际上是由代理类来调用的,代理类在调用时增加异步作用
- 如果这个有注解的方法是被同一个类中的其他方法调用的,那么该方法的调用并没有通过代理类,而是直接通过原来的那个 bean,所以就失效了
- 所以调用方与被调方不能在同一个类,主要是使用了动态代理,同一个类的时候直接调用,不是通过生成的动态代理类调用
- 一般将要异步执行的方法单独抽取成一个类
-
类中需要使用@Autowired或@Resource等注解自动注入,不能自己手动new对象
-
在Async 方法上标注@Transactional是没用的,但在Async 方法调用的方法上标注[@Transactional ](/Transactional ) 是有效的
-
编码实践
//启动类增加 @EnableAsync
// @Override
@Async
public void testSend() {
// try { // TimeUnit.MILLISECONDS.sleep(2000); // } catch (InterruptedException e) { // e.printStackTrace(); // }
long beginTime = CommonUtil.getCurrentTimestamp();
ResponseEntity<String> forEntity = restTemplate.getForEntity("http://old.xdclass.net", String.class);
String body = forEntity.getBody();
long endTime = CommonUtil.getCurrentTimestamp();
log.info("耗时={},body={}",endTime-beginTime,body);
}
<a name="81ed5763"></a>
#### 第5集 异步调用-压测高QPS后的背后原因和问题拆解
**简介:异步调用-压测高QPS后的背后原因和问题拆解**
![](img/image-20211113171330726.png#alt=image-20211113171330726)
- 默认参数下压测结果
![](img/image-20211113173226437.png#alt=image-20211113173226437)
-
现象:压测后很快跑完全部内容,是因为都在线程池内部的阻塞队列里面
-
极容易出现OOM,或者消息丢失
-
默认8个核心线程数占用满了之后, 新的调用就会进入队列, 最大值是Integer.MAX_VALUE,表现为没有执行
- task-XXX 日志里面会出现递增
-
设置下idea启动进程的jvm参数: -Xms50M -Xmx50M
![](img/image-20211113110241867.png#alt=image-20211113110241867)
-
代码位置
- TaskExecutionProperties
- TaskExecutionAutoConfiguration
-
说明:
- 直接使用 [@Async ](/Async ) 注解没指定线程池的话,即未设置TaskExecutor时
- 默认使用Spring创建ThreadPoolTaskExecutor
- 核心线程数:8
- 最大线程数:Integer.MAX_VALUE ( 21亿多)
- 队列使用LinkedBlockingQueue
- 容量是:Integer.MAX_VALUE
- 空闲线程保留时间:60s
- 线程池拒绝策略:AbortPolicy
-
如何解决上面说的问题?
<a name="066a590f"></a>
#### 第6集 【底层原理】Async+ThreadPoolTaskExecutor自定义线程池进阶实战
**简介:高并发下异步请求 @Async+ThreadPoolTaskExecutor自定义线程池实战**
- 自定义线程池可以解决上述的问题
![](img/image-20211113171648859.png#alt=image-20211113171648859)
-
大家的疑惑 使用线程池的时候搞混淆ThreadPoolTaskExecutor和ThreadPoolExecutor
-
ThreadPoolExecutor,这个类是JDK中的线程池类,继承自Executor,里面有一个execute()方法,用来执行线程,线程池主要提供一个线程队列,队列中保存着所有等待状态的线程,避免了创建与销毁的额外开销
-
ThreadPoolTaskExecutor,是spring包下的,是Spring为我们提供的线程池类
- Spring异步线程池的接口类是TaskExecutor,本质还是java.util.concurrent.Executor
-
解决方式
- spring会先搜索TaskExecutor类型的bean或者名字为taskExecutor的Executor类型的bean,
- 所以我们最好来自定义一个线程池,加入Spring IOC容器里面,即可覆盖
-
自定义线程池
@Configuration @EnableAsync public class ThreadPoolTaskConfig {
@Bean("threadPoolTaskExecutor")
public ThreadPoolTaskExecutor threadPoolTaskExecutor(){
ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
//线程池创建的核心线程数,线程池维护线程的最少数量,即使没有任务需要执行,也会一直存活
//如果设置allowCoreThreadTimeout=true(默认false)时,核心线程会超时关闭
threadPoolTaskExecutor.setCorePoolSize(4);
//最大线程池数量,当线程数>=corePoolSize,且任务队列已满时。线程池会创建新线程来处理任务
//当线程数=maxPoolSize,且任务队列已满时,线程池会拒绝处理任务而抛出异常
threadPoolTaskExecutor.setMaxPoolSize(8);
//缓存队列(阻塞队列)当核心线程数达到最大时,新任务会放在队列中排队等待执行
threadPoolTaskExecutor.setQueueCapacity(124);
//当线程空闲时间达到keepAliveTime时,线程会退出,直到线程数量=corePoolSize
//允许线程空闲时间60秒,当maxPoolSize的线程在空闲时间到达的时候销毁
//如果allowCoreThreadTimeout=true,则会直到线程数量=0
threadPoolTaskExecutor.setKeepAliveSeconds(30);
//spring 提供的 ThreadPoolTaskExecutor 线程池,是有setThreadNamePrefix() 方法的。
//jdk 提供的ThreadPoolExecutor 线程池是没有 setThreadNamePrefix() 方法的
threadPoolTaskExecutor.setThreadNamePrefix("小滴课堂Spring自带Async前缀:");
threadPoolTaskExecutor.setWaitForTasksToCompleteOnShutdown(true);
// rejection-policy:当pool已经达到max size的时候,如何处理新任务
// CallerRunsPolicy():交由调用方线程运行,比如 main 线程;如果添加到线程池失败,那么主线程会自己去执行该任务,不会等待线程池中的线程去执行
//AbortPolicy():该策略是线程池的默认策略,如果线程池队列满了丢掉这个任务并且抛出RejectedExecutionException异常。 //DiscardPolicy():如果线程池队列满了,会直接丢掉这个任务并且不会有任何异常 //DiscardOldestPolicy():丢弃队列中最老的任务,队列满了,会将最早进入队列的任务删掉腾出空间,再尝试加入队列
threadPoolTaskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
threadPoolTaskExecutor.initialize();
return threadPoolTaskExecutor;
}
}
//使用实战, 启动类可以不加@EnableAsync,改上面加 @Async(“threadPoolTaskExecutor”)
- 总结【方便记忆】
- 先是CorePoolSize是否满足,然后是Queue阻塞队列是否满,最后才是MaxPoolSize是否满足
<a name="cc4e1083"></a>
#### 第7集 ThreadPoolTaskExecutor线程池的面试题你知道怎么回答不
**简介:ThreadPoolTaskExecutor线程池的面试题你知道怎么回答不**
-
请你说下 ThreadPoolTaskExecutor线程池 有哪几个重要参数,什么时候会创建线程
- 查看核心线程池是否已满,不满就创建一条线程执行任务,否则执行第二步。
- 查看阻塞队列是否已满,不满就将任务存储在阻塞队列中,否则执行第三步。
- 查看线程池是否已满,即是否达到最大线程池数,不满就创建一条线程执行任务,否则就按照策略处理无法执行的任务。
-
高并发下核心线程怎么设置?
-
分IO密集还是CPU密集
- CPU密集设置为跟核心数一样大小
- IO密集型设置为2倍CPU核心数
-
非固定,根据实际情况压测进行调整,俗称【调参程序员】【调参算法工程师】
<a name="dd20ff47"></a>
#### 第8集 实践出真知-线程池多参数调整-性能压测+现象对比分析
**简介:实践出真知-线程池多参数调整-现象报告对比分析**
-
异步发送 + resttemplate未池化
- 线程池参数
threadPoolTaskExecutor.setCorePoolSize(4); threadPoolTaskExecutor.setMaxPoolSize(16); threadPoolTaskExecutor.setQueueCapacity(32);
- qps少,等待队列小
-
异步发送+resttemplate未池化
- 线程池参数
threadPoolTaskExecutor.setCorePoolSize(32); threadPoolTaskExecutor.setMaxPoolSize(64); threadPoolTaskExecutor.setQueueCapacity(10000); //如果等待队列长度为10万,则qps瞬间很高8k+,可能oom
- qps,等待队列大(瞬间高)
-
问题
- 采用异步发送用户体验变好了,但是存在丢失的可能,阻塞队列存储内存中,如果队列长度过多则重启容易出现丢失数据情况
- 采用了异步发送了+阻塞队列存缓冲,刚开始瞬间QPS高,但是后续也降低很多
- 问题是在哪里?消费方角度,提高消费能力
![](img/image-20211113171455126.png#alt=image-20211113171455126)
![](https://file.xdclass.net/note/2020/javaweb/%E5%9B%BE%E7%89%87/logo.png#alt=logo) **愿景:"让编程不再难学,让技术与生活更加有趣" **
**更多架构课程请访问 xdclass.net**
<a name="739935b1"></a>
### 第十章 架构核心技术-池化思想-异步结合 性能优化最佳实践《下》
<a name="39662d8f"></a>
#### 第1集 RestTemplate里面的存在的问题你知道多少- Broken pipe错误
**简介: RestTemplate里面的存在的问题你知道多少**
-
还原代码(暂时不用异步)
- 异步-里面是用线程池-是池化思想的一种应用
-
同步发送+resttemplate未池化
-
压测结果 几百吞吐量
-
错误Caused by: java.io.IOException: Broken pipe
- 服务端向前端socket连接管道写返回数据时 链接(pipe)却断开了
- 从应用角度分析,这是因为客户端等待返回超时了,主动断开了与服务端链接
- 连接数设置太小,并发量增加后,造成大量请求排队等待
- 网络延迟,是否有丢包
- 内存是否足够多支持对应的并发量
![](img/image-20211115140530464.png#alt=image-20211115140530464)
-
问题分析
- resttemplate底层是怎样的?
- 基于之前的认知-池化思想,联想到是否使用了http连接池?
-
重新认识RestTemplate
- RestTemplate是Spring提供的用于访问Rest服务的客户端
- 底层通过使用java.net包下的实现创建HTTP 请求
- 通过使用ClientHttpRequestFactory指定不同的HTTP请求方式,主要提供了两种实现方式
- SimpleClientHttpRequestFactory(默认)
- 底层使用J2SE提供的方式,既java.net包提供的方式,创建底层的Http请求连接
- 主要createRequest 方法( 断点调试),每次都会创建一个新的连接,每次都创建连接会造成极大的资源浪费,而且若连接不能及时释放,会因为无法建立新的连接导致后面的请求阻塞
- HttpComponentsClientHttpRequestFactory
- 底层使用HttpClient访问远程的Http服务
-
问题解决
- 客户端每次请求都要和服务端建立新的连接,即三次握手将会非常耗时
- 通过http连接池可以减少连接建立与释放的时间,提升http请求的性能
- Spring的restTemplate是对httpclient进行了封装, 而httpclient是支持池化机制
- 拓展
- 对httpclient进行封装的有:Apache的Fluent、es的restHighLevelClient、spring的restTemplate等
<a name="11d9dbe0"></a>
#### 第2集 高性能RestTemplate连接池封装配置实战
**简介: 高性能RestTemplate封装配置实战**
- 配置RestTemplate连接池实战
@Bean public RestTemplate restTemplate(ClientHttpRequestFactory factory) { return new RestTemplate(factory); }
@Bean
public ClientHttpRequestFactory httpRequestFactory() {
return new HttpComponentsClientHttpRequestFactory(httpClient());
}
/**
* @return
*/
@Bean
public HttpClient httpClient() {
Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create()
.register("http", PlainConnectionSocketFactory.getSocketFactory())
.register("https", SSLConnectionSocketFactory.getSocketFactory())
.build();
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(registry);
//设置整个连接池最大连接数
connectionManager.setMaxTotal(500);
//MaxPerRoute路由是对maxTotal的细分,每个主机的并发,这里route指的是域名
connectionManager.setDefaultMaxPerRoute(200);
RequestConfig requestConfig = RequestConfig.custom()
//返回数据的超时时间
.setSocketTimeout(20000)
//连接上服务器的超时时间
.setConnectTimeout(10000)
//从连接池中获取连接的超时时间
.setConnectionRequestTimeout(1000)
.build();
return HttpClientBuilder.create()
.setDefaultRequestConfig(requestConfig)
.setConnectionManager(connectionManager)
.build();
}
<a name="3bc2feee"></a>
#### 第3集 【10倍+QPS提升】Jmeter5.x压测 优化后RestTemplate前后性能对比
**简介: 【10倍+提升】Jmeter5.x压测 优化后RestTemplate前后性能对比**
-
同步发送+resttemplate未池化
- 压测结果 几百 吞吐量
-
同步发送+resttemplate池化
- 压测结果
![](img/image-20211115163815778.png#alt=image-20211115163815778)
-
检查你自己公司的项目,是否存在对应的问题
![](img/image-20211115110712980.png#alt=image-20211115110712980)
- 这些都是面试跳槽里面-项目的亮点
- 发现问题
- 找出原因
- 解决问题
![](https://file.xdclass.net/note/2020/javaweb/%E5%9B%BE%E7%89%87/logo.png#alt=logo) **愿景:"让编程不再难学,让技术与生活更加有趣" **
**更多架构课程请访问 xdclass.net**
<a name="458c965d"></a>
### 第十一章 账号微服务-发送短信验证码-池化+异步结合最佳实践
<a name="3a5e785e"></a>
#### 第1集 调用第三方短信验证码组件性能优化实战
**简介:调用第三方短信验证码组件性能优化实战**
-
调整代码
-
采用异步调用方式
-
采用 resttemplate池化方式
-
对比第一个高并发项目大课
- 海量数据项目大课更加偏底层+性能优化
- 两个大课有部分接口是重复的,但是虽然是同个接口-大家可以压测对比下两个接口的性能
<a name="cf97a9ed"></a>
#### 第2集 说说小滴课堂之前的被薅羊毛的事情+防范解决方案
**简介: 说说小滴课堂前不久的被薅羊毛的事情+防范解决方案**
- 请大家先看一个自动化脚本技术视频
- 第2分35秒:[https://xdclass.net/#/dplayer?video_id=16&e_id=1000](https://xdclass.net/#/dplayer?video_id=16&e_id=1000)
- 延伸
- 抢票
- 稀缺的酒、鞋等
![](img/image-20211116141425798.png#alt=image-20211116141425798)
-
短信邮箱轰炸机
- 什么是短信-邮箱轰炸机:
手机短信轰炸机是批量、循环给手机无限发送各种网站的注册验证码短信的方法。
- 美好的初衷-发明的由来
最早发明是用来整治街头广告电话号泛滥的一种手段,采用“手机短信轰炸机”软件可无限发送垃圾短信到牛皮癣小广告的手机号码上,使对方的手机快速消耗电量,变成高频率振动棒,且无法正常使用。
“短信轰炸机”可严厉打击城市“牛皮癣”,还城市明净容颜。
- 灰色产业的目光-也就是部分不法分子利用
某次大型程序员相亲现场-老王得罪了小王, 小王不爽,就道听途说知道了”短信轰炸机“,1天50元,轰炸了5天还打折300元。
一天内接到来自全国各地数千个陌生电话短信的轰炸骚扰,导致个人通讯中断,被工作生活受到严重影响,连刚相亲到的女友没没法联系上了。
-
原理
很多人都用手机注册一些网站的验证了,比如手机验证码。先填手机号,然后发一条验证码过去,输入验证码,完成验证,注册成功。
寻找大量肉鸡网站,寻找发送验证码的请求接口
如果找不到接口,也可以使用自动化UI工具触发
编写程序和调度任务,相关脚本录入数据库
输入目标手机号或者邮箱,触发攻击 ```
公司带来的损失
- 短信一条5分钱,如果被大量盗刷大家自己计算
- 邮箱通知不用钱,但被大量盗刷,带宽、连接等都被占用,导致无法正常使用
如何避免自己的网站成为”肉鸡“或者被刷呢
- 增加图形验证码(开发人员)
- 单IP请求次数限制(开发人员)
- 限制号码发送(一般短信提供商会做)
是否可以一劳永逸???
没有百分百的安全,验证码是可以破解的,ip也是可以租用代理ip的
攻防永远是有的,只过加大了攻击者的成本,ROI划不过来自然就放弃了
小滴课堂注册页面-短信验证码没加防范,被刷了,也不知道意图如何,反正就亏了几万块吧
第3集 图形验证码开发之谷歌kaptcha引入
简介:谷歌开源kaptcha图形验证码开发
Kaptcha 框架介绍 谷歌开源的一个可高度配置的实用验证码生成工具
- 验证码的字体/大小/颜色
- 验证码内容的范围(数字,字母,中文汉字!)
- 验证码图片的大小,边框,边框粗细,边框颜色
- 验证码的干扰线
- 验证码的样式(鱼眼样式、3D、普通模糊)
- 聚合工程依赖添加(使用国内baomidou二次封装的springboot整合starter)
<!--kaptcha依赖包-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>kaptcha-spring-boot-starter</artifactId>
<version>1.1.0</version>
</dependency>
- 账号微服务添加
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>kaptcha-spring-boot-starter</artifactId>
</dependency>
- 开发配置(任何框架和springboot整合基本都是)
@Configuration
public class CaptchaConfig {
/**
* 验证码配置
* Kaptcha配置类名
*
* @return
*/
@Bean
@Qualifier("captchaProducer")
public DefaultKaptcha kaptcha() {
DefaultKaptcha kaptcha = new DefaultKaptcha();
Properties properties = new Properties();
// properties.setProperty(Constants.KAPTCHA_BORDER, "yes");
// properties.setProperty(Constants.KAPTCHA_BORDER_COLOR, "220,220,220");
// //properties.setProperty(Constants.KAPTCHA_TEXTPRODUCER_FONT_COLOR, "38,29,12");
// properties.setProperty(Constants.KAPTCHA_IMAGE_WIDTH, "147");
// properties.setProperty(Constants.KAPTCHA_IMAGE_HEIGHT, "34");
// properties.setProperty(Constants.KAPTCHA_TEXTPRODUCER_FONT_SIZE, "25");
// //properties.setProperty(Constants.KAPTCHA_SESSION_KEY, "code");
//验证码个数
properties.setProperty(Constants.KAPTCHA_TEXTPRODUCER_CHAR_LENGTH, "4");
// properties.setProperty(Constants.KAPTCHA_TEXTPRODUCER_FONT_NAMES, "Courier");
//字体间隔
properties.setProperty(Constants.KAPTCHA_TEXTPRODUCER_CHAR_SPACE,"8");
//干扰线颜色
// properties.setProperty(Constants.KAPTCHA_NOISE_COLOR, "white");
//干扰实现类
properties.setProperty(Constants.KAPTCHA_NOISE_IMPL, "com.google.code.kaptcha.impl.NoNoise");
//图片样式
properties.setProperty(Constants.KAPTCHA_OBSCURIFICATOR_IMPL, "com.google.code.kaptcha.impl.WaterRipple");
//文字来源
properties.setProperty(Constants.KAPTCHA_TEXTPRODUCER_CHAR_STRING, "0123456789");
Config config = new Config(properties);
kaptcha.setConfig(config);
return kaptcha;
}
}
- 开发一个Controller使用测试
第4集 池化思想应用-Redis6.X配置连接池实战
简介:池化思想应用-Redis6.X配置连接池实战
连接池好处
- 使用连接池不用每次都走三次握手、每次都关闭Jedis
- 相对于直连,使用相对麻烦,在资源管理上需要很多参数来保证,规划不合理也会出现问题
- 如果pool已经分配了maxActive个jedis实例,则此时pool的状态就成exhausted了
连接池配置 common项目
<!--redis客户端-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<exclusions>
<exclusion>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
- 配置Redis连接
redis:
client-type: jedis
host: 120.79.150.146
password: xdclass.net
port: 6379
jedis:
pool:
# 连接池最大连接数(使用负值表示没有限制)
max-active: 100
# 连接池中的最大空闲连接
max-idle: 100
# 连接池中的最小空闲连接
min-idle: 100
# 连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: 60000
- 序列化配置
@Configuration
public class RedisTemplateConfiguration {
/**
* @param redisConnectionFactory
* @return
*/
@Bean
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
// 使用Jackson2JsonRedisSerialize 替换默认序列化
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
// 设置key和value的序列化规则
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
// 设置hashKey和hashValue的序列化规则
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
return redisTemplate;
}
}
第5集 账号微服务开发图形验证码加入缓存+Try-with-resource知识巩固
简介:账号微服务开发图形验证码接口+Try-with-resource知识巩固
redis做隔离, 多集群:核心集群和非核心集群,高并发集群和非高并发集群
- 资源隔离
- 数据保护
- 提高性能
key规范:业务划分,冒号隔离
- account-service:captcha:xxxx
- 长度不能过长
- 验证码接口开发
/**
*临时使用10分钟有效,方便测试
*/
private static final long CAPTCHA_CODE_EXPIRED = 60 * 1000 * 10;
/**
* 获取图形验证码
* @param request
* @param response
*/
@GetMapping("captcha")
public void getCaptcha(HttpServletRequest request, HttpServletResponse response) {
String captchaText = captchaProducer.createText();
log.info("图形验证码:{}", captchaText);
//存储
redisTemplate.opsForValue().set(getCaptchaKey(request),
captchaText, CAPTCHA_CODE_EXPIRED, TimeUnit.MILLISECONDS);
BufferedImage bufferedImage = captchaProducer.createImage(captchaText);
try (ServletOutputStream outputStream = response.getOutputStream()){
ImageIO.write(bufferedImage, "jpg", outputStream);
outputStream.flush();
} catch (IOException e) {
log.error("获取图形验证码异常:{}", e);
}
}
什么是try-with-resources
- 资源的关闭很多⼈停留在旧的流程上,jdk7新特性就有, 但是很多⼈以为是jdk8的
- 在try( …)⾥声 明的资源,会在try-catch代码块结束后⾃动关闭掉
注意点
- 实现了AutoCloseable接⼝的类,在try()⾥声明该类实例的时候,try结束后⾃动调⽤的 close⽅法,这个动作会早于finally⾥调⽤的⽅法
- 不管是否出现异常,try()⾥的实例都会被调⽤close⽅法
- try⾥⾯可以声明多个⾃动关闭的对象,越早声明的对象,会越晚被close掉
第6集 账号微服务之注册短信验证码接口开发
简介:注册短信验证码接口开发
- 接口开发
- service层
@Override
public JsonData sendCode(SendCodeEnum sendCodeType, String to) {
if(CheckUtil.isEmail(to)){
//邮箱验证码
}else if(CheckUtil.isPhone(to)){
//短信验证码
}
return JsonData.buildResult(BizCodeEnum.CODE_TO_ERROR);
}
- 邮箱工具类正则
public class CheckUtil {
/**
* 邮箱正则
*/
private static final Pattern MAIL_PATTERN = Pattern.compile("^([a-z0-9A-Z]+[-|\\.]?)+[a-z0-9A-Z]@([a-z0-9A-Z]+(-[a-z0-9A-Z]+)?\\.)+[a-zA-Z]{2,}$");
/**
* 手机号正则
*/
private static final Pattern PHONE_PATTERN = Pattern.compile("^((13[0-9])|(14[0-9])|(15[0-9])|(17[0-9])|(18[0-9]))\\d{8}$");
/**
* @param email
* @return
*/
public static boolean isEmail(String email) {
if (null == email || "".equals(email)) {
return false;
}
Matcher m = MAIL_PATTERN.matcher(email);
return m.matches();
}
/**
*
* @param phone
* @return
*/
public static boolean isPhone(String phone) {
if (null == phone || "".equals(phone)) {
return false;
}
Matcher m = PHONE_PATTERN.matcher(phone);
boolean result = m.matches();
return result;
}
}
第7集 关于注册短信验证码防刷设计方案你能想到几个
简介:注册短信验证码防刷方案你能想到几个
需求:一定时间内禁止重复发送短信,大家想下有哪几种实现方式
两个时间要求
- 60秒后才可以重新发送短信验证码
- 发送的短信验证码10分钟内有效
方式一:前端增加校验倒计时,不到60秒按钮不给点击
- 简单
- 不安全,存在绕过的情况
方式二:增加Redis存储,发送的时候设置下额外的key,并且60秒后过期
非原子操作,存在不一致性
增加的额外的key - value存储,浪费空间
/**
* 前置:判断是否重复发送
*
* 1、存储验证码到缓存
*
* 2、发送短信验证码
*
* 后置:存储发送记录
**/
方式三:基于原先的key拼装时间戳
- 好处:满足了当前节点内的原子性,也满足业务需求
第8集 【重要】注册邮箱验证码防刷代码落地+整体测试
简介:注册邮箱验证码防刷落地和整体测试
@Override
public JsonData sendCode(SendCodeEnum sendCodeEnum, String to) {
String cacheKey = String.format(RedisKey.CHECK_CODE_KEY,sendCodeEnum.name(),to);
String cacheValue = redisTemplate.opsForValue().get(cacheKey);
//如果不为空,再判断是否是60秒内重复发送 0122_232131321314132
if(StringUtils.isNotBlank(cacheValue)){
long ttl = Long.parseLong(cacheKey.split("_")[1]);
//当前时间戳-验证码发送的时间戳,如果小于60秒,则不给重复发送
long leftTime = CommonUtil.getCurrentTimestamp() - ttl;
if( leftTime < (1000*60)){
log.info("重复发送短信验证码,时间间隔:{}秒",leftTime);
return JsonData.buildResult(BizCodeEnum.CODE_LIMITED);
}
}
String code = CommonUtil.getRandomCode(6);
//生成拼接好验证码
String value = code+"_"+CommonUtil.getCurrentTimestamp();
redisTemplate.opsForValue().set(cacheKey,value,CODE_EXPIRED,TimeUnit.MILLISECONDS);
if(CheckUtil.isEmail(to)){
//发送邮箱验证码 TODO
}else if(CheckUtil.isPhone(to)){
//发送手机验证码
smsComponent.send(to,smsConfig.getTemplateId(),code);
}
return JsonData.buildSuccess();
}
- 整体测试和总结
愿景:”让编程不再难学,让技术与生活更加有趣”
更多架构课程请访问 xdclass.net
第十二章 账号微服务-阿里云OSS接入实战
第1集 分布式文件存储业界常见解决方案介绍
简介:分布式文件存储常见解决方案介绍
目前业界比较多这个解决方案,这边就挑选几个介绍下
- MinIO ``` 是在 Apache License v2.0 下发布的对象存储服务器,学习成本低,安装运维简单,主流语言的客户端整合都有, 号称最强的对象存储文件服务器,且可以和容器化技术docker/k8s等结合,社区活跃但不够成熟,业界参考资料较少
- FastDFS
一个开源的轻量级分布式文件系统,比较少的客户端可以整合,目前主要是C和java客户端,在一些互联网创业公司中有应用比较多,没有官方文档,社区不怎么活跃. 架构+部署结构复杂,出问题定位比较难定位,可以说是fastdfs零件的组装过程,需要去理解fastDFS的架构设计,才能够正确的安装部署
-
云厂商
-
阿里云OSS
-
七牛云
-
腾讯云
-
亚马逊云
-
CDN最强:Akamai [https://www.akamai.com/cn](https://www.akamai.com/cn)
-
选云厂商理由
- 优点:开发简单,功能强大,容易维护(不同网络下图片质量、水印、加密策略、扩容、加速)
- 缺点:要钱, 个性化处理,未来转移比较复杂,不排除有些厂商会提供一键迁移工具
-
选开源MinIO的理由
- 优点:功能强大、可以根据业务做二次的定制,新一代分布式文件存储系统,容器化结合强大,更重要的是免费(购买磁盘、内存、带宽)
- 缺点:自己需要有专门的团队进行维护、扩容等
-
文件上传流程
- web控制台
- 前端->后端程序->阿里云OSS
![](img/image-20211115114255911.png#alt=image-20211115114255911)
<a name="58477ac9"></a>
#### 第2集 阿里云OSS分布式对象存储介绍开通
**简介:阿里云OSS对象存储介绍和开通**
- 阿里云OSS介绍
对象存储OSS(Object Storage Service)是阿里云提供的海量、安全、低成本、高持久的云存储服务。其数据设计持久性不低于99.9999999999%(12个9),服务设计可用性不低于99.995%。
OSS具有与平台无关的RESTful API接口,您可以在任何应用、任何时间、任何地点存储和访问任意类型的数据。
提供标准、低频访问、归档和冷归档四种存储类型,全面覆盖从热到冷的各种数据存储场景:
| 标准存储类型 | 高持久、高可用、高性能的对象存储服务,支持频繁的数据访问。是各种社交、分享类的图片、音视频应用、大型网站、大数据分析的合适选择。 |
| --- | --- |
| 低频访问存储类型 | 适合长期保存不经常访问的数据(平均每月访问频率1到2次)。存储单价低于标准类型,适合各类移动应用、智能设备、企业数据的长期备份,支持实时数据访问。 |
| 归档存储类型 | 适合需要长期保存(建议半年以上)的归档数据,在存储周期内极少被访问,数据进入到可读取状态需要1分钟的解冻时间。适合需要长期保存的档案数据、医疗影像、科学资料、影视素材。 |
| 冷归档存储类型 | 适合需要超长时间存放的极冷数据。例如因合规要求需要长期留存的数据、大数据及人工智能领域长期积累的原始数据、影视行业长期留存的媒体资源、在线教育行业的归档视频等。 |
-
开通阿里云OSS
-
有阿里云账号、实名认证
-
OSS介绍:[https://www.aliyun.com/product/oss](https://www.aliyun.com/product/oss)
-
OSS控制台:[https://oss.console.aliyun.com/bucket](https://oss.console.aliyun.com/bucket)
-
学习路径:[https://help.aliyun.com/learn/learningpath/oss.html](https://help.aliyun.com/learn/learningpath/oss.html)
-
开通后的操作
- 创建Bucket
- 上传文件
- 访问文件
<a name="cf67ccb2"></a>
#### 第3集 权限知识 RBAC-ACL模式应用之阿里云RAM访问控制
**简介:权限知识 RBAC-ACL模式应用之阿里云RAM访问控制**
-
ACL: Access Control List 访问控制列表
- 以前盛行的一种权限设计,它的核心在于用户直接和权限挂钩
- 优点:简单易用,开发便捷
- 缺点:用户和权限直接挂钩,导致在授予时的复杂性,比较分散,不便于管理
- 例子:常见的文件系统权限设计, 直接给用户加权限
-
RBAC: Role Based Access Control
- 基于角色的访问控制系统。权限与角色相关联,用户通过成为适当角色的成员而得到这些角色的权限
- 优点:简化了用户与权限的管理,通过对用户进行分类,使得角色与权限关联起来
- 缺点:开发对比ACL相对复杂
- 例子:基于RBAC模型的权限验证框架与应用 Apache Shiro、spring Security
-
总结:不能过于复杂,规则过多,维护性和性能会下降, 更多分类 ABAC、PBAC等
-
RAM权限介绍
-
阿里云用于各个产品的权限,基于RBAC、ACL模型都有,进行简单管理账号、统一分配权限、集中管控资源,从而建立安全、完善的资源控制体系。
-
众多产品,一般采用子账号进行分配权限,防止越权攻击
<br />![](img/image-20211115114321621.png#alt=image-20211115114321621)
-
建立用户,勾选编程访问(保存accessKey和accessSecret,只出现一次)
- 用户登录名称 [dcloud_link@1701673122790560.onaliyun.com](mailto:dcloud_link@1701673122790560.onaliyun.com)<br />
AccessKey ID : LTAI5tHVGvYw7twoVFyruB1H<br />
AccessKey Secret : r4d0EudzSvPfVMb9Zp0TfmsE32RXmN
-
为新建用户授权OSS全部权限
<a name="39aa066b"></a>
#### 第4集 阿里云OSS客户端SDK集成和上传组件功能开发
**简介:阿里云OSS对象存储客户端集成和测试服务**
-
添加阿里云OSS的SDK
-
地址:[https://help.aliyun.com/document_detail/32008.html](https://help.aliyun.com/document_detail/32008.html)
-
添加maven依赖
- 底层聚合工程添加版本
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>3.10.2</version>
</dependency>
- 账号微服务添加
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
</dependency>
-
账号微服务配置OSS
阿里云OSS配置
aliyun: oss: endpoint: oss-cn-guangzhou.aliyuncs.com access-key-id: LTAI5tHVGvYw7twoVFyruB1H access-key-secret: r4d0EudzSvPfVMb9Zp0TfmsE32RXmN bucketname: dcloud-link
- 新建配置类 (配置里面的横杠会,自动转驼峰)
@ConfigurationProperties(prefix = “aliyun.oss”) @Configuration @Data public class OSSConfig {
private String endpoint;
private String accessKeyId;
private String accessKeySecret;
private String bucketname;
}
-
开发controller
-
开发service
@Service @Slf4j public class FileServiceImpl implements FileService {
@Autowired
private OSSConfig ossConfig;
@Override
public String uploadUserImg(MultipartFile file) {
//获取相关配置
String bucketname = ossConfig.getBucketname();
String endpoint = ossConfig.getEndpoint();
String accessKeyId = ossConfig.getAccessKeyId();
String accessKeySecret = ossConfig.getAccessKeySecret();
//创建OSS对象
OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
//获取原生文件名 xxx.jpg
String originalFileName = file.getOriginalFilename();
//JDK8的日期格式
LocalDateTime ldt = LocalDateTime.now();
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy/MM/dd");
//拼装路径,oss上存储的路径 user/2022/12/1/sdfdsafsdfdsf.jpg
String folder = dtf.format(ldt);
String fileName = CommonUtil.generateUUID();
String extension = originalFileName.substring(originalFileName.lastIndexOf("."));
// 在OSS上的bucket下创建 user 这个文件夹
String newFileName = "user/"+folder+"/"+fileName+ extension;
try {
PutObjectResult putObjectResult = ossClient.putObject(bucketname,newFileName,file.getInputStream());
//拼装返回路径
if(putObjectResult != null){
String imgUrl = "https://"+bucketname+"."+endpoint+"/"+newFileName;
return imgUrl;
}
} catch (IOException e) {
log.error("文件上传失败:{}",e);
} finally {
//oss关闭服务,不然会造成OOM
ossClient.shutdown();
}
return null;
}
}
<a name="42cda4f8"></a>
#### 第5集 账号微服务头像上传阿里云OSS接口和PostMan测试
**简介: 账号微服务头像上传阿里云OSS接口和PostMan测试**
-
文件上传流程
- 先上传文件,返回url地址,再和普通表单一并提交(推荐这种,更加灵活,失败率低)
- 文件和普通表单一并提交(设计流程比较多,容易超时和失败)
-
注意:默认SpringBoot最大文件上传是1M,大家测试的时候记得关注下
-
开发controller
- @requestPart注解 接收文件以及其他更为复杂的数据类型
/**
* 上传用户头像
*
* 默认文件大小 1M,超过会报错
*
* @param file
* @return
*/
@PostMapping(value = “upload”) public JsonData uploadHeaderImg(@RequestPart(“file”) MultipartFile file){
String result = fileService.uploadUserHeadImg(file);
return result != null?JsonData.buildSuccess(result):JsonData.buildResult(BizCodeEnum.FILE_UPLOAD_USER_IMG_FAIL);
}
![](https://file.xdclass.net/note/2020/javaweb/%E5%9B%BE%E7%89%87/logo.png#alt=logo) **愿景:"让编程不再难学,让技术与生活更加有趣" **
**更多架构课程请访问 xdclass.net**
<a name="e3363686"></a>
### 第十三章 账号微服务注册-登录功能开发完善
<a name="eb446409"></a>
#### 第1集 账号微服务注册功能业务介绍和代码编写
**简介:账号微服务注册接口介绍和业务代码编写**
-
微服务注册接口开发
- 请求实体类编写
- controller
- service
- 手机验证码验证
- 密码加密(TODO)
- 账号唯一性检查(TODO)
- 插入数据库
- 新注册用户福利发放(TODO)
- mapper
-
密码存储安全
- 彩虹表暴力破解
- 网站:[https://www.cmd5.com/](https://www.cmd5.com/)
- 密码存储常用方式
- 双重MD5
- MD5+加盐
- 双重MD5+加盐
<a name="14060ec2"></a>
#### 第2集 注册手机号唯一性保证方案和作业-分库分表下的思考
**简介:注册手机号唯一性安全保证方案和作业-分库分表下的思考**
-
注册业务
- 同个时刻注册,需要保证注册手机号在数据库里唯一
-
高并发下问题发现扩大
- 万分之一的时间,放大100万倍
- 不是你的代码安全,而是你的并发量过少,几个几十个并发量发现不了问题
- 几十万几百万并发 ,线下难模拟
- 代码暂停思维:假如非原子性代码运行到某一行暂停,其他线程重新操作是否会出问题
- 时间扩大思维:1纳秒的时间,扩大到1分钟,代码逻辑是否会有问题
- 类似幂等性处理
-
Redis:先看redis是否有,然后没的话则是新的注册
- key -value 存储, 配置60秒过期
- 非原子性操作,存在不一致
-
数据库唯一索引(建表的时间已经添加)
CREATE TABLE account
(
id
bigint unsigned NOT NULL AUTO_INCREMENT,
account_no
bigint DEFAULT NULL,
head_img
varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT ‘头像’,
phone
varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT ‘手机号’,
pwd
varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT ‘密码’,
secret
varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT ‘盐,用于个人敏感信息处理’,
mail
varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT ‘邮箱’,
username
varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT ‘用户名’,
auth
varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT ‘认证级别,DEFAULT,REALNAME,ENTERPRISE,访问次数不一样’,
gmt_create
datetime DEFAULT CURRENT_TIMESTAMP,
gmt_modified
datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (id
),
UNIQUE KEY uk_phone
(phone
) USING BTREE,
UNIQUE KEY uk_account
(account_no
) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
-
留个作业
-
分库分表下- 手机号唯一性保证怎么做?
-
知乎讨论:[https://www.zhihu.com/people/xdclass/asks](https://www.zhihu.com/people/xdclass/asks)
![](img/image-20211118145550286.png#alt=image-20211118145550286)
<a name="67fafe60"></a>
#### 第3集 账号微服务开发之登录模块逻辑和解密
**简介:账号微服务登录模块开发**
- 核心逻辑
- 通过phone找数据库记录
- 获取盐,和当前传递的密码就行加密后匹配
- 生成token令牌
- controller
/**
* 登录
* @param loginRequest
* @return
*/
@PostMapping("login")
public JsonData register(@RequestBody UserLoginRequest loginRequest){
JsonData jsonData = userService.login(loginRequest);
return jsonData;
}
- service
<a name="fc4dd2bf"></a>
#### 第4集 分布式应用下登录检验解决方案 JWT讲解
**简介:分布式应用的登录检验解决方案 JWT讲解 json web token**
-
什么是JWT
- JWT 是一个开放标准,它定义了一种用于简洁,自包含的用于通信双方之间以 JSON 对象的形式安全传递信息的方法。 可以使用 HMAC 算法或者是 RSA 的公钥密钥对进行签名
- **简单来说: 就是通过一定规范来生成token,然后可以通过解密算法逆向解密token,这样就可以获取用户信息**
{
id:888,
name:'小D',
expire:10000
}
funtion 加密(object, appsecret){
xxxx
return base64( token);
}
function 解密(token ,appsecret){
xxxx
//成功返回true,失败返回false
}
-
优点
- 生产的token可以包含基本信息,比如id、用户昵称、头像等信息,避免再次查库
- 存储在客户端,不占用服务端的内存资源
-
缺点
-
token是经过base64编码,所以可以解码,因此token加密前的对象不应该包含敏感信息,如用户权限,密码等
-
如果没有服务端存储,则不能做登录失效处理,除非服务端改秘钥
-
JWT格式组成 头部、负载、签名
- header+payload+signature
- 头部:主要是描述签名算法
- 负载:主要描述是加密对象的信息,如用户的id等,也可以加些规范里面的东西,如iss签发者,exp 过期时间,sub 面向的用户
- 签名:主要是把前面两部分进行加密,防止别人拿到token进行base解密后篡改token
-
关于jwt客户端存储
- 可以存储在cookie,localstorage和sessionStorage里面
<a name="b181b054"></a>
#### 第5集 登录校验Json Web Token实战之封装通用方法
**讲解:引入相关依赖并开发JWT工具类, 开发生产token和校验token的办法**
-
聚合工程加入版本依赖,common项目加入相关依赖
<!-- JWT相关 -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.7.0</version>
</dependency>
-
common项目中封装生产token方法
/**
* 根据用户信息,生成令牌
*
* @param user
* @return
*/
public static String geneJsonWebToken(LoginUser user) {
Long userId = user.getId();
String token = Jwts.builder().setSubject(SUBJECT)
.claim("head_img", user.getHeadImg())
.claim("id", userId)
.claim("name", user.getName())
.claim("phone", user.getPhone())
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + EXPIRE))
.signWith(SignatureAlgorithm.HS256, SECRET).compact();
token = TOKEN_PREFIX + token;
return token;
}
-
封装校验token方法
/**
* 校验token的方法
*
* @param token
* @return
*/
public static Claims checkJWT(String token) {
try {
final Claims claims = Jwts.parser().setSigningKey(SECRET)
.parseClaimsJws(token.replace(TOKEN_PREFIX, "")).getBody();
return claims;
} catch (Exception e) {
return null;
}
}
![](img/image-20211115191333987.png#alt=image-20211115191333987)
-
登录整合
//生成token令牌
LoginUser userDTO = new LoginUser();
BeanUtils.copyProperties(userDO, userDTO);
String token = JWTUtil.geneJsonWebToken(userDTO);
return JsonData.buildSuccess(token);
<a name="6008edef"></a>
#### 第6集 请求路径调整+账号微服务注册登录全流程测试
**简介: 账号微服务之注册登录全流程测试**
- 请求路径调整
- 注册流程验证
- 登录流程验证
<a name="bd08c79b"></a>
#### 第7集 账号微服务之通用登录拦截器开发和用户信息传递
**简介:账号微服务登录拦截器开发和用户信息传递**
-
开发登录拦截器
- 解密JWT
- 传递登录用户信息
- attribute传递
- threadLocal传递
-
SpringBoot拦截器代码开发
@Slf4j public class LoginInterceptor implements HandlerInterceptor {
public static ThreadLocal<LoginUser> threadLocal = new ThreadLocal<>();
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (HttpMethod.OPTIONS.toString().equalsIgnoreCase(request.getMethod())) {
response.setStatus(HttpStatus.NO_CONTENT.value());
return true;
}
String accessToken = request.getHeader("token");
if (StringUtils.isBlank(accessToken)) {
accessToken = request.getParameter("token");
}
if (StringUtils.isNotBlank(accessToken)) {
Claims claims = JWTUtil.checkJWT(accessToken);
if (claims == null) {
//未登录
CommonUtil.sendJsonMessage(response, JsonData.buildResult(BizCodeEnum.ACCOUNT_UNLOGIN));
return false;
}
Long accountNo = Long.parseLong(claims.get("account_no").toString());
String headImg = (String) claims.get("head_img");
String username = (String) claims.get("username");
String mail = (String) claims.get("mail");
String phone = (String) claims.get("phone");
String auth = (String) claims.get("auth");
LoginUser loginUser = LoginUser.builder()
.accountNo(accountNo)
.auth(auth)
.phone(phone)
.headImg(headImg)
.mail(mail)
.username(username)
.build();
//request.setAttribute("loginUser",loginUser);
//通过threadlocal
threadLocal.set(loginUser);
return true;
}
return false;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
threadLocal.remove();
}
}
<a name="2b8f6dc7"></a>
#### 第8集 登录拦截器InterceptorConfig拦截和放行路径开发配置
**简介:用户微服务登录拦截器路径配置和开发**
- 拦截器配置
- 拦截路径
- 不拦截路径
@Configuration @Slf4j public class InterceptorConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor())
//添加拦截的路径
.addPathPatterns("/api/account/*/**", "/api/traffic/*/**")
//排除不拦截
.excludePathPatterns(
"/api/account/*/register","/api/account/*/upload","/api/account/*/login",
"/api/notify/v1/captcha","/api/notify/*/send_code");
}
}
![](https://file.xdclass.net/note/2020/javaweb/%E5%9B%BE%E7%89%87/logo.png#alt=logo) **愿景:"让编程不再难学,让技术与生活更加有趣" **
**更多架构课程请访问 xdclass.net**
<a name="66bd3b13"></a>
### 第十四章 初恋的感觉-海量数据下的分库分表知识阶段一
<a name="8f46c2ee"></a>
#### 第1集 账号微服务里面的流量包业务模型梳理和需求讲解
**简介: 账号微服务里面的流量包业务模型梳理和需求讲解**
- 流量包业务模型梳理
![](img/image-20211109224700126.png#alt=image-20211109224700126)
![](img/image-20211129140435801.png#alt=image-20211129140435801)
-
数据量预估(尽量一次性扩容预估好,数据迁移成本大)
-
未来2年,短链平台累计5百万用户
- 一个用户10条记录/年,总量就是5千万条
- 单表不超过1千万数据,需要分5张表
- 进一步延伸,进行水平分表,比如 2张表、4张表、8张表、16张表
-
因为业务逻辑复杂,我们就不分那么多表,分2表操作即可
-
问题
- 分库分表怎么分?有哪些形式呢? 还会带来哪些问题?
- 比如 2张表、4张表、8张表、16张表 这样的思路?
- ...更多问题
<a name="0cf03acf"></a>
#### 第2集【面试题】业务增长-数据库性能优化思路讲解
**简介:【面试题】业务增长-数据库性能优化思路讲解**
-
面试官:这边有个数据库-单表1千万数据,未来1年还会增长多500万,性能比较慢,说下你的优化思路
<br />![](../../../shardingsphere/%E7%AC%94%E8%AE%B0-%E4%BB%A3%E7%A0%81-%E8%B5%84%E6%96%99/%E7%AC%94%E8%AE%B0/img/image-20211119180750448.png#alt=image-20211119180750448)
-
思路
- 千万不要一上来就说分库分表,这个是最忌讳的事项
- 一定要根据实际情况分析,两个角度思考
- 不分库分表
- 软优化
- 数据库参数调优
- 分析慢查询SQL语句,分析执行计划,进行sql改写和程序改写
- 优化数据库索引结构
- 优化数据表结构优化
- 引入NOSQL和程序架构调整
- 硬优化
- 提升系统硬件(更快的IO、更多的内存):带宽、CPU、硬盘
- 分库分表
- 根据业务情况而定,选择合适的分库分表策略(没有通用的策略)
- 外卖、物流、电商领域
- 先看只分表是否满足业务的需求和未来增长
- 数据库分表能够解决单表数据量很大的时,数据查询的效率问题,
- 无法给数据库的并发操作带来效率上的提高,分表的实质还是在一个数据库上进行的操作,受数据库IO性能的限制
- 如果单分表满足不了需求,再分库分表一起
-
结论
- 在数据量及访问压力不是特别大的情况,首先考虑缓存、读写分离、索引技术等方案
- 如果数据量极大,且业务持续增长快,再考虑分库分表方案
<a name="8759e6fb"></a>
#### 第3集 走进Mysql数据库分库分表后带来的优点和缺点《上》
**简介:走进Mysql数据库分库分表后带来的优点和缺点《上》**
-
分库分表解决的现状问题
-
解决数据库本身瓶颈
-
连接数: 连接数过多时,就会出现‘too many connections’的错误,访问量太大或者数据库设置的最大连接数太小的原因
- 大家学第一个大课,或者微服务的时候没物理分库,多数都出现上述问题,
-
Mysql默认的最大连接数为100.可以修改,而mysql服务允许的最大连接数为16384
-
数据库分表可以解决单表海量数据的查询性能问题
-
数据库分库可以解决单台数据库的并发访问压力问题
![](img/image-20211129150012681.png#alt=image-20211129150012681)
- 解决系统本身IO、CPU瓶颈
- 磁盘读写IO瓶颈,热点数据太多,尽管使用了数据库本身缓存,但是依旧有大量IO,导致sql执行速度慢
- 网络IO瓶颈,请求的数据太多,数据传输大,网络带宽不够,链路响应时间变长
- CPU瓶颈,尤其在基础数据量大单机复杂SQL计算,SQL语句执行占用CPU使用率高,也有扫描行数大、锁冲突、锁等待等原因
<a name="48efca7e"></a>
#### 第4集 走进Mysql数据库分库分表后带来的优点和缺点《下》
**简介:走进Mysql数据库分库分表后带来的优点和缺点《下》**
-
带来新的问题
-
问题一:跨节点数据库Join关联查询和多维度查询
- 数据库切分前,多表关联查询,可以通过sql join进行实现
- 分库分表后,数据可能分布在不同的节点上,sql join带来的问题就比较麻烦
![](img/image-20211129150012681.png#alt=image-20211129150012681)
- 不同维度查看数据,利用的partitionKey是不一样的
- 例如
- 订单表 的partionKey是user_id,用户查看自己的订单列表方便
- 但商家查看自己店铺的订单列表就麻烦,分布在不同数据节点
![](img/image-20211129145918819.png#alt=image-20211129145918819)
-
问题二:分库操作带来的分布式事务问题
- 操作内容同时分布在不同库中,不可避免会带来跨库事务问题,即分布式事务
-
问题三:执行的SQL排序、翻页、函数计算问题
- 分库后,数据分布再不同的节点上, 跨节点多库进行查询时,会出现limit分页、order by排序等问题
- 而且当排序字段非分片字段时,更加复杂了,要在不同的分片节点中将数据进行排序并返回,然后将不同分片返回的结果集进行汇总和再次排序(也会带来更多的CPU/IO资源损耗)
-
问题四:数据库全局主键重复问题
- 常规表的id是使用自增id进行实现,分库分表后,由于表中数据同时存在不同数据库中,如果用自增id,则会出现冲突问题
-
问题五:容量规划,分库分表后二次扩容问题
- 业务发展快,初次分库分表后,满足不了数据存储,导致需要多次扩容
-
问题六:分库分表技术选型问题
-
市场分库分表中间件相对较多,框架各有各的优势与短板,应该如何选择
- 更多问题。。。
<a name="67dc95a8"></a>
#### 第5集 海量数据处理之Mysql【垂直分表-垂直分库】讲解
**简介:海量数据处理之Mysql数据库垂直分表和分库讲解**
-
需求:商品表字段太多,每个字段访问频次不一样,浪费了IO资源,需要进行优化
-
垂直分表介绍
- 也就是“大表拆小表”,基于列字段进行的
- 拆分原则一般是表中的字段较多,将不常用的或者数据较大,长度较长的拆分到“扩展表 如text类型字段
- 访问频次低、字段大的商品描述信息单独存放在一张表中;
- 访问频次较高的商品基本信息单独放在一张表中
- 垂直拆分原则
- 把不常用的字段单独放在一张表;
- 把text,blob等大字段拆分出来放在附表中;
- 业务经常组合查询的列放在一张表中
- 例子:商品详情一般是拆分主表和附表
//拆分前
CREATE TABLE product
(
id
int(11) unsigned NOT NULL AUTO_INCREMENT,
title
varchar(524) DEFAULT NULL COMMENT ‘视频标题’,
cover_img
varchar(524) DEFAULT NULL COMMENT ‘封面图’,
price
int(11) DEFAULT NULL COMMENT ‘价格,分’,
total
int(10) DEFAULT ‘0’ COMMENT ‘总库存’,
left_num
int(10) DEFAULT ‘0’ COMMENT ‘剩余’,
learn_base
text COMMENT ‘课前须知,学习基础’,
learn_result
text COMMENT ‘达到水平’,
summary
varchar(1026) DEFAULT NULL COMMENT ‘概述’,
detail
text COMMENT ‘视频商品详情’,
PRIMARY KEY (id
)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
//拆分后
CREATE TABLE product
(
id
int(11) unsigned NOT NULL AUTO_INCREMENT,
title
varchar(524) DEFAULT NULL COMMENT ‘视频标题’,
cover_img
varchar(524) DEFAULT NULL COMMENT ‘封面图’,
price
int(11) DEFAULT NULL COMMENT ‘价格,分’,
total
int(10) DEFAULT ‘0’ COMMENT ‘总库存’,
left_num
int(10) DEFAULT ‘0’ COMMENT ‘剩余’,
PRIMARY KEY (id
)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
CREATE TABLE product_detail
(
id
int(11) unsigned NOT NULL AUTO_INCREMENT,
product_id
int(11) DEFAULT NULL COMMENT ‘产品主键’,
learn_base
text COMMENT ‘课前须知,学习基础’,
learn_result
text COMMENT ‘达到水平’,
summary
varchar(1026) DEFAULT NULL COMMENT ‘概述’,
detail
text COMMENT ‘视频商品详情’,
PRIMARY KEY (id
)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
-
需求:C端项目里面,单个数据库的CPU、内存长期处于90%+的利用率,数据库连接经常不够,需要进行优化
-
垂直分库讲解
-
垂直分库针对的是一个系统中的不同业务进行拆分, 数据库的连接资源比较宝贵且单机处理能力也有限
-
没拆分之前全部都是落到单一的库上的,单库处理能力成为瓶颈,还有磁盘空间,内存,tps等限制
-
拆分之后,避免不同库竞争同一个物理机的CPU、内存、网络IO、磁盘,所以在高并发场景下,垂直分库一定程度上能够突破IO、连接数及单机硬件资源的瓶颈
-
垂直分库可以更好解决业务层面的耦合,业务清晰,且方便管理和维护
-
一般从单体项目升级改造为微服务项目,就是垂直分库
<br />![](img/image-20211104151551376.png#alt=image-20211104151551376)
-
问题:垂直分库分表可以提高并发,但是依然没有解决单表数据量过大的问题
<a name="13c5621e"></a>
#### 第6集 海量数据处理之Mysql【水平分表-水平分库】讲解
**简介:海量数据处理之Mysql【水平分表-水平分库】讲解**
-
需求:当一张表的数据达到几千万时,查询一次所花的时间长,需要进行优化,缩短查询时间
-
都是大表拆小表
- 垂直分表:表结构拆分
- 水平分表:数据拆分
-
水平分表
-
把一个表的数据分到一个数据库的多张表中,每个表只有这个表的部分数据
-
核心是把一个大表,分割N个小表,每个表的结构是一样的,数据不一样,全部表的数据合起来就是全部数据
-
针对数据量巨大的单张表(比如订单表),按照某种规则(RANGE,HASH取模等),切分到多张表里面去
-
但是这些表还是在同一个库中,所以单数据库操作还是有IO瓶颈,主要是解决单表数据量过大的问题
-
减少锁表时间,没分表前,如果是DDL(create/alter/add等)语句,当需要添加一列的时候mysql会锁表,期间所有的读写操作只能等待
![](img/image-20211129152015514.png#alt=image-20211129152015514)
-
需求:高并发的项目中,水平分表后依旧在单个库上面,1个数据库资源瓶颈 CPU/内存/带宽等限制导致响应慢,需要进行优化
-
水平分库
- 把同个表的数据按照一定规则分到不同的数据库中,数据库在不同的服务器上
- 水平分库是把不同表拆到不同数据库中,它是对数据行的拆分,不影响表结构
- 每个库的结构都一样,但每个库的数据都不一样,没有交集,所有库的并集就是全量数据
- 水平分库的粒度,比水平分表更大
![](img/image-20211129152843944.png#alt=image-20211129152843944)
![](https://file.xdclass.net/note/2020/javaweb/%E5%9B%BE%E7%89%87/logo.png#alt=logo) **愿景:"让编程不再难学,让技术与生活更加有趣" **
**更多架构课程请访问 xdclass.net**
<a name="dcfdf669"></a>
### 第十五章 如漆似胶-海量数据下的分库分表策略讲解
<a name="090339c2"></a>
#### 第1集 Mysql数据库水平分库分表常见策略介绍-range
**简介: Mysql数据库水平分库分表常见策略介绍-range**
- 水平分库分表,根据什么规则进行?怎么划分?
![](img/image-20211129153058669.png#alt=image-20211129153058669)
-
方案一:自增id,根据ID范围进行分表(左闭右开)
-
规则案例
- 1~1,000,000 是 table_1
- 1,000,000 ~2,000,000 是 table_2
- 2,000,000~3,000,000 是 table_3
- ...更多
-
优点
- id是自增长,可以无限增长
- 扩容不用迁移数据,容易理解和维护
-
缺点
-
大部分读和写都访会问新的数据,有IO瓶颈,整体资源利用率低
-
数据倾斜严重,热点数据过于集中,部分节点有瓶颈
![](img/image-20211129153120812.png#alt=image-20211129153120812)
<a name="2dc6220f"></a>
#### 第2集 Mysql数据库水平分库分表策略介绍-Range延伸进阶
**简介: Mysql数据库水平分库分表常见策略介绍-range延伸**
- Range范围分库分表,有热点问题,所以这个没用?
- 关于怎么选择分库分表策略问题,如果业务适合就行,没有万能策略!!!!
- 基于方案一:自增id,根据ID范围进行分表延伸解决方案,你能想到多少种
![](img/image-20211129153258748.png#alt=image-20211129153258748)
- 范围角度思考问题 (范围的话更多是水平分表)
- 数字
- 自增id范围
- 时间
- 年、月、日范围
- 比如按照月份生成 库或表 pay_log_2022_01、pay_log_2022_02
- 空间
- 地理位置:省份、区域(华东、华北、华南)
- 比如按照 省份 生成 库或表
![](img/image-20211129153422200.png#alt=image-20211129153422200)
- 基于Range范围分库分表业务场景
- 微博发送记录、微信消息记录、日志记录,id增长/时间分区都行
- 水平分表为主,水平分库则容易造成资源的浪费
- 网站签到等活动流水数据时间分区最好
- 水平分表为主,水平分库则容易造成资源的浪费
- 大区划分(一二线城市和五六线城市活跃度不一样,如果能避免热点问题,即可选择)
- saas业务水平分库(华东、华南、华北等)
![](img/image-20211129153447553.png#alt=image-20211129153447553)
<a name="d30d1bd2"></a>
#### 第3集 Mysql数据库水平分库分表策略介绍-Hash取模
**简介: Mysql数据库水平分库分表常见策略介绍-Hash取模**
-
方案二:hash取模(Hash分库分表是最普遍的方案)
- 为啥不之间取模,如果取模的字段不是整数型要先hash,统一规则就行
![](img/image-20211129154215304.png#alt=image-20211129154215304)
-
案例规则
- 用户ID是整数型的,要分2库,每个库表数量4表,一共8张表
- 用户ID取模后,值是0到7的要平均分配到每张表
库ID = userId % 库数量 2 表ID = userId / 库数量 2 % 表数量4
- 例子
| userId | id % 2 (库-取余) | id /2 % 4 (表) |
| --- | --- | --- |
| 1 | 1 | 0 |
| 2 | 0 | 1 |
| 3 | 1 | 1 |
| 4 | 0 | 2 |
| 5 | 1 | 2 |
| 6 | 0 | 3 |
| 7 | 1 | 3 |
| 8 | 0 | 0 |
| 9 | 1 | 0 |
-
优点
- 保证数据较均匀的分散落在不同的库、表中,可以有效的避免热点数据集中问题,
-
缺点
- 扩容不是很方便,需要数据迁移
![](https://file.xdclass.net/note/2020/javaweb/%E5%9B%BE%E7%89%87/logo.png#alt=logo) **愿景:"让编程不再难学,让技术与生活更加有趣" **
**更多架构课程请访问 xdclass.net**
<a name="7f83eb22"></a>
### 第十六章 热恋的感觉-海量数据下的分库分表技术栈讲解
<a name="06606082"></a>
#### 第1集 大话业界常见数据库分库分表中间件介绍
**简介: 大话业界常见分库分表中间件介绍**
-
业界常见分库分表中间件
-
Cobar(已经被淘汰没使用了)
-
TDDL
- 淘宝根据自己的业务特点开发了 TDDL (Taobao Distributed Data Layer)
- 基于JDBC规范,没有server,以client-jar的形式存在,引入项目即可使用
- 开源功能比较少,阿里内部使用为主
-
Mycat
- 地址 [http://www.mycat.org.cn/](http://www.mycat.org.cn/)
- Java语言编写的MySQL数据库网络协议的开源中间件,前身 Cobar
- 遵守Mysql原生协议,跨语言,跨平台,跨数据库的通用中间件代理
- 是基于 Proxy,它复写了 MySQL 协议,将 Mycat Server 伪装成一个 MySQL 数据库
- 和ShardingShere下的Sharding-Proxy作用类似,需要单独部署
![](img/image-20211129154404065.png#alt=image-20211129154404065)
-
ShardingSphere 下的Sharding-JDBC
-
地址:[https://shardingsphere.apache.org/](https://shardingsphere.apache.org/)
-
Apache ShardingSphere 是一套开源的分布式数据库中间件解决方案组成的生态圈
- 它由 Sharding-JDBC、Sharding-Proxy 和 Sharding-Sidecar 3个独立产品组合
-
Sharding-JDBC
- 基于jdbc驱动,不用额外的proxy,支持任意实现 JDBC 规范的数据库
- 它使用客户端直连数据库,以 jar 包形式提供服务,无需额外部署和依赖
- 可理解为加强版的 JDBC 驱动,兼容 JDBC 和各类 ORM 框架
![](img/image-20211129154635373.png#alt=image-20211129154635373)
-
面试官最喜欢问的,是Mycat和ShardingJdbc区别
- 两者设计理念相同,主流程都是SQL解析-->SQL路由-->SQL改写-->结果归并
- sharding-jdbc
- 基于jdbc驱动,不用额外的proxy,在本地应用层重写Jdbc原生的方法,实现数据库分片形式
- 是基于 JDBC 接口的扩展,是以 jar 包的形式提供轻量级服务的,性能高
- 代码有侵入性
- Mycat
- 是基于 Proxy,它复写了 MySQL 协议,将 Mycat Server 伪装成一个 MySQL 数据库
- 客户端所有的jdbc请求都必须要先交给MyCat,再有MyCat转发到具体的真实服务器
- 缺点是效率偏低,中间包装了一层
- 代码无侵入性
<a name="5e853cea"></a>
#### 第2集 分库分表中间件Apache ShardingSphere急速认知
**简介: 分库分表中间件Apache ShardingSphere急速认知**
-
什么是ShardingSphere
- 已于2020年4月16日成为 Apache 软件基金会的顶级项目,懂的都懂
- 是一套开源的分布式数据库解决方案组成的生态圈,定位为 `Database Plus`
- 它由 JDBC、Proxy 和 Sidecar这 3 款既能够独立部署,又支持混合部署配合使用的产品组成
-
三大构成(下面图片素材来源 sharding-sphere官网)
-
ShardingSphere-Sidecar(规划中,简单知道就行)
- 定位为 Kubernetes 的云原生数据库代理,以 Sidecar(边车) 的形式代理所有对数据库的访问
- 通过无中心、零侵入的方案提供与数据库交互的啮合层,即 `Database Mesh`,又可称数据库网格
-
ShardingSphere-JDBC
-
它使用客户端直连数据库,以 jar 包形式提供服务
-
无需额外部署和依赖,可理解为增强版的 JDBC 驱动,完全兼容 JDBC 和各种 ORM 框架
-
适用于任何基于 JDBC 的 ORM 框架,如:JPA, Hibernate, Mybatis,或直接使用 JDBC
-
支持任何第三方的数据库连接池,如:DBCP, C3P0, BoneCP, HikariCP 等;
-
支持任意实现 JDBC 规范的数据库,目前支持 MySQL,PostgreSQL,Oracle,SQLServer 以及任何可使用 JDBC 访问的数据库
-
采用无中心化架构,与应用程序共享资源,适用于 Java 开发的高性能的轻量级 OLTP 应用
<br />![](img/image-20211129155322827.png#alt=image-20211129155322827)
-
ShardingSphere-Proxy
- 数据库代理端,提供封装了数据库二进制协议的服务端版本,用于完成对异构语言的支持
- 向应用程序完全透明,可直接当做 MySQL/PostgreSQL
- 它可以使用任何兼容 MySQL/PostgreSQL 协议的访问客户端(如:MySQL Command Client, MySQL Workbench, Navicat 等)操作数据
![](img/image-20211129155400045.png#alt=image-20211129155400045)
<a name="eda36e30"></a>
#### 第3集 分库分表和Sharding-Jdbc常见概念术语讲解
**简介: 分库分表和Sharding-Jdbc常见概念术语讲解**
- 站着统一水平线上,沟通无障碍,统一下专业术语
![](img/image-20211129155501993.png#alt=image-20211129155501993)
-
数据节点Node
- 数据分片的最小单元,由数据源名称和数据表组成
- 比如:ds_0.product_order_0
-
真实表
- 在分片的数据库中真实存在的物理表
- 比如订单表 product_order_0、product_order_1、product_order_2
-
逻辑表
- 水平拆分的数据库(表)的相同逻辑和数据结构表的总称
- 比如订单表 product_order_0、product_order_1、product_order_2,逻辑表就是product_order
-
绑定表
- 指分片规则一致的主表和子表
- 比如product_order表和product_order_item表,均按照order_id分片,则此两张表互为绑定表关系
- 绑定表之间的多表关联查询不会出现笛卡尔积关联,关联查询效率将大大提升
![](img/image-20211129155543476.png#alt=image-20211129155543476)
-
广播表
- 指所有的分片数据源中都存在的表,表结构和表中的数据在每个数据库中均完全一致
- 适用于数据量不大且需要与海量数据的表进行关联查询的场景
- 例如:字典表、配置表
<a name="d1ef0cbb"></a>
#### 第4集 分库分表和Sharding-Jdbc常见分片算法讲解
**简介: 分库分表和Sharding-Jdbc常见分片算法讲解**
-
数据库表分片(水平库、表)
- 包含分片键和分片策略
-
分片键 (PartitionKey)
- 用于分片的数据库字段,是将数据库(表)水平拆分的关键字段
- 比如prouduct_order订单表,根据订单号 out_trade_no做哈希取模,则out_trade_no是分片键
- 除了对单分片字段的支持,ShardingSphere也支持根据多个字段进行分片
![](img/image-20211129155728642.png#alt=image-20211129155728642)
-
分片策略(如果要看各个策略的实际操作,看ShardingSphere专题视频即可)
-
行表达式分片策略 InlineShardingStrategy
-
只支持【**单分片键**】使用Groovy的表达式,提供对SQL语句中的 =和IN 的分片操作支持
-
可以通过简单的配置使用,无需自定义分片算法,从而避免繁琐的Java代码开发
-
prouductorder$->{user_id % 8}表示订单表根据user_id模8,而分成8张表,表名称为
prouduct_order_0到
prouduct_order_7
-
标准分片策略StandardShardingStrategy
- 只支持【**单分片键**】,提供PreciseShardingAlgorithm和RangeShardingAlgorithm两个分片算法
- PreciseShardingAlgorithm 精准分片 是必选的,用于处理=和IN的分片
- RangeShardingAlgorithm 范围分配 是可选的,用于处理BETWEEN AND分片
- 如果不配置RangeShardingAlgorithm,如果SQL中用了BETWEEN AND语法,则将按照全库路由处理,性能下降
-
复合分片策略ComplexShardingStrategy
- 支持【**多分片键**】,多分片键之间的关系复杂,由开发者自己实现,提供最大的灵活度
- 提供对SQL语句中的=, IN和BETWEEN AND的分片操作支持
- prouduct_order_0_0、prouduct_order_0_1、prouduct_order_1_0、prouduct_order_1_1
-
Hint分片策略HintShardingStrategy
-
这种分片策略无需配置分片健,分片健值也不再从 SQL中解析,外部手动指定分片健或分片库,让 SQL在指定的分库、分表中执行
-
用于处理使用Hint行分片的场景,通过Hint而非SQL解析的方式分片的策略
-
Hint策略会绕过SQL解析的,对于这些比较复杂的需要分片的查询,Hint分片策略性能可能会更好
-
不分片策略 NoneShardingStrategy
- 不分片的策略。
-
自己实现分片策略的优缺点
-
优点:可以根据分片策略代码里面自己拼装 真实的数据库、真实的表,**灵活控制分片规则**
-
缺点:增加了编码,不规范的sql容易造成全库表扫描,部分sql语法支持不友好
![](https://file.xdclass.net/note/2020/javaweb/%E5%9B%BE%E7%89%87/logo.png#alt=logo) **愿景:"让编程不再难学,让技术与生活更加有趣" **
**更多架构课程请访问 xdclass.net**
<a name="9d1ecc3a"></a>
### 第十七章 流量包模块-海量数据下的分库分表《青铜玩法》
<a name="12f651a9"></a>
#### 第1集 账号微服务-流量包模块水平分表需求讲解和开发
**简介: 账号微服务-流量包模块水平分表需求讲解和开发**
-
需求
- 未来2年,短链平台累计5百万用户
- 付费流量包记录:一个用户10条/年,总量就是5千万条
- 单表不超过1千万数据,需要分5张表
- 进一步延伸,进行水平分表,比如 2张表、4张表、8张表、16张表
- 流量包traffic表数据太多,选取可用流量包 会影响性能,需要降低单表数据量,进行水平分表
- 分表数量:线上分8张表,本地分2张表即可
- 分片key: account_no,查询维度都是根据account_no进行查询
- 分片策略:行表达式分片策略 InlineShardingStrategy
-
新建表
- traffic_0
- traffic_1
CREATE TABLE traffic_0
(
id
bigint unsigned NOT NULL AUTO_INCREMENT,
day_limit
int DEFAULT NULL COMMENT ‘每天限制多少条,短链’,
day_used
int DEFAULT NULL COMMENT ‘当天用了多少条,短链’,
total_limit
int DEFAULT NULL COMMENT ‘总次数,活码才用’,
account_no
bigint DEFAULT NULL COMMENT ‘账号’,
out_trade_no
varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT ‘订单号’,
level
varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT ‘产品层级:FIRST青铜、SECOND黄金、THIRD钻石’,
expired_date
date DEFAULT NULL COMMENT ‘过期日期’,
plugin_type
varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT ‘插件类型’,
product_id
bigint DEFAULT NULL COMMENT ‘商品主键’,
gmt_create
datetime DEFAULT CURRENT_TIMESTAMP,
gmt_modified
datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (id
),
UNIQUE KEY uk_trade_no
(out_trade_no
,account_no
) USING BTREE,
KEY idx_account_no
(account_no
) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
-
配置
- 加入 sharding-jdbc依赖包,account项目注释下面的依赖排查
<exclusions>
<exclusion>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-jdbc-spring-boot-starter</artifactId>
</exclusion>
</exclusions>
- 配置文件 (注释之前jdbc单库配置)
数据源 ds0 第一个数据库
shardingsphere: datasource: ds0: connectionTimeoutMilliseconds: 30000 driver-class-name: com.mysql.cj.jdbc.Driver idleTimeoutMilliseconds: 60000 jdbc-url: jdbc:mysql://120.79.150.146:3306/dcloud_account?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true maintenanceIntervalMilliseconds: 30000 maxLifetimeMilliseconds: 1800000 maxPoolSize: 50 minPoolSize: 50 password: xdclass.net168 type: com.zaxxer.hikari.HikariDataSource username: root names: ds0 props:
# 打印执行的数据库以及语句
sql:
show: true
sharding:
tables:
traffic:
指定traffic表的数据分布情况,配置数据节点,行表达式标识符使用 ${…} 或 $->{…},但前者与 Spring 本身的文件占位符冲突,所以在 Spring 环境中建议使用 $->{…}
actual-data-nodes: ds0.traffic_$->{0..1}
<a name="51cf2683"></a>
#### 第2集 账号微服务-流量包模块水平分表策略配置和测试实战
**简介: 账号微服务-流量包模块水平分表策略配置和测试实战**
- 水平分表策略配置
水平分表策略+行表达式分片
table-strategy:
inline:
algorithm-expression: traffic_$->{account_no % 2}
sharding-column: account_no
- 单元测试
@Autowired private TrafficMapper trafficMapper;
@Test
public void testSaveTraffic(){
Random random = new Random();
for(int i=0;i<3;i++) {
TrafficDO trafficDO = new TrafficDO();
trafficDO.setAccountNo(Long.valueOf(random.nextInt(1000)));
trafficMapper.insert(trafficDO);
}
}
- 问题
- 主键id重复
<a name="efbec5a4"></a>
#### 第3集 分库分表暴露的问题-ID冲突和分布式id生成介绍
**简介: 分库分表暴露的问题-ID冲突和分布式id生成**
-
单库下一般使用Mysql自增ID, 但是分库分表后,会造成不同分片上的数据表主键会重复。
-
需求
- 性能强劲
- 全局唯一
- 防止恶意用户根据id的规则来获取数据
-
业界常用ID解决方案
-
数据库自增ID
- 利用自增id, 设置不同的自增步长,auto_increment_offset、auto-increment-increment
DB1: 单数 //从1开始、每次加2
DB2: 偶数 //从2开始,每次加2
- 缺点
- 依靠数据库系统的功能实现,但是未来扩容麻烦
- 主从切换时的不一致可能会导致重复发号
- 性能瓶颈存在单台sql上
-
UUID
- 性能非常高,没有网络消耗
- 缺点
- 无序的字符串,不具备趋势自增特性
- UUID太长,不易于存储,浪费存储空间,很多场景不适用
-
Redis发号器
- 利用Redis的INCR和INCRBY来实现,原子操作,线程安全,性能比Mysql强劲
- 缺点
- 需要占用网络资源,增加系统复杂度
-
Snowflake雪花算法
- twitter 开源的分布式 ID 生成算法,代码实现简单、不占用宽带、数据迁移不受影响
- 生成的 id 中包含有时间戳,所以生成的 id 按照时间递增
- 部署了多台服务器,需要保证系统时间一样,机器编号不一样
- 缺点
- 依赖系统时钟(多台服务器时间一定要一样)
<a name="15da1d71"></a>
#### 第4集 小D-带你彻底掌握分布式 ID 生成算法Snowflake原理
**简介: 小D-带你彻底掌握分布式 ID 生成算法Snowflake原理**
-
什么是雪花算法Snowflake
-
twitter用scala语言编写的高效生成唯一ID的算法
-
优点
- 生成的ID不重复
- 算法性能高
- 基于时间戳,基本保证有序递增
-
计算机的基础知识回顾
-
**bit与byte**
- bit(位):电脑中存储的最小单位,可以存储二进制中的0或1
- byte(字节):一个byte由8个bit组成
-
常规64位系统里面java数据类型存储字节大小
- int:4 个字节
- short:2 个字节
- long:8 个字节
- byte:1 个字节
- float:4 个字节
- double:8 个字节
- char:2 个字节
-
科普:数据类型在不同位数机器的平台下长度不同(怼面试官的严谨性)
- 16位平台 int 2个字节16位
- 32位平台 int 4个字节32位
- 64位平台 int 4个字节32位
-
雪花算法生成的数字,long类,所以就是8个byte,64bit
- 表示的值 -9223372036854775808(-2的63次方) ~ 9223372036854775807(2的63次方-1)
- 生成的唯一值用于数据库主键,不能是负数,所以值为0~9223372036854775807(2的63次方-1)
![](img/image-20211129171619650.png#alt=image-20211129171619650)
<a name="159912d1"></a>
#### 第5集 分布式ID生成器Snowflake里面的坑你是否知道
**简介: 分布式ID生成器Snowflake里面的坑你是否知道**
-
分布式ID生成器需求
- 性能强劲
- 全局唯一不能重复
- 防止恶意用户根据id的规则来获取数据
-
全局唯一不能重复-坑
-
坑一
- 分布式部署就需要分配不同的workId, 如果workId相同,可能会导致生成的id相同
-
坑二:
- 分布式情况下,需要保证各个系统时间一致,如果服务器的时钟回拨,就会导致生成的 id 重复
- 啥时候会有系统回拨????
- 小滴课堂-老王闲着,人工去生产环境做了系统时间调整,应该不会这么傻吧
- 业务需求,代码里面做了系统时间同步
-
配置实操
-
spring.shardingsphere.sharding.tables.traffic.key-generator.props.worker.id=1
-
方式一
-
订单id使用MybatisPlus的配置,TrafficDO类配置
@TableId(value = “id”, type = IdType.ASSIGN_ID) 默认实现类为DefaultIdentifierGenerator雪花算法
-
方式二
- 使用Sharding-Jdbc配置文件,注释DO类里面的id分配策略
id生成策略
key-generator:
column: id
props:
worker:
id: 0
#id生成策略
type: SNOWFLAKE
<a name="3a430453"></a>
#### 第6集 分布式ID生成器Snowflake自定义wrokId实战
**简介: 分布式ID生成器Snowflake自定义wrokId实战**
- 进阶:动态指定sharding jdbc 的雪花算法中的属性work.id属性
- 使用sharding-jdbc中的使用IP后几位来做workId, 但在某些情况下会出现生成重复ID的情况
- 解决办法时
- 在启动时给每个服务分配不同的workId, 引入redis/zk都行,缺点就是多了依赖
- 启动程序的时候,通过JVM参数去控制,覆盖变量
@Configuration public class SnowFlakeWordIdConfig {
/**
* 动态指定sharding jdbc 的雪花算法中的属性work.id属性
* 通过调用System.setProperty()的方式实现,可用容器的 id 或者机器标识位
* workId最大值 1L << 100,就是1024,即 0<= workId < 1024
* {@link SnowflakeShardingKeyGenerator#getWorkerId()}
*
*/
static {
try {
InetAddress ip4 = Inet4Address.getLocalHost();
String addressIp = ip4.getHostAddress();
System.setProperty("workerId", (Math.abs(addressIp.hashCode())%1024)+"");
} catch (UnknownHostException e) {
throw new BizException(BizCodeEnum.OPS_NETWORK_ADDRESS_ERROR);
}
}
}
![](img/image-20211130154422023.png#alt=image-20211130154422023)
- 配置
id生成策略
key-generator:
column: id
props:
worker:
id: ${workerId}
#id生成策略
type: SNOWFLAKE
<a name="5392ab58"></a>
#### 第7集 shardingjdbc-Snowflake时间回拨问题解决和封装ID生成器
**简介: shardingjdbc-Snowflake时间回拨问题解决和封装ID生成器**
- shardingjdbc-Snowflake里面解决时间回拨问题
![](img/image-20211130161746393.png#alt=image-20211130161746393)
- 需求
- 用户注册-生成的account_no需要是long类型,且全局唯一
- 利用Sharding-Jdbc封装id生成器
public class IDUtil {
private static SnowflakeShardingKeyGenerator shardingKeyGenerator = new SnowflakeShardingKeyGenerator();
/**
* 雪花算法生成器,配置workId,避免重复
*
* 10进制 654334919987691526
* 64位 0000100100010100101010100010010010010110000000000000000000000110
*
* {@link SnowFlakeWordIdConfig}
*
* @return
*/
public static Comparable<?> geneSnowFlakeID(){
return shardingKeyGenerator.generateKey();
}
}
- 修改注册时账号生成策略
![](https://file.xdclass.net/note/2020/javaweb/%E5%9B%BE%E7%89%87/logo.png#alt=logo) **愿景:"让编程不再难学,让技术与生活更加有趣" **
**更多架构课程请访问 xdclass.net**
<a name="bbbf02f4"></a>
### 第十八章 短链服务-业务需求和短链码解决方案讲解
<a name="c7c7034f"></a>
#### 第1集 短链服务介绍和应用场景讲解
**简介: 短链服务介绍和应用场景讲解**
- 什么是短链服务
![](img/image-20211202185010087.png#alt=image-20211202185010087)
-
业务背景:为啥需要短链
-
公司电商产品推广、业务活动页、广告落地页 缺少实时【数据反馈和渠道效果分析】
-
老项目业务推广【没人维护,无法做埋点】需要统计效果
- 例子 [https://tongji.baidu.com/web/demo/overview/index?siteId=16847648](https://tongji.baidu.com/web/demo/overview/index?siteId=16847648)
-
APP和营销活动发送营销短信链接过长,【浪费短信发送费用】
-
国内【反垄断后】微信、抖音、淘宝 流量互通,很多知识付费公司需要做 私域流量、社群运营
-
可以对外做产品输出,实现商业化能力增加公司营收
-
积累终端数据和人群数据,为公司未来产品人群做策略助力
-
更多。。。。
-
短链组成
- 协议://短链域名/短链码
-
最简单的方式
- 一个短链编码,去数据库select * from table where code =XXX,返回给用户就行
![](img/image-20211202192827901.png#alt=image-20211202192827901)
<a name="07f7adfa"></a>
#### 第2集 需求出发-带你详细一个短链的生命周期
**简介: 需求出发带你详细一个短链的生命周期**
- 创建者和访问者
![](img/image-20211202191815061.png#alt=image-20211202191815061)
-
创建者(UI图大体类似哈)
- 流量包管理
- 免费流量包
- 付费流量包
![](img/image-20211202193647041.png#alt=image-20211202193647041)
-
分组管理
- 新增分组
- 删除分组
- 修改分组
- 查看分组下的短链
![](img/image-20211202193729174.png#alt=image-20211202193729174)
-
短链管理
-
创建短链
- 目标地址
- 短链标题
- 短链域名
- 所属分组
- 有效期
![](img/image-20211202193906738.png#alt=image-20211202193906738)
-
删除短链
-
修改短链
-
查看短链
- 访问PV、UV
- 地域分布
- 时间分布
- 来源分布
- ...
-
访问者
- 访问短链
- 跳转目标站点
<a name="3e98e8a3"></a>
#### 第3集 短链服务生成短链URL的问题你能想到多少
**简介: 短链URL生成服务里面的问题你能想到多少**
- 短链码如何生成
![](img/image-20211202185010087.png#alt=image-20211202185010087)
-
需要解决的问题(自己是技术Leader,能否想到这些问题,并解决。做架构设计之前,要想清楚)
-
问题一:长链的关系和短链的关系
- 一对一?
- 一对多?
- 多对多?
-
问题二:前端访问短链是如何跳转到对应的页面的?
-
问题三:短链码是如何生成的
- 知道几种方式?
-
问题四:SaaS类型业务,数据量有多大,是否要分库分表
-
问题五:如果分库分表,PartitionKey是哪个?使用怎样的策略
-
问题六:如果分库分表,访问短链怎么知道具体是哪个库哪个表?
-
问题七:如果分库分表,怎么查看某个账号创建的全部短链?
<a name="de7c8772"></a>
#### 第4集 短链服务问题解决方案讲解-业务关系+跳转问题
**简介: 短链服务问题解决方案讲解-业务关系+跳转问题**
-
问题一:长链的关系和短链的关系是一对一还是一对多?
- 一个长链,在不同情况下,生成的短网址应该不一样,才不会造成冲突
- 多渠道推广下,也可以区分统计不同渠道的效果质量
- 所以是 一个短链接只能对应一个长链接,当然一个长链接可以对应多个短链接
-
问题二:前端访问短链是如何跳转到对应的页面的?
-
服务端转发
- 由服务器端进行的页面跳转,刚学Servlet时, 从OneServlet中转发到TwoServlet
- 地址栏不发生变化,显示的是上一个页面的地址
- 请求次数:只有1次请求
- 转发只能在同一个应用的组件之间进行,不可以转发给其他应用的地址
request.getRequestDispatcher(“/two”).forward(request, response);
![](img/image-20211203113753524.png#alt=image-20211203113753524)
-
页面的跳转-重定向
- 由浏览器端进行的页面跳转
![](img/image-20211203114328117.png#alt=image-20211203114328117)
- 重定向涉及到3xx状态码,访问跳转是301还是302,301和302代表啥意思?
- 301 是永久重定向
- 会被浏览器硬缓存,第一次会经过短链服务,后续再访问直接从浏览器缓存中获取目标地址
- 302 是临时重定向
- 不会被浏览器硬缓存,每次都是会访问短链服务
- 短地址一经生成就不会变化,所以用 301 是同时对服务器压力也会有一定减少
- 但是如果使用了 301,无法统计到短地址被点击的次数
- 所以选择302虽然会增加服务器压力,但是有很多数据可以获取进行分析
- 选择使用302,这个也可以对违规推广的链接进行实时封禁
<a name="b4fbbd4a"></a>
#### 第5集 短链服务问题解决方案讲解-短链码生成解决方案《上》
**简介: 短链服务问题解决方案讲解-短链码生成解决方案《上》**
- 问题三:短链码如何是如何生成的
- 短链码特点
- 生成性能强劲
- 碰撞概率低
- 避免重复
- 恶意猜测
- 业务规则安全
![](img/image-20211203145018603.png#alt=image-20211203145018603)
-
方式
-
自增ID
- 利用插入数据库,利用数据库自增id
- 把自增id转成62进制作为短链码
- 短链码的长度不固定,随着 id 变大,短链码长度也增长
- 可以指定从某个长度开始增长,到百亿、千亿数量
- 转换工具:[https://tool.lu/hexconvert/](https://tool.lu/hexconvert/)
- 是否存在重复: 不重复
- 但短链码是有序的递增,存在【业务数据安全】问题
-
MD5内容压缩
- 长链接做md5加密
43E08496,9E5CF455,E6D2D2B3,3407A6D2
- 加密串查询是否已经生成过短链接
- 如果已经存在,则拼接时间戳再MD5加密,插入数据库
- 如果不存在则把长链接、长链接加密串插入数据库
- 取MD5后 最后1 个 8 位字符串作为短链码
- 是否存在重复: 存在碰撞(重复)可能
- 是有损压缩算法,数据量超大情况碰撞概念越大
- 比如 【小滴课堂-老王的女友】有300多个,每再多1个,再同一天生日的概率越大,就更加复杂
<a name="3d4c7b71"></a>
#### 第6集 【重要】敏感数据+自增ID暴露的商业秘密
**简介: 敏感数据讲解+自增ID暴露的商业秘密**
-
什么是数据脱敏
-
也叫数据的去隐私化,在给定脱敏规则和策略的情况下,对敏感数据比如 `手机号`、`身份证` 等信息,进行转换或者修改的一种技术手段,防止敏感数据直接在不可靠的环境下使用和泄露、撞库等
-
技术分两类
- 静态数据脱敏
- 将生产数据导出,进行对外发送或者给开发、测试人员等
- 动态数据脱敏
- 程序直接连接生产数据的场景,如运维人员在运维的工作中直接连接生产数据库进行运维
- 客服人员在生产中通过后台查询的个人信息
-
数据库业务规则安全:自增ID暴露的商业秘密
-
背景
作为后端开发人员肯定离不开数据库设计,尽管你知道数据安全、接口安全、网络安全,但你也很大可能不小心暴露的公司的核心机密。
【做空上市公司的股票】如果你有炒股,那你知道如果这个数据库设计漏洞被他人知道,就可以做空一个上司公司的股票的不? 【摧毁对手的利器】如果一个公司在是靠业务数据来说话的,如果被他人知道,在核心的时间点,被披露出来,那融资可能凉凉,企业可能面临倒闭。
-
在某天一个竞争对手 老王 做竞品调研的时候,注册了你公司的应用
【暴露了总用户量】
通过业务接口抓包分析(假定数据没做加密或者加密被破解了),被他发现了自增id这个事情,他好几个号注
册了,编号都是一百多万,每次id都是自增1, 用户量最多100多万,就此暴雷了。
经过有经验的人知道不能对外暴露id这个事情,但是总有关联的业务,或者其他不知道的人员开发了对应
的功能,比如 订单表、记录表、收藏表或者其他和user_id有关联的,只要他操作业务,接口有返回user_id,就容易出问题了。
【暴露了每日拉新数据】 如果是用户自增id,老王 肯定不单停留这里这么简单,假如他想看这个产品每日新增的用户有多少。
毕竟拉新这个指标是很多公司都离不开的,比如通过投放广告、买量业务等等去获取新用户。
老王想看下这个公司每天新增用户量大不大,他就今天注册一个看id多少,明天再注册一个看id多少;
通过一段时间内去统计每天递增的值,老王就可以推断出平台新增用户大概是多少了,公司的拉新能力如何。
【暴露了平台商品数量-订单数量】
同样的思路,去爬取电商平台的商品,爬取平台最大的商品id, 第二天再次爬取,持续一段时间。就可以可以推断出新品发布数量。
同样的思路,去抓取订单接口,看最大id是多少, 第二天再次爬取,持续一段时间。就可以推断出这个公司的每天订单量有多大。
-
结语:
- 看到这里,你觉得你公司的自增id敢不敢乱用,是不是不但只是技术那么简单了。
- 合格的架构,不单止某个技术厉害,更要考虑和业务-商业上的结合。
- 正常的业务表,会用自增id,但是也会加个业务id,比如下面的
CREATE TABLE USER
(
id
int(11) unsigned NOT NULL AUTO_INCREMENT,
biz_id
varchar(64) DEFAULT NULL COMMENT ‘业务id’,
name
varchar(128) DEFAULT NULL COMMENT ‘昵称’,
pwd
varchar(124) DEFAULT NULL COMMENT ‘密码’,
head_img
varchar(524) DEFAULT NULL COMMENT ‘头像’,
phone
varchar(64) DEFAULT ‘’ COMMENT ‘手机号’,
login_type
int(10) DEFAULT NULL COMMENT ‘登录类型’,
email
varchar(128) DEFAULT NULL COMMENT ‘邮箱’,
sex
tinyint(2) DEFAULT ‘1’ COMMENT ‘0表示女,1表示男’,
create_time
datetime DEFAULT NULL COMMENT ‘创建时间’,
roles
varchar(11) DEFAULT NULL COMMENT ‘1,2,3,数字权限,逗号分隔’,
PRIMARY KEY (id
)
) ENGINE=MyISAM AUTO_INCREMENT=100000 DEFAULT CHARSET=utf8;
增加了 biz_id,这个就是业务id, 如果有关联,则用 biz_id进行关联并返回,这个可以是varchar类型,long雪花算法.
其实最靠谱的就是,不要把有业务规则的id暴露给用户,不止id字段,类似的敏感字段都是
<a name="fe455e6e"></a>
#### 第7集 短链服务问题解决方案讲解-短链码生成解决方案《下》
**简介: 短链URL服务问题解决方案讲解-短链码生成解决方案《下》**
-
哈希算法:将一个元素映射成另一个元素
-
加密哈希,如SHA256、MD5(上集讲了)
-
非加密哈希,如MurMurHash,CRC32
-
MurMurHash
Murmur哈希是一种非加密散列函数,适用于一般的基于散列的查找。 它在2008年由Austin Appleby创建,在Github上托管,名为“SMHasher” 的测试套件。 它也存在许多变种,所有这些变种都已经被公开。 该名称来自两个基本操作,乘法(MU)和旋转(R)—来自百科
-
是一种【非加密型】哈希函数且【随机分布】特征表现更良好
-
由于是非加密的哈希函数,性能会比MD5强
-
再很多地方都用到比如Guava、Jedis、HBase、Lucence等
-
存在两个版本
- MurmurHash2(产生32位或64位值)
- MurmurHash3(产生32位或128位值)
-
数据量
-
MurmurHash的 32 bit 能表示的最大值近 43 亿的10进制
- 满足多数业务,如果接近43亿则冲突概率大
-
产品目标【超理想情况】
首年日活用户: 10万
首年日新增短链数据:10万*50 = 500万
年新增短链数:500万 * 365天 = 18.2亿
年新增用户数:50万/1年
年营收目标: 10万付费用户 * 客单价200元 = 2千万
新增短链:50条/用户每日
-
MurMurHash得到的数值是10进制,一般会转化为62进制进行缩短
- 例子
- 10进制:1813342104
- 转62进制:1YIB7i
- [https://tool.lu/hexconvert/](https://tool.lu/hexconvert/)
-
常规短链码是6~8位数字+大小写字母组合
0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
6 位 62 进制数可表示 568 亿个短链(62的6次方,每位都有62个可能,如果扩大位数到7位,则可以支持3万5200亿)
- MurmurHash的 32 bit 满足多数业务 43亿
- 拼接上库-表位则可以表示更多数据(后续会讲分库分表的,库表位)
- 7位则可以到到 43亿 * 62 = 2666亿
- 8位则可以到到 2666亿 * 62 = 1.65万亿条数据
- 结合短链过期数据归档,理论上满足未来全部需求了
- 数据库存储
- 单表1千万 _ 62个库 _ 62表 = 384亿数据
![](https://file.xdclass.net/note/2020/javaweb/%E5%9B%BE%E7%89%87/logo.png#alt=logo) **愿景:"让编程不再难学,让技术与生活更加有趣" **
**更多架构课程请访问 xdclass.net**
<a name="57138a7b"></a>
### 第十九章 短链服务-Murmur哈希算法封装组件
<a name="40ae130d"></a>
#### 第1集 Guava框架里面的Murmur哈希算法测试
**简介: Guava框架里面的Murmur哈希算法测试**
- Guava框里里面自带Murmur算法
- 单元测试
@Test public void testMurmurHash() { for (int i = 0; i < 50; i++) { int num1 = random.nextInt(1000000); int num2 = random.nextInt(1000000); int num3 = random.nextInt(1000000); String originalUrl = num1 + “xdclass+” + num2 + “.net” + num3; long murmur3_32 = Hashing.murmur3_32().hashUnencodedChars(originalUrl).padToLong(); System.out.println(“murmur3_32=”+murmur3_32);
}
}
- CommonUtil工具类
/**
* murmur hash算法
* @param param
* @return
*/
public static long murmurHash32(String param){
long murmur32 = Hashing.murmur3_32().hashUnencodedChars(param).padToLong();
return murmur32;
}
<a name="d794471e"></a>
#### 第2集 短链生成组件ShortLinkComponent封装
**简介: 短链生成组件ShortLinkComponent封装**
- 创建短链组件类 ShortLinkComponent
/**
* 创建短链
* @param originalUrl
* @return db编码+6位短链编码
*/
public String createShortLinkCode(String originalUrl){
long murmur32 = CommonUtil.murmurHash32(originalUrl);
//转62进制
String shortLinkCode = encodeToBase62(murmur32);
return code;
}
- 10进制转62进制
private static final String CHARS = “0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ”;
/**
* 10进制转62进制
* @param num
* @return
*/
private static String encodeToBase62(long num) {
//StringBuffer:线程安全; StringBuilder:线程不安全
StringBuffer sb = new StringBuffer();
do {
int i = (int) (num % 62);
sb.append(CHARS.charAt(i));
num /= 62;
// num = num/ 62;
} while (num > 0);
String value = sb.reverse().toString();
return value;
}
<a name="1651a82f"></a>
#### 第3集 组件ShortLinkComponent测试和疑惑解答
**简介: 组件ShortLinkComponent测试和疑惑解答**
- 单元测试
/**
* 测试短链平台
*/
@Test
public void testCreateShortLink() {
Random random = new Random();
for (int i = 0; i < 100; i++) {
int num1 = random.nextInt(10);
int num2 = random.nextInt(1000000);
int num3 = random.nextInt(1000000);
String originalUrl = num1 + "xdclass" + num2 + ".net" + num3;
String shortLinkCode = shortLinkComponent.createShortLinkCode(originalUrl);
log.info("originalUrl:" + originalUrl + ", shortLinkCode=" + shortLinkCode);
}
}
-
为什么要用62进制转换,不是64进制?
- 62进制转换是因为62进制转换后只含数字+小写+大写字母
- 而64进制转换会含有/、+这样的符号(不符合正常URL的字符)
- 10进制转62进制可以缩短字符,如果我们要6位字符的话,已经有560亿个组合了
-
看业务情况有些短链也会加入其它特殊字符
- 短链固定6位?肯定不是的,后续会进行分库分表改造
![](img/image-20211208195821337.png#alt=image-20211208195821337)
![](https://file.xdclass.net/note/2020/javaweb/%E5%9B%BE%E7%89%87/logo.png#alt=logo) **愿景:"让编程不再难学,让技术与生活更加有趣" **
**更多架构课程请访问 xdclass.net**
<a name="9c0f1574"></a>
### 第二十章 短链服务-数据库表建立和业务代码开发
<a name="1e53bcb6"></a>
#### 第1集 数据库表模型讲解-短链分组和短链
**简介: 数据库表模型讲解-短链分组和短链**
- 关系
- 一个账号有多个分组
- 一个分组下有多个短链
![](img/image-20211208231632975.png#alt=image-20211208231632975)
- 短链-分组
CREATE TABLE link_group
(
id
bigint unsigned NOT NULL,
title
varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT ‘组名’,
account_no
bigint DEFAULT NULL COMMENT ‘账号唯一编号’,
gmt_create
datetime DEFAULT CURRENT_TIMESTAMP,
gmt_modified
datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (id
)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
- 短链
CREATE TABLE short_link
(
id
bigint unsigned NOT NULL ,
group_id
bigint DEFAULT NULL COMMENT ‘组’,
title
varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT ‘短链标题’,
original_url
varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT ‘原始url地址’,
domain
varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT ‘短链域名’,
code
varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT ‘短链压缩码’,
sign
varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT ‘长链的md5码,方便查找’,
expired
datetime DEFAULT NULL COMMENT ‘过期时间,长久就是-1’,
account_no
bigint DEFAULT NULL COMMENT ‘账号唯一编号’,
gmt_create
datetime DEFAULT CURRENT_TIMESTAMP COMMENT ‘创建时间’,
gmt_modified
datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT ‘修改时间’,
del
int unsigned NOT NULL COMMENT ‘0是默认,1是删除’,
state
varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT ‘状态,lock是锁定不可用,active是可用’,
link_type
varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT ‘链接产品层级:FIRST 免费青铜、SECOND黄金、THIRD钻石’,
PRIMARY KEY (id
),
UNIQUE KEY uk_code
(code
)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
<a name="7ceb26c3"></a>
#### 第2集 MybatisPlus逆向工具生成短链服务相关java对象
**简介: MybatisPlus逆向工具生成短链服务相关java对象**
- 配置代码
public class MyBatisPlusGenerator {
public static void main(String[] args) {
//1. 全局配置
GlobalConfig config = new GlobalConfig();
// 是否支持AR模式
config.setActiveRecord(true)
// 作者
.setAuthor("二当家小D")
// 生成路径,最好使用绝对路径,window路径是不一样的
//TODO TODO TODO TODO
.setOutputDir("/Users/xdclass/Desktop/demo/src/main/java")
// 文件覆盖
.setFileOverride(true)
// 主键策略
.setIdType(IdType.AUTO)
.setDateType(DateType.ONLY_DATE)
// 设置生成的service接口的名字的首字母是否为I,默认Service是以I开头的
.setServiceName("%sService")
//实体类结尾名称
.setEntityName("%sDO")
//生成基本的resultMap
.setBaseResultMap(true)
//不使用AR模式
.setActiveRecord(false)
//生成基本的SQL片段
.setBaseColumnList(true);
//2. 数据源配置
DataSourceConfig dsConfig = new DataSourceConfig();
// 设置数据库类型
dsConfig.setDbType(DbType.MYSQL)
.setDriverName("com.mysql.cj.jdbc.Driver")
//TODO TODO TODO TODO
.setUrl("jdbc:mysql://120.79.150.146:3306/dcloud_link?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai")
.setUsername("root")
.setPassword("xdclass.net168");
//3. 策略配置globalConfiguration中
StrategyConfig stConfig = new StrategyConfig();
//全局大写命名
stConfig.setCapitalMode(true)
// 数据库表映射到实体的命名策略
.setNaming(NamingStrategy.underline_to_camel)
//使用lombok
.setEntityLombokModel(true)
//使用restcontroller注解
.setRestControllerStyle(true)
// 生成的表, 支持多表一起生成,以数组形式填写
//TODO TODO TODO TODO
.setInclude("link_group","short_link");
//4. 包名策略配置
PackageConfig pkConfig = new PackageConfig();
pkConfig.setParent("net.xdclass")
.setMapper("mapper")
.setService("service")
.setController("controller")
.setEntity("model")
.setXml("mapper");
//5. 整合配置
AutoGenerator ag = new AutoGenerator();
ag.setGlobalConfig(config)
.setDataSource(dsConfig)
.setStrategy(stConfig)
.setPackageInfo(pkConfig);
//6. 执行操作
ag.execute();
System.out.println("======= 小滴课堂 Done 相关代码生成完毕 ========");
}
}
- 导入生成好的代码
- model (为啥不放common项目,如果是确定每个服务都用到的依赖或者类才放到common项目)
- mapper 类接口拷贝
- resource/mapper文件夹 xml脚本拷贝
- controller
- service 不拷贝
<a name="5df0baf9"></a>
#### 第3集 同学遇到的坑-配置文件修改-yml转properties
**简介: 配置文件修改-yml转properties**
-
SpringBoot的配置文件有两种
- 一种是properties结尾的
- 一种是yaml或者yml文件结尾的
- 如果同时存在properties和yml, application.properties里面的属性就会覆盖里application.yml的属性
-
yml的注意点
- yml中缩进一定不能使用TAB,否则会报很奇怪的错误
- yml每个的冒号后面一定都要加一个空格
- 第一个是yml是支持中文内容的,properties想使用中文需要unicode编码
-
账号服务配置转换
- 在线转换工具:[https://www.toyaml.com](https://www.toyaml.com)
server.port=8001 spring.application.name=dcloud-account
—————服务注册和发现———————
spring.cloud.nacos.discovery.server-addr=120.79.150.146:8848 spring.cloud.nacos.discovery.username=nacos spring.cloud.nacos.discovery.password=nacos
———-redis连接配置———-
spring.redis.client-type=jedis spring.redis.host=120.79.150.146 spring.redis.password=xdclass.net spring.redis.port=6379 spring.redis.jedis.pool.max-active=100 spring.redis.jedis.pool.max-idle=100 spring.redis.jedis.pool.min-idle=100 spring.redis.jedis.pool.max-wait=60000
———-分库分表数据源配置———-
spring.shardingsphere.datasource.names=ds0 spring.shardingsphere.datasource.ds0.connectionTimeoutMilliseconds=30000 spring.shardingsphere.datasource.ds0.driver-class-name=com.mysql.cj.jdbc.Driver spring.shardingsphere.datasource.ds0.idleTimeoutMilliseconds=60000 spring.shardingsphere.datasource.ds0.jdbc-url=jdbc:mysql://120.79.150.146:3306/dcloud_account?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true spring.shardingsphere.datasource.ds0.maintenanceIntervalMilliseconds=30000 spring.shardingsphere.datasource.ds0.maxLifetimeMilliseconds=1800000 spring.shardingsphere.datasource.ds0.maxPoolSize=50 spring.shardingsphere.datasource.ds0.minPoolSize=50 spring.shardingsphere.datasource.ds0.password=xdclass.net168 spring.shardingsphere.datasource.ds0.type=com.zaxxer.hikari.HikariDataSource spring.shardingsphere.datasource.ds0.username=root spring.shardingsphere.props.sql.show=true
指定traffic表的数据分布情况,配置数据节点,行表达式标识符使用 ${…} 或 $->{…},但前者与 Spring 本身的文件占位符冲突,所以在 Spring 环境中建议使用 $->{…}
spring.shardingsphere.sharding.tables.traffic.actual-data-nodes=ds0.traffic_$->{0..1}
水平分表策略+行表达式分片
spring.shardingsphere.sharding.tables.traffic.table-strategy.inline.algorithm-expression=traffic_$->{ account_no % 2 } spring.shardingsphere.sharding.tables.traffic.table-strategy.inline.sharding-column=account_no
id生成策略
spring.shardingsphere.sharding.tables.traffic.key-generator.column=id spring.shardingsphere.sharding.tables.traffic.key-generator.props.worker.id=${workId} spring.shardingsphere.sharding.tables.traffic.key-generator.type=SNOWFLAKE
—————sms短信配置———————
sms.app-code=6999d4df3e7d48028470bbe517169a8d sms.template-id=M72CB42894
—————阿里云OSS配置———————
aliyun.oss.endpoint=oss-cn-guangzhou.aliyuncs.com aliyun.oss.access-key-id=LTAI5tHVGvYw7twoVFyruB1H aliyun.oss.access-key-secret=r4d0EudzSvPfVMb9Zp0TfmsE32RXmN aliyun.oss.bucketname=dcloud-link
- 短链服务相关配置文件(复制账号微服务配置修改)
<a name="51507b65"></a>
#### 第4集 短链分组管理-CURD接口开发实战《上》
**简介: 短链分组管理-CURD接口开发实战《上》**
- 配置登录拦截器
- 新增接口
- 删除接口
<a name="5b28db83"></a>
#### 第5集 短链分组管理-CURD接口开发实战《下》
**简介: 短链分组管理-CURD接口开发实战《下》**
- 查询详情
- 查询用户全部分组
- 更新分组接口
<a name="db73db90"></a>
#### 第6集 短链分组管理-水平分库分表配置实战《青铜玩法》
**简介: 短链分组管理-水平分库分表配置实战**
-
需求
- 未来2年,短链平台累计5百万用户
- 短链组:一个用户30个组,就是1.5亿个组
- 单表不超过1千万数据,需要分15张表
- 进一步延伸,进行水平分库分表,比如 2库、4库、8库、16库
- 一个库一张表
- 需要降低单表数据量,进行水平分库分表
- 分库数量:线上分16库,本地分2库即可
- 分片key: account_no,查询维度都是根据account_no进行查询
- 分片策略:行表达式分片策略 InlineShardingStrategy
-
配置
—————短链组,策略:水平分库,不水平分表———————
先进行水平分库, 水平分库策略,行表达式分片
spring.shardingsphere.sharding.tables.link_group.database-strategy.inline.sharding-column=account_no spring.shardingsphere.sharding.tables.link_group.database-strategy.inline.algorithm-expression=ds$->{account_no % 2}
![](https://file.xdclass.net/note/2020/javaweb/%E5%9B%BE%E7%89%87/logo.png#alt=logo) **愿景:"让编程不再难学,让技术与生活更加有趣" **
**更多架构课程请访问 xdclass.net**
<a name="a364ed8d"></a>
### 第二十一章 短链服务分库分表-如何做到扩容免数据迁移《黄金玩法》
<a name="84188eda"></a>
#### 第1集 短链服务-ShortLink分库分表解决方案讲解《青铜》
**简介: 短链服务-ShortLink分库分表解决方案讲解**
- 前言
- 因为有些参数是和购买大课支付订单绑定,每周动态更新,小滴课堂官网开通权限
- 所以买了盗版的同学没法实操重要环节的内容。
- 如果发现你买了盗版,帮我举报。联系我这边这边大课会有特殊折扣优惠
- 加我微信,都是学习的同学,小的付出带来涨薪和技术增长,6分之1以上月薪即可回本
- 群里的学习氛围-技术讨论也是盗版没法给到的
![](https://file.xdclass.net/note/2021/redis/img/image-20210421143515942.png#alt=image-20210421143515942)
-
短链分库分表-小滴课堂老王 同学想到的
- 老王同学:老师,很容易的,前面学了那么多直接用Inline进行分库分表
![](img/image-20211210110225010.png#alt=image-20211210110225010)
-
数据量预估
- 首年日活用户: 10万
- 首年日新增短链数据:10万*50 = 500万
- 年新增短链数:500万 * 365天 = 18.2亿
- 往高的算就是100亿,支撑3年
-
分库分表策略
-
分库分表
- 16个库, 每个库64个表,总量就是 1024个表
-
分片键:短链码 code
- 比如 g1.fit/92AEva 的短链码 92AEva
-
分库分表算法:短链码进行hash取模
库ID = 短链码hash值 % 库数量
表ID = 短链码hash值 / 库数量 % 表数量
-
优点
- 保证数据较均匀的分散落在不同的库、表中,可以有效的避免热点数据集中问题,
- 分库分表方式清晰易懂
-
问题
- 扩容不是很方便,需要数据迁移
- 需要一次性建立16个库, 每个库64个表,总量就是 1024个表,浪费资源
<a name="35872087"></a>
#### 第2集 短链服务-分库分表扩容免数据迁移解决方案讲解《黄金玩法》
**简介: 短链服务-分库分表免迁移扩容解决方案讲解《黄金玩法》**
- 需要解决的问题
- 数据量增加,扩容避免迁移数据或者免迁移
- 前期数据量不多,不浪费库表系统资源
- 分库分表:16个库, 每个库64个表,总量就是 1024个表
- 讨论区直达
- [https://www.zhihu.com/people/xdclass/asks](https://www.zhihu.com/people/xdclass/asks)
- 强调一遍
- 一定要自己多思考,我说的也不一定对,留坑也说不准
- 要反驳我的内容,才能提高自己的能力
- 真心要求大家提高自己的思考能力,不能我说啥就听啥
- 举一反三,才是咱们小滴课堂出来的同学,真正靠技术说话的人
![](img/image-20211210183420307.png#alt=image-20211210183420307)
- 短链码
- 比如 g1.fit/92AEva 的短链码 92AEva
- 如何做?
- 从短链码入手-增加库表位
- 类似案例-阿里这边商品订单号-里面也包括了库表信息(规则不能说)
![](img/image-20211210155218497.png#alt=image-20211210155218497)
![](img/image-20211211101342722.png#alt=image-20211211101342722)
<a name="f80d4142"></a>
#### 第3集 短链服务-分库分表相关库表建立
**简介: 短链服务-分库分表相关库表建立**
-
为啥能做到免迁移扩容?
- A92AEva1
- 由于短链码的前缀和后缀是是固定的,所以扩容也不影响旧的数据
- 类似的免迁移扩容策略还有哪些?
- 时间范围分库分表
- id范围分库分表
-
三个库
-
一个库两个表
-
properties配置多库表
spring.shardingsphere.datasource.names=ds0,ds1,dsa
ds0配置
spring.shardingsphere.datasource.ds0.connectionTimeoutMilliseconds=30000 spring.shardingsphere.datasource.ds0.driver-class-name=com.mysql.cj.jdbc.Driver spring.shardingsphere.datasource.ds0.idleTimeoutMilliseconds=60000 spring.shardingsphere.datasource.ds0.jdbc-url=jdbc:mysql://120.79.150.146:3306/dcloud_link_0?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true spring.shardingsphere.datasource.ds0.type=com.zaxxer.hikari.HikariDataSource spring.shardingsphere.datasource.ds0.maintenanceIntervalMilliseconds=30000 spring.shardingsphere.datasource.ds0.maxLifetimeMilliseconds=1800000 spring.shardingsphere.datasource.ds0.maxPoolSize=50 spring.shardingsphere.datasource.ds0.minPoolSize=50 spring.shardingsphere.datasource.ds0.username=root spring.shardingsphere.datasource.ds0.password=xdclass.net168
ds1配置
spring.shardingsphere.datasource.ds1.connectionTimeoutMilliseconds=30000 spring.shardingsphere.datasource.ds1.driver-class-name=com.mysql.cj.jdbc.Driver spring.shardingsphere.datasource.ds1.idleTimeoutMilliseconds=60000 spring.shardingsphere.datasource.ds1.jdbc-url=jdbc:mysql://120.79.150.146:3306/dcloud_link_1?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true spring.shardingsphere.datasource.ds1.type=com.zaxxer.hikari.HikariDataSource spring.shardingsphere.datasource.ds1.maintenanceIntervalMilliseconds=30000 spring.shardingsphere.datasource.ds1.maxLifetimeMilliseconds=1800000 spring.shardingsphere.datasource.ds1.maxPoolSize=50 spring.shardingsphere.datasource.ds1.minPoolSize=50 spring.shardingsphere.datasource.ds1.username=root spring.shardingsphere.datasource.ds1.password=xdclass.net168
dsa配置
spring.shardingsphere.datasource.dsa.connectionTimeoutMilliseconds=30000 spring.shardingsphere.datasource.dsa.driver-class-name=com.mysql.cj.jdbc.Driver spring.shardingsphere.datasource.dsa.idleTimeoutMilliseconds=60000 spring.shardingsphere.datasource.dsa.jdbc-url=jdbc:mysql://120.79.150.146:3306/dcloud_link_a?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true spring.shardingsphere.datasource.dsa.type=com.zaxxer.hikari.HikariDataSource spring.shardingsphere.datasource.dsa.maintenanceIntervalMilliseconds=30000 spring.shardingsphere.datasource.dsa.maxLifetimeMilliseconds=1800000 spring.shardingsphere.datasource.dsa.maxPoolSize=50 spring.shardingsphere.datasource.dsa.minPoolSize=50 spring.shardingsphere.datasource.dsa.username=root spring.shardingsphere.datasource.dsa.password=xdclass.net168
<a name="c6dd178c"></a>
#### 第4集 短链服务水平分库分表实战-标准分片策略-精准分片算法《上》
**简介: 短链服务分库分表实战-标准分片策略-精准分片算法**
- 水平分库-标准分片策略-精准分片算法 Ae23asa1
public class CustomDBPreciseShardingAlgorithm implements PreciseShardingAlgorithm
/**
* @param availableTargetNames 数据源集合
* 在分库时值为所有分片库的集合 databaseNames
* 分表时为对应分片库中所有分片表的集合 tablesNames
* @param shardingValue 分片属性,包括
* logicTableName 为逻辑表,
* columnName 分片健(字段),
* value 为从 SQL 中解析出的分片健的值
* @return
*/
@Override
public String doSharding(Collection<String> availableTargetNames, PreciseShardingValue<String> shardingValue) {
//获取短链码第一位,即库位
String codePrefix = shardingValue.getValue().substring(0, 1);
for (String targetName : availableTargetNames) {
//获取库名的最后一位,真实配置的ds
String targetNameSuffix = targetName.substring(targetName.length() - 1);
//如果一致则返回
if (codePrefix.equals(targetNameSuffix)) {
return targetName;
}
}
//抛异常
throw new BizException(BizCodeEnum.DB_ROUTE_NOT_FOUND);
}
}
- 配置
—————短链,策略:分库+分表———————
先进行水平分库,然后再水平分表
spring.shardingsphere.sharding.tables.short_link.database-strategy.standard.sharding-column=code spring.shardingsphere.sharding.tables.short_link.database-strategy.standard.precise-algorithm-class-name=net.xdclass.strategy.CustomDBPreciseShardingAlgorithm
<a name="071d83b0"></a>
#### 第5集 短链服务水平分库分表实战-标准分片策略-精准分片算法《下》
**简介: 短链服务分库分表实战-标准分片策略-精准分片算法**
- 水平分表-标准分片策略-精准分片算法
public class CustomTablePreciseShardingAlgorithm implements PreciseShardingAlgorithm
/**
* @param availableTargetNames 数据源集合
* 在分库时值为所有分片库的集合 databaseNames
* 分表时为对应分片库中所有分片表的集合 tablesNames
* @param shardingValue 分片属性,包括
* logicTableName 为逻辑表,
* columnName 分片健(字段),
* value 为从 SQL 中解析出的分片健的值
* @return
*/
@Override
public String doSharding(Collection<String> availableTargetNames, PreciseShardingValue<String> shardingValue) {
//获取逻辑表名
String targetName = availableTargetNames.iterator().next();
String value = shardingValue.getValue();
//短链码最后一位
String codePrefix = value.substring(value.length()-1);
//拼装actual table
return targetName + "_" + codePrefix;
}
}
- 配置文件
水平分表策略,自定义策略。 真实库.逻辑表
spring.shardingsphere.sharding.tables.short_link.actual-data-nodes=ds0.short_link,ds1.short_link,dsa.short_link spring.shardingsphere.sharding.tables.short_link.table-strategy.standard.sharding-column=code spring.shardingsphere.sharding.tables.short_link.table-strategy.standard.precise-algorithm-class-name=net.xdclass.strategy.CustomTablePreciseShardingAlgorithm
id生成策略
spring.shardingsphere.sharding.tables.short_link.key-generator.column=id spring.shardingsphere.sharding.tables.short_link.key-generator.type=SNOWFLAKE spring.shardingsphere.sharding.tables.short_link.key-generator.props.worker.id=${workerId}
<a name="9a1db07c"></a>
#### 第6集 短链服务-短链码配置生成库表位实战
**简介: 短链服务-短链码配置生成库表位实战**
- 分库位
public class ShardingDBConfig {
/**
* 存储数据库位置编号
*/
private static final List<String> dbPrefixList = new ArrayList<>();
private static Random random = new Random();
//配置启用那些库的前缀
static {
dbPrefixList.add("0");
dbPrefixList.add("1");
dbPrefixList.add("a");
}
/**
* 获取随机的前缀
* @return
*/
public static String getRandomDBPrefix(){
int index = random.nextInt(dbPrefixList.size());
return dbPrefixList.get(index);
}
}
- 分表位
public class ShardingTableConfig {
/**
* 存储数据表位置编号
*/
private static final List<String> tableSuffixList = new ArrayList<>();
private static Random random = new Random();
//配置启用那些表的后缀
static {
tableSuffixList.add("0");
tableSuffixList.add("a");
}
/**
* 获取随机的后缀
* @return
*/
public static String getRandomTableSuffix(){
int index = random.nextInt(tableSuffixList.size());
return tableSuffixList.get(index);
}
}
- 短链码配置
String code = ShardingDBConfig.getRandomDBPrefix() + shortLinkCode + ShardingTableConfig.getRandomTablePrefix();
<a name="27a82901"></a>
#### 第7集 短链服务-Manager层模块CRUD开发
**简介: 短链服务-Manager层模块CRUD开发**
- 代码
public interface ShortLinkManager {
/**
* 新增域名
*
* @param shortLinkDO
* @return
*/
int addShortLink(ShortLinkDO shortLinkDO);
/**
* 根据短链码找内容
*
* @param shortLinkCode
* @return
*/
ShortLinkDO findByShortLinkCode(String shortLinkCode);
/**
* 根据短链码和accountNo删除
*
* @param shortLinkCode
* @param accountNo
* @return
*/
int del(String shortLinkCode, Long accountNo);
}
@Component @Slf4j public class ShortLinkManagerImpl implements ShortLinkManager {
@Autowired
private ShortLinkMapper shortLinkMapper;
@Override
public int addShortLink(ShortLinkDO shortLinkDO) {
return shortLinkMapper.insert(shortLinkDO);
}
@Override
public ShortLinkDO findByShortLinkCode(String shortLinkCode) {
ShortLinkDO shortLinkDO = shortLinkMapper.selectOne(new QueryWrapper<ShortLinkDO>().eq("code", shortLinkCode).eq("del", 0));
return shortLinkDO;
}
/**
* 逻辑删除
*
* @param shortLinkCode
* @param accountNo
* @return
*/
@Override
public int del(String shortLinkCode, Long accountNo) {
ShortLinkDO shortLinkDO = new ShortLinkDO();
shortLinkDO.setDel(1);
int rows = shortLinkMapper.update(shortLinkDO, new QueryWrapper<ShortLinkDO>()
.eq("code", shortLinkCode).eq("account_no", accountNo));
return rows;
}
}
<a name="acd464dd"></a>
#### 第8集 短链服务-自定义分库分表策略单元测试实战
**简介: 短链服务-自定义分库分表策略单元测试实战**
-
单元测试
- 保存
@Autowired private ShortLinkManager shortLinkManager; /**
* 保存
*/
@Test
public void testSaveShortLink() {
Random random = new Random();
//for (int i = 0; i < 10; i++) {
int num1 = random.nextInt(10);
int num2 = random.nextInt(10000000);
int num3 = random.nextInt(10000000);
String originalUrl = num1 + "xdclass" + num2 + ".net" + num3;
String shortLinkCode = shortLinkComponent.createShortLinkCode(originalUrl);
ShortLinkDO shortLinkDO = new ShortLinkDO();
shortLinkDO.setCode(shortLinkCode);
shortLinkDO.setAccountNo(Long.valueOf(num3));
shortLinkDO.setSign(originalUrl);
shortLinkDO.setDel(0);
shortLinkManager.addShortLink(shortLinkDO);
//}
}
- 查找
@Test public void testFind() {
ShortLinkDO shortLinkDO = shortLinkManager.findByShortLinkCode(“03aAg0la”); log.info(shortLinkDO.toString());
}
<a name="c2d93d34"></a>
#### 第9集 加权负载均衡思想应用-数据库表扩容-数据不均匀问题解决方案
**简介: 加权负载均衡思想应用-数据库表扩容-数据不均匀问题解决方案**
-
问题
- 假如前期分三个库,一个库两个表,项目火爆,数据量激增,进行扩容
- 增加了新的数据库表位,会导致旧的库表比新的库表数据量多,且容易出现超载情况
![](img/image-20211211154813271.png#alt=image-20211211154813271)
![](img/image-20211210182836304.png#alt=image-20211210182836304)
-
Nginx加权负载均衡的应用(架构大课)
-
不同的库表位分配的概率不一样,类似的中间件应用场景有nginx
-
Nginx常见的负载均衡策略
-
节点轮询(默认)
-
weight 权重配置
- 简介:weight和访问比率成正比,数字越大,分配得到的流量越高
- 场景:服务器性能差异大的情况使用
upstream lbs { server 192.168.159.133:8080 weight=5; server 192.168.159.133:8081 weight=10; }
![](img/image-20211210182056832.png#alt=image-20211210182056832)
-
加权解决方式(作业)
- 库表位可以使用对象形式,配置权重,避免数据倾斜、数据集中
- 编写算法,根据不同的,配置权重,不同的库表位配置不同的权重
- 加权配置,list重复添加出现的高频的库表位
-
是不是可以把这个亮点记录下
- 面试的时候再面试官前面说下项目难点和你的解决方案
- 你想到的解决方案,方便又清晰,还省服务器资源和避免了问题
- 业务量超过评估量,分库分表-二次扩容的时候避免数据迁移
- 不用一次性建立很多个库表,可以动态添加,节省服务器资源
- 使用加权库表位算法,解决扩容后数据倾斜不均匀问题
![](https://file.xdclass.net/note/2020/javaweb/%E5%9B%BE%E7%89%87/logo.png#alt=logo) **愿景:"让编程不再难学,让技术与生活更加有趣" **
**更多架构课程请访问 xdclass.net**
<a name="c8fdf9a0"></a>
### 第二十二章 短链服务-分库分表多维度查询解决方案《钻石玩法》
<a name="ee08166c"></a>
#### 第1集 短链服务-短链URL跳转302跳转接口开发实战
**简介: 短链URL 跳转302跳转接口开发实战**
-
需求
- 接收一个短链码
- 解析获取原始地址
- 302进行跳转
-
编码实战
@Controller @Slf4j public class LinkApiController {
@Autowired
private ShortLinkService shortLinkService;
/**
* 解析 301还是302,这边是返回http code是302
* <p>
* 知识点一,为什么要用 301 跳转而不是 302 呐?
* <p>
* 301 是永久重定向,302 是临时重定向。
* <p>
* 短地址一经生成就不会变化,所以用 301 是同时对服务器压力也会有一定减少
* <p>
* 但是如果使用了 301,无法统计到短地址被点击的次数。
* <p>
* 所以选择302虽然会增加服务器压力,但是有很多数据可以获取进行分析
*
* @param linkCode
* @return
*/
@GetMapping(path = "/{shortLinkCode}")
public void dispatch(@PathVariable(name = "shortLinkCode") String shortLinkCode,
HttpServletRequest request, HttpServletResponse response) {
try {
log.info("短链码:{}", shortLinkCode);
//判断短链码是否合规
if (isLetterDigit(shortLinkCode)) {
//查找短链
ShortLinkVO shortLinkVO = shortLinkService.parseShortLinkCode(shortLinkCode);
//判断是否过期和可用
if (isVisitable(shortLinkVO)) {
response.setHeader("Location", shortLinkVO.getOriginalUrl());
//302跳转
response.setStatus(HttpStatus.FOUND.value());
} else {
response.setStatus(HttpStatus.NOT_FOUND.value());
return;
}
}
} catch (Exception e) {
response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
}
}
/**
* 判断短链是否可用
*
* @param shortLinkVO
* @return
*/
private static boolean isVisitable(ShortLinkVO shortLinkVO) {
if ((shortLinkVO != null && shortLinkVO.getExpired().getTime() > CommonUtil.getCurrentTimestamp())) {
if (ShortLinkStateEnum.ACTIVE.name().equalsIgnoreCase(shortLinkVO.getState())) {
return true;
}
} else if ((shortLinkVO != null && shortLinkVO.getExpired().getTime() == -1)) {
if (ShortLinkStateEnum.ACTIVE.name().equalsIgnoreCase(shortLinkVO.getState())) {
return true;
}
}
return false;
}
/**
* 仅包括数字和字母
*
* @param str
* @return
*/
private static boolean isLetterDigit(String str) {
String regex = "^[a-z0-9A-Z]+$";
return str.matches(regex);
}
}
<a name="eba54bf1"></a>
#### 第2集 老王遇到的短链服务多维度查询问题【业界通用难点】
**简介: 老王的问题-短链服务多维度查询问题**
-
一切都是那么顺利
- 创建短链、新增、分库分表、查询
![](img/image-20211212164400990.png#alt=image-20211212164400990)
- 但是问题来了,商家怎么看自己的全部短链呢?????
- 普通用户根据短链码可以路由到对应的库表
- 但是商家创建的短链码都是没规律,分布再不同的库表上,咋整???
![](img/image-20211212164513778.png#alt=image-20211212164513778)
-
不同维度查看数据,场景是不一样的,
-
主要是分:有PartitionKey,没PartitionKey两个场景
- 电商订单案例一:
- 订单表 的partionKey是user_id,用户查看自己的订 单列表方便
- 但商家查看自己店铺的订单列表就麻烦,分布在不同数据节点
![](img/image-20211212152029514.png#alt=image-20211212152029514)
- 短链访问案例
- 普通用户访问短链,根据短链码code可以解析到对应的库表
- 但短链商家,查看自己全部的短链就麻烦了,分布再不同的库下面
![](img/image-20211212170650207.png#alt=image-20211212170650207)
-
这个是【 通用的业务场景痛点 】,大家学会总结
- 除了上面的电商业务、短链业务,还有更多
- 招聘网站业务
- 企业查看自己某个岗位的面试记录
- 应聘者查看自己的全部面试记录
- 痛点:
- 根据user_id进行hash分库分表
- 但是企业的岗位存在不同user_id进行面试
-
咋解决???
![](img/image-20211212171501922.png#alt=image-20211212171501922)
<a name="46ff08aa"></a>
#### 第3集 分库分表多维度查询解决方案一之【字段解析配置】
**简介: 分库分表多维度查询解决方案一之【字段解析配置】**
-
分库分表后的查询问题
- 有PartitionKey,没PartitionKey两个场景
- 不同维度查询是不一样的,怎么解决?
-
解决方案
-
字段解析配置
-
NOSQL冗余
-
本身库表冗余双写方案
- 部分字段冗余
- 全量内容冗余
-
解决方式一(字段解析配置):
- 建一个表,存储account_no对应的库表位,商家生成的【短链码】固定前缀或者后缀
- 即【短链码】里面包括了商家的信息
![](img/image-20211212174022539.png#alt=image-20211212174022539)
<a name="26a23ef7"></a>
#### 第4集 分库分表多维度查询解决方案二之【NOSQL方案】
**简介: 分库分表多维度查询解决方案二之【NOSQL方案】**
-
分库分表后的查询问题
- 有PartitionKey,没PartitionKey两个场景
- 不同维度查询是不一样的,怎么解决?
- 解决方案
- 字段解析配置
- NOSQL冗余
- 本身库表冗余双写方案
- 部分字段冗余
- 全量内容冗余
-
解决方式二:
- 电商订单案例
- 订单表 的partionKey是user_id,用户查看自己的订单列表方便
- 但商家查看自己店铺的订单列表就麻烦,分布在不同数据节点
- 订单冗余存储在es上一份
- 业务架构流程
![](img/image-20211212175450861.png#alt=image-20211212175450861)
- 短链平台案例
- 短链表的partionKey是短链码,用户访问短码方便解析
- 但商家查看自己某个分组下全部短链列表就麻烦,分布在不同数据节点
- 短链码冗余存储在es上一份
- 业务架构流程
![](img/image-20211212180211424.png#alt=image-20211212180211424)
<a name="131b8d5c"></a>
#### 第5集 分库分表多维度查询解决方案三之【冗余双写方案】
**简介: 分库分表多维度查询解决方案三之【冗余双写方案】**
-
分库分表后的查询问题
- 有PartitionKey,没PartitionKey两个场景
- 不同维度查询是不一样的,怎么解决?
-
解决方式三:
- 电商场景
- b2b平台,比如淘宝、京东,买家和卖家都要能够看到自己的订单列表
- 无论是按照买家id切分订单表,还是按照卖家id切分订单表都没法满足要求
- 拆分买家库和卖家库
- 买家库,按照用户的id来分库分表
- 卖家库,按照卖家的id来分库分表
- 数据冗余
- 下订单的时候写两份数据
- 在买家库和卖家库各写一份
![](img/image-20211213131242530.png#alt=image-20211213131242530)
- 短链场景
![](img/image-20211213130611913.png#alt=image-20211213130611913)
-
作业问题:
- 冗余双写会代来什么问题?
- 是时间换空间还是空间换时间?
![](img/image-20211213132258327.png#alt=image-20211213132258327)
<a name="fc61e281"></a>
#### 第6集 分库分表-冗余双写方案和分布式事务问题解决《上》
**简介: 分库分表-冗余双写方案和分布式事务问题解决**
- 冗余双写会代来什么问题?
- 存储空间更多(属于空间换时间,需要更多存储空间,减少库表数据量,提升性能)
- 冗余双写怎么实现问题
- 分布事务问题
![](img/image-20211213132642235.png#alt=image-20211213132642235)
-
冗余双写
- 解决方案一
- 直接RPC调用+Seata分布式事务框架
- 优点:强一致性,代码逻辑简单,业务侵入性小
- 缺点:性能下降,seata本身存在一定的性能损耗
- Seata支持AT、TCC、Saga 三种模式
- AT:隔离性好和低改造成本, 但性能低
- TCC:性能和隔离性,但改造成本大
- Saga:性能和低改造成本,但隔离性不好
![](img/image-20211213133145677.png#alt=image-20211213133145677)<a name="d41d8cd9"></a>
####
<a name="bf24e643"></a>
#### 第7集 分库分表-冗余双写方案和分布式事务问题解决《下》
**简介: 分库分表-冗余双写方案和分布式事务问题解决**
- 解决方案二
- 使用MQ, 生产者确认消息发送成功后,不同的消费者订阅消息消费
- 同时保证消息处理的幂等性
- 保证Broker的高可用
- 优点
- 实现简单,改造成本小
- 性能高,没有全局锁
- 缺点
- 弱一致性,需要强一致性的场景不适用
- 消费者消费失败,需要额外写接口回滚生产者业务逻辑
![](img/image-20211213161903711.png#alt=image-20211213161903711)
![](https://file.xdclass.net/note/2020/javaweb/%E5%9B%BE%E7%89%87/logo.png#alt=logo) **愿景:"让编程不再难学,让技术与生活更加有趣" **
**更多架构课程请访问 xdclass.net**
<a name="cd900cda"></a>
### 第二十三章 短链服务-冗余双写库表创建+基础模块开发
<a name="4d4f2b11"></a>
#### 第1集 短链服务-分库分表策略+冗余双写库表架构设计
**简介: 短链服务-分库分表冗余双写库表架构设计**
-
数据量预估
- 首年日活用户: 10万
- 首年日新增短链数据:10万*50 = 500万
- 年新增短链数:500万 * 365天 = 18.2亿
- 往高的算就是100亿,支撑3年
-
分库分表策略
-
分库分表
- 8个库, 每个库128个表,总量就是 1024个表
- 本地开发 2库,每个库2个表
-
分片键:
- 分库PartitionKey:account_no
- 分表PartitionKey:group_id
-
接口访问量
- C端解析,访问量大
- B端查询,访问量少,单个表的存储数据可以多点
-
冗余双写库表设计 group_code_mapping (short_link一样)
CREATE TABLE group_code_mapping_0
(
id
bigint unsigned NOT NULL,
group_id
bigint DEFAULT NULL COMMENT ‘组’,
title
varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT ‘短链标题’,
original_url
varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT ‘原始url地址’,
domain
varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT ‘短链域名’,
code
varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT ‘短链压缩码’,
sign
varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT ‘长链的md5码,方便查找’,
expired
datetime DEFAULT NULL COMMENT ‘过期时间,长久就是-1’,
account_no
bigint DEFAULT NULL COMMENT ‘账号唯一编号’,
gmt_create
datetime DEFAULT CURRENT_TIMESTAMP COMMENT ‘创建时间’,
gmt_modified
datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT ‘修改时间’,
del
int unsigned NOT NULL COMMENT ‘0是默认,1是删除’,
state
varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT ‘状态,lock是锁定不可用,active是可用’,
link_type
varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT ‘链接产品层级:FIRST 免费青铜、SECOND黄金、THIRD钻石’,
PRIMARY KEY (id
),
UNIQUE KEY uk_code
(code
)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
- 短链域名表(前期不分库分表,默认ds0)
CREATE TABLE domain
(
id
bigint unsigned NOT NULL ,
account_no
bigint DEFAULT NULL COMMENT ‘用户自己绑定的域名’,
domain_type
varchar(11) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT ‘域名类型,自建custom, 官方offical’,
value
varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,
del
int(1) unsigned zerofill DEFAULT ‘0’ COMMENT ‘0是默认,1是禁用’,
gmt_create
datetime DEFAULT CURRENT_TIMESTAMP,
gmt_modified
datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (id
)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
<a name="8d9d2903"></a>
#### 第2集 短链服务-MybatisPlus逆向工程生成相关实体类
**简介: 短链服务-分库分表冗余双写库表架构设计**
-
表
- group_code_mapping
- domain
-
拷贝model/mapper/xml
<a name="f9d136f3"></a>
#### 第3集 短链服务-B端查询短链Manager层开发实战
**简介: 短链服务-B端查询短链Manager层开发实战**
-
常用接口
- 新增
- 详情
- 删除
- 分页
- 更新状态
-
其他接口用的时候再更新
<a name="5b050a75"></a>
#### 第4集 短链服务-Domain短链域名模块开发
**简介: 短链服务-Domain短链域名模块开发**
-
预判能力,给自己留条后路
-
部分表有进行分库分表,部分没,但是不确保未来是否会有,预留字段
-
数据库设计的时候,参考同行竞品
- 很多情况下产品经理是会做比较多功能,比如自定义域名
- 但是迫于工期,就会缩减功能,但是未来一定是会加上的(只要是靠谱的功能)
-
开发Controller-Service-Manager层接口
@RestController @RequestMapping(“/api/domain/v1”) public class DomainController {
@Autowired
private DomainService domainService;
/**
* 查询全部可用域名
*
* @return
*/
@GetMapping("/list")
public JsonData listAll() {
List<DomainVO> list = domainService.listAll();
return JsonData.buildSuccess(list);
}
}
- 其他接口用的时候再加
<a name="e412585a"></a>
#### 第5集 短链服务-sharding-jdbc默认数据源配置实战
**简介: 短链服务-分库分表默认数据源配置实战**
- 某些表并不需要进行分表分库,未配置分片规则的表将通过默认数据源定位
—————配置默认数据库,比如短链域名,不分库分表———————
spring.shardingsphere.sharding.default-data-source-name=ds0
默认id生成策略
spring.shardingsphere.sharding.default-key-generator.column=id spring.shardingsphere.sharding.default-key-generator.type=SNOWFLAKE spring.shardingsphere.sharding.default-key-generator.props.worker.id=${workerId}
- domain模块单元测试
![](https://file.xdclass.net/note/2020/javaweb/%E5%9B%BE%E7%89%87/logo.png#alt=logo) **愿景:"让编程不再难学,让技术与生活更加有趣" **
**更多架构课程请访问 xdclass.net**
<a name="7d80f9c0"></a>
### 第二十四章 短链服务-冗余双写MQ架构和开发实战
<a name="5c22fe02"></a>
#### 第1集 冗余双写MQ架构讲解-Kafka+RabbitMQ方案
**简介: 冗余双写MQ架构实现讲解-Kafka+RabbitMQ方案**
- 通过MQ如何实现冗余双写?
![](img/image-20211213161903711.png#alt=image-20211213161903711)
- Kafka实现
![](img/image-20211217133831828.png#alt=image-20211217133831828)
- RabbitMQ实现
![](img/image-20211217133113370.png#alt=image-20211217133113370)
- 选择RabbitMQ理由
- 业务开发团队本身熟悉RabbitMQ(对内,省了学习成本、运维成本、现有基础设施)
- RabbitMQ自带延迟队列,更适合业务这块,比如定时任务、分布式事务处理
- Kafka比较适合在大数据领域流式计算
<a name="16ac2cce"></a>
#### 第2集 冗余双写MQ架构实现-RabbitMQ交换机知识点回顾
**简介: 冗余双写MQ架构实现 RabbitMQ交换机知识点回顾**
- RabbitMQ交换机类型
- 生产者将消息发送到 Exchange,交换器将消息路由到一个或者多个队列中,交换机有多个类型,队列和交换机是多对多的关系。
- 交换机只负责转发消息,不具备存储消息的能力,如果没有队列和exchange绑定,或者没有符合的路由规则,则消息会被丢失
- RabbitMQ有四种交换机类型,分别是Direct exchange、Fanout exchange、Topic exchange、Headers exchange,最后的基本不用
![](https://file.xdclass.net/note/2021/rabbitmq/img/image-20210107185756079.png#alt=image-20210107185756079)
-
交换机类型
- Direct Exchange 定向
- 将一个队列绑定到交换机上,要求该消息与一个特定的路由键完全匹配
- 例子:如果一个队列绑定到该交换机上要求路由键 “aabb”,则只有被标记为“aabb”的消息才被转发,不会转发aabb.cc,也不会转发gg.aabb,只会转发aabb
- 处理路由健
- Fanout Exchange 广播
- 只需要简单的将队列绑定到交换机上,一个发送到交换机的消息都会被转发到与该交换机绑定的所有队列上。很像子网广播,每台子网内的主机都获得了一份复制的消息
- Fanout交换机转发消息是最快的,用于发布订阅,广播形式,中文是扇形
- 不处理路由健
- Topic Exchange 通配符
- 主题交换机是一种发布/订阅的模式,结合了直连交换机与扇形交换机的特点
- 将路由键和某模式进行匹配。此时队列需要绑定要一个模式上
- 符号“#”匹配一个或多个词,符号“*”匹配不多不少一个词
- 例子:因此“abc.#”能够匹配到“abc.def.ghi”,但是“abc.*” 只会匹配到“abc.def”。
-
我们这个冗余双写应该采用哪种交换机?
- Fanout Exchange 广播(做幂等性)
- Topic Exchange 通配符 (推荐)
<a name="3adb1549"></a>
#### 第3集 冗余双写MQ架构实现-交换机和队列绑定配置讲解
**简介: 冗余双写MQ架构实现-RabbitMQ交换机和队列绑定配置讲解**
- RabbitMQ交换机配置讲解
- Topic Exchange 通配符
![](img/image-20211217170322433.png#alt=image-20211217170322433)
- 注释说明
/**
- 用topic模式解决分布式事务-最终一致性 *
- 交换机和队列绑定时用的binding使用通配符的路由健
- 生产者发送消息时需要使用具体的路由健 *
- BindingKey是Exchange和Queue绑定的规则描述
- RoutingKey,Exchange就据这个RoutingKey和当前Exchange所有绑定的BindingKey做匹配,符合规则则发送过去
- 真实情况下参数名都是RoutingKey,没有BindingKey这个参数,
- 为了区别用户发送的和绑定的概念,才说RoutingKey和BindingKey *
- 目的:解决短链新增数据一致性问题
- 新增短链-》发送topic消息-》新增短链、新增映射两个消费者进行监听 */ ```
第4集 冗余双写MQ架构RabbitMQ配置开发实战
简介: 冗余双写MQ架构RabbitMQ配置开发实战
配置实操
- 前期避免配置太多,同学不容易理解或者搞混,就先不抽取到配置文件里面
@Configuration
@Data
public class RabbitMQConfig {
/**
* 交换机
*/
private String shortLinkEventExchange="short_link.event.exchange";
/**
* 创建交换机 Topic类型
* 一般一个微服务一个交换机
* @return
*/
@Bean
public Exchange shortLinkEventExchange(){
return new TopicExchange(shortLinkEventExchange,true,false);
//return new FanoutExchange(shortLinkEventExchange,true,false);
}
//新增短链相关配置====================================
/**
* 新增短链 队列
*/
private String shortLinkAddLinkQueue="short_link.add.link.queue";
/**
* 新增短链映射 队列
*/
private String shortLinkAddMappingQueue="short_link.add.mapping.queue";
/**
* 新增短链具体的routingKey,【发送消息使用】
*/
private String shortLinkAddRoutingKey="short_link.add.link.mapping.routing.key";
/**
* topic类型的binding key,用于绑定队列和交换机,是用于 link 消费者
*/
private String shortLinkAddLinkBindingKey="short_link.add.link.*.routing.key";
/**
* topic类型的binding key,用于绑定队列和交换机,是用于 mapping 消费者
*/
private String shortLinkAddMappingBindingKey="short_link.add.*.mapping.routing.key";
/**
* 新增短链api队列和交换机的绑定关系建立
*/
@Bean
public Binding shortLinkAddApiBinding(){
return new Binding(shortLinkAddLinkQueue,Binding.DestinationType.QUEUE, shortLinkEventExchange,shortLinkAddLinkBindingKey,null);
}
/**
* 新增短链mapping队列和交换机的绑定关系建立
*/
@Bean
public Binding shortLinkAddMappingBinding(){
return new Binding(shortLinkAddMappingQueue,Binding.DestinationType.QUEUE, shortLinkEventExchange,shortLinkAddMappingBindingKey,null);
}
/**
* 新增短链api 普通队列,用于被监听
*/
@Bean
public Queue shortLinkAddLinkQueue(){
return new Queue(shortLinkAddLinkQueue,true,false,false);
}
/**
* 新增短链mapping 普通队列,用于被监听
*/
@Bean
public Queue shortLinkAddMappingQueue(){
return new Queue(shortLinkAddMappingQueue,true,false,false);
}
}
第5集 冗余双写MQ架构-短链和mapping消费者配置
简介: 冗余双写MQ架构-短链和mapping消费者配置
- 消息对象封装
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class EventMessage implements Serializable {
/**
* 消息队列id
*/
private String messageId;
/**
* 事件类型
*/
private String eventMessageType;
/**
* 业务id
*/
private String bizId;
/**
* 账号
*/
private Long accountNo;
/**
* 消息体
*/
private String content;
/**
* 异常备注
*/
private String remark;
}
- short_link消费者配置
@Component
@Slf4j
@RabbitListener(queues = "short_link.add.link.queue")
public class ShortLinkAddLinkMQListener {
/**
*
*
* @param eventMessage
* @param message
* @param channel
* @throws IOException
*/
@RabbitHandler
public void shortLinkHandler(EventMessage eventMessage, Message message, Channel channel) throws IOException {
log.info("监听到消息ShortLinkAddLinkMQListener:message消息内容:{}", message);
long msgTag = message.getMessageProperties().getDeliveryTag();
try {
//TODO 处理业务
} catch (Exception e) {
// 处理业务失败,还要进行其他操作,比如记录失败原因
log.error("消费失败{}", eventMessage);
throw new BizException(BizCodeEnum.MQ_CONSUME_EXCEPTION);
}
log.info("消费成功{}", eventMessage);
//确认消息消费成功
channel.basicAck(msgTag, false);
}
}
- mapping消费者配置
@Component
@Slf4j
@RabbitListener(queues = "short_link.add.mapping.queue")
public class ShortLinkAddMappingMQListener {
/**
* @param eventMessage
* @param message
* @param channel
* @throws IOException
*/
@RabbitHandler
public void shortLinkHandler(EventMessage eventMessage, Message message, Channel channel) throws IOException {
log.info("监听到消息ShortLinkAddMappingMQListener:message消息内容:{}", message);
long msgTag = message.getMessageProperties().getDeliveryTag();
try {
//TODO 处理业务
} catch (Exception e) {
// 处理业务失败,还要进行其他操作,比如记录失败原因
log.error("消费失败{}", eventMessage);
throw new BizException(BizCodeEnum.MQ_CONSUME_EXCEPTION);
}
log.info("消费成功{}", eventMessage);
//确认消息消费成功
channel.basicAck(msgTag, false);
}
}
愿景:”让编程不再难学,让技术与生活更加有趣”
更多架构课程请访问 xdclass.net
第二十五章 短链服务冗余双写-链路测试和异常消息处理实战
第1集 冗余双写MQ架构-消费者配置自动创建队列和集群测试
简介: 冗余双写MQ架构-MQ消费者配置自动创建队列
- controller-service层开发
- 配置文件配置MQ
##----------rabbit配置--------------
spring.rabbitmq.host=120.79.150.146
spring.rabbitmq.port=5672
#需要手工创建虚拟主机
spring.rabbitmq.virtual-host=dev
spring.rabbitmq.username=admin
spring.rabbitmq.password=password
#消息确认方式,manual(手动ack) 和auto(自动ack)
spring.rabbitmq.listener.simple.acknowledge-mode=auto
大家遇到的问题 (不会自动创建队列)
- 加了@bean配置交换机和queue,启动项目却没自动化创建队列
- RabbitMQ懒加载模式, 需要配置消费者监听才会创建,
@RabbitListener(queues = "short_link.add.link.queue")
另外种方式(若Mq中无相应名称的队列,会自动创建Queue)
@RabbitListener(queuesToDeclare = { @Queue("short_link.add.link.queue") })
- 链路测试-多节点启动
第2集 冗余双写架构-MQ消费者异常重试处理方案链路讲解
简介: 冗余双写架构-MQ消费者异常处理方案讲解
消费者异常情况处理
- 业务代码自己重试
- 组件重试
- RabbitMQ配置重试
#开启重试,消费者代码不能添加try catch捕获不往外抛异常
spring.rabbitmq.listener.simple.retry.enabled=true
#最大重试次数
spring.rabbitmq.listener.simple.retry.max-attempts=4
# 重试消息的时间间隔,5秒
spring.rabbitmq.listener.simple.retry.initial-interval=5000
- 问题:多次重试失败怎么处理?
解决方式:RepublishMessageRecoverer
- 消息重试一定次数后,用特定的routingKey转发到指定的交换机中,方便后续排查和告警
第3集 冗余双写架构-MQ消费者异常重试处理方案编码实战
简介: 冗余双写架构-MQ消费者异常重试处理方案编码实战
解决方式:RepublishMessageRecoverer
- 消费消息重试一定次数后,用特定的routingKey转发到指定的交换机中,方便后续排查和告警
注意
- 消息消费确认使用自动确认方式
@Configuration
@Data
public class RabbitMQErrorConfig {
private String shortLinkErrorExchange = "short_link.error.exchange";
private String shortLinkErrorQueue = "short_link.error.queue";
private String shortLinkErrorRoutingKey = "short_link.error.routing.key";
@Autowired
private RabbitTemplate rabbitTemplate;
/**
* 异常交换机
* @return
*/
@Bean
public TopicExchange errorTopicExchange(){
return new TopicExchange(shortLinkErrorExchange,true,false);
}
/**
* 异常队列
* @return
*/
@Bean
public Queue errorQueue(){
return new Queue(shortLinkErrorQueue,true);
}
/**
* 队列与交换机进行绑定
* @return
*/
@Bean
public Binding BindingErrorQueueAndExchange(Queue errorQueue,TopicExchange errorTopicExchange){
return BindingBuilder.bind(errorQueue).to(errorTopicExchange).with(shortLinkErrorRoutingKey);
}
/**
* 配置 RepublishMessageRecoverer
* 用途:消息重试一定次数后,用特定的routingKey转发到指定的交换机中,方便后续排查和告警
*
* 顶层是 MessageRecoverer接口,多个实现类
*
* @return
*/
@Bean
public MessageRecoverer messageRecoverer(){
return new RepublishMessageRecoverer(rabbitTemplate,shortLinkErrorExchange,shortLinkErrorRoutingKey);
}
}
第4集 冗余双写架构-MQ消费者异常重试处理方案测试
简介: 冗余双写架构-MQ消费者异常重试处理方案测试
- 模拟异常重试
- 消费者配置
愿景:”让编程不再难学,让技术与生活更加有趣”
更多架构课程请访问 xdclass.net
第二十六章 短链服务冗余双写问题抛出-MQ消费者链路开发
第1集 冗余双写架构-高并发下短链码生成端问题抛出
简介: 冗余双写架构-短链码生成端问题抛出
短链码是哪里生成?生产者端 还是消费者端
生产者生成短链码,下面的情况
- 用户A生成短链码AABBCC,查询数据库不存在,发送MQ,插入数据库成功
- 用户B生成短链码AABBCC,查询数据库不存在,发送MQ,插入数据库失败
消费者生成短链码,下面的情况
- 用户A生成短链码AABBCC ,C端先插入,B端还没插入
- 用户B也生成短链码AABBCC ,B端先插入,C端还没插入
- 用户A生成短链码AABBCC ,B端插入
- 用户B生成短链码AABBCC ,C端插入
第2集 冗余双写架构-商家创建短链-C端消费者开发实战
简介: 冗余双写架构-商家创建短链-C端消费者开发实战
- C端消费者开发实战
//判断短链域名是否合法
//判断组名是否合法
//生成长链摘要
//生成短链码
//加锁
//查询短链码是否存在
//构建短链对象
//保存数据库
第3集 冗余双写架构-商家创建短链-B端消费者开发实战
简介: 冗余双写架构-商家创建短链-B端消费者开发实战
- B端消费者开发实战
//生成长链摘要
//判断短链域名是否合法
//判断组名是否合法
//生成短链码
//加锁(加锁再查,不然查询后,加锁前有线程刚好新增)
//查询短链码是否存在
//构建短链mapping对象
//保存数据库
public boolean handlerAddShortLink(EventMessage eventMessage) {
Long accountNo = eventMessage.getAccountNo();
String messageType = eventMessage.getEventMessageType();
ShortLinkAddRequest addRequest = JsonUtil.json2Obj(eventMessage.getContent(), ShortLinkAddRequest.class);
//短链域名校验
DomainDO domainDO = checkDomain(addRequest.getDomainType(), addRequest.getDomainId(), accountNo);
//校验组是否合法
LinkGroupDO linkGroupDO = checkLinkGroup(addRequest.getGroupId(), accountNo);
//长链摘要
String originalUrlDigest = CommonUtil.MD5(addRequest.getOriginalUrl());
//生成短链码
String shortLinkCode = shortLinkComponent.createShortLinkCode(addRequest.getOriginalUrl());
//TODO 加锁
//先判断是否短链码被占用
ShortLinkDO ShortLinCodeDOInDB = shortLinkManager.findByShortLinCode(shortLinkCode);
if(ShortLinCodeDOInDB == null){
//C端处理
if (EventMessageType.SHORT_LINK_ADD_LINK.name().equalsIgnoreCase(messageType)) {
ShortLinkDO shortLinkDO = ShortLinkDO.builder()
.accountNo(accountNo)
.code(shortLinkCode)
.title(addRequest.getTitle())
.originalUrl(addRequest.getOriginalUrl())
.domain(domainDO.getValue())
.groupId(linkGroupDO.getId())
.expired(addRequest.getExpired())
.sign(originalUrlDigest)
.state(ShortLinkStateEnum.ACTIVE.name())
.del(0)
.build();
shortLinkManager.addShortLink(shortLinkDO);
return true;
} else if (EventMessageType.SHORT_LINK_ADD_MAPPING.name().equalsIgnoreCase(messageType)) {
//B端处理
GroupCodeMappingDO groupCodeMappingDO = GroupCodeMappingDO.builder()
.accountNo(accountNo)
.code(shortLinkCode)
.title(addRequest.getTitle())
.originalUrl(addRequest.getOriginalUrl())
.domain(domainDO.getValue())
.groupId(linkGroupDO.getId())
.expired(addRequest.getExpired())
.sign(originalUrlDigest)
.state(ShortLinkStateEnum.ACTIVE.name())
.del(0)
.build();
groupCodeMappingManager.add(groupCodeMappingDO);
return true;
}
}
return false;
}
第4集 同个URL生成短链码随机库表位问题和解决方案讲解
简介: 同个URL生成短链码随机库表位问题和解决方案讲解
需求
- 小滴课堂的App的下载链接,需要进行投放广告,并验证不同渠道的效果质量
- 渠道:抖音、百度、新浪微博、知乎、B站、头条等
- 最终下载地址一样,但是需要区分不通渠道效果质量
问题抛出
MurmurHash对同个url产生后的值是一样的,但是随机拼接了库表位,最终生成的短链码就导致可能不一致的情况,怎么解决?
问题重现
答案
- MurmurHash后的短链码,拼接随机库表位需要固定
- 采用hashCode取模生成对应的库表位
第5集 MurmurHash短链码改进之生成固定库表位编码实战
简介: MurmurHash短链码改进之生成固定库表位编码实战
- 编码实战
/**
* 获取随机的后缀
* @return
*/
public static String getRandomTableSuffix(String code){
int hashCode = code.hashCode();
int num = Math.abs(hashCode) % tableSuffixList.size();
return tableSuffixList.get(num);
}
/**
* 获取随机的前缀
* @return
*/
public static String getRandomDBPrefix(String code){
int hashCode = code.hashCode();
int num = Math.abs(hashCode) % dbPrefixList.size();
return dbPrefixList.get(num);
}
- 单元测试
第6集 同个URL生成不唯一短链码问题和解决方案讲解
简介: 同个URL生产唯一短链码问题和解决方案讲解
需求
- 小滴课堂的App的下载链接,需要进行投放广告,并验证不同渠道的效果质量
- 渠道:抖音、百度、新浪微博、知乎、B站、头条等
- 最终下载地址一样,但是需要区分不通渠道效果质量
问题抛出:
解决了随机库表问题后,一个URL怎么生成多个不一样的短链码
URL重复生成短链问题
- 如果原始URL不做处理,则重复概率很高
- 方案:原始url 拼接随机串,访问前去除
问题重现
答案
- 生产者发送消息携带一个时间戳 或 随机id
原始URL开头拼接特殊字段
- 原生 https://xdclass.net
- 拼接后 1469558440337604610||https://xdclass.net
- 如果冲突,则编号递增1
- 访问前截取去除
第7集 同个URL生成不唯一短链码问题和解决方案编码实战
简介: 同个URL生成不唯一短链码问题和解决方案编码实战
- 工具类编写
/**
* URL增加前缀
* @param url
* @return
*/
public static String addUrlPrefix(String url){
return IDUtil.geneSnowFlakeID()+"&"+url;
}
/**
* URL移除前缀
* @param url
* @return
*/
public static String removeUrlPrefix(String url){
String originalUrl = url.substring(url.indexOf("&")+1);
return originalUrl;
}
/**
* 如果短链码重复,则调用这个方法
* url前缀编号递增1,如果还是用雪花算法,则容易C和B端不一致,所以采用原先的id递增1
* @param url
* @return
*/
public static String addUrlPrefixVersion(String url){
String result = url.substring(0,url.indexOf("&"));
//原始地址
String originalUrl = url.substring(url.indexOf("&")+1);
//新id编号
Long newIdValue = Long.parseLong(result)+1;
return newIdValue+"&"+originalUrl;
}
- 编码实战
//发送MQ消息
@Override
public JsonData createShortLink(ShortLinkAddRequest request) {
Long accountNo = LoginInterceptor.threadLocal.get().getAccountNo();
String originalUrl = CommonUtil.addUrlPrefix(request.getOriginalUrl());
request.setOriginalUrl(originalUrl);
EventMessage eventMessage = EventMessage.builder().accountNo(accountNo)
.content(JsonUtil.obj2Json(request))
.messageId(IDUtil.geneSnowFlakeID().toString())
.eventMessageType(EventMessageType.SHORT_LINK_ADD.name())
.build();
rabbitTemplate.convertAndSend(rabbitMQConfig.getShortLinkEventExchange(), rabbitMQConfig.getShortLinkAddRoutingKey(),eventMessage);
return JsonData.buildSuccess();
}
//短链解析,往数据库加插入数据进去
@GetMapping(path = "/{shortLinkCode}")
public void dispatch(@PathVariable(name = "shortLinkCode") String shortLinkCode,
HttpServletRequest request, HttpServletResponse response) {
try {
log.info("短链码:{}", shortLinkCode);
//判断短链码是否合规
if (isLetterDigit(shortLinkCode)) {
//查找短链
ShortLinkVO shortLinkVO = shortLinkService.parseShortLinkCode(shortLinkCode);
//判断是否过期和可用
if (isVisitable(shortLinkVO)) {
String originalUrl = CommonUtil.removeUrlPrefix(shortLinkVO.getOriginalUrl());
response.setHeader("Location", originalUrl);
//302跳转
response.setStatus(HttpStatus.FOUND.value());
} else {
response.setStatus(HttpStatus.NOT_FOUND.value());
return;
}
}
} catch (Exception e) {
response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
}
}
愿景:”让编程不再难学,让技术与生活更加有趣”
更多架构课程请访问 xdclass.net
第二十七章 短链码生成端选择和Lua分布式锁实战
第1集 短链码是生成端选择-生产者端-消费者端方案对比
简介: 短链码是生成端选择-生产者端-消费者端方案对比
短链码是哪里生成?生产者端 还是消费者端
方案一:生产者端生成短链码code
加分布式锁 key=code,配置过期时间(加锁失败则重新生成)
需要查询一次数据库或其他存储源,判断是否存在
发送MQ
- C端插入
- B端插入
- 解分布式锁(锁过期自动解锁)
方案二:消费者端生成短链码code
生产者发送消息
C端生成
加锁key=code
- 查询数据库,如果存在,则ver版本递增,重新生成短链码
- 保存数据库
- 解分布式锁
B端生成,
加锁key=code
- 查询数据库,如果存在,则ver版本递增,重新生成短链码
- 保存数据库
- 解分布式锁
第2集 分布式锁知识基础+进阶-可重入锁在分布式下的应用
简介:分布式锁知识基础+进阶-可重入锁在分布式下的应用
不知大家是否还记得,消费者生成短链码,高并发的情况
- 1)用户A生成短链码AABBCC ,C端先插入,B端还没插入
- 2)用户B也生成短链码AABBCC ,B端先插入,C端还没插入
- 3)用户A生成短链码AABBCC ,B端插入
- 4)用户B生成短链码AABBCC ,C端插入
需要的结果是
- 1、3可以成功, 2、4可以成功
避免短链码高并发下重复
加锁
- 本地锁:synchronize、lock等,锁在当前进程内,集群部署下依旧存在问题
- 分布式锁:redis、zookeeper等实现,虽然还是锁,但是多个进程共用的锁标记,可以用Redis、Zookeeper、Mysql等都可以
设计分布式锁应该考虑的东西
排他性
- 在分布式应用集群中,同一个方法在同一时间只能被一台机器上的一个线程执行
容错性
- 分布式锁一定能得到释放,比如客户端奔溃或者网络中断
- 满足可重入、高性能、高可用
- 注意分布式锁的开销、锁粒度
方案就是加锁
单节点可重入锁
可重入锁: JDK指的是以线程为单位,当一个线程获取对象锁之后,这个线程可以再次获取本对象上的锁,而其他的线程是不可以的,synchronized 和 ReentrantLock 都是可重入锁
分布式下的可重入锁
- 进程单位,当一个线程获取对象锁之后,其他节点的同个业务线程可以再次获取本对象上的锁
第3集 短链码基于Redis实现分布式锁的坑你是否踩过《上》
简介:基于Redis实现分布式锁的几种坑
实现分布式锁 可以用 Redis、Zookeeper、Mysql数据库这几种 , 性能最好的是Redis且是最容易理解
- 分布式锁离不开 key - value 设置
key 是锁的唯一标识,一般按业务来决定命名,比如想要给一种商品的秒杀活动加锁,key 命名为 “seckill_商品ID” 。value就可以使用固定值,比如设置成1。
短链码可以:short_link:code:xxxx
- 分布式锁离不开 key - value 设置
基于redis实现分布式锁,文档:http://www.redis.cn/commands.html#string
- 加锁 SETNX key value ``` setnx 的含义就是 SET if Not Exists,有两个参数 setnx(key, value),该方法是原子性操作
如果 key 不存在,则设置当前 key 成功,返回 1;
如果当前 key 已经存在,则设置当前 key 失败,返回 0
- 解锁 del (key)
得到锁的线程执行完任务,需要释放锁,以便其他线程可以进入,调用 del(key)
- 配置锁超时 expire (key,30s)
客户端奔溃或者网络中断,资源将会永远被锁住,即死锁,因此需要给key配置过期时间,以保证即使没有被显式释放,这把锁也要在一定时间后自动释放
- 综合伪代码
methodA(){ String key = “short_link:code:abcdef”
if(setnx(key,1) == 1){ expire(key,30,TimeUnit.MILLISECONDS) try { //做对应的业务逻辑 } finally { del(key) } }else{ //睡眠100毫秒,然后自旋调用本方法 methodA() } }
- 存在哪些问题,大家自行思考下
<a name="06e363bb"></a>
#### 第4集 短链码基于Redis实现分布式锁的坑你是否踩过《下》
**简介:短链码基于Redis实现分布式锁的坑你是否踩过**
-
存在什么问题?
- 多个命令之间不是原子性操作,如`setnx`和`expire`之间,如果`setnx`成功,但是`expire`失败,且宕机了,则这个资源就是死锁
使用原子命令:设置和配置过期时间 setnx / setex 如: set key 1 ex 30 nx java代码里面
String key = “short_link:code:abcdef” redisTemplate.opsForValue().setIfAbsent(key,1,30,TimeUnit.MILLISECONDS)
- 看业务应用情况还有更多问题,可以看Redis6课程 或者 第一个高并发大课
- 业务超时,如何避免其他线程勿删
- 业务执行时间过长,如何实现锁的自动续期
- ...更多问题
-
之前说的方案二,消费者端生成短链码code
-
那C端或者B端其中一个加锁成功后,另外一个怎么加锁?
-
答案:通过value判断是否是同个账号,如果是则认为是加锁成功
-
即下面步骤
-
C端生成
-
加锁key=code,value=account
- 查询数据库,如果存在,则ver版本递增,重新生成短链码
- 保存数据库
-
解分布式锁(锁过期自动解锁)
-
B端生成
- 加锁key=code,value=account
- 查询数据库,如果存在,则ver版本递增,重新生成短链码
- 保存数据库
- 解分布式锁(锁过期自动解锁)
![](img/image-20211219192217435.png#alt=image-20211219192217435)
- 流程:加锁的方式需要保证原子性
- 先判断是否有,如没这个key,则设置key-value,配置过期时间,加锁成功
- 如果有这个key,判断value是否是同个账号,是同个账号则返回加锁成功
- 如果不是同个账号则加锁失败
- 解决方式,配置key过期时间久,比如2~5天
<a name="b298dcc0"></a>
#### 第5集 Lua脚本分布式重入锁+redis原生代码编写
**简介:Lua脚本分布式重入锁+redis原生代码编写**
- 前面说了redis做分布式锁存在的问题
- 核心是保证多个指令原子性,加锁使用setnx setex 可以保证原子性,那解锁使用判断和设置等怎么保证原子性
- 文档:[http://www.redis.cn/commands/set.html](http://www.redis.cn/commands/set.html)
- 多个命令的原子性:采用 lua脚本+redis, 由于【判断和删除】是lua脚本执行,所以要么全成功,要么全失败
- 流程
- 先判断是否有,如没这个key,则设置key-value,配置过期时间,加锁成功
- 如果有这个key,判断value是否是同个账号,是同个账号则返回加锁成功
- 如果不是同个账号则加锁失败 ```
- 代码
//key1是短链码,ARGV[1]是accountNo,ARGV[2]是过期时间
String script = "if redis.call('EXISTS',KEYS[1])==0 then redis.call('set',KEYS[1],ARGV[1]); redis.call('expire',KEYS[1],ARGV[2]); return 1;" +
" elseif redis.call('get',KEYS[1]) == ARGV[1] then return 2;" +
" else return 0; end;";
Long result = redisTemplate.execute(new
DefaultRedisScript<>(script, Long.class), Arrays.asList(code), value,100);
- 加入redis配置
#-------redis连接配置-------
spring.redis.client-type=jedis
spring.redis.host=120.79.150.146
spring.redis.password=xdclass.net
spring.redis.port=6379
spring.redis.jedis.pool.max-active=100
spring.redis.jedis.pool.max-idle=100
spring.redis.jedis.pool.min-idle=100
spring.redis.jedis.pool.max-wait=60000
第6集 短码服务冗余双写-B端+C端分布式锁代码整合
简介:短码服务冗余双写-B端+C端分布式锁代码整合
- 整合编码实战
愿景:”让编程不再难学,让技术与生活更加有趣”
更多架构课程请访问 xdclass.net
第二十八章 短链服务-冗余双写B端分库分表和链路测试
第1集 GroupCodeMapping表分库分表配置实战
简介: GroupCodeMapping表分库分表配置实战
数据量预估
- 首年日活用户: 10万
- 首年日新增短链数据:10万*50 = 500万
- 年新增短链数:500万 * 365天 = 18.2亿
- 往高的算就是100亿,支撑3年
分库分表策略
分库分表
- 8个库, 每个库128个表,总量就是 1024个表
- 本地开发 2库,每个库2个表
分片键:
- 分库PartitionKey:account_no
- 分表PartitionKey:group_id
接口访问量
- C端解析,访问量大
- B端查询,访问量少,单个表的存储数据可以多点
- 配置
##---------- 组+短链码mapping表,策略:分库+分表--------------
# 先进行水平分库,然后再水平分表, 水平分库策略,行表达式分片
spring.shardingsphere.sharding.tables.group_code_mapping.database-strategy.inline.sharding-column=account_no
spring.shardingsphere.sharding.tables.group_code_mapping.database-strategy.inline.algorithm-expression=ds$->{account_no % 2}
# 分表策略+行表达式分片
spring.shardingsphere.sharding.tables.group_code_mapping.actual-data-nodes=ds$->{0..1}.group_code_mapping_$->{0..1}
spring.shardingsphere.sharding.tables.group_code_mapping.table-strategy.inline.sharding-column=group_id
spring.shardingsphere.sharding.tables.group_code_mapping.table-strategy.inline.algorithm-expression=group_code_mapping_$->{group_id % 2}
第2集 短链服务-冗余双写架构全链路测试实战
简介: 短链服务-冗余双写架构全链路测试实战
全链路测试
- 数据库检查
- 创建短链
{
"groupId":1468878230818746370,
"title":"全链路测试测试标题",
"originalUrl":"https://xdclass.net",
"domainId":1,
"domainType":"OFFICIAL",
"expired":-1
}
第3集 短链服务-B端接口-分页查找短链开发实战
简介: 短链服务-B端接口-分页查找短链开发实战
- 分页查找某个分组下的短链数据
@Data
public class ShortLinkPageRequest {
private int page;
private int size;
private long groupId;
}
/**
* 分页查找短链
*
* @return
*/
@RequestMapping("page")
public JsonData pageShortLinkByGroupId(@RequestBody ShortLinkPageRequest request) {
Map<String, Object> pageResult = shortLinkService.pageShortLinkByGroupId(request);
return JsonData.buildSuccess(pageResult);
}
注意点
- IDEA可能有缓存,导致分库分表不生效,mvn clean清理下
删除标记位增加
- 解析
- 查找
愿景:”让编程不再难学,让技术与生活更加有趣”
更多架构课程请访问 xdclass.net
第二十九章 短链服务-冗余双写架构删除和更新开发实战
第1集 短链服务-冗余双写架构举一反三的能力应用
简介: 冗余双写架构举一反三的能力应用
- 完成了短链接口的新增,采用了冗余双写的方式
那其他接口呢
- 删除
- 更新
第2集 短链服务-删除和更新Controller层开发实战
简介: 短链服务-删除和更新Controller层开发实战
删除接口
- controller
service
- 构建消息
@Data
public class ShortLinkDelRequest {
/**
* 组
*/
private Long groupId;
/**
* groupCodeMapping映射id
*/
private Long mappingId;
/**
* 短链码
*/
private String code;
}
更新接口
- controller
service
- 构建消息
public class ShortLinkUpdateRequest {
/**
* 组
*/
private Long groupId;
/**
* groupCodeMapping映射id
*/
private Long mappingId;
/**
* 短链码
*/
private String code;
/**
* 短链标题
*/
private String title;
/**
* 域名id
*/
private Long domainId;
/**
* 域名类型
*/
private String domainType;
}
- 消息类型
public enum EventMessageType {
/**
* 短链创建
*/
SHORT_LINK_ADD,
/**
* 短链创建 C端
*/
SHORT_LINK_ADD_LINK,
/**
* 短链创建 B端
*/
SHORT_LINK_ADD_MAPPING,
/**
* 更新创建
*/
SHORT_LINK_UPDATE,
/**
* 更新 C端
*/
SHORT_LINK_UPDATE_API,
/**
* 更新 B端
*/
SHORT_LINK_UPDATE_MAPPING,
/**
* 删除
*/
SHORT_LINK_DEL,
/**
* 删除 C端
*/
SHORT_LINK_DEL_API,
/**
* 删除 B端
*/
SHORT_LINK_DEL_MAPPING,
}
第3集 冗余双写MQ实现-删除短链-交换机和队列绑定配置实战
简介: 冗余双写MQ架构实现-删除短链-交换机和队列绑定配置讲解
- 删除短链MQ架构图
- 配置文件
第4集 冗余双写MQ实现-更新短链-交换机和队列绑定配置实战
简介: 冗余双写MQ架构实现-更新短链-交换机和队列绑定配置讲解
- 更新短链MQ架构图
- 配置文件
第5集 短链服务-删除和更新模块发送MQ消息验证
简介: 短链服务-删除和更新模块发送MQ消息验证
- 删除 发送消息
- 更新 发送消息
- RabbitMQ控制台查看
愿景:”让编程不再难学,让技术与生活更加有趣”
更多架构课程请访问 xdclass.net
第三十章 短链服务-冗余双写架构删除和更新消费者开发实战
第1集 冗余双写架构-更新短链消费者开发实战
简介: 短链服务-更新短链-消费者开发实战
第2集 冗余双写架构-更新短链消费者链路测试
简介: 冗余双写架构-更新短链消费者链路测试
第3集 冗余双写架构-删除短链消费者开发实战
简介: 短链服务-删除短链-消费者开发实战
第4集 冗余双写架构-删除短链消费者链路测试和越权修复
简介: 冗余双写架构-删除短链消费者链路测试
对数据库操作的时候,一定要防止越权
- 一个是直接RPC调用
- 一个是通过MQ调用
第5集 冗余双写架构-短链服务开发总结
简介: 冗余双写架构-短链服务开发总结
- 短链服务器CRUD
冗余双写架构
- 异常队列
- 最终一致性
愿景:”让编程不再难学,让技术与生活更加有趣”
更多架构课程请访问 xdclass.net
第三十一章 流量包商品服务需求和库表讲解
第1集 流量包商品服务-业务需求讲解
简介: 流量包商品服务需求讲解
流量包需求讲解
- 百度短链案例 https://dwz.cn/console/price
- 小滴云短链平台商品
流量包商品模型
- 每个套餐都是一个虚拟商品,没库存限制
- 免费版是新用户注册即可获得
- 不同的商品每天限制的创建的条数不一样
- 用户可以叠加使用多个流量包
业务难点
- 流量包购买支付(P2)
流量包管理
- 免费流量包管理(P1)
- 付费流量包管理(P1)
- 短链业务-流量包业务联动(P1)
第2集 流量包商品服务-数据库表介绍和实体类生成
简介: 流量包商品服务-数据库表介绍
- 数据库表介绍
CREATE TABLE `product` (
`id` bigint NOT NULL,
`title` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '商品标题',
`detail` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '详情',
`img` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '图片',
`level` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '产品层级:FIRST青铜、SECOND黄金、THIRD钻石',
`old_amount` decimal(16,0) DEFAULT NULL COMMENT '原价',
`amount` decimal(16,0) DEFAULT NULL COMMENT '现价',
`plugin_type` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '工具类型 short_link、qrcode',
`day_times` int DEFAULT NULL COMMENT '日次数:短链类型',
`total_times` int DEFAULT NULL COMMENT '总次数:活码才有',
`valid_day` int DEFAULT NULL COMMENT '有效天数',
`gmt_modified` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`gmt_create` datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
- 数据库创建
- 插入初始化数据
INSERT INTO `dcloud_shop`.`product` (`id`, `title`, `detail`, `img`, `level`, `old_amount`, `amount`, `plugin_type`, `day_times`, `total_times`, `valid_day`, `gmt_modified`, `gmt_create`) VALUES (1, '青铜会员-默认', '数据查看支持||日生成短链{{dayTimes}}次||限制跳转50次||默认域名', NULL, 'FIRST', 19, 0, 'SHORT_LINK', 2, NULL, 1, '2021-10-14 17:33:44', '2021-10-11 10:49:35');
INSERT INTO `dcloud_shop`.`product` (`id`, `title`, `detail`, `img`, `level`, `old_amount`, `amount`, `plugin_type`, `day_times`, `total_times`, `valid_day`, `gmt_modified`, `gmt_create`) VALUES (2, '黄金会员-月度', '数据查看支持||日生成短链{{dayTimes}}次||限制不限制||默认域名', NULL, 'SECOND', 99, 1, 'SHORT_LINK', 5, NULL, 30, '2021-10-19 14:36:28', '2021-10-11 10:57:47');
INSERT INTO `dcloud_shop`.`product` (`id`, `title`, `detail`, `img`, `level`, `old_amount`, `amount`, `plugin_type`, `day_times`, `total_times`, `valid_day`, `gmt_modified`, `gmt_create`) VALUES (3, '黑金会员-月度', '数据查看支持||日生成短链{{dayTimes}}次||限制不限制||自定义域名', NULL, 'THIRD', 199, 2, 'SHORT_LINK', 8, NULL, 30, '2021-10-19 14:36:30', '2021-10-11 11:01:13');
- MybatisPlus实体类生成
第3集 流量包商品服务-项目基本骨架创建
简介: 流量包商品服务-项目基本骨架创建
- 配置文件增加和默认库表配置
server.port=8005
spring.application.name=dcloud-shop
#服务注册发现
spring.cloud.nacos.discovery.server-addr=120.79.150.146:8848
spring.cloud.nacos.discovery.username=nacos
spring.cloud.nacos.discovery.password=nacos
spring.shardingsphere.datasource.names=ds1
spring.shardingsphere.datasource.ds0.type=com.zaxxer.hikari.HikariDataSource
spring.shardingsphere.datasource.ds0.jdbc-url=jdbc:mysql://120.79.150.146:3306/dcloud_shop?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
spring.shardingsphere.datasource.ds0.driver-class-name=com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.ds0.username=root
spring.shardingsphere.datasource.ds0.password=xdclass.net168
spring.shardingsphere.datasource.ds0.connectionTimeoutMilliseconds=30000
spring.shardingsphere.datasource.ds0.idleTimeoutMilliseconds=60000
spring.shardingsphere.datasource.ds0.maintenanceIntervalMilliseconds=30000
spring.shardingsphere.datasource.ds0.maxLifetimeMilliseconds=1800000
spring.shardingsphere.datasource.ds0.maxPoolSize=50
spring.shardingsphere.datasource.ds0.minPoolSize=50
spring.shardingsphere.props.sql.show=true
logging.level.root=INFO
启动类配置
controller-service-manager层建立
第4集 流量包商品服务-商品列表和详情接口链路开发
简介: 流量包商品服务-商品列表和详情接口链路开发
- 商品列表接口开发
- 商品详情接口开发
愿景:”让编程不再难学,让技术与生活更加有趣”
更多架构课程请访问 xdclass.net
第三十二章 流量包订单模块需求讲解和库表介绍
第1集 流量包订单模块-业务需求讲解
简介: 流量包订单模块-业务需求讲解
- 流量包订单需求讲解
需求
支持叠加购买
- 效果:1个流量包支持1天创建50条,如果买了两个则支持1天创建100条
支持多渠道支付
- 支付宝、微信等
- 开发对接微信V3最新版支付,支付宝支付的参考第一个高并发项目大课
V2版微信支付对接可以看
PC端支付宝支付可以看
第2集 流量包订单-数据库表介绍和实体类生成
简介: 流量包订单-数据库表介绍和实体类生成
- 数据库表
CREATE TABLE `product_order` (
`id` bigint NOT NULL,
`product_id` bigint DEFAULT NULL COMMENT '订单类型',
`product_title` varchar(64) DEFAULT NULL COMMENT '商品标题',
`product_amount` decimal(16,2) DEFAULT NULL COMMENT '商品单价',
`product_snapshot` varchar(2048) DEFAULT NULL COMMENT '商品快照',
`buy_num` int DEFAULT NULL COMMENT '购买数量',
`out_trade_no` varchar(64) DEFAULT NULL COMMENT '订单唯一标识',
`state` varchar(11) DEFAULT NULL COMMENT 'NEW 未支付订单,PAY已经支付订单,CANCEL超时取消订单',
`create_time` datetime DEFAULT NULL COMMENT '订单生成时间',
`total_amount` decimal(16,2) DEFAULT NULL COMMENT '订单总金额',
`pay_amount` decimal(16,2) DEFAULT NULL COMMENT '订单实际支付价格',
`pay_type` varchar(64) DEFAULT NULL COMMENT '支付类型,微信-银行-支付宝',
`nickname` varchar(64) DEFAULT NULL COMMENT '账号昵称',
`account_no` bigint DEFAULT NULL COMMENT '用户id',
`del` int DEFAULT '0' COMMENT '0表示未删除,1表示已经删除',
`gmt_modified` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`gmt_create` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`bill_type` varchar(32) DEFAULT NULL COMMENT '发票类型:0->不开发票;1->电子发票;2->纸质发票',
`bill_header` varchar(200) DEFAULT NULL COMMENT '发票抬头',
`bill_content` varchar(200) DEFAULT NULL COMMENT '发票内容',
`bill_receiver_phone` varchar(32) DEFAULT NULL COMMENT '发票收票人电话',
`bill_receiver_email` varchar(200) DEFAULT NULL COMMENT '发票收票人邮箱',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_query` (`out_trade_no`,`account_no`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
- 数据库实体类生成
第3集 流量包订单-数据库表分库分表讲解和配置
简介: 流量包订单-数据库表分库分表讲解和配置
业务需求
- 用户查看自己的订单列表
数据存储需求(都是前期规划,上线前可以调整分库分表策略和数量)
- 未来2年,短链平台累计5百万用户
付费流包记录:
- 一个用户10条/年,总就是5千万条/年,两年是1亿
- 单表不超过1千万数据,需要分10张表
- 进一步延伸,进行水平分表,比如 2张表、4张表、8张 表、16张表
- 分表数:线上分16张表,本地分2张表即可
分片key
- account_no作为partitionKey
#----------配置默认数据库,比如短链域名,不分库分表--------------
spring.shardingsphere.sharding.default-data-source-name=ds0
#默认id生成策略
spring.shardingsphere.sharding.default-key-generator.column=id
spring.shardingsphere.sharding.default-key-generator.type=SNOWFLAKE
spring.shardingsphere.sharding.default-key-generator.props.worker.id=${workerId}
# 指定product_order表的数据分布情况,配置数据节点,行表达式标识符使用 ${...} 或 $->{...},但前者与 Spring 本身的文件占位符冲突,所以在 Spring 环境中建议使用 $->{...}
spring.shardingsphere.sharding.tables.product_order.actual-data-nodes=ds0.product_order_$->{0..1}
#水平分表策略+行表达式分片
spring.shardingsphere.sharding.tables.product_order.table-strategy.inline.algorithm-expression=product_order_$->{ account_no % 2 }
spring.shardingsphere.sharding.tables.product_order.table-strategy.inline.sharding-column=account_no
第4集 流量包订单-Manager层CRUD接口开发
简介: 流量包订单-Manager层CRUD接口开发
- 开发Manager层CRUD接口
第5集 流量包订单-基础分表Manager层单元测试
简介: 流量包订单-基础分表Manager层单元测试
- 单元测试
愿景:”让编程不再难学,让技术与生活更加有趣”
更多架构课程请访问 xdclass.net
第三十三章 流量包商品服务-下单模块开发和订单防重提交
第1集 流量包商品服务-订单controller相关接口开发
简介: 商品服务-订单controller相关接口开发
- 分页接口开发
- 订单状态查询接口(扫码支付-订单状态检查)
下单接口(controller骨架)
- 支付宝支付返回HTML
- 微信支付返回JSON
第2集 流量包商品服务-流量包下单模块链路流程分析讲解
简介: 商品服务-订单下单模块链路流程分析讲解
不涉及到商品库存管理
- 防重提交(重点)
- 获取最新的流量包价格
订单验价
- 如果有优惠券或者其他抵扣
- 验证前端显示和后台计算价格
- 创建订单对象保存数据库
- 发送延迟消息-用于自动关单(重点)
- 创建支付信息-对接三方支付(重点)
- 回调更新订单状态(重点)
- 支付成功创建流量包(重点)
第3集 流量包商品服务-下单接口链路骨架开发
简介: 商品服务-流量包下单接口链路骨架开发
业务流程
- 重防提交(TODO)
- 获取最新的流量包价格
订单验价
- 如果有优惠券或者其他抵扣
- 验证前端显示和后台计算价格
- 创建订单对象保存数据库
- 发送延迟消息-用于自动关单(TODO)
- 创建支付信息-对接三方支付(TODO)
- 回调更新订单状态(TODO)
- 支付成功创建流量包(TODO)
- 代码开发
第4集 流量包商品服务-避免重复下单你能想到几种方式
简介:避免复下单常见解决方案
开发的项目中可能会出现下面这些情况:
- 前端下单按钮复点击导致订单创建多次
- 网速等原因造成页面卡顿,用户重复刷新提交请求
- 黑客或恶意用户使用postman等http工具重复恶意提交表单
问题
- 会导致表单重复提交,造成数据重复或者错乱
- 核心接口的请求增加,消耗服务器负载,严重甚至会造成服务器宕机
因此核心接口需要做防重提交,你能想到几种方式
方式一:前端JS控制点击次数,屏蔽点击按钮无法点击
- 前端可以被绕过,前端有限制,后端也需要有限制
方式二:数据库或者其他存储增加唯一索引约束
- 需要想出满足业务需求的唯一索引约束,比如注册的手机号唯一
方式三:服务端token令牌方式
- 下单前先获取令牌-存储redis 下单时一并把token提交并检验和删除-lua脚本
- 分布式情况下,采用Lua脚本进行操作
String script = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";
方式三 是大家采用的最多的,那有没更加优雅的方式呢?
- 采用自定义注解,也有多种方式
愿景:”让编程不再难学,让技术与生活更加有趣”
更多架构课程请访问 xdclass.net
第三十四章 流量包商品服务-多场景自定义注解防重提交实战
第1集 java核心知识-教你掌握自定义注解
简介:java核心知识-自定义注解
Annotation(注解)
- 从JDK 1.5开始, Java增加了对元数据(MetaData)的支持,也就是 Annotation(注解)。
- 注解其实就是代码里的特殊标记,它用于替代配置文件
- 常见的很多 @Override 、@Deprecated 等
什么是元注解
- 注解的注解,比如当我们需要自定义注解时
- 会需要一些元注解(meta-annotation),如@Target 和@Retention
java内置4种元注解
@Target 表示该注解用于什么地方
- ElementType.CONSTRUCTOR 用在构造器
- ElementType.FIELD 用于描述域-属性上
- ElementType.METHOD 用在方法上
- ElementType.TYPE 用在类或接口上
- ElementType.PACKAGE 用于描述包
@Retention 表示在什么级别保存该注解信息
- RetentionPolicy.SOURCE 保留到源码上
- RetentionPolicy.CLASS 保留到字节码上
- RetentionPolicy.RUNTIME 保留到虚拟机运行时(最多,可通过反射获取)
@Documented 将此注解包含在 javadoc 中
@Inherited 是否允许子类继承父类中的注解
用来声明一个注解,可以通过default来声明参数的默认值
自定义注解时,自动继承了java.lang.annotation.Annotation接口
通过反射可以获取自定义注解
第2集 AOP+自定义注解-接口防重提交多场景设计
简介: AOP+自定义注解接口防重提交多场景设计
防重提交方式
- token令牌方式
- ip+类+方法方式
利用AOP
Aspect Oriented Program 面向切面编程, 在不改变原有逻辑上增加额外的功能
AOP思想把功能分两个部分,分离系统中的各种关注点
好处
- 减少代码侵入,解耦
- 可以统一处理横切逻辑
- 方便添加和删除横切逻辑
- 业务流程
- 自定义注解
import java.lang.annotation.*;
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RepeatSubmit {
/**
* 加锁过期时间,默认是5秒
* @return
*/
long lockTime() default 5;
/**
* 默认限制类型,是方法参数
* @return
*/
Type limitType() default Type.PARAM;
/**
* 两种类型,token 或者 param
*/
enum Type{ PARAM , TOKEN};
}
第3集 订单防重提交-自定义注解开发实战-Token令牌方式
简介:订单防重提交-自定义注解开发实战-Token令牌方式
- 新增redis配置
#-------redis连接配置-------
spring.redis.client-type=jedis
spring.redis.host=120.79.150.146
spring.redis.password=xdclass.net
spring.redis.port=6379
spring.redis.jedis.pool.max-active=100
spring.redis.jedis.pool.max-idle=100
spring.redis.jedis.pool.min-idle=100
spring.redis.jedis.pool.max-wait=60000
- 编写接口获取令牌
@Autowired
private StringRedisTemplate redisTemplate;
@GetMapping("token")
public JsonData getToken(){
LoginUser loginUser = LoginInterceptor.threadLocal.get();
String token = CommonUtil.getStringNumRandom(32);
//"order:submit:%s:%s"
String key = String.format(RedisKey.SUBMIT_ORDER_TOKEN_KEY, loginUser.getAccountNo(),requestToken);
redisTemplate.opsForValue().set(key, "1", 30, TimeUnit.MINUTES);
return JsonData.buildSuccess(token);
}
- 定义切面类-开发解析器
/**
* 定义 @Pointcut注解表达式,
* 方式一:@annotation:当执行的方法上拥有指定的注解时生效(我们采用这)
* 方式二:execution:一般用于指定方法的执行
*
* @param repeatSubmit
*/
@Pointcut("@annotation(repeatSubmit)")
public void pointcutNoRepeatSubmit(RepeatSubmit repeatSubmit) {
}
/**
* 环绕通知, 围绕着方法执行
* @Around 可以用来在调用一个具体方法前和调用后来完成一些具体的任务。
*
* 方式一:单用 @Around("execution(* net.xdclass.controller.*.*(..))")可以
* 方式二:用@Pointcut和@Around联合注解也可以(我们采用这个)
*
*
* 两种方式
* 方式一:加锁 固定时间内不能重复提交
* <p>
* 方式二:先请求获取token,这边再删除token,删除成功则是第一次提交
*
* @param joinPoint
* @param noRepeatSubmit
* @return
* @throws Throwable
*/
@Around("pointcutNoRepeatSubmit(noRepeatSubmit)")
public Object around(ProceedingJoinPoint joinPoint, RepeatSubmit noRepeatSubmit) throws Throwable {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
boolean res;
String type = noRepeatSubmit.limitType().name();
if (type.equals(RepeatSubmit.Type.PARAM.name())) {
//方式一方法参数 TODO
} else {
//方式二,令牌形式
String requestToken = request.getHeader("request-token");
if (StringUtils.isBlank(requestToken)) {
throw new BizException(BizCodeEnum.ORDER_CONFIRM_TOKEN_EQUAL_FAIL);
}
LoginUser loginUser = LoginInterceptor.threadLocal.get();
//"order:submit:%s:%s"
String key = String.format(RedisKey.SUBMIT_ORDER_TOKEN_KEY, loginUser.getAccountNo(),requestToken);
/**
* 提交表单的token key
* 方式一:不用lua脚本获取再判断,之前是因为 key组成是 order:submit:accountNo, value是对应的token,所以需要先获取值,再判断
* 方式二:可以直接key是 order:submit:accountNo:token,然后直接删除成功则完成
*/
res = stringRedisTemplate.delete(key);
}
if (!res) {
throw new BizException(BizCodeEnum.ORDER_CONFIRM_REPEAT);
}
System.out.println("目标方法执行前");
Object object = joinPoint.proceed();
System.out.println("目标方法执行后");
return object;
}
第4集 Spring里面的AOP常见概念复习巩固
简介:Spring里面的AOP常见概念复习巩固
能否解释下AOP里面常见的概念,比如 横切、通知、连接点、切入点、切面 ?
横切关注点
- 对哪些方法进行拦截,拦截后怎么处理,这些就叫横切关注点
- 比如 权限认证、日志、事物
通知 Advice
- 在特定的切入点上执行的增强处理
- 做啥? 比如你需要记录日志,控制事务 ,提前编写好通用的模块,需要的地方直接调用
- 比如重复提交判断逻辑
类型
@Before前置通知
- 在执行目标方法之前运行
@After后置通知
- 在目标方法运行结束之后
@AfterReturning返回通知
- 在目标方法正常返回值后运行
@AfterThrowing异常通知
- 在目标方法出现异常后运行
@Around环绕通知
- 在目标方法完成前、后做增强处理 ,环绕通知是最重要的通知类型 ,像事务,日志等都是环绕通知,注意编程中核心是一个ProceedingJoinPoint,需要手动执行 joinPoint.procced()
连接点 JointPoint
- 要用通知的地方,业务流程在运行过程中需要插入切面的具体位置,
- 一般是方法的调用前后,全部方法都可以是连接点
- 只是概念,没啥特殊
切入点 Pointcut
- 不能全部方法都是连接点,通过特定的规则来筛选连接点, 就是Pointcut,选中那几个你想要的方法
- 在程序中主要体现为书写切入点表达式(通过通配、正则表达式)过滤出特定的一组 JointPoint连接点
- 过滤出相应的 Advice 将要发生的joinpoint地方
切面 Aspect
- 通常是一个类,里面定义 切入点+通知 , 定义在什么地方; 什么时间点、做什么事情
- 通知 advice指明了时间和做的事情(前置、后置等)
- 切入点 pointcut 指定在什么地方干这个事情
- web接口设计中,web层->网关层->服务层->数据层,每一层之间也是一个切面,对象和对象,方法和方法之间都是一个个切面
目标 target
- 目标类,真正的业务逻辑,可以在目标类不知情的条件下,增加新的功能到目标类的链路上
织入 Weaving
- 把切面(某个类)应用到目标函数的过程称为织入
第5集 订单防重提交-自定义注解-Token方式效果验证
简介:订单防重提交-自定义注解开发-Token式效果验证
- 下单接口增加注解
@RepeatSubmit(limitType = RepeatSubmit.Type.TOKEN)
- 获取令牌
- 其他bug修复
第6集 订单防重提交-自定义注解开发实战-参数式
简介:订单防重提交-自定义注解开发实战
- 开发配置
//方式一方法参数
long lockTime = noRepeatSubmit.lockTime();
String ip = CommonUtil.getIpAddr(request);
//获取注解
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
//目标类、方法
String className = method.getDeclaringClass().getName();
String name = method.getName();
String key = String.format("%s#%s#%s#%s",accountNo,ip, className, name);
log.info("key={}", key);
// 分布式锁 TODO, 直接设置key配置过期时间也行
分布式锁几种实现
- Redis+Lua脚本
- Redission
- 直接redis操作
使用原子命令:设置和配置过期时间 setnx / setex
如: set key 1 ex 30 nx
java代码里面
redisTemplate.opsForValue().setIfAbsent(key,1,30,TimeUnit.MILLISECONDS)
第7集 Redission分布式锁介绍和配置引入
简介:Redission分布式锁介绍和配置引入
Redission介绍
- 是一个在Redis的基础上实现的Java驻内存数据网格,支持多样Redis配置支持、丰富连接方式、分布式对象、分布式集合、分布式锁、分布式服务、多种序列化方式、三方框架整合
- Redisson底层采用的是Netty 框架
- 官方文档:https://github.com/redisson/redisson
- 配置加入
聚合工程锁定版本,common项目添加依赖(多个服务都会用到分布式锁)
<!--分布式锁-->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.10.1</version>
</dependency>
- 代码配置
@Value("${spring.redis.host}")
private String redisHost;
@Value("${spring.redis.port}")
private String redisPort;
@Value("${spring.redis.password}")
private String redisPwd;
/**
* 配置分布式锁的redisson
* @return
*/
@Bean
public RedissonClient redissonClient(){
Config config = new Config();
//单机方式
config.useSingleServer().setPassword(redisPwd).setAddress("redis://"+redisHost+":"+redisPort);
//集群
//config.useClusterServers().addNodeAddress("redis://192.31.21.1:6379","redis://192.31.21.2:6379")
RedissonClient redissonClient = Redisson.create(config);
return redissonClient;
}
/**
* 集群模式
* 备注:可以用"rediss://"来启用SSL连接
*/
/*@Bean
public RedissonClient redissonClusterClient() {
Config config = new Config();
config.useClusterServers().setScanInterval(2000) // 集群状态扫描间隔时间,单位是毫秒
.addNodeAddress("redis://127.0.0.1:7000")
.addNodeAddress("redis://127.0.0.1:7002");
RedissonClient redisson = Redisson.create(config);
return redisson;
}*/
- 加锁调用
// 分布式锁
RLock lock = redissonClient.getLock(key);
// 尝试加锁,最多等待2秒,上锁以后5秒自动解锁 [lockTime默认为5s, 可以自定义]
res = lock.tryLock(2, lockTime, TimeUnit.SECONDS);
第8集 订单防重提交-自定义注解开发-参数式效果验证
简介:订单防重提交-自定义注解开发-参数式效果验证
效果验证
两种防重提交,应用场景不一样,也可以更多方式进行防重
【海大下期更新内容,接下去很多需要用到特定的参数,盗版无法实操和维护】
个人接入微信支付V3版参数(临时参数,同学用的时候会失效 ,群公告查看最新)
- 公众号:appid: wx5beac15ca207c40c
商户平台
- 商户号:1601644442
- APIv3密钥:peYcTwRF581UOdaUqoPOeHzJ8FgHgsnJ
- 证书序列号:7064ADC5FE84CA2A3DDE71A692E39602DEB96E61
- 证书文件:本章本集的资料里面(不可用,最新的群公告获取)
注意【参数每周都更新】
主讲最新微信支付V3版本,盗版【没法实操】接下去的全链路内容
联系客服凭海量数据项目大课订单号,进小滴课堂海量数据大课学习群
- 【群公告】那边有证书和相关参数信息,只要大家进群,就能获取最新的参数
- 我们有多个海大学习群,进其中一个即可
- 参数(每周会调整一次,盗版人员没法维护好)
- 证书
- 证书序列号
- 支付秘钥
- 商户号
- ......更多
大家如果下单失败记得及时【群公告】里查看最新的即可
目的
- 尊重原创付费视频,打击盗版没法实操
- 项目大课是市面上独一无二的课程,也是保护防止盗版不滥于市场
如果发现你买了盗版,帮我举报。联系我这边大课会有特殊折扣优惠
- 正版好处多,资料-视频齐全、群里讨论-学习氛围都是盗版给不了的,学的放心
- 看视频的都是学习的同学,小的付出带来涨薪和技术增长,6分之1以上月薪即可回本 愿景:”让编程不再难学,让技术与生活更加有趣”
更多架构课程请访问 xdclass.net
第三十五章 下单接口-超时关闭订单功能设计+延迟队列实战
第1集 订单模块下单接口-支付超时订单链路设计
简介:订单模块下单接口-支付超时订单链路设计
需求背景
原因一:第三方支付平台的支付连接都是有时效性,创建订单后,需要再一定的时间内支付完成
- 微信支付、支付宝支付等
- 也可以不关闭订单,做订单二次支付的操作,但业务链路会更加复杂
- 原因二:电商业务里面还会涉及到商品库存的锁定和释放
- 所以多数订单业务都是会有这个功能,那如何设计呢?
第2集 RabbitMQ死信队列-延迟消息知识点回顾
简介:RabbitMQ死信队列-延迟消息知识点回顾
什么是rabbitmq的死信队列
- 没有被及时消费的消息存放的队列
什么是rabbitmq的死信交换机
- Dead Letter Exchange(死信交换机,缩写:DLX)当消息成为死信后,会被重新发送到另一个交换机,这个交换机就是DLX死信交换机。
消息有哪几种情况成为死信
- 消费者拒收消息(basic.reject/ basic.nack),并且没有重新入队 requeue=false
- 消息在队列中未被消费,且超过队列或者消息本身的过期时间TTL(time-to-live)
- 队列的消息长度达到极限
- 结果:消息成为死信后,如果该队列绑定了死信交换机,则消息会被死信交换机重新路由到死信队列
什么是延迟队列
- 一种带有延迟功能的消息队列,Producer 将消息发送到消息队列 服务端,但并不期望这条消息立马投递,而是推迟到在当前时间点之后的某一个时间投递到 Consumer 进行消费,该消息即定时消息
业界的一些实现方式
- 定时任务高精度轮训
- 采用RocketMQ自带延迟消息功能
RabbitMQ本身是不支持延迟队列的,怎么办?
- 结合死信队列的特性,就可以做到延迟消息
第3集 超时关单-RabbitMQ实现延迟消息配置实战
简介:超时关单-RabbitMQ实现延迟消息配置实战
- 数据流转
- 死信队列配置
/**
* 小滴课堂,愿景:让技术不再难学
*
* 自定义消息队列配置,
* 发送 关单消息-》延迟exchange-》order.close.delay.queue-》死信exchange-》order.close.queue
*
* @Description
* @Author 二当家小D
* @Remark 有问题直接联系我,源码-笔记-技术交流群
* @Version 1.0
**/
@Configuration
@Data
public class RabbitMQConfig {
/**
* 交换机
*/
private String orderEventExchange="order.event.exchange";
/**
* 延迟队列, 不能被监听消费
*/
private String orderCloseDelayQueue="order.close.delay.queue";
/**
* 关单队列, 延迟队列的消息过期后转发的队列,被消费者监听
*/
private String orderCloseQueue="order.close.queue";
/**
* 进入延迟队列的路由key
*/
private String orderCloseDelayRoutingKey="order.close.delay.routing.key";
/**
* 进入死信队列的路由key,消息过期进入死信队列的key
*/
private String orderCloseRoutingKey="order.close.routing.key";
/**
* 过期时间 毫秒,临时改为1分钟定时关单
*/
private Integer ttl=1000*60;
/**
* 消息转换器
* @return
*/
@Bean
public MessageConverter messageConverter(){
return new Jackson2JsonMessageConverter();
}
/**
* 创建交换机 Topic类型,也可以用dirct路由
* 一般一个微服务一个交换机
* @return
*/
@Bean
public Exchange orderEventExchange(){
return new TopicExchange(orderEventExchange,true,false);
}
/**
* 延迟队列
*/
@Bean
public Queue orderCloseDelayQueue(){
Map<String,Object> args = new HashMap<>(3);
args.put("x-dead-letter-exchange",orderEventExchange);
args.put("x-dead-letter-routing-key",orderCloseRoutingKey);
args.put("x-message-ttl",ttl);
return new Queue(orderCloseDelayQueue,true,false,false,args);
}
/**
* 死信队列,普通队列,用于被监听
*/
@Bean
public Queue orderCloseQueue(){
return new Queue(orderCloseQueue,true,false,false);
}
/**
* 第一个队列,即延迟队列的绑定关系建立
* @return
*/
@Bean
public Binding orderCloseDelayBinding(){
return new Binding(orderCloseDelayQueue,Binding.DestinationType.QUEUE,orderEventExchange,orderCloseDelayRoutingKey,null);
}
/**
* 死信队列绑定关系建立
* @return
*/
@Bean
public Binding orderCloseBinding(){
return new Binding(orderCloseQueue,Binding.DestinationType.QUEUE,orderEventExchange,orderCloseRoutingKey,null);
}
}
第4集 下单接口-发送延迟消息链路测试和Bug修复
简介:下单接口-发送延迟消息链路测试和Bug修复
- 下单接口发送延迟消息
//发送延迟消息
EventMessage eventMessage = EventMessage.builder()
.eventMessageType(EventMessageType.RPODUCT_ORDER_NEW.name())
.accountNo(loginUser.getAccountNo())
.bizId(orderOutTradeNo).build();
rabbitTemplate.convertAndSend(rabbitMQConfig.getOrderEventExchange(),rabbitMQConfig.getOrderCloseDelayRoutingKey(),eventMessage);
- Bug修复
MQ配置 @bean注解增加
ProductOrderPayTypeEnum枚举类拼错
WECHAT_APY ->WECHAT_PAY
- RabbitMQ配置文件
##----------rabbit配置--------------
spring.rabbitmq.host=120.79.150.146
spring.rabbitmq.port=5672
#需要手工创建虚拟主机
spring.rabbitmq.virtual-host=dev
spring.rabbitmq.username=admin
spring.rabbitmq.password=password
#消息确认方式,manual(手动ack) 和auto(自动ack); 消息消费重试到达指定次数进到异常交换机和异常队列,需要改为自动ack确认消息
spring.rabbitmq.listener.simple.acknowledge-mode=auto
#开启重试,消费者代码不能添加try catch捕获不往外抛异常
spring.rabbitmq.listener.simple.retry.enabled=true
#最大重试次数
spring.rabbitmq.listener.simple.retry.max-attempts=4
# 重试消息的时间间隔,5秒
spring.rabbitmq.listener.simple.retry.initial-interval=5000
- 下单postman
{
"productId":2,
"buyNum":1,
"clientType":"PC",
"payType":"WECHAT_PAY",
"totalAmount":1,
"payAmount":1,
"billType":"NO_BILL",
"billHeader":"",
"billContent":"",
"billReceiverPhone":"",
"billReceiverEmail":""
}
- 查看RabbitMQ控制台
- 查看数据库数据
第5集 下单接口-超时关闭订单消费者骨架链路开发《上》
简介:下单接口-超时关闭订单消费者骨架链路开发《上》
- 消费者开发
@Component
@Slf4j
@RabbitListener(queuesToDeclare = {@Queue("order.close.queue")})
public class ProductOrderMQListener {
@Autowired
private ProductOrderService productOrderService;
@RabbitHandler
public void productOrderHandler(EventMessage eventMessage, Message message, Channel channel) throws IOException {
log.info("监听到消息ProductOrderMQListener message消息内容:{}", message);
try {
//TODO 处理业务逻辑
} catch (Exception e) {
//处理业务异常,还有进行其他操作,比如记录失败原因
log.error("消费失败:{}", eventMessage);
throw new BizException(BizCodeEnum.MQ_CONSUME_EXCEPTION);
}
log.info("消费成功:{}", eventMessage);
}
}
- 消费异常队列配置
@Configuration
@Slf4j
public class RabbitMQErrorConfig {
/**
* 异常交换机
*/
private String orderLinkErrorExchange = "order.error.exchange";
/**
* 异常队列
*/
private String orderLinkErrorQueue = "order.error.queue";
/**
* 异常routing.key
*/
private String orderErrorRoutingKey = "order.error.routing.key";
@Autowired
private RabbitTemplate rabbitTemplate;
/**
* 创建异常交换机
* @return
*/
@Bean
public TopicExchange errorTopicExchange(){
return new TopicExchange(orderLinkErrorExchange,true,false);
}
/**
* 创建异常队列
* @return
*/
@Bean
public Queue errorQueue(){
return new Queue(orderLinkErrorQueue,true);
}
/**
* 建立绑定关系
* @return
*/
@Bean
public Binding bindingErrorQueueAndExchange(){
return BindingBuilder.bind(errorQueue()).to(errorTopicExchange()).with(orderErrorRoutingKey);
}
/**
* 配置 RepublishMessageRecoverer
*
* 消费消息重试一定次数后,用特定的routingKey转发到指定的交换机中,方便后续排查和告警
*
* @return
*/
@Bean
public MessageRecoverer messageRecoverer(){
return new RepublishMessageRecoverer(rabbitTemplate, orderLinkErrorExchange, orderErrorRoutingKey);
}
}
第6集 下单接口-超时关闭订单消费者骨架链路开发《下》
简介:下单接口-超时关闭订单消费者骨架链路开发《下》
- Service链路开发
/**
* //延迟消息的时间 需要比订单过期 时间长一点,这样就不存在查询的时候,用户还能支付成功
*
* //查询订单是否存在,如果已经支付则正常结束
* //如果订单未支付,主动调用第三方支付平台查询订单状态
* //确认未支付,本地取消订单
* //如果第三方平台已经支付,主动的把订单状态改成已支付,造成该原因的情况可能是支付通道回调有问题,然后触发支付后的动作,如何触发?RPC还是?
* @param eventMessage
*/
@Override
public boolean closeProductOrder(EventMessage eventMessage) {
String outTradeNo = eventMessage.getBizId();
Long accountNo = eventMessage.getAccountNo();
ProductOrderDO productOrderDO = productOrderManager.findByOutTradeNoAndAccountNo(outTradeNo, accountNo);
if(productOrderDO == null){
//订单不存在
log.warn("订单不存在");
return true;
}
if(productOrderDO.getState().equalsIgnoreCase(ProductOrderStateEnum.PAY.name())){
//已经支付
log.info("直接确认消息,订单已经支付:{}",eventMessage);
return true;
}
//未支付,需要向第三方支付平台查询状态
if(productOrderDO.getState().equalsIgnoreCase(ProductOrderStateEnum.NEW.name())){
//向第三方查询状态
PayInfoVO payInfoVO = new PayInfoVO();
payInfoVO.setPayType(productOrderDO.getPayType());
payInfoVO.setOutTradeNo(outTradeNo);
payInfoVO.setAccountNo(accountNo);
//TODO 需要向第三方支付平台查询状态
String payResult = "";
if(StringUtils.isBlank(payResult)){
//如果为空,则未支付成功,本地取消订单
productOrderManager.updateOrderPayState(outTradeNo,accountNo,ProductOrderStateEnum.CANCEL.name(),ProductOrderStateEnum.NEW.name());
log.info("未支付成功,本地取消订单:{}",eventMessage);
}else {
//支付成功,主动把订单状态更新成支付
log.warn("支付成功,但是微信回调通知失败,需要排查问题:{}",eventMessage);
productOrderManager.updateOrderPayState(outTradeNo,accountNo,ProductOrderStateEnum.PAY.name(),ProductOrderStateEnum.NEW.name());
//触发支付成功后的逻辑, TODO
}
}
return true;
}
- 下单和关闭订单链路测试
愿景:”让编程不再难学,让技术与生活更加有趣”
更多架构课程请访问 xdclass.net
第三十六章 短链平台-从0教你掌握微信支付-最新V3版-准备阶段
第1集 常用的第三方支付和聚合支付平台介绍
简介:介绍常用的第三方支付和聚合支付
什么是第三方支付
- 第三方支付是指具备一定实力和信誉保障的独立机构,采用与各大银行签约的方式,通过与银行支付结算系统接口对接而促成交易双方进行交易的网络支付模式
通俗的例子:
- 支付宝,微信支付,百度钱包,PayPal(主要是欧美国家)
- 拉卡拉(中国最大线下便民金融服务提供商)
优点
- 支付平台降低了政府、企业、事业单位直连银行的成本,满足了企业专注发展在线业务的收付要求。
- 使用方便。对支付者而言,他所面对的是友好的界面,不必考虑背后复杂的技术操作过程
缺点
- 风险问题,在电子支付流程中,资金都会在第三方支付服务商处滞留即出现所谓的资金沉淀,如缺乏有效的流动性管理,则可能存在资金安全和支付的风险
- 电子支付经营资格的认知、保护和发展问题
什么是聚合支付也叫第四方支付
- 聚合支付是相对之前的第三方支付而言的,作为对第三方支付平台服务的拓展,第三方支付是介于银行和商户之间的,而聚合支付是介于第三方支付和商户之间
出现的场景
- 一堆第三方支付出现,并通过大量的钱补贴线上商家使用它们的支付,导致商户收银台堆满各种,POS机器,扫码设备,商户还需要去各家支付公司申请账号,结算等
- 聚合支付产品,其实聚合的是一种支付能力(支付宝支付、微信支付、百度钱包、ApplePay……),将这些收款能力聚合在一起,统一打包提供给电商网站或一些线下商家
解决的问题
- 聚合支付公司提供的二维码,支付多种方式支付,不再是一种,各个公司的竞争,就是支付渠道和方式的支持
第2集 微信支付-支付产品介绍-让你掌握不同场景选择
简介: 微信支付-支付产品介绍-让你掌握不同场景选择
微信支付
常用的支付方式
公众号支付:JSAPI
- 用户打开商家公众号下单,输入支付密码并完成支付后,返回商家的公众号
扫码支付
- 微信支付支持完成域名ICP备案的网站接入支付功能
- 地址: https://pay.weixin.qq.com/static/applyment_guide/applyment_detail_website.shtml
PC网站接入支付后,可以通过JSAPI支付或Native支付,自行开发生成二维码,用户使用微信“扫一扫”来完成支付
- JSAPI支付:商家张贴收款码物料,用户打开扫一扫,扫码后输入金额,完成付款
- Native支付:商家在系统中按微信支付协议生成支付二维码,用户扫码拉起微信收银台,确认并完成付款
app支付
- 用户在商家的APP中下单,跳转到微信中完成支付,支付完后跳回到商家APP内,展示支付结果,随后通过微信支付公众号下发账单消息
小程序支付
- 用户打开商家小程序下单,输入支付密码并完成支付后,返回商家小程序,随后通过微信支付公众号下发账单消息
第3集 【必看】微信支付-申请资料说明和商户平台介绍
简介:微信支付申请资料说明和商户平台介绍
微信支付申请流程
支付方向-公司创业的的盈利
- 盈利:费率差价、非T+0结算、腾讯返点等
- https://pay.weixin.qq.com/static/applyment_guide/applyment_detail_website.shtml
- 申请流程
微信商户平台
- 提供给商家使用,用于查看交易数据,提现等信息
- 地址:https://pay.weixin.qq.com
微信公众号
- 申请类型需要是【服务号】
- https://mp.weixin.qq.com/
- 【必读】继续往下看视频,后面会讲个人怎么接入,不要慌张!!!!!
第4集 微信支付-公众号Appid和商户号绑定指引
简介:微信支付-公众号Appid和商户号绑定指引
微信公众号
- 申请类型需要是服务号
- https://mp.weixin.qq.com/
参数路径
- 公众号-》设置和开发-》基本配置-》appid: wx5beac15ca207c40c
微信商户平台
提供给商家使用,用于查看交易数据,提现等信息
将appid和商户号绑定
- 去微信商户平台
- 【必读】继续往下看视频,后面会讲个人怎么接入,不要慌张!!!!!
第5集 微信支付接入相关参数和证书申请操作指引
简介:微信支付接入相关参数和证书申请操作指引
多数同学是没这个资质的和商户平台的,这个几集视频是告诉大家线上操作流程
- 如果你们有资质就可以实操,没资质就可以跳过这个安装配置申请,非必要技术
- 下集会教大家,个人怎么接入进去【我们小滴课堂提供正式的接入参数】
参数说明
公众号
- appid: wx5beac15ca207c40c
商户平台
API安全
API证书(文档有操作指引)
- 以下两种情况将使用该API证书证实商户身份,需妥善保管防止泄露:
- API V2中,调用微信支付安全级别较高的接口(如:退款、企业红包、企业付款)
- API V3中,调用微信支付所有接口
- 以下两种情况将使用该API证书证实商户身份,需妥善保管防止泄露:
APIv2密钥(旧api)
APIv3密钥 (新api)
- vIOIOx97ZT6952MFZj3PXXxKAQnqC1of
证书申请流程讲解(如果失效,去微信支付文档查看最新的操作)
- https://kf.qq.com/faq/161222NneAJf161222U7fARv.html
证书文件说明
- apiclient_cert.p12 :私钥信息的证书文件,win下可双击导入
- apiclient_cert.pem:商户证书,封装了公钥
- apiclient_key.pem: 商户证书,封装了私钥
第6集 【必看】海量数据大课-个人接入微信支付V3版操作
简介:【必看】海量数据大课-个人接入微信支付V3版操作
个人接入微信支付V3版参数(临时参数,同学用的时候会失效 ,群公告查看最新)
- 公众号:appid: wx5beac15ca207c40c
商户平台
- 商户号:1601644442
- APIv3密钥:peYcTwRF581UOdaUqoPOeHzJ8FgHgsnJ
- 证书序列号:7064ADC5FE84CA2A3DDE71A692E39602DEB96E61
- 证书文件:本章本集的资料里面(不可用,最新的群公告获取)
注意【参数每周都更新】
主讲最新微信支付V3版本,盗版【没法实操】接下去的全链路内容
联系客服凭海量数据项目大课订单号,进小滴课堂海量数据大课学习群
- 【群公告】那边有证书和相关参数信息,只要大家进群,就能获取最新的参数
- 我们有多个海大学习群,进其中一个即可
- 参数(每周会调整一次,盗版人员没法维护好)
- 证书
- 证书序列号
- 支付秘钥
- 商户号
- ......更多
大家如果下单失败记得及时【群公告】里查看最新的即可
目的
- 尊重原创付费视频,打击盗版没法实操
- 项目大课是市面上独一无二的课程,也是保护防止盗版不滥于市场
如果发现你买了盗版,帮我举报。联系我这边大课会有特殊折扣优惠
- 正版好处多,资料-视频齐全、群里讨论-学习氛围都是盗版给不了的,学的放心
- 看视频的都是学习的同学,小的付出带来涨薪和技术增长,6分之1以上月薪即可回本 愿景:”让编程不再难学,让技术与生活更加有趣”
更多架构课程请访问 xdclass.net
第三十七章 密码安全-对称加密和非对称加密技术你知道多少
第1集 安全攻防-密码学的那些事情-对称和非对称介绍
简介:网络攻防-密码学的那些事情-对称和非对称介绍
- 信息安全目标
- 常见加密算法的分类
- 对称加密
优点:操作比较简单,加密速度快, 秘钥简单
缺点:秘钥一旦被窃取,信息会暴露,安全性不高
场景:消息发送方需要加密大量数据时使用
常见的算法:
DES: 全称:Data Encryption Standard,现已被破解
3DES:全称: Triple Data Encryption Algorithm, 暂时未被破解
解释: 3DES 是在 DES 基础算法上的改良,该算法可向下兼容 DES 加密算法,但计算性能不高,暂时还未被破解
AES: 全称:Advanced Encryption Standard,暂未被破解
- 非对称加密
注意:非对称加密具有双向性,即公钥和私钥中的任一个均可用作加密,此时另一个则用作解密
解释:加密与解密的过程不是对称的,不是用的同一个秘钥,一把是公钥,一把是私钥,在加密的时候,用公钥去加密,接收方再用对应的私钥去解密
优点:安全性更高,公钥是公开的,秘钥是自己保存的,不需要将私钥给别人。
缺点:加密和解密花费时间长、速度慢,只适合对少量数据进行加密
场景: 数字签名与验证
常见的算法:RSA,DSA,ECC等,ECC也是比特币底层用的比较多的算法
第2集 安全攻防-数据完整性的保障方案-数据摘要
简介:安全攻防-数据完整性的保障方案数据摘要
- Hash算法-单项加密
加密过程中不需要使用密钥,输入明文后由系统直接经过加密算法处理成密文,密文无法解密。
只有重新输入明文,并经过同样的加密算法处理,得到相同的密文并被系统重新识别后,才能真正解密
算法:MD5/SHA1/SHA224/SHA256/
优点:快速计算,具有单向性 one-way,不可由散列值推出原消息
场景:文件完整性校验和(Checksum)算法、常规密码等
对称加密和非对称加密有秘钥,而只有hash摘要算法,没有秘钥
彩虹表暴力破解
- 网站:https://www.cmd5.com/
密码存储常用方式
- 双重MD5
- MD5+加盐
- 双重MD5+加盐
有些人会说为啥不直接用最强的Hash加密算法 ?
- 更安全的算法,加密解密更复杂,接口性能下降更严重
第3集 对称加密和非对称加密 混合使用的例子
简介:对称加密和非对称加密 混合使用的例子
总结
- 对称加密:操作比较简单, 加密速度快, 秘钥简单如果泄露就危险
- 非对称加密:安全性更高,加解密复杂,但是加解密速度慢
业界比较多的是是对称加密和非对称加密结合使用
- 例子:小滴课堂老王和冰冰的约会那些事情
- 业界很多这样的应用,比如HTTPS ``` HTTPS非对称加密只作用在证书验证阶段
内容传输的加密上使用的是对称加密
既保证了秘钥的安全性,又保证了后继数据传输的数据加解密的性能
- 更多https知识可以搜索博文,通过上面的例子,就很容易看懂HTTPS的文章了
<a name="d6c55951"></a>
#### 第4集 HTTPS知识扩展和支付平台里面非对称加密和对称加密的应用
**简介:HTTPS知识扩展和支付平台里面非对称加密和对称加密的应用**
-
平台的选择
-
支付宝和微信支付加密方式:采用RSA非对称加密+对称加密混合使用
- 商户应用和支付平台通信-都是采用HTTPS通信(CA机构+SSL证书)
![](img/366784-20160127222221785-258650029.png#alt=img)
- 业务参数使用非对称加密通讯:私钥加密-公钥解密
-
第三方支付平台多数都是采用非对称加密
-
支付宝/微信支付的例子
商家服务器和支付宝/微信服务器交互的过程中传输的信息,防止中间人对于信息的篡改. 小滴课堂-老王,买了1万块的【性感内裤】准备送给冰冰,然后支付的时候拦截参数,把订单价格从1万改成10块。 支付宝/微信就误认为是10元。
签名可以验证一条消息的真实性,sign参数用来填写签名,有效的防止消息在传递过程中被篡改
- 防篡改:
![](img/image-20220108143857950.png#alt=image-20220108143857950)
- 为啥需要两套公钥和私钥
- 提供了两套 RSA 加密
- 一套是用来保证步骤 【统一下单】时的信息安全
- 另一套是用来保证支付平台回调时的信息安全
![](img/image-20211230153711550.png#alt=image-20211230153711550)
![](img/image-20220110115654424.png#alt=image-20220110115654424)
![](https://file.xdclass.net/note/2020/javaweb/%E5%9B%BE%E7%89%87/logo.png#alt=logo) **愿景:"让编程不再难学,让技术与生活更加有趣" **
**更多架构课程请访问 xdclass.net**
<a name="5256f4aa"></a>
### 第三十八章 微信支付-业务流程时序图和依赖配置
<a name="d7178d18"></a>
#### 第1集 微信支付-Native支付产品文档流程梳理
**简介:微信支付-Native支付产品文档流程梳理**
-
文档地址(失效的话百度下,官方改版频繁)
- [https://pay.weixin.qq.com/wiki/doc/apiv3/open/pay/chapter2_7_0.shtml](https://pay.weixin.qq.com/wiki/doc/apiv3/open/pay/chapter2_7_0.shtml)
-
接入指引
-
接入前支付参数准备【看海量数据大课群公告获取最新】
-
项目开发指引
-
据自身开发语言,选择对应的开发库并构建项目,添加依赖
-
创建加载商户私钥、加载平台证书、初始化httpClient的通用方法
-
开发API进行测试
- 签名生成
- 签名验证
- 敏感信息加解密
- merchantPrivateKey(私钥)
- wechatpayCertificates(平台证书)
- APIV3Key(使用)
![](img/image-20220108162707328.png#alt=image-20220108162707328)
-
上面的是具体代码开发的细节
- 问题:很多同学可能都不知道交互流程,我们的代码和微信是怎样通信的
- 答案:跟着我走,带你看下整体交互时序图
- 时序图???啥意思????
![](img/5_0.png#alt=img)
<a name="b7042401"></a>
#### 第2集 架构师必备技能-业务流程时序图知识回顾
**简介:架构师必备技能-业务流程时序图知识回顾**
- 曾几何时,大学老师辛苦讲的《软件工程课程》很多同学忘记了一干二净了
![](img/image-20220108164634830.png#alt=image-20220108164634830)
-
什么是时序图
-
是一种UML交互图,描述了对象之间传递消息的时间顺序, 用来表示用例中的行为顺序, 是强调消息时间 顺序的交互图; 又名序列图、顺序图
-
通俗解释:就是交互流程图 (把大象装冰箱分几步)
-
时序图包括四个元素
- 对象(Object)
- 时序图中的对象在交互中扮演的角色就是对象,使用矩形将对象名称包含起来, 名称下有下划线
- 生命线(Lifeline)
- 生命线是一条垂直的虚线, 这条虚线表示对象的存在, 在时序图中, 每个对象都有生命线
- 激活(Activation)
- 代表时序图中对象执行一项操作的时期, 表示该对象被占用以完成某个任务,当对象处于激活时期, 生命线可以拓宽为矩形
- 消息(Message)
- 对象之间的交互是通过相互发消息来实现的,箭头上面标出消息名,一个对象可以请求(要求)另一个对象做某件事件
- 消息从源对象指向目标对象,消息一旦发送便将控制从源对象转移到目标对象
- 消息的阅读顺序是【严格自上而下】
- 消息交互中的【实线:请求消息】
- 消息交互中的【虚线:响应返回消息】
- 自己调用自己的方法:反身消息
-
再自己根据老师的讲解,去核对下流程
![](img/5_0.png#alt=img)
<a name="8254bcbc"></a>
#### 第3集 微信支付-交互业务时序图详细讲解实战
**简介:微信支付-交互业务时序图详细讲解实战**
- 业务架构流程图
![](img/image-20211230153711550.png#alt=image-20211230153711550)
- 细化到支付业务时序图(上图的2、4、5、8步骤)
![](img/5_0.png#alt=img)
![](img/image-20220108171850902.png#alt=image-20220108171850902)
<a name="f551126d"></a>
#### 第4集 微信支付-Maven依赖加入和代码参数准备
**简介:微信支付-Maven依赖加入和代码参数准备**
- 参数加入【群公告获取最新的】
商户号
pay.wechat.mch-id=1601644442
公众号id 需要和商户号绑定
pay.wechat.wx-pay-appid=wx5beac15ca207c40c
商户证书序列号,需要和证书对应
pay.wechat.mch-serial-no=7064ADC5FE84CA2A3DDE71A692E39602DEB96E61
api密钥
pay.wechat.api-v3-key=peYcTwRF581UOdaUqoPOeHzJ8FgHgsnJ
商户私钥路径(微信服务端会根据证书序列号,找到证书获取公钥进行解密数据)
pay.wechat.private-key-path=classpath:/cert/apiclient_key.pem
支付成功页面跳转
pay.wechat.success-return-url=https://xdclass.net
支付成功,回调通知
pay.wechat.callback-url=http://api.open1024.com/shop-server/api/callback/order/v1/wechat
- 证书配置加入
- Maven依赖加入
- 微信支付API v3的Apache HttpClient扩展,实现了请求签名的生成和应答签名的验证。
- 使用说明 [https://github.com/wechatpay-apiv3/wechatpay-apache-httpclient](https://github.com/wechatpay-apiv3/wechatpay-apache-httpclient)
<a name="791f8e9f"></a>
#### 第5集 微信支付-商户私钥证书代码读取开发实战
**简介:微信支付-商户私钥证书代码读取开发实战**
- java加载商户证书私钥
- 如果下面链接打开慢就不用打开 [https://github.com/wechatpay-apiv3/wechatpay-apache-httpclient](https://github.com/wechatpay-apiv3/wechatpay-apache-httpclient)
- 商户申请商户API证书时,会生成商户私钥,并保存在本地证书文件夹的文件`apiclient_key.pem`中。
- 商户开发者可以使用方法`PemUtil.loadPrivateKey()`加载证书
# 示例:私钥存储在文件
PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(
new FileInputStream(“/path/to/apiclientkey.pem”));
# 示例:私钥为String字符串
PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(
new ByteArrayInputStream(privateKey.getBytes(“utf-8”)));
- 代码读取私钥实战 PayBeanConfig
/
加载秘钥
@param payConfig
@return
@throws IOException
*/
private PrivateKey getPrivateKey() throws IOException {
InputStream inputStream = new ClassPathResource(payConfig.getPrivateKeyPath()
.replace(“classpath:”, “”)).getInputStream();
String content = new BufferedReader(new InputStreamReader(inputStream))
.lines().collect(Collectors.joining(System.lineSeparator()));
try {
String privateKey = content.replace(“——-BEGIN PRIVATE KEY——-“, “”)
.replace(“——-END PRIVATE KEY——-“, “”)
.replaceAll(“\s+”, “”);
KeyFactory kf = KeyFactory.getInstance(“RSA”);
PrivateKey finalPrivateKey = kf.generatePrivate(
new PKCS8EncodedKeySpec(Base64.getDecoder().decode(privateKey)));
return finalPrivateKey;
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(“当前Java环境不支持RSA”, e);
} catch (InvalidKeySpecException e) {
throw new RuntimeException(“无效的密钥格式”);
}
}
```
#### 第6集 微信支付-APIv3证书与密钥使用说明文档
简介: 微信支付-APIv3证书与密钥使用说明文档
- 之前的说过的应用和支付平台的交互图
- 微信签名和验签的交互图(商户证书和平台证书的使用说明)
- 目前我们有商户自己的私钥和公钥,那微信平台的证书里公钥我们怎么获取???
- 微信平台证书公钥获取方式
- 避免平台证书过期,微信提供了定时更新平台证书功能
- 地址经常改内容 https://github.com/wechatpay-apiv3/wechatpay-apache-httpclient
- 我们在【本章本集】把资料文档下载下来了
- 刚更新是0.4 版本(资料不全),我们用0.3.0的新版(版本不影响)
#### 第7集 微信支付-基于SDK二次封装HttpClient和证书管理实战
简介:微信支付-基于SDK二次封装HttpClient和证书管理
- 代码封装实战
```
/
定时获取微信签名验证器,自动获取微信平台证书(证书里面包括微信平台公钥)
@return
/
@Bean
public ScheduledUpdateCertificatesVerifier getCertificatesVerifier() throws IOException {
// 使用定时更新的签名验证器,不需要传入证书
ScheduledUpdateCertificatesVerifier verifier = null;
verifier = new ScheduledUpdateCertificatesVerifier(
new WechatPay2Credentials(payConfig.getMchId(),
new PrivateKeySigner(payConfig.getMchSerialNo(),
getPrivateKey())),
payConfig.getApiV3Key().getBytes(StandardCharsets.UTF_8));
return verifier;
}
/
获取http请求对象,会自动的处理签名和验签,
并进行证书自动更新
@return
*/
@Bean(“wechatPayClient”)
public CloseableHttpClient getWechatPayClient(ScheduledUpdateCertificatesVerifier verifier) throws IOException {
WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
.withMerchant(payConfig.getMchId(),payConfig.getMchSerialNo() , getPrivateKey())
.withValidator(new WechatPay2Validator(verifier));
// 通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签,并进行证书自动更新
CloseableHttpClient httpClient = builder.build();
return httpClient;
}
```
#### 第8集 微信支付-SDK定时更新微信平台证书源码解读
简介:微信支付-SDK定时更新微信平台证书源码解读
- 微信签名和验签的交互图(商户证书和平台证书的使用说明)
-
个人接入微信支付V3版参数(临时参数,同学用的时候会失效 ,群公告查看最新)
- 公众号:appid: wx5beac15ca207c40c
- 商户平台
- 商户号:1601644442
- APIv3密钥:peYcTwRF581UOdaUqoPOeHzJ8FgHgsnJ
- 证书序列号:7064ADC5FE84CA2A3DDE71A692E39602DEB96E61
- 证书文件:本章本集的资料里面(不可用,最新的群公告获取)
-
注意【参数每周都更新】
-
主讲最新微信支付V3版本,盗版【没法实操】接下去的全链路内容
-
联系客服凭海量数据项目大课订单号,进小滴课堂海量数据大课学习群
- 【群公告】那边有证书和相关参数信息,只要大家进群,就能获取最新的参数
- 我们有多个海大学习群,进其中一个即可
- 参数(每周会调整一次,盗版人员没法维护好)
- 证书
- 证书序列号
- 支付秘钥
- 商户号
- ……更多
-
大家如果下单失败记得及时【群公告】里查看最新的即可
愿景:”让编程不再难学,让技术与生活更加有趣”
更多架构课程请访问 xdclass.net
### 第三十九章 微信支付Native订单API测试实战和签名流程解读
#### 第1集 微信支付-快速验证参数配置方法和统一下单接口开发
简介:微信支付-快速验证参数配置方法和统一下单接口开发
-
接口文档
- https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_4_1.shtml
-
编码实战
- 配置接口地址
```
public class WechatPayApi {
/
微信支付域名
/
public static final String HOST = “https://api.mch.weixin.qq.com“;
/
Native下单
/
public static final String NATIVE_ORDER = HOST+”/v3/pay/transactions/native”;
}
- 通用测试代码(快速验证参数是否过期和失效)
@Test
public void testWechatPayNativeOrder() throws IOException {
//过期时间 RFC 3339格式
SimpleDateFormat sdf = new SimpleDateFormat(“yyyy-MM-dd’T’HH:mm:ssXXX”);
//支付订单过期时间
String timeExpire = sdf.format(new Date(System.currentTimeMillis() + 6000 60 1000));
String outTradeNo = CommonUtil.getStringNumRandom(32);
JSONObject amountObj = new JSONObject();
JSONObject payObj = new JSONObject();
payObj.put(“mchid”, payConfig.getMchId());
payObj.put(“out_trade_no”, outTradeNo);
payObj.put(“appid”, payConfig.getWxPayAppid());
payObj.put(“description”, “小滴课堂海量数据项目大课”);
payObj.put(“notify_url”, payConfig.getCallbackUrl());
payObj.put(“time_expire”, timeExpire);
//微信支付需要以分为单位
int amount = 100;
amountObj.put(“total”, amount);
amountObj.put(“currency”, “CNY”);
payObj.put(“amount”, amountObj);
//附属参数,可以用在回调携带
payObj.put(“attach”, “{\”accountNo\”:” + 8888 + “}”);
// 处理请求body参数
String body = payObj.toJSONString();
log.info(“请求参数:{}”, payObj);
StringEntity entity = new StringEntity(body, “utf-8”);
entity.setContentType(“application/json”);
//调用统一下单API
HttpPost httpPost = new HttpPost(WechatPayApi.NATIVE_ORDER);
httpPost.setHeader(“Accept”, “application/json”);
httpPost.setEntity(entity);
//httpClient自动进行参数签名
try (CloseableHttpResponse response = wechatPayClient.execute(httpPost)) {
String responseStr = EntityUtils.toString(response.getEntity());
//响应体
int statusCode = response.getStatusLine().getStatusCode();
log.info(“统一下单响应码={},响应体={}”, statusCode, responseStr);
} catch (Exception e) {
e.printStackTrace();
}
}
```
#### 第2集 微信支付-查询订单支付状态开发链路验证
简介:微信支付-查询订单支付状态开发验证
-
接口文档
- https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_4_2.shtml ``` / Native订单查询,根据商户订单号查询,out_trade_no / public static final String NATIVE_QUERY = HOST+”/v3/pay/transactions/out-trade-no/%s?mchid=%s”;
-
编码实战
@Test
public void testWechatPayNativeQuery() throws IOException {
String outTradeNo = “99lHMc8uO2ZZ03KSV24XEFaLRfSMfqwD”;
String url = String.format(WechatPayApi.NATIVE_QUERY, outTradeNo, payConfig.getMchId());
HttpGet httpGet = new HttpGet(url);
httpGet.setHeader(“Accept”, “application/json”);
//httpClient自动进行参数签名
try (CloseableHttpResponse response = wechatPayClient.execute(httpGet)) {
String responseStr = EntityUtils.toString(response.getEntity());
int statusCode = response.getStatusLine().getStatusCode();
log.info(“查询订单响应码={},响应体={}”, statusCode, responseStr);
} catch (Exception e) {
e.printStackTrace();
}
}
<a name="d0c3d87d"></a>
#### 第3集 微信支付-超时关闭订单API链路验证
**简介:微信支付-超时关闭订API单链路验证**
-
接口文档
- [https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_4_3.shtml](https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_4_3.shtml)
/
关闭订单,根据 out_trade_no
/
public static final String NATIVE_CLOSE_ORDER = HOST+”/v3/pay/transactions/out-trade-no/%s/close”;
-
编码实战
@Test
public void testWechatPayNativeCloseOrder() throws IOException {
String outTradeNo = “99lHMc8uO2ZZ03KSV24XEFaLRfSMfqwD”;
String url = String.format(WechatPayApi.NATIVE_CLOSE_ORDER, outTradeNo, payConfig.getMchId());
HttpPost httpPost = new HttpPost(url);
//组装json
JSONObject payObj = new JSONObject();
payObj.put(“mchid”, payConfig.getMchId());
String body = payObj.toJSONString();
log.info(“请求参数={}”, body);
//将请求参数设置到请求对象中
StringEntity entity = new StringEntity(body, “utf-8”);
entity.setContentType(“application/json”);
httpPost.setEntity(entity);
httpPost.setHeader(“Accept”, “application/json”);
//httpClient自动进行参数签名
try (CloseableHttpResponse response = wechatPayClient.execute(httpPost);) {
int statusCode = response.getStatusLine().getStatusCode();//响应状态码
log.info(“关闭订单响应码={},无响应体”, statusCode);
} catch (Exception e) {
e.printStackTrace();
}
}
```
#### 第4集 微信支付-SDK自动完成支付签名流程解读
简介:微信支付-SDK自动完成支付签名流程解读
-
微信支付签名规则文档
-
https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay4_0.shtml
-
配置日志级别
-
logging.level.root=debug
-
源码日志
- 旧版文档 - https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=4_3 #### 第5集 微信支付-申请订单退款API链路开发实战 简介:微信支付-申请订单退款API链路开发实战 - 接口文档 - 二维码生成工具:https://cli.im/ - https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_4_9.shtml - 注意事项
1、交易时间超过一年的订单无法提交退款
2、微信支付退款支持单笔交易分多次退款(不超50次),多次退款需要提交原支付订单的商户订单号和设置不同的退款单号。申请退款总金额不能超过订单金额。 一笔退款失败后重新提交,请不要更换退款单号,请使用原商户退款单号
3、错误或无效请求频率限制:6qps,即每秒钟异常或错误的退款申请请求不超过6次
4、每个支付订单的部分退款次数不能超过50次
5、如果同一个用户有多笔退款,建议分不同批次进行退款,避免并发退款导致退款失败
6、申请退款接口的返回仅代表业务的受理情况,具体退款是否成功,需要通过退款查询接口获取结果
7、一个月之前的订单申请退款频率限制为:5000/min
8、同一笔订单多次退款的请求需相隔1分钟
-
编码实战
```
/
{“amount”:{“currency”:”CNY”,”discount_refund”:0,”from”:[],”payer_refund”:10,
“payer_total”:100,”refund”:10,”settlement_refund”:10,”settlement_total”:100,”total”:100},
“channel”:”ORIGINAL”,”create_time”:”2022-01-18T13:14:46+08:00”,
“funds_account”:”AVAILABLE”,”out_refund_no”:”Pe9rWbRpUDu51PFvo8L17LJZHm6dpbj7”,
“out_trade_no”:”6xYsHV3UziDINu06B0XeuzmNvOedjhY5”,”promotion_detail”:[],
“refund_id”:”50302000542022011816569235991”,”status”:”PROCESSING”,
“transaction_id”:”4200001390202201189710793189”,
“user_received_account”:”民生银行信用卡5022”}
@throws IOException
*/
@Test
public void testNativeRefundOrder() throws IOException {
String outTradeNo = “iIYGHoBTO95YaZu68n7BWXsxyaaNxK6q”;
String refundNo = CommonUtil.getStringNumRandom(32);
//调用统一下单API
String url = “https://api.mch.weixin.qq.com/v3/refund/domestic/refunds“;
HttpPost httpPost = new HttpPost(url);
// 请求body参数
JSONObject refundObj = new JSONObject();
//订单号
refundObj.put(“out_trade_no”, outTradeNo);
//退款单编号,商户系统内部的退款单号,商户系统内部唯一,
// 只能是数字、大小写字母-|@ ,同一退款单号多次请求只退一笔
refundObj.put(“out_refund_no”, refundNo);
refundObj.put(“reason”,”商品已售完”);
refundObj.put(“notify_url”, payConfig.getCallbackUrl());
JSONObject amountObj = new JSONObject();
amountObj.put(“refund”, 10);
amountObj.put(“total”, 100);
amountObj.put(“currency”, “CNY”);
refundObj.put(“amount”, amountObj);
String body = refundObj.toJSONString();
log.info(“请求参数:{}”,body);
StringEntity entity = new StringEntity(body,”utf-8”);
entity.setContentType(“application/json”);
httpPost.setEntity(entity);
httpPost.setHeader(“Accept”, “application/json”);
try(CloseableHttpResponse response = wechatPayClient.execute(httpPost)){
//响应码
int statusCode = response.getStatusLine().getStatusCode();
//响应体
String responseStr = EntityUtils.toString(response.getEntity());
log.info(“退款响应码:{},响应体:{}”,statusCode,responseStr);
}catch (Exception e){
e.printStackTrace();
}
}
<a name="402053de"></a>
#### 第6集 微信支付-订单退款状态查询API链路实战
**简介:微信支付-订单退款状态查询API链路实战**
-
接口文档
- 二维码工具:[https://cli.im/](https://cli.im/)
- [https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_4_10.shtml](https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_4_10.shtml)
![](img/image-20220118133746766.png#alt=image-20220118133746766)
- 怎么知道退款状态呢?
- 微信回调通知
- 主动查询
![](img/image-20220118134518832.png#alt=image-20220118134518832)
- 查询API编码实战
/**
{“amount”:{“currency”:”CNY”,”discountrefund”:0,”from”:[],”payer_refund”:10,
“payer_total”:100,”refund”:10,”settlement_refund”:10,
“settlement_total”:100,”total”:100},”channel”:”ORIGINAL”,
“create_time”:”2022-01-18T13:14:46+08:00”,”funds_account”:”AVAILABLE”,
“out_refund_no”:”Pe9rWbRpUDu51PFvo8L17LJZHm6dpbj7”,
“out_trade_no”:”6xYsHV3UziDINu06B0XeuzmNvOedjhY5”,”promotion_detail”:[],
“refund_id”:”50302000542022011816569235991”,”status”:”SUCCESS”,
“success_time”:”2022-01-18T13:14:55+08:00”,”transaction_id”:”4200001390202201189710793189”,
“user_received_account”:”民生银行信用卡5022”}
@throws IOException
/
@Test
public void testNativeRefundQuery() throws IOException {
String refundNo = “Pe9rWbRpUDu51PFvo8L17LJZHm6dpbj7”;
String url = String.format(“https://api.mch.weixin.qq.com/v3/refund/domestic/refunds/%s",refundNo);
HttpGet httpGet = new HttpGet(url);
httpGet.setHeader(“Accept”,”application/json”);
try(CloseableHttpResponse response = wechatPayClient.execute(httpGet)){
//响应码
int statusCode = response.getStatusLine().getStatusCode();
//响应体
String responseStr = EntityUtils.toString(response.getEntity());
log.info(“查询退款响应码:{},响应体:{}”,statusCode,responseStr);
}catch (Exception e){
e.printStackTrace();
}
}
![](https://file.xdclass.net/note/2020/javaweb/%E5%9B%BE%E7%89%87/logo.png#alt=logo) **愿景:"让编程不再难学,让技术与生活更加有趣"**
<a name="bf732d44"></a>
### 第四十章 设计模式在多渠道支付里面的设计+编码实战
<a name="65b86ef4"></a>
#### 第1集 软件架构设计-设计模式的六大原则你知道多少
**简介:讲解设计模式的六大设A计原则**
-
设计模式是站在设计原则的基础之上的,软件架构也一样,有必要对这些设计原则先做一下了解
-
软件设计开发原则
- 为了让的代码更好重用性,可读性,可靠性,可维护性
- 诞生出了很多软件设计的原则,这6大设计原则是我们要掌握的
- 将六大原则的英文首字母拼在一起就是SOLID(稳定的),所以也称之为SOLID原则
![](https://file.xdclass.net/note/2020/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/img/3333.jpg#alt=img)
![](img/image-20220119102817600.png#alt=image-20220119102817600)
-
单一职责原则
- 一个类只负责一个功能领域中的相应职责,就一个类而言,应该只有一个引起它变化的原因
- 是实现**高内聚、低耦合**的指导方针
- 解释:
- 高内聚
- 尽可能类的每个成员方法只完成一件事(最大限度的聚合)
- 模块内部的代码, 相互之间的联系越强,内聚就越高, 模块的独立性就越好
- 低耦合: 减少类内部,一个成员方法调用另一个成员方法, 不要有牵一发动全身
-
开闭原则
- 对扩展开放,对修改关闭,在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果
-
里氏替换原则LSP
- 任何基类可以出现的地方,子类一定可以出现
- 在程序中尽量使用基类类型来对对象进行定义,而在运行时再确定其子类类型,用子类对象来替换父类对象
- controller->service->dao
-
依赖倒转原则
- 是开闭原则的基础,针对接口编程,依赖于抽象而不依赖于具体
- 高层模块不应该依赖低层模块,二者都应该依赖其抽象
-
接口隔离原则
- 客户端不应该依赖那些它不需要的接口
- 使用多个隔离的接口,比使用单个接口要好,降低类之间的耦合度
-
迪米特法则
- 最少知道原则,一个实体应当尽量少地与其他实体之间发生相互作用,使得系统功能模块相对独立
- 类之间的耦合度越低,就越有利于复用,一个处在松耦合中的类一旦被修改,不会对关联的类造成太大波及
- 通过引入一个合理的第三者来降低现有对象之间的耦合度
<a name="bf4c66d2"></a>
#### 第2集 设计模式最佳实践-第三方支付对接-工厂模式回顾
**简介:设计模式知识回顾-工厂模式**
-
工厂模式介绍:
- 它提供了一种创建对象的最佳方式,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象
-
工厂模式有 3 种不同的实现方式
- 简单工厂模式:通过传入相关的类型来返回相应的类,这种方式比较单 一 , 可扩展性相对较差;
- 工厂方法模式:通过实现类实现相应的方法来决定相应的返回结果,这种方式的可扩展性比较强;
- 抽象工厂模式:基于上述两种模式的拓展,且支持细化产品
-
例子:
- 需要购买一辆车,不用管车辆如何组装,且可以购买不同类型的比如轿车、SUV、跑车,直接去4s店购买就行(4s店就是工厂)
- 工厂生产电脑,除了A品牌、还可以生产B、C、D品牌电脑
- 业务开发中,支付很常见,里面有统一下单和支付接口,具体的支付实现可以微信、支付宝、银行卡等
![](https://file.xdclass.net/note/2020/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/img/image-20200926114649412.png#alt=image-20200926114649412)
-
简单工厂模式
- 又称静态工厂方法, 可以根据参数的不同返回不同类的实例,专门定义一个类来负责创建其他类的实例,被创建的实例通常都具有共同的父类
- 由于工厂方法是静态方法,可通过类名直接调用,而且只需要传入简单的参数即可
-
优点:
- 将对象的创建和对象本身业务处理分离可以降低系统的耦合度,使得两者修改起来都相对容易。
-
缺点
- 工厂类的职责相对过重,增加新的产品需要修改工厂类的判断逻辑,这一点与开闭原则是相违背
- 即开闭原则(Open Close Principle)对扩展开放,对修改关闭,程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果
- 将会增加系统中类的个数,在一定程度上增加了系统的复杂度和理解难度,不利于系统的扩展和维护
-
项目里面的应用
![](img/image-20220118180324472.png#alt=image-20220118180324472)
<a name="9601a5c3"></a>
#### 第3集 设计模式最佳实践-第三方支付对接-策略模式回顾
**简介:设计模式知识回顾-策略模式**
-
策略模式(Strategy Pattern)
- 定义一系列的算法,把它们一个个封装起来, 并且使它们可相互替换
- 淘宝天猫双十一,正在搞活动有打折的、有满减的、有返利的等等,这些算法只是一种策略,并且是随时都可能互相替换的, 我们就可以定义一组算法,将每个算法都封装起来,并且使它们之间可以互换
-
应用场景
- 老王计划外出旅游,选择骑自行车、坐汽车、飞机等,每一种旅行方式都是一个策略
- Java AWT中的LayoutManager,即布局管理器
- 如果在一个系统里面有许多类,它们之间的区别仅在于它们的行为,那么可以使用策略模式
- 不希望暴露复杂的、与算法有关的数据结构,那么可以使用策略模式来封装算法
- 对接第三方支付里面,微信支付、支付宝支付等都可以是一种策略
-
角色
- Context上下文:屏蔽高层模块对策略、算法的直接访问,封装可能存在的变化
- Strategy策略角色:抽象策略角色,是对策略、算法家族的抽象,定义每个策略或算法必须具有的方法和属性
- ConcreteStrategy具体策略角色:用于实现抽象策略中的操作,即实现具体的算法
![](img/image-20220118180344177.png#alt=image-20220118180344177)
<a name="81fc9e95"></a>
#### 第4集 多渠道支付对接-策略模式+工厂模式编码实战
**简介:多渠道支付对接-策略模式+工厂模式编码实战**
- 策略接口开发 PayStrategy
- 策略上下文 PayStrategyContext开发
- 具体支付策略开发 AlipayStrategy、WechatPayStrategy
- 简单工厂类开发
<a name="28aca387"></a>
#### 第5集 策略设计模式-微信支付下单编码实战整合
**简介:微信支付策略编码实战**
-
下单策略编码实战
@Override
public String unifiedOrder(PayInfoVO payInfoVO) {
//过期时间 RFC 3339格式
SimpleDateFormat sdf = new SimpleDateFormat(“yyyy-MM-dd’T’HH:mm:ssXXX”);
//支付订单过期时间
String timeExpire = sdf.format(new Date(System.currentTimeMillis() + payInfoVO.getOrderPayTimeoutMills()));
JSONObject amountObj = new JSONObject();
//数据库存储是double比如,100.99元,微信支付需要以分为单位
int amount = payInfoVO.getPayFee().multiply(BigDecimal.valueOf(100)).intValue();
amountObj.put(“total”, amount);
amountObj.put(“currency”, “CNY”);
JSONObject payObj = new JSONObject();
payObj.put(“mchid”, payConfig.getMchId());
payObj.put(“out_trade_no”, payInfoVO.getOutTradeNo());
payObj.put(“appid”, payConfig.getWxPayAppid());
payObj.put(“description”, payInfoVO.getTitle());
payObj.put(“notify_url”, payConfig.getCallbackUrl());
payObj.put(“time_expire”, timeExpire);
payObj.put(“amount”, amountObj);
//回调携带
payObj.put(“attach”, “{\”accountNo\”:” + payInfoVO.getAccountNo() + “}”);
// 处理请求body参数
String body = payObj.toJSONString();
log.info(“请求参数:{}”, body);
StringEntity entity = new StringEntity(body, “utf-8”);
entity.setContentType(“application/json”);
HttpPost httpPost = new HttpPost(WechatPayApi.NATIVE_ORDER);
httpPost.setHeader(“Accept”, “application/json”);
httpPost.setEntity(entity);
String result = “”;
try (CloseableHttpResponse response = wechatPayClient.execute(httpPost)) {
//响应码
int statusCode = response.getStatusLine().getStatusCode();
//响应体
String responseStr = EntityUtils.toString(response.getEntity());
log.info(“微信支付响应:resp code={},return body={}”, statusCode, responseStr);
//处理成功
if (statusCode == HttpStatus.OK.value()) {
JSONObject jsonObject = JSONObject.parseObject(responseStr);
if (jsonObject.containsKey(“code_url”)) {
result = jsonObject.getString(“code_url”);
}
} else {
log.error(“微信支付响应失败:resp code={},return body={}”, statusCode, responseStr);
}
} catch (Exception e) {
log.error(“微信支付响应异常信息:{}”, e);
}
return result;
}
<a name="e7882952"></a>
#### 第6集 新冠健康码-服务端OR客户端生成二维码优缺点分析
**简介:新冠健康码-服务端OR客户端生成二维码优缺点分析**
-
偶尔的能听到某某健康码崩溃的新闻
- 我没参与过这类的研发,简单说下【码】的生成是服务端还是客户端的优缺点
- 当然,再牛逼的公司也都有服务崩溃的时候:谷歌、FB、亚马逊、阿里、腾讯、微信都在其中
-
服务端生成二维码
- 优点
- 引入二维码jar包统一生成
- 图片生成兼容性好,传输相对安全不易被篡改
- 缺点
- 占据服务端CPU内存/资源、消耗传输带宽,性能较差
- 没法一并返回其他参数,定制化相对弱
-
客户端生成二维码
- 优点
- 在客户端生成二维码会降低服务器的运算与存储压力
- 选择框架要考虑兼容性,客户端根据js框架生成二维码
- 后端返回 二维码文本值,还可以携带其他参数(比如订单号,可以轮训支付状态)
- 缺点
- 客户端需要引入二维码生成js插件
- 需要考虑浏览器兼容性
-
老王的锅
<br />![](img/image-20220119145253740.png#alt=image-20220119145253740)
-
当然,广东省的粤康码还是挺厉害的-采用的是最简单的分布式和集群,做好了负载均衡
![](img/image-20220118183202337.png#alt=image-20220118183202337)
<a name="927aece0"></a>
#### 第7集 流量包商品下单-微信支付链路测试
**简介:流量包商品下单-微信支付链路测试**
- 下单
{
“productId”:2,
“buyNum”:1,
“clientType”:”PC”,
“payType”:”WECHAT_PAY”,
“totalAmount”:1,
“payAmount”:1,
“billType”:”NO_BILL”,
“billHeader”:””,
“billContent”:””,
“billReceiverPhone”:””,
“billReceiverEmail”:””
}
<a name="4faa54e5"></a>
#### 第8集 策略设计模式-微信支付查询和关闭订单编码实战整合
**简介:策略设计模式-微信支付查询和关闭订单编码实战整合**
- 查询微信支付状态
@Override
public String queryPayStatus(PayInfoVO payInfoVO) {
String outTradeNo = payInfoVO.getOutTradeNo();
String url = String.format(WechatPayApi.NATIVE_QUERY,outTradeNo,payConfig.getMchId());
HttpGet httpGet = new HttpGet(url);
httpGet.setHeader(“Accept”,”application/json”);
String result = “”;
try(CloseableHttpResponse response = wechatPayClient.execute(httpGet)){
//响应码
int statusCode = response.getStatusLine().getStatusCode();
//响应体
String responseStr = EntityUtils.toString(response.getEntity());
log.debug(“查询响应码:{},响应体:{}”,statusCode,responseStr);
if(statusCode == HttpStatus.OK.value()){
JSONObject jsonObject = JSONObject.parseObject(responseStr);
if(jsonObject.containsKey(“trade_state”)){
result = jsonObject.getString(“trade_state”);
}
}else {
log.error(“查询支付状态响应失败:{},响应体:{}”,statusCode,responseStr);
}
}catch (Exception e){
log.error(“微信支付响应异常:{}”,e);
}
return result;
}
- 关闭微信支付订单
@Override
public String closeOrder(PayInfoVO payInfoVO) {
String outTradeNo = payInfoVO.getOutTradeNo();
JSONObject payObj = new JSONObject();
payObj.put(“mchid”,payConfig.getMchId());
String body = payObj.toJSONString();
log.debug(“请求参数:{}”,body);
//将请求参数设置到请求对象中
StringEntity entity = new StringEntity(body,”utf-8”);
entity.setContentType(“application/json”);
String url = String.format(WechatPayApi.NATIVE_CLOSE_ORDER,outTradeNo);
HttpPost httpPost = new HttpPost(url);
httpPost.setHeader(“Accept”,”application/json”);
httpPost.setEntity(entity);
String result = “”;
try(CloseableHttpResponse response = wechatPayClient.execute(httpPost)){
//响应码
int statusCode = response.getStatusLine().getStatusCode();
log.debug(“关闭订单响应码:{},无响应体”,statusCode);
if(statusCode == HttpStatus.NO_CONTENT.value()){
result = “CLOSE_SUCCESS”;
}
}catch (Exception e){
log.error(“微信支付响应异常:{}”,e);
}
return result;
}
![](https://file.xdclass.net/note/2020/javaweb/%E5%9B%BE%E7%89%87/logo.png#alt=logo) **愿景:"让编程不再难学,让技术与生活更加有趣"**
<a name="29ad23f1"></a>
### 第四十一章 微信支付-域名映射和回调-验证签名开发实战
<a name="1a741d43"></a>
#### 第1集 微信开发常用工具-内网穿透映射介绍和使用
**简介:微信开发常用工具-内网穿透映射介绍和使用**
- 什么是内网穿透
支付成功需要配置回调通知应用服务器订单支付成功,需要配置对应的域名
在本地电脑开发,微信、支付宝没法回调,所以需要配置个地址映射,就是外部服务器
可以通过这个地址访问当前开发电脑的地址
微信登录、授权、支付等都是需要域名映射工具配合
-
工具
-
ngrock [https://ngrok.com/](https://ngrok.com/)
-
花生壳 [https://hsk.oray.com/](https://hsk.oray.com/)
-
小米球 [http://ngrok.ciqiuwl.cn/](http://ngrok.ciqiuwl.cn/)
-
natapp(采用) [https://natapp.cn/](https://natapp.cn/)
-
账号申请()
1、注册
2、实名制
3、购买
4、授权(仔细看文档,这个是第三方工具,经常改规则,所以务必看文档操作)
-
启动
- ./natapp -authtoken=b1824af621e40514
-
访问测试
<a name="6c1b16cc"></a>
#### 第2集 微信支付V3版本回调+验签流程梳理
**简介:微信支付V3版本回调+验签流程梳理**
![](img/image-20211230153711550.png#alt=image-20211230153711550)
- 回调验签流程
- 文档
- [https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_4_5.shtml](https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_4_5.shtml)
- [https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay4_1.shtml](https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay4_1.shtml)
- 注意
- 同样的通知可能会多次发送给商户系统,商户系统必须能够正确处理重复的通知
- 推荐的做法是,当商户系统收到通知进行处理时,先检查对应业务数据的状态,并判断该通知是否已经处理,如果未处理,则再进行处理;如果已处理,则直接返回结果成功。
- 在对业务数据进行状态检查和处理之前,要采用数据锁进行并发控制,以避免函数重入造成的数据混乱。
- 如果在所有通知频率后没有收到微信侧回调,商户应调用查询订单接口确认订单状态
- 确保回调URL是外部可正常访问的,且不能携带后缀参数
![](img/image-20220120170222109.png#alt=image-20220120170222109)
-
微信回调通知重复问题(不一定准确按照时间间隔推送,需要保证幂等性处理)
- 重复通知的时候,微信的请求id是一样的,用这个做请求幂等性处理
- 响应给微信的内容不规范 或者 超过5秒没响应
- 测试的问题:如果有多个未响应的,则测试的请求id,可能有之前的请求继续回调过来
-
核心流程操作
- 获取报文
- 验证签名(确保是微信传输过来的)
- 解密(AES对称解密出原始数据)
- 处理业务逻辑
- 响应请求
加密不能保证通知请求来自微信,微信会对发送给商户的通知进行签名,并将签名值放在通知的HTTP头Wechatpay-Signature,商户应当验证签名,以确认请求来自微信,而不是其他的第三方
<a name="442cc2f4"></a>
#### 第3集 微信支付V3版本回调+验签开发实战《上》
**简介:微信支付V3版本回调+验签开发实战《上》**
-
编码实战-核心流程操作
-
获取报文
-
验证签名(确保是微信传输过来的)
-
解密(AES对称解密出原始数据)
-
处理业务逻辑
-
响应请求
@Controller
@RequestMapping(“/api/callback/order/v1/“)
@Slf4j
public class PayCallbackController {
@Autowired
private WechatPayConfig wechatPayConfig;
@Autowired
private ProductOrderService productOrderService;
@Autowired
private ScheduledUpdateCertificatesVerifier verifier;
/
获取报文
验证签名(确保是微信传输过来的)
解密(AES对称解密出原始数据)
处理业务逻辑
响应请求
@param request
@param response
@return
*/
@RequestMapping(“wechat”)
public Map
-
方式三:折中方案,接收微信回调通知更新数据库,发送新增流量包MQ消息,响应微信,再消费流量包消息
#### 第3集 微信支付回调通知-发送MQ业务消息开发《上》
简介: 微信支付回调通知-发送MQ业务消息开发《上》
- MQ配置
```
//=====================订单支付成功配置========================
/
更新订单 队列
/
private String orderUpdateQueue=”order.update.queue”;
/
根据订单发放流量包 队列
/
private String orderTrafficQueue=”order.traffic.queue”;
/
微信回调通知routingKey,【发送消息使用】
/
private String orderUpdateTrafficRoutingKey=”order.update.traffic.routing.key”;
/
topic类型的binding key,用于绑定队列和交换机,是用于 订单 消费者,更新订单状态
/
private String orderUpdateBindingKey=”order.update.*.routing.key”;
/
topic类型的binding key,用于绑定队列和交换机,是用于 账号 消费者,发放流量包
/
private String orderTrafficBindingKey=”order..traffic.routing.key”;
/**
订单更新队列和交换机的绑定关系建立
/
@Bean
public Binding orderUpdateBinding(){
return new Binding(orderUpdateQueue,Binding.DestinationType.QUEUE, orderEventExchange,orderUpdateBindingKey,null);
}
/**
发放流量包队列和交换机的绑定关系建立
/
@Bean
public Binding orderTrafficBinding(){
return new Binding(orderTrafficQueue,Binding.DestinationType.QUEUE, orderEventExchange,orderTrafficBindingKey,null);
}
/**
更新订单状态队列 普通队列,用于被监听
/
@Bean
public Queue orderUpdateQueue(){
return new Queue(orderUpdateQueue,true,false,false);
}
/**
发放流量包队列 普通队列,用于被监听
/
@Bean
public Queue orderTrafficQueue(){
return new Queue(orderTrafficQueue,true,false,false);
}
-
给用户新增发送流量包
-
由业务性能决定哪种方式,空间换时间,时间换空间
-
方式一:商品信息/订单信息 可以由消费者那边 远程调用feign进行获取,多了一次开销
-
方式二:商品信息进行快照存储到订单,支付通知回调,组装消息体进行发送(空间换时间,推荐)
<a name="6ad940bb"></a>
#### 第4集 微信支付回调通知-发送MQ业务消息开发《下》
**简介: 微信支付回调通知-发送MQ业务消息开发《下》**
- 发送业务逻辑开发
/**
支付通知结果更新订单状态
@param alipay
@param paramsMap
@return
/
@Override
@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
public JsonData handlerOrderCallbackMsg(ProductOrderPayTypeEnum payType, Mapboolean flag = redisTemplate.opsForValue()
.setIfAbsent(paramsMap.get("out_trade_no"),"OK",3,TimeUnit.DAYS);
if(flag){
rabbitTemplate.convertAndSend(rabbitMQConfig.getOrderEventExchange(), rabbitMQConfig.getOrderUpdateTrafficRoutingKey(), eventMessage);
}
- 也可以自己封装Lua脚本
#### 第6集 微信支付回调-发布订阅模型链路验证
简介:微信支付回调-发布订阅模型链路验证
- 全链路验证
- 下单
- 支付
- 回调调试
- MQ消息查看
愿景:”让编程不再难学,让技术与生活更加有趣”
### 第四十三章 流量包模块+订单支付消息业务逻辑开发实战
#### 第1集 MQ消费者开发-订单支付状态更新模块
简介:MQ消费者开发-订单支付状态更新模块
- 编码实战
public void handleProductOrderMessage(EventMessage eventMessage) {
String messageType = eventMessage.getEventMessageType();
try{
if(EventMessageType.PRODUCT_ORDER_NEW.name().equalsIgnoreCase(messageType)){
//关闭订单
this.closeProductOrder(eventMessage);
} else if(EventMessageType.PRODUCT_ORDER_PAY.name().equalsIgnoreCase(messageType)){
//订单已经支付,更新订单状态
String outTradeNo = eventMessage.getBizId();
Long accountNo = eventMessage.getAccountNo();
int rows = productOrderManager.updateOrderPayState(outTradeNo,accountNo,
ProductOrderStateEnum.PAY.name(),ProductOrderStateEnum.NEW.name());
log.info("订单更新成功:rows={},eventMessage={}",rows,eventMessage);
}
}catch (Exception e){
log.error("订单消费者消费失败:{}",eventMessage);
throw new BizException(BizCodeEnum.MQ_CONSUME_EXCEPTION);
}
}
#### 第2集 账号服务-RabbitMQ相关配置开发实战
简介:账号服务-RabbitMQ相关配置开发实战
- 账号服务RabbitMQ配置
##----------rabbit配置--------------
spring.rabbitmq.host=120.79.150.146
spring.rabbitmq.port=5672
#需要手工创建虚拟主机
spring.rabbitmq.virtual-host=dev
spring.rabbitmq.username=admin
spring.rabbitmq.password=password
#消息确认方式,manual(手动ack) 和auto(自动ack); 消息消费重试到达指定次数进到异常交换机和异常队列,需要改为自动ack确认消息
spring.rabbitmq.listener.simple.acknowledge-mode=auto
#开启重试,消费者代码不能添加try catch捕获不往外抛异常
spring.rabbitmq.listener.simple.retry.enabled=true
#最大重试次数
spring.rabbitmq.listener.simple.retry.max-attempts=4
# 重试消息的时间间隔,5秒
spring.rabbitmq.listener.simple.retry.initial-interval=5000
- RabbitMQ配置
```
@Configuration
@Data
public class RabbitMQConfig {
/
消息转换器
@return
/
@Bean
public MessageConverter messageConverter(){
return new Jackson2JsonMessageConverter();
}
}
- 异常消息配置
@Configuration
@Slf4j
public class RabbitMQErrorConfig {
/**
异常交换机
/
private String trafficErrorExchange = “traffic.error.exchange”;
/**
异常队列
/
private String trafficErrorQueue = “traffic.error.queue”;
/**
异常routing.key
/
private String trafficErrorRoutingKey = “traffic.error.routing.key”;
@Autowired
private RabbitTemplate rabbitTemplate;
/**
创建异常交换机
@return
/
@Bean
public TopicExchange errorTopicExchange(){
return new TopicExchange(trafficErrorExchange,true,false);
}
/
创建异常队列
@return
*/
@Bean
public Queue errorQueue(){
return new Queue(trafficErrorQueue,true);
}
/
建立绑定关系
@return
/
@Bean
public Binding bindingErrorQueueAndExchange(){
return BindingBuilder.bind(errorQueue()).to(errorTopicExchange()).with(trafficErrorRoutingKey);
}
/**
配置 RepublishMessageRecoverer
消费消息重试一定次数后,用特定的routingKey转发到指定的交换机中,方便后续排查和告警
@return
/
@Bean
public MessageRecoverer messageRecoverer(){
return new RepublishMessageRecoverer(rabbitTemplate,trafficErrorExchange,trafficErrorRoutingKey);
}
}
<a name="a5776e96"></a>
#### 第3集 账号服务-流量包模块Manager层开发
**简介: 账号服务-流量包模块Manager层开发**
-
流量包基础模块接口开发
- 锻炼大家的发现问题能力(存在一些Bug,自己先找出)
- 除了学架构能力,还需要业务逻辑能力锻炼
- codeReview
![](img/image-20220124110958375.png#alt=image-20220124110958375)
public interface TrafficManager {
/**
新增流量包
@param trafficDO
@return
/
int add(TrafficDO trafficDO);
/**
分页查询可用的
@param page
@param size
@param accountNo
@return
/
IPage@Override
@Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED)
public void handleTrafficMessage(EventMessage eventMessage) {
String messageType = eventMessage.getEventMessageType();
if(EventMessageType.PRODUCT_ORDER_PAY.name().equalsIgnoreCase(messageType)){
//订单已经支付,新增流量
String content = eventMessage.getContent();
Map<String, Object> orderInfoMap = JsonUtil.json2Obj(content,Map.class);
//还原订单商品信息
Long accountNo = (Long)orderInfoMap.get("accountNo");
String outTradeNo = (String)orderInfoMap.get("outTradeNo");
Integer buyNum = (Integer)orderInfoMap.get("buyNum");
String productStr = (String) orderInfoMap.get("product");
ProductVO productVO = JsonUtil.json2Obj(productStr, ProductVO.class);
log.info("商品信息",productVO);
//流量包有效期
LocalDateTime expiredDateTime = LocalDateTime.now().plusDays(productVO.getValidDay());
Date date = Date.from(expiredDateTime.atZone(ZoneId.systemDefault()).toInstant());
//构建流量包对象
TrafficDO trafficDO = TrafficDO.builder()
.accountNo(accountNo)
.dayLimit(productVO.getDayTimes() * buyNum)
.dayUsed(0)
.totalLimit(productVO.getTotalTimes())
.pluginType(productVO.getPluginType())
.level(productVO.getLevel())
.productId(productVO.getId())
.outTradeNo(outTradeNo)
.expiredDate(date).build();
int rows = trafficManager.add(trafficDO);
log.info("消费消息新增流量包:rows={},trafficDO={}",rows,trafficDO);
}
}
#### 第5集 账号服务-支付发放流量包消费者链路测试
简介:账号服务-支付发放流量包消费者链路测试
-
注意事项
- 支付参数是否过期
- 域名映射是否过期
- 清空历史数据
-
Bug修复
- 消费者缺少注解
-
链路测试(查看数据库数据)
- 下单
- 支付
- 订单更新
- 流量包记录新增
愿景:”让编程不再难学,让技术与生活更加有趣”
### 第四十四章 流量包管理 +用户注册免费流量包发放开发实战
#### 第1集 流量包管理相关API模块开发实战
简介:流量包管理相关API模块开发实战
- 分页API+详情API
```
/
只能查看可用的流量包,不可用的需要去归档数据查
@param page
@param size
@return
/
@GetMapping(“page”)
public JsonData pageAvailable(
@RequestBody TrafficPageRequest request
) {
MapUNIQUE KEY `uk_trade_no` (`out_trade_no`,`account_no`) USING BTREE,
#### 第4集 新用户注册-MQ生产者发送免费流量包开发实战
简介: 新用户注册-MQ生产者发送免费流量包开发实战
- RabbitMQ配置
```
//================流量包处理:用户初始化福利==================================
/
交换机
/
private String trafficEventExchange = “traffic.event.exchange”;
/
用户注册 免费流量包新增 队列
/
private String trafficFreeInitQueue = “traffic.free_init.queue”;
/
用户注册 免费流量包新增 队列路由key
/
private String trafficFreeInitRoutingKey = “traffic.free_init.routing.key”;
/**
创建交换机 Topic类型
一般一个微服务一个交换机
@return
/
@Bean
public Exchange trafficEventExchange(){
return new TopicExchange(trafficEventExchange,true,false);
}
/**
队列的绑定关系建立:新用户注册免费流量包
@return
/
@Bean
public Binding trafficFreeInitBinding(){
return new Binding(trafficFreeInitQueue,Binding.DestinationType.QUEUE, trafficEventExchange,trafficFreeInitRoutingKey,null);
}
/
免费流量包队列
/
@Bean
public Queue trafficFreeInitQueue(){
return new Queue(trafficFreeInitQueue,true,false,false);
}
- 发送消息开发
/
免费流量包ID
/
private static final Long FREE_TRAFFIC_PRODUCT_ID = 1L;
/
用户注册,初始化福利信息
注册成功后在发送
(消费前查询下是否有这个用户,非必要)
@param accountDO
/
private void userRegisterInitTask(AccountDO accountDO,int productId) {
EventMessage freeTrafficEventMessage = EventMessage.builder()
.messageId(IDUtil.geneSnowFlakeID().toString())
.accountNo(accountDO.getAccountNo())
.eventMessageType(EventMessageType.TRAFFIC_FREE_INIT.name())
.bizId(FREE_TRAFFIC_PRODUCT_ID.toString())
.build();
rabbitTemplate.convertAndSend(rabbitMQConfig.getTrafficEventExchange(),
rabbitMQConfig.getTrafficFreeInitRoutingKey(), freeTrafficEventMessage);
}
```
#### 第5集 新用户注册-MQ消费者开发+Feign调用流量包商品服务实战
简介: 新用户注册-MQ消费者开发+Feign调用流量包商品服务实战
- 消费者开发
else if (EventMessageType.TRAFFIC_FREE_INIT.name().equals(messageType)) {
//新用户注册初始化免费流量包 user_init 唯一索引,避免重复消费
Long productId = Long.valueOf(eventMessage.getBizId());
JsonData jsonData = productFeignService.detail(productId);
ProductVO productVO = jsonData.getData(new TypeReference<ProductVO>() {
});
TrafficDO trafficDO = TrafficDO.builder()
.accountNo(accountNo)
.dayLimit(productVO.getDayTimes())
.dayUsed(0)
.totalLimit(productVO.getTotalTimes())
.pluginType(productVO.getPluginType())
.level(PluginLevelEnum.FIRSR.name())
.productId(productVO.getId())
.expiredDate(new Date())
.outTradeNo("user_init")
.build();
trafficManager.add(trafficDO);
return true;
}
- Feign调用商品服务开发
```
@FeignClient(name = “dcloud-shop-service”)
public interface ProductFeignService {
/
获取商品详情
@param product_id 商品id
@return
/
@GetMapping(value = “/api/product/v1/detail/{product_id}”)
JsonData detail(@PathVariable(“product_id”) long productId);
}
<a name="86961c5c"></a>
#### 第6集 新用户注册-发放免费流量包全链路测试实战
**简介: 新用户注册-发放免费流量包全链路测试实战**
- 链路测试
- 注册登录
- 短信选择申请接入阿里云市场:[https://market.console.aliyun.com/imageconsole/index.htm](https://market.console.aliyun.com/imageconsole/index.htm)
- 购买流量包
- 支付参数
- 回调域名
- 查看可用流量包列表
![](https://file.xdclass.net/note/2020/javaweb/%E5%9B%BE%E7%89%87/logo.png#alt=logo) **愿景:"让编程不再难学,让技术与生活更加有趣"**
<a name="5a6df39e"></a>
### 第四十五章 新版分布式定时调度XXL-Job介绍和环境搭建
<a name="2b5ea5d5"></a>
#### 第1集 流量包过期业务需求和定时任务快速介绍
**简介:流量包过期业务需求和解决方案讲解**
-
需求
- 用户购买的流量包都是有时间限制,过期的流量包需要删除
- 逻辑删除、物理删除、或者转移到日志文件归档都行
- 我们这边直接使用物理删除,比数据过多
-
解决方式
- 使用定时任务删除
- 使用场景
- 某个时间定时处理某个任务、发邮件、短信等
- 消息提醒、订单通知、统计报表系
![](img/image-20220208133306231.png#alt=image-20220208133306231)
-
定时任务划分
- 单机定时任务
- 单机的容易实现,但应用于集群环境做分布式部署,就会带来重复执行
- 解决方案有很多比如加锁、数据库等,但是增加了很多非业务逻辑
- 分布式调度
- 把需要处理的计划任务放入到统一的平台,实现集群管理调度与分布式部署的定时任务 叫做分布式定时任务
- 支持集群部署、高可用、并行调度、分片处理等
-
常见定时任务
-
单机:Java自带的java.util.Timer类配置比较麻烦,时间延后问题
-
单机:ScheduledExecutorService
- 是基于线程池来进行设计的定时任务类,在这里每个调度的任务都会分配到线程池里的一个线程去执行该任务,并发执行,互不影响
-
单机:SpringBoot框架自带
-
SpringBoot使用注解方式开启定时任务
-
启动类里面 @EnableScheduling开启定时任务,自动扫描
-
定时任务业务类 加注解 @Component被容器扫描
-
定时执行的方法加上注解 @Scheduled(fixedRate=2000) 定期执行一次
- 分布式任务调度框架
- Elastic-Job
- XXL-Job
- Quartz
<a name="383d6c3d"></a>
#### 第2集 业界主流分布式任务调度框架介绍
**简介:业界主流分布式任务调度框架介绍**
-
常见分布式定时任务
-
Quartz
- Quartz关注点在于定时任务而并非是数据,并没有一套根据数据化处理而定的流程
- 虽然可以实现数据库作业的高可用,但是缺少了分布式的并行调度功能,相对弱点
- 不支持任务分片、没UI界面管理,并行调度、失败策略等也缺少
-
TBSchedule
- 这个是阿里早期开源的分布式任务调度系统,使用的是timer而不是线程池执行任务调度,使用timer在处理异常的时候是有缺陷的,但TBSchedule的作业类型比较单一,文档也缺失得比较严重
- 目前阿里内部使用的是ScheduleX
- 阿里云也有商业化版本: [https://help.aliyun.com/product/147760.html](https://help.aliyun.com/product/147760.html)
![](img/image-20220210111019331.png#alt=image-20220210111019331)
-
Elastic-job
- 当当开发的分布式任务调度系统,功能强大,采用的是zookeeper实现分布式协调,具有高可用与分片。
- 2020年6月,ElasticJob的四个子项目已经正式迁入Apache仓库
- 由 2 个相互独立的子项目 ElasticJob-Lite 和 ElasticJob-Cloud 组成
- ElasticJob-Lite 定位为轻量级无中心化解决方案,使用jar的形式提供分布式任务的协调服务;
- ElasticJob-Cloud 使用 Mesos 的解决方案,额外提供资源治理、应用分发以及进程隔离等服务
- 地址:[https://shardingsphere.apache.org/elasticjob/index_zh.html](https://shardingsphere.apache.org/elasticjob/index_zh.html)
-
XXL-JOB
- 大众点评的员工徐雪里在15年发布的分布式任务调度平台,是轻量级的分布式任务调度框架,目标是开发迅速、简单、清理、易扩展; 老版本是依赖quartz的定时任务触发,在v2.1.0版本开始 移除quartz依赖
- 地址:[https://www.xuxueli.com/xxl-job/](https://www.xuxueli.com/xxl-job/)
-
常规对比图
| 对比项 | XXL-JOB | elastic-job |
| --- | --- | --- |
| 并行调度 | 调度系统多线程并行 | 任务分片的方式并行 |
| 弹性扩容 | 使用Quartz基于数据库分布式功能 | 通过zookeeper保证 |
| 高可用 | 通过DB锁保证 | 通过zookeeper保证 |
| 阻塞策略 | 单机串行/丢弃后续的调度/覆盖之前的调度 | 执行超过zookeeper的session timeout时间的话,会被清除,重新进行分片 |
| 动态分片策略 | 以执行器为维度进行分片、支持动态的扩容 | 平均分配/作业名hash分配/自定义策略 |
| 失败处理策略 | 失败告警/失败重试 | 执行完毕后主动获取未分配分片任务<br />服务器下线后主动寻找可以用的服务器执行任务 |
| 监控 | 支持 | 支持 |
| 日志 | 支持 | 支持 |
-
如何选择哪一个分布式任务调度平台
-
XXL-Job和Elastic-Job都具有广泛的用户基础和完善的技术文档,都可以满足定时任务的基本功能需求
-
xxl-job侧重在业务实现简单和管理方便,容易学习,失败与路由策略丰富, 推荐使用在用户基数相对较少,服务器的数量在一定的范围内的场景下使用
-
elastic-job关注的点在数据,添加了弹性扩容和数据分片的思路,更方便利用分布式服务器的资源, 但是学习难度较大,推荐在数据量庞大,服务器数量多的时候使用
<a name="df2f7401"></a>
#### 第3集 快速入门分布式调度XXL-Job架构和概念说明
**简介:快速入门分布式调度XXL-Job架构和概念说明**
-
注意
- 这里只做快速入门讲解+回顾,深入看的话可以去小滴课堂看《XXL-Job分布式调度专题》
-
xxl-job的设计思想
- 将调度行为抽象形成“调度中心”公共平台,而平台自身并不承担业务逻辑,“调度中心”负责发起调度请求。
- 将任务抽象成分散的JobHandler,交由“执行器”统一管理
- “执行器”负责接收调度请求并执行对应的JobHandler中业务逻辑。
- 因此,“调度”和“任务”两部分可以相互解耦,提高系统整体稳定性和扩展性
![](img/image-20220208144809498.png#alt=image-20220208144809498)
-
架构系统组成
-
调度中心
- 负责管理调度的信息,按照调度的配置来发出调度请求
- 支持可视化、简单的动态管理调度信息,包括新建、删除、更新等,这些操作都会实时生效,同时也支持监控调度结果以及执行日志。
-
执行器
- 负责接收请求并且执行任务的逻辑。任务模块专注于任务的执行操作等等,使得开发和维护更加的简单与高效
-
架构图(图片来源是xxl-job官网)
![](img/image-20220208142140423.png#alt=image-20220208142140423)
- XXL-Job具有哪些特性
- 调度中心HA(中心式):调度采用了中心式进行设计,“调度中心”支持集群部署,可保证调度中心HA
- 执行器HA(分布式):任务分布式的执行,任务执行器支持集群部署,可保证任务执行HA
- 触发策略:有Cron触发、固定间隔触发、固定延时触发、API事件触发、人工触发、父子任务触发
- 路由策略:执行器在集群部署的时候提供了丰富的路由策略,如:第一个、最后一个、轮询、随机、一致性HASH、最不经常使用LFU、最久未使用LRU、故障转移等等
- 故障转移:如果执行器集群的一台机器发生故障,会自动切换到一台正常的执行器发送任务调度
- Rolling实时日志的监控:支持rolling方式查看输入的完整执行日志
- 脚本任务:支持GLUE模式开发和运行脚本任务,包括Shell、python、node.js、php等等类型脚本
<a name="e353f3fe"></a>
#### 第4集 分布式调度XXl-Job搭建-Docker部署服务端《上》
**简介:分布式调度XXl-Job搭建-Docker部署服务端《上》**
-
搭建XXL-Job相关环境
- 创建数据库脚本
- 部署XXL-Job服务端
- 客户端项目添加依赖
-
注意
- Client-Server通信,需要网络互通才行
- 所以不能一个是阿里云ECS,一个是本地电脑
- 建议:本地电脑安装Docker,或者本地Linux虚拟机安装Docker部署
-
步骤
- 步骤一:数据库脚本(使用mysql8)
- xxl_job 的数据库里有如下几个表
- xxl_job_group:执行器信息表,用于维护任务执行器的信息
- xxl_job_info:调度扩展信息表,主要是用于保存xxl-job的调度任务的扩展信息,比如说像任务分组、任务名、机器的地址等等
- xxl_job_lock:任务调度锁表
- xxl_job_log:日志表,主要是用在保存xxl-job任务调度历史信息,像调度结果、执行结果、调度入参等等
- xxl_job_log_report:日志报表,会存储xxl-job任务调度的日志报表,会在调度中心里的报表功能里使用到
- xxl_job_logglue:任务的GLUE日志,用于保存GLUE日志的更新历史变化,支持GLUE版本的回溯功能
- xxl_job_registry:执行器的注册表,用在维护在线的执行器与调度中心的地址信息
- xxl_job_user:系统的用户表
![](img/image-20220208151020191.png#alt=image-20220208151020191)
<a name="02ff3983"></a>
#### 第5集 分布式调度XXl-Job搭建-Docker部署服务端《下》
**简介:分布式调度XXl-Job搭建-Docker部署服务端《下》**
-
步骤二:部署server
docker run -d -e PARAMS=”—spring.datasource.url=jdbc:mysql://120.79.150.146:3306/xxl_job?Unicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai \
—spring.datasource.username=root \
—spring.datasource.password=xdclass.net168 \
—xxl.job.accessToken=xdclass.net” \
-p 8080:8080 \
—name xxl-job-admin —restart=always xuxueli/xxl-job-admin:2.2.0
- 访问地址 [http://127.0.0.1:8080/xxl-job-admin](http://127.0.0.1:8080/xxl-job-admin)
- 默认登录账号 admin / 123456
<a name="7d2772d0"></a>
#### 第6集 分布式调度XXL-Job 可视化UI界面介绍
**简介:分布式调度XXL-Job 可视化UI界面介绍**
- 运行报表
- 以图形化来展示了整体的任务执行情况
- 任务数量:能够看到调度中心运行的任务数量
- 调度次数:调度中心所触发的调度次数
- 执行器数量:在整个调度中心中,在线的执行器数量有多少
![](img/image-20220208153017295.png#alt=image-20220208153017295)
- 任务管理
- 这里是配置执行任务/路由策略/Cron/JobHandler等
![](img/image-20220208153317555.png#alt=image-20220208153317555)
- 调度日志
- 这里是查看调度的日志,根据日志来查看任务具体的执行情况是怎样的
![](img/image-20220208153435620.png#alt=image-20220208153435620)
- 执行器管理
- 这里是配置执行器,等待执行器启动的时候都会被调度中心监听加入到地址列表
![](img/image-20220208153513964.png#alt=image-20220208153513964)
![](https://file.xdclass.net/note/2020/javaweb/%E5%9B%BE%E7%89%87/logo.png#alt=logo) **愿景:"让编程不再难学,让技术与生活更加有趣"**
<a name="e3728789"></a>
### 第四十六章 分布式定时调度XXL-Job和流量包过期业务整合
<a name="2ff728ad"></a>
#### 第1集 AlibabaCloud微服务整合XXL-Job依赖实战
**简介:AlibabaCloud微服务整合XXL-Job依赖实战**
- 聚合工程添加依赖
- common项目添加依赖
- 新增logback.xml
<?xml version=”1.0” encoding=”UTF-8”?>
- application.properties配置文件
#—————xxl-job配置———————
logging.config=classpath:logback.xml
#调度中心部署地址,多个配置逗号分隔 “http://address01,http://address02“
xxl.job.admin.addresses=http://127.0.0.1:8080/xxl-job-admin
#执行器token,非空时启用 xxl-job, access token
xxl.job.accessToken=xdclass.net
# 执行器app名称,和控制台那边配置一样的名称,不然注册不上去
xxl.job.executor.appname=traffic-app-executor
# [选填]执行器注册:优先使用该配置作为注册地址,为空时使用内嵌服务 ”IP:PORT“ 作为注册地址。
#从而更灵活的支持容器类型执行器动态IP和动态映射端口问题。
xxl.job.executor.address=
#[选填]执行器IP :默认为空表示自动获取IP(即springboot容器的ip和端口,可以自动获取,也可以指定),多网卡时可手动设置指定IP,该IP不会绑定Host仅作为通讯实用;地址信息用于 “执行器注册” 和 “调度中心请求并触发任务”,
xxl.job.executor.ip=
# [选填]执行器端口号:小于等于0则自动获取;默认端口为9999,单机部署多个执行器时,注意要配置不同执行器端口;
xxl.job.executor.port=9999
#执行器日志文件存储路径,需要对该路径拥有读写权限;为空则使用默认路径
xxl.job.executor.logpath=./data/logs/xxl-job/executor
#执行器日志保存天数
xxl.job.executor.logretentiondays=30
- 编码
@Configuration
@Slf4j
public class XxlJobConfig {
@Value(“${xxl.job.admin.addresses}”)
private String adminAddresses;
@Value(“${xxl.job.executor.appname}”)
private String appName;
@Value(“${xxl.job.executor.ip}”)
private String ip;
@Value(“${xxl.job.executor.port}”)
private int port;
@Value(“${xxl.job.accessToken}”)
private String accessToken;
@Value(“${xxl.job.executor.logpath}”)
private String logPath;
@Value(“${xxl.job.executor.logretentiondays}”)
private int logRetentionDays;
//旧版的有bug
//@Bean(initMethod = “start”, destroyMethod = “destroy”)
@Bean
public XxlJobSpringExecutor xxlJobExecutor() {
log.info(“>>>>>>>>>>> xxl-job config init.”);
XxlJobSpringExecutor xxlJobSpringExecutor = new XxlJobSpringExecutor();
xxlJobSpringExecutor.setAdminAddresses(adminAddresses);
xxlJobSpringExecutor.setAppname(appName);
xxlJobSpringExecutor.setIp(ip);
xxlJobSpringExecutor.setPort(port);
xxlJobSpringExecutor.setAccessToken(accessToken);
xxlJobSpringExecutor.setLogPath(logPath);
xxlJobSpringExecutor.setLogRetentionDays(logRetentionDays);
return xxlJobSpringExecutor;
}
}
<a name="5293e7e8"></a>
#### 第2集 创建你的第一个XXL-Job分布式调度任务
**简介:创建你的第一个XXL-Job分布式调度任务**
-
Bug
- pom文件 版本使用2.2.0
-
注解介绍
- 在 Spring Bean 实例中,开发 Job 方法方式格式要求为
public ReturnT- 为 Job 方法添加注解 (注解value值是调度中心新建任务的 JobHandler 属性的值)
@XxlJob(value=”自定义jobHandler名称”, init = “handler初始化方法”, destroy = “handler 销毁方法”)
-
编写代码
@Component
@Slf4j
public class MyJobHandler {
@XxlJob(value = “demoJobHandler”,init = “init”,destroy = “destroy”)
public ReturnT- 执行器管理
- 到xxl-job调度中心里的执行器管理->新增
- Appname:是每一个执行器的唯一表示AppName,执行器会以周期性为appname进行注册,为任务调度的时候使用
- 名称:执行器的名称,因为appname有限制字母与数字等等组成,可读性不强,这个名称就是为了提高执行器的可读性
- 注册方式:调度中心获取执行器地址的方式
- 自动注册:执行器自动进行执行器的注册,通过底层的注册表可以动态的发现执行器机器的地址
- 手动录入:人工手动录入执行器的地址信息,多地址使用逗号进行分割,供调度中心使用
- 机器地址:“注册方式”为手动录入的时候才能使用,支持人工维护执行器的地址
- 点击保存后可能要等30S左右才回显示机器的地址
![](img/image-20220209114917415.png#alt=image-20220209114917415)
- 任务管理
- 任务管理->选择所需要管理的执行器->新增执行器
![](img/image-20220209115112933.png#alt=image-20220209115112933)
- 任务管理-启动任务
- 任务管理的状态已经从STOP变更成RUNNING
![](img/image-20220209115403082.png#alt=image-20220209115403082)
- 调度日志
- 在调度日志里能够查看调度结果与日志的信息
![](img/image-20220209115509256.png#alt=image-20220209115509256)
<a name="b2fe877b"></a>
#### 第3集 付费流量包过期淘汰需求开发-整合分布式调度XXL-Job
**简介:付费流量包过期淘汰需求开发-整合分布式调度XXL-Job**
-
流量包删除接口
- 删除过期流量包记录
delete FROM traffic where expired_date <= now()
-
思考下有什么问题
![](img/image-20220209150128960.png#alt=image-20220209150128960)
<a name="5f3b7079"></a>
#### 第4集 海量数据处理维护三连问和流量包更新需求
**简介:海量数据处理维护三连问和流量包更新需求**
-
流量包更新维护需求
- 付费流量包:通过购买,然后每天都是有一定的使用次数
- 免费流量包:业务为了拉新,鼓励新用户注册,赠送一个免费流量包,每天允许有一定次免费创建短链的次数
![](img/image-20220124183548794.png#alt=image-20220124183548794)
-
怎么维护更新好流量包每天的次数呢
<br />![](img/image-20220211121339274.png#alt=image-20220211121339274)
-
留给老王的作业,下一章讲解决方案
![](https://file.xdclass.net/note/2020/javaweb/%E5%9B%BE%E7%89%87/logo.png#alt=logo) **愿景:"让编程不再难学,让技术与生活更加有趣"**
<a name="ab15f1c4"></a>
### 第四十七章 【重点】海量数据下-流量包更新维护解决方案
<a name="1cc95ecd"></a>
#### 第1集 中间件设计思想-Redis过期Key淘汰策略回顾
**简介:中间件设计思想-Redis过期Key淘汰策略回顾**
- 背景
- redis的key配置了过期时间,这个是怎么被删除的
- redis数据明明过期了,怎么还占用着内存?
- 如果是你怎么设计
- 初级工程师
- 对于如何清除过期的 key ,很自然的可以想到就是可以给每个 key 加一个定时器
- 这样当时间到达过期时间的时候就自动删除 key,这种定时策略叫 **主动策略**
- 每个带有过期时间的 key 都需要一个定时器,对 CPU 是不友好的,会占用很多的 CPU,这种方式是一种主动的行为
- 高级工程师
- 也可以不用定时器,采取被动的方式,在访问一个 key 的时候去判断这个 key 是否到达过期时间了,过期了就删除掉,这种叫做**惰性策略**
![](img/image-20220211151316390.png#alt=image-20220211151316390)
-
key过期是否立即从内存中删除?
-
Redis key过期策略: 定期删除+惰性删除
-
不会立即从内存中删除,当过期key未被客户端调用且未达到执行主动策略的时间,此key依旧存在内存中
-
Redis如何淘汰过期的keys, set name xdclass 3600(每个设置了过期时间的 key 放入到一个独立的容器中)
-
定期删除:
- 隔一段时间,就随机抽取一些设置了过期时间的 key,检查其是否过期,如果过期就删除,
- 定期删除可能会导致很多过期 key 到了时间并没有被删除掉
- 官方文档:[https://www.redis.io/commands/expire](https://www.redis.io/commands/expire)
![](img/image-20220211155152591.png#alt=image-20220211155152591)
Specifically this is what Redis does 10 times per second:
Test 20 random keys from the set of keys with an associated expire.
Delete all the keys found expired.
If more than 25% of keys were expired, start again from step 1.
-
Redis 会每秒进行十次过期扫描,过期扫描不会遍历容器中所有的 key,而是采用一种特殊策略
-
从容器中随机 20 个 key;
-
删除这 20 个 key 中已经过期的 key;
-
如果过期的 key 比率超过 1/4,那就重复步骤 1;
-
惰性删除 :
- 当某个客户端试图访问key时,发现该key已超时会把此key从内存中删除
- 如果Redis是主从复制
- 从节点不会让key过期,而是主节点的key过期删除后,成为del命令传输到从节点进行删除
- 主库在 key 到期时,会在 AOF 文件里增加一条 del 指令,同步到所有的从库,从库通过执行这条 del 指令来删除过期的 key
- 指令同步是异步进行的,所以主库过期的 key 的 del 指令没有及时同步到从库的话,会出现主从数据的不一致,主库没有的数据在从库里还存在
-
Redis服务器实际使用的是惰性删除和定期删除两种策略
-
通过配合使用这两种删除策略,服务器可以很好地在合理使用CPU时间和避免浪费内存空间之间取得平衡。
-
问题
-
如果定期删除漏掉了很多过期 key,然后你也没及时去查,也就没走惰性删除,此时会怎么样?
-
如果大量过期 key 堆积在内存里,导致 redis 内存块耗尽了,就需要走内存淘汰机制
- 看到这里,你有什么思考?有哪些设计思想可以应用到我们短链平台-流量包模块?
<a name="72bb75eb"></a>
#### 第2集 惰性策略思想在主流中间件架构中的应用讲解
**简介:惰性策略思想在主流中间件架构中的应用讲解**
![](img/image-20220212140956958.png#alt=image-20220212140956958)
-
设计模式-单例创建
- 懒汉方式:就是所谓的懒加载,延迟创建对象
- 优点:前期不占据应用内存,用时创建
- 缺点: 初次创建对象有延迟
- 饿汉方式:提前创建好对象
- 优点:实现简单,使用时没延迟
- 缺点:不管有没使用,instance对象一直占着这段内存
- 如何选择:
- 如果对象不大,且创建不复杂,直接用饿汉的方式即可
- 其他情况则采用懒汉实现方式
-
Mybatis
- 懒加载
- 按需加载,先从单表查询,需要时再从关联表去关联查询,能大大提高数据库性能, 并不是所有场景下使用懒加载都能提高效率
- ResultMap里面的association、collection有延迟加载功能
-
Redis过期可以淘汰
- 定期删除 : 隔一段时间,就随机抽取一些设置了过期时间的 key,检查其是否过期,如果过期就删除,
- 惰性删除: 当某个客户端试图访问key时,发现该key已超时会把此key从内存中删除
-
Spring中bean创建懒加载(延迟加载)
- 单实例 bean : 默认在容器启动的时候创建对象;
- 懒加载: 容器启动不创建对象。第一次使用的时候也就是获取bean创建对象,并且初始化,使用@lazy注解
![](img/35e95df28fd34b449830a17ea742bf09.png#alt=img)
<a name="3eef9609"></a>
#### 第3集 海量数据下流量包更新-惰性策略解决方案讲解
** 简历:海量数据下流量包更新-惰性策略解决方案讲解**
-
流量包更新维护需求
- 付费流量包:通过购买,然后每天都是有一定的使用次数
- 免费流量包:业务为了拉新,鼓励新用户注册,赠送一个免费流量包,每天允许有一定次免费创建短链的次数
-
采用惰性策略解决方案
- 不用每天更新全部流量包,用的时候再更新即可
- 好处
- 只要用户有使用,流量包都是可以得到更新
- 没使用的用户流量包不会去更新,避免了海量数据下更新维护的问题
- 如果采用定时更新,几千万用户更新记录都是会有不少时间的延迟
- 更进阶的作业
- 可以定时 + 惰性
![](img/image-20220212153922069.png#alt=image-20220212153922069)
-
大体步骤
-
查询用户全部可用流量包
-
遍历用户可用流量包
- 判断是否更新-用日期判断(要么都更新过,要么都没更新,根据gmt_modified)
- 没更新的流量包后加入【待更新集合】中
- 增加【今天剩余可用总次数】
- 已经更新的判断是否超过当天使用次数
- 如果没超过则增加【今天剩余可用总次数】
- 超过则忽略
-
更新用户今日流量包相关数据
-
扣减使用的某个流量包使用次数
id
bigint unsigned NOT NULL AUTO_INCREMENT,
day_limit
int DEFAULT NULL COMMENT ‘每天限制多少条,短链’,
day_used
int DEFAULT NULL COMMENT ‘当天用了多少条,短链’,
expired_date
date DEFAULT NULL COMMENT ‘过期日期’,
gmt_modified
datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
<a name="fa8bf6d2"></a>
#### 第4集 海量数据下流量包更新-惰性策略编码开发实战《上》
**简介:海量数据下流量包更新-惰性策略编码开发实战《上》**
-
伪代码
-
查询用户全部可用流量包
-
遍历用户可用流量包
- 判断是否更新-用日期判断
- 没更新的流量包后加入【待更新集合】中
- 增加【今天剩余可用总次数】
- 已经更新的判断是否超过当天使用次数
- 如果没超过则增加【今天剩余可用总次数】
- 超过则忽略
-
更新用户今日流量包相关数据
-
扣减使用的某个流量包使用次数
-
开发流量包相关的Manager层
/**
<a name="e80e4269"></a>
#### 第5集 流量包模块Manager层单元测试和Bug修复
**简介:流量包模块Manager层单元测试和Bug修复**
-
大家可能遇到的问题
- sql边界值问题 或 逻辑问题
- MybatisPlus异常:Invalid bound statement (not found)
-
查找用户全部流量包(付费和免费)
public List- 给某个流量包增加天使用次数
- 恢复流量包
- 单元测试
@Test
public void testSelectAvailableTraffics() {
List<a name="ffbd2288"></a>
#### 第6集 海量数据下流量包更新-惰性策略编码开发实战《下》
**简介:海量数据下流量包更新-惰性策略编码开发实战《下》**
-
伪代码
-
查询用户全部可用流量包
-
遍历用户可用流量包
- 判断是否更新-用日期判断
- 没更新的流量包后加入【待更新集合】中
- 增加【今天剩余可用总次数】
- 已经更新的判断是否超过当天使用次数
- 如果没超过则增加【今天剩余可用总次数】
- 超过则忽略
-
更新用户今日流量包相关数据
-
扣减使用的某个流量包使用次数
-
组装业务逻辑
@Data
@AllArgsConstructor
@NoArgsConstructor
public class UseTrafficVO {
/**
<a name="b1564145"></a>
#### 第7集 海量数据下流量包更新-惰性策略编码测试
**简介:海量数据下流量包更新-惰性策略编码测试**
- 流量包更新-惰性策略编码测试
![](https://file.xdclass.net/note/2020/javaweb/%E5%9B%BE%E7%89%87/logo.png#alt=logo) **愿景:"让编程不再难学,让技术与生活更加有趣"**
<a name="61712c6d"></a>
### 第四十八章 【重点】短链服务+流量包业务联动架构设计
<a name="25d50819"></a>
#### 第1集 短链服务和流量包业务联动RPC调用链路性能问题
**简介:短链服务和流量包业务联动RPC调用性能问题优化**
- 性能需求分析
- 创建短链->扣减流量包(RPC)->发送MQ消息
![](img/image-20220216135952510.png#alt=image-20220216135952510)
<a name="da1f6b31"></a>
#### 第2集 高并发下扣减流量包解决方案讲解-通用秒杀
**简介:高并发下扣减流量包解决方案讲解-通用秒杀**
- 扣减流量包性能优化解决方案
![](img/image-20220216132820291.png#alt=image-20220216132820291)
![](img/image-20220216152039729.png#alt=image-20220216152039729)
<a name="10970203"></a>
#### 第3集 高并发下-创建短链和流量包业务联动开发
**简介:短链服务-创建短链和流量包业务联动开发**
-
需求
- 创建短链
- 扣减流量包
- 发送MQ
-
编码实战
- 鉴权增加token(多种方式)
#用于rpc调用token验证
rpc.token=xdclass.net168
- 短链服务编码
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class UseTrafficRequest {
/
账号
/
private Long accountNo;
/
业务id, 短链码
/
private String bizId;
}
@FeignClient(name = “dcloud-account-service”)
public interface TrafficFeignService {
/
使用流量包
@param shortLinkCode 业务id,短链则是短链码
@return
*/
@PostMapping(value = “/api/traffic/v1/reduce”, headers = {“rpc-token=${rpc.token}”})
JsonData useTraffic(@RequestBody UseTrafficRequest request);
}
- C端消费者-扣减流量包
//先判断是否短链码被占用
ShortLinkDO shortLinCodeDOInDB = shortLinkManager.findByShortLinCode(shortLinkCode);
if (shortLinCodeDOInDB == null) {
boolean reduceFlag = reduceTraffic(eventMessage,shortLinkCode);
if (reduceFlag) {
ShortLinkDO shortLinkDO = ShortLinkDO.builder().accountNo(accountNo).code(shortLinkCode) .title(addRequest.getTitle()).originalUrl(addRequest.getOriginalUrl())
.domain(domainDO.getValue()).groupId(linkGroupDO.getId())
.expired(addRequest.getExpired()).sign(originalUrlDigest)
.state(ShortLinkStateEnum.ACTIVE.name()).del(0).build();
shortLinkManager.addShortLink(shortLinkDO);
return true;
}
}
/
扣减流量包
@param eventMessage
@return
/
private boolean reduceTraffic(EventMessage eventMessage,String shortLinkCode) {
//处理流量包扣减
//根据短链类型,检查是否有足够多的流量包 分布式事务问题
UseTrafficRequest request = UseTrafficRequest.builder()
.accountNo(eventMessage.getAccountNo())
.bizId(shortLinkCode)
.build();
JsonData jsonData = trafficFeignService.useTraffic(request);
//使用流量包
if (jsonData.getCode() != 0) {
log.error(“流量包不足,扣减失败操:{}”, eventMessage);
return false;
}
return true;
}
- 账号服务编码
@Value(“${rpc.token}”)
private String rpcToken;
@PostMapping(“reduce”)
public JsonData useTraffic(@RequestBody UseTrafficRequest useTrafficRequest, HttpServletRequest request){
//具体使用流量包逻辑
String requestToken = request.getHeader(“rpc-token”);
if (rpcToken.equalsIgnoreCase(requestToken)) {
JsonData jsonData = trafficService.reduce(useTrafficRequest);
return jsonData;
} else {
return JsonData.buildError(“非法访问”);
}
}
拦截器配置 “/api/traffic//reduce” 不进行登录拦截
<a name="2c2eaa9b"></a>
#### 第4集 高并发下扣减流量包-日剩余流量包计算存储Redis
**简介:高并发下扣减流量包-日剩余流量包计算存储Redis**
- 日剩余流量包计算存储
/
1天总的流量包
/
public static final String DAY_TOTAL_TRAFFIC = “lock:traffic:day_total:%s”;
//先更新,再扣减当前使用的流量包
int rows = trafficManager.addDayUsedTimes(accountNo,useTrafficVO.getCurrentTrafficDO().getId(),1);
if(rows != 1){throw new BizException(BizCodeEnum.TRAFFIC_REDUCE_FAIL);}
//往Redis设置流量包总次数,短链服务那边递减次数,如果有新增流量包,则删除这个key TODO
long leftSeconds = TimeUtil.getRemainSecondsOneDay(new Date());
String totalKey = String.format(RedisKey.DAY_TOTAL_TRAFFIC, accountNo);
//减少1是减去此次的流量次数
redisTemplate.opsForValue().set(totalKey, useTrafficVO.getDayTotalLeftTimes() - 1, leftSeconds, TimeUnit.SECONDS);
return JsonData.buildSuccess();
/ TimeUtil
获取当天剩余的秒数,用于流量包过期配置
@param currentDate
@return
/
public static Integer getRemainSecondsOneDay(Date currentDate) {
LocalDateTime midnight = LocalDateTime.ofInstant(currentDate.toInstant(),
ZoneId.systemDefault()).plusDays(1).withHour(0).withMinute(0)
.withSecond(0).withNano(0);
LocalDateTime currentDateTime = LocalDateTime.ofInstant(currentDate.toInstant(),
ZoneId.systemDefault());
long seconds = ChronoUnit.SECONDS.between(currentDateTime, midnight);
return (int) seconds;
}
<a name="c169392c"></a>
#### 第5集 高并发下创建短链-预扣减流量包Lua脚本开发
**简介:高并发下创建短链-预扣减流量包Lua脚本开发**
- 预扣减流量包Lua脚本开发
//需要预先检查下是否有足够多的可以进行创建
String cacheKey = String.format(RedisKey.DAY_TOTAL_TRAFFIC, accountNo);
//检查key是否存在,然后递减,是否大于等于0,使用lua脚本
// 如果key不存在,则未使用过,lua返回值是0; 新增流量包的时候,不用重新计算次数,直接删除key,消费的时候回计算更新
String script = “if redis.call(‘get’,KEYS[1]) then return redis.call(‘decr’,KEYS[1]) else return 0 end”;
Long leftTimes = redisTemplate.execute(new DefaultRedisScript<>(script, Long.class), Arrays.asList(cacheKey), “”);
log.info(“今日流量包剩余次数:{}”, leftTimes);
if (leftTimes >= 0) {
String newOriginalUrl = CommonUtil.addUrlPrefix(request.getOriginalUrl());
request.setOriginalUrl(newOriginalUrl);
EventMessage eventMessage = EventMessage.builder().accountNo(accountNo)
.content(JsonUtil.obj2Json(request))
.messageId(IDUtil.geneSnowFlakeID().toString())
.eventMessageType(EventMessageType.SHORT_LINK_ADD.name())
.build();
rabbitTemplate.convertAndSend(rabbitMQConfig.getShortLinkEventExchange(), rabbitMQConfig.getShortLinkAddRoutingKey(), eventMessage);
} else {
return JsonData.buildResult(BizCodeEnum.TRAFFIC_REDUCE_FAIL);
}
<a name="9b3523fc"></a>
#### 第6集 高并发下创建短链-预扣减流量包链路测试
**简介:高并发下创建短链-预扣减流量包链路测试**
-
测试
- 流量包扣减
- 流量包更新维护
-
亮点
- 惰性策略更新维护流量包-海量数据下
- 高并发扣减流量包-预扣减机制
![](https://file.xdclass.net/note/2020/javaweb/%E5%9B%BE%E7%89%87/logo.png#alt=logo) **愿景:"让编程不再难学,让技术与生活更加有趣"**
<a name="8dddc363"></a>
### 第四十九章 微服务必备技术分布式事务介绍和Base理论案例
<a name="faa4e619"></a>
#### 第1集 关于分布式事务的由来你知识多少
**简介:分布式事务介绍和产生原因**
-
什么是分布式事务
- 事务
- 事务指的就是一个操作单元,在这个操作单元中的所有操作最终要保持一致的行为,要么所有操作都成功,要么所有的操作都被撤销
- 分两种
- 一个是本地事务:本地事物其实可以认为是数据库提供的事务机
- 一个是分布式事务
- 分布式事务
指事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于不同的分布式系统的不同节点之上。
简单的说,就是一次大的操作由不同的小操作组成,这些小的操作分布在不同的服务器上,且属于不同的应用
分布式事务需要保证这些小操作要么全部成功,要么全部失败。
本质上来说,分布式事务就是为了保证不同数据库的数据一致性
-
产生的原因
- 业务发展,数据库的拆分-分库分表
- SOA和微服务架构的使用
- 多个微服务之间调用异常
- 网络异常、请求超时、数据库异常、程序宕机等
![](img/image-20220216163404059.png#alt=image-20220216163404059)
-
为啥说是核心知识呢,基本是每个微服务架构项目都离不开的难题
-
海量数据项目大课
![](img/image-20220216162339651.png#alt=image-20220216162339651)
- 高并发项目大课-电商业务
![](img/image-20220216162140016.png#alt=image-20220216162140016)
<a name="ae40cd12"></a>
#### 第2集 分布式事务的常见解决方案概览
**简介:讲解分布式事务常见解决方案概览**
- 常见分布式事务解决方案
- 2PC 和 3PC
- 两阶段提交, 基于XA协议
- TCC
- Try、Confirm、Cancel
- 事务消息
- 最大努力通知型
- 分布式事务分类
- 刚性事务:遵循ACID
- 柔性事务:遵循BASE理论
- 分布式事务框架
- TX-LCN:支持2PC、TCC等多种模式
- [https://github.com/codingapi/tx-lcn](https://github.com/codingapi/tx-lcn)
- 更新慢(个人感觉处于停滞状态)
- Seata:支持 AT、TCC、SAGA 和 XA 多种模式
- [https://github.com/seata/seata](https://github.com/seata/seata)
- 背靠阿里,专门团队推广
- 阿里云商业化产品GTS
- [https://www.aliyun.com/aliware/txc](https://www.aliyun.com/aliware/txc)
- RocketMq:自带事务消息解决分布式事务
- [https://github.com/apache/rocketmq](https://github.com/apache/rocketmq)
<a name="c37f8ec7"></a>
#### 第3集 分布式事务下数据最终一致性-CAP的权衡结果 BASE理论
**简介:分布式事务下数据最终一致性-BASE理论介绍**
![](img/image-20220216162454870.png#alt=image-20220216162454870)
- 什么是Base理论
CAP 中的一致性和可用性进行一个权衡的结果,核心思想就是:我们无法做到强一致,但每个应用都可以根据自身的业务特点,采用适当的方式来使系统达到最终一致性, 来自 ebay 的架构师提出
-
Basically Available(基本可用)
- 假设系统,出现了不可预知的故障,但还是能用, 可能会有性能或者功能上的影响,比如RT是10ms,变成50ms
-
Soft state(软状态)
- 允许系统中的数据存在中间状态,并认为该状态不影响系统的整体可用性,即允许系统在多个不同节点的数据副本存在数据延时
-
Eventually consistent(最终一致性)
- 系统能够保证在没有其他新的更新操作的情况下,数据最终一定能够达到一致的状态,因此所有客户端对系统的数据访问最终都能够获取到最新的值
-
关于数据一致性
- 强一致:操作后的能立马一致且可以访问
- 弱一致:容忍部分或者全部访问不到
- 最终一致:弱一致性经过多一段时间后,都一致且正常
![](img/image-20220216163438019.png#alt=image-20220216163438019)
<a name="24b79444"></a>
#### 第4集 BASE理论-分布式事务的解决方案之一事务消息
**简介:BASE理论-讲解分布式事务的解决方案之一事务消息**
- 事务消息
- 消息队列提供类似Open XA的分布式事务功能,通过消息队列事务消息能达到分布式事务的最终一致
- 半事务消息
- 暂不能投递的消息,发送方已经成功地将消息发送到了消息队列服务端,但是服务端未收到生产者对该消息的二次确认,此时该消息被标记成“暂不能投递”状态,处于该种状态下的消息即半事务消息。
- 消息回查
- 由于网络闪断、生产者应用重启等原因,导致某条事务消息的二次确认丢失,消息队列服务端通过扫描发现某条消息长期处于“半事务消息”时,需要主动向消息生产者询问该消息的最终状态(Commit或是Rollback),该询问过程即消息回查
- 交互图(来源rocketmq官方文档)
![](img/image-20220216164246534.png#alt=image-20220216164246534)
- 目前较为主流的MQ,比如ActiveMQ、RabbitMQ、Kafka、RocketMQ等,只有RocketMQ支持事务消息
- 如果其他队列需要事务消息,可以开发个消息服务,自行实现半消息和回查功能
- 好处
- 事务消息不仅可以实现应用之间的解耦,又能保证数据的最终一致性
- 同时将传统的大事务可以被拆分为小事务,能提升效率
- 不会因为某一个关联应用的不可用导致整体回滚,从而最大限度保证核心系统的可用性
- 缺点
- 不能实时保证数据一致性
- 极端情况下需要人工补偿,比如 假如生产者成功处理本地业务,消费者始终消费不成功
<a name="ca3c501a"></a>
#### 第5集 最终一致性的体现-第三方支付平台和微服务之间的通讯
**简介:讲解第三方支付平台和微服务之间的交互**
- 支付业务
- 支付宝支付
- 微信支付
- 其他支付
- 多个服务之间通信,怎么保证分布式事务呢?
- 利用最终一致性思想,也叫柔性事务解决方案
![](img/5_0.png#alt=image-20220216164246534)
-
关于数据一致性
-
强一致:操作后的能立马一致且可以访问
-
弱一致:容忍部分或者全部访问不到
-
最终一致:弱一致性经过多一段时间后,都一致且正常
![](https://file.xdclass.net/note/2020/javaweb/%E5%9B%BE%E7%89%87/logo.png#alt=logo) **愿景:"让编程不再难学,让技术与生活更加有趣"**
<a name="6afaa5e5"></a>
### 第五十章 高并发下短链服务和流量包服务分布式事务解决方案
<a name="ec40fcc2"></a>
#### 第1集 高并发下-创建短链和扣减流量包的分布式事务设计
**简介: 高并发下-创建短链和扣减流量包的分布式事务设计**
- 当前交互链路
- 常规分布式事务
- TCC、2PC等方案性能差,普遍都是需要加锁
- Seata框架也是,适合常规的管理后台或者并发量不高的场景解决分布事务,一个注解搞定
![](img/image-20220217133054662.png#alt=image-20220217133054662)
- 高并发下解决方式
![](img/image-20220217133127022.png#alt=image-20220217133127022)
<a name="8d3b58f5"></a>
#### 第2集 创建短链和扣减流量包的分布式事务RabbitMQ解决方案
**简介: 创建短链和扣减流量包的分布式事务RabbitMQ解决方案**
-
分布式调度定时检测
- XXL-JOB分布式定时任务
- 延迟队列
-
延迟队列
-
一种带有延迟功能的消息队列,Producer 将消息发送到消息 队列 服务端,但并不期望这条消息立⻢投递,而是推迟到在 当前时间点之后的某一个时间投递到 Consumer 进行消费
-
该消息即定时消息
-
业界的一些实现方式
- 定时任务高精度轮训
- 采用RocketMQ自带延迟消息功能
- RabbitMQ本身是不支持延迟队列的,怎么办? 结合死信队列的特性,就可以做到延迟消息
![](img/image-20220217142152583.png#alt=image-20220217142152583)
![](img/image-20220218162727394.png#alt=image-20220218162727394)
<a name="5243d8cb"></a>
#### 第3集 流量包锁定任务表设计和创建讲解
**简介: 流量包锁定任务表设计和创建讲解**
- 流量包锁定任务表
CREATE TABLE traffic_task
(
id
bigint unsigned NOT NULL AUTO_INCREMENT,
account_no
bigint DEFAULT NULL,
traffic_id
bigint DEFAULT NULL,
use_times
int DEFAULT NULL,
lock_state
varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT ‘锁定状态锁定LOCK 完成FINISH-取消CANCEL’,
biz_id
varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT ‘唯一标识’,
gmt_create
datetime DEFAULT CURRENT_TIMESTAMP,
gmt_modified
datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (id
),
UNIQUE KEY uk_biz_id
(biz_id
) USING BTREE,
KEY idx_release
(account_no
,id
) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
-
改动
- 不叫messageId,存在误解改为bizId(存储短链码,检查是否创建短链成功)
- POJO类改动
- 创建LOCK状态枚举类
public enum TaskStateEnum {
/
锁定
/
LOCK,
/
完成
/
FINISH,
/
取消,释放库存
/
CANCEL;
}
```
#### 第4集 流量包任务表Manager层相关开发
简介: 流量包任务表Manager层相关开发
- 编码
@Component
@Slf4j
public class TrafficTaskManagerImpl implements TrafficTaskManager {
@Autowired
private TrafficTaskMapper trafficTaskMapper;
@Override
public int add(TrafficTaskDO trafficTaskDO) {
return trafficTaskMapper.insert(trafficTaskDO);
}
@Override
public TrafficTaskDO findByIdAndAccountNo(Long id, Long accountNo) {
TrafficTaskDO taskDO = trafficTaskMapper.selectOne(new QueryWrapper<TrafficTaskDO>()
.eq("id", id).eq("account_no", accountNo));
return taskDO;
}
@Override
public int deleteByIdAndAccountNo(Long id, Long accountNo) {
return trafficTaskMapper.delete(new QueryWrapper<TrafficTaskDO>()
.eq("id", id).eq("account_no", accountNo));
}
}
#### 第5集 创建短链扣减流量包整合RabbitMQ死信队列配置
简介: 创建短链扣减流量包整合RabbitMQ死信队列配置
- 死信交换机配置
```
//================流量包扣减,创建短链死信队列配置==================================
// 发送锁定流量包消息-》延迟exchange-》lock.queue-》死信exchange-》release.queue 延迟队列,不能被监听消费
/
第一个队列延迟队列,
/
private String trafficReleaseDelayQueue = “traffic.release.delay.queue”;
/
第一个队列的路由key
进入队列的路由key
*/
private String trafficReleaseDelayRoutingKey = “traffic.release.delay.routing.key”;
/
第二个队列,被监听恢复流量包的队列
/
private String trafficReleaseQueue = “traffic.release.queue”;
/
第二个队列的路由key
即进入死信队列的路由key
/
private String trafficReleaseRoutingKey=”traffic.release.routing.key”;
/
过期时间,毫秒,1分钟
/
private Integer ttl = 60000;
/
延迟队列
/
@Bean
public Queue trafficReleaseDelayQueue(){
Map![](img/image-20220218162727394.png#alt=image-20220218162727394)
![](https://file.xdclass.net/note/2020/javaweb/%E5%9B%BE%E7%89%87/logo.png#alt=logo) **愿景:"让编程不再难学,让技术与生活更加有趣"**
<a name="34c3e8d8"></a>
### 第五十一章 创建短链-扣减流量包数据最终一致性编码实战
<a name="7aebf207"></a>
#### 第1集 创建短链-扣减流量包整合RabbitMQ死信队列实战《上》
**简介: 创建短链-扣减流量包整合RabbitMQ死信队列实战**
-
编码实战
- 保存Task
//先更新,再扣减当前使用的流量包
int rows = trafficManager.addDayUsedTimes(accountNo,useTrafficVO.getCurrentTrafficDO().getId(),1);
TrafficTaskDO trafficTaskDO = TrafficTaskDO.builder().accountNo(accountNo)
.bizId(trafficRequest.getBizId())
.useTimes(1).trafficId(useTrafficVO.getCurrentTrafficDO().getId())
.lockState(TaskStateEnum.LOCK.name()).build();
trafficTaskManager.add(trafficTaskDO);
- 发送MQ消息
EventMessage usedTrafficEventMessage = EventMessage.builder()
.accountNo(accountNo)
.bizId(trafficTaskDO.getId() + “”)
.eventMessageType(EventMessageType.TRAFFIC_USED.name())
.build();
//发送延迟信息,用于异常回滚,数据最终一致性
rabbitTemplate.convertAndSend(rabbitMQConfig.getTrafficEventExchange(),
rabbitMQConfig.getTrafficReleaseDelayRoutingKey(), usedTrafficEventMessage);
return JsonData.buildSuccess();
-
发送测试
<a name="56fc90bb"></a>
#### 第2集 创建短链-扣减流量包整合RabbitMQ死信队列实战《下》
**简介: 创建短链-扣减流量包整合RabbitMQ死信队列实战**
- feign远程调用
@FeignClient(name = “dcloud-link-service”)
public interface ShortLinkFeignService {
/**
检查短链是否存在
@param shortLinkCode 短链码
@return
/
@GetMapping(value = “/api/link/v1/check”, headers = {“rpc-token=${rpc.token}”})
JsonData simpleDetail(@RequestParam(“shortLinkCode”) String shortLinkCode);
}
@Value(“${rpc.token}”)
private String rpcToken;
/
rpc调用获取短链信息
@return
/
@GetMapping(“check”)
public JsonData simpleDetail(@RequestParam(“shortLinkCode”) String shortLinkCode, HttpServletRequest request) {
String requestToken = request.getHeader(“rpc-token”);
if (rpcToken.equalsIgnoreCase(requestToken)) {
ShortLinkVO shortLinkVO = shortLinkService.parseShortLinkCode(shortLinkCode);
return shortLinkVO == null ? JsonData.buildError(“不存在”) : JsonData.buildSuccess();
} else {
return JsonData.buildError(“非法访问”);
}
}
- 消费者开发
else if(EventMessageType.TRAFFIC_USED.name().equalsIgnoreCase(messageType)){
//流量包使用,检查是否成功使用
//检查task是否存在
//检查短链是否成功
//如果不成功则恢复流量包,删除缓存key
//删除task(也可以更新状态,定时删除也行)
Long trafficTaskId = Long.valueOf(eventMessage.getBizId());
TrafficTaskDO trafficTaskDO = trafficTaskManager.findByIdAndAccountNo(trafficTaskId, accountNo);
//非空 且 是锁定状态
if(trafficTaskDO!=null && trafficTaskDO.getLockState()
.equalsIgnoreCase(TaskStateEnum.LOCK.name())){
JsonData jsonData = shortLinkFeignService.check(trafficTaskDO.getBizId());
if(jsonData.getCode()!=0){
log.error(“创建短链失败,流量包回滚”);
trafficManager.releaseUsedTimes(accountNo,trafficTaskDO.getTrafficId(),trafficTaskDO.getUseTimes());
}
trafficTaskManager.deleteByIdAndAccountNo(trafficTaskId,accountNo);
}
}
```
#### 第3集 创建短链失败-流量包数据跨日期恢复问题修复
简介: 创建短链失败-流量包数据跨日期恢复问题修复
- 恢复流量包跨日期问题
- 1月1号晚上11点59分创建,然后失败了,1月2号凌晨恢复了次日流量包
- 解决方法:增加恢复流量包的日期
<!--恢复流量包-->
<update id="releaseUsedTimes">
update traffic set day_used = day_used - #{usedTimes}
where id = #{trafficId} and account_no = #{accountNo}
and (day_used - #{usedTimes}) >= 0 and date_format(gmt_modified, '%Y-%m-%d')=#{useDateStr} limit 1;
</update>
- 流量包重新计算(可以先删除key,再恢复流量包)
//恢复流量包,应该删除这个key
String totalTrafficTimesKey = String.format(RedisKey.DAY_TOTAL_TRAFFIC,accountNo);
redisTemplate.delete(totalTrafficTimesKey);
#### 第4集 创建短链-扣减流量包数据最终一致性链路测试实战
简介: 创建短链-扣减流量包数据最终一致性链路测试实战
- 链路测试
- 创建短链成功,流量包扣减成功,缓存-数据库一致
- 创建短链失败,流量包扣减回滚,缓存-数据库一致
愿景:”让编程不再难学,让技术与生活更加有趣”
### 第五十二章 架构师成长-大数据领域知识初探
#### 第1集 短链平台里面涉及到的大数据需求介绍
简介: 短链平台里面涉及到的大数据需求介绍
-
短链平台业务核心接口【大部分】开发完成
- 商品展示
- 账号管理 CRUD
- 短链管理 CRUD
- 流量包管理 CRUD
-
进入新的环节-数据处理-ETL-可视化
-
需求:
- 公司电商产品推广、业务活动⻚、广告落地⻚ 缺少实时【数据反馈和渠道效果分析】
-
百度统计案例
- https://tongji.baidu.com/web/demo/overview/index?siteId=16847648
-
小滴短链案例
- 数据分析业务
- 数据可视化案例
-
最简单的方式
- 去mysql数据库 select sum(pv),sum(uv) from table where code =XXX group by xx,返回给用户就行
- 数据量少的情况完全没问题,但是数据量大的话,估计【上午写代码,下午就办理离职手续】
- 解决方式
- 大数据处理体系
#### 第2集 浅谈后端高级工程师发展路线-大数据领域
简介: 后端高级工程师发展路线-大数据领域
-
大家的职业方向
-
阶段一:java工程师-》中级java工程师-》高级java工程师
-
阶段二:技术leader/技术组长-》初级架构师-》高级架构师
-
后端java研发岗位配合度高的组
-
【高】前端工程师-客户端工程师-测试工程师
-
【高】业务开发工程师(开发常规微服务)
-
【高】中间件开发工程师(公司基础设施比如devops平台、监控告警平台、压测平台等)
-
【中】数据分析工程师(俗称【报表工程师】,分析数据-》驱动产品,老板或产品要数据比较多)
-
【中】算法工程师(俗称【调参工程师】,推荐算法模型和优化,市面很多现成的算法模型了)
-
【高】大数据工程师(海量数据处理计算、ETL等,构建画像系统,可视化大屏等)
-
后端工程师如何和大数据工程师配合呢,前提自己要懂整个大数据链路和基本概念
-
【架构师】肯定需要懂大数据工程师相关知识,才可以统筹整个大项目
- 商用短链平台-可视化数据项目-是后端工程师-掌握大数据工程师链路的核心内容
- 精简后的链路,掌握数据产生-》传输-》处理-》分层-》存储-》可视化展示
#### 第3集 大数据里面的概念讲解-BI商业智能-数据仓库-数据湖
简介:大数据里面的概念讲解-数据仓库-数据湖
-
商业智能BI (Business Intelligence,简称BI)
- 用来帮助企业更好地利用数据提高决策质量的技术集合,是从大量的数据中钻取信息与知识的过程。
- 一套完整的解决方案,可以将来自企业的不同业务系统的数据,提取出有用的数据进行整合清洗-分析和处理,利用合适的查询和分析工具快速、准确地为企业提供报表展现与分析,为企业提供决策支持
- 最终展现给用户的信息就是可视化报表,大屏数据
- 基于可视化报表大屏数据 进行分析,发现问题,并去做决策【数据 驱动业务】
- 数据可视化、数据分析、数据仓库和大数据等词汇时会有的摸不着头脑
- 例子
小滴课堂老王和Anna小姐姐 开了小卖部, Anna小姐姐每天用Excel来处理销售和经营的数据,看营收和利润。
老王想发财,因此决定更深入了解市场,消费者偏好等,把店铺里的销售和经营、客流量、季节等数据弄到一起,弄了柱状图和饼状图进行可视化分析。 就能发现哪个商品最多人买,摆放什么位置,什么折扣,能更吸引消费者。
然后老王弄了一个程序,自动收集了全部excel数据放到一起,然后进行自动抽取分析存储【数据仓库】,
并生成图形报告【数据可视化】定时发给Anna小姐姐,来进行决策,
这种支持管理人员,发现数据做决策的系统,我们就称为“商业智能系统”
- 总结
- 商业智能BI 是一系列工具产品和技术的集合,如数据仓库技术、ETL技术、OLAP技术、前端工具等
- 目的是为了提升数据价值、辅助企业决策
- 业务系统与商业智能系统的区别
- 业务系统保证企业日常运营,商业智能系统通过辅助决策,提高企业的运营能力水平
-
什么是数据仓库 Data Warehouse
- 为了便于多维分析和多角度展现,将其数据按特定的模式进行存储而建立的数据库,数据仓库中的数据是细节的,集成的,面向主题的,是以 OLAP系统为分析目的,
- 是存储和管理一个或多个主题数据的集合,支持管理决策分析,有针对性抽取的结构化历史数据,能够生成各类报表
- 将来自不同来源的结构化数据聚合起来,用于业务智能领域的比较和分析(BI商业智能),数据仓库是包含多种数据的存储库
- 数据仓库有两个局限
- 一是只可以解决预先想到的问题, 需要提前建模
- 二是数据已经被多次处理过,无法看见其最初状态
-
什么是数据湖 Data Lake
- 存储任何形式(包括结构化和非结构化)和任何格式(包括文本、音频、视频和图像)的原始数据
- 数据不需要提前进行定义,在准备使用数据时再定义,提高了最高的灵活性与可扩展性
- 适合使用机器学习和深度学习进行使用,比如数据挖掘和数据分析,以及提取非结构化数据
- 一个新的概念,但落地还很多问题需要解决
-
总结:在企业中两者的作用是互补的,不能认为数据湖的出现是为了取代数据仓库
#### 第4集 互联网项目-常见数据可视化分析链路-架构图讲解
简介:互联网项目-常见数据可视化分析链路架构图讲解
-
出发点
- 数据从哪里来,数据到哪里去
- 通用微服务+数据仓库详细链路
- 链路存在重叠,意思是多种方式都是可以的实现
- 短链平台的数据是其中一个链路
-
数据采集传输:Flume、Kafka、Canal、Maxwell、Sqoop、Logstash,DataX
-
数据存储:MySql、ClickHouse、HDFS、HBase、Redis
-
数据计算:Hive、Spark、Flink、Storm
-
数据查询:Presto、Kylin、Druid
-
数据可视化:Echarts、Superset、DataV
#### 第5集 大数据-ETL和数据仓库建设分层
简介:大数据-ETL和数据仓库建设分层
-
什么是ETL(Extract-Transform-Load)
- 抽取(extract)、转换(transform)、加载(load)缩写
- ETL一词较常用在数据仓库,但其对象并不限于数据仓库
- 将业务系统的数据经过抽取、清洗转换之后加载到数据仓库的过程,目的是将企业中的分散、零乱、标准不统一的数据整合到一起,为企业的决策提供分析依据, ETL是BI(商业智能)项目重要的一个环节
-
数仓分层介绍
-
为什么要分层
- 开发者都希望自己的数据能够有顺序地流转,方便排查问题
- 复杂问题简单化
- 复杂任务分层处理,每层定位职责不一样,每个层只解决特定的问题
- 减少重复开发量
- 规范数据分层,开发通用的中间层,可以极大地减少重复计算的工作
- 总结:提效+懒人的智慧,主要是增加数据计算的复用 性,每次新增加统计需求时,不用从原始数据进行计算,而是从半成品继续加工,从而更快更省成本
愿景:”让编程不再难学,让技术与生活更加有趣”
### 第五十三章 小滴商用短链平台数据可视化-埋点采集讲解
#### 第1集 数据从哪里来-常见数据埋点采集方案介绍
简介: 数据从哪里来-常见数据埋点采集方案介绍
- 解决问题:数据从哪里来
-
什么是数据埋点
- 对网站、App、进行业务数据采集,包括用户行为数据及其他实际需要的数据进行采集上报
- 方便分析用户行为、提高用户体验,通过这些采集的数据就可以进行分析并辅助公司企业做决策
-
常见埋点方式
- 代码埋点
- 编写埋点代码,通过代码进行控制,前端、后端、App客户端代码,需要埋点的逻辑通过sdk函数调用,上报数据
- 可视化埋点
- 通用采集SDK,项目只需要引入埋点采集sdk。分析人员通过分析平台进行操作,对可交互的页面元素(如:图片、按钮、链接等)直接在界面上进行操作实现数据埋点,下发采集代码生效的埋点方式。
- 所见即所得,使用者只需在其可视化埋点页面上,点击想要监测的元素,编辑名字编号等,埋点就完成了
- 缺点
- 存在滞后性,每次调整埋点后需要应用重新发版才可以看到数据,也可以通过配置中心动态下发解决
- 相对生硬,满足不了全部数据采集,比如编码规范不统一、无法定位元素等,或者需要调用后台接口的数据等
- 比如看小滴课堂的一个视频,点击播放一个视频,交互行为就是一个播放,但播放的背后还想知道这个视频的名字、类别、作者、评级等信息就获取不了
- 全埋点|无埋点
- 对应用上的所有的可交互事件元素进行解析,对页面上所有的用户操作行为进行监听,当有操作行为(交互事件)发生时,监测工具会进行记录并上报
- 会记录全部用户行为事件并上报,采集量过大
- 缺点
- 满足不了全部数据采集,比如编码规范不统一、无法定位元素等,或者需要调用后台接口的数据等
- 比如看小滴课堂的一个视频,点击播放一个视频,交互行为就是一个播放,但播放的背后还想知道这个视频的名字、类别、作者、评级等信息就获取不了
#### 第2集 埋点数据分类和短链平台-数据采集链路
简介: 埋点数据分类和短链平台-数据采集链路
-
日志采集分类
-
事件
-
曝光事件:1个item 或者1个页面被展示出来,就称作曝光
-
点击事件:点击某个页面、按钮
-
属性
- 打开 App 手机型号、网络制式、App版本信息 等公参信息
- 浏览器所属的客户端信息,用户网络ip、地理位置信息等
-
埋点数据上报时机分两种
-
方式一,在离开该页面时上传在这个页面产生的所有数据
- 优点,批量上传,服务器接收数据压力小
- 缺点,数据有延迟
-
方式二,每个事件动作数据产生后马上发送
- 优点,数据没延迟,实时看到数据
- 缺点,对服务器接压力比较大,请求频繁
-
短链平台采用方式
- 访问短链码
- 记录日志-打印控制台(方便排查)
- 发送Kafka(本身异步发送)
#### 第3集 小滴短链平台-数据可视化整体链路讲解和命名规范
简介: 小滴短链平台-数据可视化整体链路讲解**
- 短链平台整体链路
- 数据分层处理概述
| 数据分层 | 分层描述 | 数据生成计算工具 | 存储 |
| —- | :—- | —- | —- |
| ODS | 原生数据,短链访问基本信息 | SpringBoot生成 | Kafka |
| DWD | 对 ODS 层做数据清洗和规范化,新老访客标记等 | Flink | Kafka |
| DWM | 对DWD数据进一步加工补齐数据,独立访客统计,操作系统/ip/城市,做宽表 | Flink | kafka |
| DWS | 对DWM进行处理,多流合并,分组|聚合|开窗|统计,形成主题宽表 | Flink | ClickHouse |
| ADS | 从ClickHouse中读取数据,根据需求进行筛选聚合,可视化展示 | ClickHouseSql | web可视化展示 |
- 命名规范
- ODS层命名为odsdocker run -d --name zookeeper -p 2181:2181 -t wurstmeister/zookeeper
- 部署kafka
docker run -d --name xdclass_kafka \
-p 9092:9092 \
-e KAFKA_BROKER_ID=0 \
--env KAFKA_HEAP_OPTS=-Xmx256M \
--env KAFKA_HEAP_OPTS=-Xms128M \
-e KAFKA_ZOOKEEPER_CONNECT=172.18.123.230:2181 \
-e KAFKA_ADVERTISED_LISTENERS=PLAINTEXT://120.79.150.146:9092 \
-e KAFKA_LISTENERS=PLAINTEXT://0.0.0.0:9092 wurstmeister/kafka:2.13-2.7.0
- 找kafka的Container ID,进入容器内部,创建topic
- docker exec -it ${CONTAINER ID} /bin/bash
- 进入kafka默认目录 cd /opt/kafka 就跟一般的kafka一样了
#创建一个主题:
kafka-topics.sh --create --zookeeper 172.18.123.230:2181 --replication-factor 1 --partitions 1 --topic mykafka
- 阿里云部署ZK+Kafka,本地开发注意事项
- 网络安全组记得开发端口 9092、2181
#### 第2集 短链访问数据 日志采集开发实战之Logback讲解
简介: 短链访问数据日志采集开发实战之Logback讲解
-
需求
- 控制台输出访问日志,方便测试
- 业务数据实际输出到kafka
- 常用的框架 log4j、logback、self4j等
-
log4j、logback、self4j 之间有啥关系
-
SLF4J(Simple logging Facade for Java) 门面设计模式 |外观设计模式
-
把不同的日志系统的实现进行了具体的抽象化,提供统一的日志使用接口
-
具体的日志系统就有log4j,logback等;
-
logback也是log4j的作者完成的,有更好的特性,可以取代log4j的一个日志框架, 是slf4j的原生实现
-
log4j、logback可以单独的使用,也可以绑定slf4j一起使用
-
编码规范建议不直接用log4j、logback的API,应该用self4j, 日后更换框架所带来的成本就很低
- Logback知识点回顾
- appender是记录日志的方式
- logger是配置某个包或者类采用哪几个appender记录日志,比如控制台/文件;
- root是默认配置的输出日志,除了logger自定义配置外的其他类输出日志的方式,也是有级别和多个appender
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<property name="LOG_HOME" value="./data/logs/link" />
<!--采用打印到控制台,记录日志的方式-->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
</appender>
<!-- 采用保存到日志文件 记录日志的方式-->
<appender name="rollingFile" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_HOME}/link.log</file>
</appender>
<!-- 指定某个类单独打印日志 -->
<logger name="net.xdclass.service.impl.LogServiceImpl"
level="INFO" additivity="false">
<appender-ref ref="rollingFile" />
<appender-ref ref="console" />
</logger>
<root level="info" additivity="false">
<appender-ref ref="console" />
</root>
</configuration>
#### 第3集 短链访问数据日志采集开发实战之Logback配置实战
简介: 短链访问数据日志采集开发实战之Logback配置实战
- Logback配置打印控制台(方便排查数据)
- 配置文件新增logback.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<property name="LOG_HOME" value="./data/logs/link" />
<!--采用打印到控制台,记录日志的方式-->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%msg%n</pattern>
</encoder>
</appender>
<!-- 采用保存到日志文件 记录日志的方式-->
<appender name="rollingFile" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_HOME}/link.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_HOME}/link-%d{yyyy-MM-dd}.log</fileNamePattern>
</rollingPolicy>
<encoder>
<pattern>%msg%n</pattern>
</encoder>
</appender>
<!-- 指定某个类单独打印日志 -->
<logger name="net.xdclass.service.impl.LogServiceImpl"
level="INFO" additivity="false">
<appender-ref ref="rollingFile" />
<appender-ref ref="console" />
</logger>
<root level="error" additivity="false">
<appender-ref ref="console" />
</root>
</configuration>
#### 第4集 数据日志采集开发实战之发送Kafka消息
简介: 数据日志采集开发实战之发送Kafka消息
-
补充
- Logback配置可以看账号服务xxl-job的配置
- 调整格式都行,看业务需求配置日志格式
-
Kafak依赖和配置
#----------kafka配置--------------
spring.kafka.bootstrap-servers=120.79.150.146:9092
spring.kafka.producer.key-serializer=org.apache.kafka.common.serialization.StringSerializer
spring.kafka.producer.value-serializer=org.apache.kafka.common.serialization.StringSerializer
- 编码实战
@Service
@Slf4j
public class LogServiceImpl implements LogService {
private static final String TOPIC_NAME = "ods_link_visit_topic";
@Autowired
private KafkaTemplate kafkaTemplate;
@Override
public JsonData recordShortLinkLog(HttpServletRequest request, String shortLinkCode,Long accountNo) {
String ip = CommonUtil.getIpAddr(request);
Map<String,String> headerMap = CommonUtil.getAllRequestHeader(request);
Map<String,String> availableMap = new HashMap<>();
availableMap.put("user-agent",headerMap.get("user-agent"));
availableMap.put("referer",headerMap.get("referer"));
availableMap.put("accountNo",accountNo.toString());
LogRecord logRecord = LogRecord.builder()
//日志类型
.event(LogTypeEnum.SHORT_LINK_TYPE.name())
//日志内容
.data(availableMap)
//客户端ip
.ip(ip)
//时间时间
.ts(CommonUtil.getCurrentTimestamp())
//业务唯一id
.bizId(shortLinkCode).build();
String jsonLog = JsonUtil.obj2Json(logRecord);
//打印控制台
log.info(jsonLog);
//发送kafka
kafkaTemplate.send(TOPIC_NAME,jsonLog);
//存储Mysql 测试数据 TODO
return JsonData.buildSuccess();
}
}
#### 第5集 日志采集开发实战之发送和消费Kafka链路测试
简介: 日志采集开发实战之发送和消费Kafka链路测试
-
链路测试
- 访问短链
- 控制台打印日志
- 发送消息到Kafka
- Kafka消费消息
-
Kafka命令
创建topic
./kafka-topics.sh --create --zookeeper 172.18.123.230:2181 --replication-factor 1 --partitions 1 --topic ods_link_visit_topic
查看topic
./kafka-topics.sh --list --zookeeper 172.18.123.230:2181
删除topic
./kafka-topics.sh --zookeeper 172.18.123.230:2181 --delete --topic ods_link_visit_topic
消费者消费消息
./kafka-console-consumer.sh --bootstrap-server 120.79.150.146:9092 --from-beginning --topic ods_link_visit_topic
生产者发送消息
./kafka-console-producer.sh --broker-list 120.79.150.146:9092 --topic ods_link_visit_topic
愿景:”让编程不再难学,让技术与生活更加有趣”
### 第五十五章 Flink实时计算项目搭建和ODS层处理实战
#### 第1集 Flink实时计算项目搭建和依赖配置引入
简介: Flink实时计算项目搭建和依赖配置引入
- 添加依赖
```
<!--flink客户端-->
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-clients_${scala.version}</artifactId>
<version>${flink.version}</version>
</dependency>
<!--scala版本-->
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-scala_${scala.version}</artifactId>
<version>${flink.version}</version>
</dependency>
<!--java版本-->
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-java</artifactId>
<version>${flink.version}</version>
</dependency>
<!--streaming的scala版本-->
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-streaming-scala_${scala.version}</artifactId>
<version>${flink.version}</version>
</dependency>
<!--streaming的java版本-->
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-streaming-java_${scala.version}</artifactId>
<version>${flink.version}</version>
</dependency>
<!--Flink web ui-->
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-runtime-web_${scala.version}</artifactId>
<version>${flink.version}</version>
</dependency>
<!--使用 RocksDBStateBackend 需要加依赖-->
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-statebackend-rocksdb_${scala.version}</artifactId>
<version>1.13.1</version>
</dependency>
<!--mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.25</version>
</dependency>
<!--flink cep依赖包-->
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-cep_${scala.version}</artifactId>
<version>${flink.version}</version>
</dependency>
<!--redis connector-->
<dependency>
<groupId>org.apache.bahir</groupId>
<artifactId>flink-connector-redis_2.11</artifactId>
<version>1.0</version>
</dependency>
<!--kafka connector-->
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-connector-kafka_${scala.version}</artifactId>
<version>${flink.version}</version>
</dependency>
<!--日志输出-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.7</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
<scope>runtime</scope>
</dependency>
<!--json依赖包-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.44</version>
</dependency>
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-connector-jdbc_${scala.version}</artifactId>
<version>${flink.version}</version>
</dependency>
</dependencies>
<!-- 指定仓库位置,先从aliyun找,找不到再从apache仓库找 -->
<repositories>
<repository>
<id>aliyun</id>
<url>http://maven.aliyun.com/nexus/content/groups/public/</url>
</repository>
<repository>
<id>apache</id>
<url>https://repository.apache.org/content/repositories/snapshots/</url>
</repository>
</repositories>
<build>
<finalName>xdclass-flink</finalName>
<plugins>
<!--默认编译版本比较低,所以用compiler插件,指定项目源码的jdk版本,编译后的jdk版本和编码,-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.6.1</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<encoding>${file.encoding}</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>2.3</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
- 目录结构划分
![](img/image-20220302155251129.png#alt=image-20220302155251129)
- 入口类编写
@Slf4j public class DwdShortLinkLogApp {
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.setParallelism(1);
DataStream<String> ds = env.socketTextStream("127.0.0.1", 8888);
ds.print();
env.execute();
}
}
<a name="40739551"></a>
#### 第2集 代码复用性提升-Kafka工具类封装开发实战
**简介: 代码复用性提升-Kafka工具类封装开发实战**
- 需求背景
- Flink的source和sink跟kafka交互频繁,提升代码复用性,封装工具类
- 每次变动的只有topic和group,其他的broker等基本属性是固定不变的
- 编码实战
/**
* ODS层 kafka相关配置
*/
public static final String SOURCE_TOPIC = “ods_link_visit_topic”; public static final String GROUP_ID = “dwd_short_link_group”;
public class KafkaUtil {
/**
* kafka broker地址
*/
private static String KAFKA_SERVER = null;
static{
Properties properties = new Properties();
// 使用ClassLoader加载properties配置文件生成对应的输入流
InputStream in = KafkaUtil.class.getClassLoader().getResourceAsStream("application.properties");
// 使用properties对象加载输入流
try {
properties.load(in);
} catch (IOException e) {
e.printStackTrace();
}
//获取key对应的value值
KAFKA_SERVER = properties.getProperty("kafka.servers");
}
/**
* 获取flink kafka消费者
* @param topic
* @param groupId
* @return
*/
public static FlinkKafkaConsumer<String> getKafkaConsumer(String topic, String groupId) {
//Kafka连接配置
Properties props = new Properties();
props.setProperty(ConsumerConfig.GROUP_ID_CONFIG, groupId);
props.setProperty(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, KAFKA_SERVER);
return new FlinkKafkaConsumer<>(topic, new SimpleStringSchema(), props);
}
/**
* 获取flink kafka生产者
* @param topic
* @return
*/
public static FlinkKafkaProducer<String> getKafkaProducer(String topic) {
return new FlinkKafkaProducer<>(KAFKA_SERVER, topic, new SimpleStringSchema());
}
}
- 配置文件 resources/application.properties
—————kafka配置———————
kafka.servers=120.79.150.146:9092
<a name="fbe856ce"></a>
#### 第3集 短链数据访问和Flink消费Kafka数据链路测试
**简介: 短链数据访问和Flink消费Kafka数据链路测试**
-
访问短链
-
发送数据
-
Flink消费打印
-
留存数据
- 通过nc -lk测试
{“ip”:”141.123.11.31”,”ts”:1646145133665,”event”:”SHORT_LINK_TYPE”,”udid”:null,”bizId”:”026m8O3a”,”data”:{“referer”:null,”accountNo”:”693100647796441088”,”user-agent”:”Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.109 Safari/537.36”}}
-
Kafka命令
创建topic ./kafka-topics.sh —create —zookeeper 172.18.123.230:2181 —replication-factor 1 —partitions 1 —topic ods_link_visit_topic
查看topic ./kafka-topics.sh —list —zookeeper 172.18.123.230:2181
删除topic ./kafka-topics.sh —zookeeper 172.18.123.230:2181 —delete —topic ods_link_visit_topic
消费者消费消息 ./kafka-console-consumer.sh —bootstrap-server 120.79.150.146:9092 —from-beginning —topic ods_link_visit_topic
生产者发送消息 ./kafka-console-producer.sh —broker-list 120.79.150.146:9092 —topic ods_link_visit_topic
<a name="8647b884"></a>
#### 第4集 ODS层处理-Flink实时标记-短链新老访客统计需求
**简介: ODS层处理-Flink实时标记-短链新老访客统计需求**
![](img/image-20220224153449131.png#alt=image-20220224153449131)
-
需求
- 从ODS读取数据,处理后存储到DWD层,做哪些职责?
- 需要识别标记出短链的新老访客
- 说明
- 新老访客可以有天、周、月维度
- 我们只做天维度的新老访客标记
![](img/image-20220304160424015.png#alt=image-20220304160424015)
-
什么是ETL(Extract-Transform-Load)
- 抽取(extract)、转换(transform)、加载(load)缩写
- ETL一词较常用在数据仓库,但其对象并不限于数据仓库
- 将业务系统的数据经过抽取、清洗转换之后加载到数据仓库的过程,目的是将企业中的分散、零乱、标准不统一的数据整合到一起,为企业的决策提供分析依据, ETL是BI(商业智能)项目重要的一个环节
-
思路(ETL的流程)
-
通过设备唯一标识,服务端进行【天维度】状态存储,标记新老访客
-
详情
- 需要生成唯一设备标识
- 利用Flink的状态存储 ValueState
<a name="6ea242d7"></a>
#### 第5集 浏览器指纹介绍和短链访客唯一标识设计开发实战
**简介: 浏览器指纹介绍和短链访客唯一标识设计开发实战**
- 日志分析
{“ip”:”141.123.11.31”,”ts”:1646145133665,”event”:”SHORT_LINK_TYPE”,”udid”:null,”bizId”:”026m8O3a”,”data”:{“referer”:null,”accountNo”:”693100647796441088”,”user-agent”:”Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.109 Safari/537.36”}}
-
**浏览器指纹(设备终端指纹)**
-
是通过浏览器对网站可见的配置来匿名识别浏览器,从硬件、操作系统、浏览器、网络等维度进行分析
-
浏览器指纹现在也比较多大厂都在使用:反欺诈,防止刷票脚本、机器人、异地可疑登录提示等,在注重用户隐私的情况下,进行一些数据分析
-
比如阿里云、淘宝等账号登录,常用设备和非常用设备登录是不一样的验证级别
-
维度参考
- MAC地址,网络中唯一标识一个网卡,固定化
- IP地址
- Http的Cookie
- Http的UserAgent,包括了客户使用的操作系统及版本、CPU 类型、浏览器及版本、渲染引擎等
- Canvas(HTML5画布),渲染文字,可以转出值
-
唯一标识ID设计
- 用户通过浏览器访问页面的同时产生一个用户唯一标识(ID)
- 考虑:**唯一性和稳定性**
- 最终参考的维度:ip+UserAgent地址(会有偏差但是不多)
-
编码实战
//2、格式进行转换、生成设备唯一标识和过滤 string->json
SingleOutputStreamOperator
}
});
/**
* 生成设备唯一标识
*
* @param jsonObject
* @return
*/
public static String getDeviceId(JSONObject jsonObject) {
Map<String, String> map = new TreeMap<>();
try {
map.put("ip", jsonObject.getString("ip"));
map.put("event", jsonObject.getString("event"));
map.put("bizId", jsonObject.getString("bizId"));
JSONObject dataJsonObj = jsonObject.getJSONObject("data");
map.put("userAgent", dataJsonObj.getString("user-agent"));
String deviceId = DeviceUtil.geneWebUniqueDeviceId(map);
return deviceId;
} catch (Exception e) {
log.error("生产唯一deviceId异常:{}", jsonObject);
return null;
}
}
//设备工具类 public class DeviceUtil {
/**
* 生成web设备唯一ID
* @param map
* @return
*/
public static String geneWebUniqueDeviceId(Map<String,String> map){
String deviceId = MD5(map.toString());
return deviceId;
}
/**
* MD5加密
*
* @param data
* @return
*/
public static String MD5(String data) {
try {
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] array = md.digest(data.getBytes("UTF-8"));
StringBuilder sb = new StringBuilder();
for (byte item : array) {
sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3));
}
return sb.toString().toUpperCase();
} catch (Exception exception) {
}
return null;
}
}
<a name="72b5c57f"></a>
#### 第6集 ODS层处理-短链访问 来源分布统计-分析开发实战
**简介: ODS层处理-短链访问来源分布统计-分析开发实战**
-
需求
- 短链访问来源(URL自己加参数的除外,比如utm_source)
统计下哪些渠道推广比较好,比如投放广告,统计各个渠道的点击率来源统计
例子: 老王推广小滴课堂 xdclass.net,对应的短链 是g1.fit/adcdeft
付费广告(花钱) 公众号广告 抖音首屏广告 B站广告 知乎广告
免费广告(没钱) CSDN发文文章 博客园发文章 开源中国发文章 知乎发文章
- 例子:[https://tongji.baidu.com/web/demo/source/all?viewType=site&siteId=16847648](https://tongji.baidu.com/web/demo/source/all?viewType=site&siteId=16847648)
![](img/image-20220305132607125.png#alt=image-20220305132607125)
-
开发编码
/**
* 获取referer信息
* @param jsonObject
* @return
*/
public static String getReferer(JSONObject jsonObject ){
JSONObject dataJsonObj = jsonObject.getJSONObject("data");
if(dataJsonObj.containsKey("referer")){
String referer = dataJsonObj.getString("referer");
if(StringUtils.isNotBlank(referer)){
try {
URL url = new URL(referer);
return url.getHost();
} catch (MalformedURLException e) {
log.error("提取referer失败,{}",e);
}
}
}
return "";
}
//增加数据 jsonObject.put(“referer”,referer);
<a name="d19f3d33"></a>
#### 第7集 ODS层处理-短链访问新老访客识别开发实战
**简介: ODS层处理-短链访问新老访客识别开发实战**
- 编码实现
//3、新老访客标记处理,根据udid对访问日志进行分组,天维度进行区分新老访客
KeyedStream
- 添加工具类
public class TimeUtil {
/**
* 默认日期格式
*/
private static final String DEFAULT_PATTERN = "yyyy-MM-dd";
/**
* 默认日期格式
*/
private static final DateTimeFormatter DEFAULT_DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern(DEFAULT_PATTERN);
private static final ZoneId DEFAULT_ZONE_ID = ZoneId.systemDefault();
/**
* LocalDateTime 转 字符串,指定日期格式
* @param time
* @param pattern
* @return
*/
public static String format(LocalDateTime localDateTime, String pattern){
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern);
String timeStr = formatter.format(localDateTime.atZone(DEFAULT_ZONE_ID));
return timeStr;
}
/**
* Date 转 字符串, 指定日期格式
* @param time
* @param pattern
* @return
*/
public static String format(Date time, String pattern){
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern);
String timeStr = formatter.format(time.toInstant().atZone(DEFAULT_ZONE_ID));
return timeStr;
}
/**
* Date 转 字符串,默认日期格式
* @param time
* @return
*/
public static String format(Date time){
String timeStr = DEFAULT_DATE_TIME_FORMATTER.format(time.toInstant().atZone(DEFAULT_ZONE_ID));
return timeStr;
}
/**
* timestamp 转 字符串,默认日期格式
*
* @param time
* @return
*/
public static String format(long timestamp) {
String timeStr = DEFAULT_DATE_TIME_FORMATTER.format(new Date(timestamp).toInstant().atZone(DEFAULT_ZONE_ID));
return timeStr;
}
/**
* 字符串 转 Date
*
* @param time
* @return
*/
public static Date strToDate(String time) {
LocalDateTime localDateTime = LocalDateTime.parse(time, DEFAULT_DATE_TIME_FORMATTER);
return Date.from(localDateTime.atZone(DEFAULT_ZONE_ID).toInstant());
}
}
- 自定义映射函数
public static class VisitorMapFunction extends RichMapFunction
//记录用户udid访问状态
private ValueState<String> newDayVisitorState;
@Override
public void open(Configuration parameters) throws Exception {
//对状态以及日期格式进行初始化
newDayVisitorState = getRuntimeContext().getState(
new ValueStateDescriptor<String>("newDayVisitorState", String.class)
);
}
/**
* 新老访客和uv 对比,如果都是天维度的话就一样
* 新老访客可以是指1天内、1个月内维度
* uv可以是指 月独立、日独立访客
*
* @param jsonObj
* @return
* @throws Exception
*/
@Override
public String map(JSONObject jsonObj) throws Exception {
//获取之前是否有访问日期
String beforeDateState = newDayVisitorState.value();
//获取访问时间戳
Long ts = jsonObj.getLong("ts");
String currentDateStr = TimeUtil.formatYMD(ts);
//如果状态不为空,并且状态日期和当前日期不相等,则是老访客
if (StringUtils.isNotBlank(beforeDateState)) {
//判断是否为同一天数据,一样则是老访客
if (beforeDateState.equals(currentDateStr)) {
jsonObj.put("is_new", 0);
System.out.println("老访客 "+currentDateStr);
} else {
//不一样是则是新访客,更新访问日期
jsonObj.put("is_new", 1);
newDayVisitorState.update(currentDateStr);
System.out.println("新访客 "+currentDateStr);
}
} else {
//如果状态为空,则之前没访问过,是新用户,
jsonObj.put("is_new", 1);
newDayVisitorState.update(currentDateStr);
System.out.println("新访客 "+currentDateStr);
}
return jsonObj.toJSONString();
}
}
<a name="ead7495b"></a>
#### 第8集 短链访问新老访客识别写入DWD层实战和链路测试
**简介: 短链访问新老访客识别开发写入DWD层实战**
- 编码实现
/**
* 写出到dwd层
*/
public static final String SINK_TOPIC = “dwd_link_visit_topic”;
//4、标记新老访客
SingleOutputStreamOperator
//将写sink到dwd层,kafka 存储
FlinkKafkaProducer
-
链路测试
-
Kafka命令
//测试数据 {“ip”:”141.123.11.31”,”ts”:1646145133665,”event”:”SHORT_LINK_TYPE”,”udid”:null,”bizId”:”026m8O3a”,”data”:{“referer”:null,”accountNo”:”693100647796441088”,”user-agent”:”Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.109 Safari/537.36”}}
创建topic ./kafka-topics.sh —create —zookeeper 172.18.123.230:2181 —replication-factor 1 —partitions 1 —topic ods_link_visit_topic
查看topic ./kafka-topics.sh —list —zookeeper 172.18.123.230:2181
删除topic ./kafka-topics.sh —zookeeper 172.18.123.230:2181 —delete —topic ods_link_visit_topic
消费者消费消息 ./kafka-console-consumer.sh —bootstrap-server 120.79.150.146:9092 —from-beginning —topic dwd_link_visit_topic
生产者发送消息 ./kafka-console-producer.sh —broker-list 120.79.150.146:9092 —topic ods_link_visit_topic
![](https://file.xdclass.net/note/2020/javaweb/%E5%9B%BE%E7%89%87/logo.png#alt=logo) **愿景:"让编程不再难学,让技术与生活更加有趣"**
<a name="74a0ffb9"></a>
### 第五十六章 Flink数仓知识进阶-DWS层数据处理和写出
<a name="05308c44"></a>
#### 第1集 数仓里面的概念-宽表-维度表-事实表概念讲解
**简介: 数仓里面的概念-宽表-维度表-事实表概念讲解**
-
数仓概念
-
度量值
- 可统计次数、个数、金额等,比如订单表中的下单金额
-
维度表
-
对事实的描述信息,每一张维表对应现实世界中的一个对象或者概念,如:用户、商品、日期、地区维度
-
比如分析产品销售情况,可以选择按商品类型,销售区域等等来分析
-
事实表(每行代表一个"业务事件")
- 联系事实与维度表的数字度量值,事实数据表 包含 描述业务内特定事件的数据
- 是数据聚合后依据【某些维度】生成的结果表
- 事实表里存放了能体现实际数据或详细数据,一般由维度编码和事实数据组成
-
例子一
- 小滴课堂-老王,在22年11月11号,在天猫超市买了100件情趣衣服花了800元,10盒霸王生发洗发水花了400元
- 维度:时间、老王用户、情趣商品、霸王洗发水、
- 事实:100件、800元,10盒、400元
![](img/image-20220307193416706.png#alt=image-20220307193416706)
-
例子二
-
电商业务领域
-
维度表: user用户表、product商品表、coupon优惠券表、provice地理信息表
-
事实表:order_info订单表、order_detail订单明细表、product_comment商品评论表
-
商品表里存放了商品类型,商品编码,商品名字等等这些都属于商品的属性,这张就是张维度表
-
订单表里存放了商品的销售数量、销售额等等这张表就是张事实表
-
某地区商品的销量,是从地区这个角度观察商品销量的
-
事实表就是销量表,维度表就是地区表
-
结论
-
站在维度的角度去看事实表,看事实表的度量值
-
事实表就是你要关注的内容,维度表就是【你观察该事物的角度,是从哪个角度去观察这个内容的,商品角度,地区角度等】
-
维度是维度建模的基础和灵魂。在维度建模中,将度量称为“事实” , 将环境描述为“维度”。
-
什么是宽表和窄表
- 宽表(明细表)
- 简单讲字段比较多的数据库表,通常是指业务主题相关的指标、维度、属性关联在一起的一张数据库表
- 把不同的内容都放在同一张表存储,宽表不符合三范式的模型设计规范
- 尽量满足多维,多度量,遵循维度建模的原则
- 缺点:数据的大量冗余
- 优点:减少表关联数量,查询性能的提高,空间换时间
- 窄表
- 严格按照数据库设计三范式,尽量减少数据冗余
- 缺点:做数据分析查询OLAP时,需要大量关联多个表,性能下降
- 优点:存储省空间,大量数据只存储某个表
-
什么是数仓建模
-
OLTP中:Mysql数据库建表,表和表之间的关系模型,叫关系建模
-
OLAP中:根据一个事实表为中心进行建表,面向业务分析为主,叫维度建模
-
我们这个短链的数据分析不属于严格意义上的数仓建模,但很多类似的东西,方便大家学大数据
<a name="51ba6590"></a>
#### 第2集 Flink实时计算-DWM层业务需求说明
**简介: Flink实时计算-DWM层业务需求说明**
-
数据处理分层说明
-
ODS、DWD和业务关联不大
-
DWM、DWS和业务关联就大,属于轻量级聚合部分数据
![](img/image-20220224153449131.png#alt=image-20220224153449131)
-
业务需求
-
需要得出短链访问的终端设备分布情况,做出【宽表】
- 浏览器类型分布 Chrome
- 操作系统分布 Android
- 设备类型分布 Mobile、Computer
- 设备生产厂商 GOOGLE、APPLE
- 系统版本 Android 10、Intel Mac OS X 10_15_7
-
例子
浏览器组getBrowserName :Chrome 设备操作系统getOs:Android 系统版本getOSVersion: Android 10 设备类型getDeviceType:MOBILE 设备生产厂商getDeviceManufacturer:GOOGLE
浏览器组getBrowserName :Firefox 设备操作系统getOs:Mac OS X 系统版本getOSVersion: Intel Mac OS X 10.15 设备类型getDeviceType:COMPUTER 设备生产厂商getDeviceManufacturer:APPLE
浏览器组getBrowserName :Chrome 设备操作系统getOs:Mac OS X 系统版本getOSVersion: Intel Mac OS X 10_15_7 设备类型getDeviceType:COMPUTER 设备生产厂商getDeviceManufacturer:APPLE
-
问题来了,怎么得到上面的信息呢?
<a name="eeceba8d"></a>
#### 第3集 浏览器头User-Agent提取工具UserAgentUtils讲解
**简介: 浏览器头User-Agent提取工具UserAgentUtils讲解**
- UserAgentUtils工具介绍
- 一个用来解析 User-Agent 字符串的 Java 类库,
- 可以识别 浏览器名字,浏览器组,浏览器类型,浏览器版本,浏览器的渲染引擎,android和ios设备的类型
- 超过150种不同的浏览器; 7种不同的浏览器类型; 9种不同的Web应用
- 超过60种不同的操作系统; 6种不同的设备类型; 9种不同的渲染引擎;
- 工具类依赖
<dependency>
<groupId>eu.bitwalker</groupId>
<artifactId>UserAgentUtils</artifactId>
<version>1.21</version>
</dependency>
- 开发实战
@Slf4j public class UtilTest {
@Test
public void testUserAgentUtil(){
//browserName=Chrome,os=Android,manufacture=Google Inc.,deviceType=Mobile
//String userAgentStr = "Mozilla/5.0 (Linux; Android 10; LIO-AN00 Build/HUAWEILIO-AN00; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/77.0.3865.120 MQQBrowser/6.2 TBS/045713 Mobile Safari/537.36 MMWEBID/3189 MicroMessenger/8.0.11.1980(0x28000B51) Process/tools WeChat/arm64 Weixin NetType/WIFI Language/zh_CN ABI/arm64";
//browserName=Chrome,os=Mac OS X,manufacture=Apple Inc.,deviceType=Computer
//String userAgentStr = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36";
//browserName=Chrome,os=Android,manufacture=Google Inc.,deviceType=Mobile
String userAgentStr = "Mozilla/5.0 (Linux; Android 10; LIO-AN00 Build/HUAWEILIO-AN00; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/77.0.3865.120 MQQBrowser/6.2 TBS/045713 Mobile Safari/537.36 MMWEBID/3189 MicroMessenger/8.0.11.1980(0x28000B51) Process/tools WeChat/arm64 Weixin NetType/WIFI Language/zh_CN ABI/arm64";
UserAgent userAgent = UserAgent.parseUserAgentString(userAgentStr);
Browser browser = userAgent.getBrowser();
OperatingSystem operatingSystem = userAgent.getOperatingSystem();
String browserName = browser.getGroup().getName();
String os = operatingSystem.getGroup().getName();
String manufacture = operatingSystem.getManufacturer().getName();
String deviceType = operatingSystem.getDeviceType().getName();
System.out.println("browserName="+browserName+",os="+os+",manufacture="+manufacture+",deviceType="+deviceType);
}
}
- 测试数据
Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36
Mozilla/5.0 (Linux; Android 10; LIO-AN00 Build/HUAWEILIO-AN00; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/77.0.3865.120 MQQBrowser/6.2 TBS/045713 Mobile Safari/537.36 MMWEBID/3189 MicroMessenger/8.0.11.1980(0x28000B51) Process/tools WeChat/arm64 Weixin NetType/WIFI Language/zh_CN ABI/arm64
<a name="bcfff144"></a>
#### 第4集 浏览器头User-Agent提取工具类封装开发实战
**简介: 浏览器头User-Agent提取工具类封装开发实战**
- 编码实战
/**
*
*
* 浏览器名称
* @param userAgent
* @return Firefox、Chrome
*/
public static String getBrowserName(String userAgent) {
Browser browser = getBrowser(userAgent);
String browserGroup = browser.getGroup().getName();
return browserGroup;
}
/**
* 获取deviceType
* @param userAgent
*
* @return MOBILE、COMPUTER
*/
public static String getDeviceType(String userAgent) {
OperatingSystem operatingSystem = getOperatingSystem(userAgent);
String deviceType = operatingSystem.getDeviceType().toString();
return deviceType;
}
/**
* 获取os:Windows/ios/Android
* @param userAgent
* @return
*/
public static String getOS(String userAgent) {
OperatingSystem operatingSystem = getOperatingSystem(userAgent);
String os = operatingSystem.getGroup().getName();
return os;
}
/**
* 获取device的生产厂家
*
* @param userAgent
* @return GOOGLE、APPLE
*/
public static String getDeviceManufacturer(String userAgent) {
OperatingSystem operatingSystem = getOperatingSystem(userAgent);
String deviceManufacturer = operatingSystem.getManufacturer().toString();
return deviceManufacturer;
}
/**
* 操作系统版本
* @param userAgent
* @return Android 1.x、Intel Mac OS X 10.15
*/
public static String getOSVersion(String userAgent) {
String osVersion = "";
if(StringUtils.isBlank(userAgent)) {
return osVersion;
}
String[] strArr = userAgent.substring(userAgent.indexOf("(")+1,
userAgent.indexOf(")")).split(";");
if(null == strArr || strArr.length == 0) {
return osVersion;
}
osVersion = strArr[1];
return osVersion;
}
/**
* 获取浏览器对象
* @param request
* @return
*/
private static Browser getBrowser(String agent) {
UserAgent userAgent = UserAgent.parseUserAgentString(agent);
Browser browser = userAgent.getBrowser();
return browser;
}
/**
* 获取操作系统对象
* @param userAgent
* @return
*/
private static OperatingSystem getOperatingSystem(String userAgent) {
UserAgent agent = UserAgent.parseUserAgentString(userAgent);
OperatingSystem operatingSystem = agent.getOperatingSystem();
return operatingSystem;
}
- 设备信息对象
@Data @AllArgsConstructor @NoArgsConstructor @Builder public class DeviceInfoDO {
/**
* 浏览器名称
*/
private String browserName;
/**
* 操作系统
*/
private String os;
/**
* 系统版本
*/
private String osVersion;
/**
* 设备类型
*/
private String deviceType;
/**
* 设备厂商
*/
private String deviceManufacturer;
/**
* 用户唯一标识
*/
private String udid;
}
<a name="6db8d95b"></a>
#### 第5集 DWD层处理-短链访问设备信息宽表开发实战
**简介: DWD层处理-短链访问设备信息宽表开发实战**
- 编码实战
/**
* DWM层 kafka相关配置
*/
public static final String SOURCE_TOPIC = "dwd_link_visit_topic";
public static final String GROUP_ID = "dwm_short_link_group";
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.setParallelism(1);
//1、获取dwd日志
FlinkKafkaConsumer<String> source = KafkaUtil.getKafkaConsumer(SOURCE_TOPIC, GROUP_ID);
DataStreamSource<String> ds = env.addSource(source);
//2、格式进行转换 补齐设备信息
SingleOutputStreamOperator<ShortLinkWideDO> deviceWideDS = ds.flatMap(new FlatMapFunction<String, ShortLinkWideDO>() {
@Override
public void flatMap(String value, Collector<ShortLinkWideDO> out) throws Exception {
//需要到 异步查询,归一化设备、地理位置信息
JSONObject jsonObject = JSON.parseObject(value);
//设备信息
DeviceInfoDO deviceInfoDO = getDeviceInfoDO(jsonObject);
ShortLinkWideDO shortLinkWideDO = ShortLinkWideDO.builder()
//短链访问基本信息
.visitTime(jsonObject.getLong("ts"))
.accountNo(jsonObject.getJSONObject("data").getLong("accountNo"))
.code(jsonObject.getString("bizId"))
.referer(jsonObject.getString("referer"))
.isNew(jsonObject.getInteger("is_new"))
.ip(jsonObject.getString("ip"))
//设备
.browserName(deviceInfoDO.getBrowserName())
.os(deviceInfoDO.getOs())
.osVersion(deviceInfoDO.getOsVersion())
.deviceType(deviceInfoDO.getDeviceType())
.deviceManufacturer(deviceInfoDO.getDeviceManufacturer())
.udid(deviceInfoDO.getUdid())
.build();
out.collect(shortLinkWideDO);
}
});
deviceWideDS.print("补齐宽表后的数据");
env.execute();
}
- 创建宽表对象
@Data @NoArgsConstructor @AllArgsConstructor @Builder public class ShortLinkWideDO {
/**
* 短链压缩码
*/
private String code;
/**
* 租户id
*/
private Long accountNo;
/**
* 访问时间
*/
private Long visitTime;
/**
* 站点来源,只记录域名
*/
private String referer;
/**
* 1是新访客,0是老访客
*/
private Integer isNew;
//==============DeviceInfoDO==================
/**
* 浏览器名称
*/
private String browserName;
/**
* 操作系统
*/
private String os;
/**
* 系统版本
*/
private String osVersion;
/**
* 设备类型
*/
private String deviceType;
/**
* 设备厂商
*/
private String deviceManufacturer;
/**
* 用户唯一标识
*/
private String udid;
}
<a name="018d8487"></a>
#### 第6集 DWD层处理-短链访问-设备信息宽表链路数据测试
**简介: DWD层处理-短链访问记录宽表链路数据测试**
- 不同浏览器访问短链
- [http://localhost:8003/026m8O3a](http://localhost:8003/026m8O3a)
- 改ip为ip访问
- [http://192.168.0.129:8003/026m8O3a](http://192.168.0.129:8003/026m8O3a)
- 打印访问记录宽表信息
![](https://file.xdclass.net/note/2020/javaweb/%E5%9B%BE%E7%89%87/logo.png#alt=logo) **愿景:"让编程不再难学,让技术与生活更加有趣"**
<a name="0af5d28e"></a>
### 第五十七章 短链访问数据可视化-地理位置信息解析实战
<a name="d1d584ee"></a>
#### 第1集 DWM层数据处理-地理位置信息解析需求讲解
**简介: DWM层数据处理-地理位置信息解析需求讲解**
-
需求说明
- IP信息转换为地理位置信息
- 案例:[https://tongji.baidu.com/web/demo/visit/district?siteId=16847648](https://tongji.baidu.com/web/demo/visit/district?siteId=16847648)
-
解决方案
- 离线
- 纯真IP库
- GeoLite2
- 埃文科技
- ip2region
- [https://github.com/lionsoul2014/ip2region](https://github.com/lionsoul2014/ip2region)
- 在线
- 百度地图API
- [https://lbsyun.baidu.com/index.php?title=webapi/ip-api](https://lbsyun.baidu.com/index.php?title=webapi/ip-api)
- 高德地图API
- [https://lbs.amap.com/api/webservice/guide/api/ipconfig](https://lbs.amap.com/api/webservice/guide/api/ipconfig)
- 淘宝IP库
- 阿里云产品
-
使用链路
<br />![](img/image-20220322111024000.png#alt=image-20220322111024000)
<a name="73cfe6c6"></a>
#### 第2集 IP地理位置信息解析-高德API接入指引
**简介: IP地理位置信息解析-高德API接入指引**
-
高德开放平台->开发支持->web服务API
- 地址:[https://lbs.amap.com/api/webservice/guide/api/ipconfig](https://lbs.amap.com/api/webservice/guide/api/ipconfig)
- 注册
- 认证
-
IP解析接口测试-Postman
- [https://lbs.amap.com/api/webservice/guide/api/ipconfig](https://lbs.amap.com/api/webservice/guide/api/ipconfig)
- 获取字段:省份-城市(不包括国家)
-
流量分析和使用限制说明
-
思考点
- 如果使用频率超过限制了怎么办
- 流量包业务流量包业务是不是和短链业务类似
![](img/image-20220322132824255.png#alt=image-20220322132824255)
<a name="c0af282a"></a>
#### 第3集 高频面试题-突破微信支付统一下单每秒600-QPS
**简介: 高频面试题-如何突破微信支付统一下单600QPS**
- 很多同学面试被问到,在秒杀或者高并发电商业务,微信支付的统一下单接口频率是有限制的,怎么办
- 微信支付文档里之前是60的QPS频率限制,现在最新的文档是600的QPS频率限制
- 如何突破这个限制?网上应该搜索不到的答案
- [https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pages/ico-guide/chapter1_5.shtml](https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pages/ico-guide/chapter1_5.shtml)
![](img/image-20220322114720264.png#alt=image-20220322114720264)
-
解决方式
- 多商户策略,采用负载均衡方式进行操作
- 记录好用户下单所用的商户信息,也可以预先绑定好
- 例子
- 准备5个商户号, 用户下单根据用户id取模是采用哪个商户进行调用
- 统一下单、退款、查询订单状态等就能分摊瞬时压力,也是固定到使用对应的商户进行操作
- 根据API进行控制好频率+监控
- 秒杀类业务,常规都是高并发锁定库存生成订单,然后支付是可以靠前端页面做离散支付的一定时间内错峰
-
那Ip解析地理位置信息是不是也类似
- 对接多个在线解析平台,比如百度、高德、腾讯等
- 做好统一的地理位置信息编码即可
<a name="7e7ae8c0"></a>
#### 第4集 DWM层处理-用户访问IP解析开发实战《上》
**简介: DWM层处理-用户访问IP解析开发实战**
-
添加maven依赖
-
补齐宽表数据DO
/
国家
/
private String country;
/
省份
/
private String province;
/
城市
/
private String city;
/
运营商
/
private String isp;
/
访问ip
/
private String ip;
- 自定义函数
@Slf4j
public class LocationMapFunction extends RichMapFunction<a name="ee6cff20"></a>
#### 第5集 DWM层处理-用户访问IP解析开发实战《下》
**简介: DWM层处理-用户访问IP解析配置开发实战**
- 开发
//3、关联ip城市
SingleOutputStreamOperator- 测试数据
{“ip”:”113.68.152.139”,”ts”:1646145133665,”event”:”SHORT_LINK_TYPE”,”udid”:null,”bizId”:”026m8O3a”,”data”:{“referer”:null,”accountNo”:”693100647796441088”,”user-agent”:”Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.109 Safari/537.36”}}
![](https://file.xdclass.net/note/2020/javaweb/%E5%9B%BE%E7%89%87/logo.png#alt=logo) **愿景:"让编程不再难学,让技术与生活更加有趣"**
<a name="7525da70"></a>
### 第五十八章 DWM层数据处理-Flink异步IO提升性能
<a name="fb081c5a"></a>
#### 第1集 IP解析地理位置问题点和Flink异步IO介绍
**简介: IP解析地理位置问题点和Flink异步IO介绍**
-
Flink实时计算处理存在的问题
-
IP解析地理位置信息,查询是同步查询,存在阻塞,性能不高
-
在构建实时数仓等应用场景下,与外部维表等的关联需要大量外部存储的交互,去补充更多维度属性信息,如HTTP网络、Redis、Mysql数据库、Hbase等进行查询
-
默认Flink里面用 MapFunction进行对象关联,只能用同步方式去进行IO调用,需要等请求完成才进行发下一个请求,这种等待占了函数时间的绝大部分;
-
一种方式是通过提高Flink并行度,是可以提高效率但是会浪费更多资源,高并行度MapFunction意味着更多的subtask,线程,网络连接,数据库连接
-
解决方案
- 在Flink需要与外部系统打交道,由于外部系统的问题,可能导致时间耗时比较长,为了不影响flink的处理性能,flink引入了异步IO来处理这个问题
- Flink 1.2里面引入了Async-IO 异步IO(阿里巴巴贡献的特性),利用异步交互意味着【单个并行函数可以同时处理多个请求并同时接收响应】,哪个请求先返回就先处理,在连续的请求的时候不需要阻塞式等待。
- 异步等待的时间可以与发送其他请求和接收响应重叠,等待时间分摊到多个请求中,系统就有更高的吞吐量
![](img/image-20220322201037379.png#alt=image-20220322201037379)
- 先决条件
- 对数据库(或键/值存储)实现适当的异步 I/O 需要该数据库的客户端支持异步请求,许多流行的数据库都提供这样的客户端。
- 在没有这样的客户端的情况下,可以通过【线程池】处理同步调用来,将同步客户端变为支持的异步并发客户端,但是这种方法肯定不如自带的异步客户端有效(一般厂商的会做更多优化)
<a name="f5940833"></a>
#### 第2集 Flink异步IO使用步骤和注意事项讲解
**简介: Flink异步IO使用步骤和注意事项讲解**
-
使用步骤
- 文档:[https://nightlies.apache.org/flink/flink-docs-release-1.14/docs/dev/datastream/operators/asyncio](https://nightlies.apache.org/flink/flink-docs-release-1.14/docs/dev/datastream/operators/asyncio)
An implementation of AsyncFunction that dispatches the requests
实现接口 AsyncFunction 用于请求分发
A callback that takes the result of the operation and hands it to the ResultFuture
定义一个callback回调函数,该函数用于取出异步请求的返回结果,并将返回的结果传递给ResultFuture
Applying the async I/O operation on a DataStream as a transformation
对DataStream的数据使用Async操作
-
例子
/**
AsyncFunction的timeout
方法来自定义超时之后的处理方式
- 响应结果的顺序:AsyncDataStream包含两种输出模式,
- unorderedWait无序:响应结果的顺序与异步请求的顺序不同
- orderedWait有序:响应结果的顺序与异步请求的顺序相同
#### 第3集 带你掌握异步编程CompletableFuture核心知识
简介: 带你掌握异步编程CompletableFuture核心知识
-
什么是CompletableFuture
- JDK1.5有了Future和Callable的实现,想要异步获取结果,通常会以轮询的方式去获取结果
@Test
public void testFuture() throws ExecutionException, InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(10);
//定义一个异步任务
Future<String> future = executorService.submit(()->{
Thread.sleep(2000);
return "我要去小滴课堂-学海量数据项目大课";
});
//轮询获取结果,耗费的CPU资源
while (true){
if(future.isDone()) {
System.out.println(future.get());
break;
}
}
}
-
JDK8里面引入的CompletableFuture,帮助我们简化异步编程复杂性,函数式编程让代码更加简洁
-
CompletableFuture类实现了Future和CompletionStage接口
Future 表示异步计算的结果,它提供了检查计算是否完成的方法,以等待计算的完成,计算完成后只能使用 get 方法来获取结果,有cancel、get、isDone、isCancelled等方法
-
方法API
-
CompletableFuture静态方法,执行异步任务的API
//无返回值,默认使用ForkJoinPool.commonPool() 作为它的线程池执行异步代码
public static CompletableFuture<Void> runAsync(Runnable runnable)
//无返回值,可以自定义线程池
public static CompletableFuture<Void> runAsync(Runnable runnable, Executor executor)
//有返回值,默认使用ForkJoinPool.commonPool() 作为它的线程池执行异步代码
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier)
//有返回值,可以自定义线程池
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor)
-
CompletableFuture对象,获取结果的API
//如果返回值没有返回,一直阻塞
V get()
//设置等待超时的时间
V get(long timeout,Timeout unit);
//有返回值就返回, 线程抛出异常就返回设置的默认值
T getNow(T defaultValue);
-
CompletableFuture对象,其他重点API
//无返回值,当前任务正常完成以后执行,当前任务的执行结果可以作为下一任务的输入参数
thenAccept
//有返回值,当前任务正常完成以后执行,当前任务的执行的结果会作为下一任务的输入参数
thenApply
//对不关心上一步的计算结果,执行下一个操作
thenRun
#### 第4集 异步编程CompletableFuture核心API实战
简介: 异步编程CompletableFuture核心API实战
- 案例
@Test
public void testFuture2() throws ExecutionException, InterruptedException {
//有返回值,默认使用ForkJoinPool.commonPool() 作为它的线程池执行异步代码
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() ->{
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) { }
System.out.println(Thread.currentThread()+"执行,返回 二当家小D");
return "二当家小D,";
});
System.out.println("future1返回值:" + future1.get()); //输出 二当家小D
}
@Test
public void testFuture3() throws ExecutionException, InterruptedException, TimeoutException {
//有返回值,默认使用ForkJoinPool.commonPool() 作为它的线程池执行异步代码
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() ->{
System.out.println("执行任务一");
return "冰冰一,";
});
//有返回值,当前任务正常完成以后执行,当前任务的执行的结果会作为下一任务的输入参数
CompletableFuture<String> future2 = future1.thenApply((element) -> {
System.out.println("入参:"+element);
System.out.println("执行任务二");
return "冰冰二";
});
System.out.println("future2返回值:" + future2.get(1, TimeUnit.SECONDS));
}
@Test
public void testFuture4() throws ExecutionException, InterruptedException, TimeoutException {
//有返回值,默认使用ForkJoinPool.commonPool() 作为它的线程池执行异步代码
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() ->{
System.out.println("执行任务一");
return "冰冰一,";
});
//无返回值,当前任务正常完成以后执行,当前任务的执行结果可以作为下一任务的输入参数
CompletableFuture<Void> future2 = future1.thenAccept((element) -> {
System.out.println("入参:"+element);
System.out.println("执行任务二");
});
//System.out.println("future2返回值:" + future2.get(1, TimeUnit.SECONDS));
System.out.println("future2返回值:" + future2.get());
}
#### 第5集 Flink异步IO优化Ip地理位置解析实战
简介: Flink异步IO优化Ip地理位置解析实战
-
asyncInvoke方法两种方式实现异步
-
ExecutorService线程池
-
HTTP异步客户端
-
引入异步http客户端
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpasyncclient</artifactId>
<version>4.1.5</version>
</dependency>
- 编码开发
@Slf4j
public class AsyncLocationRequestFunction extends RichAsyncFunction<ShortLinkWideDO, String> {
private static final String IP_PARSE_URL = "https://restapi.amap.com/v3/ip?ip=%s&output=json&key=4f6e1b4212a5fdec6198720f261892bd";
private CloseableHttpAsyncClient httpClient;
@Override
public void open(Configuration parameters) throws Exception {
this.httpClient = createAsyncHttpClient();
}
@Override
public void close() throws Exception {
if (httpClient != null) {
httpClient.close();
}
}
@Override
public void asyncInvoke(ShortLinkWideDO shortLinkWideDO, ResultFuture<String> resultFuture) throws Exception {
String ip = shortLinkWideDO.getIp();
String url = String.format(IP_PARSE_URL, ip);
HttpGet httpGet = new HttpGet(url);
try {
// 发起异步请求,获取异步请求的future对象, callback是回调函数,也可通过回调函数拿结果
Future<HttpResponse> future = httpClient.execute(httpGet, null);
// 从Future中取数据
CompletableFuture<ShortLinkWideDO> completableFuture =
CompletableFuture.supplyAsync(new Supplier<ShortLinkWideDO>() {
@Override
public ShortLinkWideDO get() {
try {
HttpResponse response = future.get();
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode == HttpStatus.SC_OK) {
HttpEntity entity = response.getEntity();
String result = EntityUtils.toString(entity, "UTF-8");
JSONObject locationObj = JSON.parseObject(result);
String city = locationObj.getString("city");
String province = locationObj.getString("province");
shortLinkWideDO.setProvince(province);
shortLinkWideDO.setCity(city);
}
return shortLinkWideDO;
} catch (Exception e) {
log.error("异步请求异常:{}",shortLinkWideDO);
return null;
}
}
});
// 取出的数据,存入ResultFuture,返回给方法
completableFuture.thenAccept(new Consumer<ShortLinkWideDO>() {
@Override
public void accept(ShortLinkWideDO result) {
//complete()里面需要的是Collection集合,集合使用singleton单例模式
resultFuture.complete(Collections.singleton(JSON.toJSONString(result)));
}
});
} catch (Exception e) {
log.error("ip解析错误,value={},msg={}", shortLinkWideDO, e.getMessage());
resultFuture.complete(Collections.singleton(null));
}
}
private CloseableHttpAsyncClient createAsyncHttpClient() {
try {
RequestConfig requestConfig = RequestConfig.custom()
//返回数据的超时时间
.setSocketTimeout(20000)
//连接上服务器的超时时间
.setConnectTimeout(10000)
//从连接池中获取连接的超时时间
.setConnectionRequestTimeout(1000)
.build();
ConnectingIOReactor ioReactor = new DefaultConnectingIOReactor();
PoolingNHttpClientConnectionManager connManager = new PoolingNHttpClientConnectionManager(ioReactor);
//设置连接池最大是500个连接
connManager.setMaxTotal(500);
//MaxPerRoute是对maxtotal的细分,每个主机的并发最大是300,route是指域名
connManager.setDefaultMaxPerRoute(300);
CloseableHttpAsyncClient httpClient = HttpAsyncClients.custom().setConnectionManager(connManager)
.setDefaultRequestConfig(requestConfig)
.build();
httpClient.start();
return httpClient;
} catch (IOReactorException e) {
log.error("初始化 CloseableHttpAsyncClient异常:{}",e.getMessage());
return null;
}
}
}
#### 第6集 Flink异步IO优化Ip地理位置解析链路测试
简介: Flink异步IO优化Ip地理位置解析链路测试
- 开发
SingleOutputStreamOperator<String> shortLinkWideDS =
AsyncDataStream.unorderedWait(deviceWideDS, new AsyncLocationRequestFunction(),
1000, TimeUnit.MILLISECONDS, 100);
- 测试
{"ip":"113.68.152.139","ts":1646145133665,"event":"SHORT_LINK_TYPE","udid":null,"bizId":"026m8O3a","data":{"referer":null,"accountNo":"693100647796441088","user-agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.109 Safari/537.36"}}
愿景:”让编程不再难学,让技术与生活更加有趣”
### 第五十九章 DWM层数据处理-UV访客统计实战
#### 第1集 大厂里面的日活跃用户-UV-活跃留存用户讲解
简介: 大厂里面的日活跃用户-UV-活跃留存用户讲解
-
在互联网公司里面,高级开发或者技术Leader都是有两个指标
- 技术指标:RT响应时间、服务器利用率、接口2xx、5xx的占比等
- 业务指标:UV、PV、留存、日活、点击率、转化率等
-
概念(产品视野的拓展)
-
UV(Uniqued Visitor)
- 独立访客就是独立IP访客(Unique Visitor),访问网站的一台电脑客户端为一个访客,在 00:00-24:00内相同的客户端只被计算一次。
- 记录独立访客数的时间标准一般可为一天,一个月,一般不计算年UV数
-
PV(Page View)
- 页面浏览量或点击量,用户每次刷新即被计算一次。指某站点总共有被浏览多少个页面,它是重复累计的,同一个页面被重复浏览也被计入PV。
-
独立IP数
- 是指1天内多少个独立的IP浏览了页面,即统计不同的IP浏览用户数量。
- 同一IP不管访问了几个页面,独立IP数均为1;不同的IP浏览页面,计数会加1,如果用户不断更换IP,则有可能被多次统计
- IP是基于用户广域网IP地址来区分不同的访问者的,多个用户(多个局域网IP)在同一个路由器(同一个广域网IP)内上网,可能被记录为一个独立IP访问者
-
什么是活跃用户 Active User
- 分为 【DAU 日活跃用户数】、【WAU 周活跃用户数】、【MAU 月活跃用户数】
- 不同产品是不一样的概念,好比你访问了【小滴课堂】网站,停留了几秒钟,这个算活跃用户?需要有活跃用户的标准,定义好关键行为
- 不同产品类型,不同的阶段,不同的场景,对活跃用户的定义差别很大
- 例子
- 社交类产品,前期:注册登录过,后期:添加好有多少个才算等
- 内容类 新闻、小说:打开网站的不算,阅读过多少分钟或者多少篇才算
-
留存用户
- 1日留存、7日留存、30日留存等,一般不要求连续做【某个关键路径】
- 第N日留存:指的是新增用户日之后的第N日依然活跃的用户占新增用户的比例
- 第7日留存率:(当天新增的用户中,新增日之后的第7天还活跃的用户数)/第一天新增总用户数;
-
这些有啥用呢?
-
数据可视化、数据仓库里等核心的统计指标(熟悉概念)
-
作为产品的核心指标,公司可以指定目标值和正常值,进一步优化提升
-
根据产品的增长情况,可以判断产品是否要进行大规模推广或者暂缓
#### 第2集 短链平台里面的UV用户统计需求和实现思路
简介: 短链平台里面的UV用户统计需求和实现思路
-
短链里面统计日活UV
- 日活跃用户:访问过短链的即可算入
- 怎么统计
- 需要知道用户的唯一ID
- 需要知道访问时间
- 如果是同一天访问的就可以去重
-
实现思路和注意事项
- 利用KeyState,分组存储是否当天访问过
- 程序一直运行下去,要定期清理内存里的过时数据
-
Flink中的状态
- 算子状态(Operatior State)
- 数据结构:ListState、BroadcaseState
- 键控状态(Keyed State)
- 数据结构:ValueState、ListState、MapState
- KeyedStream上有任务出现的状态,定义的不同的key来维护这个状态;
- 不同的key是独立访问的,一个key只能访问它自己的状态,不同key之间也不能互相访问
- Flink为每个key维护一个状态实例,并将具有相同键的所有数据,都分区到同一个算子任务中
- 状态后端(State Backends)
- 当检查点(checkpoint)机制启动时,状态将在检查点中持久化来应对数据丢失以及恢复
- MemoryStateBackend 、FsStateBackend、RocksDBStateBackend
-
状态存活时间 State Time-To-Live (TTL)
- 文档:https://www.ververica.com/blog/state-ttl-for-apache-flink-how-to-limit-the-lifetime-of-state
- Flink 1.6版本开始引入State TTL机制,可以允许作业中定义的keyed状态进行超时自动处理
- State TTL 功能给每个 Flink 的 Keyed 状态增加了一个“时间戳”,而 Flink 在状态创建、写入或读取(可选)时更新这个时间戳,并且判断状态是否过期
- 如果状态过期,会根据可见性参数,来决定是否返回已过期但还未清理的状态等等。
- 可以将存活时间(TTL)分配给任何类型的keyed state,如果配置了TTL并且状态值已过期,则将尽力清除存储的值
- 状态的清理并不是即时的,是使用了一种 Lazy 的算法来实现(类似Redis惰性删除key),从而减少状态清理对性能的影响
- 默认情况下,只有在明确读出过期值时才会删除过期值,例如通过调用 ValueState.value()
, 默认情况下,如果未读取过期状态,则不会删除它,可能会导致状态不断增长
- 构建StateTtlConfig
配置对象,可以在任何状态描述符中启用TTL功能
StateTtlConfig ttlConfig = StateTtlConfig
.newBuilder(Time.seconds(1))
.setUpdateType(StateTtlConfig.UpdateType.OnCreateAndWrite)
.setStateVisibility(StateTtlConfig.StateVisibility.NeverReturnExpired)
.build();
Time.seconds(1) 周期过期时间
setUpdateType 更新类型
setStateVisibility 是否在访问state的时候返回过期值
表示状态时间戳的更新的时机
setUpdateType:
StateTtlConfig.UpdateType.OnCreateAndWrite - 只在创建和写的时候清除 (默认)
StateTtlConfig.UpdateType.OnReadAndWrite - 在读和写的时候清除
表示对已过期但还未被清理掉的状态如何处理
setStateVisibility:
StateTtlConfig.StateVisibility.NeverReturnExpired - 从不返回过期值
StateTtlConfig.StateVisibility.ReturnExpiredIfNotCleanedUp - 如果仍然可用,
#### 第3集 DWM层数据处理-短链UV统计开发实战《上》
简介: DWM层数据处理-短链UV统计开发实战《上》
-
开发思路
- 获取数据
- 转换对象
- 分组统计
- 排重过滤
- 写入Kafka
-
编码实战
```
public class DwmUniqueVisitorApp {
/
定义source topic
/
public static final String SOURCE_TOPIC = “dwm_link_visit_topic”;
/
定义消费者组
/
public static final String GROUP_ID = “dwm_unique_visitor_group”;
/
定义输出
/
public static final String SINK_TOPIC = “dwm_unique_visitor_topic”;
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.setParallelism(1);
//DataStream<a name="05f9a43e"></a>
#### 第4集 DWM层数据处理-短链UV统计开发实战《下》
**简介: DWM层数据处理-短链UV统计开发实战《下》**
- Filter函数开发
public class UniqueVisitorFilterFunction extends RichFilterFunction<a name="158b4098"></a>
#### 第5集 DWM层数据处理-短链UV统计链路测试
**简介: DWM层数据处理-短链UV统计链路测试**
- 链路测试
//ODS层数据
{“ip”:”113.68.152.139”,”ts”:1646145133665,”event”:”SHORT_LINK_TYPE”,”udid”:null,”bizId”:”026m8O3a”,”data”:{“referer”:null,”accountNo”:”693100641796441088”,”user-agent”:”Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.109 Safari/537.36”}}
- UV统计和新老用户标记的区分说明
- 新老用户目前是用天进行区分,后续可根据业务规则改为周或者月维度区分新老访客
- 比如超过一周才来的就是新访客
- UV可以用天维度进行统计,月UV可以用月维度进行统计;
- 如果新老用户是天区分,UV也是天区分,则两者是一样的
![](https://file.xdclass.net/note/2020/javaweb/%E5%9B%BE%E7%89%87/logo.png#alt=logo) **愿景:"让编程不再难学,让技术与生活更加有趣"**
<a name="e933a3fa"></a>
### 第六十章 Flink多流合并和DWS层数据聚合实战
<a name="209e9da2"></a>
#### 第1集 DWS层数据处理-多流数据聚合和Union算子讲解
**简介: DWS层数据处理-多流数据聚合讲解**
-
数据分析中最重要、最基础的2个概念:维度Dimensions 和度量Measures
- 度量是数据表中的数值数据,维度是类别数据
- 例子:各个城市访问的UV数量
- 一个数据指标一般由一种或多种维度加上一种度量组成
-
需求统计点
- DWS层数据是做啥的?
- 度量:PV、UV
- 维度 : 新老用户、地区信息、设备信息等
- DWS层数据处理后-存储到ClickHouse、Redis、Mysql、ElasticSearch等都可以
- ADS层需要使用的数据就从上述的DWS层进行读取,主要是根据各种报表及可视化来生成统计数据。
![](img/image-20220224153449131.png#alt=image-20220224153449131)
- 数据分层
| 数据分层 | 分层描述 | 数据生成计算工具 | 存储 |
| --- | :--- | --- | --- |
| ODS | 原生数据,短链访问基本信息 | SpringBoot生成 | Kafka |
| DWD | 对 ODS 层做数据清洗和规范化,新老访客标记等 | Flink | Kafka |
| DWM | 对DWD数据进一步加工补齐数据,独立访客统计,操作系统/ip/城市,做宽表 | Flink | kafka |
| DWS | 对DWM进行处理,多流合并,分组|聚合|开窗|统计,形成主题宽表 | Flink | ClickHouse |
| ADS | 从ClickHouse中读取数据,根据需求进行筛选聚合,可视化展示 | ClickHouseSql | web可视化展示 |
- 统计指标
- PV -》从DWM层 dwm_link_visit_topic统计
- UV-》从DWM层 dwm_unique_visitor_topic统计
-
Flink多流合并
-
Flink提供了多流转换算子,方便我们对多个数据流进行整合处理
-
union
- 可以合并多个【同类型】的数据流,并生成【同类型】的数据流
- 数据将按照先进先出(First In First Out)的模式合并,且不去重
![](img/image-20220327152121640.png#alt=image-20220327152121640)
-
connect
- 和union类型,但是各有利弊
- connect只能连接两个不同类型的数据流,union可以连接多个同类型数据流
- 经过connect之后被转化为ConnectedStreams,会对两个流的数据应用不同的处理方法
<a name="fd3e3007"></a>
#### 第2集 DWS层数据处理-业务Topic主题开发实战《上》
**简介: DWS层数据处理-业务Topic主题开发实战《上》**
- 创建Bean对象 ShortLinkVisitStatsDO
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class ShortLinkVisitStatsDO {
/
窗口开始时间 Clickhouse里面会用到
/
String startTime;
/
窗口结束时间
/
String endTime;
/
================================================
/
/
短链压缩码
/
private String code;
/
租户id
/
private Long accountNo;
/
访问时间
/
private Long visitTime;
/
站点来源,只记录域名
/
private String referer;
/
1是新访客,0是老访客
/
private Integer isNew;
/
唯一标识
/
private String udid;
//==============RegionInfoDO==================
/
省份
/
private String province;
/
城市
/
private String city;
/
运营商
/
private String isp;
/
访问ip
/
private String ip;
//==============DeviceInfoDO==================
/
浏览器名称
/
private String browserName;
/
操作系统
/
private String os;
/
系统版本
/
private String osVersion;
/
设备类型
/
private String deviceType;
/
设备厂商
/
private String deviceManufacturer;
//==============度量==================
private Long uv=0L;
private Long pv=0L;
}
- 编码实战
//1、获取多个数据
//2、结构转换 uniqueVisitorDS、shortLinkDS
//3、多流合并(合并相同结构的流)
//4、设置WaterMark
//5、多维度、多个字段分组
//6、开窗 15秒一次数据插入到 ck
//7、聚合统计(补充统计起止时间)
//8、输出Clickhouse
```
public class DwsShortLinkVisitStatsApp {
/**
* 宽表
*/
public static final String SHORT_LINK_SOURCE_TOPIC = "dwm_link_visit_topic";
public static final String SHORT_LINK_SOURCE_GROUP = "dws_link_visit_group";
/**
* uv的数据流
*/
public static final String UNIQUE_VISITOR_SOURCE_TOPIC = "dwm_unique_visitor_topic";
public static final String UNIQUE_VISITOR_SOURCE_GROUP = "dws_unique_visitor_group";
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.setParallelism(1);
//1、获取多个数据
FlinkKafkaConsumer<String> shortLinkSource = KafkaUtil.getKafkaConsumer(SHORT_LINK_SOURCE_TOPIC,SHORT_LINK_SOURCE_GROUP);
DataStreamSource<String> shortLinkDS = env.addSource(shortLinkSource);
FlinkKafkaConsumer<String> uniqueVisitorSource = KafkaUtil.getKafkaConsumer(UNIQUE_VISITOR_SOURCE_TOPIC, UNIQUE_VISITOR_SOURCE_GROUP);
DataStreamSource<String> uniqueVisitorDS = env.addSource(uniqueVisitorSource);
//2、结构转换 uniqueVisitorDS、shortLinkDS
SingleOutputStreamOperator<ShortLinkVisitStatsDO> shortLinkMapDS = shortLinkDS.map(new MapFunction<String, ShortLinkVisitStatsDO>() {
@Override
public ShortLinkVisitStatsDO map(String value) throws Exception {
ShortLinkVisitStatsDO visitStatsDO = parseVisitStats(value);
visitStatsDO.setPv(1L);
visitStatsDO.setUv(0L);
return visitStatsDO;
}
});
SingleOutputStreamOperator<ShortLinkVisitStatsDO> uniqueVisitorMapDS = uniqueVisitorDS.map(new MapFunction<String, ShortLinkVisitStatsDO>() {
@Override
public ShortLinkVisitStatsDO map(String value) throws Exception {
ShortLinkVisitStatsDO visitStatsDO = parseVisitStats(value);
visitStatsDO.setPv(0L);
visitStatsDO.setUv(1L);
return visitStatsDO;
}
});
//3、多流合并(合并相同结构的流)
DataStream<String> unionDS = shortLinkDS.union(uniqueVisitorDS);
//4、设置WaterMark
//5、多维度、多个字段分组
//6、开窗 15秒一次数据插入到 ck
//7、聚合统计(补充统计起止时间)
//8、输出Clickhouse
}
private static ShortLinkVisitStatsDO parseVisitStats(String value) {
JSONObject jsonObj = JSON.parseObject(value);
ShortLinkVisitStatsDO visitStatsDO = ShortLinkVisitStatsDO.builder()
.code(jsonObj.getString("code"))
.accountNo(jsonObj.getLong("accountNo"))
.visitTime(jsonObj.getLong("visitTime"))
.referer(jsonObj.getString("referer"))
.isNew(jsonObj.getInteger("isNew"))
.udid(jsonObj.getString("udid"))
//地理位置信息
.province(jsonObj.getString("province"))
.city(jsonObj.getString("city"))
.isp(jsonObj.getString("isp"))
.ip(jsonObj.getString("ip"))
//设备信息
.browserName(jsonObj.getString("browserName"))
.os(jsonObj.getString("os"))
.osVersion(jsonObj.getString("osVersion"))
.deviceType(jsonObj.getString("deviceType"))
.build();
return visitStatsDO;
}
}
#### 第3集 DWS层数据处理-业务Topic主题开发实战《下》
简介: DWS层数据处理-业务Topic主题开发实战《下》
- 编码实战
//1、获取多个数据
//2、结构转换 uniqueVisitorDS、shortLinkDS
//3、多流合并(合并相同结构的流)
//4、设置WaterMark
//5、多维度、多个字段分组
//6、开窗 15秒一次数据插入到 ck
//7、聚合统计(补充统计起止时间)
//8、输出Clickhouse
//3、多流合并(合并相同结构的流)
DataStream<ShortLinkVisitStatsDO> unionDS = shortLinkMapDS.union(uniqueVisitorMapDS);
//4、设置WaterMark
SingleOutputStreamOperator<ShortLinkVisitStatsDO> watermarkDS = unionDS.assignTimestampsAndWatermarks(WatermarkStrategy
//指定允许乱序延迟的最大时间 3 秒
.<ShortLinkVisitStatsDO>forBoundedOutOfOrderness(Duration.ofSeconds(3))
//指定POJO事件时间列,毫秒
.withTimestampAssigner((event, timestamp) -> event.getVisitTime()));
//5、多维度、多个字段分组
// code、referer、isNew
// province、city、ip
// browserName、os、deviceType
KeyedStream<ShortLinkVisitStatsDO, Tuple9<String, String, Integer, String, String, String, String, String, String>> keyedStream = watermarkDS
.keyBy(new KeySelector<ShortLinkVisitStatsDO,
Tuple9<String, String, Integer, String, String, String, String, String, String>>() {
@Override
public Tuple9<String, String, Integer, String, String, String, String, String, String> getKey(ShortLinkVisitStatsDO obj) throws Exception {
return Tuple9.of(obj.getCode(), obj.getReferer(), obj.getIsNew(),
obj.getProvince(), obj.getCity(), obj.getIp(),
obj.getBrowserName(), obj.getOs(), obj.getDeviceType());
}
});
//6、开窗 10秒一次数据插入到 ck
WindowedStream<ShortLinkVisitStatsDO, Tuple9<String, String, Integer, String, String, String, String, String, String>, TimeWindow> windowedStream = keyedStream
.window(TumblingEventTimeWindows.of(Time.seconds(10)));
//7、聚合统计(补充统计起止时间)
SingleOutputStreamOperator<ShortLinkVisitStatsDO> reduceDS = windowedStream.reduce(
new ReduceFunction<ShortLinkVisitStatsDO>() {
@Override
public ShortLinkVisitStatsDO reduce(ShortLinkVisitStatsDO statsDO1, ShortLinkVisitStatsDO statsDO2) throws Exception {
statsDO1.setPv(statsDO1.getPv() + statsDO2.getPv());
statsDO1.setUv(statsDO1.getUv() + statsDO2.getUv());
return statsDO1;
}
}, new ProcessWindowFunction<ShortLinkVisitStatsDO, ShortLinkVisitStatsDO, Tuple9<String, String, Integer, String, String, String, String, String, String>, TimeWindow>() {
@Override
public void process(Tuple9<String, String, Integer,String, String, String, String, String, String> tuple,
Context context, Iterable<ShortLinkVisitStatsDO> elements, Collector<ShortLinkVisitStatsDO> out) throws Exception {
for (ShortLinkVisitStatsDO visitStatsDO : elements) {
//窗口开始时间
String startDate = TimeUtil.format(context.window().getStart());
//窗口结束时间
String endDate = TimeUtil.format(context.window().getEnd());
visitStatsDO.setStartTime(startDate);
visitStatsDO.setEndTime(endDate);
out.collect(visitStatsDO);
}
}
}
);
//8、输出nosql
reduceDS.print(">>>>>");
env.execute();
#### 第4集 短链访问-模拟IP数据处理链路测试实战
简介: 短链访问-模拟IP数据处理链路测试实战
-
链路测试需求
- 访问短链->记录日志->KAFKA->ODS->DWD->DWM(设备补齐、地区补齐、UV统计、PV统计)->DWS
- 问题
- 本地访问是局域网ip,没法解析地理位置信息,临时编写死ip
-
编码
private static List<String> ipList = new ArrayList<>();
static {
//深圳
ipList.add("14.197.9.110");
//广州
ipList.add("113.68.152.139");
}
private static List<String> refererList = new ArrayList<>();
static {
refererList.add("https://taobao.com");
refererList.add("https://douyin.com");
}
//ip、浏览器信息
//String ip = CommonUtil.getIpAddr(request);
Random random = new Random();
String ip = ipList.get(random.nextInt(ipList.size())) ;
String referer = refererList.get(random.nextInt(refererList.size()));
//全部请求头
Map<String,String> headerMap = CommonUtil.getAllRequestHeader(request);
Map<String,String> availableMap = new HashMap<>();
availableMap.put("user-agent",headerMap.get("user-agent"));
//availableMap.put("referer",headerMap.get("referer"));
availableMap.put("referer",referer);
availableMap.put("accountNo",accountNo.toString());
-
测试链路
- 短链地址 http://localhost:8003/026m8O3a
- A浏览器 访问短链
- B浏览器 访问短链
-
Bug修复
- KeyBy分组字段修复
- 时间工具类修复
public class TimeUtil {
/**
* 默认日期格式
*/
private static final String DEFAULT_PATTERN = "yyyy-MM-dd";
private static final String DEFAULT_PATTERN_WITH_TIME = "yyyy-MM-dd hh:mm:ss";
/**
* 默认日期格式
*/
private static final DateTimeFormatter DEFAULT_DATE_FORMATTER = DateTimeFormatter.ofPattern(DEFAULT_PATTERN);
/**
* 默认日期时间格式
*/
private static final DateTimeFormatter DEFAULT_DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern(DEFAULT_PATTERN_WITH_TIME);
private static final ZoneId DEFAULT_ZONE_ID = ZoneId.systemDefault();
/**
* LocalDateTime 转 字符串,指定日期格式
* @param time
* @param pattern
* @return
*/
public static String format(LocalDateTime localDateTime, String pattern){
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern);
String timeStr = formatter.format(localDateTime.atZone(DEFAULT_ZONE_ID));
return timeStr;
}
/**
* Date 转 字符串, 指定日期格式
* @param time
* @param pattern
* @return
*/
public static String format(Date time, String pattern){
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern);
String timeStr = formatter.format(time.toInstant().atZone(DEFAULT_ZONE_ID));
return timeStr;
}
/**
* Date 转 字符串,默认日期格式
* @param time
* @return
*/
public static String format(Date time){
String timeStr = DEFAULT_DATE_FORMATTER.format(time.toInstant().atZone(DEFAULT_ZONE_ID));
return timeStr;
}
/**
* Date 转 字符串,默认日期格式
* @param time
* @return
*/
public static String format(Long timestamp){
String timeStr = DEFAULT_DATE_FORMATTER.format(new Date(timestamp).toInstant().atZone(DEFAULT_ZONE_ID));
return timeStr;
}
/**
* timestamp 转 字符串,指定日期格式
*
* @param time
* @return
*/
public static String formatWithTime(long timestamp) {
String timeStr = DEFAULT_DATE_TIME_FORMATTER.format(new Date(timestamp).toInstant().atZone(DEFAULT_ZONE_ID));
return timeStr;
}
/**
* 字符串 转 Date
*
* @param time
* @return
*/
public static Date strToDate(String time) {
LocalDateTime localDateTime = LocalDateTime.parse(time, DEFAULT_DATE_FORMATTER);
return Date.from(localDateTime.atZone(DEFAULT_ZONE_ID).toInstant());
}
}
愿景:”让编程不再难学,让技术与生活更加有趣”
### 第六十一章 海量数据存储Flink整合ClickHouse开发实战
#### 第1集 海量数据存储ClickHouse数据库介绍
简介: 海量数据存储ClickHouse数据库介绍
- ClickHouse的由来和应用场景
- 俄罗斯Yandex在2016年开源,使用C++编写的列式存储数据库,近几年在OLAP领域大范围应用
- 官网:https://clickhouse.com/
- 中文:https://clickhouse.com/docs/zh/
- GitHub: https://github.com/ClickHouse/ClickHouse
- 特点和应用场景
- 不依赖Hadoop 生态、安装和维护简单
- 列存储在聚合、统计等操作性能会优于行存储
- 列存储将多行记录的列连续存储在一起,一列接着一列
- 列式存储是同个数据类型,会进行数据压缩率更高,更省空间
- 列存储数据更新成本较高,一般适合读多写少的场景,适合 OLAP 分析型系统
- 拓展性强,在生产中经过实战测试,从单服务器部署到具有数千个节点的集群的线性水平可扩展性
- 支持主流的大部分SQL语法和函数、吞吐能力强,官方测试支持,支持多种存储引擎,满足多数业务场景
- 广泛应用:互联网电商、在线教育、金融等领域用,户行为数据记录和分析,搭建数据可视化平台
-
有谁在用?
- 国内:阿里、字节、腾讯 、虎牙、青云、新浪等
- 国外:优步、Ebay、Spotify、思科、等
-
支持多种部署方式
- 系统:Linux或者Mac OS 源码部署,或者 Docker部署
- 如果是Window的需要成功安装Docker或者安装Linux虚拟机
- 详细请看海量数据存储-ClickHouse专题视频
- 参加大课的一律可以5折购买相关专题课程:ClickHouse、ShardingJDBC、Kafka等
- 直接联系小滴课堂里面任何一个团伙成员都行:Anna、冰冰、欣子、老王 等
- 地址:https://detail.tmall.com/item.htm?id=670994329110
#### 第2集 Linux云服务器-ClickHouse部署安装实战
简介: Linux云服务器-ClickHouse部署安装实战
- RPM包安装
- Linux机器安装ClickHouse,版本:ClickHouse 22.1.2.2,保持一致即可
- 文档地址:https://clickhouse.com/docs/zh/getting-started/install/
- 课程资料提供安装包,上传到Linux服务器
- 直接使用rpm -ivh后面跟上所有的包安装就可以了
- 基本上不缺少其他依赖,安装之后clickhouse会自动加到systemd启动当中
#各个节点上传到新建文件夹
/usr/local/software/*
#安装
sudo rpm -ivh *.rpm
#启动
systemctl start clickhouse-server
#停止
systemctl stop clickhouse-server
#重启
systemctl restart clickhouse-server
#状态查看
sudo systemctl status clickhouse-server
#查看端口占用,如果命令不存在 yum install -y lsof
lsof -i :8123
#查看日志
tail -f /var/log/clickhouse-server/clickhouse-server.log
tail -f /var/log/clickhouse-server/clickhouse-server.err.log
#开启远程访问,取消下面的注释
vim /etc/clickhouse-server/config.xml
#编辑配置文件
<listen_host>0.0.0.0</listen_host>
#重启
systemctl restart clickhouse-server
-
网络安全组记得开放http端口是8123,tcp端口是9000, 同步端口9009
- 常规企业内网通信则不用,我们是阿里云部署,本地测试
- web可视化界面:http://ip:port/play
-
通过ClickHouse可视化工具连接
- 课程资料文件夹提供软件
- win和mac苹果都有
-
其他安装方式
- Docker
docker run -d --name xdclass_clickhouse --ulimit nofile=262144:262144 \
-p 8123:8123 -p 9000:9000 -p 9009:9009 --privileged=true \
-v /mydata/docker/clickhouse/log:/var/log/clickhouse-server \
-v /mydata/docker/clickhouse/data:/var/lib/clickhouse clickhouse/clickhouse-server:22.2.3.5
#### 第3集 海量数据存储-ClickHouse引擎知识回顾
简介: 海量数据存储-ClickHouse引擎特点知识回顾
-
MergeTree系列【王炸重点】
- CLickhouse最强大的表引擎,有多个不同的种类
- 适用于高负载任务的最通用和功能最强大的表引擎,可以快速插入数据并进行后续的后台数据处理
- 支持主键索引、数据分区、数据副本等功能特性和一些其他引擎不支持的其他功能
-
去重合并树ReplaceMergeTree
-
MergeTree的拓展,该引擎和 MergeTree 的不同之处在它会删除【排序键值】相同重复项,根据OrderBy字段
-
数据的去重只会在数据合并期间进行,合并会在后台一个不确定的时间进行
-
ReplacingMergeTree 适用于在后台清除重复的数据以节省空间,但是它不保证没有重复的数据出现
-
如何判断数据重复
- 在去除重复数据时,是以ORDER BY排序键为基准的,而不是PRIMARY KEY
- 若排序字段为两个,则两个字段都相同时才会去重
-
何时删除重复数据
- 在执行分区合并时触发删除重复数据,optimize的合并操作是在后台执行的,无法预测具体执行时间点,除非是手动执行
-
不同分区的重复数据不会被去重
- ReplacingMergeTree是以分区为单位删除重复数据的,在相同的数据分区内重复的数据才会被删除,而不同数据分区之间的重复数据依然不能被删除的
-
删除策略
- ReplacingMergeTree() 填入的参数为版本字段,重复数据就会保留版本字段值最大的。
- 如果不填写版本字段,默认保留插入顺序的最后一条数据
-
参数
-
ver
— 版本列。类型为 UInt*
, Date
或 DateTime
。可选参数。
在数据合并的时候,
ReplacingMergeTree
从所有具有相同排序键的行中选择一行留下:
- 如果 ver
列未指定,保留最后一条。
- 如果 ver
列已指定,保留 ver
值最大的版本
CREATE TABLE [IF NOT EXISTS] [db.]table_name [ON CLUSTER cluster]
(
name1 [type1] [DEFAULT|MATERIALIZED|ALIAS expr1],
name2 [type2] [DEFAULT|MATERIALIZED|ALIAS expr2],
...
) ENGINE = ReplacingMergeTree([ver])
[PARTITION BY expr]
[ORDER BY expr]
[SAMPLE BY expr]
[SETTINGS name=value, ...]
-
聚合引擎SummingMergeTree
-
该引擎继承自 MergeTree,区别在于,当合并 SummingMergeTree 表的数据片段时,ClickHouse 会把所有具有 相同OrderBy排序键 的行合并为一行,该行包含了被合并的行中具有数值类型的列的汇总值。
-
类似group by的效果,这个可以显著的减少存储空间并加快数据查询的速度
-
SummingMergeTree是根据什么对数据进行合并的
- 【ORBER BY排序键相同】作为聚合数据的条件Key的行中的列进行汇总,将这些行替换为包含汇总数据的一行记录
-
跨分区内的相同排序key的数据是否会进行合并
- 以数据分区为单位来聚合数据,同一数据分区内相同ORBER BY排序键的数据会被合并汇总,而不同分区之间的数据不会被汇总
-
如果没有指定聚合字段,会怎么聚合
- 如果没有指定聚合字段,则会用非维度列,且是数值类型字段进行聚合
-
对于非汇总字段的数据,该保留哪一条
- 如果两行数据除了【ORBER BY排序键】相同,其他的非聚合字段不相同,在聚合时会【保留最初】的那条数据,新插入的数据对应的那个字段值会被舍弃
-
参数
- columns 包含了将要被汇总的列的列名的元组。可选参数。
- 所选的【列必须是数值类型】,具有 相同OrderBy排序键 的行合并为一行
- 如果没有指定 columns
,ClickHouse 会把非维度列且是【数值类型的列】都进行汇总
CREATE TABLE [IF NOT EXISTS] [db.]table_name [ON CLUSTER cluster]
(
name1 [type1] [DEFAULT|MATERIALIZED|ALIAS expr1],
name2 [type2] [DEFAULT|MATERIALIZED|ALIAS expr2],
...
) ENGINE = SummingMergeTree([columns])
[PARTITION BY expr]
[ORDER BY expr]
[SAMPLE BY expr]
[SETTINGS name=value, ...]
#### 第4集 短链数据统计-ClickHouse引擎选择和建表语句
简介: 短链数据统计-ClickHouse引擎选择和建表语句
-
引擎选择
- ReplacingMergeTree
- SummingMergeTree
- 分析
- 需要保证幂等性,合并的时候一样要sum,不知道分区啥时候合并
- SummingMergeTree其他的非聚合字段不相同,在聚合时会【保留最初】那条数据,新插入的数据对应的那个字段值会被舍弃,如果后续统计维度更多,没保留原始数据则会产生更多问题
- 所以选择 ReplacingMergeTree
-
分区选择
- 去重合并树
- 都是基于分区内的数据进行操作去重的,根据orderBy字段进行操作去重判断
- ReplacingMergeTree() 填入的参数为版本字段,重复数据就会保留版本字段值最大的。
- 如果按照小时秒 分区,会造成分区太多,容易产生异常
- 分区使用天进行分区,查询更多也是基于天进行
-
想实现数据统计需求
- 同个指标数据,Flink有很多种方式进行处理获取的、包括建表规则等
- 好比 小滴课堂-老王-想钓鱼,可以去海边、家里的金鱼池、水库、鱼塘、溪流 等,多种方式都可以完成钓鱼
-
建表语句
CREATE TABLE default.visit_stats
(
`code` String,
`referer` String,
`is_new` UInt64,
`account_no` UInt64,
`province` String,
`city` String,
`ip` String,
`browser_name` String,
`os` String,
`device_type` String,
`pv` UInt64,
`uv` UInt64,
`start_time` DateTime,
`end_time` DateTime,
`ts` UInt64
)
ENGINE = ReplacingMergeTree(ts)
PARTITION BY toYYYYMMDD(start_time)
ORDER BY (
start_time,
end_time,
code,
province,
city,
referer,
is_new,
ip,
browser_name,
os,
device_type);
- 先建立表,后续有问题再调整
#### 第5集 DWS层数据存储-Flink整合ClickHouse写入实战
简介: DWS层数据处理-Flink整合ClickHouse写入实战
- 添加依赖
<!--引入clickhouse依赖,排查版本影响-->
<dependency>
<groupId>ru.yandex.clickhouse</groupId>
<artifactId>clickhouse-jdbc</artifactId>
<version>0.2.4</version>
<exclusions>
<exclusion>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</exclusion>
<exclusion>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
</exclusion>
</exclusions>
</dependency>
- 数据库配置
#----------clickhouse配置--------------
clickhouse.servers=jdbc:clickhouse://120.79.150.146:8123/default
- Sink输出ClickHouse
@Slf4j
public class MyClickHouseSink {
/**
* CLICK_HOUSE地址
*/
private static String CLICK_HOUSE_SERVER = null;
static{
Properties properties = new Properties();
// 使用ClassLoader加载properties配置文件生成对应的输入流
InputStream in = MyClickHouseSink.class.getClassLoader().getResourceAsStream("application.properties");
// 使用properties对象加载输入流
try {
properties.load(in);
} catch (IOException e) {
log.error("加载ClickHouse配置文件失败,{}",e);
}
//获取key对应的value值
CLICK_HOUSE_SERVER = properties.getProperty("clickhouse.servers");
}
/**
* 获取向Clickhouse中写入数据的SinkFunction
*
*
* @param sql
* @param <T>
* @return
*/
public static SinkFunction getJdbcSink(String sql) {
/**
* 8、输出Clickhouse
* `code` String,
* `referer` String,
* `is_new` UInt64,
* `account_no` UInt64,
* `province` String,
* `city` String,
* `ip` String,
* `browser_name` String,
* `os` String,
* `device_type` String,
* `pv` UInt64,
* `uv` UInt64,
* `start_time` DateTime,
* `end_time` DateTime,
* `ts` UInt64
*/
SinkFunction<ShortLinkVisitStatsDO> sinkFunction = JdbcSink.sink(
sql,
//执行写入操,设置占位符
new JdbcStatementBuilder<ShortLinkVisitStatsDO>() {
@Override
public void accept(PreparedStatement ps, ShortLinkVisitStatsDO obj) throws SQLException {
ps.setObject(1, obj.getCode());
ps.setObject(2, obj.getReferer());
ps.setObject(3, obj.getIsNew());
ps.setObject(4, obj.getAccountNo());
ps.setObject(5, obj.getProvince());
ps.setObject(6, obj.getCity());
ps.setObject(7, obj.getIp());
ps.setObject(8, obj.getBrowserName());
ps.setObject(9, obj.getOs());
ps.setObject(10, obj.getDeviceType());
ps.setObject(11, obj.getPv());
ps.setObject(12, obj.getUv());
ps.setObject(13, obj.getStartTime());
ps.setObject(14, obj.getEndTime());
ps.setObject(15, obj.getVisitTime());
}
},
//batchSize属性,执行批次大小,默认5000
new JdbcExecutionOptions.Builder().withBatchSize(10).build(),
//连接配置相关
new JdbcConnectionOptions.JdbcConnectionOptionsBuilder()
.withUrl(CLICK_HOUSE_SERVER)
.withDriverName("ru.yandex.clickhouse.ClickHouseDriver")
.withUsername("default")
.build()
);
return sinkFunction;
}
}
- SQL编写
reduceDS.addSink(
MyClickHouseSink.getJdbcSink("insert into visit_stats values(?,?,?,? ,?,?,?,? ,?,?,?,? ,?,?,?)"));
#### 第6集 短链访问-Flink整合ClickHouse链路测试
简介: 短链访问-Flink整合ClickHouse链路测试
-
全链路测试
- http://localhost:8003/026m8O3a
-
bug修复
-
用户名配置错误
-
deviceManufacturer字段为空
-
地理位置信息接口空返回
-
shortLinkWideDO.setProvince("-");
shortLinkWideDO.setCity("-");
return shortLinkWideDO;
-
resultFuture.complete(Collections.singleton(null));
-
测试数据
INSERT INTO default.visit_stats (code,referer,is_new,account_no,province,city,ip,browser_name,os,device_type,pv,uv,start_time,end_time,ts) VALUES ('026m8O3a','douyin.com',0,693100647796441088,'广东省','深圳市','14.197.9.110','Chrome','Mac OS X','COMPUTER',1,0,'2022-03-29 01:34:50.000','2022-03-29 01:35:00.000',1648618495887);
INSERT INTO default.visit_stats (code,referer,is_new,account_no,province,city,ip,browser_name,os,device_type,pv,uv,start_time,end_time,ts) VALUES ('026m8O3a','baidu.com',0,693100647796441088,'广东省','广州市','113.68.152.139','Chrome','Mac OS X','COMPUTER',1,1,'2022-03-28 01:35:00.000','2022-03-28 01:35:10.000',1648618505585);
INSERT INTO default.visit_stats (code,referer,is_new,account_no,province,city,ip,browser_name,os,device_type,pv,uv,start_time,end_time,ts) VALUES ('026m8O3a','taobao.com',0,693100647796441088,'广东省','广州市','113.68.152.139','Safari','Mac OS X','COMPUTER',1,0,'2022-03-27 01:35:00.000','2022-03-27 01:35:10.000',1648618503041);
INSERT INTO default.visit_stats (code,referer,is_new,account_no,province,city,ip,browser_name,os,device_type,pv,uv,start_time,end_time,ts) VALUES ('026m8O3a','taobao.com',0,693100647796441088,'广东省','深圳市','14.197.9.110','Chrome','Mac OS X','COMPUTER',2,0,'2022-03-30 01:35:00.000','2022-03-30 01:35:10.000',1648618508068);
INSERT INTO default.visit_stats (code,referer,is_new,account_no,province,city,ip,browser_name,os,device_type,pv,uv,start_time,end_time,ts) VALUES ('026m8O3a','taobao.com',0,693100647796441088,'广东省','深圳市','14.197.9.110','Chrome','Mac OS X','Mobile',1,1,'2022-03-30 02:35:10.000','2022-03-30 02:35:20.000',1648618510247);
INSERT INTO default.visit_stats (code,referer,is_new,account_no,province,city,ip,browser_name,os,device_type,pv,uv,start_time,end_time,ts) VALUES ('026m8O3a','douyin.com',0,693100647796441088,'广东省','广州市','113.68.152.139','Safari','Mac OS X','COMPUTER',1,0,'2022-03-30 01:36:30.000','2022-03-30 01:36:40.000',1648618596394);
INSERT INTO default.visit_stats (code,referer,is_new,account_no,province,city,ip,browser_name,os,device_type,pv,uv,start_time,end_time,ts) VALUES ('026m8O3a','douyin.com',0,693100647796441088,'广东省','广州市','113.68.152.139','Chrome','Mac OS X','COMPUTER',1,2,'2022-03-30 01:38:20.000','2022-03-30 01:38:30.000',1648618704683);
INSERT INTO default.visit_stats (code,referer,is_new,account_no,province,city,ip,browser_name,os,device_type,pv,uv,start_time,end_time,ts) VALUES ('026m8O3a','xdclass.net',0,693100647796441088,'广东省','广州市','113.68.152.139','Chrome','Mac OS X','Mobile',1,0,'2022-03-31 01:38:20.000','2022-03-31 01:38:30.000',1648618708996);
INSERT INTO default.visit_stats (code,referer,is_new,account_no,province,city,ip,browser_name,os,device_type,pv,uv,start_time,end_time,ts) VALUES ('026m8O3a','douyin.com',0,693100647796441088,'四川','成都','14.197.9.110','Chrome','Mac OS X','COMPUTER',2,0,'2022-03-30 03:38:20.000','2022-03-30 03:38:30.000',1648618701386);
INSERT INTO default.visit_stats (code,referer,is_new,account_no,province,city,ip,browser_name,os,device_type,pv,uv,start_time,end_time,ts) VALUES ('026m8O3a','xdclass.net',0,693100647796441088,'湖南','长沙','113.68.152.129','Safari','Mac OS X','COMPUTER',2,0,'2022-03-30 01:38:30.000','2022-03-30 01:38:40.000',1648618711433);
INSERT INTO default.visit_stats (code,referer,is_new,account_no,province,city,ip,browser_name,os,device_type,pv,uv,start_time,end_time,ts) VALUES ('026m8O3a','taobao.com',0,693100647796441088,'广东省','广州市','113.68.152.139','Chrome','Mac OS X','COMPUTER',1,0,'2022-03-30 04:38:30.000','2022-03-30 04:38:40.000',1648618714086);
INSERT INTO default.visit_stats (code,referer,is_new,account_no,province,city,ip,browser_name,os,device_type,pv,uv,start_time,end_time,ts) VALUES ('026m8O3a','douyin.com',0,693100647796441088,'广东省','深圳市','14.197.9.110','Chrome','Mac OS X','Mobile',2,0,'2022-03-30 01:38:30.000','2022-03-30 01:38:40.000',1648618710117);
愿景:”让编程不再难学,让技术与生活更加有趣”
### 第六十二章 数据可视化ADS层-应用数据服务开发实战
#### 第1集 数据可视化ADS层介绍和微服务整合ClickHouse项目
简介: 数据可视化ADS层介绍和微服务整合ClickHouse项目
- 数据分层
| 数据分层 | 分层描述 | 数据生成计算工具 | 存储 |
| —- | :—- | —- | —- |
| ODS | 原生数据,短链访问基本信息 | SpringBoot生成 | Kafka |
| DWD | 对 ODS 层做数据清洗和规范化,新老访客标记等 | Flink | Kafka |
| DWM | 对DWD数据进一步加工补齐数据,独立访客统计,操作系统/ip/城市,做宽表 | Flink | kafka |
| DWS | 对DWM进行处理,多流合并,分组|聚合|开窗|统计,形成主题宽表 | Flink | ClickHouse |
| ADS | 从ClickHouse中读取数据,根据需求进行筛选聚合,可视化展示 | ClickHouseSql | web可视化展示 |
-
需求
- 根据web可视化报表统计需求,从ClickHouse聚合统计
- 技术框架选择:SpringBoot + MybatisPlus +ClickHouse + Idea
-
搭建实战
- 添加项目pom依赖
```
<dependency>
<groupId>net.xdclass</groupId>
<artifactId>dcloud-common</artifactId>
<version>1.0-SNAPSHOT</version>
<exclusions>
<exclusion>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-jdbc-spring-boot-starter</artifactId>
</exclusion>
</exclusions>
</dependency>
- 添加配置文件和工具类
server.port=8002 spring.application.name=dcloud-data-service
—————服务注册和发现———————
spring.cloud.nacos.discovery.server-addr=120.79.150.146:8848 spring.cloud.nacos.discovery.username=nacos spring.cloud.nacos.discovery.password=nacos
spring.datasource.driver-class-name=ru.yandex.clickhouse.ClickHouseDriver spring.datasource.url=jdbc:clickhouse://120.79.150.146:8123/default mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl logging.level.root=INFO
- 开发启动类和包
@MapperScan(“net.xdclass.mapper”) @EnableTransactionManagement @EnableFeignClients @EnableDiscoveryClient @SpringBootApplication public class DataApplication {
public static void main(String[] args) {
SpringApplication.run(DataApplication.class, args);
}
}
<a name="edcb2e27"></a>
#### 第2集 数据可视化服务-AlibabaCloud整合ClickHouse实战
**简介: 数据可视化服务-AlibabaCloud整合ClickHouse实战**
-
基础模块开发链路走通
- model 层,新增类 VisitStatsDO
public class ShortLinkVisitStatsDO {
/**
* 窗口开始时间 Clickhouse里面会用到
*/
String startTime;
/**
* 窗口结束时间
*/
String endTime;
/**
================================================ */
/**
短链压缩码 */ private String code;
/**
- 租户id */ private Long accountNo;
/**
* 访问时间
*/
private Long visitTime;
/**
* 站点来源,只记录域名
*/
private String referer;
/**
* 1是新访客,0是老访客
*/
private Integer isNew;
/**
* 唯一标识
*/
private String udid;
//==============RegionInfoDO==================
/**
* 省份
*/
private String province;
/**
* 城市
*/
private String city;
/**
* 运营商
*/
private String isp;
/**
* 访问ip
*/
private String ip;
//==============DeviceInfoDO==================
/**
* 浏览器名称
*/
private String browserName;
/**
* 操作系统
*/
private String os;
/**
* 系统版本
*/
private String osVersion;
/**
* 设备类型
*/
private String deviceType;
/**
* 设备厂商
*/
private String deviceManufacturer;
//==============度量==================
private Long uv=0L;
private Long pv=0L;
private Long ipCount=0L;
private Long newUVCount=0L;
/**
* 时间的字符串映射,天、小时
*/
private String dateTimeStr;
}
- mapper
<?xml version=”1.0” encoding=”UTF-8”?> <!DOCTYPE mapper PUBLIC “-//mybatis.org//DTD Mapper 3.0//EN” “http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- 通用查询映射结果 -->
<resultMap id="BaseResultMap" type="net.xdclass.model.ShortLinkVisitStatsDO">
<result column="code" property="code"/>
<result column="referer" property="referer"/>
<result column="is_new" property="isNew"/>
<result column="account_no" property="accountNo"/>
<result column="province" property="province"/>
<result column="city" property="city"/>
<result column="ip" property="ip"/>
<result column="browser_name" property="browserName"/>
<result column="os" property="os"/>
<result column="device_type" property="deviceType"/>
<result column="start_time" property="startTime"/>
<result column="uv_count" property="uvCount"/>
<result column="pv_count" property="pvCount"/>
<result column="ip_count" property="ipCount"/>
<result column="new_uv_count" property="newUVCount"/>
<result column="date_time_str" property="dateTimeStr"/>
</resultMap>
<!-- 通用查询结果列 -->
<sql id="Base_Column_List">
code,referer,is_new,account_no,province,city,ip,browser_name,os,device_type,start_time
</sql>
- service
- controller
<a name="a3ea1386"></a>
#### 第3集 数据可视化-分页实时查看访问记录实战
**简介: 数据可视化-分页实时查看访问记录实战**
-
需求
-
实时查看访问记录(近实时-取决Flink开窗和Watermark大小)
-
注意
- 大部分的数据分析平台,都是有【访问时间 和 条数 限制】
- 聚合查询,时间跨度越大,需要的分区和库表查询就越多,性能就存问题
- 案例:[https://tongji.baidu.com/web/demo/trend/latest?siteId=16847648](https://tongji.baidu.com/web/demo/trend/latest?siteId=16847648)
![](img/image-20220402113952313.png#alt=image-20220402113952313)
- 编码实战
//条数限制
int total = request.getSize() * request.getPage();
if(total>1000) return JsonData.buildResult(BizCodeEnum);
//手写分页逻辑
@Override
public Map
Map<String, Object> data = new HashMap<>(16);
String code = request.getCode();
int page = request.getPage();
int size = request.getSize();
int count = visitStatsMapper.countTotal(code, accountNo);
int from = (page - 1) * size;
List<VisitStatsDO> list = visitStatsMapper.pageVisitRecord(code, accountNo, from, size);
data.put("total", count);
data.put("current_page", page);
/**计算总页数*/
int totalPage = 0;
if (count % size == 0) {
totalPage = count / size;
} else {
totalPage = count / size + 1;
}
data.put("total_page", totalPage);
data.put("data", list);
return data;
}
<!--统计总条数-->
<select id="countTotal" resultType="java.lang.Integer">
select count(1) from visit_stats where account_no=#{accountNo} and code=#{code} limit 1000
</select>
<!--分页查找-->
<select id="pageVisitRecord" resultMap="BaseResultMap">
select
<include refid="Base_Column_List"/>
from visit_stats where account_no=#{accountNo} and code=#{code}
order by ts desc limit #{from},#{size}
</select>
<a name="7b573da2"></a>
#### 第4集 数据可视化- 时间范围内地区访问分布图
**简介: 数据可视化- 时间范围内地区访问分布**
- 需求案例(效果图案例更好理解,大体字段类似)
- 访问统计分布 地理位置信息图
- UV统计是天UV,月UV是是累计,没做去重(百度统计也是未去重)
- 有些业务统计月UV会根据月去重,有些业务是不去重
- 如果需要做月去重,需要记录访问唯一标识udid
- [https://echarts.apache.org/v4/examples/zh/editor.html?c=effectScatter-bmap](https://echarts.apache.org/v4/examples/zh/editor.html?c=effectScatter-bmap)
![](img/image-20220402121836398.png#alt=image-20220402121836398)
- 页面需求
![](img/image-20220402145712542.png#alt=image-20220402145712542)
- 开发实战
//测试sql select province,city,sum(pv) pv_count,sum(uv) uv_count,count( DISTINCT ip) ip_count from visit_stats where account_no=693100647796441088 and code=’026m8O3a’ and toYYYYMMDD(start_time) BETWEEN ‘20220303’ and ‘20220430’ group by province,city order by pv_count desc
@Data public class RegionQueryRequest { private String code;
private String startTime;
private String endTime;
}
//时间先不做限制,正常需要限制
@Override
public List
return visitStatsMapper.queryRegionVisitStatsWithDay(request.getCode(), request.getStartTime(), request.getEndTime(), accountNo);
}
<select id="queryRegionVisitStatsWithDay" resultMap="BaseResultMap">
select province,city,sum(pv) pv_count,sum(uv) uv_count,count( DISTINCT ip) ip_count from visit_stats
where account_no=#{accountNo} and code=#{code} and toYYYYMMDD(start_time) BETWEEN #{startTime} and #{endTime}
group by province,city order by pv_count desc
</select>
<a name="eebe36f5"></a>
#### 第5集 数据可视化-天维度访问曲线图接口实战
**简介: 数据可视化-天维度访问曲线图接口实战**
-
需求
- **性能优化的点**:别一次性查询或者展示太多数据,根据需求查询
- 比如百度统计 UV、PV、IP数等,是点击才触发
![](img/image-20220402155759981.png#alt=image-20220402155759981)
-
页面需求
![](img/image-20220402150306746.png#alt=image-20220402150306746)
- 开发实战
select toYYYYMMDD(start_time) date_time_str,sum(if(is_new=’1’, visit_stats.uv,0)) new_uv_count, sum(visit_stats.uv) uv_count, sum(pv) pv_count, count( DISTINCT ip) ip_count from visit_stats where account_no=693100647796441088 and code=’026m8O3a’ and toYYYYMMDD(start_time) BETWEEN ‘20220303’ and ‘20220430’ group by date_time_str ORDER BY date_time_str desc
@Data public class VisitTrendQueryRequest {
private String code;
/**
* 跨天、当天24小时、分钟级别
*/
private String type;
private String startTime;
private String endTime;
}
/**
* 查询访问趋势,支持多天查询,支持查询当天小时级别
*
* @param request
* @return
*/
@Override
public List<VisitStatsDO> queryVisitTrend(VisitTrendQueryRequest request) {
Long accountNo = LoginInterceptor.threadLocal.get().getAccountNo();
String code = request.getCode();
String type = request.getType();
List<VisitStatsDO> list = null;
if (DateTimeFieldEnum.DAY.name().equalsIgnoreCase(type)) {
list = visitStatsMapper.queryVisitTrendWithMultiDay(code, accountNo, request.getStartTime(), request.getEndTime());
}
List<VisitStatsVO> visitStatsVOS = list.stream().map(obj -> beanProcess(obj)).collect(Collectors.toList());
return visitStatsVOS;
}
<a name="52837ef9"></a>
#### 第6集 数据可视化-小时-分钟维度访问曲线图接口实战
**简介: 数据可视化-小时-分钟维度访问曲线图接口实战**
- 需求
- 统计小时-分钟维度的接口
![](img/image-20220402163426926.png#alt=image-20220402163426926)
- 开发实战
//一天内的访问曲线图,小时
select toHour(start_time) date_time_str,sum(if(is_new=’1’, visit_stats.uv,0)) new_uv_count,
sum(visit_stats.uv) uv_count, sum(pv) pv_count, count( DISTINCT ip) ip_count from visit_stats
where account_no=693100647796441088 and code=’026m8O3a’ and toYYYYMMDD(start_time)=’20220403’
group by date_time_str ORDER BY date_time_str desc
//小时内的访问曲线图,每分钟
select toMinute(start_time) date_time_str,sum(if(is_new=’1’, visit_stats.uv,0)) new_uv_count,
sum(visit_stats.uv) uv_count, sum(pv) pv_count, count( DISTINCT ip) ip_count from visit_stats
where account_no=693100647796441088 and code=’026m8O3a’ and toYYYYMMDDhhmmss(start_time)
BETWEEN ‘20220403210000’ and ‘20220403215959’
group by date_time_str ORDER BY date_time_str desc
<select id="queryVisitTrendWithMinute" resultMap="BaseResultMap">
select toMinute(start_time) date_time_str,sum(if(is_new='1', visit_stats.uv,0)) new_uv_count,
sum(visit_stats.uv) uv_count, sum(pv) pv_count, count( DISTINCT ip) ip_count from visit_stats where account_no=#{accountNo} and code=#{code} and toYYYYMMDDhhmmss(start_time) BETWEEN #{startTime} and #{endTime} group by date_time_str ORDER BY date_time_str desc
<a name="82b9a270"></a>
#### 第7集 数据可视化-Top10访问来源统计开发实战
**简介:数据可视化-访问来源Top10统计开发实战**
-
需求
- 访问来源Top10统计开发
![](img/image-20220402185453230.png#alt=image-20220402185453230)
-
页面需求
![](img/image-20220402185425688.png#alt=image-20220402185425688)
- 开发实战
@Data public class BaseQueryRequest {
private String code;
private String startTime;
private String endTime;
}
/**
* 高频访问来源
*
* @param request
* @return
*/
@Override
public List<VisitStatsDO> queryFrequentReferer(BaseQueryRequest request) {
Long accountNo = LoginInterceptor.threadLocal.get().getAccountNo();
List<VisitStatsDO> list = visitStatsMapper.queryFrequentReferer(request.getCode(), accountNo, 10);
List<VisitStatsVO> visitStatsVOS = list.stream().map(obj -> beanProcess(obj)).collect(Collectors.toList());
return visitStatsDOS;
}
<!--高频referer查询 访问来源-->
<select id="queryFrequentSource" resultMap="BaseResultMap">
select referer,sum(pv) pv_count from visit_stats where account_no=#{accountNo} and code=#{code} and toYYYYMMDD(start_time) BETWEEN #{startTime} and #{endTime} group by referer order by pv_count desc limit #{size}
<a name="61875e64"></a>
#### 第8集 数据可视化-设备终端访问分布接口
**简介:数据可视化-设备终端访问分布接口**
-
需求
- 统计设备访问分布情况
![](img/image-20220402191036532.png#alt=image-20220402191036532)
-
开发实战
select os,sum(pv) pv_count from visit_stats where account_no=693100647796441088 and code=’026m8O3a’ and toYYYYMMDD(start_time) BETWEEN ‘20220303’ and ‘20220430’ group by os order by pv_count
select browser_name,sum(pv) pv_count from visit_stats where account_no=693100647796441088 and code=’026m8O3a’ and toYYYYMMDD(start_time) BETWEEN ‘20220303’ and ‘20220430’ group by browser_name order by pv_count
select device_type,sum(pv) pv_count from visit_stats where account_no=693100647796441088 and code=’026m8O3a’ and toYYYYMMDD(start_time) BETWEEN ‘20220303’ and ‘20220430’ group by device_type order by pv_count
<select id="queryDeviceInfo" resultMap="BaseResultMap">
<if test=" field=='os'">
select os,sum(pv) pv_count from visit_stats where account_no=#{accountNo} and code=#{code} and toYYYYMMDD(start_time) BETWEEN #{startTime} and #{endTime}
group by os order by pv_count
</if>
<if test=" field=='browser_name'">
select browser_name,sum(pv) pv_count from visit_stats where account_no=#{accountNo} and code=#{code} and toYYYYMMDD(start_time) BETWEEN #{startTime} and #{endTime}
group by browser_name order by pv_count
</if>
<if test=" field=='device_type'">
select device_type,sum(pv) pv_count from visit_stats where account_no=#{accountNo} and code=#{code} and toYYYYMMDD(start_time) BETWEEN #{startTime} and #{endTime}
group by device_type order by pv_count
</if>
</select>
![](https://file.xdclass.net/note/2020/javaweb/%E5%9B%BE%E7%89%87/logo.png#alt=logo) **愿景:"让编程不再难学,让技术与生活更加有趣"**
<a name="5014678c"></a>
### 第六十三章 短链平台-新版Gateway网关实战和避坑指南
<a name="9b8b79d8"></a>
#### 第1集 新版Gateway网关搭建配置实战和避坑指南
**简介: 新版Gateway网关搭建配置实战和避坑指南**
-
前言
- 很多同学想比较快看到整个链路串起来和简历编写
- 那我们优先把这部分进行完成,整个大项目的核心功能基本都完成了
- 后续代码还需要缝缝补补就行
![](img/image-20220404115310310.png#alt=image-20220404115310310)
-
添加配置
<dependencies>
<!--网关依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!-- Spring Cloud 2020 中重磅推荐的负载均衡器 Spring Cloud LoadBalancer 简称 SCL-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
<version>3.0.4</version>
</dependency>
<!--添加nacos客户端-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--<dependency>-->
<!--<groupId>com.alibaba.cloud</groupId>-->
<!--<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>-->
<!--</dependency>-->
<!--坑:spring-cloud-dependencies 2020.0.0 默认不在加载bootstrap配置文件-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
<!--限流依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<!--限流持久化到nacos-->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
<!-- 需要加 servlet包,不然配置跨域会找不到类 -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
</dependency>
</dependencies>
-
避坑一
- 配置中心bootstrap.yml不加载
- 地址 [https://docs.spring.io/spring-cloud/docs/2020.0.1/reference/htmlsingle/#config-first-bootstrap](https://docs.spring.io/spring-cloud/docs/2020.0.1/reference/htmlsingle/#config-first-bootstrap)
<!--坑:spring-cloud-dependencies 2020.0.0 默认不在加载bootstrap配置文件-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
![](img/image-20220404102005202.png#alt=image-20220404102005202)
-
闭坑二
- Spring Cloud Gateway 注册到了 Nacos 无法发现服务,报503 Service Unavailable
![](img/image-20220404104112070.png#alt=image-20220404104112070)
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
<version>3.0.4</version>
</dependency>
-
添加启动类
@SpringBootApplication @EnableDiscoveryClient public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
}
<a name="b609b3c5"></a>
#### 第2集 新版Gateway网关配置接入多个服务实战
**简介: 新版Gateway网关路由配置接入多个服务实战**
- 路由转发配置
gateway:
routes: #数组形式
- id: dcloud-link-api-service #数据服务 路由唯一标识
uri: lb://dcloud-link-service #从nocas进行转发
order: 1 #优先级,数字越小优先级越高
predicates: #断言 配置哪个路径才转发,前端访问路径统一加上XXX-server,网关判断转发对应的服务,如果是回调业务记得修改
- Path=/* #匹配一个路径,用于短链解析
- id: dcloud-link-service #数据服务 路由唯一标识
uri: lb://dcloud-link-service #从nocas进行转发
order: 2 #优先级,数字越小优先级越高
predicates: #断言 配置哪个路径才转发,前端访问路径统一加上XXX-server,网关判断转发对应的服务,如果是回调业务记得修改
- Path=/link-server/**
filters: #过滤器,请求在传递过程中通过过滤器修改
- StripPrefix=1 #去掉第一层前缀,转发给后续的路径
- id: dcloud-data-service #数据服务 路由唯一标识
uri: lb://dcloud-data-service #从nocas进行转发
order: 3 #优先级,数字越小优先级越高
predicates: #断言 配置哪个路径才转发,前端访问路径统一加上XXX-server,网关判断转发对应的服务,如果是回调业务记得修改
- Path=/data-server/**
filters: #过滤器,请求在传递过程中通过过滤器修改
- StripPrefix=1 #去掉第一层前缀,转发给后续的路径
- id: dcloud-account-service #用户服务 路由唯一标识
uri: lb://dcloud-account-service #从nocas进行转发
order: 4 #优先级,数字越小优先级越高
predicates: #断言 配置哪个路径才转发,前端访问路径统一加上XXX-server,网关判断转发对应的服务,如果是回调业务记得修改
- Path=/account-server/**
filters: #过滤器,请求在传递过程中通过过滤器修改
- StripPrefix=1 #去掉第一层前缀,转发给后续的路径
- id: dcloud-shop-service #用户服务 路由唯一标识
uri: lb://dcloud-shop-service #从nocas进行转发
order: 5 #优先级,数字越小优先级越高
predicates: #断言 配置哪个路径才转发,前端访问路径统一加上XXX-server,网关判断转发对应的服务,如果是回调业务记得修改
- Path=/shop-server/**
filters: #过滤器,请求在传递过程中通过过滤器修改
- StripPrefix=1 #去掉第一层前缀,转发给后续的路径
#开启网关拉取nacos的服务
discovery:
locator:
enabled: true
设置日志级别,ERROR/WARN/INFO/DEBUG,默认是INFO以上才显示
logging: level: root: INFO
#nacos日志问题
com.alibaba.nacos.client.config.impl: WARN
![](img/image-20220404112118807.png#alt=image-20220404112118807)
- 另外一种方式
- 把C端解析的服务不加入网关配置,单独Nginx加入转发使用
![](img/image-20220404111931067.png#alt=image-20220404111931067)
<a name="a402a15d"></a>
#### 第3集 前后端分离项目-跨域讲解和新版Gateway网关配置
**简介: 前后端分离项目-跨域讲解和新版Gateway网关配置**
-
跨域:浏览器同源策略 1995年,同源政策由 Netscape 公司引入浏览器。目前,所有浏览器都实行这个政策。 最初,它的含义是指,A网页设置的 Cookie,B网页不能打开,除非这两个网页"同源"。所谓"同源"指的是"三个相同"
协议相同 http https 域名相同 www.xdclass.net 端口相同 80 81
一句话:浏览器从一个域名的网页去请求另一个域名的资源时,域名、端口、协议任一不同,都是跨域
浏览器控制台跨域提示: No ‘Access-Control-Allow-Origin’ header is present on the requested resource. Origin ‘null’ is therefore not allowed access.
-
需要解决跨域,有多种配置
- Nginx配置
- 业务微服务配置
- 前端Node层渲染
- Gateway配置
@Configuration public class CorsConfig {
@Bean
public WebFilter corsFilter() {
return (ServerWebExchange ctx, WebFilterChain chain) -> {
ServerHttpRequest request = ctx.getRequest();
if (CorsUtils.isCorsRequest(request)) {
HttpHeaders requestHeaders = request.getHeaders();
ServerHttpResponse response = ctx.getResponse();
HttpMethod requestMethod = requestHeaders.getAccessControlRequestMethod();
HttpHeaders headers = response.getHeaders();
headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, requestHeaders.getOrigin());
headers.addAll(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS,
requestHeaders.getAccessControlRequestHeaders());
if (requestMethod != null) {
headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, requestMethod.name());
}
headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
headers.add(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, "*");
if (request.getMethod() == HttpMethod.OPTIONS) {
response.setStatusCode(HttpStatus.OK);
return Mono.empty();
}
}
return chain.filter(ctx);
};
}
}
<a name="122ba5a5"></a>
#### 第4集 网关Gateway整合微服务链路测试实战
**简介: Gateway整合微服务链路测试实战**
- 启动gateway
- 启动微服务
- 访问接口
- [http://localhost:8003/026m8O3a](http://localhost:8003/026m8O3a)
![](https://file.xdclass.net/note/2020/javaweb/%E5%9B%BE%E7%89%87/logo.png#alt=logo) **愿景:"让编程不再难学,让技术与生活更加有趣"**
<a name="288d2bb7"></a>
### 第六十四章 微服务镜像构建配置和DevOps链路讲解
<a name="58bd5c92"></a>
#### 第**1**集 微服务**Docker**打包插件介绍和Dockerfile编写
**简介:微服务Docker打包插件介绍和配置实战**
-
微服务采用容器化部署->本地推送镜像到镜像仓库->Paas容器云管理平台拉取部署
- SpringBoot打包插件配置
- 聚合工程pom添加全局变量
- 每个微服务都添加依赖(服务名记得修改)
<build>
<finalName>dcloud-account</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<!--需要加这个,不然打包镜像找不到启动文件-->
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
<configuration>
<fork>true</fork>
<addResources>true</addResources>
</configuration>
</plugin>
<plugin>
<groupId>com.spotify</groupId>
<artifactId>dockerfile-maven-plugin</artifactId>
<version>1.4.10</version>
<configuration>
<repository>${docker.image.prefix}/${project.artifactId}</repository>
<buildArgs>
<JAR_FILE>target/${project.build.finalName}.jar</JAR_FILE>
</buildArgs>
</configuration>
</plugin>
</plugins>
</build>
-
微服务Dockerfile编写
FROM adoptopenjdk/openjdk11:jre11u-nightly COPY target/dcloud-account.jar dcloud-account.jar ENTRYPOINT [“java”,”-jar”,”/dcloud-account.jar”]
<a name="f5ea33d2"></a>
#### 第2集 商用短链平台-微服务Docker镜像打包实战和注意事项
**简介:多个微服务Docker镜像打包实战**
-
多个微服务本地镜像打包
- 步骤一:最外层 mvn clean install
- 步骤二:去到子模块pom文件下
mvn install -Dmaven.test.skip=true dockerfile:build
-
**注意点**
- 本地电脑要安装Docker,才可以构建成功,Mac、Win都是,网上搜索博文进行安装
- 安装失败或者不想安装则不用,因为这个只是常规操作,接下去会有DevOps自动化构建链路
-
问题点:如果发现运行的镜像不是最新的
- 项目的路径一定不要有中文和空格
- 建议mvn clean install 构建下项目,
- 再把本地历史docker镜像删除,再重新构建打包镜像
<a name="07a2ff3b"></a>
#### 第3集 多个微服务镜像本地运行链路测试
**简介:多个微服务镜像本地运行链路测试**
- 本地运行docker镜像
docker run -d —name dcloud-account -d -p 9002:9002 镜像id
docker run -d —name dcloud-gateway -d -p 8888:8888 ef63ed47a694
docker run -d —name dcloud-account -d -p 8001:8001 377f672117aa
docker run -d —name dcloud-data -d -p 8002:8002 92c924874f35
docker run -d —name dcloud-link -d -p 8003:8003 9118421aa9c9
docker run -d —name dcloud-shop -d -p 8005:8005 db91bfbb7baa
- 查看容器运行日志
docker logs -f 容器id
- 大家测试的时候,可以先启动1~2个容器镜像(全部都启动需要很强大的配置)
- 建议都在一个机器上运行,验证好后在分机器
- 先部署网关,可以nacos查看容器ip,要同个网段内才可以访问
<a name="1c7817b3"></a>
#### 第4集 互联网公司的DevOps和CI-CD你知道多少呢
**简介:互联网公司的DevOps和CI-CD你知道多少呢**
- 软件生命周期
- 一个软件从零开始到最终交付:需求评审-》设计-》开发-》测试-》灰度-》上线-》版本迭代
- 【小滴课堂】 为例子
- 前期(功能简单)
- 技术人员:老王,一人搞定所有阶段的工作
- 中期(增加需求)
- 技术人员:老王、老帆、大钊、Anna(测试)、冰冰(运维)
- 不同人员完成不同的工作,等一个阶段所有工作完成再进入下一个阶段,瀑布模型
- 上线发布,每次都要【冰冰】进行操作,一堆shell控制
- 后期(功能多、项目多、时间少)
- 应对快速变化需求的软件开发能力,就是敏捷开发,结合CI持续集成、CD持续部署
- 运维人员:定义好规则,发布平台、监控平台、运维平台
- 开发人员:根据运维人员提供的平台和定义的规则,自主完成发布上线
![](img/image-20220406135157139.png#alt=image-20220406135157139)
-
什么是CICD
- 是指持续集成、持续发布,是一套实现软件的构建测试部署的自动化流程。
-
什么是DevOps
- 一词是由英文 Development(开发)和 Operations (运维)组合而成
- 一种思想,强调软件开发测试运维的一体化,减少各个部门之间的沟通成本从而实现软件的快速高质量的发布
- DevOps与CICD紧密相关,DevOps要实现人员一体化,须要借助CICD工具来自动化整个流程
![](img/image-20220406135137061.png#alt=image-20220406135137061)
- 通俗易懂的话
- DevOps是 开发+测试+运维 交集
- 以前是单一职责 现在一专多能 考虑配合提效,
- CI/CD 多环境-构建-打包-测试-上线-监控
- 快速迭代产品,每次代码的改动都触发校验,每刻都可进行新版本的上线
<a name="553e8177"></a>
#### 第5集 短链平台-DevOps发布链路和组件选择
**简介:短链平台-发布链路和组件选择讲解**
- DevOps链路
- 开发人员将开发代码上传到Git仓库
- 触发Jenkins的自动代码编译并构建镜像,将镜像推送到docker仓库
- 选择发布环境和机器,测试环境或生产环境再从docker仓库中拉取镜像部署
![](img/image-20220406141308049.png#alt=image-20220406141308049)
-
组件
-
容器技术:Docker
-
镜像仓库:阿里云镜像仓库
-
CI/CD平台:Jenkins / gitlab-runner + Rancher容器管理平台
- Rancher为DevOps工程师提供了直观的用户界面来管理他们的服务容器
- 用户不需要深入了解Kubernetes概念就可以开始使用Rancher
-
Git仓库:开源中国Gitee
-
云服务器:阿里云ECS
![](https://file.xdclass.net/note/2020/javaweb/%E5%9B%BE%E7%89%87/logo.png#alt=logo) **愿景:"让编程不再难学,让技术与生活更加有趣"**
<a name="464472ad"></a>
### 第六十五章 DevOps实战-微服务整合新版Jenkins打包实战
<a name="2e1b7d6c"></a>
#### 第**1**集 Linux服务器容器化部署新版Jenkins实战《上》
**简介:Linux服务器容器化部署新版Jenkins实战《上》**
-
什么是Jenkins
- 是一个开源的、提供友好操作界面的持续集成(CI)工具,主要用于持续、自动的构建/测试软件项目、监控外部任务的运行,用Java语言编写,可在Tomcat等流行的servlet容器中运行,也可独立运行
- 官方文档 [https://www.jenkins.io/](https://www.jenkins.io/)
- 小滴课堂有专门的专题视频
-
Linux云服务器部署Jenkins
- 在使用Jenkins自动化部署之前,首先安装Docker容器
-
【注意】环境说明
-
使用Docker 20.10.9版本,**不要使用1.13.1版本,该版本在jenkins使用docker命令时会说找不到配置文件!**
-
云厂商:阿里云ECS服务器-CentOS7.8
-
在安装JenKins之前要保证机子的配置要高!不建议使用虚拟机,不然卡或者缺少类库东西则麻烦
-
课程使用的是阿里云2核4G 带宽1m的服务器,推荐使用2核8G 5m,后续在下载依赖与打包会非常吃服务器的配置,**服务器部署带宽一定要高!不然推送镜像时间等待漫长**
-
【再强调下】课程不是简单项目,对这些要求比较高,想要知道大型公司里,就需要这些准备
- 可以使用阿里云按量付费方式进行购买云服务器
-
安装Docker
1.先安装yml
yum install -y yum-utils device-mapper-persistent-data lvm2
2.设置阿里云镜像
sudo yum-config-manager —add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
3.查看可安装的docker版本
yum list docker-ce —showduplicates | sort -r
4. 安装docker
yum -y install docker-ce-20.10.10-3.el7
5. 查看docker版本
docker -v
6. 启动docker
systemctl start docker
7. 查看docker 启动状态
systemctl status docker
查看端口占用命令安装
yum install -y lsof
-
安装Jenkins
- 创建Jenkins持久化目录
mkdir -p /root/docker/jenkins
- 运行部署容器
docker run -d \ -u root \ —name xdclass_jenkins \ -p 9302:8080 \ -v /root/docker/jenkins:/var/jenkins_home \ -v /var/run/docker.sock:/var/run/docker.sock \ -v /usr/bin/docker:/usr/bin/docker \ jenkins/jenkins:2.319.3-lts-jdk11
第一行:表示将该容器在后台运行 第二行:表示使用root用户来运行容器
第三行:表示给这个容器命名,后面可以通过这个名字来管理容器 第四行:表示将主机的9302端口映射到8080端口上,后面就可以通过主机ip:9302来访问Jenkins,端口是可以更改的,根据自行需要
第五行:表示将本地/root/docker/jenkins目录映射为/var/jenkins_home目录,这就是第二步中的持久化目录。 第六、七行:表示把本地/var/run/docker.sock文件映射在容器中/var/run/docker.sock文件。这一步的目的就是为了把容器中的Jenkins可以与主机Docker进行通讯。
第八行:指定使用哪一个镜像和标签
- 阿里云网络安全组开发端口 9302
<a name="d30fc13c"></a>
#### 第2集 Linux服务器容器化部署新版Jenkins实战《下》
**简介:Linux服务器容器化部署新版Jenkins实战《下》**
-
Jenkins安装和查看运行情况
-
docker ps来查看是否运行
-
在浏览器输入ip+端口号,我这里是 120.79.128.42:9302 , 即可进入到Jenkins登录页面
- 网络安全组记得开放 9302 端口
-
获取登录Jenkins的密码, 把获取的密码复制上去
cat /root/docker/jenkins/secrets/initialAdminPassword
![](img/image-20220406152002825.png#alt=image-20220406152002825)
- 选择安装推荐的插件
![](img/image-20220406152110340.png#alt=image-20220406152110340)
- 等待Jenkins把插件安装好即可,安装好后进入实例配置页面,点保存就可以来到Jenkins主界面了
![](img/image-20220406152224042.png#alt=image-20220406152224042)
- 进入到全局工具配置设置JDK
![](img/image-20220406152442655.png#alt=image-20220406152442655)
-
容器内部配置JDK
- 路径为主机Jenkins容器内部里的JAVA_HOME,也就是echo $JAVA_HOME查看JAVA_HOME路径
/opt/java/openjdk
![](img/image-20220406152540010.png#alt=image-20220406152540010)
-
插件页面下载插件
- Maven Integration、docker Pipeline、docker API 、docker、docker commons
![](img/image-20220406182103652.png#alt=image-20220406182103652)
<a name="3a8ab6ce"></a>
#### 第3集 容器化部署微服务-Docker公有+私有镜像仓库讲解
**简介:容器化部署Docker公有+私有镜像仓库讲解**
- 为啥要用镜像仓库
![](img/image-20220407001917914.png#alt=image-20220407001917914)
-
官方公共镜像仓库和私有镜像仓库(画图)
-
公共镜像仓库:
- 官方:https://hub.docker.com/,基于各个软件开发或者有软件提供商开发的
- 非官方:其他组织或者公司开发的镜像,供大家免费试用
-
私有镜像仓库:
- 用于存放公司内部的镜像,不提供给外部试用;
- 有哪些?
- Harbor:由VMWare公司开源的容器镜像仓库,Habor是在Docker Registry上进行了相应的企业级扩展
- Registry: 由docker官方提供的私有镜像仓库
- 云厂商提供:阿里云、腾讯云等
-
开通阿里云私有镜像仓库
- 登录阿里云账号访问地址:
- [https://cr.console.aliyun.com/](https://cr.console.aliyun.com/)
- 初次使用会提示开通
<a name="40a61ad7"></a>
#### 第4集 Jenkins配置Git全局访问凭证实战
**简介:Jenkins配置Git全局访问凭证实战**
- Git仓库地址
- [https://gitee.com/waitforxy/dcloud-short-link](https://gitee.com/waitforxy/dcloud-short-link)
![](img/image-20220407001917914.png#alt=image-20220407001917914)
- 配置Git访问凭证
![](img/image-20220406233749698.png#alt=image-20220406233749698)
![](img/image-20220406233831235.png#alt=image-20220406233831235)
![](img/image-20220406234009905.png#alt=image-20220406234009905)
<a name="bc1f1bb5"></a>
#### 第5集 Jenkins构建微服务脚本编写实战
**简介:Jenkins构建短链平台微服务脚本编写实战**
- 脚本编写实战
- 每个微服务建立一个Item配置
![](img/image-20220406234941121.png#alt=image-20220406234941121)
- 配置实战
![](img/image-20220406235240368.png#alt=image-20220406235240368)
![](img/image-20220406235517172.png#alt=image-20220406235517172)
echo “登录阿里云镜像” docker login —username=794666918@qq.com registry.cn-shenzhen.aliyuncs.com —password=xdclass.net168 echo “构建dcloud-common” cd dcloud-common mvn install ls -alh
![](img/image-20220406235645584.png#alt=image-20220406235645584)
ls -alh cd dcloud-account ls -alh echo “账号服务构建开始” mvn install -Dmaven.test.skip=true dockerfile:build docker tag dcloud/dcloud-account:latest registry.cn-shenzhen.aliyuncs.com/xdclass-dcloud/dcloud-account:v1.1 docker push registry.cn-shenzhen.aliyuncs.com/xdclass-dcloud/dcloud-account:v1.1 mvn clean echo “账号服务构建推送成功” echo “=======构建脚本执行完毕=====”
- 点击应用-》保存即可
- 多个微服务照此重复配置即可,修改构建的代码
<a name="c865cb04"></a>
#### 第6集 Jenkins构建打包推送微服务镜像实战
**简介:Jenkins构建打包推送微服务镜像实战**
- 进入构建环节
![](img/image-20220406235948900.png#alt=image-20220406235948900)
![](img/image-20220407000238484.png#alt=image-20220407000238484)
- 重复打包构建多个微服务推送镜像仓库即可
![](img/image-20220407001917914.png#alt=image-20220407001917914)
<a name="949e2b6e"></a>
#### 第7集 Jenkins构建-打包-推送多个微服务实战
**简介:Jenkins配置构建打包推送多个微服务实战**
- 重复上面的链路
echo “登录阿里云镜像” docker login —username=794666918@qq.com registry.cn-shenzhen.aliyuncs.com —password=xdclass.net168 echo “构建dcloud-common” cd dcloud-common mvn install ls -alh
ls -alh cd dcloud-gateway ls -alh echo “网关构建开始” mvn install -Dmaven.test.skip=true dockerfile:build docker tag dcloud/dcloud-gateway:latest registry.cn-shenzhen.aliyuncs.com/xdclass-dcloud/dcloud-gateway:v1.1 docker push registry.cn-shenzhen.aliyuncs.com/xdclass-dcloud/dcloud-gateway:v1.1 mvn clean echo “网关服务构建推送成功” echo “=======构建脚本执行完毕=====”
![](https://file.xdclass.net/note/2020/javaweb/%E5%9B%BE%E7%89%87/logo.png#alt=logo) **愿景:"让编程不再难学,让技术与生活更加有趣"**
<a name="b93f3358"></a>
### 第六十六章 Docker容器部署问题和Rancher介绍
<a name="ae0d24df"></a>
#### 第1集 微服务容器化部署的问题和容器编排介绍
**简介:微服务容器化部署的问题和容器编排介绍**
- Docker容器化部署微服务比传统方式快,一直容器化部署一直爽
在实操中发现,像 Docker 之类的容器引擎,折腾少量容器还行
但如今的云原生应用、机器学习任务或者大数据分析业务,动辄就要使用成百上千的容器
要管理这么多容器,Docker 们就力不从心了
-
问题
-
简单的几个容器可以方便管理,但稍微有规模的公司里面肯定是成千上万的容器在运行
-
怎么快速部署容器
-
怎么管理这些容器
-
怎么监控这些容器状态
-
容器之间通讯怎么管理
<br />....
![](img/image-20220407190505207.png#alt=image-20220407190505207)
-
和云基础设施的任何其他组件一样,容器需要监控和控制,否则将根本不知道服务器上运行的是什么
-
技术人员都是”懒的“
- 有需求就有改变,市场上就出现了一批【容器编排】工具,典型的是 Swarm、Mesos 和 K8S等
-
为了解决容器管理的问题,就有了容器编排管理工具
- 什么是容器编排
提供调度和管理集群的技术,提供用于基于容器应用可扩展性的基本机制,这些工具使用容器服务,并编排他们以决定容器之间如何进行交互, 包括网络, 存储, 负载均衡, DNS和安全模块等的编排调度
- 主流的容器编排工具
- Docker Swarm
- Apache Mesos
- Kubernetes
经过几年的竞争,K8S 击败 Swarm 和 Mesos,几乎成了当前容器编排的事实标准
K8S 最初是由 Google 开发的,后来捐赠给了 CNCF(云原生计算基金会,隶属 Linux 基金会)
-
K8S 既然那么猛,直接拿来用不香吗?
- 文档 [http://docs.kubernetes.org.cn/251.html](http://docs.kubernetes.org.cn/251.html)
- 如果当练习,看官方文档很快上手,上手的前提要懂很多概念
Pod、ingress、Service、Node、kubelet、kube-proxy、API Server、Scheduler、Labels、Deployment…
- 当要搭建生产环境的时候,事情就变得不一样了
- 不止搭建集群过程很复杂,后期还要面对更繁琐的 K8S 控制平面维护工作,版本升级、安全管控、数据备份
- 对于k8s集群的运维复杂度也很高
-
等你掌握一系列概念后且搭建过本地的k8s
- 开发人员的你:一个月后你会忘记80%的知识
- 公司里:能用上k8s的公司,基本不太可能让一个业务开发人员去操作到k8s的东西
- 公司谁负责:运维工程师、容器开发工程师、云平台开发工程师等
![](img/architecture.png#alt=architecture)
<a name="33c83883"></a>
#### 第2集 一线互联网大厂里面的一站式DevOps平台介绍
**简介:一线互联网大厂里面的一站式DevOps平台介绍**
-
阿里、腾讯、字节、京东、美团等都有一站式DevOps平台
- 提供从「需求->开发->测试->发布->运维->运营」端到端的协同服务和研发工具
-
大厂里面是如何操作的
- 专门的团队开发维护好自己BU的容器管理平台(k8s)
- 阿里的Aone-》对应阿里云产品 云效 [https://devops.aliyun.com/](https://devops.aliyun.com/)
![](img/p203655.png#alt=img)
![](img/image-20210310120627509.png#alt=image-20210310120627509)
-
咱们工程师都是“懒的”
- 容器编排正快速发展,从主要的基础设施公司到PAAS供应商,不仅仅是编排工具,更重要的是要构建、部署、CI/CD等一系列功能
- 很多厂商也在PaaS平台做一些工作,重点在于让开发人员不必关心底层应用部署细节,只需想好要什么即可
-
容器编排PaaS云平台
-
KubeSphere
- [https://kubesphere.com.cn/](https://kubesphere.com.cn/)
KubeSphere 是在 Kubernetes 之上构建的面向云原生应用的分布式操作系统,完全开源,支持多云与多集群管理,提供全栈的 IT 自动化运维能力,简化企业的 DevOps 工作流。它的架构可以非常方便地使第三方应用与云原生生态组件进行即插即用 (plug-and-play) 的集成。
-
Rancher
- [https://rancher.com/](https://rancher.com/)
是一个开源的企业级容器管理平台, 通过Rancher企业再也不必自己使用一系列的开源软件去从头搭建容器服务平台。
Rancher提供了在生产环境中使用的管理Docker和Kubernetes的全栈化容器部署与管理平台
-
Rancher+KubeSphere总结
都是基于K8s提供功能,是比较优秀的k8s集群管理平台,对于降低k8s集群运维复杂度,降低运维成本,使开发人员能尽快上手部署微服务
<a name="cc32c9f1"></a>
#### 第3集 容器编排管理平台Rancher介绍和概念讲解
**简介:容器编排管理平台Rancher介绍和概念讲解**
- 什么是Rancher
- 地址:[https://rancher.com/](https://rancher.com/)
- 是一个开源的企业级PaaS容器管理平台。通过Rancher,企业再也不必自己使用一系列的开源软件去从头搭建容器服务平台
- Rancher提供了在生产环境中使用的管理Docker和Kubernetes的全栈化容器部署与管理平台
- 通信模型介绍
- Client-Server模式
- 每个Linux主机是Client端,需要连接到Rancher的Server端
![](img/image-20220407193254021.png#alt=image-20220407193254021)
-
特性
- 基础设施编排
- 可以使用任何公有云或者私有云的Linux主机资源
- Linux主机可以是虚拟机,也可以是物理机
- Rancher仅需要主机有CPU,内存,本地磁盘和网络资源,从Rancher的角度来说,一台云厂商提供的云主机和一台自己的物理机是一样的
- 容器编排与调度
- 包含了当前全部主流的编排调度引擎,例如Docker Swarm, Kubernetes, 和Mesos
- 同一个用户可以创建Swarm或者Kubernetes集群。并且可以使用原生的Swarm或者Kubernetes工具管理应用
- 除了Swarm,Kubernetes和Mesos之外,Rancher还支持自己的Cattle容器编排调度引擎
- Cattle被广泛用于编排Rancher自己的基础设施服务以及用于Swarm集群,Kubernetes集群和Mesos集群的配置,管理与升级
- 中文文档
- [https://docs.rancher.cn/](https://docs.rancher.cn/)
<a name="a4439221"></a>
#### 第4集 云服务器Docker容器化部署Rancher2.x实战
**简介:云服务器Docker容器化部署Rancher2.x实战**
-
注意事项
-
基于Docker镜像安装Rancher,阿里云 Linux CentOS 7.8 + Docker-20.10.10
- 2核8g内存+5M带宽
-
选择Rancher机器可以把【内存+带宽】弄大点,因为Rancher部署好后需要下载很多的镜像,不要因为内存问题导致部署没成功
-
安装Docker
1.先安装yml
yum install -y yum-utils device-mapper-persistent-data lvm2
2.设置阿里云镜像
sudo yum-config-manager —add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
3. 安装docker
yum -y install docker-ce-20.10.10-3.el7
4. 查看docker版本
docker -v
5. 启动docker
systemctl start docker
6. 查看docker 启动状态
systemctl status docker
-
安装Rancher
- 创建Rancher挂载目录
mkdir -p /data/rancher_home/rancher mkdir -p /data/rancher_home/auditlog
- 部署Rancher
docker run -d —privileged —restart=unless-stopped -p 80:80 -p 443:443 \ -v /data/rancher_home/rancher:/var/lib/rancher \ -v /data/rancher_home/auditlog:/var/log/auditlog \ —name xdclass_rancher1 rancher/rancher:v2.5.7
-
登录Rancher
- 启动成功rancher后, 可以打开浏览器输入IP地址来进入Rancher
- 登录地址为:[http://+IP](http://+IP) ,如:[http://47.106.69.xxx](http://47.106.69.xxx)
- 配置账号密码,填写完账号密码后直接Continue即可。
![](img/image-20220408130200793.png#alt=image-20220408130200793)
-
保存配置
- 点击Continue后进入到Server URL页面,这里显示的是IP(公网ip)地址,不用管直接点击SaveURL
![](img/1635650007160-88494d1c-f9d6-4956-b7c8-6483cb6a7aea.png#alt=image.png)
- 设置Rancher语言,在主页面的右下角点击切换一下即可
![](img/1634705251945-ce22755c-f35e-4eeb-8c56-e197c7904428.png#alt=image.png)
![](https://file.xdclass.net/note/2020/javaweb/%E5%9B%BE%E7%89%87/logo.png#alt=logo) **愿景:"让编程不再难学,让技术与生活更加有趣"**
<a name="9f880ceb"></a>
### 第六十七章 Rancher2.X配置镜像和Client节点添加实战
<a name="2017623e"></a>
#### 第1集 Rancher2.X配置Docker镜像加速实战
**简介:Rancher2.X配置Docker镜像加速实战**
-
Rancher平台知识点很多
- 根据课程了解常用的即可
- 更多内容-看小滴课堂-容器编排专题课程,抓紧更新中了
-
配置阿里云镜像加速地址
- 为啥要配置?Rancher依赖组件多,不配置下载镜像慢容易出问题
- 阿里云入口地址 [https://cr.console.aliyun.com/cn-shenzhen/instances/mirrors](https://cr.console.aliyun.com/cn-shenzhen/instances/mirrors)
sudo mkdir -p /etc/docker sudo tee /etc/docker/daemon.json <<-‘EOF’ { “registry-mirrors”: [“https://pkuk89br.mirror.aliyuncs.com“] } EOF sudo systemctl daemon-reload sudo systemctl restart docker
- 镜像加速器获取方式
![](img/1634888881300-bb3fd1e5-d4cf-452a-b876-767ce5e5c8d0.png#alt=image.png)
<a name="0be52d8f"></a>
#### 第2集 Rancher2.X添加集群配置RancherAgent节点
**简介:Rancher2.X添加集群配置RancherAgent节点**
-
创建项目集群
<br />![](img/1634888058376-f46f9aee-d21f-4936-a1ba-2b5b4180e2de.png#alt=image.png)
- 进入到选择集群的类型,这里选择自定义就可以
![](img/1634888105856-93a0c92a-fcac-4faf-abb6-a216817f2da1.png#alt=image.png)
- 进入到添加集群-Custom页面
![](img/1634888462299-0cbf688c-b622-4b89-9c10-56087ab7deec.png#alt=image.png)
- 下拉配置集群的选项
![](img/1634888407002-f30f5aaf-7bcb-43df-bbaf-46b6ac87f72a.png#alt=image.png)
- 我们点击下一步
![](img/1634888614160-25786421-1e90-48f6-9825-422a8e11846b.png#alt=image.png)
-
添加Linux主机节点
- 需要安装Docker
1.先安装yml
yum install -y yum-utils device-mapper-persistent-data lvm2
2.设置阿里云镜像
sudo yum-config-manager —add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
3. 安装docker
yum -y install docker-ce-20.10.10-3.el7
4. 查看docker版本
docker -v
5. 启动docker
systemctl start docker
6. 查看docker 启动状态
systemctl status docker
- 终端中运行复制下来的命令按Enter键等待下载好相应的镜像即可
- 我们就把主机给添加到所创建的集群中
![](img/1634889102899-f0691348-3bd2-4bd3-863a-beb02cbb40cd.png#alt=image.png)
- 回到Rancher的主界面
![](img/1634889363520-52a35fd2-01eb-4b7b-adaf-bc01248814af.png#alt=image.png)
- 点击自己所创建的集群,可以进去看到所监控集群的信息
![](img/1634889444535-ecfdc3eb-c875-438d-b41d-6790dad9c72c.png#alt=image.png)
<a name="890c3e01"></a>
#### 第3集 Rancher2.X配置私有镜像仓库实战
**简介:Rancher2.X配置私有镜像仓库实战**
![](img/image-20220407001917914.png#alt=image-20220407001917914)
-
私有镜像仓库配置
-
在顶部的导航栏找到-资源->密文
![](img/1634890483809-08e7d2e0-cde6-476a-9c02-a8d2fabca2e3.png#alt=image.png)
- 最右边有添加凭证按钮,点击进去
![](img/1634890519626-eb0b0248-d2aa-44dd-b561-95945e20bca7.png#alt=image.png)
-
添加镜像库凭证
<br />![](img/1634890677666-276a7a4f-5c6a-4ca9-9bba-862436c189cc.png#alt=img)
-
成功添加镜像库凭证
<br />![](img/1634890729743-dbde517b-7eec-4766-a6f7-50736e6b12dc.png#alt=img)
-
镜像地址
服务:registry.cn-shenzhen.aliyuncs.com/xdclass-dcloud/dcloud-account:v1.1
registry.cn-shenzhen.aliyuncs.com
794666918@qq.com xdclass.net168
![](https://file.xdclass.net/note/2020/javaweb/%E5%9B%BE%E7%89%87/logo.png#alt=logo) **愿景:"让编程不再难学,让技术与生活更加有趣"**
<a name="70aa1e7c"></a>
### 第六十八章 Rancher2.X部署短链平台微服务实战
<a name="0b5ef187"></a>
#### 第1集 Rancher2.X部署短链平台-Gateway实战
**简介:Rancher2.X部署短链平台-Gateway实战**
- 部署顺序
- dcloud-gateway
- dcloud-account
- dcloud-shop
- dcloud-data
- dcloud-link
- 注意
- 需要的Linux服务器资源比较多,两台节点部署
- 对应的Nacos、Mysql、Redis、RabbitMQ、Kafka等先不改动,后续再转移部署
![](img/image-20220410101616962.png#alt=image-20220410101616962)
-
部署网关
- 新增工作负载
![](img/image-20220409194458854.png#alt=image-20220409194458854)
-
编辑集群-端口可用范围
<br />![](img/image-20220409192326392.png#alt=image-20220409192326392)
<a name="5fb8f76c"></a>
#### 第2集 Rancher2.X部署account-shop微服务实战
**简介:Rancher2.X部署account-shop微服务实战**
-
部署顺序
- dcloud-gatway
- dcloud-account
- dcloud-shop
- dcloud-data
- dcloud-link
-
部署实战
- 注意:通过网关内部访问,不用配置端口映射
![image-20220410102548158](../../../../Library/Application Support/typora-user-images/image-20220410102548158.png)
- Nacos控制台查看和接口测试
- 网关所在机器,网络安全组开放 8888
<a name="aa579fc2"></a>
#### 第3集 Rancher2.X部署data-link微服务实战
**简介:Rancher2.X部署data-link微服务实战**
-
部署顺序
- dcloud-gateway
- dcloud-account
- dcloud-shop
- dcloud-data
- dcloud-link
-
部署实战
<br />![](img/image-20220410102631693.png#alt=image-20220410102631693)
-
Nacos控制台查看和接口测试
<a name="62fe1e6a"></a>
#### 第4集 Rancher2.X部署中间件-Mysql8.0
**简介:Rancher2.X部署中间件-Mysql8.0**
-
注意
- 常规互联网企业里面的中间件,运维工程师负责搭建
- 部分组件可以选择直接部署:容器、源码编译,并非一定要某个方式
- 比如某些组件需要特定的版本,但是平台存在不兼容或者配置方式不支持等
-
部署Mysql
-
需要【两个节点】
- 一个是Nacos和Xxl-Job使用
- 名称 midware-mysql
- 端口 :3306:3306
- 一个是业务微服务使用,如果有需要可以每个微服务一个节点
- 名称:service-mysql
- 端口 :3307:3306
-
配置
镜像:mysql:8.0 环境变量: MYSQL_ROOT_PASSWORD=xdclass.net168
路径映射 /home/data/mysql/data /var/lib/mysql:rw /etc/localtime /etc/localtime:ro
在创建 Docker 容器时,加上 “-v /etc/localtime:/etc/localtime:ro” 参数 让容器使用宿主机的时间,容器时间与宿主机时间同步,:ro 指定该 volume 为只读
<br />![](img/image-20220410145813077.png#alt=image-20220410145813077)
<br />![](img/image-20220410145541116.png#alt=image-20220410145541116)
<br />![](img/image-20220410145703716.png#alt=image-20220410145703716)
<a name="2dd5915c"></a>
#### 第5集 Mysql新建Nacos和XXl-Job库表实战
**简介:Mysql新建Nacos和XXl-Job库表实战**
-
导入nacos持久化库表
-
导入Xxl-Job持久化库表
-
注意
- sql在本章本集的资料里面
![](https://file.xdclass.net/note/2020/javaweb/%E5%9B%BE%E7%89%87/logo.png#alt=logo) **愿景:"让编程不再难学,让技术与生活更加有趣"**
<a name="36dc05a7"></a>
### 第六十九章 Rancher2.X部署短链平台业务中间件
<a name="0f83204c"></a>
#### 第1集 Ranche2.X部署Nacos和调整JVM内存实战
**简介:Ranche2.X部署Nacos和调整JVM内存实战**
- 部署Nacos
镜像:nacos/nacos-server:2.0.2 端口:8848:8848
环境变量: JVM_XMN=128m JVM_XMS=128m JVM_XMX=128m MODE=standalone MYSQL_SERVICE_DB_NAME=nacos_config MYSQL_SERVICE_DB_PARAM=characterEncoding=utf8&connectTimeout=10000&socketTimeout=30000&autoReconnect=true&useSSL=false MYSQL_SERVICE_HOST=112.74.107.230 MYSQL_SERVICE_PASSWORD=xdclass.net168 MYSQL_SERVICE_PORT=3306 MYSQL_SERVICE_USER=root NACOS_AUTH_ENABLE=true SPRING_DATASOURCE_PLATFORM=mysql
-Xms:初始堆大小 -Xmx:最大堆大小 -Xmn:新生代大小
![](img/image-20220411111057261.png#alt=image-20220411111057261)
![](img/image-20220411111022506.png#alt=image-20220411111022506)
![](img/image-20220411110959392.png#alt=image-20220411110959392)
- 网络安全组开放端口
- 本地代码调整
<a name="35a0f887"></a>
#### 第2集 Ranche2.X部署XXL-Job和Redis6实战
**简介:Ranche2.X部署XXL-Job和Redis6实战**
-
注意
- 很多中间件并非要Rancher部署,常规源码或者Docker部署就行
- 课程方便管理为主则用Rancher部署
- 实际公司里面,运维负责搭建安装部署,开发人员使用即可
-
部署XXL-Job
- [http://ip:8080/xxl-job-admin](http://ip:8080/xxl-job-admin)
- admin / 123456
镜像:xuxueli/xxl-job-admin:2.2.0 端口:8080:8080 环境变量: PARAMS=—spring.datasource.url=jdbc:mysql://112.74.107.230:3306/xxl_job?Unicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai \ —spring.datasource.username=root \ —spring.datasource.password=xdclass.net168 \ —xxl.job.accessToken=xdclass.net
![](img/image-20220411121813988.png#alt=image-20220411121813988)
-
部署Redis
- 在rancher中设置的,redis密码不是环境变量,而是命令入口配置
- 网络安全组记得开放端口
镜像:redis:6.2.4 端口:6379:6379
数据卷: /mydata/redis/data /data
入口命令 redis-server —requirepass xdclass.net
![](img/image-20220411114002767.png#alt=image-20220411114002767)
![](img/image-20220411113937292.png#alt=image-20220411113937292)
![](img/image-20220411112829042.png#alt=image-20220411112829042)
- 网络安全组开放端口
- 本地代码调整
<a name="acdfaedf"></a>
#### 第3集 Ranche2.X部署RabbitMQ和ClickHouse实战
**简介:Ranche2.X部署RabbitMQ和ClickHouse实战**
- 部署RabbitMQ
镜像:rabbitmq:3.8.15-management 端口:15672:15672 5672:5672
环境变量: RABBITMQ_DEFAULT_PASS=password RABBITMQ_DEFAULT_USER=admin
![](img/1635836167375-2a9815b2-b686-4b56-a78a-4eba17237d3f.png#alt=image.png)
- 部署ClickHouse
镜像:clickhouse/clickhouse-server:22.1.4.30 端口:8123:8123 9000:9000 9009:9009
nofile=262144 privileged=true ulimit=262144
1.log卷 clickhouse-log /mydata/docker/clickhouse/log /var/log/clickhouse-server
2.data卷 clickhouse-data /mydata/docker/clickhouse/data /var/lib/clickhouse
![](img/1635755607015-618bb592-acbf-4306-8ef9-5c5c4b819787.png#alt=image.png)
![](img/1635755670042-117317bf-ed99-40e0-84e9-e0b671c54584.png#alt=image.png)
![](img/1635755717559-65417abe-d1d0-49af-a4e7-99eb5bac7838.png#alt=image.png)
- dbeaver连接测试
- 默认http端口是8123,tcp端口是9000, 同步端口9009
CREATE TABLE default.visit_stats
(
code
String,
referer
String,
is_new
UInt64,
account_no
UInt64,
province
String,
city
String,
ip
String,
browser_name
String,
os
String,
device_type
String,
pv
UInt64,
uv
UInt64,
start_time
DateTime,
end_time
DateTime,
ts
UInt64
)
ENGINE = ReplacingMergeTree(ts)
PARTITION BY toYYYYMMDD(start_time)
ORDER BY (
start_time,
end_time,
code,
province,
city,
referer,
is_new,
ip,
browser_name,
os,
device_type);
<a name="595724f6"></a>
#### 第4集 Ranche2.X部署Zookeeper和Kafka
**简介:Ranche2.X部署Zookeeper和Kafka**
- 部署Zookeeper
镜像:wurstmeister/zookeeper 端口:2181:2181
![](img/1635836491612-705c8361-46b3-412a-a7d3-a5fef8967ac5.png#alt=image.png)
- 部署Kafka
镜像:wurstmeister/kafka:2.13-2.7.0 端口:9092:9092
环境变量: KAFKA_ADVERTISED_LISTENERS=PLAINTEXT://120.25.217.15:9092 KAFKA_BROKER_ID=0 KAFKA_LISTENERS=PLAINTEXT://0.0.0.0:9092 KAFKA_ZOOKEEPER_CONNECT=172.31.101.8:2181 KAFKA_PORT=9092
![](img/1635905952850-401f12ed-4825-437f-b4ec-e52ac6081f0a.png#alt=image.png)
![](https://file.xdclass.net/note/2020/javaweb/%E5%9B%BE%E7%89%87/logo.png#alt=logo) **愿景:"让编程不再难学,让技术与生活更加有趣"**
<a name="567cb406"></a>
### 第七十章 新一代分布式链路追踪Skywalking实战
<a name="717453cd"></a>
#### 第1集 新一代分布式链路追踪Skywalking介绍
**简介:新一代分布式链路追踪Skywalking介绍**
-
抛两个常见的问题
- 微服务调用链路出现了问题怎么快速排查?
- 微服务调用链路耗时长怎么定位是哪个服务
-
skywalking是什么
- skywalkings是一款国产的开源框架,在2015年开源使用,在2017年的时候加入了Apache孵化器
- skywalking是分布式应用程序的性能监控工具,专门是为了微服务(spring cloud)、云原生架构与容器架构(docker/k8s)而设计的
- 是一款APM工具,它具有分布式追踪、性能指标分析、应用和服务依赖分析等功能
- 官网:[http://skywalking.apache.org/](http://skywalking.apache.org/)
- 下载:[http://skywalking.apache.org/downloads/](http://skywalking.apache.org/downloads/)
- Github:[https://github.com/apache/skywalking](https://github.com/apache/skywalking)
- 官方文档:[https://skywalking.apache.org/docs/main/v8.5.0/readme/](https://skywalking.apache.org/docs/main/v8.5.0/readme/)
- 中文文档:[https://skyapm.github.io/document-cn-translation-of-skywalking/](https://skyapm.github.io/document-cn-translation-of-skywalking/)
-
对比区别
- Zipkin是由Twitter开源的链路分析分析工具,在springcloud sleuth得到了广泛的使用,具有轻量,部署简单的特点
- Pinpoint是由韩国人开发的链路追踪应用监控分析工具,基于字节码方式注入。具有支持多种插件,UI功能强大,接入端没有代码侵入
- Skywalking是由国人开发的链路追踪应用监控分析工具,基于字节码方式注入。具有支持多种插件,UI功能强大,接入端没有代码侵入,现已加入Apache孵化器
- CAT是大众点评开源的链路追踪分析工具,具有对应用监控的分析、日志的采集、监控报警一系列的监控平台
| 项目 | Cat | Zipkin | Skywalking |
| :---: | :---: | :---: | :---: |
| 链路追踪可视化 | 是 | 是 | 是 |
| 聚合报表 | 优 | 良好 | 中等 |
| 服务的依赖图 | 良好 | 良好 | 优 |
| 埋点方式 | 入侵式 | 入侵式 | 非入侵式,字节码增强 |
| VM监控指标 | 有 | 没有 | 有 |
| 存储机制 | mysql、本地文件 | 内存、es、mysql、等 | H2、es、mysql、TIDB等 |
| 支持语言 | java/.net | 丰富 | 丰富 |
| 社区支持 | 国内 | 国外主流 | apache支持 |
| 知名使用者 | 美团、携程 | 京东、阿里定制不开源 | 华为、小米 |
| APM | 是 | 否 | 是 |
| 开发基础 | eBay cal | Google Dapper | Google Dapper |
| 支持webflux | 不支持 | 支持 | 支持 |
| github下载量 | 12.3k | 12.2k | 11.8k |
-
更多细节内容
- 看小滴课堂《新一代分布式链路追踪Skywalking实战》专题课程
<a name="7161a7b1"></a>
#### 第2集 Apache Skywalking整体架构组件介绍
**简介:Apache Skywalking整体架构组件介绍**
- skywalking整体架构
- 可以分为:上、下、左、右四个部分
- 上部分(skywalking-agent):这一部分负责从应用程序中收集链路信息,然后把链路信息发送给skywalking OAP处理器
- 下部分(skywalking OAp):负责接收从skywalking-agent发送过来的Tracing数据信息,然后把数据信息给Analysis Core进行分析,把分析到的数据存储到外部的存储器当中,最后面把数据信息给Query Core提供查询数据的功能
- 左部分(Skywalking UI):负责给用户查看链路等信息
![](../../../skywalking%E8%AF%BE%E7%A8%8B%E6%96%87%E6%A1%A3/./img/image-20211124182324499.png#alt=image-20211124182835496)
- 部署组件介绍
- ElasticSearch7.X
- Skywalking-OAP-Server
- Skywalking UI
- Skywalking-Agent(项目引入)
<a name="6f075a28"></a>
#### 第3集 Rancher2.X部署ElasticSearch7.x
**简介:Rancher2.X部署ElasticSearch7.x**
- Elasticsearch
- Elasticsearch是⼀个开源,是⼀个基于Apache Lucene库构建的Restful搜索引擎. Elasticsearch是在Solr之后⼏年推出的。
- 它提供了⼀个分布式,多租户能⼒的全⽂搜索引擎,具有HTTP Web界⾯(REST)和API
- Elasticsearch的官⽅客户端库提供Java,Groovy,PHP,Ruby,Perl,Python,.NET和Javascript。
- 官网:[https://www.elastic.co/cn/elasticsearch/](https://www.elastic.co/cn/elasticsearch/)
- 应⽤场景
- 维基百科、Stack Overflow、GitHub
- 电商⽹站、⽇志数据分析、商品价格监控⽹站、BI系统、站内搜索
- ES专题课程:可以看小滴课堂《玩转ElasticSearch全链路实战》
-
部署
- 在rancher主界面->资源->映射配置
elasticsearch.yml
cluster.name: “docker-cluster” network.host: 0.0.0.0 xpack.security.enabled: true xpack.license.self_generated.type: basic xpack.security.transport.ssl.enabled: true
这条配置表示开启xpack认证机制
xpack.security.enabled: true
![](img/1635754747394-63e201b3-dc1a-477c-a0ef-0ff23c3f8766.png#alt=image.png)
-
导入yaml(本章本集资料)
<br />![](img/1649742633606-7f47ecb6-7db2-4c8f-9c89-a6cee9f0225a.png#alt=image.png)
-
配置主机调度
- 并到主机下的mydata给es文件夹添加777权限
chmod 777 -R /mydata/es
![](img/1649742667414-95342f65-b2fa-4a42-af68-32ebbef8a097.png#alt=image.png)
-
配置账号密码
- 命令行进入
bin/elasticsearch-setup-passwords interactive
-
按回车输入y,回车一直输入密码elastic即可
<br />![](img/1635755339427-210f00c5-3c98-4c34-941d-7f33e6c819cc.png#alt=img)
-
其他方式:docker容器化部署
创建目录
mkdir -p /mydata/es/config mkdir -p /mydata/es/data echo “http.host: 0.0.0.0” >> /mydata/es/config/elasticsearch.yml
宿主机权限不够
chmod 777 -R /mydata/es
启动运行
docker run -d —name xdclass_es7 -p 9200:9200 -p 9300:9300 \ -e “discovery.type=single-node” -e ES_JAVA_OPTS=”-Xms128m -Xmx128m” \ -v /mydata/es/config/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml \ -v /mydata/es/data:/usr/share/elasticsearch/data \ -v /mydata/es/plugins:/usr/share/elasticsearch/plugins elasticsearch:7.6.2
参数说明 -e “discovery.type=single-node” 设置为单节点 -e ES_JAVA_OPTS=”-Xms128m -Xmx128m” 设置ES的初始内存和最大内存,否则过大启动不了ES
-
问题 访问不了
- 宿主机权限不够 chmod 777 -R /mydata/es
- 网络安全组没开端口或者防火墙没开端口
- http是9200,tcp是9300端口
-
访问验证
http://120.24.7.58:9200/_cat/nodes?v=true&pretty
<a name="a5dc0910"></a>
#### 第4集 Rancher2.X部署Skywalking-OAP-Server+UI
**简介:Rancher2.X部署Skywalking-OAP-Server+UI**
- 部署Skywalking-OAP-Server
镜像:apache/skywalking-oap-server:8.5.0-es7 端口:12800:12800 11800:11800
环境变量: TZ=Asia/Shanghai SW_ES_PASSWORD=elastic SW_ES_USER=elastic SW_STORAGE=elasticsearch7 SW_STORAGE_ES_CLUSTER_NODES=120.76.231.139:9200
![](img/1635835682010-c387706d-7ff8-4bd3-aad7-304de9f584a1.png#alt=image.png)
- 部署Skywalking-UI
镜像:apache/skywalking-ui:8.5.0 端口:8080:8000 (左边是容器端口,右边是宿主机端口)
环境变量(oap是上面定义的容器服务名称) SW_OAP_ADDRESS=oap:12800 TZ=Asia/Shanghai
![](img/1635835951984-845ae850-3709-4087-8e5c-7a778920e682.png#alt=image.png)
![](img/1635835985849-34dd90c1-5ba6-43ec-af56-cb922219d0af.png#alt=image.png)
- 访问 [http://IP](http://IP):+8000/
- 查看ElasticSearch全部索引:[http://120.24.7.58:9200/_cat/indices?v](http://120.24.7.58:9200/_cat/indices?v)
![](https://file.xdclass.net/note/2020/javaweb/%E5%9B%BE%E7%89%87/logo.png#alt=logo) **愿景:"让编程不再难学,让技术与生活更加有趣"**
<a name="0fc41fc3"></a>
### 第七十一章 短链平台整合SkyWalking链路追踪实战
<a name="0373d889"></a>
#### 第1集 AlibabaCloud微服务整合SkyWalkingAgent实战
**简介:AlibabaCloud微服务整合SkyWalkingAgent实战**
-
skywalking-agent的安装和使用
-
下载skywalking
- 地址:[https://skywalking.apache.org/downloads/](https://skywalking.apache.org/downloads/)
![](img/image-20220413174218982.png#alt=image-20220413174218982)
-
课程资料包已经下载好
- 本章本集资料那边,解压即可
-
skywalking-agen目录文件的介绍
- logs:skywalking agent的相关运行日志
- bootstrap-plugins:插件包
- optional-plugins:插件包
- optional-reporter-plugins:插件包
- activations:插件包
- skywalking-agent.jar:agent代理的jar包(主要是这个!)
![](img/image-20220413174316999.png#alt=image-20220413174316999)
-
项目中添加agent
- 复制skywalking-apm的agent文件到项目当中
![](img/image-20220413174801966.png#alt=image-20220413174801966)
-
注意坑来啦
- .gitignore 默认不提交*.jar 相关文件,注意.gitignore是隐藏文件,配置电脑显示隐藏文件
- 删除掉 *.jar ,才可以成功把jar提交上去
![](img/image-20220413184543133.png#alt=image-20220413184543133)
<a name="b1da9eb2"></a>
#### 第2集 微服务打包SkyWalkingAgent镜像部署发布实战
**简介:微服务打包SkyWalkingAgent镜像部署发布实战**
- 微服务Dockerfile配置
FROM adoptopenjdk/openjdk11:jre11u-nightly
COPY target/dcloud-account.jar dcloud-account.jar COPY agent /usr/local/agent
ENV SW_AGENT_NAME “dcloud-account”
ENTRYPOINT [“java”,”-javaagent:/usr/local/agent/skywalking-agent.jar”,”-jar”,”/dcloud-account.jar”]
-
提交代码->jenkins构建->Rancher发布升级
-
Rancher部署微服务新增环境变量
- SW_AGENT_COLLECTOR_BACKEND_SERVICES = 112.74.107.230:11800
- 注意:同个Rancher命名空间下,ip可以使用【容器服务名称】进行替代
-
多个微服务都改下配置Dockerfile,并提交代码->jenkins构建->Rancher发布升级
-
埋了一个坑,大家尝试自己解决,解决不了的话看下集
![](img/image-20220413185359215.png#alt=image-20220413185359215)
<a name="3c823b66"></a>
#### 第3集 微服务访问-SkyWalkingUI数据查看和避坑
**简介:微服务访问-SkyWalkingUI数据查看和避坑**
-
链路测试
-
访问Account微服务,有服务出现,但是没数据
-
注意:网络安全组开发相关链路端口
-
Rancher环境变量
SW_AGENT_COLLECTOR_BACKEND_SERVICES=112.74.107.230:11800
-
分析链路
- 网关->业务微服务->skywalking oap-> elasticsearch
-
避坑解决问题
- ElasticSearch内存不够
which is larger than the limit of…
- Rancher升级ES,调整堆内存 为1g,有条件可以更多,8g、16g都行
-
访问微服务接口-》查看SkywalingUI数据
<a name="82bae0d0"></a>
#### 第4集 DevOps链路优化-镜像构建推送加速-阿里云VPC专有网络
**简介:DevOps链路优化-镜像构建发布加速-阿里云VPC专有网络**
![](img/image-20220414165743647.png#alt=image-20220414165743647)
- DevOps链路
![](img/image-20220407001917914.png#alt=image-20220407001917914)
- 阿里云网络
- 云服务器ECS的网络类型分为经典网络和专有网络VPC两种
- 专有网络
- (Virtual Private Cloud,简称VPC)一个隔离的网络环境,专有网络之间逻辑上彻底隔离,可以自定义专有网络的拓扑和IP地址,安全性相对是比较高的,推送阿里云镜像可以加速
- 经典网络(已经下架了)
![](https://file.xdclass.net/note/2020/javaweb/%E5%9B%BE%E7%89%87/logo.png#alt=logo) **愿景:"让编程不再难学,让技术与生活更加有趣"**
<a name="0e53192c"></a>
### 第七十二章 短链平台整合Nacos配置中心和域名配置
<a name="d860dc36"></a>
#### 第1集 账号服务整合Nacos配置中心开发和配置
**简介:账号整合Nacos配置中心开发和配置**
- common项目引入配中心(已经加入)
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
- 常规按照上面的配置即可成功使用配置中心,但是新版的话不行,需要加入下面配置
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
-
配置文件优先级讲解
- 不能使用原先的application.properties, 需要使用 bootstrap.properties作为配置文件
- 配置读取优先级 bootstrap.properties > application.properties
-
配置实操
- 服务迁移配置 增加bootstrap.properties
spring.application.name=dcloud-account-service spring.cloud.nacos.config.server-addr=120.25.217.15:8848 spring.cloud.nacos.config.file-extension=properties spring.cloud.nacos.config.enabled=true spring.cloud.nacos.config.username=nacos spring.cloud.nacos.config.password=nacos spring.cloud.nacos.config.namespace=public spring.cloud.nacos.config.config-long-poll-timeout=600000 spring.profiles.active=dev
-
dataId组成,在 Nacos Spring Cloud 中,dataId 的完整 格式如下
${prefix}-${spring.profiles.active}.${file- extension}
prefix 默认为 spring.application.name 的值 spring.profiles.active 即为当前环境对应的 profile 当 spring.profiles.active 为空时,对应的连接符 - 也 将不存在,dataId 的拼接格式变成 ${prefix}.${file-extension}
file-exetension 为配置内容的数据格式,可以通过配置项 spring.cloud.nacos.config.file-extension 来配 置。目前只支持 properties 和 yaml 类型。
-
Nacos日志打印Bug,配置文件新增
logging.level.com.alibaba.nacos.client.config.impl=WARN logging.level.root=INFO
-
注意
部分同学如果出现 config dta not exist 建议重启nacos
除开上述问题,如果还是拉取不到配置(保持和课程版本,文件名一样先)
重新构建下项目 mvn clean package -U
然后重启IDEA
![](img/image-20220416140133577.png#alt=image-20220416140133577)
- 配置文件
server.port=8001 spring.application.name=dcloud-account-service
—————服务注册和发现———————
spring.cloud.nacos.discovery.server-addr=120.24.7.58:8848 spring.cloud.nacos.discovery.username=nacos spring.cloud.nacos.discovery.password=nacos
用于rpc调用token验证
rpc.token=xdclass.net168
———-redis连接配置———-
spring.redis.client-type=jedis spring.redis.host=120.24.7.58 spring.redis.password=xdclass.net spring.redis.port=6379 spring.redis.jedis.pool.max-active=100 spring.redis.jedis.pool.max-idle=100 spring.redis.jedis.pool.min-idle=100 spring.redis.jedis.pool.max-wait=60000
———-分库分表数据源配置———-
spring.shardingsphere.datasource.names=ds0 spring.shardingsphere.datasource.ds0.connectionTimeoutMilliseconds=30000 spring.shardingsphere.datasource.ds0.driver-class-name=com.mysql.cj.jdbc.Driver spring.shardingsphere.datasource.ds0.idleTimeoutMilliseconds=60000 spring.shardingsphere.datasource.ds0.jdbc-url=jdbc:mysql://120.79.150.146:3306/dcloud_account?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true spring.shardingsphere.datasource.ds0.maintenanceIntervalMilliseconds=30000 spring.shardingsphere.datasource.ds0.maxLifetimeMilliseconds=1800000 spring.shardingsphere.datasource.ds0.maxPoolSize=50 spring.shardingsphere.datasource.ds0.minPoolSize=50 spring.shardingsphere.datasource.ds0.password=xdclass.net168 spring.shardingsphere.datasource.ds0.type=com.zaxxer.hikari.HikariDataSource spring.shardingsphere.datasource.ds0.username=root spring.shardingsphere.props.sql.show=true
指定traffic表的数据分布情况,配置数据节点,行表达式标识符使用 ${…} 或 $->{…},但前者与 Spring 本身的文件占位符冲突,所以在 Spring 环境中建议使用 $->{…}
spring.shardingsphere.sharding.tables.traffic.actual-data-nodes=ds0.traffic_$->{0..1}
水平分表策略+行表达式分片
spring.shardingsphere.sharding.tables.traffic.table-strategy.inline.algorithm-expression=traffic_$->{ account_no % 2 } spring.shardingsphere.sharding.tables.traffic.table-strategy.inline.sharding-column=account_no
id生成策略
spring.shardingsphere.sharding.tables.traffic.key-generator.column=id spring.shardingsphere.sharding.tables.traffic.key-generator.props.worker.id=${workId} spring.shardingsphere.sharding.tables.traffic.key-generator.type=SNOWFLAKE
—————sms短信配置———————
sms.app-code=6999d4df3e7d48028470bbe517169a8d sms.template-id=M72CB42894
—————阿里云OSS配置———————
aliyun.oss.endpoint=oss-cn-guangzhou.aliyuncs.com aliyun.oss.access-key-id=LTAI5tHVGvYw7twoVFyruB1H aliyun.oss.access-key-secret=r4d0EudzSvPfVMb9Zp0TfmsE32RXmN aliyun.oss.bucketname=dcloud-link
—————rabbit配置———————
spring.rabbitmq.host=112.74.107.230 spring.rabbitmq.port=5672
需要手工创建虚拟主机
spring.rabbitmq.virtual-host=dev spring.rabbitmq.username=admin spring.rabbitmq.password=password
消息确认方式,manual(手动ack) 和auto(自动ack); 消息消费重试到达指定次数进到异常交换机和异常队列,需要改为自动ack确认消息
spring.rabbitmq.listener.simple.acknowledge-mode=auto
开启重试,消费者代码不能添加try catch捕获不往外抛异常
spring.rabbitmq.listener.simple.retry.enabled=true
最大重试次数
spring.rabbitmq.listener.simple.retry.max-attempts=4
重试消息的时间间隔,5秒
spring.rabbitmq.listener.simple.retry.initial-interval=5000
—————xxl-job配置———————
logging.config=classpath:logback.xml
调度中心部署地址,多个配置逗号分隔 “http://address01,http://address02“
xxl.job.admin.addresses=http://112.74.107.230:8080/xxl-job-admin
执行器token,非空时启用 xxl-job, access token
xxl.job.accessToken=xdclass.net
执行器app名称,和控制台那边配置一样的名称,不然注册不上去
xxl.job.executor.appname=traffic-app-executor
[选填]执行器注册:优先使用该配置作为注册地址,为空时使用内嵌服务 ”IP:PORT“ 作为注册地址。
从而更灵活的支持容器类型执行器动态IP和动态映射端口问题。
xxl.job.executor.address=
[选填]执行器IP :默认为空表示自动获取IP(即springboot容器的ip和端口,可以自动获取,也可以指定),多网卡时可手动设置指定IP,该IP不会绑定Host仅作为通讯实用;地址信息用于 “执行器注册” 和 “调度中心请求并触发任务”,
xxl.job.executor.ip=
[选填]执行器端口号:小于等于0则自动获取;默认端口为9999,单机部署多个执行器时,注意要配置不同执行器端口;
xxl.job.executor.port=9999
执行器日志文件存储路径,需要对该路径拥有读写权限;为空则使用默认路径
xxl.job.executor.logpath=./data/logs/xxl-job/executor
执行器日志保存天数
xxl.job.executor.logretentiondays=30
logging.level.com.alibaba.nacos.client.config.impl=WARN logging.level.root=INFO
<a name="451b707c"></a>
#### 第2集 短链平台整合Nacos配置中心开发和配置
**简介:短链平台整合Nacos配置中心开发和配置**
-
多个服务迁移配置中心实战
-
网关注意事项
-
pom文件
<!--添加nacos客户端-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--配置中心,需要需要使用配置中心,则开启-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!--坑:spring-cloud-dependencies 2020.0.0 默认不在加载bootstrap配置文件-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
-
bootstrap.yml
spring: application: name: dcloud-gateway cloud: nacos: config: config-long-poll-timeout: 600000 enabled: true file-extension: yaml namespace: public password: nacos server-addr: 120.24.7.58:8848 username: nacos profiles: active: dev
-
dcloud-gateway-dev.yaml
server: port: 8888
应用名称
spring: application: name: dcloud-gateway
服务注册发现
cloud: nacos: discovery: server-addr: 120.24.7.58:8848 username: nacos password: nacos
gateway:
routes: #数组形式
- id: dcloud-link-api-service #数据服务 路由唯一标识
uri: lb://dcloud-link-service #从nocas进行转发
order: 1 #优先级,数字越小优先级越高
predicates: #断言 配置哪个路径才转发,前端访问路径统一加上XXX-server,网关判断转发对应的服务,如果是回调业务记得修改
- Path=/* #匹配一个路径,用于短链解析
- id: dcloud-link-service #数据服务 路由唯一标识
uri: lb://dcloud-link-service #从nocas进行转发
order: 2 #优先级,数字越小优先级越高
predicates: #断言 配置哪个路径才转发,前端访问路径统一加上XXX-server,网关判断转发对应的服务,如果是回调业务记得修改
- Path=/link-server/**
filters: #过滤器,请求在传递过程中通过过滤器修改
- StripPrefix=1 #去掉第一层前缀,转发给后续的路径
- id: dcloud-data-service #数据服务 路由唯一标识
uri: lb://dcloud-data-service #从nocas进行转发
order: 3 #优先级,数字越小优先级越高
predicates: #断言 配置哪个路径才转发,前端访问路径统一加上XXX-server,网关判断转发对应的服务,如果是回调业务记得修改
- Path=/data-server/**
filters: #过滤器,请求在传递过程中通过过滤器修改
- StripPrefix=1 #去掉第一层前缀,转发给后续的路径
- id: dcloud-account-service #用户服务 路由唯一标识
uri: lb://dcloud-account-service #从nocas进行转发
order: 4 #优先级,数字越小优先级越高
predicates: #断言 配置哪个路径才转发,前端访问路径统一加上XXX-server,网关判断转发对应的服务,如果是回调业务记得修改
- Path=/account-server/**
filters: #过滤器,请求在传递过程中通过过滤器修改
- StripPrefix=1 #去掉第一层前缀,转发给后续的路径
- id: dcloud-shop-service #用户服务 路由唯一标识
uri: lb://dcloud-shop-service #从nocas进行转发
order: 5 #优先级,数字越小优先级越高
predicates: #断言 配置哪个路径才转发,前端访问路径统一加上XXX-server,网关判断转发对应的服务,如果是回调业务记得修改
- Path=/shop-server/**
filters: #过滤器,请求在传递过程中通过过滤器修改
- StripPrefix=1 #去掉第一层前缀,转发给后续的路径
#开启网关拉取nacos的服务
discovery:
locator:
enabled: true
设置日志级别,ERROR/WARN/INFO/DEBUG,默认是INFO以上才显示
logging: level: root: INFO
#nacos日志问题
com.alibaba.nacos.client.config.impl: WARN
<a name="d2cf0ecb"></a>
#### 第3集 短链平台Nacos配置中心链路测试
**简介:短链平台Nacos配置中心链路测试**
- 迁移配置中心,构建-推送-发布
![](img/image-20220407001917914.png#alt=image-20220407001917914)
<a name="22bb3879"></a>
#### 第4集 短链平台域名解析配置实战
**简介:短链平台域名解析配置实战**
-
一个http请求基本流程
-
客户端通过发起域名资源请求 -> DNS解析获得IP -> 寻找服务器获得资源
-
域名和ip的关系,DNS作用
- DNS:Domain Name Server 域名服务器 域名虽然便于人们记忆,但网络中的计算机之间只能互相认识IP地址,它们之间的转换工作称为域名解析,域名解析需要由专门的域名解析服务器来完成,DNS 就是进行域名解析的服务器
-
什么是cname和a记录
- a记录
- 用户可以在此设置域名并指向到自己的目标主机地址上,从而实现通过域名找到服务器(也叫ip指向域名配置)
- cname
- 别名指向,可以为一个主机设置别名。比如设置open1024.com,用来指向一个主机 xdclass.net 那么以后就可以用open1024.com来代替访问 xdclass.net 了
- [http://www.xdclass.net](http://www.xdclass.net/) --> xdclass.net
-
购买域名,备案
- 阿里云 备案地址:[https://beian.aliyun.com/](https://beian.aliyun.com/)
- 配置域名解析到服务器
- 备注:后端项目一般部署Linux服务器,前端静态资源可以部署Linux也可以部署在CDN上
-
阿里云服务器一
- 公网:112.74.107.230
- 私网:172.31.101.8
-
阿里云服务器二
- 公网:120.24.7.58
- 私网:172.31.101.9
![](https://file.xdclass.net/note/2020/javaweb/%E5%9B%BE%E7%89%87/logo.png#alt=logo) **愿景:"让编程不再难学,让技术与生活更加有趣"**
<a name="a8d39ebe"></a>
### 第七十三章 短链平台大课-教你如何写面试官喜欢的简历
<a name="6b17f1b7"></a>
#### 第1集 跳槽面试的软技能-换位思考能力有多重要
**简介: 跳槽面试的软技能-换位思考能力有多重要**
- 前言
- 部分同学比较急着面试,大课还有部分部署等没讲-后续再补即可,项目核心功能已经完结
- 这两章优先把简历编写完成,方便需要的同学可以去面试
![](img/image-20220418180549304.png#alt=image-20220418180549304)
-
与人沟通的软技能提升
- 高级工程师除开技术的能力外,换位思考能力最重要
-
常规大厂里面招聘目的
- 目的一:新成立的项目组,人手不够,需要【有经验】的人员能入手马上开发
- 大厂的话,针对性改简历
- 目的二:项目组人员离职,需要人手补充坑位
- 目的三:引进新的技术人员,提升团队技术水平
- 目的四:技术人员储备,校招或者年轻团队为主,为公司技术团队培养新势力
-
如何换位思考?你需要想下从简历编写到面试投递会经过哪些环节
- 编写简历
- 投递->HR筛选->约面
- 面试
- 一面基础
- 二面项目
- 三面潜力+价值观
-
遇到最多的问题就是
-
公司:简历筛选不通过,就没下文
- 学了大课,多数都可以投递
- 符合70%就行的,企业很多招聘条件都是Copy的,具体面试知道
- 好比:你会发现P6、P7招聘条件好像没啥难度
- [https://www.zhipin.com/](https://www.zhipin.com/)
![](img/image-20220418155957490.png#alt=image-20220418155957490)
-
自己:招聘条件有部分不符合,就不敢投递
- 学历问题,记住
- 多数互联网公司并非学历第一,而是技术+项目第一
- 大专学历的你,怎么办?
- 简历写本科,不是让你学历造假,而是去自考本科,没拿到毕业证也不影响
- 写本科目的是通过HR筛选,有技术面试机会
- 技术面试如果通过,HR面再说是自考本科快拿到毕业证了,多数都是不会卡的
- 因为技术优先,多数HR的招聘压力都很大
<a name="3cd97204"></a>
#### 第2集 没有对比就没有伤害-面试官的你喜欢哪份简历项目
**简介: 没有对比就没有伤害-面试官的你喜欢哪份简历项目**
- 简历一项目版块
2021.04 — 2021.08 XX省医保信息平台业务基础子系统 项目描述: 建设医保业务系统,为规范地方医保业务经办机构的业务经办流程; 提供参保管理、用户管理、缴费管理等功能,并通过医保业务数据归集机制,汇聚地方的医保业务数据和医保经办过程信息,实现城镇职工、城乡居民医保和医疗救助经办业务的均等化、规范化。 技术栈:SpringMVC+JSP+Svn+Tomcat+JQuery 工作内容 主要负责公共业务居民部分的开发工作,包括居民参保管理、居民缴费管理等功能。
2020.01 — 2020.04 XX省医保信息平台软件项目管理系统 项目描述: 国家医保局要求全国各省进行新医保信息平台建设,XX省医保局为对新医保平台的各子系统 的开发情况和相关厂商进行管理和监督提出的管理系统。主要功能包括:项目信息、任务管理、用户管理、厂商管理、设备管理、会议管理、签到考勤等。
技术栈:SpringMVC+JSP+Svn+Tomcat 工作内容:参与需求调研和评审及文档编写。 后端代码开发,主要负责厂商管理、用户管理、会议管理、设备管理相关功能的开发。
- 简历二项目版块
2017.12-2019.5 广州 open1024 科技有限公司 岗位:高级 java 开发工程师
项目:Open1024 电商平台 综合描述:open1024 电商平台是一个自营化妆品垂直类电商平台,涵盖众多服务, C 端业务商品中心、用户中心、营销中心、支付中心、物流中心、仓储中心等;还有数据 AI 中台包括画像分析、数仓建设、推荐系统, 整个系统架构采用是阿里 P8 带队,经过多个版本迭代,用户量过千万,团队组成基本是大厂出身
技术栈:SpringBoot+AliababCloud 全家桶+Redis+RabbitMQ+阿里云 OSS
项目一:电商平台用户中心+优惠券中心开发 项目描述:用户中心+优惠券中心是 C 端业务核心内容,支持用户多通道注册、防刷和恶意登录破解、个人资料、收货地址维护,黑名单控制等功能;优惠券支持多类型配置,无门槛,满减,新人卷,支持高并发下领劵和释放,支持一体化监控-自动化扩容等等功能。
个人职责:小组组长/开发主程
- 负责用户微服务注册-登录模块开发,支持多渠道验证码发送,具有防刷防恶意注册,越权设计等;封装 OSS 文件上传组件,支持灵活的截取和清晰度压缩配置;对接新用户拉新福利模块,采用 MQ 消息解耦,保证数据最终一致性和可靠性投递模块开发。 负责优惠券微服务设计,支持多种规则配置,无门槛劵-满减劵-福利劵,限领张数等配置。 采用 redisson 分布式锁解决高并发下单用户超领,和乐观锁解决优惠券超发问题。对接订单中心,改造优惠券核销和释放功能,采用延迟队列+本地 Task 解决分布式事务问题(前方案是采用 Seata 解决分布式事务,后采用 MQ 延迟队列+Task 支持更高的并发)
项目难点: 1)优惠券服务-领取和释放需要支持高并发保证安全防止超发超领, 2)需要防止灰产恶意注册和领劵造成公司活动资损
<a name="729fa13f"></a>
#### 第3集【重点】 短链平台大课简历-如何写好个人简介
**简介:【重点】 短链平台大课简历-写和回答好个人简介**
-
目标:
- 社招:高级工程师
- 一线城市 **25~35k**月薪,互联网行业,对标阿里**P6-P7**
- 总包:50到70万年薪
- 校招:Java开发工程师
- 一线城市 **10~20k**月薪,互联网行业,对标阿里**P5**
- 总包:25到40万年薪
-
简历版块划分
- 个人信息+求职岗位
- 教育信息和获奖经历+技术栈版块
- 公司项目介绍
- 自我评价
-
简历编写讲解
-
个人信息+求职岗位
-
教育信息和获奖经历版块
- 这块比较重要,HR或面试官会比较看重
- 问题一: 非科班出身的建议先不写专业
- 问题二: 大专学历怎么办?写本科,拿到面试机会,技术面后和hr说是自考的快拿到证书
- 问题三: 不要堆砌没用的经历和技术栈,换位思考-你 认为你这个能吸引面试官不?
- 问题四: 0基础培训机构出来的,如果是垃圾培训机构建议再好好 学习,然后包装简历一个小型公司的经历
培训不是⻅不得人事情,不培训或者不自学的才容 易被鄙视A 比如一个工作了3年的后端工程师,花半月去培训了 大数据的知识,获取去培训机器学习的,这类会更 加受欢迎,但是如果是去口碑差的培训公司就算了
- 问题五: 岗位和工作年限要对应,薪资是可以面谈(取 决公司的其他福利-年终等)
-
面试官:你好,麻烦做下自我介绍
- 我最近公司面试其他同学的情况和比较多的问题点
- 自我介绍30~50秒即可不要太长
- 自我介绍讲核心经历和项目内容,不要扯和应聘岗位工作没相关的内容
- 一定要自信、从容不要乱【尤其是面试大厂】,这个是第一印象很重要
- 不要狂妄,要谦虚和礼貌【3月份我就遇到一个应聘者,从其他大厂过来的】
![](img/image-20220418231506911.png#alt=image-20220418231506911)
<a name="37a6e675"></a>
#### 第4集 后端高级工程师简历-如何编写个人技术栈
**简介: 后端高级工程师简历-如何编写个人技术栈**
- 高级工程师不是会几个框架那么简单
- 技术栈丰富,有横有纵 ,主流框架会用+懂原理设计
- 大课里面的应用,明白框架设计思想,很多业务解决方案,可以从中间件设计思想里获取到
![](img/image-20220419131855712.png#alt=image-20220419131855712)
-
不要写精通、不要写精通、不要写精通!!!
- 问题一: 根据自己情况,编写技术栈,不懂的一定不要写
- 问题二: 不要被面试官挖坑跳入,学会引导面试官问自 己会的框架
- 问题三: 如果是初中级工程师,就可以适当删减些技术栈
- 问题四: 必须要有几个框架很懂的,包括原理和业界同类解决方案对比
-
如果有哪些不熟悉的,直接看我们这边对应的专题视频就 行,尤其是后端
- 网站: xdclass.net 根据路线学习
- 好比高考一样,每个专题视频都仔细学,基本视频里面都会把点的讲出来
-
校招+社招考查点
- 计算机编程语言基础 、 设计模式
- 框架(取决你熟悉的,和面试公司用的)
- 网络、 数据库
- 算法、逻辑思维
- 高并发、高可用、分布式
- 海数据处理、 性能优化
- 测试、 监控、安全
- 产品运营思维 ...
![](img/image-20220418233310960.png#alt=image-20220418233310960)
-
注意
- 你在一个日活1万用户量 和 日活千万级用户量的APP上,写登录功能,你能照搬???
- 很多人说大厂里面【面试造飞机、进去拧螺丝】,但面试官要综合评定这个人的技术水平
- 但是你想下,你在哪里拧什么机器上的螺丝
- 比如你熟练掌握java和主流框架,但是对数据库很弱、或者网络很弱,那你能写出一个靠谱的功能?
- 千里之堤,毁于蚁穴
<a name="fb32d1a8"></a>
#### 第5集【重点】面试官⻆度告诉你项目模块-常问题目
**简介: 【重点】面试官⻆度告诉你项目模块-常问题目**
-
项目没亮点,基本也就没面试机会了
- 所以简历一定要项目亮点,且多几个好项目能带来更多面试机会
- 比如很多同学,学了高并发项目大课 和 海量数据项目大课
- 好项目除了面试,还有本身自己技术真正提高
- 简历写的项目,自己一定要会或实操过!!!
-
编写项目经验-你需要注意的事情,面试官会关注那些
-
项目需求背景
- [https://zhuanlan.zhihu.com/p/428514067](https://zhuanlan.zhihu.com/p/428514067)
-
项目组多少人、开发周期怎样
- 研发人员: 19人 (总负责人高级技术专家, 阿里P8或 者资深P7+)
- 前端4人 负责C端界面研发、管理后端研发、部分具备全栈功能
- 后端6人,一个小组组⻓(P7)
- 多个微服务开发和工程搭建
- 每个微服务都有2个主负责人,1人手上会有 2~3个项目
- 大数据处理3人
- 运维3人
- 负责K8S和Rancher平台微服务、中间件搭建、监控处理、IaaS资源管理
- 测试3人
- 产品运营3人
-
开发周期
- 6个月,不断迭代
- 初版1~2个月,接下去是每个周一个版本 技术栈
-
个人职责
- 面试官问题(通用题目,所以自己的项目想好怎么回答)
- 项目XXX模块可以支撑多少并发
- 项目难点、业务哪里比复杂
- 解决方案
- 业界有没类似的方案,这个是否最优
- 如果让你改进,还有哪些可以做的更好
- 数据怎样,项目XXX量有多少(比如日活用户、日单量、日成交金额、日访问量等)
- 比如短链
- 日活用户: 10万
- 日单量:1万
- 新增短链:30条/用户每日
- 日新增短链数据:10万*30 = 300万
- 年新增用户数:50万/1年
- 短链日访问量:短链1千次/条 * 300万 =30亿次/天
- 8核16g机器 * 10台 :单机4万/QPS
- 机器集群资源池有储备,根据CPU、内存、RT情况进行自动化缩扩容
-
建议
- 项目适当包装-会让你脱颖而出
- 身边经历:
- 阿里、腾讯 内部晋升机制,懂的人都懂,都有适当包装,前提是自己了解
- 比如项目其他模块是其他人负责的,你了解熟悉的话也 可以面试说是自己负责的
- 有些架构是公司技术老大设计的但是还没落地
- 可以和面试官说怎样怎样 核心是思路,如果问到具体实现可以说是其他部⻔ 做的
- 二线三线大厂,都会靠拢一线大厂的架构设计
- 如果计划去大厂面试,做的东⻄、业务架构、开发 流程 符合面试官团队的流程的话,也是加分项!!!
<a name="f8cbf559"></a>
#### 第6集【重点】 短链平台大课简历-账号/商品服务项目实战
**简介: 短链平台大课简历-账号/商品服务项目实战**
- 简历里面项目介绍规范
- 项目介绍
- 技术栈+开发环境
- 个人职责(有结果或数据也可以写上)
- 项目难点
![](img/image-20220419162537566.png#alt=image-20220419162537566)
-
流量包模块QPS
-
扣减:8核16G机器,3~6万QPS,采用Redis预扣减方式,流量包扣减+短链创建分布式事务 采用MQ+Task保证最终一致性
-
池化思想应用,线程池+连接池:从5千QPS提升到3万QPS
-
很多亮点,根据笔记进行总结回顾
- 多商户号模式,突破了微信统一下单接口的QPS限制
<a name="cfa8d82d"></a>
#### 第7集【重点】 短链平台大课简历-实时计算/数据可视化服务实战
**简介: 短链平台大课简历-实时计算/数据可视化服务实战**
![](img/image-20220419172842693.png#alt=image-20220419172842693)
-
面试题
- 数据采集链路图
- 数仓分层职责
-
部署机器
- Flink实时计算部署:8核16g * 8台
-
数据可视化性能点
-
数据聚合根据需求进行查询
-
查询数据-有时间跨度限制
<a name="ada32846"></a>
#### 第8集【重点】 短链平台大课简历-短链服务编写实战
**简介: 短链平台大课简历-短链服务编写实战**
![](img/image-20220419181011663.png#alt=image-20220419181011663)
-
面试官问题
-
分库分表数据量数据预估
- 首年日活用户: 10万
- 首年日新增短链数据:10万* (30~50) = 500万
- 年新增短链数:500万 * 365天 = 18.2亿
- 第18章第7集笔记
-
多维度查询怎么解决
![](https://file.xdclass.net/note/2020/javaweb/%E5%9B%BE%E7%89%87/logo.png#alt=logo) **愿景:"让编程不再难学,让技术与生活更加有趣"**
<a name="1635ca8e"></a>
### 第七十四章 短链平台-服务端性能数据评估-面试回答
<a name="94142bf4"></a>
#### 第1集 面试-短链平台后端接口扩容性能评估方案《上》
**简介:面试-短链平台后端接口扩容性能评估方案**
- 部署机器
- 阿里云ECS 8核16G,带宽按量付费
- Rancher2.X管理多台主机,资源池可以快速增加主机和节点
- Redis部署3主3从
- RabbitMQ集群6个节点
- Kafka 集群部署6个节点
- 查询的接口
- 都加入Redis缓存,单机2~5万/QPS属于正常范围
- 账号服务
- 节点数量6个
- 接口性能QPS (单机)
- 发送短信:5千/QPS
- 用户注册:1万/QPS
- 用户登录:3万/QPS
- 查询流量包:3万/QPS
- 扣减流量包:3万/QPS
- 数据库分库分表
- acccount 2库2表
- traffic 2库8表
- traffic_task 2库2表
![](img/image-20220420162903338.png#alt=image-20220420162903338)
- 后端接口性能评估增长方案
- 单机1000/qps,两个节点就不是2000/qps
- 并非线性增长,需要考虑整个链路,常规是每增加一台则 * 0.6~0.8
- 比如两个节点则是1000 + 1000 *0.75 = 1750 QPS
- 计算方式
- 1台:N 个QPS
- 2台:N + (N * 0.75)
- 3台:N + (N _ 0.75) + (N _ 0.75) * 0.75
- ...越到后面,增加越多机器,带来的QPS增幅约少
<a name="2633a6cf"></a>
#### 第2集 面试-短链平台后端接口扩容性能评估方案《下》
**简介:面试-短链平台后端接口扩容性能评估方案**
-
短链服务
- 节点数量15~30个(根据负载情况扩容)
- 接口QPS(单机)
- 创建短链:2万QPS
- C端访问短链: 5万/QPS
- 查看短链详情: 2万/QPS
- 删除短链: 2万QPS
- 新增分组: 2万QPS
- 查询分组列表: 3万/QPS
- 查询分组详情: 3万/QPS
- 数据库分库分表
- short_link表: 62个库 ,每个库 62表 ;前期4个库,每个库 62个表
- group_code_mapping: 10库,每个库62个表
- link_group: 16库 ,每个库16表
-
商品服务
-
节点数量6个
-
接口QPS(单机)
- 商品列表和详情:4万QPS
- 获取下单令牌接口:1万QPS
- 下单支付接口:1千QPS
-
数据库分库分表
- product 1库1表
- product_order 2库,每个库8表
-
数据服务
-
节点数量6个
-
接口QPS(单机)
- 查看地区访问分布 1万QPS
- 查看访问趋势小-时 1万QPS
- 查看设备信息分布图 1万QPS
-
数据库
- ClickHouse部署20节点,根据商家账号Hash进入不同分片
- 数据定期归档,只能查看1年的数据
-
Flink实时计算服务
- 节点数量根据负载情况调整,最低是10个节点
- Flink里面的TaskSolt并行度为10
- Kafka的对应的Topic有10个分区
<a name="18b68d43"></a>
#### 第3集 大厂面试必备技能-磁盘数据量预估方案
**简介:大厂面试必备技能-数据量预估方案**
-
ODS层数据存储磁盘评估
- 数据预算
一个英文字母(不分大小写)占一个字节的空间,一个中文汉字占两个字节的空间。 英文标点占一个字节,中文标点占两个字节。
1字节(Byte)=8位(bit) 1KB( Kilobyte,千字节)=1024B 1MB( Megabyte,兆字节)=1024KB
采集数据发送的MQ消息=300个字节,约等于0.3KB
- 数据大小
{“ip”:”141.123.11.31”,”ts”:1646145133665,”event”:” SHORT_LINK_TYPE”,”udid”:null,”bizId”:”026m8O3a”,”d ata”: {“referer”:null,”accountNo”:”693100647796441088”,” user-agent”:”Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.109 Safari/537.36”}}
-
部署机器
-
阿里云ECS 8核16G,带宽按量付费
-
每台Kafka安装在专属的机器上,Kafka机器上没有混布其他服务
-
Kafka 集群部署6个节点
-
副本数量*2
-
每天要向Kafka集群发送30亿条消息,防止消息丢失每条消息存储两份,并且默认保存3天时间
-
磁盘空间
- 30亿 _ 0.3KB _ 2 / 1024 / 1024 =1716GB
- 保存3天,消息压缩比0.75 ,则1.7TB _ 3 _ 0.75 = 3.8TB
- 预留10%的磁盘空间存储其他类型的数据,如索引数据等
- 则需要5TB磁盘存储ODS消息
-
类似预估(根据存储的记录大小和数据量进行评估)
-
Redis需要多大内存、Mysql需要多大磁盘空间
-
MongoDB、Hbase 等很多
-
大家可以自己尝试评估自己做的项目,当下和未来增长,起码要想过【怎么计算】
![](https://file.xdclass.net/note/2020/javaweb/%E5%9B%BE%E7%89%87/logo.png#alt=logo) **愿景:"让编程不再难学,让技术与生活更加有趣"**
<a name="d82d0fd1"></a>
### 第七十五章 跳槽季重点事项和新入职快速上手项目
<a name="9f5157e0"></a>
#### 第1集 简历去哪里投递和初步的避坑指南
**简介:简历去哪里投递和初步的避坑指南**
-
互联网公司比较喜欢的平台
- boss直聘 [https://www.zhipin.com/](https://www.zhipin.com/)
- 拉钩 [https://www.lagou.com/](https://www.lagou.com/)
- 猎聘 [https://event.liepin.com/](https://event.liepin.com/)
- 公司内部开发
- 阿里 [https://campus.alibaba.com/index.htm](https://campus.alibaba.com/index.htm)
- 腾讯 [https://careers.tencent.com/](https://careers.tencent.com/)
- 海投简历回复率
- 没大厂背景或者项目没亮点,15个回复1个
- 项目有亮点:5个回复1个
- 有二三线大厂背景,简历挂上去基本就有很多人联系了
![](img/image-202104211435159421.png#alt=image-20210421143515942)
-
为啥你的简历通过不了大厂的初步筛选
- 28岁以上-公司和项目背景普通,是否有【高并发项目经验】【海量数据处理经验】+【带团队经验】
- 20~28-项目太简单没亮点,核心点是否有【高并发项目经验】【海量数据处理经验】
-
根据大课前面开头说的PEST方法论,选择合适的领域
- PEST方法论 指的是政治(Political)、经济(Economic)、社会 (Social)和技术(Technological)
- 政治环境主要是看我们的国家现在是否鼓励相关的业务
- 经济环境又可以分为宏观经济和微观经济,包括居⺠消费 水平、产业结构
- 社会环境则是说跟社会的⻛俗习惯是否吻合
- 技术环境当然就是说的我们的技术实力(ASML光刻机)
- 案例:BAT区块链大牛找你开发特币交易平台app
- P 国家出台了相关政策、法律法规监管
- E 大家有闲钱,现在人居可支配收入高
- S 人口规模大,生活方式改变,投资理财越来越多
- T 团队已有对应的技术,行业领先,移动互联网成熟
-
案例延伸
- 互联网金融贷款产品 p2p
- 高中K12在线教育产品
- 初中同学找你开发电商平台-对标淘宝、拼多多
-
总结
- 聪明的人做聪明的事,避免自己入坑
- 行业选择错:白忙活几年,领域专家也吃亏
- 公司裁员、倒闭发不出工资:白干几个月,辛苦大半年,一夜回到解放前
<a name="04239717"></a>
#### 第2集 如何看待算法是否重要和怎么提升
**简介:如何看待算法是否重要和怎么提升**
-
如何看待算法这个是否重要和怎么提升
- 会算法的同学忽略这个哈
![](img/image-20220422110428475.png#alt=image-20220422110428475)
-
主要是不会算法的同学怎么快速提升和时间分配
-
算法题核心面试的公司:字节、微信、华为等
- 这个情况,只能少投递,或者刷题目了
- 但常规有些一线大厂会卡这个算法题,但是多数都不会卡
- 更何况还有二三线互联网大厂也基本不卡,要求没那么高
-
算法题非核心面试的公司:如阿里、美团、京东、腾讯、平安、58等等
- 都是技术基础(面试八股文)+项目为主
- 算法题会有少数,但没做出或者思路有误,多数不卡
- 因为是综合评估多个【技术点】: 语言基础、框架、网络、数据库、项目、沟通能力等掌握情况
-
时间分配
- 如果除了【算法】外,【技术点】都掌握差不多
- 则可以去补下算法题,刷下LeetCode
- 面试前15题,但期间也要定时复习【技术点】
- 如果【算法】和 【技术点】都不牢固,优先把【技术点】补充好,因为选择更多
-
算法题分两阶段难度
- 大学期间的《数据结构和算法》级别《多数掌握即可》
- LeetCode、ACM等算法比赛题
- 里面也有初级、中级、高级,常规初中级就行
-
说个趣事
- 很多大厂内部每年都会举办些算法比赛
- 以我这边多年经历告诉大家
- 算法题这个一般都是临时刷题找灵感才行
- 【多数同学】都是专注业务开发和中间件研发等,日常工作用少就生疏了
- 举办过多次比赛,3人一组,我也经常参加,队友是P7算法专家,还有另外一个业务开发同事
- 5道题目2小时,我们经常一道题都没做出来
- 另外不止我们组是这样,多数组都是题没做出来!!!
- 所以不用怀疑自己的能力和智商
![](img/image-20220422112510232.png#alt=image-20220422112510232)
<a name="805028eb"></a>
#### 第3集 职业规划和怎样应对互联网公司的多轮面试
**简介:职业规划和怎样应对互联网公司的多轮面试备**
-
对于自己的三-五-十年规划还是要有的
-
工作15年,5到10年
- 初中级工程师,传统IT企业,或者小型互联网公司,写过几个项目,有上线-有下线的
- 定个目标
- 啥时间可以带小团队,技术leader 或 CTO
- 啥时间进二三线互联网大厂,啥时间进一线大厂
- 32岁前时间有限,年龄越大机会越少,但不是没有机会
- 32岁后技术+管理,双管齐下,40岁前一定不要脱离技术编码本身
- 一步步做好自己的规划,不要温水煮青蛙!!!
- 我们【小滴课堂】不贩卖焦虑,但一定要我们的同学做好自己的规划
-
注意:处在外包公司中
- 赶紧跳槽,不要只看工资前期高,但对你职业发展没帮助
- 外包没归属感,接触不到核心内容,更多是【时间换金钱】技术没啥增长
- 最终再次跳槽的时候,你能写什么亮点?常规都是XXX管理系统
-
面试前
- 最好去查下公司相关产品业务、如果有app或者网站,提前去熟悉
- 调查公司的项目,结合技术栈进行加深,也可以看公司其他招聘岗位,推断业务方向
- 查询公司盈利能力,未来发展机会,避免公司倒闭或者发不出工资,结合网上评价
- 公司交通位置,上下班会不会特别远,面试最好早到30分钟,不要迟到
- 巩固面试岗位所需要的技术栈,有自己擅长的某一个领域 比如某个中间件或者框架
- 大厂会有人才库和面试记录,尽量做足准备再去
-
面试中
- 要有礼貌,保持头脑清晰,思维清晰
- 要有自信,不能自卑或者没信心
- 专业技术一定要准备好,尽量往自己熟悉的方向考虑,连环问
- 做过项目一定要熟悉,准备好项目的亮点和难点
- 有学习热情,掌握主流技术栈,关注前言技术
- 大厂存在交叉面,方便评级和保证公平
-
面试后
- 尽量可以自己总结,复盘
- 面试多了,问题都类似,一定去补上,下次也可能遇到
- 可以问问面试官有没回答不好的点,可以加个联系方式,多点主动联系
- 如果有多个offer,如果不是特别缺钱,优先选择能学到技术多的,未来总会返还
<a name="e45124b8"></a>
#### 第4集 【重点】新人入职-如何快速上手公司项目
**简介: 【重点】新人入职-如何快速上手公司项目**
- 很多同学最担心的问题,入职后怎么快速上手公司项目代码
![](img/image-20220422115352064.png#alt=image-20220422115352064)
- 常见的问题
- 新公司的项目分层和之前不一样,怎么办?
- 新公司编码规范和之前不一样,怎么办?
- 新公司技术栈和之前用的不一样,怎么办?
- 业务复杂-逻辑混乱-看不懂逻辑,怎么快速看懂代码?
![](img/image-20220422134157777.png#alt=image-20220422134157777)
-
这个都是很正常现象
- 阿里这边有个俗话,入职阿里的前3个月,天天想离职的冲动
- 业务复杂、数据量大、中间件多、自研平台又多
- 如HSF、鹰眼、Aone,MetaQ、Amazon、TDDL、ScheduleX、PandoraBoot等,外部也没资料可学
- 其实都是换汤不换药,应用场景都类似,宏观去认知做啥的,然后跑个Demo就知道了
-
常见的问题
-
新公司的项目分层和之前不一样
- 每个公司都有不一样的分层,取决于认知情况
- 有些人认为这样、有些人认为那样,这个只要常规遵循团队规范即可
-
新公司编码规范和之前不一样
- 目前多数公司Java开发都是遵循阿里巴巴编码规范
- 老项目则另说,有些技术债务是需要自己去承担的
-
新公司技术栈和之前用的不一样
- 大厂基本都会有自研的中间件或者基于开源中间件进行二次开发的组件
- 只要封装的合理,能加快开发速度;但也有些大厂的架构组封装不合理或者历史原因遗留的坑
- 理想看待,只要把大课技术栈掌握就行,每个技术栈都是有应用场景,可以水平替换过去。
- 比如阿里这边分布式调度是ScheduleX,外部可以用XXL-Job
- 内部RPC是用HSF,外部是用SpringCloud、Dubbo等
- 内部分库分表中间件是TDDL,外部是SharingJDBC、MyCat等
-
刚入职或接时手项目,业务复杂-逻辑混乱-看不懂逻辑
-
多数情况都是进入项目组开发,比较少机会是从0开始的
-
怎样快速上手代码-多年经验分享
- 切记、切记、切记不要直接看代码(下面不懂的则问公司老人)
- 第一步:看产品,体验整体功能,思考背后大体逻辑,App、PC、H5等
- 第二步:看产品需求文档,历史需求文档和现在进行的需求文档
- 第三步:看项目技术栈-补充单独不会的技术框架,跑起个demo案例
- 第四步:看项目技术架构图和部署环境
- 第五步:看数据库ER设计和关联关系、是否有分库分表
- 第六步:看团队负责的功能、模块、服务、包作用
- 第七步:调下团队负责的某个功能链路,从发起请求到响应结果,经过的后端调用链和代码逻辑
- 第八步: 按照上述步骤,再看其他的模块业务逻辑和调用链,不要在意细节点或者没用到的组件
-
最后:祝大家旗开得胜,当个大厂Offer收割机!!!!
![](img/image-20220422144241217.png#alt=image-20220422144241217)
![](https://file.xdclass.net/note/2020/javaweb/%E5%9B%BE%E7%89%87/logo.png#alt=logo) **愿景:"让编程不再难学,让技术与生活更加有趣"**
<a name="f70dc01a"></a>
### 第七十六章 Flink实时计算服务部署阿里云服务器实战
<a name="693792be"></a>
#### 第1集 Flink实时计算服务本地修改和打包实战
**简介:Flink实时计算服务本地修改和打包实战**
-
修改代码
-
Task Solt 是指taskmanager的并发执行能力,parallelism是指taskmanager实际使用的并发能力
taskmanager.numberOfTaskSlots:4 假如每一个taskmanager中的分配4个TaskSlot,那有3个taskmanager一共有12个TaskSlot
-
并行度和solt的疑惑
- Task Slots 是具备的并发能力,大于 Parallelism并行度(实际用的)
- 数据流里面算子的最大并行度就是Parallelism, 2-2-2-3-1 这样的并行度,最大就是3(同个任务job里面)
- parallelism并行度调整,WebUI的里面的参数优先级 小于 代码API指定的并行度,常规代码里面不指定
- 代码中取消 env.setParallelism(1);
- 短链服务-埋点数据 随机ip和referer取消,重新打包推送和更新
-
修改配置文件
- kafka
- clickhouse
-
打包
- 打包插件
- maven-jar-plugin,默认的打包插件,用来打普通的jar包,需建立lib目录里来存放需要的依赖包
- maven-shade-plugin (推荐) 将依赖的jar包打包到当前jar包,成为fat JAR包,也可以防止类冲突 隔离
- maven-assembly-plugin,大数据项目用的比较多,支持自定义的打包结构,比如sql/shell等
- 测试插件
- maven-surefire-plugin, 用于mvn 生命周期的测试阶段的插件,通过参数设置在junit下控制测试
-
构建
- mvn install
- target目录下的jar包
<a name="a83ef5f0"></a>
#### 第2集 Rancher部署Flink Job-TaskManager实战
**简介:Rancher部署Flink Job-TaskManager实战**
-
在Flink专题课程有讲,部署有多种方式
-
我们这边采用相对方便的Rancher部署,通过yml导入
- 第一个 flink-jobmanager-deployment.yaml
- 第二个 flink-jobmanager-service.yaml (不显示服务)
- 第三个 taskmanager-deployment.yaml
- 2个节点
- 每个TaskManager有6个TaskSolt
-
注意
-
3个yml文件,在本章本集的资料里面
-
固定主机运行,不然如果资源不够则容易造成整个Rancher K8s集群出问题
-
Flink节点耗内存和CPU、带宽(加载慢)等资源,阿里云ECS部署单独的机器,否则容易宕机
- 原因一:节点所在机器宕机,导致失联
- 原因二:宕机的节点对应内存设置太小,GC严重导致心跳超时,导致失联
TimeoutException: Heartbeat of TaskManager with id 10.42.1.74:37727-f1bafb timed out.
-
按量付费方式选择【 4核16G内存】并安装好Docker,添加到Rancher的主机上
-
网络安全组开放端口 web ui端口 8081
<a name="825c4962"></a>
#### 第3集 通过WebUI提交运行Flink实时计算任务
**简介:通过WebUI提交运行Flink实时计算任务**
- 访问WebUI
- 上传jar包
- 选择main入口类APP
- 提交任务查看情况
<a name="4a5aa4bd"></a>
#### 第4集 Nginx-网关Gateway的区别和微服务获取用户ip
**简介:Nginx-网关gateway的区别和微服务获取用户ip**
-
问题
- 容器部署微服务,内部如何获取用户访问来源IP呢?
- 答案:通过Nginx反向代理透传过去
-
面试官:为啥有了gateway,还需要nginx呢?
前端请求到gateway,gateway在转发到相应的业务微服务,为什么不可以直接从nginx转发到相应的业务中心,还要加多一层
![](img/image-20220425001754542.png#alt=image-20220425001754542)
- 答案
- nginx和Spring Cloud Gateway在功能上是有一些重叠的地方,但是各司其职互相配合会更强大
- Spring Cloud Gateway层
- 可以认为是业务网关,针对SpringCloud体系专门推出,但是如果有其他服务不是用Cloud开发的怎么办?
- 有部分复杂业务逻辑nginx解决不了,可以用gateway用java语言开发
- 容器部署微服务的ip地址一直在换,需要结合注册中心来使用,所以gateway更灵活
- Nginx
- 关注的是协议和路由的转发,聚合入口方便配置管理
- 在性能、容错机制上比Gateway强,多语言多环境下兼容性更好
- 日志统计、协议路由转发、业务数据缓存前置、资源压缩等也是比较强大
- 配置HTTPS证书更灵活、Openresty+Lua开发各个强大的功能模块
- 有超多应用场景,可以看Nignx专题课程
<a name="7d18dd23"></a>
#### 第5集 阿里云ECS服务器-源码安装部署Nginx和配置代理
**简介:阿里云ECS服务器-源码安装部署Nginx实战**
-
有条件的可以新买一台ECS进行安装Nginx(按量付费,不用Rancher部署)
- 这边就用Jenkins的那台机器安装
- 注意
- Rancher下的机器是不能安装的哈,gateway网关占用了80端口
- 底层是 kube-prox进行端口转发,所以需要新的机器
- 后续也会用到Nginx作为流量入口捕获的机器
-
下载压缩包 并上传
- [http://nginx.org/en/download.html](http://nginx.org/en/download.html)
-
安装依赖
- yum -y install gcc zlib zlib-devel pcre-devel openssl openssl-devel
-
创建一个文件夹,上传本地提供的nginx包
-
本章本集资料里面
-
tar -zxvf nginx-1.18.0.tar.gz
//执行命令 ./configure make make install
-
默认安装路径
- /usr/local/nginx
-
访问配置
cd /usr/local/nginx/sbin
启动
./nginx
重新加载配置文件
./nginx -s reload
-
防火墙开放端口,阿里云网络安全组配置80端口
-
配置网关代理
- nginx和gateway不在同个阿里云账号,改为公网ip转发
- 实际企业中肯定是同个内网转发的哈,改ip即可
upstream gateway {
server 112.74.107.230:80;
}
server {
listen 80;
server_name localhost;
location / {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://gateway/;
}
}
-
重新配置域名解析 到 nginx所在ip
- g1.fit
- api.link.open1024.com
<a name="a1be98ab"></a>
#### 第6集 Flink实时计算-请求链路验证实战
**简介:Flink实时计算数据请求链路验证实战**
-
访问短链
- [http://g1.fit/026m8O3a](http://g1.fit/026m8O3a)
-
ClickHouse数据验证
-
建表语句
CREATE TABLE default.visit_stats (
`code` String,
`referer` String,
`is_new` UInt64,
`account_no` UInt64,
`province` String,
`city` String,
`ip` String,
`browser_name` String,
`os` String,
`device_type` String,
`pv` UInt64,
`uv` UInt64,
`start_time` DateTime,
`end_time` DateTime,
`ts` UInt64
) ENGINE = ReplacingMergeTree(ts) PARTITION BY toYYYYMMDD(start_time) ORDER BY (start_time, end_time, code, province, city, referer, is_new, ip, browser_name, os, device_type) SETTINGS index_granularity = 8192
![](https://file.xdclass.net/note/2020/javaweb/%E5%9B%BE%E7%89%87/logo.png#alt=logo) **愿景:"让编程不再难学,让技术与生活更加有趣"**
<a name="896ec8cd"></a>
### 第七十七章 【重磅加餐】一线大厂如何产生海量请求压测
<a name="dc9f8534"></a>
#### 第1集 Jmeter单接口压测问题点和流量漏斗模型介绍
**简介: Jmeter单接口压测问题点和流量漏斗模型介绍**
-
压测工具选择
- Jmeter
- LoadLunner
- Apache AB
-
问题点
-
常用的Jmeter压测工具,进行单接口压测是没问题的,可以得出基于某个机器配置下接口的吞吐量
-
但是实际线上业务,用户不可能只访问某个接口
-
更多的是多个接口业务联动,有可能某个接口出现瓶颈导致其他接口出现问题
-
例子
小滴课堂 1、轮播图列表接口【4核8G,QPS 3万】 2、分类列表接口【4核8G,QPS 5万】 3、视频详情页接口【4核8G,QPS 4万】 4、加入购物车接口【4核8G,QPS 2万】 5、创建订单接口【4核8G,QPS 8千】 6、注册接口【4核8G,QPS 1万】 7、用户信息查询接口【4核8G,QPS 1万】 8、推荐视频拉链接口【4核8G,QPS 1K】
- 某天小滴课堂突然火了上了热门,每天几百万的用户访问
- 最高的时候每秒有3千用户访问,但后台却挂了。。。。。
![](img/image-20220426133110389.png#alt=image-20220426133110389)
- 老王就纳闷了,明明单机QPS都很高足够支撑这个访问量级了,却还出现问题
-
分析
-
单个接口支撑是没问题的,但是忽略了某些低QPS的接口,如【推荐视频】接口
-
由于这些接口性能不足,导致系统CPU、内存、链接被耗尽了
-
从而连锁反应导致其他接口RT响应超时、GC频繁、线程池耗尽
-
最主要的原因
- 老王由于工作失误,被冰冰和Anna小姐姐一直吐槽,用户都看不了视频了
- 咋办??
![](img/image-20220426134459042.png#alt=image-20220426134459042)
-
什么是流量漏斗模型(别名:流量模型)
- 谁会用:公司产品经理、运营、公司CTO、CEO
- 那我们研发工程师为啥要用?
- 举个例子大家就明白
- 互联网产品其本身就是一个虚拟的漏斗,用户的行为路径有很多,举个淘宝、JD这电商例子
- 首页->查看商品->添加购物车->注册->登录->下单->支付->确认收货
![](img/image-20220426133551521.png#alt=image-20220426133551521)
-
最终老王怎么办
-
小滴课堂老王-如果解决不了问题,就会被冰冰和Anna小姐姐看不起了
-
需要推测常规用户的访问流量模型 和 随着流量增大 和服务器性能关联指标变化情况
-
比如100万个用户进来
- 有多少是新用户会进行注册
- 有多少是新用户会进入详情页
- 有多少用户是会加入购物车
- 有多少用户是会支付订单
- ...
-
知道解决方式后,又带来新的问题,怎么记录用户访问链路【流量模型和增加大流量】?
<a name="f9613e23"></a>
#### 第2集 带你走进流量模型-流量记录重放技术
**简介: 带你走进流量模型-流量记录重放技术**
- 需要解决的问题
- 记录用户访问链路【流量模型和增加大流量】后各个指标的变化情况,CPU、RT、GC 、内存、带宽等
![](img/image-20220426132905659.png#alt=image-20220426132905659)
- 混合链路压测的要点
- 需要真实的用户访问分布数据(流量模型)
- 支持成倍的扩大缩放相关流量数据
- 敏感数据脱敏处理,压测脏数据隔离
- 隔离数据包括不限,日志、Redis、MQ、数据库等
- 敏感信息脱敏:手机号、session信息、联系方式等
![](img/image-20220426142038468.png#alt=image-20220426142038468)
-
推测用户访问链路流量漏洞模型【流量录制/重放】技术
- 通俗解释
- 把线上生产环境的流量记录存储下来,并可以重放流量
- 好比
- 小滴课堂老王 ,把自己去KTV、按摩等流程、路线、房间等记录下来
- 可以方便大钊、老帆等进行复用
![](img/image-20220426142959168.png#alt=image-20220426142959168)
-
工具
-
Nginx访问日志(基于HTTP协议)
- 需要二次开发程序读取协议或者使用三方工具
- 成本高,复用性相对弱
-
TCP Copy(基于TCP协议)
- 记录最原始的流量,支持多种协议
- 学习成本高,使用相对复杂
-
GoReplay(基于HTTP协议)
- 仅支持HTTP协议
- 成本低,上手快
<a name="ac3af2f3"></a>
#### 第3集 流量重放GoReplay介绍和依赖环境讲解
**简介: 流量重放GoReplay介绍和依赖环境讲解**
- 什么是 流量重放GoReplay
- 地址
- 官网:[https://goreplay.org/](https://goreplay.org/)
- github:[https://github.com/buger/goreplay](https://github.com/buger/goreplay)
- GO语言编写的http流量复制工具
- 使用流程简单,支持多个系统,mac、linux、win
- GoReplay 不是代理,而是在后台侦听网络接口上的流量
- 无需更改生产基础架构,只需在与服务相同的机器上运行 GoReplay 守护程序
- 使用者:腾讯、京东、阿里等一线大厂
![](img/image-20220426145837340.png#alt=image-20220426145837340)
-
流量录制重放特点
- 捕获网络指定端口流量,输出到控制台
- 捕获网络指定端口流量,将原始流量实时重放到其他环境中
- 捕获网络指定端口流量,并保存到文件中
- 捕获网络指定端口流量,请求过滤指定路径流量,并保存到文件中
-
机器和环境选择
- 机器:Nginx所在机器,入口流量
- 安装Go环境
- Go语言是Google开发的具有良好并发能力的编程语言
- Go语言别名Golang
<a name="2d80da57"></a>
#### 第4集 阿里云Linux服务器安装Go环境和GoReplay实战
**简介: 阿里云Linux服务器安装Go环境和GoReplay实战**
-
阿里云Nginx机器:112.74.55.160
-
Golang环境安装
- 安装包在本章本集资料里面
- 解压到指定目录
tar -C /usr/local -zxvf go1.5.3.linux-amd64.tar.gz
- 添加PATH环境变量
1.打开文件
vim /etc/profile
2.添加环境变量
export GOROOT=/usr/local/go export PATH=$PATH:$GOROOT/bin
3.编译生效
source /etc/profile
- 测试
输入 go version 出现版本号即为成功。
-
GoReplay安装
- 安装包在本章本集资料里面
- 解压工具
tar xvzf gor_1.3.1_x64.tar.gz
- 解压完压缩包后,可以从当前目录进行Gor,也可以将Gor文件复制到的PATH文件下
- 测试
./gor
<a name="41911036"></a>
#### 第5集 GoReplay流量录制和重放功能讲解
**简介: GoReplay流量录制和重放功能讲解**
-
重放机器准备
- 阿里云Nginx机器:112.74.55.160
- Docker安装Nginx,8080端口作为映射
docker run —name xdclass_nginx -p 8080:80 -d nginx:1.21.6
![](img/image-20220426155703343.png#alt=image-20220426155703343)
-
参数
-
输入
- --input-raw : 用于捕获 HTTP 流量时,应指定 IP 地址或界面以及应用程序端口
- --input-file :接收以前使用过的文件记录
- --input-tcp :如果决定将多个转发器Gor实例转发流量到它,Gor聚合实例使用
-
可用输出:
<br />--output-http :重播HTTP流量到给定的端点
<br />--output-file :记录传入到文件的流量
<br />--output-tcp :将传入的数据转发到另一个Gor实例
<br />--output-stdout :用于调试,输出所有数据。
-
案例测试
1.捕获网络流量,表示监听80端口发生的所有网络活动记录到stdout
gor —input-raw :80 —output-stdout
2.重放,将原始流量重放到其他环境中,同一个服务器但是端口不同,多个亦可
gor —input-raw :80 —output-http=”http://127.0.0.1:8080“
3.捕抓流量请求并保存到文件中,实际会保存为requests.gor文件名
gor —input-raw :80 —output-file requests.gor
4.从保存下来的流量文件中提取流量向某个端口输出
gor —input-file requests_0.gor —output-http=”http://127.0.0.1:8080“
5.请求过滤指定路径流量,使用该机制,只记录/account-server/api路径下的请求
gor —input-raw :80 —output-http “http://127.0.0.1:8080“ —http-allow-url /account-server/api
<a name="87fd60d9"></a>
#### 第6集 GoReplay多倍速度-循环重放流量实战
**简介: GoReplay多倍循环重放流量实战**
- 多倍重放录制流量
流量录制的监听命令
gor —input-raw :80 —output-file requests.gor
gor —input-file requests_0.gor —output-http “http://127.0.0.1:8080“
—input-file 从文件中获取请求数据,重放的时候5倍速度重放; 比如10秒20条请求,扩大2倍后就是5秒就跑完20条请求
—input-file-loop 无限循环,而不是读完这个文件就停止
—output-http 发送请求到 http://xdclass.net
—stats —output-http-stats 每 5 秒输出一次 TPS 数据
gor —input-file “requests_0.gor|200%” —input-file-loop —output-http “http://127.0.0.1:8080“ —stats —output-http-stats
gor —input-file “requests_0.gor|2000%” —output-http “http://127.0.0.1:8080“
gor —input-file “requests_0.gor|5000%” —input-file-loop —output-http “http://127.0.0.1:8080“ —stats —output-http-stats
gor —input-file “requests_0.gor|6000%” —input-file-loop —output-http “http://127.0.0.1:80“ —stats —output-http-stats
<a name="d326b528"></a>
#### 第7集 基于Gor录制短链平台基础流量模型数据
**简介: 基于Gor录制短链平台基础流量模型数据**
-
开启录制
gor —input-raw :80 —output-file requests.gor
-
注意事情
-
各个微服务可以单节点进行部署,方便压测观察数据
-
并非大规模全链路压测,环境和数据没做隔离
-
小滴课堂的《架构大课训练营》会有全链路压测内容
-
基础数据访问
-
因为我们还没上线,所以直接PostMan访问用于模拟正常用户的链路
-
公司
- 没上线,灰度发布,录制部分流量(产品、运营经理会进行推广的)
- 已经上线版本迭代,直接录制线上流量模型即可的
-
增删改查接口
- 有些新增、删除 是具备唯一性,则会新增错误
- 主要是查询为主,多数业务都 二八原则
- 流量角度:少数接口承接了80%的流量,多数接口承接了20%的流量
- 接口类型:查询类型接口承接了80%的流量,新增/更新/删除 承接了20%的流量
<a name="9f1ecc5b"></a>
#### 第8集 多倍扩大Gor回放流量模型-压测短链平台
**简介: 多倍扩大Gor回放流量模型-压测短链平台**
-
流量回放
- 10倍回放
- 50倍回放
- N倍回放(取决机器配置)
gor —input-file “requests_0.gor|300000%” —input-file-loop —output-http “http://127.0.0.1:80“ —stats —output-http-stats
-
观察结果
- 分钟调用次数:RT响应、CPU、内存、带宽占用、GC次数、接口成功率等指标
![](https://file.xdclass.net/note/2020/javaweb/%E5%9B%BE%E7%89%87/logo.png#alt=logo) **愿景:"让编程不再难学,让技术与生活更加有趣"**
<a name="7aafbda2"></a>
### 第七十八章 短链平台基础接口补充开发
<a name="aaf9576e"></a>
#### 第1集 账号服务个人信息查询基础接口开发
**简介: 账号服务个人信息查询基础接口开发**
-
账号微服务接口补充开发
- 查看个人信息模块
/**
* 查看个人信息
* @return
*/
@GetMapping("detail")
public JsonData detail(){
JsonData jsonData = accountService.detail();
return jsonData;
}
@Override
public AccountDO detail(Long accountNo) {
return accountMapper.selectOne(new QueryWrapper<AccountDO>().eq("account_no", accountNo));
}
@Data public class AccountVO {
private Long accountNo;
/**
* 头像
*/
private String headImg;
/**
* 手机号
*/
private String phone;
/**
* 邮箱
*/
private String mail;
/**
* 用户名
*/
private String username;
/**
* 认证级别,DEFAULT,REALNAME,ENTERPRISE,访问次数不一样
*/
private String auth;
@JsonProperty("createTime")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date gmtCreate;
}
<a name="7a7a1007"></a>
#### 第2集 惰性更新流量包前端展示和短链过期时间
**简介: 惰性更新流量包前端展示和短链过期时间**
-
问题点
- 流量包是用的时候才去检查更新,那如果用户昨天用了但今天没用
- 查看的总的流量包时候,还是旧的流量包
-
解决方式
- 利用当前时间和流量包更新时间进行判断
-
流量包惰性更新涉及的接口
- 分页查看流量包列表
- 查看流量包详情
-
编码实现
private TrafficVO beanProcess(TrafficDO trafficDO) {
TrafficVO trafficVO = new TrafficVO();
BeanUtils.copyProperties(trafficDO, trafficVO);
//惰性更新,前端显示问题,根据更新时间进行判断是否是最新的
//今天日期
String todayStr = TimeUtil.format(new Date(), "yyyy-MM-dd");
String trafficUpdateDate = TimeUtil.format(trafficDO.getGmtModified(), "yyyy-MM-dd");
//日期不一样,则未更新,dayUsed需要为0
if (!todayStr.equalsIgnoreCase(trafficUpdateDate)) {
trafficVO.setDayUsed(0);
}
return trafficVO;
}
- 短链服务expired过期时间处理 前端入参 expired字段
@Data public class ShortLinkAddRequest { /**
* 组
*/
private Long groupId;
/**
* 短链标题
*/
private String title;
/**
* 原生url
*/
private String originalUrl;
/**
* 域名id
*/
private Long domainId;
/**
* 域名类型
*/
private String domainType;
/**
* 过期时间
*/
@JsonFormat(locale = "zh",timezone = "GMT+8",pattern="yyyy-MM-dd HH:mm:ss")
private Date expired;
}
- 访问
低于1980的则是永久有效,毫秒 1980-01-01 00:00:00 -> 315504000000L
<a name="8f3f3268"></a>
#### 第3集 数据库 Too many connection异常解决
**简介: 数据库 Too many connection异常解决**
-
异常信息
- Mysql数据库报错,too many connection
- 结果就是导致无法连接数据库,微服务或者数据库客户端连接超时
-
原因分析
- MySQL默认的连接为100个,系统自带的连接数太小,连接的线程超过系统配置导致出现错误
- 可以通过部署多几个MysqlServer实例,物理分库
查看当前连接数
show full processlist;
查看最大连接数
show variables like “max_connections”;
set global max_connections=1000
- mysql的连接数保持时间-默认 28800(8个小时)
查看连接睡眠时间,默认是 28800,相对较少调整这个,不能太短,也不能过长;
show global variables like ‘wait_timeout’
wait_timeout解释: 当一个客户端连接到MySQL数据库后,如果客户端不自己断开,也不做任何操作,MySQL数据库会将这个连接保留”wait_timeout”这么长时间(单位是s,默认是28800s,也就是8小时),超过时间之后,MySQL数据库为了节省资源,就会断开这个连接
- 程序没及时关闭连接,产生过多sleep进程
-
注意
- 上述是临时修改,重启mysql会失效
- 永久修改可以通过修改mysql的配置/etc/my.cnf配置文件
- 搜索下mysql配置文件修改博文
<a name="f06350de"></a>
#### 第4集 Flink-IP解析地理位置信息-百度地图api案例
**简介: Flink根据IP解析地理位置信息-百度地图api案例**
-
本章内容都是跳动比较大,缝缝补补,根据大家遇到的问题补充的知识点
-
IP信息转换为地理位置信息,
- 百度地图文档 :[https://lbsyun.baidu.com/index.php?title=webapi/ip-api](https://lbsyun.baidu.com/index.php?title=webapi/ip-api)
- 离线解决方案:纯真IP库 GeoLite2 埃文科技 ip2region
-
代码
@Slf4j
public class AsyncLocationRequestFunction extends RichAsyncFunction
//private static final String IP_PARSE_URL = "https://restapi.amap.com/v3/ip?ip=%s&output=json&key=4f6e1b4212a5fdec6198720f261892bd";
private static final String IP_PARSE_URL = "http://api.map.baidu.com/location/ip?ak=ot37gt3nQm27omNBBqFHygbxVUafTl2V&ip=%s";
private CloseableHttpAsyncClient httpAsyncClient;
@Override
public void timeout(ShortLinkWideDO input, ResultFuture<String> resultFuture) throws Exception {
resultFuture.complete(Collections.singleton(null));
}
@Override
public void open(Configuration parameters) throws Exception {
this.httpAsyncClient = createAsyncHttpClient();
}
@Override
public void close() throws Exception {
if(httpAsyncClient!=null){
httpAsyncClient.close();
}
}
@Override
public void asyncInvoke(ShortLinkWideDO shortLinkWideDO, ResultFuture<String> resultFuture) throws Exception {
String ip = shortLinkWideDO.getIp();
String url = String.format(IP_PARSE_URL,ip);
HttpGet httpGet = new HttpGet(url);
Future<HttpResponse> future = httpAsyncClient.execute(httpGet, null);
CompletableFuture<ShortLinkWideDO> completableFuture = CompletableFuture.supplyAsync(new Supplier<ShortLinkWideDO>() {
@Override
public ShortLinkWideDO get() {
try {
HttpResponse response = future.get();
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode == HttpStatus.SC_OK) {
HttpEntity entity = response.getEntity();
String result = EntityUtils.toString(entity, "UTF-8");
JSONObject locationObj = JSON.parseObject(result);
//String city = locationObj.getString("city");
//String province = locationObj.getString("province");
String city = locationObj.getJSONObject("content").getJSONObject("address_detail").getString("city");
String province = locationObj.getJSONObject("content").getJSONObject("address_detail").getString("province");
shortLinkWideDO.setProvince(province);
shortLinkWideDO.setCity(city);
return shortLinkWideDO;
}
} catch (InterruptedException | ExecutionException | IOException e) {
log.error("ip解析错误,value={},msg={}", shortLinkWideDO, e.getMessage());
}
shortLinkWideDO.setProvince("-");
shortLinkWideDO.setCity("-");
return shortLinkWideDO;
}
});
completableFuture.thenAccept(new Consumer<ShortLinkWideDO>() {
@Override
public void accept(ShortLinkWideDO shortLinkWideDO) {
resultFuture.complete(Collections.singleton(JSON.toJSONString(shortLinkWideDO)));
}
});
// completableFuture.thenAccept( (dbResult) -> { // resultFuture.complete(Collections.singleton(JSON.toJSONString(shortLinkWideDO))); // });
}
private CloseableHttpAsyncClient createAsyncHttpClient() {
try {
RequestConfig requestConfig = RequestConfig.custom()
//返回数据的超时时间
.setSocketTimeout(20000)
//连接上服务器的超时时间
.setConnectTimeout(10000)
//从连接池中获取连接的超时时间
.setConnectionRequestTimeout(1000)
.build();
ConnectingIOReactor ioReactor = new DefaultConnectingIOReactor();
PoolingNHttpClientConnectionManager connManager = new PoolingNHttpClientConnectionManager(ioReactor);
//设置连接池最大是500个连接
connManager.setMaxTotal(500);
//MaxPerRoute是对maxtotal的细分,每个主机的并发最大是300,route是指域名
connManager.setDefaultMaxPerRoute(300);
CloseableHttpAsyncClient httpClient = HttpAsyncClients.custom().setConnectionManager(connManager)
.setDefaultRequestConfig(requestConfig)
.build();
httpClient.start();
return httpClient;
} catch (IOReactorException e) {
log.error("初始化 CloseableHttpAsyncClient异常:{}",e.getMessage());
return null;
}
}
}
![](https://file.xdclass.net/note/2020/javaweb/%E5%9B%BE%E7%89%87/logo.png#alt=logo) **愿景:"让编程不再难学,让技术与生活更加有趣"**
<a name="a6604ed5"></a>
### 第七十九章 小滴短链平台-前后端联调实战和Bug修复
<a name="61e5ff91"></a>
#### 第1集 小滴短链平台-前端业务功能效果演示
**简介: 小滴短链平台-前端业务功能效果演示**
- 线上地址
- 前端: link.open1024.com 或 link.devsq.com ,看哪个可以访问即可
- 效果和功能测试
<a name="b0d4d111"></a>
#### 第2集 小滴短链平台-前端技术栈环境搭建和结构讲解
**简介: 小滴短链平台-前端页面技术栈环境搭建**
-
前端代码(本章本集资料,如果有更新则用最新的git上下载)
- 最新地址:[https://gitee.com/wen_zhao/short-chain-front](https://gitee.com/wen_zhao/short-chain-front)
-
开发环境说明
-
编辑器
- Vscode( 自己下载即可,或者看HTML课程里面有安装包)
-
框架(确保大版本一致,如果之前安装了旧版node和npm,搜索博文重新升级下)
-
Node
- 版本 v16.15.0
- 【安装包】 有提供,也可以直接下载 [http://nodejs.cn/download/](http://nodejs.cn/download/)
-
Npm版本 8.5.5
-
其他版本
Vue3 版本:3.2.13
Ant-Design-Vue 版本:3.2.2
EchartJS 版本:5.3.2
![](img/image-20220525141727960.png#alt=image-20220525141727960)
-
下载代码-安装环境,如何启动?
-
导入VSCode
-
构建启动
npm install
npm run serve
-
核心目录结构讲解
- 依赖组件模块
![](img/image-20220525150027017.png#alt=image-20220525150027017)
- 请求接口地址
![](img/image-20220525145925974.png#alt=image-20220525145925974)
- 页面组件
![](img/image-20220525152505228.png#alt=image-20220525152505228)
- 后端api域名
![](img/image-20220525151949175.png#alt=image-20220525151949175)
-
前后端联调注意事项
- 前端详情启动端口不要冲突了
- 如果需要二次开发和联调,建议看下小滴课堂-Vue3课程,涉及到多个环境搭建
- 后端接口协议要和课程保持一致,不然前端代码识别不了
- 失败情况:前端 http , 后端https
- 失败情况:前端 https, 后端http
- 成功:前端http、后端http
- 成功:前端https、后端https
- 后端API地址记得修改(课程的后端api地址不一定可用,会改动,可以用自己的)
<a name="829a8a5e"></a>
#### 第3集 前后端联调-前端不能识别雪花算法id解决方案
**简介: 前后端联调-前端不能识别雪花算法id解决**
-
问题
- 雪花算法生成的id作为主键时,因为其长度为19位
- 而前端JS一般能处理16位,如果不处理的话在前端会造成精度丢失,最后两位会变成00
![](img/image-20220508170008980.png#alt=image-20220508170008980)
-
后端 解决方式
- 直接把id类型改为String就行,使用JackSon包的注解
- 对应的实体类主键属性加入注解[@JsonSerialize ](/JsonSerialize )
@JsonSerialize(using = ToStringSerializer.class) @TableId private Long id;
-
前端 解决方式
- 前端使用 json-bigint 模块进行处理,一般都是用axios数据请求
npm install json-bigint
代码封装
axios.defaults.transformResponse = [ function (data) { const json = JSONBIG({ storeAsString: true }) const res = json.parse(data) return res } ]
或
axios.defaults.transformResponse = [ function (data) { const json = JsonBigint({ storeAsString:true }) const res = json.parse(data) return res } ]
axios.create({ baseURL: ‘http://api.link.open1024.com‘, timeout: 5000, timeoutErrorMessage: ‘请求时间过长,请联系后端或者优化请求’, })
<a name="b3af53d7"></a>
#### 第4集 短链平台-前端Vue3打包发布阿里云OSS实战
**简介: 短链平台-前端Vue3打包发布阿里云OSS实战**
-
前端发布有多个方式
- Nginx文件服务器
- 云厂商文件服务器+CDN:阿里云OSS
-
Vue项目发布上线
-
打包编译
npm run build
-
上传阿里云OSS
-
配置域名和默认静态页面
<a name="fac7f2ca"></a>
#### 第5集 订单服务-主动关单查询支付平台业务处理
**简介: 订单服务-主动关单查询支付平台业务处理**
-
需求
- 用户下单支付成功了,但是回调通知失败了,导致本地订单数据库未支付成功
- 延迟队列订单超时,取消订单前需要向第三方平台查询订单是否支付完成
![](img/image-20220524133057821.png#alt=image-20220524133057821)
- 接收微信推送的支付成功消息后,我们需要做啥?(第42章内容)
- 更新订单状态
- 调用账号服务发放流包
-
编码实战
/**
* //延迟消息的时间 需要比订单过期 时间长一点,这样就不存在查询的时候,用户还能支付成功
* <p>
* //查询订单是否存在,如果已经支付则正常结束
* //如果订单未支付,主动调用第三方支付平台查询订单状态
* //确认未支付,本地取消订单
* //如果第三方平台已经支付,主动的把订单状态改成已支付,造成该原因的情况可能是支付通道回调有问题,然后触发支付后的动作,如何触发?RPC还是?
*
* @param eventMessage
*/
@Override
public boolean closeProductOrder(EventMessage eventMessage) {
String outTradeNo = eventMessage.getBizId();
Long accountNo = eventMessage.getAccountNo();
ProductOrderDO productOrderDO = productOrderManager.findByOutTradeNoAndAccountNo(outTradeNo, accountNo);
if (productOrderDO == null) {
//订单不存在
log.warn("订单不存在");
return true;
}
if (productOrderDO.getState().equalsIgnoreCase(ProductOrderStateEnum.PAY.name())) {
//已经支付
log.info("直接确认消息,订单已经支付:{}", eventMessage);
return true;
}
//未支付,需要向第三方支付平台查询状态
if (productOrderDO.getState().equalsIgnoreCase(ProductOrderStateEnum.NEW.name())) {
//向第三方查询状态
PayInfoVO payInfoVO = new PayInfoVO();
payInfoVO.setPayType(productOrderDO.getPayType());
payInfoVO.setOutTradeNo(outTradeNo);
payInfoVO.setAccountNo(accountNo);
// 需要向第三方支付平台查询状态
String payResult = payFactory.queryPayStatus(payInfoVO);
if (StringUtils.isBlank(payResult)) {
//如果为空,则未支付成功,本地取消订单
productOrderManager.updateOrderPayState(outTradeNo, accountNo, ProductOrderStateEnum.CANCEL.name(), ProductOrderStateEnum.NEW.name());
log.info("未支付成功,本地取消订单:{}", eventMessage);
} else {
//支付成功,主动把订单状态更新成支付
log.warn("支付成功,但是微信回调通知失败,需要排查问题:{}", eventMessage);
productOrderManager.updateOrderPayState(outTradeNo, accountNo, ProductOrderStateEnum.PAY.name(), ProductOrderStateEnum.NEW.name());
//触发支付成功后的逻辑,
Map<String, Object> content = new HashMap<>(4);
content.put("outTradeNo", outTradeNo);
content.put("buyNum", productOrderDO.getBuyNum());
content.put("accountNo", accountNo);
content.put("product", productOrderDO.getProductSnapshot());
//构建消息
EventMessage payEventMessage = EventMessage.builder()
.bizId(outTradeNo)
.accountNo(accountNo)
.messageId(outTradeNo)
.content(JsonUtil.obj2Json(content))
.eventMessageType(EventMessageType.PRODUCT_ORDER_PAY.name())
.build();
//如果key不存在,则设置成功,返回true
Boolean flag = redisTemplate.opsForValue().setIfAbsent(outTradeNo, "OK", 3, TimeUnit.DAYS);
if (flag) {
rabbitTemplate.convertAndSend(rabbitMQConfig.getOrderEventExchange(),
rabbitMQConfig.getOrderUpdateTrafficRoutingKey(), payEventMessage);
return false;
}
}
}
return true;
}
```
第6集 缝缝补补-小滴短链平台Bug修复
简介: 缝缝补补-短链平台Bug修复
修复一
- 分页某个分组的查找短链,未加排序
修复二
- 数据可视化服务-升序
修复三
- Flink服务TimeUtil时间格式化
修复四
- 修复Agent空判断
修复五
- 查看我的订单-降序
修复后进行发布上线即可
- 如果还有些小bug没测试发现的
愿景:”让编程不再难学,让技术与生活更加有趣”
第八十章 海量数据项目大课-商用短链平台总结
第1集 短链平台-如何包装到的简历和线上地址
简介: 短链平台-如何包装到的简历和线上地址
线上地址(面试官如果提问是否有线上地址,直接这个即可)
小滴课堂提供地址: link.open1024.com 或 link.devsq.cn ,看哪个可以访问即可
大家可以自己部署即可
- 课程提供了前端、后端代码, 自己只需要弄个前端页面和域名即可
面试官:项目是公司的还是练习的
社招
- 基本公司都是短信营销、数据分析的,所以都可以直接包装
- 实在没法:则说是公司的合伙人控股的公司,采购我们开发的短链平台进行部署
校招
- 学校已经毕业的师兄创业,拉你做主程进行开发,然后卖给其他公司
- 不要说是练习项目,不用担心背调,基本一线大厂P7级别以上才有背调的
公司需求背景和带来的价值
学历提升还是挺重要的,起码迈出这一步
- 大专-》自考本科
- 本科-》在职研究生
第2集 小滴商用短链平台-项目维护地址
简介: 小滴商用短链平台-项目维护地址
目的
- 短链平台多数核心接口都是有的了,当然也有不少小bug,逐步修复
- 包括后续更多功能增加等也会推送上去,避免大家丢失了项目或者找不到
- 仓库里面有前端、后端代码、SQL脚本,包括后续新更新的也会推送上去
GitHub地址(国内访问相对比较慢)
- 个人联系方式
第3集 学完海量数据项目大课-下一步你需要做啥
简介: 学完海量数据项目大课-下一步你需要做啥
技术培训的你应该知道的事情
- 技术是每年一小变,三年一大变,持续学习是关键
- 大家也是过来人,小滴课堂-每个技术栈课程不贵,从专题技术到大课,不同阶段你收获也不一样
- 市面上技术视频很多,但是与其不如四处搜索,还不错选个靠谱的平台持续学习
- 提升自己技术能力 + 不浪费时间 + 涨薪,是超过N倍的收益
- 我也在学习每年参加各种技术沙⻰和内部分享会投 入超过6位数,但是我认为是值的,且带来的收益更大
- 定时复习专题课程笔记+大课里面的笔记和解决方案
- 技术是不断演进的,后端+大数据,小滴课堂也会不断上架新的技术,做好技术储备,就不会被淘汰!!!
为啥说免费的才是最贵的,比如【盗版视频】或者【割韭菜的机构】
视频录制时间旧 或者 讲师水平Low,导致学不到技术甚至还【学错了技术】
课程资料、代码缺少,导致自己花了大量时间去调试最后还不一定解决
项目源码不规范、思考维度缺少,什么样的老师带出什么样的学生
正版学习群的关键性
- 盗版群里你能结实到什么人员,各种混杂人员,遇到问题卡死,花了几百块还盗版人员跑路不更新
- 正版学习群里,不少大厂技术负责人,本身内推和招聘,风气也好、价值观也好
- 遇到问题有群里的学霸和讲师帮你解决,也有更多面经分享等
大家计算下自己的时薪、日薪
月薪20k, 一天8小时,一个月工作22天,你一小时值多少钱、一天值多少钱 ,1千块!!
还有很多互联网公司,一年14到16个月工资+股票,那一天就是好几千了,阿里P6级别都是30k月薪
举个跳槽例子
- 老王月薪10k,花了4k学了大课,一线城市找到了20k Offer
- 入职第一个月月薪的四分之一就回本了,后续都是自己更高的收入
记住!!!!
跳槽涨薪不是你说跳就跳的,说涨就涨的
你需要有让面试官给你这个薪资的理由
- 比如你有好项目经历、各种高并发、分布式解决方案
- 能给公司和技术团队带来新血液,提升一个档次
- 你学了大课,里面N多技术难点解决方案,就可以让你靠这些去跳槽涨薪!!!
技术人的导航站(涵盖我们主流的技术网站、社区、工具等等)
- 地址:open1024.com
- 地址:xdclass.net
第4集 新大课预告-架构大课训练营-成为架构师的事情
简介: 新大课预告-架构大课训练营-成为架构师的事情
大课综合训练营
适合用于专项拔高,适合有一定工作经验的同学,往架构师方向发展,包括多个方向
- 综合项目大课训练营 √
- 海量数据分库分表大课训练营 √
- 架构解决方案大课训练营 ing
- 高级前端大课训练营 ing
- 测试开发平台大课训练营
- 大数据大课训练营
- 算法刷题大课训练营
- 安全攻防大课训练营
- 数据分析大课训练营
期待下一个大课:架构解决方案大课训练营,(预计22年11月首发)
架构师核心能力
- 架构思维能力+软件工程核心
- 架构设计+架构图+多领域业务架构解决方案
- 架构技术选型+团队新技术储备
- 微服务治理发现+高性能+高可用架构体系
- 业务运维+监控告警体系架构
- 质量管理体系+链路压测改造体系架构
- 高阶段全方位体系面试:存储技术、网络技术
- 高阶段全方位体系面试:高并发技术、分布式技术
- 团队沟通能力+ 管理+汇报
- ….
关于大家的好奇点
不少同学已经掌握了两个大课,那接下去就是架构综合能力提升
- 综合项目大课训练营 √
- 海量数据分库分表大课训练营 √
为啥两年前不直接讲架构课程,我直接学不就行了
大家要记住
架构师不是一朝一夕就达到的,不经过复杂项目积累,是达不到这样的架构师能力
- 比如很多中间件都有:惰性思想,那架构师具备的能力就是抽取中间件-》应用到业务需求上解决问题
- 还有很多思想、解决方案、还有架构师本身具备哪些点
不是说学了某个架构师课程、P8/P9课程就是架构师了
也不是说有软考的《架构师认证》就是架构师了
没有复杂互联网项目经历,只是懂某些理论,那PPT架构师是行不通的
架构师也看公司和领域
- 阿里的P6+或P7,那再常规小型公司就是架构师,看地方和公司大小,十人、百人、千人、万人
- 但是在阿里当架构师也就是P8了,基本从其他地方跳槽过来,本身也是一二线大厂同个级别的了
- 架构师也有多种:解决方案架构师、大数据架构师、java架构师等
我个人的一个座右铭:”什么阶段,做什么事情和规划什么事情“
- 在其位谋其政,目标是其位也要谋其政,才能逐步达到这个水平
- 工作1~5年,目标肯定是往架构师,学了两个大课,往下就是架构能力提升
- 在校大学生,学霸,两个项目大课+架构大课,你已经超越同龄人很多,大厂都喜欢潜力好的小年轻
为啥是11月才出课程
- 下半年往往比上半年忙,一线互联网公司下半年是事情最多的,大家都是搬砖的都懂
- 每一个大课,都需要讲师贯穿全部内容+原创+备课+录制,这样才有竞争力,慢工出细活!!!
- 小滴课堂-让技术不再难学,也是持续贯穿下去,做大家需要的【技术持续充电平台】