0%

分布式ID解决方案

可供选择的分布式ID方案

  1. UUID
    优点:
    - 简单,代码方便
    - 生成ID性能非常好,基本不会有性能问题
    - 全球唯一,永不重复
    缺点:
    - 没有排序,无法保证趋势递增
    - UUID存的是字符串,查询效率比较低
    - UUID 存储空间以及数据传输数据量大

  2. 基于数据库的自增ID

  3. 基于 Redis 的自增ID
    使用Redis的原子操作 INCR和INCRBY来实现
    比较适合使用Redis来生成每天从0开始的流水号。比如订单号=日期+自增长号
    优点:
    - 不依赖于数据库,灵活方便,且性能优于数据库
    - 数字ID天然排序,对分页或者排序的结果有帮助

  4. 基于MongoDB的ObjectId

  5. Snowflake
    twitter 开发的一套全局唯一ID生成服务 Snowflake

    1. 41位的时间序列(精确到毫秒,41位的长度可以使用69年)
    2. 10位的机器标识(10为的长度最多支持1024个节点部署)
    3. 12位的计数序号
  6. 类snowflake算法

  7. 百度的uid-generator

  8. 美团Leaf

本人比较偏向于基于Redis的方式生成自增ID。现在的项目基本都会引入Reids做缓存中间件,因此不会因为额外的组件。生产环境中的Reids,基本上都基于主从,或者集群可以防止单点故障。
同时代码实现也比较简单。比如ID = YYYYMMDD + 自增长号。后台使用两个字段值来作为ID,排序时先根据时间,再根据自增长ID进行排序。

MessageDigest 使用注意,并发问题

Message Digest Algorithm MD5(中文名为消息摘要算法第五版)为计算机安全领域广泛使用的一种散列函数,用以提供消息的完整性保护。该算法的文件号为RFC 1321(R.Rivest,MIT Laboratory for Computer Science and RSA Data Security Inc. April 1992)。

产生错误原因

因为这些常用的工具类之前都写好了,用的时候没有多想就直接Copy过来了,请求是并发的,刚刚开始的时候,并发请求较少(1-2)个,没有出现什么问题,后来请求3-4个同时发的时候,服务端偶尔抛出MD5值验证错误的信息,后来翻看了MD5工具类之后才发现,原来这个类写的方式并不支持并发,MessageDigest被声明为成员变量,多线程环境下会共享同一个MessageDigest对象

MessageDigest源码

1
2
3
4
5
6
7
8
9
/**
* Updates the digest using the specified array of bytes.
*
* @param input the array of bytes.
*/
public void update(byte[] input) {
engineUpdate(input, 0, input.length);
state = IN_PROGRESS;
}

调用了engineUpdate方法,此方法进行一个更新操作。

1
Updates the digest using the specified array of bytes, starting at the specified offset.

然后state属性的状态就被改变了,表明当前计算正在处理过程中。

state默认属性

1
private int state = INITIAL;

然后需要调用MessageDigest.digest()方法计算哈希值

1
2
3
4
5
6
7
8
9
10
11
12
/**
* Completes the hash computation by performing final operations
* such as padding. The digest is reset after this call is made.
*
* @return the array of bytes for the resulting hash value.
*/
public byte[] digest() {
/* Resetting is the responsibility of implementors. */
byte[] result = engineDigest();
state = INITIAL;
return result;
}

到这里已经完成了MD5值的计算,state属性恢复初始状态,如果想要重用MessageDigest对象,还需要调用MessageDigest.reset()方法进行重置,以免这次计算数据会对下一次的计算造成影响,从而导致计算结果错误。

而我所遇到的问题就是,在MessageDigest在多线程的环境下,Thread-1的计算还没有完成的情况下,Thread-2又开始使用该MessageDigest对象进行下一次的计算,Thread-2修改了MessageDigest的状态,Thread-1使用被修改过后的MessageDigest进行计算,从而导致了计算结果错误。

解决方法

pom.xml

1
2
3
4
5
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.10</version>
</dependency>
1
2
3
byte[] md5Value = DigestUtils.md5(date);

String md5Value = DigestUtils.md5Hex(date);

反射

反射的入口是名称为Class的类

代理

静态代理

动态代理

  1. Java SDK
  2. cglib
1
2
3
4
5
6
public static void main(String[] args) {
IService realService = new RealService();
IService proxyService = (IService) Proxy.newProxyInstance(IService.class.getClassLoader(),
new Class<?>[] { IService.class }, new SimpleInvocationHandler(realService));
proxyService.sayHello();
}

CGLIB 与 Java SDK 动态代理的区别

Java SDK 动态代理局限在于,他只能为接口创建代理,返回的代理对象也只能转换到某个接口类型,如果一个类没有接口,或者希望代理非接口中定义的方法,那就没有办法了。
Java SDK 代理面向的是一组接口,它为这些接口创建了一个实现类,接口的具体实现逻辑是通过自定义的InvocationHandler实现的,这个实现是自定义的,也就是说,其背后都不一定有真正被代理的对象,也可能多个实际对象,根据情况动态选择。cglib代理面向的是一个具体的类,它动态创建了一个新类,也继承了改类,重写了其方法。
从代理的角度看,Java SDK代理的是对象,需要先有一个实际对象,自定义的InvocationHandler引用该对象,然后创建一个代理类和代理对象,客户端访问的是代理对象,代理对象最后再代用实际对象的方法,cglib代理的是类,创建的对象只有一个。

动态代理的优点

使用动态代理可以编写通用的代理逻辑,用于各种类型的被代理对象,而不用每个被代理的类型都创建一个静态代理类。

Ribbon 配置

Spring Cloud Feign 的客户端负载均衡是通过 Spring Cloud Ribbon 实现的,所以可以直接通过配置Ribbon客户端的方式来自定义各个服务客户端调用的参数。

全局

1
2
ribbon.ConnectTimeout=500
ribbon.ReadTimeout=5000

单位毫秒

指定服务配置

1
<client>.ribbon.key = value

== @FeignClient 中的服务名称

Ribbon 重试机制

1
SERVICE.ribbon.MaxAutoRetries=1

重试策略先尝试访问首选实例一次,失败后才更换实例方法,更换实例访问的次数通过 ribbon.MaxAutoRetriesNextServer 参数设置。
MaxAutoRetries 单个实例的最大尝试次数,MaxAutoRetriesNextServer更换实例尝试的最大次数

TIP

Ribbon的超时与Hystrix的是超时是两个概念,Hystrix 的是超时时间需要大于Ribbon的超时时间,否则Hystrix命令超时后,该命令直接熔断,重试机制也就没有意义。

Hystrix 配置

Hystrix 实现服务降级

全局配置

1
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds = 5000

单位毫秒

1
2
feign.hystrix.enabled = false
hystrix.command.defauld.exectuion.timeout.enabled=false

关闭Feign 客户端的 Hystrix 功能

指定命令配置
采用 hystrix.command.<commandKey> 作为前缀

其他配置

请求压缩

1
2
3
4
5
6
7
8
# 开启对请求与相应的GZIP压缩,减少通行过程中的性能损耗
feign.compression.request.enabled=true
feign.compression.response.enaled=true

# 配置内容指定了压缩的请求数据类型,设置请求压缩的大小下限,只有超过这个大小的请求才会对其进行压缩
feign.compression.request.enabled=true
feign.compression.request.mime-types=text/xml,application/xml,application/json
feign.compression.request.min-request-size=2048

日志配置

Spring Cloud Actuator

为Spring Boot 构建的应用提供一系类用于监控的断点。

原生断点

spring-boot-starter-actuator 模块中已经实现的一些原生端点

  • 应用配置类: 获取应用程序中加载的应用配置、环境变量、自动化配置报告等于Spring Boot 应用密切相关的配置类信息。
  • 度量指标类: 获取应用程序运行过程中用于监控的度量指标,比如内存信息、线程池信息、http 请求统计等
  • 操作控制类: 提供了对应用的关闭等操作可供您

Spring Cloud Config

自定义参数

application.yml 文件

1
2
3
app:
name: aa
pwd: 123456

java 文件

1
2
3
4
5
6
7
8
9
10
@Component
public class Test{

@Value("${app.name}")
private String name;

@Value("${app.pwd}")
private String pwd

}

@Value 注解加载属性值的时,可支持两种表达方进行配置

  • PlaceHolder方式,格式为 ${…},大括号内为 PlaceHolder
  • SpEL 表达式,格式为 #{….}, 大括号内为SpEL表达式

参数引用

配置文件中可以通过 PlaceHolder 的方式进行引用。

加载顺序

  1. 命令行中传入的参数
  2. SPRING_APPLICATION_JSON 中的属性,SPRING_APPLICATION_JSON 是以json 格式配置在系统环境中的内容
  3. java:comp/env 中的JNDI属性
  4. Java的系统属性,可以通过System.getProperties()获得的内容。
  5. 操作系统的环境变量
  6. 通过random.* 配置的随机属性。
  7. 位于当前应用jar包之外,针对不同 {profile} 环境的配置文件内容。
  8. 位于当前应用jar包之内,针对不同{profile}环境的配置文件内容。
  9. 位于当前应用jar包之外的application.properties配置内容。
  10. 位于当前应用的jar包之内的application.properties的配置内容。
  11. 在@Configuration 注解修改的类中,通过 @PropertySource 注解定义的属性。
  12. 应用默认属性,使用SpringApplication.setDefauldProperties定义的内容。

优先级按上面的的顺序由高到底,数字越小优先级越高。
在第7项和第9项都是从应用 jar 包之外读取配置文件,所以,实现外部化配置的原理就是从此切入,为其指定外部配置文件的加载位置来取代jar包内的配置内容。

TODO

写一段测试脚本,显示 配置内容的加载顺序,覆盖顺序,以及所有配置信息的内容。

Spring Cloud Zipkin

Zipkin 集成

zipkin 项目

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
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>io.zipkin.java</groupId>
<artifactId>zipkin-server</artifactId>
</dependency>

<dependency>
<groupId>io.zipkin.java</groupId>
<artifactId>zipkin-autoconfigure-ui</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-sleuth-zipkin-stream</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-stream-rabbit</artifactId>
</dependency>

</dependencies>

普通程序项目

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
38
39
40
41
42
43
44
45
46
47
48
 <dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-sleuth-zipkin</artifactId>
</dependency>

</dependencies>



@SpringBootApplication
@EnableEurekaClient
@EnableDiscoveryClient
@EnableScheduling
public class AdminBootstrap {
public static void main(String[] args) {
try {
new SpringApplicationBuilder(AdminBootstrap.class).web(true).run(args);
} catch (Exception e) {
e.printStackTrace();
}
}
}


spring:
application:
name: trace-2
zipkin:
base-url: http://10.3.185.26:8480
sleuth:
sampler:
percentage: 1.0

Zipkin 集成 RabbitMQ

zipkin 项目

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
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>io.zipkin.java</groupId>
<artifactId>zipkin-server</artifactId>
</dependency>

<dependency>
<groupId>io.zipkin.java</groupId>
<artifactId>zipkin-autoconfigure-ui</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-sleuth-zipkin-stream</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-stream-rabbit</artifactId>
</dependency>

</dependencies>


普通程序项目

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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-ribbon</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>

<!--zipkin链路日志追踪 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-sleuth-zipkin-stream</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-stream-rabbit</artifactId>
</dependency>

</dependencies>


spring:
application:
name: trace-2
# zipkin:
# base-url: http://10.3.185.26:8480
sleuth:
sampler:
percentage: 1.0
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
default-property-inclusion: non_null
rabbitmq:
host: 10.203.105.68
port: 5672
username: test
password: test

遇到的问题