springmvc流程(前后端如何交互的)
1.先由客户端发起一个http请求到前端控制器(DispacherServlet)。注:所有的请求都必须经过DispacherServlet。
2.然后DispacherServlet控制器查询HanderMapping映射,根据请求中的url转发到controller控制器处理。
3.controller控制器调用业务逻辑层处理业务逻辑,结果返回给ModelAndView,视图处理器渲染视图
4.DispacherSerclet查询视图解析器,找到ModelAndView指定的视图
5.视图负责将结果显示到客户端
SSM框架是spring MVC ,spring和mybatis框架的整合,是标准的MVC模式,将整个系统划分为表现层,controller层,service层,DAO层四层
使用方法:
要完成一个功能:
先写实体类entity,定义对象的属性,(可以参照数据库中表的字段来设置,数据库的设计应该在所有编码开始之前)。
写Mapper.xml(Mybatis),其中定义你的功能,对应要对数据库进行的那些操作,比如 insert、selectAll、selectByKey、delete、update等。
写Mapper.java,将Mapper.xml中的操作按照id映射成Java函数。
写Service.java,为控制层提供服务,接受控制层的参数,完成相应的功能,并返回给控制层。
写Controller.java,连接页面请求和服务层,获取页面请求的参数,通过自动装配,映射不同的URL到相应的处理函数,并获取参数,对参数进行处理,之后传给服务层。写JSP页面调用,请求哪些参数,需要获取什么数据。
DataBase ===> Entity ===> Mapper.xml ===> Mapper.Java ===> Service.java ===> Controller.java ===> Jsp.
秒杀流程具体实现:
创建PromoModel秒杀模型
数据库创建promo表
4. mybatis-generator工具生成PromoDO类和PromoDOMapper
5.2 秒杀模型管理——活动模型与商品模型结合
1. Service层创建PromoSerice接口与实现类
秒杀活动service层主要的操作是:
- 根据itemId来获取即将进行的或正在进行的秒杀活动信息(返回PromoDO的dataObject)
- 将PromoDO转化为PromoModel
- 根据status来判断当前时间是否即将开始或正在进行秒杀活动
- 返回promoModel对象
在ItemVO中扩展秒杀活动的字段:
在ItemController中向itemVO增加秒杀活动信息:
3.修改商品详情getItem前端页面
增加秒杀活动状态、时间、秒杀价格以及倒计时的显示
在controller层增加参数:
@RequestParam(name = “promoId”,required = false) Integer promoId,
HTTP报文格式
请求行:
①是请求方法,GET和POST是最常见的HTTP方法,除此以外还包括DELETE、HEAD、OPTIONS、PUT、TRACE。
②为请求对应的URL地址,它和报文头的Host属性组成完整的请求URL。
③是协议名称及版本号。
请求头:
④是HTTP的报文头,报文头包含若干个属性,格式为“属性名:属性值”,服务端据此获取客户端的信息。
与缓存相关的规则信息,均包含在header中,例如语言形式,长度等等
请求体:
⑤是报文体,它将一个页面表单中的组件值通过param1=value1¶m2=value2的键值对形式编码成一个格式化串,它承载多个请求参数的数据。不但报文体可以传递请求参数,请求URL也可以通过类似于“/chapter15/user.html? param1=value1¶m2=value2”的方式传递请求参数。
HTTP响应报文
响应行:
①报文协议及版本;
②状态码及状态描述;
响应头:
③响应报文头,也是由多个属性组成;比如响应日期,响应数据类型格式等等
响应体:
④响应报文体,即我们真正要的“干货”
响应状态码
和请求报文相比,响应报文多了一个“响应状态码”,它以“清晰明确”的语言告诉客户端本次请求的处理结果。
HTTP的响应状态码由5段组成:
- 1xx 消息,一般是告诉客户端,请求已经收到了,正在处理,别急…
- 2xx 处理成功,一般表示:请求收悉、我明白你要的、请求已受理、已经处理完成等信息.
- 3xx 重定向到其它地方。它让客户端再发起一个请求以完成整个处理。
- 4xx 处理发生错误,责任在客户端,如客户端的请求一个不存在的资源,客户端未被授权,禁止访问等。
- 5xx 处理发生错误,责任在服务端,如服务端抛出异常,路由出错,HTTP版本不支持等。
以下是几个常见的状态码: 200 OK 你最希望看到的,即处理成功! 303 See Other
我把你redirect到其它的页面,目标的URL通过响应报文头的Location告诉你。304 Not Modified 告诉客户端,你请求的这个资源至你上次取得后,并没有更改,你直接用你本地的缓存吧,我很忙哦,你能不能少来烦我啊! 404 Not Found
你最不希望看到的,即找不到页面。如你在google上找到一个页面,点击这个链接返回404,表示这个页面已经被网站删除了,google那边的记录只是美好的回忆。不存在的URL
500 Internal Server Error看到这个错误,你就应该查查服务端的日志了,肯定抛出了一堆异常,别睡了,起来改BUG去吧!
◆200 (OK): 找到了该资源,并且一切正常。
◆302/307:临时重定向,指出请求的文档已被临时移动到别处, 此文档的新的url在location响应头中给出
◆304 (NOT MODIFIED): 该资源在上次请求之后没有任何修改。这通常用于浏览器的缓存机制。
◆401 (UNAUTHORIZED): 客户端无权访问该资源。这通常会使得浏览器要求用户输入用户名和密码,以登录到服务器。
◆403 (FORBIDDEN): 客户端未能获得授权。这通常是在401之后输入了不正确的用户名或密码。
◆404 (NOT FOUND): 在指定的位置不存在所申请的资源。**
问题?
1 Redis为什么这么快,做缓存?
Redis是基于内存的,内存的读写速度非常快;
Redis是单线程的,避免了不必要的上下文切换和竞争条件;1)不需要各种锁的性能消耗
Redis使用多路复用技术,可以处理并发的连接。非阻塞I/O内部实现采用epoll,采用了epoll+自己实现的简单的事件框架。epoll中的读、写、关闭、连接都转化成了事件,然后利用epoll的多路复用特性,绝不在io上浪费一点时间。
非阻塞I/O:当IO操作准备就绪可以操作的时候就会进行真正的IO操作,就是将数据从内核写入用户空间的过程。但是这样做会导致CPU的大量耗费。
select/poll一般只能处理几千的并发连接。
只需要在进程启动时建立一个epoll对象,然后在需要的时候向这个epoll对象中添加或者删除连接。同时,epoll_wait的效率也非常高,因为调用epoll_wait时,并没有一股脑的向操作系统复制这100万个连接的句柄数据,内核也不需要去遍历全部的连接。
Redis单线程理解
Redis客户端对服务端的每次调用都经历了发送命令,执行命令,返回结果三个过程。其中执行命令阶段,由于Redis是单线程来处理命令的,所有到达服务端的命令都不会立刻执行,所有的命令都会进入一个队列中,然后逐个执行,并且多个客户端发送的命令的执行顺序是不确定的,但是可以确定的是不会有两条命令被同时执行,不会产生并发问题,这就是Redis的单线程基本模型。
Springboot优势
1、首先 SpringBoot可以快速一键快速搭建Spring框架,简化初始配置 ,可与主流框架集成
2、内置Servlet容器,无需在打War包
3、使用了Starter(启动器)管理依赖并版本控制
4、大量的自动配置,简化开发,方便集成第三方
5、提供准生产环境运行时的监控,如指标,健康,外部配置等
6、无需XML配置,减少冗余代码 ,开箱即用。
2.Redistemplate是什么?为什么用springboot?
Springboot自动装配) 通过反射,把扫描到的对象创建出来
@Autowired注入RedisTemplate(后面直接使用,就不特殊说明)
- 删除单个key
- 指定key的失效时间
- 根据key获取过期时间
- 添加缓存(2/3是1的递进值
3.为什么要分布式会话?
- 三种实现分布式解决方案原理分析
采用数据库 不建议 性能不好
基于redis实现分布式锁(setnx)
多个客户端(jvm),使用setnx命令方式,同时在redis上创建一个相同的key(如果存入成功返回1,存入失败返回0),因为redis key不能够允许重复,只要谁能够创建key成功,谁就能获取到锁,没有创建成功的jvm,就会等待
分布式系统:就是能把系统进行拆分并部署到多台服务器上的系统。(注意区分分层和集群)
但是session属于会话机制,当当先会话结束时,session就会被销毁,并且web程序会为每一次不同的会话创建不同的session,所以在分布式场景下,即使是调用同一个方法执行同样的代码,但是他们的服务器不同,自然web程序不同,整个上下文对象也不同,理所当然session也是不同的。
基于令牌(Token)方式实现Session解决方案,因为Session本身就是分布式共享连接。
将生成的Token 存入到Redis中。Cookie和session区别
(1)cookie数据存放在客户的浏览器上,session数据放在服务器上
(2)cookie不是很安全,别人可以分析存放在本地的COOKIE并进行COOKIE欺骗,如果主要考虑到安全应当使用session
(3)session会在一定时间内保存在服务器上。当访问增多,会比较占用你服务器的性能,如果主要考虑到减轻服务器性能方面,应当使用COOKIE
(4)单个cookie在客户端的限制是3K,就是说一个站点在客户端存放的COOKIE不能3K。
(5)所以:将登陆信息等重要信息存放为SESSION;其他信息如果需要保留,可以放在COOKIE中4.mybatis如何连接数据库?自动生成原理?MyBatis的底层实现原理
Mybatis 读取XML配置文件后会将内容放在一个Configuration类中,Configuration类会存在整个Mybatis生命周期,以便重复读取。SqlSessionFactoryBuilder会读取Configuration类中信息创建SqlSessionFactory。
Mybatis中SqlSessionFactiory、SqlSession等都为接口,Mybatis默认使用的实现类为DefaultSqlSessionFactory和DefaultSqlSession类。
SqlSession用途主要有两种
①. 获取对应的Mapper,让映射器通过命名空间和方法名称找到对应的SQL,发送给数据库执行后返回结果。
②. 直接使用SqlSession,通过命名信息去执行SQL返回结果,该方式是IBatis版本留下的,SqlSession通过Update、Select、Insert、Delete等方法操作。
针对Mapper方式,Mybatis底层利用JDK动态代理技术实现该接口,底层最后还是使用的IBatis中SqlSession通过Update、Select、Insert、Delete等方法操作。
4.关于对象的生命周期:
①. SqlSessionFactoryBuilder的作用是创建SqlSessionFactiory,在创建完成后就会被回收,所以它的生命周期只存在于局部方法。
②.SqlSessionFactiory是单例的,作用就是创建SqlSession,每次访问数据库都需要一个SqlSession,所以SqlSessionFactiory的生命周期是贯穿整个Mybatis生命周期的,SqlSessionFactiory采用单例的原因是减少数据库连接资源。
③. SqlSession是一个会话,类似于jdbc的connection,它的生命周期是在请求数据库处理事务过程中,他是一个线程不安全的对象。多线程操作时因多注意他的隔离级别和数据库锁等。SqlSession使用需及时关闭。
④. Mapper是一个接口,它的作用是发送SQL,返回结果,因此他的生命周期不会大于SqlSession
逆向工程:
先创建数据库表,由框架负责根据数据库表,反向生成如下资源:
Java实体类
Mapper接口
Mapper配置文件5.为什么用rocketMq?怎样保证数据库与缓存一致的?
但是因为整个下单是属于一个transaction事务,如果用户下单成功,但是之后订单入库或返回前端的过程中失败,事务回滚,会导致少卖的现象,有可能造成库存堆积
我们的解决方法就是异步消息的发送要在整个事务提交成功后再发送
rocketMQ自带的transactionMQProducer来发送事务型消息
RocketMQ消息的事务架构设计:
生产者执行本地事务,修改订单支付状态(下单),并且提交事务
生产者发送事务消息到broker上,消息发送到broker上在没有确认之前,消息对于consumer是不可见状态(prepare状态)
生产者确认事务消息,使得发送到broker上的事务消息对于消费者可见
消费者获取到消息进行消费,消费完之后执行ack进行确认
RocketMQ提供了消息回查机制
如果事务消息一直处于中间状态,broker会发起重试去查询broker上这个事务的处理状态。一旦发现事务处理成功,则把当前这条消息设置为可见。
RocketMQ事务消息有三种状态:
- ROLLBACK_MESSAGE:回滚事务
- COMMIT_MESSAGE:提交事务
- UNKNOW:broker会定时回查Producer消息状态,直到彻底成功或失败
由于分布式消息队列对于可靠性的要求比较高,所以需要保证生产者将消息发送到broker之后,保证消息是不出现丢失的,因此消息队列就少不了对于可靠性存储的要求
文件系统存储,常见的比如kafka、RocketMQ、RabbitMQ都是采用消息刷盘到所部署的机器上的文件系统来做持久化,这种方案适合对于有高吞吐量要求的消息中间件,因为消息刷盘是一种高效率,高可靠、高性能的持久化方式,除非磁盘出现故障,否则一般是不会出现无法持久化的问题
//投递prepare消息 返回结果_sendResult
//发送事务型消息,消息的类型是prepare,不会被consumer立即执行
如果成功!真正要做的事,创建订单//_失败事务回滚 return LocalTransactionState.ROLLBACK_MESSAGE;
令牌桶算法
令牌桶算法实现:
RateLimiter
RateLimiter的设计思想比较超前,没有依赖于人为定时器的方式,而是将整个时间轴
归一化到一个数组内,看对应的这一秒如果不够了,预支下一秒的令牌数,并且让当前的线程睡眠;
如果当前线程睡眠成功,下一秒唤醒的时候令牌也会扣掉,程序也实现了限流
2 秒杀项目做过压测吗?压测多少QPS?
采用Jmeter开启5000个线程,每个线程执行10次,5000个用户进行秒杀抢购
3 如何分库分表的?
用户库、商品库、订单库,这些都可以当做独立数据库,不需要放到一起。好处是既能独立变更,又能隔绝相互影响。
HASH取模
一个商场系统,一般都是将用户,订单作为主表,然后将和它们相关的作为附表,这样不会造成跨库事务之类的问题。 取用户id,然后hash取模,分配到不同的数据库上
一致性哈希
幂等:每次用同一个值去计算 hash 必须保证都能得到同一个值
这个就是 hash 算法完成的。
但是采取普通的 hash 算法进行路由,如:key % N 。有一个节点由于异常退出了集群或者是心跳异常,这时再进行 hash route ,会造成大量的数据重新 分发到不同的节点 。节点在接受新的请求时候,需要重新处理获取数据的逻辑(重新哈希):如果是在缓存中,容易引起 缓存雪崩。
此时就需要引入 consistent hash 算法了。
先解决大量 rehash 的问题:
如上图,当加入一个新的节点时,影响的key只有 key31,新加入(剔除)节点后,只会影响该节点附近的数据。其他节点的数据不会受到影响,从而解决了节点变化的问题。
数据倾斜
其实上图可以看出:目前多数的key都集中在 node 1 上。如果当 node 数量比较少的情况下,可以回引发多数 key 集中在某个 node 上,监控时发现的问题就是:节点之间负载不均。
为了解决这个问题,consistent hash 引入了 virtual node 的概念。
既然是负载不均,我们就人为地构造一个均衡的场景出来,但是实际 node 只有这么多。所以就使用 virtual node 划分区域,而实际服务的节点依然是之前的 node。
传统哈希缺点
传统方式
如,现有N个缓存实例,将一个对象object映射到某一个缓存上可以采取取模方式 hash(object) % N
我们称之为简单hash算法。一般,简单hash算法确实能够比较均匀地实现分布式映射,但如果考虑缓存实例变动(增删)的情况:
某一缓存实例宕机,需要将该实例从集群中摘除,则映射公式变为 hash(object) % (N - 1)
增加一台缓存实例,将该实例加入集群,则映射公式变为 hash(object) % (N + 1)
对于以上情况,无论新增还是移除,大部分object所映射的缓存实例均会改变,缓存命中率大幅度降低从而回源到服务器,短时间内造成缓存雪崩现象
一致性哈希步骤
使用缓存实例的ip、port等信息,通过hash函数计算其hash值,将缓存实例映射到hash空间
沿着顺时针方向,查找距离对象object最近的缓存实例,并将对象映射到该实例
想象成一个首尾相接的环形队列
通过hash函数计算对象hash值,将对象映射到hash环形空间,再利用二分查找不断缩小范围,找到虚拟节点,维护映射到真实节点,实现分布均衡
计算 key 的hash
找到第一个匹配的 virtual node 的 index,并取到对应的 h.keys[index] :virtual node hash
对应到这个 ring 中去寻找一个与之匹配的 actual node
其实我们可以看到 ring 中获取到的是一个 []node 。这是因为在计算 virtual node hash ,可能会发生hash冲突,不同的 virtual node hash 对应到一个实际node。
这也说明:node 与 virtual node 是一对多的关系。而里面的 ring 就是下面这个设计:
虚拟节点的hash求值可以在真实节点的求值基础上加入编号等信息 hash(realCacheKey#1) 、 hash(realCacheKey#2)
(详细信息参考博客)
(76条消息) 一致性哈希机器在分布式中的作用_imzoer的博客-CSDN博客
https://segmentfault.com/a/1190000015336117
https://baijiahao.baidu.com/s?id=1706150292132699282&wfr=spider&for=pc
小林的一致性哈希(正确)
哈希算法。
因为对同一个关键字进行哈希计算,每次计算都是相同的值,这样就可以将某个 key 确定到一个节点了,可以满足分布式系统的负载均衡需求。
哈希算法最简单的做法就是进行取模运算,比如分布式系统中有 3 个节点,基于 hash(key) % 3 公式对数据进行了映射。
问题
但是有一个很致命的问题,如果节点数量发生了变化,也就是在对系统做扩容或者缩容时,必须迁移改变了映射关系的数据,否则会出现查询不到数据的问题。
一致哈希算法
一致哈希算法也用了取模运算,但与哈希算法不同的是,哈希算法是对节点的数量进行取模运算,而一致哈希算法是对 2^32 进行取模运算,是一个固定的值。
我们可以把一致哈希算法是对 2^32 进行取模运算的结果值组织成一个圆环,就像钟表一样,钟表的圆可以理解成由 60 个点组成的圆,而此处我们把这个圆想象成由 2^32 个点组成的圆,这个圆环被称为哈希环,如下图:
一致性哈希要进行两步哈希:
第一步:对存储节点进行哈希计算,也就是对存储节点做哈希映射,比如根据节点的 IP 地址进行哈希;
第二步:当对数据进行存储或访问时,对数据进行哈希映射;
所以,一致性哈希是指将「存储节点」和「数据」都映射到一个首尾相连的哈希环上。
问题来了,对「数据」进行哈希映射得到一个结果要怎么找到存储该数据的节点呢?
答案是,映射的结果值往顺时针的方向的找到第一个节点,就是存储该数据的节点。
首先,对 key 进行哈希计算,确定此 key 在环上的位置;
然后,从这个位置沿着顺时针方向走,遇到的第一节点就是存储 key 的节点。
好处,在一致哈希算法中,如果增加或者移除一个节点,仅影响该节点在哈希环上顺时针相邻的后继节点,其它数据也不会受到影响。
但是一致性哈希算法并不保证节点能够在哈希环上分布均匀,这样就会带来一个问题,会有大量的请求集中在一个节点上
问题
所以,一致性哈希算法虽然减少了数据迁移量,但是存在节点分布不均匀的问题
解决
,实际中我们没有那么多节点。所以这个时候我们就加入虚拟节点,也就是对一个真实节点做多个副本。
具体做法是,不再将真实节点映射到哈希环上,而是将虚拟节点映射到哈希环上,并将虚拟节点映射到实际节点,所以这里有「两层」映射关系。
比如对每个节点分别设置 3 个虚拟节点:
对节点 A 加上编号来作为虚拟节点:A-01、A-02、A-03
对节点 B 加上编号来作为虚拟节点:B-01、B-02、B-03
对节点 C 加上编号来作为虚拟节点:C-01、C-02、C-03
你可以看到,节点数量多了后,节点在哈希环上的分布就相对均匀了。这时候,如果有访问请求寻址到「A-01」这个虚拟节点,接着再通过「A-01」虚拟节点找到真实节点 A,这样请求就能访问到真实节点 A 了。
因此,带虚拟节点的一致性哈希方法不仅适合硬件配置不同的节点的场景,而且适合节点规模会发生变化的场景
集群+读写分离+分库分表的
读用MYISAM引擎,写用INNODB引擎
INNODB在做SELECT的时候,要维护的东西比MYISAM引擎多很多:1)数据块,INNODB要缓存,MYISAM只缓存索引块, 这中间还有换进换出的减少;
2)innodb寻址要映射到块,再到行,MYISAM记录的直接是文件的OFFSET,定位比INNODB要快
3)INNODB还需要维护MVCC一致;虽然你的场景没有,但他还是需要去检查和维护
当master出现故障时,通过对比 slave 之间的 I/O thread 读取主库的 binlog 的 position 号,选取最接近的slave作为备胎(被选主库),其它从库通过与备胎对比,生成差异的中继日志,在备胎上运用从原来的 master 保存的 binlig,同时将备胎提升为master。最后在其他 slave 上运用相应的差异中继日志,并从新的 master 开始复制。
优点:
- 故障切换时,自动判断哪个从库与主库离的最近,并切换到上面
- 支持binlog server,提高 binlog 的传送效率
- 结合半同步功能,确保故障切换时数据不丢失
如何保证主从数据库数据一致
总结:
就是两个思路
1 半同步复制 , 等从库复制成功才返回写成功
2 设一个key记录着一次写的数据,然后设置一个同步时间,如果在这个时间内,有一个读请求,看看对应的key有没有相关数据,有的话,说明数据近期发生过写事件,这样key的数据就继续读主库,否则就读从库
4 分布式锁,Redis如何保证并发下线程安全的?
Redis是个单线程程序,所以它是线程安全的。
单个Redis支持的读写能力还是有限的,此时我们可以使用多个redis来提高redis的并发处理能力
1.master 负责读写,并将数据同步到slave 中。
slave 为从节点,只负责读
2. 同步分为全量同步和增量同步
全量同步就是冷备份同步(RDB方式的持久化)
增量同步为指令同步
三 Redis哨兵模式
1.背景
哨兵(Sentinel)是Redis的主从架构模式下,实现高可用性(high availability)的一种机制。
由一个或多个Sentinel实例(instance)组成的Sentinel系统(system)可以监视任意多个主服务器,以及这些主服务器属下的所有从服务器,并在被监视的主服务器进入下线状态时,自动将下线主服务器属下的某个从服务器升级为新的主服务器,然后由新的主服务器代替已下线的主服务器继续处理命令请求。
Redis支持主从同步机制,redis2作为redis1的slave从机,同步复制master的内容,当其中一个数据库宕机,应用服务器是很难直接通过找地址来切换成redis2,这时就用到了redis sentinal 哨兵机制。sentinal与redis1和redis2建立长连接,与主机连接是心跳机制,miaosha.jar无需知道redis1,redis2主从关系,只需ask redis sentinal,之后sentinal就response回应redis1为master,redis2为slave
分布式锁,设置过期时间
set if not exists 我们会用到Redis的命令setnx,setnx的含义就是只有锁不存在的 情况下才会设置成功。
当key已经存在时,就不做任何操作。SETNX是”SET if Not eXists”。
删除key的时候先判断随机值是否和本线程的一致,一致的才可以删除。
这就是一段判断是否自己持有锁并释放锁的Lua脚本
为什么Lua脚本是原子性呢?因为Lua脚本是jedis用eval()函数执行的,如果执行则会全部执行完成。
误删原因:当线程A获取到正常值时,返回带代码中判断期间锁过期了,线程B刚好重新设置了新值,线程A那边有判断value是自己的标识,然后调⽤del方法,结果就是删除了新设置的线程B的值
防止其他线程误删key,可以加UUID做识别码!
Mysql创建锁表
1.新建锁表记录
CREATE TABLE methodLock
(
id
int(11) NOT NULL AUTO_INCREMENT COMMENT ‘主键’,
method_name
varchar(64) NOT NULL DEFAULT ‘’ COMMENT ‘锁定的方法名’,
desc
varchar(1024) NOT NULL DEFAULT ‘备注信息’,
2.第一种实现
当想要锁住某个方法时执行insert方法,插入一条数据,method_name有唯一约束,可以保证多次提交只有一次成功,而成功的这次就可以认为其获得了锁,而执行完成后执行delete语句释放锁
缺点:
这把锁强依赖数据库的可用性,数据库是一个单点,一旦数据库挂掉,会导致业务系统不可用。
这把锁没有失效时间,一旦解锁操作失败,就会导致锁记录一直在数据库中,其他线程无法再获得到锁。
这把锁只能是非阻塞的,因为数据的insert操作,一旦插入失败就会直接报错。没有获得锁的线程并不会进入排队队列,要想再次获得锁就要再次触发获得锁操作。
4.MySQL乐观锁实现
一般是通过为数据库表添加一个 “version”字段来实现读取出数据时,将此版本号一同读出,之后更新时,对此版本号加1,在更新过程中,会对版本号进行比较,如果是一致的,没有发生改变,则会成功执行本次操作;如果版本号不一致,则会更新失败,实际就是个diff过程
缺点:
(1). 这种操作方式,使原本一次的update操作,必须变为2次操作: select版本号一次;update一次。增加了数据库操作的次数。
(2). 如果业务场景中的一次业务流程中,多个资源都需要用保证数据一致性,那么如果全部使用基于数据库资源表的乐观锁,就要让每个资源都有一张资源表,这个在实际使用场景中肯定是无法满足的。而且这些都基于数据库操作,在高并发的要求下,对数据库连接的开销一定是无法忍受的。
(3). 乐观锁机制往往基于系统中的数据存储逻辑,因此可能会造成脏数据被更新到数据库中
5 LRU的策略,Redis的5种数据结构?
- String:字符串类型
- List:列表类型
- Set:无序集合类型
- ZSet:有序集合类型
- Hash:哈希表类型
2.LRU:最近最少使用,淘汰最近不使用的页面
实现步骤原理如下:
1. 新数据插入到链表头部;
2. 每当缓存命中(即缓存数据被访问),则将数据移到链表头部;
3. 当链表满的时候,将链表尾部的数据丢弃。
双链表 + hashtable实现原理:
将Cache的所有位置都用双连表连接起来,当一个位置被命中之后,就将通过调整链表的指向,将该位置调整到链表头的位置,新加入的Cache直接加到链表头中。这样,在多次进行Cache操作后,最近被命中的,就会被向链表头方向移动,而没有命中的,而想链表后面移动,链表尾则表示最近最少使用的Cache。当需要替换内容时候,链表的最后位置就是最少被命中的位置,我们只需要淘汰链表最后的部分即可。
具体实现
具体实现Map里边是key是itemId,value是库存,每次去Map里查是否有key,如果有就直接返回value,每次命中插入链表头,如果队列满了,就抛弃队尾节点。。
6 Spring 的controller 是单例还是多,怎么保证并发的安全
controller默认是单例的,不要使用非静态的成员变量,否则会发生数据逻辑混乱。
正因为单例所以不是线程安全的。
解决方案
1、不要在controller中定义成员变量。
2、万一必须要定义一个非静态成员变量时候,则通过注解@Scope(“prototype”),将其设置为多例模式。这样每次请求调用的类都是重新生成的(每次生成会影响效率)
3、在Controller中使用ThreadLocal来保存类变量,将类变量保存在线程的变量域中,让不同的请求隔离开来。
7 Spring生命周期
2. 生命周期的概要流程
Bean 的生命周期概括起来就是 4 个阶段:
实例化(Instantiation)
属性赋值(Populate)
初始化(Initialization)
销毁(Destruction)
8 反向代理
nginx反向代理的功能就是代理后端Tomcat服务器集群,以统一域名方式来访问
反向代理有什么用为什么要这么做:作用:用户请求过多,服务器会有一个处理的极限。所以使用反向代理服务器接受请求,再用均衡负载将请求分布给多个真实的服务器。既能提高效率还有一定的安全性。
用途:如果不采用代理,用户的IP、端口号直接暴露在Internet(尽管地址转换NAT),外部主机依然可以根据IP、端口号来开采主机安全漏洞,所以在企业网,一般都是采用代理服务器访问互联网。
正向代理与反向代理最简单的区别:
正向代理隐藏的是用户,反向代理隐藏的是服务器
9 跨域问题
为什么我的项目会产生跨域?
因为是前后端分离的,前端静态是部署在Nginx服务器上的,而后端实现动态用了两个服务器,服务器不一样时,ip地址不一样,域名不一样,自然就产生了跨域问题
什么是跨域?
CORS 是跨域资源分享(Cross-Origin Resource Sharing)的缩写。它是 W3C 标准,属于跨源 AJAX 请求的根本解决方法。
当一个请求url的协议、域名、端口三者之间任意一个与当前页面url不同即为跨域
浏览器只是针对同源策略的一种实现。同源策略会阻止一个域的javascript脚本和另外一个域的内容进行交互。所谓同源(即指在同一个域)就是两个页面具有相同的协议(protocol),主机(host)和端口号(port)
带cookie跨域请求:前后端都需要进行设置
【服务端设置】
服务器端对于CORS的支持,主要是通过设置Access-Control-Allow-Origin来进行的。如果浏览器检测到相应的设置,就可以允许Ajax进行跨域的访问。
如何实现前后端分离?
在自己最近做的项目中,使用的是利用SSM框架中的controller层来传出JSON串,
再通过jQuery中的.getJSON()来进行解析,再将数据传到前端页面。