黑马点评(速通版)
个人环境 Ubuntu 24.04
项目配置
-
repo: 搜一搜就行 https://github.com/cs001020/hmdp?tab=readme-ov-file
-
idea config: 降java版本到11就能不报错
-
redis, mysql: 搜索即可 systemd 启动
-
nginx 稍微复杂一点, 给的是win下的nginx, 配完systemd之后,用他的
nginx.conf替换/etc/nginx/nginx.conf(记得备份) 然后修改
# 指定前端项目所在的位置
location / {
root /home/ayanami/www/hmdp/html/hmdp; # 修改此处, 改为${下载的nginx文件夹原来位置}/hmdp/html/hmdp
<!--truncate--> index index.html index.htm;
}
即可
可能还需要在顶部修改 user ${Your User Name}
ps: nginx似乎套一层后会让之前的连接的token之类invalid掉, 例如b站 kimi 退出登录
目前不知道除了简单sudo systemctl stop nginx的方法
会不会nginx丢docker里面之类别放本机跑好一点
登录
threadlocal
redis 存 session, 做水平拓展负载均衡
用户校验 phone key, 验证码 value
session信息, value 用hash而不用string(json)
存储session数据 value是一个"HashMap", 支持单字段crud, 内存占用少
key ? 生成一个唯一 token, 返回给客户端
redis key加业务前缀"login:code:"
设置有效期
redisTemplate.opsForValue().set(LOGIN_CODE_KEY + phone, code, 2, TimeUnit.MINUTES);
设置30min有效期
// 保存用户信息到redis
UserDTO userDTO = new UserDTO();
BeanUtils.copyProperties(user, UserDTO.class);
String token = UUID.randomUUID().toString(true);
Map<String, Object> userMap = BeanUtil.beanToMap(userDTO);
stringRedisTemplate.opsForHash().putAll(LOGIN_USER_KEY + token, userMap);
stringRedisTemplate.expire(LOGIN_USER_KEY + token, 30, TimeUnit.MINUTES);
return Result.ok();
"用户不活跃30min": 在拦截器里面更新token有效期(再调用一次expire就行)
自定义拦截器不能做依赖注入? 老实写构造函数, 在外部@Configuration的Configurer中注入, 再调用registry时构造
快捷键缩写行尾.var
这样redis + threadlocal就绕开了登录中tomcat的session机制,减少session传递开销
前端得到token在请求头里面放
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 1.获取请求头token
// 2.由token获取redis用户
// 3.判断token是否存在
// 4.保存用户到threadlocal
// 5.刷新用户token有效期
String token = request.getHeader("authorization");
if (StrUtil.isBlank(token)){
response.setStatus(401); // unauthorized
return false;
}
Map<Object, Object> userMap = stringRedisTemplate.opsForHash().entries(LOGIN_USER_KEY + token);
UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);
// exist, save user info
UserHolder.saveUser(userDTO);
stringRedisTemplate.expire(LOGIN_USER_KEY + token, LOGIN_USER_TTL, TimeUnit.MINUTES);
return true;
}
拦截器只拦需要登录的路径->再加一个新的拦截器, 拦所有路径
拦截器顺序, 可以.order调整优先级, 从order小到order大执行, 也可以默认(相同order按照添加顺序)
缓存一致性问题
- 内存淘汰(redis机制)
- 超时剔除(用户指定)
- 主动更新(立刻触发)
低一致性的部分内存淘汰或者超时剔除就行, 如类型
高一致性主动更新+超时兜底, 如详情
主动更新
- cache aside 缓存调用者负责更新
- read/write through 缓存和数据库整体作为一个服务维持一致性
- Write Behind Caching 调用者只操作缓存,其他线程异步将缓存持久化到db(可加批处理)
后两种较复杂
cache aside
删除还是更新?
删除是一种lazy alloc, 如果在多次更新中间没有查询就浪费了,还不如删,一般删除缓存
缓存db原子性, 单体->事务, 分布式->TCC等分布式事务
先后?
先删缓存, 再更新db
并发下数据可以不一致, T1 delete cache,T2 cache miss, query db, update cache, T1 write db
得到缓存和db不一致! 并且触发概率很高, 因为查db慢
先更新db再删除缓存, 大部分时候是一致的, 除了
- 缓存已失效
- T1 查缓存, 查数据库
- T2 更新数据库, 删缓存
- T1 更新缓存
这样才会不一致, 但缓存已失效和T1查操作比T2更新操作还慢两个条件同时满足是罕见的, 所以好
所以