Idea - 编程中的tips
github网页查看方式
首先进入 https://github.com/klc407073648/cpp-notes
然后,键盘输入句号 。
编程注意事项
- 引入一个库时,先写测试类,即小模块功能验证后使用。例如,使用POCO库的LocalDateTime等
- 缓存的使用,一定要设置过期时间,不能无限期地使用,得到资源释放,重复利用的功能。
- 分布式锁等复杂逻辑,尽量采用现有的框架实现,不要自己写!!!
- 工作规划 ———— 写文档,列出需求背景、需求分析、设计方案选择、具体实现(库表设计、增删改查、业务逻辑(PO,重点需求))、以及各部分工作时间的安排
- 为什么需要请求参数包装类(DTO)?
- 请求参数名称 / 类型和实体类不一样
- 有一些参数用不到,如果要自动生成接口文档,会增加理解成本
- 对个实体类映射到同一个对象
- 为什么需要包装类?
- 可能有些字段需要隐藏,不能返回给前端。或者有些字段某些方法是不关心的
数据查询慢
用缓存:提前把数据取出来保存好(通常保存到读写更快的介质,比如内存),就可以更快地读写。
缓存
作用
提前把数据取出来保存好(通常保存到读写更快的介质,比如内存),就可以更快地读写。
分类
- Redis(分布式缓存)———— 本质**:key-value存储系统**
- memcached(分布式)
- Etcd(云原生架构的一个分布式存储,存储配置,扩容能力)
- 本地缓存(内存 Map)
设计缓存key
在项目使用过程中,往往多个模块都会用到缓存功能,可以使用 systemId:moduleId:func:options
来区分。
举例:
- yupao:user:recommed:userId
- hmdp:user:login:token
redis 内存不能无限增加,一定要设置过期时间!!!
缓存预热
问题:第一个用户访问还是很慢,也能一定程度上保护数据库
优点:
- 解决上面的问题,可以让用户始终访问很快
缺点:
- 增加开发成本(需要额外的开发、设计)
- 预热的时机和时间如果错了,有可能你缓存的数据不对或者太老
- 需要占用额外空间
怎么缓存预热?
- 定时
- 模拟触发(手动触发)
实现
用定时任务,每天刷新所有用户的推荐列表
注意点:
- 缓存预热的意义(新增少、总用户多)
- 缓存的空间不能太大,要预留给其他缓存空间
- 缓存数据的周期(此处每天一次)
分析优缺点的时候,要打开思路,从整个项目从 0 到 1 的链路上去分析
控制定时任务的执行
前文提及定时任务的执行,需要进行额外的控制
- 浪费资源,想象 10000 台服务器同时 “打鸣”
- 脏数据,比如,定时任务用于插入数据,会引发重复插入
要控制定时任务在同一时间只有 1 个服务器能执行
分离定时任务程序和主程序,只在 1 个服务器运行定时任务。————成本太大
写死配置,每个服务器都执行定时任务,但是只有 ip 符合配置的服务器才真实执行业务逻辑,其他的直接返回。————成本最低;但是服务器的 IP 可能是不固定的,把 IP 写的太死了。
动态配置,配置是可以轻松的、很方便地更新的(代码无需重启),但是只有 ip 符合配置的服务器才真实执行业务逻辑。
- 可以采用
- 数据库
- Redis
- 配置中心(Nacos、Apollo、Spring Cloud Config)
- 问题:服务器多了、IP 不可控还是很麻烦,还是要人工修改
- 可以采用
分布式锁,只有抢到锁的服务器才能执行业务逻辑。
- 坏处:增加成本,例如Redis服务器成本,人工编码成本;
- 好处:不用手动配置,多少个服务器都一样。
Zookeeper 实现(不推荐)
注意事项**:单机就会存在单点故障。**
锁
有限资源的情况下,控制同一时间(段)只有某些线程(用户 / 服务器)能访问到资源。
- Java 实现锁:synchronized 关键字、并发包的类 ———— 问题:只对单个 JVM 有效
- C++ 实现锁:进程中启动多线程,通过标志位控制 ———— 问题:只对单个服务进程有效
分布式锁
为什么需要分布式锁?
- 有限资源的情况下,控制同一时间(段)只有某些线程(用户 / 服务器)能访问到资源。
- 单个锁只对单个 JVM / 单个进程 有效
分布式锁实现的关键
抢锁机制
如何保证同一时间只有 1 个服务器能抢到锁?
核心思想 :先来的人先把数据改成自己的标识(服务器 ip),后来的人发现标识已存在,就抢锁失败,继续等待。等先来的人执行方法结束,把标识清空,其他的人继续抢锁。
实现的几种方式
- MySQL 数据库:select for update 行级锁(最简单),但是数据库操作时间会慢一些
*(乐观锁)
- ✔ Redis 实现:内存数据库,读写速度快 。支持 setnx、lua 脚本,比较方便实现分布式锁。
- setnx:set if not exists 如果不存在,则设置;只有设置成功才会返回 true,否则返回 false
注意事项
用完锁要释放(腾地方)
锁一定要加过期时间
如果方法执行时间过长,锁提前过期了?
- 问题:
- 连锁效应:释放掉别人的锁
- 这样还是会存在多个方法同时执行的情况 * 解决方案:续期
- 问题:
boolean end = false;
new Thread(() -> {
if (!end)}{
续期
})
end = true;
2
3
4
5
6
7
8
释放锁的时候,有可能先判断出是自己的锁,但这时锁过期了,最后还是释放了别人的锁
// 原子操作 if(get lock == A) { // set lock B del lock }
1
2
3
4
5Redis + lua 脚本实现
Redis 如果是集群(而不是只有一个 Redis),如果分布式锁的数据不同步怎么办?
分布式锁的实现
- 拒绝从0到1自己写,一般而言不够高效
- 采用现成的库,功能齐全,而且即使有BUG,也会持续修复
Redisson 实现分布式锁
特点:
- Java 客户端,数据网格
- 实现了很多 Java 里支持的接口和数据结构
Redisson 是一个 java 操作 Redis 的客户端,提供了大量的分布式数据集来简化对 Redis 的操作和使用,可以让开发者像使用本地集合一样使用 Redis,完全感知不到 Redis 的存在。
种引入方式
- spring boot starter 引入(不推荐,版本迭代太快,容易冲突)
- 直接引入:
- [redisson]https://github.com/redisson/redisson#quick-start
定时任务 + 锁
- waitTime 设置为 0,只抢一次,抢不到就放弃
- 注意释放锁要写在 finally 中
看门狗机制
redisson 中提供的续期机制
开一个监听线程,如果方法还没执行完,就帮你重置 redis 锁的过期时间。
原理:
- 监听当前线程,默认过期时间是 30 秒,每 10 秒续期一次(补到 30 秒)
- 如果线程挂掉(注意 debug 模式也会被它当成服务器宕机),则不会续期