nginx
为什么用它,它有什么好处
1.做文件服务器(静态资源访问)
2.限流
3.负载均衡(随机,轮询,权重,IPHash)
4.反向代理,统一路由
nginx(geteway网关代替它了)
为什么用nginx?
1.限流(IP,用户)
2.负载均衡(随机,轮询,权重,一致性hash,最少使用)
3.统一路由(文件服务器)
网关(网络的关口)
1.安全
2.统一路由
3.鉴权
4.安全认证,解密,签名(sign)
为什么要有文件服务器?
nginx搭建文件服务器
docker pull nginx:latest
-d 在后台运行
-p 将容器的 80 端口映射到本机的 80 端口
-v 挂载目录;必须使用绝对路径(/开头的地址)
docker run -d --name nginx -p 80:80 -v /mydata/nginx/conf/conf.d:/etc/nginx/conf.d -v /mydata/nginx/html:/usr/share/nginx/html nginx
把 nginx.conf放到挂载目录下
/mydata/nginx/conf
nginx.conf
关注配置:include /etc/nginx/conf.d/*.conf;
在conf.d目录新增:static-gmall.conf文件
server { listen 80; server_name static.gmall.com; location /static/ { root /usr/share/nginx/html; } }
修改本地hosts文件
C:\Windows\System32\drivers\etc
锁?
分布式锁
为什么用分布式锁?
分布式得情况下,保证线程安全,分布式锁
分布式锁得实现
1.数据库
2.redis
3.zookeeper
参数(商品id,数量)
加锁synchronized lock
{
根据id查询商品信息(库存10)
判断限购数量
(判断当前库存-数量)>=0才能购买
减库存设置到数据库
创建订单
}
分布式锁
当前页pageNum=1
总记录数 total
总页数totalPage
每页显示多少条,pageSize
totalPage = total%pageSize==0?total/pageSize:(total/pageSize)+1
<!-- 以后使用Redisson作为所有分布式锁 --> <dependency> <groupId>org.redisson</groupId> <artifactId>redisson</artifactId> <version>3.12.0</version> </dependency>
为什么用es?
数据库查询数据量大了后,模糊查询不走索引,效率低
速度快,倒排索引
ES
match模糊查询 like
term等值查询 =
terms多个值的查询 in
range区间查询
sort排序
highlight高亮(华为)
分页
pageSize 每页显示多少条 20 100
pageNum 当前页 1
total 总记录数
totalPage 总页数
int totalPage = total %pageSize ==0?total /pageSize :(total /pageSize)+1
幂等:无论执行多少次都对我自身业务没有任何影响
//1:电信2G_1:移动3G_1:联通4G,2:4.0-4.9英寸_2:4.0-4.9英寸
线程和进程区别?
线程是任务执行的最小单元,一个进程里面包含多个线程
如何实现一个线程?
继承Thread类,实现Runable接口
现在Callable接口,可以返回线程执行结果
线程安全问题?
有没有共享统一资源操作
解决线程安全的方式?
synchronized Lock AtomicInteger 线程安全的工具类ConcurrentHashMap ThreadLocal
为什么使用线程池?
线程的状态:就绪,运行,休眠,等待,唤醒,销毁
线程池的好处:提高线程的重复利用率
进程和线程有什么区别?
线程是执行的最小单位,一个进程里面会有多个线程
进程类似QQ
Java中实现一个线程有什么方式? 继承Thread类,重写run方法
实现Runable接口,重写run方法
开启一个线程去执行,没有返回结果 CompletableFuture.runAsync(()->{ System.out.println("开启了一个线程执行2"); });
//开启线下得到执行返回结果 CompletableFuture<String> infoFuture = CompletableFuture.supplyAsync(() -> { System.out.println("查询商品信息"); try { Thread.sleep(5000); }catch (Exception ex){ ex.printStackTrace(); } return "商品信息"; }); System.out.println(infoFuture.get());
//等待所有线程执行完后,代码才继续执行 CompletableFuture.allOf(f1,f2,f3,f4,f5).join();
//f1执行完后,依赖f1执行结果继续执行 CompletableFuture f6 = f1.thenApplyAsync((f)->{ try { Thread.sleep(1000); } catch (Exception ex) { ex.printStackTrace(); } return "查询图片信息"; });
为什么要用线程池?
线程数一旦多了后,cpu飙升
线程生命周期:创建,待续,执行,销毁,中断,唤醒
避免频繁的创建线程,提高线程重复利用率
ThreadPoolExecutor(int corePoolSize 核心线程数, int maximumPoolSize 最大线程数, long keepAliveTime 线程存活时间, TimeUnit unit , BlockingQueue<Runnable> workQueue) 阻塞队列 RejectedExecutionHandler handler 拒绝策略*/ //当我们创建一个线程池的时候,默认情况下是一个线程都没有的,随着任务的提交,它会拿到当前线程数和 //核心线程数比较,如果当前线程数小于核心线程数,那么创建线程,如果大于核心线程数,会把当前任务放到阻塞队列 //如果队列没有满,继续放,如果满了,再拿到当前线程数和最大线程数做比较,如果小于最大线程数,继续创建线程 //如果超过了最大线程数,触发拒绝策略 ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2,3,2,TimeUnit.SECONDS, new LinkedBlockingQueue<>(2)); //当前线程数0,核心线程数为2 threadPoolExecutor.submit(()->{ System.out.println(Thread.currentThread().getName()+" 第一个线程"); try { Thread.sleep(1000); }catch (Exception ex){ ex.printStackTrace(); } }); //当前线程数1,核心线程数为2 threadPoolExecutor.submit(()->{ System.out.println(Thread.currentThread().getName()+" 第二个线程"); printStackTrace(); } }); //当前线程数2,核心线程数为2,阻塞队列放入1个 threadPoolExecutor.submit(()->{ System.out.println(Thread.currentThread().getName()+" 第三个线程"); }); //当前线程数2,核心线程数为2,阻塞队列放入2个 threadPoolExecutor.submit(()->{ System.out.println(Thread.currentThread().getName()+" 第四个线程"); }); //当前线程数2,核心线程数为2,最大线程数为3 threadPoolExecutor.submit(()->{ System.out.println(Thread.currentThread().getName()+" 第五个线程"); }); //当前线程数3,核心线程数为2,最大线程数为3 threadPoolExecutor.submit(()->{ System.out.println(Thread.currentThread().getName()+" 第六个线程"); }); Thread.sleep(3000);xi
核心线程数和最大线程数设置?
CPU密集度(任务执行比较快,没有IO操作),核心线程数差不多设置为1或者2*cpu核数,最大线程数设置为
核心线程数基础上加30左右
IO密集度(文件读写IO,网络传输IO,操作数据库IO)核心线程数差不多设置为3或者4*cpu核数,最大线程数设置为
核心线程数基础上加30左右
想让子线程执行完后主线程继续执行,怎么办?或者让某些线程有序的执行
join,CountDownLatch
线程安全问题?
多线程同时访问同一资源,数据不一致情况的问题
购物车
List<CartItemVo>
key: "cartItemList_"+userId value: JSON.tonstring(list);
key: "cartItemList_"+userId HashMap<Long,String> (put(skuId, JSON.tonstring(cartItem)))
为什么使用MQ?
异步(发送短信)
削峰(12306)
解耦(调用es接口,换成MQ解耦)
MQ弊端?
为什么使用MQ?
异步(发送短信,不需要等待用户操作完)
削峰(12306排队抢票)
解耦(不用依赖外部接口)
用MQ有什么弊端?
使系统变得更加复杂,整个服务链路变长了(中间需要经过MQ)
MQ出现单点故障问题(做集群,持久化)
业务MQ消息丢失,重复消费
RabbitMQ
direct点对点模式,一个生产者对应着一个消费者(发送短信)
fanout广播模式,一个生产者对应着多个消费者(注册的时候,送优惠券,短信提示,消息提示)
topic广播模式,支持表达式匹配(交换机下面的队列,只要满足routekey表达式匹配就可以路由到队列)
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-amqp</artifactId> </dependency>
消息队列丢失的原因?
MQ(持久化做集群,几乎不会丢)
消息发到MQ,消费者监听的时候丢了(原因是:默认情况下rabbitMQ是自动签收,也就是消息进去消费者监听里面消息就立马删除了,消费者还没消费完,消息就删除了,万一消息消费失败,也就消息丢失了)
解决方案:采用ack确认机制,也就是消息手动签收
如果生产者发到MQ的过程消息丢失了,怎么办?
消息落地(消息保存到数据库【id,msgId,type,content,retry_num,create_time】),消费者消费完成后根据msgId删除,那么在消息记录表里面都是没有消费成功的,通过定时任务查询继续往队列里面发(定时任务做补偿)
因为重回队列,可能产生重复消费?
我们给每个消息分配一个msgId,消息消费完之后存入redis一段时间6小时,每次消费之前都会校验一下redis是否存在,如果存在说明之前已经消费过,直接结束
延迟队列?
订单15分钟后失效?拼团48小时失效?
多长时间后做什么事情,可以用延迟队列?
如果没有延迟队列,这些需求能不能做?(定时任务)
延迟队列和定时任务对比?
延迟队列实时性比较好
定时任务弊端(1.当数据量大时,每次定时查询对数据库性能有影响,2.实时性不好)
死信队列?
1.rabbitMQ消息设置时间后,消息过期了,会进去死信队列
2.rabbitMQ在消费消息达到一定的重试次数后,会进去死信队列
rabbitMQ延迟队列实现原理
有两个队列,一个队列设置了它的ttl消息过期时间,同时设置了消息过期后的路由交换机和路由的routeKey,
另外一个队列是普通队列,监听的时候只用去监听普通队列,达到延迟队列的效果
@Configuration public class RabbitMqOrderConfig { @Bean public Queue createOrderDealQueue(){ Map<String,Object> arguments = new HashMap<>(); arguments.put("x-message-ttl",60000);//消息过期时间 arguments.put("x-dead-letter-exchange","order-event-exchange");//消息过期时候的交换机 arguments.put("x-dead-letter-routing-key","gmall.order.release.queue");//消息过期的routing-key Queue queue = new Queue("gmall.order.deal.queue",true,false, false,arguments); return queue; } @Bean public Queue createOrderReleaseQuque(){ Queue queue = new Queue("gmall.order.release.queue"); return queue; } @Bean public Exchange createOrderExchange(){ Exchange exchange = new DirectExchange("order-event-exchange"); return exchange; } @Bean public Binding createBindingDealQueue(){ //String destination, Binding.DestinationType destinationType, String exchange, // String routingKey, Map<String, Object> arguments Binding binding = new Binding("gmall.order.deal.queue", Binding.DestinationType.QUEUE, "order-event-exchange","gmall.order.deal.queue",null); return binding; } @Bean public Binding createBindingReleaseQueue(){ //String destination, Binding.DestinationType destinationType, String exchange, // String routingKey, Map<String, Object> arguments Binding binding = new Binding("gmall.order.release.queue", Binding.DestinationType.QUEUE, "order-event-exchange","gmall.order.release.queue",null); return binding; } }
幂等(无论执行多少次都对自身业务没有任何影响)
select 不需要考虑
insert 需要考虑
update a set c=2 where id=1 不需要考虑
update a set c=c+2 where id=1 需要考虑
delete from a where id=1 不需要考虑
解决幂等,有哪些方式,订单重复提交?
1.token机制(进入页面的时候产生一个token放入redis里面10分钟,提交订单的时候把token传过去,如果token存在直接删除,并且执行任务,如果token不存在,提示用户请刷新页面提交)
2.加分布式锁
3.前台通过按钮置灰处理,请求开始按钮变不可用,请求结束再操作
订单业务处理
OrderSubmitVo(用户名,购物车商品idsList,token,支付金额,收货地址id)
//解决幂等问题,校验token是否有效
//根据地址id查询地址具体信息
//根据商品idsList查询购物车信息
//后台计算价格和前台做对比
//检验所有商品库存是否足够(调用wms接口)
//锁定库存(调用wms接口)
//保存订单表和订单明细表
//发送信息到延迟队列15分钟后失效
延迟队列监听
//如果订单状态不为待支付,直接结束(避免出现已经支付的订单取消)
//根据订单号修改订单状态为已取消
//释放库存(调用wms接口)
分布式事务
跨数据库,跨服务的,是没有办法用到@Transaction的,需要用到分布式事务
XA协议
TCC做补偿(T代表try,C代表confirm确认,C代表取消cancle)
最终一致性(做补偿)
satea(阿里框架)
CAP理论(C一致性,A可用性,P分区容错性)
zk保证的是CP
eureka保证AP
支付宝对接
<dependency> <groupId>com.alipay.sdk</groupId> <artifactId>alipay-sdk-java</artifactId> <version>4.9.28.ALL</version> </dependency>
支付宝对接地址:小程序文档 - 支付宝文档中心
支付宝下单发起支付逻辑
//根据订单号查询订单信息是否存在
//防止重复支付
//防止出现重复支付问题(幂等问题)
//保存一笔交易流水
//调用支付宝下单接口
支付宝回调逻辑
//验签(防止数据被篡改)
//根据交易流水号查询流水状态,如果不等于处理中,直接拒绝(解决重复回调幂等问题)
//修改交易流水状态
//修改订单状态
//减库存(调用wms接口)
支付宝回调失败补偿
//通过定时任务查询所有交易状态为处理中的,根据商户交易流水号去查询支付宝的交易状态,
//修改交易流水状态
//修改订单状态
//减库存(调用wms接口)
为什么分库分表
1.数据库的连接数都是有限的
分库分表
读写分离
1.单台数据库存在性能瓶颈,它的连接数是有限的(搞多台数据库,读写分离)
2.一主多从(读写分离)
可能存在的问题(1.资源浪费,数据做了冗余,2.从库需要同步主库的数据(binlog日志同步),网络不好的情况出现数据延迟)
3.单表数据量超过4000w,数据还在增长需要考虑分表
为什么分库分表
1.单表数据量超过4000w,数据还在增长,哪怕建了索引效率也会低的
2.单台数据库存在性能瓶颈,它的连接数是有限的,搞多台数据库可以增加并发量
分库分表弊端
1.表之间关联比较复杂,没有办法inner join
2.分布式事务问题
分库分表框架
1.TDDL(阿里的,收费的)
2.mycat(是一个第三方的数据库中间件,学习成本比较高,一般需要单独招一个人去维护)
3.shareding-jdbc(java语音开发,比较轻量级)
1.sql层面
2.数据库做主从(读写分离,mysql主从同步原理。binlog)
Java代码: springboot aop读写分离,多数据源切换 ,地址: https://www.cnblogs.com/yeya/p/11936239.html ,shareding-jdbc
3.分库分表
垂直划分(各个库表结构不一样)
水平划分(各个库表结构一样数据不一样)
ds_0
user
address
ds_1
user
address
ds_2
user
address
分库分表的规则
1.通过id做hash取模的方式划分 order_0,order_1,order_2(扩展性比较差)
2.根据时间区间分表 order_202301, order_202302, order_202303(扩展性比较好)
QPS(每秒查询访问量)
TPS(每秒处理量)
xxl-job
为什么用xxl-job?
1.解决分布式系统并发执行问题
2.统一得任务管理,配置,调度,监控
3.有负载均衡算法,还有分片广播(提高任务执行效率)
秒杀 (10000)
秒杀下单逻辑(商品Id)
lock
try{
//是否在活动时间段
//限购商品
//根据id查询商品信息 100
//判断库存是否足够
//预减库存
//保持订单和订单明细
//发送延迟队列5分钟失效
}catch(){
}finally{
unlock
}
高并发,流量大(线程安全问题,超卖,分布式锁)
熔断限流(Sentine)
接口安全问题(加密签名)
拼团
展示拼团列表
复制生成得页面跳转地址(http://127.0.0.1:80/pingtuan?id=jkfsdkljfksjlfkljkjlfds) des,aes
立刻拼团
lock(active_pingtuan_+id)
//根据id查询拼团商品信息()拼团人数10
//判断本人是否拼团过
//判断拼团人数
//预减拼团人数
//保存拼团记录
//延迟对了15分钟后失效
unlock
砍价
展示砍价列表
复制生成得页面跳转地址(http://127.0.0.1:80/pingtuan?id=jkfsdkljfksjlfkljkjlfds) des,aes
砍价
//根据id查询砍价商品信息()
//判断本人是否砍价过
//砍价记录表查询(根据id查询记录,如果一条记录都没有,97%,发延迟队列)
//保存用户砍价记录
身份认证
//调用ocr接口获取身份证上得信息
认证(身份证正面,反面,活体视频,身份证号,姓名)
//校验参数
//姓名身份证号是否被认证
//判断用户是否认证
//上传oss
//调用身份认证接口
//调用活体认证接口
//判断三个任务是否成功,如果成功修改数据库
积分流水表(签到)
select * from qiandao where between '2023-03-25 17:17:34 ' and '2023-04-03 17:17:34 '
分库分表
为什么分库分表
1.一个数据库得连接数是有限得,如果表全部放一个数据库,性能下降(分库)
2.数据量太大,建了索引也会比较慢,如果数据量超过4000w,数据还在猛烈增长靠谱分表(分表)
分库分表弊端
1.需要解决分布式事务问题
2.表之间join关联问题
垂直划分和水平划分
垂直分库:按业务划分,每个库里面的表结构不一样
水平分库:库里面表结构一样,只是数据不一样
垂直分表:按业务把字段多的大表拆分多个小表,表结构字段不一样
水平分表:表结构字段都是一样,只是数据不一样
分库分表框架
阿里TDDL(收费)
Mycat(需要mycat中间件,学习中间件,学习成本很高,并且耦合度很大)
shareding-jdbc(Java代码开发,导入依赖,配置写策略,代码耦合度小,学习成本低,通过改写sql路由)
分表算法
通过hash去模算法(tb_order,tb_order_0,tb_order_1,tb_order_2,tb_order_3,tb_order_4,tb_order_5)
不利于扩展(一旦选择了,就没有办法扩展)
通过时间分表(tb_order,tb_order_2023_02,tb_order_202303,tb_order_202304)
扩展性好些