2023年2月

reflect

reflect包
在Go语言的反射机制中,任何接口值都由是一个具体类型和具体类型的值两部分组成的。
在Go语言中反射的相关功能由内置的reflect包提供,任意接口值在反射中都可以理解为由reflect.Type和reflect.Value两部分组成,并且reflect包提供了reflect.TypeOf和reflect.ValueOf两个函数来获取任意对象的Value和Type。

package main

import (
    "fmt"
    "reflect"
)

type Cat struct {
}

func reflectType(x interface{}) {
    val := reflect.TypeOf(x)
    fmt.Println(val)
    fmt.Printf("type:%v kind:%v\n", val.Name(), val.Kind())

}

func reflectValue(x interface{}) {
    val := reflect.ValueOf(x)
    key := val.Kind()

    switch key {
    case reflect.Int64:
        fmt.Println("type is Int64, value is  ", int64(val.Int()))
    case reflect.Float32:
        fmt.Println("type is Float32, value is  ", float32(val.Float()))

    }

}

func main() {

    var a int64 = 1
    reflectType(a)
    var b float32 = 0.1
    reflectType(b)

    var c = Cat{}
    reflectType(c)

    reflectValue(a)
    reflectValue(b)
    reflectValue(c)

}

在reflect包中定义的Kind类型如下:

type Kind uint
const (
    Invalid Kind = iota  // 非法类型
    Bool                 // 布尔型
    Int                  // 有符号整型
    Int8                 // 有符号8位整型
    Int16                // 有符号16位整型
    Int32                // 有符号32位整型
    Int64                // 有符号64位整型
    Uint                 // 无符号整型
    Uint8                // 无符号8位整型
    Uint16               // 无符号16位整型
    Uint32               // 无符号32位整型
    Uint64               // 无符号64位整型
    Uintptr              // 指针
    Float32              // 单精度浮点数
    Float64              // 双精度浮点数
    Complex64            // 64位复数类型
    Complex128           // 128位复数类型
    Array                // 数组
    Chan                 // 通道
    Func                 // 函数
    Interface            // 接口
    Map                  // 映射
    Ptr                  // 指针
    Slice                // 切片
    String               // 字符串
    Struct               // 结构体
    UnsafePointer        // 底层指针
)

浅淡微服务的弊端

上篇文章就表面提到了微服务的优点,但是,微服务架构不是银弹
点我传送到上篇文章(浅谈微服务的优点)

注释:
银弹:
银弹这个词,是来源于欧洲中世纪的传说。看过电影黑夜传说的人肯定知道。说的是狼人这样的怪物,一般的子弹是打不死它的。必须使用银子做的子弹才能杀死它。
后来“银弹”这个词就被用来形容,那些特别有效果、一用就很灵的方法。和走街串巷卖的“神药”,有类比的含义。神药是指一种什么病都能治的药,这明摆着是不可能的事情,肯定是骗子。

分布式锁
在分布式系统中,常常需要协调他们的动作。
如果不同的系统或是同一个系统的不同主机之间共享了一个或一组资源,那么访问这些资源的时候,往往需要互斥来防止彼此干扰来保证一致性,这个时候,便需要使用到分布式锁。

分布式系统
所谓分布式计算机系统,是指由多台分散的计算机,经互连网络的联接而形成的系统,系统的处理和控制功能分布在各个计算机上。分布式计算机系统又简称为分布式系统。

分布式事务
分布式事务是指事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于不同的分布式系统的不同节点之上。
严格意义上的事务实现应该是具备原子性、一致性、隔离性和持久性。

原子性,可以理解为一个事务内的所有操作要么都执行,要么都不执行。

一致性,可以理解为数据是满足完整性约束的,肯定不会出现中间的状态,
比如你账上有400,我账上有100,你给我打200块,此时你账上的钱应该是200,我账上的钱应该是300,不会存在我账上钱加了,你账上钱没扣的中间状态。

隔离性,指的是多个事务并发执行的时候不会互相干扰,即一个事务内部的数据对于其他事务来说是隔离的。

持久性,指的是一个事务完成了之后数据就被永远保存下来,之后的其他操作或故障都不会对事务的结果产生影响。

总结 : 一些更新操作要么都成功,要么都失败。


目录:

  • 成本
  • 隐患以及复杂性
  • 技术栈

成本

1 . 学习/时间成本

微服务的基础设施包括:
CI、CD,限流,熔断,管理协作,分布式技术,
网关,服务监控,日志收集,重复代码
配置中心,负载均衡,发布成本
领域划分和明确
容器相关技术栈等等
也就是说对于服务来说,单个服务变得简单,整体服务变得复杂。
这些菜都端上来,如果没有很好的技术储备,估计是要不消化的。
当一个新人加入团队后,以前的单体式应用很方便于新人学习,只要在代码仓库将服务下载下来,本地启程序跑起来就好,模块与模块之间的调用不用管。
而当服务拆分成微服务之后,对于新人来说,学习成本是非常高的,需要有团队成员讲解这个服务的架构、微服务架构,再一个个的下载下来,解决服务与服务之间的调用问题,才能将服务运行起来。

2 . 金钱成本

不说了,本文句句不提钱,确句句都是钱。

3 . 维护成本

在微服务架构中最理想的模式是每个服务都可以单独运行起来,有自己的业务逻辑、数据库、中间件、机器资源,当业务逻辑改变时,对应功能的开发和部署成本很低。
但随之带来的问题是如何管理微服务拆分带来的多个微服务项目,你可能需要最底层的硬件资源都是容器,便于弹性伸缩,再到开发、测试、发布、运维时需要全自动化的系统,这需要有一个一定规模的团队,并且每个人都要有一定的技术储备。

隐患

1 . 设计

微服务架构的第一个问题是如何设计它,其中包括了如何做好服务间的最小粒度切分。
一个团队不可能再一次就设计出完美的微服务架构,一些微服务(如 PDF 生成器,Word文档分析/处理器)是显而易见的用例。
而只要是设计到处理业务逻辑,你的代码一定以及肯定,会在你思考如何将应用分割成正确的微服务集合之前,四处移动,因为程序员听最多的一句话就是“这个业务有变化”。

2 . 开发复杂性

您需要面对所有分布式系统都需要面对的复杂性,你可能需要在部署、测试和监控上做很多的工作,在服务间调用、服务的可靠性上做很多工作,甚至您还需要处理类似于分布式事务 的问题。尽管一些框架已经解决了许多的问题,但在实施微服务架构之前,您的团队必须储备足够的分布式系统相关的知识体系,以面对很多您在单体架构下可能没有面临过甚至没有考虑过的问题。

3 . 部署复杂性

在部署和管理时,由许多不同服务类型组成的系统的操作比较复杂,这将要求开发、测试及运维人员有相应的技术水平。

4 . 分布式复杂性

对于单体架构来讲,我们可以不使用分布式,但是对于微服务架构来说,分布式几乎是必会用的技术,由于分布式本身的复杂性,导致微服务架构也变得复杂起来。

技术栈(浅浅的举两个例子)

1 . 技术栈太过灵活

当每个人都可以自己主导一个服务的开发时,技术栈选型往往就变得多样化,以我的团队举例,语言有JAVA,PHP,Python,GO,Node,框架有Spring Boot,Gin,这还是大家统一用服务端语言的前提下,而HTTP连接则是显现出技术多样化的最好例子,Python有requests, Go则有 net/http, req, resty 等,他们的功能和能解决的问题都差不多,但是却因为不同成员的不同偏好而引入,而当A成员 去维护B成员的项目时,A成员就不得不再学一个重复的对他无提升的东西。

2 . 微服务难关之分布式

分布式本文以及提到很多次了,微服务技术架构必用分布式部署架构分布式架构将单机部署的业务拆分成多个机器部署,可根据业务情况无限的弹性伸缩,实现高性能、高可用、高并发。
但是使用分布式也存在很多问题,比如数据一致性问题。提供业务的服务不可能让不同的用户访问到的数据不是同一版本,这样整体就都乱了,因此使用了分布式模式之后,跨服务的操作需要分布式事务保障操作的原子性、当多人对同一个服务操作时需要分布式锁保证该操作的原子性。这些都是使用分布式架构带来的额外成本,我们享受了它所带来的福利,也必定要为其付出代价。

小结:

许多公司使用微服务并不是真正需要它们,尽管微服务现在很流行,但它们并不适合初学者。
大多数公司最好的做法是构建一个单体架构的项目,然后在绝对必要的时候将单体的部分拆分到微服务中。
把从头开始的微服务架构的机会留给那些财力雄厚的大型科技公司。

最后,感谢各位大佬的赞助!

1676563505501.jpg

浅谈微服务的优点

微服务是个极其复杂的概念,作者就一下表面问题浅谈一二。
篇幅有限,涉猎不深,有兴趣还请自行查阅。

目录

  • 微服务的意义
  • 架构理念
  • 微服务的好处
  • 微服务框架

术语解释

RPC:全称 Remote Procedure Call 中文 远程过程调用, 可以理解为调用远程机器上的方法。
单体架构:所有业务代码都在一起。

微服务的意义

我们在开发一个基础的商城程序时, 可能会包含若干模块, 如:用户模块 商品模块 广告模块 订单模块。
在系统建设初期,为了追求系统快速上线,我们可能会把一整套的模块代码, 都放到一个项目代码中。
这样的弊端:
1 . 在后期流量上来后我们发现某个模块功能失效,导致整个项目瘫痪,
举例:
公司进行业务推广,广告模块流量激增,但在此之前,没有做服务拆分,广告模块的高流量导致数据库带宽无法支撑,最终整个项目进入黑洞状态,用户无法下单,就连商城的界面也打不开。
上面的例子简单说明了传统开发模式的弊端,那么,将微服务理念加入之后呢?
示例:
在莫名的建议下,开发组将商城系统进行了模块服务拆分,这样广告模块就是一个独立的服务,用户参与活动时,直接从客户端调用活动服务,活动服务需要验证其他模块数据的时候,又通过RPC调用进行服务间的数据交互,从而实现压力的分摊,不在让全部服务压力都积压到单台服务器或者数据库上。

架构理念

随着需求的迭代,新功能的诞生,代码库会越来越大,尽管我们竭尽全力,希望将巨大的代码库做到清晰的模块化,但事实上模块与模块之间的界限很难划清,慢慢的,相似的代码随处可见,冗余的代码越来越多,修复bug和新功能越发吃力。
微服务则将这一理念应用在独立的服务上,根据业务的边界确定服务的边界,每个服务专注于服务边界之内的事情,因为可以避免很多由于代码库过大衍生出来的各种问题。
那么一个微服务到底应该多微小?足够小即可,不要过小。那么怎么衡量一个系统是否拆足够小了呢?当你面对这个系统时,不会再有 "过大" 想要拆小它的欲望时,那么它应该就足够小了。使用的服务越小,独立性带来的好处就越多,但管理大量的服务也会更加复杂

微服务的好处

  1. 扩展,如果使用较小的多个服务,则可以只对需要扩展的服务进行扩展,这样可以把那些不需要扩展的服务运行在更廉价的服务器上,节省成本。
  2. 可组合性,在微服务架构下,更细粒度的服务拆分会将这一优点体现得更淋漓尽致,像拼图一样组合功能。
  3. 高度自治,一个微服务就是一个独立的实体,可以独立被部署,也可以作为一个进程存在.
  4. 重构性高, 如果面对传统模式的(单体架构)的系统,里面的代码混乱丑陋,没人敢轻易去重构,但是如果你面对的是小规模及其小粒度的服务呢?重构一个服务甚至是重写,都是一件相对容易的事。
    我相信在(单体架构)删除一百行代码,将是一件致命的事情,但是在规划清晰的微服务架构下,删除一个服务也可以游刃有余。

微服务框架

所谓的微服务框架,是一种错误的说法,微服务是一种架构性上的概念,和框架无关。
我们服务间的互相调用,可以用 HTTP 协议 或者是 原生 TCP 协议 来实现,因此实际上,微服务和框架没有一点关系。
如果真要说是微服务框架的话,无非是封装了一些组件,让你更轻松的实现RPC调用
真正的微服务,最核心的其实是如何做好服务间的最小粒度切分,是架构规划的范畴。

小结

本篇主要将微服务和单体架构之间进行了对比,彰显了微服务的优点,具体使用还是要看具体情况。

Redis SCAN 命令

redis的扫描方法,使用scan,而不是使用 keys*
因为keys* 会全部key扫描一次,key数量很多时,容易造成阻塞太久甚至down机。

Redis Scan 命令用于迭代数据库中的数据库键。
SCAN 命令是一个基于游标的迭代器,每次被调用之后, 都会向用户返回一个新的游标, 用户在下次迭代时需要使用这个新游标作为 SCAN 命令的游标参数, 以此来延续之前的迭代过程。
SCAN 返回一个包含两个元素的数组, 第一个元素是用于进行下一次迭代的新游标, 而第二个元素则是一个数组, 这个数组中包含了所有被迭代的元素。如果新游标返回 0 表示迭代已结束。

/**
 * 查找redis key
 * @param string|null $pattern // 要匹配的规则  'test_*'
 * @param int $count    // 每次遍历数量.count越大总耗时越短,但单次阻塞越长。 建议5000-10000。并发不高则可以调至接近1w。
 * @return array
 */
function redisScan(string $pattern = '', int $count = 6000): array
{

    $keyArr = array();

    while (true){
        // $iterator 下条数据的坐标
        $key = env('redis.prefix', '') . $pattern;
        Log::debug($key);
        $data = Cache::store('redis')->handler()->scan($iterator, env('redis.prefix', '') . $pattern, $count);
        $keyArr = array_merge($keyArr,$data ?: array());

        if ($iterator === 0){   //迭代结束,未找到匹配
            break;
        }
        if ($iterator === null) {//"游标为null了,重置为0,继续扫描"
            $iterator = "0";
        }

    }

    // 反转去重
    return array_flip(array_flip($keyArr));

}