我刚来的时候四个人,马上房间就是我一个人住了。。
你要改色的文字
前端传的日期格式,与数据库2021-08-28 12:00:00不匹配的问题,
将mysql-connect的依赖改成5.1.47解决
破案了,criteria.getSearchType()==”all”一直不成立,因为字符串常量在常量池,比较的是地址,
比较String内容是否相等应该用equal
有文件提交不了,可以git add -A 大写
Redis缓存与数据一致性问题
如何更新缓存中的数据,也就是说。
- 我是先更新缓存中的数据再更新数据库的数据;
- 还是先修改数据库中的数据再更新缓存中的数据
这就是我们经常会在面试遇到的问题,数据库的数据和缓存中的数据如何达到一致性?
首先,可以肯定的是,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 | void put(H key, |
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);