作者: mynewworldyyl
往下看前, 建议完成前面 1 到 12 小节
1. 微服务中 ID 地位
如果说前面小节的功能点是微服务的大脑, 那么全局唯一 ID 则是微服务的神经系统, 没有 ID 这个神经系统, 再强的大脑也白搭, 只有有了这个神经系统, 才能有效协调整个微服务系统的正常工作, 才不会出现神经错乱. 就好像两个或多个人的身份证号码相同, 则依赖于这个身份证号唯一性的系统就无法正常工作 (无法为具有相同身份证号的这些人服务).
JMicro 中, 消息是微服务之间通讯最基本单元, 系统将之命名为 org.jmicro.API.NET.Message, 每个消息由唯一 ID 所标识, 不管系统中有多少个服务, 服务之间以多高的 QPS 做交互, 也许上万亿的 QPS, 也不管系统运行多久, 也许是1亿年, 假设这1亿年中总共有万万亿个消息, 那么这万万亿个消息的 ID 都不能重复, 否则这个微服务系统都是不可靠的. 例如, 两个转账消息, 一个转1分钱, 一个传1亿, 如果两个消息 ID 相同, 那么有50%的可能本应收到1亿的账号却收到了1分钱, 而那个本应收到1分钱账号却收到了1亿, 如果是比特币转账, 那后果就凉了.
2. JMicro 中 ID 使用场景
最典型的是服务调用链路跟踪 ([6] JMicro 微服务 - 服务日志监控 ), 从调用发起方开始, 生成一个全局唯一链路 ID, 并且将这个链路 ID 从调用者传给被调用者, 直到最终被调用者返回时, 也将链路 ID 返回, 直到返回给调用发起方. JMIcro 通过将相同的链路 ID 消息归属为一个微服务调用过程, 这个相同 ID 的链路上发生的所有事件及相关统计数据, 都可以通过这个 ID 作为维度做跟踪分析. 比如从调用发起方发送请求到收到响应, 消耗很长时间, 那么通过链路 ID 查看每个消息请求和响应时间, 即可找到问题的结点, 这里的 "结点" 即是 JMicro 服务.
还有就是 JMicro 的 RPC 调用过程中, 为了提高性能, 消息都是异步的, 但是作为 RPC 客户端调用者, 却是同步的方式使用, 比如前面的 ISimpleRpc 调用 hello 服务方法, 返回一个字符串, 是同步返回的. 这个底层异步消息提供上层同步使用, 就是通过请求 ID 实现的. 每个 RPC 请求, 会生成一个全局唯一 ID, 并将这个 ID 传给服务方, 服务方处理完成后, 也返回给调用方, 调用方底层通过这个 ID 识别这个结果应该返回给那个 RPC 方法 (唤醒调用者线程).
3. 应用全局唯一 ID
应用如果需要使用到全局唯一 ID, 也可以和 JMicro 底层一样, 使用相同接口, 如下代码是 JMicro 获取当前链路 ID 代码.
- public static Long lid(){
- JMicroContext c = get();
- Long id = c.getLong(LINKER_ID, null);
- if(id != null) {
- return id;
- }
- ComponentIdServer idGenerator = JMicro.getObjectFactory().get(ComponentIdServer.class);
- if(idGenerator != null) {
- id = idGenerator.getLongId(Linker.class);
- c.setLong(LINKER_ID, id);
- }
- return id;
- }
2 到 6 行检查当前上下文是否有链路 ID, 如果有则直接返回, 否则第 8 行取得 ComponentIdServer 实例, 并通过 ComponentIdServer 实例的相关方法获取 Linker.class 的
ID, 下图为 ComponentIdServer 实例获取 ID 相关方法:
3 种类型 6 个方法, 分别为 int 类型 ID, 获取 1 个 id 和多个 ID 两个方法, 同理 Long 和 String.
取得 ComponentIdServer 实例的另一种方式如下, 通过 @Inject 注解获取:
- @Inject
- private ComponentIdServer idGenerator;
4. 基于 Redis 全局唯一 ID 生成方案
如下代码为基于 Redis 的 lua 脚本, 获取特定 KEY 的 cnt 个 ID, 使用了 Redis 调用 Lua 脚本的原子性.
- public JMicroRedisBaseIdGenerator() {
- StringBuilder sb = new StringBuilder();
- sb.append("local k = KEYS[1];\n");
- sb.append("local cnt = ARGV[1];\n");
- sb.append("local val = tonumber(redis.call('incrby', k, cnt));\n");
- sb.append("return val;\n");
- luaScript = sb.toString();
- }
获取 Int 类型的 ID
- public Integer[] getIntIds(String idKey, int num) {
- Jedis r = pool.getResource();
- try {
- int endId = Integer.parseInt(r.eval(luaScript, 1, idKey,num+"").toString());
- Integer[] ids = new Integer[num];
- int oriId = endId - num;
- for(int i = 0; i < num; i++) {
- ids[i] = oriId+i;
- }
- return ids;
- }finally {
- r.close();
- }
- }
5. 使用 Redis
在 org.jmicro.Redis.RegistRedis 中, 有如下方法实现将 Redis 相关接口实例注册到 IObjectFactory 中
所以我们可以通过 @Inject 将以上实例对象注入到我们的代码中, 如下代码所示:
- @Inject(required=true)
- private JedisPool pool;
到目前为止, 我们在 JMIcro 环境中, 自动拥有了 ZK 及 Redis 环境支持, 后面还会有 MyBatis 相关支持, 用以操作数据库, 如果有兴趣, 可提前通过源码查看 JMIcror 插件式的模块扩展机制. GitHub 链接如下:
- https://github.com/mynewworldyyl/jmicro/tree/master/jmicro.ext/jmicro.mybatis
- https://github.com/mynewworldyyl/jmicro/tree/master/jmicro.redis
- https://github.com/mynewworldyyl/jmicro/tree/master/jmicro.zk
[1] JMicro 微服务 - RPC 体验
[2] JMicro 微服务 - Hello World
[3] JMicro 微服务 - 服务超时, 重试, 重试间隔
[4] JMicro 微服务 - 服务限流
[5] JMicro 微服务 - 熔断降级
[6] JMicro 微服务 - 服务日志监控
[7] JMicro 微服务 - 服务路由, 负载均衡
[8] JMicro 微服务 - JMicro ZKUI
[9] JMicro 微服务 - 发布订阅消息服
[10] JMicro 微服务 - API 网关
[11] JMicro 微服务 - 配置管理
[12] JMicro 微服务 - Zookeeper
来源: https://www.cnblogs.com/jmicro/p/10634612.html