倉庫地址:https://gitee.com/J_look/ssm-zookeeper/blob/master/README.md
- 鎖:我們在多線程中接觸過,作用就是讓當前的資源不會被其他線程訪問!
我的日記本,不可以被別人看到。所以要鎖在保險柜中當我打開鎖,將日記本拿走了,別人才能使用這個保險柜 - 在zookeeper中使用傳統的鎖引發的 “羊群效應” :1000個人創建節點,只有一個人能成功,999
人需要等待! - 羊群是一種很散亂的組織,平時在一起也是盲目地左沖右撞,但一旦有一只頭羊動起來,其他的羊
也會不假思索地一哄而上,全然不顧旁邊可能有的狼和不遠處更好的草。羊群效應就是比喻人都有一種從眾心理,從眾心理很容易導致盲從,而盲從往往會陷入騙局或遭到失敗。
實現分布式鎖的大致流程
整體思路
- 所有請求進來,在/lock下創建 臨時順序節點 ,放心,zookeeper會幫你編號排序
- 判斷自己是不是/lock下最小的節點
- 是,獲得鎖(創建節點)
- 否則,對前面小我一級的節點進行監聽
- 獲得鎖請求,處理完業務邏輯,釋放鎖(刪除節點),后一個節點得到通知(比你年輕的死了,你
成為最嫩的了) - 重復步驟2
安裝Nginx
安裝nginx運行所需的庫
bash
//一鍵安裝上面四個依賴
yum -y install gcc zlib zlib-devel pcre-devel openssl openssl-devel
下載nginx
在那個目錄下執行這個命令 就會下載到哪個目錄下
bash
//下載tar包
wget http://nginx.org/download/nginx-1.13.7.tar.gz
解壓
注意哦 解壓出來的文件 我們還需要安裝哦
下面所有的命令 都是在nginx-1.13.7文件夾里面進行哦
highlighter-
tar -zxvf nginx-1.13.7.tar.gz
- 查看解壓出來的文件
- highlighter-
- ll ./nginx-1.13.7
安裝
創建一個文件夾,也就是nginx需要安裝到的位置
bash
mkdir /usr/local/nginx
執行命令 考慮到后續安裝ssl證書 添加兩個模塊
bash
./configure --with-http_stub_status_module --with-http_ssl_module
執行make install命令
bash
make install
- 我們可以來到nginx安裝到的目錄下查看
- 你們沒有我這么多目錄 conf 配置 sbin 啟動nginx
- 博主技術有限,還沒有深入去學習nginx的 大致這樣介紹吧
啟動nginx服務
我這個是在/ 目錄底下執行的 你們可以根據 自己所在的目錄去執行
bash
/usr/local/nginx/sbin/nginx -c /usr/local/nginx/conf/nginx.conf
訪問nginx
nginx的默認端口是80
配置nginx
我們所做的配置大概就是
當有人請求去訪問我們服務器,然后負載到我們處理請求的服務器 我這里是為了方便 處理請求的這兩臺服務器 是在我windows上
打開配置文件
bash
# 打開配置文件
vim /usr/local/nginx/conf/nginx.conf
- 圖中 紅框的位置 是需要添加的內容
- 配置含義: 我們的nginx監聽的是服務器的80端口 當有請求訪問時 會負載到 look代理里面 server是處理請求的兩臺服務器
- 查看本機ip Windows ==>ipconfig linux ==> ip a(ip address)
xml
upstream look{
server 192.168.204.1:8001; //192.168.204.1是我本機的ip地址,8001是Tomcat的端口號
server 192.168.204.1:8002; //8002是另外一個工程的tomcat端口號
}
server {
listen 80;
server_name localhost;
#charset koi8-r;
#access_log logs/host.access.log main;
location / {
proxy_pass http://look;
root html;
index index.html index.htm;
}
工程的搭建
搭建ssm框架 有時間推出springboot的版本
- 創建一個maven項目(普通maven項目即可)
創建數據庫:
sql
-- 商品表
create table product(
id int primary key auto_increment, -- 商品編號
product_name varchar(20) not null, -- 商品名稱
stock int not null, -- 庫存
version int not null -- 版本
)
insert into product (product_name,stock,version) values('錦鯉-清空購物車-大獎',5,0)
sql
-- 訂單表
create table `order`(
id varchar(100) primary key, -- 訂單編號
pid int not null, -- 商品編號
userid int not null -- 用戶編號
)
- 項目目錄結構
添加依賴
簡單解釋一下build
我們引入的是tomcat7的插件configuration 配置的是端口 和根目錄注意哦 記得刷新pom文件 build里面會有爆紅 不要緊張 不用管他 后面的配置他會自己消失
xml
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<spring.version>5.2.7.RELEASE</spring.version>
</properties>
<packaging>war</packaging>
<dependencies>
<!-- Spring -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- MyBatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.10</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.7</version>
</dependency>
<!-- 連接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.11</version>
</dependency>
<!-- 數據庫 -->
<dependency>
<groupId>MySQL</groupId>
<artifactId>mysql-connector-JAVA</artifactId>
<version>8.0.29</version>
</dependency>
<!-- junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
</dependency>
</dependencies>
<build>
<plugins>
<!-- maven內嵌的tomcat插件 -->
<plugin>
<groupId>org.Apache.tomcat.maven</groupId>
<!-- 目前apache只提供了tomcat6和tomcat7兩個插件 -->
<artifactId>tomcat7-maven-plugin</artifactId>
<configuration>
<port>8002</port>
<path>/</path>
</configuration>
<executions>
<execution>
<!-- 打包完成后,運行服務 -->
<phase>package</phase>
<goals>
<goal>run</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
折疊
mybatis.xml
注意哦 :仔細查看上面的項目結構 創建相應的文件夾
xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!-- 后臺的日志輸出 輸出到控制臺-->
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
</configuration>
spring.xml
注意哦 :仔細查看上面的項目結構 創建相應的文件夾
xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<!-- 1.掃描包下的注解 -->
<context:component-scan base-package="controller,service,mApper"/>
<!-- 2.創建數據連接池對象 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
destroy-method="close">
<property name="url" value="jdbc:mysql://localhost:3306/2022_zkproduct?serverTimezone=GMT"/>
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="username" value="root"/>
<property name="password" value="317311"/>
<property name="maxActive" value="10"/>
<property name="minIdle" value="5"/>
</bean>
<!-- 3.創建SqlSessionFactory,并引入數據源對象 -->
<bean id="sqlSessionFactory"
class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"></property>
<property name="configLocation" value="classpath:mybatis/mybatis.xml"></property>
</bean>
<!-- 4.告訴spring容器,數據庫語句代碼在哪個文件中-->
<!-- mapper.xDao接口對應resources/mapper/xDao.xml-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="mapper"></property>
</bean>
<!-- 5.將數據源關聯到事務 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 6.開啟事務 -->
<tx:annotation-driven/>
</beans>
折疊
web.xml
注意哦 :仔細查看上面的項目結構 創建相應的文件夾
這里也會出現爆紅,后面會自己消失
xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<servlet>
<servlet-name>springMVC</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring/spring.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
<async-supported>true</async-supported>
</servlet>
<servlet-mapping>
<servlet-name>springMVC</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
實體類
- @Data 是lombok的注解
Product
java
/**
* @author : look-word
* 2022-07-17 10:12
**/
@Data
public class Product implements Serializable {
private Integer id;
private String product_name;
private Integer stock;
private Integer version;
}
Order
highlighter- php
/**
* @author : look-word
* 2022-07-17 10:12
**/
@Data
public class Order implements Serializable {
private String id;
private Integer pid;
private Integer userid;
}
持久層
ProductMapper
java
@Mapper
@Component
public interface ProductMapper {
// 查詢商品(目的查庫存)
@Select("select * from product where id = #{id}")
Product getProduct(@Param("id") int id);
// 減庫存
@Update("update product set stock = stock-1 where id = #{id}")
int reduceStock(@Param("id") int id);
}
OrderMapper
java
@Mapper
@Component
public interface OrderMapper {
// 生成訂單
@Insert("insert into `order` (id,pid,userid) values (#{id},#{pid},#{userid})")
int insert(Order order);
}
service
ProductService
java
/**
* @author : look-word
* 2022-07-17 10:28
**/
public interface ProductService {
// 扣除庫存
void reduceStock(Integer id) throws Exception;
}
ProductServiceImpl
java
/**
* @author : look-word
* 2022-07-17 10:29
**/
@Transactional
@Service
public class ProductServiceImpl implements ProductService {
@Resource
private ProductMapper productMapper;
@Resource
private OrderMapper orderMapper;
@Override
public void reduceStock(Integer id) throws Exception {
// 查詢商品庫存
Product product = productMapper.getProduct(id);
if (product.getStock() <= 0) {
throw new RuntimeException("庫存不足");
}
// 減庫存
int i = productMapper.reduceStock(id);
if (i == 1) {
Order order = new Order();
order.setId(UUID.randomUUID().toString());
order.setUserid(1);
order.setPid(id);
Thread.sleep(500);
orderMapper.insert(order);
} else {
throw new RuntimeException("扣除庫存失敗");
}
}
}
controller
java
/**
* @author : look-word
* 2022-07-17 10:12
**/
@RestController
public class ProductAction {
@Resource
private ProductService productService;
@GetMapping("product/reduce/{id}")
private Object reduce(@PathVariable Integer id) throws Exception {
productService.reduceStock(id);
return "ok";
}
}
啟動測試
- 點擊右側的maven
還記得我們在pom.xml配置的tomcat的插件嗎,我們配置的意思是打包(package)之后會自動運行
在執行打包命令之前,先執行clean命令
執行package命令
測試
- 瀏覽器訪問
highlighter- Go
http://localhost:8001/product/reduce/1
訪問流程
**注意**
在使用jmeter測試的時候 需要啟動兩個服務
- 在啟動第一個之后 去修改pom里面的build里面tomcat插件的端口 8002
- 記得要刷新pom文件,然后再打包啟動即可
啟動jmeter測試
簡單闡述一下:我們會模擬高并發場景下對這個商品的庫存進行扣減
這也就會導致一個問題,會出現商品超賣(出現負的庫存)出現的原因: 在同一時間,訪問的請求很多。
下載地址
解壓雙擊jmeter.bat啟動
創建線程組
這里的線程數量根據自己電腦去設置
創建請求
我們填寫紅框的內容即可就是訪問的地址
- 我們還需要查看請求的結果 創建結果樹 右擊會出現
配置好這些之后,點擊菜單欄綠色啟動標志
- 會出現彈窗 第一個點yes 第二個點cancel(取消)
去數據庫查看
- 沒有啟動前數據庫的庫存
- 可以看到 出現了 超賣
解決超賣
需要用到 zookeeper集群,搭建的文章
zookeeper分布式鎖不需要我們手寫去實現,有封裝好的依賴,引入即可
xml
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>4.2.0</version> <!-- 網友投票最牛逼版本 -->
</dependency>
在控制層中加入分布式鎖的邏輯代碼
- 添加了集群的ip
java
/**
* @author : look-word
* 2022-07-17 10:12
**/
@RestController
public class ProductAction {
@Resource
private ProductService productService;
// 集群ip
private String connectString = "192.168.77.132,192.168.77.131,192.168.77.130";
@GetMapping("product/reduce/{id}")
private Object reduce(@PathVariable Integer id) throws Exception {
// 重試策略 (1000毫秒試1次,最多試3次)
RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3);
//1.創建curator工具對象
CuratorFramework client = CuratorFrameworkFactory.newClient(connectString, retryPolicy);
client.start();
//2.根據工具對象創建“內部互斥鎖”
InterProcessMutex lock = new InterProcessMutex(client, "/product_" + id);
try {
//3.加鎖
lock.acquire();
productService.reduceStock(id);
} catch (Exception e) {
if (e instanceof RuntimeException) {
throw e;
}
} finally {
//4.釋放鎖
lock.release();
}
return "ok";
}
}
啟動jmeter去測試,會發現,請求就像排隊一樣,一個一個出現,數據庫也沒有超賣現象
- 可以看到 只有前面的5課請求成功了,我們的庫存只有5個
- 說明我們的分布式鎖,已經實現了
springboot版本后續會退出
原文鏈接:
https://www.cnblogs.com/look-word/p/16488623.html