java 并发 读取和更新百万 Mysql 出现问题,求大神看看

流浪鲨鱼 发布于 04/17 10:43
阅读 464
收藏 2

场景:

有一个表,每次请求都会取一条数据,按次数(Count)的字段排序,次数最少的数据优先取出(现在身为小白的我用的是select * from 表 where 条件 order by 次数(Count)limit 1),每次取出数据 里面记录次数(Count)的字段 +1 (当日凌晨 01:00 次数(Count) 全部清空重新排序)

数据库配置是 mysql 5.7 ,数据量现在是 80W+,在稳步上升中

问题:

这个方法在测试的时候是没有问题的,但是在实际生产的时候发现,并发请求的时候 有些数据明明还没用到,次数(Count)有些数据明明还是 0 但是排列在前面的数据确 有出现 次数(Count)字段已经加到 2 了,针对这个问题,我又改成了 

synchronized(){
    查找数据
    更新次数 + 1
}

这种形式,虽然解决了 数据按我们理想的方案取出,更新了,但是也出现了新的问题,在并发的情况,所有的线程都在等待 synchronized 释放,导致并发上去Http 请求马上就满了,完全处理不过来。

按着这个思路我又联想了分布式的处理方式,我又设计了新的写法

int Index = 1;     //初始化取得的排序位置
synchronized (IndexPri){    //上锁
    if(Config.INDEX == 21){    //最大取得的排序位置
        Config.INDEX = 0;    //初始化取得的排序位置
    }
    Index = Config.INDEX;    //赋值
    Config.INDEX = Config.INDEX + 1;    //位置 +1
}
Entity entity = null;    //返回数据
switch (Index){
    case 0: entity = findFastPool0(params); break;
    case 1: entity = findFastPool1(params); break;
    ...
    case 20: entity = findFastPool20(params); break;
}

因为怕轮了一圈 21 个都过一遍了第一个还没查好,所以 每个 findFastPool* 里面都是

synchronized(this){
    Entity entity = findAndUpdata(params,Index)
    return entity 
}

findAndUpdata 里面则是不需要加锁的

查找(select * from 表 where 条件 order by 次数(Count)limit Index*5,1)+ 更新语句

目前看起来是没有什么问题(测试也没有问题),但是在实际运行的时候发现,这个地方还是会出现有些数据还未使用,但是其他数据已经使用 2次,并且小概率出现第一条数据一直没有被调用过(次数(Count)为0时,如果手动把次数(Count)变成其他数字则正常调用,或者直接把默认改为 1 ,就不会出现第一条数据一直没有被调用的情况,不明所以),各路大神帮忙看看有什么办法解决,致谢

加载中
1
魔力猫
魔力猫

建议你用一个中间层缓存,然后分3个区块。初始所有的记录id都在0号区,取出来就放到1。0号区取完,开始取1号区,取完放到2号。1号区取完,取2号,放0号。3个池子循环。

如果你不需要更多的话,这样我觉得应该可以满足初步需求吧。你考虑一下缓存类方案。不然你这样取,每次排序都是很大的问题。如果count上面建索引,一样无法满足需求,因为索引可选择性太差,排序也难以提速。

魔力猫
魔力猫
回复 @流浪鲨鱼 : 启动时间长是因为预热。你80W数据入缓存,10分钟也不算长了。如果你认为启动时缓存预热造成启动时间过长,可以把这部分代码独立出来。每次启动前,先确认缓存是否已经预热好了。预热好了就不用反复预热。毕竟redis和你的应用是独立的。
流浪鲨鱼
流浪鲨鱼
一次性加载到缓存里面也设想过,但是启动程序的时候就加载了缓存,让我原先的程序只需要15-25s的启动时间直接上送到了 10分钟 以上(大概是缓存做法不对),大佬,有可以实现的方案么,现学缓存 Redis
流浪鲨鱼
流浪鲨鱼
是的 每次排序是个很大的问题,所以每次查的时候都会花费很多时间,我有给表上了索引,但是没次更新一条数据感觉都会浪费很多的性能,CPU 里面 mysql 的使用率也居高不下
1
DeMoNHaDeS
DeMoNHaDeS

看描述的意思是想要按一定顺序逐条遍历表中的数据。但是,每次查询完一条记录后,在查询下一条记录前,都不能够保证表里的数据不被其他的请求修改,而根据被修改的字段排序,当然就无法保证顺序。

对于这个问题,在程序中加锁不是一个正常的解决方案。这其实是功能设计上的问题,不清楚你真正想实现什么。查询时候的顺序和字段被修改后的顺序是冲突的。也许每次先查询一个顺序的id出来,然后根据id遍历能满足你的需求。

DeMoNHaDeS
DeMoNHaDeS
回复 @流浪鲨鱼 : 根据你描述,按我说的排序之后把id查出来然后根据id遍历是可以全部轮询一边的,不知道能不能满足你的需求。缓存可以做,但是我觉得可能需求和功能实现的方式上需要再考虑一下
流浪鲨鱼
流浪鲨鱼
我想要的功能是 确保 所有的数据都轮查一遍,之后在开始查第二遍,并且不中间不会重复查询二次,做缓存的话有什么建议么,现在暂时会的缓存是 Redis,才开始学那种
1
魔力猫
魔力猫

另外一个办法就是做队列。一边按一定算法往队列里面填信息,填了就加1。另一边只从队列里面读。避免多线程读和多线程更新。

1
gitOpen_1
gitOpen_1

可以在读取数据库数据后,首先使用redis的setnx 写入该条数据+次数的唯一标识,如果写失败了则丢弃这条数据重新去数据库取。直到执行成功。注意1是这个键必须是唯一标识加计数的组合,其次要给每条数据加上一个高于你程序操作最长时间的超时

返回顶部
顶部