一、Zookeeper實現分布式鎖
分布式鎖主要用於在分布式環境中保證數據的一致性。
包括跨進程、跨機器、跨網絡導致共享資源不一致的問題。
1. 分布式鎖的實現思路
說明:
這種實現會有一個缺點,即當有很多進程在等待鎖的時候,在釋放鎖的時候會有很多進程就過來爭奪鎖,這種現象稱為 “驚群效應”
2. 分布式鎖優化后的實現思路
3. Zookeeper分布式鎖的代碼實現
准備工作:
1)安裝Zookeeper,具體參考我前面的我文章Zookeeper系列一:Zookeeper介紹、Zookeeper安裝配置、ZK Shell的使用
2)新建一個maven項目ZK-Demo,然后在pom.xml里面引入相關的依賴
<dependency> <groupId>com.101tec</groupId> <artifactId>zkclient</artifactId> <version>0.10</version> </dependency>
3.1 Zookeeper分布式鎖的核心代碼實現
實現邏輯參考“2. 分布式鎖優化后的實現思路”中的流程圖
package com.study.demo.lock; import java.util.Collections; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import org.I0Itec.zkclient.IZkDataListener; import org.I0Itec.zkclient.ZkClient; import org.I0Itec.zkclient.serialize.SerializableSerializer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * * @Description: Zookeeper分布式鎖的核心代碼實現 * @author leeSmall * @date 2018年9月4日 * */ public class DistributedLock implements Lock { private static Logger logger = LoggerFactory.getLogger(DistributedLock.class); private static final String ZOOKEEPER_IP_PORT = "192.168.152.130:2181"; private static final String LOCK_PATH = "/LOCK"; private ZkClient client = new ZkClient(ZOOKEEPER_IP_PORT, 4000, 4000, new SerializableSerializer()); private CountDownLatch cdl; private String beforePath;// 當前請求的節點前一個節點 private String currentPath;// 當前請求的節點 // 判斷有沒有LOCK目錄,沒有則創建 public DistributedLock() { if (!this.client.exists(LOCK_PATH)) { this.client.createPersistent(LOCK_PATH); } } public void lock() { //嘗試去獲取分布式鎖失敗 if (!tryLock()) { //對次小節點進行監聽 waitForLock(); lock(); } else { logger.info(Thread.currentThread().getName() + " 獲得分布式鎖!"); } } public boolean tryLock() { // 如果currentPath為空則為第一次嘗試加鎖,第一次加鎖賦值currentPath if (currentPath == null || currentPath.length() <= 0) { // 創建一個臨時順序節點 currentPath = this.client.createEphemeralSequential(LOCK_PATH + '/', "lock"); System.out.println("---------------------------->" + currentPath); } // 獲取所有臨時節點並排序,臨時節點名稱為自增長的字符串如:0000000400 List<String> childrens = this.client.getChildren(LOCK_PATH); //由小到大排序所有子節點 Collections.sort(childrens); //判斷創建的子節點/LOCK/Node-n是否最小,即currentPath,如果當前節點等於childrens中的最小的一個就占用鎖 if (currentPath.equals(LOCK_PATH + '/' + childrens.get(0))) { return true; } //找出比創建的臨時順序節子節點/LOCK/Node-n次小的節點,並賦值給beforePath else { int wz = Collections.binarySearch(childrens, currentPath.substring(6)); beforePath = LOCK_PATH + '/' + childrens.get(wz - 1); } return false; } //等待鎖,對次小節點進行監聽 private void waitForLock() { IZkDataListener listener = new IZkDataListener() { public void handleDataDeleted(String dataPath) throws Exception { logger.info(Thread.currentThread().getName() + ":捕獲到DataDelete事件!---------------------------"); if (cdl != null) { cdl.countDown(); } } public void handleDataChange(String dataPath, Object data) throws Exception { } }; // 對次小節點進行監聽,即beforePath-給排在前面的的節點增加數據刪除的watcher this.client.subscribeDataChanges(beforePath, listener); if (this.client.exists(beforePath)) { cdl = new CountDownLatch(1); try { cdl.await(); } catch (InterruptedException e) { e.printStackTrace(); } } this.client.unsubscribeDataChanges(beforePath, listener); } //完成業務邏輯以后釋放鎖 public void unlock() { // 刪除當前臨時節點 client.delete(currentPath); } // ========================================== public void lockInterruptibly() throws InterruptedException { } public boolean tryLock(long time, TimeUnit unit) throws InterruptedException { return false; } public Condition newCondition() { return null; } }
3.2 在業務里面使用分布式鎖
package com.study.demo.lock; import java.util.concurrent.CountDownLatch; import java.util.concurrent.locks.Lock; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * * @Description: 在業務里面使用分布式鎖 * @author leeSmall * @date 2018年9月4日 * */ public class OrderServiceImpl implements Runnable { private static OrderCodeGenerator ong = new OrderCodeGenerator(); private Logger logger = LoggerFactory.getLogger(OrderServiceImpl.class); // 同時並發的線程數 private static final int NUM = 10; // 按照線程數初始化倒計數器,倒計數器 private static CountDownLatch cdl = new CountDownLatch(NUM); private Lock lock = new DistributedLock(); // 創建訂單接口 public void createOrder() { String orderCode = null; //准備獲取鎖 lock.lock(); try { // 獲取訂單編號 orderCode = ong.getOrderCode(); } catch (Exception e) { // TODO: handle exception } finally { //完成業務邏輯以后釋放鎖 lock.unlock(); } // ……業務代碼 logger.info("insert into DB使用id:=======================>" + orderCode); } public void run() { try { // 等待其他線程初始化 cdl.await(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } // 創建訂單 createOrder(); } public static void main(String[] args) { for (int i = 1; i <= NUM; i++) { // 按照線程數迭代實例化線程 new Thread(new OrderServiceImpl()).start(); // 創建一個線程,倒計數器減1 cdl.countDown(); } } }
工具類:
package com.study.demo.lock; import java.text.SimpleDateFormat; import java.util.Date; public class OrderCodeGenerator { // 自增長序列 private static int i = 0; // 按照“年-月-日-小時-分鍾-秒-自增長序列”的規則生成訂單編號 public String getOrderCode() { Date now = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss"); return sdf.format(now) + ++i; } }
二、Zookeeper實現配置中心
1. 首先在zookeeper里面創建一個Jdbc的節點,在下面分別創建4個子節點/Jdbc/url、/Jdbc/uname、/Jdbc/password、/Jdbc/driver
create /Jdbc ''
create /Jdbc/url jdbc.mysql://192.168.152.1/dbspread
create /Jdbc/uname root
create /Jdbc/password 123456
create /Jdbc/driver com.mysql.jdbc.Driver
注意:/Jdbc/url這個節點的值是錯的
2. 新建一個zkdemo的maven的web項目
項目結構如下:
2.1 在pom.xml文件里面引入下面依賴:
<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/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.study.demo</groupId> <artifactId>zkdemo</artifactId> <packaging>war</packaging> <version>0.0.1-SNAPSHOT</version> <name>zkdemo Maven Webapp</name> <url>http://maven.apache.org</url> <properties> <spring.version>4.3.8.RELEASE</spring.version> </properties> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>3.8.1</version> <scope>test</scope> </dependency> <dependency> <groupId>org.apache.zookeeper</groupId> <artifactId>zookeeper</artifactId> <version>3.4.10</version> </dependency> <dependency> <groupId>com.101tec</groupId> <artifactId>zkclient</artifactId> <version>0.10</version> </dependency> <dependency> <groupId>org.apache.curator</groupId> <artifactId>curator-framework</artifactId> <version>4.0.0</version> </dependency> <dependency> <groupId>org.apache.curator</groupId> <artifactId>curator-recipes</artifactId> <version>4.0.0</version> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.1.0</version> </dependency> <dependency> <groupId>org.apache.tomcat</groupId> <artifactId>tomcat-catalina</artifactId> <version>7.0.39</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</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-jdbc</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>com.zaxxer</groupId> <artifactId>HikariCP</artifactId> <version>2.7.1</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.41</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.25</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>jcl-over-slf4j</artifactId> <version>1.7.25</version> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-core</artifactId> <version>2.9.1</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.9.1</version> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency> </dependencies> <build> <finalName>zkdemo</finalName> </build> </project>
2.2 新建一個zookeeper配置中心類,從zookeeper動態獲取數據庫配置
package com.study.demo.config; import java.util.List; import java.util.Properties; import org.apache.curator.framework.CuratorFramework; import org.apache.curator.framework.CuratorFrameworkFactory; import org.apache.curator.framework.recipes.cache.TreeCache; import org.apache.curator.framework.recipes.cache.TreeCacheEvent; import org.apache.curator.framework.recipes.cache.TreeCacheListener; import org.apache.curator.retry.ExponentialBackoffRetry; import org.springframework.web.context.ContextLoader; import org.springframework.web.context.WebApplicationContext; import com.zaxxer.hikari.HikariDataSource; /** * * @Description: zookeeper配置中心類,從zookeeper動態獲取數據庫配置 * @author leeSmall * @date 2018年9月10日 * */ public class ZookeeperConfigurerCentral { //curator客戶端 private CuratorFramework zkClient; //curator事件監聽 private TreeCache treeCache; //zookeeper的ip和端口 private String zkServers; //zookeeper上的/Jdbc路徑 private String zkPath; //超時設置 private int sessionTimeout; //讀取zookeeper上的數據庫配置文件放到這里 private Properties props; public ZookeeperConfigurerCentral(String zkServers, String zkPath, int sessionTimeout) { this.zkServers = zkServers; this.zkPath = zkPath; this.sessionTimeout = sessionTimeout; this.props = new Properties(); //初始化curator客戶端 initZkClient(); //從zookeeper的Jdbc節點下獲取數據庫配置存入props getConfigData(); //對zookeeper上的數據庫配置文件所在節點進行監聽,如果有改變就動態刷新props addZkListener(); } //初始化curator客戶端 private void initZkClient() { zkClient = CuratorFrameworkFactory.builder().connectString(zkServers).sessionTimeoutMs(sessionTimeout) .retryPolicy(new ExponentialBackoffRetry(1000, 3)).build(); zkClient.start(); } //從zookeeper的Jdbc節點下獲取數據庫配置存入props private void getConfigData() { try { List<String> list = zkClient.getChildren().forPath(zkPath); for (String key : list) { String value = new String(zkClient.getData().forPath(zkPath + "/" + key)); if (value != null && value.length() > 0) { props.put(key, value); } } } catch (Exception e) { e.printStackTrace(); } } //對zookeeper上的數據庫配置文件所在節點進行監聽,如果有改變就動態刷新props private void addZkListener() { TreeCacheListener listener = new TreeCacheListener() { public void childEvent(CuratorFramework client, TreeCacheEvent event) throws Exception { if (event.getType() == TreeCacheEvent.Type.NODE_UPDATED) { getConfigData(); WebApplicationContext ctx = ContextLoader.getCurrentWebApplicationContext(); HikariDataSource dataSource = (HikariDataSource) ctx.getBean("dataSource"); System.out.println("================"+props.getProperty("url")); dataSource.setJdbcUrl(props.getProperty("url")); dataSource.setUsername(props.getProperty("uname")); dataSource.setPassword(props.getProperty("password ")); dataSource.setDriverClassName(props.getProperty("driver ")); } } }; treeCache = new TreeCache(zkClient, zkPath); try { treeCache.start(); treeCache.getListenable().addListener(listener); } catch (Exception e) { e.printStackTrace(); } } public Properties getProps() { return props; } public void setZkServers(String zkServers) { this.zkServers = zkServers; } public void setZkPath(String zkPath) { this.zkPath = zkPath; } public void setSessionTimeout(int sessionTimeout) { this.sessionTimeout = sessionTimeout; } }
2.3 新建一個加載props里面的數據庫配置的類
package com.study.demo.config; import java.util.Properties; import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer; /** * * @Description: 加載props里面的數據庫配置,這個類等價於以前在xml文件里面的配置: * <context:property-placeholder location="classpath:config/jdbc_conf.properties"/> * @author leeSmall * @date 2018年9月10日 * */ public class ZookeeperPlaceholderConfigurer extends PropertyPlaceholderConfigurer { private ZookeeperConfigurerCentral zkConfigurerCentral; @Override protected void processProperties(ConfigurableListableBeanFactory beanFactoryToProcess, Properties props) throws BeansException { System.out.println(zkConfigurerCentral.getProps()); super.processProperties(beanFactoryToProcess, zkConfigurerCentral.getProps()); } public void setzkConfigurerCentral(ZookeeperConfigurerCentral zkConfigurerCentral) { this.zkConfigurerCentral = zkConfigurerCentral; } }
2.4 在/zkdemo/src/main/webapp/WEB-INF/config/applicationContext.xml配置2.2和2.3新建的兩個主類
<?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:context="http://www.springframework.org/schema/context" 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"> <context:annotation-config /> <context:component-scan base-package="com.study.demo" /> <!--通過構造函數注入zkServers、sessionTimeout、zkPath從zookeeper動態獲取數據庫配置 --> <bean id="zkConfigurerCentral" class="com.study.demo.config.ZookeeperConfigurerCentral"> <constructor-arg name="zkServers" value="192.168.152.130:2181" /> <constructor-arg name="sessionTimeout" value="1000" /> <constructor-arg name="zkPath" value="/Jdbc" /> </bean> <!--這個類等價於以前在xml文件里面的配置: <context:property-placeholder location="classpath:config/jdbc_conf.properties"/> 加載 props里面的數據庫配置 --> <bean id="zkPlaceholderConfigurer" class="com.study.demo.config.ZookeeperPlaceholderConfigurer"> <property name="zkConfigurerCentral" ref="zkConfigurerCentral" /> <property name="ignoreUnresolvablePlaceholders" value="true" /> <property name="order" value="1" /> </bean> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource"> <ref bean="dataSource" /> </property> </bean> <bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource" destroy-method="shutdown"> <property name="driverClassName" value="${driver}" /> <property name="jdbcUrl" value="${url}" /> <property name="username" value="${uname}" /> <property name="password" value="${password}" /> <!-- 連接只讀數據庫時配置為true, 保證安全 --> <property name="readOnly" value="false" /> <!-- 等待連接池分配連接的最大時長(毫秒),超過這個時長還沒可用的連接則發生SQLException, 缺省:30秒 --> <property name="connectionTimeout" value="30000" /> <!-- 一個連接idle狀態的最大時長(毫秒),超時則被釋放(retired),缺省:10分鍾 --> <property name="idleTimeout" value="600000" /> <!-- 一個連接的生命時長(毫秒),超時而且沒被使用則被釋放(retired),缺省:30分鍾,建議設置比數據庫超時時長少30秒,參考MySQL wait_timeout參數(show variables like '%timeout%';) --> <property name="maxLifetime" value="1800000" /> <!-- 連接池中允許的最大連接數。缺省值:10;推薦的公式:((core_count * 2) + effective_spindle_count) --> <property name="maximumPoolSize" value="15" /> </bean> </beans>
2.5 在com.study.demo.controller新建測試類
測試類1:

package com.study.demo.controller; import java.io.Serializable; public class OrderModel implements Serializable { private static final long serialVersionUID = 1L; private int orderId; private int brandId; public int getOrderId() { return orderId; } public void setOrderId(int orderId) { this.orderId = orderId; } public int getBrandId() { return brandId; } public void setBrandId(int brandId) { this.brandId = brandId; } }
測試類2:

package com.study.demo.controller; import java.sql.ResultSet; import java.sql.SQLException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; import org.springframework.stereotype.Repository; @Repository public class OrderDao { @Autowired private JdbcTemplate jdbcTemplate; public OrderModel findById() { String sql = "select * from tbl_order where order_id = 1"; return jdbcTemplate.queryForObject(sql, new RowMapper<OrderModel>() { public OrderModel mapRow(ResultSet rs, int rowNum) throws SQLException { OrderModel payment = new OrderModel(); payment.setOrderId(rs.getInt("order_id")); payment.setBrandId(rs.getInt("brand_id")); return payment; } }); } }
測試類3:

package com.study.demo.controller; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service public class OrderService { @Autowired private OrderDao dao; public OrderModel getById() { return dao.findById(); } }
測試類4:

package com.study.demo.controller; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.ResponseBody; @Controller public class OrderController { @Autowired private OrderService service; @ResponseBody @RequestMapping(value = "/test", method = RequestMethod.GET) public String test() { OrderModel p = service.getById(); return p.getBrandId() + ""; } }
2.6 其他附加配置和數據庫腳本
/zkdemo/src/main/webapp/WEB-INF/config/log4j.properties

log4j.rootLogger=INFO,console
log4j.logger.org.apache.zookeeper=DEBUG
log4j.logger.org.apache.curator=DEBUG
log4j.logger.java.lang.Exception=INFO
log4j.appender.console=org.apache.log4j.ConsoleAppender
log4j.appender.console.layout=org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=%d{MM-dd HH:mm:ss.SSS} [%c:%p] %m%n
/zkdemo/src/main/webapp/WEB-INF/config/spring-mvc.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:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd 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/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <mvc:default-servlet-handler /> <mvc:annotation-driven content-negotiation-manager="contentNegotiationManager" /> <context:component-scan base-package="com.study.demo"> <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller" /> <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Service" /> </context:component-scan> <bean id="stringHttpMessageConverter" class="org.springframework.http.converter.StringHttpMessageConverter"> <property name="supportedMediaTypes"> <list> <bean class="org.springframework.http.MediaType"> <constructor-arg index="0" value="text" /> <constructor-arg index="1" value="plain" /> <constructor-arg index="2" value="UTF-8" /> </bean> </list> </property> </bean> <bean id="mappingJacksonHttpMessageConverter" class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter" /> <bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter"> <property name="messageConverters"> <list> <ref bean="stringHttpMessageConverter" /> <ref bean="mappingJacksonHttpMessageConverter" /> </list> </property> </bean> <bean id="contentNegotiationManager" class="org.springframework.web.accept.ContentNegotiationManagerFactoryBean"> <property name="mediaTypes"> <map> <entry key="html" value="text/html" /> <entry key="pdf" value="application/pdf" /> <entry key="xsl" value="application/vnd.ms-excel" /> <entry key="xml" value="application/xml" /> <entry key="json" value="application/json" /> </map> </property> <property name="defaultContentType" value="text/html" /> </bean> <bean id="viewResolver" class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver"> <property name="order" value="0" /> <property name="contentNegotiationManager" ref="contentNegotiationManager" /> <property name="viewResolvers"> <list> <bean class="org.springframework.web.servlet.view.BeanNameViewResolver" /> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="viewClass" value="org.springframework.web.servlet.view.JstlView" /> <property name="prefix" value="/WEB-INF/pages/" /> <property name="suffix" value=".jsp"></property> </bean> </list> </property> <property name="defaultViews"> <list> <bean class="org.springframework.web.servlet.view.json.MappingJackson2JsonView"> <property name="extractValueFromSingleKeyModel" value="true" /> </bean> </list> </property> </bean> </beans>
/zkdemo/src/main/webapp/WEB-INF/web.xml

<?xml version="1.0" encoding="UTF-8"?> <web-app id="WebApp_ID" version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"> <display-name>zkdemo</display-name> <description>Zookeeper Demo Application</description> <!--============================================================== --> <!-- Context parameters definition --> <!--============================================================== --> <context-param> <param-name>webAppRootKey</param-name> <param-value>zkdemo.root</param-value> </context-param> <context-param> <param-name>log4jConfigLocation</param-name> <param-value>/WEB-INF/config/log4j.properties</param-value> </context-param> <context-param> <param-name>log4jRefreshInterval</param-name> <param-value>60000</param-value> </context-param> <context-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/config/applicationContext.xml</param-value> </context-param> <servlet> <servlet-name>spring</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/config/spring-mvc.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>spring</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> <!--============================================================== --> <!-- Listener definition --> <!--============================================================== --> <listener> <listener-class>org.springframework.web.util.Log4jConfigListener</listener-class> </listener> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <!--============================================================== --> <!-- Filter definition --> <!--============================================================== --> <filter> <filter-name>characterEncodingFilter</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> <init-param> <param-name>forceEncoding</param-name> <param-value>true</param-value> </init-param> </filter> <filter-mapping> <filter-name>characterEncodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <!--============================================================== --> <!-- Web Session definition --> <!--============================================================== --> <session-config> <session-timeout>20</session-timeout> </session-config> <!--============================================================== --> <!-- Redirect page definition --> <!--============================================================== --> <error-page> <error-code>403</error-code> <location>/403.jsp</location> </error-page> <error-page> <error-code>404</error-code> <location>/404.jsp</location> </error-page> <error-page> <error-code>500</error-code> <location>/500.jsp</location> </error-page> <!--============================================================== --> <!-- First page definition --> <!--============================================================== --> <welcome-file-list> <welcome-file>index.htm</welcome-file> <welcome-file>index.jsp</welcome-file> </welcome-file-list> </web-app>
數據庫腳本:
CREATE TABLE `tbl_order` ( `order_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '訂單id', `brand_id` int(11) DEFAULT NULL COMMENT '品牌id', PRIMARY KEY (`order_id`) ) ENGINE=MyISAM AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COMMENT='訂單表';INSERT INTO tbl_order VALUES('1','1')
2.7 啟動項目在瀏覽器輸入地址http://localhost:8080/zkdemo/test查看效果
可以看到報錯了,這是因為我們之前設置了錯誤的url
create /Jdbc/url jdbc.mysql://192.168.152.1/dbspread
修改url為正確的
set /Jdbc/url jdbc:mysql://192.168.152.1:3306/dbspread
再次輸入地址訪問查看效果:
http://localhost:8080/zkdemo/test
可以看到在沒有重啟服務的情況下,可以正常訪問獲取到值了,這是因為zookeeper的數據庫的配置動態刷新到服務了!