前后端分离
参考🔗
1、前后端未分离架构模式
前后端未分离时代,页面逻辑处理以及页面渲染全部由后端完成。如最具代表性的MVC三层框架。用户发起请求至服务端控制层(Controller),控制层通过调用模型处理器(Model)以及渲染视图(View)并最终将页面返回给客户端。
MVC架构所示,视图与模型均放在后端处理,致使前端代码无法独立上线运行。前端工程师完成HTML等静态代码开发之后,将页面代码传递给后端工程师。后端工程师来完成上线发布操作。整个开发流程如图-2所示,前后端工程师开发流程相互牵制,整体开发效率较低。
此种架构,要求后端研发关注前端HTML、CSS代码等。前端无法单独调试,前后端无法并行开发,开发效率低,后期维护成本较高。
- 优点:View、Model 都是可以重复利用的,可以独立使用
- 缺点:Controller 的代码过于臃肿
2、前后端未完全分离架构模式
在此以RESTful架构为切入点,探讨前后端未完全分离的架构模式,目前仍有大部分前后端架构属于前后端未完全分离的模式。后端提供RESTful 风格的API接口(通俗讲,即传输JSON数据的Http API接口)。前端通过AJAX请求调用后端Http API接口,并完成页面数据的绑定,最终由客户端浏览器完成页面的渲染。此种架构看似已经做到了前后端完全分离。其实不然,后端接口依然需要关注前端的UI展示,后端为前端接口定制化严重。对于多终端场景,后端需要实现多套API接口。前后端数据以及业务耦合较紧密,因此定义为此种架构为“前后端未完全分离架构模式”。
RESTful架构使得前后端代码分离开来,前后端可以独立上线,开发效率相比未分离架构有所提升,整个系统开发流程也有所优化。
3、前后端分离架构模式
引入NodeJS层作为服务桥接层,NodeJS层由前端工程师负责搭建完成。通过NodeJS服务器在服务器端运行JS脚本,可以让前端人员快速入门搭建自己的服务器。引入NodeJS,可以预先在服务端的内网环境完成大量的前端逻辑计算和页面渲染工作,提升前端的访问性能。如图6所示展示前后端完全分离架构模式的数据以及页面渲染流程。
Redis实现分布式锁
1.背景
Redis为单进程单线程模式,采用队列模式将并发访问变成串行访问,且多客户端对Redis的连接并不存在竞争关系。其次Redis提供一些命令SETNX,GETSET,可以方便实现分布式锁机制。
2.实现
SETNX命令(SET if not exists)
语法:SETNX key value
功能: 当且仅当 key 不存在,将 key 的值设为 value ,并返回1;若给定的 key 已经存在,则 SETNX 不做任何动作,并返回0。
GETSET命令
语法: GETSET key value
功能: 将给定 key 的值设为 value ,并返回 key 的旧值 (old value),当 key 存在但不是字符串类型时,返回一个错误,当key不存在时,返回nil。
GET命令
语法:GET key
功能: 返回 key 所关联的字符串值,如果 key 不存在那么返回特殊值 nil 。
DEL命令
语法:DEL key [KEY …]
功能: 删除给定的一个或多个 key ,不存在的 key 会被忽略。
参考🔗
nodejs
一种javascript的运行环境,能够使得javascript脱离浏览器运行。
Node.js就是这样一个服务器端的、非阻断式I/O的、事件驱动的JavaScript运行环境。
悲观锁、乐观锁
悲观锁
- 它指的是对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改持保守态度,因此,在整个数据处理过程中,将数据处于锁定状态。悲观锁主要分为共享锁和排他锁
- 悲观锁适用于写操作比较频繁的场景,因为如果出现大量的读取操作,每次读取的时候都会进行加锁,这样会增加大量的锁的开销,降低了系统的吞吐量。
乐观锁
- 乐观锁不会刻意使用数据库本身的锁机制,而是依据数据本身来保证数据的正确性。MVCC就是乐观锁的一种实现方式。
- 比较适合读取操作比较频繁的场景,如果出现大量的写入操作,数据发生冲突的可能性就会增大,为了保证数据的一致性,应用层需要不断的重新获取数据,这样会增加大量的查询操作,降低了系统的吞吐量。
- 实现乐观锁一般来说有以下2种方式:
- 使用版本号使用数据版本(Version)记录机制实现,这是乐观锁最常用的一种实现方式。何谓数据版本?即为数据增加一个版本标识,一般是通过为数据库表增加一个数字类型的 “version” 字段来实现。当读取数据时,将version字段的值一同读出,数据每更新一次,对此version值加一。当我们提交更新的时候,判断数据库表对应记录的当前版本信息与第一次取出来的version值进行比对,如果数据库表当前版本号与第一次取出来的version值相等,则予以更新,否则认为是过期数据。
- 使用时间戳 乐观锁定的第二种实现方式和第一种差不多,同样是在需要乐观锁控制的table中增加一个字段,名称无所谓,字段类型使用时间戳(timestamp), 和上面的version类似,也是在更新提交的时候检查当前数据库中数据的时间戳和自己更新前取到的时间戳进行对比,如果一致则OK,否则就是版本冲突。
读锁、写锁/共享锁、排他锁
共享锁(S锁)又称读锁,若事务T对数据对象A加上S锁,则事务T可以读A但不能修改A,其他事务只能再对A加S锁,而不能加X锁,直到T释放A上的S锁。这保证了其他事务可以读A,但在T释放A上的S锁之前不能对A做任何修改。
排他锁(X锁)又称写锁。若事务T对数据对象A加上X锁,事务T可以读A也可以修改A,其他事务不能再对A加任何锁,直到T释放A上的锁。这保证了其他事务在T释放A上的锁之前不能再读取和修改A。
对于读操作,可以增加读锁,一旦数据表被加上读锁,其他请求可以对该表再次增加读锁,但是不能增加写锁。(当一个请求在读数据时,其他请求也可以读,但是不能写,因为一旦另外一个线程写了数据,就会导致当前线程读取到的数据不是最新的了。这就是不可重复读现象)
对于写操作,可以增加写锁,一旦数据表被加上写锁,其他请求无法在对该表增加读锁和写锁。(当一个请求在写数据时,其他请求不能执行任何操作,因为在当前事务提交之前,其他的请求无法看到本次修改的内容。这有可能产生脏读、不可重复读和幻读)
读锁和写锁都是阻塞锁。
如果t1对数据表增加了写锁,这是t2请求对数据表增加写锁,这时候t2并不会直接返回,而是会一直处于阻塞状态,直到t1释放了对表的锁,这时t2便有可能加锁成功,获取到结果。
FTP预分配线程池服务器中的互斥问题
多线程程序中可能会存在数据不一致的情况,那么如何保证数据一致呢?可以考虑同一时间只有一个线程访问数据。互斥量(mutex)就是一把锁。
互斥变量使用特定的数据类型:pthread_mutex_t,使用互斥量前要先初始化,使用的函数如下:
#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
int pthread_mutex_destroy(pthread_mutex_t *mutex);
简单的使用可以使用默认的属性初始化互斥量,函数的后一个参数设置为NULL即可。
对互斥量加锁解锁的函数如下:
#include <pthread.h>
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
MVVM
MVVM与MVC最大的区别就是:它实现了View和Model的自动同步,也就是当Model的属性改变时,我们不用再自己手动操作Dom元素,来改变View的显示,而是改变属性后该属性对应View层显示会自动改变。
JWT,Token认证
json Web Token(缩写JWT)是目前最流行的跨域认证解决方案。
JWT的解决方案是:将认证信息返回给客户端,给客户端提供JWT token,储存在客户端,下次客户端访问其他页面,在所有请求头带上该token,需要传递认证信息回服务器端。
JWT分为三个部分,header(头部) payload (负载) signature (签名)
头部和负载都是两个JSON对象,使用 Base64URL 算法转换成字符串,签名是对前两部分的签名(可以理解成在加密一份),防止数据篡改。
如何通俗地理解三个范式
第一范式:1NF是对属性的原子性约束,要求属性具有原子性,不可再分解;
第二范式:2NF是对记录的唯一性约束,要求记录有唯一标识,即实体的唯一性;
第三范式:3NF是对字段冗余性的约束,即任何字段不能由其他字段派生出来,它要求字段没有冗余。
范式化设计优缺点
优点:
- 可以尽量得减少数据冗余,使得更新快,体积小
缺点:
- 对于查询需要多个表进行关联,减少写得效率增加读得效率,更难进行索引优化