0%

sync.Once

专门用于确保某些初始化代码只被执行一次,不论该代码由多少个 goroutine 调用.

package main

import (
    "fmt"
    "sync"
    "time"
)

func main() {
    var once sync.Once
    for i := 0; i < 10; i++ {
        go func() {
            // sync.Once 提供了一个方法 Do,这个方法接受一个函数作为参数,并确保该函数只会被执行一次
            once.Do(func() {
                fmt.Println("once")
            })
        }()
    }

    time.Sleep(1 * time.Second)
}

常用于单例模式,如

package main

import (
    "fmt"
    "sync"
)

type Singleton struct {
    // Fields for the singleton
}

var instance *Singleton
var once sync.Once

func GetInstance() *Singleton {
    once.Do(func() {
        instance = &Singleton{}
        fmt.Println("Singleton instance created")
    })
    return instance
}

sync.WaitGroup

WaitGroup 用于等待一组 goroutine 完成。可以增加计数器,并在 goroutine 完成时减少计数器

package main

import (
    "fmt"
    "sync"
)

func main() {
    var once sync.Once
    var wg sync.WaitGroup
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            once.Do(func() {
                fmt.Println("once")
            })
        }()
    }

    wg.Wait()
}

sync.Mutex

Mutex 是一个互斥锁,用于保护临界区,确保同一时间只有一个 goroutine 能够访问被保护的代码.

package main

import (
    "fmt"
    "sync"
)

var count int
var mutex sync.Mutex

// 通过Mutex解决线程安全问题
func Increase() {
    mutex.Lock()
    count++
    mutex.Unlock()
}

func main() {
    var wg sync.WaitGroup
    for i := 0; i < 500; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            Increase()
        }()
    }
    wg.Wait()
    fmt.Println(count)
}

sync.RWMutex

RWMutex 是一种读写互斥锁,允许多个读操作同时进行,但写操作会独占锁。

package main

import (
    "fmt"
    "sync"
)

var counter int
var rwMutex sync.RWMutex

func read() int {
    rwMutex.RLock()
    defer rwMutex.RUnlock()
    return counter
}

func write(val int) {
    rwMutex.Lock()
    counter = val
    rwMutex.Unlock()
}

func main() {
    var wg sync.WaitGroup
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go func(i int) {
            defer wg.Done()
            write(i)
            fmt.Println("Read value:", read())
        }(i)
    }
    wg.Wait()
}

基本概念

  • 源码文件的第一行有效代码必须是 package pacakgeName 语句,通过该语句声明自己所在的包
  • 一般包的名称就是其源文件所在目录的名称,虽然Go语言没有强制要求包名必须和其所在的目录名同名,但还是建议包名和所在目录同名,这样结构更清晰
  • 包可以定义在很深的目录中,包名的定义是不包括目录路径的,但是包在引用时一般使用全路径引用。比如在GOPATH/src/a/b/ 下定义一个包 c。在包 c 的源码中只需声明为package c,而不是声明为package a/b/c,但是在导入 c 包时,需要带上路径,例如import "a/b/c"
  • 包名一般是小写的,使用一个简短且有意义的名称
  • 包名一般要和所在的目录同名,也可以不同,包名中不能包含 - 等特殊符号
  • 包一般使用域名作为目录名称,这样能保证包名的唯一性,比如 GitHub 项目的包一般会放到 GOPATH/src/github.com/userName/projectName 目录下
  • 包名为 main 的包为应用程序的入口包,编译不包含 main 包的源码文件时不会得到可执行文件
  • 一个文件夹下的所有源码文件只能属于同一个包,同样属于同一个包的源码文件不能放在多个文件夹下

包的导入

  • import 导入语句通常放在源码文件开头包声明语句的下面
  • 导入的包名需要使用双引号包裹起来
  • 包名是从 GOPATH/src/ 后开始计算的,使用 / 进行路径分隔
  • 包的引用路径有两种写法,分别是全路径导入和相对路径导入,相对路径只能用于导入 GOPATH 下的包,标准包的导入只能使用全路径导入

包的引用格式

  1. 标准引用格式
package main
import "fmt"

func main() {
    fmt.Println("Hello world")
}
  1. 自定义别名引用格式
package main
import F "fmt"

func main() {
    // 使用F.来代替标准引用格式的fmt.
    F.Println("Hello world")
}
  1. 省略引用格式
package main
import . "fmt"

func main() {
    // 不用加前缀fmt.
    Println("Hello world")
}
  1. 匿名引用格式
package main
import (
    // 在引用某个包时,如果只是希望执行包初始化的 init 函数,而不使用包内部的数据时,可以使用匿名引用格式
    // 一个包可以有多个 init 函数,包加载时会执行全部的 init 函数,但并不能保证执行顺序
    _ "database/sql"
    "fmt"
)
func main() {
    fmt.Println("Hello world")
}

包加载

包加载

  • 包初始化程序从 main 函数引用的包开始,逐级查找包的引用,直到找到没有引用其他包的包,最终生成一个包引用的有向无环图
  • Go 编译器会将有向无环图转换为一棵树,然后从树的叶子节点开始逐层向上对包进行初始化
  • 单个包的初始化过程如上图所示,先初始化常量,然后是全局变量,最后执行包的 init 函数

拉取非公共module

通过 Go 命令拉取项目依赖的公共 Go Module,只需要在每个开发机上设置环境变量 GOPROXY,配置一个高效且可靠的公共 GOPROXY 服务,如 set GOPROXY=https://goproxy.cn,direct。但是如果同时需要拉取私有的包怎么办呢?如企业自己开发的module依赖。这时我们需要配置set GOPRIVATE=*gitee.com,表示 gitee.com的包不使用代理而是直接拉取。通过go env命令可以看出:

set GONOPROXY=*gitee.com
set GONOSUMDB=*gitee.com
set GOPRIVATE=*gitee.com

开发私有module

  1. 如我们开发一个打印版本的方法,封装在了gitee.com/happywzy/go-module-test包中。
package pkg

func Version() string {
    return "version 1"
}

我们模拟提交了很多个版本,并在不同的commit打上了一些tag,提交到了远程仓库。

# 查看版本号
> git ls-remote -q origin          
9da810c37c774a3775a518d16cfe032a39f862ea        HEAD
9da810c37c774a3775a518d16cfe032a39f862ea        refs/heads/master
9da810c37c774a3775a518d16cfe032a39f862ea        refs/tags/v1.0.0
f4c3585328e0a686ad35537e390473917c4b202b        refs/tags/v1.0.0-test
1e40db9baf18cbb8ed5784858bc9418c738792a7        refs/tags/v2
  1. 我们需要在另一个Module中使用Version()方法
    go.mod配置如下:
module gitee.com/happywzy/go-module

go 1.19

require (
    gitee.com/happywzy/go-module-test v1.0.0-test
)
  • 版本号可以配置成commitId,执行go mod tidy后,会根据commitId自动生成伪版本号,如gitee.com/happywzy/go-module-test v0.0.0-20240524023550-1e40db9baf18,这个版本号不要手敲,防止出错。
  • 版本号可以配置成tag,如v1.0.0v1.0.0-test,但是配置成v2这种好像无法识别。
  • 可以通过go get 命令去安装module,如 go get gitee.com/happywzy/go-module-test@master@后面可以是分支名、tag名、commit ID,非常方便。

如何使用本地的Module

前面介绍的方式需要Module推送到远程仓库后才能使用,这在开发阶段非常不适用,如何直接使用本地的Module呢?有两种方式。

  1. 使用 go.mod 中 replace
replace (
    gitee.com/happywzy/go-module-test v1.0.0-test => ../../happywzy/go-module-test
)

表示gitee.com/happywzy/go-module-test v1.0.0-test这个包直接使用本地路径下的代码。这种方式需要注意在提交代码前需要将replace删除,防止出错。

  1. 使用 go.work

    go.work1.18 版本之后才有。

go 1.19

use (
    ./happywzy/go-module-test
    ./happywzy/go-module
)

go.work文件需要位于两个模块的上层目录,通过go work init初始化生成,使用go work use gitee.com/happywzy/go-module-test添加。

go.work 这个文件无需提交到git,仅用于本地开发即可。

安装

yum install keepalived -y

编写检查端口是否通的脚本

#!/bin/bash
CHK_PORT=$1
if [ -n "$CHK_PORT" ];then
        PORT_PROCESS=`ss -lnt|grep $CHK_PORT|wc -l`
        if [ $PORT_PROCESS -eq 0 ];then
                echo "Port $CHK_PORT Is Not Used,End."
                exit 1
        fi
else
        echo "Check Port Cant Be Empty!"
fi

测试:./check_port.sh 8080检测8080端口是否开通.

keepalived主节点配置

! Configuration File for keepalived

global_defs {
   router_id 20.153
}

vrrp_script chk_port {
    script "/etc/keepalived/check_port.sh 8080"
    interval 2
    weight -20
}

vrrp_instance VI_1 {
    state MASTER
    interface ens192
    virtual_router_id 51
    priority 100
    advert_int 1
    authentication {
        auth_type PASS
        auth_pass 1111
    }
    track_script {
         chk_port
    }
    unicast_src_ip 192.168.1.1  # 本地节点的 IP 地址
    unicast_peer {
        192.168.1.2  # 另一个节点的 IP 地址
        192.168.1.3  # 另一个节点的 IP 地址
    }
    virtual_ipaddress {
        172.16.20.99
    }
}

keepalived备节点配置

! Configuration File for keepalived

global_defs {
   router_id 20.154
}

vrrp_script chk_port {
    script "/etc/keepalived/check_port.sh 8080"
    interval 2
    weight -20
}

vrrp_instance VI_1 {
    state BACKUP
    interface ens192
    virtual_router_id 51
    priority 90
    advert_int 1
    authentication {
        auth_type PASS
        auth_pass 1111
    }
    track_script {
         chk_port
    }
    unicast_src_ip 192.168.1.2  # 本地节点的 IP 地址
    unicast_peer {
        192.168.1.1  # 另一个节点的 IP 地址
        192.168.1.3  # 另一个节点的 IP 地址
    }
    virtual_ipaddress {
        172.16.20.99
    }
}

非抢占式及通知服务

vrrp_instance VI_1 {
    state BACKUP // 主备都用BACKUP
    interface ens192
    virtual_router_id 51
    advert_int 1
    authentication {
        auth_type PASS
        auth_pass 1111
    }
    track_script {
         chk_port
    }
    virtual_ipaddress {
        192.168.20.99
    }
    nopreempt // 非抢占式
    notify_master /etc/keepalived/notify_master.sh
    notify_backup /etc/keepalived/notify_backup.sh
    notify_fault /etc/keepalived/notify_fault.sh
    notify_stop /etc/keepalived/notify_stop.sh
}

说明

OGNL(Object-Graph Navigation Language)和SpEL(Spring Expression Language)都是用于在Java应用程序中进行对象导航和操作的表达式语言。

SpEL

SpEL是一种表达式语言,用于在运行时在Spring应用程序中进行查询和操作对象图。您可以使用SpEL来访问对象的属性、调用对象的方法、进行算术运算等等。SpelExpressionParser是Spring框架中的一部分,用于解析SpEL(Spring表达式语言)。

以下是一个简单的示例,演示如何在Spring应用程序中使用SpelExpressionParser:

import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;

public class SpelExample {

    public static void main(String[] args) {
        // 创建SpelExpressionParser对象
        ExpressionParser parser = new SpelExpressionParser();
        
        // 创建StandardEvaluationContext对象,它表示SpEL表达式的上下文
        StandardEvaluationContext context = new StandardEvaluationContext();

        // 在上下文中添加变量
        context.setVariable("name", "Alice");
        
        // 定义SpEL表达式
        String expression = "'Hello, ' + #name";
        
        // 解析并评估表达式
        String result = parser.parseExpression(expression).getValue(context, String.class);
        
        // 输出结果
        System.out.println(result); // 输出: Hello, Alice
    }
}
  1. 访问对象属性和方法:SpEL允许您通过对象的属性名称直接访问对象的属性,也可以调用对象的方法。
person.name   // 访问person对象的name属性
person.getName()   // 调用person对象的getName()方法
  1. 进行算术运算和逻辑运算:您可以在SpEL中执行各种算术运算和逻辑运算。
Copy code
5 + 3   // 执行加法运算,结果为8
age > 18   // 执行大于运算,判断age是否大于18
  1. 调用静态方法和属性:您可以调用静态方法和属性,甚至可以调用静态常量。
Copy code
T(java.lang.Math).random()   // 调用Math类的random()方法
T(java.lang.Integer).MAX_VALUE   // 获取Integer类的MAX_VALUE常量
  1. 集合操作:SpEL支持对集合进行操作,如访问集合元素、计算集合大小等。
Copy code
myList[0]   // 访问列表中的第一个元素
myMap['key']   // 访问映射中键为'key'的值
#myList.size()   // 计算列表的大小
  1. 条件运算:您可以在SpEL中使用条件运算符执行条件判断。
Copy code
isVip ? discountPrice : regularPrice   // 如果isVip为true,则返回discountPrice,否则返回regularPrice
  1. 正则表达式:SpEL支持对字符串进行匹配和替换操作,使用正则表达式。
Copy code
#email.matches('[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}')   // 检查email是否匹配电子邮件格式
  1. 安全导航运算符:SpEL引入了安全导航运算符(?.),用于避免空指针异常。
Copy code
person?.address?.city   // 如果person或address为null,则返回null,而不是抛出空指针异常

这只是SpEL的一些基本用法示例,SpEL还提供了更多功能,例如集成Spring Bean引用、类型转换、字符串操作等等。通过这些功能,您可以编写更加灵活和强大的表达式来满足各种需求。

说明

springboot动态增加和删除restful接口.

代码

@Service
public class Test {
    @Autowired
    RequestMappingHandlerMapping requestMappingHandlerMapping;

    // 初始化后自动添加一个 GET /test 接口
    @PostConstruct
    public void test() throws NoSuchMethodException {
        RequestMappingInfo.BuilderConfiguration options = new RequestMappingInfo.BuilderConfiguration();
        options.setPatternParser(new PathPatternParser());
        RequestMappingInfo requestMappingInfo = RequestMappingInfo
                .paths("/test")
                .produces("application/json;charset=UTF-8")
//                .consumes("text/plain;charset=UTF-8")
                .methods(RequestMethod.GET)
                .options(options)
                .build();
        requestMappingHandlerMapping.registerMapping(requestMappingInfo, "handler", Handle.class.getDeclaredMethod("test", HttpServletRequest.class));
        // 删除接口
        //requestMappingHandlerMapping.unregisterMapping(requestMappingInfo);
    }
}

@RestController
public class Handler {
    public String test(HttpServletRequest request)  {
        return "hello";
    }
}