LeeQingShui's Blog

  • 标签

  • 分类

  • 归档

  • 关于

(二)Redis 事件

发表于 2019-04-11 | 更新于 2022-12-28 | 分类于 NoSQL
本文字数: 3.2k | 阅读时长 ≈ 5 分钟

序言

  Redis 服务器是一个事件驱动程序,服务器需要处理以下两类事件:

  • 文件事件:Redis 服务器通过套接字(Socket)与客户端 (或其他 Redis 服务器)进行连接,文件事件就是服务器对套接字操作的抽象。服务器与客户端的通信会产生相应的文件事件,而服务器则通过监听并处理这些事件来完成一系列网络通信操作。
  • 时间事件:Redis 服务器中的一些操作需要在给定的时间点执行,而时间事件就是服务器低这类定时操作的抽象。
阅读全文 »

(一)初识 Redis

发表于 2019-04-10 | 更新于 2024-02-08 | 分类于 NoSQL
本文字数: 12k | 阅读时长 ≈ 17 分钟

简介

  官方文档:Redis is an open source (BSD licensed), in-memory data structure store, used as a database, cache and message broker. It supports data structures such as strings, hashes, lists, sets, sorted sets with range queries, bitmaps, hyperloglogs, geospatial indexes with radius queries and streams. Redis has built-in replication, Lua scripting, LRU eviction, transactions and different levels of on-disk persistence, and provides high availability via Redis Sentinel and automatic partitioning with Redis Cluster.

  翻译:Redis 是 一个开源(BSD 许可),内存数据结构存储,用作数据库,缓存和消息代理。它支持数据结构,如字符串,散列,列表,集合,带有范围查询的排序集,位图,超级日志,具有半径查询和流的地理空间索引。

  Redis 具有内置复制,Lua 脚本,LRU 回收,事务和不同级别的磁盘持久性,同时通过 Redis Sentinel 提供高可用性,通过 Redis Cluster 提供自动分区。

阅读全文 »

NoSQL 入门介绍

发表于 2019-04-09 | 更新于 2022-12-28 | 分类于 NoSQL
本文字数: 2k | 阅读时长 ≈ 3 分钟

序言

  对互联网中的数据而言,一般都会存储在关系型数据库中,将数据解耦排列的整整齐齐,看着会很舒服,但若碰上了海量的并发数据,程序又没处理好,用户用起来可能就不太舒服了,用户不舒服老板就不舒服,这个时候就要拿程序员祭天了——产品没优化好今天不准下班!
  关系型数据库为什么不太适合处理海量的并发数据呢?这是因为它存在了一些限制:

  • 性能瓶颈:磁盘 IO 性能低下——数据查询相对较慢
  • 扩展瓶颈:数据关系复杂,扩展性差——不便于大规模集群

  那么,我们可能预期希望有一个东西能解决这些问题:

  • 降低磁盘 IO 次数,越低越好——用内存存储
  • 去除数据间关系,越简单越好——特殊的数据结构适配数据

  这个东西就是 NoSQL 了。

阅读全文 »

会话跟踪技术

发表于 2019-04-08 | 更新于 2022-10-21 | 分类于 信息安全
本文字数: 7.7k | 阅读时长 ≈ 11 分钟

序言

  HTTP 是无状态协议,它不对之前发生过的请求和响应的状态进行管理。也就是说,无法根据之前的状态进行本次的请求管理,服务器不知道用户上一次做了什么,这严重阻碍了交互式 Web 应用程序的实现。
  在典型的网上购物场景中,用户浏览了几个页面买了件衣服。最后结帐时,由于 HTTP 的无状态性,不通过额外的手段,服务器并不知道用户到底买了什么,
  不可否认,无状态协议有它的优点,由于不必保存状态,自然可以减少服务器的压力。
  但是,用户该如何在网站上购物呢?总不可能辛辛苦苦挑了件衣服给前台小姐姐(第一次请求),出去接个电话的功夫回来后准备结账(第二次请求),可前台小姐姐却说:小哥哥你谁啊?
  为此,引入了会话跟踪技术。

阅读全文 »

Servlet 复习笔记

发表于 2019-04-06 | 更新于 2022-06-21 | 分类于 Java 基础
本文字数: 10k | 阅读时长 ≈ 15 分钟

什么是 Servlet?

  Servlet(Server Applet),全称 Java Servlet,未有中文译文。是用 Java 编写的服务器端程序。其主要功能在于交互式地浏览和修改数据,生成动态Web内容。狭义的 Servlet 是指 Java 语言实现的一个接口,广义的 Servlet 是指任何实现了这个 Servlet 接口的类),一般情况下,人们将 Servlet 理解为后者

  简单来讲,Servlet就是运行在服务端的小程序,用来将客户端发送过来的 HTTP 请求解析封装进相关的Servlet对象中,当开发者对该对象的数据进行处理后又封装到另一个Servlet对象中,转换成 HTTP 响应返回给客户端。

阅读全文 »

设计模式之模版方法(Template Method)模式

发表于 2019-03-29 | 更新于 2023-06-18 | 分类于 设计模式
本文字数: 3.1k | 阅读时长 ≈ 4 分钟

序言——无纸化系统实现办证流程

  为了适应新时代,国家大力推广,支持各大中小企业打造“无纸化系统”。

  企业使用“无纸化办公”系统,将许多传统工作从线下转为线上,以电子文件替代有纸化文件,不仅能降低企业成本,而且对使用者友好,对自然环境友好,一举多得!

  在以前,人们若想办理证件,大致需要经历以下流程:

  • ① 办证人去办事处(A 地点)领取纸质文件,填写对应内容
  • ② 办证人去对应审核中心(B 地点),提交填写完成的文件
  • ③ 办证人等待审核中心审核:
    • 审核中心审核不通过,从 ① 开始重新走流程
    • 审核中心审核通过,给文件盖章后发放给办证人,进入下一步骤
  • ④ 办证人去证件办理中心(C 地点),提交盖章完毕的文件,办证人等待证件办理完成
  • ⑤ 证件办理中心通知证件已办理完成,发短信告知办证人领取
  • ⑥ 办证人去证件办理中心领取证件

  在此流程下办证,人们不仅要在各个地方来回跑,十分花费时间,而且,若文件有一个地方填错了,还得重新走一遍流程。。。

  而现在,将办理证件的流程信息化,在对应电子化系统中进行操作,那么,一切都变得简单了:

  • ① 办证人在系统中,根据办理证件类型,下载所需填写的电子文件模版
  • ② 办证人根据模版将电子文件填写完成后,上传到办证系统中
  • ③ 办证人等待办证系统审核中心工作人员审核:
    • 审核中心审核不通过,从 ① 开始重新填写上传对应文件走流程
    • 审核中心审核通过,文件自动进行电子盖章,自动进入下一步骤
  • ④ 系统判断所有文件电子盖章无误,告知证件办理中心办理证件
  • ⑤ 证件办理中心通知证件已办理完成,发短信告知办证人领取
  • ⑥ 办证人去证件办理中心领取证件

  通过“无纸化办公”系统,人们只需要在网上动动小手,当流程办理完成后,直接去证件办理中心领取,只需要跑一次,大大节约了时间(另外如果系统支持快递送件,那都不用跑了)。

设计与实现

  对于上面谈论的电子化办证流程,其实是需要程序员根据标准化的文档流程进行抽象设计,最终进行代码实现的,针对这个过程,下面我们分析一下。

  首先,程序员需要开发一个审批流系统,按需求规则去定义一个审批流模版,针对办证的各个步骤,转换为审批流当中的一个个节点。

  另外,审批流系统需要对接其他第三方的系统,因为具体办证的业务是政务机构提供的,而审批流系统并不负责此业务。

  分析下来,那么代码实现上,主要时间花费在两块:

  • ① 审批流系统设计
  • ② 对接第三方系统

  本文不谈论第一点,因为市面上现有的审批流系统非常多,具体根据需要去调研实现即可,那么,第二点如何改如何考量呢?

  由于办证相关业务是由政务机构提供,对审批流系统开发人而言,必须按其他机构提供的接口文档进行对接工作,因为需要连调测试工作,是比较耗费时间的。

流程设计

  当然,我们在进行接口文档对接前,首先应该是梳理下大致的流程步骤,那么,我们画一个流程图出来吧:

办证流程图

  从流程图可知,对接第三方系统,大致需要以下接口:

  • 申请接口
  • 文件上传接口
  • 进度状态查询接口
  • 补充资料相关接口(二次申请接口)

代码实现探秘

  在审批流系统已完成,但是还未对接第三方系统的情况下,我们先思考一下:关于对接第三方系统的代码,应该写在哪里?
  直接嵌入到各个流程节点当中嘛?

  当然不是,我们应当是定义出一个单独的接口服务,提供给审批流调用。

  那么,我们应该会定义一个这样的接口,然后使用相应的实现类去实现它:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public interface IThirdCardService {

/**
* 申请接口
*
* @param applyParams
*/
void apply(Object applyParams);

/**
* 文件上传接口
*
* @param uploadParams
*/
void upload(Object uploadParams);

/**
* 状态查询接口
*
* @param queryStatusParams
*/
void queryStatus(Object queryStatusParams);

/**
* 二次申请接口
*
* @param reApplyParams
*/
void reApply(Object reApplyParams);

}

  我们这么做会有什么问题吗?

  现在来看,确实是没啥问题的,我们现在对接了 A 办证系统,而 A 办证系统支持办理 A1、A2、A3 等种类的证件。

  但是,客户之后反馈要办只有 B 办证系统支持的 B1、B2、B3 类证件,而 A 办证系统并不支持。

  产品经理说:这肯定不行呀,客户的需求我们得满足呀,开发老弟,麻烦再去接入 B 办证系统吧!

  开发同学:……

  世界上没有两片完全相同的树叶,对 A、B 办证系统同样如此,虽然两个系统大体办证流程差不多,但部分地方还是会存在差异,比如说:

  • ① 接口地址配置需要变化:A、B 办证系统的接口地址一定是不同的
  • ② 接口方法需要变化:B 系统特别增加了文件上传前校验的接口

  开发同学分析了下需求,既然这样,我再写一个原接口的实现类,然后实现一下,这不就是策略模式吗,简单呀!

  哎,这代码怎么看起来不对劲啊?哦,少了个校验接口,emmm,怎么办呢?

  我再去接口里面加个文件校验方法吧。

  嗯?怎么又报错了?哦哦,A 办证系统的实现类没有实现该方法,我再去 A 办证系统的实现类实现下。

  这代码看起来怎么怪怪的,而且,我发现有些代码重复了。

错误的实现

  从前面知道了,现在的代码是有一定问题的:

  • 无法应对变化:办证流程新增一个步骤就需要修改原先每个相关类的代码(接口及实现),不符合开闭原则
  • A 系统并不需要校验文件接口,但是由于接口定义的抽象方法,还是必须在其所有实现类中进行实现,哪怕用不到,与业务冲突

  显而易见,策略模式无法应对这种情况,策略模式关注的是相同行为的抽象,这种情况根本就不应该使用策略模式。

  不过没关系,我们可以使用另一种设计模式——模版方法模式。

优化后的代码

  现在,即使再新增一个 C 办证系统(提供 C1、C2 证件办理),我们也只需要去新增一个模版的实现类去继承模版类即可。

  通过继承,子类可以共用相同的代码,也可以各自实现不同的代码,还可以根据需要,去实现钩子函数(某些相同或不同的代码)。

简介

  模板方法模式是一种行为设计模式, 它在超类中定义了一个算法的框架,使得子类 可以在不改变一个算法的结构的情况下,重定义该算法的某些特定步骤。

优与劣

优点

  • 钩子函数可以应对变化的行为,子类按业务需要确定是否重写
  • 利用模板将不变的行为(或属性)抽离到父类中,提高了代码的复用性,符合开闭原则
  • 将不同的算法逻辑分离到不同的子类中,通过对子类的扩展增加新的行为,提高了代码的可扩展性

缺点

  • 钩子函数破坏了里氏代换原则
  • 由于继承的缺点,若在父类添加新的抽象方法,则所有子类都需再实现一遍
  • 每一个抽象类都需要一个子类实现,这将导致类数量增加,而类数量的增加,将间接地增加了系统实现的复杂度

业务场景

  • 一次性实现一个算法的不变的部分,并将可变的行为留给子类来实现
  • 各子类中公共的行为需要被提取集中到一个公共父类中,以避免代码重复
  • 子类需要应对扩展,可在模板父类中定义只在特定点调用的钩子函数, 以兼容特殊的业务

参考

  • 谭勇德. 设计模式就该这样学 [M]. 电子工业出版社,2020

文章信息

时间 说明
2019-03-29 初版
2022-07-19 完全重构

设计模式之策略(Strategy)模式

发表于 2019-03-25 | 更新于 2025-02-10 | 分类于 设计模式
本文字数: 3.2k | 阅读时长 ≈ 5 分钟

序言——缺陷的业务代码

  假如现在你需要做一个针对第三方接口的监控预警功能(配置的指标超出阈值进行短信邮件预警),需要计算应用集成的第三方接口的各种指标,比如:

  • 接口费用
  • 接口调用量
  • 接口请求成功率
  • 接口请求失败次数
  • 接口剩余使用天数
  • 接口响应时间超时次数

  由于这些指标分别存储在 MySQL、Redis、ElasticSearch,因此计算的逻辑并不相同,所以你可能会写出下面的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// 指标枚举
public enum IndexStatisticsTypeEnum {
FAILED_QUANTITY("失败次数"),
SUCCESS_RATE("成功率"),
RESPONSE_TIMEOUT_QUANTITY("接口响应时间超时次数"),
TIME_ON_TIME_FLUCTUATION_RATE("接口环比波动比率"),
INVOKE_QUANTITY("接口调用量"),
CHARGE_COST("接口费用"),
;

private final String desc;

IndexStatisticsTypeEnum(String desc) {
this.desc = desc;
}

public String getDesc() {
return desc;
}

public static boolean isExist(int ordinal) {
return Arrays.stream(IndexStatisticsTypeEnum.values()).anyMatch(x -> x.ordinal() == ordinal);
}

public static boolean isNotExist(int ordinal) {
return !isExist(ordinal);
}

public static IndexStatisticsTypeEnum getEnumByValue(int ordinal) {
return Arrays.stream(IndexStatisticsTypeEnum.values()).filter(x -> x.ordinal() == ordinal).findFirst().orElse(null);
}

public static String getRoutedDesc(int ordinal) {
IndexStatisticsTypeEnum em = Arrays.stream(IndexStatisticsTypeEnum.values()).filter(x -> x.ordinal() == ordinal).findFirst().orElse(null);
return em == null ? null : em.desc;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public getCalculateValue(...){
Double calculateValue = 0d;
Integer indexType = index.getType();
switch (IndexStatisticsTypeEnum.getEnumByValue(indexType) {
case FAILED_QUANTITY:
calculateValue = calculateValueRequestFailedQuantity(Object needParams);
break;
case SUCCESS_RATE:
calculateValue = calculateValueRequestSuccessRateIndexHandler(Object needParams);
break;
case RESPONSE_TIMEOUT_QUANTITY:
calculateValue = calculateValueRequestResponseTimeoutQuantityIndexHandler(Object needParams);
break;
case TIME_ON_TIME_FLUCTUATION_RATE:
calculateValue = calculateValueTimeOnTimeFluctuationRateIndexHandler(Object needParams);
break;
case COMPANY_EXPIRE:
calculateValue = calculateValueCompanyExpireIndexHandler(Object needParams);
break;
case INVOKE_QUANTITY:
calculateValue = calculateValueInvokeQuantityIndexHandler(Object needParams);
break;
case CHARGE_COST:
calculateValue = calculateValueChargeCost(Object needParams);
break;
default:
break;
}
return calculateValue;
}

  由于各个指标的计算方式并不相同,所以需要分别写代码进行计算,抽离出了各种方法。

  从代码设计可以发现:

  • 代码复杂性较高:switch (或 if-else) 的分支很多且业务代码很长,即使抽离为单独的方法,但一个类中还是存在了大量的代码
  • 设计上不符合开闭原则(对修改关闭,对扩展开放):未来或许要新增、删除或修改指标。若在一个函数中来回修改无疑是件恐怖的事情,你不知道产品和客户什么想法,可能今天要你删个指标,明天又要去你加回来

  为了降低代码复杂性,使设计解耦,让程序看起来简洁且能应对未来的变化,可以使用策略模式来优化原有代码,最终改造后代码可以变成这样:

1
2
3
4
5
6
7
// 优化后的代码
public getCalculateValue(...){
Integer indexType = index.getType();
IndexStatisticsStrategy routedStrategy = indexStatisticsStrategyContext.getRoutedStrategy(indexType);
Double calculateValue = routedStrategy.calculate(indexType);
return calculateValue;
}

  改造后的代码看起来是不是非常简洁?

  那么,策略模式是什么,又如何使用呢?

  下面跟随本文来了解下吧!

阅读全文 »

设计模式之单例(Single)模式

发表于 2019-03-23 | 更新于 2025-01-17 | 分类于 设计模式
本文字数: 3.2k | 阅读时长 ≈ 5 分钟

序言

  在程序中,有一些对象其实我们只需要一个,比如说:线程池、缓存、处理偏好设置的对象等等。
  事实上,这类对象应该只创建一个实例,若制造出多个实例,就会导致许多问题发生,如程序行为异常,资源使用过量或返回的结果不一致。
  利用静态类变量、静态方法和适当的修饰符,不是也可以达到这种效果嘛?
  确实如此,但其有个缺点,那就是必须在程序一开始就创建好对象,万一这个对象非常耗费资源,而程序又在这次的执行过程中一直没用到它,就会浪费资源。
  虽然鱼和熊掌不可兼得,但我们的单例模式却可以分别实现“鱼”或“熊掌”。

阅读全文 »

(五)Spring MVC

发表于 2019-03-22 | 更新于 2023-07-15 | 分类于 Java Web 框架
本文字数: 21k | 阅读时长 ≈ 29 分钟

简介

  Spring Web MVC 是基于 Servlet API 构建的原始 Web 框架,从一开始就包含在Spring Framework中。正式名称“Spring Web MVC”,来自其源模块(spring-webmvc)的名称,但它通常被称为“Spring MVC”。

阅读全文 »

Java 之 JDK 8 新特性

发表于 2019-03-19 | 更新于 2023-06-19 | 分类于 Java 基础
本文字数: 8.6k | 阅读时长 ≈ 12 分钟

  在 JDK8 中,新增了许多新特性,可以让开发人员更优雅的编写代码,那么下面跟随本文来了解一下吧!

阅读全文 »

1…8910…15
LeeQingShui

LeeQingShui

144 日志
16 分类
68 标签
RSS
© 2018 – 2025 LeeQingShui | 站点总字数: 846k
赣 ICP 备 2022002212 号
本站已运行
本站总访问量 次 | 本站访客 人次
0%