java高并发实战<1>

我们一个请求--->tomcat--->db  我们只需要把我们的应用部署在tomcat中,  
就可以了
这就是你单体的感念,单机结构你只用一个服务器就完成了你项目的部署

单点问题一旦这台机器挂了,用户就没有办法用你这个服务,单机能力有限 随着你用户量增长的过程中  用户越来越多,呢你此时的应用你的db就不能承受这么多用户了,所以这个时候要针对于
我们可以做应用和数据进行分离

在这里插入图片描述

这个就是分布式系统,应用和数据分离,用户发起一个请求 tomcat和mysql共同完成我们的响应
整体分布在不同的服务器上来完成我们的用户请求响应的  我们就叫做分布式系统
分布式系统讲的是更多从部署层面,只要你部署超过一台机器 我们就叫做分布式系统
上述系统中还是有些问题的,比如Tomcat存在单点故障问题,一旦Tomcat所在的服务器宕机不可用了,我们就无法提供服务了,所以针对单点故障问题,我们会使用集群来解决,单机处理到达瓶颈的时候,集群中每台服务器就叫做这个集群的一个节点”,所有节点构成了一个集群。每个节点都提供相同的服务,那么这样系统的处理能力就相当于提升了好几倍
当前所有节点的负载情况,决定将这个请求交给哪个节点处理,所以就有了ngixn 做负载
nginx 做负载 有负载均衡算法

在这里插入图片描述

我们一般访问的会是服务的域名的,比如说我们访问京东
我们此时是根据域名进行轮询调用后端的服务  比如说tomcat1 或者tomcat2
我们做集群后 通常要做负载均衡
#>我们可以基于nginx 做负载均衡

在这里插入图片描述

将请求交给ngixn,再经过ngixn分发给对应的服务器,我nginx仅仅做到的将请求进行转发

在这里插入图片描述


应用+组件 是我们的系统,
##比如说我们的旅游项目 叫做应用
##它附属的组件 叫做组件

单体应用架构 从部署角度来看
最终打包的过程中 你一个项目全部东西打到一个jar包中


在这里插入图片描述

这就是我们的单体应用
随着我们的项目越来越大 参与的人数越来越多  模块越来越多  沟通能力越来越多
甚至项目部署 我可能会影响到你 的内容 对部署来说也比较困难\


###> 然后我们会将我们的应用进行拆分 拆分为垂直应用架构

在这里插入图片描述

 我将我的项目进行拆分,我就会有3个团队来负责开发 , 这样的拆分相互部署不会有影响
  比如说A服务挂了 不会影响B服务

在这里插入图片描述

##缺点就是说 会有相同的代码重复 比如说
前端系统登录 你要写一个登录  后台系统需要登录 你也要写一个登录 
所以 我们后续会有分布式架构,我们把重复的工作进行抽取

在这里插入图片描述

springcloud
>我们的整体项目部署

在这里插入图片描述

然后我们的秒杀功能

在这里插入图片描述

nginx 进行请求的分发,我们可以实现我们的nginx 高可用
gateway 集群,  canal 做数据同步
mysql优化 可以实现读写分离  分库分表

redis+token 的方式 实现分布式session
>>>>>>>我们把我们的配置统一放在nacos上
我们统一的入口是我们的网关  我先把我们的网关跑起来


首先我们要解决的是我们跨域问题 
我们
前端--------->gateway--------->uaa
前端发送请求到后端只要ip或者端口不一致就会出现跨域问题,我们需要在网关中实现跨域
我们需要在网关中实现跨域#

第二就是真实ip 的问题

服务部署登录信息,当我们一个请求
用户--->nginx--->gateway--->uaa 的时候 我们在uaa中做登录的时候 此时uaa服务
当我们在request.getRemoteIp 中是获取的网关的ip  我们怎么拿到用户的真实ip

在这里插入图片描述

package cn.wolfcode.filters;

import cn.wolfcode.common.constants.CommonConstants;
import cn.wolfcode.redis.CommonRedisKey;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

/**
 * 定义全局过滤器,功能如下:
 * 1.把客户端真实IP通过请求同的方式传递给微服务
 * 2.在请求头中添加FEIGN_REQUEST的请求头,值为0,标记请求不是Feign调用,而是客户端调用
 * 3.刷新Token的有效时间
 */
@Component
public class CommonFilter implements GlobalFilter {
    @Autowired
    private StringRedisTemplate redisTemplate;

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        /**
         * pre拦截逻辑
         * 在请求去到微服务之前,做了两个处理
         * 1.把客户端真实IP通过请求同的方式传递给微服务
         * 2.在请求头中添加FEIGN_REQUEST的请求头,值为0,标记请求不是Feign调用,而是客户端调用
         */
        ServerHttpRequest request = exchange.getRequest().mutate().
                /*在过滤器中拿到ip*/
                header(CommonConstants.REAL_IP,exchange.getRequest().getRemoteAddress().getHostString()).
                header(CommonConstants.FEIGN_REQUEST_KEY,CommonConstants.FEIGN_REQUEST_FALSE).
                build();
        return chain.filter(exchange.mutate().request(request).build()).then(Mono.fromRunnable(()->{
            /**
             * post拦截逻辑
             * 在请求执行完微服务之后,需要刷新token在redis的时间
             * 判断token不为空 && Redis还存在这个token对于的key,这时候需要延长Redis中对应key的有效时间.
             * 没有带token 不会走这里
             */
            String token,redisKey;
            if(!StringUtils.isEmpty(token =exchange.getRequest().getHeaders().getFirst(CommonConstants.TOKEN_NAME))
                    && redisTemplate.hasKey(CommonRedisKey.USER_TOKEN.getRealKey(token))){
                // 我给key 许时间  每次续30分钟
                String s  = CommonRedisKey.USER_TOKEN.getRealKey(token);
                redisTemplate.expire(s  , CommonRedisKey.USER_TOKEN.getExpireTime(), CommonRedisKey.USER_TOKEN.getUnit());
            }
        }));
    }
}

在这里插入图片描述

我们需要在gateway中拿到请求ip放入header中给传递到后续的微服务中
header(CommonConstants.REAL_IP,exchange.getRequest().getRemoteAddress().getHostString())
##>>>

在这里插入图片描述

我们这样就会在uaa中拿到用户的真实ip,
在网关中加个拦截器 fiter 获取到真实的ip地址,转发请求的时候把你的真实ip地址放到请求头中,
在网关中获取用户的真实ip 放在header中传递给下来的微服务中


在这里插入图片描述

LoginLog loginLog = new LoginLog(phone, ip, new Date());
我们要把用户的登录信息记录下来,发送mq进行记录 进行写日志  高频日志记录
我们会专门有一个服务来写日志的,
如果我们的业务数据库除了写业务之后还得插入日志.
业务日志插入比较频繁  势必会影响db的写的性能
所以我们会把数据库进行拆分, 业务和日志db 来分开  mq慢慢写

##   我们此时还得将用户的账号密码信息和用户的基本信息分开

t_user_login 用户账号密码
t_user_base_info  用户基本信息

前端会把token进行缓存  我们用LocalStorage()进行存储token

在这里插入图片描述

  UserLogin userLogin = this.getUser(phone);
###>>>>>>.
userhash  18080018188  userInfo
		  		  17070017177  userInfo 
		  
userZset  17070017177  记录当前key   的时间


我会启动一个定时任务来进行批次得  就是说我要删除7天以外得用户登录,所以用户每次登录进来 我都要刷新时间



private UserLogin getUser(Long phone) {
    UserLogin userLogin;
    String hashKey = "userHash";
    String zSetKey ="userZset";
    String userKey  = String.valueOf(phone);

    String objStr = (String) redisTemplate.opsForHash().get(hashKey, String.valueOf(phone));
    if ("null".equals(objStr) || StringUtils.isEmpty(objStr)) {
        //缓存中并没有,从数据库中查询
        userLogin = userMapper.selectUserLoginByPhone(phone);
        //把用户的登录信息存储到Hash结构中.
        redisTemplate.opsForHash().put(hashKey, userKey, JSON.toJSONString(userLogin));
        //使用zSet结构,value存用户手机号码,分数为登录时间,在定时器中找出7天前登录的用户,然后再缓存中删除.
        //我们缓存中的只存储7天的用户登录信息(热点用户)
    } else {
        //缓存中有这个key
        userLogin = JSON.parseObject(objStr, UserLogin.class);
    }
    //
    redisTemplate.opsForZSet().add(zSetKey, userKey, new Date().getTime());
    return userLogin;
}
token 用户登录成功我会生成一个token 默认为30分钟,

private String createToken(UserInfo userInfo) {
   //token创建
   String token = UUID.randomUUID().toString().replace("-", "");
   //把user对象存储到redis中
   CommonRedisKey redisKey = CommonRedisKey.USER_TOKEN;
   redisTemplate.opsForValue().set(redisKey.getRealKey(token), JSON.toJSONString(userInfo), redisKey.getExpireTime(), redisKey.getUnit());
   return token;
}
##############>>>>>>>>  然后在用户每次请求后 我都会给这个用户来续时间
你每次访问的时候我们都要延迟一下有效时间  >>>>>>>..
每次访问的时候 我要对token进行刷新
后置拦截 意思是我要让我每个接口都给toknen 续命  一定是我不用这个软件的后续30分钟
保证我们的token 始终是有效的  当你没有活跃后的30分钟后失效de
##>>>>登录得缓存设计

在这里插入图片描述


限时抢购场景
首页秒杀列表功能 场次的秒杀 场次----->秒杀场次对应的商品

在这里插入图片描述

首先第一步  商家要在后台上架秒杀商品,
##>我此时我要查询秒杀商品,我就得做rpc发起调用
商品服务      t_ptoduct
秒杀服务      t_seckil_product
做rpc  我们进行微服务拆分后很多都是单表查询

在这里插入图片描述

rpc远程调用我们用到了feign 
##>秒杀服务--------->商品服务  我们此时会用到feign降级, 如果服务挂了或者说超时了


在这里插入图片描述

按照我们的请求 是查询到了 基于场次找到对应的秒杀商品信息

在这里插入图片描述

限时抢购 我们此时3个接口
>1.首页秒杀列表
>2.秒杀详情
>3.秒杀
##> 当然我们还有一个定时任务上架的功能  发布秒杀活动

在这里插入图片描述

商家需要发布秒杀活动 将信息从商品服务获取存储于秒杀服务中
  商品服务      t_ptoduct
  秒杀服务      t_seckil_product
做rpc  我们进行微服务拆分后很多都是单表查询
我们在做rpc调用的时候可以设置feign 降级

当用户量达到一定瓶颈后 我们可以做缓存预热, 分流
验证码/登录后才可以进行秒杀,从业务角度来说

我们的秒杀逻辑

在这里插入图片描述

然后我用jmeter压测我秒杀接口

1>qps 底下, 2. 超卖
秒杀1.0的版本  我们发现我们可能会存在超卖 以及重复下单的问题

我们的秒杀列表和秒杀详情接口都是
浏览器-------->应用服务器--->数据库这样的调用方式的

在这里插入图片描述

#>在秒杀这种业务场景中
我们针对于查询接口的优化方向  我们将这些数据放在缓存中,这样我们的性能有所提升

在这里插入图片描述

>我们定时任务的执行频率是每天执行一次   我们看下定时任务

在这里插入图片描述

我们会基于场次将数据存储于redis中,比如说[10,20.30]
我们应该采用各种手段 将我们的请求拦截于redis这个层面上
大流量的请求 一定要避免进入数据库中
我们优化的请求是尽量减少大流量的请求进入数据库

这样我们做秒杀商品列表以及详情的时候,我们的qps 就会上来
###>>>
我们压测的目的主要是要知道我们系统的大概cpu 处于什么位置
这样我们更好的限制我们限流规则 不至于说是请求太多 压崩溃我们的系统
我们也可以做动静分离  把我们的图片和接口分开处理
将图片放在cdn 上  动静分离

我们的图片可以采用动静分离 ,静态资源可以采用cdn进行分发

在这里插入图片描述

就相当于我广州机房的css,js  推送至中心站点 进行分发
###############>>>>>>>>>. 然后我们解决我们的query  update create 的问题
query   40
update  -1
create  +1
#这样就会出现超卖的问题
我们一定要保证它的库存不能小于0  所以我们可以使用乐观锁机制来实现它的库存不超
因为query 和update 不是原子操作 所以我们会出现超卖的问题
###############》》》》》》》》
我们可以采用数据库的乐观锁机制

乐观锁和悲观锁
在java中 乐观锁 CAS  我在修改的时候判断有没有上锁成功
悲观锁 sy Lock 我先上锁   我先上锁
#############>>>>>>>>>>>>>>>
我update的时候吧version 版本+1

这就是我们的乐观锁
>>>>>>>>>>>这就是我们要用的乐观锁
我们通过version字段来判断   乐观锁的实现
------------>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
我们使用乐观锁 每次修改的时候版本加1  

对于mysql 来说updare的时候是 行锁
数据库本身是行锁
###>>>>>>>>> 所以第一条执行成功 第二条就会执行失败



在这里插入图片描述

 我有一条语句更新不到 影响行数为0 
我们通过乐观锁的机制来 做

我们最简单的 在update的时候 stock_count>0
就可以保证我们不会超卖

###>>>>我们采用库存数量大于0 来保证不出现超卖的情况
多线程的情况下 我们能保证只能创建10个订单
但是我发现update的时候 请求了1000次 很多都没有必要
同时很多请求对我们数据库并发压力很大 所以我们现在采用redis限制我们的人数
我们将我们的秒杀商品数量通过缓存预热的方式 放入缓存中
然后我们利用redis的incr

我们query --->update--->insert
多余的请求都在controller中被排掉了  能进入到redis中就很少了,而且访问redis 会很快
想要在多台机器下保证我们只会有一台任务来执行,利用zk的选举功能
在多台机器中选择一个leader. 由leader来执行我们的任务


利用zk的注册中心的功能,实现我们新加的机器和下线的机器  动态感知
以及故障的转移  比如说a,b,c
一般来说是B来执行的任务  我们此时B 挂了  这个时候从A和c选举  
ac 要知道b挂了 他才能发起这个选举  这样才能选出leader  利用注册中心的动态感知
###>
定时上架的job  我们要做秒杀预热

定时任务的逻辑
{1. 查询秒杀商品的信息  然后放到redis中  做缓存预热}
我们要做缓存预热
 

在这里插入图片描述

##>>>>>>>.我们这么做 最终从redis中获取
我们再多测试几次 商品列表  再去进行压测
我们的qps 就会上来的



我们一直在压测 我会检测我cpu 如果一直到达90% 以上 我就可以做限流 
发现你的程序崩溃了 或者cpu一直都是90%了 说明已经极限了
就是你的峰值

我们做压测的目的就是知道我们系统的极限能力 这样我们可以更好的设置我们的限流规则
不至于请求太多把我们的系统打崩溃了
我们一部分请求已经被限流走降级了

在这里插入图片描述

mysql 在执行update的时候 内部是有加锁的
得一条一条执行  数据库本身有行锁
我们通过version版本号得方式来控制我们得并发  不会出现修改丢失得情况


我们通过redis优化我们的项目 基于乐观锁的机制保证了我们库存不会超卖
#  并且只有10个请求最终可以进入到db中
将上架的库存存储于redis中

##>>  我们可以这样 就是 用户下完单之后再insert 到  redis中

在这里插入图片描述

我们可以用redis 减少请求  我们提前用xxjob 将秒杀商品 秒杀库存进行缓存预热
# 用redis 的incr 命令 -1 # 这样进入我们service的服务就只有10个了# 
我们后期可以使用canal  来实现你往数据库插入  我会自动的同步到redis中
可以判断是否重复下单
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值