1.并发问题
1.更新丢失
2.不一致读
2.执行语境
从与外界交互的角度看,有两个重要的执行预警:请求和会话。
一个请求对应于软件工作的外部环境发出的单个调用,针对这个调用,处理请求的软件会决定是否返回一个应答。在一个请求到来的时候,处理过程大部分是在
服务端进行的,而客户端则假设为在等待响应。
一次会话是客户端和服务器之间一次长时间的交互。它可以只是一个单独的请求,但通常是一系列用户认为逻辑上有关的请求构成的。
进程:重量级的执行预警,将其正在处理的内部数据与外部隔离开。
线程:一个轻量级的活跃执行单元,一个单独的进程里面可以存在多个线程。人们喜欢线程,是因为它能够在单个进程里面支持多个请求,充分利用资源。然后线程是
共享内存的,这样就会导致并发问题。
事务:将多个请求当作单个请求来看待。
3.隔离与不变性
解决并发的两个方案:一个隔离,而是不变性。
并发的问题发生在多个执行单元同时访问同一片数据的时候。一个解决办法就是隔离:划分数据,使得每一片数据只能被一个执行单元访问。操作系统为每个进程单独分配
一片内存,并且只有这个进程可以对这片内存进行读写操作。同样的,你可以发现现在很多留下的高效应用中都有文件锁。可以使用隔离的办法来安排资源,以便程序进入
隔离区而无需考虑并发问题。
只有在共享数据可以修改的情况下,并发问题才会出现。所以,一个避免并发冲突的方法是识别哪些是不变的数据。通过定义哪些数据是不变的,或者至少是几乎总不变的,
就可以不用总考虑这些数据的并发问题而广泛的共享它们。
4.悲观并发控制和乐观并发控制
乐观锁策略:两个人都能得到一份文件的拷贝,可以自由编辑。当要提交的时候,并发控制策略就会起作用。
悲观锁策略:只要有人先取出文件,其他人就不能对该文件进行编辑。
如果把乐观锁看作是冲突检测的,那么悲观锁就是关于冲突避免的。悲观锁的问题在于减少了并发的程度。乐观锁更自由,只有在提交的时候才能遇到阻碍。
在乐观锁和悲观锁之间进行选择的标准是:冲突的频度和严重性。如果冲突很少,或者冲突的后果不是很严重,那么选乐观锁,因为有更好的并发性,而且容易实现。但是,
如果冲突的结果对于用户来说是痛苦的,那么就需要用悲观锁。
悲观锁策略可以通过加读写锁来处理。读数据的时候需要一个读锁;写数据的时候,需要一个写锁。对于读锁,可以一次多个人对同一份数据加锁,但是之哟啊有人得道一个
读锁,其他人就无法再得道写锁。一个人得道写锁,其他人就都不能再得道读写锁。
乐观锁的策略通常是建立在数据的某种版本标记上,可能是时间戳,也可能是顺序计数器。
死锁:悲观锁技术有一个特别的问题就是死锁。
有许多技术可以来处理死锁。一种是使用软件来检测死锁的发生。在这种情况下,需要选择一个牺牲者,放弃它的工作和所加的锁,以便其他人继续工作。另外一种方法是给每个
锁都加上时间限制。一旦达到时间限制,所加的锁就会失效,工作就会丢失。超时控制实现起来比死锁检测机制容易一些,但是会出现一个问题:实际上没有死锁的情况下,有人会
因为持有锁的时间太长而成为牺牲者。
超时控制和检测机制处理已经出现的死锁,而其他的方法则尽力防止死锁的发生。死锁出现的原因是人们在已经得到锁的情况下,还希望得到更多的锁。因为,防止死锁的方法就是
强制人们在开始工作的时候就获得所有可能需要的锁。
可以硬性规定每个人获取锁的顺序。
5.事务
1.ACID
1.原子性
2.一致性
3.持久性
4.隔离性
2.事务资源
用'事务资源'来表示可以进行事务处理的任何事务---即使用事务来控制并发过程。
为了处理最大的吞吐率,现代的事务处理系统被设计成保证事务尽可能短。为此,要尽可能不让事务跨越多个请求。跨越多个请求的事务称为长事务。另外一种办法是尽可能晚的
打开事务,使用事务延迟。
使用事务,需要清除的知道被锁住的到底是什么。对于数据库来说,事务系统锁住的是被访问的数据行,这样就可以允许多个事务同时访问一个表。然后,如果一个事务锁住了一个
表的许多行,则数据库无法处理那么多锁,只能将锁升级到锁住整个表---从而将其他事务锁在外面。这种锁升级对并发影响很大,这也是为什么不能在领域的层超类型级别上使用'对象'
表的原因。这样的表很容易导致锁升级,而锁住该表后其他对数据库的访问也被阻塞了。
3.减少事务隔离以提高灵活性
可串行化
可重复读,这时允许幻读。这种幻读出现在你向一个集合中加入一些元素而读的人只能看到其中一部分的时候。
读已提交,它允许不可重复读。
读未提交,允许脏读。
4.业务事务和系统事务
如果事务中的第4条语句违反了完整性约束,数据库必须回滚前3条语句,并通知调用者事务失败。如果所有4条语句都成功了,则它们的改变必须同时而不是一次一个对其他调用者生效。
然后,系统事务对于一个业务系统的用户来说没什么意义。对一个在线银行系统的用户来说,一个事务包括登录,选择账户,填写某些账单,最后点击ok按钮付款。这就是所谓的业务事务,
并且我们希望它能显示出与系统事务意义的ACID属性。
让业务事务支持acid属性的最简单办法是在单个系统事务中完整的执行业务事务。但是,业务事务常常要通过多次请求才能完成,因此用单个系统事务的实现会产生长系统事务。而大多数
的事务系统并不能很有效的支持长事务。使用长事务可以避免许多麻烦,然而,应用将失去可伸缩性,因为长事务使数据库成为主要瓶颈。另外,将长事务改写成短事务是一个复杂且不好理解
的过程。
把业务事务拆分成一些列短事务。这意味着只能自己为跨系统事务的业务事务提供acid支持---我们称之为离线并发问题。
事务原子性和持久性是最容易为业务事务所支持的acid属性。
业务事务的acid属性中最麻烦的是隔离性,没有隔离就没有一致性。一致性要求业务事务不要将记录集置于一种无效的状态下。在单个事务中,应用要支持一致性就需要满足所有的业务规则。
在跨多个事务的时候,应用需要保证一个会话不会破坏其他会话的工作,那样将使记录集处于丢失用户工作的无效状态。
业务事务预会话密切相关。在用户看来,会话就是一连串的业务事务。
6.离线并发控制的模式
应该尽可能让事务系统自己来处理并发的问题。
处理离线并发问题的首先是使用乐观离线锁,它在业务事务期间使用乐观的并发控制。将它作为首先是因为它易于编程实现,还能提供最好的灵活性。乐观离线锁的局限是:只能在提交数据的时候才
发现业务事务将要失败,在某些情况下,发现失败太迟的代价会很大。另外一种方法是使用悲观离线锁,它可以尽早的发现错误,但难以编程实现,而且会降低系统的灵活性。
7.应用服务器并发
到目前为止我们讨论了多个会话访问共享数据源的并发。另一种形式的并发是应用服务器自身的进程并发:应用服务器怎样处理并发请求的?区别在于,这里的并发并不涉及事务。
显式的多线程编程,假设锁和同步阻塞,太复杂了。我们的策略是尽可能避免显式处理同步和锁。
最简单的办法是使用每会话一进程,就是每个会话都在自己的进程中运行。最大的问题是大量的资源消耗,因为进程是昂贵的。我们可以使用进程池提高利用率。
可以通过在一个进程中运行多个线程来进一步提高吞吐率。这就是每会话一线程的方式。由于线程使用比进程少的多的服务器资源,可以用更少的硬件处理更多的请求,因此服务器效率更高。每一会话
一线程的问题是线程之间没有隔离,任何线程都可以访问它能访问的数据。尽管每会话一线程效率高,但它们有相同的伸缩性。而且有更好的健壮性---如果某一线程崩溃了,可能会导致整个进程垮掉,
而使用每会话一进程能限制这种破坏。
如果使用每会话一线程,最重要的是创建和进入一个隔离区。在隔离区中应用开发人员可以忽略多线程问题。最常用的方法是让线程每次都创建新的对象来处理请求,以保证这些对象不会被放到其他
线程可以看见他们的任何地方。这时对象是隔离的,因为其他线程无法引用他们。
https://juejin.im/post/5c4d1577f265da612638801c