关于 MySQL 和 MyBatis 易错的几个点

由于某些不可抗拒力原因,自从开博以来断更了一个月,昨天晚上突然发现竟然解封了,今天立即写一篇小文章感谢党感谢政府感谢人民。话说,这一周有一个实习的同学,在写一个小东西的时候,发现一个问题,排序没有生效,刚好之前我也看过另外一个问题,现在算是总结一下。 ORDER BY 不生效 代码大概就是: SELECT * FROM t ORDER BY "id DESC"; 我们其实可以很明显的看出来这个 SQL 有问题,ORDER BY 的后面多了引号,关键是这个 SQL 报错吗?如果 id 是随便写的一个不存在的列报错吗?答案是都不报错,排序也不生效,大家可以测试一些,这有时候就比较坑了,具体为什么会出现这个错误,后面再说,先说第二个。 分页问题 代码如下: /** * 开始页码 */ private String pageSize; /** * 分页量 */ private String offset; <if test="pageSize != null"> <if test="offset != null"> limit #{offset}, #{pageSize} </if> <if test="offset == null"> limit #{pageSize} </if> </if> 这个错误就比较明显了,一般人也不会犯,为什么要单独说一下呢?因为某同事说,分页用 # 会出错,但是我记得我一直用 # 没问题啊,刚好在遇到上面这个问题的时候,顺便测试了一些,还真有错,奇怪啊,其实原因很简单,就是分页的对象的属性有误,我们知道分页的属性,一般都是用 Integer,这个地方却写了 String,真是没事找事啊,一般谁会这么写?个人认为除非脑子有病。下面看第三个问题,也是一个比较诡异的问题。 UPDATE UPDATE t SET username = "BridgeLi" AND passwd = "BridgeLi" WHERE id = 1; 这个 SQL 有问题吗?会报错吗?这个问题有时候真不太看得出来,其实也很明显,SET 后面的各个列应该是 “,” 相连,而这个用的是 AND,有时候脑子一抽,还真有可能写错。但是会报错吗?这个问题还真不好答复,因为这个 SQL 这么写,相当于: ...

July 8, 2019 · 1 分钟 · Bridge Li

Redis GeoHash 的一个小示例

上周产品经理提了一个类似于 LBS 的应用,第一时间想到了忘记了之前什么时候看 Redis 的 API,发现 Redis 自 3.2 版本之后,新增了一类关于地理位置相关的 API,于是拿来测试一下,发现特别好用,写一个小例子作为笔记。 首先需要说明的是,由于我们公司的 JDK 的版本是 1.7,所以我采用的 spring-data-redis 的版本是:1.8.20.RELEASE,最新二点几的版本已经不支持 JDK 1.7,而一点几和二点几的版本的 API 有略微的差异(下面会说明,还有一点点我的小感悟),废话不多说,直接看例子: @Override public Long geoAdd(String key, List<Entity> entities) { redisTemplate.delete(key); GeoOperations geoOperations = redisTemplate.opsForGeo(); Map<String, Point> map = new HashMap<>(); Point point = null; for (Entity entity : entities) { point = new Point(entity.getLongitude(), entity.getLatitude()); map.put(gson.toJson(entity), point); } Long add = geoOperations.geoAdd(key, map); return add; } 参数就两个很简单,一个是 key,一个是数据集,我们将在这个集合中找出符合条件的数据,需要说明的是: 第一行我先调用了一个 delete 方法,是将上次放进去的数据删除,因为这个命令是 add,也就是新增,但是 redis 并没有提供直接删除这个 key 的命令(有一个 remove 的方法,但是需要传入删除哪些数据,也就是不能只给一个 key,把这个 key 对应的数据都删除,个人感觉不太好用),当然你也可以在计算后取得相应的数据之后删除,个人感觉都一样,不要忘记清理数据就行,另一个方法就是设置过期时间,也都行; 为什么要清理数据?因为这个 add,不同的情况,放进去的数据应该不同的,如果 entities 已经发生变更,而一直 add,那么数据将会乱掉,所以先把之前的数据删掉再说; 我个人采用的是把数据放到了 Map 中,其中 key 是对象序列化之后的 json 串,目的是为了下面找到对应的数据之后,直接反序列化成对象进行返回,当然也可以采用其他的方案,还有就是 add 还有一些其他的 API,这个大家可以自己看文档,选择合适的就行; 数据放进去之后就是计算了,我们的需求就是算一个人旁边几公里内有多少符合条件的数据,代码如下: ...

May 19, 2019 · 2 分钟 · Bridge Li

分享 Guava 的一些常见方法

前几天同事分享了一些关于 Guava 的一起基础用法,我之前没用过,感觉挺好的,所以记一些常见的方法。 一. 基础工具类,字符串相关的 其实这些在 apache commons-lang3,算是重复造轮子吧,简单说一下。 判断字符串是否为空,之前看到很多人自己定义,这些可能是一些老程序员吧,apache commons-lang3,Guava 的如下: boolean nullOrEmpty = Strings.isNullOrEmpty(""); 补全字符串(在前面补全和后面补全) String padStart0 = Strings.padStart("3", 2, 'a'); System.out.println("padStart0 = " + padStart0); String padStart1 = Strings.padStart("333", 2, 'a'); System.out.println("padStart1 = " + padStart1); 拆分和合并字符串 List<String> list = Splitter.on(",").splitToList("Denny,BridgeLi,CCC"); System.out.println("list = " + list); String join = Joiner.on(",").join(list); System.out.println("join = " + join); 对象相等判断和 ToStringHelper 类 boolean equal = Objects.equal("", ""); MoreObjects.toStringHelper(); 二. 集合类相关的,这些个人感觉还是非常常用的 不可变集合 @Test public void testJDK() { List<String> list = new ArrayList<>(); list.add("BridgeLi"); list.add("DennyLi"); List<String> unmodifiableList = Collections.unmodifiableList(list); System.out.println(unmodifiableList); unmodifiableList.add("Blog"); System.out.println(unmodifiableList); list.add("Blog"); System.out.println(unmodifiableList); } @Test public void testImmutableList() { List<String> list = new ArrayList<>(); list.add("BridgeLi"); list.add("DennyLi"); List<String> unmodifiableList = ImmutableList.copyOf(list); System.out.println(unmodifiableList); unmodifiableList.add("Blog"); System.out.println(unmodifiableList); list.add("Blog"); System.out.println(unmodifiableList); } 可重复集合,这个其实很常用可重复 set 可以用来基数,而可重复 Map 则可以实现 Map<K, List> 或者 Map<K, Set> 这样比较复杂的集合类型的数据结构 @Test public void testMultiset() { List<String> strings = Lists.newArrayList("aa", "bb", "aa", "cc"); Multiset<String> wordsMultiset = HashMultiset.create(); wordsMultiset.addAll(strings); for (String key : wordsMultiset.elementSet()) { System.out.println(key + " count:" + wordsMultiset.count(key)); } } @Test public void teststuScoreMultimap() { Multimap<String, StudentScore> scoreMultimap = ArrayListMultimap.create(); StudentScore studentScore = null; for (int i = 10; i < 20; i++) { studentScore = new StudentScore(); studentScore.courseId = 1001 + i; studentScore.score = 100 - i; scoreMultimap.put("peida", studentScore); } System.out.println("scoreMultimap:" + scoreMultimap.size()); System.out.println("scoreMultimap:" + scoreMultimap.keys()); Collection<StudentScore> studentScores = scoreMultimap.get("peida"); StudentScore studentScore1 = new StudentScore(); studentScore1.courseId = 1034; studentScore1.score = 67; studentScores.add(studentScore1); StudentScore studentScore2 = new StudentScore(); studentScore2.courseId = 1045; studentScore2.score = 56; scoreMultimap.put("jerry", studentScore2); System.out.println("scoreMultimap:" + scoreMultimap.size()); System.out.println("scoreMultimap:" + scoreMultimap.keys()); for (StudentScore stuScore : scoreMultimap.values()) { System.out.println("stuScore one:" + stuScore.courseId + " score:" + stuScore.score); } scoreMultimap.remove("jerry", studentScore2); System.out.println("scoreMultimap:" + scoreMultimap.size()); System.out.println("scoreMultimap:" + scoreMultimap.get("jerry")); scoreMultimap.put("harry", studentScore2); scoreMultimap.removeAll("harry"); System.out.println("scoreMultimap:" + scoreMultimap.size()); System.out.println("scoreMultimap:" + scoreMultimap.get("harry")); } class StudentScore{ int courseId; int score; } 双向 Map(BiMap),Map 是一种键值对映射,这个映射是键到值的映射,而 BiMap 既提供键到值的映射,也提供值到键的映射,所以它是双向 Map @Test public void testBimap() { BiMap<String, String> biMap = HashBiMap.create(); biMap.put("星期一", "Monday"); System.out.println("by key:" + biMap.get("星期一")); BiMap<String, String> biMap1 = biMap.inverse(); System.out.println("biMap1:" + biMap1.get("Monday")); } 双键 Map(Table),有些时候需要写 Map<String, Map<String, String» 这种格式的代码。但是这种阅读起来非常的不友好,Table 提供了新的思路:通过 rowKey + columnKey + value 来支持 @Test public void testTable() { Table<String, String, String> table = HashBasedTable.create(); table.put("IBM", "101", "A"); table.put("IBM", "102", "B"); table.put("IBM", "103", "C"); table.put("sun", "111", "D"); table.put("sun", "112", "E"); table.put("sun", "113", "F"); table.put("Google", "121", "G"); table.put("Google", "102", "H"); table.put("Google", "123", "L"); System.out.println(table.get("IBM", "102")); System.out.println(table.row("IBM")); System.out.println(table.column("102")); } 部分参考:https://www.cnblogs.com/peida/tag/Guava%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/

April 30, 2019 · 2 分钟 · Bridge Li

关于 CAP 理论 和 BASE 理论

一、CAP 理论 CAP 理论是分布式计算领域公认的一个定理,是分布式架构师必须掌握的理论,目前网上关于这块的资料也很多,各种说法,其实 CAP 理论自身也是一个不断发展的过程,相比之下比较准确的说法应该是:在一个分布式系统(指互相连接并共享数据的节点的集合)中,当涉及读写操作时,只能保证一致性(Consistence)、可用性(Availability)、分区容错性(Partition Tolerance)三者中的两个,另外一个必须被牺牲。也就是说必须是相关连接并且共享数据的分布式系统才是我们讨论 CAP 的基础,另外就是 CAP 理论关注的是对数据的读写操作,而不是分布式系统的所有功能。明白了这些之后,下面对 CAP 逐一讲解: 一致性(Consistency) 很多资料都说,对一致性的讲解时所有节点在同一时刻都能看到相同的数据,其实更准确的应该是:对某个客户端来说,读操作保证能够返回最新的写操作结果,是否数据一定一直呢?还真不一定,对于系统执行事务来说,在事务执行过程中,系统其实处于一个不一致的状态,不同的节点的数据并不完全一致,因为事务在执行过程中,client 是无法读取到未提交的数据的,只有等到事务提交后,client 才能读取到事务写入的数据,而如果事务失败则会进行回滚,client 也不会读取到事务中间写入的数据。 可用性(Availability) 一般的解释是,每个请求都能得到成功或者失败的响应,更准确的是,非故障的节点在合理的时间内返回合理的响应(不是错误和超时的相应),其实什么是成功和失败?超时报错算不算失败的响应?所以应该是合理的响应,另外故障节点肯定是不能返回结果了。 分区容错性(Partition Tolerance) 一般说法是,出现消息丢失或者分区错误时系统能够继续运行,其实更准确的是,当出现网络分区后,系统能够继续“履行职责”。因为关于运行,只要不宕机就可以说在运行。 综上,其实 CAP 理论也是一个自身不断发展的历程,虽然以前的理论也能说明问题,但是仔细深究起来发现有些问题,下面会有说明。 CAP 应用 CAP 理论开明宗义就说我们必须放弃一个,但是其实仔细想想我们可以放弃 P 吗?因为网络本身无法做到 100% 可靠,有可能出故障,所以分区是一个必然的现象。如果我们选择了 CA 而放弃了 P,那么当发生分区现象时,为了保证 C,系统需要禁止写入,当有写入请求时,系统返回 error(例如,当前系统不允许写入),这又和 A 冲突了,因此,分布式系统理论上不可能选择 CA 架构,只能选择 CP 或者 AP 结果,但是需要说明的是,按照通常解释,是可以 CA 的,因为通常的解释 A 是返回失败或者成功的结果,而不允许写入,其实就是失败的结果,满足 A 的定义 CP 为了保证一致性,当发生分区现象后,N1 节点上的数据已经更新到 y,但由于 N1 和 N2 之间的复制通道中断,数据 y 无法同步到 N2,N2 节点上的数据还是 x。这时客户端 C 访问 N2 时,N2 需要返回 Error,提示客户端 C“系统现在发生了错误”,这种处理方式违背了 A 的要求,因此 CAP 三者只能满足 CP ...

March 31, 2019 · 1 分钟 · Bridge Li

小议服务器命名

这个问题其实不能称之为问题,给服务器命名应该算是一个常识性问题,任何人都可以想到的,其实不仅是服务器,在我们生活中的一切都有名字,如果我们生活在一个没有名字的世界中,你想想有多可怕把?但为什么写这篇文章呢?因为我们公司很奇怪,不知道是运维疏忽还是啥,每次通过跳板机登陆线上服务器,必须通过 IP 地址才行,所以每个人一定要记录自己负责的服务器的 IP 地址才可以,否则一筹莫展,但是 IP 地址,大家都懂得,不然也不会有域名的存在了,大家都通过 IP 访问互联网就好了,前几天看公众号,刚好看到知书堂有位老师写过一篇文章来说这个问题,所以转载过来,供大家给服务器命名参考,后续也会给出我自己的小建议。原文如下: 这个问题太简单,以致于提起来,很多人忽略掉了。今天给大家秀一下这几年见到的命名情况,供大家赏玩。 这里面没有最好,但有最差。我们按命名满分 5分来打分。 第一位:无敌的localhost 提起这个大家不会陌生,REHL 系例安装默认的名字就是:localhost 很多学习环境里都是: localhost 很多测试环境里也是: localhost 几十台上线机器所是: localhost 爷,你真的无敌了。 不举例子了。使用这个命名的。只能说 I 服了 YOU,你离误操作也不远了。 命名评分:0 分。显然没毕业,建议学习去。 第二名:业务 + 编号 使用业务命加编号,如:user01、user02、。。。 命名评分:3 分。属于有规模管理想法,会减少一些误操作。全局管理上还有一定的局限性 第三名:业务 + 角色 + 编号 如:core-master-1、core-master-2、core-slave-1、core-slave-2 非常直观,但对于机器好象不能允许随便切来切去,只能在几台机器里面切换了。在传统企业或是中小企业里,这种命名结构见的比较多。 命名评分:3分。中小规模命名规则,不适合自动化环境。 第四名:工程派命名 先分测试库uat-业务名-编号、预上线库puat-业务名-编号、生产库prod-业务名-编号 这个命名有点Oracle教课书的感觉,估计系统里分区也是/u01之类的。 评份:4 分+ ,多给一分怕骄傲。就这样吧。 第五名:有规范的命名 机器的命名,原业务名 + IP(点用下划线替代)+ 机房简写,如:userdb_192_168_11_100_cs prod.系统类型.机房.ip,如:prod.v.cs.192.168.11.100 其中V表示虚机。 机房+IP,如:cs19216811100 使用IP地址做服务器的命名,有多个IP使用重要的IP命名。 在终端提示上也可以显示IP提示,这一块形式也比较多。 评分:5分。推荐 整体上来说这种命名结构属于比较严禁的结构,从命名上基本很容易判断这台机器是做什么的。 其它 Tips: 机器命名,其实没有好坏之分,原则上让在CMDB及监控系统里容易标识出来即可。 对于登录系统,也可以考虑利用/etc/motd 把该机器上跑的业务显示出来。 同时可以利用登录执行相关文件如:/etc/profile 把系统里的关键东西显示出来,如:当前该机器运行几个MySQL,端口号是什么、当前内存使用情况、当前磁盘使用情况 原文完 对于上面知书堂老师给出的命名方案最后一种,也是他认为最好的,但是我个人还是感觉有一些问题,例如首先从这台机器看不出是属于哪个团队的,这对于有多个团队的公司来说,不方便,也看不出角色等等,而对于 IP 地址很多时候我个人是不关心的,我只需要能登录到这台机器,并且无论研发运维都很好记即可,如果我已经连 IP 地址都记得了,那么我还记其他的干嘛,直接用 IP 登陆不就好了吗? ...

February 24, 2019 · 1 分钟 · Bridge Li

MySQL sort 分页重复数据(转载)

前两天在写一个东西的时候,测试的同学说发现一个问题,排序分页,第二页和第一页有重复数据,当时我看了一下,确实有这个问题,然后就想到几年前我就曾经遇到过这个问题,淘宝数据库内核月报上也做了说明,所以这个时候就体现出了老程序员的价值:踩过的坑多,坑坑相连也就都成了平地,考虑到很多人不知道这个问题,所以把原文转载过来,以期能够让更多的人看到,原文如下: 背景 6.5 号,小编在 Aliyun 的论坛中发现一位开发者提的一个问题,说 RDS 发现了一个超级大 BUG,吓的小编一身冷汗 = =!! 赶紧来看看,背景是一个 RDS 用户创建了一张表,在一个都是 NULL 值的非索引字段上进行了排序并分页,用户发现第二页和第一页的数据有重复,然后以为是 NULL 值的问题,把这个字段都更新成相同的值,发现问题照旧。详细的信息可以登录阿里云的官方论坛查看。 小编进行了尝试,确实如此,并且 5.5 的版本和 5.6 的版本行为不一致,所以,必须要查明原因。 原因调查 在 MySQL 5.6 的版本上,优化器在遇到 order by limit 语句的时候,做了一个优化,即使用了 priority queue。参考伪代码: while (get_next_sortkey()) { if (using priority queue) push sort key into queue else{ if (no free space in sort_keys buffers) { sort sort_keys buffer; dump sorted sequence to 'tempfile'; dump BUFFPEK describing sequence location into 'buffpek_pointers'; } put sort key into 'sort_keys'; } } if (sort_keys has some elements && dumped at least once) sort-dump-dump as above; else don't sort, leave sort_keys array to be sorted by caller 使用 priority queue 的目的,就是在不能使用索引有序性的时候,如果要排序,并且使用了limit n,那么只需要在排序的过程中,保留 n 条记录即可,这样虽然不能解决所有记录都需要排序的开销,但是只需要 sort buffer 少量的内存就可以完成排序。 之所以 5.6 出现了第二页数据重复的问题,是因为 priority queue 使用了堆排序的排序方法,而堆排序是一个不稳定的排序方法,也就是相同的值可能排序出来的结果和读出来的数据顺序不一致。 5.5 没有这个优化,所以也就不会出现这个问题。 ...

January 26, 2019 · 1 分钟 · Bridge Li

Maven 打包 Excel 文件损坏

前几天在项目中遇到一个小问题,有一个 Excel 文件放在 classpath 下,通过流下载下来,本地测试的时候一点问题都没,但是部署到测试环境却不行了,说文件已损坏,然后打不开,简单代码如下: @RequestMapping(value = "/export", method = RequestMethod.GET) public void export(HttpServletResponse response) { ServletOutputStream servletOutputStream = null; String filename = "template.xlsx"; InputStream inputStream = ExcelHandleController.class.getClassLoader().getResourceAsStream(filename); try { byte[] b = new byte[inputStream.available()]; inputStream.read(b); response.setCharacterEncoding(StandardCharsets.UTF_8.name()); response.setHeader("Content-Disposition", "attachment;filename=" + filename); response.setContentType("application/octet-stream;charset=UTF-8"); //获取响应报文输出流对象 servletOutputStream = response.getOutputStream(); //输出 servletOutputStream.write(b); } catch (IOException e) { logger.error("文件下载出错", e); } finally { if (null != inputStream) { try { inputStream.close(); } catch (IOException e) { logger.error("文件下载出错", e); } } if (null != servletOutputStream) { try { servletOutputStream.flush(); servletOutputStream.close(); } catch (IOException e) { logger.error("文件下载出错", e); } } } } 当时就感觉这代码很简单啊,没什么问题的,但是下载下来就是不对,想到是不是测试环境有什么问题,查了一下没什么特殊之处,最后发现 Git 上的代码中的文件并没有什么问题,但是打成 war 包解压之后这文件本身就已经损坏了,而且比对了一下两个文件,发现 war 包之中的文件变大了,搜了一下资料,才知道原来 maven 打包会对一些文件转码,这个过程中会损坏一些文件,所以打不开,网上的资料也挺多,最简单的就是增加一个 plugin,不让该文件转码,代码如下: ...

January 13, 2019 · 2 分钟 · Bridge Li

Java 学习之路

前几天刷微博,看到博主 @Java大本营 发了一个图片,总结 Java 一些常见的知识点,感觉挺好,整理成文字版,发在我的个人博客,作为一个大家学习复习的文档,也欢迎有人在评论中留下各种参考资料,一下是正文。 一、基础篇 JVM ①. JVM 内存结构 堆、栈、方法区、直接内存、堆和栈的区别 ②. Java 内存模型 内存可见性、重排序、顺序一致性、volatile、锁、final ③. 垃圾回收 内存分配策略、垃圾收集器(G1)、GC 算法、GC 参数、对象存活的判定 ④. JVM 参数及调优 ⑤. Java 对象模型 oop-klass、对象头 ⑥. HotSpot 即时编译器、编译优化 ⑦. 类加载机制 ClassLoader、类加载过程、双亲委派(破坏双亲委派)、模块化(jboss、modules、osgl、jigsaw) ⑧. 虚拟机性能监控与故障处理工具 jps、jstack、jmap、jstat、jconsole、jinfo、jhat、javap、btrace、tprofiler 编译与反编译 javac、javap、jad、CRF Java 基础知识 ①. 阅读源代码 String、Integer、Long、Enum、BigDecimal、ThreadLocal、ClassLoader & URLClassLoader、ArrayList & LinkedList、HashMap & LinkedHashMap & TreeMap & ConcurrentHashMap、HashSet & LinkedHashSet & TreeSet ②. Java 中各种变量的类型 ③. 熟悉 Java String 的使用,熟悉 String 的各种函数 JDK 6 和 JDK 7 中 substring 的原理及区别、replaceFirst、replaceAll、replace 的区别、String 对 “+” 的重载、String.valueOf 和 Integer.toString 的区别、字符串的不可变性 ...

December 31, 2018 · 4 分钟 · Bridge Li

使用 Spring AOP 注意事项

说实话,由于我个人某些基础不是很牢固,所以前一段时间关于 Spring Aop 踩了一个坑,其实很简单,今天就记录一下,先说结论: 不能被 Spring AOP 增强的方法: 基于接口的动态代理:除 public 外的其它所有的方法,此外 public static 也不能被增强 基于 CGLib 的动态代理:private、static、final 的方法,也就是只有 public 和 protected 可以,但是要注意切入点语法的配置 测试用例如下,pom 文件: <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>1.7.7</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>4.3.11.RELEASE</version> <scope>test</scope> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.8.10</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>4.3.11.RELEASE</version> </dependency> 配置文件就比较简单了: <?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:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd"> <context:annotation-config/> <context:component-scan base-package="cn.bridgeli"/> <aop:aspectj-autoproxy/> </beans> 切面类: package cn.bridgeli.demo.aop; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.springframework.stereotype.Component; @Aspect @Component public class LogInterceptor { // @Pointcut("execution(* * com.bjsxt.service..*.add(..))") @Pointcut("execution(* cn.bridgeli.demo.service..*.*(..))") public void myMethod() { } @Before("myMethod()") public void before() { System.out.println("method before"); } @Around("myMethod()") public void aroundMethod(ProceedingJoinPoint pjp) throws Throwable { System.out.println("method around start"); pjp.proceed(); System.out.println("method around end"); } } 测试类: ...

November 25, 2018 · 2 分钟 · Bridge Li

【转载】Redis 分布式锁进化史

按:系统架构经过多年演进,现在越来越多的系统采用微服务架构,而说到微服务架构必然牵涉到分布式,以前单体应用加锁是很简单的,但现在分布式系统下加锁就比较难了,我之前曾简单写过一篇文章,关于分布式锁的实现,但有一次发现实现的分布式锁是有问题的,因为出问题的概率很低,所以当时也没在意,前几天和朋友聊这个问题,想起来看过一篇文章,写的不错,今天特转载过来,希望能让更多的人看到,同时也加深一下记忆。原文链接是:http://tech.dianwoda.com/2018/04/11/redisfen-bu-shi-suo-jin-hua-shi/ 以下为原文: 近两年来微服务变得越来越热门,越来越多的应用部署在分布式环境中,在分布式环境中,数据一致性是一直以来需要关注并且去解决的问题,分布式锁也就成为了一种广泛使用的技术,常用的分布式实现方式为Redis,Zookeeper,其中基于Redis的分布式锁的使用更加广泛。 但是在工作和网络上看到过各个版本的Redis分布式锁实现,每种实现都有一些不严谨的地方,甚至有可能是错误的实现,包括在代码中,如果不能正确的使用分布式锁,可能造成严重的生产环境故障,本文主要对目前遇到的各种分布式锁以及其缺陷做了一个整理,并对如何选择合适的Redis分布式锁给出建议。 一. 各个版本的Redis分布式锁 V1.0 tryLock() { SETNX Key 1 EXPIRE Key Seconds } release() { DELETE Key } 这个版本应该是最简单的版本,也是出现频率很高的一个版本,首先给锁加一个过期时间操作是为了避免应用在服务重启或者异常导致锁无法释放后,不会出现锁一直无法被释放的情况。 这个方案的一个问题在于每次提交一个Redis请求,如果执行完第一条命令后应用异常或者重启,锁将无法过期,一种改善方案就是使用Lua脚本(包含SETNX和EXPIRE两条命令),但是如果Redis仅执行了一条命令后crash或者发生主从切换,依然会出现锁没有过期时间,最终导致无法释放。 另外一个问题在于,很多同学在释放分布式锁的过程中,无论锁是否获取成功,都在finally中释放锁,这样是一个锁的错误使用,这个问题将在后续的V3.0版本中解决。 针对锁无法释放问题的一个解决方案基于GETSET命令来实现 V1.1 基于GETSET tryLock() { NewExpireTime = CurrentTimestamp + ExpireSeconds if (SETNX Key NewExpireTime Seconds) { oldExpireTime = GET(Key) if (oldExpireTime < CurrentTimestamp) { NewExpireTime = CurrentTimestamp+ExpireSeconds CurrentExpireTime = GETSET(Key,NewExpireTime) if (CurrentExpireTime == oldExpireTime) { return 1; } else { return 0; } } } } release() { DELETE Key } 思路: ...

October 14, 2018 · 1 分钟 · Bridge Li