赞
踩
[root@localhost opt]# tar -zxvf apache-zookeeper-3.6.0-bin.tar.gz
[root@localhost opt]# mv apache-zookeeper-3.6.0-bin zookeeper
[root@localhost zookeeper]# mkdir zkData
[root@localhost zookeeper]# mkdir zkLog
[root@localhost conf]# cp zoo_sample.cfg zoo.cfg
dataDir=/opt/zookeeper/zkData #数据目录
dataLogDir=/opt/zookeeper/zkLog #日志目录,#若没有配置可以选择不加(一般有默认路径),但是一般我们都会加上
#通常情况下,若没有对应的目录,一般都会自动创建,但为了防止意外,一般要先创建目录
[root@localhost bin]# ./zkServer.sh start
[root@localhost bin]# ./zkServer.sh status
[root@localhost bin]# jps #查看java进程,或者说对应的启动类
[root@localhost bin]# ./zkCli.sh
[zk: localhost:2181(CONNECTED) 0] quit
#ctrl+c也可以退出
#0代表你操作了多少次,每操作一次就加1,如持续的ls等等,可以自己试一下
[root@localhost bin]# ./zkServer.sh stop
#一般若出现某些问题,可以进行停止,然后重新启动
[root@localhost zkData]# vim myid
service network restart
#可以多次重启,重启后未必立即生效,可能需要等一会,就如你无论执行什么操作,都需要执行时间
server.1=192.168.164.128:2888:3888
#如果只有本身的配置或者没有这些配置,那么启动时,就是单独的,而没有操作集群,否则就是操作集群的
server.2=192.168.164.129:2888:3888
server.3=192.168.164.130:2888:3888
#这样,也就可以说明对应的总服务器是3台,所以可以挂一台,而能继续工作,即有一个头leader服务器
[root@localhost bin]# systemctl stop firewalld.service
[root@localhost bin]# ./zkServer.sh start
[root@localhost bin]# ./zkServer.sh status
[root@localhost bin]# ./zkCli.sh
help
ls / #查看根目录下面的东西
ls -s /
create /china
create /usa
create /ru "pujing"
#不是引号的(如单引号和双引号),那么获得节点值时(如命令get /ru)
#就会当成一个整体,而是引号的则是引号里面的数据
#而什么都没加的,显示null
create /japan/Tokyo "hot"
#使用ls /japan查看他的节点
get /japan/Tokyo
create -e /uk
ls /
quit
#注意:必须要quit退出,才会删除临时节点,因为需要删除的操作
#若直接的ctrl+c退出,则不会删除,到那时重启启动客户端时
#由于不是自己客户端的创建,那么都是默认持久的
#因为我们的临时节点,实际上就是客户端会给他做个标记(当执行quit就会根据标记进行删除,而ctrl+c不会)
#所以当客户端退出时,是进行主动的删除操作而已(quit的操作执行删除)
#而我们再次进行的确客户端时,对应出现的数据都是没有标记的,所以可以说他们就是持久的
#再次启动客户端
ls /
#发现没有uk节点了
#一般的,节点的显示由,分开,如[节点1,节点2],若没有节点,则就是[]
create -s /ru/city # 执行三次
ls /ru
#显示[city0000000000, city0000000001, city0000000002]
set /japan/Tokyo "too hot"
addWatch /usa
set /usa "telangpu"
WatchedEvent state:SyncConnected type:NodeDataChanged path:/usa
create /usa/NewYork
WatchedEvent state:SyncConnected type:NodeCreatedpath:/usa/NewYork
delete /usa/NewYork
#若不是非空的,则删除不了,即会有报错
WatchedEvent state:SyncConnected type:NodeDeleted path:/usa/NewYork
deleteall /ru
<dependencies> <dependency> <!--日志需要--> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-core</artifactId> <version>2.8.2</version> </dependency> <dependency> <!--对应的包需要,如ZooKeeper对象创建,即这个类的使用--> <groupId>org.apache.zookeeper</groupId> <artifactId>zookeeper</artifactId> <version>3.6.0</version> <!--与linux的zookeeper版本通常要一致,防止出现问题--> </dependency> <dependency> <!--测试--> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency> </dependencies>
# log4j.rootLogger = 表示根日志级别 log4j.rootLogger=INFO, stdout ### log4j.appender.stdout = 表示输出方式 log4j.appender.stdout=org.apache.log4j.ConsoleAppender # 表示输出格式 log4j.appender.stdout.layout=org.apache.log4j.PatternLayout # 打印信息格式 log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m%n ### log4j.appender.logfile = 表示文件日志输出方式 log4j.appender.logfile=org.apache.log4j.FileAppender #日志文件存放位置 log4j.appender.logfile.File=target/zk.log # log4j.appender.logfile.layout = 表示输出格式 log4j.appender.logfile.layout=org.apache.log4j.PatternLayout # log4j.appender.logfile.layout.ConversionPattern = 表示打印格式 log4j.appender.logfile.layout.ConversionPattern=%d %p [%c] - %m%n
package test; import org.apache.zookeeper.WatchedEvent; import org.apache.zookeeper.Watcher; import org.apache.zookeeper.ZooKeeper; import org.junit.Test; import java.io.IOException; /** * */ public class TestZK { //zookeeper集群的ip和端口,由于是共用,所以也可以连接 //2181端口是操作数据的端口,就如mysql操作3306一样 private String connStr = "192.168.164.128:2181,192.168.164.129:2181,192.168.164.129:2181"; //在对于格式正确的情况下(不正确会跳过或者报错) //只对应ip和端口,中间的逗号和其他的不做要求,如逗号可以是中文逗号,或者是- //虽然对应ip和端口信息不存在也可,他只是提供的对应的连接,操作时,才会进行确定,即那时可能就会报错了 //而之所以是可能,是因为他会随机取上面的某个ip和端口信息,所以多次运行的结果可能不同 //比如选择到错误的ip和端口信息,那么就会报错 //即一般我们不会写上不存在的ip和端口信息,防止报错 //即下面进行操作节点的代码时,可能会取得不存在的ip或者端口信息,那么就会报错 //所以虽然我们可以写一个,但为了完整,一般写多个,且正确存在的,即这里就是 //可能并不绝对,即有可能只要有正确的即可 //session超时的时间:时间不易设置太小,因为zookeeper和加载集群环境会因为性能等原因而延迟略高 //如果时间太少,还没有创建好客户端,就开始操作节点,会报错的 //大多数错误就是这里,即需要设置更大的超时时间) //即我们进行连接集群时,可能会要很久,防止还没有连上就出现问题(如报错) private int session = 60 * 1000; //当然了,若加载完成,就会直接操作,并不是等待他的所有时间 //而太小的话,是因为等待完毕,会直接操作 private ZooKeeper zooKeeper; @Before public void init() throws IOException { zooKeeper = new ZooKeeper(connStr, session, new Watcher() { //得到了对应的客户端 @Override public void process(WatchedEvent watchedEvent) { //监听操作 System.out.println("得到监听反馈,进行业务处理"); //基本上只要你操作对应的监听对象,无论是上面修改,删除,获取,或者增加,都会进行打印信息,即监听 } }); } //注意:客户端启动或者这里的初始化,都是操作2181端口,一般一个端口只分占用和不占用 //而不分使用多少,即可以给该端口传递多个信息,被获取 //就如所有的浏览器,或者客户端,都可以操作8080端口,访问一个网站,但该网站却只能占用一个8080端口 }
@Test public void createNode() throws InterruptedException, KeeperException { // 参数1:要创建的节点的路径,需要指定路径,即/,因为我们是在对于zookeeper里创建节点的 // 参数2:节点数据 // 参数3:节点权限 // 参数4:节点的类型 CreateMode.PERSISTENT持久型 String lagou = zooKeeper.create("/lagou", "laosun".getBytes(), //当然也可以是null ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); //上面创建完后,对应的监听器,就知道你创建了,即他默认监听当前 //因为在zookeeper里面是可以监听没有节点的 //即相当于addWatch /lagou,当你创建时,就会进行监听反馈 //一般的,我们使用zookeeper来得到监听反馈时,我们是指定监听的,然后默认打印对应的信息 //也相当于上面的process方法,即相当于我们创建监听时,对应的zookeeper就准备了创建对应zookeeper对象 //只是他将监听的操作给了创建的对象哪个地方,在改变之前进行创建 //而这里是改变时进行指定监听,或者说创建监听 //而使得当集群中某个节点变化时,进行打印 //节点是共存了,因为会给对应的所有集群进行一致的操作 //只是这里的打印在idea里显示了,而他的打印,只是在对应的zookeeper服务器里面 //但无论什么情况,都是客户端的操作,只是zookeeper有默认的客户端操作而已 //我们可以通过节点信息来获取入驻的商家信息的,他的信息改变 //一般会使得节点信息改变,从而得出监听反馈信息 System.out.println(lagou); // /lagou }
@Test public void getDate() throws InterruptedException, KeeperException { //参数1:路径 //参数2:是否继续监听,先说明一下,这个参数,我们在zookeeper客户端里面,一般使用addWatch进行添加监听 //而removewatches进行删除监听,前面说过,当进行操作时,会进行监听的打印,因为操作了 //当然,若程序执行完毕,那么对应基本都是进行关闭的,所有这样的你看不到对应的操作,若中间有等待的操作 //如System.in.read(),返回输入的数的ASCII值(实际上你甚至可以叫做ASCLL),当然这是我的习惯 //如输入a,返回97 //在@Test注解里面,不能输入,可以在main方法里进行输入 //这时,若你设置的是true,那么你去改变对应的节点,即会再次打印监听消息,若是false,则会进行删除监听 //所有,当是false时,对应的监听消息没有,即这个参数,可以理解为,是否继续监听,但true只会监听一次 //监听后,即对应方法执行后(一般我们设置打印信息,即打印信息后),就会删除这个监听 //注意:这里只会监听自身节点的删除和修改(实际上是可以设置部分监听的,具体去百度) //子节点的创建和删除和set和get,以及自身的get和创建不会监听 //set就是修改,正是因为自身创建监听不到,所以,一般都会删除,即可以说创建不监听 //只有打印后(即监听后),才会删除监听,而zookeeper自带的客户端不会 //发现他符合下面的第一种,删除自身的监听是共用的 //监听节点数据的变化:get path [watch] //监听子节点增减的变化:ls path [watch] //参数3:数据存放处 byte[] data = zooKeeper.getData("/lagou", false, new Stat()); //这里与zookeeper客户端一样,若没有对应的路径,则会报错,只是各自的显示不同而已 String s = new String(data); //注意:data不要是null,否则里面一般会报空指针异常的 System.out.println(s); // 打印出对应的值 //注意:与zookeeper客户端一样,都会进行打印信息 //而zookeeper的默认客户端是有操作的,即对应的打印信息可能与其他的操作是不同的 //最后要注意,若你运行时,认为没有问题,但还是报错的,你可以将session超时的时间调大点 }
//修改节点 @Test public void update()throws Exception{ //参数1:路径 //参数2:修改后的数值 //参数3:指定的版本,这里需要一致,否则会报错 //也就是dataVersion:数据变化版本号一致,可以通过ls -s /lagou查看 //这里要注意一下:每一次的修改(可以修改一样的),这个版本号就会加1,所以记得查看 Stat stat = zooKeeper.setData("/lago", "laosunAka".getBytes(), 1); System.out.println(stat); //输出38654705709,51539607589,1654160273539,1654226907038,2,0,0,0,9,0,38654705709 //是个对象 //其中2,0,0,0,9,0中2表示修改后的版本,即dataVersion值,而9表示数据长度,即laosunAka长度 //其他的也就是这个路径的其他数值了 //具体看如下: /* 2 dataVersion:数据变化版本号,每次修改数据都会加1(只是修改,像删除和创建不会加1) 0 cversion:创建版本号,子节点修改次数(如创建子节点和删除子节点都会进行加1) 0 aclVersion:权限版本号 0 ephemeralOwner:如果是临时节点,这个是znode拥有者的session id,如果不是临时节点则是0 9 dataLength:数据长度 0 numChildren:子节点数 */ //其中第一个(38654705709),第三个(1654160273539),第六个(38654705709),与路径有关 //其他的与时间有关 }
//删除节点
@Test
public void delete() throws Exception {
//参数1:路径
//参数2:也就是dataVersion值,必须一致,否则报错
zooKeeper.delete("/lagou", 8);
System.out.println("删除成功!");
}
//获取子节点
@Test
public void getChildren() throws Exception {
//参数1:路径
//参数2:是否继续监听,与上面查询节点值的参数是一样的作用
List<String> children = zooKeeper.getChildren("/",false);
for (String child : children) {
System.out.println(child); //所有节点的显示
}
}
//监听子节点 @Test public void getChildren() throws Exception { //参数1:路径 //参数2:是否继续监听,与上面查询节点值的参数是一样的作用,除了监听对象的判断 //即注意:只会监听该节点以及子节点(直接的子节点,子节点的子节点或者更深入的不会)的创建和删除操作 //由于删除后,就删除监听了,所以自身的创建基本是监听不到的,或者说,直接没有设置 //对应的get和set不会进行监听 //只有打印后(即监听后),才会删除监听 //发现他符合下面的第二种,删除自身的监听是共用的 //监听节点数据的变化:get path [watch] //监听子节点增减的变化:ls path [watch] List<String> children = zKcli.getChildren("/", true); // true:注册监听 for (String child : children) { System.out.println(child); } // 让线程不停止,等待监听的响应 System.in.read(); }
public void process(WatchedEvent watchedEvent) {
System.out.println("得到监听反馈,进行业务处理");
System.out.println(watchedEvent.getType());
}
//判断节点是否存在
@Test
public void exist() throws Exception {
//参数1:路径
//参数2:是否继续监听,与上面查询节点值的参数是一样的作用
//注意:这里只会监听自身节点的删除和修改,子节点的创建和删除和set和get,以及自身的get和创建不会监听
//set就是修改,正是因为自身创建监听不到,所以,一般都会删除,即可以说创建不监听
//只有打印后(即监听后),才会删除监听
//发现他符合下面的第一种,删除自身的监听是共用的
//监听节点数据的变化:get path [watch]
//监听子节点增减的变化:ls path [watch]
Stat stat = zooKeeper.exists("/lagou", false);
System.out.println(stat == null ? "不存在" : "存在");
}
package meituan; import org.apache.zookeeper.*; import java.io.IOException; /** * */ public class ShopServer { private String connStr = "192.168.164.128:2181,192.168.164.129:2181,192.168.164.130:2181"; private int session = 200 * 1000; private ZooKeeper zooKeeper; //连接zookeeper public void conn() throws Exception{ zooKeeper = new ZooKeeper(connStr, session, new Watcher() { @Override public void process(WatchedEvent watchedEvent) { System.out.println("监听的反馈"); } }); } //注册到zookeeper public void register(String shopName) throws Exception{ String s = zooKeeper.create("/meituan/shop", shopName.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL); //CreateMode.EPHEMERAL_SEQUENTIAL短暂有顺序的节点,即临时有序的节点 //注意用来,设置编号,以及断开时节点自动删除,也就意味着关店(这是主要判断) System.out.println("【"+ shopName + "】开始营业了" + s); } public static void main(String[] args) throws Exception { //我要开一个饭店 ShopServer sjp = new ShopServer(); //连接zookeeper集群(和美团取得联系) sjp.conn(); //将服务节点,注册到zookeeper(入驻美团) sjp.register(args[0]); //因为我们需要进行参数变化,所以这里我们操作变化的参数 //业务逻辑处理(做生意) sjp.business(args[0]); } //做买卖 public void business(String shopName) throws IOException { System.out.println("【" + shopName + "】正在火爆营业中"); System.in.read(); //做生意一般都是一直做的 } }
package meituan; import org.apache.zookeeper.KeeperException; import org.apache.zookeeper.WatchedEvent; import org.apache.zookeeper.Watcher; import org.apache.zookeeper.ZooKeeper; import org.apache.zookeeper.data.Stat; import java.io.IOException; import java.util.ArrayList; import java.util.List; /** * */ public class Customers { private String connStr = "192.168.164.128:2181,192.168.164.129:2181,192.168.164.130:2181"; private int session = 200 * 1000; private ZooKeeper zooKeeper; //连接zookeeper public void conn() throws Exception{ zooKeeper = new ZooKeeper(connStr, session, new Watcher() { @Override public void process(WatchedEvent watchedEvent) { System.out.println("监听的反馈"); } }); } //获取商家列表,或者说获取所有商家,即获取商家 private void getShopList() throws InterruptedException, KeeperException { //获取美团子节点信息,设置true,只监听自身和子节点的创建和删除 List<String> children = zooKeeper.getChildren("/meituan", true); //声明存储服务器信息的集合 ArrayList<String> list = new ArrayList<>(); for(String a : children){ byte[] data = zooKeeper.getData("/meituan/" + a, false, new Stat()); String s = new String(data); list.add(s); } System.out.println("目前正在营业的商家" +list); } public static void main(String[] args) throws Exception { //每个手机或者应用,即用户 Customers customers = new Customers(); //zookeeper连接(用户打开美团app) customers.conn(); //获取meituan下的所有子节点列表(获取商家列表) customers.getShopList(); //业务逻辑处理(对比商家,下单点餐) customers.business(); } private void business() throws IOException { System.out.println("用户正在浏览商家"); System.in.read(); } }
create /meituan/KFC "KFC" #先存放商家名称
create /meituan/BKC "BurgerKing"
create /meituan/baozi "baozi"
delete /meituan/baozi
//连接zookeeper public void conn() throws Exception{ zooKeeper = new ZooKeeper(connStr, session, new Watcher() { @Override public void process(WatchedEvent watchedEvent) { System.out.println("监听的反馈"); //再次获取商家列表 try { getShopList(); //内部类抛不出异常,类之间不可,即需要try-catch //而正是因为程序没有执行完,被System.in.read()进行拦截,使得不会进行关闭,也就是说 //不会进行监听的删除关闭或者取消监听,即这里就形成了闭环(无限的循环监听) } catch (Exception e) { e.printStackTrace(); } } }); }
-- 创建数据库zkproduct,使用字符集utf8 CREATE DATABASE zkproduct CHARACTER SET utf8; USE zkproduct; -- 商品表 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); -- 订单表 CREATE TABLE `order`( id VARCHAR(100) PRIMARY KEY, -- 订单编号 pid INT NOT NULL, -- 商品编号 userid INT NOT NULL -- 用户编号 ); -- 若出现错误,那么删除掉注释,因为可能有隐藏的符号(一般是没有的)
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven- 4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.lagou</groupId> <artifactId>zk_product</artifactId> <version>1.0-SNAPSHOT</version> <properties> <maven.compiler.source>11</maven.compiler.source> <maven.compiler.target>11</maven.compiler.target> <spring.version>5.2.7.RELEASE</spring.version> </properties> <packaging>war</packaging> <dependencies> <!-- Spring --> <dependency> <!--IOC容器的对象需要--> <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> <!--spring使用连接池的,包括一些tx操作 当需要tx的一些操作时(如事务传播),那么可以导入tx--> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>${spring.version}</version> </dependency> <!-- Mybatis --> <dependency> <!--引入mybatis依赖,如工厂的一些类--> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.5</version> </dependency> <dependency> <!--可以使用注解,配置mybatis--> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>2.0.5</version> </dependency> <!-- 连接池 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.10</version> </dependency> <!-- 数据库驱动 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.20</version> </dependency> <!-- junit @Test的操作--> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <!-- maven内嵌的tomcat插件 --> <plugin> <groupId>org.apache.tomcat.maven</groupId> <!-- 目前apache只提供了tomcat6和tomcat7两个插件 --> <artifactId>tomcat7-maven-plugin</artifactId> <version>2.1</version> <configuration> <port>8001</port> <path>/</path> </configuration> <executions> <execution> <!-- 打包完成后,运行服务 --> <phase>package</phase> <goals> <goal>run</goal> </goals> </execution> </executions> </plugin> <plugin> <!--maven的插件依赖,这里是对应编译插件,设置参数--> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.1</version> <configuration> <!--编译级别,指定JDK的版本以及操作编码,因为编译也要得到数据,那么也就需要编码--> <source>11</source> <target>11</target> <!--在设置里的Java Compiler,可以看到对应的版本发生了变化,记得刷新--> <encoding>UTF-8</encoding> </configuration> </plugin> </plugins> </build> </project>
<?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>
<?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"> <!--destroy-method="close"的作用是当数据库连接不使用的时候 就把该连接重新放到数据池中,方便下次使用调用,实际上就是调用close方法 在最后销毁的时候,而由于连接池的close方法就是归还连接,所以就算重新放入连接池中 因为这里只是提供连接池,并没有操作最终的练级去向,即还是需要我们进行关闭 --> <!--serverTimezone是数据库连接中的参数,用于设置服务时间 标识设置服务时间为东一区时间,即国际日期变更线时间--> <property name="url" value="jdbc:mysql://192.168.164.128:3306/zkproduct?serverTimezone=GMT"/> <property name="driverClassName" value="com.mysql.jdbc.Driver"/> <property name="username" value="root"/> <property name="password" value="QiDian@666"/> </bean> <!-- 3.创建SqlSessionFactory,并引入数据源对象 --> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource"></property> <property name="configLocation" value="classpath:mybatis/mybatis-config.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>
<?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由一个线程来操作,我们知道,只有当响应数据后,这个线程才基本结束 即会有页面的访问完成 若业务代码非常耗时,使得该线程资源一直占用,即页面一直没有访问完成 而使用异步处理,可以操作业务代码时,页面以及访问完成,只是需要等待一些时间,就与ajax类似 正是因为该线程是异步的,所以就出现了类似ajax的操作--> </servlet> <servlet-mapping> <servlet-name>springMVC</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> </web-app>
package mapper; import models.Order; import org.apache.ibatis.annotations.Insert; import org.apache.ibatis.annotations.Mapper; import org.springframework.stereotype.Component; /** * */ @Mapper //扫描这里(使用了注解的扫描),使得mapper即可以说是注解会起作用 //一般操作注解,因为配置文件的操作,一般都不是他来扫描的 //mybatis启动时会找他,整个项目找,找到@Mapper后,扫描当前类 //那么对应的注解也会起作用 //一般要与spring整合时使用,创建当前实例放到ioc容器里面,然后注入使得调用方法(因为sql语句的注解起作用了) //但是单独使用的话,基本不会有对应的作用 //而不用配置文件扫描了(虽然上面也扫描了,即对应配置文件已经扫描了,所以可以不写) @Component //这个可以删掉,因为配置里面有这个实例,且他会完全覆盖这个其他实例,因为他是在扫描后进行覆盖 //所以这个就没有起作用,即没有相同的实例,所以注入可以操作 //上面两个都可以不写 public interface OrderMapper { // 生成订单 @Insert("insert into `order` (id,pid,userid) values (#{id},#{pid},#{userid})") int insert(Order order); //注意:由于order在mysql里面是关键字,所有我们需要显示的指定,即加上``,来包括 }
package mapper; import models.Product; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Select; import org.apache.ibatis.annotations.Update; import org.springframework.stereotype.Component; /** * */ @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); }
package models; import java.io.Serializable; /** * */ public class Order implements Serializable { private String id; private int pid; private int userid; public String getId() { return id; } public void setId(String id) { this.id = id; } public int getPid() { return pid; } public void setPid(int pid) { this.pid = pid; } public int getUserid() { return userid; } public void setUserid(int userid) { this.userid = userid; } public Order() { } public Order(String id, int pid, int userid) { this.id = id; this.pid = pid; this.userid = userid; } }
package models; import java.io.Serializable; /** * */ public class Product implements Serializable { private int id; private String product_name; private int stock; private int version; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getProduct_name() { return product_name; } public void setProduct_name(String product_name) { this.product_name = product_name; } public int getStock() { return stock; } public void setStock(int stock) { this.stock = stock; } public int getVersion() { return version; } public void setVersion(int version) { this.version = version; } public Product() { } public Product(int id, String product_name, int stock, int version) { this.id = id; this.product_name = product_name; this.stock = stock; this.version = version; } }
package service;
/**
*
*/
public interface ProductService {
// 减库存
int reduceStock(int id);
}
package service.impl; import mapper.OrderMapper; import mapper.ProductMapper; import models.Order; import models.Product; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import service.ProductService; import java.util.UUID; /** * */ @Service public class ProductServiceImpl implements ProductService { @Autowired private ProductMapper productMapper; @Autowired private OrderMapper orderMapper; // 减库存 @Override public int reduceStock(int id) { //获取库存(根据商品id查询商品) 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()); //使用UUID工具帮我们生成一个订单号 order.setPid(id); order.setUserid(101); orderMapper.insert(order); } else { throw new RuntimeException("减库存失败,请重试!"); } return i; } }
package controller; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.ResponseBody; import service.ProductService; /** * */ @Controller public class ProductAction { @Autowired private ProductService productService; @GetMapping("/product/reduce") @ResponseBody public Object reduceStock(int id) throws Exception { productService.reduceStock(id); return "ok"; } }
./nginx -c conf/nginx.conf
#后面默认在nginx目录里,即指定该配置文件启动,若你使用/开头,那么就是根目录开始,否则默认nginx目录开始
upstream sga{
server 192.168.164.1:8001;
server 192.168.164.1:8002; #根据cmd的ipconfig,查看本机的ip地址,都是网卡,基本随便一个都可
}
server {
listen 80;
#server_name localhost; #localhost就算设置域名,也是默认本机,即127.0.0.1
server_name 192.168.164.128; #即这里改一下
location / {
proxy_pass http://sga;
root html;
index ?index.html index.htm;
}
}
<dependency>
<!--有对应的类,如CuratorFramework操作(curator工具对象)-->
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>4.2.0</version> <!-- 网友投票最牛逼版本 -->
</dependency>
package controller; import org.apache.curator.RetryPolicy; import org.apache.curator.framework.CuratorFramework; import org.apache.curator.framework.CuratorFrameworkFactory; import org.apache.curator.framework.recipes.locks.InterProcessMutex; import org.apache.curator.retry.ExponentialBackoffRetry; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.ResponseBody; import service.ProductService; /** * */ @Controller public class ProductAction { @Autowired private ProductService productService; private static String connectString = "192.168.164.128:2181,192.168.164.129:2181,192.168.164.130:2181"; @GetMapping("/product/reduce") @ResponseBody public Object reduceStock(int id) throws Exception { //重试策略(1000毫秒试一次,最多试3次),进行连接集群的用处,若还是连不上集群,就会报错 RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3); //创建curator工具对象 CuratorFramework client = CuratorFrameworkFactory.newClient(connectString, retryPolicy); //启动 client.start(); //根据工具对象,创建内部互斥锁 InterProcessMutex lock = new InterProcessMutex(client, "/product_" + id); //后面会帮我们创建编号,前面说过的 try { //加锁 lock.acquire(); productService.reduceStock(id); }catch (Exception e){ e.printStackTrace(); if(e instanceof RuntimeException){ throw e; } }finally { //释放锁 lock.release(); //会删除当前节点,并有通知,可以看到,这个节点差不多是可以随便写的 //不管你是否操作成功,都要打开锁 //可能会有疑问,假设释放锁后,再进行请求会怎么样,前面说过 //有监听的,那么等待通知,然后判断释放最小节点,无监听的直接判断最小节点 //而这样的,相当于没有监听的,那么直接判断是否是最小节点 } return "ok"; } }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。