Redis实现分布式锁原理和Redisson框架实现分布式锁,全网最详细讲解

声明:我的大部分篇幅都讲的分布式锁的原理和实现,如果想直接用Redisson框架实现分布式锁,可以直接翻至最后面

关于分布式锁,适用于并发量特别大的微服务集群,能做到同步的实现资源的获取

我其实没有经过真实项目的分布式锁的实践,以下的作为我学习的参考,但据我了解一般使用redis作为分布式锁的公司具体实现也如我下述的redisson框架,只要不是像淘宝、京东那样并发量特别高的项目都基本适用,如果以后有机会使用到了分布式锁的应该场景我也会更新本文

我会从分布式锁的原理解析、代码、框架一一解析,本文解析代码部分仅供参考

需要用到的知识点(必须会用

一、JMeter用于做压力测试: jmeter安装与使用,全图文讲解

二、Nginx用于做负载均衡(多服务):Windows安装Nginx并配置负载均衡

三、Redis用于存储Mock数据:Windows安装Redis做到双击启动

四、启动两个相同服务但端口不同的项目:idea实现同时启动两个相同服务但不同端口的项目,全图解

五、redis工具类:Redis工具类(redisTemplate)以及 redisTemplate 的用法

图解分布式锁(秒杀场景)

图解为什么需要用到分布式锁(我都忘记画返回用户的数据操作了,但不影响,知道最后需要返还给用户消息就可以了)

场景一、单服务不需要分布式锁(为了省事,我就只画两个用户)

1、不加锁就会造成数据的脏读

在这里插入图片描述

2、使用synchronized实现加锁处理

在这里插入图片描述

场景二、集群(分布式锁实现)(多个相同服务但端口不同的项目,经过负载均衡到不同的服务)(为了省事,我就只画两个服务)

1、只在服务层面加锁是达不到效果的

在这里插入图片描述

2、需要用到redis的setnx请求

原理

redis> SETNX mykey "Hello"
(integer) 1
redis> SETNX mykey "World"
(integer) 0
redis> GET mykey
"Hello"

如上面代码所示setnx请求是指,先查找查找是否有mykey这个key,如果没有则放入一个Hellovalue并返回 1(就是表名插入成功),如果已经存在mykey这个key则返回 0(表示插入失败)

由于redis是单线程的所有在redis方法会让进来的请求进行排队,下面用户一的请求比用户二的请求快一丢丢访问redis,实现分布式锁

  • 用户一在服务一使用setnx这个方法插入一个key返回成功,并表示用户一拿到分布式锁

  • 同一时间用户二在服务二使用setnx这个方法插入一个相同的key,这时提示插入失败,返回失败,然后服务二就自旋拿锁或者直接返回用户稍后重试(直接返回用户不友好)

  • 之后用户一处理完请求,修改redis数据,最后把key删除掉,这样其他的服务就可以拿到锁了,就可以使用setnx命令尝试加锁,拿到锁后操作redis的数据,拿不到锁的执行上一个步骤(这里不是有三个点吗,就是执行第二个点后面的内容)

在这里插入图片描述

代码解析分布式锁原理(秒杀场景)

初始化Redis数据(用于存储秒杀使用的商品)

1、使用redis配置一个用于秒杀服务的mock数据,我这里设置key为goods,value为50,每次使用完数据,需要重新让goods数据变为50(工具:Another Redis Desktop Manager)

在这里插入图片描述

场景一、单服务下的秒杀实现(加锁)(不需要分布式锁)(对应图解场景一)

SpringBoot作为实现分布式的基本框架,只跑单个服务的时候,使用的是一个jvm来控制代码。我们假设一个秒杀的项目实现:

1、写一个接口用于测试秒杀

@RestController
public class DemoController {
    @Autowired
    private RedisUtil redisUtil;

    @RequestMapping("/demo")
    public String demo(){
        try {
//            取goods的值
            Integer goods = (Integer) redisUtil.get("goods");
            if (goods <= 0){
                System.err.println(Thread.currentThread().getName() +
                        Thread.currentThread().getId() + "商品已经取完");
                return "商品已经取完";
            }
//            这里模拟一下延时 0.1秒 (因为数据量太少,这样可以很直观的看出加锁和不加锁的区别)
            Thread.sleep(100);
//            用户拿到了这个商品,所以这个商品需要自减一
//            使用System.err输出一下数据,这样显示控制台输出是红色比较好观察
            System.err.println(Thread.currentThread().getName() +
                    Thread.currentThread().getId() + "取出了第" + goods-- + "商品");
//            由于用户已经取到了商品,所以redis中的数据也需要更新
            redisUtil.set("goods",goods);
        } catch (InterruptedException e) {
            return "错误";
        }
        return "你已经成功获取商品";
    }
}

2、配置JMeter

一瞬间有20个用户去抢goods这个商品

在这里插入图片描述

在这里插入图片描述

3、测试

按理说,每个用户只抢一个商品,那么会剩余30个商品,我们来看看下述情况,发现全部的用户都拿到的是第50这个数据,所以减1操作,后都在redis中存储的是49,就造成了数据的脏读,而修改此处代码非常的简单

控制台:

在这里插入图片描述

redis:

在这里插入图片描述

4、解决单服务下的脏读问题(加锁),记得修改redis中的goods数据至50,还是请求20次

只需要修改代码,加入锁就可以了,这样就实现了基于jvm层面的加锁了

@RestController
public class DemoController {
    @Autowired
    private RedisUtil redisUtil;

    @RequestMapping("/demo")
    public String demo(){
        try {
//            加锁
            synchronized (this){

//            取goods的值
                Integer goods = (Integer) redisUtil.get("goods");
                if (goods <= 0){
                    System.err.println(Thread.currentThread().getName() +
                            Thread.currentThread().getId() + "商品已经取完");
                    return "商品已经取完";
                }
//            这里模拟一下延时 0.1秒 (因为数据量太少,这样可以很直观的看出加锁和不加锁的区别)
                Thread.sleep(100);
//            用户拿到了这个商品,所以这个商品需要自减一
//            使用System.err输出一下数据,这样显示控制台输出是红色比较好观察
                System.err.println(Thread.currentThread().getName() +
                        Thread.currentThread().getId() + "取出了第" + goods-- + "商品");
//            由于用户已经取到了商品,所以redis中的数据也需要更新
                redisUtil.set("goods",goods);
            }
        } catch (InterruptedException e) {
            return "错误";
        }
        return "你已经成功获取商品";
    }
}

5、测试

控制台:

在这里插入图片描述

redis:

在这里插入图片描述

场景二、集群下的秒杀实现(分布式锁实现)(对应图解场景二)

1、启动Nginx,配置负载均衡,并启动两个服务(8080端口、9090端口),测试 场景一 4 的代码会出现怎样的错误,记得修改redis中的goods数据至50(以下操作不知道怎么处理的,可以看需要用到的知识点)

Nginx配置(nginx.conf),并启动:

worker_processes  1;
events {
    worker_connections  1024;
}
http {
    include       mime.types;
    default_type  application/octet-stream;
    sendfile        on;
    keepalive_timeout  65;
	# 负载均衡配置访问路径 serverList名字随便取
	upstream serverList{
	   # 这个是tomcat的访问路径
	   server localhost:8080;
	   server localhost:9090;
	}
    server {
        listen       80;
        server_name  localhost;
        location / {
            root   html;
			proxy_pass http://serverList;
            index  index.html index.htm;
        }
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
    }
}

重新配置JMeter:

让两个服务分别处理25个请求

在这里插入图片描述

在这里插入图片描述

两个项目启动,并清空控制台:

在这里插入图片描述

2、测试

后面有很多两个项目取出了相同的商品,我这里就不拉开展示了,知道有问题就好了

8080端口:

在这里插入图片描述

9090端口:

在这里插入图片描述

redis:并不是我们想象的0,所以使用单个jvm下面的加锁是行不通的

在这里插入图片描述

3、修改代码,实现加redis分布式锁

@RestController
public class DemoController {
    @Autowired
    private RedisUtil redisUtil;

    @RequestMapping("/demo")
    public String demo(){
//        设置自旋超时时间
        long timeoutAt = System.currentTimeMillis();
        try {
//            自旋
            while (true){
                long now = System.currentTimeMillis();
//                5秒超时,退出
                if (now - timeoutAt > 5000){
                    System.out.println("连接超时请重试");
                    return "连接超时请重试";
                }
//               加锁
                boolean lock = redisUtil.setnx("lock", "先随便输入什么东西都可以啦~");
                if (!lock){
                    continue;
                }

//               取goods的值
                Integer goods = (Integer) redisUtil.get("goods");
                if (goods <= 0){
                    System.err.println(Thread.currentThread().getName() +
                            Thread.currentThread().getId() + "商品已经取完");
                    return "商品已经取完";
                }
//               这里加了锁就不需要
//                Thread.sleep(100);

//               用户拿到了这个商品,所以这个商品需要自减一
//               使用System.err输出一下数据,这样显示控制台输出是红色比较好观察
                System.err.println(Thread.currentThread().getName() +
                        Thread.currentThread().getId() + "取出了第" + goods-- + "商品");
//               由于用户已经取到了商品,所以redis中的数据也需要更新
                redisUtil.set("goods",goods);
//            解锁
                redisUtil.delete("lock");
                return "你已经成功获取商品";
            }
        } catch (Exception e) {
            return "错误";
        }
    }
}

4、测试,记得修改redis中的goods数据至50

8080端口:

在这里插入图片描述

9090端口:

在这里插入图片描述

redis:

在这里插入图片描述

一个redis分布式锁的基础已经完成了,但是这样实现的分布式锁有很多的问题,在高并发的条件下根本不够看,下面是进阶教学(以下方案均经过测试)

问题一、在解锁之前出现异常,导致不能解锁,那么其他的服务都不可以访问redis

在这里插入图片描述

解决:修改解锁的代码至finally代码块中

@RestController
public class DemoController {
    @Autowired
    private RedisUtil redisUtil;

    @RequestMapping("/demo")
    public String demo(){
//        设置自旋超时时间
        long timeoutAt = System.currentTimeMillis();
//            自旋
        while (true){
                long now = System.currentTimeMillis();
//                5秒超时,退出
                if (now - timeoutAt > 5000){
                    System.out.println("连接超时请重试");
                    return "连接超时请重试";
                }
//               加锁
                boolean lock = redisUtil.setnx("lock", "先随便输入什么东西都可以啦~");
                if (!lock){
                    continue;
                }

            try {
//               取goods的值
                Integer goods = (Integer) redisUtil.get("goods");
                if (goods <= 0){
                    System.err.println(Thread.currentThread().getName() +
                            Thread.currentThread().getId() + "商品已经取完");
                    return "商品已经取完";
                }

//               用户拿到了这个商品,所以这个商品需要自减一
//               使用System.err输出一下数据,这样显示控制台输出是红色比较好观察
                System.err.println(Thread.currentThread().getName() +
                        Thread.currentThread().getId() + "取出了第" + goods-- + "商品");
//               由于用户已经取到了商品,所以redis中的数据也需要更新
                redisUtil.set("goods",goods);
                return "你已经成功获取商品";
            } catch (Exception e) {
                e.printStackTrace();
                return "请重试";
            } finally {
//            解锁
                redisUtil.delete("lock");
            }
        }
    }
}

问题二、如果在执行到解锁之前,服务直接挂掉了,那么其他的服务都不可以访问redis

解决:设置缓存时间(10s)

@RestController
public class DemoController {
    @Autowired
    private RedisUtil redisUtil;

    @RequestMapping("/demo")
    public String demo(){
//        设置自旋超时时间
        long timeoutAt = System.currentTimeMillis();
//            自旋
        while (true){
                long now = System.currentTimeMillis();
//                5秒超时,退出
                if (now - timeoutAt > 5000){
                    System.out.println("连接超时请重试");
                    return "连接超时请重试";
                }
//               加锁,并设置缓存时间 10秒
                boolean lock = redisUtil.setnx("lock", "先随便输入什么东西都可以啦~",10, TimeUnit.SECONDS);
                if (!lock){
                    continue;
                }

            try {
//               取goods的值
                Integer goods = (Integer) redisUtil.get("goods");
                if (goods <= 0){
                    System.err.println(Thread.currentThread().getName() +
                            Thread.currentThread().getId() + "商品已经取完");
                    return "商品已经取完";
                }

//               用户拿到了这个商品,所以这个商品需要自减一
//               使用System.err输出一下数据,这样显示控制台输出是红色比较好观察
                System.err.println(Thread.currentThread().getName() +
                        Thread.currentThread().getId() + "取出了第" + goods-- + "商品");
//               由于用户已经取到了商品,所以redis中的数据也需要更新
                redisUtil.set("goods",goods);
                return "你已经成功获取商品";
            } catch (Exception e) {
                e.printStackTrace();
                return "请重试";
            } finally {
//            解锁
                redisUtil.delete("lock");
            }
        }
    }
}

问题三、假如第一个服务跑了11秒,但10秒后,然后锁的时间到了,那么第二个服务就可以拿到锁并访问redis了,这样在第二个服务期间,第一个服务完成了,那么第一个服务会释放掉第二个服务的锁,这样就导致了锁的失效问题

解决一:设置一个唯一标识(uuid),删除的时候判断是不是该请求设置的锁就可以了,但是,这里有个问题,就是10秒后,虽然第一个服务不可以删除除自己以外的锁,但是有其他的服务拿到这把锁进行redis操作,如果第一个服务在第二个服务修改redis数据之后再去修改redis,那么第三个服务拿到redis的数据就是有问题的,这样也会导致锁失效问题

@RestController
public class DemoController {
    @Autowired
    private RedisUtil redisUtil;

    @RequestMapping("/demo")
    public String demo(){
//        设置自旋超时时间
        long timeoutAt = System.currentTimeMillis();
//        设置唯一标识
        String uuid = UUID.randomUUID().toString();
//            自旋
        while (true){
                long now = System.currentTimeMillis();
//                5秒超时,退出
                if (now - timeoutAt > 5000){
                    System.out.println("连接超时请重试");
                    return "连接超时请重试";
                }
//               加锁,设置超时时间和唯一标识
                boolean lock = redisUtil.setnx("lock", uuid,10, TimeUnit.SECONDS);
                if (!lock){
                    continue;
                }

            try {
//               取goods的值
                Integer goods = (Integer) redisUtil.get("goods");
                if (goods <= 0){
                    System.err.println(Thread.currentThread().getName() +
                            Thread.currentThread().getId() + "商品已经取完");
                    return "商品已经取完";
                }

//               用户拿到了这个商品,所以这个商品需要自减一
//               使用System.err输出一下数据,这样显示控制台输出是红色比较好观察
                System.err.println(Thread.currentThread().getName() +
                        Thread.currentThread().getId() + "取出了第" + goods-- + "商品");
//               由于用户已经取到了商品,所以redis中的数据也需要更新
                redisUtil.set("goods",goods);
                return "你已经成功获取商品";
            } catch (Exception e) {
                e.printStackTrace();
                return "请重试";
            } finally {
                if (uuid.equals(redisUtil.get("lock"))){
//            解锁
                    redisUtil.delete("lock");
                }
            }
        }
    }
}

解决二:设置心跳检测,可以去看看这篇Redis分布式锁如何解决锁超时问题?

问题四、串行太慢了怎么办

解决:拆分:使用不同的锁和不同的关键字,比如goods为50个商品,可以拆分为:goods_1:10,goods_2:10,goods_3:10,goods_4:10,goods_5:10

问题五、使用Redis集群时,主节点挂了,而子节点刚好没有同步到刚刚上传的key,导致锁失效

解决:使用zookeeper实现分布式锁

等等等等。。。。。。

使用Redisson框架实现分布式锁

Redisson原理图

在这里插入图片描述

加入jar包

<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson</artifactId>
    <version>3.20.0</version>
</dependency>

配置Bean

@Configuration
public class RedissonConfig {

    @Bean
    public RedissonClient redisson(){
        // 配置
        Config config = new Config();
        config.useSingleServer().setAddress("redis://localhost:6379").setDatabase(0).setPassword("123456");
        // 创建RedissonClient对象
        return Redisson.create(config);
    }
}

测试(别看代码少,其实这个是经过多年实战,很有保障的)

@RestController
public class DemoController {
    @Autowired
    private RedisUtil redisUtil;
    @Resource
    private Redisson redisson;

    @RequestMapping("/test")
    public String Test(){
        RLock lock = redisson.getLock("lock");
//        设置超时时间30秒
        lock.lock(30,TimeUnit.SECONDS);
        try {
//               取goods的值
            Integer goods = (Integer) redisUtil.get("goods");
            if (goods <= 0){
                System.err.println(Thread.currentThread().getName() +
                        Thread.currentThread().getId() + "商品已经取完");
                return "商品已经取完";
            }

//               用户拿到了这个商品,所以这个商品需要自减一
//               使用System.err输出一下数据,这样显示控制台输出是红色比较好观察
            System.err.println(Thread.currentThread().getName() +
                    Thread.currentThread().getId() + "取出了第" + goods-- + "商品");
//               由于用户已经取到了商品,所以redis中的数据也需要更新
            redisUtil.set("goods",goods);
            return "你已经成功获取商品";
        } catch (Exception e) {
            e.printStackTrace();
            return "请重试";
        }finally {
            lock.unlock();
        }

    }
}

8080端口:

在这里插入图片描述

9090端口:

在这里插入图片描述

redis:

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【ROS2指南-1】配置ROS2环境

资料来源Configuring your ROS 2 environment — ROS 2 Documentation: Dashing documentationhttp://docs.ros.org/en/dashing/Tutorials/Configuring-ROS2-Environment.html 目标&#xff1a;本教程将向您展示如何准备 ROS 2 环境。 教程级别&#xff1a;初学者 时间&…

MyBatis(十一)、MyBatis查询语句专题

准备工作&#xff1a; 模块名&#xff1a;mybatis-007-select 打包方式&#xff1a;jar 引入依赖&#xff1a;mysql驱动依赖、mybatis依赖、logback依赖、junit依赖。 引入配置文件&#xff1a;jdbc.properties、mybatis-config.xml、logback.xml 创建pojo类&#xff1a;Car 创…

【华为机试真题详解JAVA实现】—从单向链表中删除指定值的节点

目录 一、题目描述 二、解题代码 一、题目描述 输入一个单向链表和一个节点的值,从单向链表中删除等于该值的节点,删除后如果链表中无节点则返回空指针。 链表的值不能重复。 构造过程,例如输入一行数据为: 6 2 1 2 3 2 5 1 4 5 7 2 2 则第一个参数6表示输入总共6个节点,…

AI又进化了,声音克隆革命性突破

大家好&#xff0c;我是 Jack。 因 ChatGPT、Stable Diffusion 让 AI 在文本、图像生成领域火出了圈。 但 AI 在生成方面的能力&#xff0c;可不仅如此&#xff0c;音频领域也出现了很多优秀的项目。 我用我本人的音频数据&#xff0c;训练了一个 AI 模型&#xff0c;生成了…

数据结构系列17——lambda表达式

目录 1. 基本概念 2. 基本语法 3. 函数式接口 4. Lambda表达式的基本使用 4.1 语法精简 5. 变量捕获 6. Lambda在集合当中的使用 1. 基本概念 Lambda表达式是Java SE 8中一个重要的新特性。lambda表达式允许你通过表达式来代替功能接口。 lambda表达式就和方法一样,它提供了一…

循环依赖详解及解决方案

介绍 上图就是循环依赖的三种情况,虽然方式不同,但是循环依赖的本质是一样的,就A的完整创建要依赖与B,B的完整创建要依赖于A,相互依赖导致没办法完整创建造成失败. 循环依赖代码演示 public class Demo {public static void main(String[] args) {new Demo1();} }class Demo1…

打造出ChatGPT的,是怎样一群人?

震惊世界的ChatGPT&#xff0c;要多少人才能开发出来&#xff1f;几百&#xff0c;还是几千&#xff1f; 答案是&#xff1a;87个人。 老实说&#xff0c;刚看到这个数字真是惊到我了&#xff0c;印象里&#xff0c;之前看媒体报道各大巨头人工智能人才储备时&#xff0c;动辄…

长草

4 5 .g… … …g… … 2 gggg. gggg. ggggg .ggg. #include <iostream> using namespace std;int r,l;char arr[1005][1005];int t;char dp[1005][1005]; int dx[4]{0,0,1,-1},dy[4]{1,-1,0,0}; void dfs(int x,int y) {for(int i0;i<4;i){int axdx[i];int bydy[i];if…

Qt Quick - 分隔器综述

Qt Quick - 分隔器综述一、概述二、MenuSeparator 控件1. 用法&#xff1a;三、ToolSeparator 控件1. 用法一、概述 Qt Quick Controls 提供了多种分隔符&#xff0c;其实就是分割一下MenuBar和ToolBar里面的内容。 控件功能MenuSeparator将菜单中的一组项目与相邻项目分开To…

dolphinscheduler资源中心

资源中心 资源中心介绍 资源中心提供文件管理&#xff0c;UDF管理&#xff0c;任务组管理。 文件管理可以访问要执行的hive的sql文件 UDF管理可以放置fllink执行的自定义udf函数jar包&#xff0c;hive自定义的UDF函数jar包 以上的*.sql,*.jar文件可以理解为资源&#xff0c…

【服务器数据恢复】 重装系统导致xfs文件系统分区丢失的数据恢复案例

服务器数据恢复环境&#xff1a; EMC某型号存储&#xff0c;20块磁盘组建raid5磁盘阵列&#xff0c;划分2个lun。 服务器故障&#xff1a; 管理员执行重装系统操作后发现分区发生改变&#xff0c;原先的sdc3分区丢失&#xff0c;该分区采用xfs文件系统&#xff0c;存储了公司重…

红队内网靶场

文章目录开篇介绍靶场介绍靶场下载以及配置Tomcat Get Shell突破DMZ防火墙拿下域内成员机器将内网机器上线到CS使用Adfind侦察子域信息控制子域DCRadmin登录子域进行权限维持(白银票据/ACL)子域bloodhound获取父域信息分析子域Krbtgt密钥创建跨域金票Dcsync父域PTH父域DC准备打…

什么是 三维渲染内核?

一、引言 随着计算机图形学的发展&#xff0c;三维图形已经成为 电子游戏、动画电影 和 可视化、数字孪生等领域的关键技术。为了将三维模型转换成二维图像&#xff0c;我们需要依赖一个称为三维渲染内核的工具。本文将详细介绍三维渲染内核的原理、实现方法和应用&#xff0c…

每日做题总结——day01

目录 选择题 for循环 指针数组 位段 getchar 大小端存储 进制与格式控制符 位运算 数组指针 二维数组的存储 计算二进制中1的个数 斐波那契数列求递归次数 编程题 删除公共字符 排序子序列 倒置字符串 选择题 for循环 解析&#xff1a;该题主要看for…

面试题React

1.React Fiber是什么&#xff1f; 在 React V16 将调度算法进行了重构&#xff0c; 将之前的 stack reconciler 重构成新版的 fiber reconciler&#xff0c;变成了具有链表和指针的 单链表树遍历算法。通过指针映射&#xff0c;每个单元都记录着遍历当下的上一步与下一步&…

【从零开始学Skynet】工具篇(二):虚拟机文件的复制粘贴

大家在Linux系统下开发的时候肯定会遇到虚拟机与主机间无法复制粘贴的问题&#xff0c;现在我们就来解决这样的问题&#xff0c;方便我们的开发。 1、打开设置 我们可以系统界面的菜单栏点击“控制”&#xff0c;然后打开“设置”&#xff1b; 也可以在VirtualBox界面打开“设…

项目管理中,这些思维误区一定要避开

项目需要在限定的时间要求完成的事情&#xff0c;可控的关键把握是&#xff1a;人、时、事。 但是&#xff0c;项目实施时间一般较长&#xff0c;总有很多项目实施结果不尽人意。那么&#xff0c;IT项目管理过程中&#xff0c;容易出现哪些思维误区呢&#xff1f; 1、忘记项…

TCP三次握手四次挥手及time_wait状态解析

TCP的建立——三次握手 1.服务器必须准备好接受外来的连接。通常通过调用socket&#xff0c;bind&#xff0c;listen这三个函数来完成&#xff0c;我们称之为被动打开(passive open)。 2. 客户端通过调用connect函数发起主动的打开(active open)。这导致客户TCP发送一个SYN(同步…

Nginx基础教程

Nginx 目标 Nginx简介【了解】 Nginx安装配置【掌握】 一、Nginx简介 Nginx称为:负载均衡器或 静态资源服务器:html,css,js,img ​ Nginx(发音为“engine X”)是俄罗斯人编写的十分轻量级的HTTP服务器,是一个高性能的HTTP和反向代理服务器&#xff0c;同时也是一个IMAP/P…

初探MyBatis实现简单查询

文章目录一、创建数据库与表1、创建数据库2、创建用户表3、添加表记录二、基于配置文件方式使用MyBatis1、创建Maven项目2、添加相关依赖3、创建用户实体类4、创建用户映射器配置文件5、创建MyBatis配置文件6、创建日志属性文件7、测试用户操作1)创建用户操作测试类2)测试按编号…