1 Star 0 Fork 0

Amos/amos

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
该仓库未声明开源许可证文件(LICENSE),使用请关注具体项目描述及其代码上游依赖。
克隆/下载
search.xml 48.66 KB
一键复制 编辑 原始数据 按行查看 历史
Amos 提交于 2019-01-13 22:06 . Amos技术博客
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title>《基于redis分布式锁实现秒杀》——总结</title>
<link href="/amos/2019/01/13/%E3%80%8A%E5%9F%BA%E4%BA%8Eredis%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81%E5%AE%9E%E7%8E%B0%E7%A7%92%E6%9D%80%E3%80%8B%E2%80%94%E2%80%94%E6%80%BB%E7%BB%93/"/>
<url>/amos/2019/01/13/%E3%80%8A%E5%9F%BA%E4%BA%8Eredis%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81%E5%AE%9E%E7%8E%B0%E7%A7%92%E6%9D%80%E3%80%8B%E2%80%94%E2%80%94%E6%80%BB%E7%BB%93/</url>
<content type="html"><![CDATA[<p>秒杀,从业务角度来说,是用户对同一资源进行争抢,从技术角度来说,是多个线程对资源进行操作</p><p>本文章参考简书上:<a href="https://www.jianshu.com/p/7a0f24e3d90f" target="_blank" rel="noopener">基于redis分布式锁实现“秒杀”</a></p><p>[TOC]</p><blockquote><p>秒杀:</p><pre><code>1. 从业务角度来说,是用户对同一资源进行争抢2. 从技术角度来说,是多个线程对资源进行操作</code></pre><p><br>总结:秒杀是对控制线程对资源的争抢,<strong>既要保证高效并发,也要保证操作的正确性</strong></p></blockquote><h3 id="1-实现的方法"><a href="#1-实现的方法" class="headerlink" title="1.实现的方法"></a>1.实现的方法</h3><p>对于线程的控制,常用的可以有以下三种方法:</p><ol><li>对争夺资源的方法入口加锁synchronized,这个方法是最粗暴的,会降低系统的性能。</li><li>在上面的基础上进行优化,我们可以对操作数据库的代码进行加锁,也就是对代码块进行加锁。这个加锁的的粒度也是比较大的,假设两个用户对不同的资源进行操作,比如说购买不同商品,从业务逻辑上来说是对不同的资源进行争抢,所以应该不是秒杀的业务实现,但是从技术层面来说,都对于商品这张表进行了操作,所以也就产生了竞争关系,所以也会降低系统的性能。</li><li>既然是并发的问题,理论上说将所有的请求进行串行,使用队列进行管理,自然就不会有并发问题,这样的话对于队列的负载就会很大,一旦消息出错,容易造成消息阻塞和消息丢失情况。这也不是一个理想的方法。</li></ol><h3 id="2-解决思维"><a href="#2-解决思维" class="headerlink" title="2.解决思维"></a>2.解决思维</h3><p>针对上面出现的问题,我们可以深入思考下,秒杀所出现的竞争关系是对同一个商品进行争抢,对于不同的商品是不应该出现竞争关系,所以我们需要在同一个商品上进行加锁。<strong>分布式锁</strong>可以解决上面的问题。</p><h3 id="3-分布式锁"><a href="#3-分布式锁" class="headerlink" title="3.分布式锁"></a>3.分布式锁</h3><blockquote><p>分布式锁:控制分布式系统之间同步访问共享资源的一种方式。</p></blockquote><p>很官方的解释,理解起来的话就是不同系统或者同一系统不同主机共享资源,那么访问这些资源,需要互斥来彼此进行干扰,保持一致性。</p><h3 id="4-模拟场景"><a href="#4-模拟场景" class="headerlink" title="4.模拟场景"></a>4.模拟场景</h3><p>目前分布式锁使用比较广泛的是redis,redis是key-value存储系统,他的特性很适合用来处理高并发:<br></p><ol><li>数据存储在内存中,处理速度非常快</li><li>键可以设置过期时间,使用redis键来操作锁,设置过期时间可以有效的防止死锁</li><li>单线程,消除了传统数据库串行控制的开销</li><li>支持事务,操作都是原子性</li></ol><p>现在我们来模拟秒杀的场景:<br></p><p>数据库里有一张表,column分别是商品ID,和商品ID对应的库存量,秒杀成功就将此商品库存量-1。现在假设有1000个线程来秒杀两件商品,500个线程秒杀第一个商品,500个线程秒杀第二个商品</p><h3 id="5-具体的实现"><a href="#5-具体的实现" class="headerlink" title="5. 具体的实现"></a>5. 具体的实现</h3><h4 id="5-1-redis的命令"><a href="#5-1-redis的命令" class="headerlink" title="5.1 redis的命令"></a>5.1 redis的命令</h4><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><code class="hljs undefined">## 如果key不存在就设置key以及对应的value,<br>## 如果存在就不做任何操作<br>SETNX key value <br><br>## 设置键的过期时间<br>EXPIRE key sceonds<br><br>## 删除键<br>DEL key<br></code></pre></td></tr></table></figure><h4 id="5-2-需要思考的问题"><a href="#5-2-需要思考的问题" class="headerlink" title="5.2 需要思考的问题"></a>5.2 需要思考的问题</h4><ol><li>java如何操作redis</li><li>怎么实现加锁</li><li>如果释放锁</li><li>阻塞还是非阻塞</li><li>针对异常的处理</li></ol><blockquote><p>在Spring中已经针对Redis的操作封装了jar包<br>我们针对商品的操作,其实是针对数据库中对应商品的id进行操作,对商品加锁,可以将商品对应的id来作为key存储在redis中,在对该商品进行操作时,先查看下是否在redis中存在,如果存在的话说明已经有用户在对该商品进行操作了,此时需要等待上面的用户处理完成。用户处理完成之后,可以操作删除redis中对应的键,相当于释放了锁。<br>采用阻塞方式,当发现已经上锁了,在特定的时间里轮询锁<br>业务由于种种原因导致失败,没有及时的释放锁,也就是删除redis中对应的key,我们可以添加键的失效时间来自动让锁释放。这样的话就避免了死锁的问题</p></blockquote><h4 id="5-3-代码实现"><a href="#5-3-代码实现" class="headerlink" title="5.3 代码实现"></a>5.3 代码实现</h4><p>以上都是理论性的讨论,现在开始基于之前的思考,来使用代码实现(代码基于博客上的代码进行了修改):</p><h4 id="5-3-1-自定义AOP需要切入的注解"><a href="#5-3-1-自定义AOP需要切入的注解" class="headerlink" title="5.3.1 自定义AOP需要切入的注解"></a>5.3.1 自定义AOP需要切入的注解</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">/**<br> * 方法级注解<br> *<br> * <span class="hljs-doctag">@author</span> zhuqb<br> */</span><br><span class="hljs-meta">@Target</span>(ElementType.METHOD)<br><span class="hljs-meta">@Retention</span>(RetentionPolicy.RUNTIME)<br><span class="hljs-meta">@Documented</span><br><span class="hljs-keyword">public</span> <span class="hljs-meta">@interface</span> CacheLock &#123;<br> <span class="hljs-comment">/**<br> * redis 锁key的前缀<br> *<br> * <span class="hljs-doctag">@return</span><br> */</span><br> <span class="hljs-function">String <span class="hljs-title">lockedPrefix</span><span class="hljs-params">()</span> <span class="hljs-keyword">default</span> ""</span>;<br><br> <span class="hljs-comment">/**<br> * 轮询锁的时间 默认是2s<br> *<br> * <span class="hljs-doctag">@return</span><br> */</span><br> <span class="hljs-function"><span class="hljs-keyword">long</span> <span class="hljs-title">timeOut</span><span class="hljs-params">()</span> <span class="hljs-keyword">default</span> 2000</span>;<br><br> <span class="hljs-comment">/**<br> * key在redis里存在的时间,1000S<br> *<br> * <span class="hljs-doctag">@return</span><br> */</span><br> <span class="hljs-function"><span class="hljs-keyword">int</span> <span class="hljs-title">expireTime</span><span class="hljs-params">()</span> <span class="hljs-keyword">default</span> 1000</span>;<span class="hljs-comment">//</span><br><br>&#125;<br></code></pre></td></tr></table></figure><hr><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">/**<br> * 参数级注解<br> * 自定义注解<br> *<br> * <span class="hljs-doctag">@author</span> zhuqb<br> */</span><br><span class="hljs-meta">@Target</span>(ElementType.PARAMETER)<br><span class="hljs-meta">@Retention</span>(RetentionPolicy.RUNTIME)<br><span class="hljs-meta">@Documented</span><br><span class="hljs-keyword">public</span> <span class="hljs-meta">@interface</span> LockedComplexObject &#123;<br> <span class="hljs-comment">/**<br> * 含有成员变量的复杂对象中需要加锁的成员变量,如一个商品对象的商品ID<br> *<br> * <span class="hljs-doctag">@return</span><br> */</span><br> <span class="hljs-function">String <span class="hljs-title">field</span><span class="hljs-params">()</span> <span class="hljs-keyword">default</span> ""</span>;<br>&#125;<br></code></pre></td></tr></table></figure><hr><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">/**<br> * 参数级注解<br> *<br> * <span class="hljs-doctag">@author</span> zhuqb<br> */</span><br><span class="hljs-meta">@Target</span>(ElementType.PARAMETER)<br><span class="hljs-meta">@Retention</span>(RetentionPolicy.RUNTIME)<br><span class="hljs-meta">@Documented</span><br><span class="hljs-keyword">public</span> <span class="hljs-meta">@interface</span> LockedObject &#123;<br> <span class="hljs-comment">/**<br> * 传入的参数值<br> *<br> * <span class="hljs-doctag">@return</span><br> */</span><br> <span class="hljs-function">String <span class="hljs-title">value</span><span class="hljs-params">()</span> <span class="hljs-keyword">default</span> ""</span>;<br>&#125;<br></code></pre></td></tr></table></figure><h4 id="5-3-2-AOP切入注解方法"><a href="#5-3-2-AOP切入注解方法" class="headerlink" title="5.3.2 AOP切入注解方法"></a>5.3.2 AOP切入注解方法</h4><p>注解比较简单,下面贴上主要的AOP切入代码,在切入方法执行前进行加锁,方法执行之后释放锁<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">/**<br> * 针对 含有 <span class="hljs-doctag">@CacheLock</span> 注解的方法 进行切入<br> *<br> * <span class="hljs-doctag">@author</span> zhuqb<br> */</span><br><span class="hljs-meta">@Component</span><br><span class="hljs-meta">@Aspect</span><br><span class="hljs-meta">@ComponentScan</span><br><span class="hljs-meta">@EnableAspectJAutoProxy</span><br><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">AspectAop</span> </span>&#123;<br><br> <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> Logger logger = LoggerFactory.getLogger(AspectAop.class);<br><br> <span class="hljs-meta">@Autowired</span><br> CacheLockService cacheLockService;<br><br> <span class="hljs-comment">/**<br> * 对所有方法注解了CacheLock 进行拦截<br> */</span><br> <span class="hljs-meta">@Pointcut</span>(<span class="hljs-string">"@annotation(com.amos.ms.type.CacheLock)"</span>)<br> <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">intercepter</span><span class="hljs-params">()</span> </span>&#123;<br><br> &#125;<br><br> <span class="hljs-comment">/**<br> * 方法执行前进行加锁<br> *<br> * <span class="hljs-doctag">@param</span> joinPoint<br> */</span><br> <span class="hljs-meta">@Before</span>(<span class="hljs-string">"intercepter()"</span>)<br> <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">doBeforeAdvice</span><span class="hljs-params">(JoinPoint joinPoint)</span> </span>&#123;<br> logger.info(<span class="hljs-string">"这是前置通知"</span>);<br> MethodParams params = <span class="hljs-keyword">this</span>.getValuesFromMethod(joinPoint);<br> <span class="hljs-comment">// 加锁</span><br> <span class="hljs-keyword">boolean</span> lock = cacheLockService.lock(params.getKey(), params.getTimeout(), params.getExpireTime());<br> <span class="hljs-keyword">if</span> (!lock) &#123;<br> CacheLockUtils.count++;<br> logger.info(<span class="hljs-string">"获取锁失败"</span>);<br> <span class="hljs-comment">// 这里不能抛出异常 否则会造成程序死锁 不知道为什么</span><br><span class="hljs-comment">// throw new CacheLockException("获取锁失败");</span><br> &#125;<br> &#125;<br><br> <span class="hljs-comment">/**<br> * 后置通知 只要方法执行完成了 就会执行该操作<br> *<br> * <span class="hljs-doctag">@param</span> joinPoint<br> */</span><br> <span class="hljs-meta">@After</span>(<span class="hljs-string">"intercepter()"</span>)<br> <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">doAfterAdvice</span><span class="hljs-params">(JoinPoint joinPoint)</span> </span>&#123;<br> logger.info(<span class="hljs-string">"这是后置通知"</span>);<br> MethodParams params = <span class="hljs-keyword">this</span>.getValuesFromMethod(joinPoint);<br> cacheLockService.unlock(params.getKey());<br> &#125;<br><br> <span class="hljs-comment">/**<br> * 获取锁操作 需要的参数<br> *<br> * <span class="hljs-doctag">@param</span> joinPoint<br> * <span class="hljs-doctag">@return</span><br> */</span><br> <span class="hljs-function"><span class="hljs-keyword">private</span> MethodParams <span class="hljs-title">getValuesFromMethod</span><span class="hljs-params">(JoinPoint joinPoint)</span> </span>&#123;<br> <span class="hljs-comment">// 获取所有的参数值</span><br> Object[] paramVaules = joinPoint.getArgs();<br> MethodSignature signature = (MethodSignature) joinPoint.getSignature();<br> Method method = signature.getMethod();<br> <span class="hljs-comment">// 获取 @CacheLock</span><br> Annotation annotation = method.getAnnotation(CacheLock.class);<br><br> <span class="hljs-comment">// 拦截的参数</span><br> String key = CacheLockUtils.getCacheLockKey(method,paramVaules);<br> <span class="hljs-keyword">if</span> (StringUtils.isBlank(key)) &#123;<br> <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> CacheLockException(<span class="hljs-string">"没有需要加锁的参数"</span>);<br> &#125;<br><br> <span class="hljs-comment">// 对拦截的参数进行封装</span><br> String lockKey = LockUtils.getLockKey(((CacheLock) annotation).lockedPrefix(), key);<br><br> MethodParams params = <span class="hljs-keyword">new</span> MethodParams();<br> params.setExpireTime(((CacheLock) annotation).expireTime());<br> params.setTimeout(((CacheLock) annotation).timeOut());<br> params.setPrefix(((CacheLock) annotation).lockedPrefix());<br> params.setKey(lockKey);<br> <span class="hljs-keyword">return</span> params;<br> &#125;<br><br> <span class="hljs-meta">@Data</span><br> <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MethodParams</span> </span>&#123;<br> <span class="hljs-comment">/**<br> * 拦截锁前缀<br> */</span><br><br> <span class="hljs-keyword">private</span> String prefix;<br> <span class="hljs-comment">/**<br> * 拦截对象<br> */</span><br> <span class="hljs-keyword">private</span> String key;<br> <span class="hljs-comment">/**<br> * 轮询时间<br> */</span><br> <span class="hljs-keyword">private</span> <span class="hljs-keyword">long</span> timeout;<br> <span class="hljs-comment">/**<br> * 过期时间<br> */</span><br> <span class="hljs-keyword">private</span> <span class="hljs-keyword">long</span> expireTime;<br><br> &#125;<br>&#125;<br></code></pre></td></tr></table></figure></p><h4 id="5-3-3-加锁代码"><a href="#5-3-3-加锁代码" class="headerlink" title="5.3.3 加锁代码"></a>5.3.3 加锁代码</h4><p>加锁采用的是向redis数据库添加键,通过判断键是否存在来判断是否已经加锁,如果已经加锁了,需要在轮询时间内看看是否释放锁,加锁的代码如下:<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">/**<br> * 给键添加锁<br> *<br> * <span class="hljs-doctag">@param</span> key<br> * <span class="hljs-doctag">@param</span> timeout 轮询时间<br> * <span class="hljs-doctag">@param</span> expireSeconds 过期时间<br> * <span class="hljs-doctag">@return</span><br> */</span><br> <span class="hljs-meta">@Override</span><br> <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">boolean</span> <span class="hljs-title">lock</span><span class="hljs-params">(String key, <span class="hljs-keyword">long</span> timeout, <span class="hljs-keyword">long</span> expireSeconds)</span> </span>&#123;<br> <span class="hljs-keyword">boolean</span> flag = <span class="hljs-keyword">false</span>;<br> <span class="hljs-keyword">long</span> nanoTime = System.nanoTime();<br> <span class="hljs-comment">// 轮询时间</span><br> timeout *= MILLI_NANO_TIME;<br><br> <span class="hljs-keyword">try</span>&#123;<br> <span class="hljs-keyword">while</span> (System.nanoTime() - nanoTime &lt; timeout) &#123;<br> <span class="hljs-keyword">if</span> (redisService.setnx(key,System.currentTimeMillis()+<span class="hljs-string">""</span>,expireSeconds)) &#123;<br> <span class="hljs-keyword">return</span> <span class="hljs-keyword">true</span>;<br> &#125;<br> logger.info(<span class="hljs-string">"出现锁等待"</span>);<br> <span class="hljs-comment">// 短暂休眠,避免可能的活锁</span><br> Thread.sleep(<span class="hljs-number">3</span>);<br> &#125;<br> &#125;<span class="hljs-keyword">catch</span> (Exception e) &#123;<br> <span class="hljs-keyword">if</span> (logger.isDebugEnabled()) &#123;<br> e.printStackTrace();<br> &#125;<br> flag = <span class="hljs-keyword">false</span>;<br> &#125;<br> <span class="hljs-keyword">return</span> flag;<br> &#125;<br></code></pre></td></tr></table></figure></p><p>释放锁即是删除redis中对应的键<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">/**<br> * 释放锁<br> *<br> * <span class="hljs-doctag">@param</span> key<br> * <span class="hljs-doctag">@return</span><br> */</span><br> <span class="hljs-meta">@Override</span><br> <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">boolean</span> <span class="hljs-title">unlock</span><span class="hljs-params">(String key)</span> </span>&#123;<br> <span class="hljs-keyword">return</span> redisService.del(key);<br> &#125;<br></code></pre></td></tr></table></figure></p><h4 id="5-3-4-SpringBoot操作Redis"><a href="#5-3-4-SpringBoot操作Redis" class="headerlink" title="5.3.4 SpringBoot操作Redis"></a>5.3.4 SpringBoot操作Redis</h4><p>SpringBoot可以集成对于Redis的操作<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Autowired</span><br> StringRedisTemplate stringRedisTemplate;<br><br> <span class="hljs-comment">/**<br> * 设置键,并且设置过期时间<br> *<br> * <span class="hljs-doctag">@param</span> key 键的名称<br> * <span class="hljs-doctag">@param</span> value 键的值<br> * <span class="hljs-doctag">@param</span> expireSeconds 过期时间 单位 ms<br> * <span class="hljs-doctag">@return</span><br> */</span><br> <span class="hljs-meta">@Override</span><br> <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">boolean</span> <span class="hljs-title">setnx</span><span class="hljs-params">(String key, String value, <span class="hljs-keyword">long</span> expireSeconds)</span> </span>&#123;<br> <span class="hljs-keyword">return</span> stringRedisTemplate.opsForValue().setIfAbsent(key,value,expireSeconds,TimeUnit.MILLISECONDS);<br> &#125;<br><br> <span class="hljs-comment">/**<br> * 删除键<br> *<br> * <span class="hljs-doctag">@param</span> key<br> * <span class="hljs-doctag">@return</span><br> */</span><br> <span class="hljs-meta">@Override</span><br> <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">boolean</span> <span class="hljs-title">del</span><span class="hljs-params">(String key)</span> </span>&#123;<br> <span class="hljs-keyword">return</span> stringRedisTemplate.delete(key);<br> &#125;<br></code></pre></td></tr></table></figure></p><h4 id="5-3-5-测试案例"><a href="#5-3-5-测试案例" class="headerlink" title="5.3.5 测试案例"></a>5.3.5 测试案例</h4><p>以上就是主要的业务代码,下面可以通过编写测试方法来测试,测试代码如下:<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-function"><span class="hljs-keyword">public</span> String <span class="hljs-title">secKill</span><span class="hljs-params">()</span> </span>&#123;<br> <span class="hljs-keyword">int</span> threadCount = <span class="hljs-number">1000</span>;<br> <span class="hljs-keyword">int</span> splitPoint = <span class="hljs-number">500</span>;<br> CountDownLatch endCount = <span class="hljs-keyword">new</span> CountDownLatch(threadCount);<br> CountDownLatch beginCount = <span class="hljs-keyword">new</span> CountDownLatch(<span class="hljs-number">1</span>);<br> Thread[] threads = <span class="hljs-keyword">new</span> Thread[threadCount];<br> <span class="hljs-comment">//起500个线程,秒杀第一个商品</span><br> <span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> i = <span class="hljs-number">0</span>; i &lt; splitPoint; i++) &#123;<br> threads[i] = <span class="hljs-keyword">new</span> Thread(<span class="hljs-keyword">new</span> Runnable() &#123;<br> <span class="hljs-meta">@Override</span><br> <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">run</span><span class="hljs-params">()</span> </span>&#123;<br> <span class="hljs-keyword">try</span> &#123;<br> <span class="hljs-comment">// 等待在一个信号量上,挂起</span><br> beginCount.await();<br> businessService.secKill(<span class="hljs-string">"test"</span>, <span class="hljs-number">10000001L</span>);<br> endCount.countDown();<br> &#125; <span class="hljs-keyword">catch</span> (InterruptedException e) &#123;<br> <span class="hljs-comment">// TODO Auto-generated catch block</span><br> e.printStackTrace();<br> &#125;<br> &#125;<br> &#125;);<br> threads[i].start();<br><br> &#125;<br> <span class="hljs-comment">//再起500个线程,秒杀第二件商品</span><br> <span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> i = splitPoint; i &lt; threadCount; i++) &#123;<br> threads[i] = <span class="hljs-keyword">new</span> Thread(<span class="hljs-keyword">new</span> Runnable() &#123;<br> <span class="hljs-meta">@Override</span><br> <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">run</span><span class="hljs-params">()</span> </span>&#123;<br> <span class="hljs-keyword">try</span> &#123;<br> <span class="hljs-comment">// 等待在一个信号量上,挂起</span><br> beginCount.await();<br> businessService.secKill(<span class="hljs-string">"test"</span>, <span class="hljs-number">10000002L</span>);<br> endCount.countDown();<br> &#125; <span class="hljs-keyword">catch</span> (InterruptedException e) &#123;<br> <span class="hljs-comment">// TODO Auto-generated catch block</span><br> e.printStackTrace();<br> &#125;<br> &#125;<br> &#125;);<br> threads[i].start();<br><br> &#125;<br><br><br> <span class="hljs-keyword">long</span> startTime = System.currentTimeMillis();<br> <span class="hljs-comment">//主线程释放开始信号量,并等待结束信号量,这样做保证1000个线程做到完全同时执行,保证测试的正确性</span><br> beginCount.countDown();<br><br> <span class="hljs-keyword">try</span> &#123;<br> <span class="hljs-comment">//主线程等待结束信号量</span><br> endCount.await();<br> <span class="hljs-comment">//观察秒杀结果是否正确</span><br> System.out.println(BusinessServiceImpl.inventory.get(<span class="hljs-number">10000001L</span>));<br> System.out.println(BusinessServiceImpl.inventory.get(<span class="hljs-number">10000002L</span>));<br> System.out.println(<span class="hljs-string">"error count"</span> + CacheLockUtils.count);<br> System.out.println(<span class="hljs-string">"total cost "</span> + (System.currentTimeMillis() - startTime));<br> &#125; <span class="hljs-keyword">catch</span> (InterruptedException e) &#123;<br> <span class="hljs-comment">// TODO Auto-generated catch block</span><br> e.printStackTrace();<br> &#125;<br> <span class="hljs-keyword">return</span> <span class="hljs-string">"aaaa"</span>;<br> &#125;<br></code></pre></td></tr></table></figure></p><p>本文是参考理解《基于redis分布式锁实现秒杀》一文进行的总结以及调整,在此感谢简书作者:lsfire,感谢分享秒杀业务类的实现思路和方法。<br><br>代码存放在github上:<a href="https://github.com/endyzhu/secKill" target="_blank" rel="noopener">基于Redis分布式来实现秒杀业务</a></p>]]></content>
<tags>
<tag> Java </tag>
<tag> 动态代理 </tag>
<tag> Redis </tag>
<tag> AOP </tag>
<tag> 自定义注解 </tag>
</tags>
</entry>
<entry>
<title>Java动态代理</title>
<link href="/amos/2019/01/03/Java%E5%8A%A8%E6%80%81%E4%BB%A3%E7%90%86/"/>
<url>/amos/2019/01/03/Java%E5%8A%A8%E6%80%81%E4%BB%A3%E7%90%86/</url>
<content type="html"><![CDATA[<p>Java中动态代理共有两种模式:原生动态代理和CGLIB动态代理两种</p><blockquote><p>什么是动态代理?<br>要理解动态代理的话,先了解下什么是代理,打个比方,比如说你要去处理某件事情,这件事情很急,但是你又抽不出时间,你可以请别人帮你处理下,这里的你自己就是被代理方,别人就是代理方,这个就是代理,代理你去做某件事。</p></blockquote><p>在Java中有个设计模式是代理模式,这里说的就是代理,不过对应的动态代理,既然有动态代理的话,肯定就有静态代理了。<br>代理模式一般有四个部分组成</p><ul><li><p>抽象主题角色</p><p> 定义具体主题角色和代理主题角色的共有行为</p></li><li><p>具体主题角色</p><p> 被代理方,是业务逻辑的具体执行者</p></li><li><p>代理主题角色</p><p> 代理方,负责对主题角色的调用,把所有抽象主题角色定义的方法限制委托给具体主题角色实现,并且可以在真实主题角色处理完毕前后做预处理和善后处理工作</p></li><li>调用者<br> 是执行代理主题角色的类(相当于测试用例,负责调用)</li></ul><p>静态代理比较简单,我们理解一下概念即可,在实际的运用过程中很少用到。</p><blockquote><p>静态代理:在编译时就已经将代理类确定的就是静态代理<br><br>动态代理:在程序运行时创建代理类的方式就是动态代理</p></blockquote><blockquote><p>Java的动态代理目前有两种实现的方式:原生动态代理和CGLIB动态代理两种</p><ol><li>java实现动态代理(基于接口)</li></ol></blockquote><p><strong>实现InvocationHandler接口,方法调用会被转发到该类的invoke() 方法上</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">/**<br> * <span class="hljs-doctag">@author</span> Amos<br> * <span class="hljs-doctag">@ClassName</span> HelloProxy<br> * <span class="hljs-doctag">@Description</span> 实现InvocationHandler接口,方法调用会被转发到该类的invoke() 方法上<br> * <span class="hljs-doctag">@Date</span> 2018/11/20 19:37<br> * <span class="hljs-doctag">@VERSION</span> 1.0<br> **/</span><br><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">HelloProxy</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">InvocationHandler</span> </span>&#123;<br> <span class="hljs-keyword">private</span> Hello hello;<br> <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">HelloProxy</span><span class="hljs-params">(Hello hello)</span></span>&#123;<br> <span class="hljs-keyword">this</span>.hello = hello;<br> &#125;<br> <span class="hljs-meta">@Override</span><br> <span class="hljs-function"><span class="hljs-keyword">public</span> Object <span class="hljs-title">invoke</span><span class="hljs-params">(Object proxy, Method method, Object[] args)</span> <span class="hljs-keyword">throws</span> Throwable </span>&#123;<br> <span class="hljs-keyword">if</span> (<span class="hljs-string">"say"</span>.equals(method.getName()))&#123;<br> System.out.println(<span class="hljs-string">"you say:"</span>+ Arrays.toString(args));<br> &#125;<br> <span class="hljs-keyword">return</span> method.invoke(hello,args);<br> &#125;<br>&#125;<br></code></pre></td></tr></table></figure><p><strong>使用jdk动态代理获取代理对象</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">/**<br> * loader: 指定代理对象的类加载器<br> * interfaces: 代理对象需要实现的接口,可以同时指定多个接口<br> * handler: 方法调用的实际处理者,代理对象的方法调用都会转发到这里<br> */</span><br>Hello hello = (Hello)Proxy.newProxyInstance(HelloProxy.class.getClassLoader(),<span class="hljs-keyword">new</span> Class&lt;?&gt;[]&#123;Hello.class&#125;,<br> <span class="hljs-keyword">new</span> HelloProxy(<span class="hljs-keyword">new</span> HelloImpl()));<br>System.out.println(hello.say(<span class="hljs-string">"amos's code"</span>));<br></code></pre></td></tr></table></figure><ol start="2"><li>CGLIB动态代理(通过继承方式来实现) </li></ol><p><strong>实现MethodInterceptor,方法调用会被转发到该类的intercept()方法上</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">/**<br>* <span class="hljs-doctag">@author</span> Amos<br>* <span class="hljs-doctag">@ClassName</span> MyMethodInteceptor<br>* <span class="hljs-doctag">@Description</span> 实现MethodInterceptor,方法调用会被转发到该类的intercept()方法上<br>* <span class="hljs-doctag">@Date</span> 2018/11/20 20:13<br>* <span class="hljs-doctag">@VERSION</span> 1.0<br>**/</span><br><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MyMethodInteceptor</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">MethodInterceptor</span> </span>&#123;<br> <span class="hljs-meta">@Override</span><br> <span class="hljs-function"><span class="hljs-keyword">public</span> Object <span class="hljs-title">intercept</span><span class="hljs-params">(Object o, Method method, Object[] objects, MethodProxy methodProxy)</span> <span class="hljs-keyword">throws</span> Throwable </span>&#123;<br> System.out.println(<span class="hljs-string">"the params:"</span>+ Arrays.toString(objects));<br> <span class="hljs-keyword">return</span> methodProxy.invokeSuper(o,objects);<br> &#125;<br>&#125;<br></code></pre></td></tr></table></figure><p><strong>通过CGLIB动态代理获取代理对象</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">/**<br>* 通过Enhancer来指定要代理的目标对象,实际处理代理逻辑的对象,再通过create()方法获取代理对象<br>*/</span><br>Enhancer enhancer = <span class="hljs-keyword">new</span> Enhancer();<br>enhancer.setSuperclass(Me.class);<br>enhancer.setCallback(<span class="hljs-keyword">new</span> MyMethodInteceptor());<br><span class="hljs-comment">/**<br>* 该对象的所有的非final方法的调用对会转发给MyMethodInteceptor.intercept(),<br>*/</span><br>Me me = (Me) enhancer.create();<br>System.out.println(me.say(<span class="hljs-string">"world"</span>));<br></code></pre></td></tr></table></figure>]]></content>
<tags>
<tag> Java </tag>
<tag> 动态代理 </tag>
<tag> CGLIB </tag>
</tags>
</entry>
<entry>
<title>Hello World</title>
<link href="/amos/2018/12/27/hello-world/"/>
<url>/amos/2018/12/27/hello-world/</url>
<content type="html"><![CDATA[<p>Welcome to <a href="https://hexo.io/" target="_blank" rel="noopener">Hexo</a>! This is your very first post. Check <a href="https://hexo.io/docs/" target="_blank" rel="noopener">documentation</a> for more info. If you get any problems when using Hexo, you can find the answer in <a href="https://hexo.io/docs/troubleshooting.html" target="_blank" rel="noopener">troubleshooting</a> or you can ask me on <a href="https://github.com/hexojs/hexo/issues" target="_blank" rel="noopener">GitHub</a>.</p><h2 id="Quick-Start"><a href="#Quick-Start" class="headerlink" title="Quick Start"></a>Quick Start</h2><h3 id="Create-a-new-post"><a href="#Create-a-new-post" class="headerlink" title="Create a new post"></a>Create a new post</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">$ hexo new <span class="hljs-string">"My New Post"</span><br></code></pre></td></tr></table></figure><p>More info: <a href="https://hexo.io/docs/writing.html" target="_blank" rel="noopener">Writing</a></p><h3 id="Run-server"><a href="#Run-server" class="headerlink" title="Run server"></a>Run server</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">$ hexo server<br></code></pre></td></tr></table></figure><p>More info: <a href="https://hexo.io/docs/server.html" target="_blank" rel="noopener">Server</a></p><h3 id="Generate-static-files"><a href="#Generate-static-files" class="headerlink" title="Generate static files"></a>Generate static files</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">$ hexo generate<br></code></pre></td></tr></table></figure><p>More info: <a href="https://hexo.io/docs/generating.html" target="_blank" rel="noopener">Generating</a></p><h3 id="Deploy-to-remote-sites"><a href="#Deploy-to-remote-sites" class="headerlink" title="Deploy to remote sites"></a>Deploy to remote sites</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">$ hexo deploy<br></code></pre></td></tr></table></figure><p>More info: <a href="https://hexo.io/docs/deployment.html" target="_blank" rel="noopener">Deployment</a></p>]]></content>
</entry>
<entry>
<title>about</title>
<link href="/amos/about/index.html"/>
<url>/amos/about/index.html</url>
<content type="html"><![CDATA[]]></content>
</entry>
</search>
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
HTML
1
https://gitee.com/xrea/amos.git
git@gitee.com:xrea/amos.git
xrea
amos
amos
master

搜索帮助

23e8dbc6 1850385 7e0993f3 1850385