商品秒杀接口压测及优化

news2024/11/16 22:39:13

目录

  • 一、生成测试用户
  • 二、jmeter压测
  • 三、秒杀接口优化
    • 1、优化第一步:解决超卖
    • 2、优化第二步:Redis重复抢购
    • 3、优化第三步:Redis预减库存
      • ①商品初始化
      • ②预减库存

一、生成测试用户

将UserUtils工具类导入到zmall-user模块中,运行生成测试用户信息,可根据自身电脑情况来生成用户数量。

1)必须保证zmall-user模块处于运行状态下,在进行测试用户数据生成操作;
2)注意修改UserUtils中的用户登录接口地址及端口;同时请修改用户登录接口,将生成的token令牌存入响应封装类中;

//5.通过UUID生成token令牌并保存到cookie中
String token= UUID.randomUUID().toString().replace("-","");
...
return new JsonResponseBody<>(token);

3)设置生成登录令牌存储位置;
4)修改数据库名、登录账号及密码;
5)设置生成测试用户数量;

UserUtils

package com.zking.zmall.utils;

import com.alibaba.nacos.common.utils.MD5Utils;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.zking.zmall.model.User;
import com.zking.zmall.util.JsonResponseBody;

import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

public class UserUtils {

    private static void createUser(int count) throws Exception {
        List<User> lst=new ArrayList<User>();
        //循环添加用户数据
        for(int i=0;i<count;i++){
            User user=new User();
            user.setLoginName("user"+i);
            user.setUserName("测试用户"+i);
            user.setPassword(MD5Utils.md5Hex("123456".getBytes()));
            user.setType(0);
            user.setMobile((17700000000L+i)+"");
            user.setEmail("user"+i+"@139.com");
            user.setIdentityCode((430104199912120000L+i)+"");
            lst.add(user);
        }
        System.out.println("create users");
        //获取数据库连接
        Connection conn=getConn();
        //定义SQL
        String sql="insert into zmall_user(loginName,userName,password,identityCode,email,mobile,type) values(?,?,?,?,?,?,?)";
        //执行SQL
        PreparedStatement ps=conn.prepareStatement(sql);
        //赋值
        for (User user : lst){
            ps.setString(1,user.getLoginName());
            ps.setString(2,user.getUserName());
            ps.setString(3,user.getPassword());
            ps.setString(4,user.getIdentityCode());
            ps.setString(5,user.getEmail());
            ps.setString(6,user.getMobile());
            ps.setInt(7,user.getType());
            ps.addBatch();
        }
        ps.executeBatch();
        ps.clearParameters();
        ps.close();
        conn.close();
        System.out.println("insert to db");
        //登录,生成UserTicket
        String urlString="http://localhost:8010/userLogin";
        File file=new File("C:\\Users\\Administrator\\DeskTop\\config.txt");
        if(file.exists()){
            file.delete();
        }
        RandomAccessFile accessFile=new RandomAccessFile(file,"rw");
        //设置光标位置
        accessFile.seek(0);
        for (User user : lst) {
            URL url=new URL(urlString);
            HttpURLConnection co = (HttpURLConnection) url.openConnection();
            co.setRequestMethod("POST");
            co.setDoOutput(true);
            OutputStream out=co.getOutputStream();
            String params="loginName="+user.getLoginName()+"&password=123456";
            out.write(params.getBytes());
            out.flush();
            InputStream in=co.getInputStream();
            ByteArrayOutputStream bout=new ByteArrayOutputStream();
            byte[] buffer=new byte[1024];
            int len=0;
            while((len=in.read(buffer))>=0){
                bout.write(buffer,0,len);
            }
            in.close();
            bout.close();
            String response=new String(bout.toByteArray());
            ObjectMapper mapper=new ObjectMapper();
            JsonResponseBody jsonResponseBody=mapper.readValue(response, JsonResponseBody.class);
            String token=jsonResponseBody.getData().toString();
            System.out.println("create token:"+token);
            accessFile.seek(accessFile.length());
            accessFile.write(token.getBytes());
            accessFile.write("\r\n".getBytes());
            //System.out.println("write to file:"+token);
        }
        accessFile.close();
        System.out.println("over");
    }

    private static Connection getConn() throws Exception {
        String url="jdbc:mysql://localhost:3306/zmall?useSSL=false&useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC&characterEncoding=UTF8";
        String driver="com.mysql.jdbc.Driver";
        String username="root";
        String password="123456";
        Class.forName(driver);
        return DriverManager.getConnection(url,username,password);
    }

    public static void main(String[] args) throws Exception {
        createUser(50);
    }
}

在这里插入图片描述
在这里插入图片描述

二、jmeter压测

相关配置

1.线程计划>添加>线程(用户)>线程组
在这里插入图片描述
2.线程组>添加>配置元件>http请求默认值
在这里插入图片描述
在这里插入图片描述

3.线程组>添加>取样器>http请求
在这里插入图片描述
在这里插入图片描述

4.线程组>添加>配置元件>http cookie管理器
在这里插入图片描述
在这里插入图片描述

5.线程组>添加>配置元件>CSV数据文件设置
在这里插入图片描述
在这里插入图片描述

6.线程组>添加>监听器>汇总报告
在这里插入图片描述

7.线程组>添加>监听器>查看结果树
在这里插入图片描述

8.线程组>添加>监听器>用表格查看结果
在这里插入图片描述
线程组:200个线程,1秒之内发送,循环1次。测试结果如下:吞吐量为1328/s
在这里插入图片描述
数据库中的秒杀商品表中的商品出现了库存为负数的问题。
在这里插入图片描述
订单表和订单项表中出现了秒杀商品超卖问题。
在这里插入图片描述
在这里插入图片描述

三、秒杀接口优化

1、优化第一步:解决超卖

更新秒杀商品库存的sql语句,只有当库存大于0才能更新库存;修改更新秒杀库存方法updateKillStockById的返回类型为boolean,用于判断是否更新成功。

OrderServiceImpl

@Transactional
    @Override
    public JsonResponseBody<?> createKillOrder(User user, Integer pid, Float price) {
        //1.根据秒杀商品编号获取秒杀商品库存是否为空
        //........
        
        //2.秒杀商品库存减一
        boolean update = killService.update(new UpdateWrapper<Kill>()
                .eq("item_id", pid)
                .gt("total", 0)
                .setSql("total=total-1"));

        if(!update)
            throw new BusinessException(JsonResponseStatus.STOCK_EMPTY);

        //3.生成秒杀订单及订单项
         //........

        return new JsonResponseBody();
    }

2、优化第二步:Redis重复抢购

在RedisService中新增以下两个方法,用于Redis重复抢购的判断操作。

  • 根据用户ID和秒杀商品ID为Key,将秒杀订单保存到Redis中;
  • 根据用户ID和秒杀商品ID从Redis中获取对应的秒杀商品;

RedisServiceImpl

/**
 * 将秒杀订单保存到Redis
 * @param pid    商品ID
 * @param order  秒杀订单
*/
@Override
public void setKillOrderToRedis(Integer pid, Order order) {
	redisTemplate.opsForValue().set("order:"+order.getUserId()+":"+pid,order,1800, TimeUnit.SECONDS);
}

/**
 * 根据用户ID和商品ID从Redis中获取秒杀商品,用于重复抢购判断
 * @param uid  用户ID
 * @param pid  商品ID
 * @return 返回Redis中存储的秒杀订单
*/
@Override
public Order getKillOrderByUidAndPid(Integer uid, Integer pid) {
	return (Order) redisTemplate.opsForValue().get("order:"+uid+":"+pid);
}

这里用户抢购的秒杀订单保存到Redis默认设置是1800秒,即30分钟;可视情况具体调整。

OrderServiceImpl

@Transactional
@Override
public JsonResponseBody<?> createKillOrder(User user, Integer pid) {
    ...
    /***********在库存判断是否为空之后***********/
    //6.根据秒杀商品ID和用户ID判断是否重复抢购
    Order order = redisService.getKillOrderByUidAndPid(user.getId(), pid);
    if(null!=order)
        throw new BusinessException(JsonResponseStatus.ORDER_REPART);
    /***********在根据商品ID获取商品之前***********/
    //4.秒杀商品库存减一
    boolean flag=killService.updateKillStockById(pid);
    if(!flag)
    	throw new BusinessException(JsonResponseStatus.STOCK_EMPTY);
    ...
    //生成秒杀订单等操作
    //重点,重点,重点,在此处将生成的秒杀订单保存到Redis中,用于之后的重复抢购判断
    redisService.setKillOrderToRedis(pid,order);
    
    return new JsonResponseBody<>();
}

此处第二步优化完毕,再次进行JMeter压测,并查看测试情况。

3、优化第三步:Redis预减库存

①商品初始化

将参与秒杀活动且秒杀状态、秒杀活动时间有效的商品推送到Redis中,并对秒杀商品设置超时时间。

超时时间的设定取至于活动结束时间减去活动开始时间的差值,但必须是有效活动时间,也就是当前时间在活动开始时间与结束时间范围之内。

IRedisService

/**
* 设置秒杀商品库存到Redis中
* @param pid     秒杀商品ID
* @param total   秒杀商品数量
* @param expires 秒杀商品存储过期时间
*/
void setKillTotaltoRedis(Integer pid,Integer total,long expires);

RedisServiceImpl

@Override
public void setKillTotaltoRedis(Integer pid, Integer total,long expires) {
	redisTemplate.opsForValue().set("goods:"+pid,total,expires,TimeUnit.DAYS);
}

OrderController
在zmall-order订单模块中的OrderController类上实现InitializingBean,完成秒杀商品预加载。

package com.zking.zmall.controller;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.zking.zmall.model.Kill;
import com.zking.zmall.model.Order;
import com.zking.zmall.model.User;
import com.zking.zmall.service.IOrderService;
import com.zking.zmall.service.impl.KillServiceImpl;
import com.zking.zmall.service.impl.RedisServiceImpl;
import com.zking.zmall.util.JsonResponseBody;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import java.time.Duration;
import java.time.Instant;
import java.util.Date;
import java.util.List;

@Controller
public class OrderController implements InitializingBean {
    @Autowired
    private IOrderService orderService;
    @Autowired
    private KillServiceImpl killService;
    @Autowired
    private RedisServiceImpl redisService;

    /**
     * 秒杀商品初始化
     * @throws Exception
     */
    @Override
    public void afterPropertiesSet() throws Exception {
        List<Kill> list =killService.list(new QueryWrapper<Kill>()
                //秒杀活动必须是激活状态
                .eq("is_active", 1)
                //秒杀活动结束时间必须>=当前时间,小于证明活动已结束
                .ge("end_time",new Date().toLocaleString()));
        list.forEach(kill -> {
            //计算秒杀商品存入Redis的过期时间,此处以天为单位
            Instant start = kill.getStartTime().toInstant();
            Instant end = kill.getEndTime().toInstant();
            long days = Duration.between(start, end).toDays();
            redisService.setKillTotaltoRedis(kill.getItemId(),kill.getTotal(),days);
        });
    }

    @RequestMapping("/orderUserList")
    @ResponseBody
    public List<Order> orderUserList(){
        return orderService.list(new QueryWrapper<Order>()
                .eq("userId",18));
    }

    @RequestMapping("/createOrder/{pid}/{num}")
    @ResponseBody
    public Order createOrder(@PathVariable("pid") Integer pid,
                             @PathVariable("num") Integer num){
        return orderService.createOrder(pid,num);
    }

    @RequestMapping("/createKillOrder/{pid}/{price}")
    @ResponseBody
    public JsonResponseBody<?> createKillOrder(User user,
                                               @PathVariable("pid") Integer pid,
                                               @PathVariable("price") Float price){
        return orderService.createKillOrder(user,pid,price);
    }
}

②预减库存

第一步:在RedisService中定义库存预减和递增方法。预减方法是在用户抢购商品成功后对商品进行库存预减;递增方法是在高并发情况下Redis库存预减可能会出现负数情况,通过递增方法进行库存回滚为0

IRedisService

/**
* 根据秒杀商品ID实现Redis商品库存递增
* @param pid
* @return
*/
long increment(Integer pid);

/**
* 根据秒杀商品ID实现Redis商品库存递减
* @param pid
* @return
*/
long decrement(Integer pid);

RedisServiceImpl

@Override
public long increment(Integer pid) {
	return redisTemplate.opsForValue().increment("goods:"+pid);
}

@Override
public long decrement(Integer pid) {
	return redisTemplate.opsForValue().decrement("goods:"+pid);
}

第二步:修改订单生成方法,加入Redis库存预减判断

请在Redis重复抢购判断的下面加入Redis库存预减操作。

OrderServiceImpl

package com.zking.zmall.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.zking.zmall.exception.BusinessException;
import com.zking.zmall.mapper.OrderMapper;
import com.zking.zmall.model.Kill;
import com.zking.zmall.model.Order;
import com.zking.zmall.model.OrderDetail;
import com.zking.zmall.model.User;
import com.zking.zmall.service.ApiProductService;
import com.zking.zmall.service.IOrderService;
//import io.seata.spring.annotation.GlobalTransactional;
import com.zking.zmall.util.JsonResponseBody;
import com.zking.zmall.util.JsonResponseStatus;
import com.zking.zmall.util.SnowFlake;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author xnx
 * @since 2023-02-06
 */
@Service
public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements IOrderService {
    @Autowired
    private KillServiceImpl killService;
    @Autowired
    private OrderDetailServiceImpl orderDetailService;
    @Autowired
    private ApiProductService productService;
    @Autowired
    private RedisServiceImpl redisService;

//    @Transactional
//    @Override
//    public Order createOrder(Integer pid, Integer num) {
//        //根据商品ID修改商品对应的库存
//        productService.updateStock(pid,num);
//        //新增订单
//        Order order=new Order();
//        //此处只是做模拟操作
//        this.save(order);
//        return order;
//    }

//    @GlobalTransactional
    @Transactional
    @Override
    public Order createOrder(Integer pid, Integer num) {
        //根据商品ID修改商品对应的库存
        productService.updateStock(pid,num);
        //异常模拟
        int i = 1 / 0;
        //新增订单
        Order order=new Order();
        //此处只是做模拟操作
        this.save(order);
        return order;
    }

    @Transactional
    @Override
    public JsonResponseBody<?> createKillOrder(User user, Integer pid, Float price) {
        //1.根据秒杀商品编号获取秒杀商品库存是否为空
//        Kill kill = killService.getOne(new QueryWrapper<Kill>().eq("item_id",pid));
//        if(kill.getTotal()<1)
//            throw new BusinessException(JsonResponseStatus.STOCK_EMPTY);
        //2.秒杀商品库存减一
//        killService.update(new UpdateWrapper<Kill>()
//                .eq("item_id",pid)
//                .setSql("total=total-1"));
        /***********在库存判断是否为空之后***********/
        //6.Redis库存预减
        long stock = redisService.decrement(pid);
        if(stock<0){
            redisService.increment(pid);
            throw new BusinessException(JsonResponseStatus.STOCK_EMPTY);
        }

        //5.根据秒杀商品ID和用户ID判断是否重复抢购
        Order order = redisService.getKillOrderByUidAndPid(user.getId(), pid);
        if(null!=order)
            throw new BusinessException(JsonResponseStatus.ORDER_REPART);

        //2.秒杀商品库存减一
        boolean update = killService.update(new UpdateWrapper<Kill>()
                .eq("item_id", pid)
                .gt("total", 0)
                .setSql("total=total-1"));

        if(!update)
            throw new BusinessException(JsonResponseStatus.STOCK_EMPTY);
//        boolean flag=killService.updateKillStockById(pid);
//        if(!flag)
//            throw new BusinessException(JsonResponseStatus.STOCK_EMPTY);
        //3.生成秒杀订单及订单项
        SnowFlake snowFlake=new SnowFlake(2,3);
        Long orderId=snowFlake.nextId();
        int orderIdInt = new Long(orderId).intValue();
        //创建订单
//        Order order=new Order();
        order.setUserId(user.getId());
        order.setLoginName(user.getLoginName());
        order.setCost(price);
        order.setSerialNumber(orderIdInt+"");
        this.save(order);
        //创建订单项
        OrderDetail orderDetail=new OrderDetail();
        orderDetail.setOrderId(orderIdInt);
        orderDetail.setProductId(pid);
        orderDetail.setQuantity(1);
        orderDetail.setCost(price);
        orderDetailService.save(orderDetail);
        //生成秒杀订单等操作
        //重点,重点,重点,在此处将生成的秒杀订单保存到Redis中,用于之后的重复抢购判断
        redisService.setKillOrderToRedis(pid,order);
        return new JsonResponseBody();
    }

}

第三步:还原测试数据,重新使用jmeter压测,这时可以发现明显压测效率要提升很多。

但是还是要根据不同电脑配置情况来决定,配置太低,效率也提升不了多少。

尤其是链接远程redis,会导致压测的吞吐量直线下降
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/339390.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【STM32】【HAL库】遥控关灯1主机

相关连接 【STM32】【HAL库】遥控关灯0 概述 【STM32】【HAL库】遥控关灯1主机 【STM32】【HAL库】遥控关灯2 分机 【STM32】【HAL库】遥控关灯3 遥控器 需求 主机需要以下功能: 接收来自物联网平台的命令发送RF433信号给从机接收RF433信号和红外信号驱动舵机动作 方案设计…

【计算机网络期末复习】第二章 物理层

✍个人博客&#xff1a;https://blog.csdn.net/Newin2020?spm1011.2415.3001.5343 &#x1f4e3;专栏定位&#xff1a;为想复习学校计算机网络课程的同学提供重点大纲&#xff0c;帮助大家渡过期末考~ &#x1f4da;专栏地址&#xff1a; ❤️如果有收获的话&#xff0c;欢迎点…

Verilog语法之数学函数

Verilog-2005支持一些简单的数学函数&#xff0c;其参数的数据类型只能是integer和real型。 Integer型数学函数 $clog2是一个以2为底的对数函数&#xff0c;其结果向上取整&#xff0c;返回值典型的格式&#xff1a; integer result; result $clog2(n); 最典型的应用就是通过…

数据库(三):行锁和表锁,共享锁和排他锁,数据库引擎MyISAM和InnoDB,乐观锁和悲观锁

文章目录前言零、数据库引擎一、封锁粒度二、行锁三、表锁四、数据库中的属性锁4.2. 意向锁五、乐观锁和悲观锁总结前言 之前我们提到了数据库的隔离性可能会出现的若干问题&#xff0c;以及数据库为了解决这些问题而提出来的若干种隔离级别。实际上&#xff0c;数据库底层实现…

代码随想录算法训练营第二十七天|● 39. 组合总和 ● 40.组合总和II ● 131.分割回文串

39. 组合总和 看完题后的思路 本题本质上还是一个传统排列题&#xff0c;不同之处在于每个元素可以重复选取。void f&#xff08;【】&#xff0c;startIndex&#xff0c;sum&#xff09;递归终止 if&#xff08;和target&#xff09;{ 加入&#xff1b; 返回&#xff1b; }递…

HTTPS为什么就安全了?我们做了什么?

目录 一 安全概念 二 HTTPS的安全措施 三 总结 一 安全概念 网络安全是一个比较笼统的概念。我们说网络安全的时候&#xff0c;往往会包含很多安全问题&#xff0c;包括网络设备层面、通信过程、数据本身等多方面引入的安全问题。比如设备被有意无意的破坏&#xff0c;链路…

微搭低代码从入门到精通-03 创建模型应用

我们在进行低代码开发的时候&#xff0c;第一个需要建立的概念就是应用。不管你开发的是小程序还是管理系统&#xff0c;最终的承载物就是应用。 而我们创建应用是一共可以创建两种类型的应用&#xff0c;自定义应用和模型应用。自定义应用对应着一页多端&#xff0c;你开发一…

【Hello Linux】 Linux的权限以及Shell原理

作者&#xff1a;小萌新 专栏&#xff1a;Linux 作者简介&#xff1a;大二学生 希望能和大家一起进步&#xff01; 本篇博客简介&#xff1a;介绍Linux的基础命令 Linux的权限以及Shell原理Shell的运行原理权限Linux中权限的概念如何切换用户如何提升当前操作的权限如何添加信任…

【C++:STL之栈和队列 | 模拟实现 | 优先级队列 】

目录 1. stack的介绍和使用 1.1 stack的介绍 1.2 stack的使用 2 栈的模拟实现 3 queue的介绍和使用 3.1 queue的介绍 3.2 queue的使用 4 queue的模拟实现 5 deque的介绍 5.1deque的原理介绍 5.2 deque的缺陷 5.3 为什么选择deque作为stack和queue的底层默认容器 6 p…

nodejs+vue+elementui在线求助系统vscode

目 录 摘 要 1 前 言 3 第1章 概述 4 1.1 研究背景 4 1.2 研究目的 4 1.3 研究内容 4 第二章 开发技术介绍 5 前端技术&#xff1a;nodejsvueelementui,视图层其实质就是vue页面&#xff0c;通过编写vue页面从而展示在浏览器中&#xff0c;编写完成的vue页面要能够和控制器类进…

加载sklearn covtype数据集出错 fetch_covtype() HTTPError: HTTP Error 403: Forbidden解决方案

大家好,我是爱编程的喵喵。双985硕士毕业,现担任全栈工程师一职,热衷于将数据思维应用到工作与生活中。从事机器学习以及相关的前后端开发工作。曾在阿里云、科大讯飞、CCF等比赛获得多次Top名次。喜欢通过博客创作的方式对所学的知识进行总结与归纳,不仅形成深入且独到的理…

微搭低代码从入门到精通07-基础布局组件

低码开发不同于传统开发&#xff0c;传统开发我们通常需要编写前端代码和后端代码。前端代码由HTML、CSS和JavaScript组成&#xff0c;后端代码我们通常要用后端语言比如Java来编写接口。 低码开发的特点是可视化开发&#xff0c;在编辑器中通过组件的拖拽来完成页面的编制。如…

莽村李青都看得懂的Vue响应式原理

Vue响应式原理八股文序违背老祖宗的决定将Vue响应式原理公众于世响应式数据&#xff08;Observe篇&#xff09;dom更新&#xff08;Wacther篇&#xff09;依赖收集八股文序 开篇来一段大家都会背诵的八股文。 某面试官&#xff1a; 请你简要介绍一下Vue的响应式原理。 答&am…

leaflet 读取上传的wkt文件,转换为geojson文件(示例代码056)

第056个 点击查看专栏目录 本示例的目的是介绍演示如何在vue+leaflet中上传WKT文件,解析wtk文件并转换为geojson,并在地图上显示图片。 直接复制下面的 vue+openlayers源代码,操作2分钟即可运行实现效果. 文章目录 示例效果配置方式本示例所用的a.wkt示例源代码(共139行)…

AirServer在哪下载?如何免费使用教程

苹果手机投屏到电脑mac是怎么弄&#xff1f;你知道多少&#xff1f;相信大家对苹果手机投屏到电脑mac能在电脑上操作不是很了解&#xff0c;下面就让coco玛奇朵带大家一起了解一下教程。AIrServer是一款ios投屏到mac的专用软件&#xff0c;可将iOS上的音频&#xff0c;视频&…

make的使用及Makefile万能模板

make的使用及Makefile万能模板前言为什么用makemake的使用Makefile万能模板前言 gcc 的编译&#xff0c;是将源码生成可执行程序。 例如&#xff1a; gcc hello.c -o hello源码到可执行程序需要四步处理 硬件——》机器语言——》汇编语言——》 高级语言 1&#xff09;预处理…

【openGauss实战8】Schema的图文解读

&#x1f4e2;&#x1f4e2;&#x1f4e2;&#x1f4e3;&#x1f4e3;&#x1f4e3; 哈喽&#xff01;大家好&#xff0c;我是【IT邦德】&#xff0c;江湖人称jeames007&#xff0c;10余年DBA及大数据工作经验 一位上进心十足的【大数据领域博主】&#xff01;&#x1f61c;&am…

Session与Cookie的区别(三)

中场休息 让我们先从比喻回到网络世界里&#xff0c;HTTP 是无状态的&#xff0c;所以每一个 Request 都是不相关的&#xff0c;就像是对小明来说每一位客人都是新的客人一样&#xff0c;他根本不知道谁是谁。 既然你没办法把他们关联&#xff0c;就代表状态这件事情也不存在。…

微搭低代码从入门到精通08-轮播容器

我们上一篇讲解了基础布局组件&#xff0c;讲解了普通容器和文本组件的用法&#xff0c;本篇我们继续介绍布局组件。 小程序中经常会有个功能是轮播图展示的功能&#xff0c;多张图片可以顺序进行切换。我们学习使用轮播容器的时候&#xff0c;先考虑切换的图片从哪来&#xf…

视频连载09 - 这个为生信学习和生信作图打造的开源R教程真香!!!

点击阅读原文跳转完整教案。1 思考题2 R基础2.1 R安装2.2 Rstudio基础2.2.1 Rstudio版本2.2.2 Rstudio安装2.2.3 Rstudio 使用2.3 R基本语法2.3.1 获取帮助文档&#xff0c;查看命令或函数的使用方法、事例或适用范围2.3.2 R中的变量及其初始化2.3.3 变量类型和转换2.3.4 R中矩…