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

一个在线2k的游戏,每秒钟并发都吓死人。传统的hibernate直接插库基本上是不可行的。我就一步步推导出一个无锁的数据库操作。

1. 并发中如何无锁。

一个很简单的思路,把并发转化成为单线程。Java的Disruptor就是一个很好的例子。如果用java的concurrentCollection类去做,原理就是启动一个线程,跑一个Queue,并发的时候,任务压入Queue,线程轮训读取这个Queue,然后一个个顺序执行。

在这个设计模式下,任何并发都会变成了单线程操作,而且速度非常快。现在的node.js, 或者比较普通的ARPG服务端都是这个设计,“大循环”架构。

这样,我们原来的系统就有了2个环境:并发环境 + ”大循环“环境

并发环境就是我们传统的有锁环境,性能低下。

”大循环“环境是我们使用Disruptor开辟出来的单线程无锁环境,性能强大。

2. ”大循环“环境 中如何提升处理性能。

一旦并发转成单线程,那么其中一个线程一旦出现性能问题,必然整个处理都会放慢。所以在单线程中的任何操作绝对不能涉及到IO处理。那数据库操作怎么办?

增加缓存。这个思路很简单,直接从内存读取,必然会快。至于写、更新操作,采用类似的思路,把操作提交给一个Queue,然后单独跑一个Thread去一个个获取插库。这样保证了“大循环”中不涉及到IO操作。

问题再次出现:

如果我们的游戏只有个大循环还容易解决,因为里面提供了完美的同步无锁。

但是实际上的游戏环境是并发和“大循环”并存的,即上文的2种环境。那么无论我们怎么设计,必然会发现在缓存这块上要出现锁。

3. 并发与“大循环”如何共处,消除锁?

我们知道如果在“大循环”中要避免锁操作,那么就用“异步”,把操作交给线程处理。结合这2个特点,我稍微改下数据库架构。

原本的缓存层,必然会存在着锁,例如:

public TableCache

{
  private HashMap<String, Object> caches = new ConcurrentHashMap<String, Object>();
}

这个结构是必然的了,保证了在并发的环境下能够准确的操作缓存。但是”大循环“却不能直接操作这个缓存进行修改,所以必须启动一个线程去更新缓存,例如:

private static final ExecutorService EXECUTOR = Executors.newSingleThreadExecutor();

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);

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

时间: 2024-09-30 06:11:10

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

数据库 之 高并发环境下的规则

原文:数据库 之 高并发环境下的规则 本文大部分转至沈剑老师,加上自己的一些见解. 本文前提 高并发环境 规则要点 1) 数据库字符集使用utf8mb4 无乱码风险.万国码 2)禁止使用存储过程.视图.触发器.Event 高并发大数据的互联网业务,架构设计思路是"解放数据库CPU,将计算转移到服务层",并发量大的情况下,这些功能很可能将数据库拖死,业务逻辑放到服务层具备更好的扩展性,能够轻易实现"增机器就加性能".数据库擅长存储与索引,CPU计算还是上移吧 3)禁止

高并发环境下生成唯一流水号

高并发环境下生成唯一流水号的主要思路有两种: 第一种是有一个控制全局的变量确保每个流水号的唯一性: 第二种是每台机器根据算法自己生成在系统中无冲突的流水号: 假设流水号的长度是128位(16字节): 第一种实现方法:(1)采用数据库的自增主键确保唯一性: Database.java package mine; import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import j

MQ在高并发环境下,如果队列满了,如何防止消息丢失?

1.为什么MQ能解决高并发环境下的消息堆积问题? MQ消息如果堆积,消费者不会立马消费所有的消息,不具有实时性,所以可以解决高并发的问题. 性能比较好的消息中间件:Kafka.RabbitMQ,RocketMQ. 2.什么情况下会产生消息丢失的现象? 消息队列满了的情况下. 3.如何解决消息丢失的问题? (1)生产者可以采用重试机制.因为消费者会不停的消费消息,可以重试将消息放入队列. 如果还是不行,可以将消息记录到数据库,后期做补偿.(不太推荐,不方便) (2)死信队列,可以理解为备胎.(推荐

高并发场景下秒杀项目静态锁的使用疑问

题:高并发场景下秒杀项目静态锁的使用疑问场景:我们有一个秒杀平台,可以提供所有接入公司创建的秒杀活动,简单描述如下:1.秒杀10袋洗衣粉,开始时间12:00(项目ID:A001)2.秒杀iPhone5,开始时间12:00(项目ID:A002)3.秒杀水杯,开始时间12:00(项目ID:A003)... ...(项目ID:A004-A009)10.秒杀ThinkPad,开始时间12:00(项目ID:A010) 例如上面,同时有十个秒杀,都是12:00整开始,每个秒杀之间没有任何关系. 按照我之前的

高并发环境下低级死循环bug

业务背景 在内存中,对mq消息进行分类计数. 问题描述 生产环境,运行一段时间后,发现消息队列有大量堆积.如果把计数逻辑注释掉,只接收用户访问消息而不进行处理,则mq队列无堆积.mq栈dump信息如下: ConsumeMessageThread_75    TID: 214 STATE: WAITING ConsumeMessageThread_75    sun.misc.Unsafe.park(Native Method) ConsumeMessageThread_75    java.ut

【高并发】高并发环境下诡异的加锁问题(你加的锁未必安全)

声明 特此声明:文中有关支付宝账户的说明,只是用来举例,实际支付宝账户要比文中描述的复杂的多.也与文中描述的完全不同. 前言 很多网友留言说:在编写多线程并发程序时,我明明对共享资源加锁了啊?为什么还是出问题呢?问题到底出在哪里呢?其实,我想说的是:你的加锁姿势正确吗?你真的会使用锁吗?错误的加锁方式不但不能解决并发问题,而且还会带来各种诡异的Bug问题,有时难以复现! 在上一篇<[高并发]如何使用互斥锁解决多线程的原子性问题?这次终于明白了!>一文中,我们知道在并发编程中,不能使用多把锁保护

高并发环境下,Redisson实现redis分布式锁

原文:http://tlzl0526-gmail-com.iteye.com/blog/2378853 在一些高并发的场景中,比如秒杀,抢票,抢购这些场景,都存在对核心资源,商品库存的争夺,控制不好,库存数量可能被减少到负数,出现超卖的情况,或者 产生唯一的一个递增ID,由于web应用部署在多个机器上,简单的同步加锁是无法实现的,给数据库加锁的话,对于高并发,1000/s的并发,数据库可能由行锁变成表锁,性能下降会厉害.那相对而言,redis的分布式锁,相对而言,是个很好的选择,redis官方推

Java高并发程序设计(十)--无锁

锁是一种悲观策略,总是觉得会出问题,所以小心翼翼地操作. 无锁是一种乐观策略,总是假设不会出现问题,如果出现问题,那就重新操作.无锁一般使用CAS作为策略. 比较交换CAS: CAS算法包括三个参数:需要更新的变量,预期值,更新值.只有当需要更新的值等于预期值时,说明其他线程没有对它进行操作,使需要更新值等于更新值. 在java.util.concurrent.atomic中,实现了很多无锁的类型: 用AtomicInteger写个小例子: public class demo implement

JMeter高并发场景下存在请求无数据

刚好失败33个请求