Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Sign in / Register
Toggle navigation
S
StandardDocument
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Document
StandardDocument
Commits
fb6be832
Commit
fb6be832
authored
Jun 02, 2022
by
yanzg
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
提交
parent
f44b923c
Changes
2
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
425 additions
and
0 deletions
+425
-0
开发基础.md
技术提升/java/开发基础.md
+133
-0
缓存应用案例分析.md
技术提升/java/缓存应用案例分析.md
+292
-0
No files found.
技术提升/java/开发基础.md
0 → 100644
View file @
fb6be832
# 开发基础
## 开发6原则
1.
SQL编写原则
SQL使用原则能帮助我们快速分析功能该如何去实现业务逻辑,该如何去确定性能问题优化.
2.
缓存使用原则
缓存是对整个系统性能提升最大的技术,但是用不好缓存则达不到提升性能的效果
3.
MQ使用原则
MQ是拆分业务逻辑的最好方式,将一个复杂的功能拆分成简单的功能,从而降低单个业务逻辑的复杂度.降低复杂度后减少主业务的执行时间,从而给客户更好的体验.注意:不能提升系统的整体性能,但是可以提升某个业务的响应时间,并保证系统的健壮性(MQ重试机制).
4.
代码编写原则
通过代码编写原则,能够编写出可阅读可理解的代码,并降低业务的复杂度.
5.
需求分析原则
需求分析能帮助我们快速的理解开发的功能,从而发现隐藏的漏洞和逻辑,进而分析出合适的流程,建立出准确的接口文档.
6.
表结构设计原则
通过表结构设计原则,能够准确的实现需求.
## SQL编写原则
1.
分析输入数据所在的表
2.
分析输出数据所在的表
3.
分析所涉及的业务表之间的关系
4.
分析输入数据是否可以整合.如:多条创建语句是否可以整合到一起,用createList去操作.多条update语句可以整合成一句update语句.
5.
分析输出数据是否需要分页后再组合还是分页前组合.如:Left Join并没有包含在条件的数据可以分页后在组合,从而提升查询语句性能.
-
主表5000条数据
-
Left Join表有 10万条数据
-
那么假如你通过关联后再 where条件,会在5000
*
10万条数据中进行判断.
-
但是假如先分页,则首先从 5000条中取出20条,再通过 20
*
10000条进行判断.从而提升性能非常明显.
6.
将组合后的语句结果拆分到实现语言的不同实体中.
7.
不同模块之间的表结构不要互相关联.如: 产品和讲解配置为同一个模块,但是产品和订单分属不同模块,产品和系统分属不同模块.
8.
同一个模块之间的信息,尽量通过表结构关联取得结果.
9.
不同模块之间的名称同步,可以将名称转换为枚举,并通过mq枚举同步,最后关键枚举表获取名称.
10.
在更新状态金额时,尽量通过状态历史判断和金额累加的方式去实现.
```
sql
update
table
set
status
=@
newStatus
where
id
=@
id
AND
status
=@
oldStatus
;
update
table
set
money
=
money
+@
money
where
id
=@
id
;
```
## 缓存使用原则
1.
多级缓存原则
缓存全部讲解请看博客地址:
[
缓存应用案例分析
](
http://www.yanzuoguang.com/article/1553.html
)
-
内存缓存(本地缓存), 一般用于临时缓存(3-10秒,定时刷新自动缓存),或者用于不会变动的数据长时间缓存(节假日)
-
Redis缓存,一般用于主键缓存,定时查询自动刷新缓存.缓存锁(防止同一个任务同时运行多次).
-
内存缓存+Redis缓存,该缓存的使用场景为内存缓存+Redis缓存的组合.
-
具体实现方式如下:
```java
// 实现方式1
@Cached(
// 默认规则名称:缓存时效规则,以及存储方式
area="shortTime",
// 缓存名称,通常定义到缓存常量中,防止缓存名称冲突
name = CacheName.SALE_SALE_QUERY,
// 缓存关键字,用于做唯一性判断,可以不配置,则由框架自动进行key值生成
key = "#req.toJsonMd5()",
// 结果为空时是否缓存
cacheNullValue = true,
// 同时缓存到redis,内存中(降低redis压力),可以分别配置redis缓存时间和
cacheType = CacheType.BOTH ,
// 本次配置时效单位.注意:配置文件中的值和这个单位无关.
timeUnit=TimeUnit.SECONDS,
// 本地内存缓存失效时间,可以单独配置,一般通过area属性来控制
localExpire = 3,
// Redis缓存失效时间,可以单独配置,一般通过area属性来控制
expire = 3
)
// 开启自动刷新
@CacheRefresh(
// 自动刷新时间
refresh = CacheName.REFRESH_TIME,
// 不访问后多长时间停止刷新
stopRefreshAfterLastAccess = CacheName.REFRESH_STOP
)
// 开启多线程缓存保护,即 @Cached.key 相等时则不缓存
@CachePenetrationProtect
PageSizeData<SaleSaleLoadResVo> query(SaleSaleQueryReqVo req);
// 实现方式2
@CreateCached(
// 默认规则名称:缓存时效规则,以及存储方式
area="shortTime",
// 缓存名称,通常定义到缓存常量中,防止缓存名称冲突
name = CacheName.SALE_SALE_QUERY,
// 同时缓存到redis,内存中(降低redis压力),可以分别配置redis缓存时间和
cacheType = CacheType.BOTH ,
// 本次配置时效单位.注意:配置文件中的值和这个单位无关.
timeUnit=TimeUnit.SECONDS,
// 本地内存缓存失效时间,可以单独配置,一般通过area属性来控制
localExpire = 3,
// Redis缓存失效时间,可以单独配置,一般通过area属性来控制
expire = 3
)
private Cache<String,SaleSaleLoadResVo> cache;
```
2.
缓存场景使用缓存类型原则
缓存究竟该用内存缓存还是Redis缓存?一般包含如下几个场景.
-
业务实时性要求较高的,并使用不频繁的,用Redis. 如: 订单模块单个订单,一般使用N次,但是使用频率不高,但是关联表较多,可以通过NoSql来进行缓存. 并一般会在缓存中增加判断,判断请求次数,当N秒内超过多少次时,提示请求过于频繁.并在内存中将该订单的操作锁定一定时间.
-
业务实时型要求较高的,并使用频率较多的,可以考虑内存缓存定时刷刷新+Redis缓存一定时间的二级缓存分开实现策略或单用Redis缓存一定时间. 如: 用户订单列表,可以通过15秒自动刷新内存缓存定时刷新,30秒未访问后停止刷新,防止用户不断的发送请求过来,从而让Redis崩溃,Redis缓存按照用户Id存储,并在用户新建订单时实时删除缓存+延迟 5秒删除缓存(防止删除之前开始缓存,删除之后读取到结果后写入缓存,从而导致缓存不是最新).
-
变动非常少的和使用次数非常频繁的,可以通过内存缓存+Redis缓存定时刷新.如:产品列表和库存.可以设置本地缓存实效为5秒,Redis缓存时效60秒,每10秒钟刷新缓存写入Redis,并开始缓存保护.防止多人同时请求缓存.变动次数少可以设置为较长的Redis缓存时间.
-
主键缓存,可以通过Redis缓存较长时间,在主键变动时修改Redis缓存或实时删除缓存+延迟删除缓存策略.
3.
缓存重复原则+缓存就近原则
-
查询时可以通过产品ID+公司ID查询可重复得到的数据.
-
尽量要建立可以多次使用的纬度
如: 坐标纬度重复概率较低,那么则可以通过坐标查询该坐标最近的景区,以景区坐标为开始点进行缓存,查询该景区的数据,并根据坐标整合得到当前距离.从而提升查询速度. 又如:可以对坐标进行分析,发现坐标差距有多大,建立每1公里的坐标纬度,该1公里内共用缓存,并在缓存中排序过滤数据.
4.
缓存刷新原则
-
当请求参数重复性较高,并应用较多时,可以设置缓存自动刷新.从而提升系统效率.
-
下单时获取分销库存信息.可以拆分为订单缓存(主键缓存,不自动刷新),订单对分销库存进行缓存(内存+Redis的查询条件缓存,自动刷新5秒), Fegin请求分销库存模块,分销库存模块对分销库存进行缓存(内存+Redis的查询条件缓存,自动刷新5秒).则会达到这种效果,当第一次请求时,会执行整个流程,而当该分销产品的销售请求较为频繁时,订单模块不需要调用到分销模块,则可以获取到分销库存信息.从而加快下单的速度.
5.
预缓存原则
-
在使用较为频繁的景区+产品,可以建立预查询机制,从而提升第一次查询的性能.较快的速度出现查询结果.
\ No newline at end of file
技术提升/java/缓存应用案例分析.md
0 → 100644
View file @
fb6be832
## 为什么需要缓存
1.
降低数据库查询压力
2.
提前准备好结果数据,从而提升服务器性能
3.
幂等性判断
## 什么数据需要缓存
1.
列表信息
-
变动不频繁列表,如: 员工列表
-
变动频繁的列表,如: 库存列表
2.
单个信息通过主键缓存
-
加载产品信息
-
加载渠道信息
3.
多服务器部署中,幂等性任务处理
-
订单处理中对订单进行缓存,并防止同时下单
## 环境准备工作
1.
配置JetCache运行环境配置文件"tbd-redis.yml":
```
yml
# 端口
#spring:
# redis:
# # redis服务器地址(默认为loaclhost)
# host: 192.168.0.206
# # redis端口(默认为6379)
# port: 6379
# # redis数据库索引(默认为0),避免和其他应用冲突,途比达序号为8,胖丁序号为6,数据中心序号为7
# database: 8
# # redis访问密码(默认为空)
# password: root,.123
# #超时连接
# timeout: 1000ms
# jedis:
# pool:
# #最大建立连接等待时间。如果超过此时间将接到异常。设为-1表示无限制。
# max-wait: 1000ms
# #最大连接数据库连接数,设 0 为没有限制
# max-active: 100
# #最大等待连接中的数量,设 0 为没有限制
# max-idle: 8
# #最小等待连接中的数量,设 0 为没有限制
# min-idle: 0
remoteCache
:
&remoteCache
# type: redis
# keyConvertor: fastjson
# valueEncoder: kryo
# valueDecoder: kryo
# host: ${spring.redis.host:localhost}
# port: ${spring.redis.port:6379}
# password: ${spring.redis.password}
# database: ${spring.redis.database}
# poolConfig:
# minIdle: 5
# maxIdle: 20
# maxTotal: 50
type
:
redis.lettuce
keyConvertor
:
fastjson
valueEncoder
:
kryo
valueDecoder
:
kryo
# uri格式:redis://密码@ip:端口/redis库名?timeout=5s url[0]
# uri: redis://${spring.redis.password}@${spring.redis.password}:${spring.redis.port:6379}/7?timeout=5s
# redis数据库索引(默认为0),避免和其他应用冲突,途比达序号为8,胖丁序号为6,数据中心序号为7
# uri格式:redis://密码@ip:端口/redis库名?timeout=5s
uri
:
redis://root,.123@192.168.100.4:6379/8?timeout=1s
poolConfig
:
minIdle
:
0
maxIdle
:
8
maxTotal
:
100
localCache
:
&localCache
type
:
caffeine
keyConvertor
:
fastjson
jetcache
:
statIntervalMinutes
:
15
areaInCacheName
:
false
local
:
# 默认1小时本地缓存
default
:
<<
:
*localCache
expireAfterWriteInMillis
:
60000
expireAfterAccessInMillis
:
60000
# 長時本地緩存,主要用于要求时效一般
longTime
:
<<
:
*localCache
expireAfterWriteInMillis
:
300000
expireAfterAccessInMillis
:
180000
# 短時本地緩存,主要用于要求时效较高的配置
shortTime
:
<<
:
*localCache
expireAfterWriteInMillis
:
3000
expireAfterAccessInMillis
:
3000
remote
:
# 默认1小时的远程缓存
default
:
expireAfterWriteInMillis
:
3600000
<<
:
*remoteCache
# 长时远程緩存,主要用于要求时效要求一般的集中式缓存
longTime
:
expireAfterWriteInMillis
:
7200000
<<
:
*remoteCache
# 短時远程緩存,主要用于要求时效较高的集中式缓存
shortTime
:
expireAfterWriteInMillis
:
300000
<<
:
*remoteCache
```
2.
pom.xml中引入jar包
```
xml
<dependency>
<groupId>
com.yanzuoguang
</groupId>
<artifactId>
yzg-util-redis
</artifactId>
</dependency>
或者
<dependency>
<groupId>
com.alicp.jetcache
</groupId>
<artifactId>
jetcache-starter-redis-lettuce
</artifactId>
</dependency>
```
3.
项目中 "bootstrap.yml" 增加 引入配置文件"tbd-redis.yml "
```
yml
spring
:
cloud
:
config
:
name
:
tbd-redis
```
## 列表信息缓存
一般来说列表信息缓存信息都需要时效处理,这种缓存不需要删除,但是缓存时间不能太长(建议最长1-5分钟),但是需要定时失效.并通过整个对象转换为Json字符串后MD5,来确定是否自动重新刷新.
实现方式:
```
java
/**
* 查询分销渠道
*
* @param req
* @return
*/
@Cached
(
// 默认规则名称:缓存时效规则,以及存储方式
area
=
"shortTime"
,
// 缓存名称,通常定义到缓存常量中,防止缓存名称冲突
name
=
CacheName
.
SALE_SALE_QUERY
,
// 缓存关键字,用于做唯一性判断,可以不配置,则由框架自动进行key值生成
key
=
"#req.toJsonMd5()"
,
// 结果为空时是否缓存
cacheNullValue
=
true
,
// 同时缓存到redis,内存中(降低redis压力),可以分别配置redis缓存时间和
cacheType
=
CacheType
.
BOTH
,
// 本次配置时效单位.注意:配置文件中的值和这个单位无关.
timeUnit
=
TimeUnit
.
SECONDS
,
// 本地内存缓存失效时间,可以单独配置,一般通过area属性来控制
localExpire
=
3
,
// Redis缓存失效时间,可以单独配置,一般通过area属性来控制
expire
=
3
)
// 开启自动刷新
@CacheRefresh
(
// 自动刷新时间
refresh
=
CacheName
.
REFRESH_TIME
,
// 不访问后多长时间停止刷新
stopRefreshAfterLastAccess
=
CacheName
.
REFRESH_STOP
)
// 开启多线程缓存保护,即 @Cached.key 相等时则不缓存
@CachePenetrationProtect
PageSizeData
<
SaleSaleLoadResVo
>
query
(
SaleSaleQueryReqVo
req
);
```
## 单个信息缓存
一般来说单个信息缓存信息都可以保留较长时效,这种缓存需要在增加,修改时自动删除,缓存可以设置较长时间(建议最长30-60分钟),可以支持定时刷新,从而提升加载速度.并开启多线程保护.多个请求时,只一个人去加载.这个通过主键加载时不能通过扩展参数返回2种以上的不同结果.
实现方式:
```
java
/**
* 查询分销渠道
*
* @param req
* @return
*/
@Cached
(
// 默认规则名称:缓存时效规则,以及存储方式,在配置文件中定义,默认为: default
area
=
"default"
,
// 缓存名称,通常定义到缓存常量中,防止缓存名称冲突
name
=
CacheName
.
SALE_SALE
,
// 缓存关键字,用于做唯一性判断,单个缓存中必须配置.
key
=
"#req.saleId"
,
// 结果为空时是否缓存
cacheNullValue
=
true
,
// 同时缓存到redis,内存中(降低redis压力),可以分别配置redis缓存时间和
cacheType
=
CacheType
.
BOTH
)
// 开启自动刷新
@CacheRefresh
(
// 自动刷新时间
refresh
=
CacheName
.
REFRESH_TIME
,
// 不访问后多长时间停止刷新
stopRefreshAfterLastAccess
=
CacheName
.
REFRESH_STOP
)
// 开启多线程缓存保护,即 @Cached.key 相等时则不缓存
@CachePenetrationProtect
SaleSaleLoadResVo
load
(
SaleSaleLoadReqVo
req
);
/**
* 分销渠道规则保存/修改
*
* @param req
* @return
*/
// 强制现有缓存失效
@CacheInvalidate
(
// 默认规则名称:缓存时效规则,以及存储方式,在配置文件中定义,默认为: default
area
=
"default"
,
// 缓存名称
name
=
CacheName
.
SALE_SALE
,
// 缓存主键,得和 @Cached.key 相等
key
=
"#req.saleId"
)
String
save
(
SaleSaleSaveReqVo
req
);
/**
* 删除分销信息
*
* @param req
* @return
*/
// 强制现有缓存失效
@CacheInvalidate
(
// 默认规则名称:缓存时效规则,以及存储方式,在配置文件中定义,默认为: default
area
=
"default"
,
// 缓存名称
name
=
CacheName
.
SALE_SALE
,
// 缓存主键,得和 @Cached.key 相等
key
=
"#req.saleId"
)
int
remove
(
SaleSaleRemoveReqVo
req
);
/**
* 渠道上下架
*
* @param req
* @return
*/
// 强制现有缓存失效
@CacheInvalidate
(
// 默认规则名称:缓存时效规则,以及存储方式,在配置文件中定义,默认为: default
area
=
"default"
,
// 缓存名称
name
=
CacheName
.
SALE_SALE
,
// 缓存主键,得和 @Cached.key 相等
key
=
"#req.saleId"
)
String
inline
(
SaleSaleInlineReqVo
req
);
```
## 多服务器部署中,幂等性任务处理
这种一般用于判断请求是否重复,并把一些重复的资源放入缓存中.从而提升最近一段时间该订单的加载速度.
```
java
@Compent
class
Classs
{
@CreateCache
(
name
=
"order:save:"
,
expire
=
3600
,
cacheType
=
CacheType
.
BOTH
)
private
Cache
<
String
,
OrderLoadResVo
>
cacheOrderSave
;
// 生成分布式锁执行函数,同一个key同一时间只会有一个函数执行,100秒内每1秒钟检测一次
CacheLock
.
run
(
// 缓存对象
cacheOrderSave
,
// 等待最长时长
waitTime
,
// 等待时间间隔
waitUnit
,
// 等待关键字
key
+
"_RUN"
,
// 当key对应函数,没有其他人执行时,开始执行本函数.奇谈人执行则等待
new
Runnable
()
{
@Override
public
void
run
()
{
// 读取缓存
OrderLoadResVo
loadCache
=
cacheOrderSave
.
get
(
key
);
// ....
// 写入缓存
cacheOrderSave
.
put
(
order
.
getOrderId
(),
load
);
cacheOrderSave
.
put
(
channelKey
,
load
);
}
});
}
```
\ No newline at end of file
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment