9月3日

我刚来的时候四个人,马上房间就是我一个人住了。。

你要改色的文字

前端传的日期格式,与数据库2021-08-28 12:00:00不匹配的问题,

将mysql-connect的依赖改成5.1.47解决

破案了,criteria.getSearchType()==”all”一直不成立,因为字符串常量在常量池,比较的是地址,

比较String内容是否相等应该用equal

有文件提交不了,可以git add -A 大写

Redis缓存与数据一致性问题

如何更新缓存中的数据,也就是说。

  1. 我是先更新缓存中的数据再更新数据库的数据;
  2. 还是先修改数据库中的数据再更新缓存中的数据

这就是我们经常会在面试遇到的问题,数据库的数据和缓存中的数据如何达到一致性?

首先,可以肯定的是,redis中的数据和数据库中的数据不可能保证事务性达到统一的,这个是毫无疑问的,

所以在实际应用中,我们都是基于当前的场景进行权衡降低出现不一致问题的出现概率。

此外我们还可以通过给缓存设置一个过期时间,无论以上的两种操作使用哪一种,都能够在理论上保持数据的最终一致性。

在写数据的时候,都以数据库为主,先把数据库数据写入后,再更新缓存

如果失败了,那么原来的缓存无论是否存在,那么只要经过过期时间,那么读操作时,

就会重新从数据库中读出,然后同步到缓存中。

我们接着讨论以上两种操作:

1.先删除缓存,再更新数据库。在大型分布式系统环境下,就需要考虑并发时的问题了。

这种处理方式,可能会有如下情况:

线程A,写入(更新)数据时,先删除缓存后,

同时,有一个线程B,此时去读数据,先去缓存中读,此时刚被线程A删除了,

于是去数据库读还未被线程A更新的数据,这时候就会读到脏数据了。

也就是说读到了A还没写入的数据

紧接着,线程B会把脏数据写入缓存,而线程A会把更新后的数据写入数据库。导致缓存与数据不一致。

参考方式: 给缓存设置过期时间,让B读到的那个脏数据过段时间自己消失,否则会一直读到脏数据。

处理方式:网上有几种处理方式:

1. 双删策略

先删除缓存。

写入数据库,休眠1秒,执行删除缓存(目的是把1秒内产生的脏数据重新从缓存中删除)

缺点: 需要把控好休眠时间,同时时间可能会过长,当请求量很大时,再短的时间也会造成响应过长。

其次这个请求时间是跟读操作时,会产生脏数据的时间有关的。

B去读数据库的时候,读到的是1秒后写入到数据库的数据,

当线程B去读数据库时,可能读到的是还未更新到从库的脏数据,因为当我们休眠是,不仅需要 考虑读操作的完整执行完的时间,还需要加 主从复制这段时间的几百Ms。

2. 先修改数据库中的数据再更新缓存中的数据

同样存在这种情况,线程A查询操作(读操作),线程B更新操作。

线程A先执行,线程B后执行。线程A先查询时,去缓存读取时,缓存失效了,

此时继续去数据库读,

考虑线程执行的随机性,

这时候线程B修改数据库后,把数据写入缓存了。

在此之后,线程A才执行把数据写入缓存中,此时缓存中的任然是脏数据。

分为以下几个步骤:
(1)缓存刚好失效
(2)请求A查询数据库,得一个旧值
(3) 请求B将新值写入数据库
(4)请求B删除缓存
(5)请求A将查到的旧值写入缓存

发生上述情况有一个先天性条件,就是步骤(3)的写数据库操作比步骤(2)的读数据库操作耗时更短,才有可能使得步骤(4)先于步骤(5)。

可是,大家想想,数据库的读操作的速度远快于写操作的(不然做读写分离干嘛,做读写分离的意义就是因为读操作比较快,耗资源少)

因此步骤(3)耗时比步骤(2)更短,这一情形很难出现。


join()的作用是:“等待该线程终止”

这里需要理解的就是该线程是指的主线程等待子线程的终止

主线程需要等待子线程执行完成之后再结束,这个时候就要用到join()方法

在很多情况下,主线程生成并起动了子线程,

如果子线程里要进行大量的耗时的运算,主线程往往将于子线程之前结束,

但是如果主线程处理完其他的事务后,需要用到子线程的处理结果,

③yield():暂停当前正在执行的线程对象,并执行其他线程。

Thread.yield()方法作用是:暂停当前正在执行的线程对象,并执行其他线程。

yield()应该做的是让当前运行线程回到可运行状态,以允许具有相同优先级的其他线程获得运行机会。

因此,使用yield()的目的是让相同优先级的线程之间能适当的轮转执行。

即 自己开个小差,让别人执行一下马上就自己执行

但是,实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。

结论:yield()从未导致线程转到等待/睡眠/阻塞状态。是给了别人处理机资源


1
redisTemplate.opsForHash().put(key, item, value);

put有三个参数,分别是

put(H key, HK hashKey, HV value)

1
2
3
void put(H key,
HK hashKey,
HV value)

Set the value of a hash hashKey.

  • Parameters:

    key - must not be null.

    hashKey - must not be null.

    value -

Redis hash 是一个string类型的field和value的映射表,hash特别适合用于存储对象。
Redis 中每个 hash 可以存储 2^32 - 1 键值对(40多亿)。

1
redisTemplate.opsForHash().put("user","age",18);
1
redisTemplate.opsForHash().put("user","name","Lisa");

观察以上用法,和直接命令行 hset user age 18无异

相当于是对象是user,age是属性,18是属性值

/**
* 获取hashKey对应的所有键值
*
* @param key 键
* @return 对应的多个键值
*/
public Map<Object, Object> hmget(String key) {
return redisTemplate.opsForHash().entries(key);