IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 系统运维 -> 第三章 分布式扩展(二) -> 正文阅读

[系统运维]第三章 分布式扩展(二)

第三章 分布式扩展(二)

Nginx 高性能原因

  • epoll 多路复用
    • 采用epoll 多路复用机制完成非阻塞IO操作
  • master worker 进程模型
    • 允许进程平滑重启以及平滑的加载配置保证不断开与客户端连接,可以依赖进程模型完成对应的操作
  • 协程机制
    • 基于协程的非阻塞式编程的一套机制来完成单线程、单进程的机制,却又支持并发的编程调用接口

epoll 多路复用

了解 epoll多路复用之前,我们先要知道 bio模型以及 select模型

  • java bio 模型,阻塞进程式的模型
    • bio模型建立在java C/S基础上,客户端和服务端通过socket进行连接,一对一建立,java client只有等 socket.write() 所有字节流输出到 TCP/IP 缓冲区之后才返回,如果网络传输很慢,TCP/IP缓冲区被塞满,java client也只能等信息传输过去,缓冲区有空间后,才能写入

Untitled

  • linux select模型,变更触发轮询查询,有1024数量限制
    • select 模型监听客户端连接的socket,将所有监视的socket加入阻塞队列中,阻塞自己进程,如果有连接收到数据,就遍历整个阻塞队列,将该连接移除阻塞队列,加入工作队列中

Untitled

  • epoll 模型,变更触发回调直接读取,理论上无上限
    • epoll 模型类似于select 模型,先将自己阻塞,监听客户端连接,但为了避免select遍历队列,设置回调函数,如果连接发生变化就唤醒自己并直接执行回调函数

Untitled

详细可以看下这两篇文章:

Epoll原理解析_confirmwz的博客-CSDN博客_epoll

master worker进程模型

Untitled

  • master-worker 模型,有一个master父进程,管理多个worker子进程。master进程和worker进程是父子进程的方式,通过fork的方式创建,master进程可以管理worker内部的内存空间。
  • nginx的master 进程负责启动、停止服务、重载配置文件、平缓升级等,而worker 进程负责Client端socket的send(发送)receive(接收)
  • nginx创建master进程后,会创建一个socket文件句柄,用于listen()在对应端口上,等待客户端发起Connect连接请求(这里用到了epoll多路复用技术),当Client发起连接请求时,epoll会执行对应的回调函数。

socket建立连接三次握手:

  • 首先服务端socket先 bind(绑定) 在80端口,然后监听listen()端口
  • 客户端发起Connect请求连接到80端口,在端口上的进程执行 accecpt(接收) 方法,进行三次握手,连接建立起socket句柄
  • socket 可以实现 socket.read()socket.write() 的方法,这个过程就对应了request的read和response的write。

相关问题

问题1:当请求端口连接时,worker进程负责执行accept方法,那么有多个worker进程,哪一个会执行 ?

  • 这里nginx采用的是锁竞争的原理,互斥体,哪个进程先获得锁,就优先执行。

问题2:执行 sbin/nginx -s reload 语句是无缝重启,不会断开连接,原理是什么 ?

Untitled

  • sbin/nginx -s reload 执行前后master进程不变,worker进程改变。
  • 执行语句后,master进程发送信号给对应worker进程,要求把所有socket句柄交给master进程管理。
  • master进程会重新加载配置文件,启动一个新的worker进程,并且master进程会将这些对应的socket句柄交给新的worker进程来管理(内存操作很快),这样就不用再进行客户端和nginx重连接

问题3:为什么worker进程是单线程,不采用多线程呢 ?

  • 单线程不涉及线程间的上下文切换和通信,不会造成阻塞,更高效,而因为与client端有epoll多路复用,实现了并发管理。
  • 通俗来讲,如果你的线程内部是没有任何的阻塞操作的,那对应的一个单线程的调用是会比多线程更快的!!!

协程机制

  • 依附于线程的内存模型,切换开销小。
    • 一个线程可以有多个协程,不需要cpu切换的开销,只要内存切换的开销。
  • 遇阻塞及时归还执行权,代码同步。
    • 如果协程遇到阻塞了,线程会立刻回收其执行权,然后立马调用一个不阻塞的协程去运行。
  • 无需加锁。

分布式会话管理

什么是会话 ?

  • 会话简单理解就是 用户打开浏览器,点击很多超链接,访问多个服务器资源,然后关闭浏览器,整个过程就称之为一个会话。

有哪些方式可以保存会话 ?

  • 基于cookie的管理方式。实现会话的方式如下:
    • 用户发起登录请求,服务端根据传入的用户名密码之类的信息,验证用户,然后创建一个登陆凭证(包含Id之类的信息,还有过期时间,创建时间)。
    • 服务端对登陆凭证做数字签名,对称加密,加密后写入cookie。cookie名称必须固定(比如ticket),后面再获取时还得根据名字来获取cookie值。
    • 用户下次登陆时,服务端根据上一次登陆凭证的cookie名字找到相关的cookie值,然后做解密处理再做数字签名的认证,验证是否过期等等。
  • 基于token的管理方式。实现会话的方式如下:
    • 基于token的方式和基于cookie类似,区别是token返回给客户端之后,后面每次请求,都会主动把token加到http header里或者url中,对token进行验证

两种目前流行的session实现方式:

  • 基于cookie传输sessionid:java tomcat容器session实现

    • 目前我们项目使用的是 SpringBoot内嵌tomcat容器里的 HTTPsession 机制,是基于cookie传输sessionid的一个机制,用来标识一个用户会话项目中用到的地方是登陆/注册功能:

    Untitled

    Untitled

    Untitled

  • 基于token传输类似sessionid:java代码session实现

    • 有些在移动端开发过程中,是不适合用cookie开发的

但以上两种方式并不适合分布式会话,因为用于用户登录的sessionId相关的数据都是封装在项目内嵌的tomcat容器当中,因为分布式系统的原因会导致每次请求都不是在同一个服务器中,从而导致获取不到对应的sessionId

Untitled

所以需要改变目前的状况:

  • 基于cookie传输sessionid:java tomcat容器session实现迁移到 redis
  • 基于token传输类似sessionid:java代码session实现迁移到 redis

分布式会话实现(一)

  • pom文件添加以下两个依赖
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.session</groupId>
    <artifactId>spring-session-data-redis</artifactId>
</dependency>

redis 安装部署

  • 首先下载好redis 安装包
  • 赋予压缩包权限 chmod -R 777 redis-.tar.gz
  • 执行tar -zxvf redis.tar.gz解压缩

Untitled

  • cd redis解压好的目录

Untitled

  • 执行 make?编译 redis,不成功的话执行 yum install gcc
  • 最后执行make install 命令安装 redis
  • 启动 redis 服务器 redis-server /usr/local/redis-6.2.6/etc/redis.conf
  • 启动 redis 客户端 redis-cli -p 6379

分布式会话实现(二)

  • 在配置 application.yml 中添加对redis的依赖:
spring:
	# 配置springboot对redis的依赖
  redis:
    host: 43.138.131.175
    port: 6379
    # redis默认有15个数据库,这里指定用第10个
    database: 10
    # 设置jedis连接池
    jedis:
      pool:
        max-active: 50 # 最大连接数
        min-idle: 20 # 最小连接数
  • 更改前端 gethost.js 中host:
var g_host = "localhost:80";
  • 测试登录界面,将UserModel模型实现序列化接口,这样session的信息才能写入redis数据库中
  • 也就是说,要存入redis的数据,都得实现 Serializable 序列化

Untitled

  • 使用 keys * 命令查看redis中的数据,发现session数据已经被存到里面

Untitled

重新打包部署到服务器上

  • 将打好的jar包上传到服务器的 //var/www/miaosha 目录上
  • kill 掉正在运行的java程序
  • 将重新打好的jar包替换掉原来的 miaosha.jar

Untitled

  • 给当前jar包赋予读写权限:chmod -R 777 miaosha.jar
  • 后台启动java程序:./deploy.sh &

分布式会话实现(三)

  • 修改redis配置文件 redis.conf,指定redis的访问:添加bind redis数据库的内网IP地址

Untitled

Untitled

  • 后台方式启动redis : redis-server /usr/local/redis-6.2.6/etc/redis.conf
  • 查看6379端口情况 netstat -an | grep 6379 地址正常即成功

Untitled

  • 切换到应用服务器1,修改配置文件 application.properties

Untitled

  • 增加一句 spring.redis.host = redis服务器内网IP

Untitled

  • 重新部署jar包,成功
  • 检查6379端口,出现下面的信息即为成功

Untitled

  • 测试打开登陆界面,登陆用户后下单,因为是同一个登陆用户的session,所以可重复下单

总结

  • 应用服务器都要安装redis,然后修改应用程序的配置文件,将redis的ip地址设置为另一台redis服务器的内网ip地址
  • redis服务器也需要安装redis,并将自己的可被访问ip地址设置为自己的内网地址
  • 这样,应用服务器的应用程序只能通过redis服务器的内网ip地址才能访问到redis服务器上的redis服务端

基于token的分布式会话实现(一)

  • 上面我们完成了基于cookie传输sessionid的实现,现在来做基于token的分布式会话的实现。
  • token类似于服务端下发sessionid植入到cookie,下发令牌,交给前端,等登陆请求时,把token加入到http header或url中。

操作步骤

  • 修改后端页面 UserController.java
		@Autowired
    private RedisTemplate redisTemplate;		

		// 用户登录接口
    @RequestMapping(value = "/login", method = {RequestMethod.POST}, consumes = {CONTENT_TYPE_FORMED})
    @ResponseBody
    public CommonReturnType login(@RequestParam(name = "telphone") String telphone, @RequestParam(name = "password") String password) throws BusinessException, UnsupportedEncodingException, NoSuchAlgorithmException {
        // 入参校验
        if (org.apache.commons.lang3.StringUtils.isEmpty(telphone) || org.apache.commons.lang3.StringUtils.isEmpty(password)) {
            throw new BusinessException(EmBusinessError.PARAMETER_VALIDATION_ERROR);
        }
        // 用户登录服务,用来校验用户登录是否合法
        // 手机号+用户加密后的密码
        UserModel userModel = userService.validateLogin(telphone, this.EncodeByMd5(password));

        // 将登陆凭证加入到用户登录成功的session内

        // 修改成若用户登陆验证成功,将对应的登陆信息和登陆凭证一起存入redis中
        // 生成登录凭证token,UUID
        String uuidToken = UUID.randomUUID().toString();
				// 例如:5dcac225-b98b-4618-b09d-533440b936e7 将token的"-"给去掉
        uuidToken = uuidToken.replace("-", "");

        // this.httpServletRequest.getSession().setAttribute("IS_LOGIN", true);
        // this.httpServletRequest.getSession().setAttribute("LOGIN_USER", userModel);

        // 建立token和用户登录态之间的联系
        redisTemplate.opsForValue().set(uuidToken, userModel); //上面两行替换成这行代码
        // 设置uuidToken过期时间为1小时
        redisTemplate.expire(uuidToken, 1, TimeUnit.HOURS);

        // 下发token
        return CommonReturnType.create(uuidToken);
    }
  • 修改前端页面 login.html

Untitled

  • 修改前端页面 getitem.html

Untitled

  • 修改后端页面 OrderController.java
		@Autowired
    private RedisTemplate redisTemplate;

    //封装下单请求
    @RequestMapping(value = "/createorder", method = {RequestMethod.POST}, consumes = {CONTENT_TYPE_FORMED})
    @ResponseBody
    public CommonReturnType createOrder(@RequestParam(name = "itemId") Integer itemId, //商品id
                                        //秒杀活动id,required = false表示如果秒杀活动还没开始,该参数就会自动隐藏
                                        @RequestParam(name = "promoId", required = false) Integer promoId,
                                        //商品购买数量
                                        @RequestParam(name = "amount") Integer amount) throws BusinessException {

        //Boolean isLogin = (Boolean) httpServletRequest.getSession().getAttribute("IS_LOGIN");
        //UserModel userModel = (UserModel) httpServletRequest.getSession().getAttribute("LOGIN_USER");

        // 上面注释的代码替换成下面的操作
        String token = httpServletRequest.getParameterMap().get("token")[0];
        if (StringUtils.isEmpty(token)) {
            throw new BusinessException(EmBusinessError.USER_NOT_LOGIN, "用户还未登录,不能下单");
        }
        // 获取用户的登录信息
        UserModel userModel = (UserModel) redisTemplate.opsForValue().get(token);
        if (userModel == null) {
            throw new BusinessException(EmBusinessError.USER_NOT_LOGIN, "用户还未登录,不能下单");
        }

        OrderModel orderModel = orderService.createOrder(userModel.getId(), itemId, promoId, amount);

        return CommonReturnType.create(null);
    }
  • 点击下单,请求url中出现token,说明session加入到了token中

Untitled

  系统运维 最新文章
配置小型公司网络WLAN基本业务(AC通过三层
如何在交付运维过程中建立风险底线意识,提
快速传输大文件,怎么通过网络传大文件给对
从游戏服务端角度分析移动同步(状态同步)
MySQL使用MyCat实现分库分表
如何用DWDM射频光纤技术实现200公里外的站点
国内顺畅下载k8s.gcr.io的镜像
自动化测试appium
ctfshow ssrf
Linux操作系统学习之实用指令(Centos7/8均
上一篇文章      下一篇文章      查看所有文章
加:2022-04-28 12:14:41  更:2022-04-28 12:15:03 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/15 19:58:35-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码