搜索词>>watch 耗时0.0030
  • vue watch监控对象属性变化

    vue watch监控对象属性变化watch:{ 'object.attr':function attr(value){ //处理操作 } },或者下面写法watch:{ 'objvue watch监控对象属性变化watch:{ 'object.attr':function attr(value){ //处理操作 } },或者下面写法watch:{ 'object.attr'(value){ //处理操作 } },
  • Vue2.x父子组件相互通信

    一、前言Vue 2.x 使用期间,我们会创建众多组件,这里我们将讨论一下各个组件直接的相互通讯问题如何解决一、前言Vue 2.x 使用期间,我们会创建众多组件,这里我们将讨论一下各个组件直接的相互通讯问题如何解决。二、Vue 2.x组件相互通讯原理在 Vue.js 中,父子组件的关系可以总结为 props down, events up 。父组件通过 props 向下传递数据给子组件,子组件通过 events 给父组件发送消息。看看它们是怎么工作的。​Vue 2.x组件相互通讯原理三、子组件 这里的子组件是模拟的一个搜索组件,包含返回、搜索等事件。只用关注组件的值传递即可。$title(child.vue) <template> <!--搜索条--> <div class="search-back-section searchHead flex-between animated" :class="activeClass"> <mu-button class="back" flat color="#444444" @click="back"><i class="fa fa-angle-left" aria-hidden="true"></i></mu-button> <mu-text-field :value="searchKeyword" v-model="text" :placeholder="'请输入:'+placeholder" :error-text="error" ></mu-text-field> <mu-button small class="btn" flat color="#444444" @click="search">搜索</mu-button> </div> </template> <script> import verify from "../assets/js/verify"; export default { name: "com-search-back-section", props: [ "back", //"返回按钮" "searchKeyword", //搜索关键字 "placeholder", //输入框提示 "error", //错误提示 "activeClass", //绑定CSS样式 ], data() { return { text: "", } }, watch:{ searchKeyword(val){ this.text=val; } }, created() { this.text = this.searchKeyword; }, methods: { search() { let msg = ""; if (verify.isEmpty(this.text)) { msg = this.text; } else { msg = false; } this.$emit("search", msg); } }, } </script> <style scoped> //样式代码与本文主题关系不大,省略 </style> 划重点props: [ "back", //"返回按钮" "searchKeyword", //搜索关键字----重点 "placeholder", //输入框提示 "error", //错误提示 "activeClass", //绑定CSS样式 ] watch:{ searchKeyword(val){ this.text=val; //---重点 } } 通过watch监视props中的属性值以及data中属性与数据的双向绑定,实现了外部数据的双向绑定。注意:父组件中定义子组件绑定属性的名称对应的是子组件props里面定义的名称一致search() { let msg = ""; if (verify.isEmpty(this.text)) { msg = this.text; } else { msg = false; } this.$emit("search", msg); //---重点 }通过$emit向外面抛出一个回调函数传递当前组件的值数据。四、父组件$title(patent.vue) <template> 其他省略--- <!--搜索框--> <search-back-section :active-class="isFixedState?'isFixed fadeInDown':''" :back="closeFullscreenDialog" :searchKeyword="searchKeyword" //-----重点 :placeholder="'关键字'" @search="searchBtn" :error="errorText" ></search-back-section> 其他省略--- </template> <script> let _self=null; export default { name: "door-home", data() { return { errorText: "", //错误信息 searchKeyword: "", //关键字 isFixedState:false //用于判断css样式选择,与主题无关 }, created() { _self=this; this.searchKeyword='默认的值'; //---重点 会看到初始化的子组件中输入框默认值为‘默认的值’(实现了父向子传递数据) }, methods: { /* * @"搜索"按钮 * */ searchBtn(msg) {------重点 通过子向外抛出的回调函数,子组件向父组件传递子组件中输入的内容值(实现了子向父传递数据) if (msg) { this.searchKeyword = msg; this.errorText = ""; } else { this.errorText = "请输入关键字."; return false; } } closeFullscreenDialog(){ //关闭搜索操作,与主题无关代码省略 } }, }; </script>注意:父组件中定义子组件绑定属性的名称对应的是子组件props里面定义的名称一致,传值是通过props传递所以必须一致。例子中的 :searchKeyword="searchKeyword"前面的searchKeyword是子组件的props定义的,后面这个是当前父组件定义的,后方这个searchKeyword可以随意其他名称
  • Vue.js底部导航三种写法

    普通写法​vantUI 使用字体图标和文字<template>l; <van-tabbar v-model="active" active-color="#07c160">l; <van-tabbar-item :to="{普通写法​vantUI 使用字体图标和文字<template> <van-tabbar v-model="active" active-color="#07c160"> <van-tabbar-item :to="{name:'home'}"> <span>首页</span> <img slot="icon" slot-scope="props" :src="props.active ? icon.homeActive : icon.homeNormal" > </van-tabbar-item> <van-tabbar-item :to="{name:'money'}"> <span>钱包</span> <img slot="icon" slot-scope="props" :src="props.active ? icon.moneyActive : icon.moneyNormal" > </van-tabbar-item> <van-tabbar-item :to="{name:'user'}"> <span>个人中心</span> <img slot="icon" slot-scope="props" :src="props.active ? icon.userActive : icon.userNormal" > </van-tabbar-item> </van-tabbar> </template> <script> export default { data() { return { active: 0, icon:{ homeNormal: require('./home.png'), homeActive: require('./homeActive.png'), moneyNormal: require('./money.png'), moneyActive: require('./moneyActive.png'), userNormal: require('./user.png'), userActive: require('./userActive.png'), }, } } } </script>手写 适用于背景图等复杂样式<template> <div class="footer"> <div class="foot"> <router-link v-for="(item,i) in footnavs" :key="i" :to="item.to" :class="{'selected':($route.meta.nav_index==item.nav_index)}" > <img :src="$route.meta.nav_index==item.nav_index?item.selected:item.no_selected" alt> <span v-show="!($route.meta.nav_index==item.nav_index)">{{item.name}}</span> </router-link> </div> </div> </template> <script> export default { data() { return { footnavs: [ { to: "/", name: "首页", no_selected: require("@img/home/shouye@2x.png"), selected: require("@img/home/shouye2@2x.png"), nav_index: 1 }, { to: "/money", name: "钱包", no_selected: require("@img/home/qianbao@2x.png"), selected: require("@img/home/qianbao2@2x.png"), nav_index: 2 }, { to: "/my", name: "个人中心", no_selected: require("@img/home/gerenhzongxin@2x.png"), selected: require("@img/home/gerenzhongxin@2x.png"), nav_index: 3 } ] }; } }; </script> <style lang="less" scoped> .footer { width: 100%; height: 98px; margin-top: 12px; .foot { width: 100%; position: fixed; left: 0; right: 0; bottom: 0; display: flex; justify-content: space-between; padding: 10px 20px; background: rgba(255, 255, 255, 1); box-shadow: 0px 1px 16px 0px rgba(192, 192, 192, 0.4); a { width: 33%; text-align: center; display: flex; flex-direction: column; justify-content: space-around; align-items: center; img { width: 47px; height: 47px; } span { font-size: 28px; color: rgba(102, 102, 102, 1); } &.selected { img { width: 85px; height: 85px; } } } } } </style>路由文件import Vue from 'vue' import Router from 'vue-router' import home from '@/pages/home/home' import noticeList from '@/pages/home/noticeList' import money from '@/pages/money/money' import my from '@/pages/my/my' Vue.use(Router) export default new Router({ routes: [{ path: '/', name: 'home', component: home, meta: { index: 0, hasFooter: true, nav_index: 1 }, }, { path: '/money', name: 'money', component: money, meta: { index: 0, hasFooter: true, nav_index: 2 }, }, { path: '/my', name: 'my', component: my, meta: { index: 0, hasFooter: true, nav_index: 3 }, }, { path: '/noticeList', name: 'noticeList', component: noticeList, meta: { index: 1, hasBar: true, }, }, ], //该方法用于控制滚动行为, scrollBehavior(to, form, savedPosition) { if (savedPosition) { return savedPosition } else { return { x: 0, y: 0 } } } })app.vue文件<template> <div id="app"> <tabBar v-show="$route.meta.hasBar"/> <transition :name="transitionName"> <router-view/> </transition> <Footer v-show="$route.meta.hasFooter"/> </div> </template> <script> import "./common/css/common.css"; // bug:不能为小写footer会报错 import Footer from "@/components/footer"; import tabBar from "@/components/tabBar"; export default { name: "App", data() { return { transitionName: "" }; }, components: { Footer, tabBar }, mounted() {}, watch: { /* * 使用watch 监听$router的变化 * 如果to索引大于from索引,判断为前进状态,反之则为后退状态 * 设置动画名称 */ $route(to, from) { if (to.meta.index == from.meta.index) { this.transitionName = ""; } else if (to.meta.index > from.meta.index) { this.transitionName = "slide-left"; } else { this.transitionName = "slide-right"; } } } }; </script>​​​​​​​
  • zookeeper入门篇安装zookeeper以及配置zookeeper集群

    现在zookeeper在分布式上的流行程度不亚于spring在Java中的地位。本文主要讲解zookeeper的入门,即:安装zookeeper以及配置zookeeper集群<h2>一.为啥要编写这篇入门博客?</h2> <p>    在百度上基本能百度一大片的安装教程,为啥我还要写呢?不难发现很多博客讲得很好。但是有些细节地方还是欠缺说明。所以,这里参考了一些大牛的博客,整理的一篇zookeeper安装配置集群的博客。</p> <h2>二.什么是zookeeper?</h2> <ol> <li> <p>我们可以把Zookeeper理解为一个精简的文件系统(和Linux文件系统结构非常相似),其每一个节点称为znode,znode下可以存放子节点,也可以直接对节点进行赋值存值。</p> </li> <li> <p>Zookeeper被应用与一些集群上,提高集群的高可用。它可以帮助你避免单点故障,使你的系统更加可靠。</p> </li> <li> <p>Zookeeper的集群我们可以通俗的理解为,一个有Leader的团队,团队中各个成员的数据都是一致的。团队中的Leader采用选举算法推举,所以可以保证在Leader出现问题的时候,又会选举出新的Leader。(fast paxos 选举算法大家可以深入了解下)</p> </li> <li> <p>Zookeeper使用路径来描述节点,节点可以被看做是一个目录,也可以被看做是一个文件,它同时具有两者的特点。</p> </li> <li> <p>Zookeeper的Watch机制也是它的最大被应用的原因。当我们有很多客户端连接到Zookeeper时,当被设置了Watch的数据发生了改变的时候,则服务器将这个改变发送给设置了Watch的客户端,通知它们。所以我们经常用它来做业务系统的统一配置管理。<strong>使用zk的Watch要特别注意一点就是它的“一次性触发器”(最后的Java例子中有模拟这点)</strong>。</p> </li> </ol> <h2>三.安装准备</h2> <h3>1.下载zookeeper的安装包,其实是个压缩包</h3> 官网:<a href="http://zookeeper.apache.org/releases.html" rel="external nofollow" target="_blank">http://zookeeper.apache.org/releases.html</a> <br /> 下载:<a href="http://mirrors.tuna.tsinghua.edu.cn/apache/zookeeper/zookeeper-3.4.8/" rel="external nofollow" target="_blank">http://mirrors.tuna.tsinghua.edu.cn/apache/zookeeper/zookeeper-3.4.8/</a><br /> 版本:zookeeper-3.4.8.tar.gz(可根据自己需求下载相应的版本。这里只是一个使用案例,现在已经有3.4.10了以及3.5.x的beta版) <h3>2.JAVA运行环境安装</h3> linux上安装jre可以参照之前的一篇文章,<a rel="" href="http://www.leftso.com/blog/39.html" rel="" target="_blank">linux(centos)系统配置安装jdk</a>。如果已经安装则忽略该步骤。 <h3>3.将zookeeper文件包上传至服务器</h3> <h2>四.创建配置文件以及相关文件夹</h2> <h3>4.1创建目录/zookeeper/</h3> <pre> <code class="language-html">#mkdir /zookeeper/</code></pre> <h3>4.2解压zookeeper-3.4.8.tar.gz</h3> <pre> <code class="language-html">#tar -zxvf zookeeper-3.4.8.tar.gz</code></pre> <h3>4.3将解压后的zookeeper文件移动至/zookeeper目录下并重明明server1</h3> <pre> <code class="language-html">#mv zookeeper-3.4.8 /zookeeper/server1</code></pre> <h3>4.5切换至server1的conf目录创建zoo.cfg配置文件</h3> <pre> <code class="language-html">#cd /zookeeper/server1/conf/ #vi zoo.cfg</code></pre> <h3>4.6zoo.cfg内容</h3> <pre> <code class="language-html">tickTime=2000 initLimit=5 syncLimit=2 dataDir=/zookeeper/server1/data dataLogDir=/zookeeper/server1/log clientPort=2181 server.1=127.0.0.1:2888:3888 server.2=127.0.0.1:2889:3889 server.3=127.0.0.1:2890:3890</code></pre> <p><strong>说明:这里我要在同一台机器上配置3台server所以这配置了三条server信息。注意保证配置的端口未被占用。</strong></p> <h3><br /> 4.7zoo.cfg文件中参数说明</h3> <ul> <li><strong>tickTime</strong>:zookeeper中使用的基本时间单位, 毫秒值。 </li> <li><strong>initLimit</strong>:这个配置项是用来配置 Zookeeper 接受客户端(这里所说的客户端不是用户连接 Zookeeper 服务器的客户端,而是 Zookeeper 服务器集群中连接到 Leader 的 Follower 服务器)初始化连接时最长能忍受多少个 tickTime 时间间隔数。这里设置为5表名最长容忍时间为 5 * 2000 = 10 秒。 </li> <li><strong>syncLimit</strong>:这个配置标识 Leader 与 Follower 之间发送消息,请求和应答时间长度,最长不能超过多少个 tickTime 的时间长度,总的时间长度就是 2 * 2000 = 4 秒。 </li> <li><strong>dataDir</strong> 和 <strong>dataLogDir</strong> 看配置就知道干吗的了,不用解释。 </li> <li><strong>clientPort</strong>:监听client连接的端口号,这里说的client就是连接到Zookeeper的代码程序。 </li> <li><strong>server</strong>.{myid}={ip}:{leader服务器交换信息的端口}:{当leader服务器挂了后, 选举leader的端口} </li> <li><strong>maxClientCnxns</strong>:对于一个客户端的连接数限制,默认是60,这在大部分时候是足够了。但是在我们实际使用中发现,在测试环境经常超过这个数,经过调查发现有的团队将几十个应用全部部署到一台机器上,以方便测试,于是这个数字就超过了。</li> </ul> <h3>4.8.创建data和log目录</h3> <pre> <code class="language-html">#mkdir -p /zookeeper/server1/data #mkdir -p /zookeeper/server1/log</code></pre> <h3>4.9创建data目录中的myid文件(必须)</h3> <pre> <code class="language-html">#cd /zookeeper/server1/data #echo 1>myid</code></pre> 好了到这里第一台的配置基本完成了。 <h2>4.10复制多台server</h2> <pre> <code class="language-html">#cp -r /zookeeper/server1 /zookeeper/server2 #cp -r /zookeeper/server1 /zookeeper/server3</code></pre> <p>注意修改2和3的配置文件zoo.cfg,修改的地方是clientPort=2181分别修改成,修改的地方是clientPort=2182,,修改的地方是clientPort=2183<br /> 配置清单:<br /> server1-->zoo.cfg:</p> <pre> <code class="language-html">tickTime=2000 initLimit=5 syncLimit=2 dataDir=/zookeeper/server1/data dataLogDir=/zookeeper/server1/log clientPort=2181 server.1=127.0.0.1:2888:3888 server.2=127.0.0.1:2889:3889 server.3=127.0.0.1:2890:3890</code></pre> server2-->zoo.cfg: <pre> <code class="language-html">tickTime=2000 initLimit=5 syncLimit=2 dataDir=/zookeeper/server2/data dataLogDir=/zookeeper/server2/log clientPort=2182 server.1=127.0.0.1:2888:3888 server.2=127.0.0.1:2889:3889 server.3=127.0.0.1:2890:3890</code></pre> server3-->zoo.cfg: <pre> <code class="language-html">tickTime=2000 initLimit=5 syncLimit=2 dataDir=/zookeeper/server3/data dataLogDir=/zookeeper/server3/log clientPort=2183 server.1=127.0.0.1:2888:3888 server.2=127.0.0.1:2889:3889 server.3=127.0.0.1:2890:3890</code></pre> <br /> server1 ->./data/myid <pre> <code class="language-html">1</code></pre> server2 ->./data/myid <pre> <code class="language-html">2</code></pre> server3 ->./data/myid <pre> <code class="language-html">3</code></pre> <br /> <strong>注意:myid一定要修改</strong> <h2>五.启动并测试</h2> <h3>5.1启动第一台zookeeper服务</h3> <pre> <code>#cd /zookeeper/server1/bin #./zkServer.sh start</code></pre> 查看日志内容 <pre> <code>vi /zookeeper/server1/bin/zookeeper.out</code></pre> 可以看到类似以下错误信息 <pre> <code class="language-html">java.net.ConnectException: Connection refused (Connection refused) at java.net.PlainSocketImpl.socketConnect(Native Method) at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:350) at java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:206) at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:188) at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:392) at java.net.Socket.connect(Socket.java:589) at org.apache.zookeeper.server.quorum.QuorumCnxManager.connectOne(QuorumCnxManager.java:381) at org.apache.zookeeper.server.quorum.QuorumCnxManager.toSend(QuorumCnxManager.java:354) at org.apache.zookeeper.server.quorum.FastLeaderElection$Messenger$WorkerSender.process(FastLeaderElection.java:452) at org.apache.zookeeper.server.quorum.FastLeaderElection$Messenger$WorkerSender.run(FastLeaderElection.java:433) at java.lang.Thread.run(Thread.java:748) 2017-08-06 15:42:40,542 [myid:1] - INFO [WorkerSender[myid=1]:QuorumPeer$QuorumServer@149] - Resolved hostname: 127.0.0.1 to address: /127.0.0.1 2017-08-06 15:42:40,542 [myid:1] - WARN [WorkerSender[myid=1]:QuorumCnxManager@400] - Cannot open channel to 3 at election address /127.0.0.1:3890 </code></pre> 这是因为启动的时候zookeeper去找其他两台服务了,其他两台服务还没启动所以链接失败。 <h3>5.2接下来启动第二台zookeeper服务并观察日志</h3> <pre> <code class="language-html">#cd /zookeeper/server2/bin #./zkServer.sh start</code></pre> 可以看到错误信息比第一台启动的时候要少 <h3>5.3启动第三台zookeeper服务</h3> <pre> <code class="language-html">#cd /zookeeper/server3/bin #./zkServer.sh start</code></pre> 观察第三台的启动日志,已经没有错误。说明已经正常集群了。接下来将进行简单的测试<br /> <br /> <strong>提示:<span style="color:#ff0000">启动命令为./zkServer.sh start,默认读取的conf目录下的zoo.cfg如果需要自己指定配置文件路径则为./zkServer.sh start pathtoyourcfgFile</span></strong> <h3>5.4测试</h3> 随便进入一个zk目录,连接一个server测试。 <br />   <pre> <code class="language-html">#cd /zookeeper/server1/bin #.zkCli.sh -server 127.0.0.1:2181</code></pre> 如果你要连接别的服务器,请指定具体的IP地址。<br /> <br /> <strong>几个zookeeper基本命令说明:</strong>  <ul> <li>ls 查看指定节点中包含的子节点(如:ls / 或 ls /app1/server1) </li> <li>create 创建节点并赋值 </li> <li>get 读取节点内容 </li> <li>set 改变节点内容 </li> <li>delete 删除节点 </li> <li>注意zk中所有节点都基于路径确定,如你要删除 /app1/server1/nodeA 的命令为: </li> <li>delete /app1/server1/nodeA</li> </ul> <br /> 下面是几个基本命令操作使用: <pre> <code class="language-html">WatchedEvent state:SyncConnected type:None path:null [zk: 127.0.0.1:2181(CONNECTED) 0] ls / [zookeeper] [zk: 127.0.0.1:2181(CONNECTED) 1] create /zk "leftso.com" Created /zk [zk: 127.0.0.1:2181(CONNECTED) 2] get /zk leftso.com cZxid = 0x300000003 ctime = Sun Aug 06 15:55:17 PDT 2017 mZxid = 0x300000003 mtime = Sun Aug 06 15:55:17 PDT 2017 pZxid = 0x300000003 cversion = 0 dataVersion = 0 aclVersion = 0 ephemeralOwner = 0x0 dataLength = 10 numChildren = 0 [zk: 127.0.0.1:2181(CONNECTED) 3] set /zk "www.leftso.com" cZxid = 0x300000003 ctime = Sun Aug 06 15:55:17 PDT 2017 mZxid = 0x300000004 mtime = Sun Aug 06 15:55:36 PDT 2017 pZxid = 0x300000003 cversion = 0 dataVersion = 1 aclVersion = 0 ephemeralOwner = 0x0 dataLength = 14 numChildren = 0 [zk: 127.0.0.1:2181(CONNECTED) 4] get /zk www.leftso.com cZxid = 0x300000003 ctime = Sun Aug 06 15:55:17 PDT 2017 mZxid = 0x300000004 mtime = Sun Aug 06 15:55:36 PDT 2017 pZxid = 0x300000003 cversion = 0 dataVersion = 1 aclVersion = 0 ephemeralOwner = 0x0 dataLength = 14 numChildren = 0 [zk: 127.0.0.1:2181(CONNECTED) 5] ls / [zk, zookeeper] [zk: 127.0.0.1:2181(CONNECTED) 6] delete /zk [zk: 127.0.0.1:2181(CONNECTED) 7] ls / [zookeeper] [zk: 127.0.0.1:2181(CONNECTED) 8] </code></pre> <br /> <br /> 到这里zookeeper的安装配置基本讲完,后面讲讲解Java中如何调用zookeeper<br /> <br />  
  • Spring Boot Redis 秒杀实现

    简述本文主要通过一个简单的例子模拟实现秒杀情景,其中主要使用Redis事物进行实现spring boot为提供方便的环境简述本文主要通过一个简单的例子模拟实现秒杀情景,其中主要使用Redis事物进行实现spring boot为提供方便的环境。首先导入redis依赖pom.xml文件<?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> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.6.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.leftso.demo</groupId> <artifactId>demo-redis-seckill</artifactId> <version>0.0.1-SNAPSHOT</version> <name>demo-redis-seckill</name> <description>Redis 实现产品秒杀</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <!-- https://mvnrepository.com/artifact/redis.clients/jedis --> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>3.0.1</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>Redispool配置配置redis的连接池,这个根据自己需求改。这里测试用。package com.leftso.demo.demoredisseckill; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import redis.clients.jedis.JedisPool; import redis.clients.jedis.JedisPoolConfig; @Configuration public class JedisConfig { @Bean public JedisPool jedisPool(){ JedisPoolConfig jedisPoolConfig = new JedisPoolConfig(); // 设置配置 jedisPoolConfig.setMaxTotal(1024); jedisPoolConfig.setMaxIdle(100); jedisPoolConfig.setMaxWaitMillis(100); jedisPoolConfig.setTestOnBorrow(false);//jedis 第一次启动时,会报错 jedisPoolConfig.setTestOnReturn(true); JedisPool pool=new JedisPool(jedisPoolConfig,"127.0.0.1",6379); return pool; } } 写一个秒杀的业务处理实现package com.leftso.demo.demoredisseckill.service; import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPool; import redis.clients.jedis.Transaction; import java.util.List; public class Seckill implements Runnable { private JedisPool jedisPool; private String userName; private String productKey; public Seckill(JedisPool jedisPool, String userName, String productKey) { this.jedisPool = jedisPool; this.userName = userName; this.productKey = productKey; } @Override public void run() { Jedis jedis=jedisPool.getResource(); try { jedis.watch(productKey); String val=jedis.get(productKey); int valInt=Integer.valueOf(val); if (valInt>=1){ Transaction tx=jedis.multi(); tx.incrBy(productKey,-1);//原子操作 List<Object> list=tx.exec(); if (list==null||list.isEmpty()){ System.out.println("用户:"+userName+" 抢购失败。"); this.run();//再抢 }else{ System.out.println("用户:"+userName+ " 抢购成功!!!"); // jedis.setnx(productKey,) jedis.rpush(productKey+"user",userName);//成功用户添加入队列 } }else{ System.out.println("商品已抢购完毕-------"); } }catch (Exception e){ e.printStackTrace(); } } } 最后模拟访问package com.leftso.demo.demoredisseckill; import com.leftso.demo.demoredisseckill.service.Seckill; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import redis.clients.jedis.JedisPool; import java.util.ArrayList; import java.util.List; @RunWith(SpringRunner.class) @SpringBootTest public class RedisSeckillTest { private final static Logger logger = LoggerFactory.getLogger(RedisSeckillTest.class); @Autowired JedisPool jedisPool; String productKey="SSSSSSKEY"; int productNum=10; @Before public void before(){ jedisPool.getResource().set(productKey,10+"");//设置产品默认库存数量 while (jedisPool.getResource().lpop(productKey+"user")!=null){ }//清空秒杀成功人用户列表 //end } @After public void after(){ String num=jedisPool.getResource().get(productKey); System.out.println("剩余库存:"+num); } @Test public void contextLoads() { try { for (int i = 0; i < 100; i++) { //每个用户件数 Thread t = new Thread(new Seckill(jedisPool,"用户"+i,productKey)); t.start(); } long size=jedisPool.getResource().llen(productKey+"user"); while (true){ if (size==productNum){ break; }else{ size=jedisPool.getResource().llen(productKey+"user"); } } List<String> successUsers=new ArrayList<>(); String user=jedisPool.getResource().lpop(productKey+"user"); while (user!=null){ successUsers.add(user); user=jedisPool.getResource().lpop(productKey+"user"); } System.out.println("活动结束>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>活动结束"); System.out.println("获奖名单:"+successUsers); Thread.currentThread().sleep(2000); } catch (Exception e) { e.printStackTrace(); } } } 执行单元测试,查看结果:.............................. 商品已抢购完毕------- 商品已抢购完毕------- 商品已抢购完毕------- 商品已抢购完毕------- 商品已抢购完毕------- 商品已抢购完毕------- 商品已抢购完毕------- 商品已抢购完毕------- 商品已抢购完毕------- 商品已抢购完毕------- 商品已抢购完毕------- 商品已抢购完毕------- 商品已抢购完毕------- 商品已抢购完毕------- 商品已抢购完毕------- 商品已抢购完毕------- 活动结束>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>活动结束 获奖名单:[用户66, 用户2, 用户8, 用户10, 用户56, 用户78, 用户33, 用户58, 用户16, 用户87] 剩余库存:0 Process finished with exit code 0您有任何想法欢迎评论区留言讨论,谢谢
  • Redis 秒杀整合Spring Boot 2.x实现例子

    前言继续上一篇Spring Boot Redis 秒杀实现 的一个修改版本,主要实现用ab工具进行网页正式访问的一个版本,其主要目的还是介绍Redis实现秒杀活动的一种方式前言继续上一篇Spring Boot Redis 秒杀实现 的一个修改版本,主要实现用ab工具进行网页正式访问的一个版本,其主要目的还是介绍Redis实现秒杀活动的一种方式。Redis 秒杀活动项目结构图​与上一篇文章中的区别在于多了一个GoodService服务。该服务主要提供秒杀业务。Redis秒杀活动业务层$title(GoodsService.java) package com.leftso.demo.demoredisseckill.service; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPool; import redis.clients.jedis.Transaction; import javax.annotation.PostConstruct; import java.util.ArrayList; import java.util.List; /** * 商品服务 */ @Service public class GoodsService { @Autowired JedisPool jedisPool; public static String productKey="GOODS_001";//某产品的ID int goodsStock=10;//某产品用于秒杀的库存 String successKey="Success_User_List";//成功秒杀用户的集合 /** * 初始化一些默认数据(正常情况这些数据来源于数据库) */ @PostConstruct public void init(){ Jedis jedis=jedisPool.getResource(); jedis.set(productKey,String.valueOf(goodsStock));//设置产品默认库存数量 while (jedis.lpop(successKey)!=null){ }//清空秒杀成功人用户列表 //end new Thread(()->{ long size=jedis.llen(successKey); while (true){ if (size==goodsStock){ break; }else{ size=jedis.llen(successKey); } } List<String> successUsers=new ArrayList<>(); String user=jedis.lpop(successKey); while (user!=null){ successUsers.add(user); user=jedis.lpop(successKey); } System.out.println("活动结束>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>活动结束"); System.out.println("抢购名单:"+successUsers); //可以在名单拿到后生成订单等其他业务操作。 String num=jedis.get(productKey); System.out.println("剩余库存:"+num); }).start(); } /** * 获取库存 * @param productKey * @return */ public int getStock(String productKey){ try (Jedis jedis=jedisPool.getResource();){ String val=jedis.get(productKey); if (val!=null){ return Integer.valueOf(val); } return -1; } } /** * 秒杀商品 * * @param productKey * @return */ public boolean seckill(String productKey, String userName) { if (getStock(productKey)<=0){ System.out.println("商品已抢购完毕>>>>>>>>>>>>>>>>>>>>>>>>>>欢迎下次再来。"); return false; } try(Jedis jedis = jedisPool.getResource();) { jedis.watch(productKey); String val = jedis.get(productKey); int valInt = Integer.valueOf(val); if (valInt >= 1) { Transaction tx = jedis.multi(); tx.incrBy(productKey, -1);//原子操作 List<Object> list = tx.exec(); if (list == null || list.isEmpty()) { //System.out.println("用户:" + userName + " 抢购失败。"); this.seckill(productKey, userName);//再抢 } else { System.out.println("用户:" + userName + " 抢购成功!!!"); // jedis.setnx(productKey,) jedis.rpush(successKey, userName);//成功用户添加入队列 //处理成功后的业务逻辑 return true; } } else { System.out.println("商品已抢购完毕>>>>>>>>>>>>>>>>>>>>>>>>>>欢迎下次再来。"); return false; } return false; } catch (Exception e) { e.printStackTrace(); return false; } } } 主要模拟环境与之前的文章一样。作为秒杀模拟场景。其他相关文件清单:pom.xml依赖$title(pom.xml) <?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> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.6.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.leftso.demo</groupId> <artifactId>demo-redis-seckill</artifactId> <version>0.0.1-SNAPSHOT</version> <name>demo-redis-seckill</name> <description>Redis 实现产品秒杀</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- https://mvnrepository.com/artifact/redis.clients/jedis --> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>3.0.1</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project> redis配置$title(JedisConfig.java) package com.leftso.demo.demoredisseckill; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import redis.clients.jedis.HostAndPort; import redis.clients.jedis.JedisCluster; import redis.clients.jedis.JedisPool; import redis.clients.jedis.JedisPoolConfig; import java.util.HashSet; import java.util.Set; @Configuration public class JedisConfig { /*** * 单机连接池 * @return */ @Bean public JedisPool jedisPool() { JedisPoolConfig jedisPoolConfig = new JedisPoolConfig(); // 设置配置 jedisPoolConfig.setMaxTotal(2048); jedisPoolConfig.setMaxIdle(400); jedisPoolConfig.setMaxWaitMillis(100); jedisPoolConfig.setTestOnBorrow(false);//jedis 第一次启动时,会报错 jedisPoolConfig.setTestOnReturn(true); JedisPool pool = new JedisPool(jedisPoolConfig, "127.0.0.1", 6379); return pool; } /*** * redis集群用(这里暂时没用) * @return */ // @Bean public JedisCluster jedisCluster() { //创建jedisCluster对象,有一个参数 nodes是Set类型,Set包含若干个HostAndPort对象 Set<HostAndPort> nodes = new HashSet<>(); nodes.add(new HostAndPort("127.0.0.1", 7001)); nodes.add(new HostAndPort("127.0.0.1", 7002)); nodes.add(new HostAndPort("127.0.0.1", 7003)); nodes.add(new HostAndPort("127.0.0.1", 7004)); nodes.add(new HostAndPort("127.0.0.1", 7005)); nodes.add(new HostAndPort("127.0.0.1", 7006)); JedisCluster jedisCluster = new JedisCluster(nodes); //使用jedisCluster操作redis // jedisCluster.set("test", "my forst jedis"); // String str = jedisCluster.get("test"); // System.out.println(str); // //关闭连接池 // jedisCluster.close(); //注意由于集群式单例,不要再其他地方关闭连接池!!!!由系统关闭时统一关闭。 return jedisCluster; } } web 接口$title(SecKillController.java) package com.leftso.demo.demoredisseckill.controller; import com.leftso.demo.demoredisseckill.service.GoodsService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import java.util.UUID; /** * 秒杀接口 */ @RestController public class SecKillController { @Autowired GoodsService goodsService; @GetMapping("/seckill") public Object seckill() { //创建随机用户名 String userName="用户+"+ UUID.randomUUID().toString().replace("-","").toUpperCase(); boolean suc=goodsService.seckill(GoodsService.productKey,userName); return suc?"Succes":"Fail"; } } Redis 秒杀活动测试 这里的测试主要使用ab工具进行测试。首先启动秒杀应用(注意启动一次只能测一次。再次测试请重启服务,例子简单就这么处理的。哈哈)ab测试命令:ab -c 1000 -n 3000 http://localhost:8080/seckill 执行结果:​通过测试的访问来看,错误请求有2994.好吧我目前也不知道啥情况。反正后台的输出是正常的。来看看后台的日志吧 2019-07-10 11:49:10.592 INFO 2656 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 9 ms 用户:用户+28C21F33CBD3452EB5C67B14A3939356 抢购成功!!! 用户:用户+126334102697433C8BB5B5D6E4061E38 抢购成功!!! 用户:用户+9ED7D0B472E9490E9C780B61B881E41B 抢购成功!!! 用户:用户+5AD862F2A389473AB2B4FB5EDC187617 抢购成功!!! 用户:用户+A264C1B9CB69498D8402B2E6C0B47589 抢购成功!!! 用户:用户+FC3D45232C73446E8032FDC36B0B8430 抢购成功!!! 用户:用户+D0F11B01EAB24CB385C23580CFC4E671 抢购成功!!! 用户:用户+73FD6DAED8564FEF8315CC4D6181ED77 抢购成功!!! 用户:用户+6967C042ADCA4FC9B42B70A51EE10752 抢购成功!!! 用户:用户+BBB5FF4878844B79BD11DC8C15EA9500 抢购成功!!! 商品已抢购完毕>>>>>>>>>>>>>>>>>>>>>>>>>>欢迎下次再来。 商品已抢购完毕>>>>>>>>>>>>>>>>>>>>>>>>>>欢迎下次再来。 商品已抢购完毕>>>>>>>>>>>>>>>>>>>>>>>>>>欢迎下次再来。 商品已抢购完毕>>>>>>>>>>>>>>>>>>>>>>>>>>欢迎下次再来。 商品已抢购完毕>>>>>>>>>>>>>>>>>>>>>>>>>>欢迎下次再来。 活动结束>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>活动结束 抢购名单:[用户+28C21F33CBD3452EB5C67B14A3939356, 用户+126334102697433C8BB5B5D6E4061E38, 用户+9ED7D0B472E9490E9C780B61B881E41B, 用户+5AD862F2A389473AB2B4FB5EDC187617, 用户+A264C1B9CB69498D8402B2E6C0B47589, 用户+FC3D45232C73446E8032FDC36B0B8430, 用户+D0F11B01EAB24CB385C23580CFC4E671, 用户+73FD6DAED8564FEF8315CC4D6181ED77, 用户+6967C042ADCA4FC9B42B70A51EE10752, 用户+BBB5FF4878844B79BD11DC8C15EA9500] 剩余库存:0 商品已抢购完毕>>>>>>>>>>>>>>>>>>>>>>>>>>欢迎下次再来。 商品已抢购完毕>>>>>>>>>>>>>>>>>>>>>>>>>>欢迎下次再来。 商品已抢购完毕>>>>>>>>>>>>>>>>>>>>>>>>>>欢迎下次再来。 细数了下获奖名单还是没错的。哈哈谁知道ab为啥那么多错误请求?评论告诉我一下谢谢。