商城-文档

一直有将前台改写成vue的打算,最近刚把项目样子搭出来,同学们可以来看下支持下点点star,未来计划去整合~
仓库地址
在线演示

架构图及微服务划分

商城-文档_第1张图片

商城-文档_第2张图片

环境配置

Linux 环境

JDK
yum install java-1.8.0-openjdk.x86_64
java -version
vi /etc/profile

#set java environment
JAVA_HOME=/usr/lib/jvm/jre-1.8.0-openjdk
PATH=$PATH:$JAVA_HOME/bin
CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
export JAVA_HOME CLASSPATH PATH
Docker
- 卸载系统之前的docker 
sudo yum remove docker \
                  docker-client \
                  docker-client-latest \
                  docker-common \
                  docker-latest \
                  docker-latest-logrotate \
                  docker-logrotate \
                  docker-engine

- 设置存储库
sudo yum install -y yum-utils
sudo yum-config-manager \
    --add-repo \
    https://download.docker.com/linux/centos/docker-ce.repo

- 安装DOCKER引擎
sudo yum install docker-ce docker-ce-cli containerd.io

- 启动Docker.
sudo systemctl start docker

- 配置镜像加速
sudo mkdir -p /etc/docker
sudo tee /etc/docker/daemon.json <<-'EOF'
{
  "registry-mirrors": ["https://ozz4irqv.mirror.aliyuncs.com"]
}
EOF
sudo systemctl daemon-reload
sudo systemctl restart docker

- 开机自启
sudo docker update redis --restart=always
MySQL (Docker)
- 拉取 mysql镜像
sudo docker pull mysql:8.0

- 启动mysql容器
# --name指定容器名字 -v目录挂载(左主右从) -p指定端口映射  -e设置mysql参数 -d后台运行
sudo docker run --name mysql -v /usr/local/mysql/data:/var/lib/mysql -v /usr/local/mysql:/etc/mysql/conf.d -v /usr/local/mysql/log:/var/log/mysql  -e MYSQL_ROOT_PASSWORD=root  -p 3306:3306 -d mysql:8.0

- 进入mysql容器
docker exec -it a4435a23e7a470297fded7fbdeb1a06045530e1631517585f490c680e4039891 bin/bash

- 开机自启
sudo docker update mysql --restart=always
Redis(Docker)
- 拉取redis镜像到本地
docker pull redis

- 修改需要自定义的配置(docker-redis默认没有配置文件,自己在宿主机建立后挂载映射)
位置在 /usr/local/redis/redis.conf
#开启远程权限
bind 0.0.0.0 
#开启aof持久化
appendonly yes 

- 启动redis服务运行容器
docker run --name redis  -v /usr/local/redis/data:/data  -v /usr/local/redis/redis.conf:/usr/local/etc/redis/redis.conf -p 6379:6379 -d redis redis-server /usr/local/etc/redis/redis.conf 
 
- 连接 redis
docker exec -it redis redis-cli
Nginx(Docker)
# 终极版!
docker run -p 80:80 --name nginx \
 -v /usr/local/nginx/html:/usr/share/nginx/html \
 -v /usr/local/nginx/conf/nginx.conf/:/etc/nginx/nginx.conf \
  -v /usr/local/nginx/logs:/var/log/nginx \
 -d nginx
 
 
 docker run -p 80:80 --name nginx \
-v /Users/june/AServerMiddleware/nginx-docker/html:/usr/share/nginx/html \
-v /Users/june/AServerMiddleware/nginx-docker/conf/nginx.conf:/etc/nginx/nginx.conf \
-v /Users/june/AServerMiddleware/nginx-docker/conf/default.conf:/etc/nginx/conf.d/default.conf \
-v /Users/june/AServerMiddleware/nginx-docker/logs:/var/log/nginx -d nginx 
RabbitMQ(Docker)

Docker系列之RabbitMQ安装部署教程 - 云+社区 - 腾讯云 (tencent.com)

docker run -d  --name rabbitmq -p 5671:5671 -p 5672:5672 -p 4369:4369 -p 25672:25672 -p 15671:15671 -p 15672:15672 -e RABBITMQ_DEFAULT_USER=june -e RABBITMQ_DEFAULT_PASS=L200107208017./@  rabbitmq:management 

# 4369 25672 Erlang发现&集群端口
# 5671 5672 AMQP 端口
# 15672 web管理后台端口
# 61613 61614 STOMP协议端口
# 1883 8883 MQTT 协议端口
Nacos(Docker) Linux 部署 Mac 不支持

https://www.jianshu.com/p/3d3e17bc629f

SQL位置

# 1.创建本地配置文件 
mkdir -p /home/nacos/logs/                      #新建logs目录
mkdir -p /home/nacos/init.d/          
vim /home/nacos/init.d/custom.properties        #修改配置文件
# 2.添加如下内容

server.contextPath=/nacos
server.servlet.contextPath=/nacos
server.port=8848

spring.datasource.platform=mysql

db.num=1
db.url.0=jdbc:mysql://sh-cdb-0ej7ogfe.sql.tencentcdb.com:58887/nacos_config?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true
db.user=root
db.password=L200107208017@


nacos.cmdb.dumpTaskInterval=3600
nacos.cmdb.eventTaskInterval=10
nacos.cmdb.labelTaskInterval=300
nacos.cmdb.loadDataAtStart=false

management.metrics.export.elastic.enabled=false

management.metrics.export.influx.enabled=false


server.tomcat.accesslog.enabled=true
server.tomcat.accesslog.pattern=%h %l %u %t "%r" %s %b %D %{
   User-Agent}i


nacos.security.ignore.urls=/,/**/*.css,/**/*.js,/**/*.html,/**/*.map,/**/*.svg,/**/*.png,/**/*.ico,/console-fe/public/**,/v1/auth/login,/v1/console/health/**,/v1/cs/**,/v1/ns/**,/v1/cmdb/**,/actuator/**,/v1/console/server/**
nacos.naming.distro.taskDispatchThreadCount=1
nacos.naming.distro.taskDispatchPeriod=200
nacos.naming.distro.batchSyncKeyCount=1000
nacos.naming.distro.initDataRatio=0.9
nacos.naming.distro.syncRetryDelay=5000
nacos.naming.data.warmup=true
nacos.naming.expireInstance=true

# 3.启动容器
docker  run \
--name nacos -d \
-p 8848:8848 \
--privileged=true \
--restart=always \
-e JVM_XMS=256m \
-e JVM_XMX=256m \
-e MODE=standalone \
-e PREFER_HOST_MODE=hostname \
-v /home/nacos/logs:/home/nacos/logs \
-v /home/nacos/init.d/custom.properties:/home/nacos/init.d/custom.properties \
nacos/nacos-server:1.4.2
Sentinel

Linux直接部署

# 注意,不要同时在开发机器部署微服务,云服务器部署sentinel,因为sentinel也是需要访问本机的!
java -Dserver.port=8858 -Dcsp.sentinel.dashboard.server=localhost:8858 -Dproject.name=sentinel-dashboard -Dsentinel.dashboard.auth.username=sentinel-qs -Dsentinel.dashboard.auth.password=L200107208017@ -jar sentinel-dashboard-1.8.1.jar 

本机部署

nohup java  -server -Xms64m -Xmx256m  -Dserver.port=8858 -Dcsp.sentinel.dashboard.server=localhost:8858 -Dproject.name=sentinel-dashboard -jar /Users/june/AServerMiddleware/sentinel/sentinel-dashboard-1.8.1.jar
Zipkin(Docker)

zipkin 使用外部 MySQL 持久化存储 - 简书 (jianshu.com)

docker run -d -p  9411:9411 openzipkin/zipkin:latest 
-e JAVA_OPTS=-Xmx128m \


docker  run \
--name zipkin-server -d \
--restart=always \
-p 9411:9411 \
-e MYSQL_USER=root \
-e MYSQL_PASS=L200107208017@ \
-e MYSQL_HOST=sh-cdb-0ej7ogfe.sql.tencentcdb.com \
-e STORAGE_TYPE=mysql \
-e MYSQL_DB=zipkin \
-e MYSQL_TCP_PORT=58887 \

openzipkin/zipkin

开发机器环境

git
git config --global user.name "June"  
git config --global user.email "[email protected]" 

ssh-keygen -t rsa -C "[email protected]"

# 查看生成的密钥内容
cat ~/.ssh/id_rsa.pub

# 复制密钥内容到 gitee,以后该机器就推送内容不用输入密码

# 测试
ssh -T [email protected]

# gitignore 中添加以下内容
**/mvnw
**/mvnw.cmd
**/.mvn
**/target
.idea
**/.gitignore
**/README.md
Node.js
- 官网下载 node.js 附带有 npm
- 配置 npm 镜像
npm config set registry http://registry.npm.taobao.org/

项目构架

商城-文档_第3张图片

renren-generator

利用这个模块给每一个业务模块生成代码,以 product 模块为例

商城-文档_第4张图片

选用技术

SpringCloud Alibaba - Nacos (服务发现/注册)
SpringCloud Alibaba - Nacos (动态配置管理)
SpringCloud Alibaba - Seata (分布式事务解决方案)
SpringCloud Alibaba - Sentinel (限流、降级、熔断等)
SpringCloud - Ribbon (负载均衡)
SpringCloud - Feign 远程调用服务
SpringCloud - Gateway (API网关)
SpringCloud - Sleuth (调用链监控)

核心依赖

这块配不好,踩坑少不了


<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>com.alibaba.cloudgroupId>
      <artifactId>spring-cloud-alibaba-dependenciesartifactId>
      <version>2.1.0.RELEASEversion>
      <type>pomtype>
      <scope>importscope>
    dependency>
  dependencies>
dependencyManagement>


<parent>
  <groupId>org.springframework.bootgroupId>
  <artifactId>spring-boot-starter-parentartifactId>
  <version>2.3.1.RELEASEversion>
  <relativePath/>
parent>


<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>org.springframework.cloudgroupId>
      <artifactId>spring-cloud-dependenciesartifactId>
      <version>Hoxton.SR6version>
      <type>pomtype>
      <scope>importscope>
    dependency>
  dependencies>
dependencyManagement>

技术要点

Nacos 注册中心

  1. 引入依赖
<dependency>
  <groupId>com.alibaba.cloudgroupId>
  <artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
dependency>
  1. 指定配置中心位置
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
  1. 添加注解 @EnableDiscoveryClient

OpenFeign 远程调用

  1. 引入依赖
<dependency>
  <groupId>org.springframework.cloudgroupId>
  <artifactId>spring-cloud-starter-openfeignartifactId>
dependency>
  1. 指定配置中心位置
  2. 创建接口,指定方法
商城-文档_第5张图片
  1. 添加注解,上图中一个,启动类一个
@EnableFeignClients(basePackages = "org.june.member.feign")

Nacos 配置中心

  1. 引入依赖
<dependency>
  <groupId>com.alibaba.cloudgroupId>
  <artifactId>spring-cloud-alibaba-nacos-configartifactId>
dependency>
  1. 创建 bootstrap.properties 指定配置中心位置以及自己的服务名称
spring.application.name=mall-coupon
spring.cloud.nacos.config.server-addr=127.0.0.1:8848
  1. Nacos 配置中心添加 服务名.properties 配置文件,这个文件可以动态读取
  2. 需要使用 动态配置的代码类头添加 @RefreshScope,使用 @Value("${name.age}") (在成员变量处)方式获取

注:

  • 配置中心配置项优先级较本地文件优先级高
  • 经测试,第三步中 yaml yml 都无法识别,只有properties可以识别

命名空间的说明

商城-文档_第6张图片

# 追加在 bootstrap.properties 中
spring.cloud.nacos.config.namespace=3ce35e9e-4e10-44df-b1a4-8fd753c3e4ea

配置中心的说明

商城-文档_第7张图片
# 追加在 bootstrap.properties 中
spring.cloud.nacos.config.group=ddd

类似于上面的命名空间,都是用来隔离文件的

最终实践

商城-文档_第8张图片

spring.application.name=mall-coupon
# 服务注册/发现
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
# 配置中心   这两项可以只配 第二个
spring.cloud.nacos.config.server-addr=127.0.0.1:8848
spring.cloud.nacos.config.namespace=2863031e-ae6a-4bff-903e-48d27201e1ab

spring.cloud.nacos.config.ext-config[0].data-id=datasource.yml
spring.cloud.nacos.config.ext-config[0].group=dev
spring.cloud.nacos.config.ext-config[0].refresh=true


spring.cloud.nacos.config.ext-config[1].data-id=mybatis.yml
spring.cloud.nacos.config.ext-config[1].group=dev
spring.cloud.nacos.config.ext-config[1].refresh=true

spring.cloud.nacos.config.ext-config[2].data-id=others.yml
spring.cloud.nacos.config.ext-config[2].group=dev
spring.cloud.nacos.config.ext-config[2].refresh=true

Gateway 网关

  1. 添加依赖
<dependency>
  <groupId>org.springframework.cloudgroupId>
  <artifactId>spring-cloud-starter-gatewayartifactId>
dependency>
  1. 指定配置中心(配置文件、启动类注解)
  2. 配置文件写路由

注:SpringBoot 2.3.1.RELEASE 对应 SpringCloud Hoxton.SR6不会报错

Spring Cloud Gateway 2.1.0 中文官网文档 - 云+社区 - 腾讯云 (tencent.com)

ES6

ECMAScript 是浏览器脚本语言的规范,JavaScript 是该规范的具体实现。以下示例以 JavaScript 为例

变量
  • var 变量声明变量往往会越狱,只能声明一次
  • let 声明变量有严格作用域,可以声明多次
  • const 声明变量不允许改变
解构表达式
let arr = [1,2,3]
// ↓
let [a,b,c] = arr
/
const person = {
   
  name: "June",
  age: 21
}
// ↓
const {
   name:var1,age:var2} = person;
console.log(var1,var2)
字符串扩展
let str = "helloworld"
str.startsWith()
str.endsWith()
str.includes()
模板字符串
let var1 = "June"
let var2 = "March"
let ss = `${
     var1}
			 this is a test
${
     var2}`
console.log(ss)
函数参数
function test1(a,b){
   
  ...
}
test(var1)   // 只传一个
///
function test2(...vars){
   
  ...
}
test(var1,var2)  // 传多个
箭头函数
var print = function(obj){
   
  console.log(obj)
}
// ↓
var print = obj => console.log(obj)
/ 对象解构
var person = {
   
  name:"jack",
  age:21
}
var hello = ({
    name}) => console.log("hello," + name)
对象优化
var person = {
   
  name:"jack",
  age:21
}
Object.keys(person)  -> ["name","age"]
Object.values(person) -> ["jack",21]
Object.entries(person) -> [Array(2),Array(2),Array(2)]
// 追加
const target = {
   a:1}
const source1 = {
   b:2}
const source2 = {
   c:3}
Object.assign(target, source1, source2)
target -> {
   a:1, b:2, c:3}
// 对象简写1
const age = 21
const name = "张三"
const person = {
   age,name}
// 对象简写2
let person = {
   
  name: "jack",
  eat: function(food){
   
    console.log(this.name + "在吃" + food)
  },
  eat2: food => console.log(person.name + "在吃" + food)
}
/// 拷贝对象(深拷贝)
let p1 = {
   name: "Any", age:15}
let p2 = {
   ...p1}
/// 对象合并(会覆盖)
let age = {
   age:15}
let name = {
   name: "Amy"}
let person = {
   ...age,...name}
数组增强
let arr = ['1', '2', '3', '4']
arr.map(item)=>{
   
  return item*2
}    // => [2,4,6,8]
// ↓
arr = arr.map(item=>item*2)

let result = arr.reduce((a,b)=>{
   
  return a+b
},100)   // => 110
模块化
/ js1.js
var name = "jack"
var age = 21
function add(a,b){
   
  return a + b
}
export{
   name,age,add}
// js2.js
import {
   name,age,add} from "./js1.js"
add(1,2)

Vue

v-text v-html

文本值绑定

{ { name }}

v-bind

属性值绑定,一般用于 href、class、style

<a v-bind:href:"link">gogogoa>
你好
v-model

双向绑定,不同于以上两者

商城-文档_第9张图片

v-on

事件绑定

商城-文档_第10张图片

v-for

遍历

商城-文档_第11张图片

v-if v-show

v-if 条件为 true,元素才会被渲染;v-show 条件为true,元素才会显示

前者是注释掉了相关代码,后者把样式改变了

计算属性、侦听器和过滤器

{ {totalPrice}}


组件化

商城-文档_第12张图片

商城-文档_第13张图片

生命周期和钩子函数
商城-文档_第14张图片
vue 模块化开发
sudo npm install webpack -g
npm install -g @vue/cli-init
sudo npm install --global vue-cli
# 再创建项目文件夹
vue init webpack vue-demo
cd vue-demo
npm run dev

Elasticsearch

基本概念
  1. Index(索引)

MySQL的库

  1. Type(类型)

在Index中,可以定义一个或多个类型,类似于MySQL中的表;每一种类型的数据放在一起;

  1. Document(文档)

保存在某个索引下,某种类型的一个数据,文档是JSON格式的,Document就像是MySQL中某个Table里面的内容

商城-文档_第15张图片
  1. 倒排索引

商城-文档_第16张图片

创建实例
  1. elasticsearch
# Docker  !8版本需要额外配置东西
docker pull elasticsearch:7.17.0  
docker pull kibana:7.17.0  
# 创建
mkdir -p /Users/june/AServerMiddleware/elsatic-docker/plugins
mkdir -p /Users/june/AServerMiddleware/elsatic-docker/config
mkdir -p /Users/june/AServerMiddleware/elsatic-docker/data
# 
echo "http.host: 0.0.0.0" >/Users/june/AServerMiddleware/elsatic-docker/config/elasticsearch.yml
# 
chmod -R 777 /Users/june/AServerMiddleware/elsatic-docker
# 启动Elastic search
# 9200是用户交互端口 9300是集群心跳端口
# -e指定是单阶段运行
# -e指定占用的内存大小,生产时可以设置32G
sudo docker run --name elasticsearch -p 9200:9200 -p 9300:9300 \
-e  "discovery.type=single-node" \
-e ES_JAVA_OPTS="-Xms64m -Xmx512m" \
-v /Users/june/AServerMiddleware/elsatic-docker/config/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml \
-v /Users/june/AServerMiddleware/elsatic-docker/data:/usr/share/elasticsearch/data \
-v  /Users/june/AServerMiddleware/elsatic-docker/plugins:/usr/share/elasticsearch/plugins \
-d elasticsearch:7.17.0  
  1. kibana
sudo docker run --name kibana -e ELASTICSEARCH_HOSTS=http://124.222.22.217:9200   -p 5601:5601 -d kibana:7.17.0
# 配置中文
docker cp 源 目的  # 容器写法 容器ID:路径  主机直接写路径
docker exec -it ID /bin/bash
kibana.yml 中添加 i18n.locale: "zh-CN"
初步检索
1._cat
GET /_cat/nodes   查看所有节点
GET /_cat/health  查看es健康状况
GET /_cat/master  查看主节点
GET /_cat/indices 查看所有索引  show databases
2.添加数据
商城-文档_第17张图片

注:http://124.222.22.217:9200/customer/external/1 其中的 ‘1’ 指定了id,PUT 请求必须携带id;而 POST 可以不指定 id,不指定id,会自动生成id,指定id会对其进行修改(不存在则新增)

3.查询数据

GET customer/external/1 精确根据ID查找

GET customer/_search 查询所有

GET customer/_search  条件查询
{
	"query":{"match_all":{}},
	"sort":[
		{"account_number":"asc"}
	],
	"from":10,
	"size":10
}

商城-文档_第18张图片

http://124.222.22.217:9200/customer/external/1?if_seq_no=0&if_primary_term=1    # 修改配合并发使用
4.修改数据
POST携带JSON(带上doc) http://124.222.22.217:9200/customer/external/1/_update  # 会检查前后更新内容是否一致,其余方式如PUT、POST(不带_update)都不会对比内容
5.删除数据
DELETE http://124.222.22.217:9200/customer/external/1
6.批量API
POST custmoer/external/_bulk
{"index":{"_id":1}}
{"name":"Jone"}
{"index":{"_id":"2"}}
{"name":"Jane"}
   # 回车必要
进阶检索
1.SearchAPI

ES支持两种基本方式减缩:

  • 一个是通过使用 REST request URI 发送搜索参数( uri + 检索参数)
  • 另一个是通过使用 REST request body 来发送他们( uri + 请求体)
# 样例
GET bank/_search?q=*&sort=account_number:asc
-------
GET bank/_search
{
   
	"query":{
   
		"match_all":{
   }
	},
	"sort":[{
   
		"account_number":"asc"
	},{
   
		"balance":"desc"
	}		
	]
}

请求体语法格式

{
   
  QUERY_NAME{
   
  	ARGUMENT:VALUE,
  	ARGUMENT:VALUE
	}
}
参数说明
# 一级参数
query	指定查询操作
sort  指定排序字段
from	分页操作
size  分页操作
_source 指定查询字段

# 二级参数
query:match   	{ key:value } 非字符串值模糊查询(按相关度-score排序);数字则精确匹配
query:match_phrase  类似于前者,但不会对字符串进行分词,而是当做一条短语进行匹配
query:multi_match   分词 + 多字段匹配
query:bool  				构造复杂查询 must  must_not should(可以提高得分)
query:filter				不计算相关性得分,直接过滤
商城-文档_第19张图片 商城-文档_第20张图片
query:term		term是代表完全匹配,即不进行分词器分析,文档中必须包含整个搜索的词汇;全文检索字段用 match ,其他 text 字段用term
query:aggregations  字段聚合处理
商城-文档_第21张图片 商城-文档_第22张图片
2.Mapping

指定索引下的属性类型

商城-文档_第23张图片

修改映射(仅限于添加)

商城-文档_第24张图片

那么如何修改?

数据迁移

elastic已经不推荐使用 type

商城-文档_第25张图片

3.分词

安装ik分词器

添加自定义词汇
  1. 配置 nginx 作为远程词库
  2. 在 html/es/fenci.txt 中填入新词
  3. elasticsearch/plugs/… 中配置远程词库为上面的地址

Redis 缓存

整合 Redis
  1. 引入依赖
<dependency>
  <groupId>org.springframework.bootgroupId>
  <artifactId>spring-boot-starter-data-redisartifactId>
dependency>
  1. 调用API RedisTemplate StringRedisTemplate
  2. 代码整合示例
public Map<String, List<Catalog2Vo>> getCatalogJson(){
   
    String catalogJSON = redis.opsForValue().get("catalogJson");
    if(StringUtils.isEmpty(catalogJSON)){
   
        Map<String, List<Catalog2Vo>> catalogJsonFromDB = getCatalogJsonFromDB();
        redis.opsForValue().set("catalogJson",
                JSON.toJSONString(catalogJsonFromDB));
    }
    Map<String, List<Catalog2Vo>> list = JSON.parseObject(catalogJSON,
            new TypeReference<Map<String, List<Catalog2Vo>>>(){
   });
    return list;
}
高并发下的缓存问题
缓存穿透
  • 查询一个一定不存在的数据,由于缓存一定不命中,将去查询数据库,但数据库也无记录,这就失去了缓存的意义
  • 利用不存在的数据进行攻击,数据库瞬时压力增大,最终导致崩溃
  • 解决
    • null 结果缓存,并加入短暂过期时间
缓存雪崩
  • 设置缓存时采用了相同的过期时间,导致缓存在某一时刻同时失效,请求全部转发到了数据库,压力瞬时过大
  • 解决
    • 过期时间采用随机数
缓存击穿
  • 对于一些设置了过期时间的 key,如果这些 key 可能会在某些时间点被超高并发的访问,是一种非常【热点】的数据
  • 如果这个 key 在大量请求同时进来前正好失效,那么所有对这个key的数据查询都落到db,称为缓存击穿
  • 解决
    • 加锁;大量并发只让一个去查,其他人等待,查到以后释放锁,其他人获取锁,先查缓存,就会有数据,不用去db
加锁解决【缓存击穿】

商城-文档_第26张图片

商城-文档_第27张图片

该段代码存在分布式锁的问题

分布式锁简单实现

依托于redis的 set catalog_lock lockId [ex seconds][px millseconds] nx 命令实现

public Map<String, List<Catalog2Vo>> getCatalogJson() {
   
        // double check
        String catalogJSON = redis.opsForValue().get("catalogJson");
        if (StringUtils.isEmpty(catalogJSON)) {
   
            // 分布式加锁 ↓
            String lockId = UUID.randomUUID().toString();
            // set catalog_lock lockId [ex seconds][px millseconds] nx
            if (Boolean.TRUE.equals(redis.opsForValue().
                    setIfAbsent("catalog_lock", lockId, 300L, TimeUnit.SECONDS))) {
   
//                log.error("redis成功加锁!!!");
                // 分布式加锁 ↑
                //业务执行开始//
                try {
   
                    Map<String, List<Catalog2Vo>> catalogJsonFromDB = getCatalogJsonFromDB();
                    redis.opsForValue().set("catalogJson",
                            JSON.toJSONString(catalogJsonFromDB),
                            1, TimeUnit.DAYS);
                } finally {
   
                    //业务执行结束,勿忘删除//

                    // 防止业务执行时间过长,导致删除操作实际上删除的是别人的锁
                    // 但是这两步骤并不是原子操作,获取值进行比较的时候可能锁已经过期
                    // 所以需要采用 lua 脚本来保证原子性
                    String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
                    redis.execute(new DefaultRedisScript<>(script, Long.class), Collections.singletonList("lock"), lockId);
//                    log.error("redis成功删锁!!!");
                }

            } else {
   
//                log.error("等待锁!!");
                try {
   
                    // 防止空转
                    Thread.sleep(50);
                } catch (InterruptedException e) {
   
                    e.printStackTrace();
                }
                return getCatalogJson();
            }

        }
        return JSON.parseObject(catalogJSON,
                new TypeReference<Map<String, List<Catalog2Vo>>>() {
   
                });
    }
Redisson框架解决分布式锁
  1. 引入依赖
<dependency>
  <groupId>org.redissongroupId>
  <artifactId>redissonartifactId>
  <version>3.16.8version>
dependency>
  1. 配置类
@Configuration
public class RedissonConfig {
   
    @Bean
    RedissonClient redisson(){
   
        Config config = new Config();
        config.useSingleServer()
                .setAddress("redis://127.0.0.1:6379")
                .setPassword("L200107208017@./");
        return Redisson.create(config);
    }
}
Redisson 说明
  1. ReentrantLock

商城-文档_第28张图片

商城-文档_第29张图片

商城-文档_第30张图片

商城-文档_第31张图片

商城-文档_第32张图片

  1. ReadWriteLock

商城-文档_第33张图片

加锁示例

商城-文档_第34张图片

  1. Semaphore

计数器,正计时

  1. CountDownLatch

商城-文档_第35张图片

计数器,倒计时

分布式锁 Redisson 实现
/**
* 普通锁实现
*/
public Map<String, List<Catalog2Vo>> getCatalogJson() {
   
        // 双重检查
        String catalogJson = redis.opsForValue().get("catalogJson");
        Map<String, List<Catalog2Vo>> catalogJsonFromDB;
        if (StringUtils.isEmpty(catalogJson)) {
   
            // lock
            RLock catalogLock = redisson.getLock("catalogJsonLock");
            catalogLock.lock();
            try {
   
                // 双重检查
                catalogJson = redis.opsForValue().get("catalogJson");
                if (StringUtils.isNotEmpty(catalogJson)) {
   
                    return JSON.parseObject(catalogJson,
                            new TypeReference<Map<String, List<Catalog2Vo>>>() {
   
                            });
                }
                catalogJsonFromDB = getCatalogJsonFromDB();
            } finally {
   
                catalogLock.unlock();
            }
            return catalogJsonFromDB;
        }else{
   
            return JSON.parseObject(catalogJson,
                    new TypeReference<Map<String, List<Catalog2Vo>>>() {
   
                    });
        }
    }
/**
* 读写锁实现
*/
public Map<String, List<Catalog2Vo>> getCatalogJson() {
   

        RReadWriteLock readWriteLock 

你可能感兴趣的:(实战项目,java,juc,java,教育电商,mysql,docker,spring,boot)