您的当前位置:首页如何在高并发环境下设计出无锁的数据库操作(Java版本) 转载

如何在高并发环境下设计出无锁的数据库操作(Java版本) 转载

2023-11-10 来源:六九路网

EXECUTOR.execute(new LatencyProcessor(logs));

class LatencyProcessor implements Runnable

{

public void run()  { 

// 这里可以任意的去修改内存数据。采用了异步。  }}

OK,看起来很漂亮。但是又有个问题出现了。在高速存取的过程中,非常有可能缓存还没有被更新,就被其他请求再次获取,得到了旧的数据。

 

4. 如何保证并发环境下缓存数据的唯一正确?

我们知道,如果只有读操作,没有写操作,那么这个行为是不需要加锁的。

我使用这个技巧,在缓存的上层,再加一层缓存,成为”一级缓存“,原来的就自然成为”二级缓存“。有点像CPU了对不?

一级缓存只能被”大循环“修改,但是可以被并发、”大循环“同时获取,所以是不需要锁的。

当发生数据库变动,分2种情况:

1)并发环境下的数据库变动,我们是允许有锁的存在,所以直接操作二级缓存,没有问题。

2)”大循环“环境下数据库变动,首先我们把变动数据存储在一级缓存,然后交给异步修正二级缓存,修正后删除一级缓存。

这样,无论在哪个环境下读取数据,首先判断一级缓存,没有再判断二级缓存。

这个架构就保证了内存数据的绝对准确。

而且重要的是:我们有了一个高效的无锁空间,去实现我们任意的业务逻辑。

 

最后,还有一些小技巧提升性能。

1. 既然我们的数据库操作已经被异步处理,那么某个时间,需要插库的数据可能很多,通过对表、主键、操作类型的排序,我们可以删除一些无效操作。例如:

a)同一个表同一个主键的多次UPdate,取最后一次。

b)同一个表同一个主键,只要出现Delete,前面所有操作无效。

2. 既然我们要对操作排序,必然会存在一个根据时间排序,如何保证无锁呢?使用

private final static AtomicLong _seq = new AtomicLong(0);

即可保证无锁又全局唯一自增,作为时间序列。

如何在高并发环境下设计出无锁的数据库操作(Java版本) 转载

标签:

小编还为您整理了以下内容,可能对您也有帮助:

如何在高并发环境下设计出无锁的数据库操作

1、对数据库表的字段访问比较均衡,业务导向明显(网上商城,多条业务线);2、对数据库表的字段访问比较均衡,业务导向不明显(对单一应用的高并发访问);3、对数据库表的单一字段访问比较集中(秒杀、大量用户对同一账户操作)不过对于一般的小型网站的应用,并发高的话 采用读写分离基本上就能解决问题,本文主要是针对大型网站高并发数据库设计讨论。
一、对数据库表的访问比较均衡,业务导向明显(网上商城,多条业务线)     像这种情况,一般采用数据库表垂直切分。

    垂直切分就是要把表按模块划分到不同数据库中,这种拆分在大型网站的演变过程中是很常见的。当一个网站还在很小的时候,只有小量的人来开发和维护,各模块和表都在一起,当网站不断丰富和壮大的时候,也会变成多个子系统来支撑,这时就有按模块和功能把表划分出来的需求。如下图所示:

技术分享技术分享

其实,相对于垂直切分更进一步的是服务化改造,说得简单就是要把原来强耦合的系统拆分成多个弱耦合的服务,通过服务间的调用来满足业务需求看,因此表拆出来后要通过服务的形式暴露出去,而不是直接调用不同模块的表,淘宝在架构不断演变过程,最重要的一环就是服务化改造,把用户、交易、店铺、宝贝这些核心的概念抽取成独立的服务,也非常有利于进行局部的优化和治理,保障核心模块的稳定性。这样一种拆分方式也是有代价的:

  • 表关联无法在数据库层面做
  • 单表大数据量依然存在性能瓶颈
  • 事务保证比较复杂
  • 应用端的复杂性增加
  • 上面这些问题是显而易见的,处理这些的关键在于如何解除不同模块间的耦合性,这说是技术问题,其实更是业务的设计问题,只有在业务上是松耦合的,才可能在技术设计上隔离开来。没有耦合性,也就不存在表关联和事务的需求。另外,大数据瓶颈问题可以采用水平切分。

    二、对数据库表的字段访问比较均衡,业务导向不明显(对单一应用的高并发访问)

        1)这种情况一般采用对数据库表进行水平切分。    上面谈到垂直切分只是把表按模块划分到不同数据库,但没有解决单表大数据量的问题,而水平切分就是要把一个表按照某种规则把数据划分到不同表或数据库里。例如像计费系统,通过按时间来划分表就比较合适,因为系统都是处理某一时间段的数据。而像SaaS应用,通过按用户维度来划分数据比较合适,因为用户与用户之间的隔离的,一般不存在处理多个用户数据的情况,下面是一个比较简单的按user_id来水平切分的例子:

    技术分享技术分享

    水平切分没有破坏表之间的联系,完全可以把有关系的表放在一个库里,这样就不影响应用端的业务需求,并且这样的切分能从根本上解决大数据量的问题。它的问题也是很明显的:

  • 当切分规则复杂时,增加了应用端调用的难度
  • 数据维护难度比较大,当拆分规则有变化时,需要对数据进行迁移
  • 对于第一个问题,可以参考如何整合应用端和数据库端。对于第二个问题可以参考一致性hash的算法,通过某些映射策略来降低数据维护的成本

    2)当然还可以把水平切分和垂直切分结合起来

    由上面可知垂直切分能更清晰化模块划分,区分治理,水平切分能解决大数据量性能瓶颈问题,因此常常就会把两者结合使用,这在大型网站里是种常见的策略,这可以结合两者的优点,当然缺点就是比较复杂,成本较高,不太适合小型网站,下面是结合前面两个例子的情况:

    技术分享技术分享

    三、对数据库表的单一字段访问比较集中(秒杀、大量用户对同一账户操作)

        对于这种情况有很多种解决方案,但是每一种都不是很完美:

        1)采用内存缓存或者缓存数据库来缓解数据的库的压力

           具体做法是:在利用内存缓存或者缓存数据库把后台数据库服务器上相关的表数据加载到内存中,所用用户高并发的对内存数据进行处理,然后再定时轮询的方式把内存的数据刷新到后台数据库表中,这种做法有以下问题:

         a)不能很好保持内存数据与数据库数据的一致性;

         b)如果出现断电、内存损坏等情况,会有数据丢失;

        2)采用对数据库表水平切分,然后在后台的程序中对各个表的数据整体控制

            例如,有10000亿人民币为1亿人并发提供贷款业务。在数据库中建立一个总表存下10000亿人民币,然后再建立10张分表,初始设为空;后台java程序在访问数据库时会有一个控制程序(中间件),开10个 线程池,每个线程池对应一个数据库分表,当中间件接受到贷款申请时,中间件就会根据用户的ID(可以ip地址,账户编号)hash到相应的线程去到总表中借款,这个借款数目可以根据总表的资金和用户的要借的资金去申请额度(比如用户申请10w,总表有1000亿,对应线程可以向总表申请10亿),存入相应分表,供这个用户提供贷款,如果再有下个用户再到此线程池操作数据库表,就直接操作,分表中金额不够的时候再到总表中借款。

        这样的设计解决了,高并发存储数据库的问题,但是增加了后台的程序设计的难度,加大了程序的耦合度。

        3)采用“记流水不记账“的方式应对

           还用上一个例子,这种方式,需要在数据库中设计两个表,一个用来存储账户金额(账户表),另一个记录”流水“(流水表), 所谓”记流水“是指每当有个请求到来,就向流水表中插入一条记录,然后定时对所插入的记录进行统计,update账户表的数据,当然这种方式,需要在内存中增加变量,来控制所用用户的贷款不能超过所贷款的总金额。这种处理方式是数据库端处理秒杀、高并发集中访问数据库表字段的有效方式,使用比较广泛。

        4)针对网购秒杀还有其针对性的设计,因为网购秒杀和高并发操作银行账户不同,网购秒杀允许用户请求丢失,简单的来说,只需要在内存缓存或者内存数据库(充当队列)中保存较早的用户请求,然后再异步的处理这些请求来操作数据库(更新数据库)。

















    版权声明:本文为博主原创文章,未经博主允许不得转载。

    高并发下的系统设计(偏数据库设计)

    标签:

    如何在高并发环境下设计出无锁的数据库操作

    1、对数据库表的字段访问比较均衡,业务导向明显(网上商城,多条业务线);2、对数据库表的字段访问比较均衡,业务导向不明显(对单一应用的高并发访问);3、对数据库表的单一字段访问比较集中(秒杀、大量用户对同一账户操作)不过对于一般的小型网站的应用,并发高的话 采用读写分离基本上就能解决问题,本文主要是针对大型网站高并发数据库设计讨论。
    一、对数据库表的访问比较均衡,业务导向明显(网上商城,多条业务线)     像这种情况,一般采用数据库表垂直切分。

        垂直切分就是要把表按模块划分到不同数据库中,这种拆分在大型网站的演变过程中是很常见的。当一个网站还在很小的时候,只有小量的人来开发和维护,各模块和表都在一起,当网站不断丰富和壮大的时候,也会变成多个子系统来支撑,这时就有按模块和功能把表划分出来的需求。如下图所示:

    技术分享技术分享

    其实,相对于垂直切分更进一步的是服务化改造,说得简单就是要把原来强耦合的系统拆分成多个弱耦合的服务,通过服务间的调用来满足业务需求看,因此表拆出来后要通过服务的形式暴露出去,而不是直接调用不同模块的表,淘宝在架构不断演变过程,最重要的一环就是服务化改造,把用户、交易、店铺、宝贝这些核心的概念抽取成独立的服务,也非常有利于进行局部的优化和治理,保障核心模块的稳定性。这样一种拆分方式也是有代价的:

  • 表关联无法在数据库层面做
  • 单表大数据量依然存在性能瓶颈
  • 事务保证比较复杂
  • 应用端的复杂性增加
  • 上面这些问题是显而易见的,处理这些的关键在于如何解除不同模块间的耦合性,这说是技术问题,其实更是业务的设计问题,只有在业务上是松耦合的,才可能在技术设计上隔离开来。没有耦合性,也就不存在表关联和事务的需求。另外,大数据瓶颈问题可以采用水平切分。

    二、对数据库表的字段访问比较均衡,业务导向不明显(对单一应用的高并发访问)

        1)这种情况一般采用对数据库表进行水平切分。    上面谈到垂直切分只是把表按模块划分到不同数据库,但没有解决单表大数据量的问题,而水平切分就是要把一个表按照某种规则把数据划分到不同表或数据库里。例如像计费系统,通过按时间来划分表就比较合适,因为系统都是处理某一时间段的数据。而像SaaS应用,通过按用户维度来划分数据比较合适,因为用户与用户之间的隔离的,一般不存在处理多个用户数据的情况,下面是一个比较简单的按user_id来水平切分的例子:

    技术分享技术分享

    水平切分没有破坏表之间的联系,完全可以把有关系的表放在一个库里,这样就不影响应用端的业务需求,并且这样的切分能从根本上解决大数据量的问题。它的问题也是很明显的:

  • 当切分规则复杂时,增加了应用端调用的难度
  • 数据维护难度比较大,当拆分规则有变化时,需要对数据进行迁移
  • 对于第一个问题,可以参考如何整合应用端和数据库端。对于第二个问题可以参考一致性hash的算法,通过某些映射策略来降低数据维护的成本

    2)当然还可以把水平切分和垂直切分结合起来

    由上面可知垂直切分能更清晰化模块划分,区分治理,水平切分能解决大数据量性能瓶颈问题,因此常常就会把两者结合使用,这在大型网站里是种常见的策略,这可以结合两者的优点,当然缺点就是比较复杂,成本较高,不太适合小型网站,下面是结合前面两个例子的情况:

    技术分享技术分享

    三、对数据库表的单一字段访问比较集中(秒杀、大量用户对同一账户操作)

        对于这种情况有很多种解决方案,但是每一种都不是很完美:

        1)采用内存缓存或者缓存数据库来缓解数据的库的压力

           具体做法是:在利用内存缓存或者缓存数据库把后台数据库服务器上相关的表数据加载到内存中,所用用户高并发的对内存数据进行处理,然后再定时轮询的方式把内存的数据刷新到后台数据库表中,这种做法有以下问题:

         a)不能很好保持内存数据与数据库数据的一致性;

         b)如果出现断电、内存损坏等情况,会有数据丢失;

        2)采用对数据库表水平切分,然后在后台的程序中对各个表的数据整体控制

            例如,有10000亿人民币为1亿人并发提供贷款业务。在数据库中建立一个总表存下10000亿人民币,然后再建立10张分表,初始设为空;后台java程序在访问数据库时会有一个控制程序(中间件),开10个 线程池,每个线程池对应一个数据库分表,当中间件接受到贷款申请时,中间件就会根据用户的ID(可以ip地址,账户编号)hash到相应的线程去到总表中借款,这个借款数目可以根据总表的资金和用户的要借的资金去申请额度(比如用户申请10w,总表有1000亿,对应线程可以向总表申请10亿),存入相应分表,供这个用户提供贷款,如果再有下个用户再到此线程池操作数据库表,就直接操作,分表中金额不够的时候再到总表中借款。

        这样的设计解决了,高并发存储数据库的问题,但是增加了后台的程序设计的难度,加大了程序的耦合度。

        3)采用“记流水不记账“的方式应对

           还用上一个例子,这种方式,需要在数据库中设计两个表,一个用来存储账户金额(账户表),另一个记录”流水“(流水表), 所谓”记流水“是指每当有个请求到来,就向流水表中插入一条记录,然后定时对所插入的记录进行统计,update账户表的数据,当然这种方式,需要在内存中增加变量,来控制所用用户的贷款不能超过所贷款的总金额。这种处理方式是数据库端处理秒杀、高并发集中访问数据库表字段的有效方式,使用比较广泛。

        4)针对网购秒杀还有其针对性的设计,因为网购秒杀和高并发操作银行账户不同,网购秒杀允许用户请求丢失,简单的来说,只需要在内存缓存或者内存数据库(充当队列)中保存较早的用户请求,然后再异步的处理这些请求来操作数据库(更新数据库)。

















    版权声明:本文为博主原创文章,未经博主允许不得转载。

    高并发下的系统设计(偏数据库设计)

    标签:

    如何在高并发环境下设计出无锁的数据库操作

    mongodb的一个设计方案是MVCC机制

    面试Java开发时问到高并发怎么处理的,还有sql优化有哪些办法,有哪位大神知道啊,新手!!

    Java开发高并发的处理方法:

      最基础的地方做起,优化我们写的代码,减少必要的资源浪费

      避免频繁的使用new对象,对于整个应用只需要存在一个实例的类,我们可以使用单例模式。对于String连接操作,使用      StringBuffer或StringBuilder,对于工具类可以通过静态方法来访问。

      避免使用错误的方式,尽量不用instanceof做条件判断。使用java中效率高的类,比如ArrayList比Vector性能好。

      图片服务器分离

      对于web服务器来说,图片是最消耗资源的,于是我们有必要把图片与页面进行分离,我们把图片放到的图片服务器。这样的架构可以降低提供页面访问请求的服务器系统压力,并且可以保证系统不会因为图片的问题而崩溃。在图片服务器上,我们可以对不同的配置进行优化。

      缓存

      具体接触过的缓存机制是hibernate的缓存机制。为了避免每次都向数据库中取得数据,我们把用户常常访问到的数据放到内存中,甚至缓存十分大的时候我们可以把内存中的缓存放到硬盘中。还有高级的分布式缓存数据库使用,都可以增加系统的抗压力。

      分批传送

    在做某项目的时候,一次传递的参数太多,而且数据库规定一次最多传递的参数最多是三万条,当时有五万条记录,那怎么传送呢?最终是分批传送,电梯里一次乘不下那么多的人,会报超重的bug,那就分批把人送上去。

    还有一次在考试系统中,如果那么多的考试人员同时提交到数据库中,数据库的压力增大,有时会被down掉,当时采用的方法是使用ajax异步传输,没有等待考生点击提交按钮的时候,就把考生的答案自动提交,这样也避免了突然断电考生前面做过的题出现丢失的现象。

    DB优化

    在数据库设计的时候就要考虑到后期的维护,数据库三范式是我们设计数据库索要遵循的原则。

    索引的建立:建立索引要适当,如果一个表经常用来被查询,对于增加和修改很少被用到,我们就可以为这个表建立索引,因为对于增加和修改和删除操作时,我们对索引的维护要大大超过索引给我们带来的效率。

    表字段的类型选择要恰当。包括字段的长度、类型等,要根据实际存储的数据进行选择,长度不要过长,否则会影响效率。

    外键要慎用,因为主键代表这一张表,而外键代表一群表,对表之间进行了关联,在删除修改等需要我们关联。

    在数据库操作上。 尽量使用prepareStatement,少用Statement,因为PrepareStatement是进行预编译的。

    connection设置为readOnly,Connection是对书库连接,属于重量级,我们使用即可。

    连接池的使用,我们可以修改数据库默认的连接数。

    显示全文