首页
关于
友链
Search
1
wlop 4K 壁纸 4k8k 动态 壁纸
1,472 阅读
2
Nacos持久化MySQL问题-解决方案
933 阅读
3
Docker搭建Typecho博客
753 阅读
4
滑动时间窗口算法
729 阅读
5
Nginx反向代理微服务配置
701 阅读
生活
解决方案
JAVA基础
JVM
多线程
开源框架
数据库
前端
分布式
框架整合
中间件
容器部署
设计模式
数据结构与算法
安全
开发工具
百度网盘
天翼网盘
阿里网盘
登录
Search
标签搜索
java
javase
docker
java8
springboot
thread
spring
分布式
mysql
锁
linux
redis
源码
typecho
centos
git
map
RabbitMQ
lambda
stream
少年
累计撰写
189
篇文章
累计收到
24
条评论
首页
栏目
生活
解决方案
JAVA基础
JVM
多线程
开源框架
数据库
前端
分布式
框架整合
中间件
容器部署
设计模式
数据结构与算法
安全
开发工具
百度网盘
天翼网盘
阿里网盘
页面
关于
友链
搜索到
189
篇与
的结果
2022-03-07
JDK1.8新特性
JDK1.8的新特性一、接口的默认方法Java8允许我们给接口添加一个非抽象的方法实现,只需要使用default关键字即可,这个特征又叫做扩展方法,示例如下:代码如下:interface Formula{ double calculate(int a); default double sqrt(int a){ return Math.sqrt(a); } }Formula接口在拥有calculate方法之外同时还定义了sqrt方法,实现了Formula接口的子类只需要实现一个calculate方法,默认方法sqrt将在子类上可以直接使用。代码如下:Formula formula=new Formula){@Override public double calculate(int a){return sqrt(a100);}];formula.calculate(100);/100.0 formula.sqrt(16);//4.0文中的formula被实现为一个匿名类的实例,该代码非常容易理解,6行代码实现了计算sqrt(a*100)。在下一节中,我们将会看到实现单方法接口的更简单的做法。译者注:在Java中只有单继承,如果要让一个类赋予新的特性,通常是使用接口来实现,在C++中支持多继承,允许一个子类同时具有多个父类的接口与功能,在其他语言中,让一个类同时具有其他的可复用代码的方法叫做mixin。新的Java8的这个特新在编译器实现的角度上来说更加接近Scala的trait。在C#中也有名为扩展方法的概念,允许给已存在的类型扩展方法,Java8的这个在语义上有差别。二、Lambda表达式首先看看在老版本的Java中是如何排列字符串的:代码如下:List names=Arrays.asList("peterF","anna","mike","xenia");Collections.sort(names,new Comparator){@Override public int compare(String a,String b){return b.compareTo(a);}});只需要给静态方法Collections.sort传入一个List对象以及一个比较器来按指定顺序排列。通常做法都是创建一个匿名的比较器对象然后将其传递给sort方法。在Java8中你就没必要使用这种传统的匿名对象的方式了,Java8提供了更简洁的语法,lambda表达式:代码如下:Collections.sort(names,(String a,String b)->{return b.compareTo(a);});看到了吧,代码变得更段且更具有可读性,但是实际上还可以写得更短:代码如下:Collections.sort(names,(String a,String b)->b.compareTo(a);对于函数体只有一行代码的,你可以去掉大括号0以及return关键字,但是你还可以写得更短点:代码如下:Collections.sort(names,(a,b)>b.compareTo(a);Java编译器可以自动推导出参数类型,所以你可以不用再写一次类型。接下来我们看看lambda表达式还能作出什么更方便的东西来:三、函数式接口Lambda表达式是如何在java的类型系统中表示的呢?每一个lambda表达式都对应一个类型,通常是接口类型。而“函数式接口”是指仅仅只包含一个抽象方法的接口,每一个该类型的lambda表达式都会被匹配到这个抽象方法。因为默认方法不算抽象方法,所以你也可以给你的函数式接口添加默认方法。我们可以将lambda表达式当作任意只包含一个抽象方法的接口类型,确保你的接口一定达到这个要求,你只需要给你的接口添加@Functionallnterface注解,编译器如果发现你标注了这个注解的接口有多于一个抽象方法的时候会报错的。示例如下:代码如下:@Functionallnterface interface Converter<F,T>{T convert(F from);}Converter<String,Integer>converter=from)->Integer.valueOf(from);Integer converted=converter.convert("123");System.out.printin(converted);//123需要注意如果@Functionallnterface如果没有指定,上面的代码也是对的。译者注将lambda表达式映射到一个单方法的接口上,这种做法在Java8之前就有别的语言实现,比如Rhino JavaScript解释器,如果一个函数参数接收一个单方法的接口而你传递的是一个function,Rhino 解释器会自动做一个单接口的实例到function的适配器,典型的应用场景有 org.w3c.dom.events.EventTarget的addEventListener 第二个参数EventListener。四、方法与构造函数引用前一节中的代码还可以通过静态方法引用来表示:代码如下:Converter<String,Integer>converter=Integer:valueOf;Integer converted=converter.convert("123");System.out.printin(converted);//123Java8允许你使用:关键字来传递方法或者构造函数引用,上面的代码展示了如何引用一个静态方法,我们也可以引用一个对象的方法:代码如下:converter=something::startsWith;String converted=converter.convert("Java");System.out.printin(converted);//"接下来看看构造函数是如何使用:关键字来引用的,首先我们定义一个包含多个构造函数的简单类:代码如下:代码如下:Converter<String,Integer> converter=Integer::valueOf;Integer converted=converter.convert("123");System.out.println(converted);//123Java8允许你使用::关键字来传递方法或者构造函数引用,上面的代码展示了如何引用一个静态方法,我们也可以引用一个对象的方法:代码如下:converter=something:startsWith;String converted=converter.convert("Java");system.out.println(converted);/∥"y"接下来看看构造函数是如何使用:关键字来引用的,首先我们定义一个包含多个构造函数的简单类:代码如下:class Person{String firstName;String lastName;Person)}Person(String firstName,String lastName){this.firstName =firstName;this.lastName=lastName;}}接下来我们指定一个用来创建Person对象的对象工厂接口:代码如下:interface PersonFactory{P create(String firstName,String lastName);}这里我们使用构造函数引用来将他们关联起来,而不是实现一个完整的工厂:代码如下:PersonFactorypersonFactory=Person::new;Person person=personFactory.create("Peter","Parker");我们只需要使用Person::new来获取Person类构造函数的引用,Java编译器会自动根据PersonFactory.create方法的签名来选择合适的构造函数。五、Lambda作用域在lambda表达式中访问外层作用域和老版本的匿名对象中的方式很相似。你可以直接访问标记了final的外层局部变量,或者实例的字段以及静态变量。六、访问局部变量我们可以直接在lambda表达式中访问外层的局部变量:代码如下:final int num=1;Converter<Integer,String>stringConverter=(from)->String.valueof(from +num);stringConverter.convert(2);//3但是和匿名对象不同的是,这里的变量num可以不用声明为final,该代码同样正确:代码如下:int num=1;Converter<Integer,String>stringConverter=(from)->String.valueof(from+num);stringConverter.conver(2);//3不过这里的num必须不可被后面的代码修改(即隐性的具有fina的语义),例如下面的就无法编译:代码如下:int num=1;Converter<Integer,String>stringConverter =(from)->string.valueof(from +num);num=3;在lambda表达式中试图修改num同样是不允许的。七、访问对象字段与静态变量和本地变量不同的是,lambda内部对于实例的字段以及静态变量是即可读又可写。该行为和匿名对象是一致的:代码如下:class Lambda4{static int outerStaticNum;int outerNum;void testScopes(){Converter<Integer,String>stringConverter1=(from)->{outerNum=23;return String.valueof(from);};Converter<Integer,String>stringConverter2=(from)->{outerStaticNum=72;return String.valueof(from);};}}八、访问接口的默认方法还记得第一节中的formula例子么,接口Formula定义了一个默认方法sqrt可以直接被formula的实例包括匿名对象访问到,但是在lambda表达式中这个是不行的。Lambda表达式中是无法访问到默认方法的,以下代码将无法编译:代码如下:Formula formula=(a)->sqrt(a*100);Built-in Functional InterfacesJDK 1.8API包含了很多内建的函数式接口,在老Java中常用到的比如Comparator或者Runnable接口,这些接口都增加了@Functionallnterface注解以便能用在lambda上。Java8APl同样还提供了很多全新的函数式接口来让工作更加方便,有一些接口是来自Google Guava库里的,即便你对这些很熟悉了,还是有必要看看这些是如何扩展到lambda上使用的。Predicate接口Predicate 接口只有一个参数,返回boolean类型。该接口包含多种默认方法来将Predicate组合成其他复杂的逻辑(比如:与,或,非):代码如下:Predicatepredicate=(s)->s.length()>0;predicate.test("foo");//true predicate.negate().test("foo");//false Predicate nonNull =Objects:nonNull;PredicateisNull=Objects:isNull;PredicateisEmpty=String:isEmpty;PredicateisNotEmpty=isEmpty.negate();Function接口Function 接口有一个参数并且返回一个结果,并附带了一些可以和其他函数组合的默认方法(compose,andThen):代码如下:Function<String,Integer> tolnteger=Integer:valueOf;Function<String,String>backToString=tolnteger.andThen(String:valueOf);backToString.apply("123");//"123"Supplier 接口Supplier 接口返回一个任意范型的值,和Function接口不同的是该接口没有任何参数代码如下:Supplier personSupplier=Person:new;personSupplier get();//new PersonConsumer 接口Consumer接口表示执行在单个参数上的操作。代码如下:Consumergreeter=(p)->System.out.println("Hello,"+p.firstName);greeter.accept(new Person("Luke","Skywalker"));Comparator 接口Comparator是老ava中的经典接口,Java8在此之上添加了多种默认方法:代码如下:Comparatorcomparator =(p1,p2)->p1.firstName.compareTo(p2.firstName);Person p1=new Person("John","Doe");Person p2=new Person("Alice","Wonderland");comparator.compare(p1,p2);//>0 comparator.reversed().compare(p1,p2);//<0optional接口Optional 不是函数是接口,这是个用来防止NullPointerException异常的辅助类型,这是下一届中将要用到的重要概念,现在先简单的看看这个接口能干什么:Optional被定义为一个简单的容器,其值可能是null或者不是null。在Java8之前一般某个函数应该返回非空对象但是偶尔却可能返回了null,而在Java8中,不推荐你返回null而是返回Optional。代码如下:Optionaloptional=Optional.of("bam");optional.isPresent();//true optional.get();//"bam"optional.orElse("fallback");//"bam"optional.ifPresent((s)->System.out.println(s.charAt(O));//"b"Stream接口java.util.Stream 表示能应用在一组元素上一次执行的操作序列。Stream操作分为中间操作或者最终操作两种,最终操作返回一特定类型的计算结果,而中间操作返回Stream本身,这样你就可以将多个操作依次串起来。Stream 的创建需要指定一个数据源,比如java.util.Collection的子类,List或者Set,Map不支持。Stream的操作可以串行执行或者并行执行。首先看看Stream是怎么用,首先创建实例代码的用到的数据List:代码如下:List stringCollection=new ArrayList<>();;stringCollection.add("ddd2");stringCollection.add("aaa2");stringCollection.add("bbb1");stringCollection.add("aaa1");stringCollection.add("bbb3");stringCollection.add("ccc");stringCollection.add("bbb2");stringCollection.add("ddd1");Java 8扩展了集合类,可以通过Collection.stream()或者Collection.parallelStream()来创建一个Stream。下面几节将详细解释常用的Stream操作:Filter 过滤过滤通过一个predicate接口来过滤并只保留符合条件的元素,该操作属于中间操作,所以我们可以在过滤后的结果来应用其他Stream操作(比如forEach)。forEach需要一个函数来对过滤后的元素依次执行。forEach是一个最终操作,所以我们不能在forEach之后来执行其他Stream操作。代码如下:stringCollection.stream().filter(s)->s.startsWith("a").forEach(System.out::println);//"aaa2","aaal"Sort 排序排序是一个中间操作,返回的是排序好后的Stream。如果你不指定一个自定义的Comparator则会使用默认排序。代码如下:stringCollection.stream().sorted().filter(s)->s.startsWith("a")).forEach(System.out:printin);//"aaa1","aaa2"需要注意的是,排序只创建了一个排列好后的Stream,而不会影响原有的数据源,排序之后原数据stringCollection是不会被修改的:代码如下:System.out.printin(stringCollection);//ddd2,aaa2,bbb1,aaal,bbb3,ccc,bbb2,ddd1Map映射中间操作map会将元素根据指定的Function接口来依次将元素转成另外的对象,下面的示例展示了将字符串转换为大写字符串。你也可以通过map来讲对象转换成其他类型,map返回的Stream类型是根据你map传递进去的函数的返回值决定的。代码如下:stringCollection.stream().map(String:toUpperCase).sorted(a,b)->b.compareTo(a)).forEach(System.out:printin);//"DDD2","DDD1","CCC","BBB3","BBB2","AAA2","AAA1"Map映射中间操作map会将元素根据指定的Function接口来依次将元素转成另外的对象,下面的示例展示了将字符串转换为大写字符串。你也可以通过map来讲对象转换成其他类型,map返回的Stream类型是根据你map传递进去的函数的返回值决定的。代码如下:stringCollection.stream().map(String:toUpperCase).sorted((a,b)->b.compareTo(a)).forEach(System.out:printin);//"DDD2","DDD1","CCC","BBB3","BBB2","AAA2","AAA1"Match 匹配Stream提供了多种匹配操作,允许检测指定的Predicate是否匹配整个Stream。所有的匹配操作都是最终操作,并返回一个boolean类型的值。代码如下:boolean anyStartsWithA=stringCollection.stream().anyMatch(s)->s.startsWith("a");System.out.printin(anystartsWithA);/∥true boolean allStartsWithA=stringCollection.stream().allMatch(s)->s.startsWith("a"));System.out.printin(allStartsWithA);//false boolean noneStartsWithZ=stringCollection.stream().noneMatch(s)->s.startsWith("z");System.out.printin(noneStartsWithZ);//trueCount计数计数是一个最终操作,返回Stream中元素的个数,返回值类型是long。代码如下:long startsWithB=stringCollection.stream().filter((s)->s.startsWith("b")).count();System.out.printin(startsWithB);//3Reduce规约这是一个最终操作,允许通过指定的函数来讲stream中的多个元素规约为一个元素,规越后的结果是通过Optional接口表示的:代码如下:Optional reduced=stringCollection.stream().sorted).reduce((51,52)->51+"#"+52);reduced.ifPresent(System.out::println);//"aaal#aaa2#bbb1#bbb2#bbb3#cCC#ddd1#ddd2"并行Streams前面提到过Stream有串行和并行两种,串行Stream上的操作是在一个线程中依次完成,而并行Stream则是在多个线程上同时执行。下面的例子展示了是如何通过并行Stream来提升性能:首先我们创建一个没有重复元素的大表:代码如下:int max=1000000; List values=new Arraylist<>(max); for(inti=0;i< max;i++){ UUID uuid=UUID. randomUUID(); values. add(uuid. toString());}然后我们计算一下排序这个Stream要耗时多久,串行排序:代码如下:long to=System. nanoTime(); long count=values. stream(). sorted(). count(); System. out. println(count); long t1=System. nanoTime(); long millis =TimeUnit. NANOSECONDS. toMillis(t1-to); System. out. println(String. format("sequential sort took:%d ms", millis);∥串行耗时:899ms并行排序:代码如下:long to=System.nanoTime();long count=values.parallelStream().sorted).count();System.out.println(count);long t1=System.nanoTime();long mills=TimeUnit.NANOSECONDS.toMillis(t1-to);System.out.printin(String.format("parallel sort took:%d ms",millis));//并行排序耗时:472ms上面两个代码几乎是一样的,但是并行版的快了50%之多,唯一需要做的改动就是将stream()改为parallelStream()。Map前面提到过,Map类型不支持stream,不过Map提供了一些新的有用的方法来处理一些日常任务。代码如下:Map<Integer,String>map=new HashMap<>);for(inti=0;i<10;i++){map.putlfAbsent(i,"val"+i);}map.forEach(id,val)>System.out.println(val);以上代码很容易理解,putlfAbsent不需要我们做额外的存在性检查,而forEach则接收一个Consumer接口来对map里的每一个键值对进行操作。下面的例子展示了map上的其他有用的函数:代码如下:map. computelfPresent(3,(num, val)->val+num); map. get(3);//val33map. computelfPresent(9,(num, val)->nul); map. containsKey(9);//falsemap. computelfAbsent(23, num->"val"+num); map. containsKey(23);//true map. computelfAbsent(3, num->"bam"); map. get(3);//val33接下来展示如何在Map里删除一个键值全都匹配的项:代码如下:map.remove(3,"val3");map.get(3);//val33map.remove(3,"val33");map.get(3);/∥null另外一个有用的方法:代码如下:map.getOrDefault(42,"not found");//not found对Map的元素做合并也变得很容易了:代码如下:map.getOrDefault(42,"not found");//not found对Map的元素做合并也变得很容易了:代码如下:map.merge(9,"val9",(value,newValue)->value.concat(newValue));map.get(9);//val9map.merge(9,"concat",(value,newValue)->value.concat(newValue);map.get(9);//val9concat Merge做的事情是如果键名不存在则插入,否则则对原键对应的值做合并操作并重新插入到map中。九、Date API Java 8在包java.time下包含了一组全新的时间日期APl。新的日期APl和开源Joda-Time库差不多,但又不完全一样,下面的例子展示了这组新APl里最重要的一些部分:Clock时钟Clock类提供了访问当前日期和时间的方法,Clock是时区敏感的,可以用来取代System.currentTimeMils0来获取当前的微秒数。某一个特定的时间点也可以使用Instant类来表示,Instant类也可以用来创建老的java.util.Date对象。代码如下:Clock clock=Clock.systemDefaultZone();long millis =clock.mils();Instant instant=clock.instant();Date legacyDate=Date.from(instant);//legacy java.util.DateTimezones时区在新API中时区使用Zoneld来表示。时区可以很方便的使用静态方法of来获取到。时区定义了到UTS时间的时间差,在Instant时间点对象到本地日期对象之间转换的时候是极其重要的。代码如下:System.out.println(Zoneld.getAvailableZonelds());//prints all available timezone ids Zoneld zone1=Zoneld.of("Europe/Berlin");Zoneld zone2=Zoneld.of("Brazil/East");System.out.println(zone1.getRules();System.out.printin(zone2.getRules());LocalTime本地时间LocalTime 定义了一个没有时区信息的时间,例如晚上10点,或者17:30:15。下面的例子使用前面代码创建的时区创建了两个本地时间。之后比较时间并以小时和分钟为单位计算两个时间的时间差:代码如下:LocalTime now1=LocalTime.now(zone1);LocalTime now2=LocalTime.now(zone2);System.out.println(now1.isBefore(now2);//false long hoursBetween=ChronoUnit.HOURS.between(now1,now2);long minutesBetween=ChronoUnit.MINUTES.between(now1,now2);System.out.println(hoursBetween);//-3 System.out.printin(minutesBetween);//-239LocalTime 提供了多种工厂方法来简化对象的创建,包括解析时间字符串。代码如下:LocalTime late =LocalTime.of(23,59,59);System.out.println(late);//23:59:59DateTimeFormatter germanFormatter=Date TimeFormatter.ofLocalizedTime(FormatStyle.SHORT).withLocale(Locale.GERMAN);LocalTime leetTime=LocalTime.parse("13:37",germanFormatter);System.out.printin(leetTime);//13:37LocalDate本地日期LocalDate表示了一个确切的日期,比如2014-03-11。该对象值是不可变的,用起来和LocalTime基本一致。下面的例子展示了如何给Date对象加减天/月/年。另外要注意的是这些对象是不可变的,操作返回的总是一个新实例。代码如下:LocalDate today=LocalDate.now();LocalDate tomorrow =today.plus(1,Chronounit.DAYS);LocalDate yesterday=tomorrow.minusDays(2);LocalDate independenceDay =LocalDate.of(2014,Month.JULY,4);DayofWeek dayofweek=independenceDay.getDayOfWeek();System.out.printin(dayofWeek);//FRIDAY 从字符串解析一个LocalDate类型和解析LocalTime一样简单:代码如下:DateTimeFormatter germanFormatter=Date TimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM).withLocale(Locale.GERMAN);LocalDate xmas =LocalDate.parse("24.12.2014",germanFormatter);System.out.println(xmas);//2014-12-24LocalDateTime 本地日期时间LocalDateTime同时表示了时间和日期,相当于前两节内容合并到一个对象上了。LocalDateTime和LocalTime还有LocalDate一样,都是不可变的。LocalDateTime提供了一些能访问具体字段的方法。代码如下:LocalDate Time sylvester=LocalDate Time.of(2014,Month.DECEMBER,31,23,59,59);DayofWeek dayofWeek=sylvester.getDayofWeek();System.out.println(dayOfWeek);//WEDNESDAY Month month=sylvester.getMonth();System.out.println(month);//DECEMBER long minuteOfDay=sylvester getLong(ChronoField.MINUTE_OF_DAY);System.out.println(minuteOfDay);//1439只要附加上时区信息,就可以将其转换为一个时间点Instant对象,Instant时间点对象可以很容易的转换为老式的java.util.Date。代码如下:Instant instant=sylvester.atZone(Zoneld.systemDefault().tolnstant();Date legacyDate=Date.from(instant);System.out.printin(legacyDate);//Wed Dec 31 23:59:59 CET 2014格式化LocalDateTime和格式化时间和日期一样的,除了使用预定义好的格式外,我们也可以自己定义格式:代码如下:DateTimeFormatter formatter=DateTimeFormatter.ofPattern("MMM dd,yyyy-HH:mm");LocalDateTime parsed=LocalDateTime.parse("Nov 03,2014-07:13",formatter);String string=formatter.format(parsed);System.out.println(string);//Nov 03,2014-07:13和java.text.NumberFormat不一样的是新版的DateTimeFormatter是不可变的,所以它是线程安全的。十、Annotation 注解在Java8中支持多重注解了,先看个例子来理解一下是什么意思。首先定义一个包装类Hints注解用来放置一组具体的Hint注解:代码如下:@interface Hints{Hint]value();}@Repeatable(Hints.class)@interface Hint{String value();}Java8允许我们把同一个类型的注解使用多次,只需要给该注解标注一下@Repeatable即可。例1:使用包装类当容器来存多个注解(老方法)代码如下:@Hints({@Hint("hint1"),@Hint("hint2")})class Person}例2:使用多重注解(新方法)代码如下:@Hint("hint1")@Hint("hint2")class Person}第二个例子里java编译器会隐性的帮你定义好@Hints注解,了解这一点有助于你用反射来获取这些信息:代码如下:Hint hint=Person. class. getAnnotation(Hint. class); System. out. println(hint);//null Hints hints1=Person. class. getAnnotation(Hints. class); System. out. printin(hints1. value(). length);//2Hint[]hints2=Person.class.getAnnotationsBy Type(Hint.class);System.out.printin(hints2.length);//2即便我们没有在Person类上定义@Hints注解,我们还是可以通过 getAnnotation(Hints.class)来获取@Hints注解,更加方便的方法是使用getAnnotationsBy Type 可以直接获取到所有的@Hint注解。另外ava8的注解还增加到两种新的target上了:代码如下:@Target{ElementType.TYPE_PARAMETER,ElementType.TYPE_USE})@interface MyAnnotation}关于Java8的新特性就写到这了,肯定还有更多的特性等待发掘。JDK1.8里还有很多很有用的东西,比如Arrays.parallelSort,StampedLock和CompletableFuture等等。
2022年03月07日
490 阅读
0 评论
7 点赞
2022-03-06
Docker安装Kibana
Docker安装Kibana一、拉取镜像,可视化检索数据docker pull kibana:7.4.2二、启动运行容器docker run --name kibana -e ELASTICSEARCH_HOSTS=http://192.168.56.10:9200 -p 5601:5601 \ -d kibana:7.4.2设置随机自启动docker update --restart=always kibana 注意:更改自己的ip,es端口,以及和es版本号对应。
2022年03月06日
206 阅读
0 评论
4 点赞
2022-03-06
Docker 安装ES
Docker 安装ES一、下载镜像文件,存储和检索数据docker pull elasticsearch:7.4.2二、创建实例创建文件,用于映射mkdir -p /mydata/elasticsearch/config mkdir -p /mydata/elasticsearch/data echo "http.host: 0.0.0.0" >> /mydata/elasticsearch/config/elasticsearch.yml设置权限chmod -R 777 /mydata/elasticsearch/三、启动配置镜像docker run --name elasticsearch -p 9200:9200 -p 9300:9300 \ -e "discovery.type=single-node" \ -e ES_JAVA_OPTS="-Xms64m -Xmx512m" \ -v /mydata/elasticsearch/config/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml \ -v /mydata/elasticsearch/data:/usr/share/elasticsearch/data \ -v /mydata/elasticsearch/plugins:/usr/share/elasticsearch/plugins \ -d elasticsearch:7.4.2设置随机自启动docker update --restart=always elasticsearch 以后再外面装好插件重启即可;特别注意:-e ES_JAVA_OPTS="-Xms64m -Xmx256m" \ 测试环境下,设置ES 的初始内存和最大内存,否则导致过大启动不了ES
2022年03月06日
236 阅读
0 评论
3 点赞
2022-03-06
Docker安装Redis
docker安装redis一、docker拉取redis镜像docker pull redis二、创建实例并启动创建映射配置文件路径mkdir -p /mydata/redis/conf touch /mydata/redis/conf/redis.conf创建启动docker run -p 6379:6379 --name redis -v /mydata/redis/data:/data \ -v /mydata/redis/conf/redis.conf:/etc/redis/redis.conf \ -d redis redis-server /etc/redis/redis.confredis 自描述文件:https://raw.githubusercontent.com/antirez/redis/4.0/redis.conf三、使用redis 镜像执行redis-cli 命令连接docker exec -it redis redis-cli四、设置随docker启动redis自动启动docker update --restart=always redis更多配置,请参考redis官网文档,https://redis.io/documentation。
2022年03月06日
365 阅读
0 评论
4 点赞
2022-03-06
fatal: not a git repository (or any of the parent directories): .git
$ git add .fatal: not a git repository (or any of the parent directories): .git解决方案:提示说没有.git这样一个目录git init
2022年03月06日
559 阅读
0 评论
3 点赞
2022-03-06
vue学习
一、vue的基本使用<!DOCTYPE html> <head> <title>vue的基本使用</title> </head> <body> <div id="app"> <!-- 模板语法--插值{{}} --> <!-- 优先加载template里面的内容,如果为空也加载#app,在加载#app --> <h1>{{msg}}</h1> </div> <script type="text/javascript" src="./node_modules/vue/dist/vue.js"></script> <script type="text/javascript"> new Vue({ el: '#app', data: { msg: '黄瓜' }, template: '<div><h5>{{msg}}</h5></div>' }); </script> </body> </html>二、vue指令<!DOCTYPE html> <head> <title>vue指令</title> <style> .box{ width: 300px; height: 300px; background-color: red; } .active{ background-color: green; } </style> </head> <body> <div id="app">{{msg}}</div> <script type="text/javascript" src="./node_modules/vue/dist/vue.js"></script> <script type="text/javascript"> var vm = new Vue({ el: "#app", data:function(){ return{ msg: 'vue指令', msg2: 'vue的3种{{}}插值,1、{{}} 2、v-text="" 3\v-html=""', isShow: true, isActive: true, frut: [ {id:1,name:"苹果",price:20}, {id:2,name:"火龙果",price:40}, {id:3,name:"西瓜",price:30}, {id:4,name:"荔枝",price:60} ], person:{ name:"zhangsan", age:30, sex:"boy" } } }, template: '<div>{{msg}} <p v-text="msg"></p> <p v-html="msg2"></p> <div v-if="isShow">v-if的使用</div> <div v-if="Math.random()>0.5">显示</div><div v-else>隐藏</div> <div v-show="true">v-show显示</div> <div class="box" v-on:click="clickHander" v-bind:class="{active:isActive}"></div> <ul><li v-for="(item,index) in frut"><h1>{{index}}</h1><h3>{{item.id}}</h3><h2>{{item.name}}</h2><h1>{{item.price}}</h1></li></ul> <ul><li v-for="(value,key) in person">{{value}}==={{key}}</li></ul> </div>', methods:{ clickHander(e){ console.log(this); this.isActive=!this.isActive; } } }); console.log(vm); </script> </body> </html>三、vue的双向绑定<!DOCTYPE html> <head> <title>vue的双向绑定</title> </head> <body> <div id="app"> <input type="text" v-model="msg"> <h3 >{{msg}}</h3> </div> <script type="text/javascript" src="./node_modules/vue/dist/vue.js"></script> <script type="text/javascript"> new Vue({ el:"#app", data:function(){ return{ msg:"Hello World" } }, template:'', methods:{ } }); </script> </body> </html>四、局部组件的创建<!DOCTYPE html> <head> <title>vue局部组件的创建</title> </head> <body> <div id="app"><h1>{{msg}}</h1></div> <script type="text/javascript" src="./node_modules/vue/dist/vue.js"></script> <script type="text/javascript"> var App={ data(){ return { } }, template:'<div>VUE Hello </div>' } new Vue({ el:"#app", data(){ return{ msg:"Hello ---- World" } }, template:'<App/>', methods:{ }, components:{ App } }); </script> </body> </html>五、全局组件<!DOCTYPE html> <head> <title>vue全局组件的创建</title> </head> <body> <div id="app"></div> <script type="text/javascript" src="./node_modules/vue/dist/vue.js"></script> <script type="text/javascript"> Vue.component('Vquanju',{ data(){ return{ } }, template:'<div><button>全局组件按钮</button></div>' }); var Vjubu={ data(){ return{ } }, template:'<div><h2>我是局部组件</h2><Vquanju/></div>' } var Vju={ data(){ return{ } }, template:'<div><h2>我是局部组件2</h2><Vquanju/></div>' } new Vue({ el: "#app", data(){ return { } }, template: '<div><Vjubu/><Vju/></div>', components:{ Vjubu, Vju } }); </script> </body> </html>六、通过prop往子组件通信<!DOCTYPE html> <head> <title>通过prop往子组件通信</title> </head> <div id="app">{{msg}}</div> <script type="text/javascript" src="./node_modules/vue/dist/vue.js"></script> <script type="text/javascript"> Vue.component('Vchild',{ data(){ return{ } }, template:'<div><p>我是子组件<p/><input type="text" v-model="bdata" ></input></div>', props:['bdata'] }); Vue.component('Vparent',{ data(){ return{ bmsg: 'Hello,绑定数据' } }, template:'<div><p>我是父组件</p><Vchild :bdata="bmsg"/></div>' }); var App={ data(){ return{ } }, template:'<h1><Vparent/></h1>' } new Vue({ el: '#app', data(){ return{ msg: '好罗' } }, components:{ App }, template:'<App/>' }); </script> </html>七、通过事件向子组件发送消息<!DOCTYPE html> <head> <title>通过prop往子组件通信</title> </head> <div id="app">{{msg}}</div> <script type="text/javascript" src="./node_modules/vue/dist/vue.js"></script> <script type="text/javascript"> Vue.component('Vchild',{ data(){ return{ newData: '' } }, template:'<div><p>我是子组件{{childData}}<p/><input type="text" v-model="newData" @input="changeValue(newData)" /></div>', props:['childData'], methods:{ changeValue(val){ this.$emit('childHandler',val); } } }); Vue.component('Vparent',{ data(){ return{ bmsg: 'Hello,绑定数据' } }, template:'<div><p>我是父组件</p><Vchild :childData="bmsg" @childHandler="childHandler"/></div>', methods:{ childHandler(val){ console.log(val+"-------------------") } } }); var App={ data(){ return{ } }, template:'<h1><Vparent/></h1>' } new Vue({ el: '#app', data(){ return{ msg: '好罗' } }, components:{ App }, template:'<App/>' }); </script> </html>八、全局组件的使用<!DOCTYPE html> <head> <title>vue全局组件的创建</title> <style> .buttons{ display: inline-block; line-height: 1; white-space: nowrap; cursor: pointer; background: #fff; border: 1px solid #dcdfe6; color: #606266; -webkit-appearance: none; text-align: center; box-sizing: border-box; outline: none; margin: 0; transition: .1s; font-weight: 500; padding: 12px 20px; font-size: 14px; border-radius: 4px; } .successs{ color: #fff; background-color: #67c23a; border-color: #67c23a; } .defaults{ color: #fff; background-color: #409eff; border-color: #409eff; } </style> </head> <body> <div id="app"></div> <script type="text/javascript" src="./node_modules/vue/dist/vue.js"></script> <script type="text/javascript"> Vue.component('Vquanju',{ data(){ return{ } }, template:'<div><button class="buttons" :class="type">全局组件按钮<slot></slot></button></div>', props:['type'] }); //利用父向子传值,自己封装组件,例如按钮 var Vjubu={ data(){ return{ } }, template:'<div><h2>我是局部组件 </h2><Vquanju>slot覆盖内容</Vquanju> <Vquanju type="successs">默认</Vquanju > <Vquanju type="defaults">成功</Vquanju></div>', } var Vju={ data(){ return{ } }, template:'<div><h2>我是局部组件2</h2><Vquanju/></div>' } new Vue({ el: "#app", data(){ return { } }, template: '<div><Vjubu/><Vju/></div>', components:{ Vjubu, Vju } }); </script> </body> </html>
2022年03月06日
257 阅读
0 评论
2 点赞
2022-03-06
ES6语法
ES6语法一、let<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <script> // { // //var 声明的变量往往会跨域 // //let 声明的变量有严格局部作用域 // var a = 1; // let b = 2; // } // console.log(a); //1 // console.log(b); //Uncaught ReferenceError: b is not defined // //var 可以声明多次 // //let 只能声明一次 // var m = 3; // var m = 4; // let n = 5; // // let n=5; // console.log(m); //3 // console.log(n); //Identifier 'n' has already been declared // //var 会变量提升 // //let 不存在变量提升 // console.log(x); //undefined // var x = 10; // console.log(y); //Cannot access 'y' before initialization // let y = 20; // // const 用于定义常量 // //1、声明之后不允许改变。 // //2、一旦声明必须初始化,否则会报错 // const a = 1; // a = 3; //Assignment to constant variable. </script> </body> </html>二、解构表达式<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <script> // //数组结构 // let arr =[1,2,3]; // // let a = arr[0]; // // let b = arr[1]; // // let c = arr[2]; // //快速复制 // let [a,b,c] = arr; // console.log(a,b,c); // //对象结构 // const person = { // name: "jack", // age: 21, // language: ['java','js','css'] // } // // const name = person.name; // // const age = person.age; // // const language = person.language; // //新语法 // const {name:abc, age, language} = person; // console.log(abc,age,language); // //字符串扩展 // let str ="hello.vue"; // console.log(str.startsWith("hello")); // console.log(str.endsWith(".vue")); // console.log(str.includes("e")); // console.log(str.includes("hello")); // //字符串模板 // let ss = // ` // <div> // <span>Hello World</span> // </div> // `; // console.log(ss); //字符串插入变量和表达式。变量名写在${}中,在${}中可以放入JavaScript表达式。注意符号是·不是单引号 function fun(){ return "这是一个函数"; } let abc = "zhangsan"; let age = 20; let info = `我是${abc},今年${age+10}岁了,我想说:${fun()}`; console.log(abc,age,info); </script> </body> </html>三、函数优化<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <script> // //在SE6,我们无法给一个函数参数设置默认值,只能采用变通写法; // function add(a, b){ // //判断是否为空,为空就给默认值1 // b = b || 1; // return a + b; // } // console.log(add(10)); // //现在可以这么写:直接给参数写上默认值,没传就会自动使用默认值 // function add2(a, b = 1){ // return a + b; // } // console.log(add2(10)); // //不定参数 // function fun(... values){ // console.log(values.length); // } // console.log(fun("1111","22",33)); // //箭头函数 // // var print = function (obj){ // // console.log(obj); // // } // // console.log(print("Hello")); // //现在写法,一个参数 // var print = obj => console.log(obj); // print("China") // //多个参数 // var sum = function(a, b){ // return a + b; // } // console.log(sum(1,9)); // var sum2 = (a, b) => a+b; // console.log(sum2(1,9)); // //以前写法 // var sum3 = function(a, b){ // c = a +b; //多了一步操作 // return a + c; // } // console.log(sum3(1,9)); // //新写法 // var sum4 = (a, b) =>{ // c = a + b; // return a + c; // } // console.log(sum4(1,9)); //实战:箭头函数结合解构表达式 const person = { name: "jack", age: 20, language: ['java','js','css'] }; function hello(person){ console.log("你好:"+person.name); } hello(person); var hello2 = (person)=>console.log("你好哟:"+person.name); hello2(person); //箭头函数+解构表达式 var hello3 = ({name})=>console.log("你好哟:"+ name); hello3(person); </script> </body> </html>四、对象优化<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <script> // const person = { // name: "jack", // age : 20, // fun : ['jva','css','html'] // }; // console.log(Object.keys(person)); // console.log(Object.values(person)); // console.log(Object.entries(person)); // const a ={aa: 1}; // const b ={bb: 2}; // const c ={cc: 3}; // console.log(Object.assign(a,b,c)); // //声明对象简写 // const name ="zhangsan"; // const age =10; // const person = {name, age}; // console.log(person); // //对象大的函数属性简写 // let person = { // name: "jack", // //以前 // eat: function(food){ // console.log(this.name+ "在吃"+ food); // }, // //箭头函数this不能使用 // eat2: food => { // console.log(this.name+ "在吃"+ food); // }, // eat3: food => { // console.log(person.name+ "在吃"+ food); // }, // eat4(food){ // console.log(person.name+ "在吃"+ food); // } // } // person.eat("香蕉"); // person.eat2("苹果"); // person.eat3("草莓"); // person.eat3("芒果"); //对象拓展运算符 //1、拷贝对象(深拷贝) let p1 = {name: "zhangsan", age: 19}; let p2 = {... p1}; console.log(p2) //2、合并对象 const a ={name: "lisi"} const b = {age: 20}; //后面的name会覆盖之前的name let person = {name: "wangwu"}; person = {...a, ... b}; console.log(person); </script> </body> </html>五、map和reduce<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <script> //数组中新增了map和reduce方法。 //map():接收一个函数,将原数组中的所有袁术用这个函数处理后放入新数组返回。 let arr = ['1','2','-3','4']; // arr = arr.map((item)=>{ // return item * 2; // }); arr = arr.map(item => item*2); console.log(arr); //reduce()为数组中的每一个袁术一次执行回调函数,不包括数组中被删除或从未被赋值的元素。\ //arr.reduce(callback,[initialValue]) /** * 1、previousValue(上一次调用回调返回的值,或者是提供的初始值(initialValue)) * 2、currentValue(数组中当前被处理的元素) * 3、index(当前元素在数组中的索引) * 4、array(调用reduce的数组) **/ let result = arr.reduce((a,b )=>{ console.log("上一次处理后:"+a); console.log("当前正在处理:"+b); return a+b; },100); console.log(result); </script> </body> </html>六、promise<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script> </head> <body> <!-- 案例:用户登录,并展示该用户的各科成绩。在页面发送两次请求: 1. 查询用户,查询成功说明可以登录 2. 查询用户成功,查询科目 3. 根据科目的查询结果,获取去成绩 分析:此时后台应该提供三个接口,一个提供用户查询接口,一个提供科目的接口,一个提 供各科成绩的接口,为了渲染方便,最好响应json 数据。在这里就不编写后台接口了,而 是提供三个json 文件,直接提供json 数据,模拟后台接口: --> <script> //原始写法 // $.ajax({ // url: "mock/user.json", // success(data) { // console.log("查询用户:", data); // $.ajax({ // url: `mock/user_corse_${data.id}.json`, // success(data) { // console.log("查询到课程:", data); // $.ajax({ // url: `mock/corse_score_${data.id}.json`, // success(data) { // console.log("查询到分数:", data); // }, // error(error) { // console.log("出现异常了:" + error); // } // }); // }, // error(error) { // console.log("出现异常了:" + error); // } // }); // }, // error(error) { // console.log("出现异常了:" + error); // } // }); //promise写法 // const promise = new Promise(function (resolve, reject) { // // 执行异步操作 // if (/* 异步操作成功*/) { // resolve(value);// 调用resolve,代表Promise 将返回成功的结果 // } else { // reject(error);// 调用reject,代表Promise 会返回失败结果 // } // }); // const promise = new Promise((resolve, reject) => { // // 执行异步操作 // if (/* 异步操作成功*/) { // resolve(value);// 调用resolve,代表Promise 将返回成功的结果 // } else { // reject(error);// 调用reject,代表Promise 会返回失败结果 // } // }); // promise.then(function (value) { // // 异步执行成功后的回调 // }).catch(function (error) { // // 异步执行失败后的回调 // }) // new Promise((resolve, reject) => { // $.ajax({ // url: "mock/user.json", // success(data) { // console.log("查询用户:", data); // resolve(data.id); // }, // error(error) { // console.log("出现异常了:" + error); // } // }); // }).then((userId) => { // return new Promise((resolve, reject) => { // $.ajax({ // url: `mock/user_corse_${userId}.json`, // success(data) { // console.log("查询到课程:", data); // resolve(data.id); // }, // error(error) { // console.log("出现异常了:" + error); // } // }); // }); // }).then((corseId) => { // console.log(corseId); // $.ajax({ // url: `mock/corse_score_${corseId}.json`, // success(data) { // console.log("查询到分数:", data); // }, // error(error) { // console.log("出现异常了:" + error); // } // }); // }); // promise优化处理后写法 let get = function (url, data) { // 实际开发中会单独放到common.js 中 return new Promise((resolve, reject) => { $.ajax({ url: url, type: "GET", data: data, success(result) { resolve(result); }, error(error) { reject(error); } }); }) } // 使用封装的get 方法,实现查询分数 get("mock/user.json").then((result) => { console.log("查询用户~~:", result); return get(`mock/user_corse_${result.id}.json`); }).then((result) => { console.log("查询到课程~~:", result); return get(`mock/corse_score_${result.id}.json`) }).then((result) => { console.log("查询到分数~~:", result); }).catch(() => { console.log("出现异常了~~:" + error); }); </script> </body> </html>七、模块化<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <!-- 模块化就是把代码进行拆分,方便重复利用。类似java 中的导包:要使用一个包,必须先 导包。而JS 中没有包的概念,换来的是模块。 模块功能主要由两个命令构成:`export`和`import`。 `export`命令用于规定模块的对外接口。 `import`命令用于导入其他模块提供的功能。 --> </body> </html>
2022年03月06日
273 阅读
0 评论
1 点赞
2022-03-06
免费 字体 可商用 合集 900套
免费 字体 可商用 合集 900套{cloud title="免费 字体 可商用 合集 900套" type="bd" url="https://pan.baidu.com/s/1dkeXZ4PKgSXvJU9ZMc9rkg?pwd=9tbn" password="9tbn"/}
2022年03月06日
275 阅读
0 评论
1 点赞
2022-03-03
springboot整合redis
适合当如缓存场景:即时性、数据一致性要求不高的。访问量大且更新频率不高的数据(读多,写少)承担持久化工作。读模式缓存使用流程:凡是放入缓存中的数据,我们应该指定过期时间,使其可以再系统即使没有主动更新数据也能自动触发数据加载进缓存的流程。避免业务崩溃导致的数据永久不一致问题。解决分布式缓存中本地缓存导致数据不一致问题,可以使用redis中间件解决。springboot整合redis1、引入springboot整合的start:pom文件引入依赖 <!--redis--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>引入依赖之后就会有RedisAutoConfiguration,里面可以看的到redis的配置文件Redis.Properties.@Configuration( proxyBeanMethods = false ) @ConditionalOnClass({RedisOperations.class}) @EnableConfigurationProperties({RedisProperties.class}) @Import({LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class}) public class RedisAutoConfiguration { public RedisAutoConfiguration() { } @Bean @ConditionalOnMissingBean( name = {"redisTemplate"} ) @ConditionalOnSingleCandidate(RedisConnectionFactory.class) public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) { RedisTemplate<Object, Object> template = new RedisTemplate(); template.setConnectionFactory(redisConnectionFactory); return template; } @Bean @ConditionalOnMissingBean @ConditionalOnSingleCandidate(RedisConnectionFactory.class) public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) { return new StringRedisTemplate(redisConnectionFactory); } }Redis.Properties文件里面包含了redis的配置信息:@ConfigurationProperties( prefix = "spring.redis" ) public class RedisProperties { private int database = 0; private String url; private String host = "localhost"; private String username; private String password; private int port = 6379; private boolean ssl; private Duration timeout; private Duration connectTimeout; private String clientName; private RedisProperties.ClientType clientType; private RedisProperties.Sentinel sentinel; private RedisProperties.Cluster cluster; private final RedisProperties.Jedis jedis = new RedisProperties.Jedis(); private final RedisProperties.Lettuce lettuce = new RedisProperties.Lettuce();2、redis配置,可以根据上面的Redis.Properties类型,再applicatioin.yml中配置相关属性。spring: redis: host: 127.0.0.1 port: 6379上面一个最简单的redis配置就完成了。3、使用redis由上面的RedisAutoConfiguration自动配置类,可以看到自动装配了RedisTemplate<Object, Object>、StringRedisTemplate。RedisTemplate<Object, Object>:一般是字符串的Key,和序列化后的字符串value。由于使用String类型的key、value较多,所以还提供了StringRedisTemplate。public class StringRedisTemplate extends RedisTemplate<String, String> { public StringRedisTemplate() { this.setKeySerializer(RedisSerializer.string()); this.setValueSerializer(RedisSerializer.string()); this.setHashKeySerializer(RedisSerializer.string()); this.setHashValueSerializer(RedisSerializer.string()); } public StringRedisTemplate(RedisConnectionFactory connectionFactory) { this(); this.setConnectionFactory(connectionFactory); this.afterPropertiesSet(); } protected RedisConnection preProcessConnection(RedisConnection connection, boolean existingConnection) { return new DefaultStringRedisConnection(connection); } }StringRedisTemplate也是继承了RedisTemplate<String, String>,都是字符串的key,value.this.setKeySerializer(RedisSerializer.string());this.setValueSerializer(RedisSerializer.string());this.setHashKeySerializer(RedisSerializer.string());this.setHashValueSerializer(RedisSerializer.string());上面的key、value都是用RedisSerializer.string()类型序列化。使用配置好的StringRedisTemplate。StringRedisTemplate的value由多种不同类型的值。保存获取值: @Test public void testStringRedisTemplate(){ ValueOperations<String, String> opsForValue = stringRedisTemplate.opsForValue(); opsForValue.set("hello", "world"+ UUID.randomUUID().toString()); String hello = opsForValue.get("hello"); System.out.println("之前保存值为:"+hello); }输出结果:之前保存值为:world1e9f395e-67b8-4077-9912-c690c7da0f06redis数据库的值:注意保存获取时,key必须是一样的。value一般是序列化后的json字符串,因为json字符串是跨语言跨平台的。vlaue存复杂对象时,序列化可以用alibaba的fastjson。Map<String,List<UserEntity>> data:要存入redis的复杂数据,通过序列化后存在redis。 String s = JSON.toJSONString(data); ValueOperations<String, String> ops = redisTemplate.opsForValue(); ops.set("mydata",s);同样获取的json数据也需要反序列化后才能使用:比如转化成一个复杂数据格式String jsonStr= ops.get("mydata"); Map<String,List<UserEntity>> result = JSON.parseObject(jsonStr, new TypeReference<Map<String,List<UserEntity>>>() {});注意:springboot2.0后默认使用lettuce作为操作redis的客户端,使用netty进行网络通信。但是lettuce的bug导致netty容易堆外内存溢出。netty如果没有指定堆外内存,默认使用-Xmx300m。可以通过-Dio.netty.maxDirectMemory设置堆外内存大小,但是始终会出现堆外内存溢出。 private static void incrementMemoryCounter(int capacity) { if (DIRECT_MEMORY_COUNTER != null) { long newUsedMemory = DIRECT_MEMORY_COUNTER.addAndGet((long)capacity); if (newUsedMemory > DIRECT_MEMORY_LIMIT) { DIRECT_MEMORY_COUNTER.addAndGet((long)(-capacity)); throw new OutOfDirectMemoryError("failed to allocate " + capacity + " byte(s) of direct memory (used: " + (newUsedMemory - (long)capacity) + ", max: " + DIRECT_MEMORY_LIMIT + ')'); } } }解决方案:不能只用-Dio.netty.maxDirectMemory去调大内存。1、升级netty客户端。2、切换使用jedis如何使用jedisredis默认使用的是lettuce,因此需要排除掉lettuce,引入jedis。 <!--引入redis--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> <exclusions> <exclusion><!--排除lettuce--> <groupId>io.lettuce</groupId> <artifactId>lettuce-core</artifactId> </exclusion> </exclusions> </dependency> <!--引入jedis--> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> </dependency>lettuce、jedis都是操作redis的底层客户端。RedisTemplate是对lettuce、jedis对redis操作的再次封装,以后操作都可以用RedisTemplate进行操作redis。通过redis的自动装配可以看到是引入了lettuce,jedis的。@Import({LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class}) public class RedisAutoConfiguration {通过jedis可以看到。class JedisConnectionConfiguration extends RedisConnectionConfiguration { JedisConnectionConfiguration(RedisProperties properties, ObjectProvider<RedisSentinelConfiguration> sentinelConfiguration, ObjectProvider<RedisClusterConfiguration> clusterConfiguration) { super(properties, sentinelConfiguration, clusterConfiguration); } @Bean JedisConnectionFactory redisConnectionFactory(ObjectProvider<JedisClientConfigurationBuilderCustomizer> builderCustomizers) { return this.createJedisConnectionFactory(builderCustomizers); }无论是lettuce、jedis最后都会注入JedisConnectionFactory,就是redis要用的。高并发下缓存失效问题-缓存穿透:缓存穿透:指查询一个一定不存的数据,由于缓存是不命中,将去查询数据库,但是数据库也没有此记录,我们将这此查询的null写入缓存,这将导致这个不存在的数据每次请求都要到存储层查询,失去了缓存的意义。风险:利用不存在的数据进行攻击,,数据库瞬时压力增大,最终导致崩溃。解决方案:null结果也缓存到redis,并加入短暂的过期时间。高并发下缓存失效-缓存雪崩缓存雪崩:指在我们设置缓存时key采用了相同的过期时间,导致缓存在某一时间同时失效,请求全部转发到DB,DB瞬时压力过大雪崩。解决方案:原有的失效时间基础上增加一个随机值,比如11-5分分钟随机,这样每一个缓存的过期时间重复概率就会降低,很难引发集体失效。高并发下缓存失效问题-缓存击穿缓存穿透:对于一些设置了过期时间的key,如果这些key可能会在某些时间点被超高并发地访问,是一种费用“热点”的数据。如果这个key在大量请求同时进来前正好失效,那么所有对这个key的数据查询都落到DB上,我们称为缓存击穿。解决方案:加锁大量并发只让一个去查,其他人等待,查到以后释放锁,其它人获取到锁,先差缓存,就会有数据了,而不用去DB查询。总结:1、解决缓存穿透:空结果也缓存。2、解决缓存雪崩:设置过期时间(随机值)。3、解决缓存击穿:加锁。例如:ops.set("RecordData",null, 1, TimeUnit.DAYS); //null值也缓存 ops.set("RecordData",JSON.toJSONString(recordEntity), 1, TimeUnit.DAYS); 解锁:1、通过synchronized/Lock本地锁。2、分布式锁解决方案Redisson。但是本地锁synchronized/Lock不能解决分布式带来的击穿问题。因此分布式还得用Redisson进行加锁解决。redisson分布式锁,可参考另一篇redisson分布式锁。 //本地加锁 public RecordEntity getEntityByIdByRedis(Long id){ synchronized (this){ ValueOperations<String, String> ops = redisTemplate.opsForValue(); String recordData = ops.get("RecordData"); if(recordData!=null){ System.out.println("从数据库查询数据之前,有缓存数据。。。。"); return JSON.parseObject(recordData, new TypeReference<RecordEntity>() {}); } System.out.println("从数据库查询数据...."); RecordEntity recordEntity = baseMapper.selectById(id); ops.set("RecordData",JSON.toJSONString(recordEntity), 1, TimeUnit.DAYS); return recordEntity; } } //redis分布式锁 public RecordEntity getEntityByIdByFbs(Long id){ String uuid = UUID.randomUUID().toString(); ValueOperations<String, String> ops = redisTemplate.opsForValue(); //保证原子性,加锁同时设置过期时间 Boolean lock = ops.setIfAbsent("lock", uuid, 30, TimeUnit.SECONDS); if(lock){ System.out.println("获取分布式锁成功。。。。。"); RecordEntity recordEntity = null; try{ recordEntity = getEntityById(id); }finally { //删除锁保证原子性,使用脚本 String script ="if redis.call(\"get\",KEYS[1]) == ARGV[1]\n" + "then\n" + " return redis.call(\"del\",KEYS[1])\n" + "else\n" + " return 0\n" + "end"; Long lock1 = redisTemplate.execute(new DefaultRedisScript<Long>(script, Long.class), Arrays.asList("lock"), uuid); } return recordEntity; }else{ System.out.println("获取分布式锁失败,等待重试。。。。"); //重试,可以等待sleep一下 try{ Thread.sleep(200); }catch (Exception e){ } return getEntityByIdByFbs(id); } } //业务代码 public RecordEntity getEntityById(Long id){ ValueOperations<String, String> ops = redisTemplate.opsForValue(); String recordData = ops.get("RecordData"); if(recordData!=null){ System.out.println("从数据库查询数据之前,有缓存数据。。。。"); return JSON.parseObject(recordData, new TypeReference<RecordEntity>() {}); } System.out.println("从数据库查询数据...."); RecordEntity recordEntity = baseMapper.selectById(id); ops.set("RecordData",JSON.toJSONString(recordEntity), 1, TimeUnit.DAYS); return recordEntity; } //Redisson分布式锁 public RecordEntity getEntityByIdByFbsRedisson(Long id){ //保证原子性,加锁同时设置过期时间 RLock lock = redissonClient.getLock("RecordData-lock"); lock.lock(); RecordEntity recordEntity = null; try { System.out.println("获取分布式锁成功。。。。。"); recordEntity = getEntityById(id); }finally { System.out.println("释放锁成功。。。。。"); lock.unlock(); } return recordEntity; } // 使用缓存注解方式 @Override @Cacheable(value = {"record"},key = "#root.method.name") public RecordEntity getRecordAllInfoById(Long id) { //未使用注解缓存方案 // RecordEntity recordEntity = null; // // ValueOperations<String, String> forValue = redisTemplate.opsForValue(); // String recordData = forValue.get("RecordData"); // if(recordData==null){ // System.out.println("缓存没数据,执行查询数据库方法。。。。"); // recordEntity = getEntityByIdByFbsRedisson(id); // }else{ // System.out.println("从缓存中获取数据。。。。。"); // recordEntity = JSON.parseObject(recordData, new TypeReference<RecordEntity>() { // }); // } //使用注解缓存后 RecordEntity recordEntity = getEntityById(id); if(recordEntity!=null){ Long categoryId = recordEntity.getCategoryId(); Long[] catelogPath = findCatelogPath(categoryId); recordEntity.setCatelogPath(catelogPath); } return recordEntity; }利用redis的set NX实现分布式redisson分布式锁,实现原理就是利用redis的set nx实现的。SET key value [EX seconds] [PX milliseconds] [NX|XX]分布式锁原理可以看redis官方文档set nx命令。更多分布式锁可参考,另一个篇springboot整合分布式锁redisson。
2022年03月03日
263 阅读
0 评论
6 点赞
1
...
16
17
18
...
21