编程实践精华总结集锦系列2: SpringBoot/Maven/IDEA/Java/Kotlin/Redis等等

编程实践精华总结集锦系列2: SpringBoot/Maven/IDEA/Java/Kotlin/Redis等等_第1张图片

Go Quick Start 极简教程

Go

官网:https://golang.org/
文档:https://golang.org/doc/

开发环境配置

下载 Go:
(官网)https://golang.org/dl/
(国内镜像)https://golang.google.cn/dl/

IDE :使用 GoLand is a cross-platform IDE built specially for Go developers。
https://www.jetbrains.com/go/

go 命令说明:

go
Go is a tool for managing Go source code.

Usage:

    go  [arguments]

The commands are:

    bug         start a bug report
    build       compile packages and dependencies
    clean       remove object files and cached files
    doc         show documentation for package or symbol
    env         print Go environment information
    fix         update packages to use new APIs
    fmt         gofmt (reformat) package sources
    generate    generate Go files by processing source
    get         add dependencies to current module and install them
    install     compile and install packages and dependencies
    list        list packages or modules
    mod         module maintenance
    run         compile and run Go program
    test        test packages
    tool        run specified go tool
    version     print Go version
    vet         report likely mistakes in packages

Use "go help " for more information about a command.

Additional help topics:

    buildconstraint build constraints
    buildmode       build modes
    c               calling between Go and C
    cache           build and test caching
    environment     environment variables
    filetype        file types
    go.mod          the go.mod file
    gopath          GOPATH environment variable
    gopath-get      legacy GOPATH go get
    goproxy         module proxy protocol
    importpath      import path syntax
    modules         modules, module versions, and more
    module-get      module-aware go get
    module-auth     module authentication using go.sum
    packages        package lists and patterns
    private         configuration for downloading non-public code
    testflag        testing flags
    testfunc        testing functions
    vcs             controlling version control with GOVCS

Use "go help " for more information about that topic.

Hello World

源代码文件 hello_world.go :

package main

import "fmt"

func main() {
   fmt.Println("Hello, World!")
}

命令行运行:

go run hello_world.go

输出:

Hello, World!

在 GoLang IDE 中效果如下:

编程实践精华总结集锦系列2: SpringBoot/Maven/IDEA/Java/Kotlin/Redis等等_第2张图片

Go 语言简介

概述

Go 是一个开源的编程语言,它能让构造简单、可靠且高效的软件变得容易。

Go是从2007年末由Robert Griesemer, Rob Pike, Ken Thompson主持开发,后来还加入了Ian Lance Taylor, Russ Cox等人,并最终于2009年11月开源,在2012年早些时候发布了Go 1稳定版本。现在Go的开发已经是完全开放的,并且拥有一个活跃的社区。

Go 语言特色

简洁、快速、安全
并行、有趣、开源
内存管理、数组安全、编译迅速

Go 语言用途

Go 语言被设计成一门应用于搭载 Web 服务器,存储集群或类似用途的巨型中央服务器的系统编程语言。

对于高性能分布式系统领域而言,Go 语言无疑比大多数其它语言有着更高的开发效率。它提供了海量并行的支持,这对于游戏服务端的开发而言是再好不过了。

数据类型

Go 有一个相当简单的类型系统:没有子类型(但有类型转换),没有泛型,没有多态函数,只有一些基本的类型:

基本类型:int、int64、int8、uint、float32、float64 等
struct: 结构体
interface:一组方法的集合
map[K, V]:一个从键类型到值类型的映射
[number]Type:一些 Type 类型的元素组成的数组
[]Type:某种类型的切片(具有长度和功能的数组的指针)
chan Type:一个线程安全的队列
指针 *T 指向其他类型
函数:
具名类型:可能具有关联方法的其他类型的别名(LCTT 译注:这里的别名并非指 Go 1.9 中的新特性“类型别名”):

type T struct { foo int }
type T *T
type T OtherNamedType

具名类型完全不同于它们的底层类型,所以你不能让它们互相赋值,但一些操作符,例如 +,能够处理同一底层数值类型的具名类型对象们(所以你可以在上面的示例中把两个 T 加起来)。

映射、切片和信道是类似于引用的类型——它们实际上是包含指针的结构。包括数组(具有固定长度并可被拷贝)在内的其他类型则是值传递(拷贝)。

函数

package main

import "fmt"

func main() {
    fmt.Println(sum(1, 1))
}

func sum(a int, b int) interface{} {
    return a + b
}

分支

package main

import "fmt"

func main() {
    fmt.Println(max(1, 2))
}

func max(a int, b int) interface{} {
    var max int = 0
    if a > b {
        max = a
    } else {
        max = b
    }
    return max
}

循环

package main

import "fmt"

func main() {
    fmt.Println(sum1_n(100))
}

func sum1_n(n int) interface{} {
    var sum = 0
    for i := 1; i <= n; i++ {
        sum += i
    }
    return sum
}

数组

package main

import "fmt"

func main() {
    var a []int = []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
    fmt.Println(sumArray(a))
}

func sumArray(a []int) interface{} {
    var sum = 0
    for i := 0; i < len(a); i++ {
        sum += a[i]
    }
    return sum
}

指针

package main

import "fmt"

func main() {
    var a int = 20
    /* 声明指针变量 */
    var ip *int
    fmt.Println(ip==nil)
    /* 指针变量的存储地址 */
    ip = &a
    fmt.Println(ip==nil)
    fmt.Printf("a 变量的地址是: %x\n", &a)
    /* 指针变量的存储地址 */
    fmt.Printf("ip 变量储存的指针地址: %x\n", ip)
    /* 使用指针访问值 */
    fmt.Printf("*ip 变量的值: %d\n", *ip)
}

//true
//false
//a 变量的地址是: c0000ae008
//ip 变量储存的指针地址: c0000ae008
//*ip 变量的值: 20

结构体 struct

package main

import "fmt"

func main() {
    var b = Book{
        "Go Programming",
        "ken",
        10000,
    }

    fmt.Println(b.title)
    fmt.Println(b.author)
    fmt.Println(b.id)
    //Go Programming
    //ken
    //10000
}

type Book struct {
    title  string
    author string
    id     int
}

集合 Map

Map 是一种无序的键值对的集合。Map 最重要的一点是通过 key 来快速检索数据,key 类似于索引,指向数据的值。

Map 是一种集合,所以我们可以像迭代数组和切片那样迭代它。不过,Map 是无序的,我们无法决定它的返回顺序,这是因为 Map 是使用 hash 表来实现的。

可以使用内建函数 make 也可以使用 map 关键字来定义 Map:

/* 声明变量,默认 map 是 nil */
var map_variable map[key_data_type]value_data_type

/* 使用 make 函数 */
map_variable := make(map[key_data_type]value_data_type)

如果不初始化 map,那么就会创建一个 nil map。nil map 不能用来存放键值对。

package main

import "fmt"

func main() {
    var ccmap = make(map[string]string)
    ccmap["China"] = "北京"
    ccmap["Italy"] = "罗马"
    ccmap["Germany"] = "柏林"
    for c := range ccmap {
        fmt.Println(c, "Capital is ", ccmap[c])
    }

    c, contains := ccmap["American"]
    if contains {
        fmt.Println("American's capital is ", c)
    } else {
        fmt.Println("Not exist")
    }
}

//Italy Capital is  罗马
//Germany Capital is  柏林
//China Capital is  北京
//Not exist

Go 优势与生态

Go 语言生态

当下使用 Go 语言开发的软件产品非常众多,而且知名的产品也不再少数,足见这门语言的强大:

软件 描述 链接
docker 家喻户晓的容器技术 github.com/moby/moby
kubernetes 容器编排引擎,google出品 github.com/kubernetes/kubernetes
etcd 分布式服务注册发现系统 github.com/etcd-io/etcd
influxdb 时序数据库 github.com/influxdata/influxdb
grafana 数据监控可视化看板 github.com/grafana/grafana
prometheus 开源监控系统 github.com/prometheus/prometheus
consul 分布式服务发现系统 github.com/hashicorp/consul
nsq 亿级消息队列 github.com/nsqio/nsq
TiDB 分布式数据库, go + rust 打造 github.com/pingcap/tidb

除了上述表格中列举的产品外, Go 语言还涉足于像区块链、微服务等场景,开源的框架也非常多,所以说 Go 语言是一门值得去学习的语言。

性能优势

Go 语言被称为是:"21世纪的C语言",虽然这个帽子戴的有点高,不妨这里给大家解读一下,其实这句话有两层含义:

第一层含义是: Go 语言的语法和C语言类似,如果你会C语言,上手会很快。但如果你不会C语言,其实也不用担心,比起C语言, Go 语言上手其实很简单。

第二层含义是: Go 语言的性能, Go 语言内置强大的并发模型 goroutine,它能让我们快速开发高并发web系统,并且在同样服务器资源的情况下, Go 语言表现出来的性能也是相当的优秀,这也是推荐大家选择选择 Go 语言的原因之一。

部署运维成本低

这是我选择 Go 语言的第二个原因。 Go 语言属于编译型语言,最终部署上线,我们只需要部署项目编译后的二进制文件即可,类比一下像 PHP 或 python 这种解释性语言在服务器上运行还需要安装相应的运行环境,而使用二进制代码方式使得部署变得更为简单,也不会存在多版本共享环境的兼容性问题,运维也变得非常容易。

编码格式统一

Go 语言官方内置了统一代码风格的工具 gofmt( IDE 一般都会内置集成), 用来规范大家代码风格,这对于需要多人协作项目尤为重要。

比如,现在你需要接手一个外包团队项目,这个外包项目如果项目的编码风格和你不一致,当你接手后,你的心情多半沮丧的,因为这会增加你的改造成本。

更或是,你现在需要接手一个由前前前同事所开发的老项目,如果代码风格不统一,可想而知接下来会发生什么...

Go 语言官方其实也是注意到了这些痛点,也避免了由第三方产生的规范不一致的问题,以官方的角度统一规范,从而降低整个项目的协作运维成本。

测试简单

Go 语言编写测试代码真的非常简单,这个特性真的是刚需啊。我们在项目开发中经常会遇到这样的事情,比如当你负责为项目负责开发一个独立的短信服务模块,这个功能供另个同事在项目里调用,当你开发完成后如何测试功能是否正常呢?

这种测试在 Go 语言中变得相当容易,你只需要在你代码文件同级目录创建一个以 _test.go 结尾的文件,然后在文件里编写针对特定功能测试函数即可,更重要的是,这个测试文件是可以单独运行的,你不需要再去集成整个项目运行环境, Go 语言让单元测试变得非常容易。

Go Projects

https://github.com/moby/moby
https://github.com/docker/docker-ce
https://github.com/kubernetes/kubernetes
https://github.com/etcd-io/etcd
https://github.com/gin-gonic/gin
https://github.com/hashicorp/consul
https://github.com/micro/go-micro
https://github.com/nsqio/nsq
https://github.com/elastic/beats
https://github.com/pingcap/tidb
https://github.com/CodisLabs/codis
https://github.com/baidu/bfe
https://github.com/caddyserver/caddy
https://github.com/cockroachdb/cockroach

参考资料

https://blog.csdn.net/javahongxi/article/details/109771251
https://www.runoob.com/go/go-environment.html
https://golang.org/cmd/go/
https://golang.org/doc/
https://www.cnblogs.com/xingxia/p/golang.html

PS:go内置类型

// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

/*
    Package builtin provides documentation for Go's predeclared identifiers.
    The items documented here are not actually in package builtin
    but their descriptions here allow godoc to present documentation
    for the language's special identifiers.
*/
package builtin

// bool is the set of boolean values, true and false.
type bool bool

// true and false are the two untyped boolean values.
const (
    true  = 0 == 0 // Untyped bool.
    false = 0 != 0 // Untyped bool.
)

// uint8 is the set of all unsigned 8-bit integers.
// Range: 0 through 255.
type uint8 uint8

// uint16 is the set of all unsigned 16-bit integers.
// Range: 0 through 65535.
type uint16 uint16

// uint32 is the set of all unsigned 32-bit integers.
// Range: 0 through 4294967295.
type uint32 uint32

// uint64 is the set of all unsigned 64-bit integers.
// Range: 0 through 18446744073709551615.
type uint64 uint64

// int8 is the set of all signed 8-bit integers.
// Range: -128 through 127.
type int8 int8

// int16 is the set of all signed 16-bit integers.
// Range: -32768 through 32767.
type int16 int16

// int32 is the set of all signed 32-bit integers.
// Range: -2147483648 through 2147483647.
type int32 int32

// int64 is the set of all signed 64-bit integers.
// Range: -9223372036854775808 through 9223372036854775807.
type int64 int64

// float32 is the set of all IEEE-754 32-bit floating-point numbers.
type float32 float32

// float64 is the set of all IEEE-754 64-bit floating-point numbers.
type float64 float64

// complex64 is the set of all complex numbers with float32 real and
// imaginary parts.
type complex64 complex64

// complex128 is the set of all complex numbers with float64 real and
// imaginary parts.
type complex128 complex128

// string is the set of all strings of 8-bit bytes, conventionally but not
// necessarily representing UTF-8-encoded text. A string may be empty, but
// not nil. Values of string type are immutable.
type string string

// int is a signed integer type that is at least 32 bits in size. It is a
// distinct type, however, and not an alias for, say, int32.
type int int

// uint is an unsigned integer type that is at least 32 bits in size. It is a
// distinct type, however, and not an alias for, say, uint32.
type uint uint

// uintptr is an integer type that is large enough to hold the bit pattern of
// any pointer.
type uintptr uintptr

// byte is an alias for uint8 and is equivalent to uint8 in all ways. It is
// used, by convention, to distinguish byte values from 8-bit unsigned
// integer values.
type byte = uint8

// rune is an alias for int32 and is equivalent to int32 in all ways. It is
// used, by convention, to distinguish character values from integer values.
type rune = int32

// iota is a predeclared identifier representing the untyped integer ordinal
// number of the current const specification in a (usually parenthesized)
// const declaration. It is zero-indexed.
const iota = 0 // Untyped int.

// nil is a predeclared identifier representing the zero value for a
// pointer, channel, func, interface, map, or slice type.
var nil Type // Type must be a pointer, channel, func, interface, map, or slice type

// Type is here for the purposes of documentation only. It is a stand-in
// for any Go type, but represents the same type for any given function
// invocation.
type Type int

// Type1 is here for the purposes of documentation only. It is a stand-in
// for any Go type, but represents the same type for any given function
// invocation.
type Type1 int

// IntegerType is here for the purposes of documentation only. It is a stand-in
// for any integer type: int, uint, int8 etc.
type IntegerType int

// FloatType is here for the purposes of documentation only. It is a stand-in
// for either float type: float32 or float64.
type FloatType float32

// ComplexType is here for the purposes of documentation only. It is a
// stand-in for either complex type: complex64 or complex128.
type ComplexType complex64

// The append built-in function appends elements to the end of a slice. If
// it has sufficient capacity, the destination is resliced to accommodate the
// new elements. If it does not, a new underlying array will be allocated.
// Append returns the updated slice. It is therefore necessary to store the
// result of append, often in the variable holding the slice itself:
//  slice = append(slice, elem1, elem2)
//  slice = append(slice, anotherSlice...)
// As a special case, it is legal to append a string to a byte slice, like this:
//  slice = append([]byte("hello "), "world"...)
func append(slice []Type, elems ...Type) []Type

// The copy built-in function copies elements from a source slice into a
// destination slice. (As a special case, it also will copy bytes from a
// string to a slice of bytes.) The source and destination may overlap. Copy
// returns the number of elements copied, which will be the minimum of
// len(src) and len(dst).
func copy(dst, src []Type) int

// The delete built-in function deletes the element with the specified key
// (m[key]) from the map. If m is nil or there is no such element, delete
// is a no-op.
func delete(m map[Type]Type1, key Type)

// The len built-in function returns the length of v, according to its type:
//  Array: the number of elements in v.
//  Pointer to array: the number of elements in *v (even if v is nil).
//  Slice, or map: the number of elements in v; if v is nil, len(v) is zero.
//  String: the number of bytes in v.
//  Channel: the number of elements queued (unread) in the channel buffer;
//           if v is nil, len(v) is zero.
// For some arguments, such as a string literal or a simple array expression, the
// result can be a constant. See the Go language specification's "Length and
// capacity" p for details.
func len(v Type) int

// The cap built-in function returns the capacity of v, according to its type:
//  Array: the number of elements in v (same as len(v)).
//  Pointer to array: the number of elements in *v (same as len(v)).
//  Slice: the maximum length the slice can reach when resliced;
//  if v is nil, cap(v) is zero.
//  Channel: the channel buffer capacity, in units of elements;
//  if v is nil, cap(v) is zero.
// For some arguments, such as a simple array expression, the result can be a
// constant. See the Go language specification's "Length and capacity" p for
// details.
func cap(v Type) int

// The make built-in function allocates and initializes an object of type
// slice, map, or chan (only). Like new, the first argument is a type, not a
// value. Unlike new, make's return type is the same as the type of its
// argument, not a pointer to it. The specification of the result depends on
// the type:
//  Slice: The size specifies the length. The capacity of the slice is
//  equal to its length. A second integer argument may be provided to
//  specify a different capacity; it must be no smaller than the
//  length. For example, make([]int, 0, 10) allocates an underlying array
//  of size 10 and returns a slice of length 0 and capacity 10 that is
//  backed by this underlying array.
//  Map: An empty map is allocated with enough space to hold the
//  specified number of elements. The size may be omitted, in which case
//  a small starting size is allocated.
//  Channel: The channel's buffer is initialized with the specified
//  buffer capacity. If zero, or the size is omitted, the channel is
//  unbuffered.
func make(t Type, size ...IntegerType) Type

// The new built-in function allocates memory. The first argument is a type,
// not a value, and the value returned is a pointer to a newly
// allocated zero value of that type.
func new(Type) *Type

// The complex built-in function constructs a complex value from two
// floating-point values. The real and imaginary parts must be of the same
// size, either float32 or float64 (or assignable to them), and the return
// value will be the corresponding complex type (complex64 for float32,
// complex128 for float64).
func complex(r, i FloatType) ComplexType

// The real built-in function returns the real part of the complex number c.
// The return value will be floating point type corresponding to the type of c.
func real(c ComplexType) FloatType

// The imag built-in function returns the imaginary part of the complex
// number c. The return value will be floating point type corresponding to
// the type of c.
func imag(c ComplexType) FloatType

// The close built-in function closes a channel, which must be either
// bidirectional or send-only. It should be executed only by the sender,
// never the receiver, and has the effect of shutting down the channel after
// the last sent value is received. After the last value has been received
// from a closed channel c, any receive from c will succeed without
// blocking, returning the zero value for the channel element. The form
//  x, ok := <-c
// will also set ok to false for a closed channel.
func close(c chan<- Type)

// The panic built-in function stops normal execution of the current
// goroutine. When a function F calls panic, normal execution of F stops
// immediately. Any functions whose execution was deferred by F are run in
// the usual way, and then F returns to its caller. To the caller G, the
// invocation of F then behaves like a call to panic, terminating G's
// execution and running any deferred functions. This continues until all
// functions in the executing goroutine have stopped, in reverse order. At
// that point, the program is terminated with a non-zero exit code. This
// termination sequence is called panicking and can be controlled by the
// built-in function recover.
func panic(v interface{})

// The recover built-in function allows a program to manage behavior of a
// panicking goroutine. Executing a call to recover inside a deferred
// function (but not any function called by it) stops the panicking sequence
// by restoring normal execution and retrieves the error value passed to the
// call of panic. If recover is called outside the deferred function it will
// not stop a panicking sequence. In this case, or when the goroutine is not
// panicking, or if the argument supplied to panic was nil, recover returns
// nil. Thus the return value from recover reports whether the goroutine is
// panicking.
func recover() interface{}

// The print built-in function formats its arguments in an
// implementation-specific way and writes the result to standard error.
// Print is useful for bootstrapping and debugging; it is not guaranteed
// to stay in the language.
func print(args ...Type)

// The println built-in function formats its arguments in an
// implementation-specific way and writes the result to standard error.
// Spaces are always added between arguments and a newline is appended.
// Println is useful for bootstrapping and debugging; it is not guaranteed
// to stay in the language.
func println(args ...Type)

// The error built-in interface type is the conventional interface for
// representing an error condition, with the nil value representing no error.
type error interface {
    Error() string
}

操作系统中的任务调度 & CPU 内存缓存一致性问题

处理器调度(multiprocessor scheduling)

作系统应该如何在多 CPU上调度工作?会遇到什么新问题?

CPU 根据程序寄存器(PC)加载指令,详码,执行,写回,并对程序计数器更新,周而复始。

但是CPU怎么及时响应硬件来的请求呢?
解决方案:中断机制。CPU有一个引脚就是触发中断的。

1、为了接入不同通常会有中断控制器,不同硬件连接到中断控制器上,每个硬件的中断有一个中断编号IRQ。

2、当一个硬件中断发生时,CPU会根据中断编号查到对应的中断服务处理程序,将当前执行上下文压栈,并将程序计数器设为中断处理程序的地址。这样中断处理程序就抢到了CPU。

3、中断处理完成后,再弹栈,跳回到原来的程序继续执行。

计算机中有一种硬件设备叫时钟,用来让程序按照一定的时间间隔执行特定的逻辑。时钟会产生时钟中断。而多任务操作系统把自己的中断服务处理程序注册到了时钟中断上,这样每隔一定间隔。就可以把 CPU从当前的任务手中抢过来,进行上下文切换后,交给另一个任务。这样就支持多任务"同时"执行了。

缓存一致性(cache coherence)问题

在单CPU系统中,存在多级的硬件缓存(hardware cache),一般来说会让处理器更快地执行程序。缓存是很小但很快的存储设备,通常拥有内存中最热的数据的备份。相比之下,内存很大且拥有所有的数据,但访问速度较慢。通过将频繁访问的数据放在缓存中,系统似乎拥有又大又快的内存。

举个例子,假设一个程序需要从内存中加载指令并读取一个值,系统只有一个CPU,拥有较小的缓存(如64KB)和较大的内存。

程序第一次读取数据时,数据在内存中,因此需要花费较长的时间(可能数十或数百纳秒)。处理器判断该数据很可能会被再次使用,因此将其放入CPU缓存中。如果之后程序再次需要使用同样的数据,CPU会先查找缓存。因为在缓存中找到了数据,所以取数据快得多(比如几纳秒),程序也就运行更快。

缓存是基于局部性(locality)的概念,局部性有两种,即时间局部性和空间局部性。时间局部性是指当一个数据被访问后,它很有可能会在不久的将来被再次访问,比如循环代码中的数据或指令本身。而空间局部性指的是,当程序访问地址为x的数据时,很有可能会紧接着访问x周围的数据,比如遍历数组或指令的顺序执行。由于这两种局部性存在于大多数的程序中,硬件系统可以很好地预测哪些数据可以放入缓存,从而运行得很好。

有趣的部分来了:如果系统有多个处理器,并共享同一个内存,如图所示,会怎样呢?

编程实践精华总结集锦系列2: SpringBoot/Maven/IDEA/Java/Kotlin/Redis等等_第3张图片

带缓存的单CPU

编程实践精华总结集锦系列2: SpringBoot/Maven/IDEA/Java/Kotlin/Redis等等_第4张图片

两个有缓存的CPU共享内存

事实证明,多CPU的情况下缓存要复杂得多。
例如,假设一个运行在CPU 1上的程序从内存地址A读取数据。
1、由于不在CPU 1的缓存中,所以系统直接访问内存,得到值D。程序然后修改了地址A处的值,只是将它的缓存更新为新值D'
2、将数据写回内存比较慢,因此系统(通常)会稍后再做。
3、假设这时操作系统中断了该程序的运行,并将其交给CPU 2,重新读取地址A的数据,由于CPU 2的缓存中并没有该数据,所以会直接从内存中读取,得到了旧值D,而不是正确的值D'

这一普遍的问题称为缓存一致性(cache coherence)问题。

编程实践精华总结集锦系列2: SpringBoot/Maven/IDEA/Java/Kotlin/Redis等等_第5张图片

不用被动注入的方式, 主动从 Spring Boot 应用容器中的获取 Bean 的方法

@Component

public class SpringContextUtil implements ApplicationContextAware {

// Spring应用上下文环境

    private static ApplicationContext applicationContext;

/**

* 实现ApplicationContextAware接口的回调方法,设置上下文环境

*

    * @param applicationContext

    */

    public void setApplicationContext(ApplicationContext applicationContext) {

SpringContextUtil.applicationContext = applicationContext;

}

/**

* @return ApplicationContext

*/

    public static ApplicationContext getApplicationContext() {

return applicationContext;

}

/**

* 获取对象

*

    * @param name

    * @return Object

    * @throws BeansException

    */

    public static Object getBean(String name)throws BeansException {

return applicationContext.getBean(name);

}

}

PS: @Component 注解.

从容器中主动获取Bean的使用方式

/**

* Kunlun 编译器实现类

*/

@Service

public class KunlunInterpreterImpl implements KunlunInterpreter {

SpecificationParser specificationParser;

@Override

    public Statement interpret(KunlunRule rule)throws Exception {

// 选择引擎

        selectEngine(rule);

KunlunExpression expression = rule.getExpression();

// BFS 解析嵌套子句

        bfsParseExpression(expression);

// 解析构建 ASTQuery 抽象语法树

        IQuerySpecification specification =specificationParser.parse(rule);

// 物理资源映射,生成真正执行的物理 SQL Statement

        Statement statement =new Statement();

if (specification !=null) {

statement.setSelectSql(specification.genSelectSql());

statement.setCountSql(specification.genSelectCountSql());

statement.setGroupBySql(specification.genSelectGroupBySql());

statement.setKvSql(specification.genSelectSqlForKV());

}

return statement;

}

private void selectEngine(KunlunRule rule) {

// 引擎选择

        if (EngineType.OLAP.equals(rule.getEngineSelector().getEngineType())

&&DriverType.CLICKHOUSE.equals(rule.getEngineSelector().getDriverType())) {

specificationParser = (SpecificationParser)SpringContextUtil.getBean("specificationParserClickHouse");

}else if (EngineType.OLAP.equals(rule.getEngineSelector().getEngineType())

&&DriverType.HIVE.equals(rule.getEngineSelector().getDriverType())) {

specificationParser = (SpecificationParser)SpringContextUtil.getBean("specificationParserHive");

}else {

throw new IllegalArgumentException("engine not supported yet!");

}

}

/**

* BFS 遍历表达式树

*

    * @param kunlunExpression

    * @throws Exception

    */

    private void bfsParseExpression(KunlunExpression kunlunExpression)throws Exception {

Queuequeue =new ArrayDeque<>();

ListexpressionList =new ArrayList<>();

// 根节点入队列

        queue.offer(kunlunExpression);

while (!queue.isEmpty()) {

// 出队列

            KunlunExpression expr =queue.poll();

ListsubExpressionList =expr.getSubExpression();

if (subExpressionList !=null && !subExpressionList.isEmpty()) {

for (KunlunExpression e :subExpressionList) {

// 子节点入队列

                    queue.offer(e);

}

}

expressionList.add(expr);

}

for (KunlunExpression e :expressionList) {

System.out.println(e);

// 解析

            parseExpression(e);

}

}

/**

* 解析表达式

*

    * @param kunlunExpression

    * @throws Exception

    */

    private void parseExpression(KunlunExpression kunlunExpression)throws Exception {

ExpressionLogicOperatorEnum logic = kunlunExpression.getLogic();

if (logic ==null) {

throw new RuntimeException("can't get operator from kunlunExpression:" +JsonUtil.toString(kunlunExpression));

}

FieldCondition fieldCondition = kunlunExpression.getFilterField();

if (fieldCondition !=null) {

// 获取特征值运算符

            this.processFieldFeature(specificationParser, kunlunExpression);

}else {

specificationParser.compose(kunlunExpression);

}

}

private void processFieldFeature(SpecificationParser specificationParser,KunlunExpression kunlunExpression)throws Exception {

FieldCondition fieldCondition = kunlunExpression.getFilterField();

FieldArithmeticOperatorEnum operator =fieldCondition.getOperator();

switch (operator) {

case GREATER_THAN:

specificationParser.greaterThan(kunlunExpression);

break;

case GREATER_EQUAL_THAN:

specificationParser.greaterEqualThan(kunlunExpression);

break;

case LESS_THAN:

specificationParser.lessThan(kunlunExpression);

break;

case LESS_EQUAL_THAN:

specificationParser.lessEqualThan(kunlunExpression);

break;

case EQUAL:

specificationParser.equals(kunlunExpression);

break;

case IN:

specificationParser.in(kunlunExpression);

break;

case BETWEEN:

specificationParser.between(kunlunExpression);

break;

case LIKE:

specificationParser.like(kunlunExpression);

break;

default:

throw new RuntimeException("unsupported operator.");

}

}

}

git 如何删除已经 add 的文件 ( 如何撤销已放入缓存区文件的修改)

使用 git rm 命令即可,有两种选择:

一种是 git rm --cached "文件路径",不删除物理文件,仅将该文件从缓存中删除;

一种是 git rm --f  "文件路径",不仅将该文件从缓存中删除,还会将物理文件删除(不会回收到垃圾桶)。

git --如何撤销已放入缓存区(Index区)的修改

修改或新增的文件通过 git add --all命令全部加入缓存区(index区)之后,使用 git status 查看状态

(git status -s 简单模式查看状态,第一列本地库和缓存区的差异,第二列缓存区和工作目录的差异),

提示使用 git reset HEAD 来取消缓存区的修改。

不添加参数,撤销所有缓存区的修改。

另外可以使用 git rm --cached 文件名 ,可以从缓存区移除文件,使该文件变为未跟踪的状态,

同时下次提交时从本地库中删除。

注:

没有带参数的 git reset 命令,默认执行了 --mixed 参数,即用reset版本库到指定版本,并重置缓存区,在上面的命令中指定的目录版本是HEAD,即当前版本,所以实际上没有任何修改,仅是重置了缓存区。

Maven deploy 怎么配置

在本地的pom文件配置好之后,执行deploy命令,可以将maven所打的jar包上传到远程的repository,便于其他开发者和工程共享。

pom.xml配置

首选,在pom文件中project标签下添加如下代码:

 

  releases

  Internal Releases

  http://localhost:8081/nexus/content/repositories/thirdparty

 

 

  releases

  Internal Releases

  http://localhost:8081/nexus/content/repositories/thirdparty

 

此时,执行deploy命令,会返回401错误,则需要用户验证或验证的信息有误。

setting.xml文件配置

在setting配置文件的servers标签下添加如下代码:

 

    releases

    admin

    admin

 

PS:其中此处的id,必须为pom文件中配置的repository的id。

注意事项

一般继承 parent 的version会按照如下格式写:

 

    module.some

    module_parent

    ${parent.version}

 

这样写方便统一管理版本信息,但发布到maven私服之后,maven 会试图下载 module_parent 的 ${parent.version} 的 jar。显然,这个jar是不存在的。那么为什么已经指定了 parent.version 的值了却没有解析呢?这是因为deploy 的过程中,parent 标签里的变量是不会解析的,必须是一个常量。

结果

执行maven deploy命令成功之后,登录私服进行查询,即可看到对应的jar包。

maven deploy命令打包到私服

  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

  4.0.0

  com.zeelan.app

  seller-auth

  0.0.1-SNAPSHOT

  jar

 

 

    UTF-8

    1.16.10

    1.2.17.2

    5.1.2.Final

    1.1.0.Final

 

 

 

   

      nexus

      Team Nexus Repository

      http://192.168.0.126:8081/nexus/content/groups/public

   

 

 

 

   

   

      releases

      Nexus Release Repository

      http://192.168.0.126:8081/nexus/content/repositories/releases/

   

   

   

      snapshots

      Nexus Snapshot Repository

      http://192.168.0.126:8081/nexus/content/repositories/snapshots/

   

 

 

 

   

   

      org.projectlombok

      lombok

      ${lombok.version}

   

   

   

      com.github.miemiedev

      mybatis-paginator

      ${mybatis.paginator.version}

   

   

   

      org.hibernate

      hibernate-validator

      ${hibernate.validator.version}

   

   

   

      javax.validation

      validation-api

      ${validation.api.version}

   

 

 

    seller-auth

   

   

     

     

        maven-compiler-plugin

        true

       

          1.8

          1.8

       

     

     

     

        maven-source-plugin

        2.1

       

          true

       

       

         

            compile

           

              jar

           

         

       

     

     

     

        maven-deploy-plugin

        2.7

       

         

            default-deploy

            deploy

           

              deploy

           

           

           

              true

           

         

         

            deploy-file

            deploy

           

              deploy-file

           

           

             

              ${project.distributionManagement.snapshotRepository.id}

              ${project.distributionManagement.snapshotRepository.url}

              ${project.build.directory}/${project.artifactId}.jar

              ${project.groupId}

              ${project.artifactId}

              ${project.version}

           

         

       

     

   

 

执行 mvn deploy就能打包到私服上了!

mvn -clean配置清除插件,然后在执行命令可以清除target下的文件

mvn-clean package 本地打包,jar/war/等根据jar/war控制

mvn -e 查看打包过程的错误信息

mvn -v查看mavne版本信息等等.

hive 使用 map 类型字段

创建一个map类型字段

create table test3(field2 map) 
row format delimited fields terminated by ',' 
collection items terminated by "|" 
map keys terminated by ":";

map keys terminated by ":"表示键值对之间用":"来分割

str_to_map 方式

同样的,先使用sql方式插入

insert into test3(field2)values(str_to_map("name:zhangsan,age:25")),(str_to_map("name:lisi,age:23"));

再导入文件,文件内容如下:

load data local inpath  '/Users/zhangsheng/hive/note/hive/test.txt' into table test3;

map的访问通过map[key]的形式进行,测试一下

select * from test3;
select field2["name"] as name,field2["age"] as age from test3;
select * from test3 where field2["age"] > 25;

用 map() 函数

select map("pay_order_cnt", 10, "pay_order_amt", 1000)

IDEA 源代码文件大小超出 2.56M : File size exceeds configured limit

File size exceeds configured limit (2560000), code insight features not available

Ask Question

This is built from Álvaro González's answer and How to increase IDE memory limit in IntelliJ IDEA on Mac?

Go to Help > Edit Custom Properties

Add:

idea.max.intellisense.filesize=999999

Restart the IDE.

https://stackoverflow.com/questions/23057988/file-size-exceeds-configured-limit-2560000-code-insight-features-not-available

Schema是什么?

Schema是什么?

schema是元数据的一个抽象集合,包含一套 schema component: 主要是元素与属性的声明、复杂与简单数据类型的定义。这些schema component通常是在处理一批 schema document时被创建。

数据库schema就是 :表, 列,数据类型,视图,存储过程,关系,主键,外键等。

在ISO / IEC 9075-1 SQL标准定义了一个模式作为 描述符的持久性,命名集合。

在MySQL文档指出,物理,一个模式是与数据库的代名词。因此,模式和数据库是同一件事。

在软件开发中,当讨论模式时,可以讨论概念模式,物理模式,内部模式,外部模式,逻辑模式等。这些每个都有其特定的含义。

XML Schema 是基于 XML 的 DTD 替代者。
XML Schema 可描述 XML 文档的结构。
XML Schema 语言也可作为 XSD(XML Schema Definition)来引用。
XML Schema 的作用是定义 XML 文档的合法构建模块,类似 DTD。
XML Schema:
定义可出现在文档中的元素
定义可出现在文档中的属性
定义哪个元素是子元素
定义子元素的次序
定义子元素的数目
定义元素是否为空,或者是否可包含文本
定义元素和属性的数据类型
定义元素和属性的默认值以及固定值

XML Schema 是 DTD 的继任者
我们认为 XML Schema 很快会在大部分网络应用程序中取代 DTD。
理由如下:
XML Schema 可针对未来的需求进行扩展
XML Schema 更完善,功能更强大
XML Schema 基于 XML 编写
XML Schema 支持数据类型
XML Schema 支持命名空间

ClickHouse 的 Parser与Interpreter

Parser和Interpreter是非常重要的两组接口:

  • Parser分析器负责创建AST对象;

  • Interpreter解释器则负责解释AST,并进一步创建查询的执行管道。

它们与IStorage一起,串联起了整个数据查询的过程。

Parser分析器可以将一条SQL语句以递归下降的方法解析成AST语法树的形式。

不同的SQL语句,会经由不同的Parser实现类解析。例如,有负责解析DDL查询语句的ParserRenameQuery、ParserDropQuery和ParserAlterQuery解析器,也有负责解析INSERT语句的ParserInsertQuery解析器,还有负责SELECT语句的ParserSelectQuery等。

Interpreter解释器的作用就像Service服务层一样,起到串联整个查询过程的作用,它会根据解释器的类型,聚合它所需要的资源。首先它会解析AST对象;然后执行“业务逻辑”(例如分支判断、设置参数、调用接口等);最终返回IBlock对象,以线程的形式建立起一个查询执行管道。

Interpreter模式也类似于Composite模式。Composite模式通常会为单个对象和群组对象定义一个公共接口。不过,Composite模式并不要求支持以不同方式组织的结构,尽管该模式可以支持这些结构。例如,介绍Composite模式时曾描述过ProcessComponent类层次结构,该结构允许生产流程顺序进行,也允许生产流程交替进行。Interpreter模式通常都会涉及不同类型的组合结构(Interpreter模式通常处于Composite模式结构之上)。一个类组成其他组件的方式定义了解释器类实现或解释一个操作的方式。

Interpreter模式的主要意图是可以按照自己定义的组合规则集合来组合可执行对象。

在 Mac OS X 中编译 ClickHouse

ClickHouse 支持在 Mac OS X 10.12 版本中编译。若您在用更早的操作系统版本,可以尝试在指令中使用 Gentoo Prefix 和 clang sl.

通过适当的更改,它应该可以适用于任何其他的 Linux 发行版。

安装 Homebrew

$ /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"

安装编译器,工具库

$ brew install cmake ninja libtool gettext

拉取 ClickHouse 源码

git clone --recursive [email protected]:ClickHouse/ClickHouse.git
# or: git clone --recursive https://github.com/ClickHouse/ClickHouse.git

编译 ClickHouse

$ cd ClickHouse
$ mkdir build
$ cd build
$ cmake .. -DCMAKE_CXX_COMPILER=`which clang++` -DCMAKE_C_COMPILER=`which clang`
$ ninja
$ cd ..

注意事项

若你想运行 clickhouse-server,请先确保增加系统的最大文件数配置。

注意, 可能需要用 sudo

为此,请创建以下文件:

/资源库/LaunchDaemons/limit.maxfiles.plist:



  
    Label
    limit.maxfiles
    ProgramArguments
    
      launchctl
      limit
      maxfiles
      524288
      524288
    
    RunAtLoad
    
    ServiceIPC
    
  

执行以下命令:

$ sudo chown root:wheel /Library/LaunchDaemons/limit.maxfiles.plist

然后重启。

可以通过 ulimit -n 命令来检查是否生效。

布隆过滤器(Bloom Filter)的原理和实现

什么情况下需要布隆过滤器?

先来看几个比较常见的例子

  • 字处理软件中,需要检查一个英语单词是否拼写正确

  • 在 FBI,一个嫌疑人的名字是否已经在嫌疑名单上

  • 在网络爬虫里,一个网址是否被访问过

  • yahoo, gmail等邮箱垃圾邮件过滤功能

这几个例子有一个共同的特点: 如何判断一个元素是否存在一个集合中?

常规思路

  • 数组

  • 链表

  • 树、平衡二叉树、Trie

  • Map (红黑树)

  • 哈希表

虽然上面描述的这几种数据结构配合常见的排序、二分搜索可以快速高效的处理绝大部分判断元素是否存在集合中的需求。但是当集合里面的元素数量足够大,如果有500万条记录甚至1亿条记录呢?这个时候常规的数据结构的问题就凸显出来了。数组、链表、树等数据结构会存储元素的内容,一旦数据量过大,消耗的内存也会呈现线性增长,最终达到瓶颈。有的同学可能会问,哈希表不是效率很高吗?查询效率可以达到O(1)。但是哈希表需要消耗的内存依然很高。使用哈希表存储一亿 个垃圾 email 地址的消耗?哈希表的做法:首先,哈希函数将一个email地址映射成8字节信息指纹;考虑到哈希表存储效率通常小于50%(哈希冲突);因此消耗的内存:8 * 2 * 1亿 字节 = 1.6G 内存,普通计算机是无法提供如此大的内存。这个时候,布隆过滤器(Bloom Filter)就应运而生。在继续介绍布隆过滤器的原理时,先讲解下关于哈希函数的预备知识。

哈希函数

哈希函数的概念是:将任意大小的数据转换成特定大小的数据的函数,转换后的数据称为哈希值或哈希编码。下面是一幅示意图:

编程实践精华总结集锦系列2: SpringBoot/Maven/IDEA/Java/Kotlin/Redis等等_第6张图片

可以明显的看到,原始数据经过哈希函数的映射后称为了一个个的哈希编码,数据得到压缩。哈希函数是实现哈希表和布隆过滤器的基础。

布隆过滤器介绍

  • 巴顿.布隆于一九七零年提出

  • 一个很长的二进制向量 (位数组)

  • 一系列随机函数 (哈希)

  • 空间效率和查询效率高

  • 有一定的误判率(哈希表是精确匹配)

布隆过滤器原理

布隆过滤器(Bloom Filter)的核心实现是一个超大的位数组和几个哈希函数。假设位数组的长度为m,哈希函数的个数为k

编程实践精华总结集锦系列2: SpringBoot/Maven/IDEA/Java/Kotlin/Redis等等_第7张图片

以上图为例,具体的操作流程:假设集合里面有3个元素{x, y, z},哈希函数的个数为3。首先将位数组进行初始化,将里面每个位都设置位0。对于集合里面的每一个元素,将元素依次通过3个哈希函数进行映射,每次映射都会产生一个哈希值,这个值对应位数组上面的一个点,然后将位数组对应的位置标记为1。查询W元素是否存在集合中的时候,同样的方法将W通过哈希映射到位数组上的3个点。如果3个点的其中有一个点不为1,则可以判断该元素一定不存在集合中。反之,如果3个点都为1,则该元素可能存在集合中。注意:此处不能判断该元素是否一定存在集合中,可能存在一定的误判率。可以从图中可以看到:假设某个元素通过映射对应下标为4,5,6这3个点。虽然这3个点都为1,但是很明显这3个点是不同元素经过哈希得到的位置,因此这种情况说明元素虽然不在集合中,也可能对应的都是1,这是误判率存在的原因。

布隆过滤器添加元素

  • 将要添加的元素给k个哈希函数

  • 得到对应于位数组上的k个位置

  • 将这k个位置设为1

布隆过滤器查询元素

  • 将要查询的元素给k个哈希函数

  • 得到对应于位数组上的k个位置

  • 如果k个位置有一个为0,则肯定不在集合中

  • 如果k个位置全部为1,则可能在集合中

布隆过滤器实现

下面给出python的实现,使用murmurhash算法

import mmh3
from bitarray import bitarray

# zhihu_crawler.bloom_filter

# Implement a simple bloom filter with murmurhash algorithm.
# Bloom filter is used to check wether an element exists in a collection, and it has a good performance in big data situation.
# It may has positive rate depend on hash functions and elements count.

BIT_SIZE = 5000000

class BloomFilter:

    def __init__(self):
        # Initialize bloom filter, set size and all bits to 0
        bit_array = bitarray(BIT_SIZE)
        bit_array.setall(0)

        self.bit_array = bit_array

    def add(self, url):
        # Add a url, and set points in bitarray to 1 (Points count is equal to hash funcs count.)
        # Here use 7 hash functions.
        point_list = self.get_postions(url)

        for b in point_list:
            self.bit_array[b] = 1

    def contains(self, url):
        # Check if a url is in a collection
        point_list = self.get_postions(url)

        result = True
        for b in point_list:
            result = result and self.bit_array[b]

        return result

    def get_postions(self, url):
        # Get points positions in bit vector.
        point1 = mmh3.hash(url, 41) % BIT_SIZE
        point2 = mmh3.hash(url, 42) % BIT_SIZE
        point3 = mmh3.hash(url, 43) % BIT_SIZE
        point4 = mmh3.hash(url, 44) % BIT_SIZE
        point5 = mmh3.hash(url, 45) % BIT_SIZE
        point6 = mmh3.hash(url, 46) % BIT_SIZE
        point7 = mmh3.hash(url, 47) % BIT_SIZE

        return [point1, point2, point3, point4, point5, point6, point7]

源码地址:https://github.com/cpselvis/zhihu-crawler/blob/master/bloom_filter.py

Redis 亿级用户信息存储实践:bitmap 位图存储

编程实践精华总结集锦系列2: SpringBoot/Maven/IDEA/Java/Kotlin/Redis等等_第8张图片

bitmap简介

8 个 bit 组成一个 Byte,所以 bitmap 极大的节省储存空间

你可以把它理解为一个特殊处理过的 字符串
key代表业务属性、标签。一个 bit 位来表示某个元素对应的值或者状态。

bitmap 并不是一种数据结构,实际上它就是字符串,但是可以对字符串的位进行操作。

bitmap有自己的一套命令。可以把bitmap想象成一个以bit为单位的数组,数组的每个单元存储0和1,数组的下标叫做偏移量。

Redis 提供 setbit,getbit,bitcount等几个 bitmap 相关命令。但其实 setbit 等命令只不过是在 set 上的扩展而已。

setbit 命令介绍

指令

SETBIT key offset value

复杂度 O(1)

设置或者清空 key 的 value(字符串)在 offset 处的 bit 值(只能只 0 或者 1)。

空间占用、以及第一次分配空间需要的时间
在一台 2010MacBook Pro 上

offset 为 2^32-1(分配 512MB)需要~ 300ms
offset 为 2^30-1(分配 128MB)需要~ 80ms
offset 为 2^28-1(分配 32MB)需要~ 30ms
offset 为 2^26-1(分配 8MB)需要 8ms。
– <来自官方文档: https://redis.io/commands/setbit>
大概的空间占用计算公式是:($offset/8/1024/1024)MB

Available since 2.2.0.

Time complexity: O(1)

Sets or clears the bit at offset in the string value stored at key.

The bit is either set or cleared depending on value, which can be either 0 or 1.

When key does not exist, a new string value is created. The string is grown to make sure it can hold a bit at offset. The offset argument is required to be greater than or equal to 0, and smaller than 232 (this limits bitmaps to 512MB). When the string at key is grown, added bits are set to 0.

Warning: When setting the last possible bit (offset equal to 232 -1) and the string value stored at key does not yet hold a string value, or holds a small string value, Redis needs to allocate all intermediate memory which can block the server for some time. On a 2010 MacBook Pro, setting bit number 232 -1 (512MB allocation) takes ~300ms, setting bit number 230 -1 (128MB allocation) takes ~80ms, setting bit number 228 -1 (32MB allocation) takes ~30ms and setting bit number 226 -1 (8MB allocation) takes ~8ms. Note that once this first allocation is done, subsequent calls to SETBIT for the same key will not have the allocation overhead.

Redis bitmap 的命令

bitmap的命令

常用命令 作用
1、getbit key offset 用于获取Redis中指定key对应的值,中对应offset的bit
2、setbit key key offset value 用于修改指定key对应的值,中对应offset的bit
3、 bitcount key [start end] 用于统计字符串被设置为1的bit数
4、bitop and/or/xor/not destkey key [key …] 用于对多个key求逻辑与/逻辑或/逻辑异或/逻辑非

设置 offset 位的 01 值

127.0.0.1:6379> setbit user10001 0 1
(integer) 0
127.0.0.1:6379> setbit user10001 3 1
(integer) 0
127.0.0.1:6379> getbit user10001 0
(integer) 1
127.0.0.1:6379> getbit user10001 1
(integer) 0
127.0.0.1:6379> getbit user10001 2
(integer) 0
127.0.0.1:6379> getbit user10001 3
(integer) 1
127.0.0.1:6379> getbit user10001 7
(integer) 1

批量设置 offset 位的 01 值

使用 u1 类型批量设置 offset 位的 01 值:

127.0.0.1:6379> bitfield user10001 set u1 2 1 set u1 9 1 set u1 10 1
1) (integer) 0
2) (integer) 0
3) (integer) 0
127.0.0.1:6379> getbit user10001 10
(integer) 1
127.0.0.1:6379> getbit user10001 9
(integer) 1
127.0.0.1:6379> getbit user10001 8
(integer) 0
127.0.0.1:6379> getbit user10001 7
(integer) 1

bitmap的应用场景

编程实践精华总结集锦系列2: SpringBoot/Maven/IDEA/Java/Kotlin/Redis等等_第9张图片

使用经验:
type = string,BitMap 是 sting 类型,最大 512 MB。
注意 setbit 时的偏移量,可能有较大耗时
位图不是绝对好。

通过 bitcount可以很快速的统计,比传统的关系型数据库效率高很多

1、比如统计年活跃用户数量

用户的ID作为offset,当用户在一年内访问过网站,就将对应offset的bit值设置为“1”;

通过bitcount 来统计一年内访问过网站的用户数量

2、比如统计三天内活跃用户数量

时间字符串作为key,比如 “190108:active“ “190109:active”“190110:active” ;

用户的ID就可以作为offset,当用户访问过网站,就将对应offset的bit值设置为“1”;

统计三天的活跃用户,通过bitop or 获取一周内访问过的用户数量

3、连续三天访问的用户数量 bitop and

4、三天内没有访问的用户数量 bitop not

5、统计在线人数 设置在线key:“online:active”,当用户登录时,通过setbit设置

bitmap的优势,以统计活跃用户为例

每个用户id占用空间为1bit,消耗内存非常少,存储1亿用户量只需要12.5M

使用场景: 统计活跃用户

使用时间作为 cacheKey,然后用户 ID 为 offset,如果当日活跃过就设置为 1
那么我该如果计算某几天/月/年的活跃用户呢(暂且约定,统计时间内只有有一天在线就称为活跃),有请下一个 redis 的命令
命令

BITOP operation destkey key [key ...]

说明:对一个或多个保存二进制位的字符串 key 进行位元操作,并将结果保存到 destkey 上。
说明:BITOP 命令支持 AND 、 OR 、 NOT 、 XOR 这四种操作中的任意一种参数

//日期对应的活跃用户
$data = array(
    '2020-01-10' => array(1,2,3,4,5,6,7,8,9,10),
    '2020-01-11' => array(1,2,3,4,5,6,7,8),
    '2020-01-12' => array(1,2,3,4,5,6),
    '2020-01-13' => array(1,2,3,4),
    '2020-01-14' => array(1,2)
);
//批量设置活跃状态
foreach($data as $date=>$uids) {
    $cacheKey = sprintf("stat_%s", $date);
    foreach($uids as $uid) {
        $redis->setBit($cacheKey, $uid, 1);
    }
}

$redis->bitOp('AND', 'stat', 'stat_2020-01-10', 'stat_2020-01-11', 'stat_2020-01-12');

//总活跃用户:6
echo "总活跃用户:" . $redis->bitCount('stat') . PHP_EOL;

$redis->bitOp('AND', 'stat1', 'stat_2020-01-10', 'stat_2020-01-11', 'stat_2020-01-14') . PHP_EOL;

//总活跃用户:2
echo "总活跃用户:" . $redis->bitCount('stat1') . PHP_EOL;

$redis->bitOp('AND', 'stat2', 'stat_2020-01-10', 'stat_2020-01-11') . PHP_EOL;

//总活跃用户:8
echo "总活跃用户:" . $redis->bitCount('stat2') . PHP_EOL;

假设当前站点有 5000W 用户,那么一天的数据大约为 50000000/8/1024/1024=6MB

布隆过滤器

bitmap - Redis布隆过滤器 (应对缓存穿透问题)

举例:比如爬虫服务器在爬取电商网站的商品信息时,首先经过缓存,如果缓存查不到,再去数据库获取信息,因为爬虫的效率很高,且sku很有可能是不存在或者已下架的,就会造成缓存穿透,大量请求被发送到数据库,导致服务器受到影响。

此时,可以在缓存层之前,添加一个布隆过滤器,布隆过滤器看作是一个bitmap,sku作为offset值,如果商品真实存在,bit值设为1。首先将商品数据初始化,当有请求时,通过getbit判断sku是否有效。如果布隆过滤器认为商品不存在,就拒绝访问,这样就可以保护存储层。

Data structures are nothing different. They are like the bookshelves of your application where you can organize your data. Different data structures will give you different facility and benefits. To properly use the power and accessibility of the data structures you need to know the trade-offs of using one.

大意是不同的数据结构有不同的适用场景和优缺点,你需要仔细权衡自己的需求之后妥善适用它们,布隆过滤器就是践行这句话的代表。

参考资料

https://blog.csdn.net/weixin_42383575/article/details/86684283

https://zhuanlan.zhihu.com/p/136337229

https://www.imooc.com/article/298707

https://www.jianshu.com/p/4c8e119f35db

Mac 安装 Redis: make install

make install 安装 Redis

redis官网 下载压缩包:

编程实践精华总结集锦系列2: SpringBoot/Maven/IDEA/Java/Kotlin/Redis等等_第10张图片

在终端进入下载后的目录,然后:

  • 解压:tar zxvf redis-5.0.5.tar.gz

  • 移动到:sudo mv redis-5.0.5 /usr/local

  • 切换到:cd /usr/local/redis-5.0.5/

  • 编译测试:make test

  • 编译安装:make install

完成安装。

开启redis服务端

要使用redis,先开启redis服务端,在终端输入redis-server,如下:

编程实践精华总结集锦系列2: SpringBoot/Maven/IDEA/Java/Kotlin/Redis等等_第11张图片

可以看到redis服务端默认在6379端口成功开启。不要关闭此窗口。

打开redis客户端

重新打开一个终端,输入redis-cli,打开redis客户端:

编程实践精华总结集锦系列2: SpringBoot/Maven/IDEA/Java/Kotlin/Redis等等_第12张图片

然后就可以在redis客户端交互式的使用redis的一些命令了。

关闭redis服务端

在redis客户端输入shutdown命令可以关闭redis服务端:

编程实践精华总结集锦系列2: SpringBoot/Maven/IDEA/Java/Kotlin/Redis等等_第13张图片

编程实践精华总结集锦系列2: SpringBoot/Maven/IDEA/Java/Kotlin/Redis等等_第14张图片

ps: make install 执行日志

$make install
cd src && /Library/Developer/CommandLineTools/usr/bin/make install
    CC Makefile.dep
...

Hint: It's a good idea to run 'make test' ;)

    INSTALL redis-server
    INSTALL redis-benchmark
    INSTALL redis-cli

计算机操作系统的自举过程

CPU的硬件都设计为加电即进入16位实模式状态运行。同时,还有一点非常关键的是,将CPU硬件逻辑设计为加电瞬间强行将CS的值置为0xF000、IP的值置为0xFFF0,这样CS:IP就指向0xFFFF0这个地址位置。

编程实践精华总结集锦系列2: SpringBoot/Maven/IDEA/Java/Kotlin/Redis等等_第15张图片

IP/EIP(Instruction Pointer):指令指针寄存器,存在于CPU中,记录将要执行的指令在代码段内的偏移地址,和CS组合即为将要执行的指令的内存地址。实模式为绝对地址,指令指针为16位,即IP;保护模式下为线性地址,指令指针为32位,即EIP。
小贴士
CS(Code Segment Register):代码段寄存器,存在于CPU中,指向CPU当前执行代码在内存中的区域(定义了存放代码的存储器的起始地址)。
注意,这是一个纯硬件完成的动作!如果此时这个位置没有可执行代码,那么就什么也不用说了,计算机就此死机。反之,如果这个位置有可执行代码,计算机将从这里的代码开始,沿着后续程序一直执行下去。
BIOS程序的入口地址恰恰就是0xFFFF0 ! 也就是说,BIOS程序的第一条指令就设计在这个位置。


编程实践精华总结集锦系列2: SpringBoot/Maven/IDEA/Java/Kotlin/Redis等等_第16张图片

程序执行的本质

程序执行的本质

程序执行的过程:代码区的指令不断执行,驱使动态数据区和静态数据区产生数据变化。

这一过程需要计算机的管控。下面我们着重介绍对代码区和动态数据区的管控。CPU中有三个寄存器,分别是eip、ebp和esp,情景如图所示。

编程实践精华总结集锦系列2: SpringBoot/Maven/IDEA/Java/Kotlin/Redis等等_第17张图片

对代码区和动态数据区的管控

其中eip永远指向代码区将要执行的下一条指令,它的管控方式有两种,一种是“顺序执行”,即程序执行完一条指令后自动指向下一条执行;另一种是跳转,也就是执行完一条跳转指令后跳转到指定的位置。

ebp和esp用来管控栈空间,ebp指向栈底,esp指向栈顶,在代码区中,函数调用、返回和执行伴随着不断压栈和清栈,栈中数据存储和释放的原则是后进先出。

初始情景:

eip指向main函数的第一条指令,此时程序还没有运行,栈空间里还没有数据,ebp和esp指向的位置是程序加载时内核设置的(详情请看《Linux内核设计的艺术》一书)。

什么是EIP、ESP、EBP
堆栈是一种简单的数据结构,是一种只允许在其一端进行插入或删除的线性表。
允许插入或删除操作的一端称为栈顶,另一端称为栈底,对堆栈的插入和删除操作被称入栈和出栈。
有一组CPU指令可以实现对进程的内存实现堆栈访问。其中,POP指令实现出栈操作,PUSH指令实现入栈操作。
ESP寄存器存放当前线程的栈顶指针;
EBP寄存器中保存当前线程的栈底指针;
call main下面的一条指令地址EIP(也称返回地址)寄存器;

程序开始执行main函数第一条指令,eip自动指向下一条指令。第一条指令的执行,致使ebp的地址值被保存在栈中,保存的目的是本程序执行完毕后,ebp还能返回现在的位置,复原现在的栈。随着ebp地址值的压栈,esp自动向栈顶方向移动,它将永远指向栈顶。


ByteDance字节跳动张一鸣:如何阅读、如何了解自己、如何与人沟通沟通、如何安排时间、如何正确的看待别人意见、如何激励自己、如何写作、如何坚持锻炼身体、如何耐心?

张一鸣说:“安静的时候觉得书比大部分朋友更好,他会不(疲)倦(地)深入地和你交流,而无须客套也不用搞笑,他不会即时地给你回应,但会耐心地娓娓道来,你不耐心可以暂停,有时间则随时继续,一直到你有天遇到答案。看书本来就是和有智慧的人联网的过程。”

人生本来就是获得各种体验(的过程),读到一本好书本身是最好的体验之一,而(通过)金钱只不过可以更容易获得一些体验。

古有圣人说:“三人行,必有我师。”。“看书就是跟高人先贤联网的过程。” 读书排在第一,健身排在第二。德智体美劳全面发展。

通过阅读获得解放,解放思想,思想指导行动,行动产生结果。而优秀的思想获得除了要在事上练、难上得,高质量的信息输入是必不可少的。

所以平时多读书,你读过的书,走过的路,见过的人,会给你不断地提供养料,会一点一滴地滋养你、改变你。平时读书,也许一时看不见什么成果,但是日积月累,你的气质和谈吐,你的思维和格局,一定会和以前大有不同。

张一鸣:大学八目中,我观察多数人比较喜欢提“修身齐家治国平天下”后四目。而我比较喜欢前四目:格物致知,诚意正心。

大学的八目中, “修身齐家治国平天下”是外在的表现,可以让人获得社会的肯定,获取地位与受人尊重。张一鸣选择了“格物致知,诚意正心”,这意味着他将心思放在打磨自己身上,强调自我修炼,使自己知道更多深刻的知识,变得更好。

读一本好书胜过千本庸书。

和兴趣广泛,博览群书相比,读好书、读专业的书、读有价值的书,并花时间去将这些知识长到我们的脑子里面,内化成我们的行为准则,确实比博览群书而一无所长好。

除非你是天才,能够比普通人更有效率地读书并消化掉读到的知识,博览群书当然是最好的选择。

张一鸣书单推荐

这里推荐一下张一鸣在微博微信提到的几本书,希望对你有帮助。

《如何阅读一本书》

《如何阅读一本书》一书在谈在技能之外,更多的是讲学习的态度和沟通的方法。比如赞同和反对作者一章,其实标题亦可写为,关于沟通的赞同和反对。
这是一本张一鸣在微博里面谈过无数次的书, 初版于1940年,1972年大幅增订改写为新版。不懂阅读的人,初探阅读的人,读这本书可以少走冤枉路。对阅读有所体会的人,读这本书可以有更深的印证和领悟。

《卓有成效的管理者》

卓有成效如果有什么秘诀的话,那就是善于集中精力——《卓有成效的管理》

《少有人走的路》

斯科特·派克这本书处处透露出沟通与理解的意味,它跨越时代限制,帮助我们探索爱的本质,引导我们过上崭新,宁静而丰富的生活;它帮助我们学习爱,也学习独立;它教诲我们成为更称职的、更有理解心的父母。归根到底,它告诉我们怎样找到真正的自我

这本书或许是张一鸣多次提起的“延迟满足感”的源头。强烈推荐。

《普通生物学》

据说这本书让张一鸣联想到了组织的打造。

《什么是数学》

数学是这个世界的基础之一。

《活法》

《活法》是稻盛和夫的赤诚之作,作者稻盛和夫是日本战后经济的传奇,除每一个人的人生活法之外,企业和国家,甚至社会、全人类的生存之道也包括在《活法》所阐述的范围之内。

《乔布斯传》

张一鸣看乔布斯传至少三遍。

《定位》

如果只看一本营销书籍,首选《定位》。本书提出了被称为“有史以来对美国营销影响最大的观念”——定位,改观了人类“满足需求”的旧有营销认识,开创了“胜出竞争”的营销之道。引导人在商业世界里,去认知自我,能做什么不能做什么。

《谈判是什么》

张一鸣:“这本书过去3年中认真看了2遍,很好,但是总是做不到,知识易懂,习惯和意识难怪。 书籍《谈判是什么》 ”

《格调》

讲述80年代的美国,启发我们对当下的思考。

人生就是一个不断投资的过程,在哪里投资,就会在哪里产生结果。

你可能感兴趣的:(数据库,uefi,go,webgl,glassfish)