开发中的工作随笔 -高质量

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)

扩展性好些

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值